Question

I develop a Python-based drawing program, Whyteboard (https://launchpad.net/whyteboard)

I'm developing features to allow the user to rotate and scale a polygon that they draw. Here's my problem:

I have a Polygon class containing a list of all points, which is "closed off" at the end. Users can select drawn shapes in my program, which "highlights" them, drawing selection handles at each point. These points can be "grabbed" to change its position, and altering the polygon's shape.

I have a problem: I need to figure out how to calculate a the resizing "scale" to apply to the polygon. For example, (with the mouse held down), the user moving their mouse away from the shape should be a "grow" action, and bringing the mouse towards the shape should shrink it.

I have code in place to perform the scale (which I believe is correct) but I just can't create a "good" scaling factor. The code below is what I've come up with, based on the answers

/edit -- here is the solved code.

def rescale(self, x, y):
    """ 
    x and y are the current mouse positions. the center and "original" mouse 
    coords are calculated below
    """
    if not self.center:
        a = sum([x for x, y in self.points]) / len(self.points)
        b = sum([y for x, y in self.points]) / len(self.points)
        self.center = (a, b)
    if not self.orig_click:  # where the user first clicked on
        self.orig_click = (x, y)
    if not self.original_points:  # the points before applying any scaling
        self.original_points = list(self.points)


    orig_click = self.orig_click
    original_distance = math.sqrt((orig_click[0] - self.center[0]) ** 2 + (orig_click[1] - self.center[1]) ** 2)

    current_distance = (math.sqrt((x - self.center[0]) ** 2 + 
                       (y - self.center[1]) ** 2))
    self.scale_factor = current_distance / original_distance        

    for count, point in enumerate(self.original_points): 
        dist = (point[0] - self.center[0], point[1] - self.center[1]) 
        self.points[count] = (self.scale_factor * dist[0] + self.center[0], self.scale_factor * dist[1] + self.center[1]) 

Currently this code seems to scale my polygon down to nothing quickly, and no amount of mouse movement will grow it back. Sometimes it will do the opposite, and grow quickly; but not shrink back.

Was it helpful?

Solution

First, let's correct your scaling code:

for count, point in enumerate(self.points): 
    dist = (point[0] - self.center[0], point[1] - self.center[1]) 
    self.points[count] = (self.scale_factor * dist[0] + self.center[0], self.scale_factor * dist[1] + self.center[1]) 

I hope your points are kept in floating point, because integer truncation errors are going to accumulate very quickly. It might be better to have two copies of the points, one scaled and one unscaled.

To determine the scale factor, take the ratio of the distance from the original click to the center, and the current mouse position to the center.

original_distance = sqrt((click[0] - self.center[0])**2 + (click[1] - self.center[1])**2)
current_distance = sqrt((current_position[0] - self.center[0])**2 + (current_position[1] - self.center[1])**2)
self.scale_factor = current_distance / original_distance

Edit: Your latest problem emphasizes the importance of having two sets of points, the original and the scaled. Since the scale factor is relative to the original size of the shape, you need to start with the original points of the shape each time you scale. You can consolidate that back down to one set when the user is done playing with the mouse.

And to your comment, no you don't have to recalculate the center. The center should not be moving.

Edit 2: When you scale, you're scaling from one size to another size. If you're rescaling constantly, you have two choices: keep one copy of the shape at its original size, or make your scale factor relative to the last size of the shape, rather than the original size. I prefer the two copy approach, because otherwise it's too easy for errors to accumulate, even if you're using floating point; it's also easier to get the logic right.

OTHER TIPS

The most intuitive scale factor would be the ratio of (distance from current mouse position to the polygon centre) to (distance from mouse position at start of drag to the polygon centre) - so that clicking on a point in the polygon and dragging it twice as far from the centre as it was doubles the size of the polygon.

I'm not versed in Python, so I'll try to answer in pseudocode.

First of all, you will want to calculate the center of the polygon. This is done very easily (and makes sense when you think about it): simply add all points together and divide them by the amount of points.

center = (point1 + point2 + point3) / 3

You want to scale it based on the mouse, correct? That's always going to be fiddly, but it should be something like this:

scale = lengthof(mouse - center) / MAGIC_NUMBER

Then you calculate the relative points to the center. You're effectively setting the origin of a graph at the centre point.

relative_point1 = point1 - center
relative_point2 = point2 - center
relative_point3 = point3 - center

Then you scale the relative points by the scale:

relative_point1 *= scale
relative_point2 *= scale
relative_point3 *= scale

And place them back at the correct position:

point1 = center + relative_point1
point2 = center + relative_point2
point3 = center + relative_point3

To avoid rounding errors, you'll probably want to keep the original points until the user is done scaling.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top