Mapping tools resize mode (#3630)

This commit is contained in:
Layla
2025-12-17 12:47:10 +00:00
committed by GitHub
parent dc25e9f401
commit 1b9ac54a83
10 changed files with 221 additions and 18 deletions

View File

@@ -28,7 +28,7 @@ public sealed class MeshComponent : Collider, ExecuteInEditor, ITintable, IMater
field = value;
Update();
RebuildMesh();
}
}
@@ -146,7 +146,7 @@ public sealed class MeshComponent : Collider, ExecuteInEditor, ITintable, IMater
internal override void OnEnabledInternal()
{
// Mesh needs to build before collider.
RebuildMesh();
RebuildRenderMesh();
base.OnEnabledInternal();
}
@@ -171,17 +171,17 @@ public sealed class MeshComponent : Collider, ExecuteInEditor, ITintable, IMater
{
base.OnUpdate();
Update();
RebuildMesh();
}
void Update()
public void RebuildMesh()
{
// Only rebuild dirty meshes in editor.
if ( !Active ) return;
if ( !Scene.IsEditor ) return;
if ( Mesh is null || !Mesh.IsDirty ) return;
RebuildMesh();
RebuildRenderMesh();
RebuildImmediately();
}
@@ -253,7 +253,7 @@ public sealed class MeshComponent : Collider, ExecuteInEditor, ITintable, IMater
}
}
public void RebuildMesh()
void RebuildRenderMesh()
{
if ( !Active ) return;
if ( Mesh is null ) return;

View File

@@ -165,6 +165,14 @@ public sealed partial class PolygonMesh : IJsonConvert
}
}
/// <summary>
/// Set transform without computing texture parameters from coordinates.
/// </summary>
public void SetTransform( Transform transform )
{
_transform = transform;
}
public PolygonMesh()
{
Positions = Topology.CreateVertexData<Vector3>( nameof( Positions ) );
@@ -2569,6 +2577,12 @@ public sealed partial class PolygonMesh : IJsonConvert
ComputeFaceTextureCoordinatesFromParameters( FaceHandles );
}
public void ComputeFaceTextureCoordinatesFromParameters( Transform transform )
{
var textureSizes = Materials.Select( CalculateTextureSize ).ToArray();
ComputeFaceTextureCoordinatesFromParameters( FaceHandles, transform, textureSizes, 0.25f );
}
public void ComputeFaceTextureCoordinatesFromParameters( IEnumerable<FaceHandle> faces )
{
var textureSizes = Materials.Select( CalculateTextureSize ).ToArray();

View File

@@ -0,0 +1,102 @@

namespace Editor.MeshEditor;
/// <summary>
/// Resize everything in the selection using box resize handles.
/// </summary>
[Title( "Resize" )]
[Icon( "device_hub" )]
[Alias( "mesh.resize.mode" )]
[Order( 4 )]
public sealed class ResizeMode : MoveMode
{
private BBox _startBox;
private BBox _deltaBox;
private BBox _box;
protected override void OnUpdate( SelectionTool tool )
{
if ( !Gizmo.Pressed.Any )
{
tool.EndDrag();
_startBox = tool.CalculateSelectionBounds();
_deltaBox = default;
_box = _startBox;
}
var size = _startBox.Size;
if ( size.x.AlmostEqual( 0.0f ) ) return;
if ( size.y.AlmostEqual( 0.0f ) ) return;
if ( size.z.AlmostEqual( 0.0f ) ) return;
using ( Gizmo.Scope( "box" ) )
{
Gizmo.Hitbox.DepthBias = 0.01f;
if ( Gizmo.Control.BoundingBox( "resize", _box, out var outBox ) )
{
_deltaBox.Maxs += outBox.Maxs - _box.Maxs;
_deltaBox.Mins += outBox.Mins - _box.Mins;
_box = Snap( _startBox, _deltaBox );
tool.StartDrag();
ResizeBBox( tool, _startBox, _box, Rotation.Identity );
tool.UpdateDrag();
tool.Pivot = tool.CalculateSelectionOrigin();
}
}
}
static BBox Snap( BBox startBox, BBox movement )
{
var mins = startBox.Mins + movement.Mins;
var maxs = startBox.Maxs + movement.Maxs;
var snap = Gizmo.Settings.SnapToGrid != Gizmo.IsCtrlPressed;
if ( snap )
{
mins = Gizmo.Snap( mins, movement.Mins );
maxs = Gizmo.Snap( maxs, movement.Maxs );
}
var spacing = 1.0f;
mins.x = MathF.Min( mins.x, startBox.Maxs.x - spacing );
mins.y = MathF.Min( mins.y, startBox.Maxs.y - spacing );
mins.z = MathF.Min( mins.z, startBox.Maxs.z - spacing );
maxs.x = MathF.Max( maxs.x, startBox.Mins.x + spacing );
maxs.y = MathF.Max( maxs.y, startBox.Mins.y + spacing );
maxs.z = MathF.Max( maxs.z, startBox.Mins.z + spacing );
return new BBox( mins, maxs );
}
static void ResizeBBox( SelectionTool tool, BBox prevBox, BBox newBox, Rotation basis )
{
var prevSize = prevBox.Size;
var scale = newBox.Size / prevSize;
var dMin = newBox.Mins - prevBox.Mins;
var dMax = newBox.Maxs - prevBox.Maxs;
var origin = prevBox.Center;
if ( MathF.Abs( dMax.x ) > MathF.Abs( dMin.x ) ) origin.x = prevBox.Mins.x;
else if ( MathF.Abs( dMin.x ) > MathF.Abs( dMax.x ) ) origin.x = prevBox.Maxs.x;
if ( MathF.Abs( dMax.y ) > MathF.Abs( dMin.y ) ) origin.y = prevBox.Mins.y;
else if ( MathF.Abs( dMin.y ) > MathF.Abs( dMax.y ) ) origin.y = prevBox.Maxs.y;
if ( MathF.Abs( dMax.z ) > MathF.Abs( dMin.z ) ) origin.z = prevBox.Mins.z;
else if ( MathF.Abs( dMin.z ) > MathF.Abs( dMax.z ) ) origin.z = prevBox.Maxs.z;
tool.Resize( origin, basis, scale );
}
}

View File

@@ -62,6 +62,7 @@ public sealed class BlockEditor( PrimitiveTool tool ) : PrimitiveEditor( tool )
if ( !_dragStarted )
{
EditorToolManager.SetSubTool( nameof( MeshSelection ) );
Tool.MeshTool.SetMoveMode<ResizeMode>();
}
_box = null;

View File

@@ -192,8 +192,8 @@ partial class MeshSelection
var world = meshComponent.WorldTransform;
var localCenter = world.PointToLocal( origin );
meshComponent.WorldPosition = origin;
meshComponent.Mesh.ApplyTransform( new Transform( -localCenter ) );
meshComponent.WorldPosition = origin;
meshComponent.RebuildMesh();
}

