Unity Native Spline

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Linq;
using UnityEngine.Serialization;

[AddComponentMenu("Miscellaneous/Bezier Spline")]
public class Bezier3DSpline : MonoBehaviour{

	public int KnotCount { get { return curves.Length+(closed?0:1); } }
	public int CurveCount { get { return curves.Length; } }
    /// <summary> Interpolation steps per curve </summary>
    public int cacheDensity { get { return _cacheDensity; } }
    [SerializeField] protected int _cacheDensity = 60;
    /// <summary> Whether the end of the spline connects to the start of the spline </summary>
    public bool closed { get { return _closed; } }
    [SerializeField] protected bool _closed = false;
    /// <summary> Sum of all curve lengths </summary>
    public float totalLength { get { return _totalLength; } }
    [SerializeField] protected float _totalLength = 2.370671f;
    /// <summary> Curves of the spline </summary>
    [SerializeField] protected Bezier3DCurve[] curves = new Bezier3DCurve[] { new Bezier3DCurve( new Vector3(-1,0,0), new Vector3(1,0,1), new Vector3(-1,0,-1), new Vector3(1,0,0), 60)};
    /// <summary> Automatic knots don't have handles. Instead they have a percentage and adjust their handles accordingly. A percentage of 0 indicates that this is not automatic </summary>
    [SerializeField] protected List<float> autoKnot = new List<float>() { 0, 0 };
    [SerializeField] protected List<NullableQuaternion> orientations = new List<NullableQuaternion>() { new NullableQuaternion(null), new NullableQuaternion(null) };
    [SerializeField] protected Vector3[] tangentCache = new Vector3[0];

    #region Public methods

    #region Public: get

    public float DistanceToTime(float dist) {
        float t = 0f;
        for (int i = 0; i < CurveCount; i++) {
            if (curves[i].length < dist) {
                dist -= curves[i].length;
                t += 1f / CurveCount;
            }
            else {
                t += curves[i].Dist2Time(dist) / CurveCount;
                return t;
            }
        }
        return 1f;
    }

    /// <summary> Get <see cref="Bezier3DCurve"/> by index </summary>
    public Bezier3DCurve GetCurve(int i) {
        if (i >= CurveCount || i < 0) throw new System.IndexOutOfRangeException("Cuve index " + i + " out of range");
        return curves[i];
    }

    /// <summary> Return <see cref="Knot"/> info in local coordinates </summary>
    public Knot GetKnot(int i) {
        if (i == 0) {
            if (closed) return new Knot(curves[0].a, curves[CurveCount - 1].c, curves[0].b, autoKnot[i], orientations[i].NullableValue);
            else return new Knot(curves[0].a, Vector3.zero, curves[0].b, autoKnot[i], orientations[i].NullableValue);
        }
        else if (i == CurveCount) {
            return new Knot(curves[i - 1].d, curves[i - 1].c, Vector3.zero, autoKnot[i], orientations[i].NullableValue);
        }
        else {
            return new Knot(curves[i].a, curves[i - 1].c, curves[i].b, autoKnot[i], orientations[i].NullableValue);
        }
    }

    #region Public get: Forward
    /// <summary> Return forward vector at set distance along the <see cref="Bezier3DSpline"/>. </summary>
    public Vector3 GetForward(float dist) {
        return transform.TransformDirection(GetForwardLocal(dist));
    }

    /// <summary> Return forward vector at set distance along the <see cref="Bezier3DSpline"/> in local coordinates. </summary>
    public Vector3 GetForwardLocal(float dist) {
        Bezier3DCurve curve = GetCurveDistance(dist, out dist);
        return curve.GetForward(curve.Dist2Time(dist));
    }

    /// <summary> Return forward vector at set distance along the <see cref="Bezier3DSpline"/>. Uses approximation. </summary>
    public Vector3 GetForwardFast(float dist) {
        return transform.TransformDirection(GetForwardLocalFast(dist));
    }

    /// <summary> Return forward vector at set distance along the <see cref="Bezier3DSpline"/> in local coordinates. Uses approximation. </summary>
    public Vector3 GetForwardLocalFast(float dist) {
        Bezier3DCurve curve = GetCurveDistance(dist, out dist);
        return curve.GetForwardFast(curve.Dist2Time(dist));
    }
    #endregion

    #region Public get: Up
    /// <summary> Return up vector at set distance along the <see cref="Bezier3DSpline"/>. </summary>
    public Vector3 GetUp(float dist) {
        return GetUp(dist, GetForward(dist), false);
    }

    /// <summary> Return up vector at set distance along the <see cref="Bezier3DSpline"/> in local coordinates. </summary>
    public Vector3 GetUpLocal(float dist) {
        return GetUp(dist, GetForward(dist), true);
    }
    #endregion

    #region Public get: Point
    /// <summary> Return up vector at set distance along the <see cref="Bezier3DSpline"/>. </summary>
    public Vector3 GetPoint(float dist) {
        Bezier3DCurve curve = GetCurveDistance(dist, out dist);
        return transform.TransformPoint(curve.GetPoint(curve.Dist2Time(dist)));
    }

    /// <summary> Return point at lerped position where 0 = start, 1 = end </summary>
    public Vector3 GetPointLocal(float dist) {
        Bezier3DCurve curve = GetCurveDistance(dist, out dist);
        return curve.GetPoint(curve.Dist2Time(dist));
    }
    #endregion

    #region Public get: Orientation
    public Quaternion GetOrientation(float dist) {
        Vector3 forward = GetForward(dist);
        Vector3 up = GetUp(dist, forward, false);
        if (forward.sqrMagnitude != 0) return Quaternion.LookRotation(forward, up);
        else return Quaternion.identity;
    }

    public Quaternion GetOrientationFast(float dist) {
        Vector3 forward = GetForwardFast(dist);
        Vector3 up = GetUp(dist, forward, false);
        if (forward.sqrMagnitude != 0) return Quaternion.LookRotation(forward, up);
        else return Quaternion.identity;
    }

    public Quaternion GetOrientationLocal(float dist) {
        Vector3 forward = GetForwardLocal(dist);
        Vector3 up = GetUp(dist, forward, true);
        if (forward.sqrMagnitude != 0) return Quaternion.LookRotation(forward, up);
        else return Quaternion.identity;
    }

    public Quaternion GetOrientationLocalFast(float dist) {
        Vector3 forward = GetForwardLocalFast(dist);
        Vector3 up = GetUp(dist, forward, true);
        if (forward.sqrMagnitude != 0) return Quaternion.LookRotation(forward, up);
        else return Quaternion.identity;
    }
    #endregion

    #endregion

    #region Public: Set
    /// <summary> Setting spline to closed will generate an extra curve, connecting end point to start point </summary>
    public void SetClosed(bool closed) {
        if (closed != _closed) {
            _closed = closed;
            if (closed) {
                List<Bezier3DCurve> curveList = new List<Bezier3DCurve>(curves);
                curveList.Add(new Bezier3DCurve(curves[CurveCount - 1].d, -curves[CurveCount - 1].c, -curves[0].b, curves[0].a, cacheDensity));
                curves = curveList.ToArray();
            }
            else {
                List<Bezier3DCurve> curveList = new List<Bezier3DCurve>(curves);
                curveList.RemoveAt(CurveCount - 1);
                curves = curveList.ToArray();
            }
            _totalLength = GetTotalLength();
        }
    }

    /// <summary> Recache all individual curves with new step amount </summary> 
    /// <param name="density"> Number of steps per curve </param>
    public void SetCacheDensity(int steps) {
        _cacheDensity = steps;
        for (int i = 0; i < CurveCount; i++) {
            curves[i] = new Bezier3DCurve(curves[i].a, curves[i].b, curves[i].c, curves[i].d, _cacheDensity);
        }
        _totalLength = GetTotalLength();
    }

    public void RemoveKnot(int i) {
        if (i == 0) {
            Knot knot = GetKnot(1);

            List<Bezier3DCurve> curveList = new List<Bezier3DCurve>(curves);
            curveList.RemoveAt(0);
            curves = curveList.ToArray();

            autoKnot.RemoveAt(0);
            orientations.RemoveAt(0);

            SetKnot(0, knot);
        }
        else if (i == CurveCount) {

            List<Bezier3DCurve> curveList = new List<Bezier3DCurve>(curves);
            curveList.RemoveAt(i - 1);
            curves = curveList.ToArray();

            autoKnot.RemoveAt(i);
            orientations.RemoveAt(i);

            if (autoKnot[KnotCount - 1] != 0) SetKnot(KnotCount - 1, GetKnot(KnotCount - 1));
        }
        else {
            int preCurveIndex, postCurveIndex;
            GetCurveIndicesForKnot(i, out preCurveIndex, out postCurveIndex);

            Bezier3DCurve curve = new Bezier3DCurve(curves[preCurveIndex].a, curves[preCurveIndex].b, curves[postCurveIndex].c, curves[postCurveIndex].d, cacheDensity);

            curves[preCurveIndex] = curve;

            List<Bezier3DCurve> curveList = new List<Bezier3DCurve>(curves);
            curveList.RemoveAt(postCurveIndex);
            curves = curveList.ToArray();

            autoKnot.RemoveAt(i);
            orientations.RemoveAt(i);

            int preKnotIndex, postKnotIndex;
            GetKnotIndicesForKnot(i, out preKnotIndex, out postKnotIndex);

            SetKnot(preKnotIndex, GetKnot(preKnotIndex));
        }
    }

    public void AddKnot(Knot knot) {
        Bezier3DCurve curve = new Bezier3DCurve(curves[CurveCount - 1].d, -curves[CurveCount - 1].c, knot.handleIn, knot.position, cacheDensity);

        List<Bezier3DCurve> curveList = new List<Bezier3DCurve>(curves);
        curveList.Add(curve);
        curves = curveList.ToArray();

        autoKnot.Add(knot.auto);
        orientations.Add(knot.orientation);
        SetKnot(KnotCount - 1, knot);
    }

    public void InsertKnot(int i, Knot knot) {
        Bezier3DCurve curve;
        if (i == 0) curve = new Bezier3DCurve(knot.position, knot.handleOut, -curves[0].b, curves[0].a, cacheDensity);
        else if (i == CurveCount) curve = GetCurve(i - 1);
        else curve = GetCurve(i);

        List<Bezier3DCurve> curveList = new List<Bezier3DCurve>(curves);
        curveList.Insert(i, curve);
        curves = curveList.ToArray();

        autoKnot.Insert(i, knot.auto);
        orientations.Insert(i, knot.orientation);
        SetKnot(i, knot);
    }

