initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,383 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 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
#ifndef JPH_DEBUG_RENDERER_EXPORT
// By default export the debug renderer
#define JPH_DEBUG_RENDERER_EXPORT JPH_EXPORT
#endif // !JPH_DEBUG_RENDERER_EXPORT
#include <Jolt/Core/Color.h>
#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>
JPH_NAMESPACE_BEGIN
class OrientedBox;
/// Simple triangle renderer for debugging purposes.
///
/// 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
/// Constructor
DebugRenderer();
virtual ~DebugRenderer();
/// Call once after frame is complete. Releases unused dynamically generated geometry assets.
void NextFrame();
/// Draw line
virtual void DrawLine(RVec3Arg inFrom, RVec3Arg inTo, ColorArg inColor) = 0;
/// Draw wireframe box
void DrawWireBox(const AABox &inBox, ColorArg inColor);
void DrawWireBox(const OrientedBox &inBox, ColorArg inColor);
void DrawWireBox(RMat44Arg inMatrix, const AABox &inBox, ColorArg inColor);
/// Draw a marker on a position
void DrawMarker(RVec3Arg inPosition, ColorArg inColor, float inSize);
/// Draw an arrow
void DrawArrow(RVec3Arg inFrom, RVec3Arg inTo, ColorArg inColor, float inSize);
/// Draw coordinate system (3 arrows, x = red, y = green, z = blue)
void DrawCoordinateSystem(RMat44Arg inTransform, float inSize = 1.0f);
/// Draw a plane through inPoint with normal inNormal
void DrawPlane(RVec3Arg inPoint, Vec3Arg inNormal, ColorArg inColor, float inSize);
/// Draw wireframe triangle
void DrawWireTriangle(RVec3Arg inV1, RVec3Arg inV2, RVec3Arg inV3, ColorArg inColor);
/// Draw a wireframe polygon
template <class VERTEX_ARRAY>
void DrawWirePolygon(RMat44Arg inTransform, const VERTEX_ARRAY &inVertices, ColorArg inColor, float inArrowSize = 0.0f) { for (typename VERTEX_ARRAY::size_type i = 0; i < inVertices.size(); ++i) DrawArrow(inTransform * inVertices[i], inTransform * inVertices[(i + 1) % inVertices.size()], inColor, inArrowSize); }
/// Draw wireframe sphere
void DrawWireSphere(RVec3Arg inCenter, float inRadius, ColorArg inColor, int inLevel = 3);
void DrawWireUnitSphere(RMat44Arg inMatrix, ColorArg inColor, int inLevel = 3);
/// Enum that determines if a shadow should be cast or not
enum class ECastShadow
{
On, ///< This shape should cast a shadow
Off ///< This shape should not cast a shadow
};
/// Determines how triangles are drawn
enum class EDrawMode
{
Solid, ///< Draw as a solid shape
Wireframe, ///< Draw as wireframe
};
/// Draw a single back face culled triangle
virtual void DrawTriangle(RVec3Arg inV1, RVec3Arg inV2, RVec3Arg inV3, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::Off) = 0;
/// Draw a box
void DrawBox(const AABox &inBox, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
void DrawBox(RMat44Arg inMatrix, const AABox &inBox, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
/// Draw a sphere
void DrawSphere(RVec3Arg inCenter, float inRadius, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
void DrawUnitSphere(RMat44Arg inMatrix, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
/// Draw a capsule with one half sphere at (0, -inHalfHeightOfCylinder, 0) and the other half sphere at (0, inHalfHeightOfCylinder, 0) and radius inRadius.
/// The capsule will be transformed by inMatrix.
void DrawCapsule(RMat44Arg inMatrix, float inHalfHeightOfCylinder, float inRadius, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
/// Draw a cylinder with top (0, inHalfHeight, 0) and bottom (0, -inHalfHeight, 0) and radius inRadius.
/// The cylinder will be transformed by inMatrix
void DrawCylinder(RMat44Arg inMatrix, float inHalfHeight, float inRadius, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
/// Draw a bottomless cone.
/// @param inTop Top of cone, center of base is at inTop + inAxis.
/// @param inAxis Height and direction of cone
/// @param inPerpendicular Perpendicular vector to inAxis.
/// @param inHalfAngle Specifies the cone angle in radians (angle measured between inAxis and cone surface).
/// @param inLength The length of the cone.
/// @param inColor Color to use for drawing the cone.
/// @param inCastShadow determines if this geometry should cast a shadow or not.
/// @param inDrawMode determines if we draw the geometry solid or in wireframe.
void DrawOpenCone(RVec3Arg inTop, Vec3Arg inAxis, Vec3Arg inPerpendicular, float inHalfAngle, float inLength, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
/// Draws cone rotation limits as used by the SwingTwistConstraintPart.
/// @param inMatrix Matrix that transforms from constraint space to world space
/// @param inSwingYHalfAngle See SwingTwistConstraintPart
/// @param inSwingZHalfAngle See SwingTwistConstraintPart
/// @param inEdgeLength Size of the edge of the cone shape
/// @param inColor Color to use for drawing the cone.
/// @param inCastShadow determines if this geometry should cast a shadow or not.
/// @param inDrawMode determines if we draw the geometry solid or in wireframe.
void DrawSwingConeLimits(RMat44Arg inMatrix, float inSwingYHalfAngle, float inSwingZHalfAngle, float inEdgeLength, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
/// Draws rotation limits as used by the SwingTwistConstraintPart.
/// @param inMatrix Matrix that transforms from constraint space to world space
/// @param inMinSwingYAngle See SwingTwistConstraintPart
/// @param inMaxSwingYAngle See SwingTwistConstraintPart
/// @param inMinSwingZAngle See SwingTwistConstraintPart
/// @param inMaxSwingZAngle See SwingTwistConstraintPart
/// @param inEdgeLength Size of the edge of the cone shape
/// @param inColor Color to use for drawing the cone.
/// @param inCastShadow determines if this geometry should cast a shadow or not.
/// @param inDrawMode determines if we draw the geometry solid or in wireframe.
void DrawSwingPyramidLimits(RMat44Arg inMatrix, float inMinSwingYAngle, float inMaxSwingYAngle, float inMinSwingZAngle, float inMaxSwingZAngle, float inEdgeLength, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
/// Draw a pie (part of a circle).
/// @param inCenter The center of the circle.
/// @param inRadius Radius of the circle.
/// @param inNormal The plane normal in which the pie resides.
/// @param inAxis The axis that defines an angle of 0 radians.
/// @param inMinAngle The pie will be drawn between [inMinAngle, inMaxAngle] (in radians).
/// @param inMaxAngle The pie will be drawn between [inMinAngle, inMaxAngle] (in radians).
/// @param inColor Color to use for drawing the pie.
/// @param inCastShadow determines if this geometry should cast a shadow or not.
/// @param inDrawMode determines if we draw the geometry solid or in wireframe.
void DrawPie(RVec3Arg inCenter, float inRadius, Vec3Arg inNormal, Vec3Arg inAxis, float inMinAngle, float inMaxAngle, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
/// Draw a tapered cylinder
/// @param inMatrix Matrix that transforms the cylinder to world space.
/// @param inTop Top of cylinder (along Y axis)
/// @param inBottom Bottom of cylinder (along Y axis)
/// @param inTopRadius Radius at the top
/// @param inBottomRadius Radius at the bottom
/// @param inColor Color to use for drawing the pie.
/// @param inCastShadow determines if this geometry should cast a shadow or not.
/// @param inDrawMode determines if we draw the geometry solid or in wireframe.
void DrawTaperedCylinder(RMat44Arg inMatrix, float inTop, float inBottom, float inTopRadius, float inBottomRadius, ColorArg inColor, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid);
/// Singleton instance
static DebugRenderer * sInstance;
/// Vertex format used by the triangle renderer
class Vertex
{
public:
Float3 mPosition;
Float3 mNormal;
Float2 mUV;
Color mColor;
};
/// A single triangle
class JPH_DEBUG_RENDERER_EXPORT Triangle
{
public:
Triangle() = default;
Triangle(Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3, ColorArg inColor);
Triangle(Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3, ColorArg inColor, Vec3Arg inUVOrigin, Vec3Arg inUVDirection);
Vertex mV[3];
};
/// Handle for a batch of triangles
using Batch = Ref<RefTargetVirtual>;
/// A single level of detail
class LOD
{
public:
Batch mTriangleBatch;
float mDistance;
};
/// A geometry primitive containing triangle batches for various lods
class Geometry : public RefTarget<Geometry>
{
public:
JPH_OVERRIDE_NEW_DELETE
/// Constructor
Geometry(const AABox &inBounds) : mBounds(inBounds) { }
Geometry(const Batch &inBatch, const AABox &inBounds) : mBounds(inBounds) { mLODs.push_back({ inBatch, cLargeFloat }); }
/// 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;
/// Bounding box that encapsulates all LODs
AABox mBounds;
};
/// Handle for a lodded triangle batch
using GeometryRef = Ref<Geometry>;
/// Calculate bounding box for a batch of triangles
static AABox sCalculateBounds(const Vertex *inVertices, int inVertexCount);
/// Create a batch of triangles that can be drawn efficiently
virtual Batch CreateTriangleBatch(const Triangle *inTriangles, int inTriangleCount) = 0;
virtual Batch CreateTriangleBatch(const Vertex *inVertices, int inVertexCount, const uint32 *inIndices, int inIndexCount) = 0;
Batch CreateTriangleBatch(const Array<Triangle> &inTriangles) { return CreateTriangleBatch(inTriangles.empty()? nullptr : &inTriangles[0], int(inTriangles.size())); }
Batch CreateTriangleBatch(const Array<Vertex> &inVertices, const Array<uint32> &inIndices) { return CreateTriangleBatch(inVertices.empty()? nullptr : &inVertices[0], int(inVertices.size()), inIndices.empty()? nullptr : &inIndices[0], int(inIndices.size())); }
Batch CreateTriangleBatch(const VertexList &inVertices, const IndexedTriangleNoMaterialList &inTriangles);
/// Create a primitive for a convex shape using its support function
using SupportFunction = function<Vec3 (Vec3Arg inDirection)>;
Batch CreateTriangleBatchForConvex(SupportFunction inGetSupport, int inLevel, AABox *outBounds = nullptr);
GeometryRef CreateTriangleGeometryForConvex(SupportFunction inGetSupport);
/// Determines which polygons are culled
enum class ECullMode
{
CullBackFace, ///< Don't draw backfacing polygons
CullFrontFace, ///< Don't draw front facing polygons
Off ///< Don't do culling and draw both sides
};
/// Draw some geometry
/// @param inModelMatrix is the matrix that transforms the geometry to world space.
/// @param inWorldSpaceBounds is the bounding box of the geometry after transforming it into world space.
/// @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).
/// @param inModelColor is the color with which to multiply the vertex colors in inGeometry.
/// @param inGeometry The geometry to draw.
/// @param inCullMode determines which polygons are culled.
/// @param inCastShadow determines if this geometry should cast a shadow or not.
/// @param inDrawMode determines if we draw the geometry solid or in wireframe.
virtual void DrawGeometry(RMat44Arg inModelMatrix, const AABox &inWorldSpaceBounds, float inLODScaleSq, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode = ECullMode::CullBackFace, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid) = 0;
void DrawGeometry(RMat44Arg inModelMatrix, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode = ECullMode::CullBackFace, ECastShadow inCastShadow = ECastShadow::On, EDrawMode inDrawMode = EDrawMode::Solid) { DrawGeometry(inModelMatrix, inGeometry->mBounds.Transformed(inModelMatrix), max(max(inModelMatrix.GetAxisX().LengthSq(), inModelMatrix.GetAxisY().LengthSq()), inModelMatrix.GetAxisZ().LengthSq()), inModelColor, inGeometry, inCullMode, inCastShadow, inDrawMode); }
/// Draw text
virtual void DrawText3D(RVec3Arg inPosition, const string_view &inString, ColorArg inColor = Color::sWhite, float inHeight = 0.5f) = 0;
protected:
/// Initialize the system, must be called from the constructor of the DebugRenderer implementation
void Initialize();
private:
/// Recursive helper function for DrawWireUnitSphere
void DrawWireUnitSphereRecursive(RMat44Arg inMatrix, ColorArg inColor, Vec3Arg inDir1, Vec3Arg inDir2, Vec3Arg inDir3, int inLevel);
/// Helper functions to create a box
void CreateQuad(Array<uint32> &ioIndices, Array<Vertex> &ioVertices, Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3, Vec3Arg inV4);
/// Helper functions to create a vertex and index buffer for a sphere
void Create8thSphereRecursive(Array<uint32> &ioIndices, Array<Vertex> &ioVertices, Vec3Arg inDir1, uint32 &ioIdx1, Vec3Arg inDir2, uint32 &ioIdx2, Vec3Arg inDir3, uint32 &ioIdx3, const Float2 &inUV, SupportFunction inGetSupport, int inLevel);
void Create8thSphere(Array<uint32> &ioIndices, Array<Vertex> &ioVertices, Vec3Arg inDir1, Vec3Arg inDir2, Vec3Arg inDir3, const Float2 &inUV, SupportFunction inGetSupport, int inLevel);
/// Helper functions to create a vertex and index buffer for a cylinder
Batch CreateCylinder(float inTop, float inBottom, float inTopRadius, float inBottomRadius, int inLevel);
/// Helper function for DrawSwingConeLimits and DrawSwingPyramidLimits
Geometry * CreateSwingLimitGeometry(int inNumSegments, const Vec3 *inVertices);
// Predefined shapes
GeometryRef mBox;
GeometryRef mSphere;
GeometryRef mCapsuleTop;
GeometryRef mCapsuleMid;
GeometryRef mCapsuleBottom;
GeometryRef mOpenCone;
GeometryRef mCylinder;
struct SwingConeLimits
{
bool operator == (const SwingConeLimits &inRHS) const
{
return mSwingYHalfAngle == inRHS.mSwingYHalfAngle
&& mSwingZHalfAngle == inRHS.mSwingZHalfAngle;
}
float mSwingYHalfAngle;
float mSwingZHalfAngle;
};
JPH_MAKE_HASH_STRUCT(SwingConeLimits, SwingConeLimitsHasher, t.mSwingYHalfAngle, t.mSwingZHalfAngle)
using SwingConeBatches = UnorderedMap<SwingConeLimits, GeometryRef, SwingConeLimitsHasher>;
SwingConeBatches mSwingConeLimits;
SwingConeBatches mPrevSwingConeLimits;
struct SwingPyramidLimits
{
bool operator == (const SwingPyramidLimits &inRHS) const
{
return mMinSwingYAngle == inRHS.mMinSwingYAngle
&& mMaxSwingYAngle == inRHS.mMaxSwingYAngle
&& mMinSwingZAngle == inRHS.mMinSwingZAngle
&& mMaxSwingZAngle == inRHS.mMaxSwingZAngle;
}
float mMinSwingYAngle;
float mMaxSwingYAngle;
float mMinSwingZAngle;
float mMaxSwingZAngle;
};
JPH_MAKE_HASH_STRUCT(SwingPyramidLimits, SwingPyramidLimitsHasher, t.mMinSwingYAngle, t.mMaxSwingYAngle, t.mMinSwingZAngle, t.mMaxSwingZAngle)
using SwingPyramidBatches = UnorderedMap<SwingPyramidLimits, GeometryRef, SwingPyramidLimitsHasher>;
SwingPyramidBatches mSwingPyramidLimits;
SwingPyramidBatches mPrevSwingPyramidLimits;
using PieBatces = UnorderedMap<float, GeometryRef>;
PieBatces mPieLimits;
PieBatces mPrevPieLimits;
struct TaperedCylinder
{
bool operator == (const TaperedCylinder &inRHS) const
{
return mTop == inRHS.mTop
&& mBottom == inRHS.mBottom
&& mTopRadius == inRHS.mTopRadius
&& mBottomRadius == inRHS.mBottomRadius;
}
float mTop;
float mBottom;
float mTopRadius;
float mBottomRadius;
};
JPH_MAKE_HASH_STRUCT(TaperedCylinder, TaperedCylinderHasher, t.mTop, t.mBottom, t.mTopRadius, t.mBottomRadius)
using TaperedCylinderBatces = UnorderedMap<TaperedCylinder, GeometryRef, TaperedCylinderHasher>;
TaperedCylinderBatces mTaperedCylinders;
TaperedCylinderBatces mPrevTaperedCylinders;
};
JPH_NAMESPACE_END

View File

@@ -0,0 +1,168 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <Jolt/Jolt.h>
#ifdef JPH_DEBUG_RENDERER
#include <Jolt/Renderer/DebugRendererPlayback.h>
JPH_NAMESPACE_BEGIN
void DebugRendererPlayback::Parse(StreamIn &inStream)
{
using ECommand = DebugRendererRecorder::ECommand;
for (;;)
{
// Read the next command
ECommand command;
inStream.Read(command);
if (inStream.IsEOF() || inStream.IsFailed())
return;
if (command == ECommand::CreateBatch)
{
uint32 id;
inStream.Read(id);
uint32 triangle_count;
inStream.Read(triangle_count);
DebugRenderer::Triangle *triangles = new DebugRenderer::Triangle [triangle_count];
inStream.ReadBytes(triangles, triangle_count * sizeof(DebugRenderer::Triangle));
mBatches.insert({ id, mRenderer.CreateTriangleBatch(triangles, triangle_count) });
delete [] triangles;
}
else if (command == ECommand::CreateBatchIndexed)
{
uint32 id;
inStream.Read(id);
uint32 vertex_count;
inStream.Read(vertex_count);
DebugRenderer::Vertex *vertices = new DebugRenderer::Vertex [vertex_count];
inStream.ReadBytes(vertices, vertex_count * sizeof(DebugRenderer::Vertex));
uint32 index_count;
inStream.Read(index_count);
uint32 *indices = new uint32 [index_count];
inStream.ReadBytes(indices, index_count * sizeof(uint32));
mBatches.insert({ id, mRenderer.CreateTriangleBatch(vertices, vertex_count, indices, index_count) });
delete [] indices;
delete [] vertices;
}
else if (command == ECommand::CreateGeometry)
{
uint32 geometry_id;
inStream.Read(geometry_id);
AABox bounds;
inStream.Read(bounds.mMin);
inStream.Read(bounds.mMax);
DebugRenderer::GeometryRef geometry = new DebugRenderer::Geometry(bounds);
mGeometries[geometry_id] = geometry;
uint32 num_lods;
inStream.Read(num_lods);
for (uint32 l = 0; l < num_lods; ++l)
{
DebugRenderer::LOD lod;
inStream.Read(lod.mDistance);
uint32 batch_id;
inStream.Read(batch_id);
lod.mTriangleBatch = mBatches.find(batch_id)->second;
geometry->mLODs.push_back(lod);
}
}
else if (command == ECommand::EndFrame)
{
mFrames.push_back({});
Frame &frame = mFrames.back();
// Read all lines
uint32 num_lines = 0;
inStream.Read(num_lines);
frame.mLines.resize(num_lines);
for (DebugRendererRecorder::LineBlob &line : frame.mLines)
{
inStream.Read(line.mFrom);
inStream.Read(line.mTo);
inStream.Read(line.mColor);
}
// Read all triangles
uint32 num_triangles = 0;
inStream.Read(num_triangles);
frame.mTriangles.resize(num_triangles);
for (DebugRendererRecorder::TriangleBlob &triangle : frame.mTriangles)
{
inStream.Read(triangle.mV1);
inStream.Read(triangle.mV2);
inStream.Read(triangle.mV3);
inStream.Read(triangle.mColor);
inStream.Read(triangle.mCastShadow);
}
// Read all texts
uint32 num_texts = 0;
inStream.Read(num_texts);
frame.mTexts.resize(num_texts);
for (DebugRendererRecorder::TextBlob &text : frame.mTexts)
{
inStream.Read(text.mPosition);
inStream.Read(text.mString);
inStream.Read(text.mColor);
inStream.Read(text.mHeight);
}
// Read all geometries
uint32 num_geometries = 0;
inStream.Read(num_geometries);
frame.mGeometries.resize(num_geometries);
for (DebugRendererRecorder::GeometryBlob &geom : frame.mGeometries)
{
inStream.Read(geom.mModelMatrix);
inStream.Read(geom.mModelColor);
inStream.Read(geom.mGeometryID);
inStream.Read(geom.mCullMode);
inStream.Read(geom.mCastShadow);
inStream.Read(geom.mDrawMode);
}
}
else
JPH_ASSERT(false);
}
}
void DebugRendererPlayback::DrawFrame(uint inFrameNumber) const
{
const Frame &frame = mFrames[inFrameNumber];
for (const DebugRendererRecorder::LineBlob &line : frame.mLines)
mRenderer.DrawLine(line.mFrom, line.mTo, line.mColor);
for (const DebugRendererRecorder::TriangleBlob &triangle : frame.mTriangles)
mRenderer.DrawTriangle(triangle.mV1, triangle.mV2, triangle.mV3, triangle.mColor, triangle.mCastShadow);
for (const DebugRendererRecorder::TextBlob &text : frame.mTexts)
mRenderer.DrawText3D(text.mPosition, text.mString, text.mColor, text.mHeight);
for (const DebugRendererRecorder::GeometryBlob &geom : frame.mGeometries)
mRenderer.DrawGeometry(geom.mModelMatrix, geom.mModelColor, mGeometries.find(geom.mGeometryID)->second, geom.mCullMode, geom.mCastShadow, geom.mDrawMode);
}
JPH_NAMESPACE_END
#endif // JPH_DEBUG_RENDERER

View File

@@ -0,0 +1,48 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 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/DebugRendererRecorder.h>
#include <Jolt/Core/StreamIn.h>
#include <Jolt/Core/UnorderedMap.h>
JPH_NAMESPACE_BEGIN
/// Class that can read a recorded stream from DebugRendererRecorder and plays it back trough a DebugRenderer
class JPH_DEBUG_RENDERER_EXPORT DebugRendererPlayback
{
public:
/// Constructor
DebugRendererPlayback(DebugRenderer &inRenderer) : mRenderer(inRenderer) { }
/// Parse a stream of frames
void Parse(StreamIn &inStream);
/// Get the number of parsed frames
uint GetNumFrames() const { return (uint)mFrames.size(); }
/// Draw a frame
void DrawFrame(uint inFrameNumber) const;
private:
/// The debug renderer we're using to do the actual rendering
DebugRenderer & mRenderer;
/// Mapping of ID to batch
UnorderedMap<uint32, DebugRenderer::Batch> mBatches;
/// Mapping of ID to geometry
UnorderedMap<uint32, DebugRenderer::GeometryRef> mGeometries;
/// The list of parsed frames
using Frame = DebugRendererRecorder::Frame;
Array<Frame> mFrames;
};
JPH_NAMESPACE_END

View File

@@ -0,0 +1,158 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <Jolt/Jolt.h>
#ifdef JPH_DEBUG_RENDERER
#include <Jolt/Renderer/DebugRendererRecorder.h>
JPH_NAMESPACE_BEGIN
void DebugRendererRecorder::DrawLine(RVec3Arg inFrom, RVec3Arg inTo, ColorArg inColor)
{
lock_guard lock(mMutex);
mCurrentFrame.mLines.push_back({ inFrom, inTo, inColor });
}
void DebugRendererRecorder::DrawTriangle(RVec3Arg inV1, RVec3Arg inV2, RVec3Arg inV3, ColorArg inColor, ECastShadow inCastShadow)
{
lock_guard lock(mMutex);
mCurrentFrame.mTriangles.push_back({ inV1, inV2, inV3, inColor, inCastShadow });
}
DebugRenderer::Batch DebugRendererRecorder::CreateTriangleBatch(const Triangle *inTriangles, int inTriangleCount)
{
if (inTriangles == nullptr || inTriangleCount == 0)
return new BatchImpl(0);
lock_guard lock(mMutex);
mStream.Write(ECommand::CreateBatch);
uint32 batch_id = mNextBatchID++;
JPH_ASSERT(batch_id != 0);
mStream.Write(batch_id);
mStream.Write((uint32)inTriangleCount);
mStream.WriteBytes(inTriangles, inTriangleCount * sizeof(Triangle));
return new BatchImpl(batch_id);
}
DebugRenderer::Batch DebugRendererRecorder::CreateTriangleBatch(const Vertex *inVertices, int inVertexCount, const uint32 *inIndices, int inIndexCount)
{
if (inVertices == nullptr || inVertexCount == 0 || inIndices == nullptr || inIndexCount == 0)
return new BatchImpl(0);
lock_guard lock(mMutex);
mStream.Write(ECommand::CreateBatchIndexed);
uint32 batch_id = mNextBatchID++;
JPH_ASSERT(batch_id != 0);
mStream.Write(batch_id);
mStream.Write((uint32)inVertexCount);
mStream.WriteBytes(inVertices, inVertexCount * sizeof(Vertex));
mStream.Write((uint32)inIndexCount);
mStream.WriteBytes(inIndices, inIndexCount * sizeof(uint32));
return new BatchImpl(batch_id);
}
void DebugRendererRecorder::DrawGeometry(RMat44Arg inModelMatrix, const AABox &inWorldSpaceBounds, float inLODScaleSq, ColorArg inModelColor, const GeometryRef &inGeometry, ECullMode inCullMode, ECastShadow inCastShadow, EDrawMode inDrawMode)
{
lock_guard lock(mMutex);
// See if this geometry was used before
uint32 &geometry_id = mGeometries[inGeometry];
if (geometry_id == 0)
{
mStream.Write(ECommand::CreateGeometry);
// Create a new ID
geometry_id = mNextGeometryID++;
JPH_ASSERT(geometry_id != 0);
mStream.Write(geometry_id);
// Save bounds
mStream.Write(inGeometry->mBounds.mMin);
mStream.Write(inGeometry->mBounds.mMax);
// Save the LODs
mStream.Write((uint32)inGeometry->mLODs.size());
for (const LOD & lod : inGeometry->mLODs)
{
mStream.Write(lod.mDistance);
mStream.Write(static_cast<const BatchImpl *>(lod.mTriangleBatch.GetPtr())->mID);
}
}
mCurrentFrame.mGeometries.push_back({ inModelMatrix, inModelColor, geometry_id, inCullMode, inCastShadow, inDrawMode });
}
void DebugRendererRecorder::DrawText3D(RVec3Arg inPosition, const string_view &inString, ColorArg inColor, float inHeight)
{
lock_guard lock(mMutex);
mCurrentFrame.mTexts.push_back({ inPosition, inString, inColor, inHeight });
}
void DebugRendererRecorder::EndFrame()
{
lock_guard lock(mMutex);
mStream.Write(ECommand::EndFrame);
// Write all lines
mStream.Write((uint32)mCurrentFrame.mLines.size());
for (const LineBlob &line : mCurrentFrame.mLines)
{
mStream.Write(line.mFrom);
mStream.Write(line.mTo);
mStream.Write(line.mColor);
}
mCurrentFrame.mLines.clear();
// Write all triangles
mStream.Write((uint32)mCurrentFrame.mTriangles.size());
for (const TriangleBlob &triangle : mCurrentFrame.mTriangles)
{
mStream.Write(triangle.mV1);
mStream.Write(triangle.mV2);
mStream.Write(triangle.mV3);
mStream.Write(triangle.mColor);
mStream.Write(triangle.mCastShadow);
}
mCurrentFrame.mTriangles.clear();
// Write all texts
mStream.Write((uint32)mCurrentFrame.mTexts.size());
for (const TextBlob &text : mCurrentFrame.mTexts)
{
mStream.Write(text.mPosition);
mStream.Write(text.mString);
mStream.Write(text.mColor);
mStream.Write(text.mHeight);
}
mCurrentFrame.mTexts.clear();
// Write all geometries
mStream.Write((uint32)mCurrentFrame.mGeometries.size());
for (const GeometryBlob &geom : mCurrentFrame.mGeometries)
{
mStream.Write(geom.mModelMatrix);
mStream.Write(geom.mModelColor);
mStream.Write(geom.mGeometryID);
mStream.Write(geom.mCullMode);
mStream.Write(geom.mCastShadow);
mStream.Write(geom.mDrawMode);
}
mCurrentFrame.mGeometries.clear();
}
JPH_NAMESPACE_END
#endif // JPH_DEBUG_RENDERER

View File

@@ -0,0 +1,130 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2021 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>
#include <Jolt/Core/StreamOut.h>
#include <Jolt/Core/Mutex.h>
#include <Jolt/Core/UnorderedMap.h>
JPH_NAMESPACE_BEGIN
/// Implementation of DebugRenderer that records the API invocations to be played back later
class JPH_DEBUG_RENDERER_EXPORT DebugRendererRecorder final : public DebugRenderer
{
public:
JPH_OVERRIDE_NEW_DELETE
/// Constructor
DebugRendererRecorder(StreamOut &inStream) : mStream(inStream) { Initialize(); }
/// Implementation of DebugRenderer interface
virtual void DrawLine(RVec3Arg inFrom, RVec3Arg inTo, ColorArg inColor) override;
virtual void DrawTriangle(RVec3Arg inV1, RVec3Arg inV2, RVec3Arg inV3, ColorArg inColor, ECastShadow inCastShadow) override;
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;
virtual void DrawText3D(RVec3Arg inPosition, const string_view &inString, ColorArg inColor, float inHeight) override;
/// Mark the end of a frame
void EndFrame();
/// Control commands written into the stream
enum class ECommand : uint8
{
CreateBatch,
CreateBatchIndexed,
CreateGeometry,
EndFrame
};
/// Holds a single line segment
struct LineBlob
{
RVec3 mFrom;
RVec3 mTo;
Color mColor;
};
/// Holds a single triangle
struct TriangleBlob
{
RVec3 mV1;
RVec3 mV2;
RVec3 mV3;
Color mColor;
ECastShadow mCastShadow;
};
/// Holds a single text entry
struct TextBlob
{
TextBlob() = default;
TextBlob(RVec3Arg inPosition, const string_view &inString, ColorArg inColor, float inHeight) : mPosition(inPosition), mString(inString), mColor(inColor), mHeight(inHeight) { }
RVec3 mPosition;
String mString;
Color mColor;
float mHeight;
};
/// Holds a single geometry draw call
struct GeometryBlob
{
RMat44 mModelMatrix;
Color mModelColor;
uint32 mGeometryID;
ECullMode mCullMode;
ECastShadow mCastShadow;
EDrawMode mDrawMode;
};
/// All information for a single frame
struct Frame
{
Array<LineBlob> mLines;
Array<TriangleBlob> mTriangles;
Array<TextBlob> mTexts;
Array<GeometryBlob> mGeometries;
};
private:
/// Implementation specific batch object
class BatchImpl : public RefTargetVirtual
{
public:
JPH_OVERRIDE_NEW_DELETE
BatchImpl(uint32 inID) : mID(inID) { }
virtual void AddRef() override { ++mRefCount; }
virtual void Release() override { if (--mRefCount == 0) delete this; }
atomic<uint32> mRefCount = 0;
uint32 mID;
};
/// Lock that prevents concurrent access to the internal structures
Mutex mMutex;
/// Stream that recorded data will be sent to
StreamOut & mStream;
/// Next available ID
uint32 mNextBatchID = 1;
uint32 mNextGeometryID = 1;
/// Cached geometries and their IDs
UnorderedMap<GeometryRef, uint32> mGeometries;
/// Data that is being accumulated for the current frame
Frame mCurrentFrame;
};
JPH_NAMESPACE_END

View File

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

View File

@@ -0,0 +1,88 @@
// 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, 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;
}
/// Fallback implementation that uses DrawLine to draw a triangle (override this if you have a version that renders solid triangles)
virtual void DrawTriangle(RVec3Arg inV1, RVec3Arg inV2, RVec3Arg inV3, ColorArg inColor, ECastShadow inCastShadow) override
{
DrawLine(inV1, inV2, inColor);
DrawLine(inV2, inV3, inColor);
DrawLine(inV3, inV1, inColor);
}
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