Ajout de Jolt Physics + 1ere version des factory entitecomposants - camera, transform, rigidbody, collider, renderer
This commit is contained in:
201
lib/All/JoltPhysics/Samples/Utils/ContactListenerImpl.cpp
Normal file
201
lib/All/JoltPhysics/Samples/Utils/ContactListenerImpl.cpp
Normal 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);
|
||||
}
|
||||
40
lib/All/JoltPhysics/Samples/Utils/ContactListenerImpl.h
Normal file
40
lib/All/JoltPhysics/Samples/Utils/ContactListenerImpl.h
Normal 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;
|
||||
};
|
||||
55
lib/All/JoltPhysics/Samples/Utils/DebugRendererSP.h
Normal file
55
lib/All/JoltPhysics/Samples/Utils/DebugRendererSP.h
Normal 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);
|
||||
}
|
||||
312
lib/All/JoltPhysics/Samples/Utils/RagdollLoader.cpp
Normal file
312
lib/All/JoltPhysics/Samples/Utils/RagdollLoader.cpp
Normal 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;
|
||||
}
|
||||
36
lib/All/JoltPhysics/Samples/Utils/RagdollLoader.h
Normal file
36
lib/All/JoltPhysics/Samples/Utils/RagdollLoader.h
Normal 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();
|
||||
};
|
||||
40
lib/All/JoltPhysics/Samples/Utils/ShapeCreator.cpp
Normal file
40
lib/All/JoltPhysics/Samples/Utils/ShapeCreator.cpp
Normal 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();
|
||||
}
|
||||
|
||||
};
|
||||
18
lib/All/JoltPhysics/Samples/Utils/ShapeCreator.h
Normal file
18
lib/All/JoltPhysics/Samples/Utils/ShapeCreator.h
Normal 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);
|
||||
};
|
||||
136
lib/All/JoltPhysics/Samples/Utils/SoftBodyCreator.cpp
Normal file
136
lib/All/JoltPhysics/Samples/Utils/SoftBodyCreator.cpp
Normal 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;
|
||||
}
|
||||
|
||||
};
|
||||
26
lib/All/JoltPhysics/Samples/Utils/SoftBodyCreator.h
Normal file
26
lib/All/JoltPhysics/Samples/Utils/SoftBodyCreator.h
Normal 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 });
|
||||
};
|
||||
Reference in New Issue
Block a user