Преглед на файлове

Added Shape::MakeScaleValid function (#1172)

This function will take a scale vector and check it against the scaling rules for the shape. If it is not valid, it will return a scale that is close to the provided scale which is valid. For e.g. sphere shapes it means that the output is always a uniform scale, for a cylinder X and Z are averaged.
Bugfix: The IsValidScale will now also return false when one of the scale components is zero.
Jorrit Rouwe преди 1 година
родител
ревизия
034b601d3e

+ 7 - 9
Jolt/Physics/Collision/Shape/CapsuleShape.cpp

@@ -380,15 +380,6 @@ void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec
 		}
 }
 
-void CapsuleShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const
-{
-	Vec3 scale;
-	Mat44 transform = inCenterOfMassTransform.Decompose(scale);
-	TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator());
-	ts.SetShapeScale(ScaleHelpers::MakeUniformScale(scale.Abs()));
-	ioCollector.AddHit(ts);
-}
-
 void CapsuleShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
 {
 	JPH_ASSERT(IsValidScale(inScale));
@@ -436,6 +427,13 @@ bool CapsuleShape::IsValidScale(Vec3Arg inScale) const
 	return ConvexShape::IsValidScale(inScale) && ScaleHelpers::IsUniformScale(inScale.Abs());
 }
 
+Vec3 CapsuleShape::MakeScaleValid(Vec3Arg inScale) const
+{
+	Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);
+
+	return scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs());
+}
+
 void CapsuleShape::sRegister()
 {
 	ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::Capsule);

+ 3 - 3
Jolt/Physics/Collision/Shape/CapsuleShape.h

@@ -88,9 +88,6 @@ public:
 	// See: Shape::CollideSoftBodyVertices
 	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
-	// See Shape::TransformShape
-	virtual void			TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;
-
 	// See Shape::GetTrianglesStart
 	virtual void			GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;
 
@@ -109,6 +106,9 @@ public:
 	// See Shape::IsValidScale
 	virtual bool			IsValidScale(Vec3Arg inScale) const override;
 
+	// See Shape::MakeScaleValid
+	virtual Vec3			MakeScaleValid(Vec3Arg inScale) const override;
+
 	// Register shape functions with the registry
 	static void				sRegister();
 

+ 14 - 0
Jolt/Physics/Collision/Shape/CompoundShape.cpp

@@ -388,6 +388,20 @@ bool CompoundShape::IsValidScale(Vec3Arg inScale) const
 	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)

+ 3 - 0
Jolt/Physics/Collision/Shape/CompoundShape.h

@@ -298,6 +298,9 @@ public:
 	// See Shape::IsValidScale
 	virtual bool					IsValidScale(Vec3Arg inScale) const override;
 
+	// See Shape::MakeScaleValid
+	virtual Vec3					MakeScaleValid(Vec3Arg inScale) const override;
+
 	// Register shape functions with the registry
 	static void						sRegister();
 

+ 9 - 11
Jolt/Physics/Collision/Shape/CylinderShape.cpp

@@ -360,17 +360,6 @@ void CylinderShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Ve
 		}
 }
 
-void CylinderShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const
-{
-	Vec3 scale;
-	Mat44 transform = inCenterOfMassTransform.Decompose(scale);
-	TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator());
-	Vec3 abs_scale = scale.Abs();
-	float xz = 0.5f * (abs_scale.GetX() + abs_scale.GetZ());
-	ts.SetShapeScale(Vec3(xz, abs_scale.GetY(), xz));
-	ioCollector.AddHit(ts);
-}
-
 void CylinderShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
 {
 	Mat44 unit_cylinder_transform(Vec4(mRadius, 0, 0, 0), Vec4(0, mHalfHeight, 0, 0), Vec4(0, 0, mRadius, 0), Vec4(0, 0, 0, 1));
@@ -407,6 +396,15 @@ bool CylinderShape::IsValidScale(Vec3Arg inScale) const
 	return ConvexShape::IsValidScale(inScale) && abs_scale.Swizzle<SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>().IsClose(abs_scale, ScaleHelpers::cScaleToleranceSq);
 }
 
+Vec3 CylinderShape::MakeScaleValid(Vec3Arg inScale) const
+{
+	Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);
+
+	// Average X and Z
+	Vec3 abs_scale = scale.Abs();
+	return 0.5f * scale.GetSign() * (abs_scale + abs_scale.Swizzle<SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>());
+}
+
 void CylinderShape::sRegister()
 {
 	ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::Cylinder);

