Skip to content

Commit 91fa40b

Browse files
authored
Mapping selection enhancements (#3906)
* Grow and Shrink selection * Add convert selection Will convert selection from one to another example; Selecting a face then holding alt and switching to edge mode will select the edges of that face. * Add selection shortest path find the shortest path between 2 selections (face and edge) * Make open edges easier to read
1 parent b688f95 commit 91fa40b

File tree

9 files changed

+878
-22
lines changed

9 files changed

+878
-22
lines changed

game/addons/tools/Code/Scene/Mesh/Tools/EdgeTool.UI.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,5 +824,102 @@ void OpenEdgeArchTool()
824824
tool.Manager = _tool.Manager;
825825
_tool.CurrentTool = tool;
826826
}
827+
828+
[Shortcut( "mesh.grow-selection", "KP_ADD", typeof( SceneViewWidget ) )]
829+
private void GrowSelection()
830+
{
831+
if ( _edges.Length == 0 ) return;
832+
833+
using var scope = SceneEditorSession.Scope();
834+
835+
using ( SceneEditorSession.Active.UndoScope( "Grow Selection" )
836+
.WithComponentChanges( _components )
837+
.Push() )
838+
{
839+
var selection = SceneEditorSession.Active.Selection;
840+
var newEdges = new HashSet<MeshEdge>();
841+
842+
foreach ( var edge in _edges )
843+
{
844+
if ( !edge.IsValid() )
845+
continue;
846+
847+
newEdges.Add( edge );
848+
}
849+
850+
foreach ( var edge in _edges )
851+
{
852+
if ( !edge.IsValid() )
853+
continue;
854+
855+
var mesh = edge.Component.Mesh;
856+
857+
mesh.GetEdgeVertices( edge.Handle, out var vertexA, out var vertexB );
858+
859+
mesh.GetEdgesConnectedToVertex( vertexA, out var edgesA );
860+
mesh.GetEdgesConnectedToVertex( vertexB, out var edgesB );
861+
862+
foreach ( var adjacentEdge in edgesA.Concat( edgesB ) )
863+
{
864+
if ( adjacentEdge.IsValid )
865+
newEdges.Add( new MeshEdge( edge.Component, adjacentEdge ) );
866+
}
867+
}
868+
869+
selection.Clear();
870+
foreach ( var edge in newEdges )
871+
{
872+
if ( edge.IsValid() )
873+
selection.Add( edge );
874+
}
875+
}
876+
}
877+
878+
[Shortcut( "mesh.shrink-selection", "KP_MINUS", typeof( SceneViewWidget ) )]
879+
private void ShrinkSelection()
880+
{
881+
if ( _edges.Length == 0 ) return;
882+
883+
using var scope = SceneEditorSession.Scope();
884+
885+
using ( SceneEditorSession.Active.UndoScope( "Shrink Selection" )
886+
.WithComponentChanges( _components )
887+
.Push() )
888+
{
889+
var selection = SceneEditorSession.Active.Selection;
890+
var edgesToKeep = new HashSet<MeshEdge>();
891+
892+
foreach ( var edge in _edges )
893+
{
894+
if ( !edge.IsValid() )
895+
continue;
896+
897+
var mesh = edge.Component.Mesh;
898+
mesh.GetEdgeVertices( edge.Handle, out var vertexA, out var vertexB );
899+
900+
mesh.GetEdgesConnectedToVertex( vertexA, out var edgesA );
901+
bool allEdgesASelected = edgesA.All( e =>
902+
_edges.Any( selectedEdge => selectedEdge.Component == edge.Component && selectedEdge.Handle == e )
903+
);
904+
905+
mesh.GetEdgesConnectedToVertex( vertexB, out var edgesB );
906+
bool allEdgesBSelected = edgesB.All( e =>
907+
_edges.Any( selectedEdge => selectedEdge.Component == edge.Component && selectedEdge.Handle == e )
908+
);
909+
910+
if ( allEdgesASelected && allEdgesBSelected )
911+
{
912+
edgesToKeep.Add( edge );
913+
}
914+
}
915+
916+
selection.Clear();
917+
foreach ( var edge in edgesToKeep )
918+
{
919+
if ( edge.IsValid() )
920+
selection.Add( edge );
921+
}
922+
}
923+
}
827924
}
828925
}

game/addons/tools/Code/Scene/Mesh/Tools/EdgeTool.cs

Lines changed: 176 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,33 +63,97 @@ public override void OnUpdate()
6363
AngleFromEdges( edges[0], edges[1] );
6464
}
6565