    /// <summary> Set Knot info in local coordinates </summary>
    public void SetKnot(int i, Knot knot) {
        //If knot is set to auto, adjust handles accordingly
        orientations[i] = knot.orientation;
        autoKnot[i] = knot.auto;
        if (knot.auto != 0) AutomateHandles(i, ref knot);

        //Automate knots around this knot
        int preKnotIndex, postKnotIndex;
        GetKnotIndicesForKnot(i, out preKnotIndex, out postKnotIndex);

        Knot preKnot = new Knot();
        if (preKnotIndex != -1) {
            preKnot = GetKnot(preKnotIndex);
            if (preKnot.auto != 0) {
                int preKnotPreCurveIndex, preKnotPostCurveIndex;
                GetCurveIndicesForKnot(preKnotIndex, out preKnotPreCurveIndex, out preKnotPostCurveIndex);
                if (preKnotPreCurveIndex != -1) {
                    AutomateHandles(preKnotIndex, ref preKnot, curves[preKnotPreCurveIndex].a, knot.position);
                    curves[preKnotPreCurveIndex] = new Bezier3DCurve(curves[preKnotPreCurveIndex].a, curves[preKnotPreCurveIndex].b, preKnot.handleIn, preKnot.position, cacheDensity);
                }
                else {
                    AutomateHandles(preKnotIndex, ref preKnot, Vector3.zero, knot.position);
                }
            }
        }

        Knot postKnot = new Knot();
        if (postKnotIndex != -1) {
            postKnot = GetKnot(postKnotIndex);
            if (postKnot.auto != 0) {
                int postKnotPreCurveIndex, postKnotPostCurveIndex;
                GetCurveIndicesForKnot(postKnotIndex, out postKnotPreCurveIndex, out postKnotPostCurveIndex);
                if (postKnotPostCurveIndex != -1) {
                    AutomateHandles(postKnotIndex, ref postKnot, knot.position, curves[postKnotPostCurveIndex].d);
                    curves[postKnotPostCurveIndex] = new Bezier3DCurve(postKnot.position, postKnot.handleOut, curves[postKnotPostCurveIndex].c, curves[postKnotPostCurveIndex].d, cacheDensity);
                }
                else {
                    AutomateHandles(postKnotIndex, ref postKnot, knot.position, Vector3.zero);
                }
            }
        }

        //Get the curve indices in direct contact with knot
        int preCurveIndex, postCurveIndex;
        GetCurveIndicesForKnot(i, out preCurveIndex, out postCurveIndex);

        //Adjust curves in direct contact with the knot
        if (preCurveIndex != -1) curves[preCurveIndex] = new Bezier3DCurve(preKnot.position, preKnot.handleOut, knot.handleIn, knot.position, cacheDensity);
        if (postCurveIndex != -1) curves[postCurveIndex] = new Bezier3DCurve(knot.position, knot.handleOut, postKnot.handleIn, postKnot.position, cacheDensity);

        _totalLength = GetTotalLength();

    }

    /// <summary> Flip the spline </summary>
    public void Flip() {
        Bezier3DCurve[] curves = new Bezier3DCurve[CurveCount];
        for (int i = 0; i < CurveCount; i++) {
            curves[CurveCount - 1 - i] = new Bezier3DCurve(this.curves[i].d, this.curves[i].c, this.curves[i].b, this.curves[i].a, cacheDensity);
        }
        this.curves = curves;
        autoKnot.Reverse();
        orientations.Reverse();
    }
    #endregion

    #endregion

    public struct Knot {
        public Vector3 position;
        public Vector3 handleIn;
        public Vector3 handleOut;
        public float auto;
        public Quaternion? orientation;

        /// <summary> Constructor </summary>
        /// <param name="position">Position of the knot local to spline transform</param>
        /// <param name="handleIn">Left handle position local to knot position</param>
        /// <param name="handleOut">Right handle position local to knot position</param>
        /// <param name="automatic">Any value above 0 will result in an automatically configured knot (ignoring handle inputs)</param>
        public Knot(Vector3 position, Vector3 handleIn, Vector3 handleOut, float automatic = 0f, Quaternion? orientation = null) {
            this.position = position;
            this.handleIn = handleIn;
            this.handleOut = handleOut;
            this.auto = automatic;
            this.orientation = orientation;
        }
    }

    #region Private methods
    private Vector3 GetUp(float dist, Vector3 tangent, bool local) {
        float t = DistanceToTime(dist);
        t *= CurveCount;

        Quaternion rot_a = Quaternion.identity, rot_b = Quaternion.identity;
        int t_a = 0, t_b = 0;

        //Find preceding rotation
        for (int i = Mathf.Min((int)t, CurveCount); i >= 0; i--) {
            i = (int)Mathf.Repeat(i, KnotCount - 1);
            if (orientations[i].HasValue) {
                rot_a = orientations[i].Value;
                rot_b = orientations[i].Value;
                t_a = i;
                t_b = i;
                break;
            }
        }
        //Find proceding rotation
        for (int i = Mathf.Max((int)t + 1, 0); i < orientations.Count; i++) {
            if (orientations[i].HasValue) {
                rot_b = orientations[i].Value;
                t_b = i;
                break;
            }
        }
        t = Mathf.InverseLerp(t_a, t_b, t);
        Quaternion rot = Quaternion.Lerp(rot_a, rot_b, t);
        if (!local) rot = transform.rotation * rot;
        //Debug.Log(t_a + " / " + t_b + " / " + t);
        return Vector3.ProjectOnPlane(rot * Vector3.up, tangent).normalized;
    }

    /// <summary> Get the curve indices in direct contact with knot </summary>
    private void GetCurveIndicesForKnot(int knotIndex, out int preCurveIndex, out int postCurveIndex) {
        //Get the curve index in direct contact with, before the knot
        preCurveIndex = -1;
        if (knotIndex != 0) preCurveIndex = knotIndex - 1;
        else if (closed) preCurveIndex = CurveCount - 1;

        //Get the curve index in direct contact with, after the knot
        postCurveIndex = -1;
        if (knotIndex != CurveCount) postCurveIndex = knotIndex;
        else if (closed) postCurveIndex = 0;
    }

    /// <summary> Get the knot indices in direct contact with knot </summary>
    private void GetKnotIndicesForKnot(int knotIndex, out int preKnotIndex, out int postKnotIndex) {
        //Get the curve index in direct contact with, before the knot
        preKnotIndex = -1;
        if (knotIndex != 0) preKnotIndex = knotIndex - 1;
        else if (closed) preKnotIndex = KnotCount - 1;

        //Get the curve index in direct contact with, after the knot
        postKnotIndex = -1;
        if (knotIndex != KnotCount - 1) postKnotIndex = knotIndex + 1;
        else if (closed) postKnotIndex = 0;
    }

    private Bezier3DCurve GetCurve(float splineT, out float curveT) {
        splineT *= CurveCount;
        for (int i = 0; i < CurveCount; i++) {
            if (splineT > 1f) splineT -= 1f;
            else {
                curveT = splineT;
                return curves[i];
            }
        }
        curveT = 1f;
        return curves[CurveCount - 1];
    }

    private Bezier3DCurve GetCurveDistance(float splineDist, out float curveDist) {
        for (int i = 0; i < CurveCount; i++) {
            if (curves[i].length < splineDist) splineDist -= curves[i].length;
            else {
                curveDist = splineDist;
                return curves[i];
            }
        }
        curveDist = curves[CurveCount -1].length;
        return curves[CurveCount - 1];
    }

    /// <summary> Automate handles based on previous and next point positions </summary>
    private void AutomateHandles(int i, ref Knot knot) {
        //Terminology: Points are referred to as A B and C
        //A = prev point, B = current point, C = next point

        Vector3 prevPos;
        if (i != 0) prevPos = curves[i - 1].a;
        else if (closed) prevPos = curves[CurveCount - 1].a;
        else prevPos = Vector3.zero;

        Vector3 nextPos;
        if (i != KnotCount - 1) nextPos = curves[i].d;
        else if (closed) nextPos = curves[0].a;
        else nextPos = Vector3.zero;

        AutomateHandles(i, ref knot, prevPos, nextPos);
    }

    /// <summary> Automate handles based on previous and next point positions </summary>
    private void AutomateHandles(int i, ref Knot knot, Vector3 prevPos, Vector3 nextPos) {
        //Terminology: Points are referred to as A B and C
        //A = prev point, B = current point, C = next point
        float amount = knot.auto;

        //Calculate directional vectors
        Vector3 AB = knot.position - prevPos;
        Vector3 CB = knot.position - nextPos;
        //Calculate the across vector
        Vector3 AB_CB = (CB.normalized - AB.normalized).normalized;

        if (!closed) {
            if (i == 0) {
                knot.handleOut = CB * -amount;
            }
            else if (i == CurveCount) {
                knot.handleIn = AB * -amount;
            }
            else {
                knot.handleOut = -AB_CB * CB.magnitude * amount;
                knot.handleIn = AB_CB * AB.magnitude * amount;
            }
        }
        else {
            if (KnotCount == 2) {
                Vector3 left = new Vector3(AB.z, 0,-AB.x) * amount;
                if (i == 0) {
                    knot.handleIn = left;
                    knot.handleOut = -left;
                }
                if (i == 1) {
                    knot.handleIn = left;
                    knot.handleOut = -left;
                }
            }
            else {
            knot.handleIn = AB_CB * AB.magnitude * amount;
            knot.handleOut = -AB_CB * CB.magnitude * amount;
            }
        }
    }

    private float GetTotalLength() {
        float length = 0f;
        for (int i = 0; i < CurveCount; i++) {
            length += curves[i].length;
        }
        return length;
    }
    #endregion

    /// <summary> Unity doesn't support serialization of nullable types, so here's a custom struct that does exactly the same thing </summary>
    [Serializable]
    protected struct NullableQuaternion {
        public Quaternion Value { get { return rotation; } }
        public Quaternion? NullableValue { get { if (hasValue) return rotation; else return null; } }
        public bool HasValue { get { return hasValue; } }

        [SerializeField] private Quaternion rotation;
        [SerializeField] private bool hasValue;

        public NullableQuaternion(Quaternion? rot) {
            rotation = rot.HasValue?rot.Value:Quaternion.identity;
            hasValue = rot.HasValue;
        }

