ofxMSAPhysics - C++ 3D physics library for openFrameworks
ofxMSAPhysics is a C++ 3D particle/constraint based physics library for openFrameworks. It uses a very similar api to the traer.physics library for processing to make getting into it as easy as possible.
Version 2.0a is now available for testing.
Main features include
- particles
- springs
- attractions (+ve or -ve)
- collision
- replay saving and load from disk (temporarily disabled in current alpha release)
- custom particles (extend ofxMSAParticle and add to the system)
- custom constraints (extend ofxMSAConstraint and add to the system)
- custom force fields (extend ofxMSAParticleUpdater and add to the system)
- custom drawing (extend ofxMSAParticleDrawer and add to the system)
Made with openFrameworks.
ofxMSAPhysics v2.0a requires the latest version of openFrameworks as of writing (rev240 on the OF SVN) and is aimed at OF 006 when it is released. It also requires my ofxObjCPointer v1.1+ addon which makes memory management easier. It does not require ofxVectorMath anymore as the required functionality is now in the new OF core. You can download ofxMSAPhysics from here.
List of new features (from changelog):
- a lot of new files in this one, so you'll need to remove and re-add it to your project
- no longer requires ofxVectorMath
- attractors fully implemented
- collision (between particles) fully implemented
- you can enable/disable collision per particle, and it will collide with all other particles which have collisin enabled
- AND/OR you can globally enable or disable collision
- AND/OR you can manually create a collision constraint between any 2 (or more) specific particles
- you can set world dimenensions for optimized collision (using Zach L.'s binning code) and particle world edge collision
- particles have individual drag
- particles have individual bounce (for collision)
- particles have individual size (for collision and rendering)
- all 'setter' methods return the instance so you can chain them (e.g. myParticle->setMass(1)->setBonuce(0.5)->enableCollision()->makeFree(); )
- replay functionality temporarily disabled while I fix stuff
- intense testing of memory management so should be stable as a rock, lemme know if you see anything weird (turn verbose on to see whats going on)
- using the super fast inverse square root approximation (attributed to john carmack but originally from Silicon Graphics)
- lots of internal restructing and optimization
ofxMSAPhysics uses verlet integration and principles found in this article at gamasutra and binning code for collision optimization from Zach Lieberman.
Full application code for the above demo:
#include "testApp.h" #include "ofxMSAPhysics.h" #define SPRING_MIN_STRENGTH 0.005 #define SPRING_MAX_STRENGTH 0.1 #define SPRING_MIN_WIDTH 1 #define SPRING_MAX_WIDTH 3 #define NODE_MIN_RADIUS 10 #define NODE_MAX_RADIUS 30 #define MIN_MASS 1 #define MAX_MASS 3 #define MIN_BOUNCE 0.2 #define MAX_BOUNCE 0.9 #define FIX_PROBABILITY 10 // % probability of a particle being fixed on creation #define FORCE_AMOUNT 10 #define EDGE_DRAG 0.98 #define GRAVITY 1 #define MAX_ATTRACTION 10 #define MIN_ATTRACTION 3 bool mouseAttract = false; bool doMouseXY = false; // pressing left mmouse button moves mouse in XY plane bool doMouseYZ = false; // pressing right mouse button moves mouse in YZ plane int forceTimer = false; float rotSpeed = 0; float mouseMass = 1; static int width; static int height; ofxMSAPhysics physics; ofxMSAParticle mouseNode; void initScene() { // clear all particles and springs etc physics.clear(); // you can add your own particles to the physics system physics.addParticle(&mouseNode); mouseNode.makeFixed(); mouseNode.setMass(MIN_MASS); mouseNode.moveTo(0, 0, 0); mouseNode.setRadius(NODE_MAX_RADIUS); mouseNode.enableCollision(); // or tell the system to create and add particles physics.makeParticle(-width/4, 0, -width/4, MIN_MASS)->makeFixed(); // create a node in top left back and fix physics.makeParticle( width/4, 0, -width/4, MIN_MASS)->makeFixed(); // create a node in top right back and fix physics.makeParticle(-width/4, 0, width/4, MIN_MASS)->makeFixed(); // create a node in top left front and fix physics.makeParticle( width/4, 0, width/4, MIN_MASS)->makeFixed(); // create a node in top right front and fix } //-------------------------------------------------------------- void testApp::setup(){ ofBackground(255, 255, 255); ofSetVerticalSync(true); ofSetFrameRate(60); width = ofGetWidth(); height = ofGetHeight(); // physics.verbose = true; // dump activity to log physics.setGravity(0, GRAVITY, 0); // set world dimensions, not essential, but speeds up collision physics.setWorldSize(ofPoint(-width/2, -height, -width/2), ofPoint(width/2, height, width/2)); physics.setDrag(0.97f); initScene(); // setup lighting GLfloat mat_shininess[] = { 50.0 }; GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat light_position[] = { 0, height/2, 0.0, 0.0 }; glShadeModel(GL_SMOOTH); glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, mat_shininess); glLightfv(GL_LIGHT0, GL_POSITION, light_position); glEnable(GL_COLOR_MATERIAL); glEnable(GL_LIGHT0); // enable back-face culling (so we can see through the walls) glCullFace(GL_BACK); glEnable(GL_CULL_FACE); } void addRandomParticle() { float posX = ofRandom(-width/2, width/2); float posY = ofRandom(0, height); float posZ = ofRandom(-width/2, width/2); float mass = ofRandom(MIN_MASS, MAX_MASS); float bounce = ofRandom(MIN_BOUNCE, MAX_BOUNCE); float radius = ofMap(mass, MIN_MASS, MAX_MASS, NODE_MIN_RADIUS, NODE_MAX_RADIUS); // physics.makeParticle returns a particle pointer so you can customize it ofxMSAParticle* p = physics.makeParticle(posX, posY, posZ); // and set a bunch of properties (you don't have to set all of them, there are defaults) p->setMass(mass)->setBounce(bounce)->setRadius(radius)->enableCollision()->makeFree(); // add an attraction to the mouseNode if(mouseAttract) physics.makeAttraction(&mouseNode, p, ofRandom(MIN_ATTRACTION, MAX_ATTRACTION), 0); } void addRandomSpring() { ofxMSAParticle *a = physics.getParticle((int)ofRandom(0, physics.numberOfParticles())); ofxMSAParticle *b = physics.getParticle((int)ofRandom(0, physics.numberOfParticles())); physics.makeSpring(a, b, ofRandom(SPRING_MIN_STRENGTH, SPRING_MAX_STRENGTH), ofRandom(10, width/2)); } void killRandomParticle() { ofxMSAParticle *p = physics.getParticle(floor(ofRandom(0, physics.numberOfParticles()))); if(p && p != &mouseNode) p->kill(); } void killRandomSpring() { ofxMSASpring *s = physics.getSpring( floor(ofRandom(0, physics.numberOfSprings()))); if(s) s->kill(); } void killRandomConstraint() { ofxMSAConstraint *c = physics.getConstraint(floor(ofRandom(0, physics.numberOfConstraints()))); if(c) c->kill(); } void toggleMouseAttract() { mouseAttract = !mouseAttract; if(mouseAttract) { // loop through all particles and add attraction to mouse // (doesn't matter if we attach attraction from mouse-mouse cos it won't be added internally for(int i=0; i<physics.numberOfParticles(); i++) physics.makeAttraction(&mouseNode, physics.getParticle(i), ofRandom(MIN_ATTRACTION, MAX_ATTRACTION), 0); } else { // loop through all existing attractsions and delete them for(int i=0; i<physics.numberOfAttractions(); i++) physics.getAttraction(i)->kill(); } } void addRandomForce(float f) { forceTimer = f; for(int i=0; i<physics.numberOfParticles(); i++) { ofxMSAParticle *p = physics.getParticle(i); if(p->isFree()) p->addVelocity(ofRandom(-f, f), ofRandom(-f, f), ofRandom(-f, f)); } } void lockRandomParticles() { for(int i=0; i<physics.numberOfParticles(); i++) { ofxMSAParticle *p = physics.getParticle(i); if(ofRandom(0, 100) < FIX_PROBABILITY) p->makeFixed(); else p->makeFree(); } mouseNode.makeFixed(); } void unlockRandomParticles() { for(int i=0; i<physics.numberOfParticles(); i++) { ofxMSAParticle *p = physics.getParticle(i); p->makeFree(); } mouseNode.makeFixed(); } //-------------------------------------------------------------- void testApp::update() { width = ofGetWidth(); height = ofGetHeight(); physics.update(); } //-------------------------------------------------------------- void testApp::draw() { ofEnableAlphaBlending(); glEnable(GL_DEPTH_TEST); glPushMatrix(); glTranslatef(width/2, 0, -width / 3); // center scene static float rot = 0; glRotatef(rot, 0, 1, 0); // rotate scene rot += rotSpeed; // slowly increase rotation (to get a good 3D view) if(forceTimer) { float translateMax = forceTimer; glTranslatef(ofRandom(-translateMax, translateMax), ofRandom(-translateMax, translateMax), ofRandom(-translateMax, translateMax)); forceTimer--; } glDisable(GL_LIGHTING); glBegin(GL_QUADS); // draw right wall glColor3f(.9, 0.9, 0.9); glVertex3f(width/2, height+1, width/2); glColor3f(1, 1, 1); glVertex3f(width/2, -height, width/2); glColor3f(0.95, 0.95, 0.95); glVertex3f(width/2, -height, -width/2); glColor3f(.85, 0.85, 0.85); glVertex3f(width/2, height+1, -width/2); // back wall glColor3f(.9, 0.9, 0.9); glVertex3f(width/2, height+1, -width/2); glColor3f(1, 1, 1); glVertex3f(width/2, -height, -width/2); glColor3f(0.95, 0.95, 0.95); glVertex3f(-width/2, -height, -width/2); glColor3f(.85, 0.85, 0.85); glVertex3f(-width/2, height+1, -width/2); // left wall glColor3f(.9, 0.9, 0.9); glVertex3f(-width/2, height+1, -width/2); glColor3f(1, 1, 1); glVertex3f(-width/2, -height, -width/2); glColor3f(0.95, 0.95, 0.95); glVertex3f(-width/2, -height, width/2); glColor3f(.85, 0.85, 0.85); glVertex3f(-width/2, height+1, width/2); // front wall glColor3f(0.95, 0.95, 0.95); glVertex3f(width/2, -height, width/2); glColor3f(.85, 0.85, 0.85); glVertex3f(width/2, height+1, width/2); glColor3f(.9, 0.9, 0.9); glVertex3f(-width/2, height+1, width/2); glColor3f(1, 1, 1); glVertex3f(-width/2, -height, width/2); // floor glColor3f(.8, 0.8, 0.8); glVertex3f(width/2, height+1, width/2); glVertex3f(width/2, height+1, -width/2); glVertex3f(-width/2, height+1, -width/2); glVertex3f(-width/2, height+1, width/2); glEnd(); glEnable(GL_LIGHTING); // draw springs glColor4f(0.5, 0.5, 0.5, 0.5); for(int i=0; i<physics.numberOfSprings(); i++) { ofxMSASpring *spring = (ofxMSASpring *) physics.getSpring(i); ofxMSAParticle *a = spring->getOneEnd(); ofxMSAParticle *b = spring->getTheOtherEnd(); ofPoint vec = (*b - *a); float dist = msaLength(vec); float angle = acos( vec.z / dist ) * RAD_TO_DEG; if(vec.z <= 0 ) angle = -angle; float rx = -vec.y * vec.z; float ry = vec.x * vec.z; glPushMatrix(); glTranslatef(a->x, a->y, a->z); glRotatef(angle, rx, ry, 0.0); float size = ofMap(spring->strength, SPRING_MIN_STRENGTH, SPRING_MAX_STRENGTH, SPRING_MIN_WIDTH, SPRING_MAX_WIDTH); glScalef(size, size, dist); glTranslatef(0, 0, 0.5); glutSolidCube(1); glPopMatrix(); } // draw particles for(int i=0; i<physics.numberOfParticles(); i++) { ofxMSAParticle *p = physics.getParticle(i); if(p->isFixed()) glColor4f(1, 0, 0, 1); else glColor4f(1, 1, 1, 1); // draw ball glPushMatrix(); glTranslatef(p->x, p->y, p->z); glutSolidSphere(p->getRadius(), 15, 15); glPopMatrix(); // draw shadow float alpha = ofMap(p->y, -height, height, 0, 1); if(alpha>0) { glPushMatrix(); glTranslatef(p->x, height, p->z); glRotatef(-90, 1, 0, 0); glColor4f(0, 0, 0, alpha * alpha * alpha * alpha); ofCircle(0, 0, p->getRadius()); glPopMatrix(); } } glPopMatrix(); glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glColor4f(0, 0, 0, 1); ofDrawBitmapString( " FPS: " + ofToString(ofGetFrameRate(), 2) + " | Number of particles: " + ofToString(physics.numberOfParticles(), 2) + " | Number of springs: " + ofToString(physics.numberOfSprings(), 2) + " | Mouse Mass: " + ofToString(mouseNode.getMass(), 2) , 20, 15); } //-------------------------------------------------------------- void testApp::keyPressed (int key){ switch(key) { case 'a': toggleMouseAttract(); break; case 'p': addRandomParticle(); break; case 'P': killRandomParticle(); break; case 's': addRandomSpring(); break; case 'S': killRandomSpring(); break; case 'c': physics.isCollisionEnabled() ? physics.disableCollision() : physics.enableCollision(); break; case 'C': killRandomConstraint(); break; case 'f': addRandomForce(FORCE_AMOUNT); break; case 'F': addRandomForce(FORCE_AMOUNT * 3); break; case 'l': lockRandomParticles(); break; case 'u': unlockRandomParticles(); break; case ' ': initScene(); break; case 'x': doMouseXY = true; break; case 'z': doMouseYZ = true; break; case ']': rotSpeed += 0.01f; break; case '[': rotSpeed -= 0.01f; break; case '+': mouseNode.setMass(mouseNode.getMass() +0.1); break; case '-': mouseNode.setMass(mouseNode.getMass() -0.1); break; case 'm': mouseNode.hasCollision() ? mouseNode.disableCollision() : mouseNode.enableCollision(); } } //-------------------------------------------------------------- void testApp::keyReleased (int key){ switch(key) { case 'x': doMouseXY = false; break; case 'z': doMouseYZ = false; break; } } //-------------------------------------------------------------- void testApp::mouseMoved(int x, int y ) { static int oldMouseX = -10000; static int oldMouseY = -10000; int velX = x - oldMouseX; int velY = y - oldMouseY; if(doMouseXY) mouseNode.moveBy(velX, velY, 0); if(doMouseYZ) mouseNode.moveBy(velX, 0, velY); oldMouseX = x; oldMouseY = y; } //-------------------------------------------------------------- void testApp::mouseDragged(int x, int y, int button){ switch(button) { case 0: doMouseXY = true; mouseMoved(x, y); break; case 2: doMouseYZ = true; mouseMoved(x, y); break; } } //-------------------------------------------------------------- void testApp::mousePressed(int x, int y, int button){ } //-------------------------------------------------------------- void testApp::mouseReleased(){ doMouseXY = doMouseYZ = false; }
Delicious
Digg
StumbleUpon





