// David Eberly, Geometric Tools, Redmond WA 98052 // Copyright (c) 1998-2020 // Distributed under the Boost Software License, Version 1.0. // https://www.boost.org/LICENSE_1_0.txt // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt // Version: 4.0.2019.08.13 #pragma once #include #include #include #include #include namespace WwiseGTE { // Compute the axis of the capsule segment using least-squares fitting. // The radius is the maximum distance from the points to the axis. // Hemispherical caps are chosen as close together as possible. template bool GetContainer(int numPoints, Vector3 const* points, Capsule3& capsule) { ApprOrthogonalLine3 fitter; fitter.Fit(numPoints, points); Line3 line = fitter.GetParameters(); DCPQuery, Line3> plQuery; Real maxRadiusSqr = (Real)0; for (int i = 0; i < numPoints; ++i) { auto result = plQuery(points[i], line); if (result.sqrDistance > maxRadiusSqr) { maxRadiusSqr = result.sqrDistance; } } Vector3 basis[3]; basis[0] = line.direction; ComputeOrthogonalComplement(1, basis); Real minValue = std::numeric_limits::max(); Real maxValue = -std::numeric_limits::max(); for (int i = 0; i < numPoints; ++i) { Vector3 diff = points[i] - line.origin; Real uDotDiff = Dot(diff, basis[1]); Real vDotDiff = Dot(diff, basis[2]); Real wDotDiff = Dot(diff, basis[0]); Real discr = maxRadiusSqr - (uDotDiff * uDotDiff + vDotDiff * vDotDiff); Real radical = std::sqrt(std::max(discr, (Real)0)); Real test = wDotDiff + radical; if (test < minValue) { minValue = test; } test = wDotDiff - radical; if (test > maxValue) { maxValue = test; } } Vector3 center = line.origin + ((Real)0.5 * (minValue + maxValue)) * line.direction; Real extent; if (maxValue > minValue) { // Container is a capsule. extent = (Real)0.5 * (maxValue - minValue); } else { // Container is a sphere. extent = (Real)0; } capsule.segment = Segment3(center, line.direction, extent); capsule.radius = std::sqrt(maxRadiusSqr); return true; } // Test for containment of a point by a capsule. template bool InContainer(Vector3 const& point, Capsule3 const& capsule) { DCPQuery, Segment3> psQuery; auto result = psQuery(point, capsule.segment); return result.distance <= capsule.radius; } // Test for containment of a sphere by a capsule. template bool InContainer(Sphere3 const& sphere, Capsule3 const& capsule) { Real rDiff = capsule.radius - sphere.radius; if (rDiff >= (Real)0) { DCPQuery, Segment3> psQuery; auto result = psQuery(sphere.center, capsule.segment); return result.distance <= rDiff; } return false; } // Test for containment of a capsule by a capsule. template bool InContainer(Capsule3 const& testCapsule, Capsule3 const& capsule) { Sphere3 spherePosEnd(testCapsule.segment.p[1], testCapsule.radius); Sphere3 sphereNegEnd(testCapsule.segment.p[0], testCapsule.radius); return InContainer(spherePosEnd, capsule) && InContainer(sphereNegEnd, capsule); } // Compute a capsule that contains the input capsules. The returned // capsule is not necessarily the one of smallest volume that contains // the inputs. template bool MergeContainers(Capsule3 const& capsule0, Capsule3 const& capsule1, Capsule3& merge) { if (InContainer(capsule0, capsule1)) { merge = capsule1; return true; } if (InContainer(capsule1, capsule0)) { merge = capsule0; return true; } Vector3 P0, P1, D0, D1; Real extent0, extent1; capsule0.segment.GetCenteredForm(P0, D0, extent0); capsule1.segment.GetCenteredForm(P1, D1, extent1); // Axis of final capsule. Line3 line; // Axis center is average of input axis centers. line.origin = (Real)0.5 * (P0 + P1); // Axis unit direction is average of input axis unit directions. if (Dot(D0, D1) >= (Real)0) { line.direction = D0 + D1; } else { line.direction = D0 - D1; } Normalize(line.direction); // Cylinder with axis 'line' must contain the spheres centered at the // endpoints of the input capsules. DCPQuery, Line3> plQuery; Vector3 posEnd0 = capsule0.segment.p[1]; Real radius = plQuery(posEnd0, line).distance + capsule0.radius; Vector3 negEnd0 = capsule0.segment.p[0]; Real tmp = plQuery(negEnd0, line).distance + capsule0.radius; Vector3 posEnd1 = capsule1.segment.p[1]; tmp = plQuery(posEnd1, line).distance + capsule1.radius; if (tmp > radius) { radius = tmp; } Vector3 negEnd1 = capsule1.segment.p[0]; tmp = plQuery(negEnd1, line).distance + capsule1.radius; if (tmp > radius) { radius = tmp; } // In the following blocks of code, theoretically k1*k1-k0 >= 0, but // numerical rounding errors can make it slightly negative. Guard // against this. // Process sphere . Real rDiff = radius - capsule0.radius; Real rDiffSqr = rDiff * rDiff; Vector3 diff = line.origin - posEnd0; Real k0 = Dot(diff, diff) - rDiffSqr; Real k1 = Dot(diff, line.direction); Real discr = k1 * k1 - k0; Real root = std::sqrt(std::max(discr, (Real)0)); Real tPos = -k1 - root; Real tNeg = -k1 + root; // Process sphere . diff = line.origin - negEnd0; k0 = Dot(diff, diff) - rDiffSqr; k1 = Dot(diff, line.direction); discr = k1 * k1 - k0; root = std::sqrt(std::max(discr, (Real)0)); tmp = -k1 - root; if (tmp > tPos) { tPos = tmp; } tmp = -k1 + root; if (tmp < tNeg) { tNeg = tmp; } // Process sphere . rDiff = radius - capsule1.radius; rDiffSqr = rDiff * rDiff; diff = line.origin - posEnd1; k0 = Dot(diff, diff) - rDiffSqr; k1 = Dot(diff, line.direction); discr = k1 * k1 - k0; root = std::sqrt(std::max(discr, (Real)0)); tmp = -k1 - root; if (tmp > tPos) { tPos = tmp; } tmp = -k1 + root; if (tmp < tNeg) { tNeg = tmp; } // Process sphere . diff = line.origin - negEnd1; k0 = Dot(diff, diff) - rDiffSqr; k1 = Dot(diff, line.direction); discr = k1 * k1 - k0; root = std::sqrt(std::max(discr, (Real)0)); tmp = -k1 - root; if (tmp > tPos) { tPos = tmp; } tmp = -k1 + root; if (tmp < tNeg) { tNeg = tmp; } Vector3 center = line.origin + (Real)0.5 * (tPos + tNeg) * line.direction; Real extent; if (tPos > tNeg) { // Container is a capsule. extent = (Real)0.5 * (tPos - tNeg); } else { // Container is a sphere. extent = (Real)0; } merge.segment = Segment3(center, line.direction, extent); merge.radius = radius; return true; } }