Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
810691e
Initial plan
Copilot Jan 24, 2026
ca26b61
Add WayPointInstance class with auto-naming, property form, and edito…
Copilot Jan 24, 2026
2bdb2c9
Add WayPoint serialization support for save/load functionality
Copilot Jan 24, 2026
4fe8bc6
Add WayPoint rendering support with path visualization
Copilot Jan 24, 2026
1391f22
Address code review feedback: improve pattern matching, disposal, and…
Copilot Jan 24, 2026
46bca0f
Restrict WayPoint to TombEngine and add compiler support
Copilot Jan 24, 2026
4a60133
Fix code formatting in waypoint duplicate detection
Copilot Jan 24, 2026
34ba61f
Add Circle/Ellipse shape support with Radius1 and Radius2 properties
Copilot Jan 24, 2026
7864eef
Fix compilation error: use LuaName property instead of TryGetOrDefault
Copilot Jan 24, 2026
c25a5c2
Refactor WayPoint to use single Type enum and update UI/serialization
Copilot Jan 24, 2026
c6e1426
Add circle, ellipse, square, and rectangle rendering for WayPoints
Copilot Jan 24, 2026
0957e3e
Fix compilation errors: update DrawWayPointShape rendering and tests,…
Copilot Jan 24, 2026
3e533e8
Implement batch type updates and selective compilation for WayPoints
Copilot Jan 24, 2026
0286387
Fix GraphicsDevice.Extensions compilation error in DrawWayPointShape
Copilot Jan 24, 2026
eae8208
Major refactor: Remove Sequence, use Name as identifier, disable fiel…
Copilot Jan 24, 2026
9015a21
Fix compilation errors: remove Sequence references and fix Size type …
Copilot Jan 24, 2026
d1d63c5
Fix remaining compilation errors: remove last Sequence reference and …
Copilot Jan 24, 2026
0fb2dd5
Fix crash: ensure name hash is positive and non-zero for GetRandomCol…
Copilot Jan 24, 2026
f378c86
Fix waypoint copy bugs: sync LuaName with Name, improve label format,…
Copilot Jan 24, 2026
1cdc2fc
Fix path preview and batch type update: use BaseName for comparisons
Copilot Jan 24, 2026
6246ea5
Increase waypoint shape thickness from 4.0f to 16.0f for better visib…
Copilot Jan 24, 2026
7e20ff9
Fix path preview, duplicate name validation, and add double-sided sha…
Copilot Jan 24, 2026
7363823
Enforce unique base names: prevent different types with same name unl…
Copilot Jan 24, 2026
1e10cda
Add scale gizmo, fix copy behavior, reset number on type change, and …
Copilot Jan 24, 2026
f08346d
Fix label
TrainWrack Jan 24, 2026
5eaa750
Fix compilation errors: add System.Numerics using and fix variable na…
Copilot Jan 24, 2026
4aac58f
Fix shape rendering: add RotationX to transformation matrix
Copilot Jan 25, 2026
b09d92d
Add back Sequence field as alternative waypoint identifier alongside …
Copilot Jan 25, 2026
5ba86f6
Add WayPoint button to toolbar (TombEngine only)
Copilot Jan 26, 2026
7525247
UpdateLogo
TrainWrack Jan 26, 2026
f9657ef
Add toolbar icon
TrainWrack Jan 26, 2026
1c198f4
Remove LuaName functionality from WayPoints - waypoints now use Name …
Copilot Jan 26, 2026
026ae2a
Fix WayPoint context menu and add rotation display
Copilot Jan 26, 2026
da368c1
fix bugs
TrainWrack Jan 26, 2026
6e1457f
Add sequence number uniqueness validation
Copilot Jan 26, 2026
f0a0089
Update Validation code for Sequence/Name and Number
TrainWrack Jan 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions TombEditor/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,13 @@ static CommandHandler()
args.Editor.Action = new EditorActionPlace(false, (l, r) => new FlybyCameraInstance(args.Editor.SelectedObject));
});

AddCommand("AddWayPoint", "Add waypoint", CommandType.Objects, delegate (CommandArgs args)
{
if (!EditorActions.VersionCheck(args.Editor.Level.IsTombEngine, "WayPoint"))
return;
args.Editor.Action = new EditorActionPlace(false, (l, r) => new WayPointInstance(args.Editor.SelectedObject));
});

