diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f1ceec64..3d47c249b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
### Lua API changes
* Added missing constructor for `Collision.Probe` without room number.
+* Added optional looping argument for `View.GetFlybyPosition` and `View.GetFlybyRotation` functions.
## [Version 1.8](https://github.com/TombEngine/TombEditorReleases/releases/tag/v1.8) - 2025-03-16
diff --git a/Documentation/doc/1 modules/View.html b/Documentation/doc/1 modules/View.html
index f1725ceb1..40f491a7b 100644
--- a/Documentation/doc/1 modules/View.html
+++ b/Documentation/doc/1 modules/View.html
@@ -176,11 +176,11 @@
Play a flyby sequence. |
- GetFlybyPosition(seqID, progress) |
+ GetFlybyPosition(seqID, progress[, loop]) |
Get a flyby sequence's position at a specified progress point in percent. |
- GetFlybyRotation(seqID, progress) |
+ GetFlybyRotation(seqID, progress[, loop]) |
Get a flyby sequence's rotation at a specified progress point in percent. |
@@ -499,7 +499,7 @@
- GetFlybyPosition(seqID, progress)
+ GetFlybyPosition(seqID, progress[, loop])
Get a flyby sequence's position at a specified progress point in percent.
@@ -516,6 +516,11 @@
float
Progress point in percent. Clamped to [0, 100].
+ loop
+ bool
+ Smooth the position near start and end points, as if the sequence is looped.
+ (optional)
+
Returns:
@@ -531,7 +536,7 @@
- GetFlybyRotation(seqID, progress)
+ GetFlybyRotation(seqID, progress[, loop])
Get a flyby sequence's rotation at a specified progress point in percent.
@@ -548,6 +553,11 @@
float
Progress point in percent. Clamped to [0, 100].
+ loop
+ bool
+ Smooth the position near start and end points, as if the sequence is looped.
+ (optional)
+
Returns:
diff --git a/TombEngine/Game/spotcam.cpp b/TombEngine/Game/spotcam.cpp
index ffa8be942..2b6ea8d10 100644
--- a/TombEngine/Game/spotcam.cpp
+++ b/TombEngine/Game/spotcam.cpp
@@ -839,7 +839,7 @@ int Spline(int x, int* knots, int nk)
return ((__int64)x * (((__int64)x * (((__int64)x * c1 >> 16) + c2) >> 16) + (k[2] >> 1) + ((-k[0] - 1) >> 1)) >> 16) + k[1];
}
-Pose GetCameraTransform(int sequence, float alpha)
+Pose GetCameraTransform(int sequence, float alpha, bool loop)
{
alpha = std::clamp(alpha, 0.0f, 1.0f);
@@ -873,15 +873,43 @@ Pose GetCameraTransform(int sequence, float alpha)
}
// Compute spline interpolation of main flyby camera parameters.
- auto origin = Vector3(Spline(splineAlpha, xOrigins.data(), splinePoints),
- Spline(splineAlpha, yOrigins.data(), splinePoints),
- Spline(splineAlpha, zOrigins.data(), splinePoints));
+ auto getInterpolatedPoint = [&](float t, std::vector& x, std::vector& y, std::vector& z)
+ {
+ int tAlpha = int(t * (float)USHRT_MAX);
+ return Vector3(Spline(tAlpha, x.data(), splinePoints),
+ Spline(tAlpha, y.data(), splinePoints),
+ Spline(tAlpha, z.data(), splinePoints));
+ };
- auto target = Vector3(Spline(splineAlpha, xTargets.data(), splinePoints),
- Spline(splineAlpha, yTargets.data(), splinePoints),
- Spline(splineAlpha, zTargets.data(), splinePoints));
+ auto getInterpolatedRoll = [&](float t)
+ {
+ int tAlpha = int(t * (float)USHRT_MAX);
+ return Spline(tAlpha, rolls.data(), splinePoints);
+ };
- short orientZ = Spline(splineAlpha, rolls.data(), splinePoints);
+ Vector3 origin = {};
+ Vector3 target = {};
+ short orientZ = 0;
+
+ constexpr float BLEND_RANGE = 0.1f;
+ constexpr float BLEND_START = BLEND_RANGE;
+ constexpr float BLEND_END = 1.0f - BLEND_RANGE;
+
+ // If loop is enabled and we are at the start or end of the sequence, blend between the last and first cameras.
+ if (loop && (alpha < BLEND_START || alpha >= BLEND_END))
+ {
+ float blendFactor = (alpha < BLEND_START) ? 0.5f + (alpha / BLEND_RANGE) * 0.5f : (alpha - BLEND_END) / BLEND_START * 0.5f;
+
+ origin = Vector3::Lerp(getInterpolatedPoint(BLEND_END, xOrigins, yOrigins, zOrigins), getInterpolatedPoint(BLEND_START, xOrigins, yOrigins, zOrigins), blendFactor);
+ target = Vector3::Lerp(getInterpolatedPoint(BLEND_END, xTargets, yTargets, zTargets), getInterpolatedPoint(BLEND_START, xTargets, yTargets, zTargets), blendFactor);
+ orientZ = Lerp(getInterpolatedRoll(BLEND_END), getInterpolatedRoll(BLEND_START), blendFactor);
+ }
+ else
+ {
+ origin = getInterpolatedPoint(alpha, xOrigins, yOrigins, zOrigins);
+ target = getInterpolatedPoint(alpha, xTargets, yTargets, zTargets);
+ orientZ = getInterpolatedRoll(alpha);
+ }
auto pose = Pose(origin, EulerAngles(target - origin));
pose.Orientation.z = orientZ;
diff --git a/TombEngine/Game/spotcam.h b/TombEngine/Game/spotcam.h
index 035929e93..07917c26d 100644
--- a/TombEngine/Game/spotcam.h
+++ b/TombEngine/Game/spotcam.h
@@ -64,4 +64,4 @@ void InitializeSpotCam(short sequence);
void CalculateSpotCameras();
int Spline(int x, int* knots, int nk);
-Pose GetCameraTransform(int sequence, float alpha);
+Pose GetCameraTransform(int sequence, float alpha, bool loop);
diff --git a/TombEngine/Scripting/Internal/TEN/View/ViewHandler.cpp b/TombEngine/Scripting/Internal/TEN/View/ViewHandler.cpp
index 501784af1..8076215b2 100644
--- a/TombEngine/Scripting/Internal/TEN/View/ViewHandler.cpp
+++ b/TombEngine/Scripting/Internal/TEN/View/ViewHandler.cpp
@@ -111,18 +111,18 @@ namespace TEN::Scripting::View
InitializeSpotCam(seqID);
}
- static Vec3 GetFlybyPosition(int seqID, float progress)
+ static Vec3 GetFlybyPosition(int seqID, float progress, TypeOrNil loop)
{
constexpr auto PROGRESS_MAX = 100.0f;
- return Vec3(GetCameraTransform(seqID, progress / PROGRESS_MAX).Position);
+ return Vec3(GetCameraTransform(seqID, progress / PROGRESS_MAX, ValueOr(loop, false)).Position);
}
- static Rotation GetFlybyRotation(int seqID, float progress)
+ static Rotation GetFlybyRotation(int seqID, float progress, TypeOrNil loop)
{
constexpr auto PROGRESS_MAX = 100.0f;
- return Rotation(GetCameraTransform(seqID, progress / PROGRESS_MAX).Orientation);
+ return Rotation(GetCameraTransform(seqID, progress / PROGRESS_MAX, ValueOr(loop, false)).Orientation);
}
static void FlashScreen(TypeOrNil col, TypeOrNil speed)
@@ -244,6 +244,7 @@ namespace TEN::Scripting::View
// @function GetFlybyPosition
// @tparam int seqID Flyby sequence ID.
// @tparam float progress Progress point in percent. Clamped to [0, 100].
+ // @tparam[opt] bool loop Smooth the position near start and end points, as if the sequence is looped.
// @treturn Vec3 Position at the given progress point.
tableView.set_function(ScriptReserved_GetFlybyPosition, &GetFlybyPosition);
@@ -251,6 +252,7 @@ namespace TEN::Scripting::View
// @function GetFlybyRotation
// @tparam int seqID Flyby sequence ID.
// @tparam float progress Progress point in percent. Clamped to [0, 100].
+ // @tparam[opt] bool loop Smooth the position near start and end points, as if the sequence is looped.
// @treturn Rotation Rotation at the given progress point.
tableView.set_function(ScriptReserved_GetFlybyRotation, &GetFlybyRotation);