Changeset View
Changeset View
Standalone View
Standalone View
source/graphics/Model.cpp
Show First 20 Lines • Show All 169 Lines • ▼ Show 20 Lines | void CModel::CalcAnimatedObjectBounds(CSkeletonAnimDef* anim, CBoundingBoxAligned& result) | ||||
// Following seems to stomp over the current animation time - which, unsurprisingly, | // Following seems to stomp over the current animation time - which, unsurprisingly, | ||||
// introduces artefacts in the currently playing animation. Save it here and restore it | // introduces artefacts in the currently playing animation. Save it here and restore it | ||||
// at the end. | // at the end. | ||||
float AnimTime = m_AnimTime; | float AnimTime = m_AnimTime; | ||||
// iterate through every frame of the animation | // iterate through every frame of the animation | ||||
for (size_t j=0;j<anim->GetNumFrames();j++) { | for (size_t j=0;j<anim->GetNumFrames();j++) { | ||||
m_PositionValid = false; | m_PositionValid = false; | ||||
ValidatePosition(); | ValidatePositionRec(); | ||||
// extend bounds by vertex positions at the frame | // extend bounds by vertex positions at the frame | ||||
for (size_t i=0;i<numverts;i++) | for (size_t i=0;i<numverts;i++) | ||||
{ | { | ||||
result += CModelDef::SkinPoint(verts[i], GetAnimatedBoneMatrices()); | result += CModelDef::SkinPoint(verts[i], GetAnimatedBoneMatrices()); | ||||
} | } | ||||
// advance to next frame | // advance to next frame | ||||
m_AnimTime += anim->GetFrameTime(); | m_AnimTime += anim->GetFrameTime(); | ||||
} | } | ||||
m_PositionValid = false; | m_PositionValid = false; | ||||
m_Parent = oldparent; | m_Parent = oldparent; | ||||
SetTransform(oldtransform); | SetTransform(oldtransform); | ||||
m_AnimTime = AnimTime; | m_AnimTime = AnimTime; | ||||
} | } | ||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////// | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
const CBoundingBoxAligned CModel::GetWorldBoundsRec() | |||||
{ | |||||
CBoundingBoxAligned bounds = GetWorldBounds(); | |||||
for (size_t i = 0; i < m_Props.size(); ++i) | |||||
bounds += m_Props[i].m_Model->GetWorldBoundsRec(); | |||||
return bounds; | |||||
} | |||||
const CBoundingBoxAligned CModel::GetObjectSelectionBoundsRec() | |||||
{ | |||||
CBoundingBoxAligned objBounds = GetObjectBounds(); // updates the (children-not-included) object-space bounds if necessary | |||||
// now extend these bounds to include the props' selection bounds (if any) | |||||
for (size_t i = 0; i < m_Props.size(); ++i) | |||||
{ | |||||
const Prop& prop = m_Props[i]; | |||||
if (prop.m_Hidden || !prop.m_Selectable) | |||||
continue; // prop is hidden from rendering, so it also shouldn't be used for selection | |||||
CBoundingBoxAligned propSelectionBounds = prop.m_Model->GetObjectSelectionBoundsRec(); | |||||
if (propSelectionBounds.IsEmpty()) | |||||
continue; // submodel does not wish to participate in selection box, exclude it | |||||
// We have the prop's bounds in its own object-space; now we need to transform them so they can be properly added | |||||
// to the bounds in our object-space. For that, we need the transform of the prop attachment point. | |||||
// | |||||
// We have the prop point information; however, it's not trivial to compute its exact location in our object-space | |||||
// since it may or may not be attached to a bone (see SPropPoint), which in turn may or may not be in the middle of | |||||
// an animation. The bone matrices might be of interest, but they're really only meant to be used for the animation | |||||
// system and are quite opaque to use from the outside (see @ref ValidatePosition). | |||||
// | |||||
// However, a nice side effect of ValidatePosition is that it also computes the absolute world-space transform of | |||||
// our props and sets it on their respective models. In particular, @ref ValidatePosition will compute the prop's | |||||
// world-space transform as either | |||||
// | |||||
// T' = T x B x O | |||||
// or | |||||
// T' = T x O | |||||
// | |||||
// where T' is the prop's world-space transform, T is our world-space transform, O is the prop's local | |||||
// offset/rotation matrix, and B is an optional transformation matrix of the bone the prop is attached to | |||||
// (taking into account animation and everything). | |||||
// | |||||
// From this, it is clear that either O or B x O is the object-space transformation matrix of the prop. So, | |||||
// all we need to do is apply our own inverse world-transform T^(-1) to T' to get our desired result. Luckily, | |||||
// this is precomputed upon setting the transform matrix (see @ref SetTransform), so it is free to fetch. | |||||
CMatrix3D propObjectTransform = prop.m_Model->GetTransform(); // T' | |||||
propObjectTransform.Concatenate(GetInvTransform()); // T^(-1) x T' | |||||
// Transform the prop's bounds into our object coordinate space | |||||
CBoundingBoxAligned transformedPropSelectionBounds; | |||||
propSelectionBounds.Transform(propObjectTransform, transformedPropSelectionBounds); | |||||
objBounds += transformedPropSelectionBounds; | |||||
} | |||||
return objBounds; | |||||
} | |||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
// BuildAnimation: load raw animation frame animation from given file, and build a | // BuildAnimation: load raw animation frame animation from given file, and build a | ||||
// animation specific to this model | // animation specific to this model | ||||
CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, const CStr& ID, int frequency, float speed, float actionpos, float actionpos2, float soundpos) | CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, const CStr& ID, int frequency, float speed, float actionpos, float actionpos2, float soundpos) | ||||
{ | { | ||||
CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname); | CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname); | ||||
if (!def) | if (!def) | ||||
return NULL; | return NULL; | ||||
Show All 34 Lines | void CModel::UpdateTo(float time) | ||||
// and also assures the world space, inverted bone matrices (required for normal | // and also assures the world space, inverted bone matrices (required for normal | ||||
// skinning) are up to date with respect to m_Transform | // skinning) are up to date with respect to m_Transform | ||||
m_AnimTime = time; | m_AnimTime = time; | ||||
// mark vertices as dirty | // mark vertices as dirty | ||||
SetDirty(RENDERDATA_UPDATE_VERTICES); | SetDirty(RENDERDATA_UPDATE_VERTICES); | ||||
// mark matrices as dirty | // mark matrices as dirty | ||||
InvalidatePosition(); | InvalidatePositionRec(); | ||||
} | |||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
// InvalidatePosition | |||||
void CModel::InvalidatePosition() | |||||
{ | |||||
m_PositionValid = false; | |||||
for (size_t i = 0; i < m_Props.size(); ++i) | |||||
m_Props[i].m_Model->InvalidatePosition(); | |||||
} | } | ||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////// | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
// ValidatePosition: ensure that current transform and bone matrices are both uptodate | // ValidatePosition: ensure that current transform and bone matrices are both uptodate | ||||
void CModel::ValidatePosition() | void CModel::ValidatePosition() | ||||
{ | { | ||||
if (m_PositionValid) | if (m_PositionValid) | ||||
{ | { | ||||
ENSURE(!m_Parent || m_Parent->m_PositionValid); | ENSURE(!m_Parent || m_Parent->m_PositionValid); | ||||
return; | return; | ||||
} | } | ||||
if (m_Parent && !m_Parent->m_PositionValid) | |||||
{ | |||||
// Make sure we don't base our calculations on | // Make sure we don't base our calculations on | ||||
// a parent animation state that is out of date. | // a parent animation state that is out of date. | ||||
// Don't update recursively, we only care about the parent. | |||||
if (m_Parent) | |||||
{ | |||||
if (!m_Parent->m_PositionValid) | |||||
m_Parent->ValidatePosition(); | m_Parent->ValidatePosition(); | ||||
ENSURE(m_Parent->m_PositionValid); | |||||
// Parent will recursively call our validation. | SetTransform(m_Parent->GetTransform() * m_ParentRelativeTransform); | ||||
ENSURE(m_PositionValid); | |||||
return; | |||||
} | } | ||||
if (m_Anim && m_BoneMatrices) | if (m_Anim && m_BoneMatrices) | ||||
{ | { | ||||
// PROFILE( "generating bone matrices" ); | |||||
ENSURE(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys()); | ENSURE(m_pModelDef->GetNumBones() == m_Anim->m_AnimDef->GetNumKeys()); | ||||
m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION)); | m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION)); | ||||
} | } | ||||
else if (m_BoneMatrices) | else if (m_BoneMatrices) | ||||
{ | { | ||||
// Bones but no animation - probably a buggy actor forgot to set up the animation, | // Bones but no animation - probably a buggy actor forgot to set up the animation, | ||||
// so just render it in its bind pose | // so just render it in its bind pose | ||||
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) | for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) | ||||
{ | { | ||||
m_BoneMatrices[i].SetIdentity(); | m_BoneMatrices[i].SetIdentity(); | ||||
m_BoneMatrices[i].Rotate(m_pModelDef->GetBones()[i].m_Rotation); | m_BoneMatrices[i].Rotate(m_pModelDef->GetBones()[i].m_Rotation); | ||||
m_BoneMatrices[i].Translate(m_pModelDef->GetBones()[i].m_Translation); | m_BoneMatrices[i].Translate(m_pModelDef->GetBones()[i].m_Translation); | ||||
} | } | ||||
} | } | ||||
// For CPU skinning, we precompute as much as possible so that the only | // Our transform is up to date, bone matrices are ready: | ||||
// per-vertex work is a single matrix*vec multiplication. | // time to update our prop matrices. | ||||
// For GPU skinning, we try to minimise CPU work by doing most computation | |||||
// in the vertex shader instead. | |||||
// Using g_RenderingOptions to detect CPU vs GPU is a bit hacky, | |||||
// and this doesn't allow the setting to change at runtime, but there isn't | |||||
// an obvious cleaner way to determine what data needs to be computed, | |||||
// and GPU skinning is a rarely-used experimental feature anyway. | |||||
bool worldSpaceBoneMatrices = !g_RenderingOptions.GetGPUSkinning(); | |||||
bool computeBlendMatrices = !g_RenderingOptions.GetGPUSkinning(); | |||||
if (m_BoneMatrices && worldSpaceBoneMatrices) | |||||
{ | |||||
// add world-space transformation to m_BoneMatrices | |||||
const CMatrix3D transform = GetTransform(); | |||||
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) | |||||
m_BoneMatrices[i].Concatenate(transform); | |||||
} | |||||
// our own position is now valid; now we can safely update our props' positions without fearing | |||||
// that doing so will cause a revalidation of this model (see recursion above). | |||||
m_PositionValid = true; | |||||
CMatrix3D translate; | CMatrix3D translate; | ||||
CVector3D objTranslation = m_Transform.GetTranslation(); | CVector3D objTranslation = m_Transform.GetTranslation(); | ||||
float objectHeight = 0.0f; | float objectHeight = 0.0f; | ||||
CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY); | CmpPtr<ICmpTerrain> cmpTerrain(m_Simulation, SYSTEM_ENTITY); | ||||
if (cmpTerrain) | if (cmpTerrain) | ||||
objectHeight = cmpTerrain->GetExactGroundLevel(objTranslation.X, objTranslation.Z); | objectHeight = cmpTerrain->GetExactGroundLevel(objTranslation.X, objTranslation.Z); | ||||
// Object height is incorrect for floating objects. We use water height instead. | // Object height is incorrect for floating objects. We use water height instead. | ||||
CmpPtr<ICmpWaterManager> cmpWaterManager(m_Simulation, SYSTEM_ENTITY); | CmpPtr<ICmpWaterManager> cmpWaterManager(m_Simulation, SYSTEM_ENTITY); | ||||
if (cmpWaterManager) | if (cmpWaterManager) | ||||
{ | { | ||||
float waterHeight = cmpWaterManager->GetExactWaterLevel(objTranslation.X, objTranslation.Z); | float waterHeight = cmpWaterManager->GetExactWaterLevel(objTranslation.X, objTranslation.Z); | ||||
if (waterHeight >= objectHeight && m_Flags & MODELFLAG_FLOATONWATER) | if (waterHeight >= objectHeight && m_Flags & MODELFLAG_FLOATONWATER) | ||||
objectHeight = waterHeight; | objectHeight = waterHeight; | ||||
} | } | ||||
// re-position and validate all props | for (size_t i = 0; i < m_Props.size(); ++i) | ||||
for (const Prop& prop : m_Props) | |||||
{ | { | ||||
CMatrix3D proptransform = prop.m_Point->m_Transform; | const Prop& prop = m_Props[i]; | ||||
CMatrix3D propMatrix = prop.m_Point->m_Transform; | |||||
if (prop.m_Point->m_BoneIndex != 0xff) | if (prop.m_Point->m_BoneIndex != 0xff) | ||||
{ | propMatrix.Concatenate(m_BoneMatrices[prop.m_Point->m_BoneIndex]); | ||||
CMatrix3D boneMatrix = m_BoneMatrices[prop.m_Point->m_BoneIndex]; | |||||
if (!worldSpaceBoneMatrices) | |||||
boneMatrix.Concatenate(GetTransform()); | |||||
proptransform.Concatenate(boneMatrix); | |||||
} | |||||
else | |||||
{ | |||||
// not relative to any bone; just apply world-space transformation (i.e. relative to object-space origin) | |||||
proptransform.Concatenate(m_Transform); | |||||
} | |||||
// Adjust prop height to terrain level when needed | // Adjust prop height to terrain level when needed | ||||
if (cmpTerrain && (prop.m_MaxHeight != 0.f || prop.m_MinHeight != 0.f)) | if (cmpTerrain && (prop.m_MaxHeight != 0.f || prop.m_MinHeight != 0.f)) | ||||
{ | { | ||||
const CVector3D& propTranslation = proptransform.GetTranslation(); | const CVector3D& propTranslation = propMatrix.GetTranslation(); | ||||
const float propTerrain = cmpTerrain->GetExactGroundLevel(propTranslation.X, propTranslation.Z); | const float propTerrain = cmpTerrain->GetExactGroundLevel(propTranslation.X, propTranslation.Z); | ||||
const float translateHeight = std::min(prop.m_MaxHeight, std::max(prop.m_MinHeight, propTerrain - objectHeight)); | const float translateHeight = std::min(prop.m_MaxHeight, std::max(prop.m_MinHeight, propTerrain - objectHeight)); | ||||
translate.SetTranslation(0.f, translateHeight, 0.f); | translate.SetTranslation(0.f, translateHeight, 0.f); | ||||
proptransform.Concatenate(translate); | propMatrix.Concatenate(translate); | ||||
} | } | ||||
prop.m_Model->SetTransform(proptransform); | prop.m_Model->SetParentRelativeTransform(propMatrix); | ||||
prop.m_Model->ValidatePosition(); | |||||
} | } | ||||
if (m_BoneMatrices) | |||||
// For CPU skinning, we precompute as much as possible so that the only | |||||
// per-vertex work is a single matrix*vec multiplication. | |||||
// For GPU skinning, we try to minimise CPU work by doing most computation | |||||
// in the vertex shader instead. | |||||
// Using g_RenderingOptions to detect CPU vs GPU is a bit hacky, | |||||
// and this doesn't allow the setting to change at runtime, but there isn't | |||||
// an obvious cleaner way to determine what data needs to be computed, | |||||
// and GPU skinning is a rarely-used experimental feature anyway. | |||||
bool worldSpaceBoneMatrices = !g_RenderingOptions.GetGPUSkinning(); | |||||
bool computeBlendMatrices = !g_RenderingOptions.GetGPUSkinning(); | |||||
if (m_BoneMatrices && worldSpaceBoneMatrices) | |||||
{ | { | ||||
// add world-space transformation to m_BoneMatrices | |||||
const CMatrix3D transform = GetTransform(); | |||||
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) | for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) | ||||
m_BoneMatrices[i].Concatenate(transform); | |||||
} | |||||
// Vertices are in world space, joint transforms expect joint-local data, so we'll need | |||||
// to transform the world space into joint-local space first. Thus we apply the 'inverse bind bone matrix'. | |||||
if (m_BoneMatrices) | |||||
{ | { | ||||
for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++) | |||||
m_BoneMatrices[i] = m_BoneMatrices[i] * m_pModelDef->GetInverseBindBoneMatrices()[i]; | m_BoneMatrices[i] = m_BoneMatrices[i] * m_pModelDef->GetInverseBindBoneMatrices()[i]; | ||||
} | |||||
// Note: there is a special case of joint influence, in which the vertex | // Note: there is a special case of joint influence, in which the vertex | ||||
// is influenced by the bind-shape transform instead of a particular bone, | // is influenced by the bind-shape transform instead of a particular bone, | ||||
// which we indicate with the blending bone ID set to the total number | // which we indicate with the blending bone ID set to the total number | ||||
// of bones. But since we're skinning in world space, we use the model's | // of bones. But since we're skinning in world space, we use the model's | ||||
// world space transform and store that matrix in this special index. | // world space transform and store that matrix in this special index. | ||||
// (see http://trac.wildfiregames.com/ticket/1012) | // (see http://trac.wildfiregames.com/ticket/1012) | ||||
m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform; | m_BoneMatrices[m_pModelDef->GetNumBones()] = m_Transform; | ||||
if (computeBlendMatrices) | if (computeBlendMatrices) | ||||
m_pModelDef->BlendBoneMatrices(m_BoneMatrices); | m_pModelDef->BlendBoneMatrices(m_BoneMatrices); | ||||
} | } | ||||
} | |||||
m_PositionValid = true; | |||||
} | |||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////// | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
// SetAnimation: set the given animation as the current animation on this model; | // SetAnimation: set the given animation as the current animation on this model; | ||||
// return false on error, else true | // return false on error, else true | ||||
bool CModel::SetAnimation(CSkeletonAnim* anim, bool once) | bool CModel::SetAnimation(CSkeletonAnim* anim, bool once) | ||||
{ | { | ||||
m_Anim = nullptr; // in case something fails | m_Anim = nullptr; // in case something fails | ||||
Show All 37 Lines | |||||
{ | { | ||||
m_Anim = source->m_Anim; | m_Anim = source->m_Anim; | ||||
m_AnimTime = source->m_AnimTime; | m_AnimTime = source->m_AnimTime; | ||||
m_ObjectBounds.SetEmpty(); | m_ObjectBounds.SetEmpty(); | ||||
InvalidateBounds(); | InvalidateBounds(); | ||||
} | } | ||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
// AddProp: add a prop to the model on the given point | |||||
void CModel::AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry, float minHeight, float maxHeight, bool selectable) | |||||
{ | |||||
// position model according to prop point position | |||||
// this next call will invalidate the bounds of "model", which will in turn also invalidate the selection box | |||||
model->SetTransform(point->m_Transform); | |||||
model->m_Parent = this; | |||||
Prop prop; | |||||
prop.m_Point = point; | |||||
prop.m_Model = model; | |||||
prop.m_ObjectEntry = objectentry; | |||||
prop.m_MinHeight = minHeight; | |||||
prop.m_MaxHeight = maxHeight; | |||||
prop.m_Selectable = selectable; | |||||
m_Props.push_back(prop); | |||||
} | |||||
void CModel::AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry) | void CModel::AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry) | ||||
{ | { | ||||
AddProp(point, model, objectentry); | AddProp(point, model, objectentry); | ||||
m_AmmoPropPoint = point; | m_AmmoPropPoint = point; | ||||
m_AmmoLoadedProp = m_Props.size() - 1; | m_AmmoLoadedProp = m_Props.size() - 1; | ||||
m_Props[m_AmmoLoadedProp].m_Hidden = true; | m_Props[m_AmmoLoadedProp].m_Hidden = true; | ||||
// we only need to invalidate the selection box here if it is based on props and their visibilities | // we only need to invalidate the selection box here if it is based on props and their visibilities | ||||
▲ Show 20 Lines • Show All 75 Lines • ▼ Show 20 Lines | |||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////// | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// | ||||
// SetTransform: set the transform on this object, and reorientate props accordingly | // SetTransform: set the transform on this object, and reorientate props accordingly | ||||
void CModel::SetTransform(const CMatrix3D& transform) | void CModel::SetTransform(const CMatrix3D& transform) | ||||
{ | { | ||||
// call base class to set transform on this object | // call base class to set transform on this object | ||||
CRenderableObject::SetTransform(transform); | CRenderableObject::SetTransform(transform); | ||||
InvalidatePosition(); | InvalidatePositionRec(); | ||||
} | } | ||||
////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////// | ||||
void CModel::AddFlagsRec(int flags) | void CModel::AddFlagsRec(int flags) | ||||
{ | { | ||||
m_Flags |= flags; | m_Flags |= flags; | ||||
▲ Show 20 Lines • Show All 47 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator