├── README.md └── Rulers ├── RulerData.cs └── Editor └── RulerEditor.cs /README.md: -------------------------------------------------------------------------------- 1 | # Unity Rulers 2 | A ruler editor to measure distances between game objects in the Scene view. 3 | -------------------------------------------------------------------------------- /Rulers/RulerData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace Loqheart.Utility 6 | { 7 | // Ruler editor creates rulers between transforms and displays an arrow from source to destination along with distance between them 8 | [Serializable] 9 | public class Ruler 10 | { 11 | public bool isVisible = true; 12 | public Transform a; 13 | public Transform b; 14 | public Color color; 15 | public Color textColor; 16 | public bool isLocal = false; 17 | public bool showExDist = false; 18 | public bool showExAngle = false; 19 | public bool isEditingDistance = false; 20 | 21 | public Ruler() 22 | { 23 | } 24 | 25 | public bool isValid 26 | { 27 | get 28 | { 29 | return a != null && b != null; 30 | } 31 | } 32 | 33 | public Vector3 delta 34 | { 35 | get 36 | { 37 | if (isValid) 38 | { 39 | return b.position - a.position; 40 | } 41 | else 42 | { 43 | return Vector3.zero; 44 | } 45 | } 46 | } 47 | 48 | public bool isDistanceNonzero 49 | { 50 | get 51 | { 52 | return isValid && delta.sqrMagnitude > 0f; 53 | } 54 | } 55 | 56 | public Vector3 GetAngles() 57 | { 58 | var angles = Vector3.zero; 59 | var unit = delta.normalized; 60 | 61 | if (isLocal) 62 | { 63 | angles = (Quaternion.Inverse(a.rotation) * Quaternion.LookRotation(unit, b.up)).eulerAngles; 64 | } 65 | else 66 | { 67 | angles = Quaternion.LookRotation(unit, Vector3.up).eulerAngles; 68 | } 69 | 70 | if (angles.x > 180f) 71 | { 72 | angles.x -= 360f; 73 | } 74 | if (angles.y > 180f) 75 | { 76 | angles.y -= 360f; 77 | } 78 | if (angles.z > 180f) 79 | { 80 | angles.z -= 360f; 81 | } 82 | 83 | return angles; 84 | } 85 | } 86 | 87 | // a component to contain the data in the scene for the ruler editor 88 | [Serializable] 89 | public class RulerData : MonoBehaviour 90 | { 91 | public Ruler[] rulers; 92 | 93 | public bool showTooltips = false; 94 | public bool enableShortcuts = true; 95 | 96 | public int fontSize = 14; 97 | public Color textColor = new Color(.1f, .1f, .1f); 98 | public Color rulerColor = new Color(1f, .75f, 0f); 99 | public int rulerThickness = 2; 100 | public int pointSize = 25; 101 | public int arrowSize = 10; 102 | public int precision = 2; 103 | 104 | public Transform filterTransform; 105 | 106 | public RulerData() 107 | { 108 | rulers = new Ruler[0]; 109 | } 110 | 111 | public void Add(Ruler r, int index = -1) 112 | { 113 | var newRulers = new Ruler[rulers.Length + 1]; 114 | 115 | for (int i = 0, j = 0; i < newRulers.Length; ++i) 116 | { 117 | if (index != -1 && i == index) 118 | { 119 | newRulers[i] = r; 120 | } 121 | else 122 | { 123 | if (j < rulers.Length) 124 | { 125 | newRulers[i] = rulers[j]; 126 | ++j; 127 | } 128 | else 129 | { 130 | newRulers[i] = r; 131 | } 132 | } 133 | } 134 | rulers = newRulers; 135 | } 136 | 137 | public void RemoveAt(int removeIndex) 138 | { 139 | var newRulers = new Ruler[rulers.Length - 1]; 140 | for (int i = 0, j = 0; i < rulers.Length; ++i) 141 | { 142 | if (i != removeIndex) 143 | { 144 | newRulers[j] = rulers[i]; 145 | ++j; 146 | } 147 | } 148 | 149 | rulers = newRulers; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Rulers/Editor/RulerEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Text.RegularExpressions; 4 | using System.Collections.Generic; 5 | using UnityEngine; 6 | using UnityEngine.SceneManagement; 7 | using UnityEditor; 8 | using UnityEditor.SceneManagement; 9 | 10 | namespace Loqheart.Utility 11 | { 12 | [CustomEditor(typeof(RulerData))] 13 | public class RulerDataEditor : Editor 14 | { 15 | public override void OnInspectorGUI() 16 | { 17 | EditorGUILayout.HelpBox("RulerData for the Rulers editor to persist your settings per scene. It won't be included in your build.", MessageType.Info); 18 | } 19 | } 20 | 21 | public class RulerEditor : EditorWindow 22 | { 23 | string settingsStr = "Settings"; 24 | string showTooltipsStr = "Tooltips"; 25 | string enableShortcutsStr = "Shortcuts"; 26 | string precisionStr = "0.00"; 27 | 28 | // settings strings 29 | string rulerThicknessStr = "ruler thickness"; 30 | string pointSizeStr = "point size"; 31 | string arrowSizeStr = "arrow size"; 32 | string rulerColorStr = "ruler color"; 33 | string textSizeStr = "text size"; 34 | string textColorStr = "text color"; 35 | string displayPrecisionStr = "display precision"; 36 | 37 | string ButtonStr = "Button"; 38 | string filterStr = "filter"; 39 | 40 | // exdata strings for display in scene view 41 | string deltaXStr = "Δx "; 42 | string deltaYStr = "Δy "; 43 | string deltaZStr = "Δz "; 44 | 45 | string deltaRStr = "Δr "; 46 | string deltaUStr = "Δu "; 47 | string deltaFStr = "Δf "; 48 | 49 | string angleEPStr = "∠p "; 50 | string angleEYStr = "∠y "; 51 | string angleERStr = "∠r "; 52 | 53 | string nlStr = "\n"; 54 | 55 | Color distXColor = new Color(1f, 0f, 1f, 1f); 56 | Color distYColor = new Color(1f, 1f, 0f, 1f); 57 | Color distZColor = new Color(0f, 1f, 1f, 1f); 58 | 59 | Color angleXColor = new Color(1f, 0f, 1f, 0.05f); 60 | Color angleYColor = new Color(1f, 1f, 0f, 0.05f); 61 | Color angleZColor = new Color(0f, 1f, 1f, 0.05f); 62 | 63 | GUIContent resetDataGC; 64 | GUIContent clearFilterGC; 65 | GUIContent visibilityGC; 66 | GUIContent duplicateRulerGC; 67 | 68 | GUIContent exDataIsLocalGC; 69 | GUIContent exDataIsGlobalGC; 70 | GUIContent exDataDistanceGC; 71 | GUIContent exDataAngleGC; 72 | 73 | GUIContent deleteRulerGC; 74 | GUIContent frameGC; 75 | GUIContent addRulerGC; 76 | 77 | 78 | // Editor Window UI 79 | GUIStyle headerStyle; 80 | GUIStyle miniButtonStyle; 81 | GUIStyle boldStyle; 82 | GUIStyle groupFoldoutStyle; 83 | 84 | GUIStyle toolbarStyle; 85 | string[] toolbarStrings; 86 | 87 | GUIStyle toggleStyle; 88 | 89 | GUIStyle labelStyle = new GUIStyle(); 90 | 91 | 92 | GUILayoutOption Width20 = GUILayout.Width(20); 93 | GUILayoutOption Width50 = GUILayout.Width(50); 94 | 95 | // State vars 96 | bool showSettings = false; 97 | Vector2 scrollPos = Vector2.zero; 98 | Transform selectedTransform; 99 | Scene currentScene; 100 | 101 | // for ordered selection 102 | HashSet selectionSet = new HashSet(); 103 | HashSet newSet = new HashSet(); 104 | HashSet deleteSet = new HashSet(); 105 | List selectionOrdered = new List(); 106 | 107 | RulerData data; 108 | 109 | [MenuItem("Window/Rulers")] 110 | public static void DisplayWindow() 111 | { 112 | GetWindow(); 113 | } 114 | 115 | private void OnHierarchyChange() 116 | { 117 | // manualy check to see if the scene has changed 118 | // if so reload the data 119 | if (currentScene != EditorSceneManager.GetActiveScene()) 120 | { 121 | currentScene = EditorSceneManager.GetActiveScene(); 122 | data = null; 123 | selectionSet.Clear(); 124 | newSet.Clear(); 125 | deleteSet.Clear(); 126 | selectionOrdered.Clear(); 127 | CheckInit(); 128 | } 129 | } 130 | 131 | private void OnSelectionChange() 132 | { 133 | newSet.Clear(); 134 | newSet.UnionWith(Selection.gameObjects); 135 | 136 | deleteSet.Clear(); 137 | deleteSet.UnionWith(selectionSet); 138 | deleteSet.ExceptWith(newSet); 139 | foreach (var g in deleteSet) 140 | { 141 | selectionSet.Remove(g); 142 | selectionOrdered.Remove(g); 143 | } 144 | 145 | newSet.ExceptWith(selectionSet); 146 | foreach (var g in newSet) 147 | { 148 | selectionSet.Add(g); 149 | selectionOrdered.Add(g); 150 | } 151 | } 152 | 153 | private void OnDestroy() 154 | { 155 | SceneView.onSceneGUIDelegate -= OnSceneGUI; 156 | data = null; 157 | 158 | selectionSet = null; 159 | newSet = null; 160 | deleteSet = null; 161 | selectionOrdered = null; 162 | } 163 | 164 | void OnEnable() 165 | { 166 | currentScene = EditorSceneManager.GetActiveScene(); 167 | SceneView.onSceneGUIDelegate += OnSceneGUI; 168 | titleContent = new GUIContent("Rulers"); 169 | selectionSet = new HashSet(); 170 | newSet = new HashSet(); 171 | deleteSet = new HashSet(); 172 | selectionOrdered = new List(); 173 | } 174 | 175 | void OnLostFocus() 176 | { 177 | // reset editing of rulers 178 | for (int i = 0; i < data.rulers.Length; ++i) 179 | { 180 | var r = data.rulers[i]; 181 | r.isEditingDistance = false; 182 | } 183 | } 184 | 185 | void ResetData() 186 | { 187 | if (EditorUtility.DisplayDialog("Rulers Reset Data", "Are you sure you want reset?", "Yes", "No")) 188 | { 189 | var rulerGameObject = GameObject.Find("RulerData"); 190 | if (rulerGameObject != null) 191 | { 192 | DestroyImmediate(rulerGameObject); 193 | data = null; 194 | } 195 | 196 | CheckInit(); 197 | } 198 | } 199 | 200 | void InstanceGUIContent() 201 | { 202 | if (data.showTooltips) 203 | { 204 | resetDataGC = new GUIContent("Reset Data", "Removes the rulers in the scene and resets settings."); 205 | clearFilterGC = new GUIContent("x", "clear filter"); 206 | visibilityGC = new GUIContent("", "visibility"); 207 | duplicateRulerGC = new GUIContent("★", "duplicate ruler"); 208 | exDataIsLocalGC = new GUIContent("L", "local coordinates"); 209 | exDataIsGlobalGC = new GUIContent("G", "global coordinates"); 210 | exDataDistanceGC = new GUIContent("Δ", "show component distances"); 211 | exDataAngleGC = new GUIContent("∠", "show component angles"); 212 | deleteRulerGC = new GUIContent("x", "delete ruler"); 213 | frameGC = new GUIContent("/", "frame selected"); 214 | addRulerGC = new GUIContent("+", "add empty ruler,\n or will create from 2 selected objects\n Shortcut: Ctrl + Shift + R"); 215 | } 216 | else 217 | { 218 | resetDataGC = new GUIContent("Reset Data"); 219 | clearFilterGC = new GUIContent("x"); 220 | visibilityGC = new GUIContent(""); 221 | duplicateRulerGC = new GUIContent("★"); 222 | exDataIsLocalGC = new GUIContent("L", ""); 223 | exDataIsGlobalGC = new GUIContent("G", ""); 224 | exDataDistanceGC = new GUIContent("Δ"); 225 | exDataAngleGC = new GUIContent("∠"); 226 | deleteRulerGC = new GUIContent("x"); 227 | frameGC = new GUIContent("/"); 228 | addRulerGC = new GUIContent("+"); 229 | } 230 | } 231 | 232 | void CheckInit() 233 | { 234 | if (data == null) 235 | { 236 | var rulerGameObject = GameObject.Find("RulerData"); 237 | 238 | if (rulerGameObject == null) 239 | { 240 | rulerGameObject = new GameObject("RulerData"); 241 | rulerGameObject.hideFlags = HideFlags.DontUnloadUnusedAsset | HideFlags.DontSaveInBuild; 242 | data = rulerGameObject.AddComponent(); 243 | 244 | if (!Application.isPlaying) 245 | EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); 246 | } 247 | else 248 | { 249 | data = rulerGameObject.GetComponent(); 250 | } 251 | 252 | scrollPos = Vector2.zero; 253 | 254 | headerStyle = new GUIStyle(EditorStyles.label); 255 | headerStyle.alignment = TextAnchor.MiddleCenter; 256 | headerStyle.fontSize = 14; 257 | 258 | miniButtonStyle = new GUIStyle(EditorStyles.miniButton); 259 | 260 | boldStyle = new GUIStyle(EditorStyles.boldLabel); 261 | 262 | groupFoldoutStyle = new GUIStyle(EditorStyles.foldout); 263 | groupFoldoutStyle.fontStyle = FontStyle.Bold; 264 | 265 | labelStyle.fontSize = 14; 266 | labelStyle.fontStyle = FontStyle.Bold; 267 | labelStyle.normal.textColor = new Color(.8f, .8f, .8f); 268 | var backtexture = new Texture2D(1, 1); 269 | backtexture.wrapMode = TextureWrapMode.Repeat; 270 | backtexture.SetPixel(0, 0, new Color(0f, 0f, 0f, 0f)); 271 | backtexture.Apply(); 272 | labelStyle.normal.background = backtexture; 273 | 274 | toolbarStyle = new GUIStyle(EditorStyles.toolbarButton); 275 | toolbarStyle.fontSize = 11; 276 | toolbarStrings = new string[] { "Hide All", "Show All" }; 277 | 278 | toggleStyle = new GUIStyle(EditorStyles.toggle); 279 | toggleStyle.fixedWidth = 20; 280 | 281 | precisionStr = "0." + new string('0', data.precision); 282 | 283 | InstanceGUIContent(); 284 | } 285 | } 286 | 287 | #region Dirty 288 | void MarkDirty() 289 | { 290 | if (!Application.isPlaying) 291 | EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene()); 292 | } 293 | 294 | void CheckDirty(ref T oldVal, T newVal) where T:struct 295 | { 296 | if (!newVal.Equals(oldVal)) 297 | { 298 | oldVal = newVal; 299 | MarkDirty(); 300 | } 301 | } 302 | 303 | #endregion Dirty 304 | 305 | void OnGUI() 306 | { 307 | CheckInit(); 308 | 309 | scrollPos = EditorGUILayout.BeginScrollView(scrollPos); 310 | 311 | #region settings 312 | EditorGUILayout.BeginVertical(GUI.skin.box); 313 | showSettings = EditorGUILayout.Foldout(showSettings, settingsStr, groupFoldoutStyle); 314 | 315 | if (showSettings) 316 | { 317 | EditorGUILayout.BeginHorizontal(); 318 | EditorGUILayout.LabelField(showTooltipsStr, Width50); 319 | var showTooltips = EditorGUILayout.Toggle(data.showTooltips, Width50); 320 | if (showTooltips != data.showTooltips) 321 | { 322 | data.showTooltips = showTooltips; 323 | InstanceGUIContent(); 324 | MarkDirty(); 325 | } 326 | 327 | EditorGUILayout.LabelField(enableShortcutsStr, GUILayout.Width(60)); 328 | var enableShortcuts = EditorGUILayout.Toggle(data.enableShortcuts, Width50); 329 | if (enableShortcuts != data.enableShortcuts) 330 | { 331 | data.enableShortcuts = enableShortcuts; 332 | MarkDirty(); 333 | } 334 | 335 | GUILayout.FlexibleSpace(); 336 | 337 | if (GUILayout.Button(resetDataGC, GUILayout.Width(100))) 338 | { 339 | ResetData(); 340 | } 341 | 342 | EditorGUILayout.EndHorizontal(); 343 | 344 | EditorGUILayout.LabelField(rulerThicknessStr); 345 | CheckDirty(ref data.rulerThickness, EditorGUILayout.IntSlider(data.rulerThickness, 1, 30)); 346 | 347 | EditorGUILayout.LabelField(pointSizeStr); 348 | CheckDirty(ref data.pointSize, EditorGUILayout.IntSlider(data.pointSize, 0, 100)); 349 | 350 | EditorGUILayout.LabelField(arrowSizeStr); 351 | CheckDirty(ref data.arrowSize, EditorGUILayout.IntSlider(data.arrowSize, 0, 50)); 352 | 353 | EditorGUILayout.LabelField(rulerColorStr); 354 | CheckDirty(ref data.rulerColor, EditorGUILayout.ColorField(data.rulerColor)); 355 | 356 | EditorGUILayout.LabelField(textSizeStr); 357 | CheckDirty(ref data.fontSize, EditorGUILayout.IntSlider(data.fontSize, 4, 40)); 358 | labelStyle.fontSize = data.fontSize; 359 | 360 | EditorGUILayout.LabelField(textColorStr); 361 | CheckDirty(ref data.textColor, EditorGUILayout.ColorField(data.textColor)); 362 | labelStyle.normal.textColor = data.textColor; 363 | 364 | EditorGUILayout.LabelField(displayPrecisionStr); 365 | var oldPrecision = data.precision; 366 | CheckDirty(ref data.precision, EditorGUILayout.IntSlider(data.precision, 1, 5)); 367 | if (data.precision != oldPrecision) 368 | { 369 | precisionStr = "0." + new string('0', data.precision); 370 | } 371 | } 372 | 373 | EditorGUILayout.EndVertical(); 374 | #endregion settings 375 | 376 | #region filter 377 | var toolbarSelection = GUILayout.Toolbar(-1, toolbarStrings, toolbarStyle); 378 | switch (toolbarSelection) 379 | { 380 | case 0: 381 | for (int i = 0; i < data.rulers.Length; ++i) 382 | { 383 | data.rulers[i].isVisible = false; 384 | } 385 | MarkDirty(); 386 | break; 387 | case 1: 388 | for (int i = 0; i < data.rulers.Length; ++i) 389 | { 390 | data.rulers[i].isVisible = true; 391 | } 392 | MarkDirty(); 393 | break; 394 | } 395 | 396 | EditorGUILayout.BeginHorizontal(); 397 | EditorGUILayout.LabelField(filterStr, Width50); 398 | var filterTransform = (Transform)EditorGUILayout.ObjectField(data.filterTransform, typeof(Transform), true, GUILayout.ExpandWidth(true)); 399 | if (filterTransform != data.filterTransform) 400 | { 401 | data.filterTransform = filterTransform; 402 | MarkDirty(); 403 | } 404 | 405 | if (GUILayout.Button(clearFilterGC, miniButtonStyle, Width20)) 406 | { 407 | if (data.filterTransform != null) 408 | { 409 | data.filterTransform = null; 410 | MarkDirty(); 411 | } 412 | } 413 | 414 | EditorGUILayout.EndHorizontal(); 415 | #endregion filter 416 | 417 | #region ruler 418 | EditorGUILayout.BeginVertical(); 419 | int removeRulerIndex = -1; 420 | int duplicateRulerIndex = -1; 421 | for (int i = 0; i < data.rulers.Length; ++i) 422 | { 423 | var r = data.rulers[i]; 424 | if (data.filterTransform != null && r.a != data.filterTransform && r.b != data.filterTransform) 425 | { 426 | continue; 427 | } 428 | 429 | EditorGUILayout.BeginVertical(GUI.skin.box); 430 | EditorGUILayout.BeginHorizontal(); 431 | 432 | CheckDirty(ref r.isVisible, GUILayout.Toggle(r.isVisible, visibilityGC, toggleStyle)); 433 | 434 | if (GUILayout.Button(duplicateRulerGC, miniButtonStyle, Width20)) 435 | { 436 | duplicateRulerIndex = i; 437 | } 438 | 439 | CheckDirty(ref r.color, EditorGUILayout.ColorField(r.color, Width50)); 440 | CheckDirty(ref r.textColor, EditorGUILayout.ColorField(r.textColor, Width50)); 441 | 442 | if (GUILayout.Button(r.isLocal ? exDataIsLocalGC : exDataIsGlobalGC, Width20)) 443 | { 444 | r.isLocal = !r.isLocal; 445 | MarkDirty(); 446 | } 447 | 448 | var showExDist = GUILayout.Toggle(r.showExDist, exDataDistanceGC, ButtonStr); 449 | if (showExDist != r.showExDist) 450 | { 451 | r.showExDist = showExDist; 452 | MarkDirty(); 453 | } 454 | 455 | var showExAngle = GUILayout.Toggle(r.showExAngle, exDataAngleGC, ButtonStr); 456 | if (showExAngle != r.showExAngle) 457 | { 458 | r.showExAngle = showExAngle; 459 | MarkDirty(); 460 | } 461 | 462 | GUILayout.FlexibleSpace(); 463 | if (GUILayout.Button(deleteRulerGC, miniButtonStyle, Width20)) 464 | { 465 | removeRulerIndex = i; 466 | } 467 | 468 | EditorGUILayout.EndHorizontal(); 469 | 470 | EditorGUILayout.BeginHorizontal(); 471 | if (GUILayout.Button(frameGC, miniButtonStyle, Width20)) 472 | { 473 | // search for objects 474 | selectedTransform = r.a; 475 | } 476 | var aTransform = (Transform)EditorGUILayout.ObjectField(r.a, typeof(Transform), true); 477 | if (aTransform != r.a) 478 | { 479 | r.a = aTransform; 480 | MarkDirty(); 481 | } 482 | 483 | #region distance editor 484 | if (r.isDistanceNonzero) 485 | { 486 | if (r.isEditingDistance) 487 | { 488 | GUI.SetNextControlName("deltaEditor"); 489 | var oldDistStr = r.delta.magnitude.ToString(); 490 | var newDistStr = EditorGUILayout.TextField(oldDistStr, GUILayout.Width(40)); 491 | newDistStr = Regex.Replace(newDistStr, @"[^0-9.-]", ""); // filter for real numbers only 492 | 493 | if (oldDistStr != newDistStr) 494 | { 495 | float newDist = 0f; 496 | try { 497 | newDist = float.Parse(newDistStr); 498 | } catch(FormatException e) { 499 | // ignore 500 | } 501 | if (newDist != 0f) 502 | { 503 | var newDelta = r.delta.normalized * newDist; 504 | r.b.position = r.a.position + newDelta; 505 | MarkDirty(); 506 | } 507 | } 508 | 509 | if (Event.current.isKey && Event.current.keyCode == KeyCode.Return && GUI.GetNameOfFocusedControl() == "deltaEditor") 510 | { 511 | r.isEditingDistance = false; 512 | } 513 | } 514 | else 515 | { 516 | if (GUILayout.Button(r.delta.magnitude.ToString(precisionStr), boldStyle, GUILayout.Width(40))) 517 | { 518 | r.isEditingDistance = true; 519 | } 520 | } 521 | } 522 | else 523 | { 524 | r.isEditingDistance = false; 525 | EditorGUILayout.LabelField(r.delta.magnitude.ToString(precisionStr), EditorStyles.label, GUILayout.Width(40)); 526 | } 527 | #endregion delta editor 528 | 529 | if (GUILayout.Button(frameGC, miniButtonStyle, Width20)) 530 | { 531 | // search for objects 532 | selectedTransform = r.b; 533 | } 534 | 535 | var bTransform = (Transform)EditorGUILayout.ObjectField(r.b, typeof(Transform), true); 536 | if (bTransform != r.b) 537 | { 538 | r.b = bTransform; 539 | MarkDirty(); 540 | } 541 | EditorGUILayout.EndHorizontal(); 542 | 543 | EditorGUILayout.EndVertical(); 544 | } 545 | #endregion ruler 546 | 547 | if (duplicateRulerIndex != -1) 548 | { 549 | var ruler = data.rulers[duplicateRulerIndex]; 550 | var r = new Ruler(); 551 | r.color = ruler.color; 552 | r.textColor = ruler.textColor; 553 | r.a = ruler.a; 554 | r.b = ruler.b; 555 | data.Add(r, duplicateRulerIndex + 1); 556 | MarkDirty(); 557 | } 558 | 559 | if (removeRulerIndex != -1) 560 | { 561 | data.RemoveAt(removeRulerIndex); 562 | MarkDirty(); 563 | } 564 | 565 | if (GUILayout.Button(addRulerGC, miniButtonStyle)) 566 | { 567 | AddRuler(); 568 | } 569 | 570 | EditorGUILayout.EndVertical(); 571 | 572 | EditorGUILayout.EndScrollView(); 573 | 574 | CheckShortcuts(); 575 | } 576 | 577 | void AddRuler() 578 | { 579 | var r = new Ruler(); 580 | r.color = data.rulerColor; 581 | r.textColor = data.textColor; 582 | if (selectionOrdered.Count > 0) 583 | { 584 | r.a = selectionOrdered[0].transform; 585 | } 586 | 587 | if (selectionOrdered.Count > 1) 588 | { 589 | r.b = selectionOrdered[1].transform; 590 | } 591 | data.Add(r); 592 | MarkDirty(); 593 | } 594 | 595 | void CheckShortcuts() 596 | { 597 | if (!data.enableShortcuts) 598 | { 599 | return; 600 | } 601 | 602 | Event e = Event.current; 603 | if (e.shift && e.control && e.keyCode == KeyCode.R && e.type == EventType.KeyUp) 604 | { 605 | AddRuler(); 606 | } 607 | } 608 | 609 | void OnSceneGUI(SceneView sceneView) 610 | { 611 | if (Event.current.type != EventType.Repaint) 612 | { 613 | return; 614 | } 615 | 616 | CheckInit(); 617 | 618 | CheckShortcuts(); 619 | 620 | Color oldColor = Handles.color; 621 | int controlId = 0; 622 | foreach (var r in data.rulers) 623 | { 624 | if (data.filterTransform != null && r.a != data.filterTransform && r.b != data.filterTransform) 625 | { 626 | continue; 627 | } 628 | 629 | if (!r.isVisible) 630 | { 631 | continue; 632 | } 633 | 634 | if (selectedTransform != null && (selectedTransform == r.a || selectedTransform == r.b)) 635 | { 636 | Selection.objects = new UnityEngine.Object[] { selectedTransform.gameObject }; 637 | SceneView.lastActiveSceneView.FrameSelected(); 638 | selectedTransform = null; 639 | } 640 | 641 | Handles.color = r.color; 642 | 643 | if (r.a != null && r.b != null) 644 | { 645 | Handles.DrawAAPolyLine(data.rulerThickness, new Vector3[] { r.a.position, r.b.position }); 646 | Handles.SphereHandleCap(controlId, r.a.position, r.a.rotation, 647 | HandleUtility.GetHandleSize(r.a.position) * data.pointSize / 100f, 648 | EventType.Repaint); 649 | 650 | 651 | Vector3 delta = r.delta; 652 | float mag = delta.magnitude; 653 | var n = delta.normalized; 654 | 655 | float arrowSize = HandleUtility.GetHandleSize(r.b.position) * data.arrowSize / 25f; 656 | Handles.ConeHandleCap(controlId + 1, r.b.position - n * arrowSize/1.43f, 657 | mag < 0.0001f ? Quaternion.identity : Quaternion.LookRotation(delta), 658 | arrowSize, EventType.Repaint); 659 | 660 | labelStyle.normal.textColor = r.textColor; 661 | labelStyle.normal.background.SetPixel(0, 0, new Color(r.color.r, r.color.g, r.color.b, 0.5f)); 662 | labelStyle.normal.background.Apply(); 663 | 664 | var labelSB = new StringBuilder(64); 665 | labelSB.Append(mag.ToString(precisionStr)); 666 | 667 | DrawExDist(r, labelSB); 668 | DrawExAngle(r, labelSB); 669 | 670 | Handles.Label(r.a.position + delta * 0.5f, labelSB.ToString(), labelStyle); 671 | } 672 | controlId += 2; 673 | } 674 | 675 | Handles.color = oldColor; 676 | 677 | SceneView.RepaintAll(); 678 | 679 | Repaint(); 680 | } 681 | 682 | void DrawExDist(Ruler r, StringBuilder sb) 683 | { 684 | if (!r.showExDist) return; 685 | 686 | var oldColor = Handles.color; 687 | var delta = r.delta; 688 | 689 | sb.Append(nlStr); 690 | 691 | if (r.isLocal) 692 | { 693 | var rdot = Vector3.Dot(delta, r.a.right); 694 | var udot = Vector3.Dot(delta, r.a.up); 695 | var fdot = Vector3.Dot(delta, r.a.forward); 696 | var rproj = rdot * r.a.right; 697 | var uproj = udot * r.a.up; 698 | var fproj = fdot * r.a.forward; 699 | 700 | //right 701 | Handles.color = distXColor; 702 | Handles.DrawLine(r.a.position, r.a.position + rproj); 703 | 704 | // up 705 | Handles.color = distYColor; 706 | Handles.DrawLine(r.a.position + rproj + fproj, r.a.position + rproj + fproj + uproj); 707 | 708 | // forward 709 | Handles.color = distZColor; 710 | Handles.DrawLine(r.a.position + rproj, r.a.position + rproj + fproj); 711 | 712 | sb.Append(deltaRStr); 713 | sb.AppendLine((rdot < 0 ? "-" : "") + rproj.magnitude.ToString(precisionStr)); 714 | sb.Append(deltaUStr); 715 | sb.AppendLine((udot < 0 ? "-" : "") + uproj.magnitude.ToString(precisionStr)); 716 | sb.Append(deltaFStr); 717 | sb.Append((fdot < 0 ? "-" : "") + fproj.magnitude.ToString(precisionStr)); 718 | 719 | } 720 | else 721 | { 722 | var rproj = new Vector3(delta.x, 0f, 0f); 723 | var uproj = new Vector3(0f, delta.y, 0f); 724 | var fproj = new Vector3(0f, 0f, delta.z); 725 | 726 | //right 727 | Handles.color = distXColor; 728 | Handles.DrawLine(r.a.position, r.a.position + rproj); 729 | 730 | // up 731 | Handles.color = distYColor; 732 | Handles.DrawLine(r.a.position + rproj + fproj, r.a.position + rproj + fproj + uproj); 733 | 734 | // forward 735 | Handles.color = distZColor; 736 | Handles.DrawLine(r.a.position + rproj, r.a.position + rproj + fproj); 737 | 738 | sb.Append(deltaXStr); 739 | sb.AppendLine(delta.x.ToString(precisionStr)); 740 | sb.Append(deltaYStr); 741 | sb.AppendLine(delta.y.ToString(precisionStr)); 742 | sb.Append(deltaZStr); 743 | sb.Append(delta.z.ToString(precisionStr)); 744 | } 745 | Handles.color = oldColor; 746 | } 747 | 748 | void DrawExAngle(Ruler r, StringBuilder sb) 749 | { 750 | if (!r.showExAngle) return; 751 | var oldColor = Handles.color; 752 | var delta = r.delta; 753 | var angles = r.GetAngles(); 754 | var mag = delta.magnitude; 755 | 756 | sb.Append(nlStr); 757 | 758 | if (r.isLocal) 759 | { 760 | // pitch 761 | Handles.color = angleXColor; 762 | Handles.DrawSolidArc(r.a.position, r.a.right, r.a.forward, angles.x, mag); 763 | 764 | // yaw 765 | Handles.color = angleYColor; 766 | Handles.DrawSolidArc(r.a.position, r.a.up, r.a.forward, angles.y, mag); 767 | 768 | // roll 769 | Handles.color = angleZColor; 770 | Handles.DrawSolidArc(r.a.position, r.a.forward, r.a.right, angles.z, mag); 771 | 772 | sb.Append(angleEPStr); 773 | sb.AppendLine(angles.x.ToString(precisionStr)); 774 | sb.Append(angleEYStr); 775 | sb.AppendLine(angles.y.ToString(precisionStr)); 776 | sb.Append(angleERStr); 777 | sb.Append(angles.z.ToString(precisionStr)); 778 | } 779 | else 780 | { 781 | // pitch 782 | Handles.color = angleXColor; 783 | Handles.DrawSolidArc(r.a.position, Vector3.right, Vector3.forward, angles.x, mag); 784 | 785 | // yaw 786 | Handles.color = angleYColor; 787 | Handles.DrawSolidArc(r.a.position, Vector3.up, Vector3.forward, angles.y, mag); 788 | 789 | // roll 790 | Handles.color = angleZColor; 791 | Handles.DrawSolidArc(r.a.position, Vector3.forward, Vector3.right, angles.z, mag); 792 | 793 | sb.Append(angleEPStr); 794 | sb.AppendLine(angles.x.ToString(precisionStr)); 795 | sb.Append(angleEYStr); 796 | sb.AppendLine(angles.y.ToString(precisionStr)); 797 | sb.Append(angleERStr); 798 | sb.Append(angles.z.ToString(precisionStr)); 799 | } 800 | Handles.color = oldColor; 801 | } 802 | } 803 | } 804 | --------------------------------------------------------------------------------