        //  User-defined conversion from nullable type to NullableQuaternion
        public static implicit operator NullableQuaternion(Quaternion? r) {
            return new NullableQuaternion(r);
        }
    }
#if UNITY_EDITOR
    void OnDrawGizmos() {
        //Set color depending on selection
        if (Array.IndexOf(UnityEditor.Selection.gameObjects, gameObject) >= 0) {
            Gizmos.color = Color.yellow;
        } else  Gizmos.color = new Color(1, 0.6f, 0f);

        //Loop through each curve in spline
        for (int i = 0; i < CurveCount; i++) {
            Bezier3DCurve curve = GetCurve(i);

            //Get curve in world space
            Vector3 a, b, c, d;
            a = transform.TransformPoint(curve.a);
            b = transform.TransformPoint(curve.b + curve.a);
            c = transform.TransformPoint(curve.c + curve.d);
            d = transform.TransformPoint(curve.d);

            int segments = 50;
            float spacing = 1f / segments;
            Vector3 prev = Bezier3DCurve.GetPoint(a, b, c, d, 0f);
            for (int k = 0; k <= segments; k++) {
                Vector3 cur = Bezier3DCurve.GetPoint(a, b, c, d, k * spacing);
                Gizmos.DrawLine(prev, cur);
                prev = cur;
            }
        }
    }
#endif
}

 

Wave Fields WR







 







 




https://youtu.be/ziarrVCK0G4




https://youtu.be/oTNI8hpWUrw




 

 

 

 

 

CODE REFERENCE

Unity Native Spline

 

 

 

SPHERE EXPANDER – FROM Fundamental Flower

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ProceduralToolkit;
using UnityEngine.UI;
using System;
using ArgosTweenSpacePuppy;
using VRTK;
using System.IO;
using UnityEngine.EventSystems;
using System.Linq;

public class Sphere_Expander : MonoBehaviour
{
    public bool bSeparate_Faces = false;
    public bool bDrawTetrahedron = false;

    public int totalVerts = 0;
    public int filteredVerts = 0;

    public int vSort_Count_0;
    public int vSort_Count_1;
    public int vSort_Count_2;

    private ArgosMeshDraft   aMD_TetraTerrean;
    private ArgosMeshDraft   aMD_TT_Sleeve;
    private ArgosMeshDraft[] aMD_tt_Faces = new ArgosMeshDraft[4];
    private GameObject[]     tt_Faces = new GameObject[4];
    private ArgosMeshDraft   aMD_Spheres;
    private ArgosMeshDraft   cylinder_MD;
    private ArgosMeshDraft   scythe_MD;
    private GameObject       scythe_GO;
    public  Color            col_edge_Cylinder;
    public  Color            col_Sphere;
    private GameObject[]     spheres = new GameObject[4];
    private Vector3[]        sphere_pos = new Vector3[4];
    public  GameObject       meshPF;
    public  GameObject       tetra_Vert_GOPF;
    private GameObject[]     tetra_Verts_GO = new GameObject[4];
    private GameObject[]     tetra_Edges    = new GameObject[6];
    private GameObject       TetraTerrien_GO;
    private GameObject       TT_Sleeve_GO;

    public float[] lengths = new float[256];
    public float[] hyperbola_of_R = new float[256];

    public int all_Shape_Depth = 4;

    Vector3[] vC = new Vector3[4];

    public class Vector_Sort
    {
        public Vector3 vert;
        public float dist = 0;
        public int vIDX = 0;
    }

    internal class PlaneHelper
    {
        /// <summary>
        /// Returns a value indicating what side (positive/negative) of a plane a point is
        /// </summary>
        /// <param name="point">The point to check with</param>
        /// <param name="plane">The plane to check against</param>
        /// <returns>Greater than zero if on the positive side, less than zero if on the negative size, 0 otherwise</returns>
        public static float ClassifyPoint(ref Vector3 point, ref Plane plane)
        {
            return point.x * plane.Normal.x + point.y * plane.Normal.y + point.z * plane.Normal.z + plane.D;
        }

        /// <summary>
        /// Returns the perpendicular distance from a point to a plane
        /// </summary>
        /// <param name="point">The point to check</param>
        /// <param name="plane">The place to check</param>
        /// <returns>The perpendicular distance from the point to the plane</returns>
        public static float PerpendicularDistance(ref Vector3 point, ref Plane plane)
        {
            // dist = (ax + by + cz + d) / sqrt(a*a + b*b + c*c)
            return Mathf.Abs((plane.Normal.x * point.x + plane.Normal.y * point.y + plane.Normal.z * point.z)
                                    / Mathf.Sqrt(plane.Normal.x * plane.Normal.x + plane.Normal.y * plane.Normal.y + plane.Normal.z * plane.Normal.z));
        }
    }

    public struct Plane 
    {
        #region Public Fields

        public float D;
        public Vector3 Normal;

        #endregion Public Fields

        #region Constructors

        public Plane(Vector4 value)
            : this(new Vector3(value.x, value.y, value.z), value.w)
        {

        }

        public Plane(Vector3 normal, float d)
        {
            Normal = normal;
            D = d;
        }

        public Plane(Vector3 a, Vector3 b, Vector3 c)
        {
            Vector3 ab = b - a;
            Vector3 ac = c - a;

            Vector3 cross = Vector3.Cross(ab, ac);
            Normal = Vector3.Normalize(cross);
            D = -(Vector3.Dot(Normal, a));
        }

        public Plane(float a, float b, float c, float d)
            : this(new Vector3(a, b, c), d)
        {

        }

        #endregion Constructors

        public override string ToString()
        {
            return string.Format("{{Normal:{0} D:{1}}}", Normal, D);
        }
    }

    public enum TT_TYPE
    {
        SPHERICAL,
        HYPERBOLIC,
        OTHER,
        NONE,
    }
    public TT_TYPE ttType = TT_TYPE.SPHERICAL;

    public bool bSpheresON = true;
    public bool bTetraTerrean_On = false;

    [Range(0, 50f)]
    public float tetra_Radius;
    private float tetra_Radius_Last = 0;

    [Range(0, 100f)]
    public float scale_TT= 100f;
 
    [Range(0, 500f)]
    public float sphere_Radius;
    private float sphere_Radius_Last = 0;

    [Range(0, 0.5f)]
    public float cylinder_Radius;

    private Vector3[] tetra_Verts  = new Vector3[4];
    private Vector3[] face_Centers = new Vector3[4];

    [Space(12)]
    [Header("Hyperbolic Settings")]
    [Space(12)]

    [Range(0, 1f)]
    public float base_Distance;

    [Range(0, 1f)]
    public float tangent_Base;

    [Range(0, 1f)]
    public float tangent_H;

    [Range(0, 1f)]
    public float smidge;

    public GameObject H_Editor_Label;
    public GameObject C_Editor_Label;
    public GameObject B_Editor_Label;
    public GameObject I_Editor_Label;
    public GameObject T1_Editor_Label;
    public GameObject T2_Editor_Label;
    private int frameCount = 1;
    private Vector3 vCHn, vIHn, vT1n, vT2n;
    private Vector3 vIH;

    StreamWriter sWrite;
    public bool bPrint_Trace = false;

    private Vector3[] beeS = new Vector3[4];

    StreamWriter sLineVectors;

    private ArgosMeshDraft aMD_Nurbs_Mesh;
    GameObject pQuad_CARBON_LINE_go;

    private ArgosMeshDraft aMD_PQUAD_TMP;

    public enum RESOLUTION
    {
        RES_LOW,
        RES_MED,
        RES_HIGH,
    }

    public RESOLUTION rESOLUTION = RESOLUTION.RES_LOW;
    private RESOLUTION resolution_Last = RESOLUTION.RES_LOW;

    private int nRESOLUTION = 7;

    private List<Vector3> lst_mesh_v = new List<Vector3>();
    public float[] spline_lengths = new float[256];

    [Range(0, 10f)]
    public float short_Tan_mag = 1;

    [Range(0, 10f)]
    public float long_Tan_mag = 1;

    void Start()
    {
        sLineVectors = new StreamWriter("sLineVectors.txt");

        aMD_TetraTerrean = new ArgosMeshDraft();
        aMD_Spheres = new ArgosMeshDraft();
        aMD_Spheres.Add(MeshDraft.Sphere(1,16,16));
        cylinder_MD = new ArgosMeshDraft();
        aMD_TT_Sleeve = new ArgosMeshDraft();

        scythe_MD = new ArgosMeshDraft();
        scythe_GO = Instantiate(meshPF, transform);
        scythe_GO.name = "SCYTHE_OUTLINE";

        aMD_Nurbs_Mesh = new ArgosMeshDraft();
        pQuad_CARBON_LINE_go = Instantiate(meshPF, transform);
        pQuad_CARBON_LINE_go.name = "pQuad_CARBON_LINE_go";

        pQuad_CARBON_LINE_go.GetComponent<MeshRenderer>().enabled = false;//so we can see the TT

        sWrite = new StreamWriter("Hyperbolic_Params.txt");

        for (int i = 0; i<4; i++)
        {
            spheres[i] = Instantiate(meshPF, transform);
            spheres[i].name = "sphere_" + i.ToString();
            SetColor_Element(col_Sphere, spheres[i]);
            spheres[i].GetComponent<MeshFilter>().mesh = aMD_Spheres.ToMeshInternal();

            tetra_Verts_GO[i] = Instantiate(tetra_Vert_GOPF, transform);
            tetra_Verts_GO[i].name = "TetVERT_" + i.ToString();

            tt_Faces[i] = Instantiate(meshPF, transform);
            tt_Faces[i].name = "TT_Face_" + i.ToString();
            aMD_tt_Faces[i] = new ArgosMeshDraft();
        }
        for(int i = 0; i<6; i++)
        {
            tetra_Edges[i] = Instantiate(meshPF, transform);
            tetra_Edges[i].name = "edge_" + i.ToString();
            SetColor_Element(col_edge_Cylinder, tetra_Edges[i]);
            tetra_Edges[i].GetComponent<MeshFilter>().mesh = cylinder_MD.ToMeshInternal();
        }
        Set_Tetra_Verts(tetra_Radius);
        TetraTerrien_GO = Instantiate(meshPF, transform);
        TetraTerrien_GO.name = "TetraTerrien_GO";
        aMD_TetraTerrean.Clear();
        //Create_TETRA_TERREAN(all_Shape_Depth);
        //TetraTerrien_GO.GetComponent<MeshFilter>().mesh = aMD_TetraTerrean.ToMeshInternal();

        TT_Sleeve_GO = Instantiate(meshPF, transform);
        TT_Sleeve_GO.name = "TT_Sleeve_GO";
        aMD_TT_Sleeve.Clear();

        aMD_PQUAD_TMP = new ArgosMeshDraft();
    }

    /// <image url="$(SolutionDir)\EMB\Quad_Nurb.png" scale="0.15" /> 


