Mapping polygon tool (#3620)

This commit is contained in:
Layla
2025-12-15 10:35:09 +00:00
committed by GitHub
parent 6e6bd37ac8
commit 0697a99bb8
14 changed files with 1119 additions and 659 deletions

View File

@@ -0,0 +1,350 @@
namespace Editor.MeshEditor;
/// <summary>
/// Create stuff as long as it fits in a box, woah crazy.
/// </summary>
[Title( "Block" ), Icon( "view_in_ar" )]
public sealed class BlockEditor( PrimitiveTool tool ) : PrimitiveEditor( tool )
{
PrimitiveBuilder _primitive = EditorTypeLibrary.Create<PrimitiveBuilder>( 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<TypeDescription> GetBuilderTypes()
{
return EditorTypeLibrary.GetTypes<PrimitiveBuilder>()
.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<PrimitiveBuilder>( 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();
}
}
}

View File

@@ -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 );
}
}

View File

@@ -1,426 +0,0 @@
namespace Editor.MeshEditor;
/// <summary>
/// Create new shapes by dragging out a block
/// </summary>
[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<PrimitiveBuilder> _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<GameObject>().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<PrimitiveBuilder>() );
}
_primitive = _primitives.FirstOrDefault();
_primitive.Material = meshTool.ActiveMaterial;
}
private static IEnumerable<TypeDescription> GetBuilderTypes()
{
return EditorTypeLibrary.GetTypes<PrimitiveBuilder>()
.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<MeshComponent>( 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<GameObject>().Any() )
{
return;
}
EditorToolManager.SetSubTool( nameof( PositionMode ) );
}
public override void OnUpdate()
{
if ( Selection.OfType<GameObject>().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 );
}
}
}
}

View File

@@ -17,7 +17,7 @@ public sealed partial class EdgeTool( MeshTool tool ) : SelectionTool<MeshEdge>(
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<MeshEdge>(
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<MeshEdge>(
private void SelectEdge()
{
var edge = GetClosestEdge( 8 );
var edge = MeshTrace.GetClosestEdge( 8 );
if ( edge.IsValid() )
{
using ( Gizmo.Scope( "Edge Hover" ) )

View File

@@ -110,7 +110,6 @@ public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool
public override void OnEnabled()
{
Selection.Clear();
OnSelectionChanged();
var undo = SceneEditorSession.Active.UndoSystem;

View File

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

View File

@@ -16,7 +16,7 @@ public partial class MeshTool : EditorTool
public override IEnumerable<EditorTool> 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 );

View File

@@ -0,0 +1,344 @@
using System.Runtime.InteropServices;
namespace Editor.MeshEditor;
/// <summary>
/// Draw a polygon mesh.
/// </summary>
[Title( "Polygon" ), Icon( "pentagon" )]
public sealed class PolygonEditor( PrimitiveTool tool ) : PrimitiveEditor( tool )
{
Plane _plane;
readonly List<Vector3> _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<Vector3> 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;
}
}

View File

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

View File

@@ -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<PrimitiveEditor>( 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<TypeDescription> GetBuilderTypes()
{
return EditorTypeLibrary.GetTypes<PrimitiveEditor>()
.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 );
}
}

View File

@@ -0,0 +1,66 @@
namespace Editor.MeshEditor;
/// <summary>
/// Create different types of primitive meshes.
/// </summary>
[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<PrimitiveEditor>( 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<MeshComponent>( 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();
}
}

View File

@@ -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<MeshFaceTraceResult> TraceFaces( this SceneTrace trace, int radius, Vector2 point )
{
var rays = new List<Ray> { 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<MeshFaceTraceResult>();
var faceHash = new HashSet<MeshFace>();
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;
}
}

View File

@@ -51,8 +51,6 @@ public abstract class SelectionTool<T>( 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<T>( 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<T>( MeshTool tool ) : SelectionTool where T
return new MeshFace( component, face );
}
private struct MeshFaceTraceResult
{
public MeshFace MeshFace;
public float Distance;
}
private List<MeshFaceTraceResult> TraceFaces( int radius, Vector2 point )
{
var rays = new List<Ray> { 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<MeshFaceTraceResult>();
var faceHash = new HashSet<MeshFace>();
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 )

View File

@@ -69,7 +69,7 @@ public sealed partial class VertexTool( MeshTool tool ) : SelectionTool<MeshVert
using var scope = Gizmo.Scope( "VertexTool" );
var closestVertex = GetClosestVertex( 8 );
var closestVertex = MeshTrace.GetClosestVertex( 8 );
if ( closestVertex.IsValid() )
Gizmo.Hitbox.TrySetHovered( closestVertex.PositionWorld );
@@ -93,7 +93,7 @@ public sealed partial class VertexTool( MeshTool tool ) : SelectionTool<MeshVert
private void SelectVertex()
{
var vertex = GetClosestVertex( 8 );
var vertex = MeshTrace.GetClosestVertex( 8 );
if ( vertex.IsValid() )
{
using ( Gizmo.ObjectScope( vertex.Component.GameObject, vertex.Transform ) )
@@ -112,7 +112,7 @@ public sealed partial class VertexTool( MeshTool tool ) : SelectionTool<MeshVert
private void SelectAllVertices()
{
var vertex = GetClosestVertex( 8 );
var vertex = MeshTrace.GetClosestVertex( 8 );
if ( !vertex.IsValid() )
return;