Ajout de Jolt Physics + 1ere version des factory entitecomposants - camera, transform, rigidbody, collider, renderer

This commit is contained in:
Tom Ray
2026-03-22 00:28:03 +01:00
parent 6695d46bcd
commit 48348936a8
1147 changed files with 214331 additions and 353 deletions

View File

@@ -0,0 +1,201 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Utils/ContactListenerImpl.h>
#include <Renderer/DebugRendererImp.h>
#include <Jolt/Physics/Body/Body.h>
#include <Jolt/Physics/Collision/CollideShape.h>
#include <Jolt/Core/QuickSort.h>
ValidateResult ContactListenerImpl::OnContactValidate(const Body &inBody1, const Body &inBody2, RVec3Arg inBaseOffset, const CollideShapeResult &inCollisionResult)
{
// Check ordering contract between body 1 and body 2
bool contract = inBody1.GetMotionType() >= inBody2.GetMotionType()
|| (inBody1.GetMotionType() == inBody2.GetMotionType() && inBody1.GetID() < inBody2.GetID());
if (!contract)
JPH_BREAKPOINT;
ValidateResult result;
if (mNext != nullptr)
result = mNext->OnContactValidate(inBody1, inBody2, inBaseOffset, inCollisionResult);
else
result = ContactListener::OnContactValidate(inBody1, inBody2, inBaseOffset, inCollisionResult);
RVec3 contact_point = inBaseOffset + inCollisionResult.mContactPointOn1;
DebugRenderer::sInstance->DrawArrow(contact_point, contact_point - inCollisionResult.mPenetrationAxis.NormalizedOr(Vec3::sZero()), Color::sBlue, 0.05f);
Trace("Validate %u and %u result %d", inBody1.GetID().GetIndex(), inBody2.GetID().GetIndex(), (int)result);
return result;
}
void ContactListenerImpl::OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
// Expect bodies to be sorted
if (!(inBody1.GetID() < inBody2.GetID()))
JPH_BREAKPOINT;
Trace("Contact added %u (%08x) and %u (%08x)", inBody1.GetID().GetIndex(), inManifold.mSubShapeID1.GetValue(), inBody2.GetID().GetIndex(), inManifold.mSubShapeID2.GetValue());
DebugRenderer::sInstance->DrawWirePolygon(RMat44::sTranslation(inManifold.mBaseOffset), inManifold.mRelativeContactPointsOn1, Color::sGreen, 0.05f);
DebugRenderer::sInstance->DrawWirePolygon(RMat44::sTranslation(inManifold.mBaseOffset), inManifold.mRelativeContactPointsOn2, Color::sGreen, 0.05f);
DebugRenderer::sInstance->DrawArrow(inManifold.GetWorldSpaceContactPointOn1(0), inManifold.GetWorldSpaceContactPointOn1(0) + inManifold.mWorldSpaceNormal, Color::sGreen, 0.05f);
// Insert new manifold into state map
{
lock_guard lock(mStateMutex);
SubShapeIDPair key(inBody1.GetID(), inManifold.mSubShapeID1, inBody2.GetID(), inManifold.mSubShapeID2);
if (mState.find(key) != mState.end())
JPH_BREAKPOINT; // Added contact that already existed
mState[key] = StatePair(inManifold.mBaseOffset, inManifold.mRelativeContactPointsOn1);
}
if (mNext != nullptr)
mNext->OnContactAdded(inBody1, inBody2, inManifold, ioSettings);
}
void ContactListenerImpl::OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings)
{
// Expect bodies to be sorted
if (!(inBody1.GetID() < inBody2.GetID()))
JPH_BREAKPOINT;
Trace("Contact persisted %u (%08x) and %u (%08x)", inBody1.GetID().GetIndex(), inManifold.mSubShapeID1.GetValue(), inBody2.GetID().GetIndex(), inManifold.mSubShapeID2.GetValue());
DebugRenderer::sInstance->DrawWirePolygon(RMat44::sTranslation(inManifold.mBaseOffset), inManifold.mRelativeContactPointsOn1, Color::sYellow, 0.05f);
DebugRenderer::sInstance->DrawWirePolygon(RMat44::sTranslation(inManifold.mBaseOffset), inManifold.mRelativeContactPointsOn2, Color::sYellow, 0.05f);
DebugRenderer::sInstance->DrawArrow(inManifold.GetWorldSpaceContactPointOn1(0), inManifold.GetWorldSpaceContactPointOn1(0) + inManifold.mWorldSpaceNormal, Color::sYellow, 0.05f);
// Update existing manifold in state map
{
lock_guard lock(mStateMutex);
SubShapeIDPair key(inBody1.GetID(), inManifold.mSubShapeID1, inBody2.GetID(), inManifold.mSubShapeID2);
StateMap::iterator i = mState.find(key);
if (i != mState.end())
i->second = StatePair(inManifold.mBaseOffset, inManifold.mRelativeContactPointsOn1);
else
JPH_BREAKPOINT; // Persisted contact that didn't exist
}
if (mNext != nullptr)
mNext->OnContactPersisted(inBody1, inBody2, inManifold, ioSettings);
}
void ContactListenerImpl::OnContactRemoved(const SubShapeIDPair &inSubShapePair)
{
// Expect bodies to be sorted
if (!(inSubShapePair.GetBody1ID() < inSubShapePair.GetBody2ID()))
JPH_BREAKPOINT;
Trace("Contact removed %u (%08x) and %u (%08x)", inSubShapePair.GetBody1ID().GetIndex(), inSubShapePair.GetSubShapeID1().GetValue(), inSubShapePair.GetBody2ID().GetIndex(), inSubShapePair.GetSubShapeID2().GetValue());
// Update existing manifold in state map
{
lock_guard lock(mStateMutex);
StateMap::iterator i = mState.find(inSubShapePair);
if (i != mState.end())
mState.erase(i);
else
JPH_BREAKPOINT; // Removed contact that didn't exist
}
if (mNext != nullptr)
mNext->OnContactRemoved(inSubShapePair);
}
void ContactListenerImpl::SaveState(StateRecorder &inStream) const
{
// Write length
uint32 length = uint32(mState.size());
inStream.Write(length);
// Get and sort keys
Array<SubShapeIDPair> keys;
for (const StateMap::value_type &kv : mState)
keys.push_back(kv.first);
QuickSort(keys.begin(), keys.end());
// Write key value pairs
for (const SubShapeIDPair &k : keys)
{
// Write key
inStream.Write(k);
// Write value
const StatePair &sp = mState.find(k)->second;
inStream.Write(sp.first);
inStream.Write(uint32(sp.second.size()));
inStream.WriteBytes(sp.second.data(), sp.second.size() * sizeof(Vec3));
}
}
void ContactListenerImpl::RestoreState(StateRecorder &inStream)
{
Trace("Restore Contact State");
// Read length
uint32 length;
if (inStream.IsValidating())
length = uint32(mState.size());
inStream.Read(length);
Array<SubShapeIDPair> keys;
// Clear the state and remember the old state for validation
StateMap old_state;
old_state.swap(mState);
// Prepopulate keys and values with current values if we're validating
if (inStream.IsValidating())
{
// Get and sort keys
for (const StateMap::value_type &kv : old_state)
keys.push_back(kv.first);
QuickSort(keys.begin(), keys.end());
}
// Ensure we have the correct size
keys.resize(length);
for (size_t i = 0; i < length; ++i)
{
// Read key
SubShapeIDPair key;
if (inStream.IsValidating())
key = keys[i];
inStream.Read(key);
StatePair sp;
if (inStream.IsValidating())
sp = old_state[key];
// Read offset
inStream.Read(sp.first);
// Read num contact points
uint32 num_contacts;
if (inStream.IsValidating())
num_contacts = uint32(old_state[key].second.size());
inStream.Read(num_contacts);
// Read contact points
sp.second.resize(num_contacts);
inStream.ReadBytes(sp.second.data(), num_contacts * sizeof(Vec3));
// Store the new value
mState[key] = sp;
}
}
void ContactListenerImpl::DrawState()
{
Trace("Draw Contact State");
lock_guard lock(mStateMutex);
for (const StateMap::value_type &kv : mState)
for (Vec3 v : kv.second.second)
DebugRenderer::sInstance->DrawWireSphere(kv.second.first + v, 0.05f, Color::sRed, 1);
}

