Path: blob/master/thirdparty/jolt_physics/Jolt/Geometry/GJKClosestPoint.h
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#pragma once56#include <Jolt/Core/NonCopyable.h>7#include <Jolt/Geometry/ClosestPoint.h>8#include <Jolt/Geometry/ConvexSupport.h>910//#define JPH_GJK_DEBUG11#ifdef JPH_GJK_DEBUG12#include <Jolt/Core/StringTools.h>13#include <Jolt/Renderer/DebugRenderer.h>14#endif1516JPH_NAMESPACE_BEGIN1718/// Convex vs convex collision detection19/// Based on: A Fast and Robust GJK Implementation for Collision Detection of Convex Objects - Gino van den Bergen20class GJKClosestPoint : public NonCopyable21{22private:23/// Get new closest point to origin given simplex mY of mNumPoints points24///25/// @param inPrevVLenSq Length of |outV|^2 from the previous iteration, used as a maximum value when selecting a new closest point.26/// @param outV Closest point27/// @param outVLenSq |outV|^228/// @param outSet Set of points that form the new simplex closest to the origin (bit 1 = mY[0], bit 2 = mY[1], ...)29///30/// If LastPointPartOfClosestFeature is true then the last point added will be assumed to be part of the closest feature and the function will do less work.31///32/// @return True if new closest point was found.33/// False if the function failed, in this case the output variables are not modified34template <bool LastPointPartOfClosestFeature>35bool GetClosest(float inPrevVLenSq, Vec3 &outV, float &outVLenSq, uint32 &outSet) const36{37#ifdef JPH_GJK_DEBUG38for (int i = 0; i < mNumPoints; ++i)39Trace("y[%d] = [%s], |y[%d]| = %g", i, ConvertToString(mY[i]).c_str(), i, (double)mY[i].Length());40#endif4142uint32 set;43Vec3 v;4445switch (mNumPoints)46{47case 1:48// Single point49set = 0b0001;50v = mY[0];51break;5253case 2:54// Line segment55v = ClosestPoint::GetClosestPointOnLine(mY[0], mY[1], set);56break;5758case 3:59// Triangle60v = ClosestPoint::GetClosestPointOnTriangle<LastPointPartOfClosestFeature>(mY[0], mY[1], mY[2], set);61break;6263case 4:64// Tetrahedron65v = ClosestPoint::GetClosestPointOnTetrahedron<LastPointPartOfClosestFeature>(mY[0], mY[1], mY[2], mY[3], set);66break;6768default:69JPH_ASSERT(false);70return false;71}7273#ifdef JPH_GJK_DEBUG74Trace("GetClosest: set = 0b%s, v = [%s], |v| = %g", NibbleToBinary(set), ConvertToString(v).c_str(), (double)v.Length());75#endif7677float v_len_sq = v.LengthSq();78if (v_len_sq < inPrevVLenSq) // Note, comparison order important: If v_len_sq is NaN then this expression will be false so we will return false79{80// Return closest point81outV = v;82outVLenSq = v_len_sq;83outSet = set;84return true;85}8687// No better match found88#ifdef JPH_GJK_DEBUG89Trace("New closer point is further away, failed to converge");90#endif91return false;92}9394// Get max(|Y_0|^2 .. |Y_n|^2)95float GetMaxYLengthSq() const96{97float y_len_sq = mY[0].LengthSq();98for (int i = 1; i < mNumPoints; ++i)99y_len_sq = max(y_len_sq, mY[i].LengthSq());100return y_len_sq;101}102103// Remove points that are not in the set, only updates mY104void UpdatePointSetY(uint32 inSet)105{106int num_points = 0;107for (int i = 0; i < mNumPoints; ++i)108if ((inSet & (1 << i)) != 0)109{110mY[num_points] = mY[i];111++num_points;112}113mNumPoints = num_points;114}115116// Remove points that are not in the set, only updates mP117void UpdatePointSetP(uint32 inSet)118{119int num_points = 0;120for (int i = 0; i < mNumPoints; ++i)121if ((inSet & (1 << i)) != 0)122{123mP[num_points] = mP[i];124++num_points;125}126mNumPoints = num_points;127}128129// Remove points that are not in the set, only updates mP and mQ130void UpdatePointSetPQ(uint32 inSet)131{132int num_points = 0;133for (int i = 0; i < mNumPoints; ++i)134if ((inSet & (1 << i)) != 0)135{136mP[num_points] = mP[i];137mQ[num_points] = mQ[i];138++num_points;139}140mNumPoints = num_points;141}142143// Remove points that are not in the set, updates mY, mP and mQ144void UpdatePointSetYPQ(uint32 inSet)145{146int num_points = 0;147for (int i = 0; i < mNumPoints; ++i)148if ((inSet & (1 << i)) != 0)149{150mY[num_points] = mY[i];151mP[num_points] = mP[i];152mQ[num_points] = mQ[i];153++num_points;154}155mNumPoints = num_points;156}157158// Calculate closest points on A and B159void CalculatePointAAndB(Vec3 &outPointA, Vec3 &outPointB) const160{161switch (mNumPoints)162{163case 1:164outPointA = mP[0];165outPointB = mQ[0];166break;167168case 2:169{170float u, v;171ClosestPoint::GetBaryCentricCoordinates(mY[0], mY[1], u, v);172outPointA = u * mP[0] + v * mP[1];173outPointB = u * mQ[0] + v * mQ[1];174}175break;176177case 3:178{179float u, v, w;180ClosestPoint::GetBaryCentricCoordinates(mY[0], mY[1], mY[2], u, v, w);181outPointA = u * mP[0] + v * mP[1] + w * mP[2];182outPointB = u * mQ[0] + v * mQ[1] + w * mQ[2];183}184break;185186case 4:187#ifdef JPH_DEBUG188memset(&outPointA, 0xcd, sizeof(outPointA));189memset(&outPointB, 0xcd, sizeof(outPointB));190#endif191break;192}193}194195public:196/// Test if inA and inB intersect197///198/// @param inA The convex object A, must support the GetSupport(Vec3) function.199/// @param inB The convex object B, must support the GetSupport(Vec3) function.200/// @param inTolerance Minimal distance between objects when the objects are considered to be colliding201/// @param ioV is used as initial separating axis (provide a zero vector if you don't know yet)202///203/// @return True if they intersect (in which case ioV = (0, 0, 0)).204/// False if they don't intersect in which case ioV is a separating axis in the direction from A to B (magnitude is meaningless)205template <typename A, typename B>206bool Intersects(const A &inA, const B &inB, float inTolerance, Vec3 &ioV)207{208float tolerance_sq = Square(inTolerance);209210// Reset state211mNumPoints = 0;212213#ifdef JPH_GJK_DEBUG214for (int i = 0; i < 4; ++i)215mY[i] = Vec3::sZero();216#endif217218// Previous length^2 of v219float prev_v_len_sq = FLT_MAX;220221for (;;)222{223#ifdef JPH_GJK_DEBUG224Trace("v = [%s], num_points = %d", ConvertToString(ioV).c_str(), mNumPoints);225#endif226227// Get support points for shape A and B in the direction of v228Vec3 p = inA.GetSupport(ioV);229Vec3 q = inB.GetSupport(-ioV);230231// Get support point of the minkowski sum A - B of v232Vec3 w = p - q;233234// If the support point sA-B(v) is in the opposite direction as v, then we have found a separating axis and there is no intersection235if (ioV.Dot(w) < 0.0f)236{237// Separating axis found238#ifdef JPH_GJK_DEBUG239Trace("Separating axis");240#endif241return false;242}243244// Store the point for later use245mY[mNumPoints] = w;246++mNumPoints;247248#ifdef JPH_GJK_DEBUG249Trace("w = [%s]", ConvertToString(w).c_str());250#endif251252// Determine the new closest point253float v_len_sq; // Length^2 of v254uint32 set; // Set of points that form the new simplex255if (!GetClosest<true>(prev_v_len_sq, ioV, v_len_sq, set))256return false;257258// If there are 4 points, the origin is inside the tetrahedron and we're done259if (set == 0xf)260{261#ifdef JPH_GJK_DEBUG262Trace("Full simplex");263#endif264ioV = Vec3::sZero();265return true;266}267268// If v is very close to zero, we consider this a collision269if (v_len_sq <= tolerance_sq)270{271#ifdef JPH_GJK_DEBUG272Trace("Distance zero");273#endif274ioV = Vec3::sZero();275return true;276}277278// If v is very small compared to the length of y, we also consider this a collision279if (v_len_sq <= FLT_EPSILON * GetMaxYLengthSq())280{281#ifdef JPH_GJK_DEBUG282Trace("Machine precision reached");283#endif284ioV = Vec3::sZero();285return true;286}287288// The next separation axis to test is the negative of the closest point of the Minkowski sum to the origin289// Note: This must be done before terminating as converged since the separating axis is -v290ioV = -ioV;291292// If the squared length of v is not changing enough, we've converged and there is no collision293JPH_ASSERT(prev_v_len_sq >= v_len_sq);294if (prev_v_len_sq - v_len_sq <= FLT_EPSILON * prev_v_len_sq)295{296// v is a separating axis297#ifdef JPH_GJK_DEBUG298Trace("Converged");299#endif300return false;301}302prev_v_len_sq = v_len_sq;303304// Update the points of the simplex305UpdatePointSetY(set);306}307}308309/// Get closest points between inA and inB310///311/// @param inA The convex object A, must support the GetSupport(Vec3) function.312/// @param inB The convex object B, must support the GetSupport(Vec3) function.313/// @param inTolerance The minimal distance between A and B before the objects are considered colliding and processing is terminated.314/// @param inMaxDistSq The maximum squared distance between A and B before the objects are considered infinitely far away and processing is terminated.315/// @param ioV Initial guess for the separating axis. Start with any non-zero vector if you don't know.316/// If return value is 0, ioV = (0, 0, 0).317/// If the return value is bigger than 0 but smaller than FLT_MAX, ioV will be the separating axis in the direction from A to B and its length the squared distance between A and B.318/// If the return value is FLT_MAX, ioV will be the separating axis in the direction from A to B and the magnitude of the vector is meaningless.319/// @param outPointA , outPointB320/// If the return value is 0 the points are invalid.321/// If the return value is bigger than 0 but smaller than FLT_MAX these will contain the closest point on A and B.322/// If the return value is FLT_MAX the points are invalid.323///324/// @return The squared distance between A and B or FLT_MAX when they are further away than inMaxDistSq.325template <typename A, typename B>326float GetClosestPoints(const A &inA, const B &inB, float inTolerance, float inMaxDistSq, Vec3 &ioV, Vec3 &outPointA, Vec3 &outPointB)327{328float tolerance_sq = Square(inTolerance);329330// Reset state331mNumPoints = 0;332333#ifdef JPH_GJK_DEBUG334// Generate the hull of the Minkowski difference for visualization335MinkowskiDifference diff(inA, inB);336mGeometry = DebugRenderer::sInstance->CreateTriangleGeometryForConvex([&diff](Vec3Arg inDirection) { return diff.GetSupport(inDirection); });337338for (int i = 0; i < 4; ++i)339{340mY[i] = Vec3::sZero();341mP[i] = Vec3::sZero();342mQ[i] = Vec3::sZero();343}344#endif345346// Length^2 of v347float v_len_sq = ioV.LengthSq();348349// Previous length^2 of v350float prev_v_len_sq = FLT_MAX;351352for (;;)353{354#ifdef JPH_GJK_DEBUG355Trace("v = [%s], num_points = %d", ConvertToString(ioV).c_str(), mNumPoints);356#endif357358// Get support points for shape A and B in the direction of v359Vec3 p = inA.GetSupport(ioV);360Vec3 q = inB.GetSupport(-ioV);361362// Get support point of the minkowski sum A - B of v363Vec3 w = p - q;364365float dot = ioV.Dot(w);366367#ifdef JPH_GJK_DEBUG368// Draw -ioV to show the closest point to the origin from the previous simplex369DebugRenderer::sInstance->DrawArrow(mOffset, mOffset - ioV, Color::sOrange, 0.05f);370371// Draw ioV to show where we're probing next372DebugRenderer::sInstance->DrawArrow(mOffset, mOffset + ioV, Color::sCyan, 0.05f);373374// Draw w, the support point375DebugRenderer::sInstance->DrawArrow(mOffset, mOffset + w, Color::sGreen, 0.05f);376DebugRenderer::sInstance->DrawMarker(mOffset + w, Color::sGreen, 1.0f);377378// Draw the simplex and the Minkowski difference around it379DrawState();380#endif381382// Test if we have a separation of more than inMaxDistSq, in which case we terminate early383if (dot < 0.0f && dot * dot > v_len_sq * inMaxDistSq)384{385#ifdef JPH_GJK_DEBUG386Trace("Distance bigger than max");387#endif388#ifdef JPH_DEBUG389memset(&outPointA, 0xcd, sizeof(outPointA));390memset(&outPointB, 0xcd, sizeof(outPointB));391#endif392return FLT_MAX;393}394395// Store the point for later use396mY[mNumPoints] = w;397mP[mNumPoints] = p;398mQ[mNumPoints] = q;399++mNumPoints;400401#ifdef JPH_GJK_DEBUG402Trace("w = [%s]", ConvertToString(w).c_str());403#endif404405uint32 set;406if (!GetClosest<true>(prev_v_len_sq, ioV, v_len_sq, set))407{408--mNumPoints; // Undo add last point409break;410}411412// If there are 4 points, the origin is inside the tetrahedron and we're done413if (set == 0xf)414{415#ifdef JPH_GJK_DEBUG416Trace("Full simplex");417#endif418ioV = Vec3::sZero();419v_len_sq = 0.0f;420break;421}422423// Update the points of the simplex424UpdatePointSetYPQ(set);425426// If v is very close to zero, we consider this a collision427if (v_len_sq <= tolerance_sq)428{429#ifdef JPH_GJK_DEBUG430Trace("Distance zero");431#endif432ioV = Vec3::sZero();433v_len_sq = 0.0f;434break;435}436437// If v is very small compared to the length of y, we also consider this a collision438#ifdef JPH_GJK_DEBUG439Trace("Check v small compared to y: %g <= %g", (double)v_len_sq, (double)(FLT_EPSILON * GetMaxYLengthSq()));440#endif441if (v_len_sq <= FLT_EPSILON * GetMaxYLengthSq())442{443#ifdef JPH_GJK_DEBUG444Trace("Machine precision reached");445#endif446ioV = Vec3::sZero();447v_len_sq = 0.0f;448break;449}450451// The next separation axis to test is the negative of the closest point of the Minkowski sum to the origin452// Note: This must be done before terminating as converged since the separating axis is -v453ioV = -ioV;454455// If the squared length of v is not changing enough, we've converged and there is no collision456#ifdef JPH_GJK_DEBUG457Trace("Check v not changing enough: %g <= %g", (double)(prev_v_len_sq - v_len_sq), (double)(FLT_EPSILON * prev_v_len_sq));458#endif459JPH_ASSERT(prev_v_len_sq >= v_len_sq);460if (prev_v_len_sq - v_len_sq <= FLT_EPSILON * prev_v_len_sq)461{462// v is a separating axis463#ifdef JPH_GJK_DEBUG464Trace("Converged");465#endif466break;467}468prev_v_len_sq = v_len_sq;469}470471// Get the closest points472CalculatePointAAndB(outPointA, outPointB);473474#ifdef JPH_GJK_DEBUG475Trace("Return: v = [%s], |v| = %g", ConvertToString(ioV).c_str(), (double)ioV.Length());476477// Draw -ioV to show the closest point to the origin from the previous simplex478DebugRenderer::sInstance->DrawArrow(mOffset, mOffset - ioV, Color::sOrange, 0.05f);479480// Draw the closest points481DebugRenderer::sInstance->DrawMarker(mOffset + outPointA, Color::sGreen, 1.0f);482DebugRenderer::sInstance->DrawMarker(mOffset + outPointB, Color::sPurple, 1.0f);483484// Draw the simplex and the Minkowski difference around it485DrawState();486#endif487488JPH_ASSERT(ioV.LengthSq() == v_len_sq);489return v_len_sq;490}491492/// Get the resulting simplex after the GetClosestPoints algorithm finishes.493/// If it returned a squared distance of 0, the origin will be contained in the simplex.494void GetClosestPointsSimplex(Vec3 *outY, Vec3 *outP, Vec3 *outQ, uint &outNumPoints) const495{496uint size = sizeof(Vec3) * mNumPoints;497memcpy(outY, mY, size);498memcpy(outP, mP, size);499memcpy(outQ, mQ, size);500outNumPoints = mNumPoints;501}502503/// Test if a ray inRayOrigin + lambda * inRayDirection for lambda e [0, ioLambda> intersects inA504///505/// Code based upon: Ray Casting against General Convex Objects with Application to Continuous Collision Detection - Gino van den Bergen506///507/// @param inRayOrigin Origin of the ray508/// @param inRayDirection Direction of the ray (ioLambda * inDirection determines length)509/// @param inTolerance The minimal distance between the ray and A before it is considered colliding510/// @param inA A convex object that has the GetSupport(Vec3) function511/// @param ioLambda The max fraction along the ray, on output updated with the actual collision fraction.512///513/// @return true if a hit was found, ioLambda is the solution for lambda.514template <typename A>515bool CastRay(Vec3Arg inRayOrigin, Vec3Arg inRayDirection, float inTolerance, const A &inA, float &ioLambda)516{517float tolerance_sq = Square(inTolerance);518519// Reset state520mNumPoints = 0;521522float lambda = 0.0f;523Vec3 x = inRayOrigin;524Vec3 v = x - inA.GetSupport(Vec3::sZero());525float v_len_sq = FLT_MAX;526bool allow_restart = false;527528for (;;)529{530#ifdef JPH_GJK_DEBUG531Trace("v = [%s], num_points = %d", ConvertToString(v).c_str(), mNumPoints);532#endif533534// Get new support point535Vec3 p = inA.GetSupport(v);536Vec3 w = x - p;537538#ifdef JPH_GJK_DEBUG539Trace("w = [%s]", ConvertToString(w).c_str());540#endif541542float v_dot_w = v.Dot(w);543#ifdef JPH_GJK_DEBUG544Trace("v . w = %g", (double)v_dot_w);545#endif546if (v_dot_w > 0.0f)547{548// If ray and normal are in the same direction, we've passed A and there's no collision549float v_dot_r = v.Dot(inRayDirection);550#ifdef JPH_GJK_DEBUG551Trace("v . r = %g", (double)v_dot_r);552#endif553if (v_dot_r >= 0.0f)554return false;555556// Update the lower bound for lambda557float delta = v_dot_w / v_dot_r;558float old_lambda = lambda;559lambda -= delta;560#ifdef JPH_GJK_DEBUG561Trace("lambda = %g, delta = %g", (double)lambda, (double)delta);562#endif563564// If lambda didn't change, we cannot converge any further and we assume a hit565if (old_lambda == lambda)566break;567568// If lambda is bigger or equal than max, we don't have a hit569if (lambda >= ioLambda)570return false;571572// Update x to new closest point on the ray573x = inRayOrigin + lambda * inRayDirection;574575// We've shifted x, so reset v_len_sq so that it is not used as early out for GetClosest576v_len_sq = FLT_MAX;577578// We allow rebuilding the simplex once after x changes because the simplex was built579// for another x and numerical round off builds up as you keep adding points to an580// existing simplex581allow_restart = true;582}583584// Add p to set P: P = P U {p}585mP[mNumPoints] = p;586++mNumPoints;587588// Calculate Y = {x} - P589for (int i = 0; i < mNumPoints; ++i)590mY[i] = x - mP[i];591592// Determine the new closest point from Y to origin593uint32 set; // Set of points that form the new simplex594if (!GetClosest<false>(v_len_sq, v, v_len_sq, set))595{596#ifdef JPH_GJK_DEBUG597Trace("Failed to converge");598#endif599600// Only allow 1 restart, if we still can't get a closest point601// we're so close that we return this as a hit602if (!allow_restart)603break;604605// If we fail to converge, we start again with the last point as simplex606#ifdef JPH_GJK_DEBUG607Trace("Restarting");608#endif609allow_restart = false;610mP[0] = p;611mNumPoints = 1;612v = x - p;613v_len_sq = FLT_MAX;614continue;615}616else if (set == 0xf)617{618#ifdef JPH_GJK_DEBUG619Trace("Full simplex");620#endif621622// We're inside the tetrahedron, we have a hit (verify that length of v is 0)623JPH_ASSERT(v_len_sq == 0.0f);624break;625}626627// Update the points P to form the new simplex628// Note: We're not updating Y as Y will shift with x so we have to calculate it every iteration629UpdatePointSetP(set);630631// Check if x is close enough to inA632if (v_len_sq <= tolerance_sq)633{634#ifdef JPH_GJK_DEBUG635Trace("Converged");636#endif637break;638}639}640641// Store hit fraction642ioLambda = lambda;643return true;644}645646/// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> intersects inB647///648/// @param inStart Start position and orientation of the convex object649/// @param inDirection Direction of the sweep (ioLambda * inDirection determines length)650/// @param inTolerance The minimal distance between A and B before they are considered colliding651/// @param inA The convex object A, must support the GetSupport(Vec3) function.652/// @param inB The convex object B, must support the GetSupport(Vec3) function.653/// @param ioLambda The max fraction along the sweep, on output updated with the actual collision fraction.654///655/// @return true if a hit was found, ioLambda is the solution for lambda.656template <typename A, typename B>657bool CastShape(Mat44Arg inStart, Vec3Arg inDirection, float inTolerance, const A &inA, const B &inB, float &ioLambda)658{659// Transform the shape to be cast to the starting position660TransformedConvexObject transformed_a(inStart, inA);661662// Calculate the minkowski difference inB - inA663// inA is moving, so we need to add the back side of inB to the front side of inA664MinkowskiDifference difference(inB, transformed_a);665666// Do a raycast against the Minkowski difference667return CastRay(Vec3::sZero(), inDirection, inTolerance, difference, ioLambda);668}669670/// Test if a cast shape inA moving from inStart to lambda * inStart.GetTranslation() + inDirection where lambda e [0, ioLambda> intersects inB671///672/// @param inStart Start position and orientation of the convex object673/// @param inDirection Direction of the sweep (ioLambda * inDirection determines length)674/// @param inTolerance The minimal distance between A and B before they are considered colliding675/// @param inA The convex object A, must support the GetSupport(Vec3) function.676/// @param inB The convex object B, must support the GetSupport(Vec3) function.677/// @param inConvexRadiusA The convex radius of A, this will be added on all sides to pad A.678/// @param inConvexRadiusB The convex radius of B, this will be added on all sides to pad B.679/// @param ioLambda The max fraction along the sweep, on output updated with the actual collision fraction.680/// @param outPointA is the contact point on A (if outSeparatingAxis is near zero, this may not be not the deepest point)681/// @param outPointB is the contact point on B (if outSeparatingAxis is near zero, this may not be not the deepest point)682/// @param outSeparatingAxis On return this will contain a vector that points from A to B along the smallest distance of separation.683/// The length of this vector indicates the separation of A and B without their convex radius.684/// If it is near zero, the direction may not be accurate as the bodies may overlap when lambda = 0.685///686/// @return true if a hit was found, ioLambda is the solution for lambda and outPoint and outSeparatingAxis are valid.687template <typename A, typename B>688bool CastShape(Mat44Arg inStart, Vec3Arg inDirection, float inTolerance, const A &inA, const B &inB, float inConvexRadiusA, float inConvexRadiusB, float &ioLambda, Vec3 &outPointA, Vec3 &outPointB, Vec3 &outSeparatingAxis)689{690float tolerance_sq = Square(inTolerance);691692// Calculate how close A and B (without their convex radius) need to be to each other in order for us to consider this a collision693float sum_convex_radius = inConvexRadiusA + inConvexRadiusB;694695// Transform the shape to be cast to the starting position696TransformedConvexObject transformed_a(inStart, inA);697698// Reset state699mNumPoints = 0;700701float lambda = 0.0f;702Vec3 x = Vec3::sZero(); // Since A is already transformed we can start the cast from zero703Vec3 v = -inB.GetSupport(Vec3::sZero()) + transformed_a.GetSupport(Vec3::sZero()); // See CastRay: v = x - inA.GetSupport(Vec3::sZero()) where inA is the Minkowski difference inB - transformed_a (see CastShape above) and x is zero704float v_len_sq = FLT_MAX;705bool allow_restart = false;706707// Keeps track of separating axis of the previous iteration.708// Initialized at zero as we don't know if our first v is actually a separating axis.709Vec3 prev_v = Vec3::sZero();710711for (;;)712{713#ifdef JPH_GJK_DEBUG714Trace("v = [%s], num_points = %d", ConvertToString(v).c_str(), mNumPoints);715#endif716717// Calculate the minkowski difference inB - inA718// inA is moving, so we need to add the back side of inB to the front side of inA719// Keep the support points on A and B separate so that in the end we can calculate a contact point720Vec3 p = transformed_a.GetSupport(-v);721Vec3 q = inB.GetSupport(v);722Vec3 w = x - (q - p);723724#ifdef JPH_GJK_DEBUG725Trace("w = [%s]", ConvertToString(w).c_str());726#endif727728// Difference from article to this code:729// We did not include the convex radius in p and q in order to be able to calculate a good separating axis at the end of the algorithm.730// However when moving forward along inDirection we do need to take this into account so that we keep A and B separated by the sum of their convex radii.731// From p we have to subtract: inConvexRadiusA * v / |v|732// To q we have to add: inConvexRadiusB * v / |v|733// This means that to w we have to add: -(inConvexRadiusA + inConvexRadiusB) * v / |v|734// So to v . w we have to add: v . (-(inConvexRadiusA + inConvexRadiusB) * v / |v|) = -(inConvexRadiusA + inConvexRadiusB) * |v|735float v_dot_w = v.Dot(w) - sum_convex_radius * v.Length();736#ifdef JPH_GJK_DEBUG737Trace("v . w = %g", (double)v_dot_w);738#endif739if (v_dot_w > 0.0f)740{741// If ray and normal are in the same direction, we've passed A and there's no collision742float v_dot_r = v.Dot(inDirection);743#ifdef JPH_GJK_DEBUG744Trace("v . r = %g", (double)v_dot_r);745#endif746if (v_dot_r >= 0.0f)747return false;748749// Update the lower bound for lambda750float delta = v_dot_w / v_dot_r;751float old_lambda = lambda;752lambda -= delta;753#ifdef JPH_GJK_DEBUG754Trace("lambda = %g, delta = %g", (double)lambda, (double)delta);755#endif756757// If lambda didn't change, we cannot converge any further and we assume a hit758if (old_lambda == lambda)759break;760761// If lambda is bigger or equal than max, we don't have a hit762if (lambda >= ioLambda)763return false;764765// Update x to new closest point on the ray766x = lambda * inDirection;767768// We've shifted x, so reset v_len_sq so that it is not used as early out when GetClosest returns false769v_len_sq = FLT_MAX;770771// Now that we've moved, we know that A and B are not intersecting at lambda = 0, so we can update our tolerance to stop iterating772// as soon as A and B are inConvexRadiusA + inConvexRadiusB apart773tolerance_sq = Square(inTolerance + sum_convex_radius);774775// We allow rebuilding the simplex once after x changes because the simplex was built776// for another x and numerical round off builds up as you keep adding points to an777// existing simplex778allow_restart = true;779}780781// Add p to set P, q to set Q: P = P U {p}, Q = Q U {q}782mP[mNumPoints] = p;783mQ[mNumPoints] = q;784++mNumPoints;785786// Calculate Y = {x} - (Q - P)787for (int i = 0; i < mNumPoints; ++i)788mY[i] = x - (mQ[i] - mP[i]);789790// Determine the new closest point from Y to origin791uint32 set; // Set of points that form the new simplex792if (!GetClosest<false>(v_len_sq, v, v_len_sq, set))793{794#ifdef JPH_GJK_DEBUG795Trace("Failed to converge");796#endif797798// Only allow 1 restart, if we still can't get a closest point799// we're so close that we return this as a hit800if (!allow_restart)801break;802803// If we fail to converge, we start again with the last point as simplex804#ifdef JPH_GJK_DEBUG805Trace("Restarting");806#endif807allow_restart = false;808mP[0] = p;809mQ[0] = q;810mNumPoints = 1;811v = x - q;812v_len_sq = FLT_MAX;813continue;814}815else if (set == 0xf)816{817#ifdef JPH_GJK_DEBUG818Trace("Full simplex");819#endif820821// We're inside the tetrahedron, we have a hit (verify that length of v is 0)822JPH_ASSERT(v_len_sq == 0.0f);823break;824}825826// Update the points P and Q to form the new simplex827// Note: We're not updating Y as Y will shift with x so we have to calculate it every iteration828UpdatePointSetPQ(set);829830// Check if A and B are touching according to our tolerance831if (v_len_sq <= tolerance_sq)832{833#ifdef JPH_GJK_DEBUG834Trace("Converged");835#endif836break;837}838839// Store our v to return as separating axis840prev_v = v;841}842843// Calculate Y = {x} - (Q - P) again so we can calculate the contact points844for (int i = 0; i < mNumPoints; ++i)845mY[i] = x - (mQ[i] - mP[i]);846847// Calculate the offset we need to apply to A and B to correct for the convex radius848Vec3 normalized_v = v.NormalizedOr(Vec3::sZero());849Vec3 convex_radius_a = inConvexRadiusA * normalized_v;850Vec3 convex_radius_b = inConvexRadiusB * normalized_v;851852// Get the contact point853// Note that A and B will coincide when lambda > 0. In this case we calculate only B as it is more accurate as it contains less terms.854switch (mNumPoints)855{856case 1:857outPointB = mQ[0] + convex_radius_b;858outPointA = lambda > 0.0f? outPointB : mP[0] - convex_radius_a;859break;860861case 2:862{863float bu, bv;864ClosestPoint::GetBaryCentricCoordinates(mY[0], mY[1], bu, bv);865outPointB = bu * mQ[0] + bv * mQ[1] + convex_radius_b;866outPointA = lambda > 0.0f? outPointB : bu * mP[0] + bv * mP[1] - convex_radius_a;867}868break;869870case 3:871case 4: // A full simplex, we can't properly determine a contact point! As contact point we take the closest point of the previous iteration.872{873float bu, bv, bw;874ClosestPoint::GetBaryCentricCoordinates(mY[0], mY[1], mY[2], bu, bv, bw);875outPointB = bu * mQ[0] + bv * mQ[1] + bw * mQ[2] + convex_radius_b;876outPointA = lambda > 0.0f? outPointB : bu * mP[0] + bv * mP[1] + bw * mP[2] - convex_radius_a;877}878break;879}880881// Store separating axis, in case we have a convex radius we can just return v,882// otherwise v will be very small and we resort to returning previous v as an approximation.883outSeparatingAxis = sum_convex_radius > 0.0f? -v : -prev_v;884885// Store hit fraction886ioLambda = lambda;887return true;888}889890private:891#ifdef JPH_GJK_DEBUG892/// Draw state of algorithm893void DrawState()894{895RMat44 origin = RMat44::sTranslation(mOffset);896897// Draw origin898DebugRenderer::sInstance->DrawCoordinateSystem(origin, 1.0f);899900// Draw the hull901DebugRenderer::sInstance->DrawGeometry(origin, mGeometry->mBounds.Transformed(origin), mGeometry->mBounds.GetExtent().LengthSq(), Color::sYellow, mGeometry);902903// Draw Y904for (int i = 0; i < mNumPoints; ++i)905{906// Draw support point907RVec3 y_i = origin * mY[i];908DebugRenderer::sInstance->DrawMarker(y_i, Color::sRed, 1.0f);909for (int j = i + 1; j < mNumPoints; ++j)910{911// Draw edge912RVec3 y_j = origin * mY[j];913DebugRenderer::sInstance->DrawLine(y_i, y_j, Color::sRed);914for (int k = j + 1; k < mNumPoints; ++k)915{916// Make sure triangle faces the origin917RVec3 y_k = origin * mY[k];918RVec3 center = (y_i + y_j + y_k) / Real(3);919RVec3 normal = (y_j - y_i).Cross(y_k - y_i);920if (normal.Dot(center) < Real(0))921DebugRenderer::sInstance->DrawTriangle(y_i, y_j, y_k, Color::sLightGrey);922else923DebugRenderer::sInstance->DrawTriangle(y_i, y_k, y_j, Color::sLightGrey);924}925}926}927928// Offset to the right929mOffset += Vec3(mGeometry->mBounds.GetSize().GetX() + 2.0f, 0, 0);930}931#endif // JPH_GJK_DEBUG932933Vec3 mY[4]; ///< Support points on A - B934Vec3 mP[4]; ///< Support point on A935Vec3 mQ[4]; ///< Support point on B936int mNumPoints = 0; ///< Number of points in mY, mP and mQ that are valid937938#ifdef JPH_GJK_DEBUG939DebugRenderer::GeometryRef mGeometry; ///< A visualization of the minkowski difference for state drawing940RVec3 mOffset = RVec3::sZero(); ///< Offset to use for state drawing941#endif942};943944JPH_NAMESPACE_END945946947