Browse Source

Reduced size of heightfield when all samples are 'no collision'

Jorrit Rouwe 3 years ago
parent
commit
b811308e92

+ 26 - 3
Jolt/Physics/Collision/Shape/HeightFieldShape.cpp

@@ -384,7 +384,7 @@ HeightFieldShape::HeightFieldShape(const HeightFieldShapeSettings &inSettings, S
 		// No materials assigned, validate that no materials have been specified
 		if (!inSettings.mMaterialIndices.empty())
 		{
-			outResult.SetError("No materials present, mHeightSamples should be empty");
+			outResult.SetError("No materials present, mMaterialIndices should be empty");
 			return;
 		}
 	}
@@ -392,6 +392,13 @@ HeightFieldShape::HeightFieldShape(const HeightFieldShapeSettings &inSettings, S
 	// Determine range
 	float min_value, max_value, scale;
 	inSettings.DetermineMinAndMaxSample(min_value, max_value, scale);
+	if (min_value > max_value)
+	{
+		// If there is no collision with this heightmap, leave everything empty
+		mMaterials.clear();
+		outResult.Set(this);
+		return;
+	}
 
 	// Quantize to uint16
 	vector<uint16> quantized_samples;
@@ -642,6 +649,10 @@ inline Vec3 HeightFieldShape::GetPosition(uint inX, uint inY, float inBlockOffse
 
 Vec3 HeightFieldShape::GetPosition(uint inX, uint inY) const
 {
+	// Test if there are any samples
+	if (mHeightSamples.empty())
+		return mOffset + mScale * Vec3(float(inX), 0.0f, float(inY));
+
 	// Get block location
 	uint bx = inX / mBlockSize;
 	uint by = inY / mBlockSize;
@@ -660,11 +671,15 @@ Vec3 HeightFieldShape::GetPosition(uint inX, uint inY) const
 
 bool HeightFieldShape::IsNoCollision(uint inX, uint inY) const
 { 
-	return GetHeightSample(inX, inY) == mSampleMask;
+	return mHeightSamples.empty() || GetHeightSample(inX, inY) == mSampleMask;
 }
 
 bool HeightFieldShape::ProjectOntoSurface(Vec3Arg inLocalPosition, Vec3 &outSurfacePosition, SubShapeID &outSubShapeID) const
 {
+	// Check if we have collision
+	if (mHeightSamples.empty())
+		return false;
+
 	// Convert coordinate to integer space
 	Vec3 integer_space = (inLocalPosition - mOffset) / mScale;
 
@@ -857,6 +872,10 @@ AABox HeightFieldShape::GetLocalBounds() const
 #ifdef JPH_DEBUG_RENDERER
 void HeightFieldShape::Draw(DebugRenderer *inRenderer, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const
 {
+	// Don't draw anything if we don't have any collision
+	if (mHeightSamples.empty())
+		return;
+
 	// Reset the batch if we switch coloring mode
 	if (mCachedUseMaterialColors != inUseMaterialColors)
 	{
@@ -1019,6 +1038,10 @@ public:
 	template <class Visitor>
 	JPH_INLINE void				WalkHeightField(Visitor &ioVisitor)
 	{
+		// Early out if there's no collision
+		if (mShape->mHeightSamples.empty())
+			return;
+
 		// Precalculate values relating to sample count
 		uint32 sample_count = mShape->mSampleCount;
 		UVec4 sample_count_min_1 = UVec4::sReplicate(sample_count - 1);
@@ -1722,7 +1745,7 @@ Shape::Stats HeightFieldShape::GetStats() const
 			+ mHeightSamples.size() * sizeof(uint8) 
 			+ mActiveEdges.size() * sizeof(uint8) 
 			+ mMaterialIndices.size() * sizeof(uint8), 
-		Square(mSampleCount - 1) * 2); 
+		mHeightSamples.empty()? 0 : Square(mSampleCount - 1) * 2);
 }
 
 } // JPH

+ 28 - 5
UnitTests/Physics/HeightFieldShapeTests.cpp

@@ -28,7 +28,7 @@ TEST_SUITE("HeightFieldShapeTests")
 		}
 	}
 
-	static void sValidateGetPosition(const HeightFieldShapeSettings &inSettings, float inMaxError)
+	static Ref<HeightFieldShape> sValidateGetPosition(const HeightFieldShapeSettings &inSettings, float inMaxError)
 	{
 		// Create shape
 		Ref<HeightFieldShape> shape = static_cast<HeightFieldShape *>(inSettings.Create().Get().GetPtr());
@@ -38,7 +38,7 @@ TEST_SUITE("HeightFieldShapeTests")
 		for (uint y = 0; y < inSettings.mSampleCount; ++y)
 			for (uint x = 0; x < inSettings.mSampleCount; ++x)
 			{
-				// Perform a raycast from above the terrain on this location
+				// Perform a raycast from above the height field on this location
 				RayCast ray { inSettings.mOffset + inSettings.mScale * Vec3((float)x, 100.0f, (float)y), inSettings.mScale.GetY() * Vec3(0, -200, 0) };
 				RayCastResult hit;
 				shape->CastRay(ray, SubShapeIDCreator(), hit);
@@ -76,7 +76,7 @@ TEST_SUITE("HeightFieldShapeTests")
 					// Don't test borders, the ray may or may not hit
 					if (x > 0 && y > 0 && x < inSettings.mSampleCount - 1 && y < inSettings.mSampleCount - 1)
 					{
-						// Check that the ray hit the terrain
+						// Check that the ray hit the height field
 						Vec3 hit_pos = ray.mOrigin + ray.mDirection * hit.mFraction;
 						CHECK_APPROX_EQUAL(hit_pos, shape_pos, 1.0e-3f);
 					}
@@ -93,6 +93,8 @@ TEST_SUITE("HeightFieldShapeTests")
 
 		// Check error
 		CHECK(max_diff <= inMaxError);
+
+		return shape;
 	}
 
 	TEST_CASE("TestPlane")
@@ -139,7 +141,7 @@ TEST_SUITE("HeightFieldShapeTests")
 		sValidateGetPosition(settings, 0.0f);
 	}
 
-	TEST_CASE("TestRandomTerrain")
+	TEST_CASE("TestRandomHeightField")
 	{
 		const float cMinHeight = -5.0f;
 		const float cMaxHeight = 10.0f;
@@ -147,7 +149,7 @@ TEST_SUITE("HeightFieldShapeTests")
 		UnitTestRandom random;
 		uniform_real_distribution<float> height_distribution(cMinHeight, cMaxHeight);
 
-		// Create terrain with random samples
+		// Create height field with random samples
 		HeightFieldShapeSettings settings;
 		settings.mOffset = Vec3(0.3f, 0.5f, 0.7f);
 		settings.mScale = Vec3(1.1f, 1.2f, 1.3f);
@@ -172,4 +174,25 @@ TEST_SUITE("HeightFieldShapeTests")
 		sRandomizeMaterials(settings, 1);
 		sValidateGetPosition(settings, settings.mScale.GetY() * (cMaxHeight - cMinHeight) / ((1 << settings.mBitsPerSample) - 1));
 	}
+
+	TEST_CASE("TestEmptyHeightField")
+	{
+		// Create height field with no collision
+		HeightFieldShapeSettings settings;
+		settings.mSampleCount = 32;
+		settings.mHeightSamples.resize(Square(settings.mSampleCount));
+		for (float &h : settings.mHeightSamples)
+			h = HeightFieldShapeConstants::cNoCollisionValue;
+
+		// This should use the minimum amount of bits
+		CHECK(settings.CalculateBitsPerSampleForError(0.0f) == 1);
+
+		sRandomizeMaterials(settings, 50);
+		Ref<HeightFieldShape> shape = sValidateGetPosition(settings, 0.0f);
+
+		// Check that we allocated the minimum amount of memory
+		Shape::Stats stats = shape->GetStats();
+		CHECK(stats.mNumTriangles == 0);
+		CHECK(stats.mSizeBytes == sizeof(HeightFieldShape));
+	}
 }