mirror of
https://github.com/Facepunch/sbox-public.git
synced 2025-12-23 22:48:07 -05:00
Mapping polygon tool (#3620)
This commit is contained in:
350
game/addons/tools/Code/Scene/Mesh/Tools/BlockEditor.cs
Normal file
350
game/addons/tools/Code/Scene/Mesh/Tools/BlockEditor.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" ) )
|
||||
|
||||
@@ -110,7 +110,6 @@ public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool
|
||||
|
||||
public override void OnEnabled()
|
||||
{
|
||||
Selection.Clear();
|
||||
OnSelectionChanged();
|
||||
|
||||
var undo = SceneEditorSession.Active.UndoSystem;
|
||||
|
||||
@@ -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 ) );
|
||||
|
||||
@@ -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 );
|
||||
|
||||
344
game/addons/tools/Code/Scene/Mesh/Tools/PolygonEditor.cs
Normal file
344
game/addons/tools/Code/Scene/Mesh/Tools/PolygonEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
30
game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveEditor.cs
Normal file
30
game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveEditor.cs
Normal 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;
|
||||
}
|
||||
202
game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveTool.UI.cs
Normal file
202
game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveTool.UI.cs
Normal 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 );
|
||||
}
|
||||
}
|
||||
66
game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveTool.cs
Normal file
66
game/addons/tools/Code/Scene/Mesh/Tools/PrimitiveTool.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
118
game/addons/tools/Code/Scene/Mesh/Tools/SceneTraceExtensions.cs
Normal file
118
game/addons/tools/Code/Scene/Mesh/Tools/SceneTraceExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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 )
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user