summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/CommandBuilder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/CommandBuilder.cs')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/CommandBuilder.cs3062
1 files changed, 3062 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/CommandBuilder.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/CommandBuilder.cs
new file mode 100644
index 0000000..7932907
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/CommandBuilder.cs
@@ -0,0 +1,3062 @@
+using System;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Mathematics;
+using Unity.Jobs.LowLevel.Unsafe;
+using UnityEngine;
+using System.Collections.Generic;
+using Unity.Burst;
+using UnityEngine.Profiling;
+using Unity.Collections;
+using Unity.Jobs;
+using UnityEngine.Rendering;
+using System.Runtime.InteropServices;
+
+namespace Pathfinding.Drawing {
+ using static DrawingData;
+ using BitPackedMeta = DrawingData.BuilderData.BitPackedMeta;
+ using Pathfinding.Drawing.Text;
+ using Unity.Profiling;
+
+ /// <summary>
+ /// Specifies text alignment relative to an anchor point.
+ ///
+ /// <code>
+ /// Draw.Label2D(transform.position, "Hello World", 14, LabelAlignment.TopCenter);
+ /// </code>
+ /// <code>
+ /// // Draw the label 20 pixels below the object
+ /// Draw.Label2D(transform.position, "Hello World", 14, LabelAlignment.TopCenter.withPixelOffset(0, -20));
+ /// </code>
+ ///
+ /// See: <see cref="Draw.Label2D"/>
+ /// See: <see cref="Draw.Label3D"/>
+ /// </summary>
+ public struct LabelAlignment {
+ /// <summary>
+ /// Where on the text's bounding box to anchor the text.
+ ///
+ /// The pivot is specified in relative coordinates, where (0,0) is the bottom left corner and (1,1) is the top right corner.
+ /// </summary>
+ public float2 relativePivot;
+ /// <summary>How much to move the text in screen-space</summary>
+ public float2 pixelOffset;
+
+ public static readonly LabelAlignment TopLeft = new LabelAlignment { relativePivot = new float2(0.0f, 1.0f), pixelOffset = new float2(0, 0) };
+ public static readonly LabelAlignment MiddleLeft = new LabelAlignment { relativePivot = new float2(0.0f, 0.5f), pixelOffset = new float2(0, 0) };
+ public static readonly LabelAlignment BottomLeft = new LabelAlignment { relativePivot = new float2(0.0f, 0.0f), pixelOffset = new float2(0, 0) };
+ public static readonly LabelAlignment BottomCenter = new LabelAlignment { relativePivot = new float2(0.5f, 0.0f), pixelOffset = new float2(0, 0) };
+ public static readonly LabelAlignment BottomRight = new LabelAlignment { relativePivot = new float2(1.0f, 0.0f), pixelOffset = new float2(0, 0) };
+ public static readonly LabelAlignment MiddleRight = new LabelAlignment { relativePivot = new float2(1.0f, 0.5f), pixelOffset = new float2(0, 0) };
+ public static readonly LabelAlignment TopRight = new LabelAlignment { relativePivot = new float2(1.0f, 1.0f), pixelOffset = new float2(0, 0) };
+ public static readonly LabelAlignment TopCenter = new LabelAlignment { relativePivot = new float2(0.5f, 1.0f), pixelOffset = new float2(0, 0) };
+ public static readonly LabelAlignment Center = new LabelAlignment { relativePivot = new float2(0.5f, 0.5f), pixelOffset = new float2(0, 0) };
+
+ /// <summary>
+ /// Moves the text by the specified amount of pixels in screen-space.
+ ///
+ /// <code>
+ /// // Draw the label 20 pixels below the object
+ /// Draw.Label2D(transform.position, "Hello World", 14, LabelAlignment.TopCenter.withPixelOffset(0, -20));
+ /// </code>
+ /// </summary>
+ public LabelAlignment withPixelOffset (float x, float y) {
+ return new LabelAlignment {
+ relativePivot = this.relativePivot,
+ pixelOffset = new float2(x, y),
+ };
+ }
+ }
+
+ /// <summary>Maximum allowed delay for a job that is drawing to a command buffer</summary>
+ public enum AllowedDelay {
+ /// <summary>
+ /// If the job is not complete at the end of the frame, drawing will block until it is completed.
+ /// This is recommended for most jobs that are expected to complete within a single frame.
+ /// </summary>
+ EndOfFrame,
+ /// <summary>
+ /// Wait indefinitely for the job to complete, and only submit the results for rendering once it is done.
+ /// This is recommended for long running jobs that may take many frames to complete.
+ /// </summary>
+ Infinite,
+ }
+
+ /// <summary>Some static fields that need to be in a separate class because Burst doesn't support them</summary>
+ static class CommandBuilderSamplers {
+ internal static readonly ProfilerMarker MarkerConvert = new ProfilerMarker("Convert");
+ internal static readonly ProfilerMarker MarkerSetLayout = new ProfilerMarker("SetLayout");
+ internal static readonly ProfilerMarker MarkerUpdateVertices = new ProfilerMarker("UpdateVertices");
+ internal static readonly ProfilerMarker MarkerUpdateIndices = new ProfilerMarker("UpdateIndices");
+ internal static readonly ProfilerMarker MarkerSubmesh = new ProfilerMarker("Submesh");
+ internal static readonly ProfilerMarker MarkerUpdateBuffer = new ProfilerMarker("UpdateComputeBuffer");
+
+ internal static readonly ProfilerMarker MarkerProcessCommands = new ProfilerMarker("Commands");
+ internal static readonly ProfilerMarker MarkerCreateTriangles = new ProfilerMarker("CreateTriangles");
+ }
+
+ /// <summary>
+ /// Builder for drawing commands.
+ /// You can use this to queue many drawing commands. The commands will be queued for rendering when you call the Dispose method.
+ /// It is recommended that you use the <a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement">using statement</a> which automatically calls the Dispose method.
+ ///
+ /// <code>
+ /// // Create a new CommandBuilder
+ /// using (var draw = DrawingManager.GetBuilder()) {
+ /// // Use the exact same API as the global Draw class
+ /// draw.WireBox(Vector3.zero, Vector3.one);
+ /// }
+ /// </code>
+ ///
+ /// Warning: You must call either <see cref="Dispose"/> or <see cref="DiscardAndDispose"/> when you are done with this object to avoid memory leaks.
+ /// </summary>
+ [StructLayout(LayoutKind.Sequential)]
+ [BurstCompile]
+ public partial struct CommandBuilder : IDisposable {
+ // Note: Many fields/methods are explicitly marked as private. This is because doxygen otherwise thinks they are public by default (like struct members are in c++)
+
+ [NativeDisableUnsafePtrRestriction]
+ internal unsafe UnsafeAppendBuffer* buffer;
+
+ private GCHandle gizmos;
+
+ [NativeSetThreadIndex]
+ private int threadIndex;
+
+ private DrawingData.BuilderData.BitPackedMeta uniqueID;
+
+ internal unsafe CommandBuilder(UnsafeAppendBuffer* buffer, GCHandle gizmos, int threadIndex, DrawingData.BuilderData.BitPackedMeta uniqueID) {
+ this.buffer = buffer;
+ this.gizmos = gizmos;
+ this.threadIndex = threadIndex;
+ this.uniqueID = uniqueID;
+ }
+
+
+ internal CommandBuilder(DrawingData gizmos, Hasher hasher, RedrawScope frameRedrawScope, RedrawScope customRedrawScope, bool isGizmos, bool isBuiltInCommandBuilder, int sceneModeVersion) {
+ // We need to use a GCHandle instead of a normal reference to be able to pass this object to burst compiled function pointers.
+ // The NativeSetClassTypeToNullOnSchedule unfortunately only works together with the job system, not with raw functions.
+ this.gizmos = GCHandle.Alloc(gizmos, GCHandleType.Normal);
+
+ threadIndex = 0;
+ uniqueID = gizmos.data.Reserve(isBuiltInCommandBuilder);
+ gizmos.data.Get(uniqueID).Init(hasher, frameRedrawScope, customRedrawScope, isGizmos, gizmos.GetNextDrawOrderIndex(), sceneModeVersion);
+ unsafe {
+ buffer = gizmos.data.Get(uniqueID).bufferPtr;
+ }
+ }
+
+ internal unsafe int BufferSize {
+ get {
+ return buffer->Length;
+ }
+ set {
+ buffer->Length = value;
+ }
+ }
+
+ /// <summary>
+ /// Wrapper for drawing in the XY plane.
+ ///
+ /// <code>
+ /// var p1 = new Vector2(0, 1);
+ /// var p2 = new Vector2(5, 7);
+ ///
+ /// // Draw it in the XY plane
+ /// Draw.xy.Line(p1, p2);
+ ///
+ /// // Draw it in the XZ plane
+ /// Draw.xz.Line(p1, p2);
+ /// </code>
+ ///
+ /// See: 2d-drawing (view in online documentation for working links)
+ /// See: <see cref="Draw.xz"/>
+ /// </summary>
+ public CommandBuilder2D xy => new CommandBuilder2D(this, true);
+
+ /// <summary>
+ /// Wrapper for drawing in the XZ plane.
+ ///
+ /// <code>
+ /// var p1 = new Vector2(0, 1);
+ /// var p2 = new Vector2(5, 7);
+ ///
+ /// // Draw it in the XY plane
+ /// Draw.xy.Line(p1, p2);
+ ///
+ /// // Draw it in the XZ plane
+ /// Draw.xz.Line(p1, p2);
+ /// </code>
+ ///
+ /// See: 2d-drawing (view in online documentation for working links)
+ /// See: <see cref="Draw.xy"/>
+ /// </summary>
+ public CommandBuilder2D xz => new CommandBuilder2D(this, false);
+
+ static readonly float3 DEFAULT_UP = new float3(0, 1, 0);
+
+ /// <summary>
+ /// Can be set to render specifically to these cameras.
+ /// If you set this property to an array of cameras then this command builder will only be rendered
+ /// to the specified cameras. Setting this property bypasses <see cref="Drawing.DrawingManager.allowRenderToRenderTextures"/>.
+ /// The camera will be rendered to even if it renders to a render texture.
+ ///
+ /// A null value indicates that all valid cameras should be rendered to. This is the default value.
+ ///
+ /// <code>
+ /// var draw = DrawingManager.GetBuilder(true);
+ ///
+ /// draw.cameraTargets = new Camera[] { myCamera };
+ /// // This sphere will only be rendered to myCamera
+ /// draw.WireSphere(Vector3.zero, 0.5f, Color.black);
+ /// draw.Dispose();
+ /// </code>
+ ///
+ /// See: advanced (view in online documentation for working links)
+ /// </summary>
+ public Camera[] cameraTargets {
+ get {
+ if (gizmos.IsAllocated && gizmos.Target != null) {
+ var target = gizmos.Target as DrawingData;
+ if (target.data.StillExists(uniqueID)) {
+ return target.data.Get(uniqueID).meta.cameraTargets;
+ }
+ }
+ throw new System.Exception("Cannot get cameraTargets because the command builder has already been disposed or does not exist.");
+ }
+ set {
+ if (uniqueID.isBuiltInCommandBuilder) throw new System.Exception("You cannot set the camera targets for a built-in command builder. Create a custom command builder instead.");
+ if (gizmos.IsAllocated && gizmos.Target != null) {
+ var target = gizmos.Target as DrawingData;
+ if (!target.data.StillExists(uniqueID)) {
+ throw new System.Exception("Cannot set cameraTargets because the command builder has already been disposed or does not exist.");
+ }
+ target.data.Get(uniqueID).meta.cameraTargets = value;
+ }
+ }
+ }
+
+ /// <summary>Submits this command builder for rendering</summary>
+ public void Dispose () {
+ if (uniqueID.isBuiltInCommandBuilder) throw new System.Exception("You cannot dispose a built-in command builder");
+ DisposeInternal();
+ }
+
+ /// <summary>
+ /// Disposes this command builder after the given job has completed.
+ ///
+ /// This is convenient if you are using the entity-component-system/burst in Unity and don't know exactly when the job will complete.
+ ///
+ /// You will not be able to use this command builder on the main thread anymore.
+ ///
+ /// See: job-system (view in online documentation for working links)
+ /// </summary>
+ /// <param name="dependency">The job that must complete before this command builder is disposed.</param>
+ /// <param name="allowedDelay">Whether to block on this dependency before rendering the current frame or not.
+ /// If the job is expected to complete during a single frame, leave at the default of \reflink{AllowedDelay.EndOfFrame}.
+ /// But if the job is expected to take multiple frames to complete, you can set this to \reflink{AllowedDelay.Infinite}.</param>
+ public void DisposeAfter (JobHandle dependency, AllowedDelay allowedDelay = AllowedDelay.EndOfFrame) {
+ if (!gizmos.IsAllocated) throw new System.Exception("You cannot dispose an invalid command builder. Are you trying to dispose it twice?");
+ try {
+ if (gizmos.IsAllocated && gizmos.Target != null) {
+ var target = gizmos.Target as DrawingData;
+ if (!target.data.StillExists(uniqueID)) {
+ throw new System.Exception("Cannot dispose the command builder because the drawing manager has been destroyed");
+ }
+ target.data.Get(uniqueID).SubmitWithDependency(gizmos, dependency, allowedDelay);
+ }
+ } finally {
+ this = default;
+ }
+ }
+
+ internal void DisposeInternal () {
+ if (!gizmos.IsAllocated) throw new System.Exception("You cannot dispose an invalid command builder. Are you trying to dispose it twice?");
+ try {
+ if (gizmos.IsAllocated && gizmos.Target != null) {
+ var target = gizmos.Target as DrawingData;
+ if (!target.data.StillExists(uniqueID)) {
+ throw new System.Exception("Cannot dispose the command builder because the drawing manager has been destroyed");
+ }
+ target.data.Get(uniqueID).Submit(gizmos.Target as DrawingData);
+ }
+ } finally {
+ gizmos.Free();
+ this = default;
+ }
+ }
+
+ /// <summary>
+ /// Discards the contents of this command builder without rendering anything.
+ /// If you are not going to draw anything (i.e. you do not call the <see cref="Dispose"/> method) then you must call this method to avoid
+ /// memory leaks.
+ /// </summary>
+ public void DiscardAndDispose () {
+ if (uniqueID.isBuiltInCommandBuilder) throw new System.Exception("You cannot dispose a built-in command builder");
+ DiscardAndDisposeInternal();
+ }
+
+ internal void DiscardAndDisposeInternal () {
+ try {
+ if (gizmos.IsAllocated && gizmos.Target != null) {
+ var target = gizmos.Target as DrawingData;
+ if (!target.data.StillExists(uniqueID)) {
+ throw new System.Exception("Cannot dispose the command builder because the drawing manager has been destroyed");
+ }
+ target.data.Release(uniqueID);
+ }
+ } finally {
+ if (gizmos.IsAllocated) gizmos.Free();
+ this = default;
+ }
+ }
+
+ /// <summary>
+ /// Pre-allocates the internal buffer to an additional size bytes.
+ /// This can give you a minor performance boost if you are drawing a lot of things.
+ ///
+ /// Note: Only resizes the buffer for the current thread.
+ /// </summary>
+ public void Preallocate (int size) {
+ Reserve(size);
+ }
+
+ /// <summary>Internal rendering command</summary>
+ [System.Flags]
+ internal enum Command {
+ PushColorInline = 1 << 8,
+ PushColor = 0,
+ PopColor,
+ PushMatrix,
+ PushSetMatrix,
+ PopMatrix,
+ Line,
+ Circle,
+ CircleXZ,
+ Disc,
+ DiscXZ,
+ SphereOutline,
+ Box,
+ WirePlane,
+ WireBox,
+ SolidTriangle,
+ PushPersist,
+ PopPersist,
+ Text,
+ Text3D,
+ PushLineWidth,
+ PopLineWidth,
+ CaptureState,
+ }
+
+ internal struct TriangleData {
+ public float3 a, b, c;
+ }
+
+ /// <summary>Holds rendering data for a line</summary>
+ internal struct LineData {
+ public float3 a, b;
+ }
+
+ internal struct LineDataV3 {
+ public Vector3 a, b;
+ }
+
+ /// <summary>Holds rendering data for a circle</summary>
+ internal struct CircleXZData {
+ public float3 center;
+ public float radius, startAngle, endAngle;
+ }
+
+ /// <summary>Holds rendering data for a circle</summary>
+ internal struct CircleData {
+ public float3 center;
+ public float3 normal;
+ public float radius;
+ }
+
+ /// <summary>Holds rendering data for a sphere</summary>
+ internal struct SphereData {
+ public float3 center;
+ public float radius;
+ }
+
+ /// <summary>Holds rendering data for a box</summary>
+ internal struct BoxData {
+ public float3 center;
+ public float3 size;
+ }
+
+ internal struct PlaneData {
+ public float3 center;
+ public quaternion rotation;
+ public float2 size;
+ }
+
+ internal struct PersistData {
+ public float endTime;
+ }
+
+ internal struct LineWidthData {
+ public float pixels;
+ public bool automaticJoins;
+ }
+
+
+
+ internal struct TextData {
+ public float3 center;
+ public LabelAlignment alignment;
+ public float sizeInPixels;
+ public int numCharacters;
+ }
+
+ internal struct TextData3D {
+ public float3 center;
+ public quaternion rotation;
+ public LabelAlignment alignment;
+ public float size;
+ public int numCharacters;
+ }
+
+ /// <summary>Ensures the buffer has room for at least N more bytes</summary>
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ private void Reserve (int additionalSpace) {
+ unsafe {
+ if (Unity.Burst.CompilerServices.Hint.Unlikely(threadIndex >= 0)) {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (threadIndex < 0 || threadIndex >= JobsUtility.MaxJobThreadCount) throw new System.Exception("Thread index outside the expected range");
+ if (threadIndex > 0 && uniqueID.isBuiltInCommandBuilder) throw new System.Exception("You should use a custom command builder when using the Unity Job System. Take a look at the documentation for more info.");
+ if (buffer == null) throw new System.Exception("CommandBuilder does not have a valid buffer. Is it properly initialized?");
+
+ // Exploit the fact that right after this package has drawn gizmos the buffers will be empty
+ // and the next task is that Unity will render its own internal gizmos.
+ // We can therefore easily (and without a high performance cost)
+ // trap accidental Draw.* calls from OnDrawGizmos functions
+ // by doing this check when the first Reserve call is made.
+ AssertNotRendering();
+#endif
+
+ buffer += threadIndex;
+ threadIndex = -1;
+ }
+
+ var newLength = buffer->Length + additionalSpace;
+ if (newLength > buffer->Capacity) {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ // This really should run every time we access the buffer... but that would be a bit slow
+ // This code will catch the error eventually.
+ AssertBufferExists();
+ const int MAX_BUFFER_SIZE = 1024 * 1024 * 256; // 256 MB
+ if (buffer->Length * 2 > MAX_BUFFER_SIZE) {
+ throw new System.Exception("CommandBuilder buffer is very large. Are you trying to draw things in an infinite loop?");
+ }
+#endif
+ buffer->SetCapacity(math.max(newLength, buffer->Length * 2));
+ }
+ }
+ }
+
+ [BurstDiscard]
+ private void AssertBufferExists () {
+ if (!gizmos.IsAllocated || gizmos.Target == null || !(gizmos.Target as DrawingData).data.StillExists(uniqueID)) {
+ // This command builder is invalid, clear all data on it to prevent it being used again
+ this = default;
+ throw new System.Exception("This command builder no longer exists. Are you trying to draw to a command builder which has already been disposed?");
+ }
+ }
+
+ [BurstDiscard]
+ static void AssertNotRendering () {
+ // Some checking to see if drawing is being done from inside OnDrawGizmos
+ // This check is relatively fast (about 0.05 ms), but we still do it only every 128th frame for performance reasons
+ if (!GizmoContext.drawingGizmos && !JobsUtility.IsExecutingJob && (Time.renderedFrameCount & 127) == 0) {
+ // Inspect the stack-trace to be able to provide more helpful error messages
+ var st = StackTraceUtility.ExtractStackTrace();
+ if (st.Contains("OnDrawGizmos")) {
+ throw new System.Exception("You are trying to use Draw.* functions from within Unity's OnDrawGizmos function. Use this package's gizmo callbacks instead (see the documentation).");
+ }
+ }
+ }
+
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ internal void Reserve<A>() where A : struct {
+ Reserve(UnsafeUtility.SizeOf<Command>() + UnsafeUtility.SizeOf<A>());
+ }
+
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ internal void Reserve<A, B>() where A : struct where B : struct {
+ Reserve(UnsafeUtility.SizeOf<Command>() * 2 + UnsafeUtility.SizeOf<A>() + UnsafeUtility.SizeOf<B>());
+ }
+
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ internal void Reserve<A, B, C>() where A : struct where B : struct where C : struct {
+ Reserve(UnsafeUtility.SizeOf<Command>() * 3 + UnsafeUtility.SizeOf<A>() + UnsafeUtility.SizeOf<B>() + UnsafeUtility.SizeOf<C>());
+ }
+
+ /// <summary>
+ /// Converts a Color to a Color32.
+ /// This method is faster than Unity's native color conversion, especially when using Burst.
+ /// </summary>
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ internal static unsafe uint ConvertColor (Color color) {
+ // If SSE2 is supported (which it is on essentially all X86 CPUs)
+ // then we can use a much faster conversion from Color to Color32.
+ // This will only be possible inside Burst.
+ if (Unity.Burst.Intrinsics.X86.Sse2.IsSse2Supported) {
+ // Convert from 0-1 float range to 0-255 integer range
+ var ci = (int4)(255 * new float4(color.r, color.g, color.b, color.a) + 0.5f);
+ var v32 = new Unity.Burst.Intrinsics.v128(ci.x, ci.y, ci.z, ci.w);
+ // Convert four 32-bit numbers to four 16-bit numbers
+ var v16 = Unity.Burst.Intrinsics.X86.Sse2.packs_epi32(v32, v32);
+ // Convert four 16-bit numbers to four 8-bit numbers
+ var v8 = Unity.Burst.Intrinsics.X86.Sse2.packus_epi16(v16, v16);
+ return v8.UInt0;
+ } else {
+ // If we don't have SSE2 (most likely we are not running inside Burst),
+ // then we will do a manual conversion from Color to Color32.
+ // This is significantly faster than just casting to a Color32.
+ var r = (uint)Mathf.Clamp((int)(color.r*255f + 0.5f), 0, 255);
+ var g = (uint)Mathf.Clamp((int)(color.g*255f + 0.5f), 0, 255);
+ var b = (uint)Mathf.Clamp((int)(color.b*255f + 0.5f), 0, 255);
+ var a = (uint)Mathf.Clamp((int)(color.a*255f + 0.5f), 0, 255);
+ return (a << 24) | (b << 16) | (g << 8) | r;
+ }
+ }
+
+ internal unsafe void Add<T>(T value) where T : struct {
+ int num = UnsafeUtility.SizeOf<T>();
+ var buffer = this.buffer;
+ var bufferSize = buffer->Length;
+ // We assume this because the Reserve function has already taken care of that.
+ // This removes a few branches from the assembly when running in burst.
+ Unity.Burst.CompilerServices.Hint.Assume(buffer->Ptr != null);
+ Unity.Burst.CompilerServices.Hint.Assume(buffer->Ptr + bufferSize != null);
+
+ unsafe {
+ UnsafeUtility.CopyStructureToPtr(ref value, (void*)((byte*)buffer->Ptr + bufferSize));
+ buffer->Length = bufferSize + num;
+ }
+ }
+
+ public struct ScopeMatrix : IDisposable {
+ internal CommandBuilder builder;
+ public void Dispose () {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (!builder.gizmos.IsAllocated || !(builder.gizmos.Target is DrawingData data) || !data.data.StillExists(builder.uniqueID)) throw new System.InvalidOperationException("The drawing instance this matrix scope belongs to no longer exists. Matrix scopes cannot survive for longer than a frame unless you have a custom drawing instance. Are you using a matrix scope inside a coroutine?");
+#endif
+ unsafe {
+ builder.PopMatrix();
+ builder.buffer = null;
+ }
+ }
+ }
+
+ public struct ScopeColor : IDisposable {
+ internal CommandBuilder builder;
+ public void Dispose () {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (!builder.gizmos.IsAllocated || !(builder.gizmos.Target is DrawingData data) || !data.data.StillExists(builder.uniqueID)) throw new System.InvalidOperationException("The drawing instance this color scope belongs to no longer exists. Color scopes cannot survive for longer than a frame unless you have a custom drawing instance. Are you using a color scope inside a coroutine?");
+#endif
+ unsafe {
+ builder.PopColor();
+ builder.buffer = null;
+ }
+ }
+ }
+
+ public struct ScopePersist : IDisposable {
+ internal CommandBuilder builder;
+ public void Dispose () {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (!builder.gizmos.IsAllocated || !(builder.gizmos.Target is DrawingData data) || !data.data.StillExists(builder.uniqueID)) throw new System.InvalidOperationException("The drawing instance this persist scope belongs to no longer exists. Persist scopes cannot survive for longer than a frame unless you have a custom drawing instance. Are you using a persist scope inside a coroutine?");
+#endif
+ unsafe {
+ builder.PopDuration();
+ builder.buffer = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Scope that does nothing.
+ /// Used for optimization in standalone builds.
+ /// </summary>
+ public struct ScopeEmpty : IDisposable {
+ public void Dispose () {
+ }
+ }
+
+ public struct ScopeLineWidth : IDisposable {
+ internal CommandBuilder builder;
+ public void Dispose () {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (!builder.gizmos.IsAllocated || !(builder.gizmos.Target is DrawingData data) || !data.data.StillExists(builder.uniqueID)) throw new System.InvalidOperationException("The drawing instance this line width scope belongs to no longer exists. Line width scopes cannot survive for longer than a frame unless you have a custom drawing instance. Are you using a line width scope inside a coroutine?");
+#endif
+ unsafe {
+ builder.PopLineWidth();
+ builder.buffer = null;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Scope to draw multiple things with an implicit matrix transformation.
+ /// All coordinates for items drawn inside the scope will be multiplied by the matrix.
+ /// If WithMatrix scopes are nested then coordinates are multiplied by all nested matrices in order.
+ ///
+ /// <code>
+ /// using (Draw.InLocalSpace(transform)) {
+ /// // Draw a box at (0,0,0) relative to the current object
+ /// // This means it will show up at the object's position
+ /// Draw.WireBox(Vector3.zero, Vector3.one);
+ /// }
+ ///
+ /// // Equivalent code using the lower level WithMatrix scope
+ /// using (Draw.WithMatrix(transform.localToWorldMatrix)) {
+ /// Draw.WireBox(Vector3.zero, Vector3.one);
+ /// }
+ /// </code>
+ ///
+ /// See: <see cref="InLocalSpace"/>
+ /// </summary>
+ [BurstDiscard]
+ public ScopeMatrix WithMatrix (Matrix4x4 matrix) {
+ PushMatrix(matrix);
+ // TODO: Keep track of alive scopes and prevent dispose unless all scopes have been disposed
+ unsafe {
+ return new ScopeMatrix { builder = this };
+ }
+ }
+
+ /// <summary>
+ /// Scope to draw multiple things with an implicit matrix transformation.
+ /// All coordinates for items drawn inside the scope will be multiplied by the matrix.
+ /// If WithMatrix scopes are nested then coordinates are multiplied by all nested matrices in order.
+ ///
+ /// <code>
+ /// using (Draw.InLocalSpace(transform)) {
+ /// // Draw a box at (0,0,0) relative to the current object
+ /// // This means it will show up at the object's position
+ /// Draw.WireBox(Vector3.zero, Vector3.one);
+ /// }
+ ///
+ /// // Equivalent code using the lower level WithMatrix scope
+ /// using (Draw.WithMatrix(transform.localToWorldMatrix)) {
+ /// Draw.WireBox(Vector3.zero, Vector3.one);
+ /// }
+ /// </code>
+ ///
+ /// See: <see cref="InLocalSpace"/>
+ /// </summary>
+ [BurstDiscard]
+ public ScopeMatrix WithMatrix (float3x3 matrix) {
+ PushMatrix(new float4x4(matrix, float3.zero));
+ // TODO: Keep track of alive scopes and prevent dispose unless all scopes have been disposed
+ unsafe {
+ return new ScopeMatrix { builder = this };
+ }
+ }
+
+ /// <summary>
+ /// Scope to draw multiple things with the same color.
+ ///
+ /// <code>
+ /// void Update () {
+ /// using (Draw.WithColor(Color.red)) {
+ /// Draw.Line(new Vector3(0, 0, 0), new Vector3(1, 1, 1));
+ /// Draw.Line(new Vector3(0, 0, 0), new Vector3(0, 1, 2));
+ /// }
+ /// }
+ /// </code>
+ ///
+ /// Any command that is passed an explicit color parameter will override this color.
+ /// If another color scope is nested inside this one then that scope will override this color.
+ /// </summary>
+ [BurstDiscard]
+ public ScopeColor WithColor (Color color) {
+ PushColor(color);
+ unsafe {
+ return new ScopeColor { builder = this };
+ }
+ }
+
+ /// <summary>
+ /// Scope to draw multiple things for a longer period of time.
+ ///
+ /// Normally drawn items will only be rendered for a single frame.
+ /// Using a persist scope you can make the items be drawn for any amount of time.
+ ///
+ /// <code>
+ /// void Update () {
+ /// using (Draw.WithDuration(1.0f)) {
+ /// var offset = Time.time;
+ /// Draw.Line(new Vector3(offset, 0, 0), new Vector3(offset, 0, 1));
+ /// }
+ /// }
+ /// </code>
+ ///
+ /// Note: Outside of play mode the duration is measured against Unity's Time.realtimeSinceStartup.
+ ///
+ /// Warning: It is recommended not to use this inside a DrawGizmos callback since DrawGizmos is called every frame anyway.
+ /// </summary>
+ /// <param name="duration">How long the drawn items should persist in seconds.</param>
+
+ [BurstDiscard]
+ public ScopePersist WithDuration (float duration) {
+ PushDuration(duration);
+ unsafe {
+ return new ScopePersist { builder = this };
+ }
+ }
+
+ /// <summary>
+ /// Scope to draw multiple things with a given line width.
+ ///
+ /// Note that the line join algorithm is a quite simple one optimized for speed. It normally looks good on a 2D plane, but if the polylines curve a lot in 3D space then
+ /// it can look odd from some angles.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// In the picture the top row has automaticJoins enabled and in the bottom row it is disabled.
+ /// </summary>
+ /// <param name="pixels">Line width in pixels</param>
+ /// <param name="automaticJoins">If true then sequences of lines that are adjacent will be automatically joined at their vertices. This typically produces nicer polylines without weird gaps.</param>
+ [BurstDiscard]
+ public ScopeLineWidth WithLineWidth (float pixels, bool automaticJoins = true) {
+ PushLineWidth(pixels, automaticJoins);
+ unsafe {
+ return new ScopeLineWidth { builder = this };
+ }
+ }
+
+ /// <summary>
+ /// Scope to draw multiple things relative to a transform object.
+ /// All coordinates for items drawn inside the scope will be multiplied by the transform's localToWorldMatrix.
+ ///
+ /// <code>
+ /// void Update () {
+ /// using (Draw.InLocalSpace(transform)) {
+ /// // Draw a box at (0,0,0) relative to the current object
+ /// // This means it will show up at the object's position
+ /// // The box is also rotated and scaled with the transform
+ /// Draw.WireBox(Vector3.zero, Vector3.one);
+ /// }
+ /// }
+ /// </code>
+ ///
+ /// [Open online documentation to see videos]
+ /// </summary>
+ [BurstDiscard]
+ public ScopeMatrix InLocalSpace (Transform transform) {
+ return WithMatrix(transform.localToWorldMatrix);
+ }
+
+ /// <summary>
+ /// Scope to draw multiple things in screen space of a camera.
+ /// If you draw 2D coordinates (i.e. (x,y,0)) they will be projected onto a plane approximately [2*near clip plane of the camera] world units in front of the camera (but guaranteed to be between the near and far planes).
+ ///
+ /// The lower left corner of the camera is (0,0,0) and the upper right is (camera.pixelWidth, camera.pixelHeight, 0)
+ ///
+ /// Note: As a corollary, the centers of pixels are offset by 0.5. So for example the center of the top left pixel is at (0.5, 0.5, 0).
+ /// Therefore, if you want to draw 1 pixel wide lines in screen space, you may want to offset the coordinates by 0.5 pixels.
+ ///
+ /// See: <see cref="InLocalSpace"/>
+ /// See: <see cref="WithMatrix"/>
+ /// </summary>
+ [BurstDiscard]
+ public ScopeMatrix InScreenSpace (Camera camera) {
+ return WithMatrix(camera.cameraToWorldMatrix * camera.nonJitteredProjectionMatrix.inverse * Matrix4x4.TRS(new Vector3(-1.0f, -1.0f, 0), Quaternion.identity, new Vector3(2.0f/camera.pixelWidth, 2.0f/camera.pixelHeight, 1)));
+ }
+
+ /// <summary>
+ /// Multiply all coordinates until the next PopMatrix with the given matrix.
+ /// This differs from <see cref="PushSetMatrix"/> in that this stacks with all previously pushed matrices while <see cref="PushSetMatrix"/> does not.
+ /// </summary>
+ public void PushMatrix (Matrix4x4 matrix) {
+ Reserve<float4x4>();
+ Add(Command.PushMatrix);
+ Add(matrix);
+ }
+
+ /// <summary>
+ /// Multiply all coordinates until the next PopMatrix with the given matrix.
+ /// This differs from <see cref="PushSetMatrix"/> in that this stacks with all previously pushed matrices while <see cref="PushSetMatrix"/> does not.
+ /// </summary>
+ public void PushMatrix (float4x4 matrix) {
+ Reserve<float4x4>();
+ Add(Command.PushMatrix);
+ Add(matrix);
+ }
+
+ /// <summary>
+ /// Multiply all coordinates until the next PopMatrix with the given matrix.
+ /// This differs from <see cref="PushMatrix"/> in that this sets the current matrix directly while <see cref="PushMatrix"/> stacks with all previously pushed matrices.
+ /// </summary>
+ public void PushSetMatrix (Matrix4x4 matrix) {
+ Reserve<float4x4>();
+ Add(Command.PushSetMatrix);
+ Add((float4x4)matrix);
+ }
+
+ /// <summary>
+ /// Multiply all coordinates until the next PopMatrix with the given matrix.
+ /// This differs from <see cref="PushMatrix"/> in that this sets the current matrix directly while <see cref="PushMatrix"/> stacks with all previously pushed matrices.
+ /// </summary>
+ public void PushSetMatrix (float4x4 matrix) {
+ Reserve<float4x4>();
+ Add(Command.PushSetMatrix);
+ Add(matrix);
+ }
+
+ /// <summary>Pops a matrix from the stack</summary>
+ public void PopMatrix () {
+ Reserve(4);
+ Add(Command.PopMatrix);
+ }
+
+ /// <summary>
+ /// Draws everything until the next PopColor with the given color.
+ /// Any command that is passed an explicit color parameter will override this color.
+ /// If another color scope is nested inside this one then that scope will override this color.
+ /// </summary>
+ public void PushColor (Color color) {
+ Reserve<Color32>();
+ Add(Command.PushColor);
+ Add(ConvertColor(color));
+ }
+
+ /// <summary>Pops a color from the stack</summary>
+ public void PopColor () {
+ Reserve(4);
+ Add(Command.PopColor);
+ }
+
+ /// <summary>
+ /// Draws everything until the next PopDuration for a number of seconds.
+ /// Warning: This is not recommended inside a DrawGizmos callback since DrawGizmos is called every frame anyway.
+ /// </summary>
+ public void PushDuration (float duration) {
+ Reserve<PersistData>();
+ Add(Command.PushPersist);
+ // We must use the BurstTime variable which is updated more rarely than Time.time.
+ // This is necessary because this code may be called from a burst job or from a different thread.
+ // Time.time can only be accessed in the main thread.
+ Add(new PersistData { endTime = SharedDrawingData.BurstTime.Data + duration });
+ }
+
+ /// <summary>Pops a duration scope from the stack</summary>
+ public void PopDuration () {
+ Reserve(4);
+ Add(Command.PopPersist);
+ }
+
+ /// <summary>
+ /// Draws everything until the next PopPersist for a number of seconds.
+ /// Warning: This is not recommended inside a DrawGizmos callback since DrawGizmos is called every frame anyway.
+ ///
+ /// Deprecated: Renamed to <see cref="PushDuration"/>
+ /// </summary>
+ [System.Obsolete("Renamed to PushDuration for consistency")]
+ public void PushPersist (float duration) {
+ PushDuration(duration);
+ }
+
+ /// <summary>
+ /// Pops a persist scope from the stack.
+ /// Deprecated: Renamed to <see cref="PopDuration"/>
+ /// </summary>
+ [System.Obsolete("Renamed to PopDuration for consistency")]
+ public void PopPersist () {
+ PopDuration();
+ }
+
+ /// <summary>
+ /// Draws all lines until the next PopLineWidth with a given line width in pixels.
+ ///
+ /// Note that the line join algorithm is a quite simple one optimized for speed. It normally looks good on a 2D plane, but if the polylines curve a lot in 3D space then
+ /// it can look odd from some angles.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// In the picture the top row has automaticJoins enabled and in the bottom row it is disabled.
+ /// </summary>
+ /// <param name="pixels">Line width in pixels</param>
+ /// <param name="automaticJoins">If true then sequences of lines that are adjacent will be automatically joined at their vertices. This typically produces nicer polylines without weird gaps.</param>
+ public void PushLineWidth (float pixels, bool automaticJoins = true) {
+ if (pixels < 0) throw new System.ArgumentOutOfRangeException("pixels", "Line width must be positive");
+
+ Reserve<LineWidthData>();
+ Add(Command.PushLineWidth);
+ Add(new LineWidthData { pixels = pixels, automaticJoins = automaticJoins });
+ }
+
+ /// <summary>Pops a line width scope from the stack</summary>
+ public void PopLineWidth () {
+ Reserve(4);
+ Add(Command.PopLineWidth);
+ }
+
+ /// <summary>
+ /// Draws a line between two points.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// <code>
+ /// void Update () {
+ /// Draw.Line(Vector3.zero, Vector3.up);
+ /// }
+ /// </code>
+ /// </summary>
+ public void Line (float3 a, float3 b) {
+ Reserve<LineData>();
+ Add(Command.Line);
+ Add(new LineData { a = a, b = b });
+ }
+
+ /// <summary>
+ /// Draws a line between two points.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// <code>
+ /// void Update () {
+ /// Draw.Line(Vector3.zero, Vector3.up);
+ /// }
+ /// </code>
+ /// </summary>
+ public void Line (Vector3 a, Vector3 b) {
+ Reserve<LineData>();
+ // Add(Command.Line);
+ // Add(new LineDataV3 { a = a, b = b });
+
+ // The code below is equivalent to the commented out code above.
+ // But drawing lines is the most common operation so it needs to be really fast.
+ // Having this hardcoded improves line rendering performance by about 8%.
+ var bufferSize = BufferSize;
+
+ unsafe {
+ var newLen = bufferSize + 4 + 24;
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ UnityEngine.Assertions.Assert.IsTrue(newLen <= buffer->Capacity);
+#endif
+ var ptr = (byte*)buffer->Ptr + bufferSize;
+ *(Command*)ptr = Command.Line;
+ var lineData = (LineDataV3*)(ptr + 4);
+ lineData->a = a;
+ lineData->b = b;
+ buffer->Length = newLen;
+ }
+ }
+
+ /// <summary>
+ /// Draws a line between two points.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// <code>
+ /// void Update () {
+ /// Draw.Line(Vector3.zero, Vector3.up);
+ /// }
+ /// </code>
+ /// </summary>
+ public void Line (Vector3 a, Vector3 b, Color color) {
+ Reserve<Color32, LineData>();
+ // Add(Command.Line | Command.PushColorInline);
+ // Add(ConvertColor(color));
+ // Add(new LineDataV3 { a = a, b = b });
+
+ // The code below is equivalent to the code which is commented out above.
+ // But drawing lines is the most common operation so it needs to be really fast
+ // Having this hardcoded improves line rendering performance by about 8%.
+ var bufferSize = BufferSize;
+
+ unsafe {
+ var newLen = bufferSize + 4 + 24 + 4;
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ UnityEngine.Assertions.Assert.IsTrue(newLen <= buffer->Capacity);
+#endif
+ var ptr = (byte*)buffer->Ptr + bufferSize;
+ *(Command*)ptr = Command.Line | Command.PushColorInline;
+ *(uint*)(ptr + 4) = ConvertColor(color);
+ var lineData = (LineDataV3*)(ptr + 8);
+ lineData->a = a;
+ lineData->b = b;
+ buffer->Length = newLen;
+ }
+ }
+
+ /// <summary>
+ /// Draws a ray starting at a point and going in the given direction.
+ /// The ray will end at origin + direction.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// <code>
+ /// Draw.Ray(Vector3.zero, Vector3.up);
+ /// </code>
+ /// </summary>
+ public void Ray (float3 origin, float3 direction) {
+ Line(origin, origin + direction);
+ }
+
+ /// <summary>
+ /// Draws a ray with a given length.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// <code>
+ /// Draw.Ray(Camera.main.ScreenPointToRay(Vector3.zero), 10);
+ /// </code>
+ /// </summary>
+ public void Ray (Ray ray, float length) {
+ Line(ray.origin, ray.origin + ray.direction * length);
+ }
+
+ /// <summary>
+ /// Draws an arc between two points.
+ ///
+ /// The rendered arc is the shortest arc between the two points.
+ /// The radius of the arc will be equal to the distance between center and start.
+ ///
+ /// [Open online documentation to see images]
+ /// <code>
+ /// float a1 = Mathf.PI*0.9f;
+ /// float a2 = Mathf.PI*0.1f;
+ /// var arcStart = new float3(Mathf.Cos(a1), 0, Mathf.Sin(a1));
+ /// var arcEnd = new float3(Mathf.Cos(a2), 0, Mathf.Sin(a2));
+ /// Draw.Arc(new float3(0, 0, 0), arcStart, arcEnd, color);
+ /// </code>
+ ///
+ /// See: <see cref="CommandBuilder2D.Circle(float3,float,float,float)"/>
+ /// </summary>
+ /// <param name="center">Center of the imaginary circle that the arc is part of.</param>
+ /// <param name="start">Starting point of the arc.</param>
+ /// <param name="end">End point of the arc.</param>
+ public void Arc (float3 center, float3 start, float3 end) {
+ var d1 = start - center;
+ var d2 = end - center;
+ var normal = math.cross(d2, d1);
+
+ if (math.any(normal != 0) && math.all(math.isfinite(normal))) {
+ var m = Matrix4x4.TRS(center, Quaternion.LookRotation(d1, normal), Vector3.one);
+ var angle = Vector3.SignedAngle(d1, d2, normal) * Mathf.Deg2Rad;
+ PushMatrix(m);
+ CircleXZInternal(float3.zero, math.length(d1), 90 * Mathf.Deg2Rad, 90 * Mathf.Deg2Rad - angle);
+ PopMatrix();
+ }
+ }
+
+ /// <summary>
+ /// Draws a circle in the XZ plane.
+ ///
+ /// You can draw an arc by supplying the startAngle and endAngle parameters.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="Circle(float3,float3,float)"/>
+ /// See: <see cref="CircleXY(float3,float,float,float)"/>
+ /// See: <see cref="Arc(float3,float3,float3)"/>
+ /// </summary>
+ /// <param name="center">Center of the circle or arc.</param>
+ /// <param name="radius">Radius of the circle or arc.</param>
+ /// <param name="startAngle">Starting angle in radians. 0 corrsponds to the positive X axis.</param>
+ /// <param name="endAngle">End angle in radians.</param>
+ [System.Obsolete("Use Draw.xz.Circle instead")]
+ public void CircleXZ (float3 center, float radius, float startAngle = 0f, float endAngle = 2 * Mathf.PI) {
+ CircleXZInternal(center, radius, startAngle, endAngle);
+ }
+
+ internal void CircleXZInternal (float3 center, float radius, float startAngle = 0f, float endAngle = 2 * Mathf.PI) {
+ Reserve<CircleXZData>();
+ Add(Command.CircleXZ);
+ Add(new CircleXZData { center = center, radius = radius, startAngle = startAngle, endAngle = endAngle });
+ }
+
+ internal void CircleXZInternal (float3 center, float radius, float startAngle, float endAngle, Color color) {
+ Reserve<Color32, CircleXZData>();
+ Add(Command.CircleXZ | Command.PushColorInline);
+ Add(ConvertColor(color));
+ Add(new CircleXZData { center = center, radius = radius, startAngle = startAngle, endAngle = endAngle });
+ }
+
+ internal static readonly float4x4 XZtoXYPlaneMatrix = float4x4.RotateX(-math.PI*0.5f);
+ internal static readonly float4x4 XZtoYZPlaneMatrix = float4x4.RotateZ(math.PI*0.5f);
+
+ /// <summary>
+ /// Draws a circle in the XY plane.
+ ///
+ /// You can draw an arc by supplying the startAngle and endAngle parameters.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="Circle(float3,float3,float)"/>
+ /// See: <see cref="Arc(float3,float3,float3)"/>
+ /// </summary>
+ /// <param name="center">Center of the circle or arc.</param>
+ /// <param name="radius">Radius of the circle or arc.</param>
+ /// <param name="startAngle">Starting angle in radians. 0 corrsponds to the positive X axis.</param>
+ /// <param name="endAngle">End angle in radians.</param>
+ [System.Obsolete("Use Draw.xy.Circle instead")]
+ public void CircleXY (float3 center, float radius, float startAngle = 0f, float endAngle = 2 * Mathf.PI) {
+ PushMatrix(XZtoXYPlaneMatrix);
+ CircleXZ(new float3(center.x, -center.z, center.y), radius, startAngle, endAngle);
+ PopMatrix();
+ }
+
+ /// <summary>
+ /// Draws a circle.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Note: This overload does not allow you to draw an arc. For that purpose use <see cref="Arc"/>, <see cref="CircleXY"/> or <see cref="CircleXZ"/> instead.
+ /// </summary>
+ public void Circle (float3 center, float3 normal, float radius) {
+ Reserve<CircleData>();
+ Add(Command.Circle);
+ Add(new CircleData { center = center, normal = normal, radius = radius });
+ }
+
+ /// <summary>
+ /// Draws a solid arc between two points.
+ ///
+ /// The rendered arc is the shortest arc between the two points.
+ /// The radius of the arc will be equal to the distance between center and start.
+ ///
+ /// [Open online documentation to see images]
+ /// <code>
+ /// float a1 = Mathf.PI*0.9f;
+ /// float a2 = Mathf.PI*0.1f;
+ /// var arcStart = new float3(Mathf.Cos(a1), 0, Mathf.Sin(a1));
+ /// var arcEnd = new float3(Mathf.Cos(a2), 0, Mathf.Sin(a2));
+ /// Draw.SolidArc(new float3(0, 0, 0), arcStart, arcEnd, color);
+ /// </code>
+ ///
+ /// See: <see cref="CommandBuilder2D.SolidCircle(float3,float,float,float)"/>
+ /// </summary>
+ /// <param name="center">Center of the imaginary circle that the arc is part of.</param>
+ /// <param name="start">Starting point of the arc.</param>
+ /// <param name="end">End point of the arc.</param>
+ public void SolidArc (float3 center, float3 start, float3 end) {
+ var d1 = start - center;
+ var d2 = end - center;
+ var normal = math.cross(d2, d1);
+
+ if (math.any(normal)) {
+ var m = Matrix4x4.TRS(center, Quaternion.LookRotation(d1, normal), Vector3.one);
+ var angle = Vector3.SignedAngle(d1, d2, normal) * Mathf.Deg2Rad;
+ PushMatrix(m);
+ SolidCircleXZInternal(float3.zero, math.length(d1), 90 * Mathf.Deg2Rad, 90 * Mathf.Deg2Rad - angle);
+ PopMatrix();
+ }
+ }
+
+ /// <summary>
+ /// Draws a disc in the XZ plane.
+ ///
+ /// You can draw an arc by supplying the startAngle and endAngle parameters.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="SolidCircle(float3,float3,float)"/>
+ /// See: <see cref="CommandBuilder2D.SolidCircle(float3,float,float,float)"/>
+ /// See: <see cref="SolidArc(float3,float3,float3)"/>
+ /// </summary>
+ /// <param name="center">Center of the disc or solid arc.</param>
+ /// <param name="radius">Radius of the disc or solid arc.</param>
+ /// <param name="startAngle">Starting angle in radians. 0 corrsponds to the positive X axis.</param>
+ /// <param name="endAngle">End angle in radians.</param>
+ [System.Obsolete("Use Draw.xz.SolidCircle instead")]
+ public void SolidCircleXZ (float3 center, float radius, float startAngle = 0f, float endAngle = 2 * Mathf.PI) {
+ SolidCircleXZInternal(center, radius, startAngle, endAngle);
+ }
+
+ internal void SolidCircleXZInternal (float3 center, float radius, float startAngle = 0f, float endAngle = 2 * Mathf.PI) {
+ Reserve<CircleXZData>();
+ Add(Command.DiscXZ);
+ Add(new CircleXZData { center = center, radius = radius, startAngle = startAngle, endAngle = endAngle });
+ }
+
+ internal void SolidCircleXZInternal (float3 center, float radius, float startAngle, float endAngle, Color color) {
+ Reserve<Color32, CircleXZData>();
+ Add(Command.DiscXZ | Command.PushColorInline);
+ Add(ConvertColor(color));
+ Add(new CircleXZData { center = center, radius = radius, startAngle = startAngle, endAngle = endAngle });
+ }
+
+ /// <summary>
+ /// Draws a disc in the XY plane.
+ ///
+ /// You can draw an arc by supplying the startAngle and endAngle parameters.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="SolidCircle(float3,float3,float)"/>
+ /// See: <see cref="CommandBuilder2D.SolidCircle(float3,float,float,float)"/>
+ /// See: <see cref="SolidArc(float3,float3,float3)"/>
+ /// </summary>
+ /// <param name="center">Center of the disc or solid arc.</param>
+ /// <param name="radius">Radius of the disc or solid arc.</param>
+ /// <param name="startAngle">Starting angle in radians. 0 corrsponds to the positive X axis.</param>
+ /// <param name="endAngle">End angle in radians.</param>
+ [System.Obsolete("Use Draw.xy.SolidCircle instead")]
+ public void SolidCircleXY (float3 center, float radius, float startAngle = 0f, float endAngle = 2 * Mathf.PI) {
+ PushMatrix(XZtoXYPlaneMatrix);
+ SolidCircleXZInternal(new float3(center.x, -center.z, center.y), radius, startAngle, endAngle);
+ PopMatrix();
+ }
+
+ /// <summary>
+ /// Draws a disc.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Note: This overload does not allow you to draw an arc. For that purpose use <see cref="SolidArc"/> or <see cref="CommandBuilder2D.SolidCircle(float3,float,float,float)"/> instead.
+ /// </summary>
+ public void SolidCircle (float3 center, float3 normal, float radius) {
+ Reserve<CircleData>();
+ Add(Command.Disc);
+ Add(new CircleData { center = center, normal = normal, radius = radius });
+ }
+
+ /// <summary>
+ /// Draws a circle outline around a sphere.
+ ///
+ /// Visually, this is a circle that always faces the camera, and is resized automatically to fit the sphere.
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ public void SphereOutline (float3 center, float radius) {
+ Reserve<SphereData>();
+ Add(Command.SphereOutline);
+ Add(new SphereData { center = center, radius = radius });
+ }
+
+ /// <summary>
+ /// Draws a cylinder.
+ /// The cylinder's bottom circle will be centered at the bottom parameter and similarly for the top circle.
+ ///
+ /// <code>
+ /// // Draw a tilted cylinder between the points (0,0,0) and (1,1,1) with a radius of 0.5
+ /// Draw.WireCylinder(Vector3.zero, Vector3.one, 0.5f, Color.black);
+ /// </code>
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ public void WireCylinder (float3 bottom, float3 top, float radius) {
+ WireCylinder(bottom, top - bottom, math.length(top - bottom), radius);
+ }
+
+ /// <summary>
+ /// Draws a cylinder.
+ ///
+ /// <code>
+ /// // Draw a two meter tall cylinder at the world origin with a radius of 0.5
+ /// Draw.WireCylinder(Vector3.zero, Vector3.up, 2, 0.5f, Color.black);
+ /// </code>
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="position">The center of the cylinder's "bottom" circle.</param>
+ /// <param name="up">The cylinder's main axis. Does not have to be normalized. If zero, nothing will be drawn.</param>
+ /// <param name="height">The length of the cylinder, as measured along it's main axis.</param>
+ /// <param name="radius">The radius of the cylinder.</param>
+ public void WireCylinder (float3 position, float3 up, float height, float radius) {
+ up = math.normalizesafe(up);
+ if (math.all(up == 0) || math.any(math.isnan(up)) || math.isnan(height) || math.isnan(radius)) return;
+
+ OrthonormalBasis(up, out var basis1, out var basis2);
+
+ PushMatrix(new float4x4(
+ new float4(basis1 * radius, 0),
+ new float4(up * height, 0),
+ new float4(basis2 * radius, 0),
+ new float4(position, 1)
+ ));
+
+ CircleXZInternal(float3.zero, 1);
+ if (height > 0) {
+ CircleXZInternal(new float3(0, 1, 0), 1);
+ Line(new float3(1, 0, 0), new float3(1, 1, 0));
+ Line(new float3(-1, 0, 0), new float3(-1, 1, 0));
+ Line(new float3(0, 0, 1), new float3(0, 1, 1));
+ Line(new float3(0, 0, -1), new float3(0, 1, -1));
+ }
+ PopMatrix();
+ }
+
+ /// <summary>
+ /// Constructs an orthonormal basis from a single normal vector.
+ ///
+ /// This is similar to math.orthonormal_basis, but it tries harder to be continuous in its input.
+ /// In contrast, math.orthonormal_basis has a tendency to jump around even with small changes to the normal.
+ ///
+ /// It's not as fast as math.orthonormal_basis, though.
+ /// </summary>
+ static void OrthonormalBasis (float3 normal, out float3 basis1, out float3 basis2) {
+ basis1 = math.cross(normal, new float3(1, 1, 1));
+ if (math.all(basis1 == 0)) basis1 = math.cross(normal, new float3(-1, 1, 1));
+ basis1 = math.normalizesafe(basis1);
+ basis2 = math.cross(normal, basis1);
+ }
+
+ /// <summary>
+ /// Draws a capsule with a (start,end) parameterization.
+ ///
+ /// The behavior of this method matches common Unity APIs such as Physics.CheckCapsule.
+ ///
+ /// <code>
+ /// // Draw a tilted capsule between the points (0,0,0) and (1,1,1) with a radius of 0.5
+ /// Draw.WireCapsule(Vector3.zero, Vector3.one, 0.5f, Color.black);
+ /// </code>
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="start">Center of the start hemisphere of the capsule.</param>
+ /// <param name="end">Center of the end hemisphere of the capsule.</param>
+ /// <param name="radius">Radius of the capsule.</param>
+ public void WireCapsule (float3 start, float3 end, float radius) {
+ var dir = end - start;
+ var length = math.length(dir);
+
+ if (length < 0.0001) {
+ // The endpoints are the same, we can't draw a capsule from this because we don't know its orientation.
+ // Draw a sphere as a fallback
+ WireSphere(start, radius);
+ } else {
+ var normalized_dir = dir / length;
+
+ WireCapsule(start - normalized_dir*radius, normalized_dir, length + 2*radius, radius);
+ }
+ }
+
+ // TODO: Change to center, up, height parameterization
+ /// <summary>
+ /// Draws a capsule with a (position,direction/length) parameterization.
+ ///
+ /// <code>
+ /// // Draw a capsule that touches the y=0 plane, is 2 meters tall and has a radius of 0.5
+ /// Draw.WireCapsule(Vector3.zero, Vector3.up, 2.0f, 0.5f, Color.black);
+ /// </code>
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="position">One endpoint of the capsule. This is at the edge of the capsule, not at the center of one of the hemispheres.</param>
+ /// <param name="direction">The main axis of the capsule. Does not have to be normalized. If zero, nothing will be drawn.</param>
+ /// <param name="length">Distance between the two endpoints of the capsule. The length will be clamped to be at least 2*radius.</param>
+ /// <param name="radius">The radius of the capsule.</param>
+ public void WireCapsule (float3 position, float3 direction, float length, float radius) {
+ direction = math.normalizesafe(direction);
+ if (math.all(direction == 0) || math.any(math.isnan(direction)) || math.isnan(length) || math.isnan(radius)) return;
+
+ if (radius <= 0) {
+ Line(position, position + direction * length);
+ } else {
+ length = math.max(length, radius*2);
+ OrthonormalBasis(direction, out var basis1, out var basis2);
+
+ PushMatrix(new float4x4(
+ new float4(basis1, 0),
+ new float4(direction, 0),
+ new float4(basis2, 0),
+ new float4(position, 1)
+ ));
+ CircleXZInternal(new float3(0, radius, 0), radius);
+ PushMatrix(XZtoXYPlaneMatrix);
+ CircleXZInternal(new float3(0, 0, radius), radius, Mathf.PI, 2 * Mathf.PI);
+ PopMatrix();
+ PushMatrix(XZtoYZPlaneMatrix);
+ CircleXZInternal(new float3(radius, 0, 0), radius, Mathf.PI*0.5f, Mathf.PI*1.5f);
+ PopMatrix();
+ if (length > 0) {
+ var upperY = length - radius;
+ var lowerY = radius;
+ CircleXZInternal(new float3(0, upperY, 0), radius);
+ PushMatrix(XZtoXYPlaneMatrix);
+ CircleXZInternal(new float3(0, 0, upperY), radius, 0, Mathf.PI);
+ PopMatrix();
+ PushMatrix(XZtoYZPlaneMatrix);
+ CircleXZInternal(new float3(upperY, 0, 0), radius, -Mathf.PI*0.5f, Mathf.PI*0.5f);
+ PopMatrix();
+ Line(new float3(radius, lowerY, 0), new float3(radius, upperY, 0));
+ Line(new float3(-radius, lowerY, 0), new float3(-radius, upperY, 0));
+ Line(new float3(0, lowerY, radius), new float3(0, upperY, radius));
+ Line(new float3(0, lowerY, -radius), new float3(0, upperY, -radius));
+ }
+ PopMatrix();
+ }
+ }
+
+ /// <summary>
+ /// Draws a wire sphere.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// <code>
+ /// // Draw a wire sphere at the origin with a radius of 0.5
+ /// Draw.WireSphere(Vector3.zero, 0.5f, Color.black);
+ /// </code>
+ ///
+ /// See: <see cref="Circle"/>
+ /// </summary>
+ public void WireSphere (float3 position, float radius) {
+ SphereOutline(position, radius);
+ Circle(position, new float3(1, 0, 0), radius);
+ Circle(position, new float3(0, 1, 0), radius);
+ Circle(position, new float3(0, 0, 1), radius);
+ }
+
+ /// <summary>
+ /// Draws lines through a sequence of points.
+ ///
+ /// [Open online documentation to see images]
+ /// <code>
+ /// // Draw a square
+ /// Draw.Polyline(new [] { new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(1, 1, 0), new Vector3(0, 1, 0) }, true);
+ /// </code>
+ /// </summary>
+ /// <param name="points">Sequence of points to draw lines through</param>
+ /// <param name="cycle">If true a line will be drawn from the last point in the sequence back to the first point.</param>
+ [BurstDiscard]
+ public void Polyline (List<Vector3> points, bool cycle = false) {
+ for (int i = 0; i < points.Count - 1; i++) {
+ Line(points[i], points[i+1]);
+ }
+ if (cycle && points.Count > 1) Line(points[points.Count - 1], points[0]);
+ }
+
+ /// <summary>
+ /// Draws lines through a sequence of points.
+ ///
+ /// [Open online documentation to see images]
+ /// <code>
+ /// // Draw a square
+ /// Draw.Polyline(new [] { new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(1, 1, 0), new Vector3(0, 1, 0) }, true);
+ /// </code>
+ /// </summary>
+ /// <param name="points">Sequence of points to draw lines through</param>
+ /// <param name="cycle">If true a line will be drawn from the last point in the sequence back to the first point.</param>
+ public void Polyline<T>(T points, bool cycle = false) where T : IReadOnlyList<float3> {
+ for (int i = 0; i < points.Count - 1; i++) {
+ Line(points[i], points[i+1]);
+ }
+ if (cycle && points.Count > 1) Line(points[points.Count - 1], points[0]);
+ }
+
+ /// <summary>
+ /// Draws lines through a sequence of points.
+ ///
+ /// [Open online documentation to see images]
+ /// <code>
+ /// // Draw a square
+ /// Draw.Polyline(new [] { new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(1, 1, 0), new Vector3(0, 1, 0) }, true);
+ /// </code>
+ /// </summary>
+ /// <param name="points">Sequence of points to draw lines through</param>
+ /// <param name="cycle">If true a line will be drawn from the last point in the sequence back to the first point.</param>
+ [BurstDiscard]
+ public void Polyline (Vector3[] points, bool cycle = false) {
+ for (int i = 0; i < points.Length - 1; i++) {
+ Line(points[i], points[i+1]);
+ }
+ if (cycle && points.Length > 1) Line(points[points.Length - 1], points[0]);
+ }
+
+ /// <summary>
+ /// Draws lines through a sequence of points.
+ ///
+ /// [Open online documentation to see images]
+ /// <code>
+ /// // Draw a square
+ /// Draw.Polyline(new [] { new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(1, 1, 0), new Vector3(0, 1, 0) }, true);
+ /// </code>
+ /// </summary>
+ /// <param name="points">Sequence of points to draw lines through</param>
+ /// <param name="cycle">If true a line will be drawn from the last point in the sequence back to the first point.</param>
+ [BurstDiscard]
+ public void Polyline (float3[] points, bool cycle = false) {
+ for (int i = 0; i < points.Length - 1; i++) {
+ Line(points[i], points[i+1]);
+ }
+ if (cycle && points.Length > 1) Line(points[points.Length - 1], points[0]);
+ }
+
+ /// <summary>
+ /// Draws lines through a sequence of points.
+ ///
+ /// [Open online documentation to see images]
+ /// <code>
+ /// // Draw a square
+ /// Draw.Polyline(new [] { new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(1, 1, 0), new Vector3(0, 1, 0) }, true);
+ /// </code>
+ /// </summary>
+ /// <param name="points">Sequence of points to draw lines through</param>
+ /// <param name="cycle">If true a line will be drawn from the last point in the sequence back to the first point.</param>
+ public void Polyline (NativeArray<float3> points, bool cycle = false) {
+ for (int i = 0; i < points.Length - 1; i++) {
+ Line(points[i], points[i+1]);
+ }
+ if (cycle && points.Length > 1) Line(points[points.Length - 1], points[0]);
+ }
+
+ /// <summary>Determines the symbol to use for <see cref="PolylineWithSymbol"/></summary>
+ public enum SymbolDecoration {
+ /// <summary>
+ /// No symbol.
+ ///
+ /// Space will still be reserved, but no symbol will be drawn.
+ /// Can be used to draw dashed lines.
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ None,
+ /// <summary>
+ /// An arrowhead symbol.
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ ArrowHead,
+ /// <summary>
+ /// A circle symbol.
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ Circle,
+ }
+
+ /// <summary>
+ /// Draws a dashed line between two points.
+ ///
+ /// <code>
+ /// Draw.DashedPolyline(points, 0.1f, 0.1f, color);
+ /// </code>
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Warning: An individual line segment is drawn for each dash. This means that performance may suffer if you make the dash + gap distance too small.
+ /// But for most use cases the performance is nothing to worry about.
+ ///
+ /// See: <see cref="DashedPolyline"/>
+ /// See: <see cref="PolylineWithSymbol"/>
+ /// </summary>
+ public void DashedLine (float3 a, float3 b, float dash, float gap) {
+ var p = new PolylineWithSymbol(SymbolDecoration.None, gap, 0, dash + gap);
+ p.MoveTo(ref this, a);
+ p.MoveTo(ref this, b);
+ }
+
+ /// <summary>
+ /// Draws a dashed line through a sequence of points.
+ ///
+ /// <code>
+ /// Draw.DashedPolyline(points, 0.1f, 0.1f, color);
+ /// </code>
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Warning: An individual line segment is drawn for each dash. This means that performance may suffer if you make the dash + gap distance too small.
+ /// But for most use cases the performance is nothing to worry about.
+ ///
+ /// If you have a different collection type, or you do not have the points in a collection at all, then you can use the <see cref="PolylineWithSymbol"/> struct directly.
+ ///
+ /// <code>
+ /// using (Draw.WithColor(color)) {
+ /// var dash = 0.1f;
+ /// var gap = 0.1f;
+ /// var p = new CommandBuilder.PolylineWithSymbol(CommandBuilder.SymbolDecoration.None, gap, 0, dash + gap);
+ /// for (int i = 0; i < points.Count; i++) {
+ /// p.MoveTo(ref Draw.editor, points[i]);
+ /// }
+ /// }
+ /// </code>
+ ///
+ /// See: <see cref="DashedLine"/>
+ /// See: <see cref="PolylineWithSymbol"/>
+ /// </summary>
+ public void DashedPolyline (List<Vector3> points, float dash, float gap) {
+ var p = new PolylineWithSymbol(SymbolDecoration.None, gap, 0, dash + gap);
+ for (int i = 0; i < points.Count; i++) {
+ p.MoveTo(ref this, points[i]);
+ }
+ }
+
+ /// <summary>
+ /// Helper for drawing a polyline with symbols at regular intervals.
+ ///
+ /// <code>
+ /// var generator = new CommandBuilder.PolylineWithSymbol(CommandBuilder.SymbolDecoration.Circle, 0.2f, 0.0f, 0.47f);
+ /// generator.MoveTo(ref Draw.editor, new float3(-0.5f, 0, -0.5f));
+ /// generator.MoveTo(ref Draw.editor, new float3(0.5f, 0, 0.5f));
+ /// </code>
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// You can also draw a dashed line using this struct, but for common cases you can use the <see cref="DashedPolyline"/> helper function instead.
+ ///
+ /// <code>
+ /// using (Draw.WithColor(color)) {
+ /// var dash = 0.1f;
+ /// var gap = 0.1f;
+ /// var p = new CommandBuilder.PolylineWithSymbol(CommandBuilder.SymbolDecoration.None, gap, 0, dash + gap);
+ /// for (int i = 0; i < points.Count; i++) {
+ /// p.MoveTo(ref Draw.editor, points[i]);
+ /// }
+ /// }
+ /// </code>
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ public struct PolylineWithSymbol {
+ float3 prev;
+ float offset;
+ readonly float symbolSize;
+ readonly float symbolSpacing;
+ readonly float symbolPadding;
+ readonly float symbolOffset;
+ readonly SymbolDecoration symbol;
+ readonly bool reverseSymbols;
+ bool odd;
+
+ /// <summary>Create a new polyline with symbol generator.</summary>
+ /// <param name="symbol">The symbol to use</param>
+ /// <param name="symbolSize">The size of the symbol. In case of a circle, this is the diameter.</param>
+ /// <param name="symbolPadding">The padding on both sides of the symbol between the symbol and the line.</param>
+ /// <param name="symbolSpacing">The spacing between symbols. This is the distance between the centers of the symbols.</param>
+ /// <param name="reverseSymbols">If true, the symbols will be reversed. For cicles this has no effect, but arrowhead symbols will be reversed.</param>
+ public PolylineWithSymbol(SymbolDecoration symbol, float symbolSize, float symbolPadding, float symbolSpacing, bool reverseSymbols = false) {
+ if (symbolSpacing <= math.FLT_MIN_NORMAL) throw new System.ArgumentOutOfRangeException(nameof(symbolSpacing), "Symbol spacing must be greater than zero");
+ if (symbolSize <= math.FLT_MIN_NORMAL) throw new System.ArgumentOutOfRangeException(nameof(symbolSize), "Symbol size must be greater than zero");
+ if (symbolPadding < 0) throw new System.ArgumentOutOfRangeException(nameof(symbolPadding), "Symbol padding must non-negative");
+
+ this.prev = float3.zero;
+ this.symbol = symbol;
+ this.symbolSize = symbolSize;
+ this.symbolPadding = symbolPadding;
+ this.symbolSpacing = math.max(0, symbolSpacing - symbolPadding * 2f - symbolSize);
+ this.reverseSymbols = reverseSymbols;
+ symbolOffset = symbol == SymbolDecoration.ArrowHead ? -0.25f * symbolSize : 0;
+ if (reverseSymbols) {
+ symbolOffset = -symbolOffset;
+ }
+ symbolOffset += 0.5f * symbolSize;
+ offset = -1;
+ odd = false;
+ }
+
+ /// <summary>
+ /// Move to a new point.
+ ///
+ /// This will draw the symbols and line segments between the previous point and the new point.
+ /// </summary>
+ /// <param name="draw">The command builder to draw to. You can use a built-in builder like \reflink{Draw.editor} or \reflink{Draw.ingame}, or use a custom one.</param>
+ /// <param name="next">The next point in the polyline to move to.</param>
+ public void MoveTo (ref CommandBuilder draw, float3 next) {
+ if (offset == -1) {
+ offset = this.symbolSpacing * 0.5f;
+ prev = next;
+ return;
+ }
+ var len = math.length(next - prev);
+ var invLen = math.rcp(len);
+ var dir = next - prev;
+ float3 up = default;
+ if (symbol != SymbolDecoration.None) {
+ up = math.normalizesafe(math.cross(dir, math.cross(dir, new float3(0, 1, 0))));
+ if (math.all(up == 0f)) {
+ up = new float3(0, 0, 1);
+ }
+ }
+ if (reverseSymbols) dir = -dir;
+ if (offset > 0 && !odd) {
+ draw.Line(prev, math.lerp(prev, next, math.min(offset * invLen, 1)));
+ }
+ while (offset < len) {
+ if (odd) {
+ var pLast = math.lerp(prev, next, offset * invLen);
+ offset += symbolSpacing;
+ var p = math.lerp(prev, next, math.min(offset * invLen, 1));
+ draw.Line(pLast, p);
+ offset += symbolPadding;
+ } else {
+ var p = math.lerp(prev, next, (offset + symbolOffset) * invLen);
+ switch (symbol) {
+ case SymbolDecoration.None:
+ break;
+ case SymbolDecoration.ArrowHead:
+ draw.Arrowhead(p, dir, up, symbolSize);
+ break;
+ case SymbolDecoration.Circle:
+ default:
+ draw.Circle(p, up, symbolSize * 0.5f);
+ break;
+ }
+ offset += symbolSize + symbolPadding;
+ }
+ odd = !odd;
+ }
+ offset -= len;
+ prev = next;
+ }
+ }
+
+ /// <summary>
+ /// Draws the outline of a box which is axis-aligned.
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the box</param>
+ /// <param name="size">Width of the box along all dimensions</param>
+ public void WireBox (float3 center, float3 size) {
+ Reserve<BoxData>();
+ Add(Command.WireBox);
+ Add(new BoxData { center = center, size = size });
+ }
+
+ /// <summary>
+ /// Draws the outline of a box.
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the box</param>
+ /// <param name="rotation">Rotation of the box</param>
+ /// <param name="size">Width of the box along all dimensions</param>
+ public void WireBox (float3 center, quaternion rotation, float3 size) {
+ PushMatrix(float4x4.TRS(center, rotation, size));
+ WireBox(float3.zero, new float3(1, 1, 1));
+ PopMatrix();
+ }
+
+ /// <summary>
+ /// Draws the outline of a box.
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ public void WireBox (Bounds bounds) {
+ WireBox(bounds.center, bounds.size);
+ }
+
+ /// <summary>
+ /// Draws a wire mesh.
+ /// Every single edge of the mesh will be drawn using a <see cref="Line"/> command.
+ ///
+ /// <code>
+ /// var go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
+ /// go.transform.position = new Vector3(0, 0, 0);
+ /// using (Draw.InLocalSpace(go.transform)) {
+ /// Draw.WireMesh(go.GetComponent<MeshFilter>().sharedMesh, color);
+ /// }
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="SolidMesh(Mesh)"/>
+ ///
+ /// Version: Supported in Unity 2020.1 or later.
+ /// </summary>
+ public void WireMesh (Mesh mesh) {
+#if UNITY_2020_1_OR_NEWER
+ if (mesh == null) throw new System.ArgumentNullException();
+
+ // Use a burst compiled function to draw the lines
+ // This is significantly faster than pure C# (about 5x).
+ var meshDataArray = Mesh.AcquireReadOnlyMeshData(mesh);
+ var meshData = meshDataArray[0];
+
+ JobWireMesh.JobWireMeshFunctionPointer(ref meshData, ref this);
+ meshDataArray.Dispose();
+#else
+ Debug.LogError("The WireMesh method is only suppored in Unity 2020.1 or later");
+#endif
+ }
+
+ /// <summary>
+ /// Draws a wire mesh.
+ /// Every single edge of the mesh will be drawn using a <see cref="Line"/> command.
+ ///
+ /// <code>
+ /// var go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
+ /// go.transform.position = new Vector3(0, 0, 0);
+ /// using (Draw.InLocalSpace(go.transform)) {
+ /// Draw.WireMesh(go.GetComponent<MeshFilter>().sharedMesh, color);
+ /// }
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="SolidMesh(Mesh)"/>
+ ///
+ /// Version: Supported in Unity 2020.1 or later.
+ /// </summary>
+ public void WireMesh (NativeArray<float3> vertices, NativeArray<int> triangles) {
+#if UNITY_2020_1_OR_NEWER
+ unsafe {
+ JobWireMesh.WireMesh((float3*)vertices.GetUnsafeReadOnlyPtr(), (int*)triangles.GetUnsafeReadOnlyPtr(), vertices.Length, triangles.Length, ref this);
+ }
+#else
+ Debug.LogError("The WireMesh method is only suppored in Unity 2020.1 or later");
+#endif
+ }
+
+#if UNITY_2020_1_OR_NEWER
+ /// <summary>Helper job for <see cref="WireMesh"/></summary>
+ [BurstCompile]
+ class JobWireMesh {
+ public delegate void JobWireMeshDelegate(ref Mesh.MeshData rawMeshData, ref CommandBuilder draw);
+
+ public static readonly JobWireMeshDelegate JobWireMeshFunctionPointer = BurstCompiler.CompileFunctionPointer<JobWireMeshDelegate>(Execute).Invoke;
+
+ [BurstCompile]
+ public static unsafe void WireMesh (float3* verts, int* indices, int vertexCount, int indexCount, ref CommandBuilder draw) {
+ // Ignore warning about NativeHashMap being obsolete in early versions of the collections package.
+ // It works just fine, and in later versions the NativeHashMap is not obsolete.
+ #pragma warning disable 618
+ var seenEdges = new NativeHashMap<int2, bool>(indexCount, Allocator.Temp);
+ #pragma warning restore 618
+ for (int i = 0; i < indexCount; i += 3) {
+ var a = indices[i];
+ var b = indices[i+1];
+ var c = indices[i+2];
+ if (a < 0 || b < 0 || c < 0 || a >= vertexCount || b >= vertexCount || c >= vertexCount) {
+ throw new Exception("Invalid vertex index. Index out of bounds");
+ }
+ int v1, v2;
+
+ // Draw each edge of the triangle.
+ // Check so that we do not draw an edge twice.
+ v1 = math.min(a, b);
+ v2 = math.max(a, b);
+ if (!seenEdges.ContainsKey(new int2(v1, v2))) {
+ seenEdges.Add(new int2(v1, v2), true);
+ draw.Line(verts[v1], verts[v2]);
+ }
+
+ v1 = math.min(b, c);
+ v2 = math.max(b, c);
+ if (!seenEdges.ContainsKey(new int2(v1, v2))) {
+ seenEdges.Add(new int2(v1, v2), true);
+ draw.Line(verts[v1], verts[v2]);
+ }
+
+ v1 = math.min(c, a);
+ v2 = math.max(c, a);
+ if (!seenEdges.ContainsKey(new int2(v1, v2))) {
+ seenEdges.Add(new int2(v1, v2), true);
+ draw.Line(verts[v1], verts[v2]);
+ }
+ }
+ }
+
+ [BurstCompile]
+ [AOT.MonoPInvokeCallback(typeof(JobWireMeshDelegate))]
+ static void Execute (ref Mesh.MeshData rawMeshData, ref CommandBuilder draw) {
+ int maxIndices = 0;
+ for (int subMeshIndex = 0; subMeshIndex < rawMeshData.subMeshCount; subMeshIndex++) {
+ maxIndices = math.max(maxIndices, rawMeshData.GetSubMesh(subMeshIndex).indexCount);
+ }
+ var tris = new NativeArray<int>(maxIndices, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ var verts = new NativeArray<Vector3>(rawMeshData.vertexCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ rawMeshData.GetVertices(verts);
+
+ for (int subMeshIndex = 0; subMeshIndex < rawMeshData.subMeshCount; subMeshIndex++) {
+ var submesh = rawMeshData.GetSubMesh(subMeshIndex);
+ rawMeshData.GetIndices(tris, subMeshIndex);
+ unsafe {
+ WireMesh((float3*)verts.GetUnsafeReadOnlyPtr(), (int*)tris.GetUnsafeReadOnlyPtr(), verts.Length, submesh.indexCount, ref draw);
+ }
+ }
+ }
+ }
+#endif
+
+ /// <summary>
+ /// Draws a solid mesh.
+ /// The mesh will be drawn with a solid color.
+ ///
+ /// <code>
+ /// var go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
+ /// go.transform.position = new Vector3(0, 0, 0);
+ /// using (Draw.InLocalSpace(go.transform)) {
+ /// Draw.SolidMesh(go.GetComponent<MeshFilter>().sharedMesh, color);
+ /// }
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// Note: This method is not thread safe and must not be used from the Unity Job System.
+ /// TODO: Are matrices handled?
+ ///
+ /// See: <see cref="WireMesh(Mesh)"/>
+ /// </summary>
+ public void SolidMesh (Mesh mesh) {
+ SolidMeshInternal(mesh, false);
+ }
+
+ void SolidMeshInternal (Mesh mesh, bool temporary, Color color) {
+ PushColor(color);
+ SolidMeshInternal(mesh, temporary);
+ PopColor();
+ }
+
+
+ void SolidMeshInternal (Mesh mesh, bool temporary) {
+ var g = gizmos.Target as DrawingData;
+
+ g.data.Get(uniqueID).meshes.Add(new SubmittedMesh {
+ mesh = mesh,
+ temporary = temporary,
+ });
+ // Internally we need to make sure to capture the current state
+ // (which includes the current matrix and color) so that it
+ // can be applied to the mesh.
+ Reserve(4);
+ Add(Command.CaptureState);
+ }
+
+ /// <summary>
+ /// Draws a solid mesh with the given vertices.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Note: This method is not thread safe and must not be used from the Unity Job System.
+ /// TODO: Are matrices handled?
+ /// </summary>
+ [BurstDiscard]
+ public void SolidMesh (List<Vector3> vertices, List<int> triangles, List<Color> colors) {
+ if (vertices.Count != colors.Count) throw new System.ArgumentException("Number of colors must be the same as the number of vertices");
+
+ // TODO: Is this mesh getting recycled at all?
+ var g = gizmos.Target as DrawingData;
+ var mesh = g.GetMesh(vertices.Count);
+
+ // Set all data on the mesh
+ mesh.Clear();
+ mesh.SetVertices(vertices);
+ mesh.SetTriangles(triangles, 0);
+ mesh.SetColors(colors);
+ // Upload all data
+ mesh.UploadMeshData(false);
+ SolidMeshInternal(mesh, true);
+ }
+
+ /// <summary>
+ /// Draws a solid mesh with the given vertices.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Note: This method is not thread safe and must not be used from the Unity Job System.
+ /// TODO: Are matrices handled?
+ /// </summary>
+ [BurstDiscard]
+ public void SolidMesh (Vector3[] vertices, int[] triangles, Color[] colors, int vertexCount, int indexCount) {
+ if (vertices.Length != colors.Length) throw new System.ArgumentException("Number of colors must be the same as the number of vertices");
+
+ // TODO: Is this mesh getting recycled at all?
+ var g = gizmos.Target as DrawingData;
+ var mesh = g.GetMesh(vertices.Length);
+
+ // Set all data on the mesh
+ mesh.Clear();
+ mesh.SetVertices(vertices, 0, vertexCount);
+ mesh.SetTriangles(triangles, 0, indexCount, 0);
+ mesh.SetColors(colors, 0, vertexCount);
+ // Upload all data
+ mesh.UploadMeshData(false);
+ SolidMeshInternal(mesh, true);
+ }
+
+ /// <summary>
+ /// Draws a 3D cross.
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ public void Cross (float3 position, float size = 1) {
+ size *= 0.5f;
+ Line(position - new float3(size, 0, 0), position + new float3(size, 0, 0));
+ Line(position - new float3(0, size, 0), position + new float3(0, size, 0));
+ Line(position - new float3(0, 0, size), position + new float3(0, 0, size));
+ }
+
+ /// <summary>
+ /// Draws a cross in the XZ plane.
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ [System.Obsolete("Use Draw.xz.Cross instead")]
+ public void CrossXZ (float3 position, float size = 1) {
+ size *= 0.5f;
+ Line(position - new float3(size, 0, 0), position + new float3(size, 0, 0));
+ Line(position - new float3(0, 0, size), position + new float3(0, 0, size));
+ }
+
+ /// <summary>
+ /// Draws a cross in the XY plane.
+ ///
+ /// [Open online documentation to see images]
+ /// </summary>
+ [System.Obsolete("Use Draw.xy.Cross instead")]
+ public void CrossXY (float3 position, float size = 1) {
+ size *= 0.5f;
+ Line(position - new float3(size, 0, 0), position + new float3(size, 0, 0));
+ Line(position - new float3(0, size, 0), position + new float3(0, size, 0));
+ }
+
+ /// <summary>Returns a point on a cubic bezier curve. t is clamped between 0 and 1</summary>
+ public static float3 EvaluateCubicBezier (float3 p0, float3 p1, float3 p2, float3 p3, float t) {
+ t = math.clamp(t, 0, 1);
+ float tr = 1-t;
+ return tr*tr*tr * p0 + 3 * tr*tr * t * p1 + 3 * tr * t*t * p2 + t*t*t * p3;
+ }
+
+ /// <summary>
+ /// Draws a cubic bezier curve.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// TODO: Currently uses a fixed resolution of 20 segments. Resolution should depend on the distance to the camera.
+ ///
+ /// See: https://en.wikipedia.org/wiki/Bezier_curve
+ /// </summary>
+ /// <param name="p0">Start point</param>
+ /// <param name="p1">First control point</param>
+ /// <param name="p2">Second control point</param>
+ /// <param name="p3">End point</param>
+ public void Bezier (float3 p0, float3 p1, float3 p2, float3 p3) {
+ float3 prev = p0;
+
+ for (int i = 1; i <= 20; i++) {
+ float t = i/20.0f;
+ float3 p = EvaluateCubicBezier(p0, p1, p2, p3, t);
+ Line(prev, p);
+ prev = p;
+ }
+ }
+
+ /// <summary>
+ /// Draws a smooth curve through a list of points.
+ ///
+ /// A catmull-rom spline is equivalent to a bezier curve with control points determined by an algorithm.
+ /// In fact, this package displays catmull-rom splines by first converting them to bezier curves.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
+ /// See: <see cref="CatmullRom(float3,float3,float3,float3)"/>
+ /// </summary>
+ /// <param name="points">The curve will smoothly pass through each point in the list in order.</param>
+ public void CatmullRom (List<Vector3> points) {
+ if (points.Count < 2) return;
+
+ if (points.Count == 2) {
+ Line(points[0], points[1]);
+ } else {
+ // count >= 3
+ var count = points.Count;
+ // Draw first curve, this is special because the first two control points are the same
+ CatmullRom(points[0], points[0], points[1], points[2]);
+ for (int i = 0; i + 3 < count; i++) {
+ CatmullRom(points[i], points[i+1], points[i+2], points[i+3]);
+ }
+ // Draw last curve
+ CatmullRom(points[count-3], points[count-2], points[count-1], points[count-1]);
+ }
+ }
+
+ /// <summary>
+ /// Draws a centripetal catmull rom spline.
+ ///
+ /// The curve starts at p1 and ends at p2.
+ ///
+ /// [Open online documentation to see images]
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="CatmullRom(List<Vector3>)"/>
+ /// </summary>
+ /// <param name="p0">First control point</param>
+ /// <param name="p1">Second control point. Start of the curve.</param>
+ /// <param name="p2">Third control point. End of the curve.</param>
+ /// <param name="p3">Fourth control point.</param>
+ public void CatmullRom (float3 p0, float3 p1, float3 p2, float3 p3) {
+ // References used:
+ // p.266 GemsV1
+ //
+ // tension is often set to 0.5 but you can use any reasonable value:
+ // http://www.cs.cmu.edu/~462/projects/assn2/assn2/catmullRom.pdf
+ //
+ // bias and tension controls:
+ // http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/interpolation/
+
+ // We will convert the catmull rom spline to a bezier curve for simplicity.
+ // The end result of this will be a conversion matrix where we transform catmull rom control points
+ // into the equivalent bezier curve control points.
+
+ // Conversion matrix
+ // =================
+
+ // A centripetal catmull rom spline can be separated into the following terms:
+ // 1 * p1 +
+ // t * (-0.5 * p0 + 0.5*p2) +
+ // t*t * (p0 - 2.5*p1 + 2.0*p2 + 0.5*t2) +
+ // t*t*t * (-0.5*p0 + 1.5*p1 - 1.5*p2 + 0.5*p3)
+ //
+ // Matrix form:
+ // 1 t t^2 t^3
+ // {0, -1/2, 1, -1/2}
+ // {1, 0, -5/2, 3/2}
+ // {0, 1/2, 2, -3/2}
+ // {0, 0, -1/2, 1/2}
+
+ // Transposed matrix:
+ // M_1 = {{0, 1, 0, 0}, {-1/2, 0, 1/2, 0}, {1, -5/2, 2, -1/2}, {-1/2, 3/2, -3/2, 1/2}}
+
+ // A bezier spline can be separated into the following terms:
+ // (-t^3 + 3 t^2 - 3 t + 1) * c0 +
+ // (3t^3 - 6*t^2 + 3t) * c1 +
+ // (3t^2 - 3t^3) * c2 +
+ // t^3 * c3
+ //
+ // Matrix form:
+ // 1 t t^2 t^3
+ // {1, -3, 3, -1}
+ // {0, 3, -6, 3}
+ // {0, 0, 3, -3}
+ // {0, 0, 0, 1}
+
+ // Transposed matrix:
+ // M_2 = {{1, 0, 0, 0}, {-3, 3, 0, 0}, {3, -6, 3, 0}, {-1, 3, -3, 1}}
+
+ // Thus a bezier curve can be evaluated using the expression
+ // output1 = T * M_1 * c
+ // where T = [1, t, t^2, t^3] and c being the control points c = [c0, c1, c2, c3]^T
+ //
+ // and a catmull rom spline can be evaluated using
+ //
+ // output2 = T * M_2 * p
+ // where T = same as before and p = [p0, p1, p2, p3]^T
+ //
+ // We can solve for c in output1 = output2
+ // T * M_1 * c = T * M_2 * p
+ // M_1 * c = M_2 * p
+ // c = M_1^(-1) * M_2 * p
+ // Thus a conversion matrix from p to c is M_1^(-1) * M_2
+ // This can be calculated and the result is the following matrix:
+ //
+ // {0, 1, 0, 0}
+ // {-1/6, 1, 1/6, 0}
+ // {0, 1/6, 1, -1/6}
+ // {0, 0, 1, 0}
+ // ------------------------------------------------------------------
+ //
+ // Using this we can calculate c = M_1^(-1) * M_2 * p
+ var c0 = p1;
+ var c1 = (-p0 + 6*p1 + 1*p2)*(1/6.0f);
+ var c2 = (p1 + 6*p2 - p3)*(1/6.0f);
+ var c3 = p2;
+
+ // And finally draw the bezier curve which is equivalent to the desired catmull-rom spline
+ Bezier(c0, c1, c2, c3);
+ }
+
+ /// <summary>
+ /// Draws an arrow between two points.
+ ///
+ /// The size of the head defaults to 20% of the length of the arrow.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="ArrowheadArc"/>
+ /// See: <see cref="Arrow(float3,float3,float3,float)"/>
+ /// See: <see cref="ArrowRelativeSizeHead"/>
+ /// </summary>
+ /// <param name="from">Base of the arrow.</param>
+ /// <param name="to">Head of the arrow.</param>
+ public void Arrow (float3 from, float3 to) {
+ ArrowRelativeSizeHead(from, to, DEFAULT_UP, 0.2f);
+ }
+
+ /// <summary>
+ /// Draws an arrow between two points.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="ArrowRelativeSizeHead"/>
+ /// See: <see cref="ArrowheadArc"/>
+ /// </summary>
+ /// <param name="from">Base of the arrow.</param>
+ /// <param name="to">Head of the arrow.</param>
+ /// <param name="up">Up direction of the world, the arrowhead plane will be as perpendicular as possible to this direction. Defaults to Vector3.up.</param>
+ /// <param name="headSize">The size of the arrowhead in world units.</param>
+ public void Arrow (float3 from, float3 to, float3 up, float headSize) {
+ var length_sq = math.lengthsq(to - from);
+
+ if (length_sq > 0.000001f) {
+ ArrowRelativeSizeHead(from, to, up, headSize * math.rsqrt(length_sq));
+ }
+ }
+
+ /// <summary>
+ /// Draws an arrow between two points with a head that varies with the length of the arrow.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="ArrowheadArc"/>
+ /// See: <see cref="Arrow"/>
+ /// </summary>
+ /// <param name="from">Base of the arrow.</param>
+ /// <param name="to">Head of the arrow.</param>
+ /// <param name="up">Up direction of the world, the arrowhead plane will be as perpendicular as possible to this direction.</param>
+ /// <param name="headFraction">The length of the arrowhead is the distance between from and to multiplied by this fraction. Should be between 0 and 1.</param>
+ public void ArrowRelativeSizeHead (float3 from, float3 to, float3 up, float headFraction) {
+ Line(from, to);
+ var dir = to - from;
+
+ var normal = math.cross(dir, up);
+ // Pick a different up direction if the direction happened to be colinear with that one.
+ if (math.all(normal == 0)) normal = math.cross(new float3(1, 0, 0), dir);
+ // Pick a different up direction if up=(1,0,0) and thus the above check would have generated a zero vector again
+ if (math.all(normal == 0)) normal = math.cross(new float3(0, 1, 0), dir);
+ normal = math.normalizesafe(normal) * math.length(dir);
+
+ Line(to, to - (dir + normal) * headFraction);
+ Line(to, to - (dir - normal) * headFraction);
+ }
+
+ /// <summary>
+ /// Draws an arrowhead at a point.
+ ///
+ /// <code>
+ /// Draw.Arrowhead(Vector3.zero, Vector3.forward, 0.75f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="Arrow"/>
+ /// See: <see cref="ArrowRelativeSizeHead"/>
+ /// </summary>
+ /// <param name="center">Center of the arrowhead.</param>
+ /// <param name="direction">Direction the arrow is pointing.</param>
+ /// <param name="radius">Distance from the center to each corner of the arrowhead.</param>
+ public void Arrowhead (float3 center, float3 direction, float radius) {
+ Arrowhead(center, direction, DEFAULT_UP, radius);
+ }
+
+ /// <summary>
+ /// Draws an arrowhead at a point.
+ ///
+ /// <code>
+ /// Draw.Arrowhead(Vector3.zero, Vector3.forward, 0.75f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="Arrow"/>
+ /// See: <see cref="ArrowRelativeSizeHead"/>
+ /// </summary>
+ /// <param name="center">Center of the arrowhead.</param>
+ /// <param name="direction">Direction the arrow is pointing.</param>
+ /// <param name="up">Up direction of the world, the arrowhead plane will be as perpendicular as possible to this direction. Defaults to Vector3.up. Must be normalized.</param>
+ /// <param name="radius">Distance from the center to each corner of the arrowhead.</param>
+ public void Arrowhead (float3 center, float3 direction, float3 up, float radius) {
+ if (math.all(direction == 0)) return;
+ direction = math.normalizesafe(direction);
+ var normal = math.cross(direction, up);
+ const float SinPiOver3 = 0.866025f;
+ const float CosPiOver3 = 0.5f;
+ var circleCenter = center - radius * (1 - CosPiOver3)*0.5f * direction;
+ var p1 = circleCenter + radius * direction;
+ var p2 = circleCenter - radius * CosPiOver3 * direction + radius * SinPiOver3 * normal;
+ var p3 = circleCenter - radius * CosPiOver3 * direction - radius * SinPiOver3 * normal;
+ Line(p1, p2);
+ Line(p2, circleCenter);
+ Line(circleCenter, p3);
+ Line(p3, p1);
+ }
+
+ /// <summary>
+ /// Draws an arrowhead centered around a circle.
+ ///
+ /// This can be used to for example show the direction a character is moving in.
+ ///
+ /// [Open online documentation to see images]
+ ///
+ /// Note: In the image above the arrowhead is the only part that is drawn by this method. The cylinder is only included for context.
+ ///
+ /// See: <see cref="Arrow"/>
+ /// </summary>
+ /// <param name="origin">Point around which the arc is centered</param>
+ /// <param name="direction">Direction the arrow is pointing</param>
+ /// <param name="offset">Distance from origin that the arrow starts.</param>
+ /// <param name="width">Width of the arrowhead in degrees (defaults to 60). Should be between 0 and 90.</param>
+ public void ArrowheadArc (float3 origin, float3 direction, float offset, float width = 60) {
+ if (!math.any(direction)) return;
+ if (offset < 0) throw new System.ArgumentOutOfRangeException(nameof(offset));
+ if (offset == 0) return;
+
+ var rot = Quaternion.LookRotation(direction, DEFAULT_UP);
+ PushMatrix(Matrix4x4.TRS(origin, rot, Vector3.one));
+ var a1 = math.PI * 0.5f - width * (0.5f * Mathf.Deg2Rad);
+ var a2 = math.PI * 0.5f + width * (0.5f * Mathf.Deg2Rad);
+ CircleXZInternal(float3.zero, offset, a1, a2);
+ var p1 = new float3(math.cos(a1), 0, math.sin(a1)) * offset;
+ var p2 = new float3(math.cos(a2), 0, math.sin(a2)) * offset;
+ const float sqrt2 = 1.4142f;
+ var p3 = new float3(0, 0, sqrt2 * offset);
+ Line(p1, p3);
+ Line(p3, p2);
+ PopMatrix();
+ }
+
+ /// <summary>
+ /// Draws a grid of lines.
+ ///
+ /// <code>
+ /// Draw.xz.WireGrid(Vector3.zero, new int2(3, 3), new float2(1, 1), color);
+ /// </code>
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the grid</param>
+ /// <param name="rotation">Rotation of the grid. The grid will be aligned to the X and Z axes of the rotation.</param>
+ /// <param name="cells">Number of cells of the grid. Should be greater than 0.</param>
+ /// <param name="totalSize">Total size of the grid along the X and Z axes.</param>
+ public void WireGrid (float3 center, quaternion rotation, int2 cells, float2 totalSize) {
+ cells = math.max(cells, new int2(1, 1));
+ PushMatrix(float4x4.TRS(center, rotation, new Vector3(totalSize.x, 0, totalSize.y)));
+ int w = cells.x;
+ int h = cells.y;
+ for (int i = 0; i <= w; i++) Line(new float3(i/(float)w - 0.5f, 0, -0.5f), new float3(i/(float)w - 0.5f, 0, 0.5f));
+ for (int i = 0; i <= h; i++) Line(new float3(-0.5f, 0, i/(float)h - 0.5f), new float3(0.5f, 0, i/(float)h - 0.5f));
+ PopMatrix();
+ }
+
+ /// <summary>
+ /// Draws a triangle outline.
+ ///
+ /// <code>
+ /// Draw.WireTriangle(new Vector3(-0.5f, 0, 0), new Vector3(0, 1, 0), new Vector3(0.5f, 0, 0), Color.black);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="Draw.WirePlane(float3,quaternion,float2)"/>
+ /// See: <see cref="WirePolygon"/>
+ /// See: <see cref="SolidTriangle"/>
+ /// </summary>
+ /// <param name="a">First corner of the triangle</param>
+ /// <param name="b">Second corner of the triangle</param>
+ /// <param name="c">Third corner of the triangle</param>
+ public void WireTriangle (float3 a, float3 b, float3 c) {
+ Line(a, b);
+ Line(b, c);
+ Line(c, a);
+ }
+
+ /// <summary>
+ /// Draws a rectangle outline.
+ /// The rectangle will be aligned to the X and Z axes.
+ ///
+ /// <code>
+ /// Draw.xz.WireRectangle(new Vector3(0f, 0, 0), new Vector2(1, 1), Color.black);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="WirePolygon"/>
+ /// </summary>
+ [System.Obsolete("Use Draw.xz.WireRectangle instead")]
+ public void WireRectangleXZ (float3 center, float2 size) {
+ WireRectangle(center, quaternion.identity, size);
+ }
+
+ /// <summary>
+ /// Draws a rectangle outline.
+ /// The rectangle will be oriented along the rotation's X and Z axes.
+ ///
+ /// <code>
+ /// Draw.WireRectangle(new Vector3(0f, 0, 0), Quaternion.identity, new Vector2(1, 1), Color.black);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// This is identical to <see cref="Draw.WirePlane(float3,quaternion,float2)"/>, but this name is added for consistency.
+ ///
+ /// See: <see cref="WirePolygon"/>
+ /// </summary>
+ public void WireRectangle (float3 center, quaternion rotation, float2 size) {
+ WirePlane(center, rotation, size);
+ }
+
+ /// <summary>
+ /// Draws a rectangle outline.
+ /// The rectangle corners are assumed to be in XY space.
+ /// This is particularly useful when combined with <see cref="InScreenSpace"/>.
+ ///
+ /// <code>
+ /// using (Draw.InScreenSpace(Camera.main)) {
+ /// Draw.xy.WireRectangle(new Rect(10, 10, 100, 100), Color.black);
+ /// }
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="WireRectangleXZ"/>
+ /// See: <see cref="WireRectangle(float3,quaternion,float2)"/>
+ /// See: <see cref="WirePolygon"/>
+ /// </summary>
+ [System.Obsolete("Use Draw.xy.WireRectangle instead")]
+ public void WireRectangle (Rect rect) {
+ xy.WireRectangle(rect);
+ }
+
+
+ /// <summary>
+ /// Draws a triangle outline.
+ ///
+ /// <code>
+ /// Draw.WireTriangle(Vector3.zero, Quaternion.identity, 0.5f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// Note: This is a convenience wrapper for <see cref="WirePolygon(float3,int,quaternion,float)"/>
+ ///
+ /// See: <see cref="WireTriangle(float3,float3,float3)"/>
+ /// </summary>
+ /// <param name="center">Center of the triangle.</param>
+ /// <param name="rotation">Rotation of the triangle. The first vertex will be radius units in front of center as seen from the rotation's point of view.</param>
+ /// <param name="radius">Distance from the center to each vertex.</param>
+ public void WireTriangle (float3 center, quaternion rotation, float radius) {
+ WirePolygon(center, 3, rotation, radius);
+ }
+
+ /// <summary>
+ /// Draws a pentagon outline.
+ ///
+ /// <code>
+ /// Draw.WirePentagon(Vector3.zero, Quaternion.identity, 0.5f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// Note: This is a convenience wrapper for <see cref="WirePolygon(float3,int,quaternion,float)"/>
+ /// </summary>
+ /// <param name="center">Center of the polygon.</param>
+ /// <param name="rotation">Rotation of the polygon. The first vertex will be radius units in front of center as seen from the rotation's point of view.</param>
+ /// <param name="radius">Distance from the center to each vertex.</param>
+ public void WirePentagon (float3 center, quaternion rotation, float radius) {
+ WirePolygon(center, 5, rotation, radius);
+ }
+
+ /// <summary>
+ /// Draws a hexagon outline.
+ ///
+ /// <code>
+ /// Draw.WireHexagon(Vector3.zero, Quaternion.identity, 0.5f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// Note: This is a convenience wrapper for <see cref="WirePolygon(float3,int,quaternion,float)"/>
+ /// </summary>
+ /// <param name="center">Center of the polygon.</param>
+ /// <param name="rotation">Rotation of the polygon. The first vertex will be radius units in front of center as seen from the rotation's point of view.</param>
+ /// <param name="radius">Distance from the center to each vertex.</param>
+ public void WireHexagon (float3 center, quaternion rotation, float radius) {
+ WirePolygon(center, 6, rotation, radius);
+ }
+
+ /// <summary>
+ /// Draws a regular polygon outline.
+ ///
+ /// <code>
+ /// Draw.WirePolygon(new Vector3(-0.5f, 0, +0.5f), 3, Quaternion.identity, 0.4f, color);
+ /// Draw.WirePolygon(new Vector3(+0.5f, 0, +0.5f), 4, Quaternion.identity, 0.4f, color);
+ /// Draw.WirePolygon(new Vector3(-0.5f, 0, -0.5f), 5, Quaternion.identity, 0.4f, color);
+ /// Draw.WirePolygon(new Vector3(+0.5f, 0, -0.5f), 6, Quaternion.identity, 0.4f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="WireTriangle"/>
+ /// See: <see cref="WirePentagon"/>
+ /// See: <see cref="WireHexagon"/>
+ /// </summary>
+ /// <param name="center">Center of the polygon.</param>
+ /// <param name="vertices">Number of corners (and sides) of the polygon.</param>
+ /// <param name="rotation">Rotation of the polygon. The first vertex will be radius units in front of center as seen from the rotation's point of view.</param>
+ /// <param name="radius">Distance from the center to each vertex.</param>
+ public void WirePolygon (float3 center, int vertices, quaternion rotation, float radius) {
+ PushMatrix(float4x4.TRS(center, rotation, new float3(radius, radius, radius)));
+ float3 prev = new float3(0, 0, 1);
+ for (int i = 1; i <= vertices; i++) {
+ float a = 2 * math.PI * (i / (float)vertices);
+ var p = new float3(math.sin(a), 0, math.cos(a));
+ Line(prev, p);
+ prev = p;
+ }
+ PopMatrix();
+ }
+
+ /// <summary>
+ /// Draws a solid rectangle.
+ /// The rectangle corners are assumed to be in XY space.
+ /// This is particularly useful when combined with <see cref="InScreenSpace"/>.
+ ///
+ /// Behind the scenes this is implemented using <see cref="SolidPlane"/>.
+ ///
+ /// <code>
+ /// using (Draw.InScreenSpace(Camera.main)) {
+ /// Draw.xy.SolidRectangle(new Rect(10, 10, 100, 100), Color.black);
+ /// }
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: <see cref="WireRectangleXZ"/>
+ /// See: <see cref="WireRectangle(float3,quaternion,float2)"/>
+ /// See: <see cref="SolidBox"/>
+ /// </summary>
+ [System.Obsolete("Use Draw.xy.SolidRectangle instead")]
+ public void SolidRectangle (Rect rect) {
+ xy.SolidRectangle(rect);
+ }
+
+ /// <summary>
+ /// Draws a solid plane.
+ ///
+ /// <code>
+ /// Draw.SolidPlane(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the visualized plane.</param>
+ /// <param name="normal">Direction perpendicular to the plane. If this is (0,0,0) then nothing will be rendered.</param>
+ /// <param name="size">Width and height of the visualized plane.</param>
+ public void SolidPlane (float3 center, float3 normal, float2 size) {
+ if (math.any(normal)) {
+ SolidPlane(center, Quaternion.LookRotation(calculateTangent(normal), normal), size);
+ }
+ }
+
+ /// <summary>
+ /// Draws a solid plane.
+ ///
+ /// The plane will lie in the XZ plane with respect to the rotation.
+ ///
+ /// <code>
+ /// Draw.SolidPlane(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the visualized plane.</param>
+ /// <param name="size">Width and height of the visualized plane.</param>
+ public void SolidPlane (float3 center, quaternion rotation, float2 size) {
+ PushMatrix(float4x4.TRS(center, rotation, new float3(size.x, 0, size.y)));
+ Reserve<BoxData>();
+ Add(Command.Box);
+ Add(new BoxData { center = 0, size = 1 });
+ PopMatrix();
+ }
+
+ /// <summary>Returns an arbitrary vector which is orthogonal to the given one</summary>
+ private static float3 calculateTangent (float3 normal) {
+ var tangent = math.cross(new float3(0, 1, 0), normal);
+
+ if (math.all(tangent == 0)) tangent = math.cross(new float3(1, 0, 0), normal);
+ return tangent;
+ }
+
+ /// <summary>
+ /// Draws a wire plane.
+ ///
+ /// <code>
+ /// Draw.WirePlane(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the visualized plane.</param>
+ /// <param name="normal">Direction perpendicular to the plane. If this is (0,0,0) then nothing will be rendered.</param>
+ /// <param name="size">Width and height of the visualized plane.</param>
+ public void WirePlane (float3 center, float3 normal, float2 size) {
+ if (math.any(normal)) {
+ WirePlane(center, Quaternion.LookRotation(calculateTangent(normal), normal), size);
+ }
+ }
+
+ /// <summary>
+ /// Draws a wire plane.
+ ///
+ /// This is identical to <see cref="WireRectangle(float3,quaternion,float2)"/>, but it is included for consistency.
+ ///
+ /// <code>
+ /// Draw.WirePlane(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the visualized plane.</param>
+ /// <param name="rotation">Rotation of the plane. The plane will lie in the XZ plane with respect to the rotation.</param>
+ /// <param name="size">Width and height of the visualized plane.</param>
+ public void WirePlane (float3 center, quaternion rotation, float2 size) {
+ Reserve<PlaneData>();
+ Add(Command.WirePlane);
+ Add(new PlaneData { center = center, rotation = rotation, size = size });
+ }
+
+ /// <summary>
+ /// Draws a plane and a visualization of its normal.
+ ///
+ /// <code>
+ /// Draw.PlaneWithNormal(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the visualized plane.</param>
+ /// <param name="normal">Direction perpendicular to the plane. If this is (0,0,0) then nothing will be rendered.</param>
+ /// <param name="size">Width and height of the visualized plane.</param>
+ public void PlaneWithNormal (float3 center, float3 normal, float2 size) {
+ if (math.any(normal)) {
+ PlaneWithNormal(center, Quaternion.LookRotation(calculateTangent(normal), normal), size);
+ }
+ }
+
+ /// <summary>
+ /// Draws a plane and a visualization of its normal.
+ ///
+ /// <code>
+ /// Draw.PlaneWithNormal(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the visualized plane.</param>
+ /// <param name="rotation">Rotation of the plane. The plane will lie in the XZ plane with respect to the rotation.</param>
+ /// <param name="size">Width and height of the visualized plane.</param>
+ public void PlaneWithNormal (float3 center, quaternion rotation, float2 size) {
+ SolidPlane(center, rotation, size);
+ WirePlane(center, rotation, size);
+ ArrowRelativeSizeHead(center, center + math.mul(rotation, new float3(0, 1, 0)) * 0.5f, math.mul(rotation, new float3(0, 0, 1)), 0.2f);
+ }
+
+ /// <summary>
+ /// Draws a solid triangle.
+ ///
+ /// <code>
+ /// Draw.xy.SolidTriangle(new float2(-0.43f, -0.25f), new float2(0, 0.5f), new float2(0.43f, -0.25f), color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// Note: If you are going to be drawing lots of triangles it's better to use <see cref="Draw.SolidMesh"/> instead as it will be more efficient.
+ ///
+ /// See: <see cref="Draw.SolidMesh"/>
+ /// See: <see cref="Draw.WireTriangle"/>
+ /// </summary>
+ /// <param name="a">First corner of the triangle.</param>
+ /// <param name="b">Second corner of the triangle.</param>
+ /// <param name="c">Third corner of the triangle.</param>
+ public void SolidTriangle (float3 a, float3 b, float3 c) {
+ Reserve<TriangleData>();
+ Add(Command.SolidTriangle);
+ Add(new TriangleData { a = a, b = b, c = c });
+ }
+
+ /// <summary>
+ /// Draws a solid box.
+ ///
+ /// <code>
+ /// Draw.SolidBox(new float3(0, 0, 0), new float3(1, 1, 1), color);
+ /// </code>
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the box</param>
+ /// <param name="size">Width of the box along all dimensions</param>
+ public void SolidBox (float3 center, float3 size) {
+ Reserve<BoxData>();
+ Add(Command.Box);
+ Add(new BoxData { center = center, size = size });
+ }
+
+ /// <summary>
+ /// Draws a solid box.
+ ///
+ /// <code>
+ /// Draw.SolidBox(new float3(0, 0, 0), new float3(1, 1, 1), color);
+ /// </code>
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="bounds">Bounding box of the box</param>
+ public void SolidBox (Bounds bounds) {
+ SolidBox(bounds.center, bounds.size);
+ }
+
+ /// <summary>
+ /// Draws a solid box.
+ ///
+ /// <code>
+ /// Draw.SolidBox(new float3(0, 0, 0), new float3(1, 1, 1), color);
+ /// </code>
+ /// [Open online documentation to see images]
+ /// </summary>
+ /// <param name="center">Center of the box</param>
+ /// <param name="rotation">Rotation of the box</param>
+ /// <param name="size">Width of the box along all dimensions</param>
+ public void SolidBox (float3 center, quaternion rotation, float3 size) {
+ PushMatrix(float4x4.TRS(center, rotation, size));
+ SolidBox(float3.zero, Vector3.one);
+ PopMatrix();
+ }
+
+ /// <summary>
+ /// Draws a label in 3D space.
+ ///
+ /// The default alignment is <see cref="Drawing.LabelAlignment.MiddleLeft"/>.
+ ///
+ /// <code>
+ /// Draw.Label3D(new float3(0.2f, -1f, 0.2f), Quaternion.Euler(45, -110, -90), "Label", 1, LabelAlignment.Center, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: Label3D(float3,quaternion,string,float,LabelAlignment)
+ ///
+ /// Note: Only ASCII is supported since the built-in font texture only includes ASCII. Other characters will be rendered as question marks (?).
+ /// </summary>
+ /// <param name="position">Position in 3D space.</param>
+ /// <param name="rotation">Rotation in 3D space.</param>
+ /// <param name="text">Text to display.</param>
+ /// <param name="size">World size of the text. For large sizes an SDF (signed distance field) font is used and for small sizes a normal font texture is used.</param>
+ public void Label3D (float3 position, quaternion rotation, string text, float size) {
+ Label3D(position, rotation, text, size, LabelAlignment.MiddleLeft);
+ }
+
+ /// <summary>
+ /// Draws a label in 3D space.
+ ///
+ /// <code>
+ /// Draw.Label3D(new float3(0.2f, -1f, 0.2f), Quaternion.Euler(45, -110, -90), "Label", 1, LabelAlignment.Center, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: Label3D(float3,quaternion,string,float)
+ ///
+ /// Note: Only ASCII is supported since the built-in font texture only includes ASCII. Other characters will be rendered as question marks (?).
+ ///
+ /// Note: This method cannot be used in burst since managed strings are not suppported in burst. However, you can use the separate Label3D overload which takes a FixedString.
+ /// </summary>
+ /// <param name="position">Position in 3D space.</param>
+ /// <param name="rotation">Rotation in 3D space.</param>
+ /// <param name="text">Text to display.</param>
+ /// <param name="size">World size of the text. For large sizes an SDF (signed distance field) font is used and for small sizes a normal font texture is used.</param>
+ /// <param name="alignment">How to align the text relative to the given position.</param>
+ public void Label3D (float3 position, quaternion rotation, string text, float size, LabelAlignment alignment) {
+ AssertBufferExists();
+ var g = gizmos.Target as DrawingData;
+ Reserve<TextData3D>();
+ Add(Command.Text3D);
+ Add(new TextData3D { center = position, rotation = rotation, numCharacters = text.Length, size = size, alignment = alignment });
+
+ Reserve(UnsafeUtility.SizeOf<System.UInt16>() * text.Length);
+ for (int i = 0; i < text.Length; i++) {
+ char c = text[i];
+ System.UInt16 index = (System.UInt16)g.fontData.GetIndex(c);
+ Add(index);
+ }
+ }
+
+ /// <summary>
+ /// Draws a label in 3D space aligned with the camera.
+ ///
+ /// The default alignment is <see cref="Drawing.LabelAlignment.MiddleLeft"/>.
+ ///
+ /// <code>
+ /// Draw.Label2D(Vector3.zero, "Label", 48, LabelAlignment.Center, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: Label2D(float3,string,float,LabelAlignment)
+ ///
+ /// Note: Only ASCII is supported since the built-in font texture only includes ASCII. Other characters will be rendered as question marks (?).
+ /// </summary>
+ /// <param name="position">Position in 3D space.</param>
+ /// <param name="text">Text to display.</param>
+ /// <param name="sizeInPixels">Size of the text in screen pixels. For large sizes an SDF (signed distance field) font is used and for small sizes a normal font texture is used.</param>
+ public void Label2D (float3 position, string text, float sizeInPixels = 14) {
+ Label2D(position, text, sizeInPixels, LabelAlignment.MiddleLeft);
+ }
+
+ /// <summary>
+ /// Draws a label in 3D space aligned with the camera.
+ ///
+ /// <code>
+ /// Draw.Label2D(Vector3.zero, "Label", 48, LabelAlignment.Center, color);
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: Label2D(float3,string,float)
+ ///
+ /// Note: Only ASCII is supported since the built-in font texture only includes ASCII. Other characters will be rendered as question marks (?).
+ ///
+ /// Note: This method cannot be used in burst since managed strings are not suppported in burst. However, you can use the separate Label2D overload which takes a FixedString.
+ /// </summary>
+ /// <param name="position">Position in 3D space.</param>
+ /// <param name="text">Text to display.</param>
+ /// <param name="sizeInPixels">Size of the text in screen pixels. For large sizes an SDF (signed distance field) font is used and for small sizes a normal font texture is used.</param>
+ /// <param name="alignment">How to align the text relative to the given position.</param>
+ public void Label2D (float3 position, string text, float sizeInPixels, LabelAlignment alignment) {
+ AssertBufferExists();
+ var g = gizmos.Target as DrawingData;
+ Reserve<TextData>();
+ Add(Command.Text);
+ Add(new TextData { center = position, numCharacters = text.Length, sizeInPixels = sizeInPixels, alignment = alignment });
+
+ Reserve(UnsafeUtility.SizeOf<System.UInt16>() * text.Length);
+ for (int i = 0; i < text.Length; i++) {
+ char c = text[i];
+ System.UInt16 index = (System.UInt16)g.fontData.GetIndex(c);
+ Add(index);
+ }
+ }
+
+ #region Label2DFixedString
+ /// <summary>
+ /// Draws a label in 3D space aligned with the camera.
+ ///
+ /// <code>
+ /// // This part can be inside a burst job
+ /// for (int i = 0; i < 10; i++) {
+ /// Unity.Collections.FixedString32Bytes text = $"X = {i}";
+ /// builder.Label2D(new float3(i, 0, 0), ref text, 12, LabelAlignment.Center);
+ /// }
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: Label2D(float3,string,float)
+ ///
+ /// Note: Only ASCII is supported since the built-in font texture only includes ASCII. Other characters will be rendered as question marks (?).
+ ///
+ /// Note: This method requires the Unity.Collections package version 0.8 or later.
+ /// </summary>
+ /// <param name="position">Position in 3D space.</param>
+ /// <param name="text">Text to display.</param>
+ /// <param name="sizeInPixels">Size of the text in screen pixels. For large sizes an SDF (signed distance field) font is used and for small sizes a normal font texture is used.</param>
+ public void Label2D (float3 position, ref FixedString32Bytes text, float sizeInPixels = 14) {
+ Label2D(position, ref text, sizeInPixels, LabelAlignment.MiddleLeft);
+ }
+
+ /// <summary>\copydocref{Label2D(float3,FixedString32Bytes,float)}</summary>
+ public void Label2D (float3 position, ref FixedString64Bytes text, float sizeInPixels = 14) {
+ Label2D(position, ref text, sizeInPixels, LabelAlignment.MiddleLeft);
+ }
+
+ /// <summary>\copydocref{Label2D(float3,FixedString32Bytes,float)}</summary>
+ public void Label2D (float3 position, ref FixedString128Bytes text, float sizeInPixels = 14) {
+ Label2D(position, ref text, sizeInPixels, LabelAlignment.MiddleLeft);
+ }
+
+ /// <summary>\copydocref{Label2D(float3,FixedString32Bytes,float)}</summary>
+ public void Label2D (float3 position, ref FixedString512Bytes text, float sizeInPixels = 14) {
+ Label2D(position, ref text, sizeInPixels, LabelAlignment.MiddleLeft);
+ }
+
+ /// <summary>
+ /// Draws a label in 3D space aligned with the camera.
+ ///
+ /// <code>
+ /// // This part can be inside a burst job
+ /// for (int i = 0; i < 10; i++) {
+ /// Unity.Collections.FixedString32Bytes text = $"X = {i}";
+ /// builder.Label2D(new float3(i, 0, 0), ref text, 12, LabelAlignment.Center);
+ /// }
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: Label2D(float3,string,float)
+ ///
+ /// Note: Only ASCII is supported since the built-in font texture only includes ASCII. Other characters will be rendered as question marks (?).
+ ///
+ /// Note: This method requires the Unity.Collections package version 0.8 or later.
+ /// </summary>
+ /// <param name="position">Position in 3D space.</param>
+ /// <param name="text">Text to display.</param>
+ /// <param name="sizeInPixels">Size of the text in screen pixels. For large sizes an SDF (signed distance field) font is used and for small sizes a normal font texture is used.</param>
+ /// <param name="alignment">How to align the text relative to the given position.</param>
+ public void Label2D (float3 position, ref FixedString32Bytes text, float sizeInPixels, LabelAlignment alignment) {
+#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
+ unsafe {
+ Label2D(position, text.GetUnsafePtr(), text.Length, sizeInPixels, alignment);
+ }
+#else
+ Debug.LogError("The Label2D method which takes FixedStrings requires the Unity.Collections package version 0.12 or newer");
+#endif
+ }
+
+ /// <summary>\copydocref{Label2D(float3,FixedString32Bytes,float,LabelAlignment)}</summary>
+ public void Label2D (float3 position, ref FixedString64Bytes text, float sizeInPixels, LabelAlignment alignment) {
+#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
+ unsafe {
+ Label2D(position, text.GetUnsafePtr(), text.Length, sizeInPixels, alignment);
+ }
+#else
+ Debug.LogError("The Label2D method which takes FixedStrings requires the Unity.Collections package version 0.12 or newer");
+#endif
+ }
+
+ /// <summary>\copydocref{Label2D(float3,FixedString32Bytes,float,LabelAlignment)}</summary>
+ public void Label2D (float3 position, ref FixedString128Bytes text, float sizeInPixels, LabelAlignment alignment) {
+#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
+ unsafe {
+ Label2D(position, text.GetUnsafePtr(), text.Length, sizeInPixels, alignment);
+ }
+#else
+ Debug.LogError("The Label2D method which takes FixedStrings requires the Unity.Collections package version 0.12 or newer");
+#endif
+ }
+
+ /// <summary>\copydocref{Label2D(float3,FixedString32Bytes,float,LabelAlignment)}</summary>
+ public void Label2D (float3 position, ref FixedString512Bytes text, float sizeInPixels, LabelAlignment alignment) {
+#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
+ unsafe {
+ Label2D(position, text.GetUnsafePtr(), text.Length, sizeInPixels, alignment);
+ }
+#else
+ Debug.LogError("The Label2D method which takes FixedStrings requires the Unity.Collections package version 0.12 or newer");
+#endif
+ }
+
+ /// <summary>\copydocref{Label2D(float3,FixedString32Bytes,float,LabelAlignment)}</summary>
+ internal unsafe void Label2D (float3 position, byte* text, int byteCount, float sizeInPixels, LabelAlignment alignment) {
+#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
+ AssertBufferExists();
+ Reserve<TextData>();
+ Add(Command.Text);
+ Add(new TextData { center = position, numCharacters = byteCount, sizeInPixels = sizeInPixels, alignment = alignment });
+
+ Reserve(UnsafeUtility.SizeOf<System.UInt16>() * byteCount);
+ for (int i = 0; i < byteCount; i++) {
+ // The first 128 elements in the font data are guaranteed to be laid out as ascii.
+ // We use this since we cannot use the dynamic font lookup.
+ System.UInt16 c = *(text + i);
+ if (c >= 128) c = (System.UInt16) '?';
+ if (c == (byte)'\n') c = SDFLookupData.Newline;
+ // Ignore carriage return instead of printing them as '?'. Windows encodes newlines as \r\n.
+ if (c == (byte)'\r') continue;
+ Add(c);
+ }
+#endif
+ }
+ #endregion
+
+ #region Label3DFixedString
+ /// <summary>
+ /// Draws a label in 3D space.
+ ///
+ /// <code>
+ /// // This part can be inside a burst job
+ /// for (int i = 0; i < 10; i++) {
+ /// Unity.Collections.FixedString32Bytes text = $"X = {i}";
+ /// builder.Label3D(new float3(i, 0, 0), quaternion.identity, ref text, 1, LabelAlignment.Center);
+ /// }
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: Label3D(float3,quaternion,string,float)
+ ///
+ /// Note: Only ASCII is supported since the built-in font texture only includes ASCII. Other characters will be rendered as question marks (?).
+ ///
+ /// Note: This method requires the Unity.Collections package version 0.8 or later.
+ /// </summary>
+ /// <param name="position">Position in 3D space.</param>
+ /// <param name="rotation">Rotation in 3D space.</param>
+ /// <param name="text">Text to display.</param>
+ /// <param name="size">World size of the text. For large sizes an SDF (signed distance field) font is used and for small sizes a normal font texture is used.</param>
+ public void Label3D (float3 position, quaternion rotation, ref FixedString32Bytes text, float size) {
+ Label3D(position, rotation, ref text, size, LabelAlignment.MiddleLeft);
+ }
+
+ /// <summary>\copydocref{Label3D(float3,quaternion,FixedString32Bytes,float)}</summary>
+ public void Label3D (float3 position, quaternion rotation, ref FixedString64Bytes text, float size) {
+ Label3D(position, rotation, ref text, size, LabelAlignment.MiddleLeft);
+ }
+
+ /// <summary>\copydocref{Label3D(float3,quaternion,FixedString32Bytes,float)}</summary>
+ public void Label3D (float3 position, quaternion rotation, ref FixedString128Bytes text, float size) {
+ Label3D(position, rotation, ref text, size, LabelAlignment.MiddleLeft);
+ }
+
+ /// <summary>\copydocref{Label3D(float3,quaternion,FixedString32Bytes,float)}</summary>
+ public void Label3D (float3 position, quaternion rotation, ref FixedString512Bytes text, float size) {
+ Label3D(position, rotation, ref text, size, LabelAlignment.MiddleLeft);
+ }
+
+ /// <summary>
+ /// Draws a label in 3D space.
+ ///
+ /// <code>
+ /// // This part can be inside a burst job
+ /// for (int i = 0; i < 10; i++) {
+ /// Unity.Collections.FixedString32Bytes text = $"X = {i}";
+ /// builder.Label3D(new float3(i, 0, 0), quaternion.identity, ref text, 1, LabelAlignment.Center);
+ /// }
+ /// </code>
+ /// [Open online documentation to see images]
+ ///
+ /// See: Label3D(float3,quaternion,string,float)
+ ///
+ /// Note: Only ASCII is supported since the built-in font texture only includes ASCII. Other characters will be rendered as question marks (?).
+ ///
+ /// Note: This method requires the Unity.Collections package version 0.8 or later.
+ /// </summary>
+ /// <param name="position">Position in 3D space.</param>
+ /// <param name="rotation">Rotation in 3D space.</param>
+ /// <param name="text">Text to display.</param>
+ /// <param name="size">World size of the text. For large sizes an SDF (signed distance field) font is used and for small sizes a normal font texture is used.</param>
+ /// <param name="alignment">How to align the text relative to the given position.</param>
+ public void Label3D (float3 position, quaternion rotation, ref FixedString32Bytes text, float size, LabelAlignment alignment) {
+#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
+ unsafe {
+ Label3D(position, rotation, text.GetUnsafePtr(), text.Length, size, alignment);
+ }
+#else
+ Debug.LogError("The Label3D method which takes FixedStrings requires the Unity.Collections package version 0.12 or newer");
+#endif
+ }
+
+ /// <summary>\copydocref{Label3D(float3,quaternion,FixedString32Bytes,float,LabelAlignment)}</summary>
+ public void Label3D (float3 position, quaternion rotation, ref FixedString64Bytes text, float size, LabelAlignment alignment) {
+#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
+ unsafe {
+ Label3D(position, rotation, text.GetUnsafePtr(), text.Length, size, alignment);
+ }
+#else
+ Debug.LogError("The Label3D method which takes FixedStrings requires the Unity.Collections package version 0.12 or newer");
+#endif
+ }
+
+ /// <summary>\copydocref{Label3D(float3,quaternion,FixedString32Bytes,float,LabelAlignment)}</summary>
+ public void Label3D (float3 position, quaternion rotation, ref FixedString128Bytes text, float size, LabelAlignment alignment) {
+#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
+ unsafe {
+ Label3D(position, rotation, text.GetUnsafePtr(), text.Length, size, alignment);
+ }
+#else
+ Debug.LogError("The Label3D method which takes FixedStrings requires the Unity.Collections package version 0.12 or newer");
+#endif
+ }
+
+ /// <summary>\copydocref{Label3D(float3,quaternion,FixedString32Bytes,float,LabelAlignment)}</summary>
+ public void Label3D (float3 position, quaternion rotation, ref FixedString512Bytes text, float size, LabelAlignment alignment) {
+#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
+ unsafe {
+ Label3D(position, rotation, text.GetUnsafePtr(), text.Length, size, alignment);
+ }
+#else
+ Debug.LogError("The Label3D method which takes FixedStrings requires the Unity.Collections package version 0.12 or newer");
+#endif
+ }
+
+ /// <summary>\copydocref{Label3D(float3,quaternion,FixedString32Bytes,float,LabelAlignment)}</summary>
+ internal unsafe void Label3D (float3 position, quaternion rotation, byte* text, int byteCount, float size, LabelAlignment alignment) {
+#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
+ AssertBufferExists();
+ Reserve<TextData3D>();
+ Add(Command.Text3D);
+ Add(new TextData3D { center = position, rotation = rotation, numCharacters = byteCount, size = size, alignment = alignment });
+
+ Reserve(UnsafeUtility.SizeOf<System.UInt16>() * byteCount);
+ for (int i = 0; i < byteCount; i++) {
+ // The first 128 elements in the font data are guaranteed to be laid out as ascii.
+ // We use this since we cannot use the dynamic font lookup.
+ System.UInt16 c = *(text + i);
+ if (c >= 128) c = (System.UInt16) '?';
+ if (c == (byte)'\n') c = SDFLookupData.Newline;
+ Add(c);
+ }
+#endif
+ }
+ #endregion
+ }
+}