// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) // SPDX-FileCopyrightText: 2021 Jorrit Rouwe // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #include #include #ifdef JPH_DEBUG_RENDERER #include #endif // JPH_DEBUG_RENDERER JPH_NAMESPACE_BEGIN JPH_IMPLEMENT_SERIALIZABLE_ABSTRACT(CompoundShapeSettings) { JPH_ADD_BASE_CLASS(CompoundShapeSettings, ShapeSettings) JPH_ADD_ATTRIBUTE(CompoundShapeSettings, mSubShapes) } JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(CompoundShapeSettings::SubShapeSettings) { JPH_ADD_ATTRIBUTE(CompoundShapeSettings::SubShapeSettings, mShape) JPH_ADD_ATTRIBUTE(CompoundShapeSettings::SubShapeSettings, mPosition) JPH_ADD_ATTRIBUTE(CompoundShapeSettings::SubShapeSettings, mRotation) JPH_ADD_ATTRIBUTE(CompoundShapeSettings::SubShapeSettings, mUserData) } void CompoundShapeSettings::AddShape(Vec3Arg inPosition, QuatArg inRotation, const ShapeSettings *inShape, uint32 inUserData) { // Add shape SubShapeSettings shape; shape.mPosition = inPosition; shape.mRotation = inRotation; shape.mShape = inShape; shape.mUserData = inUserData; mSubShapes.push_back(shape); } void CompoundShapeSettings::AddShape(Vec3Arg inPosition, QuatArg inRotation, const Shape *inShape, uint32 inUserData) { // Add shape SubShapeSettings shape; shape.mPosition = inPosition; shape.mRotation = inRotation; shape.mShapePtr = inShape; shape.mUserData = inUserData; mSubShapes.push_back(shape); } bool CompoundShape::MustBeStatic() const { for (const SubShape &shape : mSubShapes) if (shape.mShape->MustBeStatic()) return true; return false; } MassProperties CompoundShape::GetMassProperties() const { MassProperties p; // Calculate mass and inertia p.mMass = 0.0f; p.mInertia = Mat44::sZero(); for (const SubShape &shape : mSubShapes) { // Rotate and translate inertia of child into place MassProperties child = shape.mShape->GetMassProperties(); child.Rotate(Mat44::sRotation(shape.GetRotation())); child.Translate(shape.GetPositionCOM()); // Accumulate mass and inertia p.mMass += child.mMass; p.mInertia += child.mInertia; } // Ensure that inertia is a 3x3 matrix, adding inertias causes the bottom right element to change p.mInertia.SetColumn4(3, Vec4(0, 0, 0, 1)); return p; } AABox CompoundShape::GetWorldSpaceBounds(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale) const { if (mSubShapes.size() <= 10) { AABox bounds; for (const SubShape &shape : mSubShapes) { Mat44 transform = inCenterOfMassTransform * shape.GetLocalTransformNoScale(inScale); bounds.Encapsulate(shape.mShape->GetWorldSpaceBounds(transform, shape.TransformScale(inScale))); } return bounds; } else { // If there are too many shapes, use the base class function (this will result in a slightly wider bounding box) return Shape::GetWorldSpaceBounds(inCenterOfMassTransform, inScale); } } uint CompoundShape::GetSubShapeIDBitsRecursive() const { // Add max of child bits to our bits uint child_bits = 0; for (const SubShape &shape : mSubShapes) child_bits = max(child_bits, shape.mShape->GetSubShapeIDBitsRecursive()); return child_bits + GetSubShapeIDBits(); } const PhysicsMaterial *CompoundShape::GetMaterial(const SubShapeID &inSubShapeID) const { // Decode sub shape index SubShapeID remainder; uint32 index = GetSubShapeIndexFromID(inSubShapeID, remainder); // Pass call on return mSubShapes[index].mShape->GetMaterial(remainder); } const Shape *CompoundShape::GetLeafShape(const SubShapeID &inSubShapeID, SubShapeID &outRemainder) const { // Decode sub shape index SubShapeID remainder; uint32 index = GetSubShapeIndexFromID(inSubShapeID, remainder); if (index >= mSubShapes.size()) { // No longer valid index outRemainder = SubShapeID(); return nullptr; } // Pass call on return mSubShapes[index].mShape->GetLeafShape(remainder, outRemainder); } uint64 CompoundShape::GetSubShapeUserData(const SubShapeID &inSubShapeID) const { // Decode sub shape index SubShapeID remainder; uint32 index = GetSubShapeIndexFromID(inSubShapeID, remainder); if (index >= mSubShapes.size()) return 0; // No longer valid index // Pass call on return mSubShapes[index].mShape->GetSubShapeUserData(remainder); } TransformedShape CompoundShape::GetSubShapeTransformedShape(const SubShapeID &inSubShapeID, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale, SubShapeID &outRemainder) const { // Get the sub shape const SubShape &sub_shape = mSubShapes[GetSubShapeIndexFromID(inSubShapeID, outRemainder)]; // Calculate transform for sub shape Vec3 position = inPositionCOM + inRotation * (inScale * sub_shape.GetPositionCOM()); Quat rotation = inRotation * sub_shape.GetRotation(); Vec3 scale = sub_shape.TransformScale(inScale); // Return transformed shape TransformedShape ts(RVec3(position), rotation, sub_shape.mShape, BodyID()); ts.SetShapeScale(scale); return ts; } Vec3 CompoundShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const { // Decode sub shape index SubShapeID remainder; uint32 index = GetSubShapeIndexFromID(inSubShapeID, remainder); // Transform surface position to local space and pass call on const SubShape &shape = mSubShapes[index]; Mat44 transform = Mat44::sInverseRotationTranslation(shape.GetRotation(), shape.GetPositionCOM()); Vec3 normal = shape.mShape->GetSurfaceNormal(remainder, transform * inLocalSurfacePosition); // Transform normal to this shape's space return transform.Multiply3x3Transposed(normal); } void CompoundShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const { // Decode sub shape index SubShapeID remainder; uint32 index = GetSubShapeIndexFromID(inSubShapeID, remainder); // Apply transform and pass on to sub shape const SubShape &shape = mSubShapes[index]; Mat44 transform = shape.GetLocalTransformNoScale(inScale); shape.mShape->GetSupportingFace(remainder, transform.Multiply3x3Transposed(inDirection), shape.TransformScale(inScale), inCenterOfMassTransform * transform, outVertices); } void CompoundShape::GetSubmergedVolume(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Plane &inSurface, float &outTotalVolume, float &outSubmergedVolume, Vec3 &outCenterOfBuoyancy JPH_IF_DEBUG_RENDERER(, RVec3Arg inBaseOffset)) const { outTotalVolume = 0.0f; outSubmergedVolume = 0.0f; outCenterOfBuoyancy = Vec3::sZero(); for (const SubShape &shape : mSubShapes) { // Get center of mass transform of child Mat44 transform = inCenterOfMassTransform * shape.GetLocalTransformNoScale(inScale); // Recurse to child float total_volume, submerged_volume; Vec3 center_of_buoyancy; shape.mShape->GetSubmergedVolume(transform, shape.TransformScale(inScale), inSurface, total_volume, submerged_volume, center_of_buoyancy JPH_IF_DEBUG_RENDERER(, inBaseOffset)); // Accumulate volumes outTotalVolume += total_volume; outSubmergedVolume += submerged_volume; // The center of buoyancy is the weighted average of the center of buoyancy of our child shapes outCenterOfBuoyancy += submerged_volume * center_of_buoyancy; } if (outSubmergedVolume > 0.0f) outCenterOfBuoyancy /= outSubmergedVolume; #ifdef JPH_DEBUG_RENDERER // Draw center of buoyancy if (sDrawSubmergedVolumes) DebugRenderer::sInstance->DrawWireSphere(inBaseOffset + outCenterOfBuoyancy, 0.05f, Color::sRed, 1); #endif // JPH_DEBUG_RENDERER } #ifdef JPH_DEBUG_RENDERER void CompoundShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const { for (const SubShape &shape : mSubShapes) { Mat44 transform = shape.GetLocalTransformNoScale(inScale); shape.mShape->Draw(inRenderer, inCenterOfMassTransform * transform, shape.TransformScale(inScale), inColor, inUseMaterialColors, inDrawWireframe); } } void CompoundShape::DrawGetSupportFunction(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inDrawSupportDirection) const { for (const SubShape &shape : mSubShapes) { Mat44 transform = shape.GetLocalTransformNoScale(inScale); shape.mShape->DrawGetSupportFunction(inRenderer, inCenterOfMassTransform * transform, shape.TransformScale(inScale), inColor, inDrawSupportDirection); } } void CompoundShape::DrawGetSupportingFace(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale) const { for (const SubShape &shape : mSubShapes) { Mat44 transform = shape.GetLocalTransformNoScale(inScale); shape.mShape->DrawGetSupportingFace(inRenderer, inCenterOfMassTransform * transform, shape.TransformScale(inScale)); } } #endif // JPH_DEBUG_RENDERER void CompoundShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const { for (const SubShape &shape : mSubShapes) { Mat44 transform = shape.GetLocalTransformNoScale(inScale); shape.mShape->CollideSoftBodyVertices(inCenterOfMassTransform * transform, shape.TransformScale(inScale), inVertices, inNumVertices, inCollidingShapeIndex); } } void CompoundShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const { for (const SubShape &shape : mSubShapes) shape.mShape->TransformShape(inCenterOfMassTransform * Mat44::sRotationTranslation(shape.GetRotation(), shape.GetPositionCOM()), ioCollector); } void CompoundShape::sCastCompoundVsShape(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector) { JPH_PROFILE_FUNCTION(); // Fetch compound shape from cast shape JPH_ASSERT(inShapeCast.mShape->GetType() == EShapeType::Compound); const CompoundShape *compound = static_cast(inShapeCast.mShape); // Number of sub shapes int n = (int)compound->mSubShapes.size(); // Determine amount of bits for sub shape uint sub_shape_bits = compound->GetSubShapeIDBits(); // Recurse to sub shapes for (int i = 0; i < n; ++i) { const SubShape &shape = compound->mSubShapes[i]; // Create ID for sub shape SubShapeIDCreator shape1_sub_shape_id = inSubShapeIDCreator1.PushID(i, sub_shape_bits); // Transform the shape cast and update the shape Mat44 transform = inShapeCast.mCenterOfMassStart * shape.GetLocalTransformNoScale(inShapeCast.mScale); Vec3 scale = shape.TransformScale(inShapeCast.mScale); ShapeCast shape_cast(shape.mShape, scale, transform, inShapeCast.mDirection); CollisionDispatch::sCastShapeVsShapeLocalSpace(shape_cast, inShapeCastSettings, inShape, inScale, inShapeFilter, inCenterOfMassTransform2, shape1_sub_shape_id, inSubShapeIDCreator2, ioCollector); if (ioCollector.ShouldEarlyOut()) break; } } void CompoundShape::SaveBinaryState(StreamOut &inStream) const { Shape::SaveBinaryState(inStream); inStream.Write(mCenterOfMass); inStream.Write(mLocalBounds.mMin); inStream.Write(mLocalBounds.mMax); inStream.Write(mInnerRadius); // Write sub shapes inStream.Write(mSubShapes, [](const SubShape &inElement, StreamOut &inS) { inS.Write(inElement.mUserData); inS.Write(inElement.mPositionCOM); inS.Write(inElement.mRotation); }); } void CompoundShape::RestoreBinaryState(StreamIn &inStream) { Shape::RestoreBinaryState(inStream); inStream.Read(mCenterOfMass); inStream.Read(mLocalBounds.mMin); inStream.Read(mLocalBounds.mMax); inStream.Read(mInnerRadius); // Read sub shapes inStream.Read(mSubShapes, [](StreamIn &inS, SubShape &outElement) { inS.Read(outElement.mUserData); inS.Read(outElement.mPositionCOM); inS.Read(outElement.mRotation); outElement.mIsRotationIdentity = outElement.mRotation == Float3(0, 0, 0); }); } void CompoundShape::SaveSubShapeState(ShapeList &outSubShapes) const { outSubShapes.clear(); outSubShapes.reserve(mSubShapes.size()); for (const SubShape &shape : mSubShapes) outSubShapes.push_back(shape.mShape); } void CompoundShape::RestoreSubShapeState(const ShapeRefC *inSubShapes, uint inNumShapes) { JPH_ASSERT(mSubShapes.size() == inNumShapes); for (uint i = 0; i < inNumShapes; ++i) mSubShapes[i].mShape = inSubShapes[i]; } Shape::Stats CompoundShape::GetStatsRecursive(VisitedShapes &ioVisitedShapes) const { // Get own stats Stats stats = Shape::GetStatsRecursive(ioVisitedShapes); // Add child stats for (const SubShape &shape : mSubShapes) { Stats child_stats = shape.mShape->GetStatsRecursive(ioVisitedShapes); stats.mSizeBytes += child_stats.mSizeBytes; stats.mNumTriangles += child_stats.mNumTriangles; } return stats; } float CompoundShape::GetVolume() const { float volume = 0.0f; for (const SubShape &shape : mSubShapes) volume += shape.mShape->GetVolume(); return volume; } bool CompoundShape::IsValidScale(Vec3Arg inScale) const { if (!Shape::IsValidScale(inScale)) return false; for (const SubShape &shape : mSubShapes) { // Test if the scale is non-uniform and the shape is rotated if (!shape.IsValidScale(inScale)) return false; // Test the child shape if (!shape.mShape->IsValidScale(shape.TransformScale(inScale))) return false; } return true; } Vec3 CompoundShape::MakeScaleValid(Vec3Arg inScale) const { Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale); if (CompoundShape::IsValidScale(scale)) return scale; Vec3 abs_uniform_scale = ScaleHelpers::MakeUniformScale(scale.Abs()); Vec3 uniform_scale = scale.GetSign() * abs_uniform_scale; if (CompoundShape::IsValidScale(uniform_scale)) return uniform_scale; return Sign(scale.GetX()) * abs_uniform_scale; } void CompoundShape::sRegister() { for (EShapeSubType s1 : sCompoundSubShapeTypes) for (EShapeSubType s2 : sAllSubShapeTypes) CollisionDispatch::sRegisterCastShape(s1, s2, sCastCompoundVsShape); } JPH_NAMESPACE_END