66+
protected override IEnumerable<MeshEdge> ConvertSelectionToCurrentType()
67+
{
68+
var selectedFaces = Selection.OfType<MeshFace>().ToHashSet();
69+
var selectedVertices = Selection.OfType<MeshVertex>().ToHashSet();
70+
71+
foreach ( var face in selectedFaces )
72+
{
73+
if ( !face.IsValid() )
74+
continue;
75+
76+
var mesh = face.Component.Mesh;
77+
var edges = mesh.GetFaceEdges( face.Handle );
78+
79+
foreach ( var edge in edges )
80+
{
81+
if ( edge.IsValid )
82+
yield return new MeshEdge( face.Component, edge );
83+
}
84+
}
85+
86+
var candidateEdges = new HashSet<MeshEdge>();
87+
88+
foreach ( var vertex in selectedVertices )
89+
{
90+
if ( !vertex.IsValid() )
91+
continue;
92+
93+
var mesh = vertex.Component.Mesh;
94+
mesh.GetEdgesConnectedToVertex( vertex.Handle, out var edges );
95+
96+
foreach ( var edge in edges )
97+
{
98+
if ( edge.IsValid )
99+
candidateEdges.Add( new MeshEdge( vertex.Component, edge ) );
100+
}
101+
}
102+
103+
foreach ( var edge in candidateEdges )
104+
{
105+
var mesh = edge.Component.Mesh;
106+
mesh.GetEdgeVertices( edge.Handle, out var vertexA, out var vertexB );
107+
108+
bool bothVerticesSelected =
109+
selectedVertices.Contains( new MeshVertex( edge.Component, vertexA ) ) &&
110+
selectedVertices.Contains( new MeshVertex( edge.Component, vertexB ) );
111+
112+
if ( bothVerticesSelected )
113+
{
114+
yield return edge;
115+
}
116+
}
117+
}
118+
66119
private static void DrawOpenEdge( MeshEdge edge )
67120
{
68-
var hFace = edge.Component.Mesh.GetHalfEdgeFace( edge.Handle );
69-
var spacing = 2.0f;
121+
var mesh = edge.Component.Mesh;
122+
var hFace = mesh.GetHalfEdgeFace( edge.Handle );
123+
var spacing = 1.5f;
124+
70125
if ( !hFace.IsValid )
71126
{
72-
hFace = edge.Component.Mesh.GetHalfEdgeFace( edge.Component.Mesh.GetOppositeHalfEdge( edge.Handle ) );
127+
hFace = mesh.GetHalfEdgeFace( mesh.GetOppositeHalfEdge( edge.Handle ) );
73128
spacing *= -1.0f;
74129
}
75130

131+
if ( !hFace.IsValid )
132+
return;
133+
76134
var line = edge.Line;
77135
var a = edge.Transform.PointToWorld( line.Start );
78136
var b = edge.Transform.PointToWorld( line.End );
79-
80-
edge.Component.Mesh.ComputeFaceNormal( hFace, out var normal );
81-
var direction = (line.End - line.Start).Normal;
82137
var length = a.Distance( b );
138+
139+
mesh.ComputeFaceNormal( hFace, out var normal );
140+
var direction = (b - a).Normal;
83141
var tangent = normal.Cross( direction );
84-
var numHashes = length / 5.0f;
85142

86-
for ( int i = 0; i < numHashes; ++i )
143+
var cameraDistance = Gizmo.Camera.Position.Distance( (a + b) * 0.5f );
144+
var visualScale = (cameraDistance * 0.008f).Clamp( 0.05f, 3f );
145+
146+
spacing *= visualScale;
147+
148+
var hashSpacing = (2.5f * visualScale).Clamp( 0.5f, 50f );
149+
var numHashes = Math.Max( 3, (int)(length / hashSpacing) );
150+
151+
for ( int i = 0; i < numHashes; i++ )
87152
{
88-
var d = i / numHashes * length;
89-
var p = line.Start + direction * d;
90-
var a2 = edge.Transform.PointToWorld( p );
91-
var b2 = edge.Transform.PointToWorld( p + tangent * spacing );
92-
Gizmo.Draw.Line( a2, b2 );
153+
var t = i / (float)(numHashes - 1);
154+
var position = Vector3.Lerp( a, b, t );
155+
var hashEnd = position + tangent * spacing;
156+
Gizmo.Draw.Line( position, hashEnd );
93157
}
94158
}
95159

@@ -176,21 +240,116 @@ private static void AngleFromEdges( MeshEdge edge1, MeshEdge edge2 )
176240

