mirror of
https://github.com/bulletphysics/bullet3
synced 2024-12-14 22:00:05 +00:00
dc491936a2
fix some relative path issues for loading assets
922 lines
20 KiB
C++
922 lines
20 KiB
C++
|
|
/*
|
|
Bullet Continuous Collision Detection and Physics Library
|
|
Copyright (c) 2003-2006,2008 Erwin Coumans http://continuousphysics.com/Bullet/
|
|
|
|
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 "TerrainDemo.h" // always include our own header first!
|
|
|
|
#include "btBulletDynamicsCommon.h"
|
|
#include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h"
|
|
|
|
#include "GLDebugDrawer.h"
|
|
|
|
#include "GL_ShapeDrawer.h"
|
|
|
|
#include "GlutStuff.h"
|
|
#include "GLDebugFont.h"
|
|
|
|
|
|
|
|
// constants -------------------------------------------------------------------
|
|
static const float s_gravity = 9.8; // 9.8 m/s^2
|
|
|
|
static const int s_gridSize = 64 + 1; // must be (2^N) + 1
|
|
static const float s_gridSpacing = 5.0;
|
|
|
|
static const float s_gridHeightScale = 0.2;
|
|
|
|
// the singularity at the center of the radial model means we need a lot of
|
|
// finely-spaced time steps to get the physics right.
|
|
// These numbers are probably too aggressive for a real game!
|
|
static const int s_requestedHz = 180;
|
|
static const float s_engineTimeStep = 1.0 / s_requestedHz;
|
|
|
|
// delta phase: radians per second
|
|
static const float s_deltaPhase = 0.25 * 2.0 * SIMD_PI;
|
|
|
|
// what type of terrain is generated?
|
|
enum eTerrainModel {
|
|
eRadial = 1, // deterministic
|
|
eFractal = 2 // random
|
|
};
|
|
|
|
|
|
typedef unsigned char byte_t;
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// static helper methods
|
|
//
|
|
// Only used within this file (helpers and terrain generation, etc)
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static const char *
|
|
getTerrainTypeName
|
|
(
|
|
eTerrainModel model
|
|
)
|
|
{
|
|
switch (model) {
|
|
case eRadial:
|
|
return "Radial";
|
|
|
|
case eFractal:
|
|
return "Fractal";
|
|
|
|
default:
|
|
btAssert(!"bad terrain model type");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
getDataTypeName
|
|
(
|
|
PHY_ScalarType type
|
|
)
|
|
{
|
|
switch (type) {
|
|
case PHY_UCHAR:
|
|
return "UnsignedChar";
|
|
|
|
case PHY_SHORT:
|
|
return "Short";
|
|
|
|
case PHY_FLOAT:
|
|
return "Float";
|
|
|
|
default:
|
|
btAssert(!"bad heightfield data type");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
getUpAxisName
|
|
(
|
|
int axis
|
|
)
|
|
{
|
|
switch (axis) {
|
|
case 0:
|
|
return "X";
|
|
|
|
case 1:
|
|
return "Y";
|
|
|
|
case 2:
|
|
return "Z";
|
|
|
|
default:
|
|
btAssert(!"bad up axis");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
static btVector3
|
|
getUpVector
|
|
(
|
|
int upAxis,
|
|
btScalar regularValue,
|
|
btScalar upValue
|
|
)
|
|
{
|
|
btAssert(upAxis >= 0 && upAxis <= 2 && "bad up axis");
|
|
|
|
btVector3 v(regularValue, regularValue, regularValue);
|
|
v[upAxis] = upValue;
|
|
|
|
return v;
|
|
}
|
|
|
|
|
|
|
|
// TODO: it would probably cleaner to have a struct per data type, so
|
|
// you could lookup byte sizes, conversion functions, etc.
|
|
static int getByteSize
|
|
(
|
|
PHY_ScalarType type
|
|
)
|
|
{
|
|
int size = 0;
|
|
|
|
switch (type) {
|
|
case PHY_FLOAT:
|
|
size = sizeof(float);
|
|
break;
|
|
|
|
case PHY_UCHAR:
|
|
size = sizeof(unsigned char);
|
|
break;
|
|
|
|
case PHY_SHORT:
|
|
size = sizeof(short);
|
|
break;
|
|
|
|
default:
|
|
btAssert(!"Bad heightfield data type");
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
|
|
|
|
static float
|
|
convertToFloat
|
|
(
|
|
const byte_t * p,
|
|
PHY_ScalarType type
|
|
)
|
|
{
|
|
btAssert(p);
|
|
|
|
switch (type) {
|
|
case PHY_FLOAT:
|
|
{
|
|
float * pf = (float *) p;
|
|
return *pf;
|
|
}
|
|
|
|
case PHY_UCHAR:
|
|
{
|
|
unsigned char * pu = (unsigned char *) p;
|
|
return ((*pu) * s_gridHeightScale);
|
|
}
|
|
|
|
case PHY_SHORT:
|
|
{
|
|
short * ps = (short *) p;
|
|
return ((*ps) * s_gridHeightScale);
|
|
}
|
|
|
|
default:
|
|
btAssert(!"bad type");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static float
|
|
getGridHeight
|
|
(
|
|
byte_t * grid,
|
|
int i,
|
|
int j,
|
|
PHY_ScalarType type
|
|
)
|
|
{
|
|
btAssert(grid);
|
|
btAssert(i >= 0 && i < s_gridSize);
|
|
btAssert(j >= 0 && j < s_gridSize);
|
|
|
|
int bpe = getByteSize(type);
|
|
btAssert(bpe > 0 && "bad bytes per element");
|
|
|
|
int idx = (j * s_gridSize) + i;
|
|
long offset = ((long) bpe) * idx;
|
|
|
|
byte_t * p = grid + offset;
|
|
|
|
return convertToFloat(p, type);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
convertFromFloat
|
|
(
|
|
byte_t * p,
|
|
float value,
|
|
PHY_ScalarType type
|
|
)
|
|
{
|
|
btAssert(p && "null");
|
|
|
|
switch (type) {
|
|
case PHY_FLOAT:
|
|
{
|
|
float * pf = (float *) p;
|
|
*pf = value;
|
|
}
|
|
break;
|
|
|
|
case PHY_UCHAR:
|
|
{
|
|
unsigned char * pu = (unsigned char *) p;
|
|
*pu = (unsigned char) (value / s_gridHeightScale);
|
|
}
|
|
break;
|
|
|
|
case PHY_SHORT:
|
|
{
|
|
short * ps = (short *) p;
|
|
*ps = (short) (value / s_gridHeightScale);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
btAssert(!"bad type");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// creates a radially-varying heightfield
|
|
static void
|
|
setRadial
|
|
(
|
|
byte_t * grid,
|
|
int bytesPerElement,
|
|
PHY_ScalarType type,
|
|
float phase = 0.0
|
|
)
|
|
{
|
|
btAssert(grid);
|
|
btAssert(bytesPerElement > 0);
|
|
|
|
// min/max
|
|
float period = 0.5 / s_gridSpacing;
|
|
float floor = 0.0;
|
|
float min_r = 3.0 * sqrt(s_gridSpacing);
|
|
float magnitude = 50.0 * sqrt(s_gridSpacing);
|
|
|
|
// pick a base_phase such that phase = 0 results in max height
|
|
// (this way, if you create a heightfield with phase = 0,
|
|
// you can rely on the min/max heights that result)
|
|
float base_phase = (0.5 * SIMD_PI) - (period * min_r);
|
|
phase += base_phase;
|
|
|
|
// center of grid
|
|
float cx = 0.5 * s_gridSize * s_gridSpacing;
|
|
float cy = cx; // assume square grid
|
|
byte_t * p = grid;
|
|
for (int i = 0; i < s_gridSize; ++i) {
|
|
float x = i * s_gridSpacing;
|
|
for (int j = 0; j < s_gridSize; ++j) {
|
|
float y = j * s_gridSpacing;
|
|
|
|
float dx = x - cx;
|
|
float dy = y - cy;
|
|
|
|
float r = sqrt((dx * dx) + (dy * dy));
|
|
|
|
float z = period;
|
|
if (r < min_r) {
|
|
r = min_r;
|
|
}
|
|
z = (1.0 / r) * sin(period * r + phase);
|
|
if (z > period) {
|
|
z = period;
|
|
} else if (z < -period) {
|
|
z = -period;
|
|
}
|
|
z = floor + magnitude * z;
|
|
|
|
convertFromFloat(p, z, type);
|
|
p += bytesPerElement;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static float
|
|
randomHeight
|
|
(
|
|
int step
|
|
)
|
|
{
|
|
return (0.33 * s_gridSpacing * s_gridSize * step * (rand() - (0.5 * RAND_MAX))) / (1.0 * RAND_MAX * s_gridSize);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
dumpGrid
|
|
(
|
|
const byte_t * grid,
|
|
int bytesPerElement,
|
|
PHY_ScalarType type,
|
|
int max
|
|
)
|
|
{
|
|
//std::cerr << "Grid:\n";
|
|
|
|
char buffer[32];
|
|
|
|
for (int j = 0; j < max; ++j) {
|
|
for (int i = 0; i < max; ++i) {
|
|
long offset = j * s_gridSize + i;
|
|
float z = convertToFloat(grid + offset * bytesPerElement, type);
|
|
sprintf(buffer, "%6.2f", z);
|
|
//std::cerr << " " << buffer;
|
|
}
|
|
//std::cerr << "\n";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
updateHeight
|
|
(
|
|
byte_t * p,
|
|
float new_val,
|
|
PHY_ScalarType type
|
|
)
|
|
{
|
|
float old_val = convertToFloat(p, type);
|
|
if (!old_val) {
|
|
convertFromFloat(p, new_val, type);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// creates a random, fractal heightfield
|
|
static void
|
|
setFractal
|
|
(
|
|
byte_t * grid,
|
|
int bytesPerElement,
|
|
PHY_ScalarType type,
|
|
int step
|
|
)
|
|
{
|
|
btAssert(grid);
|
|
btAssert(bytesPerElement > 0);
|
|
btAssert(step > 0);
|
|
btAssert(step < s_gridSize);
|
|
|
|
int newStep = step / 2;
|
|
// std::cerr << "Computing grid with step = " << step << ": before\n";
|
|
// dumpGrid(grid, bytesPerElement, type, step + 1);
|
|
|
|
// special case: starting (must set four corners)
|
|
if (s_gridSize - 1 == step) {
|
|
// pick a non-zero (possibly negative) base elevation for testing
|
|
float base = randomHeight(step / 2);
|
|
|
|
convertFromFloat(grid, base, type);
|
|
convertFromFloat(grid + step * bytesPerElement, base, type);
|
|
convertFromFloat(grid + step * s_gridSize * bytesPerElement, base, type);
|
|
convertFromFloat(grid + (step * s_gridSize + step) * bytesPerElement, base, type);
|
|
}
|
|
|
|
// determine elevation of each corner
|
|
float c00 = convertToFloat(grid, type);
|
|
float c01 = convertToFloat(grid + step * bytesPerElement, type);
|
|
float c10 = convertToFloat(grid + (step * s_gridSize) * bytesPerElement, type);
|
|
float c11 = convertToFloat(grid + (step * s_gridSize + step) * bytesPerElement, type);
|
|
|
|
// set top middle
|
|
updateHeight(grid + newStep * bytesPerElement, 0.5 * (c00 + c01) + randomHeight(step), type);
|
|
|
|
// set left middle
|
|
updateHeight(grid + (newStep * s_gridSize) * bytesPerElement, 0.5 * (c00 + c10) + randomHeight(step), type);
|
|
|
|
// set right middle
|
|
updateHeight(grid + (newStep * s_gridSize + step) * bytesPerElement, 0.5 * (c01 + c11) + randomHeight(step), type);
|
|
|
|
// set bottom middle
|
|
updateHeight(grid + (step * s_gridSize + newStep) * bytesPerElement, 0.5 * (c10 + c11) + randomHeight(step), type);
|
|
|
|
// set middle
|
|
updateHeight(grid + (newStep * s_gridSize + newStep) * bytesPerElement, 0.25 * (c00 + c01 + c10 + c11) + randomHeight(step), type);
|
|
|
|
// std::cerr << "Computing grid with step = " << step << ": after\n";
|
|
// dumpGrid(grid, bytesPerElement, type, step + 1);
|
|
|
|
// terminate?
|
|
if (newStep < 2) {
|
|
return;
|
|
}
|
|
|
|
// recurse
|
|
setFractal(grid, bytesPerElement, type, newStep);
|
|
setFractal(grid + newStep * bytesPerElement, bytesPerElement, type, newStep);
|
|
setFractal(grid + (newStep * s_gridSize) * bytesPerElement, bytesPerElement, type, newStep);
|
|
setFractal(grid + ((newStep * s_gridSize) + newStep) * bytesPerElement, bytesPerElement, type, newStep);
|
|
}
|
|
|
|
|
|
|
|
static byte_t *
|
|
getRawHeightfieldData
|
|
(
|
|
eTerrainModel model,
|
|
PHY_ScalarType type,
|
|
btScalar& minHeight,
|
|
btScalar& maxHeight
|
|
)
|
|
{
|
|
// std::cerr << "\nRegenerating terrain\n";
|
|
// std::cerr << " model = " << model << "\n";
|
|
// std::cerr << " type = " << type << "\n";
|
|
|
|
long nElements = ((long) s_gridSize) * s_gridSize;
|
|
// std::cerr << " nElements = " << nElements << "\n";
|
|
|
|
int bytesPerElement = getByteSize(type);
|
|
// std::cerr << " bytesPerElement = " << bytesPerElement << "\n";
|
|
btAssert(bytesPerElement > 0 && "bad bytes per element");
|
|
|
|
long nBytes = nElements * bytesPerElement;
|
|
// std::cerr << " nBytes = " << nBytes << "\n";
|
|
byte_t * raw = new byte_t[nBytes];
|
|
btAssert(raw && "out of memory");
|
|
|
|
// reseed randomization every 30 seconds
|
|
// srand(time(NULL) / 30);
|
|
|
|
// populate based on model
|
|
switch (model) {
|
|
case eRadial:
|
|
setRadial(raw, bytesPerElement, type);
|
|
break;
|
|
|
|
case eFractal:
|
|
for (int i = 0; i < nBytes; i++)
|
|
{
|
|
raw[i] = 0;
|
|
}
|
|
setFractal(raw, bytesPerElement, type, s_gridSize - 1);
|
|
break;
|
|
|
|
default:
|
|
btAssert(!"bad model type");
|
|
}
|
|
|
|
if (0) {
|
|
// inside if(0) so it keeps compiling but isn't
|
|
// exercised and doesn't cause warnings
|
|
// std::cerr << "final grid:\n";
|
|
dumpGrid(raw, bytesPerElement, type, s_gridSize - 1);
|
|
}
|
|
|
|
// find min/max
|
|
for (int i = 0; i < s_gridSize; ++i) {
|
|
for (int j = 0; j < s_gridSize; ++j) {
|
|
float z = getGridHeight(raw, i, j, type);
|
|
// std::cerr << "i=" << i << ", j=" << j << ": z=" << z << "\n";
|
|
|
|
// update min/max
|
|
if (!i && !j) {
|
|
minHeight = z;
|
|
maxHeight = z;
|
|
} else {
|
|
if (z < minHeight) {
|
|
minHeight = z;
|
|
}
|
|
if (z > maxHeight) {
|
|
maxHeight = z;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (maxHeight < -minHeight) {
|
|
maxHeight = -minHeight;
|
|
}
|
|
if (minHeight > -maxHeight) {
|
|
minHeight = -maxHeight;
|
|
}
|
|
|
|
// std::cerr << " minHeight = " << minHeight << "\n";
|
|
// std::cerr << " maxHeight = " << maxHeight << "\n";
|
|
|
|
return raw;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// TerrainDemo class
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// class that demonstrates the btHeightfieldTerrainShape object
|
|
class TerrainDemo : public GlutDemoApplication {
|
|
public:
|
|
// constructor, destructor ---------------------------------------------
|
|
TerrainDemo(void);
|
|
~TerrainDemo(void);
|
|
|
|
virtual void initPhysics() {}
|
|
|
|
// public class methods ------------------------------------------------
|
|
void initialize(void);
|
|
|
|
// DemoApplication class interface methods -----------------------------
|
|
void clientMoveAndDisplay(void);
|
|
void keyboardCallback(unsigned char key, int x, int y);
|
|
void renderme(void);
|
|
|
|
private:
|
|
// private helper methods ----------------------------------------------
|
|
void resetPhysics(void);
|
|
void clearWorld(void);
|
|
|
|
// private data members ------------------------------------------------
|
|
btDefaultCollisionConfiguration * m_collisionConfiguration;
|
|
btCollisionDispatcher * m_dispatcher;
|
|
btAxisSweep3 * m_overlappingPairCache;
|
|
btSequentialImpulseConstraintSolver * m_constraintSolver;
|
|
btAlignedObjectArray<btCollisionShape*> m_collisionShapes;
|
|
int m_upAxis;
|
|
PHY_ScalarType m_type;
|
|
eTerrainModel m_model;
|
|
byte_t * m_rawHeightfieldData;
|
|
btScalar m_minHeight;
|
|
btScalar m_maxHeight;
|
|
float m_phase; // for dynamics
|
|
bool m_isDynamic;
|
|
};
|
|
|
|
|
|
|
|
TerrainDemo::TerrainDemo(void)
|
|
:
|
|
m_collisionConfiguration(NULL),
|
|
m_dispatcher(NULL),
|
|
m_overlappingPairCache(NULL),
|
|
m_constraintSolver(NULL),
|
|
m_upAxis(1),
|
|
m_type(PHY_FLOAT),
|
|
m_model(eFractal),
|
|
m_rawHeightfieldData(NULL),
|
|
m_phase(0.0),
|
|
m_isDynamic(true)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
TerrainDemo::~TerrainDemo(void)
|
|
{
|
|
clearWorld();
|
|
|
|
//delete dynamics world
|
|
delete m_dynamicsWorld;
|
|
|
|
//delete solver
|
|
delete m_constraintSolver;
|
|
|
|
//delete broadphase
|
|
delete m_overlappingPairCache;
|
|
|
|
//delete dispatcher
|
|
delete m_dispatcher;
|
|
|
|
delete m_collisionConfiguration;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// TerrainDemo -- public class methods
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// one-time class and physics initialization
|
|
void TerrainDemo::initialize(void)
|
|
{
|
|
// std::cerr << "initializing...\n";
|
|
|
|
// set up basic state
|
|
m_upAxis = 1; // start with Y-axis as "up"
|
|
m_type = PHY_FLOAT;
|
|
m_model = eRadial;//eFractal;
|
|
m_isDynamic = true;
|
|
|
|
// set up the physics world
|
|
m_collisionConfiguration = new btDefaultCollisionConfiguration();
|
|
m_dispatcher = new btCollisionDispatcher(m_collisionConfiguration);
|
|
btVector3 worldMin(-1000,-1000,-1000);
|
|
btVector3 worldMax(1000,1000,1000);
|
|
m_overlappingPairCache = new btAxisSweep3(worldMin,worldMax);
|
|
m_constraintSolver = new btSequentialImpulseConstraintSolver();
|
|
m_dynamicsWorld = new btDiscreteDynamicsWorld(m_dispatcher,m_overlappingPairCache,m_constraintSolver,m_collisionConfiguration);
|
|
|
|
// initialize axis- or type-dependent physics from here
|
|
this->resetPhysics();
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// TerrainDemo -- DemoApplication class interface methods
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void TerrainDemo::clientMoveAndDisplay(void)
|
|
{
|
|
// elapsed time
|
|
float us = getDeltaTimeMicroseconds();
|
|
float seconds = 1.0e-6 * us;
|
|
|
|
// we'll carefully iterate through each time step so we can update
|
|
// the dynamic model if necessary
|
|
long nStepsPerIteration = 1;
|
|
while (seconds > 1.0e-6) {
|
|
float dt = nStepsPerIteration * s_engineTimeStep;
|
|
if (dt > seconds) {
|
|
dt = seconds;
|
|
}
|
|
seconds -= dt;
|
|
// std::cerr << " Stepping through " << dt << " seconds\n";
|
|
|
|
// if dynamic and radial, go ahead and update the field
|
|
if (m_rawHeightfieldData && m_isDynamic && eRadial == m_model) {
|
|
m_phase += s_deltaPhase * dt;
|
|
if (m_phase > 2.0 * SIMD_PI) {
|
|
m_phase -= 2.0 * SIMD_PI;
|
|
}
|
|
int bpe = getByteSize(m_type);
|
|
btAssert(bpe > 0 && "Bad bytes per element");
|
|
setRadial(m_rawHeightfieldData, bpe, m_type, m_phase);
|
|
}
|
|
|
|
if (m_dynamicsWorld) {
|
|
m_dynamicsWorld->stepSimulation(dt,
|
|
nStepsPerIteration + 1, s_engineTimeStep);
|
|
}
|
|
}
|
|
|
|
// okay, render
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
renderme();
|
|
glFlush();
|
|
glutSwapBuffers();
|
|
}
|
|
|
|
|
|
static PHY_ScalarType nextType (PHY_ScalarType type)
|
|
{
|
|
switch (type)
|
|
{
|
|
case PHY_FLOAT:
|
|
return PHY_SHORT;
|
|
break;
|
|
case PHY_SHORT:
|
|
return PHY_UCHAR;
|
|
break;
|
|
case PHY_UCHAR:
|
|
return PHY_FLOAT;
|
|
break;
|
|
}
|
|
btAssert (0);
|
|
return PHY_FLOAT;
|
|
}
|
|
|
|
void TerrainDemo::keyboardCallback(unsigned char key, int x, int y) {
|
|
|
|
if (',' == key) {
|
|
// increment model
|
|
m_model = (eFractal == m_model) ? eRadial : eFractal;
|
|
this->resetPhysics();
|
|
}
|
|
if ('/' == key) {
|
|
// increment type
|
|
m_type = nextType(m_type);
|
|
this->resetPhysics();
|
|
}
|
|
if ('\\' == key) {
|
|
// increment axis
|
|
m_upAxis++;
|
|
if (m_upAxis > 2) {
|
|
m_upAxis = 0;
|
|
}
|
|
this->resetPhysics();
|
|
}
|
|
if ('[' == key) {
|
|
// toggle dynamics
|
|
m_isDynamic = !m_isDynamic;
|
|
}
|
|
|
|
// let demo base class handle!
|
|
DemoApplication::keyboardCallback(key, x, y);
|
|
}
|
|
|
|
|
|
|
|
static void doPrint(int x,int& y,int dy,const char * text)
|
|
{
|
|
GLDebugDrawString(x,y, text);
|
|
y += dy;
|
|
}
|
|
|
|
|
|
|
|
/// override the default display just so we can overlay a bit more text
|
|
void TerrainDemo::renderme(void)
|
|
{
|
|
// give base class a shot
|
|
DemoApplication::renderme();
|
|
|
|
// overlay any debug information
|
|
if (m_dynamicsWorld)
|
|
m_dynamicsWorld->debugDrawWorld();
|
|
|
|
// switch to orthographic
|
|
setOrthographicProjection();
|
|
|
|
// we'll draw on the right top of the screen
|
|
const int lineWidth = 200;
|
|
const int lineHeight = 16;
|
|
char buffer[256];
|
|
|
|
int xStart = m_glutScreenWidth - lineWidth;
|
|
int yStart = lineHeight;
|
|
|
|
sprintf(buffer, "Terrain Type: %s", getTerrainTypeName(m_model));
|
|
doPrint(xStart, yStart, lineHeight, buffer);
|
|
doPrint(xStart, yStart, lineHeight, "Press ',' to cycle terrain types");
|
|
doPrint(xStart, yStart, lineHeight, "");
|
|
|
|
sprintf(buffer, "Data Type: %s", getDataTypeName(m_type));
|
|
doPrint(xStart, yStart, lineHeight, buffer);
|
|
doPrint(xStart, yStart, lineHeight, "Press '/' to cycle data types");
|
|
doPrint(xStart, yStart, lineHeight, "");
|
|
|
|
sprintf(buffer, "'up' axis: %s", getUpAxisName(m_upAxis));
|
|
doPrint(xStart, yStart, lineHeight, buffer);
|
|
doPrint(xStart, yStart, lineHeight, "Press '\\' to cycle 'up' axes");
|
|
doPrint(xStart, yStart, lineHeight, "");
|
|
|
|
if (eRadial == m_model) {
|
|
sprintf(buffer, "Dynamic: %s", m_isDynamic ? "yes" : "no");
|
|
doPrint(xStart, yStart, lineHeight, buffer);
|
|
doPrint(xStart, yStart, lineHeight, "Press '[' to toggle dynamics");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// TerrainDemo -- private helper methods
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// called whenever key terrain attribute is changed
|
|
void TerrainDemo::resetPhysics(void)
|
|
{
|
|
// remove old heightfield
|
|
clearWorld();
|
|
|
|
// reset gravity to point in appropriate direction
|
|
m_dynamicsWorld->setGravity(getUpVector(m_upAxis, 0.0, -s_gravity));
|
|
|
|
// get new heightfield of appropriate type
|
|
m_rawHeightfieldData =
|
|
getRawHeightfieldData(m_model, m_type, m_minHeight, m_maxHeight);
|
|
btAssert(m_rawHeightfieldData && "failed to create raw heightfield");
|
|
|
|
bool flipQuadEdges = false;
|
|
btHeightfieldTerrainShape * heightfieldShape =
|
|
new btHeightfieldTerrainShape(s_gridSize, s_gridSize,
|
|
m_rawHeightfieldData,
|
|
s_gridHeightScale,
|
|
m_minHeight, m_maxHeight,
|
|
m_upAxis, m_type, flipQuadEdges);
|
|
btAssert(heightfieldShape && "null heightfield");
|
|
|
|
// scale the shape
|
|
btVector3 localScaling = getUpVector(m_upAxis, s_gridSpacing, 1.0);
|
|
heightfieldShape->setLocalScaling(localScaling);
|
|
|
|
// stash this shape away
|
|
m_collisionShapes.push_back(heightfieldShape);
|
|
|
|
// set origin to middle of heightfield
|
|
btTransform tr;
|
|
tr.setIdentity();
|
|
tr.setOrigin(btVector3(0,-20,0));
|
|
|
|
// create ground object
|
|
float mass = 0.0;
|
|
localCreateRigidBody(mass, tr, heightfieldShape);
|
|
}
|
|
|
|
|
|
/// removes all objects and shapes from the world
|
|
void TerrainDemo::clearWorld(void)
|
|
{
|
|
//remove the rigidbodies from the dynamics world and delete them
|
|
int i;
|
|
for (i=m_dynamicsWorld->getNumCollisionObjects()-1; i>=0 ;i--)
|
|
{
|
|
btCollisionObject* obj = m_dynamicsWorld->getCollisionObjectArray()[i];
|
|
btRigidBody* body = btRigidBody::upcast(obj);
|
|
if (body && body->getMotionState())
|
|
{
|
|
delete body->getMotionState();
|
|
}
|
|
m_dynamicsWorld->removeCollisionObject( obj );
|
|
delete obj;
|
|
}
|
|
|
|
//delete collision shapes
|
|
for (int j=0;j<m_collisionShapes.size();j++)
|
|
{
|
|
btCollisionShape* shape = m_collisionShapes[j];
|
|
delete shape;
|
|
}
|
|
m_collisionShapes.clear();
|
|
|
|
// delete raw heightfield data
|
|
delete m_rawHeightfieldData;
|
|
m_rawHeightfieldData = NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// TerrainDemo -- public API (exposed in header)
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/// creates an object that demonstrates terrain
|
|
GlutDemoApplication * btCreateTerrainDemo(void)
|
|
{
|
|
TerrainDemo * demo = new TerrainDemo;
|
|
btAssert(demo && "out of memory");
|
|
|
|
demo->initialize();
|
|
|
|
return demo;
|
|
}
|
|
|