Search Unity

Animation curve on spherical object

Discussion in 'Scripting' started by LoopIssuer, Oct 27, 2020.

  1. LoopIssuer

    LoopIssuer

    Joined:
    Jan 27, 2020
    Posts:
    109
    Hi,
    I need to draw a line that starts and ends in A and B, but conforms to the shape of the ball (as shown).
    I would like to change the Animation Curve from the code just so that its shape matches the sphere with the start and end points.
     

    Attached Files:

  2. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,113
  3. LoopIssuer

    LoopIssuer

    Joined:
    Jan 27, 2020
    Posts:
    109
    Yeah, that could be true, but this is a similar problem - I just need both solutions.
    The referenced answer with quaternions worked and thank you for that :)
     
  4. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,113
    Okay, that's great! I thought you couldn't figure it out.
    But then I didn't get this question.

    What should happen with AnimationCurve? Should it be circular?
     
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,113
    I mean, if you want to use AnimationCurve with the spherical interpolation you asked for in the previous thread, that it should really be flat. Like a ramp. Otherwise, maybe you want to ease the motion, but AnimationCurve doesn't need to know anything about the sphere regardless.
     
  6. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,113
    Here's a full working demo for what you want.

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [ExecuteInEditMode]
    5. public class SphericalInterpolationDemo : MonoBehaviour {
    6.  
    7.   public AnimationCurve curve = null;
    8.   public Vector3 position1 = Vector3.zero;
    9.   public Vector3 position2 = Vector3.zero;
    10.   public float duration = 5f; // in seconds
    11.   public float gizmoScale = 1f;
    12.  
    13.   private Quaternion _q1, _q2;
    14.   private float _lerp;
    15.   private Vector3 _pos;
    16.   private bool _anim;
    17.  
    18.   void OnEnable() {
    19.     _q1 = Quaternion.FromToRotation(position1.normalized, Vector3.forward);
    20.     _q2 = Quaternion.FromToRotation(position2.normalized, Vector3.forward);
    21.     _lerp = 0f;
    22.     _anim = true;
    23.   }
    24.  
    25.   void OnValidate() {
    26.     OnEnable();
    27.   }
    28.  
    29.   void Update() {
    30.     if(!_anim) return;
    31.  
    32.     _lerp = _time / duration;
    33.     if(curve != null) _lerp = curve.Evaluate(_lerp);
    34.     _pos = Quaternion.Slerp(_q1, _q2, _lerp) * Vector3.forward;
    35.  
    36.     _time += Time.deltaTime;
    37.  
    38.     if(_time > duration) {
    39.       _time = duration;
    40.       _anim = false;
    41.     }
    42.   }
    43.  
    44.   void OnDrawGizmos() {
    45.     Gizmos.color = Color.blue;
    46.     Handles.DotHandleCap(0, _q1 * Vector3.forward, Quaternion.identity, .3f * gizmoScale, EventType.Repaint);
    47.     Gizmos.color = Color.red;
    48.     Handles.DotHandleCap(0, _q2 * Vector3.forward, Quaternion.identity, .3f * gizmoScale, EventType.Repaint);
    49.     Gizmos.color = Color.yellow;
    50.     Handles.DotHandleCap(0, _pos, Quaternion.identity, .3f * gizmoScale, EventType.Repaint);
    51.   }
    52.  
    53. }
    This script will run in the editor.
    Reset the script animation by toggling the checkbox next to its name in the inspector (or change the values).
    Haven't tested this, tell me if there's something wrong.
     
    Last edited: Oct 27, 2020
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,113
    Alternatively you can set positions by Euler angles instead.
    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [ExecuteInEditMode]
    5. public class SphericalInterpolationDemo : MonoBehaviour {
    6.  
    7.   public AnimationCurve curve = null;
    8.   public Vector3 angles1 = Vector3.zero; // in degrees
    9.   public Vector3 angles2 = Vector3.zero; // in degrees
    10.   public float duration = 5f; // in seconds
    11.   public float gizmoScale = 1f;
    12.  
    13.   ...
    14.  
    15.   void OnEnable() {
    16.     _q1 = Quaternion.Euler(angles1);
    17.     _q2 = Quaternion.Euler(angles2);
    18.     _lerp = 0f;
    19.     _anim = true;
    20.   }
    21.  
    22.   ...
    23.  
    24. }
     
  8. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,113
    If you want to draw a line, you visit each point and draw a segment with the point before.

    Code (csharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [ExecuteInEditMode]
    5. public class GreatCircleArcDemo : MonoBehaviour {
    6.  
    7.   [Range(2, 64)] public int points = 16;
    8.   public Vector3 angles1 = Vector3.zero;
    9.   public Vector3 angles2 = Vector3.zero;
    10.  
    11.   private Quaternion _q1, _q2;
    12.   private Vector3[] _pts; // we want to repurpose this array, to minimize allocation
    13.  
    14.   void OnValidate() {
    15.     makeArrayOrFixOldOne();
    16.     computeQuats();
    17.     computePoints();
    18.   }
    19.  
    20.   private void makeArrayOrFixOldOne() {
    21.     if(points < 2) { // this is invalid so abort
    22.       _pts = null;
    23.       return;
    24.     }
    25.  
    26.     if(_pts == null) { // we didn't have anything, so create new array
    27.       _pts = new Vector3[points];
    28.     } else if(points != _pts.Length) { // the array has different size, so resize it
    29.       Array.Resize(ref _pts, points);
    30.     }
    31.   }
    32.  
    33.   private void computeQuats() {
    34.     _q1 = Quaternion.Euler(angles1);
    35.     _q2 = Quaternion.Euler(angles2);
    36.   }
    37.  
    38.   private void computePoints() {
    39.     if(_pts == null) return; // this prevents further errors, array is invalid
    40.  
    41.     // otherwise, compute all points
    42.     for(int i = 0; i < _pts.Length; i++) {
    43.       var t = (float)i / (float)(points - 1); // *
    44.       _pts[i] = Quaternion.Slerp(_q1, _q2, t) * Vector3.forward;
    45.     }
    46.   }
    47.  
    48.   // * first point will be at 0/(points-1); last point will be at (points-1)/(points-1)
    49.   //   i.e. 0/(16-1) = 0; 7/(16-1) = 0.46; (16-1)/(16-1) = 1
    50.  
    51.   void OnDrawGizmos() {
    52.     if(_pts == null) return; // this prevents further errors, array is invalid
    53.  
    54.     Gizmos.color = Color.yellow;
    55.     for(int i = 1; i < _pts.Length; i++)
    56.       Gizmos.DrawLine(_pts[i - 1], _pts[i]);
    57.   }
    58.  
    59. }
     
    Last edited: Oct 27, 2020
  9. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,113
    Admittedly, after trying this, the results aren't satisfying at all (and the arc isn't guaranteed to lie on the great circle), so I advise the following change

    instead of line 44, do
    Code (csharp):
    1. _pts[i] = Vector3.Slerp(_q1 * Vector3.forward, _q2 * Vector3.forward, t);
    Now this is the correct solution.
    Quaternion.Slerp is really only useful for rotations it seems.

    Sorry for not replying with the fix earlier, haven't actually tried it before.
    (The same applies to animation examples above.)
     
  10. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,113
    To redeem myself here is a tested variant that allows you to modify sphere's center and radius, while showing you the actual great circle (thus computes the plane of this circle as well).

    Code (csharp):
    1. using System;
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5. [ExecuteInEditMode]
    6. public class GreatCircleArcDemo : MonoBehaviour {
    7.  
    8.   [Range(2, 64)] public int arcPoints = 16;
    9.   public Vector3 sphereCenter = Vector3.zero;
    10.   public float sphereRadius = 1f;
    11.   public Vector3 pos1 = Vector3.zero;
    12.   public Vector3 pos2 = Vector3.zero;
    13.   public float gizmoScale = .1f;
    14.   public bool showGreatCircle = true;
    15.  
    16.   private Quaternion _q1, _q2;
    17.   private Vector3[] _pts;
    18.  
    19.   void OnValidate() {
    20.     makeArrayOrFixOldOne();
    21.     computeQuats();
    22.     computePoints();
    23.   }
    24.  
    25.   private void makeArrayOrFixOldOne() {
    26.     if(arcPoints < 2) {
    27.       _pts = null;
    28.       return;
    29.     }
    30.  
    31.     if(_pts == null) {
    32.       _pts = new Vector3[arcPoints];
    33.     } else if(arcPoints != _pts.Length) {
    34.       Array.Resize(ref _pts, arcPoints);
    35.     }
    36.   }
    37.  
    38.   private void computeQuats() {
    39.     _q1 = Quaternion.FromToRotation(Vector3.forward, (pos1 - sphereCenter).normalized);
    40.     _q2 = Quaternion.FromToRotation(Vector3.forward, (pos2 - sphereCenter).normalized);
    41.   }
    42.  
    43.   private void computePoints() {
    44.     if(_pts == null) return;
    45.  
    46.     for(int i = 0; i < _pts.Length; i++) {
    47.       var t = (float)i / (float)(arcPoints - 1);
    48.       _pts[i] = sphereCenter + sphereRadius * Vector3.Slerp(_q1 * Vector3.forward, _q2 * Vector3.forward, t);
    49.     }
    50.   }
    51.  
    52.   void OnDrawGizmos() {
    53.     if(_pts == null) return;
    54.  
    55.     drawStartEnd();
    56.     drawGreatArc();
    57.     if(showGreatCircle) drawGreatCircle();
    58.   }
    59.  
    60.   void drawStartEnd() {
    61.     var actualPos1 = sphereCenter + sphereRadius * (_q1 * Vector3.forward);
    62.     var actualPos2 = sphereCenter + sphereRadius * (_q2 * Vector3.forward);
    63.  
    64.     Handles.color = Color.blue;
    65.     Handles.DotHandleCap(0, pos1, Quaternion.identity, .15f * gizmoScale, EventType.Repaint);
    66.     Handles.DotHandleCap(0, actualPos1, Quaternion.identity, .3f * gizmoScale, EventType.Repaint);
    67.  
    68.     Handles.color = Color.red;
    69.     Handles.DotHandleCap(0, pos2, Quaternion.identity, .15f * gizmoScale, EventType.Repaint);
    70.     Handles.DotHandleCap(0, actualPos2, Quaternion.identity, .3f * gizmoScale, EventType.Repaint);
    71.   }
    72.  
    73.   void drawGreatArc() {
    74.     Gizmos.color = Color.yellow;
    75.     for(int i = 1; i < _pts.Length; i++)
    76.       Gizmos.DrawLine(_pts[i - 1], _pts[i]);
    77.   }
    78.  
    79.   void drawGreatCircle() {
    80.     var axis = computeAxis();
    81.     var last = _pts[0];
    82.     var angle = 360f / (float)31;
    83.     var arm = _pts[0] - sphereCenter;
    84.     Gizmos.color = Color.gray;
    85.     for(int i = 0; i < 32; i++) {
    86.       var next = sphereCenter + Quaternion.AngleAxis((float)i * angle, axis) * arm;
    87.       Gizmos.DrawLine(last, next);
    88.       last = next;
    89.     }
    90.   }
    91.  
    92.   Vector3 computeAxis() {
    93.     var a = _pts[0];
    94.     var b = _pts[_pts.Length - 1];
    95.     var c = sphereCenter;
    96.     return Vector3.Cross(b - a, c - a).normalized;
    97.   }
    98.  
    99. }
    To obtain the great circle's plane:
    Code (csharp):
    1. Plane computePlane() => new Plane(computeAxis(), sphereCenter);