From c261077c17d1303fdf27a384b60167ed66acfa47 Mon Sep 17 00:00:00 2001
From: adfriz <76892624+adfriz@users.noreply.github.com>
Date: Sat, 1 Aug 2026 20:59:51 +0700
Subject: [PATCH 1/2] New: Add easing functions to FunctionScript
Adds lerp, smoothstep and the full set of Robert Penner easing
functions (sine, quad, cubic, quart, quint, expo, circ, back,
elastic and bounce, each in in/out/inout variants) to the
function script API used by animated objects.
---
source/ObjectViewer/FunctionScripts.cs | 96 +++++
.../AnimatedObjects/FunctionScripts.cs | 96 +++++
.../FunctionScript.Notation.cs | 36 ++
.../FunctionScripts/FunctionScript.cs | 160 ++++++++
.../FunctionScripts/Instructions.cs | 11 +
source/OpenBveApi/Math/Easing.cs | 355 ++++++++++++++++++
source/OpenBveApi/OpenBveApi.csproj | 1 +
source/RouteViewer/FunctionScripts.cs | 96 +++++
8 files changed, 851 insertions(+)
create mode 100644 source/OpenBveApi/Math/Easing.cs
diff --git a/source/ObjectViewer/FunctionScripts.cs b/source/ObjectViewer/FunctionScripts.cs
index 5861b646c..70f0f5240 100644
--- a/source/ObjectViewer/FunctionScripts.cs
+++ b/source/ObjectViewer/FunctionScripts.cs
@@ -161,6 +161,102 @@ internal static void ExecuteFunctionScript(FunctionScript Function, TrainBase Tr
case Instructions.MathPi:
Function.Stack[s] = Math.PI;
s++; break;
+ case Instructions.MathLerp:
+ Function.Stack[s - 3] = Easing.Lerp(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
+ s -= 2; break;
+ case Instructions.MathSmoothstep:
+ Function.Stack[s - 3] = Easing.Smoothstep(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
+ s -= 2; break;
+ case Instructions.MathEaseInSine:
+ Function.Stack[s - 1] = Easing.EaseInSine(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutSine:
+ Function.Stack[s - 1] = Easing.EaseOutSine(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutSine:
+ Function.Stack[s - 1] = Easing.EaseInOutSine(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInQuad:
+ Function.Stack[s - 1] = Easing.EaseInQuad(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutQuad:
+ Function.Stack[s - 1] = Easing.EaseOutQuad(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutQuad:
+ Function.Stack[s - 1] = Easing.EaseInOutQuad(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInCubic:
+ Function.Stack[s - 1] = Easing.EaseInCubic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutCubic:
+ Function.Stack[s - 1] = Easing.EaseOutCubic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutCubic:
+ Function.Stack[s - 1] = Easing.EaseInOutCubic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInQuart:
+ Function.Stack[s - 1] = Easing.EaseInQuart(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutQuart:
+ Function.Stack[s - 1] = Easing.EaseOutQuart(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutQuart:
+ Function.Stack[s - 1] = Easing.EaseInOutQuart(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInQuint:
+ Function.Stack[s - 1] = Easing.EaseInQuint(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutQuint:
+ Function.Stack[s - 1] = Easing.EaseOutQuint(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutQuint:
+ Function.Stack[s - 1] = Easing.EaseInOutQuint(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInExpo:
+ Function.Stack[s - 1] = Easing.EaseInExpo(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutExpo:
+ Function.Stack[s - 1] = Easing.EaseOutExpo(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutExpo:
+ Function.Stack[s - 1] = Easing.EaseInOutExpo(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInCirc:
+ Function.Stack[s - 1] = Easing.EaseInCirc(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutCirc:
+ Function.Stack[s - 1] = Easing.EaseOutCirc(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutCirc:
+ Function.Stack[s - 1] = Easing.EaseInOutCirc(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInBack:
+ Function.Stack[s - 1] = Easing.EaseInBack(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutBack:
+ Function.Stack[s - 1] = Easing.EaseOutBack(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutBack:
+ Function.Stack[s - 1] = Easing.EaseInOutBack(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInElastic:
+ Function.Stack[s - 1] = Easing.EaseInElastic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutElastic:
+ Function.Stack[s - 1] = Easing.EaseOutElastic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutElastic:
+ Function.Stack[s - 1] = Easing.EaseInOutElastic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInBounce:
+ Function.Stack[s - 1] = Easing.EaseInBounce(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutBounce:
+ Function.Stack[s - 1] = Easing.EaseOutBounce(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutBounce:
+ Function.Stack[s - 1] = Easing.EaseInOutBounce(Function.Stack[s - 1]);
+ break;
// comparisons
case Instructions.CompareEqual:
Function.Stack[s - 2] = Function.Stack[s - 2] == Function.Stack[s - 1] ? 1.0 : 0.0;
diff --git a/source/OpenBVE/Game/ObjectManager/AnimatedObjects/FunctionScripts.cs b/source/OpenBVE/Game/ObjectManager/AnimatedObjects/FunctionScripts.cs
index 3710a10d0..2d5618f6b 100644
--- a/source/OpenBVE/Game/ObjectManager/AnimatedObjects/FunctionScripts.cs
+++ b/source/OpenBVE/Game/ObjectManager/AnimatedObjects/FunctionScripts.cs
@@ -163,6 +163,102 @@ internal static void ExecuteFunctionScript(FunctionScript Function, TrainBase Tr
case Instructions.MathPi:
Function.Stack[s] = Math.PI;
s++; break;
+ case Instructions.MathLerp:
+ Function.Stack[s - 3] = Easing.Lerp(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
+ s -= 2; break;
+ case Instructions.MathSmoothstep:
+ Function.Stack[s - 3] = Easing.Smoothstep(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
+ s -= 2; break;
+ case Instructions.MathEaseInSine:
+ Function.Stack[s - 1] = Easing.EaseInSine(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutSine:
+ Function.Stack[s - 1] = Easing.EaseOutSine(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutSine:
+ Function.Stack[s - 1] = Easing.EaseInOutSine(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInQuad:
+ Function.Stack[s - 1] = Easing.EaseInQuad(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutQuad:
+ Function.Stack[s - 1] = Easing.EaseOutQuad(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutQuad:
+ Function.Stack[s - 1] = Easing.EaseInOutQuad(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInCubic:
+ Function.Stack[s - 1] = Easing.EaseInCubic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutCubic:
+ Function.Stack[s - 1] = Easing.EaseOutCubic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutCubic:
+ Function.Stack[s - 1] = Easing.EaseInOutCubic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInQuart:
+ Function.Stack[s - 1] = Easing.EaseInQuart(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutQuart:
+ Function.Stack[s - 1] = Easing.EaseOutQuart(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutQuart:
+ Function.Stack[s - 1] = Easing.EaseInOutQuart(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInQuint:
+ Function.Stack[s - 1] = Easing.EaseInQuint(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutQuint:
+ Function.Stack[s - 1] = Easing.EaseOutQuint(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutQuint:
+ Function.Stack[s - 1] = Easing.EaseInOutQuint(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInExpo:
+ Function.Stack[s - 1] = Easing.EaseInExpo(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutExpo:
+ Function.Stack[s - 1] = Easing.EaseOutExpo(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutExpo:
+ Function.Stack[s - 1] = Easing.EaseInOutExpo(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInCirc:
+ Function.Stack[s - 1] = Easing.EaseInCirc(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutCirc:
+ Function.Stack[s - 1] = Easing.EaseOutCirc(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutCirc:
+ Function.Stack[s - 1] = Easing.EaseInOutCirc(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInBack:
+ Function.Stack[s - 1] = Easing.EaseInBack(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutBack:
+ Function.Stack[s - 1] = Easing.EaseOutBack(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutBack:
+ Function.Stack[s - 1] = Easing.EaseInOutBack(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInElastic:
+ Function.Stack[s - 1] = Easing.EaseInElastic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutElastic:
+ Function.Stack[s - 1] = Easing.EaseOutElastic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutElastic:
+ Function.Stack[s - 1] = Easing.EaseInOutElastic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInBounce:
+ Function.Stack[s - 1] = Easing.EaseInBounce(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutBounce:
+ Function.Stack[s - 1] = Easing.EaseOutBounce(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutBounce:
+ Function.Stack[s - 1] = Easing.EaseInOutBounce(Function.Stack[s - 1]);
+ break;
// comparisons
case Instructions.CompareEqual:
Function.Stack[s - 2] = Function.Stack[s - 2] == Function.Stack[s - 1] ? 1.0 : 0.0;
diff --git a/source/OpenBveApi/FunctionScripts/FunctionScript.Notation.cs b/source/OpenBveApi/FunctionScripts/FunctionScript.Notation.cs
index 95011d77e..1523eb66a 100644
--- a/source/OpenBveApi/FunctionScripts/FunctionScript.Notation.cs
+++ b/source/OpenBveApi/FunctionScripts/FunctionScript.Notation.cs
@@ -203,6 +203,12 @@ public static string GetPostfixNotationFromFunctionNotation(string Expression) {
return a[0] + " " + a[1] + " " + f;
}
throw new System.IO.InvalidDataException(f + " is expected to have 2 arguments in " + Expression);
+ case "lerp":
+ case "smoothstep":
+ if (n == 3) {
+ return a[0] + " " + a[1] + " " + a[2] + " " + f;
+ }
+ throw new System.IO.InvalidDataException(f + " is expected to have 3 arguments in " + Expression);
case "minus":
case "reciprocal":
case "floor":
@@ -217,6 +223,36 @@ public static string GetPostfixNotationFromFunctionNotation(string Expression) {
case "cos":
case "tan":
case "arctan":
+ case "easeinsine":
+ case "easeoutsine":
+ case "easeinoutsine":
+ case "easeinquad":
+ case "easeoutquad":
+ case "easeinoutquad":
+ case "easeincubic":
+ case "easeoutcubic":
+ case "easeinoutcubic":
+ case "easeinquart":
+ case "easeoutquart":
+ case "easeinoutquart":
+ case "easeinquint":
+ case "easeoutquint":
+ case "easeinoutquint":
+ case "easeinexpo":
+ case "easeoutexpo":
+ case "easeinoutexpo":
+ case "easeincirc":
+ case "easeoutcirc":
+ case "easeinoutcirc":
+ case "easeinback":
+ case "easeoutback":
+ case "easeinoutback":
+ case "easeinelastic":
+ case "easeoutelastic":
+ case "easeinoutelastic":
+ case "easeinbounce":
+ case "easeoutbounce":
+ case "easeinoutbounce":
if (n == 1) {
return a[0] + " " + f;
}
diff --git a/source/OpenBveApi/FunctionScripts/FunctionScript.cs b/source/OpenBveApi/FunctionScripts/FunctionScript.cs
index d9db77f7c..5a56ec74c 100644
--- a/source/OpenBveApi/FunctionScripts/FunctionScript.cs
+++ b/source/OpenBveApi/FunctionScripts/FunctionScript.cs
@@ -321,6 +321,166 @@ public FunctionScript(HostInterface Host, string Expression, bool Infix)
if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
InstructionSet[n] = Instructions.MathPi;
n++; s++; if (s >= m) m = s; break;
+ case "lerp":
+ if (s < 3) throw new InvalidOperationException(Arguments[i] + " requires at least 3 arguments on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathLerp;
+ n++; s -= 2; break;
+ case "smoothstep":
+ if (s < 3) throw new InvalidOperationException(Arguments[i] + " requires at least 3 arguments on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathSmoothstep;
+ n++; s -= 2; break;
+ case "easeinsine":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInSine;
+ n++; break;
+ case "easeoutsine":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseOutSine;
+ n++; break;
+ case "easeinoutsine":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInOutSine;
+ n++; break;
+ case "easeinquad":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInQuad;
+ n++; break;
+ case "easeoutquad":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseOutQuad;
+ n++; break;
+ case "easeinoutquad":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInOutQuad;
+ n++; break;
+ case "easeincubic":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInCubic;
+ n++; break;
+ case "easeoutcubic":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseOutCubic;
+ n++; break;
+ case "easeinoutcubic":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInOutCubic;
+ n++; break;
+ case "easeinquart":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInQuart;
+ n++; break;
+ case "easeoutquart":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseOutQuart;
+ n++; break;
+ case "easeinoutquart":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInOutQuart;
+ n++; break;
+ case "easeinquint":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInQuint;
+ n++; break;
+ case "easeoutquint":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseOutQuint;
+ n++; break;
+ case "easeinoutquint":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInOutQuint;
+ n++; break;
+ case "easeinexpo":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInExpo;
+ n++; break;
+ case "easeoutexpo":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseOutExpo;
+ n++; break;
+ case "easeinoutexpo":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInOutExpo;
+ n++; break;
+ case "easeincirc":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInCirc;
+ n++; break;
+ case "easeoutcirc":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseOutCirc;
+ n++; break;
+ case "easeinoutcirc":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInOutCirc;
+ n++; break;
+ case "easeinback":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInBack;
+ n++; break;
+ case "easeoutback":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseOutBack;
+ n++; break;
+ case "easeinoutback":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInOutBack;
+ n++; break;
+ case "easeinelastic":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInElastic;
+ n++; break;
+ case "easeoutelastic":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseOutElastic;
+ n++; break;
+ case "easeinoutelastic":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInOutElastic;
+ n++; break;
+ case "easeinbounce":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInBounce;
+ n++; break;
+ case "easeoutbounce":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseOutBounce;
+ n++; break;
+ case "easeinoutbounce":
+ if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathEaseInOutBounce;
+ n++; break;
case "==":
if (s < 2) throw new InvalidOperationException(Arguments[i] + " requires at least 2 arguments on the stack in function script " + Expression);
if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
diff --git a/source/OpenBveApi/FunctionScripts/Instructions.cs b/source/OpenBveApi/FunctionScripts/Instructions.cs
index 355a66bd0..f23263cd3 100644
--- a/source/OpenBveApi/FunctionScripts/Instructions.cs
+++ b/source/OpenBveApi/FunctionScripts/Instructions.cs
@@ -9,6 +9,17 @@ public enum Instructions {
MathIncrement, MathDecrement, MathFusedMultiplyAdd,
MathQuotient, MathMod, MathFloor, MathCeiling, MathRound, MathMin, MathMax, MathAbs, MathSign,
MathExp, MathLog, MathSqrt, MathSin, MathCos, MathTan, MathArcTan, MathPi,
+ MathLerp, MathSmoothstep,
+ MathEaseInSine, MathEaseOutSine, MathEaseInOutSine,
+ MathEaseInQuad, MathEaseOutQuad, MathEaseInOutQuad,
+ MathEaseInCubic, MathEaseOutCubic, MathEaseInOutCubic,
+ MathEaseInQuart, MathEaseOutQuart, MathEaseInOutQuart,
+ MathEaseInQuint, MathEaseOutQuint, MathEaseInOutQuint,
+ MathEaseInExpo, MathEaseOutExpo, MathEaseInOutExpo,
+ MathEaseInCirc, MathEaseOutCirc, MathEaseInOutCirc,
+ MathEaseInBack, MathEaseOutBack, MathEaseInOutBack,
+ MathEaseInElastic, MathEaseOutElastic, MathEaseInOutElastic,
+ MathEaseInBounce, MathEaseOutBounce, MathEaseInOutBounce,
CompareEqual, CompareUnequal, CompareLess, CompareGreater, CompareLessEqual, CompareGreaterEqual, CompareConditional,
LogicalNot, LogicalAnd, LogicalOr, LogicalNand, LogicalNor, LogicalXor,
/*
diff --git a/source/OpenBveApi/Math/Easing.cs b/source/OpenBveApi/Math/Easing.cs
new file mode 100644
index 000000000..a284f4fb7
--- /dev/null
+++ b/source/OpenBveApi/Math/Easing.cs
@@ -0,0 +1,355 @@
+namespace OpenBveApi.Math
+{
+ /// Provides standard easing and interpolation functions
+ /// Easing curves follow the reference implementations by Robert Penner and operate on a normalized input in the range 0..1
+ public static class Easing
+ {
+ /// Linearly interpolates between two values
+ /// The first value
+ /// The second value
+ /// The interpolation factor
+ public static double Lerp(double a, double b, double t)
+ {
+ return a + (b - a) * t;
+ }
+
+ /// Performs a Hermite (smoothstep) interpolation between two values
+ /// The first value
+ /// The second value
+ /// The interpolation input
+ public static double Smoothstep(double a, double b, double x)
+ {
+ if (a == b)
+ {
+ return a;
+ }
+ double t = (x - a) / (b - a);
+ return t * t * (3.0 - 2.0 * t);
+ }
+
+ /// Eases the input using a sine curve
+ /// The input, in the range 0..1
+ public static double EaseInSine(double t)
+ {
+ return 1.0 - System.Math.Cos(t * System.Math.PI / 2.0);
+ }
+
+ /// Eases the input using a sine curve
+ /// The input, in the range 0..1
+ public static double EaseOutSine(double t)
+ {
+ return System.Math.Sin(t * System.Math.PI / 2.0);
+ }
+
+ /// Eases the input using a sine curve
+ /// The input, in the range 0..1
+ public static double EaseInOutSine(double t)
+ {
+ return -(System.Math.Cos(System.Math.PI * t) - 1.0) / 2.0;
+ }
+
+ /// Eases the input using a quadratic curve
+ /// The input, in the range 0..1
+ public static double EaseInQuad(double t)
+ {
+ return t * t;
+ }
+
+ /// Eases the input using a quadratic curve
+ /// The input, in the range 0..1
+ public static double EaseOutQuad(double t)
+ {
+ double u = 1.0 - t;
+ return 1.0 - u * u;
+ }
+
+ /// Eases the input using a quadratic curve
+ /// The input, in the range 0..1
+ public static double EaseInOutQuad(double t)
+ {
+ if (t < 0.5)
+ {
+ return 2.0 * t * t;
+ }
+ double v = -2.0 * t + 2.0;
+ return 1.0 - v * v / 2.0;
+ }
+
+ /// Eases the input using a cubic curve
+ /// The input, in the range 0..1
+ public static double EaseInCubic(double t)
+ {
+ return t * t * t;
+ }
+
+ /// Eases the input using a cubic curve
+ /// The input, in the range 0..1
+ public static double EaseOutCubic(double t)
+ {
+ double u = 1.0 - t;
+ return 1.0 - u * u * u;
+ }
+
+ /// Eases the input using a cubic curve
+ /// The input, in the range 0..1
+ public static double EaseInOutCubic(double t)
+ {
+ if (t < 0.5)
+ {
+ return 4.0 * t * t * t;
+ }
+ double v = -2.0 * t + 2.0;
+ return 1.0 - v * v * v / 2.0;
+ }
+
+ /// Eases the input using a quartic curve
+ /// The input, in the range 0..1
+ public static double EaseInQuart(double t)
+ {
+ double s = t * t;
+ return s * s;
+ }
+
+ /// Eases the input using a quartic curve
+ /// The input, in the range 0..1
+ public static double EaseOutQuart(double t)
+ {
+ double u = 1.0 - t;
+ double s = u * u;
+ return 1.0 - s * s;
+ }
+
+ /// Eases the input using a quartic curve
+ /// The input, in the range 0..1
+ public static double EaseInOutQuart(double t)
+ {
+ if (t < 0.5)
+ {
+ double s = t * t;
+ return 8.0 * s * s;
+ }
+ double v = -2.0 * t + 2.0;
+ double s2 = v * v;
+ return 1.0 - s2 * s2 / 2.0;
+ }
+
+ /// Eases the input using a quintic curve
+ /// The input, in the range 0..1
+ public static double EaseInQuint(double t)
+ {
+ double s = t * t;
+ return s * s * t;
+ }
+
+ /// Eases the input using a quintic curve
+ /// The input, in the range 0..1
+ public static double EaseOutQuint(double t)
+ {
+ double u = 1.0 - t;
+ double s = u * u;
+ return 1.0 - s * s * u;
+ }
+
+ /// Eases the input using a quintic curve
+ /// The input, in the range 0..1
+ public static double EaseInOutQuint(double t)
+ {
+ if (t < 0.5)
+ {
+ double s = t * t;
+ return 16.0 * s * s * t;
+ }
+ double v = -2.0 * t + 2.0;
+ double s2 = v * v;
+ return 1.0 - s2 * s2 * v / 2.0;
+ }
+
+ /// Eases the input using an exponential curve
+ /// The input, in the range 0..1
+ public static double EaseInExpo(double t)
+ {
+ return t == 0.0 ? 0.0 : System.Math.Pow(2.0, 10.0 * t - 10.0);
+ }
+
+ /// Eases the input using an exponential curve
+ /// The input, in the range 0..1
+ public static double EaseOutExpo(double t)
+ {
+ return t == 1.0 ? 1.0 : 1.0 - System.Math.Pow(2.0, -10.0 * t);
+ }
+
+ /// Eases the input using an exponential curve
+ /// The input, in the range 0..1
+ public static double EaseInOutExpo(double t)
+ {
+ if (t == 0.0)
+ {
+ return 0.0;
+ }
+ if (t == 1.0)
+ {
+ return 1.0;
+ }
+ if (t < 0.5)
+ {
+ return System.Math.Pow(2.0, 20.0 * t - 10.0) / 2.0;
+ }
+ return (2.0 - System.Math.Pow(2.0, -20.0 * t + 10.0)) / 2.0;
+ }
+
+ /// Eases the input using a circular curve
+ /// The input, in the range 0..1
+ public static double EaseInCirc(double t)
+ {
+ return 1.0 - Extensions.SqrtC(1.0 - t * t);
+ }
+
+ /// Eases the input using a circular curve
+ /// The input, in the range 0..1
+ public static double EaseOutCirc(double t)
+ {
+ double u = t - 1.0;
+ return Extensions.SqrtC(1.0 - u * u);
+ }
+
+ /// Eases the input using a circular curve
+ /// The input, in the range 0..1
+ public static double EaseInOutCirc(double t)
+ {
+ if (t < 0.5)
+ {
+ return (1.0 - Extensions.SqrtC(1.0 - 4.0 * t * t)) / 2.0;
+ }
+ double v = -2.0 * t + 2.0;
+ return (Extensions.SqrtC(1.0 - v * v) + 1.0) / 2.0;
+ }
+
+ /// Eases the input using a back curve
+ /// The input, in the range 0..1
+ public static double EaseInBack(double t)
+ {
+ const double c1 = 1.70158;
+ const double c3 = c1 + 1.0;
+ return c3 * t * t * t - c1 * t * t;
+ }
+
+ /// Eases the input using a back curve
+ /// The input, in the range 0..1
+ public static double EaseOutBack(double t)
+ {
+ const double c1 = 1.70158;
+ const double c3 = c1 + 1.0;
+ double u = t - 1.0;
+ return 1.0 + c3 * u * u * u + c1 * u * u;
+ }
+
+ /// Eases the input using a back curve
+ /// The input, in the range 0..1
+ public static double EaseInOutBack(double t)
+ {
+ const double c1 = 1.70158;
+ const double c2 = c1 * 1.525;
+ if (t < 0.5)
+ {
+ double v = 2.0 * t;
+ return (v * v * ((c2 + 1.0) * v - c2)) / 2.0;
+ }
+ double w = 2.0 * t - 2.0;
+ return (w * w * ((c2 + 1.0) * w + c2) + 2.0) / 2.0;
+ }
+
+ /// Eases the input using an elastic curve
+ /// The input, in the range 0..1
+ public static double EaseInElastic(double t)
+ {
+ const double c4 = (2.0 * System.Math.PI) / 3.0;
+ if (t == 0.0)
+ {
+ return 0.0;
+ }
+ if (t == 1.0)
+ {
+ return 1.0;
+ }
+ return -System.Math.Pow(2.0, 10.0 * t - 10.0) * System.Math.Sin((t * 10.0 - 10.75) * c4);
+ }
+
+ /// Eases the input using an elastic curve
+ /// The input, in the range 0..1
+ public static double EaseOutElastic(double t)
+ {
+ const double c4 = (2.0 * System.Math.PI) / 3.0;
+ if (t == 0.0)
+ {
+ return 0.0;
+ }
+ if (t == 1.0)
+ {
+ return 1.0;
+ }
+ return System.Math.Pow(2.0, -10.0 * t) * System.Math.Sin((t * 10.0 - 0.75) * c4) + 1.0;
+ }
+
+ /// Eases the input using an elastic curve
+ /// The input, in the range 0..1
+ public static double EaseInOutElastic(double t)
+ {
+ const double c5 = (2.0 * System.Math.PI) / 4.5;
+ if (t == 0.0)
+ {
+ return 0.0;
+ }
+ if (t == 1.0)
+ {
+ return 1.0;
+ }
+ if (t < 0.5)
+ {
+ return -(System.Math.Pow(2.0, 20.0 * t - 10.0) * System.Math.Sin((20.0 * t - 11.125) * c5)) / 2.0;
+ }
+ return (System.Math.Pow(2.0, -20.0 * t + 10.0) * System.Math.Sin((20.0 * t - 11.125) * c5)) / 2.0 + 1.0;
+ }
+
+ /// Eases the input using a bounce curve
+ /// The input, in the range 0..1
+ public static double EaseInBounce(double t)
+ {
+ return 1.0 - EaseOutBounce(1.0 - t);
+ }
+
+ /// Eases the input using a bounce curve
+ /// The input, in the range 0..1
+ public static double EaseOutBounce(double t)
+ {
+ const double n1 = 7.5625;
+ const double d1 = 2.75;
+ if (t < 1.0 / d1)
+ {
+ return n1 * t * t;
+ }
+ if (t < 2.0 / d1)
+ {
+ double u = t - 1.5 / d1;
+ return n1 * u * u + 0.75;
+ }
+ if (t < 2.5 / d1)
+ {
+ double u = t - 2.25 / d1;
+ return n1 * u * u + 0.9375;
+ }
+ double v = t - 2.625 / d1;
+ return n1 * v * v + 0.984375;
+ }
+
+ /// Eases the input using a bounce curve
+ /// The input, in the range 0..1
+ public static double EaseInOutBounce(double t)
+ {
+ if (t < 0.5)
+ {
+ return (1.0 - EaseOutBounce(1.0 - 2.0 * t)) / 2.0;
+ }
+ return (1.0 + EaseOutBounce(2.0 * t - 1.0)) / 2.0;
+ }
+ }
+}
diff --git a/source/OpenBveApi/OpenBveApi.csproj b/source/OpenBveApi/OpenBveApi.csproj
index 623bd7fd1..8cf63e31d 100644
--- a/source/OpenBveApi/OpenBveApi.csproj
+++ b/source/OpenBveApi/OpenBveApi.csproj
@@ -131,6 +131,7 @@
+
diff --git a/source/RouteViewer/FunctionScripts.cs b/source/RouteViewer/FunctionScripts.cs
index 6ec5b6a6e..0da2c0977 100644
--- a/source/RouteViewer/FunctionScripts.cs
+++ b/source/RouteViewer/FunctionScripts.cs
@@ -160,6 +160,102 @@ internal static void ExecuteFunctionScript(FunctionScript Function, TrainBase Tr
case Instructions.MathPi:
Function.Stack[s] = Math.PI;
s++; break;
+ case Instructions.MathLerp:
+ Function.Stack[s - 3] = Easing.Lerp(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
+ s -= 2; break;
+ case Instructions.MathSmoothstep:
+ Function.Stack[s - 3] = Easing.Smoothstep(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
+ s -= 2; break;
+ case Instructions.MathEaseInSine:
+ Function.Stack[s - 1] = Easing.EaseInSine(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutSine:
+ Function.Stack[s - 1] = Easing.EaseOutSine(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutSine:
+ Function.Stack[s - 1] = Easing.EaseInOutSine(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInQuad:
+ Function.Stack[s - 1] = Easing.EaseInQuad(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutQuad:
+ Function.Stack[s - 1] = Easing.EaseOutQuad(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutQuad:
+ Function.Stack[s - 1] = Easing.EaseInOutQuad(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInCubic:
+ Function.Stack[s - 1] = Easing.EaseInCubic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutCubic:
+ Function.Stack[s - 1] = Easing.EaseOutCubic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutCubic:
+ Function.Stack[s - 1] = Easing.EaseInOutCubic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInQuart:
+ Function.Stack[s - 1] = Easing.EaseInQuart(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutQuart:
+ Function.Stack[s - 1] = Easing.EaseOutQuart(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutQuart:
+ Function.Stack[s - 1] = Easing.EaseInOutQuart(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInQuint:
+ Function.Stack[s - 1] = Easing.EaseInQuint(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutQuint:
+ Function.Stack[s - 1] = Easing.EaseOutQuint(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutQuint:
+ Function.Stack[s - 1] = Easing.EaseInOutQuint(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInExpo:
+ Function.Stack[s - 1] = Easing.EaseInExpo(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutExpo:
+ Function.Stack[s - 1] = Easing.EaseOutExpo(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutExpo:
+ Function.Stack[s - 1] = Easing.EaseInOutExpo(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInCirc:
+ Function.Stack[s - 1] = Easing.EaseInCirc(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutCirc:
+ Function.Stack[s - 1] = Easing.EaseOutCirc(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutCirc:
+ Function.Stack[s - 1] = Easing.EaseInOutCirc(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInBack:
+ Function.Stack[s - 1] = Easing.EaseInBack(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutBack:
+ Function.Stack[s - 1] = Easing.EaseOutBack(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutBack:
+ Function.Stack[s - 1] = Easing.EaseInOutBack(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInElastic:
+ Function.Stack[s - 1] = Easing.EaseInElastic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutElastic:
+ Function.Stack[s - 1] = Easing.EaseOutElastic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutElastic:
+ Function.Stack[s - 1] = Easing.EaseInOutElastic(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInBounce:
+ Function.Stack[s - 1] = Easing.EaseInBounce(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseOutBounce:
+ Function.Stack[s - 1] = Easing.EaseOutBounce(Function.Stack[s - 1]);
+ break;
+ case Instructions.MathEaseInOutBounce:
+ Function.Stack[s - 1] = Easing.EaseInOutBounce(Function.Stack[s - 1]);
+ break;
// comparisons
case Instructions.CompareEqual:
Function.Stack[s - 2] = Function.Stack[s - 2] == Function.Stack[s - 1] ? 1.0 : 0.0;
From e0817b01a166e1023e265d52b2217c84d902f001 Mon Sep 17 00:00:00 2001
From: adfriz <76892624+adfriz@users.noreply.github.com>
Date: Mon, 3 Aug 2026 22:52:40 +0700
Subject: [PATCH 2/2] Add slerp
---
source/ObjectViewer/FunctionScripts.cs | 3 +++
.../AnimatedObjects/FunctionScripts.cs | 3 +++
.../FunctionScripts/FunctionScript.Notation.cs | 1 +
source/OpenBveApi/FunctionScripts/FunctionScript.cs | 5 +++++
source/OpenBveApi/FunctionScripts/Instructions.cs | 2 +-
source/OpenBveApi/Math/Easing.cs | 13 +++++++++++++
source/RouteViewer/FunctionScripts.cs | 3 +++
7 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/source/ObjectViewer/FunctionScripts.cs b/source/ObjectViewer/FunctionScripts.cs
index 70f0f5240..ce178840c 100644
--- a/source/ObjectViewer/FunctionScripts.cs
+++ b/source/ObjectViewer/FunctionScripts.cs
@@ -167,6 +167,9 @@ internal static void ExecuteFunctionScript(FunctionScript Function, TrainBase Tr
case Instructions.MathSmoothstep:
Function.Stack[s - 3] = Easing.Smoothstep(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
s -= 2; break;
+ case Instructions.MathSlerp:
+ Function.Stack[s - 3] = Easing.Slerp(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
+ s -= 2; break;
case Instructions.MathEaseInSine:
Function.Stack[s - 1] = Easing.EaseInSine(Function.Stack[s - 1]);
break;
diff --git a/source/OpenBVE/Game/ObjectManager/AnimatedObjects/FunctionScripts.cs b/source/OpenBVE/Game/ObjectManager/AnimatedObjects/FunctionScripts.cs
index 2d5618f6b..4e5f61a6b 100644
--- a/source/OpenBVE/Game/ObjectManager/AnimatedObjects/FunctionScripts.cs
+++ b/source/OpenBVE/Game/ObjectManager/AnimatedObjects/FunctionScripts.cs
@@ -169,6 +169,9 @@ internal static void ExecuteFunctionScript(FunctionScript Function, TrainBase Tr
case Instructions.MathSmoothstep:
Function.Stack[s - 3] = Easing.Smoothstep(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
s -= 2; break;
+ case Instructions.MathSlerp:
+ Function.Stack[s - 3] = Easing.Slerp(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
+ s -= 2; break;
case Instructions.MathEaseInSine:
Function.Stack[s - 1] = Easing.EaseInSine(Function.Stack[s - 1]);
break;
diff --git a/source/OpenBveApi/FunctionScripts/FunctionScript.Notation.cs b/source/OpenBveApi/FunctionScripts/FunctionScript.Notation.cs
index 1523eb66a..9b085a972 100644
--- a/source/OpenBveApi/FunctionScripts/FunctionScript.Notation.cs
+++ b/source/OpenBveApi/FunctionScripts/FunctionScript.Notation.cs
@@ -205,6 +205,7 @@ public static string GetPostfixNotationFromFunctionNotation(string Expression) {
throw new System.IO.InvalidDataException(f + " is expected to have 2 arguments in " + Expression);
case "lerp":
case "smoothstep":
+ case "slerp":
if (n == 3) {
return a[0] + " " + a[1] + " " + a[2] + " " + f;
}
diff --git a/source/OpenBveApi/FunctionScripts/FunctionScript.cs b/source/OpenBveApi/FunctionScripts/FunctionScript.cs
index 5a56ec74c..7a97f64e1 100644
--- a/source/OpenBveApi/FunctionScripts/FunctionScript.cs
+++ b/source/OpenBveApi/FunctionScripts/FunctionScript.cs
@@ -331,6 +331,11 @@ public FunctionScript(HostInterface Host, string Expression, bool Infix)
if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
InstructionSet[n] = Instructions.MathSmoothstep;
n++; s -= 2; break;
+ case "slerp":
+ if (s < 3) throw new InvalidOperationException(Arguments[i] + " requires at least 3 arguments on the stack in function script " + Expression);
+ if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
+ InstructionSet[n] = Instructions.MathSlerp;
+ n++; s -= 2; break;
case "easeinsine":
if (s < 1) throw new InvalidOperationException(Arguments[i] + " requires at least 1 argument on the stack in function script " + Expression);
if (n >= InstructionSet.Length) Array.Resize(ref InstructionSet, InstructionSet.Length << 1);
diff --git a/source/OpenBveApi/FunctionScripts/Instructions.cs b/source/OpenBveApi/FunctionScripts/Instructions.cs
index f23263cd3..92a04138d 100644
--- a/source/OpenBveApi/FunctionScripts/Instructions.cs
+++ b/source/OpenBveApi/FunctionScripts/Instructions.cs
@@ -9,7 +9,7 @@ public enum Instructions {
MathIncrement, MathDecrement, MathFusedMultiplyAdd,
MathQuotient, MathMod, MathFloor, MathCeiling, MathRound, MathMin, MathMax, MathAbs, MathSign,
MathExp, MathLog, MathSqrt, MathSin, MathCos, MathTan, MathArcTan, MathPi,
- MathLerp, MathSmoothstep,
+ MathLerp, MathSmoothstep, MathSlerp,
MathEaseInSine, MathEaseOutSine, MathEaseInOutSine,
MathEaseInQuad, MathEaseOutQuad, MathEaseInOutQuad,
MathEaseInCubic, MathEaseOutCubic, MathEaseInOutCubic,
diff --git a/source/OpenBveApi/Math/Easing.cs b/source/OpenBveApi/Math/Easing.cs
index a284f4fb7..8832afca6 100644
--- a/source/OpenBveApi/Math/Easing.cs
+++ b/source/OpenBveApi/Math/Easing.cs
@@ -27,6 +27,19 @@ public static double Smoothstep(double a, double b, double x)
return t * t * (3.0 - 2.0 * t);
}
+ /// Spherically (shortest-arc) interpolates between two angles
+ /// The first angle
+ /// The second angle
+ /// The interpolation factor
+ public static double Slerp(double a, double b, double t)
+ {
+ double delta = b - a;
+ delta = (delta + System.Math.PI) % (2.0 * System.Math.PI);
+ if (delta < 0.0) delta += 2.0 * System.Math.PI;
+ delta -= System.Math.PI;
+ return a + delta * t;
+ }
+
/// Eases the input using a sine curve
/// The input, in the range 0..1
public static double EaseInSine(double t)
diff --git a/source/RouteViewer/FunctionScripts.cs b/source/RouteViewer/FunctionScripts.cs
index 0da2c0977..85efc5a41 100644
--- a/source/RouteViewer/FunctionScripts.cs
+++ b/source/RouteViewer/FunctionScripts.cs
@@ -166,6 +166,9 @@ internal static void ExecuteFunctionScript(FunctionScript Function, TrainBase Tr
case Instructions.MathSmoothstep:
Function.Stack[s - 3] = Easing.Smoothstep(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
s -= 2; break;
+ case Instructions.MathSlerp:
+ Function.Stack[s - 3] = Easing.Slerp(Function.Stack[s - 3], Function.Stack[s - 2], Function.Stack[s - 1]);
+ s -= 2; break;
case Instructions.MathEaseInSine:
Function.Stack[s - 1] = Easing.EaseInSine(Function.Stack[s - 1]);
break;