Index: source/graphics/Model.h =================================================================== --- source/graphics/Model.h +++ source/graphics/Model.h @@ -31,10 +31,10 @@ struct SPropPoint; class CObjectEntry; -class CSkeletonAnim; class CSkeletonAnimDef; class CSkeletonAnimManager; class CSimulation2; +struct SkeletonAnim; #define MODELFLAG_CASTSHADOWS (1<<0) #define MODELFLAG_NOLOOPANIMATION (1<<1) @@ -107,10 +107,10 @@ CMaterial& GetMaterial() { return m_Material; } // set the given animation as the current animation on this model - bool SetAnimation(CSkeletonAnim* anim, bool once = false); + bool SetAnimation(SkeletonAnim* anim, bool once = false); // get the currently playing animation, if any - CSkeletonAnim* GetAnimation() const { return m_Anim; } + SkeletonAnim* GetAnimation() const { return m_Anim; } // set the animation state to be the same as from another; both models should // be compatible types (same type of skeleton) @@ -196,21 +196,6 @@ return m_BoneMatrices; } - /** - * Load raw animation frame animation from given file, and build an - * animation specific to this model. - * @param pathname animation file to load - * @param name animation name (e.g. "idle") - * @param ID specific ID of the animation, to sync with props - * @param frequency influences the random choices - * @param speed animation speed as a factor of the default animation speed - * @param actionpos offset of 'action' event, in range [0, 1] - * @param actionpos2 offset of 'action2' event, in range [0, 1] - * @param sound offset of 'sound' event, in range [0, 1] - * @return new animation, or NULL on error - */ - CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, const CStr& ID, int frequency, float speed, float actionpos, float actionpos2, float soundpos); - /** * Add a prop to the model on the given point. */ @@ -274,7 +259,7 @@ // updates it when necessary. CBoundingBoxAligned m_ObjectBounds; // animation currently playing on this model, if any - CSkeletonAnim* m_Anim; + SkeletonAnim* m_Anim; // time (in MS) into the current animation float m_AnimTime; Index: source/graphics/Model.cpp =================================================================== --- source/graphics/Model.cpp +++ source/graphics/Model.cpp @@ -113,10 +113,13 @@ } else { - if (m_Anim->m_ObjectBounds.IsEmpty()) - CalcAnimatedObjectBounds(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds); - ENSURE(! m_Anim->m_ObjectBounds.IsEmpty()); // (if this happens, it'll be recalculating the bounds every time) - m_ObjectBounds = m_Anim->m_ObjectBounds; + if (!m_Anim->m_ObjectBounds) + { + m_Anim->m_ObjectBounds = std::make_unique(); + CalcAnimatedObjectBounds(m_Anim->m_AnimDef, *m_Anim->m_ObjectBounds); + } + ENSURE(!m_Anim->m_ObjectBounds->IsEmpty()); // (if this happens, it'll be recalculating the bounds every time) + m_ObjectBounds = *m_Anim->m_ObjectBounds; } // Ensure the transform is set correctly before we use it @@ -144,12 +147,15 @@ // CalcAnimatedObjectBound: calculate bounds encompassing all vertex positions for given animation void CModel::CalcAnimatedObjectBounds(CSkeletonAnimDef* anim, CBoundingBoxAligned& result) { + PROFILE2("CalcAnimatedObjectBounds"); + PROFILE2_ATTR("Tex %s", anim->m_Pathname.c_str()); + LOGWARNING("calculating bounds for %s %s", m_pModelDef->GetName().string8().c_str(), anim->m_Pathname.c_str()); result.SetEmpty(); // Set the current animation on which to perform calculations (if it's necessary) if (anim != m_Anim->m_AnimDef) { - CSkeletonAnim dummyanim; + SkeletonAnim dummyanim; dummyanim.m_AnimDef=anim; if (!SetAnimation(&dummyanim)) return; } @@ -253,43 +259,6 @@ return objBounds; } -///////////////////////////////////////////////////////////////////////////////////////////////////////////// -// BuildAnimation: load raw animation frame animation from given file, and build a -// 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) -{ - CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname); - if (!def) - return NULL; - - CSkeletonAnim* anim = new CSkeletonAnim(); - anim->m_Name = name; - anim->m_ID = ID; - anim->m_Frequency = frequency; - anim->m_AnimDef = def; - anim->m_Speed = speed; - - if (actionpos == -1.f) - anim->m_ActionPos = -1.f; - else - anim->m_ActionPos = actionpos * anim->m_AnimDef->GetDuration(); - - if (actionpos2 == -1.f) - anim->m_ActionPos2 = -1.f; - else - anim->m_ActionPos2 = actionpos2 * anim->m_AnimDef->GetDuration(); - - if (soundpos == -1.f) - anim->m_SoundPos = -1.f; - else - anim->m_SoundPos = soundpos * anim->m_AnimDef->GetDuration(); - - anim->m_ObjectBounds.SetEmpty(); - InvalidateBounds(); - - return anim; -} - ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // Update: update this model to the given time, in msec void CModel::UpdateTo(float time) @@ -454,7 +423,7 @@ ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // SetAnimation: set the given animation as the current animation on this model; // return false on error, else true -bool CModel::SetAnimation(CSkeletonAnim* anim, bool once) +bool CModel::SetAnimation(SkeletonAnim* anim, bool once) { m_Anim = nullptr; // in case something fails Index: source/graphics/ObjectBase.h =================================================================== --- source/graphics/ObjectBase.h +++ source/graphics/ObjectBase.h @@ -19,14 +19,15 @@ #define INCLUDED_OBJECTBASE #include "lib/file/vfs/vfs_path.h" +#include "graphics/SkeletonAnim.h" #include "ps/CStr.h" #include "ps/CStrIntern.h" class CModel; class CObjectManager; -class CSkeletonAnim; class CXeromyces; class XMBElement; +struct SkeletonAnim; #include #include @@ -38,26 +39,6 @@ { NONCOPYABLE(CObjectBase); public: - struct Anim - { - // constructor - Anim() : m_Frequency(0), m_Speed(1.f), m_ActionPos(-1.f), m_ActionPos2(-1.f), m_SoundPos(-1.f) {} - // name of the animation - "Idle", "Run", etc - CStr m_AnimName; - // ID of the animation: if not empty, something specific to sync with props. - CStr m_ID = ""; - int m_Frequency; - // filename of the animation - manidle.psa, manrun.psa, etc - VfsPath m_FileName; - // animation speed, as specified in XML actor file - float m_Speed; - // fraction [0.0, 1.0] of the way through the animation that the interesting bit(s) - // happens, or -1.0 if unspecified - float m_ActionPos; - float m_ActionPos2; - float m_SoundPos; - }; - struct Prop { // constructor @@ -102,7 +83,7 @@ VfsPath m_Particles; CStr m_Color; - std::vector m_Anims; + std::vector> m_Anims; std::vector m_Props; std::vector m_Samplers; }; @@ -114,7 +95,7 @@ VfsPath particles; CStr color; std::multimap props; - std::multimap anims; + std::multimap> anims; std::multimap samplers; }; Index: source/graphics/ObjectBase.cpp =================================================================== --- source/graphics/ObjectBase.cpp +++ source/graphics/ObjectBase.cpp @@ -23,11 +23,14 @@ #include "ObjectBase.h" #include "ObjectManager.h" +#include "graphics/SkeletonAnimDef.h" +#include "graphics/SkeletonAnimManager.h" +#include "lib/timer.h" +#include "maths/MathUtil.h" +#include "maths/BoundingBoxAligned.h" #include "ps/XML/Xeromyces.h" #include "ps/Filesystem.h" #include "ps/CLogger.h" -#include "lib/timer.h" -#include "maths/MathUtil.h" #include @@ -164,27 +167,43 @@ { ENSURE(anim_element.GetNodeName() == el_animation); - Anim anim; + std::shared_ptr anim = std::make_shared(); XERO_ITER_ATTR(anim_element, ae) { if (ae.Name == at_name) - anim.m_AnimName = ae.Value; + anim->m_Name = ae.Value; else if (ae.Name == at_id) - anim.m_ID = ae.Value; + anim->m_ID = ae.Value; else if (ae.Name == at_frequency) - anim.m_Frequency = ae.Value.ToInt(); + anim->m_Frequency = ae.Value.ToInt(); else if (ae.Name == at_file) - anim.m_FileName = VfsPath("art/animation") / ae.Value.FromUTF8(); + anim->m_Pathame = "art/animation/" + ae.Value; else if (ae.Name == at_speed) - anim.m_Speed = ae.Value.ToInt() > 0 ? ae.Value.ToInt() / 100.f : 1.f; + anim->m_Speed = ae.Value.ToInt() > 0 ? ae.Value.ToInt() / 100.f : 1.f; else if (ae.Name == at_event) - anim.m_ActionPos = Clamp(ae.Value.ToFloat(), 0.f, 1.f); + anim->m_ActionPos = Clamp(ae.Value.ToFloat(), 0.f, 1.f); else if (ae.Name == at_load) - anim.m_ActionPos2 = Clamp(ae.Value.ToFloat(), 0.f, 1.f); + anim->m_ActionPos2 = Clamp(ae.Value.ToFloat(), 0.f, 1.f); else if (ae.Name == at_sound) - anim.m_SoundPos = Clamp(ae.Value.ToFloat(), 0.f, 1.f); + anim->m_SoundPos = Clamp(ae.Value.ToFloat(), 0.f, 1.f); + } + CSkeletonAnimDef* def = m_ObjectManager.GetSkeletonAnimManager().GetAnimation(VfsPath(anim->m_Pathame)); + if (!def) + LOGERROR("Could not open animation at %s (actor %s)", anim->m_Pathame.c_str(), m_ShortName.ToUTF8().c_str()); + else + { + anim->m_AnimDef = def; + if (anim->m_ActionPos != -1.f) + anim->m_ActionPos *= anim->m_AnimDef->GetDuration(); + + if (anim->m_ActionPos2 != -1.f) + anim->m_ActionPos2 *= anim->m_AnimDef->GetDuration(); + + if (anim->m_SoundPos != -1.f) + anim->m_SoundPos *= anim->m_AnimDef->GetDuration(); + + currentVariant.m_Anims.emplace_back(std::move(anim)); } - currentVariant.m_Anims.push_back(anim); } } else if (option_name == el_props) @@ -458,11 +477,11 @@ // Same idea applies for animations. // So, erase all existing animations which are overridden by this variant: - for (std::vector::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) - variation.anims.erase(it->m_AnimName); + for (std::vector>::const_iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) + variation.anims.erase((*it)->m_Name); // and then insert the new ones: - for (std::vector::iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) - variation.anims.insert(make_pair(it->m_AnimName, *it)); + for (std::vector>::const_iterator it = var.m_Anims.begin(); it != var.m_Anims.end(); ++it) + variation.anims.emplace((*it)->m_Name, *it); // Same for samplers, though perhaps not strictly necessary: for (std::vector::iterator it = var.m_Samplers.begin(); it != var.m_Samplers.end(); ++it) Index: source/graphics/ObjectEntry.h =================================================================== --- source/graphics/ObjectEntry.h +++ source/graphics/ObjectEntry.h @@ -19,7 +19,6 @@ #define INCLUDED_OBJECTENTRY class CModelAbstract; -class CSkeletonAnim; class CObjectBase; class CObjectManager; class CSimulation2; @@ -51,6 +50,11 @@ // different variations of the actor. CObjectBase* m_Base; + // TODO: something more memory-efficient than storing loads of similar strings for each unit? + // Note that those are pointers to ObjectBase-owned objects + // (and shared_ptr to avoid annoying memory handling on hotloading). + std::multimap> m_Animations; + // samplers list std::vector m_Samplers; // model name @@ -69,13 +73,13 @@ * with the frequencies as weights (if there are any defined). * This method should always return an animation */ - CSkeletonAnim* GetRandomAnimation(const CStr& animationName, const CStr& ID = "") const; + SkeletonAnim* GetRandomAnimation(const CStr& animationName, const CStr& ID = "") const; /** * Returns all the animations matching the given ID or animationName if ID is empty. * If none found returns Idle animations (which are always added) */ - std::vector GetAnimations(const CStr& animationName, const CStr& ID = "") const; + std::vector GetAnimations(const CStr& animationName, const CStr& ID = "") const; // corresponding model CModelAbstract* m_Model; @@ -85,12 +89,7 @@ bool m_Outdated; private: - CSimulation2& m_Simulation; - - typedef std::multimap SkeletonAnimMap; - SkeletonAnimMap m_Animations; - // TODO: something more memory-efficient than storing loads of similar strings for each unit? }; Index: source/graphics/ObjectEntry.cpp =================================================================== --- source/graphics/ObjectEntry.cpp +++ source/graphics/ObjectEntry.cpp @@ -28,7 +28,6 @@ #include "graphics/ObjectBase.h" #include "graphics/ObjectManager.h" #include "graphics/ParticleManager.h" -#include "graphics/SkeletonAnim.h" #include "graphics/TextureManager.h" #include "lib/rand.h" #include "ps/CLogger.h" @@ -46,9 +45,6 @@ CObjectEntry::~CObjectEntry() { - for (const std::pair& anim : m_Animations) - delete anim.second; - delete m_Model; } @@ -155,41 +151,22 @@ model->CalcStaticObjectBounds(); // load the animations - for (std::multimap::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it) - { - CStr name = it->first.LowerCase(); - - if (it->second.m_FileName.empty()) - continue; - CSkeletonAnim* anim = model->BuildAnimation( - it->second.m_FileName, - name, - it->second.m_ID, - it->second.m_Frequency, - it->second.m_Speed, - it->second.m_ActionPos, - it->second.m_ActionPos2, - it->second.m_SoundPos); - if (anim) - m_Animations.insert(std::make_pair(name, anim)); - } + for (std::multimap>::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it) + m_Animations.emplace(it->first.LowerCase(), it->second); // ensure there's always an idle animation if (m_Animations.find("idle") == m_Animations.end()) { - CSkeletonAnim* anim = new CSkeletonAnim(); + std::shared_ptr anim = std::make_shared(); anim->m_Name = "idle"; anim->m_ID = ""; anim->m_AnimDef = NULL; anim->m_Frequency = 0; anim->m_Speed = 0.f; - anim->m_ActionPos = 0.f; - anim->m_ActionPos2 = 0.f; - anim->m_SoundPos = 0.f; - m_Animations.insert(std::make_pair("idle", anim)); + m_Animations.emplace("idle", std::move(anim)); // Ignore errors, since they're probably saying this is a non-animated model - model->SetAnimation(anim); + model->SetAnimation(anim.get()); } else { @@ -260,19 +237,19 @@ return true; } -CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName, const CStr& ID) const +SkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName, const CStr& ID) const { - std::vector anims = GetAnimations(animationName, ID); + std::vector anims = GetAnimations(animationName, ID); int totalFreq = 0; - for (CSkeletonAnim* anim : anims) + for (SkeletonAnim* anim : anims) totalFreq += anim->m_Frequency; if (totalFreq == 0) return anims[rand(0, anims.size())]; int r = rand(0, totalFreq); - for (CSkeletonAnim* anim : anims) + for (SkeletonAnim* anim : anims) { r -= anim->m_Frequency; if (r < 0) @@ -281,17 +258,18 @@ return NULL; } -std::vector CObjectEntry::GetAnimations(const CStr& animationName, const CStr& ID) const +std::vector CObjectEntry::GetAnimations(const CStr& animationName, const CStr& ID) const { - std::vector anims; + std::vector anims; + using SkeletonAnimMap = decltype(m_Animations); SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName); SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName); for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it) { if (ID.empty() || it->second->m_ID == ID) - anims.push_back(it->second); + anims.push_back(it->second.get()); } if (anims.empty()) @@ -299,7 +277,7 @@ lower = m_Animations.lower_bound("idle"); upper = m_Animations.upper_bound("idle"); for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it) - anims.push_back(it->second); + anims.push_back(it->second.get()); } ENSURE(!anims.empty()); Index: source/graphics/SkeletonAnim.h =================================================================== --- source/graphics/SkeletonAnim.h +++ source/graphics/SkeletonAnim.h @@ -22,37 +22,40 @@ #ifndef INCLUDED_SKELETONANIM #define INCLUDED_SKELETONANIM -#include "maths/BoundingBoxAligned.h" - +class CBoundingBoxAligned; class CSkeletonAnimDef; - //////////////////////////////////////////////////////////////////////////////////////// -// CSkeletonAnim: an instance of a CSkeletonAnimDef, for application onto a model -class CSkeletonAnim +// SkeletonAnim: an instance of a CSkeletonAnimDef, for application onto a model +struct SkeletonAnim { -public: + // Filename of the animation - manidle.psa, manrun.psa, etc. + CStr m_Pathame; + // Raw animation frame data; may be nullptr if this is a static 'animation'. + CSkeletonAnimDef* m_AnimDef; + // Object space bounds of the model when this animation is applied to it. + // Note that this could technically be a per model-def data, + // but here it's stored on a per-actor-variant basis. + // If sufficient actors reuse the same models and animations, it could be worth caching this elsewhere. + std::unique_ptr m_ObjectBounds; + // the name of the action which uses this animation (e.g. "idle") CStr m_Name; // the ID of this animation, to sync between props. CStr m_ID = ""; // frequency of the animation int m_Frequency; - // the raw animation frame data; may be NULL if this is a static 'animation' - CSkeletonAnimDef* m_AnimDef; // speed at which this animation runs, as a factor of the AnimDef default speed // (treated as 0 if m_AnimDef == NULL) - float m_Speed; + float m_Speed = 0.f; // Times during the animation at which the interesting bits happen, // as msec times in the range [0, AnimDef->GetDuration], // or special value -1 if unspecified. // ActionPos is used for melee hits, projectile launches, etc. // ActionPos2 is used for loading projectile ammunition. - float m_ActionPos; - float m_ActionPos2; - float m_SoundPos; - // object space bounds of the model when this animation is applied to it - CBoundingBoxAligned m_ObjectBounds; + float m_ActionPos = -1.f; + float m_ActionPos2 = -1.f; + float m_SoundPos = -1.f; }; #endif Index: source/graphics/SkeletonAnimDef.h =================================================================== --- source/graphics/SkeletonAnimDef.h +++ source/graphics/SkeletonAnimDef.h @@ -56,7 +56,7 @@ public: // CSkeletonAnimDef constructor + destructor - CSkeletonAnimDef(); + CSkeletonAnimDef(const CStr& pathname); ~CSkeletonAnimDef(); // return the number of keys in this animation @@ -90,6 +90,9 @@ size_t m_NumFrames; // animation data - m_NumKeys*m_NumFrames total keys Key* m_Keys; + + // Resource name + CStr m_Pathname; }; #endif Index: source/graphics/SkeletonAnimDef.cpp =================================================================== --- source/graphics/SkeletonAnimDef.cpp +++ source/graphics/SkeletonAnimDef.cpp @@ -30,7 +30,8 @@ /////////////////////////////////////////////////////////////////////////////////////////// // CSkeletonAnimDef constructor -CSkeletonAnimDef::CSkeletonAnimDef() : m_FrameTime(0), m_NumKeys(0), m_NumFrames(0), m_Keys(0) +CSkeletonAnimDef::CSkeletonAnimDef(const CStr& pathname) +: m_Pathname(pathname), m_FrameTime(0), m_NumKeys(0), m_NumFrames(0), m_Keys(0) { } @@ -100,7 +101,7 @@ } // unpack the data - CSkeletonAnimDef* anim=new CSkeletonAnimDef; + CSkeletonAnimDef* anim = new CSkeletonAnimDef(filename.string8()); try { CStr name; // unused - just here to maintain compatibility with the animation files unpacker.UnpackString(name); Index: source/graphics/Unit.h =================================================================== --- source/graphics/Unit.h +++ source/graphics/Unit.h @@ -27,9 +27,8 @@ class CModelAbstract; class CObjectEntry; class CObjectManager; -class CSkeletonAnim; class CUnitAnimation; - +struct SkeletonAnim; ///////////////////////////////////////////////////////////////////////////////////////////// // CUnit: simple "actor" definition - defines a sole object within the world Index: source/graphics/UnitAnimation.h =================================================================== --- source/graphics/UnitAnimation.h +++ source/graphics/UnitAnimation.h @@ -26,8 +26,8 @@ class CUnit; class CModel; -class CSkeletonAnim; class CObjectEntry; +struct SkeletonAnim; /** * Deals with synchronisation issues between raw animation data (CModel, CSkeletonAnim) @@ -108,7 +108,7 @@ struct SModelAnimState { CModel* model; - CSkeletonAnim* anim; + SkeletonAnim* anim; const CObjectEntry* object; float time; bool pastLoadPos; Index: source/graphics/UnitAnimation.cpp =================================================================== --- source/graphics/UnitAnimation.cpp +++ source/graphics/UnitAnimation.cpp @@ -74,8 +74,8 @@ model->SetAnimation(state.anim, !m_Looping); // Detect if this unit has any non-static animations - for (CSkeletonAnim* anim : object->GetAnimations(m_State)) - if (anim->m_AnimDef != NULL) + for (SkeletonAnim* anim : object->GetAnimations(m_State)) + if (anim->m_AnimDef != nullptr) m_AnimStatesAreStatic = false; // Recursively add all props @@ -231,7 +231,7 @@ it->time = std::fmod(nextPos, duration); // Pick a new random animation - CSkeletonAnim* anim; + SkeletonAnim* anim; if (it->model == m_Model) { // we're handling the root model