Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Twisting Issues Copying NavMeshPath to CinemachinePath and CinemachineSmoothPath

Discussion in 'Cinemachine' started by nbannist_PU, Feb 19, 2019.

  1. nbannist_PU

    nbannist_PU

    Joined:
    Feb 12, 2019
    Posts:
    4
    Below is my code for creating a CinemachineSmoothPath from two end points projected onto the NavMesh. It mostly works. It seems to go wrong in areas where path segment length varies greatly or turn direction changes. As you can see in the image, tight corners tend to result in twisted or kinked dolly tracks.

    upload_2019-2-19_12-0-44.png

    From the docs,
    So I am guessing the Bezier control points are auto-calculated based on tangents and edge length. Unfortunately, neither the control points nor the values used in their calculation are exposed, so I can't manipulate them myself.

    I tried inheriting CinemachinePath instead of CinemachineSmoothPath, calculating my own tangents and setting resolution to 1. This appears to solve the issue looking at the gizmos, but the interpolation still behaves as though it has those kinks and twists.

    I am going to try using midpoints of the NavMeshPath segments to try and avoid some of the tight corners. Any other ideas on how to fix this?

    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3. using UnityEngine.AI;
    4. using Cinemachine;
    5.  
    6. using Sirenix.OdinInspector;
    7.  
    8. public class CinemachineNavMeshPath : CinemachineSmoothPath {
    9.  
    10.     public Transform Start, End;  
    11.  
    12.     [Button]
    13.     public bool Generate () {
    14.         return Generate (Start, End);
    15.     }
    16.  
    17.     public bool Generate (Transform start, Transform end) {
    18.         Vector3 floor = new Vector3 (1f,0f,1f);
    19.         Vector3 startpos, endpos;
    20.  
    21.         //Find point on navmesh closest to starting position
    22.         NavMeshHit hit = new NavMeshHit ();
    23.         if (NavMesh.SamplePosition (Vector3.Scale (floor, start.position), out hit, 1f, NavMesh.AllAreas)) {
    24.             startpos = hit.position;
    25.         }
    26.         else {
    27.             DebugLog.Error (start.name + " not in range of NavMesh.");
    28.             return false;
    29.         }
    30.  
    31.         //Find point on navmesh closest to ending position
    32.         if (NavMesh.SamplePosition (Vector3.Scale (floor, end.position), out hit, 1f, NavMesh.AllAreas)) {
    33.             endpos = hit.position;
    34.         }
    35.         else {
    36.             DebugLog.Error (end.name + " not in range of NavMesh.");
    37.             return false;
    38.         }
    39.  
    40.         //Calculate navmesh path
    41.         NavMeshPath nm_path = new NavMeshPath ();
    42.         if (NavMesh.CalculatePath (start.position, end.position, NavMesh.AllAreas, nm_path)) {
    43.             int count = nm_path.corners.Length;
    44.             m_Waypoints = new Waypoint [count];
    45.  
    46.             for (int i = 0; i < count; i++) {
    47.                 //Copy positions from navmesh path
    48.                 m_Waypoints [i] = new Waypoint ();
    49.                 m_Waypoints [i].position = nm_path.corners [i];
    50.  
    51.                 //Calculate Tangents (Use this for CinemachinePath, comment out for CinemachineSmoothPath)
    52.                 //Vector3 edgeIn = Vector3.zero, edgeOut = Vector3.zero;
    53.                 //if (i > 0)
    54.                 //    edgeIn = nm_path.corners [i] - nm_path.corners [i - 1];              
    55.                 //if (i < count - 1)
    56.                 //    edgeOut = nm_path.corners [i + 1] - nm_path.corners [i];              
    57.                 //m_Waypoints [i].tangent = 0.5f * edgeIn.normalized + 0.5f * edgeOut.normalized;
    58.             }          
    59.             return true;
    60.         }
    61.         else {
    62.             DebugLog.Error ("No path exists between " + start.name + " and " + end.name);
    63.             return false;
    64.         }
    65.     }
    66.  
    67.     [DrawGizmo (GizmoType.Selected)]
    68.     static void drawConnections (CinemachineNavMeshPath path, GizmoType gizmoType) {
    69.         Gizmos.color = Color.red;
    70.         for (int i = 1; i < path.m_Waypoints.Length; i++) {
    71.             Gizmos.DrawLine (path.m_Waypoints[i-1].position, path.m_Waypoints [i].position);
    72.         }
    73.     }
    74.  
    75. }
    76.  
     
  2. nbannist_PU

    nbannist_PU

    Joined:
    Feb 12, 2019
    Posts:
    4
    Made the following changes to grab the midpoints instead of the corners:
    Code (CSharp):
    1. int count = nm_path.corners.Length + 1;
    2.             m_Waypoints = new Waypoint [count];
    3.  
    4.             for (int i = 0; i < count; i++) {
    5.                 //Copy positions from navmesh path
    6.                 m_Waypoints [i] = new Waypoint ();
    7.                 if (i == 0)
    8.                     m_Waypoints [i].position = nm_path.corners [i];
    9.                 else if (i == count - 1) {
    10.                     m_Waypoints [i].position = nm_path.corners [i - 1];
    11.                 }
    12.                 else {
    13.                     m_Waypoints [i].position = 0.5f * (nm_path.corners [i] + nm_path.corners [i - 1]);
    14.                 }
    It looks much better but still requires damping if using Body.CameraUp = Path on the virtual camera. But a LookAt Target works much better in a tight environment like mine.
    upload_2019-2-19_12-31-7.png
     
  3. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,658
    The resolution setting affects only the way the gizmo is drawn, and the segmented approximation of the path used when calculating NearestPointOnPath. It has no impact on the actual path shape.

    Both CinemachinePath and CinemachineSmoothPath are attempting to ensure a degree of path smoothness (SmoothPath more so, as it auto-generates the tangents), so not all path shapes can be created - specifically, non-smooth ones are excluded.

    If you want more control over the actual path shape, you can implement your own path by inheriting from CinemachinePathBase class. That would be my recommendation.
     
  4. nbannist_PU

    nbannist_PU

    Joined:
    Feb 12, 2019
    Posts:
    4
    Thank you, this is excellent info and clears up my confusion about the resolution setting.

    So in terms of implementing a custom path, that would essentially entail implementing the abstract methods listed here, correct? Also, it looks like CinemachinePath and CinemachineSmoothPath have their own waypoint structs. So I would have to write my own that is appropriate for my custom path?

    Am I missing anything else?
     
  5. Gregoryl

    Gregoryl

    Unity Technologies

    Joined:
    Dec 22, 2016
    Posts:
    7,658
    Yes, exactly. You can have a look at the implementation of CinemachinePath or CinemachineSmoothPath, and use them as examples or starting points.

    Oh, and it was very clever of you to use the midpoints of your segments, instead of the corners, as waypoints.
     
  6. nbannist_PU

    nbannist_PU

    Joined:
    Feb 12, 2019
    Posts:
    4
    I found something missing from my code above so here it is for the sake of completeness:
    After the waypoints array is updated InvalidateDistanceCache() needs to be called. I inserted it between the for loop and the return true.