1
0
forked from 0ad/0ad
0ad/source/graphics/Model.cpp

524 lines
15 KiB
C++
Raw Normal View History

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Name: Model.cpp
// Author: Rich Cross
// Contact: rich@wildfiregames.com
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include "precompiled.h"
#include "Model.h"
#include "ModelDef.h"
#include "maths/Quaternion.h"
#include "maths/Bound.h"
#include "SkeletonAnim.h"
#include "SkeletonAnimDef.h"
#include "SkeletonAnimManager.h"
#include "MeshManager.h"
#include "ObjectEntry.h"
#include "lib/res/graphics/ogl_tex.h"
#include "lib/res/h_mgr.h"
#include "ps/Profile.h"
#include "ps/CLogger.h"
#define LOG_CATEGORY "graphics"
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Constructor
CModel::CModel()
: m_Parent(NULL), m_Flags(0), m_Anim(NULL), m_AnimTime(0),
m_BoneMatrices(NULL), m_InverseBindBoneMatrices(NULL),
m_PositionValid(false), m_ShadingColor(1,1,1,1)
{
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Destructor
CModel::~CModel()
{
// Detach us from our parent
if (m_Parent)
{
for(std::vector<Prop>::iterator iter = m_Parent->m_Props.begin();
iter != m_Parent->m_Props.end();
++iter)
{
if (iter->m_Model == this)
{
m_Parent->m_Props.erase(iter);
break;
}
}
m_Parent = 0;
}
ReleaseData();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ReleaseData: delete anything allocated by the model
void CModel::ReleaseData()
{
delete[] m_BoneMatrices;
delete[] m_InverseBindBoneMatrices;
for (size_t i = 0; i < m_Props.size(); ++i)
{
m_Props[i].m_Model->m_Parent = 0;
delete m_Props[i].m_Model;
}
m_Props.clear();
m_pModelDef = CModelDefPtr();
Handle h = m_Texture.GetHandle();
ogl_tex_free(h);
m_Texture.SetHandle(0);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// InitModel: setup model from given geometry
bool CModel::InitModel(CModelDefPtr modeldef)
{
// clean up any existing data first
ReleaseData();
m_pModelDef = modeldef;
size_t numBones = modeldef->GetNumBones();
if (numBones != 0)
{
// allocate matrices for bone transformations
m_BoneMatrices = new CMatrix3D[numBones];
m_InverseBindBoneMatrices = new CMatrix3D[numBones];
// store default pose until animation assigned
CBoneState* defpose = modeldef->GetBones();
for (size_t i = 0; i < numBones; ++i)
{
m_BoneMatrices[i].SetIdentity();
m_BoneMatrices[i].Rotate(defpose[i].m_Rotation);
m_BoneMatrices[i].Translate(defpose[i].m_Translation);
m_InverseBindBoneMatrices[i].SetIdentity();
m_InverseBindBoneMatrices[i].Translate(-defpose[i].m_Translation);
m_InverseBindBoneMatrices[i].Rotate(defpose[i].m_Rotation.GetInverse());
}
}
m_PositionValid = true;
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcBound: calculate the world space bounds of this model
void CModel::CalcBounds()
{
// Need to calculate the object bounds first, if that hasn't already been done
if (! (m_Anim && m_Anim->m_AnimDef))
CalcObjectBounds();
else
{
if (m_Anim->m_ObjectBounds.IsEmpty())
CalcAnimatedObjectBound(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds);
debug_assert(! m_Anim->m_ObjectBounds.IsEmpty()); // (if this happens, it'll be recalculating the bounds every time)
m_ObjectBounds = m_Anim->m_ObjectBounds;
}
m_ObjectBounds.Transform(GetTransform(),m_Bounds);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcObjectBounds: calculate object space bounds of this model, based solely on vertex positions
void CModel::CalcObjectBounds()
{
m_ObjectBounds.SetEmpty();
size_t numverts=m_pModelDef->GetNumVertices();
SModelVertex* verts=m_pModelDef->GetVertices();
for (size_t i=0;i<numverts;i++) {
m_ObjectBounds+=verts[i].m_Coords;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcAnimatedObjectBound: calculate bounds encompassing all vertex positions for given animation
void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result)
{
result.SetEmpty();
// Set the current animation on which to perform calculations (if it's necessary)
if (anim != m_Anim->m_AnimDef)
{
CSkeletonAnim dummyanim;
dummyanim.m_AnimDef=anim;
if (!SetAnimation(&dummyanim)) return;
}
size_t numverts=m_pModelDef->GetNumVertices();
SModelVertex* verts=m_pModelDef->GetVertices();
// Remove any transformations, so that we calculate the bounding box
// at the origin. The box is later re-transformed onto the object, without
// having to recalculate the size of the box.
CMatrix3D transform, oldtransform = GetTransform();
CModel* oldparent = m_Parent;
m_Parent = 0;
transform.SetIdentity();
CRenderableObject::SetTransform(transform);
// Following seems to stomp over the current animation time - which, unsurprisingly,
// introduces artefacts in the currently playing animation. Save it here and restore it
// at the end.
float AnimTime = m_AnimTime;
// iterate through every frame of the animation
for (size_t j=0;j<anim->GetNumFrames();j++) {
m_PositionValid = false;
ValidatePosition();
// extend bounds by vertex positions at the frame
for (size_t i=0;i<numverts;i++)
{
result += CModelDef::SkinPoint(verts[i], GetAnimatedBoneMatrices(), GetInverseBindBoneMatrices());
}
// advance to next frame
m_AnimTime += anim->GetFrameTime();
}
m_PositionValid = false;
m_Parent = oldparent;
SetTransform(oldtransform);
m_AnimTime = AnimTime;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// BuildAnimation: load raw animation frame animation from given file, and build a
// animation specific to this model
CSkeletonAnim* CModel::BuildAnimation(const char* filename, const char* name, float speed, double actionpos, double actionpos2)
{
CSkeletonAnimDef* def=g_SkelAnimMan.GetAnimation(filename);
if (!def) return NULL;
CSkeletonAnim* anim=new CSkeletonAnim;
anim->m_Name = name;
anim->m_AnimDef=def;
anim->m_Speed=speed;
anim->m_ActionPos=(float)( actionpos /* * anim->m_AnimDef->GetDuration() */ / speed );
anim->m_ActionPos2=(float)( actionpos2 /* * anim->m_AnimDef->GetDuration() */ / speed );
anim->m_ObjectBounds.SetEmpty();
InvalidateBounds();
return anim;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Update: update this model by the given time, in seconds
void CModel::Update(float time)
{
if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices)
{
// adjust for animation speed
float animTimeDelta = time*m_AnimSpeed;
float oldAnimTime = m_AnimTime;
// update animation time, but don't calculate bone matrices - do that (lazily) when
// something requests them; that saves some calculation work for offscreen models,
// and also assures the world space, inverted bone matrices (required for normal
// skinning) are up to date with respect to m_Transform
m_AnimTime += animTimeDelta;
float duration = m_Anim->m_AnimDef->GetDuration();
if (m_AnimTime > duration)
{
if (m_Flags & MODELFLAG_NOLOOPANIMATION)
{
if (m_NextAnim)
SetAnimation(m_NextAnim);
else
{
// Changing to no animation - probably becoming a corpse.
// Make sure the last displayed frame is the final frame
// of the animation.
float nearlyEnd = duration - 1.f; // 1 msec
if (fabs(oldAnimTime - nearlyEnd) < 1.f)
SetAnimation(NULL);
else
m_AnimTime = nearlyEnd;
}
}
else
m_AnimTime = fmod(m_AnimTime, duration);
}
// mark vertices as dirty
SetDirty(RENDERDATA_UPDATE_VERTICES);
// mark matrices as dirty
InvalidatePosition();
}
// update props
for (size_t i = 0; i < m_Props.size(); ++i)
m_Props[i].m_Model->Update(time);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool CModel::NeedsNewAnim(float time) const
{
// TODO: fix UnitAnimation so it correctly loops animated props
if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices)
{
// adjust for animation speed
float animtime = time * m_AnimSpeed;
float duration = m_Anim->m_AnimDef->GetDuration();
if (m_AnimTime + animtime > duration)
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// InvalidatePosition
void CModel::InvalidatePosition()
{
m_PositionValid = false;
for (uint i = 0; i < m_Props.size(); ++i)
m_Props[i].m_Model->InvalidatePosition();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ValidatePosition: ensure that current transform and bone matrices are both uptodate
void CModel::ValidatePosition()
{
if (m_PositionValid)
{
debug_assert(!m_Parent || m_Parent->m_PositionValid);
return;
}
if (m_Parent && !m_Parent->m_PositionValid)
{
// Make sure we don't base our calculations on
// a parent animation state that is out of date.
m_Parent->ValidatePosition();
// Parent will recursively call our validation.
debug_assert(m_PositionValid);
return;
}
if (m_Anim && m_BoneMatrices)
{
PROFILE( "generating bone matrices" );
debug_assert(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys());
m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION));
const CMatrix3D& transform=GetTransform();
for (size_t i=0;i<m_pModelDef->GetNumBones();i++) {
m_BoneMatrices[i].Concatenate(transform);
}
}
m_PositionValid = true;
// re-position and validate all props
for (size_t j = 0; j < m_Props.size(); ++j)
{
const Prop& prop=m_Props[j];
CMatrix3D proptransform = prop.m_Point->m_Transform;;
if (prop.m_Point->m_BoneIndex != 0xff)
proptransform.Concatenate(m_BoneMatrices[prop.m_Point->m_BoneIndex]);
else
proptransform.Concatenate(m_Transform);
prop.m_Model->SetTransform(proptransform);
prop.m_Model->ValidatePosition();
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SetAnimation: set the given animation as the current animation on this model;
// return false on error, else true
bool CModel::SetAnimation(CSkeletonAnim* anim, bool once, float speed, CSkeletonAnim* next)
{
m_Anim=NULL; // in case something fails
if (anim) {
m_Flags &= ~MODELFLAG_NOLOOPANIMATION;
if (once)
{
m_Flags |= MODELFLAG_NOLOOPANIMATION;
m_NextAnim = next;
}
if (!m_BoneMatrices && anim->m_AnimDef) {
// not boned, can't animate
return false;
}
if (m_BoneMatrices && !anim->m_AnimDef) {
// boned, but animation isn't valid
// (e.g. the default (static) idle animation on an animated unit)
return false;
}
if (anim->m_AnimDef && anim->m_AnimDef->GetNumKeys() != m_pModelDef->GetNumBones()) {
// mismatch between model's skeleton and animation's skeleton
LOG(ERROR, LOG_CATEGORY, "Mismatch between model's skeleton and animation's skeleton (%d model bones != %d animation keys)",
m_pModelDef->GetNumBones(), anim->m_AnimDef->GetNumKeys());
return false;
}
// reset the cached bounds when the animation is changed
m_ObjectBounds.SetEmpty();
InvalidateBounds();
// start anim from beginning
m_AnimTime=0;
// Adjust speed by animation base rate.
m_AnimSpeed = speed * anim->m_Speed;
}
m_Anim = anim;
return true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CopyAnimation
void CModel::CopyAnimationFrom(CModel* source)
{
m_Anim = source->m_Anim;
m_NextAnim = source->m_NextAnim;
m_AnimTime = source->m_AnimTime;
m_AnimSpeed = source->m_AnimSpeed;
m_Flags &= ~MODELFLAG_CASTSHADOWS;
if (source->m_Flags & MODELFLAG_CASTSHADOWS)
m_Flags |= MODELFLAG_CASTSHADOWS;
m_ObjectBounds.SetEmpty();
InvalidateBounds();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// AddProp: add a prop to the model on the given point
void CModel::AddProp(SPropPoint* point, CModel* model, CObjectEntry* objectentry)
{
// position model according to prop point position
model->SetTransform(point->m_Transform);
model->m_Parent = this;
// check if we're already using this point, and remove it if so
// (when a prop is removed it will also remove the prop point)
uint i;
for (i = 0; i < m_Props.size(); i++) {
if (m_Props[i].m_Point == point) {
delete m_Props[i].m_Model;
break;
}
}
// not using point; add new prop
Prop prop;
prop.m_Point = point;
prop.m_Model = model;
prop.m_ObjectEntry = objectentry;
m_Props.push_back(prop);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// RemoveProp: remove a prop from the given point
void CModel::RemoveProp(SPropPoint* point)
{
uint i;
for (i=0;i<m_Props.size();i++) {
if (m_Props[i].m_Point==point) {
delete m_Props[i].m_Model;
// (when a prop is removed it will automatically remove its prop point)
break;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Clone: return a clone of this model
CModel* CModel::Clone() const
{
CModel* clone = new CModel;
clone->m_ObjectBounds = m_ObjectBounds;
clone->InitModel(m_pModelDef);
clone->SetTexture(m_Texture);
if (m_Texture.GetHandle())
h_add_ref(m_Texture.GetHandle());
clone->SetMaterial(m_Material);
clone->SetAnimation(m_Anim);
clone->SetFlags(m_Flags);
for (uint i=0;i<m_Props.size();i++) {
// eek! TODO, RC - need to investigate shallow clone here
clone->AddProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry);
}
return clone;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SetTransform: set the transform on this object, and reorientate props accordingly
void CModel::SetTransform(const CMatrix3D& transform)
{
// call base class to set transform on this object
CRenderableObject::SetTransform(transform);
InvalidateBounds();
InvalidatePosition();
}
//////////////////////////////////////////////////////////////////////////
void CModel::SetMaterial(const CMaterial &material)
{
m_Material = material;
if(m_Material.GetTexture().Trim(PS_TRIM_BOTH).length() > 0)
{
// [TODO: uh, shouldn't this be doing something?]
}
}
void CModel::SetPlayerID(int id)
{
m_Material.SetPlayerColor(id);
for (std::vector<Prop>::iterator it = m_Props.begin(); it != m_Props.end(); ++it)
it->m_Model->SetPlayerID(id);
}
void CModel::SetPlayerColor(CColor& colour)
{
m_Material.SetPlayerColor(colour);
}
void CModel::SetShadingColor(CColor& colour)
{
m_ShadingColor = colour;
for( std::vector<Prop>::iterator it = m_Props.begin(); it != m_Props.end(); ++it )
{
it->m_Model->SetShadingColor(colour);
}
}