Original Post
Hi! Now I just finished the base of my first physics engine (2D). I think that I got the physics right (atleast it looks real) but the design seems pretty bad to me. I'm not that good at it and it'd be great if you guys could look it over and give me some advice. As I said, my MAIN interest here is the OO design but if there're any physical mistakes I'd be happy to know:) Ok, first of all the only thing I've implemented so far are box's connected with springs, the springs apply linear force and torque to the boxs, no collision detection so far but I hope to make the code flexable enough so that it will be easy to implement. Here's a short overview of the classes: -State: hold parameters defining the state of a rigid body, like position, rotation, speed... - RigidBody: holds all parameters of a rigid body, an instance of State, the vertices that make it up (handy for collision detection and rendering), and a function that translates vertices from their local space to world space according to the position of the body, and a vector of Forces that act on the body. -Force: holds all parameters of a spring, hold a pointer to the body it's acting on and of the body on the other end, it also has functions that calculate force and torque. -Simulation: gets a vector of bodies and integrates them one step ahead via RK4. Here they are in code: State.h RigidBody.h/.cpp Force.h/cpp Simuation.h/cpp Now to set up a simulation I just have to make a vector of RigidBodies, add forces to them, and then pass them through the Simulation Update. Now in general this design seems weak to me when I try to play around with it and add stuff though I'm not sure exactly why. Here're some things that bother me: 1) the Force class is a little odd, one spring requires two different "Forces" each stored in it's own seperate body, and each with opposite pointers (body A's ancor is body B's main point..). 2) the pointers in general in the Force class are a little annoying to manage, atleast the way that they're implemented now. 3) I don't like how in the Simulation class all the Evauate functions and other functions have to keep on looping through the bodies. It would be better if there was an easy, built-in way to pass bodies that are linked together (such as in rope) together and bodies that are alone, by themselves(and perhaps even euler integrate them instead) 4)I could go on some more about the Force class.... 5)No clear way how to elegantly fit collision detection into all this Well thats all I could come up with now but I'm sure that there's more, as I said, I have a lot to learn about OO design. What would be a better(= more efficiant, managable, stable, elegant..) way to design this? Any way to add to or fix what I've done? Advice for further development? Thanks a lot everyone! [Edited by - daniel_i_l on August 15, 2006 5:30:06 PM]
#ifndef STATE_H
#define STATE_H
#include "Vector2D.h"
struct State{
State(){Position = Vector2D(0,0);
Velocity = Vector2D(0,0);
Mass = 1;
MomentOfInertia = 1;
Rotation = 0;
AngularVelocity = 0;}
State(Vector2D p, Vector2D v, double m, double i)
:Position(p),
Velocity(v),
Mass(m),
MomentOfInertia(i),
Rotation(0),
AngularVelocity(0)
{}
//a vector to store the position
Vector2D Position;
//a scalar to store rotation
double Rotation;
//a vector to store the velocity
Vector2D Velocity;
//a scalar to store anguler velocity
double AngularVelocity;
//mass
double Mass;
double MomentOfInertia;
//derivaitive of position
//Vector2D dPosition;
};
#endif
#ifndef RIGIDBODY
#define RIGIDBODY
#include <vector>
#include "State.h"
#include "Force.h"
#include "Vector2D.h"
using namespace std;
//-------------------Rigid Body class----------------------
// This class holds all the information needed for a rigid
// body, including it's state and the forces acting on it.
// For now it can handle only squares but I hope to upgrade
// it or inherit it in the future.
//---------------------------------------------------------
class RigidBody{
private:
//a list of all the coordinates as they're in world space
vector<Vector2D> m_CornersInWorldSpace;
//a list of all the coordinates as they're in body space
vector<Vector2D> m_CornersInBodySpace;
//the width;
double m_Width;
//the height
double m_Height;
//a vector of all the forces acting on the body;
vector<Force> Forces;
public:
//ctor, overloaded ctor, and dtor.
RigidBody(){}
RigidBody(Vector2D p, Vector2D v, double m, double w, double h);
~RigidBody();
//transforms the corners to world space
void UpdateGeometry();
//transforms a vector of point/one point to world space
void WorldTransform(vector<Vector2D> &points);
void WorldTransform(Vector2D &point);
//the state of the body
State m_MyState;
//returns vector of the corners in world space, used for rendering
vector<Vector2D> GetCorners(){return m_CornersInWorldSpace;}
//adds a force to the list of forces
void AddForce(Force F);
//clears the list
void ClearForces(){Forces.clear();}
//use the forces to get total force and torque
void ApplyForces();
Vector2D TotalForce;
double TotalTorque;
};
#endif
///////////////////////////////.CPP/////////////////////////////////////
RigidBody::RigidBody(Vector2D p, Vector2D v, double m, double w, double h)
{
double MomentOfInertia = (1.0f/12.0f)*m*(w*w + h*h);
m_MyState = State(p, v, m, MomentOfInertia);
m_Width = w;
m_Height = h;
m_CornersInBodySpace.push_back(Vector2D(-w,-h));
m_CornersInBodySpace.push_back(Vector2D( w,-h));
m_CornersInBodySpace.push_back(Vector2D( w, h));
m_CornersInBodySpace.push_back(Vector2D(-w, h));
UpdateGeometry();
}
RigidBody::~RigidBody()
{
}
void RigidBody::AddForce(Force F)
{
F.SetRigidBody(this);
Forces.push_back(F);
}
void RigidBody::ApplyForces()
{
for(unsigned int i=0; i<Forces.size(); ++i) Forces.TranslatePoints();
TotalForce.Zero();
TotalTorque = 0;
Vector2D force;
for(unsigned int i=0;i<Forces.size();++i)
{
force = Forces.CalculateForce();
TotalForce += force;
TotalTorque += Forces.CalculateTorque(force);
}
TotalForce -= m_MyState.Velocity * 2;
TotalForce += Vector2D(0,-10) * m_MyState.Mass;
TotalTorque -= 2*m_MyState.AngularVelocity;
}
void RigidBody::UpdateGeometry()
{
m_CornersInWorldSpace = m_CornersInBodySpace;
WorldTransform(m_CornersInWorldSpace);
}
void RigidBody::WorldTransform(vector<Vector2D> &points)
{
//first calculate world space of point
C2DMatrix matTransform;
//rotate
matTransform.Rotate(m_MyState.Rotation);
//and translate
matTransform.Translate(m_MyState.Position.x, m_MyState.Position.y);
//now transform the ships vertices
matTransform.TransformSPoints(points);
}
void RigidBody::WorldTransform(Vector2D &point)
{
//first calculate world space of point
C2DMatrix matTransform;
//rotate
matTransform.Rotate(m_MyState.Rotation);
//and translate
matTransform.Translate(m_MyState.Position.x, m_MyState.Position.y);
//now transform the ships vertices
vector<Vector2D> points;
points.push_back(point);
matTransform.TransformSPoints(points);
point = points[0];
}
#ifndef FORCE_H
#define FORCE_H
#include <vector>
#include "State.h"
#include "Vector2D.h"
using namespace std;
class RigidBody;
class Force{
public:
Force(){}
Force(Vector2D a, Vector2D b, double k, double d, double l,
RigidBody *r);
Force (const Force &F); //copy ctor
virtual ~Force(){}
void SetRigidBody(RigidBody *R){ OwnerOfPoint=R; }
//local and world coords in the body on the far end of the spring
Vector2D AncorInBodySpace;
Vector2D AncorInWorldSpace;
//local and world coords in the body that the force is on
Vector2D PointInBodySpace;
Vector2D PointInWorldSpace;
double SpringConstant;
double Damping;
double RestLength;
//translate all local coords to world coords
void TranslatePoints();
Vector2D CalculateForce();
double CalculateTorque(const Vector2D &force);
private:
RigidBody *OwnerOfPoint;
RigidBody *OwnerOfAncor;
};
#endif
////////////////////////////////CPP//////////////////////////////
Force::Force(Vector2D a, Vector2D b, double k, double d, double l,
RigidBody *r)
:AncorInBodySpace(a),
PointInBodySpace(b),
SpringConstant(k),
Damping(d),
RestLength(l),
OwnerOfAncor(r)
{}
Force::Force (const Force &F)
{
AncorInBodySpace = F.AncorInBodySpace;
AncorInWorldSpace = F.AncorInWorldSpace;
Damping = F.Damping;
PointInBodySpace = F.PointInBodySpace;
PointInWorldSpace = F.PointInWorldSpace;
RestLength =F.RestLength;
SpringConstant = F.SpringConstant;
//RigidBody *B=new RigidBody;
//B = F.OwnerOfPoint;
OwnerOfPoint = F.OwnerOfPoint;
OwnerOfAncor = F.OwnerOfAncor;
}
Vector2D Force::CalculateForce()
{
const State state = OwnerOfPoint->m_MyState;
double dis = Vec2DDistance(AncorInWorldSpace, PointInWorldSpace);
Vector2D ToSource = AncorInWorldSpace - PointInWorldSpace;
ToSource /= dis;
double SpringForce = SpringConstant*(dis - RestLength);
double DampingForce = Damping*state.Velocity.Dot(ToSource);
Vector2D TotalForce = (SpringForce - DampingForce)*ToSource;
return TotalForce;
}
double Force::CalculateTorque(const Vector2D &force)
{
const State state = OwnerOfPoint->m_MyState;
Vector2D ToPoint = PointInWorldSpace - state.Position;
Vector2D PerpToPoint = ToPoint.Perp();
double DampingTorque = 30*state.AngularVelocity;
double Torque = PerpToPoint.Dot(force) - DampingTorque;
return Torque;
}
void Force::TranslatePoints()
{
PointInWorldSpace = PointInBodySpace;
AncorInWorldSpace = AncorInBodySpace;
OwnerOfPoint->WorldTransform(PointInWorldSpace);
OwnerOfAncor->WorldTransform(AncorInWorldSpace);
}
#ifndef SIMULATION_H
#define SIMULATION_H
#include "Vector2D.h"
#include "State.h"
#include "RigidBody.h"
struct Derivative{
Derivative(){dPosition = Vector2D(0,0);
dRotation = 0;
dVelocity = Vector2D(0,0);
dAngularVelocity = 0;}
//deravitive of position
Vector2D dPosition;
//derivative of rotation
double dRotation;
//deravitive of velocity
Vector2D dVelocity;
//derivative of angular velocity
double dAngularVelocity;
};
class CSimulation{
private:
double dt;
//interpolate between two states according to time
vector<RigidBody> Interpolate(const vector<RigidBody> &previous,
const vector<RigidBody> ¤t, double alpha);
//get acceleration from current state
Vector2D Acceleration(RigidBody &bodies);
//get angular acceleration from state
double AngularAcceleration(RigidBody &bodies);
//overload evaluation
vector<Derivative> Evaluate(vector<RigidBody> &initials);
//evaluate derivative
vector<Derivative> Evaluate(vector<RigidBody> &initials, double dt,
vector<Derivative> &d);
//integrate with RK4
void Integrate(vector<RigidBody> &bodies, double dt);
public:
CSimulation();
//do one iteration
void Update(vector<RigidBody> ¤ts, double DeltaTime);
};
#endif
///////////////////////////////CPP/////////////////////////////////////
CSimulation::CSimulation()
{
dt = 0.1;
}
vector<RigidBody> CSimulation::Interpolate(const vector<RigidBody> &previous,
const vector<RigidBody> ¤t, double alpha)
{
vector<RigidBody> bodies;
for(unsigned int i=0; i<current.size(); ++i)
{
RigidBody body;
body = current;
body.m_MyState.Position = current.m_MyState.Position*alpha +
previous.m_MyState.Position*(1-alpha);
body.m_MyState.Velocity = current.m_MyState.Velocity*alpha + previous.m_MyState.Velocity*(1-alpha);
body.m_MyState.Rotation = current.m_MyState.Rotation*alpha +
previous.m_MyState.Rotation*(1-alpha);
body.m_MyState.AngularVelocity = current
.m_MyState.AngularVelocity*alpha + previous.m_MyState.AngularVelocity*
(1-alpha);
bodies.push_back(body);
}
return bodies;
}
Vector2D CSimulation::Acceleration(RigidBody &body)
{
Vector2D TotalForce = body.TotalForce;
return TotalForce/body.m_MyState.Mass;
}
double CSimulation::AngularAcceleration(RigidBody &body)
{
double Torque = body.TotalTorque;
double AngularAccel = Torque / body.m_MyState.MomentOfInertia;
return AngularAccel;
}
vector<Derivative> CSimulation::Evaluate(vector<RigidBody> &initials)
{
Derivative output;
vector<Derivative> outputs;
for(unsigned int i=0; i<initials.size(); ++i)
{
initials.ApplyForces();
output.dPosition = initials.m_MyState.Velocity;
output.dVelocity = Acceleration(initials);
output.dRotation = initials.m_MyState.AngularVelocity;
output.dAngularVelocity = AngularAcceleration(initials);
outputs.push_back(output);
}
return outputs;
}
vector<Derivative> CSimulation::Evaluate(vector<RigidBody> &initials,
double dt, vector<Derivative> &d)
{
RigidBody B;
vector<Derivative> outputs;
Derivative output;
for(unsigned int i=0; i<initials.size(); ++i)
{
B = initials;
B.m_MyState.Position = initials.m_MyState.Position
+ d.dPosition * dt;
B.m_MyState.Velocity = initials.m_MyState.Velocity
+ d.dVelocity * dt;
B.m_MyState.Rotation = initials.m_MyState.Rotation
+ d.dRotation * dt;
B.m_MyState.AngularVelocity = initials.m_MyState.AngularVelocity
+ d.dAngularVelocity * dt;
B.m_MyState.Mass = initials.m_MyState.Mass;
B.m_MyState.MomentOfInertia = initials.m_MyState.MomentOfInertia;
B.ApplyForces();
output.dPosition = B.m_MyState.Velocity;
output.dVelocity = Acceleration(B);
output.dRotation = B.m_MyState.AngularVelocity;
output.dAngularVelocity = AngularAcceleration(B);
outputs.push_back(output);
}
return outputs;
}
void CSimulation::Integrate(vector<RigidBody> &bodies, double dt)
{
vector<Derivative> a = Evaluate(bodies);
vector<Derivative> b = Evaluate(bodies, dt*0.5, a);
vector<Derivative> c = Evaluate(bodies, dt*0.5, b);
vector<Derivative> d = Evaluate(bodies, dt, c);
//for one body do euler
//Euler
//vector<Derivative> b = a;
//vector<Derivative> c = a;
//vector<Derivative> d = a;
for(unsigned int i=0; i<bodies.size(); ++i)
{
Vector2D dPosition = 1.0f/6.0f * (a.dPosition + 2.0f*
(b.dPosition + c.dPosition) + d.dPosition);
Vector2D dVelocity = 1.0f/6.0f * (a.dVelocity + 2.0f*
(b.dVelocity + c.dVelocity) + d.dVelocity);
double dRotation = 1.0f/6.0f * (a.dRotation + 2.0f*
(b.dRotation + c.dRotation) + d.dRotation);
double dAngularVelocity = 1.0f/6.0f * (a.dAngularVelocity +
2.0f*(b.dAngularVelocity + c.dAngularVelocity)+
d.dAngularVelocity);
bodies.m_MyState.Position = bodies.m_MyState.Position +
dPosition*dt;
bodies.m_MyState.Velocity = bodies.m_MyState.Velocity +
dVelocity*dt;
bodies.m_MyState.Rotation = bodies.m_MyState.Rotation +
dRotation*dt;
bodies.m_MyState.AngularVelocity = bodies
.m_MyState.AngularVelocity + dAngularVelocity*dt;
}
}
void CSimulation::Update(vector<RigidBody> ¤ts, double DeltaTime)
{
double accumulator = 0.0f;
vector<RigidBody> previous;
previous = currents;
if(DeltaTime > 0.25)
DeltaTime = 0.25;
//if(DeltaTime < dt)
//DeltaTime = dt;
accumulator += DeltaTime;
if(DeltaTime>dt)
{
while(accumulator>=dt)
{
accumulator -= dt;
previous = currents;
Integrate(currents, dt);
}
if(accumulator!=0)
currents = Interpolate(previous, currents, accumulator/dt);
}
else
Integrate(currents, DeltaTime);
//RigidBody r;
//if(accumulator!=0)
//currents = Interpolate(previous, currents, accumulator/dt);
// current = r;
}