+ 3 - 3
Jolt/Physics/Collision/Shape/CylinderShape.h

@@ -83,9 +83,6 @@ public:
 	// See: Shape::CollideSoftBodyVertices
 	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
-	// See Shape::TransformShape
-	virtual void			TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;
-
 	// See Shape::GetTrianglesStart
 	virtual void			GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;
 
@@ -107,6 +104,9 @@ public:
 	// See Shape::IsValidScale
 	virtual bool			IsValidScale(Vec3Arg inScale) const override;
 
+	// See Shape::MakeScaleValid
+	virtual Vec3			MakeScaleValid(Vec3Arg inScale) const override;
+
 	// Register shape functions with the registry
 	static void				sRegister();
 

+ 3 - 0
Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h

@@ -123,6 +123,9 @@ public:
 	// See Shape::IsValidScale
 	virtual bool					IsValidScale(Vec3Arg inScale) const override			{ return mInnerShape->IsValidScale(inScale); }
 
+	// See Shape::MakeScaleValid
+	virtual Vec3					MakeScaleValid(Vec3Arg inScale) const override			{ return mInnerShape->MakeScaleValid(inScale); }
+
 	// Register shape functions with the registry
 	static void						sRegister();
 

+ 18 - 0
Jolt/Physics/Collision/Shape/RotatedTranslatedShape.cpp

@@ -294,6 +294,24 @@ bool RotatedTranslatedShape::IsValidScale(Vec3Arg inScale) const
 	return mInnerShape->IsValidScale(ScaleHelpers::RotateScale(mRotation, inScale));
 }
 
+Vec3 RotatedTranslatedShape::MakeScaleValid(Vec3Arg inScale) const
+{
+	Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);
+
+	if (mIsRotationIdentity || ScaleHelpers::IsUniformScale(scale))
+		return mInnerShape->MakeScaleValid(scale);
+
+	if (ScaleHelpers::CanScaleBeRotated(mRotation, scale))
+		return ScaleHelpers::RotateScale(mRotation.Conjugated(), mInnerShape->MakeScaleValid(ScaleHelpers::RotateScale(mRotation, scale)));
+
+	Vec3 abs_uniform_scale = ScaleHelpers::MakeUniformScale(scale.Abs());
+	Vec3 uniform_scale = scale.GetSign() * abs_uniform_scale;
+	if (ScaleHelpers::CanScaleBeRotated(mRotation, uniform_scale))
+		return uniform_scale;
+
+	return Sign(scale.GetX()) * abs_uniform_scale;
+}
+
 void RotatedTranslatedShape::sRegister()
 {
 	ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::RotatedTranslated);

+ 3 - 0
Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h

@@ -124,6 +124,9 @@ public:
 	// See Shape::IsValidScale
 	virtual bool					IsValidScale(Vec3Arg inScale) const override;
 
+	// See Shape::MakeScaleValid
+	virtual Vec3					MakeScaleValid(Vec3Arg inScale) const override;
+
 	// Register shape functions with the registry
 	static void						sRegister();
 

+ 9 - 0
Jolt/Physics/Collision/Shape/ScaleHelpers.h

@@ -11,6 +11,9 @@ JPH_NAMESPACE_BEGIN
 /// Helper functions to get properties of a scaling vector
 namespace ScaleHelpers
 {
+	/// Minimum valid scale value. This is used to prevent division by zero when scaling a shape with a zero scale.
+	static constexpr float	cMinScale = 1.0e-6f;
+
 	/// The tolerance used to check if components of the scale vector are the same
 	static constexpr float	cScaleToleranceSq = 1.0e-8f;
 
@@ -26,6 +29,12 @@ namespace ScaleHelpers
 	/// Test if a scale flips an object inside out (which requires flipping all normals and polygon windings)
 	inline bool				IsInsideOut(Vec3Arg inScale)									{ return (CountBits(Vec3::sLess(inScale, Vec3::sZero()).GetTrues() & 0x7) & 1) != 0; }
 
+	/// Test if any of the components of the scale have a value below cMinScale
+	inline bool				IsZeroScale(Vec3Arg inScale)									{ return Vec3::sLess(inScale.Abs(), Vec3::sReplicate(cMinScale)).TestAnyXYZTrue(); }
+
+	/// Ensure that the scale for each component is at least cMinScale
+	inline Vec3				MakeNonZeroScale(Vec3Arg inScale)								{ return inScale.GetSign() * Vec3::sMax(inScale.Abs(), Vec3::sReplicate(cMinScale)); }
+
 	/// Get the average scale if inScale, used to make the scale uniform when a shape doesn't support non-uniform scale
 	inline Vec3				MakeUniformScale(Vec3Arg inScale)								{ return Vec3::sReplicate((inScale.GetX() + inScale.GetY() + inScale.GetZ()) / 3.0f); }
 

+ 8 - 0
Jolt/Physics/Collision/Shape/ScaledShape.cpp

@@ -5,6 +5,7 @@
 #include <Jolt/Jolt.h>
 
 #include <Jolt/Physics/Collision/Shape/ScaledShape.h>
+#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>
 #include <Jolt/Physics/Collision/RayCast.h>
 #include <Jolt/Physics/Collision/ShapeCast.h>
 #include <Jolt/Physics/Collision/TransformedShape.h>
@@ -175,6 +176,13 @@ bool ScaledShape::IsValidScale(Vec3Arg inScale) const
 	return mInnerShape->IsValidScale(inScale * mScale);
 }
 
