Merge pull request #2204 from erwincoumans/master

add raycast accelerator for btHeightfieldTerrainShape, thanks to Marc…
This commit is contained in:
erwincoumans
2019-04-15 22:20:06 -07:00
committed by GitHub
12 changed files with 1702 additions and 38 deletions

View File

@@ -313,35 +313,6 @@ public:
m_guiHelper->getRenderInterface()->drawLines(&points[0].m_floats[0], lineColor, points.size(), sizeof(btVector3FloatData), &indices[0], indices.size(), 1);
}
#if 0
glDisable (GL_LIGHTING);
glColor3f (0.0, 1.0, 0.0);
glBegin (GL_LINES);
int i;
for (i = 0; i < NUMRAYS; i++)
{
glVertex3f (source[i][0], source[i][1], source[i][2]);
glVertex3f (hit[i][0], hit[i][1], hit[i][2]);
}
glEnd ();
glColor3f (1.0, 1.0, 1.0);
glBegin (GL_LINES);
for (i = 0; i < NUMRAYS; i++)
{
glVertex3f (hit[i][0], hit[i][1], hit[i][2]);
glVertex3f (hit[i][0] + normal[i][0], hit[i][1] + normal[i][1], hit[i][2] + normal[i][2]);
}
glEnd ();
glColor3f (0.0, 1.0, 1.0);
glBegin (GL_POINTS);
for ( i = 0; i < NUMRAYS; i++)
{
glVertex3f (hit[i][0], hit[i][1], hit[i][2]);
}
glEnd ();
glEnable (GL_LIGHTING);
#endif //USE_GRAPHICAL_BENCHMARK
}
};

View File

@@ -439,12 +439,15 @@ struct CommonRigidBodyBase : public CommonExampleInterface
virtual void renderScene()
{
if (m_dynamicsWorld)
{
m_guiHelper->syncPhysicsToGraphics(m_dynamicsWorld);
}
{
m_guiHelper->syncPhysicsToGraphics(m_dynamicsWorld);
}
{
m_guiHelper->render(m_dynamicsWorld);
{
m_guiHelper->render(m_dynamicsWorld);
}
}
}
};

View File