View File

@@ -0,0 +1,40 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Physics/Collision/ContactListener.h>
#include <Jolt/Physics/StateRecorder.h>
#include <Jolt/Core/Mutex.h>
#include <Jolt/Core/UnorderedMap.h>
// Tests the contact listener callbacks
class ContactListenerImpl : public ContactListener
{
public:
// See: ContactListener
virtual ValidateResult OnContactValidate(const Body &inBody1, const Body &inBody2, RVec3Arg inBaseOffset, const CollideShapeResult &inCollisionResult) override;
virtual void OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
virtual void OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override;
virtual void OnContactRemoved(const SubShapeIDPair &inSubShapePair) override;
// Saving / restoring state for replay
void SaveState(StateRecorder &inStream) const;
void RestoreState(StateRecorder &inStream);
// Draw the current contact state
void DrawState();
// Ability to defer to the next contact listener after this one handles the callback
void SetNextListener(ContactListener *inListener) { mNext = inListener; }
private:
// Map that keeps track of the current state of contacts based on the contact listener callbacks
using StatePair = pair<RVec3, ContactPoints>;
using StateMap = UnorderedMap<SubShapeIDPair, StatePair>;
Mutex mStateMutex;
StateMap mState;
ContactListener * mNext = nullptr;
};

View File

