views:

164

answers:

3

Finding a good way to do this has stumped me for a while now: assume I have a selection box with a set of points in it. By dragging the corners you can scale the (distance between) points in the box. Now for an axis aligned box this is easy. Take a corner as an anchor point (subtract this corner from each point, scale it, then add it to the point again) and multiply each points x and y by the factor with which the box has gotten bigger.

But now take a box that is not aligned with the x and y axis. How do you scale the points inside this box when you drag its corners?

+3  A: 

Any box is contained inside a circle.
You find the circle which binds the box, find its center and do exactly the same as you do with an axis aligned box.

shoosh
elegant and correct
Charles Bretana
You've just replaced one problem with another. How do you get the circle which binds the box?
Mark Ransom
simple geometry.
shoosh
The center of the box is (p1+p3)/2 where p1 and p3 are opposite points. The angles in the corners of the box need not be 90°, it's sufficient that opposite angles are the same (parallelogram)
Aaron Digulla
@Shy, may be simple for you but the answer is incomplete without that detail. @Aaron, you've supplied half the answer - compute the radius from the center to one of the points, then you're done.
Mark Ransom
A: 

You pick one corner of the rectangle as the origin. The two edges connected to it will be the basis (u and v, which should be perpendicular to each other). You would need to normalize them first.

Subtract the origin from the coordinates and calculate the dot-product with the scaling vector (u), and with the other vector (v). This would give you how much u and v contributes to the coordinate.

Then you scale the component you want. To get the final coordinate, you just multiply the the (now scaled) components with their respective vector, and add them together.

For example:

Points: p1 = (3,5) and p2 = (6,4)

Selection corners: (0,2),(8,0),(9,4),(1,6)
selected origin = (8,0)

u = ((0,2)-(8,0))/|(0,2)-(8,0)| = <-0.970, 0.242>
v = <-0.242, -0.970>

(v is u, but with flipped coordinates, and one of them negated)

p1´ = p1 - origin = (-5, 5)
p2´ = p2 - origin = (-2, 4)

p1_u = p1´ . u = -0.970 * (-5) + 0.242 * 5 = 6.063
p1_v = p1´ . v = -0.242 * (-5) - 0.970 * 5 = -3.638

Scale p1_u by 0.5: 3.038

p1_u * u + p1_v * v + origin = <5.941, 4.265>

Same for p2: <7.412, 3.647>

As you maybe can see, they have moved towards the line (8,0)-(9,4), since we scaled by 0.5, with (0,8) as the origin.

Edit: This turned out to be a little harder to explain than I anticipated.

In python code, it could look something like this:

def scale(points, origin, u, scale):
    # normalize
    len_u = (u[0]**2 + u[1]**2) ** 0.5
    u = (u[0]/len_u, u[1]/len_u)
    # create v
    v = (-u[1],u[0])
    ret = []
    for x,y in points:
        # subtract origin
        x, y = x - origin[0], y - origin[1]
        # calculate dot product
        pu = x * u[0] + y * u[1]
        pv = x * v[0] + y * v[1]
        # scale
        pu = pu * scale
        # transform back to normal space
        x = pu * u[0] + pv * v[0] + origin[0]
        y = pu * u[1] + pv * v[1] + origin[1]
        ret.append((x,y))
    return ret

>>> scale([(3,5),(6,4)],(8,0),(-8,2),0.5)
[(5.9411764705882355, 4.2647058823529411), (7.4117647058823533, 3.6470588235294117)]
MizardX
A: 
e.James
Thanks, but this only seems to work for proportionate scaling. Is there any way to make it work for unproportionate?
Menno
My mistake. I thought you were looking for proportionate scaling.
e.James
and I probably should have made that a little more clear ;)
Menno
No worries. This was a fun one! I have added the algorithm for non-proportional scaling.
e.James