Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Bezier Solution [Open Source]

Discussion in 'Assets and Asset Store' started by yasirkula, Nov 12, 2016.

  1. siempus

    siempus

    Joined:
    Nov 29, 2017
    Posts:
    9
    Yes, that's what I mean by resetting them. Here's what you need to do to reproduce it in the demo scene:

    1) In the hierarchy, choose the face-shaped spline. Then in inspector, uncheck the "Loop".
    2) Then unfold the spline in the hierarchy to expose all its points. Then choose all its points and do the same thing: set the Z positions into 0. This will make all the points in one plane.
    3) Now, select the first point, and then set the "Preceding Control Point" and "Following Control Point" into (0,0,0) in the inspector window.
    4) Back to hierarchy, select "CubeConstantSpeed". Then set the "Travel Mode" into "Ping Pong". This step is not necessary but make things easier to see the problem later.
    5) Now start playing.

    I hope I describe the step correctly. Sorry for the late reply and thank you for your quick reply.
     
  2. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    Thank you for the detailed steps, I was able to reproduce the issue. However, I'm not sure how I should approach this problem. It is caused by this function:

    Code (CSharp):
    1. public Vector3 MoveAlongSpline( ref float normalizedT, float deltaMovement, int accuracy = 3 )
    2. {
    3.     // Credit: https://gamedev.stackexchange.com/a/27138
    4.  
    5.     float constant = deltaMovement / ( ( loop ? endPoints.Count : endPoints.Count - 1 ) * accuracy );
    6.     for( int i = 0; i < accuracy; i++ )
    7.         normalizedT += constant / GetTangent( normalizedT ).magnitude; // Problematic line
    8.  
    9.     return GetPoint( normalizedT );
    10. }
    GetTangent returns 0 if controls points are set to (0,0,0). Then normalizedT immediately becomes Infinity. I think I can force the control points to have a minimum magnitude of 0.1 which would resolve this issue. What do you think?
     
    siempus likes this.
  3. siempus

    siempus

    Joined:
    Nov 29, 2017
    Posts:
    9
    I personally like the code as it because if I work in 3d, I can still have (0,0,0) control points. But I'm sure people out there will stumble on the same issue sooner or later

    Setting up minimum control point magnitude will solve this problem and I'm okay with that. However, If possible, I rather solve the issue by manipulating the "GetTangent( normalizedT ).magnitude" to avoid infinity. Because the infinity issue seems only affecting this "MoveAlongSpline" function. This will leave everything else intact.

    I don't know much about math and this Tangent stuff surely beyond my grasp. Sorry if I say something ridiculous.
     
  4. siempus

    siempus

    Joined:
    Nov 29, 2017
    Posts:
    9
    Or, you can simply put "yellow warning icon" on control point inspector about that specific issue. Just like what Unity does:

    Screen Shot 2019-07-07 at 07.25.07.png
     
  5. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    These are really nice recommendations, thank you! I'll see what I can do.

    P.S. Updated the plugin on GitHub to show a warning message as you've suggested.
     
    Last edited: Jul 7, 2019
    siempus likes this.
  6. siempus

    siempus

    Joined:
    Nov 29, 2017
    Posts:
    9
    You're very welcome. Your plugin is one of the best there is and I'm sure anybody is willing to help you to improve it.
     
    yasirkula likes this.
  7. EatHeat

    EatHeat

    Joined:
    May 12, 2015
    Posts:
    5
    I just came upon this plugin while dealing with moving a Direction Light in a path - https://forum.unity.com/threads/moving-directional-light-in-sunpath.717725/. This has helped me achieve what I was trying to do. However, I am stuck at a certain point.

    I am trying to move a sun on two separate splines with a Slider. Here is an image for reference. Spline 1 is the topSpline and Spline 2 is the bottomSpline. Initially the Sun is on Spline 2 and then I need to move it to Spline 1 at runtime.

    sunpath2.jpg

    Here is the code that I am using -

    Code (CSharp):
    1.  
    2.     //Spline Changing Button
    3.     public Button splineButton;
    4.     private int counter = 0;
    5.  
    6.     private Transform cachedTransform;
    7.  
    8.     private bool isBottomSpline;
    9.     public BezierSpline splineBottom;
    10.     public BezierSpline splineTop;
    11.  
    12.     public float progress = 0f;
    13.  
    14.     //public BezierSpline Spline { get { return spline; } }
    15.  
    16.     public float NormalizedT
    17.     {
    18.         get { return progress; }
    19.         set { progress = value; }
    20.     }
    21.  
    22.     public float movementLerpModifier = 10f;
    23.     public float rotationLerpModifier = 10f;
    24.  
    25.     public bool lookForward = true;
    26.  
    27.     private bool isGoingForward = true;
    28.     public bool MovingForward { get { return isGoingForward; } }
    29.  
    30.     public UnityEvent onProgressChanged = new UnityEvent();
    31.  
    32.     private void Awake()
    33.     {
    34.         cachedTransform = transform;
    35.     }
    36.  
    37.     void Start()
    38.     {
    39.         isBottomSpline = true;
    40.     }
    41.  
    42.     private void Update()
    43.     {
    44.         if (isBottomSpline)
    45.         {
    46.             cachedTransform.position = Vector3.Lerp(cachedTransform.position, splineBottom.GetPoint(progress), movementLerpModifier * Time.deltaTime);
    47.             bool movingForward = MovingForward;
    48.  
    49.             //Rest of the code from BezierWalkerWithSpeed
    50.             Debug.Log("1 + isBottomSpline = " + isBottomSpline);
    51.         }
    52.         else
    53.         {
    54.             cachedTransform.position = Vector3.Lerp(cachedTransform.position, splineTop.GetPoint(progress), movementLerpModifier * Time.deltaTime);
    55.             bool movingForward = MovingForward;
    56.  
    57.             //Rest of the code from BezierWalkerWithSpeed
    58.             Debug.Log("2 + isBottomSpline = " + isBottomSpline);
    59.         }
    60.  
    61.     }
    62.  
    63.     public void MoveSunAlongPath(Slider slider)
    64.     {
    65.         progress = slider.value;
    66.     }
    67.  
    68.     public void toggleSpline()
    69.     {
    70.         counter++;
    71.         if (counter % 2 == 0)
    72.         {
    73.             isBottomSpline = true;
    74.             Debug.Log("3 + isBottomSpline = " + isBottomSpline);
    75.         }
    76.         else
    77.         {
    78.             isBottomSpline = false;
    79.             Debug.Log("4 + isBottomSpline = " + isBottomSpline);
    80.         }
    81.     }
    When the Sun is on Spline 2, it is working as intended. Now the issue is that when I am trying to change the value of isBottomSpline to false at runtime, the Sun is not moving onto Spline 1. But when I made isBottomSpline public and changed it to false through inspector, it moved onto Spline 1 at runtime. Also, Debug Log "2" is only getting called when I am changing the value to false through the inspector. What am I doing wrong?
     
  8. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    When do you call toggleSpline? I've tested the script in my project and assigned the toggleSpline function to a button in my canvas. Clicking the button did toggle between the two splines.
     
  9. EatHeat

    EatHeat

    Joined:
    May 12, 2015
    Posts:
    5
    I have assigned the toggleSpline to a Button as well but clicking the Button does not change the spline. Debug.Log("4") gets called but Debug.Log("2") does not. I have attached a screenshot of my Inspector. Here is the complete code for my Update() method.

    Code (CSharp):
    1. private void Update()
    2.     {
    3.         if (isBottomSpline == true)
    4.         {
    5.             cachedTransform.position = Vector3.Lerp(cachedTransform.position, splineBottom.GetPoint(progress), movementLerpModifier * Time.deltaTime);
    6.             bool movingForward = MovingForward;
    7.             if (lookForward)
    8.             {
    9.                 Quaternion targetRotation;
    10.                 if (movingForward)
    11.                     targetRotation = Quaternion.LookRotation(splineBottom.GetTangent(progress));
    12.                 else
    13.                     targetRotation = Quaternion.LookRotation(-splineBottom.GetTangent(progress));
    14.  
    15.                 cachedTransform.rotation = Quaternion.Lerp(cachedTransform.rotation, targetRotation, rotationLerpModifier * Time.deltaTime);
    16.             }
    17.  
    18.             if (movingForward)
    19.             {
    20.                 if (progress >= 1f)
    21.                 {
    22.                     progress = 1f;
    23.                 }
    24.                 onProgressChanged.Invoke();
    25.             }
    26.             else
    27.             {
    28.                 if (progress <= 0f)
    29.                 {
    30.                     progress = 0f;
    31.                 }
    32.                 onProgressChanged.Invoke();
    33.             }
    34.             Debug.Log("1 + isBottomSpline = " + isBottomSpline);
    35.         }
    36.         else
    37.         {
    38.             cachedTransform.position = Vector3.Lerp(cachedTransform.position, splineTop.GetPoint(progress), movementLerpModifier * Time.deltaTime);
    39.             bool movingForward = MovingForward;
    40.             if (lookForward)
    41.             {
    42.                 Quaternion targetRotation;
    43.                 if (movingForward)
    44.                     targetRotation = Quaternion.LookRotation(splineTop.GetTangent(progress));
    45.                 else
    46.                     targetRotation = Quaternion.LookRotation(-splineTop.GetTangent(progress));
    47.  
    48.                 cachedTransform.rotation = Quaternion.Lerp(cachedTransform.rotation, targetRotation, rotationLerpModifier * Time.deltaTime);
    49.             }
    50.  
    51.             if (movingForward)
    52.             {
    53.                 if (progress >= 1f)
    54.                 {
    55.                     progress = 1f;
    56.                 }
    57.                 onProgressChanged.Invoke();
    58.             }
    59.             else
    60.             {
    61.                 if (progress <= 0f)
    62.                 {
    63.                     progress = 0f;
    64.                 }
    65.                 onProgressChanged.Invoke();
    66.             }
    67.             Debug.Log("2 + isBottomSpline = " + isBottomSpline);
    68.         }
    69.  
    70.     }
    On another note, I have attached my Directional Light to the Sun. Is it possible to rotate it while moving in a manner that it keeps on facing the center of the model (0,0,0)?
     
  10. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    Here's the complete script I'm using:

    Code (CSharp):
    1. using BezierSolution;
    2. using UnityEngine;
    3. using UnityEngine.Events;
    4. using UnityEngine.UI;
    5.  
    6. public class SplineTest : MonoBehaviour
    7. {
    8.     //Spline Changing Button
    9.     public Button splineButton;
    10.     private int counter = 0;
    11.  
    12.     private Transform cachedTransform;
    13.  
    14.     private bool isBottomSpline;
    15.     public BezierSpline splineBottom;
    16.     public BezierSpline splineTop;
    17.  
    18.     public float progress = 0f;
    19.  
    20.     //public BezierSpline Spline { get { return spline; } }
    21.  
    22.     public float NormalizedT
    23.     {
    24.         get { return progress; }
    25.         set { progress = value; }
    26.     }
    27.  
    28.     public float movementLerpModifier = 10f;
    29.     public float rotationLerpModifier = 10f;
    30.  
    31.     public bool lookForward = true;
    32.  
    33.     private bool isGoingForward = true;
    34.     public bool MovingForward { get { return isGoingForward; } }
    35.  
    36.     public UnityEvent onProgressChanged = new UnityEvent();
    37.  
    38.     private void Awake()
    39.     {
    40.         cachedTransform = transform;
    41.     }
    42.  
    43.     void Start()
    44.     {
    45.         isBottomSpline = true;
    46.     }
    47.  
    48.     private void Update()
    49.     {
    50.         if( isBottomSpline == true )
    51.         {
    52.             cachedTransform.position = Vector3.Lerp( cachedTransform.position, splineBottom.GetPoint( progress ), movementLerpModifier * Time.deltaTime );
    53.             bool movingForward = MovingForward;
    54.             if( lookForward )
    55.             {
    56.                 Quaternion targetRotation;
    57.                 if( movingForward )
    58.                     targetRotation = Quaternion.LookRotation( splineBottom.GetTangent( progress ) );
    59.                 else
    60.                     targetRotation = Quaternion.LookRotation( -splineBottom.GetTangent( progress ) );
    61.  
    62.                 cachedTransform.rotation = Quaternion.Lerp( cachedTransform.rotation, targetRotation, rotationLerpModifier * Time.deltaTime );
    63.             }
    64.  
    65.             if( movingForward )
    66.             {
    67.                 if( progress >= 1f )
    68.                 {
    69.                     progress = 1f;
    70.                 }
    71.                 onProgressChanged.Invoke();
    72.             }
    73.             else
    74.             {
    75.                 if( progress <= 0f )
    76.                 {
    77.                     progress = 0f;
    78.                 }
    79.                 onProgressChanged.Invoke();
    80.             }
    81.             Debug.Log( "1 + isBottomSpline = " + isBottomSpline );
    82.         }
    83.         else
    84.         {
    85.             cachedTransform.position = Vector3.Lerp( cachedTransform.position, splineTop.GetPoint( progress ), movementLerpModifier * Time.deltaTime );
    86.             bool movingForward = MovingForward;
    87.             if( lookForward )
    88.             {
    89.                 Quaternion targetRotation;
    90.                 if( movingForward )
    91.                     targetRotation = Quaternion.LookRotation( splineTop.GetTangent( progress ) );
    92.                 else
    93.                     targetRotation = Quaternion.LookRotation( -splineTop.GetTangent( progress ) );
    94.  
    95.                 cachedTransform.rotation = Quaternion.Lerp( cachedTransform.rotation, targetRotation, rotationLerpModifier * Time.deltaTime );
    96.             }
    97.  
    98.             if( movingForward )
    99.             {
    100.                 if( progress >= 1f )
    101.                 {
    102.                     progress = 1f;
    103.                 }
    104.                 onProgressChanged.Invoke();
    105.             }
    106.             else
    107.             {
    108.                 if( progress <= 0f )
    109.                 {
    110.                     progress = 0f;
    111.                 }
    112.                 onProgressChanged.Invoke();
    113.             }
    114.             Debug.Log( "2 + isBottomSpline = " + isBottomSpline );
    115.         }
    116.  
    117.     }
    118.  
    119.     public void MoveSunAlongPath( Slider slider )
    120.     {
    121.         progress = slider.value;
    122.     }
    123.  
    124.     public void toggleSpline()
    125.     {
    126.         counter++;
    127.         if( counter % 2 == 0 )
    128.         {
    129.             isBottomSpline = true;
    130.             Debug.Log( "3 + isBottomSpline = " + isBottomSpline );
    131.         }
    132.         else
    133.         {
    134.             isBottomSpline = false;
    135.             Debug.Log( "4 + isBottomSpline = " + isBottomSpline );
    136.         }
    137.     }
    138. }
    Clicking the button does change the value of isBottomSpline for me. Unless there is an exception, its value must change, it is a very simple and straightforward function. I'm guessing that toggleSpline isn't getting called at all, you can see if this is the case by putting a Debug.Log at its first line. Maybe the button is somehow losing the functions registered to its On Click event at runtime? Or maybe the button you configure from the Inspector isn't the button you are clicking?

    The easiest way to rotate the sun would be to put a GameObject at 0,0,0 and call sunTransform.LookAt(objectAtCenter.transform) in Update.
     
  11. EatHeat

    EatHeat

    Joined:
    May 12, 2015
    Posts:
    5
    Thank you for the quick reply and sorry if I was not clear with my issue in the earlier post. The value of isBottomSpline changes for me as well and toggleSpline() is also getting called. So the problem is not with toggleSpline() getting called. My issue is that even when isBottomSpline is changed to false at runtime, the Sun is not shifting onto the topSpline in the Update() method. All my debugs except
    Debug.Log( "2 + isBottomSpline = " + isBottomSpline );
    are getting called.
     
  12. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    That's really weird. Are you sure that you didn't type
    if( isBottomSpline = true )
    instead of
    if( isBottomSpline == true )
    in Update? The code I've sent you works without any issues, can you test it on a new scene?
     
  13. EatHeat

    EatHeat

    Joined:
    May 12, 2015
    Posts:
    5
    After a bit of trial and error, I decided to make isBottomSpline static and moved toggleSpline() and isBottomSpline to a separate class. Then I called isBottomSpline from the Update() method of my SunpathControl class and I have no idea why but it worked flawlessly. Funny thing is it is the exact code that I posted above just moved to a separate class.

    Also thank you for the tip regarding rotating the sun. That worked perfectly as well. :)
     
    yasirkula likes this.
  14. Will_Dev

    Will_Dev

    Joined:
    May 24, 2017
    Posts:
    24
    Hi. I wanted to ask if the autoconstructspline method is expensive to use in runtime.
     
  15. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    I haven't tried to call it frequently at runtime. If you test it on your own, please share your findings here, as well :)
     
  16. bigChris

    bigChris

    Joined:
    Jul 5, 2017
    Posts:
    8
    I'm instantiating 12 splines at run time without issue.
     
    yasirkula likes this.
  17. bigChris

    bigChris

    Joined:
    Jul 5, 2017
    Posts:
    8
    Hello Yasirkula,

    What a wonderfully useful framework!!

    Greatly simplified, all in code, consider Spline_1 set to loop. Then StartSpline, essentially a “spur”, that has it’s endpoint set to a point on Spline_1 at Spline_1’s NormalizedT = to 1/12th (0.8333333…) and swapping the onPathCompleted Event to the next Event (a lot more happens involving a lot more splines). This all works well - objects travel down the spline(s) and smoothly transition to the next spline as needed…

    The only issue is when the object actually reaches the end of the StartSpline and transitions to Spline_1, (and all other spline transition points) at that moment, the call:

    targetRotation = Quaternion.LookRotation(spline.GetTangent(progress));


    …causes a “twitch” in the object, as

    StartSpline.GetTangent(progress)

    and

    Spline_1.GetTangent(progress)

    are so different. The spline change takes effect in FixedUpdate (superfast moving objects), so I’m thinking what is needed is to somehow change StartSpline so that at points approaching Spline_1, the difference in tangent values become much less and minimizes the “twitch”.

    I think the solution is to get a reference to the endpoint in StartSpline and modify it such that the points tangent value matches up with Spline_1 point at NormalizedT = 0.8333333… I’ve looked at the “Shape the spline” section on your GitHub repository, as well as the functions defined within BezierPoint.cs and BezierSpline.cs, and can’t figure out how to do this in code.

    Any thoughts greatly appreciated. Thanks again!!
     
  18. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    GetTangent returns the following value at the final point of a non-looping spline:

    Code (CSharp):
    1. if( normalizedT >= 1f )
    2. {
    3.     int index = endPoints.Count - 1;
    4.     return 3f * ( endPoints[index].position - endPoints[index].precedingControlPointPosition );
    5. }
    So it is affected only by the relative position of the final point's preceding control point. As you can't change the final point's position itself in your case, you need to adjust the position of StartSpline's final point's preceding control point. So, you can try something like this:

    Code (CSharp):
    1. BezierSpline startSpline = ...;
    2. Vector3 spline1TangentAtIntersection = ...;
    3. int index = startSpline.Count - 1;
    4. startSpline[index].precedingControlPointPosition = startSpline[index].position - spline1TangentAtIntersection / 3f;
     
  19. bigChris

    bigChris

    Joined:
    Jul 5, 2017
    Posts:
    8
    That worked!!

    Code (CSharp):
    1. Vector3 nextSplineTangentAtIntersection = allSplines[k].GetTangent(0.08333333333333333f);
    2. int index = startSpline.Count - 1;
    3. startSpline[index].precedingControlPointPosition = paths[k][1].waypointLocation -
    4.     nextSplineTangentAtIntersection / 3f;
    where k is the for loop iterator,
    0.83333... is the NormalizdT (progress) at startSpline to nextSpline transition point (1/12th of the way down nextSpline),
    and paths[k][1].waypointLocation is simply the Vector3 that precisely denotes the transition point from which we subtract the Vector3 nextSplineTangentAtIntersection / 3f. When all of that is set to the precedingControlPointPosition, the startSpline endpoint 1 is essentially rotated so that its tangent matches the nextsplines Tangent and we get a nice no twitch transition!
    Thanks again!
     
    yasirkula likes this.
  20. KOemxe

    KOemxe

    Joined:
    May 1, 2018
    Posts:
    33
    Hey @yasirkula,

    This seems super promising, but have you seen and know a possible implementation of leading a race car with this kind of spline locomotive? I've been struggling to come up with a good way to "lead" a little bit ahead of my race car to follow a similar structure moving between ends of curves as waypoints, but so far it seems I have either my object move too far or not far enough ahead of the car...
     
  21. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    I see. You can try this:
    • Call bezierSpline.FindNearestPointTo for player's race car to find its approximate position (normalizedT) on the spline
    • Call bezierSpline.MoveAlongSpline with the calculated normalizedT to calculate the point on the spline that is approximately deltaMovement meters ahead of normalizedT
    • Both of these functions have an optional accuracy parameter for more accurate results
     
    KOemxe likes this.
  22. GaZnoDrone

    GaZnoDrone

    Joined:
    Mar 3, 2015
    Posts:
    28
    Hey guys,
    I am sorry if its ther in the documents.
    I am using bezierWalkerWithSpeed.
    I have made some modification just for my game.

    on update

    if(m_bBallKicked)
    Execute( Time.deltaTime );

    no when i stop it by making m_bBallKicked = false

    now how do I reset it from the first point now
     
  23. GaZnoDrone

    GaZnoDrone

    Joined:
    Mar 3, 2015
    Posts:
    28

    I DID THIS, when ever I needed to reset the path traveled. Is this the correct way to do it?
    m_normalizedT = 0;
     
    yasirkula likes this.
  24. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    Yes, that is the correct way.
     
  25. KOemxe

    KOemxe

    Joined:
    May 1, 2018
    Posts:
    33
    Late response, I know. When you're finding the nearest point, are you ONLY going to get the point as it's been laid down on the spline? Like, the spline might have 51 points. Are you only going to return one of THOSE points with the FindNearestPointTo function? Or are able to get a point that's, say, IN BETWEEN two of those points on the spline?
     
  26. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    It will usually return a point that's in between two of the 51 points on the spline.
     
    KOemxe likes this.
  27. KOemxe

    KOemxe

    Joined:
    May 1, 2018
    Posts:
    33
    @yasirkula OMG THANK YOU SO MUCH.

    I have to admit, I didn't really use all of your source code, only a couple of key functions related to exactly what you mentioned. It was JUST what I needed. You're the bauss, my dude.
     
    yasirkula likes this.
  28. yaboo

    yaboo

    Joined:
    Mar 23, 2016
    Posts:
    38
    @yasirkula
    Add a line renderer component if you have time, thanks
     
  29. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
  30. xill

    xill

    Joined:
    Mar 13, 2013
    Posts:
    4
    Hey Yasir great asset, quick question how can I reset progress like
    normalizedT = 0; after completing the path and changing target spline.
    It works when I put 0 in editor but not via script.

    Code (CSharp):
    1.  
    2.     myMover = GetComponent<BezierSolution.BezierWalkerWithSpeed>();
    3. ///
    4.         myMover.progress = 0;
    5.     myMover.spline = myNextPath;
    6.  
    Also tried onPathCompletedCalledAt0&1 = false;

    I can do this
    Code (CSharp):
    1.  Destroy(myMover);
    2.         myMover = gameObject.AddComponent<BezierSolution.BezierWalkerWithSpeed>();
    3. myMover.progress = 0;
    4.     myMover.spline = myNextPath;
    5.  
    but I don't think thats the way and it limits what I'm trying to do
     
    Last edited: Apr 3, 2020
  31. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    Try inserting
    myMover.Execute(0f);
    to the bottom.
     
  32. xill

    xill

    Joined:
    Mar 13, 2013
    Posts:
    4
    Ah just noticed I was using an old version.
    Still... Execute didn't work.

    I don't get how it works when you slide the NormalizedT in the inspector but not when edited via script ¯\_(ツ)_/¯
     
  33. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    I think I've found the issue. Seems like the onPathCompleted event is called before m_normalizedT is updated internally; thus, any changes made to normalizedT inside onPathCompleted were overridden. To fix this issue, try changing the contents of BezierWalkerWithSpeed.cs as follows:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3.  
    4. namespace BezierSolution
    5. {
    6.     public class BezierWalkerWithSpeed : BezierWalker
    7.     {
    8.         public BezierSpline spline;
    9.         public TravelMode travelMode;
    10.  
    11.         public float speed = 5f;
    12.         [SerializeField]
    13.         [Range( 0f, 1f )]
    14.         private float m_normalizedT = 0f;
    15.  
    16.         public override BezierSpline Spline { get { return spline; } }
    17.  
    18.         public override float NormalizedT
    19.         {
    20.             get { return m_normalizedT; }
    21.             set { m_normalizedT = value; }
    22.         }
    23.  
    24.         //public float movementLerpModifier = 10f;
    25.         public float rotationLerpModifier = 10f;
    26.  
    27.         [System.Obsolete( "Use lookAt instead", true )]
    28.         [System.NonSerialized]
    29.         public bool lookForward = true;
    30.         public LookAtMode lookAt = LookAtMode.Forward;
    31.  
    32.         private bool isGoingForward = true;
    33.         public override bool MovingForward { get { return ( speed > 0f ) == isGoingForward; } }
    34.  
    35.         public UnityEvent onPathCompleted = new UnityEvent();
    36.         private bool onPathCompletedCalledAt1 = false;
    37.         private bool onPathCompletedCalledAt0 = false;
    38.  
    39.         private void Update()
    40.         {
    41.             Execute( Time.deltaTime );
    42.         }
    43.  
    44.         public override void Execute( float deltaTime )
    45.         {
    46.             float targetSpeed = ( isGoingForward ) ? speed : -speed;
    47.  
    48.             Vector3 targetPos = spline.MoveAlongSpline( ref m_normalizedT, targetSpeed * deltaTime );
    49.  
    50.             transform.position = targetPos;
    51.             //transform.position = Vector3.Lerp( transform.position, targetPos, movementLerpModifier * deltaTime );
    52.  
    53.             bool movingForward = MovingForward;
    54.  
    55.             if( lookAt == LookAtMode.Forward )
    56.             {
    57.                 Quaternion targetRotation;
    58.                 if( movingForward )
    59.                     targetRotation = Quaternion.LookRotation( spline.GetTangent( m_normalizedT ) );
    60.                 else
    61.                     targetRotation = Quaternion.LookRotation( -spline.GetTangent( m_normalizedT ) );
    62.  
    63.                 transform.rotation = Quaternion.Lerp( transform.rotation, targetRotation, rotationLerpModifier * deltaTime );
    64.             }
    65.             else if( lookAt == LookAtMode.SplineExtraData )
    66.                 transform.rotation = Quaternion.Lerp( transform.rotation, spline.GetExtraData( m_normalizedT, InterpolateExtraDataAsQuaternion ), rotationLerpModifier * deltaTime );
    67.  
    68.             if( movingForward )
    69.             {
    70.                 if( m_normalizedT >= 1f )
    71.                 {
    72.                     if( travelMode == TravelMode.Once )
    73.                         m_normalizedT = 1f;
    74.                     else if( travelMode == TravelMode.Loop )
    75.                         m_normalizedT -= 1f;
    76.                     else
    77.                     {
    78.                         m_normalizedT = 2f - m_normalizedT;
    79.                         isGoingForward = !isGoingForward;
    80.                     }
    81.  
    82.                     if( !onPathCompletedCalledAt1 )
    83.                     {
    84.                         onPathCompletedCalledAt1 = true;
    85. #if UNITY_EDITOR
    86.                         if( UnityEditor.EditorApplication.isPlaying )
    87. #endif
    88.                             onPathCompleted.Invoke();
    89.                     }
    90.                 }
    91.                 else
    92.                 {
    93.                     onPathCompletedCalledAt1 = false;
    94.                 }
    95.             }
    96.             else
    97.             {
    98.                 if( m_normalizedT <= 0f )
    99.                 {
    100.                     if( travelMode == TravelMode.Once )
    101.                         m_normalizedT = 0f;
    102.                     else if( travelMode == TravelMode.Loop )
    103.                         m_normalizedT += 1f;
    104.                     else
    105.                     {
    106.                         m_normalizedT = -m_normalizedT;
    107.                         isGoingForward = !isGoingForward;
    108.                     }
    109.  
    110.                     if( !onPathCompletedCalledAt0 )
    111.                     {
    112.                         onPathCompletedCalledAt0 = true;
    113. #if UNITY_EDITOR
    114.                         if( UnityEditor.EditorApplication.isPlaying )
    115. #endif
    116.                             onPathCompleted.Invoke();
    117.                     }
    118.                 }
    119.                 else
    120.                 {
    121.                     onPathCompletedCalledAt0 = false;
    122.                 }
    123.             }
    124.         }
    125.     }
    126. }
     
  34. xill

    xill

    Joined:
    Mar 13, 2013
    Posts:
    4
    Could'nt test yet thanks for fast reply mate, I'll let you know.
     
  35. xill

    xill

    Joined:
    Mar 13, 2013
    Posts:
    4
    Works like a charm. ;)
     
    yasirkula likes this.
  36. Mr-Oliv

    Mr-Oliv

    Joined:
    Sep 14, 2012
    Posts:
    26
    Thanks for sharing this amazing tool and being available to answer questions!
    Is there a handy way to get the normalized t value from a given point index without iterating over the spline?
    Thanks!
     
  37. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    If spline has 3 points, first point will have t=0, second one t=0.5, and the last one t=1. Lengths of the segments don't contribute to the t values at the moment.
     
  38. Mr-Oliv

    Mr-Oliv

    Joined:
    Sep 14, 2012
    Posts:
    26
    Ah nice! That's actually even better. Thanks!
     
  39. kobi666

    kobi666

    Joined:
    Oct 13, 2014
    Posts:
    2
    Hey there everyone on this thread :)

    I'm using the asset and it's awesome, and I want to be able to do something that i'm having a difficulty with:

    I want to create a walker that starts walking (either forward or backward) on the spline,
    but starting from a point in the middle of it.
    i'm trying to RE this,
    but I would really be happy for a tip \ shortcut on how to do so.

    Thanks!
     
  40. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
  41. ElegantMetal

    ElegantMetal

    Joined:
    Oct 17, 2015
    Posts:
    1
    Love the tool! It's been super smooth to use. I'm using the particle system to visualize a path defined by the user, and it works great. However, it doesn't seem to work in a build at all, and I can't seem to figure out why. Any idea on where I should start looking for the problem?
     
  42. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
  43. Gaviathan2

    Gaviathan2

    Joined:
    May 23, 2013
    Posts:
    13
    Hi, I've got road tile prefabs with splines in that are randomly placed at runtime. I'm using bezierWalkerWithSpeed to move vehicles along these roads. They follow the spline and when OnPathCompleted is called my script finds the closest spline point to the vehicle's current location that is not a child of the current spline. The script will set the next spline to reverse if need be and the normalised T to whatever it should be at that position and direction. The movement takes it in the correct direction and if the next spline is in the same order as the previous one (reversed/not reversed) the transition is smooth. Unfortunately if the next spline is going in the opposite direction the car twitches on the transition,as if it is turning around when it doesn't need to. I don't know if the problem is caused by the way I'm choosing the next spline or by the way I'm setting up the next spline. Here is my code,

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using BezierSolution;
    5. using PowerPlayer;
    6.  
    7. public class GTraffic : MonoBehaviour
    8. {
    9.     private BezierWalkerWithSpeed bWalker;
    10.     public BezierSpline bSpline;
    11.     private BezierPoint[] bpArray;
    12.     private BezierPoint nextBp;
    13.  
    14.     public void NextSpline()
    15.     {
    16.         nextBp = PPHelp.FindClosest3(bpArray, transform.position, bSpline.endPoints);
    17.         bWalker.spline = nextBp.transform.GetComponentInParent<BezierSpline>();
    18.         bSpline = bWalker.spline;
    19.         if(bWalker.spline.endPoints[0] == nextBp)
    20.         {
    21.             bWalker.NormalizedT = 0;
    22.             bWalker.reversed = false;
    23.         }
    24.         else
    25.         {
    26.             bWalker.NormalizedT = 1;
    27.             bWalker.reversed = true;
    28.         }
    29.     }
    30.     // Start is called before the first frame update
    31.     void Start()
    32.     {
    33.         bWalker = GetComponent<BezierWalkerWithSpeed>();
    34.         bpArray = GameObject.FindObjectsOfType(typeof(BezierPoint)) as BezierPoint[];
    35.     }
    36. }
    PPHelp.FindClosest3 (array of all bezier points in scene, the position of the object we want to find the closest object to, A list of bezier points we don't want to include in the search)

    I've probably overcomplicated or over simplified something but I'm hoping you'll know what I'm doing wrong, thanks in advance.

    EDIT - I have the walker set to once and if I disable my script and let it run the spline just once it does the twitch at the end. I tried setting if(m_normalisedT >= 1f) to if(m_normalisedT >= 0.9f) but that made no difference. I was trying to get something to kick in before the twitch could happen but obviously that was not the solution.
     
    Last edited: Aug 2, 2020
  44. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    It is possible that some of the splines have duplicate points at the end (2 points instead of 1) since you've said that the twitch also happens when the walker runs without your script. If you can't spot any issues on the splines, you can set the walker's Look At mode to None and interpolate the rotation yourself using spline.GetTangent (check out this code snippet).
     
  45. turick00

    turick00

    Joined:
    Jul 14, 2020
    Posts:
    29
    Has anybody created a hybrid component to sync this to an ECS system? Or come up with another smart way to manually instantiate the c# objects inside a system and just manually call the methods?
     
  46. turick00

    turick00

    Joined:
    Jul 14, 2020
    Posts:
    29
    Also, @yasirkula, I basically need 1 spline, but once a minute, I want to add a new point to the end of the spline and remove the first point. I'm having one heck of a time. I would submit a code snippet, but I've tried so many different iterations, I'm not sure what to even post.

    My spline currently has 20 points. The initial construction is simple... create the GO with the spline component, then loop through, then iterate over the my data and set each point in order... first data point at 0, last data point at 19. My first algorithm was:

    1) Swap points at index 0 and 19
    2) Update 19's position with the new position data
    3) Remove point at index 0
    4) Call ConstructLinearPath()

    In my mind, this should result in a new point at the end of the path and the initial segment being removed. However, I get weird results. It really looks like it's adding each new point every 60 seconds, but instead of extending the current path, it tries to connect an edge between the first point and the newly created last point. With each following minute, the new point is created, and the line between the start point and the newly created endpoint is updated.

    I then tried to update the new points at the beginning... just inserting at 0 and removing the last point. Then I tried inserting a new point at 19, then swap 20 and 19, then update 19 with the right position, then remove 0. Everything results in totally crazy splines.

    I've tried calling different combinations of `Internal_CheckDirty()` and `ConstructLinearPath()` after each update, but its still all crazy. For context, I'm visualizing satellites orbiting the earth. I will have infinite data available to me, and it should always run and always update. It makes the most sense to just let the satellite continue to travel on it's calculated path (as it changes ever so slightly with each orbit), but just loading and unloading the points.

    Is this reasonable, or was this not intended to dynamically update splines in such a manner? Thanks in advance for any advice you might be able to provide!

    ***UPDATE:

    So I can get it working by a little hackery... basically just creating a new spline and using the old spline's endpoints. While this works, I feel like it's pretty inefficient.

    Code (CSharp):
    1.         foreach (KeyValuePair<string, BezierSpline> kvp in splines)
    2.         {
    3.             // create a new spline and initialize it
    4.             BezierSpline newSpline = new GameObject().AddComponent<BezierSpline>();
    5.             newSpline.Initialize(numberOfPointsToLoad);
    6.            
    7.             // fetch 1 new data point
    8.             Queue<SatellitePosition> queue = _dataFetcher.movementData[kvp.Key];
    9.             SatellitePosition satellitePosition = queue.Dequeue();
    10.            
    11.             // get the original spline's data points -- note i made this list public
    12.             BezierSpline spline = kvp.Value;
    13.             List<BezierPoint> currentPoints = spline.endPoints;
    14.             // get rid of the original first point
    15.             currentPoints.Remove(currentPoints[0]);
    16.            
    17.             //create a new point at the next desired position
    18.             var newPoint = new GameObject().AddComponent<BezierPoint>();
    19.             newPoint.position = satellitePosition.Position * SCALE;
    20.             currentPoints.Add(newPoint);
    21.            
    22.             // set the list of points on the new spline
    23.             newSpline.endPoints = currentPoints;
    24.  
    25.             // swap out the reference to spline
    26.             splines[kvp.Key] = newSpline;
    27.          
    28.             // construct it
    29.             kvp.Value.ConstructLinearPath();
    30.         }
     
    Last edited: Sep 22, 2020
  47. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    1,690
    @turick00 I have only little experience with ECS so I couldn't help with the first topic.

    To extend the spline, you can remove the first point with RemovePointAt function, insert a new point at index 19 with InsertNewPointAt function and then move that newly inserted point to the desired location. There are a few things to consider here:
    • in terms of GC allocations, it will be more efficient to move point 0 to position 1's position, move point 1 to point 2's position, and etc. This way, you'll avoid Destroy (RemovePointAt) and Instantiate (InsertNewPointAt) calls
    • if an object was traversing the spline at the moment it was extended, the object will probably teleport slightly. That's because the object's NormalizedT value will stay the same but that point will now refer to a slightly different position on the spline. I can think of 2 approaches here:
      1. instead of teleporting the spline's points around, lerp them to their target positions every frame. This way, the spline itself will move around smoothly
      2. after modifying the spline, call FindNearestPointTo for each object traversing it and assign the resulting t value to the object's NormalizedT property
     
unityunity