Search Unity

How do i Extrude mesh along a Cubic Bezier Spline?

Discussion in 'Scripting' started by alexander11, Jul 22, 2016.

  1. alexander11

    alexander11

    Joined:
    Aug 11, 2014
    Posts:
    94
    Hello, i have been researching on how to extrude a Mesh along a Spline(Cubic Bezier), but i have found nothing on how to Extrude Mesh.

    I've gone to multiple websites to learn Mesh Extrusion but it fails to work when i implement it in code(C#).
    i have contacted Joachim holmer(who did the Unite 2015 presentation about spline-based geometry) but he has not contacted me back.
    I have look at Unity's Procedural Examples but i do not understand it.

    I can achieve the Bezier Splines but i cant Extrude Mesh along it.

    If you guys can help me to Extrude Mesh in this script of mine i will be very pleased.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3. [RequireComponent(typeof(LineRenderer))]
    4. public class Bezier : MonoBehaviour
    5. {
    6.     public Transform[] controlPoints;
    7.     public Vector3[] pts;
    8.     public LineRenderer lineRenderer;
    9.     public int curveCount = 0;
    10.     public int layerOrder = 0;
    11.     public int SEGMENT_COUNT = 50;
    12.     public Mesh extrudeShape;
    13.    
    14.     void Start()
    15.     {
    16.         //--!
    17.         if (!lineRenderer)
    18.         {
    19.             lineRenderer = GetComponent<LineRenderer>();
    20.         }
    21.         lineRenderer.sortingLayerID = layerOrder;
    22.         //--!
    23.  
    24.  
    25.         //-O
    26.         curveCount = (int)controlPoints.Length / 3;
    27.     }
    28.     void Update()
    29.     {
    30.         DrawCurve();
    31.     }
    32.     void DrawCurve()
    33.     {
    34.         pts = new Vector3[controlPoints.Length];
    35.         for (int i = 0; i < controlPoints.Length; i++)
    36.         {
    37.             pts[i] = controlPoints[i].position;
    38.         }
    39.         // how do i make this function extrude mesh?
    40.         for (int j = 0; j < curveCount; j++)
    41.         {
    42.             for (int i = 1; i <= SEGMENT_COUNT; i++)
    43.             {
    44.                 float t = i / (float)SEGMENT_COUNT;
    45.                 int nodeIndex = j * 3;
    46.                 Vector3 pixel = CalculateCubicBezierPoint(t, pts);
    47.                 lineRenderer.SetVertexCount(((j * SEGMENT_COUNT) + i));
    48.                 lineRenderer.SetPosition((j * SEGMENT_COUNT) + (i - 1), pixel);
    49.                
    50.             }
    51.         }
    52.     }
    53.     Vector3 CalculateCubicBezierPoint(float t,Vector3[] pts)
    54.     {
    55.         float omt = 1f - t;
    56.         float omt2 = omt * omt;
    57.         float t2 = t * t;
    58.         return pts[0] * (omt2 * omt) +
    59.                 pts[1] * (3f * omt2 * t) +
    60.                 pts[2] * (3f * omt * t2) +
    61.                 pts[3] * (t2 * t);
    62.  
    63.     }
    64.     Vector3 GetTangent(Vector3[] pts, float t)
    65.     {
    66.         float omt = 1f - t;
    67.         float omt2 = omt * omt;
    68.         float t2 = t * t;
    69.         Vector3 tangent =
    70.                     pts[0] * (-omt2) +
    71.                     pts[1] * (3 * omt2 - 2 * omt) +
    72.                     pts[2] * (-3 * t2 + 2 * t) +
    73.                     pts[3] * (t2);
    74.         return tangent.normalized;
    75.     }
    76.     Vector3 GetNormal3D(Vector3[] pts, float t, Vector3 up)
    77.     {
    78.         Vector3 tng = GetTangent(pts, t);
    79.         Vector3 binormal = Vector3.Cross(up, tng).normalized;
    80.         return Vector3.Cross(tng, binormal);
    81.     }
    82.     Quaternion GetOrientation3D(Vector3[] pts, float t, Vector3 up)
    83.     {
    84.         Vector3 tng = GetTangent(pts, t);
    85.         Vector3 nrm = GetNormal3D(pts, t, up);
    86.         return Quaternion.LookRotation(tng, nrm);
    87.     }
    88.     public struct OrientedPoint
    89.     {
    90.  
    91.         public Vector3 position;
    92.         public Quaternion rotation;
    93.  
    94.         public OrientedPoint(Vector3 position, Quaternion rotation)
    95.         {
    96.             this.position = position;
    97.             this.rotation = rotation;
    98.         }
    99.  
    100.         public Vector3 LocalToWorld(Vector3 point)
    101.         {
    102.             return position + rotation * point;
    103.         }
    104.  
    105.         public Vector3 WorldToLocal(Vector3 point)
    106.         {
    107.             return Quaternion.Inverse(rotation) * (point - position);
    108.         }
    109.  
    110.         public Vector3 LocalToWorldDirection(Vector3 dir)
    111.         {
    112.             return rotation * dir;
    113.         }
    114.  
    115.     }
    116.  
    117. }
    118.  
    119.  
     
  2. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,435
    eses likes this.
  3. alexander11

    alexander11

    Joined:
    Aug 11, 2014
    Posts:
    94
  4. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,435
    there's Vector3[] TunnelVertices() and int[] TunnelTriangles() which might be all you need..
     
  5. alexander11

    alexander11

    Joined:
    Aug 11, 2014
    Posts:
    94
    Ok i see i kind of understand it.

    (this question may seem Irrelevant but before i "may" run into trouble later on i would like to ask a question)
    now for an example extruding a quad how would i do this correctly, in my mind i have to create a list of vertices and normals, then somehow create the triangle according to the vertex values then update the mesh(if you do know this in code it will work a lot better with me since its easier for me to understand with functioning code)?
     
  6. cmdr2

    cmdr2

    Joined:
    Apr 21, 2015
    Posts:
    7
    I ran into the same problem after watching his talk. Here's a working implementation (including ExtrudeShape) that was written by piecing together what he described through the talk:

    Code (CSharp):
    1. [RequireComponent(typeof(MeshFilter))]
    2.     [RequireComponent(typeof(MeshFilter))]
    3.     public class GenerateMesh02 : MonoBehaviour {
    4.         /* scratchpad */
    5.         private MeshFilter mf;
    6.  
    7.  
    8.         void Start () {
    9.             mf = GetComponent<MeshFilter> ();
    10.  
    11.             GenerateMesh ();
    12.         }
    13.  
    14.  
    15.         private void GenerateMesh() {
    16.             var mesh = GetMesh ();
    17.             var shape = GetExtrudeShape ();
    18.             var path = GetPath ();
    19.  
    20.             Extrude (mesh, shape, path);
    21.         }
    22.  
    23.  
    24.         private ExtrudeShape GetExtrudeShape() {
    25.             var verts = new Vertex[] {
    26.                 new Vertex(
    27.                     new Vector3(0, 0, 0),
    28.                     new Vector3(0, 1, 0),
    29.                     0),
    30.                 new Vertex(
    31.                     new Vector3(2, 0, 0),
    32.                     new Vector3(0, 1, 0),
    33.                     0.5f),
    34.                 new Vertex(
    35.                     new Vector3(2, 0, 0),
    36.                     new Vector3(0, 1, 0),
    37.                     0.5f),
    38.                 new Vertex(
    39.                     new Vector3(4, 0, 0),
    40.                     new Vector3(0, 1, 0),
    41.                     1)
    42.             };
    43.  
    44.             var lines = new int[] {
    45.                 0, 1,
    46.                 1, 2,
    47.                 2, 3
    48.             };
    49.  
    50.             return new ExtrudeShape (verts, lines);
    51.         }
    52.  
    53.  
    54.         private OrientedPoint[] GetPath() {
    55.             /*return new OrientedPoint[] {
    56.                 new OrientedPoint(
    57.                     new Vector3(0, 0, 0),
    58.                     Quaternion.identity),
    59.                 new OrientedPoint(
    60.                     new Vector3(0, 1, 1),
    61.                     Quaternion.identity),
    62.                 new OrientedPoint(
    63.                     new Vector3(0, 0, 2),
    64.                     Quaternion.identity)
    65.             };*/
    66.  
    67.             var p = new Vector3[] {
    68.                 new Vector3(0, 0, 0),
    69.                 new Vector3(0, 0, 10),
    70.                 new Vector3(10, 0, 10),
    71.                 new Vector3(10, 0, 0)
    72.             };
    73.  
    74.             var path = new List<OrientedPoint> ();
    75.  
    76.             for (float t = 0; t <= 1; t += 0.1f) {
    77.                 var point = GetPoint (p, t);
    78.                 var rotation = GetOrientation3D (p, t, Vector3.up);
    79.                 path.Add (new OrientedPoint (point, rotation));
    80.             }
    81.  
    82.             return path.ToArray ();
    83.         }
    84.  
    85.  
    86.         private Mesh GetMesh() {
    87.             if (mf.sharedMesh == null) {
    88.                 mf.sharedMesh = new Mesh ();
    89.             }
    90.             return mf.sharedMesh;
    91.         }
    92.  
    93.  
    94.         private Vector3 GetPoint(Vector3[] p, float t) {
    95.             float omt = 1f - t;
    96.             float omt2 = omt * omt;
    97.             float t2 = t * t;
    98.             return
    99.                 p [0] * (omt2 * omt) +
    100.                 p [1] * (3f * omt2 * t) +
    101.                 p [2] * (3f * omt * t2) +
    102.                 p [3] * (t2 * t);
    103.         }
    104.  
    105.  
    106.         private Vector3 GetTangent(Vector3[] p, float t) {
    107.             float omt = 1f - t;
    108.             float omt2 = omt * omt;
    109.             float t2 = t * t;
    110.             Vector3 tangent =
    111.                 p [0] * (-omt2) +
    112.                 p [1] * (3 * omt2 - 2 * omt) +
    113.                 p [2] * (-3 * t2 + 2 * t) +
    114.                 p [3] * (t2);
    115.             return tangent.normalized;
    116.         }
    117.  
    118.  
    119.         private Vector3 GetNormal3D(Vector3[] p, float t, Vector3 up) {
    120.             var tng = GetTangent (p, t);
    121.             var binormal = Vector3.Cross (up, tng).normalized;
    122.             return Vector3.Cross (tng, binormal);
    123.         }
    124.  
    125.  
    126.         private Quaternion GetOrientation3D(Vector3[] p, float t, Vector3 up) {
    127.             var tng = GetTangent (p, t);
    128.             var nrm = GetNormal3D (p, t, up);
    129.             return Quaternion.LookRotation (tng, nrm);
    130.         }
    131.  
    132.  
    133.         private void Extrude(Mesh mesh, ExtrudeShape shape, OrientedPoint[] path) {
    134.             int vertsInShape = shape.verts.Length;
    135.             int segments = path.Length - 1;
    136.             int edgeLoops = path.Length;
    137.             int vertCount = vertsInShape * edgeLoops;
    138.             int triCount = shape.lines.Length * segments;
    139.             int triIndexCount = triCount * 3;
    140.  
    141.             var triangleIndices = new int[triIndexCount];
    142.             var vertices = new Vector3[vertCount];
    143.             var normals = new Vector3[vertCount];
    144.             var uvs = new Vector2[vertCount];
    145.  
    146.             float totalLength = 0;
    147.             float distanceCovered = 0;
    148.             for (int i = 0; i < path.Length - 1; i++) {
    149.                 var d = Vector3.Distance (path [i].position, path [i + 1].position);
    150.                 totalLength += d;
    151.             }
    152.  
    153.             for (int i = 0; i < path.Length; i++) {
    154.                 int offset = i * vertsInShape;
    155.                 if (i > 0) {
    156.                     var d = Vector3.Distance (path [i].position, path [i - 1].position);
    157.                     distanceCovered += d;
    158.                 }
    159.                 float v = distanceCovered / totalLength;
    160.  
    161.                 for (int j = 0; j < vertsInShape; j++) {
    162.                     int id = offset + j;
    163.                     vertices [id] = path [i].LocalToWorld (shape.verts [j].point);
    164.                     normals [id] = path [i].LocalToWorldDirection (shape.verts [j].normal);
    165.                     uvs [id] = new Vector2 (shape.verts [j].uCoord, v);
    166.                 }
    167.             }
    168.             int ti = 0;
    169.             for (int i = 0; i < segments; i++) {
    170.                 int offset = i * vertsInShape;
    171.                 for (int l = 0; l < shape.lines.Length; l += 2) {
    172.                     int a = offset + shape.lines [l] + vertsInShape;
    173.                     int b = offset + shape.lines [l];
    174.                     int c = offset + shape.lines [l + 1];
    175.                     int d = offset + shape.lines [l + 1] + vertsInShape;
    176.                     triangleIndices [ti] = c; ti++;
    177.                     triangleIndices [ti] = b; ti++;
    178.                     triangleIndices [ti] = a; ti++;
    179.                     triangleIndices [ti] = a; ti++;
    180.                     triangleIndices [ti] = d; ti++;
    181.                     triangleIndices [ti] = c; ti++;
    182.                 }
    183.             }
    184.  
    185.  
    186.             mesh.Clear ();
    187.             mesh.vertices = vertices;
    188.             mesh.normals = normals;
    189.             mesh.uv = uvs;
    190.             mesh.triangles = triangleIndices;
    191.         }
    192.  
    193.  
    194.         public struct ExtrudeShape {
    195.             public Vertex[] verts;
    196.             public int[] lines;
    197.  
    198.             public ExtrudeShape(Vertex[] verts, int[] lines) {
    199.                 this.verts = verts;
    200.                 this.lines = lines;
    201.             }
    202.         }
    203.  
    204.  
    205.         public struct Vertex {
    206.             public Vector3 point;
    207.             public Vector3 normal;
    208.             public float uCoord;
    209.  
    210.  
    211.             public Vertex(Vector3 point, Vector3 normal, float uCoord) {
    212.                 this.point = point;
    213.                 this.normal = normal;
    214.                 this.uCoord = uCoord;
    215.             }
    216.         }
    217.  
    218.  
    219.         public struct OrientedPoint {
    220.             public Vector3 position;
    221.             public Quaternion rotation;
    222.  
    223.  
    224.             public OrientedPoint(Vector3 position, Quaternion rotation) {
    225.                 this.position = position;
    226.                 this.rotation = rotation;
    227.             }
    228.  
    229.  
    230.             public Vector3 LocalToWorld(Vector3 point) {
    231.                 return position + rotation * point;
    232.             }
    233.  
    234.  
    235.             public Vector3 WorldToLocal(Vector3 point) {
    236.                 return Quaternion.Inverse (rotation) * (point - position);
    237.             }
    238.  
    239.  
    240.             public Vector3 LocalToWorldDirection(Vector3 dir) {
    241.                 return rotation * dir;
    242.             }
    243.         }
    244.     }
    245.  
    The points in GetExtrudeShape() define the "unit" line that'll be extruded, and the path in GetPath() is the set of points generated along the bezier curve defined in it.
     
    Last edited: May 15, 2018
  7. alexander11

    alexander11

    Joined:
    Aug 11, 2014
    Posts:
    94
  8. AndresSepulveda

    AndresSepulveda

    Joined:
    Mar 12, 2014
    Posts:
    52
    Hi.
    I watched the video too and i can't get it working.
    I followed your script @cmdr2 but the uv are not displayed correctly.
    So that is the last piece that must be decoded.
     
  9. Deleted User

    Deleted User

    Guest

    Code works great. Easy to follow and modify. Thanks cmdr2
     
  10. Rama_M

    Rama_M

    Joined:
    Apr 10, 2017
    Posts:
    1
    The error with the Code above (@cmdr2 ) is that the uvs on the left (i.e, u) are always 0. Eventually at the end of the curve the u should move from 0 to 1 and v from 1 to 0.

    P.S. I am a newbie and would like to know if I am wrong. Thanks
     
  11. cmdr2

    cmdr2

    Joined:
    Apr 21, 2015
    Posts:
    7
    @Don-Frag @Rama_IEE You're right, the UV part was incomplete, I've updated the script in the post above to apply the V across the track. The U specified in the GetExtrudeShape() is an example, it can be whatever suits the shape. I've changed it to go from 0 to 1.
     
  12. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    I'm working on a plugin that cover that need. You can check it in the WIP post
     
  13. PeteMichaud

    PeteMichaud

    Joined:
    Jul 11, 2015
    Posts:
    25
    Hey, sorry to resurrect an old thread, but I'm hoping to ask @cmdr2:

    In your code are GetPoint() and GetTangent() -- what's going on in those methods? what does the variable t represent? What is omt?

    It seems like maybe you're trying to interpolate between the points on the path and that t represents the progress from beginning to end? Ie. regardless of the number of points, the final mesh will have 10 steps along the path?
     
    Last edited: Aug 15, 2017
  14. cmdr2

    cmdr2

    Joined:
    Apr 21, 2015
    Posts:
    7
    @PeteMichaud Yeah you're right, GetPoint() and GetTangent() will return the point along the curve, and 't' represents the progress along it. This curve is defined by four points (which is the 'p' in GetPath). Note: these four points are NOT the points the curve passes through, but the points the curve tries to fit among. The original video does a better job describing what's done here (I've forwarded to the relevant time):
    (you can rewind a bit for an explanation of what those four points mean).

    And yes, because 't' increments with 0.1f, there will be 10 steps in the generated curve mesh. If you reduce that, then the curve will have sharper edges and fewer vertices, while increasing the steps gives you a smoother mesh with more vertices.

    omt is just 'one minus t' :)

    While the approach is quite general and can be extended, the limitation of the above implementation is that only 4 points are supported, and that only produces one curve. If you want a longer path than one curve (which is almost always the case), the implementation will need to be expanded.

    For my game, I got rid of the curve portions (i.e. removed GetPoint(), GetTangent() and their use in GetPath()) and changed GetPath() to return a simple list of points that my path should go through. I've given an example below. Obviously this means the smooth curves are no longer automatically generated, but if someone expands the above implementation to support more points than 4, it'd be awesome if they post it here! :)

    Code (csharp):
    1.  
    2. private OrientedPoint[] GetPath() {
    3.         List<OrientedPoint> path = new List<OrientedPoint> ();
    4.  
    5.         foreach (Transform point in trackPointsParent) { // trackPointsParent is a transform with empty child gameObjects describing the points for the path to follow, and their orientation
    6.             if (point == trackPointsParent || !point.gameObject.activeSelf) {
    7.                 continue;
    8.             }
    9.  
    10.             path.Add (new OrientedPoint (point.position, point.rotation));
    11.         }
    12.  
    13.         return path.ToArray();
    14. }
    15.  
    Cheers
    ~cmdr2
     
  15. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    chelnok likes this.
  16. badr_douah

    badr_douah

    Joined:
    Jun 22, 2017
    Posts:
    22
    Hello,
    can someone explain to me this part of code , i'm currently trying to orient my 3d mesh "cylinder" along the spline but i can't wrap my hear around this part of code , any help is much appreciated

    Code (CSharp):
    1.   public struct OrientedPoint
    2.     {
    3.         public Vector3 position;
    4.         public Quaternion rotation;
    5.  
    6.  
    7.         public OrientedPoint(Vector3 position, Quaternion rotation)
    8.         {
    9.             this.position = position;
    10.             this.rotation = rotation;
    11.         }
    12.  
    13.  
    14.         public Vector3 LocalToWorld(Vector3 point)
    15.         {
    16.             return position + rotation * point;
    17.         }
    18.  
    19.  
    20.         public Vector3 WorldToLocal(Vector3 point)
    21.         {
    22.             return Quaternion.Inverse(rotation) * (point - position);
    23.         }
    24.  
    25.  
    26.         public Vector3 LocalToWorldDirection(Vector3 dir)
    27.         {
    28.             return rotation * dir;
    29.         }
    30.     }
     
  17. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    OrientedPoint can be seen as an arrow in space. You need to have 3D coordinates for the arrow start, and the direction. These data are stored here as a Vector3 and a Quaternion.

    For you convinience, the author proposes methods to convert these data from local to world references and vice/versa.

    If there is something more specific you want to know, just ask.

    And give a try to SplineMesh, a free and open-source plugin on the asset store. It covers the whole extrusion/deformation subject and more.
     
    badr_douah likes this.
  18. badr_douah

    badr_douah

    Joined:
    Jun 22, 2017
    Posts:
    22
    @methusalah999 thank you very much for the explanation, i was trying to orient a cylindrical mesh to fit perfectly a bezier spline i created, i ended up doing this in slightly different way because honestly i didn't know what that code i posted above is really doing ,
    i opened a discussion somewhere else and got my problem solved ,i post the link here in case this can become useful for someone else
    https://forum.unity.com/threads/ori...g-center-to-spline-point.513267/#post-3361214
     
  19. alfa1993

    alfa1993

    Joined:
    Aug 20, 2015
    Posts:
    26
    @cmdr2 can i ask how you understand his choice to use vector2 for 2d shape. In video looks like he is describing shape in xy plane. I really do not understand this part. Some one said that the extrude process happends in z access but I disagree
     
  20. cmdr2

    cmdr2

    Joined:
    Apr 21, 2015
    Posts:
    7
    @alfa1993 I'm not sure which part of the code you're referring to, since Vector2 is only being used for UV coords. My guess is that you're looking at the variable called "vert2Ds" in GetExtrudeShape(), but if you notice that's actually a Vector3 (wrapped inside a custom class). That's a poorly named variable, and I have no idea why it's not called "verts" or something. Edit: I've updated the code in my original post above to use "verts" as the variable name instead of "vert2Ds".

    I think you're right, extrude doesn't necessarily need to be of 2D shapes. The code above works for extruding 3D shapes too, since it already takes Vector3 in GetExtrudeShape()

    Also, @methusalah999 your plugin is really cool.
     
    Last edited: May 15, 2018
    methusalah999 likes this.
  21. alfa1993

    alfa1993

    Joined:
    Aug 20, 2015
    Posts:
    26
  22. Simone83

    Simone83

    Joined:
    Feb 1, 2015
    Posts:
    2
    Is there a possibility to use your plugin at runtime, not in Editor? How to move the spline nodes at runtime?.
     
  23. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Spline nodes are accessible via Spline component. You only have to use methods SetPosition and SetDirection on the nodes. Everything will update automatically thanks the the observer pattern (don't change position and direction fields directly or events won't be raised).

    It works in edit and play mode.

    Please note that mesh deformation is time consuming. The more vertices, the more CPU time it will need.
     
  24. Simone83

    Simone83

    Joined:
    Feb 1, 2015
    Posts:
    2
    But when I spawn tube segments at runtime they are separated. Could you write an example of class working at runtime, when you can move nodes (as gameObjects and at runtime) to manipulate the tube?
     
  25. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    Sorry for the late answer. Here is a quick and dirty demo that shows node motion and node addition in play mode. Works fine, even if the visual result is WTF ^^

    In the EamplePipe script, add the following code in the update method, line 42 :
    Code (CSharp):
    1. if (EditorApplication.isPlaying && spline.nodes.Any()) {
    2.     SplineNode last = spline.nodes[spline.nodes.Count - 1];
    3.     last.SetPosition(last.position + Vector3.left / 100);
    4.     if (UnityEngine.Random.value > 0.99) {
    5.         spline.AddNode(new SplineNode(last.position + Vector3.one, last.direction));
    6.     }
    7. }
    8.  
    Hope it helps.
     
  26. BStheOne

    BStheOne

    Joined:
    Mar 10, 2015
    Posts:
    6
    Can anybody help me generate a mesh using unity's U2D.Spline created with Sprite Shape?
    I want to generate a 3d road using a new Sprite Shape that I have. I tried to convert the above methods, but I failed.
     
  27. methusalah999

    methusalah999

    Joined:
    May 22, 2017
    Posts:
    643
    I don't know Sprite Shape, but I beleive it uses cubic bezier curves. If you are able to get the position of the control points of the spline from Sprite Shape, you can build a spline with SplineMesh by creating node with same values.

    In SplineMesh, positions and direction are accessible via
    Spline.Nodes[index].Location
    and
    Spline.nodes[index].Direction
    . Let's hope Sprite Shape has something similar.

    Note that SplineMesh only support mirrored directions on each node.
     
  28. BStheOne

    BStheOne

    Joined:
    Mar 10, 2015
    Posts:
    6
    Thanks man, I will try it.
    I just did something based on the Unite 2015 talk, and it's quiet working.