+Vec3 ScaledShape::MakeScaleValid(Vec3Arg inScale) const
+{
+	Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);
+
+	return mInnerShape->MakeScaleValid(mScale * scale) / scale;
+}
+
 void ScaledShape::sCollideScaledVsShape(const Shape *inShape1, const Shape *inShape2, Vec3Arg inScale1, Vec3Arg inScale2, Mat44Arg inCenterOfMassTransform1, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, const CollideShapeSettings &inCollideShapeSettings, CollideShapeCollector &ioCollector, const ShapeFilter &inShapeFilter)
 {
 	JPH_ASSERT(inShape1->GetSubType() == EShapeSubType::Scaled);

+ 3 - 0
Jolt/Physics/Collision/Shape/ScaledShape.h

@@ -120,6 +120,9 @@ public:
 	// See Shape::IsValidScale
 	virtual bool					IsValidScale(Vec3Arg inScale) const override;
 
+	// See Shape::MakeScaleValid
+	virtual Vec3					MakeScaleValid(Vec3Arg inScale) const override;
+
 	// Register shape functions with the registry
 	static void						sRegister();
 

+ 11 - 1
Jolt/Physics/Collision/Shape/Shape.cpp

@@ -59,7 +59,7 @@ void Shape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCol
 	Vec3 scale;
 	Mat44 transform = inCenterOfMassTransform.Decompose(scale);
 	TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator());
-	ts.SetShapeScale(scale);
+	ts.SetShapeScale(MakeScaleValid(scale));
 	ioCollector.AddHit(ts);
 }
 
@@ -215,6 +215,16 @@ Shape::Stats Shape::GetStatsRecursive(VisitedShapes &ioVisitedShapes) const
 	return stats;
 }
 
+bool Shape::IsValidScale(Vec3Arg inScale) const
+{
+	return !ScaleHelpers::IsZeroScale(inScale);
+}
+
+Vec3 Shape::MakeScaleValid(Vec3Arg inScale) const
+{
+	return ScaleHelpers::MakeNonZeroScale(inScale);
+}
+
 Shape::ShapeResult Shape::ScaleShape(Vec3Arg inScale) const
 {
 	const Vec3 unit_scale = Vec3::sReplicate(1.0f);

+ 8 - 1
Jolt/Physics/Collision/Shape/Shape.h

@@ -424,7 +424,14 @@ public:
 	/// * CylinderShape: Scale must be uniform in XZ plane, Y can scale independently (signs of scale are ignored).
 	/// * RotatedTranslatedShape: Scale must not cause shear in the child shape.
 	/// * CompoundShape: Scale must not cause shear in any of the child shapes.
-	virtual bool					IsValidScale(Vec3Arg inScale) const									{ return !inScale.IsNearZero(); }
+	virtual bool					IsValidScale(Vec3Arg inScale) const;
+
+	/// This function will make sure that if you wrap this shape in a ScaledShape that the scale is valid.
+	/// Note that this involves discarding components of the scale that are invalid, so the resulting scaled shape may be different than the requested scale.
+	/// Compare the return value of this function with the scale you passed in to detect major inconsistencies and possibly warn the user.
+	/// @param inScale Local space scale for this shape.
+	/// @return Scale that can be used to wrap this shape in a ScaledShape. IsValidScale will return true for this scale.
+	virtual Vec3					MakeScaleValid(Vec3Arg inScale) const;
 
 #ifdef JPH_DEBUG_RENDERER
 	/// Debug helper which draws the intersection between water and the shapes, the center of buoyancy and the submerged volume

+ 7 - 9
Jolt/Physics/Collision/Shape/SphereShape.cpp

@@ -303,15 +303,6 @@ void SphereShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3
 		}
 }
 
