diff --git a/com.unity.render-pipelines.core/Editor/Gizmo/GizmoUtility.cs b/com.unity.render-pipelines.core/Editor/Gizmo/GizmoUtility.cs new file mode 100644 index 00000000000..2e24de36207 --- /dev/null +++ b/com.unity.render-pipelines.core/Editor/Gizmo/GizmoUtility.cs @@ -0,0 +1,26 @@ +using System; +using UnityEngine; + +namespace UnityEditor.Rendering +{ + public static class GizmoUtility + { + public static Color GetHandleColor(Color baseColor) + { + baseColor.a = 1f; + return baseColor; + } + + public static Color GetWireframeColor(Color baseColor) + { + baseColor.a = .7f; + return baseColor; + } + + public static Color GetWireframeColorBehindObjects(Color baseColor) + { + baseColor.a = .2f; + return baseColor; + } + } +} diff --git a/com.unity.render-pipelines.core/Editor/Gizmo/GizmoUtility.cs.meta b/com.unity.render-pipelines.core/Editor/Gizmo/GizmoUtility.cs.meta new file mode 100644 index 00000000000..3fb6b736167 --- /dev/null +++ b/com.unity.render-pipelines.core/Editor/Gizmo/GizmoUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 13d32c9cdf2984447b4802095ce716ae +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalBox.cs b/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalBox.cs index e0dbb33c4e8..d2d225dab6b 100644 --- a/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalBox.cs +++ b/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalBox.cs @@ -115,17 +115,24 @@ public Color baseColor set { value.a = 8f / 255; - m_MonochromeFillColor = value; - material.color = m_MonochromeFillColor; - value.a = 1f; - m_MonochromeHandleColor = value; - value.a = 0.7f; - m_WireframeColor = value; - value.a = 0.2f; - m_WireframeColorBehind = value; + SetBaseColor(value); } } + /// + /// Set the baseColor used to fill hull. All other colors are deduced from it except specific handle colors. + /// Instead of baseColor set, this will not force the opacity and keep what is provided for the filled faces. + /// + /// The color to use + public void SetBaseColor(Color color) + { + m_MonochromeFillColor = color; + material.color = m_MonochromeFillColor; + m_MonochromeHandleColor = GizmoUtility.GetHandleColor(color); + m_WireframeColor = GizmoUtility.GetWireframeColor(color); + m_WireframeColorBehind = GizmoUtility.GetWireframeColorBehindObjects(color); + } + //Note: Handles.Slider not allow to use a specific ControlID. //Thus Slider1D is used (with reflection) static Type k_Slider1D = Type.GetType("UnityEditorInternal.Slider1D, UnityEditor"); diff --git a/com.unity.render-pipelines.high-definition/CHANGELOG.md b/com.unity.render-pipelines.high-definition/CHANGELOG.md index 25df4a795e1..538ea16409b 100644 --- a/com.unity.render-pipelines.high-definition/CHANGELOG.md +++ b/com.unity.render-pipelines.high-definition/CHANGELOG.md @@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added support for the PlayStation 5 platform. +- Added pivot point manipulation for Decals (inspector and edit mode). +- Added UV manipulation for Decals (edit mode). +- Added color and intensity customization for Decals. ### Fixed - Fixed GC allocations from XR occlusion mesh when using multipass. @@ -33,6 +36,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixed issues with compositor's undo (cases 1305633, 1307170). ### Changed +- Removed the material pass probe volumes evaluation mode. - Change the source value for the ray tracing frame index iterator from m_FrameCount to the camera frame count (case 1301356). ## [11.0.0] - 2020-10-21 diff --git a/com.unity.render-pipelines.high-definition/Documentation~/Decal-Projector.md b/com.unity.render-pipelines.high-definition/Documentation~/Decal-Projector.md index 52e1275e04f..65b6e212e09 100644 --- a/com.unity.render-pipelines.high-definition/Documentation~/Decal-Projector.md +++ b/com.unity.render-pipelines.high-definition/Documentation~/Decal-Projector.md @@ -14,16 +14,19 @@ The Decal Projector includes a Scene view representation of its bounds and proje * A box that describes the 3D size of the projector; the projector draws its decal on every Material inside the box. -* An arrow that indicates the direction the projector faces. +* An arrow that indicates the direction the projector faces. The base of this arrow is on the pivot point. ![](Images/DecalProjector2.png) -The decal Projector also includes two gizmos that add handles on every face for you to click and drag to alter the size of the projector's bounds. +The decal Projector also includes three gizmos. The first two add handles on every face for you to click and drag to alter the size of the projector's bounds. |**Button**|**Gizmo**|**Description**| |-----|-----|-----| -|![](Images/DecalProjector3.png)|**Scale**|Scales the decal with the projector box. This changes the UVs of the Material to match the size of the projector box. This stretches the decal.| -|![](Images/DecalProjector4.png)|**Crop**|Crops the decal with the projector box. This changes the size of the projector box but not the UVs of the Material. This crops the decal.| +|![](Images/DecalProjector3.png)|**Scale**|Scales the decal with the projector box. This changes the UVs of the Material to match the size of the projector box. This stretches the decal. The Pivot remains still.| +|![](Images/DecalProjector4.png)|**Crop**|Crops the decal with the projector box. This changes the size of the projector box but not the UVs of the Material. This crops the decal. The Pivot remains still.| +|![](Images/DecalProjector5.png)|**Pivot / UV**|Moves the decal's pivot point without moving the projection box. This changes the transform position.
Note this also sets the UV used on the projected texture.| + +The color of the gizmos can be set up in the Preference window inside Color panel. ## Using the Inspector @@ -31,12 +34,13 @@ Using the Inspector allows you to change all of the Decal Projector properties, ## Properties -![](Images/DecalProjector5.png) +![](Images/DecalProjector6.png) | **Property** | **Description** | | ----------------------- | ------------------------------------------------------------ | | **Size** | The size of the projector influence box, and thus the decal along the projected plane. The projector scales the decal to match the **Width** (along the local x-axis) and **Height** (along the local y-axis) components of the **Size**. | | **Projection Depth** | The depth of the projector influence box. The projector scales the decal to match **Projection Depth**. The Decal Projector component projects decals along the local z-axis. | +| **Pivot** | The offset position of the transform regarding the projection box. To rotate the projected texture around a specific position, adjust the **X** and **Y** values. To set a depth offset for the projected texture, adjust the **Z** value. | | **Material** | The decal Material to project. The decal Material must use a HDRP/Decal Shader. | | **Decal Layer** | The layer that specifies the Materials to project the decal onto. Any Mesh Renderers or Terrain that uses a matching Decal Layer receives the decal. | | **Draw Distance** | The distance from the Camera to the Decal at which this projector stops projecting the decal and HDRP no longer renders the decal. | diff --git a/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector3.png b/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector3.png index 1fda03f14a3..f83fb4ea85f 100644 --- a/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector3.png +++ b/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ee48ea03dc20eb5f9795b6373da6f247e7d53659eb3884637d050c1a4fb7208 -size 335 +oid sha256:dc861506e84d5055dc66b6f3cf2f4f2ec2af4903996c2a57a7c01244983784d0 +size 731 diff --git a/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector4.png b/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector4.png index a51f5ee3151..849a223dd73 100644 --- a/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector4.png +++ b/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector4.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d1dc2574e29399b90a8b376882bd16b3b32f96cc281f172bb6bf4f71594e6037 -size 328 +oid sha256:ddef1d57d861335e487c5ecdaa4ff1530770d65aab69058f3f48675eb671abe6 +size 711 diff --git a/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector5.png b/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector5.png index 4ca38d845d8..321e309a2ba 100644 --- a/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector5.png +++ b/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1521303080bc06ff09dc3e6e67c42afe113dcbca9fd5d708bebe59bf2751a54 -size 21831 +oid sha256:1f682e51dd43fca42cb9592725e476aecdb6677463124b5566c2180923acddbe +size 881 diff --git a/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector6.png b/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector6.png new file mode 100644 index 00000000000..564e3c78758 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Documentation~/Images/DecalProjector6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa9f73b741574d9930dd763cae35847a4003d59b24d1997896d26b6f63f50e66 +size 16705 diff --git a/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DecalProjectorEditor.Skin.cs b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DecalProjectorEditor.Skin.cs index 98a337c4a73..0c8adc52a43 100644 --- a/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DecalProjectorEditor.Skin.cs +++ b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DecalProjectorEditor.Skin.cs @@ -6,7 +6,7 @@ partial class DecalProjectorEditor { const string k_EditShapePreservingUVTooltip = "Modifies the projector boundaries and crops/tiles the decal to fill them."; const string k_EditShapeWithoutPreservingUVTooltip = "Modifies the projector boundaries and stretches the decal to fill them."; - const string k_EditUVTooltip = "Modify the UV positions only"; + const string k_EditUVTooltip = "Modify the UV and the pivot position without moving the projection box. It can alter Transform."; static readonly GUIContent k_SizeContent = EditorGUIUtility.TrTextContent("Size", "Sets the size of the projector."); static readonly GUIContent[] k_SizeSubContent = new[] @@ -24,16 +24,8 @@ partial class DecalProjectorEditor static readonly GUIContent k_UVBiasContent = EditorGUIUtility.TrTextContent("Offset", "Sets the offset for the decal Material. Moves the decal along its UV axes."); static readonly GUIContent k_FadeFactorContent = EditorGUIUtility.TrTextContent("Fade Factor", "Controls the transparency of the decal."); static readonly GUIContent k_AffectTransparentContent = EditorGUIUtility.TrTextContent("Affects Transparent", "When enabled, HDRP draws this projector's decal on top of transparent surfaces."); + static readonly GUIContent k_Offset = EditorGUIUtility.TrTextContent("Pivot", "Controls the position of the pivot point of the decal."); - public static readonly Color k_GizmoColorBase = Color.white; - public static readonly Color[] k_BaseHandlesColor = new Color[] - { - Color.white, - Color.white, - Color.white, - Color.white, - Color.white, - Color.white - }; + public static readonly Color k_GizmoColorBase = new Color(1, 1, 1, 8f / 255); } } diff --git a/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DecalProjectorEditor.cs b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DecalProjectorEditor.cs index 89e7d567e57..97396e1feab 100644 --- a/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DecalProjectorEditor.cs +++ b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DecalProjectorEditor.cs @@ -1,7 +1,11 @@ using UnityEngine; -using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; using UnityEditor.ShortcutManagement; +using UnityEditor.IMGUI.Controls; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Linq.Expressions; using static UnityEditorInternal.EditMode; namespace UnityEditor.Rendering.HighDefinition @@ -10,6 +14,48 @@ namespace UnityEditor.Rendering.HighDefinition [CanEditMultipleObjects] partial class DecalProjectorEditor : Editor { + const float k_Limit = 100000; + const float k_LimitInv = 1 / k_Limit; + + static object s_ColorPref; + static Func GetColorPref; + static Color fullColor + { + get + { + Color c = s_LastColor; + c.a = 1; + return c; + } + } + static Color s_LastColor; + static void UpdateColorsInHandlesIfRequired() + { + Color c = GetColorPref(); + if (c != s_LastColor) + { + if (s_BoxHandle != null && !s_BoxHandle.Equals(null)) + s_BoxHandle = null; + + if (s_uvHandles != null && !s_uvHandles.Equals(null)) + s_uvHandles.baseColor = c; + + s_LastColor = c; + } + } + + static DecalProjectorEditor() + { + // PrefColor is the type to use to have a Color that is customizable inside the Preference/Colors panel. + // Sadly it is internal so we must create it and grab color from it by reflection. + Type prefColorType = typeof(Editor).Assembly.GetType("UnityEditor.PrefColor"); + s_ColorPref = Activator.CreateInstance(prefColorType, new object[] { "Scene/Decal", k_GizmoColorBase.r, k_GizmoColorBase.g, k_GizmoColorBase.b, k_GizmoColorBase.a }); + PropertyInfo colorInfo = prefColorType.GetProperty("Color"); + MemberExpression colorProperty = Expression.Property(Expression.Constant(s_ColorPref, prefColorType), colorInfo); + Expression> colorLambda = Expression.Lambda>(colorProperty); + GetColorPref = colorLambda.Compile(); + } + MaterialEditor m_MaterialEditor = null; SerializedProperty m_MaterialProperty; SerializedProperty m_DrawDistanceProperty; @@ -21,7 +67,8 @@ partial class DecalProjectorEditor : Editor SerializedProperty m_AffectsTransparencyProperty; SerializedProperty m_Size; SerializedProperty[] m_SizeValues; - SerializedProperty m_OffsetZ; + SerializedProperty m_Offset; + SerializedProperty[] m_OffsetValues; SerializedProperty m_FadeFactor; SerializedProperty m_DecalLayerMask; @@ -65,34 +112,50 @@ bool showAffectTransparencyHaveMultipleDifferentValue } } - static HierarchicalBox s_Handle; - static HierarchicalBox handle + static HierarchicalBox s_BoxHandle; + static HierarchicalBox boxHandle { get { - if (s_Handle == null || s_Handle.Equals(null)) + if (s_BoxHandle == null || s_BoxHandle.Equals(null)) { - s_Handle = new HierarchicalBox(k_GizmoColorBase, k_BaseHandlesColor); - s_Handle.monoHandle = false; + Color c = fullColor; + s_BoxHandle = new HierarchicalBox(s_LastColor, new[] { c, c, c, c, c, c }); + s_BoxHandle.SetBaseColor(s_LastColor); + s_BoxHandle.monoHandle = false; } - return s_Handle; + return s_BoxHandle; } } + static DisplacableRectHandles s_uvHandles; + static DisplacableRectHandles uvHandles + { + get + { + if (s_uvHandles == null || s_uvHandles.Equals(null)) + s_uvHandles = new DisplacableRectHandles(s_LastColor); + return s_uvHandles; + } + } + + static readonly BoxBoundsHandle s_AreaLightHandle = + new BoxBoundsHandle { axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Y }; + const SceneViewEditMode k_EditShapeWithoutPreservingUV = (SceneViewEditMode)90; const SceneViewEditMode k_EditShapePreservingUV = (SceneViewEditMode)91; - const SceneViewEditMode k_EditUV = (SceneViewEditMode)92; + const SceneViewEditMode k_EditUVAndPivot = (SceneViewEditMode)92; static readonly SceneViewEditMode[] k_EditVolumeModes = new SceneViewEditMode[] { k_EditShapeWithoutPreservingUV, k_EditShapePreservingUV }; - static readonly SceneViewEditMode[] k_EditPivotModes = new SceneViewEditMode[] + static readonly SceneViewEditMode[] k_EditUVAndPivotModes = new SceneViewEditMode[] { - k_EditUV + k_EditUVAndPivot }; - static SceneViewEditMode s_CurrentEditMode; - static bool s_ModeSwitched; + + static Func s_DrawPivotHandle; static GUIContent[] k_EditVolumeLabels = null; static GUIContent[] editVolumeLabels => k_EditVolumeLabels ?? (k_EditVolumeLabels = new GUIContent[] @@ -106,11 +169,34 @@ static HierarchicalBox handle EditorGUIUtility.TrIconContent("d_MoveTool", k_EditUVTooltip) }); - static Editor s_Owner; + static List s_Instances = new List(); + + static DecalProjectorEditor FindEditorFromSelection() + { + GameObject[] selection = Selection.gameObjects; + DecalProjector[] selectionTargets = Selection.GetFiltered(SelectionMode.Unfiltered); + + foreach (DecalProjectorEditor editor in s_Instances) + { + if (selectionTargets.Length != editor.targets.Length) + continue; + bool allOk = true; + foreach (DecalProjector selectionTarget in selectionTargets) + if (!Array.Find(editor.targets, t => t == selectionTarget)) + { + allOk = false; + break; + } + if (!allOk) + continue; + return editor; + } + return null; + } private void OnEnable() { - s_Owner = this; + s_Instances.Add(this); // Create an instance of the MaterialEditor UpdateMaterialEditor(); @@ -135,7 +221,13 @@ private void OnEnable() m_Size.FindPropertyRelative("y"), m_Size.FindPropertyRelative("z"), }; - m_OffsetZ = serializedObject.FindProperty("m_Offset").FindPropertyRelative("z"); + m_Offset = serializedObject.FindProperty("m_Offset"); + m_OffsetValues = new[] + { + m_Offset.FindPropertyRelative("x"), + m_Offset.FindPropertyRelative("y"), + m_Offset.FindPropertyRelative("z"), + }; m_FadeFactor = serializedObject.FindProperty("m_FadeFactor"); m_DecalLayerMask = serializedObject.FindProperty("m_DecalLayerMask"); } @@ -147,7 +239,8 @@ private void OnDisable() if (decalProjector != null) decalProjector.OnMaterialChange -= RequireUpdateMaterialEditor; } - s_Owner = null; + + s_Instances.Remove(this); } private void OnDestroy() => @@ -162,7 +255,7 @@ public Bounds OnGetFrameBounds() { DecalProjector decalProjector = target as DecalProjector; - return new Bounds(decalProjector.transform.position, handle.size); + return new Bounds(decalProjector.transform.position, boxHandle.size); } private bool m_RequireUpdateMaterialEditor = false; @@ -197,161 +290,189 @@ void OnSceneGUI() DrawHandles(); } - void DrawHandles() + void DrawBoxTransformationHandles(DecalProjector decalProjector) { - //Note: each target need to be handled individually to allow multi edition - DecalProjector decalProjector = target as DecalProjector; - - if (editMode == k_EditShapePreservingUV || editMode == k_EditShapeWithoutPreservingUV) + using (new Handles.DrawingScope(fullColor, Matrix4x4.TRS(decalProjector.transform.position, decalProjector.transform.rotation, Vector3.one))) { - using (new Handles.DrawingScope(Color.white, Matrix4x4.TRS(decalProjector.transform.position, decalProjector.transform.rotation, Vector3.one))) + Vector3 centerStart = decalProjector.pivot; + boxHandle.center = centerStart; + boxHandle.size = decalProjector.size; + + Vector3 boundsSizePreviousOS = boxHandle.size; + Vector3 boundsMinPreviousOS = boxHandle.size * -0.5f + boxHandle.center; + + EditorGUI.BeginChangeCheck(); + boxHandle.DrawHandle(); + if (EditorGUI.EndChangeCheck()) { - bool needToRefreshDecalProjector = false; + // Adjust decal transform if handle changed. + Undo.RecordObject(decalProjector, "Decal Projector Change"); - handle.center = decalProjector.offset; - handle.size = decalProjector.size; + decalProjector.size = boxHandle.size; + decalProjector.pivot += boxHandle.center - centerStart; - Vector3 boundsSizePreviousOS = handle.size; - Vector3 boundsMinPreviousOS = handle.size * -0.5f + handle.center; + Vector3 boundsSizeCurrentOS = boxHandle.size; + Vector3 boundsMinCurrentOS = boxHandle.size * -0.5f + boxHandle.center; - EditorGUI.BeginChangeCheck(); - handle.DrawHandle(); - if (EditorGUI.EndChangeCheck()) + if (editMode == k_EditShapePreservingUV) { - needToRefreshDecalProjector = true; - - // Adjust decal transform if handle changed. - Undo.RecordObject(decalProjector, "Decal Projector Change"); - - decalProjector.size = handle.size; - decalProjector.offset = handle.center; - - Vector3 boundsSizeCurrentOS = handle.size; - Vector3 boundsMinCurrentOS = handle.size * -0.5f + handle.center; - - if (editMode == k_EditShapePreservingUV) - { - // Treat decal projector bounds as a crop tool, rather than a scale tool. - // Compute a new uv scale and bias terms to pin decal projection pixels in world space, irrespective of projector bounds. - Vector2 uvScale = decalProjector.uvScale; - uvScale.x *= Mathf.Max(1e-5f, boundsSizeCurrentOS.x) / Mathf.Max(1e-5f, boundsSizePreviousOS.x); - uvScale.y *= Mathf.Max(1e-5f, boundsSizeCurrentOS.y) / Mathf.Max(1e-5f, boundsSizePreviousOS.y); - decalProjector.uvScale = uvScale; - - Vector2 uvBias = decalProjector.uvBias; - uvBias.x += (boundsMinCurrentOS.x - boundsMinPreviousOS.x) / Mathf.Max(1e-5f, boundsSizeCurrentOS.x) * decalProjector.uvScale.x; - uvBias.y += (boundsMinCurrentOS.y - boundsMinPreviousOS.y) / Mathf.Max(1e-5f, boundsSizeCurrentOS.y) * decalProjector.uvScale.y; - decalProjector.uvBias = uvBias; - } - - if (PrefabUtility.IsPartOfNonAssetPrefabInstance(decalProjector)) - { - PrefabUtility.RecordPrefabInstancePropertyModifications(decalProjector); - } + // Treat decal projector bounds as a crop tool, rather than a scale tool. + // Compute a new uv scale and bias terms to pin decal projection pixels in world space, irrespective of projector bounds. + Vector2 uvScale = decalProjector.uvScale; + uvScale.x *= Mathf.Max(1e-5f, boundsSizeCurrentOS.x) / Mathf.Max(1e-5f, boundsSizePreviousOS.x); + uvScale.y *= Mathf.Max(1e-5f, boundsSizeCurrentOS.y) / Mathf.Max(1e-5f, boundsSizePreviousOS.y); + decalProjector.uvScale = uvScale; + + Vector2 uvBias = decalProjector.uvBias; + uvBias.x += (boundsMinCurrentOS.x - boundsMinPreviousOS.x) / Mathf.Max(1e-5f, boundsSizeCurrentOS.x) * decalProjector.uvScale.x; + uvBias.y += (boundsMinCurrentOS.y - boundsMinPreviousOS.y) / Mathf.Max(1e-5f, boundsSizeCurrentOS.y) * decalProjector.uvScale.y; + decalProjector.uvBias = uvBias; } - // Automatically recenter our transform component if necessary. - // In order to correctly handle world-space snapping, we only perform this recentering when the user is no longer interacting with the gizmo. - if ((GUIUtility.hotControl == 0) && (decalProjector.offset != Vector3.zero)) + if (PrefabUtility.IsPartOfNonAssetPrefabInstance(decalProjector)) { - needToRefreshDecalProjector = true; - - // Both the DecalProjectorComponent, and the transform will be modified. - // The undo system will automatically group all RecordObject() calls here into a single action. - Undo.RecordObject(decalProjector.transform, "Decal Projector Change"); - - // Re-center the transform to the center of the decal projector bounds, - // while maintaining the world-space coordinates of the decal projector boundings vertices. - // Center of the decal projector is not the same of the HierarchicalBox as we want it to be on the z face as lights - decalProjector.transform.Translate(decalProjector.offset + new Vector3(0f, 0f, handle.size.z * -0.5f), Space.Self); - - decalProjector.offset = new Vector3(0f, 0f, handle.size.z * 0.5f); - if (PrefabUtility.IsPartOfNonAssetPrefabInstance(decalProjector)) - { - PrefabUtility.RecordPrefabInstancePropertyModifications(decalProjector); - } + PrefabUtility.RecordPrefabInstancePropertyModifications(decalProjector); } - if (needToRefreshDecalProjector) + // Smoothly update the decal image projected + DecalSystem.instance.UpdateCachedData(decalProjector.Handle, decalProjector.GetCachedDecalData()); + } + } + } + + void DrawPivotHandles(DecalProjector decalProjector) + { + using (new Handles.DrawingScope(fullColor, Matrix4x4.TRS(Vector3.zero, decalProjector.transform.rotation, Vector3.one))) + { + EditorGUI.BeginChangeCheck(); + Vector3 newPosition = ProjectedTransform.DrawHandles(decalProjector.transform.position, .5f * decalProjector.size.z - decalProjector.pivot.z, decalProjector.transform.rotation); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObjects(new UnityEngine.Object[] { decalProjector, decalProjector.transform }, "Decal Projector Change"); + + decalProjector.pivot += Quaternion.Inverse(decalProjector.transform.rotation) * (decalProjector.transform.position - newPosition); + decalProjector.transform.position = newPosition; + } + } + } + + void DrawUVHandles(DecalProjector decalProjector) + { + using (new Handles.DrawingScope(Matrix4x4.TRS(decalProjector.transform.position + decalProjector.transform.rotation * (decalProjector.pivot - .5f * decalProjector.size), decalProjector.transform.rotation, Vector3.one))) + { + Vector2 uvSize = new Vector2( + (decalProjector.uvScale.x > k_Limit || decalProjector.uvScale.x < -k_Limit) ? 0f : decalProjector.size.x / decalProjector.uvScale.x, + (decalProjector.uvScale.y > k_Limit || decalProjector.uvScale.y < -k_Limit) ? 0f : decalProjector.size.y / decalProjector.uvScale.y + ); + Vector2 uvCenter = uvSize * .5f - new Vector2(decalProjector.uvBias.x * uvSize.x, decalProjector.uvBias.y * uvSize.y); + + uvHandles.center = uvCenter; + uvHandles.size = uvSize; + + EditorGUI.BeginChangeCheck(); + uvHandles.DrawHandle(); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(decalProjector, "Decal Projector Change"); + + Vector2 limit = new Vector2(Mathf.Abs(decalProjector.size.x * k_LimitInv), Mathf.Abs(decalProjector.size.y * k_LimitInv)); + Vector2 uvScale = uvHandles.size; + for (int channel = 0; channel < 2; channel++) { - // Smoothly update the decal image projected - DecalSystem.instance.UpdateCachedData(decalProjector.Handle, decalProjector.GetCachedDecalData()); + if (Mathf.Abs(uvScale[channel]) > limit[channel]) + uvScale[channel] = decalProjector.size[channel] / uvScale[channel]; + else + uvScale[channel] = Mathf.Sign(decalProjector.size[channel]) * Mathf.Sign(uvScale[channel]) * k_Limit; } + decalProjector.uvScale = uvScale; + + var newUVStart = uvHandles.center - .5f * uvHandles.size; + decalProjector.uvBias = -new Vector2( + (uvHandles.size.x < k_LimitInv) && (uvHandles.size.x > -k_LimitInv) ? k_Limit * newUVStart.x / decalProjector.size.x : newUVStart.x / uvHandles.size.x, //parenthesis to force format tool + (uvHandles.size.y < k_LimitInv) && (uvHandles.size.y > -k_LimitInv) ? k_Limit * newUVStart.y / decalProjector.size.y : newUVStart.y / uvHandles.size.y //parenthesis to force format tool + ); } } + } - //[TODO: add editable pivot. Uncomment this when ready] - //else if (editMode == k_EditUV) - //{ - // //here should be handles code to manipulate the pivot without changing the UV - //} + void DrawHandles() + { + DecalProjector decalProjector = target as DecalProjector; + + if (editMode == k_EditShapePreservingUV || editMode == k_EditShapeWithoutPreservingUV) + DrawBoxTransformationHandles(decalProjector); + else if (editMode == k_EditUVAndPivot) + { + DrawPivotHandles(decalProjector); + DrawUVHandles(decalProjector); + } } [DrawGizmo(GizmoType.Selected | GizmoType.Active)] static void DrawGizmosSelected(DecalProjector decalProjector, GizmoType gizmoType) { + UpdateColorsInHandlesIfRequired(); + + const float k_DotLength = 5f; + //draw them scale independent - using (new Handles.DrawingScope(Color.white, Matrix4x4.TRS(decalProjector.transform.position, decalProjector.transform.rotation, Vector3.one))) + using (new Handles.DrawingScope(fullColor, Matrix4x4.TRS(decalProjector.transform.position, decalProjector.transform.rotation, Vector3.one))) { - handle.center = decalProjector.offset; - handle.size = decalProjector.size; - bool inEditMode = editMode == k_EditShapePreservingUV || editMode == k_EditShapeWithoutPreservingUV; - handle.DrawHull(inEditMode); - - Quaternion arrowRotation = Quaternion.LookRotation(Vector3.down, Vector3.right); - float arrowSize = decalProjector.size.z * 0.25f; - Vector3 pivot = decalProjector.offset; - Vector3 projectedPivot = pivot + decalProjector.size.z * 0.5f * Vector3.back; - Handles.ArrowHandleCap(0, projectedPivot, Quaternion.identity, arrowSize, EventType.Repaint); - - //[TODO: add editable pivot. Uncomment this when ready] - //draw pivot - //Handles.SphereHandleCap(controlID, pivot, Quaternion.identity, 0.02f, EventType.Repaint); - //Color c = Color.white; - //c.a = 0.2f; - //Handles.color = c; - //Handles.DrawLine(projectedPivot, projectedPivot + decalProjector.m_Size.x * 0.5f * Vector3.right); - //Handles.DrawLine(projectedPivot, projectedPivot + decalProjector.m_Size.y * 0.5f * Vector3.up); - //Handles.DrawLine(projectedPivot, projectedPivot + decalProjector.m_Size.z * 0.5f * Vector3.forward); + boxHandle.center = decalProjector.pivot; + boxHandle.size = decalProjector.size; + bool isVolumeEditMode = editMode == k_EditShapePreservingUV || editMode == k_EditShapeWithoutPreservingUV; + bool isPivotEditMode = editMode == k_EditUVAndPivot; + boxHandle.DrawHull(isVolumeEditMode); - //draw UV and bolder edges - using (new Handles.DrawingScope(Matrix4x4.TRS(decalProjector.transform.position - decalProjector.transform.rotation * (decalProjector.size * 0.5f + decalProjector.offset.z * Vector3.back), decalProjector.transform.rotation, Vector3.one))) + Vector3 pivot = Vector3.zero; + Vector3 projectedPivot = new Vector3(0, 0, decalProjector.pivot.z - .5f * decalProjector.size.z); + + if (isPivotEditMode) { - if (inEditMode) - { - Vector2 size = new Vector2( - (decalProjector.uvScale.x > 100000 || decalProjector.uvScale.x < -100000 ? 0f : 1f / decalProjector.uvScale.x) * decalProjector.size.x, - (decalProjector.uvScale.y > 100000 || decalProjector.uvScale.y < -100000 ? 0f : 1f / decalProjector.uvScale.y) * decalProjector.size.y - ); - Vector2 start = (Vector2)projectedPivot - new Vector2(decalProjector.uvBias.x * size.x, decalProjector.uvBias.y * size.y); - Handles.DrawDottedLines( - new Vector3[] - { - start, start + new Vector2(size.x, 0), - start + new Vector2(size.x, 0), start + size, - start + size, start + new Vector2(0, size.y), - start + new Vector2(0, size.y), start - }, - 5f); - } + Handles.DrawDottedLines(new[] { projectedPivot, pivot }, k_DotLength); + } + else + { + float arrowSize = decalProjector.size.z * 0.25f; + Handles.ArrowHandleCap(0, projectedPivot, Quaternion.identity, arrowSize, EventType.Repaint); + } - Vector2 halfSize = decalProjector.size * .5f; - Vector2 halfSize2 = new Vector2(halfSize.x, -halfSize.y); - Vector2 center = (Vector2)projectedPivot + halfSize; - Handles.DrawLine(center - halfSize, center - halfSize2, 3f); - Handles.DrawLine(center - halfSize2, center + halfSize, 3f); - Handles.DrawLine(center + halfSize, center + halfSize2, 3f); - Handles.DrawLine(center + halfSize2, center - halfSize, 3f); + //draw UV and bolder edges + using (new Handles.DrawingScope(Matrix4x4.TRS(decalProjector.transform.position + decalProjector.transform.rotation * new Vector3(decalProjector.pivot.x, decalProjector.pivot.y, decalProjector.pivot.z - .5f * decalProjector.size.z), decalProjector.transform.rotation, Vector3.one))) + { + Vector2 UVSize = new Vector2( + (decalProjector.uvScale.x > k_Limit || decalProjector.uvScale.x < -k_Limit) ? 0f : decalProjector.size.x / decalProjector.uvScale.x, + (decalProjector.uvScale.y > k_Limit || decalProjector.uvScale.y < -k_Limit) ? 0f : decalProjector.size.y / decalProjector.uvScale.y + ); + Vector2 UVCenter = UVSize * .5f - new Vector2(decalProjector.uvBias.x * UVSize.x, decalProjector.uvBias.y * UVSize.y) - (Vector2)decalProjector.size * .5f; + + uvHandles.center = UVCenter; + uvHandles.size = UVSize; + uvHandles.DrawRect(dottedLine: true, screenSpaceSize: k_DotLength); + + uvHandles.center = default; + uvHandles.size = decalProjector.size; + uvHandles.DrawRect(dottedLine: false, thickness: 3f); } } } - Bounds GetBoundsGetter() + static Func GetBoundsGetter(DecalProjector decalProjector) { - var bounds = new Bounds(); - var decalTransform = ((Component)target).transform; - bounds.Encapsulate(decalTransform.position); - return bounds; + return () => + { + var bounds = new Bounds(); + var decalTransform = decalProjector.transform; + bounds.Encapsulate(decalTransform.position); + return bounds; + }; + } + + void UpdateSize(int axe, float newSize, float oldSize) + { + m_SizeValues[axe].floatValue = newSize; + if (oldSize > Mathf.Epsilon) + m_OffsetValues[axe].floatValue *= newSize / oldSize; } public override void OnInspectorGUI() @@ -368,10 +489,8 @@ public override void OnInspectorGUI() { EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); - DoInspectorToolbar(k_EditVolumeModes, editVolumeLabels, GetBoundsGetter, this); - - //[TODO: add editable pivot. Uncomment this when ready] - //DoInspectorToolbar(k_EditPivotModes, editPivotLabels, GetBoundsGetter, this); + DoInspectorToolbar(k_EditVolumeModes, editVolumeLabels, GetBoundsGetter(target as DecalProjector), this); + DoInspectorToolbar(k_EditUVAndPivotModes, editPivotLabels, GetBoundsGetter(target as DecalProjector), this); GUILayout.FlexibleSpace(); EditorGUILayout.EndHorizontal(); @@ -385,20 +504,22 @@ public override void OnInspectorGUI() EditorGUI.MultiFloatField(rect, k_SizeContent, k_SizeSubContent, size); if (EditorGUI.EndChangeCheck()) { - m_SizeValues[0].floatValue = Mathf.Max(0, size[0]); - m_SizeValues[1].floatValue = Mathf.Max(0, size[1]); + for (int i = 0; i < 2; ++i) + UpdateSize(i, Mathf.Max(0, size[i]), m_SizeValues[i].floatValue); } EditorGUI.EndProperty(); EditorGUI.EndProperty(); EditorGUI.BeginChangeCheck(); + float oldSizeZ = m_SizeValues[2].floatValue; EditorGUILayout.PropertyField(m_SizeValues[2], k_ProjectionDepthContent); if (EditorGUI.EndChangeCheck()) { - m_SizeValues[2].floatValue = Mathf.Max(0, m_SizeValues[2].floatValue); - m_OffsetZ.floatValue = m_SizeValues[2].floatValue * 0.5f; + UpdateSize(2, Mathf.Max(0, m_SizeValues[2].floatValue), oldSizeZ); } + EditorGUILayout.PropertyField(m_Offset, k_Offset); + EditorGUILayout.PropertyField(m_MaterialProperty, k_MaterialContent); bool decalLayerEnabled = false; @@ -503,34 +624,48 @@ public override void OnInspectorGUI() static void EnterEditModeWithoutPreservingUV(ShortcutArguments args) { //If editor is not there, then the selected GameObject does not contains a DecalProjector - if (s_Owner == null || s_Owner.Equals(null)) + DecalProjector activeDecalProjector = Selection.activeGameObject?.GetComponent(); + if (activeDecalProjector == null || activeDecalProjector.Equals(null)) return; - ChangeEditMode(k_EditShapeWithoutPreservingUV, (s_Owner as DecalProjectorEditor).GetBoundsGetter(), s_Owner); + ChangeEditMode(k_EditShapeWithoutPreservingUV, GetBoundsGetter(activeDecalProjector)(), FindEditorFromSelection()); } [Shortcut("HDRP/Decal: Handle changing size cropping UV", typeof(SceneView), KeyCode.Keypad2, ShortcutModifiers.Action)] static void EnterEditModePreservingUV(ShortcutArguments args) { //If editor is not there, then the selected GameObject does not contains a DecalProjector - if (s_Owner == null || s_Owner.Equals(null)) + DecalProjector activeDecalProjector = Selection.activeGameObject?.GetComponent(); + if (activeDecalProjector == null || activeDecalProjector.Equals(null)) return; - ChangeEditMode(k_EditShapePreservingUV, (s_Owner as DecalProjectorEditor).GetBoundsGetter(), s_Owner); + ChangeEditMode(k_EditShapePreservingUV, GetBoundsGetter(activeDecalProjector)(), FindEditorFromSelection()); } - //[TODO: add editable pivot. Uncomment this when ready] - //[Shortcut("HDRP/Decal: Handle changing pivot position while preserving UV position", typeof(SceneView), KeyCode.Keypad3, ShortcutModifiers.Action)] - //static void EnterEditModePivotPreservingUV(ShortcutArguments args) => - // ChangeEditMode(k_EditUV, (s_Owner as DecalProjectorComponentEditor).GetBoundsGetter(), s_Owner); + [Shortcut("HDRP/Decal: Handle changing pivot position and UVs", typeof(SceneView), KeyCode.Keypad3, ShortcutModifiers.Action)] + static void EnterEditModePivotPreservingUV(ShortcutArguments args) + { + //If editor is not there, then the selected GameObject does not contains a DecalProjector + DecalProjector activeDecalProjector = Selection.activeGameObject?.GetComponent(); + if (activeDecalProjector == null || activeDecalProjector.Equals(null)) + return; + + ChangeEditMode(k_EditUVAndPivot, GetBoundsGetter(activeDecalProjector)(), FindEditorFromSelection()); + } [Shortcut("HDRP/Decal: Handle swap between cropping and stretching UV", typeof(SceneView), KeyCode.W, ShortcutModifiers.Action)] static void SwappingEditUVMode(ShortcutArguments args) { + //If editor is not there, then the selected GameObject does not contains a DecalProjector + DecalProjector activeDecalProjector = Selection.activeGameObject?.GetComponent(); + if (activeDecalProjector == null || activeDecalProjector.Equals(null)) + return; + SceneViewEditMode targetMode = SceneViewEditMode.None; switch (editMode) { case k_EditShapePreservingUV: + case k_EditUVAndPivot: targetMode = k_EditShapeWithoutPreservingUV; break; case k_EditShapeWithoutPreservingUV: @@ -538,10 +673,18 @@ static void SwappingEditUVMode(ShortcutArguments args) break; } if (targetMode != SceneViewEditMode.None) - ChangeEditMode(targetMode, (s_Owner as DecalProjectorEditor).GetBoundsGetter(), s_Owner); + ChangeEditMode(targetMode, GetBoundsGetter(activeDecalProjector)(), FindEditorFromSelection()); } [Shortcut("HDRP/Decal: Stop Editing", typeof(SceneView), KeyCode.Keypad0, ShortcutModifiers.Action)] - static void ExitEditMode(ShortcutArguments args) => QuitEditMode(); + static void ExitEditMode(ShortcutArguments args) + { + //If editor is not there, then the selected GameObject does not contains a DecalProjector + DecalProjector activeDecalProjector = Selection.activeGameObject?.GetComponent(); + if (activeDecalProjector == null || activeDecalProjector.Equals(null)) + return; + + QuitEditMode(); + } } } diff --git a/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DisplacableRectHandles.cs b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DisplacableRectHandles.cs new file mode 100644 index 00000000000..6579041a564 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DisplacableRectHandles.cs @@ -0,0 +1,296 @@ +using System; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace UnityEditor.Rendering.HighDefinition +{ + class DisplacableRectHandles + { + const float k_HandleSizeCoef = 0.05f; + + enum NamedEdge { Right, Top, Left, Bottom, None } + + int[] m_ControlIDs = new int[4] { 0, 0, 0, 0 }; + Color m_MonochromeHandleColor; + Color m_WireframeColor; + Color m_WireframeColorBehind; + + /// The position of the center of the box in Handle.matrix space. On plane z=0. + public Vector2 center { get; set; } + + /// The size of the box in Handle.matrix space. On plane z=0. + public Vector2 size { get; set; } + + //Note: Handles.Slider not allow to use a specific ControlID. + //Thus Slider1D is used (with reflection) + static Type k_Slider1D = Type.GetType("UnityEditorInternal.Slider1D, UnityEditor"); + static MethodInfo k_Slider1D_Do = k_Slider1D + .GetMethod( + "Do", + BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, + null, + CallingConventions.Any, + new[] { typeof(int), typeof(Vector3), typeof(Vector3), typeof(float), typeof(Handles.CapFunction), typeof(float) }, + null); + static void Slider1D(int controlID, ref Vector3 handlePosition, Vector3 handleOrientation, float snapScale) + { + handlePosition = (Vector3)k_Slider1D_Do.Invoke(null, new object[] + { + controlID, + handlePosition, + handleOrientation, + HandleUtility.GetHandleSize(handlePosition) * k_HandleSizeCoef, + new Handles.CapFunction(Handles.DotHandleCap), + snapScale + }); + } + + /// The baseColor used to draw the rect. + public Color baseColor + { + get { return m_MonochromeHandleColor; } + set + { + m_MonochromeHandleColor = GizmoUtility.GetHandleColor(value); + m_WireframeColor = GizmoUtility.GetWireframeColor(value); + m_WireframeColorBehind = GizmoUtility.GetWireframeColorBehindObjects(value); + } + } + + public DisplacableRectHandles(Color baseColor) + { + this.baseColor = baseColor; + } + + /// Draw the rect. + public void DrawRect(bool dottedLine = false, float thickness = .0f, float screenSpaceSize = 5f) + { + Vector2 start = center - size * .5f; + Vector3[] positions = new Vector3[] + { + start, + start + size * Vector2.right, + start + size, + start + size * Vector2.up + }; + Vector3[] edges = new Vector3[] + { + positions[0], positions[1], + positions[1], positions[2], + positions[2], positions[3], + positions[3], positions[0], + }; + + void Draw() + { + if (dottedLine) + Handles.DrawDottedLines(edges, screenSpaceSize); + else + { + Handles.DrawLine(positions[0], positions[1], thickness); + Handles.DrawLine(positions[1], positions[2], thickness); + Handles.DrawLine(positions[2], positions[3], thickness); + Handles.DrawLine(positions[3], positions[0], thickness); + } + } + + Color previousColor = Handles.color; + Handles.color = m_WireframeColor; + Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual; + Draw(); + Handles.color = m_WireframeColorBehind; + Handles.zTest = UnityEngine.Rendering.CompareFunction.Greater; + Draw(); + Handles.zTest = UnityEngine.Rendering.CompareFunction.Always; + Handles.color = previousColor; + } + + NamedEdge DrawSliders(ref Vector3 leftPosition, ref Vector3 rightPosition, ref Vector3 topPosition, ref Vector3 bottomPosition) + { + NamedEdge theChangedEdge = NamedEdge.None; + + using (new Handles.DrawingScope(m_MonochromeHandleColor)) + { + EditorGUI.BeginChangeCheck(); + Slider1D(m_ControlIDs[(int)NamedEdge.Left], ref leftPosition, Vector3.left, EditorSnapSettings.scale); + if (EditorGUI.EndChangeCheck()) + theChangedEdge = NamedEdge.Left; + + EditorGUI.BeginChangeCheck(); + Slider1D(m_ControlIDs[(int)NamedEdge.Right], ref rightPosition, Vector3.right, EditorSnapSettings.scale); + if (EditorGUI.EndChangeCheck()) + theChangedEdge = NamedEdge.Right; + + EditorGUI.BeginChangeCheck(); + Slider1D(m_ControlIDs[(int)NamedEdge.Top], ref topPosition, Vector3.up, EditorSnapSettings.scale); + if (EditorGUI.EndChangeCheck()) + theChangedEdge = NamedEdge.Top; + + EditorGUI.BeginChangeCheck(); + Slider1D(m_ControlIDs[(int)NamedEdge.Bottom], ref bottomPosition, Vector3.down, EditorSnapSettings.scale); + if (EditorGUI.EndChangeCheck()) + theChangedEdge = NamedEdge.Bottom; + } + + return theChangedEdge; + } + + void EnsureEdgeFacesOutsideForHomothety(NamedEdge theChangedEdge, ref Vector3 leftPosition, ref Vector3 rightPosition, ref Vector3 topPosition, ref Vector3 bottomPosition) + { + switch (theChangedEdge) + { + case NamedEdge.Left: + if (rightPosition.x < leftPosition.x) + leftPosition.x = rightPosition.x; + if (topPosition.y < bottomPosition.y) + topPosition.y = bottomPosition.y = center.y; + break; + case NamedEdge.Right: + if (rightPosition.x < leftPosition.x) + rightPosition.x = leftPosition.x; + if (topPosition.y < bottomPosition.y) + topPosition.y = bottomPosition.y = center.y; + break; + case NamedEdge.Top: + if (topPosition.y < bottomPosition.y) + topPosition.y = bottomPosition.y; + if (rightPosition.x < leftPosition.x) + rightPosition.x = leftPosition.x = center.x; + break; + case NamedEdge.Bottom: + if (topPosition.y < bottomPosition.y) + bottomPosition.y = topPosition.y; + if (rightPosition.x < leftPosition.x) + rightPosition.x = leftPosition.x = center.x; + break; + } + } + + void EnsureEdgeFacesOutsideForSymetry(NamedEdge theChangedEdge, ref Vector3 leftPosition, ref Vector3 rightPosition, ref Vector3 topPosition, ref Vector3 bottomPosition) + { + switch (theChangedEdge) + { + case NamedEdge.Left: + case NamedEdge.Right: + if (rightPosition.x < leftPosition.x) + rightPosition.x = leftPosition.x = center.x; + break; + case NamedEdge.Top: + case NamedEdge.Bottom: + if (topPosition.y < bottomPosition.y) + topPosition.y = bottomPosition.y = center.y; + break; + } + } + + void EnsureEdgeFacesOutsideForOtherTransformation(ref Vector2 max, ref Vector2 min) + { + for (int axis = 0; axis < 2; ++axis) + { + if (min[axis] > max[axis]) + { + // Control IDs in m_ControlIDs[0-1] are for positive axes + if (GUIUtility.hotControl == m_ControlIDs[axis]) + max[axis] = min[axis]; + else + min[axis] = max[axis]; + } + } + } + + /// Draw the manipulable handles + public void DrawHandle() + { + Event evt = Event.current; + bool useHomothety = evt.shift; + bool useSymetry = evt.alt || evt.command; + // Note: snapping is handled natively on ctrl for each Slider1D + + for (int i = 0, count = m_ControlIDs.Length; i < count; ++i) + m_ControlIDs[i] = GUIUtility.GetControlID("DisplacableRectHandles".GetHashCode() + i, FocusType.Passive); + + Vector3 leftPosition = center + size.x * .5f * Vector2.left; + Vector3 rightPosition = center + size.x * .5f * Vector2.right; + Vector3 topPosition = center + size.y * .5f * Vector2.up; + Vector3 bottomPosition = center + size.y * .5f * Vector2.down; + + var theChangedEdge = NamedEdge.None; + + EditorGUI.BeginChangeCheck(); + theChangedEdge = DrawSliders(ref leftPosition, ref rightPosition, ref topPosition, ref bottomPosition); + if (EditorGUI.EndChangeCheck()) + { + float delta = 0f; + switch (theChangedEdge) + { + case NamedEdge.Left: delta = ((Vector2)leftPosition - center - size.x * .5f * Vector2.left).x; break; + case NamedEdge.Right: delta = -((Vector2)rightPosition - center - size.x * .5f * Vector2.right).x; break; + case NamedEdge.Top: delta = -((Vector2)topPosition - center - size.y * .5f * Vector2.up).y; break; + case NamedEdge.Bottom: delta = ((Vector2)bottomPosition - center - size.y * .5f * Vector2.down).y; break; + } + + if (useHomothety && useSymetry) + { + var tempSize = size - Vector2.one * delta; + + //ensure that the rect edges are still facing outside + for (int axis = 0; axis < 3; ++axis) + { + if (tempSize[axis] < 0) + { + delta += tempSize[axis]; + tempSize = size - Vector2.one * delta; + } + } + + size = tempSize; + } + else + { + if (useSymetry) + { + switch (theChangedEdge) + { + case NamedEdge.Left: rightPosition.x -= delta; break; + case NamedEdge.Right: leftPosition.x += delta; break; + case NamedEdge.Top: bottomPosition.y += delta; break; + case NamedEdge.Bottom: topPosition.y -= delta; break; + } + + EnsureEdgeFacesOutsideForSymetry(theChangedEdge, ref leftPosition, ref rightPosition, ref topPosition, ref bottomPosition); + } + + if (useHomothety) + { + float halfDelta = delta * 0.5f; + switch (theChangedEdge) + { + case NamedEdge.Left: + case NamedEdge.Right: + bottomPosition.y += halfDelta; + topPosition.y -= halfDelta; + break; + case NamedEdge.Top: + case NamedEdge.Bottom: + rightPosition.x -= halfDelta; + leftPosition.x += halfDelta; + break; + } + + EnsureEdgeFacesOutsideForHomothety(theChangedEdge, ref leftPosition, ref rightPosition, ref topPosition, ref bottomPosition); + } + + var max = new Vector2(rightPosition.x, topPosition.y); + var min = new Vector2(leftPosition.x, bottomPosition.y); + + if (!useSymetry && !useHomothety) + EnsureEdgeFacesOutsideForOtherTransformation(ref max, ref min); + + center = (max + min) * .5f; + size = max - min; + } + } + } + } +} diff --git a/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DisplacableRectHandles.cs.meta b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DisplacableRectHandles.cs.meta new file mode 100644 index 00000000000..335e9f208c5 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/DisplacableRectHandles.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec7aa4f206331154bacd45514acb99b1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/Editor/Material/Decal/ProjectedTransform.cs b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/ProjectedTransform.cs new file mode 100644 index 00000000000..5b1dcc86277 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/ProjectedTransform.cs @@ -0,0 +1,246 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; +using UnityEditor; + +namespace UnityEngine.Rendering.HighDefinition +{ + class ProjectedTransform + { + struct PositionHandleIds + { + static int s_xAxisMoveHandleHash = "xAxisDecalPivot".GetHashCode(); + static int s_yAxisMoveHandleHash = "yAxisDecalPivot".GetHashCode(); + static int s_zAxisMoveHandleHash = "zAxisDecalPivot".GetHashCode(); + static int s_xyAxisMoveHandleHash = "xyAxisDecalPivot".GetHashCode(); + + public static PositionHandleIds @default + { + get + { + return new PositionHandleIds( + GUIUtility.GetControlID(s_xAxisMoveHandleHash, FocusType.Passive), + GUIUtility.GetControlID(s_yAxisMoveHandleHash, FocusType.Passive), + GUIUtility.GetControlID(s_zAxisMoveHandleHash, FocusType.Passive), + GUIUtility.GetControlID(s_xyAxisMoveHandleHash, FocusType.Passive) + ); + } + } + + public readonly int x, y, z, xy; + + public int this[int index] + { + get + { + switch (index) + { + case 0: return x; + case 1: return y; + case 2: return z; + case 3: return xy; + } + return -1; + } + } + + public bool Has(int id) + { + return x == id + || y == id + || z == id + || xy == id; + } + + public PositionHandleIds(int x, int y, int z, int xy) + { + this.x = x; + this.y = y; + this.z = z; + this.xy = xy; + } + + public override int GetHashCode() + { + return x ^ y ^ z ^ xy; + } + + public override bool Equals(object obj) + { + if (!(obj is PositionHandleIds o)) + return false; + + return o.x == x && o.y == y && o.z == z && o.xy == xy; + } + } + + struct PositionHandleParam + { + public static PositionHandleParam defaultHandleXY = new PositionHandleParam( + Handle.X | Handle.Y | Handle.XY, + Vector3.zero, Vector3.one, Vector3.zero, Vector3.one * .25f, + Orientation.Signed, Orientation.Camera); + + public static PositionHandleParam defaultHandleZ = new PositionHandleParam( + Handle.Z, + Vector3.zero, Vector3.one, Vector3.zero, Vector3.one * .25f, + Orientation.Signed, Orientation.Camera); + + [Flags] + public enum Handle + { + None = 0, + X = 1 << 0, + Y = 1 << 1, + Z = 1 << 2, + XY = 1 << 3, + All = ~None + } + + public enum Orientation + { + Signed, + Camera + } + + public readonly Vector3 axisOffset; + public readonly Vector3 axisSize; + public readonly Vector3 planeOffset; + public readonly Vector3 planeSize; + public readonly Handle handles; + public readonly Orientation axesOrientation; + public readonly Orientation planeOrientation; + + public bool ShouldShow(int axis) + { + return (handles & (Handle)(1 << axis)) != 0; + } + + public bool ShouldShow(Handle handle) + { + return (handles & handle) != 0; + } + + public PositionHandleParam( + Handle handles, + Vector3 axisOffset, + Vector3 axisSize, + Vector3 planeOffset, + Vector3 planeSize, + Orientation axesOrientation, + Orientation planeOrientation) + { + this.axisOffset = axisOffset; + this.axisSize = axisSize; + this.planeOffset = planeOffset; + this.planeSize = planeSize; + this.handles = handles; + this.axesOrientation = axesOrientation; + this.planeOrientation = planeOrientation; + } + } + + static PositionHandleParam paramXY = PositionHandleParam.defaultHandleXY; + static PositionHandleParam paramZ = PositionHandleParam.defaultHandleZ; + static PositionHandleIds ids = PositionHandleIds.@default; + + static int[] s_DoPositionHandle_Internal_NextIndex = { 1, 2, 0 }; + static int[] s_DoPositionHandle_Internal_PrevIndex = { 2, 0, 1 }; + static Vector3[] verts = { Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero }; + + static Func s_IsGridSnappingActive; + + + static ProjectedTransform() + { + //We need to know if grid snaping is active or not in Editor. Sadly this is internal so we must grab it by reflection. + Type gridSnappingType = typeof(Handles).Assembly.GetType("UnityEditor.GridSnapping"); + PropertyInfo activePropertyInfo = gridSnappingType.GetProperty("active", BindingFlags.Public | BindingFlags.Static); + MethodCallExpression activePropertyGetCall = Expression.Call(null, activePropertyInfo.GetGetMethod()); + var activeGetLambda = Expression.Lambda>(activePropertyGetCall); + s_IsGridSnappingActive = activeGetLambda.Compile(); + } + + static bool IsHovering(int controlID, Event evt) + { + return controlID == HandleUtility.nearestControl && GUIUtility.hotControl == 0 && !Tools.viewToolActive; + } + + public static Vector3 DrawHandles(Vector3 position, float zProjectionDistance, Quaternion rotation) + { + var isHot = ids.Has(GUIUtility.hotControl); + var planeSize = isHot ? paramXY.planeSize + paramXY.planeOffset : paramXY.planeSize; + var planarSize = Mathf.Max(planeSize[0], planeSize[s_DoPositionHandle_Internal_NextIndex[0]]); + Vector3 sliderRotatedWorldPos = Quaternion.Inverse(rotation) * position; + var size1D = HandleUtility.GetHandleSize(sliderRotatedWorldPos); + var size2D = HandleUtility.GetHandleSize(sliderRotatedWorldPos - new Vector3(0, 0, zProjectionDistance)) * planarSize * .5f; + Vector3 depthSlider = sliderRotatedWorldPos; + + EditorGUI.BeginChangeCheck(); + { + // dot offset = transform position seen as a sphere + EditorGUI.BeginChangeCheck(); + depthSlider = Handles.Slider(depthSlider, Vector3.forward, size1D * .1f, Handles.SphereHandleCap, -1); + if (EditorGUI.EndChangeCheck()) + sliderRotatedWorldPos.z = depthSlider.z; + + // 2D slider: square xy-axis + Vector3 sliderFaceProjected = sliderRotatedWorldPos - new Vector3(0, 0, zProjectionDistance); + sliderFaceProjected.x += size2D; + sliderFaceProjected.y += size2D; + using (new Handles.DrawingScope(Handles.zAxisColor)) + { + verts[0] = sliderFaceProjected + (Vector3.right + Vector3.up) * size2D; + verts[1] = sliderFaceProjected + (-Vector3.right + Vector3.up) * size2D; + verts[2] = sliderFaceProjected + (-Vector3.right - Vector3.up) * size2D; + verts[3] = sliderFaceProjected + (Vector3.right - Vector3.up) * size2D; + float faceOpacity = 0.8f; + if (GUIUtility.hotControl == ids.xy) + Handles.color = Handles.selectedColor; + else if (IsHovering(ids.xy, Event.current)) + faceOpacity = 0.4f; + else + faceOpacity = 0.1f; + Color faceColor = new Color(Handles.zAxisColor.r, Handles.zAxisColor.g, Handles.zAxisColor.b, Handles.zAxisColor.a * faceOpacity); + Handles.DrawSolidRectangleWithOutline(verts, faceColor, Color.clear); + EditorGUI.BeginChangeCheck(); + sliderFaceProjected = Handles.Slider2D(ids.xy, sliderFaceProjected, Vector3.forward, Vector3.right, Vector3.up, size2D, Handles.RectangleHandleCap, s_IsGridSnappingActive() ? Vector2.zero : new Vector2(EditorSnapSettings.move[0], EditorSnapSettings.move[1]), false); + if (EditorGUI.EndChangeCheck()) + { + sliderRotatedWorldPos.x = sliderFaceProjected.x; + sliderRotatedWorldPos.y = sliderFaceProjected.y; + } + } + sliderFaceProjected.x -= size2D; + sliderFaceProjected.y -= size2D; + + // 2D slider: x-axis + EditorGUI.BeginChangeCheck(); + using (new Handles.DrawingScope(Handles.xAxisColor)) + sliderFaceProjected = Handles.Slider(sliderFaceProjected, Vector3.right); + if (EditorGUI.EndChangeCheck()) + sliderRotatedWorldPos.x = sliderFaceProjected.x; + + // 2D slider: y-axis + EditorGUI.BeginChangeCheck(); + using (new Handles.DrawingScope(Handles.yAxisColor)) + sliderFaceProjected = Handles.Slider(sliderFaceProjected, Vector3.up); + if (EditorGUI.EndChangeCheck()) + sliderRotatedWorldPos.y = sliderFaceProjected.y; + + // depth: z-axis + EditorGUI.BeginChangeCheck(); + using (new Handles.DrawingScope(Handles.zAxisColor)) + depthSlider = Handles.Slider(depthSlider, Vector3.forward); + if (EditorGUI.EndChangeCheck()) + sliderRotatedWorldPos.z = depthSlider.z; + } + if (EditorGUI.EndChangeCheck()) + { + position = rotation * sliderRotatedWorldPos; + } + + return position; + } + } +} diff --git a/com.unity.render-pipelines.high-definition/Editor/Material/Decal/ProjectedTransform.cs.meta b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/ProjectedTransform.cs.meta new file mode 100644 index 00000000000..c25147271e6 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Editor/Material/Decal/ProjectedTransform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1955ced6df3d87641b9abc7588ca00da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/Runtime/Material/Decal/DecalProjector.cs b/com.unity.render-pipelines.high-definition/Runtime/Material/Decal/DecalProjector.cs index 27e9764c59b..ee36a76a410 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Material/Decal/DecalProjector.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Material/Decal/DecalProjector.cs @@ -182,10 +182,10 @@ public DecalLayerEnum decalLayerMask [SerializeField] private Vector3 m_Offset = new Vector3(0, 0, 0.5f); /// - /// Change the offset position. - /// Do not expose: Could be changed by the inspector when manipulating the gizmo. + /// Change the pivot position. + /// It is an offset between the center of the projection and the transform position. /// - internal Vector3 offset + public Vector3 pivot { get { @@ -202,13 +202,11 @@ internal Vector3 offset Vector3 m_Size = new Vector3(1, 1, 1); /// /// The size of the projection volume. + /// See also to rescale relatively to the pivot position. /// public Vector3 size { - get - { - return m_Size; - } + get => m_Size; set { m_Size = value; @@ -216,6 +214,18 @@ public Vector3 size } } + /// + /// Update the pivot to resize centered on the pivot position. + /// + /// The new size. + public void ResizeAroundPivot(Vector3 newSize) + { + for (int axis = 0; axis < 3; ++axis) + if (m_Size[axis] > Mathf.Epsilon) + m_Offset[axis] *= newSize[axis] / m_Size[axis]; + size = newSize; + } + [SerializeField] [Range(0, 1)] private float m_FadeFactor = 1.0f;