mvaganov

Lines.cs

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