Skip to content

Instantly share code, notes, and snippets.

@samfromcadott
Created June 11, 2023 16:10
Show Gist options
  • Save samfromcadott/58a59967cde3f51a51265def0ffd837a to your computer and use it in GitHub Desktop.
Save samfromcadott/58a59967cde3f51a51265def0ffd837a to your computer and use it in GitHub Desktop.
Example of using Bullet Physics with raylib
// raylib+Bullet physics
// Sam Jackson
// Partially based off this: https://github.com/bulletphysics/bullet3/blob/master/examples/HelloWorld/HelloWorld.cpp
#include <vector>
#include <btBulletDynamicsCommon.h>
#include <raylib.h>
btDefaultCollisionConfiguration* collision_configuration;
btCollisionDispatcher* dispatcher; // Collision dispatcher, handles collision
btBroadphaseInterface* overlapping_pair_cache; // Broadphase interface, detects overlapping objects
btSequentialImpulseConstraintSolver* solver; // Constraint solver
btDiscreteDynamicsWorld* dynamics_world; // The world where physics takes place
btAlignedObjectArray<btCollisionShape*> collision_shapes; // Keeps track of collision shapes
// Collider shape
enum Shape {
CUBE,
SPHERE,
};
enum PhysicsType {
STATIC,
DYNAMIC,
};
class PhysicsObject {
private:
btRigidBody* body;
btCollisionShape* collider_shape;
Model model;
public:
Color color;
PhysicsObject(
Vector3 position = {0,0,0},
Vector3 rotation = {0,0,0},
Vector3 size = {1,1,1},
Shape shape = CUBE,
PhysicsType type = STATIC,
float mass = 0,
Color color = WHITE
) {
this->color = color;
if (shape == CUBE) {
collider_shape = new btBoxShape(
btVector3( btScalar(size.x/2.0), btScalar(size.y/2.0), btScalar(size.z/2.0) )
);
model = LoadModelFromMesh( GenMeshCube(size.x, size.y, size.z) );
} else if (shape == SPHERE) {
collider_shape = new btSphereShape( btScalar(size.x) );
model = LoadModelFromMesh( GenMeshSphere(size.x, 8, 16) );
}
collision_shapes.push_back(collider_shape);
// Set location and rotation
btTransform transform;
transform.setIdentity();
transform.setOrigin( btVector3(position.x, position.y, position.z) );
transform.setRotation(
btQuaternion( btScalar(rotation.z), btScalar(rotation.y), btScalar(rotation.x) )
);
btScalar object_mass(mass); // Set the object's mass
// Calculate local inertia for dynamic objects
btVector3 local_inertia(0, 0, 0);
if (type == DYNAMIC || mass != 0.0) // Objects with 0.0 mass are static
collider_shape->calculateLocalInertia(mass, local_inertia);
btDefaultMotionState* motion_state = new btDefaultMotionState(transform);
btRigidBody::btRigidBodyConstructionInfo rb_info(object_mass, motion_state, collider_shape, local_inertia);
body = new btRigidBody(rb_info);
dynamics_world->addRigidBody(body); // Add the body to the dynamics world
}
void render() {
const float radian_scale = 57.296;
btTransform trans;
// Get the transform of the body
if (body && body->getMotionState())
body->getMotionState()->getWorldTransform(trans);
else
return;
// Get position from transform
Vector3 position = {
float(trans.getOrigin().getX()),
float(trans.getOrigin().getY()),
float(trans.getOrigin().getZ())
};
// Get rotation from transform
btQuaternion quat = trans.getRotation();
Vector3 axis = {
float(quat.getAxis().getX()),
float(quat.getAxis().getY()),
float(quat.getAxis().getZ())
};
float angle = float( quat.getAngle() ) * radian_scale; // Convert radians to degrees
// Render model
DrawModelEx(model, position, axis, angle, {1,1,1}, color);
DrawModelWiresEx(
model, position, axis, angle, {1,1,1},
{(unsigned char)(color.r/2), (unsigned char)(color.g/2), (unsigned char)(color.b/2), color.a}
);
}
void unload() {
UnloadModel(model);
}
};
int main(int argc, char** argv) {
const int screen_width = 800;
const int screen_height = 450;
const int FPS = 60;
SetConfigFlags(FLAG_MSAA_4X_HINT); // Enable anti-aliasing
InitWindow(screen_width, screen_height, "raylib + Bullet");
// Set up the camera
Camera3D camera = { 0 };
camera.position = (Vector3){ 10.0f, 5.0f, 10.0f };
camera.target = (Vector3){ 0.0f, 0.0f, 0.0f };
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
camera.fovy = 60.0f;
camera.projection = CAMERA_PERSPECTIVE;
SetTargetFPS(FPS);
// Initialize Bullet
collision_configuration = new btDefaultCollisionConfiguration();
dispatcher = new btCollisionDispatcher(collision_configuration);
overlapping_pair_cache = new btDbvtBroadphase();
solver = new btSequentialImpulseConstraintSolver;
dynamics_world = new btDiscreteDynamicsWorld(dispatcher, overlapping_pair_cache, solver, collision_configuration);
dynamics_world->setGravity(btVector3(0, -10, 0));
// Create the physics bodies
std::vector<PhysicsObject> physics_objects;
physics_objects = {
PhysicsObject( {0,0,0}, {0,0,0}, {8,0.5,8}, CUBE, STATIC, 0.0, GRAY ) // Floor
};
// Create a group of random dynamic objects
for (size_t i = 0; i < 30; i++) {
const std::vector<Color> colors {
MAROON, ORANGE, DARKGREEN, DARKBLUE, DARKPURPLE,
RED, GOLD, LIME, BLUE, VIOLET, PINK, YELLOW,
GREEN, SKYBLUE, PURPLE
};
physics_objects.push_back(
PhysicsObject(
{(float)GetRandomValue(-5, 5), (float)GetRandomValue(10, 15), (float)GetRandomValue(-5, 5)},
{(float)GetRandomValue(-3, 3), (float)GetRandomValue(-3, 3), (float)GetRandomValue(-3, 3)},
{(float)GetRandomValue(1, 3), (float)GetRandomValue(1, 3), (float)GetRandomValue(1, 3)},
(Shape)GetRandomValue(0, 1),
DYNAMIC,
1.0,
colors[ GetRandomValue(0, colors.size() - 1) ]
)
);
}
while ( !WindowShouldClose() ) {
UpdateCamera(&camera, CAMERA_ORBITAL);
dynamics_world->stepSimulation(1.0 / float(FPS), 10); // Update physics
BeginDrawing();
ClearBackground(RAYWHITE);
BeginMode3D(camera);
for (auto& object : physics_objects) {
object.render();
}
DrawGrid(10, 1.0);
EndMode3D();
DrawFPS(16, 16);
EndDrawing();
}
// Cleanup
// Remove rigid bodies
for (int i = dynamics_world->getNumCollisionObjects() - 1; i >= 0; i--) {
btCollisionObject* obj = dynamics_world->getCollisionObjectArray()[i];
btRigidBody* body = btRigidBody::upcast(obj);
if (body && body->getMotionState()) {
delete body->getMotionState();
}
dynamics_world->removeCollisionObject(obj);
delete obj;
}
// Delete collision shapes
for (int i = 0; i < collision_shapes.size(); i++) {
btCollisionShape* shape = collision_shapes[i];
collision_shapes[i] = 0;
delete shape;
}
// Unload physics objects
for (auto& object : physics_objects) {
object.unload();
}
delete dynamics_world;
delete solver;
delete overlapping_pair_cache;
delete dispatcher;
delete collision_configuration;
CloseWindow();
}
@redbullmarky
Copy link

This has been a super helpful “Hello, World!” to get me up and running. Not easy when you’re completely new to Bullet. Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment