AS3: zoom and pan movieclip in adobe air android - actionscript-3

I am using gestouch library on github.https://github.com/fljot/Gestouch
zoomall is my movieclip, I am able to zoom in and out at a specific point.
here is my code,
import org.gestouch.events.GestureEvent;
import org.gestouch.gestures.ZoomGesture;
var zoom: ZoomGesture*;
zoom = new ZoomGesture(zoomall);
zoom.addEventListener(org.gestouch.events.GestureEvent.GESTURE_BEGAN, onGesture);
zoom.addEventListener(org.gestouch.events.GestureEvent.GESTURE_CHANGED, onGesture);
function onGesture(event: org.gestouch.events.GestureEvent): void {
const gesture: ZoomGesture = event.target as ZoomGesture;
var matrix: Matrix = zoomall.transform.matrix;
var transformPoint: Point = matrix.transformPoint(zoomall.globalToLocal(zoom.location));
matrix.translate(-transformPoint.x, -transformPoint.y);
matrix.scale(gesture.scaleX, gesture.scaleY);
matrix.translate(transformPoint.x, transformPoint.y);
zoomall.transform.matrix = matrix;
}
Here I want to restrict the zoom in and out to specific scale.
And I also want to pan the movieclip(zoomall) and it should not pan outside the device screen.

If you want to try what flash is all ready built with these are great and simple tutorials that may help you out.
Pan tutorial
http://www.republicofcode.com/tutorials/flash/as3pangesture/
Pinch/Zoom
http://www.republicofcode.com/tutorials/flash/as3pinchzoom/
Hope this helps

You realize that by calling matrix.scale(B, B) you simply do A * B = C,
where A is current scale and C is resulting scale?
So if you don't want C to be bigger than maximum maxC, you should limit B:
B = Math.min(B, maxC / A)
Same for minimum minC:
B = Math.max(minC / A, B)
So you will have:
// assuming you keep the scale ratio
var minS:Number = MIN_SCALE / zoomall.scaleX;
var maxS:Number = MAX_SCALE / zoomall.scaleX;
var s:Number = Math.max(minS, Math.min(gesture.scaleX, maxS));
matrix.scale(s, s);

Related

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);

How to properly create a Point relative to an Image using FigPanZoom?

I have an Image that's 5000 pixels by 5000 pixels. It's scaled up and down regularly to fit different parts of the image into the window.
I'm making a function that focuses in on different places on the image (like a map) and then zooms into a certain scale that I specify.
I pass my point into this function (new Point(2000,2500) for example)) however this always breaks because it's not relative to the image specifically.
How do I make the Point relative to the image at the image's given scale at any given time?
Additional Info: I'm using the guide here http://www.adobe.com/devnet/flex/samples/fig_panzoom.html for panning and zooming functionality.
Solved:
One of my pitfalls was that I was using a possible bitmapScaleFactor > 1 which would mess up the scaling. This was the final function that worked for my usage.
protected function testFocus(p:Point):void
{
var content:ContentRectangle = boardViewer._contentRectangle;
var panToPoint:PanToPointCommand = new PanToPointCommand(boardViewer, content);
var scale:Number = boardViewer.bitmapScaleFactor;
var location = new Point((p.x*scale)+content.x, (p.y*scale)+content.y);
var center = new Point(boardViewer.width/2, boardViewer.height/2);
//Move the point to the center
panToPoint.fromPoint = location;
panToPoint.toPoint = center;
panToPoint.execute();
}
I've not tested this but how about using the scale factor that is being applied to the image to alter the point coordinates.
e.g.
var scaler:Number = 0.5;
image.scaleX = image.scaleY = scaler;
new Point(2000*scaler,2500*scaler);
Are you looking for DisplayObject.mouseX/mouseY? These do not take any rotation into account, however.
To take rotation into account, you could do something like (untested code):
var mat:Matrix = image.transform.concatenatedMatrix;
mat.invert();
// now use mat to transform point from 'stage space' to 'image space'
var pImage:Point = mat.transform(new Point(stage.mouseX, stage.mouseY));

Resizing BitmapData in ActionScript 3

