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