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;
///
/// Specifies text alignment relative to an anchor point.
///
///
/// Draw.Label2D(transform.position, "Hello World", 14, LabelAlignment.TopCenter);
///
///
/// // Draw the label 20 pixels below the object
/// Draw.Label2D(transform.position, "Hello World", 14, LabelAlignment.TopCenter.withPixelOffset(0, -20));
///
///
/// See:
/// See:
///
public struct LabelAlignment {
///
/// 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.
///
public float2 relativePivot;
/// How much to move the text in screen-space
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) };
///
/// Moves the text by the specified amount of pixels in screen-space.
///
///
/// // Draw the label 20 pixels below the object
/// Draw.Label2D(transform.position, "Hello World", 14, LabelAlignment.TopCenter.withPixelOffset(0, -20));
///
///
public LabelAlignment withPixelOffset (float x, float y) {
return new LabelAlignment {
relativePivot = this.relativePivot,
pixelOffset = new float2(x, y),
};
}
}
/// Maximum allowed delay for a job that is drawing to a command buffer
public enum AllowedDelay {
///
/// 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.
///
EndOfFrame,
///
/// 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.
///
Infinite,
}
/// Some static fields that need to be in a separate class because Burst doesn't support them
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");
}
///
/// 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 using statement which automatically calls the Dispose method.
///
///
/// // 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);
/// }
///
///
/// Warning: You must call either or when you are done with this object to avoid memory leaks.
///
[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;
}
}
///
/// Wrapper for drawing in the XY plane.
///
///
/// 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);
///
///
/// See: 2d-drawing (view in online documentation for working links)
/// See:
///
public CommandBuilder2D xy => new CommandBuilder2D(this, true);
///
/// Wrapper for drawing in the XZ plane.
///
///
/// 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);
///
///
/// See: 2d-drawing (view in online documentation for working links)
/// See:
///
public CommandBuilder2D xz => new CommandBuilder2D(this, false);
static readonly float3 DEFAULT_UP = new float3(0, 1, 0);
///
/// 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 .
/// 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.
///
///
/// 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();
///
///
/// See: advanced (view in online documentation for working links)
///
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;
}
}
}
/// Submits this command builder for rendering
public void Dispose () {
if (uniqueID.isBuiltInCommandBuilder) throw new System.Exception("You cannot dispose a built-in command builder");
DisposeInternal();
}
///
/// 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)
///
/// The job that must complete before this command builder is disposed.
/// 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}.
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;
}
}
///
/// 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 method) then you must call this method to avoid
/// memory leaks.
///
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;
}
}
///
/// 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.
///
public void Preallocate (int size) {
Reserve(size);
}
/// Internal rendering command
[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;
}
/// Holds rendering data for a line
internal struct LineData {
public float3 a, b;
}
internal struct LineDataV3 {
public Vector3 a, b;
}
/// Holds rendering data for a circle
internal struct CircleXZData {
public float3 center;
public float radius, startAngle, endAngle;
}
/// Holds rendering data for a circle
internal struct CircleData {
public float3 center;
public float3 normal;
public float radius;
}
/// Holds rendering data for a sphere
internal struct SphereData {
public float3 center;
public float radius;
}
/// Holds rendering data for a box
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;
}
/// Ensures the buffer has room for at least N more bytes
[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() where A : struct {
Reserve(UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf());
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
internal void Reserve() where A : struct where B : struct {
Reserve(UnsafeUtility.SizeOf() * 2 + UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf());
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
internal void Reserve() where A : struct where B : struct where C : struct {
Reserve(UnsafeUtility.SizeOf() * 3 + UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf() + UnsafeUtility.SizeOf());
}
///
/// Converts a Color to a Color32.
/// This method is faster than Unity's native color conversion, especially when using Burst.
///
[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 value) where T : struct {
int num = UnsafeUtility.SizeOf();
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;
}
}
}
///
/// Scope that does nothing.
/// Used for optimization in standalone builds.
///
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;
}
}
}
///
/// 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.
///
///
/// 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);
/// }
///
///
/// See:
///
[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 };
}
}
///
/// 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.
///
///
/// 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);
/// }
///
///
/// See:
///
[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 };
}
}
///
/// Scope to draw multiple things with the same color.
///
///
/// 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));
/// }
/// }
///
///
/// 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.
///
[BurstDiscard]
public ScopeColor WithColor (Color color) {
PushColor(color);
unsafe {
return new ScopeColor { builder = this };
}
}
///
/// 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.
///
///
/// void Update () {
/// using (Draw.WithDuration(1.0f)) {
/// var offset = Time.time;
/// Draw.Line(new Vector3(offset, 0, 0), new Vector3(offset, 0, 1));
/// }
/// }
///
///
/// 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.
///
/// How long the drawn items should persist in seconds.
[BurstDiscard]
public ScopePersist WithDuration (float duration) {
PushDuration(duration);
unsafe {
return new ScopePersist { builder = this };
}
}
///
/// 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.
///
/// Line width in pixels
/// If true then sequences of lines that are adjacent will be automatically joined at their vertices. This typically produces nicer polylines without weird gaps.
[BurstDiscard]
public ScopeLineWidth WithLineWidth (float pixels, bool automaticJoins = true) {
PushLineWidth(pixels, automaticJoins);
unsafe {
return new ScopeLineWidth { builder = this };
}
}
///
/// 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.
///
///
/// 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);
/// }
/// }
///
///
/// [Open online documentation to see videos]
///
[BurstDiscard]
public ScopeMatrix InLocalSpace (Transform transform) {
return WithMatrix(transform.localToWorldMatrix);
}
///
/// 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:
///
[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)));
}
///
/// Multiply all coordinates until the next with the given matrix.
///
/// PushMatrix and PushSetMatrix are slightly different:
///
/// - PushMatrix stacks with all previously pushed matrices. The active matrix becomes the product of the given matrix and the previously active one.
/// - PushSetMatrix sets the current matrix directly. The active matrix becomes the last pushed matrix.
///
public void PushMatrix (Matrix4x4 matrix) {
Reserve();
Add(Command.PushMatrix);
Add(matrix);
}
///
/// Multiply all coordinates until the next with the given matrix.
///
/// PushMatrix and PushSetMatrix are slightly different:
///
/// - PushMatrix stacks with all previously pushed matrices. The active matrix becomes the product of the given matrix and the previously active one.
/// - PushSetMatrix sets the current matrix directly. The active matrix becomes the last pushed matrix.
///
public void PushMatrix (float4x4 matrix) {
Reserve();
Add(Command.PushMatrix);
Add(matrix);
}
///
/// Multiply all coordinates until the next with the given matrix.
///
/// PushMatrix and PushSetMatrix are slightly different:
///
/// - PushMatrix stacks with all previously pushed matrices. The active matrix becomes the product of the given matrix and the previously active one.
/// - PushSetMatrix sets the current matrix directly. The active matrix becomes the last pushed matrix.
///
public void PushSetMatrix (Matrix4x4 matrix) {
Reserve();
Add(Command.PushSetMatrix);
Add((float4x4)matrix);
}
///
/// Multiply all coordinates until the next PopMatrix with the given matrix.
///
/// PushMatrix and PushSetMatrix are slightly different:
///
/// - PushMatrix stacks with all previously pushed matrices. The active matrix becomes the product of the given matrix and the previously active one.
/// - PushSetMatrix sets the current matrix directly. The active matrix becomes the last pushed matrix.
///
public void PushSetMatrix (float4x4 matrix) {
Reserve();
Add(Command.PushSetMatrix);
Add(matrix);
}
///
/// Pops a matrix from the stack.
///
/// See:
/// See:
///
public void PopMatrix () {
Reserve(4);
Add(Command.PopMatrix);
}
///
/// 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.
///
public void PushColor (Color color) {
Reserve();
Add(Command.PushColor);
Add(ConvertColor(color));
}
/// Pops a color from the stack
public void PopColor () {
Reserve(4);
Add(Command.PopColor);
}
///
/// 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.
///
public void PushDuration (float duration) {
Reserve();
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 });
}
/// Pops a duration scope from the stack
public void PopDuration () {
Reserve(4);
Add(Command.PopPersist);
}
///
/// 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
///
[System.Obsolete("Renamed to PushDuration for consistency")]
public void PushPersist (float duration) {
PushDuration(duration);
}
///
/// Pops a persist scope from the stack.
/// Deprecated: Renamed to
///
[System.Obsolete("Renamed to PopDuration for consistency")]
public void PopPersist () {
PopDuration();
}
///
/// 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.
///
/// Line width in pixels
/// If true then sequences of lines that are adjacent will be automatically joined at their vertices. This typically produces nicer polylines without weird gaps.
public void PushLineWidth (float pixels, bool automaticJoins = true) {
if (pixels < 0) throw new System.ArgumentOutOfRangeException("pixels", "Line width must be positive");
Reserve();
Add(Command.PushLineWidth);
Add(new LineWidthData { pixels = pixels, automaticJoins = automaticJoins });
}
/// Pops a line width scope from the stack
public void PopLineWidth () {
Reserve(4);
Add(Command.PopLineWidth);
}
///
/// Draws a line between two points.
///
/// [Open online documentation to see images]
///
///
/// void Update () {
/// Draw.Line(Vector3.zero, Vector3.up);
/// }
///
///
public void Line (float3 a, float3 b) {
Reserve();
Add(Command.Line);
Add(new LineData { a = a, b = b });
}
///
/// Draws a line between two points.
///
/// [Open online documentation to see images]
///
///
/// void Update () {
/// Draw.Line(Vector3.zero, Vector3.up);
/// }
///
///
public void Line (Vector3 a, Vector3 b) {
Reserve();
// 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;
}
}
///
/// Draws a line between two points.
///
/// [Open online documentation to see images]
///
///
/// void Update () {
/// Draw.Line(Vector3.zero, Vector3.up);
/// }
///
///
public void Line (Vector3 a, Vector3 b, Color color) {
Reserve();
// 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;
}
}
///
/// 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]
///
///
/// Draw.Ray(Vector3.zero, Vector3.up);
///
///
public void Ray (float3 origin, float3 direction) {
Line(origin, origin + direction);
}
///
/// Draws a ray with a given length.
///
/// [Open online documentation to see images]
///
///
/// Draw.Ray(Camera.main.ScreenPointToRay(Vector3.zero), 10);
///
///
public void Ray (Ray ray, float length) {
Line(ray.origin, ray.origin + ray.direction * length);
}
///
/// 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]
///
/// 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);
///
///
/// See:
///
/// Center of the imaginary circle that the arc is part of.
/// Starting point of the arc.
/// End point of the arc.
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();
}
}
///
/// 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:
/// See:
///
/// Center of the circle or arc.
/// Radius of the circle or arc.
/// Starting angle in radians. 0 corrsponds to the positive X axis.
/// End angle in radians.
[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();
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();
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);
///
/// 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:
///
/// Center of the circle or arc.
/// Radius of the circle or arc.
/// Starting angle in radians. 0 corrsponds to the positive X axis.
/// End angle in radians.
[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();
}
///
/// Draws a circle.
///
/// [Open online documentation to see images]
///
/// Note: This overload does not allow you to draw an arc. For that purpose use , or instead.
///
public void Circle (float3 center, float3 normal, float radius) {
Reserve();
Add(Command.Circle);
Add(new CircleData { center = center, normal = normal, radius = radius });
}
///
/// 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]
///
/// 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);
///
///
/// See:
///
/// Center of the imaginary circle that the arc is part of.
/// Starting point of the arc.
/// End point of the arc.
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();
}
}
///
/// 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:
/// See:
///
/// Center of the disc or solid arc.
/// Radius of the disc or solid arc.
/// Starting angle in radians. 0 corrsponds to the positive X axis.
/// End angle in radians.
[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();
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();
Add(Command.DiscXZ | Command.PushColorInline);
Add(ConvertColor(color));
Add(new CircleXZData { center = center, radius = radius, startAngle = startAngle, endAngle = endAngle });
}
///
/// 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:
/// See:
///
/// Center of the disc or solid arc.
/// Radius of the disc or solid arc.
/// Starting angle in radians. 0 corrsponds to the positive X axis.
/// End angle in radians.
[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();
}
///
/// Draws a disc.
///
/// [Open online documentation to see images]
///
/// Note: This overload does not allow you to draw an arc. For that purpose use or instead.
///
public void SolidCircle (float3 center, float3 normal, float radius) {
Reserve();
Add(Command.Disc);
Add(new CircleData { center = center, normal = normal, radius = radius });
}
///
/// 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]
///
public void SphereOutline (float3 center, float radius) {
Reserve();
Add(Command.SphereOutline);
Add(new SphereData { center = center, radius = radius });
}
///
/// Draws a cylinder.
/// The cylinder's bottom circle will be centered at the bottom parameter and similarly for the top circle.
///
///
/// // 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);
///
///
/// [Open online documentation to see images]
///
public void WireCylinder (float3 bottom, float3 top, float radius) {
WireCylinder(bottom, top - bottom, math.length(top - bottom), radius);
}
///
/// Draws a cylinder.
///
///
/// // 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);
///
///
/// [Open online documentation to see images]
///
/// The center of the cylinder's "bottom" circle.
/// The cylinder's main axis. Does not have to be normalized. If zero, nothing will be drawn.
/// The length of the cylinder, as measured along it's main axis.
/// The radius of the cylinder.
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();
}
///
/// 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.
///
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);
}
///
/// Draws a capsule with a (start,end) parameterization.
///
/// The behavior of this method matches common Unity APIs such as Physics.CheckCapsule.
///
///
/// // 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);
///
///
/// [Open online documentation to see images]
///
/// Center of the start hemisphere of the capsule.
/// Center of the end hemisphere of the capsule.
/// Radius of the capsule.
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
///
/// Draws a capsule with a (position,direction/length) parameterization.
///
///
/// // 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);
///
///
/// [Open online documentation to see images]
///
/// One endpoint of the capsule. This is at the edge of the capsule, not at the center of one of the hemispheres.
/// The main axis of the capsule. Does not have to be normalized. If zero, nothing will be drawn.
/// Distance between the two endpoints of the capsule. The length will be clamped to be at least 2*radius.
/// The radius of the capsule.
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();
}
}
///
/// Draws a wire sphere.
///
/// [Open online documentation to see images]
///
///
/// // Draw a wire sphere at the origin with a radius of 0.5
/// Draw.WireSphere(Vector3.zero, 0.5f, Color.black);
///
///
/// See:
///
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);
}
///
/// Draws lines through a sequence of points.
///
/// [Open online documentation to see images]
///
/// // 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);
///
///
/// Sequence of points to draw lines through
/// If true a line will be drawn from the last point in the sequence back to the first point.
[BurstDiscard]
public void Polyline (List 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]);
}
///
/// Draws lines through a sequence of points.
///
/// [Open online documentation to see images]
///
/// // 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);
///
///
/// Sequence of points to draw lines through
/// If true a line will be drawn from the last point in the sequence back to the first point.
public void Polyline(T points, bool cycle = false) where T : IReadOnlyList {
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]);
}
///
/// Draws lines through a sequence of points.
///
/// [Open online documentation to see images]
///
/// // 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);
///
///
/// Sequence of points to draw lines through
/// If true a line will be drawn from the last point in the sequence back to the first point.
[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]);
}
///
/// Draws lines through a sequence of points.
///
/// [Open online documentation to see images]
///
/// // 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);
///
///
/// Sequence of points to draw lines through
/// If true a line will be drawn from the last point in the sequence back to the first point.
[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]);
}
///
/// Draws lines through a sequence of points.
///
/// [Open online documentation to see images]
///
/// // 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);
///
///
/// Sequence of points to draw lines through
/// If true a line will be drawn from the last point in the sequence back to the first point.
public void Polyline (NativeArray 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]);
}
/// Determines the symbol to use for
public enum SymbolDecoration {
///
/// 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]
///
None,
///
/// An arrowhead symbol.
///
/// [Open online documentation to see images]
///
ArrowHead,
///
/// A circle symbol.
///
/// [Open online documentation to see images]
///
Circle,
}
///
/// Draws a dashed line between two points.
///
///
/// Draw.DashedPolyline(points, 0.1f, 0.1f, color);
///
///
/// [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:
///
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);
}
///
/// Draws a dashed line through a sequence of points.
///
///
/// Draw.DashedPolyline(points, 0.1f, 0.1f, color);
///
///
/// [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 struct directly.
///
///
/// 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]);
/// }
/// }
///
///
/// See:
/// See:
///
public void DashedPolyline (List 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]);
}
}
///
/// Helper for drawing a polyline with symbols at regular intervals.
///
///
/// 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));
///
///
/// [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 helper function instead.
///
///
/// 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]);
/// }
/// }
///
///
/// [Open online documentation to see images]
///
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;
/// Create a new polyline with symbol generator.
/// The symbol to use
/// The size of the symbol. In case of a circle, this is the diameter.
/// The padding on both sides of the symbol between the symbol and the line.
/// The spacing between symbols. This is the distance between the centers of the symbols.
/// If true, the symbols will be reversed. For cicles this has no effect, but arrowhead symbols will be reversed.
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;
}
///
/// Move to a new point.
///
/// This will draw the symbols and line segments between the previous point and the new point.
///
/// 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.
/// The next point in the polyline to move to.
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;
}
}
///
/// Draws the outline of a box which is axis-aligned.
///
/// [Open online documentation to see images]
///
/// Center of the box
/// Width of the box along all dimensions
public void WireBox (float3 center, float3 size) {
Reserve();
Add(Command.WireBox);
Add(new BoxData { center = center, size = size });
}
///
/// Draws the outline of a box.
///
/// [Open online documentation to see images]
///
/// Center of the box
/// Rotation of the box
/// Width of the box along all dimensions
public void WireBox (float3 center, quaternion rotation, float3 size) {
PushMatrix(float4x4.TRS(center, rotation, size));
WireBox(float3.zero, new float3(1, 1, 1));
PopMatrix();
}
///
/// Draws the outline of a box.
///
/// [Open online documentation to see images]
///
public void WireBox (Bounds bounds) {
WireBox(bounds.center, bounds.size);
}
///
/// Draws a wire mesh.
/// Every single edge of the mesh will be drawn using a command.
///
///
/// var go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
/// go.transform.position = new Vector3(0, 0, 0);
/// using (Draw.InLocalSpace(go.transform)) {
/// Draw.WireMesh(go.GetComponent().sharedMesh, color);
/// }
///
/// [Open online documentation to see images]
///
/// See:
///
/// Version: Supported in Unity 2020.1 or later.
///
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
}
///
/// Draws a wire mesh.
/// Every single edge of the mesh will be drawn using a command.
///
///
/// var go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
/// go.transform.position = new Vector3(0, 0, 0);
/// using (Draw.InLocalSpace(go.transform)) {
/// Draw.WireMesh(go.GetComponent().sharedMesh, color);
/// }
///
/// [Open online documentation to see images]
///
/// See:
///
/// Version: Supported in Unity 2020.1 or later.
///
public void WireMesh (NativeArray vertices, NativeArray 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
/// Helper job for
[BurstCompile]
class JobWireMesh {
public delegate void JobWireMeshDelegate(ref Mesh.MeshData rawMeshData, ref CommandBuilder draw);
public static readonly JobWireMeshDelegate JobWireMeshFunctionPointer = BurstCompiler.CompileFunctionPointer(Execute).Invoke;
[BurstCompile]
public static unsafe void WireMesh (float3* verts, int* indices, int vertexCount, int indexCount, ref CommandBuilder draw) {
if (indexCount % 3 != 0) {
throw new System.ArgumentException("Invalid index count. Must be a multiple of 3");
}
// 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(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(maxIndices, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
var verts = new NativeArray(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
///
/// Draws a solid mesh.
/// The mesh will be drawn with a solid color.
///
///
/// var go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
/// go.transform.position = new Vector3(0, 0, 0);
/// using (Draw.InLocalSpace(go.transform)) {
/// Draw.SolidMesh(go.GetComponent().sharedMesh, color);
/// }
///
/// [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:
///
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);
}
///
/// 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?
///
[BurstDiscard]
public void SolidMesh (List vertices, List triangles, List 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);
}
///
/// 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?
///
[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);
}
///
/// Draws a 3D cross.
///
/// [Open online documentation to see images]
///
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));
}
///
/// Draws a cross in the XZ plane.
///
/// [Open online documentation to see images]
///
[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));
}
///
/// Draws a cross in the XY plane.
///
/// [Open online documentation to see images]
///
[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));
}
/// Returns a point on a cubic bezier curve. t is clamped between 0 and 1
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;
}
///
/// 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
///
/// Start point
/// First control point
/// Second control point
/// End point
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;
}
}
///
/// 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:
///
/// The curve will smoothly pass through each point in the list in order.
public void CatmullRom (List 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]);
}
}
///
/// 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:
///
/// First control point
/// Second control point. Start of the curve.
/// Third control point. End of the curve.
/// Fourth control point.
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);
}
///
/// 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:
/// See:
///
/// Base of the arrow.
/// Head of the arrow.
public void Arrow (float3 from, float3 to) {
ArrowRelativeSizeHead(from, to, DEFAULT_UP, 0.2f);
}
///
/// Draws an arrow between two points.
///
/// [Open online documentation to see images]
///
/// See:
/// See:
///
/// Base of the arrow.
/// Head of the arrow.
/// Up direction of the world, the arrowhead plane will be as perpendicular as possible to this direction. Defaults to Vector3.up.
/// The size of the arrowhead in world units.
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));
}
}
///
/// 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:
///
/// Base of the arrow.
/// Head of the arrow.
/// Up direction of the world, the arrowhead plane will be as perpendicular as possible to this direction.
/// The length of the arrowhead is the distance between from and to multiplied by this fraction. Should be between 0 and 1.
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);
}
///
/// Draws an arrowhead at a point.
///
///
/// Draw.Arrowhead(Vector3.zero, Vector3.forward, 0.75f, color);
///
/// [Open online documentation to see images]
///
/// See:
/// See:
///
/// Center of the arrowhead.
/// Direction the arrow is pointing.
/// Distance from the center to each corner of the arrowhead.
public void Arrowhead (float3 center, float3 direction, float radius) {
Arrowhead(center, direction, DEFAULT_UP, radius);
}
///
/// Draws an arrowhead at a point.
///
///
/// Draw.Arrowhead(Vector3.zero, Vector3.forward, 0.75f, color);
///
/// [Open online documentation to see images]
///
/// See:
/// See:
///
/// Center of the arrowhead.
/// Direction the arrow is pointing.
/// Up direction of the world, the arrowhead plane will be as perpendicular as possible to this direction. Defaults to Vector3.up. Must be normalized.
/// Distance from the center to each corner of the arrowhead.
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);
}
///
/// 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:
///
/// Point around which the arc is centered
/// Direction the arrow is pointing
/// Distance from origin that the arrow starts.
/// Width of the arrowhead in degrees (defaults to 60). Should be between 0 and 90.
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();
}
///
/// Draws a grid of lines.
///
///
/// Draw.xz.WireGrid(Vector3.zero, new int2(3, 3), new float2(1, 1), color);
///
/// [Open online documentation to see images]
///
/// Center of the grid
/// Rotation of the grid. The grid will be aligned to the X and Z axes of the rotation.
/// Number of cells of the grid. Should be greater than 0.
/// Total size of the grid along the X and Z axes.
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();
}
///
/// Draws a triangle outline.
///
///
/// Draw.WireTriangle(new Vector3(-0.5f, 0, 0), new Vector3(0, 1, 0), new Vector3(0.5f, 0, 0), Color.black);
///
/// [Open online documentation to see images]
///
/// See:
/// See:
/// See:
///
/// First corner of the triangle
/// Second corner of the triangle
/// Third corner of the triangle
public void WireTriangle (float3 a, float3 b, float3 c) {
Line(a, b);
Line(b, c);
Line(c, a);
}
///
/// Draws a rectangle outline.
/// The rectangle will be aligned to the X and Z axes.
///
///
/// Draw.xz.WireRectangle(new Vector3(0f, 0, 0), new Vector2(1, 1), Color.black);
///
/// [Open online documentation to see images]
///
/// See:
///
[System.Obsolete("Use Draw.xz.WireRectangle instead")]
public void WireRectangleXZ (float3 center, float2 size) {
WireRectangle(center, quaternion.identity, size);
}
///
/// Draws a rectangle outline.
/// The rectangle will be oriented along the rotation's X and Z axes.
///
///
/// Draw.WireRectangle(new Vector3(0f, 0, 0), Quaternion.identity, new Vector2(1, 1), Color.black);
///
/// [Open online documentation to see images]
///
/// This is identical to , but this name is added for consistency.
///
/// See:
///
public void WireRectangle (float3 center, quaternion rotation, float2 size) {
WirePlane(center, rotation, size);
}
///
/// Draws a rectangle outline.
/// The rectangle corners are assumed to be in XY space.
/// This is particularly useful when combined with .
///
///
/// using (Draw.InScreenSpace(Camera.main)) {
/// Draw.xy.WireRectangle(new Rect(10, 10, 100, 100), Color.black);
/// }
///
/// [Open online documentation to see images]
///
/// See:
/// See:
/// See:
///
[System.Obsolete("Use Draw.xy.WireRectangle instead")]
public void WireRectangle (Rect rect) {
xy.WireRectangle(rect);
}
///
/// Draws a triangle outline.
///
///
/// Draw.WireTriangle(Vector3.zero, Quaternion.identity, 0.5f, color);
///
/// [Open online documentation to see images]
///
/// Note: This is a convenience wrapper for
///
/// See:
///
/// Center of the triangle.
/// Rotation of the triangle. The first vertex will be radius units in front of center as seen from the rotation's point of view.
/// Distance from the center to each vertex.
public void WireTriangle (float3 center, quaternion rotation, float radius) {
WirePolygon(center, 3, rotation, radius);
}
///
/// Draws a pentagon outline.
///
///
/// Draw.WirePentagon(Vector3.zero, Quaternion.identity, 0.5f, color);
///
/// [Open online documentation to see images]
///
/// Note: This is a convenience wrapper for
///
/// Center of the polygon.
/// Rotation of the polygon. The first vertex will be radius units in front of center as seen from the rotation's point of view.
/// Distance from the center to each vertex.
public void WirePentagon (float3 center, quaternion rotation, float radius) {
WirePolygon(center, 5, rotation, radius);
}
///
/// Draws a hexagon outline.
///
///
/// Draw.WireHexagon(Vector3.zero, Quaternion.identity, 0.5f, color);
///
/// [Open online documentation to see images]
///
/// Note: This is a convenience wrapper for
///
/// Center of the polygon.
/// Rotation of the polygon. The first vertex will be radius units in front of center as seen from the rotation's point of view.
/// Distance from the center to each vertex.
public void WireHexagon (float3 center, quaternion rotation, float radius) {
WirePolygon(center, 6, rotation, radius);
}
///
/// Draws a regular polygon outline.
///
///
/// 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);
///
/// [Open online documentation to see images]
///
/// See:
/// See:
/// See:
///
/// Center of the polygon.
/// Number of corners (and sides) of the polygon.
/// Rotation of the polygon. The first vertex will be radius units in front of center as seen from the rotation's point of view.
/// Distance from the center to each vertex.
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();
}
///
/// Draws a solid rectangle.
/// The rectangle corners are assumed to be in XY space.
/// This is particularly useful when combined with .
///
/// Behind the scenes this is implemented using .
///
///
/// using (Draw.InScreenSpace(Camera.main)) {
/// Draw.xy.SolidRectangle(new Rect(10, 10, 100, 100), Color.black);
/// }
///
/// [Open online documentation to see images]
///
/// See:
/// See:
/// See:
///
[System.Obsolete("Use Draw.xy.SolidRectangle instead")]
public void SolidRectangle (Rect rect) {
xy.SolidRectangle(rect);
}
///
/// Draws a solid plane.
///
///
/// Draw.SolidPlane(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
///
/// [Open online documentation to see images]
///
/// Center of the visualized plane.
/// Direction perpendicular to the plane. If this is (0,0,0) then nothing will be rendered.
/// Width and height of the visualized plane.
public void SolidPlane (float3 center, float3 normal, float2 size) {
if (math.any(normal)) {
SolidPlane(center, Quaternion.LookRotation(calculateTangent(normal), normal), size);
}
}
///
/// Draws a solid plane.
///
/// The plane will lie in the XZ plane with respect to the rotation.
///
///
/// Draw.SolidPlane(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
///
/// [Open online documentation to see images]
///
/// Center of the visualized plane.
/// Width and height of the visualized plane.
public void SolidPlane (float3 center, quaternion rotation, float2 size) {
PushMatrix(float4x4.TRS(center, rotation, new float3(size.x, 0, size.y)));
Reserve();
Add(Command.Box);
Add(new BoxData { center = 0, size = 1 });
PopMatrix();
}
/// Returns an arbitrary vector which is orthogonal to the given one
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;
}
///
/// Draws a wire plane.
///
///
/// Draw.WirePlane(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
///
/// [Open online documentation to see images]
///
/// Center of the visualized plane.
/// Direction perpendicular to the plane. If this is (0,0,0) then nothing will be rendered.
/// Width and height of the visualized plane.
public void WirePlane (float3 center, float3 normal, float2 size) {
if (math.any(normal)) {
WirePlane(center, Quaternion.LookRotation(calculateTangent(normal), normal), size);
}
}
///
/// Draws a wire plane.
///
/// This is identical to , but it is included for consistency.
///
///
/// Draw.WirePlane(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
///
/// [Open online documentation to see images]
///
/// Center of the visualized plane.
/// Rotation of the plane. The plane will lie in the XZ plane with respect to the rotation.
/// Width and height of the visualized plane.
public void WirePlane (float3 center, quaternion rotation, float2 size) {
Reserve();
Add(Command.WirePlane);
Add(new PlaneData { center = center, rotation = rotation, size = size });
}
///
/// Draws a plane and a visualization of its normal.
///
///
/// Draw.PlaneWithNormal(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
///
/// [Open online documentation to see images]
///
/// Center of the visualized plane.
/// Direction perpendicular to the plane. If this is (0,0,0) then nothing will be rendered.
/// Width and height of the visualized plane.
public void PlaneWithNormal (float3 center, float3 normal, float2 size) {
if (math.any(normal)) {
PlaneWithNormal(center, Quaternion.LookRotation(calculateTangent(normal), normal), size);
}
}
///
/// Draws a plane and a visualization of its normal.
///
///
/// Draw.PlaneWithNormal(new float3(0, 0, 0), new float3(0, 1, 0), 1.0f, color);
///
/// [Open online documentation to see images]
///
/// Center of the visualized plane.
/// Rotation of the plane. The plane will lie in the XZ plane with respect to the rotation.
/// Width and height of the visualized plane.
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);
}
///
/// Draws a solid triangle.
///
///
/// Draw.xy.SolidTriangle(new float2(-0.43f, -0.25f), new float2(0, 0.5f), new float2(0.43f, -0.25f), color);
///
/// [Open online documentation to see images]
///
/// Note: If you are going to be drawing lots of triangles it's better to use instead as it will be more efficient.
///
/// See:
/// See:
///
/// First corner of the triangle.
/// Second corner of the triangle.
/// Third corner of the triangle.
public void SolidTriangle (float3 a, float3 b, float3 c) {
Reserve();
Add(Command.SolidTriangle);
Add(new TriangleData { a = a, b = b, c = c });
}
///
/// Draws a solid box.
///
///
/// Draw.SolidBox(new float3(0, 0, 0), new float3(1, 1, 1), color);
///
/// [Open online documentation to see images]
///
/// Center of the box
/// Width of the box along all dimensions
public void SolidBox (float3 center, float3 size) {
Reserve();
Add(Command.Box);
Add(new BoxData { center = center, size = size });
}
///
/// Draws a solid box.
///
///
/// Draw.SolidBox(new float3(0, 0, 0), new float3(1, 1, 1), color);
///
/// [Open online documentation to see images]
///
/// Bounding box of the box
public void SolidBox (Bounds bounds) {
SolidBox(bounds.center, bounds.size);
}
///
/// Draws a solid box.
///
///
/// Draw.SolidBox(new float3(0, 0, 0), new float3(1, 1, 1), color);
///
/// [Open online documentation to see images]
///
/// Center of the box
/// Rotation of the box
/// Width of the box along all dimensions
public void SolidBox (float3 center, quaternion rotation, float3 size) {
PushMatrix(float4x4.TRS(center, rotation, size));
SolidBox(float3.zero, Vector3.one);
PopMatrix();
}
///
/// Draws a label in 3D space.
///
/// The default alignment is .
///
///
/// Draw.Label3D(new float3(0.2f, -1f, 0.2f), Quaternion.Euler(45, -110, -90), "Label", 1, LabelAlignment.Center, color);
///
/// [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 (?).
///
/// Position in 3D space.
/// Rotation in 3D space.
/// Text to display.
/// 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.
public void Label3D (float3 position, quaternion rotation, string text, float size) {
Label3D(position, rotation, text, size, LabelAlignment.MiddleLeft);
}
///
/// Draws a label in 3D space.
///
///
/// Draw.Label3D(new float3(0.2f, -1f, 0.2f), Quaternion.Euler(45, -110, -90), "Label", 1, LabelAlignment.Center, color);
///
/// [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.
///
/// Position in 3D space.
/// Rotation in 3D space.
/// Text to display.
/// 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.
/// How to align the text relative to the given position.
public void Label3D (float3 position, quaternion rotation, string text, float size, LabelAlignment alignment) {
AssertBufferExists();
Reserve();
Add(Command.Text3D);
Add(new TextData3D { center = position, rotation = rotation, numCharacters = text.Length, size = size, alignment = alignment });
AddText(text);
}
///
/// Draws a label in 3D space aligned with the camera.
///
/// The default alignment is .
///
///
/// Draw.Label2D(Vector3.zero, "Label", 48, LabelAlignment.Center, color);
///
/// [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 (?).
///
/// Position in 3D space.
/// Text to display.
/// 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.
public void Label2D (float3 position, string text, float sizeInPixels = 14) {
Label2D(position, text, sizeInPixels, LabelAlignment.MiddleLeft);
}
///
/// Draws a label in 3D space aligned with the camera.
///
///
/// Draw.Label2D(Vector3.zero, "Label", 48, LabelAlignment.Center, color);
///
/// [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.
///
/// Position in 3D space.
/// Text to display.
/// 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.
/// How to align the text relative to the given position.
public void Label2D (float3 position, string text, float sizeInPixels, LabelAlignment alignment) {
AssertBufferExists();
Reserve();
Add(Command.Text);
Add(new TextData { center = position, numCharacters = text.Length, sizeInPixels = sizeInPixels, alignment = alignment });
AddText(text);
}
void AddText (string text) {
var g = gizmos.Target as DrawingData;
Reserve(UnsafeUtility.SizeOf() * 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
///
/// Draws a label in 3D space aligned with the camera.
///
///
/// // 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);
/// }
///
/// [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.
///
/// Position in 3D space.
/// Text to display.
/// 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.
public void Label2D (float3 position, ref FixedString32Bytes text, float sizeInPixels = 14) {
Label2D(position, ref text, sizeInPixels, LabelAlignment.MiddleLeft);
}
/// \copydocref{Label2D(float3,FixedString32Bytes,float)}
public void Label2D (float3 position, ref FixedString64Bytes text, float sizeInPixels = 14) {
Label2D(position, ref text, sizeInPixels, LabelAlignment.MiddleLeft);
}
/// \copydocref{Label2D(float3,FixedString32Bytes,float)}
public void Label2D (float3 position, ref FixedString128Bytes text, float sizeInPixels = 14) {
Label2D(position, ref text, sizeInPixels, LabelAlignment.MiddleLeft);
}
/// \copydocref{Label2D(float3,FixedString32Bytes,float)}
public void Label2D (float3 position, ref FixedString512Bytes text, float sizeInPixels = 14) {
Label2D(position, ref text, sizeInPixels, LabelAlignment.MiddleLeft);
}
///
/// Draws a label in 3D space aligned with the camera.
///
///
/// // 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);
/// }
///
/// [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.
///
/// Position in 3D space.
/// Text to display.
/// 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.
/// How to align the text relative to the given position.
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
}
/// \copydocref{Label2D(float3,FixedString32Bytes,float,LabelAlignment)}
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
}
/// \copydocref{Label2D(float3,FixedString32Bytes,float,LabelAlignment)}
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
}
/// \copydocref{Label2D(float3,FixedString32Bytes,float,LabelAlignment)}
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
}
/// \copydocref{Label2D(float3,FixedString32Bytes,float,LabelAlignment)}
internal unsafe void Label2D (float3 position, byte* text, int byteCount, float sizeInPixels, LabelAlignment alignment) {
#if MODULE_COLLECTIONS_0_12_0_OR_NEWER
AssertBufferExists();
Reserve();
Add(Command.Text);
Add(new TextData { center = position, numCharacters = byteCount, sizeInPixels = sizeInPixels, alignment = alignment });
Reserve(UnsafeUtility.SizeOf() * 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
///
/// Draws a label in 3D space.
///
///
/// // 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);
/// }
///
/// [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.
///
/// Position in 3D space.
/// Rotation in 3D space.
/// Text to display.
/// 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.
public void Label3D (float3 position, quaternion rotation, ref FixedString32Bytes text, float size) {
Label3D(position, rotation, ref text, size, LabelAlignment.MiddleLeft);
}
/// \copydocref{Label3D(float3,quaternion,FixedString32Bytes,float)}
public void Label3D (float3 position, quaternion rotation, ref FixedString64Bytes text, float size) {
Label3D(position, rotation, ref text, size, LabelAlignment.MiddleLeft);
}
/// \copydocref{Label3D(float3,quaternion,FixedString32Bytes,float)}
public void Label3D (float3 position, quaternion rotation, ref FixedString128Bytes text, float size) {
Label3D(position, rotation, ref text, size, LabelAlignment.MiddleLeft);
}
/// \copydocref{Label3D(float3,quaternion,FixedString32Bytes,float)}
public void Label3D (float3 position, quaternion rotation, ref FixedString512Bytes text, float size) {
Label3D(position, rotation, ref text, size, LabelAlignment.MiddleLeft);
}
///
/// Draws a label in 3D space.
///
///
/// // 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);
/// }
///
/// [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.
///
/// Position in 3D space.
/// Rotation in 3D space.
/// Text to display.
/// 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.
/// How to align the text relative to the given position.
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
}
/// \copydocref{Label3D(float3,quaternion,FixedString32Bytes,float,LabelAlignment)}
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
}
/// \copydocref{Label3D(float3,quaternion,FixedString32Bytes,float,LabelAlignment)}
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
}
/// \copydocref{Label3D(float3,quaternion,FixedString32Bytes,float,LabelAlignment)}
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
}
/// \copydocref{Label3D(float3,quaternion,FixedString32Bytes,float,LabelAlignment)}
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();
Add(Command.Text3D);
Add(new TextData3D { center = position, rotation = rotation, numCharacters = byteCount, size = size, alignment = alignment });
Reserve(UnsafeUtility.SizeOf() * 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
}
}