Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/CastSphereVsTriangles.cpp
9913 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#include <Jolt/Jolt.h>
6
7
#include <Jolt/Physics/Collision/CastSphereVsTriangles.h>
8
#include <Jolt/Physics/Collision/TransformedShape.h>
9
#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>
10
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
11
#include <Jolt/Physics/Collision/ActiveEdges.h>
12
#include <Jolt/Physics/Collision/NarrowPhaseStats.h>
13
#include <Jolt/Geometry/ClosestPoint.h>
14
#include <Jolt/Geometry/RaySphere.h>
15
#include <Jolt/Core/Profiler.h>
16
17
JPH_NAMESPACE_BEGIN
18
19
CastSphereVsTriangles::CastSphereVsTriangles(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, CastShapeCollector &ioCollector) :
20
mStart(inShapeCast.mCenterOfMassStart.GetTranslation()),
21
mDirection(inShapeCast.mDirection),
22
mShapeCastSettings(inShapeCastSettings),
23
mCenterOfMassTransform2(inCenterOfMassTransform2),
24
mScale(inScale),
25
mSubShapeIDCreator1(inSubShapeIDCreator1),
26
mCollector(ioCollector)
27
{
28
// Cast to sphere shape
29
JPH_ASSERT(inShapeCast.mShape->GetSubType() == EShapeSubType::Sphere);
30
const SphereShape *sphere = static_cast<const SphereShape *>(inShapeCast.mShape);
31
32
// Scale the radius
33
mRadius = sphere->GetRadius() * abs(inShapeCast.mScale.GetX());
34
35
// Determine if shape is inside out or not
36
mScaleSign = ScaleHelpers::IsInsideOut(inScale)? -1.0f : 1.0f;
37
}
38
39
void CastSphereVsTriangles::AddHit(bool inBackFacing, const SubShapeID &inSubShapeID2, float inFraction, Vec3Arg inContactPointA, Vec3Arg inContactPointB, Vec3Arg inContactNormal)
40
{
41
// Convert to world space
42
Vec3 contact_point_a = mCenterOfMassTransform2 * (mStart + inContactPointA);
43
Vec3 contact_point_b = mCenterOfMassTransform2 * (mStart + inContactPointB);
44
Vec3 contact_normal_world = mCenterOfMassTransform2.Multiply3x3(inContactNormal);
45
46
// Its a hit, store the sub shape id's
47
ShapeCastResult result(inFraction, contact_point_a, contact_point_b, contact_normal_world, inBackFacing, mSubShapeIDCreator1.GetID(), inSubShapeID2, TransformedShape::sGetBodyID(mCollector.GetContext()));
48
49
// Note: We don't gather faces here because that's only useful if both shapes have a face. Since the sphere always has only 1 contact point, the manifold is always a point.
50
51
JPH_IF_TRACK_NARROWPHASE_STATS(TrackNarrowPhaseCollector track;)
52
mCollector.AddHit(result);
53
}
54
55
void CastSphereVsTriangles::AddHitWithActiveEdgeDetection(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, bool inBackFacing, Vec3Arg inTriangleNormal, uint8 inActiveEdges, const SubShapeID &inSubShapeID2, float inFraction, Vec3Arg inContactPointA, Vec3Arg inContactPointB, Vec3Arg inContactNormal)
56
{
57
// Check if we have enabled active edge detection
58
Vec3 contact_normal = inContactNormal;
59
if (mShapeCastSettings.mActiveEdgeMode == EActiveEdgeMode::CollideOnlyWithActive && inActiveEdges != 0b111)
60
{
61
// Convert the active edge velocity hint to local space
62
Vec3 active_edge_movement_direction = mCenterOfMassTransform2.Multiply3x3Transposed(mShapeCastSettings.mActiveEdgeMovementDirection);
63
64
// Update the contact normal to account for active edges
65
// Note that we flip the triangle normal as the penetration axis is pointing towards the triangle instead of away
66
contact_normal = ActiveEdges::FixNormal(inV0, inV1, inV2, inBackFacing? inTriangleNormal : -inTriangleNormal, inActiveEdges, inContactPointB, inContactNormal, active_edge_movement_direction);
67
}
68
69
AddHit(inBackFacing, inSubShapeID2, inFraction, inContactPointA, inContactPointB, contact_normal);
70
}
71
72
// This is a simplified version of the ray cylinder test from: Real Time Collision Detection - Christer Ericson
73
// Chapter 5.3.7, page 194-197. Some conditions have been removed as we're not interested in hitting the caps of the cylinder.
74
// Note that the ray origin is assumed to be the origin here.
75
float CastSphereVsTriangles::RayCylinder(Vec3Arg inRayDirection, Vec3Arg inCylinderA, Vec3Arg inCylinderB, float inRadius) const
76
{
77
// Calculate cylinder axis
78
Vec3 axis = inCylinderB - inCylinderA;
79
80
// Make ray start relative to cylinder side A (moving cylinder A to the origin)
81
Vec3 start = -inCylinderA;
82
83
// Test if segment is fully on the A side of the cylinder
84
float start_dot_axis = start.Dot(axis);
85
float direction_dot_axis = inRayDirection.Dot(axis);
86
float end_dot_axis = start_dot_axis + direction_dot_axis;
87
if (start_dot_axis < 0.0f && end_dot_axis < 0.0f)
88
return FLT_MAX;
89
90
// Test if segment is fully on the B side of the cylinder
91
float axis_len_sq = axis.LengthSq();
92
if (start_dot_axis > axis_len_sq && end_dot_axis > axis_len_sq)
93
return FLT_MAX;
94
95
// Calculate a, b and c, the factors for quadratic equation
96
// We're basically solving the ray: x = start + direction * t
97
// The closest point to x on the segment A B is: w = (x . axis) * axis / (axis . axis)
98
// The distance between x and w should be radius: (x - w) . (x - w) = radius^2
99
// Solving this gives the following:
100
float a = axis_len_sq * inRayDirection.LengthSq() - Square(direction_dot_axis);
101
if (abs(a) < 1.0e-6f)
102
return FLT_MAX; // Segment runs parallel to cylinder axis, stop processing, we will either hit at fraction = 0 or we'll hit a vertex
103
float b = axis_len_sq * start.Dot(inRayDirection) - direction_dot_axis * start_dot_axis; // should be multiplied by 2, instead we'll divide a and c by 2 when we solve the quadratic equation
104
float c = axis_len_sq * (start.LengthSq() - Square(inRadius)) - Square(start_dot_axis);
105
float det = Square(b) - a * c; // normally 4 * a * c but since both a and c need to be divided by 2 we lose the 4
106
if (det < 0.0f)
107
return FLT_MAX; // No solution to quadratic equation
108
109
// Solve fraction t where the ray hits the cylinder
110
float t = -(b + sqrt(det)) / a; // normally divided by 2 * a but since a should be divided by 2 we lose the 2
111
if (t < 0.0f || t > 1.0f)
112
return FLT_MAX; // Intersection lies outside segment
113
if (start_dot_axis + t * direction_dot_axis < 0.0f || start_dot_axis + t * direction_dot_axis > axis_len_sq)
114
return FLT_MAX; // Intersection outside the end point of the cylinder, stop processing, we will possibly hit a vertex
115
return t;
116
}
117
118
void CastSphereVsTriangles::Cast(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, uint8 inActiveEdges, const SubShapeID &inSubShapeID2)
119
{
120
JPH_PROFILE_FUNCTION();
121
122
// Scale triangle and make it relative to the start of the cast
123
Vec3 v0 = mScale * inV0 - mStart;
124
Vec3 v1 = mScale * inV1 - mStart;
125
Vec3 v2 = mScale * inV2 - mStart;
126
127
// Calculate triangle normal
128
Vec3 triangle_normal = mScaleSign * (v1 - v0).Cross(v2 - v0);
129
float triangle_normal_len = triangle_normal.Length();
130
if (triangle_normal_len == 0.0f)
131
return; // Degenerate triangle
132
triangle_normal /= triangle_normal_len;
133
134
// Backface check
135
float normal_dot_direction = triangle_normal.Dot(mDirection);
136
bool back_facing = normal_dot_direction > 0.0f;
137
if (mShapeCastSettings.mBackFaceModeTriangles == EBackFaceMode::IgnoreBackFaces && back_facing)
138
return;
139
140
// Test if distance between the sphere and plane of triangle is smaller or equal than the radius
141
if (abs(v0.Dot(triangle_normal)) <= mRadius)
142
{
143
// Check if the sphere intersects at the start of the cast
144
uint32 closest_feature;
145
Vec3 q = ClosestPoint::GetClosestPointOnTriangle(v0, v1, v2, closest_feature);
146
float q_len_sq = q.LengthSq();
147
if (q_len_sq <= Square(mRadius))
148
{
149
// Early out if this hit is deeper than the collector's early out value
150
float q_len = sqrt(q_len_sq);
151
float penetration_depth = mRadius - q_len;
152
if (-penetration_depth >= mCollector.GetEarlyOutFraction())
153
return;
154
155
// Generate contact point
156
Vec3 contact_normal = q_len > 0.0f? q / q_len : Vec3::sAxisY();
157
Vec3 contact_point_a = q + contact_normal * penetration_depth;
158
Vec3 contact_point_b = q;
159
AddHitWithActiveEdgeDetection(v0, v1, v2, back_facing, triangle_normal, inActiveEdges, inSubShapeID2, 0.0f, contact_point_a, contact_point_b, contact_normal);
160
return;
161
}
162
}
163
else
164
{
165
// Check if cast is not parallel to the plane of the triangle
166
float abs_normal_dot_direction = abs(normal_dot_direction);
167
if (abs_normal_dot_direction > 1.0e-6f)
168
{
169
// Calculate the point on the sphere that will hit the triangle's plane first and calculate a fraction where it will do so
170
Vec3 d = Sign(normal_dot_direction) * mRadius * triangle_normal;
171
float plane_intersection = (v0 - d).Dot(triangle_normal) / normal_dot_direction;
172
173
// Check if sphere will hit in the interval that we're interested in
174
if (plane_intersection * abs_normal_dot_direction < -mRadius // Sphere hits the plane before the sweep, cannot intersect
175
|| plane_intersection >= mCollector.GetEarlyOutFraction()) // Sphere hits the plane after the sweep / early out fraction, cannot intersect
176
return;
177
178
// We can only report an interior hit if we're hitting the plane during our sweep and not before
179
if (plane_intersection >= 0.0f)
180
{
181
// Calculate the point of contact on the plane
182
Vec3 p = d + plane_intersection * mDirection;
183
184
// Check if this is an interior point
185
float u, v, w;
186
if (ClosestPoint::GetBaryCentricCoordinates(v0 - p, v1 - p, v2 - p, u, v, w)
187
&& u >= 0.0f && v >= 0.0f && w >= 0.0f)
188
{
189
// Interior point, we found the collision point. We don't need to check active edges.
190
AddHit(back_facing, inSubShapeID2, plane_intersection, p, p, back_facing? triangle_normal : -triangle_normal);
191
return;
192
}
193
}
194
}
195
}
196
197
// Test 3 edges
198
float fraction = RayCylinder(mDirection, v0, v1, mRadius);
199
fraction = min(fraction, RayCylinder(mDirection, v1, v2, mRadius));
200
fraction = min(fraction, RayCylinder(mDirection, v2, v0, mRadius));
201
202
// Test 3 vertices
203
fraction = min(fraction, RaySphere(Vec3::sZero(), mDirection, v0, mRadius));
204
fraction = min(fraction, RaySphere(Vec3::sZero(), mDirection, v1, mRadius));
205
fraction = min(fraction, RaySphere(Vec3::sZero(), mDirection, v2, mRadius));
206
207
// Check if we have a collision
208
JPH_ASSERT(fraction >= 0.0f);
209
if (fraction < mCollector.GetEarlyOutFraction())
210
{
211
// Calculate the center of the sphere at the point of contact
212
Vec3 p = fraction * mDirection;
213
214
// Get contact point and normal
215
uint32 closest_feature;
216
Vec3 q = ClosestPoint::GetClosestPointOnTriangle(v0 - p, v1 - p, v2 - p, closest_feature);
217
Vec3 contact_normal = q.Normalized();
218
Vec3 contact_point_ab = p + q;
219
AddHitWithActiveEdgeDetection(v0, v1, v2, back_facing, triangle_normal, inActiveEdges, inSubShapeID2, fraction, contact_point_ab, contact_point_ab, contact_normal);
220
}
221
}
222
223
JPH_NAMESPACE_END
224
225