Skip to content

Conversation

Copy link

Copilot AI commented Jan 25, 2026

Checklist

Links to issue(s) this pull request concerns (if applicable)

n/a

Description of pull request

Adds Lua-accessible waypoint navigation API with dedicated WAYPOINT structure, WayPointType enum, path calculation supporting multiple path types, and debug visualization, completely separate from the flyby camera (SPOTCAM) system. Waypoints are implemented as a separate namespace (TEN.WayPoint) following the pattern of Collision and other standalone systems.

Core Implementation

  • WAYPOINT structure - New dedicated data structure with position, room, rotations, number, type, radius fields, name, and luaName
  • WayPointType enum - Seven waypoint types: Point, Circle, Ellipse, Square, Rectangle (singular types), Linear, Bezier (path types)
  • waypoint.h/.cpp - Separate module for waypoint management with CalculateWayPointTransform() function
  • WayPointHandler class - Lua interface in separate TEN.WayPoint namespace, accessed by waypoint name directly
  • Level loading - Reads waypoints from binary format after flyby cameras, no individual name registration needed
  • Preview method - Debug visualization for all waypoint types using existing debug drawing functions

Data Structure (matches Tomb Editor)

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, y, z, roomNumber;
    float rotationX, rotationY, roll;
    ushort number;
    int type;
    float radius1, radius2;  // For Circle, Ellipse, Square, Rectangle shapes
    string name, luaName;
}

API Surface

-- Type enum (exported to Lua)
local wpType = TEN.WayPoint.WayPointType.LINEAR

-- Create waypoint handler by name (like Collision constructor)
local wp = TEN.WayPoint.WayPoint("patrol_path")

-- Static retrieval functions
local wp = TEN.WayPoint.GetWayPointByName("patrol_path")
local wps = TEN.WayPoint.GetWayPointsByType(wpType)

-- Properties
local pos = wp:GetPosition()
local type = wp:GetType()
local number = wp:GetNumber()
local radius1 = wp:GetRadius1()
local radius2 = wp:GetRadius2()

-- Path interpolation (for all types)
local pos = wp:GetPathPosition(0.5, true)  -- alpha [0,1], loop bool
local rot = wp:GetPathRotation(0.5, true)  -- rotation interpolated along path

-- Shape perimeter positions (for singular types)
local pos = circleWP:GetPathPosition(0.0, false)   -- 12 o'clock position
local pos = circleWP:GetPathPosition(0.25, false)  -- 3 o'clock position
local pos = circleWP:GetPathPosition(0.5, false)   -- 6 o'clock position
local pos = circleWP:GetPathPosition(0.75, false)  -- 9 o'clock position

-- Debug visualization
wp:Preview()  -- Default orange color
wp:Preview(Color(1, 0, 0, 1))  -- Custom red color

Technical Notes

  • Separate namespace: Waypoints use TEN.WayPoint namespace (not TEN.Objects), following Collision pattern
  • Direct name access: Waypoints accessed by their name field directly, no luaName registration needed
  • No duplicate issues: Since waypoints aren't registered individually, luaName duplicates are not a concern
  • Separate from SPOTCAM: Waypoints have their own structure, loading mechanism, and storage (std::vector<WAYPOINT>)
  • Name-based grouping: Path calculation groups waypoints by name, sorts by number
  • Type-aware calculation:
    • Point: Returns center position (no shape)
    • Circle: Alpha interpolates around circle perimeter starting at 12 o'clock (top), rotating clockwise. Alpha 0.25 = 3 o'clock (x=radius, z=0)
    • Ellipse: Alpha interpolates around ellipse with radius1 (x-axis) and radius2 (z-axis), starting at 12 o'clock, rotating clockwise
    • Square: Alpha divides perimeter into 4 equal segments (0.25 per side), starting at top edge, moving clockwise
    • Rectangle: Alpha interpolates around perimeter weighted by side lengths for uniform speed, starting at top edge, moving clockwise
    • Linear type: Uses linear interpolation between consecutive waypoints
    • Bezier type: Uses Catmull-Rom spline interpolation for smooth curves
  • Alpha rotation mapping: All shapes start at 12 o'clock (alpha 0.0) and rotate clockwise: 0.25 = 3 o'clock, 0.5 = 6 o'clock, 0.75 = 9 o'clock
  • Shape transformation: All singular type positions properly transformed by rotationX, rotationY, and roll (applied in order: X → Y → Z) matching editor's rendering
  • Rotation interpolation: Path types properly interpolate rotationX, rotationY, and roll
  • Loop support: Seamless boundary blending with 10% blend range for continuous paths
  • Error handling: Validates waypoint count on load (0 to MAX_WAYPOINTS)
  • Debug visualization: Preview method renders waypoints to RendererDebugPage::CollisionStats
    • Point: Target marker (128 radius)
    • Circle: Wireframe sphere with radius1
    • Ellipse: 32 connected line segments forming ellipse shape
    • Square/Rectangle: Four connected lines forming perimeter
    • Linear/Bezier: Interpolated path (50 segments) with sphere markers at waypoint positions

