feat: modules moved and engine moved to submodule
This commit is contained in:
parent
dfb5e645cd
commit
c33d2130cc
5136 changed files with 225275 additions and 64485 deletions
|
|
@ -155,7 +155,8 @@ BroadPhase::AddState BroadPhaseQuadTree::AddBodiesPrepare(BodyID *ioBodies, int
|
|||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
JPH_ASSERT(inNumber > 0);
|
||||
if (inNumber <= 0)
|
||||
return nullptr;
|
||||
|
||||
const BodyVector &bodies = mBodyManager->GetBodies();
|
||||
JPH_ASSERT(mMaxBodies == mBodyManager->GetMaxBodies());
|
||||
|
|
@ -208,6 +209,12 @@ void BroadPhaseQuadTree::AddBodiesFinalize(BodyID *ioBodies, int inNumber, AddSt
|
|||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
if (inNumber <= 0)
|
||||
{
|
||||
JPH_ASSERT(inAddState == nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// This cannot run concurrently with UpdatePrepare()/UpdateFinalize()
|
||||
SharedLock lock(mUpdateMutex JPH_IF_ENABLE_ASSERTS(, mLockContext, EPhysicsLockTypes::BroadPhaseUpdate));
|
||||
|
||||
|
|
@ -244,6 +251,12 @@ void BroadPhaseQuadTree::AddBodiesAbort(BodyID *ioBodies, int inNumber, AddState
|
|||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
if (inNumber <= 0)
|
||||
{
|
||||
JPH_ASSERT(inAddState == nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
JPH_IF_ENABLE_ASSERTS(const BodyVector &bodies = mBodyManager->GetBodies();)
|
||||
JPH_ASSERT(mMaxBodies == mBodyManager->GetMaxBodies());
|
||||
|
||||
|
|
@ -278,11 +291,12 @@ void BroadPhaseQuadTree::RemoveBodies(BodyID *ioBodies, int inNumber)
|
|||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
if (inNumber <= 0)
|
||||
return;
|
||||
|
||||
// This cannot run concurrently with UpdatePrepare()/UpdateFinalize()
|
||||
SharedLock lock(mUpdateMutex JPH_IF_ENABLE_ASSERTS(, mLockContext, EPhysicsLockTypes::BroadPhaseUpdate));
|
||||
|
||||
JPH_ASSERT(inNumber > 0);
|
||||
|
||||
BodyVector &bodies = mBodyManager->GetBodies();
|
||||
JPH_ASSERT(mMaxBodies == mBodyManager->GetMaxBodies());
|
||||
|
||||
|
|
@ -325,7 +339,8 @@ void BroadPhaseQuadTree::NotifyBodiesAABBChanged(BodyID *ioBodies, int inNumber,
|
|||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
JPH_ASSERT(inNumber > 0);
|
||||
if (inNumber <= 0)
|
||||
return;
|
||||
|
||||
// This cannot run concurrently with UpdatePrepare()/UpdateFinalize()
|
||||
if (inTakeLock)
|
||||
|
|
@ -365,7 +380,8 @@ void BroadPhaseQuadTree::NotifyBodiesLayerChanged(BodyID *ioBodies, int inNumber
|
|||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
JPH_ASSERT(inNumber > 0);
|
||||
if (inNumber <= 0)
|
||||
return;
|
||||
|
||||
// First sort the bodies that actually changed layer to beginning of the array
|
||||
const BodyVector &bodies = mBodyManager->GetBodies();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <Jolt/Geometry/AABox4.h>
|
||||
#include <Jolt/Geometry/RayAABox.h>
|
||||
#include <Jolt/Geometry/OrientedBox.h>
|
||||
#include <Jolt/Core/STLLocalAllocator.h>
|
||||
|
||||
#ifdef JPH_DUMP_BROADPHASE_TREE
|
||||
JPH_SUPPRESS_WARNINGS_STD_BEGIN
|
||||
|
|
@ -57,6 +58,15 @@ void QuadTree::Node::GetChildBounds(int inChildIndex, AABox &outBounds) const
|
|||
|
||||
void QuadTree::Node::SetChildBounds(int inChildIndex, const AABox &inBounds)
|
||||
{
|
||||
// Bounding boxes provided to the quad tree should never be larger than cLargeFloat because this may trigger overflow exceptions
|
||||
// e.g. when squaring the value while testing sphere overlaps
|
||||
JPH_ASSERT(inBounds.mMin.GetX() >= -cLargeFloat && inBounds.mMin.GetX() <= cLargeFloat
|
||||
&& inBounds.mMin.GetY() >= -cLargeFloat && inBounds.mMin.GetY() <= cLargeFloat
|
||||
&& inBounds.mMin.GetZ() >= -cLargeFloat && inBounds.mMin.GetZ() <= cLargeFloat
|
||||
&& inBounds.mMax.GetX() >= -cLargeFloat && inBounds.mMax.GetX() <= cLargeFloat
|
||||
&& inBounds.mMax.GetY() >= -cLargeFloat && inBounds.mMax.GetY() <= cLargeFloat
|
||||
&& inBounds.mMax.GetZ() >= -cLargeFloat && inBounds.mMax.GetZ() <= cLargeFloat);
|
||||
|
||||
// Set max first (this keeps the bounding box invalid for reading threads)
|
||||
mBoundsMaxZ[inChildIndex] = inBounds.mMax.GetZ();
|
||||
mBoundsMaxY[inChildIndex] = inBounds.mMax.GetY();
|
||||
|
|
@ -110,7 +120,6 @@ bool QuadTree::Node::EncapsulateChildBounds(int inChildIndex, const AABox &inBou
|
|||
// QuadTree
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const float QuadTree::cLargeFloat = 1.0e30f;
|
||||
const AABox QuadTree::cInvalidBounds(Vec3::sReplicate(cLargeFloat), Vec3::sReplicate(-cLargeFloat));
|
||||
|
||||
void QuadTree::GetBodyLocation(const TrackingVector &inTracking, BodyID inBodyID, uint32 &outNodeIdx, uint32 &outChildIdx) const
|
||||
|
|
@ -152,16 +161,17 @@ QuadTree::~QuadTree()
|
|||
|
||||
// Collect all bodies
|
||||
Allocator::Batch free_batch;
|
||||
NodeID node_stack[cStackSize];
|
||||
node_stack[0] = root_node.GetNodeID();
|
||||
JPH_ASSERT(node_stack[0].IsValid());
|
||||
if (node_stack[0].IsNode())
|
||||
Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack;
|
||||
node_stack.reserve(cStackSize);
|
||||
node_stack.push_back(root_node.GetNodeID());
|
||||
JPH_ASSERT(node_stack.front().IsValid());
|
||||
if (node_stack.front().IsNode())
|
||||
{
|
||||
int top = 0;
|
||||
do
|
||||
{
|
||||
// Process node
|
||||
NodeID node_id = node_stack[top];
|
||||
NodeID node_id = node_stack.back();
|
||||
node_stack.pop_back();
|
||||
JPH_ASSERT(!node_id.IsBody());
|
||||
uint32 node_idx = node_id.GetNodeIndex();
|
||||
const Node &node = mAllocator->Get(node_idx);
|
||||
|
|
@ -169,17 +179,12 @@ QuadTree::~QuadTree()
|
|||
// Recurse and get all child nodes
|
||||
for (NodeID child_node_id : node.mChildNodeID)
|
||||
if (child_node_id.IsValid() && child_node_id.IsNode())
|
||||
{
|
||||
JPH_ASSERT(top < cStackSize);
|
||||
node_stack[top] = child_node_id;
|
||||
top++;
|
||||
}
|
||||
node_stack.push_back(child_node_id);
|
||||
|
||||
// Mark node to be freed
|
||||
mAllocator->AddObjectToBatch(free_batch, node_idx);
|
||||
--top;
|
||||
}
|
||||
while (top >= 0);
|
||||
while (!node_stack.empty());
|
||||
}
|
||||
|
||||
// Now free all nodes
|
||||
|
|
@ -191,6 +196,30 @@ uint32 QuadTree::AllocateNode(bool inIsChanged)
|
|||
uint32 index = mAllocator->ConstructObject(inIsChanged);
|
||||
if (index == Allocator::cInvalidObjectIndex)
|
||||
{
|
||||
// If you're running out of nodes, you're most likely adding too many individual bodies to the tree.
|
||||
// Because of the lock free nature of this tree, any individual body is added to the root of the tree.
|
||||
// This means that if you add a lot of bodies individually, you will end up with a very deep tree and you'll be
|
||||
// using a lot more nodes than you would if you added them in batches.
|
||||
// Please look at BodyInterface::AddBodiesPrepare/AddBodiesFinalize.
|
||||
//
|
||||
// If you have created a wrapper around Jolt then a possible solution is to activate a mode during loading
|
||||
// that queues up any bodies that need to be added. When loading is done, insert all of them as a single batch.
|
||||
// This could be implemented as a 'start batching' / 'end batching' call to switch in and out of that mode.
|
||||
// The rest of the code can then just use the regular 'add single body' call on your wrapper and doesn't need to know
|
||||
// if this mode is active or not.
|
||||
//
|
||||
// Calling PhysicsSystem::Update or PhysicsSystem::OptimizeBroadPhase will perform maintenance
|
||||
// on the tree and will make it efficient again. If you're not calling these functions and are adding a lot of bodies
|
||||
// you could still be running out of nodes because the tree is not being maintained. If your application is paused,
|
||||
// consider still calling PhysicsSystem::Update with a delta time of 0 to keep the tree in good shape.
|
||||
//
|
||||
// The system keeps track of a previous and a current tree, this allows for queries to continue using the old tree
|
||||
// while the new tree is being built. If you completely clean the PhysicsSystem and rebuild it from scratch, you may
|
||||
// want to call PhysicsSystem::OptimizeBroadPhase two times after clearing to completely get rid of any lingering nodes.
|
||||
//
|
||||
// The number of nodes that is allocated is related to the max number of bodies that is passed in PhysicsSystem::Init.
|
||||
// For normal situations there are plenty of nodes available. If all else fails, you can increase the number of nodes
|
||||
// by increasing the maximum number of bodies.
|
||||
Trace("QuadTree: Out of nodes!");
|
||||
std::abort();
|
||||
}
|
||||
|
|
@ -1472,22 +1501,28 @@ void QuadTree::ValidateTree(const BodyVector &inBodies, const TrackingVector &in
|
|||
JPH_ASSERT(inNodeIndex != cInvalidNodeIndex);
|
||||
|
||||
// To avoid call overhead, create a stack in place
|
||||
JPH_SUPPRESS_WARNING_PUSH
|
||||
JPH_CLANG_SUPPRESS_WARNING("-Wunused-member-function") // The default constructor of StackEntry is unused when using Jolt's Array class but not when using std::vector
|
||||
struct StackEntry
|
||||
{
|
||||
StackEntry() = default;
|
||||
inline StackEntry(uint32 inNodeIndex, uint32 inParentNodeIndex) : mNodeIndex(inNodeIndex), mParentNodeIndex(inParentNodeIndex) { }
|
||||
|
||||
uint32 mNodeIndex;
|
||||
uint32 mParentNodeIndex;
|
||||
};
|
||||
StackEntry stack[cStackSize];
|
||||
stack[0].mNodeIndex = inNodeIndex;
|
||||
stack[0].mParentNodeIndex = cInvalidNodeIndex;
|
||||
int top = 0;
|
||||
JPH_SUPPRESS_WARNING_POP
|
||||
Array<StackEntry, STLLocalAllocator<StackEntry, cStackSize>> stack;
|
||||
stack.reserve(cStackSize);
|
||||
stack.emplace_back(inNodeIndex, cInvalidNodeIndex);
|
||||
|
||||
uint32 num_bodies = 0;
|
||||
|
||||
do
|
||||
{
|
||||
// Copy entry from the stack
|
||||
StackEntry cur_stack = stack[top];
|
||||
StackEntry cur_stack = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
// Validate parent
|
||||
const Node &node = mAllocator->Get(cur_stack.mNodeIndex);
|
||||
|
|
@ -1506,10 +1541,7 @@ void QuadTree::ValidateTree(const BodyVector &inBodies, const TrackingVector &in
|
|||
{
|
||||
// Child is a node, recurse
|
||||
uint32 child_idx = child_node_id.GetNodeIndex();
|
||||
JPH_ASSERT(top < cStackSize);
|
||||
StackEntry &new_entry = stack[top++];
|
||||
new_entry.mNodeIndex = child_idx;
|
||||
new_entry.mParentNodeIndex = cur_stack.mNodeIndex;
|
||||
stack.emplace_back(child_idx, cur_stack.mNodeIndex);
|
||||
|
||||
// Validate that the bounding box is bigger or equal to the bounds in the tree
|
||||
// Bounding box could also be invalid if all children of our child were removed
|
||||
|
|
@ -1530,20 +1562,19 @@ void QuadTree::ValidateTree(const BodyVector &inBodies, const TrackingVector &in
|
|||
JPH_ASSERT(node_idx == cur_stack.mNodeIndex);
|
||||
JPH_ASSERT(child_idx == i);
|
||||
|
||||
// Validate that the body bounds are bigger or equal to the bounds in the tree
|
||||
// Validate that the body cached bounds still match the actual bounds
|
||||
const Body *body = inBodies[child_node_id.GetBodyID().GetIndex()];
|
||||
body->ValidateCachedBounds();
|
||||
|
||||
// Validate that the node bounds are bigger or equal to the body bounds
|
||||
AABox body_bounds;
|
||||
node.GetChildBounds(i, body_bounds);
|
||||
const Body *body = inBodies[child_node_id.GetBodyID().GetIndex()];
|
||||
AABox cached_body_bounds = body->GetWorldSpaceBounds();
|
||||
AABox real_body_bounds = body->GetShape()->GetWorldSpaceBounds(body->GetCenterOfMassTransform(), Vec3::sReplicate(1.0f));
|
||||
JPH_ASSERT(cached_body_bounds == real_body_bounds); // Check that cached body bounds are up to date
|
||||
JPH_ASSERT(body_bounds.Contains(real_body_bounds));
|
||||
JPH_ASSERT(body_bounds.Contains(body->GetWorldSpaceBounds()));
|
||||
}
|
||||
}
|
||||
}
|
||||
--top;
|
||||
}
|
||||
while (top >= 0);
|
||||
while (!stack.empty());
|
||||
|
||||
// Check that the amount of bodies in the tree matches our counter
|
||||
JPH_ASSERT(num_bodies == inNumExpectedBodies);
|
||||
|
|
@ -1565,14 +1596,14 @@ void QuadTree::DumpTree(const NodeID &inRoot, const char *inFileNamePrefix) cons
|
|||
f << "digraph {\n";
|
||||
|
||||
// Iterate the entire tree
|
||||
NodeID node_stack[cStackSize];
|
||||
node_stack[0] = inRoot;
|
||||
JPH_ASSERT(node_stack[0].IsValid());
|
||||
int top = 0;
|
||||
Array<NodeID, STLLocalAllocator<NodeID, cStackSize>> node_stack;
|
||||
node_stack.push_back(inRoot);
|
||||
JPH_ASSERT(inRoot.IsValid());
|
||||
do
|
||||
{
|
||||
// Check if node is a body
|
||||
NodeID node_id = node_stack[top];
|
||||
NodeID node_id = node_stack.back();
|
||||
node_stack.pop_back();
|
||||
if (node_id.IsBody())
|
||||
{
|
||||
// Output body
|
||||
|
|
@ -1597,9 +1628,7 @@ void QuadTree::DumpTree(const NodeID &inRoot, const char *inFileNamePrefix) cons
|
|||
for (NodeID child_node_id : node.mChildNodeID)
|
||||
if (child_node_id.IsValid())
|
||||
{
|
||||
JPH_ASSERT(top < cStackSize);
|
||||
node_stack[top] = child_node_id;
|
||||
top++;
|
||||
node_stack.push_back(child_node_id);
|
||||
|
||||
// Output link
|
||||
f << "node" << node_str << " -> ";
|
||||
|
|
@ -1610,9 +1639,8 @@ void QuadTree::DumpTree(const NodeID &inRoot, const char *inFileNamePrefix) cons
|
|||
f << "\n";
|
||||
}
|
||||
}
|
||||
--top;
|
||||
}
|
||||
while (top >= 0);
|
||||
while (!node_stack.empty());
|
||||
|
||||
// Finish DOT file
|
||||
f << "}\n";
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ private:
|
|||
/// Construct a node ID
|
||||
static inline NodeID sInvalid() { return NodeID(cInvalidNodeIndex); }
|
||||
static inline NodeID sFromBodyID(BodyID inID) { NodeID node_id(inID.GetIndexAndSequenceNumber()); JPH_ASSERT(node_id.IsBody()); return node_id; }
|
||||
static inline NodeID sFromNodeIndex(uint32 inIdx) { NodeID node_id(inIdx | cIsNode); JPH_ASSERT(node_id.IsNode()); return node_id; }
|
||||
static inline NodeID sFromNodeIndex(uint32 inIdx) { JPH_ASSERT((inIdx & cIsNode) == 0); return NodeID(inIdx | cIsNode); }
|
||||
|
||||
/// Check what type of ID it is
|
||||
inline bool IsValid() const { return mID != cInvalidNodeIndex; }
|
||||
|
|
@ -261,8 +261,7 @@ public:
|
|||
|
||||
private:
|
||||
/// Constants
|
||||
static const uint32 cInvalidNodeIndex = 0xffffffff; ///< Value used to indicate node index is invalid
|
||||
static const float cLargeFloat; ///< A large floating point number that is small enough to not cause any overflows
|
||||
static constexpr uint32 cInvalidNodeIndex = 0xffffffff; ///< Value used to indicate node index is invalid
|
||||
static const AABox cInvalidBounds; ///< Invalid bounding box using cLargeFloat
|
||||
|
||||
/// We alternate between two trees in order to let collision queries complete in parallel to adding/removing objects to the tree
|
||||
|
|
|
|||
|
|
@ -79,7 +79,8 @@ void CollideConvexVsTriangles::Collide(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2,
|
|||
mShape1ExCvxRadius = mShape1->GetSupportFunction(ConvexShape::ESupportMode::ExcludeConvexRadius, mBufferExCvxRadius, mScale1);
|
||||
|
||||
// Perform GJK step
|
||||
status = pen_depth.GetPenetrationDepthStepGJK(*mShape1ExCvxRadius, mShape1ExCvxRadius->GetConvexRadius() + mCollideShapeSettings.mMaxSeparationDistance, triangle, 0.0f, mCollideShapeSettings.mCollisionTolerance, penetration_axis, point1, point2);
|
||||
float max_separation_distance = mCollideShapeSettings.mMaxSeparationDistance;
|
||||
status = pen_depth.GetPenetrationDepthStepGJK(*mShape1ExCvxRadius, mShape1ExCvxRadius->GetConvexRadius() + max_separation_distance, triangle, 0.0f, mCollideShapeSettings.mCollisionTolerance, penetration_axis, point1, point2);
|
||||
|
||||
// Check result of collision detection
|
||||
if (status == EPAPenetrationDepth::EStatus::NotColliding)
|
||||
|
|
@ -88,12 +89,18 @@ void CollideConvexVsTriangles::Collide(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2,
|
|||
{
|
||||
// Need to run expensive EPA algorithm
|
||||
|
||||
// We know we're overlapping at this point, so we can set the max separation distance to 0.
|
||||
// Numerically it is possible that GJK finds that the shapes are overlapping but EPA finds that they're separated.
|
||||
// In order to avoid this, we clamp the max separation distance to 1 so that we don't excessively inflate the shape,
|
||||
// but we still inflate it enough to avoid the case where EPA misses the collision.
|
||||
max_separation_distance = min(max_separation_distance, 1.0f);
|
||||
|
||||
// Get the support function
|
||||
if (mShape1IncCvxRadius == nullptr)
|
||||
mShape1IncCvxRadius = mShape1->GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, mBufferIncCvxRadius, mScale1);
|
||||
|
||||
// Add convex radius
|
||||
AddConvexRadius<ConvexShape::Support> shape1_add_max_separation_distance(*mShape1IncCvxRadius, mCollideShapeSettings.mMaxSeparationDistance);
|
||||
AddConvexRadius shape1_add_max_separation_distance(*mShape1IncCvxRadius, max_separation_distance);
|
||||
|
||||
// Perform EPA step
|
||||
if (!pen_depth.GetPenetrationDepthStepEPA(shape1_add_max_separation_distance, triangle, mCollideShapeSettings.mPenetrationTolerance, penetration_axis, point1, point2))
|
||||
|
|
@ -101,14 +108,14 @@ void CollideConvexVsTriangles::Collide(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2,
|
|||
}
|
||||
|
||||
// Check if the penetration is bigger than the early out fraction
|
||||
float penetration_depth = (point2 - point1).Length() - mCollideShapeSettings.mMaxSeparationDistance;
|
||||
float penetration_depth = (point2 - point1).Length() - max_separation_distance;
|
||||
if (-penetration_depth >= mCollector.GetEarlyOutFraction())
|
||||
return;
|
||||
|
||||
// Correct point1 for the added separation distance
|
||||
float penetration_axis_len = penetration_axis.Length();
|
||||
if (penetration_axis_len > 0.0f)
|
||||
point1 -= penetration_axis * (mCollideShapeSettings.mMaxSeparationDistance / penetration_axis_len);
|
||||
point1 -= penetration_axis * (max_separation_distance / penetration_axis_len);
|
||||
|
||||
// Check if we have enabled active edge detection
|
||||
if (mCollideShapeSettings.mActiveEdgeMode == EActiveEdgeMode::CollideOnlyWithActive && inActiveEdges != 0b111)
|
||||
|
|
|
|||
93
engine/thirdparty/jolt_physics/Jolt/Physics/Collision/CollideShapeVsShapePerLeaf.h
vendored
Normal file
93
engine/thirdparty/jolt_physics/Jolt/Physics/Collision/CollideShapeVsShapePerLeaf.h
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Jolt/Physics/Collision/CollideShape.h>
|
||||
#include <Jolt/Physics/Collision/CollisionDispatch.h>
|
||||
#include <Jolt/Core/STLLocalAllocator.h>
|
||||
|
||||
JPH_NAMESPACE_BEGIN
|
||||
|
||||
/// Collide 2 shapes and returns at most 1 hit per leaf shape pairs that overlapping. This can be used when not all contacts between the shapes are needed.
|
||||
/// E.g. when testing a compound with 2 MeshShapes A and B against a compound with 2 SphereShapes C and D, then at most you'll get 4 collisions: AC, AD, BC, BD.
|
||||
/// The default CollisionDispatch::sCollideShapeVsShape function would return all intersecting triangles in A against C, all in B against C etc.
|
||||
/// @param inShape1 The first shape
|
||||
/// @param inShape2 The second shape
|
||||
/// @param inScale1 Local space scale of shape 1 (scales relative to its center of mass)
|
||||
/// @param inScale2 Local space scale of shape 2 (scales relative to its center of mass)
|
||||
/// @param inCenterOfMassTransform1 Transform to transform center of mass of shape 1 into world space
|
||||
/// @param inCenterOfMassTransform2 Transform to transform center of mass of shape 2 into world space
|
||||
/// @param inSubShapeIDCreator1 Class that tracks the current sub shape ID for shape 1
|
||||
/// @param inSubShapeIDCreator2 Class that tracks the current sub shape ID for shape 2
|
||||
/// @param inCollideShapeSettings Options for the CollideShape test
|
||||
/// @param ioCollector The collector that receives the results.
|
||||
/// @param inShapeFilter allows selectively disabling collisions between pairs of (sub) shapes.
|
||||
/// @tparam LeafCollector The type of the collector that will be used to collect hits between leaf pairs. Must be either AnyHitCollisionCollector<CollideShapeCollector> to get any hit (cheapest) or ClosestHitCollisionCollector<CollideShapeCollector> to get the deepest hit (more expensive).
|
||||
template <class LeafCollector>
|
||||
void CollideShapeVsShapePerLeaf(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter = { })
|
||||
{
|
||||
// Tracks information we need about a leaf shape
|
||||
struct LeafShape
|
||||
{
|
||||
LeafShape() = default;
|
||||
|
||||
LeafShape(const AABox &inBounds, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Shape *inShape, const SubShapeIDCreator &inSubShapeIDCreator) :
|
||||
mBounds(inBounds),
|
||||
mCenterOfMassTransform(inCenterOfMassTransform),
|
||||
mScale(inScale),
|
||||
mShape(inShape),
|
||||
mSubShapeIDCreator(inSubShapeIDCreator)
|
||||
{
|
||||
}
|
||||
|
||||
AABox mBounds;
|
||||
Mat44 mCenterOfMassTransform;
|
||||
Vec3 mScale;
|
||||
const Shape * mShape;
|
||||
SubShapeIDCreator mSubShapeIDCreator;
|
||||
};
|
||||
|
||||
constexpr uint cMaxLocalLeafShapes = 32;
|
||||
|
||||
// A collector that stores the information we need from a leaf shape in an array that is usually on the stack but can fall back to the heap if needed
|
||||
class MyCollector : public TransformedShapeCollector
|
||||
{
|
||||
public:
|
||||
MyCollector()
|
||||
{
|
||||
mHits.reserve(cMaxLocalLeafShapes);
|
||||
}
|
||||
|
||||
void AddHit(const TransformedShape &inShape) override
|
||||
{
|
||||
mHits.emplace_back(inShape.GetWorldSpaceBounds(), inShape.GetCenterOfMassTransform().ToMat44(), inShape.GetShapeScale(), inShape.mShape, inShape.mSubShapeIDCreator);
|
||||
}
|
||||
|
||||
Array<LeafShape, STLLocalAllocator<LeafShape, cMaxLocalLeafShapes>> mHits;
|
||||
};
|
||||
|
||||
// Get bounds of both shapes
|
||||
AABox bounds1 = inShape1->GetWorldSpaceBounds(inCenterOfMassTransform1, inScale1);
|
||||
AABox bounds2 = inShape2->GetWorldSpaceBounds(inCenterOfMassTransform2, inScale2);
|
||||
|
||||
// Get leaf shapes that overlap with the bounds of the other shape
|
||||
MyCollector leaf_shapes1, leaf_shapes2;
|
||||
inShape1->CollectTransformedShapes(bounds2, inCenterOfMassTransform1.GetTranslation(), inCenterOfMassTransform1.GetQuaternion(), inScale1, inSubShapeIDCreator1, leaf_shapes1, inShapeFilter);
|
||||
inShape2->CollectTransformedShapes(bounds1, inCenterOfMassTransform2.GetTranslation(), inCenterOfMassTransform2.GetQuaternion(), inScale2, inSubShapeIDCreator2, leaf_shapes2, inShapeFilter);
|
||||
|
||||
// Now test each leaf shape against each other leaf
|
||||
for (const LeafShape &leaf1 : leaf_shapes1.mHits)
|
||||
for (const LeafShape &leaf2 : leaf_shapes2.mHits)
|
||||
if (leaf1.mBounds.Overlaps(leaf2.mBounds))
|
||||
{
|
||||
// Use the leaf collector to collect max 1 hit for this pair and pass it on to ioCollector
|
||||
LeafCollector collector;
|
||||
CollisionDispatch::sCollideShapeVsShape(leaf1.mShape, leaf2.mShape, leaf1.mScale, leaf2.mScale, leaf1.mCenterOfMassTransform, leaf2.mCenterOfMassTransform, leaf1.mSubShapeIDCreator, leaf2.mSubShapeIDCreator, inCollideShapeSettings, collector, inShapeFilter);
|
||||
if (collector.HadHit())
|
||||
ioCollector.AddHit(collector.mHit);
|
||||
}
|
||||
}
|
||||
|
||||
JPH_NAMESPACE_END
|
||||
|
|
@ -66,7 +66,11 @@ public:
|
|||
/// before AddHit is called (e.g. the user data pointer or the velocity of the body).
|
||||
virtual void OnBody([[maybe_unused]] const Body &inBody) { /* Collects nothing by default */ }
|
||||
|
||||
/// Set by the collision detection functions to the current TransformedShape that we're colliding against before calling the AddHit function
|
||||
/// When running a query through the NarrowPhaseQuery class, this will be called after all AddHit calls have been made for a particular body.
|
||||
virtual void OnBodyEnd() { /* Does nothing by default */ }
|
||||
|
||||
/// Set by the collision detection functions to the current TransformedShape that we're colliding against before calling the AddHit function.
|
||||
/// Note: Only valid during AddHit! For performance reasons, the pointer is not reset after leaving AddHit so the context may point to freed memory.
|
||||
void SetContext(const TransformedShape *inContext) { mContext = inContext; }
|
||||
const TransformedShape *GetContext() const { return mContext; }
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,91 @@ private:
|
|||
bool mHadHit = false;
|
||||
};
|
||||
|
||||
/// Implementation that collects the closest / deepest hit for each body and optionally sorts them on distance
|
||||
template <class CollectorType>
|
||||
class ClosestHitPerBodyCollisionCollector : public CollectorType
|
||||
{
|
||||
public:
|
||||
/// Redeclare ResultType
|
||||
using ResultType = typename CollectorType::ResultType;
|
||||
|
||||
// See: CollectorType::Reset
|
||||
virtual void Reset() override
|
||||
{
|
||||
CollectorType::Reset();
|
||||
|
||||
mHits.clear();
|
||||
mHadHit = false;
|
||||
}
|
||||
|
||||
// See: CollectorType::OnBody
|
||||
virtual void OnBody(const Body &inBody) override
|
||||
{
|
||||
// Store the early out fraction so we can restore it after we've collected all hits for this body
|
||||
mPreviousEarlyOutFraction = CollectorType::GetEarlyOutFraction();
|
||||
}
|
||||
|
||||
// See: CollectorType::AddHit
|
||||
virtual void AddHit(const ResultType &inResult) override
|
||||
{
|
||||
float early_out = inResult.GetEarlyOutFraction();
|
||||
if (!mHadHit || early_out < CollectorType::GetEarlyOutFraction())
|
||||
{
|
||||
// Update early out fraction to avoid spending work on collecting further hits for this body
|
||||
CollectorType::UpdateEarlyOutFraction(early_out);
|
||||
|
||||
if (!mHadHit)
|
||||
{
|
||||
// First time we have a hit we append it to the array
|
||||
mHits.push_back(inResult);
|
||||
mHadHit = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Closer hits will override the previous one
|
||||
mHits.back() = inResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See: CollectorType::OnBodyEnd
|
||||
virtual void OnBodyEnd() override
|
||||
{
|
||||
if (mHadHit)
|
||||
{
|
||||
// Reset the early out fraction to the configured value so that we will continue
|
||||
// to collect hits at any distance for other bodies
|
||||
JPH_ASSERT(mPreviousEarlyOutFraction != -FLT_MAX); // Check that we got a call to OnBody
|
||||
CollectorType::ResetEarlyOutFraction(mPreviousEarlyOutFraction);
|
||||
mHadHit = false;
|
||||
}
|
||||
|
||||
// For asserting purposes we reset the stored early out fraction so we can detect that OnBody was called
|
||||
JPH_IF_ENABLE_ASSERTS(mPreviousEarlyOutFraction = -FLT_MAX;)
|
||||
}
|
||||
|
||||
/// Order hits on closest first
|
||||
void Sort()
|
||||
{
|
||||
QuickSort(mHits.begin(), mHits.end(), [](const ResultType &inLHS, const ResultType &inRHS) { return inLHS.GetEarlyOutFraction() < inRHS.GetEarlyOutFraction(); });
|
||||
}
|
||||
|
||||
/// Check if any hits were collected
|
||||
inline bool HadHit() const
|
||||
{
|
||||
return !mHits.empty();
|
||||
}
|
||||
|
||||
Array<ResultType> mHits;
|
||||
|
||||
private:
|
||||
// Store early out fraction that was initially configured for the collector
|
||||
float mPreviousEarlyOutFraction = -FLT_MAX;
|
||||
|
||||
// Flag to indicate if we have a hit for the current body
|
||||
bool mHadHit = false;
|
||||
};
|
||||
|
||||
/// Simple implementation that collects any hit
|
||||
template <class CollectorType>
|
||||
class AnyHitCollisionCollector : public CollectorType
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(CollisionGroup)
|
|||
JPH_ADD_ATTRIBUTE(CollisionGroup, mSubGroupID)
|
||||
}
|
||||
|
||||
const CollisionGroup CollisionGroup::sInvalid;
|
||||
|
||||
void CollisionGroup::SaveBinaryState(StreamOut &inStream) const
|
||||
{
|
||||
inStream.Write(mGroupID);
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ public:
|
|||
/// Restore the state of this object from inStream. Does not save group filter.
|
||||
void RestoreBinaryState(StreamIn &inStream);
|
||||
|
||||
/// An invalid collision group
|
||||
static const CollisionGroup sInvalid;
|
||||
|
||||
private:
|
||||
RefConst<GroupFilter> mGroupFilter;
|
||||
GroupID mGroupID = cInvalidGroup;
|
||||
|
|
|
|||
|
|
@ -77,8 +77,12 @@ class InternalEdgeRemovingCollector : public CollideShapeCollector
|
|||
public:
|
||||
/// Constructor, configures a collector to be called with all the results that do not hit internal edges
|
||||
explicit InternalEdgeRemovingCollector(CollideShapeCollector &inChainedCollector) :
|
||||
CollideShapeCollector(inChainedCollector),
|
||||
mChainedCollector(inChainedCollector)
|
||||
{
|
||||
// Initialize arrays to full capacity to avoid needless reallocation calls
|
||||
mVoidedFeatures.reserve(cMaxLocalVoidedFeatures);
|
||||
mDelayedResults.reserve(cMaxLocalDelayedResults);
|
||||
}
|
||||
|
||||
// See: CollideShapeCollector::Reset
|
||||
|
|
@ -221,6 +225,13 @@ public:
|
|||
mDelayedResults.clear();
|
||||
}
|
||||
|
||||
// See: CollideShapeCollector::OnBodyEnd
|
||||
virtual void OnBodyEnd() override
|
||||
{
|
||||
Flush();
|
||||
mChainedCollector.OnBodyEnd();
|
||||
}
|
||||
|
||||
/// Version of CollisionDispatch::sCollideShapeVsShape that removes internal edges
|
||||
static void sCollideShapeVsShape(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter = { })
|
||||
{
|
||||
|
|
|
|||
|
|
@ -134,8 +134,10 @@ void PruneContactPoints(Vec3Arg inPenetrationAxis, ContactPoints &ioContactPoint
|
|||
ioContactPointsOn2 = points_to_keep_on_2;
|
||||
}
|
||||
|
||||
void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inMaxContactDistanceSq , const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2 JPH_IF_DEBUG_RENDERER(, RVec3Arg inCenterOfMass))
|
||||
void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inMaxContactDistance, const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2 JPH_IF_DEBUG_RENDERER(, RVec3Arg inCenterOfMass))
|
||||
{
|
||||
JPH_ASSERT(inMaxContactDistance > 0.0f);
|
||||
|
||||
#ifdef JPH_DEBUG_RENDERER
|
||||
if (ContactConstraintManager::sDrawContactPoint)
|
||||
{
|
||||
|
|
@ -165,7 +167,7 @@ void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, V
|
|||
else if (inShape1Face.size() == 2)
|
||||
ClipPolyVsEdge(inShape2Face, inShape1Face[0], inShape1Face[1], inPenetrationAxis, clipped_face);
|
||||
|
||||
// Project the points back onto the plane of shape 1 face and only keep those that are behind the plane
|
||||
// Determine plane origin and normal for shape 1
|
||||
Vec3 plane_origin = inShape1Face[0];
|
||||
Vec3 plane_normal;
|
||||
Vec3 first_edge = inShape1Face[1] - plane_origin;
|
||||
|
|
@ -180,20 +182,25 @@ void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, V
|
|||
plane_normal = first_edge.Cross(inPenetrationAxis).Cross(first_edge);
|
||||
}
|
||||
|
||||
// Check if the plane normal has any length, if not the clipped shape is so small that we'll just use the contact points
|
||||
float plane_normal_len_sq = plane_normal.LengthSq();
|
||||
if (plane_normal_len_sq > 0.0f)
|
||||
// If penetration axis and plane normal are perpendicular, fall back to the contact points
|
||||
float penetration_axis_dot_plane_normal = inPenetrationAxis.Dot(plane_normal);
|
||||
if (penetration_axis_dot_plane_normal != 0.0f)
|
||||
{
|
||||
// Discard points of faces that are too far away to collide
|
||||
float penetration_axis_len = inPenetrationAxis.Length();
|
||||
|
||||
for (Vec3 p2 : clipped_face)
|
||||
{
|
||||
float distance = (p2 - plane_origin).Dot(plane_normal); // Note should divide by length of plane_normal (unnormalized here)
|
||||
if (distance <= 0.0f || Square(distance) < inMaxContactDistanceSq * plane_normal_len_sq) // Must be close enough to plane, note we correct for not dividing by plane normal length here
|
||||
{
|
||||
// Project point back on shape 1 using the normal, note we correct for not dividing by plane normal length here:
|
||||
// p1 = p2 - (distance / sqrt(plane_normal_len_sq)) * (plane_normal / sqrt(plane_normal_len_sq));
|
||||
Vec3 p1 = p2 - (distance / plane_normal_len_sq) * plane_normal;
|
||||
// Project clipped face back onto the plane of face 1, we do this by solving:
|
||||
// p1 = p2 + distance * penetration_axis / |penetration_axis|
|
||||
// (p1 - plane_origin) . plane_normal = 0
|
||||
// This gives us:
|
||||
// distance = -|penetration_axis| * (p2 - plane_origin) . plane_normal / penetration_axis . plane_normal
|
||||
float distance = (p2 - plane_origin).Dot(plane_normal) / penetration_axis_dot_plane_normal; // note left out -|penetration_axis| term
|
||||
|
||||
// If the point is less than inMaxContactDistance in front of the plane of face 2, add it as a contact point
|
||||
if (distance * penetration_axis_len < inMaxContactDistance)
|
||||
{
|
||||
Vec3 p1 = p2 - distance * inPenetrationAxis;
|
||||
outContactPoints1.push_back(p1);
|
||||
outContactPoints2.push_back(p2);
|
||||
}
|
||||
|
|
@ -213,15 +220,19 @@ void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, V
|
|||
DebugRenderer::sInstance->DrawWirePolygon(com, inShape2Face, Color::sGreen, 0.05f);
|
||||
|
||||
// Draw normal
|
||||
if (plane_normal_len_sq > 0.0f)
|
||||
float plane_normal_len = plane_normal.Length();
|
||||
if (plane_normal_len > 0.0f)
|
||||
{
|
||||
RVec3 plane_origin_ws = inCenterOfMass + plane_origin;
|
||||
DebugRenderer::sInstance->DrawArrow(plane_origin_ws, plane_origin_ws + plane_normal / sqrt(plane_normal_len_sq), Color::sYellow, 0.05f);
|
||||
DebugRenderer::sInstance->DrawArrow(plane_origin_ws, plane_origin_ws + plane_normal / plane_normal_len, Color::sYellow, 0.05f);
|
||||
}
|
||||
|
||||
// Draw contact points that remain after distance check
|
||||
for (ContactPoints::size_type p = old_size; p < outContactPoints1.size(); ++p)
|
||||
{
|
||||
DebugRenderer::sInstance->DrawMarker(inCenterOfMass + outContactPoints1[p], Color::sYellow, 0.1f);
|
||||
DebugRenderer::sInstance->DrawMarker(inCenterOfMass + outContactPoints2[p], Color::sOrange, 0.1f);
|
||||
}
|
||||
}
|
||||
#endif // JPH_DEBUG_RENDERER
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ JPH_EXPORT void PruneContactPoints(Vec3Arg inPenetrationAxis, ContactPoints &ioC
|
|||
/// @param inContactPoint1 The contact point on shape 1 relative to inCenterOfMass
|
||||
/// @param inContactPoint2 The contact point on shape 2 relative to inCenterOfMass
|
||||
/// @param inPenetrationAxis The local space penetration axis in world space
|
||||
/// @param inMaxContactDistanceSq After face 2 is clipped against face 1, each remaining point on face 2 is tested against the plane of face 1. If the distance^2 on the positive side of the plane is larger than this distance, the point will be discarded as a contact point.
|
||||
/// @param inMaxContactDistance After face 2 is clipped against face 1, each remaining point on face 2 is tested against the plane of face 1. If the distance on the positive side of the plane is larger than this distance, the point will be discarded as a contact point.
|
||||
/// @param inShape1Face The supporting faces on shape 1 relative to inCenterOfMass
|
||||
/// @param inShape2Face The supporting faces on shape 2 relative to inCenterOfMass
|
||||
/// @param outContactPoints1 Returns the contact points between the two shapes for shape 1 relative to inCenterOfMass (any existing points in the output array are left as is)
|
||||
|
|
@ -35,7 +35,7 @@ JPH_EXPORT void PruneContactPoints(Vec3Arg inPenetrationAxis, ContactPoints &ioC
|
|||
#ifdef JPH_DEBUG_RENDERER
|
||||
/// @param inCenterOfMass Center of mass position of body 1
|
||||
#endif
|
||||
JPH_EXPORT void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inMaxContactDistanceSq, const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2
|
||||
JPH_EXPORT void ManifoldBetweenTwoFaces(Vec3Arg inContactPoint1, Vec3Arg inContactPoint2, Vec3Arg inPenetrationAxis, float inMaxContactDistance, const ConvexShape::SupportingFace &inShape1Face, const ConvexShape::SupportingFace &inShape2Face, ContactPoints &outContactPoints1, ContactPoints &outContactPoints2
|
||||
#ifdef JPH_DEBUG_RENDERER
|
||||
, RVec3Arg inCenterOfMass
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ bool NarrowPhaseQuery::CastRay(const RRayCast &inRay, RayCastResult &ioHit, cons
|
|||
mBodyLockInterface(inBodyLockInterface),
|
||||
mBodyFilter(inBodyFilter)
|
||||
{
|
||||
UpdateEarlyOutFraction(ioHit.mFraction);
|
||||
ResetEarlyOutFraction(ioHit.mFraction);
|
||||
}
|
||||
|
||||
virtual void AddHit(const ResultType &inResult) override
|
||||
|
|
@ -126,6 +126,10 @@ void NarrowPhaseQuery::CastRay(const RRayCast &inRay, const RayCastSettings &inR
|
|||
// Do narrow phase collision check
|
||||
ts.CastRay(mRay, mRayCastSettings, mCollector, mShapeFilter);
|
||||
|
||||
// Notify collector of the end of this body
|
||||
// We do this before updating the early out fraction so that the collector can still modify it
|
||||
mCollector.OnBodyEnd();
|
||||
|
||||
// Update early out fraction based on narrow phase collector
|
||||
UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());
|
||||
}
|
||||
|
|
@ -189,6 +193,10 @@ void NarrowPhaseQuery::CollidePoint(RVec3Arg inPoint, CollidePointCollector &ioC
|
|||
// Do narrow phase collision check
|
||||
ts.CollidePoint(mPoint, mCollector, mShapeFilter);
|
||||
|
||||
// Notify collector of the end of this body
|
||||
// We do this before updating the early out fraction so that the collector can still modify it
|
||||
mCollector.OnBodyEnd();
|
||||
|
||||
// Update early out fraction based on narrow phase collector
|
||||
UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());
|
||||
}
|
||||
|
|
@ -255,6 +263,10 @@ void NarrowPhaseQuery::CollideShape(const Shape *inShape, Vec3Arg inShapeScale,
|
|||
// Do narrow phase collision check
|
||||
ts.CollideShape(mShape, mShapeScale, mCenterOfMassTransform, mCollideShapeSettings, mBaseOffset, mCollector, mShapeFilter);
|
||||
|
||||
// Notify collector of the end of this body
|
||||
// We do this before updating the early out fraction so that the collector can still modify it
|
||||
mCollector.OnBodyEnd();
|
||||
|
||||
// Update early out fraction based on narrow phase collector
|
||||
UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());
|
||||
}
|
||||
|
|
@ -284,82 +296,13 @@ void NarrowPhaseQuery::CollideShape(const Shape *inShape, Vec3Arg inShapeScale,
|
|||
|
||||
void NarrowPhaseQuery::CollideShapeWithInternalEdgeRemoval(const Shape *inShape, Vec3Arg inShapeScale, RMat44Arg inCenterOfMassTransform, const CollideShapeSettings &inCollideShapeSettings, RVec3Arg inBaseOffset, CollideShapeCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const
|
||||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
// We require these settings for internal edge removal to work
|
||||
CollideShapeSettings settings = inCollideShapeSettings;
|
||||
settings.mActiveEdgeMode = EActiveEdgeMode::CollideWithAll;
|
||||
settings.mCollectFacesMode = ECollectFacesMode::CollectFaces;
|
||||
|
||||
class MyCollector : public CollideShapeBodyCollector
|
||||
{
|
||||
public:
|
||||
MyCollector(const Shape *inShape, Vec3Arg inShapeScale, RMat44Arg inCenterOfMassTransform, const CollideShapeSettings &inCollideShapeSettings, RVec3Arg inBaseOffset, CollideShapeCollector &ioCollector, const BodyLockInterface &inBodyLockInterface, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) :
|
||||
CollideShapeBodyCollector(ioCollector),
|
||||
mShape(inShape),
|
||||
mShapeScale(inShapeScale),
|
||||
mCenterOfMassTransform(inCenterOfMassTransform),
|
||||
mBaseOffset(inBaseOffset),
|
||||
mBodyLockInterface(inBodyLockInterface),
|
||||
mBodyFilter(inBodyFilter),
|
||||
mShapeFilter(inShapeFilter),
|
||||
mCollideShapeSettings(inCollideShapeSettings),
|
||||
mCollector(ioCollector)
|
||||
{
|
||||
// We require these settings for internal edge removal to work
|
||||
mCollideShapeSettings.mActiveEdgeMode = EActiveEdgeMode::CollideWithAll;
|
||||
mCollideShapeSettings.mCollectFacesMode = ECollectFacesMode::CollectFaces;
|
||||
}
|
||||
|
||||
virtual void AddHit(const ResultType &inResult) override
|
||||
{
|
||||
// Only test shape if it passes the body filter
|
||||
if (mBodyFilter.ShouldCollide(inResult))
|
||||
{
|
||||
// Lock the body
|
||||
BodyLockRead lock(mBodyLockInterface, inResult);
|
||||
if (lock.SucceededAndIsInBroadPhase()) // Race condition: body could have been removed since it has been found in the broadphase, ensures body is in the broadphase while we call the callbacks
|
||||
{
|
||||
const Body &body = lock.GetBody();
|
||||
|
||||
// Check body filter again now that we've locked the body
|
||||
if (mBodyFilter.ShouldCollideLocked(body))
|
||||
{
|
||||
// Collect the transformed shape
|
||||
TransformedShape ts = body.GetTransformedShape();
|
||||
|
||||
// Notify collector of new body
|
||||
mCollector.OnBody(body);
|
||||
|
||||
// Release the lock now, we have all the info we need in the transformed shape
|
||||
lock.ReleaseLock();
|
||||
|
||||
// Do narrow phase collision check
|
||||
ts.CollideShape(mShape, mShapeScale, mCenterOfMassTransform, mCollideShapeSettings, mBaseOffset, mCollector, mShapeFilter);
|
||||
|
||||
// After each body, we need to flush the InternalEdgeRemovingCollector because it uses 'ts' as context and it will go out of scope at the end of this block
|
||||
mCollector.Flush();
|
||||
|
||||
// Update early out fraction based on narrow phase collector
|
||||
UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Shape * mShape;
|
||||
Vec3 mShapeScale;
|
||||
RMat44 mCenterOfMassTransform;
|
||||
RVec3 mBaseOffset;
|
||||
const BodyLockInterface & mBodyLockInterface;
|
||||
const BodyFilter & mBodyFilter;
|
||||
const ShapeFilter & mShapeFilter;
|
||||
CollideShapeSettings mCollideShapeSettings;
|
||||
InternalEdgeRemovingCollector mCollector;
|
||||
};
|
||||
|
||||
// Calculate bounds for shape and expand by max separation distance
|
||||
AABox bounds = inShape->GetWorldSpaceBounds(inCenterOfMassTransform, inShapeScale);
|
||||
bounds.ExpandBy(Vec3::sReplicate(inCollideShapeSettings.mMaxSeparationDistance));
|
||||
|
||||
// Do broadphase test
|
||||
MyCollector collector(inShape, inShapeScale, inCenterOfMassTransform, inCollideShapeSettings, inBaseOffset, ioCollector, *mBodyLockInterface, inBodyFilter, inShapeFilter);
|
||||
mBroadPhaseQuery->CollideAABox(bounds, collector, inBroadPhaseLayerFilter, inObjectLayerFilter);
|
||||
InternalEdgeRemovingCollector wrapper(ioCollector);
|
||||
CollideShape(inShape, inShapeScale, inCenterOfMassTransform, settings, inBaseOffset, wrapper, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
|
||||
}
|
||||
|
||||
void NarrowPhaseQuery::CastShape(const RShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, RVec3Arg inBaseOffset, CastShapeCollector &ioCollector, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter) const
|
||||
|
|
@ -409,6 +352,10 @@ void NarrowPhaseQuery::CastShape(const RShapeCast &inShapeCast, const ShapeCastS
|
|||
// Do narrow phase collision check
|
||||
ts.CastShape(mShapeCast, mShapeCastSettings, mBaseOffset, mCollector, mShapeFilter);
|
||||
|
||||
// Notify collector of the end of this body
|
||||
// We do this before updating the early out fraction so that the collector can still modify it
|
||||
mCollector.OnBodyEnd();
|
||||
|
||||
// Update early out fraction based on narrow phase collector
|
||||
UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());
|
||||
}
|
||||
|
|
@ -471,6 +418,10 @@ void NarrowPhaseQuery::CollectTransformedShapes(const AABox &inBox, TransformedS
|
|||
// Do narrow phase collision check
|
||||
ts.CollectTransformedShapes(mBox, mCollector, mShapeFilter);
|
||||
|
||||
// Notify collector of the end of this body
|
||||
// We do this before updating the early out fraction so that the collector can still modify it
|
||||
mCollector.OnBodyEnd();
|
||||
|
||||
// Update early out fraction based on narrow phase collector
|
||||
UpdateEarlyOutFraction(mCollector.GetEarlyOutFraction());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,12 @@ MassProperties CompoundShape::GetMassProperties() const
|
|||
|
||||
AABox CompoundShape::GetWorldSpaceBounds(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale) const
|
||||
{
|
||||
if (mSubShapes.size() <= 10)
|
||||
if (mSubShapes.empty())
|
||||
{
|
||||
// If there are no sub-shapes, we must return an empty box to avoid overflows in the broadphase
|
||||
return AABox(inCenterOfMassTransform.GetTranslation(), inCenterOfMassTransform.GetTranslation());
|
||||
}
|
||||
else if (mSubShapes.size() <= 10)
|
||||
{
|
||||
AABox bounds;
|
||||
for (const SubShape &shape : mSubShapes)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,11 @@ public:
|
|||
RefConst<Shape> mShapePtr; ///< Sub shape (either this or mShape needs to be filled up)
|
||||
Vec3 mPosition; ///< Position of the sub shape
|
||||
Quat mRotation; ///< Rotation of the sub shape
|
||||
uint32 mUserData = 0; ///< User data value (can be used by the application for any purpose)
|
||||
|
||||
/// User data value (can be used by the application for any purpose).
|
||||
/// Note this value can be retrieved through GetSubShape(...).mUserData, not through GetSubShapeUserData(...) as that returns Shape::GetUserData() of the leaf shape.
|
||||
/// Use GetSubShapeIndexFromID get a shape index from a SubShapeID to pass to GetSubShape.
|
||||
uint32 mUserData = 0;
|
||||
};
|
||||
|
||||
using SubShapes = Array<SubShapeSettings>;
|
||||
|
|
@ -338,7 +342,7 @@ protected:
|
|||
}
|
||||
|
||||
Vec3 mCenterOfMass { Vec3::sZero() }; ///< Center of mass of the compound
|
||||
AABox mLocalBounds;
|
||||
AABox mLocalBounds { Vec3::sZero(), Vec3::sZero() };
|
||||
SubShapes mSubShapes;
|
||||
float mInnerRadius = FLT_MAX; ///< Smallest radius of GetInnerRadius() of child shapes
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public:
|
|||
// See: ShapeSettings
|
||||
virtual ShapeResult Create() const override;
|
||||
|
||||
Array<Vec3> mPoints; ///< Points to create the hull from
|
||||
Array<Vec3> mPoints; ///< Points to create the hull from. Note that these points don't need to be the vertices of the convex hull, they can contain interior points or points on faces/edges.
|
||||
float mMaxConvexRadius = 0.0f; ///< Convex radius as supplied by the constructor. Note that during hull creation the convex radius can be made smaller if the value is too big for the hull.
|
||||
float mMaxErrorConvexRadius = 0.05f; ///< Maximum distance between the shrunk hull + convex radius and the actual hull.
|
||||
float mHullTolerance = 1.0e-3f; ///< Points are allowed this far outside of the hull (increasing this yields a hull with less vertices). Note that the actual used value can be larger if the points of the hull are far apart.
|
||||
|
|
|
|||
|
|
@ -57,8 +57,9 @@ void ConvexShape::sCollideConvexVsConvex(const Shape *inShape1, const Shape *inS
|
|||
Mat44 transform_2_to_1 = inverse_transform1 * inCenterOfMassTransform2;
|
||||
|
||||
// Get bounding boxes
|
||||
float max_separation_distance = inCollideShapeSettings.mMaxSeparationDistance;
|
||||
AABox shape1_bbox = shape1->GetLocalBounds().Scaled(inScale1);
|
||||
shape1_bbox.ExpandBy(Vec3::sReplicate(inCollideShapeSettings.mMaxSeparationDistance));
|
||||
shape1_bbox.ExpandBy(Vec3::sReplicate(max_separation_distance));
|
||||
AABox shape2_bbox = shape2->GetLocalBounds().Scaled(inScale2);
|
||||
|
||||
// Check if they overlap
|
||||
|
|
@ -86,10 +87,10 @@ void ConvexShape::sCollideConvexVsConvex(const Shape *inShape1, const Shape *inS
|
|||
const Support *shape2_excl_cvx_radius = shape2->GetSupportFunction(ConvexShape::ESupportMode::ExcludeConvexRadius, buffer2_excl_cvx_radius, inScale2);
|
||||
|
||||
// Transform shape 2 in the space of shape 1
|
||||
TransformedConvexObject<Support> transformed2_excl_cvx_radius(transform_2_to_1, *shape2_excl_cvx_radius);
|
||||
TransformedConvexObject transformed2_excl_cvx_radius(transform_2_to_1, *shape2_excl_cvx_radius);
|
||||
|
||||
// Perform GJK step
|
||||
status = pen_depth.GetPenetrationDepthStepGJK(*shape1_excl_cvx_radius, shape1_excl_cvx_radius->GetConvexRadius() + inCollideShapeSettings.mMaxSeparationDistance, transformed2_excl_cvx_radius, shape2_excl_cvx_radius->GetConvexRadius(), inCollideShapeSettings.mCollisionTolerance, penetration_axis, point1, point2);
|
||||
status = pen_depth.GetPenetrationDepthStepGJK(*shape1_excl_cvx_radius, shape1_excl_cvx_radius->GetConvexRadius() + max_separation_distance, transformed2_excl_cvx_radius, shape2_excl_cvx_radius->GetConvexRadius(), inCollideShapeSettings.mCollisionTolerance, penetration_axis, point1, point2);
|
||||
}
|
||||
|
||||
// Check result of collision detection
|
||||
|
|
@ -105,16 +106,22 @@ void ConvexShape::sCollideConvexVsConvex(const Shape *inShape1, const Shape *inS
|
|||
{
|
||||
// Need to run expensive EPA algorithm
|
||||
|
||||
// We know we're overlapping at this point, so we can set the max separation distance to 0.
|
||||
// Numerically it is possible that GJK finds that the shapes are overlapping but EPA finds that they're separated.
|
||||
// In order to avoid this, we clamp the max separation distance to 1 so that we don't excessively inflate the shape,
|
||||
// but we still inflate it enough to avoid the case where EPA misses the collision.
|
||||
max_separation_distance = min(max_separation_distance, 1.0f);
|
||||
|
||||
// Create support function
|
||||
SupportBuffer buffer1_incl_cvx_radius, buffer2_incl_cvx_radius;
|
||||
const Support *shape1_incl_cvx_radius = shape1->GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer1_incl_cvx_radius, inScale1);
|
||||
const Support *shape2_incl_cvx_radius = shape2->GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer2_incl_cvx_radius, inScale2);
|
||||
|
||||
// Add separation distance
|
||||
AddConvexRadius<Support> shape1_add_max_separation_distance(*shape1_incl_cvx_radius, inCollideShapeSettings.mMaxSeparationDistance);
|
||||
AddConvexRadius shape1_add_max_separation_distance(*shape1_incl_cvx_radius, max_separation_distance);
|
||||
|
||||
// Transform shape 2 in the space of shape 1
|
||||
TransformedConvexObject<Support> transformed2_incl_cvx_radius(transform_2_to_1, *shape2_incl_cvx_radius);
|
||||
TransformedConvexObject transformed2_incl_cvx_radius(transform_2_to_1, *shape2_incl_cvx_radius);
|
||||
|
||||
// Perform EPA step
|
||||
if (!pen_depth.GetPenetrationDepthStepEPA(shape1_add_max_separation_distance, transformed2_incl_cvx_radius, inCollideShapeSettings.mPenetrationTolerance, penetration_axis, point1, point2))
|
||||
|
|
@ -124,14 +131,14 @@ void ConvexShape::sCollideConvexVsConvex(const Shape *inShape1, const Shape *inS
|
|||
}
|
||||
|
||||
// Check if the penetration is bigger than the early out fraction
|
||||
float penetration_depth = (point2 - point1).Length() - inCollideShapeSettings.mMaxSeparationDistance;
|
||||
float penetration_depth = (point2 - point1).Length() - max_separation_distance;
|
||||
if (-penetration_depth >= ioCollector.GetEarlyOutFraction())
|
||||
return;
|
||||
|
||||
// Correct point1 for the added separation distance
|
||||
float penetration_axis_len = penetration_axis.Length();
|
||||
if (penetration_axis_len > 0.0f)
|
||||
point1 -= penetration_axis * (inCollideShapeSettings.mMaxSeparationDistance / penetration_axis_len);
|
||||
point1 -= penetration_axis * (max_separation_distance / penetration_axis_len);
|
||||
|
||||
// Convert to world space
|
||||
point1 = inCenterOfMassTransform1 * point1;
|
||||
|
|
@ -164,7 +171,7 @@ bool ConvexShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubSh
|
|||
|
||||
// Create support function
|
||||
SupportBuffer buffer;
|
||||
const Support *support = GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, Vec3::sReplicate(1.0f));
|
||||
const Support *support = GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, Vec3::sOne());
|
||||
|
||||
// Cast ray
|
||||
GJKClosestPoint gjk;
|
||||
|
|
@ -234,7 +241,7 @@ void ConvexShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubSh
|
|||
{
|
||||
// Create support function
|
||||
SupportBuffer buffer;
|
||||
const Support *support = GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, Vec3::sReplicate(1.0f));
|
||||
const Support *support = GetSupportFunction(ConvexShape::ESupportMode::IncludeConvexRadius, buffer, Vec3::sOne());
|
||||
|
||||
// Create support function for point
|
||||
PointConvexSupport point { inPoint };
|
||||
|
|
@ -312,7 +319,7 @@ public:
|
|||
mLocalToWorld(Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(inScale)),
|
||||
mIsInsideOut(ScaleHelpers::IsInsideOut(inScale))
|
||||
{
|
||||
mSupport = inShape->GetSupportFunction(ESupportMode::IncludeConvexRadius, mSupportBuffer, Vec3::sReplicate(1.0f));
|
||||
mSupport = inShape->GetSupportFunction(ESupportMode::IncludeConvexRadius, mSupportBuffer, Vec3::sOne());
|
||||
}
|
||||
|
||||
SupportBuffer mSupportBuffer;
|
||||
|
|
@ -446,7 +453,7 @@ void ConvexShape::DrawGetSupportFunction(DebugRenderer *inRenderer, RMat44Arg in
|
|||
// Get the support function with convex radius
|
||||
SupportBuffer buffer;
|
||||
const Support *support = GetSupportFunction(ESupportMode::ExcludeConvexRadius, buffer, inScale);
|
||||
AddConvexRadius<Support> add_convex(*support, support->GetConvexRadius());
|
||||
AddConvexRadius add_convex(*support, support->GetConvexRadius());
|
||||
|
||||
// Draw the shape
|
||||
DebugRenderer::GeometryRef geometry = inRenderer->CreateTriangleGeometryForConvex([&add_convex](Vec3Arg inDirection) { return add_convex.GetSupport(inDirection); });
|
||||
|
|
@ -498,7 +505,7 @@ void ConvexShape::DrawGetSupportingFace(DebugRenderer *inRenderer, RMat44Arg inC
|
|||
SupportingFace face = ftd.first;
|
||||
|
||||
// Displace the face a little bit forward so it is easier to see
|
||||
Vec3 normal = face.size() >= 3? (face[2] - face[1]).Cross(face[0] - face[1]).Normalized() : Vec3::sZero();
|
||||
Vec3 normal = face.size() >= 3? (face[2] - face[1]).Cross(face[0] - face[1]).NormalizedOr(Vec3::sZero()) : Vec3::sZero();
|
||||
Vec3 displacement = 0.001f * normal;
|
||||
|
||||
// Transform face to world space and calculate center of mass
|
||||
|
|
|
|||
|
|
@ -200,13 +200,14 @@ void CylinderShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg in
|
|||
float scaled_radius = scale_xz * mRadius;
|
||||
|
||||
float x = inDirection.GetX(), y = inDirection.GetY(), z = inDirection.GetZ();
|
||||
float o = sqrt(Square(x) + Square(z));
|
||||
float xz_sq = Square(x) + Square(z);
|
||||
float y_sq = Square(y);
|
||||
|
||||
// If o / |y| > scaled_radius / scaled_half_height, we're hitting the side
|
||||
if (o * scaled_half_height > scaled_radius * abs(y))
|
||||
// Check which component is bigger
|
||||
if (xz_sq > y_sq)
|
||||
{
|
||||
// Hitting side
|
||||
float f = -scaled_radius / o;
|
||||
float f = -scaled_radius / sqrt(xz_sq);
|
||||
float vx = x * f;
|
||||
float vz = z * f;
|
||||
outVertices.push_back(inCenterOfMassTransform * Vec3(vx, scaled_half_height, vz));
|
||||
|
|
@ -215,8 +216,21 @@ void CylinderShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg in
|
|||
else
|
||||
{
|
||||
// Hitting top or bottom
|
||||
|
||||
// When the inDirection is more than 5 degrees from vertical, align the vertices so that 1 of the vertices
|
||||
// points towards inDirection in the XZ plane. This ensures that we always have a vertex towards max penetration depth.
|
||||
Mat44 transform = inCenterOfMassTransform;
|
||||
if (xz_sq > 0.00765427f * y_sq)
|
||||
{
|
||||
Vec4 base_x = Vec4(x, 0, z, 0) / sqrt(xz_sq);
|
||||
Vec4 base_z = base_x.Swizzle<SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X, SWIZZLE_W>() * Vec4(-1, 0, 1, 0);
|
||||
transform = transform * Mat44(base_x, Vec4(0, 1, 0, 0), base_z, Vec4(0, 0, 0, 1));
|
||||
}
|
||||
|
||||
// Adjust for scale and height
|
||||
Vec3 multiplier = y < 0.0f? Vec3(scaled_radius, scaled_half_height, scaled_radius) : Vec3(-scaled_radius, -scaled_half_height, scaled_radius);
|
||||
Mat44 transform = inCenterOfMassTransform.PreScaled(multiplier);
|
||||
transform = transform.PreScaled(multiplier);
|
||||
|
||||
for (const Vec3 &v : cCylinderTopFace)
|
||||
outVertices.push_back(transform * v);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,119 +201,155 @@ uint32 HeightFieldShapeSettings::CalculateBitsPerSampleForError(float inMaxError
|
|||
|
||||
void HeightFieldShape::CalculateActiveEdges(uint inX, uint inY, uint inSizeX, uint inSizeY, const float *inHeights, uint inHeightsStartX, uint inHeightsStartY, intptr_t inHeightsStride, float inHeightsScale, float inActiveEdgeCosThresholdAngle, TempAllocator &inAllocator)
|
||||
{
|
||||
// Limit the block size so we don't allocate more than 64K memory from the temp allocator
|
||||
uint block_size_x = min(inSizeX, 44u);
|
||||
uint block_size_y = min(inSizeY, 44u);
|
||||
|
||||
// Allocate temporary buffer for normals
|
||||
uint normals_size = 2 * inSizeX * inSizeY * sizeof(Vec3);
|
||||
uint normals_size = 2 * (block_size_x + 1) * (block_size_y + 1) * sizeof(Vec3);
|
||||
Vec3 *normals = (Vec3 *)inAllocator.Allocate(normals_size);
|
||||
JPH_SCOPE_EXIT([&inAllocator, normals, normals_size]{ inAllocator.Free(normals, normals_size); });
|
||||
|
||||
// Calculate triangle normals and make normals zero for triangles that are missing
|
||||
Vec3 *out_normal = normals;
|
||||
for (uint y = 0; y < inSizeY; ++y)
|
||||
for (uint x = 0; x < inSizeX; ++x)
|
||||
// Update the edges in blocks
|
||||
for (uint block_y = 0; block_y < inSizeY; block_y += block_size_y)
|
||||
for (uint block_x = 0; block_x < inSizeX; block_x += block_size_x)
|
||||
{
|
||||
// Get height on diagonal
|
||||
const float *height_samples = inHeights + (inY - inHeightsStartY + y) * inHeightsStride + (inX - inHeightsStartX + x);
|
||||
float x1y1_h = height_samples[0];
|
||||
float x2y2_h = height_samples[inHeightsStride + 1];
|
||||
if (x1y1_h != cNoCollisionValue && x2y2_h != cNoCollisionValue)
|
||||
{
|
||||
// Calculate normal for lower left triangle (e.g. T1A)
|
||||
float x1y2_h = height_samples[inHeightsStride];
|
||||
if (x1y2_h != cNoCollisionValue)
|
||||
{
|
||||
Vec3 x2y2_minus_x1y2(mScale.GetX(), inHeightsScale * (x2y2_h - x1y2_h), 0);
|
||||
Vec3 x1y1_minus_x1y2(0, inHeightsScale * (x1y1_h - x1y2_h), -mScale.GetZ());
|
||||
out_normal[0] = x2y2_minus_x1y2.Cross(x1y1_minus_x1y2).Normalized();
|
||||
}
|
||||
else
|
||||
out_normal[0] = Vec3::sZero();
|
||||
// Calculate the bottom right corner of the block
|
||||
uint block_x_end = min(block_x + block_size_x, inSizeX);
|
||||
uint block_y_end = min(block_y + block_size_y, inSizeY);
|
||||
|
||||
// Calculate normal for upper right triangle (e.g. T1B)
|
||||
float x2y1_h = height_samples[1];
|
||||
if (x2y1_h != cNoCollisionValue)
|
||||
{
|
||||
Vec3 x1y1_minus_x2y1(-mScale.GetX(), inHeightsScale * (x1y1_h - x2y1_h), 0);
|
||||
Vec3 x2y2_minus_x2y1(0, inHeightsScale * (x2y2_h - x2y1_h), mScale.GetZ());
|
||||
out_normal[1] = x1y1_minus_x2y1.Cross(x2y2_minus_x2y1).Normalized();
|
||||
}
|
||||
else
|
||||
out_normal[1] = Vec3::sZero();
|
||||
// If we're not at the first block in x, we need one extra column of normals to the left
|
||||
uint normals_x_start, normals_x_skip;
|
||||
if (block_x > 0)
|
||||
{
|
||||
normals_x_start = block_x - 1;
|
||||
normals_x_skip = 2; // We need to skip over that extra column
|
||||
}
|
||||
else
|
||||
{
|
||||
out_normal[0] = Vec3::sZero();
|
||||
out_normal[1] = Vec3::sZero();
|
||||
normals_x_start = 0;
|
||||
normals_x_skip = 0;
|
||||
}
|
||||
|
||||
out_normal += 2;
|
||||
// If we're not at the last block in y, we need one extra row of normals at the bottom
|
||||
uint normals_y_end = block_y_end < inSizeY? block_y_end + 1 : inSizeY;
|
||||
|
||||
// Calculate triangle normals and make normals zero for triangles that are missing
|
||||
Vec3 *out_normal = normals;
|
||||
for (uint y = block_y; y < normals_y_end; ++y)
|
||||
{
|
||||
for (uint x = normals_x_start; x < block_x_end; ++x)
|
||||
{
|
||||
// Get height on diagonal
|
||||
const float *height_samples = inHeights + (inY - inHeightsStartY + y) * inHeightsStride + (inX - inHeightsStartX + x);
|
||||
float x1y1_h = height_samples[0];
|
||||
float x2y2_h = height_samples[inHeightsStride + 1];
|
||||
if (x1y1_h != cNoCollisionValue && x2y2_h != cNoCollisionValue)
|
||||
{
|
||||
// Calculate normal for lower left triangle (e.g. T1A)
|
||||
float x1y2_h = height_samples[inHeightsStride];
|
||||
if (x1y2_h != cNoCollisionValue)
|
||||
{
|
||||
Vec3 x2y2_minus_x1y2(mScale.GetX(), inHeightsScale * (x2y2_h - x1y2_h), 0);
|
||||
Vec3 x1y1_minus_x1y2(0, inHeightsScale * (x1y1_h - x1y2_h), -mScale.GetZ());
|
||||
out_normal[0] = x2y2_minus_x1y2.Cross(x1y1_minus_x1y2).Normalized();
|
||||
}
|
||||
else
|
||||
out_normal[0] = Vec3::sZero();
|
||||
|
||||
// Calculate normal for upper right triangle (e.g. T1B)
|
||||
float x2y1_h = height_samples[1];
|
||||
if (x2y1_h != cNoCollisionValue)
|
||||
{
|
||||
Vec3 x1y1_minus_x2y1(-mScale.GetX(), inHeightsScale * (x1y1_h - x2y1_h), 0);
|
||||
Vec3 x2y2_minus_x2y1(0, inHeightsScale * (x2y2_h - x2y1_h), mScale.GetZ());
|
||||
out_normal[1] = x1y1_minus_x2y1.Cross(x2y2_minus_x2y1).Normalized();
|
||||
}
|
||||
else
|
||||
out_normal[1] = Vec3::sZero();
|
||||
}
|
||||
else
|
||||
{
|
||||
out_normal[0] = Vec3::sZero();
|
||||
out_normal[1] = Vec3::sZero();
|
||||
}
|
||||
|
||||
out_normal += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Number of vectors to skip to get to the next row of normals
|
||||
uint normals_pitch = 2 * (block_x_end - normals_x_start);
|
||||
|
||||
// Calculate active edges
|
||||
const Vec3 *in_normal = normals;
|
||||
uint global_bit_pos = 3 * ((inY + block_y) * (mSampleCount - 1) + (inX + block_x));
|
||||
for (uint y = block_y; y < block_y_end; ++y)
|
||||
{
|
||||
in_normal += normals_x_skip; // If we have an extra column to the left, skip it here, we'll read it with in_normal[-1] below
|
||||
|
||||
for (uint x = block_x; x < block_x_end; ++x)
|
||||
{
|
||||
// Get vertex heights
|
||||
const float *height_samples = inHeights + (inY - inHeightsStartY + y) * inHeightsStride + (inX - inHeightsStartX + x);
|
||||
float x1y1_h = height_samples[0];
|
||||
float x1y2_h = height_samples[inHeightsStride];
|
||||
float x2y2_h = height_samples[inHeightsStride + 1];
|
||||
bool x1y1_valid = x1y1_h != cNoCollisionValue;
|
||||
bool x1y2_valid = x1y2_h != cNoCollisionValue;
|
||||
bool x2y2_valid = x2y2_h != cNoCollisionValue;
|
||||
|
||||
// Calculate the edge flags (3 bits)
|
||||
// See diagram in the next function for the edge numbering
|
||||
uint16 edge_mask = 0b111;
|
||||
uint16 edge_flags = 0;
|
||||
|
||||
// Edge 0
|
||||
if (x == 0)
|
||||
edge_mask &= 0b110; // We need normal x - 1 which we didn't calculate, don't update this edge
|
||||
else if (x1y1_valid && x1y2_valid)
|
||||
{
|
||||
Vec3 edge0_direction(0, inHeightsScale * (x1y2_h - x1y1_h), mScale.GetZ());
|
||||
if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[-1], edge0_direction, inActiveEdgeCosThresholdAngle))
|
||||
edge_flags |= 0b001;
|
||||
}
|
||||
|
||||
// Edge 1
|
||||
if (y == inSizeY - 1)
|
||||
edge_mask &= 0b101; // We need normal y + 1 which we didn't calculate, don't update this edge
|
||||
else if (x1y2_valid && x2y2_valid)
|
||||
{
|
||||
Vec3 edge1_direction(mScale.GetX(), inHeightsScale * (x2y2_h - x1y2_h), 0);
|
||||
if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[normals_pitch + 1], edge1_direction, inActiveEdgeCosThresholdAngle))
|
||||
edge_flags |= 0b010;
|
||||
}
|
||||
|
||||
// Edge 2
|
||||
if (x1y1_valid && x2y2_valid)
|
||||
{
|
||||
Vec3 edge2_direction(-mScale.GetX(), inHeightsScale * (x1y1_h - x2y2_h), -mScale.GetZ());
|
||||
if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[1], edge2_direction, inActiveEdgeCosThresholdAngle))
|
||||
edge_flags |= 0b100;
|
||||
}
|
||||
|
||||
// Store the edge flags in the array
|
||||
uint byte_pos = global_bit_pos >> 3;
|
||||
uint bit_pos = global_bit_pos & 0b111;
|
||||
JPH_ASSERT(byte_pos < mActiveEdgesSize);
|
||||
uint8 *edge_flags_ptr = &mActiveEdges[byte_pos];
|
||||
uint16 combined_edge_flags = uint16(edge_flags_ptr[0]) | uint16(uint16(edge_flags_ptr[1]) << 8);
|
||||
combined_edge_flags &= ~(edge_mask << bit_pos);
|
||||
combined_edge_flags |= edge_flags << bit_pos;
|
||||
edge_flags_ptr[0] = uint8(combined_edge_flags);
|
||||
edge_flags_ptr[1] = uint8(combined_edge_flags >> 8);
|
||||
|
||||
in_normal += 2;
|
||||
global_bit_pos += 3;
|
||||
}
|
||||
|
||||
global_bit_pos += 3 * (mSampleCount - 1 - (block_x_end - block_x));
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate active edges
|
||||
const Vec3 *in_normal = normals;
|
||||
uint global_bit_pos = 3 * (inY * (mSampleCount - 1) + inX);
|
||||
for (uint y = 0; y < inSizeY; ++y)
|
||||
{
|
||||
for (uint x = 0; x < inSizeX; ++x)
|
||||
{
|
||||
// Get vertex heights
|
||||
const float *height_samples = inHeights + (inY - inHeightsStartY + y) * inHeightsStride + (inX - inHeightsStartX + x);
|
||||
float x1y1_h = height_samples[0];
|
||||
float x1y2_h = height_samples[inHeightsStride];
|
||||
float x2y2_h = height_samples[inHeightsStride + 1];
|
||||
bool x1y1_valid = x1y1_h != cNoCollisionValue;
|
||||
bool x1y2_valid = x1y2_h != cNoCollisionValue;
|
||||
bool x2y2_valid = x2y2_h != cNoCollisionValue;
|
||||
|
||||
// Calculate the edge flags (3 bits)
|
||||
// See diagram in the next function for the edge numbering
|
||||
uint16 edge_mask = 0b111;
|
||||
uint16 edge_flags = 0;
|
||||
|
||||
// Edge 0
|
||||
if (x == 0)
|
||||
edge_mask &= 0b110; // We need normal x - 1 which we didn't calculate, don't update this edge
|
||||
else if (x1y1_valid && x1y2_valid)
|
||||
{
|
||||
Vec3 edge0_direction(0, inHeightsScale * (x1y2_h - x1y1_h), mScale.GetZ());
|
||||
if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[-1], edge0_direction, inActiveEdgeCosThresholdAngle))
|
||||
edge_flags |= 0b001;
|
||||
}
|
||||
|
||||
// Edge 1
|
||||
if (y == inSizeY - 1)
|
||||
edge_mask &= 0b101; // We need normal y + 1 which we didn't calculate, don't update this edge
|
||||
else if (x1y2_valid && x2y2_valid)
|
||||
{
|
||||
Vec3 edge1_direction(mScale.GetX(), inHeightsScale * (x2y2_h - x1y2_h), 0);
|
||||
if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[2 * inSizeX + 1], edge1_direction, inActiveEdgeCosThresholdAngle))
|
||||
edge_flags |= 0b010;
|
||||
}
|
||||
|
||||
// Edge 2
|
||||
if (x1y1_valid && x2y2_valid)
|
||||
{
|
||||
Vec3 edge2_direction(-mScale.GetX(), inHeightsScale * (x1y1_h - x2y2_h), -mScale.GetZ());
|
||||
if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[1], edge2_direction, inActiveEdgeCosThresholdAngle))
|
||||
edge_flags |= 0b100;
|
||||
}
|
||||
|
||||
// Store the edge flags in the array
|
||||
uint byte_pos = global_bit_pos >> 3;
|
||||
uint bit_pos = global_bit_pos & 0b111;
|
||||
JPH_ASSERT(byte_pos < mActiveEdgesSize);
|
||||
uint8 *edge_flags_ptr = &mActiveEdges[byte_pos];
|
||||
uint16 combined_edge_flags = uint16(edge_flags_ptr[0]) | uint16(uint16(edge_flags_ptr[1]) << 8);
|
||||
combined_edge_flags &= ~(edge_mask << bit_pos);
|
||||
combined_edge_flags |= edge_flags << bit_pos;
|
||||
edge_flags_ptr[0] = uint8(combined_edge_flags);
|
||||
edge_flags_ptr[1] = uint8(combined_edge_flags >> 8);
|
||||
|
||||
in_normal += 2;
|
||||
global_bit_pos += 3;
|
||||
}
|
||||
|
||||
global_bit_pos += 3 * (mSampleCount - 1 - inSizeX);
|
||||
}
|
||||
}
|
||||
|
||||
void HeightFieldShape::CalculateActiveEdges(const HeightFieldShapeSettings &inSettings)
|
||||
|
|
@ -1710,7 +1746,7 @@ public:
|
|||
JPH_INLINE explicit DecodingContext(const HeightFieldShape *inShape) :
|
||||
mShape(inShape)
|
||||
{
|
||||
static_assert(sizeof(sGridOffsets) / sizeof(uint) == cNumBitsXY + 1, "Offsets array is not long enough");
|
||||
static_assert(std::size(sGridOffsets) == cNumBitsXY + 1, "Offsets array is not long enough");
|
||||
|
||||
// Construct root stack entry
|
||||
mPropertiesStack[0] = 0; // level: 0, x: 0, y: 0
|
||||
|
|
@ -1869,8 +1905,8 @@ public:
|
|||
uint32 stride = block_size_plus_1 - size_x_plus_1;
|
||||
|
||||
// Start range with a very large inside-out box
|
||||
Vec3 value_min = Vec3::sReplicate(1.0e30f);
|
||||
Vec3 value_max = Vec3::sReplicate(-1.0e30f);
|
||||
Vec3 value_min = Vec3::sReplicate(cLargeFloat);
|
||||
Vec3 value_max = Vec3::sReplicate(-cLargeFloat);
|
||||
|
||||
// Loop over the samples to determine the min and max of this block
|
||||
for (uint32 block_y = 0; block_y < size_y_plus_1; ++block_y)
|
||||
|
|
|
|||
|
|
@ -69,14 +69,14 @@ public:
|
|||
/// The height field is a surface defined by: mOffset + mScale * (x, mHeightSamples[y * mSampleCount + x], y).
|
||||
/// where x and y are integers in the range x and y e [0, mSampleCount - 1].
|
||||
Vec3 mOffset = Vec3::sZero();
|
||||
Vec3 mScale = Vec3::sReplicate(1.0f);
|
||||
Vec3 mScale = Vec3::sOne();
|
||||
uint32 mSampleCount = 0;
|
||||
|
||||
/// Artificial minimal value of mHeightSamples, used for compression and can be used to update the terrain after creating with lower height values. If there are any lower values in mHeightSamples, this value will be ignored.
|
||||
float mMinHeightValue = FLT_MAX;
|
||||
float mMinHeightValue = cLargeFloat;
|
||||
|
||||
/// Artificial maximum value of mHeightSamples, used for compression and can be used to update the terrain after creating with higher height values. If there are any higher values in mHeightSamples, this value will be ignored.
|
||||
float mMaxHeightValue = -FLT_MAX;
|
||||
float mMaxHeightValue = -cLargeFloat;
|
||||
|
||||
/// When bigger than mMaterials.size() the internal material list will be preallocated to support this number of materials.
|
||||
/// This avoids reallocations when calling HeightFieldShape::SetMaterials with new materials later.
|
||||
|
|
@ -349,7 +349,7 @@ private:
|
|||
/// The height field is a surface defined by: mOffset + mScale * (x, mHeightSamples[y * mSampleCount + x], y).
|
||||
/// where x and y are integers in the range x and y e [0, mSampleCount - 1].
|
||||
Vec3 mOffset = Vec3::sZero();
|
||||
Vec3 mScale = Vec3::sReplicate(1.0f);
|
||||
Vec3 mScale = Vec3::sOne();
|
||||
|
||||
/// Height data
|
||||
uint32 mSampleCount = 0; ///< See HeightFieldShapeSettings::mSampleCount
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include <Jolt/Geometry/Plane.h>
|
||||
#include <Jolt/Geometry/OrientedBox.h>
|
||||
#include <Jolt/TriangleSplitter/TriangleSplitterBinning.h>
|
||||
#include <Jolt/TriangleSplitter/TriangleSplitterMean.h>
|
||||
#include <Jolt/AABBTree/AABBTreeBuilder.h>
|
||||
#include <Jolt/AABBTree/AABBTreeToBuffer.h>
|
||||
#include <Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h>
|
||||
|
|
@ -55,6 +56,7 @@ JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(MeshShapeSettings)
|
|||
JPH_ADD_ATTRIBUTE(MeshShapeSettings, mMaxTrianglesPerLeaf)
|
||||
JPH_ADD_ATTRIBUTE(MeshShapeSettings, mActiveEdgeCosThresholdAngle)
|
||||
JPH_ADD_ATTRIBUTE(MeshShapeSettings, mPerTriangleUserData)
|
||||
JPH_ADD_ENUM_ATTRIBUTE(MeshShapeSettings, mBuildQuality)
|
||||
}
|
||||
|
||||
// Codecs this mesh shape is using
|
||||
|
|
@ -190,12 +192,36 @@ MeshShape::MeshShape(const MeshShapeSettings &inSettings, ShapeResult &outResult
|
|||
sFindActiveEdges(inSettings, indexed_triangles);
|
||||
|
||||
// Create triangle splitter
|
||||
TriangleSplitterBinning splitter(inSettings.mTriangleVertices, indexed_triangles);
|
||||
union Storage
|
||||
{
|
||||
Storage() { }
|
||||
~Storage() { }
|
||||
|
||||
TriangleSplitterBinning mBinning;
|
||||
TriangleSplitterMean mMean;
|
||||
};
|
||||
Storage storage;
|
||||
TriangleSplitter *splitter = nullptr;
|
||||
switch (inSettings.mBuildQuality)
|
||||
{
|
||||
case MeshShapeSettings::EBuildQuality::FavorRuntimePerformance:
|
||||
splitter = new (&storage.mBinning) TriangleSplitterBinning(inSettings.mTriangleVertices, indexed_triangles);
|
||||
break;
|
||||
|
||||
case MeshShapeSettings::EBuildQuality::FavorBuildSpeed:
|
||||
splitter = new (&storage.mMean) TriangleSplitterMean(inSettings.mTriangleVertices, indexed_triangles);
|
||||
break;
|
||||
|
||||
default:
|
||||
JPH_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Build tree
|
||||
AABBTreeBuilder builder(splitter, inSettings.mMaxTrianglesPerLeaf);
|
||||
AABBTreeBuilder builder(*splitter, inSettings.mMaxTrianglesPerLeaf);
|
||||
AABBTreeBuilderStats builder_stats;
|
||||
const AABBTreeBuilder::Node *root = builder.Build(builder_stats);
|
||||
splitter->~TriangleSplitter();
|
||||
|
||||
// Convert to buffer
|
||||
AABBTreeToBuffer<TriangleCodec, NodeCodec> buffer;
|
||||
|
|
@ -221,6 +247,14 @@ MeshShape::MeshShape(const MeshShapeSettings &inSettings, ShapeResult &outResult
|
|||
|
||||
void MeshShape::sFindActiveEdges(const MeshShapeSettings &inSettings, IndexedTriangleList &ioIndices)
|
||||
{
|
||||
// Check if we're requested to make all edges active
|
||||
if (inSettings.mActiveEdgeCosThresholdAngle < 0.0f)
|
||||
{
|
||||
for (IndexedTriangle &triangle : ioIndices)
|
||||
triangle.mMaterialIndex |= 0b111 << FLAGS_ACTIVE_EGDE_SHIFT;
|
||||
return;
|
||||
}
|
||||
|
||||
// A struct to hold the two vertex indices of an edge
|
||||
struct Edge
|
||||
{
|
||||
|
|
@ -349,7 +383,7 @@ MassProperties MeshShape::GetMassProperties() const
|
|||
// creating a Body:
|
||||
//
|
||||
// BodyCreationSettings::mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
|
||||
// BodyCreationSettings::mMassPropertiesOverride.SetMassAndInertiaOfSolidBox(Vec3::sReplicate(1.0f), 1000.0f);
|
||||
// BodyCreationSettings::mMassPropertiesOverride.SetMassAndInertiaOfSolidBox(Vec3::sOne(), 1000.0f);
|
||||
//
|
||||
// Note that for a mesh shape to simulate properly, it is best if the mesh is manifold
|
||||
// (i.e. closed, all edges shared by only two triangles, consistent winding order).
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ public:
|
|||
/// Cosine of the threshold angle (if the angle between the two triangles is bigger than this, the edge is active, note that a concave edge is always inactive).
|
||||
/// Setting this value too small can cause ghost collisions with edges, setting it too big can cause depenetration artifacts (objects not depenetrating quickly).
|
||||
/// Valid ranges are between cos(0 degrees) and cos(90 degrees). The default value is cos(5 degrees).
|
||||
/// Negative values will make all edges active and causes EActiveEdgeMode::CollideOnlyWithActive to behave as EActiveEdgeMode::CollideWithAll.
|
||||
/// This speeds up the build process but will require all bodies that can interact with the mesh to use BodyCreationSettings::mEnhancedInternalEdgeRemoval = true.
|
||||
float mActiveEdgeCosThresholdAngle = 0.996195f; // cos(5 degrees)
|
||||
|
||||
/// When true, we store the user data coming from Triangle::mUserData or IndexedTriangle::mUserData in the mesh shape.
|
||||
|
|
@ -65,6 +67,15 @@ public:
|
|||
/// Can be retrieved using MeshShape::GetTriangleUserData.
|
||||
/// Turning this on increases the memory used by the MeshShape by roughly 25%.
|
||||
bool mPerTriangleUserData = false;
|
||||
|
||||
enum class EBuildQuality
|
||||
{
|
||||
FavorRuntimePerformance, ///< Favor runtime performance, takes more time to build the MeshShape but performs better
|
||||
FavorBuildSpeed, ///< Favor build speed, build the tree faster but the MeshShape will be slower
|
||||
};
|
||||
|
||||
/// Determines the quality of the tree building process.
|
||||
EBuildQuality mBuildQuality = EBuildQuality::FavorRuntimePerformance;
|
||||
};
|
||||
|
||||
/// A mesh shape, consisting of triangles. Mesh shapes are mostly used for static geometry.
|
||||
|
|
|
|||
|
|
@ -142,8 +142,8 @@ void MutableCompoundShape::CalculateLocalBounds()
|
|||
}
|
||||
else
|
||||
{
|
||||
// There are no subshapes, set the bounding box to invalid
|
||||
mLocalBounds.SetEmpty();
|
||||
// There are no subshapes, make the bounding box empty
|
||||
mLocalBounds.mMin = mLocalBounds.mMax = Vec3::sZero();
|
||||
}
|
||||
|
||||
// Cache the inner radius as it can take a while to recursively iterate over all sub shapes
|
||||
|
|
@ -181,7 +181,7 @@ void MutableCompoundShape::CalculateSubShapeBounds(uint inStartIdx, uint inNumbe
|
|||
Mat44 transform = Mat44::sRotationTranslation(sub_shape.GetRotation(), sub_shape.GetPositionCOM());
|
||||
|
||||
// Get the bounding box
|
||||
sub_shape_bounds = sub_shape.mShape->GetWorldSpaceBounds(transform, Vec3::sReplicate(1.0f));
|
||||
sub_shape_bounds = sub_shape.mShape->GetWorldSpaceBounds(transform, Vec3::sOne());
|
||||
}
|
||||
|
||||
// Put the bounds as columns in a matrix
|
||||
|
|
@ -206,29 +206,37 @@ void MutableCompoundShape::CalculateSubShapeBounds(uint inStartIdx, uint inNumbe
|
|||
CalculateLocalBounds();
|
||||
}
|
||||
|
||||
uint MutableCompoundShape::AddShape(Vec3Arg inPosition, QuatArg inRotation, const Shape *inShape, uint32 inUserData)
|
||||
uint MutableCompoundShape::AddShape(Vec3Arg inPosition, QuatArg inRotation, const Shape *inShape, uint32 inUserData, uint inIndex)
|
||||
{
|
||||
SubShape sub_shape;
|
||||
sub_shape.mShape = inShape;
|
||||
sub_shape.mUserData = inUserData;
|
||||
sub_shape.SetTransform(inPosition, inRotation, mCenterOfMass);
|
||||
mSubShapes.push_back(sub_shape);
|
||||
uint shape_idx = (uint)mSubShapes.size() - 1;
|
||||
|
||||
CalculateSubShapeBounds(shape_idx, 1);
|
||||
|
||||
return shape_idx;
|
||||
if (inIndex >= mSubShapes.size())
|
||||
{
|
||||
uint shape_idx = uint(mSubShapes.size());
|
||||
mSubShapes.push_back(sub_shape);
|
||||
CalculateSubShapeBounds(shape_idx, 1);
|
||||
return shape_idx;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSubShapes.insert(mSubShapes.begin() + inIndex, sub_shape);
|
||||
CalculateSubShapeBounds(inIndex, uint(mSubShapes.size()) - inIndex);
|
||||
return inIndex;
|
||||
}
|
||||
}
|
||||
|
||||
void MutableCompoundShape::RemoveShape(uint inIndex)
|
||||
{
|
||||
mSubShapes.erase(mSubShapes.begin() + inIndex);
|
||||
|
||||
// We always need to recalculate the bounds of the sub shapes as we test blocks
|
||||
// of 4 sub shapes at a time and removed shapes get their bounds updated
|
||||
// to repeat the bounds of the previous sub shape
|
||||
uint num_bounds = (uint)mSubShapes.size() - inIndex;
|
||||
if (num_bounds > 0)
|
||||
CalculateSubShapeBounds(inIndex, num_bounds);
|
||||
else
|
||||
CalculateLocalBounds();
|
||||
CalculateSubShapeBounds(inIndex, num_bounds);
|
||||
}
|
||||
|
||||
void MutableCompoundShape::ModifyShape(uint inIndex, Vec3Arg inPosition, QuatArg inRotation)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ public:
|
|||
/// Note: If you're using MutableCompoundShape and are querying data while modifying the shape you'll have a race condition.
|
||||
/// In this case it is best to create a new MutableCompoundShape using the Clone function. You replace the shape on a body using BodyInterface::SetShape.
|
||||
/// If a query is still working on the old shape, it will have taken a reference and keep the old shape alive until the query finishes.
|
||||
///
|
||||
/// When you modify a MutableCompoundShape, beware that the SubShapeIDs of all other shapes can change. So be careful when storing SubShapeIDs.
|
||||
class JPH_EXPORT MutableCompoundShape final : public CompoundShape
|
||||
{
|
||||
public:
|
||||
|
|
@ -66,8 +68,13 @@ public:
|
|||
|
||||
/// Adding a new shape.
|
||||
/// Beware this can create a race condition if you're running collision queries in parallel. See class documentation for more information.
|
||||
/// @param inPosition The position of the new shape
|
||||
/// @param inRotation The orientation of the new shape
|
||||
/// @param inShape The shape to add
|
||||
/// @param inUserData User data that will be stored with the shape and can be retrieved using GetCompoundUserData
|
||||
/// @param inIndex Index where to insert the shape, UINT_MAX to add to the end
|
||||
/// @return The index of the newly added shape
|
||||
uint AddShape(Vec3Arg inPosition, QuatArg inRotation, const Shape *inShape, uint32 inUserData = 0);
|
||||
uint AddShape(Vec3Arg inPosition, QuatArg inRotation, const Shape *inShape, uint32 inUserData = 0, uint inIndex = UINT_MAX);
|
||||
|
||||
/// Remove a shape by index.
|
||||
/// Beware this can create a race condition if you're running collision queries in parallel. See class documentation for more information.
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ public:
|
|||
virtual MassProperties GetMassProperties() const override;
|
||||
|
||||
// See Shape::GetMaterial
|
||||
virtual const PhysicsMaterial * GetMaterial(const SubShapeID &inSubShapeID) const override { JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID"); return mMaterial != nullptr? mMaterial : PhysicsMaterial::sDefault; }
|
||||
virtual const PhysicsMaterial * GetMaterial(const SubShapeID &inSubShapeID) const override { JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID"); return GetMaterial(); }
|
||||
|
||||
// See Shape::GetSurfaceNormal
|
||||
virtual Vec3 GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const override { JPH_ASSERT(inSubShapeID.IsEmpty(), "Invalid subshape ID"); return mPlane.GetNormal(); }
|
||||
|
|
@ -114,6 +114,10 @@ public:
|
|||
// See Shape::GetVolume
|
||||
virtual float GetVolume() const override { return 0; }
|
||||
|
||||
/// Material of the shape
|
||||
void SetMaterial(const PhysicsMaterial *inMaterial) { mMaterial = inMaterial; }
|
||||
const PhysicsMaterial * GetMaterial() const { return mMaterial != nullptr? mMaterial : PhysicsMaterial::sDefault; }
|
||||
|
||||
// Register shape functions with the registry
|
||||
static void sRegister();
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace ScaleHelpers
|
|||
static constexpr float cScaleToleranceSq = 1.0e-8f;
|
||||
|
||||
/// Test if a scale is identity
|
||||
inline bool IsNotScaled(Vec3Arg inScale) { return inScale.IsClose(Vec3::sReplicate(1.0f), cScaleToleranceSq); }
|
||||
inline bool IsNotScaled(Vec3Arg inScale) { return inScale.IsClose(Vec3::sOne(), cScaleToleranceSq); }
|
||||
|
||||
/// Test if a scale is uniform
|
||||
inline bool IsUniformScale(Vec3Arg inScale) { return inScale.Swizzle<SWIZZLE_Y, SWIZZLE_Z, SWIZZLE_X>().IsClose(inScale, cScaleToleranceSq); }
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ Vec3 Shape::MakeScaleValid(Vec3Arg inScale) const
|
|||
|
||||
Shape::ShapeResult Shape::ScaleShape(Vec3Arg inScale) const
|
||||
{
|
||||
const Vec3 unit_scale = Vec3::sReplicate(1.0f);
|
||||
const Vec3 unit_scale = Vec3::sOne();
|
||||
|
||||
if (inScale.IsNearZero())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ void SphereShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3
|
|||
void SphereShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
|
||||
{
|
||||
float scaled_radius = GetScaledRadius(inScale);
|
||||
new (&ioContext) GetTrianglesContextVertexList(inPositionCOM, inRotation, Vec3::sReplicate(1.0f), Mat44::sScale(scaled_radius), sUnitSphereTriangles.data(), sUnitSphereTriangles.size(), GetMaterial());
|
||||
new (&ioContext) GetTrianglesContextVertexList(inPositionCOM, inRotation, Vec3::sOne(), Mat44::sScale(scaled_radius), sUnitSphereTriangles.data(), sUnitSphereTriangles.size(), GetMaterial());
|
||||
}
|
||||
|
||||
int SphereShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ StaticCompoundShape::StaticCompoundShape(const StaticCompoundShapeSettings &inSe
|
|||
|
||||
// Transform the shape's bounds into our local space
|
||||
Mat44 transform = Mat44::sRotationTranslation(shape.GetRotation(), shape.GetPositionCOM());
|
||||
AABox shape_bounds = shape.mShape->GetWorldSpaceBounds(transform, Vec3::sReplicate(1.0f));
|
||||
AABox shape_bounds = shape.mShape->GetWorldSpaceBounds(transform, Vec3::sOne());
|
||||
|
||||
// Store bounds and body index for tree construction
|
||||
bounds[i] = shape_bounds;
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ void TaperedCapsuleShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMa
|
|||
if (mGeometry == nullptr)
|
||||
{
|
||||
SupportBuffer buffer;
|
||||
const Support *support = GetSupportFunction(ESupportMode::IncludeConvexRadius, buffer, Vec3::sReplicate(1.0f));
|
||||
const Support *support = GetSupportFunction(ESupportMode::IncludeConvexRadius, buffer, Vec3::sOne());
|
||||
mGeometry = inRenderer->CreateTriangleGeometryForConvex([support](Vec3Arg inDirection) { return support->GetSupport(inDirection); });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -265,24 +265,40 @@ void TaperedCylinderShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec
|
|||
outVertices.push_back(inCenterOfMassTransform * (normal_xz * top_radius + Vec3(0, top, 0)));
|
||||
outVertices.push_back(inCenterOfMassTransform * (normal_xz * bottom_radius + Vec3(0, bottom, 0)));
|
||||
}
|
||||
else if (inDirection.GetY() < 0.0f)
|
||||
{
|
||||
// Top of the cylinder
|
||||
if (top_radius > cMinRadius)
|
||||
{
|
||||
Vec3 top_3d(0, top, 0);
|
||||
for (Vec3 v : cTaperedCylinderFace)
|
||||
outVertices.push_back(inCenterOfMassTransform * (top_radius * v + top_3d));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bottom of the cylinder
|
||||
if (bottom_radius > cMinRadius)
|
||||
// When the inDirection is more than 5 degrees from vertical, align the vertices so that 1 of the vertices
|
||||
// points towards inDirection in the XZ plane. This ensures that we always have a vertex towards max penetration depth.
|
||||
Mat44 transform = inCenterOfMassTransform;
|
||||
Vec4 base_x = Vec4(inDirection.GetX(), 0, inDirection.GetZ(), 0);
|
||||
float xz_sq = base_x.LengthSq();
|
||||
float y_sq = Square(inDirection.GetY());
|
||||
if (xz_sq > 0.00765427f * y_sq)
|
||||
{
|
||||
Vec3 bottom_3d(0, bottom, 0);
|
||||
for (const Vec3 *v = cTaperedCylinderFace + std::size(cTaperedCylinderFace) - 1; v >= cTaperedCylinderFace; --v)
|
||||
outVertices.push_back(inCenterOfMassTransform * (bottom_radius * *v + bottom_3d));
|
||||
base_x /= sqrt(xz_sq);
|
||||
Vec4 base_z = base_x.Swizzle<SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X, SWIZZLE_W>() * Vec4(-1, 0, 1, 0);
|
||||
transform = transform * Mat44(base_x, Vec4(0, 1, 0, 0), base_z, Vec4(0, 0, 0, 1));
|
||||
}
|
||||
|
||||
if (inDirection.GetY() < 0.0f)
|
||||
{
|
||||
// Top of the cylinder
|
||||
if (top_radius > cMinRadius)
|
||||
{
|
||||
Vec3 top_3d(0, top, 0);
|
||||
for (Vec3 v : cTaperedCylinderFace)
|
||||
outVertices.push_back(transform * (top_radius * v + top_3d));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bottom of the cylinder
|
||||
if (bottom_radius > cMinRadius)
|
||||
{
|
||||
Vec3 bottom_3d(0, bottom, 0);
|
||||
for (const Vec3 *v = cTaperedCylinderFace + std::size(cTaperedCylinderFace) - 1; v >= cTaperedCylinderFace; --v)
|
||||
outVertices.push_back(transform * (bottom_radius * *v + bottom_3d));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ MassProperties TriangleShape::GetMassProperties() const
|
|||
// creating a Body:
|
||||
//
|
||||
// BodyCreationSettings::mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
|
||||
// BodyCreationSettings::mMassPropertiesOverride.SetMassAndInertiaOfSolidBox(Vec3::sReplicate(1.0f), 1000.0f);
|
||||
// BodyCreationSettings::mMassPropertiesOverride.SetMassAndInertiaOfSolidBox(Vec3::sOne(), 1000.0f);
|
||||
//
|
||||
// Note that this makes the triangle shape behave the same as a mesh shape with a single triangle.
|
||||
// In practice there is very little use for a dynamic triangle shape as back side collisions will be ignored
|
||||
|
|
@ -413,6 +413,9 @@ void TriangleShape::sRegister()
|
|||
{
|
||||
CollisionDispatch::sRegisterCollideShape(s, EShapeSubType::Triangle, sCollideConvexVsTriangle);
|
||||
CollisionDispatch::sRegisterCastShape(s, EShapeSubType::Triangle, sCastConvexVsTriangle);
|
||||
|
||||
CollisionDispatch::sRegisterCollideShape(EShapeSubType::Triangle, s, CollisionDispatch::sReversedCollideShape);
|
||||
CollisionDispatch::sRegisterCastShape(EShapeSubType::Triangle, s, CollisionDispatch::sReversedCastShape);
|
||||
}
|
||||
|
||||
// Specialized collision functions
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue