57class TOAST_API Simulator {
58 friend class Collider;
59 friend class Rigidbody;
60 friend class DynamicRigidbody;
76 Simulator(
const Simulator&) =
delete;
77 auto operator=(
const Simulator&) -> Simulator& =
delete;
78 Simulator(Simulator&&) =
delete;
79 auto operator=(Simulator&&) -> Simulator& =
delete;
82 void integrate(
float dt);
86 void destroyBody(
BodyID body);
90 glm::vec3 origin, glm::vec3 direction,
float max_distance = -1.0f,
BodyID ignored = {}, uint32_t layers = k_all_layers
91 ) -> std::optional<RayHit>;
92 static auto raymulticast(
93 glm::vec3 origin, glm::vec3 direction,
float max_distance = -1.0f,
int max_hits = -1,
BodyID ignored = {},
94 uint32_t layers = k_all_layers
95 ) -> std::vector<RayHit>;
96 static auto linecast(glm::vec3 from, glm::vec3 to,
int max_hits = -1, BodyID ignored = {}, uint32_t layers = k_all_layers)
97 -> std::vector<RayHit>;
100 static auto bodyFor(
const toast::Node& node) -> BodyID;
103 auto valid(BodyID body)
const -> bool;
105 auto state(BodyID body)
const -> std::optional<BodyState>;
107 auto setTransform(BodyID body,
const glm::vec3& position,
const glm::quat& rotation) -> bool;
108 auto setLinearVelocity(BodyID body,
const glm::vec3& velocity) -> bool;
110 void recordDamage(DamageCommand&& command);
111 void applyDamageCommands();
112 void applyDamageCommand(
const DamageCommand& c);
113 void damageArea(
const AreaDamage& damage);
114 auto explode(
const ExplosionDamage& explosion) -> uint32_t;
117 const glm::vec3& origin,
const glm::vec3& direction,
float max_distance,
float energy_density,
float fracture_radius,
118 const std::optional<FragmentPush>& push = std::nullopt, Hardness max_hardness = k_hardest
122 const CapsuleShape& capsule,
const glm::vec3& position,
const glm::quat& rotation,
const glm::vec3& sweep,
123 const CapsuleSmash& smash
127 const glm::vec3& origin,
const glm::vec3& direction,
float max_distance,
float energy_density,
float radius,
float depth,
128 Hardness max_hardness = k_hardest
132 auto runConnectivityAnalysis() -> std::vector<ConnectivityResult>;
135 auto debugDirtyBricks() const -> std::span<const DebugDirtyBrick> {
136 return m_debug_dirty_bricks;
141 std::array<size_t, static_cast<size_t>(NarrowPhasePairType::count)> narrow_pair_candidates = {};
142 std::array<double, static_cast<size_t>(NarrowPhasePairType::count)> narrow_pair_time_ms = {};
143 size_t narrow_jobs = 0;
144 size_t narrow_candidates = 0;
147 size_t voxel_voxel_split_pairs = 0;
150 size_t voxel_voxel_nondegenerate_pairs = 0;
153 size_t voxel_voxel_max_estimated_voxels = 0;
156 size_t narrow_collisions = 0;
157 size_t sleeping_pairs_skipped = 0;
158 size_t rejected_manifolds = 0;
159 size_t contact_points = 0;
160 size_t bodies_woken = 0;
161 size_t bodies_slept = 0;
164 size_t woken_by_racing = 0;
165 size_t woken_by_contact_end = 0;
166 size_t woken_by_support_loss = 0;
167 size_t contact_begins = 0;
168 size_t contact_persists = 0;
169 size_t contact_ends = 0;
170 size_t reused_cached_contacts = 0;
171 size_t cold_cached_contacts = 0;
172 size_t constraints = 0;
173 size_t rejected_constraints = 0;
174 size_t warm_started_constraints = 0;
175 size_t island_jobs = 0;
176 size_t invalid_constraints = 0;
177 size_t position_corrections = 0;
179 double tick_ms = 0.0;
180 double damage_apply_ms = 0.0;
181 double connectivity_ms = 0.0;
182 double character_step_ms = 0.0;
183 double narrow_phase_ms = 0.0;
184 double solve_ms = 0.0;
188 size_t character_overlap_calls = 0;
192 double wake_groups_ms = 0.0;
193 double prepare_constraints_ms = 0.0;
194 double build_islands_ms = 0.0;
195 double island_solve_ms = 0.0;
196 double sleep_update_ms = 0.0;
198 size_t body_count = 0;
199 size_t awake_body_count = 0;
200 size_t voxel_shape_count = 0;
201 size_t manifold_count = 0;
203 size_t damage_commands = 0;
204 size_t dirty_bricks = 0;
205 size_t surface_bricks_repaired = 0;
207 size_t connectivity_jobs = 0;
208 size_t connectivity_jobs_dispatched = 0;
209 size_t connectivity_jobs_stale = 0;
210 size_t connectivity_shapes_waiting = 0;
212 size_t fragments_spawned = 0;
213 size_t fragment_spawn_failures = 0;
214 size_t fragments_pending = 0;
215 size_t fragments_active = 0;
216 size_t fragments_sleep_locked = 0;
217 size_t fragments_despawned = 0;
218 size_t fragments_evicted = 0;
219 size_t static_splits_spawned = 0;
221 uint32_t brick_pool_allocated = 0;
222 uint32_t brick_pool_capacity = 0;
244 static auto interpolationAlpha() -> float;
260 static auto shapeWorldBounds(
ShapeID shape) -> std::optional<AABB>;
266 static void callTick();
268 static void bindToThisThread() noexcept;
271 static auto onSimulatorThread() noexcept ->
bool;
284 static
void requestWake(
BodyID id);
285 static
void registerRigidbody(Rigidbody& node);
286 static
void unregisterRigidbody(Rigidbody& node);
287 static
void registerVoxelNode(toast::VoxelNode& node);
288 static
void unregisterVoxelNode(toast::VoxelNode& node);
289 static
void registerTrigger(
Trigger& node);
290 static
void unregisterTrigger(
Trigger& node);
294 BodyID ignored, const
CapsuleShape& capsule, const glm::vec3& position, const glm::quat& rotation,
float min_penetration,
300 const
SphereShape& sphere, const glm::vec3& position,
float min_penetration, std::vector<
QueryContact>& contacts
309 BodyID ignored, const
CapsuleShape& capsule, const glm::quat& rotation, const glm::vec3& from, const glm::vec3& to,
310 float skin,
bool refine = true
314 void moveKinematicBody(
BodyID id, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& velocity);
316 void pushBody(
BodyID id, const glm::vec3& point, const glm::vec3& direction,
float speed,
float max_impulse);
319 auto takeDebrisSpawns() -> std::vector<
DebrisSpawn>;
323 static auto
current() -> Simulator*;
328 static
void setWind(const glm::vec3& wind);
330 static auto wind() -> glm::vec3;
331 static
void resetWind();
334 enum class SimulationPhase : uint8_t {
342 PhaseScope(
Simulator& simulator, SimulationPhase expected, SimulationPhase next);
344 PhaseScope(
const PhaseScope&) =
delete;
345 auto operator=(
const PhaseScope&) -> PhaseScope& =
delete;
349 SimulationPhase m_previous;
350 SimulationPhase m_active;
353 struct ColliderBinding {
360 toast::Box<Rigidbody> node;
361 std::vector<ColliderBinding> colliders;
364 struct VoxelNodeBinding {
367 toast::Box<toast::VoxelNode> node;
368 uint32_t source_revision = 0;
369 uint64_t source_model = 0;
370 uint64_t source_palette = 0;
373 struct SimulationIsland {
375 std::vector<BodyID> dynamic_bodies;
376 std::vector<Constraint> constraints;
377 std::vector<size_t> manifold_indices;
381 std::vector<size_t> batch_offsets;
384 struct FragmentRecord {
387 uint64_t sequence = 0;
388 float rest_seconds = 0.0f;
389 glm::vec3 rest_center = {};
390 glm::quat rest_rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
393 struct PendingFragments {
395 std::vector<DetachedComponent> components;
397 std::optional<FragmentPush> push;
401 glm::vec3 origin = {};
402 glm::vec3 direction = {};
403 float max_distance = std::numeric_limits<float>::infinity();
405 uint32_t layers = k_all_layers;
406 bool voxels_only =
false;
407 bool hit_passable =
false;
413 float distance = 0.0f;
414 glm::vec3 normal = {};
415 uint8_t palette_index = 0;
418 template<
typename OnContact>
419 auto castRay(
const RayQuery& query, OnContact&& on_contact)
const -> uint32_t;
423 auto windBlocked(
const glm::vec3& position,
const glm::vec3& toward,
float distance, BodyID ignored)
const -> bool;
426 static auto makeRayQuery(glm::vec3 origin, glm::vec3 direction,
float max_distance, BodyID ignored, uint32_t layers)
427 -> std::optional<RayQuery>;
430 static auto makeRayHit(
const RayQuery& query,
const RayContact& contact, toast::Box<toast::Node> node) -> RayHit;
434 float distance = 0.0f;
438 static auto colliderFor(BodyID body, ShapeID shape) -> toast::Box<toast::Node>;
440 auto mainThreadMutationAllowed() const ->
bool;
443 static
void request(std::move_only_function<
void()> work);
445 void dropRigidbodyBinding(const toast::Box<Rigidbody>& node);
446 void dropVoxelBinding(const toast::Box<toast::VoxelNode>& node);
449 auto createSphere(BodyID owner, const SphereShape& sphere, PhysicsMaterial material) -> ShapeID;
451 auto createBox(BodyID owner, const BoxShape& box, PhysicsMaterial material) -> ShapeID;
453 auto createCapsule(BodyID owner, const CapsuleShape& capsule, PhysicsMaterial material) -> ShapeID;
455 auto createVoxelShape(
456 BodyID owner, const VoxelShape& shape, voxel::Volume& volume, const voxel::Palette& palette,
457 const voxel::MaterialLibrary& materials
460 auto createVoxelShape(
461 BodyID owner, const VoxelShape& shape, voxel::Volume&& volume, const voxel::Palette& palette,
462 const voxel::MaterialLibrary& materials
465 auto createVoxelShape(BodyID owner, toast::VoxelNode& node) -> ShapeID;
467 void destroyShape(ShapeID shape);
469 auto valid(ShapeID shape) const ->
bool;
471 auto tryGetShape(ShapeID shape) -> Shape*;
473 auto tryGetShape(ShapeID shape) const -> const Shape*;
475 auto valid(VoxelDataID data) const ->
bool;
479 static auto tryGetVoxelData(VoxelDataID data) -> VoxelShapeData*;
482 static auto tryGetVoxelDataConst(VoxelDataID data) -> const VoxelShapeData*;
485 void destroyVoxelData(VoxelDataID data);
487 void rebuildMassProperties(BodyID
id);
490 auto tryGetBody(BodyID body) -> Body*;
492 auto tryGetBody(BodyID body) const -> const Body*;
494 static
void integrateBody(BodyID
id, Body& body, const glm::vec3& gravity,
float air_density, const glm::vec3& wind,
float dt);
495 static
void setBodyEnabled(BodyID body,
bool enabled);
497 static
void setBodyTransform(BodyID body, const glm::vec3& position, const glm::quat& rotation);
498 static
void setBodyLayers(BodyID body, LayerFilter layers);
499 static auto setBodyLinearVelocity(BodyID body, const glm::vec3& velocity) ->
bool;
501 static
void setShapeEnabled(ShapeID shape,
bool enabled);
502 void syncEnabledState();
505 auto queryCandidates(BodyID ignored, const AABB& bounds, std::optional<LayerFilter> layers = std::nullopt) const
506 -> std::vector<ShapeID>;
508 auto layersOf(BodyID body) const -> std::optional<LayerFilter>;
509 void collideCapsuleProbe(
510 const Body& probe_body, const Shape& probe_shape, std::span<const ShapeID> candidates, std::vector<QueryContact>& contacts
512 void collideSphereProbe(
513 const Body& probe_body, const Shape& probe_shape, std::span<const ShapeID> candidates, std::vector<QueryContact>& contacts
516 static
void wakeBody(BodyID
id);
517 static
void sleepBody(BodyID
id);
519 void releaseSleeper(BodyID sleeper, BodyID other, const glm::vec3& normal_to_other);
520 void wakeBodiesTouching(BodyID
id);
521 void wakeBodiesTouching(ShapeID
id);
522 void wakeBodiesInBounds(const AABB& bounds);
523 void convertImpulsesToDamage(std::span<const SimulationIsland> islands);
524 void wakeDisturbedSleepers(
float dt);
525 void unlockDisturbedFragments();
526 void markSupportedBodies();
528 auto isStill(const Body& body,
float scale) const ->
bool;
530 auto sleepScale(
size_t slot) const ->
float;
532 auto sleeperIsHeldUp(BodyID
id) const ->
bool;
533 void updateSleeping(
float dt);
535 auto shouldSolve(const Manifold& manifold) const ->
bool;
537 auto pairNeedsNarrowPhase(CollisionWorldView world, const BroadPhasePair& pair) const ->
bool;
540 auto generateManifoldsAsync(CollisionWorldView world, std::span<const BroadPhasePair> candidates) -> std::vector<Manifold>;
543 auto prepareConstraints(const std::vector<Manifold>& manifolds) -> std::vector<Constraint>;
545 auto prepareConstraint(const Manifold& manifold, const ContactPoint& contact) const -> std::optional<Constraint>;
547 auto buildIslands(std::span<const Manifold> manifolds, const std::vector<Constraint>& constraints)
548 -> std::vector<SimulationIsland>;
549 void updateCache(std::span<const Manifold> manifolds);
551 auto findCachedContact(const BroadPhasePair& pair, uint8_t normal_index, ContactFeatureID feature_a, ContactFeatureID feature_b)
554 auto findCachedContact(
555 const BroadPhasePair& pair, uint8_t normal_index, ContactFeatureID feature_a, ContactFeatureID feature_b
556 ) const -> const CachedContact*;
557 void warmStartConstraints(std::span<Constraint> constraints);
558 void storeConstraintImpulses(std::span<const Constraint> constraints);
560 auto shapeRevision(ShapeID shape) const -> uint32_t;
561 void incrementShapeRevision(ShapeID shape);
562 void solveIslands(std::vector<SimulationIsland>& islands);
564 auto solveConstraintBatch(std::span<Constraint> batch) ->
size_t;
566 auto solveConstraint(Constraint& constraint) ->
bool;
567 void publishTransforms();
569 auto publishTransform(NodeBinding& binding) ->
bool;
571 auto publishVoxelTransform(VoxelNodeBinding& binding) ->
bool;
572 void publishVoxelRenderRecords();
573 void rebuildRenderPoseLookup();
575 static auto velocityAtPoint(const Body& body, const glm::vec3& r) -> glm::vec3;
576 static auto effectiveMassAlong(
577 const Body& body_a, const Body& body_b, const glm::vec3& r_a, const glm::vec3& r_b, const glm::vec3& direction
578 ) -> std::optional<
float>;
579 static auto angularMassAround(const Body& body_a, const Body& body_b, const glm::vec3& axis) ->
float;
580 static
void applyImpulse(Body& body_a, Body& body_b, const glm::vec3& r_a, const glm::vec3& r_b, const glm::vec3& impulse);
581 static
void applyAngularImpulse(Body& body_a, Body& body_b, const glm::vec3& impulse);
582 static auto solveNormal(Constraint& constraint, Body& body_a, Body& body_b) ->
bool;
583 static auto solveFriction(Constraint& constraint, Body& body_a, Body& body_b) ->
bool;
584 static auto solveRolling(Constraint& constraint, Body& body_a, Body& body_b) ->
bool;
586 auto correctPositions(std::span<const
size_t> manifold_indices) ->
size_t;
587 void publishProfile(std::span<const SimulationIsland> islands);
589 void clearFragmentFromSource(
590 ShapeID shape_id, VoxelShapeData& data, voxel::Volume& source, const DetachedComponent& component,
591 const RemovedVoxels* already_removed =
nullptr
595 ShapeID shape_id, std::span<const DebrisVoxel> voxels, const DebrisBlast* blast, const FragmentPush* push, uint64_t seed
598 void emitComponentDebris(ShapeID shape_id, const DetachedComponent& component, const FragmentPush* push);
601 auto spawnFragmentBody(ShapeID source_shape_id, const DetachedComponent& component, const FragmentPush* push =
nullptr) ->
bool;
602 auto smashShape(ShapeID shape_id, const SmashVolume& volume, const CapsuleSmash& smash,
bool chunks) -> uint32_t;
603 auto explodeShape(ShapeID shape_id, const ExplosionDamage& explosion) -> uint32_t;
605 ShapeID shape_id, SmashPieces& picked,
bool chunks,
606 const std::function<std::optional<FragmentPush>(const DetachedComponent&)>& push_for, const AABB& wake_bounds
609 auto marchVoxelRay(const glm::vec3& origin, const glm::vec3& direction,
float max_distance) -> std::optional<VoxelRayHit>;
611 auto spawnStaticSplitBody(ShapeID source_shape_id, const DetachedComponent& component) ->
bool;
612 void despawnSettledFragments(
float dt);
613 void retireVoxelBody(BodyID
id);
614 void destroyFragmentsOf(BodyID origin);
617 auto voxelNodeFor(ShapeID shape) -> toast::VoxelNode*;
619 void reapFragments();
620 void destroyFragmentRecord(BodyID
id);
621 void queuePendingFragments(std::span<const ConnectivityResult> results);
622 void spawnBudgetedFragments();
624 auto reconcileComponent(const voxel::Volume& volume, const DetachedComponent& component) const ->
bool;
625 void enforceFragmentBudget(
float dt);
626 void unlockSleep(BodyID
id);
627 void recheckNeighborsOf(BodyID
id);
628 void rebuildFragmentIndex();
629 void refreshPalette(uint64_t palette_uid);
630 void refreshAllPalettes();
631 auto createVoxelShapeInternal(
632 BodyID owner, const VoxelShape& shape, voxel::Volume* external, std::unique_ptr<voxel::Volume>& owned,
633 const voxel::Palette& palette, const voxel::MaterialLibrary& materials, const voxel::MassMoments* known_moments =
nullptr
636 void checkTriggers();
638 BodyID ignored, LayerFilter layers, const Shape& probe, const glm::vec3& position, const glm::quat& rotation,
639 std::vector<BodyID>& bodies
642 inline static Simulator* instance =
nullptr;
644 std::mutex m_wind_mutex;
645 std::optional<glm::vec3> m_level_wind;
647 mutable std::thread::
id m_owner_thread;
648 std::atomic<SimulationPhase> m_phase = SimulationPhase::idle;
650 std::vector<BodySlot> m_bodies;
651 std::deque<uint32_t> m_free_body_slots;
652 std::vector<NodeBinding> m_node_bindings;
653 std::vector<VoxelNodeBinding> m_voxel_bindings;
656 std::vector<uint8_t> m_body_supported;
657 std::vector<uint8_t> m_sleep_ready;
658 std::vector<uint8_t> m_sleep_moving;
659 std::vector<uint8_t> m_sleep_blocked;
661 std::vector<uint8_t> m_contact_supported;
663 std::vector<glm::vec3> m_position_shift;
666 std::vector<
size_t> m_island_parents;
667 std::vector<
bool> m_island_participates;
668 std::vector<
size_t> m_island_by_root;
669 std::vector<uint32_t> m_island_next_free_batch;
670 std::vector<uint32_t> m_island_constraint_batch;
671 std::vector<
size_t> m_island_batch_cursor;
672 std::vector<Constraint> m_island_reordered_scratch;
675 std::vector<
size_t> m_wave_chunk_invalid_counts;
677 std::vector<ShapeSlot> m_shapes;
678 std::deque<uint32_t> m_free_shape_slots;
680 BroadPhase m_broad_phase;
681 NarrowPhase m_narrow_phase;
682 std::vector<Manifold> m_manifolds;
683 std::vector<CachedManifold> m_cached_manifolds;
684 mutable PhysicsStepProfile m_profile;
685 PhysicsStepProfile m_published_profile;
687 std::vector<VoxelShapeSlot> m_voxel_shapes;
688 std::deque<uint32_t> m_free_voxel_shape_slots;
689 std::vector<VoxelRenderRecord> m_voxel_render_records;
690 std::vector<DebrisSpawn> m_debris_spawns;
692 std::mutex m_damage_mutex;
693 std::vector<DamageCommand> m_damage_commands;
694 std::vector<DebugDirtyBrick> m_debug_dirty_bricks;
696 uint64_t m_next_fragment_sequence = 1;
697 uint64_t m_tick_index = 0;
698 std::vector<FragmentRecord> m_fragments;
699 std::unordered_map<uint32_t,
size_t> m_fragment_index;
700 std::vector<PendingFragments> m_pending_fragments;
701 std::vector<BodyID> m_doomed_fragments;
704 size_t m_connectivity_cursor = 0;
706 float m_interpolation_alpha = 0.0f;
709 std::unordered_map<uint64_t, BodyID> m_render_body_of_node;
711 event::Listener m_listener;
714 std::vector<toast::Box<Trigger>> m_triggers;