yasirkula/UnityAdjustPivot

Runtime support

Opened this issue · 3 comments

yosun commented

Hi! Can you add runtime support?

May I learn more about your runtime use-case?

yosun commented

I've decided not to add this feature to the plugin at the moment. However, I've created the following class which you may find useful:

NOTE: CopyComponent needs to be implemented if createColliderObjectOnPivotChange or createNavMeshObstacleObjectOnPivotChange is set to true. It was originally handled via EditorUtility.CopySerialized.

using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_5_5_OR_NEWER
using UnityEngine.AI;
#endif

public static class AdjustPivotRuntime
{
	private const string GENERATED_COLLIDER_NAME = "__GeneratedCollider";
	private const string GENERATED_NAVMESH_OBSTACLE_NAME = "__GeneratedNavMeshObstacle";
	private const string GENERATED_EMPTY_PARENT_NAME = "__GeneratedParent";

	public static void ModifyPivot( Transform transform, Vector3 pivotLocalPosition, Vector3 pivotLocalEulerAngles, bool createColliderObjectOnPivotChange = false, bool createNavMeshObstacleObjectOnPivotChange = false )
	{
		if( pivotLocalPosition == Vector3.zero && pivotLocalEulerAngles == Vector3.zero )
		{
			Debug.LogWarning( "Pivot hasn't changed!" );
			return;
		}

		if( pivotLocalEulerAngles != Vector3.zero )
		{
			Vector3 parentScale = transform.localScale;
			if( !Mathf.Approximately( parentScale.x, parentScale.y ) || !Mathf.Approximately( parentScale.x, parentScale.z ) )
			{
				// This is an edge case (object has non-uniform scale and pivot is rotated). We must create an empty parent GameObject in this scenario
				GameObject emptyParentObject = new GameObject( GENERATED_EMPTY_PARENT_NAME );
				if( !IsNull( transform.parent ) )
					emptyParentObject.transform.SetParent( transform.parent, false );
				else
					SceneManager.MoveGameObjectToScene( emptyParentObject, transform.gameObject.scene );

				emptyParentObject.transform.localPosition = transform.localPosition;
				emptyParentObject.transform.localRotation = transform.localRotation;
				emptyParentObject.transform.localScale = transform.localScale;

				transform.SetParent( emptyParentObject.transform );
			}
		}

		MeshFilter meshFilter = transform.GetComponent<MeshFilter>();
		Mesh originalMesh = null;
		if( !IsNull( meshFilter ) && !IsNull( meshFilter.sharedMesh ) )
		{
			originalMesh = meshFilter.sharedMesh;
			Mesh mesh = Object.Instantiate( meshFilter.sharedMesh );
			meshFilter.sharedMesh = mesh;

			Vector3[] vertices = mesh.vertices;
			Vector3[] normals = mesh.normals;
			Vector4[] tangents = mesh.tangents;

			if( pivotLocalPosition != Vector3.zero )
			{
				Vector3 deltaPosition = -pivotLocalPosition;
				for( int i = 0; i < vertices.Length; i++ )
					vertices[i] += deltaPosition;
			}

			if( pivotLocalEulerAngles != Vector3.zero )
			{
				Quaternion deltaRotation = Quaternion.Inverse( Quaternion.Euler( pivotLocalEulerAngles ) );
				for( int i = 0; i < vertices.Length; i++ )
				{
					vertices[i] = deltaRotation * vertices[i];
					normals[i] = deltaRotation * normals[i];

					Vector3 tangentDir = deltaRotation * tangents[i];
					tangents[i] = new Vector4( tangentDir.x, tangentDir.y, tangentDir.z, tangents[i].w );
				}
			}

			mesh.vertices = vertices;
			mesh.normals = normals;
			mesh.tangents = tangents;

			mesh.RecalculateBounds();
		}

		Collider[] colliders = transform.GetComponents<Collider>();
		foreach( Collider collider in colliders )
		{
			MeshCollider meshCollider = collider as MeshCollider;
			if( !IsNull( meshCollider ) && !IsNull( originalMesh ) && meshCollider.sharedMesh == originalMesh )
				meshCollider.sharedMesh = meshFilter.sharedMesh;
		}

		if( createColliderObjectOnPivotChange && IsNull( transform.Find( GENERATED_COLLIDER_NAME ) ) )
		{
			GameObject colliderObj = null;
			foreach( Collider collider in colliders )
			{
				if( IsNull( collider ) )
					continue;

				MeshCollider meshCollider = collider as MeshCollider;
				if( IsNull( meshCollider ) || meshCollider.sharedMesh != meshFilter.sharedMesh )
				{
					if( colliderObj == null )
					{
						colliderObj = new GameObject( GENERATED_COLLIDER_NAME );
						colliderObj.transform.SetParent( transform, false );
					}

					CopyComponent( collider, colliderObj.AddComponent( collider.GetType() ) );
				}
			}
		}

		if( createNavMeshObstacleObjectOnPivotChange && IsNull( transform.Find( GENERATED_NAVMESH_OBSTACLE_NAME ) ) )
		{
			NavMeshObstacle obstacle = transform.GetComponent<NavMeshObstacle>();
			if( !IsNull( obstacle ) )
			{
				GameObject obstacleObj = new GameObject( GENERATED_NAVMESH_OBSTACLE_NAME );
				obstacleObj.transform.SetParent( transform, false );
				CopyComponent( obstacle, obstacleObj.AddComponent( obstacle.GetType() ) );
			}
		}

		Transform[] children = new Transform[transform.childCount];
		Vector3[] childrenPositions = new Vector3[children.Length];
		Quaternion[] childrenRotations = new Quaternion[children.Length];
		for( int i = children.Length - 1; i >= 0; i-- )
		{
			children[i] = transform.GetChild( i );
			childrenPositions[i] = children[i].position;
			childrenRotations[i] = children[i].rotation;
		}

		transform.position = transform.TransformPoint( pivotLocalPosition );
		transform.rotation = transform.rotation * Quaternion.Euler( pivotLocalEulerAngles );

		for( int i = 0; i < children.Length; i++ )
		{
			children[i].position = childrenPositions[i];
			children[i].rotation = childrenRotations[i];
		}
	}

	private static void CopyComponent<T>( T from, T to ) where T : Component
	{
		throw new System.NotImplementedException();
	}

	private static bool IsNull( Object obj )
	{
		return obj == null || obj.Equals( null );
	}
}