//////////////////////////////////////////////////////////////////////////////
/// \file SoftBody.h
///
/// Declares:
/// SpringPointState
/// SpringPoint
/// SpringEdge
/// SpringCornerBrace
/// SoftBody
///
/// Stephen Timothy Cooney, 2009
//////////////////////////////////////////////////////////////////////////////
#ifndef COONEYSOFTBODY_H
#define COONEYSOFTBODY_H
#include <iostream> // file io
#include <vector> // springs container
#include "CooneyMath.h"
using namespace Cooney::Math;
// Some basic physics sytem settings.
// TODO: In a more robust system, move this into a dynamic object.
#define COONEY_PHYSICS_GRAVITY 9.81
#define COONEY_PHYSICS_WIND 0, 0, 0
// Apple has a different std directory for GLUT
// than linux and windows.
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
namespace Cooney
{
/// Represents the dynamic state of a point in the
/// spring lattice.
struct SpringPointState
{
Cooney::Math::Vec3 pointVelocity;
Cooney::Math::Vec3 pointPosition;
};
/// Represents static information of a point in the
/// spring lattice.
struct SpringPoint
{
/// The mass of the spring point effects the relationship
/// with points connected via edges.
float mass;
/// Describes the base-state before simulation.
SpringPointState initialState;
/// If the spring point is locked it will not move under simulation.
/// However, the spring point will influence points connected
/// via edges.
bool bLocked;
/// A index into the array of dynamic states in the soft body.
/// This SpringPoint is not directly updated. It's related state
/// is updated.
int stateIndex;
SpringPoint(){mass=1.0f;stateIndex = -1;bLocked=false;}
/// Determine whether or not the stateIndex references a state that
/// exists inside a states array. Fundamentally, this is just doing
/// bounds checking.
inline bool VerifyIntegrity(std::vector<SpringPointState> *states=0)
{
if( states == 0 )
return false;
if( stateIndex < 0 || stateIndex >= (int)states->size() )
return false;
return true;
}
};
/// A SpringEdge represents the relatively constant information
/// about point connectivity and edge stiffness.
struct SpringEdge
{
/// The length at which the edge is at rest.
/// This is automatically calculated in the Freeze()
/// function call in the SoftBody class.
float restlength;
/// Represents the tension and desire for the
/// edge to return to it's rest length.
/// This is automatically calculated in the Freeze()
/// function call in the SoftBody class. This is
/// calculated by defining a materialStiffness that
/// represents a relationship between the tension of
/// the edge as a division of the length of the edge.
/// (Short edges of a material are considered to be
/// stronger than long edges of a material).
float stiffness;
/// The rate at which a spring edge's velocity
/// counter-acts itself. This dissipates energy
/// over time and assists edges in returning to
/// a rest-state.
float damping;
/// An index into the array of points (in SoftBody)
/// that represents one side of the edge.
int pointAIndex;
/// An index into the array of points (in SoftBody)
/// that represents one side of the edge.
int pointBIndex;
/// For rendering purposes, hidden edges aren't shown.
/// For example: in the fat simulation demo, only square
/// framework edges are shown, not interconnection edges.
/// This keeps the lattice information understandable.
bool bHidden;
SpringEdge(){pointAIndex=pointBIndex=-1;restlength=0.0f;stiffness=100.0f;damping=1.0f;bHidden=false;}
/// Initializes the edge by telling the edge that the
/// current state is the rest state. This looks at its
/// point connections and determines the restLength and
/// stiffness.
/// Note: Damping will not be changed, change it elsewhere
void Freeze(std::vector<SpringPoint> *points, std::vector<SpringPointState> *states, float materialStiffness=100.0f);
/// Calculate forces to the edge points based on their current state.
void CalculateForces(Cooney::Math::Vec3 &outForceA, Cooney::Math::Vec3 &outForceB,
Cooney::Math::Vec3 &outDampingA, Cooney::Math::Vec3 &outDampingB,
std::vector<SpringPoint> *points, std::vector<SpringPointState> *states);
/// Check to make sure the spromgu references valid points in the point/state list.
/// Returns true if valid, false if invalid.
/// Inline for run-time boost. Right now the SoftBody implementation uses
/// this check often.
inline bool VerifyIntegrity(std::vector<SpringPoint> *points, std::vector<SpringPointState> *states)
{
if( points == 0 || states == 0 )
return false;
// check to make sure the point indexes are
// in good bounds
if( pointAIndex < 0 || pointBIndex < 0 ||
pointAIndex >= (int)points->size() ||
pointBIndex >= (int)points->size() )
return false;
// check to make sure the point indexes's state
// indexes are in good bounds
if( (*points)[pointAIndex].stateIndex < 0 ||
(*points)[pointBIndex].stateIndex < 0 ||
(*points)[pointAIndex].stateIndex >= (int)states->size() ||
(*points)[pointBIndex].stateIndex >= (int)states->size() )
return false;
return true;
}
};
/// A corner brace connects two edges together by a spring
/// that attempts to keep them at the same angle.
struct SpringCornerBrace
{
/// The angle at which the two edges normally rest.
/// The brace attempts to get the edges to rotate toward
/// this angle. Automatically set in the Freeze() call.
float restAngle;
/// Represents the tension and desire of the edges
/// to return to the original restAngle.
float stiffness;
/// In order to dissipate energy, damping works by pushing
/// against the two edges relative angular velocity.
float damping;
// Root, B and C rotate around A as base off hinge
/// A is the corner of the two edges.
int pointAIndex;
/// B represents the first edge.
int pointBIndex;
/// C represents the second edge.
int pointCIndex;
SpringCornerBrace(){restAngle=0.0f;stiffness=100.0f;damping=1.0f;pointAIndex=pointBIndex=pointCIndex=-1;}
/// Freezes the current state of the edge brace as the rest state.
/// This looks at the points that make up the brace and calculates
/// their current angle.
void Freeze(std::vector<SpringPoint> *points, std::vector<SpringPointState> *states);
/// Calculate forces to the edge points based on their current angular state.
void CalculateForces(Cooney::Math::Vec3 &outForceA, Cooney::Math::Vec3 &outForceB, Cooney::Math::Vec3 &outForceC,
Cooney::Math::Vec3 &outDampingA, Cooney::Math::Vec3 &outDampingB, Cooney::Math::Vec3 &outDampingC,
std::vector<SpringPoint> *points, std::vector<SpringPointState> *states);
/// Check to make sure the brace references valid points in the point/state list.
/// Returns true if valid, false if invalid.
/// Inline for run-time boost. Right now the SoftBody implementation uses
/// this check often.
inline bool VerifyIntegrity(std::vector<SpringPoint> *points, std::vector<SpringPointState> *states)
{
if( points == 0 || states == 0 )
return false;
// check to make sure the point indexes are
// in good bounds
if( pointAIndex < 0 || pointBIndex < 0 || pointCIndex < 0 ||
pointAIndex >= (int)points->size() ||
pointBIndex >= (int)points->size() ||
pointCIndex >= (int)points->size() )
return false;
// check to make sure the point indexes's state
// indexes are in good bounds
if( (*points)[pointAIndex].stateIndex < 0 ||
(*points)[pointBIndex].stateIndex < 0 ||
(*points)[pointCIndex].stateIndex < 0 ||
(*points)[pointAIndex].stateIndex >= (int)states->size() ||
(*points)[pointBIndex].stateIndex >= (int)states->size() ||
(*points)[pointCIndex].stateIndex >= (int)states->size() )
return false;
return true;
}
};
/// The soft-body mesh is the collection of spring edges and corner
/// braces that represent a soft-body simulation.
class SoftBody
{
/// When a freeze operation is run on the simulation,
/// the material stiffness is used to automatically
/// calculate tension based on edge lengths.
float materialStiffness;
/// A collection of states that represent the current
/// updated set of states.
std::vector<SpringPointState> currentStates;
/// A collection of points that make up the grid.
/// edges and braces reference this list.
std::vector<SpringPoint> points;
/// The edges list represents all direct point
/// to point springs that attempt to keep the
/// edge's length at the rest length.
std::vector<SpringEdge> edges;
/// A collection of corner braces that attempt
/// to keep the angle between two edges at a
/// rest angle.
std::vector<SpringCornerBrace> braces;
public:
SoftBody()
{
materialStiffness = 1.0f;
}
void Clear()
{
currentStates.clear();
points.clear();
edges.clear();
braces.clear();
}
/// Return the number of points in the simulation.
/// This number is pulled from the states array, not the
/// points array. There is a possibility that if no
/// simulation has been run, this number would be innacurate.
unsigned int NumPoints(){return currentStates.size();}
/// Return the position of a point state at an index.
Cooney::Math::Vec3 GetPoint(unsigned int index){return currentStates[index].pointPosition;}
/// Set the position of a point state at an index.
void SetPoint(unsigned int index, Cooney::Math::Vec3 point){currentStates[index].pointPosition = point;}
/// Locks a point at an index and keeps it from moving under
/// simulation. It does influence neighbors, however.
void SetLocked(unsigned int index){points[index].bLocked=true;}
/// Unlocks a point at an index and allows it to move under
/// simulation. If it was previously unlocked, nothing happens.
void SetUnLocked(unsigned int index){points[index].bLocked=false;}
/// Adds a springy quad to the soft-body mesh simulation. This quad is
/// represented by four springy edges and 4 corner braces. It will
/// automatically check and link to duplicate points, edges and braces.
/// The output represents the index of the points created or linked to.
void AddQuad(Cooney::Math::Vec3 pointA, Cooney::Math::Vec3 pointB, Cooney::Math::Vec3 pointC, Cooney::Math::Vec3 pointD,
int *indexAOut=0, int *indexBOut=0, int *indexCOut=0, int *indexDOut=0);
/// Adds a springy triangle to the soft-body mesh simulation. This triangle
/// is represented by three springy edges and 3 corner braces. It will
/// automatically check and link to duplicate points, edges and braces.
/// The output represents the index of the points created or linked to.
void AddTriangle(Cooney::Math::Vec3 pointA, Cooney::Math::Vec3 pointB, Cooney::Math::Vec3 pointC,
int *indexAOut=0, int *indexBOut=0, int *indexCOut=0);
/// Adds a edge to the soft-body mesh via point-positions.
/// This will automatically check for duplicate points and edges.
/// The parameter bHidden indicates whether or not the edge will
/// render if the SoftBody is rendered directly.
/// It's important to note that, if your mesh previously relied on corner braces, this will
/// not automatically add braces where corners exist.
void AddEdge(Cooney::Math::Vec3 pointA, Cooney::Math::Vec3 pointB, bool bHidden=false);
/// Adds a edge to the soft-body mesh via point-indexes.
/// This will automatically check for duplicate points and edges.
/// The parameter bHidden indicates whether or not the edge will
/// render if the SoftBody is rendered directly.
/// It's important to note that, if your mesh previously relied on corner braces, this will
/// not automatically add braces where corners exist.
void AddEdge(const unsigned int pointAIndex, const unsigned int pointBIndex, bool bHidden=false);
/// Utility function that connects EVERY point to EVERY other point.
void InterConnect()
{
for(unsigned int i=0; i<(unsigned int)points.size(); ++i)
for(unsigned int j=0; j<(unsigned int)points.size(); ++j)
AddEdge(i, j, true);
}
/// Constructs and initiates a grid of divisionsX by divisionsY
/// by divisionsZ dimensions with a size of scale.
/// Structurally, the grid contains numerous sub-cell-blocks that
/// are cubes connected by 12 edges with optional corner braces
/// and optional interconnected cell-edges to strengthen sub-cell
/// rigidity.
/// This can be used to create a soft-body lattice deformer for
/// complex visual objects.
///
/// stiffness and damping represent edge properties.
/// cornerstiffness and cornerdamping represent brace properties.
/// The optional transformation matrix allows the grid to be
/// constructed off of the origin.
///
/// Returns true on success.
bool ConstructSpringyGrid(unsigned int divisionsX=5, unsigned int divisionsY=5, unsigned int divisionsZ=5, float scale=1.0f,
float stiffness=100.0f, float damping=1.0f, float cornerstiffness=50.0f, float cornerdamping=1.0f,
Cooney::Math::Mat4 TransformationMat = Cooney::Math::Mat4(), bool bInterConnect=false);
/// Constructs and Initiates a soft-body cube that structurally
/// consists of 12 edges and corner braces.
/// An optional transformation matrix can be provided to transform
/// the points off of the origin.
bool ConstructSpringyCube(float scale=1.0f, float stiffness=100.0f, float damping=1.0f,
float cornerstiffness=100.0f, float cornerdamping=1.0f,
Cooney::Math::Mat4 TransformationMat = Cooney::Math::Mat4());
/// Constructs and Initiates a soft-body cube that is
/// structurally similar to the ConstructSpringyCube()
/// except that instead of quads, the entire mesh is
/// built out of triangles.
/// An optional transformation matrix can be provided to transform
/// the points off of the origin.
bool ConstructSpringyTriCube(float scale=1.0f, float stiffness=100.0f, float damping=1.0f,
float cornerstiffness=100.0f, float cornerdamping=1.0f,
Cooney::Math::Mat4 TransformationMat = Cooney::Math::Mat4());
/// Utility to quickly overwrite/recalculate all
/// of the soft-body settings.
void FloodSettings(float stiffness, float damping, float cornerStiffness, float cornerDamping);
/// Prepare the SoftBody for simulation by filling
/// the dynamic state-array with initial parameters
/// and Freeze()'ing all of the dynamic spring systems
/// which communicates that the current state is their
/// rest-state.
void Initiate();
/// Add a velocity to the entire soft-body mesh.
void AddVelocity(Cooney::Math::Vec3 velocity);
/// Based on the current state of the SoftBody, calclate
/// all of the forces based on edge-tension/damping and
/// corner-brace-tension/damping.
/// Returns an array of all the forces.
/// TODO: If implementing into a serious application, alter
/// this to take an out-array for the forces. A new std
/// vector, possible multiple times per frame(RK4) is not
/// the most efficient.
std::vector<Cooney::Math::Vec3> CalculateForces(std::vector<SpringPointState> *states, float timeStep);
/// Take a substep which is basically an euler step to
/// determine where the point state will be after a certain
/// time period based on forces/velocity.
/// See Step() for it's usage in Euler/RK2/RK4.
void SubStep(std::vector<SpringPointState> *thisstates, std::vector<SpringPointState> *nextstates, std::vector<Cooney::Math::Vec3> *forces, float timeStep);
/// Take a full time-step integrating with either
/// Euler, RK2 or RK4.
void Step(float timeStep);
/// Push the spring system onto OpenGL to be rendered.
/// Edges marked with bHidden will not be pushed to
/// OpenGL.
void PushGL();
};
}
#endif