    public void PQuad_SetUp_VertsAndTans()
    {
        Vector3 v0;
        Vector3 v1;
        Vector3 v2;
        Vector3 v3;
        Vector3 t01;
        Vector3 t10;
        Vector3 t13;
        Vector3 t31;
        Vector3 t23;
        Vector3 t32;
        Vector3 t20;
        Vector3 t02;

        v0 = tetra_Verts[2];
        v1 = v0;
        v1.y = -v1.y;

        v2 = tetra_Verts[3];
        v3 = v2;
        v3.y = -v3.y;

        //short_Tan_mag;
        //long_Tan_mag;

        Vector3[] vTansNorm = new Vector3[4];

        vTansNorm[0] = -v0.normalized;
        vTansNorm[1] = -v1.normalized;
        vTansNorm[2] = -v2.normalized;
        vTansNorm[3] = -v3.normalized;

        t01 = v0 + vTansNorm[0] * short_Tan_mag;
        t02 = v0 + vTansNorm[0] * long_Tan_mag;

        t10 = v1 + vTansNorm[1] * short_Tan_mag;
        t13 = v1 + vTansNorm[1] * long_Tan_mag;

        t23 = v2 + vTansNorm[2] * short_Tan_mag;
        t20 = v2 + vTansNorm[2] * long_Tan_mag;

        t32 = v3 + vTansNorm[3] * short_Tan_mag;
        t31 = v3 + vTansNorm[3] * long_Tan_mag;

        P_Quad_Generate(v0, v1, v2, v3,
                       t01, t10, t13, t31,
                       t23, t32, t20, t02);
    }


    public void P_Quad_Generate(Vector3 v0, Vector3 v1, Vector3 v2, Vector3 v3,
                       Vector3 t01, Vector3 t10, Vector3 t13, Vector3 t31,
                       Vector3 t23, Vector3 t32, Vector3 t20, Vector3 t02)
    {
        Set_Resolution();

        lst_mesh_v.Clear();
        aMD_Nurbs_Mesh.Clear();

        float u = 0;
        float v = 0;
        float du = 1 / (float)nRESOLUTION;
        float dv = 1 / (float)nRESOLUTION;

        Vector3 t0, t1;
        Vector3 p0, p1;
        Vector3 p;

        for (int i = 0; i < nRESOLUTION + 1; i++)
        {
            t0 = GetPointOnBezierCurve(t02, t01, t10, t13, get_Adjusted(u, ref spline_lengths));
            p0 = GetPointOnBezierCurve(v0, t01, t10, v1, get_Adjusted(u, ref spline_lengths));
            t1 = GetPointOnBezierCurve(t20, t23, t32, t31, get_Adjusted(u, ref spline_lengths));
            p1 = GetPointOnBezierCurve(v2, t23, t32, v3, get_Adjusted(u, ref spline_lengths));

            Compute_Dist(ref spline_lengths, v0, t02, t20, v2);

            v = 0f;
            for (int j = 0; j < nRESOLUTION + 1; j++)
            {
                p = GetPointOnBezierCurve(p0, t0, t1, p1, get_Adjusted(v, ref spline_lengths));
                lst_mesh_v.Add(p);
                v += dv;
            }
            u += du;
        }

        int stride = nRESOLUTION + 1;
        int k = 0;
        for (int i = 0; i < nRESOLUTION; i++)
        {
            for (int j = 0; j < nRESOLUTION; j++)
            {
                aMD_Nurbs_Mesh.Add(MeshDraft.Quad(lst_mesh_v[k], lst_mesh_v[k + 1], lst_mesh_v[k + stride + 1], lst_mesh_v[k + stride]));
                k++;
            }
            k++;
        }

        Quaternion q;
        float angle = 120;

        ArgosMeshDraft amdTmp = new ArgosMeshDraft();

        amdTmp.Copy_MeshDraft(aMD_Nurbs_Mesh);

        amdTmp.Rotate(Quaternion.AngleAxis(angle, Vector3.up));

        aMD_Nurbs_Mesh.Add(amdTmp);

        amdTmp.Rotate(Quaternion.AngleAxis(angle, Vector3.up));

        aMD_Nurbs_Mesh.Add(amdTmp);

        aMD_PQUAD_TMP.Clear();

        subdivide_PQUAD_TRIANGLE(tetra_Verts[2], tetra_Verts[3], tetra_Verts[1], all_Shape_Depth, spheres[2].transform.localPosition, 3, true);

        aMD_Nurbs_Mesh.Add(aMD_PQUAD_TMP);

        aMD_PQUAD_TMP.Rotate(Quaternion.AngleAxis(180, Vector3.forward));

        aMD_Nurbs_Mesh.Add(aMD_PQUAD_TMP);

        pQuad_CARBON_LINE_go.GetComponent<MeshFilter>().mesh = aMD_Nurbs_Mesh.ToMeshInternal();
        pQuad_CARBON_LINE_go.GetComponent<MeshFilter>().mesh.RecalculateNormals(60);

    }

    void subdivide_PQUAD_TRIANGLE(Vector3 v1, Vector3 v2, Vector3 v3, int depth, Vector3 sector_Foc, int nID, bool bFlipNorm)
    {
        Vector3 v12, v23, v31;
        Vector3 v12_n, v23_n, v31_n;

        if (depth == 0)
        {
            if (bFlipNorm)
            {
                addTriangle_UV_Tag_PQUAD(v1, v3, v2, (float)nID);
            }
            else
            {
                //if (nID == 1)
                //{
                //    vTest[i] = v1;
                //    vTest2[i] = v2;
                //    i++;
                //}
                addTriangle_UV_Tag_PQUAD(v3, v2, v1, (float)nID);
            }
            return;
        }

        v12 = (v1 + v2) / 2.0f;
        v23 = (v2 + v3) / 2.0f;
        v31 = (v3 + v1) / 2.0f;

        v12_n = Sphere_Surf(v12, sector_Foc); //sector_Foc
        v23_n = Sphere_Surf(v23, sector_Foc);
        v31_n = Sphere_Surf(v31, sector_Foc);

        //v12_n = v12; //sector_Foc
        //v23_n = v23;
        //v31_n = v31;

        /* recursively subdivide new triangles */
        subdivide_PQUAD_TRIANGLE(v1, v12_n, v31_n, depth - 1, sector_Foc, 1, bFlipNorm);
        subdivide_PQUAD_TRIANGLE(v12_n, v2, v23_n, depth - 1, sector_Foc, 2, bFlipNorm);
        subdivide_PQUAD_TRIANGLE(v31_n, v23_n, v3, depth - 1, sector_Foc, 3, bFlipNorm);
        subdivide_PQUAD_TRIANGLE(v23_n, v31_n, v12_n, depth - 1, sector_Foc, 4, bFlipNorm);
    }

    private void Create_Top_Bottom(int depth)
    {
        //sleeve(tetra_Verts[1], tetra_Verts[2], tetra_Verts[3], spheres[2].transform.localPosition);
        subdivide_CarbonLine(tetra_Verts[1], tetra_Verts[3], tetra_Verts[2], depth, spheres[2].transform.localPosition, 3, false);
        List<Vector_Sort>[] vsortList = new List<Vector_Sort>[3];

        vsortList[0] = new List<Vector_Sort>();
        vsortList[1] = new List<Vector_Sort>();
        vsortList[2] = new List<Vector_Sort>();

        int[] id = new int[] { 0, 1, 2, 0, 2, 3, 0, 3, 1 };
        Vector3 vert;

        for (int j = 0; j < 3; j++)
        {
            Plane p = new Plane(tetra_Verts[id[3 * j]], tetra_Verts[id[3 * j + 1]], tetra_Verts[id[3 * j + 2]]);
            p.D = p.D * 0.999f;

            Vector_Sort vs;

            filteredVerts = 0;

            for (int i = 0; i < aMD_TT_Sleeve.vertices.Count; i++)
            {
                vert = aMD_TT_Sleeve.vertices[i];
                float res = PlaneHelper.ClassifyPoint(ref vert, ref p);

                if (res > 0)
                {
                    filteredVerts++;
                    vs = new Vector_Sort();
                    vs.vert = vert;
                    vs.vIDX = i;
                    vsortList[j].Add(vs);
                }
            }
            RemoveDuplicates(vsortList[j]);
            for (int i = 0; i < vsortList[j].Count; i++)
            {
                vsortList[j][i].dist = (tetra_Verts[j + 1] - vsortList[j][i].vert).magnitude;
            }

            var ordered = from element in vsortList[j]
                          orderby element.dist
                          select element;

            vsortList[j] = ordered.ToList<Vector_Sort>();
        }

        for (int i = 0; i < aMD_TT_Sleeve.vertices.Count; i++)
        {
            aMD_TT_Sleeve.vertices[i] = Sphere_Surf(aMD_TT_Sleeve.vertices[i], spheres[2].transform.localPosition);
        }

        Plane p2 = new Plane(Vector3.up, 0);
        int vertCount = aMD_TT_Sleeve.vertices.Count;

        ArgosMeshDraft amdTemp = new ArgosMeshDraft();

        amdTemp.Add(aMD_TT_Sleeve);
        aMD_TT_Sleeve.FlipTriangles();

        aMD_TT_Sleeve.Add(amdTemp);
        aMD_TT_Sleeve.FlipNormals();
        float d;

        for (int i = 0; i < vertCount; i++)//FLIP
        {
            vert = aMD_TT_Sleeve.vertices[i];
            d = PlaneHelper.ClassifyPoint(ref vert, ref p2);
            aMD_TT_Sleeve.vertices[i] = vert - 2f * d * Vector3.up;
        }

        List<Vector3> vInnerLower = new List<Vector3>();
        List<Vector3> vInnerUpper = new List<Vector3>();

        List<Vector3> vPrint = new List<Vector3>();

        for (int j = 0; j < 3; j++)
        {
            for (int i = 0; i < vsortList[j].Count; i++)
            {
                vert = aMD_TT_Sleeve.vertices[vsortList[j][i].vIDX];
                //vCurr.y = 0;
                d = PlaneHelper.ClassifyPoint(ref vert, ref p2);

                if (j == 0)
                {
                    vPrint.Add(vert - 2f * d * Vector3.up);
                }

                vInnerLower.Add(vert);
                vInnerUpper.Add(vert - 2f * d * Vector3.up);
            }
        }
        aMD_TT_Sleeve.Add(MeshDraft.Band(vInnerUpper, vInnerLower));
        aMD_TT_Sleeve.Add(MeshDraft.Band(vInnerLower, vInnerUpper));

        Generate_Line_List(vPrint);

        vSort_Count_0 = vsortList[0].Count;
        vSort_Count_1 = vsortList[1].Count;
        vSort_Count_2 = vsortList[2].Count;
    }

