# Bezier Solution [Open Source]

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

1. ### 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

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

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

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:

5. ### 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

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

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.

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

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

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)?

File size:
41.4 KB
Views:
99
10. ### 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

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

Joined:
Aug 1, 2011
Posts:
1,690
That's really weird. Are you sure that you didn't type
``if( isBottomSpline = true )``
``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

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

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

15. ### 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

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

yasirkula likes this.
17. ### 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

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

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

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

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

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

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

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

25. ### 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

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

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

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

Joined:
Aug 1, 2011
Posts:
1,690
30. ### 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);
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

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

32. ### 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

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

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

35. ### xill

Joined:
Mar 13, 2013
Posts:
4
Works like a charm.

yasirkula likes this.
36. ### 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

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

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

39. ### 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!

Joined:
Aug 1, 2011
Posts:
1,690
41. ### 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?

Joined:
Aug 1, 2011
Posts:
1,690
43. ### 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

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

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

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

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