Path: blob/master/modules/jolt_physics/spaces/jolt_space_3d.cpp
20969 views
/**************************************************************************/1/* jolt_space_3d.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "jolt_space_3d.h"3132#include "../joints/jolt_joint_3d.h"33#include "../jolt_physics_server_3d.h"34#include "../jolt_project_settings.h"35#include "../misc/jolt_stream_wrappers.h"36#include "../objects/jolt_area_3d.h"37#include "../objects/jolt_body_3d.h"38#include "../shapes/jolt_custom_shape_type.h"39#include "../shapes/jolt_shape_3d.h"40#include "jolt_body_activation_listener_3d.h"41#include "jolt_contact_listener_3d.h"42#include "jolt_layers.h"43#include "jolt_physics_direct_space_state_3d.h"44#include "jolt_temp_allocator.h"4546#include "core/io/file_access.h"47#include "core/os/time.h"48#include "core/string/print_string.h"49#include "core/variant/variant_utility.h"5051#include "Jolt/Physics/Collision/CollideShapeVsShapePerLeaf.h"52#include "Jolt/Physics/Collision/CollisionCollectorImpl.h"53#include "Jolt/Physics/PhysicsScene.h"5455namespace {5657constexpr double SPACE_DEFAULT_CONTACT_RECYCLE_RADIUS = 0.01;58constexpr double SPACE_DEFAULT_CONTACT_MAX_SEPARATION = 0.05;59constexpr double SPACE_DEFAULT_CONTACT_MAX_ALLOWED_PENETRATION = 0.01;60constexpr double SPACE_DEFAULT_CONTACT_DEFAULT_BIAS = 0.8;61constexpr double SPACE_DEFAULT_SLEEP_THRESHOLD_LINEAR = 0.1;62constexpr double SPACE_DEFAULT_SLEEP_THRESHOLD_ANGULAR = 8.0 * Math::PI / 180;63constexpr double SPACE_DEFAULT_SOLVER_ITERATIONS = 8;6465} // namespace6667void JoltSpace3D::_pre_step(float p_step) {68flush_pending_objects();6970while (needs_optimization_list.first()) {71JoltShapedObject3D *object = needs_optimization_list.first()->self();72needs_optimization_list.remove(needs_optimization_list.first());73object->commit_shapes(true);74}7576contact_listener->pre_step();7778const JPH::BodyLockInterface &lock_iface = get_lock_iface();79const JPH::BodyID *active_rigid_bodies = physics_system->GetActiveBodiesUnsafe(JPH::EBodyType::RigidBody);80const JPH::uint32 active_rigid_body_count = physics_system->GetNumActiveBodies(JPH::EBodyType::RigidBody);8182for (JPH::uint32 i = 0; i < active_rigid_body_count; i++) {83JPH::Body *jolt_body = lock_iface.TryGetBody(active_rigid_bodies[i]);84JoltObject3D *object = reinterpret_cast<JoltObject3D *>(jolt_body->GetUserData());85object->pre_step(p_step, *jolt_body);86}87}8889void JoltSpace3D::_post_step(float p_step) {90contact_listener->post_step();9192while (shapes_changed_list.first()) {93JoltShapedObject3D *object = shapes_changed_list.first()->self();94shapes_changed_list.remove(shapes_changed_list.first());95object->clear_previous_shape();96}97}9899JoltSpace3D::JoltSpace3D(JPH::JobSystem *p_job_system) :100job_system(p_job_system),101temp_allocator(new JoltTempAllocator()),102layers(new JoltLayers()),103contact_listener(new JoltContactListener3D(this)),104body_activation_listener(new JoltBodyActivationListener3D()),105physics_system(new JPH::PhysicsSystem()) {106physics_system->Init((JPH::uint)JoltProjectSettings::max_bodies, 0, (JPH::uint)JoltProjectSettings::max_body_pairs, (JPH::uint)JoltProjectSettings::max_contact_constraints, *layers, *layers, *layers);107108JPH::PhysicsSettings settings;109settings.mBaumgarte = JoltProjectSettings::baumgarte_stabilization_factor;110settings.mSpeculativeContactDistance = JoltProjectSettings::speculative_contact_distance;111settings.mPenetrationSlop = JoltProjectSettings::penetration_slop;112settings.mLinearCastThreshold = JoltProjectSettings::ccd_movement_threshold;113settings.mLinearCastMaxPenetration = JoltProjectSettings::ccd_max_penetration;114settings.mBodyPairCacheMaxDeltaPositionSq = JoltProjectSettings::body_pair_cache_distance_sq;115settings.mBodyPairCacheCosMaxDeltaRotationDiv2 = JoltProjectSettings::body_pair_cache_angle_cos_div2;116settings.mNumVelocitySteps = (JPH::uint)JoltProjectSettings::simulation_velocity_steps;117settings.mNumPositionSteps = (JPH::uint)JoltProjectSettings::simulation_position_steps;118settings.mMinVelocityForRestitution = JoltProjectSettings::bounce_velocity_threshold;119settings.mTimeBeforeSleep = JoltProjectSettings::sleep_time_threshold;120settings.mPointVelocitySleepThreshold = JoltProjectSettings::sleep_velocity_threshold;121settings.mUseBodyPairContactCache = JoltProjectSettings::body_pair_contact_cache_enabled;122settings.mAllowSleeping = JoltProjectSettings::sleep_allowed;123124physics_system->SetPhysicsSettings(settings);125physics_system->SetGravity(JPH::Vec3::sZero());126physics_system->SetContactListener(contact_listener);127physics_system->SetSoftBodyContactListener(contact_listener);128129physics_system->SetSimCollideBodyVsBody([](const JPH::Body &p_body1, const JPH::Body &p_body2, JPH::Mat44Arg p_transform_com1, JPH::Mat44Arg p_transform_com2, JPH::CollideShapeSettings &p_collide_shape_settings, JPH::CollideShapeCollector &p_collector, const JPH::ShapeFilter &p_shape_filter) {130if (p_body1.IsSensor() || p_body2.IsSensor()) {131JPH::CollideShapeSettings new_collide_shape_settings = p_collide_shape_settings;132// Since we're breaking the sensor down into leaf shapes we'll end up stripping away our `JoltCustomDoubleSidedShape` decorator shape and thus any back-face collision, so we simply force-enable it like this rather than going through the trouble of reapplying the decorator.133new_collide_shape_settings.mBackFaceMode = JPH::EBackFaceMode::CollideWithBackFaces;134JPH::SubShapeIDCreator part1, part2;135JPH::CollideShapeVsShapePerLeaf<JPH::AnyHitCollisionCollector<JPH::CollideShapeCollector>>(p_body1.GetShape(), p_body2.GetShape(), JPH::Vec3::sOne(), JPH::Vec3::sOne(), p_transform_com1, p_transform_com2, part1, part2, new_collide_shape_settings, p_collector, p_shape_filter);136} else {137JPH::PhysicsSystem::sDefaultSimCollideBodyVsBody(p_body1, p_body2, p_transform_com1, p_transform_com2, p_collide_shape_settings, p_collector, p_shape_filter);138}139});140141physics_system->SetCombineFriction([](const JPH::Body &p_body1, const JPH::SubShapeID &p_sub_shape_id1, const JPH::Body &p_body2, const JPH::SubShapeID &p_sub_shape_id2) {142return Math::abs(MIN(p_body1.GetFriction(), p_body2.GetFriction()));143});144145physics_system->SetCombineRestitution([](const JPH::Body &p_body1, const JPH::SubShapeID &p_sub_shape_id1, const JPH::Body &p_body2, const JPH::SubShapeID &p_sub_shape_id2) {146return CLAMP(p_body1.GetRestitution() + p_body2.GetRestitution(), 0.0f, 1.0f);147});148}149150JoltSpace3D::~JoltSpace3D() {151if (direct_state != nullptr) {152memdelete(direct_state);153direct_state = nullptr;154}155156if (physics_system != nullptr) {157delete physics_system;158physics_system = nullptr;159}160161if (body_activation_listener != nullptr) {162delete body_activation_listener;163body_activation_listener = nullptr;164}165166if (contact_listener != nullptr) {167delete contact_listener;168contact_listener = nullptr;169}170171if (layers != nullptr) {172delete layers;173layers = nullptr;174}175176if (temp_allocator != nullptr) {177delete temp_allocator;178temp_allocator = nullptr;179}180}181182void JoltSpace3D::step(float p_step) {183stepping = true;184last_step = p_step;185186_pre_step(p_step);187188physics_system->SetBodyActivationListener(body_activation_listener);189190const JPH::EPhysicsUpdateError update_error = physics_system->Update(p_step, 1, temp_allocator, job_system);191192if ((update_error & JPH::EPhysicsUpdateError::ManifoldCacheFull) != JPH::EPhysicsUpdateError::None) {193WARN_PRINT_ONCE(vformat("Jolt Physics manifold cache exceeded capacity and contacts were ignored. "194"Consider increasing maximum number of contact constraints in project settings. "195"Maximum number of contact constraints is currently set to %d.",196JoltProjectSettings::max_contact_constraints));197}198199if ((update_error & JPH::EPhysicsUpdateError::BodyPairCacheFull) != JPH::EPhysicsUpdateError::None) {200WARN_PRINT_ONCE(vformat("Jolt Physics body pair cache exceeded capacity and contacts were ignored. "201"Consider increasing maximum number of body pairs in project settings. "202"Maximum number of body pairs is currently set to %d.",203JoltProjectSettings::max_body_pairs));204}205206if ((update_error & JPH::EPhysicsUpdateError::ContactConstraintsFull) != JPH::EPhysicsUpdateError::None) {207WARN_PRINT_ONCE(vformat("Jolt Physics contact constraint buffer exceeded capacity and contacts were ignored. "208"Consider increasing maximum number of contact constraints in project settings. "209"Maximum number of contact constraints is currently set to %d.",210JoltProjectSettings::max_contact_constraints));211}212213// We only want a listener during the step, as it will otherwise be called when pending bodies are flushed, which causes issues (e.g. GH-115322).214physics_system->SetBodyActivationListener(nullptr);215216_post_step(p_step);217218stepping = false;219}220221void JoltSpace3D::call_queries() {222while (body_call_queries_list.first()) {223JoltBody3D *body = body_call_queries_list.first()->self();224body_call_queries_list.remove(body_call_queries_list.first());225body->call_queries();226}227228while (area_call_queries_list.first()) {229JoltArea3D *body = area_call_queries_list.first()->self();230area_call_queries_list.remove(area_call_queries_list.first());231body->call_queries();232}233}234235double JoltSpace3D::get_param(PhysicsServer3D::SpaceParameter p_param) const {236switch (p_param) {237case PhysicsServer3D::SPACE_PARAM_CONTACT_RECYCLE_RADIUS: {238return SPACE_DEFAULT_CONTACT_RECYCLE_RADIUS;239}240case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_SEPARATION: {241return SPACE_DEFAULT_CONTACT_MAX_SEPARATION;242}243case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_ALLOWED_PENETRATION: {244return SPACE_DEFAULT_CONTACT_MAX_ALLOWED_PENETRATION;245}246case PhysicsServer3D::SPACE_PARAM_CONTACT_DEFAULT_BIAS: {247return SPACE_DEFAULT_CONTACT_DEFAULT_BIAS;248}249case PhysicsServer3D::SPACE_PARAM_BODY_LINEAR_VELOCITY_SLEEP_THRESHOLD: {250return SPACE_DEFAULT_SLEEP_THRESHOLD_LINEAR;251}252case PhysicsServer3D::SPACE_PARAM_BODY_ANGULAR_VELOCITY_SLEEP_THRESHOLD: {253return SPACE_DEFAULT_SLEEP_THRESHOLD_ANGULAR;254}255case PhysicsServer3D::SPACE_PARAM_BODY_TIME_TO_SLEEP: {256return JoltProjectSettings::sleep_time_threshold;257}258case PhysicsServer3D::SPACE_PARAM_SOLVER_ITERATIONS: {259return SPACE_DEFAULT_SOLVER_ITERATIONS;260}261default: {262ERR_FAIL_V_MSG(0.0, vformat("Unhandled space parameter: '%d'. This should not happen. Please report this.", p_param));263}264}265}266267void JoltSpace3D::set_param(PhysicsServer3D::SpaceParameter p_param, double p_value) {268switch (p_param) {269case PhysicsServer3D::SPACE_PARAM_CONTACT_RECYCLE_RADIUS: {270WARN_PRINT("Space-specific contact recycle radius is not supported when using Jolt Physics. Any such value will be ignored.");271} break;272case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_SEPARATION: {273WARN_PRINT("Space-specific contact max separation is not supported when using Jolt Physics. Any such value will be ignored.");274} break;275case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_ALLOWED_PENETRATION: {276WARN_PRINT("Space-specific contact max allowed penetration is not supported when using Jolt Physics. Any such value will be ignored.");277} break;278case PhysicsServer3D::SPACE_PARAM_CONTACT_DEFAULT_BIAS: {279WARN_PRINT("Space-specific contact default bias is not supported when using Jolt Physics. Any such value will be ignored.");280} break;281case PhysicsServer3D::SPACE_PARAM_BODY_LINEAR_VELOCITY_SLEEP_THRESHOLD: {282WARN_PRINT("Space-specific linear velocity sleep threshold is not supported when using Jolt Physics. Any such value will be ignored.");283} break;284case PhysicsServer3D::SPACE_PARAM_BODY_ANGULAR_VELOCITY_SLEEP_THRESHOLD: {285WARN_PRINT("Space-specific angular velocity sleep threshold is not supported when using Jolt Physics. Any such value will be ignored.");286} break;287case PhysicsServer3D::SPACE_PARAM_BODY_TIME_TO_SLEEP: {288WARN_PRINT("Space-specific body sleep time is not supported when using Jolt Physics. Any such value will be ignored.");289} break;290case PhysicsServer3D::SPACE_PARAM_SOLVER_ITERATIONS: {291WARN_PRINT("Space-specific solver iterations is not supported when using Jolt Physics. Any such value will be ignored.");292} break;293default: {294ERR_FAIL_MSG(vformat("Unhandled space parameter: '%d'. This should not happen. Please report this.", p_param));295} break;296}297}298299JPH::BodyInterface &JoltSpace3D::get_body_iface() {300return physics_system->GetBodyInterfaceNoLock();301}302303const JPH::BodyInterface &JoltSpace3D::get_body_iface() const {304return physics_system->GetBodyInterfaceNoLock();305}306307const JPH::BodyLockInterface &JoltSpace3D::get_lock_iface() const {308return physics_system->GetBodyLockInterfaceNoLock();309}310311const JPH::BroadPhaseQuery &JoltSpace3D::get_broad_phase_query() const {312return physics_system->GetBroadPhaseQuery();313}314315const JPH::NarrowPhaseQuery &JoltSpace3D::get_narrow_phase_query() const {316return physics_system->GetNarrowPhaseQueryNoLock();317}318319JPH::ObjectLayer JoltSpace3D::map_to_object_layer(JPH::BroadPhaseLayer p_broad_phase_layer, uint32_t p_collision_layer, uint32_t p_collision_mask) {320return layers->to_object_layer(p_broad_phase_layer, p_collision_layer, p_collision_mask);321}322323void JoltSpace3D::map_from_object_layer(JPH::ObjectLayer p_object_layer, JPH::BroadPhaseLayer &r_broad_phase_layer, uint32_t &r_collision_layer, uint32_t &r_collision_mask) const {324layers->from_object_layer(p_object_layer, r_broad_phase_layer, r_collision_layer, r_collision_mask);325}326327JPH::Body *JoltSpace3D::try_get_jolt_body(const JPH::BodyID &p_body_id) const {328return get_lock_iface().TryGetBody(p_body_id);329}330331JoltObject3D *JoltSpace3D::try_get_object(const JPH::BodyID &p_body_id) const {332const JPH::Body *jolt_body = try_get_jolt_body(p_body_id);333if (unlikely(jolt_body == nullptr)) {334return nullptr;335}336337return reinterpret_cast<JoltObject3D *>(jolt_body->GetUserData());338}339340JoltShapedObject3D *JoltSpace3D::try_get_shaped(const JPH::BodyID &p_body_id) const {341JoltObject3D *object = try_get_object(p_body_id);342if (unlikely(object == nullptr)) {343return nullptr;344}345346return object->as_shaped();347}348349JoltBody3D *JoltSpace3D::try_get_body(const JPH::BodyID &p_body_id) const {350JoltObject3D *object = try_get_object(p_body_id);351if (unlikely(object == nullptr)) {352return nullptr;353}354355return object->as_body();356}357358JoltArea3D *JoltSpace3D::try_get_area(const JPH::BodyID &p_body_id) const {359JoltObject3D *object = try_get_object(p_body_id);360if (unlikely(object == nullptr)) {361return nullptr;362}363364return object->as_area();365}366367JoltSoftBody3D *JoltSpace3D::try_get_soft_body(const JPH::BodyID &p_body_id) const {368JoltObject3D *object = try_get_object(p_body_id);369if (unlikely(object == nullptr)) {370return nullptr;371}372373return object->as_soft_body();374}375376JoltPhysicsDirectSpaceState3D *JoltSpace3D::get_direct_state() {377if (direct_state == nullptr) {378direct_state = memnew(JoltPhysicsDirectSpaceState3D(this));379}380381return direct_state;382}383384void JoltSpace3D::set_default_area(JoltArea3D *p_area) {385if (default_area == p_area) {386return;387}388389if (default_area != nullptr) {390default_area->set_default_area(false);391}392393default_area = p_area;394395if (default_area != nullptr) {396default_area->set_default_area(true);397}398}399400JPH::Body *JoltSpace3D::add_object(const JoltObject3D &p_object, const JPH::BodyCreationSettings &p_settings, bool p_sleeping) {401JPH::BodyInterface &body_iface = get_body_iface();402JPH::Body *jolt_body = body_iface.CreateBody(p_settings);403if (unlikely(jolt_body == nullptr)) {404ERR_PRINT_ONCE(vformat("Failed to create underlying Jolt Physics body for '%s'. "405"Consider increasing maximum number of bodies in project settings. "406"Maximum number of bodies is currently set to %d.",407p_object.to_string(), JoltProjectSettings::max_bodies));408409return nullptr;410}411412if (p_sleeping) {413pending_objects_sleeping.push_back(jolt_body->GetID());414} else {415pending_objects_awake.push_back(jolt_body->GetID());416}417418return jolt_body;419}420421JPH::Body *JoltSpace3D::add_object(const JoltObject3D &p_object, const JPH::SoftBodyCreationSettings &p_settings, bool p_sleeping) {422JPH::BodyInterface &body_iface = get_body_iface();423JPH::Body *jolt_body = body_iface.CreateSoftBody(p_settings);424if (unlikely(jolt_body == nullptr)) {425ERR_PRINT_ONCE(vformat("Failed to create underlying Jolt Physics body for '%s'. "426"Consider increasing maximum number of bodies in project settings. "427"Maximum number of bodies is currently set to %d.",428p_object.to_string(), JoltProjectSettings::max_bodies));429430return nullptr;431}432433if (p_sleeping) {434pending_objects_sleeping.push_back(jolt_body->GetID());435} else {436pending_objects_awake.push_back(jolt_body->GetID());437}438439return jolt_body;440}441442void JoltSpace3D::remove_object(const JPH::BodyID &p_jolt_id) {443JPH::BodyInterface &body_iface = get_body_iface();444445if (!pending_objects_sleeping.erase_unordered(p_jolt_id) && !pending_objects_awake.erase_unordered(p_jolt_id)) {446body_iface.RemoveBody(p_jolt_id);447}448449body_iface.DestroyBody(p_jolt_id);450451// If we're never going to step this space, like in the editor viewport, we need to manually clean up Jolt's broad phase instead, otherwise performance can degrade when doing things like switching scenes.452// We'll never actually have zero bodies in any space though, since we always have the default area, so we check if there's one or fewer left instead.453if (!JoltPhysicsServer3D::get_singleton()->is_active() && physics_system->GetNumBodies() <= 1) {454physics_system->OptimizeBroadPhase();455}456}457458void JoltSpace3D::flush_pending_objects() {459if (pending_objects_sleeping.is_empty() && pending_objects_awake.is_empty()) {460return;461}462463// We only care about locking within this method, because it's called when performing queries, which aren't covered by `PhysicsServer3DWrapMT`.464MutexLock pending_objects_lock(pending_objects_mutex);465466JPH::BodyInterface &body_iface = get_body_iface();467468if (!pending_objects_sleeping.is_empty()) {469JPH::BodyInterface::AddState add_state = body_iface.AddBodiesPrepare(pending_objects_sleeping.ptr(), pending_objects_sleeping.size());470body_iface.AddBodiesFinalize(pending_objects_sleeping.ptr(), pending_objects_sleeping.size(), add_state, JPH::EActivation::DontActivate);471pending_objects_sleeping.reset();472}473474if (!pending_objects_awake.is_empty()) {475JPH::BodyInterface::AddState add_state = body_iface.AddBodiesPrepare(pending_objects_awake.ptr(), pending_objects_awake.size());476body_iface.AddBodiesFinalize(pending_objects_awake.ptr(), pending_objects_awake.size(), add_state, JPH::EActivation::Activate);477pending_objects_awake.reset();478}479}480481void JoltSpace3D::set_is_object_sleeping(const JPH::BodyID &p_jolt_id, bool p_enable) {482if (p_enable) {483if (pending_objects_awake.erase_unordered(p_jolt_id)) {484pending_objects_sleeping.push_back(p_jolt_id);485} else if (pending_objects_sleeping.has(p_jolt_id)) {486// Do nothing.487} else {488get_body_iface().DeactivateBody(p_jolt_id);489}490} else {491if (pending_objects_sleeping.erase_unordered(p_jolt_id)) {492pending_objects_awake.push_back(p_jolt_id);493} else if (pending_objects_awake.has(p_jolt_id)) {494// Do nothing.495} else {496get_body_iface().ActivateBody(p_jolt_id);497}498}499}500501void JoltSpace3D::enqueue_call_queries(SelfList<JoltBody3D> *p_body) {502// This method will be called from the body activation listener on multiple threads during the simulation step.503MutexLock body_call_queries_lock(body_call_queries_mutex);504505if (!p_body->in_list()) {506body_call_queries_list.add(p_body);507}508}509510void JoltSpace3D::enqueue_call_queries(SelfList<JoltArea3D> *p_area) {511if (!p_area->in_list()) {512area_call_queries_list.add(p_area);513}514}515516void JoltSpace3D::dequeue_call_queries(SelfList<JoltBody3D> *p_body) {517if (p_body->in_list()) {518body_call_queries_list.remove(p_body);519}520}521522void JoltSpace3D::dequeue_call_queries(SelfList<JoltArea3D> *p_area) {523if (p_area->in_list()) {524area_call_queries_list.remove(p_area);525}526}527528void JoltSpace3D::enqueue_shapes_changed(SelfList<JoltShapedObject3D> *p_object) {529if (!p_object->in_list()) {530shapes_changed_list.add(p_object);531}532}533534void JoltSpace3D::dequeue_shapes_changed(SelfList<JoltShapedObject3D> *p_object) {535if (p_object->in_list()) {536shapes_changed_list.remove(p_object);537}538}539540void JoltSpace3D::enqueue_needs_optimization(SelfList<JoltShapedObject3D> *p_object) {541if (!p_object->in_list()) {542needs_optimization_list.add(p_object);543}544}545546void JoltSpace3D::dequeue_needs_optimization(SelfList<JoltShapedObject3D> *p_object) {547if (p_object->in_list()) {548needs_optimization_list.remove(p_object);549}550}551552void JoltSpace3D::add_joint(JPH::Constraint *p_jolt_ref) {553physics_system->AddConstraint(p_jolt_ref);554}555556void JoltSpace3D::add_joint(JoltJoint3D *p_joint) {557add_joint(p_joint->get_jolt_ref());558}559560void JoltSpace3D::remove_joint(JPH::Constraint *p_jolt_ref) {561physics_system->RemoveConstraint(p_jolt_ref);562}563564void JoltSpace3D::remove_joint(JoltJoint3D *p_joint) {565remove_joint(p_joint->get_jolt_ref());566}567568#ifdef DEBUG_ENABLED569570void JoltSpace3D::dump_debug_snapshot(const String &p_dir) {571const Dictionary datetime = Time::get_singleton()->get_datetime_dict_from_system();572const String datetime_str = vformat("%04d-%02d-%02d_%02d-%02d-%02d", datetime["year"], datetime["month"], datetime["day"], datetime["hour"], datetime["minute"], datetime["second"]);573const String path = p_dir + vformat("/jolt_snapshot_%s_%d.bin", datetime_str, rid.get_id());574575Ref<FileAccess> file_access = FileAccess::open(path, FileAccess::ModeFlags::WRITE);576ERR_FAIL_COND_MSG(file_access.is_null(), vformat("Failed to open '%s' for writing when saving snapshot of physics space with RID '%d'.", path, rid.get_id()));577578JPH::PhysicsScene physics_scene;579physics_scene.FromPhysicsSystem(physics_system);580581for (JPH::BodyCreationSettings &settings : physics_scene.GetBodies()) {582const JoltObject3D *object = reinterpret_cast<const JoltObject3D *>(settings.mUserData);583584if (const JoltBody3D *body = object->as_body()) {585// Since we do our own integration of gravity and damping, while leaving Jolt's own values at zero, we need to transfer over the correct values.586settings.mGravityFactor = body->get_gravity_scale();587settings.mLinearDamping = body->get_total_linear_damp();588settings.mAngularDamping = body->get_total_angular_damp();589}590591settings.SetShape(JoltShape3D::without_custom_shapes(settings.GetShape()));592}593594JoltStreamOutputWrapper output_stream(file_access);595physics_scene.SaveBinaryState(output_stream, true, false);596597ERR_FAIL_COND_MSG(file_access->get_error() != OK, vformat("Writing snapshot of physics space with RID '%d' to '%s' failed with error '%s'.", rid.get_id(), path, VariantUtilityFunctions::error_string(file_access->get_error())));598599print_line(vformat("Snapshot of physics space with RID '%d' saved to '%s'.", rid.get_id(), path));600}601602const PackedVector3Array &JoltSpace3D::get_debug_contacts() const {603return contact_listener->get_debug_contacts();604}605606int JoltSpace3D::get_debug_contact_count() const {607return contact_listener->get_debug_contact_count();608}609610int JoltSpace3D::get_max_debug_contacts() const {611return contact_listener->get_max_debug_contacts();612}613614void JoltSpace3D::set_max_debug_contacts(int p_count) {615contact_listener->set_max_debug_contacts(p_count);616}617618#endif619620621