mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-10 17:46:53 -10:00
318 lines
11 KiB
C++
318 lines
11 KiB
C++
#include "model/modelanimation.h"
|
|
#include "ship/ship.h"
|
|
|
|
extern float flFrametime;
|
|
|
|
namespace animation {
|
|
|
|
std::multimap<ship*, std::shared_ptr<ModelAnimation>> ModelAnimation::s_runningAnimations;
|
|
|
|
ModelAnimationState ModelAnimation::play(float frametime, ship* ship) {
|
|
|
|
switch (m_state) {
|
|
case ModelAnimationState::UNTRIGGERED:
|
|
//We have a new animation starting up in this phase. Put it in the list of running animations to track and step it later
|
|
s_runningAnimations.emplace(ship, shared_from_this());
|
|
m_duration = 0.0f;
|
|
//Stop other running animations on subsystems we care about. Store subsystems initial values as well.
|
|
for (const auto& animation : m_submodelAnimation) {
|
|
//We need to make sure that we have the submodel index cached before we check if other animations have the same index
|
|
animation->findSubmodel(ship);
|
|
|
|
auto animIterRange = s_runningAnimations.equal_range(ship);
|
|
for (auto animIter = animIterRange.first; animIter != animIterRange.second; animIter++) {
|
|
//Don't stop this animation, even though it (obviously) runs on the same submodels
|
|
if (animIter->second == shared_from_this())
|
|
continue;
|
|
|
|
const auto& otherAnims = animIter->second->m_submodelAnimation;
|
|
bool needStop = false;
|
|
|
|
//See if we can find a submodel that must be reset. In case the other submodel has not been cached, it cannot have been running, so it is fine to miss these here
|
|
for (const auto& otherAnim : otherAnims) {
|
|
if (otherAnim->m_submodel == animation->m_submodel) {
|
|
needStop = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (needStop) {
|
|
animIter->second->stop(animIter->first, false);
|
|
}
|
|
}
|
|
|
|
//Store the submodels current data as the base for this animation and calculate this animations parameters
|
|
float duration = animation->saveCurrentAsBase(ship);
|
|
|
|
//Update this animations duration based on all the submodel animations (note that these could differ in multiple executions of the same animation, for example in case of absolute angles)
|
|
if (duration > m_duration)
|
|
m_duration = duration;
|
|
}
|
|
|
|
m_state = ModelAnimationState::RUNNING_FWD;
|
|
case ModelAnimationState::RUNNING_FWD:
|
|
m_time += frametime;
|
|
|
|
//Cap time if needed
|
|
if (m_time > m_duration) {
|
|
m_time = m_duration;
|
|
m_state = ModelAnimationState::COMPLETED;
|
|
}
|
|
|
|
for (const auto& animation : m_submodelAnimation) {
|
|
animation->play(m_time, ship);
|
|
}
|
|
break;
|
|
|
|
case ModelAnimationState::COMPLETED:
|
|
//This means someone requested to start once we were complete, so start moving backwards.
|
|
m_state = ModelAnimationState::RUNNING_RWD;
|
|
case ModelAnimationState::RUNNING_RWD:
|
|
m_time -= frametime;
|
|
|
|
//Cap time at 0, but don't clean up the animations here since this function is called in a loop over the running animations, and cleaning now would invalidate the iterator
|
|
if (m_time < 0) {
|
|
stop(ship, false);
|
|
break;
|
|
}
|
|
|
|
for (const auto& animation : m_submodelAnimation) {
|
|
animation->play(m_time, ship);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return m_state;
|
|
}
|
|
|
|
void ModelAnimation::cleanRunning() {
|
|
auto removeIt = s_runningAnimations.cbegin();
|
|
while (removeIt != s_runningAnimations.cend()) {
|
|
if (removeIt->second->m_state == ModelAnimationState::UNTRIGGERED) {
|
|
removeIt = s_runningAnimations.erase(removeIt);
|
|
}
|
|
else
|
|
removeIt++;
|
|
}
|
|
}
|
|
|
|
void ModelAnimation::start(ship* ship, bool reverse) {
|
|
if (reverse) {
|
|
switch (m_state) {
|
|
case ModelAnimationState::RUNNING_RWD:
|
|
case ModelAnimationState::UNTRIGGERED:
|
|
//Cannot reverse-start if it's already running rwd or fully untriggered
|
|
return;
|
|
case ModelAnimationState::RUNNING_FWD:
|
|
//Just pretend we were going in the right direction
|
|
m_state = ModelAnimationState::RUNNING_RWD;
|
|
break;
|
|
case ModelAnimationState::COMPLETED:
|
|
//Nothing special to do. Expected case
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
switch (m_state) {
|
|
case ModelAnimationState::RUNNING_FWD:
|
|
case ModelAnimationState::COMPLETED:
|
|
//Cannot start if it's already running fwd or fully triggered
|
|
return;
|
|
case ModelAnimationState::RUNNING_RWD:
|
|
//Just pretend we were going in the right direction
|
|
m_state = ModelAnimationState::RUNNING_FWD;
|
|
break;
|
|
case ModelAnimationState::UNTRIGGERED:
|
|
//Nothing special to do. Expected case
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Play them once without changing their set time, this will make sure they stop other conflicting animations and set up properly.
|
|
play(0, ship);
|
|
//In case this stopped some other animations, we need to remove them from the playing buffer
|
|
cleanRunning();
|
|
}
|
|
|
|
void ModelAnimation::stop(ship* ship, bool cleanup) {
|
|
for (const auto& animation : m_submodelAnimation) {
|
|
animation->reset(ship);
|
|
}
|
|
|
|
m_time = 0;
|
|
m_state = ModelAnimationState::UNTRIGGERED;
|
|
|
|
if (cleanup)
|
|
cleanRunning();
|
|
}
|
|
|
|
void ModelAnimation::addSubsystemAnimation(std::unique_ptr<ModelAnimationSubmodel> animation) {
|
|
m_submodelAnimation.push_back(std::move(animation));
|
|
}
|
|
|
|
void ModelAnimation::stepAnimations(float frametime) {
|
|
for (const auto& anim : s_runningAnimations) {
|
|
switch (anim.second->m_state) {
|
|
case ModelAnimationState::RUNNING_FWD:
|
|
case ModelAnimationState::RUNNING_RWD:
|
|
anim.second->play(frametime, anim.first);
|
|
break;
|
|
case ModelAnimationState::COMPLETED:
|
|
//Fully triggered. Keep in buffer in case some other animation starts on that submodel, but don't play without manual starting
|
|
break;
|
|
case ModelAnimationState::UNTRIGGERED:
|
|
UNREACHABLE("An untriggered animation should not be in the runningAnimations buffer");
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
//Clear Animations that might have completed this frame.
|
|
cleanRunning();
|
|
}
|
|
|
|
void ModelAnimation::clearAnimations() {
|
|
for (const auto& anim : s_runningAnimations) {
|
|
anim.second->stop(anim.first, false);
|
|
}
|
|
|
|
s_runningAnimations.clear();
|
|
}
|
|
|
|
/*std::shared_ptr<ModelAnimation> ModelAnimation::parseAnimationTable() {
|
|
//TODO. This is not the function to parse the legacy table, that is still part of modelread.cpp
|
|
//This will eventually be the function to parse the new animation table
|
|
return std::make_shared<ModelAnimation>(nullptr);
|
|
}*/
|
|
|
|
|
|
ModelAnimationSubmodel::ModelAnimationSubmodel(const SCP_string& submodelName, std::unique_ptr<ModelAnimationSegment> mainSegment) : m_name(submodelName), m_mainSegment(std::move(mainSegment)) { }
|
|
|
|
ModelAnimationSubmodel::ModelAnimationSubmodel(const SCP_string& subsystemName, bool findBarrel, std::unique_ptr<ModelAnimationSegment> mainSegment) : m_name(subsystemName), m_findBarrel(findBarrel), m_mainSegment(std::move(mainSegment)) { }
|
|
|
|
void ModelAnimationSubmodel::play(float frametime, ship* ship) {
|
|
auto dataIt = m_initialData.find(ship);
|
|
auto lastDataIt = m_lastFrame.find(ship);
|
|
|
|
//Specified submodel not found. Don't play
|
|
if (dataIt == m_initialData.end() || lastDataIt == m_lastFrame.end())
|
|
return;
|
|
|
|
//Cap the frametime at this submodels duration
|
|
if (frametime > m_mainSegment->getDuration())
|
|
frametime = m_mainSegment->getDuration();
|
|
|
|
ModelAnimationData<> currentFrame = dataIt->second;
|
|
//Calculate the submodels data (or it's delta from the data stored at the animation's start)
|
|
ModelAnimationData<true> delta = m_mainSegment->calculateAnimation(currentFrame, lastDataIt->second, frametime);
|
|
|
|
currentFrame.applyDelta(delta);
|
|
|
|
//Execute stuff of the animation that doesn't modify this delta (stuff like sounds / particles)
|
|
m_mainSegment->executeAnimation(currentFrame, frametime);
|
|
|
|
//Actually apply the result to the submodel
|
|
copyToSubmodel(currentFrame, ship);
|
|
m_lastFrame[ship] = currentFrame;
|
|
}
|
|
|
|
void ModelAnimationSubmodel::reset(ship* ship) {
|
|
auto dataIt = m_initialData.find(ship);
|
|
if (dataIt == m_initialData.end())
|
|
return;
|
|
|
|
//Since resetting the submodel animation also has to happen for the submodel itself, copy it's state at the animation's start back to the submodel
|
|
copyToSubmodel(dataIt->second, ship);
|
|
}
|
|
|
|
void ModelAnimationSubmodel::copyToSubmodel(const ModelAnimationData<>& data, ship* ship) {
|
|
submodel_instance* submodel = findSubmodel(ship).first;
|
|
if (!submodel)
|
|
return;
|
|
|
|
submodel->canonical_orient = data.orientation;
|
|
//TODO: Once translation is a thing
|
|
//m_subsys->submodel_instance_1->offset = data.position;
|
|
}
|
|
|
|
float ModelAnimationSubmodel::saveCurrentAsBase(ship* ship) {
|
|
auto submodel = findSubmodel(ship);
|
|
if (!submodel.first || !submodel.second)
|
|
return 0;
|
|
|
|
ModelAnimationData<>& data = m_initialData[ship];
|
|
data.orientation = submodel.first->canonical_orient;
|
|
//TODO: Once translation is a thing
|
|
//data.position = m_subsys->submodel_instance_1->offset;
|
|
|
|
m_lastFrame[ship] = data;
|
|
m_mainSegment->recalculate(submodel.first, submodel.second, data);
|
|
return m_mainSegment->getDuration();
|
|
}
|
|
|
|
std::pair<submodel_instance*, bsp_info*> ModelAnimationSubmodel::findSubmodel(ship* ship) {
|
|
//Ship was deleted
|
|
if(ship->objnum < 0)
|
|
return { nullptr, nullptr };
|
|
|
|
int submodelNumber = -1;
|
|
|
|
polymodel* pm = model_get(Ship_info[ship->ship_info_index].model_num);
|
|
|
|
//Do we have a submodel number already cached?
|
|
if (m_submodel.has())
|
|
submodelNumber = m_submodel;
|
|
//Do we know if we were told to find the barrel submodel or not? This implies we have a subsystem name, not a submodel name
|
|
else if (m_findBarrel.has()) {
|
|
ship_info* sip = &Ship_info[ship->ship_info_index];
|
|
for (int i = 0; i < sip->n_subsystems; i++) {
|
|
if (!subsystem_stricmp(sip->subsystems[i].subobj_name, m_name.c_str())) {
|
|
if ((bool)m_findBarrel) {
|
|
//Check if the barrel subobj is a dedicated existing subobj or just the base turret.
|
|
if (sip->subsystems[i].turret_gun_sobj != sip->subsystems[i].subobj_num)
|
|
submodelNumber = sip->subsystems[i].turret_gun_sobj;
|
|
else
|
|
submodelNumber = -1;
|
|
}
|
|
else
|
|
submodelNumber = sip->subsystems[i].subobj_num;
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_submodel = submodelNumber;
|
|
}
|
|
//We seem to have a submodel name
|
|
else {
|
|
for (int i = 0; i < pm->n_models; i++) {
|
|
if (!subsystem_stricmp(pm->submodel[i].name, m_name.c_str())) {
|
|
submodelNumber = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_submodel = submodelNumber;
|
|
}
|
|
|
|
//If the model does not exist, return null. The system is expected to just silently tolerate this,
|
|
//as this needs to be the case to be able to handle turrets whose barrels may or may not exist, depending on the pof which is not loaded at table parse time
|
|
if (submodelNumber < 0 || submodelNumber >= pm->n_models)
|
|
return { nullptr, nullptr };
|
|
|
|
polymodel_instance* pmi = model_get_instance(ship->model_instance_num);
|
|
return { &pmi->submodel[submodelNumber], &pm->submodel[submodelNumber] };
|
|
}
|
|
|
|
|
|
float ModelAnimationSegment::getDuration() const {
|
|
return m_duration;
|
|
}
|
|
|
|
|
|
int ModelAnimationSet::SUBTYPE_DEFAULT = ANIMATION_SUBTYPE_ALL;
|
|
|
|
void ModelAnimationSet::emplace(std::shared_ptr<ModelAnimation> animation, SCP_string name, ModelAnimationTriggerType type, int subtype) {
|
|
animationSet[{type, subtype}].emplace(name, animation);
|
|
}
|
|
} |