Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp
9912 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#include <Jolt/Jolt.h>
6
7
#include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
8
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
9
#include <Jolt/Physics/SoftBody/SoftBodyContactListener.h>
10
#include <Jolt/Physics/SoftBody/SoftBodyManifold.h>
11
#include <Jolt/Physics/Collision/CollideSoftBodyVertexIterator.h>
12
#include <Jolt/Physics/Collision/SimShapeFilterWrapper.h>
13
#include <Jolt/Physics/PhysicsSystem.h>
14
#include <Jolt/Physics/Body/BodyManager.h>
15
#include <Jolt/Core/ScopeExit.h>
16
#ifdef JPH_DEBUG_RENDERER
17
#include <Jolt/Renderer/DebugRenderer.h>
18
#endif // JPH_DEBUG_RENDERER
19
20
JPH_NAMESPACE_BEGIN
21
22
using namespace JPH::literals;
23
24
void SoftBodyMotionProperties::CalculateMassAndInertia()
25
{
26
MassProperties mp;
27
28
for (const Vertex &v : mVertices)
29
if (v.mInvMass > 0.0f)
30
{
31
Vec3 pos = v.mPosition;
32
33
// Accumulate mass
34
float mass = 1.0f / v.mInvMass;
35
mp.mMass += mass;
36
37
// Inertia tensor, diagonal
38
// See equations https://en.wikipedia.org/wiki/Moment_of_inertia section 'Inertia Tensor'
39
for (int i = 0; i < 3; ++i)
40
mp.mInertia(i, i) += mass * (Square(pos[(i + 1) % 3]) + Square(pos[(i + 2) % 3]));
41
42
// Inertia tensor off diagonal
43
for (int i = 0; i < 3; ++i)
44
for (int j = 0; j < 3; ++j)
45
if (i != j)
46
mp.mInertia(i, j) -= mass * pos[i] * pos[j];
47
}
48
else
49
{
50
// If one vertex is kinematic, the entire body will have infinite mass and inertia
51
SetInverseMass(0.0f);
52
SetInverseInertia(Vec3::sZero(), Quat::sIdentity());
53
return;
54
}
55
56
SetMassProperties(EAllowedDOFs::All, mp);
57
}
58
59
void SoftBodyMotionProperties::Initialize(const SoftBodyCreationSettings &inSettings)
60
{
61
// Store settings
62
mSettings = inSettings.mSettings;
63
mNumIterations = inSettings.mNumIterations;
64
mPressure = inSettings.mPressure;
65
mUpdatePosition = inSettings.mUpdatePosition;
66
67
// Initialize vertices
68
mVertices.resize(inSettings.mSettings->mVertices.size());
69
Mat44 rotation = inSettings.mMakeRotationIdentity? Mat44::sRotation(inSettings.mRotation) : Mat44::sIdentity();
70
for (Array<Vertex>::size_type v = 0, s = mVertices.size(); v < s; ++v)
71
{
72
const SoftBodySharedSettings::Vertex &in_vertex = inSettings.mSettings->mVertices[v];
73
Vertex &out_vertex = mVertices[v];
74
out_vertex.mPreviousPosition = out_vertex.mPosition = rotation * Vec3(in_vertex.mPosition);
75
out_vertex.mVelocity = rotation.Multiply3x3(Vec3(in_vertex.mVelocity));
76
out_vertex.ResetCollision();
77
out_vertex.mInvMass = in_vertex.mInvMass;
78
mLocalBounds.Encapsulate(out_vertex.mPosition);
79
}
80
81
// Allocate space for skinned vertices
82
if (!inSettings.mSettings->mSkinnedConstraints.empty())
83
mSkinState.resize(mVertices.size());
84
85
// We don't know delta time yet, so we can't predict the bounds and use the local bounds as the predicted bounds
86
mLocalPredictedBounds = mLocalBounds;
87
88
CalculateMassAndInertia();
89
}
90
91
float SoftBodyMotionProperties::GetVolumeTimesSix() const
92
{
93
float six_volume = 0.0f;
94
for (const Face &f : mSettings->mFaces)
95
{
96
Vec3 x1 = mVertices[f.mVertex[0]].mPosition;
97
Vec3 x2 = mVertices[f.mVertex[1]].mPosition;
98
Vec3 x3 = mVertices[f.mVertex[2]].mPosition;
99
six_volume += x1.Cross(x2).Dot(x3); // We pick zero as the origin as this is the center of the bounding box so should give good accuracy
100
}
101
return six_volume;
102
}
103
104
void SoftBodyMotionProperties::DetermineCollidingShapes(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem, const BodyLockInterface &inBodyLockInterface)
105
{
106
JPH_PROFILE_FUNCTION();
107
108
// Reset flag prior to collision detection
109
mNeedContactCallback.store(false, memory_order_relaxed);
110
111
struct Collector : public CollideShapeBodyCollector
112
{
113
Collector(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem, const BodyLockInterface &inBodyLockInterface, const AABox &inLocalBounds, SimShapeFilterWrapper &inShapeFilter, Array<CollidingShape> &ioHits, Array<CollidingSensor> &ioSensors) :
114
mContext(inContext),
115
mInverseTransform(inContext.mCenterOfMassTransform.InversedRotationTranslation()),
116
mLocalBounds(inLocalBounds),
117
mBodyLockInterface(inBodyLockInterface),
118
mCombineFriction(inSystem.GetCombineFriction()),
119
mCombineRestitution(inSystem.GetCombineRestitution()),
120
mShapeFilter(inShapeFilter),
121
mHits(ioHits),
122
mSensors(ioSensors)
123
{
124
}
125
126
virtual void AddHit(const BodyID &inResult) override
127
{
128
BodyLockRead lock(mBodyLockInterface, inResult);
129
if (lock.Succeeded())
130
{
131
const Body &soft_body = *mContext.mBody;
132
const Body &body = lock.GetBody();
133
if (body.IsRigidBody() // TODO: We should support soft body vs soft body
134
&& soft_body.GetCollisionGroup().CanCollide(body.GetCollisionGroup()))
135
{
136
SoftBodyContactSettings settings;
137
settings.mIsSensor = body.IsSensor();
138
139
if (mContext.mContactListener == nullptr)
140
{
141
// If we have no contact listener, we can ignore sensors
142
if (settings.mIsSensor)
143
return;
144
}
145
else
146
{
147
// Call the contact listener to see if we should accept this contact
148
if (mContext.mContactListener->OnSoftBodyContactValidate(soft_body, body, settings) != SoftBodyValidateResult::AcceptContact)
149
return;
150
151
// Check if there will be any interaction
152
if (!settings.mIsSensor
153
&& settings.mInvMassScale1 == 0.0f
154
&& (body.GetMotionType() != EMotionType::Dynamic || settings.mInvMassScale2 == 0.0f))
155
return;
156
}
157
158
// Calculate transform of this body relative to the soft body
159
Mat44 com = (mInverseTransform * body.GetCenterOfMassTransform()).ToMat44();
160
161
// Collect leaf shapes
162
mShapeFilter.SetBody2(&body);
163
struct LeafShapeCollector : public TransformedShapeCollector
164
{
165
virtual void AddHit(const TransformedShape &inResult) override
166
{
167
mHits.emplace_back(Mat44::sRotationTranslation(inResult.mShapeRotation, Vec3(inResult.mShapePositionCOM)), inResult.GetShapeScale(), inResult.mShape);
168
}
169
170
Array<LeafShape> mHits;
171
};
172
LeafShapeCollector collector;
173
body.GetShape()->CollectTransformedShapes(mLocalBounds, com.GetTranslation(), com.GetQuaternion(), Vec3::sOne(), SubShapeIDCreator(), collector, mShapeFilter);
174
if (collector.mHits.empty())
175
return;
176
177
if (settings.mIsSensor)
178
{
179
CollidingSensor cs;
180
cs.mCenterOfMassTransform = com;
181
cs.mShapes = std::move(collector.mHits);
182
cs.mBodyID = inResult;
183
mSensors.push_back(cs);
184
}
185
else
186
{
187
CollidingShape cs;
188
cs.mCenterOfMassTransform = com;
189
cs.mShapes = std::move(collector.mHits);
190
cs.mBodyID = inResult;
191
cs.mMotionType = body.GetMotionType();
192
cs.mUpdateVelocities = false;
193
cs.mFriction = mCombineFriction(soft_body, SubShapeID(), body, SubShapeID());
194
cs.mRestitution = mCombineRestitution(soft_body, SubShapeID(), body, SubShapeID());
195
cs.mSoftBodyInvMassScale = settings.mInvMassScale1;
196
if (cs.mMotionType == EMotionType::Dynamic)
197
{
198
const MotionProperties *mp = body.GetMotionProperties();
199
cs.mInvMass = settings.mInvMassScale2 * mp->GetInverseMass();
200
cs.mInvInertia = settings.mInvInertiaScale2 * mp->GetInverseInertiaForRotation(cs.mCenterOfMassTransform.GetRotation());
201
cs.mOriginalLinearVelocity = cs.mLinearVelocity = mInverseTransform.Multiply3x3(mp->GetLinearVelocity());
202
cs.mOriginalAngularVelocity = cs.mAngularVelocity = mInverseTransform.Multiply3x3(mp->GetAngularVelocity());
203
}
204
mHits.push_back(cs);
205
}
206
}
207
}
208
}
209
210
private:
211
const SoftBodyUpdateContext &mContext;
212
RMat44 mInverseTransform;
213
AABox mLocalBounds;
214
const BodyLockInterface & mBodyLockInterface;
215
ContactConstraintManager::CombineFunction mCombineFriction;
216
ContactConstraintManager::CombineFunction mCombineRestitution;
217
SimShapeFilterWrapper & mShapeFilter;
218
Array<CollidingShape> & mHits;
219
Array<CollidingSensor> & mSensors;
220
};
221
222
// Calculate local bounding box
223
AABox local_bounds = mLocalBounds;
224
local_bounds.Encapsulate(mLocalPredictedBounds);
225
local_bounds.ExpandBy(Vec3::sReplicate(mSettings->mVertexRadius));
226
227
// Calculate world space bounding box
228
AABox world_bounds = local_bounds.Transformed(inContext.mCenterOfMassTransform);
229
230
// Create shape filter
231
SimShapeFilterWrapperUnion shape_filter_union(inContext.mSimShapeFilter, inContext.mBody);
232
SimShapeFilterWrapper &shape_filter = shape_filter_union.GetSimShapeFilterWrapper();
233
234
Collector collector(inContext, inSystem, inBodyLockInterface, local_bounds, shape_filter, mCollidingShapes, mCollidingSensors);
235
ObjectLayer layer = inContext.mBody->GetObjectLayer();
236
DefaultBroadPhaseLayerFilter broadphase_layer_filter = inSystem.GetDefaultBroadPhaseLayerFilter(layer);
237
DefaultObjectLayerFilter object_layer_filter = inSystem.GetDefaultLayerFilter(layer);
238
inSystem.GetBroadPhaseQuery().CollideAABox(world_bounds, collector, broadphase_layer_filter, object_layer_filter);
239
mNumSensors = uint(mCollidingSensors.size()); // Workaround for TSAN false positive: store mCollidingSensors.size() in a separate variable.
240
}
241
242
void SoftBodyMotionProperties::DetermineCollisionPlanes(uint inVertexStart, uint inNumVertices)
243
{
244
JPH_PROFILE_FUNCTION();
245
246
// Generate collision planes
247
for (const CollidingShape &cs : mCollidingShapes)
248
for (const LeafShape &shape : cs.mShapes)
249
shape.mShape->CollideSoftBodyVertices(shape.mTransform, shape.mScale, CollideSoftBodyVertexIterator(mVertices.data() + inVertexStart), inNumVertices, int(&cs - mCollidingShapes.data()));
250
}
251
252
void SoftBodyMotionProperties::DetermineSensorCollisions(CollidingSensor &ioSensor)
253
{
254
JPH_PROFILE_FUNCTION();
255
256
Plane collision_plane;
257
float largest_penetration = -FLT_MAX;
258
int colliding_shape_idx = -1;
259
260
// Collide sensor against all vertices
261
CollideSoftBodyVertexIterator vertex_iterator(
262
StridedPtr<const Vec3>(&mVertices[0].mPosition, sizeof(SoftBodyVertex)), // The position and mass come from the soft body vertex
263
StridedPtr<const float>(&mVertices[0].mInvMass, sizeof(SoftBodyVertex)),
264
StridedPtr<Plane>(&collision_plane, 0), // We want all vertices to result in a single collision so we pass stride 0
265
StridedPtr<float>(&largest_penetration, 0),
266
StridedPtr<int>(&colliding_shape_idx, 0));
267
for (const LeafShape &shape : ioSensor.mShapes)
268
shape.mShape->CollideSoftBodyVertices(shape.mTransform, shape.mScale, vertex_iterator, uint(mVertices.size()), 0);
269
ioSensor.mHasContact = largest_penetration > 0.0f;
270
271
// We need a contact callback if one of the sensors collided
272
if (ioSensor.mHasContact)
273
mNeedContactCallback.store(true, memory_order_relaxed);
274
}
275
276
void SoftBodyMotionProperties::ApplyPressure(const SoftBodyUpdateContext &inContext)
277
{
278
JPH_PROFILE_FUNCTION();
279
280
float dt = inContext.mSubStepDeltaTime;
281
float pressure_coefficient = mPressure;
282
if (pressure_coefficient > 0.0f)
283
{
284
// Calculate total volume
285
float six_volume = GetVolumeTimesSix();
286
if (six_volume > 0.0f)
287
{
288
// Apply pressure
289
// p = F / A = n R T / V (see https://en.wikipedia.org/wiki/Pressure)
290
// Our pressure coefficient is n R T so the impulse is:
291
// P = F dt = pressure_coefficient / V * A * dt
292
float coefficient = pressure_coefficient * dt / six_volume; // Need to still multiply by 6 for the volume
293
for (const Face &f : mSettings->mFaces)
294
{
295
Vec3 x1 = mVertices[f.mVertex[0]].mPosition;
296
Vec3 x2 = mVertices[f.mVertex[1]].mPosition;
297
Vec3 x3 = mVertices[f.mVertex[2]].mPosition;
298
299
Vec3 impulse = coefficient * (x2 - x1).Cross(x3 - x1); // Area is half the cross product so need to still divide by 2
300
for (uint32 i : f.mVertex)
301
{
302
Vertex &v = mVertices[i];
303
v.mVelocity += v.mInvMass * impulse; // Want to divide by 3 because we spread over 3 vertices
304
}
305
}
306
}
307
}
308
}
309
310
void SoftBodyMotionProperties::IntegratePositions(const SoftBodyUpdateContext &inContext)
311
{
312
JPH_PROFILE_FUNCTION();
313
314
float dt = inContext.mSubStepDeltaTime;
315
float linear_damping = max(0.0f, 1.0f - GetLinearDamping() * dt); // See: MotionProperties::ApplyForceTorqueAndDragInternal
316
317
// Integrate
318
Vec3 sub_step_gravity = inContext.mGravity * dt;
319
Vec3 sub_step_impulse = GetAccumulatedForce() * dt / max(float(mVertices.size()), 1.0f);
320
for (Vertex &v : mVertices)
321
if (v.mInvMass > 0.0f)
322
{
323
// Gravity
324
v.mVelocity += sub_step_gravity + sub_step_impulse * v.mInvMass;
325
326
// Damping
327
v.mVelocity *= linear_damping;
328
329
// Integrate
330
v.mPreviousPosition = v.mPosition;
331
v.mPosition += v.mVelocity * dt;
332
}
333
else
334
{
335
// Integrate
336
v.mPreviousPosition = v.mPosition;
337
v.mPosition += v.mVelocity * dt;
338
}
339
}
340
341
void SoftBodyMotionProperties::ApplyDihedralBendConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex)
342
{
343
JPH_PROFILE_FUNCTION();
344
345
float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
346
347
for (const DihedralBend *b = mSettings->mDihedralBendConstraints.data() + inStartIndex, *b_end = mSettings->mDihedralBendConstraints.data() + inEndIndex; b < b_end; ++b)
348
{
349
Vertex &v0 = mVertices[b->mVertex[0]];
350
Vertex &v1 = mVertices[b->mVertex[1]];
351
Vertex &v2 = mVertices[b->mVertex[2]];
352
Vertex &v3 = mVertices[b->mVertex[3]];
353
354
// Get positions
355
Vec3 x0 = v0.mPosition;
356
Vec3 x1 = v1.mPosition;
357
Vec3 x2 = v2.mPosition;
358
Vec3 x3 = v3.mPosition;
359
360
/*
361
x2
362
e1/ \e3
363
/ \
364
x0----x1
365
\ e0 /
366
e2\ /e4
367
x3
368
*/
369
370
// Calculate the shared edge of the triangles
371
Vec3 e = x1 - x0;
372
float e_len = e.Length();
373
if (e_len < 1.0e-6f)
374
continue;
375
376
// Calculate the normals of the triangles
377
Vec3 x1x2 = x2 - x1;
378
Vec3 x1x3 = x3 - x1;
379
Vec3 n1 = (x2 - x0).Cross(x1x2);
380
Vec3 n2 = x1x3.Cross(x3 - x0);
381
float n1_len_sq = n1.LengthSq();
382
float n2_len_sq = n2.LengthSq();
383
float n1_len_sq_n2_len_sq = n1_len_sq * n2_len_sq;
384
if (n1_len_sq_n2_len_sq < 1.0e-24f)
385
continue;
386
387
// Calculate constraint equation
388
// As per "Strain Based Dynamics" Appendix A we need to negate the gradients when (n1 x n2) . e > 0, instead we make sure that the sign of the constraint equation is correct
389
float sign = Sign(n2.Cross(n1).Dot(e));
390
float d = n1.Dot(n2) / sqrt(n1_len_sq_n2_len_sq);
391
float c = sign * ACosApproximate(d) - b->mInitialAngle;
392
393
// Ensure the range is -PI to PI
394
if (c > JPH_PI)
395
c -= 2.0f * JPH_PI;
396
else if (c < -JPH_PI)
397
c += 2.0f * JPH_PI;
398
399
// Calculate gradient of constraint equation
400
// Taken from "Strain Based Dynamics" - Matthias Muller et al. (Appendix A)
401
// with p1 = x2, p2 = x3, p3 = x0 and p4 = x1
402
// which in turn is based on "Simulation of Clothing with Folds and Wrinkles" - R. Bridson et al. (Section 4)
403
n1 /= n1_len_sq;
404
n2 /= n2_len_sq;
405
Vec3 d0c = (x1x2.Dot(e) * n1 + x1x3.Dot(e) * n2) / e_len;
406
Vec3 d2c = e_len * n1;
407
Vec3 d3c = e_len * n2;
408
409
// The sum of the gradients must be zero (see "Strain Based Dynamics" section 4)
410
Vec3 d1c = -d0c - d2c - d3c;
411
412
// Get masses
413
float w0 = v0.mInvMass;
414
float w1 = v1.mInvMass;
415
float w2 = v2.mInvMass;
416
float w3 = v3.mInvMass;
417
418
// Calculate -lambda
419
float denom = w0 * d0c.LengthSq() + w1 * d1c.LengthSq() + w2 * d2c.LengthSq() + w3 * d3c.LengthSq() + b->mCompliance * inv_dt_sq;
420
if (denom < 1.0e-12f)
421
continue;
422
float minus_lambda = c / denom;
423
424
// Apply correction
425
v0.mPosition = x0 - minus_lambda * w0 * d0c;
426
v1.mPosition = x1 - minus_lambda * w1 * d1c;
427
v2.mPosition = x2 - minus_lambda * w2 * d2c;
428
v3.mPosition = x3 - minus_lambda * w3 * d3c;
429
}
430
}
431
432
void SoftBodyMotionProperties::ApplyVolumeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex)
433
{
434
JPH_PROFILE_FUNCTION();
435
436
float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
437
438
// Satisfy volume constraints
439
for (const Volume *v = mSettings->mVolumeConstraints.data() + inStartIndex, *v_end = mSettings->mVolumeConstraints.data() + inEndIndex; v < v_end; ++v)
440
{
441
Vertex &v1 = mVertices[v->mVertex[0]];
442
Vertex &v2 = mVertices[v->mVertex[1]];
443
Vertex &v3 = mVertices[v->mVertex[2]];
444
Vertex &v4 = mVertices[v->mVertex[3]];
445
446
Vec3 x1 = v1.mPosition;
447
Vec3 x2 = v2.mPosition;
448
Vec3 x3 = v3.mPosition;
449
Vec3 x4 = v4.mPosition;
450
451
// Calculate constraint equation
452
Vec3 x1x2 = x2 - x1;
453
Vec3 x1x3 = x3 - x1;
454
Vec3 x1x4 = x4 - x1;
455
float c = abs(x1x2.Cross(x1x3).Dot(x1x4)) - v->mSixRestVolume;
456
457
// Calculate gradient of constraint equation
458
Vec3 d1c = (x4 - x2).Cross(x3 - x2);
459
Vec3 d2c = x1x3.Cross(x1x4);
460
Vec3 d3c = x1x4.Cross(x1x2);
461
Vec3 d4c = x1x2.Cross(x1x3);
462
463
// Get masses
464
float w1 = v1.mInvMass;
465
float w2 = v2.mInvMass;
466
float w3 = v3.mInvMass;
467
float w4 = v4.mInvMass;
468
469
// Calculate -lambda
470
float denom = w1 * d1c.LengthSq() + w2 * d2c.LengthSq() + w3 * d3c.LengthSq() + w4 * d4c.LengthSq() + v->mCompliance * inv_dt_sq;
471
if (denom < 1.0e-12f)
472
continue;
473
float minus_lambda = c / denom;
474
475
// Apply correction
476
v1.mPosition = x1 - minus_lambda * w1 * d1c;
477
v2.mPosition = x2 - minus_lambda * w2 * d2c;
478
v3.mPosition = x3 - minus_lambda * w3 * d3c;
479
v4.mPosition = x4 - minus_lambda * w4 * d4c;
480
}
481
}
482
483
void SoftBodyMotionProperties::ApplySkinConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex)
484
{
485
// Early out if nothing to do
486
if (mSettings->mSkinnedConstraints.empty() || !mEnableSkinConstraints)
487
return;
488
489
JPH_PROFILE_FUNCTION();
490
491
// We're going to iterate multiple times over the skin constraints, update the skinned position accordingly.
492
// If we don't do this, the simulation will see a big jump and the first iteration will cause a big velocity change in the system.
493
float factor = mSkinStatePreviousPositionValid? inContext.mNextIteration.load(std::memory_order_relaxed) / float(mNumIterations) : 1.0f;
494
float prev_factor = 1.0f - factor;
495
496
// Apply the constraints
497
Vertex *vertices = mVertices.data();
498
const SkinState *skin_states = mSkinState.data();
499
for (const Skinned *s = mSettings->mSkinnedConstraints.data() + inStartIndex, *s_end = mSettings->mSkinnedConstraints.data() + inEndIndex; s < s_end; ++s)
500
{
501
Vertex &vertex = vertices[s->mVertex];
502
const SkinState &skin_state = skin_states[s->mVertex];
503
float max_distance = s->mMaxDistance * mSkinnedMaxDistanceMultiplier;
504
505
// Calculate the skinned position by interpolating from previous to current position
506
Vec3 skin_pos = prev_factor * skin_state.mPreviousPosition + factor * skin_state.mPosition;
507
508
if (max_distance > 0.0f)
509
{
510
// Move vertex if it violated the back stop
511
if (s->mBackStopDistance < max_distance)
512
{
513
// Center of the back stop sphere
514
Vec3 center = skin_pos - skin_state.mNormal * (s->mBackStopDistance + s->mBackStopRadius);
515
516
// Check if we're inside the back stop sphere
517
Vec3 delta = vertex.mPosition - center;
518
float delta_len_sq = delta.LengthSq();
519
if (delta_len_sq < Square(s->mBackStopRadius))
520
{
521
// Push the vertex to the surface of the back stop sphere
522
float delta_len = sqrt(delta_len_sq);
523
vertex.mPosition = delta_len > 0.0f?
524
center + delta * (s->mBackStopRadius / delta_len)
525
: center + skin_state.mNormal * s->mBackStopRadius;
526
}
527
}
528
529
// Clamp vertex distance to max distance from skinned position
530
if (max_distance < FLT_MAX)
531
{
532
Vec3 delta = vertex.mPosition - skin_pos;
533
float delta_len_sq = delta.LengthSq();
534
float max_distance_sq = Square(max_distance);
535
if (delta_len_sq > max_distance_sq)
536
vertex.mPosition = skin_pos + delta * sqrt(max_distance_sq / delta_len_sq);
537
}
538
}
539
else
540
{
541
// Kinematic: Just update the vertex position
542
vertex.mPosition = skin_pos;
543
}
544
}
545
}
546
547
void SoftBodyMotionProperties::ApplyEdgeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex)
548
{
549
JPH_PROFILE_FUNCTION();
550
551
float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
552
553
// Satisfy edge constraints
554
for (const Edge *e = mSettings->mEdgeConstraints.data() + inStartIndex, *e_end = mSettings->mEdgeConstraints.data() + inEndIndex; e < e_end; ++e)
555
{
556
Vertex &v0 = mVertices[e->mVertex[0]];
557
Vertex &v1 = mVertices[e->mVertex[1]];
558
559
// Get positions
560
Vec3 x0 = v0.mPosition;
561
Vec3 x1 = v1.mPosition;
562
563
// Calculate current length
564
Vec3 delta = x1 - x0;
565
float length = delta.Length();
566
567
// Apply correction
568
float denom = length * (v0.mInvMass + v1.mInvMass + e->mCompliance * inv_dt_sq);
569
if (denom < 1.0e-12f)
570
continue;
571
Vec3 correction = delta * (length - e->mRestLength) / denom;
572
v0.mPosition = x0 + v0.mInvMass * correction;
573
v1.mPosition = x1 - v1.mInvMass * correction;
574
}
575
}
576
577
void SoftBodyMotionProperties::ApplyLRAConstraints(uint inStartIndex, uint inEndIndex)
578
{
579
JPH_PROFILE_FUNCTION();
580
581
// Satisfy LRA constraints
582
Vertex *vertices = mVertices.data();
583
for (const LRA *lra = mSettings->mLRAConstraints.data() + inStartIndex, *lra_end = mSettings->mLRAConstraints.data() + inEndIndex; lra < lra_end; ++lra)
584
{
585
JPH_ASSERT(lra->mVertex[0] < mVertices.size());
586
JPH_ASSERT(lra->mVertex[1] < mVertices.size());
587
const Vertex &vertex0 = vertices[lra->mVertex[0]];
588
Vertex &vertex1 = vertices[lra->mVertex[1]];
589
590
Vec3 x0 = vertex0.mPosition;
591
Vec3 delta = vertex1.mPosition - x0;
592
float delta_len_sq = delta.LengthSq();
593
if (delta_len_sq > Square(lra->mMaxDistance))
594
vertex1.mPosition = x0 + delta * lra->mMaxDistance / sqrt(delta_len_sq);
595
}
596
}
597
598
void SoftBodyMotionProperties::ApplyCollisionConstraintsAndUpdateVelocities(const SoftBodyUpdateContext &inContext)
599
{
600
JPH_PROFILE_FUNCTION();
601
602
float dt = inContext.mSubStepDeltaTime;
603
float restitution_threshold = -2.0f * inContext.mGravity.Length() * dt;
604
float vertex_radius = mSettings->mVertexRadius;
605
for (Vertex &v : mVertices)
606
if (v.mInvMass > 0.0f)
607
{
608
// Remember previous velocity for restitution calculations
609
Vec3 prev_v = v.mVelocity;
610
611
// XPBD velocity update
612
v.mVelocity = (v.mPosition - v.mPreviousPosition) / dt;
613
614
// Satisfy collision constraint
615
if (v.mCollidingShapeIndex >= 0)
616
{
617
// Check if there is a collision
618
float projected_distance = -v.mCollisionPlane.SignedDistance(v.mPosition) + vertex_radius;
619
if (projected_distance > 0.0f)
620
{
621
// Remember that there was a collision
622
v.mHasContact = true;
623
624
// We need a contact callback if one of the vertices collided
625
mNeedContactCallback.store(true, memory_order_relaxed);
626
627
// Note that we already calculated the velocity, so this does not affect the velocity (next iteration starts by setting previous position to current position)
628
CollidingShape &cs = mCollidingShapes[v.mCollidingShapeIndex];
629
Vec3 contact_normal = v.mCollisionPlane.GetNormal();
630
v.mPosition += contact_normal * projected_distance;
631
632
// Apply friction as described in Detailed Rigid Body Simulation with Extended Position Based Dynamics - Matthias Muller et al.
633
// See section 3.6:
634
// Inverse mass: w1 = 1 / m1, w2 = 1 / m2 + (r2 x n)^T I^-1 (r2 x n) = 0 for a static object
635
// r2 are the contact point relative to the center of mass of body 2
636
// Lagrange multiplier for contact: lambda = -c / (w1 + w2)
637
// Where c is the constraint equation (the distance to the plane, negative because penetrating)
638
// Contact normal force: fn = lambda / dt^2
639
// Delta velocity due to friction dv = -vt / |vt| * min(dt * friction * fn * (w1 + w2), |vt|) = -vt * min(-friction * c / (|vt| * dt), 1)
640
// Note that I think there is an error in the paper, I added a mass term, see: https://github.com/matthias-research/pages/issues/29
641
// Relative velocity: vr = v1 - v2 - omega2 x r2
642
// Normal velocity: vn = vr . contact_normal
643
// Tangential velocity: vt = vr - contact_normal * vn
644
// Impulse: p = dv / (w1 + w2)
645
// Changes in particle velocities:
646
// v1 = v1 + p / m1
647
// v2 = v2 - p / m2 (no change when colliding with a static body)
648
// w2 = w2 - I^-1 (r2 x p) (no change when colliding with a static body)
649
if (cs.mMotionType == EMotionType::Dynamic)
650
{
651
// Calculate normal and tangential velocity (equation 30)
652
Vec3 r2 = v.mPosition - cs.mCenterOfMassTransform.GetTranslation();
653
Vec3 v2 = cs.GetPointVelocity(r2);
654
Vec3 relative_velocity = v.mVelocity - v2;
655
Vec3 v_normal = contact_normal * contact_normal.Dot(relative_velocity);
656
Vec3 v_tangential = relative_velocity - v_normal;
657
float v_tangential_length = v_tangential.Length();
658
659
// Calculate resulting inverse mass of vertex
660
float vertex_inv_mass = cs.mSoftBodyInvMassScale * v.mInvMass;
661
662
// Calculate inverse effective mass
663
Vec3 r2_cross_n = r2.Cross(contact_normal);
664
float w2 = cs.mInvMass + r2_cross_n.Dot(cs.mInvInertia * r2_cross_n);
665
float w1_plus_w2 = vertex_inv_mass + w2;
666
if (w1_plus_w2 > 0.0f)
667
{
668
// Calculate delta relative velocity due to friction (modified equation 31)
669
Vec3 dv;
670
if (v_tangential_length > 0.0f)
671
dv = v_tangential * min(cs.mFriction * projected_distance / (v_tangential_length * dt), 1.0f);
672
else
673
dv = Vec3::sZero();
674
675
// Calculate delta relative velocity due to restitution (equation 35)
676
dv += v_normal;
677
float prev_v_normal = (prev_v - v2).Dot(contact_normal);
678
if (prev_v_normal < restitution_threshold)
679
dv += cs.mRestitution * prev_v_normal * contact_normal;
680
681
// Calculate impulse
682
Vec3 p = dv / w1_plus_w2;
683
684
// Apply impulse to particle
685
v.mVelocity -= p * vertex_inv_mass;
686
687
// Apply impulse to rigid body
688
cs.mLinearVelocity += p * cs.mInvMass;
689
cs.mAngularVelocity += cs.mInvInertia * r2.Cross(p);
690
691
// Mark that the velocities of the body we hit need to be updated
692
cs.mUpdateVelocities = true;
693
}
694
}
695
else if (cs.mSoftBodyInvMassScale > 0.0f)
696
{
697
// Body is not movable, equations are simpler
698
699
// Calculate normal and tangential velocity (equation 30)
700
Vec3 v_normal = contact_normal * contact_normal.Dot(v.mVelocity);
701
Vec3 v_tangential = v.mVelocity - v_normal;
702
float v_tangential_length = v_tangential.Length();
703
704
// Apply friction (modified equation 31)
705
if (v_tangential_length > 0.0f)
706
v.mVelocity -= v_tangential * min(cs.mFriction * projected_distance / (v_tangential_length * dt), 1.0f);
707
708
// Apply restitution (equation 35)
709
v.mVelocity -= v_normal;
710
float prev_v_normal = prev_v.Dot(contact_normal);
711
if (prev_v_normal < restitution_threshold)
712
v.mVelocity -= cs.mRestitution * prev_v_normal * contact_normal;
713
}
714
}
715
}
716
}
717
}
718
719
void SoftBodyMotionProperties::UpdateSoftBodyState(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings)
720
{
721
JPH_PROFILE_FUNCTION();
722
723
// Contact callback
724
if (mNeedContactCallback.load(memory_order_relaxed) && ioContext.mContactListener != nullptr)
725
{
726
// Remove non-colliding sensors from the list
727
for (int i = int(mCollidingSensors.size()) - 1; i >= 0; --i)
728
if (!mCollidingSensors[i].mHasContact)
729
{
730
mCollidingSensors[i] = std::move(mCollidingSensors.back());
731
mCollidingSensors.pop_back();
732
}
733
734
ioContext.mContactListener->OnSoftBodyContactAdded(*ioContext.mBody, SoftBodyManifold(this));
735
}
736
737
// Loop through vertices once more to update the global state
738
float dt = ioContext.mDeltaTime;
739
float max_linear_velocity_sq = Square(GetMaxLinearVelocity());
740
float max_v_sq = 0.0f;
741
Vec3 linear_velocity = Vec3::sZero(), angular_velocity = Vec3::sZero();
742
mLocalPredictedBounds = mLocalBounds = { };
743
for (Vertex &v : mVertices)
744
{
745
// Calculate max square velocity
746
float v_sq = v.mVelocity.LengthSq();
747
max_v_sq = max(max_v_sq, v_sq);
748
749
// Clamp if velocity is too high
750
if (v_sq > max_linear_velocity_sq)
751
v.mVelocity *= sqrt(max_linear_velocity_sq / v_sq);
752
753
// Calculate local linear/angular velocity
754
linear_velocity += v.mVelocity;
755
angular_velocity += v.mPosition.Cross(v.mVelocity);
756
757
// Update local bounding box
758
mLocalBounds.Encapsulate(v.mPosition);
759
760
// Create predicted position for the next frame in order to detect collisions before they happen
761
mLocalPredictedBounds.Encapsulate(v.mPosition + v.mVelocity * dt + ioContext.mDisplacementDueToGravity);
762
763
// Reset collision data for the next iteration
764
v.ResetCollision();
765
}
766
767
// Calculate linear/angular velocity of the body by averaging all vertices and bringing the value to world space
768
float num_vertices_divider = float(max(int(mVertices.size()), 1));
769
SetLinearVelocityClamped(ioContext.mCenterOfMassTransform.Multiply3x3(linear_velocity / num_vertices_divider));
770
SetAngularVelocity(ioContext.mCenterOfMassTransform.Multiply3x3(angular_velocity / num_vertices_divider));
771
772
if (mUpdatePosition)
773
{
774
// Shift the body so that the position is the center of the local bounds
775
Vec3 delta = mLocalBounds.GetCenter();
776
ioContext.mDeltaPosition = ioContext.mCenterOfMassTransform.Multiply3x3(delta);
777
for (Vertex &v : mVertices)
778
v.mPosition -= delta;
779
780
// Update the skin state too since we will use this position as the previous position in the next update
781
for (SkinState &s : mSkinState)
782
s.mPosition -= delta;
783
JPH_IF_DEBUG_RENDERER(mSkinStateTransform.SetTranslation(mSkinStateTransform.GetTranslation() + ioContext.mDeltaPosition);)
784
785
// Offset bounds to match new position
786
mLocalBounds.Translate(-delta);
787
mLocalPredictedBounds.Translate(-delta);
788
}
789
else
790
ioContext.mDeltaPosition = Vec3::sZero();
791
792
// Test if we should go to sleep
793
if (GetAllowSleeping())
794
{
795
if (max_v_sq > inPhysicsSettings.mPointVelocitySleepThreshold)
796
{
797
ResetSleepTestTimer();
798
ioContext.mCanSleep = ECanSleep::CannotSleep;
799
}
800
else
801
ioContext.mCanSleep = AccumulateSleepTime(dt, inPhysicsSettings.mTimeBeforeSleep);
802
}
803
else
804
ioContext.mCanSleep = ECanSleep::CannotSleep;
805
806
// If SkinVertices is not called after this then don't use the previous position as the skin is static
807
mSkinStatePreviousPositionValid = false;
808
809
// Reset force accumulator
810
ResetForce();
811
}
812
813
void SoftBodyMotionProperties::UpdateRigidBodyVelocities(const SoftBodyUpdateContext &inContext, BodyInterface &inBodyInterface)
814
{
815
JPH_PROFILE_FUNCTION();
816
817
// Write back velocity deltas
818
for (const CollidingShape &cs : mCollidingShapes)
819
if (cs.mUpdateVelocities)
820
inBodyInterface.AddLinearAndAngularVelocity(cs.mBodyID, inContext.mCenterOfMassTransform.Multiply3x3(cs.mLinearVelocity - cs.mOriginalLinearVelocity), inContext.mCenterOfMassTransform.Multiply3x3(cs.mAngularVelocity - cs.mOriginalAngularVelocity));
821
822
// Clear colliding shapes/sensors to avoid hanging on to references to shapes
823
mCollidingShapes.clear();
824
mCollidingSensors.clear();
825
}
826
827
void SoftBodyMotionProperties::InitializeUpdateContext(float inDeltaTime, Body &inSoftBody, const PhysicsSystem &inSystem, SoftBodyUpdateContext &ioContext)
828
{
829
JPH_PROFILE_FUNCTION();
830
831
// Store body
832
ioContext.mBody = &inSoftBody;
833
ioContext.mMotionProperties = this;
834
ioContext.mContactListener = inSystem.GetSoftBodyContactListener();
835
ioContext.mSimShapeFilter = inSystem.GetSimShapeFilter();
836
837
// Convert gravity to local space
838
ioContext.mCenterOfMassTransform = inSoftBody.GetCenterOfMassTransform();
839
ioContext.mGravity = ioContext.mCenterOfMassTransform.Multiply3x3Transposed(GetGravityFactor() * inSystem.GetGravity());
840
841
// Calculate delta time for sub step
842
ioContext.mDeltaTime = inDeltaTime;
843
ioContext.mSubStepDeltaTime = inDeltaTime / mNumIterations;
844
845
// Calculate total displacement we'll have due to gravity over all sub steps
846
// The total displacement as produced by our integrator can be written as: Sum(i * g * dt^2, i = 0..mNumIterations).
847
// This is bigger than 0.5 * g * dt^2 because we first increment the velocity and then update the position
848
// Using Sum(i, i = 0..n) = n * (n + 1) / 2 we can write this as:
849
ioContext.mDisplacementDueToGravity = (0.5f * mNumIterations * (mNumIterations + 1) * Square(ioContext.mSubStepDeltaTime)) * ioContext.mGravity;
850
}
851
852
void SoftBodyMotionProperties::StartNextIteration(const SoftBodyUpdateContext &ioContext)
853
{
854
ApplyPressure(ioContext);
855
856
IntegratePositions(ioContext);
857
}
858
859
void SoftBodyMotionProperties::StartFirstIteration(SoftBodyUpdateContext &ioContext)
860
{
861
// Start the first iteration
862
JPH_IF_ENABLE_ASSERTS(uint iteration =) ioContext.mNextIteration.fetch_add(1, memory_order_relaxed);
863
JPH_ASSERT(iteration == 0);
864
StartNextIteration(ioContext);
865
ioContext.mState.store(SoftBodyUpdateContext::EState::ApplyConstraints, memory_order_release);
866
}
867
868
SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelDetermineCollisionPlanes(SoftBodyUpdateContext &ioContext)
869
{
870
// Do a relaxed read first to see if there is any work to do (this prevents us from doing expensive atomic operations and also prevents us from continuously incrementing the counter and overflowing it)
871
uint num_vertices = (uint)mVertices.size();
872
if (ioContext.mNextCollisionVertex.load(memory_order_relaxed) < num_vertices)
873
{
874
// Fetch next batch of vertices to process
875
uint next_vertex = ioContext.mNextCollisionVertex.fetch_add(SoftBodyUpdateContext::cVertexCollisionBatch, memory_order_acquire);
876
if (next_vertex < num_vertices)
877
{
878
// Process collision planes
879
uint num_vertices_to_process = min(SoftBodyUpdateContext::cVertexCollisionBatch, num_vertices - next_vertex);
880
DetermineCollisionPlanes(next_vertex, num_vertices_to_process);
881
uint vertices_processed = ioContext.mNumCollisionVerticesProcessed.fetch_add(SoftBodyUpdateContext::cVertexCollisionBatch, memory_order_acq_rel) + num_vertices_to_process;
882
if (vertices_processed >= num_vertices)
883
{
884
// Determine next state
885
if (mCollidingSensors.empty())
886
StartFirstIteration(ioContext);
887
else
888
ioContext.mState.store(SoftBodyUpdateContext::EState::DetermineSensorCollisions, memory_order_release);
889
}
890
return EStatus::DidWork;
891
}
892
}
893
894
return EStatus::NoWork;
895
}
896
897
SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelDetermineSensorCollisions(SoftBodyUpdateContext &ioContext)
898
{
899
// Do a relaxed read to see if there are more sensors to process
900
if (ioContext.mNextSensorIndex.load(memory_order_relaxed) < mNumSensors)
901
{
902
// Fetch next sensor to process
903
uint sensor_index = ioContext.mNextSensorIndex.fetch_add(1, memory_order_acquire);
904
if (sensor_index < mNumSensors)
905
{
906
// Process this sensor
907
DetermineSensorCollisions(mCollidingSensors[sensor_index]);
908
909
// Determine next state
910
uint sensors_processed = ioContext.mNumSensorsProcessed.fetch_add(1, memory_order_acq_rel) + 1;
911
if (sensors_processed >= mNumSensors)
912
StartFirstIteration(ioContext);
913
return EStatus::DidWork;
914
}
915
}
916
917
return EStatus::NoWork;
918
}
919
920
void SoftBodyMotionProperties::ProcessGroup(const SoftBodyUpdateContext &ioContext, uint inGroupIndex)
921
{
922
// Determine start and end
923
SoftBodySharedSettings::UpdateGroup start { 0, 0, 0, 0, 0 };
924
const SoftBodySharedSettings::UpdateGroup &prev = inGroupIndex > 0? mSettings->mUpdateGroups[inGroupIndex - 1] : start;
925
const SoftBodySharedSettings::UpdateGroup &current = mSettings->mUpdateGroups[inGroupIndex];
926
927
// Process volume constraints
928
ApplyVolumeConstraints(ioContext, prev.mVolumeEndIndex, current.mVolumeEndIndex);
929
930
// Process bend constraints
931
ApplyDihedralBendConstraints(ioContext, prev.mDihedralBendEndIndex, current.mDihedralBendEndIndex);
932
933
// Process skinned constraints
934
ApplySkinConstraints(ioContext, prev.mSkinnedEndIndex, current.mSkinnedEndIndex);
935
936
// Process edges
937
ApplyEdgeConstraints(ioContext, prev.mEdgeEndIndex, current.mEdgeEndIndex);
938
939
// Process LRA constraints
940
ApplyLRAConstraints(prev.mLRAEndIndex, current.mLRAEndIndex);
941
}
942
943
SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelApplyConstraints(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings)
944
{
945
uint num_groups = (uint)mSettings->mUpdateGroups.size();
946
JPH_ASSERT(num_groups > 0, "SoftBodySharedSettings::Optimize should have been called!");
947
--num_groups; // Last group is the non-parallel group, we don't want to execute it in parallel
948
949
// Do a relaxed read first to see if there is any work to do (this prevents us from doing expensive atomic operations and also prevents us from continuously incrementing the counter and overflowing it)
950
uint next_group = ioContext.mNextConstraintGroup.load(memory_order_relaxed);
951
if (next_group < num_groups || (num_groups == 0 && next_group == 0))
952
{
953
// Fetch the next group process
954
next_group = ioContext.mNextConstraintGroup.fetch_add(1, memory_order_acquire);
955
if (next_group < num_groups || (num_groups == 0 && next_group == 0))
956
{
957
uint num_groups_processed = 0;
958
if (num_groups > 0)
959
{
960
// Process this group
961
ProcessGroup(ioContext, next_group);
962
963
// Increment total number of groups processed
964
num_groups_processed = ioContext.mNumConstraintGroupsProcessed.fetch_add(1, memory_order_acq_rel) + 1;
965
}
966
967
if (num_groups_processed >= num_groups)
968
{
969
// Finish the iteration
970
JPH_PROFILE("FinishIteration");
971
972
// Process non-parallel group
973
ProcessGroup(ioContext, num_groups);
974
975
ApplyCollisionConstraintsAndUpdateVelocities(ioContext);
976
977
uint iteration = ioContext.mNextIteration.fetch_add(1, memory_order_relaxed);
978
if (iteration < mNumIterations)
979
{
980
// Start a new iteration
981
StartNextIteration(ioContext);
982
983
// Reset group logic
984
ioContext.mNumConstraintGroupsProcessed.store(0, memory_order_release);
985
ioContext.mNextConstraintGroup.store(0, memory_order_release);
986
}
987
else
988
{
989
// On final iteration we update the state
990
UpdateSoftBodyState(ioContext, inPhysicsSettings);
991
992
ioContext.mState.store(SoftBodyUpdateContext::EState::Done, memory_order_release);
993
return EStatus::Done;
994
}
995
}
996
997
return EStatus::DidWork;
998
}
999
}
1000
return EStatus::NoWork;
1001
}
1002
1003
SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelUpdate(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings)
1004
{
1005
switch (ioContext.mState.load(memory_order_acquire))
1006
{
1007
case SoftBodyUpdateContext::EState::DetermineCollisionPlanes:
1008
return ParallelDetermineCollisionPlanes(ioContext);
1009
1010
case SoftBodyUpdateContext::EState::DetermineSensorCollisions:
1011
return ParallelDetermineSensorCollisions(ioContext);
1012
1013
case SoftBodyUpdateContext::EState::ApplyConstraints:
1014
return ParallelApplyConstraints(ioContext, inPhysicsSettings);
1015
1016
case SoftBodyUpdateContext::EState::Done:
1017
return EStatus::Done;
1018
1019
default:
1020
JPH_ASSERT(false);
1021
return EStatus::NoWork;
1022
}
1023
}
1024
1025
void SoftBodyMotionProperties::SkinVertices([[maybe_unused]] RMat44Arg inCenterOfMassTransform, const Mat44 *inJointMatrices, [[maybe_unused]] uint inNumJoints, bool inHardSkinAll, TempAllocator &ioTempAllocator)
1026
{
1027
// Calculate the skin matrices
1028
uint num_skin_matrices = uint(mSettings->mInvBindMatrices.size());
1029
uint skin_matrices_size = num_skin_matrices * sizeof(Mat44);
1030
Mat44 *skin_matrices = (Mat44 *)ioTempAllocator.Allocate(skin_matrices_size);
1031
JPH_SCOPE_EXIT([&ioTempAllocator, skin_matrices, skin_matrices_size]{ ioTempAllocator.Free(skin_matrices, skin_matrices_size); });
1032
const Mat44 *skin_matrices_end = skin_matrices + num_skin_matrices;
1033
const InvBind *inv_bind_matrix = mSettings->mInvBindMatrices.data();
1034
for (Mat44 *s = skin_matrices; s < skin_matrices_end; ++s, ++inv_bind_matrix)
1035
{
1036
JPH_ASSERT(inv_bind_matrix->mJointIndex < inNumJoints);
1037
*s = inJointMatrices[inv_bind_matrix->mJointIndex] * inv_bind_matrix->mInvBind;
1038
}
1039
1040
// Skin the vertices
1041
JPH_IF_DEBUG_RENDERER(mSkinStateTransform = inCenterOfMassTransform;)
1042
JPH_IF_ENABLE_ASSERTS(uint num_vertices = uint(mSettings->mVertices.size());)
1043
JPH_ASSERT(mSkinState.size() == num_vertices);
1044
const SoftBodySharedSettings::Vertex *in_vertices = mSettings->mVertices.data();
1045
for (const Skinned &s : mSettings->mSkinnedConstraints)
1046
{
1047
// Get bind pose
1048
JPH_ASSERT(s.mVertex < num_vertices);
1049
Vec3 bind_pos = Vec3::sLoadFloat3Unsafe(in_vertices[s.mVertex].mPosition);
1050
1051
// Skin vertex
1052
Vec3 pos = Vec3::sZero();
1053
for (const SkinWeight &w : s.mWeights)
1054
{
1055
// We assume that the first zero weight is the end of the list
1056
if (w.mWeight == 0.0f)
1057
break;
1058
1059
JPH_ASSERT(w.mInvBindIndex < num_skin_matrices);
1060
pos += w.mWeight * (skin_matrices[w.mInvBindIndex] * bind_pos);
1061
}
1062
SkinState &skin_state = mSkinState[s.mVertex];
1063
skin_state.mPreviousPosition = skin_state.mPosition;
1064
skin_state.mPosition = pos;
1065
}
1066
1067
// Calculate the normals
1068
for (const Skinned &s : mSettings->mSkinnedConstraints)
1069
{
1070
Vec3 normal = Vec3::sZero();
1071
uint32 num_faces = s.mNormalInfo >> 24;
1072
if (num_faces > 0)
1073
{
1074
// Calculate normal
1075
const uint32 *f = &mSettings->mSkinnedConstraintNormals[s.mNormalInfo & 0xffffff];
1076
const uint32 *f_end = f + num_faces;
1077
while (f < f_end)
1078
{
1079
const Face &face = mSettings->mFaces[*f];
1080
Vec3 v0 = mSkinState[face.mVertex[0]].mPosition;
1081
Vec3 v1 = mSkinState[face.mVertex[1]].mPosition;
1082
Vec3 v2 = mSkinState[face.mVertex[2]].mPosition;
1083
normal += (v1 - v0).Cross(v2 - v0).NormalizedOr(Vec3::sZero());
1084
++f;
1085
}
1086
normal = normal.NormalizedOr(Vec3::sZero());
1087
}
1088
mSkinState[s.mVertex].mNormal = normal;
1089
}
1090
1091
if (inHardSkinAll)
1092
{
1093
// Hard skin all vertices and reset their velocities
1094
for (const Skinned &s : mSettings->mSkinnedConstraints)
1095
{
1096
Vertex &vertex = mVertices[s.mVertex];
1097
SkinState &skin_state = mSkinState[s.mVertex];
1098
skin_state.mPreviousPosition = skin_state.mPosition;
1099
vertex.mPosition = skin_state.mPosition;
1100
vertex.mVelocity = Vec3::sZero();
1101
}
1102
}
1103
else if (!mEnableSkinConstraints)
1104
{
1105
// Hard skin only the kinematic vertices as we will not solve the skin constraints later
1106
for (const Skinned &s : mSettings->mSkinnedConstraints)
1107
if (s.mMaxDistance == 0.0f)
1108
{
1109
Vertex &vertex = mVertices[s.mVertex];
1110
vertex.mPosition = mSkinState[s.mVertex].mPosition;
1111
}
1112
}
1113
1114
// Indicate that the previous positions are valid for the coming update
1115
mSkinStatePreviousPositionValid = true;
1116
}
1117
1118
void SoftBodyMotionProperties::CustomUpdate(float inDeltaTime, Body &ioSoftBody, PhysicsSystem &inSystem)
1119
{
1120
JPH_PROFILE_FUNCTION();
1121
1122
// Create update context
1123
SoftBodyUpdateContext context;
1124
InitializeUpdateContext(inDeltaTime, ioSoftBody, inSystem, context);
1125
1126
// Determine bodies we're colliding with
1127
DetermineCollidingShapes(context, inSystem, inSystem.GetBodyLockInterface());
1128
1129
// Call the internal update until it finishes
1130
EStatus status;
1131
const PhysicsSettings &settings = inSystem.GetPhysicsSettings();
1132
while ((status = ParallelUpdate(context, settings)) == EStatus::DidWork)
1133
continue;
1134
JPH_ASSERT(status == EStatus::Done);
1135
1136
// Update the state of the bodies we've collided with
1137
UpdateRigidBodyVelocities(context, inSystem.GetBodyInterface());
1138
1139
// Update position of the soft body
1140
if (mUpdatePosition)
1141
inSystem.GetBodyInterface().SetPosition(ioSoftBody.GetID(), ioSoftBody.GetPosition() + context.mDeltaPosition, EActivation::DontActivate);
1142
}
1143
1144
#ifdef JPH_DEBUG_RENDERER
1145
1146
void SoftBodyMotionProperties::DrawVertices(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const
1147
{
1148
for (const Vertex &v : mVertices)
1149
inRenderer->DrawMarker(inCenterOfMassTransform * v.mPosition, v.mInvMass > 0.0f? Color::sGreen : Color::sRed, 0.05f);
1150
}
1151
1152
void SoftBodyMotionProperties::DrawVertexVelocities(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const
1153
{
1154
for (const Vertex &v : mVertices)
1155
inRenderer->DrawArrow(inCenterOfMassTransform * v.mPosition, inCenterOfMassTransform * (v.mPosition + v.mVelocity), Color::sYellow, 0.01f);
1156
}
1157
1158
template <typename GetEndIndex, typename DrawConstraint>
1159
inline void SoftBodyMotionProperties::DrawConstraints(ESoftBodyConstraintColor inConstraintColor, const GetEndIndex &inGetEndIndex, const DrawConstraint &inDrawConstraint, ColorArg inBaseColor) const
1160
{
1161
uint start = 0;
1162
for (uint i = 0; i < (uint)mSettings->mUpdateGroups.size(); ++i)
1163
{
1164
uint end = inGetEndIndex(mSettings->mUpdateGroups[i]);
1165
1166
Color base_color;
1167
if (inConstraintColor != ESoftBodyConstraintColor::ConstraintType)
1168
base_color = Color::sGetDistinctColor((uint)mSettings->mUpdateGroups.size() - i - 1); // Ensure that color 0 is always the last group
1169
else
1170
base_color = inBaseColor;
1171
1172
for (uint idx = start; idx < end; ++idx)
1173
{
1174
Color color = inConstraintColor == ESoftBodyConstraintColor::ConstraintOrder? base_color * (float(idx - start) / (end - start)) : base_color;
1175
inDrawConstraint(idx, color);
1176
}
1177
1178
start = end;
1179
}
1180
}
1181
1182
void SoftBodyMotionProperties::DrawEdgeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
1183
{
1184
DrawConstraints(inConstraintColor,
1185
[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
1186
return inGroup.mEdgeEndIndex;
1187
},
1188
[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {
1189
const Edge &e = mSettings->mEdgeConstraints[inIndex];
1190
inRenderer->DrawLine(inCenterOfMassTransform * mVertices[e.mVertex[0]].mPosition, inCenterOfMassTransform * mVertices[e.mVertex[1]].mPosition, inColor);
1191
},
1192
Color::sWhite);
1193
}
1194
1195
void SoftBodyMotionProperties::DrawBendConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
1196
{
1197
DrawConstraints(inConstraintColor,
1198
[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
1199
return inGroup.mDihedralBendEndIndex;
1200
},
1201
[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {
1202
const DihedralBend &b = mSettings->mDihedralBendConstraints[inIndex];
1203
1204
RVec3 x0 = inCenterOfMassTransform * mVertices[b.mVertex[0]].mPosition;
1205
RVec3 x1 = inCenterOfMassTransform * mVertices[b.mVertex[1]].mPosition;
1206
RVec3 x2 = inCenterOfMassTransform * mVertices[b.mVertex[2]].mPosition;
1207
RVec3 x3 = inCenterOfMassTransform * mVertices[b.mVertex[3]].mPosition;
1208
RVec3 c_edge = 0.5_r * (x0 + x1);
1209
RVec3 c0 = (x0 + x1 + x2) / 3.0_r;
1210
RVec3 c1 = (x0 + x1 + x3) / 3.0_r;
1211
1212
inRenderer->DrawArrow(0.9_r * x0 + 0.1_r * x1, 0.1_r * x0 + 0.9_r * x1, inColor, 0.01f);
1213
inRenderer->DrawLine(c_edge, 0.1_r * c_edge + 0.9_r * c0, inColor);
1214
inRenderer->DrawLine(c_edge, 0.1_r * c_edge + 0.9_r * c1, inColor);
1215
},
1216
Color::sGreen);
1217
}
1218
1219
void SoftBodyMotionProperties::DrawVolumeConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
1220
{
1221
DrawConstraints(inConstraintColor,
1222
[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
1223
return inGroup.mVolumeEndIndex;
1224
},
1225
[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {
1226
const Volume &v = mSettings->mVolumeConstraints[inIndex];
1227
1228
RVec3 x1 = inCenterOfMassTransform * mVertices[v.mVertex[0]].mPosition;
1229
RVec3 x2 = inCenterOfMassTransform * mVertices[v.mVertex[1]].mPosition;
1230
RVec3 x3 = inCenterOfMassTransform * mVertices[v.mVertex[2]].mPosition;
1231
RVec3 x4 = inCenterOfMassTransform * mVertices[v.mVertex[3]].mPosition;
1232
1233
inRenderer->DrawTriangle(x1, x3, x2, inColor, DebugRenderer::ECastShadow::On);
1234
inRenderer->DrawTriangle(x2, x3, x4, inColor, DebugRenderer::ECastShadow::On);
1235
inRenderer->DrawTriangle(x1, x4, x3, inColor, DebugRenderer::ECastShadow::On);
1236
inRenderer->DrawTriangle(x1, x2, x4, inColor, DebugRenderer::ECastShadow::On);
1237
},
1238
Color::sYellow);
1239
}
1240
1241
void SoftBodyMotionProperties::DrawSkinConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
1242
{
1243
DrawConstraints(inConstraintColor,
1244
[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
1245
return inGroup.mSkinnedEndIndex;
1246
},
1247
[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {
1248
const Skinned &s = mSettings->mSkinnedConstraints[inIndex];
1249
const SkinState &skin_state = mSkinState[s.mVertex];
1250
inRenderer->DrawArrow(mSkinStateTransform * skin_state.mPosition, mSkinStateTransform * (skin_state.mPosition + 0.1f * skin_state.mNormal), inColor, 0.01f);
1251
inRenderer->DrawLine(mSkinStateTransform * skin_state.mPosition, inCenterOfMassTransform * mVertices[s.mVertex].mPosition, Color::sBlue);
1252
},
1253
Color::sOrange);
1254
}
1255
1256
void SoftBodyMotionProperties::DrawLRAConstraints(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, ESoftBodyConstraintColor inConstraintColor) const
1257
{
1258
DrawConstraints(inConstraintColor,
1259
[](const SoftBodySharedSettings::UpdateGroup &inGroup) {
1260
return inGroup.mLRAEndIndex;
1261
},
1262
[this, inRenderer, &inCenterOfMassTransform](uint inIndex, ColorArg inColor) {
1263
const LRA &l = mSettings->mLRAConstraints[inIndex];
1264
inRenderer->DrawLine(inCenterOfMassTransform * mVertices[l.mVertex[0]].mPosition, inCenterOfMassTransform * mVertices[l.mVertex[1]].mPosition, inColor);
1265
},
1266
Color::sGrey);
1267
}
1268
1269
void SoftBodyMotionProperties::DrawPredictedBounds(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform) const
1270
{
1271
inRenderer->DrawWireBox(inCenterOfMassTransform, mLocalPredictedBounds, Color::sRed);
1272
}
1273
1274
#endif // JPH_DEBUG_RENDERER
1275
1276
void SoftBodyMotionProperties::SaveState(StateRecorder &inStream) const
1277
{
1278
MotionProperties::SaveState(inStream);
1279
1280
for (const Vertex &v : mVertices)
1281
{
1282
inStream.Write(v.mPreviousPosition);
1283
inStream.Write(v.mPosition);
1284
inStream.Write(v.mVelocity);
1285
}
1286
1287
for (const SkinState &s : mSkinState)
1288
{
1289
inStream.Write(s.mPreviousPosition);
1290
inStream.Write(s.mPosition);
1291
inStream.Write(s.mNormal);
1292
}
1293
1294
inStream.Write(mLocalBounds.mMin);
1295
inStream.Write(mLocalBounds.mMax);
1296
inStream.Write(mLocalPredictedBounds.mMin);
1297
inStream.Write(mLocalPredictedBounds.mMax);
1298
}
1299
1300
void SoftBodyMotionProperties::RestoreState(StateRecorder &inStream)
1301
{
1302
MotionProperties::RestoreState(inStream);
1303
1304
for (Vertex &v : mVertices)
1305
{
1306
inStream.Read(v.mPreviousPosition);
1307
inStream.Read(v.mPosition);
1308
inStream.Read(v.mVelocity);
1309
}
1310
1311
for (SkinState &s : mSkinState)
1312
{
1313
inStream.Read(s.mPreviousPosition);
1314
inStream.Read(s.mPosition);
1315
inStream.Read(s.mNormal);
1316
}
1317
1318
inStream.Read(mLocalBounds.mMin);
1319
inStream.Read(mLocalBounds.mMax);
1320
inStream.Read(mLocalPredictedBounds.mMin);
1321
inStream.Read(mLocalPredictedBounds.mMax);
1322
}
1323
1324
JPH_NAMESPACE_END
1325
1326