410 lines
11 KiB
C++
410 lines
11 KiB
C++
|
/*
|
||
|
Bullet Continuous Collision Detection and Physics Library
|
||
|
Copyright (c) 2003-2009 Erwin Coumans 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.
|
||
|
*/
|
||
|
|
||
|
#include "btHeightfieldTerrainShape.h"
|
||
|
|
||
|
#include "LinearMath/btTransformUtil.h"
|
||
|
|
||
|
|
||
|
|
||
|
btHeightfieldTerrainShape::btHeightfieldTerrainShape
|
||
|
(
|
||
|
int heightStickWidth, int heightStickLength, const void* heightfieldData,
|
||
|
btScalar heightScale, btScalar minHeight, btScalar maxHeight,int upAxis,
|
||
|
PHY_ScalarType hdt, bool flipQuadEdges
|
||
|
)
|
||
|
{
|
||
|
initialize(heightStickWidth, heightStickLength, heightfieldData,
|
||
|
heightScale, minHeight, maxHeight, upAxis, hdt,
|
||
|
flipQuadEdges);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
btHeightfieldTerrainShape::btHeightfieldTerrainShape(int heightStickWidth, int heightStickLength,const void* heightfieldData,btScalar maxHeight,int upAxis,bool useFloatData,bool flipQuadEdges)
|
||
|
{
|
||
|
// legacy constructor: support only float or unsigned char,
|
||
|
// and min height is zero
|
||
|
PHY_ScalarType hdt = (useFloatData) ? PHY_FLOAT : PHY_UCHAR;
|
||
|
btScalar minHeight = 0.0f;
|
||
|
|
||
|
// previously, height = uchar * maxHeight / 65535.
|
||
|
// So to preserve legacy behavior, heightScale = maxHeight / 65535
|
||
|
btScalar heightScale = maxHeight / 65535;
|
||
|
|
||
|
initialize(heightStickWidth, heightStickLength, heightfieldData,
|
||
|
heightScale, minHeight, maxHeight, upAxis, hdt,
|
||
|
flipQuadEdges);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void btHeightfieldTerrainShape::initialize
|
||
|
(
|
||
|
int heightStickWidth, int heightStickLength, const void* heightfieldData,
|
||
|
btScalar heightScale, btScalar minHeight, btScalar maxHeight, int upAxis,
|
||
|
PHY_ScalarType hdt, bool flipQuadEdges
|
||
|
)
|
||
|
{
|
||
|
// validation
|
||
|
btAssert(heightStickWidth > 1);// && "bad width");
|
||
|
btAssert(heightStickLength > 1);// && "bad length");
|
||
|
btAssert(heightfieldData);// && "null heightfield data");
|
||
|
// btAssert(heightScale) -- do we care? Trust caller here
|
||
|
btAssert(minHeight <= maxHeight);// && "bad min/max height");
|
||
|
btAssert(upAxis >= 0 && upAxis < 3);// && "bad upAxis--should be in range [0,2]");
|
||
|
btAssert(hdt != PHY_UCHAR || hdt != PHY_FLOAT || hdt != PHY_SHORT);// && "Bad height data type enum");
|
||
|
|
||
|
// initialize member variables
|
||
|
m_shapeType = TERRAIN_SHAPE_PROXYTYPE;
|
||
|
m_heightStickWidth = heightStickWidth;
|
||
|
m_heightStickLength = heightStickLength;
|
||
|
m_minHeight = minHeight;
|
||
|
m_maxHeight = maxHeight;
|
||
|
m_width = (btScalar) (heightStickWidth - 1);
|
||
|
m_length = (btScalar) (heightStickLength - 1);
|
||
|
m_heightScale = heightScale;
|
||
|
m_heightfieldDataUnknown = heightfieldData;
|
||
|
m_heightDataType = hdt;
|
||
|
m_flipQuadEdges = flipQuadEdges;
|
||
|
m_useDiamondSubdivision = false;
|
||
|
m_useZigzagSubdivision = false;
|
||
|
m_upAxis = upAxis;
|
||
|
m_localScaling.setValue(btScalar(1.), btScalar(1.), btScalar(1.));
|
||
|
|
||
|
// determine min/max axis-aligned bounding box (aabb) values
|
||
|
switch (m_upAxis)
|
||
|
{
|
||
|
case 0:
|
||
|
{
|
||
|
m_localAabbMin.setValue(m_minHeight, 0, 0);
|
||
|
m_localAabbMax.setValue(m_maxHeight, m_width, m_length);
|
||
|
break;
|
||
|
}
|
||
|
case 1:
|
||
|
{
|
||
|
m_localAabbMin.setValue(0, m_minHeight, 0);
|
||
|
m_localAabbMax.setValue(m_width, m_maxHeight, m_length);
|
||
|
break;
|
||
|
};
|
||
|
case 2:
|
||
|
{
|
||
|
m_localAabbMin.setValue(0, 0, m_minHeight);
|
||
|
m_localAabbMax.setValue(m_width, m_length, m_maxHeight);
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
{
|
||
|
//need to get valid m_upAxis
|
||
|
btAssert(0);// && "Bad m_upAxis");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// remember origin (defined as exact middle of aabb)
|
||
|
m_localOrigin = btScalar(0.5) * (m_localAabbMin + m_localAabbMax);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
btHeightfieldTerrainShape::~btHeightfieldTerrainShape()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void btHeightfieldTerrainShape::getAabb(const btTransform& t,btVector3& aabbMin,btVector3& aabbMax) const
|
||
|
{
|
||
|
btVector3 halfExtents = (m_localAabbMax-m_localAabbMin)* m_localScaling * btScalar(0.5);
|
||
|
|
||
|
btVector3 localOrigin(0, 0, 0);
|
||
|
localOrigin[m_upAxis] = (m_minHeight + m_maxHeight) * btScalar(0.5);
|
||
|
localOrigin *= m_localScaling;
|
||
|
|
||
|
btMatrix3x3 abs_b = t.getBasis().absolute();
|
||
|
btVector3 center = t.getOrigin();
|
||
|
btVector3 extent = halfExtents.dot3(abs_b[0], abs_b[1], abs_b[2]);
|
||
|
extent += btVector3(getMargin(),getMargin(),getMargin());
|
||
|
|
||
|
aabbMin = center - extent;
|
||
|
aabbMax = center + extent;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// This returns the "raw" (user's initial) height, not the actual height.
|
||
|
/// The actual height needs to be adjusted to be relative to the center
|
||
|
/// of the heightfield's AABB.
|
||
|
btScalar
|
||
|
btHeightfieldTerrainShape::getRawHeightFieldValue(int x,int y) const
|
||
|
{
|
||
|
btScalar val = 0.f;
|
||
|
switch (m_heightDataType)
|
||
|
{
|
||
|
case PHY_FLOAT:
|
||
|
{
|
||
|
val = m_heightfieldDataFloat[(y*m_heightStickWidth)+x];
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case PHY_UCHAR:
|
||
|
{
|
||
|
unsigned char heightFieldValue = m_heightfieldDataUnsignedChar[(y*m_heightStickWidth)+x];
|
||
|
val = heightFieldValue * m_heightScale;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case PHY_SHORT:
|
||
|
{
|
||
|
short hfValue = m_heightfieldDataShort[(y * m_heightStickWidth) + x];
|
||
|
val = hfValue * m_heightScale;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
{
|
||
|
btAssert(!"Bad m_heightDataType");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/// this returns the vertex in bullet-local coordinates
|
||
|
void btHeightfieldTerrainShape::getVertex(int x,int y,btVector3& vertex) const
|
||
|
{
|
||
|
btAssert(x>=0);
|
||
|
btAssert(y>=0);
|
||
|
btAssert(x<m_heightStickWidth);
|
||
|
btAssert(y<m_heightStickLength);
|
||
|
|
||
|
btScalar height = getRawHeightFieldValue(x,y);
|
||
|
|
||
|
switch (m_upAxis)
|
||
|
{
|
||
|
case 0:
|
||
|
{
|
||
|
vertex.setValue(
|
||
|
height - m_localOrigin.getX(),
|
||
|
(-m_width/btScalar(2.0)) + x,
|
||
|
(-m_length/btScalar(2.0) ) + y
|
||
|
);
|
||
|
break;
|
||
|
}
|
||
|
case 1:
|
||
|
{
|
||
|
vertex.setValue(
|
||
|
(-m_width/btScalar(2.0)) + x,
|
||
|
height - m_localOrigin.getY(),
|
||
|
(-m_length/btScalar(2.0)) + y
|
||
|
);
|
||
|
break;
|
||
|
};
|
||
|
case 2:
|
||
|
{
|
||
|
vertex.setValue(
|
||
|
(-m_width/btScalar(2.0)) + x,
|
||
|
(-m_length/btScalar(2.0)) + y,
|
||
|
height - m_localOrigin.getZ()
|
||
|
);
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
{
|
||
|
//need to get valid m_upAxis
|
||
|
btAssert(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
vertex*=m_localScaling;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static inline int
|
||
|
getQuantized
|
||
|
(
|
||
|
btScalar x
|
||
|
)
|
||
|
{
|
||
|
if (x < 0.0) {
|
||
|
return (int) (x - 0.5);
|
||
|
}
|
||
|
return (int) (x + 0.5);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/// given input vector, return quantized version
|
||
|
/**
|
||
|
This routine is basically determining the gridpoint indices for a given
|
||
|
input vector, answering the question: "which gridpoint is closest to the
|
||
|
provided point?".
|
||
|
|
||
|
"with clamp" means that we restrict the point to be in the heightfield's
|
||
|
axis-aligned bounding box.
|
||
|
*/
|
||
|
void btHeightfieldTerrainShape::quantizeWithClamp(int* out, const btVector3& point,int /*isMax*/) const
|
||
|
{
|
||
|
btVector3 clampedPoint(point);
|
||
|
clampedPoint.setMax(m_localAabbMin);
|
||
|
clampedPoint.setMin(m_localAabbMax);
|
||
|
|
||
|
out[0] = getQuantized(clampedPoint.getX());
|
||
|
out[1] = getQuantized(clampedPoint.getY());
|
||
|
out[2] = getQuantized(clampedPoint.getZ());
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/// process all triangles within the provided axis-aligned bounding box
|
||
|
/**
|
||
|
basic algorithm:
|
||
|
- convert input aabb to local coordinates (scale down and shift for local origin)
|
||
|
- convert input aabb to a range of heightfield grid points (quantize)
|
||
|
- iterate over all triangles in that subset of the grid
|
||
|
*/
|
||
|
void btHeightfieldTerrainShape::processAllTriangles(btTriangleCallback* callback,const btVector3& aabbMin,const btVector3& aabbMax) const
|
||
|
{
|
||
|
// scale down the input aabb's so they are in local (non-scaled) coordinates
|
||
|
btVector3 localAabbMin = aabbMin*btVector3(1.f/m_localScaling[0],1.f/m_localScaling[1],1.f/m_localScaling[2]);
|
||
|
btVector3 localAabbMax = aabbMax*btVector3(1.f/m_localScaling[0],1.f/m_localScaling[1],1.f/m_localScaling[2]);
|
||
|
|
||
|
// account for local origin
|
||
|
localAabbMin += m_localOrigin;
|
||
|
localAabbMax += m_localOrigin;
|
||
|
|
||
|
//quantize the aabbMin and aabbMax, and adjust the start/end ranges
|
||
|
int quantizedAabbMin[3];
|
||
|
int quantizedAabbMax[3];
|
||
|
quantizeWithClamp(quantizedAabbMin, localAabbMin,0);
|
||
|
quantizeWithClamp(quantizedAabbMax, localAabbMax,1);
|
||
|
|
||
|
// expand the min/max quantized values
|
||
|
// this is to catch the case where the input aabb falls between grid points!
|
||
|
for (int i = 0; i < 3; ++i) {
|
||
|
quantizedAabbMin[i]--;
|
||
|
quantizedAabbMax[i]++;
|
||
|
}
|
||
|
|
||
|
int startX=0;
|
||
|
int endX=m_heightStickWidth-1;
|
||
|
int startJ=0;
|
||
|
int endJ=m_heightStickLength-1;
|
||
|
|
||
|
switch (m_upAxis)
|
||
|
{
|
||
|
case 0:
|
||
|
{
|
||
|
if (quantizedAabbMin[1]>startX)
|
||
|
startX = quantizedAabbMin[1];
|
||
|
if (quantizedAabbMax[1]<endX)
|
||
|
endX = quantizedAabbMax[1];
|
||
|
if (quantizedAabbMin[2]>startJ)
|
||
|
startJ = quantizedAabbMin[2];
|
||
|
if (quantizedAabbMax[2]<endJ)
|
||
|
endJ = quantizedAabbMax[2];
|
||
|
break;
|
||
|
}
|
||
|
case 1:
|
||
|
{
|
||
|
if (quantizedAabbMin[0]>startX)
|
||
|
startX = quantizedAabbMin[0];
|
||
|
if (quantizedAabbMax[0]<endX)
|
||
|
endX = quantizedAabbMax[0];
|
||
|
if (quantizedAabbMin[2]>startJ)
|
||
|
startJ = quantizedAabbMin[2];
|
||
|
if (quantizedAabbMax[2]<endJ)
|
||
|
endJ = quantizedAabbMax[2];
|
||
|
break;
|
||
|
};
|
||
|
case 2:
|
||
|
{
|
||
|
if (quantizedAabbMin[0]>startX)
|
||
|
startX = quantizedAabbMin[0];
|
||
|
if (quantizedAabbMax[0]<endX)
|
||
|
endX = quantizedAabbMax[0];
|
||
|
if (quantizedAabbMin[1]>startJ)
|
||
|
startJ = quantizedAabbMin[1];
|
||
|
if (quantizedAabbMax[1]<endJ)
|
||
|
endJ = quantizedAabbMax[1];
|
||
|
break;
|
||
|
}
|
||
|
default:
|
||
|
{
|
||
|
//need to get valid m_upAxis
|
||
|
btAssert(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
for(int j=startJ; j<endJ; j++)
|
||
|
{
|
||
|
for(int x=startX; x<endX; x++)
|
||
|
{
|
||
|
btVector3 vertices[3];
|
||
|
if (m_flipQuadEdges || (m_useDiamondSubdivision && !((j+x) & 1))|| (m_useZigzagSubdivision && !(j & 1)))
|
||
|
{
|
||
|
//first triangle
|
||
|
getVertex(x,j,vertices[0]);
|
||
|
getVertex(x, j + 1, vertices[1]);
|
||
|
getVertex(x + 1, j + 1, vertices[2]);
|
||
|
callback->processTriangle(vertices,x,j);
|
||
|
//second triangle
|
||
|
// getVertex(x,j,vertices[0]);//already got this vertex before, thanks to Danny Chapman
|
||
|
getVertex(x+1,j+1,vertices[1]);
|
||
|
getVertex(x + 1, j, vertices[2]);
|
||
|
callback->processTriangle(vertices, x, j);
|
||
|
|
||
|
} else
|
||
|
{
|
||
|
//first triangle
|
||
|
getVertex(x,j,vertices[0]);
|
||
|
getVertex(x,j+1,vertices[1]);
|
||
|
getVertex(x+1,j,vertices[2]);
|
||
|
callback->processTriangle(vertices,x,j);
|
||
|
//second triangle
|
||
|
getVertex(x+1,j,vertices[0]);
|
||
|
//getVertex(x,j+1,vertices[1]);
|
||
|
getVertex(x+1,j+1,vertices[2]);
|
||
|
callback->processTriangle(vertices,x,j);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
void btHeightfieldTerrainShape::calculateLocalInertia(btScalar ,btVector3& inertia) const
|
||
|
{
|
||
|
//moving concave objects not supported
|
||
|
|
||
|
inertia.setValue(btScalar(0.),btScalar(0.),btScalar(0.));
|
||
|
}
|
||
|
|
||
|
void btHeightfieldTerrainShape::setLocalScaling(const btVector3& scaling)
|
||
|
{
|
||
|
m_localScaling = scaling;
|
||
|
}
|
||
|
const btVector3& btHeightfieldTerrainShape::getLocalScaling() const
|
||
|
{
|
||
|
return m_localScaling;
|
||
|
}
|