private static VertexInfo[][] tubeFromCurve(Pt[] pts, double radius, int revSteps)
        {
            var normals = new Pt[pts.Length];

            normals[0] = ((pts[1] - pts[0]) * pt(0, 1, 0)).Normalize() * radius;
            for (int i = 1; i < pts.Length - 1; i++)
            {
                normals[i] = normals[i - 1].ProjectOntoPlane((pts[i + 1] - pts[i]) + (pts[i] - pts[i - 1])).Normalize() * radius;
            }
            normals[pts.Length - 1] = normals[pts.Length - 2].ProjectOntoPlane(pts[pts.Length - 1] - pts[pts.Length - 2]).Normalize() * radius;

            var axes = pts.Select((p, i) =>
                                  i == 0 ? new { Start = pts[0], End = pts[1] } :
                                  i == pts.Length - 1 ? new { Start = pts[pts.Length - 2], End = pts[pts.Length - 1] } :
                                  new { Start = p, End = p + (pts[i + 1] - p) + (p - pts[i - 1]) }).ToArray();

            return(Enumerable.Range(0, pts.Length)
                   .Select(ix => new { Axis = axes[ix], Perp = pts[ix] + normals[ix], Point = pts[ix] })
                   .Select(inf => Enumerable.Range(0, revSteps)
                           .Select(i => 360 * i / revSteps)
                           .Select(angle => inf.Perp.Rotate(inf.Axis.Start, inf.Axis.End, angle))
                           .Select(p => new VertexInfo {
                Point = p, Normal = p - inf.Point
            }).Reverse().ToArray())
                   .ToArray());
        }
        public static Mesh GenerateWire(Pt start, Pt startControl, Pt endControl, Pt end, int numSegments, WirePiece piece, Mode mode, int seed, Pt raiseBy)
        {
            const int bézierSteps  = 16;
            var       tubeRevSteps = mode == Mode.Collider ? 4 : 16;

            var rnd       = new Rnd(seed);
            var thickness = mode != Mode.Wire ? _wireRadiusHighlight : _wireRadius;

            var iStart             = startControl * .8 + endControl * .2;
            var iEnd               = startControl * .2 + endControl * .8;
            var interpolatedPoints = Ut.NewArray <Pt>(numSegments - 1, i => raiseBy + iStart + (iEnd - iStart) * i / (numSegments - 2));
            var controlPointsB     = Ut.NewArray <Pt>(numSegments - 1, i =>
            {
                var p1            = interpolatedPoints[i];
                var p2            = i == numSegments - 2 ? endControl : interpolatedPoints[i + 1];
                var v             = (p2 - p1).Normalize() * p1.Distance(p2) * .25;
                var dummy         = v.X > .5 ? new Pt(0, 1, 0) : new Pt(1, 0, 0);
                var perpendicular = dummy * v;
                v = (p1 + v).Rotate(p1, p1 + perpendicular, 45 * rnd.NextDouble());
                v = v.Rotate(p1, p2, 360 * rnd.NextDouble());
                return(v);
            });
            var controlPointsA = Ut.NewArray <Pt>(numSegments - 1, i => 2 * interpolatedPoints[i] - controlPointsB[i]);

            if (piece == WirePiece.Uncut)
            {
                if (mode == Mode.Collider)
                {
                    var points = new[] { start, startControl }.Concat(interpolatedPoints).Concat(new[] { endControl, end }).ToArray();
                    return(toMesh(createFaces(false, true, tubeFromCurve(points, thickness, tubeRevSteps))));
                }
                else
                {
                    var points =
                        new[] { new { ControlBefore = default(Pt), Point = start, ControlAfter = startControl } }
                    .Concat(interpolatedPoints.Select((p, i) => new { ControlBefore = controlPointsA[i], Point = p, ControlAfter = controlPointsB[i] }))
                    .Concat(new[] { new { ControlBefore = endControl, Point = end, ControlAfter = default(Pt) } })
                    .SelectConsecutivePairs(false, (one, two) => bézier(one.Point, one.ControlAfter, two.ControlBefore, two.Point, bézierSteps))
                    .SelectMany((x, i) => i == 0 ? x : x.Skip(1))
                    .ToArray();
                    return(toMesh(createFaces(false, true, tubeFromCurve(points, thickness, tubeRevSteps))));
                }
            }

            var partialWire = new Func <IEnumerable <CPC>, IEnumerable <VertexInfo[]> >(pts =>
            {
                var points = pts
                             .SelectConsecutivePairs(false, (one, two) => bézier(one.Point, one.ControlAfter, two.ControlBefore, two.Point, bézierSteps))
                             .SelectMany((x, i) => i == 0 ? x : x.Skip(1))
                             .ToArray();

                var reserveForCopper = 6;
                var discardCopper    = 2;

                if (piece == WirePiece.Cut)
                {
                    var tube      = tubeFromCurve(points, thickness, tubeRevSteps).SkipLast(reserveForCopper).ToArray();
                    var capCenter = points[points.Length - 1 - reserveForCopper];
                    var normal    = capCenter - points[points.Length - 2 - reserveForCopper];
                    var cap       = tube[tube.Length - 1].SelectConsecutivePairs(true, (v1, v2) => new[] { capCenter, v2.Point, v1.Point }.Select(p => new VertexInfo {
                        Point = p, Normal = normal
                    }).ToArray()).ToArray();
                    return(createFaces(false, true, tube).Concat(cap));
                }
                else
                {
                    var copper          = tubeFromCurve(points.TakeLast(reserveForCopper + 2).SkipLast(discardCopper).ToArray(), thickness / 2, tubeRevSteps).Skip(1).ToArray();
                    var copperCapCenter = points[points.Length - 1 - discardCopper];
                    var copperNormal    = copperCapCenter - points[points.Length - 2];
                    var copperCap       = copper[copper.Length - 1].SelectConsecutivePairs(true, (v1, v2) => new[] { copperCapCenter, v2.Point, v1.Point }.Select(p => new VertexInfo {
                        Point = p, Normal = copperNormal
                    }).ToArray()).ToArray();
                    return(createFaces(false, true, copper).Concat(copperCap));
                }
            });

            var           cutOffEarly  = false;// rnd.Next(2) == 0;
            var           angleForward = rnd.Next(2) == 0;
            var           rotAngle     = (rnd.NextDouble() * 10 + 1) * (angleForward ? -1 : 1);
            var           rotAxisStart = start;
            var           rotAxisEnd   = startControl;
            Func <Pt, Pt> rot          = p => p.Rotate(rotAxisStart, rotAxisEnd, rotAngle);
            var           beforeCut    =
                new[] { new CPC {
                            ControlBefore = default(Pt), Point = start, ControlAfter = startControl
                        } }
            .Concat(interpolatedPoints.Take((cutOffEarly ? numSegments : numSegments + 1) / 2).Select((p, i) => new CPC {
                ControlBefore = rot(controlPointsA[i]), Point = rot(p), ControlAfter = rot(controlPointsB[i])
            }));
            var bcTube = partialWire(beforeCut);

            var cutOffPoint = (cutOffEarly ? numSegments - 2 : numSegments - 1) / 2;

            rotAngle     = (rnd.NextDouble() * 10 + 1) * (angleForward ? -1 : 1);
            rotAxisStart = end;
            rotAxisEnd   = endControl;
            var afterCut =
                new[] { new CPC {
                            ControlBefore = default(Pt), Point = end, ControlAfter = endControl
                        } }
            .Concat(interpolatedPoints.Skip(cutOffPoint).Select((p, i) => new CPC {
                ControlBefore = rot(controlPointsB[i + cutOffPoint]), Point = rot(p), ControlAfter = rot(controlPointsA[i + cutOffPoint])
            }).Reverse());
            var acTube = partialWire(afterCut);

            return(toMesh(bcTube.Concat(acTube).ToArray()));
        }
 private static IEnumerable <Pt> bézier(Pt start, Pt control1, Pt control2, Pt end, int steps)
 {
     return(Enumerable.Range(0, steps)
            .Select(i => (double)i / (steps - 1))
            .Select(t => pow(1 - t, 3) * start + 3 * pow(1 - t, 2) * t * control1 + 3 * (1 - t) * t * t * control2 + pow(t, 3) * end));
 }