AddCommand("AddSink", "Add sink", CommandType.Objects, delegate (CommandArgs args)
{
args.Editor.Action = new EditorActionPlace(false, (l, r) => new SinkInstance());
Expand Down
4 changes: 2 additions & 2 deletions TombEditor/Controls/ContextMenus/MaterialObjectContextMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class MaterialObjectContextMenu : BaseContextMenu
public MaterialObjectContextMenu(Editor editor, IWin32Window owner, ObjectInstance targetObject)
: base(editor, owner)
{
if (targetObject is IHasScriptID)
if (targetObject is IHasScriptID && !(targetObject is WayPointInstance))
{
if (_editor.Level.IsNG && targetObject == editor.SelectedObject)
{
Expand Down Expand Up @@ -137,7 +137,7 @@ public MaterialObjectContextMenu(Editor editor, IWin32Window owner, ObjectInstan
}));
}

if (targetObject is PositionAndScriptBasedObjectInstance && _editor.Level.Settings.GameVersion == TRVersion.Game.TombEngine)
if (targetObject is PositionAndScriptBasedObjectInstance && !(targetObject is WayPointInstance) && _editor.Level.Settings.GameVersion == TRVersion.Game.TombEngine)
{
Items.Add(new ToolStripMenuItem("Copy Lua name to clipboard", null, (o, e) =>
{
Expand Down
6 changes: 6 additions & 0 deletions TombEditor/Controls/ContextMenus/SectorContextMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ public SectorContextMenu(Editor editor, IWin32Window owner, Room targetRoom, Vec
EditorActions.PlaceObject(targetRoom, targetSector, new FlybyCameraInstance(editor.SelectedObject));
}));

if (editor.Level.IsTombEngine)
Items.Add(new ToolStripMenuItem("Add waypoint", Properties.Resources.objects_movie_projector_16, (o, e) =>
{
EditorActions.PlaceObject(targetRoom, targetSector, new WayPointInstance(editor.SelectedObject));
}));

Items.Add(new ToolStripMenuItem("Add sink", Properties.Resources.objects_tornado_16, (o, e) =>
{
EditorActions.PlaceObject(targetRoom, targetSector, new SinkInstance());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ public SelectedGeometryContextMenu(Editor editor, IWin32Window owner, Room targe
EditorActions.PlaceObject(targetRoom, targetSector, new FlybyCameraInstance(editor.SelectedObject));
}));

if (editor.Level.IsTombEngine)
Items.Add(new ToolStripMenuItem("Add waypoint", Properties.Resources.objects_movie_projector_16, (o, e) =>
{
EditorActions.PlaceObject(targetRoom, targetSector, new WayPointInstance(editor.SelectedObject));
}));

Items.Add(new ToolStripMenuItem("Add sink", Properties.Resources.objects_tornado_16, (o, e) =>
{
EditorActions.PlaceObject(targetRoom, targetSector, new SinkInstance());
Expand Down
2 changes: 2 additions & 0 deletions TombEditor/Controls/Panel3D/Panel3D.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public bool DisablePickingForHiddenRooms
private bool _drawHeightLine;
private Buffer<SolidVertex> _objectHeightLineVertexBuffer;
private Buffer<SolidVertex> _flybyPathVertexBuffer;
private Buffer<SolidVertex> _wayPointPathVertexBuffer;
private Buffer<SolidVertex> _ghostBlockVertexBuffer;
private Buffer<SolidVertex> _boxVertexBuffer;

Expand Down Expand Up @@ -217,6 +218,7 @@ protected override void Dispose(bool disposing)
_rasterizerWireframe?.Dispose();
_objectHeightLineVertexBuffer?.Dispose();
_flybyPathVertexBuffer?.Dispose();
_wayPointPathVertexBuffer?.Dispose();
_gizmo?.Dispose();
_sphere?.Dispose();
_cone?.Dispose();
Expand Down
206 changes: 206 additions & 0 deletions TombEditor/Controls/Panel3D/Panel3DDraw.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,19 @@ private void DrawFlybyPath(Effect effect)
effect.CurrentTechnique.Passes[0].Apply();
_legacyDevice.Draw(PrimitiveType.TriangleList, _flybyPathVertexBuffer.ElementCount);
}

// Add the path of waypoints
if (_editor.SelectedObject is WayPointInstance waypoint &&
AddWayPointPath(waypoint.BaseName))
{
_legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullNone);
_legacyDevice.SetVertexBuffer(_wayPointPathVertexBuffer);
_legacyDevice.SetVertexInputLayout(VertexInputLayout.FromBuffer(0, _wayPointPathVertexBuffer));
effect.Parameters["ModelViewProjection"].SetValue(_viewProjection.ToSharpDX());
effect.Parameters["Color"].SetValue(new Vector4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange for waypoints
effect.CurrentTechnique.Passes[0].Apply();
_legacyDevice.Draw(PrimitiveType.TriangleList, _wayPointPathVertexBuffer.ElementCount);
}
}

private void DrawSectorSplitHighlights(Effect effect)
Expand Down Expand Up @@ -1138,6 +1151,55 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis
DrawOrQueueServiceObject(instance, _littleCube, color, effect, sprites);
}

if (group.Key == typeof(WayPointInstance))
foreach (WayPointInstance instance in group)
{
_legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullBack);

var color = new Vector4(1.0f, 0.5f, 0.0f, 1.0f); // Orange color for waypoints

if (_editor.SelectedObject is WayPointInstance selectedWaypoint && selectedWaypoint.Name == instance.Name)
{
int nameHash = Math.Abs(instance.Name?.GetHashCode() ?? 1);
if (nameHash == 0) nameHash = 1;
color = MathC.GetRandomColorByIndex(nameHash, 32, 0.7f);
}

if (_highlightedObjects.Contains(instance))
{
color = _editor.Configuration.UI_ColorScheme.ColorSelection;
_legacyDevice.SetRasterizerState(_rasterizerWireframe);

if (_editor.SelectedObject == instance)
{
// Add text message with format: Name (Number) for multi-point, just Name for singular
string label = instance.IsSingularType() ?
$"{instance.BaseName} " :
$"{instance.BaseName} ({instance.Number}) ";

string rotationInfo = GetObjectRotationString(instance.Room, instance);
if (!string.IsNullOrEmpty(rotationInfo))
rotationInfo = "\n" + rotationInfo;

textToDraw.Add(CreateTextTagForObject(
instance.RotationPositionMatrix * _viewProjection,
label +
GetObjectPositionString(instance.Room, instance) + rotationInfo + GetObjectTriggerString(instance)));

// Add the line height of the object
AddObjectHeightLine(instance.Room, instance.Position);
}
}

DrawOrQueueServiceObject(instance, _littleCube, color, effect, sprites);

// Draw shape for shape types (Circle, Ellipse, Square, Rectangle)
if (instance.RequiresRadius())
{
DrawWayPointShape(instance, color);
}
}

if (group.Key == typeof(MemoInstance))
foreach (MemoInstance instance in group)
{
Expand Down Expand Up @@ -1424,6 +1486,150 @@ private void DrawOrQueueServiceObject(ISpatial instance, GeometricPrimitive prim
_legacyDevice.DrawIndexed(PrimitiveType.TriangleList, primitive.IndexBuffer.ElementCount);
}

private void DrawWayPointShape(WayPointInstance instance, Vector4 color)
{
// Get world position
Vector3 position = instance.Position + instance.Room.WorldPos;

// Create transformation matrix for the shape orientation
// Apply rotations in order: X, Y, Z (Roll)
Matrix4x4 rotation = Matrix4x4.CreateRotationX(instance.RotationX * (float)Math.PI / 180.0f) *
Matrix4x4.CreateRotationY(instance.RotationY * (float)Math.PI / 180.0f) *
Matrix4x4.CreateRotationZ(instance.Roll * (float)Math.PI / 180.0f);

// Number of segments for circles/ellipses
int segments = 32;
var points = new List<Vector3>();

switch (instance.Type)
{
case WayPointType.Circle:
// Draw circle with Radius1
for (int i = 0; i <= segments; i++)
{
float angle = (i / (float)segments) * 2.0f * (float)Math.PI;
float x = (float)Math.Cos(angle) * instance.Radius1;
float z = (float)Math.Sin(angle) * instance.Radius1;
Vector3 point = Vector3.Transform(new Vector3(x, 0, z), rotation) + position;
points.Add(point);
}
break;

case WayPointType.Ellipse:
// Draw ellipse with Radius1 and Radius2
for (int i = 0; i <= segments; i++)
{
float angle = (i / (float)segments) * 2.0f * (float)Math.PI;
float x = (float)Math.Cos(angle) * instance.Radius1;
float z = (float)Math.Sin(angle) * instance.Radius2;
Vector3 point = Vector3.Transform(new Vector3(x, 0, z), rotation) + position;
points.Add(point);
}
break;

case WayPointType.Square:
// Draw square with Radius1
{
float r = instance.Radius1;
Vector3[] corners = new Vector3[]
{
new Vector3(-r, 0, -r),
new Vector3(r, 0, -r),
new Vector3(r, 0, r),
new Vector3(-r, 0, r),
new Vector3(-r, 0, -r) // Close the loop
};
foreach (var corner in corners)
{
points.Add(Vector3.Transform(corner, rotation) + position);
}
}
break;

case WayPointType.Rectangle:
// Draw rectangle with Radius1 and Radius2
{
float r1 = instance.Radius1;
float r2 = instance.Radius2;
Vector3[] corners = new Vector3[]
{
new Vector3(-r1, 0, -r2),
new Vector3(r1, 0, -r2),
new Vector3(r1, 0, r2),
new Vector3(-r1, 0, r2),
new Vector3(-r1, 0, -r2) // Close the loop
};
foreach (var corner in corners)
{
points.Add(Vector3.Transform(corner, rotation) + position);
}
}
break;
}

// Convert points to line vertices using the same approach as flyby paths
if (points.Count > 1)
{
var vertices = new List<SolidVertex>();
float th = 16.0f; // Line thickness (increased for better visibility)

for (int i = 0; i < points.Count - 1; i++)
{
var linePoints = new List<Vector3[]>()
{
new Vector3[]
{
points[i],
new Vector3(points[i].X + th, points[i].Y + th, points[i].Z + th),
new Vector3(points[i].X - th, points[i].Y + th, points[i].Z + th)
},
new Vector3[]
{
points[i + 1],
new Vector3(points[i + 1].X + th, points[i + 1].Y + th, points[i + 1].Z + th),
new Vector3(points[i + 1].X - th, points[i + 1].Y + th, points[i + 1].Z + th)
}
};

// Add triangles to form the line segment (both sides for double-sided rendering)
for (int k = 0; k < _flybyPathIndices.Count; k++)
{
var v = new SolidVertex();
v.Position = linePoints[_flybyPathIndices[k].Y][_flybyPathIndices[k].X];
v.Color = color;
vertices.Add(v);
}

// Add reversed triangles for the back side
for (int k = _flybyPathIndices.Count - 1; k >= 0; k--)
{
var v = new SolidVertex();
v.Position = linePoints[_flybyPathIndices[k].Y][_flybyPathIndices[k].X];
v.Color = color;
vertices.Add(v);
}
}

// Create temporary vertex buffer for this shape
if (vertices.Count > 0)
{
var shapeBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice,
vertices.ToArray(), SharpDX.Direct3D11.ResourceUsage.Dynamic);

// Draw the shape
var effect = DeviceManager.DefaultDeviceManager.___LegacyEffects["Solid"];
effect.Parameters["ModelViewProjection"].SetValue(_viewProjection.ToSharpDX());
effect.Parameters["Color"].SetValue(color);
effect.Techniques[0].Passes[0].Apply();
_legacyDevice.SetVertexBuffer(shapeBuffer);
_legacyDevice.SetVertexInputLayout(VertexInputLayout.FromBuffer(0, shapeBuffer));
_legacyDevice.Draw(PrimitiveType.TriangleList, vertices.Count);

shapeBuffer.Dispose();
}
}
}

private void DrawCardinalDirections(List<Text> textToDraw)
{
string[] messages;
Expand Down
Loading