├── .github ├── Images │ └── screenshot.png └── README.md ├── LICENSE.txt ├── LICENSE.txt.meta ├── Plugins.meta ├── Plugins ├── AdjustPivot.meta └── AdjustPivot │ ├── Editor.meta │ ├── Editor │ ├── AdjustPivot.Editor.asmdef │ ├── AdjustPivot.Editor.asmdef.meta │ ├── AdjustPivot.cs │ └── AdjustPivot.cs.meta │ ├── README.txt │ └── README.txt.meta ├── package.json └── package.json.meta /.github/Images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasirkula/UnityAdjustPivot/2573d7eb6a9700898ef87e12390d8935bb706791/.github/Images/screenshot.png -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 | # Pivot Editor for Unity 2 | ![screenshot](Images/screenshot.png) 3 | 4 | **Available on Asset Store:** https://assetstore.unity.com/packages/tools/utilities/adjust-pivot-112883 5 | 6 | **Forum Thread:** https://forum.unity.com/threads/adjust-pivot-without-using-an-empty-parent-object-open-source.520178/ 7 | 8 | **Discord:** https://discord.gg/UJJt549AaV 9 | 10 | **[GitHub Sponsors ☕](https://github.com/sponsors/yasirkula)** 11 | 12 | This script allows you to change the pivot point of an object without having to create an empty GameObject as the pivot point. There are two types of pivot adjustments: 13 | 14 | **a.** If the object does not have a mesh (**MeshFilter**, to be precise), then the script simply changes the positions and rotations of child objects accordingly 15 | 16 | **b.** If the object does have a mesh, then the script first creates an instance of the mesh, adjusts the mesh's pivot point by altering its vertices, normals and tangents, and finally changes the positions and rotations of child objects accordingly 17 | 18 | *Not tested with SkinnedMeshRenderer.* 19 | 20 | ## INSTALLATION 21 | 22 | There are 5 ways to install this plugin: 23 | 24 | - import [AdjustPivot.unitypackage](https://github.com/yasirkula/UnityAdjustPivot/releases) via *Assets-Import Package* 25 | - clone/[download](https://github.com/yasirkula/UnityAdjustPivot/archive/master.zip) this repository and move the *Plugins* folder to your Unity project's *Assets* folder 26 | - import it from [Asset Store](https://assetstore.unity.com/packages/tools/utilities/adjust-pivot-112883) 27 | - *(via Package Manager)* click the + button and install the package from the following git URL: 28 | - `https://github.com/yasirkula/UnityAdjustPivot.git` 29 | - *(via [OpenUPM](https://openupm.com))* after installing [openupm-cli](https://github.com/openupm/openupm-cli), run the following command: 30 | - `openupm add com.yasirkula.adjustpivot` 31 | 32 | ## HOW TO 33 | 34 | Open the *Adjust Pivot* window via the **Window-Adjust Pivot** menu. 35 | 36 | To change an object's pivot point, you can create an empty **child GameObject** and move it to the desired pivot position. Then, you can press the **Move X's pivot here** button to move the parent object's pivot there. It is safe to delete the empty child object afterwards. 37 | 38 | Note that if the object has a mesh (**option b**), to apply the changes to the prefab, you have to save the instantiated mesh to your project. Otherwise, the asset will be serialized in the scene and won't be available to the prefab. You have two options there: 39 | 40 | - save the mesh as asset (**.asset**) 41 | - save the mesh as OBJ (**.obj**) 42 | 43 | Afterwards, you can safely apply your changes to the prefab. 44 | 45 | There are a few options that may be helpful in certain circumstances: 46 | 47 | - **Create Child Collider Object On Pivot Change**: imagine your object has a BoxCollider and you rotate the pivot by 45 degrees. A BoxCollider itself can not be rotated and therefore, your BoxCollider will no longer be aligned with your object. If you enable this option, however, a child object with a BoxCollider will be created and it will be aligned with your object properly. But be aware that the original BoxCollider will not be removed automatically to prevent any Inspector references to it from getting lost. You should remove the original BoxCollider manually 48 | 49 | - **Create Child NavMesh Obstacle Object On Pivot Change**: if enabled, a child object with NavMesh Obstacle component will be created automatically when pivot changes. This child object will be aligned with your object properly 50 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Süleyman Yasir KULA 2 | 3 | Permission to use, copy, modify, and/or distribute this software for 4 | any purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 7 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 8 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 9 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 10 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 11 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 12 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /LICENSE.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d0e4641b96c9d8a4e800195109a01b89 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b232edfc363709d449f6247a5b55b962 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/AdjustPivot.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 36b35164a03dfd0418a01c8d2f490d5a 3 | folderAsset: yes 4 | timeCreated: 1522577373 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/AdjustPivot/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 05fa137cbb96346409ed008fccd5adbe 3 | folderAsset: yes 4 | timeCreated: 1520092686 5 | licenseType: Store 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Plugins/AdjustPivot/Editor/AdjustPivot.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AdjustPivot.Editor", 3 | "references": [], 4 | "includePlatforms": [ 5 | "Editor" 6 | ], 7 | "excludePlatforms": [], 8 | "allowUnsafeCode": false, 9 | "overrideReferences": false, 10 | "precompiledReferences": [], 11 | "autoReferenced": true, 12 | "defineConstraints": [], 13 | "versionDefines": [], 14 | "noEngineReferences": false 15 | } -------------------------------------------------------------------------------- /Plugins/AdjustPivot/Editor/AdjustPivot.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0e48c19c61ecc9e4d95b3177131dec57 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Plugins/AdjustPivot/Editor/AdjustPivot.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.IO; 3 | using System.Text; 4 | using UnityEditor; 5 | using UnityEngine; 6 | using UnityEngine.SceneManagement; 7 | #if UNITY_5_5_OR_NEWER 8 | using UnityEngine.AI; 9 | #endif 10 | 11 | public class AdjustPivot : EditorWindow 12 | { 13 | private const string GENERATED_COLLIDER_NAME = "__GeneratedCollider"; 14 | private const string GENERATED_NAVMESH_OBSTACLE_NAME = "__GeneratedNavMeshObstacle"; 15 | private const string GENERATED_EMPTY_PARENT_NAME = "__GeneratedParent"; 16 | 17 | private const string UNDO_CREATE_PIVOT_REFERENCE = "Create Pivot Reference"; 18 | private const string UNDO_ADJUST_PIVOT = "Move Pivot"; 19 | private const string UNDO_SAVE_MODEL_AS = "Save Model As"; 20 | 21 | private bool createColliderObjectOnPivotChange = false; 22 | private bool createNavMeshObstacleObjectOnPivotChange = false; 23 | 24 | private readonly GUILayoutOption headerHeight = GUILayout.Height( 25 ); 25 | 26 | private GUIStyle buttonStyle; 27 | private GUIStyle headerStyle; 28 | 29 | private Vector3 selectionPrevPos; 30 | private Vector3 selectionPrevRot; 31 | 32 | private Vector2 scrollPos = Vector2.zero; 33 | 34 | [MenuItem( "Window/Adjust Pivot" )] 35 | private static void Init() 36 | { 37 | AdjustPivot window = GetWindow(); 38 | window.titleContent = new GUIContent( "Adjust Pivot" ); 39 | window.minSize = new Vector2( 330f, 200f ); 40 | 41 | window.Show(); 42 | } 43 | 44 | private void OnEnable() 45 | { 46 | GetPrefs(); 47 | 48 | Selection.selectionChanged += Repaint; 49 | EditorApplication.update += OnUpdate; 50 | } 51 | 52 | private void OnDisable() 53 | { 54 | Selection.selectionChanged -= Repaint; 55 | EditorApplication.update -= OnUpdate; 56 | } 57 | 58 | private void OnUpdate() 59 | { 60 | Transform selection = Selection.activeTransform; 61 | if( !IsNull( selection ) ) 62 | { 63 | Vector3 pos = selection.localPosition; 64 | Vector3 rot = selection.localEulerAngles; 65 | 66 | if( pos != selectionPrevPos || rot != selectionPrevRot ) 67 | { 68 | Repaint(); 69 | 70 | selectionPrevPos = pos; 71 | selectionPrevRot = rot; 72 | } 73 | } 74 | } 75 | 76 | private void OnGUI() 77 | { 78 | if( buttonStyle == null ) 79 | { 80 | buttonStyle = new GUIStyle( GUI.skin.button ) { richText = true, wordWrap = true, padding = new RectOffset( 7, 7, 7, 7 ) }; 81 | headerStyle = new GUIStyle( GUI.skin.box ) { alignment = TextAnchor.MiddleCenter }; 82 | } 83 | 84 | scrollPos = EditorGUILayout.BeginScrollView( scrollPos ); 85 | 86 | GUILayout.Box( "ADJUST PIVOT", headerStyle, GUILayout.ExpandWidth( true ), headerHeight ); 87 | 88 | Transform selection = Selection.activeTransform; 89 | if( !IsNull( selection ) ) 90 | { 91 | if( !IsNull( selection.parent ) ) 92 | { 93 | #if UNITY_2021_2_OR_NEWER 94 | if( UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage() == null ) 95 | #elif UNITY_2018_3_OR_NEWER 96 | if( UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage() == null ) 97 | #endif 98 | { 99 | if( selection.localPosition != Vector3.zero || selection.localEulerAngles != Vector3.zero ) 100 | { 101 | if( GUILayout.Button( string.Concat( "Move ", selection.parent.name, "'s pivot here" ), buttonStyle ) ) 102 | SetParentPivot( selection ); 103 | 104 | if( selection.localEulerAngles != Vector3.zero ) 105 | { 106 | Vector3 parentScale = selection.parent.localScale; 107 | if( Mathf.Approximately( parentScale.x, parentScale.y ) && Mathf.Approximately( parentScale.x, parentScale.z ) ) 108 | EditorGUILayout.HelpBox( string.Concat( "Pivot will also be rotated to match ", selection.name, "'s rotation." ), MessageType.None ); 109 | else 110 | EditorGUILayout.HelpBox( string.Concat( "Edge case! 1) ", selection.parent.name, " has non-uniform scale and 2) ", selection.name, " is rotated.\n\nTo change the pivot correctly in this scenario, ", selection.parent.name, " will be parented to an empty GameObject with the same non-uniform scale value." ), MessageType.Warning ); 111 | } 112 | } 113 | else 114 | { 115 | GUI.enabled = false; 116 | GUILayout.Button( "Selected object is at pivot position", buttonStyle ); 117 | GUI.enabled = true; 118 | } 119 | } 120 | #if UNITY_2018_3_OR_NEWER 121 | else 122 | { 123 | GUI.enabled = false; 124 | GUILayout.Button( "Modifying prefabs directly is not allowed, create an instance in the scene instead!", buttonStyle ); 125 | GUI.enabled = true; 126 | } 127 | #endif 128 | } 129 | else 130 | { 131 | GUI.enabled = false; 132 | GUILayout.Button( "Selected object has no parent", buttonStyle ); 133 | GUI.enabled = true; 134 | } 135 | } 136 | else 137 | { 138 | GUI.enabled = false; 139 | GUILayout.Button( "Nothing is selected", buttonStyle ); 140 | GUI.enabled = true; 141 | } 142 | 143 | GUILayout.Space( 15f ); 144 | 145 | GUILayout.Box( "MESH UTILITY", headerStyle, GUILayout.ExpandWidth( true ), headerHeight ); 146 | 147 | EditorGUILayout.HelpBox( "If an object has a MeshFilter, changing its pivot will modify the mesh. That modified mesh must be saved before it can be applied to prefab.", MessageType.None ); 148 | 149 | if( !IsNull( selection ) ) 150 | { 151 | MeshFilter meshFilter = selection.GetComponent(); 152 | if( !IsNull( meshFilter ) && !IsNull( meshFilter.sharedMesh ) ) 153 | { 154 | if( GUILayout.Button( string.Concat( "Save ", selection.name, "'s mesh as Asset (Recommended)" ), buttonStyle ) ) 155 | SaveMesh( meshFilter, true ); 156 | 157 | GUILayout.Space( 5f ); 158 | 159 | if( GUILayout.Button( string.Concat( "Save ", selection.name, "'s mesh as OBJ" ), buttonStyle ) ) 160 | SaveMesh( meshFilter, false ); 161 | } 162 | else 163 | { 164 | GUI.enabled = false; 165 | GUILayout.Button( "Selected object has no mesh", buttonStyle ); 166 | GUI.enabled = true; 167 | } 168 | } 169 | else 170 | { 171 | GUI.enabled = false; 172 | GUILayout.Button( "Nothing is selected", buttonStyle ); 173 | GUI.enabled = true; 174 | } 175 | 176 | GUILayout.Space( 15f ); 177 | 178 | GUILayout.Box( "SETTINGS", headerStyle, GUILayout.ExpandWidth( true ), headerHeight ); 179 | 180 | EditorGUI.BeginChangeCheck(); 181 | createColliderObjectOnPivotChange = EditorGUILayout.ToggleLeft( "Create Child Collider Object On Pivot Change", createColliderObjectOnPivotChange ); 182 | EditorGUILayout.HelpBox( "Note that original collider(s) (if exists) will not be destroyed automatically.", MessageType.None ); 183 | if( EditorGUI.EndChangeCheck() ) 184 | EditorPrefs.SetBool( "AdjustPivotCreateColliders", createColliderObjectOnPivotChange ); 185 | 186 | GUILayout.Space( 10f ); 187 | 188 | EditorGUI.BeginChangeCheck(); 189 | createNavMeshObstacleObjectOnPivotChange = EditorGUILayout.ToggleLeft( "Create Child NavMesh Obstacle Object On Pivot Change", createNavMeshObstacleObjectOnPivotChange ); 190 | EditorGUILayout.HelpBox( "Note that original NavMesh Obstacle (if exists) will not be destroyed automatically.", MessageType.None ); 191 | if( EditorGUI.EndChangeCheck() ) 192 | EditorPrefs.SetBool( "AdjustPivotCreateNavMeshObstacle", createNavMeshObstacleObjectOnPivotChange ); 193 | 194 | GUILayout.Space( 10f ); 195 | 196 | EditorGUILayout.EndScrollView(); 197 | } 198 | 199 | private void GetPrefs() 200 | { 201 | createColliderObjectOnPivotChange = EditorPrefs.GetBool( "AdjustPivotCreateColliders", false ); 202 | createNavMeshObstacleObjectOnPivotChange = EditorPrefs.GetBool( "AdjustPivotCreateNavMeshObstacle", false ); 203 | } 204 | 205 | private void SetParentPivot( Transform pivot ) 206 | { 207 | Transform pivotParent = pivot.parent; 208 | if( IsPrefab( pivotParent ) ) 209 | { 210 | Debug.LogWarning( "Modifying prefabs directly is not allowed, create an instance in the scene instead!" ); 211 | return; 212 | } 213 | 214 | if( pivot.localPosition == Vector3.zero && pivot.localEulerAngles == Vector3.zero ) 215 | { 216 | Debug.LogWarning( "Pivot hasn't changed!" ); 217 | return; 218 | } 219 | 220 | if( pivot.localEulerAngles != Vector3.zero ) 221 | { 222 | Vector3 parentScale = pivotParent.localScale; 223 | if( !Mathf.Approximately( parentScale.x, parentScale.y ) || !Mathf.Approximately( parentScale.x, parentScale.z ) ) 224 | { 225 | // This is an edge case (object has non-uniform scale and pivot is rotated). We must create an empty parent GameObject in this scenario 226 | GameObject emptyParentObject = new GameObject( GENERATED_EMPTY_PARENT_NAME ); 227 | if( !IsNull( pivotParent.parent ) ) 228 | emptyParentObject.transform.SetParent( pivotParent.parent, false ); 229 | else 230 | SceneManager.MoveGameObjectToScene( emptyParentObject, pivotParent.gameObject.scene ); 231 | 232 | emptyParentObject.transform.localPosition = pivotParent.localPosition; 233 | emptyParentObject.transform.localRotation = pivotParent.localRotation; 234 | emptyParentObject.transform.localScale = pivotParent.localScale; 235 | 236 | Undo.RegisterCreatedObjectUndo( emptyParentObject, UNDO_ADJUST_PIVOT ); 237 | Undo.SetTransformParent( pivotParent, emptyParentObject.transform, UNDO_ADJUST_PIVOT ); 238 | 239 | // Automatically expand the newly created empty parent GameObject in Hierarchy 240 | EditorGUIUtility.PingObject( pivot.gameObject ); 241 | } 242 | } 243 | 244 | MeshFilter meshFilter = pivotParent.GetComponent(); 245 | Mesh originalMesh = null; 246 | if( !IsNull( meshFilter ) && !IsNull( meshFilter.sharedMesh ) ) 247 | { 248 | Undo.RecordObject( meshFilter, UNDO_ADJUST_PIVOT ); 249 | 250 | originalMesh = meshFilter.sharedMesh; 251 | Mesh mesh = Instantiate( meshFilter.sharedMesh ); 252 | meshFilter.sharedMesh = mesh; 253 | 254 | Vector3[] vertices = mesh.vertices; 255 | Vector3[] normals = mesh.normals; 256 | Vector4[] tangents = mesh.tangents; 257 | 258 | if( pivot.localPosition != Vector3.zero ) 259 | { 260 | Vector3 deltaPosition = -pivot.localPosition; 261 | for( int i = 0; i < vertices.Length; i++ ) 262 | vertices[i] += deltaPosition; 263 | } 264 | 265 | if( pivot.localEulerAngles != Vector3.zero ) 266 | { 267 | Quaternion deltaRotation = Quaternion.Inverse( pivot.localRotation ); 268 | for( int i = 0; i < vertices.Length; i++ ) 269 | { 270 | vertices[i] = deltaRotation * vertices[i]; 271 | normals[i] = deltaRotation * normals[i]; 272 | 273 | Vector3 tangentDir = deltaRotation * tangents[i]; 274 | tangents[i] = new Vector4( tangentDir.x, tangentDir.y, tangentDir.z, tangents[i].w ); 275 | } 276 | } 277 | 278 | mesh.vertices = vertices; 279 | mesh.normals = normals; 280 | mesh.tangents = tangents; 281 | 282 | mesh.RecalculateBounds(); 283 | } 284 | 285 | GetPrefs(); 286 | 287 | Collider[] colliders = pivotParent.GetComponents(); 288 | foreach( Collider collider in colliders ) 289 | { 290 | MeshCollider meshCollider = collider as MeshCollider; 291 | if( !IsNull( meshCollider ) && !IsNull( originalMesh ) && meshCollider.sharedMesh == originalMesh ) 292 | { 293 | Undo.RecordObject( meshCollider, UNDO_ADJUST_PIVOT ); 294 | meshCollider.sharedMesh = meshFilter.sharedMesh; 295 | } 296 | } 297 | 298 | if( createColliderObjectOnPivotChange && IsNull( pivotParent.Find( GENERATED_COLLIDER_NAME ) ) ) 299 | { 300 | GameObject colliderObj = null; 301 | foreach( Collider collider in colliders ) 302 | { 303 | if( IsNull( collider ) ) 304 | continue; 305 | 306 | MeshCollider meshCollider = collider as MeshCollider; 307 | if( IsNull( meshCollider ) || meshCollider.sharedMesh != meshFilter.sharedMesh ) 308 | { 309 | if( colliderObj == null ) 310 | { 311 | colliderObj = new GameObject( GENERATED_COLLIDER_NAME ); 312 | colliderObj.transform.SetParent( pivotParent, false ); 313 | } 314 | 315 | EditorUtility.CopySerialized( collider, colliderObj.AddComponent( collider.GetType() ) ); 316 | } 317 | } 318 | 319 | if( colliderObj != null ) 320 | Undo.RegisterCreatedObjectUndo( colliderObj, UNDO_ADJUST_PIVOT ); 321 | } 322 | 323 | if( createNavMeshObstacleObjectOnPivotChange && IsNull( pivotParent.Find( GENERATED_NAVMESH_OBSTACLE_NAME ) ) ) 324 | { 325 | NavMeshObstacle obstacle = pivotParent.GetComponent(); 326 | if( !IsNull( obstacle ) ) 327 | { 328 | GameObject obstacleObj = new GameObject( GENERATED_NAVMESH_OBSTACLE_NAME ); 329 | obstacleObj.transform.SetParent( pivotParent, false ); 330 | EditorUtility.CopySerialized( obstacle, obstacleObj.AddComponent( obstacle.GetType() ) ); 331 | Undo.RegisterCreatedObjectUndo( obstacleObj, UNDO_ADJUST_PIVOT ); 332 | } 333 | } 334 | 335 | Transform[] children = new Transform[pivotParent.childCount]; 336 | Vector3[] childrenPositions = new Vector3[children.Length]; 337 | Quaternion[] childrenRotations = new Quaternion[children.Length]; 338 | for( int i = children.Length - 1; i >= 0; i-- ) 339 | { 340 | children[i] = pivotParent.GetChild( i ); 341 | childrenPositions[i] = children[i].position; 342 | childrenRotations[i] = children[i].rotation; 343 | 344 | Undo.RecordObject( children[i], UNDO_ADJUST_PIVOT ); 345 | } 346 | 347 | Undo.RecordObject( pivotParent, UNDO_ADJUST_PIVOT ); 348 | pivotParent.position = pivot.position; 349 | pivotParent.rotation = pivot.rotation; 350 | 351 | for( int i = 0; i < children.Length; i++ ) 352 | { 353 | children[i].position = childrenPositions[i]; 354 | children[i].rotation = childrenRotations[i]; 355 | } 356 | 357 | pivot.localPosition = Vector3.zero; 358 | pivot.localRotation = Quaternion.identity; 359 | } 360 | 361 | private void SaveMesh( MeshFilter meshFilter, bool saveAsAsset ) 362 | { 363 | if( IsPrefab( meshFilter ) ) 364 | { 365 | Debug.LogWarning( "Modifying prefabs directly is not allowed, create an instance in the scene instead!" ); 366 | return; 367 | } 368 | 369 | string savedMeshName = meshFilter.sharedMesh.name; 370 | while( savedMeshName.EndsWith( "(Clone)" ) ) 371 | savedMeshName = savedMeshName.Substring( 0, savedMeshName.Length - 7 ); 372 | 373 | string savePath = EditorUtility.SaveFilePanelInProject( "Save As", savedMeshName, saveAsAsset ? "asset" : "obj", string.Empty ); 374 | if( string.IsNullOrEmpty( savePath ) ) 375 | return; 376 | 377 | Mesh originalMesh = meshFilter.sharedMesh; 378 | Mesh savedMesh = saveAsAsset ? SaveMeshAsAsset( meshFilter, savePath ) : SaveMeshAsOBJ( meshFilter, savePath ); 379 | if( meshFilter.sharedMesh != savedMesh ) 380 | { 381 | Undo.RecordObject( meshFilter, UNDO_SAVE_MODEL_AS ); 382 | meshFilter.sharedMesh = savedMesh; 383 | } 384 | 385 | MeshCollider[] meshColliders = meshFilter.GetComponents(); 386 | foreach( MeshCollider meshCollider in meshColliders ) 387 | { 388 | if( !IsNull( meshCollider ) && meshCollider.sharedMesh == originalMesh && meshCollider.sharedMesh != savedMesh ) 389 | { 390 | Undo.RecordObject( meshCollider, UNDO_SAVE_MODEL_AS ); 391 | meshCollider.sharedMesh = savedMesh; 392 | } 393 | } 394 | } 395 | 396 | private Mesh SaveMeshAsAsset( MeshFilter meshFilter, string savePath ) 397 | { 398 | Mesh mesh = meshFilter.sharedMesh; 399 | if( !string.IsNullOrEmpty( AssetDatabase.GetAssetPath( mesh ) ) ) // If mesh is an asset, clone it 400 | mesh = Instantiate( mesh ); 401 | 402 | AssetDatabase.CreateAsset( mesh, savePath ); 403 | AssetDatabase.SaveAssets(); 404 | 405 | return mesh; 406 | } 407 | 408 | //Credit: http://wiki.unity3d.com/index.php?title=ObjExporter 409 | private Mesh SaveMeshAsOBJ( MeshFilter meshFilter, string savePath ) 410 | { 411 | Mesh mesh = meshFilter.sharedMesh; 412 | 413 | Renderer renderer = meshFilter.GetComponent(); 414 | Material[] mats = !IsNull( renderer ) ? renderer.sharedMaterials : null; 415 | 416 | StringBuilder meshString = new StringBuilder(); 417 | 418 | meshString.Append( "g " ).Append( Path.GetFileNameWithoutExtension( savePath ) ).Append( "\n" ); 419 | foreach( Vector3 v in mesh.vertices ) 420 | meshString.Append( string.Format( CultureInfo.InvariantCulture, "v {0} {1} {2}\n", -v.x, v.y, v.z ) ); 421 | 422 | meshString.Append( "\n" ); 423 | 424 | foreach( Vector3 v in mesh.normals ) 425 | meshString.Append( string.Format( CultureInfo.InvariantCulture, "vn {0} {1} {2}\n", -v.x, v.y, v.z ) ); 426 | 427 | meshString.Append( "\n" ); 428 | 429 | foreach( Vector3 v in mesh.uv ) 430 | meshString.Append( string.Format( CultureInfo.InvariantCulture, "vt {0} {1}\n", v.x, v.y ) ); 431 | 432 | for( int material = 0; material < mesh.subMeshCount; material++ ) 433 | { 434 | meshString.Append( "\n" ); 435 | 436 | if( mats != null && mats.Length > material ) 437 | { 438 | meshString.Append( "usemtl " ).Append( mats[material].name ).Append( "\n" ); 439 | meshString.Append( "usemap " ).Append( mats[material].name ).Append( "\n" ); 440 | } 441 | 442 | int[] triangles = mesh.GetTriangles( material ); 443 | for( int i = 0; i < triangles.Length; i += 3 ) 444 | { 445 | meshString.Append( string.Format( "f {1}/{1}/{1} {0}/{0}/{0} {2}/{2}/{2}\n", 446 | triangles[i] + 1, triangles[i + 1] + 1, triangles[i + 2] + 1 ) ); 447 | } 448 | } 449 | 450 | File.WriteAllText( savePath, meshString.ToString() ); 451 | AssetDatabase.ImportAsset( savePath, ImportAssetOptions.ForceUpdate ); 452 | 453 | return AssetDatabase.LoadAssetAtPath( savePath ); 454 | } 455 | 456 | private bool IsPrefab( Object obj ) 457 | { 458 | return AssetDatabase.Contains( obj ); 459 | } 460 | 461 | private bool IsNull( Object obj ) 462 | { 463 | return obj == null || obj.Equals( null ); 464 | } 465 | } -------------------------------------------------------------------------------- /Plugins/AdjustPivot/Editor/AdjustPivot.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1726886cca57cfe42b2ed776c1e552c2 3 | timeCreated: 1520092702 4 | licenseType: Store 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /Plugins/AdjustPivot/README.txt: -------------------------------------------------------------------------------- 1 | = Adjust Pivot = 2 | 3 | Online documentation available at: https://github.com/yasirkula/UnityAdjustPivot 4 | E-mail: yasirkula@gmail.com 5 | 6 | 1. ABOUT 7 | This tool helps you change the pivot point of an object without having to create an empty parent object as the pivot point. There are two types of pivot adjustments: 8 | 9 | a. If the object does not have a mesh (MeshFilter, to be precise), then the script simply changes the positions and rotations of child objects accordingly 10 | b. If the object does have a mesh, then the script first creates an instance of the mesh, adjusts the mesh's pivot point by altering its vertices, normals and tangents, and finally changes the positions and rotations of child objects accordingly 11 | 12 | 2. HOW TO 13 | To change an object's pivot point, create an empty child GameObject and move it to the desired pivot position. Then, open the Adjust Pivot window via the Window-Adjust Pivot menu and press the "Move X's pivot here" button to move the parent object's pivot there. It is safe to delete the empty child object afterwards. 14 | 15 | Note that if the object has a mesh (option b), to apply the changes to the prefab, you have to save the instantiated mesh to your project. Otherwise, the asset will be serialized in the scene and won't be available to the prefab. You have two options there: 16 | 17 | - save the mesh as asset (.asset) 18 | - save the mesh as OBJ (.obj) 19 | 20 | Afterwards, you can safely apply your changes to the prefab. -------------------------------------------------------------------------------- /Plugins/AdjustPivot/README.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2673d4828d17e9a439523053792445a3 3 | timeCreated: 1563308480 4 | licenseType: Free 5 | TextScriptImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.yasirkula.adjustpivot", 3 | "displayName": "Adjust Pivot", 4 | "version": "1.0.6", 5 | "documentationUrl": "https://github.com/yasirkula/UnityAdjustPivot", 6 | "changelogUrl": "https://github.com/yasirkula/UnityAdjustPivot/releases", 7 | "licensesUrl": "https://github.com/yasirkula/UnityAdjustPivot/blob/master/LICENSE.txt", 8 | "description": "This tool helps you change the pivot point of an object without having to create an empty parent object as the pivot point." 9 | } 10 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4049ed14516200345a79c529e6701946 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------