Files
WickedEngine/WickedEngine/wiPhysics_Jolt.cpp
T
2025-02-25 07:21:46 +01:00

2348 lines
84 KiB
C++

#include "wiPhysics.h"
#include "wiScene.h"
#include "wiProfiler.h"
#include "wiBacklog.h"
#include "wiJobSystem.h"
#include "wiRenderer.h"
#include "wiTimer.h"
#include <Jolt/Jolt.h>
#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/Collision/Shape/CapsuleShape.h>
#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
#include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
#include <Jolt/Physics/Collision/Shape/HeightFieldShape.h>
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
#include <Jolt/Physics/Collision/RayCast.h>
#include <Jolt/Physics/Collision/CastResult.h>
#include <Jolt/Physics/Collision/CollisionCollectorImpl.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 <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
#include <Jolt/Physics/SoftBody/SoftBodyShape.h>
#include <Jolt/Physics/Constraints/DistanceConstraint.h>
#include <Jolt/Physics/Constraints/FixedConstraint.h>
#include <Jolt/Physics/Constraints/SwingTwistConstraint.h>
#include <Jolt/Physics/Constraints/HingeConstraint.h>
#include <Jolt/Physics/Ragdoll/Ragdoll.h>
#include <Jolt/Skeleton/Skeleton.h>
#ifdef JPH_DEBUG_RENDERER
#include <Jolt/Renderer/DebugRendererSimple.h>
#endif // JPH_DEBUG_RENDERER
#include <thread>
// 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_WARNINGS
// All Jolt symbols are in the JPH namespace
using namespace JPH;
using namespace wi::ecs;
using namespace wi::scene;
namespace wi::physics
{
inline XMMATRIX GetOrientation(XMVECTOR P0, XMVECTOR P1, XMVECTOR P2)
{
XMVECTOR T = P2 - P1;
XMVECTOR B = P1 - P0;
XMVECTOR N = XMVector3Cross(B, T);
T = XMVector3Cross(B, N);
T = XMVector3Normalize(T);
B = XMVector3Normalize(B);
N = XMVector3Normalize(N);
return XMMATRIX(T, B, N, XMVectorSetW(P0, 1));
}
namespace jolt
{
bool ENABLED = true;
bool SIMULATION_ENABLED = true;
bool DEBUGDRAW_ENABLED = false;
int ACCURACY = 4;
int softbodyIterationCount = 6;
float TIMESTEP = 1.0f / 60.0f;
bool INTERPOLATION = true;
const uint cMaxBodies = 65536;
const uint cNumBodyMutexes = 0;
const uint cMaxBodyPairs = 65536;
const uint cMaxContactConstraints = 65536;
const EMotionQuality cMotionQuality = EMotionQuality::LinearCast;
inline Vec3 cast(const Float3& v) { return Vec3(v.x, v.y, v.z); }
inline Vec3 cast(const XMFLOAT3& v) { return Vec3(v.x, v.y, v.z); }
inline Quat cast(const XMFLOAT4& v) { return Quat(v.x, v.y, v.z, v.w); }
inline Mat44 cast(const XMFLOAT4X4& v)
{
return Mat44(
Vec4(v._11, v._12, v._13, v._14),
Vec4(v._21, v._22, v._23, v._24),
Vec4(v._31, v._32, v._33, v._34),
Vec4(v._41, v._42, v._43, v._44)
);
}
inline XMFLOAT3 cast(Vec3Arg v) { return XMFLOAT3(v.GetX(), v.GetY(), v.GetZ()); }
inline XMFLOAT4 cast(QuatArg v) { return XMFLOAT4(v.GetX(), v.GetY(), v.GetZ(), v.GetW()); }
inline XMFLOAT4X4 cast(Mat44 v)
{
XMFLOAT4X4 ret;
v.StoreFloat4x4((Float4*)&ret);
return ret;
}
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:
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);
};
// BroadPhaseLayerInterface implementation
// This defines a mapping between 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];
}
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;
}
}
};
struct JoltDestroyer
{
~JoltDestroyer()
{
// we don't call UnregisterTypes() here, this is intentional
// see #945 and jrouwe/JoltPhysics#1458 for more information
delete Factory::sInstance;
Factory::sInstance = nullptr;
}
} jolt_destroyer;
struct PhysicsScene
{
PhysicsSystem physics_system;
BPLayerInterfaceImpl broad_phase_layer_interface;
ObjectVsBroadPhaseLayerFilterImpl object_vs_broadphase_layer_filter;
ObjectLayerPairFilterImpl object_vs_object_layer_filter;
float accumulator = 0;
float alpha = 0;
bool activate_all_rigid_bodies = false;
float GetKinematicDT(float dt) const
{
return clamp(accumulator + dt, 0.0f, TIMESTEP * ACCURACY);
}
};
PhysicsScene& GetPhysicsScene(Scene& scene)
{
if (scene.physics_scene == nullptr)
{
auto physics_scene = std::make_shared<PhysicsScene>();
physics_scene->physics_system.Init(
cMaxBodies,
cNumBodyMutexes,
cMaxBodyPairs,
cMaxContactConstraints,
physics_scene->broad_phase_layer_interface,
physics_scene->object_vs_broadphase_layer_filter,
physics_scene->object_vs_object_layer_filter
);
scene.physics_scene = physics_scene;
}
return *(PhysicsScene*)scene.physics_scene.get();
}
struct RigidBody
{
std::shared_ptr<void> physics_scene;
ShapeRefC shape;
BodyID bodyID;
Entity entity = INVALID_ENTITY;
// Things for parented objects:
XMFLOAT4X4 parentMatrix = wi::math::IDENTITY_MATRIX;
XMFLOAT4X4 parentMatrixInverse = wi::math::IDENTITY_MATRIX;
// Interpolation state:
Vec3 prev_position = Vec3::sZero();
Quat prev_rotation = Quat::sIdentity();
// Local body offset:
Mat44 additionalTransform = Mat44::sIdentity();
Mat44 additionalTransformInverse = Mat44::sIdentity();
// This is to fixup ragdolls that are applied to skeletons from different kinds of model imports
Mat44 restBasis = Mat44::sIdentity();
Mat44 restBasisInverse = Mat44::sIdentity();
// for trace hit reporting:
wi::ecs::Entity humanoid_ragdoll_entity = wi::ecs::INVALID_ENTITY;
wi::scene::HumanoidComponent::HumanoidBone humanoid_bone = wi::scene::HumanoidComponent::HumanoidBone::Count;
wi::primitive::Capsule capsule;
// water ripple stuff:
bool was_underwater = false;
~RigidBody()
{
if (physics_scene == nullptr || bodyID.IsInvalid())
return;
BodyInterface& body_interface = ((PhysicsScene*)physics_scene.get())->physics_system.GetBodyInterface(); // locking version because destructor can be called from any thread
body_interface.RemoveBody(bodyID);
body_interface.DestroyBody(bodyID);
}
};
struct SoftBody
{
std::shared_ptr<void> physics_scene;
BodyID bodyID;
Entity entity = INVALID_ENTITY;
SoftBodySharedSettings shared_settings;
wi::vector<XMFLOAT4X4> inverseBindMatrices;
struct Neighbors
{
uint32_t left = 0;
uint32_t right = 0;
constexpr void set(uint32_t l, uint32_t r)
{
left = l;
right = r;
}
};
wi::vector<Neighbors> physicsNeighbors;
~SoftBody()
{
if (physics_scene == nullptr || bodyID.IsInvalid())
return;
BodyInterface& body_interface = ((PhysicsScene*)physics_scene.get())->physics_system.GetBodyInterface(); // locking version because destructor can be called from any thread
body_interface.RemoveBody(bodyID);
body_interface.DestroyBody(bodyID);
}
};
RigidBody& GetRigidBody(wi::scene::RigidBodyPhysicsComponent& physicscomponent)
{
if (physicscomponent.physicsobject == nullptr)
{
physicscomponent.physicsobject = std::make_shared<RigidBody>();
}
return *(RigidBody*)physicscomponent.physicsobject.get();
}
const RigidBody& GetRigidBody(const wi::scene::RigidBodyPhysicsComponent& physicscomponent)
{
return *(RigidBody*)physicscomponent.physicsobject.get();
}
SoftBody& GetSoftBody(wi::scene::SoftBodyPhysicsComponent& physicscomponent)
{
if (physicscomponent.physicsobject == nullptr)
{
physicscomponent.physicsobject = std::make_shared<SoftBody>();
}
return *(SoftBody*)physicscomponent.physicsobject.get();
}
const SoftBody& GetSoftBody(const wi::scene::SoftBodyPhysicsComponent& physicscomponent)
{
return *(SoftBody*)physicscomponent.physicsobject.get();
}
void AddRigidBody(
wi::scene::Scene& scene,
Entity entity,
wi::scene::RigidBodyPhysicsComponent& physicscomponent,
const wi::scene::TransformComponent& _transform,
const wi::scene::MeshComponent* mesh
)
{
RigidBody& physicsobject = GetRigidBody(physicscomponent);
TransformComponent transform = _transform;
scene.locker.lock();
XMMATRIX parentMatrix = scene.ComputeParentMatrixRecursive(entity);
scene.locker.unlock();
XMStoreFloat4x4(&transform.world, parentMatrix * transform.GetLocalMatrix());
transform.ApplyTransform();
if (mesh != nullptr && mesh->precomputed_rigidbody_physics_shape.physicsobject != nullptr)
{
// The shape comes from mesh's precomputed shape:
const RigidBody& precomputed_rigidbody_with_shape = GetRigidBody(mesh->precomputed_rigidbody_physics_shape);
physicsobject.shape = precomputed_rigidbody_with_shape.shape;
}
if (physicsobject.shape == nullptr) // shape creation can be called from outside as optimization from threads
{
CreateRigidBodyShape(physicscomponent, transform.scale_local, mesh);
}
if (physicsobject.shape != nullptr)
{
physicsobject.physics_scene = scene.physics_scene;
physicsobject.entity = entity;
XMStoreFloat4x4(&physicsobject.parentMatrix, parentMatrix);
XMStoreFloat4x4(&physicsobject.parentMatrixInverse, XMMatrixInverse(nullptr, parentMatrix));
PhysicsScene& physics_scene = GetPhysicsScene(scene);
Mat44 mat = cast(transform.world);
Vec3 local_offset = cast(physicscomponent.local_offset);
physicsobject.additionalTransform.SetTranslation(local_offset);
physicsobject.additionalTransformInverse = physicsobject.additionalTransform.Inversed();
physicsobject.prev_position = mat.GetTranslation();
physicsobject.prev_rotation = mat.GetQuaternion().Normalized();
const EMotionType motionType = physicscomponent.mass == 0 ? EMotionType::Static : (physicscomponent.IsKinematic() ? EMotionType::Kinematic : EMotionType::Dynamic);
BodyCreationSettings settings(
physicsobject.shape.GetPtr(),
local_offset + physicsobject.prev_position,
physicsobject.prev_rotation,
motionType,
Layers::MOVING
);
settings.mRestitution = physicscomponent.restitution;
settings.mFriction = physicscomponent.friction;
settings.mLinearDamping = physicscomponent.damping_linear;
settings.mAngularDamping = physicscomponent.damping_angular;
settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
if (motionType == EMotionType::Dynamic)
{
settings.mMassPropertiesOverride.mMass = physicscomponent.mass;
}
else
{
settings.mMassPropertiesOverride.mMass = 1;
}
settings.mAllowSleeping = !physicscomponent.IsDisableDeactivation();
settings.mMotionQuality = cMotionQuality;
settings.mUserData = (uint64_t)&physicsobject;
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterface(); // locking version because this is called from job system!
const EActivation activation = physicscomponent.IsStartDeactivated() ? EActivation::DontActivate : EActivation::Activate;
physicsobject.bodyID = body_interface.CreateAndAddBody(settings, activation);
if (physicsobject.bodyID.IsInvalid())
{
wi::backlog::post("AddRigidBody failed: body couldn't be created! This could mean that there are too many physics objects.", wi::backlog::LogLevel::Error);
return;
}
}
}
void AddSoftBody(
wi::scene::Scene& scene,
Entity entity,
wi::scene::SoftBodyPhysicsComponent& physicscomponent,
wi::scene::MeshComponent& mesh
)
{
SoftBody& physicsobject = GetSoftBody(physicscomponent);
physicsobject.physics_scene = scene.physics_scene;
physicsobject.entity = entity;
PhysicsScene& physics_scene = GetPhysicsScene(scene);
physicsobject.shared_settings.SetEmbedded();
physicsobject.shared_settings.mVertexRadius = physicscomponent.vertex_radius;
physicscomponent.CreateFromMesh(mesh);
if (physicscomponent.physicsIndices.empty())
{
wi::backlog::post("AddSoftBody failed: physics faces are empty, this means generating physics mesh has failed, try to change settings.", wi::backlog::LogLevel::Error);
return;
}
const size_t vertexCount = physicscomponent.physicsToGraphicsVertexMapping.size();
physicsobject.physicsNeighbors.resize(vertexCount);
for (size_t i = 0; i < physicscomponent.physicsIndices.size(); i += 3)
{
const uint32_t physicsInd0 = physicscomponent.physicsIndices[i + 0];
const uint32_t physicsInd1 = physicscomponent.physicsIndices[i + 1];
const uint32_t physicsInd2 = physicscomponent.physicsIndices[i + 2];
SoftBodySharedSettings::Face& face = physicsobject.shared_settings.mFaces.emplace_back();
face.mVertex[0] = physicsInd0;
face.mVertex[2] = physicsInd1;
face.mVertex[1] = physicsInd2;
physicsobject.physicsNeighbors[physicsInd0].set(physicsInd2, physicsInd1);
physicsobject.physicsNeighbors[physicsInd1].set(physicsInd0, physicsInd2);
physicsobject.physicsNeighbors[physicsInd2].set(physicsInd1, physicsInd0);
}
const XMMATRIX worldMatrix = XMLoadFloat4x4(&physicscomponent.worldMatrix);
physicsobject.shared_settings.mVertices.resize(vertexCount);
physicsobject.inverseBindMatrices.resize(vertexCount);
physicscomponent.boneData.resize(vertexCount);
const float distributed_mass = physicscomponent.mass / vertexCount;
for (size_t i = 0; i < vertexCount; ++i)
{
uint32_t graphicsInd = physicscomponent.physicsToGraphicsVertexMapping[i];
XMFLOAT3 position = mesh.vertex_positions[graphicsInd];
XMVECTOR P = XMLoadFloat3(&position);
P = XMVector3Transform(P, worldMatrix);
XMStoreFloat3(&position, P);
physicsobject.shared_settings.mVertices[i].mPosition = Float3(position.x, position.y, position.z);
float weight = physicscomponent.weights[graphicsInd] * distributed_mass;
physicsobject.shared_settings.mVertices[i].mInvMass = weight == 0 ? 0 : 1.0f / (1.0f + weight);
// The soft body node will have a bind matrix similar to an armature bone:
XMVECTOR P0 = XMLoadFloat3(&mesh.vertex_positions[graphicsInd]);
XMVECTOR P1 = XMLoadFloat3(&mesh.vertex_positions[physicscomponent.physicsToGraphicsVertexMapping[physicsobject.physicsNeighbors[i].left]]);
XMVECTOR P2 = XMLoadFloat3(&mesh.vertex_positions[physicscomponent.physicsToGraphicsVertexMapping[physicsobject.physicsNeighbors[i].right]]);
XMMATRIX B = GetOrientation(P0, P1, P2);
B = XMMatrixInverse(nullptr, B);
XMStoreFloat4x4(&physicsobject.inverseBindMatrices[i], B);
}
SoftBodySharedSettings::VertexAttributes vertexAttributes = { 1.0e-5f, 1.0e-5f, 1.0e-5f };
physicsobject.shared_settings.CreateConstraints(&vertexAttributes, 1);
physicsobject.shared_settings.Optimize();
SoftBodyCreationSettings settings(&physicsobject.shared_settings, Vec3::sZero(), Quat::sIdentity(), Layers::MOVING);
settings.mNumIterations = (uint32)softbodyIterationCount;
settings.mFriction = physicscomponent.friction;
settings.mRestitution = physicscomponent.restitution;
settings.mPressure = physicscomponent.pressure;
settings.mUpdatePosition = false;
settings.mAllowSleeping = !physicscomponent.IsDisableDeactivation();
settings.mUserData = (uint64_t)&physicsobject;
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterface(); // locking version because this is called from job system!
physicsobject.bodyID = body_interface.CreateAndAddSoftBody(settings, EActivation::Activate);
if (physicsobject.bodyID.IsInvalid())
{
wi::backlog::post("AddSoftBody failed: body couldn't be created! This could mean that there are too many physics objects.", wi::backlog::LogLevel::Error);
return;
}
}
struct Ragdoll
{
enum BODYPART
{
BODYPART_PELVIS = 0,
BODYPART_SPINE,
BODYPART_HEAD,
BODYPART_LEFT_UPPER_LEG,
BODYPART_LEFT_LOWER_LEG,
BODYPART_RIGHT_UPPER_LEG,
BODYPART_RIGHT_LOWER_LEG,
BODYPART_LEFT_UPPER_ARM,
BODYPART_LEFT_LOWER_ARM,
BODYPART_RIGHT_UPPER_ARM,
BODYPART_RIGHT_LOWER_ARM,
BODYPART_COUNT
};
std::shared_ptr<void> physics_scene;
RigidBody rigidbodies[BODYPART_COUNT];
Entity saved_parents[BODYPART_COUNT] = {};
Skeleton skeleton;
RagdollSettings settings;
Ref<JPH::Ragdoll> ragdoll;
bool state_active = false;
float scale = 1;
Ragdoll(Scene& scene, HumanoidComponent& humanoid, Entity humanoidEntity, float scale)
{
physics_scene = scene.physics_scene;
PhysicsSystem& physics_system = ((PhysicsScene*)physics_scene.get())->physics_system;
BodyInterface& body_interface = physics_system.GetBodyInterface(); // locking version because this is called from job system!
float masses[BODYPART_COUNT] = {};
Vec3 positions[BODYPART_COUNT] = {};
Vec3 constraint_positions[BODYPART_COUNT] = {};
Mat44 final_transforms[BODYPART_COUNT] = {};
#if 0
// slow speed and visualizer to aid debugging:
wi::renderer::SetGameSpeed(0.1f);
SetDebugDrawEnabled(true);
#endif
// Detect which way humanoid is facing in rest pose:
const float facing = humanoid.default_facing;
// Whole ragdoll will take a uniform scaling:
const XMMATRIX scaleMatrix = XMMatrixScaling(scale, scale, scale);
this->scale = scale;
// Calculate the bone lengths and radiuses in armature local space and create rigid bodies for bones:
for (int c = 0; c < BODYPART_COUNT; ++c)
{
HumanoidComponent::HumanoidBone humanoid_bone = HumanoidComponent::HumanoidBone::Count;
Entity entityA = INVALID_ENTITY;
Entity entityB = INVALID_ENTITY;
switch (c)
{
case BODYPART_PELVIS:
humanoid_bone = HumanoidComponent::HumanoidBone::Hips;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::Hips];
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::Spine];
break;
case BODYPART_SPINE:
humanoid_bone = HumanoidComponent::HumanoidBone::Spine;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::Spine];
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::Neck]; // prefer neck instead of head
if (entityB == INVALID_ENTITY)
{
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::Head]; // fall back to head if neck not available
}
break;
case BODYPART_HEAD:
humanoid_bone = HumanoidComponent::HumanoidBone::Neck;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::Neck]; // prefer neck instead of head
if (entityA == INVALID_ENTITY)
{
humanoid_bone = HumanoidComponent::HumanoidBone::Head;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::Head]; // fall back to head if neck not available
}
break;
case BODYPART_LEFT_UPPER_LEG:
humanoid_bone = HumanoidComponent::HumanoidBone::LeftUpperLeg;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftUpperLeg];
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftLowerLeg];
break;
case BODYPART_LEFT_LOWER_LEG:
humanoid_bone = HumanoidComponent::HumanoidBone::LeftLowerLeg;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftLowerLeg];
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftFoot];
break;
case BODYPART_RIGHT_UPPER_LEG:
humanoid_bone = HumanoidComponent::HumanoidBone::RightUpperLeg;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightUpperLeg];
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightLowerLeg];
break;
case BODYPART_RIGHT_LOWER_LEG:
humanoid_bone = HumanoidComponent::HumanoidBone::RightLowerLeg;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightLowerLeg];
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightFoot];
break;
case BODYPART_LEFT_UPPER_ARM:
humanoid_bone = HumanoidComponent::HumanoidBone::LeftUpperArm;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftUpperArm];
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftLowerArm];
break;
case BODYPART_LEFT_LOWER_ARM:
humanoid_bone = HumanoidComponent::HumanoidBone::LeftLowerArm;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftLowerArm];
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::LeftHand];
break;
case BODYPART_RIGHT_UPPER_ARM:
humanoid_bone = HumanoidComponent::HumanoidBone::RightUpperArm;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightUpperArm];
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightLowerArm];
break;
case BODYPART_RIGHT_LOWER_ARM:
humanoid_bone = HumanoidComponent::HumanoidBone::RightLowerArm;
entityA = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightLowerArm];
entityB = humanoid.bones[(size_t)HumanoidComponent::HumanoidBone::RightHand];
break;
}
if (entityA == INVALID_ENTITY)
{
wilog_warning("Ragdoll creation aborted because of a missing body part: %d", c);
return;
}
// Calculations here will be done in armature local space.
// Unfortunately since humanoid can be separate from armature, we use a "find" utility to find bone rest matrix in armature
// Note that current scaling of character is applied here separately from rest pose
XMMATRIX restA = scene.GetRestPose(entityA) * scaleMatrix;
XMMATRIX restB = scene.GetRestPose(entityB) * scaleMatrix;
XMVECTOR rootA = restA.r[3];
XMVECTOR rootB = restB.r[3];
// Every bone will be a rigid body:
RigidBody& physicsobject = rigidbodies[c];
physicsobject.entity = entityA;
float mass = scale;
float capsule_height = scale;
float capsule_radius = scale * humanoid.ragdoll_fatness;
if (c == BODYPART_HEAD)
{
// Head doesn't necessarily have a child, so make up something reasonable:
capsule_height = 0.05f * scale;
capsule_radius = 0.1f * scale * humanoid.ragdoll_headsize;
}
else
{
// bone length:
XMVECTOR len = XMVector3Length(XMVectorSubtract(rootB, rootA));
capsule_height = XMVectorGetX(len);
// capsule radius and length is tweaked per body part:
switch (c)
{
case BODYPART_PELVIS:
capsule_radius = 0.1f * scale * humanoid.ragdoll_fatness;
break;
case BODYPART_SPINE:
capsule_radius = 0.1f * scale * humanoid.ragdoll_fatness;
capsule_height -= capsule_radius * 2;
break;
case BODYPART_LEFT_LOWER_ARM:
case BODYPART_RIGHT_LOWER_ARM:
capsule_radius = capsule_height * 0.15f * humanoid.ragdoll_fatness;
capsule_height += capsule_radius;
break;
case BODYPART_LEFT_UPPER_LEG:
case BODYPART_RIGHT_UPPER_LEG:
capsule_radius = capsule_height * 0.15f * humanoid.ragdoll_fatness;
capsule_height -= capsule_radius * 2;
break;
case BODYPART_LEFT_LOWER_LEG:
case BODYPART_RIGHT_LOWER_LEG:
capsule_radius = capsule_height * 0.15f * humanoid.ragdoll_fatness;
capsule_height -= capsule_radius;
break;
default:
capsule_radius = capsule_height * 0.2f * humanoid.ragdoll_fatness;
capsule_height -= capsule_radius * 2;
break;
}
}
capsule_radius = std::abs(capsule_radius);
capsule_height = std::abs(capsule_height);
ShapeSettings::ShapeResult shape_result;
CapsuleShapeSettings shape_settings(capsule_height * 0.5f, capsule_radius);
shape_settings.SetEmbedded();
shape_result = shape_settings.Create();
RotatedTranslatedShapeSettings rtshape_settings;
rtshape_settings.SetEmbedded();
rtshape_settings.mInnerShapePtr = shape_result.Get();
rtshape_settings.mPosition = Vec3::sZero();
rtshape_settings.mRotation = Quat::sIdentity();
switch (c)
{
case BODYPART_LEFT_UPPER_ARM:
case BODYPART_LEFT_LOWER_ARM:
case BODYPART_RIGHT_UPPER_ARM:
case BODYPART_RIGHT_LOWER_ARM:
physicsobject.capsule = wi::primitive::Capsule(XMFLOAT3(-capsule_height * 0.5f - capsule_radius, 0, 0), XMFLOAT3(capsule_height * 0.5f + capsule_radius, 0, 0), capsule_radius);
rtshape_settings.mRotation = Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI).Normalized();
break;
default:
physicsobject.capsule = wi::primitive::Capsule(XMFLOAT3(0, -capsule_height * 0.5f - capsule_radius, 0), XMFLOAT3(0, capsule_height * 0.5f + capsule_radius, 0), capsule_radius);
break;
}
shape_result = rtshape_settings.Create();
physicsobject.shape = shape_result.Get();
// capsule offset on axis is performed because otherwise capsule center would be in the bone root position
// which is not what we want. Instead the bone is moved on its axis so it resides between root and tail
const float offset = capsule_height * 0.5f + capsule_radius;
Vec3 local_offset = Vec3::sZero();
switch (c)
{
case BODYPART_PELVIS:
break;
case BODYPART_SPINE:
case BODYPART_HEAD:
local_offset = Vec3(0, offset, 0);
break;
case BODYPART_LEFT_UPPER_LEG:
case BODYPART_LEFT_LOWER_LEG:
case BODYPART_RIGHT_UPPER_LEG:
case BODYPART_RIGHT_LOWER_LEG:
local_offset = Vec3(0, -offset, 0);
break;
case BODYPART_LEFT_UPPER_ARM:
case BODYPART_LEFT_LOWER_ARM:
local_offset = Vec3(-offset * facing, 0, 0);
break;
case BODYPART_RIGHT_UPPER_ARM:
case BODYPART_RIGHT_LOWER_ARM:
local_offset = Vec3(offset * facing, 0, 0);
break;
default:
break;
}
physicsobject.additionalTransform.SetTranslation(local_offset);
physicsobject.additionalTransformInverse = physicsobject.additionalTransform.Inversed();
// Get the translation and rotation part of rest matrix:
XMVECTOR SCA = {};
XMVECTOR ROT = {};
XMVECTOR TRA = {};
XMMatrixDecompose(&SCA, &ROT, &TRA, restA);
XMFLOAT4 rot = {};
XMFLOAT3 tra = {};
XMStoreFloat4(&rot, ROT);
XMStoreFloat3(&tra, TRA);
Vec3 root = cast(tra);
Mat44 mat = Mat44::sTranslation(root);
mat = mat * physicsobject.additionalTransform;
physicsobject.restBasis = Mat44::sRotation(cast(rot));
physicsobject.restBasisInverse = physicsobject.restBasis.Inversed();
physicsobject.humanoid_ragdoll_entity = humanoidEntity;
physicsobject.humanoid_bone = humanoid_bone;
physicsobject.physics_scene = scene.physics_scene;
masses[c] = mass;
positions[c] = mat.GetTranslation();
constraint_positions[c] = root;
final_transforms[c] = Mat44::sTranslation(cast(tra)) * Mat44::sRotation(cast(rot));
physicsobject.prev_position = final_transforms[c].GetTranslation();
physicsobject.prev_rotation = final_transforms[c].GetQuaternion().Normalized();
final_transforms[c] = final_transforms[c] * physicsobject.restBasisInverse;
final_transforms[c] = final_transforms[c] * physicsobject.additionalTransform;
}
// For constraint setup, see examples in Jolt/Samples/Utils/RagdollLoader.cpp
skeleton.SetEmbedded();
uint bodyparts[BODYPART_COUNT] = {};
bodyparts[BODYPART_PELVIS] = skeleton.AddJoint("LowerBody");
bodyparts[BODYPART_SPINE] = skeleton.AddJoint("UpperBody", bodyparts[BODYPART_PELVIS]);
bodyparts[BODYPART_HEAD] = skeleton.AddJoint("Head", bodyparts[BODYPART_SPINE]);
bodyparts[BODYPART_LEFT_UPPER_LEG] = skeleton.AddJoint("UpperLegL", bodyparts[BODYPART_PELVIS]);
bodyparts[BODYPART_LEFT_LOWER_LEG] = skeleton.AddJoint("LowerLegL", bodyparts[BODYPART_LEFT_UPPER_LEG]);
bodyparts[BODYPART_RIGHT_UPPER_LEG] = skeleton.AddJoint("UpperLegR", bodyparts[BODYPART_PELVIS]);
bodyparts[BODYPART_RIGHT_LOWER_LEG] = skeleton.AddJoint("LowerLegR", bodyparts[BODYPART_RIGHT_UPPER_LEG]);
bodyparts[BODYPART_LEFT_UPPER_ARM] = skeleton.AddJoint("UpperArmL", bodyparts[BODYPART_SPINE]);
bodyparts[BODYPART_LEFT_LOWER_ARM] = skeleton.AddJoint("LowerArmL", bodyparts[BODYPART_LEFT_UPPER_ARM]);
bodyparts[BODYPART_RIGHT_UPPER_ARM] = skeleton.AddJoint("UpperArmR", bodyparts[BODYPART_SPINE]);
bodyparts[BODYPART_RIGHT_LOWER_ARM] = skeleton.AddJoint("LowerArmR", bodyparts[BODYPART_RIGHT_UPPER_ARM]);
// Constraint limits
const float twist_angle[] = {
0.0f, // Lower Body (unused, there's no parent)
5.0f, // Upper Body
90.0f, // Head
45.0f, // Upper Leg L
45.0f, // Lower Leg L
45.0f, // Upper Leg R
45.0f, // Lower Leg R
45.0f, // Upper Arm L
45.0f, // Lower Arm L
45.0f, // Upper Arm R
45.0f, // Lower Arm R
};
const float normal_angle[] = {
0.0f, // Lower Body (unused, there's no parent)
40.0f, // Upper Body
45.0f, // Head
45.0f, // Upper Leg L
0.0f, // Lower Leg L
45.0f, // Upper Leg R
0.0f, // Lower Leg R
90.0f, // Upper Arm L
0.0f, // Lower Arm L
90.0f, // Upper Arm R
0.0f, // Lower Arm R
};
const float plane_angle[] = {
0.0f, // Lower Body (unused, there's no parent)
40.0f, // Upper Body
45.0f, // Head
45.0f, // Upper Leg L
60.0f, // Lower Leg L (cheating here, a knee is not symmetric, we should have rotated the twist axis)
45.0f, // Upper Leg R
60.0f, // Lower Leg R
45.0f, // Upper Arm L
90.0f, // Lower Arm L
45.0f, // Upper Arm R
90.0f, // Lower Arm R
};
static float constraint_dbg = 0.1f;
static bool fixpose = false; // enable to fix the pose to rest pose, useful for debugging
settings.SetEmbedded();
settings.mSkeleton = &skeleton;
settings.mParts.resize(skeleton.GetJointCount());
for (int p = 0; p < skeleton.GetJointCount(); ++p)
{
RagdollSettings::Part& part = settings.mParts[p];
part.SetShape(rigidbodies[p].shape);
part.mPosition = positions[p];
part.mRotation = Quat::sIdentity();
part.mMotionType = EMotionType::Kinematic;
part.mMotionQuality = cMotionQuality;
part.mObjectLayer = Layers::MOVING;
part.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
part.mMassPropertiesOverride.mMass = masses[p];
// First part is the root, doesn't have a parent and doesn't have a constraint
if (p > 0)
{
if (p == BODYPART_LEFT_LOWER_LEG || p == BODYPART_RIGHT_LOWER_LEG)
{
Ref<HingeConstraintSettings> constraint = new HingeConstraintSettings;
constraint->mDrawConstraintSize = constraint_dbg;
constraint->mPoint1 = constraint->mPoint2 = constraint_positions[p];
constraint->mHingeAxis1 = constraint->mHingeAxis2 = Vec3::sAxisX() * facing;
constraint->mNormalAxis1 = constraint->mNormalAxis2 = -Vec3::sAxisY();
if (fixpose)
{
constraint->mLimitsMin = constraint->mLimitsMax = 0;
}
else
{
constraint->mLimitsMin = 0;
constraint->mLimitsMax = JPH_PI * 0.8f;
}
part.mToParent = constraint;
}
else if (p == BODYPART_LEFT_LOWER_ARM)
{
Ref<HingeConstraintSettings> constraint = new HingeConstraintSettings;
constraint->mDrawConstraintSize = constraint_dbg;
constraint->mPoint1 = constraint->mPoint2 = constraint_positions[p];
constraint->mHingeAxis1 = constraint->mHingeAxis2 = Vec3::sAxisY();
constraint->mNormalAxis1 = constraint->mNormalAxis2 = (constraint_positions[p] - constraint_positions[p - 1]).Normalized();
if (fixpose)
{
constraint->mLimitsMin = constraint->mLimitsMax = 0;
}
else
{
constraint->mLimitsMin = 0;
constraint->mLimitsMax = JPH_PI * 0.6f;
}
part.mToParent = constraint;
}
else if (p == BODYPART_RIGHT_LOWER_ARM)
{
Ref<HingeConstraintSettings> constraint = new HingeConstraintSettings;
constraint->mDrawConstraintSize = constraint_dbg;
constraint->mPoint1 = constraint->mPoint2 = constraint_positions[p];
constraint->mHingeAxis1 = constraint->mHingeAxis2 = -Vec3::sAxisY();
constraint->mNormalAxis1 = constraint->mNormalAxis2 = (constraint_positions[p] - constraint_positions[p - 1]).Normalized();
if (fixpose)
{
constraint->mLimitsMin = constraint->mLimitsMax = 0;
}
else
{
constraint->mLimitsMin = 0;
constraint->mLimitsMax = JPH_PI * 0.6f;
}
part.mToParent = constraint;
}
else
{
Ref<SwingTwistConstraintSettings> constraint = new SwingTwistConstraintSettings;
constraint->mDrawConstraintSize = constraint_dbg;
constraint->mPosition1 = constraint->mPosition2 = constraint_positions[p];
constraint->mTwistAxis1 = constraint->mTwistAxis2 = (positions[p] - constraint_positions[p]).Normalized();
constraint->mPlaneAxis1 = constraint->mPlaneAxis2 = Vec3::sAxisZ() * facing;
if (fixpose)
{
constraint->mTwistMinAngle = constraint->mTwistMaxAngle = 0;
constraint->mNormalHalfConeAngle = 0;
constraint->mPlaneHalfConeAngle = 0;
}
else
{
constraint->mTwistMinAngle = -DegreesToRadians(twist_angle[p]);
constraint->mTwistMaxAngle = DegreesToRadians(twist_angle[p]);
constraint->mNormalHalfConeAngle = DegreesToRadians(normal_angle[p]);
constraint->mPlaneHalfConeAngle = DegreesToRadians(plane_angle[p]);
}
part.mToParent = constraint;
}
}
}
settings.Stabilize();
settings.DisableParentChildCollisions();
settings.CalculateBodyIndexToConstraintIndex();
static std::atomic<uint32_t> collisionGroupID{}; // generate unique collision group for each ragdoll to enable collision between them
ragdoll = settings.CreateRagdoll(collisionGroupID.fetch_add(1), 0, &physics_system);
ragdoll->SetPose(Vec3::sZero(), final_transforms);
ragdoll->AddToPhysicsSystem(EActivation::Activate);
const int count = (int)ragdoll->GetBodyCount();
for (int index = 0; index < count; ++index)
{
rigidbodies[index].bodyID = ragdoll->GetBodyID(index);
body_interface.SetUserData(rigidbodies[index].bodyID, (uint64_t)&rigidbodies[index]);
}
}
~Ragdoll()
{
if (physics_scene == nullptr)
return;
PhysicsSystem& physics_system = ((PhysicsScene*)physics_scene.get())->physics_system;
const int count = (int)ragdoll->GetBodyCount();
for (int index = 0; index < count; ++index)
{
rigidbodies[index].bodyID = {}; // will be removed by ragdoll
}
ragdoll->RemoveFromPhysicsSystem();
}
// Activates ragdoll as dynamic physics object:
void Activate(
Scene& scene,
Entity humanoidEntity
)
{
if (state_active)
return;
state_active = true;
const HumanoidComponent* humanoid = scene.humanoids.GetComponent(humanoidEntity);
if (humanoid == nullptr)
return;
PhysicsSystem& physics_system = ((PhysicsScene*)physics_scene.get())->physics_system;
BodyInterface& body_interface = physics_system.GetBodyInterface(); // locking version because this is called from job system!
int c = 0;
for (auto& x : rigidbodies)
{
body_interface.SetMotionType(x.bodyID, EMotionType::Dynamic, EActivation::Activate);
// Save parenting information to be able to restore it:
const HierarchyComponent* hier = scene.hierarchy.GetComponent(x.entity);
if (hier != nullptr)
{
saved_parents[c] = hier->parentID;
}
else
{
saved_parents[c] = INVALID_ENTITY;
}
// detach bone because it will be simulated in world space:
scene.Component_Detach(x.entity);
c++;
}
// Stop all anims that are children of humanoid:
for (size_t i = 0; i < scene.animations.GetCount(); ++i)
{
Entity entity = scene.animations.GetEntity(i);
if (!scene.Entity_IsDescendant(entity, humanoidEntity))
continue;
AnimationComponent& animation = scene.animations[i];
animation.Stop();
}
}
// Disables dynamic ragdoll and reattaches loose parts as they were:
void Deactivate(
Scene& scene
)
{
if (!state_active)
return;
state_active = false;
PhysicsSystem& physics_system = ((PhysicsScene*)physics_scene.get())->physics_system;
BodyInterface& body_interface = physics_system.GetBodyInterface(); // locking version because this is called from job system!
int c = 0;
for (auto& x : rigidbodies)
{
body_interface.SetMotionType(x.bodyID, EMotionType::Kinematic, EActivation::Activate);
if (saved_parents[c] != INVALID_ENTITY)
{
scene.Component_Attach(x.entity, saved_parents[c]);
}
c++;
}
}
};
}
using namespace jolt;
void Initialize()
{
wi::Timer timer;
RegisterDefaultAllocator();
Factory::sInstance = new Factory();
RegisterTypes();
wilog("wi::physics Initialized [Jolt Physics %d.%d.%d] (%d ms)", JPH_VERSION_MAJOR, JPH_VERSION_MINOR, JPH_VERSION_PATCH, (int)std::round(timer.elapsed()));
}
void CreateRigidBodyShape(
wi::scene::RigidBodyPhysicsComponent& physicscomponent,
const XMFLOAT3& scale_local,
const wi::scene::MeshComponent* mesh
)
{
ShapeSettings::ShapeResult shape_result;
// The default convex radius caused issues when creating small box shape, etc, so I decrease it:
const float convexRadius = 0.001f;
switch (physicscomponent.shape)
{
case RigidBodyPhysicsComponent::CollisionShape::BOX:
{
BoxShapeSettings settings(Vec3(physicscomponent.box.halfextents.x * scale_local.x, physicscomponent.box.halfextents.y * scale_local.y, physicscomponent.box.halfextents.z * scale_local.z), convexRadius);
settings.SetEmbedded();
shape_result = settings.Create();
}
break;
case RigidBodyPhysicsComponent::CollisionShape::SPHERE:
{
SphereShapeSettings settings(physicscomponent.sphere.radius * scale_local.x);
settings.SetEmbedded();
shape_result = settings.Create();
}
break;
case RigidBodyPhysicsComponent::CollisionShape::CAPSULE:
{
CapsuleShapeSettings settings(physicscomponent.capsule.height * scale_local.y, physicscomponent.capsule.radius * scale_local.x);
settings.SetEmbedded();
shape_result = settings.Create();
}
break;
case RigidBodyPhysicsComponent::CollisionShape::CYLINDER:
{
CylinderShapeSettings settings(physicscomponent.capsule.height * scale_local.y, physicscomponent.capsule.radius * scale_local.x, convexRadius);
settings.SetEmbedded();
shape_result = settings.Create();
}
break;
case RigidBodyPhysicsComponent::CollisionShape::CONVEX_HULL:
if (mesh != nullptr)
{
Array<Vec3> points;
points.reserve(mesh->vertex_positions.size());
for (auto& pos : mesh->vertex_positions)
{
points.push_back(Vec3(pos.x * scale_local.x, pos.y * scale_local.y, pos.z * scale_local.z));
}
ConvexHullShapeSettings settings(points, convexRadius);
settings.SetEmbedded();
shape_result = settings.Create();
}
else
{
wi::backlog::post("CreateRigidBodyShape failed: convex hull physics requested, but no MeshComponent provided!", wi::backlog::LogLevel::Error);
return;
}
break;
case RigidBodyPhysicsComponent::CollisionShape::TRIANGLE_MESH:
if (mesh != nullptr)
{
TriangleList trianglelist;
uint32_t first_subset = 0;
uint32_t last_subset = 0;
mesh->GetLODSubsetRange(physicscomponent.mesh_lod, first_subset, last_subset);
for (uint32_t subsetIndex = first_subset; subsetIndex < last_subset; ++subsetIndex)
{
const MeshComponent::MeshSubset& subset = mesh->subsets[subsetIndex];
const uint32_t* indices = mesh->indices.data() + subset.indexOffset;
for (uint32_t i = 0; i < subset.indexCount; i += 3)
{
Triangle triangle;
triangle.mMaterialIndex = 0;
triangle.mV[0] = Float3(mesh->vertex_positions[indices[i + 0]].x * scale_local.x, mesh->vertex_positions[indices[i + 0]].y * scale_local.y, mesh->vertex_positions[indices[i + 0]].z * scale_local.z);
triangle.mV[2] = Float3(mesh->vertex_positions[indices[i + 1]].x * scale_local.x, mesh->vertex_positions[indices[i + 1]].y * scale_local.y, mesh->vertex_positions[indices[i + 1]].z * scale_local.z);
triangle.mV[1] = Float3(mesh->vertex_positions[indices[i + 2]].x * scale_local.x, mesh->vertex_positions[indices[i + 2]].y * scale_local.y, mesh->vertex_positions[indices[i + 2]].z * scale_local.z);
trianglelist.push_back(triangle);
}
}
MeshShapeSettings settings(trianglelist);
settings.SetEmbedded();
shape_result = settings.Create();
}
else
{
wi::backlog::post("CreateRigidBodyShape failed: triangle mesh physics requested, but no MeshComponent provided!", wi::backlog::LogLevel::Error);
return;
}
break;
}
if (!shape_result.IsValid())
{
wilog_error("CreateRigidBodyShape failed, shape_result: %s", shape_result.GetError().c_str());
return;
}
RigidBody& physicsobject = GetRigidBody(physicscomponent);
physicsobject.shape = shape_result.Get();
}
bool IsEnabled() { return ENABLED; }
void SetEnabled(bool value) { ENABLED = value; }
bool IsSimulationEnabled() { return ENABLED && SIMULATION_ENABLED; }
void SetSimulationEnabled(bool value) { SIMULATION_ENABLED = value; }
bool IsInterpolationEnabled() { return INTERPOLATION; }
void SetInterpolationEnabled(bool value) { INTERPOLATION = value; }
bool IsDebugDrawEnabled() { return DEBUGDRAW_ENABLED; }
void SetDebugDrawEnabled(bool value) { DEBUGDRAW_ENABLED = value; }
int GetAccuracy() { return ACCURACY; }
void SetAccuracy(int value) { ACCURACY = value; }
float GetFrameRate() { return 1.0f / TIMESTEP; }
void SetFrameRate(float value) { TIMESTEP = 1.0f / value; }
void RunPhysicsUpdateSystem(
wi::jobsystem::context& ctx,
wi::scene::Scene& scene,
float dt
)
{
if (!IsEnabled() || dt <= 0)
return;
wi::jobsystem::Wait(ctx);
auto range = wi::profiler::BeginRangeCPU("Physics");
PhysicsScene& physics_scene = GetPhysicsScene(scene);
physics_scene.physics_system.SetGravity(cast(scene.weather.gravity));
// System will register rigidbodies to objects:
wi::jobsystem::Dispatch(ctx, (uint32_t)scene.rigidbodies.GetCount(), 64, [&scene, &physics_scene](wi::jobsystem::JobArgs args) {
RigidBodyPhysicsComponent& physicscomponent = scene.rigidbodies[args.jobIndex];
Entity entity = scene.rigidbodies.GetEntity(args.jobIndex);
if (physicscomponent.physicsobject == nullptr && scene.transforms.Contains(entity))
{
TransformComponent* transform = scene.transforms.GetComponent(entity);
if (transform == nullptr)
return;
const ObjectComponent* object = scene.objects.GetComponent(entity);
const MeshComponent* mesh = nullptr;
if (object != nullptr)
{
mesh = scene.meshes.GetComponent(object->meshID);
}
AddRigidBody(scene, entity, physicscomponent, *transform, mesh);
}
if (physicscomponent.physicsobject != nullptr)
{
RigidBody& physicsobject = GetRigidBody(physicscomponent);
if (physicsobject.bodyID.IsInvalid())
return;
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterface(); // locking, these jobs can be adding bodies
body_interface.SetFriction(physicsobject.bodyID, physicscomponent.friction);
body_interface.SetRestitution(physicsobject.bodyID, physicscomponent.restitution);
const EMotionType prevMotionType = body_interface.GetMotionType(physicsobject.bodyID);
const EMotionType currentMotionType = physicscomponent.mass == 0 ? EMotionType::Static : (physicscomponent.IsKinematic() ? EMotionType::Kinematic : EMotionType::Dynamic);
if (prevMotionType != currentMotionType)
{
// Changed motion type:
body_interface.SetMotionType(physicsobject.bodyID, currentMotionType, EActivation::Activate);
if (currentMotionType == EMotionType::Dynamic)
{
// Changed to dynamic, remember attachment matrices at this point:
scene.locker.lock();
XMMATRIX parentMatrix = scene.ComputeParentMatrixRecursive(entity);
scene.locker.unlock();
XMStoreFloat4x4(&physicsobject.parentMatrix, parentMatrix);
XMStoreFloat4x4(&physicsobject.parentMatrixInverse, XMMatrixInverse(nullptr, parentMatrix));
}
}
TransformComponent* transform = scene.transforms.GetComponent(entity);
if (transform == nullptr)
return;
if (currentMotionType == EMotionType::Dynamic)
{
// Detaching object manually before the physics simulation:
transform->MatrixTransform(physicsobject.parentMatrix);
}
if (physics_scene.activate_all_rigid_bodies)
{
body_interface.ActivateBody(physicsobject.bodyID);
}
const bool is_active = body_interface.IsActive(physicsobject.bodyID);
if (currentMotionType == EMotionType::Dynamic && is_active)
{
// Apply effects on dynamics if needed:
if (scene.weather.IsOceanEnabled())
{
const Vec3 com = body_interface.GetCenterOfMassPosition(physicsobject.bodyID);
const Vec3 surface_position = cast(scene.GetOceanPosAt(cast(com)));
const float diff = com.GetY() - surface_position.GetY();
if (diff < 0)
{
const Vec3 p2 = cast(scene.GetOceanPosAt(cast(com + Vec3(0, 0, 0.1f))));
const Vec3 p3 = cast(scene.GetOceanPosAt(cast(com + Vec3(0.1f, 0, 0))));
const Vec3 surface_normal = Vec3(p2 - surface_position).Cross(Vec3(p3 - surface_position)).Normalized();
body_interface.ApplyBuoyancyImpulse(
physicsobject.bodyID,
surface_position,
surface_normal,
physicscomponent.buoyancy,
0.8f,
0.6f,
Vec3::sZero(),
physics_scene.physics_system.GetGravity(),
scene.dt
);
if (!physicsobject.was_underwater)
{
physicsobject.was_underwater = true;
scene.PutWaterRipple(cast(surface_position));
}
}
else
{
physicsobject.was_underwater = false;
}
}
}
const Vec3 position = cast(transform->GetPosition());
const Quat rotation = cast(transform->GetRotation());
Mat44 m = Mat44::sTranslation(position) * Mat44::sRotation(rotation);
m = m * physicsobject.additionalTransform;
if (IsSimulationEnabled())
{
// Feedback system transform to kinematic and static physics objects:
if (currentMotionType == EMotionType::Kinematic)
{
body_interface.MoveKinematic(
physicsobject.bodyID,
m.GetTranslation(),
m.GetQuaternion().Normalized(),
physics_scene.GetKinematicDT(scene.dt)
);
}
else if (currentMotionType == EMotionType::Static || !is_active)
{
body_interface.SetPositionAndRotation(
physicsobject.bodyID,
m.GetTranslation(),
m.GetQuaternion().Normalized(),
EActivation::DontActivate
);
}
}
else
{
// Simulation is disabled, update physics state immediately:
physicsobject.prev_position = position;
physicsobject.prev_rotation = rotation;
body_interface.SetPositionAndRotation(
physicsobject.bodyID,
m.GetTranslation(),
m.GetQuaternion().Normalized(),
EActivation::Activate
);
}
}
});
// System will register softbodies to meshes and update physics engine state:
wi::jobsystem::Dispatch(ctx, (uint32_t)scene.softbodies.GetCount(), 1, [&scene, &physics_scene](wi::jobsystem::JobArgs args) {
SoftBodyPhysicsComponent& physicscomponent = scene.softbodies[args.jobIndex];
Entity entity = scene.softbodies.GetEntity(args.jobIndex);
if (!scene.meshes.Contains(entity))
return;
MeshComponent* mesh = scene.meshes.GetComponent(entity);
if (mesh == nullptr)
return;
const ArmatureComponent* armature = mesh->IsSkinned() ? scene.armatures.GetComponent(mesh->armatureID) : nullptr;
mesh->SetDynamic(true);
if (physicscomponent._flags & SoftBodyPhysicsComponent::SAFE_TO_REGISTER && physicscomponent.physicsobject == nullptr)
{
AddSoftBody(scene, entity, physicscomponent, *mesh);
}
if (physicscomponent.physicsobject != nullptr)
{
SoftBody& physicsobject = GetSoftBody(physicscomponent);
if (physicsobject.bodyID.IsInvalid())
return;
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterface(); // locking, these jobs can be adding bodies
body_interface.SetFriction(physicsobject.bodyID, physicscomponent.friction);
body_interface.SetRestitution(physicsobject.bodyID, physicscomponent.restitution);
if (IsSimulationEnabled() && physicscomponent.IsWindEnabled())
{
// Add wind:
const Vec3 wind = cast(scene.weather.windDirection);
if (!wind.IsNearZero())
{
body_interface.AddForce(physicsobject.bodyID, wind, EActivation::Activate);
}
}
// This is different from rigid bodies, because soft body is a per mesh component (no TransformComponent). World matrix is propagated down from single mesh instance (ObjectUpdateSystem).
XMMATRIX worldMatrix = XMLoadFloat4x4(&physicscomponent.worldMatrix);
BodyLockRead lock(physics_scene.physics_system.GetBodyLockInterfaceNoLock(), physicsobject.bodyID);
if (!lock.Succeeded())
return;
const Body& body = lock.GetBody();
SoftBodyMotionProperties* motion = (SoftBodyMotionProperties*)body.GetMotionProperties();
// System controls zero weight soft body nodes:
for (size_t ind = 0; ind < physicscomponent.physicsToGraphicsVertexMapping.size(); ++ind)
{
uint32_t graphicsInd = physicscomponent.physicsToGraphicsVertexMapping[ind];
float weight = physicscomponent.weights[graphicsInd];
if (weight == 0)
{
XMFLOAT3 position = mesh->vertex_positions[graphicsInd];
XMVECTOR P = armature == nullptr ? XMLoadFloat3(&position) : wi::scene::SkinVertex(*mesh, *armature, graphicsInd);
P = XMVector3Transform(P, worldMatrix);
XMStoreFloat3(&position, P);
SoftBodyMotionProperties::Vertex& node = motion->GetVertex((uint)ind);
node.mPosition = cast(position);
}
}
}
});
// Ragdoll management:
wi::jobsystem::Dispatch(ctx, (uint32_t)scene.humanoids.GetCount(), 1, [&scene, &physics_scene](wi::jobsystem::JobArgs args) {
HumanoidComponent& humanoid = scene.humanoids[args.jobIndex];
Entity humanoidEntity = scene.humanoids.GetEntity(args.jobIndex);
float scale = 1;
if (scene.transforms.Contains(humanoidEntity))
{
scale = scene.transforms.GetComponent(humanoidEntity)->scale_local.x;
}
if (humanoid.ragdoll != nullptr)
{
Ragdoll& ragdoll = *(Ragdoll*)humanoid.ragdoll.get();
if (!wi::math::float_equal(ragdoll.scale, scale))
{
humanoid.SetRagdollPhysicsEnabled(false); // while scaling ragdoll, it will be kinematic
ragdoll.Deactivate(scene); // recreate attached skeleton hierarchy structure
humanoid.ragdoll = {}; // delete ragdoll if scale changed, it will be recreated
}
}
if (humanoid.ragdoll == nullptr)
{
humanoid.ragdoll = std::make_shared<Ragdoll>(scene, humanoid, humanoidEntity, scale);
}
Ragdoll& ragdoll = *(Ragdoll*)humanoid.ragdoll.get();
if (humanoid.IsRagdollPhysicsEnabled())
{
ragdoll.Activate(scene, humanoidEntity);
}
if (IsSimulationEnabled())
{
if (humanoid.IsRagdollPhysicsEnabled())
{
// Apply effects on dynamics if needed:
if (scene.weather.IsOceanEnabled())
{
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterface(); // locking, these jobs can be adding bodies
static const Ragdoll::BODYPART floating_bodyparts[] = {
Ragdoll::BODYPART_PELVIS,
Ragdoll::BODYPART_SPINE,
};
for (auto& bodypart : floating_bodyparts)
{
auto& rb = ragdoll.rigidbodies[bodypart];
const Vec3 com = body_interface.GetCenterOfMassPosition(rb.bodyID);
const Vec3 surface_position = cast(scene.GetOceanPosAt(cast(com)));
const float diff = com.GetY() - surface_position.GetY();
if (diff < 0)
{
const Vec3 p2 = cast(scene.GetOceanPosAt(cast(com + Vec3(0, 0, 0.1f))));
const Vec3 p3 = cast(scene.GetOceanPosAt(cast(com + Vec3(0.1f, 0, 0))));
const Vec3 surface_normal = Vec3(p2 - surface_position).Cross(Vec3(p3 - surface_position)).Normalized();
body_interface.ApplyBuoyancyImpulse(
rb.bodyID,
surface_position,
surface_normal,
6.0f,
0.8f,
0.6f,
Vec3::sZero(),
physics_scene.physics_system.GetGravity(),
scene.dt
);
if (!rb.was_underwater)
{
rb.was_underwater = true;
scene.PutWaterRipple(cast(surface_position));
}
}
else
{
rb.was_underwater = false;
}
}
}
}
else
{
ragdoll.Deactivate(scene);
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterface(); // locking, these jobs can be adding bodies
for (auto& rb : ragdoll.rigidbodies)
{
TransformComponent* transform = scene.transforms.GetComponent(rb.entity);
if (transform == nullptr)
return;
const Vec3 position = cast(transform->GetPosition());
const Quat rotation = cast(transform->GetRotation());
Mat44 m = Mat44::sTranslation(position) * Mat44::sRotation(rotation);
m = m * rb.restBasisInverse;
m = m * rb.additionalTransform;
body_interface.MoveKinematic(
rb.bodyID,
m.GetTranslation(),
m.GetQuaternion().Normalized(),
physics_scene.GetKinematicDT(scene.dt)
);
}
}
}
else if(!humanoid.IsRagdollPhysicsEnabled())
{
// Simulation is disabled, update physics state immediately:
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterface(); // locking, these jobs can be adding bodies
for (auto& rb : ragdoll.rigidbodies)
{
TransformComponent* transform = scene.transforms.GetComponent(rb.entity);
if (transform == nullptr)
return;
const Vec3 position = cast(transform->GetPosition());
const Quat rotation = cast(transform->GetRotation());
Mat44 m = Mat44::sTranslation(position) * Mat44::sRotation(rotation);
m = m * rb.additionalTransform;
// Simulation is disabled, update physics state immediately:
rb.prev_position = position;
rb.prev_rotation = rotation;
body_interface.SetPositionAndRotation(
rb.bodyID,
m.GetTranslation(),
m.GetQuaternion().Normalized(),
EActivation::Activate
);
}
}
});
wi::jobsystem::Wait(ctx);
physics_scene.activate_all_rigid_bodies = false;
// Perform internal simulation step:
bool simulation_happened = false;
if (IsSimulationEnabled())
{
//static TempAllocatorImpl temp_allocator(10 * 1024 * 1024);
static TempAllocatorMalloc temp_allocator; // 10-100 MB was not enough for large simulation, I don't want to reserve more memory up front
static JobSystemThreadPool job_system(cMaxPhysicsJobs, cMaxPhysicsBarriers, thread::hardware_concurrency() - 1);
physics_scene.accumulator += dt;
physics_scene.accumulator = clamp(physics_scene.accumulator, 0.0f, TIMESTEP * ACCURACY);
while (physics_scene.accumulator >= TIMESTEP)
{
const float next_accumulator = physics_scene.accumulator - TIMESTEP;
if (IsInterpolationEnabled() && next_accumulator < TIMESTEP)
{
// On the last step, save previous locations, this is only needed for interpolation:
// We don't only save it for dynamic objects that will be interpolated, because on the next frame maybe simulation doesn't run
// but object types can change!
wi::jobsystem::Dispatch(ctx, (uint32_t)scene.rigidbodies.GetCount(), 64, [&scene, &physics_scene](wi::jobsystem::JobArgs args) {
RigidBodyPhysicsComponent& physicscomponent = scene.rigidbodies[args.jobIndex];
if (physicscomponent.physicsobject == nullptr)
return;
RigidBody& rb = GetRigidBody(physicscomponent);
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
Mat44 mat = body_interface.GetWorldTransform(rb.bodyID);
mat = mat * rb.additionalTransformInverse;
rb.prev_position = mat.GetTranslation();
rb.prev_rotation = mat.GetQuaternion().Normalized();
});
wi::jobsystem::Dispatch(ctx, (uint32_t)scene.humanoids.GetCount(), 1, [&scene, &physics_scene](wi::jobsystem::JobArgs args) {
HumanoidComponent& humanoid = scene.humanoids[args.jobIndex];
if (humanoid.ragdoll == nullptr)
return;
Ragdoll& ragdoll = *(Ragdoll*)humanoid.ragdoll.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
for (auto& rb : ragdoll.rigidbodies)
{
TransformComponent* transform = scene.transforms.GetComponent(rb.entity);
if (transform == nullptr)
continue;
Mat44 mat = body_interface.GetWorldTransform(rb.bodyID);
mat = mat * rb.additionalTransformInverse;
mat = mat * rb.restBasis;
rb.prev_position = mat.GetTranslation();
rb.prev_rotation = mat.GetQuaternion().Normalized();
}
});
wi::jobsystem::Wait(ctx);
}
simulation_happened = true;
physics_scene.physics_system.Update(TIMESTEP, 1, &temp_allocator, &job_system);
physics_scene.accumulator = next_accumulator;
}
physics_scene.alpha = physics_scene.accumulator / TIMESTEP;
}
// Feedback physics objects to system:
wi::jobsystem::Dispatch(ctx, (uint32_t)scene.rigidbodies.GetCount(), 64, [&scene, &physics_scene](wi::jobsystem::JobArgs args) {
RigidBodyPhysicsComponent& physicscomponent = scene.rigidbodies[args.jobIndex];
if (physicscomponent.physicsobject == nullptr)
return;
RigidBody& physicsobject = GetRigidBody(physicscomponent);
if (physicsobject.bodyID.IsInvalid())
return;
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
if (body_interface.GetMotionType(physicsobject.bodyID) != EMotionType::Dynamic)
return;
Entity entity = scene.rigidbodies.GetEntity(args.jobIndex);
TransformComponent* transform = scene.transforms.GetComponent(entity);
if (transform == nullptr)
return;
Mat44 mat = body_interface.GetWorldTransform(physicsobject.bodyID);
mat = mat * physicsobject.additionalTransformInverse;
Vec3 position = mat.GetTranslation();
Quat rotation = mat.GetQuaternion().Normalized();
if (IsInterpolationEnabled())
{
position = position * physics_scene.alpha + physicsobject.prev_position * (1 - physics_scene.alpha);
rotation = physicsobject.prev_rotation.SLERP(rotation, physics_scene.alpha);
}
transform->translation_local = cast(position);
transform->rotation_local = cast(rotation);
// Back to local space of parent:
transform->MatrixTransform(physicsobject.parentMatrixInverse);
});
wi::jobsystem::Dispatch(ctx, (uint32_t)scene.softbodies.GetCount(), 1, [&scene, &physics_scene](wi::jobsystem::JobArgs args) {
SoftBodyPhysicsComponent& physicscomponent = scene.softbodies[args.jobIndex];
if (physicscomponent.physicsobject == nullptr)
return;
Entity entity = scene.softbodies.GetEntity(args.jobIndex);
SoftBody& physicsobject = GetSoftBody(physicscomponent);
if (physicsobject.bodyID.IsInvalid())
return;
BodyLockRead lock(physics_scene.physics_system.GetBodyLockInterfaceNoLock(), physicsobject.bodyID);
if (!lock.Succeeded())
return;
const Body& body = lock.GetBody();
MeshComponent* mesh = scene.meshes.GetComponent(entity);
if (mesh == nullptr)
return;
physicscomponent.aabb = wi::primitive::AABB();
const SoftBodyMotionProperties* motion = (const SoftBodyMotionProperties*)body.GetMotionProperties();
const Array<SoftBodyMotionProperties::Vertex>& soft_vertices = motion->GetVertices();
// Update bone matrices from physics vertices:
for (size_t i = 0; i < soft_vertices.size(); ++i)
{
XMFLOAT3 p0 = cast(soft_vertices[i].mPosition);
XMFLOAT3 p1 = cast(soft_vertices[physicsobject.physicsNeighbors[i].left].mPosition);
XMFLOAT3 p2 = cast(soft_vertices[physicsobject.physicsNeighbors[i].right].mPosition);
XMVECTOR P0 = XMLoadFloat3(&p0);
XMVECTOR P1 = XMLoadFloat3(&p1);
XMVECTOR P2 = XMLoadFloat3(&p2);
XMMATRIX W = GetOrientation(P0, P1, P2);
XMMATRIX B = XMLoadFloat4x4(&physicsobject.inverseBindMatrices[i]);
XMMATRIX M = B * W;
XMFLOAT4X4 boneData;
XMStoreFloat4x4(&boneData, M);
physicscomponent.boneData[i].Create(boneData);
#if 0
scene.locker.lock();
wi::renderer::DrawAxis(W, 0.05f, false);
scene.locker.unlock();
#endif
physicscomponent.aabb._min = wi::math::Min(physicscomponent.aabb._min, p0);
physicscomponent.aabb._max = wi::math::Max(physicscomponent.aabb._max, p0);
}
scene.skinningAllocator.fetch_add(uint32_t(physicscomponent.boneData.size() * sizeof(ShaderTransform)));
});
wi::jobsystem::Dispatch(ctx, (uint32_t)scene.humanoids.GetCount(), 1, [&scene, &physics_scene](wi::jobsystem::JobArgs args) {
HumanoidComponent& humanoid = scene.humanoids[args.jobIndex];
if (humanoid.ragdoll == nullptr)
return;
Ragdoll& ragdoll = *(Ragdoll*)humanoid.ragdoll.get();
if (humanoid.ragdoll_bodyparts.size() != Ragdoll::BODYPART_COUNT)
{
humanoid.ragdoll_bodyparts.resize(Ragdoll::BODYPART_COUNT);
}
humanoid.ragdoll_bounds = wi::primitive::AABB();
int caps = 0;
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
for (auto& rb : ragdoll.rigidbodies)
{
TransformComponent* transform = scene.transforms.GetComponent(rb.entity);
if (transform == nullptr)
continue;
Mat44 mat = body_interface.GetWorldTransform(rb.bodyID);
XMFLOAT4X4 capsulemat = cast(mat);
XMMATRIX M = XMLoadFloat4x4(&capsulemat);
auto& bp = humanoid.ragdoll_bodyparts[caps++];
bp.bone = rb.humanoid_bone;
bp.capsule.radius = rb.capsule.radius;
XMStoreFloat3(&bp.capsule.base, XMVector3Transform(XMLoadFloat3(&rb.capsule.base), M));
XMStoreFloat3(&bp.capsule.tip, XMVector3Transform(XMLoadFloat3(&rb.capsule.tip), M));
humanoid.ragdoll_bounds = wi::primitive::AABB::Merge(humanoid.ragdoll_bounds, bp.capsule.getAABB());
#if 0
scene.locker.lock();
wi::renderer::DrawCapsule(bp.capsule, XMFLOAT4(1, 1, 1, 1), false);
scene.locker.unlock();
#endif
if (humanoid.IsRagdollPhysicsEnabled())
{
mat = mat * rb.additionalTransformInverse;
mat = mat * rb.restBasis;
Vec3 position = mat.GetTranslation();
Quat rotation = mat.GetQuaternion().Normalized();
if (IsInterpolationEnabled())
{
position = position * physics_scene.alpha + rb.prev_position * (1 - physics_scene.alpha);
rotation = rb.prev_rotation.SLERP(rotation, physics_scene.alpha);
}
transform->translation_local = cast(position);
transform->rotation_local = cast(rotation);
transform->SetDirty();
}
}
#if 0
scene.locker.lock();
XMFLOAT4X4 mat;
XMStoreFloat4x4(&mat, humanoid.ragdoll_bounds.getAsBoxMatrix());
wi::renderer::DrawBox(mat, XMFLOAT4(1, 1, 1, 1), false);
scene.locker.unlock();
#endif
});
#ifdef JPH_DEBUG_RENDERER
if (IsDebugDrawEnabled())
{
class JoltDebugRenderer : public DebugRendererSimple
{
void DrawLine(RVec3Arg inFrom, RVec3Arg inTo, ColorArg inColor) override
{
wi::renderer::RenderableLine line;
line.start = XMFLOAT3(inFrom.GetX(), inFrom.GetY(), inFrom.GetZ());
line.end = XMFLOAT3(inTo.GetX(), inTo.GetY(), inTo.GetZ());
line.color_start = line.color_end = wi::Color(inColor.r, inColor.g, inColor.b, inColor.a);
wi::renderer::DrawLine(line);
}
void DrawTriangle(RVec3Arg inV1, RVec3Arg inV2, RVec3Arg inV3, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::Off) override
{
// Not needed if we only want to draw wireframes
}
void DrawText3D(RVec3Arg inPosition, const string_view& inString, ColorArg inColor = JPH::Color::sWhite, float inHeight = 0.5f) override
{
wi::renderer::DebugTextParams params;
params.position.x = inPosition.GetX();
params.position.y = inPosition.GetY();
params.position.z = inPosition.GetZ();
params.scaling = 0.6f;
params.flags |= wi::renderer::DebugTextParams::CAMERA_FACING;
params.flags |= wi::renderer::DebugTextParams::CAMERA_SCALING;
wi::renderer::DrawDebugText(inString.data(), params);
}
};
static JoltDebugRenderer debug_renderer;
BodyManager::DrawSettings settings;
settings.mDrawCenterOfMassTransform = false;
settings.mDrawShape = true;
settings.mDrawSoftBodyVertices = true;
settings.mDrawSoftBodyEdgeConstraints = true;
settings.mDrawShapeWireframe = true;
settings.mDrawShapeColor = BodyManager::EShapeColor::ShapeTypeColor;
physics_scene.physics_system.DrawBodies(settings, &debug_renderer);
physics_scene.physics_system.DrawConstraints(&debug_renderer);
}
#endif // JPH_DEBUG_RENDERER
wi::jobsystem::Wait(ctx);
wi::profiler::EndRange(range); // Physics
}
void SetLinearVelocity(
wi::scene::RigidBodyPhysicsComponent& physicscomponent,
const XMFLOAT3& velocity
)
{
if (physicscomponent.physicsobject != nullptr)
{
RigidBody& physicsobject = GetRigidBody(physicscomponent);
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
body_interface.SetLinearVelocity(physicsobject.bodyID, cast(velocity));
}
}
void SetAngularVelocity(
wi::scene::RigidBodyPhysicsComponent& physicscomponent,
const XMFLOAT3& velocity
)
{
if (physicscomponent.physicsobject != nullptr)
{
RigidBody& physicsobject = GetRigidBody(physicscomponent);
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
body_interface.SetAngularVelocity(physicsobject.bodyID, cast(velocity));
}
}
void ApplyForce(
wi::scene::RigidBodyPhysicsComponent& physicscomponent,
const XMFLOAT3& force
)
{
if (physicscomponent.physicsobject != nullptr)
{
RigidBody& physicsobject = GetRigidBody(physicscomponent);
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
body_interface.AddForce(physicsobject.bodyID, cast(force));
}
}
void ApplyForceAt(
wi::scene::RigidBodyPhysicsComponent& physicscomponent,
const XMFLOAT3& force,
const XMFLOAT3& at,
bool at_local
)
{
if (physicscomponent.physicsobject != nullptr)
{
RigidBody& physicsobject = GetRigidBody(physicscomponent);
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
Vec3 at_world = at_local ? body_interface.GetCenterOfMassTransform(physicsobject.bodyID).Inversed() * cast(at) : cast(at);
body_interface.AddForce(physicsobject.bodyID, cast(force), at_world);
}
}
void ApplyImpulse(
wi::scene::RigidBodyPhysicsComponent& physicscomponent,
const XMFLOAT3& impulse
)
{
if (physicscomponent.physicsobject != nullptr)
{
RigidBody& physicsobject = GetRigidBody(physicscomponent);
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
body_interface.AddImpulse(physicsobject.bodyID, cast(impulse));
}
}
void ApplyImpulse(
wi::scene::HumanoidComponent& humanoid,
wi::scene::HumanoidComponent::HumanoidBone bone,
const XMFLOAT3& impulse
)
{
if (humanoid.ragdoll == nullptr)
return;
Ragdoll::BODYPART bodypart = Ragdoll::BODYPART_COUNT;
switch (bone)
{
case HumanoidComponent::HumanoidBone::Hips:
bodypart = Ragdoll::BODYPART_PELVIS;
break;
case HumanoidComponent::HumanoidBone::Spine:
bodypart = Ragdoll::BODYPART_SPINE;
break;
case HumanoidComponent::HumanoidBone::Head:
case HumanoidComponent::HumanoidBone::Neck:
bodypart = Ragdoll::BODYPART_HEAD;
break;
case HumanoidComponent::HumanoidBone::RightUpperArm:
bodypart = Ragdoll::BODYPART_RIGHT_UPPER_ARM;
break;
case HumanoidComponent::HumanoidBone::RightLowerArm:
bodypart = Ragdoll::BODYPART_RIGHT_LOWER_ARM;
break;
case HumanoidComponent::HumanoidBone::LeftUpperArm:
bodypart = Ragdoll::BODYPART_LEFT_UPPER_ARM;
break;
case HumanoidComponent::HumanoidBone::LeftLowerArm:
bodypart = Ragdoll::BODYPART_LEFT_LOWER_ARM;
break;
case HumanoidComponent::HumanoidBone::RightUpperLeg:
bodypart = Ragdoll::BODYPART_RIGHT_UPPER_LEG;
break;
case HumanoidComponent::HumanoidBone::RightLowerLeg:
bodypart = Ragdoll::BODYPART_RIGHT_LOWER_LEG;
break;
case HumanoidComponent::HumanoidBone::LeftUpperLeg:
bodypart = Ragdoll::BODYPART_LEFT_UPPER_LEG;
break;
case HumanoidComponent::HumanoidBone::LeftLowerLeg:
bodypart = Ragdoll::BODYPART_LEFT_LOWER_LEG;
break;
}
if (bodypart == Ragdoll::BODYPART_COUNT)
return;
Ragdoll& ragdoll = *(Ragdoll*)humanoid.ragdoll.get();
if (ragdoll.rigidbodies[bodypart].bodyID.IsInvalid())
return;
RigidBody& physicsobject = ragdoll.rigidbodies[bodypart];
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
body_interface.SetMotionType(physicsobject.bodyID, EMotionType::Dynamic, EActivation::Activate);
body_interface.AddImpulse(physicsobject.bodyID, cast(impulse));
}
void ApplyImpulseAt(
wi::scene::RigidBodyPhysicsComponent& physicscomponent,
const XMFLOAT3& impulse,
const XMFLOAT3& at,
bool at_local
)
{
if (physicscomponent.physicsobject != nullptr)
{
RigidBody& physicsobject = GetRigidBody(physicscomponent);
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
Vec3 at_world = at_local ? body_interface.GetCenterOfMassTransform(physicsobject.bodyID) * cast(at) : cast(at);
body_interface.AddImpulse(physicsobject.bodyID, cast(impulse), at_world);
}
}
void ApplyImpulseAt(
wi::scene::HumanoidComponent& humanoid,
wi::scene::HumanoidComponent::HumanoidBone bone,
const XMFLOAT3& impulse,
const XMFLOAT3& at,
bool at_local
)
{
if (humanoid.ragdoll == nullptr)
return;
Ragdoll::BODYPART bodypart = Ragdoll::BODYPART_COUNT;
switch (bone)
{
case HumanoidComponent::HumanoidBone::Hips:
bodypart = Ragdoll::BODYPART_PELVIS;
break;
case HumanoidComponent::HumanoidBone::Spine:
bodypart = Ragdoll::BODYPART_SPINE;
break;
case HumanoidComponent::HumanoidBone::Head:
case HumanoidComponent::HumanoidBone::Neck:
bodypart = Ragdoll::BODYPART_HEAD;
break;
case HumanoidComponent::HumanoidBone::RightUpperArm:
bodypart = Ragdoll::BODYPART_RIGHT_UPPER_ARM;
break;
case HumanoidComponent::HumanoidBone::RightLowerArm:
bodypart = Ragdoll::BODYPART_RIGHT_LOWER_ARM;
break;
case HumanoidComponent::HumanoidBone::LeftUpperArm:
bodypart = Ragdoll::BODYPART_LEFT_UPPER_ARM;
break;
case HumanoidComponent::HumanoidBone::LeftLowerArm:
bodypart = Ragdoll::BODYPART_LEFT_LOWER_ARM;
break;
case HumanoidComponent::HumanoidBone::RightUpperLeg:
bodypart = Ragdoll::BODYPART_RIGHT_UPPER_LEG;
break;
case HumanoidComponent::HumanoidBone::RightLowerLeg:
bodypart = Ragdoll::BODYPART_RIGHT_LOWER_LEG;
break;
case HumanoidComponent::HumanoidBone::LeftUpperLeg:
bodypart = Ragdoll::BODYPART_LEFT_UPPER_LEG;
break;
case HumanoidComponent::HumanoidBone::LeftLowerLeg:
bodypart = Ragdoll::BODYPART_LEFT_LOWER_LEG;
break;
}
if (bodypart == Ragdoll::BODYPART_COUNT)
return;
Ragdoll& ragdoll = *(Ragdoll*)humanoid.ragdoll.get();
if (ragdoll.rigidbodies[bodypart].bodyID.IsInvalid())
return;
RigidBody& physicsobject = ragdoll.rigidbodies[bodypart];
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
Vec3 at_world = at_local ? body_interface.GetCenterOfMassTransform(physicsobject.bodyID) * cast(at) : cast(at);
body_interface.SetMotionType(physicsobject.bodyID, EMotionType::Dynamic, EActivation::Activate);
body_interface.AddImpulse(physicsobject.bodyID, cast(impulse), at_world);
}
void ApplyTorque(
wi::scene::RigidBodyPhysicsComponent& physicscomponent,
const XMFLOAT3& torque
)
{
if (physicscomponent.physicsobject != nullptr)
{
RigidBody& physicsobject = GetRigidBody(physicscomponent);
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
body_interface.AddTorque(physicsobject.bodyID, cast(torque), EActivation::Activate);
}
}
void SetActivationState(
wi::scene::RigidBodyPhysicsComponent& physicscomponent,
ActivationState state
)
{
RigidBody& physicsobject = GetRigidBody(physicscomponent);
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
switch (state)
{
case wi::physics::ActivationState::Active:
body_interface.ActivateBody(physicsobject.bodyID);
break;
case wi::physics::ActivationState::Inactive:
body_interface.DeactivateBody(physicsobject.bodyID);
break;
default:
break;
}
}
void SetActivationState(
wi::scene::SoftBodyPhysicsComponent& physicscomponent,
ActivationState state
)
{
SoftBody& physicsobject = GetSoftBody(physicscomponent);
PhysicsScene& physics_scene = *(PhysicsScene*)physicsobject.physics_scene.get();
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
switch (state)
{
case wi::physics::ActivationState::Active:
body_interface.ActivateBody(physicsobject.bodyID);
break;
case wi::physics::ActivationState::Inactive:
body_interface.DeactivateBody(physicsobject.bodyID);
break;
default:
break;
}
}
void ActivateAllRigidBodies(Scene& scene)
{
PhysicsScene& physics_scene = *(PhysicsScene*)scene.physics_scene.get();
physics_scene.activate_all_rigid_bodies = true;
}
XMFLOAT3 GetSoftBodyNodePosition(
wi::scene::SoftBodyPhysicsComponent& physicscomponent,
uint32_t physicsIndex
)
{
SoftBody& physicsobject = GetSoftBody(physicscomponent);
if (physicsobject.bodyID.IsInvalid() || physicsobject.physics_scene == nullptr)
return XMFLOAT3(0, 0, 0);
const PhysicsScene& physics_scene = *(const PhysicsScene*)physicsobject.physics_scene.get();
BodyLockRead lock(physics_scene.physics_system.GetBodyLockInterfaceNoLock(), physicsobject.bodyID);
if (!lock.Succeeded())
return XMFLOAT3(0, 0, 0);
const Body& body = lock.GetBody();
const SoftBodyMotionProperties* motion = (const SoftBodyMotionProperties*)body.GetMotionProperties();
const Array<SoftBodyMotionProperties::Vertex>& soft_vertices = motion->GetVertices();
return cast(soft_vertices[physicsIndex].mPosition);
}
template <class CollectorType>
class WickedClosestHitCollector : public JPH::ClosestHitCollisionCollector<CollectorType>
{
public:
const Scene* scene = nullptr;
const PhysicsScene* physics_scene = nullptr;
using ResultType = typename CollectorType::ResultType;
void AddHit(const ResultType& inResult) override
{
BodyLockRead lock(physics_scene->physics_system.GetBodyLockInterfaceNoLock(), inResult.mBodyID);
if (!lock.Succeeded())
return;
const Body& body = lock.GetBody();
const uint64_t userdata = body.GetUserData();
if (body.IsRigidBody())
{
const RigidBody* physicsobject = (RigidBody*)userdata;
if (physicsobject->humanoid_ragdoll_entity != INVALID_ENTITY)
{
const HumanoidComponent* humanoid = scene->humanoids.GetComponent(physicsobject->humanoid_ragdoll_entity);
if (humanoid != nullptr && humanoid->IsIntersectionDisabled())
return; // skip this from AddHit
}
}
ClosestHitCollisionCollector<CollectorType>::AddHit(inResult);
}
};
RayIntersectionResult Intersects(
const wi::scene::Scene& scene,
wi::primitive::Ray ray
)
{
RayIntersectionResult result;
if (scene.physics_scene == nullptr)
return result;
PhysicsScene& physics_scene = *(PhysicsScene*)scene.physics_scene.get();
const float tmin = clamp(ray.TMin, 0.0f, 1000000.0f);
const float tmax = clamp(ray.TMax, 0.0f, 1000000.0f);
const float range = tmax - tmin;
RRayCast inray{
cast(ray.origin),
cast(ray.direction).Normalized()
};
inray.mOrigin = inray.mOrigin + inray.mDirection * tmin;
inray.mDirection = inray.mDirection * range;
RayCastSettings settings;
settings.mBackFaceModeTriangles = EBackFaceMode::IgnoreBackFaces;
settings.mTreatConvexAsSolid = false;
WickedClosestHitCollector<CastRayCollector> collector;
collector.scene = &scene;
collector.physics_scene = &physics_scene;
physics_scene.physics_system.GetNarrowPhaseQuery().CastRay(inray, settings, collector);
if (!collector.HadHit())
return result;
if (collector.mHit.mBodyID.IsInvalid())
return result;
BodyLockRead lock(physics_scene.physics_system.GetBodyLockInterfaceNoLock(), collector.mHit.mBodyID);
if (!lock.Succeeded())
return result;
const Body& body = lock.GetBody();
const uint64_t userdata = body.GetUserData();
const Vec3 position = inray.GetPointOnRay(collector.mHit.mFraction);
const Vec3 position_local = body.GetCenterOfMassTransform().Inversed() * position;
const Vec3 normal = body.GetWorldSpaceSurfaceNormal(collector.mHit.mSubShapeID2, position);
if (body.IsRigidBody())
{
const RigidBody* physicsobject = (RigidBody*)userdata;
result.entity = physicsobject->entity;
result.position = cast(position);
result.position_local = cast(position_local);
result.normal = cast(normal);
result.physicsobject = &body;
result.humanoid_ragdoll_entity = physicsobject->humanoid_ragdoll_entity;
result.humanoid_bone = physicsobject->humanoid_bone;
}
else // soft body
{
const SoftBody* physicsobject = (SoftBody*)userdata;
result.entity = physicsobject->entity;
result.position = cast(position);
result.position_local = cast(position_local);
result.normal = cast(normal);
result.physicsobject = &body;
const SoftBodyShape* shape = (const SoftBodyShape*)body.GetShape();
result.softbody_triangleID = (int)shape->GetFaceIndex(collector.mHit.mSubShapeID2);
}
return result;
}
struct PickDragOperation_Jolt
{
std::shared_ptr<void> physics_scene;
Ref<TwoBodyConstraint> constraint;
float pick_distance = 0;
Body* bodyA = nullptr;
Body* bodyB = nullptr;
int softBodyVertex = -1;
Vec3 softBodyVertexOffset = Vec3::sZero();
float prevInvMass = 0;
~PickDragOperation_Jolt()
{
if (physics_scene == nullptr || bodyB == nullptr)
return;
PhysicsScene& physics_scene = *((PhysicsScene*)this->physics_scene.get());
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
if (bodyA != nullptr)
{
// Rigid body constraint removal
physics_scene.physics_system.RemoveConstraint(constraint);
body_interface.RemoveBody(bodyA->GetID());
body_interface.DestroyBody(bodyA->GetID());
}
if (bodyB->IsSoftBody() && softBodyVertex >= 0)
{
SoftBodyMotionProperties* motion = (SoftBodyMotionProperties*)bodyB->GetMotionProperties();
SoftBodyMotionProperties::Vertex& node = motion->GetVertex((uint)softBodyVertex);
node.mInvMass = prevInvMass;
}
}
};
void PickDrag(
const wi::scene::Scene& scene,
wi::primitive::Ray ray,
PickDragOperation& op,
ConstraintType constraint_type
)
{
if (scene.physics_scene == nullptr)
return;
PhysicsScene& physics_scene = *((PhysicsScene*)scene.physics_scene.get());
BodyInterface& body_interface = physics_scene.physics_system.GetBodyInterfaceNoLock();
if (op.IsValid())
{
// Continue dragging:
PickDragOperation_Jolt* internal_state = (PickDragOperation_Jolt*)op.internal_state.get();
const float dist = internal_state->pick_distance;
Vec3 pos = Vec3(ray.origin.x + ray.direction.x * dist, ray.origin.y + ray.direction.y * dist, ray.origin.z + ray.direction.z * dist);
if (internal_state->softBodyVertex >= 0)
{
// Soft body vertex:
SoftBodyMotionProperties* motion = (SoftBodyMotionProperties*)internal_state->bodyB->GetMotionProperties();
SoftBodyMotionProperties::Vertex& node = motion->GetVertex((uint)internal_state->softBodyVertex);
node.mPosition = pos + internal_state->softBodyVertexOffset;
}
else
{
// Rigid body constraint:
//body_interface.SetPosition(internal_state->bodyA->GetID(), pos, EActivation::Activate);
body_interface.MoveKinematic(internal_state->bodyA->GetID(), pos, Quat::sIdentity(), physics_scene.GetKinematicDT(scene.dt));
}
}
else
{
// Begin picking:
RayIntersectionResult result = Intersects(scene, ray);
if (!result.IsValid())
return;
Body* body = (Body*)result.physicsobject;
auto internal_state = std::make_shared<PickDragOperation_Jolt>();
internal_state->physics_scene = scene.physics_scene;
internal_state->pick_distance = wi::math::Distance(ray.origin, result.position);
internal_state->bodyB = body;
if (body->IsRigidBody())
{
Vec3 pos = cast(result.position);
internal_state->bodyA = body_interface.CreateBody(BodyCreationSettings(new SphereShape(0.01f), pos, Quat::sIdentity(), EMotionType::Kinematic, Layers::MOVING));
body_interface.AddBody(internal_state->bodyA->GetID(), EActivation::Activate);
if (constraint_type == ConstraintType::Fixed)
{
FixedConstraintSettings settings;
settings.SetEmbedded();
settings.mAutoDetectPoint = true;
internal_state->constraint = settings.Create(*internal_state->bodyA, *internal_state->bodyB);
}
else if (constraint_type == ConstraintType::Point)
{
DistanceConstraintSettings settings;
settings.SetEmbedded();
settings.mPoint1 = settings.mPoint2 = pos;
internal_state->constraint = settings.Create(*internal_state->bodyA, *internal_state->bodyB);
}
physics_scene.physics_system.AddConstraint(internal_state->constraint);
}
else if (body->IsSoftBody())
{
SoftBodyMotionProperties* motion = (SoftBodyMotionProperties*)body->GetMotionProperties();
internal_state->softBodyVertex = (int)motion->GetFace((uint)result.softbody_triangleID).mVertex[0];
SoftBodyVertex& vertex = motion->GetVertex((uint)internal_state->softBodyVertex);
internal_state->prevInvMass = vertex.mInvMass;
vertex.mInvMass = 0;
internal_state->softBodyVertexOffset = vertex.mPosition - cast(result.position);
}
op.internal_state = internal_state;
}
}
}