//////////////////////////////////////////////////////////////////////////////
/// \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