public static float Radius3DHandle(Quaternion rotation, Vector3 position, float radius, float minRadius = 0, float maxRadius = float.PositiveInfinity) { minRadius = Mathf.Abs(minRadius); maxRadius = Mathf.Abs(maxRadius); if (maxRadius < minRadius) { maxRadius = minRadius; } const float kEpsilon = 0.000001F; var camera = Camera.current; var cameraLocalPos = SceneHandles.inverseMatrix.MultiplyPoint(camera.transform.position); var cameraLocalForward = SceneHandles.inverseMatrix.MultiplyVector(camera.transform.forward); var isCameraInsideSphere = (cameraLocalPos - position).magnitude < radius; var isCameraOrthographic = camera.orthographic; var isStatic = (!Tools.hidden && EditorApplication.isPlaying && GameObjectUtility.ContainsStatic(Selection.gameObjects)); var prevDisabled = SceneHandles.disabled; var prevColor = SceneHandles.color; var forward = rotation * Vector3.forward; var up = rotation * Vector3.up; var right = rotation * Vector3.right; bool guiHasChanged = GUI.changed; GUI.changed = false; Vector3 positiveXDir = right; Vector3 negativeXDir = -right; Vector3 positiveYDir = up; Vector3 negativeYDir = -up; Vector3 positiveZDir = forward; Vector3 negativeZDir = -forward; Vector3 positiveXHandle = position + positiveXDir * radius; Vector3 negativeXHandle = position + negativeXDir * radius; Vector3 positiveYHandle = position + positiveYDir * radius; Vector3 negativeYHandle = position + negativeYDir * radius; Vector3 positiveZHandle = position + positiveZDir * radius; Vector3 negativeZHandle = position + negativeZDir * radius; bool positiveXBackfaced = false; bool negativeXBackfaced = false; bool positiveYBackfaced = false; bool negativeYBackfaced = false; bool positiveZBackfaced = false; bool negativeZBackfaced = false; if (!isCameraInsideSphere) { float cosV; cosV = isCameraOrthographic ? Vector3.Dot(positiveXDir, -cameraLocalForward) : Vector3.Dot(positiveXDir, (cameraLocalPos - positiveXHandle)); positiveXBackfaced = (cosV < -0.0001f); cosV = isCameraOrthographic ? Vector3.Dot(negativeXDir, -cameraLocalForward) : Vector3.Dot(negativeXDir, (cameraLocalPos - negativeXHandle)); negativeXBackfaced = (cosV < -0.0001f); cosV = isCameraOrthographic ? Vector3.Dot(positiveYDir, -cameraLocalForward) : Vector3.Dot(positiveYDir, (cameraLocalPos - positiveYHandle)); positiveYBackfaced = (cosV < -0.0001f); cosV = isCameraOrthographic ? Vector3.Dot(negativeYDir, -cameraLocalForward) : Vector3.Dot(negativeYDir, (cameraLocalPos - negativeYHandle)); negativeYBackfaced = (cosV < -0.0001f); cosV = isCameraOrthographic ? Vector3.Dot(positiveZDir, -cameraLocalForward) : Vector3.Dot(positiveZDir, (cameraLocalPos - positiveZHandle)); positiveZBackfaced = (cosV < -0.0001f); cosV = isCameraOrthographic ? Vector3.Dot(negativeZDir, -cameraLocalForward) : Vector3.Dot(negativeZDir, (cameraLocalPos - negativeZHandle)); negativeZBackfaced = (cosV < -0.0001f); } float positiveXSize = UnityEditor.HandleUtility.GetHandleSize(positiveXHandle) * 0.05f * (positiveXBackfaced ? backfaceSizeMultiplier : 1); float negativeXSize = UnityEditor.HandleUtility.GetHandleSize(negativeXHandle) * 0.05f * (negativeXBackfaced ? backfaceSizeMultiplier : 1); float positiveYSize = UnityEditor.HandleUtility.GetHandleSize(positiveYHandle) * 0.05f * (positiveYBackfaced ? backfaceSizeMultiplier : 1); float negativeYSize = UnityEditor.HandleUtility.GetHandleSize(negativeYHandle) * 0.05f * (negativeYBackfaced ? backfaceSizeMultiplier : 1); float positiveZSize = UnityEditor.HandleUtility.GetHandleSize(positiveZHandle) * 0.05f * (positiveZBackfaced ? backfaceSizeMultiplier : 1); float negativeZSize = UnityEditor.HandleUtility.GetHandleSize(negativeZHandle) * 0.05f * (negativeZBackfaced ? backfaceSizeMultiplier : 1); var isDisabled = isStatic || prevDisabled || Snapping.AxisLocking[0]; var color = SceneHandles.StateColor(prevColor, isDisabled, false); var backfacedColor = SceneHandles.MultiplyTransparency(color, SceneHandles.backfaceAlphaMultiplier); GUI.changed = false; SceneHandles.color = positiveXBackfaced ? backfacedColor : color; positiveXHandle = Slider2DHandle(positiveXHandle, Vector3.zero, forward, up, right, positiveXSize, OutlinedDotHandleCap); if (GUI.changed) { radius = Vector3.Dot(positiveXHandle - position, positiveXDir); guiHasChanged = true; } GUI.changed = false; SceneHandles.color = negativeXBackfaced ? backfacedColor : color; negativeXHandle = Slider2DHandle(negativeXHandle, Vector3.zero, forward, up, right, negativeXSize, OutlinedDotHandleCap); if (GUI.changed) { radius = Vector3.Dot(negativeXHandle - position, negativeXDir); guiHasChanged = true; } isDisabled = isStatic || prevDisabled || Snapping.AxisLocking[1]; color = SceneHandles.StateColor(prevColor, isDisabled, false); backfacedColor = SceneHandles.MultiplyTransparency(color, SceneHandles.backfaceAlphaMultiplier); GUI.changed = false; SceneHandles.color = positiveYBackfaced ? backfacedColor : color; positiveYHandle = Slider2DHandle(positiveYHandle, Vector3.zero, forward, up, right, positiveYSize, OutlinedDotHandleCap); if (GUI.changed) { radius = Vector3.Dot(positiveYHandle - position, positiveYDir); guiHasChanged = true; } GUI.changed = false; SceneHandles.color = negativeYBackfaced ? backfacedColor : color; negativeYHandle = Slider2DHandle(negativeYHandle, Vector3.zero, forward, up, right, negativeYSize, OutlinedDotHandleCap); if (GUI.changed) { radius = Vector3.Dot(negativeYHandle - position, negativeYDir); guiHasChanged = true; } isDisabled = isStatic || prevDisabled || Snapping.AxisLocking[2]; color = SceneHandles.StateColor(prevColor, isDisabled, false); backfacedColor = SceneHandles.MultiplyTransparency(color, SceneHandles.backfaceAlphaMultiplier); GUI.changed = false; SceneHandles.color = positiveZBackfaced ? backfacedColor : color; positiveZHandle = Slider2DHandle(positiveZHandle, Vector3.zero, up, forward, right, positiveZSize, OutlinedDotHandleCap); if (GUI.changed) { radius = Vector3.Dot(positiveZHandle - position, positiveZDir); guiHasChanged = true; } GUI.changed = false; SceneHandles.color = negativeZBackfaced ? backfacedColor : color; negativeZHandle = Slider2DHandle(negativeZHandle, Vector3.zero, up, forward, right, negativeZSize, OutlinedDotHandleCap); if (GUI.changed) { radius = Vector3.Dot(negativeZHandle - position, negativeZDir); guiHasChanged = true; } radius = Mathf.Max(minRadius, Mathf.Min(Mathf.Abs(radius), maxRadius)); GUI.changed |= guiHasChanged; if (radius > 0) { isDisabled = isStatic || prevDisabled || (Snapping.AxisLocking[0] && Snapping.AxisLocking[1]); color = SceneHandles.StateColor(prevColor, isDisabled, false); backfacedColor = SceneHandles.MultiplyTransparency(color, SceneHandles.backfaceAlphaMultiplier); var discOrientations = new Vector3[] { rotation *Vector3.right, rotation *Vector3.up, rotation *Vector3.forward }; var currentCamera = Camera.current; var cameraTransform = currentCamera.transform; if (currentCamera.orthographic) { var planeNormal = cameraTransform.forward; SceneHandles.DrawWireDisc(position, planeNormal, radius); planeNormal.Normalize(); for (int i = 0; i < 3; i++) { var discOrientation = discOrientations[i]; var discTangent = Vector3.Cross(discOrientation, planeNormal); // we may have view dir locked to one axis if (discTangent.sqrMagnitude > kEpsilon) { SceneHandles.color = color; SceneHandles.DrawWireArc(position, discOrientation, discTangent, 180, radius); SceneHandles.color = backfacedColor; SceneHandles.DrawWireArc(position, discOrientation, discTangent, -180, radius); } } } else { // Since the geometry is transformed by Handles.matrix during rendering, we transform the camera position // by the inverse matrix so that the two-shaded wireframe will have the proper orientation. var invMatrix = SceneHandles.inverseMatrix; var cameraCenter = cameraTransform.position; var cameraToCenter = position - invMatrix.MultiplyPoint(cameraCenter); // vector from camera to center var sqrDistCameraToCenter = cameraToCenter.sqrMagnitude; var sqrRadius = radius * radius; // squared radius var sqrOffset = sqrRadius * sqrRadius / sqrDistCameraToCenter; // squared distance from actual center to drawn disc center var insideAmount = sqrOffset / sqrRadius; if (insideAmount < 1) { if (Mathf.Abs(sqrDistCameraToCenter) < kEpsilon) { return(radius); } var horizonRadius = Mathf.Sqrt(sqrRadius - sqrOffset); var horizonCenter = position - sqrRadius * cameraToCenter / sqrDistCameraToCenter; SceneHandles.color = color; SceneHandles.DrawWireDisc(horizonCenter, cameraToCenter, horizonRadius); var planeNormal = cameraToCenter.normalized; for (int i = 0; i < 3; i++) { var discOrientation = discOrientations[i]; var angleBetweenDiscAndNormal = Mathf.Acos(Vector3.Dot(discOrientation, planeNormal)); angleBetweenDiscAndNormal = (Mathf.PI * 0.5f) - Mathf.Min(angleBetweenDiscAndNormal, Mathf.PI - angleBetweenDiscAndNormal); float f = Mathf.Tan(angleBetweenDiscAndNormal); float g = Mathf.Sqrt(sqrOffset + f * f * sqrOffset) / radius; if (g < 1) { var angleToHorizon = Mathf.Asin(g) * Mathf.Rad2Deg; var discTangent = Vector3.Cross(discOrientation, planeNormal); var vectorToPointOnHorizon = Quaternion.AngleAxis(angleToHorizon, discOrientation) * discTangent; var horizonArcLength = (90 - angleToHorizon) * 2.0f; SceneHandles.color = color; SceneHandles.DrawWireArc(position, discOrientation, vectorToPointOnHorizon, horizonArcLength, radius); SceneHandles.color = backfacedColor; SceneHandles.DrawWireArc(position, discOrientation, vectorToPointOnHorizon, horizonArcLength - 360, radius); } else { SceneHandles.color = backfacedColor; SceneHandles.DrawWireDisc(position, discOrientation, radius); } } } else { SceneHandles.color = backfacedColor; for (int i = 0; i < 3; i++) { var discOrientation = discOrientations[i]; SceneHandles.DrawWireDisc(position, discOrientation, radius); } } } } SceneHandles.disabled = prevDisabled; SceneHandles.color = prevColor; return(radius); }
public static Bounds BoundsHandle(Bounds bounds, Quaternion rotation, CapFunction sideCapFunction, CapFunction pointCapFunction, Vector3?snappingSteps = null) { var hotControl = GUIUtility.hotControl; bool isControlHot = false; for (int i = 0; i < s_BoundsControlIds.Length; i++) { s_BoundsControlIds[i] = GUIUtility.GetControlID(s_BoundsHash, FocusType.Keyboard); s_BoundsAxisHot[i] = s_BoundsControlIds[i] == hotControl; isControlHot = isControlHot || s_BoundsAxisHot[i]; } s_BoundsSlideDirs[0] = rotation * Vector3.right; s_BoundsSlideDirs[1] = rotation * Vector3.up; s_BoundsSlideDirs[2] = rotation * Vector3.forward; var min = bounds.min; var max = bounds.max; var center = bounds.center; s_BoundsValues[0] = min.x; s_BoundsValues[1] = min.y; s_BoundsValues[2] = min.z; s_BoundsValues[3] = max.x; s_BoundsValues[4] = max.y; s_BoundsValues[5] = max.z; s_BoundsVertices[0] = rotation * new Vector3(s_BoundsValues[0], s_BoundsValues[1], s_BoundsValues[2]); s_BoundsVertices[1] = rotation * new Vector3(s_BoundsValues[3], s_BoundsValues[1], s_BoundsValues[2]); s_BoundsVertices[2] = rotation * new Vector3(s_BoundsValues[3], s_BoundsValues[4], s_BoundsValues[2]); s_BoundsVertices[3] = rotation * new Vector3(s_BoundsValues[0], s_BoundsValues[4], s_BoundsValues[2]); s_BoundsVertices[4] = rotation * new Vector3(s_BoundsValues[0], s_BoundsValues[1], s_BoundsValues[5]); s_BoundsVertices[5] = rotation * new Vector3(s_BoundsValues[3], s_BoundsValues[1], s_BoundsValues[5]); s_BoundsVertices[6] = rotation * new Vector3(s_BoundsValues[3], s_BoundsValues[4], s_BoundsValues[5]); s_BoundsVertices[7] = rotation * new Vector3(s_BoundsValues[0], s_BoundsValues[4], s_BoundsValues[5]); s_BoundsSidePoint[0] = rotation * new Vector3(s_BoundsValues[0], center.y, center.z); s_BoundsSidePoint[1] = rotation * new Vector3(center.x, s_BoundsValues[1], center.z); s_BoundsSidePoint[2] = rotation * new Vector3(center.x, center.y, s_BoundsValues[2]); s_BoundsSidePoint[3] = rotation * new Vector3(s_BoundsValues[3], center.y, center.z); s_BoundsSidePoint[4] = rotation * new Vector3(center.x, s_BoundsValues[4], center.z); s_BoundsSidePoint[5] = rotation * new Vector3(center.x, center.y, s_BoundsValues[5]); // TODO: add handles in the corners of each quad on the bounds, with an offset from the vertex, to drag from there using (new SceneHandles.DrawingScope()) { var prevDisabled = SceneHandles.disabled; var isStatic = (!Tools.hidden && EditorApplication.isPlaying && GameObjectUtility.ContainsStatic(Selection.gameObjects)); for (int i = 0; i < s_BoundsAxisDisabled.Length; i++) { s_BoundsAxisDisabled[i] = isStatic || prevDisabled || Snapping.AxisLocking[i % 3] || (isControlHot && !s_BoundsAxisHot[i]); } var camera = Camera.current; var cameraLocalPos = SceneHandles.inverseMatrix.MultiplyPoint(camera.transform.position); var cameraLocalForward = SceneHandles.inverseMatrix.MultiplyVector(camera.transform.forward); var isCameraInsideBox = bounds.Contains(cameraLocalPos); var isCameraOrthographic = camera.orthographic; var boundsColor = SceneHandles.handleColor; var backfacedColor = new Color(boundsColor.r, boundsColor.g, boundsColor.b, boundsColor.a * SceneHandles.backfaceAlphaMultiplier); var prevGUIchanged = GUI.changed; bool haveChanged = false; var selectedAxes = Axes.None; // all sides of bounds int currentFocusControl = SceneHandleUtility.focusControl; for (int i = 0; i < s_BoundsValues.Length; i++) { var id = s_BoundsControlIds[i]; GUI.changed = false; var localPoint = s_BoundsSidePoint[i]; var handleSize = UnityEditor.HandleUtility.GetHandleSize(localPoint); var pointSize = handleSize * HandleRendering.kPointScale; var direction = s_BoundsSlideDirs[i % 3]; var normal = (i < 3) ? -direction : direction; normal.x *= (bounds.size.x < 0) ? -1 : 1; normal.y *= (bounds.size.y < 0) ? -1 : 1; normal.z *= (bounds.size.z < 0) ? -1 : 1; if (Event.current.type == EventType.Repaint) { s_BoundsBackfaced[i] = false; if (!isCameraInsideBox) { var cosV = isCameraOrthographic ? Vector3.Dot(normal, -cameraLocalForward) : Vector3.Dot(normal, (cameraLocalPos - localPoint)); if (cosV < -0.0001f) { // TODO: do not set backfaced to true when side is infinitely thin s_BoundsBackfaced[i] = !(isControlHot && !s_BoundsAxisHot[i % 3]); } } var sideColor = (s_BoundsBackfaced[i] ? backfacedColor: boundsColor); SceneHandles.color = SceneHandles.StateColor(sideColor, s_BoundsAxisDisabled[i], (currentFocusControl == id)); if (currentFocusControl == id) { var sceneView = SceneView.currentDrawingSceneView; if (sceneView) { var rect = sceneView.position; rect.min = Vector2.zero; EditorGUIUtility.AddCursorRect(rect, SceneHandleUtility.GetCursorForDirection(localPoint, normal)); } selectedAxes = s_BoundsAxes[i]; } if (s_BoundsBackfaced[i]) { pointSize *= backfaceSizeMultiplier; } } var steps = snappingSteps ?? Snapping.MoveSnappingSteps; var newPoint = Slider1DHandle(id, (Axis)(i % 3), localPoint, normal, steps[i % 3], pointSize, sideCapFunction); if (GUI.changed) { s_BoundsValues[i] += Vector3.Dot(direction, newPoint - localPoint); haveChanged = true; } } // all edges of bounds for (int i = 0; i < s_BoundsEdgeIndices.GetLength(0); i++) { var id = GUIUtility.GetControlID(s_BoundsHash, FocusType.Keyboard); GUI.changed = false; var index1 = s_BoundsEdgeIndices[i, 0]; var index2 = s_BoundsEdgeIndices[i, 1]; var point1 = s_BoundsVertices[index1]; var point2 = s_BoundsVertices[index2]; var midPoint = (point1 + point2) * 0.5f; var offset1 = s_EdgeDirectionOffsets[i, 0]; var offset2 = s_EdgeDirectionOffsets[i, 1]; var offset3 = s_EdgeDirectionOffsets[i, 2]; var offset1_dir = offset1 % 3; var offset2_dir = offset2 % 3; if (Event.current.type == EventType.Repaint) { var highlight = (currentFocusControl == id) || (currentFocusControl == s_BoundsControlIds[offset1]) || (currentFocusControl == s_BoundsControlIds[offset2]); var edgeColor = (s_BoundsBackfaced[offset1] && s_BoundsBackfaced[offset2]) ? backfacedColor : boundsColor; var edgeDisabled = (s_BoundsAxisDisabled[offset1] && s_BoundsAxisDisabled[offset2]); SceneHandles.color = SceneHandles.StateColor(edgeColor, edgeDisabled, highlight); if (currentFocusControl == id) { selectedAxes = s_EdgeAxes[i]; } } // only use capFunction (render point) when in ortho mode & aligned with box or when side size is 0 bool isSideAlignedWithCamera = false; // TODO: determine if aligned with camera direction & in ortho mode bool showSidePoint = !isSideAlignedWithCamera && ((point2 - point1).sqrMagnitude < kShowPointThreshold); float pointSize; Vector3 offset; if (showSidePoint) { pointSize = UnityEditor.HandleUtility.GetHandleSize(midPoint) * HandleRendering.kPointScale; offset = Edge2DHandleOffset(id, point1, point2, midPoint, s_BoundsSlideDirs[offset3], s_BoundsSlideDirs[offset1_dir], s_BoundsSlideDirs[offset2_dir], pointSize, pointCapFunction, s_EdgeAxes[i], snappingSteps: snappingSteps); } else { offset = Edge2DHandleOffset(id, point1, point2, midPoint, s_BoundsSlideDirs[offset3], s_BoundsSlideDirs[offset1_dir], s_BoundsSlideDirs[offset2_dir], 0, null, s_EdgeAxes[i], snappingSteps: snappingSteps); } if (GUI.changed) { offset = Quaternion.Inverse(rotation) * offset; if (Mathf.Abs(offset[offset1_dir]) > 0.000001f || Mathf.Abs(offset[offset2_dir]) > 0.000001f) { s_BoundsValues[offset1] += offset[offset1_dir]; s_BoundsValues[offset2] += offset[offset2_dir]; haveChanged = true; } else { GUI.changed = false; } } } GUI.changed = prevGUIchanged || haveChanged; if (haveChanged) { var size = bounds.size; center.x = (s_BoundsValues[3] + s_BoundsValues[0]) * 0.5f; size.x = (s_BoundsValues[3] - s_BoundsValues[0]); center.y = (s_BoundsValues[4] + s_BoundsValues[1]) * 0.5f; size.y = (s_BoundsValues[4] - s_BoundsValues[1]); center.z = (s_BoundsValues[5] + s_BoundsValues[2]) * 0.5f; size.z = (s_BoundsValues[5] - s_BoundsValues[2]); bounds.center = center; bounds.size = size; } // TODO: paint XZ intersection with grid plane + 'shadow' SceneHandles.disabled = prevDisabled; } return(bounds); }