@@ -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,30 @@ 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+ if ( snappedObject != SnappedObject )
113+ Viewer . EditorShapes . BoundingBoxShapes . RemoveAll ( s => s == SnappedObject ) ;
114+ SnappedObject = snappedObject ;
115+ if ( ! Viewer . EditorShapes . BoundingBoxShapes . Contains ( SnappedObject ) )
116+ Viewer . EditorShapes . BoundingBoxShapes . Add ( SnappedObject ) ;
117+ }
118+ else
119+ {
120+ Viewer . EditorShapes . BoundingBoxShapes . RemoveAll ( s => s == SnappedObject ) ;
121+ SnappedObject = null ;
122+ }
105123 MovedObject . Location . XNAMatrix = GetMovingMatrix ( MovedObjectOriginalPosition , HandleOriginalPosition , HandlePosition ) ;
106124 Viewer . EditorShapes . MovedObject = MovedObject ;
107125 Viewer . EditorShapes . MovedObjectLocation = MovedObject . Location ;
126+ Viewer . EditorShapes . HandleLocation = HandlePosition ;
127+ Viewer . EditorShapes . ConstructionOriginalTranslation = ( HandleOriginalPosition ?? MovedObjectOriginalPosition ) . XNAMatrix . Translation ;
128+ Viewer . EditorShapes . ConstructionLinesEnabled = true ;
129+ Viewer . EditorShapes . ConsturcionLineAngleStyle = EditorMoveState == EditorMoveState . Rotate ;
130+ Viewer . EditorShapes . ConstructionLineX = ConstructionX ;
131+ Viewer . EditorShapes . ConstructionLineY = ConstructionY ;
132+ Viewer . EditorShapes . ConstructionLineZ = ConstructionZ ;
133+ Viewer . EditorShapes . ConstructionAngle = ConstructionAngle ;
108134 }
109135 else
110136 {
@@ -116,6 +142,18 @@ public void Update(GameTime gameTime)
116142 {
117143 HandlePosition . XNAMatrix = GetMovingMatrix ( HandleOriginalPosition ) ;
118144 Viewer . EditorShapes . HandleLocation = HandlePosition ;
145+ Viewer . EditorShapes . ConstructionOriginalTranslation = HandleOriginalPosition . XNAMatrix . Translation ;
146+ Viewer . EditorShapes . ConstructionLinesEnabled = true ;
147+ Viewer . EditorShapes . ConsturcionLineAngleStyle = EditorMoveState == EditorMoveState . Rotate ;
148+ Viewer . EditorShapes . ConstructionLineX = ConstructionX ;
149+ Viewer . EditorShapes . ConstructionLineY = ConstructionY ;
150+ Viewer . EditorShapes . ConstructionLineZ = ConstructionZ ;
151+ Viewer . EditorShapes . ConstructionAngle = ConstructionAngle ;
152+ }
153+
154+ if ( EditorState != EditorState . ObjectMoving && EditorState != EditorState . HandleMoving )
155+ {
156+ Viewer . EditorShapes . ConstructionLinesEnabled = false ;
119157 }
120158
121159 FillDeltaStatus ( ) ;
@@ -198,39 +236,59 @@ public async Task SetCameraLocation(WorldLocation worldLocation)
198236
199237 Matrix GetMovingMatrix ( in WorldPosition originalPosition , in WorldPosition handleOriginalPosition = null , WorldPosition handlePosition = null )
200238 {
201- var handle = handleOriginalPosition ?? originalPosition ;
239+ var handle = handlePosition ?? originalPosition ;
202240 var xnaMatrix = originalPosition . XNAMatrix ;
203241
204242 if ( EditorMoveState == EditorMoveState . Rotate )
205243 {
206- var distance = WorldLocation . GetDistance ( handle . WorldLocation , CursorLocation ) ;
207- distance . Z *= - 1 ;
244+ float constructionRadius ;
245+ double targetAngle ;
246+ if ( ObjectSnap && SnappedObject != null )
247+ {
248+ var distance = WorldLocation . GetDistance ( handle . WorldLocation , SnappedObject . Location . WorldLocation ) ;
249+ distance . Z *= - 1 ;
250+ distance . Y = 0 ;
251+ constructionRadius = distance . Length ( ) ;
252+ targetAngle = Math . Atan2 ( SnappedObject . Location . XNAMatrix . M13 , SnappedObject . Location . XNAMatrix . M33 ) ;
253+ }
254+ else
255+ {
256+ var distance = WorldLocation . GetDistance ( handle . WorldLocation , CursorLocation ) ;
257+ distance . Z *= - 1 ;
258+ distance . Y = 0 ;
259+ constructionRadius = distance . Length ( ) ;
260+ targetAngle = Math . Atan2 ( distance . Z , distance . X ) ;
261+ }
208262
209- var angle = MathHelper . WrapAngle ( ( float ) ( Math . Atan2 ( originalPosition . XNAMatrix . M13 , originalPosition . XNAMatrix . M33 ) - Math . Atan2 ( distance . Z , distance . X ) ) ) ;
263+ var angle = MathHelper . WrapAngle ( ( float ) ( Math . Atan2 ( originalPosition . XNAMatrix . M13 , originalPosition . XNAMatrix . M33 ) - targetAngle ) ) ;
210264 var rotation = Matrix . CreateFromYawPitchRoll ( angle , 0 , 0 ) ;
211265 var translation = handle . XNAMatrix . Translation ;
212266 xnaMatrix . Translation -= translation ;
213267 xnaMatrix *= rotation ;
214268 xnaMatrix . Translation += translation ;
215269
270+ DeltaX = 0 ;
271+ DeltaY = MathHelper . ToDegrees ( angle ) ;
272+ DeltaZ = 0 ;
273+ ConstructionAngle = angle ;
274+ ConstructionX = constructionRadius * Vector3 . UnitX ;
275+ ConstructionZ = constructionRadius * Vector3 . Transform ( Vector3 . UnitX , Matrix . CreateFromYawPitchRoll ( - angle , 0 , 0 ) ) ;
276+
216277 if ( handlePosition != null && handleOriginalPosition != null )
217278 {
218- angle = MathHelper . WrapAngle ( ( float ) ( Math . Atan2 ( handleOriginalPosition . XNAMatrix . M13 , handleOriginalPosition . XNAMatrix . M33 ) - Math . Atan2 ( distance . Z , distance . X ) ) ) ;
279+ angle = MathHelper . WrapAngle ( ( float ) ( Math . Atan2 ( handleOriginalPosition . XNAMatrix . M13 , handleOriginalPosition . XNAMatrix . M33 ) - targetAngle ) ) ;
219280 rotation = Matrix . CreateFromYawPitchRoll ( angle , 0 , 0 ) ;
220281 var handleMatrix = handleOriginalPosition . XNAMatrix ;
221282 handleMatrix . Translation -= translation ;
222283 handleMatrix *= rotation ;
223284 handleMatrix . Translation += translation ;
224285 handlePosition . XNAMatrix = handleMatrix ;
225286 }
226-
227- DeltaX = 0 ;
228- DeltaY = MathHelper . ToDegrees ( angle ) ;
229- DeltaZ = 0 ;
230287 }
231288 else if ( EditorMoveState == EditorMoveState . Move )
232289 {
233- var distance = WorldLocation . GetDistance ( originalPosition . WorldLocation , CursorLocation ) ;
290+ var targetLocation = ObjectSnap && SnappedObject != null ? SnappedObject . Location . WorldLocation : CursorLocation ;
291+ var distance = WorldLocation . GetDistance ( originalPosition . WorldLocation , targetLocation ) ;
234292 distance . Z *= - 1 ;
235293
236294 var axisX = Vector3 . Normalize ( handle . XNAMatrix . Right ) ;
@@ -239,7 +297,7 @@ Matrix GetMovingMatrix(in WorldPosition originalPosition, in WorldPosition handl
239297
240298 var tileLocation = xnaMatrix . Translation ;
241299
242- if ( UserInput . IsDown ( UserCommand . EditorLockOrthogonal ) )
300+ if ( OrthoMode )
243301 {
244302 var distanceX = Vector3 . Dot ( axisX , distance ) ;
245303 var distanceZ = Vector3 . Dot ( axisZ , distance ) ;
@@ -252,7 +310,7 @@ Matrix GetMovingMatrix(in WorldPosition originalPosition, in WorldPosition handl
252310 tileLocation . Z += distance . Z ;
253311 }
254312
255- if ( ! UserInput . IsDown ( UserCommand . EditorLockElevation ) )
313+ if ( ! ElevationLock )
256314 {
257315 tileLocation . Y = Viewer . Tiles . GetElevation ( handle . TileX , handle . TileZ , tileLocation . X , - tileLocation . Z ) ;
258316 }
@@ -270,6 +328,9 @@ Matrix GetMovingMatrix(in WorldPosition originalPosition, in WorldPosition handl
270328 DeltaX = Vector3 . Dot ( axisX , distance ) ;
271329 DeltaY = Vector3 . Dot ( axisY , distance ) ;
272330 DeltaZ = Vector3 . Dot ( axisZ , distance ) ;
331+ ConstructionX = DeltaX * Vector3 . UnitX ;
332+ ConstructionY = DeltaY * Vector3 . UnitY ;
333+ ConstructionZ = DeltaZ * Vector3 . UnitZ ;
273334 }
274335 return xnaMatrix ;
275336 }
@@ -376,6 +437,11 @@ void CancelObjectMove()
376437 {
377438 MovedObject . Location . CopyFrom ( MovedObjectOriginalPosition ) ;
378439 MovedObject = null ;
440+ if ( HandleOriginalPosition != null )
441+ HandlePosition . CopyFrom ( HandleOriginalPosition ) ;
442+ else
443+ HandlePosition = null ;
444+ Viewer . EditorShapes . BoundingBoxShapes . Clear ( ) ;
379445 EditorState = EditorState . ObjectSelected ;
380446 }
381447
@@ -395,6 +461,7 @@ void ApplyObjectMove()
395461
396462 DeltaContext = UndoStack . Peek ( ) ;
397463 MovedObject = null ;
464+ Viewer . EditorShapes . BoundingBoxShapes . Clear ( ) ;
398465 EditorState = EditorState . ObjectSelected ;
399466 }
400467
@@ -410,12 +477,14 @@ void CancelHandleMove()
410477 {
411478 HandlePosition = null ;
412479 HandleOriginalPosition = null ;
480+ Viewer . EditorShapes . BoundingBoxShapes . Clear ( ) ;
413481 EditorState = EditorState . ObjectSelected ;
414482 }
415483
416484 void ApplyHandleMove ( )
417485 {
418486 HandleOriginalPosition = new WorldPosition ( HandlePosition ) ;
487+ Viewer . EditorShapes . BoundingBoxShapes . Clear ( ) ;
419488 EditorState = EditorState . ObjectSelected ;
420489 }
421490
@@ -424,8 +493,10 @@ void SelectedObjectChanged()
424493 Viewer . EditorShapes . SelectedObject = SelectedObject ;
425494 Viewer . EditorShapes . MovedObject = null ;
426495 Viewer . EditorShapes . HandleLocation = null ;
496+ Viewer . EditorShapes . BoundingBoxShapes . Clear ( ) ;
427497 HandlePosition = null ;
428498 HandleOriginalPosition = null ;
499+ SnappedObject = null ;
429500
430501 SelectedWorldFile = Viewer . World . Scenery . WorldFiles . SingleOrDefault ( w => w . TileX == SelectedObject ? . Location . TileX && w . TileZ == SelectedObject ? . Location . TileZ ) ;
431502 SelectedWorldObject = SelectedWorldFile ? . MstsWFile ? . Tr_Worldfile ? . SingleOrDefault ( o => o . UID == SelectedObject ? . Uid ) ;
@@ -477,11 +548,6 @@ protected override void OnClosing(CancelEventArgs e)
477548 Hide ( ) ;
478549 }
479550
480- private void IntValidationTextBox ( object sender , TextCompositionEventArgs e )
481- {
482- e . Handled = int . TryParse ( e . Text , out var _ ) ;
483- }
484-
485551 private void UndoRedoCanExecute ( object sender , CanExecuteRoutedEventArgs e )
486552 {
487553 e . CanExecute = EditorState == EditorState . Default || EditorState == EditorState . ObjectSelected ;
@@ -500,25 +566,34 @@ private void CancelCommand(object sender, ExecutedRoutedEventArgs e)
500566 private void RotateCommand ( object sender , ExecutedRoutedEventArgs e )
501567 {
502568 EditorMoveState = EditorMoveState . Rotate ;
503-
504569 if ( EditorState == EditorState . ObjectSelected )
505570 StartObjectMove ( ) ;
571+ Keyboard . Focus ( DeltaYBlock ) ;
506572 }
507573
508574 private void MoveCommand ( object sender , ExecutedRoutedEventArgs e )
509575 {
510576 EditorMoveState = EditorMoveState . Move ;
511-
512577 if ( EditorState == EditorState . ObjectSelected )
513578 StartObjectMove ( ) ;
579+ Keyboard . Focus ( DeltaXBlock ) ;
514580 }
515581
516582 private void MoveHandleCommand ( object sender , ExecutedRoutedEventArgs e )
517583 {
518584 EditorMoveState = EditorMoveState . Move ;
519-
520585 if ( EditorState == EditorState . ObjectSelected )
521586 StartHandleMove ( ) ;
587+ Keyboard . Focus ( DeltaXBlock ) ;
588+ }
589+
590+ private void ToggleOrthoCommand ( object sender , ExecutedRoutedEventArgs e ) => OrthoMode = ! OrthoMode ;
591+ private void ToggleElevationLockCommand ( object sender , ExecutedRoutedEventArgs e ) => ElevationLock = ! ElevationLock ;
592+ private void ToggleObjectSnapCommand ( object sender , ExecutedRoutedEventArgs e ) => ObjectSnap = ! ObjectSnap ;
593+
594+ private void IntValidationTextBox ( object sender , TextCompositionEventArgs e )
595+ {
596+ e . Handled = int . TryParse ( e . Text , out var _ ) ;
522597 }
523598
524599 private void UintValidationTextBox ( object sender , TextCompositionEventArgs e )
0 commit comments