0
votes

I am trying to implement a simple "zoom" function in a map presentation type app. The user interacts with a NumericStepper to dial in a scale value and I then use that value to set the scaleX and scaleY properties of my map sprite. The parent of the map sprite has a scrollRect defined so the map is cropped as it scales. That all seems to work fine.

Naturally when I change the scale, the visible content shifts as the sprite becomes larger or smaller. I would like to keep the content in relatively the same screen location. I've taken a first pass at it below but it's not quite right.

Question: Am I on the right track here thinking that I can determine how much to shift the x/y by comparing the change in the width/height of the sprite after scaling? (as I write this I am thinking I can determine the center of the sprite before scaling, then reposition it so it stays centered over that point. Hmm. . .).

        protected function scaleStepper_changeHandler(event:Event):void
        {
            var cX:Number = wrapper.x + (wrapper.width /2);
            var cY:Number = wrapper.y + (wrapper.height /2);

            wrapper.scaleX = scaleStepper.value;
            wrapper.scaleY = scaleStepper.value;

            wrapper.x = cX - (wrapper.width /2);
            wrapper.y = cY - (wrapper.height /2);

        }
2

2 Answers

0
votes

You are on the right track, but for a better solution you should use a matrix to transform your Sprite. Use the following code below to achieve what you need:

private var originalMatrix:Matrix;

private function scaleAroundPoint(target:Sprite, scalePoint:Point, scaleFactor:Number):void 
{ 
    if(originalMatrix == null)
        originalMatrix = target.transform.matrix;

    var matrix:Matrix = originalMatrix.clone();             
    matrix.translate(-scalePoint.x, -scalePoint.y); 
    matrix.scale(scaleFactor, scaleFactor); 
    matrix.translate(scalePoint.x, scalePoint.y); 
    target.transform.matrix = matrix; 
}

You can call this method like this:

scaleAroundPoint(wrapper, new Point(yourWidth/2, yourHeight/2), scaleStepper.value);

Hope this helps and solves your problem.

0
votes

Am I on the right track here thinking that I can determine how much to shift the x/y by comparing the change in the width/height of the sprite after scaling?

Yes. As all values are known, you don't really have to "test" after scaling. You basically want to distribute the movement of the bounding box borders evenly.

Here's an example in one dimension, scaling factor 2, X is the registration point, | a boundary:

before scaling          |--X--|
after  scaling        |----X----|

No problem there. Now what if the registration point is not in the middle?

before scaling          |-X---|
after  scaling         |--X------|

As a last example, the edge case with the registration point on the boundary:

before scaling          |X----|
after  scaling          |X--------|

Note how the boundaries of all 3 examples are equal before scaling and within each example, the registration point remains constant.

The problem is clearly identified. Now how to solve this?

We do know how much the width changes

before scaling          width
after  scaling          width * scaleFactor

and from the first example we can determine where the left boundary should be after scaling (assuming that the registration point is at 0, so the object is centered):

before scaling         -width * 0.5
after  scaling         -width * 0.5 * scaleFactor

This value depends on where the registration point of course is within the display object relative to the left boarder. To circumvent this dependency, subtract the values from each other to know how much the left boundary is moved to the left after scaling while keeping the object centered:

boundary shift          width * 0.5 * (scaleFactor - 1)

Comparing before and after scaling, the left boundary should be further to the left by that amount and the right boundary should be further to the right by that amount.

The problem is that you cannot really set the left or right boundary directly. You have to set the registration point, which will influence where the boundaries are. To know how far you should move the registration point, imagine both edge cases:

before scaling          |X----|
after  scaling          |X--------|
corrected,            |X--------|

before scaling          |----X|
after  scaling      |--------X|
corrected,            |--------X|

In both cases, the registration point has to be moved by the amount which the boundary should move, because essentially, the registration point is on the boundary and thus behaves the same way.

Any value in between can be found by linearly interpolating between both cases:

-[width * 0.5 * (scaleFactor - 1)]  <= value <= +[width * 0.5 * (scaleFactor - 1)]
-[width * 0.5 * (scaleFactor - 1)] * (1-t) + [width * 0.5 * (scaleFactor - 1)] * t

To find the interpolation value t, which is 0 if X is on the left and 1 when on the right:

t = (X - L) / width

Add -[width * 0.5 * (scaleFactor - 1)] * (1-t) + [width * 0.5 * (scaleFactor - 1)] * t to the x position of the registration point and the scale the object.

Do the same for y in a similar fashion.