Gesture Zoom and Pan in a VCL Windows application - zooming

I'm trying to zoom and pan an image (TImage) with an object (TShape) on it in a VCL Windows application running on an all-in-one pc with touch display.
I put the image in a panel (TPanel) so when I zoom it, it remains always inside the panel.
Then I put a shape (TShape) on the image.
What I would like to get is to zoom and pan the image and the shape should zoom and move with the image.
I started from the Embarcadero sample "Mobile Snippets - InteractiveGestures - ImageZoom" and with the following code I managed to do image zoom and pan with two fingers:
void __fastcall TForm1::Image1Gesture(TObject *Sender, const TGestureEventInfo &EventInfo, bool &Handled) {
TPointF LImageCenter;
double expance;
TPointF movement;
try {
if (EventInfo.GestureID == static_cast<short>(Vcl::Controls::igiZoom)) {
if (!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfBegin) &&
!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfEnd)) {
// zoom the image
TImage * LImage = dynamic_cast<TImage*>(Image1);
LImageCenter.x = LImage->Left + (LImage->Width / 2);
LImageCenter.y = LImage->Top + (LImage->Height / 2);
expance = EventInfo.Distance - FLastDistance;
if (((LImage->Width + (expance * AR))> MIN_DIM) &&
((LImage->Height + (expance))> MIN_DIM)) {
LImage->Width = (int)(LImage->Width + (expance * AR));
LImage->Height = (int)(LImage->Width / AR );
}
LImage->Left = LImageCenter.X - LImage->Width / 2;
LImage->Top = LImageCenter.Y - LImage->Height / 2;
}
FLastDistance = EventInfo.Distance;
}
else if (EventInfo.GestureID == static_cast<short>(Vcl::Controls::igiPan)) {
if (!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfBegin) &&
!EventInfo.Flags.Contains(TInteractiveGestureFlag::gfEnd)) {
// move the image
TImage * LImage = dynamic_cast<TImage*>(Image1);
movement.x = EventInfo.Location.X - FLastLocation.x;
movement.y = EventInfo.Location.Y - FLastLocation.y;
LImage->Left += movement.x;
LImage->Top += movement.y;
}
FLastLocation.x = EventInfo.Location.X;
FLastLocation.y = EventInfo.Location.Y;
}
} catch (Exception &e) {
ShowMessage(e.Message);
} }
AR is the image aspect ratio and MIN_DIM is a define to set the minimum image dimensions for the zoom.
The pan movement is not so great...
I don't know how to make the shape to behave in accordance with the image zoom and move.
The shape parent is the panel under the image, not the image, so the shape is completely independent from the image changes.
I can't set the image as the shape parent.
Whatever suggestion to get the desired result will be appreciated.
The pan movement is not smooth because EventInfo.Location.X and EventInfo.Location.Y do not vary in a smooth way.
If I start the pan movement and I stop, even if fingers are still EventInfo.Location continues to change.
Is there a way to avoid this behavior?

Related

How to stop a sprite at exact touchdown position coordinates

