LiraNuna
LiraNuna

Reputation: 67292

Flash: Closest point to MovieClip

I need to constrain a point inside a DisplayObject given to me by the artist.

I got it working but only for the occations where the cursor is still inside bounds.
The limited object is called limited.

function onSqMouseMove(event:MouseEvent) {
    if(bounds.hitTestPoint(event.stageX, event.stageY, true)) {
        limited.x = event.stageX;
        limited.y = event.stageY;
    } else {
        /* Find closest point in the Sprite */
    }
}

limited.addEventListener(MouseEvent.MOUSE_DOWN, function(event:MouseEvent) {
    stage.addEventListener(MouseEvent.MOUSE_MOVE, onSqMouseMove);
});

limited.addEventListener(MouseEvent.MOUSE_UP, function(event:MouseEvent) {
    stage.removeEventListener(MouseEvent.MOUSE_MOVE, onSqMouseMove);
});

How do I go about implementing the other half of the function? I am aware Sprite's startDrag accepts arguments, where the second one is the constraint rectangle, but in my case, bounds are an arbitrary shape.

When the object is dragged outside the bounds, I want to calculate the closest point from the cursor to bounds' polygon.

Just to note that bounds can have 'holes'.

Edit:

To be clear, I don't want to find if a point is inside the MovieClip or not, I want the closest point from a point outside the MovieClip (note that hitTestPoint fails!) to the bounds of it.

alt text
(source: liranuna.com)

Upvotes: 3

Views: 1667

Answers (5)

avanderw
avanderw

Reputation: 675

Hate to give another theoretical answer and open this up again, but I have been pondering this problem myself recently. I would assume that a ray-tracing method could work. The idea is as follows:

  • cast ray from the cursor to the center of the [movie-clip/sprite]
  • get the distance where it collides with the [movieclip/sprite]
  • get left & right normals of the line from the mouse to the center point
  • move the center point along the one normal
  • recast ray for the new point
  • get the distance for where it collides with the [movie-clip/sprite]
  • if the distance is greater than the previous distance, progress the center point on the opposite normal
  • repeat until you have found the minimum distance

The only problem I can think of with this method, is that it might be susceptible to a local minima / maxima problem. It could also potentially be expensive to compute.

A quick google gave me the following resources, if you were to attempt the above (which I think I might be doing if I cannot find an alternative)

An example of what can be achieved if you had the point on the inside of the shape and found the nearest point on the surface (leading in most cases to the normal at that point), is this one on boids http://www.red3d.com/cwr/steer/Containment.html

Did you find a solution for this?

Upvotes: 0

SEK
SEK

Reputation: 1222

There are multiple algorithmic methods that may work well, depending upon your situation:

  1. Preprocess the artwork and mark points every 20 pixels or so. This is to reduce the number of distance tests you need to perform. If the drag object is outside the bounds, then determine the closest reference point. Determine which of its two neighboring reference points is closest to the drag object. Pick some point between them based upon the ratio of distance (if they are nearly equidistant, it would imply the closest point is almost halfway). Something like this: Point.interpolate(closestRef, nextClosestRef, factor). The drawbacks of this method would be that it would not be 100% accurate, since areas in between the reference points would be reduced to straight lines (which may influence the placement of reference points). In other words, if your bounding art has lots of complex edges, this method is not good, if on the other hand it has lots of straight lines, it's great.
  2. On an abstract level, your problem is called the "nearest-neighbor problem". A google search may turn up some efficient solution.
  3. Similar to what shortstick described above, attach a circular hitdetect sprite to the cursor position. However, rather than incrementing the radius by 5px each time, double it each time until it intersects the bounding shape. Then perform a binary search using the last two radius lengths used as the lower and upper bounds. This will give you the distance to the closest point on the bounding shape. To translate that to an actual point is a bit more difficult, but not impossible. You just need a secondary partial circle (not sure exactly how this is called) hitdetect sprite attached. Adjust the arc length in a similar binary search to determine the angle. Now do Point.polar(radius, angle).add(cursorPosition) to get the actual point on the border of the shape.
  4. I'm pretty sure that there's a way to do 3, but using rectangles, which have a native implementation in the Flash Platform. I'm not entirely sure though, but it's worth some thought.

Just for reference, it's always a good idea to dig through the API first and see if there's already a library that does what you need.

Upvotes: 0

Bart van Heukelom
Bart van Heukelom

Reputation: 44124

You can start at the mouse position and start spiraling out doing the hit test for every pixel. This will result in a long loop but depending on your use case it should probably perform reasonably ok. This you can only see by testing.

Upvotes: 1

longstaff
longstaff

Reputation: 2051

well, it really depends on your accuracy you are after as far as im concerned (and exactly what the style or complexity of the background image is). as far as i know there is no direct method of testing distance unless you have a specific mathmatical reference, therefore unless you are dynamically drawing your background, you are in a lot of trouble.

two methods come straight to mind:

  1. on loading of the level, compute it into an array of nth accuracy (5x5 pixel for example), copy a section of the background and then test if it contains transparency (getColorBoundsRect( 0xff000000, 0, false ); perhaps?). note which contain edges in the array, then you can test this grid against mouse position to tell where the most likely closest edge is (any one square apart, then two, then three etc). if you need greater accuracy then you can try some sort of computation once you know which ones are most likely to hold the closest pixel. This wont be pinpoint accurate and will require some computation at the start, but it should be quick to run.

  2. if you have a circular hitdetect sprite that you attach to the cursor position, then you can expand/contract it and find the hitpoint. starting at width:0, height:0 for when it is in contact with the background, when it moves over the edge expand it by a reasonable amount(eg 5px) per loop until it has a hittest. you can then track this point (as demonstrated in the Grant Skinners Page as viatropos posted), if the collision area is too large then your cursor is moving closer to the collision point, and can reduce by an amount until the collision size is sensible again. this is a bit intensive on processor, but its only a few hittests a frame, same as having 30 balls bouncing around, it shouldnt be too hard in it.

hope this gives you some ideas!

Upvotes: 1

Lance Pollard
Lance Pollard

Reputation: 79458

I saw this guys blog once a while back on something about "pixel precision on images", where he was able to something really cool with images and hit tests, but I can't remember :p.

How about these for a start:

Given that your DisplayObject is a solid color, but an arbitrary shape, it would be really easy going by Doug McCune's tutorial. Let me know if that gets you closer.

Best, Lance

Upvotes: 1

Related Questions