From 9736148d7db49e5557201e099ca897fc8868c9d8 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Wed, 25 Oct 2023 14:46:07 -0300 Subject: [PATCH 1/6] NewCommand 2052 - AnimateVariable @raw 2052, "linear/bounceOut", 0, 1, 0, 0, 0, 240, 0, 640, 0, 0 ` CommandInterpolateVariable("easeTypeStart/easeTypeEnd",[targetIsVar, target, StartIsVar, start, endIsVar, end, durationIsVar, duration, pauseIsVar, pause]) ` cubic bezier is broken. There are some libraries about it, I'll see what to do. I'll need help with this one. It's almost fully working. --- src/game_interpreter.cpp | 333 +++++++++++++++++++++++++++++++++++++++ src/game_interpreter.h | 1 + 2 files changed, 334 insertions(+) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 91821c45e0..cb51fba79c 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -786,6 +786,8 @@ bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) { return CommandManiacCallCommand(com); case Cmd::Maniac_GetGameInfo: return CommandManiacGetGameInfo(com); + case Cmd::EasyRpg_AnimateVariable: + return CommandEasyRpgAnimateVariable(com); case Cmd::EasyRpg_SetInterpreterFlag: return CommandEasyRpgSetInterpreterFlag(com); case Cmd::EasyRpg_ProcessJson: @@ -5502,3 +5504,334 @@ int Game_Interpreter::ManiacBitmask(int value, int mask) const { return value; } +std::vector parseBezier(const std::string& bezierParams) { + std::vector params; + std::string temp; + size_t startPos = bezierParams.find("(") + 1; + size_t endPos = bezierParams.find(")"); + std::string valuesString = bezierParams.substr(startPos, endPos - startPos); + + size_t commaPos = valuesString.find(","); + while (commaPos != std::string::npos) { + temp = valuesString.substr(0, commaPos); + params.push_back(std::stod(temp)); + valuesString = valuesString.substr(commaPos + 1); + commaPos = valuesString.find(","); + } + // Push the last value into the vector + params.push_back(std::stod(valuesString)); + + return params; +} + +//FIXME: cubicBezier is completely Broken +// references to how it should work: +// https://matthewlein.com/tools/ceaser +// https://cubic-bezier.com/ + +double cubicBezier(double t, double p0, double p1, double p2, double p3) { + double u = 1 - t; + double tt = t * t; + double uu = u * u; + double uuu = uu * u; + double ttt = tt * t; + + double p = uuu * p0; // (1-t)^3 + double q = 3 * uu * t * p1; // 3t(1-t)^2 + double r = 3 * u * tt * p2; // 3(1-t)t^2 + double s = ttt * p3; // t^3 + + return p + q + r + s; +} + +double getEasedT(const std::string& easingType, double t, double b, double c, double d) { + if (easingType == "linear") { + return c * t / d + b; + } + else if (easingType == "quadIn") { + t /= d; + return c * t * t + b; + } + else if (easingType == "quadOut") { + t /= d; + return -c * t * (t - 2) + b; + } + else if (easingType == "quadInOut") { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t + b; + } + else { + t -= 1; + return -c / 2 * (t * (t - 2) - 1) + b; + } + } + else if (easingType == "cubicIn") { + t /= d; + return c * t * t * t + b; + } + else if (easingType == "cubicOut") { + t = (t / d) - 1; + return c * (t * t * t + 1) + b; + } + else if (easingType == "cubicInOut") { + t /= d / 2; + if (t < 1) { + return c / 2 * t * t * t + b; + } + else { + t -= 2; + return c / 2 * (t * t * t + 2) + b; + } + } + else if (easingType == "sinIn") { + return -c * cos(t / d * (M_PI / 2)) + c + b; + } + else if (easingType == "sinOut") { + return c * sin(t / d * (M_PI / 2)) + b; + } + else if (easingType == "sinInOut") { + return -c / 2 * (cos(M_PI * t / d) - 1) + b; + } + else if (easingType == "expoIn") { + return c * pow(2, 10 * (t / d - 1)) + b; + } + else if (easingType == "expoOut") { + return c * (-pow(2, -10 * t / d) + 1) + b; + } + else if (easingType == "expoInOut") { + t /= d / 2; + if (t < 1) { + return c / 2 * pow(2, 10 * (t - 1)) + b; + } + else { + t -= 1; + return c / 2 * (-pow(2, -10 * t) + 2) + b; + } + } + else if (easingType == "circIn") { + t /= d; + return -c * (sqrt(1 - t * t) - 1) + b; + } + else if (easingType == "circOut") { + t = (t / d) - 1; + return c * sqrt(1 - t * t) + b; + } + else if (easingType == "circInOut") { + t /= d / 2; + if (t < 1) { + return -c / 2 * (sqrt(1 - t * t) - 1) + b; + } + else { + t -= 2; + return c / 2 * (sqrt(1 - t * t) + 1) + b; + } + } + else if (easingType == "elasticIn") { + if (t == 0) { + return b; + } + if ((t /= d) == 1) { + return b + c; + } + + double p = d * 0.3; + double a = c; + double s = p / 4; + + double postFix = a * pow(2, 10 * (t -= 1)); // this is a fix, again, with post-increment operators + return -(postFix * sin((t * d - s) * (2 * M_PI) / p)) + b; + } + else if (easingType == "elasticOut") { + if (t == 0) { + return b; + } + if ((t /= d) == 1) { + return b + c; + } + + double p = d * 0.3; + double a = c; + double s = p / 4; + + return (a * pow(2, -10 * t) * sin((t * d - s) * (2 * M_PI) / p) + c + b); + } + else if (easingType == "elasticInOut") { + if (t == 0) { + return b; + } + if ((t /= d / 2) == 2) { + return b + c; + } + + double p = d * (0.3 * 1.5); + double a = c; + double s = p / 4; + + if (t < 1) { + double postFix = a * pow(2, 10 * (t -= 1)); // this is a fix, again, with post-increment operators + return -0.5 * (postFix * sin((t * d - s) * (2 * M_PI) / p)) + b; + } + + double postFix = a * pow(2, -10 * (t -= 1)); // this is a fix, again, with post-increment operators + return postFix * sin((t * d - s) * (2 * M_PI) / p) * 0.5 + c + b; + } + else if (easingType == "bounceIn") { + return c - getEasedT("bounceOut", d - t, 0, c, d) + b; + } + else if (easingType == "bounceOut") { + if ((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } + else if (t < (2 / 2.75)) { + t -= (1.5 / 2.75); + return c * (7.5625 * t * t + 0.75) + b; + } + else if (t < (2.5 / 2.75)) { + t -= (2.25 / 2.75); + return c * (7.5625 * t * t + 0.9375) + b; + } + else { + t -= (2.625 / 2.75); + return c * (7.5625 * t * t + 0.984375) + b; + } + } + else if (easingType == "bounceInOut") { + if (t < d / 2) { + return getEasedT("bounceIn", t * 2, 0, c, d) * 0.5 + b; + } + else { + return getEasedT("bounceOut", t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; + } + } + if (easingType.substr(0, 6) == "bezier") { + std::vector < double > bezierParams = parseBezier(easingType.substr(7)); + if (bezierParams.size() == 4) { + return cubicBezier(t / d, bezierParams[0], bezierParams[1], bezierParams[2], bezierParams[3]); + } + } + + return c * t / d + b; // Default to linear easing if the easing type is not recognized +} + +std::vector interpolate(double start, double end, double duration, const std::string& easingTypeAtStart, const std::string& easingTypeAtEnd) { + std::vector interpolatedValues; + interpolatedValues.push_back(start); + + // Calculate the number of steps based on the duration + int numSteps = static_cast(duration); // Convert duration to an integer + double stepSize = 1.0 / numSteps; + + // Calculate the halfway point + double halfway = start + (end - start) * 0.5; + + if (easingTypeAtEnd == "null") { + // Use easingTypeAtStart for the entire animation + for (int step = 1; step <= numSteps; ++step) { + double t = step * stepSize; + double easedT = getEasedT(easingTypeAtStart, t, 0, 1, 1); // Call getEasedT with appropriate parameters + double interpolatedValue = start + easedT * (end - start); + interpolatedValues.push_back(interpolatedValue); + } + } + else { + // Generate the first half of the interpolation + for (int step = 1; step <= numSteps / 2; ++step) { + double t = step * stepSize; + double normalizedT = t / 0.5; // Normalize the time for the first half + double easedT = getEasedT(easingTypeAtStart, normalizedT, 0, 1, 1); // Call getEasedT with appropriate parameters + double interpolatedValue = start + easedT * (halfway - start); + interpolatedValues.push_back(interpolatedValue); + } + + // Generate the second half of the interpolation + for (int step = numSteps / 2 + 1; step <= numSteps; ++step) { + double t = step * stepSize; + double normalizedT = (t - 0.5) / 0.5; // Normalize the time for the second half + double easedT = getEasedT(easingTypeAtEnd, normalizedT, 0, 1, 1); // Call getEasedT with appropriate parameters + double interpolatedValue = halfway + easedT * (end - halfway); + interpolatedValues.push_back(interpolatedValue); + } + } + + interpolatedValues.push_back(end); + + return interpolatedValues; +} + +bool Game_Interpreter::CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand const& com) { + // CommandInterpolateVariable("typeStart/typeEnd",[useVarTarget, target, useVarStart, start, useVarEnd, end, useVarDuration, duration]) + + auto* frame = GetFramePtr(); + const auto& list = frame->commands; + auto& index = frame->current_command; + + int i = frame->current_command + 1; + + // Extract parameters: target, start, end, and duration for the animation + int32_t target = ValueOrVariable(com.parameters[0], com.parameters[1]); + int32_t start = ValueOrVariable(com.parameters[2], com.parameters[3]); + int32_t end = ValueOrVariable(com.parameters[4], com.parameters[5]); + int32_t duration = ValueOrVariable(com.parameters[6], com.parameters[7]); + + // Prepare animation-related commands + lcf::rpg::EventCommand waitCom; + waitCom.code = int(Cmd::Wait); + + lcf::rpg::EventCommand updateVarCom; + updateVarCom.code = int(Cmd::ControlVars); + std::vector updateVarParams = { 0, static_cast(target), 0, 0, 0, static_cast(end) }; + updateVarCom.parameters = lcf::DBArray(updateVarParams.begin(), updateVarParams.end()); + + lcf::rpg::EventCommand branchCom; + branchCom.code = int(Cmd::ShowChoiceOption); + + // Extract easing information + std::string easeStart = ToString(com.string); + std::string easeEnd = "null"; + + std::size_t pos = easeStart.find('/'); + + if (pos != std::string::npos) { + easeEnd = easeStart.substr(pos + 1); + easeStart = easeStart.substr(0, pos); + } + + // Check if new commands don't exist in the timeline yet + if (!(i < frame->commands.size() && frame->commands.at(i).code == int(Cmd::ShowChoiceOption))) { + // Insert animation commands + Output::Debug("inserting animation commands"); + std::vector interpolatedValues = interpolate(start, end, duration, easeStart, easeEnd); + + // Insert ShowChoiceOption command + // This helps me isolating all the "keyframes" commands inside a nested commands, it also helps to avoid creating a repeated list. + // It's problematic when "start", "end" and "duration" are variables. + frame->commands.insert(frame->commands.begin() + i, branchCom); + i++; + + // Insert updateVarCom and waitCom commands for each interpolated value + for (int value : interpolatedValues) { + updateVarParams.back() = value; + updateVarCom.parameters = lcf::DBArray(updateVarParams.begin(), updateVarParams.end()); + updateVarCom.indent = com.indent + 1; + + frame->commands.insert(frame->commands.begin() + i, updateVarCom); + i++; + frame->commands.insert(frame->commands.begin() + i, waitCom); + i++; + } + + // Insert ShowChoiceEnd command + branchCom.code = int(Cmd::ShowChoiceEnd); + frame->commands.insert(frame->commands.begin() + i, branchCom); + i++; + } + else { + Output::Debug("Animated Commands Already Exists"); + } + + // Update current_command index and return true to indicate success + frame->current_command = index + 2; + return false; +} + diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 04bf3afda3..d69d9b8315 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -297,6 +297,7 @@ class Game_Interpreter : public Game_BaseInterpreterContext bool CommandManiacSetGameOption(lcf::rpg::EventCommand const& com); bool CommandManiacControlStrings(lcf::rpg::EventCommand const& com); bool CommandManiacCallCommand(lcf::rpg::EventCommand const& com); + bool CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand const& com); bool CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand const& com); bool CommandEasyRpgProcessJson(lcf::rpg::EventCommand const& com); bool CommandEasyRpgCloneMapEvent(lcf::rpg::EventCommand const& com); From 0fde051b0e8383e47866321b9caade5ff962514c Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Fri, 24 Nov 2023 20:32:41 -0300 Subject: [PATCH 2/6] AnimateVars - Refactor to use Interpreter's Push( ) command --- src/game_interpreter.cpp | 62 +++++++++++----------------------------- 1 file changed, 17 insertions(+), 45 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index cb51fba79c..ca924dd597 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5760,15 +5760,8 @@ std::vector interpolate(double start, double end, double duration, const } bool Game_Interpreter::CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand const& com) { - // CommandInterpolateVariable("typeStart/typeEnd",[useVarTarget, target, useVarStart, start, useVarEnd, end, useVarDuration, duration]) + // $InterpolateVariable("typeStart/typeEnd",[useVarTarget, target, useVarStart, start, useVarEnd, end, useVarDuration, duration]) - auto* frame = GetFramePtr(); - const auto& list = frame->commands; - auto& index = frame->current_command; - - int i = frame->current_command + 1; - - // Extract parameters: target, start, end, and duration for the animation int32_t target = ValueOrVariable(com.parameters[0], com.parameters[1]); int32_t start = ValueOrVariable(com.parameters[2], com.parameters[3]); int32_t end = ValueOrVariable(com.parameters[4], com.parameters[5]); @@ -5778,13 +5771,12 @@ bool Game_Interpreter::CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand cons lcf::rpg::EventCommand waitCom; waitCom.code = int(Cmd::Wait); - lcf::rpg::EventCommand updateVarCom; - updateVarCom.code = int(Cmd::ControlVars); - std::vector updateVarParams = { 0, static_cast(target), 0, 0, 0, static_cast(end) }; - updateVarCom.parameters = lcf::DBArray(updateVarParams.begin(), updateVarParams.end()); + lcf::rpg::EventCommand animatedCom; + animatedCom.code = int(Cmd::ControlVars); + std::vector animatedVarParams = { 0, static_cast(target), 0, 0, 0, static_cast(end) }; + animatedCom.parameters = lcf::DBArray(animatedVarParams.begin(), animatedVarParams.end()); - lcf::rpg::EventCommand branchCom; - branchCom.code = int(Cmd::ShowChoiceOption); + std::vector cmdList; // Extract easing information std::string easeStart = ToString(com.string); @@ -5797,41 +5789,21 @@ bool Game_Interpreter::CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand cons easeStart = easeStart.substr(0, pos); } - // Check if new commands don't exist in the timeline yet - if (!(i < frame->commands.size() && frame->commands.at(i).code == int(Cmd::ShowChoiceOption))) { - // Insert animation commands - Output::Debug("inserting animation commands"); - std::vector interpolatedValues = interpolate(start, end, duration, easeStart, easeEnd); - - // Insert ShowChoiceOption command - // This helps me isolating all the "keyframes" commands inside a nested commands, it also helps to avoid creating a repeated list. - // It's problematic when "start", "end" and "duration" are variables. - frame->commands.insert(frame->commands.begin() + i, branchCom); - i++; - - // Insert updateVarCom and waitCom commands for each interpolated value - for (int value : interpolatedValues) { - updateVarParams.back() = value; - updateVarCom.parameters = lcf::DBArray(updateVarParams.begin(), updateVarParams.end()); - updateVarCom.indent = com.indent + 1; + // Insert animation commands + std::vector interpolatedValues = interpolate(start, end, duration, easeStart, easeEnd); - frame->commands.insert(frame->commands.begin() + i, updateVarCom); - i++; - frame->commands.insert(frame->commands.begin() + i, waitCom); - i++; - } + // Insert animatedCom and waitCom commands for each interpolated value + for (int value : interpolatedValues) { + animatedVarParams.back() = value; + animatedCom.parameters = lcf::DBArray(animatedVarParams.begin(), animatedVarParams.end()); + animatedCom.indent = com.indent + 1; - // Insert ShowChoiceEnd command - branchCom.code = int(Cmd::ShowChoiceEnd); - frame->commands.insert(frame->commands.begin() + i, branchCom); - i++; - } - else { - Output::Debug("Animated Commands Already Exists"); + cmdList.push_back(animatedCom); + cmdList.push_back(waitCom); } // Update current_command index and return true to indicate success - frame->current_command = index + 2; - return false; + Push(cmdList, 0, false); + return true; } From 4bda7c20407255e3d7a35494fe4959c19e519089 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Sat, 25 Nov 2023 05:07:04 -0300 Subject: [PATCH 3/6] AnimateVars - Major Refactor, everything works! Update game_interpreter.cpp --- src/game_interpreter.cpp | 287 +++++++++++++++------------------------ 1 file changed, 107 insertions(+), 180 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index ca924dd597..54bf0336d8 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5504,129 +5504,65 @@ int Game_Interpreter::ManiacBitmask(int value, int mask) const { return value; } -std::vector parseBezier(const std::string& bezierParams) { - std::vector params; - std::string temp; - size_t startPos = bezierParams.find("(") + 1; - size_t endPos = bezierParams.find(")"); - std::string valuesString = bezierParams.substr(startPos, endPos - startPos); - size_t commaPos = valuesString.find(","); - while (commaPos != std::string::npos) { - temp = valuesString.substr(0, commaPos); - params.push_back(std::stod(temp)); - valuesString = valuesString.substr(commaPos + 1); - commaPos = valuesString.find(","); - } - // Push the last value into the vector - params.push_back(std::stod(valuesString)); - - return params; -} - -//FIXME: cubicBezier is completely Broken -// references to how it should work: +// references for cubic bezier: // https://matthewlein.com/tools/ceaser // https://cubic-bezier.com/ +double cubicBezier(float t, const double& p0,const double& p1, const double& p2, const double& p3) { -double cubicBezier(double t, double p0, double p1, double p2, double p3) { - double u = 1 - t; - double tt = t * t; - double uu = u * u; - double uuu = uu * u; - double ttt = tt * t; + float u = 1 - t; + float tt = t * t; + float uu = u * u; + float uuu = uu * u; + float ttt = tt * t; - double p = uuu * p0; // (1-t)^3 - double q = 3 * uu * t * p1; // 3t(1-t)^2 - double r = 3 * u * tt * p2; // 3(1-t)t^2 - double s = ttt * p3; // t^3 + //Point2d p = {0,0}; + //p.x = uuu * 0 + 3 * uu * t * p0 + 3 * u * tt * p2 + ttt * 1; + return uuu * 0 + 3 * uu * t * p1 + 3 * u * tt * p3 + ttt * 1; - return p + q + r + s; + //return p.y; } -double getEasedT(const std::string& easingType, double t, double b, double c, double d) { - if (easingType == "linear") { - return c * t / d + b; - } - else if (easingType == "quadIn") { - t /= d; - return c * t * t + b; - } - else if (easingType == "quadOut") { - t /= d; - return -c * t * (t - 2) + b; - } - else if (easingType == "quadInOut") { - t /= d / 2; - if (t < 1) { - return c / 2 * t * t + b; - } - else { - t -= 1; - return -c / 2 * (t * (t - 2) - 1) + b; - } - } - else if (easingType == "cubicIn") { - t /= d; - return c * t * t * t + b; - } - else if (easingType == "cubicOut") { - t = (t / d) - 1; - return c * (t * t * t + 1) + b; - } - else if (easingType == "cubicInOut") { - t /= d / 2; - if (t < 1) { - return c / 2 * t * t * t + b; - } - else { - t -= 2; - return c / 2 * (t * t * t + 2) + b; - } - } - else if (easingType == "sinIn") { - return -c * cos(t / d * (M_PI / 2)) + c + b; - } - else if (easingType == "sinOut") { - return c * sin(t / d * (M_PI / 2)) + b; - } - else if (easingType == "sinInOut") { - return -c / 2 * (cos(M_PI * t / d) - 1) + b; - } - else if (easingType == "expoIn") { - return c * pow(2, 10 * (t / d - 1)) + b; - } - else if (easingType == "expoOut") { - return c * (-pow(2, -10 * t / d) + 1) + b; - } - else if (easingType == "expoInOut") { - t /= d / 2; - if (t < 1) { - return c / 2 * pow(2, 10 * (t - 1)) + b; - } - else { - t -= 1; - return c / 2 * (-pow(2, -10 * t) + 2) + b; - } - } - else if (easingType == "circIn") { - t /= d; - return -c * (sqrt(1 - t * t) - 1) + b; - } - else if (easingType == "circOut") { - t = (t / d) - 1; - return c * sqrt(1 - t * t) + b; - } - else if (easingType == "circInOut") { - t /= d / 2; - if (t < 1) { - return -c / 2 * (sqrt(1 - t * t) - 1) + b; - } - else { - t -= 2; - return c / 2 * (sqrt(1 - t * t) + 1) + b; - } - } +double getEasedTime(const std::string& easingType, double t, double b, double c, double d) { + if (easingType == "linear") return cubicBezier(t, 0.250, 0.250, 0.750, 0.750); + + else if (easingType == "ease") return cubicBezier(t, 0.250, 0.100, 0.250, 1.000); + else if (easingType == "easeIn") return cubicBezier(t, 0.420, 0.000, 1.000, 1.000); + else if (easingType == "easeOut") return cubicBezier(t, 0.000, 0.000, 0.580, 1.000); + else if (easingType == "easeInOut") return cubicBezier(t, 0.420, 0.000, 0.580, 1.000); + + else if (easingType == "quadIn") return cubicBezier(t, 0.550, 0.085, 0.680, 0.530); + else if (easingType == "quadOut") return cubicBezier(t, 0.250, 0.460, 0.450, 0.940); + else if (easingType == "quadInOut") return cubicBezier(t, 0.455, 0.030, 0.515, 0.955); + + else if (easingType == "cubicIn") return cubicBezier(t, 0.550, 0.055, 0.675, 0.190); + else if (easingType == "cubicOut") return cubicBezier(t, 0.215, 0.610, 0.355, 1.000); + else if (easingType == "cubicInOut") return cubicBezier(t, 0.645, 0.045, 0.355, 1.000); + + else if (easingType == "quartIn") return cubicBezier(t, 0.895, 0.030, 0.685, 0.220); + else if (easingType == "quartOut") return cubicBezier(t, 0.165, 0.840, 0.440, 1.000); + else if (easingType == "quartInOut") return cubicBezier(t, 0.770, 0.000, 0.175, 1.000); + + else if (easingType == "quintIn") return cubicBezier(t, 0.755, 0.050, 0.855, 0.060); + else if (easingType == "quintOut") return cubicBezier(t, 0.230, 1.000, 0.320, 1.000); + else if (easingType == "quintInOut") return cubicBezier(t, 0.860, 0.000, 0.070, 1.000); + + else if (easingType == "sineIn") return cubicBezier(t, 0.470, 0.000, 0.745, 0.715); + else if (easingType == "sineOut") return cubicBezier(t, 0.390, 0.575, 0.565, 1.000); + else if (easingType == "sineInOut") return cubicBezier(t, 0.445, 0.050, 0.550, 0.950); + + else if (easingType == "ExpoIn") return cubicBezier(t, 0.950, 0.050, 0.795, 0.035); + else if (easingType == "expoOut") return cubicBezier(t, 0.190, 1.000, 0.220, 1.000); + else if (easingType == "expoInOut") return cubicBezier(t, 1.000, 0.000, 0.000, 1.000); + + else if (easingType == "circIn") return cubicBezier(t, 0.600, 0.040, 0.980, 0.335); + else if (easingType == "circOut") return cubicBezier(t, 0.075, 0.820, 0.165, 1.000); + else if (easingType == "circInOut") return cubicBezier(t, 0.785, 0.135, 0.150, 0.860); + + else if (easingType == "backIn") return cubicBezier(t, 0.600, -0.280, 0.735, 0.045); + else if (easingType == "backOut") return cubicBezier(t, 0.175, 0.885, 0.320, 1.275); + else if (easingType == "backInOut") return cubicBezier(t, 0.680, -0.550, 0.265, 1.550); + else if (easingType == "elasticIn") { if (t == 0) { return b; @@ -5676,8 +5612,9 @@ double getEasedT(const std::string& easingType, double t, double b, double c, do double postFix = a * pow(2, -10 * (t -= 1)); // this is a fix, again, with post-increment operators return postFix * sin((t * d - s) * (2 * M_PI) / p) * 0.5 + c + b; } + else if (easingType == "bounceIn") { - return c - getEasedT("bounceOut", d - t, 0, c, d) + b; + return c - getEasedTime("bounceOut", d - t, 0, c, d) + b; } else if (easingType == "bounceOut") { if ((t /= d) < (1 / 2.75)) { @@ -5698,65 +5635,30 @@ double getEasedT(const std::string& easingType, double t, double b, double c, do } else if (easingType == "bounceInOut") { if (t < d / 2) { - return getEasedT("bounceIn", t * 2, 0, c, d) * 0.5 + b; + return getEasedTime("bounceIn", t * 2, 0, c, d) * 0.5 + b; } else { - return getEasedT("bounceOut", t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; - } - } - if (easingType.substr(0, 6) == "bezier") { - std::vector < double > bezierParams = parseBezier(easingType.substr(7)); - if (bezierParams.size() == 4) { - return cubicBezier(t / d, bezierParams[0], bezierParams[1], bezierParams[2], bezierParams[3]); + return getEasedTime("bounceOut", t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; } } - return c * t / d + b; // Default to linear easing if the easing type is not recognized -} - -std::vector interpolate(double start, double end, double duration, const std::string& easingTypeAtStart, const std::string& easingTypeAtEnd) { - std::vector interpolatedValues; - interpolatedValues.push_back(start); + if (easingType.substr(0, 6) == "bezier") { + std::vector bezierParams; - // Calculate the number of steps based on the duration - int numSteps = static_cast(duration); // Convert duration to an integer - double stepSize = 1.0 / numSteps; + size_t startPos = easingType.find("(") + 1; + size_t endPos = easingType.find(")"); + std::string valuesString = easingType.substr(startPos, endPos - startPos); - // Calculate the halfway point - double halfway = start + (end - start) * 0.5; + std::istringstream iss(valuesString); + double value; - if (easingTypeAtEnd == "null") { - // Use easingTypeAtStart for the entire animation - for (int step = 1; step <= numSteps; ++step) { - double t = step * stepSize; - double easedT = getEasedT(easingTypeAtStart, t, 0, 1, 1); // Call getEasedT with appropriate parameters - double interpolatedValue = start + easedT * (end - start); - interpolatedValues.push_back(interpolatedValue); - } - } - else { - // Generate the first half of the interpolation - for (int step = 1; step <= numSteps / 2; ++step) { - double t = step * stepSize; - double normalizedT = t / 0.5; // Normalize the time for the first half - double easedT = getEasedT(easingTypeAtStart, normalizedT, 0, 1, 1); // Call getEasedT with appropriate parameters - double interpolatedValue = start + easedT * (halfway - start); - interpolatedValues.push_back(interpolatedValue); - } + while (iss >> value) bezierParams.push_back(value), iss.ignore(); - // Generate the second half of the interpolation - for (int step = numSteps / 2 + 1; step <= numSteps; ++step) { - double t = step * stepSize; - double normalizedT = (t - 0.5) / 0.5; // Normalize the time for the second half - double easedT = getEasedT(easingTypeAtEnd, normalizedT, 0, 1, 1); // Call getEasedT with appropriate parameters - double interpolatedValue = halfway + easedT * (end - halfway); - interpolatedValues.push_back(interpolatedValue); - } + if (bezierParams.size() == 4) + return cubicBezier(t, bezierParams[0], bezierParams[1], bezierParams[2], bezierParams[3]); } - interpolatedValues.push_back(end); - - return interpolatedValues; + return c * t / d + b; // Default to linear easing if the easing type is not recognized } bool Game_Interpreter::CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand const& com) { @@ -5767,6 +5669,17 @@ bool Game_Interpreter::CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand cons int32_t end = ValueOrVariable(com.parameters[4], com.parameters[5]); int32_t duration = ValueOrVariable(com.parameters[6], com.parameters[7]); + // Extract easing information + std::string easingTypeAtStart = ToString(com.string); + std::string easingTypeAtEnd = "null"; + + std::size_t pos = easingTypeAtStart.find('/'); + + if (pos != std::string::npos) { + easingTypeAtEnd = easingTypeAtStart.substr(pos + 1); + easingTypeAtStart = easingTypeAtStart.substr(0, pos); + } + // Prepare animation-related commands lcf::rpg::EventCommand waitCom; waitCom.code = int(Cmd::Wait); @@ -5774,35 +5687,49 @@ bool Game_Interpreter::CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand cons lcf::rpg::EventCommand animatedCom; animatedCom.code = int(Cmd::ControlVars); std::vector animatedVarParams = { 0, static_cast(target), 0, 0, 0, static_cast(end) }; - animatedCom.parameters = lcf::DBArray(animatedVarParams.begin(), animatedVarParams.end()); std::vector cmdList; - // Extract easing information - std::string easeStart = ToString(com.string); - std::string easeEnd = "null"; + int numSteps = static_cast(duration); + double stepSize = 1.0 / numSteps; - std::size_t pos = easeStart.find('/'); + for (int step = 1; step <= numSteps; ++step) { + double normalizedTime; + double currentTime = step * stepSize; + double halfway; - if (pos != std::string::npos) { - easeEnd = easeStart.substr(pos + 1); - easeStart = easeStart.substr(0, pos); - } + std::string easingType; + + if (easingTypeAtEnd == "null") { // use a single interpolation. + normalizedTime = currentTime; + easingType = easingTypeAtStart; + halfway = (step <= numSteps / 2) ? end : start; + } + else { + if (step <= numSteps / 2) { // use 2 interpolations: start and end. + normalizedTime = currentTime / 0.5; + easingType = easingTypeAtStart; + } + else { + normalizedTime = (currentTime - 0.5) / 0.5; + easingType = easingTypeAtEnd; + } + halfway = start + 0.5 * (end - start); + } + + double easedTime = getEasedTime(easingType, normalizedTime, 0, 1, 1); - // Insert animation commands - std::vector interpolatedValues = interpolate(start, end, duration, easeStart, easeEnd); + double startValue = (step <= numSteps / 2) ? start : halfway; + double endValue = (step <= numSteps / 2) ? halfway : end; + double interpolatedValue = startValue + easedTime * (endValue - startValue); - // Insert animatedCom and waitCom commands for each interpolated value - for (int value : interpolatedValues) { - animatedVarParams.back() = value; + animatedVarParams.back() = interpolatedValue; animatedCom.parameters = lcf::DBArray(animatedVarParams.begin(), animatedVarParams.end()); - animatedCom.indent = com.indent + 1; cmdList.push_back(animatedCom); cmdList.push_back(waitCom); } - // Update current_command index and return true to indicate success Push(cmdList, 0, false); return true; } From eec6d7700b5cecd2cb2982c12102efd642ca7077 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Fri, 10 May 2024 18:38:44 -0300 Subject: [PATCH 4/6] AnimateVars - Syntax Rewritten and Cleanup - Moved 2 longer functions to a file called animation_helper. - Snake_Case and removal of useless comments. Update Makefile.am --- CMakeLists.txt | 2 + Makefile.am | 2 + src/animation_helper.cpp | 161 +++++++++++++++++++++ src/animation_helper.h | 11 ++ src/game_interpreter.cpp | 304 ++++++++++----------------------------- 5 files changed, 250 insertions(+), 230 deletions(-) create mode 100644 src/animation_helper.cpp create mode 100644 src/animation_helper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cb50b102d2..911e7509e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,8 @@ add_library(${PROJECT_NAME} OBJECT src/async_op.h src/algo.h src/algo.cpp + src/animation_helper.cpp + src/animation_helper.h src/attribute.h src/attribute.cpp src/audio.cpp diff --git a/Makefile.am b/Makefile.am index 02992d0f91..73056fbb7a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,6 +14,8 @@ libeasyrpg_player_a_SOURCES = \ src/async_op.h \ src/algo.h \ src/algo.cpp \ + src/animation_helper.cpp \ + src/animation_helper.h \ src/attribute.h \ src/attribute.cpp \ src/audio.cpp \ diff --git a/src/animation_helper.cpp b/src/animation_helper.cpp new file mode 100644 index 0000000000..00edda4f3e --- /dev/null +++ b/src/animation_helper.cpp @@ -0,0 +1,161 @@ +#include "animation_helper.h" +#include +#include +#include + + +// references for cubic bezier: +// https://matthewlein.com/tools/ceaser +// https://cubic-bezier.com/ +double Animation_Helper::CubicBezier(float t, const double& p0, const double& p1, const double& p2, const double& p3) { + + float u = 1 - t; + float tt = t * t; + float uu = u * u; + float uuu = uu * u; + float ttt = tt * t; + + //Point2d p = {0,0}; + //p.x = uuu * 0 + 3 * uu * t * p0 + 3 * u * tt * p2 + ttt * 1; + return uuu * 0 + 3 * uu * t * p1 + 3 * u * tt * p3 + ttt * 1; + + //return p.y; +} + +double Animation_Helper::GetEasedTime(const std::string& easing_type, double t, double b, double c, double d) { + if (easing_type == "linear") return Animation_Helper::CubicBezier(t, 0.250, 0.250, 0.750, 0.750); + + else if (easing_type == "ease") return Animation_Helper::CubicBezier(t, 0.250, 0.100, 0.250, 1.000); + else if (easing_type == "easeIn") return Animation_Helper::CubicBezier(t, 0.420, 0.000, 1.000, 1.000); + else if (easing_type == "easeOut") return Animation_Helper::CubicBezier(t, 0.000, 0.000, 0.580, 1.000); + else if (easing_type == "easeInOut") return Animation_Helper::CubicBezier(t, 0.420, 0.000, 0.580, 1.000); + + else if (easing_type == "quadIn") return Animation_Helper::CubicBezier(t, 0.550, 0.085, 0.680, 0.530); + else if (easing_type == "quadOut") return Animation_Helper::CubicBezier(t, 0.250, 0.460, 0.450, 0.940); + else if (easing_type == "quadInOut") return Animation_Helper::CubicBezier(t, 0.455, 0.030, 0.515, 0.955); + + else if (easing_type == "cubicIn") return Animation_Helper::CubicBezier(t, 0.550, 0.055, 0.675, 0.190); + else if (easing_type == "cubicOut") return Animation_Helper::CubicBezier(t, 0.215, 0.610, 0.355, 1.000); + else if (easing_type == "cubicInOut") return Animation_Helper::CubicBezier(t, 0.645, 0.045, 0.355, 1.000); + + else if (easing_type == "quartIn") return Animation_Helper::CubicBezier(t, 0.895, 0.030, 0.685, 0.220); + else if (easing_type == "quartOut") return Animation_Helper::CubicBezier(t, 0.165, 0.840, 0.440, 1.000); + else if (easing_type == "quartInOut") return Animation_Helper::CubicBezier(t, 0.770, 0.000, 0.175, 1.000); + + else if (easing_type == "quintIn") return Animation_Helper::CubicBezier(t, 0.755, 0.050, 0.855, 0.060); + else if (easing_type == "quintOut") return Animation_Helper::CubicBezier(t, 0.230, 1.000, 0.320, 1.000); + else if (easing_type == "quintInOut") return Animation_Helper::CubicBezier(t, 0.860, 0.000, 0.070, 1.000); + + else if (easing_type == "sineIn") return Animation_Helper::CubicBezier(t, 0.470, 0.000, 0.745, 0.715); + else if (easing_type == "sineOut") return Animation_Helper::CubicBezier(t, 0.390, 0.575, 0.565, 1.000); + else if (easing_type == "sineInOut") return Animation_Helper::CubicBezier(t, 0.445, 0.050, 0.550, 0.950); + + else if (easing_type == "ExpoIn") return Animation_Helper::CubicBezier(t, 0.950, 0.050, 0.795, 0.035); + else if (easing_type == "expoOut") return Animation_Helper::CubicBezier(t, 0.190, 1.000, 0.220, 1.000); + else if (easing_type == "expoInOut") return Animation_Helper::CubicBezier(t, 1.000, 0.000, 0.000, 1.000); + + else if (easing_type == "circIn") return Animation_Helper::CubicBezier(t, 0.600, 0.040, 0.980, 0.335); + else if (easing_type == "circOut") return Animation_Helper::CubicBezier(t, 0.075, 0.820, 0.165, 1.000); + else if (easing_type == "circInOut") return Animation_Helper::CubicBezier(t, 0.785, 0.135, 0.150, 0.860); + + else if (easing_type == "backIn") return Animation_Helper::CubicBezier(t, 0.600, -0.280, 0.735, 0.045); + else if (easing_type == "backOut") return Animation_Helper::CubicBezier(t, 0.175, 0.885, 0.320, 1.275); + else if (easing_type == "backInOut") return Animation_Helper::CubicBezier(t, 0.680, -0.550, 0.265, 1.550); + + else if (easing_type == "elasticIn") { + if (t == 0) { + return b; + } + if ((t /= d) == 1) { + return b + c; + } + + double p = d * 0.3; + double a = c; + double s = p / 4; + + double post_increment_fix = a * pow(2, 10 * (t -= 1)); + return -(post_increment_fix * sin((t * d - s) * (2 * M_PI) / p)) + b; + } + else if (easing_type == "elasticOut") { + if (t == 0) { + return b; + } + if ((t /= d) == 1) { + return b + c; + } + + double p = d * 0.3; + double a = c; + double s = p / 4; + + return (a * pow(2, -10 * t) * sin((t * d - s) * (2 * M_PI) / p) + c + b); + } + else if (easing_type == "elasticInOut") { + if (t == 0) { + return b; + } + if ((t /= d / 2) == 2) { + return b + c; + } + + double p = d * (0.3 * 1.5); + double a = c; + double s = p / 4; + + if (t < 1) { + double post_increment_fix = a * pow(2, 10 * (t -= 1)); + return -0.5 * (post_increment_fix * sin((t * d - s) * (2 * M_PI) / p)) + b; + } + + double post_increment_fix = a * pow(2, -10 * (t -= 1)); + return post_increment_fix * sin((t * d - s) * (2 * M_PI) / p) * 0.5 + c + b; + } + + else if (easing_type == "bounceIn") { + return c - Animation_Helper::GetEasedTime("bounceOut", d - t, 0, c, d) + b; + } + else if (easing_type == "bounceOut") { + if ((t /= d) < (1 / 2.75)) { + return c * (7.5625 * t * t) + b; + } + else if (t < (2 / 2.75)) { + t -= (1.5 / 2.75); + return c * (7.5625 * t * t + 0.75) + b; + } + else if (t < (2.5 / 2.75)) { + t -= (2.25 / 2.75); + return c * (7.5625 * t * t + 0.9375) + b; + } + else { + t -= (2.625 / 2.75); + return c * (7.5625 * t * t + 0.984375) + b; + } + } + else if (easing_type == "bounceInOut") { + if (t < d / 2) { + return Animation_Helper::GetEasedTime("bounceIn", t * 2, 0, c, d) * 0.5 + b; + } + else { + return Animation_Helper::GetEasedTime("bounceOut", t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; + } + } + + if (easing_type.substr(0, 6) == "bezier") { + std::vector bezier_params; + + size_t start_pos = easing_type.find("(") + 1; + size_t end_pos = easing_type.find(")"); + std::string value_string = easing_type.substr(start_pos, end_pos - start_pos); + + std::istringstream iss(value_string); + double value; + + while (iss >> value) bezier_params.push_back(value), iss.ignore(); + + if (bezier_params.size() == 4) + return Animation_Helper::CubicBezier(t, bezier_params[0], bezier_params[1], bezier_params[2], bezier_params[3]); + } + + return c * t / d + b; // Default to linear easing if the easing type is not recognized +} diff --git a/src/animation_helper.h b/src/animation_helper.h new file mode 100644 index 0000000000..26efcdbbf8 --- /dev/null +++ b/src/animation_helper.h @@ -0,0 +1,11 @@ +#ifndef ANIMATION_HELPER_H +#define ANIMATION_HELPER_H + +#include + +namespace Animation_Helper { + double CubicBezier(float t, const double& p0, const double& p1, const double& p2, const double& p3); + double GetEasedTime(const std::string& easing_type, double t, double b, double c, double d); +} + +#endif diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 54bf0336d8..a73812b9c8 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -26,6 +26,7 @@ #include #include "game_interpreter.h" #include "async_handler.h" +#include "animation_helper.h" #include "audio.h" #include "game_dynrpg.h" #include "filefinder.h" @@ -5321,6 +5322,79 @@ bool Game_Interpreter::CommandManiacCallCommand(lcf::rpg::EventCommand const& co return true; } +bool Game_Interpreter::CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand const& com) { + // $InterpolateVariable("typeStart/typeEnd",[useVarTarget, target, useVarStart, start, useVarEnd, end, useVarDuration, duration]) + + int32_t target = ValueOrVariable(com.parameters[0], com.parameters[1]); + int32_t start = ValueOrVariable(com.parameters[2], com.parameters[3]); + int32_t end = ValueOrVariable(com.parameters[4], com.parameters[5]); + int32_t duration = ValueOrVariable(com.parameters[6], com.parameters[7]); + + // Extract easing information + std::string easing_type_at_start = ToString(com.string); + std::string easing_type_at_end = "null"; + + std::size_t pos = easing_type_at_start.find('/'); + + if (pos != std::string::npos) { + easing_type_at_end = easing_type_at_start.substr(pos + 1); + easing_type_at_start = easing_type_at_start.substr(0, pos); + } + + // Prepare animation-related commands + lcf::rpg::EventCommand wait_com; + wait_com.code = int(Cmd::Wait); + + lcf::rpg::EventCommand animated_com; + animated_com.code = int(Cmd::ControlVars); + std::vector animated_var_params = { 0, static_cast(target), 0, 0, 0, static_cast(end) }; + + std::vector cmd_list; + + int num_steps = static_cast(duration); + double step_size = 1.0 / num_steps; + + for (int step = 1; step <= num_steps; ++step) { + double normalized_time; + double current_time = step * step_size; + double half_way; + + std::string easing_type; + + if (easing_type_at_end == "null") { // use a single interpolation. + normalized_time = current_time; + easing_type = easing_type_at_start; + half_way = (step <= num_steps / 2) ? end : start; + } + else { + if (step <= num_steps / 2) { // use 2 interpolations: start and end. + normalized_time = current_time / 0.5; + easing_type = easing_type_at_start; + } + else { + normalized_time = (current_time - 0.5) / 0.5; + easing_type = easing_type_at_end; + } + half_way = start + 0.5 * (end - start); + } + + double eased_time = Animation_Helper::GetEasedTime(easing_type, normalized_time, 0, 1, 1); + + double start_value = (step <= num_steps / 2) ? start : half_way; + double end_value = (step <= num_steps / 2) ? half_way : end; + double interpolated_value = start_value + eased_time * (end_value - start_value); + + animated_var_params.back() = interpolated_value; + animated_com.parameters = lcf::DBArray(animated_var_params.begin(), animated_var_params.end()); + + cmd_list.push_back(animated_com); + cmd_list.push_back(wait_com); + } + + Push(cmd_list, 0, false); + return true; +} + bool Game_Interpreter::CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand const& com) { if (!Player::HasEasyRpgExtensions()) { return true; @@ -5504,233 +5578,3 @@ int Game_Interpreter::ManiacBitmask(int value, int mask) const { return value; } - -// references for cubic bezier: -// https://matthewlein.com/tools/ceaser -// https://cubic-bezier.com/ -double cubicBezier(float t, const double& p0,const double& p1, const double& p2, const double& p3) { - - float u = 1 - t; - float tt = t * t; - float uu = u * u; - float uuu = uu * u; - float ttt = tt * t; - - //Point2d p = {0,0}; - //p.x = uuu * 0 + 3 * uu * t * p0 + 3 * u * tt * p2 + ttt * 1; - return uuu * 0 + 3 * uu * t * p1 + 3 * u * tt * p3 + ttt * 1; - - //return p.y; -} - -double getEasedTime(const std::string& easingType, double t, double b, double c, double d) { - if (easingType == "linear") return cubicBezier(t, 0.250, 0.250, 0.750, 0.750); - - else if (easingType == "ease") return cubicBezier(t, 0.250, 0.100, 0.250, 1.000); - else if (easingType == "easeIn") return cubicBezier(t, 0.420, 0.000, 1.000, 1.000); - else if (easingType == "easeOut") return cubicBezier(t, 0.000, 0.000, 0.580, 1.000); - else if (easingType == "easeInOut") return cubicBezier(t, 0.420, 0.000, 0.580, 1.000); - - else if (easingType == "quadIn") return cubicBezier(t, 0.550, 0.085, 0.680, 0.530); - else if (easingType == "quadOut") return cubicBezier(t, 0.250, 0.460, 0.450, 0.940); - else if (easingType == "quadInOut") return cubicBezier(t, 0.455, 0.030, 0.515, 0.955); - - else if (easingType == "cubicIn") return cubicBezier(t, 0.550, 0.055, 0.675, 0.190); - else if (easingType == "cubicOut") return cubicBezier(t, 0.215, 0.610, 0.355, 1.000); - else if (easingType == "cubicInOut") return cubicBezier(t, 0.645, 0.045, 0.355, 1.000); - - else if (easingType == "quartIn") return cubicBezier(t, 0.895, 0.030, 0.685, 0.220); - else if (easingType == "quartOut") return cubicBezier(t, 0.165, 0.840, 0.440, 1.000); - else if (easingType == "quartInOut") return cubicBezier(t, 0.770, 0.000, 0.175, 1.000); - - else if (easingType == "quintIn") return cubicBezier(t, 0.755, 0.050, 0.855, 0.060); - else if (easingType == "quintOut") return cubicBezier(t, 0.230, 1.000, 0.320, 1.000); - else if (easingType == "quintInOut") return cubicBezier(t, 0.860, 0.000, 0.070, 1.000); - - else if (easingType == "sineIn") return cubicBezier(t, 0.470, 0.000, 0.745, 0.715); - else if (easingType == "sineOut") return cubicBezier(t, 0.390, 0.575, 0.565, 1.000); - else if (easingType == "sineInOut") return cubicBezier(t, 0.445, 0.050, 0.550, 0.950); - - else if (easingType == "ExpoIn") return cubicBezier(t, 0.950, 0.050, 0.795, 0.035); - else if (easingType == "expoOut") return cubicBezier(t, 0.190, 1.000, 0.220, 1.000); - else if (easingType == "expoInOut") return cubicBezier(t, 1.000, 0.000, 0.000, 1.000); - - else if (easingType == "circIn") return cubicBezier(t, 0.600, 0.040, 0.980, 0.335); - else if (easingType == "circOut") return cubicBezier(t, 0.075, 0.820, 0.165, 1.000); - else if (easingType == "circInOut") return cubicBezier(t, 0.785, 0.135, 0.150, 0.860); - - else if (easingType == "backIn") return cubicBezier(t, 0.600, -0.280, 0.735, 0.045); - else if (easingType == "backOut") return cubicBezier(t, 0.175, 0.885, 0.320, 1.275); - else if (easingType == "backInOut") return cubicBezier(t, 0.680, -0.550, 0.265, 1.550); - - else if (easingType == "elasticIn") { - if (t == 0) { - return b; - } - if ((t /= d) == 1) { - return b + c; - } - - double p = d * 0.3; - double a = c; - double s = p / 4; - - double postFix = a * pow(2, 10 * (t -= 1)); // this is a fix, again, with post-increment operators - return -(postFix * sin((t * d - s) * (2 * M_PI) / p)) + b; - } - else if (easingType == "elasticOut") { - if (t == 0) { - return b; - } - if ((t /= d) == 1) { - return b + c; - } - - double p = d * 0.3; - double a = c; - double s = p / 4; - - return (a * pow(2, -10 * t) * sin((t * d - s) * (2 * M_PI) / p) + c + b); - } - else if (easingType == "elasticInOut") { - if (t == 0) { - return b; - } - if ((t /= d / 2) == 2) { - return b + c; - } - - double p = d * (0.3 * 1.5); - double a = c; - double s = p / 4; - - if (t < 1) { - double postFix = a * pow(2, 10 * (t -= 1)); // this is a fix, again, with post-increment operators - return -0.5 * (postFix * sin((t * d - s) * (2 * M_PI) / p)) + b; - } - - double postFix = a * pow(2, -10 * (t -= 1)); // this is a fix, again, with post-increment operators - return postFix * sin((t * d - s) * (2 * M_PI) / p) * 0.5 + c + b; - } - - else if (easingType == "bounceIn") { - return c - getEasedTime("bounceOut", d - t, 0, c, d) + b; - } - else if (easingType == "bounceOut") { - if ((t /= d) < (1 / 2.75)) { - return c * (7.5625 * t * t) + b; - } - else if (t < (2 / 2.75)) { - t -= (1.5 / 2.75); - return c * (7.5625 * t * t + 0.75) + b; - } - else if (t < (2.5 / 2.75)) { - t -= (2.25 / 2.75); - return c * (7.5625 * t * t + 0.9375) + b; - } - else { - t -= (2.625 / 2.75); - return c * (7.5625 * t * t + 0.984375) + b; - } - } - else if (easingType == "bounceInOut") { - if (t < d / 2) { - return getEasedTime("bounceIn", t * 2, 0, c, d) * 0.5 + b; - } - else { - return getEasedTime("bounceOut", t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; - } - } - - if (easingType.substr(0, 6) == "bezier") { - std::vector bezierParams; - - size_t startPos = easingType.find("(") + 1; - size_t endPos = easingType.find(")"); - std::string valuesString = easingType.substr(startPos, endPos - startPos); - - std::istringstream iss(valuesString); - double value; - - while (iss >> value) bezierParams.push_back(value), iss.ignore(); - - if (bezierParams.size() == 4) - return cubicBezier(t, bezierParams[0], bezierParams[1], bezierParams[2], bezierParams[3]); - } - - return c * t / d + b; // Default to linear easing if the easing type is not recognized -} - -bool Game_Interpreter::CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand const& com) { - // $InterpolateVariable("typeStart/typeEnd",[useVarTarget, target, useVarStart, start, useVarEnd, end, useVarDuration, duration]) - - int32_t target = ValueOrVariable(com.parameters[0], com.parameters[1]); - int32_t start = ValueOrVariable(com.parameters[2], com.parameters[3]); - int32_t end = ValueOrVariable(com.parameters[4], com.parameters[5]); - int32_t duration = ValueOrVariable(com.parameters[6], com.parameters[7]); - - // Extract easing information - std::string easingTypeAtStart = ToString(com.string); - std::string easingTypeAtEnd = "null"; - - std::size_t pos = easingTypeAtStart.find('/'); - - if (pos != std::string::npos) { - easingTypeAtEnd = easingTypeAtStart.substr(pos + 1); - easingTypeAtStart = easingTypeAtStart.substr(0, pos); - } - - // Prepare animation-related commands - lcf::rpg::EventCommand waitCom; - waitCom.code = int(Cmd::Wait); - - lcf::rpg::EventCommand animatedCom; - animatedCom.code = int(Cmd::ControlVars); - std::vector animatedVarParams = { 0, static_cast(target), 0, 0, 0, static_cast(end) }; - - std::vector cmdList; - - int numSteps = static_cast(duration); - double stepSize = 1.0 / numSteps; - - for (int step = 1; step <= numSteps; ++step) { - double normalizedTime; - double currentTime = step * stepSize; - double halfway; - - std::string easingType; - - if (easingTypeAtEnd == "null") { // use a single interpolation. - normalizedTime = currentTime; - easingType = easingTypeAtStart; - halfway = (step <= numSteps / 2) ? end : start; - } - else { - if (step <= numSteps / 2) { // use 2 interpolations: start and end. - normalizedTime = currentTime / 0.5; - easingType = easingTypeAtStart; - } - else { - normalizedTime = (currentTime - 0.5) / 0.5; - easingType = easingTypeAtEnd; - } - halfway = start + 0.5 * (end - start); - } - - double easedTime = getEasedTime(easingType, normalizedTime, 0, 1, 1); - - double startValue = (step <= numSteps / 2) ? start : halfway; - double endValue = (step <= numSteps / 2) ? halfway : end; - double interpolatedValue = startValue + easedTime * (endValue - startValue); - - animatedVarParams.back() = interpolatedValue; - animatedCom.parameters = lcf::DBArray(animatedVarParams.begin(), animatedVarParams.end()); - - cmdList.push_back(animatedCom); - cmdList.push_back(waitCom); - } - - Push(cmdList, 0, false); - return true; -} - From f39448d8685ca317866bc8997f57a9cae060b1f9 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Tue, 21 May 2024 21:48:51 -0300 Subject: [PATCH 5/6] AnimateVars - License --- src/animation_helper.cpp | 17 +++++++++++++++++ src/animation_helper.h | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/animation_helper.cpp b/src/animation_helper.cpp index 00edda4f3e..3a939f0929 100644 --- a/src/animation_helper.cpp +++ b/src/animation_helper.cpp @@ -1,3 +1,20 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + #include "animation_helper.h" #include #include diff --git a/src/animation_helper.h b/src/animation_helper.h index 26efcdbbf8..2ab1955040 100644 --- a/src/animation_helper.h +++ b/src/animation_helper.h @@ -1,3 +1,20 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + #ifndef ANIMATION_HELPER_H #define ANIMATION_HELPER_H From c35776d2d03cb4bf7c8fcc2e89db88b343393bb3 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Tue, 11 Feb 2025 18:31:47 -0300 Subject: [PATCH 6/6] Improve Bezier Curve --- src/animation_helper.cpp | 377 +++++++++++++++++++++++---------------- src/animation_helper.h | 101 ++++++++++- src/game_interpreter.cpp | 120 ++++++++----- 3 files changed, 398 insertions(+), 200 deletions(-) diff --git a/src/animation_helper.cpp b/src/animation_helper.cpp index 3a939f0929..c0c46b9fb3 100644 --- a/src/animation_helper.cpp +++ b/src/animation_helper.cpp @@ -17,162 +17,237 @@ #include "animation_helper.h" #include -#include +#include +#include #include +#include +namespace { + constexpr double PI = 3.14159265358979323846; + + // Forward declarations + double HandleElasticEasing(Animation_Helper::EaseType type, double t, double b, double c, double d); + double HandleBounceEasing(Animation_Helper::EaseType type, double t, double b, double c, double d); + + struct EasingData { + std::string_view name; + double x1, y1, x2, y2; + Animation_Helper::EaseType type; + }; + + const EasingData EASING_DATA[] = { + {"linear", 0.250, 0.250, 0.750, 0.750, Animation_Helper::EaseType::Linear}, + {"ease", 0.250, 0.100, 0.250, 1.000, Animation_Helper::EaseType::Ease}, + {"easeIn", 0.420, 0.000, 1.000, 1.000, Animation_Helper::EaseType::EaseIn}, + {"easeOut", 0.000, 0.000, 0.580, 1.000, Animation_Helper::EaseType::EaseOut}, + {"easeInOut", 0.420, 0.000, 0.580, 1.000, Animation_Helper::EaseType::EaseInOut}, + {"quadIn", 0.550, 0.085, 0.680, 0.530, Animation_Helper::EaseType::QuadIn}, + {"quadOut", 0.250, 0.460, 0.450, 0.940, Animation_Helper::EaseType::QuadOut}, + {"quadInOut", 0.455, 0.030, 0.515, 0.955, Animation_Helper::EaseType::QuadInOut}, + {"cubicIn", 0.550, 0.055, 0.675, 0.190, Animation_Helper::EaseType::CubicIn}, + {"cubicOut", 0.215, 0.610, 0.355, 1.000, Animation_Helper::EaseType::CubicOut}, + {"cubicInOut", 0.645, 0.045, 0.355, 1.000, Animation_Helper::EaseType::CubicInOut}, + {"quartIn", 0.895, 0.030, 0.685, 0.220, Animation_Helper::EaseType::QuartIn}, + {"quartOut", 0.165, 0.840, 0.440, 1.000, Animation_Helper::EaseType::QuartOut}, + {"quartInOut", 0.770, 0.000, 0.175, 1.000, Animation_Helper::EaseType::QuartInOut}, + {"quintIn", 0.755, 0.050, 0.855, 0.060, Animation_Helper::EaseType::QuintIn}, + {"quintOut", 0.230, 1.000, 0.320, 1.000, Animation_Helper::EaseType::QuintOut}, + {"quintInOut", 0.860, 0.000, 0.070, 1.000, Animation_Helper::EaseType::QuintInOut}, + {"sineIn", 0.470, 0.000, 0.745, 0.715, Animation_Helper::EaseType::SineIn}, + {"sineOut", 0.390, 0.575, 0.565, 1.000, Animation_Helper::EaseType::SineOut}, + {"sineInOut", 0.445, 0.050, 0.550, 0.950, Animation_Helper::EaseType::SineInOut}, + {"expoIn", 0.950, 0.050, 0.795, 0.035, Animation_Helper::EaseType::ExpoIn}, + {"expoOut", 0.190, 1.000, 0.220, 1.000, Animation_Helper::EaseType::ExpoOut}, + {"expoInOut", 1.000, 0.000, 0.000, 1.000, Animation_Helper::EaseType::ExpoInOut}, + {"circIn", 0.600, 0.040, 0.980, 0.335, Animation_Helper::EaseType::CircIn}, + {"circOut", 0.075, 0.820, 0.165, 1.000, Animation_Helper::EaseType::CircOut}, + {"circInOut", 0.785, 0.135, 0.150, 0.860, Animation_Helper::EaseType::CircInOut}, + {"backIn", 0.600, -0.280, 0.735, 0.045, Animation_Helper::EaseType::BackIn}, + {"backOut", 0.175, 0.885, 0.320, 1.275, Animation_Helper::EaseType::BackOut}, + {"backInOut", 0.680, -0.550, 0.265, 1.550, Animation_Helper::EaseType::BackInOut}, + // Special cases (no bezier points needed) + {"elasticIn", 0, 0, 0, 0, Animation_Helper::EaseType::ElasticIn}, + {"elasticOut", 0, 0, 0, 0, Animation_Helper::EaseType::ElasticOut}, + {"elasticInOut", 0, 0, 0, 0, Animation_Helper::EaseType::ElasticInOut}, + {"bounceIn", 0, 0, 0, 0, Animation_Helper::EaseType::BounceIn}, + {"bounceOut", 0, 0, 0, 0, Animation_Helper::EaseType::BounceOut}, + {"bounceInOut", 0, 0, 0, 0, Animation_Helper::EaseType::BounceInOut} + }; + + double HandleElasticEasing(Animation_Helper::EaseType type, double t, double b, double c, double d) { + constexpr double pi2 = 2.0 * PI; + const double p = d * 0.3; + const double s = p / 4.0; + + if (t <= 0.0) return b; + if (t >= 1.0) return b + c; + + switch (type) { + case Animation_Helper::EaseType::ElasticIn: { + const double post_fix = c * std::pow(2.0, 10.0 * (t - 1.0)); + return -(post_fix * std::sin((t * d - s) * pi2 / p)) + b; + } + case Animation_Helper::EaseType::ElasticOut: { + return (c * std::pow(2.0, -10.0 * t) * std::sin((t * d - s) * pi2 / p) + c + b); + } + case Animation_Helper::EaseType::ElasticInOut: { + if (t < 0.5) { + const double post_fix = c * std::pow(2.0, 10.0 * (2.0 * t - 1.0)); + return -0.5 * (post_fix * std::sin(((2.0 * t - 1.0) * d - s) * pi2 / p)) + b; + } + const double post_fix = c * std::pow(2.0, -10.0 * (2.0 * t - 1.0)); + return post_fix * std::sin(((2.0 * t - 1.0) * d - s) * pi2 / p) * 0.5 + c + b; + } + default: + return b + c * t; + } + } + + double HandleBounceEasing(Animation_Helper::EaseType type, double t, double b, double c, double d) { + switch (type) { + case Animation_Helper::EaseType::BounceIn: + return c - HandleBounceEasing(Animation_Helper::EaseType::BounceOut, 1.0 - t, 0, c, d) + b; + case Animation_Helper::EaseType::BounceOut: + if (t < (1.0 / 2.75)) { + return c * (7.5625 * t * t) + b; + } else if (t < (2.0 / 2.75)) { + t -= (1.5 / 2.75); + return c * (7.5625 * t * t + 0.75) + b; + } else if (t < (2.5 / 2.75)) { + t -= (2.25 / 2.75); + return c * (7.5625 * t * t + 0.9375) + b; + } + t -= (2.625 / 2.75); + return c * (7.5625 * t * t + 0.984375) + b; + case Animation_Helper::EaseType::BounceInOut: + if (t < 0.5) { + return HandleBounceEasing(Animation_Helper::EaseType::BounceIn, t * 2.0, 0, c, d) * 0.5 + b; + } + return HandleBounceEasing(Animation_Helper::EaseType::BounceOut, t * 2.0 - 1.0, 0, c, d) * 0.5 + c * 0.5 + b; + default: + return b + c * t; + } + } +} -// references for cubic bezier: -// https://matthewlein.com/tools/ceaser -// https://cubic-bezier.com/ -double Animation_Helper::CubicBezier(float t, const double& p0, const double& p1, const double& p2, const double& p3) { - - float u = 1 - t; - float tt = t * t; - float uu = u * u; - float uuu = uu * u; - float ttt = tt * t; +namespace Animation_Helper { + +double CubicBezier(double progress, double x1, double y1, double x2, double y2) { + // This is a cubic Bezier curve for animation timing: + // P₀(0,0) - start point, always fixed + // P₁(x1,y1) - first control point + // P₂(x2,y2) - second control point + // P₃(1,1) - end point, always fixed + // + // x coordinates (x1,x2) control the timing curve's shape + // y coordinates (y1,y2) control the rate of change at those points + + ////progress = std::clamp(progress, 0.0, 1.0); + + // Since we need to find y for a given x (progress), + // we need to solve for t where the curve's x equals our progress + // This is a rough approximation using a few iterations + double t = progress; // Initial guess + + // Newton's method to find better t value + for (int i = 0; i < 5; i++) { // Usually converges in 4-5 iterations + const double currentT = t; + const double oneMinusT = 1.0 - t; + + // Calculate x(t) - current x position on curve + const double x = 3.0 * oneMinusT * oneMinusT * t * x1 + + 3.0 * oneMinusT * t * t * x2 + + t * t * t; + + // If we're close enough to desired x, calculate final y + if (std::abs(x - progress) < 0.001) { + break; + } + + // Calculate x'(t) - derivative of x with respect to t + const double dx = 3.0 * oneMinusT * oneMinusT * x1 + + 6.0 * oneMinusT * t * (x2 - x1) + + 3.0 * t * t * (1 - x2); + + // Avoid division by zero + if (std::abs(dx) < 0.0001) { + break; + } + + // Newton iteration + t = t - (x - progress) / dx; + //// t = std::clamp(t, 0.0, 1.0); + + // If t hasn't changed significantly, we're done + if (std::abs(t - currentT) < 0.0001) { + break; + } + } + + // Calculate final y value using found t + const double oneMinusT = 1.0 - t; + return 3.0 * oneMinusT * oneMinusT * t * y1 + + 3.0 * oneMinusT * t * t * y2 + + t * t * t; +} - //Point2d p = {0,0}; - //p.x = uuu * 0 + 3 * uu * t * p0 + 3 * u * tt * p2 + ttt * 1; - return uuu * 0 + 3 * uu * t * p1 + 3 * u * tt * p3 + ttt * 1; +EaseType StringToEaseType(std::string_view type_name) { + for (const auto& data : EASING_DATA) { + if (data.name == type_name) { + return data.type; + } + } + return EaseType::Linear; +} - //return p.y; +double GetEasedTime(EaseType type, double t, double b, double c, double d) { + t = std::clamp(t, 0.0, d); + const double normalized_t = t / d; + + // Handle special cases first + switch (type) { + case EaseType::ElasticIn: + case EaseType::ElasticOut: + case EaseType::ElasticInOut: + return HandleElasticEasing(type, normalized_t, b, c, d); + case EaseType::BounceIn: + case EaseType::BounceOut: + case EaseType::BounceInOut: + return HandleBounceEasing(type, normalized_t, b, c, d); + default: + break; + } + + // Find bezier points for the type + for (const auto& data : EASING_DATA) { + if (data.type == type) { + return b + c * CubicBezier(normalized_t, data.x1, data.y1, data.x2, data.y2); + } + } + + return b + c * normalized_t; // Linear fallback } -double Animation_Helper::GetEasedTime(const std::string& easing_type, double t, double b, double c, double d) { - if (easing_type == "linear") return Animation_Helper::CubicBezier(t, 0.250, 0.250, 0.750, 0.750); - - else if (easing_type == "ease") return Animation_Helper::CubicBezier(t, 0.250, 0.100, 0.250, 1.000); - else if (easing_type == "easeIn") return Animation_Helper::CubicBezier(t, 0.420, 0.000, 1.000, 1.000); - else if (easing_type == "easeOut") return Animation_Helper::CubicBezier(t, 0.000, 0.000, 0.580, 1.000); - else if (easing_type == "easeInOut") return Animation_Helper::CubicBezier(t, 0.420, 0.000, 0.580, 1.000); - - else if (easing_type == "quadIn") return Animation_Helper::CubicBezier(t, 0.550, 0.085, 0.680, 0.530); - else if (easing_type == "quadOut") return Animation_Helper::CubicBezier(t, 0.250, 0.460, 0.450, 0.940); - else if (easing_type == "quadInOut") return Animation_Helper::CubicBezier(t, 0.455, 0.030, 0.515, 0.955); - - else if (easing_type == "cubicIn") return Animation_Helper::CubicBezier(t, 0.550, 0.055, 0.675, 0.190); - else if (easing_type == "cubicOut") return Animation_Helper::CubicBezier(t, 0.215, 0.610, 0.355, 1.000); - else if (easing_type == "cubicInOut") return Animation_Helper::CubicBezier(t, 0.645, 0.045, 0.355, 1.000); - - else if (easing_type == "quartIn") return Animation_Helper::CubicBezier(t, 0.895, 0.030, 0.685, 0.220); - else if (easing_type == "quartOut") return Animation_Helper::CubicBezier(t, 0.165, 0.840, 0.440, 1.000); - else if (easing_type == "quartInOut") return Animation_Helper::CubicBezier(t, 0.770, 0.000, 0.175, 1.000); - - else if (easing_type == "quintIn") return Animation_Helper::CubicBezier(t, 0.755, 0.050, 0.855, 0.060); - else if (easing_type == "quintOut") return Animation_Helper::CubicBezier(t, 0.230, 1.000, 0.320, 1.000); - else if (easing_type == "quintInOut") return Animation_Helper::CubicBezier(t, 0.860, 0.000, 0.070, 1.000); - - else if (easing_type == "sineIn") return Animation_Helper::CubicBezier(t, 0.470, 0.000, 0.745, 0.715); - else if (easing_type == "sineOut") return Animation_Helper::CubicBezier(t, 0.390, 0.575, 0.565, 1.000); - else if (easing_type == "sineInOut") return Animation_Helper::CubicBezier(t, 0.445, 0.050, 0.550, 0.950); - - else if (easing_type == "ExpoIn") return Animation_Helper::CubicBezier(t, 0.950, 0.050, 0.795, 0.035); - else if (easing_type == "expoOut") return Animation_Helper::CubicBezier(t, 0.190, 1.000, 0.220, 1.000); - else if (easing_type == "expoInOut") return Animation_Helper::CubicBezier(t, 1.000, 0.000, 0.000, 1.000); - - else if (easing_type == "circIn") return Animation_Helper::CubicBezier(t, 0.600, 0.040, 0.980, 0.335); - else if (easing_type == "circOut") return Animation_Helper::CubicBezier(t, 0.075, 0.820, 0.165, 1.000); - else if (easing_type == "circInOut") return Animation_Helper::CubicBezier(t, 0.785, 0.135, 0.150, 0.860); - - else if (easing_type == "backIn") return Animation_Helper::CubicBezier(t, 0.600, -0.280, 0.735, 0.045); - else if (easing_type == "backOut") return Animation_Helper::CubicBezier(t, 0.175, 0.885, 0.320, 1.275); - else if (easing_type == "backInOut") return Animation_Helper::CubicBezier(t, 0.680, -0.550, 0.265, 1.550); - - else if (easing_type == "elasticIn") { - if (t == 0) { - return b; - } - if ((t /= d) == 1) { - return b + c; - } - - double p = d * 0.3; - double a = c; - double s = p / 4; - - double post_increment_fix = a * pow(2, 10 * (t -= 1)); - return -(post_increment_fix * sin((t * d - s) * (2 * M_PI) / p)) + b; - } - else if (easing_type == "elasticOut") { - if (t == 0) { - return b; - } - if ((t /= d) == 1) { - return b + c; - } - - double p = d * 0.3; - double a = c; - double s = p / 4; - - return (a * pow(2, -10 * t) * sin((t * d - s) * (2 * M_PI) / p) + c + b); - } - else if (easing_type == "elasticInOut") { - if (t == 0) { - return b; - } - if ((t /= d / 2) == 2) { - return b + c; - } - - double p = d * (0.3 * 1.5); - double a = c; - double s = p / 4; - - if (t < 1) { - double post_increment_fix = a * pow(2, 10 * (t -= 1)); - return -0.5 * (post_increment_fix * sin((t * d - s) * (2 * M_PI) / p)) + b; - } - - double post_increment_fix = a * pow(2, -10 * (t -= 1)); - return post_increment_fix * sin((t * d - s) * (2 * M_PI) / p) * 0.5 + c + b; - } - - else if (easing_type == "bounceIn") { - return c - Animation_Helper::GetEasedTime("bounceOut", d - t, 0, c, d) + b; - } - else if (easing_type == "bounceOut") { - if ((t /= d) < (1 / 2.75)) { - return c * (7.5625 * t * t) + b; - } - else if (t < (2 / 2.75)) { - t -= (1.5 / 2.75); - return c * (7.5625 * t * t + 0.75) + b; - } - else if (t < (2.5 / 2.75)) { - t -= (2.25 / 2.75); - return c * (7.5625 * t * t + 0.9375) + b; - } - else { - t -= (2.625 / 2.75); - return c * (7.5625 * t * t + 0.984375) + b; - } - } - else if (easing_type == "bounceInOut") { - if (t < d / 2) { - return Animation_Helper::GetEasedTime("bounceIn", t * 2, 0, c, d) * 0.5 + b; - } - else { - return Animation_Helper::GetEasedTime("bounceOut", t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; - } - } - - if (easing_type.substr(0, 6) == "bezier") { - std::vector bezier_params; - - size_t start_pos = easing_type.find("(") + 1; - size_t end_pos = easing_type.find(")"); - std::string value_string = easing_type.substr(start_pos, end_pos - start_pos); - - std::istringstream iss(value_string); - double value; - - while (iss >> value) bezier_params.push_back(value), iss.ignore(); - - if (bezier_params.size() == 4) - return Animation_Helper::CubicBezier(t, bezier_params[0], bezier_params[1], bezier_params[2], bezier_params[3]); - } - - return c * t / d + b; // Default to linear easing if the easing type is not recognized +double GetEasedTime(const std::string& easing_type, double t, double b, double c, double d) { + if (easing_type.substr(0, 6) == "bezier") { + std::vector params; + size_t start = easing_type.find('(') + 1; + size_t end = easing_type.find(')'); + if (start != std::string::npos && end != std::string::npos) { + std::string values = easing_type.substr(start, end - start); + std::istringstream iss(values); + double val; + while (iss >> val) { + params.push_back(val); + iss.ignore(); + } + if (params.size() == 4) { + return b + c * CubicBezier(t/d, params[0], params[1], params[2], params[3]); + } + } + } + + return GetEasedTime(StringToEaseType(easing_type), t, b, c, d); } + +} // namespace Animation_Helper diff --git a/src/animation_helper.h b/src/animation_helper.h index 2ab1955040..563599a86b 100644 --- a/src/animation_helper.h +++ b/src/animation_helper.h @@ -19,10 +19,107 @@ #define ANIMATION_HELPER_H #include +#include namespace Animation_Helper { - double CubicBezier(float t, const double& p0, const double& p1, const double& p2, const double& p3); - double GetEasedTime(const std::string& easing_type, double t, double b, double c, double d); + /** + * Supported easing types for animations. + * These match CSS animation timing functions for familiarity. + */ + enum class EaseType { + Linear, + // Basic + Ease, + EaseIn, + EaseOut, + EaseInOut, + // Quadratic + QuadIn, + QuadOut, + QuadInOut, + // Cubic + CubicIn, + CubicOut, + CubicInOut, + // Quartic + QuartIn, + QuartOut, + QuartInOut, + // Quintic + QuintIn, + QuintOut, + QuintInOut, + // Sinusoidal + SineIn, + SineOut, + SineInOut, + // Exponential + ExpoIn, + ExpoOut, + ExpoInOut, + // Circular + CircIn, + CircOut, + CircInOut, + // Back + BackIn, + BackOut, + BackInOut, + // Elastic + ElasticIn, + ElasticOut, + ElasticInOut, + // Bounce + BounceIn, + BounceOut, + BounceInOut + }; + + /** + * Converts a string to an EaseType enum value + * @param type_name The name of the easing type + * @return The corresponding EaseType enum value + */ + EaseType StringToEaseType(std::string_view type_name); + + /** + * Calculates a point on a cubic Bezier curve for animation timing + * @param progress Current animation progress (0 to 1) + * @param x1 X coordinate of first control point, controls when early changes occur + * @param y1 Y coordinate of first control point, controls magnitude of early changes + * @param x2 X coordinate of second control point, controls when later changes occur + * @param y2 Y coordinate of second control point, controls magnitude of later changes + * @return The calculated animation progress value + * + * Control points affect the animation as follows: + * - x values control timing (when changes happen) + * - y values control magnitude (how much change occurs) + * - Values > current progress create overshoots + * - Values < current progress create undershoots + */ + double CubicBezier(double progress, double x1, double y1, double x2, double y2); + + /** + * Gets the eased time value based on the specified easing type + * @param easing_type Type of easing to apply + * @param t Current time (0 to 1) + * @param b Start value + * @param c Change in value (end - start) + * @param d Duration + * @return The eased value + */ + double GetEasedTime(const std::string& easing_type, double t, double b, double c, double d); + + /** + * Gets the eased time value using enum-based easing type + * @param type The easing type enum + * @param t Current time (0 to 1) + * @param b Start value + * @param c Change in value (end - start) + * @param d Duration + * @return The eased value + */ + double GetEasedTime(EaseType type, double t, double b, double c, double d); } #endif diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index a73812b9c8..4200dc448e 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5323,71 +5323,97 @@ bool Game_Interpreter::CommandManiacCallCommand(lcf::rpg::EventCommand const& co } bool Game_Interpreter::CommandEasyRpgAnimateVariable(lcf::rpg::EventCommand const& com) { - // $InterpolateVariable("typeStart/typeEnd",[useVarTarget, target, useVarStart, start, useVarEnd, end, useVarDuration, duration]) - - int32_t target = ValueOrVariable(com.parameters[0], com.parameters[1]); - int32_t start = ValueOrVariable(com.parameters[2], com.parameters[3]); - int32_t end = ValueOrVariable(com.parameters[4], com.parameters[5]); - int32_t duration = ValueOrVariable(com.parameters[6], com.parameters[7]); - - // Extract easing information - std::string easing_type_at_start = ToString(com.string); - std::string easing_type_at_end = "null"; + struct AnimationParams { + int32_t target; + int32_t start; + int32_t end; + int32_t duration; + std::string easing_start; + std::string easing_end; + }; - std::size_t pos = easing_type_at_start.find('/'); + // Parse command parameters + AnimationParams params{ + ValueOrVariable(com.parameters[0], com.parameters[1]), + ValueOrVariable(com.parameters[2], com.parameters[3]), + ValueOrVariable(com.parameters[4], com.parameters[5]), + ValueOrVariable(com.parameters[6], com.parameters[7]), + ToString(com.string), + "null" + }; - if (pos != std::string::npos) { - easing_type_at_end = easing_type_at_start.substr(pos + 1); - easing_type_at_start = easing_type_at_start.substr(0, pos); + // Extract dual easing types if present + const auto separator_pos = params.easing_start.find('/'); + if (separator_pos != std::string::npos) { + params.easing_end = params.easing_start.substr(separator_pos + 1); + params.easing_start = params.easing_start.substr(0, separator_pos); } - // Prepare animation-related commands - lcf::rpg::EventCommand wait_com; - wait_com.code = int(Cmd::Wait); - - lcf::rpg::EventCommand animated_com; - animated_com.code = int(Cmd::ControlVars); - std::vector animated_var_params = { 0, static_cast(target), 0, 0, 0, static_cast(end) }; - + // Prepare animation commands std::vector cmd_list; + cmd_list.reserve(params.duration * 2); // Pre-allocate for efficiency + + lcf::rpg::EventCommand wait_com; + wait_com.code = static_cast(Cmd::Wait); + + lcf::rpg::EventCommand var_com; + var_com.code = static_cast(Cmd::ControlVars); + std::vector var_params = { + 0, + params.target, + 0, + 0, + 0, + params.end + }; - int num_steps = static_cast(duration); - double step_size = 1.0 / num_steps; + const double step_size = 1.0 / params.duration; + const int half_duration = params.duration / 2; - for (int step = 1; step <= num_steps; ++step) { + // Generate animation steps + for (int step = 1; step <= params.duration; ++step) { + const double current_time = step * step_size; double normalized_time; - double current_time = step * step_size; - double half_way; - - std::string easing_type; + const std::string& current_easing = (params.easing_end == "null") + ? params.easing_start + : (step <= half_duration ? params.easing_start : params.easing_end); - if (easing_type_at_end == "null") { // use a single interpolation. + // Calculate normalized time based on single or dual easing + if (params.easing_end == "null") { normalized_time = current_time; - easing_type = easing_type_at_start; - half_way = (step <= num_steps / 2) ? end : start; } else { - if (step <= num_steps / 2) { // use 2 interpolations: start and end. - normalized_time = current_time / 0.5; - easing_type = easing_type_at_start; + // For dual easing, each half uses its own normalized time from 0 to 1 + normalized_time = step <= half_duration + ? (current_time * 2.0) // First half: 0->1 + : ((current_time - 0.5) * 2.0); // Second half: 0->1 + } + + // Get eased time for current segment + const double eased_time = Animation_Helper::GetEasedTime(current_easing, normalized_time, 0, 1, 1); + + // Calculate interpolated value + double interpolated; + if (params.easing_end == "null") { + // Single easing - interpolate directly from start to end + interpolated = params.start + eased_time * (params.end - params.start); + } + else { + // Dual easing - interpolate each half separately + const double mid_point = (params.start + params.end) * 0.5; + if (step <= half_duration) { + interpolated = params.start + eased_time * (mid_point - params.start); } else { - normalized_time = (current_time - 0.5) / 0.5; - easing_type = easing_type_at_end; + interpolated = mid_point + eased_time * (params.end - mid_point); } - half_way = start + 0.5 * (end - start); } - double eased_time = Animation_Helper::GetEasedTime(easing_type, normalized_time, 0, 1, 1); - - double start_value = (step <= num_steps / 2) ? start : half_way; - double end_value = (step <= num_steps / 2) ? half_way : end; - double interpolated_value = start_value + eased_time * (end_value - start_value); - - animated_var_params.back() = interpolated_value; - animated_com.parameters = lcf::DBArray(animated_var_params.begin(), animated_var_params.end()); + // Update and push commands + var_params.back() = static_cast(interpolated); + var_com.parameters = lcf::DBArray(var_params.begin(), var_params.end()); - cmd_list.push_back(animated_com); + cmd_list.push_back(var_com); cmd_list.push_back(wait_com); }