Pick closest point of a polyline
-
I have a polyline with arbitrary 3-d structure, drawn as a series of cline-s. Inside my DragTool.mouse_move I want to determine the closest point on this polyline corresponding to the mouse position. Yes, this is in general ambiguous, but I don't care, and any solution will do. Indeed, as long as it is "close", the user will deal with it by moving the mouse closer to the polyline to remove the ambiguity. Basically I am placing a ComponentInstance along the polyline, moving it as the user moves the mouse, but it's always constrained to line on the polyline (oriented, too, but that's not the issue, and once I have the segment and point of the polyline I'll also have the orientation).
Any suggestion how to do this?
-
Let's start simple...
With just ONE cline.
This has astart_point
and anend_point
.
From those you can also find its "line"line=[start_point, start_point.vector_to(end_point)]
You can get the mouse's position as ampoint
.
[This is the tricky bit - see API for InputPoints and PickHelpers]
You can use the API methods to determine thempoint
projected toline
ppoint=mpoint.project_to_line(line)
So this is the mouse's location projected to the cline's "line"...
BUT it might not fall 'on' the cline itself...
You need to do some checking...
OK if ppoint == start_point || ppoint == end_point ### it's on one of the cline's 'ends'
OK if ppoint.vector_to(start_point).normalize != ppoint.vector_to(start_point).normalize ### it's on the cline itself because the vectors are reversed.
BUT if ppoint.vector_to(start_point).normalize == ppoint.vector_to(start_point).normalize ### it's on the "line" BUT off the cline itself, so you need to take the nearest 'end' of the cline instead...
if ppoint.distance(start_point) < ppoint.distance(end_point)
ppoint=start_point.clone
else
ppoint=end_point.clone
endSo now we have a system for projecting the mouse-point onto a single cline.
If you have a collection of clines you need to determine the one that is 'nearest' the mouse-point]..
To do this you need to get the "line" of every cline in turn, and use the above code to get the projected-point of each, then get the distance of the mouse-point to each of those projected-points - the shortest distance gives you point to use... If you have a distance==0 then the cursor is on the cline, so you can break as there is no point testing further. -
@tig said:
Let's start simple...
Thanks, TIG, for the suggestion. Unfortunately it does not work very well at all, basically because in 3D the mouse represents a ray, not a point. Sketchup generates a point via InputPoint and PickHelper using a heuristic that does not work very well at all in this application.
I have a method that works well:
-
get the mouse ray via
view.pickray(x,y)
-
loop over all segments of the polyline
-
compute the position along the line containing the segment, where the ray is closest to the line
-
trim that to the endpoints of the segment
-
compute distance from the resulting point to the ray
-
remember the closest one
-
now I have the segment and location within the segment of the mouse
-
generate the Geom::Transform for that location
Basically this represents the centerline of a particle accelerator, and the code is inside my custom DragTool, using the mouse to place an object along the centerline; so the object is constrained to lie on the centerline, and its local z-axis is aligned with the centerline where it is located. For <= 200 segments this tracks the mouse quite well; for 1000 segments it takes about 1 second to catch up. That's acceptable for now; ultimately I may optimize it. I'm rather surprised that this much Ruby computation is acceptable.
If a segment is parallel to the pickray, the math will divide by zero. So my code disallows any segment that is nearly parallel to the pickray. This makes sense, as the user cannot possibly select a position along such a segment; use the middle button to rotate the display so the desired position is visible.
I ended the centerline with a half-infinite straight line. SketchUp cannot draw that (or rather, it tries to do so, zooming out so much that it's useless). So I split the final segment in two, with the first being half as long as the preceding centerline; the code does not display the final segment.
The next challenge is to generalize this to include segments that are circular arcs (so far the code is limited to radius=0)....
If anyone wants my code, or references to the geometrical calculations I'm using, just ask.
-
Advertisement