@@ -0,0 +1,55 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2022 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Renderer/DebugRendererImp.h>
// This file contains debug renderer functions that take single precision arguments for tests that do not need to deal with large worlds.
// They're split off so that we don't accidentally call single precision versions.
inline void DrawLineSP(DebugRenderer *inRenderer, Vec3Arg inFrom, Vec3Arg inTo, Color inColor)
{
inRenderer->DrawLine(RVec3(inFrom), RVec3(inTo), inColor);
}
inline void DrawMarkerSP(DebugRenderer *inRenderer, Vec3Arg inPosition, ColorArg inColor, float inSize)
{
inRenderer->DrawMarker(RVec3(inPosition), inColor, inSize);
}
inline void DrawArrowSP(DebugRenderer *inRenderer, Vec3Arg inFrom, Vec3Arg inTo, ColorArg inColor, float inSize)
{
inRenderer->DrawArrow(RVec3(inFrom), RVec3(inTo), inColor, inSize);
}
inline void DrawTriangleSP(DebugRenderer *inRenderer, Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3, ColorArg inColor)
{
inRenderer->DrawTriangle(RVec3(inV1), RVec3(inV2), RVec3(inV3), inColor);
}
inline void DrawWireBoxSP(DebugRenderer *inRenderer, Mat44Arg inMatrix, const AABox &inBox, ColorArg inColor)
{
inRenderer->DrawWireBox(RMat44(inMatrix), inBox, inColor);
}
inline void DrawBoxSP(DebugRenderer *inRenderer, Mat44Arg inMatrix, const AABox &inBox, ColorArg inColor, DebugRenderer::ECastShadow inCastShadow = DebugRenderer::ECastShadow::On, DebugRenderer::EDrawMode inDrawMode = DebugRenderer::EDrawMode::Solid)
{
inRenderer->DrawBox(RMat44(inMatrix), inBox, inColor, inCastShadow, inDrawMode);
}
inline void DrawWireSphereSP(DebugRenderer *inRenderer, Vec3Arg inCenter, float inRadius, ColorArg inColor, int inLevel = 3)
{
inRenderer->DrawWireSphere(RVec3(inCenter), inRadius, inColor, inLevel);
}
inline void DrawSphereSP(DebugRenderer *inRenderer, Vec3Arg inCenter, float inRadius, ColorArg inColor, DebugRenderer::ECastShadow inCastShadow = DebugRenderer::ECastShadow::On, DebugRenderer::EDrawMode inDrawMode = DebugRenderer::EDrawMode::Solid)
{
inRenderer->DrawSphere(RVec3(inCenter), inRadius, inColor, inCastShadow, inDrawMode);
}
inline void DrawText3DSP(DebugRenderer *inRenderer, Vec3Arg inPosition, const string_view &inString, ColorArg inColor = Color::sWhite, float inHeight = 0.5f)
{
inRenderer->DrawText3D(RVec3(inPosition), inString, inColor, inHeight);
}

View File

