Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/ConvexHullShape.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/Shape/ConvexHullShape.h>
8
#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>
9
#include <Jolt/Physics/Collision/Shape/PolyhedronSubmergedVolumeCalculator.h>
10
#include <Jolt/Physics/Collision/RayCast.h>
11
#include <Jolt/Physics/Collision/CastResult.h>
12
#include <Jolt/Physics/Collision/CollidePointResult.h>
13
#include <Jolt/Physics/Collision/TransformedShape.h>
14
#include <Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h>
15
#include <Jolt/Geometry/ConvexHullBuilder.h>
16
#include <Jolt/Geometry/ClosestPoint.h>
17
#include <Jolt/ObjectStream/TypeDeclarations.h>
18
#include <Jolt/Core/StringTools.h>
19
#include <Jolt/Core/StreamIn.h>
20
#include <Jolt/Core/StreamOut.h>
21
#include <Jolt/Core/UnorderedMap.h>
22
23
JPH_NAMESPACE_BEGIN
24
25
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(ConvexHullShapeSettings)
26
{
27
JPH_ADD_BASE_CLASS(ConvexHullShapeSettings, ConvexShapeSettings)
28
29
JPH_ADD_ATTRIBUTE(ConvexHullShapeSettings, mPoints)
30
JPH_ADD_ATTRIBUTE(ConvexHullShapeSettings, mMaxConvexRadius)
31
JPH_ADD_ATTRIBUTE(ConvexHullShapeSettings, mMaxErrorConvexRadius)
32
JPH_ADD_ATTRIBUTE(ConvexHullShapeSettings, mHullTolerance)
33
}
34
35
ShapeSettings::ShapeResult ConvexHullShapeSettings::Create() const
36
{
37
if (mCachedResult.IsEmpty())
38
Ref<Shape> shape = new ConvexHullShape(*this, mCachedResult);
39
return mCachedResult;
40
}
41
42
ConvexHullShape::ConvexHullShape(const ConvexHullShapeSettings &inSettings, ShapeResult &outResult) :
43
ConvexShape(EShapeSubType::ConvexHull, inSettings, outResult),
44
mConvexRadius(inSettings.mMaxConvexRadius)
45
{
46
using BuilderFace = ConvexHullBuilder::Face;
47
using Edge = ConvexHullBuilder::Edge;
48
using Faces = Array<BuilderFace *>;
49
50
// Check convex radius
51
if (mConvexRadius < 0.0f)
52
{
53
outResult.SetError("Invalid convex radius");
54
return;
55
}
56
57
// Build convex hull
58
const char *error = nullptr;
59
ConvexHullBuilder builder(inSettings.mPoints);
60
ConvexHullBuilder::EResult result = builder.Initialize(cMaxPointsInHull, inSettings.mHullTolerance, error);
61
if (result != ConvexHullBuilder::EResult::Success && result != ConvexHullBuilder::EResult::MaxVerticesReached)
62
{
63
outResult.SetError(error);
64
return;
65
}
66
const Faces &builder_faces = builder.GetFaces();
67
68
// Check the consistency of the resulting hull if we fully built it
69
if (result == ConvexHullBuilder::EResult::Success)
70
{
71
ConvexHullBuilder::Face *max_error_face;
72
float max_error_distance, coplanar_distance;
73
int max_error_idx;
74
builder.DetermineMaxError(max_error_face, max_error_distance, max_error_idx, coplanar_distance);
75
if (max_error_distance > 4.0f * max(coplanar_distance, inSettings.mHullTolerance)) // Coplanar distance could be bigger than the allowed tolerance if the points are far apart
76
{
77
outResult.SetError(StringFormat("Hull building failed, point %d had an error of %g (relative to tolerance: %g)", max_error_idx, (double)max_error_distance, double(max_error_distance / inSettings.mHullTolerance)));
78
return;
79
}
80
}
81
82
// Calculate center of mass and volume
83
builder.GetCenterOfMassAndVolume(mCenterOfMass, mVolume);
84
85
// Calculate covariance matrix
86
// See:
87
// - Why the inertia tensor is the inertia tensor - Jonathan Blow (http://number-none.com/blow/inertia/deriving_i.html)
88
// - How to find the inertia tensor (or other mass properties) of a 3D solid body represented by a triangle mesh (Draft) - Jonathan Blow, Atman J Binstock (http://number-none.com/blow/inertia/bb_inertia.doc)
89
Mat44 covariance_canonical(Vec4(1.0f / 60.0f, 1.0f / 120.0f, 1.0f / 120.0f, 0), Vec4(1.0f / 120.0f, 1.0f / 60.0f, 1.0f / 120.0f, 0), Vec4(1.0f / 120.0f, 1.0f / 120.0f, 1.0f / 60.0f, 0), Vec4(0, 0, 0, 1));
90
Mat44 covariance_matrix = Mat44::sZero();
91
for (BuilderFace *f : builder_faces)
92
{
93
// Fourth point of the tetrahedron is at the center of mass, we subtract it from the other points so we get a tetrahedron with one vertex at zero
94
// The first point on the face will be used to form a triangle fan
95
Edge *e = f->mFirstEdge;
96
Vec3 v1 = inSettings.mPoints[e->mStartIdx] - mCenterOfMass;
97
98
// Get the 2nd point
99
e = e->mNextEdge;
100
Vec3 v2 = inSettings.mPoints[e->mStartIdx] - mCenterOfMass;
101
102
// Loop over the triangle fan
103
for (e = e->mNextEdge; e != f->mFirstEdge; e = e->mNextEdge)
104
{
105
Vec3 v3 = inSettings.mPoints[e->mStartIdx] - mCenterOfMass;
106
107
// Affine transform that transforms a unit tetrahedron (with vertices (0, 0, 0), (1, 0, 0), (0, 1, 0) and (0, 0, 1) to this tetrahedron
108
Mat44 a(Vec4(v1, 0), Vec4(v2, 0), Vec4(v3, 0), Vec4(0, 0, 0, 1));
109
110
// Calculate covariance matrix for this tetrahedron
111
float det_a = a.GetDeterminant3x3();
112
Mat44 c = det_a * (a * covariance_canonical * a.Transposed());
113
114
// Add it
115
covariance_matrix += c;
116
117
// Prepare for next triangle
118
v2 = v3;
119
}
120
}
121
122
// Calculate inertia matrix assuming density is 1, note that element (3, 3) is garbage
123
mInertia = Mat44::sIdentity() * (covariance_matrix(0, 0) + covariance_matrix(1, 1) + covariance_matrix(2, 2)) - covariance_matrix;
124
125
// Convert polygons from the builder to our internal representation
126
using VtxMap = UnorderedMap<int, uint8>;
127
VtxMap vertex_map;
128
vertex_map.reserve(VtxMap::size_type(inSettings.mPoints.size()));
129
for (BuilderFace *builder_face : builder_faces)
130
{
131
// Determine where the vertices go
132
JPH_ASSERT(mVertexIdx.size() <= 0xFFFF);
133
uint16 first_vertex = (uint16)mVertexIdx.size();
134
uint16 num_vertices = 0;
135
136
// Loop over vertices in face
137
Edge *edge = builder_face->mFirstEdge;
138
do
139
{
140
// Remap to new index, not all points in the original input set are required to form the hull
141
uint8 new_idx;
142
int original_idx = edge->mStartIdx;
143
VtxMap::iterator m = vertex_map.find(original_idx);
144
if (m != vertex_map.end())
145
{
146
// Found, reuse
147
new_idx = m->second;
148
}
149
else
150
{
151
// This is a new point
152
// Make relative to center of mass
153
Vec3 p = inSettings.mPoints[original_idx] - mCenterOfMass;
154
155
// Update local bounds
156
mLocalBounds.Encapsulate(p);
157
158
// Add to point list
159
JPH_ASSERT(mPoints.size() <= 0xff);
160
new_idx = (uint8)mPoints.size();
161
mPoints.push_back({ p });
162
vertex_map[original_idx] = new_idx;
163
}
164
165
// Append to vertex list
166
JPH_ASSERT(mVertexIdx.size() < 0xffff);
167
mVertexIdx.push_back(new_idx);
168
num_vertices++;
169
170
edge = edge->mNextEdge;
171
} while (edge != builder_face->mFirstEdge);
172
173
// Add face
174
mFaces.push_back({ first_vertex, num_vertices });
175
176
// Add plane
177
Plane plane = Plane::sFromPointAndNormal(builder_face->mCentroid - mCenterOfMass, builder_face->mNormal.Normalized());
178
mPlanes.push_back(plane);
179
}
180
181
// Test if GetSupportFunction can support this many points
182
if (mPoints.size() > cMaxPointsInHull)
183
{
184
outResult.SetError(StringFormat("Internal error: Too many points in hull (%u), max allowed %d", (uint)mPoints.size(), cMaxPointsInHull));
185
return;
186
}
187
188
for (int p = 0; p < (int)mPoints.size(); ++p)
189
{
190
// For each point, find faces that use the point
191
Array<int> faces;
192
for (int f = 0; f < (int)mFaces.size(); ++f)
193
{
194
const Face &face = mFaces[f];
195
for (int v = 0; v < face.mNumVertices; ++v)
196
if (mVertexIdx[face.mFirstVertex + v] == p)
197
{
198
faces.push_back(f);
199
break;
200
}
201
}
202
203
if (faces.size() < 2)
204
{
205
outResult.SetError("A point must be connected to 2 or more faces!");
206
return;
207
}
208
209
// Find the 3 normals that form the largest tetrahedron
210
// The largest tetrahedron we can get is ((1, 0, 0) x (0, 1, 0)) . (0, 0, 1) = 1, if the volume is only 5% of that,
211
// the three vectors are too coplanar and we fall back to using only 2 plane normals
212
float biggest_volume = 0.05f;
213
int best3[3] = { -1, -1, -1 };
214
215
// When using 2 normals, we get the two with the biggest angle between them with a minimal difference of 1 degree
216
// otherwise we fall back to just using 1 plane normal
217
float smallest_dot = Cos(DegreesToRadians(1.0f));
218
int best2[2] = { -1, -1 };
219
220
for (int face1 = 0; face1 < (int)faces.size(); ++face1)
221
{
222
Vec3 normal1 = mPlanes[faces[face1]].GetNormal();
223
for (int face2 = face1 + 1; face2 < (int)faces.size(); ++face2)
224
{
225
Vec3 normal2 = mPlanes[faces[face2]].GetNormal();
226
Vec3 cross = normal1.Cross(normal2);
227
228
// Determine the 2 face normals that are most apart
229
float dot = normal1.Dot(normal2);
230
if (dot < smallest_dot)
231
{
232
smallest_dot = dot;
233
best2[0] = faces[face1];
234
best2[1] = faces[face2];
235
}
236
237
// Determine the 3 face normals that form the largest tetrahedron
238
for (int face3 = face2 + 1; face3 < (int)faces.size(); ++face3)
239
{
240
Vec3 normal3 = mPlanes[faces[face3]].GetNormal();
241
float volume = abs(cross.Dot(normal3));
242
if (volume > biggest_volume)
243
{
244
biggest_volume = volume;
245
best3[0] = faces[face1];
246
best3[1] = faces[face2];
247
best3[2] = faces[face3];
248
}
249
}
250
}
251
}
252
253
// If we didn't find 3 planes, use 2, if we didn't find 2 use 1
254
if (best3[0] != -1)
255
faces = { best3[0], best3[1], best3[2] };
256
else if (best2[0] != -1)
257
faces = { best2[0], best2[1] };
258
else
259
faces = { faces[0] };
260
261
// Copy the faces to the points buffer
262
Point &point = mPoints[p];
263
point.mNumFaces = (int)faces.size();
264
for (int i = 0; i < (int)faces.size(); ++i)
265
point.mFaces[i] = faces[i];
266
}
267
268
// If the convex radius is already zero, there's no point in further reducing it
269
if (mConvexRadius > 0.0f)
270
{
271
// Find out how thin the hull is by walking over all planes and checking the thickness of the hull in that direction
272
float min_size = FLT_MAX;
273
for (const Plane &plane : mPlanes)
274
{
275
// Take the point that is furthest away from the plane as thickness of this hull
276
float max_dist = 0.0f;
277
for (const Point &point : mPoints)
278
{
279
float dist = -plane.SignedDistance(point.mPosition); // Point is always behind plane, so we need to negate
280
if (dist > max_dist)
281
max_dist = dist;
282
}
283
min_size = min(min_size, max_dist);
284
}
285
286
// We need to fit in 2x the convex radius in min_size, so reduce the convex radius if it's bigger than that
287
mConvexRadius = min(mConvexRadius, 0.5f * min_size);
288
}
289
290
// Now walk over all points and see if we have to further reduce the convex radius because of sharp edges
291
if (mConvexRadius > 0.0f)
292
{
293
for (const Point &point : mPoints)
294
if (point.mNumFaces != 1) // If we have a single face, shifting back is easy and we don't need to reduce the convex radius
295
{
296
// Get first two planes
297
Plane p1 = mPlanes[point.mFaces[0]];
298
Plane p2 = mPlanes[point.mFaces[1]];
299
Plane p3;
300
Vec3 offset_mask;
301
302
if (point.mNumFaces == 3)
303
{
304
// Get third plane
305
p3 = mPlanes[point.mFaces[2]];
306
307
// All 3 planes will be offset by the convex radius
308
offset_mask = Vec3::sReplicate(1);
309
}
310
else
311
{
312
// Third plane has normal perpendicular to the other two planes and goes through the vertex position
313
JPH_ASSERT(point.mNumFaces == 2);
314
p3 = Plane::sFromPointAndNormal(point.mPosition, p1.GetNormal().Cross(p2.GetNormal()));
315
316
// Only the first and 2nd plane will be offset, the 3rd plane is only there to guide the intersection point
317
offset_mask = Vec3(1, 1, 0);
318
}
319
320
// Plane equation: point . normal + constant = 0
321
// Offsetting the plane backwards with convex radius r: point . normal + constant + r = 0
322
// To find the intersection 'point' of 3 planes we solve:
323
// |n1x n1y n1z| |x| | r + c1 |
324
// |n2x n2y n2z| |y| = - | r + c2 | <=> n point = -r (1, 1, 1) - (c1, c2, c3)
325
// |n3x n3y n3z| |z| | r + c3 |
326
// Where point = (x, y, z), n1x is the x component of the first plane, c1 = plane constant of plane 1, etc.
327
// The relation between how much the intersection point shifts as a function of r is: -r * n^-1 (1, 1, 1) = r * offset
328
// Where offset = -n^-1 (1, 1, 1) or -n^-1 (1, 1, 0) in case only the first 2 planes are offset
329
// The error that is introduced by a convex radius r is: error = r * |offset| - r
330
// So the max convex radius given error is: r = error / (|offset| - 1)
331
Mat44 n = Mat44(Vec4(p1.GetNormal(), 0), Vec4(p2.GetNormal(), 0), Vec4(p3.GetNormal(), 0), Vec4(0, 0, 0, 1)).Transposed();
332
float det_n = n.GetDeterminant3x3();
333
if (det_n == 0.0f)
334
{
335
// If the determinant is zero, the matrix is not invertible so no solution exists to move the point backwards and we have to choose a convex radius of zero
336
mConvexRadius = 0.0f;
337
break;
338
}
339
Mat44 adj_n = n.Adjointed3x3();
340
float offset = ((adj_n * offset_mask) / det_n).Length();
341
JPH_ASSERT(offset > 1.0f);
342
float max_convex_radius = inSettings.mMaxErrorConvexRadius / (offset - 1.0f);
343
mConvexRadius = min(mConvexRadius, max_convex_radius);
344
}
345
}
346
347
// Calculate the inner radius by getting the minimum distance from the origin to the planes of the hull
348
mInnerRadius = FLT_MAX;
349
for (const Plane &p : mPlanes)
350
mInnerRadius = min(mInnerRadius, -p.GetConstant());
351
mInnerRadius = max(0.0f, mInnerRadius); // Clamp against zero, this should do nothing as the shape is centered around the center of mass but for flat convex hulls there may be numerical round off issues
352
353
outResult.Set(this);
354
}
355
356
MassProperties ConvexHullShape::GetMassProperties() const
357
{
358
MassProperties p;
359
360
float density = GetDensity();
361
362
// Calculate mass
363
p.mMass = density * mVolume;
364
365
// Calculate inertia matrix
366
p.mInertia = density * mInertia;
367
p.mInertia(3, 3) = 1.0f;
368
369
return p;
370
}
371
372
Vec3 ConvexHullShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const
373
{
374
JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");
375
376
const Plane &first_plane = mPlanes[0];
377
Vec3 best_normal = first_plane.GetNormal();
378
float best_dist = abs(first_plane.SignedDistance(inLocalSurfacePosition));
379
380
// Find the face that has the shortest distance to the surface point
381
for (Array<Face>::size_type i = 1; i < mFaces.size(); ++i)
382
{
383
const Plane &plane = mPlanes[i];
384
Vec3 plane_normal = plane.GetNormal();
385
float dist = abs(plane.SignedDistance(inLocalSurfacePosition));
386
if (dist < best_dist)
387
{
388
best_dist = dist;
389
best_normal = plane_normal;
390
}
391
}
392
393
return best_normal;
394
}
395
396
class ConvexHullShape::HullNoConvex final : public Support
397
{
398
public:
399
explicit HullNoConvex(float inConvexRadius) :
400
mConvexRadius(inConvexRadius)
401
{
402
static_assert(sizeof(HullNoConvex) <= sizeof(SupportBuffer), "Buffer size too small");
403
JPH_ASSERT(IsAligned(this, alignof(HullNoConvex)));
404
}
405
406
virtual Vec3 GetSupport(Vec3Arg inDirection) const override
407
{
408
// Find the point with the highest projection on inDirection
409
float best_dot = -FLT_MAX;
410
Vec3 best_point = Vec3::sZero();
411
412
for (Vec3 point : mPoints)
413
{
414
// Check if its support is bigger than the current max
415
float dot = point.Dot(inDirection);
416
if (dot > best_dot)
417
{
418
best_dot = dot;
419
best_point = point;
420
}
421
}
422
423
return best_point;
424
}
425
426
virtual float GetConvexRadius() const override
427
{
428
return mConvexRadius;
429
}
430
431
using PointsArray = StaticArray<Vec3, cMaxPointsInHull>;
432
433
inline PointsArray & GetPoints()
434
{
435
return mPoints;
436
}
437
438
const PointsArray & GetPoints() const
439
{
440
return mPoints;
441
}
442
443
private:
444
float mConvexRadius;
445
PointsArray mPoints;
446
};
447
448
class ConvexHullShape::HullWithConvex final : public Support
449
{
450
public:
451
explicit HullWithConvex(const ConvexHullShape *inShape) :
452
mShape(inShape)
453
{
454
static_assert(sizeof(HullWithConvex) <= sizeof(SupportBuffer), "Buffer size too small");
455
JPH_ASSERT(IsAligned(this, alignof(HullWithConvex)));
456
}
457
458
virtual Vec3 GetSupport(Vec3Arg inDirection) const override
459
{
460
// Find the point with the highest projection on inDirection
461
float best_dot = -FLT_MAX;
462
Vec3 best_point = Vec3::sZero();
463
464
for (const Point &point : mShape->mPoints)
465
{
466
// Check if its support is bigger than the current max
467
float dot = point.mPosition.Dot(inDirection);
468
if (dot > best_dot)
469
{
470
best_dot = dot;
471
best_point = point.mPosition;
472
}
473
}
474
475
return best_point;
476
}
477
478
virtual float GetConvexRadius() const override
479
{
480
return 0.0f;
481
}
482
483
private:
484
const ConvexHullShape * mShape;
485
};
486
487
class ConvexHullShape::HullWithConvexScaled final : public Support
488
{
489
public:
490
HullWithConvexScaled(const ConvexHullShape *inShape, Vec3Arg inScale) :
491
mShape(inShape),
492
mScale(inScale)
493
{
494
static_assert(sizeof(HullWithConvexScaled) <= sizeof(SupportBuffer), "Buffer size too small");
495
JPH_ASSERT(IsAligned(this, alignof(HullWithConvexScaled)));
496
}
497
498
virtual Vec3 GetSupport(Vec3Arg inDirection) const override
499
{
500
// Find the point with the highest projection on inDirection
501
float best_dot = -FLT_MAX;
502
Vec3 best_point = Vec3::sZero();
503
504
for (const Point &point : mShape->mPoints)
505
{
506
// Calculate scaled position
507
Vec3 pos = mScale * point.mPosition;
508
509
// Check if its support is bigger than the current max
510
float dot = pos.Dot(inDirection);
511
if (dot > best_dot)
512
{
513
best_dot = dot;
514
best_point = pos;
515
}
516
}
517
518
return best_point;
519
}
520
521
virtual float GetConvexRadius() const override
522
{
523
return 0.0f;
524
}
525
526
private:
527
const ConvexHullShape * mShape;
528
Vec3 mScale;
529
};
530
531
const ConvexShape::Support *ConvexHullShape::GetSupportFunction(ESupportMode inMode, SupportBuffer &inBuffer, Vec3Arg inScale) const
532
{
533
// If there's no convex radius, we don't need to shrink the hull
534
if (mConvexRadius == 0.0f)
535
{
536
if (ScaleHelpers::IsNotScaled(inScale))
537
return new (&inBuffer) HullWithConvex(this);
538
else
539
return new (&inBuffer) HullWithConvexScaled(this, inScale);
540
}
541
542
switch (inMode)
543
{
544
case ESupportMode::IncludeConvexRadius:
545
case ESupportMode::Default:
546
if (ScaleHelpers::IsNotScaled(inScale))
547
return new (&inBuffer) HullWithConvex(this);
548
else
549
return new (&inBuffer) HullWithConvexScaled(this, inScale);
550
551
case ESupportMode::ExcludeConvexRadius:
552
if (ScaleHelpers::IsNotScaled(inScale))
553
{
554
// Create support function
555
HullNoConvex *hull = new (&inBuffer) HullNoConvex(mConvexRadius);
556
HullNoConvex::PointsArray &transformed_points = hull->GetPoints();
557
JPH_ASSERT(mPoints.size() <= cMaxPointsInHull, "Not enough space, this should have been caught during shape creation!");
558
559
for (const Point &point : mPoints)
560
{
561
Vec3 new_point;
562
563
if (point.mNumFaces == 1)
564
{
565
// Simply shift back by the convex radius using our 1 plane
566
new_point = point.mPosition - mPlanes[point.mFaces[0]].GetNormal() * mConvexRadius;
567
}
568
else
569
{
570
// Get first two planes and offset inwards by convex radius
571
Plane p1 = mPlanes[point.mFaces[0]].Offset(-mConvexRadius);
572
Plane p2 = mPlanes[point.mFaces[1]].Offset(-mConvexRadius);
573
Plane p3;
574
575
if (point.mNumFaces == 3)
576
{
577
// Get third plane and offset inwards by convex radius
578
p3 = mPlanes[point.mFaces[2]].Offset(-mConvexRadius);
579
}
580
else
581
{
582
// Third plane has normal perpendicular to the other two planes and goes through the vertex position
583
JPH_ASSERT(point.mNumFaces == 2);
584
p3 = Plane::sFromPointAndNormal(point.mPosition, p1.GetNormal().Cross(p2.GetNormal()));
585
}
586
587
// Find intersection point between the three planes
588
if (!Plane::sIntersectPlanes(p1, p2, p3, new_point))
589
{
590
// Fallback: Just push point back using the first plane
591
new_point = point.mPosition - p1.GetNormal() * mConvexRadius;
592
}
593
}
594
595
// Add point
596
transformed_points.push_back(new_point);
597
}
598
599
return hull;
600
}
601
else
602
{
603
// Calculate scaled convex radius
604
float convex_radius = ScaleHelpers::ScaleConvexRadius(mConvexRadius, inScale);
605
606
// Create new support function
607
HullNoConvex *hull = new (&inBuffer) HullNoConvex(convex_radius);
608
HullNoConvex::PointsArray &transformed_points = hull->GetPoints();
609
JPH_ASSERT(mPoints.size() <= cMaxPointsInHull, "Not enough space, this should have been caught during shape creation!");
610
611
// Precalculate inverse scale
612
Vec3 inv_scale = inScale.Reciprocal();
613
614
for (const Point &point : mPoints)
615
{
616
// Calculate scaled position
617
Vec3 pos = inScale * point.mPosition;
618
619
// Transform normals for plane 1 with scale
620
Vec3 n1 = (inv_scale * mPlanes[point.mFaces[0]].GetNormal()).Normalized();
621
622
Vec3 new_point;
623
624
if (point.mNumFaces == 1)
625
{
626
// Simply shift back by the convex radius using our 1 plane
627
new_point = pos - n1 * convex_radius;
628
}
629
else
630
{
631
// Transform normals for plane 2 with scale
632
Vec3 n2 = (inv_scale * mPlanes[point.mFaces[1]].GetNormal()).Normalized();
633
634
// Get first two planes and offset inwards by convex radius
635
Plane p1 = Plane::sFromPointAndNormal(pos, n1).Offset(-convex_radius);
636
Plane p2 = Plane::sFromPointAndNormal(pos, n2).Offset(-convex_radius);
637
Plane p3;
638
639
if (point.mNumFaces == 3)
640
{
641
// Transform last normal with scale
642
Vec3 n3 = (inv_scale * mPlanes[point.mFaces[2]].GetNormal()).Normalized();
643
644
// Get third plane and offset inwards by convex radius
645
p3 = Plane::sFromPointAndNormal(pos, n3).Offset(-convex_radius);
646
}
647
else
648
{
649
// Third plane has normal perpendicular to the other two planes and goes through the vertex position
650
JPH_ASSERT(point.mNumFaces == 2);
651
p3 = Plane::sFromPointAndNormal(pos, n1.Cross(n2));
652
}
653
654
// Find intersection point between the three planes
655
if (!Plane::sIntersectPlanes(p1, p2, p3, new_point))
656
{
657
// Fallback: Just push point back using the first plane
658
new_point = pos - n1 * convex_radius;
659
}
660
}
661
662
// Add point
663
transformed_points.push_back(new_point);
664
}
665
666
return hull;
667
}
668
}
669
670
JPH_ASSERT(false);
671
return nullptr;
672
}
673
674
void ConvexHullShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const
675
{
676
JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");
677
678
Vec3 inv_scale = inScale.Reciprocal();
679
680
// Need to transform the plane normals using inScale
681
// Transforming a direction with matrix M is done through multiplying by (M^-1)^T
682
// In this case M is a diagonal matrix with the scale vector, so we need to multiply our normal by 1 / scale and renormalize afterwards
683
Vec3 plane0_normal = inv_scale * mPlanes[0].GetNormal();
684
float best_dot = plane0_normal.Dot(inDirection) / plane0_normal.Length();
685
int best_face_idx = 0;
686
687
for (Array<Plane>::size_type i = 1; i < mPlanes.size(); ++i)
688
{
689
Vec3 plane_normal = inv_scale * mPlanes[i].GetNormal();
690
float dot = plane_normal.Dot(inDirection) / plane_normal.Length();
691
if (dot < best_dot)
692
{
693
best_dot = dot;
694
best_face_idx = (int)i;
695
}
696
}
697
698
// Get vertices
699
const Face &best_face = mFaces[best_face_idx];
700
const uint8 *first_vtx = mVertexIdx.data() + best_face.mFirstVertex;
701
const uint8 *end_vtx = first_vtx + best_face.mNumVertices;
702
703
// If we have more than 1/2 the capacity of outVertices worth of vertices, we start skipping vertices (note we can't fill the buffer completely since extra edges will be generated by clipping).
704
// TODO: This really needs a better algorithm to determine which vertices are important!
705
int max_vertices_to_return = outVertices.capacity() / 2;
706
int delta_vtx = (int(best_face.mNumVertices) + max_vertices_to_return) / max_vertices_to_return;
707
708
// Calculate transform with scale
709
Mat44 transform = inCenterOfMassTransform.PreScaled(inScale);
710
711
if (ScaleHelpers::IsInsideOut(inScale))
712
{
713
// Flip winding of supporting face
714
for (const uint8 *v = end_vtx - 1; v >= first_vtx; v -= delta_vtx)
715
outVertices.push_back(transform * mPoints[*v].mPosition);
716
}
717
else
718
{
719
// Normal winding of supporting face
720
for (const uint8 *v = first_vtx; v < end_vtx; v += delta_vtx)
721
outVertices.push_back(transform * mPoints[*v].mPosition);
722
}
723
}
724
725
void ConvexHullShape::GetSubmergedVolume(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Plane &inSurface, float &outTotalVolume, float &outSubmergedVolume, Vec3 &outCenterOfBuoyancy JPH_IF_DEBUG_RENDERER(, RVec3Arg inBaseOffset)) const
726
{
727
// Trivially calculate total volume
728
Vec3 abs_scale = inScale.Abs();
729
outTotalVolume = mVolume * abs_scale.GetX() * abs_scale.GetY() * abs_scale.GetZ();
730
731
// Check if shape has been scaled inside out
732
bool is_inside_out = ScaleHelpers::IsInsideOut(inScale);
733
734
// Convert the points to world space and determine the distance to the surface
735
int num_points = int(mPoints.size());
736
PolyhedronSubmergedVolumeCalculator::Point *buffer = (PolyhedronSubmergedVolumeCalculator::Point *)JPH_STACK_ALLOC(num_points * sizeof(PolyhedronSubmergedVolumeCalculator::Point));
737
PolyhedronSubmergedVolumeCalculator submerged_vol_calc(inCenterOfMassTransform * Mat44::sScale(inScale), &mPoints[0].mPosition, sizeof(Point), num_points, inSurface, buffer JPH_IF_DEBUG_RENDERER(, inBaseOffset));
738
739
if (submerged_vol_calc.AreAllAbove())
740
{
741
// We're above the water
742
outSubmergedVolume = 0.0f;
743
outCenterOfBuoyancy = Vec3::sZero();
744
}
745
else if (submerged_vol_calc.AreAllBelow())
746
{
747
// We're fully submerged
748
outSubmergedVolume = outTotalVolume;
749
outCenterOfBuoyancy = inCenterOfMassTransform.GetTranslation();
750
}
751
else
752
{
753
// Calculate submerged volume
754
int reference_point_idx = submerged_vol_calc.GetReferencePointIdx();
755
for (const Face &f : mFaces)
756
{
757
const uint8 *first_vtx = mVertexIdx.data() + f.mFirstVertex;
758
const uint8 *end_vtx = first_vtx + f.mNumVertices;
759
760
// If any of the vertices of this face are the reference point, the volume will be zero so we can skip this face
761
bool degenerate = false;
762
for (const uint8 *v = first_vtx; v < end_vtx; ++v)
763
if (*v == reference_point_idx)
764
{
765
degenerate = true;
766
break;
767
}
768
if (degenerate)
769
continue;
770
771
// Triangulate the face
772
int i1 = *first_vtx;
773
if (is_inside_out)
774
{
775
// Reverse winding
776
for (const uint8 *v = first_vtx + 2; v < end_vtx; ++v)
777
{
778
int i2 = *(v - 1);
779
int i3 = *v;
780
submerged_vol_calc.AddFace(i1, i3, i2);
781
}
782
}
783
else
784
{
785
// Normal winding
786
for (const uint8 *v = first_vtx + 2; v < end_vtx; ++v)
787
{
788
int i2 = *(v - 1);
789
int i3 = *v;
790
submerged_vol_calc.AddFace(i1, i2, i3);
791
}
792
}
793
}
794
795
// Get the results
796
submerged_vol_calc.GetResult(outSubmergedVolume, outCenterOfBuoyancy);
797
}
798
799
#ifdef JPH_DEBUG_RENDERER
800
// Draw center of buoyancy
801
if (sDrawSubmergedVolumes)
802
DebugRenderer::sInstance->DrawWireSphere(inBaseOffset + outCenterOfBuoyancy, 0.05f, Color::sRed, 1);
803
#endif // JPH_DEBUG_RENDERER
804
}
805
806
#ifdef JPH_DEBUG_RENDERER
807
void ConvexHullShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const
808
{
809
if (mGeometry == nullptr)
810
{
811
Array<DebugRenderer::Triangle> triangles;
812
for (const Face &f : mFaces)
813
{
814
const uint8 *first_vtx = mVertexIdx.data() + f.mFirstVertex;
815
const uint8 *end_vtx = first_vtx + f.mNumVertices;
816
817
// Draw first triangle of polygon
818
Vec3 v0 = mPoints[first_vtx[0]].mPosition;
819
Vec3 v1 = mPoints[first_vtx[1]].mPosition;
820
Vec3 v2 = mPoints[first_vtx[2]].mPosition;
821
Vec3 uv_direction = (v1 - v0).Normalized();
822
triangles.push_back({ v0, v1, v2, Color::sWhite, v0, uv_direction });
823
824
// Draw any other triangles in this polygon
825
for (const uint8 *v = first_vtx + 3; v < end_vtx; ++v)
826
triangles.push_back({ v0, mPoints[*(v - 1)].mPosition, mPoints[*v].mPosition, Color::sWhite, v0, uv_direction });
827
}
828
mGeometry = new DebugRenderer::Geometry(inRenderer->CreateTriangleBatch(triangles), GetLocalBounds());
829
}
830
831
// Test if the shape is scaled inside out
832
DebugRenderer::ECullMode cull_mode = ScaleHelpers::IsInsideOut(inScale)? DebugRenderer::ECullMode::CullFrontFace : DebugRenderer::ECullMode::CullBackFace;
833
834
// Determine the draw mode
835
DebugRenderer::EDrawMode draw_mode = inDrawWireframe? DebugRenderer::EDrawMode::Wireframe : DebugRenderer::EDrawMode::Solid;
836
837
// Draw the geometry
838
Color color = inUseMaterialColors? GetMaterial()->GetDebugColor() : inColor;
839
RMat44 transform = inCenterOfMassTransform.PreScaled(inScale);
840
inRenderer->DrawGeometry(transform, color, mGeometry, cull_mode, DebugRenderer::ECastShadow::On, draw_mode);
841
842
// Draw the outline if requested
843
if (sDrawFaceOutlines)
844
for (const Face &f : mFaces)
845
{
846
const uint8 *first_vtx = mVertexIdx.data() + f.mFirstVertex;
847
const uint8 *end_vtx = first_vtx + f.mNumVertices;
848
849
// Draw edges of face
850
inRenderer->DrawLine(transform * mPoints[*(end_vtx - 1)].mPosition, transform * mPoints[*first_vtx].mPosition, Color::sGrey);
851
for (const uint8 *v = first_vtx + 1; v < end_vtx; ++v)
852
inRenderer->DrawLine(transform * mPoints[*(v - 1)].mPosition, transform * mPoints[*v].mPosition, Color::sGrey);
853
}
854
}
855
856
void ConvexHullShape::DrawShrunkShape(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale) const
857
{
858
// Get the shrunk points
859
SupportBuffer buffer;
860
const HullNoConvex *support = mConvexRadius > 0.0f? static_cast<const HullNoConvex *>(GetSupportFunction(ESupportMode::ExcludeConvexRadius, buffer, inScale)) : nullptr;
861
862
RMat44 transform = inCenterOfMassTransform * Mat44::sScale(inScale);
863
864
for (int p = 0; p < (int)mPoints.size(); ++p)
865
{
866
const Point &point = mPoints[p];
867
RVec3 position = transform * point.mPosition;
868
RVec3 shrunk_point = support != nullptr? transform * support->GetPoints()[p] : position;
869
870
// Draw difference between shrunk position and position
871
inRenderer->DrawLine(position, shrunk_point, Color::sGreen);
872
873
// Draw face normals that are contributing
874
for (int i = 0; i < point.mNumFaces; ++i)
875
inRenderer->DrawLine(position, position + 0.1f * mPlanes[point.mFaces[i]].GetNormal(), Color::sYellow);
876
877
// Draw point index
878
inRenderer->DrawText3D(position, ConvertToString(p), Color::sWhite, 0.1f);
879
}
880
}
881
#endif // JPH_DEBUG_RENDERER
882
883
bool ConvexHullShape::CastRayHelper(const RayCast &inRay, float &outMinFraction, float &outMaxFraction) const
884
{
885
if (mFaces.size() == 2)
886
{
887
// If we have only 2 faces, we're a flat convex hull and we need to test edges instead of planes
888
889
// Check if plane is parallel to ray
890
const Plane &p = mPlanes.front();
891
Vec3 plane_normal = p.GetNormal();
892
float direction_projection = inRay.mDirection.Dot(plane_normal);
893
if (abs(direction_projection) >= 1.0e-12f)
894
{
895
// Calculate intersection point
896
float distance_to_plane = inRay.mOrigin.Dot(plane_normal) + p.GetConstant();
897
float fraction = -distance_to_plane / direction_projection;
898
if (fraction < 0.0f || fraction > 1.0f)
899
{
900
// Does not hit plane, no hit
901
outMinFraction = 0.0f;
902
outMaxFraction = 1.0f + FLT_EPSILON;
903
return false;
904
}
905
Vec3 intersection_point = inRay.mOrigin + fraction * inRay.mDirection;
906
907
// Test all edges to see if point is inside polygon
908
const Face &f = mFaces.front();
909
const uint8 *first_vtx = mVertexIdx.data() + f.mFirstVertex;
910
const uint8 *end_vtx = first_vtx + f.mNumVertices;
911
Vec3 p1 = mPoints[*end_vtx].mPosition;
912
for (const uint8 *v = first_vtx; v < end_vtx; ++v)
913
{
914
Vec3 p2 = mPoints[*v].mPosition;
915
if ((p2 - p1).Cross(intersection_point - p1).Dot(plane_normal) < 0.0f)
916
{
917
// Outside polygon, no hit
918
outMinFraction = 0.0f;
919
outMaxFraction = 1.0f + FLT_EPSILON;
920
return false;
921
}
922
p1 = p2;
923
}
924
925
// Inside polygon, a hit
926
outMinFraction = fraction;
927
outMaxFraction = fraction;
928
return true;
929
}
930
else
931
{
932
// Parallel ray doesn't hit
933
outMinFraction = 0.0f;
934
outMaxFraction = 1.0f + FLT_EPSILON;
935
return false;
936
}
937
}
938
else
939
{
940
// Clip ray against all planes
941
int fractions_set = 0;
942
bool all_inside = true;
943
float min_fraction = 0.0f, max_fraction = 1.0f + FLT_EPSILON;
944
for (const Plane &p : mPlanes)
945
{
946
// Check if the ray origin is behind this plane
947
Vec3 plane_normal = p.GetNormal();
948
float distance_to_plane = inRay.mOrigin.Dot(plane_normal) + p.GetConstant();
949
bool is_outside = distance_to_plane > 0.0f;
950
all_inside &= !is_outside;
951
952
// Check if plane is parallel to ray
953
float direction_projection = inRay.mDirection.Dot(plane_normal);
954
if (abs(direction_projection) >= 1.0e-12f)
955
{
956
// Get intersection fraction between ray and plane
957
float fraction = -distance_to_plane / direction_projection;
958
959
// Update interval of ray that is inside the hull
960
if (direction_projection < 0.0f)
961
{
962
min_fraction = max(fraction, min_fraction);
963
fractions_set |= 1;
964
}
965
else
966
{
967
max_fraction = min(fraction, max_fraction);
968
fractions_set |= 2;
969
}
970
}
971
else if (is_outside)
972
return false; // Outside the plane and parallel, no hit!
973
}
974
975
// Test if both min and max have been set
976
if (fractions_set == 3)
977
{
978
// Output fractions
979
outMinFraction = min_fraction;
980
outMaxFraction = max_fraction;
981
982
// Test if the infinite ray intersects with the hull (the length will be checked later)
983
return min_fraction <= max_fraction && max_fraction >= 0.0f;
984
}
985
else
986
{
987
// Degenerate case, either the ray is parallel to all planes or the ray has zero length
988
outMinFraction = 0.0f;
989
outMaxFraction = 1.0f + FLT_EPSILON;
990
991
// Return if the origin is inside the hull
992
return all_inside;
993
}
994
}
995
}
996
997
bool ConvexHullShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const
998
{
999
// Determine if ray hits the shape
1000
float min_fraction, max_fraction;
1001
if (CastRayHelper(inRay, min_fraction, max_fraction)
1002
&& min_fraction < ioHit.mFraction) // Check if this is a closer hit
1003
{
1004
// Better hit than the current hit
1005
ioHit.mFraction = min_fraction;
1006
ioHit.mSubShapeID2 = inSubShapeIDCreator.GetID();
1007
return true;
1008
}
1009
return false;
1010
}
1011
1012
void ConvexHullShape::CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter) const
1013
{
1014
// Test shape filter
1015
if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))
1016
return;
1017
1018
// Determine if ray hits the shape
1019
float min_fraction, max_fraction;
1020
if (CastRayHelper(inRay, min_fraction, max_fraction)
1021
&& min_fraction < ioCollector.GetEarlyOutFraction()) // Check if this is closer than the early out fraction
1022
{
1023
// Better hit than the current hit
1024
RayCastResult hit;
1025
hit.mBodyID = TransformedShape::sGetBodyID(ioCollector.GetContext());
1026
hit.mSubShapeID2 = inSubShapeIDCreator.GetID();
1027
1028
// Check front side hit
1029
if (inRayCastSettings.mTreatConvexAsSolid || min_fraction > 0.0f)
1030
{
1031
hit.mFraction = min_fraction;
1032
ioCollector.AddHit(hit);
1033
}
1034
1035
// Check back side hit
1036
if (inRayCastSettings.mBackFaceModeConvex == EBackFaceMode::CollideWithBackFaces
1037
&& max_fraction < ioCollector.GetEarlyOutFraction())
1038
{
1039
hit.mFraction = max_fraction;
1040
ioCollector.AddHit(hit);
1041
}
1042
}
1043
}
1044
1045
void ConvexHullShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const
1046
{
1047
// Test shape filter
1048
if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))
1049
return;
1050
1051
// Check if point is behind all planes
1052
for (const Plane &p : mPlanes)
1053
if (p.SignedDistance(inPoint) > 0.0f)
1054
return;
1055
1056
// Point is inside
1057
ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
1058
}
1059
1060
void ConvexHullShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const
1061
{
1062
Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();
1063
1064
Vec3 inv_scale = inScale.Reciprocal();
1065
bool is_not_scaled = ScaleHelpers::IsNotScaled(inScale);
1066
float scale_flip = ScaleHelpers::IsInsideOut(inScale)? -1.0f : 1.0f;
1067
1068
for (CollideSoftBodyVertexIterator v = inVertices, sbv_end = inVertices + inNumVertices; v != sbv_end; ++v)
1069
if (v.GetInvMass() > 0.0f)
1070
{
1071
Vec3 local_pos = inverse_transform * v.GetPosition();
1072
1073
// Find most facing plane
1074
float max_distance = -FLT_MAX;
1075
Vec3 max_plane_normal = Vec3::sZero();
1076
uint max_plane_idx = 0;
1077
if (is_not_scaled)
1078
{
1079
// Without scale, it is trivial to calculate the distance to the hull
1080
for (const Plane &p : mPlanes)
1081
{
1082
float distance = p.SignedDistance(local_pos);
1083
if (distance > max_distance)
1084
{
1085
max_distance = distance;
1086
max_plane_normal = p.GetNormal();
1087
max_plane_idx = uint(&p - mPlanes.data());
1088
}
1089
}
1090
}
1091
else
1092
{
1093
// When there's scale we need to calculate the planes first
1094
for (uint i = 0; i < (uint)mPlanes.size(); ++i)
1095
{
1096
// Calculate plane normal and point by scaling the original plane
1097
Vec3 plane_normal = (inv_scale * mPlanes[i].GetNormal()).Normalized();
1098
Vec3 plane_point = inScale * mPoints[mVertexIdx[mFaces[i].mFirstVertex]].mPosition;
1099
1100
float distance = plane_normal.Dot(local_pos - plane_point);
1101
if (distance > max_distance)
1102
{
1103
max_distance = distance;
1104
max_plane_normal = plane_normal;
1105
max_plane_idx = i;
1106
}
1107
}
1108
}
1109
bool is_outside = max_distance > 0.0f;
1110
1111
// Project point onto that plane
1112
Vec3 closest_point = local_pos - max_distance * max_plane_normal;
1113
1114
// Check edges if we're outside the hull (when inside we know the closest face is also the closest point to the surface)
1115
if (is_outside)
1116
{
1117
// Loop over edges
1118
float closest_point_dist_sq = FLT_MAX;
1119
const Face &face = mFaces[max_plane_idx];
1120
for (const uint8 *v_start = &mVertexIdx[face.mFirstVertex], *v1 = v_start, *v_end = v_start + face.mNumVertices; v1 < v_end; ++v1)
1121
{
1122
// Find second point
1123
const uint8 *v2 = v1 + 1;
1124
if (v2 == v_end)
1125
v2 = v_start;
1126
1127
// Get edge points
1128
Vec3 p1 = inScale * mPoints[*v1].mPosition;
1129
Vec3 p2 = inScale * mPoints[*v2].mPosition;
1130
1131
// Check if the position is outside the edge (if not, the face will be closer)
1132
Vec3 edge_normal = (p2 - p1).Cross(max_plane_normal);
1133
if (scale_flip * edge_normal.Dot(local_pos - p1) > 0.0f)
1134
{
1135
// Get closest point on edge
1136
uint32 set;
1137
Vec3 closest = ClosestPoint::GetClosestPointOnLine(p1 - local_pos, p2 - local_pos, set);
1138
float distance_sq = closest.LengthSq();
1139
if (distance_sq < closest_point_dist_sq)
1140
closest_point = local_pos + closest;
1141
}
1142
}
1143
}
1144
1145
// Check if this is the largest penetration
1146
Vec3 normal = local_pos - closest_point;
1147
float normal_length = normal.Length();
1148
float penetration = normal_length;
1149
if (is_outside)
1150
penetration = -penetration;
1151
else
1152
normal = -normal;
1153
if (v.UpdatePenetration(penetration))
1154
{
1155
// Calculate contact plane
1156
normal = normal_length > 0.0f? normal / normal_length : max_plane_normal;
1157
Plane plane = Plane::sFromPointAndNormal(closest_point, normal);
1158
1159
// Store collision
1160
v.SetCollision(plane.GetTransformed(inCenterOfMassTransform), inCollidingShapeIndex);
1161
}
1162
}
1163
}
1164
1165
class ConvexHullShape::CHSGetTrianglesContext
1166
{
1167
public:
1168
CHSGetTrianglesContext(Mat44Arg inTransform, bool inIsInsideOut) : mTransform(inTransform), mIsInsideOut(inIsInsideOut) { }
1169
1170
Mat44 mTransform;
1171
bool mIsInsideOut;
1172
size_t mCurrentFace = 0;
1173
};
1174
1175
void ConvexHullShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
1176
{
1177
static_assert(sizeof(CHSGetTrianglesContext) <= sizeof(GetTrianglesContext), "GetTrianglesContext too small");
1178
JPH_ASSERT(IsAligned(&ioContext, alignof(CHSGetTrianglesContext)));
1179
1180
new (&ioContext) CHSGetTrianglesContext(Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(inScale), ScaleHelpers::IsInsideOut(inScale));
1181
}
1182
1183
int ConvexHullShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const
1184
{
1185
static_assert(cGetTrianglesMinTrianglesRequested >= 12, "cGetTrianglesMinTrianglesRequested is too small");
1186
JPH_ASSERT(inMaxTrianglesRequested >= cGetTrianglesMinTrianglesRequested);
1187
1188
CHSGetTrianglesContext &context = (CHSGetTrianglesContext &)ioContext;
1189
1190
int total_num_triangles = 0;
1191
for (; context.mCurrentFace < mFaces.size(); ++context.mCurrentFace)
1192
{
1193
const Face &f = mFaces[context.mCurrentFace];
1194
1195
const uint8 *first_vtx = mVertexIdx.data() + f.mFirstVertex;
1196
const uint8 *end_vtx = first_vtx + f.mNumVertices;
1197
1198
// Check if there is still room in the output buffer for this face
1199
int num_triangles = f.mNumVertices - 2;
1200
inMaxTrianglesRequested -= num_triangles;
1201
if (inMaxTrianglesRequested < 0)
1202
break;
1203
total_num_triangles += num_triangles;
1204
1205
// Get first triangle of polygon
1206
Vec3 v0 = context.mTransform * mPoints[first_vtx[0]].mPosition;
1207
Vec3 v1 = context.mTransform * mPoints[first_vtx[1]].mPosition;
1208
Vec3 v2 = context.mTransform * mPoints[first_vtx[2]].mPosition;
1209
v0.StoreFloat3(outTriangleVertices++);
1210
if (context.mIsInsideOut)
1211
{
1212
// Store first triangle in this polygon flipped
1213
v2.StoreFloat3(outTriangleVertices++);
1214
v1.StoreFloat3(outTriangleVertices++);
1215
1216
// Store other triangles in this polygon flipped
1217
for (const uint8 *v = first_vtx + 3; v < end_vtx; ++v)
1218
{
1219
v0.StoreFloat3(outTriangleVertices++);
1220
(context.mTransform * mPoints[*v].mPosition).StoreFloat3(outTriangleVertices++);
1221
(context.mTransform * mPoints[*(v - 1)].mPosition).StoreFloat3(outTriangleVertices++);
1222
}
1223
}
1224
else
1225
{
1226
// Store first triangle in this polygon
1227
v1.StoreFloat3(outTriangleVertices++);
1228
v2.StoreFloat3(outTriangleVertices++);
1229
1230
// Store other triangles in this polygon
1231
for (const uint8 *v = first_vtx + 3; v < end_vtx; ++v)
1232
{
1233
v0.StoreFloat3(outTriangleVertices++);
1234
(context.mTransform * mPoints[*(v - 1)].mPosition).StoreFloat3(outTriangleVertices++);
1235
(context.mTransform * mPoints[*v].mPosition).StoreFloat3(outTriangleVertices++);
1236
}
1237
}
1238
}
1239
1240
// Store materials
1241
if (outMaterials != nullptr)
1242
{
1243
const PhysicsMaterial *material = GetMaterial();
1244
for (const PhysicsMaterial **m = outMaterials, **m_end = outMaterials + total_num_triangles; m < m_end; ++m)
1245
*m = material;
1246
}
1247
1248
return total_num_triangles;
1249
}
1250
1251
void ConvexHullShape::SaveBinaryState(StreamOut &inStream) const
1252
{
1253
ConvexShape::SaveBinaryState(inStream);
1254
1255
inStream.Write(mCenterOfMass);
1256
inStream.Write(mInertia);
1257
inStream.Write(mLocalBounds.mMin);
1258
inStream.Write(mLocalBounds.mMax);
1259
inStream.Write(mPoints);
1260
inStream.Write(mFaces);
1261
inStream.Write(mPlanes);
1262
inStream.Write(mVertexIdx);
1263
inStream.Write(mConvexRadius);
1264
inStream.Write(mVolume);
1265
inStream.Write(mInnerRadius);
1266
}
1267
1268
void ConvexHullShape::RestoreBinaryState(StreamIn &inStream)
1269
{
1270
ConvexShape::RestoreBinaryState(inStream);
1271
1272
inStream.Read(mCenterOfMass);
1273
inStream.Read(mInertia);
1274
inStream.Read(mLocalBounds.mMin);
1275
inStream.Read(mLocalBounds.mMax);
1276
inStream.Read(mPoints);
1277
inStream.Read(mFaces);
1278
inStream.Read(mPlanes);
1279
inStream.Read(mVertexIdx);
1280
inStream.Read(mConvexRadius);
1281
inStream.Read(mVolume);
1282
inStream.Read(mInnerRadius);
1283
}
1284
1285
Shape::Stats ConvexHullShape::GetStats() const
1286
{
1287
// Count number of triangles
1288
uint triangle_count = 0;
1289
for (const Face &f : mFaces)
1290
triangle_count += f.mNumVertices - 2;
1291
1292
return Stats(
1293
sizeof(*this)
1294
+ mPoints.size() * sizeof(Point)
1295
+ mFaces.size() * sizeof(Face)
1296
+ mPlanes.size() * sizeof(Plane)
1297
+ mVertexIdx.size() * sizeof(uint8),
1298
triangle_count);
1299
}
1300
1301
void ConvexHullShape::sRegister()
1302
{
1303
ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::ConvexHull);
1304
f.mConstruct = []() -> Shape * { return new ConvexHullShape; };
1305
f.mColor = Color::sGreen;
1306
}
1307
1308
JPH_NAMESPACE_END
1309
1310