Index: ps/trunk/source/graphics/Model.cpp =================================================================== --- ps/trunk/source/graphics/Model.cpp +++ ps/trunk/source/graphics/Model.cpp @@ -130,66 +130,16 @@ // CalcObjectBounds: calculate object space bounds of this model, based solely on vertex positions void CModel::CalcStaticObjectBounds() { - m_ObjectBounds.SetEmpty(); - - size_t numverts=m_pModelDef->GetNumVertices(); - SModelVertex* verts=m_pModelDef->GetVertices(); - - for (size_t i=0;iGetMaxBounds(nullptr, !(m_Flags & MODELFLAG_NOLOOPANIMATION), m_ObjectBounds); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // CalcAnimatedObjectBound: calculate bounds encompassing all vertex positions for given animation void CModel::CalcAnimatedObjectBounds(CSkeletonAnimDef* anim, CBoundingBoxAligned& 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(); - CModelAbstract* 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;jGetNumFrames();j++) { - m_PositionValid = false; - ValidatePosition(); - - // extend bounds by vertex positions at the frame - for (size_t i=0;iGetFrameTime(); - } - - m_PositionValid = false; - m_Parent = oldparent; - SetTransform(oldtransform); - m_AnimTime = AnimTime; + PROFILE2("CalcAnimatedObjectBounds"); + m_pModelDef->GetMaxBounds(anim, !(m_Flags & MODELFLAG_NOLOOPANIMATION), result); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// Index: ps/trunk/source/graphics/ModelDef.h =================================================================== --- ps/trunk/source/graphics/ModelDef.h +++ ps/trunk/source/graphics/ModelDef.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* 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 @@ -23,16 +23,20 @@ #define INCLUDED_MODELDEF #include "ps/CStr.h" +#include "maths/BoundingBoxAligned.h" #include "maths/Matrix3D.h" #include "maths/Vector2D.h" #include "maths/Vector3D.h" #include "maths/Quaternion.h" #include "lib/file/vfs/vfs_path.h" #include "renderer/VertexArray.h" -#include + #include +#include +#include class CBoneState; +class CSkeletonAnimDef; /** * Describes the position of a prop point within its parent model. A prop point is the location within a parent model @@ -189,6 +193,11 @@ const SPropPoint* FindPropPoint(const char* name) const; /** + * @param anim may be null + */ + void GetMaxBounds(CSkeletonAnimDef* anim, bool loop, CBoundingBoxAligned& result); + + /** * Transform the given vertex's position from the bind pose into the new pose. * * @return new world-space vertex coordinates @@ -266,6 +275,9 @@ private: VfsPath m_Name; // filename + // Maximal bounding box of this mesh for a given animation. + std::unordered_map m_MaxBoundsPerAnimDef; + // renderdata shared by models of the same modeldef, // by render path typedef std::map RenderDataMap; Index: ps/trunk/source/graphics/ModelDef.cpp =================================================================== --- ps/trunk/source/graphics/ModelDef.cpp +++ ps/trunk/source/graphics/ModelDef.cpp @@ -31,6 +31,37 @@ # 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); + 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; + return; + } + ENSURE(anim->m_UID != 0); + 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); + for (size_t i = 0; i < numverts; ++i) + result += SkinPoint(verts[i], boneMatrix.data()); + } + m_MaxBoundsPerAnimDef[anim->m_UID] = result; +} + CVector3D CModelDef::SkinPoint(const SModelVertex& vtx, const CMatrix3D newPoseMatrices[]) { Index: ps/trunk/source/graphics/SkeletonAnimDef.h =================================================================== --- ps/trunk/source/graphics/SkeletonAnimDef.h +++ ps/trunk/source/graphics/SkeletonAnimDef.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -26,6 +26,9 @@ #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 @@ -78,8 +81,8 @@ void BuildBoneMatrices(float time, CMatrix3D* matrices, bool loop) const; // anim I/O functions - static CSkeletonAnimDef* Load(const VfsPath& filename); - static void Save(const VfsPath& pathname, const CSkeletonAnimDef* anim); + 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 @@ -89,7 +92,11 @@ // number of frames in the animation size_t m_NumFrames; // animation data - m_NumKeys*m_NumFrames total keys - Key* m_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 Index: ps/trunk/source/graphics/SkeletonAnimDef.cpp =================================================================== --- ps/trunk/source/graphics/SkeletonAnimDef.cpp +++ ps/trunk/source/graphics/SkeletonAnimDef.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 @@ -25,20 +25,30 @@ #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "ps/CStr.h" +#include "ps/CLogger.h" #include "ps/FileIo.h" +// Start IDs at 1 to leave 0 as a special value. +u32 CSkeletonAnimDef::nextUID = 1; /////////////////////////////////////////////////////////////////////////////////////////// // CSkeletonAnimDef constructor -CSkeletonAnimDef::CSkeletonAnimDef() : m_FrameTime(0), m_NumKeys(0), m_NumFrames(0), m_Keys(0) +CSkeletonAnimDef::CSkeletonAnimDef() : m_FrameTime(0), m_NumKeys(0), m_NumFrames(0) { + m_UID = nextUID++; + // Log a warning if we ever overflow. Should that not result from a bug, bumping to u64 ought to suffice. + if (nextUID == 0) + { + // Reset to 1. + nextUID++; + LOGWARNING("CSkeletonAnimDef unique ID overflowed to 0 - model-animation bounds may be incorrect."); + } } /////////////////////////////////////////////////////////////////////////////////////////// // CSkeletonAnimDef destructor CSkeletonAnimDef::~CSkeletonAnimDef() { - delete[] m_Keys; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -89,7 +99,7 @@ /////////////////////////////////////////////////////////////////////////////////////////// // Load: try to load the anim from given file; return a new anim if successful -CSkeletonAnimDef* CSkeletonAnimDef::Load(const VfsPath& filename) +std::unique_ptr CSkeletonAnimDef::Load(const VfsPath& filename) { CFileUnpacker unpacker; unpacker.Read(filename,"PSSA"); @@ -100,17 +110,17 @@ } // unpack the data - CSkeletonAnimDef* anim=new CSkeletonAnimDef; + auto anim = std::make_unique(); 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=new Key[anim->m_NumKeys*anim->m_NumFrames]; - unpacker.UnpackRaw(anim->m_Keys,anim->m_NumKeys*anim->m_NumFrames*sizeof(Key)); + 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&) { - delete anim; + anim.reset(); throw; } @@ -119,18 +129,18 @@ /////////////////////////////////////////////////////////////////////////////////////////// // Save: try to save anim to file -void CSkeletonAnimDef::Save(const VfsPath& pathname,const CSkeletonAnimDef* anim) +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.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; + const size_t numFrames = anim.m_NumFrames; packer.PackSize(numFrames); - packer.PackRaw(anim->m_Keys,numKeys*numFrames*sizeof(Key)); + 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/SkeletonAnimManager.h =================================================================== --- ps/trunk/source/graphics/SkeletonAnimManager.h +++ ps/trunk/source/graphics/SkeletonAnimManager.h @@ -24,6 +24,7 @@ #include "lib/file/vfs/vfs_path.h" +#include #include class CColladaManager; @@ -47,7 +48,7 @@ private: // map of all known animations. Value is NULL if it failed to load. - std::unordered_map m_Animations; + std::unordered_map> m_Animations; CColladaManager& m_ColladaManager; }; Index: ps/trunk/source/graphics/SkeletonAnimManager.cpp =================================================================== --- ps/trunk/source/graphics/SkeletonAnimManager.cpp +++ ps/trunk/source/graphics/SkeletonAnimManager.cpp @@ -40,9 +40,6 @@ // CSkeletonAnimManager destructor CSkeletonAnimManager::~CSkeletonAnimManager() { - using Iter = std::unordered_map::iterator; - for (Iter i = m_Animations.begin(); i != m_Animations.end(); ++i) - delete i->second; } /////////////////////////////////////////////////////////////////////////////// @@ -53,22 +50,18 @@ VfsPath name = pathname.ChangeExtension(L""); // Find if it's already been loaded - std::unordered_map::iterator iter = m_Animations.find(name); + std::unordered_map>::iterator iter = m_Animations.find(name); if (iter != m_Animations.end()) - return iter->second; + return iter->second.get(); - CSkeletonAnimDef* def = NULL; + std::unique_ptr def; // Find the file to load VfsPath psaFilename = m_ColladaManager.GetLoadablePath(name, CColladaManager::PSA); if (psaFilename.empty()) - { LOGERROR("Could not load animation '%s'", pathname.string8()); - def = NULL; - } else - { try { def = CSkeletonAnimDef::Load(psaFilename); @@ -77,14 +70,12 @@ { LOGERROR("Could not load animation '%s'", psaFilename.string8()); } - } if (def) LOGMESSAGE("CSkeletonAnimManager::GetAnimation(%s): Loaded successfully", pathname.string8()); else LOGERROR("CSkeletonAnimManager::GetAnimation(%s): Failed loading, marked file as bad", pathname.string8()); - // Add to map - m_Animations[name] = def; // NULL if failed to load - we won't try loading it again - return def; + // Add to map, NULL if failed to load - we won't try loading it again + return m_Animations.insert_or_assign(name, std::move(def)).first->second.get(); }