-
Notifications
You must be signed in to change notification settings - Fork 1
/
joltSample.cpp
520 lines (429 loc) ยท 20.2 KB
/
joltSample.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
// The Jolt headers don't include Jolt.h. Always include Jolt.h before including any other Jolt header.
// You can use Jolt.h in your precompiled header to speed up compilation.
#include <Jolt/Jolt.h>
// Jolt includes
#include <Jolt/RegisterTypes.h>
#include <Jolt/Core/Factory.h>
#include <Jolt/Core/TempAllocator.h>
#include <Jolt/Core/JobSystemThreadPool.h>
#include <Jolt/Physics/PhysicsSettings.h>
#include <Jolt/Physics/PhysicsSystem.h>
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Body/BodyCreationSettings.h>
#include <Jolt/Physics/Body/BodyActivationListener.h>
#include <Jolt/Physics/SoftBody/SoftBodySharedSettings.h>
#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
#include <raylib.h>
#include <raymath.h>
// STL includes
#include <iostream>
#include <cstdarg>
#include <thread>
#include <vector>
// Disable common warnings triggered by Jolt, you can use JPH_SUPPRESS_WARNING_PUSH / JPH_SUPPRESS_WARNING_POP to store and restore the warning state
JPH_SUPPRESS_WARNING_PUSH
// All Jolt symbols are in the JPH namespace
using namespace JPH;
// If you want your code to compile using single or double precision write 0.0_r to get a Real value that compiles to double or float depending if JPH_DOUBLE_PRECISION is set or not.
using namespace JPH::literals;
// We're also using STL classes in this example
using namespace std;
SoftBodySharedSettings *CreateSphere(float inRadius, uint inNumTheta, uint inNumPhi)
{
// Create settings
SoftBodySharedSettings *settings = new SoftBodySharedSettings;
// Create vertices
SoftBodySharedSettings::Vertex v;
(inRadius * Vec3::sUnitSpherical(0, 0)).StoreFloat3(&v.mPosition);
settings->mVertices.push_back(v);
(inRadius * Vec3::sUnitSpherical(JPH_PI, 0)).StoreFloat3(&v.mPosition);
settings->mVertices.push_back(v);
for (uint theta = 1; theta < inNumTheta - 1; ++theta)
for (uint phi = 0; phi < inNumPhi; ++phi)
{
(inRadius * Vec3::sUnitSpherical(JPH_PI * theta / (inNumTheta - 1), 2.0f * JPH_PI * phi / inNumPhi)).StoreFloat3(&v.mPosition);
settings->mVertices.push_back(v);
}
// Function to get the vertex index of a point on the sphere
auto vertex_index = [inNumTheta, inNumPhi](uint inTheta, uint inPhi) -> uint
{
if (inTheta == 0)
return 0;
else if (inTheta == inNumTheta - 1)
return 1;
else
return 2 + (inTheta - 1) * inNumPhi + inPhi % inNumPhi;
};
// Create edge constraints
for (uint phi = 0; phi < inNumPhi; ++phi)
{
for (uint theta = 0; theta < inNumTheta - 1; ++theta)
{
SoftBodySharedSettings::Edge e;
e.mCompliance = 0.0001f;
e.mVertex[0] = vertex_index(theta, phi);
e.mVertex[1] = vertex_index(theta + 1, phi);
settings->mEdgeConstraints.push_back(e);
e.mVertex[1] = vertex_index(theta + 1, phi + 1);
settings->mEdgeConstraints.push_back(e);
if (theta > 0)
{
e.mVertex[1] = vertex_index(theta, phi + 1);
settings->mEdgeConstraints.push_back(e);
}
}
}
settings->CalculateEdgeLengths();
// Create faces
SoftBodySharedSettings::Face f;
for (uint phi = 0; phi < inNumPhi; ++phi)
{
for (uint theta = 0; theta < inNumTheta - 2; ++theta)
{
f.mVertex[0] = vertex_index(theta, phi);
f.mVertex[1] = vertex_index(theta + 1, phi);
f.mVertex[2] = vertex_index(theta + 1, phi + 1);
settings->AddFace(f);
if (theta > 0)
{
f.mVertex[1] = vertex_index(theta + 1, phi + 1);
f.mVertex[2] = vertex_index(theta, phi + 1);
settings->AddFace(f);
}
}
f.mVertex[0] = vertex_index(inNumTheta - 2, phi + 1);
f.mVertex[1] = vertex_index(inNumTheta - 2, phi);
f.mVertex[2] = vertex_index(inNumTheta - 1, 0);
settings->AddFace(f);
}
// Optimize the settings
settings->Optimize();
return settings;
}
// Callback for traces, connect this to your own trace function if you have one
static void TraceImpl(const char *inFMT, ...)
{
// Format the message
va_list list;
va_start(list, inFMT);
char buffer[1024];
vsnprintf(buffer, sizeof(buffer), inFMT, list);
va_end(list);
// Print to the TTY
cout << buffer << endl;
}
// Callback for asserts, connect this to your own assert handler if you have one
static bool AssertFailedImpl(const char *inExpression, const char *inMessage, const char *inFile, uint inLine)
{
// Print to the TTY
cout << inFile << ":" << inLine << ": (" << inExpression << ") " << (inMessage != nullptr? inMessage : "") << endl;
// Breakpoint
return true;
};
// Layer that objects can be in, determines which other objects it can collide with
// Typically you at least want to have 1 layer for moving bodies and 1 layer for static bodies, but you can have more
// layers if you want. E.g. you could have a layer for high detail collision (which is not used by the physics simulation
// but only if you do collision testing).
namespace Layers
{
static constexpr ObjectLayer NON_MOVING = 0;
static constexpr ObjectLayer MOVING = 1;
static constexpr ObjectLayer NUM_LAYERS = 2;
};
/// Class that determines if two object layers can collide
class ObjectLayerPairFilterImpl : public ObjectLayerPairFilter
{
public:
virtual bool ShouldCollide(ObjectLayer inObject1, ObjectLayer inObject2) const override
{
switch (inObject1)
{
case Layers::NON_MOVING:
return inObject2 == Layers::MOVING; // Non moving only collides with moving
case Layers::MOVING:
return true; // Moving collides with everything
default:
JPH_ASSERT(false);
return false;
}
}
};
// Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least want to have
// a layer for non-moving and moving objects to avoid having to update a tree full of static objects every frame.
// You can have a 1-on-1 mapping between object layers and broadphase layers (like in this case) but if you have
// many object layers you'll be creating many broad phase trees, which is not efficient. If you want to fine tune
// your broadphase layers define JPH_TRACK_BROADPHASE_STATS and look at the stats reported on the TTY.
namespace BroadPhaseLayers
{
static constexpr BroadPhaseLayer NON_MOVING(0);
static constexpr BroadPhaseLayer MOVING(1);
static constexpr uint NUM_LAYERS(2);
};
#define JPH_EXTERNAL_PROFILEtween object and broadphase layers.
class BPLayerInterfaceImpl final : public BroadPhaseLayerInterface
{
public:
BPLayerInterfaceImpl()
{
// Create a mapping table from object to broad phase layer
mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
}
virtual uint GetNumBroadPhaseLayers() const override
{
return BroadPhaseLayers::NUM_LAYERS;
}
virtual BroadPhaseLayer GetBroadPhaseLayer(ObjectLayer inLayer) const override
{
JPH_ASSERT(inLayer < Layers::NUM_LAYERS);
return mObjectToBroadPhase[inLayer];
}
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
virtual const char * GetBroadPhaseLayerName(BroadPhaseLayer inLayer) const override
{
switch ((BroadPhaseLayer::Type)inLayer)
{
case (BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING: return "NON_MOVING";
case (BroadPhaseLayer::Type)BroadPhaseLayers::MOVING: return "MOVING";
default: JPH_ASSERT(false); return "INVALID";
}
}
#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
private:
BroadPhaseLayer mObjectToBroadPhase[Layers::NUM_LAYERS];
};
/// Class that determines if an object layer can collide with a broadphase layer
class ObjectVsBroadPhaseLayerFilterImpl : public ObjectVsBroadPhaseLayerFilter
{
public:
virtual bool ShouldCollide(ObjectLayer inLayer1, BroadPhaseLayer inLayer2) const override
{
switch (inLayer1)
{
case Layers::NON_MOVING:
return inLayer2 == BroadPhaseLayers::MOVING;
case Layers::MOVING:
return true;
default:
JPH_ASSERT(false);
return false;
}
}
};
// An example contact listener
class MyContactListener : public ContactListener
{
public:
// See: ContactListener
virtual ValidateResult OnContactValidate(const Body &inBody1, const Body &inBody2, RVec3Arg inBaseOffset, const CollideShapeResult &inCollisionResult) override
{
//cout << "Contact validate callback" << endl;
// Allows you to ignore a contact before it is created (using layers to not make objects collide is cheaper!)
return ValidateResult::AcceptAllContactsForThisBodyPair;
}
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
{
//cout << "A contact was added" << endl;
}
virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
{
//cout << "A contact was persisted" << endl;
}
virtual void OnContactRemoved(const SubShapeIDPair &inSubShapePair) override
{
//cout << "A contact was removed" << endl;
}
};
// An example activation listener
class MyBodyActivationListener : public BodyActivationListener
{
public:
virtual void OnBodyActivated(const BodyID &inBodyID, uint64 inBodyUserData) override
{
cout << "A body got activated" << endl;
}
virtual void OnBodyDeactivated(const BodyID &inBodyID, uint64 inBodyUserData) override
{
cout << "A body went to sleep" << endl;
}
};
// Program entry point
int main(int argc, char** argv)
{
// Register allocation hook
RegisterDefaultAllocator();
// Install callbacks
Trace = TraceImpl;
JPH_IF_ENABLE_ASSERTS(AssertFailed = AssertFailedImpl;)
// Create a factory
Factory::sInstance = new Factory();
// Register all Jolt physics types
RegisterTypes();
// We need a temp allocator for temporary allocations during the physics update. We're
// pre-allocating 10 MB to avoid having to do allocations during the physics update.
// B.t.w. 10 MB is way too much for this example but it is a typical value you can use.
// If you don't want to pre-allocate you can also use TempAllocatorMalloc to fall back to
// malloc / free.
TempAllocatorImpl temp_allocator(10 * 1024 * 1024);
// We need a job system that will execute physics jobs on multiple threads. Typically
// you would implement the JobSystem interface yourself and let Jolt Physics run on top
// of your own job scheduler. JobSystemThreadPool is an example implementation.
JobSystemThreadPool job_system(cMaxPhysicsJobs, cMaxPhysicsBarriers, thread::hardware_concurrency() - 1);
// This is the max amount of rigid bodies that you can add to the physics system. If you try to add more you'll get an error.
// Note: This value is low because this is a simple test. For a real project use something in the order of 65536.
const uint cMaxBodies = 1024;
// This determines how many mutexes to allocate to protect rigid bodies from concurrent access. Set it to 0 for the default settings.
const uint cNumBodyMutexes = 0;
// This is the max amount of body pairs that can be queued at any time (the broad phase will detect overlapping
// body pairs based on their bounding boxes and will insert them into a queue for the narrowphase). If you make this buffer
// too small the queue will fill up and the broad phase jobs will start to do narrow phase work. This is slightly less efficient.
// Note: This value is low because this is a simple test. For a real project use something in the order of 65536.
const uint cMaxBodyPairs = 1024;
// This is the maximum size of the contact constraint buffer. If more contacts (collisions between bodies) are detected than this
// number then these contacts will be ignored and bodies will start interpenetrating / fall through the world.
// Note: This value is low because this is a simple test. For a real project use something in the order of 10240.
const uint cMaxContactConstraints = 1024;
// Create mapping table from object layer to broadphase layer
// Note: As this is an interface, PhysicsSystem will take a reference to this so this instance needs to stay alive!
BPLayerInterfaceImpl broad_phase_layer_interface;
// Create class that filters object vs broadphase layers
// Note: As this is an interface, PhysicsSystem will take a reference to this so this instance needs to stay alive!
ObjectVsBroadPhaseLayerFilterImpl object_vs_broadphase_layer_filter;
// Create class that filters object vs object layers
// Note: As this is an interface, PhysicsSystem will take a reference to this so this instance needs to stay alive!
ObjectLayerPairFilterImpl object_vs_object_layer_filter;
// Now we can create the actual physics system.
PhysicsSystem physics_system;
physics_system.Init(cMaxBodies, cNumBodyMutexes, cMaxBodyPairs, cMaxContactConstraints, broad_phase_layer_interface, object_vs_broadphase_layer_filter, object_vs_object_layer_filter);
// A body activation listener gets notified when bodies activate and go to sleep
// Note that this is called from a job so whatever you do here needs to be thread safe.
// Registering one is entirely optional.
MyBodyActivationListener body_activation_listener;
physics_system.SetBodyActivationListener(&body_activation_listener);
// A contact listener gets notified when bodies (are about to) collide, and when they separate again.
// Note that this is called from a job so whatever you do here needs to be thread safe.
// Registering one is entirely optional.
MyContactListener contact_listener;
physics_system.SetContactListener(&contact_listener);
// The main way to interact with the bodies in the physics system is through the body interface. There is a locking and a non-locking
// variant of this. We're going to use the locking version (even though we're not planning to access bodies from multiple threads)
BodyInterface &body_interface = physics_system.GetBodyInterface();
// Next we can create a rigid body to serve as the floor, we make a large box
// Create the settings for the collision volume (the shape).
// Note that for simple shapes (like boxes) you can also directly construct a BoxShape.
BoxShapeSettings floor_shape_settings(Vec3(5.0f, 1.0f, 5.0f));
// Create the shape
ShapeSettings::ShapeResult floor_shape_result = floor_shape_settings.Create();
ShapeRefC floor_shape = floor_shape_result.Get(); // We don't expect an error here, but you can check floor_shape_result for HasError() / GetError()
// Create the settings for the body itself. Note that here you can also set other properties like the restitution / friction.
BodyCreationSettings floor_settings(floor_shape, RVec3(0.0_r, -1.0_r, 0.0_r), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
floor_settings.mRestitution = 0.5f;
floor_settings.mAllowSleeping = true;
// Create the actual rigid body
Body *floor = body_interface.CreateBody(floor_settings); // Note that if we run out of bodies this can return nullptr
//floor->SetFriction(1.0f);
// Add it to the world
body_interface.AddBody(floor->GetID(), EActivation::DontActivate);
// Now create a dynamic body to bounce on the floor
// Note that this uses the shorthand version of creating and adding a body to the world
BodyCreationSettings sphere_settings(new SphereShape(1.0f), RVec3(0.0_r, 20.0_r, 0.0_r), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
//sphere_settings.mFriction = 0.5f;
sphere_settings.mAllowSleeping = true;
BodyID sphere_id = body_interface.CreateAndAddBody(sphere_settings, EActivation::Activate);
//body_interface.SetFriction(sphere_id, 1.0f);
// Now you can interact with the dynamic body, in this case we're going to give it a velocity.
// (note that if we had used CreateBody then we could have set the velocity straight on the body before adding it to the physics system)
body_interface.SetLinearVelocity(sphere_id, Vec3(0.0f, -5.0f, 0.0f));
// We simulate the physics world in discrete time steps. 60 Hz is a good rate to update the physics system.
const float cDeltaTime = 1.0f / 60.0f;
// Optional step: Before starting the physics simulation you can optimize the broad phase. This improves collision detection performance (it's pointless here because we only have 2 bodies).
// You should definitely not call this every frame or when e.g. streaming in a new level section as it is an expensive operation.
// Instead insert all new objects in batches instead of 1 at a time to keep the broad phase efficient.
physics_system.OptimizeBroadPhase();
// Now we're ready to simulate the body, keep simulating until it goes to sleep
uint step = 0;
// while (body_interface.IsActive(sphere_id))
// {
// // Next step
// ++step;
// // Output current position and velocity of the sphere
// RVec3 position = body_interface.GetCenterOfMassPosition(sphere_id);
// Vec3 velocity = body_interface.GetLinearVelocity(sphere_id);
// cout << "Step " << step << ": Position = (" << position.GetX() << ", " << position.GetY() << ", " << position.GetZ() << "), Velocity = (" << velocity.GetX() << ", " << velocity.GetY() << ", " << velocity.GetZ() << ")" << endl;
// // If you take larger steps than 1 / 60th of a second you need to do multiple collision steps in order to keep the simulation stable. Do 1 collision step per 1 / 60th of a second (round up).
// const int cCollisionSteps = 1;
// // Step the world
// physics_system.Update(cDeltaTime, cCollisionSteps, &temp_allocator, &job_system);
// }
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [models] example - box collisions");
// Define the camera to look into our 3d world
Camera camera = { 0 };
camera.position = Vector3{ 0.0f, 10.0f, 10.0f };
camera.target = Vector3{ 0.0f, 0.0f, 0.0f };
camera.up = Vector3{ 0.0f, 1.0f, 0.0f };
camera.fovy = 45.0f;
camera.projection = CAMERA_PERSPECTIVE;
double tickRate = 60.0;
double tickTime = 1.0 / tickRate;
double tickTimer = 0;
float lastFrame = 1.0;
bool jump = false;
std::vector<BodyID> bodies;
bodies.push_back(sphere_id);
const float fixedTimestep = 1.0f / 60.0f;
float accumulator = 0.0f;
Model sphere_model = LoadModelFromMesh(GenMeshSphere(1.0, 15, 15));
while (!WindowShouldClose()) {
float deltaTime = GetFrameTime();
accumulator += deltaTime;
while (accumulator >= fixedTimestep) {
const int cCollisionSteps = 1;
physics_system.Update(fixedTimestep, cCollisionSteps, &temp_allocator, &job_system);
tickTimer -= tickTime;
++step;
accumulator -= fixedTimestep;
}
if (IsKeyReleased(KEY_R)) {
BodyCreationSettings new_sphere_settings(new SphereShape(1.0f), RVec3(0.0_r, 20.0_r, 1.0_r), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
BodyID new_sphere_id = body_interface.CreateAndAddBody(new_sphere_settings, EActivation::Activate);
body_interface.SetLinearVelocity(new_sphere_id, Vec3(0.0f, -5.0f, 0.0f));
bodies.push_back(new_sphere_id);
}
BeginDrawing();
ClearBackground({ 0, 0, 0, 0 });
BeginMode3D(camera);
DrawCube({0, -1, 0}, 10, 2, 10, { 255, 255, 255, 255 });
for (int i = 0; i < bodies.size(); ++i) {
RVec3 position = body_interface.GetCenterOfMassPosition(bodies[i]);
Vec3 velocity = body_interface.GetLinearVelocity(bodies[i]);
JPH::Quat quat = body_interface.GetRotation(bodies[i]);
Quaternion quaternion = {quat.GetX(), quat.GetY(), quat.GetZ(), quat.GetW()};
sphere_model.transform = QuaternionToMatrix(quaternion);
DrawModel(sphere_model, {position.GetX(), position.GetY(), position.GetZ()}, 1.0f, { 255, 0, 0, 255 });
DrawModelWires(sphere_model, {position.GetX(), position.GetY(), position.GetZ()}, 1.0f, {255, 255, 255, 255});
//cout << "id " << i << " " << "Step " << step << ": Position = (" << position.GetX() << ", " << position.GetY() << ", " << position.GetZ() << "), Velocity = (" << velocity.GetX() << ", " << velocity.GetY() << ", " << velocity.GetZ() << ")" << endl;
}
EndMode3D();
EndDrawing();
}
CloseWindow();
// Remove the sphere from the physics system. Note that the sphere itself keeps all of its state and can be re-added at any time.
body_interface.RemoveBody(sphere_id);
// Destroy the sphere. After this the sphere ID is no longer valid.
body_interface.DestroyBody(sphere_id);
// Remove and destroy the floor
body_interface.RemoveBody(floor->GetID());
body_interface.DestroyBody(floor->GetID());
// Unregisters all types with the factory and cleans up the default material
UnregisterTypes();
// Destroy the factory
delete Factory::sInstance;
Factory::sInstance = nullptr;
return 0;
}