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