In my first attempt, I decided to use realistic units of measure. That is, a billiard ball is 0.0572 m (2.25 inches) in diameter. I'm using MKS and I expect/assume that Bullet expects and works well in this standard.
The problem is that using such a small value, the ball either tunnels through a box I have in my scene under it, or gets stuck in the upper face of that box.
I really don't want to have to scale all of my objects up by a factor of 10 or 100. However, it works with a scale of 100. What's up with this?
Code: Select all
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <btBulletDynamicsCommon.h>
#ifdef HAVE_GLDEBUG_DRAWER
#include "GLDebugDrawer.h"
#endif
#include <vector>
using namespace std;
#define PHYSICS_MAX_PROXIES 8192
// Simple initialization for Bullet.
struct Physics
{
typedef std::vector<btCollisionShape*> ShapeVector;
btDiscreteDynamicsWorld *dynamicsWorld;
btCollisionDispatcher *dispatcher;
btDefaultCollisionConfiguration *collisionConfiguration;
btSequentialImpulseConstraintSolver *constraintSolver;
btAxisSweep3 *overlappingPairCache;
ShapeVector collisionShapes;
#ifdef HAVE_GLDEBUG_DRAWER
GLDebugDrawer drawer;
#endif
Physics(const btVector3& worldMin, const btVector3& worldMax);
virtual ~Physics();
};
Physics::Physics(const btVector3& worldMin, const btVector3& worldMax)
{
collisionConfiguration = new btDefaultCollisionConfiguration();
dispatcher = new btCollisionDispatcher(collisionConfiguration);
overlappingPairCache = new btAxisSweep3(worldMin, worldMax,
PHYSICS_MAX_PROXIES);
constraintSolver = new btSequentialImpulseConstraintSolver;
dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, overlappingPairCache,
constraintSolver,
collisionConfiguration);
dynamicsWorld->setGravity(btVector3(0, -9.81, 0));
#ifdef HAVE_GLDEBUG_DRAWER
drawer.setDebugMode(btIDebugDraw::DBG_DrawWireframe|
btIDebugDraw::DBG_DrawAabb|
btIDebugDraw::DBG_DrawFeaturesText|
btIDebugDraw::DBG_DrawContactPoints|
btIDebugDraw::DBG_DrawText);
dynamicsWorld->setDebugDrawer(&drawer);
#endif
}
Physics::~Physics()
{
for (int i = dynamicsWorld->getNumCollisionObjects() - 1; i >= 0; --i)
{
btCollisionObject* obj = dynamicsWorld->getCollisionObjectArray()[i];
btRigidBody* body = btRigidBody::upcast(obj);
if (body && body->getMotionState())
delete body->getMotionState();
dynamicsWorld->removeCollisionObject(obj);
delete obj;
}
for (int j = 0; j < collisionShapes.size(); ++j)
delete collisionShapes[j];
delete dynamicsWorld;
delete constraintSolver;
delete overlappingPairCache;
delete dispatcher;
delete collisionConfiguration;
}
// Constants controlling this demo
// PROBLEM # 1
// ================================================================
// The diameter of a billiards ball is 0.0572 m.
// If I use this number however, the ball either tunnels through the box
// or gets stuck in the top face of the box.
// Thus, I'm forced to change my base unit of measure for lengths
// to the centimeter.
#define USE_METERS
//#define USE_CENTIMETERS
// PROBLEM # 2
// ========================================================
// Here I simulate a spinning ball by applying some torque so that
// the ball spins in place (about our UP vector, the +Y axis).
// However since the collision shape is an idealized sphere, the
// ball does not ever (I stopped waiting) stop spinning since there
// is only 1 contact point between it and the box, and thus not a
// lot of friction. I use angular damping but it seems hard to
// get a *feel* for the proper values. Note that the first problem
// plays directly into this problem... If I use centimeters, then
// I have to tweak the torque + impulse values differently in my
// app; it's unintuituve...
// Comment this out to hide this problem.
#define NO_APPLY_TORQUE
float ballDiameter = 0.0572; // m
#ifdef USE_METERS
float ballRadius = ballDiameter/2.f; // m
float boxExtent = 1; // m
#else // use centimeters:
float ballRadius = (ballDiameter/2.f) * 100; // cm
float boxExtent = 100; // cm
#endif
float ballMass = 0.260; // kg
float boxHalfExtent = boxExtent*0.5;
float boxColor[4] = {0.4,0.4,0.4,1};
float ballColor[4] = {1,0,1,1};
float outlineColor[4] = {1, 1, 1, 0.5};
float lightAmbient[] = {0.5, 0.5, 0.5, 1.0};
float lightDiffuse[] = {1.0, 1.0, 1.0, 1.0};
float lightPosition[] = {0, boxExtent*2, boxExtent*2, 0.0};
float lightShininess = 64.0;
float matAmbient[] = {0.4, 0.4, 0.4, 1.0};
float matDiffuse[] = {0.7, 0.7, 0.7, 1.0};
float matSpecular[] = {1.0, 1.0, 1.0, 1.0};
float eye[3] = {0, 0, boxExtent};
// Physics setup for this demo
struct DemoPhysics : public Physics
{
DemoPhysics();
btRigidBody *boxBody;
btRigidBody *ballBody;
};
DemoPhysics::DemoPhysics()
: Physics(btVector3(-boxExtent*5,-boxExtent*5,-boxExtent*5),
btVector3(+boxExtent*5,+boxExtent*5,+boxExtent*5))
{
// add a box that we refer to as the "box"
btCollisionShape *boxShape = new btBoxShape(btVector3(boxHalfExtent, boxHalfExtent, boxHalfExtent));
collisionShapes.push_back(boxShape);
btTransform boxTransform;
boxTransform.setIdentity();
boxTransform.setOrigin(btVector3(0, -boxHalfExtent, 0)); // Top of box at Y=0
btDefaultMotionState *motionState = new btDefaultMotionState(boxTransform);
btScalar boxMass = 0;
btVector3 localInertia(0,0,0);
btRigidBody::btRigidBodyConstructionInfo info(boxMass, motionState, boxShape, localInertia);
boxBody = new btRigidBody(info);
dynamicsWorld->addRigidBody(boxBody);
// add a ball
btCollisionShape *ballShape = new btSphereShape(ballRadius);
collisionShapes.push_back(ballShape);
btTransform ballTransform;
ballTransform.setIdentity();
ballTransform.setOrigin(btVector3(0, boxExtent * 2, 0)); // Above the box
btDefaultMotionState *ballMotionState = new btDefaultMotionState(ballTransform);
btVector3 ballLocalInertia;
ballShape->calculateLocalInertia(ballMass, ballLocalInertia);
btRigidBody::btRigidBodyConstructionInfo ballBodyInfo(ballMass, ballMotionState, ballShape, ballLocalInertia);
// Thresholds for when the body goes to sleep. The defaults are a little high
// for our application.
ballBodyInfo.m_linearSleepingThreshold = 0.1;
ballBodyInfo.m_angularSleepingThreshold = 0.1;
ballBody = new btRigidBody(ballBodyInfo);
dynamicsWorld->addRigidBody(ballBody);
#ifndef NO_APPLY_TORQUE
ballBody->applyTorque(btVector3(0,45,0));
float linearDamping = 0;
float angularDamping = 0.05;
ballBody->setDamping(linearDamping, angularDamping);
#endif
}
DemoPhysics *physics = new DemoPhysics();
enum { fDrawSolid = 0x1, fDrawOutline = 0x2 };
void drawBoxShapedBody(btRigidBody *body, float size, float color[3], int flags = 0xFFFF)
{
btTransform transform;
body->getMotionState()->getWorldTransform(transform);
float worldMatrix[16];
transform.getOpenGLMatrix(worldMatrix);
glPushMatrix();
glMultMatrixf(worldMatrix);
if (flags & fDrawSolid) {
glEnable(GL_LIGHTING);
glColor4fv(color);
glutSolidCube(size);
}
if (flags & fDrawOutline) {
glDisable(GL_LIGHTING);
glColor4fv(outlineColor);
glutWireCube(size);
}
glPopMatrix();
}
void drawSphereShapedBody(btRigidBody *body, float radius, float color[3], int flags = 0xFFFF)
{
btTransform transform;
body->getMotionState()->getWorldTransform(transform);
float worldMatrix[16];
transform.getOpenGLMatrix(worldMatrix);
glPushMatrix();
glMultMatrixf(worldMatrix);
int slices = 10;
int stacks = 10;
if (flags & fDrawSolid) {
glEnable(GL_LIGHTING);
glColor4fv(color);
glutSolidSphere(radius, slices, stacks);
}
if (flags & fDrawOutline) {
glDisable(GL_LIGHTING);
glColor4fv(outlineColor);
glutWireSphere(radius, slices, stacks);
}
glPopMatrix();
}
static void draw(void)
{
glLoadIdentity();
gluLookAt(eye[0], eye[1], eye[2], 0,0,0, 0,1,0);
glClearColor(0.1, 0.1, 0.1, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
#ifdef HAVE_GLDEBUG_DRAWER
//physics->dynamicsWorld->debugDrawWorld();
#endif
drawBoxShapedBody(physics->boxBody, boxExtent, boxColor);
drawSphereShapedBody(physics->ballBody, ballRadius, ballColor);
glutSwapBuffers();
}
static void timer(int msec)
{
physics->dynamicsWorld->stepSimulation(10);
glutTimerFunc(msec, timer, 5);
glutPostRedisplay();
}
static void keyboard(unsigned char key, int, int)
{
if (key == 27)
{
delete physics;
exit(0);
}
}
int main(int argc, char **argv)
{
float w = 512, h = 384;
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize(w, h);
glutCreateWindow("bullet demo");
glutDisplayFunc(draw);
glutKeyboardFunc(keyboard);
glutTimerFunc(100, timer, 10);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_COLOR_MATERIAL);
glShadeModel(GL_SMOOTH);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, matAmbient);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, matDiffuse);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, matSpecular);
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, lightShininess);
glLightfv(GL_LIGHT0, GL_AMBIENT, lightAmbient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightDiffuse);
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glMatrixMode(GL_PROJECTION);
gluPerspective(45, w/h, 1, 5000.0);
glMatrixMode(GL_MODELVIEW);
glHint(GL_LINE_SMOOTH, GL_NICEST);
glLineWidth(2);
glutMainLoop();
}