yasirkula / UnityAdjustPivot

Adjust pivot point of an object in Unity without creating an empty parent object
BSD Zero Clause License
151 stars 17 forks source link

Runtime support #2

Open yosun opened 2 years ago

yosun commented 2 years ago

Hi! Can you add runtime support?

yasirkula commented 2 years ago

May I learn more about your runtime use-case?

yosun commented 2 years ago

Runtime imported models may need pivots corrected at runtime

So just a method to call it at runtime

On Tue, Jan 18, 2022 at 4:49 AM Süleyman Yasir KULA < @.***> wrote:

May I learn more about your runtime use-case?

— Reply to this email directly, view it on GitHub https://github.com/yasirkula/UnityAdjustPivot/issues/2#issuecomment-1015379240, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAG57PXLIQK7PNIHO3AUX73UWVOWJANCNFSM5MHBAW4A . Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub.

You are receiving this because you authored the thread.Message ID: @.***>

-- Yosun Chang, Founder and CTO

Permute.xyz | AIMagical.com

Phone: (415) 779 6786 WWW: AReality3D.com Twitter: @yosun

yasirkula commented 2 years ago

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 );
    }
}