I want to move a sprite (which happens to be a Rectangle) from any position of the screen and make it stop at exactly the touched position of the screen. Now, I can stop my sprite already, but not at the exact touched position. I cannot find a good way of doing this without sacrificing either accuracy or risking the sprite to not stop at all.
Naturally - the problem arises because the current position is Float, so that Vector will never (or extremely rarely) have the exact same coordinates as the touch point (which is an int).
In the code below, I stop my sprite by simply checking the distance between the current position and the target position (i.e. the touched position Vector3), like so if (touch.dst(currentPsition.x, currentPosition.y, 0) < 4).
For example, if the sprite is at position (5,5) and I touch the screen at (100,100), it will stop at like (98.5352,96.8283).
My question is, how do I stop the sprite at exactly the touch position, without having to approximate?
void updateMotion() {
if (moveT) {
movement.set(velocity).scl(Gdx.graphics.getDeltaTime());
this.setPosition(currentPosition.add(movement));
if (touch.dst(currentPosition.x, currentPosition.y, 0) < 4)
moveT = false;
}
}
public void setMoveToTouchPosition(boolean moveT) {
this.moveT = moveT;
this.touch = new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0);
GameScreen.getCamera().unproject(touch);
currentPosition = new Vector2(this.x, this.y);
direction.set(new Vector2(touch.x, touch.y)).sub(currentPosition).nor();
velocity = new Vector2(direction).scl(speed);
}
Of course sprite can't move smoothly to touch position and then stop in exactly the same position because of many reasons. Just change this
if (touch.dst(currentPosition.x, currentPosition.y, 0) < 4)
moveT = false;
to this
if (touch.dst(currentPosition.x, currentPosition.y, 0) < 2) {
currentPosition.x = touch.x;
currentPosition.y = touch.y;
moveT = false;
}
A quick yet acceptable solution to this could be the use of the Rectangle class. Considering you make a Rectangle surrounding the moving entity and constantly update it's bounds based on its current location, it's texture width, and it's texture height. You could stop it when it overlaps with the "target position". If you do this you guarantee yourself that it will stop exactly at that position. For example:
Texture entityTexture = new Texture("assets/image.png");
Rectangle entityBounds = new Rectangle();
entityBounds.set((currentPosition.x, currentPosition.y, entityTexture .getWidth(), entityTexture .getHeight()));
Rectangle targetBounds = new Rectangle();
targetBounds.set((targetPosition.x, targetPosition.y, 1, 1)); // make width and height 1 by 1 for max accuracy
public void update(){
// update bounds based on new position
entityBounds.set((currentPosition.x, currentPosition.y, entityTexture.getWidth(), entityTexture.getHeight()));
targetBounds.set((targetPosition.x, targetPosition.y, 1, 1));
if(entityBounds.overlaps(targetBounds)){
// do something
}
}

Scaling points to match background. Actionscript 3