-void SphereShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const
-{
-	Vec3 scale;
-	Mat44 transform = inCenterOfMassTransform.Decompose(scale);
-	TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator());
-	ts.SetShapeScale(ScaleHelpers::MakeUniformScale(scale.Abs()));
-	ioCollector.AddHit(ts);
-}
-
 void SphereShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
 {
 	float scaled_radius = GetScaledRadius(inScale);
@@ -342,6 +333,13 @@ bool SphereShape::IsValidScale(Vec3Arg inScale) const
 	return ConvexShape::IsValidScale(inScale) && ScaleHelpers::IsUniformScale(inScale.Abs());
 }
 
+Vec3 SphereShape::MakeScaleValid(Vec3Arg inScale) const
+{
+	Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);
+
+	return scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs());
+}
+
 void SphereShape::sRegister()
 {
 	ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::Sphere);

+ 3 - 3
Jolt/Physics/Collision/Shape/SphereShape.h

@@ -83,9 +83,6 @@ public:
 	// See: Shape::CollideSoftBodyVertices
 	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
-	// See Shape::TransformShape
-	virtual void			TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;
-
 	// See Shape::GetTrianglesStart
 	virtual void			GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;
 
@@ -104,6 +101,9 @@ public:
 	// See Shape::IsValidScale
 	virtual bool			IsValidScale(Vec3Arg inScale) const override;
 
+	// See Shape::MakeScaleValid
+	virtual Vec3			MakeScaleValid(Vec3Arg inScale) const override;
+
 	// Register shape functions with the registry
 	static void				sRegister();
 

+ 7 - 9
Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp

@@ -406,15 +406,6 @@ AABox TaperedCapsuleShape::GetInertiaApproximation() const
 	return AABox(Vec3(-avg_radius, mBottomCenter - mBottomRadius, -avg_radius), Vec3(avg_radius, mTopCenter + mTopRadius, avg_radius));
 }
 
-void TaperedCapsuleShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const
-{
-	Vec3 scale;
-	Mat44 transform = inCenterOfMassTransform.Decompose(scale);
-	TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator());
-	ts.SetShapeScale(scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs()));
-	ioCollector.AddHit(ts);
-}
-
 void TaperedCapsuleShape::SaveBinaryState(StreamOut &inStream) const
 {
 	ConvexShape::SaveBinaryState(inStream);
@@ -448,6 +439,13 @@ bool TaperedCapsuleShape::IsValidScale(Vec3Arg inScale) const
 	return ConvexShape::IsValidScale(inScale) && ScaleHelpers::IsUniformScale(inScale.Abs());
 }
 
+Vec3 TaperedCapsuleShape::MakeScaleValid(Vec3Arg inScale) const
+{
+	Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);
+
+	return scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs());
+}
+
 void TaperedCapsuleShape::sRegister()
 {
 	ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::TaperedCapsule);

+ 3 - 3
Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h

@@ -79,9 +79,6 @@ public:
 	virtual void			Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const override;
 #endif // JPH_DEBUG_RENDERER
 
-	// See Shape::TransformShape
-	virtual void			TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;
-
 	// See Shape
 	virtual void			SaveBinaryState(StreamOut &inStream) const override;
 
@@ -94,6 +91,9 @@ public:
 	// See Shape::IsValidScale
 	virtual bool			IsValidScale(Vec3Arg inScale) const override;
 
+	// See Shape::MakeScaleValid
+	virtual Vec3			MakeScaleValid(Vec3Arg inScale) const override;
+
 	// Register shape functions with the registry
 	static void				sRegister();
 

+ 10 - 9
Jolt/Physics/Collision/Shape/TriangleShape.cpp

@@ -313,15 +313,6 @@ void TriangleShape::sCastSphereVsTriangle(const ShapeCast &inShapeCast, const Sh
 	caster.Cast(shape->mV1, shape->mV2, shape->mV3, 0b111, inSubShapeIDCreator2.GetID());
 }
 
