Search Unity

Question Create bounding box for all frames of animation.

Discussion in 'General Graphics' started by Przemyslaw_Zaworski, Mar 9, 2023.

  1. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    328
    I need to create bounding box for animated object (for custom occlusion culling system). Animation clip is used for object rotation. For performance purposes it is better to create precomputed bounding box generated from world space positions of vertices from all animation frames, rather than recretate it from current renderer in every frame (see 29.5.2 Animated Objects from https://developer.nvidia.com/gpugem...lities/chapter-29-efficient-occlusion-culling ).

    But AnimationClip.localBounds only contains local mesh bounds.

    When I try to calculate it, it seems that transforms aren't updated immediately when animation state is changed. It means that when animation is spread over time, rotation is changed (values in Debug.Log are different):

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class AnimationTest : MonoBehaviour
    4. {
    5.     public Animator animator;
    6.  
    7.     int frame = 0;
    8.     float length = 0f;
    9.     float framecount = 0;
    10.  
    11.     void Start()
    12.     {
    13.         length = animator.runtimeAnimatorController.animationClips[0].length;
    14.         float frameRate = animator.runtimeAnimatorController.animationClips[0].frameRate;
    15.         framecount = length * frameRate;
    16.     }
    17.  
    18.     void Update()
    19.     {
    20.         if (Input.GetKey(KeyCode.J))
    21.         {
    22.             animator.speed = 0f;
    23.             animator.Play("Open", -1, (float) frame / (float) framecount);
    24.             if (frame > 0) frame--;  
    25.             Debug.Log(animator.gameObject.transform.eulerAngles);
    26.         }
    27.         if (Input.GetKey(KeyCode.K))
    28.         {
    29.             animator.speed = 0f;
    30.             animator.Play("Open", -1, (float) frame / (float) framecount);
    31.             if (frame < framecount) frame++;
    32.             Debug.Log(animator.gameObject.transform.eulerAngles);
    33.         }
    34.     }
    35. }
    But when I try to simulate animation in loop (to generate bounds with bounds.Encapsulate for all frames on demand), it doesn't work, even there is no impact for transform.eulerAngles :

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class AnimationTest : MonoBehaviour
    4. {
    5.     public Animator animator;
    6.  
    7.     int frame = 0;
    8.     float length = 0f;
    9.     float framecount = 0;
    10.  
    11.     void Start()
    12.     {
    13.         length = animator.runtimeAnimatorController.animationClips[0].length;
    14.         float frameRate = animator.runtimeAnimatorController.animationClips[0].frameRate;
    15.         framecount = length * frameRate;
    16.     }
    17.  
    18.     void Update()
    19.     {
    20.         if (Input.GetKeyDown(KeyCode.P))
    21.         {
    22.             frame = 0;
    23.             for (int i = 0; i < framecount; i++)
    24.             {
    25.                 animator.speed = 0f;
    26.                 animator.Play("Open", -1, (float) frame / (float) framecount);
    27.                 frame++;
    28.                 Debug.Log(animator.gameObject.transform.eulerAngles);
    29.             }
    30.         }
    31.     }
    32. }
    I need to make own solution to solve this issue, or maybe there is a way to create bounding box for animation with Unity API ?
     
  2. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    328
  3. Sluggy

    Sluggy

    Joined:
    Nov 27, 2012
    Posts:
    983
    It seems you found a workaround for your problem. For the record though, as you noticed, Animators do not immediately update their state when you change what animation you want them to play. Normally it happens during the animation phase of Unity's update loop. However, you can force the animator pump through to the next animation state by simply calling Update() on it.
     
    Przemyslaw_Zaworski likes this.
  4. Przemyslaw_Zaworski

    Przemyslaw_Zaworski

    Joined:
    Jun 9, 2017
    Posts:
    328
    Indeed, Animator.Update(), this is what I was looking for ! This source code works perfectly:

    Code (CSharp):
    1. // Create bounding box for all frames of animation, example animation state has name "Open".
    2. using UnityEngine;
    3.  
    4. public class AnimationBounds : MonoBehaviour
    5. {
    6.     [SerializeField] Animator _Animator;
    7.     [SerializeField] MeshRenderer _MeshRenderer;
    8.  
    9.     int _Frame = 0;
    10.     float _Length = 0f, _FrameCount = 0f, _FrameRate = 0f;
    11.     Bounds _Bounds = new Bounds (Vector3.zero, Vector3.zero);
    12.  
    13.     void Start()
    14.     {
    15.         _Length = _Animator.runtimeAnimatorController.animationClips[0].length;
    16.         _FrameRate = _Animator.runtimeAnimatorController.animationClips[0].frameRate;
    17.         _FrameCount = _Length * _FrameRate;
    18.     }
    19.  
    20.     void CalculateBounds()
    21.     {
    22.         _Frame = 0;
    23.         _Animator.Rebind();
    24.         _Animator.Update(0f);
    25.         _Bounds = _MeshRenderer.bounds;
    26.         for (int i = 0; i < _FrameCount; i++)
    27.         {
    28.             _Animator.Play("Open", -1, (float) _Frame / (float) _FrameCount);
    29.             _Animator.Update(1.0f / _FrameRate);
    30.             _Bounds.Encapsulate(_MeshRenderer.bounds);
    31.             _Frame++;
    32.         }
    33.         _Animator.Rebind();
    34.         _Animator.Update(0f);
    35.     }
    36.  
    37.     void Update()
    38.     {
    39.         if (Input.GetKeyDown(KeyCode.P))
    40.         {
    41.             CalculateBounds();
    42.         }
    43.     }
    44.  
    45.     void OnDrawGizmos()
    46.     {
    47.         Gizmos.DrawWireCube(_Bounds.center, _Bounds.size);
    48.     }
    49.  
    50.     void OnGUI()
    51.     {
    52.         GUI.Label(new Rect(10, 10, 200, 20), "Bounds size: " + _Bounds.size.ToString());
    53.     }
    54. }
    This is brilliant. Now I have two working solutions and I can use, depending on the needs, GPU threads for generation of AABB for dynamic objects (post #2), or this script with Animator.Update() to precompute AABB for animated objects. Perfect combination !
     
    Sluggy likes this.