Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/HeightFieldShape.h
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#pragma once56#include <Jolt/Physics/Collision/Shape/Shape.h>7#include <Jolt/Physics/Collision/PhysicsMaterial.h>8#ifdef JPH_DEBUG_RENDERER9#include <Jolt/Renderer/DebugRenderer.h>10#endif // JPH_DEBUG_RENDERER1112JPH_NAMESPACE_BEGIN1314class ConvexShape;15class CollideShapeSettings;16class TempAllocator;1718/// Constants for HeightFieldShape, this was moved out of the HeightFieldShape because of a linker bug19namespace HeightFieldShapeConstants20{21/// Value used to create gaps in the height field22constexpr float cNoCollisionValue = FLT_MAX;2324/// Stack size to use during WalkHeightField25constexpr int cStackSize = 128;2627/// A position in the hierarchical grid is defined by a level (which grid), x and y position. We encode this in a single uint32 as: level << 28 | y << 14 | x28constexpr uint cNumBitsXY = 14;29constexpr uint cMaskBitsXY = (1 << cNumBitsXY) - 1;30constexpr uint cLevelShift = 2 * cNumBitsXY;3132/// When height samples are converted to 16 bit:33constexpr uint16 cNoCollisionValue16 = 0xffff; ///< This is the magic value for 'no collision'34constexpr uint16 cMaxHeightValue16 = 0xfffe; ///< This is the maximum allowed height value35};3637/// Class that constructs a HeightFieldShape38class JPH_EXPORT HeightFieldShapeSettings final : public ShapeSettings39{40JPH_DECLARE_SERIALIZABLE_VIRTUAL(JPH_EXPORT, HeightFieldShapeSettings)4142public:43/// Default constructor for deserialization44HeightFieldShapeSettings() = default;4546/// Create a height field shape of inSampleCount * inSampleCount vertices.47/// The height field is a surface defined by: inOffset + inScale * (x, inSamples[y * inSampleCount + x], y).48/// where x and y are integers in the range x and y e [0, inSampleCount - 1].49/// inSampleCount: inSampleCount / mBlockSize must be minimally 2 and a power of 2 is the most efficient in terms of performance and storage.50/// inSamples: inSampleCount^2 vertices.51/// inMaterialIndices: (inSampleCount - 1)^2 indices that index into inMaterialList.52HeightFieldShapeSettings(const float *inSamples, Vec3Arg inOffset, Vec3Arg inScale, uint32 inSampleCount, const uint8 *inMaterialIndices = nullptr, const PhysicsMaterialList &inMaterialList = PhysicsMaterialList());5354// See: ShapeSettings55virtual ShapeResult Create() const override;5657/// Determine the minimal and maximal value of mHeightSamples (will ignore cNoCollisionValue)58/// @param outMinValue The minimal value of mHeightSamples or FLT_MAX if no samples have collision59/// @param outMaxValue The maximal value of mHeightSamples or -FLT_MAX if no samples have collision60/// @param outQuantizationScale (value - outMinValue) * outQuantizationScale quantizes a height sample to 16 bits61void DetermineMinAndMaxSample(float &outMinValue, float &outMaxValue, float &outQuantizationScale) const;6263/// Given mBlockSize, mSampleCount and mHeightSamples, calculate the amount of bits needed to stay below absolute error inMaxError64/// @param inMaxError Maximum allowed error in mHeightSamples after compression (note that this does not take mScale.Y into account)65/// @return Needed bits per sample in the range [1, 8].66uint32 CalculateBitsPerSampleForError(float inMaxError) const;6768/// The height field is a surface defined by: mOffset + mScale * (x, mHeightSamples[y * mSampleCount + x], y).69/// where x and y are integers in the range x and y e [0, mSampleCount - 1].70Vec3 mOffset = Vec3::sZero();71Vec3 mScale = Vec3::sOne();72uint32 mSampleCount = 0;7374/// Artificial minimal value of mHeightSamples, used for compression and can be used to update the terrain after creating with lower height values. If there are any lower values in mHeightSamples, this value will be ignored.75float mMinHeightValue = cLargeFloat;7677/// Artificial maximum value of mHeightSamples, used for compression and can be used to update the terrain after creating with higher height values. If there are any higher values in mHeightSamples, this value will be ignored.78float mMaxHeightValue = -cLargeFloat;7980/// When bigger than mMaterials.size() the internal material list will be preallocated to support this number of materials.81/// This avoids reallocations when calling HeightFieldShape::SetMaterials with new materials later.82uint32 mMaterialsCapacity = 0;8384/// The heightfield is divided in blocks of mBlockSize * mBlockSize * 2 triangles and the acceleration structure culls blocks only,85/// bigger block sizes reduce memory consumption but also reduce query performance. Sensible values are [2, 8], does not need to be86/// a power of 2. Note that at run-time we'll perform one more grid subdivision, so the effective block size is half of what is provided here.87uint32 mBlockSize = 2;8889/// How many bits per sample to use to compress the height field. Can be in the range [1, 8].90/// Note that each sample is compressed relative to the min/max value of its block of mBlockSize * mBlockSize pixels so the effective precision is higher.91/// Also note that increasing mBlockSize saves more memory than reducing the amount of bits per sample.92uint32 mBitsPerSample = 8;9394/// An array of mSampleCount^2 height samples. Samples are stored in row major order, so the sample at (x, y) is at index y * mSampleCount + x.95Array<float> mHeightSamples;9697/// An array of (mSampleCount - 1)^2 material indices.98Array<uint8> mMaterialIndices;99100/// The materials of square at (x, y) is: mMaterials[mMaterialIndices[x + y * (mSampleCount - 1)]]101PhysicsMaterialList mMaterials;102103/// Cosine of the threshold angle (if the angle between the two triangles is bigger than this, the edge is active, note that a concave edge is always inactive).104/// Setting this value too small can cause ghost collisions with edges, setting it too big can cause depenetration artifacts (objects not depenetrating quickly).105/// Valid ranges are between cos(0 degrees) and cos(90 degrees). The default value is cos(5 degrees).106float mActiveEdgeCosThresholdAngle = 0.996195f; // cos(5 degrees)107};108109/// A height field shape. Cannot be used as a dynamic object.110///111/// Note: If you're using HeightFieldShape and are querying data while modifying the shape you'll have a race condition.112/// In this case it is best to create a new HeightFieldShape using the Clone function. You replace the shape on a body using BodyInterface::SetShape.113/// If a query is still working on the old shape, it will have taken a reference and keep the old shape alive until the query finishes.114class JPH_EXPORT HeightFieldShape final : public Shape115{116public:117JPH_OVERRIDE_NEW_DELETE118119/// Constructor120HeightFieldShape() : Shape(EShapeType::HeightField, EShapeSubType::HeightField) { }121HeightFieldShape(const HeightFieldShapeSettings &inSettings, ShapeResult &outResult);122virtual ~HeightFieldShape() override;123124/// Clone this shape. Can be used to avoid race conditions. See the documentation of this class for more information.125Ref<HeightFieldShape> Clone() const;126127// See Shape::MustBeStatic128virtual bool MustBeStatic() const override { return true; }129130/// Get the size of the height field. Note that this will always be rounded up to the nearest multiple of GetBlockSize().131inline uint GetSampleCount() const { return mSampleCount; }132133/// Get the size of a block134inline uint GetBlockSize() const { return mBlockSize; }135136// See Shape::GetLocalBounds137virtual AABox GetLocalBounds() const override;138139// See Shape::GetSubShapeIDBitsRecursive140virtual uint GetSubShapeIDBitsRecursive() const override { return GetSubShapeIDBits(); }141142// See Shape::GetInnerRadius143virtual float GetInnerRadius() const override { return 0.0f; }144145// See Shape::GetMassProperties146virtual MassProperties GetMassProperties() const override;147148// See Shape::GetMaterial149virtual const PhysicsMaterial * GetMaterial(const SubShapeID &inSubShapeID) const override;150151/// Overload to get the material at a particular location152const PhysicsMaterial * GetMaterial(uint inX, uint inY) const;153154// See Shape::GetSurfaceNormal155virtual Vec3 GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const override;156157// See Shape::GetSupportingFace158virtual void GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const override;159160// See Shape::GetSubmergedVolume161virtual void GetSubmergedVolume(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const Plane &inSurface, float &outTotalVolume, float &outSubmergedVolume, Vec3 &outCenterOfBuoyancy JPH_IF_DEBUG_RENDERER(, RVec3Arg inBaseOffset)) const override { JPH_ASSERT(false, "Not supported"); }162163#ifdef JPH_DEBUG_RENDERER164// See Shape::Draw165virtual void Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const override;166#endif // JPH_DEBUG_RENDERER167168// See Shape::CastRay169virtual bool CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const override;170virtual void CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;171172// See: Shape::CollidePoint173virtual void CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;174175// See: Shape::CollideSoftBodyVertices176virtual void CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const override;177178// See Shape::GetTrianglesStart179virtual void GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;180181// See Shape::GetTrianglesNext182virtual int GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials = nullptr) const override;183184/// Get height field position at sampled location (inX, inY).185/// where inX and inY are integers in the range inX e [0, mSampleCount - 1] and inY e [0, mSampleCount - 1].186Vec3 GetPosition(uint inX, uint inY) const;187188/// Check if height field at sampled location (inX, inY) has collision (has a hole or not)189bool IsNoCollision(uint inX, uint inY) const;190191/// Projects inLocalPosition (a point in the space of the shape) along the Y axis onto the surface and returns it in outSurfacePosition.192/// When there is no surface position (because of a hole or because the point is outside the heightfield) the function will return false.193bool ProjectOntoSurface(Vec3Arg inLocalPosition, Vec3 &outSurfacePosition, SubShapeID &outSubShapeID) const;194195/// Returns the coordinates of the triangle that a sub shape ID represents196/// @param inSubShapeID The sub shape ID to decode197/// @param outX X coordinate of the triangle (in the range [0, mSampleCount - 2])198/// @param outY Y coordinate of the triangle (in the range [0, mSampleCount - 2])199/// @param outTriangleIndex Triangle within the quad (0 = lower triangle or 1 = upper triangle)200void GetSubShapeCoordinates(const SubShapeID &inSubShapeID, uint &outX, uint &outY, uint &outTriangleIndex) const;201202/// Get the range of height values that this height field can encode. Can be used to determine the allowed range when setting the height values with SetHeights.203float GetMinHeightValue() const { return mOffset.GetY(); }204float GetMaxHeightValue() const { return mOffset.GetY() + mScale.GetY() * HeightFieldShapeConstants::cMaxHeightValue16; }205206/// Get the height values of a block of data.207/// Note that the height values are decompressed so will be slightly different from what the shape was originally created with.208/// @param inX Start X position, must be a multiple of mBlockSize and in the range [0, mSampleCount - 1]209/// @param inY Start Y position, must be a multiple of mBlockSize and in the range [0, mSampleCount - 1]210/// @param inSizeX Number of samples in X direction, must be a multiple of mBlockSize and in the range [0, mSampleCount - inX]211/// @param inSizeY Number of samples in Y direction, must be a multiple of mBlockSize and in the range [0, mSampleCount - inY]212/// @param outHeights Returned height values, must be at least inSizeX * inSizeY floats. Values are returned in x-major order and can be cNoCollisionValue.213/// @param inHeightsStride Stride in floats between two consecutive rows of outHeights (can be negative if the data is upside down).214void GetHeights(uint inX, uint inY, uint inSizeX, uint inSizeY, float *outHeights, intptr_t inHeightsStride) const;215216/// Set the height values of a block of data.217/// Note that this requires decompressing and recompressing a border of size mBlockSize in the negative x/y direction so will cause some precision loss.218/// Beware this can create a race condition if you're running collision queries in parallel. See class documentation for more information.219/// @param inX Start X position, must be a multiple of mBlockSize and in the range [0, mSampleCount - 1]220/// @param inY Start Y position, must be a multiple of mBlockSize and in the range [0, mSampleCount - 1]221/// @param inSizeX Number of samples in X direction, must be a multiple of mBlockSize and in the range [0, mSampleCount - inX]222/// @param inSizeY Number of samples in Y direction, must be a multiple of mBlockSize and in the range [0, mSampleCount - inY]223/// @param inHeights The new height values to set, must be an array of inSizeX * inSizeY floats, can be cNoCollisionValue. Values outside of the range [GetMinHeightValue(), GetMaxHeightValue()] will be clamped.224/// @param inHeightsStride Stride in floats between two consecutive rows of inHeights (can be negative if the data is upside down).225/// @param inAllocator Allocator to use for temporary memory226/// @param inActiveEdgeCosThresholdAngle Cosine of the threshold angle (if the angle between the two triangles is bigger than this, the edge is active, note that a concave edge is always inactive).227void SetHeights(uint inX, uint inY, uint inSizeX, uint inSizeY, const float *inHeights, intptr_t inHeightsStride, TempAllocator &inAllocator, float inActiveEdgeCosThresholdAngle = 0.996195f);228229/// Get the current list of materials, the indices returned by GetMaterials() will index into this list.230const PhysicsMaterialList & GetMaterialList() const { return mMaterials; }231232/// Get the material indices of a block of data.233/// @param inX Start X position, must in the range [0, mSampleCount - 1]234/// @param inY Start Y position, must in the range [0, mSampleCount - 1]235/// @param inSizeX Number of samples in X direction236/// @param inSizeY Number of samples in Y direction237/// @param outMaterials Returned material indices, must be at least inSizeX * inSizeY uint8s. Values are returned in x-major order.238/// @param inMaterialsStride Stride in uint8s between two consecutive rows of outMaterials (can be negative if the data is upside down).239void GetMaterials(uint inX, uint inY, uint inSizeX, uint inSizeY, uint8 *outMaterials, intptr_t inMaterialsStride) const;240241/// Set the material indices of a block of data.242/// Beware this can create a race condition if you're running collision queries in parallel. See class documentation for more information.243/// @param inX Start X position, must in the range [0, mSampleCount - 1]244/// @param inY Start Y position, must in the range [0, mSampleCount - 1]245/// @param inSizeX Number of samples in X direction246/// @param inSizeY Number of samples in Y direction247/// @param inMaterials The new material indices, must be at least inSizeX * inSizeY uint8s. Values are returned in x-major order.248/// @param inMaterialsStride Stride in uint8s between two consecutive rows of inMaterials (can be negative if the data is upside down).249/// @param inMaterialList The material list to use for the new material indices or nullptr if the material list should not be updated250/// @param inAllocator Allocator to use for temporary memory251/// @return True if the material indices were set, false if the total number of materials exceeded 256252bool SetMaterials(uint inX, uint inY, uint inSizeX, uint inSizeY, const uint8 *inMaterials, intptr_t inMaterialsStride, const PhysicsMaterialList *inMaterialList, TempAllocator &inAllocator);253254// See Shape255virtual void SaveBinaryState(StreamOut &inStream) const override;256virtual void SaveMaterialState(PhysicsMaterialList &outMaterials) const override;257virtual void RestoreMaterialState(const PhysicsMaterialRefC *inMaterials, uint inNumMaterials) override;258259// See Shape::GetStats260virtual Stats GetStats() const override;261262// See Shape::GetVolume263virtual float GetVolume() const override { return 0; }264265#ifdef JPH_DEBUG_RENDERER266// Settings267static bool sDrawTriangleOutlines;268#endif // JPH_DEBUG_RENDERER269270// Register shape functions with the registry271static void sRegister();272273protected:274// See: Shape::RestoreBinaryState275virtual void RestoreBinaryState(StreamIn &inStream) override;276277private:278class DecodingContext; ///< Context class for walking through all nodes of a heightfield279struct HSGetTrianglesContext; ///< Context class for GetTrianglesStart/Next280281/// Calculate commonly used values and store them in the shape282void CacheValues();283284/// Allocate the mRangeBlocks, mHeightSamples and mActiveEdges buffers as a single data block285void AllocateBuffers();286287/// Calculate bit mask for all active edges in the heightfield for a specific region288void CalculateActiveEdges(uint inX, uint inY, uint inSizeX, uint inSizeY, const float *inHeights, uint inHeightsStartX, uint inHeightsStartY, intptr_t inHeightsStride, float inHeightsScale, float inActiveEdgeCosThresholdAngle, TempAllocator &inAllocator);289290/// Calculate bit mask for all active edges in the heightfield291void CalculateActiveEdges(const HeightFieldShapeSettings &inSettings);292293/// Store material indices in the least amount of bits per index possible294void StoreMaterialIndices(const HeightFieldShapeSettings &inSettings);295296/// Get the amount of horizontal/vertical blocks297inline uint GetNumBlocks() const { return mSampleCount / mBlockSize; }298299/// Get the maximum level (amount of grids) of the tree300static inline uint sGetMaxLevel(uint inNumBlocks) { return 32 - CountLeadingZeros(inNumBlocks - 1); }301302/// Get the range block offset and stride for GetBlockOffsetAndScale303static inline void sGetRangeBlockOffsetAndStride(uint inNumBlocks, uint inMaxLevel, uint &outRangeBlockOffset, uint &outRangeBlockStride);304305/// For block (inBlockX, inBlockY) get the offset and scale needed to decode a uint8 height sample to a uint16306inline void GetBlockOffsetAndScale(uint inBlockX, uint inBlockY, uint inRangeBlockOffset, uint inRangeBlockStride, float &outBlockOffset, float &outBlockScale) const;307308/// Get the height sample at position (inX, inY)309inline uint8 GetHeightSample(uint inX, uint inY) const;310311/// Faster version of GetPosition when block offset and scale are already known312inline Vec3 GetPosition(uint inX, uint inY, float inBlockOffset, float inBlockScale, bool &outNoCollision) const;313314/// Determine amount of bits needed to encode sub shape id315uint GetSubShapeIDBits() const;316317/// En/decode a sub shape ID. inX and inY specify the coordinate of the triangle. inTriangle == 0 is the lower triangle, inTriangle == 1 is the upper triangle.318inline SubShapeID EncodeSubShapeID(const SubShapeIDCreator &inCreator, uint inX, uint inY, uint inTriangle) const;319inline void DecodeSubShapeID(const SubShapeID &inSubShapeID, uint &outX, uint &outY, uint &outTriangle) const;320321/// Get the edge flags for a triangle322inline uint8 GetEdgeFlags(uint inX, uint inY, uint inTriangle) const;323324// Helper functions called by CollisionDispatch325static void sCollideConvexVsHeightField(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);326static void sCollideSphereVsHeightField(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);327static void sCastConvexVsHeightField(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector);328static void sCastSphereVsHeightField(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector);329330/// Visit the entire height field using a visitor pattern331/// Note: Used to be inlined but this triggers a bug in MSVC where it will not free the memory allocated by alloca which causes a stack overflow when WalkHeightField is called in a loop (clang does it correct)332template <class Visitor>333void WalkHeightField(Visitor &ioVisitor) const;334335/// A block of 2x2 ranges used to form a hierarchical grid, ordered left top, right top, left bottom, right bottom336struct alignas(16) RangeBlock337{338uint16 mMin[4];339uint16 mMax[4];340};341342/// For block (inBlockX, inBlockY) get the range block and the entry in the range block343inline void GetRangeBlock(uint inBlockX, uint inBlockY, uint inRangeBlockOffset, uint inRangeBlockStride, RangeBlock *&outBlock, uint &outIndexInBlock);344345/// Offset of first RangedBlock in grid per level346static const uint sGridOffsets[];347348/// The height field is a surface defined by: mOffset + mScale * (x, mHeightSamples[y * mSampleCount + x], y).349/// where x and y are integers in the range x and y e [0, mSampleCount - 1].350Vec3 mOffset = Vec3::sZero();351Vec3 mScale = Vec3::sOne();352353/// Height data354uint32 mSampleCount = 0; ///< See HeightFieldShapeSettings::mSampleCount355uint32 mBlockSize = 2; ///< See HeightFieldShapeSettings::mBlockSize356uint32 mHeightSamplesSize = 0; ///< Size of mHeightSamples in bytes357uint32 mRangeBlocksSize = 0; ///< Size of mRangeBlocks in elements358uint32 mActiveEdgesSize = 0; ///< Size of mActiveEdges in bytes359uint8 mBitsPerSample = 8; ///< See HeightFieldShapeSettings::mBitsPerSample360uint8 mSampleMask = 0xff; ///< All bits set for a sample: (1 << mBitsPerSample) - 1, used to indicate that there's no collision361uint16 mMinSample = HeightFieldShapeConstants::cNoCollisionValue16; ///< Min and max value in mHeightSamples quantized to 16 bit, for calculating bounding box362uint16 mMaxSample = HeightFieldShapeConstants::cNoCollisionValue16;363RangeBlock * mRangeBlocks = nullptr; ///< Hierarchical grid of range data describing the height variations within 1 block. The grid for level <level> starts at offset sGridOffsets[<level>]364uint8 * mHeightSamples = nullptr; ///< mBitsPerSample-bit height samples. Value [0, mMaxHeightValue] maps to highest detail grid in mRangeBlocks [mMin, mMax]. mNoCollisionValue is reserved to indicate no collision.365uint8 * mActiveEdges = nullptr; ///< (mSampleCount - 1)^2 * 3-bit active edge flags.366367/// Materials368PhysicsMaterialList mMaterials; ///< The materials of square at (x, y) is: mMaterials[mMaterialIndices[x + y * (mSampleCount - 1)]]369Array<uint8> mMaterialIndices; ///< Compressed to the minimum amount of bits per material index (mSampleCount - 1) * (mSampleCount - 1) * mNumBitsPerMaterialIndex bits of data370uint32 mNumBitsPerMaterialIndex = 0; ///< Number of bits per material index371372#ifdef JPH_DEBUG_RENDERER373/// Temporary rendering data374mutable Array<DebugRenderer::GeometryRef> mGeometry;375mutable bool mCachedUseMaterialColors = false; ///< This is used to regenerate the triangle batch if the drawing settings change376#endif // JPH_DEBUG_RENDERER377};378379JPH_NAMESPACE_END380381382