I am looking for a library or algorithm that would allow me to add simple 'flight sim' capabilities to rigid bodies in a 2D world.
I've made a game as an art project, where virtual fish swim in a tank of water. I started off using Box2D/liquidfun for particle-based fluid simulation, and it behaved realistically when rigid bodies were dragged through the fluid. However, the computational load was intense, even though it is very well optimized.
Instead, I want to compute the lift and drag forces for each face so that the bodies behave as if they are moving through water, with no particles needed. It would be like the flight models of some flight simulators. I believe X-Plane (https://www.x-plane.com/) works in this way. Some of the answers to other questions on this site suggest modeling the lift and drag of your entire 'airplane' as constants- but they are too simple, because i need to show the physical consequences of the body's geometry, and how it affects movement.
I was not able to find a module that offered this, but I had a go at writing my own. It breaks a polygon down into faces and applies the lift and drag equations to them. ("BoneUserData" is a struct that stores information for a single rigid body. Box2D / C++):
bool chooseDMostVertex(float angle, b2Vec2 p1, b2Vec2 p2) {
// given 2 vertices, identify which one lines most in a given direction. return true if it is p1 and false if it is p2.
// unrotate the set of vertices by the direction, so that the distance to measure is along the Y axis.
// get the centroid, this is your rotation center
b2Vec2 thisFaceCentroid = b2Vec2((p1.x + p2.x)/2,(p1.y+p2.y)/2);
// perform the rotation
b2Vec2 p1r = rotatePoint(thisFaceCentroid.x, thisFaceCentroid.y, angle, p1);
b2Vec2 p2r = rotatePoint(thisFaceCentroid.x, thisFaceCentroid.y, angle, p2);
if (p1r.y > p2r.y) {
return true;
}
else {
return false;
}
}
// provides FEA-based lift and drag calculations
void flightModel(BoneUserData * bone) {
uint nVertices = bone->shape.GetVertexCount();
for (uint j = 0; j < nVertices; ++j)
{
// get the face vertices from the b2 shape
b2Vec2 p1 = bone->shape.GetVertex(j);
b2Vec2 p2 = b2Vec2(0.0f, 0.0f);
// use the pair n and n-1 to make a face. if n is zero, make a face from the first to the last vertices.
if (j == 0) {
p2 = bone->shape.GetVertex(nVertices-1);
}
else {
p2 = bone->shape.GetVertex(j-1);
}
// get the position of the face center point, taking into account the body's present rotation.
b2Vec2 worldCenter = bone->p_body->GetWorldCenter();
b2Vec2 p1r = rotatePoint(0.0f, 0.0f, bone->p_body->GetAngle(), p1 );
p1r += worldCenter;
b2Vec2 p2r = rotatePoint(0.0f, 0.0f, bone->p_body->GetAngle(), p2 );
p2r += worldCenter;
b2Vec2 faceCenter = b2Vec2( (p1r.x + p2r.x)/2, (p1r.y + p2r.y)/2 ) ;
float faceAngle = atan2(p1r.y - p2r.y, p1r.x - p2r.x);
// calculate the angle of incidence into the oncoming 'wind'
b2Vec2 linearVelocity = bone->p_body->GetLinearVelocity();// must get the linear velocity of the face center, not just of the body itself.
// you can calculate the face center velocity, by taking the radius and multiplying it by the angular velocity.
b2Vec2 localFaceCenter = b2Vec2( (p1.x + p2.x)/2, (p1.y + p2.y)/2 ) ;
float magLinearVelocityOfRotatingFace = (bone->p_body->GetAngularVelocity() * magnitude( localFaceCenter)); // https://courses.lumenlearning.com/boundless-physics/chapter/velocity-acceleration-and-force/
b2Vec2 faceAngularVelocity = b2Vec2( cos(faceAngle) * magLinearVelocityOfRotatingFace, sin(faceAngle) * magLinearVelocityOfRotatingFace);
b2Vec2 totalVelocity = b2Vec2(linearVelocity.x + faceAngularVelocity.x, linearVelocity.y + faceAngularVelocity.y);
float magnitudeVelocity = magnitude(totalVelocity);
b2Vec2 distanceBetweenPoints = b2Vec2(p2r.x - p1r.x, p2r.y - p1r.y);
float magnitudeArea = magnitude(distanceBetweenPoints);
float angleOfForwardDirection = atan2(linearVelocity.x, linearVelocity.y) - 0.5 * pi;
// calculate the force of drag
float dragCoefficient = 0.002;
float dragForce = magnitudeVelocity * magnitudeArea * dragCoefficient * -1; // the -1 in this statement is what makes it an opposing force.
b2Vec2 dragVector = b2Vec2( cos(angleOfForwardDirection) * dragForce , sin(angleOfForwardDirection) * dragForce *-1);
// calculate the force of lift
float liftCoeff = 0.5;
float atmosphericDensity = 1;
float liftForce = liftCoeff * ((atmosphericDensity * (magnitudeVelocity*magnitudeVelocity))/2) * magnitudeArea * -1;
// the lift angle is normal to the face but its direction is determined by how the plane meets the incoming wind.
float liftAngle = faceAngle + 0.5*pi;
if (chooseDMostVertex(angleOfForwardDirection, p1r, p2r)) {
liftForce = liftForce * -1;
}
b2Vec2 fluidDynamicForce = b2Vec2(cos(liftAngle ) * liftForce, sin(liftAngle ) * liftForce );
// apply the force to the body directly in the center of the face.
bone->p_body->ApplyForce(dragVector, faceCenter, true);
if ( fluidDynamicForce.x < 1000 && fluidDynamicForce.y < 1000
&& fluidDynamicForce.x > -1000 && fluidDynamicForce.y > -1000 ) {
bone->p_body->ApplyForce(fluidDynamicForce, faceCenter, true);
}
}
}
However, it suffers from physical bugs. I understand that the way I am modelling drag is incorrect, that it should depend on the area presented to the wind, and not the area overall. But there are other problems such as near-stationary objects randomly jumping, rotating symmetrical objects do not slow down, and some rotating objects will speed up exponentially. I don't understand how these issues are being introduced by my algorithm. If I could get rid of them, I would be happy enough to keep using this model.