godot-module-template/engine/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/MeshShape.cpp

1267 lines
47 KiB
C++

// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <Jolt/Jolt.h>
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
#include <Jolt/Physics/Collision/Shape/ConvexShape.h>
#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/RayCast.h>
#include <Jolt/Physics/Collision/ShapeCast.h>
#include <Jolt/Physics/Collision/ShapeFilter.h>
#include <Jolt/Physics/Collision/CastResult.h>
#include <Jolt/Physics/Collision/CollideConvexVsTriangles.h>
#include <Jolt/Physics/Collision/CollideSphereVsTriangles.h>
#include <Jolt/Physics/Collision/CastConvexVsTriangles.h>
#include <Jolt/Physics/Collision/CastSphereVsTriangles.h>
#include <Jolt/Physics/Collision/TransformedShape.h>
#include <Jolt/Physics/Collision/ActiveEdges.h>
#include <Jolt/Physics/Collision/CollisionDispatch.h>
#include <Jolt/Physics/Collision/SortReverseAndStore.h>
#include <Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h>
#include <Jolt/Core/StringTools.h>
#include <Jolt/Core/StreamIn.h>
#include <Jolt/Core/StreamOut.h>
#include <Jolt/Core/Profiler.h>
#include <Jolt/Core/UnorderedMap.h>
#include <Jolt/Geometry/AABox4.h>
#include <Jolt/Geometry/RayAABox.h>
#include <Jolt/Geometry/Indexify.h>
#include <Jolt/Geometry/Plane.h>
#include <Jolt/Geometry/OrientedBox.h>
#include <Jolt/TriangleSplitter/TriangleSplitterBinning.h>
#include <Jolt/AABBTree/AABBTreeBuilder.h>
#include <Jolt/AABBTree/AABBTreeToBuffer.h>
#include <Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h>
#include <Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h>
#include <Jolt/ObjectStream/TypeDeclarations.h>
JPH_NAMESPACE_BEGIN
#ifdef JPH_DEBUG_RENDERER
bool MeshShape::sDrawTriangleGroups = false;
bool MeshShape::sDrawTriangleOutlines = false;
#endif // JPH_DEBUG_RENDERER
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(MeshShapeSettings)
{
JPH_ADD_BASE_CLASS(MeshShapeSettings, ShapeSettings)
JPH_ADD_ATTRIBUTE(MeshShapeSettings, mTriangleVertices)
JPH_ADD_ATTRIBUTE(MeshShapeSettings, mIndexedTriangles)
JPH_ADD_ATTRIBUTE(MeshShapeSettings, mMaterials)
JPH_ADD_ATTRIBUTE(MeshShapeSettings, mMaxTrianglesPerLeaf)
JPH_ADD_ATTRIBUTE(MeshShapeSettings, mActiveEdgeCosThresholdAngle)
JPH_ADD_ATTRIBUTE(MeshShapeSettings, mPerTriangleUserData)
}
// Codecs this mesh shape is using
using TriangleCodec = TriangleCodecIndexed8BitPackSOA4Flags;
using NodeCodec = NodeCodecQuadTreeHalfFloat;
// Get header for tree
static JPH_INLINE const NodeCodec::Header *sGetNodeHeader(const ByteBuffer &inTree)
{
return inTree.Get<NodeCodec::Header>(0);
}
// Get header for triangles
static JPH_INLINE const TriangleCodec::TriangleHeader *sGetTriangleHeader(const ByteBuffer &inTree)
{
return inTree.Get<TriangleCodec::TriangleHeader>(NodeCodec::HeaderSize);
}
MeshShapeSettings::MeshShapeSettings(const TriangleList &inTriangles, PhysicsMaterialList inMaterials) :
mMaterials(std::move(inMaterials))
{
Indexify(inTriangles, mTriangleVertices, mIndexedTriangles);
Sanitize();
}
MeshShapeSettings::MeshShapeSettings(VertexList inVertices, IndexedTriangleList inTriangles, PhysicsMaterialList inMaterials) :
mTriangleVertices(std::move(inVertices)),
mIndexedTriangles(std::move(inTriangles)),
mMaterials(std::move(inMaterials))
{
Sanitize();
}
void MeshShapeSettings::Sanitize()
{
// Remove degenerate and duplicate triangles
UnorderedSet<IndexedTriangle> triangles;
triangles.reserve(UnorderedSet<IndexedTriangle>::size_type(mIndexedTriangles.size()));
TriangleCodec::ValidationContext validation_ctx(mIndexedTriangles, mTriangleVertices);
for (int t = (int)mIndexedTriangles.size() - 1; t >= 0; --t)
{
const IndexedTriangle &tri = mIndexedTriangles[t];
if (tri.IsDegenerate(mTriangleVertices) // Degenerate triangle
|| validation_ctx.IsDegenerate(tri) // Triangle is degenerate in the quantized space
|| !triangles.insert(tri.GetLowestIndexFirst()).second) // Duplicate triangle
{
// The order of triangles doesn't matter (gets reordered while building the tree), so we can just swap the last triangle into this slot
mIndexedTriangles[t] = mIndexedTriangles.back();
mIndexedTriangles.pop_back();
}
}
}
ShapeSettings::ShapeResult MeshShapeSettings::Create() const
{
if (mCachedResult.IsEmpty())
Ref<Shape> shape = new MeshShape(*this, mCachedResult);
return mCachedResult;
}
MeshShape::MeshShape(const MeshShapeSettings &inSettings, ShapeResult &outResult) :
Shape(EShapeType::Mesh, EShapeSubType::Mesh, inSettings, outResult)
{
// Check if there are any triangles
if (inSettings.mIndexedTriangles.empty())
{
outResult.SetError("Need triangles to create a mesh shape!");
return;
}
// Check triangles
TriangleCodec::ValidationContext validation_ctx(inSettings.mIndexedTriangles, inSettings.mTriangleVertices);
for (int t = (int)inSettings.mIndexedTriangles.size() - 1; t >= 0; --t)
{
const IndexedTriangle &triangle = inSettings.mIndexedTriangles[t];
if (triangle.IsDegenerate(inSettings.mTriangleVertices)
|| validation_ctx.IsDegenerate(triangle))
{
outResult.SetError(StringFormat("Triangle %d is degenerate!", t));
return;
}
else
{
// Check vertex indices
for (uint32 idx : triangle.mIdx)
if (idx >= inSettings.mTriangleVertices.size())
{
outResult.SetError(StringFormat("Vertex index %u is beyond vertex list (size: %u)", idx, (uint)inSettings.mTriangleVertices.size()));
return;
}
}
}
// Copy materials
mMaterials = inSettings.mMaterials;
if (!mMaterials.empty())
{
// Validate materials
if (mMaterials.size() > (1 << FLAGS_MATERIAL_BITS))
{
outResult.SetError(StringFormat("Supporting max %d materials per mesh", 1 << FLAGS_MATERIAL_BITS));
return;
}
for (const IndexedTriangle &t : inSettings.mIndexedTriangles)
if (t.mMaterialIndex >= mMaterials.size())
{
outResult.SetError(StringFormat("Triangle material %u is beyond material list (size: %u)", t.mMaterialIndex, (uint)mMaterials.size()));
return;
}
}
else
{
// No materials assigned, validate that all triangles use material index 0
for (const IndexedTriangle &t : inSettings.mIndexedTriangles)
if (t.mMaterialIndex != 0)
{
outResult.SetError("No materials present, all triangles should have material index 0");
return;
}
}
// Check max triangles
if (inSettings.mMaxTrianglesPerLeaf < 1 || inSettings.mMaxTrianglesPerLeaf > MaxTrianglesPerLeaf)
{
outResult.SetError("Invalid max triangles per leaf");
return;
}
// Fill in active edge bits
IndexedTriangleList indexed_triangles = inSettings.mIndexedTriangles; // Copy indices since we're adding the 'active edge' flag
sFindActiveEdges(inSettings, indexed_triangles);
// Create triangle splitter
TriangleSplitterBinning splitter(inSettings.mTriangleVertices, indexed_triangles);
// Build tree
AABBTreeBuilder builder(splitter, inSettings.mMaxTrianglesPerLeaf);
AABBTreeBuilderStats builder_stats;
const AABBTreeBuilder::Node *root = builder.Build(builder_stats);
// Convert to buffer
AABBTreeToBuffer<TriangleCodec, NodeCodec> buffer;
const char *error = nullptr;
if (!buffer.Convert(builder.GetTriangles(), builder.GetNodes(), inSettings.mTriangleVertices, root, inSettings.mPerTriangleUserData, error))
{
outResult.SetError(error);
return;
}
// Move data to this class
mTree.swap(buffer.GetBuffer());
// Check if we're not exceeding the amount of sub shape id bits
if (GetSubShapeIDBitsRecursive() > SubShapeID::MaxBits)
{
outResult.SetError("Mesh is too big and exceeds the amount of available sub shape ID bits");
return;
}
outResult.Set(this);
}
void MeshShape::sFindActiveEdges(const MeshShapeSettings &inSettings, IndexedTriangleList &ioIndices)
{
// A struct to hold the two vertex indices of an edge
struct Edge
{
Edge(int inIdx1, int inIdx2) : mIdx1(min(inIdx1, inIdx2)), mIdx2(max(inIdx1, inIdx2)) { }
uint GetIndexInTriangle(const IndexedTriangle &inTriangle) const
{
for (uint edge_idx = 0; edge_idx < 3; ++edge_idx)
{
Edge edge(inTriangle.mIdx[edge_idx], inTriangle.mIdx[(edge_idx + 1) % 3]);
if (*this == edge)
return edge_idx;
}
JPH_ASSERT(false);
return ~uint(0);
}
bool operator == (const Edge &inRHS) const
{
return mIdx1 == inRHS.mIdx1 && mIdx2 == inRHS.mIdx2;
}
uint64 GetHash() const
{
static_assert(sizeof(*this) == 2 * sizeof(int), "No padding expected");
return HashBytes(this, sizeof(*this));
}
int mIdx1;
int mIdx2;
};
// A struct to hold the triangles that are connected to an edge
struct TriangleIndices
{
uint mNumTriangles = 0;
uint mTriangleIndices[2];
};
// Build a list of edge to triangles
using EdgeToTriangle = UnorderedMap<Edge, TriangleIndices>;
EdgeToTriangle edge_to_triangle;
edge_to_triangle.reserve(EdgeToTriangle::size_type(ioIndices.size() * 3));
for (uint triangle_idx = 0; triangle_idx < ioIndices.size(); ++triangle_idx)
{
IndexedTriangle &triangle = ioIndices[triangle_idx];
for (uint edge_idx = 0; edge_idx < 3; ++edge_idx)
{
Edge edge(triangle.mIdx[edge_idx], triangle.mIdx[(edge_idx + 1) % 3]);
EdgeToTriangle::iterator edge_to_triangle_it = edge_to_triangle.try_emplace(edge, TriangleIndices()).first;
TriangleIndices &indices = edge_to_triangle_it->second;
if (indices.mNumTriangles < 2)
{
// Store index of triangle that connects to this edge
indices.mTriangleIndices[indices.mNumTriangles] = triangle_idx;
indices.mNumTriangles++;
}
else
{
// 3 or more triangles share an edge, mark this edge as active
uint32 mask = 1 << (edge_idx + FLAGS_ACTIVE_EGDE_SHIFT);
JPH_ASSERT((triangle.mMaterialIndex & mask) == 0);
triangle.mMaterialIndex |= mask;
}
}
}
// Walk over all edges and determine which ones are active
for (const EdgeToTriangle::value_type &edge : edge_to_triangle)
{
uint num_active = 0;
if (edge.second.mNumTriangles == 1)
{
// Edge is not shared, it is an active edge
num_active = 1;
}
else if (edge.second.mNumTriangles == 2)
{
// Simple shared edge, determine if edge is active based on the two adjacent triangles
const IndexedTriangle &triangle1 = ioIndices[edge.second.mTriangleIndices[0]];
const IndexedTriangle &triangle2 = ioIndices[edge.second.mTriangleIndices[1]];
// Find which edge this is for both triangles
uint edge_idx1 = edge.first.GetIndexInTriangle(triangle1);
uint edge_idx2 = edge.first.GetIndexInTriangle(triangle2);
// Construct a plane for triangle 1 (e1 = edge vertex 1, e2 = edge vertex 2, op = opposing vertex)
Vec3 triangle1_e1 = Vec3(inSettings.mTriangleVertices[triangle1.mIdx[edge_idx1]]);
Vec3 triangle1_e2 = Vec3(inSettings.mTriangleVertices[triangle1.mIdx[(edge_idx1 + 1) % 3]]);
Vec3 triangle1_op = Vec3(inSettings.mTriangleVertices[triangle1.mIdx[(edge_idx1 + 2) % 3]]);
Plane triangle1_plane = Plane::sFromPointsCCW(triangle1_e1, triangle1_e2, triangle1_op);
// Construct a plane for triangle 2
Vec3 triangle2_e1 = Vec3(inSettings.mTriangleVertices[triangle2.mIdx[edge_idx2]]);
Vec3 triangle2_e2 = Vec3(inSettings.mTriangleVertices[triangle2.mIdx[(edge_idx2 + 1) % 3]]);
Vec3 triangle2_op = Vec3(inSettings.mTriangleVertices[triangle2.mIdx[(edge_idx2 + 2) % 3]]);
Plane triangle2_plane = Plane::sFromPointsCCW(triangle2_e1, triangle2_e2, triangle2_op);
// Determine if the edge is active
num_active = ActiveEdges::IsEdgeActive(triangle1_plane.GetNormal(), triangle2_plane.GetNormal(), triangle1_e2 - triangle1_e1, inSettings.mActiveEdgeCosThresholdAngle)? 2 : 0;
}
else
{
// More edges incoming, we've already marked all edges beyond the 2nd as active
num_active = 2;
}
// Mark edges of all original triangles active
for (uint i = 0; i < num_active; ++i)
{
uint triangle_idx = edge.second.mTriangleIndices[i];
IndexedTriangle &triangle = ioIndices[triangle_idx];
uint edge_idx = edge.first.GetIndexInTriangle(triangle);
uint32 mask = 1 << (edge_idx + FLAGS_ACTIVE_EGDE_SHIFT);
JPH_ASSERT((triangle.mMaterialIndex & mask) == 0);
triangle.mMaterialIndex |= mask;
}
}
}
MassProperties MeshShape::GetMassProperties() const
{
// We cannot calculate the volume for an arbitrary mesh, so we return invalid mass properties.
// If you want your mesh to be dynamic, then you should provide the mass properties yourself when
// creating a Body:
//
// BodyCreationSettings::mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
// BodyCreationSettings::mMassPropertiesOverride.SetMassAndInertiaOfSolidBox(Vec3::sReplicate(1.0f), 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).
return MassProperties();
}
void MeshShape::DecodeSubShapeID(const SubShapeID &inSubShapeID, const void *&outTriangleBlock, uint32 &outTriangleIndex) const
{
// Get block
SubShapeID triangle_idx_subshape_id;
uint32 block_id = inSubShapeID.PopID(NodeCodec::DecodingContext::sTriangleBlockIDBits(sGetNodeHeader(mTree)), triangle_idx_subshape_id);
outTriangleBlock = NodeCodec::DecodingContext::sGetTriangleBlockStart(&mTree[0], block_id);
// Fetch the triangle index
SubShapeID remainder;
outTriangleIndex = triangle_idx_subshape_id.PopID(NumTriangleBits, remainder);
JPH_ASSERT(remainder.IsEmpty(), "Invalid subshape ID");
}
uint MeshShape::GetMaterialIndex(const SubShapeID &inSubShapeID) const
{
// Decode ID
const void *block_start;
uint32 triangle_idx;
DecodeSubShapeID(inSubShapeID, block_start, triangle_idx);
// Fetch the flags
uint8 flags = TriangleCodec::DecodingContext::sGetFlags(block_start, triangle_idx);
return flags & FLAGS_MATERIAL_MASK;
}
const PhysicsMaterial *MeshShape::GetMaterial(const SubShapeID &inSubShapeID) const
{
// Return the default material if there are no materials on this shape
if (mMaterials.empty())
return PhysicsMaterial::sDefault;
return mMaterials[GetMaterialIndex(inSubShapeID)];
}
Vec3 MeshShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const
{
// Decode ID
const void *block_start;
uint32 triangle_idx;
DecodeSubShapeID(inSubShapeID, block_start, triangle_idx);
// Decode triangle
Vec3 v1, v2, v3;
const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree));
triangle_ctx.GetTriangle(block_start, triangle_idx, v1, v2, v3);
// Calculate normal
return (v3 - v2).Cross(v1 - v2).Normalized();
}
void MeshShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const
{
// Decode ID
const void *block_start;
uint32 triangle_idx;
DecodeSubShapeID(inSubShapeID, block_start, triangle_idx);
// Decode triangle
const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree));
outVertices.resize(3);
triangle_ctx.GetTriangle(block_start, triangle_idx, outVertices[0], outVertices[1], outVertices[2]);
// Flip triangle if scaled inside out
if (ScaleHelpers::IsInsideOut(inScale))
std::swap(outVertices[1], outVertices[2]);
// Calculate transform with scale
Mat44 transform = inCenterOfMassTransform.PreScaled(inScale);
// Transform to world space
for (Vec3 &v : outVertices)
v = transform * v;
}
AABox MeshShape::GetLocalBounds() const
{
const NodeCodec::Header *header = sGetNodeHeader(mTree);
return AABox(Vec3::sLoadFloat3Unsafe(header->mRootBoundsMin), Vec3::sLoadFloat3Unsafe(header->mRootBoundsMax));
}
uint MeshShape::GetSubShapeIDBitsRecursive() const
{
return NodeCodec::DecodingContext::sTriangleBlockIDBits(sGetNodeHeader(mTree)) + NumTriangleBits;
}
template <class Visitor>
JPH_INLINE void MeshShape::WalkTree(Visitor &ioVisitor) const
{
const NodeCodec::Header *header = sGetNodeHeader(mTree);
NodeCodec::DecodingContext node_ctx(header);
const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree));
const uint8 *buffer_start = &mTree[0];
node_ctx.WalkTree(buffer_start, triangle_ctx, ioVisitor);
}
template <class Visitor>
JPH_INLINE void MeshShape::WalkTreePerTriangle(const SubShapeIDCreator &inSubShapeIDCreator2, Visitor &ioVisitor) const
{
struct ChainedVisitor
{
JPH_INLINE ChainedVisitor(Visitor &ioVisitor, const SubShapeIDCreator &inSubShapeIDCreator2, uint inTriangleBlockIDBits) :
mVisitor(ioVisitor),
mSubShapeIDCreator2(inSubShapeIDCreator2),
mTriangleBlockIDBits(inTriangleBlockIDBits)
{
}
JPH_INLINE bool ShouldAbort() const
{
return mVisitor.ShouldAbort();
}
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
{
return mVisitor.ShouldVisitNode(inStackTop);
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
{
return mVisitor.VisitNodes(inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, ioProperties, inStackTop);
}
JPH_INLINE void VisitTriangles(const TriangleCodec::DecodingContext &ioContext, const void *inTriangles, int inNumTriangles, uint32 inTriangleBlockID)
{
// Create ID for triangle block
SubShapeIDCreator block_sub_shape_id = mSubShapeIDCreator2.PushID(inTriangleBlockID, mTriangleBlockIDBits);
// Decode vertices and flags
JPH_ASSERT(inNumTriangles <= MaxTrianglesPerLeaf);
Vec3 vertices[MaxTrianglesPerLeaf * 3];
uint8 flags[MaxTrianglesPerLeaf];
ioContext.Unpack(inTriangles, inNumTriangles, vertices, flags);
int triangle_idx = 0;
for (const Vec3 *v = vertices, *v_end = vertices + inNumTriangles * 3; v < v_end; v += 3, triangle_idx++)
{
// Determine active edges
uint8 active_edges = (flags[triangle_idx] >> FLAGS_ACTIVE_EGDE_SHIFT) & FLAGS_ACTIVE_EDGE_MASK;
// Create ID for triangle
SubShapeIDCreator triangle_sub_shape_id = block_sub_shape_id.PushID(triangle_idx, NumTriangleBits);
mVisitor.VisitTriangle(v[0], v[1], v[2], active_edges, triangle_sub_shape_id.GetID());
// Check if we should early out now
if (mVisitor.ShouldAbort())
break;
}
}
Visitor & mVisitor;
SubShapeIDCreator mSubShapeIDCreator2;
uint mTriangleBlockIDBits;
};
ChainedVisitor visitor(ioVisitor, inSubShapeIDCreator2, NodeCodec::DecodingContext::sTriangleBlockIDBits(sGetNodeHeader(mTree)));
WalkTree(visitor);
}
#ifdef JPH_DEBUG_RENDERER
void MeshShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const
{
// Reset the batch if we switch coloring mode
if (mCachedTrianglesColoredPerGroup != sDrawTriangleGroups || mCachedUseMaterialColors != inUseMaterialColors)
{
mGeometry = nullptr;
mCachedTrianglesColoredPerGroup = sDrawTriangleGroups;
mCachedUseMaterialColors = inUseMaterialColors;
}
if (mGeometry == nullptr)
{
struct Visitor
{
JPH_INLINE bool ShouldAbort() const
{
return false;
}
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
{
return true;
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
{
UVec4 valid = UVec4::sOr(UVec4::sOr(Vec4::sLess(inBoundsMinX, inBoundsMaxX), Vec4::sLess(inBoundsMinY, inBoundsMaxY)), Vec4::sLess(inBoundsMinZ, inBoundsMaxZ));
return CountAndSortTrues(valid, ioProperties);
}
JPH_INLINE void VisitTriangles(const TriangleCodec::DecodingContext &ioContext, const void *inTriangles, int inNumTriangles, [[maybe_unused]] uint32 inTriangleBlockID)
{
JPH_ASSERT(inNumTriangles <= MaxTrianglesPerLeaf);
Vec3 vertices[MaxTrianglesPerLeaf * 3];
ioContext.Unpack(inTriangles, inNumTriangles, vertices);
if (mDrawTriangleGroups || !mUseMaterialColors || mMaterials.empty())
{
// Single color for mesh
Color color = mDrawTriangleGroups? Color::sGetDistinctColor(mColorIdx++) : (mUseMaterialColors? PhysicsMaterial::sDefault->GetDebugColor() : Color::sWhite);
for (const Vec3 *v = vertices, *v_end = vertices + inNumTriangles * 3; v < v_end; v += 3)
mTriangles.push_back({ v[0], v[1], v[2], color });
}
else
{
// Per triangle color
uint8 flags[MaxTrianglesPerLeaf];
TriangleCodec::DecodingContext::sGetFlags(inTriangles, inNumTriangles, flags);
const uint8 *f = flags;
for (const Vec3 *v = vertices, *v_end = vertices + inNumTriangles * 3; v < v_end; v += 3, f++)
mTriangles.push_back({ v[0], v[1], v[2], mMaterials[*f & FLAGS_MATERIAL_MASK]->GetDebugColor() });
}
}
Array<DebugRenderer::Triangle> & mTriangles;
const PhysicsMaterialList & mMaterials;
bool mUseMaterialColors;
bool mDrawTriangleGroups;
int mColorIdx = 0;
};
Array<DebugRenderer::Triangle> triangles;
Visitor visitor { triangles, mMaterials, mCachedUseMaterialColors, mCachedTrianglesColoredPerGroup };
WalkTree(visitor);
mGeometry = new DebugRenderer::Geometry(inRenderer->CreateTriangleBatch(triangles), GetLocalBounds());
}
// Test if the shape is scaled inside out
DebugRenderer::ECullMode cull_mode = ScaleHelpers::IsInsideOut(inScale)? DebugRenderer::ECullMode::CullFrontFace : DebugRenderer::ECullMode::CullBackFace;
// Determine the draw mode
DebugRenderer::EDrawMode draw_mode = inDrawWireframe? DebugRenderer::EDrawMode::Wireframe : DebugRenderer::EDrawMode::Solid;
// Draw the geometry
inRenderer->DrawGeometry(inCenterOfMassTransform * Mat44::sScale(inScale), inColor, mGeometry, cull_mode, DebugRenderer::ECastShadow::On, draw_mode);
if (sDrawTriangleOutlines)
{
struct Visitor
{
JPH_INLINE Visitor(DebugRenderer *inRenderer, RMat44Arg inTransform) :
mRenderer(inRenderer),
mTransform(inTransform)
{
}
JPH_INLINE bool ShouldAbort() const
{
return false;
}
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
{
return true;
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
{
UVec4 valid = UVec4::sOr(UVec4::sOr(Vec4::sLess(inBoundsMinX, inBoundsMaxX), Vec4::sLess(inBoundsMinY, inBoundsMaxY)), Vec4::sLess(inBoundsMinZ, inBoundsMaxZ));
return CountAndSortTrues(valid, ioProperties);
}
JPH_INLINE void VisitTriangles(const TriangleCodec::DecodingContext &ioContext, const void *inTriangles, int inNumTriangles, uint32 inTriangleBlockID)
{
// Decode vertices and flags
JPH_ASSERT(inNumTriangles <= MaxTrianglesPerLeaf);
Vec3 vertices[MaxTrianglesPerLeaf * 3];
uint8 flags[MaxTrianglesPerLeaf];
ioContext.Unpack(inTriangles, inNumTriangles, vertices, flags);
// Loop through triangles
const uint8 *f = flags;
for (Vec3 *v = vertices, *v_end = vertices + inNumTriangles * 3; v < v_end; v += 3, ++f)
{
// Loop through edges
for (uint edge_idx = 0; edge_idx < 3; ++edge_idx)
{
RVec3 v1 = mTransform * v[edge_idx];
RVec3 v2 = mTransform * v[(edge_idx + 1) % 3];
// Draw active edge as a green arrow, other edges as grey
if (*f & (1 << (edge_idx + FLAGS_ACTIVE_EGDE_SHIFT)))
mRenderer->DrawArrow(v1, v2, Color::sGreen, 0.01f);
else
mRenderer->DrawLine(v1, v2, Color::sGrey);
}
}
}
DebugRenderer * mRenderer;
RMat44 mTransform;
};
Visitor visitor { inRenderer, inCenterOfMassTransform.PreScaled(inScale) };
WalkTree(visitor);
}
}
#endif // JPH_DEBUG_RENDERER
bool MeshShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const
{
JPH_PROFILE_FUNCTION();
struct Visitor
{
JPH_INLINE explicit Visitor(RayCastResult &ioHit) :
mHit(ioHit)
{
}
JPH_INLINE bool ShouldAbort() const
{
return mHit.mFraction <= 0.0f;
}
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
{
return mDistanceStack[inStackTop] < mHit.mFraction;
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
{
// Test bounds of 4 children
Vec4 distance = RayAABox4(mRayOrigin, mRayInvDirection, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
return SortReverseAndStore(distance, mHit.mFraction, ioProperties, &mDistanceStack[inStackTop]);
}
JPH_INLINE void VisitTriangles(const TriangleCodec::DecodingContext &ioContext, const void *inTriangles, int inNumTriangles, uint32 inTriangleBlockID)
{
// Test against triangles
uint32 triangle_idx;
float fraction = ioContext.TestRay(mRayOrigin, mRayDirection, inTriangles, inNumTriangles, mHit.mFraction, triangle_idx);
if (fraction < mHit.mFraction)
{
mHit.mFraction = fraction;
mHit.mSubShapeID2 = mSubShapeIDCreator.PushID(inTriangleBlockID, mTriangleBlockIDBits).PushID(triangle_idx, NumTriangleBits).GetID();
mReturnValue = true;
}
}
RayCastResult & mHit;
Vec3 mRayOrigin;
Vec3 mRayDirection;
RayInvDirection mRayInvDirection;
uint mTriangleBlockIDBits;
SubShapeIDCreator mSubShapeIDCreator;
bool mReturnValue = false;
float mDistanceStack[NodeCodec::StackSize];
};
Visitor visitor(ioHit);
visitor.mRayOrigin = inRay.mOrigin;
visitor.mRayDirection = inRay.mDirection;
visitor.mRayInvDirection.Set(inRay.mDirection);
visitor.mTriangleBlockIDBits = NodeCodec::DecodingContext::sTriangleBlockIDBits(sGetNodeHeader(mTree));
visitor.mSubShapeIDCreator = inSubShapeIDCreator;
WalkTree(visitor);
return visitor.mReturnValue;
}
void MeshShape::CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter) const
{
JPH_PROFILE_FUNCTION();
// Test shape filter
if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))
return;
struct Visitor
{
JPH_INLINE explicit Visitor(CastRayCollector &ioCollector) :
mCollector(ioCollector)
{
}
JPH_INLINE bool ShouldAbort() const
{
return mCollector.ShouldEarlyOut();
}
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
{
return mDistanceStack[inStackTop] < mCollector.GetEarlyOutFraction();
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
{
// Test bounds of 4 children
Vec4 distance = RayAABox4(mRayOrigin, mRayInvDirection, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
return SortReverseAndStore(distance, mCollector.GetEarlyOutFraction(), ioProperties, &mDistanceStack[inStackTop]);
}
JPH_INLINE void VisitTriangle(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, [[maybe_unused]] uint8 inActiveEdges, SubShapeID inSubShapeID2)
{
// Back facing check
if (mBackFaceMode == EBackFaceMode::IgnoreBackFaces && (inV2 - inV0).Cross(inV1 - inV0).Dot(mRayDirection) < 0)
return;
// Check the triangle
float fraction = RayTriangle(mRayOrigin, mRayDirection, inV0, inV1, inV2);
if (fraction < mCollector.GetEarlyOutFraction())
{
RayCastResult hit;
hit.mBodyID = TransformedShape::sGetBodyID(mCollector.GetContext());
hit.mFraction = fraction;
hit.mSubShapeID2 = inSubShapeID2;
mCollector.AddHit(hit);
}
}
CastRayCollector & mCollector;
Vec3 mRayOrigin;
Vec3 mRayDirection;
RayInvDirection mRayInvDirection;
EBackFaceMode mBackFaceMode;
float mDistanceStack[NodeCodec::StackSize];
};
Visitor visitor(ioCollector);
visitor.mBackFaceMode = inRayCastSettings.mBackFaceModeTriangles;
visitor.mRayOrigin = inRay.mOrigin;
visitor.mRayDirection = inRay.mDirection;
visitor.mRayInvDirection.Set(inRay.mDirection);
WalkTreePerTriangle(inSubShapeIDCreator, visitor);
}
void MeshShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const
{
sCollidePointUsingRayCast(*this, inPoint, inSubShapeIDCreator, ioCollector, inShapeFilter);
}
void MeshShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const
{
JPH_PROFILE_FUNCTION();
struct Visitor : public CollideSoftBodyVerticesVsTriangles
{
using CollideSoftBodyVerticesVsTriangles::CollideSoftBodyVerticesVsTriangles;
JPH_INLINE bool ShouldAbort() const
{
return false;
}
JPH_INLINE bool ShouldVisitNode([[maybe_unused]] int inStackTop) const
{
return mDistanceStack[inStackTop] < mClosestDistanceSq;
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
{
// Get distance to vertex
Vec4 dist_sq = AABox4DistanceSqToPoint(mLocalPosition, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
return SortReverseAndStore(dist_sq, mClosestDistanceSq, ioProperties, &mDistanceStack[inStackTop]);
}
JPH_INLINE void VisitTriangle(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, [[maybe_unused]] uint8 inActiveEdges, [[maybe_unused]] SubShapeID inSubShapeID2)
{
ProcessTriangle(inV0, inV1, inV2);
}
float mDistanceStack[NodeCodec::StackSize];
};
Visitor visitor(inCenterOfMassTransform, inScale);
for (CollideSoftBodyVertexIterator v = inVertices, sbv_end = inVertices + inNumVertices; v != sbv_end; ++v)
if (v.GetInvMass() > 0.0f)
{
visitor.StartVertex(v);
WalkTreePerTriangle(SubShapeIDCreator(), visitor);
visitor.FinishVertex(v, inCollidingShapeIndex);
}
}
void MeshShape::sCastConvexVsMesh(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector)
{
JPH_PROFILE_FUNCTION();
struct Visitor : public CastConvexVsTriangles
{
using CastConvexVsTriangles::CastConvexVsTriangles;
JPH_INLINE bool ShouldAbort() const
{
return mCollector.ShouldEarlyOut();
}
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
{
return mDistanceStack[inStackTop] < mCollector.GetPositiveEarlyOutFraction();
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
{
// Scale the bounding boxes of this node
Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;
AABox4Scale(mScale, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
// Enlarge them by the casted shape's box extents
AABox4EnlargeWithExtent(mBoxExtent, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
// Test bounds of 4 children
Vec4 distance = RayAABox4(mBoxCenter, mInvDirection, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
return SortReverseAndStore(distance, mCollector.GetPositiveEarlyOutFraction(), ioProperties, &mDistanceStack[inStackTop]);
}
JPH_INLINE void VisitTriangle(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, uint8 inActiveEdges, SubShapeID inSubShapeID2)
{
Cast(inV0, inV1, inV2, inActiveEdges, inSubShapeID2);
}
RayInvDirection mInvDirection;
Vec3 mBoxCenter;
Vec3 mBoxExtent;
float mDistanceStack[NodeCodec::StackSize];
};
JPH_ASSERT(inShape->GetSubType() == EShapeSubType::Mesh);
const MeshShape *shape = static_cast<const MeshShape *>(inShape);
Visitor visitor(inShapeCast, inShapeCastSettings, inScale, inCenterOfMassTransform2, inSubShapeIDCreator1, ioCollector);
visitor.mInvDirection.Set(inShapeCast.mDirection);
visitor.mBoxCenter = inShapeCast.mShapeWorldBounds.GetCenter();
visitor.mBoxExtent = inShapeCast.mShapeWorldBounds.GetExtent();
shape->WalkTreePerTriangle(inSubShapeIDCreator2, visitor);
}
void MeshShape::sCastSphereVsMesh(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector)
{
JPH_PROFILE_FUNCTION();
struct Visitor : public CastSphereVsTriangles
{
using CastSphereVsTriangles::CastSphereVsTriangles;
JPH_INLINE bool ShouldAbort() const
{
return mCollector.ShouldEarlyOut();
}
JPH_INLINE bool ShouldVisitNode(int inStackTop) const
{
return mDistanceStack[inStackTop] < mCollector.GetPositiveEarlyOutFraction();
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
{
// Scale the bounding boxes of this node
Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;
AABox4Scale(mScale, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
// Enlarge them by the radius of the sphere
AABox4EnlargeWithExtent(Vec3::sReplicate(mRadius), bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
// Test bounds of 4 children
Vec4 distance = RayAABox4(mStart, mInvDirection, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
return SortReverseAndStore(distance, mCollector.GetPositiveEarlyOutFraction(), ioProperties, &mDistanceStack[inStackTop]);
}
JPH_INLINE void VisitTriangle(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, uint8 inActiveEdges, SubShapeID inSubShapeID2)
{
Cast(inV0, inV1, inV2, inActiveEdges, inSubShapeID2);
}
RayInvDirection mInvDirection;
float mDistanceStack[NodeCodec::StackSize];
};
JPH_ASSERT(inShape->GetSubType() == EShapeSubType::Mesh);
const MeshShape *shape = static_cast<const MeshShape *>(inShape);
Visitor visitor(inShapeCast, inShapeCastSettings, inScale, inCenterOfMassTransform2, inSubShapeIDCreator1, ioCollector);
visitor.mInvDirection.Set(inShapeCast.mDirection);
shape->WalkTreePerTriangle(inSubShapeIDCreator2, visitor);
}
struct MeshShape::MSGetTrianglesContext
{
JPH_INLINE MSGetTrianglesContext(const MeshShape *inShape, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) :
mDecodeCtx(sGetNodeHeader(inShape->mTree)),
mShape(inShape),
mLocalBox(Mat44::sInverseRotationTranslation(inRotation, inPositionCOM), inBox),
mMeshScale(inScale),
mLocalToWorld(Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(inScale)),
mIsInsideOut(ScaleHelpers::IsInsideOut(inScale))
{
}
JPH_INLINE bool ShouldAbort() const
{
return mShouldAbort;
}
JPH_INLINE bool ShouldVisitNode([[maybe_unused]] int inStackTop) const
{
return true;
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
{
// Scale the bounding boxes of this node
Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;
AABox4Scale(mMeshScale, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
// Test which nodes collide
UVec4 collides = AABox4VsBox(mLocalBox, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
return CountAndSortTrues(collides, ioProperties);
}
JPH_INLINE void VisitTriangles(const TriangleCodec::DecodingContext &ioContext, const void *inTriangles, int inNumTriangles, [[maybe_unused]] uint32 inTriangleBlockID)
{
// When the buffer is full and we cannot process the triangles, abort the tree walk. The next time GetTrianglesNext is called we will continue here.
if (mNumTrianglesFound + inNumTriangles > mMaxTrianglesRequested)
{
mShouldAbort = true;
return;
}
// Decode vertices
JPH_ASSERT(inNumTriangles <= MaxTrianglesPerLeaf);
Vec3 vertices[MaxTrianglesPerLeaf * 3];
ioContext.Unpack(inTriangles, inNumTriangles, vertices);
// Store vertices as Float3
if (mIsInsideOut)
{
// Scaled inside out, flip the triangles
for (const Vec3 *v = vertices, *v_end = v + 3 * inNumTriangles; v < v_end; v += 3)
{
(mLocalToWorld * v[0]).StoreFloat3(mTriangleVertices++);
(mLocalToWorld * v[2]).StoreFloat3(mTriangleVertices++);
(mLocalToWorld * v[1]).StoreFloat3(mTriangleVertices++);
}
}
else
{
// Normal scale
for (const Vec3 *v = vertices, *v_end = v + 3 * inNumTriangles; v < v_end; ++v)
(mLocalToWorld * *v).StoreFloat3(mTriangleVertices++);
}
if (mMaterials != nullptr)
{
if (mShape->mMaterials.empty())
{
// No materials, output default
const PhysicsMaterial *default_material = PhysicsMaterial::sDefault;
for (int m = 0; m < inNumTriangles; ++m)
*mMaterials++ = default_material;
}
else
{
// Decode triangle flags
uint8 flags[MaxTrianglesPerLeaf];
TriangleCodec::DecodingContext::sGetFlags(inTriangles, inNumTriangles, flags);
// Store materials
for (const uint8 *f = flags, *f_end = f + inNumTriangles; f < f_end; ++f)
*mMaterials++ = mShape->mMaterials[*f & FLAGS_MATERIAL_MASK].GetPtr();
}
}
// Accumulate triangles found
mNumTrianglesFound += inNumTriangles;
}
NodeCodec::DecodingContext mDecodeCtx;
const MeshShape * mShape;
OrientedBox mLocalBox;
Vec3 mMeshScale;
Mat44 mLocalToWorld;
int mMaxTrianglesRequested;
Float3 * mTriangleVertices;
int mNumTrianglesFound;
const PhysicsMaterial ** mMaterials;
bool mShouldAbort;
bool mIsInsideOut;
};
void MeshShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
{
static_assert(sizeof(MSGetTrianglesContext) <= sizeof(GetTrianglesContext), "GetTrianglesContext too small");
JPH_ASSERT(IsAligned(&ioContext, alignof(MSGetTrianglesContext)));
new (&ioContext) MSGetTrianglesContext(this, inBox, inPositionCOM, inRotation, inScale);
}
int MeshShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const
{
static_assert(cGetTrianglesMinTrianglesRequested >= MaxTrianglesPerLeaf, "cGetTrianglesMinTrianglesRequested is too small");
JPH_ASSERT(inMaxTrianglesRequested >= cGetTrianglesMinTrianglesRequested);
// Check if we're done
MSGetTrianglesContext &context = (MSGetTrianglesContext &)ioContext;
if (context.mDecodeCtx.IsDoneWalking())
return 0;
// Store parameters on context
context.mMaxTrianglesRequested = inMaxTrianglesRequested;
context.mTriangleVertices = outTriangleVertices;
context.mMaterials = outMaterials;
context.mShouldAbort = false; // Reset the abort flag
context.mNumTrianglesFound = 0;
// Continue (or start) walking the tree
const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree));
const uint8 *buffer_start = &mTree[0];
context.mDecodeCtx.WalkTree(buffer_start, triangle_ctx, context);
return context.mNumTrianglesFound;
}
void MeshShape::sCollideConvexVsMesh(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, [[maybe_unused]] const ShapeFilter &inShapeFilter)
{
JPH_PROFILE_FUNCTION();
// Get the shapes
JPH_ASSERT(inShape1->GetType() == EShapeType::Convex);
JPH_ASSERT(inShape2->GetType() == EShapeType::Mesh);
const ConvexShape *shape1 = static_cast<const ConvexShape *>(inShape1);
const MeshShape *shape2 = static_cast<const MeshShape *>(inShape2);
struct Visitor : public CollideConvexVsTriangles
{
using CollideConvexVsTriangles::CollideConvexVsTriangles;
JPH_INLINE bool ShouldAbort() const
{
return mCollector.ShouldEarlyOut();
}
JPH_INLINE bool ShouldVisitNode([[maybe_unused]] int inStackTop) const
{
return true;
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
{
// Scale the bounding boxes of this node
Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;
AABox4Scale(mScale2, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
// Test which nodes collide
UVec4 collides = AABox4VsBox(mBoundsOf1InSpaceOf2, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
return CountAndSortTrues(collides, ioProperties);
}
JPH_INLINE void VisitTriangle(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, uint8 inActiveEdges, SubShapeID inSubShapeID2)
{
Collide(inV0, inV1, inV2, inActiveEdges, inSubShapeID2);
}
};
Visitor visitor(shape1, inScale1, inScale2, inCenterOfMassTransform1, inCenterOfMassTransform2, inSubShapeIDCreator1.GetID(), inCollideShapeSettings, ioCollector);
shape2->WalkTreePerTriangle(inSubShapeIDCreator2, visitor);
}
void MeshShape::sCollideSphereVsMesh(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, [[maybe_unused]] const ShapeFilter &inShapeFilter)
{
JPH_PROFILE_FUNCTION();
// Get the shapes
JPH_ASSERT(inShape1->GetSubType() == EShapeSubType::Sphere);
JPH_ASSERT(inShape2->GetType() == EShapeType::Mesh);
const SphereShape *shape1 = static_cast<const SphereShape *>(inShape1);
const MeshShape *shape2 = static_cast<const MeshShape *>(inShape2);
struct Visitor : public CollideSphereVsTriangles
{
using CollideSphereVsTriangles::CollideSphereVsTriangles;
JPH_INLINE bool ShouldAbort() const
{
return mCollector.ShouldEarlyOut();
}
JPH_INLINE bool ShouldVisitNode([[maybe_unused]] int inStackTop) const
{
return true;
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
{
// Scale the bounding boxes of this node
Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;
AABox4Scale(mScale2, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
// Test which nodes collide
UVec4 collides = AABox4VsSphere(mSphereCenterIn2, mRadiusPlusMaxSeparationSq, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
return CountAndSortTrues(collides, ioProperties);
}
JPH_INLINE void VisitTriangle(Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2, uint8 inActiveEdges, SubShapeID inSubShapeID2)
{
Collide(inV0, inV1, inV2, inActiveEdges, inSubShapeID2);
}
};
Visitor visitor(shape1, inScale1, inScale2, inCenterOfMassTransform1, inCenterOfMassTransform2, inSubShapeIDCreator1.GetID(), inCollideShapeSettings, ioCollector);
shape2->WalkTreePerTriangle(inSubShapeIDCreator2, visitor);
}
void MeshShape::SaveBinaryState(StreamOut &inStream) const
{
Shape::SaveBinaryState(inStream);
inStream.Write(static_cast<const ByteBufferVector &>(mTree)); // Make sure we use the Array<> overload
}
void MeshShape::RestoreBinaryState(StreamIn &inStream)
{
Shape::RestoreBinaryState(inStream);
inStream.Read(static_cast<ByteBufferVector &>(mTree)); // Make sure we use the Array<> overload
}
void MeshShape::SaveMaterialState(PhysicsMaterialList &outMaterials) const
{
outMaterials = mMaterials;
}
void MeshShape::RestoreMaterialState(const PhysicsMaterialRefC *inMaterials, uint inNumMaterials)
{
mMaterials.assign(inMaterials, inMaterials + inNumMaterials);
}
Shape::Stats MeshShape::GetStats() const
{
// Walk the tree to count the triangles
struct Visitor
{
JPH_INLINE bool ShouldAbort() const
{
return false;
}
JPH_INLINE bool ShouldVisitNode([[maybe_unused]] int inStackTop) const
{
return true;
}
JPH_INLINE int VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
{
// Visit all valid children
UVec4 valid = UVec4::sOr(UVec4::sOr(Vec4::sLess(inBoundsMinX, inBoundsMaxX), Vec4::sLess(inBoundsMinY, inBoundsMaxY)), Vec4::sLess(inBoundsMinZ, inBoundsMaxZ));
return CountAndSortTrues(valid, ioProperties);
}
JPH_INLINE void VisitTriangles([[maybe_unused]] const TriangleCodec::DecodingContext &ioContext, [[maybe_unused]] const void *inTriangles, int inNumTriangles, [[maybe_unused]] uint32 inTriangleBlockID)
{
mNumTriangles += inNumTriangles;
}
uint mNumTriangles = 0;
};
Visitor visitor;
WalkTree(visitor);
return Stats(sizeof(*this) + mMaterials.size() * sizeof(Ref<PhysicsMaterial>) + mTree.size() * sizeof(uint8), visitor.mNumTriangles);
}
uint32 MeshShape::GetTriangleUserData(const SubShapeID &inSubShapeID) const
{
// Decode ID
const void *block_start;
uint32 triangle_idx;
DecodeSubShapeID(inSubShapeID, block_start, triangle_idx);
// Decode triangle
const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree));
return triangle_ctx.GetUserData(block_start, triangle_idx);
}
void MeshShape::sRegister()
{
ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::Mesh);
f.mConstruct = []() -> Shape * { return new MeshShape; };
f.mColor = Color::sRed;
for (EShapeSubType s : sConvexSubShapeTypes)
{
CollisionDispatch::sRegisterCollideShape(s, EShapeSubType::Mesh, sCollideConvexVsMesh);
CollisionDispatch::sRegisterCastShape(s, EShapeSubType::Mesh, sCastConvexVsMesh);
CollisionDispatch::sRegisterCastShape(EShapeSubType::Mesh, s, CollisionDispatch::sReversedCastShape);
CollisionDispatch::sRegisterCollideShape(EShapeSubType::Mesh, s, CollisionDispatch::sReversedCollideShape);
}
// Specialized collision functions
CollisionDispatch::sRegisterCollideShape(EShapeSubType::Sphere, EShapeSubType::Mesh, sCollideSphereVsMesh);
CollisionDispatch::sRegisterCastShape(EShapeSubType::Sphere, EShapeSubType::Mesh, sCastSphereVsMesh);
}
JPH_NAMESPACE_END