MassProperties.cpp 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include <Jolt/Jolt.h>
  5. #include <Jolt/Physics/Body/MassProperties.h>
  6. #include <Jolt/Math/Matrix.h>
  7. #include <Jolt/Math/Vector.h>
  8. #include <Jolt/Math/EigenValueSymmetric.h>
  9. #include <Jolt/ObjectStream/TypeDeclarations.h>
  10. #include <Jolt/Core/StreamIn.h>
  11. #include <Jolt/Core/StreamOut.h>
  12. #include <Jolt/Core/InsertionSort.h>
  13. JPH_NAMESPACE_BEGIN
  14. JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(MassProperties)
  15. {
  16. JPH_ADD_ATTRIBUTE(MassProperties, mMass)
  17. JPH_ADD_ATTRIBUTE(MassProperties, mInertia)
  18. }
  19. bool MassProperties::DecomposePrincipalMomentsOfInertia(Mat44 &outRotation, Vec3 &outDiagonal) const
  20. {
  21. // Using eigendecomposition to get the principal components of the inertia tensor
  22. // See: https://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix
  23. Matrix<3, 3> inertia;
  24. inertia.CopyPart(mInertia, 0, 0, 3, 3, 0, 0);
  25. Matrix<3, 3> eigen_vec = Matrix<3, 3>::sIdentity();
  26. Vector<3> eigen_val;
  27. if (!EigenValueSymmetric(inertia, eigen_vec, eigen_val))
  28. return false;
  29. // Sort so that the biggest value goes first
  30. int indices[] = { 0, 1, 2 };
  31. InsertionSort(indices, indices + 3, [&eigen_val](int inLeft, int inRight) { return eigen_val[inLeft] > eigen_val[inRight]; });
  32. // Convert to a regular Mat44 and Vec3
  33. outRotation = Mat44::sIdentity();
  34. for (int i = 0; i < 3; ++i)
  35. {
  36. outRotation.SetColumn3(i, Vec3(reinterpret_cast<Float3 &>(eigen_vec.GetColumn(indices[i]))));
  37. outDiagonal.SetComponent(i, eigen_val[indices[i]]);
  38. }
  39. // Make sure that the rotation matrix is a right handed matrix
  40. if (outRotation.GetAxisX().Cross(outRotation.GetAxisY()).Dot(outRotation.GetAxisZ()) < 0.0f)
  41. outRotation.SetAxisZ(-outRotation.GetAxisZ());
  42. #ifdef JPH_ENABLE_ASSERTS
  43. // Validate that the solution is correct, for each axis we want to make sure that the difference in inertia is
  44. // smaller than some fraction of the inertia itself in that axis
  45. Mat44 new_inertia = outRotation * Mat44::sScale(outDiagonal) * outRotation.Inversed();
  46. for (int i = 0; i < 3; ++i)
  47. JPH_ASSERT(new_inertia.GetColumn3(i).IsClose(mInertia.GetColumn3(i), mInertia.GetColumn3(i).LengthSq() * 1.0e-10f));
  48. #endif
  49. return true;
  50. }
  51. void MassProperties::SetMassAndInertiaOfSolidBox(Vec3Arg inBoxSize, float inDensity)
  52. {
  53. // Calculate mass
  54. mMass = inBoxSize.GetX() * inBoxSize.GetY() * inBoxSize.GetZ() * inDensity;
  55. // Calculate inertia
  56. Vec3 size_sq = inBoxSize * inBoxSize;
  57. Vec3 scale = (size_sq.Swizzle<SWIZZLE_Y, SWIZZLE_X, SWIZZLE_X>() + size_sq.Swizzle<SWIZZLE_Z, SWIZZLE_Z, SWIZZLE_Y>()) * (mMass / 12.0f);
  58. mInertia = Mat44::sScale(scale);
  59. }
  60. void MassProperties::ScaleToMass(float inMass)
  61. {
  62. if (mMass > 0.0f)
  63. {
  64. // Calculate how much we have to scale the inertia tensor
  65. float mass_scale = inMass / mMass;
  66. // Update mass
  67. mMass = inMass;
  68. // Update inertia tensor
  69. for (int i = 0; i < 3; ++i)
  70. mInertia.SetColumn4(i, mInertia.GetColumn4(i) * mass_scale);
  71. }
  72. else
  73. {
  74. // Just set the mass
  75. mMass = inMass;
  76. }
  77. }
  78. Vec3 MassProperties::sGetEquivalentSolidBoxSize(float inMass, Vec3Arg inInertiaDiagonal)
  79. {
  80. // Moment of inertia of a solid box has diagonal:
  81. // mass / 12 * [size_y^2 + size_z^2, size_x^2 + size_z^2, size_x^2 + size_y^2]
  82. // Solving for size_x, size_y and size_y (diagonal and mass are known):
  83. Vec3 diagonal = inInertiaDiagonal * (12.0f / inMass);
  84. return Vec3(sqrt(0.5f * (-diagonal[0] + diagonal[1] + diagonal[2])), sqrt(0.5f * (diagonal[0] - diagonal[1] + diagonal[2])), sqrt(0.5f * (diagonal[0] + diagonal[1] - diagonal[2])));
  85. }
  86. void MassProperties::Scale(Vec3Arg inScale)
  87. {
  88. // See: https://en.wikipedia.org/wiki/Moment_of_inertia#Inertia_tensor
  89. // The diagonal of the inertia tensor can be calculated like this:
  90. // Ixx = sum_{k = 1 to n}(m_k * (y_k^2 + z_k^2))
  91. // Iyy = sum_{k = 1 to n}(m_k * (x_k^2 + z_k^2))
  92. // Izz = sum_{k = 1 to n}(m_k * (x_k^2 + y_k^2))
  93. //
  94. // We want to isolate the terms x_k, y_k and z_k:
  95. // d = [0.5, 0.5, 0.5].[Ixx, Iyy, Izz]
  96. // [sum_{k = 1 to n}(m_k * x_k^2), sum_{k = 1 to n}(m_k * y_k^2), sum_{k = 1 to n}(m_k * z_k^2)] = [d, d, d] - [Ixx, Iyy, Izz]
  97. Vec3 diagonal = mInertia.GetDiagonal3();
  98. Vec3 xyz_sq = Vec3::sReplicate(Vec3::sReplicate(0.5f).Dot(diagonal)) - diagonal;
  99. // When scaling a shape these terms change like this:
  100. // sum_{k = 1 to n}(m_k * (scale_x * x_k)^2) = scale_x^2 * sum_{k = 1 to n}(m_k * x_k^2)
  101. // Same for y_k and z_k
  102. // Using these terms we can calculate the new diagonal of the inertia tensor:
  103. Vec3 xyz_scaled_sq = inScale * inScale * xyz_sq;
  104. float i_xx = xyz_scaled_sq.GetY() + xyz_scaled_sq.GetZ();
  105. float i_yy = xyz_scaled_sq.GetX() + xyz_scaled_sq.GetZ();
  106. float i_zz = xyz_scaled_sq.GetX() + xyz_scaled_sq.GetY();
  107. // The off diagonal elements are calculated like:
  108. // Ixy = -sum_{k = 1 to n}(x_k y_k)
  109. // Ixz = -sum_{k = 1 to n}(x_k z_k)
  110. // Iyz = -sum_{k = 1 to n}(y_k z_k)
  111. // Scaling these is simple:
  112. float i_xy = inScale.GetX() * inScale.GetY() * mInertia(0, 1);
  113. float i_xz = inScale.GetX() * inScale.GetZ() * mInertia(0, 2);
  114. float i_yz = inScale.GetY() * inScale.GetZ() * mInertia(1, 2);
  115. // Update inertia tensor
  116. mInertia(0, 0) = i_xx;
  117. mInertia(0, 1) = i_xy;
  118. mInertia(1, 0) = i_xy;
  119. mInertia(1, 1) = i_yy;
  120. mInertia(0, 2) = i_xz;
  121. mInertia(2, 0) = i_xz;
  122. mInertia(1, 2) = i_yz;
  123. mInertia(2, 1) = i_yz;
  124. mInertia(2, 2) = i_zz;
  125. // Mass scales linear with volume (note that the scaling can be negative and we don't want the mass to become negative)
  126. float mass_scale = abs(inScale.GetX() * inScale.GetY() * inScale.GetZ());
  127. mMass *= mass_scale;
  128. // Inertia scales linear with mass. This updates the m_k terms above.
  129. mInertia *= mass_scale;
  130. // Ensure that the bottom right element is a 1 again
  131. mInertia(3, 3) = 1.0f;
  132. }
  133. void MassProperties::Rotate(Mat44Arg inRotation)
  134. {
  135. mInertia = inRotation.Multiply3x3(mInertia).Multiply3x3RightTransposed(inRotation);
  136. }
  137. void MassProperties::Translate(Vec3Arg inTranslation)
  138. {
  139. // Transform the inertia using the parallel axis theorem: I' = I + m * (translation^2 E - translation translation^T)
  140. // Where I is the original body's inertia and E the identity matrix
  141. // See: https://en.wikipedia.org/wiki/Parallel_axis_theorem
  142. mInertia += mMass * (Mat44::sScale(inTranslation.Dot(inTranslation)) - Mat44::sOuterProduct(inTranslation, inTranslation));
  143. // Ensure that inertia is a 3x3 matrix, adding inertias causes the bottom right element to change
  144. mInertia.SetColumn4(3, Vec4(0, 0, 0, 1));
  145. }
  146. void MassProperties::SaveBinaryState(StreamOut &inStream) const
  147. {
  148. inStream.Write(mMass);
  149. inStream.Write(mInertia);
  150. }
  151. void MassProperties::RestoreBinaryState(StreamIn &inStream)
  152. {
  153. inStream.Read(mMass);
  154. inStream.Read(mInertia);
  155. }
  156. JPH_NAMESPACE_END