ConvexHullShape.cpp 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #include <Jolt/Jolt.h>
  4. #include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
  5. #include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>
  6. #include <Jolt/Physics/Collision/Shape/PolyhedronSubmergedVolumeCalculator.h>
  7. #include <Jolt/Physics/Collision/RayCast.h>
  8. #include <Jolt/Physics/Collision/CastResult.h>
  9. #include <Jolt/Physics/Collision/CollidePointResult.h>
  10. #include <Jolt/Physics/Collision/TransformedShape.h>
  11. #include <Jolt/Geometry/ConvexHullBuilder.h>
  12. #include <Jolt/ObjectStream/TypeDeclarations.h>
  13. #include <Jolt/Core/StringTools.h>
  14. #include <Jolt/Core/StreamIn.h>
  15. #include <Jolt/Core/StreamOut.h>
  16. JPH_SUPPRESS_WARNINGS_STD_BEGIN
  17. #include <unordered_set>
  18. #include <unordered_map>
  19. JPH_SUPPRESS_WARNINGS_STD_END
  20. JPH_NAMESPACE_BEGIN
  21. JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(ConvexHullShapeSettings)
  22. {
  23. JPH_ADD_BASE_CLASS(ConvexHullShapeSettings, ConvexShapeSettings)
  24. JPH_ADD_ATTRIBUTE(ConvexHullShapeSettings, mPoints)
  25. JPH_ADD_ATTRIBUTE(ConvexHullShapeSettings, mMaxConvexRadius)
  26. JPH_ADD_ATTRIBUTE(ConvexHullShapeSettings, mMaxErrorConvexRadius)
  27. JPH_ADD_ATTRIBUTE(ConvexHullShapeSettings, mHullTolerance)
  28. }
  29. ShapeSettings::ShapeResult ConvexHullShapeSettings::Create() const
  30. {
  31. if (mCachedResult.IsEmpty())
  32. Ref<Shape> shape = new ConvexHullShape(*this, mCachedResult);
  33. return mCachedResult;
  34. }
  35. ConvexHullShape::ConvexHullShape(const ConvexHullShapeSettings &inSettings, ShapeResult &outResult) :
  36. ConvexShape(EShapeSubType::ConvexHull, inSettings, outResult),
  37. mConvexRadius(inSettings.mMaxConvexRadius)
  38. {
  39. using BuilderFace = ConvexHullBuilder::Face;
  40. using Edge = ConvexHullBuilder::Edge;
  41. using Faces = vector<BuilderFace *>;
  42. // Check convex radius
  43. if (mConvexRadius < 0.0f)
  44. {
  45. outResult.SetError("Invalid convex radius");
  46. return;
  47. }
  48. // Build convex hull
  49. const char *error = nullptr;
  50. ConvexHullBuilder builder(inSettings.mPoints);
  51. ConvexHullBuilder::EResult result = builder.Initialize(cMaxPointsInHull, inSettings.mHullTolerance, error);
  52. if (result != ConvexHullBuilder::EResult::Success && result != ConvexHullBuilder::EResult::MaxVerticesReached)
  53. {
  54. outResult.SetError(error);
  55. return;
  56. }
  57. const Faces &builder_faces = builder.GetFaces();
  58. // Check the consistency of the resulting hull if we fully built it
  59. if (result == ConvexHullBuilder::EResult::Success)
  60. {
  61. ConvexHullBuilder::Face *max_error_face;
  62. float max_error_distance, coplanar_distance;
  63. int max_error_idx;
  64. builder.DetermineMaxError(max_error_face, max_error_distance, max_error_idx, coplanar_distance);
  65. 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
  66. {
  67. 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)));
  68. return;
  69. }
  70. }
  71. // Calculate center of mass and volume
  72. builder.GetCenterOfMassAndVolume(mCenterOfMass, mVolume);
  73. // Calculate covariance matrix
  74. // See:
  75. // - Why the inertia tensor is the inertia tensor - Jonathan Blow (http://number-none.com/blow/inertia/deriving_i.html)
  76. // - 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)
  77. 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));
  78. Mat44 covariance_matrix = Mat44::sZero();
  79. for (BuilderFace *f : builder_faces)
  80. {
  81. // 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
  82. // The first point on the face will be used to form a triangle fan
  83. Edge *e = f->mFirstEdge;
  84. Vec3 v1 = inSettings.mPoints[e->mStartIdx] - mCenterOfMass;
  85. // Get the 2nd point
  86. e = e->mNextEdge;
  87. Vec3 v2 = inSettings.mPoints[e->mStartIdx] - mCenterOfMass;
  88. // Loop over the triangle fan
  89. for (e = e->mNextEdge; e != f->mFirstEdge; e = e->mNextEdge)
  90. {
  91. Vec3 v3 = inSettings.mPoints[e->mStartIdx] - mCenterOfMass;
  92. // Affine transform that transforms a unit tetrahedon (with vertices (0, 0, 0), (1, 0, 0), (0, 1, 0) and (0, 0, 1) to this tetrahedron
  93. Mat44 a(Vec4(v1, 0), Vec4(v2, 0), Vec4(v3, 0), Vec4(0, 0, 0, 1));
  94. // Calculate covariance matrix for this tetrahedron
  95. float det_a = a.GetDeterminant3x3();
  96. Mat44 c = det_a * (a * covariance_canonical * a.Transposed());
  97. // Add it
  98. covariance_matrix += c;
  99. // Prepare for next triangle
  100. v2 = v3;
  101. }
  102. }
  103. // Calculate inertia matrix assuming density is 1, note that element (3, 3) is garbage
  104. mInertia = Mat44::sIdentity() * (covariance_matrix(0, 0) + covariance_matrix(1, 1) + covariance_matrix(2, 2)) - covariance_matrix;
  105. // Convert polygons fron the builder to our internal representation
  106. using VtxMap = unordered_map<int, uint8>;
  107. VtxMap vertex_map;
  108. for (BuilderFace *builder_face : builder_faces)
  109. {
  110. // Determine where the vertices go
  111. uint16 first_vertex = (uint16)mVertexIdx.size();
  112. uint16 num_vertices = 0;
  113. // Loop over vertices in face
  114. Edge *edge = builder_face->mFirstEdge;
  115. do
  116. {
  117. // Remap to new index, not all points in the original input set are required to form the hull
  118. uint8 new_idx;
  119. int original_idx = edge->mStartIdx;
  120. VtxMap::iterator m = vertex_map.find(original_idx);
  121. if (m != vertex_map.end())
  122. {
  123. // Found, reuse
  124. new_idx = m->second;
  125. }
  126. else
  127. {
  128. // This is a new point
  129. // Make relative to center of mass
  130. Vec3 p = inSettings.mPoints[original_idx] - mCenterOfMass;
  131. // Update local bounds
  132. mLocalBounds.Encapsulate(p);
  133. // Add to point list
  134. JPH_ASSERT(mPoints.size() <= 0xff);
  135. new_idx = (uint8)mPoints.size();
  136. mPoints.push_back({ p });
  137. vertex_map[original_idx] = new_idx;
  138. }
  139. // Append to vertex list
  140. JPH_ASSERT(mVertexIdx.size() < 0xffff);
  141. mVertexIdx.push_back(new_idx);
  142. num_vertices++;
  143. edge = edge->mNextEdge;
  144. } while (edge != builder_face->mFirstEdge);
  145. // Add face
  146. mFaces.push_back({ first_vertex, num_vertices });
  147. // Add plane
  148. Plane plane = Plane::sFromPointAndNormal(builder_face->mCentroid - mCenterOfMass, builder_face->mNormal.Normalized());
  149. mPlanes.push_back(plane);
  150. }
  151. // Test if GetSupportFunction can support this many points
  152. if (mPoints.size() > cMaxPointsInHull)
  153. {
  154. outResult.SetError(StringFormat("Internal error: Too many points in hull (%d), max allowed %d", mPoints.size(), cMaxPointsInHull));
  155. return;
  156. }
  157. for (int p = 0; p < (int)mPoints.size(); ++p)
  158. {
  159. // For each point, find faces that use the point
  160. vector<int> faces;
  161. for (int f = 0; f < (int)mFaces.size(); ++f)
  162. {
  163. const Face &face = mFaces[f];
  164. for (int v = 0; v < face.mNumVertices; ++v)
  165. if (mVertexIdx[face.mFirstVertex + v] == p)
  166. {
  167. faces.push_back(f);
  168. break;
  169. }
  170. }
  171. if (faces.size() < 2)
  172. {
  173. outResult.SetError("A point must be connected to 2 or more faces!");
  174. return;
  175. }
  176. if (faces.size() > 1)
  177. {
  178. // Find the 3 normals that form the largest tetrahedron
  179. // 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,
  180. // the three vectors are too coplanar and we fall back to using only 2 plane normals
  181. float biggest_volume = 0.05f;
  182. int best3[3] = { -1, -1, -1 };
  183. // When using 2 normals, we get the two with the biggest angle between them with a minimal difference of 1 degree
  184. // otherwise we fall back to just using 1 plane normal
  185. float smallest_dot = cos(DegreesToRadians(1.0f));
  186. int best2[2] = { -1, -1 };
  187. for (int face1 = 0; face1 < (int)faces.size(); ++face1)
  188. {
  189. Vec3 normal1 = mPlanes[faces[face1]].GetNormal();
  190. for (int face2 = face1 + 1; face2 < (int)faces.size(); ++face2)
  191. {
  192. Vec3 normal2 = mPlanes[faces[face2]].GetNormal();
  193. Vec3 cross = normal1.Cross(normal2);
  194. // Determine the 2 face normals that are most apart
  195. float dot = normal1.Dot(normal2);
  196. if (dot < smallest_dot)
  197. {
  198. smallest_dot = dot;
  199. best2[0] = faces[face1];
  200. best2[1] = faces[face2];
  201. }
  202. // Determine the 3 face normals that form the largest tetrahedron
  203. for (int face3 = face2 + 1; face3 < (int)faces.size(); ++face3)
  204. {
  205. Vec3 normal3 = mPlanes[faces[face3]].GetNormal();
  206. float volume = abs(cross.Dot(normal3));
  207. if (volume > biggest_volume)
  208. {
  209. biggest_volume = volume;
  210. best3[0] = faces[face1];
  211. best3[1] = faces[face2];
  212. best3[2] = faces[face3];
  213. }
  214. }
  215. }
  216. }
  217. // If we didn't find 3 planes, use 2, if we didn't find 2 use 1
  218. if (best3[0] != -1)
  219. faces = { best3[0], best3[1], best3[2] };
  220. else if (best2[0] != -1)
  221. faces = { best2[0], best2[1] };
  222. else
  223. faces = { faces[0] };
  224. }
  225. // Copy the faces to the points buffer
  226. Point &point = mPoints[p];
  227. point.mNumFaces = (int)faces.size();
  228. for (int i = 0; i < (int)faces.size(); ++i)
  229. point.mFaces[i] = faces[i];
  230. }
  231. // If the convex radius is already zero, there's no point in further reducing it
  232. if (mConvexRadius > 0.0f)
  233. {
  234. // Find out how thin the hull is by walking over all planes and checking the thickness of the hull in that direction
  235. float min_size = FLT_MAX;
  236. for (const Plane &plane : mPlanes)
  237. {
  238. // Take the point that is furthest away from the plane as thickness of this hull
  239. float max_dist = 0.0f;
  240. for (const Point &point : mPoints)
  241. {
  242. float dist = -plane.SignedDistance(point.mPosition); // Point is always behind plane, so we need to negate
  243. if (dist > max_dist)
  244. max_dist = dist;
  245. }
  246. min_size = min(min_size, max_dist);
  247. }
  248. // We need to fit in 2x the convex radius in min_size, so reduce the convex radius if it's bigger than that
  249. mConvexRadius = min(mConvexRadius, 0.5f * min_size);
  250. }
  251. // Now walk over all points and see if we have to further reduce the convex radius because of sharp edges
  252. if (mConvexRadius > 0.0f)
  253. {
  254. for (const Point &point : mPoints)
  255. if (point.mNumFaces != 1) // If we have a single face, shifting back is easy and we don't need to reduce the convex radius
  256. {
  257. // Get first two planes
  258. Plane p1 = mPlanes[point.mFaces[0]];
  259. Plane p2 = mPlanes[point.mFaces[1]];
  260. Plane p3;
  261. Vec3 offset_mask;
  262. if (point.mNumFaces == 3)
  263. {
  264. // Get third plane
  265. p3 = mPlanes[point.mFaces[2]];
  266. // All 3 planes will be offset by the convex radius
  267. offset_mask = Vec3::sReplicate(1);
  268. }
  269. else
  270. {
  271. // Third plane has normal perpendicular to the other two planes and goes through the vertex position
  272. JPH_ASSERT(point.mNumFaces == 2);
  273. p3 = Plane::sFromPointAndNormal(point.mPosition, p1.GetNormal().Cross(p2.GetNormal()));
  274. // Only the first and 2nd plane will be offset, the 3rd plane is only there to guide the intersection point
  275. offset_mask = Vec3(1, 1, 0);
  276. }
  277. // Plane equation: point . normal + constant = 0
  278. // Offsetting the plane backwards with convex radius r: point . normal + constant + r = 0
  279. // To find the intersection 'point' of 3 planes we solve:
  280. // |n1x n1y n1z| |x| | r + c1 |
  281. // |n2x n2y n2z| |y| = - | r + c2 | <=> n point = -r (1, 1, 1) - (c1, c2, c3)
  282. // |n3x n3y n3z| |z| | r + c3 |
  283. // Where point = (x, y, z), n1x is the x component of the first plane, c1 = plane constant of plane 1, etc.
  284. // The relation between how much the interesection point shifts as a function of r is: -r * n^-1 (1, 1, 1) = r * offset
  285. // Where offset = -n^-1 (1, 1, 1) or -n^-1 (1, 1, 0) in case only the first 2 planes are offset
  286. // The error that is introduced by a convex radius r is: error = r * |offset| - r
  287. // So the max convex radius given error is: r = error / (|offset| - 1)
  288. Mat44 n = Mat44(Vec4(p1.GetNormal(), 0), Vec4(p2.GetNormal(), 0), Vec4(p3.GetNormal(), 0), Vec4(0, 0, 0, 1)).Transposed();
  289. float det_n = n.GetDeterminant3x3();
  290. if (det_n == 0.0f)
  291. {
  292. // 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
  293. mConvexRadius = 0.0f;
  294. break;
  295. }
  296. Mat44 adj_n = n.Adjointed3x3();
  297. float offset = ((adj_n * offset_mask) / det_n).Length();
  298. JPH_ASSERT(offset > 1.0f);
  299. float max_convex_radius = inSettings.mMaxErrorConvexRadius / (offset - 1.0f);
  300. mConvexRadius = min(mConvexRadius, max_convex_radius);
  301. }
  302. }
  303. // Calculate the inner radius by getting the minimum distance from the origin to the planes of the hull
  304. mInnerRadius = FLT_MAX;
  305. for (const Plane &p : mPlanes)
  306. mInnerRadius = min(mInnerRadius, -p.GetConstant());
  307. 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
  308. outResult.Set(this);
  309. }
  310. MassProperties ConvexHullShape::GetMassProperties() const
  311. {
  312. MassProperties p;
  313. float density = GetDensity();
  314. // Calculate mass
  315. p.mMass = density * mVolume;
  316. // Calculate inertia matrix
  317. p.mInertia = density * mInertia;
  318. p.mInertia(3, 3) = 1.0f;
  319. return p;
  320. }
  321. Vec3 ConvexHullShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const
  322. {
  323. JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID");
  324. const Plane &first_plane = mPlanes[0];
  325. Vec3 best_normal = first_plane.GetNormal();
  326. float best_dist = abs(first_plane.SignedDistance(inLocalSurfacePosition));
  327. // Find the face that has the shortest distance to the surface point
  328. for (vector<Face>::size_type i = 1; i < mFaces.size(); ++i)
  329. {
  330. const Plane &plane = mPlanes[i];
  331. Vec3 plane_normal = plane.GetNormal();
  332. float dist = abs(plane.SignedDistance(inLocalSurfacePosition));
  333. if (dist < best_dist)
  334. {
  335. best_dist = dist;
  336. best_normal = plane_normal;
  337. }
  338. }
  339. return best_normal;
  340. }
  341. class ConvexHullShape::HullNoConvex final : public Support
  342. {
  343. public:
  344. explicit HullNoConvex(float inConvexRadius) :
  345. mConvexRadius(inConvexRadius)
  346. {
  347. static_assert(sizeof(HullNoConvex) <= sizeof(SupportBuffer), "Buffer size too small");
  348. JPH_ASSERT(IsAligned(this, alignof(HullNoConvex)));
  349. }
  350. virtual Vec3 GetSupport(Vec3Arg inDirection) const override
  351. {
  352. // Find the point with the highest projection on inDirection
  353. float best_dot = -FLT_MAX;
  354. Vec3 best_point = Vec3::sZero();
  355. for (Vec3 point : mPoints)
  356. {
  357. // Check if its support is bigger than the current max
  358. float dot = point.Dot(inDirection);
  359. if (dot > best_dot)
  360. {
  361. best_dot = dot;
  362. best_point = point;
  363. }
  364. }
  365. return best_point;
  366. }
  367. virtual float GetConvexRadius() const override
  368. {
  369. return mConvexRadius;
  370. }
  371. using PointsArray = StaticArray<Vec3, cMaxPointsInHull>;
  372. inline PointsArray & GetPoints()
  373. {
  374. return mPoints;
  375. }
  376. const PointsArray & GetPoints() const
  377. {
  378. return mPoints;
  379. }
  380. private:
  381. float mConvexRadius;
  382. PointsArray mPoints;
  383. };
  384. class ConvexHullShape::HullWithConvex final : public Support
  385. {
  386. public:
  387. explicit HullWithConvex(const ConvexHullShape *inShape) :
  388. mShape(inShape)
  389. {
  390. static_assert(sizeof(HullWithConvex) <= sizeof(SupportBuffer), "Buffer size too small");
  391. JPH_ASSERT(IsAligned(this, alignof(HullWithConvex)));
  392. }
  393. virtual Vec3 GetSupport(Vec3Arg inDirection) const override
  394. {
  395. // Find the point with the highest projection on inDirection
  396. float best_dot = -FLT_MAX;
  397. Vec3 best_point = Vec3::sZero();
  398. for (const Point &point : mShape->mPoints)
  399. {
  400. // Check if its support is bigger than the current max
  401. float dot = point.mPosition.Dot(inDirection);
  402. if (dot > best_dot)
  403. {
  404. best_dot = dot;
  405. best_point = point.mPosition;
  406. }
  407. }
  408. return best_point;
  409. }
  410. virtual float GetConvexRadius() const override
  411. {
  412. return 0.0f;
  413. }
  414. private:
  415. const ConvexHullShape * mShape;
  416. };
  417. class ConvexHullShape::HullWithConvexScaled final : public Support
  418. {
  419. public:
  420. HullWithConvexScaled(const ConvexHullShape *inShape, Vec3Arg inScale) :
  421. mShape(inShape),
  422. mScale(inScale)
  423. {
  424. static_assert(sizeof(HullWithConvexScaled) <= sizeof(SupportBuffer), "Buffer size too small");
  425. JPH_ASSERT(IsAligned(this, alignof(HullWithConvexScaled)));
  426. }
  427. virtual Vec3 GetSupport(Vec3Arg inDirection) const override
  428. {
  429. // Find the point with the highest projection on inDirection
  430. float best_dot = -FLT_MAX;
  431. Vec3 best_point = Vec3::sZero();
  432. for (const Point &point : mShape->mPoints)
  433. {
  434. // Calculate scaled position
  435. Vec3 pos = mScale * point.mPosition;
  436. // Check if its support is bigger than the current max
  437. float dot = pos.Dot(inDirection);
  438. if (dot > best_dot)
  439. {
  440. best_dot = dot;
  441. best_point = pos;
  442. }
  443. }
  444. return best_point;
  445. }
  446. virtual float GetConvexRadius() const override
  447. {
  448. return 0.0f;
  449. }
  450. private:
  451. const ConvexHullShape * mShape;
  452. Vec3 mScale;
  453. };
  454. const ConvexShape::Support *ConvexHullShape::GetSupportFunction(ESupportMode inMode, SupportBuffer &inBuffer, Vec3Arg inScale) const
  455. {
  456. // If there's no convex radius, we don't need to shrink the hull
  457. if (mConvexRadius == 0.0f)
  458. {
  459. if (ScaleHelpers::IsNotScaled(inScale))
  460. return new (&inBuffer) HullWithConvex(this);
  461. else
  462. return new (&inBuffer) HullWithConvexScaled(this, inScale);
  463. }
  464. switch (inMode)
  465. {
  466. case ESupportMode::IncludeConvexRadius:
  467. if (ScaleHelpers::IsNotScaled(inScale))
  468. return new (&inBuffer) HullWithConvex(this);
  469. else
  470. return new (&inBuffer) HullWithConvexScaled(this, inScale);
  471. case ESupportMode::ExcludeConvexRadius:
  472. if (ScaleHelpers::IsNotScaled(inScale))
  473. {
  474. // Create support function
  475. HullNoConvex *hull = new (&inBuffer) HullNoConvex(mConvexRadius);
  476. HullNoConvex::PointsArray &transformed_points = hull->GetPoints();
  477. JPH_ASSERT(mPoints.size() <= cMaxPointsInHull, "Not enough space, this should have been caught during shape creation!");
  478. for (const Point &point : mPoints)
  479. {
  480. Vec3 new_point;
  481. if (point.mNumFaces == 1)
  482. {
  483. // Simply shift back by the convex radius using our 1 plane
  484. new_point = point.mPosition - mPlanes[point.mFaces[0]].GetNormal() * mConvexRadius;
  485. }
  486. else
  487. {
  488. // Get first two planes and offset inwards by convex radius
  489. Plane p1 = mPlanes[point.mFaces[0]].Offset(-mConvexRadius);
  490. Plane p2 = mPlanes[point.mFaces[1]].Offset(-mConvexRadius);
  491. Plane p3;
  492. if (point.mNumFaces == 3)
  493. {
  494. // Get third plane and offset inwards by convex radius
  495. p3 = mPlanes[point.mFaces[2]].Offset(-mConvexRadius);
  496. }
  497. else
  498. {
  499. // Third plane has normal perpendicular to the other two planes and goes through the vertex position
  500. JPH_ASSERT(point.mNumFaces == 2);
  501. p3 = Plane::sFromPointAndNormal(point.mPosition, p1.GetNormal().Cross(p2.GetNormal()));
  502. }
  503. // Find intersection point between the three planes
  504. if (!Plane::sIntersectPlanes(p1, p2, p3, new_point))
  505. {
  506. // Fallback: Just push point back using the first plane
  507. new_point = point.mPosition - p1.GetNormal() * mConvexRadius;
  508. }
  509. }
  510. // Add point
  511. transformed_points.push_back(new_point);
  512. }
  513. return hull;
  514. }
  515. else
  516. {
  517. // Calculate scaled convex radius
  518. float convex_radius = ScaleHelpers::ScaleConvexRadius(mConvexRadius, inScale);
  519. // Create new support function
  520. HullNoConvex *hull = new (&inBuffer) HullNoConvex(convex_radius);
  521. HullNoConvex::PointsArray &transformed_points = hull->GetPoints();
  522. JPH_ASSERT(mPoints.size() <= cMaxPointsInHull, "Not enough space, this should have been caught during shape creation!");
  523. // Precalculate inverse scale
  524. Vec3 inv_scale = inScale.Reciprocal();
  525. for (const Point &point : mPoints)
  526. {
  527. // Calculate scaled position
  528. Vec3 pos = inScale * point.mPosition;
  529. // Transform normals for plane 1 with scale
  530. Vec3 n1 = (inv_scale * mPlanes[point.mFaces[0]].GetNormal()).Normalized();
  531. Vec3 new_point;
  532. if (point.mNumFaces == 1)
  533. {
  534. // Simply shift back by the convex radius using our 1 plane
  535. new_point = pos - n1 * convex_radius;
  536. }
  537. else
  538. {
  539. // Transform normals for plane 2 with scale
  540. Vec3 n2 = (inv_scale * mPlanes[point.mFaces[1]].GetNormal()).Normalized();
  541. // Get first two planes and offset inwards by convex radius
  542. Plane p1 = Plane::sFromPointAndNormal(pos, n1).Offset(-convex_radius);
  543. Plane p2 = Plane::sFromPointAndNormal(pos, n2).Offset(-convex_radius);
  544. Plane p3;
  545. if (point.mNumFaces == 3)
  546. {
  547. // Transform last normal with scale
  548. Vec3 n3 = (inv_scale * mPlanes[point.mFaces[2]].GetNormal()).Normalized();
  549. // Get third plane and offset inwards by convex radius
  550. p3 = Plane::sFromPointAndNormal(pos, n3).Offset(-convex_radius);
  551. }
  552. else
  553. {
  554. // Third plane has normal perpendicular to the other two planes and goes through the vertex position
  555. JPH_ASSERT(point.mNumFaces == 2);
  556. p3 = Plane::sFromPointAndNormal(pos, n1.Cross(n2));
  557. }
  558. // Find intersection point between the three planes
  559. if (!Plane::sIntersectPlanes(p1, p2, p3, new_point))
  560. {
  561. // Fallback: Just push point back using the first plane
  562. new_point = pos - n1 * convex_radius;
  563. }
  564. }
  565. // Add point
  566. transformed_points.push_back(new_point);
  567. }
  568. return hull;
  569. }
  570. }
  571. JPH_ASSERT(false);
  572. return nullptr;
  573. }
  574. void ConvexHullShape::GetSupportingFace(Vec3Arg inDirection, Vec3Arg inScale, SupportingFace &outVertices) const
  575. {
  576. Vec3 inv_scale = inScale.Reciprocal();
  577. // Need to transform the plane normals using inScale
  578. // Transforming a direction with matrix M is done through multiplying by (M^-1)^T
  579. // 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
  580. Vec3 plane0_normal = inv_scale * mPlanes[0].GetNormal();
  581. float best_dot = plane0_normal.Dot(inDirection) / plane0_normal.Length();
  582. int best_face_idx = 0;
  583. for (vector<Plane>::size_type i = 1; i < mPlanes.size(); ++i)
  584. {
  585. Vec3 plane_normal = inv_scale * mPlanes[i].GetNormal();
  586. float dot = plane_normal.Dot(inDirection) / plane_normal.Length();
  587. if (dot < best_dot)
  588. {
  589. best_dot = dot;
  590. best_face_idx = (int)i;
  591. }
  592. }
  593. // Get vertices
  594. const Face &best_face = mFaces[best_face_idx];
  595. const uint8 *first_vtx = mVertexIdx.data() + best_face.mFirstVertex;
  596. const uint8 *end_vtx = first_vtx + best_face.mNumVertices;
  597. // 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).
  598. // TODO: This really needs a better algorithm to determine which vertices are important!
  599. int max_vertices_to_return = outVertices.capacity() / 2;
  600. int delta_vtx = (int(best_face.mNumVertices) + max_vertices_to_return) / max_vertices_to_return;
  601. if (ScaleHelpers::IsInsideOut(inScale))
  602. {
  603. // Flip winding of supporting face
  604. for (const uint8 *v = end_vtx - 1; v >= first_vtx; v -= delta_vtx)
  605. outVertices.push_back(inScale * mPoints[*v].mPosition);
  606. }
  607. else
  608. {
  609. // Normal winding of supporting face
  610. for (const uint8 *v = first_vtx; v < end_vtx; v += delta_vtx)
  611. outVertices.push_back(inScale * mPoints[*v].mPosition);
  612. }
  613. }
  614. void ConvexHullShape::GetSubmergedVolume(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Plane &inSurface, float &outTotalVolume, float &outSubmergedVolume, Vec3 &outCenterOfBuoyancy) const
  615. {
  616. // Trivially calculate total volume
  617. Vec3 abs_scale = inScale.Abs();
  618. outTotalVolume = mVolume * abs_scale.GetX() * abs_scale.GetY() * abs_scale.GetZ();
  619. // Check if shape has been scaled inside out
  620. bool is_inside_out = ScaleHelpers::IsInsideOut(inScale);
  621. // Convert the points to world space and determine the distance to the surface
  622. int num_points = int(mPoints.size());
  623. PolyhedronSubmergedVolumeCalculator::Point *buffer = (PolyhedronSubmergedVolumeCalculator::Point *)JPH_STACK_ALLOC(num_points * sizeof(PolyhedronSubmergedVolumeCalculator::Point));
  624. PolyhedronSubmergedVolumeCalculator submerged_vol_calc(inCenterOfMassTransform * Mat44::sScale(inScale), &mPoints[0].mPosition, sizeof(Point), num_points, inSurface, buffer);
  625. if (submerged_vol_calc.AreAllAbove())
  626. {
  627. // We're above the water
  628. outSubmergedVolume = 0.0f;
  629. outCenterOfBuoyancy = Vec3::sZero();
  630. }
  631. else if (submerged_vol_calc.AreAllBelow())
  632. {
  633. // We're fully submerged
  634. outSubmergedVolume = outTotalVolume;
  635. outCenterOfBuoyancy = inCenterOfMassTransform.GetTranslation();
  636. }
  637. else
  638. {
  639. // Calculate submerged volume
  640. int reference_point_idx = submerged_vol_calc.GetReferencePointIdx();
  641. for (const Face &f : mFaces)
  642. {
  643. const uint8 *first_vtx = mVertexIdx.data() + f.mFirstVertex;
  644. const uint8 *end_vtx = first_vtx + f.mNumVertices;
  645. // If any of the vertices of this face are the reference point, the volume will be zero so we can skip this face
  646. bool degenerate = false;
  647. for (const uint8 *v = first_vtx; v < end_vtx; ++v)
  648. if (*v == reference_point_idx)
  649. {
  650. degenerate = true;
  651. break;
  652. }
  653. if (degenerate)
  654. continue;
  655. // Triangulate the face
  656. int i1 = *first_vtx;
  657. if (is_inside_out)
  658. {
  659. // Reverse winding
  660. for (const uint8 *v = first_vtx + 2; v < end_vtx; ++v)
  661. {
  662. int i2 = *(v - 1);
  663. int i3 = *v;
  664. submerged_vol_calc.AddFace(i1, i3, i2);
  665. }
  666. }
  667. else
  668. {
  669. // Normal winding
  670. for (const uint8 *v = first_vtx + 2; v < end_vtx; ++v)
  671. {
  672. int i2 = *(v - 1);
  673. int i3 = *v;
  674. submerged_vol_calc.AddFace(i1, i2, i3);
  675. }
  676. }
  677. }
  678. // Get the results
  679. submerged_vol_calc.GetResult(outSubmergedVolume, outCenterOfBuoyancy);
  680. }
  681. #ifdef JPH_DEBUG_RENDERER
  682. // Draw center of buoyancy
  683. if (sDrawSubmergedVolumes)
  684. DebugRenderer::sInstance->DrawWireSphere(outCenterOfBuoyancy, 0.05f, Color::sRed, 1);
  685. #endif // JPH_DEBUG_RENDERER
  686. }
  687. #ifdef JPH_DEBUG_RENDERER
  688. void ConvexHullShape::Draw(DebugRenderer *inRenderer, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const
  689. {
  690. if (mGeometry == nullptr)
  691. {
  692. vector<DebugRenderer::Triangle> triangles;
  693. for (const Face &f : mFaces)
  694. {
  695. const uint8 *first_vtx = mVertexIdx.data() + f.mFirstVertex;
  696. const uint8 *end_vtx = first_vtx + f.mNumVertices;
  697. // Draw first triangle of polygon
  698. Vec3 v0 = mPoints[first_vtx[0]].mPosition;
  699. Vec3 v1 = mPoints[first_vtx[1]].mPosition;
  700. Vec3 v2 = mPoints[first_vtx[2]].mPosition;
  701. Vec3 uv_direction = (v1 - v0).Normalized();
  702. triangles.push_back({ v0, v1, v2, Color::sWhite, v0, uv_direction });
  703. // Draw any other triangles in this polygon
  704. for (const uint8 *v = first_vtx + 3; v < end_vtx; ++v)
  705. triangles.push_back({ v0, mPoints[*(v - 1)].mPosition, mPoints[*v].mPosition, Color::sWhite, v0, uv_direction });
  706. }
  707. mGeometry = new DebugRenderer::Geometry(inRenderer->CreateTriangleBatch(triangles), GetLocalBounds());
  708. }
  709. // Test if the shape is scaled inside out
  710. DebugRenderer::ECullMode cull_mode = ScaleHelpers::IsInsideOut(inScale)? DebugRenderer::ECullMode::CullFrontFace : DebugRenderer::ECullMode::CullBackFace;
  711. // Determine the draw mode
  712. DebugRenderer::EDrawMode draw_mode = inDrawWireframe? DebugRenderer::EDrawMode::Wireframe : DebugRenderer::EDrawMode::Solid;
  713. // Draw the geometry
  714. Color color = inUseMaterialColors? GetMaterial()->GetDebugColor() : inColor;
  715. inRenderer->DrawGeometry(inCenterOfMassTransform * Mat44::sScale(inScale), color, mGeometry, cull_mode, DebugRenderer::ECastShadow::On, draw_mode);
  716. }
  717. void ConvexHullShape::DrawShrunkShape(DebugRenderer *inRenderer, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale) const
  718. {
  719. // Get the shrunk points
  720. SupportBuffer buffer;
  721. const HullNoConvex *support = mConvexRadius > 0.0f? static_cast<const HullNoConvex *>(GetSupportFunction(ESupportMode::ExcludeConvexRadius, buffer, inScale)) : nullptr;
  722. Mat44 transform = inCenterOfMassTransform * Mat44::sScale(inScale);
  723. for (int p = 0; p < (int)mPoints.size(); ++p)
  724. {
  725. const Point &point = mPoints[p];
  726. Vec3 position = transform * point.mPosition;
  727. Vec3 shrunk_point = support != nullptr? transform * support->GetPoints()[p] : position;
  728. // Draw difference between shrunk position and position
  729. inRenderer->DrawLine(position, shrunk_point, Color::sGreen);
  730. // Draw face normals that are contributing
  731. for (int i = 0; i < point.mNumFaces; ++i)
  732. inRenderer->DrawLine(position, position + 0.1f * mPlanes[point.mFaces[i]].GetNormal(), Color::sYellow);
  733. // Draw point index
  734. inRenderer->DrawText3D(position, ConvertToString(p), Color::sWhite, 0.1f);
  735. }
  736. }
  737. #endif // JPH_DEBUG_RENDERER
  738. bool ConvexHullShape::CastRayHelper(const RayCast &inRay, float &outMinFraction, float &outMaxFraction) const
  739. {
  740. if (mFaces.size() == 2)
  741. {
  742. // If we have only 2 faces, we're a flat convex hull and we need to test edges instead of planes
  743. // Check if plane is parallel to ray
  744. const Plane &p = mPlanes.front();
  745. Vec3 plane_normal = p.GetNormal();
  746. float direction_projection = inRay.mDirection.Dot(plane_normal);
  747. if (abs(direction_projection) >= 1.0e-12f)
  748. {
  749. // Calculate intersection point
  750. float distance_to_plane = inRay.mOrigin.Dot(plane_normal) + p.GetConstant();
  751. float fraction = -distance_to_plane / direction_projection;
  752. if (fraction < 0.0f || fraction > 1.0f)
  753. {
  754. // Does not hit plane, no hit
  755. outMinFraction = 0.0f;
  756. outMaxFraction = 1.0f + FLT_EPSILON;
  757. return false;
  758. }
  759. Vec3 intersection_point = inRay.mOrigin + fraction * inRay.mDirection;
  760. // Test all edges to see if point is inside polygon
  761. const Face &f = mFaces.front();
  762. const uint8 *first_vtx = mVertexIdx.data() + f.mFirstVertex;
  763. const uint8 *end_vtx = first_vtx + f.mNumVertices;
  764. Vec3 p1 = mPoints[*end_vtx].mPosition;
  765. for (const uint8 *v = first_vtx; v < end_vtx; ++v)
  766. {
  767. Vec3 p2 = mPoints[*v].mPosition;
  768. if ((p2 - p1).Cross(intersection_point - p1).Dot(plane_normal) < 0.0f)
  769. {
  770. // Outside polygon, no hit
  771. outMinFraction = 0.0f;
  772. outMaxFraction = 1.0f + FLT_EPSILON;
  773. return false;
  774. }
  775. p1 = p2;
  776. }
  777. // Inside polygon, a hit
  778. outMinFraction = fraction;
  779. outMaxFraction = fraction;
  780. return true;
  781. }
  782. else
  783. {
  784. // Parallel ray doesn't hit
  785. outMinFraction = 0.0f;
  786. outMaxFraction = 1.0f + FLT_EPSILON;
  787. return false;
  788. }
  789. }
  790. else
  791. {
  792. // Clip ray against all planes
  793. int fractions_set = 0;
  794. bool all_inside = true;
  795. float min_fraction = 0.0f, max_fraction = 1.0f + FLT_EPSILON;
  796. for (const Plane &p : mPlanes)
  797. {
  798. // Check if the ray origin is behind this plane
  799. Vec3 plane_normal = p.GetNormal();
  800. float distance_to_plane = inRay.mOrigin.Dot(plane_normal) + p.GetConstant();
  801. bool is_outside = distance_to_plane > 0.0f;
  802. all_inside &= !is_outside;
  803. // Check if plane is parallel to ray
  804. float direction_projection = inRay.mDirection.Dot(plane_normal);
  805. if (abs(direction_projection) >= 1.0e-12f)
  806. {
  807. // Get intersection fraction between ray and plane
  808. float fraction = -distance_to_plane / direction_projection;
  809. // Update interval of ray that is inside the hull
  810. if (direction_projection < 0.0f)
  811. {
  812. min_fraction = max(fraction, min_fraction);
  813. fractions_set |= 1;
  814. }
  815. else
  816. {
  817. max_fraction = min(fraction, max_fraction);
  818. fractions_set |= 2;
  819. }
  820. }
  821. else if (is_outside)
  822. return false; // Outside the plane and parallel, no hit!
  823. }
  824. // Test if both min and max have been set
  825. if (fractions_set == 3)
  826. {
  827. // Output fractions
  828. outMinFraction = min_fraction;
  829. outMaxFraction = max_fraction;
  830. // Test if the infinite ray intersects with the hull (the length will be checked later)
  831. return min_fraction <= max_fraction && max_fraction >= 0.0f;
  832. }
  833. else
  834. {
  835. // Degenerate case, either the ray is parallel to all planes or the ray has zero length
  836. outMinFraction = 0.0f;
  837. outMaxFraction = 1.0f + FLT_EPSILON;
  838. // Return if the origin is inside the hull
  839. return all_inside;
  840. }
  841. }
  842. }
  843. bool ConvexHullShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const
  844. {
  845. // Determine if ray hits the shape
  846. float min_fraction, max_fraction;
  847. if (CastRayHelper(inRay, min_fraction, max_fraction)
  848. && min_fraction < ioHit.mFraction) // Check if this is a closer hit
  849. {
  850. // Better hit than the current hit
  851. ioHit.mFraction = min_fraction;
  852. ioHit.mSubShapeID2 = inSubShapeIDCreator.GetID();
  853. return true;
  854. }
  855. return false;
  856. }
  857. void ConvexHullShape::CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector) const
  858. {
  859. // Determine if ray hits the shape
  860. float min_fraction, max_fraction;
  861. if (CastRayHelper(inRay, min_fraction, max_fraction)
  862. && min_fraction < ioCollector.GetEarlyOutFraction()) // Check if this is closer than the early out fraction
  863. {
  864. // Better hit than the current hit
  865. RayCastResult hit;
  866. hit.mBodyID = TransformedShape::sGetBodyID(ioCollector.GetContext());
  867. hit.mSubShapeID2 = inSubShapeIDCreator.GetID();
  868. // Check front side hit
  869. if (inRayCastSettings.mTreatConvexAsSolid || min_fraction > 0.0f)
  870. {
  871. hit.mFraction = min_fraction;
  872. ioCollector.AddHit(hit);
  873. }
  874. // Check back side hit
  875. if (inRayCastSettings.mBackFaceMode == EBackFaceMode::CollideWithBackFaces
  876. && max_fraction < ioCollector.GetEarlyOutFraction())
  877. {
  878. hit.mFraction = max_fraction;
  879. ioCollector.AddHit(hit);
  880. }
  881. }
  882. }
  883. void ConvexHullShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector) const
  884. {
  885. // Check if point is behind all planes
  886. for (const Plane &p : mPlanes)
  887. if (p.SignedDistance(inPoint) > 0.0f)
  888. return;
  889. // Point is inside
  890. ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
  891. }
  892. class ConvexHullShape::CHSGetTrianglesContext
  893. {
  894. public:
  895. CHSGetTrianglesContext(Mat44Arg inTransform, bool inIsInsideOut) : mTransform(inTransform), mIsInsideOut(inIsInsideOut) { }
  896. Mat44 mTransform;
  897. bool mIsInsideOut;
  898. size_t mCurrentFace = 0;
  899. };
  900. void ConvexHullShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
  901. {
  902. static_assert(sizeof(CHSGetTrianglesContext) <= sizeof(GetTrianglesContext), "GetTrianglesContext too small");
  903. JPH_ASSERT(IsAligned(&ioContext, alignof(CHSGetTrianglesContext)));
  904. new (&ioContext) CHSGetTrianglesContext(Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(inScale), ScaleHelpers::IsInsideOut(inScale));
  905. }
  906. int ConvexHullShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const
  907. {
  908. static_assert(cGetTrianglesMinTrianglesRequested >= 12, "cGetTrianglesMinTrianglesRequested is too small");
  909. JPH_ASSERT(inMaxTrianglesRequested >= cGetTrianglesMinTrianglesRequested);
  910. CHSGetTrianglesContext &context = (CHSGetTrianglesContext &)ioContext;
  911. int total_num_triangles = 0;
  912. for (; context.mCurrentFace < mFaces.size(); ++context.mCurrentFace)
  913. {
  914. const Face &f = mFaces[context.mCurrentFace];
  915. const uint8 *first_vtx = mVertexIdx.data() + f.mFirstVertex;
  916. const uint8 *end_vtx = first_vtx + f.mNumVertices;
  917. // Check if there is still room in the output buffer for this face
  918. int num_triangles = f.mNumVertices - 2;
  919. inMaxTrianglesRequested -= num_triangles;
  920. if (inMaxTrianglesRequested < 0)
  921. break;
  922. total_num_triangles += num_triangles;
  923. // Get first triangle of polygon
  924. Vec3 v0 = context.mTransform * mPoints[first_vtx[0]].mPosition;
  925. Vec3 v1 = context.mTransform * mPoints[first_vtx[1]].mPosition;
  926. Vec3 v2 = context.mTransform * mPoints[first_vtx[2]].mPosition;
  927. v0.StoreFloat3(outTriangleVertices++);
  928. if (context.mIsInsideOut)
  929. {
  930. // Store first triangle in this polygon flipped
  931. v2.StoreFloat3(outTriangleVertices++);
  932. v1.StoreFloat3(outTriangleVertices++);
  933. // Store other triangles in this polygon flipped
  934. for (const uint8 *v = first_vtx + 3; v < end_vtx; ++v)
  935. {
  936. v0.StoreFloat3(outTriangleVertices++);
  937. (context.mTransform * mPoints[*v].mPosition).StoreFloat3(outTriangleVertices++);
  938. (context.mTransform * mPoints[*(v - 1)].mPosition).StoreFloat3(outTriangleVertices++);
  939. }
  940. }
  941. else
  942. {
  943. // Store first triangle in this polygon
  944. v1.StoreFloat3(outTriangleVertices++);
  945. v2.StoreFloat3(outTriangleVertices++);
  946. // Store other triangles in this polygon
  947. for (const uint8 *v = first_vtx + 3; v < end_vtx; ++v)
  948. {
  949. v0.StoreFloat3(outTriangleVertices++);
  950. (context.mTransform * mPoints[*(v - 1)].mPosition).StoreFloat3(outTriangleVertices++);
  951. (context.mTransform * mPoints[*v].mPosition).StoreFloat3(outTriangleVertices++);
  952. }
  953. }
  954. }
  955. // Store materials
  956. if (outMaterials != nullptr)
  957. {
  958. const PhysicsMaterial *material = GetMaterial();
  959. for (const PhysicsMaterial **m = outMaterials, **m_end = outMaterials + total_num_triangles; m < m_end; ++m)
  960. *m = material;
  961. }
  962. return total_num_triangles;
  963. }
  964. void ConvexHullShape::SaveBinaryState(StreamOut &inStream) const
  965. {
  966. ConvexShape::SaveBinaryState(inStream);
  967. inStream.Write(mCenterOfMass);
  968. inStream.Write(mInertia);
  969. inStream.Write(mLocalBounds.mMin);
  970. inStream.Write(mLocalBounds.mMax);
  971. inStream.Write(mPoints);
  972. inStream.Write(mFaces);
  973. inStream.Write(mPlanes);
  974. inStream.Write(mVertexIdx);
  975. inStream.Write(mConvexRadius);
  976. inStream.Write(mVolume);
  977. inStream.Write(mInnerRadius);
  978. }
  979. void ConvexHullShape::RestoreBinaryState(StreamIn &inStream)
  980. {
  981. ConvexShape::RestoreBinaryState(inStream);
  982. inStream.Read(mCenterOfMass);
  983. inStream.Read(mInertia);
  984. inStream.Read(mLocalBounds.mMin);
  985. inStream.Read(mLocalBounds.mMax);
  986. inStream.Read(mPoints);
  987. inStream.Read(mFaces);
  988. inStream.Read(mPlanes);
  989. inStream.Read(mVertexIdx);
  990. inStream.Read(mConvexRadius);
  991. inStream.Read(mVolume);
  992. inStream.Read(mInnerRadius);
  993. }
  994. Shape::Stats ConvexHullShape::GetStats() const
  995. {
  996. // Count number of triangles
  997. uint triangle_count = 0;
  998. for (const Face &f : mFaces)
  999. triangle_count += f.mNumVertices - 2;
  1000. return Stats(
  1001. sizeof(*this)
  1002. + mPoints.size() * sizeof(Point)
  1003. + mFaces.size() * sizeof(Face)
  1004. + mPlanes.size() * sizeof(Plane)
  1005. + mVertexIdx.size() * sizeof(uint8),
  1006. triangle_count);
  1007. }
  1008. void ConvexHullShape::sRegister()
  1009. {
  1010. ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::ConvexHull);
  1011. f.mConstruct = []() -> Shape * { return new ConvexHullShape; };
  1012. f.mColor = Color::sGreen;
  1013. }
  1014. JPH_NAMESPACE_END