    private float get_Adjusted(float t, ref float[] lengths)
    {
        int i = 0;
        while (i < 256 && lengths[i] < t)
        {
            i++;
        }
        return (float)i / 256;
    }

    private void Compute_Dist(ref float[] lengths, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
    {
        float t = 0.0f;
        float dt = 1f / 256;

        Vector3 vB = GetPointOnBezierCurve(p0, p1, p2, p3, t);
        Vector3 vB_Last = vB;
        float running_Len = 0;

        for (int i = 0; i < 256; i++)
        {
            vB = GetPointOnBezierCurve(p0, p1, p2, p3, t);
            running_Len += (vB - vB_Last).magnitude;
            lengths[i] = running_Len;
            vB_Last = vB;
            t += dt;
        }
        for (int i = 0; i < 256; i++)
        {
            lengths[i] /= running_Len;
        }
    }

    private void Set_Resolution()
    {
        if (rESOLUTION != resolution_Last)
        {
            if (rESOLUTION == RESOLUTION.RES_LOW)
            {
                nRESOLUTION = 7;
            }
            else if (rESOLUTION == RESOLUTION.RES_MED)
            {
                nRESOLUTION = 18;
            }
            else if (rESOLUTION == RESOLUTION.RES_HIGH)
            {
                nRESOLUTION = 36;
            }
        }
        resolution_Last = rESOLUTION;
    }


    //void OnDrawGizmos()
    //{
    //    Gizmos.DrawIcon(H_Editor_Label.transform.position, "H.png", true);
    //    Gizmos.DrawIcon(C_Editor_Label.transform.position, "C.png", true);
    //    Gizmos.DrawIcon(I_Editor_Label.transform.position, "I.png", true);
    //    Gizmos.DrawIcon(B_Editor_Label.transform.position, "B.png", true);
    //    Gizmos.DrawIcon(T1_Editor_Label.transform.position, "T1.png", true);
    //    Gizmos.DrawIcon(T2_Editor_Label.transform.position, "T2.png", true);
    //    Gizmos.color = Color.green;
    //    Gizmos.DrawLine(B_Editor_Label.transform.position, T1_Editor_Label.transform.position);
    //    Gizmos.DrawLine(H_Editor_Label.transform.position, T2_Editor_Label.transform.position);
    //}

    private void OnApplicationQuit()
    {
        sWrite.Close();
    }

    /// <image url="$(SolutionDir)\EMB\hyperbole2.png" scale="0.35"></image>

    private void build_Reference_Hyperbolic_Spline(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 vI)
    {
        //p4 = H
        float t = 0.0f;
        float dt = 1f / 256;

        Vector3 vB = GetPointOnBezierCurve(p0, p1, p2, p3, t);
        Vector3 vB_Last = vB;
        float running_Len = 0;

        float   vI_Len = vI.magnitude;
        Vector3 vIn    = vI.normalized;

        for (int i = 0; i < 256; i++)
        {
            vB = GetPointOnBezierCurve(p0, p1, p2, p3, t);
            running_Len += (vB - vB_Last).magnitude;
            lengths[i] = running_Len;
            hyperbola_of_R[i] = Vector3.Dot(vB, vIn);
            vB_Last = vB;
            t += dt;
        }
        for (int i = 0; i < 256; i++)
        {
            lengths[i] /= running_Len;
        }
    }

    public void Print_Trace(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 vI)
    {
        for (int i = 0; i < 256; i++)
        {
            sWrite.WriteLine(i.ToString() + "\t " + " -\t " + hyperbola_of_R[i].ToString("F2"));
        }
        sWrite.WriteLine("p0 Base " + "\t " + " -\t " + p0.ToString("F2"));
        sWrite.WriteLine("p1 Tangent_0 " + "\t " + " -\t " + p1.ToString("F2"));
        sWrite.WriteLine("p2 Tangent_1 " + "\t " + " -\t " + p2.ToString("F2"));
        sWrite.WriteLine("p3 H " + "\t " + " -\t " + p3.ToString("F2"));
        sWrite.WriteLine("vI " + "\t " + " -\t " + vI.ToString("F2"));
    }

    private void SetColor_Element(Color col, GameObject go)
    {
        float alpha = col.a;
        float alpha_out = alpha / 3f;

        Color cola = col;
        cola.a = alpha_out;

        go.GetComponent<MeshRenderer>().material.SetColor("_EmissionColor", cola);
        go.GetComponent<MeshRenderer>().material.SetColor("_Color", col);
    }

    public void Set_Tetra_Verts(float radius)
    {
        float tetrahedralAngle = Mathf.PI * -19.471220333f / 180;
        float segmentAngle = Mathf.PI * 2 / 3;
        float currentAngle = 0f;

        Vector3 v = new Vector3(0, radius, 0);
        tetra_Verts[0] = v;
        for (var i = 1; i < 4; i++)
        {
            tetra_Verts[i] = PTUtils.PointOnSphere(radius, currentAngle, tetrahedralAngle);
            currentAngle += segmentAngle;
        }

        face_Centers[0] = (tetra_Verts[0] + tetra_Verts[1] + tetra_Verts[2]) / 3f;
        face_Centers[1] = (tetra_Verts[0] + tetra_Verts[2] + tetra_Verts[3]) / 3f;
        face_Centers[2] = (tetra_Verts[1] + tetra_Verts[2] + tetra_Verts[3]) / 3f;
        face_Centers[3] = (tetra_Verts[0] + tetra_Verts[1] + tetra_Verts[3]) / 3f;

        float edge_length = (tetra_Verts[0] - tetra_Verts[1]).magnitude;
        edge_length = radius * 2f * Mathf.Sqrt(2f) / Mathf.Sqrt(3);

        cylinder_MD.Add(MeshDraft.Cylinder_Z(cylinder_Radius, 5, edge_length));

        //tetra_Edges[0].transform.localPosition = tetra_Verts[0];
        //tetra_Edges[0].transform.localRotation = Quaternion.LookRotation(tetra_Verts[1] - tetra_Verts[0]);
        //tetra_Edges[0].GetComponent<MeshFilter>().mesh = cylinder_MD.ToMeshInternal();

        //tetra_Edges[1].transform.localPosition = tetra_Verts[1];
        //tetra_Edges[1].transform.localRotation = Quaternion.LookRotation(tetra_Verts[2] - tetra_Verts[1]);
        //tetra_Edges[1].GetComponent<MeshFilter>().mesh = cylinder_MD.ToMeshInternal();

        //tetra_Edges[2].transform.localPosition = tetra_Verts[2];
        //tetra_Edges[2].transform.localRotation = Quaternion.LookRotation(tetra_Verts[3] - tetra_Verts[2]);
        //tetra_Edges[2].GetComponent<MeshFilter>().mesh = cylinder_MD.ToMeshInternal();

        //tetra_Edges[3].transform.localPosition = tetra_Verts[3];
        //tetra_Edges[3].transform.localRotation = Quaternion.LookRotation(tetra_Verts[0] - tetra_Verts[3]);
        //tetra_Edges[3].GetComponent<MeshFilter>().mesh = cylinder_MD.ToMeshInternal();

        //tetra_Edges[4].transform.localPosition = tetra_Verts[1];
        //tetra_Edges[4].transform.localRotation = Quaternion.LookRotation(tetra_Verts[3] - tetra_Verts[1]);
        //tetra_Edges[4].GetComponent<MeshFilter>().mesh = cylinder_MD.ToMeshInternal();

        //tetra_Edges[5].transform.localPosition = tetra_Verts[2];
        //tetra_Edges[5].transform.localRotation = Quaternion.LookRotation(tetra_Verts[0] - tetra_Verts[2]);
        //tetra_Edges[5].GetComponent<MeshFilter>().mesh = cylinder_MD.ToMeshInternal();
    }

    private void Create_Scythe(int depth)
    {


        subdivide_CarbonLine(tetra_Verts[1], tetra_Verts[3], tetra_Verts[2], depth, spheres[2].transform.localPosition, 3, false);

    }

    void Update() 
    {
        PQuad_SetUp_VertsAndTans();

        cylinder_MD.Clear();
        scythe_MD.Clear();
        Set_Tetra_Verts(tetra_Radius);

        if (sphere_Radius_Last != sphere_Radius)
        {
            float face_dist;
            float tetra_Height;
            Vector3 face_Norm;
            float a, b;

            aMD_Spheres.Clear();
            aMD_Spheres.Add(MeshDraft.Sphere(sphere_Radius, 36, 32));
            for (int i = 0; i < 4; i++)
            {
                face_dist = face_Centers[i].magnitude;
                tetra_Height = 1.33333f * tetra_Radius;
                a = 2 * Mathf.Sqrt(2) / 3;
                b = Mathf.Sqrt(sphere_Radius * sphere_Radius - tetra_Radius * tetra_Radius * 8f / 9f);
                face_Norm = face_Centers[i].normalized;

                spheres[i].transform.localPosition = face_Norm * (tetra_Radius / 3 + b);
                spheres[i].transform.localRotation = Quaternion.LookRotation(sphere_pos[i]);
                spheres[i].GetComponent<MeshFilter>().mesh = aMD_Spheres.ToMeshInternal();
                tetra_Verts_GO[i].transform.localPosition = tetra_Verts[i];
            }
        }
        sphere_Radius_Last = sphere_Radius;
        tetra_Radius_Last = tetra_Radius;
        if (bSpheresON)
        {
            ShowSpheres(true);
        }
        else
        {
            ShowSpheres(false);
        }

        aMD_TetraTerrean.Clear();

        for (int i = 0; i < 4; i++)
        {
            aMD_tt_Faces[i].Clear();
        }

        Create_TETRA_TERREAN(all_Shape_Depth);
        if (bSeparate_Faces)
        {
            for (int i = 0; i < 4; i++)
            {
                tt_Faces[i].GetComponent<MeshFilter>().mesh = aMD_tt_Faces[i].ToMeshInternal();
            }
        }
        else
        {
            aMD_TetraTerrean.FlipTriangles();
            aMD_TetraTerrean.FlipNormals();

            TetraTerrien_GO.GetComponent<MeshFilter>().mesh = aMD_TetraTerrean.ToMeshInternal();
            TetraTerrien_GO.GetComponent<MeshFilter>().mesh.RecalculateNormals(60);
        }

        //Create_Scythe(all_Shape_Depth);

        //aMD_TT_Sleeve.Clear();
        //Create_TETRA_TERREAN_Sleeve(all_Shape_Depth);
        //TT_Sleeve_GO.GetComponent<MeshFilter>().mesh = aMD_TT_Sleeve.ToMeshInternal();
        //TT_Sleeve_GO.GetComponent<MeshFilter>().mesh.RecalculateNormals(60);

        sphere_Radius_Last = sphere_Radius;
        tetra_Radius_Last  = tetra_Radius;
    }

    private void ShowSpheres(bool bOn)
    {
        for(int i = 0; i<4; i++)
        {
            spheres[i].GetComponent<MeshRenderer>().enabled = bOn;
        }
    }

    public void Calc_Tetra_Verts(float radius)
    {
        float tetrahedralAngle = Mathf.PI * -19.471220333f / 180;
        float segmentAngle = Mathf.PI * 2 / 3;
        float currentAngle = 0f;

        Vector3 v = new Vector3(0, radius, 0);
        sphere_pos[0] = v;
        for (var i = 1; i < 4; i++)
        {
            sphere_pos[i] = PTUtils.PointOnSphere(radius, currentAngle, tetrahedralAngle);
            currentAngle += segmentAngle;
        }
    }

    private Vector3 Sphere_Surf(Vector3 vP0, Vector3 sector_Focus)
    {
        Vector3 l = vP0.normalized;

        Vector3 OminC = -sector_Focus;
        float rsqr = sphere_Radius * sphere_Radius;

        float dotLOminC = Vector3.Dot(l, OminC);
        float Omag = OminC.magnitude;

        float d = -dotLOminC - Mathf.Sqrt(dotLOminC * dotLOminC - Omag * Omag + sphere_Radius * sphere_Radius);

        return l * d;
    }

    private Vector3 Hyperbolic(Vector3 vP0, Vector3 face_Center)
    {
        Vector3 vFCn = face_Center.normalized;
        Vector3 dotV = Vector3.Dot(vP0, vFCn)* vFCn;
        Vector3 radV = vP0 - dotV;
        Vector3 vP0n = vP0.normalized;
        float   radial_mag = radV.magnitude;
        float   perc = (radial_mag / vIH.magnitude)*255;
        float   gVal = 0f;

        //if (perc < 256)
        //{
        gVal = hyperbola_of_R[(int)perc];
        //}

        return  (gVal*vFCn + radV);
    }
     
    void Extrude_1(Vector3 v1, Vector3 v2, Vector3 v3, int nID, Vector3 sector_Foc)
    {
        Vector3 nSect = tetra_Verts[0].normalized;
        float spar = (1f / 9f) * sphere_Radius;

        float d1, d2, d3;

        d1 = Vector3.Dot(v1, nSect);
        d2 = Vector3.Dot(v2, nSect);
        d3 = Vector3.Dot(v3, nSect);

        Vector3 ob1 = (spar - d1) * nSect;
        Vector3 ob2 = (spar - d2) * nSect;
        Vector3 ob3 = (spar - d3) * nSect;

        if (nID == 0)
        {
            addTriangle(v1 + 2 * ob1, v2 + 2 * ob2, v3 + 2 * ob3);
        }
        else
        {
            ob1 = PosCheck(nSect, d1, (d1 / spar), v1 - d1 * nSect);
            ob2 = PosCheck(nSect, d2, (d2 / spar), v2 - d2 * nSect);
            ob3 = PosCheck(nSect, d3, (d3 / spar), v3 - d3 * nSect);
            addTriangle(v1 + ob1, v2 + ob2, v3 + ob3);
        }
    }

    private Vector3 PosCheck(Vector3 v1, float d, float multiplier, Vector3 vHorz)
    {
        return Vector3.zero;
    }

    public static void RemoveDuplicates(List<Vector_Sort> list)
    {
        if (list == null)
        {
            return;
        }
        int i = 1;
        while (i < list.Count)
        {
            int j = 0;
            bool remove = false;
            while (j < i && !remove)
            {
                if (list[i].vert.Equals(list[j].vert))
                {
                    remove = true;
                }
                j++;
            }
            if (remove)
            {
                list.RemoveAt(i);
            }
            else
            {
                i++;
            }
        }
    }

    private void Create_TETRA_TERREAN_Sleeve(int depth)
    {
        //sleeve(tetra_Verts[1], tetra_Verts[2], tetra_Verts[3], spheres[2].transform.localPosition);
        subdivide_CarbonLine(tetra_Verts[1], tetra_Verts[3], tetra_Verts[2], depth, spheres[2].transform.localPosition, 3, false);
        List<Vector_Sort>[] vsortList = new List<Vector_Sort>[3];

        vsortList[0] = new List<Vector_Sort>();
        vsortList[1] = new List<Vector_Sort>();
        vsortList[2] = new List<Vector_Sort>();

        int[] id = new int [] { 0, 1, 2, 0, 2, 3, 0, 3, 1 }; 
        Vector3   vert;

        for (int j = 0; j < 3; j++)
        {
            Plane p = new Plane(tetra_Verts[id[3*j]], tetra_Verts[id[3*j+1]], tetra_Verts[id[3*j+2]]);
            p.D = p.D * 0.999f;
            
            Vector_Sort vs;

            filteredVerts = 0;

            for (int i = 0; i < aMD_TT_Sleeve.vertices.Count; i++)
            {
                vert = aMD_TT_Sleeve.vertices[i];
                float res = PlaneHelper.ClassifyPoint(ref vert, ref p);

                if (res > 0)
                {
                    filteredVerts++;
                    vs = new Vector_Sort();
                    vs.vert = vert;
                    vs.vIDX = i;
                    vsortList[j].Add(vs);
                }
            }
            RemoveDuplicates(vsortList[j]);
            for (int i = 0; i < vsortList[j].Count; i++)
            {
                vsortList[j][i].dist = (tetra_Verts[j+1] - vsortList[j][i].vert).magnitude;
            }

            var ordered = from element in vsortList[j]
                      orderby element.dist
                      select element;

            vsortList[j] = ordered.ToList<Vector_Sort>();
        }

        for(int i = 0; i < aMD_TT_Sleeve.vertices.Count; i++)
        {
            aMD_TT_Sleeve.vertices[i] = Sphere_Surf(aMD_TT_Sleeve.vertices[i], spheres[2].transform.localPosition);
        }

        Plane p2 = new Plane(Vector3.up, 0);
        int vertCount = aMD_TT_Sleeve.vertices.Count;

        ArgosMeshDraft amdTemp = new ArgosMeshDraft();

        amdTemp.Add(aMD_TT_Sleeve);
        aMD_TT_Sleeve.FlipTriangles();
        
        aMD_TT_Sleeve.Add(amdTemp);
        aMD_TT_Sleeve.FlipNormals();
        float d;

        for(int i = 0; i < vertCount; i++)//FLIP
        {
            vert = aMD_TT_Sleeve.vertices[i];
            d = PlaneHelper.ClassifyPoint(ref vert, ref p2);
            aMD_TT_Sleeve.vertices[i] = vert - 2f * d * Vector3.up;
        }

        List<Vector3> vInnerLower = new List<Vector3>();
        List<Vector3> vInnerUpper = new List<Vector3>();

        List<Vector3> vPrint = new List<Vector3>();

        for (int j = 0; j < 3; j++)
        {
            for (int i = 0; i < vsortList[j].Count; i++)
            {
                vert = aMD_TT_Sleeve.vertices[vsortList[j][i].vIDX];
                //vCurr.y = 0;
                d = PlaneHelper.ClassifyPoint(ref vert, ref p2);

                if(j==0)
                {
                    vPrint.Add(vert - 2f * d * Vector3.up);
                }

                vInnerLower.Add(vert);
                vInnerUpper.Add(vert - 2f * d * Vector3.up);
            }
        }
        aMD_TT_Sleeve.Add(MeshDraft.Band(vInnerUpper, vInnerLower));
        aMD_TT_Sleeve.Add(MeshDraft.Band(vInnerLower, vInnerUpper));

        Generate_Line_List(vPrint);

        vSort_Count_0 = vsortList[0].Count;
        vSort_Count_1 = vsortList[1].Count;
        vSort_Count_2 = vsortList[2].Count;
    }


    bool bwritten = false;
    void Generate_Line_List(List<Vector3> vPrint)
    {
        if (!bwritten)
        {
            vPrint.Insert(0, tetra_Verts[1]);
            vPrint.Add(tetra_Verts[2]);
            //vPrint[0] = tetra_Verts[1];
            //vPrint[vPrint.Count - 1] = tetra_Verts[2];

            for (int i = 0; i < vPrint.Count-1; i++)
            {
                sLineVectors.Write("new Vector3(" + vPrint[i].x.ToString("F3") + "f, " + vPrint[i].y.ToString("F3") + "f, " + vPrint[i].z.ToString("F3") + "f), " +
                                   "new Vector3(" + vPrint[i+1].x.ToString("F3") + "f, " + vPrint[i+1].y.ToString("F3") + "f, " + vPrint[i+1].z.ToString("F3") + "f), ");
            }

            Quaternion q;
            float angle = 120f;
            Vector3 axis = Vector3.up;

            q = Quaternion.AngleAxis(angle, axis);
            Vector3 vRota;
            Vector3 vRota2;

            for (int i = 0; i< vPrint.Count-1; i++)
            {
                vRota  = q * vPrint[i];
                vRota2 = q * vPrint[i+1];
                sLineVectors.Write("new Vector3(" + vRota.x.ToString("F3") + "f, " + vRota.y.ToString("F3") + "f, " + vRota.z.ToString("F3") + "f), " +
                    "new Vector3(" + vRota2.x.ToString("F3") + "f, " + vRota2.y.ToString("F3") + "f, " + vRota2.z.ToString("F3") + "f), ");


            }
            angle = 240;
            q = Quaternion.AngleAxis(angle, axis);
            for (int i = 0; i < vPrint.Count - 1; i++)
            {
                vRota = q * vPrint[i];
                vRota2 = q * vPrint[i + 1];
                sLineVectors.Write("new Vector3(" + vRota.x.ToString("F3") + "f, " + vRota.y.ToString("F3") + "f, " + vRota.z.ToString("F3") + "f), " +
                    "new Vector3(" + vRota2.x.ToString("F3") + "f, " + vRota2.y.ToString("F3") + "f, " + vRota2.z.ToString("F3") + "f), ");

            }
            /////////////////////////UPPERS//////////////////////

            List<Vector3> vlUppers = new List<Vector3>();
            axis = tetra_Verts[1].normalized;
            angle = 120;
            q = Quaternion.AngleAxis(angle, axis);
            for (int i = 0; i < vPrint.Count - 1; i++)
            {
                vRota = q * vPrint[i];
                vRota2 = q * vPrint[i + 1];

                vlUppers.Add(vRota);
                vlUppers.Add(vRota2);

                sLineVectors.Write("new Vector3(" + vRota.x.ToString("F3") + "f, " + vRota.y.ToString("F3") + "f, " + vRota.z.ToString("F3") + "f), " +
                    "new Vector3(" + vRota2.x.ToString("F3") + "f, " + vRota2.y.ToString("F3") + "f, " + vRota2.z.ToString("F3") + "f), ");

            }

            axis = Vector3.up;
            angle = 120;
            q = Quaternion.AngleAxis(angle, axis);
            for (int i = 0; i < vlUppers.Count - 1; i++)
            {
                vRota = q * vlUppers[i];
                vRota2 = q * vlUppers[i + 1];
                sLineVectors.Write("new Vector3(" + vRota.x.ToString("F3") + "f, " + vRota.y.ToString("F3") + "f, " + vRota.z.ToString("F3") + "f), " +
                    "new Vector3(" + vRota2.x.ToString("F3") + "f, " + vRota2.y.ToString("F3") + "f, " + vRota2.z.ToString("F3") + "f), ");
            }
            angle = 240;
            q = Quaternion.AngleAxis(angle, axis);
            for (int i = 0; i < vlUppers.Count - 1; i++)
            {
                vRota = q * vlUppers[i];
                vRota2 = q * vlUppers[i + 1];
                sLineVectors.Write("new Vector3(" + vRota.x.ToString("F3") + "f, " + vRota.y.ToString("F3") + "f, " + vRota.z.ToString("F3") + "f), " +
                    "new Vector3(" + vRota2.x.ToString("F3") + "f, " + vRota2.y.ToString("F3") + "f, " + vRota2.z.ToString("F3") + "f), ");
            }
        }
        sLineVectors.Close();
        bwritten = true;
    }

    void sleeve(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 sector_Foc)
    {
        Vector3 vCurr;

        //v1.y = v2.y = v3.y = 0;

        List<Vector3> vInnerLower = new List<Vector3>();
        List<Vector3> vInnerUpper = new List<Vector3>();

        Vector3 vUp = Vector3.up;

        Vector3[] vAlpha = new[] { v1, v2, v3 };
        Vector3[] vOmega = new[] { v2, v3, v1 };

        float t = 0;
        float dt = 1 / 60f;
        float d;
        float el;
        for (int j = 0; j < 3; j++)
        {
            t = 0;
            for (int i = 0; i < 60; i++)
            {
                vCurr = Sphere_Surf(Vector3.Lerp(vAlpha[j], vOmega[j], t), sector_Foc);
                //vCurr.y = 0;
                d  =  vCurr.y;
                el = Mathf.Sqrt(tetra_Radius * tetra_Radius - d * d); 

                //vInnerLower.Add(vCurr + el*vUp);
                //vInnerUpper.Add(vCurr - el*vUp);

                vInnerLower.Add(vCurr);
                vInnerUpper.Add(vCurr - 2f * d * vUp);

                t += dt;
            }
        }
        aMD_TT_Sleeve.Add(MeshDraft.Band(vInnerUpper, vInnerLower));
        aMD_TT_Sleeve.Add(MeshDraft.Band(vInnerLower, vInnerUpper));
    }


    Vector3[] vTest = new Vector3[1024];
    Vector3[] vTest2 = new Vector3[1024];
    int i = 0;
    void subdivide_CarbonLine(Vector3 v1, Vector3 v2, Vector3 v3, int depth, Vector3 sector_Foc, int nID, bool bFlipNorm)
    {
        Vector3 v12, v23, v31;
        Vector3 v12_n, v23_n, v31_n;

        if (depth == 0)
        {
            if (bFlipNorm)
            {
                addTriangle_UV_Tag_CarbonLine(v1, v3, v2, (float)nID);
            }
            else
            {
                //if (nID == 1)
                //{
                //    vTest[i] = v1;
                //    vTest2[i] = v2;
                //    i++;
                //}
                addTriangle_UV_Tag_CarbonLine(v1, v2, v3, (float)nID);
            }
            return;
        }

        v12 = (v1 + v2) / 2.0f;
        v23 = (v2 + v3) / 2.0f;
        v31 = (v3 + v1) / 2.0f;

        //v12_n = Sphere_Surf(v12, sector_Foc); //sector_Foc
        //v23_n = Sphere_Surf(v23, sector_Foc);
        //v31_n = Sphere_Surf(v31, sector_Foc);

        v12_n = v12; //sector_Foc
        v23_n = v23;
        v31_n = v31;

        /* recursively subdivide new triangles */
        subdivide_CarbonLine(v1, v12_n, v31_n, depth - 1, sector_Foc, 1, bFlipNorm);
        subdivide_CarbonLine(v12_n, v2, v23_n, depth - 1, sector_Foc, 2, bFlipNorm);
        subdivide_CarbonLine(v31_n, v23_n, v3, depth - 1, sector_Foc, 3, bFlipNorm);
        subdivide_CarbonLine(v23_n, v31_n, v12_n, depth - 1, sector_Foc, 4, bFlipNorm);
    }

    private void Create_TETRA_TERREAN(int depth)
    {
        float scl = scale_TT / 100f;

        if (ttType == TT_TYPE.SPHERICAL)
        {
            subdivide(tetra_Verts[0], tetra_Verts[1], tetra_Verts[2], depth, spheres[0].transform.localPosition, 0, true);
            subdivide(tetra_Verts[0], tetra_Verts[2], tetra_Verts[3], depth, spheres[1].transform.localPosition, 1, true);
            subdivide(tetra_Verts[0], tetra_Verts[3], tetra_Verts[1], depth, spheres[3].transform.localPosition, 2, true);
            subdivide(tetra_Verts[1], tetra_Verts[3], tetra_Verts[2], depth, spheres[2].transform.localPosition, 3, true);
            //subdivide(tetra_Verts[0], tetra_Verts[1], tetra_Verts[2], depth, spheres[0].transform.localPosition, 0, false);//Inside
            //subdivide(tetra_Verts[0], tetra_Verts[2], tetra_Verts[3], depth, spheres[1].transform.localPosition, 1, false);
            //subdivide(tetra_Verts[0], tetra_Verts[3], tetra_Verts[1], depth, spheres[3].transform.localPosition, 2, false);
        }
        else if(ttType == TT_TYPE.HYPERBOLIC)
        {
            Vector3[] fc = new Vector3[4];

            fc[0] = (tetra_Verts[0] + tetra_Verts[1] + tetra_Verts[2]) / 3f;
            fc[1] = (tetra_Verts[0] + tetra_Verts[2] + tetra_Verts[3]) / 3f;
            fc[2] = (tetra_Verts[1] + tetra_Verts[3] + tetra_Verts[2]) / 3f;
            fc[3] = (tetra_Verts[0] + tetra_Verts[3] + tetra_Verts[1]) / 3f;

            beeS[0] = base_Distance * tetra_Radius * fc[0].normalized;
            beeS[1] = base_Distance * tetra_Radius * fc[1].normalized;
            beeS[2] = base_Distance * tetra_Radius * fc[2].normalized;
            beeS[3] = base_Distance * tetra_Radius * fc[3].normalized;

            subdivide(tetra_Verts[0], tetra_Verts[1], tetra_Verts[2], depth, fc[0], 0, false);
            subdivide(tetra_Verts[0], tetra_Verts[2], tetra_Verts[3], depth, fc[1], 1, false);
            subdivide(tetra_Verts[1], tetra_Verts[3], tetra_Verts[2], depth, fc[2], 2, false);
            subdivide(tetra_Verts[0], tetra_Verts[3], tetra_Verts[1], depth, fc[3], 3, false);

            Scale_Radially();
        }
    }

    private void Scale_Radially()
    {
        float ratio = 0;
        Vector3 v;
        float fidx;
        float len;

        for (int i = 0; i<aMD_TetraTerrean.vertices.Count; i++)
        {  
            fidx  = aMD_TetraTerrean.uv[i].x;

            ratio = aMD_TetraTerrean.vertices[i].magnitude / tetra_Radius;

            aMD_TetraTerrean.vertices[i] *= ((smidge * ratio) + (1 - smidge));
        }
    }

    void subdivide(Vector3 v1, Vector3 v2, Vector3 v3, int depth, Vector3 sector_Foc, int nID, bool bFlipNorm)
    {
        Vector3 v12, v23, v31;
        Vector3 v12_n, v23_n, v31_n;

        if (depth == 0)
        {
            if (ttType == TT_TYPE.SPHERICAL || ttType == TT_TYPE.HYPERBOLIC)
            {
                if (bFlipNorm)
                {
                    addTriangle_UV_Tag(v1, v3, v2, (float)nID);
                }
                else
                {
                    addTriangle_UV_Tag(v1, v2, v3, (float)nID);
                }
            }
            else if (ttType == TT_TYPE.OTHER)
            {
                Extrude_1(v1, v2, v3, nID, sector_Foc);
            }
            return;
        }

        v12 = (v1 + v2) / 2.0f;
        v23 = (v2 + v3) / 2.0f;
        v31 = (v3 + v1) / 2.0f;

        //intrude midpoints
        if (ttType == TT_TYPE.SPHERICAL)
        {
            v12_n = Sphere_Surf(v12, sector_Foc); //sector_Foc
            v23_n = Sphere_Surf(v23, sector_Foc);
            v31_n = Sphere_Surf(v31, sector_Foc);
        }
        else if(ttType == TT_TYPE.HYPERBOLIC)
        {
            v12_n = Hyperbolic(v12, sector_Foc); //sector_Foc
            v23_n = Hyperbolic(v23, sector_Foc);
            v31_n = Hyperbolic(v31, sector_Foc);
        }
        else if (ttType == TT_TYPE.OTHER)
        {
            v12_n = Extrude_1(v12, sector_Foc); //sector_Foc
            v23_n = Extrude_1(v23, sector_Foc);
            v31_n = Extrude_1(v31, sector_Foc);
        }
        else
        {
            v12_n = Vector3.zero;
            v23_n = Vector3.zero;
            v31_n = Vector3.zero;
        }
        /* recursively subdivide new triangles */
        subdivide(v1,    v12_n, v31_n, depth - 1, sector_Foc, nID, bFlipNorm);
        subdivide(v12_n, v2,    v23_n, depth - 1, sector_Foc, nID, bFlipNorm);
        subdivide(v31_n, v23_n, v3,    depth - 1, sector_Foc, nID, bFlipNorm);
        subdivide(v23_n, v31_n, v12_n, depth - 1, sector_Foc, nID, bFlipNorm);
    }

    private Vector3 Extrude_1(Vector3 vP0, Vector3 sector_Focus)
    {
        Vector3 l = vP0.normalized;

        Vector3 OminC = -sector_Focus;
        float rsqr = sphere_Radius * sphere_Radius;

        float dotLOminC = Vector3.Dot(l, OminC);
        float Omag = OminC.magnitude;

        float d = -dotLOminC - Mathf.Sqrt(dotLOminC * dotLOminC - Omag * Omag + sphere_Radius * sphere_Radius);

        return (l * d);
    }

    void addTriangle(Vector3 v0, Vector3 v1, Vector3 v2)
    {
        aMD_TetraTerrean.Add(MeshDraft.Triangle(v0, v1, v2));
    }

    void addTriangle_UV_Tag(Vector3 v0, Vector3 v1, Vector3 v2, float uvTag)
    {
        if (bSeparate_Faces)
        {
            aMD_tt_Faces[(int)uvTag].Add(MeshDraft.Triangl_UV_Tag(v0, v1, v2, uvTag));
        }
        else
        {
            aMD_TetraTerrean.Add(MeshDraft.Triangl_UV_Tag(v0, v1, v2, uvTag));
        }
    }

    void addTriangle_UV_Tag_CarbonLine(Vector3 v0, Vector3 v1, Vector3 v2, float uvTag)
    {
        aMD_TT_Sleeve.Add(MeshDraft.Triangl_UV_Tag(v0, v1, v2, uvTag));
    }

    void addTriangle_Skythe(Vector3 v0, Vector3 v1, Vector3 v2, float uvTag)
    {
        scythe_MD.Add(MeshDraft.Triangl_UV_Tag(v0, v1, v2, uvTag));
    }

    void addTriangle_UV_Tag_PQUAD(Vector3 v0, Vector3 v1, Vector3 v2, float uvTag)
    {
        aMD_PQUAD_TMP.Add(MeshDraft.Triangl_UV_Tag(v0, v1, v2, uvTag));
    }

    public Vector3 GetPointOnBezierCurve(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
    {
        float u = 1f - t;
        float t2 = t * t;
        float u2 = u * u;
        float u3 = u2 * u;
        float t3 = t2 * t;

        Vector3 result =
            (u3) * p0 +
            (3f * u2 * t) * p1 +
            (3f * u * t2) * p2 +
            (t3) * p3;

        return result;
    }

    /// <image url="$(SolutionDir)\EMB\hyperbole.png" scale="0.45"></image>
    /// 



}







/// <image url="$(SolutionDir)\EMB\CD.png" scale="0.16803" /> 


//int countDown = 10;
//void Update() // Original
//{
//    H_Editor_Label.transform.position  = tetra_Verts_GO[0].transform.position;
//    C_Editor_Label.transform.position  = transform.position;

//    Vector3 v = (tetra_Verts_GO[0].transform.localPosition + tetra_Verts_GO[1].transform.localPosition + tetra_Verts_GO[2].transform.localPosition) /3;

//    I_Editor_Label.transform.position = v + transform.position;
//    B_Editor_Label.transform.position = transform.position + base_Distance*tetra_Radius*v.normalized;

//    vCHn = (H_Editor_Label.transform.localPosition - C_Editor_Label.transform.localPosition).normalized;
//    vIH  = (H_Editor_Label.transform.localPosition - I_Editor_Label.transform.localPosition);
//    vIHn = vIH.normalized;

//    vT1n = Vector3.Dot(vCHn, vIHn) * vIHn;
//    vT2n = vCHn;

//    T1_Editor_Label.transform.localPosition = (vT1n * tangent_Base * tetra_Radius + B_Editor_Label.transform.localPosition);
//    T2_Editor_Label.transform.localPosition = (H_Editor_Label.transform.localPosition - vT2n * tangent_H * tetra_Radius);

//    if(frameCount-- == 0 || bPrint_Trace)
//    {
//        frameCount = 10;
//        build_Reference_Hyperbolic_Spline(B_Editor_Label.transform.localPosition,  T1_Editor_Label.transform.localPosition, 
//                                          T2_Editor_Label.transform.localPosition, H_Editor_Label.transform.localPosition,
//                                          I_Editor_Label.transform.localPosition);

//        if(--countDown == 0 || bPrint_Trace)
//        {
//            Print_Trace(B_Editor_Label.transform.localPosition, T1_Editor_Label.transform.localPosition,
//                                          T2_Editor_Label.transform.localPosition, H_Editor_Label.transform.localPosition,
//                                          I_Editor_Label.transform.localPosition);             
//        }
//    }
//    bPrint_Trace = false;
//    aMD_Spheres.Clear();
//    aMD_Spheres.Add(MeshDraft.Sphere(sphere_Radius, 36, 32));
//    cylinder_MD.Clear();

//    Set_Tetra_Verts(tetra_Radius);

//    float face_dist;
//    float tetra_Height;
//    Vector3 face_Norm;
//    float a, b;

//    if (sphere_Radius_Last != sphere_Radius)
//    {
//        for (int i = 0; i < 4; i++)
//        {
//            face_dist = face_Centers[i].magnitude;
//            tetra_Height = 1.33333f * tetra_Radius;
//            a = 2 * Mathf.Sqrt(2) / 3;
//            b = Mathf.Sqrt(sphere_Radius * sphere_Radius - tetra_Radius * tetra_Radius * 8f / 9f);
//            face_Norm = face_Centers[i].normalized;

//            spheres[i].transform.localPosition = face_Norm * (tetra_Radius / 3 + b);
//            spheres[i].transform.localRotation = Quaternion.LookRotation(sphere_pos[i]);
//            spheres[i].GetComponent<MeshFilter>().mesh = aMD_Spheres.ToMeshInternal();
//            tetra_Verts_GO[i].transform.localPosition = tetra_Verts[i];
//        }
//    }
//    sphere_Radius_Last = sphere_Radius;
//    tetra_Radius_Last = tetra_Radius;
//    //if ((sphere_Radius_Last != sphere_Radius || tetra_Radius_Last != tetra_Radius) && bTetraTerrean_On)
//    //{
//    aMD_TetraTerrean.Clear();

//    for (int i = 0; i < 4; i++)
//    {
//        aMD_tt_Faces[i].Clear();
//    }

//    Create_TETRA_TERREAN(all_Shape_Depth);
//    if (bSeparate_Faces)
//    {
//        for (int i = 0; i < 4; i++)
//        {
//            tt_Faces[i].GetComponent<MeshFilter>().mesh = aMD_tt_Faces[i].ToMeshInternal();
//        }
//    }
//    else
//    {
//        TetraTerrien_GO.GetComponent<MeshFilter>().mesh = aMD_TetraTerrean.ToMeshInternal();
//        TetraTerrien_GO.GetComponent<MeshFilter>().mesh.RecalculateNormals(60);
//    }

//    aMD_TT_Sleeve.Clear();
//    Create_TETRA_TERREAN_Sleeve();
//    TT_Sleeve_GO.GetComponent<MeshFilter>().mesh = aMD_TT_Sleeve.ToMeshInternal();
//    TT_Sleeve_GO.GetComponent<MeshFilter>().mesh.RecalculateNormals(60);

//    sphere_Radius_Last = sphere_Radius;
//    tetra_Radius_Last  = tetra_Radius;

//    if (bSpheresON)
//    {
//        ShowSpheres(true);
//    }
//    else
//    {
//        ShowSpheres(false);
//    }
//}

 

Unexpected Result

    public void OnShow_Differential_Rings()
    {
        if (amd == null)
        {
            amd = new ArgosMeshDraft();
        }
        else
        {
            amd.Clear();
        }

        GetComponent<MeshRenderer>().enabled = true;
        setColor(orbitColor);

        List<Vector3> vInnerLower = new List<Vector3>();
        List<Vector3> vOuterLower = new List<Vector3>();
        List<Vector3> vInnerUpper = new List<Vector3>();
        List<Vector3> vOuterUpper = new List<Vector3>();

        List<Vector3> vDisk = new List<Vector3>();

        float op = Mathf.PI * 2f;
        float delta_theta = op / 180;
        float theta = 0;

        Vector3 vPos_Lower;
        Vector3 vPos_Upper;
        Vector3 vNorm;
        float w_by_2 = cylinder_Width * 0.66666f;//to differentiate from Helix
        float rad = cylinder_Radius;

        Vector3 vZero = (cylinder_Offset + cylinder_Height / 2) * Vector3.up;
        vDisk.Add(vZero);

        for (int i = 0; i < 180; i++)
        {
            vPos_Lower = rad * Mathf.Cos(theta) * Vector3.right + rad * Mathf.Sin(theta) * Vector3.forward;

            float theta_Mod = theta % (Mathf.PI*2f/3f);
            float lout = rad;

            if (theta_Mod < 60)
            {
                lout = (1f - 0.5f * theta_Mod / (Mathf.PI/ 3f));
            }
            else
            {
                lout = (0.5f + 0.5f * (theta_Mod - (Mathf.PI / 3f)) / (Mathf.PI / 3f));
            }
            vPos_Lower *= lout;
            vPos_Upper = vPos_Lower;
            vNorm = vPos_Lower.normalized;
            vPos_Lower += cylinder_Offset * Vector3.up;

            vInnerLower.Add(vPos_Lower - w_by_2 * vNorm);
            vOuterLower.Add(vPos_Lower + w_by_2 * vNorm);

            vPos_Lower += (cylinder_Height / 2) * Vector3.up;
            vDisk.Add(vPos_Lower - w_by_2 * vNorm);

            vPos_Upper += cylinder_Height * Vector3.up + cylinder_Offset * Vector3.up;

            vInnerUpper.Add(vPos_Upper - w_by_2 * vNorm);
            vOuterUpper.Add(vPos_Upper + w_by_2 * vNorm);

            theta += delta_theta;
        }
        amd.Add(MeshDraft.Band(vOuterLower, vInnerLower));
        amd.Add(MeshDraft.Band(vInnerUpper, vOuterUpper));
        amd.Add(MeshDraft.Band(vInnerLower, vInnerUpper));
        amd.Add(MeshDraft.Band(vOuterUpper, vOuterLower));

        if (bAdd_Disk)
        {
            amd.Add(MeshDraft.TriangleFan(vDisk));
        }
        GetComponent<MeshFilter>().mesh = amd.ToMeshInternal();
    }