|
| 1 | +/* |
| 2 | + * Version: 1.0 |
| 3 | + * Author: Yilmaz Kiymaz (@VoxelBoy) |
| 4 | + * Purpose: To be able to change the pivot of Game Objects |
| 5 | + * without needing to use a separate 3D application. |
| 6 | + * License: Free to use and distribute, in both free and commercial projects. |
| 7 | + * Do not try to sell as your own work. Simply put, play nice :) |
| 8 | + * Contact: VoxelBoy on Unity Forums |
| 9 | + */ |
| 10 | + |
| 11 | +/* |
| 12 | + * TODO: |
| 13 | + * - Doesn't work properly with rotated objects. |
| 14 | + * - Can't compensate for the positioning of Mesh Colliders. |
| 15 | + * - Need to figure out if the "Instantiating mesh" error in Editor is a big issue, if not, how to supress it. |
| 16 | + * - Allowing the pivot to move outside the bounds of the mesh, ideally using the movement gizmo but only affecting the pivot. |
| 17 | + */ |
| 18 | + |
| 19 | + |
| 20 | +using UnityEngine; |
| 21 | +using UnityEditor; |
| 22 | + |
| 23 | +public class SetPivot : EditorWindow { |
| 24 | + |
| 25 | + Vector3 p; //Pivot value -1..1, calculated from Mesh bounds |
| 26 | + Vector3 last_p; //Last used pivot |
| 27 | + |
| 28 | + GameObject obj; //Selected object in the Hierarchy |
| 29 | + MeshFilter meshFilter; //Mesh Filter of the selected object |
| 30 | + Mesh mesh; //Mesh of the selected object |
| 31 | + Collider col; //Collider of the selected object |
| 32 | + |
| 33 | + bool pivotUnchanged; //Flag to decide when to instantiate a copy of the mesh |
| 34 | + |
| 35 | + [MenuItem ("GameObject/Set Pivot")] //Place the Set Pivot menu item in the GameObject menu |
| 36 | + static void Init () { |
| 37 | + SetPivot window = (SetPivot)EditorWindow.GetWindow (typeof (SetPivot)); |
| 38 | + window.RecognizeSelectedObject(); //Initialize the variables by calling RecognizeSelectedObject on the class instance |
| 39 | + window.Show (); |
| 40 | + } |
| 41 | + |
| 42 | + void OnGUI() { |
| 43 | + if(obj) { |
| 44 | + if(mesh) { |
| 45 | + p.x = EditorGUILayout.Slider("X", p.x, -1.0f, 1.0f); |
| 46 | + p.y = EditorGUILayout.Slider("Y", p.y, -1.0f, 1.0f); |
| 47 | + p.z = EditorGUILayout.Slider("Z", p.z, -1.0f, 1.0f); |
| 48 | + if(p != last_p) { //Detects user input on any of the three sliders |
| 49 | + //Only create instance of mesh when user changes pivot |
| 50 | + if(pivotUnchanged) mesh = meshFilter.mesh; pivotUnchanged = false; |
| 51 | + UpdatePivot(); |
| 52 | + last_p = p; |
| 53 | + } |
| 54 | + if(GUILayout.Button("Center")) { //Set pivot to the center of the mesh bounds |
| 55 | + //Only create instance of mesh when user changes pivot |
| 56 | + if(pivotUnchanged) mesh = meshFilter.mesh; pivotUnchanged = false; |
| 57 | + p = Vector3.zero; |
| 58 | + UpdatePivot(); |
| 59 | + last_p = p; |
| 60 | + } |
| 61 | + GUILayout.Label("Bounds " + mesh.bounds.ToString()); |
| 62 | + } else { |
| 63 | + GUILayout.Label("Selected object does not have a Mesh specified."); |
| 64 | + } |
| 65 | + } else { |
| 66 | + GUILayout.Label("No object selected in Hierarchy."); |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + //Achieve the movement of the pivot by moving the transform position in the specified direction |
| 71 | + //and then moving all vertices of the mesh in the opposite direction back to where they were in world-space |
| 72 | + void UpdatePivot() { |
| 73 | + Vector3 diff = Vector3.Scale(mesh.bounds.extents, last_p - p); //Calculate difference in 3d position |
| 74 | + obj.transform.position -= Vector3.Scale(diff, obj.transform.localScale); //Move object position by taking localScale into account |
| 75 | + //Iterate over all vertices and move them in the opposite direction of the object position movement |
| 76 | + Vector3[] verts = mesh.vertices; |
| 77 | + for(int i=0; i<verts.Length; i++) { |
| 78 | + verts[i] += diff; |
| 79 | + } |
| 80 | + mesh.vertices = verts; //Assign the vertex array back to the mesh |
| 81 | + mesh.RecalculateBounds(); //Recalculate bounds of the mesh, for the renderer's sake |
| 82 | + //The 'center' parameter of certain colliders needs to be adjusted |
| 83 | + //when the transform position is modified |
| 84 | + if(col) { |
| 85 | + if(col is BoxCollider) { |
| 86 | + ((BoxCollider) col).center += diff; |
| 87 | + } else if(col is CapsuleCollider) { |
| 88 | + ((CapsuleCollider) col).center += diff; |
| 89 | + } else if(col is SphereCollider) { |
| 90 | + ((SphereCollider) col).center += diff; |
| 91 | + } |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + //Look at the object's transform position in comparison to the center of its mesh bounds |
| 96 | + //and calculate the pivot values for xyz |
| 97 | + void UpdatePivotVector() { |
| 98 | + Bounds b = mesh.bounds; |
| 99 | + Vector3 offset = -1 * b.center; |
| 100 | + p = last_p = new Vector3(offset.x / b.extents.x, offset.y / b.extents.y, offset.z / b.extents.z); |
| 101 | + } |
| 102 | + |
| 103 | + //When a selection change notification is received |
| 104 | + //recalculate the variables and references for the new object |
| 105 | + void OnSelectionChange() { |
| 106 | + RecognizeSelectedObject(); |
| 107 | + } |
| 108 | + |
| 109 | + //Gather references for the selected object and its components |
| 110 | + //and update the pivot vector if the object has a Mesh specified |
| 111 | + void RecognizeSelectedObject() { |
| 112 | + Transform t = Selection.activeTransform; |
| 113 | + obj = t ? t.gameObject : null; |
| 114 | + if(obj) { |
| 115 | + meshFilter = obj.GetComponent(typeof(MeshFilter)) as MeshFilter; |
| 116 | + mesh = meshFilter ? meshFilter.sharedMesh : null; |
| 117 | + if(mesh) |
| 118 | + UpdatePivotVector(); |
| 119 | + col = obj.GetComponent(typeof(Collider)) as Collider; |
| 120 | + pivotUnchanged = true; |
| 121 | + } else { |
| 122 | + mesh = null; |
| 123 | + } |
| 124 | + } |
| 125 | +} |
0 commit comments