@@ -207,6 +207,8 @@ SET(BulletExampleBrowser_SRCS
../MultiThreadedDemo/MultiThreadedDemo.h
../MultiThreadedDemo/CommonRigidBodyMTBase.cpp
../MultiThreadedDemo/CommonRigidBodyMTBase.h
../Heightfield/HeightfieldExample.cpp
../Heightfield/HeightfieldExample.h
../BlockSolver/btBlockSolver.cpp
../BlockSolver/btBlockSolver.h
../BlockSolver/BlockSolverExample.cpp

View File

@@ -5,6 +5,7 @@
#include "../BlockSolver/RigidBodyBoxes.h"
#include "LinearMath/btAlignedObjectArray.h"
#include "EmptyExample.h"
#include "../Heightfield/HeightfieldExample.h"
#include "../RenderingExamples/RenderInstancingDemo.h"
#include "../RenderingExamples/CoordinateSystemDemo.h"
#include "../RenderingExamples/RaytracerSetup.h"
@@ -157,8 +158,8 @@ static ExampleEntry gDefaultExamples[] =
ExampleEntry(1, "Stack MultiBody MLCP PGS", "Create a stack of blocks, with heavy block at the top", BlockSolverExampleCreateFunc, BLOCK_SOLVER_SCENE_MB_STACK + BLOCK_SOLVER_MLCP_PGS),
ExampleEntry(1, "Stack MultiBody MLCP Dantzig", "Create a stack of blocks, with heavy block at the top", BlockSolverExampleCreateFunc, BLOCK_SOLVER_SCENE_MB_STACK + BLOCK_SOLVER_MLCP_DANTZIG),
ExampleEntry(1, "Stack MultiBody Block", "Create a stack of blocks, with heavy block at the top", BlockSolverExampleCreateFunc, BLOCK_SOLVER_SCENE_MB_STACK + BLOCK_SOLVER_BLOCK),
ExampleEntry(1, "Stack RigidBody SI", "Create a stack of blocks, with heavy block at the top", RigidBodyBoxesCreateFunc, BLOCK_SOLVER_SI),
ExampleEntry(1, "Stack RigidBody Block", "Create a stack of blocks, with heavy block at the top", RigidBodyBoxesCreateFunc, BLOCK_SOLVER_BLOCK),
//ExampleEntry(1, "Stack RigidBody SI", "Create a stack of blocks, with heavy block at the top", RigidBodyBoxesCreateFunc, BLOCK_SOLVER_SI),
//ExampleEntry(1, "Stack RigidBody Block", "Create a stack of blocks, with heavy block at the top", RigidBodyBoxesCreateFunc, BLOCK_SOLVER_BLOCK),
ExampleEntry(0, "Inverse Dynamics"),
ExampleEntry(1, "Inverse Dynamics URDF", "Create a btMultiBody from URDF. Create an inverse MultiBodyTree model from that. Use either decoupled PD control or computed torque control using the inverse model to track joint position targets", InverseDynamicsExampleCreateFunc, BT_ID_LOAD_URDF),
@@ -234,6 +235,7 @@ static ExampleEntry gDefaultExamples[] =
ExampleEntry(1, "Convex vs Mesh", "Benchmark the performance and stability of rigid bodies using convex hull collision shapes (btConvexHullShape), resting on a triangle mesh, btBvhTriangleMeshShape.", BenchmarkCreateFunc, 6),
ExampleEntry(1, "Raycast", "Benchmark the performance of the btCollisionWorld::rayTest. Note that currently the rays are not rendered.", BenchmarkCreateFunc, 7),
ExampleEntry(1, "Convex Pack", "Benchmark the performance of the convex hull primitive.", BenchmarkCreateFunc, 8),
ExampleEntry(1, "Heightfield", "Raycast against a btHeightfieldTerrainShape", HeightfieldExampleCreateFunc),
//#endif
ExampleEntry(0, "Importers"),

View File

@@ -1,7 +1,7 @@
#include "OpenGLGuiHelper.h"
#include "btBulletDynamicsCommon.h"
#include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h"
#include "../CommonInterfaces/CommonGraphicsAppInterface.h"
#include "../CommonInterfaces/CommonRenderInterface.h"
#include "Bullet3Common/b3Scalar.h"
@@ -255,6 +255,38 @@ void OpenGLGuiHelper::createRigidBodyGraphicsObject(btRigidBody* body, const btV
createCollisionObjectGraphicsObject(body, color);
}
class MyTriangleCollector2 : public btTriangleCallback
{
public:
btAlignedObjectArray<GLInstanceVertex>* m_pVerticesOut;
btAlignedObjectArray<int>* m_pIndicesOut;
MyTriangleCollector2()
{
m_pVerticesOut = 0;
m_pIndicesOut = 0;
}
virtual void processTriangle(btVector3* tris, int partId, int triangleIndex)
{
for (int k = 0; k < 3; k++)
{
GLInstanceVertex v;
v.xyzw[3] = 0;
v.uv[0] = v.uv[1] = 0.5f;
btVector3 normal = (tris[0] - tris[1]).cross(tris[0] - tris[2]);
normal.safeNormalize();
for (int l = 0; l < 3; l++)
{
v.xyzw[l] = tris[k][l];
v.normal[l] = normal[l];
}
m_pIndicesOut->push_back(m_pVerticesOut->size());
m_pVerticesOut->push_back(v);
}
}
};
void OpenGLGuiHelper::createCollisionObjectGraphicsObject(btCollisionObject* body, const btVector3& color)
{
if (body->getUserIndex() < 0)
@@ -409,6 +441,30 @@ void OpenGLGuiHelper::createCollisionShapeGraphicsObject(btCollisionShape* colli
//if (collisionShape->getShapeType()==BOX_SHAPE_PROXYTYPE)
{
}
if (collisionShape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE)
{
const btHeightfieldTerrainShape* heightField = static_cast<const btHeightfieldTerrainShape*>(collisionShape);
MyTriangleCollector2 col;
col.m_pVerticesOut = &gfxVertices;
col.m_pIndicesOut = &indices;
btVector3 aabbMin, aabbMax;
for (int k = 0; k < 3; k++)
{
aabbMin[k] = -BT_LARGE_FLOAT;
aabbMax[k] = BT_LARGE_FLOAT;
}
heightField->processAllTriangles(&col, aabbMin, aabbMax);
if (gfxVertices.size() && indices.size())
{
int shapeId = m_data->m_glApp->m_renderer->registerShape(&gfxVertices[0].xyzw[0], gfxVertices.size(), &indices[0], indices.size());
collisionShape->setUserIndex(shapeId);
}
return;
}
if (collisionShape->getShapeType() == SOFTBODY_SHAPE_PROXYTYPE)
{
computeSoftBodyVertices(collisionShape, gfxVertices, indices);

View File

@@ -169,6 +169,7 @@ project "App_BulletExampleBrowser"
"../Collision/Internal/*",
"../Benchmarks/*",
"../MultiThreadedDemo/*",
"../Heightfield/HeightfieldExample.*",
"../CommonInterfaces/*.h",
"../ForkLift/ForkLiftDemo.*",
"../Importers/**",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
/*
Bullet Continuous Collision Detection and Physics Library
Copyright (c) 2015 Google Inc. http://bulletphysics.org
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it freely,
subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef HEIGHTFIELD_EXAMPLE_H
#define HEIGHTFIELD_EXAMPLE_H
class CommonExampleInterface* HeightfieldExampleCreateFunc(struct CommonExampleOptions& options);
#endif //HEIGHTFIELD_EXAMPLE_H

View File

@@ -22,6 +22,7 @@ subject to the following restrictions:
#include "BulletCollision/CollisionShapes/btSphereShape.h" //for raycasting
#include "BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h" //for raycasting
#include "BulletCollision/CollisionShapes/btScaledBvhTriangleMeshShape.h" //for raycasting
#include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h" //for raycasting
#include "BulletCollision/NarrowPhaseCollision/btRaycastCallback.h"
#include "BulletCollision/CollisionShapes/btCompoundShape.h"
#include "BulletCollision/NarrowPhaseCollision/btSubSimplexConvexCast.h"
@@ -413,6 +414,21 @@ void btCollisionWorld::rayTestSingleInternal(const btTransform& rayFromTrans, co
rcb.m_hitFraction = resultCallback.m_closestHitFraction;
triangleMesh->performRaycast(&rcb, rayFromLocalScaled, rayToLocalScaled);
}
else if (((resultCallback.m_flags&btTriangleRaycastCallback::kF_DisableHeightfieldAccelerator)==0)
&& collisionShape->getShapeType() == TERRAIN_SHAPE_PROXYTYPE &&
(((btHeightfieldTerrainShape*)collisionShape)->getUpAxis()==1)//accelerator only supports Y axis at the moment
)
{
///optimized version for btHeightfieldTerrainShape
btHeightfieldTerrainShape* heightField = (btHeightfieldTerrainShape*)collisionShape;
btTransform worldTocollisionObject = colObjWorldTransform.inverse();
btVector3 rayFromLocal = worldTocollisionObject * rayFromTrans.getOrigin();
btVector3 rayToLocal = worldTocollisionObject * rayToTrans.getOrigin();
BridgeTriangleRaycastCallback rcb(rayFromLocal, rayToLocal, &resultCallback, collisionObjectWrap->getCollisionObject(), heightField, colObjWorldTransform);
rcb.m_hitFraction = resultCallback.m_closestHitFraction;
heightField->performRaycast(&rcb, rayFromLocal, rayToLocal);
}
else
{
//generic (slower) case

View File

@@ -74,6 +74,10 @@ void btHeightfieldTerrainShape::initialize(
m_upAxis = upAxis;
m_localScaling.setValue(btScalar(1.), btScalar(1.), btScalar(1.));
m_vboundsChunkSize = 0;
m_vboundsGridWidth = 0;
m_vboundsGridLength = 0;
// determine min/max axis-aligned bounding box (aabb) values
switch (m_upAxis)
{
@@ -108,6 +112,7 @@ void btHeightfieldTerrainShape::initialize(
btHeightfieldTerrainShape::~btHeightfieldTerrainShape()
{
clearAccelerator();
}
void btHeightfieldTerrainShape::getAabb(const btTransform& t, btVector3& aabbMin, btVector3& aabbMax) const
@@ -323,6 +328,8 @@ void btHeightfieldTerrainShape::processAllTriangles(btTriangleCallback* callback
}
}
// TODO If m_vboundsGrid is available, use it to determine if we really need to process this area
for (int j = startJ; j < endJ; j++)
{
for (int x = startX; x < endX; x++)
@@ -373,3 +380,447 @@ const btVector3& btHeightfieldTerrainShape::getLocalScaling() const
{
return m_localScaling;
}
namespace
{
struct GridRaycastState
{
int x; // Next quad coords
int z;
int prev_x; // Previous quad coords
int prev_z;
btScalar param; // Exit param for previous quad
btScalar prevParam; // Enter param for previous quad
btScalar maxDistanceFlat;
btScalar maxDistance3d;
};
}
// TODO Does it really need to take 3D vectors?
/// Iterates through a virtual 2D grid of unit-sized square cells,
/// and executes an action on each cell intersecting the given segment, ordered from begin to end.
/// Initially inspired by http://www.cse.yorku.ca/~amana/research/grid.pdf
template <typename Action_T>
void gridRaycast(Action_T& quadAction, const btVector3& beginPos, const btVector3& endPos)
{
GridRaycastState rs;
rs.maxDistance3d = beginPos.distance(endPos);
if (rs.maxDistance3d < 0.0001)
{
// Consider the ray is too small to hit anything
return;
}
btScalar rayDirectionFlatX = endPos[0] - beginPos[0];
btScalar rayDirectionFlatZ = endPos[2] - beginPos[2];
rs.maxDistanceFlat = btSqrt(rayDirectionFlatX * rayDirectionFlatX + rayDirectionFlatZ * rayDirectionFlatZ);
if (rs.maxDistanceFlat < 0.0001)
{
// Consider the ray vertical
rayDirectionFlatX = 0;
rayDirectionFlatZ = 0;
}
else
{
rayDirectionFlatX /= rs.maxDistanceFlat;
rayDirectionFlatZ /= rs.maxDistanceFlat;
}
const int xiStep = rayDirectionFlatX > 0 ? 1 : rayDirectionFlatX < 0 ? -1 : 0;
const int ziStep = rayDirectionFlatZ > 0 ? 1 : rayDirectionFlatZ < 0 ? -1 : 0;
const float infinite = 9999999;
const btScalar paramDeltaX = xiStep != 0 ? 1.f / btFabs(rayDirectionFlatX) : infinite;
const btScalar paramDeltaZ = ziStep != 0 ? 1.f / btFabs(rayDirectionFlatZ) : infinite;
// pos = param * dir
btScalar paramCrossX; // At which value of `param` we will cross a x-axis lane?
btScalar paramCrossZ; // At which value of `param` we will cross a z-axis lane?
// paramCrossX and paramCrossZ are initialized as being the first cross
// X initialization
if (xiStep != 0)
{
if (xiStep == 1)
{
paramCrossX = (ceil(beginPos[0]) - beginPos[0]) * paramDeltaX;
}
else
{
paramCrossX = (beginPos[0] - floor(beginPos[0])) * paramDeltaX;
}
}
else
{
paramCrossX = infinite; // Will never cross on X
}
// Z initialization
if (ziStep != 0)
{
if (ziStep == 1)
{
paramCrossZ = (ceil(beginPos[2]) - beginPos[2]) * paramDeltaZ;
}
else
{
paramCrossZ = (beginPos[2] - floor(beginPos[2])) * paramDeltaZ;
}
}
else
{
paramCrossZ = infinite; // Will never cross on Z
}
rs.x = static_cast<int>(floor(beginPos[0]));
rs.z = static_cast<int>(floor(beginPos[2]));
// Workaround cases where the ray starts at an integer position
if (paramCrossX == 0.0)
{
paramCrossX += paramDeltaX;
// If going backwards, we should ignore the position we would get by the above flooring,
// because the ray is not heading in that direction
if (xiStep == -1)
{
rs.x -= 1;
}
}
if (paramCrossZ == 0.0)
{
paramCrossZ += paramDeltaZ;
if (ziStep == -1)
rs.z -= 1;
}
rs.prev_x = rs.x;
rs.prev_z = rs.z;
rs.param = 0;
while (true)
{
rs.prev_x = rs.x;
rs.prev_z = rs.z;
rs.prevParam = rs.param;
if (paramCrossX < paramCrossZ)
{
// X lane
rs.x += xiStep;
// Assign before advancing the param,
// to be in sync with the initialization step
rs.param = paramCrossX;
paramCrossX += paramDeltaX;
}
else
{
// Z lane
rs.z += ziStep;
rs.param = paramCrossZ;
paramCrossZ += paramDeltaZ;
}
if (rs.param > rs.maxDistanceFlat)
{
rs.param = rs.maxDistanceFlat;
quadAction(rs);
break;
}
else
{
quadAction(rs);
}
}
}
struct ProcessTrianglesAction
{
const btHeightfieldTerrainShape* shape;
bool flipQuadEdges;
bool useDiamondSubdivision;
int width;
int length;
btTriangleCallback* callback;
void exec(int x, int z) const
{
if (x < 0 || z < 0 || x >= width || z >= length)
{
return;
}
btVector3 vertices[3];
// TODO Since this is for raycasts, we could greatly benefit from an early exit on the first hit
// Check quad
if (flipQuadEdges || (useDiamondSubdivision && (((z + x) & 1) > 0)))
{
// First triangle
shape->getVertex(x, z, vertices[0]);
shape->getVertex(x + 1, z, vertices[1]);
shape->getVertex(x + 1, z + 1, vertices[2]);
callback->processTriangle(vertices, x, z);
// Second triangle
shape->getVertex(x, z, vertices[0]);
shape->getVertex(x + 1, z + 1, vertices[1]);
shape->getVertex(x, z + 1, vertices[2]);
callback->processTriangle(vertices, x, z);
}
else
{
// First triangle
shape->getVertex(x, z, vertices[0]);
shape->getVertex(x, z + 1, vertices[1]);
shape->getVertex(x + 1, z, vertices[2]);
callback->processTriangle(vertices, x, z);
// Second triangle
shape->getVertex(x + 1, z, vertices[0]);
shape->getVertex(x, z + 1, vertices[1]);
shape->getVertex(x + 1, z + 1, vertices[2]);
callback->processTriangle(vertices, x, z);
}
}
void operator()(const GridRaycastState& bs) const
{
exec(bs.prev_x, bs.prev_z);
}
};
struct ProcessVBoundsAction
{
const btAlignedObjectArray<btHeightfieldTerrainShape::Range>& vbounds;
int width;
int length;
int chunkSize;
btVector3 rayBegin;
btVector3 rayEnd;
btVector3 rayDir;
ProcessTrianglesAction processTriangles;
ProcessVBoundsAction(const btAlignedObjectArray<btHeightfieldTerrainShape::Range>& bnd)
: vbounds(bnd)
{
}
void operator()(const GridRaycastState& rs) const
{
int x = rs.prev_x;
int z = rs.prev_z;
if (x < 0 || z < 0 || x >= width || z >= length)
{
return;
}
const btHeightfieldTerrainShape::Range chunk = vbounds[x + z * width];
btVector3 enterPos;
btVector3 exitPos;
if (rs.maxDistanceFlat > 0.0001)
{
btScalar flatTo3d = chunkSize * rs.maxDistance3d / rs.maxDistanceFlat;
btScalar enterParam3d = rs.prevParam * flatTo3d;
btScalar exitParam3d = rs.param * flatTo3d;
enterPos = rayBegin + rayDir * enterParam3d;
exitPos = rayBegin + rayDir * exitParam3d;
// We did enter the flat projection of the AABB,
// but we have to check if we intersect it on the vertical axis
if (enterPos[1] > chunk.max && exitPos[1] > chunk.max)
{
return;
}
if (enterPos[1] < chunk.min && exitPos[1] < chunk.min)
{
return;
}
}
else
{
// Consider the ray vertical
// (though we shouldn't reach this often because there is an early check up-front)
enterPos = rayBegin;
exitPos = rayEnd;
}
gridRaycast(processTriangles, enterPos, exitPos);
// Note: it could be possible to have more than one grid at different levels,
// to do this there would be a branch using a pointer to another ProcessVBoundsAction
}
};
// TODO How do I interrupt the ray when there is a hit? `callback` does not return any result
/// Performs a raycast using a hierarchical Bresenham algorithm.
/// Does not allocate any memory by itself.
void btHeightfieldTerrainShape::performRaycast(btTriangleCallback* callback, const btVector3& raySource, const btVector3& rayTarget) const
{
// Transform to cell-local
btVector3 beginPos = raySource / m_localScaling;
btVector3 endPos = rayTarget / m_localScaling;
beginPos += m_localOrigin;
endPos += m_localOrigin;
ProcessTrianglesAction processTriangles;
processTriangles.shape = this;
processTriangles.flipQuadEdges = m_flipQuadEdges;
processTriangles.useDiamondSubdivision = m_useDiamondSubdivision;
processTriangles.callback = callback;
processTriangles.width = m_heightStickWidth - 1;
processTriangles.length = m_heightStickLength - 1;
// TODO Transform vectors to account for m_upAxis
int iBeginX = static_cast<int>(floor(beginPos[0]));
int iBeginZ = static_cast<int>(floor(beginPos[2]));
int iEndX = static_cast<int>(floor(endPos[0]));
int iEndZ = static_cast<int>(floor(endPos[2]));
if (iBeginX == iEndX && iBeginZ == iEndZ)
{
// The ray will never cross quads within the plane,
// so directly process triangles within one quad
// (typically, vertical rays should end up here)
processTriangles.exec(iBeginX, iEndZ);
return;
}
if (m_vboundsGrid.size()==0)
{
// Process all quads intersecting the flat projection of the ray
gridRaycast(processTriangles, beginPos, endPos);
}
else
{
btVector3 rayDiff = endPos - beginPos;
btScalar flatDistance2 = rayDiff[0] * rayDiff[0] + rayDiff[2] * rayDiff[2];
if (flatDistance2 < m_vboundsChunkSize * m_vboundsChunkSize)
{
// Don't use chunks, the ray is too short in the plane
gridRaycast(processTriangles, beginPos, endPos);
}
ProcessVBoundsAction processVBounds(m_vboundsGrid);
processVBounds.width = m_vboundsGridWidth;
processVBounds.length = m_vboundsGridLength;
processVBounds.rayBegin = beginPos;
processVBounds.rayEnd = endPos;
processVBounds.rayDir = rayDiff.normalized();
processVBounds.processTriangles = processTriangles;
processVBounds.chunkSize = m_vboundsChunkSize;
// The ray is long, run raycast on a higher-level grid
gridRaycast(processVBounds, beginPos / m_vboundsChunkSize, endPos / m_vboundsChunkSize);
}
}
/// Builds a grid data structure storing the min and max heights of the terrain in chunks.
/// if chunkSize is zero, that accelerator is removed.
/// If you modify the heights, you need to rebuild this accelerator.
void btHeightfieldTerrainShape::buildAccelerator(int chunkSize)
{
if (chunkSize <= 0)
{
clearAccelerator();
return;
}
m_vboundsChunkSize = chunkSize;
int nChunksX = m_heightStickWidth / chunkSize;
int nChunksZ = m_heightStickLength / chunkSize;
if (m_heightStickWidth % chunkSize > 0)
{
++nChunksX; // In case terrain size isn't dividable by chunk size
}
if (m_heightStickLength % chunkSize > 0)
{
++nChunksZ;
}
if (m_vboundsGridWidth != nChunksX || m_vboundsGridLength != nChunksZ)
{
clearAccelerator();
m_vboundsGridWidth = nChunksX;
m_vboundsGridLength = nChunksZ;
}
if (nChunksX == 0 || nChunksZ == 0)
{
return;
}
// This data structure is only reallocated if the required size changed
m_vboundsGrid.resize(nChunksX * nChunksZ);
// Compute min and max height for all chunks
for (int cz = 0; cz < nChunksZ; ++cz)
{
int z0 = cz * chunkSize;
for (int cx = 0; cx < nChunksX; ++cx)
{
int x0 = cx * chunkSize;
Range r;
r.min = getRawHeightFieldValue(x0, z0);
r.max = r.min;
// Compute min and max height for this chunk.
// We have to include one extra cell to account for neighbors.
// Here is why:
// Say we have a flat terrain, and a plateau that fits a chunk perfectly.
//
// Left Right
// 0---0---0---1---1---1
// | | | | | |
// 0---0---0---1---1---1
// | | | | | |
// 0---0---0---1---1---1
// x
//
// If the AABB for the Left chunk did not share vertices with the Right,
// then we would fail collision tests at x due to a gap.
//
for (int z = z0; z < z0 + chunkSize + 1; ++z)
{
if (z >= m_heightStickLength)
{
continue;
}
for (int x = x0; x < x0 + chunkSize + 1; ++x)
{
if (x >= m_heightStickWidth)
{
continue;
}
btScalar height = getRawHeightFieldValue(x, z);
if (height < r.min)
{
r.min = height;
}
else if (height > r.max)
{
r.max = height;
}
}
}
m_vboundsGrid[cx + cz * nChunksX] = r;
}
}
}
void btHeightfieldTerrainShape::clearAccelerator()
{
m_vboundsGrid.clear();
}

View File

@@ -17,6 +17,7 @@ subject to the following restrictions:
#define BT_HEIGHTFIELD_TERRAIN_SHAPE_H
#include "btConcaveShape.h"
#include "LinearMath/btAlignedObjectArray.h"
///btHeightfieldTerrainShape simulates a 2D heightfield terrain
/**
@@ -71,6 +72,13 @@ subject to the following restrictions:
ATTRIBUTE_ALIGNED16(class)
btHeightfieldTerrainShape : public btConcaveShape
{
public:
struct Range
{
btScalar min;
btScalar max;
};
protected:
btVector3 m_localAabbMin;
btVector3 m_localAabbMax;
@@ -100,9 +108,14 @@ protected:
btVector3 m_localScaling;
// Accelerator
btAlignedObjectArray<Range> m_vboundsGrid;
int m_vboundsGridWidth;
int m_vboundsGridLength;
int m_vboundsChunkSize;
virtual btScalar getRawHeightFieldValue(int x, int y) const;
void quantizeWithClamp(int* out, const btVector3& point, int isMax) const;
void getVertex(int x, int y, btVector3& vertex) const;
/// protected initialization
/**
@@ -155,6 +168,17 @@ public:
virtual const btVector3& getLocalScaling() const;
void getVertex(int x, int y, btVector3& vertex) const;
void performRaycast(btTriangleCallback * callback, const btVector3& raySource, const btVector3& rayTarget) const;
void buildAccelerator(int chunkSize = 16);
void clearAccelerator();
int getUpAxis() const
{
return m_upAxis;
}
//debugging
virtual const char* getName() const { return "HEIGHTFIELD"; }
};

View File

@@ -37,6 +37,7 @@ public:
///SubSimplexConvexCastRaytest is the default, even if kF_None is set.
kF_UseSubSimplexConvexCastRaytest = 1 << 2, // Uses an approximate but faster ray versus convex intersection algorithm
kF_UseGjkConvexCastRaytest = 1 << 3,
kF_DisableHeightfieldAccelerator = 1 << 4, //don't use the heightfield raycast accelerator. See https://github.com/bulletphysics/bullet3/pull/2062
kF_Terminator = 0xFFFFFFFF
};
unsigned int m_flags;