@@ -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