PolyhedronSubmergedVolumeCalculator.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #pragma once
  5. #include <Jolt/Geometry/Plane.h>
  6. #ifdef JPH_DEBUG_RENDERER
  7. #include <Jolt/Renderer/DebugRenderer.h>
  8. #endif // JPH_DEBUG_RENDERER
  9. JPH_NAMESPACE_BEGIN
  10. /// This class calculates the intersection between a fluid surface and a polyhedron and returns the submerged volume and its center of buoyancy
  11. /// Construct this class and then one by one add all faces of the polyhedron using the AddFace function. After all faces have been added the result
  12. /// can be gotten through GetResult.
  13. class PolyhedronSubmergedVolumeCalculator
  14. {
  15. private:
  16. // Calculate submerged volume * 6 and center of mass * 4 for a tetrahedron with 4 vertices submerged
  17. // inV1 .. inV4 are submerged
  18. inline static void sTetrahedronVolume4(Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3, Vec3Arg inV4, float &outVolumeTimes6, Vec3 &outCenterTimes4)
  19. {
  20. // Calculate center of mass and mass of this tetrahedron,
  21. // see: https://en.wikipedia.org/wiki/Tetrahedron#Volume
  22. outVolumeTimes6 = max((inV1 - inV4).Dot((inV2 - inV4).Cross(inV3 - inV4)), 0.0f); // All contributions should be positive because we use a reference point that is on the surface of the hull
  23. outCenterTimes4 = inV1 + inV2 + inV3 + inV4;
  24. }
  25. // Get the intersection point with a plane.
  26. // inV1 is inD1 distance away from the plane, inV2 is inD2 distance away from the plane
  27. inline static Vec3 sGetPlaneIntersection(Vec3Arg inV1, float inD1, Vec3Arg inV2, float inD2)
  28. {
  29. JPH_ASSERT(Sign(inD1) != Sign(inD2), "Assuming both points are on opposite ends of the plane");
  30. float delta = inD1 - inD2;
  31. if (abs(delta) < 1.0e-6f)
  32. return inV1; // Parallel to plane, just pick a point
  33. else
  34. return inV1 + inD1 * (inV2 - inV1) / delta;
  35. }
  36. // Calculate submerged volume * 6 and center of mass * 4 for a tetrahedron with 1 vertex submerged
  37. // inV1 is submerged, inV2 .. inV4 are not
  38. // inD1 .. inD4 are the distances from the points to the plane
  39. inline JPH_IF_NOT_DEBUG_RENDERER(static) void sTetrahedronVolume1(Vec3Arg inV1, float inD1, Vec3Arg inV2, float inD2, Vec3Arg inV3, float inD3, Vec3Arg inV4, float inD4, float &outVolumeTimes6, Vec3 &outCenterTimes4)
  40. {
  41. // A tetrahedron with 1 point submerged is cut along 3 edges forming a new tetrahedron
  42. Vec3 v2 = sGetPlaneIntersection(inV1, inD1, inV2, inD2);
  43. Vec3 v3 = sGetPlaneIntersection(inV1, inD1, inV3, inD3);
  44. Vec3 v4 = sGetPlaneIntersection(inV1, inD1, inV4, inD4);
  45. #ifdef JPH_DEBUG_RENDERER
  46. // Draw intersection between tetrahedron and surface
  47. if (Shape::sDrawSubmergedVolumes)
  48. {
  49. RVec3 v2w = mBaseOffset + v2;
  50. RVec3 v3w = mBaseOffset + v3;
  51. RVec3 v4w = mBaseOffset + v4;
  52. DebugRenderer::sInstance->DrawTriangle(v4w, v3w, v2w, Color::sGreen);
  53. DebugRenderer::sInstance->DrawWireTriangle(v4w, v3w, v2w, Color::sWhite);
  54. }
  55. #endif // JPH_DEBUG_RENDERER
  56. sTetrahedronVolume4(inV1, v2, v3, v4, outVolumeTimes6, outCenterTimes4);
  57. }
  58. // Calculate submerged volume * 6 and center of mass * 4 for a tetrahedron with 2 vertices submerged
  59. // inV1, inV2 are submerged, inV3, inV4 are not
  60. // inD1 .. inD4 are the distances from the points to the plane
  61. inline JPH_IF_NOT_DEBUG_RENDERER(static) void sTetrahedronVolume2(Vec3Arg inV1, float inD1, Vec3Arg inV2, float inD2, Vec3Arg inV3, float inD3, Vec3Arg inV4, float inD4, float &outVolumeTimes6, Vec3 &outCenterTimes4)
  62. {
  63. // A tetrahedron with 2 points submerged is cut along 4 edges forming a quad
  64. Vec3 c = sGetPlaneIntersection(inV1, inD1, inV3, inD3);
  65. Vec3 d = sGetPlaneIntersection(inV1, inD1, inV4, inD4);
  66. Vec3 e = sGetPlaneIntersection(inV2, inD2, inV4, inD4);
  67. Vec3 f = sGetPlaneIntersection(inV2, inD2, inV3, inD3);
  68. #ifdef JPH_DEBUG_RENDERER
  69. // Draw intersection between tetrahedron and surface
  70. if (Shape::sDrawSubmergedVolumes)
  71. {
  72. RVec3 cw = mBaseOffset + c;
  73. RVec3 dw = mBaseOffset + d;
  74. RVec3 ew = mBaseOffset + e;
  75. RVec3 fw = mBaseOffset + f;
  76. DebugRenderer::sInstance->DrawTriangle(cw, ew, dw, Color::sGreen);
  77. DebugRenderer::sInstance->DrawTriangle(cw, fw, ew, Color::sGreen);
  78. DebugRenderer::sInstance->DrawWireTriangle(cw, ew, dw, Color::sWhite);
  79. DebugRenderer::sInstance->DrawWireTriangle(cw, fw, ew, Color::sWhite);
  80. }
  81. #endif // JPH_DEBUG_RENDERER
  82. // We pick point c as reference (which is on the cut off surface)
  83. // This leaves us with three tetrahedrons to sum up (any faces that are in the same plane as c will have zero volume)
  84. Vec3 center1, center2, center3;
  85. float volume1, volume2, volume3;
  86. sTetrahedronVolume4(e, f, inV2, c, volume1, center1);
  87. sTetrahedronVolume4(e, inV1, d, c, volume2, center2);
  88. sTetrahedronVolume4(e, inV2, inV1, c, volume3, center3);
  89. // Tally up the totals
  90. outVolumeTimes6 = volume1 + volume2 + volume3;
  91. outCenterTimes4 = outVolumeTimes6 > 0.0f? (volume1 * center1 + volume2 * center2 + volume3 * center3) / outVolumeTimes6 : Vec3::sZero();
  92. }
  93. // Calculate submerged volume * 6 and center of mass * 4 for a tetrahedron with 3 vertices submerged
  94. // inV1, inV2, inV3 are submerged, inV4 is not
  95. // inD1 .. inD4 are the distances from the points to the plane
  96. inline JPH_IF_NOT_DEBUG_RENDERER(static) void sTetrahedronVolume3(Vec3Arg inV1, float inD1, Vec3Arg inV2, float inD2, Vec3Arg inV3, float inD3, Vec3Arg inV4, float inD4, float &outVolumeTimes6, Vec3 &outCenterTimes4)
  97. {
  98. // A tetrahedron with 1 point above the surface is cut along 3 edges forming a new tetrahedron
  99. Vec3 v1 = sGetPlaneIntersection(inV1, inD1, inV4, inD4);
  100. Vec3 v2 = sGetPlaneIntersection(inV2, inD2, inV4, inD4);
  101. Vec3 v3 = sGetPlaneIntersection(inV3, inD3, inV4, inD4);
  102. #ifdef JPH_DEBUG_RENDERER
  103. // Draw intersection between tetrahedron and surface
  104. if (Shape::sDrawSubmergedVolumes)
  105. {
  106. RVec3 v1w = mBaseOffset + v1;
  107. RVec3 v2w = mBaseOffset + v2;
  108. RVec3 v3w = mBaseOffset + v3;
  109. DebugRenderer::sInstance->DrawTriangle(v3w, v2w, v1w, Color::sGreen);
  110. DebugRenderer::sInstance->DrawWireTriangle(v3w, v2w, v1w, Color::sWhite);
  111. }
  112. #endif // JPH_DEBUG_RENDERER
  113. Vec3 dry_center, total_center;
  114. float dry_volume, total_volume;
  115. // We first calculate the part that is above the surface
  116. sTetrahedronVolume4(v1, v2, v3, inV4, dry_volume, dry_center);
  117. // Calculate the total volume
  118. sTetrahedronVolume4(inV1, inV2, inV3, inV4, total_volume, total_center);
  119. // From this we can calculate the center and volume of the submerged part
  120. outVolumeTimes6 = max(total_volume - dry_volume, 0.0f);
  121. outCenterTimes4 = outVolumeTimes6 > 0.0f? (total_center * total_volume - dry_center * dry_volume) / outVolumeTimes6 : Vec3::sZero();
  122. }
  123. public:
  124. /// A helper class that contains cached information about a polyhedron vertex
  125. class Point
  126. {
  127. public:
  128. Vec3 mPosition; ///< World space position of vertex
  129. float mDistanceToSurface; ///< Signed distance to the surface (> 0 is above, < 0 is below)
  130. bool mAboveSurface; ///< If the point is above the surface (mDistanceToSurface > 0)
  131. };
  132. /// Constructor
  133. /// @param inTransform Transform to transform all incoming points with
  134. /// @param inPoints Array of points that are part of the polyhedron
  135. /// @param inPointStride Amount of bytes between each point (should usually be sizeof(Vec3))
  136. /// @param inNumPoints The amount of points
  137. /// @param inSurface The plane that forms the fluid surface (normal should point up)
  138. /// @param ioBuffer A temporary buffer of Point's that should have inNumPoints entries and should stay alive while this class is alive
  139. #ifdef JPH_DEBUG_RENDERER
  140. /// @param inBaseOffset The offset to transform inTransform to world space (in double precision mode this can be used to shift the whole operation closer to the origin). Only used for debug drawing.
  141. #endif // JPH_DEBUG_RENDERER
  142. PolyhedronSubmergedVolumeCalculator(const Mat44 &inTransform, const Vec3 *inPoints, int inPointStride, int inNumPoints, const Plane &inSurface, Point *ioBuffer
  143. #ifdef JPH_DEBUG_RENDERER // Not using JPH_IF_DEBUG_RENDERER for Doxygen
  144. , RVec3 inBaseOffset
  145. #endif // JPH_DEBUG_RENDERER
  146. ) :
  147. mPoints(ioBuffer)
  148. #ifdef JPH_DEBUG_RENDERER
  149. , mBaseOffset(inBaseOffset)
  150. #endif // JPH_DEBUG_RENDERER
  151. {
  152. // Convert the points to world space and determine the distance to the surface
  153. float reference_dist = FLT_MAX;
  154. for (int p = 0; p < inNumPoints; ++p)
  155. {
  156. // Calculate values
  157. Vec3 transformed_point = inTransform * *reinterpret_cast<const Vec3 *>(reinterpret_cast<const uint8 *>(inPoints) + p * inPointStride);
  158. float dist = inSurface.SignedDistance(transformed_point);
  159. bool above = dist >= 0.0f;
  160. // Keep track if all are above or below
  161. mAllAbove &= above;
  162. mAllBelow &= !above;
  163. // Calculate lowest point, we use this to create tetrahedrons out of all faces
  164. if (reference_dist > dist)
  165. {
  166. mReferencePointIdx = p;
  167. reference_dist = dist;
  168. }
  169. // Store values
  170. ioBuffer->mPosition = transformed_point;
  171. ioBuffer->mDistanceToSurface = dist;
  172. ioBuffer->mAboveSurface = above;
  173. ++ioBuffer;
  174. }
  175. }
  176. /// Check if all points are above the surface. Should be used as early out.
  177. inline bool AreAllAbove() const
  178. {
  179. return mAllAbove;
  180. }
  181. /// Check if all points are below the surface. Should be used as early out.
  182. inline bool AreAllBelow() const
  183. {
  184. return mAllBelow;
  185. }
  186. /// Get the lowest point of the polyhedron. Used to form the 4th vertex to make a tetrahedron out of a polyhedron face.
  187. inline int GetReferencePointIdx() const
  188. {
  189. return mReferencePointIdx;
  190. }
  191. /// Add a polyhedron face. Supply the indices of the points that form the face (in counter clockwise order).
  192. void AddFace(int inIdx1, int inIdx2, int inIdx3)
  193. {
  194. JPH_ASSERT(inIdx1 != mReferencePointIdx && inIdx2 != mReferencePointIdx && inIdx3 != mReferencePointIdx, "A face using the reference point will not contribute to the volume");
  195. // Find the points
  196. const Point &ref = mPoints[mReferencePointIdx];
  197. const Point &p1 = mPoints[inIdx1];
  198. const Point &p2 = mPoints[inIdx2];
  199. const Point &p3 = mPoints[inIdx3];
  200. // Determine which vertices are submerged
  201. uint code = (p1.mAboveSurface? 0 : 0b001) | (p2.mAboveSurface? 0 : 0b010) | (p3.mAboveSurface? 0 : 0b100);
  202. float volume;
  203. Vec3 center;
  204. switch (code)
  205. {
  206. case 0b000:
  207. // One point submerged
  208. sTetrahedronVolume1(ref.mPosition, ref.mDistanceToSurface, p3.mPosition, p3.mDistanceToSurface, p2.mPosition, p2.mDistanceToSurface, p1.mPosition, p1.mDistanceToSurface, volume, center);
  209. break;
  210. case 0b001:
  211. // Two points submerged
  212. sTetrahedronVolume2(ref.mPosition, ref.mDistanceToSurface, p1.mPosition, p1.mDistanceToSurface, p3.mPosition, p3.mDistanceToSurface, p2.mPosition, p2.mDistanceToSurface, volume, center);
  213. break;
  214. case 0b010:
  215. // Two points submerged
  216. sTetrahedronVolume2(ref.mPosition, ref.mDistanceToSurface, p2.mPosition, p2.mDistanceToSurface, p1.mPosition, p1.mDistanceToSurface, p3.mPosition, p3.mDistanceToSurface, volume, center);
  217. break;
  218. case 0b100:
  219. // Two points submerged
  220. sTetrahedronVolume2(ref.mPosition, ref.mDistanceToSurface, p3.mPosition, p3.mDistanceToSurface, p2.mPosition, p2.mDistanceToSurface, p1.mPosition, p1.mDistanceToSurface, volume, center);
  221. break;
  222. case 0b011:
  223. // Three points submerged
  224. sTetrahedronVolume3(ref.mPosition, ref.mDistanceToSurface, p2.mPosition, p2.mDistanceToSurface, p1.mPosition, p1.mDistanceToSurface, p3.mPosition, p3.mDistanceToSurface, volume, center);
  225. break;
  226. case 0b101:
  227. // Three points submerged
  228. sTetrahedronVolume3(ref.mPosition, ref.mDistanceToSurface, p1.mPosition, p1.mDistanceToSurface, p3.mPosition, p3.mDistanceToSurface, p2.mPosition, p2.mDistanceToSurface, volume, center);
  229. break;
  230. case 0b110:
  231. // Three points submerged
  232. sTetrahedronVolume3(ref.mPosition, ref.mDistanceToSurface, p3.mPosition, p3.mDistanceToSurface, p2.mPosition, p2.mDistanceToSurface, p1.mPosition, p1.mDistanceToSurface, volume, center);
  233. break;
  234. case 0b111:
  235. // Four points submerged
  236. sTetrahedronVolume4(ref.mPosition, p3.mPosition, p2.mPosition, p1.mPosition, volume, center);
  237. break;
  238. default:
  239. // Should not be possible
  240. JPH_ASSERT(false);
  241. volume = 0.0f;
  242. center = Vec3::sZero();
  243. break;
  244. }
  245. mSubmergedVolume += volume;
  246. mCenterOfBuoyancy += volume * center;
  247. }
  248. /// Call after all faces have been added. Returns the submerged volume and the center of buoyancy for the submerged volume.
  249. void GetResult(float &outSubmergedVolume, Vec3 &outCenterOfBuoyancy) const
  250. {
  251. outCenterOfBuoyancy = mSubmergedVolume > 0.0f? mCenterOfBuoyancy / (4.0f * mSubmergedVolume) : Vec3::sZero(); // Do this before dividing submerged volume by 6 to get correct weight factor
  252. outSubmergedVolume = mSubmergedVolume / 6.0f;
  253. }
  254. private:
  255. // The precalculated points for this polyhedron
  256. const Point * mPoints;
  257. // If all points are above/below the surface
  258. bool mAllBelow = true;
  259. bool mAllAbove = true;
  260. // The lowest point
  261. int mReferencePointIdx = 0;
  262. // Aggregator for submerged volume and center of buoyancy
  263. float mSubmergedVolume = 0.0f;
  264. Vec3 mCenterOfBuoyancy = Vec3::sZero();
  265. #ifdef JPH_DEBUG_RENDERER
  266. // Base offset used for drawing
  267. RVec3 mBaseOffset;
  268. #endif
  269. };
  270. JPH_NAMESPACE_END