diff --git a/CHANGELOG.md b/CHANGELOG.md index 5382f0ab6..d96204db3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed +- [UUM-141074] Fixed an issue where the CreateShape and CreatePolyshape tools would not properly snap to the grid. - [UUM-133529] Fixed an issue where ProBuilder GameObjects could not change back to 3D shapes after being changed to Plane or Sprite. - [UUM-133861] Fixed "Look rotation viewing vector is zero" log being spammed when holding shift while using a create tool such as Create Sprite. - [UUM-133859] Fixed an issue in URP projects where the Editor would recompile scripts when after a rectangle selection in ProBuilder. diff --git a/Editor/EditorCore/DrawShapeTool.cs b/Editor/EditorCore/DrawShapeTool.cs index e11f2344c..7b532be53 100644 --- a/Editor/EditorCore/DrawShapeTool.cs +++ b/Editor/EditorCore/DrawShapeTool.cs @@ -564,7 +564,7 @@ public override void OnWillBeDeactivated() ProBuilderEditor.selectModeChanged -= OnSelectModeChanged; EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; - if (m_ProBuilderShape != null && !( m_CurrentState is ShapeState_InitShape )) + if (m_ProBuilderShape != null) m_CurrentState = ShapeState.ResetState(); if (m_DuplicateGO != null) diff --git a/Editor/EditorCore/PolyShapeTool.cs b/Editor/EditorCore/PolyShapeTool.cs index 81ab5e431..138284e34 100644 --- a/Editor/EditorCore/PolyShapeTool.cs +++ b/Editor/EditorCore/PolyShapeTool.cs @@ -236,6 +236,8 @@ static bool CanCreateNewPolyShape() [EditorTool("Edit PolyShape", typeof(PolyShape))] public class PolyShapeTool : EditorTool { + public override bool gridSnapEnabled => true; + [MenuItem("Tools/ProBuilder/Edit/Edit PolyShape", true, PreferenceKeys.menuEditor + 10)] static bool ValidateEditShapeTool() { diff --git a/Editor/StateMachines/ShapeState_InitShape.cs b/Editor/StateMachines/ShapeState_InitShape.cs index 9e8e9d347..68298898f 100644 --- a/Editor/StateMachines/ShapeState_InitShape.cs +++ b/Editor/StateMachines/ShapeState_InitShape.cs @@ -47,59 +47,54 @@ public override ShapeState DoState(Event evt) if(evt.isMouse && HandleUtility.nearestControl == tool.controlID) { - if (evt.type != EventType.MouseDown) - { - HandleUtility.PlaceObject(evt.mousePosition, out m_HitPosition, out _); - m_HitPosition = tool.GetPoint(m_HitPosition); - } - else + var res = EditorHandleUtility.FindBestPlaneAndBitangent(evt.mousePosition, tool.m_DuplicateGO); + Ray ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition); + float hit; + + if (res.item1.Raycast(ray, out hit)) { - var res = EditorHandleUtility.FindBestPlaneAndBitangent(evt.mousePosition, tool.m_DuplicateGO); - Ray ray = HandleUtility.GUIPointToWorldRay(evt.mousePosition); - float hit; + var planeNormal = res.item1.normal; + var planeCenter = res.item1.normal * -res.item1.distance; + + // if hit point on plane is cardinal axis and on grid, snap to grid. + if (Math.IsCardinalAxis(planeNormal)) + { + const float epsilon = .00001f; + bool offGrid = false; + Vector3 snapVal = EditorSnapping.activeMoveSnapValue; + Vector3 center = + Vector3.Scale(ProBuilderSnapping.GetSnappingMaskBasedOnNormalVector(planeNormal), + planeCenter); + for (int i = 0; i < 3; i++) + offGrid |= Mathf.Abs(snapVal[i] % center[i]) > epsilon; + tool.m_IsOnGrid = !offGrid; + } + else + { + tool.m_IsOnGrid = false; + } - if (evt.button == 0 && res.item1.Raycast(ray, out hit)) + m_HitPosition = tool.GetPoint(ray.GetPoint(hit)); + + //Click has been done => Define a plane for the tool + if (evt.type == EventType.MouseDown && evt.button == 0) { - //Plane init + //Plane init (persist for subsequent states) tool.m_Plane = res.item1; tool.m_PlaneForward = res.item2; tool.m_PlaneRight = Vector3.Cross(tool.m_Plane.normal, tool.m_PlaneForward); - var planeNormal = tool.m_Plane.normal; - var planeCenter = tool.m_Plane.normal * -tool.m_Plane.distance; - // if hit point on plane is cardinal axis and on grid, snap to grid. - if (Math.IsCardinalAxis(planeNormal)) - { - const float epsilon = .00001f; - bool offGrid = false; - Vector3 snapVal = EditorSnapping.activeMoveSnapValue; - Vector3 center = - Vector3.Scale(ProBuilderSnapping.GetSnappingMaskBasedOnNormalVector(planeNormal), - planeCenter); - for (int i = 0; i < 3; i++) - offGrid |= Mathf.Abs(snapVal[i] % center[i]) > epsilon; - tool.m_IsOnGrid = !offGrid; - } - else - { - tool.m_IsOnGrid = false; - } - - m_HitPosition = tool.GetPoint(ray.GetPoint(hit)); - - //Click has been done => Define a plane for the tool - if (evt.type == EventType.MouseDown && evt.button == 0) - { - //BB init - tool.m_BB_Origin = m_HitPosition; - tool.m_BB_HeightCorner = tool.m_BB_Origin; - tool.m_BB_OppositeCorner = tool.m_BB_Origin; - - return NextState(); - } + //BB init + tool.m_BB_Origin = m_HitPosition; + tool.m_BB_HeightCorner = tool.m_BB_Origin; + tool.m_BB_OppositeCorner = tool.m_BB_Origin; + + return NextState(); } - else - m_HitPosition = Vector3.positiveInfinity; + } + else + { + m_HitPosition = Vector3.positiveInfinity; } } diff --git a/Tests/Editor/Editor/ShapeToolGridSnapTests.cs b/Tests/Editor/Editor/ShapeToolGridSnapTests.cs new file mode 100644 index 000000000..9b38bf9fe --- /dev/null +++ b/Tests/Editor/Editor/ShapeToolGridSnapTests.cs @@ -0,0 +1,112 @@ +using NUnit.Framework; +using UnityEditor; +using UnityEditor.EditorTools; +using UnityEditor.ProBuilder; +using UnityEngine; +using UnityEngine.ProBuilder; +using ToolManager = UnityEditor.EditorTools.ToolManager; + +public class ShapeToolGridSnapTests +{ + [SetUp] + public void SetUp() + { + ToolManager.SetActiveContext(); + } + + [TearDown] + public void TearDown() + { + ToolManager.RestorePreviousPersistentTool(); + } + + [Test] + public void CreateCubeTool_HasGridSnapEnabled() + { + ToolManager.SetActiveTool(); + var toolType = ToolManager.activeToolType; + + Assert.That(toolType, Is.EqualTo(typeof(CreateCubeTool))); + + // Verify the tool has gridSnapEnabled property set to true + var tool = DrawShapeTool.instance; + Assert.That(tool, Is.Not.Null); + Assert.That(tool.gridSnapEnabled, Is.True); + } + + [Test] + public void DrawShapeTool_GetPoint_SnapsWhenOnGridAndGridSnapActive() + { + ToolManager.SetActiveTool(); + var tool = DrawShapeTool.instance; + Assume.That(tool, Is.Not.Null); + + EditorSnapSettings.gridSnapEnabled = true; + EditorSnapSettings.snapEnabled = true; + + var previousPivotRotation = Tools.pivotRotation; + Tools.pivotRotation = PivotRotation.Global; + + // Enable grid mode (this is set by ShapeState_InitShape during hover) + tool.m_IsOnGrid = true; + + // Only test if grid snapping is currently active in the editor + if (EditorSnapSettings.gridSnapActive) + { + // Test position that should snap to grid + Vector3 unsnappedPosition = new Vector3(1.23f, 2.34f, 3.45f); + Vector3 snappedPosition = tool.GetPoint(unsnappedPosition); + + // Position should be snapped (not equal to original) + Assert.That(snappedPosition, Is.Not.EqualTo(unsnappedPosition)); + } + else + { + // Skip test if grid snap is not active + Assert.Ignore("Grid snapping is not active in the editor, skipping snap verification"); + } + + Tools.pivotRotation = previousPivotRotation; + } + + [Test] + public void DrawShapeTool_GetPoint_DoesNotSnapWhenOffGrid() + { + ToolManager.SetActiveTool(); + var tool = DrawShapeTool.instance; + Assume.That(tool, Is.Not.Null); + + // Disable grid mode + tool.m_IsOnGrid = false; + + // Test position should NOT snap regardless of editor snap settings + Vector3 position = new Vector3(1.23f, 2.34f, 3.45f); + Vector3 result = tool.GetPoint(position); + + // Position should remain unchanged when m_IsOnGrid is false + Assert.That(result, Is.EqualTo(position)); + } + + [Test] + public void DrawShapeTool_GetPoint_RespectsIncrementalSnap() + { + ToolManager.SetActiveTool(); + var tool = DrawShapeTool.instance; + Assume.That(tool, Is.Not.Null); + + // Get current incremental snap value + Vector3 snapValue = EditorSnapping.incrementalSnapMoveValue; + + // Test with incremental snap enabled + Vector3 unsnappedPosition = new Vector3(1.23f, 2.34f, 3.45f); + Vector3 snappedPosition = tool.GetPoint(unsnappedPosition, useIncrementSnap: true); + + // Position should be snapped to incremental snap value + Assert.That(snappedPosition, Is.Not.EqualTo(unsnappedPosition)); + + // Verify each component is a multiple of snap value (within tolerance) + Assert.That(snappedPosition.x % snapValue.x, Is.EqualTo(0).Within(0.001f)); + Assert.That(snappedPosition.y % snapValue.y, Is.EqualTo(0).Within(0.001f)); + Assert.That(snappedPosition.z % snapValue.z, Is.EqualTo(0).Within(0.001f)); + } +} diff --git a/Tests/Editor/Editor/ShapeToolGridSnapTests.cs.meta b/Tests/Editor/Editor/ShapeToolGridSnapTests.cs.meta new file mode 100644 index 000000000..f112cffd4 --- /dev/null +++ b/Tests/Editor/Editor/ShapeToolGridSnapTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 41c2a001abcc39e439cbf0c7a5d4df2b \ No newline at end of file