├── Assets └── RuntimeGizmo │ ├── Custom │ └── TransformGizmoCustomGizmo.cs │ ├── ExampleScene.unity │ ├── ExampleScene.unity.meta │ ├── Helpers │ ├── ExtMathf.cs │ ├── ExtTransform.cs │ ├── ExtTransformType.cs │ ├── ExtVector3.cs │ └── Geometry.cs │ ├── Objects │ ├── AxisInfo.cs │ ├── AxisVectors.cs │ ├── Commands │ │ ├── SelectCommand.cs │ │ └── TransformCommand.cs │ ├── Enums.cs │ ├── IntersectPoints.cs │ ├── Square.cs │ └── TargetInfo.cs │ ├── Shader │ └── Resources │ │ ├── Lines.shader │ │ └── Outline.shader │ ├── TransformGizmo.cs │ ├── TransformGizmo.cs.meta │ └── UndoRedo │ ├── CommandGroup.cs │ ├── DropoutStack.cs │ ├── ICommand.cs │ ├── UndoRedo.cs │ └── UndoRedoManager.cs ├── LICENSE └── README.md /Assets/RuntimeGizmo/Custom/TransformGizmoCustomGizmo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RuntimeGizmos 5 | { 6 | //Currently doesnt really handle TransformType.All 7 | public class TransformGizmoCustomGizmo : MonoBehaviour 8 | { 9 | public bool autoFindTransformGizmo = true; 10 | public TransformGizmo transformGizmo; 11 | 12 | public CustomTransformGizmos customTranslationGizmos = new CustomTransformGizmos(); 13 | public CustomTransformGizmos customRotationGizmos = new CustomTransformGizmos(); 14 | public CustomTransformGizmos customScaleGizmos = new CustomTransformGizmos(); 15 | 16 | public bool scaleBasedOnDistance = true; 17 | public float scaleMultiplier = .4f; 18 | 19 | public int gizmoLayer = 2; //2 is the ignoreRaycast layer. Set to whatever you want. 20 | 21 | LayerMask mask; 22 | 23 | void Awake() 24 | { 25 | if(transformGizmo == null && autoFindTransformGizmo) 26 | { 27 | transformGizmo = GameObject.FindObjectOfType(); 28 | } 29 | 30 | transformGizmo.manuallyHandleGizmo = true; 31 | 32 | //Since we are using a mesh, rotating can get weird due to how the rotation method works, 33 | //so we use a different rotation method that will let us rotate by acting like our custom rotation gizmo is a wheel. 34 | //Can still give weird results depending on camera angle, but I think its more understanding for the user as to why its messing up. 35 | transformGizmo.circularRotationMethod = true; 36 | 37 | mask = LayerMask.GetMask(LayerMask.LayerToName(gizmoLayer)); 38 | 39 | customTranslationGizmos.Init(gizmoLayer); 40 | customRotationGizmos.Init(gizmoLayer); 41 | customScaleGizmos.Init(gizmoLayer); 42 | } 43 | 44 | void OnEnable() 45 | { 46 | transformGizmo.onCheckForSelectedAxis += CheckForSelectedAxis; 47 | transformGizmo.onDrawCustomGizmo += OnDrawCustomGizmos; 48 | } 49 | void OnDisable() 50 | { 51 | transformGizmo.onCheckForSelectedAxis -= CheckForSelectedAxis; 52 | transformGizmo.onDrawCustomGizmo -= OnDrawCustomGizmos; 53 | } 54 | 55 | void CheckForSelectedAxis() 56 | { 57 | ShowProperGizmoType(); 58 | 59 | if(Input.GetMouseButtonDown(0)) 60 | { 61 | RaycastHit hitInfo; 62 | if(Physics.Raycast(transformGizmo.myCamera.ScreenPointToRay(Input.mousePosition), out hitInfo, Mathf.Infinity, mask)) 63 | { 64 | Axis selectedAxis = Axis.None; 65 | TransformType type = transformGizmo.transformType; 66 | 67 | if(selectedAxis == Axis.None && transformGizmo.TransformTypeContains(TransformType.Move)) 68 | { 69 | selectedAxis = customTranslationGizmos.GetSelectedAxis(hitInfo.collider); 70 | type = TransformType.Move; 71 | } 72 | if(selectedAxis == Axis.None && transformGizmo.TransformTypeContains(TransformType.Rotate)) 73 | { 74 | selectedAxis = customRotationGizmos.GetSelectedAxis(hitInfo.collider); 75 | type = TransformType.Rotate; 76 | } 77 | if(selectedAxis == Axis.None && transformGizmo.TransformTypeContains(TransformType.Scale)) 78 | { 79 | selectedAxis = customScaleGizmos.GetSelectedAxis(hitInfo.collider); 80 | type = TransformType.Scale; 81 | } 82 | 83 | transformGizmo.SetTranslatingAxis(type, selectedAxis); 84 | } 85 | } 86 | } 87 | 88 | void OnDrawCustomGizmos() 89 | { 90 | if(transformGizmo.TranslatingTypeContains(TransformType.Move)) DrawCustomGizmo(customTranslationGizmos); 91 | if(transformGizmo.TranslatingTypeContains(TransformType.Rotate)) DrawCustomGizmo(customRotationGizmos); 92 | if(transformGizmo.TranslatingTypeContains(TransformType.Scale)) DrawCustomGizmo(customScaleGizmos); 93 | } 94 | 95 | void DrawCustomGizmo(CustomTransformGizmos customGizmo) 96 | { 97 | AxisInfo axisInfo = transformGizmo.GetAxisInfo(); 98 | customGizmo.SetAxis(axisInfo); 99 | customGizmo.SetPosition(transformGizmo.pivotPoint); 100 | 101 | Vector4 totalScaleMultiplier = Vector4.one; 102 | if(scaleBasedOnDistance) 103 | { 104 | totalScaleMultiplier.w *= (scaleMultiplier * transformGizmo.GetDistanceMultiplier()); 105 | } 106 | 107 | if(transformGizmo.transformingType == TransformType.Scale) 108 | { 109 | float totalScaleAmount = 1f + transformGizmo.totalScaleAmount; 110 | if(transformGizmo.translatingAxis == Axis.Any) totalScaleMultiplier += (Vector4.one * totalScaleAmount); 111 | else if(transformGizmo.translatingAxis == Axis.X) totalScaleMultiplier.x *= totalScaleAmount; 112 | else if(transformGizmo.translatingAxis == Axis.Y) totalScaleMultiplier.y *= totalScaleAmount; 113 | else if(transformGizmo.translatingAxis == Axis.Z) totalScaleMultiplier.z *= totalScaleAmount; 114 | } 115 | 116 | customGizmo.ScaleMultiply(totalScaleMultiplier); 117 | } 118 | 119 | void ShowProperGizmoType() 120 | { 121 | bool hasSelection = transformGizmo.mainTargetRoot != null; 122 | customTranslationGizmos.SetEnable(hasSelection && transformGizmo.TranslatingTypeContains(TransformType.Move)); 123 | customRotationGizmos.SetEnable(hasSelection && transformGizmo.TranslatingTypeContains(TransformType.Rotate)); 124 | customScaleGizmos.SetEnable(hasSelection && transformGizmo.TranslatingTypeContains(TransformType.Scale)); 125 | } 126 | } 127 | 128 | [Serializable] 129 | public class CustomTransformGizmos 130 | { 131 | public Transform xAxisGizmo; 132 | public Transform yAxisGizmo; 133 | public Transform zAxisGizmo; 134 | public Transform anyAxisGizmo; 135 | 136 | Collider xAxisGizmoCollider; 137 | Collider yAxisGizmoCollider; 138 | Collider zAxisGizmoCollider; 139 | Collider anyAxisGizmoCollider; 140 | 141 | Vector3 originalXAxisScale; 142 | Vector3 originalYAxisScale; 143 | Vector3 originalZAxisScale; 144 | Vector3 originalAnyAxisScale; 145 | 146 | public void Init(int layer) 147 | { 148 | if(xAxisGizmo != null) 149 | { 150 | SetLayerRecursively(xAxisGizmo.gameObject, layer); 151 | xAxisGizmoCollider = xAxisGizmo.GetComponentInChildren(); 152 | originalXAxisScale = xAxisGizmo.localScale; 153 | } 154 | if(yAxisGizmo != null) 155 | { 156 | SetLayerRecursively(yAxisGizmo.gameObject, layer); 157 | yAxisGizmoCollider = yAxisGizmo.GetComponentInChildren(); 158 | originalYAxisScale = yAxisGizmo.localScale; 159 | } 160 | if(zAxisGizmo != null) 161 | { 162 | SetLayerRecursively(zAxisGizmo.gameObject, layer); 163 | zAxisGizmoCollider = zAxisGizmo.GetComponentInChildren(); 164 | originalZAxisScale = zAxisGizmo.localScale; 165 | } 166 | if(anyAxisGizmo != null) 167 | { 168 | SetLayerRecursively(anyAxisGizmo.gameObject, layer); 169 | anyAxisGizmoCollider = anyAxisGizmo.GetComponentInChildren(); 170 | originalAnyAxisScale = anyAxisGizmo.localScale; 171 | } 172 | } 173 | 174 | public void SetEnable(bool enable) 175 | { 176 | if(xAxisGizmo != null && xAxisGizmo.gameObject.activeSelf != enable) xAxisGizmo.gameObject.SetActive(enable); 177 | if(yAxisGizmo != null && yAxisGizmo.gameObject.activeSelf != enable) yAxisGizmo.gameObject.SetActive(enable); 178 | if(zAxisGizmo != null && zAxisGizmo.gameObject.activeSelf != enable) zAxisGizmo.gameObject.SetActive(enable); 179 | if(anyAxisGizmo != null && anyAxisGizmo.gameObject.activeSelf != enable) anyAxisGizmo.gameObject.SetActive(enable); 180 | } 181 | 182 | public void SetAxis(AxisInfo axisInfo) 183 | { 184 | Quaternion lookRotation = Quaternion.LookRotation(axisInfo.zDirection, axisInfo.yDirection); 185 | 186 | if(xAxisGizmo != null) xAxisGizmo.rotation = lookRotation; 187 | if(yAxisGizmo != null) yAxisGizmo.rotation = lookRotation; 188 | if(zAxisGizmo != null) zAxisGizmo.rotation = lookRotation; 189 | if(anyAxisGizmo != null) anyAxisGizmo.rotation = lookRotation; 190 | } 191 | 192 | public void SetPosition(Vector3 position) 193 | { 194 | if(xAxisGizmo != null) xAxisGizmo.position = position; 195 | if(yAxisGizmo != null) yAxisGizmo.position = position; 196 | if(zAxisGizmo != null) zAxisGizmo.position = position; 197 | if(anyAxisGizmo != null) anyAxisGizmo.position = position; 198 | } 199 | 200 | public void ScaleMultiply(Vector4 scaleMultiplier) 201 | { 202 | if(xAxisGizmo != null) xAxisGizmo.localScale = Vector3.Scale(originalXAxisScale, new Vector3(scaleMultiplier.w + scaleMultiplier.x, scaleMultiplier.w, scaleMultiplier.w)); 203 | if(yAxisGizmo != null) yAxisGizmo.localScale = Vector3.Scale(originalYAxisScale, new Vector3(scaleMultiplier.w, scaleMultiplier.w + scaleMultiplier.y, scaleMultiplier.w)); 204 | if(zAxisGizmo != null) zAxisGizmo.localScale = Vector3.Scale(originalZAxisScale, new Vector3(scaleMultiplier.w, scaleMultiplier.w, scaleMultiplier.w + scaleMultiplier.z)); 205 | if(anyAxisGizmo != null) anyAxisGizmo.localScale = originalAnyAxisScale * scaleMultiplier.w; 206 | } 207 | 208 | public Axis GetSelectedAxis(Collider selectedCollider) 209 | { 210 | if(xAxisGizmoCollider != null && xAxisGizmoCollider == selectedCollider) return Axis.X; 211 | if(yAxisGizmoCollider != null && yAxisGizmoCollider == selectedCollider) return Axis.Y; 212 | if(zAxisGizmoCollider != null && zAxisGizmoCollider == selectedCollider) return Axis.Z; 213 | if(anyAxisGizmoCollider != null && anyAxisGizmoCollider == selectedCollider) return Axis.Any; 214 | 215 | return Axis.None; 216 | } 217 | 218 | void SetLayerRecursively(GameObject gameObject, int layer) 219 | { 220 | Transform[] selfAndChildren = gameObject.GetComponentsInChildren(true); 221 | 222 | for(int i = 0; i < selfAndChildren.Length; i++) 223 | { 224 | selfAndChildren[i].gameObject.layer = layer; 225 | } 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/ExampleScene.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiddenMonk/Unity3DRuntimeTransformGizmo/eb008d9676735faab55132a7bbee2d0d180a1828/Assets/RuntimeGizmo/ExampleScene.unity -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/ExampleScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 678d4f03f0bd01b4faf0609706d37c25 3 | timeCreated: 1476929737 4 | licenseType: Free 5 | DefaultImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Helpers/ExtMathf.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RuntimeGizmos 4 | { 5 | public static class ExtMathf 6 | { 7 | public static float Squared(this float value) 8 | { 9 | return value * value; 10 | } 11 | 12 | public static float SafeDivide(float value, float divider) 13 | { 14 | if(divider == 0) return 0; 15 | return value / divider; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Helpers/ExtTransform.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RuntimeGizmos 5 | { 6 | public static class ExtTransform 7 | { 8 | //This acts as if you are using a parent transform as your new pivot and transforming that parent instead of the child. 9 | //So instead of creating a gameobject and parenting "target" to it and translating only the parent gameobject, we can use this method. 10 | public static void SetScaleFrom(this Transform target, Vector3 worldPivot, Vector3 newScale) 11 | { 12 | Vector3 localOffset = target.InverseTransformPoint(worldPivot); 13 | 14 | Vector3 localScale = target.localScale; 15 | Vector3 scaleRatio = new Vector3(ExtMathf.SafeDivide(newScale.x, localScale.x), ExtMathf.SafeDivide(newScale.y, localScale.y), ExtMathf.SafeDivide(newScale.z, localScale.z)); 16 | Vector3 scaledLocalOffset = Vector3.Scale(localOffset, scaleRatio); 17 | 18 | Vector3 newPosition = target.TransformPoint(localOffset - scaledLocalOffset); 19 | 20 | target.localScale = newScale; 21 | target.position = newPosition; 22 | } 23 | 24 | //This acts as if you are scaling based on a point that is offset from the actual pivot. 25 | //It gives results similar to when you scale an object in the unity editor when in Center mode instead of Pivot mode. 26 | //The Center was an offset from the actual Pivot. 27 | public static void SetScaleFromOffset(this Transform target, Vector3 worldPivot, Vector3 newScale) 28 | { 29 | //Seemed to work, except when under a parent that has a non uniform scale and rotation it was a bit off. 30 | //This might be due to transform.lossyScale not being accurate under those conditions, or possibly something else is wrong... 31 | //Maybe things can work if we can find a way to convert the "newPosition = ..." line to use Matrix4x4 for possibly more scale accuracy. 32 | //However, I have tried and tried and have no idea how to do that kind of math =/ 33 | //Seems like unity editor also has some inaccuracies with skewed scales, such as scaling little by little compared to scaling one large scale. 34 | // 35 | //Will mess up or give undesired results if the target.localScale or target.lossyScale has any set to 0. 36 | //Unity editor doesnt even allow you to scale an axis when it is set to 0. 37 | 38 | Vector3 localOffset = target.InverseTransformPoint(worldPivot); 39 | 40 | Vector3 localScale = target.localScale; 41 | Vector3 scaleRatio = new Vector3(ExtMathf.SafeDivide(newScale.x, localScale.x), ExtMathf.SafeDivide(newScale.y, localScale.y), ExtMathf.SafeDivide(newScale.z, localScale.z)); 42 | Vector3 scaledLocalOffset = Vector3.Scale(localOffset, scaleRatio); 43 | 44 | Vector3 newPosition = target.rotation * Vector3.Scale(localOffset - scaledLocalOffset, target.lossyScale) + target.position; 45 | 46 | target.localScale = newScale; 47 | target.position = newPosition; 48 | } 49 | 50 | public static Vector3 GetCenter(this Transform transform, CenterType centerType) 51 | { 52 | if(centerType == CenterType.Solo) 53 | { 54 | Renderer renderer = transform.GetComponent(); 55 | if(renderer != null) 56 | { 57 | return renderer.bounds.center; 58 | }else{ 59 | return transform.position; 60 | } 61 | } 62 | else if(centerType == CenterType.All) 63 | { 64 | Bounds totalBounds = new Bounds(transform.position, Vector3.zero); 65 | GetCenterAll(transform, ref totalBounds); 66 | return totalBounds.center; 67 | } 68 | 69 | return transform.position; 70 | } 71 | static void GetCenterAll(this Transform transform, ref Bounds currentTotalBounds) 72 | { 73 | Renderer renderer = transform.GetComponent(); 74 | if(renderer != null) 75 | { 76 | currentTotalBounds.Encapsulate(renderer.bounds); 77 | }else{ 78 | currentTotalBounds.Encapsulate(transform.position); 79 | } 80 | 81 | for(int i = 0; i < transform.childCount; i++) 82 | { 83 | transform.GetChild(i).GetCenterAll(ref currentTotalBounds); 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Helpers/ExtTransformType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RuntimeGizmos 4 | { 5 | public static class ExtTransformType 6 | { 7 | public static bool TransformTypeContains(this TransformType mainType, TransformType type, TransformSpace space) 8 | { 9 | if(type == mainType) return true; 10 | 11 | if(mainType == TransformType.All) 12 | { 13 | if(type == TransformType.Move) return true; 14 | else if(type == TransformType.Rotate) return true; 15 | //else if(type == TransformType.RectTool) return false; 16 | else if(type == TransformType.Scale && space == TransformSpace.Local) return true; 17 | } 18 | 19 | return false; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Helpers/ExtVector3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RuntimeGizmos 5 | { 6 | public static class ExtVector3 7 | { 8 | public static float MagnitudeInDirection(Vector3 vector, Vector3 direction, bool normalizeParameters = true) 9 | { 10 | if(normalizeParameters) direction.Normalize(); 11 | return Vector3.Dot(vector, direction); 12 | } 13 | 14 | public static Vector3 Abs(this Vector3 vector) 15 | { 16 | return new Vector3(Mathf.Abs(vector.x), Mathf.Abs(vector.y), Mathf.Abs(vector.z)); 17 | } 18 | 19 | public static bool IsParallel(Vector3 direction, Vector3 otherDirection, float precision = .0001f) 20 | { 21 | return Vector3.Cross(direction, otherDirection).sqrMagnitude < precision; 22 | } 23 | 24 | public static bool IsInDirection(Vector3 direction, Vector3 otherDirection) 25 | { 26 | return Vector3.Dot(direction, otherDirection) > 0f; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Helpers/Geometry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RuntimeGizmos 5 | { 6 | public static class Geometry 7 | { 8 | public static float LinePlaneDistance(Vector3 linePoint, Vector3 lineVec, Vector3 planePoint, Vector3 planeNormal) 9 | { 10 | //calculate the distance between the linePoint and the line-plane intersection point 11 | float dotNumerator = Vector3.Dot((planePoint - linePoint), planeNormal); 12 | float dotDenominator = Vector3.Dot(lineVec, planeNormal); 13 | 14 | //line and plane are not parallel 15 | if(dotDenominator != 0f) 16 | { 17 | return dotNumerator / dotDenominator; 18 | } 19 | 20 | return 0; 21 | } 22 | 23 | //Note that the line is infinite, this is not a line-segment plane intersect 24 | public static Vector3 LinePlaneIntersect(Vector3 linePoint, Vector3 lineVec, Vector3 planePoint, Vector3 planeNormal) 25 | { 26 | float distance = LinePlaneDistance(linePoint, lineVec, planePoint, planeNormal); 27 | 28 | //line and plane are not parallel 29 | if(distance != 0f) 30 | { 31 | return linePoint + (lineVec * distance); 32 | } 33 | 34 | return Vector3.zero; 35 | } 36 | 37 | //Returns 2 points since on line 1 there will be a closest point to line 2, and on line 2 there will be a closest point to line 1. 38 | public static IntersectPoints ClosestPointsOnTwoLines(Vector3 point1, Vector3 point1Direction, Vector3 point2, Vector3 point2Direction) 39 | { 40 | IntersectPoints intersections = new IntersectPoints(); 41 | 42 | //I dont think we need to normalize 43 | //point1Direction.Normalize(); 44 | //point2Direction.Normalize(); 45 | 46 | float a = Vector3.Dot(point1Direction, point1Direction); 47 | float b = Vector3.Dot(point1Direction, point2Direction); 48 | float e = Vector3.Dot(point2Direction, point2Direction); 49 | 50 | float d = a*e - b*b; 51 | 52 | //This is a check if parallel, howeverm since we are not normalizing the directions, it seems even if they are parallel they will not == 0 53 | //so they will get past this point, but its seems to be alright since it seems to still give a correct point (although a point very fary away). 54 | //Also, if they are parallel and we dont normalize, the deciding point seems randomly choses on the lines, which while is still correct, 55 | //our ClosestPointsOnTwoLineSegments gets undesireable results when on corners. (for example when using it in our ClosestPointOnTriangleToLine). 56 | if(d != 0f) 57 | { 58 | Vector3 r = point1 - point2; 59 | float c = Vector3.Dot(point1Direction, r); 60 | float f = Vector3.Dot(point2Direction, r); 61 | 62 | float s = (b*f - c*e) / d; 63 | float t = (a*f - c*b) / d; 64 | 65 | intersections.first = point1 + point1Direction * s; 66 | intersections.second = point2 + point2Direction * t; 67 | }else{ 68 | //Lines are parallel, select any points next to eachother 69 | intersections.first = point1; 70 | intersections.second = point2 + Vector3.Project(point1 - point2, point2Direction); 71 | } 72 | 73 | return intersections; 74 | } 75 | 76 | public static IntersectPoints ClosestPointsOnSegmentToLine(Vector3 segment0, Vector3 segment1, Vector3 linePoint, Vector3 lineDirection) 77 | { 78 | IntersectPoints closests = ClosestPointsOnTwoLines(segment0, segment1 - segment0, linePoint, lineDirection); 79 | closests.first = ClampToSegment(closests.first, segment0, segment1); 80 | 81 | return closests; 82 | } 83 | 84 | //Assumes the point is already on the line somewhere 85 | public static Vector3 ClampToSegment(Vector3 point, Vector3 linePoint1, Vector3 linePoint2) 86 | { 87 | Vector3 lineDirection = linePoint2 - linePoint1; 88 | 89 | if(!ExtVector3.IsInDirection(point - linePoint1, lineDirection)) 90 | { 91 | point = linePoint1; 92 | } 93 | else if(ExtVector3.IsInDirection(point - linePoint2, lineDirection)) 94 | { 95 | point = linePoint2; 96 | } 97 | 98 | return point; 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Objects/AxisInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RuntimeGizmos 5 | { 6 | public struct AxisInfo 7 | { 8 | public Vector3 pivot; 9 | public Vector3 xDirection; 10 | public Vector3 yDirection; 11 | public Vector3 zDirection; 12 | 13 | public void Set(Transform target, Vector3 pivot, TransformSpace space) 14 | { 15 | if(space == TransformSpace.Global) 16 | { 17 | xDirection = Vector3.right; 18 | yDirection = Vector3.up; 19 | zDirection = Vector3.forward; 20 | } 21 | else if(space == TransformSpace.Local) 22 | { 23 | xDirection = target.right; 24 | yDirection = target.up; 25 | zDirection = target.forward; 26 | } 27 | 28 | this.pivot = pivot; 29 | } 30 | 31 | public Vector3 GetXAxisEnd(float size) 32 | { 33 | return pivot + (xDirection * size); 34 | } 35 | public Vector3 GetYAxisEnd(float size) 36 | { 37 | return pivot + (yDirection * size); 38 | } 39 | public Vector3 GetZAxisEnd(float size) 40 | { 41 | return pivot + (zDirection * size); 42 | } 43 | public Vector3 GetAxisEnd(Vector3 direction, float size) 44 | { 45 | return pivot + (direction * size); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Objects/AxisVectors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace RuntimeGizmos 6 | { 7 | public class AxisVectors 8 | { 9 | public List x = new List(); 10 | public List y = new List(); 11 | public List z = new List(); 12 | public List all = new List(); 13 | 14 | public void Add(AxisVectors axisVectors) 15 | { 16 | x.AddRange(axisVectors.x); 17 | y.AddRange(axisVectors.y); 18 | z.AddRange(axisVectors.z); 19 | all.AddRange(axisVectors.all); 20 | } 21 | 22 | public void Clear() 23 | { 24 | x.Clear(); 25 | y.Clear(); 26 | z.Clear(); 27 | all.Clear(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Objects/Commands/SelectCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandUndoRedo; 3 | using UnityEngine; 4 | using System.Collections.Generic; 5 | 6 | namespace RuntimeGizmos 7 | { 8 | public abstract class SelectCommand : ICommand 9 | { 10 | protected Transform target; 11 | protected TransformGizmo transformGizmo; 12 | 13 | public SelectCommand(TransformGizmo transformGizmo, Transform target) 14 | { 15 | this.transformGizmo = transformGizmo; 16 | this.target = target; 17 | } 18 | 19 | public abstract void Execute(); 20 | public abstract void UnExecute(); 21 | } 22 | 23 | public class AddTargetCommand : SelectCommand 24 | { 25 | List targetRoots = new List(); 26 | 27 | public AddTargetCommand(TransformGizmo transformGizmo, Transform target, List targetRoots) : base(transformGizmo, target) 28 | { 29 | //Since we might have had a child selected and then selected the parent, the child would have been removed from the selected, 30 | //so we store all the targetRoots before we add so that if we undo we can properly have the children selected again. 31 | this.targetRoots.AddRange(targetRoots); 32 | } 33 | 34 | public override void Execute() 35 | { 36 | transformGizmo.AddTarget(target, false); 37 | } 38 | 39 | public override void UnExecute() 40 | { 41 | transformGizmo.RemoveTarget(target, false); 42 | 43 | for(int i = 0; i < targetRoots.Count; i++) 44 | { 45 | transformGizmo.AddTarget(targetRoots[i], false); 46 | } 47 | } 48 | } 49 | 50 | public class RemoveTargetCommand : SelectCommand 51 | { 52 | public RemoveTargetCommand(TransformGizmo transformGizmo, Transform target) : base(transformGizmo, target) {} 53 | 54 | public override void Execute() 55 | { 56 | transformGizmo.RemoveTarget(target, false); 57 | } 58 | 59 | public override void UnExecute() 60 | { 61 | transformGizmo.AddTarget(target, false); 62 | } 63 | } 64 | 65 | public class ClearTargetsCommand : SelectCommand 66 | { 67 | List targetRoots = new List(); 68 | 69 | public ClearTargetsCommand(TransformGizmo transformGizmo, List targetRoots) : base(transformGizmo, null) 70 | { 71 | this.targetRoots.AddRange(targetRoots); 72 | } 73 | 74 | public override void Execute() 75 | { 76 | transformGizmo.ClearTargets(false); 77 | } 78 | 79 | public override void UnExecute() 80 | { 81 | for(int i = 0; i < targetRoots.Count; i++) 82 | { 83 | transformGizmo.AddTarget(targetRoots[i], false); 84 | } 85 | } 86 | } 87 | 88 | public class ClearAndAddTargetCommand : SelectCommand 89 | { 90 | List targetRoots = new List(); 91 | 92 | public ClearAndAddTargetCommand(TransformGizmo transformGizmo, Transform target, List targetRoots) : base(transformGizmo, target) 93 | { 94 | this.targetRoots.AddRange(targetRoots); 95 | } 96 | 97 | public override void Execute() 98 | { 99 | transformGizmo.ClearTargets(false); 100 | transformGizmo.AddTarget(target, false); 101 | } 102 | 103 | public override void UnExecute() 104 | { 105 | transformGizmo.RemoveTarget(target, false); 106 | 107 | for(int i = 0; i < targetRoots.Count; i++) 108 | { 109 | transformGizmo.AddTarget(targetRoots[i], false); 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Objects/Commands/TransformCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandUndoRedo; 3 | using UnityEngine; 4 | 5 | namespace RuntimeGizmos 6 | { 7 | public class TransformCommand : ICommand 8 | { 9 | TransformValues newValues; 10 | TransformValues oldValues; 11 | 12 | Transform transform; 13 | TransformGizmo transformGizmo; 14 | 15 | public TransformCommand(TransformGizmo transformGizmo, Transform transform) 16 | { 17 | this.transformGizmo = transformGizmo; 18 | this.transform = transform; 19 | 20 | oldValues = new TransformValues() {position=transform.position, rotation=transform.rotation, scale=transform.localScale}; 21 | } 22 | 23 | public void StoreNewTransformValues() 24 | { 25 | newValues = new TransformValues() {position=transform.position, rotation=transform.rotation, scale=transform.localScale}; 26 | } 27 | 28 | public void Execute() 29 | { 30 | transform.position = newValues.position; 31 | transform.rotation = newValues.rotation; 32 | transform.localScale = newValues.scale; 33 | 34 | transformGizmo.SetPivotPoint(); 35 | } 36 | 37 | public void UnExecute() 38 | { 39 | transform.position = oldValues.position; 40 | transform.rotation = oldValues.rotation; 41 | transform.localScale = oldValues.scale; 42 | 43 | transformGizmo.SetPivotPoint(); 44 | } 45 | 46 | struct TransformValues 47 | { 48 | public Vector3 position; 49 | public Quaternion rotation; 50 | public Vector3 scale; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Objects/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace RuntimeGizmos 2 | { 3 | public enum TransformSpace {Global, Local} 4 | public enum TransformType {Move, Rotate, Scale /*, RectTool*/, All} 5 | public enum TransformPivot {Pivot, Center} 6 | public enum Axis {None, X, Y, Z, Any} 7 | 8 | //CenterType.All is the center of the current object mesh or pivot if not mesh and all its childrens mesh or pivot if no mesh. 9 | // CenterType.All might give different results than unity I think because unity only counts empty gameobjects a little bit, as if they have less weight. 10 | //CenterType.Solo is the center of the current objects mesh or pivot if no mesh. 11 | //Unity seems to use colliders first to use to find how much weight the object has or something to decide how much it effects the center, 12 | //but for now we only look at the Renderer.bounds.center, so expect some differences between unity. 13 | public enum CenterType {All, Solo} 14 | 15 | //ScaleType.FromPoint acts as if you are using a parent transform as your new pivot and transforming that parent instead of the child. 16 | //ScaleType.FromPointOffset acts as if you are scaling based on a point that is offset from the actual pivot. Its similar to unity editor scaling in Center pivot mode (though a little inaccurate if object is skewed) 17 | public enum ScaleType {FromPoint, FromPointOffset} 18 | } 19 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Objects/IntersectPoints.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RuntimeGizmos 5 | { 6 | public struct IntersectPoints 7 | { 8 | public Vector3 first; 9 | public Vector3 second; 10 | 11 | public IntersectPoints(Vector3 first, Vector3 second) 12 | { 13 | this.first = first; 14 | this.second = second; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Objects/Square.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RuntimeGizmos 5 | { 6 | public struct Square 7 | { 8 | public Vector3 bottomLeft; 9 | public Vector3 bottomRight; 10 | public Vector3 topLeft; 11 | public Vector3 topRight; 12 | 13 | public Vector3 this[int index] 14 | { 15 | get 16 | { 17 | switch (index) 18 | { 19 | case 0: 20 | return this.bottomLeft; 21 | case 1: 22 | return this.topLeft; 23 | case 2: 24 | return this.topRight; 25 | case 3: 26 | return this.bottomRight; 27 | case 4: 28 | return this.bottomLeft; //so we wrap around back to start 29 | default: 30 | return Vector3.zero; 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Objects/TargetInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace RuntimeGizmos 5 | { 6 | public class TargetInfo 7 | { 8 | public Vector3 centerPivotPoint; 9 | 10 | public Vector3 previousPosition; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Shader/Resources/Lines.shader: -------------------------------------------------------------------------------- 1 | Shader "Custom/Lines" 2 | { 3 | SubShader 4 | { 5 | Pass 6 | { 7 | Blend SrcAlpha OneMinusSrcAlpha 8 | ZWrite Off 9 | ZTest Always 10 | Cull Off 11 | Fog { Mode Off } 12 | 13 | BindChannels 14 | { 15 | Bind "vertex", vertex 16 | Bind "color", color 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/Shader/Resources/Outline.shader: -------------------------------------------------------------------------------- 1 | //Taken and modified from github.com/Shrimpey/Outlined-Diffuse-Shader-Fixed/blob/master/CustomOutline.shader 2 | 3 | Shader "Custom/Outline" { 4 | Properties { 5 | _OutlineColor ("Outline Color", Color) = (1,.5,0,1) 6 | _Outline ("Outline width", Range (0, 1)) = .1 7 | } 8 | 9 | CGINCLUDE 10 | #include "UnityCG.cginc" 11 | 12 | struct appdata { 13 | float4 vertex : POSITION; 14 | float3 normal : NORMAL; 15 | }; 16 | 17 | struct v2f { 18 | float4 pos : POSITION; 19 | float4 color : COLOR; 20 | }; 21 | 22 | uniform float _Outline; 23 | uniform float4 _OutlineColor; 24 | 25 | v2f vert(appdata v) { 26 | // just make a copy of incoming vertex data but scaled according to normal direction 27 | v2f o; 28 | 29 | v.vertex *= ( 1 + _Outline); 30 | 31 | o.pos = UnityObjectToClipPos(v.vertex); 32 | 33 | o.color = _OutlineColor; 34 | return o; 35 | } 36 | ENDCG 37 | 38 | SubShader { 39 | Tags { "DisableBatching" = "True" } 40 | Pass { 41 | Name "OUTLINE" 42 | Tags {"LightMode" = "Always" } 43 | Cull Front 44 | ZWrite On 45 | ColorMask RGB 46 | Blend SrcAlpha OneMinusSrcAlpha 47 | 48 | CGPROGRAM 49 | #pragma vertex vert 50 | #pragma fragment frag 51 | half4 frag(v2f i) :COLOR { return i.color; } 52 | ENDCG 53 | } 54 | } 55 | 56 | Fallback "Diffuse" 57 | } 58 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/TransformGizmo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using System.Collections.Generic; 4 | using System.Collections; 5 | using CommandUndoRedo; 6 | 7 | namespace RuntimeGizmos 8 | { 9 | //To be safe, if you are changing any transforms hierarchy, such as parenting an object to something, 10 | //you should call ClearTargets before doing so just to be sure nothing unexpected happens... as well as call UndoRedoManager.Clear() 11 | //For example, if you select an object that has children, move the children elsewhere, deselect the original object, then try to add those old children to the selection, I think it wont work. 12 | 13 | [RequireComponent(typeof(Camera))] 14 | public class TransformGizmo : MonoBehaviour 15 | { 16 | public TransformSpace space = TransformSpace.Global; 17 | public TransformType transformType = TransformType.Move; 18 | public TransformPivot pivot = TransformPivot.Pivot; 19 | public CenterType centerType = CenterType.All; 20 | public ScaleType scaleType = ScaleType.FromPoint; 21 | 22 | //These are the same as the unity editor hotkeys 23 | public KeyCode SetMoveType = KeyCode.W; 24 | public KeyCode SetRotateType = KeyCode.E; 25 | public KeyCode SetScaleType = KeyCode.R; 26 | //public KeyCode SetRectToolType = KeyCode.T; 27 | public KeyCode SetAllTransformType = KeyCode.Y; 28 | public KeyCode SetSpaceToggle = KeyCode.X; 29 | public KeyCode SetPivotModeToggle = KeyCode.Z; 30 | public KeyCode SetCenterTypeToggle = KeyCode.C; 31 | public KeyCode SetScaleTypeToggle = KeyCode.S; 32 | public KeyCode translationSnapping = KeyCode.LeftControl; 33 | public KeyCode AddSelection = KeyCode.LeftShift; 34 | public KeyCode RemoveSelection = KeyCode.LeftControl; 35 | public KeyCode ActionKey = KeyCode.LeftShift; //Its set to shift instead of control so that while in the editor we dont accidentally undo editor changes =/ 36 | public KeyCode UndoAction = KeyCode.Z; 37 | public KeyCode RedoAction = KeyCode.Y; 38 | 39 | public Color xColor = new Color(1, 0, 0, 0.8f); 40 | public Color yColor = new Color(0, 1, 0, 0.8f); 41 | public Color zColor = new Color(0, 0, 1, 0.8f); 42 | public Color allColor = new Color(.7f, .7f, .7f, 0.8f); 43 | public Color selectedColor = new Color(1, 1, 0, 0.8f); 44 | public Color hoverColor = new Color(1, .75f, 0, 0.8f); 45 | public float planesOpacity = .5f; 46 | //public Color rectPivotColor = new Color(0, 0, 1, 0.8f); 47 | //public Color rectCornerColor = new Color(0, 0, 1, 0.8f); 48 | //public Color rectAnchorColor = new Color(.7f, .7f, .7f, 0.8f); 49 | //public Color rectLineColor = new Color(.7f, .7f, .7f, 0.8f); 50 | 51 | public float movementSnap = .25f; 52 | public float rotationSnap = 15f; 53 | public float scaleSnap = 1f; 54 | 55 | public float handleLength = .25f; 56 | public float handleWidth = .003f; 57 | public float planeSize = .035f; 58 | public float triangleSize = .03f; 59 | public float boxSize = .03f; 60 | public int circleDetail = 40; 61 | public float allMoveHandleLengthMultiplier = 1f; 62 | public float allRotateHandleLengthMultiplier = 1.4f; 63 | public float allScaleHandleLengthMultiplier = 1.6f; 64 | public float minSelectedDistanceCheck = .01f; 65 | public float moveSpeedMultiplier = 1f; 66 | public float scaleSpeedMultiplier = 1f; 67 | public float rotateSpeedMultiplier = 1f; 68 | public float allRotateSpeedMultiplier = 20f; 69 | 70 | public bool useFirstSelectedAsMain = true; 71 | 72 | //If circularRotationMethod is true, when rotating you will need to move your mouse around the object as if turning a wheel. 73 | //If circularRotationMethod is false, when rotating you can just click and drag in a line to rotate. 74 | public bool circularRotationMethod; 75 | 76 | //Mainly for if you want the pivot point to update correctly if selected objects are moving outside the transformgizmo. 77 | //Might be poor on performance if lots of objects are selected... 78 | public bool forceUpdatePivotPointOnChange = true; 79 | 80 | public int maxUndoStored = 100; 81 | 82 | public bool manuallyHandleGizmo; 83 | 84 | public LayerMask selectionMask = Physics.DefaultRaycastLayers; 85 | 86 | public Action onCheckForSelectedAxis; 87 | public Action onDrawCustomGizmo; 88 | 89 | public Camera myCamera {get; private set;} 90 | 91 | public bool isTransforming {get; private set;} 92 | public float totalScaleAmount {get; private set;} 93 | public Quaternion totalRotationAmount {get; private set;} 94 | public Axis translatingAxis {get {return nearAxis;}} 95 | public Axis translatingAxisPlane {get {return planeAxis;}} 96 | public bool hasTranslatingAxisPlane {get {return translatingAxisPlane != Axis.None && translatingAxisPlane != Axis.Any;}} 97 | public TransformType transformingType {get {return translatingType;}} 98 | 99 | public Vector3 pivotPoint {get; private set;} 100 | Vector3 totalCenterPivotPoint; 101 | 102 | public Transform mainTargetRoot {get {return (targetRootsOrdered.Count > 0) ? (useFirstSelectedAsMain) ? targetRootsOrdered[0] : targetRootsOrdered[targetRootsOrdered.Count - 1] : null;}} 103 | 104 | AxisInfo axisInfo; 105 | Axis nearAxis = Axis.None; 106 | Axis planeAxis = Axis.None; 107 | TransformType translatingType; 108 | 109 | AxisVectors handleLines = new AxisVectors(); 110 | AxisVectors handlePlanes = new AxisVectors(); 111 | AxisVectors handleTriangles = new AxisVectors(); 112 | AxisVectors handleSquares = new AxisVectors(); 113 | AxisVectors circlesLines = new AxisVectors(); 114 | 115 | //We use a HashSet and a List for targetRoots so that we get fast lookup with the hashset while also keeping track of the order with the list. 116 | List targetRootsOrdered = new List(); 117 | Dictionary targetRoots = new Dictionary(); 118 | HashSet highlightedRenderers = new HashSet(); 119 | HashSet children = new HashSet(); 120 | 121 | List childrenBuffer = new List(); 122 | List renderersBuffer = new List(); 123 | List materialsBuffer = new List(); 124 | 125 | WaitForEndOfFrame waitForEndOFFrame = new WaitForEndOfFrame(); 126 | Coroutine forceUpdatePivotCoroutine; 127 | 128 | static Material lineMaterial; 129 | static Material outlineMaterial; 130 | 131 | void Awake() 132 | { 133 | myCamera = GetComponent(); 134 | SetMaterial(); 135 | } 136 | 137 | void OnEnable() 138 | { 139 | forceUpdatePivotCoroutine = StartCoroutine(ForceUpdatePivotPointAtEndOfFrame()); 140 | } 141 | 142 | void OnDisable() 143 | { 144 | ClearTargets(); //Just so things gets cleaned up, such as removing any materials we placed on objects. 145 | 146 | StopCoroutine(forceUpdatePivotCoroutine); 147 | } 148 | 149 | void OnDestroy() 150 | { 151 | ClearAllHighlightedRenderers(); 152 | } 153 | 154 | void Update() 155 | { 156 | HandleUndoRedo(); 157 | 158 | SetSpaceAndType(); 159 | 160 | if(manuallyHandleGizmo) 161 | { 162 | if(onCheckForSelectedAxis != null) onCheckForSelectedAxis(); 163 | }else{ 164 | SetNearAxis(); 165 | } 166 | 167 | GetTarget(); 168 | 169 | if(mainTargetRoot == null) return; 170 | 171 | TransformSelected(); 172 | } 173 | 174 | void LateUpdate() 175 | { 176 | if(mainTargetRoot == null) return; 177 | 178 | //We run this in lateupdate since coroutines run after update and we want our gizmos to have the updated target transform position after TransformSelected() 179 | SetAxisInfo(); 180 | 181 | if(manuallyHandleGizmo) 182 | { 183 | if(onDrawCustomGizmo != null) onDrawCustomGizmo(); 184 | }else{ 185 | SetLines(); 186 | } 187 | } 188 | 189 | void OnPostRender() 190 | { 191 | if(mainTargetRoot == null || manuallyHandleGizmo) return; 192 | 193 | lineMaterial.SetPass(0); 194 | 195 | Color xColor = (nearAxis == Axis.X) ? (isTransforming) ? selectedColor : hoverColor : this.xColor; 196 | Color yColor = (nearAxis == Axis.Y) ? (isTransforming) ? selectedColor : hoverColor : this.yColor; 197 | Color zColor = (nearAxis == Axis.Z) ? (isTransforming) ? selectedColor : hoverColor : this.zColor; 198 | Color allColor = (nearAxis == Axis.Any) ? (isTransforming) ? selectedColor : hoverColor : this.allColor; 199 | 200 | //Note: The order of drawing the axis decides what gets drawn over what. 201 | 202 | TransformType moveOrScaleType = (transformType == TransformType.Scale || (isTransforming && translatingType == TransformType.Scale)) ? TransformType.Scale : TransformType.Move; 203 | DrawQuads(handleLines.z, GetColor(moveOrScaleType, this.zColor, zColor, hasTranslatingAxisPlane)); 204 | DrawQuads(handleLines.x, GetColor(moveOrScaleType, this.xColor, xColor, hasTranslatingAxisPlane)); 205 | DrawQuads(handleLines.y, GetColor(moveOrScaleType, this.yColor, yColor, hasTranslatingAxisPlane)); 206 | 207 | DrawTriangles(handleTriangles.x, GetColor(TransformType.Move, this.xColor, xColor, hasTranslatingAxisPlane)); 208 | DrawTriangles(handleTriangles.y, GetColor(TransformType.Move, this.yColor, yColor, hasTranslatingAxisPlane)); 209 | DrawTriangles(handleTriangles.z, GetColor(TransformType.Move, this.zColor, zColor, hasTranslatingAxisPlane)); 210 | 211 | DrawQuads(handlePlanes.z, GetColor(TransformType.Move, this.zColor, zColor, planesOpacity, !hasTranslatingAxisPlane)); 212 | DrawQuads(handlePlanes.x, GetColor(TransformType.Move, this.xColor, xColor, planesOpacity, !hasTranslatingAxisPlane)); 213 | DrawQuads(handlePlanes.y, GetColor(TransformType.Move, this.yColor, yColor, planesOpacity, !hasTranslatingAxisPlane)); 214 | 215 | DrawQuads(handleSquares.x, GetColor(TransformType.Scale, this.xColor, xColor)); 216 | DrawQuads(handleSquares.y, GetColor(TransformType.Scale, this.yColor, yColor)); 217 | DrawQuads(handleSquares.z, GetColor(TransformType.Scale, this.zColor, zColor)); 218 | DrawQuads(handleSquares.all, GetColor(TransformType.Scale, this.allColor, allColor)); 219 | 220 | DrawQuads(circlesLines.all, GetColor(TransformType.Rotate, this.allColor, allColor)); 221 | DrawQuads(circlesLines.x, GetColor(TransformType.Rotate, this.xColor, xColor)); 222 | DrawQuads(circlesLines.y, GetColor(TransformType.Rotate, this.yColor, yColor)); 223 | DrawQuads(circlesLines.z, GetColor(TransformType.Rotate, this.zColor, zColor)); 224 | } 225 | 226 | Color GetColor(TransformType type, Color normalColor, Color nearColor, bool forceUseNormal = false) 227 | { 228 | return GetColor(type, normalColor, nearColor, false, 1, forceUseNormal); 229 | } 230 | Color GetColor(TransformType type, Color normalColor, Color nearColor, float alpha, bool forceUseNormal = false) 231 | { 232 | return GetColor(type, normalColor, nearColor, true, alpha, forceUseNormal); 233 | } 234 | Color GetColor(TransformType type, Color normalColor, Color nearColor, bool setAlpha, float alpha, bool forceUseNormal = false) 235 | { 236 | Color color; 237 | if(!forceUseNormal && TranslatingTypeContains(type, false)) 238 | { 239 | color = nearColor; 240 | }else{ 241 | color = normalColor; 242 | } 243 | 244 | if(setAlpha) 245 | { 246 | color.a = alpha; 247 | } 248 | 249 | return color; 250 | } 251 | 252 | void HandleUndoRedo() 253 | { 254 | if(maxUndoStored != UndoRedoManager.maxUndoStored) { UndoRedoManager.maxUndoStored = maxUndoStored; } 255 | 256 | if(Input.GetKey(ActionKey)) 257 | { 258 | if(Input.GetKeyDown(UndoAction)) 259 | { 260 | UndoRedoManager.Undo(); 261 | } 262 | else if(Input.GetKeyDown(RedoAction)) 263 | { 264 | UndoRedoManager.Redo(); 265 | } 266 | } 267 | } 268 | 269 | //We only support scaling in local space. 270 | public TransformSpace GetProperTransformSpace() 271 | { 272 | return transformType == TransformType.Scale ? TransformSpace.Local : space; 273 | } 274 | 275 | public bool TransformTypeContains(TransformType type) 276 | { 277 | return TransformTypeContains(transformType, type); 278 | } 279 | public bool TranslatingTypeContains(TransformType type, bool checkIsTransforming = true) 280 | { 281 | TransformType transType = !checkIsTransforming || isTransforming ? translatingType : transformType; 282 | return TransformTypeContains(transType, type); 283 | } 284 | public bool TransformTypeContains(TransformType mainType, TransformType type) 285 | { 286 | return ExtTransformType.TransformTypeContains(mainType, type, GetProperTransformSpace()); 287 | } 288 | 289 | public float GetHandleLength(TransformType type, Axis axis = Axis.None, bool multiplyDistanceMultiplier = true) 290 | { 291 | float length = handleLength; 292 | if(transformType == TransformType.All) 293 | { 294 | if(type == TransformType.Move) length *= allMoveHandleLengthMultiplier; 295 | if(type == TransformType.Rotate) length *= allRotateHandleLengthMultiplier; 296 | if(type == TransformType.Scale) length *= allScaleHandleLengthMultiplier; 297 | } 298 | 299 | if(multiplyDistanceMultiplier) length *= GetDistanceMultiplier(); 300 | 301 | if(type == TransformType.Scale && isTransforming && (translatingAxis == axis || translatingAxis == Axis.Any)) length += totalScaleAmount; 302 | 303 | return length; 304 | } 305 | 306 | void SetSpaceAndType() 307 | { 308 | if(Input.GetKey(ActionKey)) return; 309 | 310 | if(Input.GetKeyDown(SetMoveType)) transformType = TransformType.Move; 311 | else if(Input.GetKeyDown(SetRotateType)) transformType = TransformType.Rotate; 312 | else if(Input.GetKeyDown(SetScaleType)) transformType = TransformType.Scale; 313 | //else if(Input.GetKeyDown(SetRectToolType)) type = TransformType.RectTool; 314 | else if(Input.GetKeyDown(SetAllTransformType)) transformType = TransformType.All; 315 | 316 | if(!isTransforming) translatingType = transformType; 317 | 318 | if(Input.GetKeyDown(SetPivotModeToggle)) 319 | { 320 | if(pivot == TransformPivot.Pivot) pivot = TransformPivot.Center; 321 | else if(pivot == TransformPivot.Center) pivot = TransformPivot.Pivot; 322 | 323 | SetPivotPoint(); 324 | } 325 | 326 | if(Input.GetKeyDown(SetCenterTypeToggle)) 327 | { 328 | if(centerType == CenterType.All) centerType = CenterType.Solo; 329 | else if(centerType == CenterType.Solo) centerType = CenterType.All; 330 | 331 | SetPivotPoint(); 332 | } 333 | 334 | if(Input.GetKeyDown(SetSpaceToggle)) 335 | { 336 | if(space == TransformSpace.Global) space = TransformSpace.Local; 337 | else if(space == TransformSpace.Local) space = TransformSpace.Global; 338 | } 339 | 340 | if(Input.GetKeyDown(SetScaleTypeToggle)) 341 | { 342 | if(scaleType == ScaleType.FromPoint) scaleType = ScaleType.FromPointOffset; 343 | else if(scaleType == ScaleType.FromPointOffset) scaleType = ScaleType.FromPoint; 344 | } 345 | 346 | if(transformType == TransformType.Scale) 347 | { 348 | if(pivot == TransformPivot.Pivot) scaleType = ScaleType.FromPoint; //FromPointOffset can be inaccurate and should only really be used in Center mode if desired. 349 | } 350 | } 351 | 352 | void TransformSelected() 353 | { 354 | if(mainTargetRoot != null) 355 | { 356 | if(nearAxis != Axis.None && Input.GetMouseButtonDown(0)) 357 | { 358 | StartCoroutine(TransformSelected(translatingType)); 359 | } 360 | } 361 | } 362 | 363 | IEnumerator TransformSelected(TransformType transType) 364 | { 365 | isTransforming = true; 366 | totalScaleAmount = 0; 367 | totalRotationAmount = Quaternion.identity; 368 | 369 | Vector3 originalPivot = pivotPoint; 370 | 371 | Vector3 otherAxis1, otherAxis2; 372 | Vector3 axis = GetNearAxisDirection(out otherAxis1, out otherAxis2); 373 | Vector3 planeNormal = hasTranslatingAxisPlane ? axis : (transform.position - originalPivot).normalized; 374 | Vector3 projectedAxis = Vector3.ProjectOnPlane(axis, planeNormal).normalized; 375 | Vector3 previousMousePosition = Vector3.zero; 376 | 377 | Vector3 currentSnapMovementAmount = Vector3.zero; 378 | float currentSnapRotationAmount = 0; 379 | float currentSnapScaleAmount = 0; 380 | 381 | List transformCommands = new List(); 382 | for(int i = 0; i < targetRootsOrdered.Count; i++) 383 | { 384 | transformCommands.Add(new TransformCommand(this, targetRootsOrdered[i])); 385 | } 386 | 387 | while(!Input.GetMouseButtonUp(0)) 388 | { 389 | Ray mouseRay = myCamera.ScreenPointToRay(Input.mousePosition); 390 | Vector3 mousePosition = Geometry.LinePlaneIntersect(mouseRay.origin, mouseRay.direction, originalPivot, planeNormal); 391 | bool isSnapping = Input.GetKey(translationSnapping); 392 | 393 | if(previousMousePosition != Vector3.zero && mousePosition != Vector3.zero) 394 | { 395 | if(transType == TransformType.Move) 396 | { 397 | Vector3 movement = Vector3.zero; 398 | 399 | if(hasTranslatingAxisPlane) 400 | { 401 | movement = mousePosition - previousMousePosition; 402 | }else{ 403 | float moveAmount = ExtVector3.MagnitudeInDirection(mousePosition - previousMousePosition, projectedAxis) * moveSpeedMultiplier; 404 | movement = axis * moveAmount; 405 | } 406 | 407 | if(isSnapping && movementSnap > 0) 408 | { 409 | currentSnapMovementAmount += movement; 410 | movement = Vector3.zero; 411 | 412 | if(hasTranslatingAxisPlane) 413 | { 414 | float amountInAxis1 = ExtVector3.MagnitudeInDirection(currentSnapMovementAmount, otherAxis1); 415 | float amountInAxis2 = ExtVector3.MagnitudeInDirection(currentSnapMovementAmount, otherAxis2); 416 | 417 | float remainder1; 418 | float snapAmount1 = CalculateSnapAmount(movementSnap, amountInAxis1, out remainder1); 419 | float remainder2; 420 | float snapAmount2 = CalculateSnapAmount(movementSnap, amountInAxis2, out remainder2); 421 | 422 | if(snapAmount1 != 0) 423 | { 424 | Vector3 snapMove = (otherAxis1 * snapAmount1); 425 | movement += snapMove; 426 | currentSnapMovementAmount -= snapMove; 427 | } 428 | if(snapAmount2 != 0) 429 | { 430 | Vector3 snapMove = (otherAxis2 * snapAmount2); 431 | movement += snapMove; 432 | currentSnapMovementAmount -= snapMove; 433 | } 434 | } 435 | else 436 | { 437 | float remainder; 438 | float snapAmount = CalculateSnapAmount(movementSnap, currentSnapMovementAmount.magnitude, out remainder); 439 | 440 | if(snapAmount != 0) 441 | { 442 | movement = currentSnapMovementAmount.normalized * snapAmount; 443 | currentSnapMovementAmount = currentSnapMovementAmount.normalized * remainder; 444 | } 445 | } 446 | } 447 | 448 | for(int i = 0; i < targetRootsOrdered.Count; i++) 449 | { 450 | Transform target = targetRootsOrdered[i]; 451 | 452 | target.Translate(movement, Space.World); 453 | } 454 | 455 | SetPivotPointOffset(movement); 456 | } 457 | else if(transType == TransformType.Scale) 458 | { 459 | Vector3 projected = (nearAxis == Axis.Any) ? transform.right : projectedAxis; 460 | float scaleAmount = ExtVector3.MagnitudeInDirection(mousePosition - previousMousePosition, projected) * scaleSpeedMultiplier; 461 | 462 | if(isSnapping && scaleSnap > 0) 463 | { 464 | currentSnapScaleAmount += scaleAmount; 465 | scaleAmount = 0; 466 | 467 | float remainder; 468 | float snapAmount = CalculateSnapAmount(scaleSnap, currentSnapScaleAmount, out remainder); 469 | 470 | if(snapAmount != 0) 471 | { 472 | scaleAmount = snapAmount; 473 | currentSnapScaleAmount = remainder; 474 | } 475 | } 476 | 477 | //WARNING - There is a bug in unity 5.4 and 5.5 that causes InverseTransformDirection to be affected by scale which will break negative scaling. Not tested, but updating to 5.4.2 should fix it - https://issuetracker.unity3d.com/issues/transformdirection-and-inversetransformdirection-operations-are-affected-by-scale 478 | Vector3 localAxis = (GetProperTransformSpace() == TransformSpace.Local && nearAxis != Axis.Any) ? mainTargetRoot.InverseTransformDirection(axis) : axis; 479 | 480 | Vector3 targetScaleAmount = Vector3.one; 481 | if(nearAxis == Axis.Any) targetScaleAmount = (ExtVector3.Abs(mainTargetRoot.localScale.normalized) * scaleAmount); 482 | else targetScaleAmount = localAxis * scaleAmount; 483 | 484 | for(int i = 0; i < targetRootsOrdered.Count; i++) 485 | { 486 | Transform target = targetRootsOrdered[i]; 487 | 488 | Vector3 targetScale = target.localScale + targetScaleAmount; 489 | 490 | if(pivot == TransformPivot.Pivot) 491 | { 492 | target.localScale = targetScale; 493 | } 494 | else if(pivot == TransformPivot.Center) 495 | { 496 | if(scaleType == ScaleType.FromPoint) 497 | { 498 | target.SetScaleFrom(originalPivot, targetScale); 499 | } 500 | else if(scaleType == ScaleType.FromPointOffset) 501 | { 502 | target.SetScaleFromOffset(originalPivot, targetScale); 503 | } 504 | } 505 | } 506 | 507 | totalScaleAmount += scaleAmount; 508 | } 509 | else if(transType == TransformType.Rotate) 510 | { 511 | float rotateAmount = 0; 512 | Vector3 rotationAxis = axis; 513 | 514 | if(nearAxis == Axis.Any) 515 | { 516 | Vector3 rotation = transform.TransformDirection(new Vector3(Input.GetAxis("Mouse Y"), -Input.GetAxis("Mouse X"), 0)); 517 | Quaternion.Euler(rotation).ToAngleAxis(out rotateAmount, out rotationAxis); 518 | rotateAmount *= allRotateSpeedMultiplier; 519 | }else{ 520 | if(circularRotationMethod) 521 | { 522 | float angle = Vector3.SignedAngle(previousMousePosition - originalPivot, mousePosition - originalPivot, axis); 523 | rotateAmount = angle * rotateSpeedMultiplier; 524 | }else{ 525 | Vector3 projected = (nearAxis == Axis.Any || ExtVector3.IsParallel(axis, planeNormal)) ? planeNormal : Vector3.Cross(axis, planeNormal); 526 | rotateAmount = (ExtVector3.MagnitudeInDirection(mousePosition - previousMousePosition, projected) * (rotateSpeedMultiplier * 100f)) / GetDistanceMultiplier(); 527 | } 528 | } 529 | 530 | if(isSnapping && rotationSnap > 0) 531 | { 532 | currentSnapRotationAmount += rotateAmount; 533 | rotateAmount = 0; 534 | 535 | float remainder; 536 | float snapAmount = CalculateSnapAmount(rotationSnap, currentSnapRotationAmount, out remainder); 537 | 538 | if(snapAmount != 0) 539 | { 540 | rotateAmount = snapAmount; 541 | currentSnapRotationAmount = remainder; 542 | } 543 | } 544 | 545 | for(int i = 0; i < targetRootsOrdered.Count; i++) 546 | { 547 | Transform target = targetRootsOrdered[i]; 548 | 549 | if(pivot == TransformPivot.Pivot) 550 | { 551 | target.Rotate(rotationAxis, rotateAmount, Space.World); 552 | } 553 | else if(pivot == TransformPivot.Center) 554 | { 555 | target.RotateAround(originalPivot, rotationAxis, rotateAmount); 556 | } 557 | } 558 | 559 | totalRotationAmount *= Quaternion.Euler(rotationAxis * rotateAmount); 560 | } 561 | } 562 | 563 | previousMousePosition = mousePosition; 564 | 565 | yield return null; 566 | } 567 | 568 | for(int i = 0; i < transformCommands.Count; i++) 569 | { 570 | ((TransformCommand)transformCommands[i]).StoreNewTransformValues(); 571 | } 572 | CommandGroup commandGroup = new CommandGroup(); 573 | commandGroup.Set(transformCommands); 574 | UndoRedoManager.Insert(commandGroup); 575 | 576 | totalRotationAmount = Quaternion.identity; 577 | totalScaleAmount = 0; 578 | isTransforming = false; 579 | SetTranslatingAxis(transformType, Axis.None); 580 | 581 | SetPivotPoint(); 582 | } 583 | 584 | float CalculateSnapAmount(float snapValue, float currentAmount, out float remainder) 585 | { 586 | remainder = 0; 587 | if(snapValue <= 0) return currentAmount; 588 | 589 | float currentAmountAbs = Mathf.Abs(currentAmount); 590 | if(currentAmountAbs > snapValue) 591 | { 592 | remainder = currentAmountAbs % snapValue; 593 | return snapValue * (Mathf.Sign(currentAmount) * Mathf.Floor(currentAmountAbs / snapValue)); 594 | } 595 | 596 | return 0; 597 | } 598 | 599 | Vector3 GetNearAxisDirection(out Vector3 otherAxis1, out Vector3 otherAxis2) 600 | { 601 | otherAxis1 = otherAxis2 = Vector3.zero; 602 | 603 | if(nearAxis != Axis.None) 604 | { 605 | if(nearAxis == Axis.X) 606 | { 607 | otherAxis1 = axisInfo.yDirection; 608 | otherAxis2 = axisInfo.zDirection; 609 | return axisInfo.xDirection; 610 | } 611 | if(nearAxis == Axis.Y) 612 | { 613 | otherAxis1 = axisInfo.xDirection; 614 | otherAxis2 = axisInfo.zDirection; 615 | return axisInfo.yDirection; 616 | } 617 | if(nearAxis == Axis.Z) 618 | { 619 | otherAxis1 = axisInfo.xDirection; 620 | otherAxis2 = axisInfo.yDirection; 621 | return axisInfo.zDirection; 622 | } 623 | if(nearAxis == Axis.Any) 624 | { 625 | return Vector3.one; 626 | } 627 | } 628 | 629 | return Vector3.zero; 630 | } 631 | 632 | void GetTarget() 633 | { 634 | if(nearAxis == Axis.None && Input.GetMouseButtonDown(0)) 635 | { 636 | bool isAdding = Input.GetKey(AddSelection); 637 | bool isRemoving = Input.GetKey(RemoveSelection); 638 | 639 | RaycastHit hitInfo; 640 | if(Physics.Raycast(myCamera.ScreenPointToRay(Input.mousePosition), out hitInfo, Mathf.Infinity, selectionMask)) 641 | { 642 | Transform target = hitInfo.transform; 643 | 644 | if(isAdding) 645 | { 646 | AddTarget(target); 647 | } 648 | else if(isRemoving) 649 | { 650 | RemoveTarget(target); 651 | } 652 | else if(!isAdding && !isRemoving) 653 | { 654 | ClearAndAddTarget(target); 655 | } 656 | }else{ 657 | if(!isAdding && !isRemoving) 658 | { 659 | ClearTargets(); 660 | } 661 | } 662 | } 663 | } 664 | 665 | public void AddTarget(Transform target, bool addCommand = true) 666 | { 667 | if(target != null) 668 | { 669 | if(targetRoots.ContainsKey(target)) return; 670 | if(children.Contains(target)) return; 671 | 672 | if(addCommand) UndoRedoManager.Insert(new AddTargetCommand(this, target, targetRootsOrdered)); 673 | 674 | AddTargetRoot(target); 675 | AddTargetHighlightedRenderers(target); 676 | 677 | SetPivotPoint(); 678 | } 679 | } 680 | 681 | public void RemoveTarget(Transform target, bool addCommand = true) 682 | { 683 | if(target != null) 684 | { 685 | if(!targetRoots.ContainsKey(target)) return; 686 | 687 | if(addCommand) UndoRedoManager.Insert(new RemoveTargetCommand(this, target)); 688 | 689 | RemoveTargetHighlightedRenderers(target); 690 | RemoveTargetRoot(target); 691 | 692 | SetPivotPoint(); 693 | } 694 | } 695 | 696 | public void ClearTargets(bool addCommand = true) 697 | { 698 | if(addCommand) UndoRedoManager.Insert(new ClearTargetsCommand(this, targetRootsOrdered)); 699 | 700 | ClearAllHighlightedRenderers(); 701 | targetRoots.Clear(); 702 | targetRootsOrdered.Clear(); 703 | children.Clear(); 704 | } 705 | 706 | void ClearAndAddTarget(Transform target) 707 | { 708 | UndoRedoManager.Insert(new ClearAndAddTargetCommand(this, target, targetRootsOrdered)); 709 | 710 | ClearTargets(false); 711 | AddTarget(target, false); 712 | } 713 | 714 | void AddTargetHighlightedRenderers(Transform target) 715 | { 716 | if(target != null) 717 | { 718 | GetTargetRenderers(target, renderersBuffer); 719 | 720 | for(int i = 0; i < renderersBuffer.Count; i++) 721 | { 722 | Renderer render = renderersBuffer[i]; 723 | 724 | if(!highlightedRenderers.Contains(render)) 725 | { 726 | materialsBuffer.Clear(); 727 | materialsBuffer.AddRange(render.sharedMaterials); 728 | 729 | if(!materialsBuffer.Contains(outlineMaterial)) 730 | { 731 | materialsBuffer.Add(outlineMaterial); 732 | render.materials = materialsBuffer.ToArray(); 733 | } 734 | 735 | highlightedRenderers.Add(render); 736 | } 737 | } 738 | 739 | materialsBuffer.Clear(); 740 | } 741 | } 742 | 743 | void GetTargetRenderers(Transform target, List renderers) 744 | { 745 | renderers.Clear(); 746 | if(target != null) 747 | { 748 | target.GetComponentsInChildren(true, renderers); 749 | } 750 | } 751 | 752 | void ClearAllHighlightedRenderers() 753 | { 754 | foreach(var target in targetRoots) 755 | { 756 | RemoveTargetHighlightedRenderers(target.Key); 757 | } 758 | 759 | //In case any are still left, such as if they changed parents or what not when they were highlighted. 760 | renderersBuffer.Clear(); 761 | renderersBuffer.AddRange(highlightedRenderers); 762 | RemoveHighlightedRenderers(renderersBuffer); 763 | } 764 | 765 | void RemoveTargetHighlightedRenderers(Transform target) 766 | { 767 | GetTargetRenderers(target, renderersBuffer); 768 | 769 | RemoveHighlightedRenderers(renderersBuffer); 770 | } 771 | 772 | void RemoveHighlightedRenderers(List renderers) 773 | { 774 | for(int i = 0; i < renderersBuffer.Count; i++) 775 | { 776 | Renderer render = renderersBuffer[i]; 777 | if(render != null) 778 | { 779 | materialsBuffer.Clear(); 780 | materialsBuffer.AddRange(render.sharedMaterials); 781 | 782 | if(materialsBuffer.Contains(outlineMaterial)) 783 | { 784 | materialsBuffer.Remove(outlineMaterial); 785 | render.materials = materialsBuffer.ToArray(); 786 | } 787 | } 788 | 789 | highlightedRenderers.Remove(render); 790 | } 791 | 792 | renderersBuffer.Clear(); 793 | } 794 | 795 | void AddTargetRoot(Transform targetRoot) 796 | { 797 | targetRoots.Add(targetRoot, new TargetInfo()); 798 | targetRootsOrdered.Add(targetRoot); 799 | 800 | AddAllChildren(targetRoot); 801 | } 802 | void RemoveTargetRoot(Transform targetRoot) 803 | { 804 | if(targetRoots.Remove(targetRoot)) 805 | { 806 | targetRootsOrdered.Remove(targetRoot); 807 | 808 | RemoveAllChildren(targetRoot); 809 | } 810 | } 811 | 812 | void AddAllChildren(Transform target) 813 | { 814 | childrenBuffer.Clear(); 815 | target.GetComponentsInChildren(true, childrenBuffer); 816 | childrenBuffer.Remove(target); 817 | 818 | for(int i = 0; i < childrenBuffer.Count; i++) 819 | { 820 | Transform child = childrenBuffer[i]; 821 | children.Add(child); 822 | RemoveTargetRoot(child); //We do this in case we selected child first and then the parent. 823 | } 824 | 825 | childrenBuffer.Clear(); 826 | } 827 | void RemoveAllChildren(Transform target) 828 | { 829 | childrenBuffer.Clear(); 830 | target.GetComponentsInChildren(true, childrenBuffer); 831 | childrenBuffer.Remove(target); 832 | 833 | for(int i = 0; i < childrenBuffer.Count; i++) 834 | { 835 | children.Remove(childrenBuffer[i]); 836 | } 837 | 838 | childrenBuffer.Clear(); 839 | } 840 | 841 | public void SetPivotPoint() 842 | { 843 | if(mainTargetRoot != null) 844 | { 845 | if(pivot == TransformPivot.Pivot) 846 | { 847 | pivotPoint = mainTargetRoot.position; 848 | } 849 | else if(pivot == TransformPivot.Center) 850 | { 851 | totalCenterPivotPoint = Vector3.zero; 852 | 853 | Dictionary.Enumerator targetsEnumerator = targetRoots.GetEnumerator(); //We avoid foreach to avoid garbage. 854 | while(targetsEnumerator.MoveNext()) 855 | { 856 | Transform target = targetsEnumerator.Current.Key; 857 | TargetInfo info = targetsEnumerator.Current.Value; 858 | info.centerPivotPoint = target.GetCenter(centerType); 859 | 860 | totalCenterPivotPoint += info.centerPivotPoint; 861 | } 862 | 863 | totalCenterPivotPoint /= targetRoots.Count; 864 | 865 | if(centerType == CenterType.Solo) 866 | { 867 | pivotPoint = targetRoots[mainTargetRoot].centerPivotPoint; 868 | } 869 | else if(centerType == CenterType.All) 870 | { 871 | pivotPoint = totalCenterPivotPoint; 872 | } 873 | } 874 | } 875 | } 876 | void SetPivotPointOffset(Vector3 offset) 877 | { 878 | pivotPoint += offset; 879 | totalCenterPivotPoint += offset; 880 | } 881 | 882 | 883 | IEnumerator ForceUpdatePivotPointAtEndOfFrame() 884 | { 885 | while(this.enabled) 886 | { 887 | ForceUpdatePivotPointOnChange(); 888 | yield return waitForEndOFFrame; 889 | } 890 | } 891 | 892 | void ForceUpdatePivotPointOnChange() 893 | { 894 | if(forceUpdatePivotPointOnChange) 895 | { 896 | if(mainTargetRoot != null && !isTransforming) 897 | { 898 | bool hasSet = false; 899 | Dictionary.Enumerator targets = targetRoots.GetEnumerator(); 900 | while(targets.MoveNext()) 901 | { 902 | if(!hasSet) 903 | { 904 | if(targets.Current.Value.previousPosition != Vector3.zero && targets.Current.Key.position != targets.Current.Value.previousPosition) 905 | { 906 | SetPivotPoint(); 907 | hasSet = true; 908 | } 909 | } 910 | 911 | targets.Current.Value.previousPosition = targets.Current.Key.position; 912 | } 913 | } 914 | } 915 | } 916 | 917 | public void SetTranslatingAxis(TransformType type, Axis axis, Axis planeAxis = Axis.None) 918 | { 919 | this.translatingType = type; 920 | this.nearAxis = axis; 921 | this.planeAxis = planeAxis; 922 | } 923 | 924 | public AxisInfo GetAxisInfo() 925 | { 926 | AxisInfo currentAxisInfo = axisInfo; 927 | 928 | if(isTransforming && GetProperTransformSpace() == TransformSpace.Global && translatingType == TransformType.Rotate) 929 | { 930 | currentAxisInfo.xDirection = totalRotationAmount * Vector3.right; 931 | currentAxisInfo.yDirection = totalRotationAmount * Vector3.up; 932 | currentAxisInfo.zDirection = totalRotationAmount * Vector3.forward; 933 | } 934 | 935 | return currentAxisInfo; 936 | } 937 | 938 | void SetNearAxis() 939 | { 940 | if(isTransforming) return; 941 | 942 | SetTranslatingAxis(transformType, Axis.None); 943 | 944 | if(mainTargetRoot == null) return; 945 | 946 | float distanceMultiplier = GetDistanceMultiplier(); 947 | float handleMinSelectedDistanceCheck = (this.minSelectedDistanceCheck + handleWidth) * distanceMultiplier; 948 | 949 | if(nearAxis == Axis.None && (TransformTypeContains(TransformType.Move) || TransformTypeContains(TransformType.Scale))) 950 | { 951 | //Important to check scale lines before move lines since in TransformType.All the move planes would block the scales center scale all gizmo. 952 | if(nearAxis == Axis.None && TransformTypeContains(TransformType.Scale)) 953 | { 954 | float tipMinSelectedDistanceCheck = (this.minSelectedDistanceCheck + boxSize) * distanceMultiplier; 955 | HandleNearestPlanes(TransformType.Scale, handleSquares, tipMinSelectedDistanceCheck); 956 | } 957 | 958 | if(nearAxis == Axis.None && TransformTypeContains(TransformType.Move)) 959 | { 960 | //Important to check the planes first before the handle tip since it makes selecting the planes easier. 961 | float planeMinSelectedDistanceCheck = (this.minSelectedDistanceCheck + planeSize) * distanceMultiplier; 962 | HandleNearestPlanes(TransformType.Move, handlePlanes, planeMinSelectedDistanceCheck); 963 | 964 | if(nearAxis != Axis.None) 965 | { 966 | planeAxis = nearAxis; 967 | } 968 | else 969 | { 970 | float tipMinSelectedDistanceCheck = (this.minSelectedDistanceCheck + triangleSize) * distanceMultiplier; 971 | HandleNearestLines(TransformType.Move, handleTriangles, tipMinSelectedDistanceCheck); 972 | } 973 | } 974 | 975 | if(nearAxis == Axis.None) 976 | { 977 | //Since Move and Scale share the same handle line, we give Move the priority. 978 | TransformType transType = transformType == TransformType.All ? TransformType.Move : transformType; 979 | HandleNearestLines(transType, handleLines, handleMinSelectedDistanceCheck); 980 | } 981 | } 982 | 983 | if(nearAxis == Axis.None && TransformTypeContains(TransformType.Rotate)) 984 | { 985 | HandleNearestLines(TransformType.Rotate, circlesLines, handleMinSelectedDistanceCheck); 986 | } 987 | } 988 | 989 | void HandleNearestLines(TransformType type, AxisVectors axisVectors, float minSelectedDistanceCheck) 990 | { 991 | float xClosestDistance = ClosestDistanceFromMouseToLines(axisVectors.x); 992 | float yClosestDistance = ClosestDistanceFromMouseToLines(axisVectors.y); 993 | float zClosestDistance = ClosestDistanceFromMouseToLines(axisVectors.z); 994 | float allClosestDistance = ClosestDistanceFromMouseToLines(axisVectors.all); 995 | 996 | HandleNearest(type, xClosestDistance, yClosestDistance, zClosestDistance, allClosestDistance, minSelectedDistanceCheck); 997 | } 998 | 999 | void HandleNearestPlanes(TransformType type, AxisVectors axisVectors, float minSelectedDistanceCheck) 1000 | { 1001 | float xClosestDistance = ClosestDistanceFromMouseToPlanes(axisVectors.x); 1002 | float yClosestDistance = ClosestDistanceFromMouseToPlanes(axisVectors.y); 1003 | float zClosestDistance = ClosestDistanceFromMouseToPlanes(axisVectors.z); 1004 | float allClosestDistance = ClosestDistanceFromMouseToPlanes(axisVectors.all); 1005 | 1006 | HandleNearest(type, xClosestDistance, yClosestDistance, zClosestDistance, allClosestDistance, minSelectedDistanceCheck); 1007 | } 1008 | 1009 | void HandleNearest(TransformType type, float xClosestDistance, float yClosestDistance, float zClosestDistance, float allClosestDistance, float minSelectedDistanceCheck) 1010 | { 1011 | if(type == TransformType.Scale && allClosestDistance <= minSelectedDistanceCheck) SetTranslatingAxis(type, Axis.Any); 1012 | else if(xClosestDistance <= minSelectedDistanceCheck && xClosestDistance <= yClosestDistance && xClosestDistance <= zClosestDistance) SetTranslatingAxis(type, Axis.X); 1013 | else if(yClosestDistance <= minSelectedDistanceCheck && yClosestDistance <= xClosestDistance && yClosestDistance <= zClosestDistance) SetTranslatingAxis(type, Axis.Y); 1014 | else if(zClosestDistance <= minSelectedDistanceCheck && zClosestDistance <= xClosestDistance && zClosestDistance <= yClosestDistance) SetTranslatingAxis(type, Axis.Z); 1015 | else if(type == TransformType.Rotate && mainTargetRoot != null) 1016 | { 1017 | Ray mouseRay = myCamera.ScreenPointToRay(Input.mousePosition); 1018 | Vector3 mousePlaneHit = Geometry.LinePlaneIntersect(mouseRay.origin, mouseRay.direction, pivotPoint, (transform.position - pivotPoint).normalized); 1019 | if((pivotPoint - mousePlaneHit).sqrMagnitude <= (GetHandleLength(TransformType.Rotate)).Squared()) SetTranslatingAxis(type, Axis.Any); 1020 | } 1021 | } 1022 | 1023 | float ClosestDistanceFromMouseToLines(List lines) 1024 | { 1025 | Ray mouseRay = myCamera.ScreenPointToRay(Input.mousePosition); 1026 | 1027 | float closestDistance = float.MaxValue; 1028 | for(int i = 0; i + 1 < lines.Count; i++) 1029 | { 1030 | IntersectPoints points = Geometry.ClosestPointsOnSegmentToLine(lines[i], lines[i + 1], mouseRay.origin, mouseRay.direction); 1031 | float distance = Vector3.Distance(points.first, points.second); 1032 | if(distance < closestDistance) 1033 | { 1034 | closestDistance = distance; 1035 | } 1036 | } 1037 | return closestDistance; 1038 | } 1039 | 1040 | float ClosestDistanceFromMouseToPlanes(List planePoints) 1041 | { 1042 | float closestDistance = float.MaxValue; 1043 | 1044 | if(planePoints.Count >= 4) 1045 | { 1046 | Ray mouseRay = myCamera.ScreenPointToRay(Input.mousePosition); 1047 | 1048 | for(int i = 0; i < planePoints.Count; i += 4) 1049 | { 1050 | Plane plane = new Plane(planePoints[i], planePoints[i + 1], planePoints[i + 2]); 1051 | 1052 | float distanceToPlane; 1053 | if(plane.Raycast(mouseRay, out distanceToPlane)) 1054 | { 1055 | Vector3 pointOnPlane = mouseRay.origin + (mouseRay.direction * distanceToPlane); 1056 | Vector3 planeCenter = (planePoints[0] + planePoints[1] + planePoints[2] + planePoints[3]) / 4f; 1057 | 1058 | float distance = Vector3.Distance(planeCenter, pointOnPlane); 1059 | if(distance < closestDistance) 1060 | { 1061 | closestDistance = distance; 1062 | } 1063 | } 1064 | } 1065 | } 1066 | 1067 | return closestDistance; 1068 | } 1069 | 1070 | //float DistanceFromMouseToPlane(List planeLines) 1071 | //{ 1072 | // if(planeLines.Count >= 4) 1073 | // { 1074 | // Ray mouseRay = myCamera.ScreenPointToRay(Input.mousePosition); 1075 | // Plane plane = new Plane(planeLines[0], planeLines[1], planeLines[2]); 1076 | 1077 | // float distanceToPlane; 1078 | // if(plane.Raycast(mouseRay, out distanceToPlane)) 1079 | // { 1080 | // Vector3 pointOnPlane = mouseRay.origin + (mouseRay.direction * distanceToPlane); 1081 | // Vector3 planeCenter = (planeLines[0] + planeLines[1] + planeLines[2] + planeLines[3]) / 4f; 1082 | 1083 | // return Vector3.Distance(planeCenter, pointOnPlane); 1084 | // } 1085 | // } 1086 | 1087 | // return float.MaxValue; 1088 | //} 1089 | 1090 | void SetAxisInfo() 1091 | { 1092 | if(mainTargetRoot != null) 1093 | { 1094 | axisInfo.Set(mainTargetRoot, pivotPoint, GetProperTransformSpace()); 1095 | } 1096 | } 1097 | 1098 | //This helps keep the size consistent no matter how far we are from it. 1099 | public float GetDistanceMultiplier() 1100 | { 1101 | if(mainTargetRoot == null) return 0f; 1102 | 1103 | if(myCamera.orthographic) return Mathf.Max(.01f, myCamera.orthographicSize * 2f); 1104 | return Mathf.Max(.01f, Mathf.Abs(ExtVector3.MagnitudeInDirection(pivotPoint - transform.position, myCamera.transform.forward))); 1105 | } 1106 | 1107 | void SetLines() 1108 | { 1109 | SetHandleLines(); 1110 | SetHandlePlanes(); 1111 | SetHandleTriangles(); 1112 | SetHandleSquares(); 1113 | SetCircles(GetAxisInfo(), circlesLines); 1114 | } 1115 | 1116 | void SetHandleLines() 1117 | { 1118 | handleLines.Clear(); 1119 | 1120 | if(TranslatingTypeContains(TransformType.Move) || TranslatingTypeContains(TransformType.Scale)) 1121 | { 1122 | float lineWidth = handleWidth * GetDistanceMultiplier(); 1123 | 1124 | float xLineLength = 0; 1125 | float yLineLength = 0; 1126 | float zLineLength = 0; 1127 | if(TranslatingTypeContains(TransformType.Move)) 1128 | { 1129 | xLineLength = yLineLength = zLineLength = GetHandleLength(TransformType.Move); 1130 | } 1131 | else if(TranslatingTypeContains(TransformType.Scale)) 1132 | { 1133 | xLineLength = GetHandleLength(TransformType.Scale, Axis.X); 1134 | yLineLength = GetHandleLength(TransformType.Scale, Axis.Y); 1135 | zLineLength = GetHandleLength(TransformType.Scale, Axis.Z); 1136 | } 1137 | 1138 | AddQuads(pivotPoint, axisInfo.xDirection, axisInfo.yDirection, axisInfo.zDirection, xLineLength, lineWidth, handleLines.x); 1139 | AddQuads(pivotPoint, axisInfo.yDirection, axisInfo.xDirection, axisInfo.zDirection, yLineLength, lineWidth, handleLines.y); 1140 | AddQuads(pivotPoint, axisInfo.zDirection, axisInfo.xDirection, axisInfo.yDirection, zLineLength, lineWidth, handleLines.z); 1141 | } 1142 | } 1143 | int AxisDirectionMultiplier(Vector3 direction, Vector3 otherDirection) 1144 | { 1145 | return ExtVector3.IsInDirection(direction, otherDirection) ? 1 : -1; 1146 | } 1147 | 1148 | void SetHandlePlanes() 1149 | { 1150 | handlePlanes.Clear(); 1151 | 1152 | if(TranslatingTypeContains(TransformType.Move)) 1153 | { 1154 | Vector3 pivotToCamera = myCamera.transform.position - pivotPoint; 1155 | float cameraXSign = Mathf.Sign(Vector3.Dot(axisInfo.xDirection, pivotToCamera)); 1156 | float cameraYSign = Mathf.Sign(Vector3.Dot(axisInfo.yDirection, pivotToCamera)); 1157 | float cameraZSign = Mathf.Sign(Vector3.Dot(axisInfo.zDirection, pivotToCamera)); 1158 | 1159 | float planeSize = this.planeSize; 1160 | if(transformType == TransformType.All) { planeSize *= allMoveHandleLengthMultiplier; } 1161 | planeSize *= GetDistanceMultiplier(); 1162 | 1163 | Vector3 xDirection = (axisInfo.xDirection * planeSize) * cameraXSign; 1164 | Vector3 yDirection = (axisInfo.yDirection * planeSize) * cameraYSign; 1165 | Vector3 zDirection = (axisInfo.zDirection * planeSize) * cameraZSign; 1166 | 1167 | Vector3 xPlaneCenter = pivotPoint + (yDirection + zDirection); 1168 | Vector3 yPlaneCenter = pivotPoint + (xDirection + zDirection); 1169 | Vector3 zPlaneCenter = pivotPoint + (xDirection + yDirection); 1170 | 1171 | AddQuad(xPlaneCenter, axisInfo.yDirection, axisInfo.zDirection, planeSize, handlePlanes.x); 1172 | AddQuad(yPlaneCenter, axisInfo.xDirection, axisInfo.zDirection, planeSize, handlePlanes.y); 1173 | AddQuad(zPlaneCenter, axisInfo.xDirection, axisInfo.yDirection, planeSize, handlePlanes.z); 1174 | } 1175 | } 1176 | 1177 | void SetHandleTriangles() 1178 | { 1179 | handleTriangles.Clear(); 1180 | 1181 | if(TranslatingTypeContains(TransformType.Move)) 1182 | { 1183 | float triangleLength = triangleSize * GetDistanceMultiplier(); 1184 | AddTriangles(axisInfo.GetXAxisEnd(GetHandleLength(TransformType.Move)), axisInfo.xDirection, axisInfo.yDirection, axisInfo.zDirection, triangleLength, handleTriangles.x); 1185 | AddTriangles(axisInfo.GetYAxisEnd(GetHandleLength(TransformType.Move)), axisInfo.yDirection, axisInfo.xDirection, axisInfo.zDirection, triangleLength, handleTriangles.y); 1186 | AddTriangles(axisInfo.GetZAxisEnd(GetHandleLength(TransformType.Move)), axisInfo.zDirection, axisInfo.yDirection, axisInfo.xDirection, triangleLength, handleTriangles.z); 1187 | } 1188 | } 1189 | 1190 | void AddTriangles(Vector3 axisEnd, Vector3 axisDirection, Vector3 axisOtherDirection1, Vector3 axisOtherDirection2, float size, List resultsBuffer) 1191 | { 1192 | Vector3 endPoint = axisEnd + (axisDirection * (size * 2f)); 1193 | Square baseSquare = GetBaseSquare(axisEnd, axisOtherDirection1, axisOtherDirection2, size / 2f); 1194 | 1195 | resultsBuffer.Add(baseSquare.bottomLeft); 1196 | resultsBuffer.Add(baseSquare.topLeft); 1197 | resultsBuffer.Add(baseSquare.topRight); 1198 | resultsBuffer.Add(baseSquare.topLeft); 1199 | resultsBuffer.Add(baseSquare.bottomRight); 1200 | resultsBuffer.Add(baseSquare.topRight); 1201 | 1202 | for(int i = 0; i < 4; i++) 1203 | { 1204 | resultsBuffer.Add(baseSquare[i]); 1205 | resultsBuffer.Add(baseSquare[i + 1]); 1206 | resultsBuffer.Add(endPoint); 1207 | } 1208 | } 1209 | 1210 | void SetHandleSquares() 1211 | { 1212 | handleSquares.Clear(); 1213 | 1214 | if(TranslatingTypeContains(TransformType.Scale)) 1215 | { 1216 | float boxSize = this.boxSize * GetDistanceMultiplier(); 1217 | AddSquares(axisInfo.GetXAxisEnd(GetHandleLength(TransformType.Scale, Axis.X)), axisInfo.xDirection, axisInfo.yDirection, axisInfo.zDirection, boxSize, handleSquares.x); 1218 | AddSquares(axisInfo.GetYAxisEnd(GetHandleLength(TransformType.Scale, Axis.Y)), axisInfo.yDirection, axisInfo.xDirection, axisInfo.zDirection, boxSize, handleSquares.y); 1219 | AddSquares(axisInfo.GetZAxisEnd(GetHandleLength(TransformType.Scale, Axis.Z)), axisInfo.zDirection, axisInfo.xDirection, axisInfo.yDirection, boxSize, handleSquares.z); 1220 | AddSquares(pivotPoint - (axisInfo.xDirection * (boxSize * .5f)), axisInfo.xDirection, axisInfo.yDirection, axisInfo.zDirection, boxSize, handleSquares.all); 1221 | } 1222 | } 1223 | 1224 | void AddSquares(Vector3 axisStart, Vector3 axisDirection, Vector3 axisOtherDirection1, Vector3 axisOtherDirection2, float size, List resultsBuffer) 1225 | { 1226 | AddQuads(axisStart, axisDirection, axisOtherDirection1, axisOtherDirection2, size, size * .5f, resultsBuffer); 1227 | } 1228 | void AddQuads(Vector3 axisStart, Vector3 axisDirection, Vector3 axisOtherDirection1, Vector3 axisOtherDirection2, float length, float width, List resultsBuffer) 1229 | { 1230 | Vector3 axisEnd = axisStart + (axisDirection * length); 1231 | AddQuads(axisStart, axisEnd, axisOtherDirection1, axisOtherDirection2, width, resultsBuffer); 1232 | } 1233 | void AddQuads(Vector3 axisStart, Vector3 axisEnd, Vector3 axisOtherDirection1, Vector3 axisOtherDirection2, float width, List resultsBuffer) 1234 | { 1235 | Square baseRectangle = GetBaseSquare(axisStart, axisOtherDirection1, axisOtherDirection2, width); 1236 | Square baseRectangleEnd = GetBaseSquare(axisEnd, axisOtherDirection1, axisOtherDirection2, width); 1237 | 1238 | resultsBuffer.Add(baseRectangle.bottomLeft); 1239 | resultsBuffer.Add(baseRectangle.topLeft); 1240 | resultsBuffer.Add(baseRectangle.topRight); 1241 | resultsBuffer.Add(baseRectangle.bottomRight); 1242 | 1243 | resultsBuffer.Add(baseRectangleEnd.bottomLeft); 1244 | resultsBuffer.Add(baseRectangleEnd.topLeft); 1245 | resultsBuffer.Add(baseRectangleEnd.topRight); 1246 | resultsBuffer.Add(baseRectangleEnd.bottomRight); 1247 | 1248 | for(int i = 0; i < 4; i++) 1249 | { 1250 | resultsBuffer.Add(baseRectangle[i]); 1251 | resultsBuffer.Add(baseRectangleEnd[i]); 1252 | resultsBuffer.Add(baseRectangleEnd[i + 1]); 1253 | resultsBuffer.Add(baseRectangle[i + 1]); 1254 | } 1255 | } 1256 | 1257 | void AddQuad(Vector3 axisStart, Vector3 axisOtherDirection1, Vector3 axisOtherDirection2, float width, List resultsBuffer) 1258 | { 1259 | Square baseRectangle = GetBaseSquare(axisStart, axisOtherDirection1, axisOtherDirection2, width); 1260 | 1261 | resultsBuffer.Add(baseRectangle.bottomLeft); 1262 | resultsBuffer.Add(baseRectangle.topLeft); 1263 | resultsBuffer.Add(baseRectangle.topRight); 1264 | resultsBuffer.Add(baseRectangle.bottomRight); 1265 | } 1266 | 1267 | Square GetBaseSquare(Vector3 axisEnd, Vector3 axisOtherDirection1, Vector3 axisOtherDirection2, float size) 1268 | { 1269 | Square square; 1270 | Vector3 offsetUp = ((axisOtherDirection1 * size) + (axisOtherDirection2 * size)); 1271 | Vector3 offsetDown = ((axisOtherDirection1 * size) - (axisOtherDirection2 * size)); 1272 | //These might not really be the proper directions, as in the bottomLeft might not really be at the bottom left... 1273 | square.bottomLeft = axisEnd + offsetDown; 1274 | square.topLeft = axisEnd + offsetUp; 1275 | square.bottomRight = axisEnd - offsetUp; 1276 | square.topRight = axisEnd - offsetDown; 1277 | return square; 1278 | } 1279 | 1280 | void SetCircles(AxisInfo axisInfo, AxisVectors axisVectors) 1281 | { 1282 | axisVectors.Clear(); 1283 | 1284 | if(TranslatingTypeContains(TransformType.Rotate)) 1285 | { 1286 | float circleLength = GetHandleLength(TransformType.Rotate); 1287 | AddCircle(pivotPoint, axisInfo.xDirection, circleLength, axisVectors.x); 1288 | AddCircle(pivotPoint, axisInfo.yDirection, circleLength, axisVectors.y); 1289 | AddCircle(pivotPoint, axisInfo.zDirection, circleLength, axisVectors.z); 1290 | AddCircle(pivotPoint, (pivotPoint - transform.position).normalized, circleLength, axisVectors.all, false); 1291 | } 1292 | } 1293 | 1294 | void AddCircle(Vector3 origin, Vector3 axisDirection, float size, List resultsBuffer, bool depthTest = true) 1295 | { 1296 | Vector3 up = axisDirection.normalized * size; 1297 | Vector3 forward = Vector3.Slerp(up, -up, .5f); 1298 | Vector3 right = Vector3.Cross(up, forward).normalized * size; 1299 | 1300 | Matrix4x4 matrix = new Matrix4x4(); 1301 | 1302 | matrix[0] = right.x; 1303 | matrix[1] = right.y; 1304 | matrix[2] = right.z; 1305 | 1306 | matrix[4] = up.x; 1307 | matrix[5] = up.y; 1308 | matrix[6] = up.z; 1309 | 1310 | matrix[8] = forward.x; 1311 | matrix[9] = forward.y; 1312 | matrix[10] = forward.z; 1313 | 1314 | Vector3 lastPoint = origin + matrix.MultiplyPoint3x4(new Vector3(Mathf.Cos(0), 0, Mathf.Sin(0))); 1315 | Vector3 nextPoint = Vector3.zero; 1316 | float multiplier = 360f / circleDetail; 1317 | 1318 | Plane plane = new Plane((transform.position - pivotPoint).normalized, pivotPoint); 1319 | 1320 | float circleHandleWidth = handleWidth * GetDistanceMultiplier(); 1321 | 1322 | for(int i = 0; i < circleDetail + 1; i++) 1323 | { 1324 | nextPoint.x = Mathf.Cos((i * multiplier) * Mathf.Deg2Rad); 1325 | nextPoint.z = Mathf.Sin((i * multiplier) * Mathf.Deg2Rad); 1326 | nextPoint.y = 0; 1327 | 1328 | nextPoint = origin + matrix.MultiplyPoint3x4(nextPoint); 1329 | 1330 | if(!depthTest || plane.GetSide(lastPoint)) 1331 | { 1332 | Vector3 centerPoint = (lastPoint + nextPoint) * .5f; 1333 | Vector3 upDirection = (centerPoint - origin).normalized; 1334 | AddQuads(lastPoint, nextPoint, upDirection, axisDirection, circleHandleWidth, resultsBuffer); 1335 | } 1336 | 1337 | lastPoint = nextPoint; 1338 | } 1339 | } 1340 | 1341 | void DrawLines(List lines, Color color) 1342 | { 1343 | if(lines.Count == 0) return; 1344 | 1345 | GL.Begin(GL.LINES); 1346 | GL.Color(color); 1347 | 1348 | for(int i = 0; i < lines.Count; i += 2) 1349 | { 1350 | GL.Vertex(lines[i]); 1351 | GL.Vertex(lines[i + 1]); 1352 | } 1353 | 1354 | GL.End(); 1355 | } 1356 | 1357 | void DrawTriangles(List lines, Color color) 1358 | { 1359 | if(lines.Count == 0) return; 1360 | 1361 | GL.Begin(GL.TRIANGLES); 1362 | GL.Color(color); 1363 | 1364 | for(int i = 0; i < lines.Count; i += 3) 1365 | { 1366 | GL.Vertex(lines[i]); 1367 | GL.Vertex(lines[i + 1]); 1368 | GL.Vertex(lines[i + 2]); 1369 | } 1370 | 1371 | GL.End(); 1372 | } 1373 | 1374 | void DrawQuads(List lines, Color color) 1375 | { 1376 | if(lines.Count == 0) return; 1377 | 1378 | GL.Begin(GL.QUADS); 1379 | GL.Color(color); 1380 | 1381 | for(int i = 0; i < lines.Count; i += 4) 1382 | { 1383 | GL.Vertex(lines[i]); 1384 | GL.Vertex(lines[i + 1]); 1385 | GL.Vertex(lines[i + 2]); 1386 | GL.Vertex(lines[i + 3]); 1387 | } 1388 | 1389 | GL.End(); 1390 | } 1391 | 1392 | void DrawFilledCircle(List lines, Color color) 1393 | { 1394 | if(lines.Count == 0) return; 1395 | 1396 | Vector3 center = Vector3.zero; 1397 | for(int i = 0; i < lines.Count; i++) 1398 | { 1399 | center += lines[i]; 1400 | } 1401 | center /= lines.Count; 1402 | 1403 | GL.Begin(GL.TRIANGLES); 1404 | GL.Color(color); 1405 | 1406 | for(int i = 0; i + 1 < lines.Count; i++) 1407 | { 1408 | GL.Vertex(lines[i]); 1409 | GL.Vertex(lines[i + 1]); 1410 | GL.Vertex(center); 1411 | } 1412 | 1413 | GL.End(); 1414 | } 1415 | 1416 | void SetMaterial() 1417 | { 1418 | if(lineMaterial == null) 1419 | { 1420 | lineMaterial = new Material(Shader.Find("Custom/Lines")); 1421 | outlineMaterial = new Material(Shader.Find("Custom/Outline")); 1422 | } 1423 | } 1424 | } 1425 | } 1426 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/TransformGizmo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1c0ad0e8e55c95c4e931911c4b943a64 3 | timeCreated: 1476929157 4 | licenseType: Free 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/UndoRedo/CommandGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CommandUndoRedo 5 | { 6 | public class CommandGroup : ICommand 7 | { 8 | List commands = new List(); 9 | 10 | public CommandGroup() {} 11 | public CommandGroup(List commands) 12 | { 13 | this.commands.AddRange(commands); 14 | } 15 | 16 | public void Set(List commands) 17 | { 18 | this.commands = commands; 19 | } 20 | 21 | public void Add(ICommand command) 22 | { 23 | commands.Add(command); 24 | } 25 | 26 | public void Remove(ICommand command) 27 | { 28 | commands.Remove(command); 29 | } 30 | 31 | public void Clear() 32 | { 33 | commands.Clear(); 34 | } 35 | 36 | public void Execute() 37 | { 38 | for(int i = 0; i < commands.Count; i++) 39 | { 40 | commands[i].Execute(); 41 | } 42 | } 43 | 44 | public void UnExecute() 45 | { 46 | for(int i = commands.Count - 1; i >= 0; i--) 47 | { 48 | commands[i].UnExecute(); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/UndoRedo/DropoutStack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CommandUndoRedo 5 | { 6 | public class DropoutStack : LinkedList 7 | { 8 | int _maxLength = int.MaxValue; 9 | public int maxLength {get {return _maxLength;} set {SetMaxLength(value);}} 10 | 11 | public DropoutStack() {} 12 | public DropoutStack(int maxLength) 13 | { 14 | this.maxLength = maxLength; 15 | } 16 | 17 | public void Push(T item) 18 | { 19 | if(this.Count > 0 && this.Count + 1 > maxLength) 20 | { 21 | this.RemoveLast(); 22 | } 23 | 24 | if(this.Count + 1 <= maxLength) 25 | { 26 | this.AddFirst(item); 27 | } 28 | } 29 | 30 | public T Pop() 31 | { 32 | T item = this.First.Value; 33 | this.RemoveFirst(); 34 | return item; 35 | } 36 | 37 | void SetMaxLength(int max) 38 | { 39 | _maxLength = max; 40 | 41 | if(this.Count > _maxLength) 42 | { 43 | int leftover = this.Count - _maxLength; 44 | for(int i = 0; i < leftover; i++) 45 | { 46 | this.RemoveLast(); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/UndoRedo/ICommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CommandUndoRedo 4 | { 5 | public interface ICommand 6 | { 7 | void Execute(); 8 | void UnExecute(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/UndoRedo/UndoRedo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CommandUndoRedo 5 | { 6 | public class UndoRedo 7 | { 8 | public int maxUndoStored {get {return undoCommands.maxLength;} set {SetMaxLength(value);}} 9 | 10 | DropoutStack undoCommands = new DropoutStack(); 11 | DropoutStack redoCommands = new DropoutStack(); 12 | 13 | public UndoRedo() {} 14 | public UndoRedo(int maxUndoStored) 15 | { 16 | this.maxUndoStored = maxUndoStored; 17 | } 18 | 19 | public void Clear() 20 | { 21 | undoCommands.Clear(); 22 | redoCommands.Clear(); 23 | } 24 | 25 | public void Undo() 26 | { 27 | if(undoCommands.Count > 0) 28 | { 29 | ICommand command = undoCommands.Pop(); 30 | command.UnExecute(); 31 | redoCommands.Push(command); 32 | } 33 | } 34 | 35 | public void Redo() 36 | { 37 | if(redoCommands.Count > 0) 38 | { 39 | ICommand command = redoCommands.Pop(); 40 | command.Execute(); 41 | undoCommands.Push(command); 42 | } 43 | } 44 | 45 | public void Insert(ICommand command) 46 | { 47 | if(maxUndoStored <= 0) return; 48 | 49 | undoCommands.Push(command); 50 | redoCommands.Clear(); 51 | } 52 | 53 | public void Execute(ICommand command) 54 | { 55 | command.Execute(); 56 | Insert(command); 57 | } 58 | 59 | void SetMaxLength(int max) 60 | { 61 | undoCommands.maxLength = max; 62 | redoCommands.maxLength = max; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Assets/RuntimeGizmo/UndoRedo/UndoRedoManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CommandUndoRedo 4 | { 5 | public static class UndoRedoManager 6 | { 7 | static UndoRedo undoRedo = new UndoRedo(); 8 | 9 | public static int maxUndoStored {get {return undoRedo.maxUndoStored;} set {undoRedo.maxUndoStored = value;}} 10 | 11 | public static void Clear() 12 | { 13 | undoRedo.Clear(); 14 | } 15 | 16 | public static void Undo() 17 | { 18 | undoRedo.Undo(); 19 | } 20 | 21 | public static void Redo() 22 | { 23 | undoRedo.Redo(); 24 | } 25 | 26 | public static void Insert(ICommand command) 27 | { 28 | undoRedo.Insert(command); 29 | } 30 | 31 | public static void Execute(ICommand command) 32 | { 33 | undoRedo.Execute(command); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 HiddenMonk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity3DRuntimeTransformGizmo 2 | A runtime transform gizmo similar to unitys editor so you can translate (move, rotate, scale) objects at runtime. 3 | 4 | Video demonstration - https://www.youtube.com/watch?v=IUQqhS8tsNo 5 | 6 | cyrilBoucher created a fork that works with Unitys PackageManager - https://github.com/cyrilBoucher/Unity3DRuntimeTransformGizmo/tree/feature/upm-package 7 | 8 | I also just found that unity seems to be working on a runtime editor, though it currently still seems buggy and doesnt feel that great. 9 | https://github.com/Unity-Technologies/giles 10 | _________ 11 | WARNING - There is a bug in unity 5.4 and 5.5 that causes InverseTransformDirection to be affected by scale which will break negative scaling. Not tested, but updating to 5.4.2 should fix it - https://issuetracker.unity3d.com/issues/transformdirection-and-inversetransformdirection-operations-are-affected-by-scale 12 | 13 | I have also ran into a unity bug (Unity 2017.2.0f3 (64-bit)) where when rotating a object (it was skewed, not sure if that matters) it will sometimes not have its mesh collider updated or something, causing me to not be able to click it again in certain spots until I disable and reenable the mesh collider in the inspector. 14 | Reenabling the mesh collider also updated where unity saw the center of the object to be. 15 | Maybe related to this bug? https://issuetracker.unity3d.com/issues/colliders-are-not-updating-to-match-attached-gameobject-location 16 | 17 | There is also a bug where if you have multiple objects selected in the unity editor and then you select the last selected of those selected objects, unity will start throwing the error 18 | "type is not a supported pptr value 19 | UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)" 20 | It seems to be the mesh renderer in the inspector is having issues realizing that we added a new material to it (the highlight selected material). 21 | If you collapse the mesh renderer, the errors will stop. 22 | _________ 23 | 24 | Just place the TransformGizmo on a gameobject with a camera. 25 | Objects must have a collider on them to be selected. 26 | 27 | Could use some work with how the moving is being handled. For example, I dont like how if you try to move something towards the camera, you cant get it to move past you to behind. 28 | 29 | Added a pivot center mode so you can translate based on the Renderer.bounds.center instead of the normal pivot point. 30 | 31 | Good news is that I have the code to replace using temporary parent gameobjects as a pivot with the same accuracy. 32 | Bad news is that it seems using a parent gameobject as a pivot, even when placed at the center of the desired object, does not give the same result as unitys Center mode. 33 | This leaves us wondering what unity is doing differently in Center mode. 34 | Either way, I am going to completely remove the temporary gameobject since its no longer needed, as well as put 2 Scale types that people can switch between. 35 | One scale type called "ScaleFrom" being like using a parent gameobject as a pivot, and one scale type called "ScaleFromOffset" being like unitys Center mode (though a bit inaccurate with skewed objects). 36 | 37 | I talk about this more in this thread https://forum.unity.com/threads/scale-from-a-point-other-than-pivot-almost-working-code.544302/#post-3591286 38 | 39 | 40 | There may also be some differences between unity and how I have things set up currently in regards to Center pivots, 41 | such as unity seems to use colliders first to find how much weight the object has or something to decide how much it effects the center, 42 | but for now we only look at the Renderer.bounds.center, so expect some differences between unity. 43 | 44 | 45 | Also Added the ability to select multiple objects =) 46 | 47 | Added a UndoRedo system. 48 | By default the keys to Undo is Shift+Z and Redo is Shift+Y 49 | We use Shift instead of Control so that while in the editor we dont accidentally undo editor changes (for some reason unity doesnt seem to give us the option to disable editor hotkeys while in play mode? https://issuetracker.unity3d.com/issues/disable-editor-keyboard-shortcuts-while-playing) 50 | 51 | Added ability to give your own custom Gizmo meshes with the TransformGizmoCustomGizmo component. 52 | Just drop the TransformGizmoCustomGizmo in the scene and assign the inspector values. 53 | --------------------------------------------------------------------------------