Browse Source

Added easy to use implementation of DebugRenderer (#1019)

Jorrit Rouwe 1 year ago
parent
commit
c94628d2a0

+ 1 - 1
Docs/Architecture.md

@@ -577,7 +577,7 @@ These functions / systems need to be registered in advance.
 
 # Debug Rendering {#debug-rendering}
 
-When the define JPH_DEBUG_RENDERER is defined (which by default is defined in Debug and Release but not Distribution), Jolt is able to render its internal state. To integrate this into your own application you must inherit from the DebugRenderer class and implement the pure virtual functions DebugRenderer::DrawLine, DebugRenderer::DrawTriangle, DebugRenderer::CreateTriangleBatch, DebugRenderer::DrawGeometry and DebugRenderer::DrawText3D. The CreateTriangleBatch is used to prepare a batch of triangles to be drawn by a single DrawGeometry call, which means that Jolt can render a complex scene much more efficiently than when each triangle in that scene would have been drawn through DrawTriangle. At run-time create an instance of your DebugRenderer which will internally assign itself to DebugRenderer::sInstance. Finally call for example PhysicsSystem::DrawBodies or PhysicsSystem::DrawConstraints to draw the state of the simulation. For an example implementation see [the DebugRenderer from the Samples application](https://github.com/jrouwe/JoltPhysics/blob/master/TestFramework/Renderer/DebugRendererImp.h).
+When the define JPH_DEBUG_RENDERER is defined (which by default is defined in Debug and Release but not Distribution), Jolt is able to render its internal state. To integrate this into your own application you must inherit from the DebugRenderer class and implement the pure virtual functions DebugRenderer::DrawLine, DebugRenderer::DrawTriangle, DebugRenderer::CreateTriangleBatch, DebugRenderer::DrawGeometry and DebugRenderer::DrawText3D. The CreateTriangleBatch is used to prepare a batch of triangles to be drawn by a single DrawGeometry call, which means that Jolt can render a complex scene much more efficiently than when each triangle in that scene would have been drawn through DrawTriangle. At run-time create an instance of your DebugRenderer which will internally assign itself to DebugRenderer::sInstance. Finally call for example PhysicsSystem::DrawBodies or PhysicsSystem::DrawConstraints to draw the state of the simulation. For an example implementation see [the DebugRenderer from the Samples application](https://github.com/jrouwe/JoltPhysics/blob/master/TestFramework/Renderer/DebugRendererImp.h) or to get started quickly take a look at DebugRendererSimple.
 
 # Memory Management {#memory-management}
 

+ 3 - 0
Jolt/Core/Color.h

@@ -34,6 +34,9 @@ public:
 	inline uint8			operator () (uint inIdx) const											{ JPH_ASSERT(inIdx < 4); return (&r)[inIdx]; }
 	inline uint8 &			operator () (uint inIdx)												{ JPH_ASSERT(inIdx < 4); return (&r)[inIdx]; }
 
+	/// Multiply two colors
+	inline Color			operator * (const Color &inRHS) const									{ return Color(uint8((uint32(r) * inRHS.r) >> 8), uint8((uint32(g) * inRHS.g) >> 8), uint8((uint32(b) * inRHS.b) >> 8), uint8((uint32(a) * inRHS.a) >> 8)); }
+
 	/// Convert to Vec4 with range [0, 1]
 	inline Vec4				ToVec4() const															{ return Vec4(r, g, b, a) / 255.0f; }
 

+ 2 - 0
Jolt/Jolt.cmake

@@ -420,6 +420,8 @@ set(JOLT_PHYSICS_SRC_FILES
 	${JOLT_PHYSICS_ROOT}/Renderer/DebugRendererPlayback.h
 	${JOLT_PHYSICS_ROOT}/Renderer/DebugRendererRecorder.cpp
 	${JOLT_PHYSICS_ROOT}/Renderer/DebugRendererRecorder.h
+	${JOLT_PHYSICS_ROOT}/Renderer/DebugRendererSimple.cpp
+	${JOLT_PHYSICS_ROOT}/Renderer/DebugRendererSimple.h
 	${JOLT_PHYSICS_ROOT}/Skeleton/SkeletalAnimation.cpp
 	${JOLT_PHYSICS_ROOT}/Skeleton/SkeletalAnimation.h
 	${JOLT_PHYSICS_ROOT}/Skeleton/Skeleton.cpp

+ 33 - 1
Jolt/Renderer/DebugRenderer.h

@@ -17,6 +17,7 @@
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Core/HashCombine.h>
 #include <Jolt/Core/UnorderedMap.h>
+#include <Jolt/Core/NonCopyable.h>
 #include <Jolt/Math/Float2.h>
 #include <Jolt/Geometry/IndexedTriangle.h>
 #include <Jolt/Geometry/AABox.h>
@@ -26,7 +27,23 @@ JPH_NAMESPACE_BEGIN
 class OrientedBox;
 
 /// Simple triangle renderer for debugging purposes.
-class JPH_DEBUG_RENDERER_EXPORT DebugRenderer
+///
+/// Inherit from this class to provide your own implementation.
+///
+/// Implement the following virtual functions:
+/// - DrawLine
+/// - DrawTriangle
+/// - DrawText3D
+/// - CreateTriangleBatch
+/// - DrawGeometry
+///
+/// Make sure you call Initialize() from the constructor of your implementation.
+///
+/// The CreateTriangleBatch is used to prepare a batch of triangles to be drawn by a single DrawGeometry call,
+/// which means that Jolt can render a complex scene much more efficiently than when each triangle in that scene would have been drawn through DrawTriangle.
+///
+/// Note that an implementation that implements CreateTriangleBatch and DrawGeometry is provided by DebugRendererSimple which can be used to start quickly.
+class JPH_DEBUG_RENDERER_EXPORT DebugRenderer : public NonCopyable
 {
 public:
 	JPH_OVERRIDE_NEW_DELETE
@@ -192,6 +209,21 @@ public:
 										Geometry(const AABox &inBounds) : mBounds(inBounds) { }
 										Geometry(const Batch &inBatch, const AABox &inBounds) : mBounds(inBounds) { mLODs.push_back({ inBatch, FLT_MAX }); }
 
+		/// Determine which LOD to render
+		/// @param inCameraPosition Current position of the camera
+		/// @param inWorldSpaceBounds World space bounds for this geometry (transform mBounds by model space matrix)
+		/// @param inLODScaleSq is the squared scale of the model matrix, it is multiplied with the LOD distances in inGeometry to calculate the real LOD distance (so a number > 1 will force a higher LOD).
+		/// @return The selected LOD.
+		const LOD &						GetLOD(Vec3Arg inCameraPosition, const AABox &inWorldSpaceBounds, float inLODScaleSq) const
+		{
+			float dist_sq = inWorldSpaceBounds.GetSqDistanceTo(inCameraPosition);
+			for (const LOD &lod : mLODs)
+				if (dist_sq <= inLODScaleSq * Square(lod.mDistance))
+					return lod;
+
+			return mLODs.back();
+		}
+
 		/// All level of details for this mesh
 		Array<LOD>						mLODs;
 

+ 80 - 0
Jolt/Renderer/DebugRendererSimple.cpp

@@ -0,0 +1,80 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#include <Jolt/Jolt.h>
+
+#ifdef JPH_DEBUG_RENDERER
+
+#include <Jolt/Renderer/DebugRendererSimple.h>
+
+JPH_NAMESPACE_BEGIN
+
+DebugRendererSimple::DebugRendererSimple()
+{
+	Initialize();
+}
+
+DebugRenderer::Batch DebugRendererSimple::CreateTriangleBatch(const Triangle *inTriangles, int inTriangleCount)
+{
+	BatchImpl *batch = new BatchImpl;
+	if (inTriangles == nullptr || inTriangleCount == 0)
+		return batch;
+
+	batch->mTriangles.assign(inTriangles, inTriangles + inTriangleCount);
+	return batch;
+}
+
+DebugRenderer::Batch DebugRendererSimple::CreateTriangleBatch(const Vertex *inVertices, int inVertexCount, const uint32 *inIndices, int inIndexCount)
+{
+	BatchImpl *batch = new BatchImpl;
+	if (inVertices == nullptr || inVertexCount == 0 || inIndices == nullptr || inIndexCount == 0)
+		return batch;
+
+	// Convert indexed triangle list to triangle list
+	batch->mTriangles.resize(inIndexCount / 3);
+	for (size_t t = 0; t < batch->mTriangles.size(); ++t)
+	{
+		Triangle &triangle = batch->mTriangles[t];
+		triangle.mV[0] = inVertices[inIndices[t * 3 + 0]];
+		triangle.mV[1] = inVertices[inIndices[t * 3 + 1]];
+		triangle.mV[2] = inVertices[inIndices[t * 3 + 2]];
+	}
+
+	return batch;
+}
+
+void DebugRendererSimple::DrawGeometry(RMat44Arg inModelMatrix, const AABox &inWorldSpaceBounds, float inLODScaleSq, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode)
+{
+	// Figure out which LOD to use
+	const LOD *lod = inGeometry->mLODs.data();
+	if (mCameraPosSet)
+		lod = &inGeometry->GetLOD(Vec3(mCameraPos), inWorldSpaceBounds, inLODScaleSq);
+
+	// Draw the batch
+	const BatchImpl *batch = static_cast<const BatchImpl *>(lod->mTriangleBatch.GetPtr());
+	for (const Triangle &triangle : batch->mTriangles)
+	{
+		RVec3 v0 = inModelMatrix * Vec3(triangle.mV[0].mPosition);
+		RVec3 v1 = inModelMatrix * Vec3(triangle.mV[1].mPosition);
+		RVec3 v2 = inModelMatrix * Vec3(triangle.mV[2].mPosition);
+		Color color = inModelColor * triangle.mV[0].mColor;
+
+		switch (inDrawMode)
+		{
+		case EDrawMode::Wireframe:
+			DrawLine(v0, v1, color);
+			DrawLine(v1, v2, color);
+			DrawLine(v2, v0, color);
+			break;
+
+		case EDrawMode::Solid:
+			DrawTriangle(v0, v1, v2, color, inCastShadow);
+			break;
+		}
+	}
+}
+
+JPH_NAMESPACE_END
+
+#endif // JPH_DEBUG_RENDERER

+ 80 - 0
Jolt/Renderer/DebugRendererSimple.h

@@ -0,0 +1,80 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#ifndef JPH_DEBUG_RENDERER
+	#error This file should only be included when JPH_DEBUG_RENDERER is defined
+#endif // !JPH_DEBUG_RENDERER
+
+#include <Jolt/Renderer/DebugRenderer.h>
+
+JPH_NAMESPACE_BEGIN
+
+/// Inherit from this class to simplify implementing a debug renderer, start with this implementation:
+///
+///		class MyDebugRenderer : public JPH::DebugRendererSimple
+///		{
+///		public:
+///			virtual void DrawLine(JPH::RVec3Arg inFrom, JPH::RVec3Arg inTo, JPH::ColorArg inColor) override
+///			{
+///				// Implement
+///			}
+///
+///			virtual void DrawTriangle(JPH::RVec3Arg inV1, JPH::RVec3Arg inV2, JPH::RVec3Arg inV3, JPH::ColorArg inColor, JPH::ECastShadow inCastShadow) override
+///			{
+///				// Implement
+///			}
+///
+///			virtual void DrawText3D(JPH::RVec3Arg inPosition, const string_view &inString, JPH::ColorArg inColor, float inHeight) override
+///			{
+///				// Implement
+///			}
+///		};
+///
+/// Note that this class is meant to be a quick start for implementing a debug renderer, it is not the most efficient way to implement a debug renderer.
+class JPH_DEBUG_RENDERER_EXPORT DebugRendererSimple : public DebugRenderer
+{
+public:
+	JPH_OVERRIDE_NEW_DELETE
+
+	/// Constructor
+								DebugRendererSimple();
+
+	/// Should be called every frame by the application to provide the camera position.
+	/// This is used to determine the correct LOD for rendering.
+	void						SetCameraPos(RVec3Arg inCameraPos)
+	{
+		mCameraPos = inCameraPos;
+		mCameraPosSet = true;
+	}
+
+protected:
+	/// Implementation of DebugRenderer interface
+	virtual Batch				CreateTriangleBatch(const Triangle *inTriangles, int inTriangleCount) override;
+	virtual Batch				CreateTriangleBatch(const Vertex *inVertices, int inVertexCount, const uint32 *inIndices, int inIndexCount) override;
+	virtual void				DrawGeometry(RMat44Arg inModelMatrix, const AABox &inWorldSpaceBounds, float inLODScaleSq, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode) override;
+
+private:
+	/// Implementation specific batch object
+	class BatchImpl : public RefTargetVirtual
+	{
+	public:
+		JPH_OVERRIDE_NEW_DELETE
+
+		virtual void			AddRef() override			{ ++mRefCount; }
+		virtual void			Release() override			{ if (--mRefCount == 0) delete this; }
+
+		Array<Triangle>			mTriangles;
+
+	private:
+		atomic<uint32>			mRefCount = 0;
+	};
+
+	/// Last provided camera position
+	RVec3						mCameraPos;
+	bool						mCameraPosSet = false;
+};
+
+JPH_NAMESPACE_END

+ 8 - 11
TestFramework/Renderer/DebugRendererImp.cpp

@@ -334,17 +334,14 @@ void DebugRendererImp::DrawTriangles()
 					if (light_overlaps || camera_overlaps)
 					{
 						// Figure out which LOD to use
-						float dist_sq = src_instance.mWorldSpaceBounds.GetSqDistanceTo(camera_pos);
-						for (size_t lod = 0; lod < num_lods; ++lod)
-							if (dist_sq <= src_instance.mLODScaleSq * Square(geometry_lods[lod].mDistance))
-							{
-								// Store which index goes in which LOD
-								if (light_overlaps)
-									lod_indices[0][lod].push_back((int)i);
-								if (camera_overlaps)
-									lod_indices[1][lod].push_back((int)i);
-								break;
-							}
+						const LOD &lod = v.first->GetLOD(camera_pos, src_instance.mWorldSpaceBounds, src_instance.mLODScaleSq);
+						size_t lod_index = &lod - geometry_lods.data();
+
+						// Store which index goes in which LOD
+						if (light_overlaps)
+							lod_indices[0][lod_index].push_back((int)i);
+						if (camera_overlaps)
+							lod_indices[1][lod_index].push_back((int)i);
 					}
 				}