multi-screen-projector/engine/modules/jolt_physics/spaces/jolt_space_3d.cpp
2025-03-13 08:40:48 +00:00

521 lines
21 KiB
C++

/**************************************************************************/
/* jolt_space_3d.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "jolt_space_3d.h"
#include "../joints/jolt_joint_3d.h"
#include "../jolt_physics_server_3d.h"
#include "../jolt_project_settings.h"
#include "../misc/jolt_stream_wrappers.h"
#include "../objects/jolt_area_3d.h"
#include "../objects/jolt_body_3d.h"
#include "../shapes/jolt_custom_shape_type.h"
#include "../shapes/jolt_shape_3d.h"
#include "jolt_contact_listener_3d.h"
#include "jolt_layers.h"
#include "jolt_physics_direct_space_state_3d.h"
#include "jolt_temp_allocator.h"
#include "core/io/file_access.h"
#include "core/os/time.h"
#include "core/string/print_string.h"
#include "core/variant/variant_utility.h"
#include "Jolt/Physics/PhysicsScene.h"
namespace {
constexpr double DEFAULT_CONTACT_RECYCLE_RADIUS = 0.01;
constexpr double DEFAULT_CONTACT_MAX_SEPARATION = 0.05;
constexpr double DEFAULT_CONTACT_MAX_ALLOWED_PENETRATION = 0.01;
constexpr double DEFAULT_CONTACT_DEFAULT_BIAS = 0.8;
constexpr double DEFAULT_SLEEP_THRESHOLD_LINEAR = 0.1;
constexpr double DEFAULT_SLEEP_THRESHOLD_ANGULAR = 8.0 * Math_PI / 180;
constexpr double DEFAULT_SOLVER_ITERATIONS = 8;
} // namespace
void JoltSpace3D::_pre_step(float p_step) {
while (needs_optimization_list.first()) {
JoltShapedObject3D *object = needs_optimization_list.first()->self();
needs_optimization_list.remove(needs_optimization_list.first());
object->commit_shapes(true);
}
contact_listener->pre_step();
const JPH::BodyLockInterface &lock_iface = get_lock_iface();
const JPH::BodyID *active_rigid_bodies = physics_system->GetActiveBodiesUnsafe(JPH::EBodyType::RigidBody);
const JPH::uint32 active_rigid_body_count = physics_system->GetNumActiveBodies(JPH::EBodyType::RigidBody);
for (JPH::uint32 i = 0; i < active_rigid_body_count; i++) {
JPH::Body *jolt_body = lock_iface.TryGetBody(active_rigid_bodies[i]);
JoltObject3D *object = reinterpret_cast<JoltObject3D *>(jolt_body->GetUserData());
object->pre_step(p_step, *jolt_body);
}
}
void JoltSpace3D::_post_step(float p_step) {
contact_listener->post_step();
while (shapes_changed_list.first()) {
JoltShapedObject3D *object = shapes_changed_list.first()->self();
shapes_changed_list.remove(shapes_changed_list.first());
object->clear_previous_shape();
}
}
JoltSpace3D::JoltSpace3D(JPH::JobSystem *p_job_system) :
job_system(p_job_system),
temp_allocator(new JoltTempAllocator()),
layers(new JoltLayers()),
contact_listener(new JoltContactListener3D(this)),
physics_system(new JPH::PhysicsSystem()) {
physics_system->Init((JPH::uint)JoltProjectSettings::get_max_bodies(), 0, (JPH::uint)JoltProjectSettings::get_max_pairs(), (JPH::uint)JoltProjectSettings::get_max_contact_constraints(), *layers, *layers, *layers);
JPH::PhysicsSettings settings;
settings.mBaumgarte = JoltProjectSettings::get_baumgarte_stabilization_factor();
settings.mSpeculativeContactDistance = JoltProjectSettings::get_speculative_contact_distance();
settings.mPenetrationSlop = JoltProjectSettings::get_penetration_slop();
settings.mLinearCastThreshold = JoltProjectSettings::get_ccd_movement_threshold();
settings.mLinearCastMaxPenetration = JoltProjectSettings::get_ccd_max_penetration();
settings.mBodyPairCacheMaxDeltaPositionSq = JoltProjectSettings::get_body_pair_cache_distance_sq();
settings.mBodyPairCacheCosMaxDeltaRotationDiv2 = JoltProjectSettings::get_body_pair_cache_angle_cos_div2();
settings.mNumVelocitySteps = (JPH::uint)JoltProjectSettings::get_simulation_velocity_steps();
settings.mNumPositionSteps = (JPH::uint)JoltProjectSettings::get_simulation_position_steps();
settings.mMinVelocityForRestitution = JoltProjectSettings::get_bounce_velocity_threshold();
settings.mTimeBeforeSleep = JoltProjectSettings::get_sleep_time_threshold();
settings.mPointVelocitySleepThreshold = JoltProjectSettings::get_sleep_velocity_threshold();
settings.mUseBodyPairContactCache = JoltProjectSettings::is_body_pair_contact_cache_enabled();
settings.mAllowSleeping = JoltProjectSettings::is_sleep_allowed();
physics_system->SetPhysicsSettings(settings);
physics_system->SetGravity(JPH::Vec3::sZero());
physics_system->SetContactListener(contact_listener);
physics_system->SetSoftBodyContactListener(contact_listener);
physics_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) {
return ABS(MIN(p_body1.GetFriction(), p_body2.GetFriction()));
});
physics_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) {
return CLAMP(p_body1.GetRestitution() + p_body2.GetRestitution(), 0.0f, 1.0f);
});
}
JoltSpace3D::~JoltSpace3D() {
if (direct_state != nullptr) {
memdelete(direct_state);
direct_state = nullptr;
}
if (physics_system != nullptr) {
delete physics_system;
physics_system = nullptr;
}
if (contact_listener != nullptr) {
delete contact_listener;
contact_listener = nullptr;
}
if (layers != nullptr) {
delete layers;
layers = nullptr;
}
if (temp_allocator != nullptr) {
delete temp_allocator;
temp_allocator = nullptr;
}
}
void JoltSpace3D::step(float p_step) {
stepping = true;
last_step = p_step;
_pre_step(p_step);
const JPH::EPhysicsUpdateError update_error = physics_system->Update(p_step, 1, temp_allocator, job_system);
if ((update_error & JPH::EPhysicsUpdateError::ManifoldCacheFull) != JPH::EPhysicsUpdateError::None) {
WARN_PRINT_ONCE(vformat("Jolt Physics manifold cache exceeded capacity and contacts were ignored. "
"Consider increasing maximum number of contact constraints in project settings. "
"Maximum number of contact constraints is currently set to %d.",
JoltProjectSettings::get_max_contact_constraints()));
}
if ((update_error & JPH::EPhysicsUpdateError::BodyPairCacheFull) != JPH::EPhysicsUpdateError::None) {
WARN_PRINT_ONCE(vformat("Jolt Physics body pair cache exceeded capacity and contacts were ignored. "
"Consider increasing maximum number of body pairs in project settings. "
"Maximum number of body pairs is currently set to %d.",
JoltProjectSettings::get_max_pairs()));
}
if ((update_error & JPH::EPhysicsUpdateError::ContactConstraintsFull) != JPH::EPhysicsUpdateError::None) {
WARN_PRINT_ONCE(vformat("Jolt Physics contact constraint buffer exceeded capacity and contacts were ignored. "
"Consider increasing maximum number of contact constraints in project settings. "
"Maximum number of contact constraints is currently set to %d.",
JoltProjectSettings::get_max_contact_constraints()));
}
_post_step(p_step);
bodies_added_since_optimizing = 0;
stepping = false;
}
void JoltSpace3D::call_queries() {
while (body_call_queries_list.first()) {
JoltBody3D *body = body_call_queries_list.first()->self();
body_call_queries_list.remove(body_call_queries_list.first());
body->call_queries();
}
while (area_call_queries_list.first()) {
JoltArea3D *body = area_call_queries_list.first()->self();
area_call_queries_list.remove(area_call_queries_list.first());
body->call_queries();
}
}
double JoltSpace3D::get_param(PhysicsServer3D::SpaceParameter p_param) const {
switch (p_param) {
case PhysicsServer3D::SPACE_PARAM_CONTACT_RECYCLE_RADIUS: {
return DEFAULT_CONTACT_RECYCLE_RADIUS;
}
case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_SEPARATION: {
return DEFAULT_CONTACT_MAX_SEPARATION;
}
case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_ALLOWED_PENETRATION: {
return DEFAULT_CONTACT_MAX_ALLOWED_PENETRATION;
}
case PhysicsServer3D::SPACE_PARAM_CONTACT_DEFAULT_BIAS: {
return DEFAULT_CONTACT_DEFAULT_BIAS;
}
case PhysicsServer3D::SPACE_PARAM_BODY_LINEAR_VELOCITY_SLEEP_THRESHOLD: {
return DEFAULT_SLEEP_THRESHOLD_LINEAR;
}
case PhysicsServer3D::SPACE_PARAM_BODY_ANGULAR_VELOCITY_SLEEP_THRESHOLD: {
return DEFAULT_SLEEP_THRESHOLD_ANGULAR;
}
case PhysicsServer3D::SPACE_PARAM_BODY_TIME_TO_SLEEP: {
return JoltProjectSettings::get_sleep_time_threshold();
}
case PhysicsServer3D::SPACE_PARAM_SOLVER_ITERATIONS: {
return DEFAULT_SOLVER_ITERATIONS;
}
default: {
ERR_FAIL_V_MSG(0.0, vformat("Unhandled space parameter: '%d'. This should not happen. Please report this.", p_param));
}
}
}
void JoltSpace3D::set_param(PhysicsServer3D::SpaceParameter p_param, double p_value) {
switch (p_param) {
case PhysicsServer3D::SPACE_PARAM_CONTACT_RECYCLE_RADIUS: {
WARN_PRINT("Space-specific contact recycle radius is not supported when using Jolt Physics. Any such value will be ignored.");
} break;
case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_SEPARATION: {
WARN_PRINT("Space-specific contact max separation is not supported when using Jolt Physics. Any such value will be ignored.");
} break;
case PhysicsServer3D::SPACE_PARAM_CONTACT_MAX_ALLOWED_PENETRATION: {
WARN_PRINT("Space-specific contact max allowed penetration is not supported when using Jolt Physics. Any such value will be ignored.");
} break;
case PhysicsServer3D::SPACE_PARAM_CONTACT_DEFAULT_BIAS: {
WARN_PRINT("Space-specific contact default bias is not supported when using Jolt Physics. Any such value will be ignored.");
} break;
case PhysicsServer3D::SPACE_PARAM_BODY_LINEAR_VELOCITY_SLEEP_THRESHOLD: {
WARN_PRINT("Space-specific linear velocity sleep threshold is not supported when using Jolt Physics. Any such value will be ignored.");
} break;
case PhysicsServer3D::SPACE_PARAM_BODY_ANGULAR_VELOCITY_SLEEP_THRESHOLD: {
WARN_PRINT("Space-specific angular velocity sleep threshold is not supported when using Jolt Physics. Any such value will be ignored.");
} break;
case PhysicsServer3D::SPACE_PARAM_BODY_TIME_TO_SLEEP: {
WARN_PRINT("Space-specific body sleep time is not supported when using Jolt Physics. Any such value will be ignored.");
} break;
case PhysicsServer3D::SPACE_PARAM_SOLVER_ITERATIONS: {
WARN_PRINT("Space-specific solver iterations is not supported when using Jolt Physics. Any such value will be ignored.");
} break;
default: {
ERR_FAIL_MSG(vformat("Unhandled space parameter: '%d'. This should not happen. Please report this.", p_param));
} break;
}
}
JPH::BodyInterface &JoltSpace3D::get_body_iface() {
return physics_system->GetBodyInterfaceNoLock();
}
const JPH::BodyInterface &JoltSpace3D::get_body_iface() const {
return physics_system->GetBodyInterfaceNoLock();
}
const JPH::BodyLockInterface &JoltSpace3D::get_lock_iface() const {
return physics_system->GetBodyLockInterfaceNoLock();
}
const JPH::BroadPhaseQuery &JoltSpace3D::get_broad_phase_query() const {
return physics_system->GetBroadPhaseQuery();
}
const JPH::NarrowPhaseQuery &JoltSpace3D::get_narrow_phase_query() const {
return physics_system->GetNarrowPhaseQueryNoLock();
}
JPH::ObjectLayer JoltSpace3D::map_to_object_layer(JPH::BroadPhaseLayer p_broad_phase_layer, uint32_t p_collision_layer, uint32_t p_collision_mask) {
return layers->to_object_layer(p_broad_phase_layer, p_collision_layer, p_collision_mask);
}
void 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 {
layers->from_object_layer(p_object_layer, r_broad_phase_layer, r_collision_layer, r_collision_mask);
}
JoltReadableBody3D JoltSpace3D::read_body(const JPH::BodyID &p_body_id) const {
return JoltReadableBody3D(*this, p_body_id);
}
JoltReadableBody3D JoltSpace3D::read_body(const JoltObject3D &p_object) const {
return read_body(p_object.get_jolt_id());
}
JoltWritableBody3D JoltSpace3D::write_body(const JPH::BodyID &p_body_id) const {
return JoltWritableBody3D(*this, p_body_id);
}
JoltWritableBody3D JoltSpace3D::write_body(const JoltObject3D &p_object) const {
return write_body(p_object.get_jolt_id());
}
JoltReadableBodies3D JoltSpace3D::read_bodies(const JPH::BodyID *p_body_ids, int p_body_count) const {
return JoltReadableBodies3D(*this, p_body_ids, p_body_count);
}
JoltWritableBodies3D JoltSpace3D::write_bodies(const JPH::BodyID *p_body_ids, int p_body_count) const {
return JoltWritableBodies3D(*this, p_body_ids, p_body_count);
}
JoltPhysicsDirectSpaceState3D *JoltSpace3D::get_direct_state() {
if (direct_state == nullptr) {
direct_state = memnew(JoltPhysicsDirectSpaceState3D(this));
}
return direct_state;
}
void JoltSpace3D::set_default_area(JoltArea3D *p_area) {
if (default_area == p_area) {
return;
}
if (default_area != nullptr) {
default_area->set_default_area(false);
}
default_area = p_area;
if (default_area != nullptr) {
default_area->set_default_area(true);
}
}
JPH::BodyID JoltSpace3D::add_rigid_body(const JoltObject3D &p_object, const JPH::BodyCreationSettings &p_settings, bool p_sleeping) {
const JPH::BodyID body_id = get_body_iface().CreateAndAddBody(p_settings, p_sleeping ? JPH::EActivation::DontActivate : JPH::EActivation::Activate);
if (unlikely(body_id.IsInvalid())) {
ERR_PRINT_ONCE(vformat("Failed to create underlying Jolt Physics body for '%s'. "
"Consider increasing maximum number of bodies in project settings. "
"Maximum number of bodies is currently set to %d.",
p_object.to_string(), JoltProjectSettings::get_max_bodies()));
return JPH::BodyID();
}
bodies_added_since_optimizing += 1;
return body_id;
}
JPH::BodyID JoltSpace3D::add_soft_body(const JoltObject3D &p_object, const JPH::SoftBodyCreationSettings &p_settings, bool p_sleeping) {
const JPH::BodyID body_id = get_body_iface().CreateAndAddSoftBody(p_settings, p_sleeping ? JPH::EActivation::DontActivate : JPH::EActivation::Activate);
if (unlikely(body_id.IsInvalid())) {
ERR_PRINT_ONCE(vformat("Failed to create underlying Jolt Physics body for '%s'. "
"Consider increasing maximum number of bodies in project settings. "
"Maximum number of bodies is currently set to %d.",
p_object.to_string(), JoltProjectSettings::get_max_bodies()));
return JPH::BodyID();
}
bodies_added_since_optimizing += 1;
return body_id;
}
void JoltSpace3D::remove_body(const JPH::BodyID &p_body_id) {
JPH::BodyInterface &body_iface = get_body_iface();
body_iface.RemoveBody(p_body_id);
body_iface.DestroyBody(p_body_id);
}
void JoltSpace3D::try_optimize() {
// This makes assumptions about the underlying acceleration structure of Jolt's broad-phase, which currently uses a
// quadtree, and which gets walked with a fixed-size node stack of 128. This means that when the quadtree is
// completely unbalanced, as is the case if we add bodies one-by-one without ever stepping the simulation, like in
// the editor viewport, we would exceed this stack size (resulting in an incomplete search) as soon as we perform a
// physics query after having added somewhere in the order of 128 * 3 bodies. We leave a hefty margin just in case.
if (likely(bodies_added_since_optimizing < 128)) {
return;
}
physics_system->OptimizeBroadPhase();
bodies_added_since_optimizing = 0;
}
void JoltSpace3D::enqueue_call_queries(SelfList<JoltBody3D> *p_body) {
if (!p_body->in_list()) {
body_call_queries_list.add(p_body);
}
}
void JoltSpace3D::enqueue_call_queries(SelfList<JoltArea3D> *p_area) {
if (!p_area->in_list()) {
area_call_queries_list.add(p_area);
}
}
void JoltSpace3D::dequeue_call_queries(SelfList<JoltBody3D> *p_body) {
if (p_body->in_list()) {
body_call_queries_list.remove(p_body);
}
}
void JoltSpace3D::dequeue_call_queries(SelfList<JoltArea3D> *p_area) {
if (p_area->in_list()) {
area_call_queries_list.remove(p_area);
}
}
void JoltSpace3D::enqueue_shapes_changed(SelfList<JoltShapedObject3D> *p_object) {
if (!p_object->in_list()) {
shapes_changed_list.add(p_object);
}
}
void JoltSpace3D::dequeue_shapes_changed(SelfList<JoltShapedObject3D> *p_object) {
if (p_object->in_list()) {
shapes_changed_list.remove(p_object);
}
}
void JoltSpace3D::enqueue_needs_optimization(SelfList<JoltShapedObject3D> *p_object) {
if (!p_object->in_list()) {
needs_optimization_list.add(p_object);
}
}
void JoltSpace3D::dequeue_needs_optimization(SelfList<JoltShapedObject3D> *p_object) {
if (p_object->in_list()) {
needs_optimization_list.remove(p_object);
}
}
void JoltSpace3D::add_joint(JPH::Constraint *p_jolt_ref) {
physics_system->AddConstraint(p_jolt_ref);
}
void JoltSpace3D::add_joint(JoltJoint3D *p_joint) {
add_joint(p_joint->get_jolt_ref());
}
void JoltSpace3D::remove_joint(JPH::Constraint *p_jolt_ref) {
physics_system->RemoveConstraint(p_jolt_ref);
}
void JoltSpace3D::remove_joint(JoltJoint3D *p_joint) {
remove_joint(p_joint->get_jolt_ref());
}
#ifdef DEBUG_ENABLED
void JoltSpace3D::dump_debug_snapshot(const String &p_dir) {
const Dictionary datetime = Time::get_singleton()->get_datetime_dict_from_system();
const String datetime_str = vformat("%04d-%02d-%02d_%02d-%02d-%02d", datetime["year"], datetime["month"], datetime["day"], datetime["hour"], datetime["minute"], datetime["second"]);
const String path = p_dir + vformat("/jolt_snapshot_%s_%d.bin", datetime_str, rid.get_id());
Ref<FileAccess> file_access = FileAccess::open(path, FileAccess::ModeFlags::WRITE);
ERR_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()));
JPH::PhysicsScene physics_scene;
physics_scene.FromPhysicsSystem(physics_system);
for (JPH::BodyCreationSettings &settings : physics_scene.GetBodies()) {
const JoltObject3D *object = reinterpret_cast<const JoltObject3D *>(settings.mUserData);
if (const JoltBody3D *body = object->as_body()) {
// 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.
settings.mGravityFactor = body->get_gravity_scale();
settings.mLinearDamping = body->get_total_linear_damp();
settings.mAngularDamping = body->get_total_angular_damp();
}
settings.SetShape(JoltShape3D::without_custom_shapes(settings.GetShape()));
}
JoltStreamOutputWrapper output_stream(file_access);
physics_scene.SaveBinaryState(output_stream, true, false);
ERR_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())));
print_line(vformat("Snapshot of physics space with RID '%d' saved to '%s'.", rid.get_id(), path));
}
const PackedVector3Array &JoltSpace3D::get_debug_contacts() const {
return contact_listener->get_debug_contacts();
}
int JoltSpace3D::get_debug_contact_count() const {
return contact_listener->get_debug_contact_count();
}
int JoltSpace3D::get_max_debug_contacts() const {
return contact_listener->get_max_debug_contacts();
}
void JoltSpace3D::set_max_debug_contacts(int p_count) {
contact_listener->set_max_debug_contacts(p_count);
}
#endif