#include "CooneyAnimatedMesh.h"
#include "CooneyMath.h"
using namespace Cooney;
using namespace Cooney::Math;
#include <iostream>
#include <fstream>
using namespace std;
namespace Cooney
{
AnimatedMesh::AnimatedMesh()
{
timeRate = 1.0f;
bLoop = true;
bPlaying = false;
currentTime = 0.0f;
// since the mesh is constantly updating(animating) we need it to stream to openGL.
interpolatedMesh.SetGLStreaming(true, true, true, false, false, false, false);
}
AnimatedMesh::~AnimatedMesh()
{
keys.clear();
for(unsigned int i=0; i<(unsigned int)keymeshes.size(); ++i)
{
delete keymeshes[i];
}
keymeshes.clear();
}
bool AnimatedMesh::AddKey(float time, const char* fileName)
{
if( !fileName || time < 0.0f )
return false;
Cooney::Mesh* newMesh = new Cooney::Mesh();
if( !newMesh->Load(fileName) )
{
std::cout << "Could not load the keyframe mesh: " << fileName << "\n";
return false;
}
// Some safety, only allow meshes to load in that are the same size as the
// first keyframe added/the size of the interpolated mesh. If it's the
// first keyframe added, we set the interpolatedMesh to be the same size
// as the first key.
if( keys.size() == 0 )
{
interpolatedMesh.Copy(*newMesh);
interpolatedMesh.PopGLData(); // just in case.
}
else if( newMesh->NumVertexes() != interpolatedMesh.NumVertexes()
|| newMesh->NumFaces() != interpolatedMesh.NumFaces()
|| newMesh->NumQuads() != interpolatedMesh.NumQuads() )
{
delete newMesh;
std::cout << "The keyframe mesh was not of proper size: " << fileName << "\n";
return false;
}
// Need to save this mesh for memory cleanup later.
keymeshes.push_back(newMesh);
// Assign the new keyframe.
keys[time] = newMesh;
return true;
}
bool AnimatedMesh::AddKey(float time, Cooney::Mesh* mesh)
{
if( !mesh || time < 0.0f )
return false;
// Some safety, only allow meshes to load in that are the same size as the
// first keyframe added/the size of the interpolated mesh. If it's the
// first keyframe added, we set the interpolatedMesh to be the same size
// as the first key.
if( keys.size() == 0 )
{
interpolatedMesh.Copy(*mesh);
interpolatedMesh.PopGLData(); // Just in case.
}
else if( mesh->NumVertexes() != interpolatedMesh.NumVertexes()
|| mesh->NumFaces() != interpolatedMesh.NumFaces()
|| mesh->NumQuads() != interpolatedMesh.NumQuads() )
{
std::cout << "The keyframe mesh was not of proper size\n";
return false;
}
// Assign the new keyframe.
keys[time] = mesh;
return false;
}
bool AnimatedMesh::DeleteKey(float time)
{
return (keys.erase(time) != 0);
}
bool AnimatedMesh::DeleteKeys(float lowTime, float highTime)
{
// To erase, get an iterator for the start and end.
std::map<float, Cooney::Mesh*>::iterator it = keys.lower_bound(lowTime), itend = it;
if( it == keys.end() )
return false;
// Find the end.
while( itend->first <= highTime )
itend++;
// Erase.
keys.erase(it, itend);
return true;
}
bool AnimatedMesh::SetTime(float time)
{
if( keys.size() == 0 )
{
// No keyframes, can't set the time.
currentTime = 0.0f;
return true;
}
if( keys.size() == 1 )
{
// One keyframe, set the time to that time.
currentTime = keys.begin()->first;
return true;
}
float lowTime=keys.begin()->first, highTime = keys.rbegin()->first;
currentTime = time;
// Loop so that the time winds up in the proper place,
// instead of merely clamping time. This is useful for
// larger timesteps. (larger relative to the length
// of the animation and the timeRate).
while(true)
{
if( currentTime < lowTime )
{
currentTime = highTime + currentTime - lowTime;
continue;
}
if( currentTime > highTime )
{
currentTime = lowTime + currentTime - highTime;
continue;
}
break;
}
return true;
}
bool AnimatedMesh::Play(bool bShouldLoop, float timePlayRate)
{
// Can't play if there are 1 or less keyframes.
if( keys.size() <= 1 )
return false;
bLoop = bShouldLoop;
timeRate = timePlayRate;
if( timeRate < 0.0001f )
timeRate = 0.0001f; // epsilon'ish.
bPlaying = true;
return true;
}
void AnimatedMesh::Stop(bool bReset)
{
bPlaying = false;
if( bReset )
Reset();
}
void AnimatedMesh::Reset()
{
if( keys.size() == 0 )
currentTime = 0.0f;
else if( keys.size() >= 1 )
currentTime = keys.begin()->first;
}
void AnimatedMesh::Update(float timeStep)
{
// Can't actually step if there are one or no keys.
if( keys.size() <= 1 )
return;
if( bPlaying )
{
// scale the timeStep by the timeRate for the modified step size.
float scaledStep = timeStep * timeRate;
// If we aren't looping, we need to clamp the timestep.
if( !bLoop )
{
// If we are about to step over the last (rbegin) keyframe,
// clamp it to the end.
if( currentTime + scaledStep > keys.rbegin()->first )
scaledStep = keys.rbegin()->first - currentTime;
else if( currentTime + scaledStep < keys.begin()->first ) // Reverse check if the timeRate is backwards.
scaledStep = keys.begin()->first - currentTime;
}
// Only update the mesh if we've moved through time.
if( scaledStep != 0.0f )
{
// Step through time.
SetTime(currentTime + scaledStep);
// Update the mesh model. Remember animations are
// mesh interpolations, not skeletal systems.
UpdateMesh();
}
}
}
void AnimatedMesh::UpdateMesh()
{
if( keys.size() == 0 )
return; // no keyframes.
Cooney::Mesh *influenceA=NULL, *influenceB=NULL;
float interpolation = 0.0f;
// Find the two influences for the updated mesh.
// (left and right keyframes).
// If we have only one influence, we just need the one keyframe.
if( keys.size() == 1 )
{
interpolation = 0.0f;
influenceA = keys.begin()->second;
influenceB = 0x0;
}
else
{
// use iterators for finding the left and right keyframe.
std::map<float, Cooney::Mesh*>::iterator ita=keys.begin(), itb=ita;
// move iterator b one step ahead of a.
++itb;
// Loop through until we find what we want.
// Iterator B should be ahead of time, iterator A should
// be behind time.
while(1)
{
// First check to see if itb is already ahead of the current time.
// If so, bail out!
if( itb->first > currentTime )
break;
// Step iterator B before A. It is now
// two steps ahead of A.
++itb;
// Check to see if we went into dead space.
// If we did, stop now. This put's iterator
// A one before the last keyframe.
if( itb == keys.end() )
break;
// We're still safe so iterate A with B.
++ita;
}
// The B iterator may have gone into dead space.
// Validate its values.
itb = ita;
itb++;
// Get the mesh pointers from the mapped
// iterator parameter.
influenceA = ita->second;
influenceB = itb->second;
// Determine the interpolation value.
if( ita->first >= currentTime )
{
// The left keyframe is ahead of the current
// time. We use no interpolation.
interpolation = 0.0f;
}
else if( itb->first <= currentTime )
{
// The right keyframe is before the current
// time. We use no interpolation.
interpolation = 1.0f;
}
else
{
// Calculate the interpolation value as a ratio
// of time.
float lowTime = ita->first, highTime = itb->first;
if( highTime == lowTime )
interpolation = 0.0f; // Bad, just use the first mesh.
else
interpolation = (currentTime - lowTime) / (highTime - lowTime);
}
}
// Deform beween the two meshes into the interpolatedMesh by the interpolation value.
Interp(*influenceA, *influenceB, interpolation, interpolatedMesh);
}
void AnimatedMesh::PushGL(bool bPositions, bool bNormals, bool bTangents, bool bColors, bool bTexCoords)
{
// Delegate rendering to the mesh object.
interpolatedMesh.PushGL(bPositions, bNormals, bTangents, bColors, bTexCoords);
}
bool AnimatedMesh::Load(const char* filePath)
{
ifstream inFile(filePath, ios::in | ios::binary);
if( !inFile.is_open() )
{
std::cout << "Could not open file: " << filePath << "\n";
return false;
}
// Expects CNYAMSH0 as its magic number.
char inIdBuffer[8] = {0};
char identifier[8] = {'C','N','Y','A','M','S','H','0'};
inFile.read((char *)inIdBuffer, 8);
bool bValidFile = false;
if( !memcmp(inIdBuffer, identifier, 8) )
bValidFile = true;
if( !bValidFile )
{
// Invalid file.
std::cout << "File is invalid: " << filePath << "\n";
inFile.close();
return false;
}
// Stream the mesh data from the file stream.
StreamInAnimatedMesh(inFile);
inFile.close();
return true;
}
bool AnimatedMesh::Save(const char* filePath)
{
if( keys.size() < 1 )
return false;
std::ofstream outFile(filePath, ios::out | ios::binary | ios::trunc);
if( !outFile.is_open() )
return false;
// Format is of magic number CNYAMSH0.
char identifier[8] = {'C','N','Y','A','M','S','H','0'};
outFile.write((char *)identifier, 8);
// Stream the mesh data to the file stream.
StreamOutAnimatedMesh(outFile);
outFile.close();
return false;
}
void AnimatedMesh::StreamInAnimatedMesh(std::istream& inStream)
{
// First thing, read the number of keyframes.
unsigned int numKeys = 0;
inStream.read((char*)&numKeys, sizeof(unsigned int));
// Each key is actually just a mesh object.
// Loop through for each keyframe and stream in a mesh.
for(unsigned int i=0; i<numKeys; ++i)
{
// Store the new mesh object for local memory management.
Cooney::Mesh* newMesh = new Cooney::Mesh();
keymeshes.push_back(newMesh);
// Read the time associated with the mesh.
float keyTime=0.0f;
inStream.read((char*)&keyTime, sizeof(float));
// Stream in the mesh using the mesh object's stream operation.
newMesh->StreamInMesh(inStream);
// Add the key to the list.
AddKey(keyTime, newMesh);
}
}
void AnimatedMesh::StreamOutAnimatedMesh(std::ostream& outStream)
{
// Write the number of keyframes to the stream.
unsigned int numKeys = (unsigned int)keys.size();
outStream.write((char*)&numKeys, sizeof(unsigned int));
std::map<float, Cooney::Mesh*>::iterator it = keys.begin();
do
{
// Write the time associated with the keyframe to stream.
outStream.write((char*)&it->first, sizeof(float));
// Write the mesh to the stream with the object's stream opration.
it->second->StreamOutMesh(outStream);
}while( (++it) != keys.end() );
}
void Interp(Cooney::Mesh &meshA, Cooney::Mesh &meshB, float interpolation, Cooney::Mesh &meshOut)
{
float invinterpolation = 1.0f - interpolation;
for(unsigned int i=0; i<meshOut.NumVertexes(); ++i)
{
// Simple linear interpolation.
meshOut.VertPositions()[i] = meshA.VertPositions()[i] * invinterpolation + meshB.VertPositions()[i] * interpolation;
meshOut.VertColors()[i] = meshA.VertColors()[i] * invinterpolation + meshB.VertColors()[i] * interpolation;
meshOut.VertTexCoords()[i] = meshA.VertTexCoords()[i] * invinterpolation + meshB.VertTexCoords()[i] * interpolation;
// Normalize normal/tangent interpolation.
meshOut.VertNormals()[i] = Normalized3(meshA.VertNormals()[i] * invinterpolation + meshB.VertNormals()[i] * interpolation);
meshOut.VertTangents()[i] = Normalized3(meshA.VertTangents()[i] * invinterpolation + meshB.VertTangents()[i] * interpolation);
}
}
};