View File

@@ -12,7 +12,8 @@ public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool
{
public MeshTool Tool { get; private init; } = tool;
readonly Dictionary<GameObject, Transform> _startPoints = [];
readonly Dictionary<MeshComponent, Transform> _startPoints = [];
readonly Dictionary<MeshVertex, Vector3> _transformVertices = [];
IDisposable _undoScope;
MeshComponent[] _meshes = [];
@@ -27,6 +28,7 @@ public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool
{
_undoScope ??= SceneEditorSession.Active.UndoScope( "Duplicate Object(s)" )
.WithGameObjectCreations()
.WithComponentChanges( _meshes )
.Push();
DuplicateSelection();
@@ -36,12 +38,19 @@ public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool
{
_undoScope ??= SceneEditorSession.Active.UndoScope( "Transform Object(s)" )
.WithGameObjectChanges( _meshes.Select( x => x.GameObject ), GameObjectUndoFlags.Properties )
.WithComponentChanges( _meshes )
.Push();
}
foreach ( var mesh in _meshes )
{
_startPoints[mesh.GameObject] = mesh.WorldTransform;
_startPoints[mesh] = mesh.WorldTransform;
foreach ( var vertex in mesh.Mesh.VertexHandles )
{
var v = new MeshVertex( mesh, vertex );
_transformVertices[v] = mesh.WorldTransform.PointToWorld( mesh.Mesh.GetVertexPosition( vertex ) );
}
}
}
@@ -95,6 +104,40 @@ public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool
}
}
public override void Resize( Vector3 origin, Rotation basis, Vector3 scale )
{
foreach ( var startPoint in _startPoints )
{
var position = (startPoint.Value.Position - origin) * basis.Inverse;
position *= scale;
position *= basis;
position += origin;
var component = startPoint.Key;
var transform = component.WorldTransform.WithPosition( position );
component.Mesh.SetTransform( transform );
}
foreach ( var entry in _transformVertices )
{
var position = (entry.Value - origin) * basis.Inverse;
position *= scale;
position *= basis;
position += origin;
var transform = entry.Key.Component.Mesh.Transform;
entry.Key.Component.Mesh.SetVertexPosition( entry.Key.Handle, transform.PointToLocal( position ) );
}
foreach ( var startPoint in _startPoints )
{
var component = startPoint.Key;
component.Mesh.ComputeFaceTextureCoordinatesFromParameters();
component.WorldTransform = component.Mesh.Transform;
component.RebuildMesh();
}
}
public override BBox CalculateLocalBounds()
{
return CalculateSelectionBounds();
@@ -147,10 +190,17 @@ public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool
Tool.MoveMode.Update( this );
}
BBox CalculateSelectionBounds()
public override Vector3 CalculateSelectionOrigin()
{
var meshes = _meshes.Where( x => x.IsValid() && x.Model.IsValid() );
return BBox.FromBoxes( meshes.Select( x => x.Model.Bounds.Transform( x.WorldTransform ) ) );
var mesh = _meshes.FirstOrDefault();
return mesh.IsValid() ? mesh.WorldPosition : default;
}
public override BBox CalculateSelectionBounds()
{
return BBox.FromPoints( _transformVertices
.Where( x => x.Key.IsValid() )
.Select( x => x.Key.Transform.PointToWorld( x.Key.Component.Mesh.GetVertexPosition( x.Key.Handle ) ) ) );
}
public override void OnSelectionChanged()
@@ -160,6 +210,17 @@ public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool
.Where( x => x.IsValid() )
.ToArray();
_transformVertices.Clear();
foreach ( var mesh in _meshes )
{
foreach ( var vertex in mesh.Mesh.VertexHandles )
{
var v = new MeshVertex( mesh, vertex );
_transformVertices[v] = mesh.WorldTransform.PointToWorld( mesh.Mesh.GetVertexPosition( vertex ) );
}
}
ClearPivot();
}
@@ -354,8 +415,7 @@ public sealed partial class MeshSelection( MeshTool tool ) : SelectionTool
public void ClearPivot()
{
var mesh = _meshes.FirstOrDefault();
Pivot = mesh.IsValid() ? mesh.WorldPosition : default;
Pivot = CalculateSelectionOrigin();
_pivotIndex = 0;
}

