// Traer Physics 3.0 // Terms from Traer's download page, http://traer.cc/mainsite/physics/ // LICENSE - Use this code for whatever you want, just send me a link jeff@traer.cc // // traer3.pde // From traer.physics // Attraction Particle // EulerIntegrator ParticleSystem // Force RungeKuttaIntegrator // Integrator Spring // ModifiedEulerIntegrator Vector3D // // From traer.animator // Smoother // Smoother3D // Tickable // //=========================================================================================== // Attraction //=========================================================================================== // attract positive repel negative //package traer.physics; public class Attraction implements Force { Particle one; Particle b; float k; boolean on = true; float distanceMin; float distanceMinSquared; public Attraction( Particle a, Particle b, float k, float distanceMin ) { this.one = a; this.b = b; this.k = k; this.distanceMin = distanceMin; this.distanceMinSquared = distanceMin*distanceMin; } protected void setA( Particle p ) { one = p; } protected void setB( Particle p ) { b = p; } public final float getMinimumDistance() { return distanceMin; } public final void setMinimumDistance( float d ) { distanceMin = d; distanceMinSquared = d*d; } public final void turnOff() { on = false; } public final void turnOn() { on = true; } public final void setStrength( float k ) { this.k = k; } public final Particle getOneEnd() { return one; } public final Particle getTheOtherEnd() { return b; } public void apply() { if ( on && ( one.isFree() || b.isFree() ) ) { PVector a2b = PVector.sub(one.position, b.position, new PVector()); float a2bDistanceSquared = a2b.dot(a2b); if ( a2bDistanceSquared < distanceMinSquared ) a2bDistanceSquared = distanceMinSquared; float force = k * one.mass0 * b.mass0 / (a2bDistanceSquared * (float)Math.sqrt(a2bDistanceSquared)); a2b.mult( force ); // apply if ( b.isFree() ) b.force.add( a2b ); if ( one.isFree() ) { a2b.mult(-1f); one.force.add( a2b ); } } } public final float getStrength() { return k; } public final boolean isOn() { return on; } public final boolean isOff() { return !on; } } // Attraction //=========================================================================================== // UniversalAttraction //=========================================================================================== // attract positive repel negative public class UniversalAttraction implements Force { public UniversalAttraction( float k, float distanceMin, ArrayList targetList ) { this.k = k; this.distanceMin = distanceMin; this.distanceMinSquared = distanceMin*distanceMin; this.targetList = targetList; } float k; boolean on = true; float distanceMin; float distanceMinSquared; ArrayList targetList; public final float getMinimumDistance() { return distanceMin; } public final void setMinimumDistance( float d ) { distanceMin = d; distanceMinSquared = d*d; } public final void turnOff() { on = false; } public final void turnOn() { on = true; } public final void setStrength( float k ) { this.k = k; } public final float getStrength() { return k; } public final boolean isOn() { return on; } public final boolean isOff() { return !on; } public void apply() { if ( on ) { for (int i=0; i < targetList.size(); i++ ) { for (int j=i+1; j < targetList.size(); j++) { Particle a = (Particle)targetList.get(i); Particle b = (Particle)targetList.get(j); if ( a.isFree() || b.isFree() ) { PVector a2b = PVector.sub(a.position, b.position, new PVector()); float a2bDistanceSquared = a2b.dot(a2b); if ( a2bDistanceSquared < distanceMinSquared ) a2bDistanceSquared = distanceMinSquared; float force = k * a.mass0 * b.mass0 / (a2bDistanceSquared * (float)Math.sqrt(a2bDistanceSquared)); a2b.mult( force ); if ( b.isFree() ) b.force.add( a2b ); if ( a.isFree() ) { a2b.mult(-1f); a.force.add( a2b ); } } } } } } } //UniversalAttraction //=========================================================================================== // Pulse //=========================================================================================== public class Pulse implements Force { public Pulse( float k, float distanceMin, PVector origin, float lifetime, ArrayList targetList ) { this.k = k; this.distanceMin = distanceMin; this.distanceMinSquared = distanceMin*distanceMin; this.origin = origin; this.targetList = targetList; this.lifetime = lifetime; } float k; boolean on = true; float distanceMin; float distanceMinSquared; float lifetime; PVector origin; ArrayList targetList; public final void turnOff() { on = false; } public final void turnOn() { on = true; } public final boolean isOn() { return on; } public final boolean isOff() { return !on; } public final boolean tick( float time ) { lifetime-=time; if (lifetime <= 0f) turnOff(); return on; } public void apply() { if (on) { PVector holder = new PVector(); int count = 0; for (Iterator i = targetList.iterator(); i.hasNext(); ) { Particle p = (Particle)i.next(); if ( p.isFree() ) { holder.set( p.position.x, p.position.y, p.position.z ); holder.sub( origin ); float distanceSquared = holder.dot(holder); if (distanceSquared < distanceMinSquared) distanceSquared = distanceMinSquared; holder.mult(k / (distanceSquared * (float)Math.sqrt(distanceSquared)) ); p.force.add( holder ); } } } } }//Pulse //=========================================================================================== // EulerIntegrator //=========================================================================================== //package traer.physics; public class EulerIntegrator implements Integrator { ParticleSystem s; public EulerIntegrator( ParticleSystem s ) { this.s = s; } public void step( float t ) { s.clearForces(); s.applyForces(); for ( Iterator i = s.particles.iterator(); i.hasNext(); ) { Particle p = (Particle)i.next(); if ( p.isFree() ) { p.velocity.add( PVector.mult(p.force, t/p.mass0) ); p.position.add( PVector.mult(p.velocity, t) ); } } } } // EulerIntegrator //=========================================================================================== // Force //=========================================================================================== // May 29, 2005 //package traer.physics; // @author jeffrey traer bernstein public interface Force { public void turnOn(); public void turnOff(); public boolean isOn(); public boolean isOff(); public void apply(); } // Force //=========================================================================================== // Integrator //=========================================================================================== //package traer.physics; public interface Integrator { public void step( float t ); } // Integrator //=========================================================================================== // ModifiedEulerIntegrator //=========================================================================================== //package traer.physics; public class ModifiedEulerIntegrator implements Integrator { ParticleSystem s; public ModifiedEulerIntegrator( ParticleSystem s ) { this.s = s; } public void step( float t ) { s.clearForces(); s.applyForces(); float halft = 0.5f*t; PVector a = new PVector(); PVector holder = new PVector(); for ( int i = 0; i < s.numberOfParticles(); i++ ) { Particle p = s.getParticle( i ); if ( p.isFree() ) { PVector.div(p.force, p.mass0, a); p.position.add( PVector.mult(p.velocity, t, holder) ); p.velocity.add( PVector.mult(a, t, a) ); p.position.add( PVector.mult(a, halft, a) ); } } } } // ModifiedEulerIntegrator //=========================================================================================== // Particle //=========================================================================================== //package traer.physics; public class Particle { PVector position = new PVector(); PVector velocity = new PVector(); PVector force = new PVector(); protected float mass0; protected float age0 = 0; protected boolean dead0 = false; boolean fixed0 = false; public Particle( float m ) { mass0 = m; } // @see traer.physics.AbstractParticle#distanceTo(traer.physics.Particle) public final float distanceTo( Particle p ) { return this.position.dist( p.position ); } // @see traer.physics.AbstractParticle#makeFixed() public final Particle makeFixed() { fixed0 = true; velocity.set(0f,0f,0f); force.set(0f, 0f, 0f); return this; } // @see traer.physics.AbstractParticle#makeFree() public final Particle makeFree() { fixed0 = false; return this; } // @see traer.physics.AbstractParticle#isFixed() public final boolean isFixed() { return fixed0; } // @see traer.physics.AbstractParticle#isFree() public final boolean isFree() { return !fixed0; } // @see traer.physics.AbstractParticle#mass() public final float mass() { return mass0; } // @see traer.physics.AbstractParticle#setMass(float) public final void setMass( float m ) { mass0 = m; } // @see traer.physics.AbstractParticle#age() public final float age() { return age0; } protected void reset() { age0 = 0; dead0 = false; position.set(0f,0f,0f); velocity.set(0f,0f,0f); force.set(0f,0f,0f); mass0 = 1f; } } // Particle //=========================================================================================== // ParticleSystem //=========================================================================================== // May 29, 2005 //package traer.physics; //import java.util.*; public class ParticleSystem { public static final int RUNGE_KUTTA = 0; public static final int MODIFIED_EULER = 1; protected static final float DEFAULT_GRAVITY = 0; protected static final float DEFAULT_DRAG = 0.001f; ArrayList particles = new ArrayList(); ArrayList springs = new ArrayList(); ArrayList attractions = new ArrayList(); ArrayList customForces = new ArrayList(); ArrayList pulses = new ArrayList(); Integrator integrator; PVector gravity = new PVector(); float drag; boolean hasDeadParticles = false; public final void setIntegrator( int which ) { switch ( which ) { case RUNGE_KUTTA: this.integrator = new RungeKuttaIntegrator( this ); break; case MODIFIED_EULER: this.integrator = new ModifiedEulerIntegrator( this ); break; } } public final void setGravity( float x, float y, float z ) { gravity.set( x, y, z ); } // default down gravity public final void setGravity( float g ) { gravity.set( 0, g, 0 ); } public final void setDrag( float d ) { drag = d; } public final void tick() { tick( 1 ); } public final void tick( float t ) { integrator.step( t ); for (int i = 0; i originalPositions.size() ) { originalPositions.add( new PVector() ); originalVelocities.add( new PVector() ); k1Forces.add( new PVector() ); k1Velocities.add( new PVector() ); k2Forces.add( new PVector() ); k2Velocities.add( new PVector() ); k3Forces.add( new PVector() ); k3Velocities.add( new PVector() ); k4Forces.add( new PVector() ); k4Velocities.add( new PVector() ); } } private final void setIntermediate(ArrayList forces, ArrayList velocities) { s.applyForces(); for ( int i = 0; i < s.particles.size(); ++i ) { Particle p = (Particle)s.particles.get( i ); if ( p.isFree() ) { ((PVector)forces.get( i )).set( p.force.x, p.force.y, p.force.z ); ((PVector)velocities.get( i )).set( p.velocity.x, p.velocity.y, p.velocity.z ); p.force.set(0f,0f,0f); } } } private final void updateIntermediate(ArrayList forces, ArrayList velocities, float multiplier) { PVector holder = new PVector(); for ( int i = 0; i < s.particles.size(); ++i ) { Particle p = (Particle)s.particles.get( i ); if ( p.isFree() ) { PVector op = (PVector)(originalPositions.get( i )); p.position.set(op.x, op.y, op.z); p.position.add(PVector.mult((PVector)(velocities.get( i )), multiplier, holder)); PVector ov = (PVector)(originalVelocities.get( i )); p.velocity.set(ov.x, ov.y, ov.z); p.velocity.add(PVector.mult((PVector)(forces.get( i )), multiplier/p.mass0, holder)); } } } private final void initialize() { for ( int i = 0; i < s.particles.size(); ++i ) { Particle p = (Particle)(s.particles.get( i )); if ( p.isFree() ) { ((PVector)(originalPositions.get( i ))).set( p.position.x, p.position.y, p.position.z ); ((PVector)(originalVelocities.get( i ))).set( p.velocity.x, p.velocity.y, p.velocity.z ); } p.force.set(0f,0f,0f); // and clear the forces } } public final void step( float deltaT ) { allocateParticles(); initialize(); setIntermediate(k1Forces, k1Velocities); updateIntermediate(k1Forces, k1Velocities, 0.5f*deltaT ); setIntermediate(k2Forces, k2Velocities); updateIntermediate(k2Forces, k2Velocities, 0.5f*deltaT ); setIntermediate(k3Forces, k3Velocities); updateIntermediate(k3Forces, k3Velocities, deltaT ); setIntermediate(k4Forces, k4Velocities); ///////////////////////////////////////////////////////////// // put them all together and what do you get? for ( int i = 0; i < s.particles.size(); ++i ) { Particle p = (Particle)s.particles.get( i ); p.age0 += deltaT; if ( p.isFree() ) { // update position PVector holder = (PVector)(k2Velocities.get( i )); holder.add((PVector)k3Velocities.get( i )); holder.mult(2.0f); holder.add((PVector)k1Velocities.get( i )); holder.add((PVector)k4Velocities.get( i )); holder.mult(deltaT / 6.0f); holder.add((PVector)originalPositions.get( i )); p.position.set(holder.x, holder.y, holder.z); // update velocity holder = (PVector)k2Forces.get( i ); holder.add((PVector)k3Forces.get( i )); holder.mult(2.0f); holder.add((PVector)k1Forces.get( i )); holder.add((PVector)k4Forces.get( i )); holder.mult(deltaT / (6.0f * p.mass0 )); holder.add((PVector)originalVelocities.get( i )); p.velocity.set(holder.x, holder.y, holder.z); } } } } // RungeKuttaIntegrator //=========================================================================================== // Spring //=========================================================================================== // May 29, 2005 //package traer.physics; // @author jeffrey traer bernstein public class Spring implements Force { float springConstant0; float damping0; float restLength0; Particle one, b; boolean on = true; public Spring( Particle A, Particle B, float ks, float d, float r ) { springConstant0 = ks; damping0 = d; restLength0 = r; one = A; b = B; } public final void turnOff() { on = false; } public final void turnOn() { on = true; } public final boolean isOn() { return on; } public final boolean isOff() { return !on; } public final Particle getOneEnd() { return one; } public final Particle getTheOtherEnd() { return b; } public final float currentLength() { return one.distanceTo( b ); } public final float restLength() { return restLength0; } public final float strength() { return springConstant0; } public final void setStrength( float ks ) { springConstant0 = ks; } public final float damping() { return damping0; } public final void setDamping( float d ) { damping0 = d; } public final void setRestLength( float l ) { restLength0 = l; } public final void apply() { if ( on && ( one.isFree() || b.isFree() ) ) { PVector a2b = PVector.sub(one.position, b.position, new PVector()); float a2bDistance = a2b.mag(); if (a2bDistance!=0f) { a2b.div(a2bDistance); } // spring force is proportional to how much it stretched float springForce = -( a2bDistance - restLength0 ) * springConstant0; PVector vDamping = PVector.sub(one.velocity, b.velocity, new PVector()); float dampingForce = -damping0 * a2b.dot(vDamping); // forceB is same as forceA in opposite direction float r = springForce + dampingForce; a2b.mult(r); if ( one.isFree() ) one.force.add( a2b ); if ( b.isFree() ) b.force.add( PVector.mult(a2b, -1, a2b) ); } } protected void setA( Particle p ) { one = p; } protected void setB( Particle p ) { b = p; } } // Spring //=========================================================================================== // Smoother //=========================================================================================== //package traer.animator; public class Smoother implements Tickable { public Smoother(float smoothness) { setSmoothness(smoothness); setValue(0.0F); } public Smoother(float smoothness, float start) { setSmoothness(smoothness); setValue(start); } public final void setSmoothness(float smoothness) { a = -smoothness; gain = 1.0F + a; } public final void setTarget(float target) { input = target; } public void setValue(float x) { input = x; lastOutput = x; } public final float getTarget() { return input; } public final void tick() { lastOutput = gain * input - a * lastOutput; } public final float getValue() { return lastOutput; } public float a, gain, lastOutput, input; } // Smoother //=========================================================================================== // Smoother3D //=========================================================================================== //package traer.animator; public class Smoother3D implements Tickable { public Smoother3D(float smoothness) { x0 = new Smoother(smoothness); y0 = new Smoother(smoothness); z0 = new Smoother(smoothness); } public Smoother3D(float initialX, float initialY, float initialZ, float smoothness) { x0 = new Smoother(smoothness, initialX); y0 = new Smoother(smoothness, initialY); z0 = new Smoother(smoothness, initialZ); } public final void setXTarget(float X) { x0.setTarget(X); } public final void setYTarget(float X) { y0.setTarget(X); } public final void setZTarget(float X) { z0.setTarget(X); } public final float getXTarget() { return x0.getTarget(); } public final float getYTarget() { return y0.getTarget(); } public final float getZTarget() { return z0.getTarget(); } public final void setTarget(float X, float Y, float Z) { x0.setTarget(X); y0.setTarget(Y); z0.setTarget(Z); } public final void setValue(float X, float Y, float Z) { x0.setValue(X); y0.setValue(Y); z0.setValue(Z); } public final void setX(float X) { x0.setValue(X); } public final void setY(float Y) { y0.setValue(Y); } public final void setZ(float Z) { z0.setValue(Z); } public final void setSmoothness(float smoothness) { x0.setSmoothness(smoothness); y0.setSmoothness(smoothness); z0.setSmoothness(smoothness); } public final void tick() { x0.tick(); y0.tick(); z0.tick(); } public final float x() { return x0.getValue(); } public final float y() { return y0.getValue(); } public final float z() { return z0.getValue(); } public Smoother x0, y0, z0; } // Smoother3D //=========================================================================================== // Tickable //=========================================================================================== //package traer.animator; public interface Tickable { public abstract void tick(); public abstract void setSmoothness(float f); } // Tickable