Index: ps/trunk/source/graphics/ModelDef.cpp
===================================================================
--- ps/trunk/source/graphics/ModelDef.cpp (revision 25386)
+++ ps/trunk/source/graphics/ModelDef.cpp (revision 25387)
@@ -1,526 +1,537 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
/*
* Defines a raw 3d model.
*/
#include "precompiled.h"
#include "ModelDef.h"
#include "graphics/SkeletonAnimDef.h"
#include "lib/sysdep/arch/x86_x64/simd.h"
#include "ps/FileIo.h"
#include "maths/Vector4D.h"
#if COMPILER_HAS_SSE
# include
#endif
void CModelDef::GetMaxBounds(CSkeletonAnimDef* anim, bool loop, CBoundingBoxAligned& result)
{
- std::unordered_map::const_iterator it = m_MaxBoundsPerAnimDef.find(anim ? anim->m_UID : 0);
+ const u32 animIndex = anim ? anim->m_UID : 0;
+
+ std::unordered_map::const_iterator it = m_MaxBoundsPerAnimDef.find(animIndex);
if (it != m_MaxBoundsPerAnimDef.end())
{
result = it->second;
return;
}
size_t numverts = GetNumVertices();
SModelVertex* verts = GetVertices();
if (!anim)
{
for (size_t i = 0; i < numverts; ++i)
result += verts[i].m_Coords;
- m_MaxBoundsPerAnimDef[0] = result;
+ m_MaxBoundsPerAnimDef[animIndex] = result;
return;
}
- ENSURE(anim->m_UID != 0);
+ ENSURE(animIndex != 0);
+ CMatrix3D* inverseBindBoneMatrix = GetInverseBindBoneMatrices();
std::vector boneMatrix(anim->GetNumKeys());
- // NB: by using frames, the bounds are technically pessimistic (since interpolation could end up outside of them).
- for (size_t j = 0; j < anim->GetNumFrames(); ++j)
- {
- anim->BuildBoneMatrices(j*anim->GetFrameTime(), boneMatrix.data(), loop);
+
+ const size_t numFrames = anim->GetNumFrames();
+ const float frameTime = anim->GetFrameTime();
+ const size_t numBones = GetNumBones();
+
+ // NB: by using frames, the bounds are technically pessimistic,
+ // since interpolation between frames can put vertices farther.
+ for (size_t j = 0; j < numFrames; ++j)
+ {
+ anim->BuildBoneMatrices(j * frameTime, boneMatrix.data(), loop);
+ for (size_t i = 0; i < numBones; ++i)
+ boneMatrix[i] *= inverseBindBoneMatrix[i];
for (size_t i = 0; i < numverts; ++i)
result += SkinPoint(verts[i], boneMatrix.data());
}
- m_MaxBoundsPerAnimDef[anim->m_UID] = result;
+ m_MaxBoundsPerAnimDef[animIndex] = result;
}
CVector3D CModelDef::SkinPoint(const SModelVertex& vtx,
const CMatrix3D newPoseMatrices[])
{
CVector3D result (0, 0, 0);
for (int i = 0; i < SVertexBlend::SIZE && vtx.m_Blend.m_Bone[i] != 0xff; ++i)
{
result += newPoseMatrices[vtx.m_Blend.m_Bone[i]].Transform(vtx.m_Coords) * vtx.m_Blend.m_Weight[i];
}
return result;
}
CVector3D CModelDef::SkinNormal(const SModelVertex& vtx,
const CMatrix3D newPoseMatrices[])
{
// To be correct, the normal vectors apparently need to be multiplied by the
// inverse of the transpose. Unfortunately inverses are slow.
// If a matrix is orthogonal, M * M^T = I and so the inverse of the transpose
// is the original matrix. But that's not entirely relevant here, because
// the bone matrices include translation components and so they're not
// orthogonal.
// But that's okay because we have
// M = T * R
// and want to find
// n' = (M^T^-1) * n
// = (T * R)^T^-1 * n
// = (R^T * T^T)^-1 * n
// = (T^T^-1 * R^T^-1) * n
// R is indeed orthogonal so R^T^-1 = R. T isn't orthogonal at all.
// But n is only a 3-vector, and from the forms of T and R (which have
// lots of zeroes) I can convince myself that replacing T with T^T^-1 has no
// effect on anything but the fourth component of M^T^-1 - and the fourth
// component is discarded since it has no effect on n', and so we can happily
// use n' = M*n.
//
// (This isn't very good as a proof, but it's better than assuming M is
// orthogonal when it's clearly not.)
CVector3D result (0, 0, 0);
for (int i = 0; i < SVertexBlend::SIZE && vtx.m_Blend.m_Bone[i] != 0xff; ++i)
{
result += newPoseMatrices[vtx.m_Blend.m_Bone[i]].Rotate(vtx.m_Norm) * vtx.m_Blend.m_Weight[i];
}
// If there was more than one influence, the result is probably not going
// to be of unit length (since it's a weighted sum of several independent
// unit vectors), so we need to normalise it.
// (It's fairly common to only have one influence, so it seems sensible to
// optimise that case a bit.)
if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence
result.Normalize();
return result;
}
void(*CModelDef::SkinPointsAndNormals)(
size_t numVertices,
const VertexArrayIterator& Position,
const VertexArrayIterator& Normal,
const SModelVertex* vertices,
const size_t* blendIndices,
const CMatrix3D newPoseMatrices[]) {};
static void SkinPointsAndNormalsFallback(
size_t numVertices,
const VertexArrayIterator& Position,
const VertexArrayIterator& Normal,
const SModelVertex* vertices,
const size_t* blendIndices,
const CMatrix3D newPoseMatrices[])
{
// To avoid some performance overhead, get the raw vertex array pointers
char* PositionData = Position.GetData();
size_t PositionStride = Position.GetStride();
char* NormalData = Normal.GetData();
size_t NormalStride = Normal.GetStride();
for (size_t j = 0; j < numVertices; ++j)
{
const SModelVertex& vtx = vertices[j];
CVector3D pos = newPoseMatrices[blendIndices[j]].Transform(vtx.m_Coords);
CVector3D norm = newPoseMatrices[blendIndices[j]].Rotate(vtx.m_Norm);
// If there was more than one influence, the result is probably not going
// to be of unit length (since it's a weighted sum of several independent
// unit vectors), so we need to normalise it.
// (It's fairly common to only have one influence, so it seems sensible to
// optimise that case a bit.)
if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence
norm.Normalize();
memcpy(PositionData + PositionStride*j, &pos.X, 3*sizeof(float));
memcpy(NormalData + NormalStride*j, &norm.X, 3*sizeof(float));
}
}
#if COMPILER_HAS_SSE
static void SkinPointsAndNormalsSSE(
size_t numVertices,
const VertexArrayIterator& Position,
const VertexArrayIterator& Normal,
const SModelVertex* vertices,
const size_t* blendIndices,
const CMatrix3D newPoseMatrices[])
{
// To avoid some performance overhead, get the raw vertex array pointers
char* PositionData = Position.GetData();
size_t PositionStride = Position.GetStride();
char* NormalData = Normal.GetData();
size_t NormalStride = Normal.GetStride();
// Must be aligned correctly for SSE
ASSERT((intptr_t)newPoseMatrices % 16 == 0);
ASSERT((intptr_t)PositionData % 16 == 0);
ASSERT((intptr_t)PositionStride % 16 == 0);
ASSERT((intptr_t)NormalData % 16 == 0);
ASSERT((intptr_t)NormalStride % 16 == 0);
__m128 col0, col1, col2, col3, vec0, vec1, vec2;
for (size_t j = 0; j < numVertices; ++j)
{
const SModelVertex& vtx = vertices[j];
const CMatrix3D& mtx = newPoseMatrices[blendIndices[j]];
// Loads matrix to xmm registers.
col0 = _mm_load_ps(mtx._data);
col1 = _mm_load_ps(mtx._data + 4);
col2 = _mm_load_ps(mtx._data + 8);
col3 = _mm_load_ps(mtx._data + 12);
// Loads and computes vertex coordinates.
vec0 = _mm_load1_ps(&vtx.m_Coords.X); // v0 = [x, x, x, x]
vec0 = _mm_mul_ps(col0, vec0); // v0 = [_11*x, _21*x, _31*x, _41*x]
vec1 = _mm_load1_ps(&vtx.m_Coords.Y); // v1 = [y, y, y, y]
vec1 = _mm_mul_ps(col1, vec1); // v1 = [_12*y, _22*y, _32*y, _42*y]
vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y, ...]
vec1 = _mm_load1_ps(&vtx.m_Coords.Z); // v1 = [z, z, z, z]
vec1 = _mm_mul_ps(col2, vec1); // v1 = [_13*z, _23*z, _33*z, _43*z]
vec1 = _mm_add_ps(vec1, col3); // v1 = [_13*z + _14, ...]
vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y + _13*z + _14, ...]
_mm_store_ps((float*)(PositionData + PositionStride*j), vec0);
// Loads and computes normal vectors.
vec0 = _mm_load1_ps(&vtx.m_Norm.X); // v0 = [x, x, x, x]
vec0 = _mm_mul_ps(col0, vec0); // v0 = [_11*x, _21*x, _31*x, _41*x]
vec1 = _mm_load1_ps(&vtx.m_Norm.Y); // v1 = [y, y, y, y]
vec1 = _mm_mul_ps(col1, vec1); // v1 = [_12*y, _22*y, _32*y, _42*y]
vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y, ...]
vec1 = _mm_load1_ps(&vtx.m_Norm.Z); // v1 = [z, z, z, z]
vec1 = _mm_mul_ps(col2, vec1); // v1 = [_13*z, _23*z, _33*z, _43*z]
vec0 = _mm_add_ps(vec0, vec1); // v0 = [_11*x + _12*y + _13*z, ...]
// If there was more than one influence, the result is probably not going
// to be of unit length (since it's a weighted sum of several independent
// unit vectors), so we need to normalise it.
// (It's fairly common to only have one influence, so it seems sensible to
// optimise that case a bit.)
if (vtx.m_Blend.m_Bone[1] != 0xff) // if more than one influence
{
// Normalization.
// vec1 = [x*x, y*y, z*z, ?*?]
vec1 = _mm_mul_ps(vec0, vec0);
// vec2 = [y*y, z*z, x*x, ?*?]
vec2 = _mm_shuffle_ps(vec1, vec1, _MM_SHUFFLE(3, 0, 2, 1));
vec1 = _mm_add_ps(vec1, vec2);
// vec2 = [z*z, x*x, y*y, ?*?]
vec2 = _mm_shuffle_ps(vec2, vec2, _MM_SHUFFLE(3, 0, 2, 1));
vec1 = _mm_add_ps(vec1, vec2);
// rsqrt(a) = 1 / sqrt(a)
vec1 = _mm_rsqrt_ps(vec1);
vec0 = _mm_mul_ps(vec0, vec1);
}
_mm_store_ps((float*)(NormalData + NormalStride*j), vec0);
}
}
#endif
void CModelDef::BlendBoneMatrices(
CMatrix3D boneMatrices[])
{
for (size_t i = 0; i < m_NumBlends; ++i)
{
const SVertexBlend& blend = m_pBlends[i];
CMatrix3D& boneMatrix = boneMatrices[m_NumBones + 1 + i];
// Note: there is a special case of joint influence, in which the vertex
// is influenced by the bind-shape matrix instead of a particular bone,
// which we indicate by setting the bone ID to the total number of bones.
// It should be blended with the world space transform and we have already
// set up this matrix in boneMatrices.
// (see http://trac.wildfiregames.com/ticket/1012)
boneMatrix.Blend(boneMatrices[blend.m_Bone[0]], blend.m_Weight[0]);
for (size_t j = 1; j < SVertexBlend::SIZE && blend.m_Bone[j] != 0xFF; ++j)
boneMatrix.AddBlend(boneMatrices[blend.m_Bone[j]], blend.m_Weight[j]);
}
}
// CModelDef Constructor
CModelDef::CModelDef() :
m_NumVertices(0), m_NumUVsPerVertex(0), m_pVertices(0), m_NumFaces(0), m_pFaces(0),
m_NumBones(0), m_Bones(0), m_InverseBindBoneMatrices(NULL),
m_NumBlends(0), m_pBlends(0), m_pBlendIndices(0),
m_Name(L"[not loaded]")
{
}
// CModelDef Destructor
CModelDef::~CModelDef()
{
for(RenderDataMap::iterator it = m_RenderData.begin(); it != m_RenderData.end(); ++it)
delete it->second;
delete[] m_pVertices;
delete[] m_pFaces;
delete[] m_Bones;
delete[] m_InverseBindBoneMatrices;
delete[] m_pBlends;
delete[] m_pBlendIndices;
}
// FindPropPoint: find and return pointer to prop point matching given name;
// return null if no match (case insensitive search)
const SPropPoint* CModelDef::FindPropPoint(const char* name) const
{
for (size_t i = 0; i < m_PropPoints.size(); ++i)
if (m_PropPoints[i].m_Name == name)
return &m_PropPoints[i];
return 0;
}
// Load: read and return a new CModelDef initialised with data from given file
CModelDef* CModelDef::Load(const VfsPath& filename, const VfsPath& name)
{
CFileUnpacker unpacker;
// read everything in from file
unpacker.Read(filename,"PSMD");
// check version
if (unpacker.GetVersion() mdef = std::make_unique();
mdef->m_Name = name;
// now unpack everything
mdef->m_NumVertices = unpacker.UnpackSize();
// versions prior to 4 only support 1 UV set, 4 and later store it here
if (unpacker.GetVersion() <= 3)
{
mdef->m_NumUVsPerVertex = 1;
}
else
{
mdef->m_NumUVsPerVertex = unpacker.UnpackSize();
}
mdef->m_pVertices=new SModelVertex[mdef->m_NumVertices];
for (size_t i = 0; i < mdef->m_NumVertices; ++i)
{
unpacker.UnpackRaw(&mdef->m_pVertices[i].m_Coords, 12);
unpacker.UnpackRaw(&mdef->m_pVertices[i].m_Norm, 12);
for (size_t s = 0; s < mdef->m_NumUVsPerVertex; ++s)
{
float uv[2];
unpacker.UnpackRaw(&uv[0], 8);
mdef->m_pVertices[i].m_UVs.push_back(uv[0]);
mdef->m_pVertices[i].m_UVs.push_back(uv[1]);
}
unpacker.UnpackRaw(&mdef->m_pVertices[i].m_Blend, sizeof(SVertexBlend));
}
mdef->m_NumFaces = unpacker.UnpackSize();
mdef->m_pFaces=new SModelFace[mdef->m_NumFaces];
unpacker.UnpackRaw(mdef->m_pFaces,sizeof(SModelFace)*mdef->m_NumFaces);
mdef->m_NumBones = unpacker.UnpackSize();
if (mdef->m_NumBones)
{
mdef->m_Bones=new CBoneState[mdef->m_NumBones];
unpacker.UnpackRaw(mdef->m_Bones,mdef->m_NumBones*sizeof(CBoneState));
mdef->m_pBlendIndices = new size_t[mdef->m_NumVertices];
std::vector blends;
for (size_t i = 0; i < mdef->m_NumVertices; i++)
{
const SVertexBlend &blend = mdef->m_pVertices[i].m_Blend;
if (blend.m_Bone[1] == 0xFF)
{
mdef->m_pBlendIndices[i] = blend.m_Bone[0];
}
else
{
// If there's already a vertex using the same blend as this, then
// reuse its entry from blends; otherwise add the new one to blends
size_t j;
for (j = 0; j < blends.size(); j++)
{
if (blend == blends[j]) break;
}
if (j >= blends.size())
blends.push_back(blend);
// This index is offset by one to allow the special case of a
// weighted influence relative to the bind-shape rather than
// a particular bone. See comment in BlendBoneMatrices.
mdef->m_pBlendIndices[i] = mdef->m_NumBones + 1 + j;
}
}
mdef->m_NumBlends = blends.size();
mdef->m_pBlends = new SVertexBlend[mdef->m_NumBlends];
std::copy(blends.begin(), blends.end(), mdef->m_pBlends);
}
if (unpacker.GetVersion() >= 2)
{
// versions >=2 also have prop point data
size_t numPropPoints = unpacker.UnpackSize();
mdef->m_PropPoints.resize(numPropPoints);
if (numPropPoints)
{
for (size_t i = 0; i < numPropPoints; i++)
{
unpacker.UnpackString(mdef->m_PropPoints[i].m_Name);
unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_Position.X, sizeof(mdef->m_PropPoints[i].m_Position));
unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_Rotation.m_V.X, sizeof(mdef->m_PropPoints[i].m_Rotation));
unpacker.UnpackRaw(&mdef->m_PropPoints[i].m_BoneIndex, sizeof(mdef->m_PropPoints[i].m_BoneIndex));
// build prop point transform
mdef->m_PropPoints[i].m_Transform.SetIdentity();
mdef->m_PropPoints[i].m_Transform.Rotate(mdef->m_PropPoints[i].m_Rotation);
mdef->m_PropPoints[i].m_Transform.Translate(mdef->m_PropPoints[i].m_Position);
}
}
}
if (unpacker.GetVersion() <= 2)
{
// Versions <=2 don't include the default 'root' prop point, so add it here
SPropPoint prop;
prop.m_Name = "root";
prop.m_Transform.SetIdentity();
prop.m_BoneIndex = 0xFF;
mdef->m_PropPoints.push_back(prop);
}
if (unpacker.GetVersion() <= 2)
{
// Versions <=2 store the vertexes relative to the bind pose. That
// isn't useful when you want to do correct skinning, so later versions
// store them in world space. So, fix the old models by skinning each
// vertex:
if (mdef->m_NumBones) // only do skinned models
{
std::vector bindPose (mdef->m_NumBones);
for (size_t i = 0; i < mdef->m_NumBones; ++i)
{
bindPose[i].SetIdentity();
bindPose[i].Rotate(mdef->m_Bones[i].m_Rotation);
bindPose[i].Translate(mdef->m_Bones[i].m_Translation);
}
for (size_t i = 0; i < mdef->m_NumVertices; ++i)
{
mdef->m_pVertices[i].m_Coords = SkinPoint(mdef->m_pVertices[i], &bindPose[0]);
mdef->m_pVertices[i].m_Norm = SkinNormal(mdef->m_pVertices[i], &bindPose[0]);
}
}
}
// Compute the inverse bind-pose matrices, needed by the skinning code
mdef->m_InverseBindBoneMatrices = new CMatrix3D[mdef->m_NumBones];
CBoneState* defpose = mdef->GetBones();
for (size_t i = 0; i < mdef->m_NumBones; ++i)
{
mdef->m_InverseBindBoneMatrices[i].SetIdentity();
mdef->m_InverseBindBoneMatrices[i].Translate(-defpose[i].m_Translation);
mdef->m_InverseBindBoneMatrices[i].Rotate(defpose[i].m_Rotation.GetInverse());
}
return mdef.release();
}
// Save: write the given CModelDef to the given file
void CModelDef::Save(const VfsPath& filename, const CModelDef* mdef)
{
CFilePacker packer(FILE_VERSION, "PSMD");
// pack everything up
const size_t numVertices = mdef->GetNumVertices();
packer.PackSize(numVertices);
packer.PackRaw(mdef->GetVertices(), sizeof(SModelVertex) * numVertices);
const size_t numFaces = mdef->GetNumFaces();
packer.PackSize(numFaces);
packer.PackRaw(mdef->GetFaces(), sizeof(SModelFace) * numFaces);
const size_t numBones = mdef->m_NumBones;
packer.PackSize(numBones);
if (numBones)
packer.PackRaw(mdef->m_Bones, sizeof(CBoneState) * numBones);
const size_t numPropPoints = mdef->m_PropPoints.size();
packer.PackSize(numPropPoints);
for (size_t i = 0; i < numPropPoints; i++)
{
packer.PackString(mdef->m_PropPoints[i].m_Name);
packer.PackRaw(&mdef->m_PropPoints[i].m_Position.X, sizeof(mdef->m_PropPoints[i].m_Position));
packer.PackRaw(&mdef->m_PropPoints[i].m_Rotation.m_V.X, sizeof(mdef->m_PropPoints[i].m_Rotation));
packer.PackRaw(&mdef->m_PropPoints[i].m_BoneIndex, sizeof(mdef->m_PropPoints[i].m_BoneIndex));
}
// flush everything out to file
packer.Write(filename);
}
// SetRenderData: Set the render data object for the given key,
void CModelDef::SetRenderData(const void* key, CModelDefRPrivate* data)
{
delete m_RenderData[key];
m_RenderData[key] = data;
}
// GetRenderData: Get the render data object for the given key,
// or 0 if no such object exists.
// Reference count of the render data object is automatically increased.
CModelDefRPrivate* CModelDef::GetRenderData(const void* key) const
{
RenderDataMap::const_iterator it = m_RenderData.find(key);
if (it != m_RenderData.end())
return it->second;
return 0;
}
void ModelDefActivateFastImpl()
{
#if COMPILER_HAS_SSE
if (HostHasSSE())
{
CModelDef::SkinPointsAndNormals = SkinPointsAndNormalsSSE;
return;
}
#endif
CModelDef::SkinPointsAndNormals = SkinPointsAndNormalsFallback;
}
Index: ps/trunk/source/graphics/SkeletonAnimDef.cpp
===================================================================
--- ps/trunk/source/graphics/SkeletonAnimDef.cpp (revision 25386)
+++ ps/trunk/source/graphics/SkeletonAnimDef.cpp (revision 25387)
@@ -1,147 +1,150 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
/*
* Raw description of a skeleton animation
*/
#include "precompiled.h"
#include "SkeletonAnimDef.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
#include "ps/CStr.h"
#include "ps/CLogger.h"
#include "ps/FileIo.h"
+namespace
+{
// Start IDs at 1 to leave 0 as a special value.
-u32 CSkeletonAnimDef::nextUID = 1;
+u32 g_NextSkeletonDefUID = 1;
+}
///////////////////////////////////////////////////////////////////////////////////////////
// CSkeletonAnimDef constructor
CSkeletonAnimDef::CSkeletonAnimDef() : m_FrameTime(0), m_NumKeys(0), m_NumFrames(0)
{
- m_UID = nextUID++;
+ m_UID = g_NextSkeletonDefUID++;
// Log a warning if we ever overflow. Should that not result from a bug, bumping to u64 ought to suffice.
- if (nextUID == 0)
+ if (g_NextSkeletonDefUID == 0)
{
// Reset to 1.
- nextUID++;
+ g_NextSkeletonDefUID++;
LOGWARNING("CSkeletonAnimDef unique ID overflowed to 0 - model-animation bounds may be incorrect.");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// CSkeletonAnimDef destructor
CSkeletonAnimDef::~CSkeletonAnimDef()
{
}
///////////////////////////////////////////////////////////////////////////////////////////
// BuildBoneMatrices: build matrices for all bones at the given time (in MS) in this
// animation
void CSkeletonAnimDef::BuildBoneMatrices(float time, CMatrix3D* matrices, bool loop) const
{
float fstartframe = time/m_FrameTime;
size_t startframe = (size_t)(int)(time/m_FrameTime);
float deltatime = fstartframe-startframe;
startframe %= m_NumFrames;
size_t endframe = startframe + 1;
endframe %= m_NumFrames;
if (!loop && endframe == 0)
{
// This might be something like a death animation, and interpolating
// between the final frame and the initial frame is wrong, because they're
// totally different. So if we've looped around to endframe==0, just display
// the animation's final frame with no interpolation.
for (size_t i = 0; i < m_NumKeys; i++)
{
const Key& key = GetKey(startframe, i);
matrices[i].SetIdentity();
matrices[i].Rotate(key.m_Rotation);
matrices[i].Translate(key.m_Translation);
}
}
else
{
for (size_t i = 0; i < m_NumKeys; i++)
{
const Key& startkey = GetKey(startframe, i);
const Key& endkey = GetKey(endframe, i);
CVector3D trans = Interpolate(startkey.m_Translation, endkey.m_Translation, deltatime);
// TODO: is slerp the best thing to use here?
CQuaternion rot;
rot.Slerp(startkey.m_Rotation, endkey.m_Rotation, deltatime);
rot.ToMatrix(matrices[i]);
matrices[i].Translate(trans);
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Load: try to load the anim from given file; return a new anim if successful
std::unique_ptr CSkeletonAnimDef::Load(const VfsPath& filename)
{
CFileUnpacker unpacker;
unpacker.Read(filename,"PSSA");
// check version
if (unpacker.GetVersion()();
try {
CStr name; // unused - just here to maintain compatibility with the animation files
unpacker.UnpackString(name);
unpacker.UnpackRaw(&anim->m_FrameTime,sizeof(anim->m_FrameTime));
anim->m_NumKeys = unpacker.UnpackSize();
anim->m_NumFrames = unpacker.UnpackSize();
anim->m_Keys.resize(anim->m_NumKeys*anim->m_NumFrames);
unpacker.UnpackRaw(anim->m_Keys.data(), anim->m_Keys.size() * sizeof(decltype(anim->m_Keys)::value_type));
} catch (PSERROR_File&) {
anim.reset();
throw;
}
return anim;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Save: try to save anim to file
void CSkeletonAnimDef::Save(const VfsPath& pathname, const CSkeletonAnimDef& anim)
{
CFilePacker packer(FILE_VERSION, "PSSA");
// pack up all the data
packer.PackString("");
packer.PackRaw(&anim.m_FrameTime,sizeof(anim.m_FrameTime));
const size_t numKeys = anim.m_NumKeys;
packer.PackSize(numKeys);
const size_t numFrames = anim.m_NumFrames;
packer.PackSize(numFrames);
packer.PackRaw(anim.m_Keys.data(), anim.m_Keys.size() * sizeof(decltype(anim.m_Keys)::value_type));
// now write it
packer.Write(pathname);
}
Index: ps/trunk/source/graphics/SkeletonAnimDef.h
===================================================================
--- ps/trunk/source/graphics/SkeletonAnimDef.h (revision 25386)
+++ ps/trunk/source/graphics/SkeletonAnimDef.h (revision 25387)
@@ -1,102 +1,101 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
/*
* Raw description of a skeleton animation
*/
#ifndef INCLUDED_SKELETONANIMDEF
#define INCLUDED_SKELETONANIMDEF
#include "maths/Vector3D.h"
#include "maths/Quaternion.h"
#include "lib/file/vfs/vfs_path.h"
#include
#include
////////////////////////////////////////////////////////////////////////////////////////
// CBoneState: structure describing state of a bone at some point
class CBoneState
{
public:
// translation of bone relative to root
CVector3D m_Translation;
// rotation of bone relative to root
CQuaternion m_Rotation;
};
////////////////////////////////////////////////////////////////////////////////////////
// CSkeletonAnimDef: raw description - eg bonestates - of an animation that plays upon
// a skeleton
class CSkeletonAnimDef
{
public:
// current file version given to saved animations
enum { FILE_VERSION = 1 };
// supported file read version - files with a version less than this will be rejected
enum { FILE_READ_VERSION = 1 };
public:
// Key: description of a single key in a skeleton animation
typedef CBoneState Key;
public:
// CSkeletonAnimDef constructor + destructor
CSkeletonAnimDef();
~CSkeletonAnimDef();
// return the number of keys in this animation
size_t GetNumKeys() const { return (size_t)m_NumKeys; }
// accessors: get a key for given bone at given time
Key& GetKey(size_t frame, size_t bone) { return m_Keys[frame*m_NumKeys+bone]; }
const Key& GetKey(size_t frame, size_t bone) const { return m_Keys[frame*m_NumKeys+bone]; }
// get duration of this anim, in ms
float GetDuration() const { return m_NumFrames*m_FrameTime; }
// return length of each frame, in ms
float GetFrameTime() const { return m_FrameTime; }
// return number of frames in animation
size_t GetNumFrames() const { return (size_t)m_NumFrames; }
// build matrices for all bones at the given time (in MS) in this animation
void BuildBoneMatrices(float time, CMatrix3D* matrices, bool loop) const;
// anim I/O functions
static std::unique_ptr Load(const VfsPath& filename);
static void Save(const VfsPath& pathname, const CSkeletonAnimDef& anim);
public:
// frame time - time between successive frames, in ms
float m_FrameTime;
// number of keys in each frame - should match number of bones in the skeleton
size_t m_NumKeys;
// number of frames in the animation
size_t m_NumFrames;
// animation data - m_NumKeys*m_NumFrames total keys
std::vector m_Keys;
// Unique identifier - used by CModelDef to cache bounds per-animDef.
// (hopefully we won't run into the u32 limit too soon).
u32 m_UID;
- static u32 nextUID;
};
#endif