Skip to content

Commit f42910f

Browse files
committed
Construction lines at object move
1 parent 28e061d commit f42910f

File tree

3 files changed

+263
-61
lines changed

3 files changed

+263
-61
lines changed

Source/Contrib/TrackViewer/UserInterface/SceneView.xaml

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,38 @@
1010
<CommandBinding Command="Undo" Executed="UndoCommand" CanExecute="UndoRedoCanExecute"/>
1111
<CommandBinding Command="Redo" Executed="RedoCommand" CanExecute="UndoRedoCanExecute"/>
1212
<CommandBinding Command="Stop" Executed="CancelCommand"/>
13-
<CommandBinding Command="MoveLeft" Executed="RotateCommand"/>
14-
<CommandBinding Command="MoveRight" Executed="MoveCommand"/>
15-
<CommandBinding Command="MoveUp" Executed="MoveHandleCommand"/>
13+
<CommandBinding Command="{x:Static local:SceneView.Rotate}" Executed="RotateCommand"/>
14+
<CommandBinding Command="{x:Static local:SceneView.Move}" Executed="MoveCommand"/>
15+
<CommandBinding Command="{x:Static local:SceneView.MoveHandle}" Executed="MoveHandleCommand"/>
16+
<CommandBinding Command="{x:Static local:SceneView.ToggleOrtho}" Executed="ToggleOrthoCommand"/>
17+
<CommandBinding Command="{x:Static local:SceneView.ToggleElevationLock}" Executed="ToggleElevationLockCommand"/>
18+
<CommandBinding Command="{x:Static local:SceneView.ToggleObjectSnap}" Executed="ToggleObjectSnapCommand"/>
1619
</Window.CommandBindings>
1720
<Window.InputBindings>
18-
<KeyBinding Key="Esc" Command="Stop"/><!--This doesn't work-->
19-
<KeyBinding Key="M" Command="MoveRight"/>
20-
<KeyBinding Key="R" Command="MoveLeft"/>
21-
<KeyBinding Key="H" Command="MoveUp"/>
21+
<KeyBinding Key="Esc" Command="Stop"/><!--This one doesn't work, Escape is eaten somewhere, need to workaround it in code-->
22+
<KeyBinding Key="R" Command="{x:Static local:SceneView.Rotate}"/>
23+
<KeyBinding Key="M" Command="{x:Static local:SceneView.Move}"/>
24+
<KeyBinding Key="H" Command="{x:Static local:SceneView.MoveHandle}"/>
25+
<KeyBinding Key="F3" Command="{x:Static local:SceneView.ToggleObjectSnap}"/>
26+
<KeyBinding Key="F7" Command="{x:Static local:SceneView.ToggleElevationLock}"/>
27+
<KeyBinding Key="F8" Command="{x:Static local:SceneView.ToggleOrtho}"/>
2228
</Window.InputBindings>
2329
<DockPanel LastChildFill="True">
2430
<Menu DockPanel.Dock="Top" Height="22" Name="menuMain" Width="Auto">
2531
<MenuItem Header="_File">
2632
<MenuItem Header="Nothing to see here" Name="menuNothing" />
2733
</MenuItem>
2834
<MenuItem Header="_Edit">
29-
<MenuItem Header="_Undo" Name="menuUndo" Command="Undo" InputGestureText="Ctrl+Z"/>
30-
<MenuItem Header="_Redo" Name="menuRedo" Command="Redo" InputGestureText="Ctrl+Y"/>
35+
<MenuItem Header="_Undo" Command="Undo" InputGestureText="Ctrl+Z"/>
36+
<MenuItem Header="_Redo" Command="Redo" InputGestureText="Ctrl+Y"/>
3137
<Separator/>
32-
<MenuItem Header="_Move" Name="menuName" Command="MoveRight" InputGestureText="M"/>
33-
<MenuItem Header="_Rotate" Name="menuRotate" Command="MoveLeft" InputGestureText="R"/>
34-
<MenuItem Header="Move _handle" Name="menuHandle" Command="MoveUp" InputGestureText="H"/>
38+
<MenuItem Header="_Move" Command="{x:Static local:SceneView.Move}" InputGestureText="M"/>
39+
<MenuItem Header="_Rotate" Command="{x:Static local:SceneView.Rotate}" InputGestureText="R"/>
40+
<MenuItem Header="Move _Handle" Command="{x:Static local:SceneView.MoveHandle}" InputGestureText="H"/>
41+
<Separator/>
42+
<MenuItem Header="Ortho Mode X-Z" Command="{x:Static local:SceneView.ToggleOrtho}" InputGestureText="F8"/>
43+
<MenuItem Header="Elevation Lock Y" Command="{x:Static local:SceneView.ToggleElevationLock}" InputGestureText="F7"/>
44+
<MenuItem Header="Object Snap" Command="{x:Static local:SceneView.ToggleObjectSnap}" InputGestureText="F3" IsCheckable="True"/>
3545
</MenuItem>
3646
<MenuItem Header="_View">
3747
<MenuItem Header="Nothing to see here" Name="menuNothing3" />

