feat: modules moved and engine moved to submodule

This commit is contained in:
Jan van der Weide 2025-04-12 18:40:44 +02:00
parent dfb5e645cd
commit c33d2130cc
5136 changed files with 225275 additions and 64485 deletions

View file

@ -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)

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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);
}

View file

@ -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)

View file

@ -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

View file

@ -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).

View file

@ -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.

View file

@ -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)

View file

@ -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.

View file

@ -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();

View file

@ -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); }

View file

@ -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())
{

View file

@ -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

View file

@ -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;

View file

@ -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); });
}

View file

@ -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));
}
}
}
}

View file

@ -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