diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/BlockEditor.cs b/game/addons/tools/Code/Scene/Mesh/Tools/BlockEditor.cs new file mode 100644 index 00000000..c7b53fe3 --- /dev/null +++ b/game/addons/tools/Code/Scene/Mesh/Tools/BlockEditor.cs @@ -0,0 +1,350 @@ +namespace Editor.MeshEditor; + +/// +/// Create stuff as long as it fits in a box, woah crazy. +/// +[Title( "Block" ), Icon( "view_in_ar" )] +public sealed class BlockEditor( PrimitiveTool tool ) : PrimitiveEditor( tool ) +{ + PrimitiveBuilder _primitive = EditorTypeLibrary.Create( nameof( BlockPrimitive ) ); + Material _activeMaterial = tool.ActiveMaterial; + + BBox? _box; + BBox _startBox; + BBox _deltaBox; + Vector3 _dragStartPos; + bool _dragStarted; + Model _previewModel; + + static float TextSize => 22 * Gizmo.Settings.GizmoScale * Application.DpiScale; + + private static float s_lastHeight = 128; + + public override bool CanBuild => _primitive is not null && _box.HasValue; + public override bool InProgress => _dragStarted || CanBuild; + + public override PolygonMesh Build() + { + if ( !CanBuild ) return null; + + var box = _box.Value; + if ( _primitive.Is2D ) + { + box.Maxs.z = box.Mins.z; + } + + var material = Tool.ActiveMaterial; + var primitive = new PrimitiveBuilder.PolygonMesh(); + _primitive.Material = material; + _primitive.SetFromBox( box ); + _primitive.Build( primitive ); + + var mesh = new PolygonMesh(); + var vertices = mesh.AddVertices( [.. primitive.Vertices] ); + + foreach ( var face in primitive.Faces ) + { + var index = mesh.AddFace( [.. face.Indices.Select( x => vertices[x] )] ); + mesh.SetFaceMaterial( index, material ); + } + + mesh.TextureAlignToGrid( Transform.Zero ); + mesh.SetSmoothingAngle( 40.0f ); + + return mesh; + } + + public override void OnCreated( MeshComponent component ) + { + var selection = SceneEditorSession.Active.Selection; + selection.Set( component.GameObject ); + + if ( !_dragStarted ) + { + EditorToolManager.SetSubTool( nameof( MeshSelection ) ); + } + + _box = null; + _dragStarted = false; + } + + void StartStage( SceneTrace trace ) + { + var tr = trace.Run(); + + if ( !tr.Hit ) + { + var plane = new Plane( Vector3.Up, 0.0f ); + if ( plane.TryTrace( Gizmo.CurrentRay, out var point, true ) ) + { + tr.Hit = true; + tr.Normal = plane.Normal; + tr.EndPosition = point; + } + } + + if ( !tr.Hit ) return; + + tr.EndPosition = GridSnap( tr.EndPosition, tr.Normal ); + + if ( Gizmo.WasLeftMousePressed ) + { + _dragStartPos = tr.EndPosition; + _dragStarted = true; + + if ( _box.HasValue ) + { + Tool.Create(); + } + + _box = null; + _dragStarted = true; + } + else + { + var size = 3.0f * Gizmo.Camera.Position.Distance( tr.EndPosition ) / 1000.0f; + Gizmo.Draw.Color = Color.White; + Gizmo.Draw.SolidSphere( tr.EndPosition, size ); + } + } + + void DraggingStage() + { + var plane = new Plane( _dragStartPos, Vector3.Up ); + if ( !plane.TryTrace( Gizmo.CurrentRay, out var point, true ) ) return; + + point = GridSnap( point, Vector3.Up ); + + if ( !Gizmo.IsLeftMouseDown ) + { + var delta = point - _dragStartPos; + + if ( delta.x.AlmostEqual( 0.0f ) || delta.y.AlmostEqual( 0.0f ) ) + { + _box = null; + _dragStarted = false; + + return; + } + + _box = new BBox( _dragStartPos, point + Vector3.Up * s_lastHeight ); + _dragStarted = false; + + BuildPreview(); + } + else + { + var box = new BBox( _dragStartPos, point ); + Gizmo.Draw.IgnoreDepth = true; + Gizmo.Draw.LineThickness = 2; + Gizmo.Draw.Color = Gizmo.Colors.Active.WithAlpha( 0.5f ); + Gizmo.Draw.LineBBox( box ); + Gizmo.Draw.Color = Gizmo.Colors.Left; + Gizmo.Draw.ScreenText( $"L: {box.Size.y:0.#}", box.Mins.WithY( box.Center.y ), Vector2.Up * 32, size: TextSize ); + Gizmo.Draw.Color = Gizmo.Colors.Forward; + Gizmo.Draw.ScreenText( $"W: {box.Size.x:0.#}", box.Mins.WithX( box.Center.x ), Vector2.Up * 32, size: TextSize ); + } + } + + public override void OnUpdate( SceneTrace trace ) + { + if ( Application.IsKeyDown( KeyCode.Escape ) || + Application.IsKeyDown( KeyCode.Delete ) ) + { + Cancel(); + } + + if ( _activeMaterial != Tool.ActiveMaterial ) + { + BuildPreview(); + _activeMaterial = Tool.ActiveMaterial; + } + + if ( !Gizmo.Pressed.Any ) + { + if ( _dragStarted ) + { + DraggingStage(); + } + else + { + StartStage( trace ); + } + } + + DrawBox(); + } + + void Cancel() + { + _box = null; + _dragStarted = false; + } + + public override void OnCancel() + { + Cancel(); + } + + void DrawBox() + { + if ( !_box.HasValue ) return; + + var box = _box.Value; + + if ( _primitive.Is2D ) + { + box.Maxs.z = box.Mins.z; + } + + using ( Gizmo.Scope( "box" ) ) + { + Gizmo.Hitbox.DepthBias = 0.01f; + + if ( !Gizmo.Pressed.Any ) + { + _startBox = box; + _deltaBox = default; + } + + if ( Gizmo.Control.BoundingBox( "Resize", box, out var outBox ) ) + { + _deltaBox.Maxs += outBox.Maxs - box.Maxs; + _deltaBox.Mins += outBox.Mins - box.Mins; + + box = Gizmo.Snap( _startBox, _deltaBox ); + + if ( _primitive.Is2D ) + { + var b = box; + b.Mins.z = _box.Value.Mins.z; + b.Maxs.z = _box.Value.Maxs.z; + _box = b; + + box.Mins.z = b.Mins.z; + box.Maxs.z = b.Mins.z; + } + else + { + s_lastHeight = MathF.Abs( box.Size.z ); + + _box = box; + } + + BuildPreview(); + } + + Gizmo.Draw.IgnoreDepth = true; + Gizmo.Draw.LineThickness = 2; + Gizmo.Draw.Color = Gizmo.Colors.Active.WithAlpha( 0.5f ); + Gizmo.Draw.LineBBox( box ); + Gizmo.Draw.LineThickness = 3; + Gizmo.Draw.Color = Gizmo.Colors.Left; + Gizmo.Draw.ScreenText( $"L: {box.Size.y:0.#}", box.Maxs.WithY( box.Center.y ), Vector2.Up * 32, size: TextSize ); + Gizmo.Draw.Line( box.Maxs.WithY( box.Mins.y ), box.Maxs.WithY( box.Maxs.y ) ); + Gizmo.Draw.Color = Gizmo.Colors.Forward; + Gizmo.Draw.ScreenText( $"W: {box.Size.x:0.#}", box.Maxs.WithX( box.Center.x ), Vector2.Up * 32, size: TextSize ); + Gizmo.Draw.Line( box.Maxs.WithX( box.Mins.x ), box.Maxs.WithX( box.Maxs.x ) ); + Gizmo.Draw.Color = Gizmo.Colors.Up; + Gizmo.Draw.ScreenText( $"H: {box.Size.z:0.#}", box.Maxs.WithZ( box.Center.z ), Vector2.Up * 32, size: TextSize ); + Gizmo.Draw.Line( box.Maxs.WithZ( box.Mins.z ), box.Maxs.WithZ( box.Maxs.z ) ); + } + + if ( _previewModel.IsValid() && !_previewModel.IsError ) + { + Gizmo.Draw.Model( _previewModel ); + } + } + + static Vector3 GridSnap( Vector3 point, Vector3 normal ) + { + var basis = Rotation.LookAt( normal ); + return Gizmo.Snap( point * basis.Inverse, new Vector3( 0, 1, 1 ) ) * basis; + } + + public override Widget CreateWidget() + { + return new BlockEditorWidget( this, BuildPreview ); + } + + void BuildPreview() + { + var mesh = Build(); + _previewModel = mesh?.Rebuild(); + } + + private static IEnumerable GetBuilderTypes() + { + return EditorTypeLibrary.GetTypes() + .Where( x => !x.IsAbstract ) + .OrderBy( x => x.Name ); + } + + class BlockEditorWidget : ToolSidebarWidget + { + readonly BlockEditor _editor; + readonly Layout _controlLayout; + PrimitiveBuilder _primitive; + readonly Action _onEdited; + + public BlockEditorWidget( BlockEditor editor, Action onEdited ) + { + _editor = editor; + _primitive = _editor._primitive; + _onEdited = onEdited; + + Layout.Margin = 0; + + { + var group = AddGroup( "Shape Type" ); + var list = group.Add( new PrimitiveListView( this ) ); + list.FixedWidth = 200; + list.SetItems( GetBuilderTypes() ); + list.SelectItem( list.Items.FirstOrDefault( x => (x as TypeDescription).TargetType == _primitive?.GetType() ) ); + list.ItemSelected = ( e ) => OnPrimitiveSelected( (e as TypeDescription).TargetType ); + list.BuildLayout(); + } + + _controlLayout = Layout.AddColumn(); + BuildControlSheet(); + + Layout.AddStretchCell(); + } + + void OnPrimitiveSelected( Type type ) + { + _editor._primitive = EditorTypeLibrary.Create( type ); + _editor.BuildPreview(); + } + + void BuildControlSheet() + { + using var x = SuspendUpdates.For( this ); + + _controlLayout.Clear( true ); + + if ( _primitive is null ) return; + + var title = EditorTypeLibrary.GetType( _primitive.GetType() ).Title; + var w = new ToolSidebarWidget( this ); + w.Layout.Margin = 0; + _controlLayout.Add( w ); + + var group = w.AddGroup( $"{title} Properties" ); + var so = _primitive.GetSerialized(); + so.OnPropertyChanged += ( e ) => _onEdited?.Invoke(); + var sheet = new ControlSheet(); + sheet.AddObject( so ); + group.Add( sheet ); + } + + [EditorEvent.Frame] + public void Frame() + { + if ( _primitive == _editor._primitive ) return; + + _primitive = _editor._primitive; + BuildControlSheet(); + } + } +} diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/BlockTool.UI.cs b/game/addons/tools/Code/Scene/Mesh/Tools/BlockTool.UI.cs deleted file mode 100644 index 7480d8c6..00000000 --- a/game/addons/tools/Code/Scene/Mesh/Tools/BlockTool.UI.cs +++ /dev/null @@ -1,110 +0,0 @@ - -namespace Editor.MeshEditor; - -public partial class BlockTool -{ - private Layout ControlLayout { get; set; } - - public override Widget CreateToolSidebar() - { - var widget = new ToolSidebarWidget(); - - widget.AddTitle( "Create Primitive" ); - - var list = new PrimitiveListView( widget ); - list.SetItems( GetBuilderTypes() ); - - { - var group = widget.AddGroup( "Shape Type" ); - - group.Add( list ); - list.SelectItem( list.Items.FirstOrDefault() ); - list.ItemSelected = ( e ) => Current = _primitives.FirstOrDefault( x => x.GetType() == (e as TypeDescription).TargetType ); - } - - { - var group = widget.AddGroup( "Shape Settings" ); - - ControlLayout = group; - BuildControlSheet(); - } - - widget.Layout.AddStretchCell(); - - return widget; - } - - public void OnEdited( SerializedProperty property ) - { - RebuildMesh(); - } - - private void BuildControlSheet() - { - if ( !ControlLayout.IsValid() ) - return; - - ControlLayout.Clear( true ); - - if ( Current is null ) - return; - - var so = Current.GetSerialized(); - so.OnPropertyChanged += OnEdited; - var sheet = new ControlSheet(); - sheet.AddObject( so ); - ControlLayout.Add( sheet ); - } -} - -file class PrimitiveListView : ListView -{ - public PrimitiveListView( Widget parent ) : base( parent ) - { - ItemSpacing = 0; - ItemSize = 24; - - HorizontalScrollbarMode = ScrollbarMode.Off; - VerticalScrollbarMode = ScrollbarMode.Off; - } - - protected override void DoLayout() - { - base.DoLayout(); - - var rect = CanvasRect; - var itemSize = ItemSize; - var itemSpacing = ItemSpacing; - var itemsPerRow = 1; - var itemCount = Items.Count(); - - if ( itemSize.x > 0 ) itemsPerRow = ((rect.Width + itemSpacing.x) / (itemSize.x + itemSpacing.x)).FloorToInt(); - itemsPerRow = Math.Max( 1, itemsPerRow ); - - var rowCount = MathX.CeilToInt( itemCount / (float)itemsPerRow ); - FixedHeight = rowCount * (itemSize.y + itemSpacing.y) + Margin.EdgeSize.y; - } - - protected override string GetTooltip( object obj ) - { - var builder = obj as TypeDescription; - var displayInfo = DisplayInfo.ForType( builder.TargetType ); - return displayInfo.Name; - } - - protected override void PaintItem( VirtualWidget item ) - { - if ( item.Selected ) - { - Paint.ClearPen(); - Paint.SetBrush( Theme.Blue ); - Paint.DrawRect( item.Rect, 4 ); - } - - var builder = item.Object as TypeDescription; - var displayInfo = DisplayInfo.ForType( builder.TargetType ); - - Paint.SetPen( item.Selected || item.Hovered ? Color.White : Color.Gray ); - Paint.DrawIcon( item.Rect, displayInfo.Icon ?? "square", HeaderBarStyle.IconSize ); - } -} diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/BlockTool.cs b/game/addons/tools/Code/Scene/Mesh/Tools/BlockTool.cs deleted file mode 100644 index 0b72cc00..00000000 --- a/game/addons/tools/Code/Scene/Mesh/Tools/BlockTool.cs +++ /dev/null @@ -1,426 +0,0 @@ - -namespace Editor.MeshEditor; - -/// -/// Create new shapes by dragging out a block -/// -[Title( "Block Tool" )] -[Icon( "view_in_ar" )] -[Group( "4" )] -[Alias( "tools.block-tool" )] -public partial class BlockTool( MeshTool meshTool ) : EditorTool -{ - private BBox _box; - private BBox _startBox; - private BBox _deltaBox; - private bool _resizing; - private bool _resizePressed; - private bool _inProgress; - private bool _dragging; - private Vector3 _dragStartPos; - - private readonly HashSet _primitives = new(); - private PrimitiveBuilder _primitive; - private SceneObject _sceneObject; - - private PrimitiveBuilder Current - { - get => _primitive; - set - { - if ( _primitive == value ) - return; - - _primitive = value; - _primitive.Material = meshTool.ActiveMaterial; - - BuildControlSheet(); - RebuildMesh(); - } - } - - private bool InProgress - { - get => _inProgress; - set - { - if ( _inProgress == value ) - return; - - _inProgress = value; - } - } - - private static float LastHeight = 128; - - public override void OnEnabled() - { - base.OnEnabled(); - - AllowGameObjectSelection = false; - Selection.Clear(); - - CreatePrimitiveBuilders(); - } - - public override void OnDisabled() - { - base.OnDisabled(); - - if ( _sceneObject.IsValid() ) - { - _sceneObject.RenderingEnabled = false; - _sceneObject.Delete(); - _sceneObject = null; - } - - if ( InProgress ) - { - using ( Scene.Push() ) - { - var go = CreateFromBox( _box ); - Selection.Set( go ); - } - InProgress = false; - } - else - { - var selectedObjects = Selection.OfType().ToArray(); - Selection.Clear(); - foreach ( var o in selectedObjects ) - Selection.Add( o ); - } - - _resizing = false; - _resizePressed = false; - _inProgress = false; - _dragging = false; - } - - private PolygonMesh Build( BBox box ) - { - var primitive = new PrimitiveBuilder.PolygonMesh(); - _primitive.SetFromBox( box ); - _primitive.Build( primitive ); - - var mesh = new PolygonMesh(); - var hVertices = mesh.AddVertices( primitive.Vertices.ToArray() ); - - foreach ( var face in primitive.Faces ) - { - var index = mesh.AddFace( face.Indices.Select( x => hVertices[x] ).ToArray() ); - mesh.SetFaceMaterial( index, face.Material ); - } - - return mesh; - } - - private void RebuildMesh() - { - if ( !InProgress ) - return; - - if ( Current.Is2D ) - { - _box.Maxs.z = _box.Mins.z; - } - else - { - _box.Maxs.z = _box.Mins.z + LastHeight; - } - - var box = _box; - var position = box.Center; - box = BBox.FromPositionAndSize( 0, box.Size ); - - var mesh = Build( box ); - - foreach ( var hFace in mesh.FaceHandles ) - mesh.SetFaceMaterial( hFace, _primitive.Material ); - - mesh.TextureAlignToGrid( Transform.Zero.WithPosition( position ) ); - mesh.SetSmoothingAngle( 40.0f ); - - var model = mesh.Rebuild(); - var transform = new Transform( position ); - - if ( !_sceneObject.IsValid() ) - { - _sceneObject = new SceneObject( Scene.SceneWorld, model, transform ); - } - else - { - _sceneObject.Model = model; - _sceneObject.Transform = transform; - } - } - - private void CreatePrimitiveBuilders() - { - _primitives.Clear(); - - foreach ( var type in GetBuilderTypes() ) - { - _primitives.Add( type.Create() ); - } - - _primitive = _primitives.FirstOrDefault(); - _primitive.Material = meshTool.ActiveMaterial; - } - - private static IEnumerable GetBuilderTypes() - { - return EditorTypeLibrary.GetTypes() - .Where( x => !x.IsAbstract ).OrderBy( x => x.Name ); - } - - private GameObject CreateFromBox( BBox box ) - { - if ( _primitive is null ) - return null; - - using ( SceneEditorSession.Active.UndoScope( "Create Block" ).WithGameObjectCreations().Push() ) - { - if ( _sceneObject.IsValid() ) - { - _sceneObject.RenderingEnabled = false; - _sceneObject.Delete(); - _sceneObject = null; - } - - var go = new GameObject( true, "Box" ); - var mc = go.Components.Create( false ); - - var position = box.Center; - box = BBox.FromPositionAndSize( 0, box.Size ); - - var polygonMesh = Build( box ); - - foreach ( var hFace in polygonMesh.FaceHandles ) - polygonMesh.SetFaceMaterial( hFace, _primitive.Material ); - - polygonMesh.TextureAlignToGrid( Transform.Zero.WithPosition( position ) ); - - mc.WorldPosition = position; - mc.Mesh = polygonMesh; - mc.SmoothingAngle = 40.0f; - mc.Enabled = true; - - return go; - } - } - - public override void OnSelectionChanged() - { - base.OnSelectionChanged(); - - if ( !Selection.OfType().Any() ) - { - return; - } - - EditorToolManager.SetSubTool( nameof( PositionMode ) ); - } - - public override void OnUpdate() - { - if ( Selection.OfType().Any() ) - return; - - if ( InProgress && Application.FocusWidget.IsValid() ) - { - if ( Application.IsKeyDown( KeyCode.Escape ) || - Application.IsKeyDown( KeyCode.Delete ) ) - { - _resizing = false; - _dragging = false; - InProgress = false; - - if ( _sceneObject.IsValid() ) - { - _sceneObject.RenderingEnabled = false; - _sceneObject.Delete(); - _sceneObject = null; - } - } - } - - if ( Current is null ) - return; - - var textSize = 22 * Gizmo.Settings.GizmoScale * Application.DpiScale; - - if ( InProgress ) - { - using ( Gizmo.Scope( "Tool" ) ) - { - Gizmo.Hitbox.DepthBias = 0.01f; - - if ( !Gizmo.Pressed.Any && Gizmo.HasMouseFocus ) - { - _resizing = false; - _deltaBox = default; - _startBox = default; - - if ( Current.Is2D ) - { - _box.Maxs.z = _box.Mins.z; - } - else - { - _box.Maxs.z = _box.Mins.z + LastHeight; - } - } - - if ( Gizmo.Control.BoundingBox( "Resize", _box, out var outBox, out _resizePressed ) ) - { - if ( !_resizing ) - { - _startBox = _box; - _resizing = true; - _deltaBox = new BBox( Vector3.Zero, Vector3.Zero ); - } - - _deltaBox.Maxs += outBox.Maxs - _box.Maxs; - _deltaBox.Mins += outBox.Mins - _box.Mins; - - _box = Gizmo.Snap( _startBox, _deltaBox ); - - if ( Current.Is2D ) - { - _box.Mins.z = _startBox.Mins.z; - _box.Maxs.z = _startBox.Mins.z; - } - else - { - LastHeight = System.MathF.Abs( _box.Size.z ); - } - - RebuildMesh(); - } - - Gizmo.Draw.Color = Color.Red.WithAlpha( 0.5f ); - Gizmo.Draw.LineBBox( _startBox ); - } - - using ( Gizmo.Scope( "box" ) ) - { - Gizmo.Draw.IgnoreDepth = true; - Gizmo.Draw.LineThickness = 2; - Gizmo.Draw.Color = Gizmo.Colors.Active.WithAlpha( 0.5f ); - Gizmo.Draw.LineBBox( _box ); - Gizmo.Draw.LineThickness = 3; - Gizmo.Draw.Color = Gizmo.Colors.Left; - Gizmo.Draw.ScreenText( $"L: {_box.Size.y:0.#}", _box.Maxs.WithY( _box.Center.y ), Vector2.Up * 32, size: textSize ); - Gizmo.Draw.Line( _box.Maxs.WithY( _box.Mins.y ), _box.Maxs.WithY( _box.Maxs.y ) ); - Gizmo.Draw.Color = Gizmo.Colors.Forward; - Gizmo.Draw.ScreenText( $"W: {_box.Size.x:0.#}", _box.Maxs.WithX( _box.Center.x ), Vector2.Up * 32, size: textSize ); - Gizmo.Draw.Line( _box.Maxs.WithX( _box.Mins.x ), _box.Maxs.WithX( _box.Maxs.x ) ); - Gizmo.Draw.Color = Gizmo.Colors.Up; - Gizmo.Draw.ScreenText( $"H: {_box.Size.z:0.#}", _box.Maxs.WithZ( _box.Center.z ), Vector2.Up * 32, size: textSize ); - Gizmo.Draw.Line( _box.Maxs.WithZ( _box.Mins.z ), _box.Maxs.WithZ( _box.Maxs.z ) ); - } - - if ( Application.FocusWidget.IsValid() && Application.IsKeyDown( KeyCode.Enter ) ) - { - var go = CreateFromBox( _box ); - Selection.Set( go ); - - InProgress = false; - - EditorToolManager.SetSubTool( nameof( PositionMode ) ); - } - } - else - { - _resizePressed = false; - } - - if ( _resizePressed ) - return; - - var tr = Trace.UseRenderMeshes( true ) - .UsePhysicsWorld( true ) - .Run(); - - if ( !tr.Hit || _dragging ) - { - var plane = _dragging ? new Plane( _dragStartPos, Vector3.Up ) : new Plane( Vector3.Up, 0.0f ); - if ( plane.TryTrace( new Ray( tr.StartPosition, tr.Direction ), out tr.EndPosition, true ) ) - { - tr.Hit = true; - tr.Normal = plane.Normal; - } - } - - if ( !tr.Hit ) - return; - - var r = Rotation.LookAt( tr.Normal ); - var localPosition = tr.EndPosition * r.Inverse; - localPosition = Gizmo.Snap( localPosition, new Vector3( 0, 1, 1 ) ); - tr.EndPosition = localPosition * r; - - if ( !_dragging ) - { - using ( Gizmo.Scope( "Aim Handle", new Transform( tr.EndPosition, Rotation.LookAt( tr.Normal ) ) ) ) - { - Gizmo.Draw.Color = Color.White; - var size = 3.0f * Gizmo.Camera.Position.Distance( tr.EndPosition ) / 1000.0f; - Gizmo.Draw.SolidSphere( 0, size ); - } - } - - if ( Gizmo.WasLeftMousePressed ) - { - if ( InProgress ) - CreateFromBox( _box ); - - _dragging = true; - _dragStartPos = tr.EndPosition; - InProgress = false; - } - else if ( Gizmo.WasLeftMouseReleased && _dragging ) - { - var spacing = Gizmo.Settings.SnapToGrid ? Gizmo.Settings.GridSpacing : 1.0f; - var box = new BBox( _dragStartPos, tr.EndPosition ); - - if ( box.Size.x >= spacing || box.Size.y >= spacing ) - { - if ( Gizmo.Settings.SnapToGrid ) - { - if ( box.Size.x < spacing ) box.Maxs.x += spacing; - if ( box.Size.y < spacing ) box.Maxs.y += spacing; - } - - float height = Current.Is2D ? 0 : LastHeight; - var size = box.Size.WithZ( height ); - var position = box.Center.WithZ( box.Center.z + (height * 0.5f) ); - _box = BBox.FromPositionAndSize( position, size ); - InProgress = true; - - RebuildMesh(); - } - - _dragging = false; - _dragStartPos = default; - } - - if ( _dragging ) - { - using ( Gizmo.Scope( "Rect", 0 ) ) - { - var box = new BBox( _dragStartPos, tr.EndPosition ); - - Gizmo.Draw.IgnoreDepth = true; - Gizmo.Draw.LineThickness = 2; - Gizmo.Draw.Color = Gizmo.Colors.Active.WithAlpha( 0.5f ); - Gizmo.Draw.LineBBox( box ); - Gizmo.Draw.Color = Gizmo.Colors.Left; - Gizmo.Draw.ScreenText( $"L: {box.Size.y:0.#}", box.Mins.WithY( box.Center.y ), Vector2.Up * 32, size: textSize ); - Gizmo.Draw.Color = Gizmo.Colors.Forward; - Gizmo.Draw.ScreenText( $"W: {box.Size.x:0.#}", box.Mins.WithX( box.Center.x ), Vector2.Up * 32, size: textSize ); - } - } - } -} diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/EdgeTool.cs b/game/addons/tools/Code/Scene/Mesh/Tools/EdgeTool.cs index e4c6489e..b95eb3e5 100644 --- a/game/addons/tools/Code/Scene/Mesh/Tools/EdgeTool.cs +++ b/game/addons/tools/Code/Scene/Mesh/Tools/EdgeTool.cs @@ -17,7 +17,7 @@ public sealed partial class EdgeTool( MeshTool tool ) : SelectionTool( using var scope = Gizmo.Scope( "EdgeTool" ); - var closestEdge = GetClosestEdge( 8 ); + var closestEdge = MeshTrace.GetClosestEdge( 8 ); if ( closestEdge.IsValid() ) Gizmo.Hitbox.TrySetHovered( closestEdge.Transform.PointToWorld( closestEdge.Line.Center ) ); @@ -167,7 +167,7 @@ public sealed partial class EdgeTool( MeshTool tool ) : SelectionTool( private void SelectEdgeLoop() { - var edge = GetClosestEdge( 8 ); + var edge = MeshTrace.GetClosestEdge( 8 ); if ( !edge.IsValid() ) return; @@ -184,7 +184,7 @@ public sealed partial class EdgeTool( MeshTool tool ) : SelectionTool( private void SelectEdge() { - var edge = GetClosestEdge( 8 ); + var edge = MeshTrace.GetClosestEdge( 8 ); if ( edge.IsValid() ) { using ( Gizmo.Scope( "Edge Hover" ) ) diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/MeshSelection.cs b/game/addons/tools/Code/Scene/Mesh/Tools/MeshSelection.cs index 72beb55b..0e21e319 100644 --- a/game/addons/tools/Code/Scene/Mesh/Tools/MeshSelection.cs +++ b/game/addons/tools/Code/Scene/Mesh/Tools/MeshSelection.cs @@ -110,7 +110,6 @@ public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool public override void OnEnabled() { - Selection.Clear(); OnSelectionChanged(); var undo = SceneEditorSession.Active.UndoSystem; diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/MeshTool.UI.cs b/game/addons/tools/Code/Scene/Mesh/Tools/MeshTool.UI.cs index f1c5b621..81df49a0 100644 --- a/game/addons/tools/Code/Scene/Mesh/Tools/MeshTool.UI.cs +++ b/game/addons/tools/Code/Scene/Mesh/Tools/MeshTool.UI.cs @@ -21,8 +21,8 @@ partial class MeshTool file class MeshToolShortcutsWidget : Widget { - [Shortcut( "tools.block-tool", "Shift+B", typeof( SceneDock ) )] - public void ActivateBlockTool() => EditorToolManager.SetSubTool( nameof( BlockTool ) ); + [Shortcut( "tools.primitive-tool", "Shift+B", typeof( SceneDock ) )] + public void ActivatePrimitiveTool() => EditorToolManager.SetSubTool( nameof( PrimitiveTool ) ); [Shortcut( "tools.vertex-tool", "1", typeof( SceneDock ) )] public void ActivateVertexTool() => EditorToolManager.SetSubTool( nameof( VertexTool ) ); diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/MeshTool.cs b/game/addons/tools/Code/Scene/Mesh/Tools/MeshTool.cs index 657b8378..59ea2737 100644 --- a/game/addons/tools/Code/Scene/Mesh/Tools/MeshTool.cs +++ b/game/addons/tools/Code/Scene/Mesh/Tools/MeshTool.cs @@ -16,7 +16,7 @@ public partial class MeshTool : EditorTool public override IEnumerable GetSubtools() { - yield return new BlockTool( this ); + yield return new PrimitiveTool( this ); yield return new MeshSelection( this ); yield return new VertexTool( this ); yield return new EdgeTool( this ); diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/PolygonEditor.cs b/game/addons/tools/Code/Scene/Mesh/Tools/PolygonEditor.cs new file mode 100644 index 00000000..fecb528c --- /dev/null +++ b/game/addons/tools/Code/Scene/Mesh/Tools/PolygonEditor.cs @@ -0,0 +1,344 @@ +using System.Runtime.InteropServices; + +namespace Editor.MeshEditor; + +/// +/// Draw a polygon mesh. +/// +[Title( "Polygon" ), Icon( "pentagon" )] +public sealed class PolygonEditor( PrimitiveTool tool ) : PrimitiveEditor( tool ) +{ + Plane _plane; + readonly List _points = []; + HalfEdgeMesh.FaceHandle _face; + Model _previewModel; + bool _valid; + Material _activeMaterial = tool.ActiveMaterial; + + public override bool CanBuild => _points.Count >= 3 && _valid; + public override bool InProgress => _points.Count > 0; + + public override PolygonMesh Build() + { + if ( !CanBuild ) return default; + + var mesh = new PolygonMesh(); + var count = _points.Count; + + PlaneEquation( _points, out var faceNormal ); + + var hasHeight = !Height.AlmostEqual( 0.0f ); + var flip = faceNormal.Dot( _plane.Normal ) < 0.0f; + if ( hasHeight && !Hollow ) flip = !flip; + + var basePoints = new Vector3[count]; + if ( flip ) for ( var i = 0; i < count; i++ ) basePoints[i] = _points[count - 1 - i]; + else _points.CopyTo( basePoints ); + + var bottomRing = mesh.AddVertices( basePoints ); + var bottomFace = mesh.AddFace( bottomRing ); + if ( !bottomFace.IsValid ) return default; + + mesh.SetFaceMaterial( bottomFace, Tool.ActiveMaterial ); + + if ( !hasHeight ) + { + mesh.SetSmoothingAngle( 40.0f ); + mesh.TextureAlignToGrid( Transform.Zero ); + _face = bottomFace; + return mesh; + } + + var offset = Vector3.Up * Height; + var topPoints = new Vector3[count]; + for ( var i = 0; i < count; i++ ) topPoints[i] = basePoints[i] + offset; + + var topRing = mesh.AddVertices( topPoints ); + var topCap = new HalfEdgeMesh.VertexHandle[count]; + for ( var i = 0; i < count; i++ ) topCap[i] = topRing[count - 1 - i]; + + var topFace = mesh.AddFace( topCap ); + mesh.SetFaceMaterial( topFace, Tool.ActiveMaterial ); + + for ( var i = 0; i < count; i++ ) + { + mesh.SetFaceMaterial( mesh.AddFace( [bottomRing[i], topRing[i], topRing[(i + 1) % count], bottomRing[(i + 1) % count]] ), + Tool.ActiveMaterial ); + } + + mesh.SetSmoothingAngle( 40.0f ); + mesh.TextureAlignToGrid( Transform.Zero ); + + _face = topFace; + return mesh; + } + + + void BuildPreview() + { + var mesh = Build(); + _previewModel = mesh?.Rebuild(); + } + + public override void OnCreated( MeshComponent component ) + { + _points.Clear(); + _valid = false; + + var selection = SceneEditorSession.Active.Selection; + selection.Clear(); + selection.Add( component.GameObject ); + selection.Add( new MeshFace( component, _face ) ); + + EditorToolManager.SetSubTool( nameof( FaceTool ) ); + } + + public override void OnUpdate( SceneTrace trace ) + { + if ( Application.IsKeyDown( KeyCode.Escape ) ) + { + Cancel(); + } + + if ( _activeMaterial != Tool.ActiveMaterial ) + { + BuildPreview(); + _activeMaterial = Tool.ActiveMaterial; + } + + if ( !Gizmo.Pressed.Any ) + { + if ( _points.Count > 0 ) + { + DrawingStage(); + } + else + { + StartStage( trace ); + } + } + + DrawGizmos(); + } + + public override void OnCancel() + { + Cancel(); + } + + void Cancel() + { + _points.Clear(); + _valid = false; + } + + void DrawGizmos() + { + Gizmo.Draw.IgnoreDepth = true; + Gizmo.Draw.LineThickness = 2; + + var valid = _previewModel.IsValid() && !_previewModel.IsError; + + if ( valid ) + { + Gizmo.Draw.Model( _previewModel ); + } + + if ( _points.Count < 3 ) valid = true; + + Gizmo.Draw.Color = valid ? Color.Yellow : Color.Red; + + for ( int i = 0; i < _points.Count; i++ ) + { + var a = _points[i]; + var b = _points[(i + 1) % _points.Count]; + + Gizmo.Draw.Line( a, b ); + } + + for ( int i = 0; i < _points.Count; i++ ) + { + var point = _points[i]; + var size = 3.0f * Gizmo.Camera.Position.Distance( point ) / 1000.0f; + + using ( Gizmo.Scope( $"point {i}" ) ) + { + Gizmo.Hitbox.DepthBias = 0.01f; + Gizmo.Hitbox.Sphere( new Sphere( point, size * 2 ) ); + + if ( Gizmo.Pressed.This ) + { + if ( _plane.TryTrace( Gizmo.CurrentRay, out var newPoint, true ) ) + { + newPoint = GridSnap( newPoint, _plane.Normal ); + if ( !point.AlmostEqual( newPoint ) ) + { + _points[i] = newPoint; + point = newPoint; + + OnPointsChanged(); + } + } + } + + Gizmo.Draw.Color = Gizmo.IsHovered ? Color.Yellow : Color.White; + Gizmo.Draw.SolidSphere( point, Gizmo.IsHovered ? size * 2 : size ); + } + } + } + + void AddPoint( Vector3 point ) + { + _points.Add( point ); + OnPointsChanged(); + } + + void RemovePoint() + { + if ( _points.Count == 0 ) return; + _points.RemoveAt( _points.Count - 1 ); + OnPointsChanged(); + } + + void OnPointsChanged() + { + _valid = _points.Count < 3 || Mesh.TriangulatePolygon( CollectionsMarshal.AsSpan( _points ) ).Length >= 3; + BuildPreview(); + } + + void DrawingStage() + { + if ( !_plane.TryTrace( Gizmo.CurrentRay, out var point, true ) ) return; + + point = GridSnap( point, _plane.Normal ); + + if ( Gizmo.WasLeftMousePressed ) + { + AddPoint( point ); + } + + if ( !Gizmo.HasHovered ) + { + var size = 3.0f * Gizmo.Camera.Position.Distance( point ) / 1000.0f; + Gizmo.Draw.Color = Color.White; + Gizmo.Draw.SolidSphere( point, size ); + Gizmo.Draw.Line( _points.Last(), point ); + } + } + + void StartStage( SceneTrace trace ) + { + _previewModel = null; + + var tr = trace.Run(); + + if ( !tr.Hit ) + { + var plane = new Plane( Vector3.Up, 0.0f ); + if ( plane.TryTrace( Gizmo.CurrentRay, out var point, true ) ) + { + tr.Hit = true; + tr.Normal = plane.Normal; + tr.EndPosition = point; + } + } + + if ( !tr.Hit ) return; + + tr.EndPosition = GridSnap( tr.EndPosition, tr.Normal ); + + if ( Gizmo.WasLeftMousePressed ) + { + _plane = new Plane( tr.EndPosition, tr.Normal ); + _points.Add( tr.EndPosition ); + } + + if ( !Gizmo.HasHovered ) + { + var size = 3.0f * Gizmo.Camera.Position.Distance( tr.EndPosition ) / 1000.0f; + Gizmo.Draw.Color = Color.White; + Gizmo.Draw.SolidSphere( tr.EndPosition, size ); + } + } + + public override Widget CreateWidget() + { + return new PolygonEditorWidget( this ); + } + + [WideMode] + public float Height + { + get; + set + { + if ( field == value ) return; + + field = value; + + BuildPreview(); + } + } + + [WideMode] + public bool Hollow + { + get; + set + { + if ( field == value ) return; + + field = value; + + BuildPreview(); + } + } + + class PolygonEditorWidget : ToolSidebarWidget + { + readonly PolygonEditor _editor; + + public PolygonEditorWidget( PolygonEditor editor ) + { + _editor = editor; + + Layout.Margin = 0; + + { + var group = AddGroup( "Polygon Properties" ); + var row = group.AddRow(); + var so = editor.GetSerialized(); + row.Add( ControlSheetRow.Create( so.GetProperty( nameof( editor.Height ) ) ) ); + row.Add( ControlSheetRow.Create( so.GetProperty( nameof( editor.Hollow ) ) ) ).FixedWidth = 60; + } + + Layout.AddStretchCell(); + } + + [Shortcut( "editor.delete", "DEL", typeof( SceneDock ) )] + public void DeletePoint() => _editor.RemovePoint(); + } + + static Vector3 GridSnap( Vector3 point, Vector3 normal ) + { + var basis = Rotation.LookAt( normal ); + return Gizmo.Snap( point * basis.Inverse, new Vector3( 0, 1, 1 ) ) * basis; + } + + static void PlaneEquation( IReadOnlyList vertices, out Vector3 outNormal ) + { + var normal = Vector3.Zero; + var count = vertices.Count; + + for ( var i = 0; i < count; i++ ) + { + var u = vertices[i]; + var v = vertices[(i + 1) % count]; + normal.x += (u.y - v.y) * (u.z + v.z); + normal.y += (u.z - v.z) * (u.x + v.x); + normal.z += (u.x - v.x) * (u.y + v.y); + } + + outNormal = normal.Normal; + } +} diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveEditor.cs b/game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveEditor.cs new file mode 100644 index 00000000..76fd116c --- /dev/null +++ b/game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveEditor.cs @@ -0,0 +1,30 @@ +namespace Editor.MeshEditor; + +public abstract class PrimitiveEditor +{ + private readonly TypeDescription _type; + + protected PrimitiveTool Tool { get; private init; } + + public string Title => _type.Title; + public string Icon => _type.Icon; + + public virtual bool CanBuild => false; + public virtual bool InProgress => false; + + protected PrimitiveEditor( PrimitiveTool tool ) + { + Tool = tool; + _type = EditorTypeLibrary.GetType( GetType() ); + } + + public abstract void OnUpdate( SceneTrace trace ); + public abstract void OnCancel(); + public abstract PolygonMesh Build(); + + public virtual void OnCreated( MeshComponent component ) + { + } + + public virtual Widget CreateWidget() => null; +} diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveTool.UI.cs b/game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveTool.UI.cs new file mode 100644 index 00000000..9276837f --- /dev/null +++ b/game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveTool.UI.cs @@ -0,0 +1,202 @@ +using Sandbox.UI; + +namespace Editor.MeshEditor; + +partial class PrimitiveTool +{ + public override Widget CreateToolSidebar() + { + return new PrimitiveToolWidget( this ); + } + + public class PrimitiveToolWidget : ToolSidebarWidget + { + readonly PrimitiveTool _tool; + readonly Widget _settingsWidget; + readonly Button _createButton; + readonly Button _cancelButton; + readonly IconButton _iconLabel; + readonly Label.Header _titleLabel; + + void OnEditorSelected( Type type ) + { + _tool.Editor = EditorTypeLibrary.Create( type, [_tool] ); + + UpdateTitle(); + BuildSettings(); + } + + void BuildSettings() + { + using var x = SuspendUpdates.For( this ); + + var widget = _tool.Editor?.CreateWidget(); + if ( widget is null ) + { + _settingsWidget.Hide(); + } + else + { + _settingsWidget.Layout.Clear( true ); + _settingsWidget.Layout.Add( widget ); + _settingsWidget.Show(); + } + } + + [EditorEvent.Frame] + public void Frame() + { + UpdateButtons(); + } + + void UpdateButtons() + { + _createButton?.Enabled = _tool.Editor is not null && _tool.Editor.CanBuild; + _cancelButton?.Enabled = _tool.Editor is not null && _tool.Editor.InProgress; + } + + void UpdateTitle() + { + var type = EditorTypeLibrary.GetType( _tool.Editor?.GetType() ); + if ( type is null ) return; + + _iconLabel?.Icon = type.Icon; + _titleLabel?.Text = $"Create {type.Title}"; + } + + public PrimitiveToolWidget( PrimitiveTool tool ) : base() + { + _tool = tool; + + { + var titleRow = Layout.AddRow(); + titleRow.Margin = new Margin( 0, 0, 0, 8 ); + titleRow.Spacing = 4; + + _iconLabel = titleRow.Add( new IconButton( null ), 0 ); + _iconLabel.IconSize = 18; + _iconLabel.Background = Color.Transparent; + _iconLabel.Foreground = Theme.Blue; + _titleLabel = titleRow.Add( new Label.Header( null ), 1 ); + + UpdateTitle(); + } + + { + var list = new PrimitiveListView( this ); + list.FixedWidth = 200; + list.SetItems( GetBuilderTypes() ); + list.SelectItem( list.Items.FirstOrDefault( x => (x as TypeDescription).TargetType == tool.Editor?.GetType() ) ); + list.ItemSelected = ( e ) => OnEditorSelected( (e as TypeDescription).TargetType ); + list.BuildLayout(); + + var group = AddGroup( "Primitive Type" ); + group.Add( list ); + } + + { + Layout.AddSpacingCell( 4 ); + + var row = Layout.AddRow(); + row.Spacing = 4; + + _createButton = row.Add( new Button( "Create", "done" ) + { + Clicked = Create, + ToolTip = "[Create " + EditorShortcuts.GetKeys( "mesh.primitive-tool-create" ) + "]", + } ); + + _cancelButton = row.Add( new Button( "Cancel", "close" ) + { + Clicked = Cancel, + ToolTip = "[Cancel " + EditorShortcuts.GetKeys( "mesh.primitive-tool-cancel" ) + "]" + } ); + + UpdateButtons(); + + Layout.AddSpacingCell( 4 ); + } + + { + _settingsWidget = new ToolSidebarWidget( this ); + _settingsWidget.Layout.Margin = 0; + Layout.Add( _settingsWidget ); + + BuildSettings(); + } + + Layout.AddStretchCell(); + } + + [Shortcut( "mesh.primitive-tool-create", "enter", ShortcutType.Application )] + void Create() => _tool.Create(); + + [Shortcut( "mesh.primitive-tool-cancel", "ESC", ShortcutType.Application )] + void Cancel() => _tool.Cancel(); + + static IEnumerable GetBuilderTypes() + { + return EditorTypeLibrary.GetTypes() + .Where( x => !x.IsAbstract ) + .OrderBy( x => x.Name ); + } + } +} + +public class PrimitiveListView : ListView +{ + public PrimitiveListView( Widget parent ) : base( parent ) + { + ItemSpacing = 0; + ItemSize = 32; + Margin = 0; + + HorizontalScrollbarMode = ScrollbarMode.Off; + VerticalScrollbarMode = ScrollbarMode.Off; + } + + protected override void DoLayout() + { + base.DoLayout(); + + BuildLayout(); + } + + public void BuildLayout() + { + var rect = CanvasRect; + var itemSize = ItemSize; + var itemSpacing = ItemSpacing; + var itemsPerRow = 1; + var itemCount = Items.Count(); + + if ( itemSize.x > 0 ) itemsPerRow = ((rect.Width + itemSpacing.x) / (itemSize.x + itemSpacing.x)).FloorToInt(); + itemsPerRow = Math.Max( 1, itemsPerRow ); + + var rowCount = MathX.CeilToInt( itemCount / (float)itemsPerRow ); + FixedHeight = rowCount * (itemSize.y + itemSpacing.y) + Margin.EdgeSize.y; + } + + protected override string GetTooltip( object obj ) + { + var builder = obj as TypeDescription; + var displayInfo = DisplayInfo.ForType( builder.TargetType ); + return displayInfo.Name; + } + + protected override void PaintItem( VirtualWidget item ) + { + if ( item.Selected ) + { + Paint.ClearPen(); + Paint.SetBrush( Theme.Blue ); + Paint.DrawRect( item.Rect, 4 ); + } + + var builder = item.Object as TypeDescription; + var displayInfo = DisplayInfo.ForType( builder.TargetType ); + + Paint.SetPen( item.Selected || item.Hovered ? Color.White : Color.Gray ); + Paint.DrawIcon( item.Rect, displayInfo.Icon ?? "square", HeaderBarStyle.IconSize ); + } +} diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveTool.cs b/game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveTool.cs new file mode 100644 index 00000000..785b3c3d --- /dev/null +++ b/game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveTool.cs @@ -0,0 +1,66 @@ +namespace Editor.MeshEditor; + +/// +/// Create different types of primitive meshes. +/// +[Title( "Primitive Tool" )] +[Icon( "view_in_ar" )] +[Alias( "tools.primitive-tool" )] +public partial class PrimitiveTool( MeshTool tool ) : EditorTool +{ + public PrimitiveEditor Editor { get; private set; } + + public Material ActiveMaterial => tool.ActiveMaterial; + + public override void OnEnabled() + { + Editor = EditorTypeLibrary.Create( typeof( BlockEditor ), [this] ); + } + + public override void OnDisabled() + { + Create(); + + Editor = null; + } + + public void Create() + { + if ( Editor is null ) return; + if ( !Editor.CanBuild ) return; + + var mesh = Editor.Build(); + if ( mesh is null ) return; + + var name = Editor.Title; + + using var scope = SceneEditorSession.Scope(); + using ( SceneEditorSession.Active.UndoScope( $"Create {name}" ) + .WithGameObjectCreations() + .Push() ) + { + var bounds = mesh.CalculateBounds(); + mesh.ApplyTransform( new Transform( -bounds.Center ) ); + + var go = new GameObject( true, name ); + go.WorldPosition = bounds.Center; + var c = go.Components.Create( false ); + c.Mesh = mesh; + c.SmoothingAngle = 40.0f; + + Editor.OnCreated( c ); + + c.Enabled = true; + } + } + + public override void OnUpdate() + { + Editor?.OnUpdate( MeshTrace ); + } + + public void Cancel() + { + Editor?.OnCancel(); + } +} diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/SceneTraceExtensions.cs b/game/addons/tools/Code/Scene/Mesh/Tools/SceneTraceExtensions.cs new file mode 100644 index 00000000..24386d26 --- /dev/null +++ b/game/addons/tools/Code/Scene/Mesh/Tools/SceneTraceExtensions.cs @@ -0,0 +1,118 @@ + +namespace Editor.MeshEditor; + +static class SceneTraceMeshExtensions +{ + static Vector2 RayScreenPosition => SceneViewportWidget.MousePosition; + + public static MeshVertex GetClosestVertex( this SceneTrace trace, int radius ) + { + var point = RayScreenPosition; + var bestFace = TraceFace( trace, out var bestHitDistance ); + var bestVertex = bestFace.GetClosestVertex( point, radius ); + + if ( bestFace.IsValid() && bestVertex.IsValid() ) + return bestVertex; + + var results = TraceFaces( trace, radius, point ); + foreach ( var result in results ) + { + var face = result.MeshFace; + var hitDistance = result.Distance; + var vertex = face.GetClosestVertex( point, radius ); + if ( !vertex.IsValid() ) + continue; + + if ( hitDistance < bestHitDistance || !bestFace.IsValid() ) + { + bestHitDistance = hitDistance; + bestVertex = vertex; + bestFace = face; + } + } + + return bestVertex; + } + + public static MeshEdge GetClosestEdge( this SceneTrace trace, int radius ) + { + var point = RayScreenPosition; + var bestFace = TraceFace( trace, out var bestHitDistance ); + var hitPosition = Gizmo.CurrentRay.Project( bestHitDistance ); + var bestEdge = bestFace.GetClosestEdge( hitPosition, point, radius ); + + if ( bestFace.IsValid() && bestEdge.IsValid() ) + return bestEdge; + + var results = TraceFaces( trace, radius, point ); + foreach ( var result in results ) + { + var face = result.MeshFace; + var hitDistance = result.Distance; + hitPosition = Gizmo.CurrentRay.Project( hitDistance ); + + var edge = face.GetClosestEdge( hitPosition, point, radius ); + if ( !edge.IsValid() ) + continue; + + if ( hitDistance < bestHitDistance || !bestFace.IsValid() ) + { + bestHitDistance = hitDistance; + bestEdge = edge; + bestFace = face; + } + } + + return bestEdge; + } + + static MeshFace TraceFace( this SceneTrace trace, out float distance ) + { + distance = default; + + var result = trace.Run(); + if ( !result.Hit || result.Component is not MeshComponent component ) + return default; + + distance = result.Distance; + var face = component.Mesh.TriangleToFace( result.Triangle ); + return new MeshFace( component, face ); + } + + struct MeshFaceTraceResult + { + public MeshFace MeshFace; + public float Distance; + } + + static List TraceFaces( this SceneTrace trace, int radius, Vector2 point ) + { + var rays = new List { Gizmo.CurrentRay }; + for ( var ring = 1; ring < radius; ring++ ) + { + rays.Add( Gizmo.Camera.GetRay( point + new Vector2( 0, ring ) ) ); + rays.Add( Gizmo.Camera.GetRay( point + new Vector2( ring, 0 ) ) ); + rays.Add( Gizmo.Camera.GetRay( point + new Vector2( 0, -ring ) ) ); + rays.Add( Gizmo.Camera.GetRay( point + new Vector2( -ring, 0 ) ) ); + } + + var faces = new List(); + var faceHash = new HashSet(); + foreach ( var ray in rays ) + { + var result = trace.Ray( ray, Gizmo.RayDepth ).Run(); + if ( !result.Hit ) + continue; + + if ( result.Component is not MeshComponent component ) + continue; + + var face = component.Mesh.TriangleToFace( result.Triangle ); + var faceElement = new MeshFace( component, face ); + if ( faceHash.Add( faceElement ) ) + faces.Add( new MeshFaceTraceResult { MeshFace = faceElement, Distance = result.Distance } ); + } + + return faces; + } +} diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/SelectionTool.cs b/game/addons/tools/Code/Scene/Mesh/Tools/SelectionTool.cs index 4653b1d9..f5bbf817 100644 --- a/game/addons/tools/Code/Scene/Mesh/Tools/SelectionTool.cs +++ b/game/addons/tools/Code/Scene/Mesh/Tools/SelectionTool.cs @@ -51,8 +51,6 @@ public abstract class SelectionTool( MeshTool tool ) : SelectionTool where T protected virtual bool HasMoveMode => true; - public static Vector2 RayScreenPosition => SceneViewportWidget.MousePosition; - public static bool IsMultiSelecting => Application.KeyboardModifiers.HasFlag( KeyboardModifiers.Ctrl ) || Application.KeyboardModifiers.HasFlag( KeyboardModifiers.Shift ); @@ -510,80 +508,6 @@ public abstract class SelectionTool( MeshTool tool ) : SelectionTool where T _undoScope = null; } - public MeshVertex GetClosestVertex( int radius ) - { - var point = RayScreenPosition; - var bestFace = TraceFace( out var bestHitDistance ); - var bestVertex = bestFace.GetClosestVertex( point, radius ); - - if ( bestFace.IsValid() && bestVertex.IsValid() ) - return bestVertex; - - var results = TraceFaces( radius, point ); - foreach ( var result in results ) - { - var face = result.MeshFace; - var hitDistance = result.Distance; - var vertex = face.GetClosestVertex( point, radius ); - if ( !vertex.IsValid() ) - continue; - - if ( hitDistance < bestHitDistance || !bestFace.IsValid() ) - { - bestHitDistance = hitDistance; - bestVertex = vertex; - bestFace = face; - } - } - - return bestVertex; - } - - public MeshEdge GetClosestEdge( int radius ) - { - var point = RayScreenPosition; - var bestFace = TraceFace( out var bestHitDistance ); - var hitPosition = Gizmo.CurrentRay.Project( bestHitDistance ); - var bestEdge = bestFace.GetClosestEdge( hitPosition, point, radius ); - - if ( bestFace.IsValid() && bestEdge.IsValid() ) - return bestEdge; - - var results = TraceFaces( radius, point ); - foreach ( var result in results ) - { - var face = result.MeshFace; - var hitDistance = result.Distance; - hitPosition = Gizmo.CurrentRay.Project( hitDistance ); - - var edge = face.GetClosestEdge( hitPosition, point, radius ); - if ( !edge.IsValid() ) - continue; - - if ( hitDistance < bestHitDistance || !bestFace.IsValid() ) - { - bestHitDistance = hitDistance; - bestEdge = edge; - bestFace = face; - } - } - - return bestEdge; - } - - private MeshFace TraceFace( out float distance ) - { - distance = default; - - var result = MeshTrace.Run(); - if ( !result.Hit || result.Component is not MeshComponent component ) - return default; - - distance = result.Distance; - var face = component.Mesh.TriangleToFace( result.Triangle ); - return new MeshFace( component, face ); - } - public MeshFace TraceFace() { if ( IsBoxSelecting ) @@ -597,43 +521,6 @@ public abstract class SelectionTool( MeshTool tool ) : SelectionTool where T return new MeshFace( component, face ); } - private struct MeshFaceTraceResult - { - public MeshFace MeshFace; - public float Distance; - } - - private List TraceFaces( int radius, Vector2 point ) - { - var rays = new List { Gizmo.CurrentRay }; - for ( var ring = 1; ring < radius; ring++ ) - { - rays.Add( Gizmo.Camera.GetRay( point + new Vector2( 0, ring ) ) ); - rays.Add( Gizmo.Camera.GetRay( point + new Vector2( ring, 0 ) ) ); - rays.Add( Gizmo.Camera.GetRay( point + new Vector2( 0, -ring ) ) ); - rays.Add( Gizmo.Camera.GetRay( point + new Vector2( -ring, 0 ) ) ); - } - - var faces = new List(); - var faceHash = new HashSet(); - foreach ( var ray in rays ) - { - var result = MeshTrace.Ray( ray, Gizmo.RayDepth ).Run(); - if ( !result.Hit ) - continue; - - if ( result.Component is not MeshComponent component ) - continue; - - var face = component.Mesh.TriangleToFace( result.Triangle ); - var faceElement = new MeshFace( component, face ); - if ( faceHash.Add( faceElement ) ) - faces.Add( new MeshFaceTraceResult { MeshFace = faceElement, Distance = result.Distance } ); - } - - return faces; - } - public static Vector3 ComputeTextureVAxis( Vector3 normal ) => FaceDownVectors[GetOrientationForPlane( normal )]; private static int GetOrientationForPlane( Vector3 plane ) diff --git a/game/addons/tools/Code/Scene/Mesh/Tools/VertexTool.cs b/game/addons/tools/Code/Scene/Mesh/Tools/VertexTool.cs index a9bc30cb..9e0b5571 100644 --- a/game/addons/tools/Code/Scene/Mesh/Tools/VertexTool.cs +++ b/game/addons/tools/Code/Scene/Mesh/Tools/VertexTool.cs @@ -69,7 +69,7 @@ public sealed partial class VertexTool( MeshTool tool ) : SelectionTool