View File

@@ -14,6 +14,12 @@ public partial class MeshTool : EditorTool
public MoveMode MoveMode { get; set; }
public void SetMoveMode<T>() where T : MoveMode
{
if ( MoveMode?.GetType() == typeof( T ) ) return;
MoveMode = EditorTypeLibrary.Create<MoveMode>( typeof( T ) );
}
public override IEnumerable<EditorTool> GetSubtools()
{
yield return new PrimitiveTool( this );
@@ -33,7 +39,7 @@ public partial class MeshTool : EditorTool
Selection.Clear();
MoveMode = EditorTypeLibrary.Create<MoveMode>( "PositionMode" );
SetMoveMode<PositionMode>();
}
public override void OnSelectionChanged()

View File

@@ -35,6 +35,9 @@ class MoveModeToolBar : Widget
[Shortcut( "tools.pivot-tool", "t", typeof( SceneDock ) )]
public void ActivatePivotMode() => SetMode( "mesh.pivot.mode" );
[Shortcut( "tools.resize-tool", "y", typeof( SceneDock ) )]
public void ActivateResizeMode() => SetMode( "mesh.resize.mode" );
}
file class MoveModeButton : Widget

View File

@@ -8,9 +8,11 @@ namespace Editor.MeshEditor;
[Alias( "tools.primitive-tool" )]
public partial class PrimitiveTool( MeshTool tool ) : EditorTool
{
public MeshTool MeshTool { get; private init; } = tool;
public PrimitiveEditor Editor { get; private set; }
public Material ActiveMaterial => tool.ActiveMaterial;
public Material ActiveMaterial => MeshTool.ActiveMaterial;
public override void OnEnabled()
{

View File

@@ -5,11 +5,21 @@ public abstract class SelectionTool : EditorTool
{
public Vector3 Pivot { get; set; }
public virtual Vector3 CalculateSelectionOrigin()
{
return default;
}
public virtual Rotation CalculateSelectionBasis()
{
return Rotation.Identity;
}
public virtual BBox CalculateSelectionBounds()
{
return default;
}
public virtual BBox CalculateLocalBounds()
{
return default;
@@ -38,6 +48,11 @@ public abstract class SelectionTool : EditorTool
public virtual void Scale( Vector3 origin, Rotation basis, Vector3 scale )
{
}
public virtual void Resize( Vector3 origin, Rotation basis, Vector3 scale )
{
Scale( origin, basis, scale );
}
}
public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool where T : IMeshElement
@@ -332,14 +347,14 @@ public abstract class SelectionTool<T>( MeshTool tool ) : SelectionTool where T
_nudge = true;
}
public BBox CalculateSelectionBounds()
public override BBox CalculateSelectionBounds()
{
return BBox.FromPoints( _vertexSelection
.Where( x => x.IsValid() )
.Select( x => x.Transform.PointToWorld( x.Component.Mesh.GetVertexPosition( x.Handle ) ) ) );
}
public virtual Vector3 CalculateSelectionOrigin()
public override Vector3 CalculateSelectionOrigin()
{
var bounds = CalculateSelectionBounds();
return bounds.Center;