Path: blob/master/thirdparty/jolt_physics/Jolt/Geometry/ClosestPoint.h
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#pragma once56JPH_NAMESPACE_BEGIN78// Turn off fused multiply add instruction because it makes the equations of the form a * b - c * d inaccurate below9JPH_PRECISE_MATH_ON1011/// Helper utils to find the closest point to a line segment, triangle or tetrahedron12namespace ClosestPoint13{14/// Compute barycentric coordinates of closest point to origin for infinite line defined by (inA, inB)15/// Point can then be computed as inA * outU + inB * outV16/// Returns false if the points inA, inB do not form a line (are at the same point)17inline bool GetBaryCentricCoordinates(Vec3Arg inA, Vec3Arg inB, float &outU, float &outV)18{19Vec3 ab = inB - inA;20float denominator = ab.LengthSq();21if (denominator < Square(FLT_EPSILON))22{23// Degenerate line segment, fallback to points24if (inA.LengthSq() < inB.LengthSq())25{26// A closest27outU = 1.0f;28outV = 0.0f;29}30else31{32// B closest33outU = 0.0f;34outV = 1.0f;35}36return false;37}38else39{40outV = -inA.Dot(ab) / denominator;41outU = 1.0f - outV;42}43return true;44}4546/// Compute barycentric coordinates of closest point to origin for plane defined by (inA, inB, inC)47/// Point can then be computed as inA * outU + inB * outV + inC * outW48/// Returns false if the points inA, inB, inC do not form a plane (are on the same line or at the same point)49inline bool GetBaryCentricCoordinates(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, float &outU, float &outV, float &outW)50{51// Taken from: Real-Time Collision Detection - Christer Ericson (Section: Barycentric Coordinates)52// With p = 053// Adjusted to always include the shortest edge of the triangle in the calculation to improve numerical accuracy5455// First calculate the three edges56Vec3 v0 = inB - inA;57Vec3 v1 = inC - inA;58Vec3 v2 = inC - inB;5960// Make sure that the shortest edge is included in the calculation to keep the products a * b - c * d as small as possible to preserve accuracy61float d00 = v0.LengthSq();62float d11 = v1.LengthSq();63float d22 = v2.LengthSq();64if (d00 <= d22)65{66// Use v0 and v1 to calculate barycentric coordinates67float d01 = v0.Dot(v1);6869// Denominator must be positive:70// |v0|^2 * |v1|^2 - (v0 . v1)^2 = |v0|^2 * |v1|^2 * (1 - cos(angle)^2) >= 071float denominator = d00 * d11 - d01 * d01;72if (denominator < 1.0e-12f)73{74// Degenerate triangle, return coordinates along longest edge75if (d00 > d11)76{77GetBaryCentricCoordinates(inA, inB, outU, outV);78outW = 0.0f;79}80else81{82GetBaryCentricCoordinates(inA, inC, outU, outW);83outV = 0.0f;84}85return false;86}87else88{89float a0 = inA.Dot(v0);90float a1 = inA.Dot(v1);91outV = (d01 * a1 - d11 * a0) / denominator;92outW = (d01 * a0 - d00 * a1) / denominator;93outU = 1.0f - outV - outW;94}95}96else97{98// Use v1 and v2 to calculate barycentric coordinates99float d12 = v1.Dot(v2);100101float denominator = d11 * d22 - d12 * d12;102if (denominator < 1.0e-12f)103{104// Degenerate triangle, return coordinates along longest edge105if (d11 > d22)106{107GetBaryCentricCoordinates(inA, inC, outU, outW);108outV = 0.0f;109}110else111{112GetBaryCentricCoordinates(inB, inC, outV, outW);113outU = 0.0f;114}115return false;116}117else118{119float c1 = inC.Dot(v1);120float c2 = inC.Dot(v2);121outU = (d22 * c1 - d12 * c2) / denominator;122outV = (d11 * c2 - d12 * c1) / denominator;123outW = 1.0f - outU - outV;124}125}126return true;127}128129/// Get the closest point to the origin of line (inA, inB)130/// outSet describes which features are closest: 1 = a, 2 = b, 3 = line segment ab131inline Vec3 GetClosestPointOnLine(Vec3Arg inA, Vec3Arg inB, uint32 &outSet)132{133float u, v;134GetBaryCentricCoordinates(inA, inB, u, v);135if (v <= 0.0f)136{137// inA is closest point138outSet = 0b0001;139return inA;140}141else if (u <= 0.0f)142{143// inB is closest point144outSet = 0b0010;145return inB;146}147else148{149// Closest point lies on line inA inB150outSet = 0b0011;151return u * inA + v * inB;152}153}154155/// Get the closest point to the origin of triangle (inA, inB, inC)156/// outSet describes which features are closest: 1 = a, 2 = b, 4 = c, 5 = line segment ac, 7 = triangle interior etc.157/// If MustIncludeC is true, the function assumes that C is part of the closest feature (vertex, edge, face) and does less work, if the assumption is not true then a closest point to the other features is returned.158template <bool MustIncludeC = false>159inline Vec3 GetClosestPointOnTriangle(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, uint32 &outSet)160{161// Taken from: Real-Time Collision Detection - Christer Ericson (Section: Closest Point on Triangle to Point)162// With p = 0163164// The most accurate normal is calculated by using the two shortest edges165// See: https://box2d.org/posts/2014/01/troublesome-triangle/166// The difference in normals is most pronounced when one edge is much smaller than the others (in which case the other 2 must have roughly the same length).167// Therefore we can suffice by just picking the shortest from 2 edges and use that with the 3rd edge to calculate the normal.168// We first check which of the edges is shorter and if bc is shorter than ac then we swap a with c to a is always on the shortest edge169UVec4 swap_ac;170{171Vec3 ac = inC - inA;172Vec3 bc = inC - inB;173swap_ac = Vec4::sLess(bc.DotV4(bc), ac.DotV4(ac));174}175Vec3 a = Vec3::sSelect(inA, inC, swap_ac);176Vec3 c = Vec3::sSelect(inC, inA, swap_ac);177178// Calculate normal179Vec3 ab = inB - a;180Vec3 ac = c - a;181Vec3 n = ab.Cross(ac);182float n_len_sq = n.LengthSq();183184// Check degenerate185if (n_len_sq < 1.0e-10f) // Square(FLT_EPSILON) was too small and caused numerical problems, see test case TestCollideParallelTriangleVsCapsule186{187// Degenerate, fallback to vertices and edges188189// Start with vertex C being the closest190uint32 closest_set = 0b0100;191Vec3 closest_point = inC;192float best_dist_sq = inC.LengthSq();193194// If the closest point must include C then A or B cannot be closest195// Note that we test vertices first because we want to prefer a closest vertex over a closest edge (this results in an outSet with fewer bits set)196if constexpr (!MustIncludeC)197{198// Try vertex A199float a_len_sq = inA.LengthSq();200if (a_len_sq < best_dist_sq)201{202closest_set = 0b0001;203closest_point = inA;204best_dist_sq = a_len_sq;205}206207// Try vertex B208float b_len_sq = inB.LengthSq();209if (b_len_sq < best_dist_sq)210{211closest_set = 0b0010;212closest_point = inB;213best_dist_sq = b_len_sq;214}215}216217// Edge AC218float ac_len_sq = ac.LengthSq();219if (ac_len_sq > Square(FLT_EPSILON))220{221float v = Clamp(-a.Dot(ac) / ac_len_sq, 0.0f, 1.0f);222Vec3 q = a + v * ac;223float dist_sq = q.LengthSq();224if (dist_sq < best_dist_sq)225{226closest_set = 0b0101;227closest_point = q;228best_dist_sq = dist_sq;229}230}231232// Edge BC233Vec3 bc = inC - inB;234float bc_len_sq = bc.LengthSq();235if (bc_len_sq > Square(FLT_EPSILON))236{237float v = Clamp(-inB.Dot(bc) / bc_len_sq, 0.0f, 1.0f);238Vec3 q = inB + v * bc;239float dist_sq = q.LengthSq();240if (dist_sq < best_dist_sq)241{242closest_set = 0b0110;243closest_point = q;244best_dist_sq = dist_sq;245}246}247248// If the closest point must include C then AB cannot be closest249if constexpr (!MustIncludeC)250{251// Edge AB252ab = inB - inA;253float ab_len_sq = ab.LengthSq();254if (ab_len_sq > Square(FLT_EPSILON))255{256float v = Clamp(-inA.Dot(ab) / ab_len_sq, 0.0f, 1.0f);257Vec3 q = inA + v * ab;258float dist_sq = q.LengthSq();259if (dist_sq < best_dist_sq)260{261closest_set = 0b0011;262closest_point = q;263best_dist_sq = dist_sq;264}265}266}267268outSet = closest_set;269return closest_point;270}271272// Check if P in vertex region outside A273Vec3 ap = -a;274float d1 = ab.Dot(ap);275float d2 = ac.Dot(ap);276if (d1 <= 0.0f && d2 <= 0.0f)277{278outSet = swap_ac.GetX()? 0b0100 : 0b0001;279return a; // barycentric coordinates (1,0,0)280}281282// Check if P in vertex region outside B283Vec3 bp = -inB;284float d3 = ab.Dot(bp);285float d4 = ac.Dot(bp);286if (d3 >= 0.0f && d4 <= d3)287{288outSet = 0b0010;289return inB; // barycentric coordinates (0,1,0)290}291292// Check if P in edge region of AB, if so return projection of P onto AB293if (d1 * d4 <= d3 * d2 && d1 >= 0.0f && d3 <= 0.0f)294{295float v = d1 / (d1 - d3);296outSet = swap_ac.GetX()? 0b0110 : 0b0011;297return a + v * ab; // barycentric coordinates (1-v,v,0)298}299300// Check if P in vertex region outside C301Vec3 cp = -c;302float d5 = ab.Dot(cp);303float d6 = ac.Dot(cp);304if (d6 >= 0.0f && d5 <= d6)305{306outSet = swap_ac.GetX()? 0b0001 : 0b0100;307return c; // barycentric coordinates (0,0,1)308}309310// Check if P in edge region of AC, if so return projection of P onto AC311if (d5 * d2 <= d1 * d6 && d2 >= 0.0f && d6 <= 0.0f)312{313float w = d2 / (d2 - d6);314outSet = 0b0101;315return a + w * ac; // barycentric coordinates (1-w,0,w)316}317318// Check if P in edge region of BC, if so return projection of P onto BC319float d4_d3 = d4 - d3;320float d5_d6 = d5 - d6;321if (d3 * d6 <= d5 * d4 && d4_d3 >= 0.0f && d5_d6 >= 0.0f)322{323float w = d4_d3 / (d4_d3 + d5_d6);324outSet = swap_ac.GetX()? 0b0011 : 0b0110;325return inB + w * (c - inB); // barycentric coordinates (0,1-w,w)326}327328// P inside face region.329// Here we deviate from Christer Ericson's article to improve accuracy.330// Determine distance between triangle and origin: distance = (centroid - origin) . normal / |normal|331// Closest point to origin is then: distance . normal / |normal|332// Note that this way of calculating the closest point is much more accurate than first calculating barycentric coordinates333// and then calculating the closest point based on those coordinates.334outSet = 0b0111;335return n * (a + inB + c).Dot(n) / (3.0f * n_len_sq);336}337338/// Check if the origin is outside the plane of triangle (inA, inB, inC). inD specifies the front side of the plane.339inline bool OriginOutsideOfPlane(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, Vec3Arg inD)340{341// Taken from: Real-Time Collision Detection - Christer Ericson (Section: Closest Point on Tetrahedron to Point)342// With p = 0343344// Test if point p and d lie on opposite sides of plane through abc345Vec3 n = (inB - inA).Cross(inC - inA);346float signp = inA.Dot(n); // [AP AB AC]347float signd = (inD - inA).Dot(n); // [AD AB AC]348349// Points on opposite sides if expression signs are the same350// Note that we left out the minus sign in signp so we need to check > 0 instead of < 0 as in Christer's book351// We compare against a small negative value to allow for a little bit of slop in the calculations352return signp * signd > -FLT_EPSILON;353}354355/// Returns for each of the planes of the tetrahedron if the origin is inside it356/// Roughly equivalent to:357/// [OriginOutsideOfPlane(inA, inB, inC, inD),358/// OriginOutsideOfPlane(inA, inC, inD, inB),359/// OriginOutsideOfPlane(inA, inD, inB, inC),360/// OriginOutsideOfPlane(inB, inD, inC, inA)]361inline UVec4 OriginOutsideOfTetrahedronPlanes(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, Vec3Arg inD)362{363Vec3 ab = inB - inA;364Vec3 ac = inC - inA;365Vec3 ad = inD - inA;366Vec3 bd = inD - inB;367Vec3 bc = inC - inB;368369Vec3 ab_cross_ac = ab.Cross(ac);370Vec3 ac_cross_ad = ac.Cross(ad);371Vec3 ad_cross_ab = ad.Cross(ab);372Vec3 bd_cross_bc = bd.Cross(bc);373374// For each plane get the side on which the origin is375float signp0 = inA.Dot(ab_cross_ac); // ABC376float signp1 = inA.Dot(ac_cross_ad); // ACD377float signp2 = inA.Dot(ad_cross_ab); // ADB378float signp3 = inB.Dot(bd_cross_bc); // BDC379Vec4 signp(signp0, signp1, signp2, signp3);380381// For each plane get the side that is outside (determined by the 4th point)382float signd0 = ad.Dot(ab_cross_ac); // D383float signd1 = ab.Dot(ac_cross_ad); // B384float signd2 = ac.Dot(ad_cross_ab); // C385float signd3 = -ab.Dot(bd_cross_bc); // A386Vec4 signd(signd0, signd1, signd2, signd3);387388// The winding of all triangles has been chosen so that signd should have the389// same sign for all components. If this is not the case the tetrahedron390// is degenerate and we return that the origin is in front of all sides391int sign_bits = signd.GetSignBits();392switch (sign_bits)393{394case 0:395// All positive396return Vec4::sGreaterOrEqual(signp, Vec4::sReplicate(-FLT_EPSILON));397398case 0xf:399// All negative400return Vec4::sLessOrEqual(signp, Vec4::sReplicate(FLT_EPSILON));401402default:403// Mixed signs, degenerate tetrahedron404return UVec4::sReplicate(0xffffffff);405}406}407408/// Get the closest point between tetrahedron (inA, inB, inC, inD) to the origin409/// outSet specifies which feature was closest, 1 = a, 2 = b, 4 = c, 8 = d. Edges have 2 bits set, triangles 3 and if the point is in the interior 4 bits are set.410/// If MustIncludeD is true, the function assumes that D is part of the closest feature (vertex, edge, face, tetrahedron) and does less work, if the assumption is not true then a closest point to the other features is returned.411template <bool MustIncludeD = false>412inline Vec3 GetClosestPointOnTetrahedron(Vec3Arg inA, Vec3Arg inB, Vec3Arg inC, Vec3Arg inD, uint32 &outSet)413{414// Taken from: Real-Time Collision Detection - Christer Ericson (Section: Closest Point on Tetrahedron to Point)415// With p = 0416417// Start out assuming point inside all halfspaces, so closest to itself418uint32 closest_set = 0b1111;419Vec3 closest_point = Vec3::sZero();420float best_dist_sq = FLT_MAX;421422// Determine for each of the faces of the tetrahedron if the origin is in front of the plane423UVec4 origin_out_of_planes = OriginOutsideOfTetrahedronPlanes(inA, inB, inC, inD);424425// If point outside face abc then compute closest point on abc426if (origin_out_of_planes.GetX()) // OriginOutsideOfPlane(inA, inB, inC, inD)427{428if constexpr (MustIncludeD)429{430// If the closest point must include D then ABC cannot be closest but the closest point431// cannot be an interior point either so we return A as closest point432closest_set = 0b0001;433closest_point = inA;434}435else436{437// Test the face normally438closest_point = GetClosestPointOnTriangle<false>(inA, inB, inC, closest_set);439}440best_dist_sq = closest_point.LengthSq();441}442443// Repeat test for face acd444if (origin_out_of_planes.GetY()) // OriginOutsideOfPlane(inA, inC, inD, inB)445{446uint32 set;447Vec3 q = GetClosestPointOnTriangle<MustIncludeD>(inA, inC, inD, set);448float dist_sq = q.LengthSq();449if (dist_sq < best_dist_sq)450{451best_dist_sq = dist_sq;452closest_point = q;453closest_set = (set & 0b0001) + ((set & 0b0110) << 1);454}455}456457// Repeat test for face adb458if (origin_out_of_planes.GetZ()) // OriginOutsideOfPlane(inA, inD, inB, inC)459{460// Keep original vertex order, it doesn't matter if the triangle is facing inward or outward461// and it improves consistency for GJK which will always add a new vertex D and keep the closest462// feature from the previous iteration in ABC463uint32 set;464Vec3 q = GetClosestPointOnTriangle<MustIncludeD>(inA, inB, inD, set);465float dist_sq = q.LengthSq();466if (dist_sq < best_dist_sq)467{468best_dist_sq = dist_sq;469closest_point = q;470closest_set = (set & 0b0011) + ((set & 0b0100) << 1);471}472}473474// Repeat test for face bdc475if (origin_out_of_planes.GetW()) // OriginOutsideOfPlane(inB, inD, inC, inA)476{477// Keep original vertex order, it doesn't matter if the triangle is facing inward or outward478// and it improves consistency for GJK which will always add a new vertex D and keep the closest479// feature from the previous iteration in ABC480uint32 set;481Vec3 q = GetClosestPointOnTriangle<MustIncludeD>(inB, inC, inD, set);482float dist_sq = q.LengthSq();483if (dist_sq < best_dist_sq)484{485closest_point = q;486closest_set = set << 1;487}488}489490outSet = closest_set;491return closest_point;492}493};494495JPH_PRECISE_MATH_OFF496497JPH_NAMESPACE_END498499500