Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/HeightFieldShape.cpp
9913 views
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)1// SPDX-FileCopyrightText: 2021 Jorrit Rouwe2// SPDX-License-Identifier: MIT34#include <Jolt/Jolt.h>56#include <Jolt/Physics/Collision/Shape/HeightFieldShape.h>7#include <Jolt/Physics/Collision/Shape/ConvexShape.h>8#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>9#include <Jolt/Physics/Collision/Shape/SphereShape.h>10#include <Jolt/Physics/Collision/RayCast.h>11#include <Jolt/Physics/Collision/ShapeCast.h>12#include <Jolt/Physics/Collision/CastResult.h>13#include <Jolt/Physics/Collision/CollidePointResult.h>14#include <Jolt/Physics/Collision/ShapeFilter.h>15#include <Jolt/Physics/Collision/CastConvexVsTriangles.h>16#include <Jolt/Physics/Collision/CastSphereVsTriangles.h>17#include <Jolt/Physics/Collision/CollideConvexVsTriangles.h>18#include <Jolt/Physics/Collision/CollideSphereVsTriangles.h>19#include <Jolt/Physics/Collision/TransformedShape.h>20#include <Jolt/Physics/Collision/ActiveEdges.h>21#include <Jolt/Physics/Collision/CollisionDispatch.h>22#include <Jolt/Physics/Collision/SortReverseAndStore.h>23#include <Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h>24#include <Jolt/Core/Profiler.h>25#include <Jolt/Core/StringTools.h>26#include <Jolt/Core/StreamIn.h>27#include <Jolt/Core/StreamOut.h>28#include <Jolt/Core/TempAllocator.h>29#include <Jolt/Core/ScopeExit.h>30#include <Jolt/Geometry/AABox4.h>31#include <Jolt/Geometry/RayTriangle.h>32#include <Jolt/Geometry/RayAABox.h>33#include <Jolt/Geometry/OrientedBox.h>34#include <Jolt/ObjectStream/TypeDeclarations.h>3536//#define JPH_DEBUG_HEIGHT_FIELD3738JPH_NAMESPACE_BEGIN3940#ifdef JPH_DEBUG_RENDERER41bool HeightFieldShape::sDrawTriangleOutlines = false;42#endif // JPH_DEBUG_RENDERER4344using namespace HeightFieldShapeConstants;4546JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(HeightFieldShapeSettings)47{48JPH_ADD_BASE_CLASS(HeightFieldShapeSettings, ShapeSettings)4950JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mHeightSamples)51JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mOffset)52JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mScale)53JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMinHeightValue)54JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaxHeightValue)55JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterialsCapacity)56JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mSampleCount)57JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mBlockSize)58JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mBitsPerSample)59JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterialIndices)60JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterials)61JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mActiveEdgeCosThresholdAngle)62}6364const uint HeightFieldShape::sGridOffsets[] =65{660, // level: 0, max x/y: 0, offset: 0671, // level: 1, max x/y: 1, offset: 1685, // level: 2, max x/y: 3, offset: 1 + 46921, // level: 3, max x/y: 7, offset: 1 + 4 + 167085, // level: 4, max x/y: 15, offset: 1 + 4 + 16 + 6471341, // level: 5, max x/y: 31, offset: 1 + 4 + 16 + 64 + 256721365, // level: 6, max x/y: 63, offset: 1 + 4 + 16 + 64 + 256 + 1024735461, // level: 7, max x/y: 127, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 40967421845, // level: 8, max x/y: 255, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...7587381, // level: 9, max x/y: 511, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...76349525, // level: 10, max x/y: 1023, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...771398101, // level: 11, max x/y: 2047, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...785592405, // level: 12, max x/y: 4095, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...7922369621, // level: 13, max x/y: 8191, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...8089478485, // level: 14, max x/y: 16383, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...81};8283HeightFieldShapeSettings::HeightFieldShapeSettings(const float *inSamples, Vec3Arg inOffset, Vec3Arg inScale, uint32 inSampleCount, const uint8 *inMaterialIndices, const PhysicsMaterialList &inMaterialList) :84mOffset(inOffset),85mScale(inScale),86mSampleCount(inSampleCount)87{88mHeightSamples.assign(inSamples, inSamples + Square(inSampleCount));8990if (!inMaterialList.empty() && inMaterialIndices != nullptr)91{92mMaterialIndices.assign(inMaterialIndices, inMaterialIndices + Square(inSampleCount - 1));93mMaterials = inMaterialList;94}95else96{97JPH_ASSERT(inMaterialList.empty());98JPH_ASSERT(inMaterialIndices == nullptr);99}100}101102ShapeSettings::ShapeResult HeightFieldShapeSettings::Create() const103{104if (mCachedResult.IsEmpty())105Ref<Shape> shape = new HeightFieldShape(*this, mCachedResult);106return mCachedResult;107}108109void HeightFieldShapeSettings::DetermineMinAndMaxSample(float &outMinValue, float &outMaxValue, float &outQuantizationScale) const110{111// Determine min and max value112outMinValue = mMinHeightValue;113outMaxValue = mMaxHeightValue;114for (float h : mHeightSamples)115if (h != cNoCollisionValue)116{117outMinValue = min(outMinValue, h);118outMaxValue = max(outMaxValue, h);119}120121// Prevent dividing by zero by setting a minimal height difference122float height_diff = max(outMaxValue - outMinValue, 1.0e-6f);123124// Calculate the scale factor to quantize to 16 bits125outQuantizationScale = float(cMaxHeightValue16) / height_diff;126}127128uint32 HeightFieldShapeSettings::CalculateBitsPerSampleForError(float inMaxError) const129{130// Start with 1 bit per sample131uint32 bits_per_sample = 1;132133// Determine total range134float min_value, max_value, scale;135DetermineMinAndMaxSample(min_value, max_value, scale);136if (min_value < max_value)137{138// Loop over all blocks139for (uint y = 0; y < mSampleCount; y += mBlockSize)140for (uint x = 0; x < mSampleCount; x += mBlockSize)141{142// Determine min and max block value + take 1 sample border just like we do while building the hierarchical grids143float block_min_value = FLT_MAX, block_max_value = -FLT_MAX;144for (uint bx = x; bx < min(x + mBlockSize + 1, mSampleCount); ++bx)145for (uint by = y; by < min(y + mBlockSize + 1, mSampleCount); ++by)146{147float h = mHeightSamples[by * mSampleCount + bx];148if (h != cNoCollisionValue)149{150block_min_value = min(block_min_value, h);151block_max_value = max(block_max_value, h);152}153}154155if (block_min_value < block_max_value)156{157// Quantize then dequantize block min/max value158block_min_value = min_value + floor((block_min_value - min_value) * scale) / scale;159block_max_value = min_value + ceil((block_max_value - min_value) * scale) / scale;160float block_height = block_max_value - block_min_value;161162// Loop over the block again163for (uint bx = x; bx < x + mBlockSize; ++bx)164for (uint by = y; by < y + mBlockSize; ++by)165{166// Get the height167float height = mHeightSamples[by * mSampleCount + bx];168if (height != cNoCollisionValue)169{170for (;;)171{172// Determine bitmask for sample173uint32 sample_mask = (1 << bits_per_sample) - 1;174175// Quantize176float quantized_height = floor((height - block_min_value) * float(sample_mask) / block_height);177quantized_height = Clamp(quantized_height, 0.0f, float(sample_mask - 1));178179// Dequantize and check error180float dequantized_height = block_min_value + (quantized_height + 0.5f) * block_height / float(sample_mask);181if (abs(dequantized_height - height) <= inMaxError)182break;183184// Not accurate enough, increase bits per sample185bits_per_sample++;186187// Don't go above 8 bits per sample188if (bits_per_sample == 8)189return bits_per_sample;190}191}192}193}194}195196}197198return bits_per_sample;199}200201void HeightFieldShape::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)202{203// Limit the block size so we don't allocate more than 64K memory from the temp allocator204uint block_size_x = min(inSizeX, 44u);205uint block_size_y = min(inSizeY, 44u);206207// Allocate temporary buffer for normals208uint normals_size = 2 * (block_size_x + 1) * (block_size_y + 1) * sizeof(Vec3);209Vec3 *normals = (Vec3 *)inAllocator.Allocate(normals_size);210JPH_SCOPE_EXIT([&inAllocator, normals, normals_size]{ inAllocator.Free(normals, normals_size); });211212// Update the edges in blocks213for (uint block_y = 0; block_y < inSizeY; block_y += block_size_y)214for (uint block_x = 0; block_x < inSizeX; block_x += block_size_x)215{216// Calculate the bottom right corner of the block217uint block_x_end = min(block_x + block_size_x, inSizeX);218uint block_y_end = min(block_y + block_size_y, inSizeY);219220// If we're not at the first block in x, we need one extra column of normals to the left221uint normals_x_start, normals_x_skip;222if (block_x > 0)223{224normals_x_start = block_x - 1;225normals_x_skip = 2; // We need to skip over that extra column226}227else228{229normals_x_start = 0;230normals_x_skip = 0;231}232233// If we're not at the last block in y, we need one extra row of normals at the bottom234uint normals_y_end = block_y_end < inSizeY? block_y_end + 1 : inSizeY;235236// Calculate triangle normals and make normals zero for triangles that are missing237Vec3 *out_normal = normals;238for (uint y = block_y; y < normals_y_end; ++y)239{240for (uint x = normals_x_start; x < block_x_end; ++x)241{242// Get height on diagonal243const float *height_samples = inHeights + (inY - inHeightsStartY + y) * inHeightsStride + (inX - inHeightsStartX + x);244float x1y1_h = height_samples[0];245float x2y2_h = height_samples[inHeightsStride + 1];246if (x1y1_h != cNoCollisionValue && x2y2_h != cNoCollisionValue)247{248// Calculate normal for lower left triangle (e.g. T1A)249float x1y2_h = height_samples[inHeightsStride];250if (x1y2_h != cNoCollisionValue)251{252Vec3 x2y2_minus_x1y2(mScale.GetX(), inHeightsScale * (x2y2_h - x1y2_h), 0);253Vec3 x1y1_minus_x1y2(0, inHeightsScale * (x1y1_h - x1y2_h), -mScale.GetZ());254out_normal[0] = x2y2_minus_x1y2.Cross(x1y1_minus_x1y2).Normalized();255}256else257out_normal[0] = Vec3::sZero();258259// Calculate normal for upper right triangle (e.g. T1B)260float x2y1_h = height_samples[1];261if (x2y1_h != cNoCollisionValue)262{263Vec3 x1y1_minus_x2y1(-mScale.GetX(), inHeightsScale * (x1y1_h - x2y1_h), 0);264Vec3 x2y2_minus_x2y1(0, inHeightsScale * (x2y2_h - x2y1_h), mScale.GetZ());265out_normal[1] = x1y1_minus_x2y1.Cross(x2y2_minus_x2y1).Normalized();266}267else268out_normal[1] = Vec3::sZero();269}270else271{272out_normal[0] = Vec3::sZero();273out_normal[1] = Vec3::sZero();274}275276out_normal += 2;277}278}279280// Number of vectors to skip to get to the next row of normals281uint normals_pitch = 2 * (block_x_end - normals_x_start);282283// Calculate active edges284const Vec3 *in_normal = normals;285uint global_bit_pos = 3 * ((inY + block_y) * (mSampleCount - 1) + (inX + block_x));286for (uint y = block_y; y < block_y_end; ++y)287{288in_normal += normals_x_skip; // If we have an extra column to the left, skip it here, we'll read it with in_normal[-1] below289290for (uint x = block_x; x < block_x_end; ++x)291{292// Get vertex heights293const float *height_samples = inHeights + (inY - inHeightsStartY + y) * inHeightsStride + (inX - inHeightsStartX + x);294float x1y1_h = height_samples[0];295float x1y2_h = height_samples[inHeightsStride];296float x2y2_h = height_samples[inHeightsStride + 1];297bool x1y1_valid = x1y1_h != cNoCollisionValue;298bool x1y2_valid = x1y2_h != cNoCollisionValue;299bool x2y2_valid = x2y2_h != cNoCollisionValue;300301// Calculate the edge flags (3 bits)302// See diagram in the next function for the edge numbering303uint16 edge_mask = 0b111;304uint16 edge_flags = 0;305306// Edge 0307if (x == 0)308edge_mask &= 0b110; // We need normal x - 1 which we didn't calculate, don't update this edge309else if (x1y1_valid && x1y2_valid)310{311Vec3 edge0_direction(0, inHeightsScale * (x1y2_h - x1y1_h), mScale.GetZ());312if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[-1], edge0_direction, inActiveEdgeCosThresholdAngle))313edge_flags |= 0b001;314}315316// Edge 1317if (y == inSizeY - 1)318edge_mask &= 0b101; // We need normal y + 1 which we didn't calculate, don't update this edge319else if (x1y2_valid && x2y2_valid)320{321Vec3 edge1_direction(mScale.GetX(), inHeightsScale * (x2y2_h - x1y2_h), 0);322if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[normals_pitch + 1], edge1_direction, inActiveEdgeCosThresholdAngle))323edge_flags |= 0b010;324}325326// Edge 2327if (x1y1_valid && x2y2_valid)328{329Vec3 edge2_direction(-mScale.GetX(), inHeightsScale * (x1y1_h - x2y2_h), -mScale.GetZ());330if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[1], edge2_direction, inActiveEdgeCosThresholdAngle))331edge_flags |= 0b100;332}333334// Store the edge flags in the array335uint byte_pos = global_bit_pos >> 3;336uint bit_pos = global_bit_pos & 0b111;337JPH_ASSERT(byte_pos < mActiveEdgesSize);338uint8 *edge_flags_ptr = &mActiveEdges[byte_pos];339uint16 combined_edge_flags = uint16(edge_flags_ptr[0]) | uint16(uint16(edge_flags_ptr[1]) << 8);340combined_edge_flags &= ~(edge_mask << bit_pos);341combined_edge_flags |= edge_flags << bit_pos;342edge_flags_ptr[0] = uint8(combined_edge_flags);343edge_flags_ptr[1] = uint8(combined_edge_flags >> 8);344345in_normal += 2;346global_bit_pos += 3;347}348349global_bit_pos += 3 * (mSampleCount - 1 - (block_x_end - block_x));350}351}352}353354void HeightFieldShape::CalculateActiveEdges(const HeightFieldShapeSettings &inSettings)355{356/*357Store active edges. The triangles are organized like this:358x --->359360y + +361| \ T1B | \ T2B362| e0 e2 | \363| | T1A \ | T2A \364V +--e1---+-------+365| \ T3B | \ T4B366| \ | \367| T3A \ | T4A \368+-------+-------+369We store active edges e0 .. e2 as bits 0 .. 2.370We store triangles horizontally then vertically (order T1A, T2A, T3A and T4A).371The top edge and right edge of the heightfield are always active so we do not need to store them,372therefore we only need to store (mSampleCount - 1)^2 * 3-bit373The triangles T1B, T2B, T3B and T4B do not need to be stored, their active edges can be constructed from adjacent triangles.374Add 1 byte padding so we can always read 1 uint16 to get the bits that cross an 8 bit boundary375*/376377// Make all edges active (if mSampleCount is bigger than inSettings.mSampleCount we need to fill up the padding,378// also edges at x = 0 and y = inSettings.mSampleCount - 1 are not updated)379memset(mActiveEdges, 0xff, mActiveEdgesSize);380381// Now clear the edges that are not active382TempAllocatorMalloc allocator;383CalculateActiveEdges(0, 0, inSettings.mSampleCount - 1, inSettings.mSampleCount - 1, inSettings.mHeightSamples.data(), 0, 0, inSettings.mSampleCount, inSettings.mScale.GetY(), inSettings.mActiveEdgeCosThresholdAngle, allocator);384}385386void HeightFieldShape::StoreMaterialIndices(const HeightFieldShapeSettings &inSettings)387{388// We need to account for any rounding of the sample count to the nearest block size389uint in_count_min_1 = inSettings.mSampleCount - 1;390uint out_count_min_1 = mSampleCount - 1;391392mNumBitsPerMaterialIndex = 32 - CountLeadingZeros(max((uint32)mMaterials.size(), inSettings.mMaterialsCapacity) - 1);393mMaterialIndices.resize(((Square(out_count_min_1) * mNumBitsPerMaterialIndex + 7) >> 3) + 1, 0); // Add 1 byte so we don't read out of bounds when reading an uint16394395if (mMaterials.size() > 1)396for (uint y = 0; y < out_count_min_1; ++y)397for (uint x = 0; x < out_count_min_1; ++x)398{399// Read material400uint16 material_index = x < in_count_min_1 && y < in_count_min_1? uint16(inSettings.mMaterialIndices[x + y * in_count_min_1]) : 0;401402// Calculate byte and bit position where the material index needs to go403uint sample_pos = x + y * out_count_min_1;404uint bit_pos = sample_pos * mNumBitsPerMaterialIndex;405uint byte_pos = bit_pos >> 3;406bit_pos &= 0b111;407408// Write the material index409material_index <<= bit_pos;410JPH_ASSERT(byte_pos + 1 < mMaterialIndices.size());411mMaterialIndices[byte_pos] |= uint8(material_index);412mMaterialIndices[byte_pos + 1] |= uint8(material_index >> 8);413}414}415416void HeightFieldShape::CacheValues()417{418mSampleMask = uint8((uint32(1) << mBitsPerSample) - 1);419}420421void HeightFieldShape::AllocateBuffers()422{423uint num_blocks = GetNumBlocks();424uint max_stride = (num_blocks + 1) >> 1;425mRangeBlocksSize = sGridOffsets[sGetMaxLevel(num_blocks) - 1] + Square(max_stride);426mHeightSamplesSize = (mSampleCount * mSampleCount * mBitsPerSample + 7) / 8 + 1;427mActiveEdgesSize = (Square(mSampleCount - 1) * 3 + 7) / 8 + 1; // See explanation at HeightFieldShape::CalculateActiveEdges428429JPH_ASSERT(mRangeBlocks == nullptr && mHeightSamples == nullptr && mActiveEdges == nullptr);430void *data = AlignedAllocate(mRangeBlocksSize * sizeof(RangeBlock) + mHeightSamplesSize + mActiveEdgesSize, alignof(RangeBlock));431mRangeBlocks = reinterpret_cast<RangeBlock *>(data);432mHeightSamples = reinterpret_cast<uint8 *>(mRangeBlocks + mRangeBlocksSize);433mActiveEdges = mHeightSamples + mHeightSamplesSize;434}435436HeightFieldShape::HeightFieldShape(const HeightFieldShapeSettings &inSettings, ShapeResult &outResult) :437Shape(EShapeType::HeightField, EShapeSubType::HeightField, inSettings, outResult),438mOffset(inSettings.mOffset),439mScale(inSettings.mScale),440mSampleCount(((inSettings.mSampleCount + inSettings.mBlockSize - 1) / inSettings.mBlockSize) * inSettings.mBlockSize), // Round sample count to nearest block size441mBlockSize(inSettings.mBlockSize),442mBitsPerSample(uint8(inSettings.mBitsPerSample))443{444CacheValues();445446// Reserve a bigger materials list if requested447if (inSettings.mMaterialsCapacity > 0)448mMaterials.reserve(inSettings.mMaterialsCapacity);449mMaterials = inSettings.mMaterials;450451// Check block size452if (mBlockSize < 2 || mBlockSize > 8)453{454outResult.SetError("HeightFieldShape: Block size must be in the range [2, 8]!");455return;456}457458// Check bits per sample459if (inSettings.mBitsPerSample < 1 || inSettings.mBitsPerSample > 8)460{461outResult.SetError("HeightFieldShape: Bits per sample must be in the range [1, 8]!");462return;463}464465// We stop at mBlockSize x mBlockSize height sample blocks466uint num_blocks = GetNumBlocks();467468// We want at least 1 grid layer469if (num_blocks < 2)470{471outResult.SetError("HeightFieldShape: Sample count too low!");472return;473}474475// Check that we don't overflow our 32 bit 'properties'476if (num_blocks > (1 << cNumBitsXY))477{478outResult.SetError("HeightFieldShape: Sample count too high!");479return;480}481482// Check if we're not exceeding the amount of sub shape id bits483if (GetSubShapeIDBitsRecursive() > SubShapeID::MaxBits)484{485outResult.SetError("HeightFieldShape: Size exceeds the amount of available sub shape ID bits!");486return;487}488489if (!mMaterials.empty())490{491// Validate materials492if (mMaterials.size() > 256)493{494outResult.SetError("Supporting max 256 materials per height field");495return;496}497for (uint8 s : inSettings.mMaterialIndices)498if (s >= mMaterials.size())499{500outResult.SetError(StringFormat("Material %u is beyond material list (size: %u)", s, (uint)mMaterials.size()));501return;502}503}504else505{506// No materials assigned, validate that no materials have been specified507if (!inSettings.mMaterialIndices.empty())508{509outResult.SetError("No materials present, mMaterialIndices should be empty");510return;511}512}513514// Determine range515float min_value, max_value, scale;516inSettings.DetermineMinAndMaxSample(min_value, max_value, scale);517if (min_value > max_value)518{519// If there is no collision with this heightmap, leave everything empty520mMaterials.clear();521outResult.Set(this);522return;523}524525// Allocate space for this shape526AllocateBuffers();527528// Quantize to uint16529Array<uint16> quantized_samples;530quantized_samples.reserve(mSampleCount * mSampleCount);531for (uint y = 0; y < inSettings.mSampleCount; ++y)532{533for (uint x = 0; x < inSettings.mSampleCount; ++x)534{535float h = inSettings.mHeightSamples[x + y * inSettings.mSampleCount];536if (h == cNoCollisionValue)537{538quantized_samples.push_back(cNoCollisionValue16);539}540else541{542// Floor the quantized height to get a lower bound for the quantized value543int quantized_height = (int)floor(scale * (h - min_value));544545// Ensure that the height says below the max height value so we can safely add 1 to get the upper bound for the quantized value546quantized_height = Clamp(quantized_height, 0, int(cMaxHeightValue16 - 1));547548quantized_samples.push_back(uint16(quantized_height));549}550}551// Pad remaining columns with no collision552for (uint x = inSettings.mSampleCount; x < mSampleCount; ++x)553quantized_samples.push_back(cNoCollisionValue16);554}555// Pad remaining rows with no collision556for (uint y = inSettings.mSampleCount; y < mSampleCount; ++y)557for (uint x = 0; x < mSampleCount; ++x)558quantized_samples.push_back(cNoCollisionValue16);559560// Update offset and scale to account for the compression to uint16561if (min_value <= max_value) // Only when there was collision562{563// In GetPosition we always add 0.5 to the quantized sample in order to reduce the average error.564// We want to be able to exactly quantize min_value (this is important in case the heightfield is entirely flat) so we subtract that value from min_value.565min_value -= 0.5f / (scale * mSampleMask);566567mOffset.SetY(mOffset.GetY() + mScale.GetY() * min_value);568}569mScale.SetY(mScale.GetY() / scale);570571// Calculate amount of grids572uint max_level = sGetMaxLevel(num_blocks);573574// Temporary data structure used during creating of a hierarchy of grids575struct Range576{577uint16 mMin;578uint16 mMax;579};580581// Reserve size for temporary range data + reserve 1 extra for a 1x1 grid that we won't store but use for calculating the bounding box582Array<Array<Range>> ranges;583ranges.resize(max_level + 1);584585// Calculate highest detail grid by combining mBlockSize x mBlockSize height samples586Array<Range> *cur_range_vector = &ranges.back();587uint num_blocks_pow2 = GetNextPowerOf2(num_blocks); // We calculate the range blocks as if the heightfield was a power of 2, when we save the range blocks we'll ignore the extra samples (this makes downsampling easier)588cur_range_vector->resize(num_blocks_pow2 * num_blocks_pow2);589Range *range_dst = &cur_range_vector->front();590for (uint y = 0; y < num_blocks_pow2; ++y)591for (uint x = 0; x < num_blocks_pow2; ++x)592{593range_dst->mMin = 0xffff;594range_dst->mMax = 0;595uint max_bx = x == num_blocks_pow2 - 1? mBlockSize : mBlockSize + 1; // for interior blocks take 1 more because the triangles connect to the next block so we must include their height too596uint max_by = y == num_blocks_pow2 - 1? mBlockSize : mBlockSize + 1;597for (uint by = 0; by < max_by; ++by)598for (uint bx = 0; bx < max_bx; ++bx)599{600uint sx = x * mBlockSize + bx;601uint sy = y * mBlockSize + by;602if (sx < mSampleCount && sy < mSampleCount)603{604uint16 h = quantized_samples[sy * mSampleCount + sx];605if (h != cNoCollisionValue16)606{607range_dst->mMin = min(range_dst->mMin, h);608range_dst->mMax = max(range_dst->mMax, uint16(h + 1)); // Add 1 to the max so we know the real value is between mMin and mMax609}610}611}612++range_dst;613}614615// Calculate remaining grids616for (uint n = num_blocks_pow2 >> 1; n >= 1; n >>= 1)617{618// Get source buffer619const Range *range_src = &cur_range_vector->front();620621// Previous array element622--cur_range_vector;623624// Make space for this grid625cur_range_vector->resize(n * n);626627// Get target buffer628range_dst = &cur_range_vector->front();629630// Combine the results of 2x2 ranges631for (uint y = 0; y < n; ++y)632for (uint x = 0; x < n; ++x)633{634range_dst->mMin = 0xffff;635range_dst->mMax = 0;636for (uint by = 0; by < 2; ++by)637for (uint bx = 0; bx < 2; ++bx)638{639const Range &r = range_src[(y * 2 + by) * n * 2 + x * 2 + bx];640range_dst->mMin = min(range_dst->mMin, r.mMin);641range_dst->mMax = max(range_dst->mMax, r.mMax);642}643++range_dst;644}645}646JPH_ASSERT(cur_range_vector == &ranges.front());647648// Store global range for bounding box calculation649mMinSample = ranges[0][0].mMin;650mMaxSample = ranges[0][0].mMax;651652#ifdef JPH_ENABLE_ASSERTS653// Validate that we did not lose range along the way654uint16 minv = 0xffff, maxv = 0;655for (uint16 v : quantized_samples)656if (v != cNoCollisionValue16)657{658minv = min(minv, v);659maxv = max(maxv, uint16(v + 1));660}661JPH_ASSERT(mMinSample == minv && mMaxSample == maxv);662#endif663664// Now erase the first element, we need a 2x2 grid to start with665ranges.erase(ranges.begin());666667// Create blocks668uint max_stride = (num_blocks + 1) >> 1;669RangeBlock *current_block = mRangeBlocks;670for (uint level = 0; level < ranges.size(); ++level)671{672JPH_ASSERT(uint(current_block - mRangeBlocks) == sGridOffsets[level]);673674uint in_n = 1 << level;675uint out_n = min(in_n, max_stride); // At the most detailed level we store a non-power of 2 number of blocks676677for (uint y = 0; y < out_n; ++y)678for (uint x = 0; x < out_n; ++x)679{680// Convert from 2x2 Range structure to 1 RangeBlock structure681RangeBlock &rb = *current_block++;682for (uint by = 0; by < 2; ++by)683for (uint bx = 0; bx < 2; ++bx)684{685uint src_pos = (y * 2 + by) * 2 * in_n + (x * 2 + bx);686uint dst_pos = by * 2 + bx;687rb.mMin[dst_pos] = ranges[level][src_pos].mMin;688rb.mMax[dst_pos] = ranges[level][src_pos].mMax;689}690}691}692JPH_ASSERT(uint32(current_block - mRangeBlocks) == mRangeBlocksSize);693694// Quantize height samples695memset(mHeightSamples, 0, mHeightSamplesSize);696int sample = 0;697for (uint y = 0; y < mSampleCount; ++y)698for (uint x = 0; x < mSampleCount; ++x)699{700uint32 output_value;701702float h = x < inSettings.mSampleCount && y < inSettings.mSampleCount? inSettings.mHeightSamples[x + y * inSettings.mSampleCount] : cNoCollisionValue;703if (h == cNoCollisionValue)704{705// No collision706output_value = mSampleMask;707}708else709{710// Get range of block so we know what range to compress to711uint bx = x / mBlockSize;712uint by = y / mBlockSize;713const Range &range = ranges.back()[by * num_blocks_pow2 + bx];714JPH_ASSERT(range.mMin < range.mMax);715716// Quantize to mBitsPerSample bits, note that mSampleMask is reserved for indicating that there's no collision.717// We divide the range into mSampleMask segments and use the mid points of these segments as the quantized values.718// This results in a lower error than if we had quantized our data using the lowest point of all these segments.719float h_min = min_value + range.mMin / scale;720float h_delta = float(range.mMax - range.mMin) / scale;721float quantized_height = floor((h - h_min) * float(mSampleMask) / h_delta);722output_value = uint32(Clamp((int)quantized_height, 0, int(mSampleMask) - 1)); // mSampleMask is reserved as 'no collision value'723}724725// Store the sample726uint byte_pos = sample >> 3;727uint bit_pos = sample & 0b111;728output_value <<= bit_pos;729JPH_ASSERT(byte_pos + 1 < mHeightSamplesSize);730mHeightSamples[byte_pos] |= uint8(output_value);731mHeightSamples[byte_pos + 1] |= uint8(output_value >> 8);732sample += inSettings.mBitsPerSample;733}734735// Calculate the active edges736CalculateActiveEdges(inSettings);737738// Compress material indices739if (mMaterials.size() > 1 || inSettings.mMaterialsCapacity > 1)740StoreMaterialIndices(inSettings);741742outResult.Set(this);743}744745HeightFieldShape::~HeightFieldShape()746{747if (mRangeBlocks != nullptr)748AlignedFree(mRangeBlocks);749}750751Ref<HeightFieldShape> HeightFieldShape::Clone() const752{753Ref<HeightFieldShape> clone = new HeightFieldShape;754clone->SetUserData(GetUserData());755756clone->mOffset = mOffset;757clone->mScale = mScale;758clone->mSampleCount = mSampleCount;759clone->mBlockSize = mBlockSize;760clone->mBitsPerSample = mBitsPerSample;761clone->mSampleMask = mSampleMask;762clone->mMinSample = mMinSample;763clone->mMaxSample = mMaxSample;764765clone->AllocateBuffers();766memcpy(clone->mRangeBlocks, mRangeBlocks, mRangeBlocksSize * sizeof(RangeBlock) + mHeightSamplesSize + mActiveEdgesSize); // Copy the entire buffer in 1 go767768clone->mMaterials.reserve(mMaterials.capacity()); // Ensure we keep the capacity of the original769clone->mMaterials = mMaterials;770clone->mMaterialIndices = mMaterialIndices;771clone->mNumBitsPerMaterialIndex = mNumBitsPerMaterialIndex;772773#ifdef JPH_DEBUG_RENDERER774clone->mGeometry = mGeometry;775clone->mCachedUseMaterialColors = mCachedUseMaterialColors;776#endif // JPH_DEBUG_RENDERER777778return clone;779}780781inline void HeightFieldShape::sGetRangeBlockOffsetAndStride(uint inNumBlocks, uint inMaxLevel, uint &outRangeBlockOffset, uint &outRangeBlockStride)782{783outRangeBlockOffset = sGridOffsets[inMaxLevel - 1];784outRangeBlockStride = (inNumBlocks + 1) >> 1;785}786787inline void HeightFieldShape::GetRangeBlock(uint inBlockX, uint inBlockY, uint inRangeBlockOffset, uint inRangeBlockStride, RangeBlock *&outBlock, uint &outIndexInBlock)788{789JPH_ASSERT(inBlockX < GetNumBlocks() && inBlockY < GetNumBlocks());790791// Convert to location of range block792uint rbx = inBlockX >> 1;793uint rby = inBlockY >> 1;794outIndexInBlock = ((inBlockY & 1) << 1) + (inBlockX & 1);795796uint offset = inRangeBlockOffset + rby * inRangeBlockStride + rbx;797JPH_ASSERT(offset < mRangeBlocksSize);798outBlock = mRangeBlocks + offset;799}800801inline void HeightFieldShape::GetBlockOffsetAndScale(uint inBlockX, uint inBlockY, uint inRangeBlockOffset, uint inRangeBlockStride, float &outBlockOffset, float &outBlockScale) const802{803JPH_ASSERT(inBlockX < GetNumBlocks() && inBlockY < GetNumBlocks());804805// Convert to location of range block806uint rbx = inBlockX >> 1;807uint rby = inBlockY >> 1;808uint n = ((inBlockY & 1) << 1) + (inBlockX & 1);809810// Calculate offset and scale811uint offset = inRangeBlockOffset + rby * inRangeBlockStride + rbx;812JPH_ASSERT(offset < mRangeBlocksSize);813const RangeBlock &block = mRangeBlocks[offset];814outBlockOffset = float(block.mMin[n]);815outBlockScale = float(block.mMax[n] - block.mMin[n]) / float(mSampleMask);816}817818inline uint8 HeightFieldShape::GetHeightSample(uint inX, uint inY) const819{820JPH_ASSERT(inX < mSampleCount);821JPH_ASSERT(inY < mSampleCount);822823// Determine bit position of sample824uint sample = (inY * mSampleCount + inX) * uint(mBitsPerSample);825uint byte_pos = sample >> 3;826uint bit_pos = sample & 0b111;827828// Fetch the height sample value829JPH_ASSERT(byte_pos + 1 < mHeightSamplesSize);830const uint8 *height_samples = mHeightSamples + byte_pos;831uint16 height_sample = uint16(height_samples[0]) | uint16(uint16(height_samples[1]) << 8);832return uint8(height_sample >> bit_pos) & mSampleMask;833}834835inline Vec3 HeightFieldShape::GetPosition(uint inX, uint inY, float inBlockOffset, float inBlockScale, bool &outNoCollision) const836{837// Get quantized value838uint8 height_sample = GetHeightSample(inX, inY);839outNoCollision = height_sample == mSampleMask;840841// Add 0.5 to the quantized value to minimize the error (see constructor)842return mOffset + mScale * Vec3(float(inX), inBlockOffset + (0.5f + height_sample) * inBlockScale, float(inY));843}844845Vec3 HeightFieldShape::GetPosition(uint inX, uint inY) const846{847// Test if there are any samples848if (mHeightSamplesSize == 0)849return mOffset + mScale * Vec3(float(inX), 0.0f, float(inY));850851// Get block location852uint bx = inX / mBlockSize;853uint by = inY / mBlockSize;854855// Calculate offset and stride856uint num_blocks = GetNumBlocks();857uint range_block_offset, range_block_stride;858sGetRangeBlockOffsetAndStride(num_blocks, sGetMaxLevel(num_blocks), range_block_offset, range_block_stride);859860float offset, scale;861GetBlockOffsetAndScale(bx, by, range_block_offset, range_block_stride, offset, scale);862863bool no_collision;864return GetPosition(inX, inY, offset, scale, no_collision);865}866867bool HeightFieldShape::IsNoCollision(uint inX, uint inY) const868{869return mHeightSamplesSize == 0 || GetHeightSample(inX, inY) == mSampleMask;870}871872bool HeightFieldShape::ProjectOntoSurface(Vec3Arg inLocalPosition, Vec3 &outSurfacePosition, SubShapeID &outSubShapeID) const873{874// Check if we have collision875if (mHeightSamplesSize == 0)876return false;877878// Convert coordinate to integer space879Vec3 integer_space = (inLocalPosition - mOffset) / mScale;880881// Get x coordinate and fraction882float x_frac = integer_space.GetX();883if (x_frac < 0.0f || x_frac >= mSampleCount - 1)884return false;885uint x = (uint)floor(x_frac);886x_frac -= x;887888// Get y coordinate and fraction889float y_frac = integer_space.GetZ();890if (y_frac < 0.0f || y_frac >= mSampleCount - 1)891return false;892uint y = (uint)floor(y_frac);893y_frac -= y;894895// If one of the diagonal points doesn't have collision, we don't have a height at this location896if (IsNoCollision(x, y) || IsNoCollision(x + 1, y + 1))897return false;898899if (y_frac >= x_frac)900{901// Left bottom triangle, test the 3rd point902if (IsNoCollision(x, y + 1))903return false;904905// Interpolate height value906Vec3 v1 = GetPosition(x, y);907Vec3 v2 = GetPosition(x, y + 1);908Vec3 v3 = GetPosition(x + 1, y + 1);909outSurfacePosition = v1 + y_frac * (v2 - v1) + x_frac * (v3 - v2);910SubShapeIDCreator creator;911outSubShapeID = EncodeSubShapeID(creator, x, y, 0);912return true;913}914else915{916// Right top triangle, test the third point917if (IsNoCollision(x + 1, y))918return false;919920// Interpolate height value921Vec3 v1 = GetPosition(x, y);922Vec3 v2 = GetPosition(x + 1, y + 1);923Vec3 v3 = GetPosition(x + 1, y);924outSurfacePosition = v1 + y_frac * (v2 - v3) + x_frac * (v3 - v1);925SubShapeIDCreator creator;926outSubShapeID = EncodeSubShapeID(creator, x, y, 1);927return true;928}929}930931void HeightFieldShape::GetHeights(uint inX, uint inY, uint inSizeX, uint inSizeY, float *outHeights, intptr_t inHeightsStride) const932{933if (inSizeX == 0 || inSizeY == 0)934return;935936JPH_ASSERT(inX % mBlockSize == 0 && inY % mBlockSize == 0);937JPH_ASSERT(inX < mSampleCount && inY < mSampleCount);938JPH_ASSERT(inX + inSizeX <= mSampleCount && inY + inSizeY <= mSampleCount);939940// Test if there are any samples941if (mHeightSamplesSize == 0)942{943// No samples, return the offset944float offset = mOffset.GetY();945for (uint y = 0; y < inSizeY; ++y, outHeights += inHeightsStride)946for (uint x = 0; x < inSizeX; ++x)947outHeights[x] = offset;948}949else950{951// Calculate offset and stride952uint num_blocks = GetNumBlocks();953uint range_block_offset, range_block_stride;954sGetRangeBlockOffsetAndStride(num_blocks, sGetMaxLevel(num_blocks), range_block_offset, range_block_stride);955956// Loop over blocks957uint block_start_x = inX / mBlockSize;958uint block_start_y = inY / mBlockSize;959uint num_blocks_x = inSizeX / mBlockSize;960uint num_blocks_y = inSizeY / mBlockSize;961for (uint block_y = 0; block_y < num_blocks_y; ++block_y)962for (uint block_x = 0; block_x < num_blocks_x; ++block_x)963{964// Get offset and scale for block965float offset, scale;966GetBlockOffsetAndScale(block_start_x + block_x, block_start_y + block_y, range_block_offset, range_block_stride, offset, scale);967968// Adjust by global offset and scale969// Note: This is the math applied in GetPosition() written out to reduce calculations in the inner loop970scale *= mScale.GetY();971offset = mOffset.GetY() + mScale.GetY() * offset + 0.5f * scale;972973// Loop over samples in block974for (uint sample_y = 0; sample_y < mBlockSize; ++sample_y)975for (uint sample_x = 0; sample_x < mBlockSize; ++sample_x)976{977// Calculate output coordinate978uint output_x = block_x * mBlockSize + sample_x;979uint output_y = block_y * mBlockSize + sample_y;980981// Get quantized value982uint8 height_sample = GetHeightSample(inX + output_x, inY + output_y);983984// Dequantize985float h = height_sample != mSampleMask? offset + height_sample * scale : cNoCollisionValue;986outHeights[output_y * inHeightsStride + output_x] = h;987}988}989}990}991992void HeightFieldShape::SetHeights(uint inX, uint inY, uint inSizeX, uint inSizeY, const float *inHeights, intptr_t inHeightsStride, TempAllocator &inAllocator, float inActiveEdgeCosThresholdAngle)993{994if (inSizeX == 0 || inSizeY == 0)995return;996997JPH_ASSERT(mHeightSamplesSize > 0);998JPH_ASSERT(inX % mBlockSize == 0 && inY % mBlockSize == 0);999JPH_ASSERT(inX < mSampleCount && inY < mSampleCount);1000JPH_ASSERT(inX + inSizeX <= mSampleCount && inY + inSizeY <= mSampleCount);10011002// If we have a block in negative x/y direction, we will affect its range so we need to take it into account1003bool need_temp_heights = false;1004uint affected_x = inX;1005uint affected_y = inY;1006uint affected_size_x = inSizeX;1007uint affected_size_y = inSizeY;1008if (inX > 0) { affected_x -= mBlockSize; affected_size_x += mBlockSize; need_temp_heights = true; }1009if (inY > 0) { affected_y -= mBlockSize; affected_size_y += mBlockSize; need_temp_heights = true; }10101011// If we have a block in positive x/y direction, our ranges are affected by it so we need to take it into account1012uint heights_size_x = affected_size_x;1013uint heights_size_y = affected_size_y;1014if (inX + inSizeX < mSampleCount) { heights_size_x += mBlockSize; need_temp_heights = true; }1015if (inY + inSizeY < mSampleCount) { heights_size_y += mBlockSize; need_temp_heights = true; }10161017// Get heights for affected area1018const float *heights;1019intptr_t heights_stride;1020float *temp_heights;1021if (need_temp_heights)1022{1023// Fetch the surrounding height data (note we're forced to recompress this data with a potentially different range so there will be some precision loss here)1024temp_heights = (float *)inAllocator.Allocate(heights_size_x * heights_size_y * sizeof(float));1025heights = temp_heights;1026heights_stride = heights_size_x;10271028// We need to fill in the following areas:1029//1030// +-----------------+1031// | 2 |1032// |---+---------+---|1033// | | | |1034// | 3 | 1 | 4 |1035// | | | |1036// |---+---------+---|1037// | 5 |1038// +-----------------+1039//1040// 1. The area that is affected by the new heights (we just copy these)1041// 2-5. These areas are either needed to calculate the range of the affected blocks or they need to be recompressed with a different range1042uint offset_x = inX - affected_x;1043uint offset_y = inY - affected_y;10441045// Area 21046GetHeights(affected_x, affected_y, heights_size_x, offset_y, temp_heights, heights_size_x);1047float *area3_start = temp_heights + offset_y * heights_size_x;10481049// Area 31050GetHeights(affected_x, inY, offset_x, inSizeY, area3_start, heights_size_x);10511052// Area 11053float *area1_start = area3_start + offset_x;1054for (uint y = 0; y < inSizeY; ++y, area1_start += heights_size_x, inHeights += inHeightsStride)1055memcpy(area1_start, inHeights, inSizeX * sizeof(float));10561057// Area 41058uint area4_x = inX + inSizeX;1059GetHeights(area4_x, inY, affected_x + heights_size_x - area4_x, inSizeY, area3_start + area4_x - affected_x, heights_size_x);10601061// Area 51062uint area5_y = inY + inSizeY;1063float *area5_start = temp_heights + (area5_y - affected_y) * heights_size_x;1064GetHeights(affected_x, area5_y, heights_size_x, affected_y + heights_size_y - area5_y, area5_start, heights_size_x);1065}1066else1067{1068// We can directly use the input buffer because there are no extra edges to take into account1069heights = inHeights;1070heights_stride = inHeightsStride;1071temp_heights = nullptr;1072}10731074// Calculate offset and stride1075uint num_blocks = GetNumBlocks();1076uint range_block_offset, range_block_stride;1077uint max_level = sGetMaxLevel(num_blocks);1078sGetRangeBlockOffsetAndStride(num_blocks, max_level, range_block_offset, range_block_stride);10791080// Loop over blocks1081uint block_start_x = affected_x / mBlockSize;1082uint block_start_y = affected_y / mBlockSize;1083uint num_blocks_x = affected_size_x / mBlockSize;1084uint num_blocks_y = affected_size_y / mBlockSize;1085for (uint block_y = 0, sample_start_y = 0; block_y < num_blocks_y; ++block_y, sample_start_y += mBlockSize)1086for (uint block_x = 0, sample_start_x = 0; block_x < num_blocks_x; ++block_x, sample_start_x += mBlockSize)1087{1088// Determine quantized min and max value for block1089// Note that we need to include 1 extra row in the positive x/y direction to account for connecting triangles1090int min_value = 0xffff;1091int max_value = 0;1092uint sample_x_end = min(sample_start_x + mBlockSize + 1, mSampleCount - affected_x);1093uint sample_y_end = min(sample_start_y + mBlockSize + 1, mSampleCount - affected_y);1094for (uint sample_y = sample_start_y; sample_y < sample_y_end; ++sample_y)1095for (uint sample_x = sample_start_x; sample_x < sample_x_end; ++sample_x)1096{1097float h = heights[sample_y * heights_stride + sample_x];1098if (h != cNoCollisionValue)1099{1100int quantized_height = Clamp((int)floor((h - mOffset.GetY()) / mScale.GetY()), 0, int(cMaxHeightValue16 - 1));1101min_value = min(min_value, quantized_height);1102max_value = max(max_value, quantized_height + 1);1103}1104}1105if (min_value > max_value)1106min_value = max_value = cNoCollisionValue16;11071108// Update range for block1109RangeBlock *range_block;1110uint index_in_block;1111GetRangeBlock(block_start_x + block_x, block_start_y + block_y, range_block_offset, range_block_stride, range_block, index_in_block);1112range_block->mMin[index_in_block] = uint16(min_value);1113range_block->mMax[index_in_block] = uint16(max_value);11141115// Get offset and scale for block1116float offset_block = float(min_value);1117float scale_block = float(max_value - min_value) / float(mSampleMask);11181119// Calculate scale and offset using the formula used in GetPosition() solved for the quantized height (excluding 0.5 because we round down while quantizing)1120float scale = scale_block * mScale.GetY();1121float offset = mOffset.GetY() + offset_block * mScale.GetY();11221123// Loop over samples in block1124sample_x_end = sample_start_x + mBlockSize;1125sample_y_end = sample_start_y + mBlockSize;1126for (uint sample_y = sample_start_y; sample_y < sample_y_end; ++sample_y)1127for (uint sample_x = sample_start_x; sample_x < sample_x_end; ++sample_x)1128{1129// Quantize height1130float h = heights[sample_y * heights_stride + sample_x];1131uint8 quantized_height = h != cNoCollisionValue? uint8(Clamp((int)floor((h - offset) / scale), 0, int(mSampleMask) - 1)) : mSampleMask;11321133// Determine bit position of sample1134uint sample = ((affected_y + sample_y) * mSampleCount + affected_x + sample_x) * uint(mBitsPerSample);1135uint byte_pos = sample >> 3;1136uint bit_pos = sample & 0b111;11371138// Update the height value sample1139JPH_ASSERT(byte_pos + 1 < mHeightSamplesSize);1140uint8 *height_samples = mHeightSamples + byte_pos;1141uint16 height_sample = uint16(height_samples[0]) | uint16(uint16(height_samples[1]) << 8);1142height_sample &= ~(uint16(mSampleMask) << bit_pos);1143height_sample |= uint16(quantized_height) << bit_pos;1144height_samples[0] = uint8(height_sample);1145height_samples[1] = uint8(height_sample >> 8);1146}1147}11481149// Update active edges1150// Note that we must take an extra row on all sides to account for connecting triangles1151uint ae_x = inX > 1? inX - 2 : 0;1152uint ae_y = inY > 1? inY - 2 : 0;1153uint ae_sx = min(inX + inSizeX + 1, mSampleCount - 1) - ae_x;1154uint ae_sy = min(inY + inSizeY + 1, mSampleCount - 1) - ae_y;1155CalculateActiveEdges(ae_x, ae_y, ae_sx, ae_sy, heights, affected_x, affected_y, heights_stride, 1.0f, inActiveEdgeCosThresholdAngle, inAllocator);11561157// Free temporary buffer1158if (temp_heights != nullptr)1159inAllocator.Free(temp_heights, heights_size_x * heights_size_y * sizeof(float));11601161// Update hierarchy of range blocks1162while (max_level > 1)1163{1164// Get offset and stride for destination blocks1165uint dst_range_block_offset, dst_range_block_stride;1166sGetRangeBlockOffsetAndStride(num_blocks >> 1, max_level - 1, dst_range_block_offset, dst_range_block_stride);11671168// We'll be processing 2x2 blocks below so we need the start coordinates to be even and we extend the number of blocks to correct for that1169if (block_start_x & 1) { --block_start_x; ++num_blocks_x; }1170if (block_start_y & 1) { --block_start_y; ++num_blocks_y; }11711172// Loop over all affected blocks1173uint block_end_x = block_start_x + num_blocks_x;1174uint block_end_y = block_start_y + num_blocks_y;1175for (uint block_y = block_start_y; block_y < block_end_y; block_y += 2)1176for (uint block_x = block_start_x; block_x < block_end_x; block_x += 2)1177{1178// Get source range block1179RangeBlock *src_range_block;1180uint index_in_src_block;1181GetRangeBlock(block_x, block_y, range_block_offset, range_block_stride, src_range_block, index_in_src_block);11821183// Determine quantized min and max value for the entire 2x2 block1184uint16 min_value = 0xffff;1185uint16 max_value = 0;1186for (uint i = 0; i < 4; ++i)1187if (src_range_block->mMin[i] != cNoCollisionValue16)1188{1189min_value = min(min_value, src_range_block->mMin[i]);1190max_value = max(max_value, src_range_block->mMax[i]);1191}11921193// Write to destination block1194RangeBlock *dst_range_block;1195uint index_in_dst_block;1196GetRangeBlock(block_x >> 1, block_y >> 1, dst_range_block_offset, dst_range_block_stride, dst_range_block, index_in_dst_block);1197dst_range_block->mMin[index_in_dst_block] = uint16(min_value);1198dst_range_block->mMax[index_in_dst_block] = uint16(max_value);1199}12001201// Go up one level1202--max_level;1203num_blocks >>= 1;1204block_start_x >>= 1;1205block_start_y >>= 1;1206num_blocks_x = min((num_blocks_x + 1) >> 1, num_blocks);1207num_blocks_y = min((num_blocks_y + 1) >> 1, num_blocks);12081209// Update stride and offset for source to old destination1210range_block_offset = dst_range_block_offset;1211range_block_stride = dst_range_block_stride;1212}12131214// Calculate new min and max sample for the entire height field1215mMinSample = 0xffff;1216mMaxSample = 0;1217for (uint i = 0; i < 4; ++i)1218if (mRangeBlocks[0].mMin[i] != cNoCollisionValue16)1219{1220mMinSample = min(mMinSample, mRangeBlocks[0].mMin[i]);1221mMaxSample = max(mMaxSample, mRangeBlocks[0].mMax[i]);1222}12231224#ifdef JPH_DEBUG_RENDERER1225// Invalidate temporary rendering data1226mGeometry.clear();1227#endif1228}12291230void HeightFieldShape::GetMaterials(uint inX, uint inY, uint inSizeX, uint inSizeY, uint8 *outMaterials, intptr_t inMaterialsStride) const1231{1232if (inSizeX == 0 || inSizeY == 0)1233return;12341235if (mMaterialIndices.empty())1236{1237// Return all 0's1238for (uint y = 0; y < inSizeY; ++y)1239{1240uint8 *out_indices = outMaterials + y * inMaterialsStride;1241for (uint x = 0; x < inSizeX; ++x)1242*out_indices++ = 0;1243}1244return;1245}12461247JPH_ASSERT(inX < mSampleCount && inY < mSampleCount);1248JPH_ASSERT(inX + inSizeX < mSampleCount && inY + inSizeY < mSampleCount);12491250uint count_min_1 = mSampleCount - 1;1251uint16 material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1);12521253for (uint y = 0; y < inSizeY; ++y)1254{1255// Calculate input position1256uint bit_pos = (inX + (inY + y) * count_min_1) * mNumBitsPerMaterialIndex;1257const uint8 *in_indices = mMaterialIndices.data() + (bit_pos >> 3);1258bit_pos &= 0b111;12591260// Calculate output position1261uint8 *out_indices = outMaterials + y * inMaterialsStride;12621263for (uint x = 0; x < inSizeX; ++x)1264{1265// Get material index1266uint16 material_index = uint16(in_indices[0]) + uint16(uint16(in_indices[1]) << 8);1267material_index >>= bit_pos;1268material_index &= material_index_mask;1269*out_indices = uint8(material_index);12701271// Go to the next index1272bit_pos += mNumBitsPerMaterialIndex;1273in_indices += bit_pos >> 3;1274bit_pos &= 0b111;1275++out_indices;1276}1277}1278}12791280bool HeightFieldShape::SetMaterials(uint inX, uint inY, uint inSizeX, uint inSizeY, const uint8 *inMaterials, intptr_t inMaterialsStride, const PhysicsMaterialList *inMaterialList, TempAllocator &inAllocator)1281{1282if (inSizeX == 0 || inSizeY == 0)1283return true;12841285JPH_ASSERT(inX < mSampleCount && inY < mSampleCount);1286JPH_ASSERT(inX + inSizeX < mSampleCount && inY + inSizeY < mSampleCount);12871288// Remap materials1289uint material_remap_table_size = uint(inMaterialList != nullptr? inMaterialList->size() : mMaterials.size());1290uint8 *material_remap_table = (uint8 *)inAllocator.Allocate(material_remap_table_size);1291JPH_SCOPE_EXIT([&inAllocator, material_remap_table, material_remap_table_size]{ inAllocator.Free(material_remap_table, material_remap_table_size); });1292if (inMaterialList != nullptr)1293{1294// Conservatively reserve more space if the incoming material list is bigger1295if (inMaterialList->size() > mMaterials.size())1296mMaterials.reserve(inMaterialList->size());12971298// Create a remap table1299uint8 *remap_entry = material_remap_table;1300for (const PhysicsMaterial *material : *inMaterialList)1301{1302// Try to find it in the existing list1303PhysicsMaterialList::const_iterator it = std::find(mMaterials.begin(), mMaterials.end(), material);1304if (it != mMaterials.end())1305{1306// Found it, calculate index1307*remap_entry = uint8(it - mMaterials.begin());1308}1309else1310{1311// Not found, add it1312if (mMaterials.size() >= 256)1313{1314// We can't have more than 256 materials since we use uint8 as indices1315return false;1316}1317*remap_entry = uint8(mMaterials.size());1318mMaterials.push_back(material);1319}1320++remap_entry;1321}1322}1323else1324{1325// No remapping1326for (uint i = 0; i < material_remap_table_size; ++i)1327material_remap_table[i] = uint8(i);1328}13291330if (mMaterials.size() == 1)1331{1332// Only 1 material, we don't need to store the material indices1333return true;1334}13351336// Check if we need to resize the material indices array1337uint count_min_1 = mSampleCount - 1;1338uint32 new_bits_per_material_index = 32 - CountLeadingZeros((uint32)mMaterials.size() - 1);1339JPH_ASSERT(mNumBitsPerMaterialIndex <= 8 && new_bits_per_material_index <= 8);1340if (new_bits_per_material_index > mNumBitsPerMaterialIndex)1341{1342// Resize the material indices array1343mMaterialIndices.resize(((Square(count_min_1) * new_bits_per_material_index + 7) >> 3) + 1, 0); // Add 1 byte so we don't read out of bounds when reading an uint1613441345// Calculate old and new mask1346uint16 old_material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1);1347uint16 new_material_index_mask = uint16((1 << new_bits_per_material_index) - 1);13481349// Loop through the array backwards to avoid overwriting data1350int in_bit_pos = (count_min_1 * count_min_1 - 1) * mNumBitsPerMaterialIndex;1351const uint8 *in_indices = mMaterialIndices.data() + (in_bit_pos >> 3);1352in_bit_pos &= 0b111;1353int out_bit_pos = (count_min_1 * count_min_1 - 1) * new_bits_per_material_index;1354uint8 *out_indices = mMaterialIndices.data() + (out_bit_pos >> 3);1355out_bit_pos &= 0b111;13561357while (out_indices >= mMaterialIndices.data())1358{1359// Read the material index1360uint16 material_index = uint16(in_indices[0]) + uint16(uint16(in_indices[1]) << 8);1361material_index >>= in_bit_pos;1362material_index &= old_material_index_mask;13631364// Write the material index1365uint16 output_data = uint16(out_indices[0]) + uint16(uint16(out_indices[1]) << 8);1366output_data &= ~(new_material_index_mask << out_bit_pos);1367output_data |= material_index << out_bit_pos;1368out_indices[0] = uint8(output_data);1369out_indices[1] = uint8(output_data >> 8);13701371// Go to the previous index1372in_bit_pos -= int(mNumBitsPerMaterialIndex);1373in_indices += in_bit_pos >> 3;1374in_bit_pos &= 0b111;1375out_bit_pos -= int(new_bits_per_material_index);1376out_indices += out_bit_pos >> 3;1377out_bit_pos &= 0b111;1378}13791380// Accept the new bits per material index1381mNumBitsPerMaterialIndex = new_bits_per_material_index;1382}13831384uint16 material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1);1385for (uint y = 0; y < inSizeY; ++y)1386{1387// Calculate input position1388const uint8 *in_indices = inMaterials + y * inMaterialsStride;13891390// Calculate output position1391uint bit_pos = (inX + (inY + y) * count_min_1) * mNumBitsPerMaterialIndex;1392uint8 *out_indices = mMaterialIndices.data() + (bit_pos >> 3);1393bit_pos &= 0b111;13941395for (uint x = 0; x < inSizeX; ++x)1396{1397// Update material1398uint16 output_data = uint16(out_indices[0]) + uint16(uint16(out_indices[1]) << 8);1399output_data &= ~(material_index_mask << bit_pos);1400output_data |= material_remap_table[*in_indices] << bit_pos;1401out_indices[0] = uint8(output_data);1402out_indices[1] = uint8(output_data >> 8);14031404// Go to the next index1405in_indices++;1406bit_pos += mNumBitsPerMaterialIndex;1407out_indices += bit_pos >> 3;1408bit_pos &= 0b111;1409}1410}14111412return true;1413}14141415MassProperties HeightFieldShape::GetMassProperties() const1416{1417// Object should always be static, return default mass properties1418return MassProperties();1419}14201421const PhysicsMaterial *HeightFieldShape::GetMaterial(uint inX, uint inY) const1422{1423if (mMaterials.empty())1424return PhysicsMaterial::sDefault;1425if (mMaterials.size() == 1)1426return mMaterials[0];14271428uint count_min_1 = mSampleCount - 1;1429JPH_ASSERT(inX < count_min_1);1430JPH_ASSERT(inY < count_min_1);14311432// Calculate at which bit the material index starts1433uint bit_pos = (inX + inY * count_min_1) * mNumBitsPerMaterialIndex;1434uint byte_pos = bit_pos >> 3;1435bit_pos &= 0b111;14361437// Read the material index1438JPH_ASSERT(byte_pos + 1 < mMaterialIndices.size());1439const uint8 *material_indices = mMaterialIndices.data() + byte_pos;1440uint16 material_index = uint16(material_indices[0]) + uint16(uint16(material_indices[1]) << 8);1441material_index >>= bit_pos;1442material_index &= (1 << mNumBitsPerMaterialIndex) - 1;14431444// Return the material1445return mMaterials[material_index];1446}14471448uint HeightFieldShape::GetSubShapeIDBits() const1449{1450// Need to store X, Y and 1 extra bit to specify the triangle number in the quad1451return 2 * (32 - CountLeadingZeros(mSampleCount - 1)) + 1;1452}14531454SubShapeID HeightFieldShape::EncodeSubShapeID(const SubShapeIDCreator &inCreator, uint inX, uint inY, uint inTriangle) const1455{1456return inCreator.PushID((inX + inY * mSampleCount) * 2 + inTriangle, GetSubShapeIDBits()).GetID();1457}14581459void HeightFieldShape::DecodeSubShapeID(const SubShapeID &inSubShapeID, uint &outX, uint &outY, uint &outTriangle) const1460{1461// Decode sub shape id1462SubShapeID remainder;1463uint32 id = inSubShapeID.PopID(GetSubShapeIDBits(), remainder);1464JPH_ASSERT(remainder.IsEmpty(), "Invalid subshape ID");14651466// Get triangle index1467outTriangle = id & 1;1468id >>= 1;14691470// Fetch the x and y coordinate1471outX = id % mSampleCount;1472outY = id / mSampleCount;1473}14741475void HeightFieldShape::GetSubShapeCoordinates(const SubShapeID &inSubShapeID, uint &outX, uint &outY, uint &outTriangleIndex) const1476{1477DecodeSubShapeID(inSubShapeID, outX, outY, outTriangleIndex);1478}14791480const PhysicsMaterial *HeightFieldShape::GetMaterial(const SubShapeID &inSubShapeID) const1481{1482// Decode ID1483uint x, y, triangle;1484DecodeSubShapeID(inSubShapeID, x, y, triangle);14851486// Fetch the material1487return GetMaterial(x, y);1488}14891490Vec3 HeightFieldShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const1491{1492// Decode ID1493uint x, y, triangle;1494DecodeSubShapeID(inSubShapeID, x, y, triangle);14951496// Fetch vertices that both triangles share1497Vec3 x1y1 = GetPosition(x, y);1498Vec3 x2y2 = GetPosition(x + 1, y + 1);14991500// Get normal depending on which triangle was selected1501Vec3 normal;1502if (triangle == 0)1503{1504Vec3 x1y2 = GetPosition(x, y + 1);1505normal = (x2y2 - x1y2).Cross(x1y1 - x1y2);1506}1507else1508{1509Vec3 x2y1 = GetPosition(x + 1, y);1510normal = (x1y1 - x2y1).Cross(x2y2 - x2y1);1511}15121513return normal.Normalized();1514}15151516void HeightFieldShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const1517{1518// Decode ID1519uint x, y, triangle;1520DecodeSubShapeID(inSubShapeID, x, y, triangle);15211522// Fetch the triangle1523outVertices.resize(3);1524outVertices[0] = GetPosition(x, y);1525Vec3 v2 = GetPosition(x + 1, y + 1);1526if (triangle == 0)1527{1528outVertices[1] = GetPosition(x, y + 1);1529outVertices[2] = v2;1530}1531else1532{1533outVertices[1] = v2;1534outVertices[2] = GetPosition(x + 1, y);1535}15361537// Flip triangle if scaled inside out1538if (ScaleHelpers::IsInsideOut(inScale))1539std::swap(outVertices[1], outVertices[2]);15401541// Transform to world space1542Mat44 transform = inCenterOfMassTransform.PreScaled(inScale);1543for (Vec3 &v : outVertices)1544v = transform * v;1545}15461547inline uint8 HeightFieldShape::GetEdgeFlags(uint inX, uint inY, uint inTriangle) const1548{1549JPH_ASSERT(inX < mSampleCount - 1 && inY < mSampleCount - 1);15501551if (inTriangle == 0)1552{1553// The edge flags for this triangle are directly stored, find the right 3 bits1554uint bit_pos = 3 * (inX + inY * (mSampleCount - 1));1555uint byte_pos = bit_pos >> 3;1556bit_pos &= 0b111;1557JPH_ASSERT(byte_pos + 1 < mActiveEdgesSize);1558const uint8 *active_edges = mActiveEdges + byte_pos;1559uint16 edge_flags = uint16(active_edges[0]) + uint16(uint16(active_edges[1]) << 8);1560return uint8(edge_flags >> bit_pos) & 0b111;1561}1562else1563{1564// We don't store this triangle directly, we need to look at our three neighbours to construct the edge flags1565uint8 edge0 = (GetEdgeFlags(inX, inY, 0) & 0b100) != 0? 0b001 : 0; // Diagonal edge1566uint8 edge1 = inX == mSampleCount - 2 || (GetEdgeFlags(inX + 1, inY, 0) & 0b001) != 0? 0b010 : 0; // Vertical edge1567uint8 edge2 = inY == 0 || (GetEdgeFlags(inX, inY - 1, 0) & 0b010) != 0? 0b100 : 0; // Horizontal edge1568return edge0 | edge1 | edge2;1569}1570}15711572AABox HeightFieldShape::GetLocalBounds() const1573{1574if (mMinSample == cNoCollisionValue16)1575{1576// This whole height field shape doesn't have any collision, return the center point1577Vec3 center = mOffset + 0.5f * mScale * Vec3(float(mSampleCount - 1), 0.0f, float(mSampleCount - 1));1578return AABox(center, center);1579}1580else1581{1582// Bounding box based on min and max sample height1583Vec3 bmin = mOffset + mScale * Vec3(0.0f, float(mMinSample), 0.0f);1584Vec3 bmax = mOffset + mScale * Vec3(float(mSampleCount - 1), float(mMaxSample), float(mSampleCount - 1));1585return AABox(bmin, bmax);1586}1587}15881589#ifdef JPH_DEBUG_RENDERER1590void HeightFieldShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const1591{1592// Don't draw anything if we don't have any collision1593if (mHeightSamplesSize == 0)1594return;15951596// Reset the batch if we switch coloring mode1597if (mCachedUseMaterialColors != inUseMaterialColors)1598{1599mGeometry.clear();1600mCachedUseMaterialColors = inUseMaterialColors;1601}16021603if (mGeometry.empty())1604{1605// Divide terrain in triangle batches of max 64x64x2 triangles to allow better culling of the terrain1606uint32 block_size = min<uint32>(mSampleCount, 64);1607for (uint32 by = 0; by < mSampleCount; by += block_size)1608for (uint32 bx = 0; bx < mSampleCount; bx += block_size)1609{1610// Create vertices for a block1611Array<DebugRenderer::Triangle> triangles;1612triangles.resize(block_size * block_size * 2);1613DebugRenderer::Triangle *out_tri = &triangles[0];1614for (uint32 y = by, max_y = min(by + block_size, mSampleCount - 1); y < max_y; ++y)1615for (uint32 x = bx, max_x = min(bx + block_size, mSampleCount - 1); x < max_x; ++x)1616if (!IsNoCollision(x, y) && !IsNoCollision(x + 1, y + 1))1617{1618Vec3 x1y1 = GetPosition(x, y);1619Vec3 x2y2 = GetPosition(x + 1, y + 1);1620Color color = inUseMaterialColors? GetMaterial(x, y)->GetDebugColor() : Color::sWhite;16211622if (!IsNoCollision(x, y + 1))1623{1624Vec3 x1y2 = GetPosition(x, y + 1);16251626x1y1.StoreFloat3(&out_tri->mV[0].mPosition);1627x1y2.StoreFloat3(&out_tri->mV[1].mPosition);1628x2y2.StoreFloat3(&out_tri->mV[2].mPosition);16291630Vec3 normal = (x2y2 - x1y2).Cross(x1y1 - x1y2).Normalized();1631for (DebugRenderer::Vertex &v : out_tri->mV)1632{1633v.mColor = color;1634v.mUV = Float2(0, 0);1635normal.StoreFloat3(&v.mNormal);1636}16371638++out_tri;1639}16401641if (!IsNoCollision(x + 1, y))1642{1643Vec3 x2y1 = GetPosition(x + 1, y);16441645x1y1.StoreFloat3(&out_tri->mV[0].mPosition);1646x2y2.StoreFloat3(&out_tri->mV[1].mPosition);1647x2y1.StoreFloat3(&out_tri->mV[2].mPosition);16481649Vec3 normal = (x1y1 - x2y1).Cross(x2y2 - x2y1).Normalized();1650for (DebugRenderer::Vertex &v : out_tri->mV)1651{1652v.mColor = color;1653v.mUV = Float2(0, 0);1654normal.StoreFloat3(&v.mNormal);1655}16561657++out_tri;1658}1659}16601661// Resize triangles array to actual amount of triangles written1662size_t num_triangles = out_tri - &triangles[0];1663triangles.resize(num_triangles);16641665// Create batch1666if (num_triangles > 0)1667mGeometry.push_back(new DebugRenderer::Geometry(inRenderer->CreateTriangleBatch(triangles), DebugRenderer::sCalculateBounds(&triangles[0].mV[0], int(3 * num_triangles))));1668}1669}16701671// Get transform including scale1672RMat44 transform = inCenterOfMassTransform.PreScaled(inScale);16731674// Test if the shape is scaled inside out1675DebugRenderer::ECullMode cull_mode = ScaleHelpers::IsInsideOut(inScale)? DebugRenderer::ECullMode::CullFrontFace : DebugRenderer::ECullMode::CullBackFace;16761677// Determine the draw mode1678DebugRenderer::EDrawMode draw_mode = inDrawWireframe? DebugRenderer::EDrawMode::Wireframe : DebugRenderer::EDrawMode::Solid;16791680// Draw the geometry1681for (const DebugRenderer::GeometryRef &b : mGeometry)1682inRenderer->DrawGeometry(transform, inColor, b, cull_mode, DebugRenderer::ECastShadow::On, draw_mode);16831684if (sDrawTriangleOutlines)1685{1686struct Visitor1687{1688JPH_INLINE explicit Visitor(const HeightFieldShape *inShape, DebugRenderer *inRenderer, RMat44Arg inTransform) :1689mShape(inShape),1690mRenderer(inRenderer),1691mTransform(inTransform)1692{1693}16941695JPH_INLINE bool ShouldAbort() const1696{1697return false;1698}16991700JPH_INLINE bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const1701{1702return true;1703}17041705JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const1706{1707UVec4 valid = Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY);1708return CountAndSortTrues(valid, ioProperties);1709}17101711JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2) const1712{1713// Determine active edges1714uint8 active_edges = mShape->GetEdgeFlags(inX, inY, inTriangle);17151716// Loop through edges1717Vec3 v[] = { inV0, inV1, inV2 };1718for (uint edge_idx = 0; edge_idx < 3; ++edge_idx)1719{1720RVec3 v1 = mTransform * v[edge_idx];1721RVec3 v2 = mTransform * v[(edge_idx + 1) % 3];17221723// Draw active edge as a green arrow, other edges as grey1724if (active_edges & (1 << edge_idx))1725mRenderer->DrawArrow(v1, v2, Color::sGreen, 0.01f);1726else1727mRenderer->DrawLine(v1, v2, Color::sGrey);1728}1729}17301731const HeightFieldShape *mShape;1732DebugRenderer * mRenderer;1733RMat44 mTransform;1734};17351736Visitor visitor(this, inRenderer, inCenterOfMassTransform.PreScaled(inScale));1737WalkHeightField(visitor);1738}1739}1740#endif // JPH_DEBUG_RENDERER17411742class HeightFieldShape::DecodingContext1743{1744public:1745JPH_INLINE explicit DecodingContext(const HeightFieldShape *inShape) :1746mShape(inShape)1747{1748static_assert(std::size(sGridOffsets) == cNumBitsXY + 1, "Offsets array is not long enough");17491750// Construct root stack entry1751mPropertiesStack[0] = 0; // level: 0, x: 0, y: 01752}17531754template <class Visitor>1755JPH_INLINE void WalkHeightField(Visitor &ioVisitor)1756{1757// Early out if there's no collision1758if (mShape->mHeightSamplesSize == 0)1759return;17601761// Assert that an inside-out bounding box does not collide1762JPH_IF_ENABLE_ASSERTS(UVec4 dummy = UVec4::sReplicate(0);)1763JPH_ASSERT(ioVisitor.VisitRangeBlock(Vec4::sReplicate(-1.0e6f), Vec4::sReplicate(1.0e6f), Vec4::sReplicate(-1.0e6f), Vec4::sReplicate(1.0e6f), Vec4::sReplicate(-1.0e6f), Vec4::sReplicate(1.0e6f), dummy, 0) == 0);17641765// Precalculate values relating to sample count1766uint32 sample_count = mShape->mSampleCount;1767UVec4 sample_count_min_1 = UVec4::sReplicate(sample_count - 1);17681769// Precalculate values relating to block size1770uint32 block_size = mShape->mBlockSize;1771uint32 block_size_plus_1 = block_size + 1;1772uint num_blocks = mShape->GetNumBlocks();1773uint num_blocks_min_1 = num_blocks - 1;1774uint max_level = HeightFieldShape::sGetMaxLevel(num_blocks);1775uint32 max_stride = (num_blocks + 1) >> 1;17761777// Precalculate range block offset and stride for GetBlockOffsetAndScale1778uint range_block_offset, range_block_stride;1779sGetRangeBlockOffsetAndStride(num_blocks, max_level, range_block_offset, range_block_stride);17801781// Allocate space for vertices and 'no collision' flags1782int array_size = Square(block_size_plus_1);1783Vec3 *vertices = reinterpret_cast<Vec3 *>(JPH_STACK_ALLOC(array_size * sizeof(Vec3)));1784bool *no_collision = reinterpret_cast<bool *>(JPH_STACK_ALLOC(array_size * sizeof(bool)));17851786// Splat offsets1787Vec4 ox = mShape->mOffset.SplatX();1788Vec4 oy = mShape->mOffset.SplatY();1789Vec4 oz = mShape->mOffset.SplatZ();17901791// Splat scales1792Vec4 sx = mShape->mScale.SplatX();1793Vec4 sy = mShape->mScale.SplatY();1794Vec4 sz = mShape->mScale.SplatZ();17951796do1797{1798// Decode properties1799uint32 properties_top = mPropertiesStack[mTop];1800uint32 x = properties_top & cMaskBitsXY;1801uint32 y = (properties_top >> cNumBitsXY) & cMaskBitsXY;1802uint32 level = properties_top >> cLevelShift;18031804if (level >= max_level)1805{1806// Determine actual range of samples (minus one because we eventually want to iterate over the triangles, not the samples)1807uint32 min_x = x * block_size;1808uint32 max_x = min_x + block_size;1809uint32 min_y = y * block_size;1810uint32 max_y = min_y + block_size;18111812// Decompress vertices of block at (x, y)1813Vec3 *dst_vertex = vertices;1814bool *dst_no_collision = no_collision;1815float block_offset, block_scale;1816mShape->GetBlockOffsetAndScale(x, y, range_block_offset, range_block_stride, block_offset, block_scale);1817for (uint32 v_y = min_y; v_y < max_y; ++v_y)1818{1819for (uint32 v_x = min_x; v_x < max_x; ++v_x)1820{1821*dst_vertex = mShape->GetPosition(v_x, v_y, block_offset, block_scale, *dst_no_collision);1822++dst_vertex;1823++dst_no_collision;1824}18251826// Skip last column, these values come from a different block1827++dst_vertex;1828++dst_no_collision;1829}18301831// Decompress block (x + 1, y)1832uint32 max_x_decrement = 0;1833if (x < num_blocks_min_1)1834{1835dst_vertex = vertices + block_size;1836dst_no_collision = no_collision + block_size;1837mShape->GetBlockOffsetAndScale(x + 1, y, range_block_offset, range_block_stride, block_offset, block_scale);1838for (uint32 v_y = min_y; v_y < max_y; ++v_y)1839{1840*dst_vertex = mShape->GetPosition(max_x, v_y, block_offset, block_scale, *dst_no_collision);1841dst_vertex += block_size_plus_1;1842dst_no_collision += block_size_plus_1;1843}1844}1845else1846max_x_decrement = 1; // We don't have a next block, one less triangle to test18471848// Decompress block (x, y + 1)1849if (y < num_blocks_min_1)1850{1851uint start = block_size * block_size_plus_1;1852dst_vertex = vertices + start;1853dst_no_collision = no_collision + start;1854mShape->GetBlockOffsetAndScale(x, y + 1, range_block_offset, range_block_stride, block_offset, block_scale);1855for (uint32 v_x = min_x; v_x < max_x; ++v_x)1856{1857*dst_vertex = mShape->GetPosition(v_x, max_y, block_offset, block_scale, *dst_no_collision);1858++dst_vertex;1859++dst_no_collision;1860}18611862// Decompress single sample of block at (x + 1, y + 1)1863if (x < num_blocks_min_1)1864{1865mShape->GetBlockOffsetAndScale(x + 1, y + 1, range_block_offset, range_block_stride, block_offset, block_scale);1866*dst_vertex = mShape->GetPosition(max_x, max_y, block_offset, block_scale, *dst_no_collision);1867}1868}1869else1870--max_y; // We don't have a next block, one less triangle to test18711872// Update max_x (we've been using it so we couldn't update it earlier)1873max_x -= max_x_decrement;18741875// We're going to divide the vertices in 4 blocks to do one more runtime sub-division, calculate the ranges of those blocks1876struct Range1877{1878uint32 mMinX, mMinY, mNumTrianglesX, mNumTrianglesY;1879};1880uint32 half_block_size = block_size >> 1;1881uint32 block_size_x = max_x - min_x - half_block_size;1882uint32 block_size_y = max_y - min_y - half_block_size;1883Range ranges[] =1884{1885{ 0, 0, half_block_size, half_block_size },1886{ half_block_size, 0, block_size_x, half_block_size },1887{ 0, half_block_size, half_block_size, block_size_y },1888{ half_block_size, half_block_size, block_size_x, block_size_y },1889};18901891// Calculate the min and max of each of the blocks1892Mat44 block_min, block_max;1893for (int block = 0; block < 4; ++block)1894{1895// Get the range for this block1896const Range &range = ranges[block];1897uint32 start = range.mMinX + range.mMinY * block_size_plus_1;1898uint32 size_x_plus_1 = range.mNumTrianglesX + 1;1899uint32 size_y_plus_1 = range.mNumTrianglesY + 1;19001901// Calculate where to start reading1902const Vec3 *src_vertex = vertices + start;1903const bool *src_no_collision = no_collision + start;1904uint32 stride = block_size_plus_1 - size_x_plus_1;19051906// Start range with a very large inside-out box1907Vec3 value_min = Vec3::sReplicate(cLargeFloat);1908Vec3 value_max = Vec3::sReplicate(-cLargeFloat);19091910// Loop over the samples to determine the min and max of this block1911for (uint32 block_y = 0; block_y < size_y_plus_1; ++block_y)1912{1913for (uint32 block_x = 0; block_x < size_x_plus_1; ++block_x)1914{1915if (!*src_no_collision)1916{1917value_min = Vec3::sMin(value_min, *src_vertex);1918value_max = Vec3::sMax(value_max, *src_vertex);1919}1920++src_vertex;1921++src_no_collision;1922}1923src_vertex += stride;1924src_no_collision += stride;1925}1926block_min.SetColumn4(block, Vec4(value_min));1927block_max.SetColumn4(block, Vec4(value_max));1928}19291930#ifdef JPH_DEBUG_HEIGHT_FIELD1931// Draw the bounding boxes of the sub-nodes1932for (int block = 0; block < 4; ++block)1933{1934AABox bounds(block_min.GetColumn3(block), block_max.GetColumn3(block));1935if (bounds.IsValid())1936DebugRenderer::sInstance->DrawWireBox(bounds, Color::sYellow);1937}1938#endif // JPH_DEBUG_HEIGHT_FIELD19391940// Transpose so we have the mins and maxes of each of the blocks in rows instead of columns1941Mat44 transposed_min = block_min.Transposed();1942Mat44 transposed_max = block_max.Transposed();19431944// Check which blocks collide1945// Note: At this point we don't use our own stack but we do allow the visitor to use its own stack1946// to store collision distances so that we can still early out when no closer hits have been found.1947UVec4 colliding_blocks(0, 1, 2, 3);1948int num_results = ioVisitor.VisitRangeBlock(transposed_min.GetColumn4(0), transposed_min.GetColumn4(1), transposed_min.GetColumn4(2), transposed_max.GetColumn4(0), transposed_max.GetColumn4(1), transposed_max.GetColumn4(2), colliding_blocks, mTop);19491950// Loop through the results backwards (closest first)1951int result = num_results - 1;1952while (result >= 0)1953{1954// Calculate the min and max of this block1955uint32 block = colliding_blocks[result];1956const Range &range = ranges[block];1957uint32 block_min_x = min_x + range.mMinX;1958uint32 block_max_x = block_min_x + range.mNumTrianglesX;1959uint32 block_min_y = min_y + range.mMinY;1960uint32 block_max_y = block_min_y + range.mNumTrianglesY;19611962// Loop triangles1963for (uint32 v_y = block_min_y; v_y < block_max_y; ++v_y)1964for (uint32 v_x = block_min_x; v_x < block_max_x; ++v_x)1965{1966// Get first vertex1967const int offset = (v_y - min_y) * block_size_plus_1 + (v_x - min_x);1968const Vec3 *start_vertex = vertices + offset;1969const bool *start_no_collision = no_collision + offset;19701971// Check if vertices shared by both triangles have collision1972if (!start_no_collision[0] && !start_no_collision[block_size_plus_1 + 1])1973{1974// Loop 2 triangles1975for (uint t = 0; t < 2; ++t)1976{1977// Determine triangle vertices1978Vec3 v0, v1, v2;1979if (t == 0)1980{1981// Check third vertex1982if (start_no_collision[block_size_plus_1])1983continue;19841985// Get vertices for triangle1986v0 = start_vertex[0];1987v1 = start_vertex[block_size_plus_1];1988v2 = start_vertex[block_size_plus_1 + 1];1989}1990else1991{1992// Check third vertex1993if (start_no_collision[1])1994continue;19951996// Get vertices for triangle1997v0 = start_vertex[0];1998v1 = start_vertex[block_size_plus_1 + 1];1999v2 = start_vertex[1];2000}20012002#ifdef JPH_DEBUG_HEIGHT_FIELD2003DebugRenderer::sInstance->DrawWireTriangle(RVec3(v0), RVec3(v1), RVec3(v2), Color::sWhite);2004#endif20052006// Call visitor2007ioVisitor.VisitTriangle(v_x, v_y, t, v0, v1, v2);20082009// Check if we're done2010if (ioVisitor.ShouldAbort())2011return;2012}2013}2014}20152016// Fetch next block until we find one that the visitor wants to see2017do2018--result;2019while (result >= 0 && !ioVisitor.ShouldVisitRangeBlock(mTop + result));2020}2021}2022else2023{2024// Visit child grid2025uint32 stride = min(1U << level, max_stride); // At the most detailed level we store a non-power of 2 number of blocks2026uint32 offset = sGridOffsets[level] + stride * y + x;20272028// Decode min/max height2029JPH_ASSERT(offset < mShape->mRangeBlocksSize);2030UVec4 block = UVec4::sLoadInt4Aligned(reinterpret_cast<const uint32 *>(&mShape->mRangeBlocks[offset]));2031Vec4 bounds_miny = oy + sy * block.Expand4Uint16Lo().ToFloat();2032Vec4 bounds_maxy = oy + sy * block.Expand4Uint16Hi().ToFloat();20332034// Calculate size of one cell at this grid level2035UVec4 internal_cell_size = UVec4::sReplicate(block_size << (max_level - level - 1)); // subtract 1 from level because we have an internal grid of 2x220362037// Calculate min/max x and z2038UVec4 two_x = UVec4::sReplicate(2 * x); // multiply by two because we have an internal grid of 2x22039Vec4 bounds_minx = ox + sx * (internal_cell_size * (two_x + UVec4(0, 1, 0, 1))).ToFloat();2040Vec4 bounds_maxx = ox + sx * UVec4::sMin(internal_cell_size * (two_x + UVec4(1, 2, 1, 2)), sample_count_min_1).ToFloat();20412042UVec4 two_y = UVec4::sReplicate(2 * y);2043Vec4 bounds_minz = oz + sz * (internal_cell_size * (two_y + UVec4(0, 0, 1, 1))).ToFloat();2044Vec4 bounds_maxz = oz + sz * UVec4::sMin(internal_cell_size * (two_y + UVec4(1, 1, 2, 2)), sample_count_min_1).ToFloat();20452046// Calculate properties of child blocks2047UVec4 properties = UVec4::sReplicate(((level + 1) << cLevelShift) + (y << (cNumBitsXY + 1)) + (x << 1)) + UVec4(0, 1, 1 << cNumBitsXY, (1 << cNumBitsXY) + 1);20482049#ifdef JPH_DEBUG_HEIGHT_FIELD2050// Draw boxes2051for (int i = 0; i < 4; ++i)2052{2053AABox b(Vec3(bounds_minx[i], bounds_miny[i], bounds_minz[i]), Vec3(bounds_maxx[i], bounds_maxy[i], bounds_maxz[i]));2054if (b.IsValid())2055DebugRenderer::sInstance->DrawWireBox(b, Color::sGreen);2056}2057#endif20582059// Check which sub nodes to visit2060int num_results = ioVisitor.VisitRangeBlock(bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz, properties, mTop);20612062// Push them onto the stack2063JPH_ASSERT(mTop + 4 < cStackSize);2064properties.StoreInt4(&mPropertiesStack[mTop]);2065mTop += num_results;2066}20672068// Check if we're done2069if (ioVisitor.ShouldAbort())2070return;20712072// Fetch next node until we find one that the visitor wants to see2073do2074--mTop;2075while (mTop >= 0 && !ioVisitor.ShouldVisitRangeBlock(mTop));2076}2077while (mTop >= 0);2078}20792080// This can be used to have the visitor early out (ioVisitor.ShouldAbort() returns true) and later continue again (call WalkHeightField() again)2081JPH_INLINE bool IsDoneWalking() const2082{2083return mTop < 0;2084}20852086private:2087const HeightFieldShape * mShape;2088int mTop = 0;2089uint32 mPropertiesStack[cStackSize];2090};20912092template <class Visitor>2093void HeightFieldShape::WalkHeightField(Visitor &ioVisitor) const2094{2095DecodingContext ctx(this);2096ctx.WalkHeightField(ioVisitor);2097}20982099bool HeightFieldShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const2100{2101JPH_PROFILE_FUNCTION();21022103struct Visitor2104{2105JPH_INLINE explicit Visitor(const HeightFieldShape *inShape, const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) :2106mHit(ioHit),2107mRayOrigin(inRay.mOrigin),2108mRayDirection(inRay.mDirection),2109mRayInvDirection(inRay.mDirection),2110mShape(inShape),2111mSubShapeIDCreator(inSubShapeIDCreator)2112{2113}21142115JPH_INLINE bool ShouldAbort() const2116{2117return mHit.mFraction <= 0.0f;2118}21192120JPH_INLINE bool ShouldVisitRangeBlock(int inStackTop) const2121{2122return mDistanceStack[inStackTop] < mHit.mFraction;2123}21242125JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)2126{2127// Test bounds of 4 children2128Vec4 distance = RayAABox4(mRayOrigin, mRayInvDirection, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);21292130// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)2131return SortReverseAndStore(distance, mHit.mFraction, ioProperties, &mDistanceStack[inStackTop]);2132}21332134JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)2135{2136float fraction = RayTriangle(mRayOrigin, mRayDirection, inV0, inV1, inV2);2137if (fraction < mHit.mFraction)2138{2139// It's a closer hit2140mHit.mFraction = fraction;2141mHit.mSubShapeID2 = mShape->EncodeSubShapeID(mSubShapeIDCreator, inX, inY, inTriangle);2142mReturnValue = true;2143}2144}21452146RayCastResult & mHit;2147Vec3 mRayOrigin;2148Vec3 mRayDirection;2149RayInvDirection mRayInvDirection;2150const HeightFieldShape *mShape;2151SubShapeIDCreator mSubShapeIDCreator;2152bool mReturnValue = false;2153float mDistanceStack[cStackSize];2154};21552156Visitor visitor(this, inRay, inSubShapeIDCreator, ioHit);2157WalkHeightField(visitor);21582159return visitor.mReturnValue;2160}21612162void HeightFieldShape::CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter) const2163{2164JPH_PROFILE_FUNCTION();21652166// Test shape filter2167if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))2168return;21692170struct Visitor2171{2172JPH_INLINE explicit Visitor(const HeightFieldShape *inShape, const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector) :2173mCollector(ioCollector),2174mRayOrigin(inRay.mOrigin),2175mRayDirection(inRay.mDirection),2176mRayInvDirection(inRay.mDirection),2177mBackFaceMode(inRayCastSettings.mBackFaceModeTriangles),2178mShape(inShape),2179mSubShapeIDCreator(inSubShapeIDCreator)2180{2181}21822183JPH_INLINE bool ShouldAbort() const2184{2185return mCollector.ShouldEarlyOut();2186}21872188JPH_INLINE bool ShouldVisitRangeBlock(int inStackTop) const2189{2190return mDistanceStack[inStackTop] < mCollector.GetEarlyOutFraction();2191}21922193JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)2194{2195// Test bounds of 4 children2196Vec4 distance = RayAABox4(mRayOrigin, mRayInvDirection, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);21972198// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)2199return SortReverseAndStore(distance, mCollector.GetEarlyOutFraction(), ioProperties, &mDistanceStack[inStackTop]);2200}22012202JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2) const2203{2204// Back facing check2205if (mBackFaceMode == EBackFaceMode::IgnoreBackFaces && (inV2 - inV0).Cross(inV1 - inV0).Dot(mRayDirection) < 0)2206return;22072208// Check the triangle2209float fraction = RayTriangle(mRayOrigin, mRayDirection, inV0, inV1, inV2);2210if (fraction < mCollector.GetEarlyOutFraction())2211{2212RayCastResult hit;2213hit.mBodyID = TransformedShape::sGetBodyID(mCollector.GetContext());2214hit.mFraction = fraction;2215hit.mSubShapeID2 = mShape->EncodeSubShapeID(mSubShapeIDCreator, inX, inY, inTriangle);2216mCollector.AddHit(hit);2217}2218}22192220CastRayCollector & mCollector;2221Vec3 mRayOrigin;2222Vec3 mRayDirection;2223RayInvDirection mRayInvDirection;2224EBackFaceMode mBackFaceMode;2225const HeightFieldShape *mShape;2226SubShapeIDCreator mSubShapeIDCreator;2227float mDistanceStack[cStackSize];2228};22292230Visitor visitor(this, inRay, inRayCastSettings, inSubShapeIDCreator, ioCollector);2231WalkHeightField(visitor);2232}22332234void HeightFieldShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const2235{2236// A height field doesn't have volume, so we can't test insideness2237}22382239void HeightFieldShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const2240{2241JPH_PROFILE_FUNCTION();22422243struct Visitor : public CollideSoftBodyVerticesVsTriangles2244{2245using CollideSoftBodyVerticesVsTriangles::CollideSoftBodyVerticesVsTriangles;22462247JPH_INLINE bool ShouldAbort() const2248{2249return false;2250}22512252JPH_INLINE bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const2253{2254return mDistanceStack[inStackTop] < mClosestDistanceSq;2255}22562257JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)2258{2259// Get distance to vertex2260Vec4 dist_sq = AABox4DistanceSqToPoint(mLocalPosition, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);22612262// Clear distance for invalid bounds2263dist_sq = Vec4::sSelect(Vec4::sReplicate(FLT_MAX), dist_sq, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));22642265// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)2266return SortReverseAndStore(dist_sq, mClosestDistanceSq, ioProperties, &mDistanceStack[inStackTop]);2267}22682269JPH_INLINE void VisitTriangle([[maybe_unused]] uint inX, [[maybe_unused]] uint inY, [[maybe_unused]] uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)2270{2271ProcessTriangle(inV0, inV1, inV2);2272}22732274float mDistanceStack[cStackSize];2275};22762277Visitor visitor(inCenterOfMassTransform, inScale);22782279for (CollideSoftBodyVertexIterator v = inVertices, sbv_end = inVertices + inNumVertices; v != sbv_end; ++v)2280if (v.GetInvMass() > 0.0f)2281{2282visitor.StartVertex(v);2283WalkHeightField(visitor);2284visitor.FinishVertex(v, inCollidingShapeIndex);2285}2286}22872288void HeightFieldShape::sCastConvexVsHeightField(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector)2289{2290JPH_PROFILE_FUNCTION();22912292struct Visitor : public CastConvexVsTriangles2293{2294using CastConvexVsTriangles::CastConvexVsTriangles;22952296JPH_INLINE bool ShouldAbort() const2297{2298return mCollector.ShouldEarlyOut();2299}23002301JPH_INLINE bool ShouldVisitRangeBlock(int inStackTop) const2302{2303return mDistanceStack[inStackTop] < mCollector.GetPositiveEarlyOutFraction();2304}23052306JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)2307{2308// Scale the bounding boxes of this node2309Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;2310AABox4Scale(mScale, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);23112312// Enlarge them by the casted shape's box extents2313AABox4EnlargeWithExtent(mBoxExtent, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);23142315// Test bounds of 4 children2316Vec4 distance = RayAABox4(mBoxCenter, mInvDirection, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);23172318// Clear distance for invalid bounds2319distance = Vec4::sSelect(Vec4::sReplicate(FLT_MAX), distance, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));23202321// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)2322return SortReverseAndStore(distance, mCollector.GetPositiveEarlyOutFraction(), ioProperties, &mDistanceStack[inStackTop]);2323}23242325JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)2326{2327// Create sub shape id for this part2328SubShapeID triangle_sub_shape_id = mShape2->EncodeSubShapeID(mSubShapeIDCreator2, inX, inY, inTriangle);23292330// Determine active edges2331uint8 active_edges = mShape2->GetEdgeFlags(inX, inY, inTriangle);23322333Cast(inV0, inV1, inV2, active_edges, triangle_sub_shape_id);2334}23352336const HeightFieldShape * mShape2;2337RayInvDirection mInvDirection;2338Vec3 mBoxCenter;2339Vec3 mBoxExtent;2340SubShapeIDCreator mSubShapeIDCreator2;2341float mDistanceStack[cStackSize];2342};23432344JPH_ASSERT(inShape->GetSubType() == EShapeSubType::HeightField);2345const HeightFieldShape *shape = static_cast<const HeightFieldShape *>(inShape);23462347Visitor visitor(inShapeCast, inShapeCastSettings, inScale, inCenterOfMassTransform2, inSubShapeIDCreator1, ioCollector);2348visitor.mShape2 = shape;2349visitor.mInvDirection.Set(inShapeCast.mDirection);2350visitor.mBoxCenter = inShapeCast.mShapeWorldBounds.GetCenter();2351visitor.mBoxExtent = inShapeCast.mShapeWorldBounds.GetExtent();2352visitor.mSubShapeIDCreator2 = inSubShapeIDCreator2;2353shape->WalkHeightField(visitor);2354}23552356void HeightFieldShape::sCastSphereVsHeightField(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector)2357{2358JPH_PROFILE_FUNCTION();23592360struct Visitor : public CastSphereVsTriangles2361{2362using CastSphereVsTriangles::CastSphereVsTriangles;23632364JPH_INLINE bool ShouldAbort() const2365{2366return mCollector.ShouldEarlyOut();2367}23682369JPH_INLINE bool ShouldVisitRangeBlock(int inStackTop) const2370{2371return mDistanceStack[inStackTop] < mCollector.GetPositiveEarlyOutFraction();2372}23732374JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)2375{2376// Scale the bounding boxes of this node2377Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;2378AABox4Scale(mScale, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);23792380// Enlarge them by the radius of the sphere2381AABox4EnlargeWithExtent(Vec3::sReplicate(mRadius), bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);23822383// Test bounds of 4 children2384Vec4 distance = RayAABox4(mStart, mInvDirection, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);23852386// Clear distance for invalid bounds2387distance = Vec4::sSelect(Vec4::sReplicate(FLT_MAX), distance, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));23882389// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)2390return SortReverseAndStore(distance, mCollector.GetPositiveEarlyOutFraction(), ioProperties, &mDistanceStack[inStackTop]);2391}23922393JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)2394{2395// Create sub shape id for this part2396SubShapeID triangle_sub_shape_id = mShape2->EncodeSubShapeID(mSubShapeIDCreator2, inX, inY, inTriangle);23972398// Determine active edges2399uint8 active_edges = mShape2->GetEdgeFlags(inX, inY, inTriangle);24002401Cast(inV0, inV1, inV2, active_edges, triangle_sub_shape_id);2402}24032404const HeightFieldShape * mShape2;2405RayInvDirection mInvDirection;2406SubShapeIDCreator mSubShapeIDCreator2;2407float mDistanceStack[cStackSize];2408};24092410JPH_ASSERT(inShape->GetSubType() == EShapeSubType::HeightField);2411const HeightFieldShape *shape = static_cast<const HeightFieldShape *>(inShape);24122413Visitor visitor(inShapeCast, inShapeCastSettings, inScale, inCenterOfMassTransform2, inSubShapeIDCreator1, ioCollector);2414visitor.mShape2 = shape;2415visitor.mInvDirection.Set(inShapeCast.mDirection);2416visitor.mSubShapeIDCreator2 = inSubShapeIDCreator2;2417shape->WalkHeightField(visitor);2418}24192420struct HeightFieldShape::HSGetTrianglesContext2421{2422HSGetTrianglesContext(const HeightFieldShape *inShape, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) :2423mDecodeCtx(inShape),2424mShape(inShape),2425mLocalBox(Mat44::sInverseRotationTranslation(inRotation, inPositionCOM), inBox),2426mHeightFieldScale(inScale),2427mLocalToWorld(Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(inScale)),2428mIsInsideOut(ScaleHelpers::IsInsideOut(inScale))2429{2430}24312432bool ShouldAbort() const2433{2434return mShouldAbort;2435}24362437bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const2438{2439return true;2440}24412442int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const2443{2444// Scale the bounding boxes of this node2445Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;2446AABox4Scale(mHeightFieldScale, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);24472448// Test which nodes collide2449UVec4 collides = AABox4VsBox(mLocalBox, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);24502451// Filter out invalid bounding boxes2452collides = UVec4::sAnd(collides, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));24532454return CountAndSortTrues(collides, ioProperties);2455}24562457void VisitTriangle(uint inX, uint inY, [[maybe_unused]] uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)2458{2459// When the buffer is full and we cannot process the triangles, abort the height field walk. The next time GetTrianglesNext is called we will continue here.2460if (mNumTrianglesFound + 1 > mMaxTrianglesRequested)2461{2462mShouldAbort = true;2463return;2464}24652466// Store vertices as Float32467if (mIsInsideOut)2468{2469// Reverse vertices2470(mLocalToWorld * inV0).StoreFloat3(mTriangleVertices++);2471(mLocalToWorld * inV2).StoreFloat3(mTriangleVertices++);2472(mLocalToWorld * inV1).StoreFloat3(mTriangleVertices++);2473}2474else2475{2476// Normal scale2477(mLocalToWorld * inV0).StoreFloat3(mTriangleVertices++);2478(mLocalToWorld * inV1).StoreFloat3(mTriangleVertices++);2479(mLocalToWorld * inV2).StoreFloat3(mTriangleVertices++);2480}24812482// Decode material2483if (mMaterials != nullptr)2484*mMaterials++ = mShape->GetMaterial(inX, inY);24852486// Accumulate triangles found2487mNumTrianglesFound++;2488}24892490DecodingContext mDecodeCtx;2491const HeightFieldShape * mShape;2492OrientedBox mLocalBox;2493Vec3 mHeightFieldScale;2494Mat44 mLocalToWorld;2495int mMaxTrianglesRequested;2496Float3 * mTriangleVertices;2497int mNumTrianglesFound;2498const PhysicsMaterial ** mMaterials;2499bool mShouldAbort;2500bool mIsInsideOut;2501};25022503void HeightFieldShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const2504{2505static_assert(sizeof(HSGetTrianglesContext) <= sizeof(GetTrianglesContext), "GetTrianglesContext too small");2506JPH_ASSERT(IsAligned(&ioContext, alignof(HSGetTrianglesContext)));25072508new (&ioContext) HSGetTrianglesContext(this, inBox, inPositionCOM, inRotation, inScale);2509}25102511int HeightFieldShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const2512{2513static_assert(cGetTrianglesMinTrianglesRequested >= 1, "cGetTrianglesMinTrianglesRequested is too small");2514JPH_ASSERT(inMaxTrianglesRequested >= cGetTrianglesMinTrianglesRequested);25152516// Check if we're done2517HSGetTrianglesContext &context = (HSGetTrianglesContext &)ioContext;2518if (context.mDecodeCtx.IsDoneWalking())2519return 0;25202521// Store parameters on context2522context.mMaxTrianglesRequested = inMaxTrianglesRequested;2523context.mTriangleVertices = outTriangleVertices;2524context.mMaterials = outMaterials;2525context.mShouldAbort = false; // Reset the abort flag2526context.mNumTrianglesFound = 0;25272528// Continue (or start) walking the height field2529context.mDecodeCtx.WalkHeightField(context);2530return context.mNumTrianglesFound;2531}25322533void HeightFieldShape::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, [[maybe_unused]] const ShapeFilter &inShapeFilter)2534{2535JPH_PROFILE_FUNCTION();25362537// Get the shapes2538JPH_ASSERT(inShape1->GetType() == EShapeType::Convex);2539JPH_ASSERT(inShape2->GetType() == EShapeType::HeightField);2540const ConvexShape *shape1 = static_cast<const ConvexShape *>(inShape1);2541const HeightFieldShape *shape2 = static_cast<const HeightFieldShape *>(inShape2);25422543struct Visitor : public CollideConvexVsTriangles2544{2545using CollideConvexVsTriangles::CollideConvexVsTriangles;25462547JPH_INLINE bool ShouldAbort() const2548{2549return mCollector.ShouldEarlyOut();2550}25512552JPH_INLINE bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const2553{2554return true;2555}25562557JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const2558{2559// Scale the bounding boxes of this node2560Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;2561AABox4Scale(mScale2, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);25622563// Test which nodes collide2564UVec4 collides = AABox4VsBox(mBoundsOf1InSpaceOf2, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);25652566// Filter out invalid bounding boxes2567collides = UVec4::sAnd(collides, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));25682569return CountAndSortTrues(collides, ioProperties);2570}25712572JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)2573{2574// Create ID for triangle2575SubShapeID triangle_sub_shape_id = mShape2->EncodeSubShapeID(mSubShapeIDCreator2, inX, inY, inTriangle);25762577// Determine active edges2578uint8 active_edges = mShape2->GetEdgeFlags(inX, inY, inTriangle);25792580Collide(inV0, inV1, inV2, active_edges, triangle_sub_shape_id);2581}25822583const HeightFieldShape * mShape2;2584SubShapeIDCreator mSubShapeIDCreator2;2585};25862587Visitor visitor(shape1, inScale1, inScale2, inCenterOfMassTransform1, inCenterOfMassTransform2, inSubShapeIDCreator1.GetID(), inCollideShapeSettings, ioCollector);2588visitor.mShape2 = shape2;2589visitor.mSubShapeIDCreator2 = inSubShapeIDCreator2;2590shape2->WalkHeightField(visitor);2591}25922593void HeightFieldShape::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, [[maybe_unused]] const ShapeFilter &inShapeFilter)2594{2595JPH_PROFILE_FUNCTION();25962597// Get the shapes2598JPH_ASSERT(inShape1->GetSubType() == EShapeSubType::Sphere);2599JPH_ASSERT(inShape2->GetType() == EShapeType::HeightField);2600const SphereShape *shape1 = static_cast<const SphereShape *>(inShape1);2601const HeightFieldShape *shape2 = static_cast<const HeightFieldShape *>(inShape2);26022603struct Visitor : public CollideSphereVsTriangles2604{2605using CollideSphereVsTriangles::CollideSphereVsTriangles;26062607JPH_INLINE bool ShouldAbort() const2608{2609return mCollector.ShouldEarlyOut();2610}26112612JPH_INLINE bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const2613{2614return true;2615}26162617JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const2618{2619// Scale the bounding boxes of this node2620Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;2621AABox4Scale(mScale2, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);26222623// Test which nodes collide2624UVec4 collides = AABox4VsSphere(mSphereCenterIn2, mRadiusPlusMaxSeparationSq, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);26252626// Filter out invalid bounding boxes2627collides = UVec4::sAnd(collides, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));26282629return CountAndSortTrues(collides, ioProperties);2630}26312632JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)2633{2634// Create ID for triangle2635SubShapeID triangle_sub_shape_id = mShape2->EncodeSubShapeID(mSubShapeIDCreator2, inX, inY, inTriangle);26362637// Determine active edges2638uint8 active_edges = mShape2->GetEdgeFlags(inX, inY, inTriangle);26392640Collide(inV0, inV1, inV2, active_edges, triangle_sub_shape_id);2641}26422643const HeightFieldShape * mShape2;2644SubShapeIDCreator mSubShapeIDCreator2;2645};26462647Visitor visitor(shape1, inScale1, inScale2, inCenterOfMassTransform1, inCenterOfMassTransform2, inSubShapeIDCreator1.GetID(), inCollideShapeSettings, ioCollector);2648visitor.mShape2 = shape2;2649visitor.mSubShapeIDCreator2 = inSubShapeIDCreator2;2650shape2->WalkHeightField(visitor);2651}26522653void HeightFieldShape::SaveBinaryState(StreamOut &inStream) const2654{2655Shape::SaveBinaryState(inStream);26562657inStream.Write(mOffset);2658inStream.Write(mScale);2659inStream.Write(mSampleCount);2660inStream.Write(mBlockSize);2661inStream.Write(mBitsPerSample);2662inStream.Write(mMinSample);2663inStream.Write(mMaxSample);2664inStream.Write(mMaterialIndices);2665inStream.Write(mNumBitsPerMaterialIndex);26662667if (mRangeBlocks != nullptr)2668{2669inStream.Write(true);2670inStream.WriteBytes(mRangeBlocks, mRangeBlocksSize * sizeof(RangeBlock) + mHeightSamplesSize + mActiveEdgesSize);2671}2672else2673{2674inStream.Write(false);2675}2676}26772678void HeightFieldShape::RestoreBinaryState(StreamIn &inStream)2679{2680Shape::RestoreBinaryState(inStream);26812682inStream.Read(mOffset);2683inStream.Read(mScale);2684inStream.Read(mSampleCount);2685inStream.Read(mBlockSize);2686inStream.Read(mBitsPerSample);2687inStream.Read(mMinSample);2688inStream.Read(mMaxSample);2689inStream.Read(mMaterialIndices);2690inStream.Read(mNumBitsPerMaterialIndex);26912692// We don't have the exact number of reserved materials anymore, but ensure that our array is big enough2693// TODO: Next time when we bump the binary serialization format of this class we should store the capacity and allocate the right amount, for now we accept a little bit of waste2694mMaterials.reserve(PhysicsMaterialList::size_type(1) << mNumBitsPerMaterialIndex);26952696CacheValues();26972698bool has_heights = false;2699inStream.Read(has_heights);2700if (has_heights)2701{2702AllocateBuffers();2703inStream.ReadBytes(mRangeBlocks, mRangeBlocksSize * sizeof(RangeBlock) + mHeightSamplesSize + mActiveEdgesSize);2704}2705}27062707void HeightFieldShape::SaveMaterialState(PhysicsMaterialList &outMaterials) const2708{2709outMaterials = mMaterials;2710}27112712void HeightFieldShape::RestoreMaterialState(const PhysicsMaterialRefC *inMaterials, uint inNumMaterials)2713{2714mMaterials.assign(inMaterials, inMaterials + inNumMaterials);2715}27162717Shape::Stats HeightFieldShape::GetStats() const2718{2719return Stats(2720sizeof(*this)2721+ mMaterials.size() * sizeof(Ref<PhysicsMaterial>)2722+ mRangeBlocksSize * sizeof(RangeBlock)2723+ mHeightSamplesSize * sizeof(uint8)2724+ mActiveEdgesSize * sizeof(uint8)2725+ mMaterialIndices.size() * sizeof(uint8),2726mHeightSamplesSize == 0? 0 : Square(mSampleCount - 1) * 2);2727}27282729void HeightFieldShape::sRegister()2730{2731ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::HeightField);2732f.mConstruct = []() -> Shape * { return new HeightFieldShape; };2733f.mColor = Color::sPurple;27342735for (EShapeSubType s : sConvexSubShapeTypes)2736{2737CollisionDispatch::sRegisterCollideShape(s, EShapeSubType::HeightField, sCollideConvexVsHeightField);2738CollisionDispatch::sRegisterCastShape(s, EShapeSubType::HeightField, sCastConvexVsHeightField);27392740CollisionDispatch::sRegisterCastShape(EShapeSubType::HeightField, s, CollisionDispatch::sReversedCastShape);2741CollisionDispatch::sRegisterCollideShape(EShapeSubType::HeightField, s, CollisionDispatch::sReversedCollideShape);2742}27432744// Specialized collision functions2745CollisionDispatch::sRegisterCollideShape(EShapeSubType::Sphere, EShapeSubType::HeightField, sCollideSphereVsHeightField);2746CollisionDispatch::sRegisterCastShape(EShapeSubType::Sphere, EShapeSubType::HeightField, sCastSphereVsHeightField);2747}27482749JPH_NAMESPACE_END275027512752