Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/HeightFieldShape.cpp
9913 views
1
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3
// SPDX-License-Identifier: MIT
4
5
#include <Jolt/Jolt.h>
6
7
#include <Jolt/Physics/Collision/Shape/HeightFieldShape.h>
8
#include <Jolt/Physics/Collision/Shape/ConvexShape.h>
9
#include <Jolt/Physics/Collision/Shape/ScaleHelpers.h>
10
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
11
#include <Jolt/Physics/Collision/RayCast.h>
12
#include <Jolt/Physics/Collision/ShapeCast.h>
13
#include <Jolt/Physics/Collision/CastResult.h>
14
#include <Jolt/Physics/Collision/CollidePointResult.h>
15
#include <Jolt/Physics/Collision/ShapeFilter.h>
16
#include <Jolt/Physics/Collision/CastConvexVsTriangles.h>
17
#include <Jolt/Physics/Collision/CastSphereVsTriangles.h>
18
#include <Jolt/Physics/Collision/CollideConvexVsTriangles.h>
19
#include <Jolt/Physics/Collision/CollideSphereVsTriangles.h>
20
#include <Jolt/Physics/Collision/TransformedShape.h>
21
#include <Jolt/Physics/Collision/ActiveEdges.h>
22
#include <Jolt/Physics/Collision/CollisionDispatch.h>
23
#include <Jolt/Physics/Collision/SortReverseAndStore.h>
24
#include <Jolt/Physics/Collision/CollideSoftBodyVerticesVsTriangles.h>
25
#include <Jolt/Core/Profiler.h>
26
#include <Jolt/Core/StringTools.h>
27
#include <Jolt/Core/StreamIn.h>
28
#include <Jolt/Core/StreamOut.h>
29
#include <Jolt/Core/TempAllocator.h>
30
#include <Jolt/Core/ScopeExit.h>
31
#include <Jolt/Geometry/AABox4.h>
32
#include <Jolt/Geometry/RayTriangle.h>
33
#include <Jolt/Geometry/RayAABox.h>
34
#include <Jolt/Geometry/OrientedBox.h>
35
#include <Jolt/ObjectStream/TypeDeclarations.h>
36
37
//#define JPH_DEBUG_HEIGHT_FIELD
38
39
JPH_NAMESPACE_BEGIN
40
41
#ifdef JPH_DEBUG_RENDERER
42
bool HeightFieldShape::sDrawTriangleOutlines = false;
43
#endif // JPH_DEBUG_RENDERER
44
45
using namespace HeightFieldShapeConstants;
46
47
JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(HeightFieldShapeSettings)
48
{
49
JPH_ADD_BASE_CLASS(HeightFieldShapeSettings, ShapeSettings)
50
51
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mHeightSamples)
52
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mOffset)
53
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mScale)
54
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMinHeightValue)
55
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaxHeightValue)
56
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterialsCapacity)
57
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mSampleCount)
58
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mBlockSize)
59
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mBitsPerSample)
60
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterialIndices)
61
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mMaterials)
62
JPH_ADD_ATTRIBUTE(HeightFieldShapeSettings, mActiveEdgeCosThresholdAngle)
63
}
64
65
const uint HeightFieldShape::sGridOffsets[] =
66
{
67
0, // level: 0, max x/y: 0, offset: 0
68
1, // level: 1, max x/y: 1, offset: 1
69
5, // level: 2, max x/y: 3, offset: 1 + 4
70
21, // level: 3, max x/y: 7, offset: 1 + 4 + 16
71
85, // level: 4, max x/y: 15, offset: 1 + 4 + 16 + 64
72
341, // level: 5, max x/y: 31, offset: 1 + 4 + 16 + 64 + 256
73
1365, // level: 6, max x/y: 63, offset: 1 + 4 + 16 + 64 + 256 + 1024
74
5461, // level: 7, max x/y: 127, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096
75
21845, // level: 8, max x/y: 255, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...
76
87381, // level: 9, max x/y: 511, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...
77
349525, // level: 10, max x/y: 1023, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...
78
1398101, // level: 11, max x/y: 2047, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...
79
5592405, // level: 12, max x/y: 4095, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...
80
22369621, // level: 13, max x/y: 8191, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...
81
89478485, // level: 14, max x/y: 16383, offset: 1 + 4 + 16 + 64 + 256 + 1024 + 4096 + ...
82
};
83
84
HeightFieldShapeSettings::HeightFieldShapeSettings(const float *inSamples, Vec3Arg inOffset, Vec3Arg inScale, uint32 inSampleCount, const uint8 *inMaterialIndices, const PhysicsMaterialList &inMaterialList) :
85
mOffset(inOffset),
86
mScale(inScale),
87
mSampleCount(inSampleCount)
88
{
89
mHeightSamples.assign(inSamples, inSamples + Square(inSampleCount));
90
91
if (!inMaterialList.empty() && inMaterialIndices != nullptr)
92
{
93
mMaterialIndices.assign(inMaterialIndices, inMaterialIndices + Square(inSampleCount - 1));
94
mMaterials = inMaterialList;
95
}
96
else
97
{
98
JPH_ASSERT(inMaterialList.empty());
99
JPH_ASSERT(inMaterialIndices == nullptr);
100
}
101
}
102
103
ShapeSettings::ShapeResult HeightFieldShapeSettings::Create() const
104
{
105
if (mCachedResult.IsEmpty())
106
Ref<Shape> shape = new HeightFieldShape(*this, mCachedResult);
107
return mCachedResult;
108
}
109
110
void HeightFieldShapeSettings::DetermineMinAndMaxSample(float &outMinValue, float &outMaxValue, float &outQuantizationScale) const
111
{
112
// Determine min and max value
113
outMinValue = mMinHeightValue;
114
outMaxValue = mMaxHeightValue;
115
for (float h : mHeightSamples)
116
if (h != cNoCollisionValue)
117
{
118
outMinValue = min(outMinValue, h);
119
outMaxValue = max(outMaxValue, h);
120
}
121
122
// Prevent dividing by zero by setting a minimal height difference
123
float height_diff = max(outMaxValue - outMinValue, 1.0e-6f);
124
125
// Calculate the scale factor to quantize to 16 bits
126
outQuantizationScale = float(cMaxHeightValue16) / height_diff;
127
}
128
129
uint32 HeightFieldShapeSettings::CalculateBitsPerSampleForError(float inMaxError) const
130
{
131
// Start with 1 bit per sample
132
uint32 bits_per_sample = 1;
133
134
// Determine total range
135
float min_value, max_value, scale;
136
DetermineMinAndMaxSample(min_value, max_value, scale);
137
if (min_value < max_value)
138
{
139
// Loop over all blocks
140
for (uint y = 0; y < mSampleCount; y += mBlockSize)
141
for (uint x = 0; x < mSampleCount; x += mBlockSize)
142
{
143
// Determine min and max block value + take 1 sample border just like we do while building the hierarchical grids
144
float block_min_value = FLT_MAX, block_max_value = -FLT_MAX;
145
for (uint bx = x; bx < min(x + mBlockSize + 1, mSampleCount); ++bx)
146
for (uint by = y; by < min(y + mBlockSize + 1, mSampleCount); ++by)
147
{
148
float h = mHeightSamples[by * mSampleCount + bx];
149
if (h != cNoCollisionValue)
150
{
151
block_min_value = min(block_min_value, h);
152
block_max_value = max(block_max_value, h);
153
}
154
}
155
156
if (block_min_value < block_max_value)
157
{
158
// Quantize then dequantize block min/max value
159
block_min_value = min_value + floor((block_min_value - min_value) * scale) / scale;
160
block_max_value = min_value + ceil((block_max_value - min_value) * scale) / scale;
161
float block_height = block_max_value - block_min_value;
162
163
// Loop over the block again
164
for (uint bx = x; bx < x + mBlockSize; ++bx)
165
for (uint by = y; by < y + mBlockSize; ++by)
166
{
167
// Get the height
168
float height = mHeightSamples[by * mSampleCount + bx];
169
if (height != cNoCollisionValue)
170
{
171
for (;;)
172
{
173
// Determine bitmask for sample
174
uint32 sample_mask = (1 << bits_per_sample) - 1;
175
176
// Quantize
177
float quantized_height = floor((height - block_min_value) * float(sample_mask) / block_height);
178
quantized_height = Clamp(quantized_height, 0.0f, float(sample_mask - 1));
179
180
// Dequantize and check error
181
float dequantized_height = block_min_value + (quantized_height + 0.5f) * block_height / float(sample_mask);
182
if (abs(dequantized_height - height) <= inMaxError)
183
break;
184
185
// Not accurate enough, increase bits per sample
186
bits_per_sample++;
187
188
// Don't go above 8 bits per sample
189
if (bits_per_sample == 8)
190
return bits_per_sample;
191
}
192
}
193
}
194
}
195
}
196
197
}
198
199
return bits_per_sample;
200
}
201
202
void 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)
203
{
204
// Limit the block size so we don't allocate more than 64K memory from the temp allocator
205
uint block_size_x = min(inSizeX, 44u);
206
uint block_size_y = min(inSizeY, 44u);
207
208
// Allocate temporary buffer for normals
209
uint normals_size = 2 * (block_size_x + 1) * (block_size_y + 1) * sizeof(Vec3);
210
Vec3 *normals = (Vec3 *)inAllocator.Allocate(normals_size);
211
JPH_SCOPE_EXIT([&inAllocator, normals, normals_size]{ inAllocator.Free(normals, normals_size); });
212
213
// Update the edges in blocks
214
for (uint block_y = 0; block_y < inSizeY; block_y += block_size_y)
215
for (uint block_x = 0; block_x < inSizeX; block_x += block_size_x)
216
{
217
// Calculate the bottom right corner of the block
218
uint block_x_end = min(block_x + block_size_x, inSizeX);
219
uint block_y_end = min(block_y + block_size_y, inSizeY);
220
221
// If we're not at the first block in x, we need one extra column of normals to the left
222
uint normals_x_start, normals_x_skip;
223
if (block_x > 0)
224
{
225
normals_x_start = block_x - 1;
226
normals_x_skip = 2; // We need to skip over that extra column
227
}
228
else
229
{
230
normals_x_start = 0;
231
normals_x_skip = 0;
232
}
233
234
// If we're not at the last block in y, we need one extra row of normals at the bottom
235
uint normals_y_end = block_y_end < inSizeY? block_y_end + 1 : inSizeY;
236
237
// Calculate triangle normals and make normals zero for triangles that are missing
238
Vec3 *out_normal = normals;
239
for (uint y = block_y; y < normals_y_end; ++y)
240
{
241
for (uint x = normals_x_start; x < block_x_end; ++x)
242
{
243
// Get height on diagonal
244
const float *height_samples = inHeights + (inY - inHeightsStartY + y) * inHeightsStride + (inX - inHeightsStartX + x);
245
float x1y1_h = height_samples[0];
246
float x2y2_h = height_samples[inHeightsStride + 1];
247
if (x1y1_h != cNoCollisionValue && x2y2_h != cNoCollisionValue)
248
{
249
// Calculate normal for lower left triangle (e.g. T1A)
250
float x1y2_h = height_samples[inHeightsStride];
251
if (x1y2_h != cNoCollisionValue)
252
{
253
Vec3 x2y2_minus_x1y2(mScale.GetX(), inHeightsScale * (x2y2_h - x1y2_h), 0);
254
Vec3 x1y1_minus_x1y2(0, inHeightsScale * (x1y1_h - x1y2_h), -mScale.GetZ());
255
out_normal[0] = x2y2_minus_x1y2.Cross(x1y1_minus_x1y2).Normalized();
256
}
257
else
258
out_normal[0] = Vec3::sZero();
259
260
// Calculate normal for upper right triangle (e.g. T1B)
261
float x2y1_h = height_samples[1];
262
if (x2y1_h != cNoCollisionValue)
263
{
264
Vec3 x1y1_minus_x2y1(-mScale.GetX(), inHeightsScale * (x1y1_h - x2y1_h), 0);
265
Vec3 x2y2_minus_x2y1(0, inHeightsScale * (x2y2_h - x2y1_h), mScale.GetZ());
266
out_normal[1] = x1y1_minus_x2y1.Cross(x2y2_minus_x2y1).Normalized();
267
}
268
else
269
out_normal[1] = Vec3::sZero();
270
}
271
else
272
{
273
out_normal[0] = Vec3::sZero();
274
out_normal[1] = Vec3::sZero();
275
}
276
277
out_normal += 2;
278
}
279
}
280
281
// Number of vectors to skip to get to the next row of normals
282
uint normals_pitch = 2 * (block_x_end - normals_x_start);
283
284
// Calculate active edges
285
const Vec3 *in_normal = normals;
286
uint global_bit_pos = 3 * ((inY + block_y) * (mSampleCount - 1) + (inX + block_x));
287
for (uint y = block_y; y < block_y_end; ++y)
288
{
289
in_normal += normals_x_skip; // If we have an extra column to the left, skip it here, we'll read it with in_normal[-1] below
290
291
for (uint x = block_x; x < block_x_end; ++x)
292
{
293
// Get vertex heights
294
const float *height_samples = inHeights + (inY - inHeightsStartY + y) * inHeightsStride + (inX - inHeightsStartX + x);
295
float x1y1_h = height_samples[0];
296
float x1y2_h = height_samples[inHeightsStride];
297
float x2y2_h = height_samples[inHeightsStride + 1];
298
bool x1y1_valid = x1y1_h != cNoCollisionValue;
299
bool x1y2_valid = x1y2_h != cNoCollisionValue;
300
bool x2y2_valid = x2y2_h != cNoCollisionValue;
301
302
// Calculate the edge flags (3 bits)
303
// See diagram in the next function for the edge numbering
304
uint16 edge_mask = 0b111;
305
uint16 edge_flags = 0;
306
307
// Edge 0
308
if (x == 0)
309
edge_mask &= 0b110; // We need normal x - 1 which we didn't calculate, don't update this edge
310
else if (x1y1_valid && x1y2_valid)
311
{
312
Vec3 edge0_direction(0, inHeightsScale * (x1y2_h - x1y1_h), mScale.GetZ());
313
if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[-1], edge0_direction, inActiveEdgeCosThresholdAngle))
314
edge_flags |= 0b001;
315
}
316
317
// Edge 1
318
if (y == inSizeY - 1)
319
edge_mask &= 0b101; // We need normal y + 1 which we didn't calculate, don't update this edge
320
else if (x1y2_valid && x2y2_valid)
321
{
322
Vec3 edge1_direction(mScale.GetX(), inHeightsScale * (x2y2_h - x1y2_h), 0);
323
if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[normals_pitch + 1], edge1_direction, inActiveEdgeCosThresholdAngle))
324
edge_flags |= 0b010;
325
}
326
327
// Edge 2
328
if (x1y1_valid && x2y2_valid)
329
{
330
Vec3 edge2_direction(-mScale.GetX(), inHeightsScale * (x1y1_h - x2y2_h), -mScale.GetZ());
331
if (ActiveEdges::IsEdgeActive(in_normal[0], in_normal[1], edge2_direction, inActiveEdgeCosThresholdAngle))
332
edge_flags |= 0b100;
333
}
334
335
// Store the edge flags in the array
336
uint byte_pos = global_bit_pos >> 3;
337
uint bit_pos = global_bit_pos & 0b111;
338
JPH_ASSERT(byte_pos < mActiveEdgesSize);
339
uint8 *edge_flags_ptr = &mActiveEdges[byte_pos];
340
uint16 combined_edge_flags = uint16(edge_flags_ptr[0]) | uint16(uint16(edge_flags_ptr[1]) << 8);
341
combined_edge_flags &= ~(edge_mask << bit_pos);
342
combined_edge_flags |= edge_flags << bit_pos;
343
edge_flags_ptr[0] = uint8(combined_edge_flags);
344
edge_flags_ptr[1] = uint8(combined_edge_flags >> 8);
345
346
in_normal += 2;
347
global_bit_pos += 3;
348
}
349
350
global_bit_pos += 3 * (mSampleCount - 1 - (block_x_end - block_x));
351
}
352
}
353
}
354
355
void HeightFieldShape::CalculateActiveEdges(const HeightFieldShapeSettings &inSettings)
356
{
357
/*
358
Store active edges. The triangles are organized like this:
359
x --->
360
361
y + +
362
| \ T1B | \ T2B
363
| e0 e2 | \
364
| | T1A \ | T2A \
365
V +--e1---+-------+
366
| \ T3B | \ T4B
367
| \ | \
368
| T3A \ | T4A \
369
+-------+-------+
370
We store active edges e0 .. e2 as bits 0 .. 2.
371
We store triangles horizontally then vertically (order T1A, T2A, T3A and T4A).
372
The top edge and right edge of the heightfield are always active so we do not need to store them,
373
therefore we only need to store (mSampleCount - 1)^2 * 3-bit
374
The triangles T1B, T2B, T3B and T4B do not need to be stored, their active edges can be constructed from adjacent triangles.
375
Add 1 byte padding so we can always read 1 uint16 to get the bits that cross an 8 bit boundary
376
*/
377
378
// Make all edges active (if mSampleCount is bigger than inSettings.mSampleCount we need to fill up the padding,
379
// also edges at x = 0 and y = inSettings.mSampleCount - 1 are not updated)
380
memset(mActiveEdges, 0xff, mActiveEdgesSize);
381
382
// Now clear the edges that are not active
383
TempAllocatorMalloc allocator;
384
CalculateActiveEdges(0, 0, inSettings.mSampleCount - 1, inSettings.mSampleCount - 1, inSettings.mHeightSamples.data(), 0, 0, inSettings.mSampleCount, inSettings.mScale.GetY(), inSettings.mActiveEdgeCosThresholdAngle, allocator);
385
}
386
387
void HeightFieldShape::StoreMaterialIndices(const HeightFieldShapeSettings &inSettings)
388
{
389
// We need to account for any rounding of the sample count to the nearest block size
390
uint in_count_min_1 = inSettings.mSampleCount - 1;
391
uint out_count_min_1 = mSampleCount - 1;
392
393
mNumBitsPerMaterialIndex = 32 - CountLeadingZeros(max((uint32)mMaterials.size(), inSettings.mMaterialsCapacity) - 1);
394
mMaterialIndices.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 uint16
395
396
if (mMaterials.size() > 1)
397
for (uint y = 0; y < out_count_min_1; ++y)
398
for (uint x = 0; x < out_count_min_1; ++x)
399
{
400
// Read material
401
uint16 material_index = x < in_count_min_1 && y < in_count_min_1? uint16(inSettings.mMaterialIndices[x + y * in_count_min_1]) : 0;
402
403
// Calculate byte and bit position where the material index needs to go
404
uint sample_pos = x + y * out_count_min_1;
405
uint bit_pos = sample_pos * mNumBitsPerMaterialIndex;
406
uint byte_pos = bit_pos >> 3;
407
bit_pos &= 0b111;
408
409
// Write the material index
410
material_index <<= bit_pos;
411
JPH_ASSERT(byte_pos + 1 < mMaterialIndices.size());
412
mMaterialIndices[byte_pos] |= uint8(material_index);
413
mMaterialIndices[byte_pos + 1] |= uint8(material_index >> 8);
414
}
415
}
416
417
void HeightFieldShape::CacheValues()
418
{
419
mSampleMask = uint8((uint32(1) << mBitsPerSample) - 1);
420
}
421
422
void HeightFieldShape::AllocateBuffers()
423
{
424
uint num_blocks = GetNumBlocks();
425
uint max_stride = (num_blocks + 1) >> 1;
426
mRangeBlocksSize = sGridOffsets[sGetMaxLevel(num_blocks) - 1] + Square(max_stride);
427
mHeightSamplesSize = (mSampleCount * mSampleCount * mBitsPerSample + 7) / 8 + 1;
428
mActiveEdgesSize = (Square(mSampleCount - 1) * 3 + 7) / 8 + 1; // See explanation at HeightFieldShape::CalculateActiveEdges
429
430
JPH_ASSERT(mRangeBlocks == nullptr && mHeightSamples == nullptr && mActiveEdges == nullptr);
431
void *data = AlignedAllocate(mRangeBlocksSize * sizeof(RangeBlock) + mHeightSamplesSize + mActiveEdgesSize, alignof(RangeBlock));
432
mRangeBlocks = reinterpret_cast<RangeBlock *>(data);
433
mHeightSamples = reinterpret_cast<uint8 *>(mRangeBlocks + mRangeBlocksSize);
434
mActiveEdges = mHeightSamples + mHeightSamplesSize;
435
}
436
437
HeightFieldShape::HeightFieldShape(const HeightFieldShapeSettings &inSettings, ShapeResult &outResult) :
438
Shape(EShapeType::HeightField, EShapeSubType::HeightField, inSettings, outResult),
439
mOffset(inSettings.mOffset),
440
mScale(inSettings.mScale),
441
mSampleCount(((inSettings.mSampleCount + inSettings.mBlockSize - 1) / inSettings.mBlockSize) * inSettings.mBlockSize), // Round sample count to nearest block size
442
mBlockSize(inSettings.mBlockSize),
443
mBitsPerSample(uint8(inSettings.mBitsPerSample))
444
{
445
CacheValues();
446
447
// Reserve a bigger materials list if requested
448
if (inSettings.mMaterialsCapacity > 0)
449
mMaterials.reserve(inSettings.mMaterialsCapacity);
450
mMaterials = inSettings.mMaterials;
451
452
// Check block size
453
if (mBlockSize < 2 || mBlockSize > 8)
454
{
455
outResult.SetError("HeightFieldShape: Block size must be in the range [2, 8]!");
456
return;
457
}
458
459
// Check bits per sample
460
if (inSettings.mBitsPerSample < 1 || inSettings.mBitsPerSample > 8)
461
{
462
outResult.SetError("HeightFieldShape: Bits per sample must be in the range [1, 8]!");
463
return;
464
}
465
466
// We stop at mBlockSize x mBlockSize height sample blocks
467
uint num_blocks = GetNumBlocks();
468
469
// We want at least 1 grid layer
470
if (num_blocks < 2)
471
{
472
outResult.SetError("HeightFieldShape: Sample count too low!");
473
return;
474
}
475
476
// Check that we don't overflow our 32 bit 'properties'
477
if (num_blocks > (1 << cNumBitsXY))
478
{
479
outResult.SetError("HeightFieldShape: Sample count too high!");
480
return;
481
}
482
483
// Check if we're not exceeding the amount of sub shape id bits
484
if (GetSubShapeIDBitsRecursive() > SubShapeID::MaxBits)
485
{
486
outResult.SetError("HeightFieldShape: Size exceeds the amount of available sub shape ID bits!");
487
return;
488
}
489
490
if (!mMaterials.empty())
491
{
492
// Validate materials
493
if (mMaterials.size() > 256)
494
{
495
outResult.SetError("Supporting max 256 materials per height field");
496
return;
497
}
498
for (uint8 s : inSettings.mMaterialIndices)
499
if (s >= mMaterials.size())
500
{
501
outResult.SetError(StringFormat("Material %u is beyond material list (size: %u)", s, (uint)mMaterials.size()));
502
return;
503
}
504
}
505
else
506
{
507
// No materials assigned, validate that no materials have been specified
508
if (!inSettings.mMaterialIndices.empty())
509
{
510
outResult.SetError("No materials present, mMaterialIndices should be empty");
511
return;
512
}
513
}
514
515
// Determine range
516
float min_value, max_value, scale;
517
inSettings.DetermineMinAndMaxSample(min_value, max_value, scale);
518
if (min_value > max_value)
519
{
520
// If there is no collision with this heightmap, leave everything empty
521
mMaterials.clear();
522
outResult.Set(this);
523
return;
524
}
525
526
// Allocate space for this shape
527
AllocateBuffers();
528
529
// Quantize to uint16
530
Array<uint16> quantized_samples;
531
quantized_samples.reserve(mSampleCount * mSampleCount);
532
for (uint y = 0; y < inSettings.mSampleCount; ++y)
533
{
534
for (uint x = 0; x < inSettings.mSampleCount; ++x)
535
{
536
float h = inSettings.mHeightSamples[x + y * inSettings.mSampleCount];
537
if (h == cNoCollisionValue)
538
{
539
quantized_samples.push_back(cNoCollisionValue16);
540
}
541
else
542
{
543
// Floor the quantized height to get a lower bound for the quantized value
544
int quantized_height = (int)floor(scale * (h - min_value));
545
546
// Ensure that the height says below the max height value so we can safely add 1 to get the upper bound for the quantized value
547
quantized_height = Clamp(quantized_height, 0, int(cMaxHeightValue16 - 1));
548
549
quantized_samples.push_back(uint16(quantized_height));
550
}
551
}
552
// Pad remaining columns with no collision
553
for (uint x = inSettings.mSampleCount; x < mSampleCount; ++x)
554
quantized_samples.push_back(cNoCollisionValue16);
555
}
556
// Pad remaining rows with no collision
557
for (uint y = inSettings.mSampleCount; y < mSampleCount; ++y)
558
for (uint x = 0; x < mSampleCount; ++x)
559
quantized_samples.push_back(cNoCollisionValue16);
560
561
// Update offset and scale to account for the compression to uint16
562
if (min_value <= max_value) // Only when there was collision
563
{
564
// In GetPosition we always add 0.5 to the quantized sample in order to reduce the average error.
565
// 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.
566
min_value -= 0.5f / (scale * mSampleMask);
567
568
mOffset.SetY(mOffset.GetY() + mScale.GetY() * min_value);
569
}
570
mScale.SetY(mScale.GetY() / scale);
571
572
// Calculate amount of grids
573
uint max_level = sGetMaxLevel(num_blocks);
574
575
// Temporary data structure used during creating of a hierarchy of grids
576
struct Range
577
{
578
uint16 mMin;
579
uint16 mMax;
580
};
581
582
// Reserve size for temporary range data + reserve 1 extra for a 1x1 grid that we won't store but use for calculating the bounding box
583
Array<Array<Range>> ranges;
584
ranges.resize(max_level + 1);
585
586
// Calculate highest detail grid by combining mBlockSize x mBlockSize height samples
587
Array<Range> *cur_range_vector = &ranges.back();
588
uint 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)
589
cur_range_vector->resize(num_blocks_pow2 * num_blocks_pow2);
590
Range *range_dst = &cur_range_vector->front();
591
for (uint y = 0; y < num_blocks_pow2; ++y)
592
for (uint x = 0; x < num_blocks_pow2; ++x)
593
{
594
range_dst->mMin = 0xffff;
595
range_dst->mMax = 0;
596
uint 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 too
597
uint max_by = y == num_blocks_pow2 - 1? mBlockSize : mBlockSize + 1;
598
for (uint by = 0; by < max_by; ++by)
599
for (uint bx = 0; bx < max_bx; ++bx)
600
{
601
uint sx = x * mBlockSize + bx;
602
uint sy = y * mBlockSize + by;
603
if (sx < mSampleCount && sy < mSampleCount)
604
{
605
uint16 h = quantized_samples[sy * mSampleCount + sx];
606
if (h != cNoCollisionValue16)
607
{
608
range_dst->mMin = min(range_dst->mMin, h);
609
range_dst->mMax = max(range_dst->mMax, uint16(h + 1)); // Add 1 to the max so we know the real value is between mMin and mMax
610
}
611
}
612
}
613
++range_dst;
614
}
615
616
// Calculate remaining grids
617
for (uint n = num_blocks_pow2 >> 1; n >= 1; n >>= 1)
618
{
619
// Get source buffer
620
const Range *range_src = &cur_range_vector->front();
621
622
// Previous array element
623
--cur_range_vector;
624
625
// Make space for this grid
626
cur_range_vector->resize(n * n);
627
628
// Get target buffer
629
range_dst = &cur_range_vector->front();
630
631
// Combine the results of 2x2 ranges
632
for (uint y = 0; y < n; ++y)
633
for (uint x = 0; x < n; ++x)
634
{
635
range_dst->mMin = 0xffff;
636
range_dst->mMax = 0;
637
for (uint by = 0; by < 2; ++by)
638
for (uint bx = 0; bx < 2; ++bx)
639
{
640
const Range &r = range_src[(y * 2 + by) * n * 2 + x * 2 + bx];
641
range_dst->mMin = min(range_dst->mMin, r.mMin);
642
range_dst->mMax = max(range_dst->mMax, r.mMax);
643
}
644
++range_dst;
645
}
646
}
647
JPH_ASSERT(cur_range_vector == &ranges.front());
648
649
// Store global range for bounding box calculation
650
mMinSample = ranges[0][0].mMin;
651
mMaxSample = ranges[0][0].mMax;
652
653
#ifdef JPH_ENABLE_ASSERTS
654
// Validate that we did not lose range along the way
655
uint16 minv = 0xffff, maxv = 0;
656
for (uint16 v : quantized_samples)
657
if (v != cNoCollisionValue16)
658
{
659
minv = min(minv, v);
660
maxv = max(maxv, uint16(v + 1));
661
}
662
JPH_ASSERT(mMinSample == minv && mMaxSample == maxv);
663
#endif
664
665
// Now erase the first element, we need a 2x2 grid to start with
666
ranges.erase(ranges.begin());
667
668
// Create blocks
669
uint max_stride = (num_blocks + 1) >> 1;
670
RangeBlock *current_block = mRangeBlocks;
671
for (uint level = 0; level < ranges.size(); ++level)
672
{
673
JPH_ASSERT(uint(current_block - mRangeBlocks) == sGridOffsets[level]);
674
675
uint in_n = 1 << level;
676
uint out_n = min(in_n, max_stride); // At the most detailed level we store a non-power of 2 number of blocks
677
678
for (uint y = 0; y < out_n; ++y)
679
for (uint x = 0; x < out_n; ++x)
680
{
681
// Convert from 2x2 Range structure to 1 RangeBlock structure
682
RangeBlock &rb = *current_block++;
683
for (uint by = 0; by < 2; ++by)
684
for (uint bx = 0; bx < 2; ++bx)
685
{
686
uint src_pos = (y * 2 + by) * 2 * in_n + (x * 2 + bx);
687
uint dst_pos = by * 2 + bx;
688
rb.mMin[dst_pos] = ranges[level][src_pos].mMin;
689
rb.mMax[dst_pos] = ranges[level][src_pos].mMax;
690
}
691
}
692
}
693
JPH_ASSERT(uint32(current_block - mRangeBlocks) == mRangeBlocksSize);
694
695
// Quantize height samples
696
memset(mHeightSamples, 0, mHeightSamplesSize);
697
int sample = 0;
698
for (uint y = 0; y < mSampleCount; ++y)
699
for (uint x = 0; x < mSampleCount; ++x)
700
{
701
uint32 output_value;
702
703
float h = x < inSettings.mSampleCount && y < inSettings.mSampleCount? inSettings.mHeightSamples[x + y * inSettings.mSampleCount] : cNoCollisionValue;
704
if (h == cNoCollisionValue)
705
{
706
// No collision
707
output_value = mSampleMask;
708
}
709
else
710
{
711
// Get range of block so we know what range to compress to
712
uint bx = x / mBlockSize;
713
uint by = y / mBlockSize;
714
const Range &range = ranges.back()[by * num_blocks_pow2 + bx];
715
JPH_ASSERT(range.mMin < range.mMax);
716
717
// Quantize to mBitsPerSample bits, note that mSampleMask is reserved for indicating that there's no collision.
718
// We divide the range into mSampleMask segments and use the mid points of these segments as the quantized values.
719
// This results in a lower error than if we had quantized our data using the lowest point of all these segments.
720
float h_min = min_value + range.mMin / scale;
721
float h_delta = float(range.mMax - range.mMin) / scale;
722
float quantized_height = floor((h - h_min) * float(mSampleMask) / h_delta);
723
output_value = uint32(Clamp((int)quantized_height, 0, int(mSampleMask) - 1)); // mSampleMask is reserved as 'no collision value'
724
}
725
726
// Store the sample
727
uint byte_pos = sample >> 3;
728
uint bit_pos = sample & 0b111;
729
output_value <<= bit_pos;
730
JPH_ASSERT(byte_pos + 1 < mHeightSamplesSize);
731
mHeightSamples[byte_pos] |= uint8(output_value);
732
mHeightSamples[byte_pos + 1] |= uint8(output_value >> 8);
733
sample += inSettings.mBitsPerSample;
734
}
735
736
// Calculate the active edges
737
CalculateActiveEdges(inSettings);
738
739
// Compress material indices
740
if (mMaterials.size() > 1 || inSettings.mMaterialsCapacity > 1)
741
StoreMaterialIndices(inSettings);
742
743
outResult.Set(this);
744
}
745
746
HeightFieldShape::~HeightFieldShape()
747
{
748
if (mRangeBlocks != nullptr)
749
AlignedFree(mRangeBlocks);
750
}
751
752
Ref<HeightFieldShape> HeightFieldShape::Clone() const
753
{
754
Ref<HeightFieldShape> clone = new HeightFieldShape;
755
clone->SetUserData(GetUserData());
756
757
clone->mOffset = mOffset;
758
clone->mScale = mScale;
759
clone->mSampleCount = mSampleCount;
760
clone->mBlockSize = mBlockSize;
761
clone->mBitsPerSample = mBitsPerSample;
762
clone->mSampleMask = mSampleMask;
763
clone->mMinSample = mMinSample;
764
clone->mMaxSample = mMaxSample;
765
766
clone->AllocateBuffers();
767
memcpy(clone->mRangeBlocks, mRangeBlocks, mRangeBlocksSize * sizeof(RangeBlock) + mHeightSamplesSize + mActiveEdgesSize); // Copy the entire buffer in 1 go
768
769
clone->mMaterials.reserve(mMaterials.capacity()); // Ensure we keep the capacity of the original
770
clone->mMaterials = mMaterials;
771
clone->mMaterialIndices = mMaterialIndices;
772
clone->mNumBitsPerMaterialIndex = mNumBitsPerMaterialIndex;
773
774
#ifdef JPH_DEBUG_RENDERER
775
clone->mGeometry = mGeometry;
776
clone->mCachedUseMaterialColors = mCachedUseMaterialColors;
777
#endif // JPH_DEBUG_RENDERER
778
779
return clone;
780
}
781
782
inline void HeightFieldShape::sGetRangeBlockOffsetAndStride(uint inNumBlocks, uint inMaxLevel, uint &outRangeBlockOffset, uint &outRangeBlockStride)
783
{
784
outRangeBlockOffset = sGridOffsets[inMaxLevel - 1];
785
outRangeBlockStride = (inNumBlocks + 1) >> 1;
786
}
787
788
inline void HeightFieldShape::GetRangeBlock(uint inBlockX, uint inBlockY, uint inRangeBlockOffset, uint inRangeBlockStride, RangeBlock *&outBlock, uint &outIndexInBlock)
789
{
790
JPH_ASSERT(inBlockX < GetNumBlocks() && inBlockY < GetNumBlocks());
791
792
// Convert to location of range block
793
uint rbx = inBlockX >> 1;
794
uint rby = inBlockY >> 1;
795
outIndexInBlock = ((inBlockY & 1) << 1) + (inBlockX & 1);
796
797
uint offset = inRangeBlockOffset + rby * inRangeBlockStride + rbx;
798
JPH_ASSERT(offset < mRangeBlocksSize);
799
outBlock = mRangeBlocks + offset;
800
}
801
802
inline void HeightFieldShape::GetBlockOffsetAndScale(uint inBlockX, uint inBlockY, uint inRangeBlockOffset, uint inRangeBlockStride, float &outBlockOffset, float &outBlockScale) const
803
{
804
JPH_ASSERT(inBlockX < GetNumBlocks() && inBlockY < GetNumBlocks());
805
806
// Convert to location of range block
807
uint rbx = inBlockX >> 1;
808
uint rby = inBlockY >> 1;
809
uint n = ((inBlockY & 1) << 1) + (inBlockX & 1);
810
811
// Calculate offset and scale
812
uint offset = inRangeBlockOffset + rby * inRangeBlockStride + rbx;
813
JPH_ASSERT(offset < mRangeBlocksSize);
814
const RangeBlock &block = mRangeBlocks[offset];
815
outBlockOffset = float(block.mMin[n]);
816
outBlockScale = float(block.mMax[n] - block.mMin[n]) / float(mSampleMask);
817
}
818
819
inline uint8 HeightFieldShape::GetHeightSample(uint inX, uint inY) const
820
{
821
JPH_ASSERT(inX < mSampleCount);
822
JPH_ASSERT(inY < mSampleCount);
823
824
// Determine bit position of sample
825
uint sample = (inY * mSampleCount + inX) * uint(mBitsPerSample);
826
uint byte_pos = sample >> 3;
827
uint bit_pos = sample & 0b111;
828
829
// Fetch the height sample value
830
JPH_ASSERT(byte_pos + 1 < mHeightSamplesSize);
831
const uint8 *height_samples = mHeightSamples + byte_pos;
832
uint16 height_sample = uint16(height_samples[0]) | uint16(uint16(height_samples[1]) << 8);
833
return uint8(height_sample >> bit_pos) & mSampleMask;
834
}
835
836
inline Vec3 HeightFieldShape::GetPosition(uint inX, uint inY, float inBlockOffset, float inBlockScale, bool &outNoCollision) const
837
{
838
// Get quantized value
839
uint8 height_sample = GetHeightSample(inX, inY);
840
outNoCollision = height_sample == mSampleMask;
841
842
// Add 0.5 to the quantized value to minimize the error (see constructor)
843
return mOffset + mScale * Vec3(float(inX), inBlockOffset + (0.5f + height_sample) * inBlockScale, float(inY));
844
}
845
846
Vec3 HeightFieldShape::GetPosition(uint inX, uint inY) const
847
{
848
// Test if there are any samples
849
if (mHeightSamplesSize == 0)
850
return mOffset + mScale * Vec3(float(inX), 0.0f, float(inY));
851
852
// Get block location
853
uint bx = inX / mBlockSize;
854
uint by = inY / mBlockSize;
855
856
// Calculate offset and stride
857
uint num_blocks = GetNumBlocks();
858
uint range_block_offset, range_block_stride;
859
sGetRangeBlockOffsetAndStride(num_blocks, sGetMaxLevel(num_blocks), range_block_offset, range_block_stride);
860
861
float offset, scale;
862
GetBlockOffsetAndScale(bx, by, range_block_offset, range_block_stride, offset, scale);
863
864
bool no_collision;
865
return GetPosition(inX, inY, offset, scale, no_collision);
866
}
867
868
bool HeightFieldShape::IsNoCollision(uint inX, uint inY) const
869
{
870
return mHeightSamplesSize == 0 || GetHeightSample(inX, inY) == mSampleMask;
871
}
872
873
bool HeightFieldShape::ProjectOntoSurface(Vec3Arg inLocalPosition, Vec3 &outSurfacePosition, SubShapeID &outSubShapeID) const
874
{
875
// Check if we have collision
876
if (mHeightSamplesSize == 0)
877
return false;
878
879
// Convert coordinate to integer space
880
Vec3 integer_space = (inLocalPosition - mOffset) / mScale;
881
882
// Get x coordinate and fraction
883
float x_frac = integer_space.GetX();
884
if (x_frac < 0.0f || x_frac >= mSampleCount - 1)
885
return false;
886
uint x = (uint)floor(x_frac);
887
x_frac -= x;
888
889
// Get y coordinate and fraction
890
float y_frac = integer_space.GetZ();
891
if (y_frac < 0.0f || y_frac >= mSampleCount - 1)
892
return false;
893
uint y = (uint)floor(y_frac);
894
y_frac -= y;
895
896
// If one of the diagonal points doesn't have collision, we don't have a height at this location
897
if (IsNoCollision(x, y) || IsNoCollision(x + 1, y + 1))
898
return false;
899
900
if (y_frac >= x_frac)
901
{
902
// Left bottom triangle, test the 3rd point
903
if (IsNoCollision(x, y + 1))
904
return false;
905
906
// Interpolate height value
907
Vec3 v1 = GetPosition(x, y);
908
Vec3 v2 = GetPosition(x, y + 1);
909
Vec3 v3 = GetPosition(x + 1, y + 1);
910
outSurfacePosition = v1 + y_frac * (v2 - v1) + x_frac * (v3 - v2);
911
SubShapeIDCreator creator;
912
outSubShapeID = EncodeSubShapeID(creator, x, y, 0);
913
return true;
914
}
915
else
916
{
917
// Right top triangle, test the third point
918
if (IsNoCollision(x + 1, y))
919
return false;
920
921
// Interpolate height value
922
Vec3 v1 = GetPosition(x, y);
923
Vec3 v2 = GetPosition(x + 1, y + 1);
924
Vec3 v3 = GetPosition(x + 1, y);
925
outSurfacePosition = v1 + y_frac * (v2 - v3) + x_frac * (v3 - v1);
926
SubShapeIDCreator creator;
927
outSubShapeID = EncodeSubShapeID(creator, x, y, 1);
928
return true;
929
}
930
}
931
932
void HeightFieldShape::GetHeights(uint inX, uint inY, uint inSizeX, uint inSizeY, float *outHeights, intptr_t inHeightsStride) const
933
{
934
if (inSizeX == 0 || inSizeY == 0)
935
return;
936
937
JPH_ASSERT(inX % mBlockSize == 0 && inY % mBlockSize == 0);
938
JPH_ASSERT(inX < mSampleCount && inY < mSampleCount);
939
JPH_ASSERT(inX + inSizeX <= mSampleCount && inY + inSizeY <= mSampleCount);
940
941
// Test if there are any samples
942
if (mHeightSamplesSize == 0)
943
{
944
// No samples, return the offset
945
float offset = mOffset.GetY();
946
for (uint y = 0; y < inSizeY; ++y, outHeights += inHeightsStride)
947
for (uint x = 0; x < inSizeX; ++x)
948
outHeights[x] = offset;
949
}
950
else
951
{
952
// Calculate offset and stride
953
uint num_blocks = GetNumBlocks();
954
uint range_block_offset, range_block_stride;
955
sGetRangeBlockOffsetAndStride(num_blocks, sGetMaxLevel(num_blocks), range_block_offset, range_block_stride);
956
957
// Loop over blocks
958
uint block_start_x = inX / mBlockSize;
959
uint block_start_y = inY / mBlockSize;
960
uint num_blocks_x = inSizeX / mBlockSize;
961
uint num_blocks_y = inSizeY / mBlockSize;
962
for (uint block_y = 0; block_y < num_blocks_y; ++block_y)
963
for (uint block_x = 0; block_x < num_blocks_x; ++block_x)
964
{
965
// Get offset and scale for block
966
float offset, scale;
967
GetBlockOffsetAndScale(block_start_x + block_x, block_start_y + block_y, range_block_offset, range_block_stride, offset, scale);
968
969
// Adjust by global offset and scale
970
// Note: This is the math applied in GetPosition() written out to reduce calculations in the inner loop
971
scale *= mScale.GetY();
972
offset = mOffset.GetY() + mScale.GetY() * offset + 0.5f * scale;
973
974
// Loop over samples in block
975
for (uint sample_y = 0; sample_y < mBlockSize; ++sample_y)
976
for (uint sample_x = 0; sample_x < mBlockSize; ++sample_x)
977
{
978
// Calculate output coordinate
979
uint output_x = block_x * mBlockSize + sample_x;
980
uint output_y = block_y * mBlockSize + sample_y;
981
982
// Get quantized value
983
uint8 height_sample = GetHeightSample(inX + output_x, inY + output_y);
984
985
// Dequantize
986
float h = height_sample != mSampleMask? offset + height_sample * scale : cNoCollisionValue;
987
outHeights[output_y * inHeightsStride + output_x] = h;
988
}
989
}
990
}
991
}
992
993
void HeightFieldShape::SetHeights(uint inX, uint inY, uint inSizeX, uint inSizeY, const float *inHeights, intptr_t inHeightsStride, TempAllocator &inAllocator, float inActiveEdgeCosThresholdAngle)
994
{
995
if (inSizeX == 0 || inSizeY == 0)
996
return;
997
998
JPH_ASSERT(mHeightSamplesSize > 0);
999
JPH_ASSERT(inX % mBlockSize == 0 && inY % mBlockSize == 0);
1000
JPH_ASSERT(inX < mSampleCount && inY < mSampleCount);
1001
JPH_ASSERT(inX + inSizeX <= mSampleCount && inY + inSizeY <= mSampleCount);
1002
1003
// If we have a block in negative x/y direction, we will affect its range so we need to take it into account
1004
bool need_temp_heights = false;
1005
uint affected_x = inX;
1006
uint affected_y = inY;
1007
uint affected_size_x = inSizeX;
1008
uint affected_size_y = inSizeY;
1009
if (inX > 0) { affected_x -= mBlockSize; affected_size_x += mBlockSize; need_temp_heights = true; }
1010
if (inY > 0) { affected_y -= mBlockSize; affected_size_y += mBlockSize; need_temp_heights = true; }
1011
1012
// If we have a block in positive x/y direction, our ranges are affected by it so we need to take it into account
1013
uint heights_size_x = affected_size_x;
1014
uint heights_size_y = affected_size_y;
1015
if (inX + inSizeX < mSampleCount) { heights_size_x += mBlockSize; need_temp_heights = true; }
1016
if (inY + inSizeY < mSampleCount) { heights_size_y += mBlockSize; need_temp_heights = true; }
1017
1018
// Get heights for affected area
1019
const float *heights;
1020
intptr_t heights_stride;
1021
float *temp_heights;
1022
if (need_temp_heights)
1023
{
1024
// 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)
1025
temp_heights = (float *)inAllocator.Allocate(heights_size_x * heights_size_y * sizeof(float));
1026
heights = temp_heights;
1027
heights_stride = heights_size_x;
1028
1029
// We need to fill in the following areas:
1030
//
1031
// +-----------------+
1032
// | 2 |
1033
// |---+---------+---|
1034
// | | | |
1035
// | 3 | 1 | 4 |
1036
// | | | |
1037
// |---+---------+---|
1038
// | 5 |
1039
// +-----------------+
1040
//
1041
// 1. The area that is affected by the new heights (we just copy these)
1042
// 2-5. These areas are either needed to calculate the range of the affected blocks or they need to be recompressed with a different range
1043
uint offset_x = inX - affected_x;
1044
uint offset_y = inY - affected_y;
1045
1046
// Area 2
1047
GetHeights(affected_x, affected_y, heights_size_x, offset_y, temp_heights, heights_size_x);
1048
float *area3_start = temp_heights + offset_y * heights_size_x;
1049
1050
// Area 3
1051
GetHeights(affected_x, inY, offset_x, inSizeY, area3_start, heights_size_x);
1052
1053
// Area 1
1054
float *area1_start = area3_start + offset_x;
1055
for (uint y = 0; y < inSizeY; ++y, area1_start += heights_size_x, inHeights += inHeightsStride)
1056
memcpy(area1_start, inHeights, inSizeX * sizeof(float));
1057
1058
// Area 4
1059
uint area4_x = inX + inSizeX;
1060
GetHeights(area4_x, inY, affected_x + heights_size_x - area4_x, inSizeY, area3_start + area4_x - affected_x, heights_size_x);
1061
1062
// Area 5
1063
uint area5_y = inY + inSizeY;
1064
float *area5_start = temp_heights + (area5_y - affected_y) * heights_size_x;
1065
GetHeights(affected_x, area5_y, heights_size_x, affected_y + heights_size_y - area5_y, area5_start, heights_size_x);
1066
}
1067
else
1068
{
1069
// We can directly use the input buffer because there are no extra edges to take into account
1070
heights = inHeights;
1071
heights_stride = inHeightsStride;
1072
temp_heights = nullptr;
1073
}
1074
1075
// Calculate offset and stride
1076
uint num_blocks = GetNumBlocks();
1077
uint range_block_offset, range_block_stride;
1078
uint max_level = sGetMaxLevel(num_blocks);
1079
sGetRangeBlockOffsetAndStride(num_blocks, max_level, range_block_offset, range_block_stride);
1080
1081
// Loop over blocks
1082
uint block_start_x = affected_x / mBlockSize;
1083
uint block_start_y = affected_y / mBlockSize;
1084
uint num_blocks_x = affected_size_x / mBlockSize;
1085
uint num_blocks_y = affected_size_y / mBlockSize;
1086
for (uint block_y = 0, sample_start_y = 0; block_y < num_blocks_y; ++block_y, sample_start_y += mBlockSize)
1087
for (uint block_x = 0, sample_start_x = 0; block_x < num_blocks_x; ++block_x, sample_start_x += mBlockSize)
1088
{
1089
// Determine quantized min and max value for block
1090
// Note that we need to include 1 extra row in the positive x/y direction to account for connecting triangles
1091
int min_value = 0xffff;
1092
int max_value = 0;
1093
uint sample_x_end = min(sample_start_x + mBlockSize + 1, mSampleCount - affected_x);
1094
uint sample_y_end = min(sample_start_y + mBlockSize + 1, mSampleCount - affected_y);
1095
for (uint sample_y = sample_start_y; sample_y < sample_y_end; ++sample_y)
1096
for (uint sample_x = sample_start_x; sample_x < sample_x_end; ++sample_x)
1097
{
1098
float h = heights[sample_y * heights_stride + sample_x];
1099
if (h != cNoCollisionValue)
1100
{
1101
int quantized_height = Clamp((int)floor((h - mOffset.GetY()) / mScale.GetY()), 0, int(cMaxHeightValue16 - 1));
1102
min_value = min(min_value, quantized_height);
1103
max_value = max(max_value, quantized_height + 1);
1104
}
1105
}
1106
if (min_value > max_value)
1107
min_value = max_value = cNoCollisionValue16;
1108
1109
// Update range for block
1110
RangeBlock *range_block;
1111
uint index_in_block;
1112
GetRangeBlock(block_start_x + block_x, block_start_y + block_y, range_block_offset, range_block_stride, range_block, index_in_block);
1113
range_block->mMin[index_in_block] = uint16(min_value);
1114
range_block->mMax[index_in_block] = uint16(max_value);
1115
1116
// Get offset and scale for block
1117
float offset_block = float(min_value);
1118
float scale_block = float(max_value - min_value) / float(mSampleMask);
1119
1120
// Calculate scale and offset using the formula used in GetPosition() solved for the quantized height (excluding 0.5 because we round down while quantizing)
1121
float scale = scale_block * mScale.GetY();
1122
float offset = mOffset.GetY() + offset_block * mScale.GetY();
1123
1124
// Loop over samples in block
1125
sample_x_end = sample_start_x + mBlockSize;
1126
sample_y_end = sample_start_y + mBlockSize;
1127
for (uint sample_y = sample_start_y; sample_y < sample_y_end; ++sample_y)
1128
for (uint sample_x = sample_start_x; sample_x < sample_x_end; ++sample_x)
1129
{
1130
// Quantize height
1131
float h = heights[sample_y * heights_stride + sample_x];
1132
uint8 quantized_height = h != cNoCollisionValue? uint8(Clamp((int)floor((h - offset) / scale), 0, int(mSampleMask) - 1)) : mSampleMask;
1133
1134
// Determine bit position of sample
1135
uint sample = ((affected_y + sample_y) * mSampleCount + affected_x + sample_x) * uint(mBitsPerSample);
1136
uint byte_pos = sample >> 3;
1137
uint bit_pos = sample & 0b111;
1138
1139
// Update the height value sample
1140
JPH_ASSERT(byte_pos + 1 < mHeightSamplesSize);
1141
uint8 *height_samples = mHeightSamples + byte_pos;
1142
uint16 height_sample = uint16(height_samples[0]) | uint16(uint16(height_samples[1]) << 8);
1143
height_sample &= ~(uint16(mSampleMask) << bit_pos);
1144
height_sample |= uint16(quantized_height) << bit_pos;
1145
height_samples[0] = uint8(height_sample);
1146
height_samples[1] = uint8(height_sample >> 8);
1147
}
1148
}
1149
1150
// Update active edges
1151
// Note that we must take an extra row on all sides to account for connecting triangles
1152
uint ae_x = inX > 1? inX - 2 : 0;
1153
uint ae_y = inY > 1? inY - 2 : 0;
1154
uint ae_sx = min(inX + inSizeX + 1, mSampleCount - 1) - ae_x;
1155
uint ae_sy = min(inY + inSizeY + 1, mSampleCount - 1) - ae_y;
1156
CalculateActiveEdges(ae_x, ae_y, ae_sx, ae_sy, heights, affected_x, affected_y, heights_stride, 1.0f, inActiveEdgeCosThresholdAngle, inAllocator);
1157
1158
// Free temporary buffer
1159
if (temp_heights != nullptr)
1160
inAllocator.Free(temp_heights, heights_size_x * heights_size_y * sizeof(float));
1161
1162
// Update hierarchy of range blocks
1163
while (max_level > 1)
1164
{
1165
// Get offset and stride for destination blocks
1166
uint dst_range_block_offset, dst_range_block_stride;
1167
sGetRangeBlockOffsetAndStride(num_blocks >> 1, max_level - 1, dst_range_block_offset, dst_range_block_stride);
1168
1169
// 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 that
1170
if (block_start_x & 1) { --block_start_x; ++num_blocks_x; }
1171
if (block_start_y & 1) { --block_start_y; ++num_blocks_y; }
1172
1173
// Loop over all affected blocks
1174
uint block_end_x = block_start_x + num_blocks_x;
1175
uint block_end_y = block_start_y + num_blocks_y;
1176
for (uint block_y = block_start_y; block_y < block_end_y; block_y += 2)
1177
for (uint block_x = block_start_x; block_x < block_end_x; block_x += 2)
1178
{
1179
// Get source range block
1180
RangeBlock *src_range_block;
1181
uint index_in_src_block;
1182
GetRangeBlock(block_x, block_y, range_block_offset, range_block_stride, src_range_block, index_in_src_block);
1183
1184
// Determine quantized min and max value for the entire 2x2 block
1185
uint16 min_value = 0xffff;
1186
uint16 max_value = 0;
1187
for (uint i = 0; i < 4; ++i)
1188
if (src_range_block->mMin[i] != cNoCollisionValue16)
1189
{
1190
min_value = min(min_value, src_range_block->mMin[i]);
1191
max_value = max(max_value, src_range_block->mMax[i]);
1192
}
1193
1194
// Write to destination block
1195
RangeBlock *dst_range_block;
1196
uint index_in_dst_block;
1197
GetRangeBlock(block_x >> 1, block_y >> 1, dst_range_block_offset, dst_range_block_stride, dst_range_block, index_in_dst_block);
1198
dst_range_block->mMin[index_in_dst_block] = uint16(min_value);
1199
dst_range_block->mMax[index_in_dst_block] = uint16(max_value);
1200
}
1201
1202
// Go up one level
1203
--max_level;
1204
num_blocks >>= 1;
1205
block_start_x >>= 1;
1206
block_start_y >>= 1;
1207
num_blocks_x = min((num_blocks_x + 1) >> 1, num_blocks);
1208
num_blocks_y = min((num_blocks_y + 1) >> 1, num_blocks);
1209
1210
// Update stride and offset for source to old destination
1211
range_block_offset = dst_range_block_offset;
1212
range_block_stride = dst_range_block_stride;
1213
}
1214
1215
// Calculate new min and max sample for the entire height field
1216
mMinSample = 0xffff;
1217
mMaxSample = 0;
1218
for (uint i = 0; i < 4; ++i)
1219
if (mRangeBlocks[0].mMin[i] != cNoCollisionValue16)
1220
{
1221
mMinSample = min(mMinSample, mRangeBlocks[0].mMin[i]);
1222
mMaxSample = max(mMaxSample, mRangeBlocks[0].mMax[i]);
1223
}
1224
1225
#ifdef JPH_DEBUG_RENDERER
1226
// Invalidate temporary rendering data
1227
mGeometry.clear();
1228
#endif
1229
}
1230
1231
void HeightFieldShape::GetMaterials(uint inX, uint inY, uint inSizeX, uint inSizeY, uint8 *outMaterials, intptr_t inMaterialsStride) const
1232
{
1233
if (inSizeX == 0 || inSizeY == 0)
1234
return;
1235
1236
if (mMaterialIndices.empty())
1237
{
1238
// Return all 0's
1239
for (uint y = 0; y < inSizeY; ++y)
1240
{
1241
uint8 *out_indices = outMaterials + y * inMaterialsStride;
1242
for (uint x = 0; x < inSizeX; ++x)
1243
*out_indices++ = 0;
1244
}
1245
return;
1246
}
1247
1248
JPH_ASSERT(inX < mSampleCount && inY < mSampleCount);
1249
JPH_ASSERT(inX + inSizeX < mSampleCount && inY + inSizeY < mSampleCount);
1250
1251
uint count_min_1 = mSampleCount - 1;
1252
uint16 material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1);
1253
1254
for (uint y = 0; y < inSizeY; ++y)
1255
{
1256
// Calculate input position
1257
uint bit_pos = (inX + (inY + y) * count_min_1) * mNumBitsPerMaterialIndex;
1258
const uint8 *in_indices = mMaterialIndices.data() + (bit_pos >> 3);
1259
bit_pos &= 0b111;
1260
1261
// Calculate output position
1262
uint8 *out_indices = outMaterials + y * inMaterialsStride;
1263
1264
for (uint x = 0; x < inSizeX; ++x)
1265
{
1266
// Get material index
1267
uint16 material_index = uint16(in_indices[0]) + uint16(uint16(in_indices[1]) << 8);
1268
material_index >>= bit_pos;
1269
material_index &= material_index_mask;
1270
*out_indices = uint8(material_index);
1271
1272
// Go to the next index
1273
bit_pos += mNumBitsPerMaterialIndex;
1274
in_indices += bit_pos >> 3;
1275
bit_pos &= 0b111;
1276
++out_indices;
1277
}
1278
}
1279
}
1280
1281
bool HeightFieldShape::SetMaterials(uint inX, uint inY, uint inSizeX, uint inSizeY, const uint8 *inMaterials, intptr_t inMaterialsStride, const PhysicsMaterialList *inMaterialList, TempAllocator &inAllocator)
1282
{
1283
if (inSizeX == 0 || inSizeY == 0)
1284
return true;
1285
1286
JPH_ASSERT(inX < mSampleCount && inY < mSampleCount);
1287
JPH_ASSERT(inX + inSizeX < mSampleCount && inY + inSizeY < mSampleCount);
1288
1289
// Remap materials
1290
uint material_remap_table_size = uint(inMaterialList != nullptr? inMaterialList->size() : mMaterials.size());
1291
uint8 *material_remap_table = (uint8 *)inAllocator.Allocate(material_remap_table_size);
1292
JPH_SCOPE_EXIT([&inAllocator, material_remap_table, material_remap_table_size]{ inAllocator.Free(material_remap_table, material_remap_table_size); });
1293
if (inMaterialList != nullptr)
1294
{
1295
// Conservatively reserve more space if the incoming material list is bigger
1296
if (inMaterialList->size() > mMaterials.size())
1297
mMaterials.reserve(inMaterialList->size());
1298
1299
// Create a remap table
1300
uint8 *remap_entry = material_remap_table;
1301
for (const PhysicsMaterial *material : *inMaterialList)
1302
{
1303
// Try to find it in the existing list
1304
PhysicsMaterialList::const_iterator it = std::find(mMaterials.begin(), mMaterials.end(), material);
1305
if (it != mMaterials.end())
1306
{
1307
// Found it, calculate index
1308
*remap_entry = uint8(it - mMaterials.begin());
1309
}
1310
else
1311
{
1312
// Not found, add it
1313
if (mMaterials.size() >= 256)
1314
{
1315
// We can't have more than 256 materials since we use uint8 as indices
1316
return false;
1317
}
1318
*remap_entry = uint8(mMaterials.size());
1319
mMaterials.push_back(material);
1320
}
1321
++remap_entry;
1322
}
1323
}
1324
else
1325
{
1326
// No remapping
1327
for (uint i = 0; i < material_remap_table_size; ++i)
1328
material_remap_table[i] = uint8(i);
1329
}
1330
1331
if (mMaterials.size() == 1)
1332
{
1333
// Only 1 material, we don't need to store the material indices
1334
return true;
1335
}
1336
1337
// Check if we need to resize the material indices array
1338
uint count_min_1 = mSampleCount - 1;
1339
uint32 new_bits_per_material_index = 32 - CountLeadingZeros((uint32)mMaterials.size() - 1);
1340
JPH_ASSERT(mNumBitsPerMaterialIndex <= 8 && new_bits_per_material_index <= 8);
1341
if (new_bits_per_material_index > mNumBitsPerMaterialIndex)
1342
{
1343
// Resize the material indices array
1344
mMaterialIndices.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 uint16
1345
1346
// Calculate old and new mask
1347
uint16 old_material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1);
1348
uint16 new_material_index_mask = uint16((1 << new_bits_per_material_index) - 1);
1349
1350
// Loop through the array backwards to avoid overwriting data
1351
int in_bit_pos = (count_min_1 * count_min_1 - 1) * mNumBitsPerMaterialIndex;
1352
const uint8 *in_indices = mMaterialIndices.data() + (in_bit_pos >> 3);
1353
in_bit_pos &= 0b111;
1354
int out_bit_pos = (count_min_1 * count_min_1 - 1) * new_bits_per_material_index;
1355
uint8 *out_indices = mMaterialIndices.data() + (out_bit_pos >> 3);
1356
out_bit_pos &= 0b111;
1357
1358
while (out_indices >= mMaterialIndices.data())
1359
{
1360
// Read the material index
1361
uint16 material_index = uint16(in_indices[0]) + uint16(uint16(in_indices[1]) << 8);
1362
material_index >>= in_bit_pos;
1363
material_index &= old_material_index_mask;
1364
1365
// Write the material index
1366
uint16 output_data = uint16(out_indices[0]) + uint16(uint16(out_indices[1]) << 8);
1367
output_data &= ~(new_material_index_mask << out_bit_pos);
1368
output_data |= material_index << out_bit_pos;
1369
out_indices[0] = uint8(output_data);
1370
out_indices[1] = uint8(output_data >> 8);
1371
1372
// Go to the previous index
1373
in_bit_pos -= int(mNumBitsPerMaterialIndex);
1374
in_indices += in_bit_pos >> 3;
1375
in_bit_pos &= 0b111;
1376
out_bit_pos -= int(new_bits_per_material_index);
1377
out_indices += out_bit_pos >> 3;
1378
out_bit_pos &= 0b111;
1379
}
1380
1381
// Accept the new bits per material index
1382
mNumBitsPerMaterialIndex = new_bits_per_material_index;
1383
}
1384
1385
uint16 material_index_mask = uint16((1 << mNumBitsPerMaterialIndex) - 1);
1386
for (uint y = 0; y < inSizeY; ++y)
1387
{
1388
// Calculate input position
1389
const uint8 *in_indices = inMaterials + y * inMaterialsStride;
1390
1391
// Calculate output position
1392
uint bit_pos = (inX + (inY + y) * count_min_1) * mNumBitsPerMaterialIndex;
1393
uint8 *out_indices = mMaterialIndices.data() + (bit_pos >> 3);
1394
bit_pos &= 0b111;
1395
1396
for (uint x = 0; x < inSizeX; ++x)
1397
{
1398
// Update material
1399
uint16 output_data = uint16(out_indices[0]) + uint16(uint16(out_indices[1]) << 8);
1400
output_data &= ~(material_index_mask << bit_pos);
1401
output_data |= material_remap_table[*in_indices] << bit_pos;
1402
out_indices[0] = uint8(output_data);
1403
out_indices[1] = uint8(output_data >> 8);
1404
1405
// Go to the next index
1406
in_indices++;
1407
bit_pos += mNumBitsPerMaterialIndex;
1408
out_indices += bit_pos >> 3;
1409
bit_pos &= 0b111;
1410
}
1411
}
1412
1413
return true;
1414
}
1415
1416
MassProperties HeightFieldShape::GetMassProperties() const
1417
{
1418
// Object should always be static, return default mass properties
1419
return MassProperties();
1420
}
1421
1422
const PhysicsMaterial *HeightFieldShape::GetMaterial(uint inX, uint inY) const
1423
{
1424
if (mMaterials.empty())
1425
return PhysicsMaterial::sDefault;
1426
if (mMaterials.size() == 1)
1427
return mMaterials[0];
1428
1429
uint count_min_1 = mSampleCount - 1;
1430
JPH_ASSERT(inX < count_min_1);
1431
JPH_ASSERT(inY < count_min_1);
1432
1433
// Calculate at which bit the material index starts
1434
uint bit_pos = (inX + inY * count_min_1) * mNumBitsPerMaterialIndex;
1435
uint byte_pos = bit_pos >> 3;
1436
bit_pos &= 0b111;
1437
1438
// Read the material index
1439
JPH_ASSERT(byte_pos + 1 < mMaterialIndices.size());
1440
const uint8 *material_indices = mMaterialIndices.data() + byte_pos;
1441
uint16 material_index = uint16(material_indices[0]) + uint16(uint16(material_indices[1]) << 8);
1442
material_index >>= bit_pos;
1443
material_index &= (1 << mNumBitsPerMaterialIndex) - 1;
1444
1445
// Return the material
1446
return mMaterials[material_index];
1447
}
1448
1449
uint HeightFieldShape::GetSubShapeIDBits() const
1450
{
1451
// Need to store X, Y and 1 extra bit to specify the triangle number in the quad
1452
return 2 * (32 - CountLeadingZeros(mSampleCount - 1)) + 1;
1453
}
1454
1455
SubShapeID HeightFieldShape::EncodeSubShapeID(const SubShapeIDCreator &inCreator, uint inX, uint inY, uint inTriangle) const
1456
{
1457
return inCreator.PushID((inX + inY * mSampleCount) * 2 + inTriangle, GetSubShapeIDBits()).GetID();
1458
}
1459
1460
void HeightFieldShape::DecodeSubShapeID(const SubShapeID &inSubShapeID, uint &outX, uint &outY, uint &outTriangle) const
1461
{
1462
// Decode sub shape id
1463
SubShapeID remainder;
1464
uint32 id = inSubShapeID.PopID(GetSubShapeIDBits(), remainder);
1465
JPH_ASSERT(remainder.IsEmpty(), "Invalid subshape ID");
1466
1467
// Get triangle index
1468
outTriangle = id & 1;
1469
id >>= 1;
1470
1471
// Fetch the x and y coordinate
1472
outX = id % mSampleCount;
1473
outY = id / mSampleCount;
1474
}
1475
1476
void HeightFieldShape::GetSubShapeCoordinates(const SubShapeID &inSubShapeID, uint &outX, uint &outY, uint &outTriangleIndex) const
1477
{
1478
DecodeSubShapeID(inSubShapeID, outX, outY, outTriangleIndex);
1479
}
1480
1481
const PhysicsMaterial *HeightFieldShape::GetMaterial(const SubShapeID &inSubShapeID) const
1482
{
1483
// Decode ID
1484
uint x, y, triangle;
1485
DecodeSubShapeID(inSubShapeID, x, y, triangle);
1486
1487
// Fetch the material
1488
return GetMaterial(x, y);
1489
}
1490
1491
Vec3 HeightFieldShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocalSurfacePosition) const
1492
{
1493
// Decode ID
1494
uint x, y, triangle;
1495
DecodeSubShapeID(inSubShapeID, x, y, triangle);
1496
1497
// Fetch vertices that both triangles share
1498
Vec3 x1y1 = GetPosition(x, y);
1499
Vec3 x2y2 = GetPosition(x + 1, y + 1);
1500
1501
// Get normal depending on which triangle was selected
1502
Vec3 normal;
1503
if (triangle == 0)
1504
{
1505
Vec3 x1y2 = GetPosition(x, y + 1);
1506
normal = (x2y2 - x1y2).Cross(x1y1 - x1y2);
1507
}
1508
else
1509
{
1510
Vec3 x2y1 = GetPosition(x + 1, y);
1511
normal = (x1y1 - x2y1).Cross(x2y2 - x2y1);
1512
}
1513
1514
return normal.Normalized();
1515
}
1516
1517
void HeightFieldShape::GetSupportingFace(const SubShapeID &inSubShapeID, Vec3Arg inDirection, Vec3Arg inScale, Mat44Arg inCenterOfMassTransform, SupportingFace &outVertices) const
1518
{
1519
// Decode ID
1520
uint x, y, triangle;
1521
DecodeSubShapeID(inSubShapeID, x, y, triangle);
1522
1523
// Fetch the triangle
1524
outVertices.resize(3);
1525
outVertices[0] = GetPosition(x, y);
1526
Vec3 v2 = GetPosition(x + 1, y + 1);
1527
if (triangle == 0)
1528
{
1529
outVertices[1] = GetPosition(x, y + 1);
1530
outVertices[2] = v2;
1531
}
1532
else
1533
{
1534
outVertices[1] = v2;
1535
outVertices[2] = GetPosition(x + 1, y);
1536
}
1537
1538
// Flip triangle if scaled inside out
1539
if (ScaleHelpers::IsInsideOut(inScale))
1540
std::swap(outVertices[1], outVertices[2]);
1541
1542
// Transform to world space
1543
Mat44 transform = inCenterOfMassTransform.PreScaled(inScale);
1544
for (Vec3 &v : outVertices)
1545
v = transform * v;
1546
}
1547
1548
inline uint8 HeightFieldShape::GetEdgeFlags(uint inX, uint inY, uint inTriangle) const
1549
{
1550
JPH_ASSERT(inX < mSampleCount - 1 && inY < mSampleCount - 1);
1551
1552
if (inTriangle == 0)
1553
{
1554
// The edge flags for this triangle are directly stored, find the right 3 bits
1555
uint bit_pos = 3 * (inX + inY * (mSampleCount - 1));
1556
uint byte_pos = bit_pos >> 3;
1557
bit_pos &= 0b111;
1558
JPH_ASSERT(byte_pos + 1 < mActiveEdgesSize);
1559
const uint8 *active_edges = mActiveEdges + byte_pos;
1560
uint16 edge_flags = uint16(active_edges[0]) + uint16(uint16(active_edges[1]) << 8);
1561
return uint8(edge_flags >> bit_pos) & 0b111;
1562
}
1563
else
1564
{
1565
// We don't store this triangle directly, we need to look at our three neighbours to construct the edge flags
1566
uint8 edge0 = (GetEdgeFlags(inX, inY, 0) & 0b100) != 0? 0b001 : 0; // Diagonal edge
1567
uint8 edge1 = inX == mSampleCount - 2 || (GetEdgeFlags(inX + 1, inY, 0) & 0b001) != 0? 0b010 : 0; // Vertical edge
1568
uint8 edge2 = inY == 0 || (GetEdgeFlags(inX, inY - 1, 0) & 0b010) != 0? 0b100 : 0; // Horizontal edge
1569
return edge0 | edge1 | edge2;
1570
}
1571
}
1572
1573
AABox HeightFieldShape::GetLocalBounds() const
1574
{
1575
if (mMinSample == cNoCollisionValue16)
1576
{
1577
// This whole height field shape doesn't have any collision, return the center point
1578
Vec3 center = mOffset + 0.5f * mScale * Vec3(float(mSampleCount - 1), 0.0f, float(mSampleCount - 1));
1579
return AABox(center, center);
1580
}
1581
else
1582
{
1583
// Bounding box based on min and max sample height
1584
Vec3 bmin = mOffset + mScale * Vec3(0.0f, float(mMinSample), 0.0f);
1585
Vec3 bmax = mOffset + mScale * Vec3(float(mSampleCount - 1), float(mMaxSample), float(mSampleCount - 1));
1586
return AABox(bmin, bmax);
1587
}
1588
}
1589
1590
#ifdef JPH_DEBUG_RENDERER
1591
void HeightFieldShape::Draw(DebugRenderer *inRenderer, RMat44Arg inCenterOfMassTransform, Vec3Arg inScale, ColorArg inColor, bool inUseMaterialColors, bool inDrawWireframe) const
1592
{
1593
// Don't draw anything if we don't have any collision
1594
if (mHeightSamplesSize == 0)
1595
return;
1596
1597
// Reset the batch if we switch coloring mode
1598
if (mCachedUseMaterialColors != inUseMaterialColors)
1599
{
1600
mGeometry.clear();
1601
mCachedUseMaterialColors = inUseMaterialColors;
1602
}
1603
1604
if (mGeometry.empty())
1605
{
1606
// Divide terrain in triangle batches of max 64x64x2 triangles to allow better culling of the terrain
1607
uint32 block_size = min<uint32>(mSampleCount, 64);
1608
for (uint32 by = 0; by < mSampleCount; by += block_size)
1609
for (uint32 bx = 0; bx < mSampleCount; bx += block_size)
1610
{
1611
// Create vertices for a block
1612
Array<DebugRenderer::Triangle> triangles;
1613
triangles.resize(block_size * block_size * 2);
1614
DebugRenderer::Triangle *out_tri = &triangles[0];
1615
for (uint32 y = by, max_y = min(by + block_size, mSampleCount - 1); y < max_y; ++y)
1616
for (uint32 x = bx, max_x = min(bx + block_size, mSampleCount - 1); x < max_x; ++x)
1617
if (!IsNoCollision(x, y) && !IsNoCollision(x + 1, y + 1))
1618
{
1619
Vec3 x1y1 = GetPosition(x, y);
1620
Vec3 x2y2 = GetPosition(x + 1, y + 1);
1621
Color color = inUseMaterialColors? GetMaterial(x, y)->GetDebugColor() : Color::sWhite;
1622
1623
if (!IsNoCollision(x, y + 1))
1624
{
1625
Vec3 x1y2 = GetPosition(x, y + 1);
1626
1627
x1y1.StoreFloat3(&out_tri->mV[0].mPosition);
1628
x1y2.StoreFloat3(&out_tri->mV[1].mPosition);
1629
x2y2.StoreFloat3(&out_tri->mV[2].mPosition);
1630
1631
Vec3 normal = (x2y2 - x1y2).Cross(x1y1 - x1y2).Normalized();
1632
for (DebugRenderer::Vertex &v : out_tri->mV)
1633
{
1634
v.mColor = color;
1635
v.mUV = Float2(0, 0);
1636
normal.StoreFloat3(&v.mNormal);
1637
}
1638
1639
++out_tri;
1640
}
1641
1642
if (!IsNoCollision(x + 1, y))
1643
{
1644
Vec3 x2y1 = GetPosition(x + 1, y);
1645
1646
x1y1.StoreFloat3(&out_tri->mV[0].mPosition);
1647
x2y2.StoreFloat3(&out_tri->mV[1].mPosition);
1648
x2y1.StoreFloat3(&out_tri->mV[2].mPosition);
1649
1650
Vec3 normal = (x1y1 - x2y1).Cross(x2y2 - x2y1).Normalized();
1651
for (DebugRenderer::Vertex &v : out_tri->mV)
1652
{
1653
v.mColor = color;
1654
v.mUV = Float2(0, 0);
1655
normal.StoreFloat3(&v.mNormal);
1656
}
1657
1658
++out_tri;
1659
}
1660
}
1661
1662
// Resize triangles array to actual amount of triangles written
1663
size_t num_triangles = out_tri - &triangles[0];
1664
triangles.resize(num_triangles);
1665
1666
// Create batch
1667
if (num_triangles > 0)
1668
mGeometry.push_back(new DebugRenderer::Geometry(inRenderer->CreateTriangleBatch(triangles), DebugRenderer::sCalculateBounds(&triangles[0].mV[0], int(3 * num_triangles))));
1669
}
1670
}
1671
1672
// Get transform including scale
1673
RMat44 transform = inCenterOfMassTransform.PreScaled(inScale);
1674
1675
// Test if the shape is scaled inside out
1676
DebugRenderer::ECullMode cull_mode = ScaleHelpers::IsInsideOut(inScale)? DebugRenderer::ECullMode::CullFrontFace : DebugRenderer::ECullMode::CullBackFace;
1677
1678
// Determine the draw mode
1679
DebugRenderer::EDrawMode draw_mode = inDrawWireframe? DebugRenderer::EDrawMode::Wireframe : DebugRenderer::EDrawMode::Solid;
1680
1681
// Draw the geometry
1682
for (const DebugRenderer::GeometryRef &b : mGeometry)
1683
inRenderer->DrawGeometry(transform, inColor, b, cull_mode, DebugRenderer::ECastShadow::On, draw_mode);
1684
1685
if (sDrawTriangleOutlines)
1686
{
1687
struct Visitor
1688
{
1689
JPH_INLINE explicit Visitor(const HeightFieldShape *inShape, DebugRenderer *inRenderer, RMat44Arg inTransform) :
1690
mShape(inShape),
1691
mRenderer(inRenderer),
1692
mTransform(inTransform)
1693
{
1694
}
1695
1696
JPH_INLINE bool ShouldAbort() const
1697
{
1698
return false;
1699
}
1700
1701
JPH_INLINE bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const
1702
{
1703
return true;
1704
}
1705
1706
JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
1707
{
1708
UVec4 valid = Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY);
1709
return CountAndSortTrues(valid, ioProperties);
1710
}
1711
1712
JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2) const
1713
{
1714
// Determine active edges
1715
uint8 active_edges = mShape->GetEdgeFlags(inX, inY, inTriangle);
1716
1717
// Loop through edges
1718
Vec3 v[] = { inV0, inV1, inV2 };
1719
for (uint edge_idx = 0; edge_idx < 3; ++edge_idx)
1720
{
1721
RVec3 v1 = mTransform * v[edge_idx];
1722
RVec3 v2 = mTransform * v[(edge_idx + 1) % 3];
1723
1724
// Draw active edge as a green arrow, other edges as grey
1725
if (active_edges & (1 << edge_idx))
1726
mRenderer->DrawArrow(v1, v2, Color::sGreen, 0.01f);
1727
else
1728
mRenderer->DrawLine(v1, v2, Color::sGrey);
1729
}
1730
}
1731
1732
const HeightFieldShape *mShape;
1733
DebugRenderer * mRenderer;
1734
RMat44 mTransform;
1735
};
1736
1737
Visitor visitor(this, inRenderer, inCenterOfMassTransform.PreScaled(inScale));
1738
WalkHeightField(visitor);
1739
}
1740
}
1741
#endif // JPH_DEBUG_RENDERER
1742
1743
class HeightFieldShape::DecodingContext
1744
{
1745
public:
1746
JPH_INLINE explicit DecodingContext(const HeightFieldShape *inShape) :
1747
mShape(inShape)
1748
{
1749
static_assert(std::size(sGridOffsets) == cNumBitsXY + 1, "Offsets array is not long enough");
1750
1751
// Construct root stack entry
1752
mPropertiesStack[0] = 0; // level: 0, x: 0, y: 0
1753
}
1754
1755
template <class Visitor>
1756
JPH_INLINE void WalkHeightField(Visitor &ioVisitor)
1757
{
1758
// Early out if there's no collision
1759
if (mShape->mHeightSamplesSize == 0)
1760
return;
1761
1762
// Assert that an inside-out bounding box does not collide
1763
JPH_IF_ENABLE_ASSERTS(UVec4 dummy = UVec4::sReplicate(0);)
1764
JPH_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);
1765
1766
// Precalculate values relating to sample count
1767
uint32 sample_count = mShape->mSampleCount;
1768
UVec4 sample_count_min_1 = UVec4::sReplicate(sample_count - 1);
1769
1770
// Precalculate values relating to block size
1771
uint32 block_size = mShape->mBlockSize;
1772
uint32 block_size_plus_1 = block_size + 1;
1773
uint num_blocks = mShape->GetNumBlocks();
1774
uint num_blocks_min_1 = num_blocks - 1;
1775
uint max_level = HeightFieldShape::sGetMaxLevel(num_blocks);
1776
uint32 max_stride = (num_blocks + 1) >> 1;
1777
1778
// Precalculate range block offset and stride for GetBlockOffsetAndScale
1779
uint range_block_offset, range_block_stride;
1780
sGetRangeBlockOffsetAndStride(num_blocks, max_level, range_block_offset, range_block_stride);
1781
1782
// Allocate space for vertices and 'no collision' flags
1783
int array_size = Square(block_size_plus_1);
1784
Vec3 *vertices = reinterpret_cast<Vec3 *>(JPH_STACK_ALLOC(array_size * sizeof(Vec3)));
1785
bool *no_collision = reinterpret_cast<bool *>(JPH_STACK_ALLOC(array_size * sizeof(bool)));
1786
1787
// Splat offsets
1788
Vec4 ox = mShape->mOffset.SplatX();
1789
Vec4 oy = mShape->mOffset.SplatY();
1790
Vec4 oz = mShape->mOffset.SplatZ();
1791
1792
// Splat scales
1793
Vec4 sx = mShape->mScale.SplatX();
1794
Vec4 sy = mShape->mScale.SplatY();
1795
Vec4 sz = mShape->mScale.SplatZ();
1796
1797
do
1798
{
1799
// Decode properties
1800
uint32 properties_top = mPropertiesStack[mTop];
1801
uint32 x = properties_top & cMaskBitsXY;
1802
uint32 y = (properties_top >> cNumBitsXY) & cMaskBitsXY;
1803
uint32 level = properties_top >> cLevelShift;
1804
1805
if (level >= max_level)
1806
{
1807
// Determine actual range of samples (minus one because we eventually want to iterate over the triangles, not the samples)
1808
uint32 min_x = x * block_size;
1809
uint32 max_x = min_x + block_size;
1810
uint32 min_y = y * block_size;
1811
uint32 max_y = min_y + block_size;
1812
1813
// Decompress vertices of block at (x, y)
1814
Vec3 *dst_vertex = vertices;
1815
bool *dst_no_collision = no_collision;
1816
float block_offset, block_scale;
1817
mShape->GetBlockOffsetAndScale(x, y, range_block_offset, range_block_stride, block_offset, block_scale);
1818
for (uint32 v_y = min_y; v_y < max_y; ++v_y)
1819
{
1820
for (uint32 v_x = min_x; v_x < max_x; ++v_x)
1821
{
1822
*dst_vertex = mShape->GetPosition(v_x, v_y, block_offset, block_scale, *dst_no_collision);
1823
++dst_vertex;
1824
++dst_no_collision;
1825
}
1826
1827
// Skip last column, these values come from a different block
1828
++dst_vertex;
1829
++dst_no_collision;
1830
}
1831
1832
// Decompress block (x + 1, y)
1833
uint32 max_x_decrement = 0;
1834
if (x < num_blocks_min_1)
1835
{
1836
dst_vertex = vertices + block_size;
1837
dst_no_collision = no_collision + block_size;
1838
mShape->GetBlockOffsetAndScale(x + 1, y, range_block_offset, range_block_stride, block_offset, block_scale);
1839
for (uint32 v_y = min_y; v_y < max_y; ++v_y)
1840
{
1841
*dst_vertex = mShape->GetPosition(max_x, v_y, block_offset, block_scale, *dst_no_collision);
1842
dst_vertex += block_size_plus_1;
1843
dst_no_collision += block_size_plus_1;
1844
}
1845
}
1846
else
1847
max_x_decrement = 1; // We don't have a next block, one less triangle to test
1848
1849
// Decompress block (x, y + 1)
1850
if (y < num_blocks_min_1)
1851
{
1852
uint start = block_size * block_size_plus_1;
1853
dst_vertex = vertices + start;
1854
dst_no_collision = no_collision + start;
1855
mShape->GetBlockOffsetAndScale(x, y + 1, range_block_offset, range_block_stride, block_offset, block_scale);
1856
for (uint32 v_x = min_x; v_x < max_x; ++v_x)
1857
{
1858
*dst_vertex = mShape->GetPosition(v_x, max_y, block_offset, block_scale, *dst_no_collision);
1859
++dst_vertex;
1860
++dst_no_collision;
1861
}
1862
1863
// Decompress single sample of block at (x + 1, y + 1)
1864
if (x < num_blocks_min_1)
1865
{
1866
mShape->GetBlockOffsetAndScale(x + 1, y + 1, range_block_offset, range_block_stride, block_offset, block_scale);
1867
*dst_vertex = mShape->GetPosition(max_x, max_y, block_offset, block_scale, *dst_no_collision);
1868
}
1869
}
1870
else
1871
--max_y; // We don't have a next block, one less triangle to test
1872
1873
// Update max_x (we've been using it so we couldn't update it earlier)
1874
max_x -= max_x_decrement;
1875
1876
// We're going to divide the vertices in 4 blocks to do one more runtime sub-division, calculate the ranges of those blocks
1877
struct Range
1878
{
1879
uint32 mMinX, mMinY, mNumTrianglesX, mNumTrianglesY;
1880
};
1881
uint32 half_block_size = block_size >> 1;
1882
uint32 block_size_x = max_x - min_x - half_block_size;
1883
uint32 block_size_y = max_y - min_y - half_block_size;
1884
Range ranges[] =
1885
{
1886
{ 0, 0, half_block_size, half_block_size },
1887
{ half_block_size, 0, block_size_x, half_block_size },
1888
{ 0, half_block_size, half_block_size, block_size_y },
1889
{ half_block_size, half_block_size, block_size_x, block_size_y },
1890
};
1891
1892
// Calculate the min and max of each of the blocks
1893
Mat44 block_min, block_max;
1894
for (int block = 0; block < 4; ++block)
1895
{
1896
// Get the range for this block
1897
const Range &range = ranges[block];
1898
uint32 start = range.mMinX + range.mMinY * block_size_plus_1;
1899
uint32 size_x_plus_1 = range.mNumTrianglesX + 1;
1900
uint32 size_y_plus_1 = range.mNumTrianglesY + 1;
1901
1902
// Calculate where to start reading
1903
const Vec3 *src_vertex = vertices + start;
1904
const bool *src_no_collision = no_collision + start;
1905
uint32 stride = block_size_plus_1 - size_x_plus_1;
1906
1907
// Start range with a very large inside-out box
1908
Vec3 value_min = Vec3::sReplicate(cLargeFloat);
1909
Vec3 value_max = Vec3::sReplicate(-cLargeFloat);
1910
1911
// Loop over the samples to determine the min and max of this block
1912
for (uint32 block_y = 0; block_y < size_y_plus_1; ++block_y)
1913
{
1914
for (uint32 block_x = 0; block_x < size_x_plus_1; ++block_x)
1915
{
1916
if (!*src_no_collision)
1917
{
1918
value_min = Vec3::sMin(value_min, *src_vertex);
1919
value_max = Vec3::sMax(value_max, *src_vertex);
1920
}
1921
++src_vertex;
1922
++src_no_collision;
1923
}
1924
src_vertex += stride;
1925
src_no_collision += stride;
1926
}
1927
block_min.SetColumn4(block, Vec4(value_min));
1928
block_max.SetColumn4(block, Vec4(value_max));
1929
}
1930
1931
#ifdef JPH_DEBUG_HEIGHT_FIELD
1932
// Draw the bounding boxes of the sub-nodes
1933
for (int block = 0; block < 4; ++block)
1934
{
1935
AABox bounds(block_min.GetColumn3(block), block_max.GetColumn3(block));
1936
if (bounds.IsValid())
1937
DebugRenderer::sInstance->DrawWireBox(bounds, Color::sYellow);
1938
}
1939
#endif // JPH_DEBUG_HEIGHT_FIELD
1940
1941
// Transpose so we have the mins and maxes of each of the blocks in rows instead of columns
1942
Mat44 transposed_min = block_min.Transposed();
1943
Mat44 transposed_max = block_max.Transposed();
1944
1945
// Check which blocks collide
1946
// Note: At this point we don't use our own stack but we do allow the visitor to use its own stack
1947
// to store collision distances so that we can still early out when no closer hits have been found.
1948
UVec4 colliding_blocks(0, 1, 2, 3);
1949
int 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);
1950
1951
// Loop through the results backwards (closest first)
1952
int result = num_results - 1;
1953
while (result >= 0)
1954
{
1955
// Calculate the min and max of this block
1956
uint32 block = colliding_blocks[result];
1957
const Range &range = ranges[block];
1958
uint32 block_min_x = min_x + range.mMinX;
1959
uint32 block_max_x = block_min_x + range.mNumTrianglesX;
1960
uint32 block_min_y = min_y + range.mMinY;
1961
uint32 block_max_y = block_min_y + range.mNumTrianglesY;
1962
1963
// Loop triangles
1964
for (uint32 v_y = block_min_y; v_y < block_max_y; ++v_y)
1965
for (uint32 v_x = block_min_x; v_x < block_max_x; ++v_x)
1966
{
1967
// Get first vertex
1968
const int offset = (v_y - min_y) * block_size_plus_1 + (v_x - min_x);
1969
const Vec3 *start_vertex = vertices + offset;
1970
const bool *start_no_collision = no_collision + offset;
1971
1972
// Check if vertices shared by both triangles have collision
1973
if (!start_no_collision[0] && !start_no_collision[block_size_plus_1 + 1])
1974
{
1975
// Loop 2 triangles
1976
for (uint t = 0; t < 2; ++t)
1977
{
1978
// Determine triangle vertices
1979
Vec3 v0, v1, v2;
1980
if (t == 0)
1981
{
1982
// Check third vertex
1983
if (start_no_collision[block_size_plus_1])
1984
continue;
1985
1986
// Get vertices for triangle
1987
v0 = start_vertex[0];
1988
v1 = start_vertex[block_size_plus_1];
1989
v2 = start_vertex[block_size_plus_1 + 1];
1990
}
1991
else
1992
{
1993
// Check third vertex
1994
if (start_no_collision[1])
1995
continue;
1996
1997
// Get vertices for triangle
1998
v0 = start_vertex[0];
1999
v1 = start_vertex[block_size_plus_1 + 1];
2000
v2 = start_vertex[1];
2001
}
2002
2003
#ifdef JPH_DEBUG_HEIGHT_FIELD
2004
DebugRenderer::sInstance->DrawWireTriangle(RVec3(v0), RVec3(v1), RVec3(v2), Color::sWhite);
2005
#endif
2006
2007
// Call visitor
2008
ioVisitor.VisitTriangle(v_x, v_y, t, v0, v1, v2);
2009
2010
// Check if we're done
2011
if (ioVisitor.ShouldAbort())
2012
return;
2013
}
2014
}
2015
}
2016
2017
// Fetch next block until we find one that the visitor wants to see
2018
do
2019
--result;
2020
while (result >= 0 && !ioVisitor.ShouldVisitRangeBlock(mTop + result));
2021
}
2022
}
2023
else
2024
{
2025
// Visit child grid
2026
uint32 stride = min(1U << level, max_stride); // At the most detailed level we store a non-power of 2 number of blocks
2027
uint32 offset = sGridOffsets[level] + stride * y + x;
2028
2029
// Decode min/max height
2030
JPH_ASSERT(offset < mShape->mRangeBlocksSize);
2031
UVec4 block = UVec4::sLoadInt4Aligned(reinterpret_cast<const uint32 *>(&mShape->mRangeBlocks[offset]));
2032
Vec4 bounds_miny = oy + sy * block.Expand4Uint16Lo().ToFloat();
2033
Vec4 bounds_maxy = oy + sy * block.Expand4Uint16Hi().ToFloat();
2034
2035
// Calculate size of one cell at this grid level
2036
UVec4 internal_cell_size = UVec4::sReplicate(block_size << (max_level - level - 1)); // subtract 1 from level because we have an internal grid of 2x2
2037
2038
// Calculate min/max x and z
2039
UVec4 two_x = UVec4::sReplicate(2 * x); // multiply by two because we have an internal grid of 2x2
2040
Vec4 bounds_minx = ox + sx * (internal_cell_size * (two_x + UVec4(0, 1, 0, 1))).ToFloat();
2041
Vec4 bounds_maxx = ox + sx * UVec4::sMin(internal_cell_size * (two_x + UVec4(1, 2, 1, 2)), sample_count_min_1).ToFloat();
2042
2043
UVec4 two_y = UVec4::sReplicate(2 * y);
2044
Vec4 bounds_minz = oz + sz * (internal_cell_size * (two_y + UVec4(0, 0, 1, 1))).ToFloat();
2045
Vec4 bounds_maxz = oz + sz * UVec4::sMin(internal_cell_size * (two_y + UVec4(1, 1, 2, 2)), sample_count_min_1).ToFloat();
2046
2047
// Calculate properties of child blocks
2048
UVec4 properties = UVec4::sReplicate(((level + 1) << cLevelShift) + (y << (cNumBitsXY + 1)) + (x << 1)) + UVec4(0, 1, 1 << cNumBitsXY, (1 << cNumBitsXY) + 1);
2049
2050
#ifdef JPH_DEBUG_HEIGHT_FIELD
2051
// Draw boxes
2052
for (int i = 0; i < 4; ++i)
2053
{
2054
AABox b(Vec3(bounds_minx[i], bounds_miny[i], bounds_minz[i]), Vec3(bounds_maxx[i], bounds_maxy[i], bounds_maxz[i]));
2055
if (b.IsValid())
2056
DebugRenderer::sInstance->DrawWireBox(b, Color::sGreen);
2057
}
2058
#endif
2059
2060
// Check which sub nodes to visit
2061
int num_results = ioVisitor.VisitRangeBlock(bounds_minx, bounds_miny, bounds_minz, bounds_maxx, bounds_maxy, bounds_maxz, properties, mTop);
2062
2063
// Push them onto the stack
2064
JPH_ASSERT(mTop + 4 < cStackSize);
2065
properties.StoreInt4(&mPropertiesStack[mTop]);
2066
mTop += num_results;
2067
}
2068
2069
// Check if we're done
2070
if (ioVisitor.ShouldAbort())
2071
return;
2072
2073
// Fetch next node until we find one that the visitor wants to see
2074
do
2075
--mTop;
2076
while (mTop >= 0 && !ioVisitor.ShouldVisitRangeBlock(mTop));
2077
}
2078
while (mTop >= 0);
2079
}
2080
2081
// This can be used to have the visitor early out (ioVisitor.ShouldAbort() returns true) and later continue again (call WalkHeightField() again)
2082
JPH_INLINE bool IsDoneWalking() const
2083
{
2084
return mTop < 0;
2085
}
2086
2087
private:
2088
const HeightFieldShape * mShape;
2089
int mTop = 0;
2090
uint32 mPropertiesStack[cStackSize];
2091
};
2092
2093
template <class Visitor>
2094
void HeightFieldShape::WalkHeightField(Visitor &ioVisitor) const
2095
{
2096
DecodingContext ctx(this);
2097
ctx.WalkHeightField(ioVisitor);
2098
}
2099
2100
bool HeightFieldShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const
2101
{
2102
JPH_PROFILE_FUNCTION();
2103
2104
struct Visitor
2105
{
2106
JPH_INLINE explicit Visitor(const HeightFieldShape *inShape, const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) :
2107
mHit(ioHit),
2108
mRayOrigin(inRay.mOrigin),
2109
mRayDirection(inRay.mDirection),
2110
mRayInvDirection(inRay.mDirection),
2111
mShape(inShape),
2112
mSubShapeIDCreator(inSubShapeIDCreator)
2113
{
2114
}
2115
2116
JPH_INLINE bool ShouldAbort() const
2117
{
2118
return mHit.mFraction <= 0.0f;
2119
}
2120
2121
JPH_INLINE bool ShouldVisitRangeBlock(int inStackTop) const
2122
{
2123
return mDistanceStack[inStackTop] < mHit.mFraction;
2124
}
2125
2126
JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
2127
{
2128
// Test bounds of 4 children
2129
Vec4 distance = RayAABox4(mRayOrigin, mRayInvDirection, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
2130
2131
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
2132
return SortReverseAndStore(distance, mHit.mFraction, ioProperties, &mDistanceStack[inStackTop]);
2133
}
2134
2135
JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)
2136
{
2137
float fraction = RayTriangle(mRayOrigin, mRayDirection, inV0, inV1, inV2);
2138
if (fraction < mHit.mFraction)
2139
{
2140
// It's a closer hit
2141
mHit.mFraction = fraction;
2142
mHit.mSubShapeID2 = mShape->EncodeSubShapeID(mSubShapeIDCreator, inX, inY, inTriangle);
2143
mReturnValue = true;
2144
}
2145
}
2146
2147
RayCastResult & mHit;
2148
Vec3 mRayOrigin;
2149
Vec3 mRayDirection;
2150
RayInvDirection mRayInvDirection;
2151
const HeightFieldShape *mShape;
2152
SubShapeIDCreator mSubShapeIDCreator;
2153
bool mReturnValue = false;
2154
float mDistanceStack[cStackSize];
2155
};
2156
2157
Visitor visitor(this, inRay, inSubShapeIDCreator, ioHit);
2158
WalkHeightField(visitor);
2159
2160
return visitor.mReturnValue;
2161
}
2162
2163
void HeightFieldShape::CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter) const
2164
{
2165
JPH_PROFILE_FUNCTION();
2166
2167
// Test shape filter
2168
if (!inShapeFilter.ShouldCollide(this, inSubShapeIDCreator.GetID()))
2169
return;
2170
2171
struct Visitor
2172
{
2173
JPH_INLINE explicit Visitor(const HeightFieldShape *inShape, const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector) :
2174
mCollector(ioCollector),
2175
mRayOrigin(inRay.mOrigin),
2176
mRayDirection(inRay.mDirection),
2177
mRayInvDirection(inRay.mDirection),
2178
mBackFaceMode(inRayCastSettings.mBackFaceModeTriangles),
2179
mShape(inShape),
2180
mSubShapeIDCreator(inSubShapeIDCreator)
2181
{
2182
}
2183
2184
JPH_INLINE bool ShouldAbort() const
2185
{
2186
return mCollector.ShouldEarlyOut();
2187
}
2188
2189
JPH_INLINE bool ShouldVisitRangeBlock(int inStackTop) const
2190
{
2191
return mDistanceStack[inStackTop] < mCollector.GetEarlyOutFraction();
2192
}
2193
2194
JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
2195
{
2196
// Test bounds of 4 children
2197
Vec4 distance = RayAABox4(mRayOrigin, mRayInvDirection, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
2198
2199
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
2200
return SortReverseAndStore(distance, mCollector.GetEarlyOutFraction(), ioProperties, &mDistanceStack[inStackTop]);
2201
}
2202
2203
JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2) const
2204
{
2205
// Back facing check
2206
if (mBackFaceMode == EBackFaceMode::IgnoreBackFaces && (inV2 - inV0).Cross(inV1 - inV0).Dot(mRayDirection) < 0)
2207
return;
2208
2209
// Check the triangle
2210
float fraction = RayTriangle(mRayOrigin, mRayDirection, inV0, inV1, inV2);
2211
if (fraction < mCollector.GetEarlyOutFraction())
2212
{
2213
RayCastResult hit;
2214
hit.mBodyID = TransformedShape::sGetBodyID(mCollector.GetContext());
2215
hit.mFraction = fraction;
2216
hit.mSubShapeID2 = mShape->EncodeSubShapeID(mSubShapeIDCreator, inX, inY, inTriangle);
2217
mCollector.AddHit(hit);
2218
}
2219
}
2220
2221
CastRayCollector & mCollector;
2222
Vec3 mRayOrigin;
2223
Vec3 mRayDirection;
2224
RayInvDirection mRayInvDirection;
2225
EBackFaceMode mBackFaceMode;
2226
const HeightFieldShape *mShape;
2227
SubShapeIDCreator mSubShapeIDCreator;
2228
float mDistanceStack[cStackSize];
2229
};
2230
2231
Visitor visitor(this, inRay, inRayCastSettings, inSubShapeIDCreator, ioCollector);
2232
WalkHeightField(visitor);
2233
}
2234
2235
void HeightFieldShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter) const
2236
{
2237
// A height field doesn't have volume, so we can't test insideness
2238
}
2239
2240
void HeightFieldShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, const CollideSoftBodyVertexIterator &inVertices, uint inNumVertices, int inCollidingShapeIndex) const
2241
{
2242
JPH_PROFILE_FUNCTION();
2243
2244
struct Visitor : public CollideSoftBodyVerticesVsTriangles
2245
{
2246
using CollideSoftBodyVerticesVsTriangles::CollideSoftBodyVerticesVsTriangles;
2247
2248
JPH_INLINE bool ShouldAbort() const
2249
{
2250
return false;
2251
}
2252
2253
JPH_INLINE bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const
2254
{
2255
return mDistanceStack[inStackTop] < mClosestDistanceSq;
2256
}
2257
2258
JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
2259
{
2260
// Get distance to vertex
2261
Vec4 dist_sq = AABox4DistanceSqToPoint(mLocalPosition, inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
2262
2263
// Clear distance for invalid bounds
2264
dist_sq = Vec4::sSelect(Vec4::sReplicate(FLT_MAX), dist_sq, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));
2265
2266
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
2267
return SortReverseAndStore(dist_sq, mClosestDistanceSq, ioProperties, &mDistanceStack[inStackTop]);
2268
}
2269
2270
JPH_INLINE void VisitTriangle([[maybe_unused]] uint inX, [[maybe_unused]] uint inY, [[maybe_unused]] uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)
2271
{
2272
ProcessTriangle(inV0, inV1, inV2);
2273
}
2274
2275
float mDistanceStack[cStackSize];
2276
};
2277
2278
Visitor visitor(inCenterOfMassTransform, inScale);
2279
2280
for (CollideSoftBodyVertexIterator v = inVertices, sbv_end = inVertices + inNumVertices; v != sbv_end; ++v)
2281
if (v.GetInvMass() > 0.0f)
2282
{
2283
visitor.StartVertex(v);
2284
WalkHeightField(visitor);
2285
visitor.FinishVertex(v, inCollidingShapeIndex);
2286
}
2287
}
2288
2289
void 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)
2290
{
2291
JPH_PROFILE_FUNCTION();
2292
2293
struct Visitor : public CastConvexVsTriangles
2294
{
2295
using CastConvexVsTriangles::CastConvexVsTriangles;
2296
2297
JPH_INLINE bool ShouldAbort() const
2298
{
2299
return mCollector.ShouldEarlyOut();
2300
}
2301
2302
JPH_INLINE bool ShouldVisitRangeBlock(int inStackTop) const
2303
{
2304
return mDistanceStack[inStackTop] < mCollector.GetPositiveEarlyOutFraction();
2305
}
2306
2307
JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
2308
{
2309
// Scale the bounding boxes of this node
2310
Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;
2311
AABox4Scale(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);
2312
2313
// Enlarge them by the casted shape's box extents
2314
AABox4EnlargeWithExtent(mBoxExtent, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
2315
2316
// Test bounds of 4 children
2317
Vec4 distance = RayAABox4(mBoxCenter, mInvDirection, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
2318
2319
// Clear distance for invalid bounds
2320
distance = Vec4::sSelect(Vec4::sReplicate(FLT_MAX), distance, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));
2321
2322
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
2323
return SortReverseAndStore(distance, mCollector.GetPositiveEarlyOutFraction(), ioProperties, &mDistanceStack[inStackTop]);
2324
}
2325
2326
JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)
2327
{
2328
// Create sub shape id for this part
2329
SubShapeID triangle_sub_shape_id = mShape2->EncodeSubShapeID(mSubShapeIDCreator2, inX, inY, inTriangle);
2330
2331
// Determine active edges
2332
uint8 active_edges = mShape2->GetEdgeFlags(inX, inY, inTriangle);
2333
2334
Cast(inV0, inV1, inV2, active_edges, triangle_sub_shape_id);
2335
}
2336
2337
const HeightFieldShape * mShape2;
2338
RayInvDirection mInvDirection;
2339
Vec3 mBoxCenter;
2340
Vec3 mBoxExtent;
2341
SubShapeIDCreator mSubShapeIDCreator2;
2342
float mDistanceStack[cStackSize];
2343
};
2344
2345
JPH_ASSERT(inShape->GetSubType() == EShapeSubType::HeightField);
2346
const HeightFieldShape *shape = static_cast<const HeightFieldShape *>(inShape);
2347
2348
Visitor visitor(inShapeCast, inShapeCastSettings, inScale, inCenterOfMassTransform2, inSubShapeIDCreator1, ioCollector);
2349
visitor.mShape2 = shape;
2350
visitor.mInvDirection.Set(inShapeCast.mDirection);
2351
visitor.mBoxCenter = inShapeCast.mShapeWorldBounds.GetCenter();
2352
visitor.mBoxExtent = inShapeCast.mShapeWorldBounds.GetExtent();
2353
visitor.mSubShapeIDCreator2 = inSubShapeIDCreator2;
2354
shape->WalkHeightField(visitor);
2355
}
2356
2357
void 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)
2358
{
2359
JPH_PROFILE_FUNCTION();
2360
2361
struct Visitor : public CastSphereVsTriangles
2362
{
2363
using CastSphereVsTriangles::CastSphereVsTriangles;
2364
2365
JPH_INLINE bool ShouldAbort() const
2366
{
2367
return mCollector.ShouldEarlyOut();
2368
}
2369
2370
JPH_INLINE bool ShouldVisitRangeBlock(int inStackTop) const
2371
{
2372
return mDistanceStack[inStackTop] < mCollector.GetPositiveEarlyOutFraction();
2373
}
2374
2375
JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop)
2376
{
2377
// Scale the bounding boxes of this node
2378
Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;
2379
AABox4Scale(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);
2380
2381
// Enlarge them by the radius of the sphere
2382
AABox4EnlargeWithExtent(Vec3::sReplicate(mRadius), bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
2383
2384
// Test bounds of 4 children
2385
Vec4 distance = RayAABox4(mStart, mInvDirection, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
2386
2387
// Clear distance for invalid bounds
2388
distance = Vec4::sSelect(Vec4::sReplicate(FLT_MAX), distance, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));
2389
2390
// Sort so that highest values are first (we want to first process closer hits and we process stack top to bottom)
2391
return SortReverseAndStore(distance, mCollector.GetPositiveEarlyOutFraction(), ioProperties, &mDistanceStack[inStackTop]);
2392
}
2393
2394
JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)
2395
{
2396
// Create sub shape id for this part
2397
SubShapeID triangle_sub_shape_id = mShape2->EncodeSubShapeID(mSubShapeIDCreator2, inX, inY, inTriangle);
2398
2399
// Determine active edges
2400
uint8 active_edges = mShape2->GetEdgeFlags(inX, inY, inTriangle);
2401
2402
Cast(inV0, inV1, inV2, active_edges, triangle_sub_shape_id);
2403
}
2404
2405
const HeightFieldShape * mShape2;
2406
RayInvDirection mInvDirection;
2407
SubShapeIDCreator mSubShapeIDCreator2;
2408
float mDistanceStack[cStackSize];
2409
};
2410
2411
JPH_ASSERT(inShape->GetSubType() == EShapeSubType::HeightField);
2412
const HeightFieldShape *shape = static_cast<const HeightFieldShape *>(inShape);
2413
2414
Visitor visitor(inShapeCast, inShapeCastSettings, inScale, inCenterOfMassTransform2, inSubShapeIDCreator1, ioCollector);
2415
visitor.mShape2 = shape;
2416
visitor.mInvDirection.Set(inShapeCast.mDirection);
2417
visitor.mSubShapeIDCreator2 = inSubShapeIDCreator2;
2418
shape->WalkHeightField(visitor);
2419
}
2420
2421
struct HeightFieldShape::HSGetTrianglesContext
2422
{
2423
HSGetTrianglesContext(const HeightFieldShape *inShape, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) :
2424
mDecodeCtx(inShape),
2425
mShape(inShape),
2426
mLocalBox(Mat44::sInverseRotationTranslation(inRotation, inPositionCOM), inBox),
2427
mHeightFieldScale(inScale),
2428
mLocalToWorld(Mat44::sRotationTranslation(inRotation, inPositionCOM) * Mat44::sScale(inScale)),
2429
mIsInsideOut(ScaleHelpers::IsInsideOut(inScale))
2430
{
2431
}
2432
2433
bool ShouldAbort() const
2434
{
2435
return mShouldAbort;
2436
}
2437
2438
bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const
2439
{
2440
return true;
2441
}
2442
2443
int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
2444
{
2445
// Scale the bounding boxes of this node
2446
Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;
2447
AABox4Scale(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);
2448
2449
// Test which nodes collide
2450
UVec4 collides = AABox4VsBox(mLocalBox, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
2451
2452
// Filter out invalid bounding boxes
2453
collides = UVec4::sAnd(collides, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));
2454
2455
return CountAndSortTrues(collides, ioProperties);
2456
}
2457
2458
void VisitTriangle(uint inX, uint inY, [[maybe_unused]] uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)
2459
{
2460
// 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.
2461
if (mNumTrianglesFound + 1 > mMaxTrianglesRequested)
2462
{
2463
mShouldAbort = true;
2464
return;
2465
}
2466
2467
// Store vertices as Float3
2468
if (mIsInsideOut)
2469
{
2470
// Reverse vertices
2471
(mLocalToWorld * inV0).StoreFloat3(mTriangleVertices++);
2472
(mLocalToWorld * inV2).StoreFloat3(mTriangleVertices++);
2473
(mLocalToWorld * inV1).StoreFloat3(mTriangleVertices++);
2474
}
2475
else
2476
{
2477
// Normal scale
2478
(mLocalToWorld * inV0).StoreFloat3(mTriangleVertices++);
2479
(mLocalToWorld * inV1).StoreFloat3(mTriangleVertices++);
2480
(mLocalToWorld * inV2).StoreFloat3(mTriangleVertices++);
2481
}
2482
2483
// Decode material
2484
if (mMaterials != nullptr)
2485
*mMaterials++ = mShape->GetMaterial(inX, inY);
2486
2487
// Accumulate triangles found
2488
mNumTrianglesFound++;
2489
}
2490
2491
DecodingContext mDecodeCtx;
2492
const HeightFieldShape * mShape;
2493
OrientedBox mLocalBox;
2494
Vec3 mHeightFieldScale;
2495
Mat44 mLocalToWorld;
2496
int mMaxTrianglesRequested;
2497
Float3 * mTriangleVertices;
2498
int mNumTrianglesFound;
2499
const PhysicsMaterial ** mMaterials;
2500
bool mShouldAbort;
2501
bool mIsInsideOut;
2502
};
2503
2504
void HeightFieldShape::GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const
2505
{
2506
static_assert(sizeof(HSGetTrianglesContext) <= sizeof(GetTrianglesContext), "GetTrianglesContext too small");
2507
JPH_ASSERT(IsAligned(&ioContext, alignof(HSGetTrianglesContext)));
2508
2509
new (&ioContext) HSGetTrianglesContext(this, inBox, inPositionCOM, inRotation, inScale);
2510
}
2511
2512
int HeightFieldShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials) const
2513
{
2514
static_assert(cGetTrianglesMinTrianglesRequested >= 1, "cGetTrianglesMinTrianglesRequested is too small");
2515
JPH_ASSERT(inMaxTrianglesRequested >= cGetTrianglesMinTrianglesRequested);
2516
2517
// Check if we're done
2518
HSGetTrianglesContext &context = (HSGetTrianglesContext &)ioContext;
2519
if (context.mDecodeCtx.IsDoneWalking())
2520
return 0;
2521
2522
// Store parameters on context
2523
context.mMaxTrianglesRequested = inMaxTrianglesRequested;
2524
context.mTriangleVertices = outTriangleVertices;
2525
context.mMaterials = outMaterials;
2526
context.mShouldAbort = false; // Reset the abort flag
2527
context.mNumTrianglesFound = 0;
2528
2529
// Continue (or start) walking the height field
2530
context.mDecodeCtx.WalkHeightField(context);
2531
return context.mNumTrianglesFound;
2532
}
2533
2534
void 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)
2535
{
2536
JPH_PROFILE_FUNCTION();
2537
2538
// Get the shapes
2539
JPH_ASSERT(inShape1->GetType() == EShapeType::Convex);
2540
JPH_ASSERT(inShape2->GetType() == EShapeType::HeightField);
2541
const ConvexShape *shape1 = static_cast<const ConvexShape *>(inShape1);
2542
const HeightFieldShape *shape2 = static_cast<const HeightFieldShape *>(inShape2);
2543
2544
struct Visitor : public CollideConvexVsTriangles
2545
{
2546
using CollideConvexVsTriangles::CollideConvexVsTriangles;
2547
2548
JPH_INLINE bool ShouldAbort() const
2549
{
2550
return mCollector.ShouldEarlyOut();
2551
}
2552
2553
JPH_INLINE bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const
2554
{
2555
return true;
2556
}
2557
2558
JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
2559
{
2560
// Scale the bounding boxes of this node
2561
Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;
2562
AABox4Scale(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);
2563
2564
// Test which nodes collide
2565
UVec4 collides = AABox4VsBox(mBoundsOf1InSpaceOf2, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
2566
2567
// Filter out invalid bounding boxes
2568
collides = UVec4::sAnd(collides, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));
2569
2570
return CountAndSortTrues(collides, ioProperties);
2571
}
2572
2573
JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)
2574
{
2575
// Create ID for triangle
2576
SubShapeID triangle_sub_shape_id = mShape2->EncodeSubShapeID(mSubShapeIDCreator2, inX, inY, inTriangle);
2577
2578
// Determine active edges
2579
uint8 active_edges = mShape2->GetEdgeFlags(inX, inY, inTriangle);
2580
2581
Collide(inV0, inV1, inV2, active_edges, triangle_sub_shape_id);
2582
}
2583
2584
const HeightFieldShape * mShape2;
2585
SubShapeIDCreator mSubShapeIDCreator2;
2586
};
2587
2588
Visitor visitor(shape1, inScale1, inScale2, inCenterOfMassTransform1, inCenterOfMassTransform2, inSubShapeIDCreator1.GetID(), inCollideShapeSettings, ioCollector);
2589
visitor.mShape2 = shape2;
2590
visitor.mSubShapeIDCreator2 = inSubShapeIDCreator2;
2591
shape2->WalkHeightField(visitor);
2592
}
2593
2594
void 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)
2595
{
2596
JPH_PROFILE_FUNCTION();
2597
2598
// Get the shapes
2599
JPH_ASSERT(inShape1->GetSubType() == EShapeSubType::Sphere);
2600
JPH_ASSERT(inShape2->GetType() == EShapeType::HeightField);
2601
const SphereShape *shape1 = static_cast<const SphereShape *>(inShape1);
2602
const HeightFieldShape *shape2 = static_cast<const HeightFieldShape *>(inShape2);
2603
2604
struct Visitor : public CollideSphereVsTriangles
2605
{
2606
using CollideSphereVsTriangles::CollideSphereVsTriangles;
2607
2608
JPH_INLINE bool ShouldAbort() const
2609
{
2610
return mCollector.ShouldEarlyOut();
2611
}
2612
2613
JPH_INLINE bool ShouldVisitRangeBlock([[maybe_unused]] int inStackTop) const
2614
{
2615
return true;
2616
}
2617
2618
JPH_INLINE int VisitRangeBlock(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
2619
{
2620
// Scale the bounding boxes of this node
2621
Vec4 bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z;
2622
AABox4Scale(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);
2623
2624
// Test which nodes collide
2625
UVec4 collides = AABox4VsSphere(mSphereCenterIn2, mRadiusPlusMaxSeparationSq, bounds_min_x, bounds_min_y, bounds_min_z, bounds_max_x, bounds_max_y, bounds_max_z);
2626
2627
// Filter out invalid bounding boxes
2628
collides = UVec4::sAnd(collides, Vec4::sLessOrEqual(inBoundsMinY, inBoundsMaxY));
2629
2630
return CountAndSortTrues(collides, ioProperties);
2631
}
2632
2633
JPH_INLINE void VisitTriangle(uint inX, uint inY, uint inTriangle, Vec3Arg inV0, Vec3Arg inV1, Vec3Arg inV2)
2634
{
2635
// Create ID for triangle
2636
SubShapeID triangle_sub_shape_id = mShape2->EncodeSubShapeID(mSubShapeIDCreator2, inX, inY, inTriangle);
2637
2638
// Determine active edges
2639
uint8 active_edges = mShape2->GetEdgeFlags(inX, inY, inTriangle);
2640
2641
Collide(inV0, inV1, inV2, active_edges, triangle_sub_shape_id);
2642
}
2643
2644
const HeightFieldShape * mShape2;
2645
SubShapeIDCreator mSubShapeIDCreator2;
2646
};
2647
2648
Visitor visitor(shape1, inScale1, inScale2, inCenterOfMassTransform1, inCenterOfMassTransform2, inSubShapeIDCreator1.GetID(), inCollideShapeSettings, ioCollector);
2649
visitor.mShape2 = shape2;
2650
visitor.mSubShapeIDCreator2 = inSubShapeIDCreator2;
2651
shape2->WalkHeightField(visitor);
2652
}
2653
2654
void HeightFieldShape::SaveBinaryState(StreamOut &inStream) const
2655
{
2656
Shape::SaveBinaryState(inStream);
2657
2658
inStream.Write(mOffset);
2659
inStream.Write(mScale);
2660
inStream.Write(mSampleCount);
2661
inStream.Write(mBlockSize);
2662
inStream.Write(mBitsPerSample);
2663
inStream.Write(mMinSample);
2664
inStream.Write(mMaxSample);
2665
inStream.Write(mMaterialIndices);
2666
inStream.Write(mNumBitsPerMaterialIndex);
2667
2668
if (mRangeBlocks != nullptr)
2669
{
2670
inStream.Write(true);
2671
inStream.WriteBytes(mRangeBlocks, mRangeBlocksSize * sizeof(RangeBlock) + mHeightSamplesSize + mActiveEdgesSize);
2672
}
2673
else
2674
{
2675
inStream.Write(false);
2676
}
2677
}
2678
2679
void HeightFieldShape::RestoreBinaryState(StreamIn &inStream)
2680
{
2681
Shape::RestoreBinaryState(inStream);
2682
2683
inStream.Read(mOffset);
2684
inStream.Read(mScale);
2685
inStream.Read(mSampleCount);
2686
inStream.Read(mBlockSize);
2687
inStream.Read(mBitsPerSample);
2688
inStream.Read(mMinSample);
2689
inStream.Read(mMaxSample);
2690
inStream.Read(mMaterialIndices);
2691
inStream.Read(mNumBitsPerMaterialIndex);
2692
2693
// We don't have the exact number of reserved materials anymore, but ensure that our array is big enough
2694
// 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 waste
2695
mMaterials.reserve(PhysicsMaterialList::size_type(1) << mNumBitsPerMaterialIndex);
2696
2697
CacheValues();
2698
2699
bool has_heights = false;
2700
inStream.Read(has_heights);
2701
if (has_heights)
2702
{
2703
AllocateBuffers();
2704
inStream.ReadBytes(mRangeBlocks, mRangeBlocksSize * sizeof(RangeBlock) + mHeightSamplesSize + mActiveEdgesSize);
2705
}
2706
}
2707
2708
void HeightFieldShape::SaveMaterialState(PhysicsMaterialList &outMaterials) const
2709
{
2710
outMaterials = mMaterials;
2711
}
2712
2713
void HeightFieldShape::RestoreMaterialState(const PhysicsMaterialRefC *inMaterials, uint inNumMaterials)
2714
{
2715
mMaterials.assign(inMaterials, inMaterials + inNumMaterials);
2716
}
2717
2718
Shape::Stats HeightFieldShape::GetStats() const
2719
{
2720
return Stats(
2721
sizeof(*this)
2722
+ mMaterials.size() * sizeof(Ref<PhysicsMaterial>)
2723
+ mRangeBlocksSize * sizeof(RangeBlock)
2724
+ mHeightSamplesSize * sizeof(uint8)
2725
+ mActiveEdgesSize * sizeof(uint8)
2726
+ mMaterialIndices.size() * sizeof(uint8),
2727
mHeightSamplesSize == 0? 0 : Square(mSampleCount - 1) * 2);
2728
}
2729
2730
void HeightFieldShape::sRegister()
2731
{
2732
ShapeFunctions &f = ShapeFunctions::sGet(EShapeSubType::HeightField);
2733
f.mConstruct = []() -> Shape * { return new HeightFieldShape; };
2734
f.mColor = Color::sPurple;
2735
2736
for (EShapeSubType s : sConvexSubShapeTypes)
2737
{
2738
CollisionDispatch::sRegisterCollideShape(s, EShapeSubType::HeightField, sCollideConvexVsHeightField);
2739
CollisionDispatch::sRegisterCastShape(s, EShapeSubType::HeightField, sCastConvexVsHeightField);
2740
2741
CollisionDispatch::sRegisterCastShape(EShapeSubType::HeightField, s, CollisionDispatch::sReversedCastShape);
2742
CollisionDispatch::sRegisterCollideShape(EShapeSubType::HeightField, s, CollisionDispatch::sReversedCollideShape);
2743
}
2744
2745
// Specialized collision functions
2746
CollisionDispatch::sRegisterCollideShape(EShapeSubType::Sphere, EShapeSubType::HeightField, sCollideSphereVsHeightField);
2747
CollisionDispatch::sRegisterCastShape(EShapeSubType::Sphere, EShapeSubType::HeightField, sCastSphereVsHeightField);
2748
}
2749
2750
JPH_NAMESPACE_END
2751
2752