@@ -0,0 +1,312 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Utils/RagdollLoader.h>
#include <Jolt/Physics/Ragdoll/Ragdoll.h>
#include <Jolt/Physics/Constraints/PointConstraint.h>
#include <Jolt/Physics/Constraints/FixedConstraint.h>
#include <Jolt/Physics/Constraints/HingeConstraint.h>
#include <Jolt/Physics/Constraints/SliderConstraint.h>
#include <Jolt/Physics/Constraints/ConeConstraint.h>
#include <Jolt/Physics/Constraints/SwingTwistConstraint.h>
#include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
#include <Jolt/ObjectStream/ObjectStreamIn.h>
#include <Jolt/ObjectStream/ObjectStreamOut.h>
#include <Layers.h>
#include <Utils/Log.h>
#include <Utils/AssetStream.h>
#ifdef JPH_OBJECT_STREAM
RagdollSettings *RagdollLoader::sLoad(const char *inFileName, EMotionType inMotionType, EConstraintOverride inConstraintOverride)
{
// Read the ragdoll
RagdollSettings *ragdoll = nullptr;
AssetStream stream(inFileName, std::ios::in);
if (!ObjectStreamIn::sReadObject(stream.Get(), ragdoll))
FatalError("Unable to read ragdoll");
for (RagdollSettings::Part &p : ragdoll->mParts)
{
// Update motion type
p.mMotionType = inMotionType;
// Override layer
p.mObjectLayer = Layers::MOVING;
// Create new constraint
Ref<SwingTwistConstraintSettings> original = DynamicCast<SwingTwistConstraintSettings>(p.mToParent);
if (original != nullptr)
switch (inConstraintOverride)
{
case EConstraintOverride::TypeFixed:
{
FixedConstraintSettings *settings = new FixedConstraintSettings();
settings->mPoint1 = settings->mPoint2 = original->mPosition1;
p.mToParent = settings;
break;
}
case EConstraintOverride::TypePoint:
{
PointConstraintSettings *settings = new PointConstraintSettings();
settings->mPoint1 = settings->mPoint2 = original->mPosition1;
p.mToParent = settings;
break;
}
case EConstraintOverride::TypeHinge:
{
HingeConstraintSettings *settings = new HingeConstraintSettings();
settings->mPoint1 = original->mPosition1;
settings->mHingeAxis1 = original->mPlaneAxis1;
settings->mNormalAxis1 = original->mTwistAxis1;
settings->mPoint2 = original->mPosition2;
settings->mHingeAxis2 = original->mPlaneAxis2;
settings->mNormalAxis2 = original->mTwistAxis2;
settings->mLimitsMin = -original->mNormalHalfConeAngle;
settings->mLimitsMax = original->mNormalHalfConeAngle;
settings->mMaxFrictionTorque = original->mMaxFrictionTorque;
settings->mMotorSettings = original->mSwingMotorSettings;
p.mToParent = settings;
break;
}
case EConstraintOverride::TypeSlider:
{
SliderConstraintSettings *settings = new SliderConstraintSettings();
settings->mPoint1 = settings->mPoint2 = original->mPosition1;
settings->mSliderAxis1 = settings->mSliderAxis2 = original->mTwistAxis1;
settings->mNormalAxis1 = settings->mNormalAxis2 = original->mTwistAxis1.GetNormalizedPerpendicular();
settings->mLimitsMin = -1.0f;
settings->mLimitsMax = 1.0f;
settings->mMaxFrictionForce = original->mMaxFrictionTorque;
settings->mMotorSettings = original->mSwingMotorSettings;
p.mToParent = settings;
break;
}
case EConstraintOverride::TypeCone:
{
ConeConstraintSettings *settings = new ConeConstraintSettings();
settings->mPoint1 = original->mPosition1;
settings->mTwistAxis1 = original->mTwistAxis1;
settings->mPoint2 = original->mPosition2;
settings->mTwistAxis2 = original->mTwistAxis2;
settings->mHalfConeAngle = original->mNormalHalfConeAngle;
p.mToParent = settings;
break;
}
case EConstraintOverride::TypeRagdoll:
break;
}
}
// Initialize the skeleton
ragdoll->GetSkeleton()->CalculateParentJointIndices();
// Stabilize the constraints of the ragdoll
ragdoll->Stabilize();
// Optional: Calculate constraint priorities to give more priority to the root
ragdoll->CalculateConstraintPriorities();
// Calculate body <-> constraint map
ragdoll->CalculateBodyIndexToConstraintIndex();
ragdoll->CalculateConstraintIndexToBodyIdxPair();
return ragdoll;
}
#endif // JPH_OBJECT_STREAM
RagdollSettings *RagdollLoader::sCreate()
{
// Create skeleton
Ref<Skeleton> skeleton = new Skeleton;
uint lower_body = skeleton->AddJoint("LowerBody");
uint mid_body = skeleton->AddJoint("MidBody", lower_body);
uint upper_body = skeleton->AddJoint("UpperBody", mid_body);
/*uint head =*/ skeleton->AddJoint("Head", upper_body);
uint upper_arm_l = skeleton->AddJoint("UpperArmL", upper_body);
uint upper_arm_r = skeleton->AddJoint("UpperArmR", upper_body);
/*uint lower_arm_l =*/ skeleton->AddJoint("LowerArmL", upper_arm_l);
/*uint lower_arm_r =*/ skeleton->AddJoint("LowerArmR", upper_arm_r);
uint upper_leg_l = skeleton->AddJoint("UpperLegL", lower_body);
uint upper_leg_r = skeleton->AddJoint("UpperLegR", lower_body);
/*uint lower_leg_l =*/ skeleton->AddJoint("LowerLegL", upper_leg_l);
/*uint lower_leg_r =*/ skeleton->AddJoint("LowerLegR", upper_leg_r);
// Create shapes for limbs
Ref<Shape> shapes[] = {
new CapsuleShape(0.15f, 0.10f), // Lower Body
new CapsuleShape(0.15f, 0.10f), // Mid Body
new CapsuleShape(0.15f, 0.10f), // Upper Body
new CapsuleShape(0.075f, 0.10f), // Head
new CapsuleShape(0.15f, 0.06f), // Upper Arm L
new CapsuleShape(0.15f, 0.06f), // Upper Arm R
new CapsuleShape(0.15f, 0.05f), // Lower Arm L
new CapsuleShape(0.15f, 0.05f), // Lower Arm R
new CapsuleShape(0.2f, 0.075f), // Upper Leg L
new CapsuleShape(0.2f, 0.075f), // Upper Leg R
new CapsuleShape(0.2f, 0.06f), // Lower Leg L
new CapsuleShape(0.2f, 0.06f), // Lower Leg R
};
// Positions of body parts in world space
RVec3 positions[] = {
RVec3(0, 1.15f, 0), // Lower Body
RVec3(0, 1.35f, 0), // Mid Body
RVec3(0, 1.55f, 0), // Upper Body
RVec3(0, 1.825f, 0), // Head
RVec3(-0.425f, 1.55f, 0), // Upper Arm L
RVec3(0.425f, 1.55f, 0), // Upper Arm R
RVec3(-0.8f, 1.55f, 0), // Lower Arm L
RVec3(0.8f, 1.55f, 0), // Lower Arm R
RVec3(-0.15f, 0.8f, 0), // Upper Leg L
RVec3(0.15f, 0.8f, 0), // Upper Leg R
RVec3(-0.15f, 0.3f, 0), // Lower Leg L
RVec3(0.15f, 0.3f, 0), // Lower Leg R
};
// Rotations of body parts in world space
Quat rotations[] = {
Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), // Lower Body
Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), // Mid Body
Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), // Upper Body
Quat::sIdentity(), // Head
Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), // Upper Arm L
Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), // Upper Arm R
Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), // Lower Arm L
Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), // Lower Arm R
Quat::sIdentity(), // Upper Leg L
Quat::sIdentity(), // Upper Leg R
Quat::sIdentity(), // Lower Leg L
Quat::sIdentity() // Lower Leg R
};
// World space constraint positions
RVec3 constraint_positions[] = {
RVec3::sZero(), // Lower Body (unused, there's no parent)
RVec3(0, 1.25f, 0), // Mid Body
RVec3(0, 1.45f, 0), // Upper Body
RVec3(0, 1.65f, 0), // Head
RVec3(-0.225f, 1.55f, 0), // Upper Arm L
RVec3(0.225f, 1.55f, 0), // Upper Arm R
RVec3(-0.65f, 1.55f, 0), // Lower Arm L
RVec3(0.65f, 1.55f, 0), // Lower Arm R
RVec3(-0.15f, 1.05f, 0), // Upper Leg L
RVec3(0.15f, 1.05f, 0), // Upper Leg R
RVec3(-0.15f, 0.55f, 0), // Lower Leg L
RVec3(0.15f, 0.55f, 0), // Lower Leg R
};
// World space twist axis directions
Vec3 twist_axis[] = {
Vec3::sZero(), // Lower Body (unused, there's no parent)
Vec3::sAxisY(), // Mid Body
Vec3::sAxisY(), // Upper Body
Vec3::sAxisY(), // Head
-Vec3::sAxisX(), // Upper Arm L
Vec3::sAxisX(), // Upper Arm R
-Vec3::sAxisX(), // Lower Arm L
Vec3::sAxisX(), // Lower Arm R
-Vec3::sAxisY(), // Upper Leg L
-Vec3::sAxisY(), // Upper Leg R
-Vec3::sAxisY(), // Lower Leg L
-Vec3::sAxisY(), // Lower Leg R
};
// Constraint limits
float twist_angle[] = {
0.0f, // Lower Body (unused, there's no parent)
5.0f, // Mid Body
5.0f, // Upper Body
90.0f, // Head
45.0f, // Upper Arm L
45.0f, // Upper Arm R
45.0f, // Lower Arm L
45.0f, // Lower Arm R
45.0f, // Upper Leg L
45.0f, // Upper Leg R
45.0f, // Lower Leg L
45.0f, // Lower Leg R
};
float normal_angle[] = {
0.0f, // Lower Body (unused, there's no parent)
10.0f, // Mid Body
10.0f, // Upper Body
45.0f, // Head
90.0f, // Upper Arm L
90.0f, // Upper Arm R
0.0f, // Lower Arm L
0.0f, // Lower Arm R
45.0f, // Upper Leg L
45.0f, // Upper Leg R
0.0f, // Lower Leg L
0.0f, // Lower Leg R
};
float plane_angle[] = {
0.0f, // Lower Body (unused, there's no parent)
10.0f, // Mid Body
10.0f, // Upper Body
45.0f, // Head
45.0f, // Upper Arm L
45.0f, // Upper Arm R
90.0f, // Lower Arm L
90.0f, // Lower Arm R
45.0f, // Upper Leg L
45.0f, // Upper Leg R
60.0f, // Lower Leg L (cheating here, a knee is not symmetric, we should have rotated the twist axis)
60.0f, // Lower Leg R
};
// Create ragdoll settings
RagdollSettings *settings = new RagdollSettings;
settings->mSkeleton = skeleton;
settings->mParts.resize(skeleton->GetJointCount());
for (int p = 0; p < skeleton->GetJointCount(); ++p)
{
RagdollSettings::Part &part = settings->mParts[p];
part.SetShape(shapes[p]);
part.mPosition = positions[p];
part.mRotation = rotations[p];
part.mMotionType = EMotionType::Dynamic;
part.mObjectLayer = Layers::MOVING;
// First part is the root, doesn't have a parent and doesn't have a constraint
if (p > 0)
{
SwingTwistConstraintSettings *constraint = new SwingTwistConstraintSettings;
constraint->mDrawConstraintSize = 0.1f;
constraint->mPosition1 = constraint->mPosition2 = constraint_positions[p];
constraint->mTwistAxis1 = constraint->mTwistAxis2 = twist_axis[p];
constraint->mPlaneAxis1 = constraint->mPlaneAxis2 = Vec3::sAxisZ();
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;
}
}
// Optional: Stabilize the inertia of the limbs
settings->Stabilize();
// Optional: Calculate constraint priorities to give more priority to the root
settings->CalculateConstraintPriorities();
// Disable parent child collisions so that we don't get collisions between constrained bodies
settings->DisableParentChildCollisions();
// Calculate the map needed for GetBodyIndexToConstraintIndex()
settings->CalculateBodyIndexToConstraintIndex();
return settings;
}