Source/Contrib/TrackViewer/UserInterface/SceneView.xaml.cs

Lines changed: 112 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,32 @@ public partial class SceneView : Window
3333
EditorState EditorState;
3434
EditorMoveState EditorMoveState;
3535
StaticShape SelectedObject;
36+
StaticShape SnappedObject;
3637
WorldFile SelectedWorldFile;
3738
Orts.Formats.Msts.WorldObject SelectedWorldObject;
3839
StaticShape MovedObject;
3940
WorldPosition MovedObjectOriginalPosition;
4041
WorldPosition HandlePosition;
4142
WorldPosition HandleOriginalPosition;
4243
float DeltaX, DeltaY, DeltaZ;
44+
Vector3 ConstructionX, ConstructionY, ConstructionZ;
45+
float ConstructionAngle;
4346
UndoDataSet DeltaContext;
4447
WorldLocation CursorLocation;
4548
readonly List<(int TileX, int TileZ)> FlaggedTiles = new List<(int, int)>();
4649

50+
bool OrthoMode { get; set; }
51+
bool ElevationLock { get; set; }
52+
bool ObjectSnap { get; set; }
53+
54+
// Editing commands
55+
public static RoutedCommand Rotate = new RoutedCommand();
56+
public static RoutedCommand Move = new RoutedCommand();
57+
public static RoutedCommand MoveHandle = new RoutedCommand();
58+
public static RoutedCommand ToggleOrtho = new RoutedCommand();
59+
public static RoutedCommand ToggleElevationLock = new RoutedCommand();
60+
public static RoutedCommand ToggleObjectSnap = new RoutedCommand();
61+
4762
public SceneView(IntPtr hostWindow)
4863
{
4964
InitializeComponent();
@@ -64,31 +79,21 @@ public void Update(GameTime gameTime)
6479
{
6580
ApplicationCommands.Stop.Execute(null, null);
6681
}
67-
68-
if (EditorState == EditorState.Default || EditorState == EditorState.ObjectSelected)
69-
{
70-
if (UserInput.IsMouseLeftButtonPressed && UserInput.ModifiersMaskShiftCtrlAlt(false, false, false))
71-
{
72-
if (Camera.PickByMouse(out var selectedObject))
73-
{
74-
SelectedObject = selectedObject;
75-
SelectedObjectChanged();
76-
EditorState = EditorState.ObjectSelected;
77-
}
78-
}
79-
}
80-
if (EditorState == EditorState.HandleMoving)
81-
{
82-
if (UserInput.IsMouseLeftButtonPressed)
83-
{
84-
ApplyHandleMove();
85-
}
86-
}
87-
if (EditorState == EditorState.ObjectMoving)
82+
if (UserInput.IsMouseLeftButtonPressed)
8883
{
89-
if (UserInput.IsMouseLeftButtonPressed)
84+
switch (EditorState)
9085
{
91-
ApplyObjectMove();
86+
case EditorState.Default:
87+
case EditorState.ObjectSelected:
88+
if (UserInput.ModifiersMaskShiftCtrlAlt(false, false, false) && Camera.PickByMouse(out var selectedObject))
89+
{
90+
SelectedObject = selectedObject;
91+
SelectedObjectChanged();
92+
EditorState = EditorState.ObjectSelected;
93+
}
94+
break;
95+
case EditorState.HandleMoving: ApplyHandleMove(); break;
96+
case EditorState.ObjectMoving: ApplyObjectMove(); break;
9297
}
9398
}
9499

@@ -102,9 +107,27 @@ public void Update(GameTime gameTime)
102107
// A second pass after user input handled, do the effective work
103108
if (EditorState == EditorState.ObjectMoving)
104109
{
110+
if (ObjectSnap && Camera.PickByMouse(out var snappedObject))
111+
{
112+
SnappedObject = snappedObject;
113+
Viewer.EditorShapes.BoundingBoxShapes.Add(SnappedObject);
114+
}
115+
else
116+
{
117+
SnappedObject = null;
118+
Viewer.EditorShapes.BoundingBoxShapes.TryTake(out _);
119+
}
105120
MovedObject.Location.XNAMatrix = GetMovingMatrix(MovedObjectOriginalPosition, HandleOriginalPosition, HandlePosition);
106121
Viewer.EditorShapes.MovedObject = MovedObject;
107122
Viewer.EditorShapes.MovedObjectLocation = MovedObject.Location;
123+
Viewer.EditorShapes.HandleLocation = HandlePosition;
124+
Viewer.EditorShapes.ConstructionOriginalTranslation = (HandleOriginalPosition ?? MovedObjectOriginalPosition).XNAMatrix.Translation;
125+
Viewer.EditorShapes.ConstructionLinesEnabled = true;
126+
Viewer.EditorShapes.ConsturcionLineAngleStyle = EditorMoveState == EditorMoveState.Rotate;
127+
Viewer.EditorShapes.ConstructionLineX = ConstructionX;
128+
Viewer.EditorShapes.ConstructionLineY = ConstructionY;
129+
Viewer.EditorShapes.ConstructionLineZ = ConstructionZ;
130+
Viewer.EditorShapes.ConstructionAngle = ConstructionAngle;
108131
}
109132
else
110133
{
@@ -116,6 +139,18 @@ public void Update(GameTime gameTime)
116139
{
117140
HandlePosition.XNAMatrix = GetMovingMatrix(HandleOriginalPosition);
118141
Viewer.EditorShapes.HandleLocation = HandlePosition;
142+
Viewer.EditorShapes.ConstructionOriginalTranslation = HandleOriginalPosition.XNAMatrix.Translation;
143+
Viewer.EditorShapes.ConstructionLinesEnabled = true;
144+
Viewer.EditorShapes.ConsturcionLineAngleStyle = EditorMoveState == EditorMoveState.Rotate;
145+
Viewer.EditorShapes.ConstructionLineX = ConstructionX;
146+
Viewer.EditorShapes.ConstructionLineY = ConstructionY;
147+
Viewer.EditorShapes.ConstructionLineZ = ConstructionZ;
148+
Viewer.EditorShapes.ConstructionAngle = ConstructionAngle;
149+
}
150+
151+
if (EditorState != EditorState.ObjectMoving && EditorState != EditorState.HandleMoving)
152+
{
153+
Viewer.EditorShapes.ConstructionLinesEnabled = false;
119154
}
120155

121156
FillDeltaStatus();
@@ -198,39 +233,59 @@ public async Task SetCameraLocation(WorldLocation worldLocation)
198233

199234
Matrix GetMovingMatrix(in WorldPosition originalPosition, in WorldPosition handleOriginalPosition = null, WorldPosition handlePosition = null)
200235
{
201-
var handle = handleOriginalPosition ?? originalPosition;
236+
var handle = handlePosition ?? originalPosition;
202237
var xnaMatrix = originalPosition.XNAMatrix;
203238

204239
if (EditorMoveState == EditorMoveState.Rotate)
205240
{
206-
var distance = WorldLocation.GetDistance(handle.WorldLocation, CursorLocation);
207-
distance.Z *= -1;
241+
float constructionRadius;
242+
double targetAngle;
243+
if (ObjectSnap && SnappedObject != null)
244+
{
245+
var distance = WorldLocation.GetDistance(handle.WorldLocation, SnappedObject.Location.WorldLocation);
246+
distance.Z *= -1;
247+
distance.Y = 0;
248+
constructionRadius = distance.Length();
249+
targetAngle = Math.Atan2(SnappedObject.Location.XNAMatrix.M13, SnappedObject.Location.XNAMatrix.M33);
250+
}
251+
else
252+
{
253+
var distance = WorldLocation.GetDistance(handle.WorldLocation, CursorLocation);
254+
distance.Z *= -1;
255+
distance.Y = 0;
256+
constructionRadius = distance.Length();
257+
targetAngle = Math.Atan2(distance.Z, distance.X);
258+
}
208259

209-
var angle = MathHelper.WrapAngle((float)(Math.Atan2(originalPosition.XNAMatrix.M13, originalPosition.XNAMatrix.M33) - Math.Atan2(distance.Z, distance.X)));
260+
var angle = MathHelper.WrapAngle((float)(Math.Atan2(originalPosition.XNAMatrix.M13, originalPosition.XNAMatrix.M33) - targetAngle));
210261
var rotation = Matrix.CreateFromYawPitchRoll(angle, 0, 0);
211262
var translation = handle.XNAMatrix.Translation;
212263
xnaMatrix.Translation -= translation;
213264
xnaMatrix *= rotation;
214265
xnaMatrix.Translation += translation;
215266

267+
DeltaX = 0;
268+
DeltaY = MathHelper.ToDegrees(angle);
269+
DeltaZ = 0;
270+
ConstructionAngle = angle;
271+
ConstructionX = constructionRadius * Vector3.UnitX;
272+
ConstructionZ = constructionRadius * Vector3.Transform(Vector3.UnitX, Matrix.CreateFromYawPitchRoll(-angle, 0, 0));
273+
216274
if (handlePosition != null && handleOriginalPosition != null)
217275
{
218-
angle = MathHelper.WrapAngle((float)(Math.Atan2(handleOriginalPosition.XNAMatrix.M13, handleOriginalPosition.XNAMatrix.M33) - Math.Atan2(distance.Z, distance.X)));
276+
angle = MathHelper.WrapAngle((float)(Math.Atan2(handleOriginalPosition.XNAMatrix.M13, handleOriginalPosition.XNAMatrix.M33) - targetAngle));
219277
rotation = Matrix.CreateFromYawPitchRoll(angle, 0, 0);
220278
var handleMatrix = handleOriginalPosition.XNAMatrix;
221279
handleMatrix.Translation -= translation;
222280
handleMatrix *= rotation;
223281
handleMatrix.Translation += translation;
224282
handlePosition.XNAMatrix = handleMatrix;
225283
}
226-
227-
DeltaX = 0;
228-
DeltaY = MathHelper.ToDegrees(angle);
229-
DeltaZ = 0;
230284
}
231285
else if (EditorMoveState == EditorMoveState.Move)
232286
{
233-
var distance = WorldLocation.GetDistance(originalPosition.WorldLocation, CursorLocation);
287+
var targetLocation = ObjectSnap && SnappedObject != null ? SnappedObject.Location.WorldLocation : CursorLocation;
288+
var distance = WorldLocation.GetDistance(originalPosition.WorldLocation, targetLocation);
234289
distance.Z *= -1;
235290

236291
var axisX = Vector3.Normalize(handle.XNAMatrix.Right);
@@ -239,7 +294,7 @@ Matrix GetMovingMatrix(in WorldPosition originalPosition, in WorldPosition handl
239294

240295
var tileLocation = xnaMatrix.Translation;
241296

242-
if (UserInput.IsDown(UserCommand.EditorLockOrthogonal))
297+
if (OrthoMode)
243298
{
244299
var distanceX = Vector3.Dot(axisX, distance);
245300
var distanceZ = Vector3.Dot(axisZ, distance);
@@ -252,7 +307,7 @@ Matrix GetMovingMatrix(in WorldPosition originalPosition, in WorldPosition handl
252307
tileLocation.Z += distance.Z;
253308
}
254309

255-
if (!UserInput.IsDown(UserCommand.EditorLockElevation))
310+
if (!ElevationLock)
256311
{
257312
tileLocation.Y = Viewer.Tiles.GetElevation(handle.TileX, handle.TileZ, tileLocation.X, -tileLocation.Z);
258313
}
@@ -270,6 +325,9 @@ Matrix GetMovingMatrix(in WorldPosition originalPosition, in WorldPosition handl
270325
DeltaX = Vector3.Dot(axisX, distance);
271326
DeltaY = Vector3.Dot(axisY, distance);
272327
DeltaZ = Vector3.Dot(axisZ, distance);
328+
ConstructionX = DeltaX * Vector3.UnitX;
329+
ConstructionY = DeltaY * Vector3.UnitY;
330+
ConstructionZ = DeltaZ * Vector3.UnitZ;
273331
}
274332
return xnaMatrix;
275333
}
@@ -376,6 +434,10 @@ void CancelObjectMove()
376434
{
377435
MovedObject.Location.CopyFrom(MovedObjectOriginalPosition);
378436
MovedObject = null;
437+
if (HandleOriginalPosition != null)
438+
HandlePosition.CopyFrom(HandleOriginalPosition);
439+
else
440+
HandlePosition = null;
379441
EditorState = EditorState.ObjectSelected;
380442
}
381443

@@ -424,8 +486,11 @@ void SelectedObjectChanged()
424486
Viewer.EditorShapes.SelectedObject = SelectedObject;
425487
Viewer.EditorShapes.MovedObject = null;
426488
Viewer.EditorShapes.HandleLocation = null;
489+
while (!Viewer.EditorShapes.BoundingBoxShapes.IsEmpty)
490+
Viewer.EditorShapes.BoundingBoxShapes.TryTake(out _);
427491
HandlePosition = null;
428492
HandleOriginalPosition = null;
493+
SnappedObject = null;
429494

430495
SelectedWorldFile = Viewer.World.Scenery.WorldFiles.SingleOrDefault(w => w.TileX == SelectedObject?.Location.TileX && w.TileZ == SelectedObject?.Location.TileZ);
431496
SelectedWorldObject = SelectedWorldFile?.MstsWFile?.Tr_Worldfile?.SingleOrDefault(o => o.UID == SelectedObject?.Uid);
@@ -477,11 +542,6 @@ protected override void OnClosing(CancelEventArgs e)
477542
Hide();
478543
}
479544

480-
private void IntValidationTextBox(object sender, TextCompositionEventArgs e)
481-
{
482-
e.Handled = int.TryParse(e.Text, out var _);
483-
}
484-
485545
private void UndoRedoCanExecute(object sender, CanExecuteRoutedEventArgs e)
486546
{
487547
e.CanExecute = EditorState == EditorState.Default || EditorState == EditorState.ObjectSelected;
@@ -500,25 +560,34 @@ private void CancelCommand(object sender, ExecutedRoutedEventArgs e)
500560
private void RotateCommand(object sender, ExecutedRoutedEventArgs e)
501561
{
502562
EditorMoveState = EditorMoveState.Rotate;
503-
504563
if (EditorState == EditorState.ObjectSelected)
505564
StartObjectMove();
565+
Keyboard.Focus(DeltaYBlock);
506566
}
507567

508568
private void MoveCommand(object sender, ExecutedRoutedEventArgs e)
509569
{
510570
EditorMoveState = EditorMoveState.Move;
511-
512571
if (EditorState == EditorState.ObjectSelected)
513572
StartObjectMove();
573+
Keyboard.Focus(DeltaXBlock);
514574
}
515575

516576
private void MoveHandleCommand(object sender, ExecutedRoutedEventArgs e)
517577
{
518578
EditorMoveState = EditorMoveState.Move;
519-
520579
if (EditorState == EditorState.ObjectSelected)
521580
StartHandleMove();
581+
Keyboard.Focus(DeltaXBlock);
582+
}
583+
584+
private void ToggleOrthoCommand(object sender, ExecutedRoutedEventArgs e) => OrthoMode = !OrthoMode;
585+
private void ToggleElevationLockCommand(object sender, ExecutedRoutedEventArgs e) => ElevationLock = !ElevationLock;
586+
private void ToggleObjectSnapCommand(object sender, ExecutedRoutedEventArgs e) => ObjectSnap = !ObjectSnap;
587+
588+
private void IntValidationTextBox(object sender, TextCompositionEventArgs e)
589+
{
590+
e.Handled = int.TryParse(e.Text, out var _);
522591
}
523592

524593
private void UintValidationTextBox(object sender, TextCompositionEventArgs e)

0 commit comments

Comments
 (0)