From 0bf17a8dc4a4e7c7c1b763a2e12ac0de27caff1a Mon Sep 17 00:00:00 2001 From: WasabiRaptor Date: Sun, 18 May 2025 20:05:41 -0400 Subject: big feature expansion --- source/base/StarAnimatedPartSet.cpp | 138 ++++++++++++++++++++++++++++++++++-- 1 file changed, 132 insertions(+), 6 deletions(-) (limited to 'source/base/StarAnimatedPartSet.cpp') diff --git a/source/base/StarAnimatedPartSet.cpp b/source/base/StarAnimatedPartSet.cpp index 5ffc68f..9e5f816 100644 --- a/source/base/StarAnimatedPartSet.cpp +++ b/source/base/StarAnimatedPartSet.cpp @@ -1,11 +1,12 @@ #include "StarAnimatedPartSet.hpp" #include "StarMathCommon.hpp" +#include "StarJsonExtra.hpp" namespace Star { AnimatedPartSet::AnimatedPartSet() {} -AnimatedPartSet::AnimatedPartSet(Json config) { +AnimatedPartSet::AnimatedPartSet(Json config, uint8_t animatorVersion) { for (auto const& stateTypePair : config.get("stateTypes", JsonObject()).iterateObject()) { auto const& stateTypeName = stateTypePair.first; auto const& stateTypeConfig = stateTypePair.second; @@ -33,6 +34,7 @@ AnimatedPartSet::AnimatedPartSet(Json config) { newStateType.states.sortByKey(); newStateType.activeState.stateTypeName = stateTypeName; + newStateType.activeState.reverse = false; newStateType.activeStateDirty = true; if (newStateType.defaultState.empty() && !newStateType.states.empty()) @@ -65,6 +67,7 @@ AnimatedPartSet::AnimatedPartSet(Json config) { } } newPart.activePart.partName = partPair.first; + newPart.activePart.setAnimationAffineTransform(Mat3F::identity()); newPart.activePartDirty = true; m_parts[partName] = std::move(newPart); @@ -110,11 +113,12 @@ StringList AnimatedPartSet::partNames() const { return m_parts.keys(); } -bool AnimatedPartSet::setActiveState(String const& stateTypeName, String const& stateName, bool alwaysStart) { +bool AnimatedPartSet::setActiveState(String const& stateTypeName, String const& stateName, bool alwaysStart, bool reverse) { auto& stateType = m_stateTypes.get(stateTypeName); - if (stateType.activeState.stateName != stateName || alwaysStart) { + if (stateType.activeState.stateName != stateName || alwaysStart || stateType.activeState.reverse != reverse) { stateType.activeState.stateName = stateName; stateType.activeState.timer = 0.0f; + stateType.activeState.frameProgress = 0.0f; stateType.activeStatePointer = stateType.states.get(stateName).get(); stateType.activeStateDirty = true; @@ -174,11 +178,15 @@ size_t AnimatedPartSet::activeStateIndex(String const& stateTypeName) const { auto const& stateType = m_stateTypes.get(stateTypeName); return *stateType.states.indexOf(stateType.activeState.stateName); } +bool AnimatedPartSet::activeStateReverse(String const& stateTypeName) const { + auto const& stateType = m_stateTypes.get(stateTypeName); + return stateType.activeState.reverse; +} -bool AnimatedPartSet::setActiveStateIndex(String const& stateTypeName, size_t stateIndex, bool alwaysStart) { +bool AnimatedPartSet::setActiveStateIndex(String const& stateTypeName, size_t stateIndex, bool alwaysStart, bool reverse) { auto const& stateType = m_stateTypes.get(stateTypeName); String const& stateName = stateType.states.keyAt(stateIndex); - return setActiveState(stateTypeName, stateName, alwaysStart); + return setActiveState(stateTypeName, stateName, alwaysStart, reverse); } void AnimatedPartSet::update(float dt) { @@ -247,15 +255,40 @@ void AnimatedPartSet::freshenActiveState(StateType& stateType) { if (stateType.activeStateDirty) { auto const& state = *stateType.activeStatePointer; auto& activeState = stateType.activeState; - activeState.frame = clamp(activeState.timer / state.cycle * state.frames, 0, state.frames - 1); + + double progress = (activeState.timer / state.cycle * state.frames); + activeState.frameProgress = std::fmod(progress, 1); + activeState.frame = clamp(progress, 0, state.frames - 1); + if (activeState.reverse) { + activeState.frame = (state.frames - 1) - activeState.frame; + if (state.animationMode == Loop) + if (activeState.frame <= 0 ) { + activeState.nextFrame = state.frames - 1; + } else { + activeState.nextFrame = clamp(activeState.frame - 1, 0, state.frames - 1); + } + } else { + if (state.animationMode == Loop) + if (activeState.frame >= (state.frames-1) ) { + activeState.nextFrame = 0; + } else { + activeState.nextFrame = clamp(activeState.frame + 1, 0, state.frames - 1); + } + } activeState.properties = stateType.stateTypeProperties; activeState.properties.merge(state.stateProperties, true); + activeState.nextProperties = activeState.properties; + for (auto const& pair : state.stateFrameProperties) { if (activeState.frame < pair.second.size()) activeState.properties[pair.first] = pair.second.get(activeState.frame); } + for (auto const& pair : state.stateFrameProperties) { + if (activeState.nextFrame < pair.second.size()) + activeState.nextProperties[pair.first] = pair.second.get(activeState.nextFrame); + } stateType.activeStateDirty = false; } @@ -292,16 +325,73 @@ void AnimatedPartSet::freshenActivePart(Part& part) { freshenActiveState(stateType); activePart.activeState = stateType.activeState; unsigned frame = stateType.activeState.frame; + unsigned nextFrame = stateType.activeState.nextFrame; // 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); + activePart.nextProperties = activePart.properties; for (auto const& pair : partState->partStateFrameProperties) { if (frame < pair.second.size()) activePart.properties[pair.first] = pair.second.get(frame); } + for (auto const& pair : partState->partStateFrameProperties) { + if (nextFrame< pair.second.size()) + activePart.nextProperties[pair.first] = pair.second.get(nextFrame); + } + if (version() > 0) { + auto processTransforms = [](Mat3F mat, JsonArray transforms, JsonObject properties) -> Mat3F { + for (auto const& v : transforms) { + auto actionData = v.toArray(); + auto action = actionData[0].toString(); + if (action == "reset") { + mat = Mat3F::identity(); + } else if (action == "translate") { + mat.translate(jsonToVec2F(actionData[1])); + } else if (action == "rotate") { + if (auto center = actionData[2]) { + mat.rotate(actionData[1].toFloat(), jsonToVec2F(center)); + } else if (auto center = properties.ptr("rotationCenter")) { + mat.rotate(actionData[1].toFloat(), jsonToVec2F(center)); + } else if (auto center = properties.ptr("center")) { + mat.rotate(actionData[1].toFloat(), jsonToVec2F(center)); + } else { + mat.rotate(actionData[1].toFloat()); + } + } else if (action == "scale") { + if (auto center = actionData[2]) { + mat.scale(actionData[1].toFloat(), jsonToVec2F(center)); + } else if (auto center = properties.ptr("scaleCenter")) { + mat.scale(actionData[1].toFloat(), jsonToVec2F(center)); + } else if (auto center = properties.ptr("center")) { + mat.scale(actionData[1].toFloat(), jsonToVec2F(center)); + } else { + mat.scale(actionData[1].toFloat()); + } + } else if (action == "transform") { + mat = Mat3F(actionData[1].toFloat(), actionData[2].toFloat(), actionData[3].toFloat(), actionData[4].toFloat(), actionData[5].toFloat(), actionData[6].toFloat(), 0, 0, 1) * mat; + } + } + return mat; + }; + + + if (auto transforms = activePart.properties.ptr("transforms")) { + auto mat = processTransforms(activePart.animationAffineTransform(), transforms->toArray(), activePart.properties); + if (activePart.properties.value("interpolated").optBool().value(false)) { + if (auto nextTransforms = activePart.nextProperties.ptr("transforms")) { + auto nextMat = processTransforms(activePart.animationAffineTransform(), nextTransforms->toArray(), activePart.nextProperties); + activePart.setAnimationAffineTransform(mat, nextMat, stateType.activeState.frameProgress); + } else { + activePart.setAnimationAffineTransform(mat); + } + } else { + activePart.setAnimationAffineTransform(mat); + } + } + } // Each part can only have one state type x state match, so we are done. break; } @@ -310,4 +400,40 @@ void AnimatedPartSet::freshenActivePart(Part& part) { } } +void AnimatedPartSet::ActivePartInformation::setAnimationAffineTransform(Mat3F const& matrix) { + xTranslationAnimation = matrix[0][2]; + yTranslationAnimation = matrix[1][2]; + xScaleAnimation = sqrt(square(matrix[0][0]) + square(matrix[0][1])); + yScaleAnimation = sqrt(square(matrix[1][0]) + square(matrix[1][1])); + xShearAnimation = atan2(matrix[0][1], matrix[0][0]); + yShearAnimation = atan2(matrix[1][0], matrix[1][1]); +} +void AnimatedPartSet::ActivePartInformation::setAnimationAffineTransform(Mat3F const& mat1, Mat3F const& mat2, float progress) { + xTranslationAnimation = mat1[0][2]; + yTranslationAnimation = mat1[1][2]; + xScaleAnimation = sqrt(square(mat1[0][0]) + square(mat1[0][1])); + yScaleAnimation = sqrt(square(mat1[1][0]) + square(mat1[1][1])); + xShearAnimation = atan2(mat1[0][1], mat1[0][0]); + yShearAnimation = atan2(mat1[1][0], mat1[1][1]); + + xTranslationAnimation += (mat2[0][2] - xTranslationAnimation) * progress; + yTranslationAnimation += (mat2[1][2] - yTranslationAnimation) * progress; + xScaleAnimation += (sqrt(square(mat2[0][0]) + square(mat2[0][1])) - xScaleAnimation) * progress; + yScaleAnimation += (sqrt(square(mat2[1][0]) + square(mat2[1][1])) - yScaleAnimation) * progress; + xShearAnimation += (atan2(mat2[0][1], mat2[0][0]) - xShearAnimation) * progress; + yShearAnimation += (atan2(mat2[1][0], mat2[1][1]) - yShearAnimation) * progress; +} + +Mat3F AnimatedPartSet::ActivePartInformation::animationAffineTransform() const { + return Mat3F( + xScaleAnimation * cos(xShearAnimation), xScaleAnimation * sin(xShearAnimation), xTranslationAnimation, + yScaleAnimation * sin(yShearAnimation), yScaleAnimation * cos(yShearAnimation), yTranslationAnimation, + 0, 0, 1 + ); +} + +uint8_t AnimatedPartSet::version() const { + return m_animatorVersion; +} + } -- cgit v1.2.3 From 72ace1219e2105d10fc41d20a4f9057cfaed250d Mon Sep 17 00:00:00 2001 From: WasabiRaptor Date: Sun, 18 May 2025 21:26:23 -0400 Subject: fixes after testing --- source/base/StarAnimatedPartSet.cpp | 29 ++++++-------------------- source/game/StarNetworkedAnimator.cpp | 39 +++++++++++------------------------ 2 files changed, 18 insertions(+), 50 deletions(-) (limited to 'source/base/StarAnimatedPartSet.cpp') diff --git a/source/base/StarAnimatedPartSet.cpp b/source/base/StarAnimatedPartSet.cpp index 9e5f816..fb6e6ac 100644 --- a/source/base/StarAnimatedPartSet.cpp +++ b/source/base/StarAnimatedPartSet.cpp @@ -344,34 +344,17 @@ void AnimatedPartSet::freshenActivePart(Part& part) { if (version() > 0) { auto processTransforms = [](Mat3F mat, JsonArray transforms, JsonObject properties) -> Mat3F { for (auto const& v : transforms) { - auto actionData = v.toArray(); - auto action = actionData[0].toString(); + auto action = v.getString(0); if (action == "reset") { mat = Mat3F::identity(); } else if (action == "translate") { - mat.translate(jsonToVec2F(actionData[1])); + mat.translate(jsonToVec2F(v.getArray(1))); } else if (action == "rotate") { - if (auto center = actionData[2]) { - mat.rotate(actionData[1].toFloat(), jsonToVec2F(center)); - } else if (auto center = properties.ptr("rotationCenter")) { - mat.rotate(actionData[1].toFloat(), jsonToVec2F(center)); - } else if (auto center = properties.ptr("center")) { - mat.rotate(actionData[1].toFloat(), jsonToVec2F(center)); - } else { - mat.rotate(actionData[1].toFloat()); - } + mat.rotate(v.getFloat(1), jsonToVec2F(v.getArray(2, properties.maybe("rotationCenter").value(JsonArray({0,0})).toArray()))); } else if (action == "scale") { - if (auto center = actionData[2]) { - mat.scale(actionData[1].toFloat(), jsonToVec2F(center)); - } else if (auto center = properties.ptr("scaleCenter")) { - mat.scale(actionData[1].toFloat(), jsonToVec2F(center)); - } else if (auto center = properties.ptr("center")) { - mat.scale(actionData[1].toFloat(), jsonToVec2F(center)); - } else { - mat.scale(actionData[1].toFloat()); - } + mat.scale(jsonToVec2F(v.getArray(1)), jsonToVec2F(v.getArray(2, properties.maybe("scalingCenter").value(JsonArray({0,0})).toArray()))); } else if (action == "transform") { - mat = Mat3F(actionData[1].toFloat(), actionData[2].toFloat(), actionData[3].toFloat(), actionData[4].toFloat(), actionData[5].toFloat(), actionData[6].toFloat(), 0, 0, 1) * mat; + mat = Mat3F(v.getFloat(1), v.getFloat(2), v.getFloat(3), v.getFloat(4), v.getFloat(5), v.getFloat(6), 0, 0, 1) * mat; } } return mat; @@ -380,7 +363,7 @@ void AnimatedPartSet::freshenActivePart(Part& part) { if (auto transforms = activePart.properties.ptr("transforms")) { auto mat = processTransforms(activePart.animationAffineTransform(), transforms->toArray(), activePart.properties); - if (activePart.properties.value("interpolated").optBool().value(false)) { + if (activePart.properties.maybe("interpolated").value(false)) { if (auto nextTransforms = activePart.nextProperties.ptr("transforms")) { auto nextMat = processTransforms(activePart.animationAffineTransform(), nextTransforms->toArray(), activePart.nextProperties); activePart.setAnimationAffineTransform(mat, nextMat, stateType.activeState.frameProgress); diff --git a/source/game/StarNetworkedAnimator.cpp b/source/game/StarNetworkedAnimator.cpp index 396738c..3319daf 100644 --- a/source/game/StarNetworkedAnimator.cpp +++ b/source/game/StarNetworkedAnimator.cpp @@ -90,8 +90,8 @@ NetworkedAnimator::NetworkedAnimator(Json config, String relativePath) : Network m_animatorVersion = config.getUInt("version", 0); if (version() > 0) { - if (auto v = config.get("includes")) - config = mergeIncludes(config, v, relativePath); + if (config.contains("includes")) + config = mergeIncludes(config, config.get("includes"), relativePath); } m_animatedParts = AnimatedPartSet(config.get("animatedParts", JsonObject()), version()); @@ -362,7 +362,7 @@ Mat3F NetworkedAnimator::globalTransformation() const { Mat3F NetworkedAnimator::groupTransformation(StringList const& transformationGroups) const { auto mat = Mat3F::identity(); for (auto const& tg : transformationGroups) - mat = m_transformationGroups.get(tg).affineTransform() * m_transformationGroups.get(tg).localAffineTransform() * mat; + mat = m_transformationGroups.get(tg).affineTransform() * m_transformationGroups.get(tg).localAffineTransform() * m_transformationGroups.get(tg).animationAffineTransform() * mat; return mat; } @@ -373,6 +373,8 @@ Mat3F NetworkedAnimator::partTransformation(String const& partName) const { if (auto offset = part.properties.value("offset").opt().apply(jsonToVec2F)) transformation = Mat3F::translation(*offset) * transformation; + transformation = part.animationAffineTransform() * transformation; + auto transformationGroups = jsonToStringList(part.properties.value("transformationGroups", JsonArray())); transformation = groupTransformation(transformationGroups) * transformation; @@ -956,34 +958,17 @@ void NetworkedAnimator::update(float dt, DynamicTarget* dynamicTarget) { if (version() > 0){ auto processTransforms = [](Mat3F mat, JsonArray transforms, JsonObject properties) -> Mat3F { for (auto const& v : transforms) { - auto actionData = v.toArray(); - auto action = actionData[0].toString(); + auto action = v.getString(0); if (action == "reset") { mat = Mat3F::identity(); } else if (action == "translate") { - mat.translate(jsonToVec2F(actionData[1])); + mat.translate(jsonToVec2F(v.getArray(1))); } else if (action == "rotate") { - if (auto center = actionData[2]) { - mat.rotate(actionData[1].toFloat(), jsonToVec2F(center)); - } else if (auto center = properties.ptr("rotationCenter")) { - mat.rotate(actionData[1].toFloat(), jsonToVec2F(center)); - } else if (auto center = properties.ptr("center")) { - mat.rotate(actionData[1].toFloat(), jsonToVec2F(center)); - } else { - mat.rotate(actionData[1].toFloat()); - } + mat.rotate(v.getFloat(1), jsonToVec2F(v.getArray(2, properties.maybe("rotationCenter").value(JsonArray({0,0})).toArray()))); } else if (action == "scale") { - if (auto center = actionData[2]) { - mat.scale(actionData[1].toFloat(), jsonToVec2F(center)); - } else if (auto center = properties.ptr("scaleCenter")) { - mat.scale(actionData[1].toFloat(), jsonToVec2F(center)); - } else if (auto center = properties.ptr("center")) { - mat.scale(actionData[1].toFloat(), jsonToVec2F(center)); - } else { - mat.scale(actionData[1].toFloat()); - } + mat.scale(jsonToVec2F(v.getArray(1)), jsonToVec2F(v.getArray(2, properties.maybe("scalingCenter").value(JsonArray({0,0})).toArray()))); } else if (action == "transform") { - mat = Mat3F(actionData[1].toFloat(), actionData[2].toFloat(), actionData[3].toFloat(), actionData[4].toFloat(), actionData[5].toFloat(), actionData[6].toFloat(), 0, 0, 1) * mat; + mat = Mat3F(v.getFloat(1), v.getFloat(2), v.getFloat(3), v.getFloat(4), v.getFloat(5), v.getFloat(6), 0, 0, 1) * mat; } } return mat; @@ -1330,8 +1315,8 @@ uint8_t NetworkedAnimator::version() const { Json NetworkedAnimator::mergeIncludes(Json config, Json includes, String relativePath){ for (Json const& path : includes.iterateArray()) { auto includeConfig = Root::singleton().assets()->json(AssetPath::relativeTo(relativePath, path.toString())); - if (auto v = includeConfig.get("includes")) - includeConfig = mergeIncludes(includeConfig, v, relativePath); + if (includeConfig.contains("includes")) + includeConfig = mergeIncludes(includeConfig, includeConfig.get("includes"), relativePath); config = jsonMerge(includeConfig, config); } return config; -- cgit v1.2.3 From c5788fd8495406d7e44bc55a8a531431c4afaee8 Mon Sep 17 00:00:00 2001 From: WasabiRaptor Date: Sun, 18 May 2025 22:41:12 -0400 Subject: part state alias shortcut --- source/base/StarAnimatedPartSet.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'source/base/StarAnimatedPartSet.cpp') diff --git a/source/base/StarAnimatedPartSet.cpp b/source/base/StarAnimatedPartSet.cpp index fb6e6ac..8826208 100644 --- a/source/base/StarAnimatedPartSet.cpp +++ b/source/base/StarAnimatedPartSet.cpp @@ -60,7 +60,9 @@ AnimatedPartSet::AnimatedPartSet(Json config, uint8_t animatorVersion) { for (auto const& partStatePair : partStateTypePair.second.toObject()) { auto const& stateName = partStatePair.first; - auto const& stateConfig = partStatePair.second; + auto stateConfig = partStatePair.second; + if ((version() > 0) && stateConfig.isType(Json::Type::String)) + stateConfig = partStatePair.second.get(stateConfig.toString()); PartState partState = {stateConfig.getObject("properties", {}), stateConfig.getObject("frameProperties", {})}; newPart.partStates[stateTypeName][stateName] = std::move(partState); -- cgit v1.2.3 From e703ea1614d92e4dddf104ef3bec789b37562c57 Mon Sep 17 00:00:00 2001 From: WasabiRaptor Date: Wed, 21 May 2025 22:34:08 -0400 Subject: potential fixes --- source/base/StarAnimatedPartSet.cpp | 2 +- source/game/StarNetworkedAnimator.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'source/base/StarAnimatedPartSet.cpp') diff --git a/source/base/StarAnimatedPartSet.cpp b/source/base/StarAnimatedPartSet.cpp index 8826208..9b9f9d7 100644 --- a/source/base/StarAnimatedPartSet.cpp +++ b/source/base/StarAnimatedPartSet.cpp @@ -76,7 +76,7 @@ AnimatedPartSet::AnimatedPartSet(Json config, uint8_t animatorVersion) { } for (auto const& pair : m_stateTypes) - setActiveState(pair.first, pair.second.defaultState, true); + setActiveState(pair.first, pair.second.defaultState, true, false); } StringList AnimatedPartSet::stateTypes() const { diff --git a/source/game/StarNetworkedAnimator.cpp b/source/game/StarNetworkedAnimator.cpp index 31bd066..6ea3053 100644 --- a/source/game/StarNetworkedAnimator.cpp +++ b/source/game/StarNetworkedAnimator.cpp @@ -1264,6 +1264,7 @@ void NetworkedAnimator::setupNetStates() { addNetElement(&m_partTags[part]); for (auto& pair : m_stateInfo) { + pair.second.wasUpdated = true; pair.second.reverse.setCompatibilityVersion(8); addNetElement(&pair.second.reverse); addNetElement(&pair.second.stateIndex); @@ -1356,7 +1357,7 @@ void NetworkedAnimator::netElementsNeedLoad(bool initial) { void NetworkedAnimator::netElementsNeedStore() { for (auto& pair : m_stateInfo) { - if (pair.second.wasUpdated) { + if (pair.second.wasUpdated || (version() < 1)) { pair.second.stateIndex.set(m_animatedParts.activeStateIndex(pair.first)); pair.second.reverse.set(m_animatedParts.activeStateReverse(pair.first)); } -- cgit v1.2.3