public override void NormalizeCylindricCoordinates(ShapeCoordinates coords)
        {
            CalculateVolume();  // Update the bezier curve control nodes
            // The Bezier curve control points are NOT normalized.
            //  p0 = new Vector2(bottomDiameter, -length / 2f);
            //  p3 = new Vector2(topDiameter, length / 2f);
            // But we do need to normalize the length and shift such that 0 <= t <= 1
            float   t  = (coords.y / length) + (1f / 2);
            Vector2 Bt = B(t);

            Debug.Log($"{ModTag} Normalized {coords.y} to {t} (0=bottom, 1=top), B(t)={Bt} as (diameter, length). TopDiameter:{topDiameter} BotDiameter:{bottomDiameter} Len: {length}");
            // For a given normalized length (t), B(t).x is the diameter of the surface cross-section at that length, and B(t).y is .. what?

            coords.y /= length;
            coords.r /= (Bt.x / 2);
        }
 public abstract Vector3 FromCylindricCoordinates(ShapeCoordinates coords);
 public abstract void GetCylindricCoordinates(Vector3 position, ShapeCoordinates coords);
        public override Vector3 FromCylindricCoordinates(ShapeCoordinates coords)
        {
            Vector3 position = new Vector3();

            switch (coords.HeightMode)
            {
                case ShapeCoordinates.YMode.RELATIVE_TO_SHAPE:
                    float halfLength = (lastProfile.Last.Value.y - lastProfile.First.Value.y) / 2.0f;
                    position.y = halfLength * coords.y;
                    break;

                case ShapeCoordinates.YMode.OFFSET_FROM_SHAPE_CENTER:
                    position.y = coords.y;
                    break;

                case ShapeCoordinates.YMode.OFFSET_FROM_SHAPE_BOTTOM:
                    position.y = coords.y + lastProfile.First.Value.y;
                    break;

                case ShapeCoordinates.YMode.OFFSET_FROM_SHAPE_TOP:
                    position.y = coords.y + lastProfile.Last.Value.y;
                    break;
                default:
                    Debug.LogError("Can not handle PartCoordinate attribute: " + coords.HeightMode);
                    position.y = 0.0f;
                    break;
            }

            float radius = coords.r;

            if (coords.RadiusMode != ShapeCoordinates.RMode.OFFSET_FROM_SHAPE_CENTER)
            {
                if (position.y < lastProfile.First.Value.y)
                    radius = coords.RadiusMode == ShapeCoordinates.RMode.OFFSET_FROM_SHAPE_RADIUS ?
                        lastProfile.First.Value.dia / 2.0f + coords.r :
                        radius = lastProfile.First.Value.dia / 2.0f * coords.r;

                else if (position.y > lastProfile.Last.Value.y)
                    radius = coords.RadiusMode == ShapeCoordinates.RMode.OFFSET_FROM_SHAPE_RADIUS ?
                        lastProfile.Last.Value.dia / 2.0f + coords.r :
                        radius = lastProfile.Last.Value.dia / 2.0f * coords.r;
                else
                {
                    ProfilePoint pt = lastProfile.First.Value;
                    for (LinkedListNode<ProfilePoint> ptNode = lastProfile.First.Next; ptNode != null; ptNode = ptNode.Next)
                    {
                        if (!ptNode.Value.inCollider)
                            continue;
                        ProfilePoint pv = pt;
                        pt = ptNode.Value;

                        if (position.y >= Mathf.Min(pv.y, pt.y) && position.y < Mathf.Max(pv.y, pt.y))
                        {
                            float t = Mathf.InverseLerp(Mathf.Min(pv.y, pt.y), Mathf.Max(pv.y, pt.y), position.y);
                            float profileRadius = Mathf.Lerp(pv.dia, pt.dia, t) / 2.0f;

                            radius = coords.RadiusMode == ShapeCoordinates.RMode.OFFSET_FROM_SHAPE_RADIUS ?
                                profileRadius + coords.r :
                                radius = profileRadius * coords.r;
                        }
                    }
                }
            }

            float theta = Mathf.Lerp(0, Mathf.PI * 2f, coords.u);

            position.x = Mathf.Cos(theta) * radius;
            position.z = -Mathf.Sin(theta) * radius;

            return position;
        }
        public override void GetCylindricCoordinates(Vector3 position, ShapeCoordinates result)
        {
            Vector2 direction = new Vector2(position.x, position.z);

            switch(result.HeightMode)
            {
                case ShapeCoordinates.YMode.RELATIVE_TO_SHAPE:
                    float halfLength = (lastProfile.Last.Value.y - lastProfile.First.Value.y) / 2.0f;
                    result.y = position.y / halfLength;
                    break;

                case ShapeCoordinates.YMode.OFFSET_FROM_SHAPE_CENTER:
                    result.y = position.y;
                    break;

                case ShapeCoordinates.YMode.OFFSET_FROM_SHAPE_BOTTOM:
                    result.y = position.y - lastProfile.First.Value.y;
                    break;

                case ShapeCoordinates.YMode.OFFSET_FROM_SHAPE_TOP:
                    result.y = position.y - lastProfile.Last.Value.y;
                    break;
                default:
                    Debug.LogError("Can not handle PartCoordinate attribute: " + result.HeightMode);
                    result.y = 0.0f;
                    break;
            }

            result.r = 0;

            float theta = Mathf.Atan2(-direction.y, direction.x);

            result.u = (Mathf.InverseLerp(-Mathf.PI, Mathf.PI, theta) + 0.5f) % 1.0f;

            if(result.RadiusMode == ShapeCoordinates.RMode.OFFSET_FROM_SHAPE_CENTER)
            {
                result.r = direction.magnitude;
                return;
            }

            if (position.y <= lastProfile.First.Value.y)
                result.r = result.RadiusMode == ShapeCoordinates.RMode.OFFSET_FROM_SHAPE_RADIUS ?
                    direction.magnitude - lastProfile.First.Value.dia / 2.0f :
                    direction.magnitude / (lastProfile.First.Value.dia / 2.0f); // RELATIVE_TO_SHAPE_RADIUS

            else if (position.y >= lastProfile.Last.Value.y)
                result.r = result.RadiusMode == ShapeCoordinates.RMode.OFFSET_FROM_SHAPE_RADIUS ?
                    direction.magnitude - lastProfile.Last.Value.dia / 2.0f :
                    direction.magnitude / (lastProfile.Last.Value.dia / 2.0f); // RELATIVE_TO_SHAPE_RADIUS
            else
            {
                ProfilePoint pt = lastProfile.First.Value;
                for (LinkedListNode<ProfilePoint> ptNode = lastProfile.First.Next; ptNode != null; ptNode = ptNode.Next)
                {
                    if (!ptNode.Value.inCollider)
                        continue;
                    ProfilePoint pv = pt;
                    pt = ptNode.Value;

                    if(position.y >= Mathf.Min(pv.y, pt.y) && position.y < Mathf.Max(pv.y, pt.y))
                    {
                        float t = Mathf.InverseLerp(Mathf.Min(pv.y, pt.y), Mathf.Max(pv.y, pt.y), position.y);
                        float r = Mathf.Lerp(pv.dia, pt.dia, t) / 2.0f;

                        result.r = result.RadiusMode == ShapeCoordinates.RMode.OFFSET_FROM_SHAPE_RADIUS ?
                            direction.magnitude - r : direction.magnitude / r;
                    }

                }

            }

            // sometimes, if the shapes radius is 0, r rersults in NaN
            if (float.IsNaN(result.r))
            {
                result.r = 0;
            }
        }
 public abstract void GetCylindricCoordinates(Vector3 position, ShapeCoordinates coords);
 public abstract Vector3 FromCylindricCoordinates(ShapeCoordinates coords);