Using some code I found online has helped me create a zoom function for a program I am attempting to make. It is to make a map that allows a user to mark points. Currently the code scales in on the map image alone but I cant get the point icons to realign to where they originally where. I cant workout the maths of it.
Code to zoom in and out
if (mev.shiftKey) {
image.scaleX = Math.max(scaleFactor*image.scaleX, minScale);
image.scaleY = Math.max(scaleFactor*image.scaleY, minScale);
}
if (mev.ctrlKey) {
image.scaleX = Math.min(1/scaleFactor*image.scaleX, maxScale);
image.scaleY = Math.min(1/scaleFactor*image.scaleY, maxScale);
mat = image.transform.matrix.clone();
MatrixTransformer.matchInternalPointWithExternal(mat,internalCenter,externalCenter);
image.transform.matrix=mat;
This allows the image to scale up with the following factors
public var scaleFactor:Number = 0.8;
public var minScale:Number = 0.25;
public var maxScale:Number = 2.0;
The problem occurs when I try to move the pointer icons that are overlaid on this image. They are not to grow or shrink at the moment but they I cant get the maths to get them to move the correct number of pixels away from the mouse location so that they are still in line. Currently I am using the following formulas
//decrease zoom
stage.getChildAt(i).x = stage.getChildAt(i).x * scaleFactor;
//increase zoom
stage.getChildAt(i2).x = stage.getChildAt(i2).x / scaleFactor;
Any thoughts ? Code I am using came from
http://www.flashandmath.com/howtos/zoom/
Quite a few elements missing from the question like the moving map underneath. Anyway now that it's sorted out ...
If you are not a math genius and can't tackle 2 math formulas at the same time then don't and tackle them one by one then combine them. Once again don't use the x,y property of point for calculation but create specific property (like in a custom class for example). I will name them here origin for convenience.
Given a point with origin property of x:100, y:200, its position on the map is (assuming map is top left coordinate, if not adapt accordingly):
point.x = map.x + point.origin.x;
point.y = map.y + point.origin.y;
the positioning is solved now you need to solve for scale which is easy:
point.x = point.origin.x * scaleFactor;
point.y = point.origin.y * scaleFactor;
Both systems are solved now you can combine the two:
point.x = map.x + (point.origin.x * scaleFactor);
point.y = map.y + (point.origin.y * scaleFactor);

Workaround for custom UIViewController animations in landscape?

I have a custom animated UIViewController transition, and it seems that there is a bug in iOS that screws up the layout in landscape orientation. In the main animation method, i'm given a mix of landscape and portrait views. (In portrait the views are all portrait, so no problem.)
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *containerView = [transitionContext containerView];
// fromViewController.view => landscape, transform
// toViewController.view => portrait, transform
// containerView => portrait, no transform
[containerView addSubview:toViewController.view];
// ...animation... //
}
I know that the frame property is not reliable when a view has a transform - so I'm guessing this is the root of the problem. In landscape mode, the to/from viewControllers views have a 90 deg clockwise transform [0 -1 1 0]. I've tried using bounds/center to size and position the view, as well removing the transform and then reapplying it, but UIKit fights me and insists on displaying the view as portrait. Annoying!
In the screenshot, the dark grey is the UIWindow background, and the red is the added modal view controller which should cover the whole screen.
Anyone found a workaround?
Ok, the fix is surprisingly simple:
Set the toViewController frame to the container before adding the view to the container.
toViewController.view.frame = containerView.frame;
[containerView addSubview:toViewController.view];
Update: There is still a limitation in that you don't know the orientation of the frame. It is portrait initially, but stretched into landscape when it is displayed on screen. If you wanted to slide in the view from the right, in landscape it might slide in from the "top" (or the bottom if viewing the other landscape!)
I came across this issue and I just don't feel that the above solutions do this any justice. I propose a solution that doesn't require hacky code and hard coded frames.
UIView has an awesome function to convert a CGRect into the coordinate space of another (namely; +[UIView convertRect:fromView:]). So I want to detail a far simpler way one can achieve this effect in any orientation without any hardcoded values. In this example lets say we want a simple animation that slides a view in from the right of the screen.
So in our animator's animateTransition(:) we could simply perform the following:
Swift
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)!
let toView = toViewController.view
let fromView = fromViewController.view
let containerView = transitionContext.containerView()
if(isPresenting) {
//now we want to slide in from the right
let startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0)
toView.frame = containerView.convertRect(startingRect, fromView:fromView);
containerView.addSubview(toView)
let destinationRect = containerView.convertRect(fromView.bounds, fromView: fromView)
UIView.animateWithDuration(transitionDuration(transitionContext),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.7,
options: .BeginFromCurrentState,
animations: { () -> Void in
toView.frame = destinationRect
}, completion: { (complete) -> Void in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
} else {
//we want to slide out to the right
let endingRect = containerView.convertRect(CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0), fromView: fromView)
UIView.animateWithDuration(transitionDuration(transitionContext),
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.7,
options: .BeginFromCurrentState,
animations: { () -> Void in
fromView.frame = endingRect
}, completion: { (complete) -> Void in
if !transitionContext.transitionWasCancelled() {
fromView.removeFromSuperview()
}
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
}
}
Objective-C
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *toView = toViewController.view;
UIView *fromView = fromViewController.view;
UIView *containerView = [transitionContext containerView];
if(self.isPresenting) {
//now we want to slide in from the right
CGRect startingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
toView.frame = [containerView convertRect:startingRect fromView:fromView];
[containerView addSubview:toView];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
toView.frame = [containerView convertRect:fromView.bounds
fromView:fromView];
}
completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
} else {
//we want to slide out to the right
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
CGRect endingRect = CGRectOffset(fromView.bounds, CGRectGetWidth(fromView.bounds), 0);
fromView.frame = [containerView convertRect:endingRect fromView:fromView];
}
completion:^(BOOL finished) {
[fromView removeFromSuperview];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
I hope this helps someone else who came here in the same boat (if it does, an up-vote won't hurt :) )
The existing answer goes part way but not all the way (we want proper frames and rotation handling on both devices, all orientations, for both animated and interactive transitions).
This blog post helps:
http://www.brightec.co.uk/blog/ios-7-custom-view-controller-transitions-and-rotation-making-it-all-work
And it quotes an Apple Support person stating the true nature of the problem:
"For custom presentation transitions we setup an intermediate view between the window and the windows rootViewController's view. This view is the containerView that you perform your animation within. Due to an implementation detail of auto-rotation on iOS, when the interface rotates we apply an affine transform to the windows rootViewController's view and modify its bounds accordingly. Because the containerView inherits its dimensions from the window instead of the root view controller's view, it is always in the portrait orientation."
"If your presentation animation depends upon the orientation of the presenting view controller, you will need to detect the presenting view controller's orientation and modify your animation appropriately. The system will apply the correct transform to the incoming view controller but you're animator need to configure the frame of the incoming view controller."
But it doesn't address interactive transitions.
I worked out a complete solution to the problem here:
https://github.com/alfiehanssen/Cards
Essentially, you need to calculate the frames of your viewControllers based on the orientation of one of the viewControllers (toViewController or fromViewController) rather than the bounds of the transitionContext's containerView.
I was stumped with this issue as well. I didn't like the switch/case solution too much. I ended up creating this function instead:
#implementation UIView (Extras)
- (CGRect)orientationCorrectedRect:(CGRect)rect {
CGAffineTransform ct = self.transform;
if (!CGAffineTransformIsIdentity(ct)) {
CGRect superFrame = self.superview.frame;
CGPoint transOrigin = rect.origin;
transOrigin = CGPointApplyAffineTransform(transOrigin, ct);
rect.origin = CGPointZero;
rect = CGRectApplyAffineTransform(rect, ct);
if (rect.origin.x < 0.0) {
transOrigin.x = superFrame.size.width + rect.origin.x + transOrigin.x;
}
if (rect.origin.y < 0.0) {
transOrigin.y = superFrame.size.height + rect.origin.y + transOrigin.y;
}
rect.origin = transOrigin;
}
return rect;
}
- (CGRect)orientationCorrectedRectInvert:(CGRect)rect {
CGAffineTransform ct = self.transform;
if (!CGAffineTransformIsIdentity(ct)) {
ct = CGAffineTransformInvert(ct);
CGRect superFrame = self.superview.frame;
superFrame = CGRectApplyAffineTransform(superFrame, ct);
CGPoint transOrigin = rect.origin;
transOrigin = CGPointApplyAffineTransform(transOrigin, ct);
rect.origin = CGPointZero;
rect = CGRectApplyAffineTransform(rect, ct);
if (rect.origin.x < 0.0) {
transOrigin.x = superFrame.size.width + rect.origin.x + transOrigin.x;
}
if (rect.origin.y < 0.0) {
transOrigin.y = superFrame.size.height + rect.origin.y + transOrigin.y;
}
rect.origin = transOrigin;
}
return rect;
}
Basically, you can create your frame rects using the portrait or landscape coordinates but run it through the function with the view's transform before applying it to the view. With this method, you can use bounds to get correct view size.
CGRect endFrame = toViewController.view.frame;
CGRect startFrame = endFrame;
startFrame.origin.y = fromViewController.view.bounds.size.height;
endFrame = [fromViewController.view orientationCorrectedRect:endFrame];
startFrame = [fromViewController.view orientationCorrectedRect:startFrame];
toViewController.view.frame = startFrame;
One solution is to have a very short (or zero-second) transition, then once the transition is finished and your view controller is presented, it will have the correct transforms applied to it. You then perform your animations from within the presented view controller itself.

removing old tiles in a running game made with as3 and flash

I started to build a run game app for Android. I chose to make it in flash using tiles and Adobe Air. The game is that a player should run automatically to the right and avoid some obstacles by jumping or sliding along the ground.
I have made a function which always takes the first level in the array and uses as the starting level.
private function createLevel()
{
map_level.levell();
level = map_level.level1;
for(var t = 0; t < level.length; t++)
{
for(var u = 0; u < level[t].length; u++)
{
if(level[t][u] != 0)
{
var new_tile:platform_tile = new platform_tile;
addChild(new_tile);
new_tile.gotoAndStop(level[t][u]);
new_tile.x = u * 32;
new_tile.y = t * 32;
tiles.push(new_tile);
}
}
}
total_tile_width += u;
}
Then I create a function that takes a random level in the array of paths.
private function random_level ()
This level is then added at the end of the first track when the player has reached a certain length along the track, then the track seems endless and then made such that the camera follows the player.
private function update_level ()
{
random_level();
for(var t = 0; t < mid_lvl.length; t++)
{
for(u = 0; u < mid_lvl[t].length; u++)
{
if(mid_lvl[t][u] != 0)
{
var new_tile:platform_tile = new platform_tile;
level[t][u + total_tile_width] = mid_lvl[t][u];
addChild(new_tile);
new_tile.gotoAndStop(mid_lvl[t][u]);
new_tile.x = (u + total_tile_width) * 32;
new_tile.y = t * 32;
tiles.push(new_tile);
}
}
}
// Indstiller hvis spilleren skal have en stigende fart
if( movementspeed < 40)
{
movementspeed = movementspeed + 2;
}
else
movementspeed = movementspeed;
total_tile_width += u;
trace ("speed: " + movementspeed);
}
All this works as it should and game function also perfect as a PC, but the phone seems quick to overload it, since I can not figure out how to remove the old levels that have already been played and therefore there's going to be a lot levels in the phone memory.
I need to something like removeChild("old tiles the left the stage again) but got no idear how to only find the tiles that old and not just all tiles.
Anyone able to help me? btw hope you understand my question as im not the best at writing english.
Morten
You must use 2 BitmapData objects.
2 are the size of the screen. We call them screenBuffer1 and screenBuffer2
the other is larger than the screen by at least the width of a tile. We call it terrainBuffer
Then :
Init the terrainBuffer by drawing all visible tiles of the start screen
Copy the currently visible area on screenBuffer1
Draw sprites on screenBuffer1
Attach screenBuffer1 to stage
Now we start scrolling
Copy the currently visible area on screenBuffer2, offset by scroll amount
Draw sprites on screenBuffer2
Attach screenBuffer2 to stage
Continue increasing offset and alternate screenBuffer1 and screenBuffer2 so one is visible while you draw on the other
When offset reach the witdh of a tile :
if(offset>tileWidth){
offset-=tileWidth;
// Move content of terrainBuffer to the left by tileWidth pixels
// Draw a new column of tile on the right
}
Then keep on looping !

Centering an image inside a Loader

I'm developing an ActionScript 3.0 app for Blackberry Playbook.
I have a Loader with a fixed size of 240x240px. The images that can be loaded inside are smaller or bigger than 240x240px, and also they aren't squared.
I use this code to resize that images:
private function onLoadedEvent(event:Event):void
{
var targetLoader:Loader = Loader(event.target.loader);
var factor:Number;
if (targetLoader.content.height > targetLoader.content.width) {
factor = 240/targetLoader.content.height;
}
else
{
factor = 240/targetLoader.content.width;
}
targetLoader.content.height = targetLoader.content.height * factor;
targetLoader.content.width = targetLoader.content.width * factor;
}
How can I do to set that images centered vertically inside that Loader?
I think this should do (putting it after the resize) unless I misunderstood your question:
targetLoader.content.y = (240 - targetLoader.content.height) / 2