Search Unity

SplineMesh, the plugin to create curved content

Discussion in 'Assets and Asset Store' started by methusalah999, Dec 14, 2017.

  1. TheReverseSide

    TheReverseSide

    Joined:
    Aug 31, 2018
    Posts:
    5
    Hello, I am loving the app so far, but have encountered an issue: when I am creating multiple tentacle splines from a parent object, all of the tentacles disappear when I run the game. Earlier this problem occured when editing, not playing the game - you could just see the mesh, but not texture - I just slightly adjusted the size to make them render again.
    Now it is occuring while the game is playing and complicates things. Has anyone else encountered this issue?
     

    Attached Files:

  2. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Hi,

    I will need more detail to figure out what's happening. Maybe you could send me more details in PM? Like the gameobject hierarchy and a screenshot of a mesh renderer to see what's happening with your texture.

    On the following video, I'm doing what I think you are doing (creating multiple tentacles with copy/paste) without any issue, even on play mode. Do you see something different on your side?


    Let's continue the conversation in PM !
     
  3. Vittix

    Vittix

    Joined:
    Sep 11, 2013
    Posts:
    7
    Hi, it's a very cool project, i would like to know if there is a way to orient the up vector of the spline nodes and lerping it between nodes in a way i can twist the extrude example to face the top of the mesh path the way i want, any help or advice would be appreciated.
     
  4. Vittix

    Vittix

    Joined:
    Sep 11, 2013
    Posts:
    7
    i've managed to had a twist value to spline nodes to have the mesh twisting around the forward of the tangent in the oriented point, now i would like to make the mesh have a smooth twist between two spline nodes.

    Code (CSharp):
    1.  private List<OrientedPoint> GetPath()
    2.     {
    3.         var path = new List<OrientedPoint>();
    4.         for (float t = 0; t < spline.nodes.Count-1; t += 1/12.0f)
    5.         {
    6.             var point = spline.GetLocationAlongSpline(t);
    7.             float twistValue = spline.GetTwistValueAlongSpline(t);
    8.             var rotation = CubicBezierCurve.GetRotationFromTangent(spline.GetTangentAlongSpline(t));
    9.             Vector3 euler =  rotation.eulerAngles + new Vector3(0, 0, twistValue);
    10.             rotation = Quaternion.Euler(euler);
    11.             path.Add(new OrientedPoint(point, rotation));
    12.         }
    13.         return path;
    14.     }
    like to make the mesh lerp is twisting between two 2 points.
     

    Attached Files:

  5. Caruos

    Caruos

    Joined:
    Dec 30, 2016
    Posts:
    42
    Hello,
    I tried to make a ring with splines, it works mostly fine but there are some mesh distorsions on the vertical tangents :

    upload_2018-10-9_22-28-58.png

    Do you have any ideas where it comes from ? I tried tweaking the directions and checking the MeshBender script, but I didn't find anything...
     
  6. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Something is already present in SplineMesh to do that. It is not present in the mathematical level (Spline, CubicBezierCurve) but in the MeshBender and it is called "roll". Just use SetStartRoll and SetEndRoll and it will interpolate the value, and will rotate mesh vertices around the tangent at each sample.

    I admit that the feature is quick and dirty and may have issues

    Imagine you have a twist of 0 degrees in p0 and 0 degrees in p1. Does that mean no twist, or a whole turn around? you will have to allow angles that are out of the traditionnal -180/+180 bounds to indicate the direction and amplitude of the twist.

    This will make the GUI hard to do, as a rotation handle will be bound in -180/+180. But maybe you don't care about GUI at all for that.

    Hope it helps.
     
  7. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    This problem is called Gimbal lock : when you reach a rotation in which two axis overlap, you loose a degree of freedom : https://en.wikipedia.org/wiki/Gimbal_lock

    There is no simple solution or if you know one, I would be glad to know about it ^^ Unreal Engine SplineMesh has the exact same problem.

    Some workarounds :
    - have two splines, one for the upper part, one for the lower part
    - merge the nodes of the flipping curve so you can't see the deformation at acceptable distance
    - place a node at the exact position where the flipping occur, with perfect orientation (may be impossible)
    - identify the flipping curve in code then mess with the MeshBender.SetStartRoll to have a counter flipping

    I hope you will find a solution and I would love to ear about it.
     
  8. Vittix

    Vittix

    Joined:
    Sep 11, 2013
    Posts:
    7
    Ok i've manage to find the knot about twisting the extrusion shape along the spline using my twist value.

    i will try to go step by step but the gimbal lock is a bit a pain in the ass and i didn't figure it out yet on how to avoid that behaviour.


    so lets start adding the float value on the spline nodes:
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Events;
    6.  
    7. /// <summary>
    8. /// Spline node storing a position and a direction (tangent).
    9. /// Note : you shouldn't modify position and direction manualy but use dedicated methods instead, to insure event raising.
    10. /// </summary>
    11. [Serializable]
    12. public class SplineNode {
    13.  
    14.     /// <summary>
    15.     /// Node position
    16.     /// Note : you shouldn't modify position and direction manualy but use dedicated methods instead, to insure event raising.
    17.     /// </summary>
    18.     public Vector3 position;
    19.  
    20.     /// <summary>
    21.     /// Node direction
    22.     /// Note : you shouldn't modify position and direction manualy but use dedicated methods instead, to insure event raising.
    23.     /// </summary>
    24.     public Vector3 direction;
    25.  
    26.     /// <summary>
    27.     /// Node twist Value
    28.     /// Note : you shouldn't modify position and direction manualy but use dedicated methods instead, to insure event raising.
    29.     /// </summary>
    30.     public float twist;
    31.  
    32.     public SplineNode(Vector3 position, Vector3 direction) {
    33.         SetPosition(position);
    34.         SetDirection(direction);
    35.     }
    36.  
    37.     /// <summary>
    38.     /// Sets the new position and raises an event.
    39.     /// </summary>
    40.     /// <param name="p"></param>
    41.     public void SetPosition(Vector3 p) {
    42.         if (!position.Equals(p)) {
    43.             position.x = p.x;
    44.             position.y = p.y;
    45.             position.z = p.z;
    46.             if (Changed != null)
    47.                 Changed.Invoke();
    48.         }
    49.     }
    50.  
    51.     /// <summary>
    52.     /// Sets the new direction and raises an event.
    53.     /// </summary>
    54.     /// <param name="d"></param>
    55.     public void SetDirection(Vector3 d) {
    56.         if (!direction.Equals(d)) {
    57.             direction.x = d.x;
    58.             direction.y = d.y;
    59.             direction.z = d.z;
    60.             if (Changed != null)
    61.                 Changed.Invoke();
    62.         }
    63.     }
    64.  
    65.     public void SetTwist(float t)
    66.     {
    67.         if (t != twist)
    68.         {
    69.             twist = t;
    70.             if (Changed != null)
    71.                 Changed.Invoke();
    72.         }
    73.     }
    74.  
    75.     /// <summary>
    76.     /// Event raised when position or direct changes.
    77.     /// </summary>
    78.     [HideInInspector]
    79.     public UnityEvent Changed = new UnityEvent();
    80. }
    81.  

    Add twist value retrieving and lerping methods and the new tangent calculation with twist applied method to CubicBezierCurve.cs
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Events;
    6.  
    7. /// <summary>
    8. /// Mathematical object for cubic Bézier curve definition.
    9. /// It is made of two spline nodes which hold the four needed control points : two positions and two directions
    10. /// It provides methods to get positions and tangent along the curve, specifying a distance or a ratio, plus the curve length.
    11. ///
    12. /// Note that a time of 0.5 and half the total distance won't necessarily define the same curve point as the curve curvature is not linear.
    13. /// </summary>
    14. [Serializable]
    15. public class CubicBezierCurve {
    16.  
    17.     private const int STEP_COUNT = 30;
    18.     private const float T_STEP = 1.0f/STEP_COUNT;
    19.  
    20.     public SplineNode n1, n2;
    21.    
    22.     /// <summary>
    23.     /// Length of the curve in world unit.
    24.     /// </summary>
    25.     public float Length { get; private set; }
    26.     private readonly List<CurveSample> samples = new List<CurveSample>(STEP_COUNT);
    27.  
    28.     /// <summary>
    29.     /// This event is raised when of of the control points has moved.
    30.     /// </summary>
    31.     public UnityEvent Changed = new UnityEvent();
    32.  
    33.     /// <summary>
    34.     /// Build a new cubic Bézier curve between two given spline node.
    35.     /// </summary>
    36.     /// <param name="n1"></param>
    37.     /// <param name="n2"></param>
    38.     public CubicBezierCurve(SplineNode n1, SplineNode n2)
    39.     {
    40.         this.n1 = n1;
    41.         this.n2 = n2;
    42.         n1.Changed.AddListener(() => ComputePoints());
    43.         n2.Changed.AddListener(() => ComputePoints());
    44.         ComputePoints();
    45.     }
    46.  
    47.     /// <summary>
    48.     /// Change the start node of the curve.
    49.     /// </summary>
    50.     /// <param name="n1"></param>
    51.     public void ConnectStart(SplineNode n1) {
    52.         this.n1.Changed.RemoveListener(() => ComputePoints());
    53.         this.n1 = n1;
    54.         n1.Changed.AddListener(() => ComputePoints());
    55.         ComputePoints();
    56.     }
    57.  
    58.     /// <summary>
    59.     /// Change the end node of the curve.
    60.     /// </summary>
    61.     /// <param name="n2"></param>
    62.     public void ConnectEnd(SplineNode n2) {
    63.         this.n2.Changed.RemoveListener(() => ComputePoints());
    64.         this.n2 = n2;
    65.         n2.Changed.AddListener(() => ComputePoints());
    66.         ComputePoints();
    67.     }
    68.  
    69.     /// <summary>
    70.     /// Convinent method to get the third control point of the curve, as the direction of the end spline node indicates the starting tangent of the next curve.
    71.     /// </summary>
    72.     /// <returns></returns>
    73.     public Vector3 GetInverseDirection() {
    74.         return (2 * n2.position) - n2.direction;
    75.     }
    76.  
    77.     /// <summary>
    78.     /// Returns point on curve at given time. Time must be between 0 and 1.
    79.     /// </summary>
    80.     /// <param name="t"></param>
    81.     /// <returns></returns>
    82.     public Vector3 GetLocation(float t)
    83.     {
    84.         if (t < 0 || t > 1)
    85.             throw new ArgumentException("Time must be between 0 and 1. Given time was " + t);
    86.         float omt = 1f - t;
    87.         float omt2 = omt * omt;
    88.         float t2 = t * t;
    89.         return
    90.             n1.position * (omt2 * omt) +
    91.             n1.direction * (3f * omt2 * t) +
    92.             GetInverseDirection() * (3f * omt * t2) +
    93.             n2.position * (t2 * t);
    94.     }
    95.  
    96.  
    97.     /// <summary>
    98.     /// Returns tangent of curve at given time. Time must be between 0 and 1.
    99.     /// </summary>
    100.     /// <param name="t"></param>
    101.     /// <returns></returns>
    102.     public Vector3 GetTangent(float t)
    103.     {
    104.         if (t < 0 || t > 1)
    105.             throw new ArgumentException("Time must be between 0 and 1. Given time was " + t);
    106.         float omt = 1f - t;
    107.         float omt2 = omt * omt;
    108.         float t2 = t * t;
    109.         Vector3 tangent =
    110.             n1.position * (-omt2) +
    111.             n1.direction * (3 * omt2 - 2 * omt) +
    112.             GetInverseDirection() * (-3 * t2 + 2 * t) +
    113.             n2.position * (t2);
    114.         return tangent.normalized;
    115.     }
    116.     /// <summary>
    117.     /// Returns twist value of curve at given time. Time must be between 0 and 1.
    118.     /// </summary>
    119.     /// <param name="t"></param>
    120.     /// <returns></returns>
    121.     public float GetTwist(float t)
    122.     {
    123.         if (t < 0 || t > 1)
    124.             throw new ArgumentException("Time must be between 0 and 1. Given time was " + t);
    125.  
    126.         return Mathf.Lerp(n1.twist, n2.twist, t);
    127.     }
    128.  
    129.     private void ComputePoints()
    130.     {
    131.         samples.Clear();
    132.         Length = 0;
    133.         Vector3 previousPosition = GetLocation(0);
    134.         for (float t = 0; t < 1; t += T_STEP)
    135.         {
    136.             CurveSample sample = new CurveSample();
    137.             sample.location = GetLocation(t);
    138.             sample.twist = GetTwist(t);
    139.             sample.tangent = GetTangent(t);
    140.             Length += Vector3.Distance(previousPosition, sample.location);
    141.             sample.distance = Length;
    142.  
    143.             previousPosition = sample.location;
    144.             samples.Add(sample);
    145.         }
    146.         CurveSample lastSample = new CurveSample();
    147.         lastSample.location = GetLocation(1);
    148.         lastSample.tangent = GetTangent(1);
    149.         lastSample.twist = GetTwist(1);
    150.         Length += Vector3.Distance(previousPosition, lastSample.location);
    151.         lastSample.distance = Length;
    152.         samples.Add(lastSample);
    153.  
    154.         if (Changed != null)
    155.             Changed.Invoke();
    156.     }
    157.  
    158.     private CurveSample getCurvePointAtDistance(float d)
    159.     {
    160.         if (d < 0 || d > Length)
    161.             throw new ArgumentException("Distance must be positive and less than curve length. Length = " + Length + ", given distance was " + d);
    162.  
    163.         CurveSample previous = samples[0];
    164.         CurveSample next = null;
    165.         foreach (CurveSample cp in samples)
    166.         {
    167.             if (cp.distance >= d) {
    168.                 next = cp;
    169.                 break;
    170.             }
    171.             previous = cp;
    172.         }
    173.         if(next == null) {
    174.             throw new Exception("Can't find curve samples.");
    175.         }
    176.         float t = next == previous ? 0 : (d - previous.distance) / (next.distance - previous.distance);
    177.  
    178.         CurveSample res = new CurveSample();
    179.         res.distance = d;
    180.         res.location = Vector3.Lerp(previous.location, next.location, t);
    181.         res.twist = Mathf.Lerp(previous.twist, next.twist, t);
    182.         res.tangent = Vector3.Lerp(previous.tangent, next.tangent, t).normalized;
    183.         return res;
    184.     }
    185.  
    186.     /// <summary>
    187.     /// Returns point on curve at distance. Distance must be between 0 and curve length.
    188.     /// </summary>
    189.     /// <param name="d"></param>
    190.     /// <returns></returns>
    191.     public Vector3 GetLocationAtDistance(float d) {
    192.         return getCurvePointAtDistance(d).location;
    193.     }
    194.  
    195.     /// <summary>
    196.     /// Returns tangent of curve at distance. Distance must be between 0 and curve length.
    197.     /// </summary>
    198.     /// <param name="d"></param>
    199.     /// <returns></returns>
    200.     public Vector3 GetTangentAtDistance(float d)
    201.     {
    202.         return getCurvePointAtDistance(d).tangent;
    203.     }
    204.  
    205.     private class CurveSample
    206.     {
    207.         public Vector3 location;
    208.         public Vector3 tangent;
    209.         public float distance;
    210.         public float twist;
    211.     }
    212.  
    213.     /// <summary>
    214.     /// Convenient method that returns a quaternion used rotate an object in the tangent direction, considering Y-axis as up vector.
    215.     /// </summary>
    216.     /// <param name="Tangent"></param>
    217.     /// <returns></returns>
    218.     public static Quaternion GetRotationFromTangent(Vector3 Tangent) {
    219.         if (Tangent == Vector3.zero)
    220.             return Quaternion.identity;
    221.         return Quaternion.LookRotation(Tangent, Vector3.Cross(Tangent, Vector3.Cross(Vector3.up, Tangent).normalized));
    222.     }
    223.  
    224.     public static Quaternion GetRotationFromTangent(Vector3 Tangent,float twist)
    225.     {
    226.         Quaternion q = Quaternion.LookRotation(Tangent, Vector3.Cross(Tangent, Vector3.Cross(Vector3.up, Tangent).normalized));
    227.        
    228.          if (Tangent == Vector3.zero)
    229.             q = Quaternion.identity;
    230.  
    231.          Vector3 euler = q.eulerAngles + new Vector3(0, 0, twist);
    232.          q = Quaternion.Euler(euler);
    233.  
    234.         return q;
    235.     }
    236. }
    237.  

    Add GetTwistAlongSpline to Spline.cs to retrieve the twist value
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Collections.ObjectModel;
    5. using UnityEngine;
    6. using UnityEngine.Events;
    7.  
    8. /// <summary>
    9. /// A curved line made of oriented nodes.
    10. /// Each segment is a cubic Bézier curve connected to spline nodes.
    11. /// It provides methods to get positions and tangent along the spline, specifying a distance or a ratio, plus the curve length.
    12. /// The spline and the nodes raise events each time something is changed.
    13. /// </summary>
    14. [DisallowMultipleComponent]
    15. [ExecuteInEditMode]
    16. public class Spline : MonoBehaviour {
    17.     /// <summary>
    18.     /// The spline nodes.
    19.     /// Warning, this collection shouldn't be changed manualy. Use specific methods to add and remove nodes.
    20.     /// It is public only for the user to enter exact values of position and direction in the inspector (end serialization purposes).
    21.     /// </summary>
    22.     public List<SplineNode> nodes = new List<SplineNode>();
    23.  
    24.     /// <summary>
    25.     /// The generated curves. Should not be changed in any way, use nodes instead.
    26.     /// </summary>
    27.     [HideInInspector]
    28.     public List<CubicBezierCurve> curves = new List<CubicBezierCurve>();
    29.  
    30.     /// <summary>
    31.     /// The spline length in world units.
    32.     /// </summary>
    33.     public float Length;
    34.  
    35.     /// <summary>
    36.     /// Event raised when the node collection changes
    37.     /// </summary>
    38.     [HideInInspector]
    39.     public UnityEvent NodeCountChanged = new UnityEvent();
    40.  
    41.     /// <summary>
    42.     /// Event raised when one of the curve changes.
    43.     /// </summary>
    44.     [HideInInspector]
    45.     public UnityEvent CurveChanged = new UnityEvent();
    46.  
    47.     /// <summary>
    48.     /// Clear the nodes and curves, then add two default nodes for the reset spline to be visible in editor.
    49.     /// </summary>
    50.     private void Reset() {
    51.         nodes.Clear();
    52.         curves.Clear();
    53.         AddNode(new SplineNode(new Vector3(5, 0, 0), new Vector3(5, 0, -3)));
    54.         AddNode(new SplineNode(new Vector3(10, 0, 0), new Vector3(10, 0, 3)));
    55.         RaiseNodeCountChanged();
    56.         UpdateAfterCurveChanged();
    57.     }
    58.  
    59.     private void OnEnable() {
    60.         curves.Clear();
    61.         for (int i = 0; i < nodes.Count - 1; i++) {
    62.             SplineNode n = nodes[i];
    63.             SplineNode next = nodes[i + 1];
    64.  
    65.             CubicBezierCurve curve = new CubicBezierCurve(n, next);
    66.             curve.Changed.AddListener(() => UpdateAfterCurveChanged());
    67.             curves.Add(curve);
    68.         }
    69.         RaiseNodeCountChanged();
    70.         UpdateAfterCurveChanged();
    71.     }
    72.  
    73.     public ReadOnlyCollection<CubicBezierCurve> GetCurves() {
    74.         return curves.AsReadOnly();
    75.     }
    76.  
    77.     private void RaiseNodeCountChanged() {
    78.         if (NodeCountChanged != null)
    79.             NodeCountChanged.Invoke();
    80.     }
    81.  
    82.     private void UpdateAfterCurveChanged() {
    83.         Length = 0;
    84.         foreach (var curve in curves) {
    85.             Length += curve.Length;
    86.         }
    87.         if (CurveChanged != null) {
    88.             CurveChanged.Invoke();
    89.         }
    90.     }
    91.  
    92.     /// <summary>
    93.     /// Returns the point on spline at time. Time must be between 0 and the nodes count.
    94.     /// </summary>
    95.     /// <param name="t"></param>
    96.     /// <returns></returns>
    97.     public Vector3 GetLocationAlongSpline(float t)
    98.     {
    99.         int index = GetNodeIndexForTime(t);
    100.         return curves[index].GetLocation(t - index);
    101.     }
    102.  
    103.     /// <summary>
    104.     /// Returns the tangent of spline at time. Time must be between 0 and the nodes count.
    105.     /// </summary>
    106.     /// <param name="t"></param>
    107.     /// <returns></returns>
    108.     public Vector3 GetTangentAlongSpline(float t)
    109.     {
    110.         int index = GetNodeIndexForTime(t);
    111.         return curves[index].GetTangent(t - index);
    112.     }
    113.  
    114.     /// <summary>
    115.     /// Returns the twist value of spline at time. Time must be between 0 and the nodes count.
    116.     /// </summary>
    117.     /// <param name="t"></param>
    118.     /// <returns></returns>
    119.     public float GetTwistAlongSpline(float t)
    120.     {
    121.         int index = GetNodeIndexForTime(t);
    122.         return curves[index].GetTwist(t - index);
    123.     }
    124.  
    125.     private int GetNodeIndexForTime(float t) {
    126.         if (t < 0 || t > nodes.Count - 1) {
    127.             throw new ArgumentException(string.Format("Time must be between 0 and last node index ({0}). Given time was {1}.", nodes.Count-1, t));
    128.         }
    129.         int res = Mathf.FloorToInt(t);
    130.         if (res == nodes.Count - 1)
    131.             res--;
    132.         return res;
    133.     }
    134.  
    135.     /// <summary>
    136.     /// Returns the point on spline at distance. Distance must be between 0 and spline length.
    137.     /// </summary>
    138.     /// <param name="d"></param>
    139.     /// <returns></returns>
    140.     public Vector3 GetLocationAlongSplineAtDistance(float d) {
    141.         if(d < 0 || d > Length)
    142.             throw new ArgumentException(string.Format("Distance must be between 0 and spline length ({0}). Given distance was {1}.", Length, d));
    143.         foreach (CubicBezierCurve curve in curves) {
    144.             if (d > curve.Length) {
    145.                 d -= curve.Length;
    146.             } else {
    147.                 return curve.GetLocationAtDistance(d);
    148.             }
    149.         }
    150.         throw new Exception("Something went wrong with GetLocationAlongSplineAtDistance");
    151.     }
    152.  
    153.     /// <summary>
    154.     /// Returns the tangent of spline at distance. Distance must be between 0 and spline length.
    155.     /// </summary>
    156.     /// <param name="d"></param>
    157.     /// <returns></returns>
    158.     public Vector3 GetTangentAlongSplineAtDistance(float d) {
    159.         if (d < 0 || d > Length)
    160.             throw new ArgumentException(string.Format("Distance must be between 0 and spline length ({0}). Given distance was {1}.", Length, d));
    161.         foreach (CubicBezierCurve curve in curves) {
    162.             if (d > curve.Length) {
    163.                 d -= curve.Length;
    164.             } else {
    165.                 return curve.GetTangentAtDistance(d);
    166.             }
    167.         }
    168.         throw new Exception("Something went wrong with GetTangentAlongSplineAtDistance");
    169.     }
    170.  
    171.     /// <summary>
    172.     /// Adds a node at the end of the spline.
    173.     /// </summary>
    174.     /// <param name="node"></param>
    175.     public void AddNode(SplineNode node)
    176.     {
    177.         nodes.Add(node);
    178.         if (nodes.Count != 1) {
    179.             SplineNode previousNode = nodes[nodes.IndexOf(node)-1];
    180.             CubicBezierCurve curve = new CubicBezierCurve(previousNode, node);
    181.             curve.Changed.AddListener(() => UpdateAfterCurveChanged());
    182.             curves.Add(curve);
    183.         }
    184.         RaiseNodeCountChanged();
    185.         UpdateAfterCurveChanged();
    186.     }
    187.  
    188.     /// <summary>
    189.     /// Insert the given node in the spline at index. Index must be greater than 0 and less than node count.
    190.     /// </summary>
    191.     /// <param name="index"></param>
    192.     /// <param name="node"></param>
    193.     public void InsertNode(int index, SplineNode node)
    194.     {
    195.         if (index == 0)
    196.             throw new Exception("Can't insert a node at index 0");
    197.  
    198.         SplineNode previousNode = nodes[index - 1];
    199.         SplineNode nextNode = nodes[index];
    200.  
    201.         nodes.Insert(index, node);
    202.        
    203.         curves[index-1].ConnectEnd(node);
    204.  
    205.         CubicBezierCurve curve = new CubicBezierCurve(node, nextNode);
    206.         curve.Changed.AddListener(() => UpdateAfterCurveChanged());
    207.         curves.Insert(index, curve);
    208.         RaiseNodeCountChanged();
    209.         UpdateAfterCurveChanged();
    210.     }
    211.  
    212.     /// <summary>
    213.     /// Remove the given node from the spline. The given node must exist and the spline must have more than 2 nodes.
    214.     /// </summary>
    215.     /// <param name="node"></param>
    216.     public void RemoveNode(SplineNode node)
    217.     {
    218.         int index = nodes.IndexOf(node);
    219.  
    220.         if(nodes.Count <= 2) {
    221.             throw new Exception("Can't remove the node because a spline needs at least 2 nodes.");
    222.         }
    223.  
    224.         CubicBezierCurve toRemove = index == nodes.Count - 1? curves[index - 1] : curves[index];
    225.         if (index != 0 && index != nodes.Count - 1) {
    226.             SplineNode nextNode = nodes[index + 1];
    227.             curves[index - 1].ConnectEnd(nextNode);
    228.         }
    229.  
    230.         nodes.RemoveAt(index);
    231.         toRemove.Changed.RemoveListener(() => UpdateAfterCurveChanged());
    232.         curves.Remove(toRemove);
    233.  
    234.         RaiseNodeCountChanged();
    235.         UpdateAfterCurveChanged();
    236.     }
    237. }
    238.  

    Overriding GetRotationFromTangent adding twist value
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. /// <summary>
    7. /// Special component to extrude shape along a spline.
    8. ///
    9. /// Note : This component is not lightweight and should be used as-is mostly for prototyping. It allows to quickly create meshes by
    10. /// drawing only the 2D shape to extrude along the spline. The result is not intended to be used in a production context and you will most likely
    11. /// create eventuelly the mesh you need in a modeling tool to save performances and have better control.
    12. ///
    13. /// The special editor of this component allow you to draw a 2D shape with vertices, normals and U texture coordinate. The V coordinate is set
    14. /// for the whole spline, by setting the number of times the texture must be repeated.
    15. ///
    16. /// All faces of the resulting mesh are smoothed. If you want to obtain an edge without smoothing, you will have to overlap two vertices and set two normals.
    17. ///
    18. /// You can expand the vertices list in the inspector to access data and enter precise values.
    19. ///
    20. /// This component doesn't offer much control as Unity is not a modeling tool. That said, you should be able to create your own version easily.
    21. /// /// </summary>
    22. [ExecuteInEditMode]
    23. [RequireComponent(typeof(MeshFilter))]
    24. [RequireComponent(typeof(MeshRenderer))]
    25. [RequireComponent(typeof(Spline))]
    26. public class SplineExtrusion : MonoBehaviour {
    27.  
    28.     private MeshFilter mf;
    29.  
    30.     public Spline spline;
    31.     public float TextureScale = 1;
    32.     public List<Vertex> ShapeVertices = new List<Vertex>();
    33.  
    34.     private bool toUpdate = true;
    35.  
    36.     /// <summary>
    37.     /// Clear shape vertices, then create three vertices with three normals for the extrusion to be visible
    38.     /// </summary>
    39.     private void Reset() {
    40.         ShapeVertices.Clear();
    41.         ShapeVertices.Add(new Vertex(new Vector2(0, 0.5f), new Vector2(0, 1), 0));
    42.         ShapeVertices.Add(new Vertex(new Vector2(1, -0.5f), new Vector2(1, -1), 0.33f));
    43.         ShapeVertices.Add(new Vertex(new Vector2(-1, -0.5f), new Vector2(-1, -1), 0.66f));
    44.         toUpdate = true;
    45.         OnEnable();
    46.     }
    47.  
    48.     private void OnValidate() {
    49.         toUpdate = true;
    50.     }
    51.  
    52.     private void OnEnable() {
    53.         mf = GetComponent<MeshFilter>();
    54.         spline = GetComponent<Spline>();
    55.         if (mf.sharedMesh == null) {
    56.             mf.sharedMesh = new Mesh();
    57.         }
    58.         spline.NodeCountChanged.AddListener(() => toUpdate = true);
    59.         spline.CurveChanged.AddListener(() => toUpdate = true);
    60.     }
    61.  
    62.     private void Update() {
    63.         if (toUpdate) {
    64.             GenerateMesh();
    65.             toUpdate = false;
    66.         }
    67.     }
    68.  
    69.     private List<OrientedPoint> GetPath()
    70.     {
    71.         var path = new List<OrientedPoint>();
    72.         for (float t = 0; t < spline.nodes.Count-1; t += 1/10.0f)
    73.         {
    74.             var point = spline.GetLocationAlongSpline(t);
    75.        
    76.             var rotation = CubicBezierCurve.GetRotationFromTangent(spline.GetTangentAlongSpline(t), spline.GetTwistAlongSpline(t));
    77.  
    78.             path.Add(new OrientedPoint(point, rotation));
    79.         }
    80.         return path;
    81.     }
    82.  
    83.     public void GenerateMesh() {
    84.         List<OrientedPoint> path = GetPath();
    85.  
    86.         int vertsInShape = ShapeVertices.Count;
    87.         int segments = path.Count - 1;
    88.         int edgeLoops = path.Count;
    89.         int vertCount = vertsInShape * edgeLoops;
    90.  
    91.         var triangleIndices = new List<int>(vertsInShape * 2 * segments * 3);
    92.         var vertices = new Vector3[vertCount];
    93.         var normals = new Vector3[vertCount];
    94.         var uvs = new Vector2[vertCount];
    95.  
    96.         int index = 0;
    97.         foreach(OrientedPoint op in path) {
    98.             foreach(Vertex v in ShapeVertices) {
    99.                 vertices[index] = op.LocalToWorld(v.point);
    100.                 normals[index] = op.LocalToWorldDirection(v.normal);
    101.                 uvs[index] = new Vector2(v.uCoord, path.IndexOf(op) / ((float)edgeLoops)* TextureScale);
    102.                 index++;
    103.             }
    104.         }
    105.         index = 0;
    106.         for (int i = 0; i < segments; i++) {
    107.             for (int j = 0; j < ShapeVertices.Count; j++) {
    108.                 int offset = j == ShapeVertices.Count - 1 ? -(ShapeVertices.Count - 1) : 1;
    109.                 int a = index + ShapeVertices.Count;
    110.                 int b = index;
    111.                 int c = index + offset;
    112.                 int d = index + offset + ShapeVertices.Count;
    113.                 triangleIndices.Add(c);
    114.                 triangleIndices.Add(b);
    115.                 triangleIndices.Add(a);
    116.                 triangleIndices.Add(a);
    117.                 triangleIndices.Add(d);
    118.                 triangleIndices.Add(c);
    119.                 index++;
    120.             }
    121.         }
    122.  
    123.         mf.sharedMesh.Clear();
    124.         mf.sharedMesh.vertices = vertices;
    125.         mf.sharedMesh.normals = normals;
    126.         mf.sharedMesh.uv = uvs;
    127.         mf.sharedMesh.triangles = triangleIndices.ToArray();
    128.     }
    129.  
    130.     [Serializable]
    131.     public class Vertex
    132.     {
    133.         public Vector2 point;
    134.         public Vector2 normal;
    135.         public float uCoord;
    136.  
    137.         public Vertex(Vector2 point, Vector2 normal, float uCoord)
    138.         {
    139.             this.point = point;
    140.             this.normal = normal;
    141.             this.uCoord = uCoord;
    142.         }
    143.     }
    144.  
    145.     public struct OrientedPoint
    146.     {
    147.         public Vector3 position;
    148.         public Quaternion rotation;
    149.  
    150.         public OrientedPoint(Vector3 position, Quaternion rotation)
    151.         {
    152.             this.position = position;
    153.             this.rotation = rotation;
    154.         }
    155.  
    156.         public Vector3 LocalToWorld(Vector3 point)
    157.         {
    158.             return position + rotation * point;
    159.         }
    160.  
    161.         public Vector3 LocalToWorldDirection(Vector3 dir)
    162.         {
    163.             return rotation * dir;
    164.         }
    165.     }
    166. }
    167.  

    i have expose the twist value on the SplineEditor.cs to be able to modify them by the inspector
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. [CustomEditor(typeof(Spline))]
    7. public class SplineEditor : Editor {
    8.  
    9.     private const int QUAD_SIZE = 12;
    10.     private Color CURVE_COLOR = new Color(0.8f, 0.8f, 0.8f);
    11.     private Color CURVE_BUTTON_COLOR = new Color(0.8f, 0.8f, 0.8f);
    12.     private Color DIRECTION_COLOR = Color.red;
    13.     private Color DIRECTION_BUTTON_COLOR = Color.red;
    14.  
    15.     private enum SelectionType {
    16.         Node,
    17.         Direction,
    18.         InverseDirection
    19.     }
    20.  
    21.     private SplineNode selection;
    22.     private SelectionType selectionType;
    23.     private bool mustCreateNewNode = false;
    24.     private SerializedProperty nodes;
    25.     private Spline spline;
    26.  
    27.     private GUIStyle nodeButtonStyle, directionButtonStyle;
    28.  
    29.     private void OnEnable() {
    30.         spline = (Spline)target;
    31.         nodes = serializedObject.FindProperty("nodes");
    32.  
    33.         Texture2D t = new Texture2D(1, 1);
    34.         t.SetPixel(0, 0, CURVE_BUTTON_COLOR);
    35.         t.Apply();
    36.         nodeButtonStyle = new GUIStyle();
    37.         nodeButtonStyle.normal.background = t;
    38.  
    39.         t = new Texture2D(1, 1);
    40.         t.SetPixel(0, 0, DIRECTION_BUTTON_COLOR);
    41.         t.Apply();
    42.         directionButtonStyle = new GUIStyle();
    43.         directionButtonStyle.normal.background = t;
    44.     }
    45.  
    46.     SplineNode AddClonedNode(SplineNode node) {
    47.         int index = spline.nodes.IndexOf(node);
    48.         SplineNode res = new SplineNode(node.position, node.direction);
    49.         if (index == spline.nodes.Count - 1) {
    50.             spline.AddNode(res);
    51.         } else {
    52.             spline.InsertNode(index + 1, res);
    53.         }
    54.         return res;
    55.     }
    56.  
    57.     void DeleteNode(SplineNode node)
    58.     {
    59.         if (spline.nodes.Count > 2)
    60.             spline.RemoveNode(node);
    61.     }
    62.  
    63.     void OnSceneGUI()
    64.     {
    65.         Event e = Event.current;
    66.         if (e.type == EventType.MouseDown)
    67.         {
    68.             Undo.RegisterCompleteObjectUndo(spline, "change spline topography");
    69.             // if alt key pressed, we will have to create a new node if node position is changed
    70.             if (e.alt) {
    71.                 mustCreateNewNode = true;
    72.             }
    73.         }
    74.         if (e.type == EventType.MouseUp)
    75.         {
    76.             mustCreateNewNode = false;
    77.         }
    78.  
    79.         // disable game object transform gyzmo
    80.         if (Selection.activeGameObject == spline.gameObject) {
    81.             Tools.current = Tool.None;
    82.             if (selection == null && spline.nodes.Count > 0)
    83.                 selection = spline.nodes[0];
    84.         }
    85.  
    86.         // draw a bezier curve for each curve in the spline
    87.         foreach (CubicBezierCurve curve in spline.GetCurves()) {
    88.             Handles.DrawBezier(spline.transform.TransformPoint(curve.n1.position),
    89.                 spline.transform.TransformPoint(curve.n2.position),
    90.                 spline.transform.TransformPoint(curve.n1.direction),
    91.                 spline.transform.TransformPoint(curve.GetInverseDirection()),
    92.                 CURVE_COLOR,
    93.                 null,
    94.                 3);
    95.         }
    96.  
    97.         // draw the selection handles
    98.         switch (selectionType) {
    99.             case SelectionType.Node:
    100.                 // place a handle on the node and manage position change
    101.                 Vector3 newPosition = spline.transform.InverseTransformPoint(Handles.PositionHandle(spline.transform.TransformPoint(selection.position), Quaternion.identity));
    102.                 if (newPosition != selection.position) {
    103.                     // position handle has been moved
    104.                     if (mustCreateNewNode) {
    105.                         mustCreateNewNode = false;
    106.                         selection = AddClonedNode(selection);
    107.                         selection.SetDirection(selection.direction + newPosition - selection.position);
    108.                         selection.SetPosition(newPosition);
    109.                     } else {
    110.                         selection.SetDirection(selection.direction + newPosition - selection.position);
    111.                         selection.SetPosition(newPosition);
    112.                     }
    113.                 }
    114.                 break;
    115.             case SelectionType.Direction:
    116.                 selection.SetDirection(spline.transform.InverseTransformPoint(Handles.PositionHandle(spline.transform.TransformPoint(selection.direction), Quaternion.identity)));
    117.                 break;
    118.             case SelectionType.InverseDirection:
    119.                 selection.SetDirection(2 * selection.position - spline.transform.InverseTransformPoint(Handles.PositionHandle(2 * spline.transform.TransformPoint(selection.position) - spline.transform.TransformPoint(selection.direction), Quaternion.identity)));
    120.                 break;
    121.         }
    122.  
    123.         // draw the handles of all nodes, and manage selection motion
    124.         Handles.BeginGUI();
    125.         foreach (SplineNode n in spline.nodes)
    126.         {
    127.             Vector3 guiPos = HandleUtility.WorldToGUIPoint(spline.transform.TransformPoint(n.position));
    128.             if (n == selection) {
    129.                 Vector3 guiDir = HandleUtility.WorldToGUIPoint(spline.transform.TransformPoint(n.direction));
    130.                 Vector3 guiInvDir = HandleUtility.WorldToGUIPoint(spline.transform.TransformPoint(2 * n.position - n.direction));
    131.  
    132.                 // for the selected node, we also draw a line and place two buttons for directions
    133.                 Handles.color = Color.red;
    134.                 Handles.DrawLine(guiDir, guiInvDir);
    135.  
    136.                 // draw quads direction and inverse direction if they are not selected
    137.                 if (selectionType != SelectionType.Node) {
    138.                     if (Button(guiPos, directionButtonStyle)) {
    139.                         selectionType = SelectionType.Node;
    140.                     }
    141.                 }
    142.                 if (selectionType != SelectionType.Direction) {
    143.                     if (Button(guiDir, directionButtonStyle)) {
    144.                         selectionType = SelectionType.Direction;
    145.                     }
    146.                 }
    147.                 if (selectionType != SelectionType.InverseDirection) {
    148.                     if (Button(guiInvDir, directionButtonStyle)) {
    149.                         selectionType = SelectionType.InverseDirection;
    150.                     }
    151.                 }
    152.             } else {
    153.                 if (Button(guiPos, nodeButtonStyle)) {
    154.                     selection = n;
    155.                     selectionType = SelectionType.Node;
    156.                 }
    157.             }
    158.         }
    159.         Handles.EndGUI();
    160.  
    161.         if (GUI.changed)
    162.             EditorUtility.SetDirty(target);
    163.     }
    164.  
    165.     bool Button(Vector2 position, GUIStyle style) {
    166.         return GUI.Button(new Rect(position - new Vector2(QUAD_SIZE / 2, QUAD_SIZE / 2), new Vector2(QUAD_SIZE, QUAD_SIZE)), GUIContent.none, style);
    167.     }
    168.  
    169.     public override void OnInspectorGUI() {
    170.         serializedObject.Update();
    171.         // hint
    172.         EditorGUILayout.HelpBox("Hold Alt and drag a node to create a new one.", MessageType.Info);
    173.  
    174.         // delete button
    175.         if(selection == null || spline.nodes.Count <= 2) {
    176.             GUI.enabled = false;
    177.         }
    178.         if (GUILayout.Button("Delete selected node")) {
    179.             Undo.RegisterCompleteObjectUndo(spline, "delete spline node");
    180.             DeleteNode(selection);
    181.             selection = null;
    182.         }
    183.         GUI.enabled = true;
    184.  
    185.         // nodes
    186.         EditorGUILayout.PropertyField(nodes);
    187.         EditorGUI.indentLevel++;
    188.         if (nodes.isExpanded) {
    189.             for (int i = 0; i < nodes.arraySize; i++) {
    190.                 SerializedProperty node = nodes.GetArrayElementAtIndex(i);
    191.                 EditorGUILayout.PropertyField(node);
    192.                 EditorGUI.indentLevel++;
    193.                 if (node.isExpanded)
    194.                 {
    195.                     using (EditorGUI.ChangeCheckScope check = new EditorGUI.ChangeCheckScope())
    196.                     {
    197.                         EditorGUILayout.PropertyField(node.FindPropertyRelative("position"), new GUIContent("Position"));
    198.                         if (check.changed)
    199.                         {
    200.                             ((Spline)target).nodes[i].SetPosition(node.FindPropertyRelative("position").vector3Value);
    201.                         }
    202.                     }
    203.  
    204.                     using (EditorGUI.ChangeCheckScope check = new EditorGUI.ChangeCheckScope())
    205.                     {
    206.                         EditorGUILayout.PropertyField(node.FindPropertyRelative("direction"), new GUIContent("Direction"));
    207.                         if (check.changed)
    208.                         {
    209.                             ((Spline)target).nodes[i].SetDirection(node.FindPropertyRelative("direction").vector3Value);
    210.                         }
    211.                     }
    212.  
    213.                     using (EditorGUI.ChangeCheckScope check = new EditorGUI.ChangeCheckScope())
    214.                     {
    215.                         EditorGUILayout.PropertyField(node.FindPropertyRelative("twist"), new GUIContent("Twist"));
    216.                         if (check.changed)
    217.                         {
    218.                             ((Spline)target).nodes[i].SetTwist(node.FindPropertyRelative("twist").floatValue);
    219.                         }
    220.                     }
    221.                 }
    222.                 EditorGUI.indentLevel--;
    223.             }
    224.         }
    225.         EditorGUI.indentLevel--;
    226.     }
    227.  
    228.     [MenuItem("GameObject/3D Object/Spline")]
    229.     public static void CreateSpline() {
    230.         new GameObject("Spline", typeof(Spline));
    231.     }
    232. }
    233.  


    in this way i've been able to achieve this kind of geometry that i've been looking for:
    Twist-lerping-between-nodes.png

    but still:
    badTwist.png

    just can't get it to work the way i intend to, if you have any other advice, i don't want to have to split the spline in various segment.
     

    Attached Files:

  9. Vittix

    Vittix

    Joined:
    Sep 11, 2013
    Posts:
    7
    i've managed to obtain my effect using a custom upVector and lerping the value between spline nodes, also added a visualizer to handle it by scenegui handlers.

    it loops!
    ItLoops.png
    the selected handles manage the up vector value
     
  10. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    This is great result ! I wonder why you haven't used the Roll parameter in the MeshBender. Can you say a word about it? My main concern is that the CubicBezierCurve is purely mathematical and the twist feature should be on a higher level and should be lerped elsewhere, to keep things separated.

    Can you give details on that point? I am interested by the solution and I'm sure @Caruos would be too.
     
  11. Vittix

    Vittix

    Joined:
    Sep 11, 2013
    Posts:
    7
    MeshBender is a class that work only for the mesh bending component i'm working with spline extrusion that seem an handy tool.

    also i need the upvector and twistvalue to be on the splinenode class cause i need the value to make orient the mesh or the follower in case of the spline follow example.

    i'm working on the latest version on git hub if you want i can push my modification.
     
  12. one_one

    one_one

    Joined:
    May 20, 2013
    Posts:
    621

    Instead of comments in the code advising people to use safe methods, it's best not to even give them the option not to. In this case, you could make the fields that actually store the data private and use the [SerializeField] attribute. C# properties offer an elegant way to make the field accessible from the outside and run code when setting the data. However, be advised that events in setters can quickly lead to infinite loops/stack overflows that might be hard to debug.
     
  13. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    With great pleasure :)

    Public position/rotation is a know issue indeed and I need to push fix on AssetStore.

    About the events, I not particularily proud of the event pattern I've used in SplineMesh, however I haven't heard about any issue on that part so far, so I haven't planned to rework that for now.

    Thanks for your advices.
     
    one_one likes this.
  14. Vittix

    Vittix

    Joined:
    Sep 11, 2013
    Posts:
    7
    Chill dude, i'm just getting along with the code provided and using their standards. that said thanks for the advice.
    if something like that occurr i will now know where to look. :)
     
  15. Vittix

    Vittix

    Joined:
    Sep 11, 2013
    Posts:
    7
    If you want me to push i will need permission to do so, if this is not possible i will share my modification in here
     
  16. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    You can fork the project and create a pull request. This is the good way and it's pretty easy. But you can send me code via PM if you prefer.

    Thanks anyway for your contribution
     
  17. Caruos

    Caruos

    Joined:
    Dec 30, 2016
    Posts:
    42
    I've been tinkering with Spline Mesh a little (including Gimbal Lock), and here's what I tried :

    1) First I made a loop with the basic Mesh Spline config and a striped texture to see the mesh distortions more easily

    upload_2018-10-13_16-34-22.png

    Then to solve the Gimbal lock that happens at vertical tangents, I changed MeshBender to pick the previous quaternion instead :

    Replace
    ///
    Quaternion q = CubicBezierCurve.GetRotationFromTangent(curveTangent) * Quaternion.Euler(0, 90, 0);
    ///

    by

    ///
    Quaternion q = Quaternion.identity;
    if ((curveTangent == new Vector3(0, 1, 0)) || (curveTangent == new Vector3(0, -1, 0)))
    {
    q = previousq;
    }
    else
    {
    q = CubicBezierCurve.GetRotationFromTangent(curveTangent) * Quaternion.Euler(0, 90, 0);
    }
    previousq = q;
    ///

    Then I get this result :

    upload_2018-10-13_16-44-40.png
    The good news is, the mesh distortion disappeared \o/ I tried different loops and different orientations, and the Gimbal Lock seems completely gone.
    The bad news is, you have to add these workarounds for every single script that applies a rotation to the curve tangent : Sower, Object movement etc.

    I still had a non-Gimbal related problem : I wanted the red stripe to be inside the loop at all angles. To fix this I made the following changes to the scripts :

    SplineNode.cs
    public float previousRoll;
    public float nextRoll;


    SplineEditor.cs
    EditorGUI.BeginChangeCheck();
    localPointFloat = EditorGUILayout.FloatField("previousRoll", spline.nodes.previousRoll);
    if (EditorGUI.EndChangeCheck())
    {
    spline.nodes.previousRoll = localPointFloat;
    }

    EditorGUI.BeginChangeCheck();
    localPointFloat = EditorGUILayout.FloatField("nextRoll", spline.nodes.nextRoll);
    if (EditorGUI.EndChangeCheck())
    {
    spline.nodes.nextRoll = localPointFloat;
    }

    ExampleRailing.cs
    mb.SetStartRoll(spline.nodes[i - 1].nextRoll, false);
    mb.SetEndRoll(spline.nodes.previousRoll, false);

    Then I put the roll value at 180 for the upper splines, and I'm quite satisfied with the result :

    upload_2018-10-13_16-53-24.png

    There are still small errors on the vertical tangents, but it can probably be fixed by tweaking the mesh or the sample step, or by lerping between previous and next quaternion (not sure if it's possible).

    As usual @methusalah999 , feel free to add these changes in your asset if you think they can be useful ;)
     
    Last edited: Oct 13, 2018
    AkshayKaps likes this.
  18. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Thanks a lot for you sharing. I have originaly put the rolling logic into the MeshBender but you're the second one to move it to the spline level. I will definitly have to rethink that choice.

    About gimbal lock, your hack is interesting, but I guess it works only with a node (or sample?) at the exact place where the spline has perfect vertical tangent. I will need a system to detect the verticality of the spline, wherever are the nodes and samples. Anyway, your solutions are inspiring.

    Thanks and have fun with curves ^^
     
  19. Caruos

    Caruos

    Joined:
    Dec 30, 2016
    Posts:
    42
    That's because I need the roll information for all the scripts that use the spline - for example, if I want to use the Sower script to put objects that are all oriented toward the center of the loop. Also, from what I can see the MeshBender roll value applies to the entire spline, while putting the roll value on nodes allows to handle each section of the spline separately. I guess I could do a different spline for each roll value, but that seems needlessly tedious and would mess with the texture continuity.

    It doesn't look like it, here's a half-circle with only 2 nodes :
    upload_2018-10-14_10-31-19.png
    There are still some deformations, but they are much smaller than the Gimbal lock and seem caused by the 'previous quaternion' trick causing some slight offsets. In the Debug.log, the vertical tangent is corrected as intended.

    Edit : actually, there are still big mesh distortions in some configurations :
    upload_2018-10-14_11-37-59.png
    I'm not sure it's Gimbal lock though, it might just be how Meshbender calculates the quaternion in lower and upper splines, so you indeed need a node at the intersection to apply roll.

    I'm glad to help ;) Have fun too.
     
    Last edited: Oct 14, 2018
  20. Caruos

    Caruos

    Joined:
    Dec 30, 2016
    Posts:
    42
    @methusalah999 I know you're not a fan of tweaking the spline code, but for me it really feels like the most elegant and practical solution, so here's how I handled it :

    upload_2018-10-14_16-5-23.png

    Spline.cs
    public float GetRollAlongSplineAtDistance(float d)
    {
    int i = 0;
    if (d < 0 || d > Length)
    throw new ArgumentException(string.Format("Distance must be between 0 and spline length ({0}). Given distance was {1}.", Length, d));
    foreach (CubicBezierCurve curve in curves)
    {
    if (d > curve.Length)
    {
    d -= curve.Length;
    i += 1;
    }
    else
    {
    float rollAtDistance = nodes[_i].nextRoll + (nodes[i+1].previousRoll - nodes[_i].nextRoll) * (d/curve.Length);
    return rollAtDistance;
    }
    }
    throw new Exception("Something went wrong with GetRollAlongSplineAtDistance");
    }

    ExampleSower.cs

    go.transform.rotation = Quaternion.LookRotation(horTangent) * Quaternion.LookRotation(Vector3.left, Vector3.up);

    //#Add
    go.transform.rotation = go.transform.rotation * Quaternion.AngleAxis(spline.GetRollAlongSplineAtDistance(distance), go.transform.forward);
    And here's the result :

    upload_2018-10-14_15-57-42.png
    If you really want to keep the spline component rotation-free, maybe you could add a SplineRoll.cs or a CurveRoll.cs component ? Just random ideas.
     
    Last edited: Oct 14, 2018
  21. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    I think it's no big deal that the roll logic is into the spline level. It remains optionnal and does not add much complexity, while beeing a needed feature.

    I will certainly update the asset in that way some day.

    Thx again !
     
  22. mikecaputo

    mikecaputo

    Joined:
    Jul 9, 2017
    Posts:
    18
    Hi, I'm considering buying the paid version, but first wanted to know if it could support my use case for a mobile app:

    - The user can trace their finger in any curve they want (image tracing the letter S), and the app samples the input position several times per second.
    - When the user lifts their finger, within a few frames the app generates a 3d curve using a mesh template (say it was Unity's cylinder primitive)

    Regarding performance, is there a ceiling to the number of input positions that would cause the second step to be so slow that it couldn't be done in, basically, real time? For large curves is it possible to coroutine the "build" step so that there isn't a big hitch in framerate? Thank you!
     
  23. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    SplineMesh won't be of any help for the 2D shape drawing part. In SplineMesh, a "S" shape is only three nodes.

    You will have to collect user touch samples, then convert this 2D shape in Bezier curves finding the best "nodes" (positions + tangent), which will be the tricky part.

    Then you just have to build a spline using these nodes.
     
  24. mikecaputo

    mikecaputo

    Joined:
    Jul 9, 2017
    Posts:
    18
    Sorry if I was unclear -- the sampled points will be in 3D space, even though they're all in the same plane. I have a proof-of-concept for collecting the positions & tangents, and instead of making a Bezier curve I connect the points with straight cylinders.

    I'm trying to get a "curvier" appearance by turning those [positions + tangents] into a Bezier curve. My question is: what kind of performance limitations should I look out for? At what amount of nodes (dozens? hundreds? thousands?) would I expect to see significant lag between the time the user lifts their finger and the time the curve appears on the screen?

    (And could that lag be eliminated by generating one sub-section of the curve each frame via a coroutine, rather than having the CPU generate the entire curve in a single frame?)
     

    Attached Files:

    Last edited: Nov 12, 2018
  25. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    tl;dr: you can tweak the CubicBezierCurve.STEP_COUNT and provide a cylinder with less vertices to match your needs. You should be able to draw hundreds of curves without any performance issues (from SplineMesh at least).

    There are two subjects here :
    • Number of nodes in the spline
    The number of nodes will indeed cause performance issues. For now in SplineMesh, the number of samples for each curve (spline's "segments" between two nodes) is hard coded to a constant 30. So for each curve, SplineMesh will calculate the position and direction on the curve for 30 points, whatever the length or curvature.

    This is far from ideal. You can change it for your own needs in CubicBezierCurve class. You can also implement a dynamic solution that calculate more sample on the most curvy places
    https://hal.inria.fr/file/index/docid/518379/filename/Xiao-DiaoChen2007c.pdf
    https://en.wikipedia.org/wiki/Sturm's_theorem
    • Number of vertices in the mesh to deform
    To have a smooth curved mesh, you need vertices along the spline. That's why the default Unity cylinder won't work in SplineMesh : it only has vertices on the top and bottom faces. SplineMesh requires vertices along the shape to deform.

    If you provide a cylinder with 4 stages, it will be less smooth than a cylinder with 40 stages. But with the latter, SplineMesh will spend x10 CPU time to calculate the vertices positions according to the curve samples.

    In conclusion, a great number of samples will offer better curve fidelity on the most curved places, and a great number of vertices will provide smoother meshes. The number of vertices will have a much bigger impact on performances from my experience.

    In anyway, SplineMesh will only spend time on the curve you are creating/editing (and the two adjacents), whatever the number of curve in the spline. So the CPU time required should not change during the drawing. You will most likely hear from Unity first, if the number of mesh becomes too important in the scene.
     
  26. mikecaputo

    mikecaputo

    Joined:
    Jul 9, 2017
    Posts:
    18
    Thanks a lot - Going for the paid version now. I really appreciate you going into your answers.
     
  27. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Thank you for your support!
     
  28. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Hi guys, I've worked on some fixes and updates !

    - You can now change scale (x and y) and roll for each spline node ! It allows to scale and roll any content, not only the bent meshes. Edition is in inspector only, no handle for now.

    - You can now set precise position and rotation values in the editor, and any change will now trigger the correct events

    Plus many minor fixes and refactors. The update will be submitted to the asset store soon. In the mean time, fell free to test it via Github.

    Thanks for all your contributions and support !

    upload_2019-1-3_0-7-11.png
     
    Caruos likes this.
  29. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    @Caruos @Vittix I'm currently messing with the up-vector-mixing-with-tangent problem ^^

    upload_2019-1-8_0-31-52.png
     
  30. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Of all the solutions I have tested, @Vittix had the best. Managing the up vector is maybe not the most user friendly, but it allows to do exactly what you need, in all situations. So here is the 10th example in the showcase.

    Extrusion component was very slow when you had many nodes. That's past ! Each segment is now updated only when needed.

    upload_2019-1-9_1-57-58.png
     
    Last edited: Jan 9, 2019
  31. MBarnesPVR

    MBarnesPVR

    Joined:
    Jan 15, 2019
    Posts:
    2
    Hi,

    I've been trying to apply lightmaps to these SplineMeshes, but then at runtime the mesh gets recreated and so the lightmaps are invalidated. Is there a way to get around this? Maybe a way to stop the automatic re-creation of meshes at runtime?

    Thanks
     
  32. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Of course, you just need to avoid re-creating objects when Application.IsPlaying like this:

    Code (CSharp):
    1.         private void Update() {
    2.             if (Application.isPlaying) return;
    3.  
    4.             if (toUpdate) {
    5.                 toUpdate = false;
    6.                 CreateMeshes();
    7.             }
    8.         }
    9.  
     
  33. MBarnesPVR

    MBarnesPVR

    Joined:
    Jan 15, 2019
    Posts:
    2
    Thanks for replying. I've tried something like that already (but with a public blockUpdate variable to control it).
    Unfortunately it doesn't work. If I do that, then after starting play mode the mesh doesn't appear at all. The SplineMesh0 object is still in the hierarchy and shows the mesh wireframe when selected, but it doesn't render. Any idea why?

    I'm making these changes to ExemplePipe.cs by the way.
     
  34. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Please check your console, you may have an exception. You also have to re-think the life cycle of the generated objects (they may be destroyed when the game start which is not what you want).

    I'm using the given solution to keep baked lightmap on my own projects and it works well, but with my own script. I paste it here for your inspiration, but please don't use it as-is as it is made with a more recent version of SplineMesh.

    You will see the Application.IsPlaying trick and a more advanced (more real-life) management of the generated objects. Remember that examples provided in SplineMesh showcase are a little naive for the sake of simplicity ^^

    Anyway I think it's a good idea to include such an (simplified) example in the next version.

    Code (CSharp):
    1.     [ExecuteInEditMode]
    2.     [SelectionBase]
    3.     [RequireComponent(typeof(Spline))]
    4.     public class SplineTrack : MonoBehaviour {
    5.         private GameObject generated;
    6.         private Spline spline = null;
    7.         private bool toUpdate = false;
    8.  
    9.         public List<TrackSegment> segments = new List<TrackSegment>();
    10.         public bool updateInPlayMode;
    11.  
    12.         private void OnEnable() {
    13.             string generatedName = $"generated by {GetType().Name}{GetInstanceID()}";
    14.             generated = transform.Cast<Transform>().FirstOrDefault(go => go.name == generatedName)?.gameObject;
    15.             if (generated == null) {
    16.                 generated = UOUtility.Create(generatedName, gameObject);
    17.             }
    18.  
    19.             spline = GetComponent<Spline>();
    20.             spline.NodeCountChanged.AddListener(() => { toUpdate = true; });
    21.             spline.NodeCountChanged.AddListener(() =>
    22.             {
    23.                 while (spline.nodes.Count > segments.Count) {
    24.                     segments.Add(new TrackSegment());
    25.                 }
    26.                 while (spline.nodes.Count < segments.Count) {
    27.                     segments.RemoveAt(segments.Count - 1);
    28.                 }
    29.             });
    30.             toUpdate = true;
    31.         }
    32.  
    33.         private void OnValidate() {
    34.             if (spline == null) return;
    35.             toUpdate = true;
    36.         }
    37.  
    38.         private void Update() {
    39.             if (!updateInPlayMode && Application.isPlaying) return;
    40.  
    41.             if (toUpdate) {
    42.                 toUpdate = false;
    43.                 CreateMeshes();
    44.             }
    45.         }
    46.  
    47.         public void CreateMeshes() {
    48.             List<GameObject> used = new List<GameObject>();
    49.  
    50.             for(int i = 0; i < spline.GetCurves().Count; i++){
    51.                 var curve = spline.GetCurves()[i];
    52.                 foreach(var tm in segments[i].transformedMeshes) {
    53.                     if (tm.mesh == null) {
    54.                         continue;
    55.                     }
    56.                     var childTransform = generated.transform.Cast<Transform>().FirstOrDefault(child => child.name == $"segment {i} mesh {tm.mesh.name}");
    57.                     GameObject go;
    58.                     if (childTransform == null) {
    59.                         go = new GameObject($"segment {i} mesh {tm.mesh.name}",
    60.                             typeof(MeshFilter),
    61.                             typeof(MeshRenderer),
    62.                             typeof(MeshBender),
    63.                             typeof(MeshCollider));
    64.                         go.transform.parent = generated.transform;
    65.                         go.transform.localRotation = Quaternion.identity;
    66.                         go.transform.localPosition = Vector3.zero;
    67.                         go.transform.localScale = Vector3.one;
    68.                         go.layer = LayerMask.NameToLayer("TrackGround");
    69.                         go.isStatic = true;
    70.                     } else {
    71.                         go = childTransform.gameObject;
    72.                     }
    73.                     go.GetComponent<MeshRenderer>().material = tm.material;
    74.                     go.GetComponent<MeshCollider>().material = tm.physicMaterial;
    75.                     MeshBender mb = go.GetComponent<MeshBender>();
    76.                     mb.Source = tm.mesh;
    77.                     mb.Curve = curve;
    78.                     mb.Translation = tm.translation;
    79.                     mb.Rotation = Quaternion.Euler(tm.rotation);
    80.                     mb.Scale = tm.scale;
    81.                     mb.ComputeIfNeeded();
    82.                     used.Add(go);
    83.                 }
    84.             }
    85.             foreach(var go in generated.transform
    86.                 .Cast<Transform>()
    87.                 .Select(child => child.gameObject).Except(used)) {
    88.                 UOUtility.Destroy(go);
    89.             }
    90.         }
    91.     }
    92.  
    93.     [Serializable]
    94.     public class TrackSegment {
    95.         public List<TransformedMesh> transformedMeshes = new List<TransformedMesh>();
    96.     }
    97.  
    98.     [Serializable]
    99.     public class TransformedMesh {
    100.         public Mesh mesh;
    101.         public Material material;
    102.         public PhysicMaterial physicMaterial;
    103.         public Vector3 translation;
    104.         public Vector3 rotation;
    105.         public Vector3 scale;
    106.     }
     
  35. GordGames

    GordGames

    Joined:
    Nov 7, 2016
    Posts:
    32
    Hi,

    I tried using the ExampleSower and added some bits for adjusting xyz rotation. I have rings that I want auto placed so they follow the curve. Everything works fine except it's only getting horizontal rotation, whereas I'd like both horizontal and vertical rotation (see attached image):




    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. /// <summary>
    7. /// Exemple of component to places assets along a spline. This component can be used as-is but will most likely be a base for your own component.
    8. ///
    9. /// In this exemple, the user gives the prefab to place, a spacing value between two placements, the prefab scale and an horizontal offset to the spline.
    10. /// These three last values have an additional range, allowing to add some randomness. for each placement, the computed value will be between value and value+range.
    11. ///
    12. /// Prefabs are placed from the start of the spline at computed spacing, unitl there is no lentgh remaining. Prefabs are stored, destroyed
    13. /// and built again each time the spline or one of its curves change.
    14. ///
    15. /// A random seed is used to obtain the same random numbers at each update. The user can specify the seed to test some other random number set.
    16. ///
    17. /// Place prefab along a spline and deform it easily have a lot of usages if you have some imagination :
    18. ///  - place trees along a road
    19. ///  - create a rocky bridge
    20. ///  - create a footstep track with decals
    21. ///  - create a path of firefly in the dark
    22. ///  - create a natural wall with overlapping rocks
    23. ///  - etc.
    24. /// </summary>
    25. [ExecuteInEditMode]
    26. [SelectionBase]
    27. public class ExempleSowerRing : MonoBehaviour {
    28.  
    29.     public GameObject prefab = null;
    30.     public float scale = 1, scaleRange = 0;
    31.     public float spacing = 1, spacingRange = 0;
    32.     public float offset = 0, offsetRange = 0;
    33.     public bool isRandomYaw = false;
    34.     public int randomSeed = 0;
    35.  
    36.     public float yrot = 0;
    37.     public float xrot = 0;
    38.     public float zrot = 0;
    39.  
    40.     [HideInInspector]
    41.     public List<GameObject> prefabs = new List<GameObject>();
    42.  
    43.     private Spline spline = null;
    44.     private bool toUpdate = true;
    45.  
    46.  
    47.     private void OnEnable() {
    48.         spline = GetComponent<Spline>();
    49.         spline.NodeCountChanged.AddListener(() => {
    50.             toUpdate = true;
    51.             foreach (CubicBezierCurve curve in spline.GetCurves()) {
    52.                 curve.Changed.AddListener(() => toUpdate = true);
    53.             }
    54.         });
    55.         foreach (CubicBezierCurve curve in spline.GetCurves()) {
    56.             curve.Changed.AddListener(() => toUpdate = true);
    57.         }
    58.     }
    59.  
    60.     private void OnValidate() {
    61.         toUpdate = true;
    62.     }
    63.  
    64.     private void Update() {
    65.         if (toUpdate) {
    66.             Sow();
    67.             toUpdate = false;
    68.         }
    69.     }
    70.  
    71.     public void Sow() {
    72.         foreach (GameObject go in prefabs) {
    73.             if (gameObject != null) {
    74.                 if (Application.isPlaying) {
    75.                     Destroy(go);
    76.                 } else {
    77.                     DestroyImmediate(go);
    78.                 }
    79.             }
    80.         }
    81.         prefabs.Clear();
    82.  
    83.         UnityEngine.Random.InitState(randomSeed);
    84.         if (spacing + spacingRange <= 0 ||
    85.             prefab == null)
    86.             return;
    87.  
    88.         float distance = 0;
    89.         while (distance <= spline.Length) {
    90.             GameObject go = Instantiate(prefab, transform);
    91.             go.transform.localRotation = Quaternion.identity;
    92.             //go.transform.localRotation = Quaternion.Euler(xrot, yrot, zrot);
    93.             go.transform.localPosition = Vector3.zero;
    94.             go.transform.localScale = Vector3.one;
    95.  
    96.             // move along spline, according to spacing + random
    97.             go.transform.localPosition = spline.GetLocationAlongSplineAtDistance(distance);
    98.             // apply scale + random
    99.             float rangedScale = scale + UnityEngine.Random.Range(0, scaleRange);
    100.             go.transform.localScale = new Vector3(rangedScale, rangedScale, rangedScale);
    101.             // rotate with random yaw
    102.             if (isRandomYaw) {
    103.                 go.transform.Rotate(0, 0, UnityEngine.Random.Range(-180, 180));
    104.             } else {
    105.                 Vector3 horTangent = spline.GetTangentAlongSplineAtDistance(distance);
    106.                 horTangent.y = 0;
    107.                 //go.transform.rotation = Quaternion.LookRotation(horTangent) * Quaternion.LookRotation(Vector3.left, Vector3.up);
    108.                 //go.transform.rotation = Quaternion.LookRotation(horTangent) * Quaternion.LookRotation(Vector3.left, Vector3.up) * Quaternion.Euler(xrot, yrot, zrot);
    109.                 go.transform.rotation = Quaternion.LookRotation(horTangent) * Quaternion.Euler(xrot, yrot, zrot);
    110.  
    111.             }
    112.             // move orthogonaly to the spline, according to offset + random
    113.             Vector3 binormal = spline.GetTangentAlongSplineAtDistance(distance);
    114.             binormal = Quaternion.LookRotation(Vector3.right, Vector3.up) * binormal;
    115.             binormal *= offset + UnityEngine.Random.Range(0, offsetRange * Math.Sign(offset));
    116.             go.transform.position += binormal;
    117.  
    118.             prefabs.Add(go);
    119.  
    120.             distance += spacing + UnityEngine.Random.Range(0, spacingRange);
    121.         }
    122.     }
    123. }
     

    Attached Files:

  36. GordGames

    GordGames

    Joined:
    Nov 7, 2016
    Posts:
    32
    NEVER MIND! I see that the y compontent was zeroed out.

    Removed horTangent.y = 0; and it was great.
     
  37. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    I haven't been fast enough. I'm glad to hear that your problem is solved.
     
  38. Caruos

    Caruos

    Joined:
    Dec 30, 2016
    Posts:
    42
    Hello @methusalah999,
    That's great news ! However the asset page hasn't been updated - the latest version is still 1.0.1 from 22 January 2018. Do you have any estimated release date for the new changes ?
     
  39. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    The new version has been submited and should be live in a few days.
     
    Caruos likes this.
  40. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Version 1.1 is now live !

     
    Caruos likes this.
  41. GordGames

    GordGames

    Joined:
    Nov 7, 2016
    Posts:
    32
    Hi! Tried out version 1.1 and noticed the handles draw a little strangely depending on where the camera is. The node markers fly at the end, and the red adjuster line goes weird.

     

    Attached Files:

  42. GordGames

    GordGames

    Joined:
    Nov 7, 2016
    Posts:
    32
    Oh, and it also seems to be generating a new set of gameobject on ever load of unity. Not sure if that's because I copied the new code into my existing ring generator script or not.

    Capture.PNG
     
  43. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Yeah it's a know issue from day 1. I admit I've never dig deep into it but I must. Thanks for the report.

    The life cycle of generated object has changed a little, mainly for the sake of simplicity in example code. I think it's better like this but you will have to rethink the rest of your code to adapt, i'm afraid. Another solution is to stick to the 1.0 solution in your script.

    Please remember that the code in examples is only a source of inspiration. I don't claim it to be a best practice, as it is very project dependant.

    Thanks for your feedbacks, I really appreciate it!
     
  44. GordGames

    GordGames

    Joined:
    Nov 7, 2016
    Posts:
    32
    I think you might need to check if the interaction point are in the camera's view, only then draw them... I think they're flipped artifact maybe? Only a guess!

    I'll modify the code, no problem, just wanted to make sure you know it's making duplicates right now. :)
     
  45. GordGames

    GordGames

    Joined:
    Nov 7, 2016
    Posts:
    32
    Hey,

    I think I fixed the weird nodes showing up, check this out in SplineEditor.cs but I'm not sure if that breaks anything else, so, yeah, enjoy!

    Code (CSharp):
    1. // draw the handles of all nodes, and manage selection motion
    2.             Handles.BeginGUI();
    3.             foreach (SplineNode n in spline.nodes) {
    4.                 Vector3 guiPos = HandleUtility.WorldToGUIPoint(spline.transform.TransformPoint(n.Position));
    5.                 if (n == selection) {
    6.                     Vector3 guiDir = HandleUtility.WorldToGUIPoint(spline.transform.TransformPoint(n.Direction));
    7.                     Vector3 guiInvDir = HandleUtility.WorldToGUIPoint(spline.transform.TransformPoint(2 * n.Position - n.Direction));
    8.                     Vector3 guiUp = HandleUtility.WorldToGUIPoint(spline.transform.TransformPoint(n.Position + n.Up));
    9.  
    10.                  
    11.  
    12.                         // for the selected node, we also draw a line and place two buttons for directions
    13.                         Handles.color = Color.red;
    14.                         Handles.DrawLine(guiDir, guiInvDir);
    15.  
    16.                         // draw quads direction and inverse direction if they are not selected
    17.                         if (selectionType != SelectionType.Node) {
    18.                             if (Button(guiPos, directionButtonStyle)) {
    19.                                 selectionType = SelectionType.Node;
    20.                             }
    21.                         }
    22.                         if (selectionType != SelectionType.Direction) {
    23.                             if (Button(guiDir, directionButtonStyle)) {
    24.                                 selectionType = SelectionType.Direction;
    25.                             }
    26.                         }
    27.                         if (selectionType != SelectionType.InverseDirection) {
    28.                             if (Button(guiInvDir, directionButtonStyle)) {
    29.                                 selectionType = SelectionType.InverseDirection;
    30.                             }
    31.                         }
    32.                         if (showUpVector) {
    33.                             Handles.color = Color.green;
    34.                             Handles.DrawLine(guiPos, guiUp);
    35.                             if (selectionType != SelectionType.Up) {
    36.                                 if (Button(guiUp, upButtonStyle)) {
    37.                                     selectionType = SelectionType.Up;
    38.                                 }
    39.                             }
    40.                         }
    41.  
    42.                 } else {
    43.                     //Added this heading vector and checked the sceneview camera
    44.                     Vector3 heading = spline.transform.TransformPoint(n.Position) - SceneView.lastActiveSceneView.camera.transform.position;
    45.                     if (Vector3.Dot(SceneView.lastActiveSceneView.camera.transform.forward, heading) > 0)
    46.                     {
    47.                         if (Button(guiPos, nodeButtonStyle))
    48.                         {
    49.                             selection = n;
    50.                             selectionType = SelectionType.Node;
    51.                         }
    52.                     }
    53.                 }
    54.             }
     
  46. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Issue is now fixed on github. I've used another solution that also avoid drawing things that are in front of the camera but out of the field of view. Thank you a lot for your contribution !

    Code (CSharp):
    1.         public static bool IsOnScreen(Vector3 position) {
    2.             Vector3 onScreen = Camera.current.WorldToViewportPoint(position);
    3.             return onScreen.z > 0 && onScreen.x > 0 && onScreen.y > 0 && onScreen.x < 1 && onScreen.y < 1;
    4.         }
     
    GordGames likes this.
  47. GordGames

    GordGames

    Joined:
    Nov 7, 2016
    Posts:
    32
    Cool! Glad to help. :)
     
  48. GordGames

    GordGames

    Joined:
    Nov 7, 2016
    Posts:
    32
    Having another weird issue... on some splines (like first image), they look fine, on others (second image), they're rotated wrong and don't follow the curve. Scratching my head what could be wrong, I don't even know where to begin troubleshooting. I made the first ones with the original SplineMesh, and the others after upgrading to the new version.

    1.PNG 2.PNG
     
  49. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    I make things like that and I haven't met this issue so far. Can you check that the root object with spline component has a zero rotation?

    If it's not the problem, could you send me you script here or in PM?
     
  50. GordGames

    GordGames

    Joined:
    Nov 7, 2016
    Posts:
    32
    That was totally the problem, I had a rotation on the object that I didn't on the other.

    Also, when making a build, I get this error:
    3.PNG

    You need to add #if UNITY_EDITOR and #endif to wrap the parts that use UnityEditor parts, as they won't be available in a full build. I changed Update and Sow:

    Code (CSharp):
    1. private void Update() {
    2.             //Skip while running
    3.             if (Application.isPlaying) return;
    4.  
    5.             if (toUpdate) {
    6.                 Sow();
    7.                 toUpdate = false;
    8.             }
    9.         }
    Code (CSharp):
    1. public void Sow() {
    2.             #if UNITY_EDITOR
    3.             UOUtility.DestroyChildren(generated);
    4.  
    5.             //THE REST OF THE SOW CODE
    6.             #endif
    7.         }
     

    Attached Files: