I have 3 points (A, B and X) and a distance (d). I need to make a function that tests if point X is closer than distance d to any point on the line segment AB.
The question is firstly, is my solution correct and then to come up with a better (faster) solution.
My first pass is as follows
AX = X-A BX = X-B AB = A-B // closer than d to A (done squared to avoid needing to compute the sqrt in mag) If d^2 > AX.mag^2 return true // closer than d to B If d^2 > BX.mag^2 return true // 'beyond' B If (dot(BX,AB) < 0) return false // 'beyond' A If (dot(AX,AB) > 0) return false // find component of BX perpendicular to AB Return (BX.mag)^2 - (dot(AB,BX)/AB.mag)^2 < d^2
This code will end up being run for a large set of P’s and a large set of A/B/d triplets with the intent of finding all P’s that pass for at least one A/B/d so I suspect that there is a way to reduce overall the cost based on that but I haven’t looked into that yet.
(BTW: I am aware that some reordering, some temporary values and some algebraic identities could decrease the cost of the above. I just omitted them for clarity.)
EDIT: this is a 2D problem (but solution that generalizes to 3D would be cool
Edit: On further reflection, I expect the hit rate to be around 50%. The X point can be formed in a nested hierarchy so I expect to be able to prune large subtrees as all-pass and all-fail. The A/B/d limiting the triplets will be more of a trick.
Edit: d is in the same order of magnitude as AB.
edit: Artelius posted a nice solution. I’m not sure I understand exactly what he’s getting at as I got off on a tangent before I fully understood it. Anyway another thought came to mind as a result:
- First Artelius’ bit, pre-cacluate a matrix that will place AB centered ate the origin and aligned with the X-axis. (2 adds, 4 muls and 2 adds)
- fold it all into the 1st quadrant (2 abs)
- scale in X&Y to make the central portion of the zone into a unit square (2 mul)
- test if the point is in that square (2 test) is so quit
- test the end cap (go back to the unscaled values
- translate in x to place the end at the origin (1 add)
- square and add (2 mul, 1 add)
- compare to d^2 (1 cmp)
I’m fairly sure this beats my solution.
(if nothing better comes along sone Artelius gets the ‘prize’ 🙂
If your set of (A,B,d) in fixed, you can calculate a pair of matrices for each to translate the co-ordinate system, so that the line AB becomes the X axis, and the midpoint of AB is the origin.
I think this is a simple way to construct the matrices:
Then you just take each X and do
rot * (X + trans).The region in question is actually symmetric in both the x and y axes now, so you can take the absolute value of the x co-ordinate, and of the y co-ordinate.
Then you just need to check:
You can do another trick; the matrix can scale down by a factor of
AB.mag/2(remember the matrices are only calculated once per (A,B) meaning that it’s better that finding them is slower, than the actual check itself). This means your check becomes:Having replaced two instances of AB.mag/2 with the constant 1, it might be a touch faster. And of course you can precalculate
2*d/AB.magand(2*d/AB.mag)^2as well.Whether this will work out faster than other methods depends on the inputs you give it. But if the length of AB is long compared to d, I think it will turn out considerably faster than the method you posted.