#if UNITY_2021_2_OR_NEWER using System; using System.Collections.Generic; using Cinemachine.Utility; using UnityEditor; using UnityEditor.EditorTools; using UnityEngine; namespace Cinemachine.Editor { /// /// Static class that manages Cinemachine Tools. It knows which tool is active, /// and ensures that exclusive tools are not active at the same time. /// The tools and editors requiring tools register/unregister themselves here. /// static class CinemachineSceneToolUtility { #if UNITY_2022_1_OR_NEWER /// /// Checks whether tool is the currently active exclusive tool. /// /// Tool to check. /// True, when the tool is the active exclusive tool. False, otherwise. public static bool IsToolActive(Type tool) { return s_ActiveExclusiveTool == tool; } static Type s_ActiveExclusiveTool; /// /// Register your Type from the editor script's OnEnable function. /// This way CinemachineTools will know which tools to display. /// /// Tool to register public static void RegisterTool(Type tool) { if (s_RequiredTools.ContainsKey(tool)) { s_RequiredTools[tool]++; } else { s_RequiredTools.Add(tool, 1); } s_TriggerToolBarRefresh = true; } /// /// Unregister your Type from the editor script's OnDisable function. /// This way CinemachineTools will know which tools to display. /// /// Tool to register public static void UnregisterTool(Type tool) { if (s_RequiredTools.ContainsKey(tool)) { s_RequiredTools[tool]--; if (s_RequiredTools[tool] <= 0) { s_RequiredTools.Remove(tool); } } s_TriggerToolBarRefresh = true; } internal static bool IsToolRequired(Type tool) { return s_RequiredTools.ContainsKey(tool); } static Dictionary s_RequiredTools; internal static void SetTool(bool active, Type tool) { if (active) { s_ActiveExclusiveTool = tool; } else { s_ActiveExclusiveTool = s_ActiveExclusiveTool == tool ? null : s_ActiveExclusiveTool; } s_TriggerToolBarRefresh = true; } static CinemachineSceneToolUtility() { s_RequiredTools = new Dictionary(); EditorApplication.update += RefreshToolbar; } static bool s_TriggerToolBarRefresh; static void RefreshToolbar() { if (s_TriggerToolBarRefresh) { #if UNITY_2022_2_OR_NEWER ToolManager.RefreshAvailableTools(); #else // The following is a hack to refresh the toolbars foreach (var scene in SceneView.sceneViews) { if (((SceneView)scene).TryGetOverlay("unity-transform-toolbar", out var tools)) { if (tools.displayed) { tools.displayed = false; tools.displayed = true; break; } } } #endif s_TriggerToolBarRefresh = false; } } #else /// /// Checks whether tool is the currently active exclusive tool. /// /// Tool to check. /// True, when the tool is the active exclusive tool. False, otherwise. public static bool IsToolActive(Type tool) { return s_ActiveExclusiveTool == tool; } static Type s_ActiveExclusiveTool; /// /// Register your Type from the editor script's OnEnable function. /// This way CinemachineTools will know which tools to display. /// /// Tool to register public static void RegisterTool(Type tool) { if (s_RequiredTools.ContainsKey(tool)) { s_RequiredTools[tool]++; } else { s_RequiredTools.Add(tool, 1); } } /// /// Unregister your Type from the editor script's OnDisable function. /// This way CinemachineTools will know which tools to display. /// /// Tool to register public static void UnregisterTool(Type tool) { if (s_RequiredTools.ContainsKey(tool)) { s_RequiredTools[tool]--; if (s_RequiredTools[tool] <= 0) { s_RequiredTools.Remove(tool); } } } static Dictionary s_RequiredTools; public delegate void ToolHandler(bool v); struct CinemachineSceneToolDelegates { public ToolHandler ToggleSetter; public ToolHandler IsDisplayedSetter; } static Dictionary s_ExclusiveTools; // tools that can't be active at the same time static Dictionary s_Tools; // tools without restrictions /// /// Use for registering tool handlers for tools that are exclusive with each other, /// meaning they cannot be active at the same time. /// /// The tool to register. /// The tool's toggle value setter. /// The tool's isDisplayed setter. internal static void RegisterExclusiveToolHandlers(Type tool, ToolHandler toggleSetter, ToolHandler isDisplayedSetter) { RegisterToolHandlers(ref s_ExclusiveTools, tool, toggleSetter, isDisplayedSetter); } /// /// Use for registering tool handlers for tools that can be active anytime /// without taking other tools into consideration. /// /// The tool to register. /// The tool's toggle value setter. /// The tool's isDisplayed setter. internal static void RegisterToolHandlers(Type tool, ToolHandler toggleSetter, ToolHandler isDisplayedSetter) { RegisterToolHandlers(ref s_Tools, tool, toggleSetter, isDisplayedSetter); } static void RegisterToolHandlers(ref Dictionary tools, Type tool, ToolHandler toggleSetter, ToolHandler isDisplayedSetter) { if (tools.ContainsKey(tool)) { tools.Remove(tool); } tools.Add(tool, new CinemachineSceneToolDelegates { ToggleSetter = toggleSetter, IsDisplayedSetter = isDisplayedSetter, }); } internal delegate bool ToolbarHandler(); static ToolbarHandler s_ToolBarIsDisplayed; internal static void RegisterToolbarIsDisplayedHandler(ToolbarHandler handler) { s_ToolBarIsDisplayed = handler; } static bool s_ToolbarTurnOffByMe; internal delegate bool ToolbarTurnOnOffHandler(bool on); static ToolbarTurnOnOffHandler s_ToolBarDisplay; internal static void RegisterToolbarDisplayHandler(ToolbarTurnOnOffHandler handler) { s_ToolBarDisplay = handler; } internal static void SetTool(bool active, Type tool) { if (active) { s_ActiveExclusiveTool = tool; EnsureCinemachineToolsAreExclusiveWithUnityTools(); } else { s_ActiveExclusiveTool = s_ActiveExclusiveTool == tool ? null : s_ActiveExclusiveTool; } } static void EnsureCinemachineToolsAreExclusiveWithUnityTools() { foreach (var toolHandle in s_ExclusiveTools) { toolHandle.Value.ToggleSetter(toolHandle.Key == s_ActiveExclusiveTool); } if (s_ActiveExclusiveTool != null) { Tools.current = Tool.None; // Cinemachine tools are exclusive with unity tools } } static CinemachineSceneToolUtility() { s_ExclusiveTools = new Dictionary(); s_Tools = new Dictionary(); s_RequiredTools = new Dictionary(); EditorApplication.update += () => { if (s_RequiredTools.Count <= 0) { s_ToolbarTurnOffByMe |= s_ToolBarDisplay(false); } else if (s_ToolbarTurnOffByMe) { s_ToolBarDisplay(true); s_ToolbarTurnOffByMe = false; } var cmToolbarIsHidden = !s_ToolBarIsDisplayed(); // if a unity tool is selected or cmToolbar is hidden, unselect our tools. if (s_ActiveExclusiveTool != null && (Tools.current != Tool.None || cmToolbarIsHidden)) { SetTool(true, null); } if (!cmToolbarIsHidden) { // only display cm tools that are relevant for the current selection foreach (var toolHandle in s_ExclusiveTools) { toolHandle.Value.IsDisplayedSetter(s_RequiredTools.ContainsKey(toolHandle.Key)); } foreach (var toolHandle in s_Tools) { toolHandle.Value.IsDisplayedSetter(s_RequiredTools.ContainsKey(toolHandle.Key)); } } }; } #endif } static class CinemachineSceneToolHelpers { public const float LineThickness = 2f; public static readonly Color HelperLineDefaultColor = new Color(255, 255, 255, 25); const float k_DottedLineSpacing = 4f; static GUIStyle s_LabelStyle = new GUIStyle { normal = { background = AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility. CinemachineRealativeInstallPath + "/Editor/EditorResources/SceneToolsLabelBackground.png"), textColor = Handles.selectedColor, }, fontStyle = FontStyle.Bold, padding = new RectOffset(5, 0, 5, 0) }; public static float SliderHandleDelta(Vector3 newPos, Vector3 oldPos, Vector3 forward) { var delta = newPos - oldPos; return Mathf.Sign(Vector3.Dot(delta, forward)) * delta.magnitude; } /// /// Calculate delta and discard imprecision. /// public static Vector3 PositionHandleDelta(Quaternion rot, Vector3 newPos, Vector3 oldPos) { var delta = Quaternion.Inverse(rot) * (newPos - oldPos); delta = new Vector3( Mathf.Abs(delta.x) < UnityVectorExtensions.Epsilon ? 0 : delta.x, Mathf.Abs(delta.y) < UnityVectorExtensions.Epsilon ? 0 : delta.y, Mathf.Abs(delta.z) < UnityVectorExtensions.Epsilon ? 0 : delta.z); return delta; } public static void DrawLabel(Vector3 position, string text) { var labelOffset = HandleUtility.GetHandleSize(position) / 5f; Handles.Label(position + new Vector3(0, -labelOffset, 0), text, s_LabelStyle); } public static float CubeHandleCapSize(Vector3 position) => HandleUtility.GetHandleSize(position) / 10f; static int s_ScaleSliderHash = "ScaleSliderHash".GetHashCode(); static float s_FOVAfterLastToolModification; public static void FovToolHandle(CinemachineVirtualCameraBase vcam, SerializedProperty lensProperty, in LensSettings lens, bool isLensHorizontal) { var orthographic = lens.Orthographic; if (GUIUtility.hotControl == 0) { s_FOVAfterLastToolModification = orthographic ? lens.OrthographicSize : lens.FieldOfView; } var originalColor = Handles.color; Handles.color = Handles.preselectionColor; var camPos = vcam.State.FinalPosition; var camRot = vcam.State.FinalOrientation; var camForward = camRot * Vector3.forward; EditorGUI.BeginChangeCheck(); #if UNITY_2022_2_OR_NEWER var fovHandleId = GUIUtility.GetControlID(s_ScaleSliderHash, FocusType.Passive); var newFov = Handles.ScaleSlider(fovHandleId, s_FOVAfterLastToolModification, camPos, camForward, camRot, HandleUtility.GetHandleSize(camPos), 0.1f); #else var fovHandleId = GUIUtility.GetControlID(s_ScaleSliderHash, FocusType.Passive) + 1; var newFov = Handles.ScaleSlider( s_FOVAfterLastToolModification, camPos, camForward, camRot, HandleUtility.GetHandleSize(camPos), 0.1f); #endif if (EditorGUI.EndChangeCheck()) { if (orthographic) { lensProperty.FindPropertyRelative("OrthographicSize").floatValue += (s_FOVAfterLastToolModification - newFov); } else { lensProperty.FindPropertyRelative("FieldOfView").floatValue += (s_FOVAfterLastToolModification - newFov); lensProperty.FindPropertyRelative("FieldOfView").floatValue = Mathf.Clamp(lensProperty.FindPropertyRelative("FieldOfView").floatValue, 1f, 179f); } lensProperty.serializedObject.ApplyModifiedProperties(); } s_FOVAfterLastToolModification = newFov; var fovHandleDraggedOrHovered = GUIUtility.hotControl == fovHandleId || HandleUtility.nearestControl == fovHandleId; if (fovHandleDraggedOrHovered) { var labelPos = camPos + camForward * HandleUtility.GetHandleSize(camPos); if (lens.IsPhysicalCamera) { DrawLabel(labelPos, "Focal Length (" + Camera.FieldOfViewToFocalLength(lens.FieldOfView, lens.SensorSize.y).ToString("F1") + ")"); } else if (orthographic) { DrawLabel(labelPos, "Orthographic Size (" + lens.OrthographicSize.ToString("F1") + ")"); } else if (isLensHorizontal) { DrawLabel(labelPos, "Horizontal FOV (" + Camera.VerticalToHorizontalFieldOfView(lens.FieldOfView, lens.Aspect).ToString("F1") + ")"); } else { DrawLabel(labelPos, "Vertical FOV (" + lens.FieldOfView.ToString("F1") + ")"); } } Handles.color = fovHandleDraggedOrHovered ? Handles.selectedColor : HelperLineDefaultColor; var vcamLocalToWorld = Matrix4x4.TRS(camPos, camRot, Vector3.one); DrawFrustum(vcamLocalToWorld, lens); SoloOnDrag(GUIUtility.hotControl == fovHandleId, vcam, fovHandleId); Handles.color = originalColor; } public static void NearFarClipHandle(CinemachineVirtualCameraBase vcam, SerializedProperty lens) { var originalColor = Handles.color; Handles.color = Handles.preselectionColor; var vcamState = vcam.State; var camPos = vcamState.FinalPosition; var camRot = vcamState.FinalOrientation; var camForward = camRot * Vector3.forward; var nearClipPlane = lens.FindPropertyRelative("NearClipPlane"); var farClipPlane = lens.FindPropertyRelative("FarClipPlane"); var nearClipPos = camPos + camForward * nearClipPlane.floatValue; var farClipPos = camPos + camForward * farClipPlane.floatValue; var vcamLens = vcamState.Lens; EditorGUI.BeginChangeCheck(); var ncHandleId = GUIUtility.GetControlID(FocusType.Passive); var newNearClipPos = Handles.Slider(ncHandleId, nearClipPos, camForward, CubeHandleCapSize(nearClipPos), Handles.CubeHandleCap, 0.5f); var fcHandleId = GUIUtility.GetControlID(FocusType.Passive); var newFarClipPos = Handles.Slider(fcHandleId, farClipPos, camForward, CubeHandleCapSize(farClipPos), Handles.CubeHandleCap, 0.5f); if (EditorGUI.EndChangeCheck()) { nearClipPlane.floatValue += SliderHandleDelta(newNearClipPos, nearClipPos, camForward); if (!vcamLens.Orthographic) { nearClipPlane.floatValue = Mathf.Max(0.01f, nearClipPlane.floatValue); } farClipPlane.floatValue += SliderHandleDelta(newFarClipPos, farClipPos, camForward); lens.serializedObject.ApplyModifiedProperties(); } var vcamLocalToWorld = Matrix4x4.TRS(camPos, camRot, Vector3.one); Handles.color = HelperLineDefaultColor; DrawFrustum(vcamLocalToWorld, vcamLens); if (GUIUtility.hotControl == ncHandleId || HandleUtility.nearestControl == ncHandleId) { DrawPreFrustum(vcamLocalToWorld, vcamLens); DrawLabel(nearClipPos, "Near Clip Plane (" + nearClipPlane.floatValue.ToString("F1") + ")"); } if (GUIUtility.hotControl == fcHandleId || HandleUtility.nearestControl == fcHandleId) { DrawPreFrustum(vcamLocalToWorld, vcamLens); DrawLabel(farClipPos, "Far Clip Plane (" + farClipPlane.floatValue.ToString("F1") + ")"); } SoloOnDrag(GUIUtility.hotControl == ncHandleId || GUIUtility.hotControl == fcHandleId, vcam, Mathf.Min(ncHandleId, fcHandleId)); Handles.color = originalColor; } static void DrawPreFrustum(Matrix4x4 transform, LensSettings lens) { if (!lens.Orthographic && lens.NearClipPlane >= 0) { DrawPerspectiveFrustum(transform, lens.FieldOfView, lens.NearClipPlane, 0, lens.Aspect, true); } } static void DrawFrustum(Matrix4x4 transform, LensSettings lens) { if (lens.Orthographic) { DrawOrthographicFrustum(transform, lens.OrthographicSize, lens.FarClipPlane, lens.NearClipPlane, lens.Aspect); } else { DrawPerspectiveFrustum(transform, lens.FieldOfView, lens.FarClipPlane, lens.NearClipPlane, lens.Aspect, false); } } static void DrawOrthographicFrustum(Matrix4x4 transform, float orthographicSize, float farClipPlane, float nearClipRange, float aspect) { var originalMatrix = Handles.matrix; Handles.matrix = transform; var size = new Vector3(aspect * orthographicSize * 2, orthographicSize * 2, farClipPlane - nearClipRange); Handles.DrawWireCube(new Vector3(0, 0, (size.z / 2) + nearClipRange), size); Handles.matrix = originalMatrix; } static void DrawPerspectiveFrustum(Matrix4x4 transform, float fov, float farClipPlane, float nearClipRange, float aspect, bool dottedLine) { var originalMatrix = Handles.matrix; Handles.matrix = transform; fov = fov * 0.5f * Mathf.Deg2Rad; var tanfov = Mathf.Tan(fov); var farEnd = new Vector3(0, 0, farClipPlane); var endSizeX = new Vector3(farClipPlane * tanfov * aspect, 0, 0); var endSizeY = new Vector3(0, farClipPlane * tanfov, 0); Vector3 s1, s2, s3, s4; var e1 = farEnd + endSizeX + endSizeY; var e2 = farEnd - endSizeX + endSizeY; var e3 = farEnd - endSizeX - endSizeY; var e4 = farEnd + endSizeX - endSizeY; if (nearClipRange <= 0.0f) { s1 = s2 = s3 = s4 = Vector3.zero; } else { var startSizeX = new Vector3(nearClipRange * tanfov * aspect, 0, 0); var startSizeY = new Vector3(0, nearClipRange * tanfov, 0); var startPoint = new Vector3(0, 0, nearClipRange); s1 = startPoint + startSizeX + startSizeY; s2 = startPoint - startSizeX + startSizeY; s3 = startPoint - startSizeX - startSizeY; s4 = startPoint + startSizeX - startSizeY; if (dottedLine) { Handles.DrawDottedLine(s1, s2, k_DottedLineSpacing); Handles.DrawDottedLine(s2, s3, k_DottedLineSpacing); Handles.DrawDottedLine(s3, s4, k_DottedLineSpacing); Handles.DrawDottedLine(s4, s1, k_DottedLineSpacing); } else { Handles.DrawLine(s1, s2); Handles.DrawLine(s2, s3); Handles.DrawLine(s3, s4); Handles.DrawLine(s4, s1); } } if (dottedLine) { Handles.DrawDottedLine(e1, e2, k_DottedLineSpacing); Handles.DrawDottedLine(e2, e3, k_DottedLineSpacing); Handles.DrawDottedLine(e3, e4, k_DottedLineSpacing); Handles.DrawDottedLine(e4, e1, k_DottedLineSpacing); Handles.DrawDottedLine(s1, e1, k_DottedLineSpacing); Handles.DrawDottedLine(s2, e2, k_DottedLineSpacing); Handles.DrawDottedLine(s3, e3, k_DottedLineSpacing); Handles.DrawDottedLine(s4, e4, k_DottedLineSpacing); } else { Handles.DrawLine(e1, e2); Handles.DrawLine(e2, e3); Handles.DrawLine(e3, e4); Handles.DrawLine(e4, e1); Handles.DrawLine(s1, e1); Handles.DrawLine(s2, e2); Handles.DrawLine(s3, e3); Handles.DrawLine(s4, e4); } Handles.matrix = originalMatrix; } public static void TrackedObjectOffsetTool( CinemachineComponentBase cmComponent, SerializedProperty trackedObjectOffset) { var originalColor = Handles.color; var lookAtPos = cmComponent.LookAtTargetPosition; var lookAtRot = cmComponent.LookAtTargetRotation; var trackedObjectPos = lookAtPos + lookAtRot * trackedObjectOffset.vector3Value; EditorGUI.BeginChangeCheck(); #if UNITY_2022_2_OR_NEWER var tooHandleIds = Handles.PositionHandleIds.@default; var newTrackedObjectPos = Handles.PositionHandle(tooHandleIds, trackedObjectPos, lookAtRot); var tooHandleMinId = tooHandleIds.x - 1; var tooHandleMaxId = tooHandleIds.xyz + 1; #else var tooHandleMinId = GUIUtility.GetControlID(FocusType.Passive); var newTrackedObjectPos = Handles.PositionHandle(trackedObjectPos, lookAtRot); var tooHandleMaxId = GUIUtility.GetControlID(FocusType.Passive); #endif if (EditorGUI.EndChangeCheck()) { trackedObjectOffset.vector3Value += PositionHandleDelta(lookAtRot, newTrackedObjectPos, trackedObjectPos); trackedObjectOffset.serializedObject.ApplyModifiedProperties(); } var trackedObjectOffsetHandleIsDragged = tooHandleMinId < GUIUtility.hotControl && GUIUtility.hotControl < tooHandleMaxId; var trackedObjectOffsetHandleIsUsedOrHovered = trackedObjectOffsetHandleIsDragged || tooHandleMinId < HandleUtility.nearestControl && HandleUtility.nearestControl < tooHandleMaxId; if (trackedObjectOffsetHandleIsUsedOrHovered) { DrawLabel(trackedObjectPos, "(" + cmComponent.Stage + ") Tracked Object Offset " + trackedObjectOffset.vector3Value.ToString("F1")); } Handles.color = trackedObjectOffsetHandleIsUsedOrHovered ? Handles.selectedColor : HelperLineDefaultColor; Handles.DrawDottedLine(lookAtPos, trackedObjectPos, k_DottedLineSpacing); Handles.DrawLine(trackedObjectPos, cmComponent.VcamState.FinalPosition); SoloOnDrag(trackedObjectOffsetHandleIsDragged, cmComponent.VirtualCamera, tooHandleMaxId); Handles.color = originalColor; } public static void TransposerFollowOffsetTool(CinemachineTransposer cmComponent) { var originalColor = Handles.color; var brain = CinemachineCore.Instance.FindPotentialTargetBrain(cmComponent.VirtualCamera); var up = brain != null ? brain.DefaultWorldUp : Vector3.up; var camPos = cmComponent.GetTargetCameraPosition(up); var camRot = cmComponent.GetReferenceOrientation(up); EditorGUI.BeginChangeCheck(); #if UNITY_2022_2_OR_NEWER var foHandleIds = Handles.PositionHandleIds.@default; var newPos = Handles.PositionHandle(foHandleIds, camPos, camRot); var foHandleMinId = foHandleIds.x - 1; var foHandleMaxId = foHandleIds.xyz + 1; #else var foHandleMinId = GUIUtility.GetControlID(FocusType.Passive); var newPos = Handles.PositionHandle(camPos, camRot); var foHandleMaxId = GUIUtility.GetControlID(FocusType.Passive); #endif if (EditorGUI.EndChangeCheck()) { var so = new SerializedObject(cmComponent); var followOffset = so.FindProperty(() => cmComponent.m_FollowOffset); followOffset.vector3Value += PositionHandleDelta(camRot, newPos, camPos); so.ApplyModifiedProperties(); followOffset.vector3Value = cmComponent.EffectiveOffset; so.ApplyModifiedProperties(); } var followOffsetHandleIsDragged = foHandleMinId < GUIUtility.hotControl && GUIUtility.hotControl < foHandleMaxId; var followOffsetHandleIsDraggedOrHovered = followOffsetHandleIsDragged || foHandleMinId < HandleUtility.nearestControl && HandleUtility.nearestControl < foHandleMaxId; if (followOffsetHandleIsDraggedOrHovered) { DrawLabel(camPos, "Follow offset " + cmComponent.m_FollowOffset.ToString("F1")); } Handles.color = followOffsetHandleIsDraggedOrHovered ? Handles.selectedColor : HelperLineDefaultColor; Handles.DrawDottedLine(cmComponent.FollowTargetPosition, camPos, k_DottedLineSpacing); SoloOnDrag(followOffsetHandleIsDragged, cmComponent.VirtualCamera, foHandleMaxId); Handles.color = originalColor; } /// /// Draws Orbit handles (e.g. for freelook) /// /// Index of the rig being edited, or -1 if none public static int OrbitControlHandle( CinemachineVirtualCameraBase vcam, SerializedProperty orbits) { var originalColor = Handles.color; var followPos = vcam.Follow.position; var draggedRig = -1; var minIndex = 1; for (var rigIndex = 0; rigIndex < orbits.arraySize; ++rigIndex) { var orbit = orbits.GetArrayElementAtIndex(rigIndex); var orbitHeight = orbit.FindPropertyRelative("m_Height"); var orbitRadius = orbit.FindPropertyRelative("m_Radius"); Handles.color = Handles.preselectionColor; EditorGUI.BeginChangeCheck(); var heightHandleId = GUIUtility.GetControlID(FocusType.Passive); var heightHandlePos = followPos + Vector3.up * orbitHeight.floatValue; var newHeightHandlePos = Handles.Slider(heightHandleId, heightHandlePos, Vector3.up, CubeHandleCapSize(heightHandlePos), Handles.CubeHandleCap, 0.5f); var radiusHandleOffset = Vector3.right; var radiusHandleId = GUIUtility.GetControlID(FocusType.Passive); var radiusHandlePos = followPos + Vector3.up * orbitHeight.floatValue + radiusHandleOffset * orbitRadius.floatValue; var newRadiusHandlePos = Handles.Slider(radiusHandleId, radiusHandlePos, radiusHandleOffset, CubeHandleCapSize(radiusHandlePos), Handles.CubeHandleCap, 0.5f); if (EditorGUI.EndChangeCheck()) { orbitHeight.floatValue += SliderHandleDelta(newHeightHandlePos, heightHandlePos, Vector3.up); orbitRadius.floatValue += SliderHandleDelta(newRadiusHandlePos, radiusHandlePos, radiusHandleOffset); orbits.serializedObject.ApplyModifiedProperties(); } var isDragged = GUIUtility.hotControl == heightHandleId || GUIUtility.hotControl == radiusHandleId; Handles.color = isDragged || HandleUtility.nearestControl == heightHandleId || HandleUtility.nearestControl == radiusHandleId ? Handles.selectedColor : HelperLineDefaultColor; if (GUIUtility.hotControl == heightHandleId || HandleUtility.nearestControl == heightHandleId) { DrawLabel(heightHandlePos, "Height: " + orbitHeight.floatValue); } if (GUIUtility.hotControl == radiusHandleId || HandleUtility.nearestControl == radiusHandleId) { DrawLabel(radiusHandlePos, "Radius: " + orbitRadius.floatValue); } Handles.DrawWireDisc(newHeightHandlePos, Vector3.up, orbitRadius.floatValue); if (isDragged) { draggedRig = rigIndex; minIndex = Mathf.Min(Mathf.Min(heightHandleId), radiusHandleId); } } SoloOnDrag(draggedRig != -1, vcam, minIndex); Handles.color = originalColor; return draggedRig; } static bool s_IsDragging; static ICinemachineCamera s_UserSolo; public static void SoloOnDrag(bool isDragged, ICinemachineCamera vcam, int handleMaxId) { if (isDragged) { if (!s_IsDragging) { s_UserSolo = CinemachineBrain.SoloCamera; s_IsDragging = true; } CinemachineBrain.SoloCamera = vcam; } else if (s_IsDragging && handleMaxId != -1) // Handles sometimes return -1 as id, ignore those frames { CinemachineBrain.SoloCamera = s_UserSolo; InspectorUtility.RepaintGameView(); s_IsDragging = false; s_UserSolo = null; } } } } #endif