Search Unity

  1. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  2. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  3. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  4. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

SplineMesh, the plugin to create curved content

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

  1. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    bandeau github.png



    SplineMesh is a free and open-source plugin that allows you to create curved content :
    - bend meshes,
    - extrude 2D shapes,
    - place assets along splines,
    and anything you can imagine, thanks to a very simple and expandable tool set.

    Get it on the Asset Store now !

    Don't expect giant editors with tons of options here. SplineMesh encourages you to write your own scripts by providing concise documentation and a lot of simple exemples to expand.

    Want to contribute? Consider giving feedback, credits, sending your code modifications or your own exemple via GitHub, and showing your creations here !

    Finaly, SplineMesh exists in paid version. No additional content here, buy this only to support the author :)

    Hope you like it !

    Showcase.PNG Road.PNG




    (thx to Thunderent's assets Temple Props)
     
    Last edited: Jan 27, 2018
    OCASM, movra and theANMATOR2b like this.
  2. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    As requested by many of you, i've just created a short video tutorial to create your first tentacle ^^

     
    movra likes this.
  3. movra

    movra

    Joined:
    Feb 16, 2013
    Posts:
    570
    I have no use for it now, but it looks sexy ;)
     
    methusalah999 likes this.
  4. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    After a month or so on the asset store, SplineMesh has received:
    - 400+ downloads
    - 5/5 stars from 11 reviewers
    - 30$ of gross revenu with the paid version
    - a lot of kind words by e-mail and twitter !

    Thank you so much to all of you guys !
     
  5. foyleman

    foyleman

    Joined:
    Feb 19, 2009
    Posts:
    111
    I'm having an issue with using multiple extruded meshes. It seems that only a single mesh can exist at once.
     
  6. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    Hi @foyleman,

    Are you talking about extrusion with the extrusion component? in this case, only a single 2d shape is supported by the exemple included into the asset.

    If you are talking about deforming multiple meshes on each curve of the spline, this can easily be done ! You will need to write your own component from the basic pipe example and add a list of meshes. Then for each curve, just give a different mesh from the list to the mesh deformer.

    If you encounter an issue with this approach, or if you're trying a different approach, could you please provide some more information by private message ?
     
    Last edited: Feb 26, 2018
  7. foyleman

    foyleman

    Joined:
    Feb 19, 2009
    Posts:
    111
    Yes, I am talking about extrusion with the extrusion component. I was actually able to solve it by generating a new mesh in Update like so:
    Code (CSharp):
    1.  
    2.     private void Update() {
    3.         if (toUpdate) {
    4.             if (mf.sharedMesh == null) {
    5.                 mf.sharedMesh = new Mesh();
    6.             }
    7.             GenerateMesh();
    8.             toUpdate = false;
    9.         }
    10.     }
    I also remove the "if null" in OnEnable, but I don't think that helped me.

    THANK YOU for your reply!
     
    methusalah999 likes this.
  8. Guacamolay

    Guacamolay

    Joined:
    Jun 24, 2013
    Posts:
    33
    Hey, this asset seems really great so far, and would actually like to swap out my current spline system for it, but one issue I'm having is that I can't manually change the values of the nodes in the inspector. I've noticed a note in the code saying it should be possible? I'm on 2017.3, if that might be a factor
     
  9. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    It's a known issue. Alas, I haven't time to fix it for the moment. Help is welcome !
     
  10. foyleman

    foyleman

    Joined:
    Feb 19, 2009
    Posts:
    111
    I needed this too, so here's what I did.

    I wanted to:
    • Show the splines drawn in the editor regardless of being selected so I could line multiple splines up. That's where void OnScene () comes into play.
    • The nodes were originally built for local coordinates. I wanted both local and world when editing the points.

    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.     SplineEditor ()
    30.     {
    31.         SceneView.onSceneGUIDelegate += OnScene;
    32.     }
    33.  
    34.     private void OnEnable() {
    35.         spline = (Spline)target;
    36.         nodes = serializedObject.FindProperty("nodes");
    37.  
    38.         Texture2D t = new Texture2D(1, 1);
    39.         t.SetPixel(0, 0, CURVE_BUTTON_COLOR);
    40.         t.Apply();
    41.         nodeButtonStyle = new GUIStyle();
    42.         nodeButtonStyle.normal.background = t;
    43.  
    44.         t = new Texture2D(1, 1);
    45.         t.SetPixel(0, 0, DIRECTION_BUTTON_COLOR);
    46.         t.Apply();
    47.         directionButtonStyle = new GUIStyle();
    48.         directionButtonStyle.normal.background = t;
    49.     }
    50.  
    51.     SplineNode AddClonedNode(SplineNode node) {
    52.         int index = spline.nodes.IndexOf(node);
    53.         SplineNode res = new SplineNode(node.position, node.direction);
    54.         if (index == spline.nodes.Count - 1) {
    55.             spline.AddNode(res);
    56.         } else {
    57.             spline.InsertNode(index + 1, res);
    58.         }
    59.         return res;
    60.     }
    61.  
    62.     void DeleteNode(SplineNode node)
    63.     {
    64.         if (spline.nodes.Count > 2)
    65.             spline.RemoveNode(node);
    66.     }
    67.  
    68.     void OnScene(SceneView scene)
    69.     {
    70.         if (spline == null)
    71.             return;
    72.  
    73.         // draw a bezier curve for each curve in the spline
    74.         foreach (CubicBezierCurve curve in spline.GetCurves()) {
    75.             Handles.DrawBezier(spline.transform.TransformPoint(curve.n1.position),
    76.                 spline.transform.TransformPoint(curve.n2.position),
    77.                 spline.transform.TransformPoint(curve.n1.direction),
    78.                 spline.transform.TransformPoint(curve.GetInverseDirection()),
    79.                 CURVE_COLOR,
    80.                 null,
    81.                 3);
    82.         }
    83.     }
    84.  
    85.     void OnSceneGUI()
    86.     {
    87.         Event e = Event.current;
    88.         if (e.type == EventType.mouseDown)
    89.         {
    90.             Undo.RegisterCompleteObjectUndo(spline, "change spline topography");
    91.             // if alt key pressed, we will have to create a new node if node position is changed
    92.             if (e.alt) {
    93.                 mustCreateNewNode = true;
    94.             }
    95.         }
    96.         if (e.type == EventType.mouseUp)
    97.         {
    98.             mustCreateNewNode = false;
    99.         }
    100.  
    101.         // disable game object transform gyzmo
    102.         if (Selection.activeGameObject == spline.gameObject) {
    103.             Tools.current = Tool.None;
    104.             if (selection == null && spline.nodes.Count > 0)
    105.                 selection = spline.nodes[0];
    106.         }
    107.  
    108.         // draw a bezier curve for each curve in the spline
    109.         foreach (CubicBezierCurve curve in spline.GetCurves()) {
    110.             Handles.DrawBezier(spline.transform.TransformPoint(curve.n1.position),
    111.                 spline.transform.TransformPoint(curve.n2.position),
    112.                 spline.transform.TransformPoint(curve.n1.direction),
    113.                 spline.transform.TransformPoint(curve.GetInverseDirection()),
    114.                 CURVE_COLOR,
    115.                 null,
    116.                 3);
    117.         }
    118.  
    119.         // draw the selection handles
    120.         switch (selectionType) {
    121.             case SelectionType.Node:
    122.                 // place a handle on the node and manage position change
    123.                 Vector3 newPosition = spline.transform.InverseTransformPoint(Handles.PositionHandle(spline.transform.TransformPoint(selection.position), Quaternion.identity));
    124.                 if (newPosition != selection.position) {
    125.                     // position handle has been moved
    126.                     if (mustCreateNewNode) {
    127.                         mustCreateNewNode = false;
    128.                         selection = AddClonedNode(selection);
    129.                         selection.SetDirection(selection.direction + newPosition - selection.position);
    130.                         selection.SetPosition(newPosition);
    131.                     } else {
    132.                         selection.SetDirection(selection.direction + newPosition - selection.position);
    133.                         selection.SetPosition(newPosition);
    134.                     }
    135.                 }
    136.                 break;
    137.             case SelectionType.Direction:
    138.                 selection.SetDirection(spline.transform.InverseTransformPoint(Handles.PositionHandle(spline.transform.TransformPoint(selection.direction), Quaternion.identity)));
    139.                 break;
    140.             case SelectionType.InverseDirection:
    141.                 selection.SetDirection(2 * selection.position - spline.transform.InverseTransformPoint(Handles.PositionHandle(2 * spline.transform.TransformPoint(selection.position) - spline.transform.TransformPoint(selection.direction), Quaternion.identity)));
    142.                 break;
    143.         }
    144.  
    145.         // draw the handles of all nodes, and manage selection motion
    146.         Handles.BeginGUI();
    147.         foreach (SplineNode n in spline.nodes)
    148.         {
    149.             Vector3 guiPos = HandleUtility.WorldToGUIPoint(spline.transform.TransformPoint(n.position));
    150.             if (n == selection) {
    151.                 Vector3 guiDir = HandleUtility.WorldToGUIPoint(spline.transform.TransformPoint(n.direction));
    152.                 Vector3 guiInvDir = HandleUtility.WorldToGUIPoint(spline.transform.TransformPoint(2 * n.position - n.direction));
    153.  
    154.                 // for the selected node, we also draw a line and place two buttons for directions
    155.                 Handles.color = Color.red;
    156.                 Handles.DrawLine(guiDir, guiInvDir);
    157.  
    158.                 // draw quads direction and inverse direction if they are not selected
    159.                 if (selectionType != SelectionType.Node) {
    160.                     if (Button(guiPos, directionButtonStyle)) {
    161.                         selectionType = SelectionType.Node;
    162.                     }
    163.                 }
    164.                 if (selectionType != SelectionType.Direction) {
    165.                     if (Button(guiDir, directionButtonStyle)) {
    166.                         selectionType = SelectionType.Direction;
    167.                     }
    168.                 }
    169.                 if (selectionType != SelectionType.InverseDirection) {
    170.                     if (Button(guiInvDir, directionButtonStyle)) {
    171.                         selectionType = SelectionType.InverseDirection;
    172.                     }
    173.                 }
    174.             } else {
    175.                 if (Button(guiPos, nodeButtonStyle)) {
    176.                     selection = n;
    177.                     selectionType = SelectionType.Node;
    178.                 }
    179.             }
    180.         }
    181.         Handles.EndGUI();
    182.  
    183.         if (GUI.changed)
    184.             EditorUtility.SetDirty(target);
    185.     }
    186.  
    187.     bool Button(Vector2 position, GUIStyle style) {
    188.         return GUI.Button(new Rect(position - new Vector2(QUAD_SIZE / 2, QUAD_SIZE / 2), new Vector2(QUAD_SIZE, QUAD_SIZE)), GUIContent.none, style);
    189.     }
    190.  
    191.     public override void OnInspectorGUI() {
    192.         serializedObject.Update();
    193.         // hint
    194.         EditorGUILayout.HelpBox("Hold Alt and drag a node to create a new one.", MessageType.Info);
    195.  
    196.         // delete button
    197.         if(selection == null || spline.nodes.Count <= 2) {
    198.             GUI.enabled = false;
    199.         }
    200.         if (GUILayout.Button("Delete selected node")) {
    201.             Undo.RegisterCompleteObjectUndo(spline, "delete spline node");
    202.             DeleteNode(selection);
    203.             selection = null;
    204.         }
    205.         GUI.enabled = true;
    206.  
    207.         // nodes
    208.         // This special editor prevent the user to modify the list because nodes are listened.
    209.         // But I can't understand why these guys are not editable in the inspector...
    210.         EditorGUILayout.PropertyField(nodes);
    211.         EditorGUI.indentLevel += 1;
    212.         if (nodes.isExpanded) {
    213.             for (int i = 0; i < nodes.arraySize; i++) {
    214.  
    215.                 //I made it editable
    216.                 Vector3 localPointV3;
    217.                 EditorGUILayout.LabelField("Node " + i + ":");
    218.  
    219.                 EditorGUI.BeginChangeCheck();
    220.                 localPointV3 = EditorGUILayout.Vector3Field("World Position:", spline.transform.TransformPoint (spline.nodes [i].position));
    221.                 if (EditorGUI.EndChangeCheck()) {
    222.                     Undo.RecordObject(spline, "Move Point");
    223.                     spline.nodes [i].SetPosition (spline.transform.InverseTransformPoint (localPointV3));
    224.                     EditorUtility.SetDirty(spline);
    225.                 }
    226.                 EditorGUI.BeginChangeCheck();
    227.                 localPointV3 = EditorGUILayout.Vector3Field("World Direction:", spline.transform.TransformPoint (spline.nodes [i].direction));
    228.                 if (EditorGUI.EndChangeCheck()) {
    229.                     Undo.RecordObject(spline, "Move Point");
    230.                     spline.nodes [i].SetDirection (spline.transform.InverseTransformDirection (localPointV3));
    231.                     EditorUtility.SetDirty(spline);
    232.                 }
    233.  
    234.                 EditorGUI.BeginChangeCheck();
    235.                 localPointV3 = EditorGUILayout.Vector3Field("Local Position:", spline.nodes [i].position);
    236.                 if (EditorGUI.EndChangeCheck()) {
    237.                     Undo.RecordObject(spline, "Move Point");
    238.                     spline.nodes [i].SetPosition (localPointV3);
    239.                     EditorUtility.SetDirty(spline);
    240.                 }
    241.                 EditorGUI.BeginChangeCheck();
    242.                 localPointV3 = EditorGUILayout.Vector3Field("Local Direction:", spline.nodes [i].direction);
    243.                 if (EditorGUI.EndChangeCheck()) {
    244.                     Undo.RecordObject(spline, "Move Point");
    245.                     spline.nodes [i].SetDirection (localPointV3);
    246.                     EditorUtility.SetDirty(spline);
    247.                 }
    248.  
    249. //                EditorGUILayout.PropertyField(nodes.GetArrayElementAtIndex(i), new GUIContent("Node " + i), true);
    250.             }
    251.         }
    252.         EditorGUI.indentLevel -= 1;
    253.  
    254.         serializedObject.ApplyModifiedProperties();
    255.     }
    256.  
    257.     [MenuItem("GameObject/3D Object/Spline")]
    258.     public static void CreateSpline() {
    259.         new GameObject("Spline", typeof(Spline));
    260.     }
    261.  
    262.     public Vector3 ToVector3 (string s)
    263.     {
    264.         string[] temp = s.Substring (1, s.Length-2).Split (',');
    265.         return new Vector3 (float.Parse(temp[0]), float.Parse(temp[1]), float.Parse(temp[2]));
    266.     }
    267. }
    268.  

    Next up, I wanted to extrude a shape along a spline and I wanted to have multiple instances that were exclusive. Previously you could only have 1 extruded mesh and they were all the same mesh if you had more than 1 instance. I also made sure to apply this new mesh to the mesh colliders if they existed.

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

    Guacamolay

    Joined:
    Jun 24, 2013
    Posts:
    33
    Wow these are very useful features, thanks for sharing :). Let the integration begin! If I come up with anything that might be useful to someone else, I'll post it :)

    methusalah999 great job on the gizmos for the extrustion tool btw, works very nicely
     
    methusalah999 likes this.
  12. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    Thanks for the sharing !

    Don't you mind if I use some of your code for the next SplineMesh release?

    Could you show some visuals as a result here?

    Thank you for the kind words !

    SplineMesh is open-source and it is always a great pleasure to integrate contributions.
     
  13. hungseparate

    hungseparate

    Joined:
    Aug 25, 2014
    Posts:
    10
    Hi @methusalah999
    About following object with difference curve length issue, as i saw in your example :

    Code (CSharp):
    1. rate += Time.deltaTime / DurationInSecond;
    I tried to change DurationInSecond base on length ratio with the first curve, something like this :

    Code (CSharp):
    1. float _baseLength = _curve[0].Length;
    2.  
    3. float _ratio = _curve[1]  / _baseLength;
    4.  
    5. rate += Time.deltaTime / (DurationInSecond / _ratio );
    But it still not work, and i don't really understand about your ideas :

    "You just have to make your object velocity dependent on a fixed speed".
     
  14. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    In the "follow spline" exemple, the speed of the object is dependent on the spline length and the user-specified travelling duration.

    If I understand correctly, what you want to do is to specify a speed for the follower, in world units per seconds.

    I wrote a pseudo code for you. You will se the usage of Spline.GetTangentAlongSplineAtDistance, which is what you need here. I hope it helps.

    Code (CSharp):
    1.     public float constantSpeed;
    2.     private float traveledDistance = 0;
    3.  
    4.     void Update() {
    5.         // we increase the traveled distance according to speed of the follower
    6.         // and the elapsed time since last frame
    7.         traveledDistance += constantSpeed * Time.deltaTime;
    8.  
    9.         // (optionl) if the traveled distance is longer than spline length,
    10.         // we come back to the spline start to make the follower loop infinitly
    11.         if(traveledDistance > spline.Length) {
    12.             traveledDistance -= spline.Length;
    13.         }
    14.  
    15.         // we update the follower position and rotation accordingly to the traveled distance
    16.         follower.position = spline.GetLocationAlongSplineAtDistance(traveledDistance);
    17.         follower.rotation = CubicBezierCurve.GetRotationFromTangent(spline.GetTangentAlongSplineAtDistance(traveledDistance));
    18.     }
     
  15. hungseparate

    hungseparate

    Joined:
    Aug 25, 2014
    Posts:
    10
    Thanks for your reply, i already tried this, it worked "but" it's not steady at all, object keep shaking when moving along the path. You can see it on my video.

    This one using GetLocationAlongSplineAtDistance


    This one using GetLocationAlongSpline


    Do you know why this happen ? I think these 2 functions should make the same result ?
     
  16. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    This is another problem.

    Using rate, the calculation is very precise and I can't explain the shaking. Maybe the texture?

    Using distance there is an interpolation to find the correcte sample. Can you go in CubicBezierCurve class, line 17 and change the number of samples to 300 or 1000? This will have a massive performance impact but you should discover that it becomes as precise as the search by rate.
    Code (CSharp):
    1. private const int STEP_COUNT = 1000;
    Could you paste your code here for me to check things?
     
    Last edited: May 26, 2018
  17. hungseparate

    hungseparate

    Joined:
    Aug 25, 2014
    Posts:
    10
    After change to 300-500, it's perfect now, the "shaking" reduced a a lot.

    Here is my code :
    Code (CSharp):
    1. public class PlayerRoad : MonoBehaviour
    2. {
    3.     private Spline _spline;
    4.  
    5.     // Use this for initialization
    6.     void Start ()
    7.     {
    8.         _spline = GetComponent<Spline> ();  
    9.     }
    10.    
    11.     public Vector3 GetNextPosition (float rate)
    12.     {
    13.         return _spline.GetLocationAlongSplineAtDistance (rate);
    14.     }
    15.  
    16.     public Quaternion GetNextRotation (float rate)
    17.     {
    18.         return CubicBezierCurve.GetRotationFromTangent (_spline.GetTangentAlongSplineAtDistance (rate));
    19.     }
    20. }
    >> Game controller class
    Code (CSharp):
    1. void Update()
    2.     {
    3.         rate += Time.deltaTime * _moveSpeed;
    4.         _player.UpdatePosition (_playerRoad.GetNextPosition (rate), _playerRoad.GetNextRotation (rate));
    5.     }
    >> Player class
    Code (CSharp):
    1. // Update is called once per frame
    2.     public void UpdatePosition (Vector3 pos, Quaternion rot)
    3.     {
    4.         _myTransform.position = pos;
    5.         _myTransform.rotation = rot;
    6.     }
     
  18. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    So it's a precision problem of the sample points. Maybe I should let the user choose the number of sample in the Spline component.

    The best solution is to implement the Casteljau's divide-and-conquer algorithm, which allows to compute less samples where the curve is flat, and more where it is curvy. This way you can have an excellent precision while saving perfs.

    Contribution appreciated :)
     
  19. cspid

    cspid

    Joined:
    Apr 25, 2014
    Posts:
    13
    hello, I'm wondering if you can help me debug this error: "Time must be between 0 and last node index (2). Given time was 5.755882."

    Do you know what could be causing the time to exceed the node index?

    Thanks very much
     
  20. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    You are trying to use Spline.GetTangentAlongSpline with a time that exceeds the total spline time. The spline time is equal to the number of individual curve in the spline. Giving 5.75 means that you want to get a position at time 0.75 on the 6th curve, while your spline has only 2 curves.

    I can't tell what leaded you to do that without seeing the code. Can you explain or paste some code?
     
  21. cspid

    cspid

    Joined:
    Apr 25, 2014
    Posts:
    13
    Ah, ok it is probably that I am changing the durationInSeconds parameter from another script. I'm using the splines to predict footsteps, and I am multiplying durationInSeconds by the step distance. Would it make any sense to just use Mathf.Clamp on the 'rate' float? What is the most efficient way to set the speed of the follower?

    One other question if you don't mind - if I want to an object to only travel the length of a spline only once and then stop and remain at the end of the spline, what would I need to do? disabling the spline wont work in my case..

    Thanks so much for your help
     
  22. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    The "follow spline" is a very simple exemple to show how to make a game object move along the spline. But in the exemple, the follower has no speed, but a time to travel along the whole spline. The longer the spline, the faste it goes.

    For what I think you're trying to do, you should use a distance from 0 to spline's length, that increase accoridng to a contant speed at each update with something like that:
    Code (CSharp):
    1. dist += Time.deltaTime * speed;
    2. if(dist > spline.Length){
    3.     dist = spline.length;
    4. }
    5. position = spline.GetLocationAlongSplineAtDistance(dist);
    6. tangent = spline.GetTangentAlongSplineAtDistance(dist);
    Of course, you could have a boolean that becomes true when arrived, to avoid making pointless calculations forever.
     
  23. cspid

    cspid

    Joined:
    Apr 25, 2014
    Posts:
    13
    Fantastic! Thank you
     
  24. Mic17

    Mic17

    Joined:
    Jul 9, 2018
    Posts:
    1
    Hi methusala999,
    a have a short Question: I got a little Game for myself with your script "ExampleFollowSpline". But if i choose to "Build", Unity throws Errors - the UnityEditor-code is marked (i used your script 1:1).

    What have i to do, to build the Programm/Skript successfully?

    Thanks.
     
  25. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    tl:dr Use the snippet bellow instead of ExampleFollowSpline.

    Hi there and sorry for the late answer. You're right, this script can't be build as-is. As it was a demo, it was never intended. Good news is that the fix is simple, I'll update the plugin with it.

    Explanation : ExampleFollowSpline make the asset move along the spline during edit mode. I've done it for SplineMesh users to watch the result without even having to enter play mode. This is done by using the class EditorApplication. The issue is that this class is not packaged with your build and exists only in the Unity Editor environnement.

    The solution is to ask unity to ignore the lines where EditorApplication is used when it compiles your code to make a "build", using preprocessor directive #if as described here : https://answers.unity.com/questions/784973/the-name-unityeditor-does-not-exist-in-the-current.html.

    Code (CSharp):
    1. [ExecuteInEditMode]
    2. [RequireComponent(typeof(Spline))]
    3. public class ExampleFollowSpline : MonoBehaviour {
    4.  
    5.     public GameObject Follower;
    6.     public float DurationInSecond;
    7.  
    8.     [HideInInspector]
    9.     public GameObject go;
    10.  
    11.     private Spline spline;
    12.     private float rate = 0;
    13.  
    14.     private void OnEnable() {
    15.         rate = 0;
    16.         if(go == null) {
    17.             go = Instantiate(Follower, transform);
    18.         }
    19.  
    20.         go.transform.localRotation = Quaternion.identity;
    21.         go.transform.localPosition = Vector3.zero;
    22.         go.transform.localScale = Vector3.one;
    23.  
    24.         spline = GetComponent<Spline>();
    25.         PlaceFollower();
    26. #if UNITY_EDITOR
    27.         EditorApplication.update += EditorUpdate;
    28. #endif
    29.     }
    30.  
    31.     void OnDisable() {
    32. #if UNITY_EDITOR
    33.         EditorApplication.update -= EditorUpdate;
    34. #endif
    35.     }
    36.  
    37.     void EditorUpdate() {
    38.         rate += Time.deltaTime / DurationInSecond;
    39.         if (rate > spline.nodes.Count - 1) {
    40.             rate -= spline.nodes.Count - 1;
    41.         }
    42.         PlaceFollower();
    43.     }
    44.  
    45.     private void PlaceFollower() {
    46.         if (go != null) {
    47.             go.transform.localPosition = spline.GetLocationAlongSpline(rate);
    48.             go.transform.localRotation = CubicBezierCurve.GetRotationFromTangent(spline.GetTangentAlongSpline(rate));
    49.         }
    50.     }
    51. }
     
    Last edited: Jul 12, 2018
  26. gdelomier

    gdelomier

    Joined:
    Jan 6, 2016
    Posts:
    2
    Hi,
    First thank you for this plugin, it's really great and help me with my current development.

    I also find a weird behavior when updating the spline at runtime. it seems thats randomly I get this (and so rarely I don't know how to replicate) :
    When updating spline at runtime with a S
    etPosition from one of my function I get a nullreference from UnityEvent. Here is the callstack from the plugin part :
    If you have any idea where it could come from I would really appreciate your help.

    In any case thanks again for your work :)
     
  27. RendCycle

    RendCycle

    Joined:
    Apr 24, 2016
    Posts:
    299
    Thanks for this asset! I'm wondering, can this be used to create some sort of a Net that I can animate in runtime later on similar to this Youtube video?
     
  28. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    Nope, SplineMesh uses Bezier curves to interpolate things (like mesh vertices) between curve points.

    The example you linked is more about physics : a grid of points reacting to gravity and respecting the constraints of a physic link. An interesting subject, but with nothing in common with SplineMesh.

    But you may try to study Bezier surfaces as it provides some good physic simulations like fabric motion, etc.
     
    RendCycle likes this.
  29. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    Hi and thanks for your kind words :)

    I've found some wonky code that might cause the problem. I was using an anonymous lambda expression, which most likely prevents the RemoveListener to work properly. I would like you to try this fix :
    n1.Changed.AddListener(ComputePoints);

    instead of
    n1.Changed.AddListener(() => ComputePoints());


    Could you please go in the CubicBezierCurve class and replace the existing code from line 33 to 67 by the following one, and give me feedback?
    Code (CSharp):
    1.    /// <summary>
    2.     /// Build a new cubic Bézier curve between two given spline node.
    3.     /// </summary>
    4.     /// <param name="n1"></param>
    5.     /// <param name="n2"></param>
    6.     public CubicBezierCurve(SplineNode n1, SplineNode n2)
    7.     {
    8.         this.n1 = n1;
    9.         this.n2 = n2;
    10.         n1.Changed.AddListener(ComputePoints);
    11.         n2.Changed.AddListener(ComputePoints);
    12.         ComputePoints();
    13.     }
    14.  
    15.     /// <summary>
    16.     /// Change the start node of the curve.
    17.     /// </summary>
    18.     /// <param name="n1"></param>
    19.     public void ConnectStart(SplineNode n1) {
    20.         this.n1.Changed.RemoveListener(ComputePoints);
    21.         this.n1 = n1;
    22.         n1.Changed.AddListener(ComputePoints);
    23.         ComputePoints();
    24.     }
    25.  
    26.     /// <summary>
    27.     /// Change the end node of the curve.
    28.     /// </summary>
    29.     /// <param name="n2"></param>
    30.     public void ConnectEnd(SplineNode n2) {
    31.         this.n2.Changed.RemoveListener(ComputePoints);
    32.         this.n2 = n2;
    33.         n2.Changed.AddListener(ComputePoints);
    34.         ComputePoints();
    35.     }
     
  30. RendCycle

    RendCycle

    Joined:
    Apr 24, 2016
    Posts:
    299
    Ok, thanks for the tip! :)
     
  31. gdelomier

    gdelomier

    Joined:
    Jan 6, 2016
    Posts:
    2
    Thanks I'll give a try :)
     
  32. Javideas

    Javideas

    Joined:
    Jul 19, 2016
    Posts:
    1
    Hi, first thank you very much!
    I just tried today your plugin and I thinking about purchasing (I know that doesn't give any extras) for a commercial project. Im a 3D artist with a basic understanding of C#, an expert of mixing scripts mostly.

    I am having some difficulties so
    here is what I want to accomplish:

    I made a "game/app" where you start with an sphere. From here you can create another one, like a "son" (although there is no hierarchy yet) in 3D.

    I would like to access to the "node 0" and "node n(the higher number aka the last one) positions, in order to parent them each one to each sphere. One node to "father" sphere and another one to "son" sphere. So if I move "father" sphere, "node 0" will follow. And if I move "son" sphere, "node n" will follow.

    But I can't access those node positions. As I said I am not a professional programmer so what I tried is to write inside "Spline Script":
    //
    public GameObject FatherPosition
    public GameObject SonPosition
    //
    So I can relate the "Vector 3D default position" of each node(0 and 1) in the code lines I found in your "Spline Script" to father/son positions....

    But nothing become public in your "Spline Script". I can't drop father/son gameobjects anywhere. It's like is bloked.

    I am sure that have to be a better or easy way. I hope I explain myself correctly.

    Thanks in advance!
     
  33. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    You should not edit the Spline script, but one of the examples instead. Let's have a look to the ExamplePipe script together :
    On line 31, we get the Spline component that is present on the game object with
    spline = GetComponent<Spline>();
    . And on line 59, you can see a call to
    spline.GetCurves()
    . This is the correct way to access the spline nodes.

    As you can see in the CubicBezierCurve, which is basically a "segment" of the spline, n1 and n2 elements are the start and end node for this curve. The n2 point of the curve "n", will be the n1 node of the curve "n+1". Curves are connected this way. Each node have a position and a direction.

    So as far as I understand what you are trying to do :
    • father's position is
      spline.GetCurves().First().n1.position
    • son's positionis
      spline.GetCurves().Last().n2.position

    (First() and Last() methods come from Linq, please use GetCurves()[0] and GetCurves()[GetCurves().Count-1] if you don't know about Linq)

    I hope it helps.

    Ben
     
  34. Justice0Juic3

    Justice0Juic3

    Joined:
    Apr 29, 2013
    Posts:
    143
    I think I've missed something. Why are there no colliders on my spline?
     
  35. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    In the SplineMesh showroom, you can see MeshCollider components on the pipe, the tentacle or the extruded gutter. Hit play to see the white ball rolling along the curved gutter.

    Note that the MeshCollider is probably building the colliding shape at game start. If you update your extruded or bended mesh during play mode, you may have to ask the MeshCollider to update too.

    upload_2018-8-18_21-51-51.png
     
    Justice0Juic3 likes this.
  36. Fletcher-Studios

    Fletcher-Studios

    Joined:
    Sep 25, 2014
    Posts:
    5
    This looks awesome! Does it work in edit mode only, or can it work when the game is playing? I'm wondering if the player can pick up an end and move it - like a rope?
     
  37. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    SplineMesh is updated in both edit and play mode. You can totally do what you describe.

    But note that mesh update is time consuming. With a lot of nodes and a lot of vertices to deform, you will have less performances.
     
    Fletcher-Studios likes this.
  38. Mad_Fox

    Mad_Fox

    Joined:
    May 27, 2013
    Posts:
    47
    Hi! Very nice asset! Thanks for taking the time to make it.
    Quick question, is there a way to find the closest point on the spline to a given worldspace target point?
     
  39. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    You can simply iterate through sample points along the spline, and find the closest to target like this :

    Code (CSharp):
    1. Vector3 closest;
    2. float minDistance = float.PositiveInfinity;
    3. for(float sampleDistance = 0; sampleDistance < spline.Length; sampleDistance += SAMPLE_STEP) {
    4.     Vector3 sample = spline.GetLocationAlongSplineAtDistance(sampleDistance);
    5.     float dist = Vector3.Distance(sample, target);
    6.     if (dist < minDistance) {
    7.         minDistance = dist;
    8.         closest = sample;
    9.     }
    10. }
    The less SAMPLE_STEP is, the more precise and more time consuming it will be.

    Unfortunatly, I haven't implemented the mathematical approach. It's a well documented subject, and contributions to SplineMesh are welcome ^^
    https://hal.inria.fr/file/index/docid/518379/filename/Xiao-DiaoChen2007c.pdf
    https://en.wikipedia.org/wiki/Sturm's_theorem
     
    Mad_Fox likes this.
  40. Mad_Fox

    Mad_Fox

    Joined:
    May 27, 2013
    Posts:
    47
    Thanks for the quick response!
     
  41. JaniFucka

    JaniFucka

    Joined:
    Apr 26, 2018
    Posts:
    4
    Hey, an amazing asset, only just started using but I already know I'll purchase it sometime in the future, saved me a bunch of time so far!

    I'm wondering if you could help me with something, if it's even possible to do. I'm creating a tenticle-like sculpture that I would like to animate growing out of the ground. Was hoping to acomplish that with Unity's animation window by adding nodes to the SplineMesh while in record mode, though it might automatically make the animation of the sculpture growing as I'm creating it.
    Nothing I tried so far worked, scrolling through the time line after adding keyframes for each new node just shows the mesh in it's last state, with all the nodes I added in process.
    I'm guessing it can be achieved by manually adding keyframe to a specific instance of the GameObject in preview mode in animatiob window. The thing is that there's a lot of these instances when dealing with an asset created with SplineMesh, I'm hoping you could maybe point out which ones I need to include?

    I'm farly new at animations, only dealt with simple transform animations and this is by far the most complex thing I'm trying. Any imput would be helpful!
     
  42. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    Hi and thanks for you kind words about the asset.

    What you are trying to do seems great and I would love to see the final result ! I have no experience with animation in Unity but I'm pretty sure you will have a hard time doing that.

    Unity will need to interpolate things between key frames, but SplineMesh produces a new mesh each time a spline node changes. Unity won't be able to interpolate vertices between different mesh instances and you will have to rely on SplineMesh to update the meshes.

    Also, the motion is not linear but follow a bézier curve. Unity knows nothing about this curve and you will certainly need a lot of key frames per second to make the animation smooth. You will face performance issues if there is a lot of tentacles with a lot of nodes.

    But it's worth a try !

    I feel like you are trying to add the children of your Spline object to the key frames. That won't work : children are only generated meshes. The Spline and ExampleTentacle components are all you need and they should be the only one added to the key frames. Children objects will be created/updated by the ExampleTentacle component when needed and shouldn't be serialized at all.

    This way, nodes creation should work correctly but I can see a couple reasons why it may fail. In such case, you can try to instantiate all nodes from the beginning, and make unused nodes overlap, until they begin to grow.

    It's all I can tell right now, I hope it helps.
     
  43. JaniFucka

    JaniFucka

    Joined:
    Apr 26, 2018
    Posts:
    4
    Thank you for a qick reply and the tips! I did not think about it in that way at all ans while I realized new meshes are being created, I didn't connect that with my issue...

    Will let you know how it goes, thanks again for now!
     
  44. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    Mad_Fox likes this.
  45. Caruos

    Caruos

    Joined:
    Dec 30, 2016
    Posts:
    8
    Hello,

    I have started using this asset, and I really love it so far (gave it 5 stars on the asset store ;) )

    That being said, I have a problem when I try to create a rail with a non-uniform texture (such as stripes). In this example, the cylinder and the rail have the same material, the cylinder has stripes as it should but the rail just mixes the colors together. Does anyone know how to fix this ?

    upload_2018-9-16_21-56-57.png
     
  46. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    I think maybe the UVmap is messed up and the texture is repeated too often. Can you zoom on the object or scale it larger to confirm?

    If that's the issue, it's a bug on the mesh bender that I will need to work on.
     
  47. Caruos

    Caruos

    Joined:
    Dec 30, 2016
    Posts:
    8
    Thanks for the quick answer.

    I enlarged the rail and zoomed as much as possible, but I only see an unique color :

    upload_2018-9-16_23-57-12.png

    Edit : I tried to increase and decrease the tiling, without any visible result.
     
    Last edited: Sep 16, 2018
  48. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    I've looked on my side and I cannot reproduce. The UV mapping scales and bends fine. Could you please send me your mesh and texture in a private message so I can investigate?
     
  49. Caruos

    Caruos

    Joined:
    Dec 30, 2016
    Posts:
    8
    As we discussed, the striped texture didn't apply because the base cylinder mesh in the demo scene doesn't have an UV map. I added one on Blender and now it works perfectly.

    upload_2018-9-17_23-2-20.png

    The mesh is in the attachment. Please feel free to use it in your asset ;)
     

    Attached Files:

  50. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    44
    thank you for your contribution and have fun with SplineMesh !