306 lines
11 KiB
C++
306 lines
11 KiB
C++
#include "StarAnimatedPartSet.hpp"
|
|
#include "StarMathCommon.hpp"
|
|
|
|
namespace Star {
|
|
|
|
AnimatedPartSet::AnimatedPartSet() {}
|
|
|
|
AnimatedPartSet::AnimatedPartSet(Json config) {
|
|
for (auto const& stateTypePair : config.get("stateTypes", JsonObject()).iterateObject()) {
|
|
auto const& stateTypeName = stateTypePair.first;
|
|
auto const& stateTypeConfig = stateTypePair.second;
|
|
|
|
StateType newStateType;
|
|
newStateType.priority = stateTypeConfig.getFloat("priority", 0.0f);
|
|
newStateType.enabled = stateTypeConfig.getBool("enabled", true);
|
|
newStateType.defaultState = stateTypeConfig.getString("default", "");
|
|
newStateType.stateTypeProperties = stateTypeConfig.getObject("properties", {});
|
|
|
|
for (auto const& statePair : stateTypeConfig.get("states", JsonObject()).iterateObject()) {
|
|
auto const& stateName = statePair.first;
|
|
auto const& stateConfig = statePair.second;
|
|
|
|
auto newState = make_shared<State>();
|
|
newState->frames = stateConfig.getInt("frames", 1);
|
|
newState->cycle = stateConfig.getFloat("cycle", 1.0f);
|
|
newState->animationMode = stringToAnimationMode(stateConfig.getString("mode", "end"));
|
|
newState->transitionState = stateConfig.getString("transition", "");
|
|
newState->stateProperties = stateConfig.getObject("properties", {});
|
|
newState->stateFrameProperties = stateConfig.getObject("frameProperties", {});
|
|
newStateType.states[stateName] = move(newState);
|
|
}
|
|
|
|
newStateType.states.sortByKey();
|
|
|
|
newStateType.activeState.stateTypeName = stateTypeName;
|
|
newStateType.activeStateDirty = true;
|
|
|
|
if (newStateType.defaultState.empty() && !newStateType.states.empty())
|
|
newStateType.defaultState = newStateType.states.firstKey();
|
|
|
|
m_stateTypes[stateTypeName] = move(newStateType);
|
|
}
|
|
|
|
// Sort state types by decreasing priority.
|
|
m_stateTypes.sort([](pair<String, StateType> const& a, pair<String, StateType> const& b) {
|
|
return b.second.priority < a.second.priority;
|
|
});
|
|
|
|
for (auto const& partPair : config.get("parts", JsonObject()).iterateObject()) {
|
|
auto const& partName = partPair.first;
|
|
auto const& partConfig = partPair.second;
|
|
|
|
Part newPart;
|
|
newPart.partProperties = partConfig.getObject("properties", {});
|
|
|
|
for (auto const& partStateTypePair : partConfig.get("partStates", JsonObject()).iterateObject()) {
|
|
auto const& stateTypeName = partStateTypePair.first;
|
|
|
|
for (auto const& partStatePair : partStateTypePair.second.toObject()) {
|
|
auto const& stateName = partStatePair.first;
|
|
auto const& stateConfig = partStatePair.second;
|
|
|
|
PartState partState = {stateConfig.getObject("properties", {}), stateConfig.getObject("frameProperties", {})};
|
|
newPart.partStates[stateTypeName][stateName] = move(partState);
|
|
}
|
|
}
|
|
newPart.activePart.partName = partPair.first;
|
|
newPart.activePartDirty = true;
|
|
|
|
m_parts[partName] = move(newPart);
|
|
}
|
|
|
|
for (auto const& pair : m_stateTypes)
|
|
setActiveState(pair.first, pair.second.defaultState, true);
|
|
}
|
|
|
|
StringList AnimatedPartSet::stateTypes() const {
|
|
return m_stateTypes.keys();
|
|
}
|
|
|
|
void AnimatedPartSet::setStateTypeEnabled(String const& stateTypeName, bool enabled) {
|
|
auto& stateType = m_stateTypes.get(stateTypeName);
|
|
if (stateType.enabled != enabled) {
|
|
stateType.enabled = enabled;
|
|
for (auto& pair : m_parts)
|
|
pair.second.activePartDirty = true;
|
|
}
|
|
}
|
|
|
|
void AnimatedPartSet::setEnabledStateTypes(StringList const& stateTypeNames) {
|
|
for (auto& pair : m_stateTypes)
|
|
pair.second.enabled = false;
|
|
|
|
for (auto const& stateTypeName : stateTypeNames)
|
|
m_stateTypes.get(stateTypeName).enabled = true;
|
|
|
|
for (auto& pair : m_parts)
|
|
pair.second.activePartDirty = true;
|
|
}
|
|
|
|
bool AnimatedPartSet::stateTypeEnabled(String const& stateTypeName) const {
|
|
return m_stateTypes.get(stateTypeName).enabled;
|
|
}
|
|
|
|
StringList AnimatedPartSet::states(String const& stateTypeName) const {
|
|
return m_stateTypes.get(stateTypeName).states.keys();
|
|
}
|
|
|
|
StringList AnimatedPartSet::parts() const {
|
|
return m_parts.keys();
|
|
}
|
|
|
|
bool AnimatedPartSet::setActiveState(String const& stateTypeName, String const& stateName, bool alwaysStart) {
|
|
auto& stateType = m_stateTypes.get(stateTypeName);
|
|
if (stateType.activeState.stateName != stateName || alwaysStart) {
|
|
stateType.activeState.stateName = stateName;
|
|
stateType.activeState.timer = 0.0f;
|
|
stateType.activeStatePointer = stateType.states.get(stateName).get();
|
|
|
|
stateType.activeStateDirty = true;
|
|
for (auto& pair : m_parts)
|
|
pair.second.activePartDirty = true;
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void AnimatedPartSet::restartState(String const& stateTypeName) {
|
|
auto& stateType = m_stateTypes.get(stateTypeName);
|
|
stateType.activeState.timer = 0.0f;
|
|
|
|
stateType.activeStateDirty = true;
|
|
for (auto& pair : m_parts)
|
|
pair.second.activePartDirty = true;
|
|
}
|
|
|
|
AnimatedPartSet::ActiveStateInformation const& AnimatedPartSet::activeState(String const& stateTypeName) const {
|
|
auto& stateType = const_cast<StateType&>(m_stateTypes.get(stateTypeName));
|
|
const_cast<AnimatedPartSet*>(this)->freshenActiveState(stateType);
|
|
return stateType.activeState;
|
|
}
|
|
|
|
AnimatedPartSet::ActivePartInformation const& AnimatedPartSet::activePart(String const& partName) const {
|
|
auto& part = const_cast<Part&>(m_parts.get(partName));
|
|
const_cast<AnimatedPartSet*>(this)->freshenActivePart(part);
|
|
return part.activePart;
|
|
}
|
|
|
|
void AnimatedPartSet::forEachActiveState(function<void(String const&, ActiveStateInformation const&)> callback) const {
|
|
for (auto const& p : m_stateTypes) {
|
|
const_cast<AnimatedPartSet*>(this)->freshenActiveState(const_cast<StateType&>(p.second));
|
|
callback(p.first, p.second.activeState);
|
|
}
|
|
}
|
|
|
|
void AnimatedPartSet::forEachActivePart(function<void(String const&, ActivePartInformation const&)> callback) const {
|
|
for (auto const& p : m_parts) {
|
|
const_cast<AnimatedPartSet*>(this)->freshenActivePart(const_cast<Part&>(p.second));
|
|
callback(p.first, p.second.activePart);
|
|
}
|
|
}
|
|
|
|
size_t AnimatedPartSet::activeStateIndex(String const& stateTypeName) const {
|
|
auto const& stateType = m_stateTypes.get(stateTypeName);
|
|
return *stateType.states.indexOf(stateType.activeState.stateName);
|
|
}
|
|
|
|
bool AnimatedPartSet::setActiveStateIndex(String const& stateTypeName, size_t stateIndex, bool alwaysStart) {
|
|
auto const& stateType = m_stateTypes.get(stateTypeName);
|
|
String const& stateName = stateType.states.keyAt(stateIndex);
|
|
return setActiveState(stateTypeName, stateName, alwaysStart);
|
|
}
|
|
|
|
void AnimatedPartSet::update(float dt) {
|
|
for (auto& pair : m_stateTypes) {
|
|
auto& stateType = pair.second;
|
|
auto const& state = *stateType.activeStatePointer;
|
|
|
|
stateType.activeState.timer += dt;
|
|
if (stateType.activeState.timer > state.cycle) {
|
|
if (state.animationMode == End) {
|
|
stateType.activeState.timer = state.cycle;
|
|
} else if (state.animationMode == Loop) {
|
|
stateType.activeState.timer = std::fmod(stateType.activeState.timer, state.cycle);
|
|
} else if (state.animationMode == Transition) {
|
|
stateType.activeState.stateName = state.transitionState;
|
|
stateType.activeState.timer = 0.0f;
|
|
stateType.activeStatePointer = stateType.states.get(state.transitionState).get();
|
|
}
|
|
}
|
|
|
|
stateType.activeStateDirty = true;
|
|
}
|
|
|
|
for (auto& pair : m_parts)
|
|
pair.second.activePartDirty = true;
|
|
}
|
|
|
|
void AnimatedPartSet::finishAnimations() {
|
|
for (auto& pair : m_stateTypes) {
|
|
auto& stateType = pair.second;
|
|
|
|
while (true) {
|
|
auto const& state = *stateType.activeStatePointer;
|
|
|
|
if (state.animationMode == End) {
|
|
stateType.activeState.timer = state.cycle;
|
|
} else if (state.animationMode == Transition) {
|
|
stateType.activeState.stateName = state.transitionState;
|
|
stateType.activeState.timer = 0.0f;
|
|
stateType.activeStatePointer = stateType.states.get(state.transitionState).get();
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
stateType.activeStateDirty = true;
|
|
}
|
|
|
|
for (auto& pair : m_parts)
|
|
pair.second.activePartDirty = true;
|
|
}
|
|
|
|
AnimatedPartSet::AnimationMode AnimatedPartSet::stringToAnimationMode(String const& string) {
|
|
if (string.equals("end", String::CaseInsensitive)) {
|
|
return End;
|
|
} else if (string.equals("loop", String::CaseInsensitive)) {
|
|
return Loop;
|
|
} else if (string.equals("transition", String::CaseInsensitive)) {
|
|
return Transition;
|
|
} else {
|
|
throw AnimatedPartSetException(strf("No such AnimationMode '{}'", string));
|
|
}
|
|
}
|
|
|
|
void AnimatedPartSet::freshenActiveState(StateType& stateType) {
|
|
if (stateType.activeStateDirty) {
|
|
auto const& state = *stateType.activeStatePointer;
|
|
auto& activeState = stateType.activeState;
|
|
activeState.frame = clamp<int>(activeState.timer / state.cycle * state.frames, 0, state.frames - 1);
|
|
|
|
activeState.properties = stateType.stateTypeProperties;
|
|
activeState.properties.merge(state.stateProperties, true);
|
|
|
|
for (auto const& pair : state.stateFrameProperties) {
|
|
if (activeState.frame < pair.second.size())
|
|
activeState.properties[pair.first] = pair.second.get(activeState.frame);
|
|
}
|
|
|
|
stateType.activeStateDirty = false;
|
|
}
|
|
}
|
|
|
|
void AnimatedPartSet::freshenActivePart(Part& part) {
|
|
if (part.activePartDirty) {
|
|
// First reset all the active part information assuming that no state type
|
|
// x state match exists.
|
|
auto& activePart = part.activePart;
|
|
activePart.activeState = {};
|
|
activePart.properties = part.partProperties;
|
|
|
|
// Then go through each of the state types and states and look for a part
|
|
// state match in order of priority.
|
|
for (auto& stateTypePair : m_stateTypes) {
|
|
auto const& stateTypeName = stateTypePair.first;
|
|
auto& stateType = stateTypePair.second;
|
|
|
|
// Skip disabled state types
|
|
if (!stateType.enabled)
|
|
continue;
|
|
|
|
auto partStateType = part.partStates.ptr(stateTypeName);
|
|
if (!partStateType)
|
|
continue;
|
|
|
|
auto const& stateName = stateType.activeState.stateName;
|
|
auto partState = partStateType->ptr(stateName);
|
|
if (!partState)
|
|
continue;
|
|
|
|
// If we have a partState match, then set the active state information.
|
|
freshenActiveState(stateType);
|
|
activePart.activeState = stateType.activeState;
|
|
unsigned frame = stateType.activeState.frame;
|
|
|
|
// Then set the part state data, as well as any part state frame data if
|
|
// the current frame is within the list size.
|
|
activePart.properties.merge(partState->partStateProperties, true);
|
|
|
|
for (auto const& pair : partState->partStateFrameProperties) {
|
|
if (frame < pair.second.size())
|
|
activePart.properties[pair.first] = pair.second.get(frame);
|
|
}
|
|
|
|
// Each part can only have one state type x state match, so we are done.
|
|
break;
|
|
}
|
|
|
|
part.activePartDirty = false;
|
|
}
|
|
}
|
|
|
|
}
|