Files

New: waypoint.h/.cpp, WayPointHandler.h/.cpp, WayPointTypes.h
Modified: ReservedScriptNames.h, ScriptInterfaceState.cpp, level.cpp, TombEngine.vcxproj

Binary Format

Waypoints are loaded after flyby cameras in the level file with the following format per waypoint:

  • Position (x, y, z), room number (4 x int32)
  • Rotations: rotationX, rotationY, roll (3 x float)
  • Number (uint16), type (int32)
  • Radius1, radius2 (2 x float)
  • Name, LuaName (2 x UTF-8 strings)

This implementation fully matches the TombEngine Tomb Editor integration (PR TombEngine#1112) for complete editor-engine compatibility, including proper type handling, shape perimeter calculation with correct alpha rotation mapping starting at 12 o'clock, path interpolation for all seven waypoint types, and debug visualization support. Waypoints are accessible via a clean namespace API similar to Collision, avoiding name registration conflicts.

Original prompt

Create a new TEN Lua class for reading and interacting with waypoint objects. This class should:

  1. Load all waypoint objects into a list.
  2. Provide methods for retrieving waypoints by name (GetWayPointByName) and by type (GetWayPointsByType).
  3. Include a feature to calculate the path, similar to the existing GetCameraTransform function, to compute the orientation and position of a waypoint at a specific alpha.
  4. Implement a loop boolean to handle continuous path traversal, akin to the behavior in GetCameraTransform.

The Lua class must be structured, efficient, and align with the existing codebase, while maintaining clarity and reusability.

This pull request was created from Copilot chat.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits January 25, 2026 03:14
Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Copilot AI changed the title [WIP] Add TEN Lua class for interacting with waypoint objects Add WayPoint Lua class for navigation and path interpolation Jan 25, 2026
Copilot AI requested a review from TrainWrack January 25, 2026 03:20
@TrainWrack
Copy link
Owner

@copilot Not sure why its being called SpotCam. Its not a spotcam its a way point. Also we do not need targets functions. Its not a camera.
This is the PR that adds the functionality to level compiler. The formula to preview the path in the editor should exactly be the same to calculate the position and rotation in the editor. We would need the waypoint to use its own calculation function and not rely on flyby path. That was to be used as a reference.

TombEngine/Tomb-Editor#1112

Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 25, 2026

@copilot Not sure why its being called SpotCam. Its not a spotcam its a way point. Also we do not need targets functions. Its not a camera.
This is the PR that adds the functionality to level compile...

Removed target functions and created separate CalculateWayPointTransform function that calculates orientation from path tangent instead of using camera targets (commit d0a9aa5).

For the level loading: Currently using SPOTCAM structure as temporary implementation. To properly separate waypoints from cameras, I would need:

  1. The waypoint data structure definition from the Tomb Editor PR
  2. The binary format/chunk ID for waypoint data
  3. Confirmation on whether waypoints should use a completely new structure or if they share the SPOTCAM format but are loaded separately

Could you provide the waypoint structure from the editor PR or indicate if waypoints should have their own dedicated data structure in the level file format?

@TrainWrack
Copy link
Owner

@copilot Can't you review the PR I had linked? It lists all the changes for WayPoints including the preview function.

They should use a separate structure and a list.

TombEngine/Tomb-Editor#1112

Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 25, 2026

@copilot Can't you review the PR I had linked? It lists all the changes for WayPoints including the preview function.

They should use a separate structure and a list....

Created separate WAYPOINT structure and loading mechanism per the Tomb Editor PR (commit a913297):

New Structure:

  • Matches TombEngineWayPoint from editor with all fields: position, room, rotations, number, type, radius1/2, name, luaName
  • Stored in std::vector<WAYPOINT> WayPoints
  • Separate from SPOTCAM (flyby cameras)

Level Loading:

  • Reads waypoints after flyby cameras
  • Loads all fields including name and LuaName
  • Registers with script engine using LuaName (if set) or Name

Path Calculation:

  • CalculateWayPointTransform() uses name-based grouping
  • Groups waypoints by name, sorts by number
  • Uses same Catmull-Rom spline as editor preview

API:

  • GetNumber/SetNumber, GetRadius1/2, SetRadius1/2 added
  • GetPathPosition/GetPathRotation work with named waypoint groups

Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Copilot AI changed the title Add WayPoint Lua class for navigation and path interpolation Add WayPoint Lua class with dedicated structure for navigation and path interpolation Jan 25, 2026
@TrainWrack
Copy link
Owner

@copilot Yes, this is good. The only thing that needs to be updated is add an internal enum to waypoint.h then add export that same enum to lua for type.

Plus I believe the calculation for CalculateWayPointTransform is still wrong.

Basically the types are as follows:

public enum WayPointType
{
Point, // Single point, no radius Only can have rotation
Circle, // Single point with radius1, Can have rotation, can be rotated on all3 axis
Ellipse, // Single point with two radii, can have rotation, can be rotated on all3 axis
Square, // Single point with radius (rendered as square) can be rotated on all3 axis
Rectangle, // Single point with two radii (rendered as rectangle), can be rotated on all3 axis
Linear, // Multi-point linear path, each point on the path can have a rotation
Bezier // Multi-point bezier path, each point on the path can have a rotation
}

Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 25, 2026

@copilot Yes, this is good. The only thing that needs to be updated is add an internal enum to waypoint.h then add export that same enum to lua for type.

Plus I believe the calculation for Calculate...

Added WayPointType enum and fixed path calculation (commit 9ed4713):

Enum Added:

  • Created WayPointType enum in waypoint.h with all 7 types (Point, Circle, Ellipse, Square, Rectangle, Linear, Bezier)
  • Exported to Lua as Objects.WayPointType with values: POINT, CIRCLE, ELLIPSE, SQUARE, RECTANGLE, LINEAR, BEZIER

Path Calculation Fixed:

  • Singular types (Point/Circle/Ellipse/Square/Rectangle): Return position and rotation directly from waypoint data
  • Linear type: Uses linear interpolation between consecutive waypoints with proper rotation interpolation
  • Bezier type: Uses Catmull-Rom spline for smooth curves with interpolated rotations
  • All types support loop parameter with blending at boundaries
  • Rotations properly converted from degrees to radians using rotationX, rotationY, and roll fields

… navigation

Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Copilot AI changed the title Add WayPoint Lua class with dedicated structure for navigation and path interpolation Add WayPoint Lua class with type enum and proper path calculation for navigation Jan 25, 2026
@TrainWrack
Copy link
Owner

@copilot For singular types if we call the CalculateWayPointTransform it should calculate the position on the shape being generated by the singular type. I am not sure where the 0 position should be maybe it can be local x = radius and z = 0 and rotating clockwise. So for singular waypoint of circle type if I call CalculateWayPointTransform at 0.25 alpha

It should give me the position of the circle at 3 o' clock

Use the same logic to calculate rectangle, elipse and square as well

Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 25, 2026

@copilot For singular types if we call the CalculateWayPointTransform it should calculate the position on the shape being generated by the singular type. I am not sure where the 0 position should be m...

Implemented shape perimeter position calculation for singular types (commit 8ceb3ed):

Circle & Ellipse:

  • Alpha 0.0 starts at (radius, 0, 0) - 3 o'clock position
  • Rotates clockwise: 0.25 = 12 o'clock, 0.5 = 9 o'clock, 0.75 = 6 o'clock
  • Uses cos(alpha * 2π) for x, sin(alpha * 2π) for z

Square:

  • Alpha divides perimeter into 4 equal segments (0.25 per side)
  • Starts at (r, 0, -r) and moves clockwise around corners

Rectangle:

  • Alpha weighted by side lengths for uniform speed around perimeter
  • Starts at (r1, 0, -r2) and moves clockwise

All positions properly transformed by waypoint's rotationX, rotationY, and roll before being added to center position. Matches editor's transformation order: X → Y → Z(Roll).

Copilot AI changed the title Add WayPoint Lua class with type enum and proper path calculation for navigation Add WayPoint Lua class with type enum and shape perimeter calculation for navigation Jan 25, 2026
@TrainWrack
Copy link
Owner

@copilot Its actually Alpha 0.25 = local x = radius, z = 0 (3 o'clock position) Alpha 0= 12 o'clock Alpha 0.5 = 6 o'clock Alpha 0.75 = 9 o'clock Rotating clockwise

Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 25, 2026

@copilot Its actually Alpha 0.25 = local x = radius, z = 0 (3 o'clock position) Alpha 0= 12 o'clock Alpha 0.5 = 6 o'clock Alpha 0.75 = 9 o'clock Rotating clockwise

Fixed alpha rotation mapping to start at 12 o'clock (commit 0bc8bee):

Circle & Ellipse:

  • Alpha 0.0 = 12 o'clock (top)
  • Alpha 0.25 = 3 o'clock (right, x=radius, z=0)
  • Alpha 0.5 = 6 o'clock (bottom)
  • Alpha 0.75 = 9 o'clock (left)
  • Angle offset by -π/2 to start at top

Square:

  • Alpha 0.0 starts at top-left corner
  • Moves clockwise along edges: top → right → bottom → left
  • Each side is 0.25 of total perimeter

Rectangle:

  • Alpha 0.0 starts at top-left corner
  • Moves clockwise with distance weighted by side lengths
  • Uniform speed around entire perimeter

@TrainWrack
Copy link
Owner

@copilot Can you add a :Preview method to lua class. If its a point you can use
DrawDebugTarget(target, Quaternion::Identity, TARGET_RADIUS, COLOR, RendererDebugPage::CollisionStats);

For rectangle, square you can use
DrawDebugLine(_origin, target, COLOR, RendererDebugPage::CollisionStats);

For squre, elipse I am not sure a DrawDebugCircle exists search the codebase for it, if it doesn't please add it

For Linear path use
DrawDebugLine(_origin, target, COLOR, RendererDebugPage::CollisionStats);

Not sure if Bezier path exists, if it doesn't please add it

Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 25, 2026

@copilot Can you add a :Preview method to lua class. If its a point you can use
DrawDebugTarget(target, Quaternion::Identity, TARGET_RADIUS, COLOR, RendererDebugPage::CollisionStats);
...

Added Preview() method for waypoint debug visualization (commit 4d967cf):

Implementation:

  • Point: DrawDebugTarget with 128 radius
  • Circle: DrawDebugSphere with radius1
  • Ellipse: Connected line segments forming ellipse shape (32 segments)
  • Square: Four DrawDebugLine calls forming square perimeter
  • Rectangle: Four DrawDebugLine calls forming rectangle perimeter
  • Linear/Bezier: Interpolated path with 50 segments + sphere markers at each waypoint

All shapes properly transformed by rotationX, rotationY, roll. Default color is orange, optional color parameter supported. Renders to RendererDebugPage::CollisionStats.

Note: Used DrawDebugSphere for Circle (no separate DrawDebugCircle exists). For Ellipse, implemented custom rendering with line segments since no ellipse primitive exists.

Copilot AI changed the title Add WayPoint Lua class with type enum and shape perimeter calculation for navigation Add WayPoint Lua class with type enum, shape perimeter calculation, and debug visualization Jan 25, 2026
@TrainWrack
Copy link
Owner

@copilot I was thinking about it last night. I think adding waypoints to ObjectHandler is a mistake. For lua class we can make it a separate type like collision class with its own constructors that access the waypoint based on its std::string name in TombEngine/Game/waypoint.h:21 or its type to get a lua table of all the waypoints based on its type TombEngine/Game/waypoint.h:31. This way we do not have to deal with luaName duplicates as well.

Co-authored-by: TrainWrack <120750885+TrainWrack@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 25, 2026

@copilot I was thinking about it last night. I think adding waypoints to ObjectHandler is a mistake. For lua class we can make it a separate type like collision class with its own constructors that ac...

Refactored WayPoint as separate namespace following the Collision class pattern (commit 4c804f9):

Changes:

  • Created TEN::Scripting::WayPoint namespace
  • WayPointHandler class with constructor taking waypoint name
  • Static functions GetWayPointByName() and GetWayPointsByType()
  • Removed from ObjectsHandler registration
  • Removed luaName/name registration during level loading
  • Registered at same level as Collision, Effects, etc.

New Usage:

-- Constructor
local wp = TEN.WayPoint.WayPoint("patrol_path")

-- Static access
local wp = TEN.WayPoint.GetWayPointByName("patrol_path")
local wps = TEN.WayPoint.GetWayPointsByType(TEN.WayPoint.WayPointType.LINEAR)

No more luaName duplicate issues - waypoints are accessed directly by their name field without ObjectsHandler name map.

Copilot AI changed the title Add WayPoint Lua class with type enum, shape perimeter calculation, and debug visualization Add WayPoint Lua namespace with type enum, shape perimeter calculation, and debug visualization Jan 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants