mvaganov

Wires.cs

Sep 16th, 2022 (edited)
365
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 65.42 KB | Source Code | 0 0
  1. using System;
  2. using UnityEngine;
  3. using System.Collections.Generic;
  4. using Debug = UnityEngine.Debug;
  5. #if UNITY_EDITOR
  6. using System.Diagnostics;
  7. #endif
  8.  
  9. // license: Copyfree, public domain. This is free code! Great artists, steal this code!
  10. namespace NonStandard {
  11.     /// <summary>
  12.     /// static functions for Unity's <see cref="LineRenderer"/>. Creates visualizations for <see cref="Vector3"/> math.
  13.     /// This library isn't optimized for performance, it's built to make math less invisible, even at compiled runtime.
  14.     /// <see cref="Wires"/> can also create <see cref="Wire"/> objects, which have runtime optimizations to reduce
  15.     /// calculation and simplify (re)drawing with a more ergonomic API.
  16.     /// This file could use some slimming down (get rid of "unuseful" functionality).
  17.     /// </summary>
  18.     public class Wires : MonoBehaviour {
  19.         /// <summary>
  20.         /// how many times wider the arrow-head base is than the <see cref="LineRenderer"/>'s end point.
  21.         /// </summary>
  22.         public const float ARROW_SIZE = 3;
  23.         /// <summary>
  24.         /// default line width of the <see cref="LineRenderer"/>
  25.         /// </summary>
  26.         public const float LINE_WIDTH = 1f / 8;
  27.         /// <summary>
  28.         /// used for end width variables, to duplicate start width.
  29.         /// </summary>
  30.         public const float SAME_AS_START_SIZE = -1;
  31.  
  32.         [Tooltip("automatically puts new Wires into this global Wires object")]
  33.         public bool autoParentWiresToGlobalObject = true;
  34.         [Tooltip("a line constantly being reassigned the same color will gently pulse instead (as a performance warning")]
  35.         public bool pulseConstantlyReassignedColor = true;
  36.         [Tooltip("Used to draw lines. Ideally a white Sprites/Default shader."), SerializeField]
  37.         private Material _wireMaterial;
  38.  
  39.         /// <summary>
  40.         /// the Material used by all new Wires. will generate default primitive material if unspecified
  41.         /// </summary>
  42.         public static Material _defaultMaterial;
  43.         /// <summary>
  44.         /// the dictionary of named <see cref="Wire"/>s.
  45.         /// allows <see cref="Wires"/> to create lines without explicit variables of type <see cref="Wire"/>.
  46.         /// </summary>
  47.         private static readonly Dictionary<string, GameObject> NamedObject = new Dictionary<string, GameObject>();
  48.         /// <summary>
  49.         /// The singleton instance
  50.         /// </summary>
  51.         private static Wires _instance;
  52.         /// <summary>
  53.         /// a wireframe that looks like a map pin. useful as a visual proxy for a <see cref="Transform"/>
  54.         /// </summary>
  55.         private static Vector3[] _mapPinPointsBase;
  56.  
  57.         public static Wires Instance => (_instance) ? _instance : _instance = FindGlobalComponentInstance<Wires>();
  58.  
  59.         public static Material DefaultMaterial {
  60.             get {
  61.                 if (_defaultMaterial != null) return _defaultMaterial;
  62.                 GameObject primitive = GameObject.CreatePrimitive(PrimitiveType.Plane);
  63.                 _defaultMaterial = primitive.GetComponent<MeshRenderer>().sharedMaterial;
  64.                 DestroyImmediate(primitive);
  65.                 return _defaultMaterial;
  66.             }
  67.         }
  68.  
  69.         public static Material WireMaterial {
  70.             get {
  71.                 Wires wires = Instance;
  72.                 if (wires._wireMaterial != null) return wires._wireMaterial;
  73.                 const string colorShaderName = "Sprites/Default";//"Unlit/Color";
  74.                 wires._wireMaterial = FindShaderMaterial(colorShaderName);
  75.                 return wires._wireMaterial;
  76.             }
  77.         }
  78.  
  79.         public static Material FindShaderMaterial(string shaderName) {
  80.             Shader s = Shader.Find(shaderName);
  81.             if (!s) {
  82.                 throw new Exception($"Missing shader: {shaderName}. Please make sure it is in a \"Resources\" folder, or " +
  83.                     $"used by at least one object in the build. Ideally, assign in material of {nameof(Wires)} in Hierarchy.");
  84.             }
  85.             return new Material(s);
  86.         }
  87.  
  88.         /// <summary>
  89.         /// finds <see cref="Component"/> of type T. if none exist, makes a new <see cref="GameObject"/> and adds T to it
  90.         /// </summary>
  91.         public static T FindGlobalComponentInstance<T>() where T : Component {
  92.             T instance;
  93.             if ((instance = FindObjectOfType(typeof(T)) as T) != null) return instance;
  94.             GameObject g = new GameObject($"<{typeof(T).Name}>");
  95.             instance = g.AddComponent<T>();
  96.             return instance;
  97.         }
  98.  
  99.         private void Start() {
  100.             if (_instance == null || _instance == this) { return; }
  101.             Debug.LogWarning($"<{typeof(Wires).Name}> should be a singleton. Deleting extra {name}");
  102.             Destroy(this);
  103.         }
  104.  
  105.         /// <returns>null if named object is missing, or a <see cref="LineRenderer"/> object with the given name</returns>
  106.         /// <param name="createIfNotFound">if true, this function will create a missing <see cref="LineRenderer"/></param>
  107.         public static GameObject Get(string name, bool createIfNotFound = false) {
  108.             if ((NamedObject.TryGetValue(name, out GameObject go) && go) || !createIfNotFound) return go;
  109.             go = NamedObject[name] = MakeLineRenderer(ref go).gameObject;
  110.             go.name = name;
  111.             return go;
  112.         }
  113.  
  114.         public static LineRenderer MakeLineRenderer(ref GameObject lineObject) {
  115.             if (!lineObject) {
  116.                 lineObject = new GameObject();
  117.                 if (Instance.autoParentWiresToGlobalObject) {
  118.                     lineObject.transform.SetParent(_instance.transform);
  119.                 }
  120.             }
  121.             return MakeLineRenderer(lineObject);
  122.         }
  123.  
  124.         public static LineRenderer MakeLineRenderer(GameObject lineObject) {
  125.             LineRenderer lr = lineObject.GetComponent<LineRenderer>();
  126.             if (!lr) { lr = lineObject.AddComponent<LineRenderer>(); }
  127.             return lr;
  128.         }
  129.  
  130.         /// <returns>an unnamed, unmanaged <see cref="Wire"/> object. more performant than constantly calling
  131.         /// <see cref="Get"/> or <see cref="Make(string, bool)"/>, but only if you do it once!</returns>
  132.         public static Wire MakeWire(string wirename = null) {
  133.             GameObject go = null;
  134.             MakeLineRenderer(ref go);
  135.             if (!string.IsNullOrEmpty(wirename)) { go.name = wirename; }
  136.             Wire wire = go.GetComponent<Wire>();
  137.             if (!wire) { wire = go.AddComponent<Wire>(); wire.RefreshSource(); }
  138.             go.layer = LayerMask.NameToLayer("UI");
  139.             return wire;
  140.         }
  141.  
  142.         /// <returns>
  143.         /// a <see cref="Wire"/> with the given name, or null if createIfNotFound is false and the object doesn't exist
  144.         /// </returns>
  145.         public static Wire Make(string name, bool createIfNotFound = true) {
  146.             GameObject go = Get(name, createIfNotFound);
  147.             if (go == null) return null;
  148.             Wire wire = go.GetComponent<Wire>();
  149.             if (!wire) { wire = go.AddComponent<Wire>(); wire.RefreshSource(); }
  150.             return wire;
  151.         }
  152.  
  153.         /// <summary>Make the specified line from a list of points. does not make a <see cref="Wire"/>.</summary>
  154.         /// <returns>The <see cref="LineRenderer"/> hosting the line points</returns>
  155.         /// <param name="wireObject"><see cref="GameObject"/> will have a <see cref="LineRenderer"/> and added</param>
  156.         /// <param name="points"><see cref="List"/> of <see cref="Vector3"/> coordinates</param>
  157.         /// <param name="pointCount">will use all points if -1</param>
  158.         /// <param name="startSize">How wide the line is at the start</param>
  159.         /// <param name="endSize">How wide the line is at the end</param>
  160.         public static LineRenderer MakeLine(ref GameObject wireObject, IList<Vector3> points, Color color = default,
  161.         int pointCount = -1, float startSize = LINE_WIDTH, float endSize = SAME_AS_START_SIZE) {
  162.             LineRenderer lr = MakeLineRenderer(ref wireObject);
  163.             return MakeLine(lr, points, color, pointCount, startSize, endSize);
  164.         }
  165.  
  166.         public static LineRenderer MakeLine(LineRenderer lr, IList<Vector3> points, Color color = default,
  167.         int pointCount = -1, float startSize = LINE_WIDTH, float endSize = SAME_AS_START_SIZE) {
  168.             SetLine(lr, color, startSize, endSize);
  169.             return MakeLine(lr, points, pointCount);
  170.         }
  171.  
  172.         public static LineRenderer MakeLine(LineRenderer lr, IList<Vector3> points, int pointCount) {
  173.             if (pointCount < 0) { pointCount = points.Count; }
  174.             lr.positionCount = pointCount;
  175.             for (int i = 0; i < pointCount; ++i) { lr.SetPosition(i, points[i]); }
  176.             return lr;
  177.         }
  178.  
  179.         public static LineRenderer MakeLine(LineRenderer lr, IList<Vector3> points, Color color, float startSize,
  180.         float endSize, LineEnd lineEnds) {
  181.             if (Math3d.EQ2(endSize, SAME_AS_START_SIZE)) { endSize = startSize; }
  182.             if (points == null) { lr = MakeLine(lr, null, color, 0, startSize, endSize); return lr; }
  183.             if (lineEnds == LineEnd.Arrow || lineEnds == LineEnd.ArrowBothEnds) {
  184.                 ApplyArrowToLine(ref lr, points, color, startSize, endSize, lineEnds);
  185.             } else {
  186.                 lr = MakeLine(lr, points, color, points.Count, startSize, endSize);
  187.                 FlattenKeyFrame(lr);
  188.             }
  189.             if (lr.loop && lineEnds != LineEnd.Normal) { lr.loop = false; }
  190.             return lr;
  191.         }
  192.  
  193.         public static void ApplyArrowToLine(ref LineRenderer lineRendr, IList<Vector3> points, Color color,
  194.         float startSize, float endSize, LineEnd lineEnds) {
  195.             Keyframe[] keyframes = CalculateArrowKeyframes(points, points.Count, out var line, startSize, endSize);
  196.             lineRendr = MakeArrow(lineRendr, line, line.Length, color, startSize, endSize);
  197.             lineRendr.widthCurve = new AnimationCurve(keyframes);
  198.             if (lineEnds != LineEnd.ArrowBothEnds) { return; }
  199.             ReverseLineInternal(ref lineRendr);
  200.             Vector3[] p = new Vector3[lineRendr.positionCount];
  201.             lineRendr.GetPositions(p);
  202.             lineRendr = MakeArrow(lineRendr, p, p.Length, color, endSize, startSize, ARROW_SIZE, lineRendr.widthCurve.keys);
  203.             ReverseLineInternal(ref lineRendr);
  204.         }
  205.  
  206.         /// <summary>
  207.         /// replaces a <see cref="LineRenderer"/>'s <see cref="Keyframe"/>s to be a flat line equal to element 0
  208.         /// </summary>
  209.         public static void FlattenKeyFrame(LineRenderer lineRenderer) {
  210.             Keyframe[] keys = lineRenderer.widthCurve.keys;
  211.             if (keys != null && keys.Length > 2) {
  212.                 lineRenderer.widthCurve = new AnimationCurve(new Keyframe[] { keys[0], keys[keys.Length - 1] });
  213.             }
  214.         }
  215.  
  216.         public static LineRenderer SetLine(LineRenderer lr, Color color, float startSize, float endSize) {
  217.             lr.startWidth = startSize;
  218.             if (Math3d.EQ2(endSize, SAME_AS_START_SIZE)) { endSize = startSize; }
  219.             lr.endWidth = endSize;
  220.             SetColor(lr, color);
  221.             return lr;
  222.         }
  223.  
  224.         /// <summary>
  225.         /// it's possible to call this method from an Update method and not realize it. doing this would cause a slight
  226.         /// performance issue. if <see cref="pulseConstantlyReassignedColor"/> is true, that is noticable.
  227.         /// </summary>
  228.         public static void SetColor(LineRenderer lineRenderer, Color color) {
  229.             bool needsMaterial = lineRenderer.material == null || lineRenderer.material.name.StartsWith("Default-Material");
  230.             if (needsMaterial) { lineRenderer.material = WireMaterial; }
  231.             if (color == default) { color = Color.magenta; }
  232.             if (lineRenderer.material.color == color && (_instance == null || _instance.pulseConstantlyReassignedColor)) {
  233.                 long t = Math.Abs(Environment.TickCount);
  234.                 const long duration = 500;
  235.                 long secComponent = t % duration;
  236.                 float a = Mathf.Abs((2f * secComponent - duration) / duration);
  237.                 Color.RGBToHSV(color, out float h, out float s, out float v);
  238.                 color = Color.HSVToRGB(h, s + (a * .25f), v + (a * .25f));
  239.             }
  240.             lineRenderer.material.color = color;
  241.         }
  242.  
  243.         /// <summary>Makes a circle with a <see cref="LineRenderer"/></summary>
  244.         /// <param name="lineObj">GameObject host of the <see cref="LineRenderer"/></param>
  245.         /// <param name="color">Color of the line</param>
  246.         /// <param name="center"><see cref="Vector3"/> coordinate</param>
  247.         /// <param name="normal">Which way the circle is facing</param>
  248.         /// <param name="pointCount">How many points to use for the circle. If zero, will do 24*PI*r</param>
  249.         /// <param name="lineSize">The width of the line</param>
  250.         public static LineRenderer MakeCircle(ref GameObject lineObj, Vector3 center = default, Vector3 normal = default,
  251.         Color color = default, float radius = 0.5f, int pointCount = 0, float lineSize = LINE_WIDTH) {
  252.             Vector3[] points = null;
  253.             Math3d.WriteCircle(ref points, center, normal, radius, pointCount);
  254.             LineRenderer lr = Wires.MakeLine(ref lineObj, points, color, points.Length, lineSize, lineSize);
  255.             lr.loop = true;
  256.             return lr;
  257.         }
  258.  
  259.         public static LineRenderer MakeSphere(string name, float radius = 0.5f, Vector3 center = default,
  260.         Color color = default, float lineSize = LINE_WIDTH) {
  261.             GameObject go = Get(name, true);
  262.             return MakeSphere(ref go, radius, center, color, lineSize);
  263.         }
  264.  
  265.         /// <returns>a <see cref="LineRenderer"/> in the shape of a sphere made of 3 circles, for the x.y.z axis</returns>
  266.         public static LineRenderer MakeSphere(ref GameObject lineObj, float radius = 0.5f, Vector3 center = default,
  267.         Color color = default, float lineSize = LINE_WIDTH) {
  268.             Vector3[] circles = new Vector3[24 * 3];
  269.             Math3d.WriteArc(ref circles, 24, Vector3.forward, Vector3.up, 360, center, 24 * 0);
  270.             Math3d.WriteArc(ref circles, 24, Vector3.right, Vector3.up, 360, center, 24 * 1);
  271.             Math3d.WriteArc(ref circles, 24, Vector3.up, Vector3.forward, 360, center, 24 * 2);
  272.             if (Math3d.EQ2(radius, 1)) { for (int i = 0; i < circles.Length; ++i) { circles[i] *= radius; } }
  273.             return Wires.MakeLine(ref lineObj, circles, color, circles.Length, lineSize, lineSize);
  274.         }
  275.  
  276.         public static LineRenderer MakeBox(ref GameObject lineObj, Vector3 center,
  277.             Vector3 size, Quaternion rotation, Color color = default, float lineSize = LINE_WIDTH) {
  278.             Vector3 y = Vector3.up / 2 * size.y;
  279.             Vector3 x = Vector3.right / 2 * size.x;
  280.             Vector3 z = Vector3.forward / 2 * size.z;
  281.             Vector3[] line = new Vector3[] {
  282.                  z+y-x, -z+y-x, -z-y-x, -z-y+x, -z+y+x,  z+y+x,  z-y+x,  z-y-x,
  283.                  z+y-x,  z+y+x,  z-y+x, -z-y+x, -z+y+x, -z+y-x, -z-y-x,  z-y-x
  284.             };
  285.             for (int i = 0; i < line.Length; ++i) { line[i] = rotation * line[i] + center; }
  286.             LineRenderer lr = MakeLine(ref lineObj, line, color, line.Length, lineSize, lineSize);
  287.             lr.numCornerVertices = 4;
  288.             return lr;
  289.         }
  290.  
  291.         public static LineRenderer MakeMapPin(string name, Color c = default, float size = 1,
  292.         float lineSize = LINE_WIDTH) {
  293.             GameObject go = Get(name, true);
  294.             return MakeMapPin(ref go, c, size, lineSize);
  295.         }
  296.  
  297.         /// <summary>Draws a "map pin", which shows a visualization for direction and orientation</summary>
  298.         /// <returns><see cref="LineRenderer"/> hosting the map pin line. adjust the transform to move it!</returns>
  299.         /// <param name="size">Size: radius of the map pin</param>
  300.         /// <param name="lineSize">Line width.</param>
  301.         public static LineRenderer MakeMapPin(ref GameObject lineObj, Color color = default, float size = 1,
  302.         float lineSize = LINE_WIDTH) {
  303.             if (_mapPinPointsBase == null) {
  304.                 GenerateMapPin();
  305.             }
  306.             Vector3[] mapPinPoints = (size == 1) ? _mapPinPointsBase : new Vector3[_mapPinPointsBase.Length];
  307.             if (size != 1) {
  308.                 for (int i = 0; i < _mapPinPointsBase.Length; ++i) {
  309.                     mapPinPoints[i] = _mapPinPointsBase[i] * size;
  310.                 }
  311.             }
  312.             LineRenderer lr = Wires.MakeLine(ref lineObj, mapPinPoints, color, mapPinPoints.Length, lineSize, lineSize);
  313.             lr.useWorldSpace = false;
  314.             return lr;
  315.         }
  316.  
  317.         private static void GenerateMapPin() {
  318.             const float epsilon = 1 / 1024.0f;
  319.             Vector3 pos = Vector3.zero, forward = Vector3.forward, right = Vector3.right, up = Vector3.up;
  320.             const float startAngle = (360.0f / 4) - (360.0f / 32);
  321.             Vector3 v = Quaternion.AngleAxis(startAngle, up) * forward;
  322.             Math3d.WriteArc(ref _mapPinPointsBase, 32, up, v, 360, pos);
  323.             Vector3 tip = pos + forward * Mathf.Sqrt(2);
  324.             _mapPinPointsBase[0] = _mapPinPointsBase[_mapPinPointsBase.Length - 1];
  325.             int m = (32 * 5 / 8);
  326.             _mapPinPointsBase[++m] = _mapPinPointsBase[m] + (tip - _mapPinPointsBase[m]) * (1 - epsilon);
  327.             _mapPinPointsBase[++m] = tip;
  328.             int n = (32 * 7 / 8) + 1;
  329.             while (n < 32) { _mapPinPointsBase[++m] = _mapPinPointsBase[n++]; }
  330.             Vector3 side = pos + right;
  331.             _mapPinPointsBase[++m] = _mapPinPointsBase[m] + (side - _mapPinPointsBase[m]) * (1 - epsilon);
  332.             _mapPinPointsBase[++m] = pos + right;
  333.             _mapPinPointsBase[++m] = pos + right * epsilon;
  334.             _mapPinPointsBase[++m] = pos;
  335.             _mapPinPointsBase[++m] = pos + up * (1 - epsilon);
  336.             _mapPinPointsBase[++m] = pos + up;
  337.         }
  338.  
  339.         public static LineRenderer SetMapPin(string name, Transform t, Color c = default, float size = 1,
  340.         float lineWidth = LINE_WIDTH) {
  341.             GameObject go = Get(name, true);
  342.             return SetMapPin(ref go, t, c, size, lineWidth);
  343.         }
  344.  
  345.         /// <summary>Draws a "map pin", which shows a visualization for direction and orientation</summary>
  346.         /// <returns>The LineRenderer hosting the map pin line</returns>
  347.         /// <param name="parent">t: the transform to attach the map pin visualisation to</param>
  348.         /// <param name="size">Size: radius of the map pin</param>
  349.         public static LineRenderer SetMapPin(ref GameObject lineObj, Transform parent, Color color = default,
  350.         float size = 1, float lineWidth = LINE_WIDTH) {
  351.             LineRenderer line_ = MakeMapPin(ref lineObj, color, size, lineWidth);
  352.             Transform transform = line_.transform;
  353.             transform.SetParent(parent);
  354.             transform.localPosition = Vector3.zero;
  355.             transform.localRotation = Quaternion.identity;
  356.             return line_;
  357.         }
  358.  
  359.         /// <returns>a line renderer in the shape of a spiraling sphere, spiraling about the Vector3.up axis</returns>
  360.         public static LineRenderer MakeSpiralSphere(ref GameObject lineObj, float radius = 1,
  361.             Vector3 center = default, Quaternion rotation = default, Color color = default, float lineSize = LINE_WIDTH) {
  362.             Vector3[] vertices = Math3d.CreateSpiralSphere(center, radius, rotation, 24, 3);
  363.             return MakeLine(ref lineObj, vertices, color, vertices.Length, lineSize, lineSize);
  364.         }
  365.  
  366.         public static LineRenderer MakeArrow(ref GameObject lineObject, Vector3 start, Vector3 end, Color color = default,
  367.         float startSize = LINE_WIDTH, float endSize = SAME_AS_START_SIZE, float arrowHeadSize = ARROW_SIZE) {
  368.             return MakeArrow(ref lineObject, new Vector3[] { start, end }, 2, color, startSize, endSize, arrowHeadSize);
  369.         }
  370.  
  371.         public static LineRenderer MakeArrow(ref GameObject lineObject, IList<Vector3> points, int pointCount,
  372.         Color color = default, float startSize = LINE_WIDTH, float endSize = SAME_AS_START_SIZE,
  373.         float arrowHeadSize = ARROW_SIZE, Keyframe[] lineKeyFrames = null) {
  374.             LineRenderer lr = MakeLineRenderer(ref lineObject);
  375.             return MakeArrow(lr, points, pointCount, color, startSize, endSize, arrowHeadSize, lineKeyFrames);
  376.         }
  377.  
  378.         public static LineRenderer MakeArrow(LineRenderer lr, IList<Vector3> points, int pointCount,
  379.                 Color color = default, float startSize = LINE_WIDTH, float endSize = SAME_AS_START_SIZE,
  380.                 float arrowHeadSize = ARROW_SIZE, Keyframe[] lineKeyFrames = null) {
  381.             Keyframe[] keyframes = CalculateArrowKeyframes(points, pointCount, out Vector3[] line, startSize, endSize,
  382.                 arrowHeadSize, lineKeyFrames);
  383.             MakeLine(lr, line, color, line.Length, startSize, endSize);
  384.             lr.widthCurve = new AnimationCurve(keyframes);
  385.             return lr;
  386.         }
  387.  
  388.         public static Keyframe[] CalculateArrowKeyframes(IList<Vector3> points, int pointCount, out Vector3[] line,
  389.         float startSize = LINE_WIDTH, float endSize = SAME_AS_START_SIZE, float arrowHeadSize = ARROW_SIZE,
  390.         Keyframe[] lineKeyFrames = null) {
  391.             float arrowSize = endSize * arrowHeadSize;
  392.             int lastGoodIndex = 0;
  393.             Vector3 arrowheadBase = Vector3.zero;
  394.             const float distanceBetweenArrowBaseAndWidePoint = 1.0f / 512;
  395.             Vector3 delta, dir = Vector3.zero;
  396.             // find where, in the list of points, to place the arrowhead
  397.             float dist = 0;
  398.             int lastPoint = pointCount - 1;
  399.             for (int i = lastPoint; i > 0; --i) { // go backwards (from the pointy end)
  400.                 float d = Vector3.Distance(points[i], points[i - 1]);
  401.                 dist += d;
  402.                 // if the arrow direction hasn't been calculated and sufficient distance for the arrowhead has been passed
  403.                 if (dir == Vector3.zero && dist >= arrowSize) {
  404.                     // calculate w,here the arrowheadBase should be (requires 2 points) based on the direction of this segment
  405.                     lastGoodIndex = i - 1;
  406.                     delta = points[i] - points[i - 1];
  407.                     dir = delta.normalized;
  408.                     float extraFromLastGoodIndex = dist - arrowSize;
  409.                     arrowheadBase = points[lastGoodIndex] + dir * extraFromLastGoodIndex;
  410.                 }
  411.             }
  412.             // if the line is not long enough for an arrow head, make the whole thing an arrowhead
  413.             if (dist <= arrowSize) {
  414.                 line = new Vector3[] { points[0], points[lastPoint] };
  415.                 return new Keyframe[] { new Keyframe(0, arrowSize), new Keyframe(1, 0) };
  416.             }
  417.             delta = points[lastPoint] - arrowheadBase;
  418.             dir = delta.normalized;
  419.             Vector3 arrowheadWidest = arrowheadBase + dir * (dist * distanceBetweenArrowBaseAndWidePoint);
  420.             line = new Vector3[lastGoodIndex + 4];
  421.             for (int i = 0; i <= lastGoodIndex; i++) {
  422.                 line[i] = points[i];
  423.             }
  424.             line[lastGoodIndex + 3] = points[lastPoint];
  425.             line[lastGoodIndex + 2] = arrowheadWidest;
  426.             line[lastGoodIndex + 1] = arrowheadBase;
  427.             Keyframe[] keyframes;
  428.             float arrowHeadBaseStart = 1 - arrowSize / dist;
  429.             float arrowHeadBaseWidest = 1 - (arrowSize / dist - distanceBetweenArrowBaseAndWidePoint);
  430.             if (lineKeyFrames == null) {
  431.                 keyframes = new Keyframe[] {
  432.                     new Keyframe(0, startSize), new Keyframe(arrowHeadBaseStart, endSize),
  433.                     new Keyframe(arrowHeadBaseWidest, arrowSize), new Keyframe(1, 0)
  434.                 };
  435.             } else {
  436.                 // count how many there are after arrowHeadBaseStart.
  437.                 int validCount = lineKeyFrames.Length;
  438.                 for (int i = 0; i < lineKeyFrames.Length; ++i) {
  439.                     float t = lineKeyFrames[i].time;
  440.                     if (t > arrowHeadBaseStart) { validCount = i; break; }
  441.                 }
  442.                 // those are irrelevant now. they'll be replaced by the 3 extra points
  443.                 keyframes = new Keyframe[validCount + 3];
  444.                 for (int i = 0; i < validCount; ++i) { keyframes[i] = lineKeyFrames[i]; }
  445.                 keyframes[validCount + 0] = new Keyframe(arrowHeadBaseStart, endSize);
  446.                 keyframes[validCount + 1] = new Keyframe(arrowHeadBaseWidest, arrowSize);
  447.                 keyframes[validCount + 2] = new Keyframe(1, 0);
  448.             }
  449.             return keyframes;
  450.         }
  451.  
  452.         public static LineRenderer MakeArrowBothEnds(ref GameObject lineObject, Vector3 start, Vector3 end,
  453.         Color color = default, float startSize = LINE_WIDTH, float endSize = SAME_AS_START_SIZE,
  454.         float arrowHeadSize = ARROW_SIZE) {
  455.             Vector3[] points = new Vector3[] { end, start };
  456.             return MakeArrowBothEnds(ref lineObject, points, points.Length, color, startSize, endSize, arrowHeadSize);
  457.         }
  458.         public static LineRenderer MakeArrowBothEnds(ref GameObject lineObject, IList<Vector3> points, int pointCount,
  459.         Color color = default, float startSize = LINE_WIDTH, float endSize = SAME_AS_START_SIZE,
  460.         float arrowHeadSize = ARROW_SIZE) {
  461.             LineRenderer lr = MakeArrow(ref lineObject, points, pointCount, color, startSize, endSize, arrowHeadSize, null);
  462.             ReverseLineInternal(ref lr);
  463.             Vector3[] p = new Vector3[lr.positionCount];
  464.             lr.GetPositions(p);
  465.             lr = MakeArrow(ref lineObject, p, p.Length, color, endSize, startSize, arrowHeadSize, lr.widthCurve.keys);
  466.             ReverseLineInternal(ref lr);
  467.             return lr;
  468.         }
  469.  
  470.         public static LineRenderer ReverseLineInternal(ref LineRenderer lr) {
  471.             Vector3[] p = new Vector3[lr.positionCount];
  472.             lr.GetPositions(p);
  473.             Array.Reverse(p);
  474.             lr.SetPositions(p);
  475.             AnimationCurve widthCurve = lr.widthCurve;
  476.             if (widthCurve != null && widthCurve.length > 1) {
  477.                 Keyframe[] kf = new Keyframe[widthCurve.keys.Length];
  478.                 Keyframe[] okf = widthCurve.keys;
  479.                 Array.Copy(okf, kf, okf.Length); //for(int i = 0; i<kf.Length; ++i) { kf[i]=okf[i]; }
  480.                 Array.Reverse(kf);
  481.                 for (int i = 0; i < kf.Length; ++i) { kf[i].time = 1 - kf[i].time; }
  482.                 lr.widthCurve = new AnimationCurve(kf);
  483.             }
  484.             return lr;
  485.         }
  486.  
  487.         public static LineRenderer MakeArcArrow(ref GameObject lineObj, float angle, int pointCount,
  488.         Vector3 arcPlaneNormal = default, Vector3 firstPoint = default, Vector3 center = default, Color color = default,
  489.         float startSize = LINE_WIDTH, float endSize = SAME_AS_START_SIZE, float arrowHeadSize = ARROW_SIZE) {
  490.             if (arcPlaneNormal == default) { arcPlaneNormal = Vector3.up; }
  491.             if (center == default && firstPoint == default) { firstPoint = Vector3.right; }
  492.             Vector3[] points = null;
  493.             Math3d.WriteArc(ref points, pointCount, arcPlaneNormal, firstPoint, angle, center);
  494.             return MakeArrow(ref lineObj, points, pointCount, color, startSize, endSize, arrowHeadSize);
  495.         }
  496.  
  497.         public static LineRenderer MakeArcArrowBothEnds(ref GameObject lineObj, float angle, int pointCount,
  498.         Vector3 arcPlaneNormal = default, Vector3 firstPoint = default, Vector3 center = default, Color color = default,
  499.         float startSize = LINE_WIDTH, float endSize = SAME_AS_START_SIZE, float arrowHeadSize = ARROW_SIZE) {
  500.             LineRenderer lr = MakeArcArrow(ref lineObj, angle, pointCount, arcPlaneNormal, firstPoint, center, color,
  501.                 startSize, endSize, arrowHeadSize);
  502.             ReverseLineInternal(ref lr);
  503.             Vector3[] p = new Vector3[lr.positionCount];
  504.             lr.GetPositions(p);
  505.             lr = MakeArrow(ref lineObj, p, p.Length, color, endSize, startSize, arrowHeadSize, lr.widthCurve.keys);
  506.             ReverseLineInternal(ref lr);
  507.             return lr;
  508.         }
  509.  
  510.         public static LineRenderer MakeArcArrow(ref GameObject lineObject, Vector3 start, Vector3 end,
  511.         Color color = default, float angle = 90, Vector3 upNormal = default, float startSize = LINE_WIDTH,
  512.         float endSize = SAME_AS_START_SIZE, float arrowHeadSize = ARROW_SIZE, int pointCount = 0) {
  513.             Vector3[] arc;
  514.             if (end == start || Mathf.Abs(angle) >= 360) {
  515.                 arc = new Vector3[] { start, end };
  516.             } else {
  517.                 if (upNormal == default) { upNormal = Vector3.up; }
  518.                 if (pointCount == 0) { pointCount = Mathf.Max((int)(angle * 24 / 180) + 1, 2); }
  519.                 arc = new Vector3[pointCount];
  520.                 Vector3 delta = end - start;
  521.                 float dist = delta.magnitude;
  522.                 Vector3 dir = delta / dist;
  523.                 Vector3 right = Vector3.Cross(upNormal, dir).normalized;
  524.                 Math3d.WriteArc(ref arc, pointCount, right, -upNormal, angle);
  525.                 Vector3 arcDelta = arc[arc.Length - 1] - arc[0];
  526.                 float arcDist = arcDelta.magnitude;
  527.                 float angleDiff = Vector3.Angle(arcDelta / arcDist, delta / dist);
  528.                 Quaternion turn = Quaternion.AngleAxis(angleDiff, right);
  529.                 float ratio = dist / arcDist;
  530.                 for (int i = 0; i < arc.Length; ++i) { arc[i] = (turn * arc[i]) * ratio; }
  531.                 Vector3 offset = start - arc[0];
  532.                 for (int i = 0; i < arc.Length; ++i) { arc[i] += offset; }
  533.             }
  534.             return MakeArrow(ref lineObject, arc, arc.Length, color, startSize, endSize, arrowHeadSize);
  535.         }
  536.  
  537.         public static void MakeQuaternion(ref GameObject axisObj, Wire[] childWire, Vector3 axis, float angle,
  538.         Vector3 position = default, Color color = default, Quaternion orientation = default, int arcPoints = -1,
  539.         float lineSize = LINE_WIDTH, float arrowHeadSize = ARROW_SIZE, Vector3[] startPoint = null) {
  540.             if (childWire.Length != startPoint.Length) {
  541.                 throw new Exception("childWire and startPoint should be parallel arrays");
  542.             }
  543.             while (angle >= 180) { angle -= 360; }
  544.             while (angle < -180) { angle += 360; }
  545.             Vector3 axisRotated = orientation * axis;
  546.             MakeArrow(ref axisObj, position - axisRotated, position + axisRotated, color, lineSize, lineSize, arrowHeadSize);
  547.             for (int i = 0; i < childWire.Length; ++i) {
  548.                 Wire aObj = childWire[i];
  549.                 aObj.Arc(angle, axisRotated, startPoint[i], position, color, LineEnd.Arrow, arcPoints, lineSize);
  550.                 childWire[i] = aObj;
  551.             }
  552.         }
  553.  
  554.         public static void MakeCartesianPlane(Vector3 center, Vector3 up, Vector3 right, Wire[] wires,
  555.         Color color = default, float lineWidth = LINE_WIDTH, float size = .5f, float increments = 0.125f,
  556.         Vector3 offset = default) {
  557.             // prep data structures
  558.             int wireCount = _CartesianPlaneChildCount(size, increments, out int thinLines);
  559.             while (wires.Length < wireCount) {
  560.                 throw new Exception($"can't make {wireCount} wires with {wires.Length} slots");
  561.             }
  562.             Vector3[] endPoints = new Vector3[2];
  563.             // prep math
  564.             Vector3 minX = right * -size, maxX = right * size;
  565.             Vector3 minY = up * -size, maxY = up * size;
  566.             Vector3 p = center + offset;
  567.             int index = 1;
  568.             float thinLineWidth = lineWidth / 4;
  569.             // draw the X and Y axis
  570.             endPoints[0] = p + minX; endPoints[1] = p + maxX; wires[0].Line(endPoints, color, LineEnd.Arrow, lineWidth);
  571.             endPoints[0] = p + minY; endPoints[1] = p + maxY; wires[1].Line(endPoints, color, LineEnd.Arrow, lineWidth);
  572.             // positiveY
  573.             for (int i = 0; i < thinLines; ++i) {
  574.                 Vector3 delta = up * (increments * (i + 1));
  575.                 endPoints[0] = p + minX + delta; endPoints[1] = p + maxX + delta;
  576.                 wires[++index].Line(endPoints, color, LineEnd.Normal, thinLineWidth);
  577.             }
  578.             // negativeY
  579.             for (int i = 0; i < thinLines; ++i) {
  580.                 Vector3 delta = -up * (increments * (i + 1));
  581.                 endPoints[0] = p + minX + delta; endPoints[1] = p + maxX + delta;
  582.                 wires[++index].Line(endPoints, color, LineEnd.Normal, thinLineWidth);
  583.             }
  584.             // positiveX
  585.             for (int i = 0; i < thinLines; ++i) {
  586.                 Vector3 delta = right * (increments * (i + 1));
  587.                 endPoints[0] = p + minY + delta; endPoints[1] = p + maxY + delta;
  588.                 wires[++index].Line(endPoints, color, LineEnd.Normal, thinLineWidth);
  589.             }
  590.             // negativeX
  591.             for (int i = 0; i < thinLines; ++i) {
  592.                 Vector3 delta = -right * (increments * (i + 1));
  593.                 endPoints[0] = p + minY + delta; endPoints[1] = p + maxY + delta;
  594.                 wires[++index].Line(endPoints, color, LineEnd.Normal, thinLineWidth);
  595.             }
  596.         }
  597.  
  598.         internal static int _CartesianPlaneChildCount(float extents, float increment, out int linesPerDomainHalf) {
  599.             linesPerDomainHalf = (int)(extents / increment);
  600.             if (Mathf.Abs(linesPerDomainHalf - (extents / increment)) < Math3d.TOLERANCE) --linesPerDomainHalf;
  601.             return 2 + linesPerDomainHalf * 4;
  602.         }
  603.  
  604.  
  605.         public static void WriteRectangleCoordinates(Vector3[] out_corner, Vector3 origin, Quaternion rotation,
  606.         Vector3 halfSize, Vector2 position2D) {
  607.             out_corner[0] = (position2D + new Vector2(-halfSize.x, +halfSize.y)); // top left
  608.             out_corner[1] = (position2D + new Vector2(+halfSize.x, +halfSize.y)); // top right
  609.             out_corner[2] = (position2D + new Vector2(+halfSize.x, -halfSize.y)); // bottom right
  610.             out_corner[3] = (position2D + new Vector2(-halfSize.x, -halfSize.y)); // bottom left
  611.             for (int i = 0; i < 4; ++i) { out_corner[i] = rotation * out_corner[i] + origin; }
  612.         }
  613.  
  614.         /// <param name="rectTransform">rectangle to draw on. should have RawImage (or no Renderer at all)</param>
  615.         public static Texture2D GetRawImageTexture(RectTransform rectTransform) {
  616.             UnityEngine.UI.RawImage rImg = rectTransform.GetComponent<UnityEngine.UI.RawImage>();
  617.             if (rImg == null) { rImg = rectTransform.gameObject.AddComponent<UnityEngine.UI.RawImage>(); }
  618.             if (rImg == null) {
  619.                 throw new Exception($"unable to create a RawImage on {rectTransform.name}, does it have another renderer?");
  620.             }
  621.             Texture2D img = rImg.texture as Texture2D;
  622.             if (img == null) {
  623.                 Rect r = rectTransform.rect;
  624.                 img = new Texture2D((int)r.width, (int)r.height);
  625.                 img.SetPixels32(0, 0, (int)r.width, (int)r.height, new Color32[(int)(r.width * r.height)]); // pixels are clear
  626.                 rImg.texture = img;
  627.             }
  628.             return img;
  629.         }
  630.  
  631.         /// <param name="rTransform">rectangle to draw on. should have RawImage (or no Renderer at all)</param>
  632.         /// <param name="start">(0,0) is lower left</param>
  633.         /// <param name="end"></param>
  634.         /// <param name="color"></param>
  635.         public static void DrawLine(RectTransform rTransform, Vector2 start, Vector2 end, Color color, bool apply = true) {
  636.             DrawLine(rTransform, (int)start.x, (int)start.y, (int)end.x, (int)end.y, color, apply);
  637.         }
  638.  
  639.         /// <param name="rectTransform">rectangle to draw on. should have RawImage (or no Renderer at all)</param>
  640.         /// <param name="x0">0 is left</param>
  641.         /// <param name="y0">0 is bottom</param>
  642.         /// <param name="x1"></param>
  643.         /// <param name="y1"></param>
  644.         /// <param name="col"></param>
  645.         public static void DrawLine(RectTransform rectTransform, int x0, int y0, int x1, int y1, Color col,
  646.         bool apply = true) {
  647.             Texture2D img = GetRawImageTexture(rectTransform);
  648.             DrawLine(img, x0, y0, x1, y1, col);
  649.             if (apply) img.Apply();
  650.         }
  651.  
  652.         public static void DrawAABB(RectTransform rectTransform, Vector2 p0, Vector2 p1, Color col, bool apply = true) {
  653.             DrawAABB(rectTransform, (int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, col, apply);
  654.         }
  655.  
  656.         public static void DrawAABB(RectTransform rectTransform, int x0, int y0, int x1, int y1, Color col,
  657.         bool apply = true) {
  658.             Texture2D img = GetRawImageTexture(rectTransform);
  659.             DrawLine(img, x0, y0, x0, y1, col);
  660.             DrawLine(img, x0, y1, x1, y1, col);
  661.             DrawLine(img, x1, y0, x1, y1, col);
  662.             DrawLine(img, x0, y0, x1, y0, col);
  663.             if (apply) img.Apply();
  664.         }
  665.  
  666.         /// <summary>draws an un-aliased single-pixel line on the given texture with the given color</summary>ne
  667.         /// <param name="texture"></param>
  668.         /// <param name="x0">0 is left</param>
  669.         /// <param name="y0">0 is bottom</param>
  670.         /// <param name="x1"></param>
  671.         /// <param name="y1"></param>
  672.         /// <param name="color"></param>
  673.         public static void DrawLine(Texture2D texture, int x0, int y0, int x1, int y1, Color color) {
  674.             int dy = y1 - y0;
  675.             int dx = x1 - x0;
  676.             int stepY, stepX;
  677.             if (dy < 0) { dy = -dy; stepY = -1; } else { stepY = 1; }
  678.             if (dx < 0) { dx = -dx; stepX = -1; } else { stepX = 1; }
  679.             dy <<= 1;
  680.             dx <<= 1;
  681.             float fraction;
  682.             texture.SetPixel(x0, y0, color);
  683.             if (dx > dy) {
  684.                 fraction = dy - (dx >> 1);
  685.                 while (Mathf.Abs(x0 - x1) > 1) {
  686.                     if (fraction >= 0) {
  687.                         y0 += stepY;
  688.                         fraction -= dx;
  689.                     }
  690.                     x0 += stepX;
  691.                     fraction += dy;
  692.                     texture.SetPixel(x0, y0, color);
  693.                 }
  694.             } else {
  695.                 fraction = dx - (dy >> 1);
  696.                 while (Mathf.Abs(y0 - y1) > 1) {
  697.                     if (fraction >= 0) {
  698.                         x0 += stepX;
  699.                         fraction -= dy;
  700.                     }
  701.                     y0 += stepY;
  702.                     fraction += dx;
  703.                     texture.SetPixel(x0, y0, color);
  704.                 }
  705.             }
  706.         }
  707.     }
  708. }
  709.  
  710. namespace NonStandard {
  711.     public static class Math3d {
  712.  
  713.         /// <summary>
  714.         /// how close two floating point values need to be before they are considered equal in this library
  715.         /// </summary>
  716.         public const float TOLERANCE = 1f / (1 << 23); // one sixteen-millionth
  717.  
  718.         public static float Snap(float number, float snap) {
  719.             if (snap == 0) return number;
  720.             snap = Mathf.Abs(snap);
  721.             if (snap <= 1f)
  722.                 return Mathf.Floor(number) + (Mathf.Round((number - Mathf.Floor(number)) * (1f / snap)) * snap);
  723.             else
  724.                 return Mathf.Round(number / snap) * snap;
  725.         }
  726.         public static float RoundUpToNearest(float n, float snap) {
  727.             if (snap != 0) { return (float)Math.Ceiling(n / snap) * snap; }
  728.             return n;
  729.         }
  730.  
  731.         public static float RoundDownToNearest(float n, float snap) {
  732.             if (snap != 0) { return (float)Math.Floor(n / snap) * snap; }
  733.             return n;
  734.         }
  735.  
  736.         public static void IncrementWithSnap(ref float value, float change, ref float snapProgress, float snap,
  737.         float angleSnapStickiness) {
  738.             if (change == 0) return;
  739.             float lowerBound, upperBound;
  740.             if (value >= 0) {
  741.                 lowerBound = Math3d.RoundDownToNearest(value, snap);
  742.                 upperBound = (lowerBound == value) ? value : Math3d.RoundUpToNearest(value, snap);
  743.             } else {
  744.                 upperBound = Math3d.RoundUpToNearest(value, snap);
  745.                 lowerBound = (upperBound == value) ? value : Math3d.RoundDownToNearest(value, snap);
  746.             }
  747.             IncrementWithSnap(ref value, lowerBound, upperBound, change, ref snapProgress, angleSnapStickiness);
  748.         }
  749.  
  750.         public static void IncrementWithSnap(ref float value, float lowerBound, float upperBound, float change,
  751.         ref float snapProgress, float angleSnapStickiness) {
  752.             float excess;
  753.             float newValue = value + change;
  754.             if (change < 0) {
  755.                 if (newValue < lowerBound) {
  756.                     excess = newValue - lowerBound;
  757.                     snapProgress += excess;
  758.                     newValue = lowerBound;
  759.                 }
  760.                 if (snapProgress < -angleSnapStickiness) {
  761.                     excess = snapProgress + angleSnapStickiness;
  762.                     newValue += excess;
  763.                     snapProgress = 0;
  764.                 }
  765.             } else {
  766.                 if (newValue > upperBound) {
  767.                     excess = newValue - upperBound;
  768.                     snapProgress += excess;
  769.                     newValue = upperBound;
  770.                 }
  771.                 if (snapProgress > +angleSnapStickiness) {
  772.                     excess = snapProgress - angleSnapStickiness;
  773.                     newValue += excess;
  774.                     snapProgress = 0;
  775.                 }
  776.             }
  777.             value = newValue;
  778.         }
  779.  
  780.         /// <summary>
  781.         /// used to check equality of two floats that are not expected to be assigned as powers of 2
  782.         /// </summary>
  783.         /// <param name="delta">the difference between two floats</param>
  784.         public static bool EQ(float delta) { return Mathf.Abs(delta) < Math3d.TOLERANCE; }
  785.  
  786.         /// <summary>
  787.         /// intended for use when comparing whole numbers or fractional powers of 2
  788.         /// </summary>
  789.         public static bool EQ2(float a, float b) {
  790.             // ReSharper disable once CompareOfFloatsByEqualityOperator
  791.             return a == b;
  792.         }
  793.  
  794.         public static Vector3 GetForwardVector(Quaternion q) {
  795.             return new Vector3(2 * (q.x * q.z + q.w * q.y), 2 * (q.y * q.z + q.w * q.x), 1 - 2 * (q.x * q.x + q.y * q.y));
  796.         }
  797.         public static Vector3 GetUpVector(Quaternion q) {
  798.             return new Vector3(2 * (q.x * q.y + q.w * q.z), 1 - 2 * (q.x * q.x + q.z * q.z), 2 * (q.y * q.z + q.w * q.x));
  799.         }
  800.         public static Vector3 GetRightVector(Quaternion q) {
  801.             return new Vector3(1 - 2 * (q.y * q.y + q.z * q.z), 2 * (q.x * q.y + q.w * q.z), 2 * (q.x * q.z + q.w * q.y));
  802.         }
  803.  
  804.         /// <summary>Write 2D arc in 3D space, into given Vector3 array</summary>
  805.         /// <param name="points">Will host the list of coordinates</param>
  806.         /// <param name="pointCount">How many vertices to make &gt; 1</param>
  807.         /// <param name="normal">The surface-normal of the arc's plane</param>
  808.         /// <param name="firstPoint">Arc start, rotate about Vector3.zero</param>
  809.         /// <param name="angle">2D angle. Tip: Vector3.Angle(v1, v2)</param>
  810.         /// <param name="offset">How to translate the arc</param>
  811.         /// <param name="startIndex"></param>
  812.         public static void WriteArc(ref Vector3[] points, int pointCount,
  813.             Vector3 normal, Vector3 firstPoint, float angle = 360, Vector3 offset = default, int startIndex = 0) {
  814.             if (pointCount < 0) {
  815.                 pointCount = (int)Mathf.Abs(24 * angle / 180f) + 1;
  816.             }
  817.             if (pointCount <= 1) { pointCount = 2; }
  818.             if (pointCount < 0 || pointCount >= 32767) { throw new Exception($"bad point count value: {pointCount}"); }
  819.             if (points == null) { points = new Vector3[pointCount]; }
  820.             if (startIndex >= points.Length) return;
  821.             points[startIndex] = firstPoint;
  822.             Quaternion q = Quaternion.AngleAxis(angle / (pointCount - 1), normal);
  823.             for (int i = startIndex + 1; i < startIndex + pointCount; ++i) { points[i] = q * points[i - 1]; }
  824.             if (offset != Vector3.zero)
  825.                 for (int i = startIndex; i < startIndex + pointCount; ++i) { points[i] += offset; }
  826.         }
  827.  
  828.         public static void WriteBezier(IList<Vector3> points, Vector3 start, Vector3 startControl, Vector3 endControl,
  829.         Vector3 end, int startIndex = 0, int count = -1) {
  830.             if (count < 0) { count = points.Count - startIndex; }
  831.             float num = count - 1;
  832.             for (int i = 0; i < count; ++i) {
  833.                 points[i + startIndex] = GetBezierPoint(start, startControl, endControl, end, i / num);
  834.             }
  835.         }
  836.  
  837.         public static Vector3 GetBezierPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
  838.             t = Mathf.Clamp01(t); float o = 1 - t, tt = t * t, oo = o * o;
  839.             return oo * o * p0 + 3 * oo * t * p1 + 3 * o * tt * p2 + t * tt * p3;
  840.         }
  841.  
  842.         public static void WriteArcOnSphere(ref Vector3[] points, int pointCount, Vector3 sphereCenter, Vector3 start,
  843.         Vector3 end) {
  844.             Vector3 axis;
  845.             if (start == -end) {
  846.                 axis = (start != Vector3.up && end != Vector3.up) ? Vector3.up : Vector3.right;
  847.             } else {
  848.                 axis = Vector3.Cross(start, end).normalized;
  849.             }
  850.             Vector3 a = start - sphereCenter, b = end - sphereCenter;
  851.             float aRad = a.magnitude, bRad = b.magnitude, angle = 0;
  852.             if (EQ2(aRad, 0) && EQ2(bRad, 0)) {
  853.                 a /= aRad; b /= bRad;
  854.                 angle = Vector3.Angle(a, b);
  855.                 if (float.IsNaN(angle)) { angle = 0; }
  856.             }
  857.             WriteArc(ref points, pointCount, axis, a, angle, Vector3.zero);
  858.             float radDelta = bRad - aRad;
  859.             for (int i = 0; i < points.Length; ++i) {
  860.                 points[i] = points[i] * ((i * radDelta / points.Length) + aRad);
  861.                 points[i] += sphereCenter;
  862.             }
  863.         }
  864.  
  865.         public static int WriteCircle(ref Vector3[] points, Vector3 center, Vector3 normal, float radius = 1,
  866.         int pointCount = 0) {
  867.             if (pointCount == 0) {
  868.                 pointCount = (int)Mathf.Round(24 * Mathf.PI * radius + 0.5f);
  869.                 if (points != null) {
  870.                     pointCount = Mathf.Min(points.Length, pointCount);
  871.                 }
  872.             }
  873.             if (normal == Vector3.zero) { normal = Vector3.up; }
  874.             Vector3 crossDir = (normal == Vector3.up || normal == Vector3.down) ? Vector3.forward : Vector3.up;
  875.             Vector3 r = Vector3.Cross(normal, crossDir).normalized;
  876.             WriteArc(ref points, pointCount, normal, r * radius, 360, center);
  877.             return pointCount;
  878.         }
  879.  
  880.         /// <example>CreateSpiralSphere(transform.position, 0.5f, transform.up, transform.forward, 16, 8);</example>
  881.         /// <summary>creates a line spiraled onto a sphere</summary>
  882.         /// <param name="center"></param>
  883.         /// <param name="radius"></param>
  884.         /// <param name="rotation"></param>
  885.         /// <param name="sides"></param>
  886.         /// <param name="rotations"></param>
  887.         /// <returns></returns>
  888.         public static Vector3[] CreateSpiralSphere(Vector3 center = default, float radius = 1,
  889.             Quaternion rotation = default, float sides = 12, float rotations = 6) {
  890.             List<Vector3> points = new List<Vector3>(); // List instead of Array because sides and rotations are floats!
  891.             Vector3 axis = Vector3.up;
  892.             Vector3 axisFace = Vector3.right;
  893.             if (EQ2(sides, 0) && EQ2(rotations, 0)) {
  894.                 float iter = 0;
  895.                 float increment = 1f / (rotations * sides);
  896.                 points.Add(center + axis * radius);
  897.                 do {
  898.                     iter += increment;
  899.                     Quaternion faceTurn = Quaternion.AngleAxis(iter * 360 * rotations, axis);
  900.                     Vector3 newFace = faceTurn * axisFace;
  901.                     Quaternion q = Quaternion.LookRotation(newFace);
  902.                     Vector3 right = GetUpVector(q);
  903.                     Vector3 r = right * radius;
  904.                     q = Quaternion.AngleAxis(iter * 180, newFace);
  905.                     r = q * r;
  906.                     r = rotation * r;
  907.                     Vector3 newPoint = center + r;
  908.                     points.Add(newPoint);
  909.                 }
  910.                 while (iter < 1);
  911.             }
  912.             return points.ToArray();
  913.         }
  914.     }
  915. }
  916.  
  917. namespace NonStandard {
  918.     /// <summary>
  919.     /// describes the ends of a <see cref="LineRenderer"/> being drawn by <see cref="Wire"/>.
  920.     /// </summary>
  921.     public enum LineEnd {
  922.         /// <summary>
  923.         /// a simple rectangular end, or curve if the <see cref="LineRenderer"/> has multiple vertices at the end
  924.         /// </summary>
  925.         Normal,
  926.         /// <summary>
  927.         /// ends in an arrow head
  928.         /// </summary>
  929.         Arrow,
  930.         /// <summary>
  931.         /// starts and ends with an arrow head
  932.         /// </summary>
  933.         ArrowBothEnds
  934.     };
  935.  
  936.     /// <summary>cached calculations. used to validate if a line needs to be re-calculated</summary>
  937.     public class Wire : MonoBehaviour {
  938.         public enum Kind {
  939.             None,
  940.             /// <summary>
  941.             /// expects to have a 2 points, a start and end
  942.             /// </summary>
  943.             Line,
  944.             /// <summary>
  945.             /// expects multiple points, mostly uniform distance apart
  946.             /// </summary>
  947.             Arc,
  948.             /// <summary>
  949.             /// like an arc, but more useful of drawing a simulated missle trajectory flying over a sphere
  950.             /// </summary>
  951.             Orbital,
  952.             /// <summary>
  953.             /// a sphere made out of a spiral of points
  954.             /// </summary>
  955.             SpiralSphere,
  956.             /// <summary>
  957.             /// a wireframe box
  958.             /// </summary>
  959.             Box,
  960.             /// <summary>
  961.             /// an arc and a line, as representations of <see cref="Quaternion.AngleAxis(float, Vector3)"/>
  962.             /// </summary>
  963.             Quaternion,
  964.             /// <summary>
  965.             /// an X and Y axis with little marks uniformly spaced out along each axis
  966.             /// </summary>
  967.             CartesianPlane,
  968.             /// <summary>
  969.             /// a 2D box
  970.             /// </summary>
  971.             Rectangle,
  972.             /// <summary>
  973.             /// local-space constrained line, so it can be moved and rotated with it's transform instead of recalculation
  974.             /// </summary>
  975.             Rod
  976.         }
  977.         private Kind _kind;
  978.         private Vector3[] _points;
  979.         private Vector3 _normal;
  980.         private Quaternion _rotation;
  981.         private int _count;
  982.         private float _startSize, _endSize, _angle;
  983.         private LineEnd _lineEnds;
  984.         private LineRenderer _lr;
  985. #if UNITY_EDITOR
  986.         [Tooltip("Where the code is that created this Wire. Not present outside the UnityEditor.")]
  987.         // ReSharper disable once NotAccessedField.Global
  988.         [SerializeField] private string sourceCode;
  989. #endif
  990.         /// <summary>
  991.         /// which point rotations to show on a quaternion
  992.         /// </summary>
  993.         private static readonly Vector3[] DefaultQuaternionVisualizationPoints =
  994.             new Vector3[] { Vector3.forward, Vector3.up };
  995.  
  996.         public LineRenderer LineRenderer => _lr != null ? _lr : _lr = Wires.MakeLineRenderer(gameObject);
  997.         public Vector3 StartPoint {
  998.             get {
  999.                 if (!LineRenderer.useWorldSpace) { return transform.position + _points[0]; }
  1000.                 return _points[0];
  1001.             }
  1002.         }
  1003.         public Vector3 EndPoint {
  1004.             get {
  1005.                 if (!LineRenderer.useWorldSpace) { return transform.position + _points[_points.Length - 1]; }
  1006.                 return _points[_points.Length - 1];
  1007.             }
  1008.         }
  1009.         public int NumCapVertices {
  1010.             get => LineRenderer.numCapVertices;
  1011.             set => LineRenderer.numCapVertices = value;
  1012.         }
  1013.  
  1014.         public void RefreshSource() {
  1015. #if UNITY_EDITOR
  1016.             StackTrace stackTrace = new StackTrace(true);
  1017.             StackFrame f = stackTrace.GetFrame(2);
  1018.             string path = f.GetFileName();
  1019.             if (path == null) return;
  1020.             int fileIndex = path.LastIndexOf(System.IO.Path.DirectorySeparatorChar);
  1021.             sourceCode = $"{path.Substring(fileIndex + 1)}:{f.GetFileLineNumber()}";
  1022. #endif
  1023.         }
  1024.  
  1025.         public Kind kind {
  1026.             get => _kind;
  1027.             set {
  1028.                 // special cleanup for Quaternions, CartesianPlanes, and Rectangles, these have child wires
  1029.                 if ((_kind == Kind.Quaternion && value != Kind.Quaternion)
  1030.                 || (_kind == Kind.CartesianPlane && value != Kind.CartesianPlane)
  1031.                 || (_kind == Kind.Rectangle && value != Kind.Rectangle)) {
  1032.                     DisposeOfChildWires();
  1033.                 }
  1034.                 _kind = value;
  1035.             }
  1036.         }
  1037.  
  1038.         private void DisposeOfChildWires() {
  1039.             Wire[] obj = ChildWires(_count, false);
  1040.             if (obj != null) {
  1041.                 Array.ForEach(obj, w => { w.transform.SetParent(null); Destroy(w.gameObject); });
  1042.             }
  1043.         }
  1044.  
  1045.         private void SetLine(IList<Vector3> points, float startSize, float endSize, LineEnd lineEnds) {
  1046.             kind = Kind.Line;
  1047.             if (points != null) {
  1048.                 _points = new Vector3[points.Count];
  1049.                 for (int i = 0; i < _points.Length; ++i) { _points[i] = points[i]; }
  1050.             }
  1051.             _startSize = startSize; _endSize = endSize; _lineEnds = lineEnds;
  1052.         }
  1053.  
  1054.         /// <summary>
  1055.         /// makes the line along the local forward vector, and moves+rotates the transform to be at the correct position
  1056.         /// </summary>
  1057.         private void SetRod(IList<Vector3> points, float startSize, float endSize, LineEnd lineEnds) {
  1058.             kind = Kind.Rod;
  1059.             if (points != null) {
  1060.                 _points = new Vector3[points.Count];
  1061.                 Vector3 start = points[0];
  1062.                 Vector3 end = points[points.Count - 1];
  1063.                 Vector3 delta = end - start;
  1064.                 float dist = delta.magnitude;
  1065.                 Vector3 dir = dist != 0 ? delta / dist : Vector3.forward;
  1066.                 Vector3 axisOfRotation = Vector3.Cross(Vector3.forward, dir);
  1067.                 float angleOfRotation;
  1068.                 if (axisOfRotation != Vector3.zero) {
  1069.                     axisOfRotation = axisOfRotation.normalized;
  1070.                     angleOfRotation = Vector3.SignedAngle(Vector3.forward, dir, axisOfRotation);
  1071.                 } else { axisOfRotation = Vector3.up; angleOfRotation = 0; }
  1072.                 Quaternion q = UnityEngine.Quaternion.AngleAxis(angleOfRotation, axisOfRotation);
  1073.                 Quaternion unq = UnityEngine.Quaternion.AngleAxis(-angleOfRotation, axisOfRotation);
  1074.                 for (int i = 1; i < _points.Length; ++i) { _points[i] = unq * (points[i] - start); }
  1075.                 transform.rotation = q;
  1076.                 transform.position = start;
  1077.             }
  1078.             _startSize = startSize; _endSize = endSize; _lineEnds = lineEnds;
  1079.         }
  1080.  
  1081.         private bool IsArc(Vector3 start, Vector3 normal, Vector3 center, float angle, float startSize, float endSize,
  1082.         LineEnd lineEnds, int pointCount) {
  1083.             return kind == Kind.Arc && _points != null && _points.Length == 1 && _points[0] == start && _count == pointCount
  1084.                 && _normal == normal && Math3d.EQ(startSize - _startSize) && Math3d.EQ(endSize - _endSize)
  1085.                 && _lineEnds == lineEnds && transform.position == center && _normal == normal && Math3d.EQ(_angle - angle);
  1086.         }
  1087.  
  1088.         private void SetArc(Vector3 start, Vector3 normal, Vector3 center, float angle, float startSize, float endSize,
  1089.         LineEnd lineEnds, int pointCount) {
  1090.             kind = Kind.Arc;
  1091.             _points = new Vector3[] { start }; _count = pointCount;
  1092.             _startSize = startSize; _endSize = endSize; _lineEnds = lineEnds;
  1093.             transform.position = center; _normal = normal; _angle = angle;
  1094.         }
  1095.  
  1096.         private bool IsOrbital(Vector3 start, Vector3 end, Vector3 center, float startSize, float endSize,
  1097.         LineEnd lineEnds, int pointCount) {
  1098.             return kind == Kind.Orbital && _points != null && _points.Length == 2 && _count == pointCount
  1099.                 && _points[0] == start && _points[1] == end
  1100.                 && Math3d.EQ(startSize - _startSize) && Math3d.EQ(endSize - _endSize) && _lineEnds == lineEnds
  1101.                 && transform.position == center;
  1102.         }
  1103.  
  1104.         private void SetOrbital(Vector3 start, Vector3 end, Vector3 center = default, float startSize = Wires.LINE_WIDTH,
  1105.         float endSize = Wires.SAME_AS_START_SIZE, LineEnd lineEnds = default, int pointCount = -1) {
  1106.             kind = Kind.Orbital;
  1107.             _points = new Vector3[] { start, end }; _count = pointCount;
  1108.             _startSize = startSize; _endSize = endSize; _lineEnds = lineEnds;
  1109.             transform.position = center;
  1110.         }
  1111.  
  1112.         private bool IsSpiralSphere(Vector3 center, float radius, float lineSize, Quaternion rotation) {
  1113.             return kind == Kind.SpiralSphere
  1114.                 && Math3d.EQ(_startSize - lineSize) && Math3d.EQ(_endSize - lineSize) && transform.position == center
  1115.                 && Math3d.EQ(_angle - radius) && (_rotation.Equals(rotation) || _rotation == rotation);
  1116.         }
  1117.  
  1118.         private void SetSpiralSphere(Vector3 center, float radius, float lineSize, Quaternion rotation) {
  1119.             kind = Kind.SpiralSphere;
  1120.             _startSize = _endSize = lineSize;
  1121.             transform.position = center; _angle = radius; _rotation = rotation;
  1122.         }
  1123.  
  1124.         private bool IsBox(Vector3 center, Vector3 size, Quaternion rotation, float lineSize) {
  1125.             Transform t = transform;
  1126.             return kind == Kind.Box && Math3d.EQ(_startSize - lineSize) && Math3d.EQ(_endSize - lineSize)
  1127.                 && t.position == center && t.localScale == size && t.rotation == rotation;
  1128.         }
  1129.  
  1130.         private void SetBox(Vector3 center, Vector3 size, Quaternion rotation, float lineSize) {
  1131.             Transform t = transform;
  1132.             kind = Kind.Box;
  1133.             _startSize = _endSize = lineSize;
  1134.             t.position = center;
  1135.             t.localScale = size;
  1136.             t.rotation = rotation;
  1137.         }
  1138.  
  1139.         private bool IsQuaternion(float an, Vector3 ax, Vector3 position, Vector3[] startPoints, Quaternion orientation,
  1140.         float lineSize) {
  1141.             return kind == Kind.Quaternion && IsSameArrayOfVectors(_points, startPoints)
  1142.                 && Math3d.EQ(_startSize - lineSize) && Math3d.EQ(_endSize - lineSize)
  1143.                 && transform.position == position && _normal == ax && Math3d.EQ(_angle - an) && _count == startPoints.Length
  1144.                 // quaternions can't easily be tested for equality because of floating point errors
  1145.                 && (_rotation.Equals(orientation) || _rotation == orientation);
  1146.         }
  1147.  
  1148.         private static bool IsSameArrayOfVectors(IList<Vector3> a, IList<Vector3> b) {
  1149.             if (ReferenceEquals(a, b)) { return true; }
  1150.             if (a == null || b == null || a.Count != b.Count) { return false; }
  1151.             for (int i = 0; i < a.Count; ++i) { if (a[i] != b[i]) return false; }
  1152.             return true;
  1153.         }
  1154.  
  1155.         private void SetQuaternion(float an, Vector3 ax, Vector3 position, Vector3[] startPoints, Quaternion orientation,
  1156.         float lineSize) {
  1157.             kind = Kind.Quaternion;
  1158.             if (ReferenceEquals(startPoints, DefaultQuaternionVisualizationPoints)) {
  1159.                 _points = DefaultQuaternionVisualizationPoints;
  1160.             } else {
  1161.                 _points = new Vector3[startPoints.Length]; Array.Copy(startPoints, _points, startPoints.Length);
  1162.             }
  1163.             _startSize = _endSize = lineSize;
  1164.             transform.position = position; _normal = ax; _angle = an; _count = startPoints.Length;
  1165.             _rotation = orientation;
  1166.         }
  1167.  
  1168.         private bool IsCartesianPlane(Vector3 center, Quaternion rotation, float size, float extents, float increment) {
  1169.             return kind == Kind.CartesianPlane && Math3d.EQ(_startSize - extents) && Math3d.EQ(_endSize - size)
  1170.             && Math3d.EQ(_angle - increment) && (_rotation.Equals(rotation) || _rotation == rotation)
  1171.             && transform.position == center;
  1172.         }
  1173.  
  1174.         private void SetCartesianPlane(Vector3 center, Quaternion rotation, float size, float extents, float increment) {
  1175.             kind = Kind.CartesianPlane; _startSize = extents; _endSize = size; _angle = increment;
  1176.             _rotation = rotation; transform.position = center;
  1177.             _count = Wires._CartesianPlaneChildCount(extents, increment, out _);
  1178.         }
  1179.  
  1180.         private bool IsRectangle(Vector3 origin, Vector2 offset2d, Vector2 halfSize, float lineSize, Quaternion rotation) {
  1181.             return kind == Kind.Rectangle && origin == transform.position && Math3d.EQ(_startSize - lineSize)
  1182.                 && (_rotation.Equals(rotation) || _rotation == rotation) && Math3d.EQ(_normal.x - offset2d.x)
  1183.                 && Math3d.EQ(_normal.y - offset2d.y) && Math3d.EQ(_normal.z - halfSize.x) && Math3d.EQ(_endSize - halfSize.y);
  1184.         }
  1185.  
  1186.         private void SetRectangle(Vector3 origin, Vector2 offset2d, Vector2 halfSize, float size, Quaternion rotation) {
  1187.             kind = Kind.Rectangle; transform.position = origin; _startSize = size; _rotation = rotation;
  1188.             _normal.x = offset2d.x; _normal.y = offset2d.y; _normal.z = halfSize.x; _endSize = halfSize.y; _count = 4;
  1189.         }
  1190.  
  1191.         public Wire Line(Vector3 start, Vector3 end, Color color = default, float startSize = Wires.LINE_WIDTH,
  1192.         float endSize = Wires.SAME_AS_START_SIZE) {
  1193.             return Line(new Vector3[] { start, end }, color, LineEnd.Normal, startSize, endSize);
  1194.         }
  1195.  
  1196.         /// <summary>
  1197.         /// useful if a line that rotates performantly with it's parent object is needed
  1198.         /// </summary>
  1199.         public Wire Rod(Vector3 start, Vector3 end, Color color = default, float startSize = Wires.LINE_WIDTH,
  1200.         float endSize = Wires.SAME_AS_START_SIZE) {
  1201.             return Rod(new Vector3[] { start, end }, color, LineEnd.Normal, startSize, endSize);
  1202.         }
  1203.  
  1204.         public Wire Arrow(Vector3 start, Vector3 end, Color color = default, float startSize = Wires.LINE_WIDTH,
  1205.         float endSize = Wires.SAME_AS_START_SIZE) {
  1206.             return Line(new Vector3[] { start, end }, color, LineEnd.Arrow, startSize, endSize);
  1207.         }
  1208.  
  1209.         public Wire Arrow(Vector3 vector, Color color = default, float startSize = Wires.LINE_WIDTH,
  1210.         float endSize = Wires.SAME_AS_START_SIZE) {
  1211.             return Line(new Vector3[] { Vector3.zero, vector }, color, LineEnd.Arrow, startSize, endSize);
  1212.         }
  1213.  
  1214.         public Wire Arrow(Ray ray, Color color = default, float startSize = Wires.LINE_WIDTH,
  1215.         float endSize = Wires.SAME_AS_START_SIZE) {
  1216.             return Line(new Vector3[] { ray.origin, ray.origin + ray.direction }, color, LineEnd.Arrow, startSize, endSize);
  1217.         }
  1218.  
  1219.         public Wire Bezier(Vector3 start, Vector3 startControl, Vector3 endControl, Vector3 end, Color color = default,
  1220.         LineEnd cap = LineEnd.Normal, float startSize = Wires.LINE_WIDTH, int bezierPointCount = 25,
  1221.         float endSize = Wires.SAME_AS_START_SIZE) {
  1222.             Vector3[] bezier = new Vector3[bezierPointCount];
  1223.             Math3d.WriteBezier(bezier, start, startControl, endControl, end);
  1224.             return Line(bezier, color, cap, startSize, endSize);
  1225.         }
  1226.  
  1227.         public Wire Line(Vector3 vector, Color color = default, float startSize = Wires.LINE_WIDTH,
  1228.         float endSize = Wires.SAME_AS_START_SIZE) {
  1229.             return Line(new Vector3[] { Vector3.zero, vector }, color, LineEnd.Normal, startSize, endSize);
  1230.         }
  1231.  
  1232.         public Wire Line(IList<Vector3> points, Color color = default, LineEnd lineEnds = default,
  1233.         float startSize = Wires.LINE_WIDTH, float endSize = Wires.SAME_AS_START_SIZE) {
  1234.             if (!IsLine(points, startSize, endSize, lineEnds)) {
  1235.                 SetLine(points, startSize, endSize, lineEnds);
  1236.                 _lr = Wires.MakeLine(LineRenderer, points, color, startSize, endSize, lineEnds);
  1237.             } //else { Debug.Log("don't need to recalculate line "+name); }
  1238.             if (_lr) { Wires.SetColor(_lr, color); }
  1239.             return this;
  1240.         }
  1241.  
  1242.         private bool IsLine(IList<Vector3> points, float startSize, float endSize, LineEnd lineEnds) {
  1243.             return kind == Kind.Line && IsSameArrayOfVectors(_points, points)
  1244.                 && Math3d.EQ(startSize - _startSize) && Math3d.EQ(endSize - _endSize) && _lineEnds == lineEnds;
  1245.         }
  1246.  
  1247.         /// <summary>
  1248.         /// useful if a line that rotates performantly with it's parent object is needed
  1249.         /// </summary>
  1250.         public Wire Rod(IList<Vector3> points, Color color = default, LineEnd lineEnds = default,
  1251.         float startSize = Wires.LINE_WIDTH, float endSize = Wires.SAME_AS_START_SIZE) {
  1252.             if (!IsRod(points, startSize, endSize, lineEnds)) {
  1253.                 SetRod(points, startSize, endSize, lineEnds);
  1254.                 _lr = Wires.MakeLine(LineRenderer, _points, color, startSize, endSize, lineEnds);
  1255.                 _lr.useWorldSpace = false;
  1256.             } //else { Debug.Log("don't need to recalculate line "+name); }
  1257.             if (_lr) { Wires.SetColor(_lr, color); }
  1258.             return this;
  1259.         }
  1260.  
  1261.         private bool IsRod(IList<Vector3> points, float startSize, float endSize, LineEnd lineEnds) {
  1262.             return kind == Kind.Rod && IsSameArrayOfVectors(_points, points, transform.rotation, transform.position)
  1263.                 && Math3d.EQ(startSize - _startSize) && Math3d.EQ(endSize - _endSize) && _lineEnds == lineEnds;
  1264.         }
  1265.  
  1266.         private static bool IsSameArrayOfVectors(IList<Vector3> a, IList<Vector3> b, Quaternion rotateA,
  1267.         Vector3 offsetA = default) {
  1268.             if (ReferenceEquals(a, b)) { return true; }
  1269.             if (a == null || b == null || a.Count != b.Count) { return false; }
  1270.             for (int i = 0; i < a.Count; ++i) { if (rotateA * a[i] + offsetA != b[i]) return false; }
  1271.             return true;
  1272.         }
  1273.  
  1274.         public Wire Arc(Vector3 start, Vector3 end, Vector3 height, Color color = default, LineEnd lineEnds = default,
  1275.         int pointCount = 24, float startSize = Wires.LINE_WIDTH, float endSize = Wires.SAME_AS_START_SIZE) {
  1276.             return Bezier(start, start+height, end+height, end, color, lineEnds, startSize, pointCount, endSize);
  1277.         }
  1278.  
  1279.         public Wire Arc(float angle, Vector3 normal, Vector3 firstPoint, Vector3 center = default, Color color = default,
  1280.         LineEnd lineEnds = default, int pointCount = -1, float startSize = Wires.LINE_WIDTH,
  1281.         float endSize = Wires.SAME_AS_START_SIZE) {
  1282.             if (pointCount < 0) { pointCount = (int)(24 * angle / 180f) + 1; }
  1283.             if (!IsArc(firstPoint, normal, center, angle, startSize, endSize, LineEnd.Normal, pointCount)) {
  1284.                 SetArc(firstPoint, normal, center, angle, startSize, endSize, LineEnd.Normal, pointCount);
  1285.                 Vector3[] linePoints = null;
  1286.                 Math3d.WriteArc(ref linePoints, pointCount, normal, firstPoint, angle, center);
  1287.                 _lr = Wires.MakeLine(LineRenderer, linePoints, color, startSize, endSize, lineEnds);
  1288.             } //else { Debug.Log("don't need to recalculate arc "+name);  }
  1289.             if (Math3d.EQ2(angle, 360)) { LineRenderer.loop = true; }
  1290.             Wires.SetColor(_lr, color);
  1291.             return this;
  1292.         }
  1293.  
  1294.         public Wire Circle(Vector3 center = default, Vector3 normal = default, Color color = default, float radius = 1,
  1295.         float lineSize = Wires.LINE_WIDTH, int pointCount = -1) {
  1296.             if (Math3d.EQ2(radius, 0)) { return Line(null, color, LineEnd.Normal, lineSize, lineSize); }
  1297.             if (normal == default) { normal = Vector3.up; }
  1298.             Vector3 firstPoint = Vector3.zero;
  1299.             if (kind == Kind.Arc && this._normal == normal && _points != null && _points.Length > 0) {
  1300.                 float firstRad = _points[0].magnitude;
  1301.                 if (Math3d.EQ2(firstRad, radius)) {
  1302.                     firstPoint = _points[0];
  1303.                 } else {
  1304.                     firstPoint = _points[0] * (radius / firstRad);
  1305.                 }
  1306.             }
  1307.             if (firstPoint == Vector3.zero) {
  1308.                 firstPoint = Vector3.right;
  1309.                 if (normal != Vector3.up && normal != Vector3.forward && normal != Vector3.back) {
  1310.                     firstPoint = Vector3.Cross(normal, Vector3.forward).normalized;
  1311.                 }
  1312.                 firstPoint *= radius;
  1313.             }
  1314.             return Arc(360, normal, firstPoint, center, color, LineEnd.Normal, pointCount, lineSize, lineSize);
  1315.         }
  1316.  
  1317.         /// <summary>
  1318.         /// draw line that orbits a sphere with the given center, from the given start to the given end
  1319.         /// </summary>
  1320.         public Wire Orbital(Vector3 sphereCenter, Vector3 start, Vector3 end, Color color = default,
  1321.         LineEnd lineEnds = default, float startSize = Wires.LINE_WIDTH, float endSize = Wires.SAME_AS_START_SIZE,
  1322.         int pointCount = -1) {
  1323.             if (!IsOrbital(start, end, sphereCenter, startSize, endSize, lineEnds, pointCount)) {
  1324.                 SetOrbital(start, end, sphereCenter, startSize, endSize, lineEnds, pointCount);
  1325.                 Vector3[] linePoints = null;
  1326.                 Math3d.WriteArcOnSphere(ref linePoints, pointCount, sphereCenter, start, end);
  1327.                 _lr = Wires.MakeLine(LineRenderer, linePoints, color, startSize, endSize, lineEnds);
  1328.             } //else { Debug.Log("don't need to recalculate orbital " + name); }
  1329.             Wires.SetColor(LineRenderer, color);
  1330.             return this;
  1331.         }
  1332.  
  1333.         public Wire SpiralSphere(Color color = default, Vector3 center = default, float radius = 1,
  1334.         Quaternion rotation = default, float lineSize = Wires.LINE_WIDTH) {
  1335.             GameObject go = gameObject;
  1336.             if (!IsSpiralSphere(center, radius, lineSize, rotation)) {
  1337.                 SetSpiralSphere(center, radius, lineSize, rotation);
  1338.                 _lr = Wires.MakeSpiralSphere(ref go, radius, center, rotation, color, lineSize);
  1339.             } //else { Debug.Log("don't need to recalculate spiral sphere " + name); }
  1340.             Wires.SetColor(_lr, color);
  1341.             return this;
  1342.         }
  1343.  
  1344.         public Wire Box(Vector3 size, Vector3 center = default, Quaternion rotation = default, Color color = default,
  1345.         float lineSize = Wires.LINE_WIDTH) {
  1346.             GameObject go = gameObject;
  1347.             if (!IsBox(center, size, rotation, lineSize)) {
  1348.                 SetBox(center, size, rotation, lineSize);
  1349.                 _lr = Wires.MakeBox(ref go, center, size, rotation, color, lineSize);
  1350.             } //else { Debug.Log("don't need to recalculate box " + name); }
  1351.             Wires.SetColor(_lr, color);
  1352.             return this;
  1353.         }
  1354.  
  1355.         public Wire Quaternion(Quaternion q, Color color, Vector3 position = default, Vector3[] startPoints = null,
  1356.             Quaternion orientation = default, int arcPoints = -1, float lineSize = Wires.LINE_WIDTH) {
  1357.             GameObject go = gameObject;
  1358.             q.ToAngleAxis(out float an, out Vector3 ax);
  1359.             if (startPoints == null) { startPoints = DefaultQuaternionVisualizationPoints; }
  1360.             if (!IsQuaternion(an, ax, position, startPoints, orientation, lineSize)) {
  1361.                 SetQuaternion(an, ax, position, startPoints, orientation, lineSize);
  1362.                 Wire[] childWires = ChildWires(startPoints.Length, true);
  1363.                 Wires.MakeQuaternion(ref go, childWires, ax, an, position, color, orientation, arcPoints, lineSize,
  1364.                     Wires.ARROW_SIZE, startPoints);
  1365.                 _lr = go.GetComponent<LineRenderer>();
  1366.             } //else { Debug.Log("don't need to recalculate quaternion " + name); }
  1367.             Wires.SetColor(_lr, color);
  1368.             return this;
  1369.         }
  1370.         private Wire[] ChildWires(int objectCount, bool createIfNoneExist) {
  1371.             Wire[] wireObjs = null;
  1372.             const string _name = "__";
  1373.             if (transform.childCount >= objectCount) {
  1374.                 int childrenWithWire = 0;
  1375.                 Transform[] children = new Transform[transform.childCount];
  1376.                 for (int i = 0; i < children.Length; ++i) { children[i] = transform.GetChild(i); }
  1377.                 Array.ForEach(children, (child) => {
  1378.                     if (child.name.Contains(_name) && child.GetComponent<Wire>() != null) { ++childrenWithWire; }
  1379.                 });
  1380.                 if (childrenWithWire >= objectCount) {
  1381.                     wireObjs = new Wire[objectCount];
  1382.                     int validLine = 0;
  1383.                     for (int i = 0; i < children.Length && validLine < wireObjs.Length; ++i) {
  1384.                         Wire w;
  1385.                         if (children[i].name.Contains(_name) && (w = children[i].GetComponent<Wire>()) != null)
  1386.                             wireObjs[validLine++] = w;
  1387.                     }
  1388.                 }
  1389.             }
  1390.             if (wireObjs == null && createIfNoneExist) {
  1391.                 wireObjs = new Wire[objectCount];
  1392.                 for (int i = 0; i < wireObjs.Length; ++i) {
  1393.                     Wire wireObject = Wires.MakeWire();
  1394.                     wireObject.name = _name + name + i;
  1395.                     wireObject.transform.SetParent(transform);
  1396.                     wireObjs[i] = wireObject;
  1397.                 }
  1398.             }
  1399.             return wireObjs;
  1400.         }
  1401.         public Wire CartesianPlane(Vector3 center, Quaternion rotation, Color color = default,
  1402.         float lineSize = Wires.LINE_WIDTH, float extents = 1, float increment = 0.25f) {
  1403.             bool colorIsSet = false;
  1404.             if (!IsCartesianPlane(center, rotation, lineSize, extents, increment)) {
  1405.                 SetCartesianPlane(center, rotation, lineSize, extents, increment);
  1406.                 Vector3 up = rotation * Vector3.up;
  1407.                 Vector3 right = rotation * Vector3.right;
  1408.                 Wire[] wires = ChildWires(_count, true);
  1409.                 Wires.MakeCartesianPlane(center, up, right, wires, color, lineSize, extents, increment);
  1410.                 colorIsSet = true;
  1411.                 Vector3 normal = Vector3.Cross(right, up).normalized;
  1412.                 Vector3[] points = new Vector3[] { center, center + normal * increment };
  1413.                 _lr = Wires.MakeLine(LineRenderer, points, color, lineSize, lineSize, LineEnd.Arrow);
  1414.             } //else { Debug.Log("don't need to recalculate quaternion " + name); }
  1415.             if (!colorIsSet) {
  1416.                 Wires.SetColor(LineRenderer, color);
  1417.                 Wire[] wires = ChildWires(_count, true);
  1418.                 Array.ForEach(wires, w => Wires.SetColor(w.LineRenderer, color));
  1419.             }
  1420.             return this;
  1421.         }
  1422.         public Wire Rectangle(Vector3 origin, Vector2 halfSize, Color a_color = default, Quaternion rotation = default,
  1423.         Vector2 offset2d = default, float lineSize = Wires.LINE_WIDTH) {
  1424.             //if(halfSize == default) { halfSize = Vector2.one / 2; }
  1425.             bool colorIsSet = false;
  1426.             if (!IsRectangle(origin, offset2d, halfSize, lineSize, rotation)) {
  1427.                 SetRectangle(origin, offset2d, halfSize, lineSize, rotation);
  1428.                 Wire[] wires = ChildWires(_count, true);
  1429.                 MakeRectangle(wires, origin, offset2d, halfSize, lineSize, a_color, rotation);
  1430.                 colorIsSet = true;
  1431.                 LineRenderer.startWidth = LineRenderer.endWidth = 0;
  1432.             } //else { Debug.Log("don't need to recalculate quaternion " + name); }
  1433.             if (!colorIsSet) {
  1434.                 //SetColor(lr, a_color);
  1435.                 Wire[] wires = ChildWires(_count, true);
  1436.                 Array.ForEach(wires, w => Wires.SetColor(w.LineRenderer, a_color));
  1437.             }
  1438.             return this;
  1439.         }
  1440.  
  1441.         public static void MakeRectangle(Wire[] wires, Vector3 origin, Vector2 position2D, Vector2 halfSize,
  1442.         float lineSize, Color a_color, Quaternion rotation) {
  1443.             Vector3[] corners = new Vector3[4];
  1444.             Wires.WriteRectangleCoordinates(corners, origin, rotation, halfSize, position2D);
  1445.             for (int i = 0; i < corners.Length; ++i) {
  1446.                 Vector3 a = corners[i];
  1447.                 Vector3 b = corners[(i + 1) % corners.Length];
  1448.                 wires[i].Line(a, b, a_color, lineSize).NumCapVertices = 4;
  1449.             }
  1450.         }
  1451.     }
  1452. }
  1453.  
Tags: C# Unity 3D Math
Add Comment
Please, Sign In to add comment