mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-05-19 12:27:52 -04:00
mapping tool mesh selection mode (#3588)
This commit is contained in:
@@ -18,7 +18,7 @@ public sealed class MeshComponent : Collider, ExecuteInEditor, ITintable, IMater
|
||||
Hull
|
||||
}
|
||||
|
||||
[Property, Order( 0 )]
|
||||
[Property, Hide]
|
||||
public PolygonMesh Mesh
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -6,80 +6,12 @@ namespace Editor.MeshEditor;
|
||||
/// </summary>
|
||||
public abstract class MoveMode
|
||||
{
|
||||
protected IReadOnlyDictionary<MeshVertex, Vector3> TransformVertices => _transformVertices;
|
||||
|
||||
private readonly Dictionary<MeshVertex, Vector3> _transformVertices = [];
|
||||
private List<MeshFace> _transformFaces;
|
||||
private IDisposable _undoScope;
|
||||
|
||||
public void Update( SelectionTool tool )
|
||||
{
|
||||
if ( !tool.Selection.OfType<IMeshElement>().Any() )
|
||||
return;
|
||||
|
||||
OnUpdate( tool );
|
||||
}
|
||||
|
||||
protected virtual void OnUpdate( SelectionTool tool )
|
||||
{
|
||||
}
|
||||
|
||||
protected void StartDrag( SelectionTool tool )
|
||||
{
|
||||
if ( _transformVertices.Count != 0 )
|
||||
return;
|
||||
|
||||
var components = tool.Selection.OfType<IMeshElement>()
|
||||
.Select( x => x.Component )
|
||||
.Distinct();
|
||||
|
||||
_undoScope ??= SceneEditorSession.Active.UndoScope( $"{(Gizmo.IsShiftPressed ? "Extrude" : "Move")} Selection" )
|
||||
.WithComponentChanges( components )
|
||||
.Push();
|
||||
|
||||
if ( Gizmo.IsShiftPressed )
|
||||
{
|
||||
_transformFaces = tool.ExtrudeSelection();
|
||||
}
|
||||
|
||||
foreach ( var vertex in tool.VertexSelection )
|
||||
{
|
||||
_transformVertices[vertex] = vertex.PositionWorld;
|
||||
}
|
||||
}
|
||||
|
||||
protected void UpdateDrag()
|
||||
{
|
||||
if ( _transformFaces is not null )
|
||||
{
|
||||
foreach ( var group in _transformFaces.GroupBy( x => x.Component ) )
|
||||
{
|
||||
var mesh = group.Key.Mesh;
|
||||
var faces = group.Select( x => x.Handle ).ToArray();
|
||||
|
||||
foreach ( var face in faces )
|
||||
{
|
||||
mesh.TextureAlignToGrid( mesh.Transform, face );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var meshes = TransformVertices
|
||||
.Select( x => x.Key.Component.Mesh )
|
||||
.Distinct();
|
||||
|
||||
foreach ( var mesh in meshes )
|
||||
{
|
||||
mesh.ComputeFaceTextureCoordinatesFromParameters();
|
||||
}
|
||||
}
|
||||
|
||||
protected void EndDrag()
|
||||
{
|
||||
_transformVertices.Clear();
|
||||
_transformFaces = null;
|
||||
|
||||
_undoScope?.Dispose();
|
||||
_undoScope = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public sealed class PivotMode : MoveMode
|
||||
{
|
||||
var origin = tool.Pivot;
|
||||
|
||||
if ( !Gizmo.Pressed.Any && Gizmo.HasMouseFocus )
|
||||
if ( !Gizmo.Pressed.Any )
|
||||
{
|
||||
_pivot = origin;
|
||||
_basis = tool.CalculateSelectionBasis();
|
||||
|
||||
@@ -20,9 +20,9 @@ public sealed class PositionMode : MoveMode
|
||||
{
|
||||
var origin = tool.Pivot;
|
||||
|
||||
if ( !Gizmo.Pressed.Any && Gizmo.HasMouseFocus )
|
||||
if ( !Gizmo.Pressed.Any )
|
||||
{
|
||||
EndDrag();
|
||||
tool.EndDrag();
|
||||
|
||||
_basis = tool.CalculateSelectionBasis();
|
||||
_origin = origin;
|
||||
@@ -45,16 +45,9 @@ public sealed class PositionMode : MoveMode
|
||||
|
||||
moveDelta -= _origin;
|
||||
|
||||
StartDrag( tool );
|
||||
|
||||
foreach ( var entry in TransformVertices )
|
||||
{
|
||||
var position = entry.Value + moveDelta;
|
||||
var transform = entry.Key.Transform;
|
||||
entry.Key.Component.Mesh.SetVertexPosition( entry.Key.Handle, transform.PointToLocal( position ) );
|
||||
}
|
||||
|
||||
UpdateDrag();
|
||||
tool.StartDrag();
|
||||
tool.Translate( moveDelta );
|
||||
tool.UpdateDrag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ public sealed class RotateMode : MoveMode
|
||||
|
||||
protected override void OnUpdate( SelectionTool tool )
|
||||
{
|
||||
if ( !Gizmo.Pressed.Any && Gizmo.HasMouseFocus )
|
||||
if ( !Gizmo.Pressed.Any )
|
||||
{
|
||||
EndDrag();
|
||||
tool.EndDrag();
|
||||
|
||||
_moveDelta = default;
|
||||
_basis = tool.CalculateSelectionBasis();
|
||||
@@ -35,22 +35,11 @@ public sealed class RotateMode : MoveMode
|
||||
{
|
||||
_moveDelta += angleDelta;
|
||||
|
||||
StartDrag( tool );
|
||||
|
||||
var snapDelta = Gizmo.Snap( _moveDelta, _moveDelta );
|
||||
|
||||
foreach ( var entry in TransformVertices )
|
||||
{
|
||||
var rotation = _basis * snapDelta * _basis.Inverse;
|
||||
var position = entry.Value - _origin;
|
||||
position *= rotation;
|
||||
position += _origin;
|
||||
|
||||
var transform = entry.Key.Transform;
|
||||
entry.Key.Component.Mesh.SetVertexPosition( entry.Key.Handle, transform.PointToLocal( position ) );
|
||||
}
|
||||
|
||||
UpdateDrag();
|
||||
tool.StartDrag();
|
||||
tool.Rotate( _origin, _basis, snapDelta );
|
||||
tool.UpdateDrag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,16 +19,13 @@ public sealed class ScaleMode : MoveMode
|
||||
|
||||
protected override void OnUpdate( SelectionTool tool )
|
||||
{
|
||||
if ( !Gizmo.Pressed.Any && Gizmo.HasMouseFocus )
|
||||
if ( !Gizmo.Pressed.Any )
|
||||
{
|
||||
EndDrag();
|
||||
tool.EndDrag();
|
||||
|
||||
_moveDelta = default;
|
||||
_basis = tool.CalculateSelectionBasis();
|
||||
|
||||
var bounds = BBox.FromPoints( tool.VertexSelection
|
||||
.Select( x => _basis.Inverse * x.PositionWorld ) );
|
||||
|
||||
var bounds = tool.CalculateLocalBounds();
|
||||
_size = bounds.Size;
|
||||
_origin = tool.Pivot;
|
||||
|
||||
@@ -52,20 +49,9 @@ public sealed class ScaleMode : MoveMode
|
||||
_size.z != 0 ? size.z / _size.z : 1
|
||||
);
|
||||
|
||||
StartDrag( tool );
|
||||
|
||||
foreach ( var entry in TransformVertices )
|
||||
{
|
||||
var position = (entry.Value - _origin) * _basis.Inverse;
|
||||
position *= scale;
|
||||
position *= _basis;
|
||||
position += _origin;
|
||||
|
||||
var transform = entry.Key.Transform;
|
||||
entry.Key.Component.Mesh.SetVertexPosition( entry.Key.Handle, transform.PointToLocal( position ) );
|
||||
}
|
||||
|
||||
UpdateDrag();
|
||||
tool.StartDrag();
|
||||
tool.Scale( _origin, _basis, scale );
|
||||
tool.UpdateDrag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ partial class EdgeTool
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut( "editor.delete", "DEL", typeof( SceneViewportWidget ) )]
|
||||
[Shortcut( "editor.delete", "DEL", typeof( SceneDock ) )]
|
||||
private void DeleteSelection()
|
||||
{
|
||||
var groups = _edges.GroupBy( face => face.Component );
|
||||
|
||||
@@ -112,7 +112,7 @@ partial class FaceTool
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut( "editor.delete", "DEL", typeof( SceneViewportWidget ) )]
|
||||
[Shortcut( "editor.delete", "DEL", typeof( SceneDock ) )]
|
||||
private void DeleteSelection()
|
||||
{
|
||||
var groups = _faces.GroupBy( face => face.Component );
|
||||
|
||||
181
game/addons/tools/Code/Scene/Mesh/Tools/MeshSelection.UI.cs
Normal file
181
game/addons/tools/Code/Scene/Mesh/Tools/MeshSelection.UI.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
|
||||
namespace Editor.MeshEditor;
|
||||
|
||||
partial class MeshSelection
|
||||
{
|
||||
public override Widget CreateToolSidebar()
|
||||
{
|
||||
return new MeshSelectionWidget( GetSerializedSelection(), this );
|
||||
}
|
||||
|
||||
public class MeshSelectionWidget : ToolSidebarWidget
|
||||
{
|
||||
readonly MeshComponent[] _meshes;
|
||||
readonly MeshSelection _tool;
|
||||
|
||||
public MeshSelectionWidget( SerializedObject so, MeshSelection tool ) : base()
|
||||
{
|
||||
_tool = tool;
|
||||
|
||||
AddTitle( "Mesh Mode", "layers" );
|
||||
|
||||
_meshes = so.Targets.OfType<GameObject>()
|
||||
.Select( x => x.GetComponent<MeshComponent>() )
|
||||
.Where( x => x.IsValid() )
|
||||
.ToArray();
|
||||
|
||||
{
|
||||
var group = AddGroup( "Move Mode" );
|
||||
var row = group.AddRow();
|
||||
row.Spacing = 8;
|
||||
tool.Tool.CreateMoveModeButtons( row );
|
||||
}
|
||||
|
||||
{
|
||||
var group = AddGroup( "Operations" );
|
||||
|
||||
var grid = Layout.Row();
|
||||
grid.Spacing = 4;
|
||||
|
||||
CreateButton( "Set Origin To Pivot", "gps_fixed", "mesh.set-origin-to-pivot", SetOriginToPivot, _meshes.Length > 0, grid );
|
||||
CreateButton( "Center Origin", "center_focus_strong", "mesh.center-origin", CenterOrigin, _meshes.Length > 0, grid );
|
||||
CreateButton( "Bake Scale", "straighten", "mesh.bake-scale", BakeScale, _meshes.Length > 0, grid );
|
||||
|
||||
grid.AddStretchCell();
|
||||
|
||||
group.Add( grid );
|
||||
}
|
||||
|
||||
{
|
||||
var group = AddGroup( "Pivot" );
|
||||
|
||||
var grid = Layout.Row();
|
||||
grid.Spacing = 4;
|
||||
|
||||
CreateButton( "Previous", "chevron_left", "mesh.previous-pivot", PreviousPivot, _meshes.Length > 0, grid );
|
||||
CreateButton( "Next", "chevron_right", "mesh.next-pivot", NextPivot, _meshes.Length > 0, grid );
|
||||
CreateButton( "Clear", "restart_alt", "mesh.clear-pivot", ClearPivot, _meshes.Length > 0, grid );
|
||||
CreateButton( "World Origin", "language", "mesh.zero-pivot", ZeroPivot, _meshes.Length > 0, grid );
|
||||
|
||||
grid.AddStretchCell();
|
||||
|
||||
group.Add( grid );
|
||||
}
|
||||
|
||||
Layout.AddStretchCell();
|
||||
}
|
||||
|
||||
[Shortcut( "mesh.previous-pivot", "N+MWheelDn", typeof( SceneDock ) )]
|
||||
public void PreviousPivot() => _tool.PreviousPivot();
|
||||
|
||||
[Shortcut( "mesh.next-pivot", "N+MWheelUp", typeof( SceneDock ) )]
|
||||
public void NextPivot() => _tool.NextPivot();
|
||||
|
||||
[Shortcut( "mesh.clear-pivot", "Home", typeof( SceneDock ) )]
|
||||
public void ClearPivot() => _tool.ClearPivot();
|
||||
|
||||
[Shortcut( "mesh.zero-pivot", "Ctrl+End", typeof( SceneDock ) )]
|
||||
public void ZeroPivot() => _tool.ZeroPivot();
|
||||
|
||||
[Shortcut( "mesh.set-origin-to-pivot", "Ctrl+D", typeof( SceneDock ) )]
|
||||
public void SetOriginToPivot()
|
||||
{
|
||||
using var scope = SceneEditorSession.Scope();
|
||||
|
||||
using ( SceneEditorSession.Active.UndoScope( "Set Origin To Pivot" )
|
||||
.WithGameObjectChanges( _meshes.Select( x => x.GameObject ), GameObjectUndoFlags.Properties )
|
||||
.WithComponentChanges( _meshes )
|
||||
.Push() )
|
||||
{
|
||||
foreach ( var mesh in _meshes )
|
||||
{
|
||||
SetMeshOrigin( mesh, _tool.Pivot );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut( "mesh.center-origin", "End", typeof( SceneDock ) )]
|
||||
public void CenterOrigin()
|
||||
{
|
||||
using var scope = SceneEditorSession.Scope();
|
||||
|
||||
using ( SceneEditorSession.Active.UndoScope( "Center Origin" )
|
||||
.WithGameObjectChanges( _meshes.Select( x => x.GameObject ), GameObjectUndoFlags.Properties )
|
||||
.WithComponentChanges( _meshes )
|
||||
.Push() )
|
||||
{
|
||||
foreach ( var mesh in _meshes )
|
||||
{
|
||||
CenterMeshOrigin( mesh );
|
||||
}
|
||||
}
|
||||
|
||||
_tool.ClearPivot();
|
||||
}
|
||||
|
||||
public void BakeScale()
|
||||
{
|
||||
using var scope = SceneEditorSession.Scope();
|
||||
|
||||
using ( SceneEditorSession.Active.UndoScope( "Bake Scale" )
|
||||
.WithGameObjectChanges( _meshes.Select( x => x.GameObject ), GameObjectUndoFlags.Properties )
|
||||
.WithComponentChanges( _meshes )
|
||||
.Push() )
|
||||
{
|
||||
foreach ( var mesh in _meshes )
|
||||
{
|
||||
BakeScale( mesh );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void CenterMeshOrigin( MeshComponent meshComponent )
|
||||
{
|
||||
if ( !meshComponent.IsValid() ) return;
|
||||
|
||||
var mesh = meshComponent.Mesh;
|
||||
if ( mesh is null ) return;
|
||||
|
||||
var children = meshComponent.GameObject.Children
|
||||
.Select( x => (GameObject: x, Transform: x.WorldTransform) )
|
||||
.ToArray();
|
||||
|
||||
var world = meshComponent.WorldTransform;
|
||||
var bounds = mesh.CalculateBounds( world );
|
||||
var center = bounds.Center;
|
||||
var localCenter = world.PointToLocal( center );
|
||||
meshComponent.WorldPosition = center;
|
||||
meshComponent.Mesh.ApplyTransform( new Transform( -localCenter ) );
|
||||
meshComponent.RebuildMesh();
|
||||
|
||||
foreach ( var child in children )
|
||||
{
|
||||
child.GameObject.WorldTransform = child.Transform;
|
||||
}
|
||||
}
|
||||
|
||||
static void SetMeshOrigin( MeshComponent meshComponent, Vector3 origin )
|
||||
{
|
||||
if ( !meshComponent.IsValid() ) return;
|
||||
|
||||
var mesh = meshComponent.Mesh;
|
||||
if ( mesh is null ) return;
|
||||
|
||||
var world = meshComponent.WorldTransform;
|
||||
var localCenter = world.PointToLocal( origin );
|
||||
meshComponent.WorldPosition = origin;
|
||||
meshComponent.Mesh.ApplyTransform( new Transform( -localCenter ) );
|
||||
meshComponent.RebuildMesh();
|
||||
}
|
||||
|
||||
static void BakeScale( MeshComponent meshComponent )
|
||||
{
|
||||
if ( !meshComponent.IsValid() ) return;
|
||||
|
||||
var scale = meshComponent.WorldScale;
|
||||
meshComponent.WorldScale = 1.0f;
|
||||
meshComponent.Mesh.Scale( scale );
|
||||
meshComponent.RebuildMesh();
|
||||
}
|
||||
}
|
||||
}
|
||||
368
game/addons/tools/Code/Scene/Mesh/Tools/MeshSelection.cs
Normal file
368
game/addons/tools/Code/Scene/Mesh/Tools/MeshSelection.cs
Normal file
@@ -0,0 +1,368 @@
|
||||
|
||||
namespace Editor.MeshEditor;
|
||||
|
||||
/// <summary>
|
||||
/// Select and edit mesh objects.
|
||||
/// </summary>
|
||||
[Title( "Mesh Selection" )]
|
||||
[Icon( "layers" )]
|
||||
[Alias( "tools.mesh-selection" )]
|
||||
[Group( "5" )]
|
||||
public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool
|
||||
{
|
||||
public MeshTool Tool { get; private init; } = tool;
|
||||
|
||||
readonly Dictionary<GameObject, Transform> _startPoints = [];
|
||||
IDisposable _undoScope;
|
||||
|
||||
MeshComponent[] _meshes = [];
|
||||
|
||||
public override void StartDrag()
|
||||
{
|
||||
if ( _startPoints.Count > 0 ) return;
|
||||
if ( _meshes.Length == 0 ) return;
|
||||
if ( _meshes.Any( x => !x.IsValid() ) ) return;
|
||||
|
||||
if ( Gizmo.IsShiftPressed )
|
||||
{
|
||||
_undoScope ??= SceneEditorSession.Active.UndoScope( "Duplicate Object(s)" )
|
||||
.WithGameObjectCreations()
|
||||
.Push();
|
||||
|
||||
DuplicateSelection();
|
||||
OnSelectionChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
_undoScope ??= SceneEditorSession.Active.UndoScope( "Transform Object(s)" )
|
||||
.WithGameObjectChanges( _meshes.Select( x => x.GameObject ), GameObjectUndoFlags.Properties )
|
||||
.Push();
|
||||
}
|
||||
|
||||
foreach ( var mesh in _meshes )
|
||||
{
|
||||
_startPoints[mesh.GameObject] = mesh.WorldTransform;
|
||||
}
|
||||
}
|
||||
|
||||
public override void EndDrag()
|
||||
{
|
||||
_startPoints.Clear();
|
||||
|
||||
_undoScope?.Dispose();
|
||||
_undoScope = null;
|
||||
}
|
||||
|
||||
public override void Translate( Vector3 delta )
|
||||
{
|
||||
foreach ( var entry in _startPoints )
|
||||
{
|
||||
entry.Key.WorldPosition = entry.Value.Position + delta;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Rotate( Vector3 origin, Rotation basis, Rotation delta )
|
||||
{
|
||||
foreach ( var entry in _startPoints )
|
||||
{
|
||||
var rot = basis * delta * basis.Inverse;
|
||||
var position = entry.Value.Position - origin;
|
||||
position *= rot;
|
||||
position += origin;
|
||||
rot *= entry.Value.Rotation;
|
||||
var scale = entry.Value.Scale;
|
||||
entry.Key.WorldTransform = new Transform( position, rot, scale );
|
||||
}
|
||||
}
|
||||
|
||||
public override void Scale( Vector3 origin, Rotation basis, Vector3 deltaScale )
|
||||
{
|
||||
foreach ( var entry in _startPoints )
|
||||
{
|
||||
var position = entry.Value.Position - origin;
|
||||
position *= basis.Inverse;
|
||||
position *= deltaScale;
|
||||
position *= basis;
|
||||
position += origin;
|
||||
|
||||
var scale = entry.Value.Scale * deltaScale;
|
||||
|
||||
entry.Key.WorldTransform = new Transform(
|
||||
position,
|
||||
entry.Value.Rotation,
|
||||
scale
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public override BBox CalculateLocalBounds()
|
||||
{
|
||||
return CalculateSelectionBounds();
|
||||
}
|
||||
|
||||
public override Rotation CalculateSelectionBasis()
|
||||
{
|
||||
if ( Gizmo.Settings.GlobalSpace ) return Rotation.Identity;
|
||||
|
||||
var mesh = _meshes.FirstOrDefault();
|
||||
return mesh.IsValid() ? mesh.WorldRotation : Rotation.Identity;
|
||||
}
|
||||
|
||||
public override void OnEnabled()
|
||||
{
|
||||
Selection.Clear();
|
||||
OnSelectionChanged();
|
||||
|
||||
var undo = SceneEditorSession.Active.UndoSystem;
|
||||
undo.OnUndo += OnUndoRedo;
|
||||
undo.OnRedo += OnUndoRedo;
|
||||
}
|
||||
|
||||
public override void OnDisabled()
|
||||
{
|
||||
var undo = SceneEditorSession.Active.UndoSystem;
|
||||
undo.OnUndo -= OnUndoRedo;
|
||||
undo.OnRedo -= OnUndoRedo;
|
||||
}
|
||||
|
||||
void OnUndoRedo( object _ )
|
||||
{
|
||||
OnSelectionChanged();
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
UpdateMoveMode();
|
||||
UpdateHovered();
|
||||
UpdateSelectionMode();
|
||||
DrawBounds();
|
||||
}
|
||||
|
||||
void UpdateMoveMode()
|
||||
{
|
||||
if ( Tool is null ) return;
|
||||
if ( Tool.MoveMode is null ) return;
|
||||
if ( _meshes.Length == 0 ) return;
|
||||
if ( _meshes.Any( x => !x.IsValid() ) ) return;
|
||||
|
||||
Tool.MoveMode.Update( this );
|
||||
}
|
||||
|
||||
BBox CalculateSelectionBounds()
|
||||
{
|
||||
var meshes = _meshes.Where( x => x.IsValid() && x.Model.IsValid() );
|
||||
return BBox.FromBoxes( meshes.Select( x => x.Model.Bounds.Transform( x.WorldTransform ) ) );
|
||||
}
|
||||
|
||||
public override void OnSelectionChanged()
|
||||
{
|
||||
_meshes = Selection.OfType<GameObject>()
|
||||
.Select( x => x.GetComponent<MeshComponent>() )
|
||||
.Where( x => x.IsValid() )
|
||||
.ToArray();
|
||||
|
||||
ClearPivot();
|
||||
}
|
||||
|
||||
void UpdateSelectionMode()
|
||||
{
|
||||
if ( !Gizmo.HasMouseFocus ) return;
|
||||
|
||||
if ( Gizmo.WasLeftMouseReleased && !Gizmo.Pressed.Any && !IsBoxSelecting )
|
||||
{
|
||||
using ( Scene.Editor?.UndoScope( "Deselect all" ).Push() )
|
||||
{
|
||||
EditorScene.Selection.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateHovered()
|
||||
{
|
||||
if ( IsBoxSelecting ) return;
|
||||
|
||||
var tr = MeshTrace.Run();
|
||||
|
||||
if ( !tr.Hit ) return;
|
||||
if ( tr.Component is not MeshComponent component ) return;
|
||||
|
||||
using ( Gizmo.ObjectScope( tr.GameObject, tr.GameObject.WorldTransform ) )
|
||||
{
|
||||
Gizmo.Hitbox.DepthBias = 1;
|
||||
Gizmo.Hitbox.TrySetHovered( tr.Distance );
|
||||
|
||||
if ( !Gizmo.IsHovered ) return;
|
||||
|
||||
if ( component.IsValid() && component.Model.IsValid() && !Selection.Contains( tr.GameObject ) )
|
||||
{
|
||||
Gizmo.Draw.Color = Gizmo.Colors.Active.WithAlpha( MathF.Sin( RealTime.Now * 20.0f ).Remap( -1, 1, 0.3f, 0.8f ) );
|
||||
Gizmo.Draw.LineBBox( component.Model.Bounds );
|
||||
}
|
||||
}
|
||||
|
||||
if ( Gizmo.WasLeftMousePressed )
|
||||
{
|
||||
Select( tr.GameObject );
|
||||
}
|
||||
}
|
||||
|
||||
void Select( GameObject element )
|
||||
{
|
||||
bool ctrl = Application.KeyboardModifiers.HasFlag( KeyboardModifiers.Ctrl );
|
||||
bool shift = Application.KeyboardModifiers.HasFlag( KeyboardModifiers.Shift );
|
||||
bool contains = Selection.Contains( element );
|
||||
|
||||
if ( shift && contains ) return;
|
||||
|
||||
using ( Scene.Editor?.UndoScope( "Select Mesh" ).Push() )
|
||||
{
|
||||
if ( ctrl )
|
||||
{
|
||||
if ( contains ) Selection.Remove( element );
|
||||
else Selection.Add( element );
|
||||
}
|
||||
else if ( shift )
|
||||
{
|
||||
Selection.Add( element );
|
||||
}
|
||||
else
|
||||
{
|
||||
Selection.Set( element );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnBoxSelect( Frustum frustum, Rect screenRect, bool isFinal )
|
||||
{
|
||||
var selection = new HashSet<GameObject>();
|
||||
var previous = new HashSet<GameObject>();
|
||||
|
||||
bool fullyInside = true;
|
||||
bool removing = Gizmo.IsCtrlPressed;
|
||||
|
||||
foreach ( var mr in Scene.GetAllComponents<MeshComponent>() )
|
||||
{
|
||||
var bounds = mr.GetWorldBounds();
|
||||
if ( !frustum.IsInside( bounds, !fullyInside ) )
|
||||
{
|
||||
previous.Add( mr.GameObject );
|
||||
continue;
|
||||
}
|
||||
|
||||
selection.Add( mr.GameObject );
|
||||
}
|
||||
|
||||
foreach ( var selectedObj in selection )
|
||||
{
|
||||
if ( !removing )
|
||||
{
|
||||
if ( Selection.Contains( selectedObj ) ) continue;
|
||||
|
||||
Selection.Add( selectedObj );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !Selection.Contains( selectedObj ) ) continue;
|
||||
|
||||
Selection.Remove( selectedObj );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( var removed in previous )
|
||||
{
|
||||
if ( removing )
|
||||
{
|
||||
Selection.Add( removed );
|
||||
}
|
||||
else
|
||||
{
|
||||
Selection.Remove( removed );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBounds()
|
||||
{
|
||||
using ( Gizmo.Scope( "Bounds" ) )
|
||||
{
|
||||
Gizmo.Draw.IgnoreDepth = true;
|
||||
Gizmo.Draw.Color = Color.White;
|
||||
Gizmo.Draw.LineThickness = 4;
|
||||
|
||||
var box = CalculateSelectionBounds();
|
||||
var textSize = 22 * Gizmo.Settings.GizmoScale * Application.DpiScale;
|
||||
|
||||
Gizmo.Draw.Color = Gizmo.Colors.Active.WithAlpha( 0.5f );
|
||||
Gizmo.Draw.LineThickness = 1;
|
||||
Gizmo.Draw.LineBBox( box );
|
||||
|
||||
Gizmo.Draw.LineThickness = 2;
|
||||
Gizmo.Draw.Color = Gizmo.Colors.Left;
|
||||
if ( box.Size.y > 0.01f )
|
||||
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;
|
||||
if ( box.Size.x > 0.01f )
|
||||
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;
|
||||
if ( box.Size.z > 0.01f )
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasBoxSelectionMode() => true;
|
||||
|
||||
static IReadOnlyList<Vector3> GetPivots( BBox box )
|
||||
{
|
||||
var mins = box.Mins;
|
||||
var maxs = box.Maxs;
|
||||
var center = box.Center;
|
||||
|
||||
return
|
||||
[
|
||||
new Vector3( mins.x, mins.y, mins.z ),
|
||||
new Vector3( maxs.x, mins.y, mins.z ),
|
||||
new Vector3( mins.x, maxs.y, mins.z ),
|
||||
new Vector3( maxs.x, maxs.y, mins.z ),
|
||||
|
||||
new Vector3( mins.x, mins.y, maxs.z ),
|
||||
new Vector3( maxs.x, mins.y, maxs.z ),
|
||||
new Vector3( mins.x, maxs.y, maxs.z ),
|
||||
new Vector3( maxs.x, maxs.y, maxs.z ),
|
||||
|
||||
new Vector3( center.x, center.y, mins.z ),
|
||||
new Vector3( center.x, center.y, maxs.z ),
|
||||
];
|
||||
}
|
||||
|
||||
int _pivotIndex = 0;
|
||||
|
||||
void StepPivot( int direction )
|
||||
{
|
||||
var box = CalculateSelectionBounds();
|
||||
if ( box.Size.Length <= 0 ) return;
|
||||
|
||||
var pivots = GetPivots( box );
|
||||
|
||||
_pivotIndex = (_pivotIndex + direction + pivots.Count) % pivots.Count;
|
||||
Pivot = pivots[_pivotIndex];
|
||||
}
|
||||
|
||||
public void PreviousPivot() => StepPivot( -1 );
|
||||
public void NextPivot() => StepPivot( 1 );
|
||||
|
||||
public void ClearPivot()
|
||||
{
|
||||
var mesh = _meshes.FirstOrDefault();
|
||||
Pivot = mesh.IsValid() ? mesh.WorldPosition : default;
|
||||
_pivotIndex = 0;
|
||||
}
|
||||
|
||||
public void ZeroPivot()
|
||||
{
|
||||
Pivot = default;
|
||||
_pivotIndex = 0;
|
||||
}
|
||||
}
|
||||
@@ -35,4 +35,7 @@ file class MeshToolShortcutsWidget : Widget
|
||||
|
||||
[Shortcut( "tools.texture-tool", "4", typeof( SceneDock ) )]
|
||||
public void ActivateTextureTool() => EditorToolManager.SetSubTool( nameof( TextureTool ) );
|
||||
|
||||
[Shortcut( "tools.mesh-selection", "5", typeof( SceneDock ) )]
|
||||
public void ActivateMeshSelection() => EditorToolManager.SetSubTool( nameof( MeshSelection ) );
|
||||
}
|
||||
|
||||
@@ -12,11 +12,12 @@ public partial class MeshTool : EditorTool
|
||||
{
|
||||
public Material ActiveMaterial { get; set; } = Material.Load( "materials/dev/reflectivity_30.vmat" );
|
||||
|
||||
public MoveMode CurrentMoveMode { get; set; }
|
||||
public MoveMode MoveMode { get; set; }
|
||||
|
||||
public override IEnumerable<EditorTool> GetSubtools()
|
||||
{
|
||||
yield return new BlockTool( this );
|
||||
yield return new MeshSelection( this );
|
||||
yield return new VertexTool( this );
|
||||
yield return new EdgeTool( this );
|
||||
yield return new FaceTool( this );
|
||||
@@ -32,7 +33,12 @@ public partial class MeshTool : EditorTool
|
||||
|
||||
Selection.Clear();
|
||||
|
||||
CurrentMoveMode = EditorTypeLibrary.Create<MoveMode>( "PositionMode" );
|
||||
MoveMode = EditorTypeLibrary.Create<MoveMode>( "PositionMode" );
|
||||
}
|
||||
|
||||
public override void OnSelectionChanged()
|
||||
{
|
||||
CurrentTool?.OnSelectionChanged();
|
||||
}
|
||||
|
||||
[Shortcut( "tools.mesh-tool", "m", typeof( SceneDock ) )]
|
||||
|
||||
@@ -20,7 +20,7 @@ class MoveModeToolBar : Widget
|
||||
|
||||
void SetMode( string id )
|
||||
{
|
||||
_tool.CurrentMoveMode = EditorTypeLibrary.Create<MoveMode>( id );
|
||||
_tool.MoveMode = EditorTypeLibrary.Create<MoveMode>( id );
|
||||
Update();
|
||||
}
|
||||
|
||||
@@ -72,9 +72,9 @@ file class MoveModeButton : Widget
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
if ( _type.TargetType == _tool.CurrentMoveMode?.GetType() ) return;
|
||||
if ( _type.TargetType == _tool.MoveMode?.GetType() ) return;
|
||||
|
||||
_tool.CurrentMoveMode = _type.Create<MoveMode>();
|
||||
_tool.MoveMode = _type.Create<MoveMode>();
|
||||
}
|
||||
|
||||
protected override void OnPaint()
|
||||
@@ -82,7 +82,7 @@ file class MoveModeButton : Widget
|
||||
Paint.Antialiasing = true;
|
||||
Paint.TextAntialiasing = true;
|
||||
|
||||
if ( _type.TargetType == _tool.CurrentMoveMode?.GetType() )
|
||||
if ( _type.TargetType == _tool.MoveMode?.GetType() )
|
||||
{
|
||||
Paint.ClearPen();
|
||||
Paint.SetBrush( Theme.Blue );
|
||||
|
||||
@@ -5,23 +5,50 @@ public abstract class SelectionTool : EditorTool
|
||||
{
|
||||
public Vector3 Pivot { get; set; }
|
||||
|
||||
public HashSet<MeshVertex> VertexSelection { get; init; } = [];
|
||||
|
||||
public virtual Rotation CalculateSelectionBasis()
|
||||
{
|
||||
return Rotation.Identity;
|
||||
}
|
||||
|
||||
public virtual List<MeshFace> ExtrudeSelection( Vector3 delta = default )
|
||||
public virtual BBox CalculateLocalBounds()
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
public virtual void StartDrag()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void UpdateDrag()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void EndDrag()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Translate( Vector3 delta )
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Rotate( Vector3 origin, Rotation basis, Rotation delta )
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Scale( Vector3 origin, Rotation basis, Vector3 scale )
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool where T : IMeshElement
|
||||
{
|
||||
protected MeshTool Tool { get; private init; } = tool;
|
||||
|
||||
readonly HashSet<MeshVertex> _vertexSelection = [];
|
||||
readonly Dictionary<MeshVertex, Vector3> _transformVertices = [];
|
||||
List<MeshFace> _transformFaces;
|
||||
IDisposable _undoScope;
|
||||
|
||||
protected virtual bool HasMoveMode => true;
|
||||
|
||||
public static Vector2 RayScreenPosition => SceneViewportWidget.MousePosition;
|
||||
@@ -37,7 +64,49 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
|
||||
public virtual bool DrawVertices => false;
|
||||
|
||||
protected IDisposable _undoScope;
|
||||
public override void Translate( Vector3 delta )
|
||||
{
|
||||
foreach ( var entry in _transformVertices )
|
||||
{
|
||||
var position = entry.Value + delta;
|
||||
var transform = entry.Key.Transform;
|
||||
entry.Key.Component.Mesh.SetVertexPosition( entry.Key.Handle, transform.PointToLocal( position ) );
|
||||
}
|
||||
}
|
||||
|
||||
public override void Rotate( Vector3 origin, Rotation basis, Rotation delta )
|
||||
{
|
||||
foreach ( var entry in _transformVertices )
|
||||
{
|
||||
var rotation = basis * delta * basis.Inverse;
|
||||
var position = entry.Value - origin;
|
||||
position *= rotation;
|
||||
position += origin;
|
||||
|
||||
var transform = entry.Key.Transform;
|
||||
entry.Key.Component.Mesh.SetVertexPosition( entry.Key.Handle, transform.PointToLocal( position ) );
|
||||
}
|
||||
}
|
||||
|
||||
public override void Scale( Vector3 origin, Rotation basis, Vector3 scale )
|
||||
{
|
||||
foreach ( var entry in _transformVertices )
|
||||
{
|
||||
var position = (entry.Value - origin) * basis.Inverse;
|
||||
position *= scale;
|
||||
position *= basis;
|
||||
position += origin;
|
||||
|
||||
var transform = entry.Key.Transform;
|
||||
entry.Key.Component.Mesh.SetVertexPosition( entry.Key.Handle, transform.PointToLocal( position ) );
|
||||
}
|
||||
}
|
||||
|
||||
public override BBox CalculateLocalBounds()
|
||||
{
|
||||
return BBox.FromPoints( _vertexSelection
|
||||
.Select( x => CalculateSelectionBasis().Inverse * x.PositionWorld ) );
|
||||
}
|
||||
|
||||
public override void OnEnabled()
|
||||
{
|
||||
@@ -54,10 +123,7 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
if ( HasMoveMode )
|
||||
{
|
||||
Tool.CurrentMoveMode?.Update( this );
|
||||
}
|
||||
UpdateMoveMode();
|
||||
|
||||
if ( Gizmo.WasLeftMouseReleased && !Gizmo.Pressed.Any && Gizmo.Pressed.CursorDelta.Length < 1 )
|
||||
{
|
||||
@@ -95,6 +161,16 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
DrawSelection();
|
||||
}
|
||||
|
||||
void UpdateMoveMode()
|
||||
{
|
||||
if ( !HasMoveMode ) return;
|
||||
if ( Tool is null ) return;
|
||||
if ( Tool.MoveMode is null ) return;
|
||||
if ( !Selection.OfType<IMeshElement>().Any() ) return;
|
||||
|
||||
Tool.MoveMode.Update( this );
|
||||
}
|
||||
|
||||
void SelectElements()
|
||||
{
|
||||
var elements = Selection.OfType<T>().ToArray();
|
||||
@@ -204,6 +280,11 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
}
|
||||
}
|
||||
|
||||
public virtual List<MeshFace> ExtrudeSelection( Vector3 delta = default )
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private void UpdateNudge()
|
||||
{
|
||||
if ( Gizmo.Pressed.Any || !Application.FocusWidget.IsValid() )
|
||||
@@ -241,7 +322,7 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ( var vertex in VertexSelection )
|
||||
foreach ( var vertex in _vertexSelection )
|
||||
{
|
||||
var transform = vertex.Transform;
|
||||
var position = vertex.Component.Mesh.GetVertexPosition( vertex.Handle );
|
||||
@@ -255,7 +336,7 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
|
||||
public BBox CalculateSelectionBounds()
|
||||
{
|
||||
return BBox.FromPoints( VertexSelection
|
||||
return BBox.FromPoints( _vertexSelection
|
||||
.Where( x => x.IsValid() )
|
||||
.Select( x => x.Transform.PointToWorld( x.Component.Mesh.GetVertexPosition( x.Handle ) ) ) );
|
||||
}
|
||||
@@ -268,27 +349,27 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
|
||||
public void CalculateSelectionVertices()
|
||||
{
|
||||
VertexSelection.Clear();
|
||||
_vertexSelection.Clear();
|
||||
|
||||
foreach ( var face in Selection.OfType<MeshFace>() )
|
||||
{
|
||||
foreach ( var vertex in face.Component.Mesh.GetFaceVertices( face.Handle )
|
||||
.Select( i => new MeshVertex( face.Component, i ) ) )
|
||||
{
|
||||
VertexSelection.Add( vertex );
|
||||
_vertexSelection.Add( vertex );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( var vertex in Selection.OfType<MeshVertex>() )
|
||||
{
|
||||
VertexSelection.Add( vertex );
|
||||
_vertexSelection.Add( vertex );
|
||||
}
|
||||
|
||||
foreach ( var edge in Selection.OfType<MeshEdge>() )
|
||||
{
|
||||
edge.Component.Mesh.GetEdgeVertices( edge.Handle, out var hVertexA, out var hVertexB );
|
||||
VertexSelection.Add( new MeshVertex( edge.Component, hVertexA ) );
|
||||
VertexSelection.Add( new MeshVertex( edge.Component, hVertexB ) );
|
||||
_vertexSelection.Add( new MeshVertex( edge.Component, hVertexA ) );
|
||||
_vertexSelection.Add( new MeshVertex( edge.Component, hVertexB ) );
|
||||
}
|
||||
|
||||
_meshSelectionDirty = false;
|
||||
@@ -370,6 +451,65 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
}
|
||||
}
|
||||
|
||||
public override void StartDrag()
|
||||
{
|
||||
if ( _transformVertices.Count != 0 )
|
||||
return;
|
||||
|
||||
var components = Selection.OfType<IMeshElement>()
|
||||
.Select( x => x.Component )
|
||||
.Distinct();
|
||||
|
||||
_undoScope ??= SceneEditorSession.Active.UndoScope( $"{(Gizmo.IsShiftPressed ? "Extrude" : "Move")} Selection" )
|
||||
.WithComponentChanges( components )
|
||||
.Push();
|
||||
|
||||
if ( Gizmo.IsShiftPressed )
|
||||
{
|
||||
_transformFaces = ExtrudeSelection();
|
||||
}
|
||||
|
||||
foreach ( var vertex in _vertexSelection )
|
||||
{
|
||||
_transformVertices[vertex] = vertex.PositionWorld;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateDrag()
|
||||
{
|
||||
if ( _transformFaces is not null )
|
||||
{
|
||||
foreach ( var group in _transformFaces.GroupBy( x => x.Component ) )
|
||||
{
|
||||
var mesh = group.Key.Mesh;
|
||||
var faces = group.Select( x => x.Handle ).ToArray();
|
||||
|
||||
foreach ( var face in faces )
|
||||
{
|
||||
mesh.TextureAlignToGrid( mesh.Transform, face );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var meshes = _transformVertices
|
||||
.Select( x => x.Key.Component.Mesh )
|
||||
.Distinct();
|
||||
|
||||
foreach ( var mesh in meshes )
|
||||
{
|
||||
mesh.ComputeFaceTextureCoordinatesFromParameters();
|
||||
}
|
||||
}
|
||||
|
||||
public override void EndDrag()
|
||||
{
|
||||
_transformVertices.Clear();
|
||||
_transformFaces = null;
|
||||
|
||||
_undoScope?.Dispose();
|
||||
_undoScope = null;
|
||||
}
|
||||
|
||||
public MeshVertex GetClosestVertex( int radius )
|
||||
{
|
||||
var point = RayScreenPosition;
|
||||
@@ -515,6 +655,7 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
return orientation;
|
||||
}
|
||||
|
||||
[SkipHotload]
|
||||
private static readonly Vector3[] FaceNormals =
|
||||
{
|
||||
new( 0, 0, 1 ),
|
||||
@@ -525,6 +666,7 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool
|
||||
new( 1, 0, 0 ),
|
||||
};
|
||||
|
||||
[SkipHotload]
|
||||
private static readonly Vector3[] FaceDownVectors =
|
||||
{
|
||||
new( 0, -1, 0 ),
|
||||
|
||||
@@ -137,7 +137,7 @@ partial class TextureTool
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut( "editor.delete", "DEL", typeof( SceneViewportWidget ) )]
|
||||
[Shortcut( "editor.delete", "DEL", typeof( SceneDock ) )]
|
||||
private void DeleteSelection()
|
||||
{
|
||||
var groups = _faces.GroupBy( face => face.Component );
|
||||
|
||||
@@ -56,42 +56,47 @@ partial class VertexTool
|
||||
_components = _vertexGroups.Select( x => x.Key ).ToList();
|
||||
|
||||
{
|
||||
var row = new Widget { Layout = Layout.Row() };
|
||||
row.Layout.Spacing = 4;
|
||||
var group = AddGroup( "Operations" );
|
||||
|
||||
CreateButton( "Merge", "merge", "mesh.merge", Merge, _vertices.Length > 1, row.Layout );
|
||||
{
|
||||
var row = new Widget { Layout = Layout.Row() };
|
||||
row.Layout.Spacing = 4;
|
||||
|
||||
var mergeObject = mergeProperties.GetSerialized();
|
||||
var range = ControlWidget.Create( mergeObject.GetProperty( nameof( MergeProperties.Range ) ) );
|
||||
var distance = ControlWidget.Create( mergeObject.GetProperty( nameof( MergeProperties.Distance ) ) );
|
||||
distance.HorizontalSizeMode = SizeMode.Expand;
|
||||
CreateButton( "Merge", "merge", "mesh.merge", Merge, _vertices.Length > 1, row.Layout );
|
||||
|
||||
range.FixedHeight = Theme.ControlHeight;
|
||||
distance.FixedHeight = Theme.ControlHeight;
|
||||
var mergeObject = mergeProperties.GetSerialized();
|
||||
var range = ControlWidget.Create( mergeObject.GetProperty( nameof( MergeProperties.Range ) ) );
|
||||
var distance = ControlWidget.Create( mergeObject.GetProperty( nameof( MergeProperties.Distance ) ) );
|
||||
distance.HorizontalSizeMode = SizeMode.Expand;
|
||||
|
||||
row.Layout.Add( range );
|
||||
row.Layout.Add( distance );
|
||||
range.FixedHeight = Theme.ControlHeight;
|
||||
distance.FixedHeight = Theme.ControlHeight;
|
||||
|
||||
Layout.Add( row );
|
||||
}
|
||||
{
|
||||
var row = new Widget { Layout = Layout.Row() };
|
||||
row.Layout.Spacing = 4;
|
||||
row.Layout.Add( range );
|
||||
row.Layout.Add( distance );
|
||||
|
||||
CreateButton( "Snap To Vertex", "gps_fixed", "mesh.snap_to_vertex", SnapToVertex, _vertices.Length > 1, row.Layout );
|
||||
CreateButton( "Weld UVs", "scatter_plot", "mesh.vertex-weld-uvs", WeldUVs, _vertices.Length > 0, row.Layout );
|
||||
CreateButton( "Bevel", "straighten", "mesh.bevel", Bevel, _vertices.Length > 0, row.Layout );
|
||||
CreateButton( "Connect", "link", "mesh.connect", Connect, _vertices.Length > 1, row.Layout );
|
||||
group.Add( row );
|
||||
}
|
||||
|
||||
row.Layout.AddStretchCell();
|
||||
{
|
||||
var row = new Widget { Layout = Layout.Row() };
|
||||
row.Layout.Spacing = 4;
|
||||
|
||||
Layout.Add( row );
|
||||
CreateButton( "Snap To Vertex", "gps_fixed", "mesh.snap_to_vertex", SnapToVertex, _vertices.Length > 1, row.Layout );
|
||||
CreateButton( "Weld UVs", "scatter_plot", "mesh.vertex-weld-uvs", WeldUVs, _vertices.Length > 0, row.Layout );
|
||||
CreateButton( "Bevel", "straighten", "mesh.bevel", Bevel, _vertices.Length > 0, row.Layout );
|
||||
CreateButton( "Connect", "link", "mesh.connect", Connect, _vertices.Length > 1, row.Layout );
|
||||
|
||||
row.Layout.AddStretchCell();
|
||||
|
||||
group.Add( row );
|
||||
}
|
||||
}
|
||||
|
||||
Layout.AddStretchCell();
|
||||
}
|
||||
|
||||
[Shortcut( "mesh.connect", "V", typeof( SceneViewportWidget ) )]
|
||||
[Shortcut( "mesh.connect", "V", typeof( SceneDock ) )]
|
||||
private void Connect()
|
||||
{
|
||||
if ( _vertices.Length < 2 )
|
||||
@@ -273,7 +278,7 @@ partial class VertexTool
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut( "editor.delete", "DEL", typeof( SceneViewportWidget ) )]
|
||||
[Shortcut( "editor.delete", "DEL", typeof( SceneDock ) )]
|
||||
private void DeleteSelection()
|
||||
{
|
||||
var groups = _vertices.GroupBy( face => face.Component );
|
||||
|
||||
@@ -110,7 +110,7 @@ public class RotationEditorTool : EditorTool
|
||||
}
|
||||
|
||||
|
||||
[Shortcut( "tools.rotate-tool", "e", typeof( SceneViewportWidget ) )]
|
||||
[Shortcut( "tools.rotate-tool", "e", typeof( SceneDock ) )]
|
||||
public static void ActivateSubTool()
|
||||
{
|
||||
if ( !(EditorToolManager.CurrentModeName == nameof( ObjectEditorTool ) || EditorToolManager.CurrentModeName == "object") ) return;
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ScaleEditorTool : EditorTool
|
||||
}
|
||||
|
||||
|
||||
[Shortcut( "tools.scale-tool", "r", typeof( SceneViewportWidget ) )]
|
||||
[Shortcut( "tools.scale-tool", "r", typeof( SceneDock ) )]
|
||||
public static void ActivateSubTool()
|
||||
{
|
||||
if ( !(EditorToolManager.CurrentModeName == nameof( ObjectEditorTool ) || EditorToolManager.CurrentModeName == "object") ) return;
|
||||
|
||||
@@ -305,7 +305,7 @@ file class ViewportToolBar : Widget
|
||||
void OnToolChanged()
|
||||
{
|
||||
// Prevent flicker when changing tools
|
||||
using var x = SuspendUpdates.For( GetAncestor<SceneViewWidget>() );
|
||||
using var x = SuspendUpdates.For( this );
|
||||
|
||||
var rootTool = SceneViewWidget.Current?.Tools.CurrentTool;
|
||||
var subTool = SceneViewWidget.Current?.Tools.CurrentSubTool;
|
||||
@@ -327,7 +327,7 @@ file class ViewportToolBar : Widget
|
||||
|
||||
if ( toolWidget.IsValid() )
|
||||
{
|
||||
var scroller = new ScrollArea( this );
|
||||
var scroller = new ScrollArea( null );
|
||||
scroller.FixedWidth = 240;
|
||||
toolWidget.FixedWidth = 240;
|
||||
scroller.HorizontalSizeMode = SizeMode.Flexible;
|
||||
|
||||
Reference in New Issue
Block a user