-
Notifications
You must be signed in to change notification settings - Fork 92
Enhance cut tool functionality and code refactor #669
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
df84778
93988b9
6109554
b8d9edf
2bbf078
9919da7
418dcf3
7df978a
472f697
7a2a64f
0ad2382
8c68e31
df2a0ee
1bfe346
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using UnityEditor.EditorTools; | ||
| using UnityEngine; | ||
| using UnityEngine.ProBuilder; | ||
| using UnityEngine.ProBuilder.MeshOperations; | ||
| using UnityEngine.UIElements; | ||
| using Cursor = UnityEngine.Cursor; | ||
| using Edge = UnityEngine.ProBuilder.Edge; | ||
| using Math = UnityEngine.ProBuilder.Math; | ||
| using UObject = UnityEngine.Object; | ||
| using RaycastHit = UnityEngine.ProBuilder.RaycastHit; | ||
| using UHandleUtility = UnityEditor.HandleUtility; | ||
|
|
||
| using ToolManager = UnityEditor.EditorTools.ToolManager; | ||
| using Vertex = UnityEngine.ProBuilder.Vertex; | ||
|
|
||
| namespace UnityEditor.ProBuilder | ||
| { | ||
| partial class CutTool | ||
| { | ||
| /// <summary> | ||
| /// Create a local coordinate system on the face plane. | ||
| /// </summary> | ||
| static void GetFacePlaneAxes(Vector3 faceNormal, out Vector3 faceRight, out Vector3 faceUp) | ||
| { | ||
| if (Mathf.Abs(Vector3.Dot(faceNormal, Vector3.up)) > 0.99f) | ||
| faceRight = Vector3.Cross(faceNormal, Vector3.forward).normalized; | ||
| else | ||
| faceRight = Vector3.Cross(faceNormal, Vector3.up).normalized; | ||
| faceUp = Vector3.Cross(faceNormal, faceRight).normalized; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Rectangle mode: click and drag to define a rectangular cut on the face. | ||
| /// On mouse up, auto-places the 4 corners and executes the cut. | ||
| /// </summary> | ||
| void DoRectanglePlacement(EditorWindow window) | ||
| { | ||
| Event evt = Event.current; | ||
| EventType evtType = evt.type; | ||
|
|
||
| m_SnappingPoint = m_SnapToGeometry || (evt.modifiers & EventModifiers.Control) != 0; | ||
| m_ModifyingPoint = false; | ||
|
|
||
| bool hasHitPosition = UpdateHitPosition(); | ||
|
|
||
| // Visual helpers | ||
| if (evtType == EventType.Repaint) | ||
| { | ||
| if (hasHitPosition && IsCursorInSceneView(window)) | ||
| { | ||
| m_CurrentCutCursor = m_CutCursorTexture; | ||
| m_CurrentHandleColor = k_HandleColorAddNewVertex; | ||
| } | ||
| else | ||
| { | ||
| m_CurrentCutCursor = null; | ||
| m_CurrentPosition = Vector3.positiveInfinity; | ||
| } | ||
| } | ||
|
|
||
| // Mouse down: start rectangle drag | ||
| if (hasHitPosition | ||
| && evtType == EventType.MouseDown && evt.button == 0 | ||
| && HandleUtility.nearestControl == m_ControlId | ||
| && !m_RectDragging) | ||
| { | ||
| m_RectDragging = true; | ||
| m_RectStartPoint = m_CurrentPosition; | ||
| m_RectEndPoint = m_CurrentPosition; | ||
| m_TargetFace = m_CurrentFace; | ||
|
|
||
| var edges = m_TargetFace.edges; | ||
| m_SelectedVertices = edges.Select(e => e.a).ToArray(); | ||
| m_SelectedEdges = edges.ToArray(); | ||
|
|
||
| m_CutPath.Clear(); | ||
| m_MeshConnections.Clear(); | ||
| evt.Use(); | ||
| } | ||
|
|
||
| // Mouse drag: update rectangle end point | ||
| if (m_RectDragging && evtType == EventType.MouseDrag && evt.button == 0) | ||
| { | ||
| if (hasHitPosition && m_CurrentFace == m_TargetFace) | ||
| { | ||
| m_RectEndPoint = m_CurrentPosition; | ||
| } | ||
| evt.Use(); | ||
| } | ||
|
|
||
| // Mouse up: finalize the rectangle and execute cut | ||
| if (m_RectDragging | ||
| && (evtType == EventType.MouseUp && evt.button == 0)) | ||
| { | ||
| m_RectDragging = false; | ||
|
|
||
| // Project start/end onto the face plane to compute the other 2 corners | ||
| Vector3 start = m_RectStartPoint; | ||
| Vector3 end = m_RectEndPoint; | ||
|
|
||
| // Compute face normal for projection | ||
| Vector3 faceNormal = Math.Normal(m_Mesh, m_TargetFace); | ||
|
|
||
| Vector3 faceRight, faceUp; | ||
| GetFacePlaneAxes(faceNormal, out faceRight, out faceUp); | ||
|
|
||
| // Decompose rect diagonals in face space | ||
| Vector3 diagonal = end - start; | ||
| float rightDot = Vector3.Dot(diagonal, faceRight); | ||
| float upDot = Vector3.Dot(diagonal, faceUp); | ||
|
|
||
| // Compute all 4 corners strictly on the face plane (coplanar) | ||
| Vector3 corner0 = start; | ||
| Vector3 corner1 = start + faceRight * rightDot; | ||
| Vector3 corner2 = start + faceRight * rightDot + faceUp * upDot; | ||
| Vector3 corner3 = start + faceUp * upDot; | ||
|
|
||
| // Snap only start point to grid, then project back onto face plane | ||
| if (m_SnapToGrid) | ||
| { | ||
| Vector3 snapped = ProBuilderSnapping.Snap(corner0, EditorSnapping.activeMoveSnapValue); | ||
| Plane facePlane = new Plane(faceNormal, corner0); | ||
| corner0 = facePlane.ClosestPointOnPlane(snapped); | ||
| corner1 = corner0 + faceRight * rightDot; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While Because all other corners are offset from Have you considered projecting if (m_SnapToGrid)
{
Vector3 snapped = ProBuilderSnapping.Snap(corner0, EditorSnapping.activeMoveSnapValue);
Plane facePlane = new Plane(faceNormal, corner0);
corner0 = facePlane.ClosestPointOnPlane(snapped);
corner1 = corner0 + faceRight * rightDot;
corner2 = corner0 + faceRight * rightDot + faceUp * upDot;
corner3 = corner0 + faceUp * upDot;
}🤖 Helpful? 👍/👎 by bug_hunter |
||
| corner2 = corner0 + faceRight * rightDot + faceUp * upDot; | ||
| corner3 = corner0 + faceUp * upDot; | ||
| } | ||
|
|
||
| if (HasSignificantRectangle(corner0, corner2)) | ||
| { | ||
| // Build cut path: 4 corners + close back to start to form a loop | ||
| UndoUtility.RecordObject(this, "Rectangle Cut"); | ||
|
|
||
| m_CurrentPositionNormal = faceNormal; | ||
| m_CurrentFace = m_TargetFace; | ||
|
|
||
| // Corner 0: run geometry snap so it connects to existing vertices/edges | ||
| m_CurrentPosition = corner0; | ||
| m_CurrentVertexTypes = VertexTypes.None; | ||
| if (m_SnapToGeometry) | ||
| CheckPointInMesh(); | ||
| if (m_CurrentVertexTypes == VertexTypes.None) | ||
| m_CurrentVertexTypes = VertexTypes.NewVertex; | ||
| corner0 = m_CurrentPosition; | ||
| AddCurrentPositionToPath(false); | ||
|
|
||
| // Corner 1 | ||
| m_CurrentPosition = corner1; | ||
| m_CurrentVertexTypes = VertexTypes.None; | ||
| if (m_SnapToGeometry) | ||
| CheckPointInMesh(); | ||
| if (m_CurrentVertexTypes == VertexTypes.None) | ||
| m_CurrentVertexTypes = VertexTypes.NewVertex; | ||
| AddCurrentPositionToPath(false); | ||
|
|
||
| // Corner 2 | ||
| m_CurrentPosition = corner2; | ||
| m_CurrentVertexTypes = VertexTypes.None; | ||
| if (m_SnapToGeometry) | ||
| CheckPointInMesh(); | ||
| if (m_CurrentVertexTypes == VertexTypes.None) | ||
| m_CurrentVertexTypes = VertexTypes.NewVertex; | ||
| AddCurrentPositionToPath(false); | ||
|
|
||
| // Corner 3 | ||
| m_CurrentPosition = corner3; | ||
| m_CurrentVertexTypes = VertexTypes.None; | ||
| if (m_SnapToGeometry) | ||
| CheckPointInMesh(); | ||
| if (m_CurrentVertexTypes == VertexTypes.None) | ||
| m_CurrentVertexTypes = VertexTypes.NewVertex; | ||
| AddCurrentPositionToPath(false); | ||
|
|
||
| // Close the loop by returning to the start corner | ||
| m_CurrentPosition = corner0; | ||
| m_CurrentVertexTypes = VertexTypes.VertexInShape; | ||
| AddCurrentPositionToPath(false); | ||
|
|
||
| // Don't auto-execute—let user click Complete button like point mode | ||
| RebuildCutShape(false); | ||
| } | ||
|
|
||
| m_RectStartPoint = Vector3.positiveInfinity; | ||
| m_RectEndPoint = Vector3.positiveInfinity; | ||
| evt.Use(); | ||
| } | ||
|
|
||
| if (TryPassThroughSelection(window, hasHitPosition)) | ||
| return; | ||
| } | ||
|
|
||
| bool HasSignificantRectangle(Vector3 start, Vector3 end) | ||
| { | ||
| return Vector3.Distance(start, end) > 0.001f; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Draw the rectangle preview during a drag operation. | ||
| /// </summary> | ||
| void DoRectanglePreview() | ||
| { | ||
| if (!m_RectDragging || m_Mesh == null || m_TargetFace == null) | ||
| return; | ||
|
|
||
| Transform trs = m_Mesh.transform; | ||
|
|
||
| // Compute the 4 corners in local space | ||
| Vector3 faceNormal = Math.Normal(m_Mesh, m_TargetFace); | ||
|
|
||
| Vector3 faceRight, faceUp; | ||
| GetFacePlaneAxes(faceNormal, out faceRight, out faceUp); | ||
|
|
||
| Vector3 diagonal = m_RectEndPoint - m_RectStartPoint; | ||
| float rightDot = Vector3.Dot(diagonal, faceRight); | ||
| float upDot = Vector3.Dot(diagonal, faceUp); | ||
|
|
||
| Vector3 c0 = trs.TransformPoint(m_RectStartPoint); | ||
| Vector3 c1 = trs.TransformPoint(m_RectStartPoint + faceRight * rightDot); | ||
| Vector3 c2 = trs.TransformPoint(m_RectEndPoint); | ||
| Vector3 c3 = trs.TransformPoint(m_RectStartPoint + faceUp * upDot); | ||
|
|
||
| // Draw filled rectangle (reuse cached arrays to avoid per-frame allocation) | ||
| Handles.color = k_RectPreviewColor; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you considered avoiding allocation of new You can declare reusable fields on the class level to avoid allocation completely: private readonly Vector3[] m_RectConvexPolygon = new Vector3[4];
private readonly Vector3[] m_RectPreviewPath = new Vector3[5];And then populate and pass them to the Handles methods. 🤖 Helpful? 👍/👎 by guardian |
||
| m_RectConvexPolygon[0] = c0; | ||
| m_RectConvexPolygon[1] = c1; | ||
| m_RectConvexPolygon[2] = c2; | ||
| m_RectConvexPolygon[3] = c3; | ||
| Handles.DrawAAConvexPolygon(m_RectConvexPolygon); | ||
|
|
||
| // Draw outline | ||
| Handles.color = k_RectOutlineColor; | ||
| m_RectPreviewPath[0] = c0; | ||
| m_RectPreviewPath[1] = c1; | ||
| m_RectPreviewPath[2] = c2; | ||
| m_RectPreviewPath[3] = c3; | ||
| m_RectPreviewPath[4] = c0; | ||
| Handles.DrawAAPolyLine(2f, m_RectPreviewPath); | ||
| } | ||
|
|
||
| bool TryPassThroughSelection(EditorWindow window, bool hasHitPosition) | ||
| { | ||
| if (m_CutPath.Count != 0 | ||
| || hasHitPosition | ||
| || HandleUtility.nearestControl != m_ControlId) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| SceneView sceneView = window as SceneView; | ||
| if (sceneView == null) | ||
| sceneView = SceneView.lastActiveSceneView; | ||
|
|
||
| if (sceneView == null || ProBuilderEditor.instance == null) | ||
| return false; | ||
|
|
||
| ProBuilderEditor.instance.HandleMouseEvent(sceneView, m_ControlId); | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Applying grid snapping independently to all four corners will distort the cut into an irregular, non-planar quadrilateral if the target face isn't perfectly axis-aligned.
Additionally, because
end(m_RectEndPoint) was grid-snapped during the drag interaction, it might possess an elevation offset relative to the face's plane. Usingcorner2 = endincorporates this offset, makingcorner2non-coplanar with the other three corners.Have you considered deriving
corner2strictly on the plane (e.g.,start + faceRight * rightDot + faceUp * upDot) and removing this secondary grid-snap block so the resulting shape remains a valid, planar rectangle?🤖 Helpful? 👍/👎 by bug_hunter