diff --git a/TombEngine/Game/waypoint.cpp b/TombEngine/Game/waypoint.cpp new file mode 100644 index 0000000000..5b501d1c47 --- /dev/null +++ b/TombEngine/Game/waypoint.cpp @@ -0,0 +1,326 @@ +#include "framework.h" +#include "waypoint.h" +#include "Game/spotcam.h" + +// Conversion constant: degrees to angle units (used in roll calculations) +constexpr float DEGREES_TO_ANGLE_UNITS = 182.0444f; + +std::vector WayPoints; + +void ClearWayPoints() +{ + WayPoints.clear(); +} + +// 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; + 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; + } + + // Check if this is a singular type waypoint (Point, Circle, etc) + WayPointType waypointType = static_cast(pathWaypoints[0]->type); + if (IsSingularType(waypointType)) + { + const WAYPOINT* wp = pathWaypoints[0]; + Vector3 centerPosition(wp->x, wp->y, wp->z); + + // 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 = 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; + break; + } + + case WayPointType::Ellipse: + { + // Ellipse with radius1 (x-axis) and radius2 (z-axis) + // 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; + break; + } + + case WayPointType::Square: + { + // Square with radius1 (half-width/height) + // 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; + float t = segment - side; + + switch (side) + { + 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 2: // Bottom edge: 6 to 9 o'clock (moving right to left along bottom) + localPosition = Vector3(Lerp(r, -r, t), 0.0f, r); + break; + default: // Left edge: 9 to 12 o'clock (moving bottom to top along left) + localPosition = Vector3(-r, 0.0f, Lerp(r, -r, t)); + break; + } + break; + } + + 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 < 2.0f * r1) + { + // 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 < 2.0f * r1 + 2.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 < 4.0f * r1 + 2.0f * r2) + { + // 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 + { + // 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; + } + } + + // 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(worldPosition, 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); + 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(); + + // Handle Linear path type + if (waypointType == WayPointType::Linear) + { + // 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_RANGE) * 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); + } + + // Handle Bezier path type (using Catmull-Rom spline for smooth curves) + if (waypointType == WayPointType::Bezier) + { + 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) + { + // 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; + // Index offset by 1 because vector has padding at start + return Lerp(rot[idx + 1], rot[idx + 2], localT); + }; + + 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_RANGE) * 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); + } + + // 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 new file mode 100644 index 0000000000..55a741ce51 --- /dev/null +++ b/TombEngine/Game/waypoint.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include "Math/Math.h" + +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; + int y; + int z; + int roomNumber; + float rotationX; + float rotationY; + float roll; + unsigned short sequence; + unsigned short number; + int type; + float radius1; + float radius2; + std::string name; +}; + +extern std::vector WayPoints; + +void ClearWayPoints(); +Pose CalculateWayPointTransform(const std::string& name, float alpha, bool loop); diff --git a/TombEngine/Scripting/Internal/ReservedScriptNames.h b/TombEngine/Scripting/Internal/ReservedScriptNames.h index 923bc35d85..a83bcd9e61 100644 --- a/TombEngine/Scripting/Internal/ReservedScriptNames.h +++ b/TombEngine/Scripting/Internal/ReservedScriptNames.h @@ -488,6 +488,23 @@ constexpr char ScriptReserved_SinkSetName[] = "SetName"; constexpr char ScriptReserved_SinkSetPosition[] = "SetPosition"; 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"; +constexpr char ScriptReserved_GetPathRotation[] = "GetPathRotation"; +constexpr char ScriptReserved_GetType[] = "GetType"; +constexpr char ScriptReserved_GetNumber[] = "GetNumber"; +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 constexpr char ScriptReserved_Static[] = "Static"; 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/WayPoint/WayPointHandler.cpp b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.cpp new file mode 100644 index 0000000000..d7ce1304b4 --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.cpp @@ -0,0 +1,441 @@ +#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, + + /// Get the waypoint's type. + // @function WayPoint:GetType + // @treturn int The waypoint's type identifier. + ScriptReserved_GetType, &WayPointHandler::GetType, + + /// 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, + + /// Get the waypoint's radius1. + // @function WayPoint:GetRadius1 + // @treturn float The waypoint's radius1. + ScriptReserved_GetDimension1, &WayPointHandler::GetRadius1, + + /// Set the waypoint's radius1. + // @function WayPoint:SetRadius1 + // @tparam float radius The waypoint's new radius1. + ScriptReserved_SetDimension1, &WayPointHandler::SetRadius1, + + /// Get the waypoint's radius2. + // @function WayPoint:GetRadius2 + // @treturn float The waypoint's radius2. + ScriptReserved_GetDimension2, &WayPointHandler::GetRadius2, + + /// Set the waypoint's radius2. + // @function WayPoint:SetRadius2 + // @tparam float radius The waypoint's new radius2. + ScriptReserved_SetDimension2, &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; + } + + int WayPointHandler::GetType() const + { + return m_waypoint->type; + } + + int WayPointHandler::GetSequence() const + { + return m_waypoint->sequence; + } + + int WayPointHandler::GetNumber() const + { + return m_waypoint->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.ToVector3(); + Vector3 pos2 = CalculateWayPointTransform(m_waypoint->name, alpha2, false).Position.ToVector3(); + + 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 GetWayPointByNameAndNumber(const std::string& name) + { + // Find waypoint by name + for (auto& wp : WayPoints) + { + if (wp.name == name) + { + return std::make_unique(name); + } + } + + 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). + // @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..03f4718bbd --- /dev/null +++ b/TombEngine/Scripting/Internal/TEN/WayPoint/WayPointHandler.h @@ -0,0 +1,65 @@ +#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; + + int GetType() const; + + int GetSequence() const; + int GetNumber() const; + + 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 b198694909..884ce994da 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" @@ -527,6 +528,39 @@ void LoadCameras() if (NumberSpotcams != 0) ReadBytes(SpotCam, NumberSpotcams * sizeof(SPOTCAM)); + // Load waypoints + 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); + + 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.sequence = ReadUInt16(); + waypoint.number = ReadUInt16(); + waypoint.type = ReadInt32(); + waypoint.radius1 = ReadFloat(); + waypoint.radius2 = ReadFloat(); + waypoint.name = ReadString(); + + WayPoints.push_back(waypoint); + } + int sinkCount = ReadCount(); TENLog("Sink count: " + std::to_string(sinkCount), LogLevel::Info); diff --git a/TombEngine/TombEngine.vcxproj b/TombEngine/TombEngine.vcxproj index b25f151112..8156c830c9 100644 --- a/TombEngine/TombEngine.vcxproj +++ b/TombEngine/TombEngine.vcxproj @@ -495,6 +495,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. + @@ -917,6 +918,8 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. + + @@ -1058,6 +1061,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. + @@ -1369,6 +1373,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings. + 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)\