-void TriangleShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const
-{
-	Vec3 scale;
-	Mat44 transform = inCenterOfMassTransform.Decompose(scale);
-	TransformedShape ts(RVec3(transform.GetTranslation()), transform.GetQuaternion(), this, BodyID(), SubShapeIDCreator());
-	ts.SetShapeScale(mConvexRadius == 0.0f? scale : scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs()));
-	ioCollector.AddHit(ts);
-}
-
 class TriangleShape::TSGetTrianglesContext
 {
 public:
@@ -393,6 +384,16 @@ bool TriangleShape::IsValidScale(Vec3Arg inScale) const
 	return ConvexShape::IsValidScale(inScale) && (mConvexRadius == 0.0f || ScaleHelpers::IsUniformScale(inScale.Abs()));
 }
 
+Vec3 TriangleShape::MakeScaleValid(Vec3Arg inScale) const
+{
+	Vec3 scale = ScaleHelpers::MakeNonZeroScale(inScale);
+
+	if (mConvexRadius == 0.0f)
+		return scale;
+
+	return scale.GetSign() * ScaleHelpers::MakeUniformScale(scale.Abs());
+}
+
 void TriangleShape::sRegister()
 {
 	ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::Triangle);

+ 3 - 3
Jolt/Physics/Collision/Shape/TriangleShape.h

@@ -87,9 +87,6 @@ public:
 	// See: Shape::CollideSoftBodyVertices
 	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
-	// See Shape::TransformShape
-	virtual void			TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;
-
 	// See Shape::GetTrianglesStart
 	virtual void			GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;
 
@@ -108,6 +105,9 @@ public:
 	// See Shape::IsValidScale
 	virtual bool			IsValidScale(Vec3Arg inScale) const override;
 
+	// See Shape::MakeScaleValid
+	virtual Vec3			MakeScaleValid(Vec3Arg inScale) const override;
+
 	// Register shape functions with the registry
 	static void				sRegister();
 

+ 1 - 1
Jolt/Physics/Constraints/HingeConstraint.h

@@ -31,7 +31,7 @@ public:
 	/// Hinge axis is the axis where rotation is allowed.
 	/// When the normal axis of both bodies align in world space, the hinge angle is defined to be 0.
 	/// mHingeAxis1 and mNormalAxis1 should be perpendicular. mHingeAxis2 and mNormalAxis2 should also be perpendicular.
-	/// If you configure the joint in world space and create both bodies with a relative rotation you want to be defined as zero, 
+	/// If you configure the joint in world space and create both bodies with a relative rotation you want to be defined as zero,
 	/// you can simply set mHingeAxis1 = mHingeAxis2 and mNormalAxis1 = mNormalAxis2.
 	RVec3						mPoint1 = RVec3::sZero();
 	Vec3						mHingeAxis1 = Vec3::sAxisY();

+ 3 - 15
Samples/SamplesApp.cpp

