From bea912ddb2ee740edaa18f9d16dd44ef6b8708da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:08:21 +0000 Subject: [PATCH 01/16] Initial plan From c3901a0a29af4b7843bd3662a05f829c0ec701ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:14:26 +0000 Subject: [PATCH 02/16] Add WayPoint Lua object class with path interpolation Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEngine/Scripting/Include/VarMapVal.h | 4 +- .../Scripting/Internal/ReservedScriptNames.h | 11 ++ .../Internal/TEN/Objects/ObjectsHandler.cpp | 22 +++ .../Internal/TEN/Objects/ObjectsHandler.h | 22 +++ .../TEN/Objects/WayPoint/WayPointObject.cpp | 155 ++++++++++++++++++ .../TEN/Objects/WayPoint/WayPointObject.h | 46 ++++++ TombEngine/Specific/level.cpp | 10 ++ 7 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp create mode 100644 TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h diff --git a/TombEngine/Scripting/Include/VarMapVal.h b/TombEngine/Scripting/Include/VarMapVal.h index c601126565..45e330c070 100644 --- a/TombEngine/Scripting/Include/VarMapVal.h +++ b/TombEngine/Scripting/Include/VarMapVal.h @@ -7,6 +7,7 @@ struct SoundSourceInfo; struct TriggerVolume; struct AI_OBJECT; struct RoomData; +struct SPOTCAM; using VarMapVal = std::variant< int, @@ -16,4 +17,5 @@ using VarMapVal = std::variant< std::reference_wrapper, std::reference_wrapper, std::reference_wrapper, - std::reference_wrapper>; + std::reference_wrapper, + std::reference_wrapper>; diff --git a/TombEngine/Scripting/Internal/ReservedScriptNames.h b/TombEngine/Scripting/Internal/ReservedScriptNames.h index 923bc35d85..4bf5684295 100644 --- a/TombEngine/Scripting/Internal/ReservedScriptNames.h +++ b/TombEngine/Scripting/Internal/ReservedScriptNames.h @@ -488,6 +488,17 @@ constexpr char ScriptReserved_SinkSetName[] = "SetName"; constexpr char ScriptReserved_SinkSetPosition[] = "SetPosition"; constexpr char ScriptReserved_SinkSetStrength[] = "SetStrength"; +// WayPoint + +constexpr char ScriptReserved_WayPoint[] = "WayPoint"; +constexpr char ScriptReserved_GetWayPointByName[] = "GetWayPointByName"; +constexpr char ScriptReserved_GetWayPointsByType[] = "GetWayPointsByType"; +constexpr char ScriptReserved_GetPathPosition[] = "GetPathPosition"; +constexpr char ScriptReserved_GetPathRotation[] = "GetPathRotation"; +constexpr char ScriptReserved_GetType[] = "GetType"; +constexpr char ScriptReserved_SetType[] = "SetType"; +constexpr char ScriptReserved_GetSequence[] = "GetSequence"; + // Static constexpr char ScriptReserved_Static[] = "Static"; diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp index eb11057770..868d5e94f1 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp @@ -19,6 +19,7 @@ #include "Scripting/Internal/TEN/Objects/Sink/SinkObject.h" #include "Scripting/Internal/TEN/Objects/SoundSource/SoundSourceObject.h" #include "Scripting/Internal/TEN/Objects/Volume/VolumeObject.h" +#include "Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h" using namespace TEN::Scripting::Objects; @@ -81,6 +82,22 @@ ObjectsHandler::ObjectsHandler(sol::state* lua, sol::table& parent) : */ _table_objects.set_function(ScriptReserved_GetSinkByName, &ObjectsHandler::GetByName, this); + /*** + Get a WayPoint by its name. + @function GetWayPointByName + @tparam string name The unique name of the waypoint. + @treturn Objects.WayPoint A non-owning WayPoint referencing the waypoint. + */ + _table_objects.set_function(ScriptReserved_GetWayPointByName, &ObjectsHandler::GetByName, this); + + /*** + Get waypoints by their type/camera number. + @function GetWayPointsByType + @tparam int type The type/camera number of the waypoints. + @treturn table Table of waypoints referencing the given type. + */ + _table_objects.set_function(ScriptReserved_GetWayPointsByType, &ObjectsHandler::GetWayPointsByType, this); + /*** Get a SoundSource by its name. @function GetSoundSourceByName @@ -172,6 +189,11 @@ ObjectsHandler::ObjectsHandler(sol::state* lua, sol::table& parent) : [this](auto && ... param) { return AddName(std::forward(param)...); }, [this](auto && ... param) { return RemoveName(std::forward(param)...); }); + WayPointObject::Register(_table_objects); + WayPointObject::SetNameCallbacks( + [this](auto && ... param) { return AddName(std::forward(param)...); }, + [this](auto && ... param) { return RemoveName(std::forward(param)...); }); + _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_ObjID, GAME_OBJECT_IDS); _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_RoomFlagID, ROOM_FLAG_IDS); _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_RoomReverb, ROOM_REVERB_TYPES); diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h index ad2264f93c..d72fc13065 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h @@ -8,6 +8,8 @@ #include "Scripting/Internal/TEN/Objects/Static/StaticObject.h" #include "Scripting/Internal/TEN/Objects/AIObject/AIObject.h" +struct SPOTCAM; + class ObjectsHandler : public ScriptInterfaceObjectsHandler { public: @@ -126,6 +128,26 @@ class ObjectsHandler : public ScriptInterfaceObjectsHandler return rooms; } + template + std::vector > GetWayPointsByType(int type) + { + auto waypoints = std::vector>{}; + for (const auto& [key, value] : _nameMap) + { + if (!std::holds_alternative>(value)) + continue; + + auto waypoint = std::get>(value).get(); + + if (waypoint.camera == type) + { + waypoints.push_back(GetByName(key)); + } + } + + return waypoints; + } + int GetIndexByName(std::string const& name) const override { if (_nameMap.find(name) == _nameMap.end()) diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp new file mode 100644 index 0000000000..365b0a1752 --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp @@ -0,0 +1,155 @@ +#include "framework.h" +#include "WayPointObject.h" +#include "Game/spotcam.h" + +#include "Scripting/Internal/ReservedScriptNames.h" +#include "Scripting/Internal/ScriptAssert.h" +#include "Scripting/Internal/ScriptUtil.h" +#include "Scripting/Internal/TEN/Types/Vec3/Vec3.h" +#include "Scripting/Internal/TEN/Types/Rotation/Rotation.h" +#include "Specific/level.h" + +/*** +Waypoint objects for navigation and path traversal. + +@tenclass Objects.WayPoint +@pragma nostrip +*/ + +static auto IndexError = IndexErrorMaker(WayPointObject, ScriptReserved_WayPoint); +static auto NewIndexError = NewIndexErrorMaker(WayPointObject, ScriptReserved_WayPoint); + +WayPointObject::WayPointObject(SPOTCAM& ref) : m_waypoint{ref} +{}; + +void WayPointObject::Register(sol::table& parent) +{ + parent.new_usertype(ScriptReserved_WayPoint, + sol::no_constructor, + sol::meta_function::index, IndexError, + sol::meta_function::new_index, NewIndexError, + + /// Get the waypoint's position. + // @function WayPoint:GetPosition + // @treturn Vec3 Waypoint's position. + ScriptReserved_GetPosition, &WayPointObject::GetPos, + + /// Set the waypoint's position. + // @function WayPoint:SetPosition + // @tparam Vec3 position The new position of the waypoint. + ScriptReserved_SetPosition, &WayPointObject::SetPos, + + /// Get the waypoint's target position. + // @function WayPoint:GetTarget + // @treturn Vec3 Waypoint's target position. + ScriptReserved_GetTarget, &WayPointObject::GetTarget, + + /// Set the waypoint's target position. + // @function WayPoint:SetTarget + // @tparam Vec3 target The new target position. + ScriptReserved_SetTarget, &WayPointObject::SetTarget, + + /// Get the waypoint's unique string identifier. + // @function WayPoint:GetName + // @treturn string The waypoint's name. + ScriptReserved_GetName, &WayPointObject::GetName, + + /// Set the waypoint's name (its unique string identifier). + // @function WayPoint:SetName + // @tparam string name The waypoint's new name. + ScriptReserved_SetName, &WayPointObject::SetName, + + /// Get the waypoint's type/camera number. + // @function WayPoint:GetType + // @treturn int The waypoint's type identifier. + ScriptReserved_GetType, &WayPointObject::GetType, + + /// Set the waypoint's type/camera number. + // @function WayPoint:SetType + // @tparam int type The waypoint's new type identifier. + ScriptReserved_SetType, &WayPointObject::SetType, + + /// Get the waypoint's sequence number. + // @function WayPoint:GetSequence + // @treturn int The waypoint's sequence number. + ScriptReserved_GetSequence, &WayPointObject::GetSequence, + + /// Get an interpolated position along the waypoint path. + // @function WayPoint:GetPathPosition + // @tparam float alpha Progress along the path (0.0 to 1.0). + // @tparam bool loop Whether to loop the path continuously. + // @treturn Vec3 Interpolated position at the given alpha. + ScriptReserved_GetPathPosition, &WayPointObject::GetPathPosition, + + /// Get an interpolated rotation along the waypoint path. + // @function WayPoint:GetPathRotation + // @tparam float alpha Progress along the path (0.0 to 1.0). + // @tparam bool loop Whether to loop the path continuously. + // @treturn Rotation Interpolated rotation at the given alpha. + ScriptReserved_GetPathRotation, &WayPointObject::GetPathRotation); +} + +Vec3 WayPointObject::GetPos() const +{ + return Vec3(m_waypoint.x, m_waypoint.y, m_waypoint.z); +} + +void WayPointObject::SetPos(Vec3 const& pos) +{ + m_waypoint.x = pos.x; + m_waypoint.y = pos.y; + m_waypoint.z = pos.z; +} + +Vec3 WayPointObject::GetTarget() const +{ + return Vec3(m_waypoint.tx, m_waypoint.ty, m_waypoint.tz); +} + +void WayPointObject::SetTarget(Vec3 const& target) +{ + m_waypoint.tx = target.x; + m_waypoint.ty = target.y; + m_waypoint.tz = target.z; +} + +std::string WayPointObject::GetName() const +{ + // Generate name based on sequence and camera index + return "waypoint_" + std::to_string(m_waypoint.sequence) + "_" + std::to_string(m_waypoint.camera); +} + +void WayPointObject::SetName(std::string const& id) +{ + if (!ScriptAssert(!id.empty(), "Name cannot be blank. Not setting name.")) + return; + + // Note: SPOTCAM structure doesn't support arbitrary names + // This would require extending the level format + TENLog("Setting custom waypoint names is not supported. Waypoint names are auto-generated based on sequence and camera index.", LogLevel::Warning, LogConfig::All); +} + +int WayPointObject::GetType() const +{ + return m_waypoint.camera; +} + +void WayPointObject::SetType(int type) +{ + m_waypoint.camera = (unsigned char)type; +} + +int WayPointObject::GetSequence() const +{ + return m_waypoint.sequence; +} + +Vec3 WayPointObject::GetPathPosition(float alpha, bool loop) const +{ + return Vec3(GetCameraTransform(m_waypoint.sequence, alpha, loop).Position); +} + +Rotation WayPointObject::GetPathRotation(float alpha, bool loop) const +{ + return Rotation(GetCameraTransform(m_waypoint.sequence, alpha, loop).Orientation); +} diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h new file mode 100644 index 0000000000..b72689f0f7 --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h @@ -0,0 +1,46 @@ +#pragma once + +#include "Scripting/Internal/TEN/Objects/NamedBase.h" +#include "Math/Math.h" + +struct SPOTCAM; + +namespace sol +{ + class state; +} +class Vec3; +class Rotation; + +class WayPointObject : public NamedBase +{ +public: + using IdentifierType = std::reference_wrapper; + WayPointObject(SPOTCAM& ref); + ~WayPointObject() = default; + + WayPointObject& operator=(WayPointObject const& other) = delete; + WayPointObject(WayPointObject const& other) = delete; + + static void Register(sol::table&); + + Vec3 GetPos() const; + void SetPos(Vec3 const& pos); + + Vec3 GetTarget() const; + void SetTarget(Vec3 const& target); + + std::string GetName() const; + void SetName(std::string const&); + + int GetType() const; + void SetType(int type); + + int GetSequence() const; + + Vec3 GetPathPosition(float alpha, bool loop) const; + Rotation GetPathRotation(float alpha, bool loop) const; + +private: + SPOTCAM& m_waypoint; +}; diff --git a/TombEngine/Specific/level.cpp b/TombEngine/Specific/level.cpp index b198694909..c753fd9752 100644 --- a/TombEngine/Specific/level.cpp +++ b/TombEngine/Specific/level.cpp @@ -525,7 +525,17 @@ void LoadCameras() // TODO: Read properly! if (NumberSpotcams != 0) + { ReadBytes(SpotCam, NumberSpotcams * sizeof(SPOTCAM)); + + // Register waypoints with script engine + for (int i = 0; i < NumberSpotcams; i++) + { + // Generate auto-generated name for waypoint + std::string waypointName = "waypoint_" + std::to_string(SpotCam[i].sequence) + "_" + std::to_string(SpotCam[i].camera); + g_GameScriptEntities->AddName(waypointName, SpotCam[i]); + } + } int sinkCount = ReadCount(); TENLog("Sink count: " + std::to_string(sinkCount), LogLevel::Info); From d5154ca9f1bb3aa862263ad46fdb4136b9493ca5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:15:22 +0000 Subject: [PATCH 03/16] Add WayPointObject to Visual Studio project Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEngine/TombEngine.vcxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj index b25f151112..1410fd6d4b 100644 --- a/TombEngine/TombEngine.vcxproj +++ b/TombEngine/TombEngine.vcxproj @@ -916,6 +916,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. + @@ -1367,6 +1368,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. + From 3d9637ed1caab120d5768321b673c9c8ba04f5b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:17:01 +0000 Subject: [PATCH 04/16] Fix spacing in WayPointObject registration to match code style Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp index 868d5e94f1..b6fabc29a3 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp @@ -188,7 +188,6 @@ ObjectsHandler::ObjectsHandler(sol::state* lua, sol::table& parent) : Volume::SetNameCallbacks( [this](auto && ... param) { return AddName(std::forward(param)...); }, [this](auto && ... param) { return RemoveName(std::forward(param)...); }); - WayPointObject::Register(_table_objects); WayPointObject::SetNameCallbacks( [this](auto && ... param) { return AddName(std::forward(param)...); }, From d0a9aa5f33155a73e02bb65cfc7e4fc8dff6a191 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:28:59 +0000 Subject: [PATCH 05/16] Remove target functions and implement separate waypoint path calculation Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- .../TEN/Objects/WayPoint/WayPointObject.cpp | 122 ++++++++++++++---- .../TEN/Objects/WayPoint/WayPointObject.h | 6 +- 2 files changed, 101 insertions(+), 27 deletions(-) diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp index 365b0a1752..2121f5e4ff 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp @@ -39,16 +39,6 @@ void WayPointObject::Register(sol::table& parent) // @tparam Vec3 position The new position of the waypoint. ScriptReserved_SetPosition, &WayPointObject::SetPos, - /// Get the waypoint's target position. - // @function WayPoint:GetTarget - // @treturn Vec3 Waypoint's target position. - ScriptReserved_GetTarget, &WayPointObject::GetTarget, - - /// Set the waypoint's target position. - // @function WayPoint:SetTarget - // @tparam Vec3 target The new target position. - ScriptReserved_SetTarget, &WayPointObject::SetTarget, - /// Get the waypoint's unique string identifier. // @function WayPoint:GetName // @treturn string The waypoint's name. @@ -101,18 +91,6 @@ void WayPointObject::SetPos(Vec3 const& pos) m_waypoint.z = pos.z; } -Vec3 WayPointObject::GetTarget() const -{ - return Vec3(m_waypoint.tx, m_waypoint.ty, m_waypoint.tz); -} - -void WayPointObject::SetTarget(Vec3 const& target) -{ - m_waypoint.tx = target.x; - m_waypoint.ty = target.y; - m_waypoint.tz = target.z; -} - std::string WayPointObject::GetName() const { // Generate name based on sequence and camera index @@ -144,12 +122,108 @@ int WayPointObject::GetSequence() const return m_waypoint.sequence; } +// Catmull-Rom spline interpolation for waypoint paths +Pose WayPointObject::CalculateWayPointTransform(int sequence, float alpha, bool loop) const +{ + constexpr auto BLEND_RANGE = 0.1f; + constexpr auto BLEND_START = BLEND_RANGE; + constexpr auto BLEND_END = 1.0f - BLEND_RANGE; + + alpha = std::clamp(alpha, 0.0f, 1.0f); + + if (sequence < 0 || sequence >= MAX_SPOTCAMS) + { + TENLog("Invalid waypoint sequence number provided for path calculation.", LogLevel::Warning); + return Pose::Zero; + } + + // Retrieve waypoint count in sequence + int waypointCount = CameraCnt[SpotCamRemap[sequence]]; + if (waypointCount < 2) + { + TENLog("Not enough waypoints in sequence to calculate the path.", LogLevel::Warning); + return Pose::Zero; + } + + // Find first ID for sequence + int firstSeqID = 0; + for (int i = 0; i < SpotCamRemap[sequence]; i++) + firstSeqID += CameraCnt[i]; + + // Determine number of spline points and spline position + int splinePoints = waypointCount + 2; + int splineAlpha = int(alpha * (float)USHRT_MAX); + + // Extract waypoint positions and rolls into separate vectors for interpolation + std::vector xPos, yPos, zPos, rolls; + for (int i = -1; i < (waypointCount + 1); i++) + { + int seqID = std::clamp(firstSeqID + i, firstSeqID, (firstSeqID + waypointCount) - 1); + + xPos.push_back(SpotCam[seqID].x); + yPos.push_back(SpotCam[seqID].y); + zPos.push_back(SpotCam[seqID].z); + rolls.push_back(SpotCam[seqID].roll); + } + + // Compute spline interpolation of waypoint parameters + 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 getInterpolatedRoll = [&](float t) + { + int tAlpha = int(t * (float)USHRT_MAX); + return Spline(tAlpha, rolls.data(), splinePoints); + }; + + auto position = Vector3::Zero; + short orientZ = 0; + + // If loop is enabled and alpha is at sequence start or end, blend between last and first waypoints + 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); + + position = Vector3::Lerp(getInterpolatedPoint(BLEND_END, xPos, yPos, zPos), getInterpolatedPoint(BLEND_START, xPos, yPos, zPos), blendFactor); + orientZ = Lerp(getInterpolatedRoll(BLEND_END), getInterpolatedRoll(BLEND_START), blendFactor); + } + else + { + position = getInterpolatedPoint(alpha, xPos, yPos, zPos); + orientZ = getInterpolatedRoll(alpha); + } + + // Calculate direction from current position to next position for orientation + // For waypoints, we compute the forward direction based on the path tangent + float deltaAlpha = 0.01f; // Small delta for tangent calculation + float nextAlpha = std::min(alpha + deltaAlpha, 1.0f); + auto nextPosition = getInterpolatedPoint(nextAlpha, xPos, yPos, zPos); + auto direction = nextPosition - position; + + if (direction.LengthSquared() < 0.0001f) + { + // If positions are too close, use previous point + float prevAlpha = std::max(alpha - deltaAlpha, 0.0f); + auto prevPosition = getInterpolatedPoint(prevAlpha, xPos, yPos, zPos); + direction = position - prevPosition; + } + + auto pose = Pose(position, EulerAngles(direction)); + pose.Orientation.z = orientZ; + return pose; +} + Vec3 WayPointObject::GetPathPosition(float alpha, bool loop) const { - return Vec3(GetCameraTransform(m_waypoint.sequence, alpha, loop).Position); + return Vec3(CalculateWayPointTransform(m_waypoint.sequence, alpha, loop).Position); } Rotation WayPointObject::GetPathRotation(float alpha, bool loop) const { - return Rotation(GetCameraTransform(m_waypoint.sequence, alpha, loop).Orientation); + return Rotation(CalculateWayPointTransform(m_waypoint.sequence, alpha, loop).Orientation); } diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h index b72689f0f7..fdf5bf07ba 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h @@ -27,9 +27,6 @@ class WayPointObject : public NamedBase Vec3 GetPos() const; void SetPos(Vec3 const& pos); - Vec3 GetTarget() const; - void SetTarget(Vec3 const& target); - std::string GetName() const; void SetName(std::string const&); @@ -43,4 +40,7 @@ class WayPointObject : public NamedBase private: SPOTCAM& m_waypoint; + + // Helper function to calculate waypoint path transform + Pose CalculateWayPointTransform(int sequence, float alpha, bool loop) const; }; From a913297eb6744c4e4fe56a737e0fb18c5867aea1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:35:52 +0000 Subject: [PATCH 06/16] Create separate WAYPOINT structure and implement proper waypoint loading Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEngine/Game/waypoint.cpp | 113 +++++++++++ TombEngine/Game/waypoint.h | 30 +++ TombEngine/Scripting/Include/VarMapVal.h | 4 +- .../Scripting/Internal/ReservedScriptNames.h | 7 +- .../Internal/TEN/Objects/ObjectsHandler.h | 8 +- .../TEN/Objects/WayPoint/WayPointObject.cpp | 177 +++++++----------- .../TEN/Objects/WayPoint/WayPointObject.h | 22 ++- TombEngine/Specific/level.cpp | 37 +++- TombEngine/TombEngine.vcxproj | 2 + 9 files changed, 270 insertions(+), 130 deletions(-) create mode 100644 TombEngine/Game/waypoint.cpp create mode 100644 TombEngine/Game/waypoint.h diff --git a/TombEngine/Game/waypoint.cpp b/TombEngine/Game/waypoint.cpp new file mode 100644 index 0000000000..f8ea3cf320 --- /dev/null +++ b/TombEngine/Game/waypoint.cpp @@ -0,0 +1,113 @@ +#include "framework.h" +#include "waypoint.h" +#include "Game/spotcam.h" +#include "Specific/logging.h" + +std::vector WayPoints; + +void ClearWayPoints() +{ + WayPoints.clear(); +} + +// Catmull-Rom spline interpolation for waypoint paths +Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) +{ + constexpr auto BLEND_RANGE = 0.1f; + constexpr auto BLEND_START = BLEND_RANGE; + constexpr auto BLEND_END = 1.0f - BLEND_RANGE; + + alpha = std::clamp(alpha, 0.0f, 1.0f); + + // Find all waypoints with the given name + std::vector pathWaypoints; + for (const auto& wp : WayPoints) + { + if (wp.name == name) + pathWaypoints.push_back(&wp); + } + + if (pathWaypoints.empty()) + { + TENLog("No waypoints found with name: " + name, LogLevel::Warning); + return Pose::Zero; + } + + if (pathWaypoints.size() < 2) + { + TENLog("Not enough waypoints in path to calculate transform for: " + name, LogLevel::Warning); + return Pose::Zero; + } + + // Sort waypoints by number + std::sort(pathWaypoints.begin(), pathWaypoints.end(), + [](const WAYPOINT* a, const WAYPOINT* b) { return a->number < b->number; }); + + int waypointCount = pathWaypoints.size(); + int splinePoints = waypointCount + 2; + int splineAlpha = int(alpha * (float)USHRT_MAX); + + // Extract waypoint positions and rolls into separate vectors for interpolation + std::vector xPos, yPos, zPos, rolls; + for (int i = -1; i < (waypointCount + 1); i++) + { + int idx = std::clamp(i, 0, waypointCount - 1); + const WAYPOINT* wp = pathWaypoints[idx]; + + xPos.push_back(wp->x); + yPos.push_back(wp->y); + zPos.push_back(wp->z); + rolls.push_back((int)(wp->roll * 182.0444f)); // Convert degrees to angle units + } + + // Compute spline interpolation of waypoint parameters + 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 getInterpolatedRoll = [&](float t) + { + int tAlpha = int(t * (float)USHRT_MAX); + return Spline(tAlpha, rolls.data(), splinePoints); + }; + + auto position = Vector3::Zero; + short orientZ = 0; + + // If loop is enabled and alpha is at sequence start or end, blend between last and first waypoints + 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); + + position = Vector3::Lerp(getInterpolatedPoint(BLEND_END, xPos, yPos, zPos), getInterpolatedPoint(BLEND_START, xPos, yPos, zPos), blendFactor); + orientZ = Lerp(getInterpolatedRoll(BLEND_END), getInterpolatedRoll(BLEND_START), blendFactor); + } + else + { + position = getInterpolatedPoint(alpha, xPos, yPos, zPos); + orientZ = getInterpolatedRoll(alpha); + } + + // Calculate direction from current position to next position for orientation + // For waypoints, we compute the forward direction based on the path tangent + float deltaAlpha = 0.01f; // Small delta for tangent calculation + float nextAlpha = std::min(alpha + deltaAlpha, 1.0f); + auto nextPosition = getInterpolatedPoint(nextAlpha, xPos, yPos, zPos); + auto direction = nextPosition - position; + + if (direction.LengthSquared() < 0.0001f) + { + // If positions are too close, use previous point + float prevAlpha = std::max(alpha - deltaAlpha, 0.0f); + auto prevPosition = getInterpolatedPoint(prevAlpha, xPos, yPos, zPos); + direction = position - prevPosition; + } + + auto pose = Pose(position, EulerAngles(direction)); + pose.Orientation.z = orientZ; + return pose; +} diff --git a/TombEngine/Game/waypoint.h b/TombEngine/Game/waypoint.h new file mode 100644 index 0000000000..c5d5c9ecbe --- /dev/null +++ b/TombEngine/Game/waypoint.h @@ -0,0 +1,30 @@ +#pragma once +#include +#include +#include "Math/Math.h" + +constexpr auto MAX_WAYPOINTS = 1024; + +class Pose; + +struct WAYPOINT +{ + int x; + int y; + int z; + int roomNumber; + float rotationX; + float rotationY; + float roll; + unsigned short number; + int type; + float radius1; + float radius2; + std::string name; + std::string luaName; +}; + +extern std::vector WayPoints; + +void ClearWayPoints(); +Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop); diff --git a/TombEngine/Scripting/Include/VarMapVal.h b/TombEngine/Scripting/Include/VarMapVal.h index 45e330c070..68728a49da 100644 --- a/TombEngine/Scripting/Include/VarMapVal.h +++ b/TombEngine/Scripting/Include/VarMapVal.h @@ -7,7 +7,7 @@ struct SoundSourceInfo; struct TriggerVolume; struct AI_OBJECT; struct RoomData; -struct SPOTCAM; +struct WAYPOINT; using VarMapVal = std::variant< int, @@ -18,4 +18,4 @@ using VarMapVal = std::variant< std::reference_wrapper, std::reference_wrapper, std::reference_wrapper, - std::reference_wrapper>; + std::reference_wrapper>; diff --git a/TombEngine/Scripting/Internal/ReservedScriptNames.h b/TombEngine/Scripting/Internal/ReservedScriptNames.h index 4bf5684295..f0b65ec0af 100644 --- a/TombEngine/Scripting/Internal/ReservedScriptNames.h +++ b/TombEngine/Scripting/Internal/ReservedScriptNames.h @@ -497,7 +497,12 @@ constexpr char ScriptReserved_GetPathPosition[] = "GetPathPosition"; constexpr char ScriptReserved_GetPathRotation[] = "GetPathRotation"; constexpr char ScriptReserved_GetType[] = "GetType"; constexpr char ScriptReserved_SetType[] = "SetType"; -constexpr char ScriptReserved_GetSequence[] = "GetSequence"; +constexpr char ScriptReserved_GetNumber[] = "GetNumber"; +constexpr char ScriptReserved_SetNumber[] = "SetNumber"; +constexpr char ScriptReserved_GetRadius1[] = "GetRadius1"; +constexpr char ScriptReserved_SetRadius1[] = "SetRadius1"; +constexpr char ScriptReserved_GetRadius2[] = "GetRadius2"; +constexpr char ScriptReserved_SetRadius2[] = "SetRadius2"; // Static diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h index d72fc13065..18b5a471df 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h @@ -8,7 +8,7 @@ #include "Scripting/Internal/TEN/Objects/Static/StaticObject.h" #include "Scripting/Internal/TEN/Objects/AIObject/AIObject.h" -struct SPOTCAM; +struct WAYPOINT; class ObjectsHandler : public ScriptInterfaceObjectsHandler { @@ -134,12 +134,12 @@ class ObjectsHandler : public ScriptInterfaceObjectsHandler auto waypoints = std::vector>{}; for (const auto& [key, value] : _nameMap) { - if (!std::holds_alternative>(value)) + if (!std::holds_alternative>(value)) continue; - auto waypoint = std::get>(value).get(); + auto waypoint = std::get>(value).get(); - if (waypoint.camera == type) + if (waypoint.type == type) { waypoints.push_back(GetByName(key)); } diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp index 2121f5e4ff..e79b07f960 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp @@ -1,6 +1,6 @@ #include "framework.h" #include "WayPointObject.h" -#include "Game/spotcam.h" +#include "Game/waypoint.h" #include "Scripting/Internal/ReservedScriptNames.h" #include "Scripting/Internal/ScriptAssert.h" @@ -19,7 +19,7 @@ Waypoint objects for navigation and path traversal. static auto IndexError = IndexErrorMaker(WayPointObject, ScriptReserved_WayPoint); static auto NewIndexError = NewIndexErrorMaker(WayPointObject, ScriptReserved_WayPoint); -WayPointObject::WayPointObject(SPOTCAM& ref) : m_waypoint{ref} +WayPointObject::WayPointObject(WAYPOINT& ref) : m_waypoint{ref} {}; void WayPointObject::Register(sol::table& parent) @@ -49,20 +49,45 @@ void WayPointObject::Register(sol::table& parent) // @tparam string name The waypoint's new name. ScriptReserved_SetName, &WayPointObject::SetName, - /// Get the waypoint's type/camera number. + /// Get the waypoint's type. // @function WayPoint:GetType // @treturn int The waypoint's type identifier. ScriptReserved_GetType, &WayPointObject::GetType, - /// Set the waypoint's type/camera number. + /// Set the waypoint's type. // @function WayPoint:SetType // @tparam int type The waypoint's new type identifier. ScriptReserved_SetType, &WayPointObject::SetType, - /// Get the waypoint's sequence number. - // @function WayPoint:GetSequence - // @treturn int The waypoint's sequence number. - ScriptReserved_GetSequence, &WayPointObject::GetSequence, + /// Get the waypoint's number. + // @function WayPoint:GetNumber + // @treturn int The waypoint's number. + ScriptReserved_GetNumber, &WayPointObject::GetNumber, + + /// Set the waypoint's number. + // @function WayPoint:SetNumber + // @tparam int number The waypoint's new number. + ScriptReserved_SetNumber, &WayPointObject::SetNumber, + + /// Get the waypoint's radius1. + // @function WayPoint:GetRadius1 + // @treturn float The waypoint's radius1. + ScriptReserved_GetRadius1, &WayPointObject::GetRadius1, + + /// Set the waypoint's radius1. + // @function WayPoint:SetRadius1 + // @tparam float radius The waypoint's new radius1. + ScriptReserved_SetRadius1, &WayPointObject::SetRadius1, + + /// Get the waypoint's radius2. + // @function WayPoint:GetRadius2 + // @treturn float The waypoint's radius2. + ScriptReserved_GetRadius2, &WayPointObject::GetRadius2, + + /// Set the waypoint's radius2. + // @function WayPoint:SetRadius2 + // @tparam float radius The waypoint's new radius2. + ScriptReserved_SetRadius2, &WayPointObject::SetRadius2, /// Get an interpolated position along the waypoint path. // @function WayPoint:GetPathPosition @@ -93,8 +118,7 @@ void WayPointObject::SetPos(Vec3 const& pos) std::string WayPointObject::GetName() const { - // Generate name based on sequence and camera index - return "waypoint_" + std::to_string(m_waypoint.sequence) + "_" + std::to_string(m_waypoint.camera); + return m_waypoint.name; } void WayPointObject::SetName(std::string const& id) @@ -102,128 +126,65 @@ void WayPointObject::SetName(std::string const& id) if (!ScriptAssert(!id.empty(), "Name cannot be blank. Not setting name.")) return; - // Note: SPOTCAM structure doesn't support arbitrary names - // This would require extending the level format - TENLog("Setting custom waypoint names is not supported. Waypoint names are auto-generated based on sequence and camera index.", LogLevel::Warning, LogConfig::All); + if (_callbackSetName(id, m_waypoint)) + { + // Remove old name if it exists + _callbackRemoveName(m_waypoint.name); + m_waypoint.name = id; + } + else + { + ScriptAssertF(false, "Could not add name {} - does a waypoint with this name already exist?", id); + TENLog("Name will not be set", LogLevel::Warning, LogConfig::All); + } } int WayPointObject::GetType() const { - return m_waypoint.camera; + return m_waypoint.type; } void WayPointObject::SetType(int type) { - m_waypoint.camera = (unsigned char)type; + m_waypoint.type = type; } -int WayPointObject::GetSequence() const +int WayPointObject::GetNumber() const { - return m_waypoint.sequence; + return m_waypoint.number; } -// Catmull-Rom spline interpolation for waypoint paths -Pose WayPointObject::CalculateWayPointTransform(int sequence, float alpha, bool loop) const +void WayPointObject::SetNumber(int number) { - constexpr auto BLEND_RANGE = 0.1f; - constexpr auto BLEND_START = BLEND_RANGE; - constexpr auto BLEND_END = 1.0f - BLEND_RANGE; - - alpha = std::clamp(alpha, 0.0f, 1.0f); - - if (sequence < 0 || sequence >= MAX_SPOTCAMS) - { - TENLog("Invalid waypoint sequence number provided for path calculation.", LogLevel::Warning); - return Pose::Zero; - } - - // Retrieve waypoint count in sequence - int waypointCount = CameraCnt[SpotCamRemap[sequence]]; - if (waypointCount < 2) - { - TENLog("Not enough waypoints in sequence to calculate the path.", LogLevel::Warning); - return Pose::Zero; - } - - // Find first ID for sequence - int firstSeqID = 0; - for (int i = 0; i < SpotCamRemap[sequence]; i++) - firstSeqID += CameraCnt[i]; - - // Determine number of spline points and spline position - int splinePoints = waypointCount + 2; - int splineAlpha = int(alpha * (float)USHRT_MAX); - - // Extract waypoint positions and rolls into separate vectors for interpolation - std::vector xPos, yPos, zPos, rolls; - for (int i = -1; i < (waypointCount + 1); i++) - { - int seqID = std::clamp(firstSeqID + i, firstSeqID, (firstSeqID + waypointCount) - 1); - - xPos.push_back(SpotCam[seqID].x); - yPos.push_back(SpotCam[seqID].y); - zPos.push_back(SpotCam[seqID].z); - rolls.push_back(SpotCam[seqID].roll); - } - - // Compute spline interpolation of waypoint parameters - 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 getInterpolatedRoll = [&](float t) - { - int tAlpha = int(t * (float)USHRT_MAX); - return Spline(tAlpha, rolls.data(), splinePoints); - }; - - auto position = Vector3::Zero; - short orientZ = 0; + m_waypoint.number = (unsigned short)number; +} - // If loop is enabled and alpha is at sequence start or end, blend between last and first waypoints - 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); +float WayPointObject::GetRadius1() const +{ + return m_waypoint.radius1; +} - position = Vector3::Lerp(getInterpolatedPoint(BLEND_END, xPos, yPos, zPos), getInterpolatedPoint(BLEND_START, xPos, yPos, zPos), blendFactor); - orientZ = Lerp(getInterpolatedRoll(BLEND_END), getInterpolatedRoll(BLEND_START), blendFactor); - } - else - { - position = getInterpolatedPoint(alpha, xPos, yPos, zPos); - orientZ = getInterpolatedRoll(alpha); - } +void WayPointObject::SetRadius1(float radius) +{ + m_waypoint.radius1 = radius; +} - // Calculate direction from current position to next position for orientation - // For waypoints, we compute the forward direction based on the path tangent - float deltaAlpha = 0.01f; // Small delta for tangent calculation - float nextAlpha = std::min(alpha + deltaAlpha, 1.0f); - auto nextPosition = getInterpolatedPoint(nextAlpha, xPos, yPos, zPos); - auto direction = nextPosition - position; - - if (direction.LengthSquared() < 0.0001f) - { - // If positions are too close, use previous point - float prevAlpha = std::max(alpha - deltaAlpha, 0.0f); - auto prevPosition = getInterpolatedPoint(prevAlpha, xPos, yPos, zPos); - direction = position - prevPosition; - } +float WayPointObject::GetRadius2() const +{ + return m_waypoint.radius2; +} - auto pose = Pose(position, EulerAngles(direction)); - pose.Orientation.z = orientZ; - return pose; +void WayPointObject::SetRadius2(float radius) +{ + m_waypoint.radius2 = radius; } Vec3 WayPointObject::GetPathPosition(float alpha, bool loop) const { - return Vec3(CalculateWayPointTransform(m_waypoint.sequence, alpha, loop).Position); + return Vec3(CalculateWayPointTransform(m_waypoint.name, alpha, loop).Position); } Rotation WayPointObject::GetPathRotation(float alpha, bool loop) const { - return Rotation(CalculateWayPointTransform(m_waypoint.sequence, alpha, loop).Orientation); + return Rotation(CalculateWayPointTransform(m_waypoint.name, alpha, loop).Orientation); } diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h index fdf5bf07ba..0a79c8554d 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h @@ -3,7 +3,7 @@ #include "Scripting/Internal/TEN/Objects/NamedBase.h" #include "Math/Math.h" -struct SPOTCAM; +struct WAYPOINT; namespace sol { @@ -12,11 +12,11 @@ namespace sol class Vec3; class Rotation; -class WayPointObject : public NamedBase +class WayPointObject : public NamedBase { public: - using IdentifierType = std::reference_wrapper; - WayPointObject(SPOTCAM& ref); + using IdentifierType = std::reference_wrapper; + WayPointObject(WAYPOINT& ref); ~WayPointObject() = default; WayPointObject& operator=(WayPointObject const& other) = delete; @@ -33,14 +33,18 @@ class WayPointObject : public NamedBase int GetType() const; void SetType(int type); - int GetSequence() const; + int GetNumber() const; + void SetNumber(int number); + + float GetRadius1() const; + void SetRadius1(float radius); + + float GetRadius2() const; + void SetRadius2(float radius); Vec3 GetPathPosition(float alpha, bool loop) const; Rotation GetPathRotation(float alpha, bool loop) const; private: - SPOTCAM& m_waypoint; - - // Helper function to calculate waypoint path transform - Pose CalculateWayPointTransform(int sequence, float alpha, bool loop) const; + WAYPOINT& m_waypoint; }; diff --git a/TombEngine/Specific/level.cpp b/TombEngine/Specific/level.cpp index c753fd9752..3b724d0bbf 100644 --- a/TombEngine/Specific/level.cpp +++ b/TombEngine/Specific/level.cpp @@ -19,6 +19,7 @@ #include "Game/Setup.h" #include "Game/Sink.h" #include "Game/spotcam.h" +#include "Game/waypoint.h" #include "Objects/Generic/Doors/generic_doors.h" #include "Physics/Physics.h" #include "Renderer/Renderer.h" @@ -525,15 +526,39 @@ void LoadCameras() // TODO: Read properly! if (NumberSpotcams != 0) - { ReadBytes(SpotCam, NumberSpotcams * sizeof(SPOTCAM)); + + // Load waypoints + int waypointCount = ReadCount(); + TENLog("Waypoint count: " + std::to_string(waypointCount), LogLevel::Info); + + WayPoints.clear(); + WayPoints.reserve(waypointCount); + + for (int i = 0; i < waypointCount; i++) + { + WAYPOINT waypoint; + waypoint.x = ReadInt32(); + waypoint.y = ReadInt32(); + waypoint.z = ReadInt32(); + waypoint.roomNumber = ReadInt32(); + waypoint.rotationX = ReadFloat(); + waypoint.rotationY = ReadFloat(); + waypoint.roll = ReadFloat(); + waypoint.number = ReadUInt16(); + waypoint.type = ReadInt32(); + waypoint.radius1 = ReadFloat(); + waypoint.radius2 = ReadFloat(); + waypoint.name = ReadString(); + waypoint.luaName = ReadString(); + + WayPoints.push_back(waypoint); - // Register waypoints with script engine - for (int i = 0; i < NumberSpotcams; i++) + // Register with script engine using LuaName if available, otherwise use Name + std::string scriptName = waypoint.luaName.empty() ? waypoint.name : waypoint.luaName; + if (!scriptName.empty()) { - // Generate auto-generated name for waypoint - std::string waypointName = "waypoint_" + std::to_string(SpotCam[i].sequence) + "_" + std::to_string(SpotCam[i].camera); - g_GameScriptEntities->AddName(waypointName, SpotCam[i]); + g_GameScriptEntities->AddName(scriptName, WayPoints.back()); } } diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj index 1410fd6d4b..9d477a0b37 100644 --- a/TombEngine/TombEngine.vcxproj +++ b/TombEngine/TombEngine.vcxproj @@ -495,6 +495,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. + @@ -1059,6 +1060,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. + From f2186a9d386aafa9ab9b07bbf537755cba88fa91 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:36:50 +0000 Subject: [PATCH 07/16] Add error handling and named constant per code review Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEngine/Game/waypoint.cpp | 5 ++++- TombEngine/Specific/level.cpp | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/TombEngine/Game/waypoint.cpp b/TombEngine/Game/waypoint.cpp index f8ea3cf320..e3ecd4155b 100644 --- a/TombEngine/Game/waypoint.cpp +++ b/TombEngine/Game/waypoint.cpp @@ -3,6 +3,9 @@ #include "Game/spotcam.h" #include "Specific/logging.h" +// Conversion constant: degrees to angle units (used in roll calculations) +constexpr float DEGREES_TO_ANGLE_UNITS = 182.0444f; + std::vector WayPoints; void ClearWayPoints() @@ -57,7 +60,7 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) xPos.push_back(wp->x); yPos.push_back(wp->y); zPos.push_back(wp->z); - rolls.push_back((int)(wp->roll * 182.0444f)); // Convert degrees to angle units + rolls.push_back((int)(wp->roll * DEGREES_TO_ANGLE_UNITS)); } // Compute spline interpolation of waypoint parameters diff --git a/TombEngine/Specific/level.cpp b/TombEngine/Specific/level.cpp index 3b724d0bbf..0b8c7c7980 100644 --- a/TombEngine/Specific/level.cpp +++ b/TombEngine/Specific/level.cpp @@ -532,6 +532,12 @@ void LoadCameras() int waypointCount = ReadCount(); TENLog("Waypoint count: " + std::to_string(waypointCount), LogLevel::Info); + if (waypointCount < 0 || waypointCount > MAX_WAYPOINTS) + { + TENLog("Invalid waypoint count: " + std::to_string(waypointCount), LogLevel::Error); + waypointCount = 0; + } + WayPoints.clear(); WayPoints.reserve(waypointCount); From 9ed4713bbf519f848316b591980889841ddad301 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:44:46 +0000 Subject: [PATCH 08/16] Add WayPointType enum and fix path calculation for Linear/Bezier types Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEngine/Game/waypoint.cpp | 220 +++++++++++++----- TombEngine/Game/waypoint.h | 11 + .../Scripting/Internal/ReservedScriptNames.h | 1 + .../Internal/TEN/Objects/ObjectsHandler.cpp | 2 + .../TEN/Objects/WayPoint/WayPointTypes.h | 44 ++++ TombEngine/TombEngine.vcxproj | 1 + 6 files changed, 218 insertions(+), 61 deletions(-) create mode 100644 TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointTypes.h diff --git a/TombEngine/Game/waypoint.cpp b/TombEngine/Game/waypoint.cpp index e3ecd4155b..84990bc2da 100644 --- a/TombEngine/Game/waypoint.cpp +++ b/TombEngine/Game/waypoint.cpp @@ -13,7 +13,17 @@ void ClearWayPoints() WayPoints.clear(); } -// Catmull-Rom spline interpolation for waypoint paths +// Helper function to check if a waypoint type is singular (non-path) +static bool IsSingularType(WayPointType type) +{ + return type == WayPointType::Point || + type == WayPointType::Circle || + type == WayPointType::Ellipse || + type == WayPointType::Square || + type == WayPointType::Rectangle; +} + +// Path interpolation for waypoint paths (Linear and Bezier) Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) { constexpr auto BLEND_RANGE = 0.1f; @@ -36,6 +46,24 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) return Pose::Zero; } + // Check if this is a singular type waypoint (Point, Circle, etc) + WayPointType waypointType = static_cast(pathWaypoints[0]->type); + if (IsSingularType(waypointType)) + { + // For singular types, just return the position and rotation of the single point + const WAYPOINT* wp = pathWaypoints[0]; + Vector3 position(wp->x, wp->y, wp->z); + + // Create rotation from rotationX, rotationY, and roll + EulerAngles orientation; + orientation.x = wp->rotationX * (float)RADIAN; + orientation.y = wp->rotationY * (float)RADIAN; + orientation.z = wp->roll * (float)RADIAN; + + return Pose(position, orientation); + } + + // For path types (Linear, Bezier), we need at least 2 points if (pathWaypoints.size() < 2) { TENLog("Not enough waypoints in path to calculate transform for: " + name, LogLevel::Warning); @@ -47,70 +75,140 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) [](const WAYPOINT* a, const WAYPOINT* b) { return a->number < b->number; }); int waypointCount = pathWaypoints.size(); - int splinePoints = waypointCount + 2; - int splineAlpha = int(alpha * (float)USHRT_MAX); - - // Extract waypoint positions and rolls into separate vectors for interpolation - std::vector xPos, yPos, zPos, rolls; - for (int i = -1; i < (waypointCount + 1); i++) - { - int idx = std::clamp(i, 0, waypointCount - 1); - const WAYPOINT* wp = pathWaypoints[idx]; - - xPos.push_back(wp->x); - yPos.push_back(wp->y); - zPos.push_back(wp->z); - rolls.push_back((int)(wp->roll * DEGREES_TO_ANGLE_UNITS)); - } - - // Compute spline interpolation of waypoint parameters - 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 getInterpolatedRoll = [&](float t) - { - int tAlpha = int(t * (float)USHRT_MAX); - return Spline(tAlpha, rolls.data(), splinePoints); - }; - - auto position = Vector3::Zero; - short orientZ = 0; - - // If loop is enabled and alpha is at sequence start or end, blend between last and first waypoints - 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); - - position = Vector3::Lerp(getInterpolatedPoint(BLEND_END, xPos, yPos, zPos), getInterpolatedPoint(BLEND_START, xPos, yPos, zPos), blendFactor); - orientZ = Lerp(getInterpolatedRoll(BLEND_END), getInterpolatedRoll(BLEND_START), blendFactor); - } - else + + // Handle Linear path type + if (waypointType == WayPointType::Linear) { - position = getInterpolatedPoint(alpha, xPos, yPos, zPos); - orientZ = getInterpolatedRoll(alpha); + // Linear interpolation between waypoints + float segmentLength = 1.0f / (waypointCount - 1); + int segmentIndex = (int)(alpha / segmentLength); + + // Handle loop blending + 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); + + // Blend between last and first waypoints + const WAYPOINT* wp1 = pathWaypoints[waypointCount - 1]; + const WAYPOINT* wp2 = pathWaypoints[0]; + + Vector3 pos1(wp1->x, wp1->y, wp1->z); + Vector3 pos2(wp2->x, wp2->y, wp2->z); + Vector3 position = Vector3::Lerp(pos1, pos2, blendFactor); + + // Interpolate rotations + EulerAngles orient1(wp1->rotationX * (float)RADIAN, wp1->rotationY * (float)RADIAN, wp1->roll * (float)RADIAN); + EulerAngles orient2(wp2->rotationX * (float)RADIAN, wp2->rotationY * (float)RADIAN, wp2->roll * (float)RADIAN); + + EulerAngles orientation; + orientation.x = Lerp(orient1.x, orient2.x, blendFactor); + orientation.y = Lerp(orient1.y, orient2.y, blendFactor); + orientation.z = Lerp(orient1.z, orient2.z, blendFactor); + + return Pose(position, orientation); + } + + // Clamp segment index + segmentIndex = std::clamp(segmentIndex, 0, waypointCount - 2); + + // Calculate local alpha within the segment + float localAlpha = (alpha - (segmentIndex * segmentLength)) / segmentLength; + localAlpha = std::clamp(localAlpha, 0.0f, 1.0f); + + const WAYPOINT* wp1 = pathWaypoints[segmentIndex]; + const WAYPOINT* wp2 = pathWaypoints[segmentIndex + 1]; + + // Linear interpolation of position + Vector3 pos1(wp1->x, wp1->y, wp1->z); + Vector3 pos2(wp2->x, wp2->y, wp2->z); + Vector3 position = Vector3::Lerp(pos1, pos2, localAlpha); + + // Linear interpolation of rotation + EulerAngles orient1(wp1->rotationX * (float)RADIAN, wp1->rotationY * (float)RADIAN, wp1->roll * (float)RADIAN); + EulerAngles orient2(wp2->rotationX * (float)RADIAN, wp2->rotationY * (float)RADIAN, wp2->roll * (float)RADIAN); + + EulerAngles orientation; + orientation.x = Lerp(orient1.x, orient2.x, localAlpha); + orientation.y = Lerp(orient1.y, orient2.y, localAlpha); + orientation.z = Lerp(orient1.z, orient2.z, localAlpha); + + return Pose(position, orientation); } - - // Calculate direction from current position to next position for orientation - // For waypoints, we compute the forward direction based on the path tangent - float deltaAlpha = 0.01f; // Small delta for tangent calculation - float nextAlpha = std::min(alpha + deltaAlpha, 1.0f); - auto nextPosition = getInterpolatedPoint(nextAlpha, xPos, yPos, zPos); - auto direction = nextPosition - position; - if (direction.LengthSquared() < 0.0001f) + // Handle Bezier path type (using Catmull-Rom spline for smooth curves) + if (waypointType == WayPointType::Bezier) { - // If positions are too close, use previous point - float prevAlpha = std::max(alpha - deltaAlpha, 0.0f); - auto prevPosition = getInterpolatedPoint(prevAlpha, xPos, yPos, zPos); - direction = position - prevPosition; + int splinePoints = waypointCount + 2; + + // Extract waypoint positions and rotations for interpolation + std::vector xPos, yPos, zPos; + std::vector rotX, rotY, rotZ; + + for (int i = -1; i < (waypointCount + 1); i++) + { + int idx = std::clamp(i, 0, waypointCount - 1); + const WAYPOINT* wp = pathWaypoints[idx]; + + xPos.push_back(wp->x); + yPos.push_back(wp->y); + zPos.push_back(wp->z); + rotX.push_back(wp->rotationX); + rotY.push_back(wp->rotationY); + rotZ.push_back(wp->roll); + } + + // Compute spline interpolation of waypoint parameters + 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 getInterpolatedRotation = [&](float t, std::vector& rot) + { + // Simple linear interpolation for rotations in Bezier mode + float pos = t * (waypointCount - 1); + int idx = (int)pos; + idx = std::clamp(idx, 0, waypointCount - 2); + float localT = pos - idx; + return Lerp(rot[idx + 1], rot[idx + 2], localT); // +1 offset due to padding + }; + + auto position = Vector3::Zero; + EulerAngles orientation; + + // If loop is enabled and alpha is at sequence start or end, blend between last and first waypoints + 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); + + position = Vector3::Lerp( + getInterpolatedPoint(BLEND_END, xPos, yPos, zPos), + getInterpolatedPoint(BLEND_START, xPos, yPos, zPos), + blendFactor); + + orientation.x = Lerp(getInterpolatedRotation(BLEND_END, rotX), getInterpolatedRotation(BLEND_START, rotX), blendFactor) * (float)RADIAN; + orientation.y = Lerp(getInterpolatedRotation(BLEND_END, rotY), getInterpolatedRotation(BLEND_START, rotY), blendFactor) * (float)RADIAN; + orientation.z = Lerp(getInterpolatedRotation(BLEND_END, rotZ), getInterpolatedRotation(BLEND_START, rotZ), blendFactor) * (float)RADIAN; + } + else + { + position = getInterpolatedPoint(alpha, xPos, yPos, zPos); + orientation.x = getInterpolatedRotation(alpha, rotX) * (float)RADIAN; + orientation.y = getInterpolatedRotation(alpha, rotY) * (float)RADIAN; + orientation.z = getInterpolatedRotation(alpha, rotZ) * (float)RADIAN; + } + + return Pose(position, orientation); } - auto pose = Pose(position, EulerAngles(direction)); - pose.Orientation.z = orientZ; - return pose; + // Default fallback + TENLog("Unknown waypoint type for path: " + name, LogLevel::Warning); + return Pose::Zero; } diff --git a/TombEngine/Game/waypoint.h b/TombEngine/Game/waypoint.h index c5d5c9ecbe..f0648f7613 100644 --- a/TombEngine/Game/waypoint.h +++ b/TombEngine/Game/waypoint.h @@ -7,6 +7,17 @@ constexpr auto MAX_WAYPOINTS = 1024; class Pose; +enum class WayPointType +{ + Point = 0, // Single point, no radius. Only can have rotation + Circle = 1, // Single point with radius1. Can have rotation, can be rotated on all 3 axes + Ellipse = 2, // Single point with two radii. Can have rotation, can be rotated on all 3 axes + Square = 3, // Single point with radius (rendered as square). Can be rotated on all 3 axes + Rectangle = 4, // Single point with two radii (rendered as rectangle). Can be rotated on all 3 axes + Linear = 5, // Multi-point linear path, each point on the path can have a rotation + Bezier = 6 // Multi-point bezier path, each point on the path can have a rotation +}; + struct WAYPOINT { int x; diff --git a/TombEngine/Scripting/Internal/ReservedScriptNames.h b/TombEngine/Scripting/Internal/ReservedScriptNames.h index f0b65ec0af..05e12d928b 100644 --- a/TombEngine/Scripting/Internal/ReservedScriptNames.h +++ b/TombEngine/Scripting/Internal/ReservedScriptNames.h @@ -491,6 +491,7 @@ constexpr char ScriptReserved_SinkSetStrength[] = "SetStrength"; // WayPoint constexpr char ScriptReserved_WayPoint[] = "WayPoint"; +constexpr char ScriptReserved_WayPointType[] = "WayPointType"; constexpr char ScriptReserved_GetWayPointByName[] = "GetWayPointByName"; constexpr char ScriptReserved_GetWayPointsByType[] = "GetWayPointsByType"; constexpr char ScriptReserved_GetPathPosition[] = "GetPathPosition"; diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp index b6fabc29a3..87570ae120 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp @@ -20,6 +20,7 @@ #include "Scripting/Internal/TEN/Objects/SoundSource/SoundSourceObject.h" #include "Scripting/Internal/TEN/Objects/Volume/VolumeObject.h" #include "Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h" +#include "Scripting/Internal/TEN/Objects/WayPoint/WayPointTypes.h" using namespace TEN::Scripting::Objects; @@ -200,6 +201,7 @@ ObjectsHandler::ObjectsHandler(sol::state* lua, sol::table& parent) : _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_AmmoType, AMMO_TYPES); _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_HandStatus, HAND_STATUSES); _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_MoveableStatus, MOVEABLE_STATUSES); + _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_WayPointType, WAYPOINT_TYPES); } void ObjectsHandler::TestCollidingObjects() diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointTypes.h b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointTypes.h new file mode 100644 index 0000000000..c1e3efb02e --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointTypes.h @@ -0,0 +1,44 @@ +#pragma once + +#include "Game/waypoint.h" +#include +#include + +namespace TEN::Scripting +{ + /// Constants for waypoint types. + // To be used with @{Objects.WayPoint.GetType} and @{Objects.WayPoint.SetType} functions. + // @enum Objects.WayPointType + // @pragma nostrip + + static const auto WAYPOINT_TYPES = std::unordered_map + { + /// Single point, no radius. Only can have rotation. + // @mem POINT + { "POINT", WayPointType::Point }, + + /// Single point with radius1. Can have rotation, can be rotated on all 3 axes. + // @mem CIRCLE + { "CIRCLE", WayPointType::Circle }, + + /// Single point with two radii. Can have rotation, can be rotated on all 3 axes. + // @mem ELLIPSE + { "ELLIPSE", WayPointType::Ellipse }, + + /// Single point with radius (rendered as square). Can be rotated on all 3 axes. + // @mem SQUARE + { "SQUARE", WayPointType::Square }, + + /// Single point with two radii (rendered as rectangle). Can be rotated on all 3 axes. + // @mem RECTANGLE + { "RECTANGLE", WayPointType::Rectangle }, + + /// Multi-point linear path, each point on the path can have a rotation. + // @mem LINEAR + { "LINEAR", WayPointType::Linear }, + + /// Multi-point bezier path, each point on the path can have a rotation. + // @mem BEZIER + { "BEZIER", WayPointType::Bezier } + }; +} diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj index 9d477a0b37..cc620b3ef7 100644 --- a/TombEngine/TombEngine.vcxproj +++ b/TombEngine/TombEngine.vcxproj @@ -918,6 +918,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. + From 0dd2f27a967a666f8c05c19542e587fcbd01afdf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:46:41 +0000 Subject: [PATCH 09/16] Add WayPoint Lua class with type enum and proper path calculation for navigation Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEngine/Game/waypoint.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/TombEngine/Game/waypoint.cpp b/TombEngine/Game/waypoint.cpp index 84990bc2da..3b38a061b4 100644 --- a/TombEngine/Game/waypoint.cpp +++ b/TombEngine/Game/waypoint.cpp @@ -88,7 +88,7 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) { float blendFactor = (alpha < BLEND_START) ? (0.5f + ((alpha / BLEND_RANGE) * 0.5f)) : - (((alpha - BLEND_END) / BLEND_START) * 0.5f); + (((alpha - BLEND_END) / BLEND_RANGE) * 0.5f); // Blend between last and first waypoints const WAYPOINT* wp1 = pathWaypoints[waypointCount - 1]; @@ -170,12 +170,14 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) auto getInterpolatedRotation = [&](float t, std::vector& rot) { - // Simple linear interpolation for rotations in Bezier mode + // Linear interpolation for rotations in Bezier mode + // Note: rot vector has padding (first and last elements duplicated for spline continuity) float pos = t * (waypointCount - 1); int idx = (int)pos; idx = std::clamp(idx, 0, waypointCount - 2); float localT = pos - idx; - return Lerp(rot[idx + 1], rot[idx + 2], localT); // +1 offset due to padding + // Index offset by 1 because vector has padding at start + return Lerp(rot[idx + 1], rot[idx + 2], localT); }; auto position = Vector3::Zero; @@ -186,7 +188,7 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) { float blendFactor = (alpha < BLEND_START) ? (0.5f + ((alpha / BLEND_RANGE) * 0.5f)) : - (((alpha - BLEND_END) / BLEND_START) * 0.5f); + (((alpha - BLEND_END) / BLEND_RANGE) * 0.5f); position = Vector3::Lerp( getInterpolatedPoint(BLEND_END, xPos, yPos, zPos), From 8ceb3ed63dd5e29709da00d7f44e2d4aeb2a09a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:53:11 +0000 Subject: [PATCH 10/16] Calculate positions on shape perimeter for singular waypoint types Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEngine/Game/waypoint.cpp | 112 +++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/TombEngine/Game/waypoint.cpp b/TombEngine/Game/waypoint.cpp index 3b38a061b4..785df7d0ba 100644 --- a/TombEngine/Game/waypoint.cpp +++ b/TombEngine/Game/waypoint.cpp @@ -50,17 +50,121 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) WayPointType waypointType = static_cast(pathWaypoints[0]->type); if (IsSingularType(waypointType)) { - // For singular types, just return the position and rotation of the single point const WAYPOINT* wp = pathWaypoints[0]; - Vector3 position(wp->x, wp->y, wp->z); + Vector3 centerPosition(wp->x, wp->y, wp->z); - // Create rotation from rotationX, rotationY, and roll + // Create rotation matrix from waypoint's rotations + // Rotations are applied in order: X, Y, Z (Roll) + Matrix rotationMatrix = Matrix::CreateRotationX(wp->rotationX * (float)RADIAN) * + Matrix::CreateRotationY(wp->rotationY * (float)RADIAN) * + Matrix::CreateRotationZ(wp->roll * (float)RADIAN); + + Vector3 localPosition = Vector3::Zero; + + // Calculate position on the shape perimeter based on type and alpha + // Starting at 3 o'clock (local x = radius, z = 0) and rotating clockwise + switch (waypointType) + { + case WayPointType::Point: + // Point has no shape, return center position + localPosition = Vector3::Zero; + break; + + case WayPointType::Circle: + { + // Circle with radius1 + // Alpha 0.0 = 3 o'clock, rotating clockwise + float angle = alpha * 2.0f * (float)PI; + localPosition.x = std::cos(angle) * wp->radius1; + localPosition.y = 0.0f; + localPosition.z = std::sin(angle) * wp->radius1; + break; + } + + case WayPointType::Ellipse: + { + // Ellipse with radius1 (x-axis) and radius2 (z-axis) + float angle = alpha * 2.0f * (float)PI; + localPosition.x = std::cos(angle) * wp->radius1; + localPosition.y = 0.0f; + localPosition.z = std::sin(angle) * wp->radius2; + break; + } + + case WayPointType::Square: + { + // Square with radius1 (half-width/height) + // Divide perimeter into 4 equal segments + float r = wp->radius1; + float segment = alpha * 4.0f; + int side = (int)segment; + float t = segment - side; + + switch (side) + { + case 0: // Right side (3 to 6 o'clock) + localPosition = Vector3(r, 0.0f, Lerp(-r, r, t)); + break; + case 1: // Top side (6 to 9 o'clock) + localPosition = Vector3(Lerp(r, -r, t), 0.0f, r); + break; + case 2: // Left side (9 to 12 o'clock) + localPosition = Vector3(-r, 0.0f, Lerp(r, -r, t)); + break; + default: // Bottom side (12 to 3 o'clock) + localPosition = Vector3(Lerp(-r, r, t), 0.0f, -r); + break; + } + break; + } + + case WayPointType::Rectangle: + { + // Rectangle with radius1 (half-width x) and radius2 (half-height z) + float r1 = wp->radius1; + float r2 = wp->radius2; + + // Calculate perimeter segments weighted by side length + float perimeter = 2.0f * (r1 + r2); + float distance = alpha * perimeter; + + if (distance < r2) + { + // Right side (3 to 6 o'clock) + localPosition = Vector3(r1, 0.0f, Lerp(-r2, r2, distance / r2)); + } + else if (distance < r2 + r1) + { + // Top side (6 to 9 o'clock) + float t = (distance - r2) / r1; + localPosition = Vector3(Lerp(r1, -r1, t), 0.0f, r2); + } + else if (distance < 2.0f * r2 + r1) + { + // Left side (9 to 12 o'clock) + float t = (distance - r2 - r1) / r2; + localPosition = Vector3(-r1, 0.0f, Lerp(r2, -r2, t)); + } + else + { + // Bottom side (12 to 3 o'clock) + float t = (distance - 2.0f * r2 - r1) / r1; + localPosition = Vector3(Lerp(-r1, r1, t), 0.0f, -r2); + } + break; + } + } + + // Transform local position by rotation matrix and add to center + Vector3 worldPosition = Vector3::Transform(localPosition, rotationMatrix) + centerPosition; + + // Create orientation from waypoint's rotations EulerAngles orientation; orientation.x = wp->rotationX * (float)RADIAN; orientation.y = wp->rotationY * (float)RADIAN; orientation.z = wp->roll * (float)RADIAN; - return Pose(position, orientation); + return Pose(worldPosition, orientation); } // For path types (Linear, Bezier), we need at least 2 points From 0bc8bee3774a1aa50bfff8c0259798be4007e2c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 03:57:54 +0000 Subject: [PATCH 11/16] Fix alpha rotation to start at 12 o'clock for all shape types Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEngine/Game/waypoint.cpp | 55 ++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/TombEngine/Game/waypoint.cpp b/TombEngine/Game/waypoint.cpp index 785df7d0ba..f47a45f4d1 100644 --- a/TombEngine/Game/waypoint.cpp +++ b/TombEngine/Game/waypoint.cpp @@ -73,8 +73,9 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) case WayPointType::Circle: { // Circle with radius1 - // Alpha 0.0 = 3 o'clock, rotating clockwise - float angle = alpha * 2.0f * (float)PI; + // Alpha 0.0 = 12 o'clock, rotating clockwise + // Offset angle by -90 degrees to start at top + float angle = (alpha * 2.0f * (float)PI) - ((float)PI / 2.0f); localPosition.x = std::cos(angle) * wp->radius1; localPosition.y = 0.0f; localPosition.z = std::sin(angle) * wp->radius1; @@ -84,7 +85,9 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) case WayPointType::Ellipse: { // Ellipse with radius1 (x-axis) and radius2 (z-axis) - float angle = alpha * 2.0f * (float)PI; + // Alpha 0.0 = 12 o'clock, rotating clockwise + // Offset angle by -90 degrees to start at top + float angle = (alpha * 2.0f * (float)PI) - ((float)PI / 2.0f); localPosition.x = std::cos(angle) * wp->radius1; localPosition.y = 0.0f; localPosition.z = std::sin(angle) * wp->radius2; @@ -94,7 +97,8 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) case WayPointType::Square: { // Square with radius1 (half-width/height) - // Divide perimeter into 4 equal segments + // Alpha 0.0 = 12 o'clock (top center), rotating clockwise + // Divide perimeter into 4 equal segments (0.25 per side) float r = wp->radius1; float segment = alpha * 4.0f; int side = (int)segment; @@ -102,18 +106,18 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) switch (side) { - case 0: // Right side (3 to 6 o'clock) + case 0: // Top edge: 12 to 3 o'clock (moving left to right along top) + localPosition = Vector3(Lerp(-r, r, t), 0.0f, -r); + break; + case 1: // Right edge: 3 to 6 o'clock (moving top to bottom along right) localPosition = Vector3(r, 0.0f, Lerp(-r, r, t)); break; - case 1: // Top side (6 to 9 o'clock) + case 2: // Bottom edge: 6 to 9 o'clock (moving right to left along bottom) localPosition = Vector3(Lerp(r, -r, t), 0.0f, r); break; - case 2: // Left side (9 to 12 o'clock) + default: // Left edge: 9 to 12 o'clock (moving bottom to top along left) localPosition = Vector3(-r, 0.0f, Lerp(r, -r, t)); break; - default: // Bottom side (12 to 3 o'clock) - localPosition = Vector3(Lerp(-r, r, t), 0.0f, -r); - break; } break; } @@ -121,35 +125,38 @@ Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop) case WayPointType::Rectangle: { // Rectangle with radius1 (half-width x) and radius2 (half-height z) + // Alpha 0.0 = 12 o'clock (top center), rotating clockwise float r1 = wp->radius1; float r2 = wp->radius2; // Calculate perimeter segments weighted by side length + // Perimeter order: top edge (2*r1), right edge (2*r2), bottom edge (2*r1), left edge (2*r2) float perimeter = 2.0f * (r1 + r2); float distance = alpha * perimeter; - if (distance < r2) + if (distance < 2.0f * r1) { - // Right side (3 to 6 o'clock) - localPosition = Vector3(r1, 0.0f, Lerp(-r2, r2, distance / r2)); + // Top edge: 12 to 3 o'clock (moving left to right along top) + float t = distance / (2.0f * r1); + localPosition = Vector3(Lerp(-r1, r1, t), 0.0f, -r2); } - else if (distance < r2 + r1) + else if (distance < 2.0f * r1 + 2.0f * r2) { - // Top side (6 to 9 o'clock) - float t = (distance - r2) / r1; - localPosition = Vector3(Lerp(r1, -r1, t), 0.0f, r2); + // Right edge: 3 to 6 o'clock (moving top to bottom along right) + float t = (distance - 2.0f * r1) / (2.0f * r2); + localPosition = Vector3(r1, 0.0f, Lerp(-r2, r2, t)); } - else if (distance < 2.0f * r2 + r1) + else if (distance < 4.0f * r1 + 2.0f * r2) { - // Left side (9 to 12 o'clock) - float t = (distance - r2 - r1) / r2; - localPosition = Vector3(-r1, 0.0f, Lerp(r2, -r2, t)); + // Bottom edge: 6 to 9 o'clock (moving right to left along bottom) + float t = (distance - 2.0f * r1 - 2.0f * r2) / (2.0f * r1); + localPosition = Vector3(Lerp(r1, -r1, t), 0.0f, r2); } else { - // Bottom side (12 to 3 o'clock) - float t = (distance - 2.0f * r2 - r1) / r1; - localPosition = Vector3(Lerp(-r1, r1, t), 0.0f, -r2); + // Left edge: 9 to 12 o'clock (moving bottom to top along left) + float t = (distance - 4.0f * r1 - 2.0f * r2) / (2.0f * r2); + localPosition = Vector3(-r1, 0.0f, Lerp(r2, -r2, t)); } break; } From 4d967cf69a5dfa66bc0f4461c4fa1b7c49798540 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 04:05:02 +0000 Subject: [PATCH 12/16] Add Preview method to WayPoint Lua class for debug visualization Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- .../Scripting/Internal/ReservedScriptNames.h | 1 + .../TEN/Objects/WayPoint/WayPointObject.cpp | 153 +++++++++++++++++- .../TEN/Objects/WayPoint/WayPointObject.h | 2 + 3 files changed, 155 insertions(+), 1 deletion(-) diff --git a/TombEngine/Scripting/Internal/ReservedScriptNames.h b/TombEngine/Scripting/Internal/ReservedScriptNames.h index 05e12d928b..9a8f0ab8c0 100644 --- a/TombEngine/Scripting/Internal/ReservedScriptNames.h +++ b/TombEngine/Scripting/Internal/ReservedScriptNames.h @@ -504,6 +504,7 @@ constexpr char ScriptReserved_GetRadius1[] = "GetRadius1"; constexpr char ScriptReserved_SetRadius1[] = "SetRadius1"; constexpr char ScriptReserved_GetRadius2[] = "GetRadius2"; constexpr char ScriptReserved_SetRadius2[] = "SetRadius2"; +constexpr char ScriptReserved_Preview[] = "Preview"; // Static diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp index e79b07f960..a38fefdce3 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp @@ -1,6 +1,7 @@ #include "framework.h" #include "WayPointObject.h" #include "Game/waypoint.h" +#include "Game/debug/debug.h" #include "Scripting/Internal/ReservedScriptNames.h" #include "Scripting/Internal/ScriptAssert.h" @@ -9,6 +10,8 @@ #include "Scripting/Internal/TEN/Types/Rotation/Rotation.h" #include "Specific/level.h" +using namespace TEN::Debug; + /*** Waypoint objects for navigation and path traversal. @@ -101,7 +104,12 @@ void WayPointObject::Register(sol::table& parent) // @tparam float alpha Progress along the path (0.0 to 1.0). // @tparam bool loop Whether to loop the path continuously. // @treturn Rotation Interpolated rotation at the given alpha. - ScriptReserved_GetPathRotation, &WayPointObject::GetPathRotation); + ScriptReserved_GetPathRotation, &WayPointObject::GetPathRotation, + + /// Preview/visualize the waypoint for debugging purposes. + // @function WayPoint:Preview + // @tparam[opt] Color color Optional color for the preview (defaults to orange). + ScriptReserved_Preview, &WayPointObject::Preview); } Vec3 WayPointObject::GetPos() const @@ -188,3 +196,146 @@ Rotation WayPointObject::GetPathRotation(float alpha, bool loop) const { return Rotation(CalculateWayPointTransform(m_waypoint.name, alpha, loop).Orientation); } + +void WayPointObject::Preview(sol::optional color) const +{ + constexpr auto DEFAULT_COLOR = Vector4(1.0f, 0.65f, 0.0f, 1.0f); // Orange + constexpr auto TARGET_RADIUS = 128.0f; + constexpr auto NUM_SEGMENTS = 32; + + Vector4 previewColor = color.value_or(DEFAULT_COLOR); + Color drawColor(previewColor.x, previewColor.y, previewColor.z, previewColor.w); + + Vector3 centerPos(m_waypoint.x, m_waypoint.y, m_waypoint.z); + WayPointType wpType = static_cast(m_waypoint.type); + + // Create rotation matrix from waypoint's rotations + Matrix rotationMatrix = Matrix::CreateRotationX(m_waypoint.rotationX * (float)RADIAN) * + Matrix::CreateRotationY(m_waypoint.rotationY * (float)RADIAN) * + Matrix::CreateRotationZ(m_waypoint.roll * (float)RADIAN); + Quaternion orient = Quaternion::CreateFromRotationMatrix(rotationMatrix); + + switch (wpType) + { + case WayPointType::Point: + // Draw a target marker for a single point + DrawDebugTarget(centerPos, orient, TARGET_RADIUS, drawColor, RendererDebugPage::CollisionStats); + break; + + case WayPointType::Circle: + { + // Draw a sphere to represent the circle + DrawDebugSphere(centerPos, m_waypoint.radius1, drawColor, RendererDebugPage::CollisionStats, true); + break; + } + + case WayPointType::Ellipse: + { + // Draw ellipse as a series of lines + // Since there's no direct ellipse function, we'll draw it as connected line segments + for (int i = 0; i < NUM_SEGMENTS; i++) + { + float angle1 = (i / (float)NUM_SEGMENTS) * 2.0f * (float)PI; + float angle2 = ((i + 1) / (float)NUM_SEGMENTS) * 2.0f * (float)PI; + + Vector3 local1(std::cos(angle1) * m_waypoint.radius1, 0.0f, std::sin(angle1) * m_waypoint.radius2); + Vector3 local2(std::cos(angle2) * m_waypoint.radius1, 0.0f, std::sin(angle2) * m_waypoint.radius2); + + Vector3 world1 = Vector3::Transform(local1, rotationMatrix) + centerPos; + Vector3 world2 = Vector3::Transform(local2, rotationMatrix) + centerPos; + + DrawDebugLine(world1, world2, drawColor, RendererDebugPage::CollisionStats); + } + break; + } + + case WayPointType::Square: + { + // Draw square as four connected lines + float r = m_waypoint.radius1; + Vector3 corners[5] = { + Vector3(-r, 0.0f, -r), + Vector3(r, 0.0f, -r), + Vector3(r, 0.0f, r), + Vector3(-r, 0.0f, r), + Vector3(-r, 0.0f, -r) // Close the loop + }; + + for (int i = 0; i < 4; i++) + { + Vector3 world1 = Vector3::Transform(corners[i], rotationMatrix) + centerPos; + Vector3 world2 = Vector3::Transform(corners[i + 1], rotationMatrix) + centerPos; + DrawDebugLine(world1, world2, drawColor, RendererDebugPage::CollisionStats); + } + break; + } + + case WayPointType::Rectangle: + { + // Draw rectangle as four connected lines + float r1 = m_waypoint.radius1; + float r2 = m_waypoint.radius2; + Vector3 corners[5] = { + Vector3(-r1, 0.0f, -r2), + Vector3(r1, 0.0f, -r2), + Vector3(r1, 0.0f, r2), + Vector3(-r1, 0.0f, r2), + Vector3(-r1, 0.0f, -r2) // Close the loop + }; + + for (int i = 0; i < 4; i++) + { + Vector3 world1 = Vector3::Transform(corners[i], rotationMatrix) + centerPos; + Vector3 world2 = Vector3::Transform(corners[i + 1], rotationMatrix) + centerPos; + DrawDebugLine(world1, world2, drawColor, RendererDebugPage::CollisionStats); + } + break; + } + + case WayPointType::Linear: + case WayPointType::Bezier: + { + // Draw path as connected line segments + // Find all waypoints with the same name + std::vector pathWaypoints; + for (const auto& wp : WayPoints) + { + if (wp.name == m_waypoint.name) + pathWaypoints.push_back(&wp); + } + + if (pathWaypoints.size() < 2) + { + // If there's only one waypoint, just draw a target + DrawDebugTarget(centerPos, orient, TARGET_RADIUS, drawColor, RendererDebugPage::CollisionStats); + break; + } + + // Sort by number + std::sort(pathWaypoints.begin(), pathWaypoints.end(), + [](const WAYPOINT* a, const WAYPOINT* b) { return a->number < b->number; }); + + // Draw lines between waypoints or interpolated path + constexpr int PATH_SEGMENTS = 50; + for (int i = 0; i < PATH_SEGMENTS; i++) + { + float alpha1 = i / (float)PATH_SEGMENTS; + float alpha2 = (i + 1) / (float)PATH_SEGMENTS; + + Vector3 pos1 = CalculateWayPointTransform(m_waypoint.name, alpha1, false).Position; + Vector3 pos2 = CalculateWayPointTransform(m_waypoint.name, alpha2, false).Position; + + DrawDebugLine(pos1, pos2, drawColor, RendererDebugPage::CollisionStats); + } + + // Draw markers at each waypoint position + for (const auto* wp : pathWaypoints) + { + Vector3 wpPos(wp->x, wp->y, wp->z); + DrawDebugSphere(wpPos, 32.0f, drawColor, RendererDebugPage::CollisionStats, true); + } + break; + } + } +} + diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h index 0a79c8554d..5c30322a86 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h @@ -44,6 +44,8 @@ class WayPointObject : public NamedBase Vec3 GetPathPosition(float alpha, bool loop) const; Rotation GetPathRotation(float alpha, bool loop) const; + + void Preview(sol::optional color) const; private: WAYPOINT& m_waypoint; From 4c804f9fa2e2660798e05cf0935094f8eb2a24bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 25 Jan 2026 14:56:42 +0000 Subject: [PATCH 13/16] Refactor WayPoint to separate namespace instead of ObjectsHandler Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com> --- TombEngine/Scripting/Include/VarMapVal.h | 4 +- .../Internal/ScriptInterfaceState.cpp | 2 + .../Internal/TEN/Objects/ObjectsHandler.cpp | 23 - .../Internal/TEN/Objects/ObjectsHandler.h | 20 - .../Internal/TEN/WayPoint/WayPointHandler.cpp | 413 ++++++++++++++++++ .../Internal/TEN/WayPoint/WayPointHandler.h | 67 +++ .../Internal/TEN/WayPoint/WayPointTypes.h | 44 ++ TombEngine/Specific/level.cpp | 7 - TombEngine/TombEngine.vcxproj | 6 +- 9 files changed, 530 insertions(+), 56 deletions(-) create mode 100644 TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.cpp create mode 100644 TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.h create mode 100644 TombEngine/Scripting/Internal/TEN/WayPoint/WayPointTypes.h diff --git a/TombEngine/Scripting/Include/VarMapVal.h b/TombEngine/Scripting/Include/VarMapVal.h index 68728a49da..c601126565 100644 --- a/TombEngine/Scripting/Include/VarMapVal.h +++ b/TombEngine/Scripting/Include/VarMapVal.h @@ -7,7 +7,6 @@ struct SoundSourceInfo; struct TriggerVolume; struct AI_OBJECT; struct RoomData; -struct WAYPOINT; using VarMapVal = std::variant< int, @@ -17,5 +16,4 @@ using VarMapVal = std::variant< std::reference_wrapper, std::reference_wrapper, std::reference_wrapper, - std::reference_wrapper, - std::reference_wrapper>; + std::reference_wrapper>; diff --git a/TombEngine/Scripting/Internal/ScriptInterfaceState.cpp b/TombEngine/Scripting/Internal/ScriptInterfaceState.cpp index 32cd280193..141cc3db27 100644 --- a/TombEngine/Scripting/Internal/ScriptInterfaceState.cpp +++ b/TombEngine/Scripting/Internal/ScriptInterfaceState.cpp @@ -13,6 +13,7 @@ #include "Scripting/Internal/TEN/Sound/SoundHandler.h" #include "Scripting/Internal/TEN/Util/Util.h" #include "Scripting/Internal/TEN/View/ViewHandler.h" +#include "Scripting/Internal/TEN/WayPoint/WayPointHandler.h" constexpr auto DEADLOCK_CHECK_INTERVAL = 500; constexpr auto DEADLOCK_HOOK_INSTRUCTION_COUNT = 1000; @@ -106,4 +107,5 @@ void ScriptInterfaceState::Init(const std::string& assetsDir) TEN::Scripting::Sound::Register(&SolState, RootTable); TEN::Scripting::Util::Register(&SolState, RootTable); TEN::Scripting::View::Register(&SolState, RootTable); + TEN::Scripting::WayPoint::Register(&SolState, RootTable); } diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp index 87570ae120..eb11057770 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.cpp @@ -19,8 +19,6 @@ #include "Scripting/Internal/TEN/Objects/Sink/SinkObject.h" #include "Scripting/Internal/TEN/Objects/SoundSource/SoundSourceObject.h" #include "Scripting/Internal/TEN/Objects/Volume/VolumeObject.h" -#include "Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h" -#include "Scripting/Internal/TEN/Objects/WayPoint/WayPointTypes.h" using namespace TEN::Scripting::Objects; @@ -83,22 +81,6 @@ ObjectsHandler::ObjectsHandler(sol::state* lua, sol::table& parent) : */ _table_objects.set_function(ScriptReserved_GetSinkByName, &ObjectsHandler::GetByName, this); - /*** - Get a WayPoint by its name. - @function GetWayPointByName - @tparam string name The unique name of the waypoint. - @treturn Objects.WayPoint A non-owning WayPoint referencing the waypoint. - */ - _table_objects.set_function(ScriptReserved_GetWayPointByName, &ObjectsHandler::GetByName, this); - - /*** - Get waypoints by their type/camera number. - @function GetWayPointsByType - @tparam int type The type/camera number of the waypoints. - @treturn table Table of waypoints referencing the given type. - */ - _table_objects.set_function(ScriptReserved_GetWayPointsByType, &ObjectsHandler::GetWayPointsByType, this); - /*** Get a SoundSource by its name. @function GetSoundSourceByName @@ -189,10 +171,6 @@ ObjectsHandler::ObjectsHandler(sol::state* lua, sol::table& parent) : Volume::SetNameCallbacks( [this](auto && ... param) { return AddName(std::forward(param)...); }, [this](auto && ... param) { return RemoveName(std::forward(param)...); }); - WayPointObject::Register(_table_objects); - WayPointObject::SetNameCallbacks( - [this](auto && ... param) { return AddName(std::forward(param)...); }, - [this](auto && ... param) { return RemoveName(std::forward(param)...); }); _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_ObjID, GAME_OBJECT_IDS); _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_RoomFlagID, ROOM_FLAG_IDS); @@ -201,7 +179,6 @@ ObjectsHandler::ObjectsHandler(sol::state* lua, sol::table& parent) : _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_AmmoType, AMMO_TYPES); _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_HandStatus, HAND_STATUSES); _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_MoveableStatus, MOVEABLE_STATUSES); - _handler.MakeReadOnlyTable(_table_objects, ScriptReserved_WayPointType, WAYPOINT_TYPES); } void ObjectsHandler::TestCollidingObjects() diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h index 18b5a471df..846a91477c 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h @@ -128,26 +128,6 @@ class ObjectsHandler : public ScriptInterfaceObjectsHandler return rooms; } - template - std::vector > GetWayPointsByType(int type) - { - auto waypoints = std::vector>{}; - for (const auto& [key, value] : _nameMap) - { - if (!std::holds_alternative>(value)) - continue; - - auto waypoint = std::get>(value).get(); - - if (waypoint.type == type) - { - waypoints.push_back(GetByName(key)); - } - } - - return waypoints; - } - int GetIndexByName(std::string const& name) const override { if (_nameMap.find(name) == _nameMap.end()) diff --git a/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.cpp b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.cpp new file mode 100644 index 0000000000..c277636bde --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.cpp @@ -0,0 +1,413 @@ +#include "framework.h" +#include "WayPointHandler.h" +#include "Game/waypoint.h" +#include "Game/debug/debug.h" +#include "WayPointTypes.h" + +#include "Scripting/Internal/LuaHandler.h" +#include "Scripting/Internal/ReservedScriptNames.h" +#include "Scripting/Internal/ScriptAssert.h" +#include "Scripting/Internal/ScriptUtil.h" +#include "Scripting/Internal/TEN/Types/Vec3/Vec3.h" +#include "Scripting/Internal/TEN/Types/Rotation/Rotation.h" +#include "Specific/level.h" + +using namespace TEN::Debug; + +namespace TEN::Scripting::WayPoint +{ + /*** + Waypoint objects for navigation and path traversal. + + @tenclass WayPoint.WayPoint + @pragma nostrip + */ + + static auto IndexError = IndexErrorMaker(WayPointHandler, ScriptReserved_WayPoint); + static auto NewIndexError = NewIndexErrorMaker(WayPointHandler, ScriptReserved_WayPoint); + + /// Create a WayPoint handler by waypoint name. + // @function WayPoint + // @tparam string name Waypoint name. + // @treturn WayPoint A new WayPoint handler. + WayPointHandler::WayPointHandler(const std::string& name) : m_waypoint(nullptr) + { + // Find waypoint by name + for (auto& wp : WayPoints) + { + if (wp.name == name) + { + m_waypoint = ℘ + return; + } + } + + ScriptAssertF(false, "Waypoint with name '{}' not found.", name); + } + + void WayPointHandler::Register(sol::table& parent) + { + using ctors = sol::constructors; + + parent.new_usertype( + ScriptReserved_WayPoint, + ctors(), sol::call_constructor, ctors(), + sol::meta_function::index, IndexError, + sol::meta_function::new_index, NewIndexError, + + /// Get the waypoint's position. + // @function WayPoint:GetPosition + // @treturn Vec3 Waypoint's position. + ScriptReserved_GetPosition, &WayPointHandler::GetPosition, + + /// Set the waypoint's position. + // @function WayPoint:SetPosition + // @tparam Vec3 position The new position of the waypoint. + ScriptReserved_SetPosition, &WayPointHandler::SetPosition, + + /// Get the waypoint's unique string identifier. + // @function WayPoint:GetName + // @treturn string The waypoint's name. + ScriptReserved_GetName, &WayPointHandler::GetName, + + /// Set the waypoint's name (its unique string identifier). + // @function WayPoint:SetName + // @tparam string name The waypoint's new name. + ScriptReserved_SetName, &WayPointHandler::SetName, + + /// Get the waypoint's type. + // @function WayPoint:GetType + // @treturn int The waypoint's type identifier. + ScriptReserved_GetType, &WayPointHandler::GetType, + + /// Set the waypoint's type. + // @function WayPoint:SetType + // @tparam int type The waypoint's new type identifier. + ScriptReserved_SetType, &WayPointHandler::SetType, + + /// Get the waypoint's number. + // @function WayPoint:GetNumber + // @treturn int The waypoint's number. + ScriptReserved_GetNumber, &WayPointHandler::GetNumber, + + /// Set the waypoint's number. + // @function WayPoint:SetNumber + // @tparam int number The waypoint's new number. + ScriptReserved_SetNumber, &WayPointHandler::SetNumber, + + /// Get the waypoint's radius1. + // @function WayPoint:GetRadius1 + // @treturn float The waypoint's radius1. + ScriptReserved_GetRadius1, &WayPointHandler::GetRadius1, + + /// Set the waypoint's radius1. + // @function WayPoint:SetRadius1 + // @tparam float radius The waypoint's new radius1. + ScriptReserved_SetRadius1, &WayPointHandler::SetRadius1, + + /// Get the waypoint's radius2. + // @function WayPoint:GetRadius2 + // @treturn float The waypoint's radius2. + ScriptReserved_GetRadius2, &WayPointHandler::GetRadius2, + + /// Set the waypoint's radius2. + // @function WayPoint:SetRadius2 + // @tparam float radius The waypoint's new radius2. + ScriptReserved_SetRadius2, &WayPointHandler::SetRadius2, + + /// Get an interpolated position along the waypoint path. + // @function WayPoint:GetPathPosition + // @tparam float alpha Progress along the path (0.0 to 1.0). + // @tparam bool loop Whether to loop the path continuously. + // @treturn Vec3 Interpolated position at the given alpha. + ScriptReserved_GetPathPosition, &WayPointHandler::GetPathPosition, + + /// Get an interpolated rotation along the waypoint path. + // @function WayPoint:GetPathRotation + // @tparam float alpha Progress along the path (0.0 to 1.0). + // @tparam bool loop Whether to loop the path continuously. + // @treturn Rotation Interpolated rotation at the given alpha. + ScriptReserved_GetPathRotation, &WayPointHandler::GetPathRotation, + + /// Preview/visualize the waypoint for debugging purposes. + // @function WayPoint:Preview + // @tparam[opt] Color color Optional color for the preview (defaults to orange). + ScriptReserved_Preview, &WayPointHandler::Preview); + } + + Vec3 WayPointHandler::GetPosition() const + { + return Vec3(m_waypoint->x, m_waypoint->y, m_waypoint->z); + } + + void WayPointHandler::SetPosition(const Vec3& pos) + { + m_waypoint->x = pos.x; + m_waypoint->y = pos.y; + m_waypoint->z = pos.z; + } + + std::string WayPointHandler::GetName() const + { + return m_waypoint->name; + } + + void WayPointHandler::SetName(const std::string& name) + { + m_waypoint->name = name; + } + + int WayPointHandler::GetType() const + { + return m_waypoint->type; + } + + void WayPointHandler::SetType(int type) + { + m_waypoint->type = type; + } + + int WayPointHandler::GetNumber() const + { + return m_waypoint->number; + } + + void WayPointHandler::SetNumber(int number) + { + m_waypoint->number = number; + } + + float WayPointHandler::GetRadius1() const + { + return m_waypoint->radius1; + } + + void WayPointHandler::SetRadius1(float radius) + { + m_waypoint->radius1 = radius; + } + + float WayPointHandler::GetRadius2() const + { + return m_waypoint->radius2; + } + + void WayPointHandler::SetRadius2(float radius) + { + m_waypoint->radius2 = radius; + } + + Vec3 WayPointHandler::GetPathPosition(float alpha, bool loop) const + { + return Vec3(CalculateWayPointTransform(m_waypoint->name, alpha, loop).Position); + } + + Rotation WayPointHandler::GetPathRotation(float alpha, bool loop) const + { + return Rotation(CalculateWayPointTransform(m_waypoint->name, alpha, loop).Orientation); + } + + void WayPointHandler::Preview(sol::optional color) const + { + constexpr auto DEFAULT_COLOR = Vector4(1.0f, 0.65f, 0.0f, 1.0f); // Orange + constexpr auto TARGET_RADIUS = 128.0f; + constexpr auto NUM_SEGMENTS = 32; + + Vector4 previewColor = color.value_or(DEFAULT_COLOR); + Color drawColor(previewColor.x, previewColor.y, previewColor.z, previewColor.w); + + Vector3 centerPos(m_waypoint->x, m_waypoint->y, m_waypoint->z); + WayPointType wpType = static_cast(m_waypoint->type); + + // Create rotation matrix from waypoint's rotations + Matrix rotationMatrix = Matrix::CreateRotationX(m_waypoint->rotationX * (float)RADIAN) * + Matrix::CreateRotationY(m_waypoint->rotationY * (float)RADIAN) * + Matrix::CreateRotationZ(m_waypoint->roll * (float)RADIAN); + Quaternion orient = Quaternion::CreateFromRotationMatrix(rotationMatrix); + + switch (wpType) + { + case WayPointType::Point: + // Draw a target marker for a single point + DrawDebugTarget(centerPos, orient, TARGET_RADIUS, drawColor, RendererDebugPage::CollisionStats); + break; + + case WayPointType::Circle: + { + // Draw a sphere to represent the circle + DrawDebugSphere(centerPos, m_waypoint->radius1, drawColor, RendererDebugPage::CollisionStats, true); + break; + } + + case WayPointType::Ellipse: + { + // Draw ellipse as a series of lines + // Since there's no direct ellipse function, we'll draw it as connected line segments + for (int i = 0; i < NUM_SEGMENTS; i++) + { + float angle1 = (i / (float)NUM_SEGMENTS) * 2.0f * (float)PI; + float angle2 = ((i + 1) / (float)NUM_SEGMENTS) * 2.0f * (float)PI; + + Vector3 local1(std::cos(angle1) * m_waypoint->radius1, 0.0f, std::sin(angle1) * m_waypoint->radius2); + Vector3 local2(std::cos(angle2) * m_waypoint->radius1, 0.0f, std::sin(angle2) * m_waypoint->radius2); + + Vector3 world1 = Vector3::Transform(local1, rotationMatrix) + centerPos; + Vector3 world2 = Vector3::Transform(local2, rotationMatrix) + centerPos; + + DrawDebugLine(world1, world2, drawColor, RendererDebugPage::CollisionStats); + } + break; + } + + case WayPointType::Square: + { + // Draw square as four connected lines + float r = m_waypoint->radius1; + Vector3 corners[5] = { + Vector3(-r, 0.0f, -r), + Vector3(r, 0.0f, -r), + Vector3(r, 0.0f, r), + Vector3(-r, 0.0f, r), + Vector3(-r, 0.0f, -r) // Close the loop + }; + + for (int i = 0; i < 4; i++) + { + Vector3 world1 = Vector3::Transform(corners[i], rotationMatrix) + centerPos; + Vector3 world2 = Vector3::Transform(corners[i + 1], rotationMatrix) + centerPos; + DrawDebugLine(world1, world2, drawColor, RendererDebugPage::CollisionStats); + } + break; + } + + case WayPointType::Rectangle: + { + // Draw rectangle as four connected lines + float r1 = m_waypoint->radius1; + float r2 = m_waypoint->radius2; + Vector3 corners[5] = { + Vector3(-r1, 0.0f, -r2), + Vector3(r1, 0.0f, -r2), + Vector3(r1, 0.0f, r2), + Vector3(-r1, 0.0f, r2), + Vector3(-r1, 0.0f, -r2) // Close the loop + }; + + for (int i = 0; i < 4; i++) + { + Vector3 world1 = Vector3::Transform(corners[i], rotationMatrix) + centerPos; + Vector3 world2 = Vector3::Transform(corners[i + 1], rotationMatrix) + centerPos; + DrawDebugLine(world1, world2, drawColor, RendererDebugPage::CollisionStats); + } + break; + } + + case WayPointType::Linear: + case WayPointType::Bezier: + { + // Draw path as connected line segments + // Find all waypoints with the same name + std::vector pathWaypoints; + for (const auto& wp : WayPoints) + { + if (wp.name == m_waypoint->name) + pathWaypoints.push_back(&wp); + } + + if (pathWaypoints.size() < 2) + { + // If there's only one waypoint, just draw a target + DrawDebugTarget(centerPos, orient, TARGET_RADIUS, drawColor, RendererDebugPage::CollisionStats); + break; + } + + // Sort by number + std::sort(pathWaypoints.begin(), pathWaypoints.end(), + [](const WAYPOINT* a, const WAYPOINT* b) { return a->number < b->number; }); + + // Draw lines between waypoints or interpolated path + constexpr int PATH_SEGMENTS = 50; + for (int i = 0; i < PATH_SEGMENTS; i++) + { + float alpha1 = i / (float)PATH_SEGMENTS; + float alpha2 = (i + 1) / (float)PATH_SEGMENTS; + + Vector3 pos1 = CalculateWayPointTransform(m_waypoint->name, alpha1, false).Position; + Vector3 pos2 = CalculateWayPointTransform(m_waypoint->name, alpha2, false).Position; + + DrawDebugLine(pos1, pos2, drawColor, RendererDebugPage::CollisionStats); + } + + // Draw markers at each waypoint position + for (const auto* wp : pathWaypoints) + { + Vector3 wpPos(wp->x, wp->y, wp->z); + DrawDebugSphere(wpPos, 32.0f, drawColor, RendererDebugPage::CollisionStats, true); + } + break; + } + } + } + + /// Get a waypoint by name. + // @function GetWayPointByName + // @tparam string name The waypoint name. + // @treturn WayPoint The waypoint with the given name, or nil if not found. + std::unique_ptr GetWayPointByName(const std::string& name) + { + // Find waypoint by name + for (auto& wp : WayPoints) + { + if (wp.name == name) + { + return std::make_unique(name); + } + } + + return nullptr; + } + + /// Get all waypoints with a specific type. + // @function GetWayPointsByType + // @tparam int type The waypoint type (use WayPointType enum). + // @treturn table Table of waypoints with the given type. + sol::table GetWayPointsByType(sol::this_state s, int type) + { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int index = 1; + for (auto& wp : WayPoints) + { + if (wp.type == type) + { + result[index++] = std::make_unique(wp.name); + } + } + + return result; + } + + void Register(sol::state* state, sol::table& parent) + { + auto wpTable = sol::table(state->lua_state(), sol::create); + parent.set(ScriptReserved_WayPoint, wpTable); + + WayPointHandler::Register(wpTable); + + /// Get a waypoint by name. + // @function WayPoint.GetWayPointByName + // @tparam string name The waypoint name. + // @treturn WayPoint The waypoint with the given name, or nil if not found. + wpTable.set_function(ScriptReserved_GetWayPointByName, GetWayPointByName); + + /// Get all waypoints with a specific type. + // @function WayPoint.GetWayPointsByType + // @tparam int type The waypoint type (use WayPointType enum). + // @treturn table Table of waypoints with the given type. + wpTable.set_function(ScriptReserved_GetWayPointsByType, GetWayPointsByType); + + auto handler = LuaHandler(state); + handler.MakeReadOnlyTable(wpTable, ScriptReserved_WayPointType, WAYPOINT_TYPES); + } +} diff --git a/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.h b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.h new file mode 100644 index 0000000000..70df97e275 --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include "Math/Math.h" + +struct WAYPOINT; + +namespace sol +{ + class state; + class table; +} + +class Vec3; +class Rotation; + +namespace TEN::Scripting::WayPoint +{ + /// Represents a waypoint in the game world for navigation and path traversal. + // @tenclass WayPoint.WayPoint + // @pragma nostrip + + class WayPointHandler + { + public: + static void Register(sol::table& parent); + + private: + WAYPOINT* m_waypoint; + + public: + // Constructors + WayPointHandler() = delete; + WayPointHandler(const std::string& name); + + // Getters + Vec3 GetPosition() const; + void SetPosition(const Vec3& pos); + + std::string GetName() const; + void SetName(const std::string& name); + + int GetType() const; + void SetType(int type); + + int GetNumber() const; + void SetNumber(int number); + + float GetRadius1() const; + void SetRadius1(float radius); + + float GetRadius2() const; + void SetRadius2(float radius); + + Vec3 GetPathPosition(float alpha, bool loop) const; + Rotation GetPathRotation(float alpha, bool loop) const; + + void Preview(sol::optional color) const; + }; + + // Static functions to get waypoints + std::unique_ptr GetWayPointByName(const std::string& name); + sol::table GetWayPointsByType(sol::this_state s, int type); + + void Register(sol::state* state, sol::table& parent); +} diff --git a/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointTypes.h b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointTypes.h new file mode 100644 index 0000000000..c1e3efb02e --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointTypes.h @@ -0,0 +1,44 @@ +#pragma once + +#include "Game/waypoint.h" +#include +#include + +namespace TEN::Scripting +{ + /// Constants for waypoint types. + // To be used with @{Objects.WayPoint.GetType} and @{Objects.WayPoint.SetType} functions. + // @enum Objects.WayPointType + // @pragma nostrip + + static const auto WAYPOINT_TYPES = std::unordered_map + { + /// Single point, no radius. Only can have rotation. + // @mem POINT + { "POINT", WayPointType::Point }, + + /// Single point with radius1. Can have rotation, can be rotated on all 3 axes. + // @mem CIRCLE + { "CIRCLE", WayPointType::Circle }, + + /// Single point with two radii. Can have rotation, can be rotated on all 3 axes. + // @mem ELLIPSE + { "ELLIPSE", WayPointType::Ellipse }, + + /// Single point with radius (rendered as square). Can be rotated on all 3 axes. + // @mem SQUARE + { "SQUARE", WayPointType::Square }, + + /// Single point with two radii (rendered as rectangle). Can be rotated on all 3 axes. + // @mem RECTANGLE + { "RECTANGLE", WayPointType::Rectangle }, + + /// Multi-point linear path, each point on the path can have a rotation. + // @mem LINEAR + { "LINEAR", WayPointType::Linear }, + + /// Multi-point bezier path, each point on the path can have a rotation. + // @mem BEZIER + { "BEZIER", WayPointType::Bezier } + }; +} diff --git a/TombEngine/Specific/level.cpp b/TombEngine/Specific/level.cpp index 0b8c7c7980..76878e8a29 100644 --- a/TombEngine/Specific/level.cpp +++ b/TombEngine/Specific/level.cpp @@ -559,13 +559,6 @@ void LoadCameras() waypoint.luaName = ReadString(); WayPoints.push_back(waypoint); - - // Register with script engine using LuaName if available, otherwise use Name - std::string scriptName = waypoint.luaName.empty() ? waypoint.name : waypoint.luaName; - if (!scriptName.empty()) - { - g_GameScriptEntities->AddName(scriptName, WayPoints.back()); - } } int sinkCount = ReadCount(); diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj index cc620b3ef7..8156c830c9 100644 --- a/TombEngine/TombEngine.vcxproj +++ b/TombEngine/TombEngine.vcxproj @@ -917,9 +917,9 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. - - + + @@ -1371,9 +1371,9 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. - + From c0d4f2d5d66d5f870dba60ecfcca0839303f66e8 Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Sun, 25 Jan 2026 10:36:00 -0500 Subject: [PATCH 14/16] WIP --- TombEngine/TombEngine.vcxproj.user | 1 + 1 file changed, 1 insertion(+) diff --git a/TombEngine/TombEngine.vcxproj.user b/TombEngine/TombEngine.vcxproj.user index 376f694fce..dcd10484fd 100644 --- a/TombEngine/TombEngine.vcxproj.user +++ b/TombEngine/TombEngine.vcxproj.user @@ -16,6 +16,7 @@ $(SolutionDir)Build\$(Configuration)\ WindowsLocalDebugger /debug + /debug| $(SolutionDir)Build\$(Configuration)\ From fc771022a0f41da61cd0bb688947a8820a5b9cbd Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Sun, 25 Jan 2026 10:39:24 -0500 Subject: [PATCH 15/16] Fix ai code --- .../Internal/TEN/Objects/ObjectsHandler.h | 2 - .../TEN/Objects/WayPoint/WayPointObject.cpp | 341 ------------------ .../TEN/Objects/WayPoint/WayPointObject.h | 52 --- .../TEN/Objects/WayPoint/WayPointTypes.h | 44 --- 4 files changed, 439 deletions(-) delete mode 100644 TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp delete mode 100644 TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h delete mode 100644 TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointTypes.h diff --git a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h index 846a91477c..ad2264f93c 100644 --- a/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h +++ b/TombEngine/Scripting/Internal/TEN/Objects/ObjectsHandler.h @@ -8,8 +8,6 @@ #include "Scripting/Internal/TEN/Objects/Static/StaticObject.h" #include "Scripting/Internal/TEN/Objects/AIObject/AIObject.h" -struct WAYPOINT; - class ObjectsHandler : public ScriptInterfaceObjectsHandler { public: diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp deleted file mode 100644 index a38fefdce3..0000000000 --- a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.cpp +++ /dev/null @@ -1,341 +0,0 @@ -#include "framework.h" -#include "WayPointObject.h" -#include "Game/waypoint.h" -#include "Game/debug/debug.h" - -#include "Scripting/Internal/ReservedScriptNames.h" -#include "Scripting/Internal/ScriptAssert.h" -#include "Scripting/Internal/ScriptUtil.h" -#include "Scripting/Internal/TEN/Types/Vec3/Vec3.h" -#include "Scripting/Internal/TEN/Types/Rotation/Rotation.h" -#include "Specific/level.h" - -using namespace TEN::Debug; - -/*** -Waypoint objects for navigation and path traversal. - -@tenclass Objects.WayPoint -@pragma nostrip -*/ - -static auto IndexError = IndexErrorMaker(WayPointObject, ScriptReserved_WayPoint); -static auto NewIndexError = NewIndexErrorMaker(WayPointObject, ScriptReserved_WayPoint); - -WayPointObject::WayPointObject(WAYPOINT& ref) : m_waypoint{ref} -{}; - -void WayPointObject::Register(sol::table& parent) -{ - parent.new_usertype(ScriptReserved_WayPoint, - sol::no_constructor, - sol::meta_function::index, IndexError, - sol::meta_function::new_index, NewIndexError, - - /// Get the waypoint's position. - // @function WayPoint:GetPosition - // @treturn Vec3 Waypoint's position. - ScriptReserved_GetPosition, &WayPointObject::GetPos, - - /// Set the waypoint's position. - // @function WayPoint:SetPosition - // @tparam Vec3 position The new position of the waypoint. - ScriptReserved_SetPosition, &WayPointObject::SetPos, - - /// Get the waypoint's unique string identifier. - // @function WayPoint:GetName - // @treturn string The waypoint's name. - ScriptReserved_GetName, &WayPointObject::GetName, - - /// Set the waypoint's name (its unique string identifier). - // @function WayPoint:SetName - // @tparam string name The waypoint's new name. - ScriptReserved_SetName, &WayPointObject::SetName, - - /// Get the waypoint's type. - // @function WayPoint:GetType - // @treturn int The waypoint's type identifier. - ScriptReserved_GetType, &WayPointObject::GetType, - - /// Set the waypoint's type. - // @function WayPoint:SetType - // @tparam int type The waypoint's new type identifier. - ScriptReserved_SetType, &WayPointObject::SetType, - - /// Get the waypoint's number. - // @function WayPoint:GetNumber - // @treturn int The waypoint's number. - ScriptReserved_GetNumber, &WayPointObject::GetNumber, - - /// Set the waypoint's number. - // @function WayPoint:SetNumber - // @tparam int number The waypoint's new number. - ScriptReserved_SetNumber, &WayPointObject::SetNumber, - - /// Get the waypoint's radius1. - // @function WayPoint:GetRadius1 - // @treturn float The waypoint's radius1. - ScriptReserved_GetRadius1, &WayPointObject::GetRadius1, - - /// Set the waypoint's radius1. - // @function WayPoint:SetRadius1 - // @tparam float radius The waypoint's new radius1. - ScriptReserved_SetRadius1, &WayPointObject::SetRadius1, - - /// Get the waypoint's radius2. - // @function WayPoint:GetRadius2 - // @treturn float The waypoint's radius2. - ScriptReserved_GetRadius2, &WayPointObject::GetRadius2, - - /// Set the waypoint's radius2. - // @function WayPoint:SetRadius2 - // @tparam float radius The waypoint's new radius2. - ScriptReserved_SetRadius2, &WayPointObject::SetRadius2, - - /// Get an interpolated position along the waypoint path. - // @function WayPoint:GetPathPosition - // @tparam float alpha Progress along the path (0.0 to 1.0). - // @tparam bool loop Whether to loop the path continuously. - // @treturn Vec3 Interpolated position at the given alpha. - ScriptReserved_GetPathPosition, &WayPointObject::GetPathPosition, - - /// Get an interpolated rotation along the waypoint path. - // @function WayPoint:GetPathRotation - // @tparam float alpha Progress along the path (0.0 to 1.0). - // @tparam bool loop Whether to loop the path continuously. - // @treturn Rotation Interpolated rotation at the given alpha. - ScriptReserved_GetPathRotation, &WayPointObject::GetPathRotation, - - /// Preview/visualize the waypoint for debugging purposes. - // @function WayPoint:Preview - // @tparam[opt] Color color Optional color for the preview (defaults to orange). - ScriptReserved_Preview, &WayPointObject::Preview); -} - -Vec3 WayPointObject::GetPos() const -{ - return Vec3(m_waypoint.x, m_waypoint.y, m_waypoint.z); -} - -void WayPointObject::SetPos(Vec3 const& pos) -{ - m_waypoint.x = pos.x; - m_waypoint.y = pos.y; - m_waypoint.z = pos.z; -} - -std::string WayPointObject::GetName() const -{ - return m_waypoint.name; -} - -void WayPointObject::SetName(std::string const& id) -{ - if (!ScriptAssert(!id.empty(), "Name cannot be blank. Not setting name.")) - return; - - if (_callbackSetName(id, m_waypoint)) - { - // Remove old name if it exists - _callbackRemoveName(m_waypoint.name); - m_waypoint.name = id; - } - else - { - ScriptAssertF(false, "Could not add name {} - does a waypoint with this name already exist?", id); - TENLog("Name will not be set", LogLevel::Warning, LogConfig::All); - } -} - -int WayPointObject::GetType() const -{ - return m_waypoint.type; -} - -void WayPointObject::SetType(int type) -{ - m_waypoint.type = type; -} - -int WayPointObject::GetNumber() const -{ - return m_waypoint.number; -} - -void WayPointObject::SetNumber(int number) -{ - m_waypoint.number = (unsigned short)number; -} - -float WayPointObject::GetRadius1() const -{ - return m_waypoint.radius1; -} - -void WayPointObject::SetRadius1(float radius) -{ - m_waypoint.radius1 = radius; -} - -float WayPointObject::GetRadius2() const -{ - return m_waypoint.radius2; -} - -void WayPointObject::SetRadius2(float radius) -{ - m_waypoint.radius2 = radius; -} - -Vec3 WayPointObject::GetPathPosition(float alpha, bool loop) const -{ - return Vec3(CalculateWayPointTransform(m_waypoint.name, alpha, loop).Position); -} - -Rotation WayPointObject::GetPathRotation(float alpha, bool loop) const -{ - return Rotation(CalculateWayPointTransform(m_waypoint.name, alpha, loop).Orientation); -} - -void WayPointObject::Preview(sol::optional color) const -{ - constexpr auto DEFAULT_COLOR = Vector4(1.0f, 0.65f, 0.0f, 1.0f); // Orange - constexpr auto TARGET_RADIUS = 128.0f; - constexpr auto NUM_SEGMENTS = 32; - - Vector4 previewColor = color.value_or(DEFAULT_COLOR); - Color drawColor(previewColor.x, previewColor.y, previewColor.z, previewColor.w); - - Vector3 centerPos(m_waypoint.x, m_waypoint.y, m_waypoint.z); - WayPointType wpType = static_cast(m_waypoint.type); - - // Create rotation matrix from waypoint's rotations - Matrix rotationMatrix = Matrix::CreateRotationX(m_waypoint.rotationX * (float)RADIAN) * - Matrix::CreateRotationY(m_waypoint.rotationY * (float)RADIAN) * - Matrix::CreateRotationZ(m_waypoint.roll * (float)RADIAN); - Quaternion orient = Quaternion::CreateFromRotationMatrix(rotationMatrix); - - switch (wpType) - { - case WayPointType::Point: - // Draw a target marker for a single point - DrawDebugTarget(centerPos, orient, TARGET_RADIUS, drawColor, RendererDebugPage::CollisionStats); - break; - - case WayPointType::Circle: - { - // Draw a sphere to represent the circle - DrawDebugSphere(centerPos, m_waypoint.radius1, drawColor, RendererDebugPage::CollisionStats, true); - break; - } - - case WayPointType::Ellipse: - { - // Draw ellipse as a series of lines - // Since there's no direct ellipse function, we'll draw it as connected line segments - for (int i = 0; i < NUM_SEGMENTS; i++) - { - float angle1 = (i / (float)NUM_SEGMENTS) * 2.0f * (float)PI; - float angle2 = ((i + 1) / (float)NUM_SEGMENTS) * 2.0f * (float)PI; - - Vector3 local1(std::cos(angle1) * m_waypoint.radius1, 0.0f, std::sin(angle1) * m_waypoint.radius2); - Vector3 local2(std::cos(angle2) * m_waypoint.radius1, 0.0f, std::sin(angle2) * m_waypoint.radius2); - - Vector3 world1 = Vector3::Transform(local1, rotationMatrix) + centerPos; - Vector3 world2 = Vector3::Transform(local2, rotationMatrix) + centerPos; - - DrawDebugLine(world1, world2, drawColor, RendererDebugPage::CollisionStats); - } - break; - } - - case WayPointType::Square: - { - // Draw square as four connected lines - float r = m_waypoint.radius1; - Vector3 corners[5] = { - Vector3(-r, 0.0f, -r), - Vector3(r, 0.0f, -r), - Vector3(r, 0.0f, r), - Vector3(-r, 0.0f, r), - Vector3(-r, 0.0f, -r) // Close the loop - }; - - for (int i = 0; i < 4; i++) - { - Vector3 world1 = Vector3::Transform(corners[i], rotationMatrix) + centerPos; - Vector3 world2 = Vector3::Transform(corners[i + 1], rotationMatrix) + centerPos; - DrawDebugLine(world1, world2, drawColor, RendererDebugPage::CollisionStats); - } - break; - } - - case WayPointType::Rectangle: - { - // Draw rectangle as four connected lines - float r1 = m_waypoint.radius1; - float r2 = m_waypoint.radius2; - Vector3 corners[5] = { - Vector3(-r1, 0.0f, -r2), - Vector3(r1, 0.0f, -r2), - Vector3(r1, 0.0f, r2), - Vector3(-r1, 0.0f, r2), - Vector3(-r1, 0.0f, -r2) // Close the loop - }; - - for (int i = 0; i < 4; i++) - { - Vector3 world1 = Vector3::Transform(corners[i], rotationMatrix) + centerPos; - Vector3 world2 = Vector3::Transform(corners[i + 1], rotationMatrix) + centerPos; - DrawDebugLine(world1, world2, drawColor, RendererDebugPage::CollisionStats); - } - break; - } - - case WayPointType::Linear: - case WayPointType::Bezier: - { - // Draw path as connected line segments - // Find all waypoints with the same name - std::vector pathWaypoints; - for (const auto& wp : WayPoints) - { - if (wp.name == m_waypoint.name) - pathWaypoints.push_back(&wp); - } - - if (pathWaypoints.size() < 2) - { - // If there's only one waypoint, just draw a target - DrawDebugTarget(centerPos, orient, TARGET_RADIUS, drawColor, RendererDebugPage::CollisionStats); - break; - } - - // Sort by number - std::sort(pathWaypoints.begin(), pathWaypoints.end(), - [](const WAYPOINT* a, const WAYPOINT* b) { return a->number < b->number; }); - - // Draw lines between waypoints or interpolated path - constexpr int PATH_SEGMENTS = 50; - for (int i = 0; i < PATH_SEGMENTS; i++) - { - float alpha1 = i / (float)PATH_SEGMENTS; - float alpha2 = (i + 1) / (float)PATH_SEGMENTS; - - Vector3 pos1 = CalculateWayPointTransform(m_waypoint.name, alpha1, false).Position; - Vector3 pos2 = CalculateWayPointTransform(m_waypoint.name, alpha2, false).Position; - - DrawDebugLine(pos1, pos2, drawColor, RendererDebugPage::CollisionStats); - } - - // Draw markers at each waypoint position - for (const auto* wp : pathWaypoints) - { - Vector3 wpPos(wp->x, wp->y, wp->z); - DrawDebugSphere(wpPos, 32.0f, drawColor, RendererDebugPage::CollisionStats, true); - } - break; - } - } -} - diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h deleted file mode 100644 index 5c30322a86..0000000000 --- a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointObject.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "Scripting/Internal/TEN/Objects/NamedBase.h" -#include "Math/Math.h" - -struct WAYPOINT; - -namespace sol -{ - class state; -} -class Vec3; -class Rotation; - -class WayPointObject : public NamedBase -{ -public: - using IdentifierType = std::reference_wrapper; - WayPointObject(WAYPOINT& ref); - ~WayPointObject() = default; - - WayPointObject& operator=(WayPointObject const& other) = delete; - WayPointObject(WayPointObject const& other) = delete; - - static void Register(sol::table&); - - Vec3 GetPos() const; - void SetPos(Vec3 const& pos); - - std::string GetName() const; - void SetName(std::string const&); - - int GetType() const; - void SetType(int type); - - int GetNumber() const; - void SetNumber(int number); - - float GetRadius1() const; - void SetRadius1(float radius); - - float GetRadius2() const; - void SetRadius2(float radius); - - Vec3 GetPathPosition(float alpha, bool loop) const; - Rotation GetPathRotation(float alpha, bool loop) const; - - void Preview(sol::optional color) const; - -private: - WAYPOINT& m_waypoint; -}; diff --git a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointTypes.h b/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointTypes.h deleted file mode 100644 index c1e3efb02e..0000000000 --- a/TombEngine/Scripting/Internal/TEN/Objects/WayPoint/WayPointTypes.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "Game/waypoint.h" -#include -#include - -namespace TEN::Scripting -{ - /// Constants for waypoint types. - // To be used with @{Objects.WayPoint.GetType} and @{Objects.WayPoint.SetType} functions. - // @enum Objects.WayPointType - // @pragma nostrip - - static const auto WAYPOINT_TYPES = std::unordered_map - { - /// Single point, no radius. Only can have rotation. - // @mem POINT - { "POINT", WayPointType::Point }, - - /// Single point with radius1. Can have rotation, can be rotated on all 3 axes. - // @mem CIRCLE - { "CIRCLE", WayPointType::Circle }, - - /// Single point with two radii. Can have rotation, can be rotated on all 3 axes. - // @mem ELLIPSE - { "ELLIPSE", WayPointType::Ellipse }, - - /// Single point with radius (rendered as square). Can be rotated on all 3 axes. - // @mem SQUARE - { "SQUARE", WayPointType::Square }, - - /// Single point with two radii (rendered as rectangle). Can be rotated on all 3 axes. - // @mem RECTANGLE - { "RECTANGLE", WayPointType::Rectangle }, - - /// Multi-point linear path, each point on the path can have a rotation. - // @mem LINEAR - { "LINEAR", WayPointType::Linear }, - - /// Multi-point bezier path, each point on the path can have a rotation. - // @mem BEZIER - { "BEZIER", WayPointType::Bezier } - }; -} From 6ced7307e0f1f4db19b9e98eb070f065d124c06e Mon Sep 17 00:00:00 2001 From: TrainWrack <120750885+TrainWrack@users.noreply.github.com> Date: Sun, 25 Jan 2026 21:17:19 -0500 Subject: [PATCH 16/16] WIP --- TombEngine/Game/waypoint.cpp | 1 - TombEngine/Game/waypoint.h | 2 +- .../Scripting/Internal/ReservedScriptNames.h | 11 +-- .../Internal/TEN/WayPoint/WayPointHandler.cpp | 94 ++++++++++++------- .../Internal/TEN/WayPoint/WayPointHandler.h | 8 +- TombEngine/Specific/level.cpp | 2 +- 6 files changed, 71 insertions(+), 47 deletions(-) diff --git a/TombEngine/Game/waypoint.cpp b/TombEngine/Game/waypoint.cpp index f47a45f4d1..5b501d1c47 100644 --- a/TombEngine/Game/waypoint.cpp +++ b/TombEngine/Game/waypoint.cpp @@ -1,7 +1,6 @@ #include "framework.h" #include "waypoint.h" #include "Game/spotcam.h" -#include "Specific/logging.h" // Conversion constant: degrees to angle units (used in roll calculations) constexpr float DEGREES_TO_ANGLE_UNITS = 182.0444f; diff --git a/TombEngine/Game/waypoint.h b/TombEngine/Game/waypoint.h index f0648f7613..55a741ce51 100644 --- a/TombEngine/Game/waypoint.h +++ b/TombEngine/Game/waypoint.h @@ -27,12 +27,12 @@ struct WAYPOINT float rotationX; float rotationY; float roll; + unsigned short sequence; unsigned short number; int type; float radius1; float radius2; std::string name; - std::string luaName; }; extern std::vector WayPoints; diff --git a/TombEngine/Scripting/Internal/ReservedScriptNames.h b/TombEngine/Scripting/Internal/ReservedScriptNames.h index 9a8f0ab8c0..a83bcd9e61 100644 --- a/TombEngine/Scripting/Internal/ReservedScriptNames.h +++ b/TombEngine/Scripting/Internal/ReservedScriptNames.h @@ -497,13 +497,12 @@ constexpr char ScriptReserved_GetWayPointsByType[] = "GetWayPointsByType"; constexpr char ScriptReserved_GetPathPosition[] = "GetPathPosition"; constexpr char ScriptReserved_GetPathRotation[] = "GetPathRotation"; constexpr char ScriptReserved_GetType[] = "GetType"; -constexpr char ScriptReserved_SetType[] = "SetType"; constexpr char ScriptReserved_GetNumber[] = "GetNumber"; -constexpr char ScriptReserved_SetNumber[] = "SetNumber"; -constexpr char ScriptReserved_GetRadius1[] = "GetRadius1"; -constexpr char ScriptReserved_SetRadius1[] = "SetRadius1"; -constexpr char ScriptReserved_GetRadius2[] = "GetRadius2"; -constexpr char ScriptReserved_SetRadius2[] = "SetRadius2"; +constexpr char ScriptReserved_GetSequence[] = "GetSequence"; +constexpr char ScriptReserved_GetDimension1[] = "GetDimension1"; +constexpr char ScriptReserved_SetDimension1[] = "SetDimension1"; +constexpr char ScriptReserved_GetDimension2[] = "GetDimension2"; +constexpr char ScriptReserved_SetDimension2[] = "SetDimension2"; constexpr char ScriptReserved_Preview[] = "Preview"; // Static diff --git a/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.cpp b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.cpp index c277636bde..d7ce1304b4 100644 --- a/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.cpp +++ b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.cpp @@ -70,50 +70,40 @@ namespace TEN::Scripting::WayPoint // @treturn string The waypoint's name. ScriptReserved_GetName, &WayPointHandler::GetName, - /// Set the waypoint's name (its unique string identifier). - // @function WayPoint:SetName - // @tparam string name The waypoint's new name. - ScriptReserved_SetName, &WayPointHandler::SetName, - /// Get the waypoint's type. // @function WayPoint:GetType // @treturn int The waypoint's type identifier. ScriptReserved_GetType, &WayPointHandler::GetType, - /// Set the waypoint's type. - // @function WayPoint:SetType - // @tparam int type The waypoint's new type identifier. - ScriptReserved_SetType, &WayPointHandler::SetType, + /// Get the waypoint's number. + // @function WayPoint:GetSequence + // @treturn int The waypoint's sequence number. + ScriptReserved_GetSequence, & WayPointHandler::GetSequence, /// Get the waypoint's number. // @function WayPoint:GetNumber // @treturn int The waypoint's number. ScriptReserved_GetNumber, &WayPointHandler::GetNumber, - /// Set the waypoint's number. - // @function WayPoint:SetNumber - // @tparam int number The waypoint's new number. - ScriptReserved_SetNumber, &WayPointHandler::SetNumber, - /// Get the waypoint's radius1. // @function WayPoint:GetRadius1 // @treturn float The waypoint's radius1. - ScriptReserved_GetRadius1, &WayPointHandler::GetRadius1, + ScriptReserved_GetDimension1, &WayPointHandler::GetRadius1, /// Set the waypoint's radius1. // @function WayPoint:SetRadius1 // @tparam float radius The waypoint's new radius1. - ScriptReserved_SetRadius1, &WayPointHandler::SetRadius1, + ScriptReserved_SetDimension1, &WayPointHandler::SetRadius1, /// Get the waypoint's radius2. // @function WayPoint:GetRadius2 // @treturn float The waypoint's radius2. - ScriptReserved_GetRadius2, &WayPointHandler::GetRadius2, + ScriptReserved_GetDimension2, &WayPointHandler::GetRadius2, /// Set the waypoint's radius2. // @function WayPoint:SetRadius2 // @tparam float radius The waypoint's new radius2. - ScriptReserved_SetRadius2, &WayPointHandler::SetRadius2, + ScriptReserved_SetDimension2, &WayPointHandler::SetRadius2, /// Get an interpolated position along the waypoint path. // @function WayPoint:GetPathPosition @@ -152,19 +142,14 @@ namespace TEN::Scripting::WayPoint return m_waypoint->name; } - void WayPointHandler::SetName(const std::string& name) - { - m_waypoint->name = name; - } - int WayPointHandler::GetType() const { return m_waypoint->type; } - void WayPointHandler::SetType(int type) + int WayPointHandler::GetSequence() const { - m_waypoint->type = type; + return m_waypoint->sequence; } int WayPointHandler::GetNumber() const @@ -172,11 +157,6 @@ namespace TEN::Scripting::WayPoint return m_waypoint->number; } - void WayPointHandler::SetNumber(int number) - { - m_waypoint->number = number; - } - float WayPointHandler::GetRadius1() const { return m_waypoint->radius1; @@ -332,8 +312,8 @@ namespace TEN::Scripting::WayPoint float alpha1 = i / (float)PATH_SEGMENTS; float alpha2 = (i + 1) / (float)PATH_SEGMENTS; - Vector3 pos1 = CalculateWayPointTransform(m_waypoint->name, alpha1, false).Position; - Vector3 pos2 = CalculateWayPointTransform(m_waypoint->name, alpha2, false).Position; + Vector3 pos1 = CalculateWayPointTransform(m_waypoint->name, alpha1, false).Position.ToVector3(); + Vector3 pos2 = CalculateWayPointTransform(m_waypoint->name, alpha2, false).Position.ToVector3(); DrawDebugLine(pos1, pos2, drawColor, RendererDebugPage::CollisionStats); } @@ -353,7 +333,7 @@ namespace TEN::Scripting::WayPoint // @function GetWayPointByName // @tparam string name The waypoint name. // @treturn WayPoint The waypoint with the given name, or nil if not found. - std::unique_ptr GetWayPointByName(const std::string& name) + std::unique_ptr GetWayPointByNameAndNumber(const std::string& name) { // Find waypoint by name for (auto& wp : WayPoints) @@ -367,6 +347,54 @@ namespace TEN::Scripting::WayPoint return nullptr; } + std::unique_ptr GetWayPointBySequenceAndNumber(const std::string& name) + { + // Find waypoint by name + for (auto& wp : WayPoints) + { + if (wp.name == name) + { + return std::make_unique(name); + } + } + + return nullptr; + } + + sol::table GetWayPointsByName(sol::this_state s, int type) + { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int index = 1; + for (auto& wp : WayPoints) + { + if (wp.type == type) + { + result[index++] = std::make_unique(wp.name); + } + } + + return result; + } + + sol::table GetWayPointsBySequence(sol::this_state s, int type) + { + sol::state_view lua(s); + sol::table result = lua.create_table(); + + int index = 1; + for (auto& wp : WayPoints) + { + if (wp.type == type) + { + result[index++] = std::make_unique(wp.name); + } + } + + return result; + } + /// Get all waypoints with a specific type. // @function GetWayPointsByType // @tparam int type The waypoint type (use WayPointType enum). diff --git a/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.h b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.h index 70df97e275..03f4718bbd 100644 --- a/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.h +++ b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.h @@ -39,14 +39,12 @@ namespace TEN::Scripting::WayPoint void SetPosition(const Vec3& pos); std::string GetName() const; - void SetName(const std::string& name); int GetType() const; - void SetType(int type); - + + int GetSequence() const; int GetNumber() const; - void SetNumber(int number); - + float GetRadius1() const; void SetRadius1(float radius); diff --git a/TombEngine/Specific/level.cpp b/TombEngine/Specific/level.cpp index 76878e8a29..884ce994da 100644 --- a/TombEngine/Specific/level.cpp +++ b/TombEngine/Specific/level.cpp @@ -551,12 +551,12 @@ void LoadCameras() waypoint.rotationX = ReadFloat(); waypoint.rotationY = ReadFloat(); waypoint.roll = ReadFloat(); + waypoint.sequence = ReadUInt16(); waypoint.number = ReadUInt16(); waypoint.type = ReadInt32(); waypoint.radius1 = ReadFloat(); waypoint.radius2 = ReadFloat(); waypoint.name = ReadString(); - waypoint.luaName = ReadString(); WayPoints.push_back(waypoint); }