View File

@@ -0,0 +1,36 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
namespace JPH {
class RagdollSettings;
enum class EMotionType : uint8;
}
#ifdef JPH_OBJECT_STREAM
enum class EConstraintOverride
{
TypeFixed,
TypePoint,
TypeHinge,
TypeSlider,
TypeCone,
TypeRagdoll,
};
#endif // JPH_OBJECT_STREAM
class RagdollLoader
{
public:
#ifdef JPH_OBJECT_STREAM
/// Load a ragdoll from an ObjectStream file
static RagdollSettings * sLoad(const char *inFileName, EMotionType inMotionType, EConstraintOverride inConstraintOverride = EConstraintOverride::TypeRagdoll);
#endif // JPH_OBJECT_STREAM
/// Create a ragdoll from code
static RagdollSettings * sCreate();
};

View File

@@ -0,0 +1,40 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2022 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Utils/ShapeCreator.h>
namespace ShapeCreator {
ShapeRefC CreateTorusMesh(float inTorusRadius, float inTubeRadius, uint inTorusSegments, uint inTubeSegments)
{
uint cNumVertices = inTorusSegments * inTubeSegments;
// Create torus
MeshShapeSettings mesh;
mesh.mTriangleVertices.reserve(cNumVertices);
mesh.mIndexedTriangles.reserve(cNumVertices * 2);
for (uint torus_segment = 0; torus_segment < inTorusSegments; ++torus_segment)
{
Mat44 rotation = Mat44::sRotation(Vec3::sAxisY(), float(torus_segment) * 2.0f * JPH_PI / inTorusSegments);
for (uint tube_segment = 0; tube_segment < inTubeSegments; ++tube_segment)
{
// Create vertices
float tube_angle = float(tube_segment) * 2.0f * JPH_PI / inTubeSegments;
Vec3 pos = rotation * Vec3(inTorusRadius + inTubeRadius * Sin(tube_angle), inTubeRadius * Cos(tube_angle), 0);
Float3 v;
pos.StoreFloat3(&v);
mesh.mTriangleVertices.push_back(v);
// Create indices
uint start_idx = torus_segment * inTubeSegments + tube_segment;
mesh.mIndexedTriangles.emplace_back(start_idx, (start_idx + 1) % cNumVertices, (start_idx + inTubeSegments) % cNumVertices);
mesh.mIndexedTriangles.emplace_back((start_idx + 1) % cNumVertices, (start_idx + inTubeSegments + 1) % cNumVertices, (start_idx + inTubeSegments) % cNumVertices);
}
}
return mesh.Create().Get();
}
};