I am using ActionScript 3.0 to capture image from users webcam ,It is working fine , however the problem is that the size of image is too big for my liking . Can I make it small , I tried changing coordinates of Bitmap Data.
Can anybody suggest me the solution.
Thanks
When you capture the webcam you have to provide a matrix. This matrix can handle a rescaling.
var output:BitmapData = new BitmapData(camera.width * scaleFactor, camera.height * scaleFactor, false);
var matrix:Matrix = new Matrix();
matrix.scale(scaleFactor, scaleFactor);
output.draw(camera, matrix, null, null, null, true);
Sometimes the smoothing of this method is not really satisfying. A solution would be to use an intermediate:
var capture:BitmapData = new BitmapData(camera.width, camera.height, false);
capture.draw(camera);
//or with a newer compiler
//camera.drawToBitmapData(capture);
var intermediate:Bitmap = new Bitmap(capture);
intermediate.scaleX = intermediate.scaleY = scaleFactor;
output.draw(intermediate);
capture.dispose();
Prefer the first method if you are okay with the smoothing.

How do i calculate an objects scale using a perspective projection matrix?

Im currently building a little 3D particle engine in flash the uses sprites.
to set the position of each sprite I am using the projectVector function below. (the viewTransform matrix is the particles world matrix concatenated with a perspective projection matrix)
var projectedPoint:Vector3D = Utils3D.projectVector(viewTransform, point);
sprite.x = projectedPoint.x;
sprite.y = projectedPoint.y;
this works really well an places the sprites exactly where they should be :D
The problem I am having is trying to figure out how to calculate the scale of the each particle based on is distance from the camera..
sprite.scaleX = sprite.scaleY = ??
If I wasn't using a perspective projection matrix I would usually do something like this..
var scaleRatio:Number = (focus * zoom)/(focus + particle.globalz);
particle.depth = scaleRatio;
sprite.x = particle.globalx * scaleRatio;
sprite.y = particle.globaly * scaleRatio;
// set scale..
sprite.scaleX = sprite.scaleY = scaleRatio;
If there is anyone out there able to show me how to calculate the "scaleRatio" using a perspective projection matrix that would be ace
thanks!
I'm sure there's a more succinct way to do this, but since you already know how to project a point, you could do this:
var tl:Point = sprite.getRect(sprite.parent).topLeft;
var br:Point = sprite.getRect(sprite.parent).bottomRight;
var projectedTL:Point = Utils3D.projectVector(viewTransform, tl);
var projectedBR:Point = Utils3D.projectVector(viewTransform, br);
trace("projected width = "+(projectedBR.x - projectedTL.x));
trace("projected height = "+(projectedBR.y - projectedTL.y));

How to get unrotated display object width/height of a rotated display object?

