mvaganov

Wires.cs

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