View File

@@ -0,0 +1,18 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2022 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
namespace ShapeCreator
{
/// Create a mesh shape in the shape of a torus
/// @param inTorusRadius Radius of the torus ring
/// @param inTubeRadius Radius of the torus tube
/// @param inTorusSegments Number of segments around the torus
/// @param inTubeSegments Number of segments around the tube of the torus
/// @return The mesh shape
ShapeRefC CreateTorusMesh(float inTorusRadius, float inTubeRadius, uint inTorusSegments = 16, uint inTubeSegments = 16);
};

View File

@@ -0,0 +1,136 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <TestFramework.h>
#include <Utils/SoftBodyCreator.h>
namespace SoftBodyCreator {
Ref<SoftBodySharedSettings> CreateCloth(uint inGridSizeX, uint inGridSizeZ, float inGridSpacing, const function<float(uint, uint)> &inVertexGetInvMass, const function<Vec3(uint, uint)> &inVertexPerturbation, SoftBodySharedSettings::EBendType inBendType, const SoftBodySharedSettings::VertexAttributes &inVertexAttributes)
{
const float cOffsetX = -0.5f * inGridSpacing * (inGridSizeX - 1);
const float cOffsetZ = -0.5f * inGridSpacing * (inGridSizeZ - 1);
// Create settings
SoftBodySharedSettings *settings = new SoftBodySharedSettings;
for (uint z = 0; z < inGridSizeZ; ++z)
for (uint x = 0; x < inGridSizeX; ++x)
{
SoftBodySharedSettings::Vertex v;
Vec3 position = inVertexPerturbation(x, z) + Vec3(cOffsetX + x * inGridSpacing, 0.0f, cOffsetZ + z * inGridSpacing);
position.StoreFloat3(&v.mPosition);
v.mInvMass = inVertexGetInvMass(x, z);
settings->mVertices.push_back(v);
}
// Function to get the vertex index of a point on the cloth
auto vertex_index = [inGridSizeX](uint inX, uint inY) -> uint
{
return inX + inY * inGridSizeX;
};
// Create faces
for (uint z = 0; z < inGridSizeZ - 1; ++z)
for (uint x = 0; x < inGridSizeX - 1; ++x)
{
SoftBodySharedSettings::Face f;
f.mVertex[0] = vertex_index(x, z);
f.mVertex[1] = vertex_index(x, z + 1);
f.mVertex[2] = vertex_index(x + 1, z + 1);
settings->AddFace(f);
f.mVertex[1] = vertex_index(x + 1, z + 1);
f.mVertex[2] = vertex_index(x + 1, z);
settings->AddFace(f);
}
// Create constraints
settings->CreateConstraints(&inVertexAttributes, 1, inBendType);
// Optimize the settings
settings->Optimize();
return settings;
}
Ref<SoftBodySharedSettings> CreateClothWithFixatedCorners(uint inGridSizeX, uint inGridSizeZ, float inGridSpacing)
{
auto inv_mass = [inGridSizeX, inGridSizeZ](uint inX, uint inZ) {
return (inX == 0 && inZ == 0)
|| (inX == inGridSizeX - 1 && inZ == 0)
|| (inX == 0 && inZ == inGridSizeZ - 1)
|| (inX == inGridSizeX - 1 && inZ == inGridSizeZ - 1)? 0.0f : 1.0f;
};
return CreateCloth(inGridSizeX, inGridSizeZ, inGridSpacing, inv_mass);
}
Ref<SoftBodySharedSettings> CreateSphere(float inRadius, uint inNumTheta, uint inNumPhi, SoftBodySharedSettings::EBendType inBendType, const SoftBodySharedSettings::VertexAttributes &inVertexAttributes)
{
// Create settings
SoftBodySharedSettings *settings = new SoftBodySharedSettings;
// Create vertices
// NOTE: This is not how you should create a soft body sphere, we explicitly use polar coordinates to make the vertices unevenly distributed.
// Doing it this way tests the pressure algorithm as it receives non-uniform triangles. Better is to use uniform triangles,
// see the use of DebugRenderer::Create8thSphere for an example.
SoftBodySharedSettings::Vertex v;
(inRadius * Vec3::sUnitSpherical(0, 0)).StoreFloat3(&v.mPosition);
settings->mVertices.push_back(v);
(inRadius * Vec3::sUnitSpherical(JPH_PI, 0)).StoreFloat3(&v.mPosition);
settings->mVertices.push_back(v);
for (uint theta = 1; theta < inNumTheta - 1; ++theta)
for (uint phi = 0; phi < inNumPhi; ++phi)
{
(inRadius * Vec3::sUnitSpherical(JPH_PI * theta / (inNumTheta - 1), 2.0f * JPH_PI * phi / inNumPhi)).StoreFloat3(&v.mPosition);
settings->mVertices.push_back(v);
}
// Function to get the vertex index of a point on the sphere
auto vertex_index = [inNumTheta, inNumPhi](uint inTheta, uint inPhi) -> uint
{
if (inTheta == 0)
return 0;
else if (inTheta == inNumTheta - 1)
return 1;
else
return 2 + (inTheta - 1) * inNumPhi + inPhi % inNumPhi;
};
// Create faces
SoftBodySharedSettings::Face f;
for (uint phi = 0; phi < inNumPhi; ++phi)
{
for (uint theta = 0; theta < inNumTheta - 2; ++theta)
{
f.mVertex[0] = vertex_index(theta, phi);
f.mVertex[1] = vertex_index(theta + 1, phi);
f.mVertex[2] = vertex_index(theta + 1, phi + 1);
settings->AddFace(f);
if (theta > 0)
{
f.mVertex[1] = vertex_index(theta + 1, phi + 1);
f.mVertex[2] = vertex_index(theta, phi + 1);
settings->AddFace(f);
}
}
f.mVertex[0] = vertex_index(inNumTheta - 2, phi + 1);
f.mVertex[1] = vertex_index(inNumTheta - 2, phi);
f.mVertex[2] = vertex_index(inNumTheta - 1, 0);
settings->AddFace(f);
}
// Create constraints
settings->CreateConstraints(&inVertexAttributes, 1, inBendType);
// Optimize the settings
settings->Optimize();
return settings;
}
};