If I create a rectangle with 100px width and 100px height and then rotate it, the size of the element's "box" will have increased.
With 45 rotation, the size becomes about 143x143 (from 100x100).
Doing sometimes like cos(angleRad) * currentWidth seems to work for 45 rotation, but for other bigger angles it doesn't.
At the moment I am doing this:
var currentRotation = object.rotation;
object.rotation = 0;
var normalizedWidth = object.width;
var normalizedHeight = object.height;
object.rotation = currentRotation;
Surely, there must be a better and more efficient way. How should I get the "normalized" width and height of a displayobject, aka the size when it has not been rotated?
The best approach would probably be to use the code posted in the question - i.e. to unrotate the object, check its width, and then re-rotate it. Here's why.
First, simplicity. It's obvious what's being done, and why it works. Anyone coming along later should have no trouble understanding it.
Second, accuracy. Out of curiosity I coded up all three suggestions currently in this thread, and I was not really surprised to find that for an arbitrarily scaled object, they give three slightly different answers. The reason for this, in a nutshell, is that Flash's rendering internals are heavily optimized, and among other things, width and height are not stored internally as floats. They're stored as "twips" (twentieths of a pixel) on the ground that further accuracy is visually irrelevant.
Anyway, if the three methods give different answers, which is the most accurate? For my money, the most correct answer is what Flash thinks the width of the object is when it's unrotated, which is what the simple method gives us. Also, this method is the only one that always give answers rounded to the nearest 1/20, which I surmise (though I'm guessing) to mean it's probably equal to the value being stored internally, as opposed to being a calculated value.
Finally, speed. I assume this will surprise you, but when I coded the three methods up, the simple approach was the fastest by a small margin. (Don't read too much into that - they were all very close, and if you tweak my code, a different method might edge into the lead. The point is they're very comparable.)
You probably expected the simple method to be slower on the grounds that changing an object's rotation would cause lots of other things to be recalculated, incurring overhead. But all that really happens immediately when you change the rotation is that the object's transform matrix gets some new values. Flash doesn't really do much with that matrix until it's next time to draw the object on the screen. As for what math occurs when you then read the object's width/height, it's difficult to say. But it's worth noting that whatever math takes place in the simple method is done by the Player's heavily optimized internals, rather than being done in AS3 like the algebraic method.
Anyway I invite you to try out the sample code, and I think you'll find that the simple straightforward method is, at the least, no slower than any other. That plus simplicity makes it the one I'd go with.
Here's the code I used:
// init
var clip:MovieClip = new MovieClip();
clip.graphics.lineStyle( 10 );
clip.graphics.moveTo( 12.345, 37.123 ); // arbitrary
clip.graphics.lineTo( 45.678, 29.456 ); // arbitrary
clip.scaleX = .87; // arbitrary
clip.scaleY = 1.12; // arbitrary
clip.rotation = 47.123; // arbitrary
// run the test
var iterations:int = 1000000;
test( method1, iterations );
test( method2, iterations );
test( method3, iterations );
function test( fcn:Function, iter:int ) {
var t0:uint = getTimer();
for (var i:int=0; i<iter; i++) {
fcn( clip, i==0 );
}
trace(["Elapsed time", getTimer()-t0]);
}
// the "simple" method
function method1( m:MovieClip, traceSize:Boolean ) {
var rot:Number = m.rotation;
m.rotation = 0;
var w:Number = m.width;
var h:Number = m.height;
m.rotation = rot;
if (traceSize) { trace([ "method 1", w, h ]); }
}
// the "algebraic" method
function method2( m:MovieClip, traceSize:Boolean ) {
var r:Number = m.rotation * Math.PI/180;
var c:Number = Math.abs( Math.cos( r ) );
var s:Number = Math.abs( Math.sin( r ) );
var denominator:Number = (c*c - s*s); // an optimization
var w:Number = (m.width * c - m.height * s) / denominator;
var h:Number = (m.height * c - m.width * s) / denominator;
if (traceSize) { trace([ "method 2", w, h ]); }
}
// the "getBounds" method
function method3( m:MovieClip, traceSize:Boolean ) {
var r:Rectangle = m.getBounds(m);
var w:Number = r.width*m.scaleX;
var h:Number = r.height*m.scaleY;
if (traceSize) { trace([ "method 3", w, h ]); }
}
And my output:
method 1,37.7,19.75
Elapsed time,1416
method 2,37.74191378925391,19.608455916982187
Elapsed time,1703
method 3,37.7145,19.768000000000004
Elapsed time,1589
Surprising, eh? But there's an important lesson here about Flash development. I hereby christen Fen's Law of Flash Laziness:
Whenever possible, avoid tricky math by getting the renderer to do it for you.
It not only gets you done quicker, in my experience it usually results in a performance win anyway. Happy optimizing!
Here's the algorithmic approach, and its derivation.
First, let's do the opposite problem: Given a rectangle of unrotated width w, unrotated height h, and rotation r, what is the rotated width and height?
wr = abs(sin(r)) * h + abs(cos(r)) * w
hr = abs(sin(r)) * w + abs(cos(r)) * h
Now, try the problem as given: Given a rectangle of rotated width wr, rotated height hr, and rotation r, what is the unrotated width and height?
We need to solve the above equations for h and w. Let c represent abs(cos(r)) and s represent abs(sin(r)). If my rusty algebra skills still work, then the above equations can be solved with:
w = (wr * c - hr * s) / (c2 - s2)
h = (hr * c - wr * s) / (c2 - s2)
You should get the bounds of your square in your object's coordinate space (which means no rotations).
e.g.
var b:Sprite = new Sprite();
b.graphics.lineStyle(0.1);
b.graphics.drawRect(0,0,100,100);
b.rotation = 10;
trace('global coordinate bounds: ' + b.getBounds(this));//prints global coordinate bounds: (x=-17.35, y=0, w=115.85, h=115.85);
trace('local coordinate bounds: ' + b.getBounds(b));//prints local coordinate bounds: (x=0, y=0, w=100, h=100)
HTH,
George
Chip's answer in code:
// convert degrees to radians
var r:Number = this.rotation * Math.PI/180;
// cos, c in the equation
var c:Number = Math.abs(Math.cos(r));
// sin, s in the equation
var s:Number = Math.abs(Math.sin(r));
// get the unrotated width
var w:Number = (this.width * c - this.height * s) / (Math.pow(c, 2) - Math.pow(s, 2));