#include "Runtime/Character/CAnimData.hpp" #include "Runtime/CStateManager.hpp" #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/rstl.hpp" #include "Runtime/Character/CAdditiveAnimPlayback.hpp" #include "Runtime/Character/CAllFormatsAnimSource.hpp" #include "Runtime/Character/CAnimPerSegmentData.hpp" #include "Runtime/Character/CAnimPlaybackParms.hpp" #include "Runtime/Character/CAnimTreeBlend.hpp" #include "Runtime/Character/CAnimTreeNode.hpp" #include "Runtime/Character/CAnimationManager.hpp" #include "Runtime/Character/CBoolPOINode.hpp" #include "Runtime/Character/CCharLayoutInfo.hpp" #include "Runtime/Character/CCharacterFactory.hpp" #include "Runtime/Character/CCharacterInfo.hpp" #include "Runtime/Character/CInt32POINode.hpp" #include "Runtime/Character/CParticleGenInfo.hpp" #include "Runtime/Character/CParticlePOINode.hpp" #include "Runtime/Character/CPrimitive.hpp" #include "Runtime/Character/CSegStatementSet.hpp" #include "Runtime/Character/CSoundPOINode.hpp" #include "Runtime/Character/CTransitionManager.hpp" #include "Runtime/Character/IAnimReader.hpp" #include "Runtime/Graphics/CSkinnedModel.hpp" #include <logvisor/logvisor.hpp> namespace urde { static logvisor::Module Log("CAnimData"); rstl::reserved_vector<CBoolPOINode, 8> CAnimData::g_BoolPOINodes; rstl::reserved_vector<CInt32POINode, 16> CAnimData::g_Int32POINodes; rstl::reserved_vector<CParticlePOINode, 20> CAnimData::g_ParticlePOINodes; rstl::reserved_vector<CSoundPOINode, 20> CAnimData::g_SoundPOINodes; rstl::reserved_vector<CInt32POINode, 16> CAnimData::g_TransientInt32POINodes; void CAnimData::FreeCache() {} void CAnimData::InitializeCache() {} CAnimData::CAnimData(CAssetId id, const CCharacterInfo& character, int defaultAnim, int charIdx, bool loop, TLockedToken<CCharLayoutInfo> layout, TToken<CSkinnedModel> model, const std::optional<TToken<CMorphableSkinnedModel>>& iceModel, const std::weak_ptr<CAnimSysContext>& ctx, std::shared_ptr<CAnimationManager> animMgr, std::shared_ptr<CTransitionManager> transMgr, TLockedToken<CCharacterFactory> charFactory, int drawInstCount) : x0_charFactory(std::move(charFactory)) , xc_charInfo(character) , xcc_layoutData(std::move(layout)) , xd8_modelData(std::move(model)) , xfc_animCtx(ctx.lock()) , x100_animMgr(std::move(animMgr)) , x1d8_selfId(id) , x1fc_transMgr(std::move(transMgr)) , x204_charIdx(charIdx) , x208_defaultAnim(defaultAnim) , x224_pose(layout->GetSegIdList().GetList().size()) , x2fc_poseBuilder(layout) , m_drawInstCount(drawInstCount) { x220_25_loop = loop; if (iceModel) xe4_iceModelData = *iceModel; g_BoolPOINodes.resize(8); g_Int32POINodes.resize(16); g_ParticlePOINodes.resize(20); g_SoundPOINodes.resize(20); g_TransientInt32POINodes.resize(16); x108_aabb = xd8_modelData->GetModel()->GetAABB(); x120_particleDB.CacheParticleDesc(xc_charInfo.GetParticleResData()); CHierarchyPoseBuilder pb(xcc_layoutData); pb.BuildNoScale(x224_pose); x220_30_poseBuilt = true; if (defaultAnim == -1) { defaultAnim = 0; Log.report(logvisor::Warning, fmt("Character {} has invalid initial animation, so defaulting to first."), character.GetCharacterName()); } auto treeNode = GetAnimationManager()->GetAnimationTree(character.GetAnimationIndex(defaultAnim), CMetaAnimTreeBuildOrders::NoSpecialOrders()); if (treeNode != x1f8_animRoot) { x1f8_animRoot = std::move(treeNode); } } void CAnimData::SetParticleEffectState(std::string_view effectName, bool active, CStateManager& mgr) { auto search = std::find_if(xc_charInfo.x98_effects.begin(), xc_charInfo.x98_effects.end(), [effectName](const auto& v) { return v.first == effectName; }); if (search != xc_charInfo.x98_effects.end()) for (const auto& p : search->second) x120_particleDB.SetParticleEffectState(p.GetComponentName(), active, mgr); } void CAnimData::InitializeEffects(CStateManager& mgr, TAreaId aId, const zeus::CVector3f& scale) { for (const auto& effects : xc_charInfo.GetEffectList()) { for (const auto& effect : effects.second) { x120_particleDB.CacheParticleDesc(effect.GetParticleTag()); x120_particleDB.AddParticleEffect(effect.GetSegmentName(), effect.GetFlags(), CParticleData(), scale, mgr, aId, true, x21c_particleLightIdx); x120_particleDB.SetParticleEffectState(effect.GetComponentName(), false, mgr); } } } CAssetId CAnimData::GetEventResourceIdForAnimResourceId(CAssetId id) const { return x0_charFactory->GetEventResourceIdForAnimResourceId(id); } void CAnimData::AddAdditiveSegData(const CSegIdList& list, CSegStatementSet& stSet) { for (auto& additive : x434_additiveAnims) if (additive.second.GetTargetWeight() > 0.00001f) additive.second.AddToSegStatementSet(list, *xcc_layoutData.GetObj(), stSet); } SAdvancementResults CAnimData::AdvanceAdditiveAnim(std::shared_ptr<CAnimTreeNode>& anim, const CCharAnimTime& time) { SAdvancementResults ret = anim->VAdvanceView(time); auto simplified = anim->Simplified(); if (simplified) anim = CAnimTreeNode::Cast(std::move(*simplified)); return ret; } SAdvancementDeltas CAnimData::AdvanceAdditiveAnims(float dt) { CCharAnimTime time(dt); SAdvancementDeltas deltas = {}; for (auto& additive : x434_additiveAnims) { std::shared_ptr<CAnimTreeNode>& anim = additive.second.GetAnim(); if (additive.second.IsActive()) { while (time.GreaterThanZero() && std::fabs(time.GetSeconds()) >= 0.00001f) { x210_passedIntCount += anim->GetInt32POIList(time, g_Int32POINodes.data(), 16, x210_passedIntCount, 0); x20c_passedBoolCount += anim->GetBoolPOIList(time, g_BoolPOINodes.data(), 8, x20c_passedBoolCount, 0); x214_passedParticleCount += anim->GetParticlePOIList(time, g_ParticlePOINodes.data(), 8, x214_passedParticleCount, 0); x218_passedSoundCount += anim->GetSoundPOIList(time, g_SoundPOINodes.data(), 8, x218_passedSoundCount, 0); SAdvancementResults results = AdvanceAdditiveAnim(anim, time); deltas.x0_posDelta += results.x8_deltas.x0_posDelta; deltas.xc_rotDelta *= results.x8_deltas.xc_rotDelta; time = results.x0_remTime; } } else { CCharAnimTime remTime = anim->VGetTimeRemaining(); while (remTime.GreaterThanZero() && std::fabs(remTime.GetSeconds()) >= 0.00001f) { x210_passedIntCount += anim->GetInt32POIList(time, g_Int32POINodes.data(), 16, x210_passedIntCount, 0); x20c_passedBoolCount += anim->GetBoolPOIList(time, g_BoolPOINodes.data(), 8, x20c_passedBoolCount, 0); x214_passedParticleCount += anim->GetParticlePOIList(time, g_ParticlePOINodes.data(), 8, x214_passedParticleCount, 0); x218_passedSoundCount += anim->GetSoundPOIList(time, g_SoundPOINodes.data(), 8, x218_passedSoundCount, 0); SAdvancementResults results = AdvanceAdditiveAnim(anim, time); deltas.x0_posDelta += results.x8_deltas.x0_posDelta; deltas.xc_rotDelta *= results.x8_deltas.xc_rotDelta; CCharAnimTime tmpTime = anim->VGetTimeRemaining(); if (tmpTime < results.x0_remTime) remTime = tmpTime; else remTime = results.x0_remTime; } } } return deltas; } SAdvancementDeltas CAnimData::UpdateAdditiveAnims(float dt) { for (auto it = x434_additiveAnims.begin(); it != x434_additiveAnims.end();) { it->second.Update(dt); CCharAnimTime timeRem = it->second.GetAnim()->VGetTimeRemaining(); if (timeRem.EpsilonZero() && it->second.NeedsFadeOut()) it->second.FadeOut(); if (it->second.GetPhase() == EAdditivePlaybackPhase::FadedOut) { it = x434_additiveAnims.erase(it); continue; } ++it; } return AdvanceAdditiveAnims(dt); } bool CAnimData::IsAdditiveAnimation(s32 idx) const { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); return x0_charFactory->HasAdditiveInfo(animIdx); } bool CAnimData::IsAdditiveAnimationAdded(s32 idx) const { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(), [animIdx](const auto& pair) { return pair.first == animIdx; }); return search != x434_additiveAnims.cend(); } const std::shared_ptr<CAnimTreeNode>& CAnimData::GetAdditiveAnimationTree(s32 idx) const { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(), [animIdx](const auto& pair) { return pair.first == animIdx; }); return search->second.GetAnim(); } bool CAnimData::IsAdditiveAnimationActive(s32 idx) const { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(), [animIdx](const auto& pair) { return pair.first == animIdx; }); if (search == x434_additiveAnims.cend()) return false; return search->second.IsActive(); } void CAnimData::DelAdditiveAnimation(s32 idx) { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.begin(), x434_additiveAnims.end(), [animIdx](const auto& pair) { return pair.first == animIdx; }); if (search != x434_additiveAnims.cend() && search->second.GetPhase() != EAdditivePlaybackPhase::FadingOut && search->second.GetPhase() != EAdditivePlaybackPhase::FadedOut) { search->second.FadeOut(); } } void CAnimData::AddAdditiveAnimation(s32 idx, float weight, bool active, bool fadeOut) { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.begin(), x434_additiveAnims.end(), [animIdx](const auto& pair) { return pair.first == animIdx; }); if (search != x434_additiveAnims.cend()) { search->second.SetActive(active); search->second.SetWeight(weight); search->second.SetNeedsFadeOut(!search->second.IsActive() && fadeOut); } else { std::shared_ptr<CAnimTreeNode> node = GetAnimationManager()->GetAnimationTree(animIdx, CMetaAnimTreeBuildOrders::NoSpecialOrders()); const CAdditiveAnimationInfo& info = x0_charFactory->FindAdditiveInfo(animIdx); x434_additiveAnims.emplace_back( std::make_pair(animIdx, CAdditiveAnimPlayback(node, weight, active, info, fadeOut))); } } float CAnimData::GetAdditiveAnimationWeight(s32 idx) const { s32 animIdx = xc_charInfo.GetAnimationIndex(idx); auto search = std::find_if(x434_additiveAnims.cbegin(), x434_additiveAnims.cend(), [animIdx](const auto& pair) { return pair.first == animIdx; }); if (search != x434_additiveAnims.cend()) return search->second.GetTargetWeight(); return 0.f; } std::shared_ptr<CAnimationManager> CAnimData::GetAnimationManager() { return x100_animMgr; } void CAnimData::SetPhase(float ph) { x1f8_animRoot->VSetPhase(ph); } void CAnimData::Touch(const CSkinnedModel& model, int shadIdx) const { model.GetModelInst()->Touch(shadIdx); } SAdvancementDeltas CAnimData::GetAdvancementDeltas(const CCharAnimTime& a, const CCharAnimTime& b) const { return x1f8_animRoot->VGetAdvancementResults(a, b).x8_deltas; } CCharAnimTime CAnimData::GetTimeOfUserEvent(EUserEventType type, const CCharAnimTime& time) const { u32 count = x1f8_animRoot->GetInt32POIList(time, g_TransientInt32POINodes.data(), 16, 0, 64); for (u32 i = 0; i < count; ++i) { CInt32POINode& poi = g_TransientInt32POINodes[i]; if (poi.GetPoiType() == EPOIType::UserEvent && EUserEventType(poi.GetValue()) == type) { CCharAnimTime ret = poi.GetTime(); for (; i < count; ++i) g_TransientInt32POINodes[i] = CInt32POINode(); return ret; } else { poi = CInt32POINode(); } } return CCharAnimTime::Infinity(); } void CAnimData::MultiplyPlaybackRate(float mul) { x200_speedScale *= mul; } void CAnimData::SetPlaybackRate(float set) { x200_speedScale = set; } void CAnimData::SetRandomPlaybackRate(CRandom16& r) { for (u32 i = 0; i < x210_passedIntCount; ++i) { CInt32POINode& poi = g_Int32POINodes[i]; if (poi.GetPoiType() == EPOIType::RandRate) { float tmp = (r.Next() % poi.GetValue()) / 100.f; if ((r.Next() % 100) < 50) x200_speedScale = 1.f + tmp; else x200_speedScale = 1.f - tmp; break; } } } void CAnimData::CalcPlaybackAlignmentParms(const CAnimPlaybackParms& parms, const std::shared_ptr<CAnimTreeNode>& node) { zeus::CQuaternion orient; x1e8_alignRot = zeus::CQuaternion(); x220_27_ = false; if (parms.GetDeltaOrient() && parms.GetObjectXform()) { ResetPOILists(); x210_passedIntCount += node->GetInt32POIList(CCharAnimTime::Infinity(), g_Int32POINodes.data(), 16, x210_passedIntCount, 64); for (u32 i = 0; i < x210_passedIntCount; ++i) { CInt32POINode& poi = g_Int32POINodes[i]; if (poi.GetPoiType() == EPOIType::UserEvent && EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetRot) { SAdvancementResults res = node->VGetAdvancementResults(poi.GetTime(), 0.f); orient = zeus::CQuaternion::slerp(zeus::CQuaternion(), *parms.GetDeltaOrient() * zeus::CQuaternion(parms.GetObjectXform()->buildMatrix3f().inverted()) * res.x8_deltas.xc_rotDelta.inverse(), 1.f / (60.f * poi.GetTime().GetSeconds())); x1e8_alignRot = orient; x220_27_ = true; } } } if (!x220_27_) { bool didAlign = false; bool didStart = false; zeus::CVector3f posStart, posAlign; CCharAnimTime timeStart, timeAlign; if (parms.GetTargetPos() && parms.GetObjectXform()) { ResetPOILists(); x210_passedIntCount += node->GetInt32POIList(CCharAnimTime::Infinity(), g_Int32POINodes.data(), 16, x210_passedIntCount, 64); for (u32 i = 0; i < x210_passedIntCount; ++i) { CInt32POINode& poi = g_Int32POINodes[i]; if (poi.GetPoiType() == EPOIType::UserEvent) { if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPosStart) { didStart = true; SAdvancementResults res = node->VGetAdvancementResults(poi.GetTime(), 0.f); posStart = res.x8_deltas.x0_posDelta; timeStart = poi.GetTime(); if (parms.GetIsUseLocator()) posStart += GetLocatorTransform(poi.GetLocatorName(), &poi.GetTime()).origin; if (didAlign) break; } else if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPos) { didAlign = true; SAdvancementResults res = node->VGetAdvancementResults(poi.GetTime(), 0.f); posAlign = res.x8_deltas.x0_posDelta; timeAlign = poi.GetTime(); if (parms.GetIsUseLocator()) posAlign += GetLocatorTransform(poi.GetLocatorName(), &poi.GetTime()).origin; if (didStart) break; } } } if (didAlign && didStart) { zeus::CVector3f scaleStart = *parms.GetObjectScale() * posStart; zeus::CVector3f scaleAlign = *parms.GetObjectScale() * posAlign; x1dc_alignPos = (parms.GetObjectXform()->inverse() * *parms.GetTargetPos() - scaleStart - (scaleAlign - scaleStart)) / *parms.GetObjectScale() * (1.f / (timeAlign.GetSeconds() - timeStart.GetSeconds())); x220_28_ = true; x220_26_aligningPos = false; } else { x1dc_alignPos = zeus::skZero3f; x220_28_ = false; x220_26_aligningPos = false; } } } else { bool didStart = false; bool didAlign = false; CCharAnimTime timeStart, timeAlign; zeus::CVector3f startPos; if (parms.GetTargetPos() && parms.GetObjectXform()) { ResetPOILists(); x210_passedIntCount += node->GetInt32POIList(CCharAnimTime::Infinity(), g_Int32POINodes.data(), 16, x210_passedIntCount, 64); for (u32 i = 0; i < x210_passedIntCount; ++i) { CInt32POINode& poi = g_Int32POINodes[i]; if (poi.GetPoiType() == EPOIType::UserEvent) { if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPosStart) { didStart = true; timeStart = poi.GetTime(); if (didAlign) break; } else if (EUserEventType(poi.GetValue()) == EUserEventType::AlignTargetPos) { didAlign = true; timeAlign = poi.GetTime(); if (didStart) break; } } } if (didAlign && didStart) { CCharAnimTime frameInterval(1.f / 60.f); orient = zeus::CQuaternion(); x1e8_alignRot = zeus::CQuaternion(); x220_27_ = true; CCharAnimTime time; zeus::CVector3f pos; zeus::CQuaternion quat; bool foundStartPos = false; while (time < timeAlign) { SAdvancementResults res = node->VGetAdvancementResults(frameInterval, time); pos += quat.toTransform() * res.x8_deltas.x0_posDelta; quat *= (res.x8_deltas.xc_rotDelta * orient); if (!foundStartPos && time >= timeStart) { startPos = pos; foundStartPos = true; } time += frameInterval; } zeus::CVector3f scaleStart = startPos * *parms.GetObjectScale(); zeus::CVector3f scaleAlign = pos * *parms.GetObjectScale(); x1dc_alignPos = (parms.GetObjectXform()->inverse() * *parms.GetTargetPos() - scaleStart - (scaleAlign - scaleStart)) / *parms.GetObjectScale() * (1.f / (timeAlign.GetSeconds() - timeStart.GetSeconds())); x220_28_ = true; x220_26_aligningPos = false; } else { x1dc_alignPos = zeus::skZero3f; x220_28_ = false; x220_26_aligningPos = false; } } else { x1dc_alignPos = zeus::skZero3f; x220_28_ = false; x220_26_aligningPos = false; } } } zeus::CTransform CAnimData::GetLocatorTransform(CSegId id, const CCharAnimTime* time) const { if (id.IsInvalid()) { return {}; } zeus::CTransform ret; if (time || !x220_31_poseCached) { const_cast<CAnimData*>(this)->RecalcPoseBuilder(time); const_cast<CAnimData*>(this)->x220_31_poseCached = time == nullptr; } if (!x220_30_poseBuilt) x2fc_poseBuilder.BuildTransform(id, ret); else { ret.setRotation(x224_pose.GetTransformMinusOffset(id)); ret.origin = x224_pose.GetOffset(id); } return ret; } zeus::CTransform CAnimData::GetLocatorTransform(std::string_view name, const CCharAnimTime* time) const { return GetLocatorTransform(xcc_layoutData->GetSegIdFromString(name), time); } bool CAnimData::IsAnimTimeRemaining(float rem, std::string_view name) const { if (!x1f8_animRoot) return false; return x1f8_animRoot->VGetTimeRemaining().GetSeconds() >= rem; } float CAnimData::GetAnimTimeRemaining(std::string_view name) const { float rem = x1f8_animRoot->VGetTimeRemaining().GetSeconds(); if (x200_speedScale) return rem / x200_speedScale; return rem; } float CAnimData::GetAnimationDuration(int animIn) const { std::shared_ptr<IMetaAnim> anim = x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(animIn)); std::set<CPrimitive> prims; anim->GetUniquePrimitives(prims); SObjectTag tag{FOURCC('ANIM'), 0}; float durAccum = 0.f; for (const CPrimitive& prim : prims) { tag.id = prim.GetAnimResId(); TLockedToken<CAllFormatsAnimSource> animRes = xfc_animCtx->xc_store.GetObj(tag); CCharAnimTime dur; switch (animRes->GetFormat()) { case EAnimFormat::Uncompressed: default: { const CAnimSource& src = animRes->GetAsCAnimSource(); dur = src.GetDuration(); break; } case EAnimFormat::BitstreamCompressed: case EAnimFormat::BitstreamCompressed24: { const CFBStreamedCompression& src = animRes->GetAsCFBStreamedCompression(); dur = src.GetAnimationDuration(); break; } } durAccum += dur.GetSeconds(); } if (anim->GetType() == EMetaAnimType::Random) return durAccum / float(prims.size()); return durAccum; } std::shared_ptr<CAnimSysContext> CAnimData::GetAnimSysContext() const { return xfc_animCtx; } std::shared_ptr<CAnimationManager> CAnimData::GetAnimationManager() const { return x100_animMgr; } void CAnimData::RecalcPoseBuilder(const CCharAnimTime* time) { if (!x1f8_animRoot) return; const CSegIdList& segIdList = GetCharLayoutInfo().GetSegIdList(); CSegStatementSet segSet; if (time) x1f8_animRoot->VGetSegStatementSet(segIdList, segSet, *time); else x1f8_animRoot->VGetSegStatementSet(segIdList, segSet); AddAdditiveSegData(segIdList, segSet); for (const CSegId& id : segIdList.GetList()) { if (id == 3) continue; CAnimPerSegmentData& segData = segSet[id]; if (segData.x1c_hasOffset) x2fc_poseBuilder.Insert(id, segData.x0_rotation, segData.x10_offset); else x2fc_poseBuilder.Insert(id, segData.x0_rotation); } } void CAnimData::RenderAuxiliary(const zeus::CFrustum& frustum) const { x120_particleDB.AddToRendererClipped(frustum); } void CAnimData::Render(CSkinnedModel& model, const CModelFlags& drawFlags, const std::optional<CVertexMorphEffect>& morphEffect, const float* morphMagnitudes) { SetupRender(model, drawFlags, morphEffect, morphMagnitudes); DrawSkinnedModel(model, drawFlags); } void CAnimData::SetupRender(CSkinnedModel& model, const CModelFlags& drawFlags, const std::optional<CVertexMorphEffect>& morphEffect, const float* morphMagnitudes) { if (!x220_30_poseBuilt) { x2fc_poseBuilder.BuildNoScale(x224_pose); x220_30_poseBuilt = true; } PoseSkinnedModel(model, x224_pose, drawFlags, morphEffect, morphMagnitudes); } void CAnimData::DrawSkinnedModel(CSkinnedModel& model, const CModelFlags& flags) { model.Draw(flags); } void CAnimData::PreRender() { if (!x220_31_poseCached) { RecalcPoseBuilder(nullptr); x220_31_poseCached = true; x220_30_poseBuilt = false; } } void CAnimData::BuildPose() { if (!x220_31_poseCached) { RecalcPoseBuilder(nullptr); x220_31_poseCached = true; x220_30_poseBuilt = false; } if (!x220_30_poseBuilt) { x2fc_poseBuilder.BuildNoScale(x224_pose); x220_30_poseBuilt = true; } } void CAnimData::PrimitiveSetToTokenVector(const std::set<CPrimitive>& primSet, std::vector<CToken>& tokensOut, bool preLock) { tokensOut.reserve(primSet.size()); SObjectTag tag{FOURCC('ANIM'), 0}; for (const CPrimitive& prim : primSet) { tag.id = prim.GetAnimResId(); tokensOut.push_back(g_SimplePool->GetObj(tag)); if (preLock) tokensOut.back().Lock(); } } void CAnimData::GetAnimationPrimitives(const CAnimPlaybackParms& parms, std::set<CPrimitive>& primsOut) const { std::shared_ptr<IMetaAnim> animA = x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(parms.GetAnimationId())); animA->GetUniquePrimitives(primsOut); if (parms.GetSecondAnimationId() != -1) { std::shared_ptr<IMetaAnim> animB = x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(parms.GetSecondAnimationId())); animB->GetUniquePrimitives(primsOut); } } void CAnimData::SetAnimation(const CAnimPlaybackParms& parms, bool noTrans) { if (parms.GetAnimationId() == x40c_playbackParms.GetAnimationId() || (parms.GetSecondAnimationId() == x40c_playbackParms.GetSecondAnimationId() && parms.GetSecondAnimationId() != -1) || (parms.GetBlendFactor() == x40c_playbackParms.GetBlendFactor() && parms.GetBlendFactor() != 1.f)) { if (x220_29_animationJustStarted) return; } x40c_playbackParms.SetAnimationId(parms.GetAnimationId()); x40c_playbackParms.SetSecondAnimationId(parms.GetSecondAnimationId()); x40c_playbackParms.SetBlendFactor(parms.GetBlendFactor()); x200_speedScale = 1.f; x208_defaultAnim = parms.GetAnimationId(); s32 animIdxA = xc_charInfo.GetAnimationIndex(parms.GetAnimationId()); ResetPOILists(); std::shared_ptr<CAnimTreeNode> blendNode; if (parms.GetSecondAnimationId() != -1) { s32 animIdxB = xc_charInfo.GetAnimationIndex(parms.GetSecondAnimationId()); std::shared_ptr<CAnimTreeNode> treeA = x100_animMgr->GetAnimationTree(animIdxA, CMetaAnimTreeBuildOrders::NoSpecialOrders()); std::shared_ptr<CAnimTreeNode> treeB = x100_animMgr->GetAnimationTree(animIdxB, CMetaAnimTreeBuildOrders::NoSpecialOrders()); blendNode = std::make_shared<CAnimTreeBlend>(false, treeA, treeB, parms.GetBlendFactor(), CAnimTreeBlend::CreatePrimitiveName(treeA, treeB, parms.GetBlendFactor())); } else { blendNode = x100_animMgr->GetAnimationTree(animIdxA, CMetaAnimTreeBuildOrders::NoSpecialOrders()); } if (!noTrans && x1f8_animRoot) x1f8_animRoot = x1fc_transMgr->GetTransitionTree(x1f8_animRoot, blendNode); else x1f8_animRoot = blendNode; x220_24_animating = parms.GetIsPlayAnimation(); CalcPlaybackAlignmentParms(parms, blendNode); ResetPOILists(); x220_29_animationJustStarted = true; } SAdvancementDeltas CAnimData::DoAdvance(float dt, bool& suspendParticles, CRandom16& random, bool advTree) { suspendParticles = false; zeus::CVector3f offsetPre, offsetPost; zeus::CQuaternion quatPre, quatPost; ResetPOILists(); float scaleDt = dt * x200_speedScale; if (x2fc_poseBuilder.HasRoot()) { SAdvancementDeltas deltas = UpdateAdditiveAnims(scaleDt); offsetPre = deltas.x0_posDelta; quatPre = deltas.xc_rotDelta; } if (!x220_24_animating) { suspendParticles = true; return {}; } if (x220_29_animationJustStarted) { x220_29_animationJustStarted = false; suspendParticles = true; } if (advTree && x1f8_animRoot) { SetRandomPlaybackRate(random); CCharAnimTime time(scaleDt); if (x220_25_loop) { while (time.GreaterThanZero() && !time.EpsilonZero()) { x210_passedIntCount += x1f8_animRoot->GetInt32POIList(time, g_Int32POINodes.data(), 16, x210_passedIntCount, 0); x20c_passedBoolCount += x1f8_animRoot->GetBoolPOIList(time, g_BoolPOINodes.data(), 16, x20c_passedBoolCount, 0); x214_passedParticleCount += x1f8_animRoot->GetParticlePOIList(time, g_ParticlePOINodes.data(), 16, x214_passedParticleCount, 0); x218_passedSoundCount += x1f8_animRoot->GetSoundPOIList(time, g_SoundPOINodes.data(), 16, x218_passedSoundCount, 0); AdvanceAnim(time, offsetPost, quatPost); } } else { CCharAnimTime remTime = x1f8_animRoot->VGetTimeRemaining(); while (!remTime.EpsilonZero() && !time.EpsilonZero()) { x210_passedIntCount += x1f8_animRoot->GetInt32POIList(time, g_Int32POINodes.data(), 16, x210_passedIntCount, 0); x20c_passedBoolCount += x1f8_animRoot->GetBoolPOIList(time, g_BoolPOINodes.data(), 16, x20c_passedBoolCount, 0); x214_passedParticleCount += x1f8_animRoot->GetParticlePOIList(time, g_ParticlePOINodes.data(), 16, x214_passedParticleCount, 0); x218_passedSoundCount += x1f8_animRoot->GetSoundPOIList(time, g_SoundPOINodes.data(), 16, x218_passedSoundCount, 0); AdvanceAnim(time, offsetPost, quatPost); remTime = x1f8_animRoot->VGetTimeRemaining(); time = std::max(0.f, std::min(remTime.GetSeconds(), time.GetSeconds())); if (remTime.EpsilonZero()) { x220_24_animating = false; x1dc_alignPos = zeus::skZero3f; x220_28_ = false; x220_26_aligningPos = false; } } } x220_31_poseCached = false; x220_30_poseBuilt = false; } return {offsetPost + offsetPre, quatPost * quatPre}; } SAdvancementDeltas CAnimData::Advance(float dt, const zeus::CVector3f& scale, CStateManager& stateMgr, TAreaId aid, bool advTree) { bool suspendParticles; SAdvancementDeltas deltas = DoAdvance(dt, suspendParticles, *stateMgr.GetActiveRandom(), advTree); if (suspendParticles) x120_particleDB.SuspendAllActiveEffects(stateMgr); for (u32 i = 0; i < x214_passedParticleCount; ++i) { CParticlePOINode& node = g_ParticlePOINodes[i]; if (node.GetCharacterIndex() == -1 || node.GetCharacterIndex() == x204_charIdx) { x120_particleDB.AddParticleEffect(node.GetString(), node.GetFlags(), node.GetParticleData(), scale, stateMgr, aid, false, x21c_particleLightIdx); } } return deltas; } SAdvancementDeltas CAnimData::AdvanceIgnoreParticles(float dt, CRandom16& random, bool advTree) { bool suspendParticles; return DoAdvance(dt, suspendParticles, random, advTree); } void CAnimData::AdvanceAnim(CCharAnimTime& time, zeus::CVector3f& offset, zeus::CQuaternion& quat) { SAdvancementResults results; std::optional<std::unique_ptr<IAnimReader>> simplified; if (x104_animDir == EAnimDir::Forward) { results = x1f8_animRoot->VAdvanceView(time); simplified = x1f8_animRoot->Simplified(); } if (simplified) x1f8_animRoot = CAnimTreeNode::Cast(std::move(*simplified)); if ((x220_28_ || x220_27_) && x210_passedIntCount > 0) { for (u32 i = 0; i < x210_passedIntCount; ++i) { CInt32POINode& node = g_Int32POINodes[i]; if (node.GetPoiType() == EPOIType::UserEvent) { switch (EUserEventType(node.GetValue())) { case EUserEventType::AlignTargetPosStart: { x220_26_aligningPos = true; break; } case EUserEventType::AlignTargetPos: { x1dc_alignPos = zeus::skZero3f; x220_28_ = false; x220_26_aligningPos = false; break; } case EUserEventType::AlignTargetRot: { x1e8_alignRot = zeus::CQuaternion(); x220_27_ = false; break; } default: break; } } } } offset += results.x8_deltas.x0_posDelta; if (x220_26_aligningPos) offset += x1dc_alignPos * time.GetSeconds(); zeus::CQuaternion rot = results.x8_deltas.xc_rotDelta * x1e8_alignRot; quat = quat * rot; x1dc_alignPos = rot.transform(x1dc_alignPos); time = results.x0_remTime; } void CAnimData::SetXRayModel(const TLockedToken<CModel>& model, const TLockedToken<CSkinRules>& skinRules) { xf4_xrayModel = std::make_shared<CSkinnedModel>(model, skinRules, xd8_modelData->GetLayoutInfo(), 0, m_drawInstCount); } void CAnimData::SetInfraModel(const TLockedToken<CModel>& model, const TLockedToken<CSkinRules>& skinRules) { xf8_infraModel = std::make_shared<CSkinnedModel>(model, skinRules, xd8_modelData->GetLayoutInfo(), 0, m_drawInstCount); } void CAnimData::PoseSkinnedModel(CSkinnedModel& model, const CPoseAsTransforms& pose, const CModelFlags& drawFlags, const std::optional<CVertexMorphEffect>& morphEffect, const float* morphMagnitudes) { model.Calculate(pose, drawFlags, morphEffect, morphMagnitudes); } void CAnimData::AdvanceParticles(const zeus::CTransform& xf, float dt, const zeus::CVector3f& vec, CStateManager& stateMgr) { x120_particleDB.Update(dt, x224_pose, *xcc_layoutData, xf, vec, stateMgr); } float CAnimData::GetAverageVelocity(int animIn) const { std::shared_ptr<IMetaAnim> anim = x100_animMgr->GetMetaAnimation(xc_charInfo.GetAnimationIndex(animIn)); std::set<CPrimitive> prims; anim->GetUniquePrimitives(prims); SObjectTag tag{FOURCC('ANIM'), 0}; float velAccum = 0.f; float durAccum = 0.f; for (const CPrimitive& prim : prims) { tag.id = prim.GetAnimResId(); TLockedToken<CAllFormatsAnimSource> animRes = xfc_animCtx->xc_store.GetObj(tag); CCharAnimTime dur; float avgVel; switch (animRes->GetFormat()) { case EAnimFormat::Uncompressed: default: { const CAnimSource& src = animRes->GetAsCAnimSource(); dur = src.GetDuration(); avgVel = src.GetAverageVelocity(); break; } case EAnimFormat::BitstreamCompressed: case EAnimFormat::BitstreamCompressed24: { const CFBStreamedCompression& src = animRes->GetAsCFBStreamedCompression(); dur = src.GetAnimationDuration(); avgVel = src.GetAverageVelocity(); break; } } velAccum += dur.GetSeconds() * avgVel; durAccum += dur.GetSeconds(); } if (durAccum > 0.f) return velAccum / durAccum; return 0.f; } void CAnimData::ResetPOILists() { x20c_passedBoolCount = 0; x210_passedIntCount = 0; x214_passedParticleCount = 0; x218_passedSoundCount = 0; } CSegId CAnimData::GetLocatorSegId(std::string_view name) const { return xcc_layoutData->GetSegIdFromString(name); } zeus::CAABox CAnimData::GetBoundingBox(const zeus::CTransform& xf) const { return GetBoundingBox().getTransformedAABox(xf); } zeus::CAABox CAnimData::GetBoundingBox() const { auto aabbList = xc_charInfo.GetAnimBBoxList(); if (aabbList.empty()) return x108_aabb; CAnimTreeEffectiveContribution contrib = x1f8_animRoot->GetContributionOfHighestInfluence(); auto search = rstl::binary_find( aabbList.cbegin(), aabbList.cend(), contrib.x4_name, [](const std::pair<std::string, zeus::CAABox>& other) -> const std::string& { return other.first; }); if (search == aabbList.cend()) return x108_aabb; return search->second; } void CAnimData::SubstituteModelData(const TCachedToken<CSkinnedModel>& model) { xd8_modelData = model; x108_aabb = xd8_modelData->GetModel()->GetAABB(); } void CAnimData::SetParticleCEXTValue(std::string_view name, int idx, float value) { auto search = std::find_if(xc_charInfo.x98_effects.begin(), xc_charInfo.x98_effects.end(), [&name](const auto& v) { return v.first == name; }); if (search != xc_charInfo.x98_effects.end() && search->second.size()) x120_particleDB.SetCEXTValue(search->second.front().GetComponentName(), idx, value); } } // namespace urde