UI 자동화를 위해 바인딩 기능 구현
- 유니티 에셋 인증 오류로 meta 재생성
This commit is contained in:
@@ -0,0 +1,701 @@
|
||||
using System;
|
||||
using UnityEngine.InputSystem.Utilities;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using UnityEngine.InputSystem.LowLevel;
|
||||
|
||||
////REVIEW: for vector2 visualizers of sticks, it could be useful to also visualize deadzones and raw values
|
||||
|
||||
namespace UnityEngine.InputSystem.Samples
|
||||
{
|
||||
internal static class VisualizationHelpers
|
||||
{
|
||||
public enum Axis { X, Y, Z }
|
||||
|
||||
public abstract class Visualizer
|
||||
{
|
||||
public abstract void OnDraw(Rect rect);
|
||||
public abstract void AddSample(object value, double time);
|
||||
}
|
||||
|
||||
public abstract class ValueVisualizer<TValue> : Visualizer
|
||||
where TValue : struct
|
||||
{
|
||||
public RingBuffer<TValue> samples;
|
||||
public RingBuffer<GUIContent> samplesText;
|
||||
|
||||
protected ValueVisualizer(int numSamples = 10)
|
||||
{
|
||||
samples = new RingBuffer<TValue>(numSamples);
|
||||
samplesText = new RingBuffer<GUIContent>(numSamples);
|
||||
}
|
||||
|
||||
public override void AddSample(object value, double time)
|
||||
{
|
||||
var v = default(TValue);
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
if (!(value is TValue val))
|
||||
throw new ArgumentException(
|
||||
$"Expecting value of type '{typeof(TValue).Name}' but value of type '{value?.GetType().Name}' instead",
|
||||
nameof(value));
|
||||
v = val;
|
||||
}
|
||||
|
||||
samples.Append(v);
|
||||
samplesText.Append(new GUIContent(v.ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
// Visualizes integer and real type primitives.
|
||||
public class ScalarVisualizer<TValue> : ValueVisualizer<TValue>
|
||||
where TValue : struct
|
||||
{
|
||||
public TValue limitMin;
|
||||
public TValue limitMax;
|
||||
public TValue min;
|
||||
public TValue max;
|
||||
|
||||
public ScalarVisualizer(int numSamples = 10)
|
||||
: base(numSamples)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnDraw(Rect rect)
|
||||
{
|
||||
// For now, only draw the current value.
|
||||
DrawRectangle(rect, new Color(1, 1, 1, 0.1f));
|
||||
if (samples.count == 0)
|
||||
return;
|
||||
var sample = samples[samples.count - 1];
|
||||
if (Compare(sample, default) == 0)
|
||||
return;
|
||||
if (Compare(limitMin, default) != 0)
|
||||
{
|
||||
// Two-way visualization with positive and negative side.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
// One-way visualization with only positive side.
|
||||
var ratio = Divide(sample, limitMax);
|
||||
var fillRect = rect;
|
||||
fillRect.width = rect.width * ratio;
|
||||
DrawRectangle(fillRect, new Color(0, 1, 0, 0.75f));
|
||||
|
||||
var valuePos = new Vector2(fillRect.xMax, fillRect.y + fillRect.height / 2);
|
||||
DrawText(samplesText[samples.count - 1], valuePos, ValueTextStyle);
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddSample(object value, double time)
|
||||
{
|
||||
base.AddSample(value, time);
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
var val = (TValue)value;
|
||||
if (Compare(min, val) > 0)
|
||||
min = val;
|
||||
if (Compare(max, val) < 0)
|
||||
max = val;
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe int Compare(TValue left, TValue right)
|
||||
{
|
||||
var leftPtr = UnsafeUtility.AddressOf(ref left);
|
||||
var rightPtr = UnsafeUtility.AddressOf(ref right);
|
||||
if (typeof(TValue) == typeof(int))
|
||||
return ((int*)leftPtr)->CompareTo(*(int*)rightPtr);
|
||||
if (typeof(TValue) == typeof(float))
|
||||
return ((float*)leftPtr)->CompareTo(*(float*)rightPtr);
|
||||
throw new NotImplementedException("Scalar value type: " + typeof(TValue).Name);
|
||||
}
|
||||
|
||||
private static unsafe void Subtract(ref TValue left, TValue right)
|
||||
{
|
||||
var leftPtr = UnsafeUtility.AddressOf(ref left);
|
||||
var rightPtr = UnsafeUtility.AddressOf(ref right);
|
||||
|
||||
if (typeof(TValue) == typeof(int))
|
||||
*(int*)leftPtr = *(int*)leftPtr - *(int*)rightPtr;
|
||||
if (typeof(TValue) == typeof(float))
|
||||
*(float*)leftPtr = *(float*)leftPtr - *(float*)rightPtr;
|
||||
throw new NotImplementedException("Scalar value type: " + typeof(TValue).Name);
|
||||
}
|
||||
|
||||
private static unsafe float Divide(TValue left, TValue right)
|
||||
{
|
||||
var leftPtr = UnsafeUtility.AddressOf(ref left);
|
||||
var rightPtr = UnsafeUtility.AddressOf(ref right);
|
||||
|
||||
if (typeof(TValue) == typeof(int))
|
||||
return (float)*(int*)leftPtr / *(int*)rightPtr;
|
||||
if (typeof(TValue) == typeof(float))
|
||||
return *(float*)leftPtr / *(float*)rightPtr;
|
||||
throw new NotImplementedException("Scalar value type: " + typeof(TValue).Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Visualizes .current device value
|
||||
public class CurrentDeviceVisualizer : Visualizer
|
||||
{
|
||||
private InputDevice m_CurrentDevice = null;
|
||||
|
||||
public override void OnDraw(Rect rect)
|
||||
{
|
||||
// For now, only draw the current value.
|
||||
DrawRectangle(rect, new Color(1, 1, 1, 0.1f));
|
||||
|
||||
var name = m_CurrentDevice != null ? m_CurrentDevice.name : "null";
|
||||
DrawText(name, new Vector2(rect.xMin + 4, (rect.yMin + rect.yMax) / 2.0f), ValueTextStyle);
|
||||
}
|
||||
|
||||
public override void AddSample(object value, double time)
|
||||
{
|
||||
var device = (InputDevice)value;
|
||||
if (device is Gamepad)
|
||||
m_CurrentDevice = Gamepad.current;
|
||||
else if (device is Mouse)
|
||||
m_CurrentDevice = Mouse.current;
|
||||
else if (device is Pen)
|
||||
m_CurrentDevice = Pen.current;
|
||||
else if (device is Pointer) // should be last, because it's a base class for Mouse and Pen
|
||||
m_CurrentDevice = Pointer.current;
|
||||
else
|
||||
throw new ArgumentException(
|
||||
$"Expected device type that implements .current, but got '{device.name}' (deviceId: {device.deviceId}) instead ");
|
||||
}
|
||||
}
|
||||
|
||||
////TODO: allow asymmetric center (i.e. center not being a midpoint of rectangle)
|
||||
////TODO: enforce proper proportion between X and Y; it's confusing that X and Y can have different units yet have the same length
|
||||
public class Vector2Visualizer : ValueVisualizer<Vector2>
|
||||
{
|
||||
// Our value space extends radially from the center, i.e. we have
|
||||
// 360 discrete directions. Sampling at that granularity doesn't work
|
||||
// super well in visualizations so we quantize to 3 degree increments.
|
||||
public Vector2[] maximums = new Vector2[360 / 3];
|
||||
public Vector2 limits = new Vector2(1, 1);
|
||||
|
||||
private GUIContent limitsXText;
|
||||
private GUIContent limitsYText;
|
||||
|
||||
public Vector2Visualizer(int numSamples = 10)
|
||||
: base(numSamples)
|
||||
{
|
||||
}
|
||||
|
||||
public override void AddSample(object value, double time)
|
||||
{
|
||||
base.AddSample(value, time);
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
// Keep track of radial maximums.
|
||||
var vector = (Vector2)value;
|
||||
var angle = Vector2.SignedAngle(Vector2.right, vector);
|
||||
if (angle < 0)
|
||||
angle = 360 + angle;
|
||||
var angleInt = Mathf.FloorToInt(angle) / 3;
|
||||
if (vector.sqrMagnitude > maximums[angleInt].sqrMagnitude)
|
||||
maximums[angleInt] = vector;
|
||||
|
||||
// Extend limits if value is out of range.
|
||||
var limitX = Mathf.Max(Mathf.Abs(vector.x), limits.x);
|
||||
var limitY = Mathf.Max(Mathf.Abs(vector.y), limits.y);
|
||||
if (!Mathf.Approximately(limitX, limits.x))
|
||||
{
|
||||
limits.x = limitX;
|
||||
limitsXText = null;
|
||||
}
|
||||
if (!Mathf.Approximately(limitY, limits.y))
|
||||
{
|
||||
limits.y = limitY;
|
||||
limitsYText = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnDraw(Rect rect)
|
||||
{
|
||||
DrawRectangle(rect, new Color(1, 1, 1, 0.1f));
|
||||
DrawAxis(Axis.X, rect, new Color(0, 1, 0, 0.75f));
|
||||
DrawAxis(Axis.Y, rect, new Color(0, 1, 0, 0.75f));
|
||||
|
||||
var sampleCount = samples.count;
|
||||
if (sampleCount == 0)
|
||||
return;
|
||||
|
||||
// If limits aren't (1,1), show the actual values.
|
||||
if (limits != new Vector2(1, 1))
|
||||
{
|
||||
if (limitsXText == null)
|
||||
limitsXText = new GUIContent(limits.x.ToString());
|
||||
if (limitsYText == null)
|
||||
limitsYText = new GUIContent(limits.y.ToString());
|
||||
|
||||
var limitsXSize = ValueTextStyle.CalcSize(limitsXText);
|
||||
var limitsXPos = new Vector2(rect.x - limitsXSize.x, rect.y - 5);
|
||||
var limitsYPos = new Vector2(rect.xMax, rect.yMax);
|
||||
|
||||
DrawText(limitsXText, limitsXPos, ValueTextStyle);
|
||||
DrawText(limitsYText, limitsYPos, ValueTextStyle);
|
||||
}
|
||||
|
||||
// Draw maximums.
|
||||
var numMaximums = 0;
|
||||
var firstMaximumPos = default(Vector2);
|
||||
var lastMaximumPos = default(Vector2);
|
||||
for (var i = 0; i < 360 / 3; ++i)
|
||||
{
|
||||
var value = maximums[i];
|
||||
if (value == default)
|
||||
continue;
|
||||
var valuePos = PixelPosForValue(value, rect);
|
||||
if (numMaximums > 0)
|
||||
DrawLine(lastMaximumPos, valuePos, new Color(1, 1, 1, 0.25f));
|
||||
else
|
||||
firstMaximumPos = valuePos;
|
||||
lastMaximumPos = valuePos;
|
||||
++numMaximums;
|
||||
}
|
||||
if (numMaximums > 1)
|
||||
DrawLine(lastMaximumPos, firstMaximumPos, new Color(1, 1, 1, 0.25f));
|
||||
|
||||
// Draw samples.
|
||||
var alphaStep = 1f / sampleCount;
|
||||
var alpha = 1f;
|
||||
for (var i = sampleCount - 1; i >= 0; --i) // Go newest to oldest.
|
||||
{
|
||||
var value = samples[i];
|
||||
var valueRect = RectForValue(value, rect);
|
||||
DrawRectangle(valueRect, new Color(1, 0, 0, alpha));
|
||||
alpha -= alphaStep;
|
||||
}
|
||||
|
||||
// Print value of most recent sample. Draw last so
|
||||
// we draw over the other stuff.
|
||||
var lastSample = samples[sampleCount - 1];
|
||||
var lastSamplePos = PixelPosForValue(lastSample, rect);
|
||||
lastSamplePos.x += 3;
|
||||
lastSamplePos.y += 3;
|
||||
DrawText(samplesText[sampleCount - 1], lastSamplePos, ValueTextStyle);
|
||||
}
|
||||
|
||||
private Rect RectForValue(Vector2 value, Rect rect)
|
||||
{
|
||||
var pos = PixelPosForValue(value, rect);
|
||||
return new Rect(pos.x - 1, pos.y - 1, 2, 2);
|
||||
}
|
||||
|
||||
private Vector2 PixelPosForValue(Vector2 value, Rect rect)
|
||||
{
|
||||
var center = rect.center;
|
||||
var x = Mathf.Abs(value.x) / limits.x * Mathf.Sign(value.x);
|
||||
var y = Mathf.Abs(value.y) / limits.y * Mathf.Sign(value.y) * -1; // GUI Y is upside down.
|
||||
var xInPixels = x * rect.width / 2;
|
||||
var yInPixels = y * rect.height / 2;
|
||||
return new Vector2(center.x + xInPixels,
|
||||
center.y + yInPixels);
|
||||
}
|
||||
}
|
||||
|
||||
// Y axis is time, X axis can be multiple visualizations.
|
||||
public class TimelineVisualizer : Visualizer
|
||||
{
|
||||
public bool showLegend { get; set; }
|
||||
public bool showLimits { get; set; }
|
||||
public TimeUnit timeUnit { get; set; } = TimeUnit.Seconds;
|
||||
public GUIContent valueUnit { get; set; }
|
||||
////REVIEW: should this be per timeline?
|
||||
public int timelineCount => m_Timelines != null ? m_Timelines.Length : 0;
|
||||
public int historyDepth { get; set; } = 100;
|
||||
|
||||
public Vector2 limitsY
|
||||
{
|
||||
get => m_LimitsY;
|
||||
set
|
||||
{
|
||||
m_LimitsY = value;
|
||||
m_LimitsYMin = null;
|
||||
m_LimitsYMax = null;
|
||||
}
|
||||
}
|
||||
|
||||
public TimelineVisualizer(float totalTimeUnitsShown = 4)
|
||||
{
|
||||
m_TotalTimeUnitsShown = totalTimeUnitsShown;
|
||||
}
|
||||
|
||||
public override void OnDraw(Rect rect)
|
||||
{
|
||||
var endTime = Time.realtimeSinceStartup;
|
||||
var startTime = endTime - m_TotalTimeUnitsShown;
|
||||
var endFrame = InputState.updateCount;
|
||||
var startFrame = endFrame - (int)m_TotalTimeUnitsShown;
|
||||
|
||||
for (var i = 0; i < timelineCount; ++i)
|
||||
{
|
||||
var timeline = m_Timelines[i];
|
||||
var sampleCount = timeUnit == TimeUnit.Frames
|
||||
? timeline.frameSamples.count
|
||||
: timeline.timeSamples.count;
|
||||
|
||||
// Set up clip rect so that we can do stuff like render lines to samples
|
||||
// falling outside the render rectangle and have them get clipped.
|
||||
GUI.BeginGroup(rect);
|
||||
var plotType = timeline.plotType;
|
||||
var lastPos = default(Vector2);
|
||||
var timeUnitsPerPixel = rect.width / m_TotalTimeUnitsShown;
|
||||
var color = m_Timelines[i].color;
|
||||
for (var n = sampleCount - 1; n >= 0; --n)
|
||||
{
|
||||
var sample = timeUnit == TimeUnit.Frames
|
||||
? timeline.frameSamples[n].value
|
||||
: timeline.timeSamples[n].value;
|
||||
|
||||
////TODO: respect limitsY
|
||||
|
||||
float y;
|
||||
if (sample.isEmpty)
|
||||
y = 0.5f;
|
||||
else
|
||||
y = sample.ToSingle();
|
||||
|
||||
y /= limitsY.y;
|
||||
|
||||
var deltaTime = timeUnit == TimeUnit.Frames
|
||||
? timeline.frameSamples[n].frame - startFrame
|
||||
: timeline.timeSamples[n].time - startTime;
|
||||
var pos = new Vector2(deltaTime * timeUnitsPerPixel, rect.height - y * rect.height);
|
||||
|
||||
if (plotType == PlotType.LineGraph)
|
||||
{
|
||||
if (n != sampleCount - 1)
|
||||
{
|
||||
DrawLine(lastPos, pos, color, 2);
|
||||
if (pos.x < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (plotType == PlotType.BarChart)
|
||||
{
|
||||
////TODO: make rectangles have a progressively stronger hue or saturation
|
||||
var barRect = new Rect(pos.x, pos.y, timeUnitsPerPixel, y * limitsY.y * rect.height);
|
||||
DrawRectangle(barRect, color);
|
||||
}
|
||||
|
||||
lastPos = pos;
|
||||
}
|
||||
GUI.EndGroup();
|
||||
}
|
||||
|
||||
if (showLegend && timelineCount > 0)
|
||||
{
|
||||
var legendRect = rect;
|
||||
legendRect.x += rect.width + 2;
|
||||
legendRect.width = 400;
|
||||
legendRect.height = ValueTextStyle.CalcHeight(m_Timelines[0].name, 400);
|
||||
for (var i = 0; i < m_Timelines.Length; ++i)
|
||||
{
|
||||
var colorTagRect = legendRect;
|
||||
colorTagRect.width = 5;
|
||||
var labelRect = legendRect;
|
||||
labelRect.x += 8;
|
||||
labelRect.width -= 8;
|
||||
|
||||
DrawRectangle(colorTagRect, m_Timelines[i].color);
|
||||
DrawText(m_Timelines[i].name, labelRect.position, ValueTextStyle);
|
||||
|
||||
legendRect.y += labelRect.height + 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (showLimits)
|
||||
{
|
||||
if (m_LimitsYMax == null)
|
||||
m_LimitsYMax = new GUIContent(m_LimitsY.y.ToString());
|
||||
if (m_LimitsYMin == null)
|
||||
m_LimitsYMin = new GUIContent(m_LimitsY.x.ToString());
|
||||
|
||||
DrawText(m_LimitsYMax, new Vector2(rect.x + rect.width, rect.y), ValueTextStyle);
|
||||
DrawText(m_LimitsYMin, new Vector2(rect.x + rect.width, rect.y + rect.height), ValueTextStyle);
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddSample(object value, double time)
|
||||
{
|
||||
if (timelineCount == 0)
|
||||
throw new InvalidOperationException("Must have set up a timeline first");
|
||||
AddSample(0, PrimitiveValue.FromObject(value), (float)time);
|
||||
}
|
||||
|
||||
public int AddTimeline(string name, Color color, PlotType plotType = default)
|
||||
{
|
||||
var timeline = new Timeline
|
||||
{
|
||||
name = new GUIContent(name),
|
||||
color = color,
|
||||
plotType = plotType,
|
||||
};
|
||||
if (timeUnit == TimeUnit.Frames)
|
||||
timeline.frameSamples = new RingBuffer<FrameSample>(historyDepth);
|
||||
else
|
||||
timeline.timeSamples = new RingBuffer<TimeSample>(historyDepth);
|
||||
|
||||
var index = timelineCount;
|
||||
Array.Resize(ref m_Timelines, timelineCount + 1);
|
||||
m_Timelines[index] = timeline;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public int GetTimeline(string name)
|
||||
{
|
||||
for (var i = 0; i < timelineCount; ++i)
|
||||
if (string.Compare(m_Timelines[i].name.text, name, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Add a time-based sample.
|
||||
public void AddSample(int timelineIndex, PrimitiveValue value, float time)
|
||||
{
|
||||
m_Timelines[timelineIndex].timeSamples.Append(new TimeSample
|
||||
{
|
||||
value = value,
|
||||
time = time
|
||||
});
|
||||
}
|
||||
|
||||
// Add a frame-based sample.
|
||||
public ref PrimitiveValue GetOrCreateSample(int timelineIndex, int frame)
|
||||
{
|
||||
ref var timeline = ref m_Timelines[timelineIndex];
|
||||
ref var samples = ref timeline.frameSamples;
|
||||
var count = samples.count;
|
||||
if (count > 0)
|
||||
{
|
||||
if (samples[count - 1].frame == frame)
|
||||
return ref samples[count - 1].value;
|
||||
|
||||
Debug.Assert(samples[count - 1].frame < frame, "Frame numbers must be ascending");
|
||||
}
|
||||
|
||||
return ref samples.Append(new FrameSample {frame = frame}).value;
|
||||
}
|
||||
|
||||
private float m_TotalTimeUnitsShown;
|
||||
private Vector2 m_LimitsY = new Vector2(-1, 1);
|
||||
private GUIContent m_LimitsYMin;
|
||||
private GUIContent m_LimitsYMax;
|
||||
private Timeline[] m_Timelines;
|
||||
|
||||
private struct TimeSample
|
||||
{
|
||||
public PrimitiveValue value;
|
||||
public float time;
|
||||
}
|
||||
|
||||
private struct FrameSample
|
||||
{
|
||||
public PrimitiveValue value;
|
||||
public int frame;
|
||||
}
|
||||
|
||||
private struct Timeline
|
||||
{
|
||||
public GUIContent name;
|
||||
public Color color;
|
||||
public RingBuffer<TimeSample> timeSamples;
|
||||
public RingBuffer<FrameSample> frameSamples;
|
||||
public PrimitiveValue minValue;
|
||||
public PrimitiveValue maxValue;
|
||||
public PlotType plotType;
|
||||
}
|
||||
|
||||
public enum PlotType
|
||||
{
|
||||
LineGraph,
|
||||
BarChart,
|
||||
}
|
||||
|
||||
public enum TimeUnit
|
||||
{
|
||||
Seconds,
|
||||
Frames,
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawAxis(Axis axis, Rect rect, Color color = default, float width = 1)
|
||||
{
|
||||
Vector2 start, end, tickOffset;
|
||||
switch (axis)
|
||||
{
|
||||
case Axis.X:
|
||||
start = new Vector2(rect.x, rect.y + rect.height / 2);
|
||||
end = new Vector2(start.x + rect.width, rect.y + rect.height / 2);
|
||||
tickOffset = new Vector2(0, 3);
|
||||
break;
|
||||
|
||||
case Axis.Y:
|
||||
start = new Vector2(rect.x + rect.width / 2, rect.y);
|
||||
end = new Vector2(start.x, rect.y + rect.height);
|
||||
tickOffset = new Vector2(3, 0);
|
||||
break;
|
||||
|
||||
case Axis.Z:
|
||||
// From bottom left corner to upper right corner.
|
||||
start = new Vector2(rect.x, rect.yMax);
|
||||
end = new Vector2(rect.xMax, rect.y);
|
||||
tickOffset = new Vector2(1.5f, 1.5f);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
////TODO: label limits
|
||||
|
||||
DrawLine(start, end, color, width);
|
||||
DrawLine(start - tickOffset, start + tickOffset, color, width);
|
||||
DrawLine(end - tickOffset, end + tickOffset, color, width);
|
||||
}
|
||||
|
||||
public static void DrawRectangle(Rect rect, Color color)
|
||||
{
|
||||
var savedColor = GUI.color;
|
||||
GUI.color = color;
|
||||
GUI.DrawTexture(rect, OnePixTex);
|
||||
GUI.color = savedColor;
|
||||
}
|
||||
|
||||
public static void DrawText(string text, Vector2 pos, GUIStyle style)
|
||||
{
|
||||
var content = new GUIContent(text);
|
||||
DrawText(content, pos, style);
|
||||
}
|
||||
|
||||
public static void DrawText(GUIContent text, Vector2 pos, GUIStyle style)
|
||||
{
|
||||
var content = new GUIContent(text);
|
||||
var size = style.CalcSize(content);
|
||||
var rect = new Rect(pos.x, pos.y, size.x, size.y);
|
||||
style.Draw(rect, content, false, false, false, false);
|
||||
}
|
||||
|
||||
// Adapted from http://wiki.unity3d.com/index.php?title=DrawLine
|
||||
public static void DrawLine(Vector2 pointA, Vector2 pointB, Color color = default, float width = 1)
|
||||
{
|
||||
// Save the current GUI matrix, since we're going to make changes to it.
|
||||
var matrix = GUI.matrix;
|
||||
|
||||
// Store current GUI color, so we can switch it back later,
|
||||
// and set the GUI color to the color parameter
|
||||
var savedColor = GUI.color;
|
||||
GUI.color = color;
|
||||
|
||||
// Determine the angle of the line.
|
||||
var angle = Vector3.Angle(pointB - pointA, Vector2.right);
|
||||
|
||||
// Vector3.Angle always returns a positive number.
|
||||
// If pointB is above pointA, then angle needs to be negative.
|
||||
if (pointA.y > pointB.y)
|
||||
angle = -angle;
|
||||
|
||||
// Use ScaleAroundPivot to adjust the size of the line.
|
||||
// We could do this when we draw the texture, but by scaling it here we can use
|
||||
// non-integer values for the width and length (such as sub 1 pixel widths).
|
||||
// Note that the pivot point is at +.5 from pointA.y, this is so that the width of the line
|
||||
// is centered on the origin at pointA.
|
||||
GUIUtility.ScaleAroundPivot(new Vector2((pointB - pointA).magnitude, width), new Vector2(pointA.x, pointA.y + 0.5f));
|
||||
|
||||
// Set the rotation for the line.
|
||||
// The angle was calculated with pointA as the origin.
|
||||
GUIUtility.RotateAroundPivot(angle, pointA);
|
||||
|
||||
// Finally, draw the actual line.
|
||||
// We're really only drawing a 1x1 texture from pointA.
|
||||
// The matrix operations done with ScaleAroundPivot and RotateAroundPivot will make this
|
||||
// render with the proper width, length, and angle.
|
||||
GUI.DrawTexture(new Rect(pointA.x, pointA.y, 1, 1), OnePixTex);
|
||||
|
||||
// We're done. Restore the GUI matrix and GUI color to whatever they were before.
|
||||
GUI.matrix = matrix;
|
||||
GUI.color = savedColor;
|
||||
}
|
||||
|
||||
private static Texture2D s_OnePixTex;
|
||||
private static GUIStyle s_ValueTextStyle;
|
||||
|
||||
internal static GUIStyle ValueTextStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_ValueTextStyle == null)
|
||||
{
|
||||
s_ValueTextStyle = new GUIStyle();
|
||||
s_ValueTextStyle.fontSize -= 2;
|
||||
s_ValueTextStyle.normal.textColor = Color.white;
|
||||
}
|
||||
return s_ValueTextStyle;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Texture2D OnePixTex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_OnePixTex == null)
|
||||
s_OnePixTex = new Texture2D(1, 1);
|
||||
return s_OnePixTex;
|
||||
}
|
||||
}
|
||||
|
||||
public struct RingBuffer<TValue>
|
||||
{
|
||||
public TValue[] array;
|
||||
public int head;
|
||||
public int count;
|
||||
|
||||
public RingBuffer(int size)
|
||||
{
|
||||
array = new TValue[size];
|
||||
head = 0;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
public ref TValue Append(TValue value)
|
||||
{
|
||||
int index;
|
||||
var bufferSize = array.Length;
|
||||
if (count < bufferSize)
|
||||
{
|
||||
Debug.Assert(head == 0, "Head can't have moved if buffer isn't full yet");
|
||||
index = count;
|
||||
++count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Buffer is full. Bump head.
|
||||
index = (head + count) % bufferSize;
|
||||
++head;
|
||||
}
|
||||
array[index] = value;
|
||||
return ref array[index];
|
||||
}
|
||||
|
||||
public ref TValue this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index < 0 || index >= count)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return ref array[(head + index) % array.Length];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user