@@ -810,18 +810,10 @@ void SamplesApp::TakeAndReloadSnapshot()
 
 RefConst<Shape> SamplesApp::CreateProbeShape()
 {
-	// Get the scale
-	Vec3 scale = mScaleShape? mShapeScale : Vec3::sReplicate(1.0f);
-
-	// Make it minimally -0.1 or 0.1 depending on the sign
-	Vec3 clamped_value = Vec3::sSelect(Vec3::sReplicate(-0.1f), Vec3::sReplicate(0.1f), Vec3::sGreaterOrEqual(scale, Vec3::sZero()));
-	scale = Vec3::sSelect(scale, clamped_value, Vec3::sLess(scale.Abs(), Vec3::sReplicate(0.1f)));
-
 	RefConst<Shape> shape;
 	switch (mProbeShape)
 	{
 	case EProbeShape::Sphere:
-		scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_X, SWIZZLE_X>(); // Only uniform scale supported
 		shape = new SphereShape(0.2f);
 		break;
 
@@ -842,27 +834,22 @@ RefConst<Shape> SamplesApp::CreateProbeShape()
 		break;
 
 	case EProbeShape::Capsule:
-		scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_X, SWIZZLE_X>(); // Only uniform scale supported
 		shape = new CapsuleShape(0.2f, 0.1f);
 		break;
 
 	case EProbeShape::TaperedCapsule:
-		scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_X, SWIZZLE_X>(); // Only uniform scale supported
 		shape = TaperedCapsuleShapeSettings(0.2f, 0.1f, 0.2f).Create().Get();
 		break;
 
 	case EProbeShape::Cylinder:
-		scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_Y, SWIZZLE_X>(); // Scale X must be same as Z
 		shape = new CylinderShape(0.2f, 0.1f);
 		break;
 
 	case EProbeShape::Triangle:
-		scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_X, SWIZZLE_X>(); // Only uniform scale supported
 		shape = new TriangleShape(Vec3(0.1f, 0.9f, 0.3f), Vec3(-0.9f, -0.5f, 0.2f), Vec3(0.7f, -0.3f, -0.1f));
 		break;
 
 	case EProbeShape::RotatedTranslated:
-		scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_Y, SWIZZLE_X>(); // Can freely scale around y but x and z must be the same
 		shape = new RotatedTranslatedShape(Vec3(0.1f, 0.2f, 0.3f), Quat::sRotation(Vec3::sAxisY(), 0.25f * JPH_PI), new BoxShape(Vec3(0.1f, 0.2f, 0.3f)));
 		break;
 
@@ -883,7 +870,6 @@ RefConst<Shape> SamplesApp::CreateProbeShape()
 
 	case EProbeShape::StaticCompound2:
 		{
-			scale = scale.Swizzle<SWIZZLE_X, SWIZZLE_X, SWIZZLE_X>(); // Only uniform scale supported
 			Ref<StaticCompoundShapeSettings> compound = new StaticCompoundShapeSettings();
 			compound->AddShape(Vec3(0, 0.5f, 0), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new BoxShape(Vec3(0.5f, 0.15f, 0.1f)));
 			compound->AddShape(Vec3(0.5f, 0, 0), Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), new CylinderShape(0.5f, 0.1f));
@@ -918,7 +904,9 @@ RefConst<Shape> SamplesApp::CreateProbeShape()
 	JPH_ASSERT(shape != nullptr);
 
 	// Scale the shape
-	if (scale != Vec3::sReplicate(1.0f))
+	Vec3 scale = mScaleShape? shape->MakeScaleValid(mShapeScale) : Vec3::sReplicate(1.0f);
+	JPH_ASSERT(shape->IsValidScale(scale)); // Double check the MakeScaleValid function
+	if (!ScaleHelpers::IsNotScaled(scale))
 		shape = new ScaledShape(shape, scale);
 
 	return shape;

+ 56 - 0
UnitTests/Physics/ShapeTests.cpp

@@ -120,6 +120,8 @@ TEST_SUITE("ShapeTests")
 	// Test IsValidScale function
 	TEST_CASE("TestIsValidScale")
 	{
+		constexpr float cMinScaleToleranceSq = Square(1.0e-6f * ScaleHelpers::cMinScale);
+
 		// Test simple shapes
 		Ref<Shape> sphere = new SphereShape(2.0f);
 		CHECK(!sphere->IsValidScale(Vec3::sZero()));
@@ -128,14 +130,20 @@ TEST_SUITE("ShapeTests")
 		CHECK(!sphere->IsValidScale(Vec3(2, 1, 1)));
 		CHECK(!sphere->IsValidScale(Vec3(1, 2, 1)));
 		CHECK(!sphere->IsValidScale(Vec3(1, 1, 2)));
+		CHECK(sphere->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq)); // Averaging can cause a slight error
+		CHECK(sphere->MakeScaleValid(Vec3(-2, 3, 4)) == Vec3(-3, 3, 3));
 
 		Ref<Shape> capsule = new CapsuleShape(2.0f, 0.5f);
 		CHECK(!capsule->IsValidScale(Vec3::sZero()));
+		CHECK(!capsule->IsValidScale(Vec3(0, 1, 0)));
+		CHECK(!capsule->IsValidScale(Vec3(1, 0, 1)));
 		CHECK(capsule->IsValidScale(Vec3(2, 2, 2)));
 		CHECK(capsule->IsValidScale(Vec3(-1, 1, -1)));
 		CHECK(!capsule->IsValidScale(Vec3(2, 1, 1)));
 		CHECK(!capsule->IsValidScale(Vec3(1, 2, 1)));
 		CHECK(!capsule->IsValidScale(Vec3(1, 1, 2)));
+		CHECK(capsule->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
+		CHECK(capsule->MakeScaleValid(Vec3(-2, 3, 4)) == Vec3(-3, 3, 3));
 
 		Ref<Shape> tapered_capsule = TaperedCapsuleShapeSettings(2.0f, 0.5f, 0.7f).Create().Get();
 		CHECK(!tapered_capsule->IsValidScale(Vec3::sZero()));
@@ -144,30 +152,47 @@ TEST_SUITE("ShapeTests")
 		CHECK(!tapered_capsule->IsValidScale(Vec3(2, 1, 1)));
 		CHECK(!tapered_capsule->IsValidScale(Vec3(1, 2, 1)));
 		CHECK(!tapered_capsule->IsValidScale(Vec3(1, 1, 2)));
+		CHECK(tapered_capsule->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
+		CHECK(tapered_capsule->MakeScaleValid(Vec3(2, -3, 4)) == Vec3(3, -3, 3));
 
 		Ref<Shape> cylinder = new CylinderShape(0.5f, 2.0f);
 		CHECK(!cylinder->IsValidScale(Vec3::sZero()));
+		CHECK(!cylinder->IsValidScale(Vec3(0, 1, 0)));
+		CHECK(!cylinder->IsValidScale(Vec3(1, 0, 1)));
 		CHECK(cylinder->IsValidScale(Vec3(2, 2, 2)));
 		CHECK(cylinder->IsValidScale(Vec3(-1, 1, -1)));
 		CHECK(!cylinder->IsValidScale(Vec3(2, 1, 1)));
 		CHECK(cylinder->IsValidScale(Vec3(1, 2, 1)));
 		CHECK(!cylinder->IsValidScale(Vec3(1, 1, 2)));
+		CHECK(cylinder->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
+		CHECK(cylinder->MakeScaleValid(Vec3(-1.0e-10f, 1, 1.0e-10f)) == Vec3(-ScaleHelpers::cMinScale, 1, ScaleHelpers::cMinScale));
+		CHECK(cylinder->MakeScaleValid(Vec3(2, 5, -4)) == Vec3(3, 5, -3));
 
 		Ref<Shape> triangle = new TriangleShape(Vec3(1, 2, 3), Vec3(4, 5, 6), Vec3(7, 8, 9));
 		CHECK(!triangle->IsValidScale(Vec3::sZero()));
+		CHECK(!triangle->IsValidScale(Vec3::sAxisX()));
+		CHECK(!triangle->IsValidScale(Vec3::sAxisY()));
+		CHECK(!triangle->IsValidScale(Vec3::sAxisZ()));
 		CHECK(triangle->IsValidScale(Vec3(2, 2, 2)));
 		CHECK(triangle->IsValidScale(Vec3(-1, 1, -1)));
 		CHECK(triangle->IsValidScale(Vec3(2, 1, 1)));
 		CHECK(triangle->IsValidScale(Vec3(1, 2, 1)));
 		CHECK(triangle->IsValidScale(Vec3(1, 1, 2)));
+		CHECK(triangle->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
+		CHECK(triangle->MakeScaleValid(Vec3(2, 5, -4)) == Vec3(2, 5, -4));
 
 		Ref<Shape> triangle2 = new TriangleShape(Vec3(1, 2, 3), Vec3(4, 5, 6), Vec3(7, 8, 9), 0.01f); // With convex radius
 		CHECK(!triangle2->IsValidScale(Vec3::sZero()));
+		CHECK(!triangle2->IsValidScale(Vec3::sAxisX()));
+		CHECK(!triangle2->IsValidScale(Vec3::sAxisY()));
+		CHECK(!triangle2->IsValidScale(Vec3::sAxisZ()));
 		CHECK(triangle2->IsValidScale(Vec3(2, 2, 2)));
 		CHECK(triangle2->IsValidScale(Vec3(-1, 1, -1)));
 		CHECK(!triangle2->IsValidScale(Vec3(2, 1, 1)));
 		CHECK(!triangle2->IsValidScale(Vec3(1, 2, 1)));
 		CHECK(!triangle2->IsValidScale(Vec3(1, 1, 2)));
+		CHECK(triangle2->MakeScaleValid(Vec3::sZero()).IsClose(Vec3::sReplicate(ScaleHelpers::cMinScale), cMinScaleToleranceSq));
+		CHECK(triangle2->MakeScaleValid(Vec3(2, 6, -4)) == Vec3(4, 4, -4));
 
 		Ref<Shape> scaled = new ScaledShape(sphere, Vec3(1, 2, 1));
 		CHECK(!scaled->IsValidScale(Vec3::sZero()));
@@ -282,6 +307,22 @@ TEST_SUITE("ShapeTests")
 		CHECK(!mutable_compound4->IsValidScale(Vec3(1, 2, 1)));
 		CHECK(mutable_compound4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
 
+		// Test a cylinder rotated by 90 degrees around Z rotating Y to X, meaning that Y and Z should be scaled uniformly
+		MutableCompoundShapeSettings mutable_compound_settings5;
+		mutable_compound_settings5.AddShape(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), new CylinderShape(1.0f, 0.5f));
+		Ref<Shape> mutable_compound5 = mutable_compound_settings5.Create().Get();
+		CHECK(mutable_compound5->IsValidScale(Vec3::sReplicate(2)));
+		CHECK(mutable_compound5->IsValidScale(Vec3(1, 2, 2)));
+		CHECK(mutable_compound5->IsValidScale(Vec3(1, 2, -2)));
+		CHECK(!mutable_compound5->IsValidScale(Vec3(2, 1, 2)));
+		CHECK(!mutable_compound5->IsValidScale(Vec3(2, 2, 1)));
+		CHECK(mutable_compound5->MakeScaleValid(Vec3::sReplicate(2)).IsClose(Vec3::sReplicate(2)));
+		CHECK(mutable_compound5->MakeScaleValid(Vec3::sReplicate(-2)).IsClose(Vec3::sReplicate(-2)));
+		CHECK(mutable_compound5->MakeScaleValid(Vec3(1, 2, 2)).IsClose(Vec3(1, 2, 2)));
+		CHECK(mutable_compound5->MakeScaleValid(Vec3(1, 2, -2)).IsClose(Vec3(1, 2, -2)));
+		CHECK(mutable_compound5->MakeScaleValid(Vec3(2, 1, 2)).IsClose(Vec3::sReplicate(5.0f / 3.0f))); // Not the best solution, but we don't have logic to average over YZ only
+		CHECK(mutable_compound5->MakeScaleValid(Vec3(2, 2, 1)).IsClose(Vec3::sReplicate(5.0f / 3.0f))); // Not the best solution, but we don't have logic to average over YZ only
+
 		// Test a rotated translated shape that can only be scaled uniformly
 		RotatedTranslatedShapeSettings rt_settings(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisX(), 0.1f * JPH_PI), sphere);
 		Ref<Shape> rt_shape = rt_settings.Create().Get();
@@ -321,6 +362,21 @@ TEST_SUITE("ShapeTests")
 		CHECK(!rt_shape4->IsValidScale(Vec3(2, 1, 1)));
 		CHECK(!rt_shape4->IsValidScale(Vec3(1, 2, 1)));
 		CHECK(rt_shape4->IsValidScale(Vec3(1, 1, 2))); // We're rotation around Z, so non-uniform in the Z direction is ok
+
+		// Test a cylinder rotated by 90 degrees around Z rotating Y to X, meaning that Y and Z should be scaled uniformly
+		RotatedTranslatedShapeSettings rt_settings5(Vec3(1, 2, 3), Quat::sRotation(Vec3::sAxisZ(), -0.5f * JPH_PI), new CylinderShape(1.0f, 0.5f));
+		Ref<Shape> rt_shape5 = rt_settings5.Create().Get();
+		CHECK(rt_shape5->IsValidScale(Vec3::sReplicate(2)));
+		CHECK(rt_shape5->IsValidScale(Vec3(1, 2, 2)));
+		CHECK(rt_shape5->IsValidScale(Vec3(1, 2, -2)));
+		CHECK(!rt_shape5->IsValidScale(Vec3(2, 1, 2)));
+		CHECK(!rt_shape5->IsValidScale(Vec3(2, 2, 1)));
+		CHECK(rt_shape5->MakeScaleValid(Vec3::sReplicate(2)).IsClose(Vec3::sReplicate(2)));
+		CHECK(rt_shape5->MakeScaleValid(Vec3::sReplicate(-2)).IsClose(Vec3::sReplicate(-2)));
+		CHECK(rt_shape5->MakeScaleValid(Vec3(1, 2, 2)).IsClose(Vec3(1, 2, 2)));
+		CHECK(rt_shape5->MakeScaleValid(Vec3(1, 2, -2)).IsClose(Vec3(1, 2, -2)));
+		CHECK(rt_shape5->MakeScaleValid(Vec3(2, 1, 2)).IsClose(Vec3(2, 1.5f, 1.5f))); // YZ will be averaged here
+		CHECK(rt_shape5->MakeScaleValid(Vec3(2, 2, 1)).IsClose(Vec3(2, 1.5f, 1.5f))); // YZ will be averaged here
 	}
 
 	// Test embedded shape