diff --git a/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs b/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs index 9f1fb770a..4d9326bb5 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.Settings.cs @@ -75,6 +75,11 @@ namespace FlaxEditor.Gizmo /// public bool ScaleSnapEnabled = false; + /// + /// True if enable absolute grid snapping (snaps objects to world-space grid, not the one relative to gizmo location) + /// + public bool AbsoluteSnapEnabled = false; + /// /// Translation snap value /// diff --git a/Source/Editor/Gizmo/TransformGizmoBase.cs b/Source/Editor/Gizmo/TransformGizmoBase.cs index 92846c907..da1ed575e 100644 --- a/Source/Editor/Gizmo/TransformGizmoBase.cs +++ b/Source/Editor/Gizmo/TransformGizmoBase.cs @@ -2,8 +2,10 @@ #if USE_LARGE_WORLDS using Real = System.Double; +using Mathr = FlaxEngine.Mathd; #else using Real = System.Single; +using Mathr = FlaxEngine.Mathf; #endif using System; @@ -40,6 +42,7 @@ namespace FlaxEditor.Gizmo private Vector3 _intersectPosition; private bool _isActive; private bool _isDuplicating; + private bool _hasAbsoluteSnapped; private bool _isTransforming; private bool _isSelected; @@ -366,6 +369,7 @@ namespace FlaxEditor.Gizmo if ((isScaling ? ScaleSnapEnabled : TranslationSnapEnable) || Owner.UseSnapping) { var snapValue = new Vector3(isScaling ? ScaleSnapValue : TranslationSnapValue); + _translationScaleSnapDelta += delta; if (!isScaling && snapValue.X < 0.0f) { @@ -384,11 +388,29 @@ namespace FlaxEditor.Gizmo else snapValue.Z = (Real)b.Minimum.Z - b.Maximum.Z; } + + Vector3 absoluteDelta = Vector3.Zero; + if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled && ActiveTransformSpace == TransformSpace.World) + { + // Remove delta to offset local-space grid into the world-space grid + _hasAbsoluteSnapped = true; + Vector3 currentTranslationScale = isScaling ? GetSelectedTransform(0).Scale : GetSelectedTransform(0).Translation; + absoluteDelta = currentTranslationScale - new Vector3( + Mathr.Round(currentTranslationScale.X / snapValue.X) * snapValue.X, + Mathr.Round(currentTranslationScale.Y / snapValue.Y) * snapValue.Y, + Mathr.Round(currentTranslationScale.Z / snapValue.Z) * snapValue.Z); + } + delta = new Vector3( (int)(_translationScaleSnapDelta.X / snapValue.X) * snapValue.X, (int)(_translationScaleSnapDelta.Y / snapValue.Y) * snapValue.Y, (int)(_translationScaleSnapDelta.Z / snapValue.Z) * snapValue.Z); _translationScaleSnapDelta -= delta; + delta -= absoluteDelta; + } + else + { + _hasAbsoluteSnapped = false; } if (_activeMode == Mode.Translate) @@ -418,12 +440,33 @@ namespace FlaxEditor.Gizmo if (RotationSnapEnabled || Owner.UseSnapping) { float snapValue = RotationSnapValue * Mathf.DegreesToRadians; + + float absoluteDelta = 0.0f; + if (!_hasAbsoluteSnapped && AbsoluteSnapEnabled && ActiveTransformSpace == TransformSpace.World) + { + // Remove delta to offset world-space grid into the local-space grid + _hasAbsoluteSnapped = true; + float currentAngle = 0.0f; + switch (_activeAxis) + { + case Axis.X: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.X; break; + case Axis.Y: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.Y; break; + case Axis.Z: currentAngle = GetSelectedTransform(0).Orientation.EulerAngles.Z; break; + } + absoluteDelta = currentAngle - (Mathf.Round(currentAngle / RotationSnapValue) * RotationSnapValue); + } + _rotationSnapDelta += delta; float snapped = Mathf.Round(_rotationSnapDelta / snapValue) * snapValue; _rotationSnapDelta -= snapped; delta = snapped; + delta -= absoluteDelta * Mathf.DegreesToRadians; + } + else + { + _hasAbsoluteSnapped = false; } switch (_activeAxis) diff --git a/Source/Editor/Viewport/EditorGizmoViewport.cs b/Source/Editor/Viewport/EditorGizmoViewport.cs index d230f53fb..39c3d4bf3 100644 --- a/Source/Editor/Viewport/EditorGizmoViewport.cs +++ b/Source/Editor/Viewport/EditorGizmoViewport.cs @@ -138,7 +138,9 @@ namespace FlaxEditor.Viewport if (useProjectCache) { // Initialize snapping enabled from cached values - if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out bool cachedBool)) + if (editor.ProjectCache.TryGetCustomData("AbsoluteSnapState", out bool cachedBool)) + transformGizmo.AbsoluteSnapEnabled = cachedBool; + if (editor.ProjectCache.TryGetCustomData("TranslateSnapState", out cachedBool)) transformGizmo.TranslationSnapEnable = cachedBool; if (editor.ProjectCache.TryGetCustomData("RotationSnapState", out cachedBool)) transformGizmo.RotationSnapEnabled = cachedBool; @@ -162,13 +164,31 @@ namespace FlaxEditor.Viewport TooltipText = $"Gizmo transform space (world or local) ({inputOptions.ToggleTransformSpace})", Parent = transformSpaceWidget }; + transformSpaceWidget.Parent = viewport; + + // Absolute snapping widget + var absoluteSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); + var enableAbsoluteSnapping = new ViewportWidgetButton("A", SpriteHandle.Invalid, null, true) + { + Checked = transformGizmo.AbsoluteSnapEnabled, + TooltipText = "Enable absolute grid snapping (world-space absolute grid, rather than object-relative grid)", + Parent = absoluteSnappingWidget + }; + enableAbsoluteSnapping.Toggled += _ => + { + transformGizmo.AbsoluteSnapEnabled = !transformGizmo.AbsoluteSnapEnabled; + if (useProjectCache) + editor.ProjectCache.SetCustomData("AbsoluteSnapState", transformGizmo.AbsoluteSnapEnabled); + }; + absoluteSnappingWidget.Parent = viewport; + transformSpaceToggle.Toggled += _ => { transformGizmo.ToggleTransformSpace(); if (useProjectCache) editor.ProjectCache.SetCustomData("TransformSpaceState", transformGizmo.ActiveTransformSpace.ToString()); + absoluteSnappingWidget.Visible = transformGizmo.ActiveTransformSpace == TransformGizmoBase.TransformSpace.World; }; - transformSpaceWidget.Parent = viewport; // Scale snapping widget var scaleSnappingWidget = new ViewportWidgetsContainer(ViewportWidgetLocation.UpperRight); @@ -383,17 +403,17 @@ namespace FlaxEditor.Viewport gizmoModeRotate.Checked = mode == TransformGizmoBase.Mode.Rotate; gizmoModeScale.Checked = mode == TransformGizmoBase.Mode.Scale; }; - + // Setup input actions - viewport.InputActions.Add(options => options.TranslateMode, () => + viewport.InputActions.Add(options => options.TranslateMode, () => { viewport.GetInput(out var input); if (input.IsMouseRightDown) return; - + transformGizmo.ActiveMode = TransformGizmoBase.Mode.Translate; }); - viewport.InputActions.Add(options => options.RotateMode, () => + viewport.InputActions.Add(options => options.RotateMode, () => { viewport.GetInput(out var input); if (input.IsMouseRightDown)