View File

@@ -0,0 +1,26 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#include <Jolt/Physics/SoftBody/SoftBodySharedSettings.h>
namespace SoftBodyCreator
{
/// Create a square cloth
/// @param inGridSizeX Number of points along the X axis
/// @param inGridSizeZ Number of points along the Z axis
/// @param inGridSpacing Distance between points
/// @param inVertexGetInvMass Function that determines the inverse mass of each vertex
Ref<SoftBodySharedSettings> CreateCloth(uint inGridSizeX = 30, uint inGridSizeZ = 30, float inGridSpacing = 0.75f, const function<float(uint, uint)> &inVertexGetInvMass = [](uint, uint) { return 1.0f; }, const function<Vec3(uint, uint)> &inVertexPerturbation = [](uint, uint) { return Vec3::sZero(); }, SoftBodySharedSettings::EBendType inBendType = SoftBodySharedSettings::EBendType::None, const SoftBodySharedSettings::VertexAttributes &inVertexAttributes = { 1.0e-5f, 1.0e-5f, 1.0e-5f });
/// Same as above but fixates the corners of the cloth
Ref<SoftBodySharedSettings> CreateClothWithFixatedCorners(uint inGridSizeX = 30, uint inGridSizeZ = 30, float inGridSpacing = 0.75f);
/// Create a hollow sphere
/// @param inRadius Radius of the sphere
/// @param inNumTheta Number of segments in the theta direction
/// @param inNumPhi Number of segments in the phi direction
Ref<SoftBodySharedSettings> CreateSphere(float inRadius = 1.0f, uint inNumTheta = 10, uint inNumPhi = 20, SoftBodySharedSettings::EBendType inBendType = SoftBodySharedSettings::EBendType::None, const SoftBodySharedSettings::VertexAttributes &inVertexAttributes = { 1.0e-4f, 1.0e-4f, 1.0e-3f });
};