177241
private void SelectEdgeLoop()
178242
{
179-
var edge = MeshTrace.GetClosestEdge( 8 );
180-
if ( !edge.IsValid() )
243+
var targetEdge = MeshTrace.GetClosestEdge( 8 );
244+
if ( !targetEdge.IsValid() )
245+
return;
246+
247+
if ( Application.KeyboardModifiers.HasFlag( KeyboardModifiers.Shift ) && TrySelectEdgePath( targetEdge ) )
181248
return;
182249

183250
if ( !Application.KeyboardModifiers.HasFlag( KeyboardModifiers.Shift ) )
184251
Selection.Clear();
185252

186253
if ( !Application.KeyboardModifiers.HasFlag( KeyboardModifiers.Ctrl ) )
187254
{
188-
edge.Component.Mesh.FindEdgeLoopForEdges( [edge.Handle], out var hEdges );
255+
targetEdge.Component.Mesh.FindEdgeLoopForEdges( [targetEdge.Handle], out var hEdges );
189256
foreach ( var hEdge in hEdges )
190-
Selection.Add( new MeshEdge( edge.Component, hEdge ) );
257+
Selection.Add( new MeshEdge( targetEdge.Component, hEdge ) );
191258
}
192259
}
193260

261+
private bool TrySelectEdgePath( MeshEdge targetEdge )
262+
{
263+
var selected = Selection.OfType<MeshEdge>()
264+
.Where( e => e.IsValid() && e.Component == targetEdge.Component )
265+
.ToList();
266+
267+
if ( selected.Count == 0 || selected.Count > 2 )
268+
return false;
269+
270+
var startEdge = selected.FirstOrDefault( e =>
271+
e.Handle != targetEdge.Handle &&
272+
e.Handle != targetEdge.Component.Mesh.GetOppositeHalfEdge( targetEdge.Handle )
273+
);
274+
275+
if ( !startEdge.IsValid() )
276+
return false;
277+
278+
var path = FindShortestEdgePath( startEdge, targetEdge );
279+
if ( path == null || path.Count == 0 )
280+
return false;
281+
282+
foreach ( var edge in path.Where( e => !Selection.Contains( e ) ) )
283+
Selection.Add( edge );
284+
285+
return true;
286+
}
287+
288+
private List<MeshEdge> FindShortestEdgePath( MeshEdge start, MeshEdge end )
289+
{
290+
if ( start.Component != end.Component )
291+
return null;
292+
293+
var mesh = start.Component.Mesh;
294+
var queue = new Queue<HalfEdgeHandle>();
295+
var visited = new HashSet<HalfEdgeHandle>();
296+
var parent = new Dictionary<HalfEdgeHandle, HalfEdgeHandle>();
297+
298+
var startHandle = start.Handle.Index < mesh.GetOppositeHalfEdge( start.Handle ).Index ?
299+
start.Handle : mesh.GetOppositeHalfEdge( start.Handle );
300+
var endHandle = end.Handle.Index < mesh.GetOppositeHalfEdge( end.Handle ).Index ?
301+
end.Handle : mesh.GetOppositeHalfEdge( end.Handle );
302+
303+
queue.Enqueue( startHandle );
304+
visited.Add( startHandle );
305+
306+
while ( queue.Count > 0 )
307+
{
308+
var current = queue.Dequeue();
309+
310+
if ( current == endHandle || mesh.GetOppositeHalfEdge( current ) == endHandle )
311+
{
312+
var path = new List<MeshEdge>();
313+
var step = current;
314+
315+
while ( step.IsValid )
316+
{
317+
path.Add( new MeshEdge( start.Component, step ) );
318+
if ( step == startHandle || mesh.GetOppositeHalfEdge( step ) == startHandle )
319+
break;
320+
if ( !parent.TryGetValue( step, out step ) )
321+
break;
322+
}
323+
324+
path.Reverse();
325+
return path;
326+
}
327+
328+
mesh.GetEdgeVertices( current, out var vertexA, out var vertexB );
329+
330+
mesh.GetEdgesConnectedToVertex( vertexA, out var edgesA );
331+
mesh.GetEdgesConnectedToVertex( vertexB, out var edgesB );
332+
333+
foreach ( var neighbor in edgesA.Concat( edgesB ) )
334+
{
335+
if ( neighbor.IsValid && !visited.Contains( neighbor ) )
336+
{
337+
var oppositeNeighbor = mesh.GetOppositeHalfEdge( neighbor );
338+
if ( !visited.Contains( oppositeNeighbor ) )
339+
{
340+
visited.Add( neighbor );
341+
visited.Add( oppositeNeighbor );
342+
parent[neighbor] = current;
343+
parent[oppositeNeighbor] = current;
344+
queue.Enqueue( neighbor );
345+
}
346+
}
347+
}
348+
}
349+
350+
return null;
351+
}
352+
194353
private void SelectEdge()
195354
{
196355
var edge = MeshTrace.GetClosestEdge( 8 );

0 commit comments

Comments
 (0)