# Fixed skeletal animation algorithm.

* Skinning is done in a way that works when there's more than one bone
influencing a vertex.
 * PMDs now store vertexes in world-space instead of bind-space. (The
loader converts the old-version PMDs so they still work.)
 * Moved SkinPoint, SkinNormal into CModelDef so it could use them when
loading the old PMDs.
 * Made the FastNormals approach non-optional, so the inverse-transpose
bone matrices could be removed. Changed the explanation of why it's a
valid approach.
 * Quaternion: Made GetInverse assume that the quaternions have unit
length (which they do when they're representing 3D rotations).
 * lib: Added support for DDS files that aren't a multiple of 4x4 (most
useful for 1x1, 2x2, etc that are still powers of two).
 * Actor Viewer: Added white terrain texture to the minimal test mod, so
shadows are visible. Changed default so walk/run animations don't move
the unit along the ground.
 * Removed some redundant repetition in doc comments.
 * Removed some unnecessary #includes.

This was SVN commit r4696.
This commit is contained in:
Ykkrosh 2006-12-15 16:09:30 +00:00
parent fc97a81743
commit 173c56140c
21 changed files with 280 additions and 322 deletions

Binary file not shown.

View File

@ -55,7 +55,6 @@ struct BoneTransform
{
float translation[3];
float orientation[4];
FMMatrix44 matrix; // not output in the PMD, but useful for calculating bone transforms
};
@ -146,11 +145,7 @@ public:
FCDGeometryPolygons* polys = GetPolysFromGeometry((FCDGeometry*)baseTarget);
// Make sure it doesn't use more bones per vertex than the game can handle
// SkinReduceInfluences(skin, maxInfluences, 0.001f);
// XXX The game is broken if there's >1 influence per vertex. Until
// that's fixed, just limit it to 1 and put up with the ugly blending...
SkinReduceInfluences(skin, 1, 0.001f);
SkinReduceInfluences(skin, maxInfluences, 0.001f);
// Convert the bone influences into VertexBlend structures for the PMD
@ -207,8 +202,7 @@ public:
BoneTransform b = {
{ parts.t.x, parts.t.y, parts.t.z },
{ parts.q.x, parts.q.y, parts.q.z, parts.q.w },
bindPose
{ parts.q.x, parts.q.y, parts.q.z, parts.q.w }
};
int boneId = StdSkeletons::FindStandardBoneID(joint->joint->GetName());
@ -231,7 +225,7 @@ public:
assert(normal.size() == vertices*3);
assert(texcoord.size() == vertices*2);
TransformVertices(position, normal, blends, bones, transform);
TransformVertices(position, normal, bones, transform);
WritePMD(output, vertices, bones.size(), &position[0], &normal[0], &texcoord[0], &blends[0], &bones[0]);
}
@ -257,7 +251,7 @@ public:
if (boneCount) assert(boneWeights && boneTransforms);
output("PSMD", 4); // magic number
write<uint32>(output, 2); // version number
write<uint32>(output, 3); // version number
write<uint32>(output, (uint32)(
4 + 13*4*vertexCount + // vertices
4 + 6*vertexCount/3 + // faces
@ -364,52 +358,17 @@ public:
}
}
static void TransformVertices(FloatList& position, FloatList& normal, std::vector<VertexBlend>& blends, std::vector<BoneTransform>& bones, const FMMatrix44& transform)
static void TransformVertices(FloatList& position, FloatList& normal, std::vector<BoneTransform>& bones, const FMMatrix44& transform)
{
for (size_t vtxId = 0; vtxId < position.size()/3; ++vtxId)
{
// Skinned vertices need to be transformed by the inverse of their
// rest states:
float zero16[16] = {0};
FMMatrix44 bindPoseTransform (zero16);
// Calculate the weighted sum of influence matrices
for (size_t j = 0; j < maxInfluences; ++j)
{
// Ignore unused bone influences
if (blends[vtxId].bones[j] == 0xff)
continue;
float weight = blends[vtxId].weights[j];
const BoneTransform& b = bones[blends[vtxId].bones[j]];
// The transformation matrix could be reconstructed with:
/*
FMMatrix44 R = QuatToMatrix(b.orientation[0], b.orientation[1], b.orientation[2], b.orientation[3]);
FMMatrix44 T = FMMatrix44::TranslationMatrix(FMVector3(b.translation, 0));
FMMatrix44 boneMatrix = T * R;
*/
// but since we generated orientation/translation from a matrix,
// just use that matrix directly:
FMMatrix44 boneMatrix = b.matrix;
bindPoseTransform = bindPoseTransform + weight * boneMatrix;
}
FMVector3 pos (&position[vtxId*3], 0);
FMVector3 norm (&normal[vtxId*3], 0);
// Apply the scene-node transforms first
// Apply the scene-node transforms
pos = transform.TransformCoordinate(pos);
norm = transform.TransformVector(norm).Normalize();
// Apply the inverse bind pose transform, so the model will display
// correctly after it's been transform back again
FMMatrix44 bindPoseTransformInverse = bindPoseTransform.Inverted();
pos = bindPoseTransformInverse.TransformCoordinate(pos);
norm = bindPoseTransformInverse.TransformVector(norm);
// Switch from Max's coordinate system into the game's:
std::swap(pos.y, pos.z);

View File

@ -27,9 +27,9 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Constructor
CModel::CModel()
: m_Parent(0), m_Flags(0), m_Anim(0), m_AnimTime(0),
m_BoneMatrices(0), m_InvTranspBoneMatrices(0),
m_PositionValid(false), m_InvTranspValid(false), m_ShadingColor(1,1,1,1)
: 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)
{
}
@ -62,8 +62,9 @@ CModel::~CModel()
void CModel::ReleaseData()
{
delete[] m_BoneMatrices;
delete[] m_InvTranspBoneMatrices;
for (size_t i=0;i<m_Props.size();i++) {
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;
}
@ -84,19 +85,24 @@ bool CModel::InitModel(CModelDefPtr modeldef)
m_pModelDef = modeldef;
size_t numBones=modeldef->GetNumBones();
if (numBones != 0) {
size_t numBones = modeldef->GetNumBones();
if (numBones != 0)
{
// allocate matrices for bone transformations
m_BoneMatrices=new CMatrix3D[numBones];
m_InvTranspBoneMatrices=new CMatrix3D[numBones];
m_BoneMatrices = new CMatrix3D[numBones];
m_InverseBindBoneMatrices = new CMatrix3D[numBones];
// store default pose until animation assigned
CBoneState* defpose=modeldef->GetBones();
for (uint i=0;i<numBones;i++) {
CMatrix3D& m=m_BoneMatrices[i];
m.SetIdentity();
m.Rotate(defpose[i].m_Rotation);
m.Translate(defpose[i].m_Translation);
m_InvTranspValid = false;
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());
}
}
@ -106,33 +112,6 @@ bool CModel::InitModel(CModelDefPtr modeldef)
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SkinPoint: skin the given point using the given blend and matrix data
static CVector3D SkinPoint(const CVector3D& pos, const SVertexBlend& blend,
const CMatrix3D* bonestates)
{
CVector3D result,tmp;
// must have at least one valid bone if we're using SkinPoint
if (blend.m_Bone[0] == 0xff)
{
ONCE( debug_warn("SkinPoint called for vertex with no bone weights") );
return CVector3D(0, 0, 0);
}
const CMatrix3D& m = bonestates[blend.m_Bone[0]];
m.Transform(pos, result);
result *= blend.m_Weight[0];
for (int i = 1; i < SVertexBlend::SIZE && blend.m_Bone[i] != 0xff; i++) {
const CMatrix3D& m = bonestates[blend.m_Bone[i]];
m.Transform(pos, tmp);
result += tmp*blend.m_Weight[i];
}
return result;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcBound: calculate the world space bounds of this model
void CModel::CalcBounds()
@ -203,10 +182,10 @@ void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result)
ValidatePosition();
// extend bounds by vertex positions at the frame
for (size_t i=0;i<numverts;i++) {
CVector3D tmp = SkinPoint(verts[i].m_Coords,verts[i].m_Blend,GetBoneMatrices());
result+=tmp;
}
for (size_t i=0;i<numverts;i++)
{
result += CModelDef::SkinPoint(verts[i], GetAnimatedBoneMatrices(), GetInverseBindBoneMatrices());
}
// advance to next frame
m_AnimTime += anim->GetFrameTime();
}
@ -322,7 +301,6 @@ void CModel::ValidatePosition()
}
m_PositionValid = true;
m_InvTranspValid = false;
// re-position and validate all props
for (size_t j = 0; j < m_Props.size(); ++j)
@ -341,24 +319,6 @@ void CModel::ValidatePosition()
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CalcInvTranspBoneMatrices
void CModel::CalcInvTranspBoneMatrices()
{
debug_assert(m_BoneMatrices);
PROFILE( "invert transpose bone matrices" );
CMatrix3D tmp;
for(size_t i = 0; i < m_pModelDef->GetNumBones(); ++i)
{
m_BoneMatrices[i].GetInverse(tmp);
tmp.GetTranspose(m_InvTranspBoneMatrices[i]);
}
m_InvTranspValid = true;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SetAnimation: set the given animation as the current animation on this model;
// return false on error, else true

View File

@ -101,24 +101,26 @@ public:
void CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result);
/**
* SetTransform: Set transform of this object.
* Set transform of this object.
*
* @note In order to ensure that all child props are updated properly,
* you must call ValidatePosition().
*/
void SetTransform(const CMatrix3D& transform);
/**
* Return whether this is a skinned/skeletal model. If it is, Get*BoneMatrices()
* will return valid non-NULL arrays.
*/
bool IsSkinned() { return (m_BoneMatrices != NULL); }
// return the models bone matrices
const CMatrix3D* GetBoneMatrices() {
const CMatrix3D* GetAnimatedBoneMatrices() {
debug_assert(m_PositionValid);
return m_BoneMatrices;
}
// return the models inverted transposed bone matrices for normal transformation
const CMatrix3D* GetInvTranspBoneMatrices() {
debug_assert(m_PositionValid);
if (!m_InvTranspValid)
CalcInvTranspBoneMatrices();
return m_InvTranspBoneMatrices;
const CMatrix3D* GetInverseBindBoneMatrices() {
return m_InverseBindBoneMatrices;
}
// load raw animation frame animation from given file, and build a
@ -137,7 +139,7 @@ public:
CModel* Clone() const;
/**
* ValidatePosition: Ensure that both the transformation and the bone
* Ensure that both the transformation and the bone
* matrices are correct for this model and all its props.
*/
void ValidatePosition();
@ -147,19 +149,13 @@ private:
void ReleaseData();
/**
* InvalidatePosition: Mark this model's position and bone matrices,
* Mark this model's position and bone matrices,
* and all props' positions as invalid.
*/
void InvalidatePosition();
/**
* CalcInvTranspBoneMatrices: Calc inverse transpose bone matrices
* from bone matrices.
*/
void CalcInvTranspBoneMatrices();
/**
* m_Parent: If non-null, m_Parent points to the model that we
* If non-null, m_Parent points to the model that we
* are attached to.
*/
CModel* m_Parent;
@ -186,23 +182,16 @@ private:
float m_AnimTime;
// current state of all bones on this model; null if associated modeldef isn't skeletal
CMatrix3D* m_BoneMatrices;
// inverse of the transpose of the above matrices
CMatrix3D* m_InvTranspBoneMatrices;
// inverse matrices for the bind pose's bones; null if not skeletal
CMatrix3D* m_InverseBindBoneMatrices;
// list of current props on model
std::vector<Prop> m_Props;
/**
* m_PositionValid: true if both transform and and bone matrices
* are valid.
* true if both transform and and bone matrices are valid.
*/
bool m_PositionValid;
/**
* m_InvTranspValid: true if m_InvTranspBoneMatrices match the current
* m_BoneMatrices
*/
bool m_InvTranspValid;
// modulating color
CColor m_ShadingColor;
};

View File

@ -9,9 +9,73 @@
#include "precompiled.h"
#include "ModelDef.h"
#include "graphics/SkeletonAnimDef.h"
#include "ps/FilePacker.h"
#include "ps/FileUnpacker.h"
#include "maths/Vector4D.h"
CVector3D CModelDef::SkinPoint(const SModelVertex& vtx,
const CMatrix3D newPoseMatrices[],
const CMatrix3D inverseBindMatrices[])
{
CVector3D result (0, 0, 0);
for (int i = 0; i < SVertexBlend::SIZE && vtx.m_Blend.m_Bone[i] != 0xff; ++i)
{
CVector3D bindSpace = inverseBindMatrices[vtx.m_Blend.m_Bone[i]].Transform(vtx.m_Coords);
CVector3D worldSpace = newPoseMatrices[vtx.m_Blend.m_Bone[i]].Transform(bindSpace);
result += worldSpace * vtx.m_Blend.m_Weight[i];
}
return result;
}
CVector3D CModelDef::SkinNormal(const SModelVertex& vtx,
const CMatrix3D newPoseMatrices[],
const CMatrix3D inverseBindMatrices[])
{
// 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 cone 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)
{
CVector3D bindSpace = inverseBindMatrices[vtx.m_Blend.m_Bone[i]].Rotate(vtx.m_Norm);
CVector3D worldSpace = newPoseMatrices[vtx.m_Blend.m_Bone[i]].Rotate(bindSpace);
result += worldSpace * 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;
}
///////////////////////////////////////////////////////////////////////////////
// CModelDef Constructor
@ -38,11 +102,10 @@ CModelDef::~CModelDef()
// return null if no match (case insensitive search)
SPropPoint* CModelDef::FindPropPoint(const char* name) const
{
for (uint i=0;i<m_NumPropPoints;i++) {
if (stricmp(name,m_PropPoints[i].m_Name)==0) {
for (u32 i = 0; i < m_NumPropPoints; ++i)
if (m_PropPoints[i].m_Name == name)
return &m_PropPoints[i];
}
}
return 0;
}
@ -60,49 +123,76 @@ CModelDef* CModelDef::Load(const char* filename)
throw PSERROR_File_InvalidVersion();
}
CModelDef* mdef=new CModelDef;
std::auto_ptr<CModelDef> mdef (new CModelDef);
mdef->m_Name = filename;
try {
// now unpack everything
unpacker.UnpackRaw(&mdef->m_NumVertices,sizeof(mdef->m_NumVertices));
mdef->m_pVertices=new SModelVertex[mdef->m_NumVertices];
unpacker.UnpackRaw(mdef->m_pVertices,sizeof(SModelVertex)*mdef->m_NumVertices);
unpacker.UnpackRaw(&mdef->m_NumFaces,sizeof(mdef->m_NumFaces));
mdef->m_pFaces=new SModelFace[mdef->m_NumFaces];
unpacker.UnpackRaw(mdef->m_pFaces,sizeof(SModelFace)*mdef->m_NumFaces);
unpacker.UnpackRaw(&mdef->m_NumBones,sizeof(mdef->m_NumBones));
if (mdef->m_NumBones) {
mdef->m_Bones=new CBoneState[mdef->m_NumBones];
unpacker.UnpackRaw(mdef->m_Bones,mdef->m_NumBones*sizeof(CBoneState));
}
if (unpacker.GetVersion()>=2) {
// versions >=2 also have prop point data
unpacker.UnpackRaw(&mdef->m_NumPropPoints,sizeof(mdef->m_NumPropPoints));
if (mdef->m_NumPropPoints) {
mdef->m_PropPoints=new SPropPoint[mdef->m_NumPropPoints];
for (u32 i=0;i<mdef->m_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);
}
}
}
} catch (PSERROR_File_UnexpectedEOF&) {
delete mdef;
throw;
// now unpack everything
unpacker.UnpackRaw(&mdef->m_NumVertices,sizeof(mdef->m_NumVertices));
mdef->m_pVertices=new SModelVertex[mdef->m_NumVertices];
unpacker.UnpackRaw(mdef->m_pVertices,sizeof(SModelVertex)*mdef->m_NumVertices);
unpacker.UnpackRaw(&mdef->m_NumFaces,sizeof(mdef->m_NumFaces));
mdef->m_pFaces=new SModelFace[mdef->m_NumFaces];
unpacker.UnpackRaw(mdef->m_pFaces,sizeof(SModelFace)*mdef->m_NumFaces);
unpacker.UnpackRaw(&mdef->m_NumBones,sizeof(mdef->m_NumBones));
if (mdef->m_NumBones)
{
mdef->m_Bones=new CBoneState[mdef->m_NumBones];
unpacker.UnpackRaw(mdef->m_Bones,mdef->m_NumBones*sizeof(CBoneState));
}
return mdef;
if (unpacker.GetVersion() >= 2)
{
// versions >=2 also have prop point data
unpacker.UnpackRaw(&mdef->m_NumPropPoints,sizeof(mdef->m_NumPropPoints));
if (mdef->m_NumPropPoints) {
mdef->m_PropPoints=new SPropPoint[mdef->m_NumPropPoints];
for (u32 i=0;i<mdef->m_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 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
{
CMatrix3D identity;
identity.SetIdentity();
std::vector<CMatrix3D> identityBones (mdef->m_NumBones, identity);
std::vector<CMatrix3D> bindPose (mdef->m_NumBones);
for (u32 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 (u32 i = 0; i < mdef->m_NumVertices; ++i)
{
mdef->m_pVertices[i].m_Coords = SkinPoint(mdef->m_pVertices[i], &bindPose[0], &identityBones[0]);
mdef->m_pVertices[i].m_Norm = SkinNormal(mdef->m_pVertices[i], &bindPose[0], &identityBones[0]);
}
}
}
return mdef.release();
}
///////////////////////////////////////////////////////////////////////////////
@ -137,7 +227,6 @@ void CModelDef::Save(const char* filename,const CModelDef* mdef)
packer.Write(filename);
}
///////////////////////////////////////////////////////////////////////////////
// SetRenderData: Set the render data object for the given key,
void CModelDef::SetRenderData(const void* key, CModelDefRPrivate* data)

View File

@ -11,13 +11,12 @@
#include "ps/CStr.h"
#include "maths/Vector3D.h"
#include "MeshManager.h"
#include "SkeletonAnimDef.h"
#include "maths/Quaternion.h"
#include <map>
#include <vector>
class CMeshManager;
class CModelDef;
class CBoneState;
///////////////////////////////////////////////////////////////////////////////
// SPropPoint: structure describing a prop point
@ -89,7 +88,7 @@ class CModelDef
friend class CMeshManager;
public:
// current file version given to saved animations
enum { FILE_VERSION = 2 };
enum { FILE_VERSION = 3 };
// supported file read version - files with a version less than this will be rejected
enum { FILE_READ_VERSION = 1 };
@ -101,7 +100,7 @@ public:
virtual ~CModelDef();
// model I/O functions
static void Save(const char* filename,const CModelDef* mdef);
static void Save(const char* filename,const CModelDef* mdef);
public:
// accessor: get vertex data
@ -116,7 +115,6 @@ public:
size_t GetNumBones() const { return (size_t)m_NumBones; }
CBoneState* GetBones() const { return m_Bones; }
// accessor: get prop data
int GetNumPropPoints() const { return m_NumPropPoints; }
SPropPoint* GetPropPoints() const { return m_PropPoints; }
@ -126,7 +124,23 @@ public:
SPropPoint* FindPropPoint(const char* name) const;
/**
* SetRenderData: Register renderer private data. Use the key to
* Transform the given vertex's position from the bind pose into the new pose.
*
* @return new world-space vertex coordinates
*/
static CVector3D SkinPoint(const SModelVertex& vtx,
const CMatrix3D newPoseMatrices[], const CMatrix3D inverseBindMatrices[]);
/**
* Transform the given vertex's normal from the bind pose into the new pose.
*
* @return new world-space vertex normal
*/
static CVector3D SkinNormal(const SModelVertex& vtx,
const CMatrix3D newPoseMatrices[], const CMatrix3D inverseBindMatrices[]);
/**
* Register renderer private data. Use the key to
* distinguish between private data used by different render paths.
* The private data will be managed by this CModelDef object:
* It will be deleted when CModelDef is destructed or when private

View File

@ -2,18 +2,17 @@
#include <math.h>
#include "MiniMap.h"
#include "graphics/GameView.h"
#include "graphics/MiniPatch.h"
#include "graphics/Model.h"
#include "graphics/Terrain.h"
#include "graphics/TextureEntry.h"
#include "graphics/TextureManager.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "gui/MiniMap.h"
#include "lib/ogl.h"
#include "lib/sdl.h"
#include "maths/Bound.h"
#include "ps/Game.h"
#include "ps/Interact.h"
#include "network/NetMessage.h"

View File

@ -2,9 +2,10 @@
#define __H_MINIMAP_H__
#include "gui/GUI.h"
#include "ps/Vector2D.h"
#include "maths/Vector3D.h"
#include "graphics/Camera.h"
class CVector2D;
class CVector3D;
class CCamera;
class CTerrain;
class CUnitManager;

View File

@ -517,16 +517,28 @@ static LibError decode_sd(const DDSURFACEDESC2* sd, uint* w_, uint* h_,
// image dimensions
const u32 h = read_le32(&sd->dwHeight);
const u32 w = read_le32(&sd->dwWidth);
// .. not padded to S3TC block size
if(w % 4 || h % 4)
WARN_RETURN(ERR::TEX_INVALID_SIZE);
// pixel format
uint bpp, flags;
RETURN_ERR(decode_pf(&sd->ddpfPixelFormat, &bpp, &flags));
// if the image is not aligned with the S3TC block size, it is stored
// with extra pixels on the bottom left to fill up the space, so we need
// to account for those when calculating how big it should be
u32 stored_h, stored_w;
if(flags & TEX_DXT)
{
stored_h = round_up(h, 4);
stored_w = round_up(w, 4);
}
else
{
stored_h = h;
stored_w = w;
}
// verify pitch or linear size, if given
const size_t pitch = w*bpp/8;
const size_t pitch = stored_w*bpp/8;
const u32 sd_pitch_or_size = read_le32(&sd->dwPitchOrLinearSize);
if(sd_flags & DDSD_PITCH)
{
@ -535,7 +547,7 @@ static LibError decode_sd(const DDSURFACEDESC2* sd, uint* w_, uint* h_,
}
if(sd_flags & DDSD_LINEARSIZE)
{
if(sd_pitch_or_size != pitch*h)
if(sd_pitch_or_size != pitch*stored_h)
WARN_RETURN(ERR::CORRUPTED);
}
// note: both flags set would be invalid; no need to check for that,

View File

@ -13,6 +13,7 @@
#include "Matrix3D.h"
#include "Quaternion.h"
#include "Vector4D.h"
CMatrix3D::CMatrix3D ()
{
@ -262,8 +263,10 @@ void CMatrix3D::Scale (float x_scale, float y_scale, float z_scale)
//Returns the transpose of the matrix. For orthonormal
//matrices, this is the same is the inverse matrix
void CMatrix3D::GetTranspose(CMatrix3D& result) const
CMatrix3D CMatrix3D::GetTranspose() const
{
CMatrix3D result;
result._11 = _11;
result._21 = _12;
result._31 = _13;
@ -283,6 +286,8 @@ void CMatrix3D::GetTranspose(CMatrix3D& result) const
result._24 = _42;
result._34 = _43;
result._44 = _44;
return result;
}
@ -474,6 +479,13 @@ void CMatrix3D::GetInverse(CMatrix3D& dst) const
}
}
CMatrix3D CMatrix3D::GetInverse() const
{
CMatrix3D r;
GetInverse(r);
return r;
}
void CMatrix3D::Rotate(const CQuaternion& quat)
{
CMatrix3D rotationMatrix=quat.ToMatrix();

View File

@ -1,10 +1,8 @@
#ifndef __MATRIX3D_H
#define __MATRIX3D_H
#include <math.h>
#include "Vector3D.h"
#include "Vector4D.h"
class CVector3D;
class CVector4D;
class CQuaternion;
/////////////////////////////////////////////////////////////////////////
@ -97,8 +95,11 @@ public:
// calculate the inverse of this matrix, store in dst
void GetInverse(CMatrix3D& dst) const;
// return the inverse of this matrix
CMatrix3D GetInverse() const;
// calculate the transpose of this matrix, store in dst
void GetTranspose(CMatrix3D& dst) const;
CMatrix3D GetTranspose() const;
// return the translation component of this matrix
CVector3D GetTranslation() const;

View File

@ -130,26 +130,21 @@ CMatrix3D CQuaternion::ToMatrix () const
void CQuaternion::ToMatrix(CMatrix3D& result) const
{
float x2, y2, z2;
float wx, wy, wz, xx, xy, xz, yy, yz, zz;
// calculate coefficients
x2 = m_V.X + m_V.X;
y2 = m_V.Y + m_V.Y;
z2 = m_V.Z + m_V.Z;
xx = m_V.X * x2;
xy = m_V.X * y2;
xz = m_V.X * z2;
xx = m_V.X * m_V.X * 2.f;
xy = m_V.X * m_V.Y * 2.f;
xz = m_V.X * m_V.Z * 2.f;
yy = m_V.Y * y2;
yz = m_V.Y * z2;
yy = m_V.Y * m_V.Y * 2.f;
yz = m_V.Y * m_V.Z * 2.f;
zz = m_V.Z * z2;
zz = m_V.Z * m_V.Z * 2.f;
wx = m_W * x2;
wy = m_W * y2;
wz = m_W * z2;
wx = m_W * m_V.X * 2.f;
wy = m_W * m_V.Y * 2.f;
wz = m_W * m_V.Z * 2.f;
result._11 = 1.0f - (yy + zz);
result._12 = xy - wz;
@ -277,6 +272,8 @@ CVector3D CQuaternion::Rotate(const CVector3D& vec) const
CQuaternion CQuaternion::GetInverse() const
{
float lensqrd = SQR(m_V.X) + SQR(m_V.Y) + SQR(m_V.Z) + SQR(m_W);
return CQuaternion(-m_V.X/lensqrd, -m_V.Y/lensqrd, -m_V.Z/lensqrd, m_W/lensqrd);
// (x,y,z,w)^-1 = (-x/l^2, -y/l^2, -z/l^2, w/l^2) where l^2=x^2+y^2+z^2+w^2
// Since we're only using quaternions for rotation, they should always have unit
// length, so assume l=1
return CQuaternion(-m_V.X, -m_V.Y, -m_V.Z, m_W);
}

View File

@ -22,39 +22,39 @@ public:
CQuaternion();
CQuaternion(float x, float y, float z, float w);
//quaternion addition
// Quaternion addition
CQuaternion operator + (const CQuaternion &quat) const;
//quaternion addition/assignment
// Quaternion addition/assignment
CQuaternion &operator += (const CQuaternion &quat);
//quaternion multiplication
// Quaternion multiplication
CQuaternion operator * (const CQuaternion &quat) const;
//quaternion multiplication/assignment
// Quaternion multiplication/assignment
CQuaternion &operator *= (const CQuaternion &quat);
void FromEulerAngles (float x, float y, float z);
CVector3D ToEulerAngles();
//convert the quaternion to matrix
// Convert the quaternion to matrix
CMatrix3D ToMatrix() const;
void ToMatrix(CMatrix3D& result) const;
//sphere interpolation
// Sphere interpolation
void Slerp(const CQuaternion& from, const CQuaternion& to, float ratio);
// create a quaternion from axis/angle representation of a rotation
// Create a quaternion from axis/angle representation of a rotation
void FromAxisAngle(const CVector3D& axis, float angle);
// convert the quaternion to axis/angle representation of a rotation
// Convert the quaternion to axis/angle representation of a rotation
void ToAxisAngle(CVector3D& axis, float& angle);
// normalize this quaternion
// Normalize this quaternion
void Normalize();
// rotate a vector by this quaternion
// Rotate a vector by this quaternion. Assumes the quaternion is normalised.
CVector3D Rotate(const CVector3D& vec) const;
// calculate q^-1
// Calculate q^-1. Assumes the quaternion is normalised.
CQuaternion GetInverse() const;
};

View File

@ -49,8 +49,8 @@ public:
q.ToMatrix(m);
CQuaternion q2 = m.GetRotation();
// I hope there's a good reason why they're sometimes negated, and
// it's not just a bug...
// Quaternions (x,y,z,w) and (-x,-y,-z,-w) are equivalent when
// interpreted as rotations, so it doesn't matter which we get
const bool ok_oneway =
feq(q2.m_W, q.m_W) &&
feq(q2.m_V.X, q.m_V.X) &&

View File

@ -132,7 +132,7 @@ void* InstancingModelRenderer::CreateModelData(CModel* model)
CModelDefPtr mdef = model->GetModelDef();
IModelDef* imodeldef = (IModelDef*)mdef->GetRenderData(m);
debug_assert(!model->GetBoneMatrices());
debug_assert(!model->IsSkinned());
if (!imodeldef)
{

View File

@ -31,58 +31,6 @@
#define LOG_CATEGORY "graphics"
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SkinPoint: skin the vertex position using it's blend data and given bone matrices
static void SkinPoint(const SModelVertex& vertex,const CMatrix3D* matrices,CVector3D& result)
{
CVector3D tmp;
const SVertexBlend& blend=vertex.m_Blend;
// must have at least one valid bone if we're using SkinPoint
if (blend.m_Bone[0] == 0xff)
{
// (CModel should have already complained about this)
result = CVector3D(0, 0, 0);
return;
}
const CMatrix3D& m=matrices[blend.m_Bone[0]];
m.Transform(vertex.m_Coords,result);
result*=blend.m_Weight[0];
for (u32 i=1; i<SVertexBlend::SIZE && blend.m_Bone[i]!=0xff; i++) {
const CMatrix3D& m=matrices[blend.m_Bone[i]];
m.Transform(vertex.m_Coords,tmp);
result+=tmp*blend.m_Weight[i];
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SkinPoint: skin the vertex normal using it's blend data and given bone matrices
static void SkinNormal(const SModelVertex& vertex, const CMatrix3D* invtranspmatrices, CVector3D& result)
{
CVector3D tmp;
const SVertexBlend& blend=vertex.m_Blend;
// must have at least one valid bone if we're using SkinNormal
if (blend.m_Bone[0] == 0xff)
{
// (CModel should have already complained about this)
result = CVector3D(0, 0, 0);
return;
}
const CMatrix3D& m = invtranspmatrices[blend.m_Bone[0]];
m.Rotate(vertex.m_Norm, result);
result*=blend.m_Weight[0];
for (u32 i=1; i<SVertexBlend::SIZE && vertex.m_Blend.m_Bone[i]!=0xff; i++) {
const CMatrix3D& m = invtranspmatrices[blend.m_Bone[i]];
m.Rotate(vertex.m_Norm,tmp);
result+=tmp*blend.m_Weight[i];
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
// ModelRenderer implementation
@ -112,27 +60,10 @@ void ModelRenderer::BuildPositionAndNormals(
size_t numVertices = mdef->GetNumVertices();
SModelVertex* vertices=mdef->GetVertices();
const CMatrix3D* bonematrices = model->GetBoneMatrices();
if (bonematrices)
if (model->IsSkinned())
{
// boned model - calculate skinned vertex positions/normals
PROFILE( "skinning bones" );
const CMatrix3D* invtranspbonematrices;
// Analytic geometry tells us that normal vectors need to be
// multiplied by the inverse of the transpose. However, calculating
// the inverse is slow, and analytic geometry also tells us that
// for orthogonal matrices, the inverse is equal to the transpose,
// so the inverse of the transpose is, in fact, the original matrix.
//
// The "fast normals" code assumes that bone transformation contain
// no "weird" transformations like shears or non-uniform scaling
// (actually, the entire code assumes no scaling) and thus gets
// around the slow calculation of the inverse.
if (g_Renderer.m_FastNormals)
invtranspbonematrices = bonematrices;
else
invtranspbonematrices = model->GetInvTranspBoneMatrices();
// Avoid the noisy warnings that occur inside SkinPoint/SkinNormal in
// some broken situations
@ -144,8 +75,8 @@ void ModelRenderer::BuildPositionAndNormals(
for (size_t j=0; j<numVertices; j++)
{
SkinPoint(vertices[j], bonematrices, Position[j]);
SkinNormal(vertices[j], invtranspbonematrices, Normal[j]);
Position[j] = CModelDef::SkinPoint(vertices[j], model->GetAnimatedBoneMatrices(), model->GetInverseBindBoneMatrices());
Normal[j] = CModelDef::SkinNormal(vertices[j], model->GetAnimatedBoneMatrices(), model->GetInverseBindBoneMatrices());
}
}
else

View File

@ -353,7 +353,6 @@ CRenderer::CRenderer()
m_ClearColor[0]=m_ClearColor[1]=m_ClearColor[2]=m_ClearColor[3]=0;
m_SortAllTransparent = false;
m_FastNormals = true;
m_DisplayFrustum = false;
m_DisableCopyShadow = false;
m_FastPlayerColor = true;
@ -1747,7 +1746,6 @@ void CRenderer::ScriptingInit()
AddProperty(L"renderpath", &CRenderer::JSI_GetRenderPath, &CRenderer::JSI_SetRenderPath);
AddProperty(L"useDepthTexture", &CRenderer::JSI_GetUseDepthTexture, &CRenderer::JSI_SetUseDepthTexture);
AddProperty(L"sortAllTransparent", &CRenderer::m_SortAllTransparent);
AddProperty(L"fastNormals", &CRenderer::m_FastNormals);
AddProperty(L"displayFrustum", &CRenderer::m_DisplayFrustum);
AddProperty(L"shadowZBias", &CRenderer::m_ShadowZBias);
AddProperty(L"disableCopyShadow", &CRenderer::m_DisableCopyShadow);

View File

@ -431,13 +431,6 @@ protected:
*/
bool m_SortAllTransparent;
/**
* m_FastNormals: Use faster normal transformation in the
* software transform by multiplying with the bone matrix itself
* instead of the transpose of the inverse.
*/
bool m_FastNormals;
/**
* m_DisplayFrustum: Render the cull frustum and other data that may be interesting
* to evaluate culling and shadow map calculations

View File

@ -166,7 +166,7 @@ static void SendToGame(const AtlasMessage::sEnvironmentSettings& settings)
ActorViewer::ActorViewer(wxWindow* parent)
: wxFrame(parent, wxID_ANY, _("Actor Viewer"), wxDefaultPosition, wxSize(800, 600)),
m_CurrentSpeed(0.f), m_BackgroundColour(wxColour(255, 255, 255)),
m_ToggledWalking(true), m_ToggledWireframe(false), m_ToggledGround(true),
m_ToggledWalking(false), m_ToggledWireframe(false), m_ToggledGround(true),
m_ToggledShadows(true), m_ToggledStats(false),
m_ObjectSettings(m_ObjectSelection, AtlasMessage::eRenderView::ACTOR)
{

View File

@ -51,7 +51,7 @@ ActorViewer::ActorViewer()
: m(*new ActorViewerImpl())
{
m.Unit = NULL;
m.WalkEnabled = true;
m.WalkEnabled = false;
m.Background = SColor4ub(255, 255, 255, 255);
// Set up the renderer

View File

@ -168,7 +168,7 @@ MESSAGEHANDLER(LookAt)
0, 0, 0, 1
);
M.GetTranspose(camera.m_Orientation);
camera.m_Orientation = M.GetTranspose();
camera.m_Orientation.Translate(-eye);
camera.UpdateFrustum();