Search Unity

Bezier Solution [Open Source]

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

  1. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    Last edited: May 31, 2023
  2. kenshin

    kenshin

    Joined:
    Apr 21, 2010
    Posts:
    940
    Thanks for sharing! :)
     
  3. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    Now available on Asset Store!
     
    one_one likes this.
  4. ColinAmosGames

    ColinAmosGames

    Joined:
    Dec 11, 2017
    Posts:
    6
    I'm finding this tool very useful for my current project!

    I found one bug in the AutoConstructSpline method (BezierSpline.cs, line 488):

    endPoints[0].precedingControlPointPosition = endPoints[0].position + direction * controlPointDistance * 0.5f;

    The 0.5f multiplier inappropriately shrinks the control handles of the first point of a loop spline, which warps the curve at that point. Removing the * 0.5f fixes this issue.
     
    yasirkula likes this.
  5. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    Sounds great! Thank you for your help.
     
  6. melotraumatic

    melotraumatic

    Joined:
    Jul 24, 2013
    Posts:
    3
    I'm using your bezier solution for my on-rails game. Thanks for making it, it's fantastic! Was wondering if you could add (or point me in the direction to add myself) a way to start a follower moving from a point other than the beginning point. So if I wanted to start 50% of the way on the curve to test that part of my game, I could quickly do so?
     
  7. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    If you are using "Once" travel mode, the fastest way would be to change "private float progress = 0f;" to "public float progress = 0f;". Then, you can give Progress a value in range [0,1] via Inspector. Otherwise, we'd have to make a few more tweaks. Let me know in that case.
     
  8. melotraumatic

    melotraumatic

    Joined:
    Jul 24, 2013
    Posts:
    3
    Worked like a charm, exactly what I needed. Thank you!
     
    yasirkula likes this.
  9. danibacon

    danibacon

    Joined:
    Feb 13, 2017
    Posts:
    9
    Really awesome thanks!
    One question re 2D usage, It seems the Look Forward turns it in Z space, any fix I can implement to make it stay in 2D ?
     
    JamesArndt likes this.
  10. jococo

    jococo

    Joined:
    Dec 15, 2012
    Posts:
    232
    Awesome asset!!
    I have assigned a sprite and box collider so that at runtime on device (not only in editor mode) the user can see and move the "Bezier Control Points". Works well. At runtime on device the handles do not appear.

    How can I assign a box collider and sprite to the handles?


    Or is there another way to allow the user to adjust the handles at runtime on device (not only in editor mode)?
     
    Last edited: May 20, 2018
  11. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    Try this:

    Code (CSharp):
    1. if( lookForward )
    2. {
    3.     // Credit: http://answers.unity.com/answers/651344/view.html
    4.     Vector2 direction = spline.GetTangent( progress ) * ( isGoingForward ? 1 : -1 );
    5.     float angle = Mathf.Atan2( direction.y, direction.x ) * Mathf.Rad2Deg;
    6.     Quaternion targetRotation = Quaternion.AngleAxis( angle, Vector3.forward );
    7.  
    8.     cachedTransform.rotation = Quaternion.Lerp( cachedTransform.rotation, targetRotation, rotationLerpModifier * Time.deltaTime );
    9. }
     
    unityfreeman and danibacon like this.
  12. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    @jococo Thank you! You can add two empty child objects to each point of the spline with the following component attached, as well as a sprite and a box collider:

    Code (CSharp):
    1. using BezierSolution;
    2. using UnityEngine;
    3.  
    4. public class BezierCPRuntimeEditor : MonoBehaviour
    5. {
    6.     private BezierPoint point;
    7.     private bool precedingCP;
    8.     private BezierCPRuntimeEditor otherCP;
    9.    
    10.     private void Start()
    11.     {
    12.         point = transform.parent.GetComponent<BezierPoint>();
    13.         precedingCP = transform.GetSiblingIndex() == 0;
    14.         otherCP = transform.parent.GetChild( precedingCP ? 1 : 0 ).GetComponent<BezierCPRuntimeEditor>();
    15.  
    16.         UpdatePosition();
    17.     }
    18.  
    19.     public void UpdatePosition()
    20.     {
    21.         transform.localPosition = precedingCP ? point.precedingControlPointLocalPosition : point.followingControlPointLocalPosition;
    22.     }
    23.  
    24.     // Should be called when dragged via BoxCollider
    25.     public void OnDrag()
    26.     {
    27.         if( precedingCP )
    28.             point.precedingControlPointLocalPosition = transform.localPosition;
    29.         else
    30.             point.followingControlPointLocalPosition = transform.localPosition;
    31.  
    32.         otherCP.UpdatePosition();
    33.     }
    34. }
    EDIT: fixed a bug in the code.
     
    Last edited: May 21, 2018
  13. jococo

    jococo

    Joined:
    Dec 15, 2012
    Posts:
    232
    Thanks for the fast reply!
     
    Last edited: May 21, 2018
  14. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    It may not work while dragging the object in Scene view but it should work when you call OnDrag on the dragged control point (if you call both control points' OnDrag simultaneously, one control point may always override the other; similar to what you are experiencing). Are you sure that OnDrag is only getting called for the dragged control point? Also, are you possibly calling the UpdatePosition function manually somewhere in your code?
     
    jococo likes this.
  15. danibacon

    danibacon

    Joined:
    Feb 13, 2017
    Posts:
    9
    Thanks so much for the quick reaction.
    It works!
     
    yasirkula likes this.
  16. jococo

    jococo

    Joined:
    Dec 15, 2012
    Posts:
    232
    Cool! Thanks for the tips. It seems to be working well.

    I need to have the control point handles to be oriented 90degrees from created position. I'm creating a mobile app and having them oriented 90 degree offset helps with UI. Attached is a pic if that helps. Is this fairly easy code mod?
     

    Attached Files:

    Last edited: May 24, 2018
  17. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    This one works if your points are scattered across the XY plane and all have the same position-z value:

    Code (CSharp):
    1. using BezierSolution;
    2. using UnityEngine;
    3.  
    4. public class BezierCPRuntimeEditor : MonoBehaviour
    5. {
    6.     private BezierPoint point;
    7.     private bool precedingCP;
    8.     private BezierCPRuntimeEditor otherCP;
    9.  
    10.     private Vector3 INWARDS = new Vector3( 0f, 0f, 1f );
    11.     private Vector3 OUTWARDS = new Vector3( 0f, 0f, -1f );
    12.  
    13.     private void Start()
    14.     {
    15.         point = transform.parent.GetComponent<BezierPoint>();
    16.         precedingCP = transform.GetSiblingIndex() == 0;
    17.         otherCP = transform.parent.GetChild( precedingCP ? 1 : 0 ).GetComponent<BezierCPRuntimeEditor>();
    18.  
    19.         UpdatePosition();
    20.     }
    21.  
    22.     public void UpdatePosition()
    23.     {
    24.         Vector3 position = precedingCP ? point.precedingControlPointLocalPosition : point.followingControlPointLocalPosition;
    25.         transform.localPosition = Vector3.Cross( position, INWARDS );
    26.     }
    27.  
    28.     // Should be called when dragged via BoxCollider
    29.     public void OnDrag()
    30.     {
    31.         Vector3 position = Vector3.Cross( transform.localPosition, OUTWARDS );
    32.         if( precedingCP )
    33.             point.precedingControlPointLocalPosition = position;
    34.         else
    35.             point.followingControlPointLocalPosition = position;
    36.  
    37.         otherCP.UpdatePosition();
    38.     }
    39.  
    40.     // Does the same thing without requiring you to call OnDrag manually
    41.     // and works in editor as well, but uses Update and is therefore slower
    42.     // If uncommented, comment OnDrag
    43.     //public void Update()
    44.     //{
    45.     //    if( transform.hasChanged )
    46.     //    {
    47.     //        Vector3 position = Vector3.Cross( transform.localPosition, OUTWARDS );
    48.     //        if( precedingCP )
    49.     //            point.precedingControlPointLocalPosition = position;
    50.     //        else
    51.     //            point.followingControlPointLocalPosition = position;
    52.  
    53.     //        otherCP.UpdatePosition();
    54.  
    55.     //        transform.hasChanged = false;
    56.     //        otherCP.transform.hasChanged = false;
    57.     //    }
    58.     //}
    59. }
    If you use a different plane, try changing the values of INWARDS and OUTWARDS.

    Protip: for a smoother spline in Game view, call spline.DrawGizmos(color,smoothness) function at Start function.
     
    jococo likes this.
  18. jococo

    jococo

    Joined:
    Dec 15, 2012
    Posts:
    232
    Awesme! Thank you for yet another fast and effective reply!

    Yeah I have no z depth.

    Now all I need to do is figure out how to constrain bez points to only the X axis. I assume that constraint can be done in my drag and drop routine?
     
  19. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    I agree.
     
  20. jonasca

    jonasca

    Joined:
    Dec 19, 2017
    Posts:
    4
    yasirkula, first of all, congratulations for the amazing job you did with this asset. Incredible!

    I'm already using your asset on my project for moving sprites with curves, but I was wondering if there is a way to draw a line renderer into it. I want to draw some circles on the object's path, so the player can know which way the object will follow, just like some baskets on Dunk Shot =)

    Sorry if you've already explained this somewhere and I didn't noticed!

    Thanks and congratulations again!
     
  21. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    You can add the following script to a LineRenderer object:
    Code (CSharp):
    1. using UnityEngine;
    2. using BezierSolution;
    3.  
    4. [RequireComponent( typeof( LineRenderer ) )]
    5. public class SplineLineRenderer : MonoBehaviour
    6. {
    7.     public BezierSpline spline;
    8.     [Range( 0, 30 )]
    9.     public int smoothness = 5;
    10.  
    11.     private LineRenderer lineRenderer;
    12.     private Vector3[] lineRendererPoints;
    13.  
    14.     private void Awake()
    15.     {
    16.         lineRenderer = GetComponent<LineRenderer>();
    17.     }
    18.  
    19.     [ContextMenu( "Refresh" )]
    20.     private void Start()
    21.     {
    22.         Synchronize( smoothness );
    23.     }
    24.  
    25.     public void Synchronize( int smoothness )
    26.     {
    27.         int numberOfPoints = spline.Count * Mathf.Clamp( smoothness, 1, 30 );
    28.  
    29.         if( lineRendererPoints == null || lineRendererPoints.Length != numberOfPoints + 1 )
    30.             lineRendererPoints = new Vector3[numberOfPoints + 1];
    31.  
    32.         float lineStep = 1f / numberOfPoints;
    33.         for( int i = 0; i < numberOfPoints; i++ )
    34.             lineRendererPoints[i] = spline.GetPoint( lineStep * i );
    35.  
    36.         lineRendererPoints[numberOfPoints] = spline.GetPoint( 1f );
    37.  
    38.         lineRenderer.positionCount = lineRendererPoints.Length;
    39.         lineRenderer.SetPositions( lineRendererPoints );
    40.         lineRenderer.loop = spline.loop;
    41.     }
    42. }
     
    Last edited: Apr 29, 2021
    eliteforcevn likes this.
  22. jonasca

    jonasca

    Joined:
    Dec 19, 2017
    Posts:
    4
    Thanks yasirkula! I'll try it :D
     
  23. zero_null

    zero_null

    Joined:
    Mar 11, 2014
    Posts:
    159
    The asset is extremely powerful, helpful and free. Can't expect anything more than what I already got. Thanks for the great effort and sharing. It is awesome.
     
    yasirkula likes this.
  24. yumianhuli1

    yumianhuli1

    Joined:
    Mar 14, 2015
    Posts:
    92
    Hello!Now I have Vector3 points and gameObject points, And how can I use these points create bezierpoints?
    Or how can I create bezier points using your API?
     
    Last edited: Oct 31, 2018
  25. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
  26. yumianhuli1

    yumianhuli1

    Joined:
    Mar 14, 2015
    Posts:
    92
    Hello,I imported the package to Unity.But when I modified the BezierWalkerWithSpeed script,I could not find other class in the project. So what is the reason about this?Thank U very much!
     
  27. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    If you are trying to access BezierWalkerWithSpeed from your own scripts, you should add
    using BezierSolution;
    first.
     
  28. zero_null

    zero_null

    Joined:
    Mar 11, 2014
    Posts:
    159
    can you suggest how can I add like a train carts on the spline. The one object walks fine on spline but I can't figure it out about adding multiple objects.
     
  29. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    I've added BezierWalkerLocomotion component to the GitHub repository. I think it can help you with that issue.
     
  30. sebastianrauh

    sebastianrauh

    Joined:
    Oct 6, 2018
    Posts:
    2
    Thanks for the great work!
    Is it possible to assign materials/meshes to the curve to e.g. create wires with it?
     
  31. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    Currently, there is no built-in way to create a procedural mesh from a spline using this plugin.
     
  32. sebastianrauh

    sebastianrauh

    Joined:
    Oct 6, 2018
    Posts:
    2
    Thanks for the fast reply anyway :)
     
  33. Flindt

    Flindt

    Joined:
    Dec 28, 2016
    Posts:
    78
    Hi Yasirkula - great framework. I have tried it out a bit now - but is it possible to have particles flow along the splines - where there are constant distance between each particle in the row? also getting near the corners etc. while still being able to controll the look of the corners? :)

    Thanks a milllion
     
  34. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    Hi! Are you looking for something like this:

    ss1.png

    Here's how it is set up:

    ss2.png

    All other modules except Renderer are disabled.
     
  35. Flindt

    Flindt

    Joined:
    Dec 28, 2016
    Posts:
    78
    Hi - and thanks for the fast reply.
    No i mean where all dots are flowing along the curve - with a constant distance to each-other - so by row i meant all dots along the curve.
    Issue seams to be that along the curve - if we insert or remove points, or edit control points, then the distance between each dot are not constant = it some places moves slower and some places moves faster.
     
  36. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    If you are using the Strict mode, you can change the else statement of ParticlesFollowBezier.LateUpdate's
    if( followMode == FollowMode.Relaxed )
    like this:

    Code (CSharp):
    1. if( particleData == null )
    2.     particleData = new List<Vector4>( particles.Length );
    3.  
    4. cachedPS.GetCustomParticleData( particleData, ParticleSystemCustomData.Custom1 );
    5.  
    6. float speed = spline.Length / cachedPS.main.duration * Time.deltaTime;
    7. Vector3 deltaPosition = cachedTransform.position - spline.GetPoint( 0f );
    8. for( int i = 0; i < aliveParticles; i++ )
    9. {
    10.     float t = particleData[i].x;
    11.     Vector3 point = spline.MoveAlongSpline( ref t, speed ) + deltaPosition;
    12.     particleData[i] = new Vector4( t, 0f, 0f, 0f );
    13.                    
    14.     if( isLocalSpace )
    15.         point = cachedTransform.InverseTransformPoint( point );
    16.  
    17.     particles[i].position = point;
    18. }
    19.  
    20. cachedPS.SetCustomParticleData( particleData, ParticleSystemCustomData.Custom1 );
     
  37. Flindt

    Flindt

    Joined:
    Dec 28, 2016
    Posts:
    78
    Thanks - i just tested and that works great.

    Now when i need to setup more curves - with different shapes, length etc. but i need to be able to control the speed of the different liens - (not the same speed) but is there an easy way to combine a control for the duration, lifetime and emission rate - so that one can more easy change the speed and distance between particles?
    I'm considering if these these values could be dependent on the length of the curve...?

    Thanks a lot.
     
  38. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    Speed is determined as following:
    spline's length/particle start lifetime
    . So, you can change particles' Start Lifetime to adjust the speed and change Rate over Time to adjust the distance between particles. For ease, you can attach the following component to the particle system, adjust the parameters, click the cog icon and select "Set Speed":

    Code (CSharp):
    1. using UnityEngine;
    2. using BezierSolution;
    3.  
    4. public class ParticlesSpeedSetter : MonoBehaviour
    5. {
    6.     public float speed = 1f;
    7.     public float distance = 1f;
    8.  
    9.     [ContextMenu( "Set Speed" )]
    10.     public void SetSpeed()
    11.     {
    12.         var ps = GetComponent<ParticleSystem>();
    13.         var main = ps.main;
    14.         var emission = ps.emission;
    15.  
    16.         main.startLifetime = GetComponent<ParticlesFollowBezier>().spline.Length / speed;
    17.         emission.rateOverTime = speed / distance;
    18.     }
    19. }
    P.S. It should've been
    cachedPS.main.startLifetime.constant
    and not
    cachedPS.main.duration
    in my previous code.
     
    Flindt likes this.
  39. Flindt

    Flindt

    Joined:
    Dec 28, 2016
    Posts:
    78
    again - thanks so much Yasirkula - it works really great.
    Just to know - it is not and easy task for you to set this new "Distance" parameter based on the length of a curve? = to extract the curve length info from the BezierSpline ?

    If that was the case then i would be able to control the same or different speeds on multiple splines - but based on the same space... i hope it makes senes.
     
  40. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    Is the distance not independent of the curvature of the spline now? As particles move alongside the spline with constant speed, I think Rate over Time will determine the length in units between each particle.

    Let me know if I understood you wrong.
     
  41. Flindt

    Flindt

    Joined:
    Dec 28, 2016
    Posts:
    78
    Sorry - i believe you are right. As long as i "set sepeed" after any edit of the curve it have the same speed. Great - Thanks
     
  42. WHiPerino

    WHiPerino

    Joined:
    Jul 1, 2016
    Posts:
    7
    Hey there, I am trying to make a race game that follows their own tracks, just like a 400m running like the olympics. The inner tracks are shorter. The outer tracks racer starts further ahead than the inner tracks one.

    For example,

    Runner A has Track A of length 50.
    Runner B has Track B of length 70.

    In order for them to race fairly for 50m,

    Runner A starts at progress of 0,
    while Runner B starts at a further progress ahead of runner A, which is (70-50)/70 = 0.2857

    They run at the same speed of 5.

    In theory, they should arrive at the finishing line at the same time. However, thats not the case.

    Do you have any pointers/ideas that i could further explore on to make them arrive at the same time as accurately as possible?

    I use BezierWalkerWithSpeed.cs

    Thanks.
     
    Last edited: Mar 12, 2019
  43. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    Spline's progress (t) is not uniform due to the nature of splines, so 0.2857 may not be really 20m. ahead of the starting point. For this, I'd recommend you to first call
    spline.MoveAlongSpline(ref t, 20.0f)
    and then use the value of t at
    bezierWalker.NormalizedT = t;
     
    JamesArndt likes this.
  44. Unity_3DeersGames

    Unity_3DeersGames

    Joined:
    Oct 2, 2017
    Posts:
    1
    Hi Yasirkula. Thank you for the great codes that you provided to the community. I'm trying to create a game that includes the use of tanks and this tool right here is what I was looking for a long time. Now I need to ask you some questions: Is it possible to place an array of objects along the spline with the distance between them based on the amount of objects that you have? And the other question is: Is it possible to maintain the spline length when one or more control points move like up/down?

    Thank you again for the great help!
     
  45. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    To place an array of objects, you may use something like this:

    Code (CSharp):
    1. public Vector3[] GetPointsOnSpline( BezierSpline spline, int count )
    2. {
    3.     Vector3[] points = new Vector3[count];
    4.     float space = spline.Length / ( count - 1 );
    5.     float t = 0f;
    6.     for( int i = 0; i < count - 1; i++ )
    7.     {
    8.         points[i] = spline.GetPoint( t );
    9.         spline.MoveAlongSpline( ref t, space );
    10.     }
    11.    
    12.     points[count - 1] = spline.GetPoint ( 1f );
    13.     return points;
    14. }
    You can't maintain the spline's length when a control point is moved, unfortunately.
     
    eliteforcevn likes this.
  46. yamslayer

    yamslayer

    Joined:
    Jun 5, 2015
    Posts:
    5
    Hi yasirkula, loving this! Thank you so much. Quick question about rotation -

    I have a slider that I can drag back and forth to move an object back and forth along a spline. I am trying to have the object also set it's rotation from the control points on the spline.

    I don't want it to look forward, rather, I want the object to rotate to the rotation values of the control points. When I create the spline, I can set rotation for each control point using something like
    Spline[i].rotation = pointRotation;
    , but I'm having a hard time trying to figure out how to get the object that's walking along the spline to get those values - I'm guessing I would need to lerp between the 2 closest control points somehow? Do you have any ideas on this? Thank you!
     
  47. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    You can add this function to BezierSpline.cs and call it to retrieve the rotation:

    Code (CSharp):
    1. public Quaternion GetRotation( float normalizedT )
    2. {
    3.     if( !loop )
    4.     {
    5.         if( normalizedT <= 0f )
    6.             return endPoints[0].rotation;
    7.         else if( normalizedT >= 1f )
    8.             return endPoints[endPoints.Count - 1].rotation;
    9.     }
    10.     else
    11.     {
    12.         if( normalizedT < 0f )
    13.             normalizedT += 1f;
    14.         else if( normalizedT >= 1f )
    15.             normalizedT -= 1f;
    16.     }
    17.  
    18.     float t = normalizedT * ( loop ? endPoints.Count : ( endPoints.Count - 1 ) );
    19.  
    20.     BezierPoint startPoint, endPoint;
    21.  
    22.     int startIndex = (int) t;
    23.     int endIndex = startIndex + 1;
    24.  
    25.     if( endIndex == endPoints.Count )
    26.         endIndex = 0;
    27.  
    28.     startPoint = endPoints[startIndex];
    29.     endPoint = endPoints[endIndex];
    30.  
    31.     return Quaternion.LerpUnclamped( startPoint.rotation, endPoint.rotation, t - startIndex );
    32. }
     
    yamslayer likes this.
  48. yamslayer

    yamslayer

    Joined:
    Jun 5, 2015
    Posts:
    5
    Thank you!! Very helpful! This would be great to include in your next release - extremely easy to add spin to objects flying down the splines now :D


     
    yasirkula likes this.
  49. siempus

    siempus

    Joined:
    Nov 29, 2017
    Posts:
    12
    I used to use CatLikeCoding's bezier spline but I find your package far easier to use due to the editor gizmo and additional advanced features. Thank you for that.

    Anyway, I like "SplineWalkerWithSpeed" and it working excellent. I'm not sure its a bug or not, but when I reset the handles of the beginning of the spline (first point). The game object just jump to the end of the spline (last point). This problem not occurring when going backward. Basically, the walker can't start from a point with zeroed handles.

    I use the walker (with speed) in 2D project, so I can workaround it by moving the handle in z axis. I hope you have time to address this minor issue in the future. Thanks.
     
  50. yasirkula

    yasirkula

    Joined:
    Aug 1, 2011
    Posts:
    2,879
    Do you mean setting both "Preceding Control Point Local Position" and "Following Control Point Local Position" to (0,0,0)? I've tried applying that to the face-shaped spline's first point in the demo scene but couldn't reproduce the issue. Are there any other repro steps?
     
    siempus likes this.