summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/GeometryBuilder.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/GeometryBuilder.cs')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/GeometryBuilder.cs1152
1 files changed, 1152 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/GeometryBuilder.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/GeometryBuilder.cs
new file mode 100644
index 0000000..743b3bb
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Drawing/GeometryBuilder.cs
@@ -0,0 +1,1152 @@
+using System;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Mathematics;
+using Unity.Jobs.LowLevel.Unsafe;
+using UnityEngine;
+using Unity.Burst;
+using UnityEngine.Profiling;
+using Unity.Collections;
+using Unity.Jobs;
+
+namespace Pathfinding.Drawing {
+ using static DrawingData;
+ using static CommandBuilder;
+ using Pathfinding.Drawing.Text;
+ using Unity.Profiling;
+ using System.Collections.Generic;
+ using UnityEngine.Rendering;
+
+ static class GeometryBuilder {
+ public struct CameraInfo {
+ public float3 cameraPosition;
+ public quaternion cameraRotation;
+ public float2 cameraDepthToPixelSize;
+ public bool cameraIsOrthographic;
+
+ public CameraInfo(Camera camera) {
+ var tr = camera?.transform;
+ cameraPosition = tr != null ? (float3)tr.position : float3.zero;
+ cameraRotation = tr != null ? (quaternion)tr.rotation : quaternion.identity;
+ cameraDepthToPixelSize = (camera != null ? CameraDepthToPixelSize(camera) : 0);
+ cameraIsOrthographic = camera != null ? camera.orthographic : false;
+ }
+ }
+
+ internal static unsafe JobHandle Build (DrawingData gizmos, ProcessedBuilderData.MeshBuffers* buffers, ref CameraInfo cameraInfo, JobHandle dependency) {
+ // Create a new builder and schedule it.
+ // Why is characterInfo passed as a pointer and a length instead of just a NativeArray?
+ // This is because passing it as a NativeArray invokes the safety system which adds some tracking to the NativeArray.
+ // This is normally not a problem, but we may be scheduling hundreds of jobs that use that particular NativeArray and this causes a bit of a slowdown
+ // in the safety checking system. Passing it as a pointer + length makes the whole scheduling code about twice as fast compared to passing it as a NativeArray.
+ return new GeometryBuilderJob {
+ buffers = buffers,
+ currentMatrix = Matrix4x4.identity,
+ currentLineWidthData = new LineWidthData {
+ pixels = 1,
+ automaticJoins = false,
+ },
+ lineWidthMultiplier = DrawingManager.lineWidthMultiplier,
+ currentColor = (Color32)Color.white,
+ cameraPosition = cameraInfo.cameraPosition,
+ cameraRotation = cameraInfo.cameraRotation,
+ cameraDepthToPixelSize = cameraInfo.cameraDepthToPixelSize,
+ cameraIsOrthographic = cameraInfo.cameraIsOrthographic,
+ characterInfo = (SDFCharacter*)gizmos.fontData.characters.GetUnsafeReadOnlyPtr(),
+ characterInfoLength = gizmos.fontData.characters.Length,
+ maxPixelError = GeometryBuilderJob.MaxCirclePixelError / math.max(0.1f, gizmos.settingsRef.curveResolution),
+ }.Schedule(dependency);
+ }
+
+ /// <summary>
+ /// Helper for determining how large a pixel is at a given depth.
+ /// A a distance D from the camera a pixel corresponds to roughly value.x * D + value.y world units.
+ /// Where value is the return value from this function.
+ /// </summary>
+ private static float2 CameraDepthToPixelSize (Camera camera) {
+ if (camera.orthographic) {
+ return new float2(0.0f, 2.0f * camera.orthographicSize / camera.pixelHeight);
+ } else {
+ return new float2(Mathf.Tan(camera.fieldOfView * Mathf.Deg2Rad * 0.5f) / (0.5f * camera.pixelHeight), 0.0f);
+ }
+ }
+
+ private static NativeArray<T> ConvertExistingDataToNativeArray<T>(UnsafeAppendBuffer data) where T : struct {
+ unsafe {
+ var arr = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(data.Ptr, data.Length / UnsafeUtility.SizeOf<T>(), Allocator.Invalid);
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref arr, AtomicSafetyHandle.GetTempMemoryHandle());
+#endif
+ return arr;
+ }
+ }
+
+ internal static unsafe void BuildMesh (DrawingData gizmos, List<MeshWithType> meshes, ProcessedBuilderData.MeshBuffers* inputBuffers) {
+ if (inputBuffers->triangles.Length > 0) {
+ CommandBuilderSamplers.MarkerUpdateBuffer.Begin();
+ var mesh = AssignMeshData<GeometryBuilderJob.Vertex>(gizmos, inputBuffers->bounds, inputBuffers->vertices, inputBuffers->triangles, MeshLayouts.MeshLayout);
+ meshes.Add(new MeshWithType { mesh = mesh, type = MeshType.Lines });
+ CommandBuilderSamplers.MarkerUpdateBuffer.End();
+ }
+
+ if (inputBuffers->solidTriangles.Length > 0) {
+ var mesh = AssignMeshData<GeometryBuilderJob.Vertex>(gizmos, inputBuffers->bounds, inputBuffers->solidVertices, inputBuffers->solidTriangles, MeshLayouts.MeshLayout);
+ meshes.Add(new MeshWithType { mesh = mesh, type = MeshType.Solid });
+ }
+
+ if (inputBuffers->textTriangles.Length > 0) {
+ var mesh = AssignMeshData<GeometryBuilderJob.TextVertex>(gizmos, inputBuffers->bounds, inputBuffers->textVertices, inputBuffers->textTriangles, MeshLayouts.MeshLayoutText);
+ meshes.Add(new MeshWithType { mesh = mesh, type = MeshType.Text });
+ }
+ }
+
+ private static Mesh AssignMeshData<VertexType>(DrawingData gizmos, Bounds bounds, UnsafeAppendBuffer vertices, UnsafeAppendBuffer triangles, VertexAttributeDescriptor[] layout) where VertexType : struct {
+ CommandBuilderSamplers.MarkerConvert.Begin();
+ var verticesView = ConvertExistingDataToNativeArray<VertexType>(vertices);
+ var trianglesView = ConvertExistingDataToNativeArray<int>(triangles);
+ CommandBuilderSamplers.MarkerConvert.End();
+ var mesh = gizmos.GetMesh(verticesView.Length);
+
+ CommandBuilderSamplers.MarkerSetLayout.Begin();
+ // Resize the vertex buffer if necessary
+ // Note: also resized if the vertex buffer is significantly larger than necessary.
+ // This is because apparently when executing the command buffer Unity does something with the whole buffer for some reason (shows up as Mesh.CreateMesh in the profiler)
+ // TODO: This could potentially cause bad behaviour if multiple meshes are used each frame and they have differing sizes.
+ // We should query for meshes that already have an appropriately sized buffer.
+ // if (mesh.vertexCount < verticesView.Length || mesh.vertexCount > verticesView.Length * 2) {
+
+ // }
+ // TODO: Use Mesh.GetVertexBuffer/Mesh.GetIndexBuffer once they stop being buggy.
+ // Currently they don't seem to get refreshed properly after resizing them (2022.2.0b1)
+ mesh.SetVertexBufferParams(math.ceilpow2(verticesView.Length), layout);
+ mesh.SetIndexBufferParams(math.ceilpow2(trianglesView.Length), IndexFormat.UInt32);
+ CommandBuilderSamplers.MarkerSetLayout.End();
+
+ CommandBuilderSamplers.MarkerUpdateVertices.Begin();
+ // Update the mesh data
+ mesh.SetVertexBufferData(verticesView, 0, 0, verticesView.Length);
+ CommandBuilderSamplers.MarkerUpdateVertices.End();
+ CommandBuilderSamplers.MarkerUpdateIndices.Begin();
+ // Update the index buffer and assume all our indices are correct
+ mesh.SetIndexBufferData(trianglesView, 0, 0, trianglesView.Length, MeshUpdateFlags.DontValidateIndices);
+ CommandBuilderSamplers.MarkerUpdateIndices.End();
+
+
+ CommandBuilderSamplers.MarkerSubmesh.Begin();
+ mesh.subMeshCount = 1;
+ var submesh = new SubMeshDescriptor(0, trianglesView.Length, MeshTopology.Triangles) {
+ vertexCount = verticesView.Length,
+ bounds = bounds
+ };
+ mesh.SetSubMesh(0, submesh, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontNotifyMeshUsers);
+ mesh.bounds = bounds;
+ CommandBuilderSamplers.MarkerSubmesh.End();
+ return mesh;
+ }
+ }
+
+ /// <summary>Some static fields that need to be in a separate class because Burst doesn't support them</summary>
+ static class MeshLayouts {
+ internal static readonly VertexAttributeDescriptor[] MeshLayout = {
+ new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
+ new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3),
+ new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.UNorm8, 4),
+ new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
+ };
+
+ internal static readonly VertexAttributeDescriptor[] MeshLayoutText = {
+ new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
+ new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.UNorm8, 4),
+ new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
+ };
+ }
+
+ /// <summary>
+ /// Job to build the geometry from a stream of rendering commands.
+ ///
+ /// See: <see cref="CommandBuilder"/>
+ /// </summary>
+ // Note: Setting FloatMode to Fast causes visual artificats when drawing circles.
+ // I think it is because math.sin(float4) produces slightly different results
+ // for each component in the input.
+ [BurstCompile(FloatMode = FloatMode.Default)]
+ internal struct GeometryBuilderJob : IJob {
+ [NativeDisableUnsafePtrRestriction]
+ public unsafe ProcessedBuilderData.MeshBuffers* buffers;
+
+ [NativeDisableUnsafePtrRestriction]
+ public unsafe SDFCharacter* characterInfo;
+ public int characterInfoLength;
+
+ public Color32 currentColor;
+ public float4x4 currentMatrix;
+ public LineWidthData currentLineWidthData;
+ public float lineWidthMultiplier;
+ float3 minBounds;
+ float3 maxBounds;
+ public float3 cameraPosition;
+ public quaternion cameraRotation;
+ public float2 cameraDepthToPixelSize;
+ public float maxPixelError;
+ public bool cameraIsOrthographic;
+
+ [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
+ public struct Vertex {
+ public float3 position;
+ public float3 uv2;
+ public Color32 color;
+ public float2 uv;
+ }
+
+ [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
+ public struct TextVertex {
+ public float3 position;
+ public Color32 color;
+ public float2 uv;
+ }
+
+ static unsafe void Add<T>(UnsafeAppendBuffer* buffer, T value) where T : unmanaged {
+ int size = UnsafeUtility.SizeOf<T>();
+ // We know that the buffer has enough capacity, so we can just write to the buffer without
+ // having to add branches for the overflow case (like buffer->Add will do).
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ UnityEngine.Assertions.Assert.IsTrue(buffer->Length + size <= buffer->Capacity);
+#endif
+ *(T*)(buffer->Ptr + buffer->Length) = value;
+ buffer->Length = buffer->Length + size;
+ }
+
+ static unsafe void Reserve (UnsafeAppendBuffer* buffer, int size) {
+ var newSize = buffer->Length + size;
+
+ if (newSize > buffer->Capacity) {
+ buffer->SetCapacity(math.max(newSize, buffer->Capacity * 2));
+ }
+ }
+
+ internal static float3 PerspectiveDivide (float4 p) {
+ return p.xyz * math.rcp(p.w);
+ }
+
+ unsafe void AddText (System.UInt16* text, TextData textData, Color32 color) {
+ var pivot = PerspectiveDivide(math.mul(currentMatrix, new float4(textData.center, 1.0f)));
+
+ AddTextInternal(
+ text,
+ pivot,
+ math.mul(cameraRotation, new float3(1, 0, 0)),
+ math.mul(cameraRotation, new float3(0, 1, 0)),
+ textData.alignment,
+ textData.sizeInPixels,
+ true,
+ textData.numCharacters,
+ color
+ );
+ }
+
+ unsafe void AddText3D (System.UInt16* text, TextData3D textData, Color32 color) {
+ var pivot = PerspectiveDivide(math.mul(currentMatrix, new float4(textData.center, 1.0f)));
+ var m = math.mul(currentMatrix, new float4x4(textData.rotation, float3.zero));
+
+ AddTextInternal(
+ text,
+ pivot,
+ m.c0.xyz,
+ m.c1.xyz,
+ textData.alignment,
+ textData.size,
+ false,
+ textData.numCharacters,
+ color
+ );
+ }
+
+
+ unsafe void AddTextInternal (System.UInt16* text, float3 pivot, float3 right, float3 up, LabelAlignment alignment, float size, bool sizeIsInPixels, int numCharacters, Color32 color) {
+ var distance = math.abs(math.dot(pivot - cameraPosition, math.mul(cameraRotation, new float3(0, 0, 1))));
+ var pixelSize = cameraDepthToPixelSize.x * distance + cameraDepthToPixelSize.y;
+ float fontWorldSize = size;
+
+ if (sizeIsInPixels) fontWorldSize *= pixelSize;
+
+ right *= fontWorldSize;
+ up *= fontWorldSize;
+
+ // Calculate the total width (in pixels divided by fontSize) of the text
+ float maxWidth = 0;
+ float currentWidth = 0;
+ float numLines = 1;
+
+ for (int i = 0; i < numCharacters; i++) {
+ var characterInfoIndex = text[i];
+ if (characterInfoIndex == SDFLookupData.Newline) {
+ maxWidth = math.max(maxWidth, currentWidth);
+ currentWidth = 0;
+ numLines++;
+ } else {
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (characterInfoIndex >= characterInfoLength) throw new System.Exception("Invalid character. No info exists. This is a bug.");
+#endif
+ currentWidth += characterInfo[characterInfoIndex].advance;
+ }
+ }
+ maxWidth = math.max(maxWidth, currentWidth);
+
+ // Calculate the world space position of the text given the camera and text alignment
+ var pos = pivot;
+ pos -= right * maxWidth * alignment.relativePivot.x;
+ // Size of a character as a fraction of a whole line using the current font
+ const float FontCharacterFractionOfLine = 0.75f;
+ // Where the upper and lower parts of the text will be assuming we start to write at y=0
+ var lower = 1 - numLines;
+ var upper = FontCharacterFractionOfLine;
+ var yAdjustment = math.lerp(lower, upper, alignment.relativePivot.y);
+ pos -= up * yAdjustment;
+ pos += math.mul(cameraRotation, new float3(1, 0, 0)) * (pixelSize * alignment.pixelOffset.x);
+ pos += math.mul(cameraRotation, new float3(0, 1, 0)) * (pixelSize * alignment.pixelOffset.y);
+
+ var textVertices = &buffers->textVertices;
+ var textTriangles = &buffers->textTriangles;
+
+ // Reserve all buffer space beforehand
+ Reserve(textVertices, numCharacters * VerticesPerCharacter * UnsafeUtility.SizeOf<TextVertex>());
+ Reserve(textTriangles, numCharacters * TrianglesPerCharacter * UnsafeUtility.SizeOf<int>());
+
+ var lineStart = pos;
+
+ for (int i = 0; i < numCharacters; i++) {
+ var characterInfoIndex = text[i];
+
+ if (characterInfoIndex == SDFLookupData.Newline) {
+ lineStart -= up;
+ pos = lineStart;
+ continue;
+ }
+
+ // Get character rendering information from the font
+ SDFCharacter ch = characterInfo[characterInfoIndex];
+
+ int vertexIndexStart = textVertices->Length / UnsafeUtility.SizeOf<TextVertex>();
+
+ float3 v;
+
+ v = pos + ch.vertexTopLeft.x * right + ch.vertexTopLeft.y * up;
+ minBounds = math.min(minBounds, v);
+ maxBounds = math.max(maxBounds, v);
+ Add(textVertices, new TextVertex {
+ position = v,
+ uv = ch.uvTopLeft,
+ color = color,
+ });
+
+ v = pos + ch.vertexTopRight.x * right + ch.vertexTopRight.y * up;
+ minBounds = math.min(minBounds, v);
+ maxBounds = math.max(maxBounds, v);
+ Add(textVertices, new TextVertex {
+ position = v,
+ uv = ch.uvTopRight,
+ color = color,
+ });
+
+ v = pos + ch.vertexBottomRight.x * right + ch.vertexBottomRight.y * up;
+ minBounds = math.min(minBounds, v);
+ maxBounds = math.max(maxBounds, v);
+ Add(textVertices, new TextVertex {
+ position = v,
+ uv = ch.uvBottomRight,
+ color = color,
+ });
+
+ v = pos + ch.vertexBottomLeft.x * right + ch.vertexBottomLeft.y * up;
+ minBounds = math.min(minBounds, v);
+ maxBounds = math.max(maxBounds, v);
+ Add(textVertices, new TextVertex {
+ position = v,
+ uv = ch.uvBottomLeft,
+ color = color,
+ });
+
+ Add(textTriangles, vertexIndexStart + 0);
+ Add(textTriangles, vertexIndexStart + 1);
+ Add(textTriangles, vertexIndexStart + 2);
+
+ Add(textTriangles, vertexIndexStart + 0);
+ Add(textTriangles, vertexIndexStart + 2);
+ Add(textTriangles, vertexIndexStart + 3);
+
+ // Advance character position
+ pos += right * ch.advance;
+ }
+ }
+
+ float3 lastNormalizedLineDir;
+ float lastLineWidth;
+
+ public const float MaxCirclePixelError = 0.5f;
+
+ public const int VerticesPerCharacter = 4;
+ public const int TrianglesPerCharacter = 6;
+
+ void AddLine (LineData line) {
+ // Store the line direction in the vertex.
+ // A line consists of 4 vertices. The line direction will be used to
+ // offset the vertices to create a line with a fixed pixel thickness
+ var a = PerspectiveDivide(math.mul(currentMatrix, new float4(line.a, 1.0f)));
+ var b = PerspectiveDivide(math.mul(currentMatrix, new float4(line.b, 1.0f)));
+
+ float lineWidth = currentLineWidthData.pixels;
+ var normalizedLineDir = math.normalizesafe(b - a);
+
+ if (math.any(math.isnan(normalizedLineDir))) throw new Exception("Nan line coordinates");
+ if (lineWidth <= 0) {
+ return;
+ }
+
+ // Update the bounding box
+ minBounds = math.min(minBounds, math.min(a, b));
+ maxBounds = math.max(maxBounds, math.max(a, b));
+
+ unsafe {
+ var outlineVertices = &buffers->vertices;
+
+ // Make sure there is enough allocated capacity for 4 more vertices
+ Reserve(outlineVertices, 4 * UnsafeUtility.SizeOf<Vertex>());
+
+ // Insert 4 vertices
+ // Doing it with pointers is faster, and this is the hottest
+ // code of the whole gizmo drawing process.
+ var ptr = (Vertex*)((byte*)outlineVertices->Ptr + outlineVertices->Length);
+
+ var startLineDir = normalizedLineDir * lineWidth;
+ var endLineDir = normalizedLineDir * lineWidth;
+
+ // If dot(last dir, this dir) >= 0 => use join
+ if (lineWidth > 1 && currentLineWidthData.automaticJoins && outlineVertices->Length > 2*UnsafeUtility.SizeOf<Vertex>()) {
+ // has previous vertex
+ Vertex* lastVertex1 = (Vertex*)(ptr - 1);
+ Vertex* lastVertex2 = (Vertex*)(ptr - 2);
+
+ var cosAngle = math.dot(normalizedLineDir, lastNormalizedLineDir);
+ if (math.all(lastVertex2->position == a) && lastLineWidth == lineWidth && cosAngle >= -0.6f) {
+ // Safety: tangent cannot be 0 because cosAngle > -1
+ var tangent = normalizedLineDir + lastNormalizedLineDir;
+ // From the law of cosines we get that
+ // tangent.magnitude = sqrt(2)*sqrt(1+cosAngle)
+
+ // Create join!
+ // Trigonometry gives us
+ // joinRadius = lineWidth / (2*cos(alpha / 2))
+ // Using half angle identity for cos we get
+ // joinRadius = lineWidth / (sqrt(2)*sqrt(1 + cos(alpha))
+ // Since the tangent already has mostly the same factors we can simplify the calculation
+ // normalize(tangent) * joinRadius * 2
+ // = tangent / (sqrt(2)*sqrt(1+cosAngle)) * joinRadius * 2
+ // = tangent * lineWidth / (1 + cos(alpha)
+ var joinLineDir = tangent * lineWidth / (1 + cosAngle);
+
+ startLineDir = joinLineDir;
+ lastVertex1->uv2 = startLineDir;
+ lastVertex2->uv2 = startLineDir;
+ }
+ }
+
+ outlineVertices->Length = outlineVertices->Length + 4 * UnsafeUtility.SizeOf<Vertex>();
+ *ptr++ = new Vertex {
+ position = a,
+ color = currentColor,
+ uv = new float2(0, 0),
+ uv2 = startLineDir,
+ };
+ *ptr++ = new Vertex {
+ position = a,
+ color = currentColor,
+ uv = new float2(1, 0),
+ uv2 = startLineDir,
+ };
+
+ *ptr++ = new Vertex {
+ position = b,
+ color = currentColor,
+ uv = new float2(0, 1),
+ uv2 = endLineDir,
+ };
+ *ptr++ = new Vertex {
+ position = b,
+ color = currentColor,
+ uv = new float2(1, 1),
+ uv2 = endLineDir,
+ };
+
+ lastNormalizedLineDir = normalizedLineDir;
+ lastLineWidth = lineWidth;
+ }
+ }
+
+ /// <summary>Calculate number of steps to use for drawing a circle at the specified point and radius to get less than the specified pixel error.</summary>
+ internal static int CircleSteps (float3 center, float radius, float maxPixelError, ref float4x4 currentMatrix, float2 cameraDepthToPixelSize, float3 cameraPosition) {
+ var centerv4 = math.mul(currentMatrix, new float4(center, 1.0f));
+
+ if (math.abs(centerv4.w) < 0.0000001f) return 3;
+ var cc = PerspectiveDivide(centerv4);
+ // Take the maximum scale factor among the 3 axes.
+ // If the current matrix has a uniform scale then they are all the same.
+ var maxScaleFactor = math.sqrt(math.max(math.max(math.lengthsq(currentMatrix.c0.xyz), math.lengthsq(currentMatrix.c1.xyz)), math.lengthsq(currentMatrix.c2.xyz))) / centerv4.w;
+ var realWorldRadius = radius * maxScaleFactor;
+ var distance = math.length(cc - cameraPosition);
+
+ var pixelSize = cameraDepthToPixelSize.x * distance + cameraDepthToPixelSize.y;
+ // realWorldRadius += pixelSize * this.currentLineWidthData.pixels * 0.5f;
+ var cosAngle = 1 - (maxPixelError * pixelSize) / realWorldRadius;
+ int steps = cosAngle < 0 ? 3 : (int)math.ceil(math.PI / (math.acos(cosAngle)));
+ return steps;
+ }
+
+ void AddCircle (CircleData circle) {
+ // If the circle has a zero normal then just ignore it
+ if (math.all(circle.normal == 0)) return;
+
+ circle.normal = math.normalize(circle.normal);
+ // Canonicalize
+ if (circle.normal.y < 0) circle.normal = -circle.normal;
+
+ float3 tangent1;
+ if (math.all(math.abs(circle.normal - new float3(0, 1, 0)) < 0.001f)) {
+ // The normal was (almost) identical to (0, 1, 0)
+ tangent1 = new float3(0, 0, 1);
+ } else {
+ // Common case
+ tangent1 = math.normalizesafe(math.cross(circle.normal, new float3(0, 1, 0)));
+ }
+
+ var ex = tangent1;
+ var ey = circle.normal;
+ var ez = math.cross(ey, ex);
+ var oldMatrix = currentMatrix;
+
+ currentMatrix = math.mul(currentMatrix, new float4x4(
+ new float4(ex, 0) * circle.radius,
+ new float4(ey, 0) * circle.radius,
+ new float4(ez, 0) * circle.radius,
+ new float4(circle.center, 1)
+ ));
+
+ AddCircle(new CircleXZData {
+ center = new float3(0, 0, 0),
+ radius = 1,
+ startAngle = 0,
+ endAngle = 2 * math.PI,
+ });
+
+ currentMatrix = oldMatrix;
+ }
+
+ void AddDisc (CircleData circle) {
+ // If the circle has a zero normal then just ignore it
+ if (math.all(circle.normal == 0)) return;
+
+ var steps = CircleSteps(circle.center, circle.radius, maxPixelError, ref currentMatrix, cameraDepthToPixelSize, cameraPosition);
+
+ circle.normal = math.normalize(circle.normal);
+ float3 tangent1;
+ if (math.all(math.abs(circle.normal - new float3(0, 1, 0)) < 0.001f)) {
+ // The normal was (almost) identical to (0, 1, 0)
+ tangent1 = new float3(0, 0, 1);
+ } else {
+ // Common case
+ tangent1 = math.cross(circle.normal, new float3(0, 1, 0));
+ }
+
+ float invSteps = 1.0f / steps;
+
+ unsafe {
+ var solidVertices = &buffers->solidVertices;
+ var solidTriangles = &buffers->solidTriangles;
+ Reserve(solidVertices, steps * UnsafeUtility.SizeOf<Vertex>());
+ Reserve(solidTriangles, 3*(steps-2) * UnsafeUtility.SizeOf<int>());
+
+ var matrix = math.mul(currentMatrix, Matrix4x4.TRS(circle.center, Quaternion.LookRotation(circle.normal, tangent1), new Vector3(circle.radius, circle.radius, circle.radius)));
+
+ var mn = minBounds;
+ var mx = maxBounds;
+ int vertexCount = solidVertices->Length / UnsafeUtility.SizeOf<Vertex>();
+
+ for (int i = 0; i < steps; i++) {
+ var t = math.lerp(0, 2*Mathf.PI, i * invSteps);
+ math.sincos(t, out float sin, out float cos);
+
+ var p = PerspectiveDivide(math.mul(matrix, new float4(cos, sin, 0, 1)));
+ // Update the bounding box
+ mn = math.min(mn, p);
+ mx = math.max(mx, p);
+
+ Add(solidVertices, new Vertex {
+ position = p,
+ color = currentColor,
+ uv = new float2(0, 0),
+ uv2 = new float3(0, 0, 0),
+ });
+ }
+
+ minBounds = mn;
+ maxBounds = mx;
+
+ for (int i = 0; i < steps - 2; i++) {
+ Add(solidTriangles, vertexCount);
+ Add(solidTriangles, vertexCount + i + 1);
+ Add(solidTriangles, vertexCount + i + 2);
+ }
+ }
+ }
+
+ void AddSphereOutline (SphereData circle) {
+ var centerv4 = math.mul(currentMatrix, new float4(circle.center, 1.0f));
+
+ if (math.abs(centerv4.w) < 0.0000001f) return;
+ var center = PerspectiveDivide(centerv4);
+ // Figure out the actual radius of the sphere after all the matrix multiplications.
+ // In case of a non-uniform scale, pick the largest radius
+ var maxScaleFactor = math.sqrt(math.max(math.max(math.lengthsq(currentMatrix.c0.xyz), math.lengthsq(currentMatrix.c1.xyz)), math.lengthsq(currentMatrix.c2.xyz))) / centerv4.w;
+ var realWorldRadius = circle.radius * maxScaleFactor;
+
+ if (cameraIsOrthographic) {
+ var prevMatrix = this.currentMatrix;
+ this.currentMatrix = float4x4.identity;
+ AddCircle(new CircleData {
+ center = center,
+ normal = math.mul(this.cameraRotation, new float3(0, 0, 1)),
+ radius = realWorldRadius,
+ });
+ this.currentMatrix = prevMatrix;
+ } else {
+ var dist = math.length(this.cameraPosition - center);
+ // Camera is inside the sphere, cannot draw
+ if (dist <= realWorldRadius) return;
+
+ var offsetTowardsCamera = realWorldRadius * realWorldRadius / dist;
+ var outlineRadius = math.sqrt(realWorldRadius * realWorldRadius - offsetTowardsCamera * offsetTowardsCamera);
+ var normal = math.normalize(this.cameraPosition - center);
+ var prevMatrix = this.currentMatrix;
+ this.currentMatrix = float4x4.identity;
+ AddCircle(new CircleData {
+ center = center + normal * offsetTowardsCamera,
+ normal = normal,
+ radius = outlineRadius,
+ });
+ this.currentMatrix = prevMatrix;
+ }
+ }
+
+ void AddCircle (CircleXZData circle) {
+ circle.endAngle = math.clamp(circle.endAngle, circle.startAngle - Mathf.PI * 2, circle.startAngle + Mathf.PI * 2);
+
+ unsafe {
+ var m = math.mul(currentMatrix, new float4x4(
+ new float4(circle.radius, 0, 0, 0),
+ new float4(0, circle.radius, 0, 0),
+ new float4(0, 0, circle.radius, 0),
+ new float4(circle.center, 1)
+ ));
+ var steps = CircleSteps(float3.zero, 1.0f, maxPixelError, ref m, cameraDepthToPixelSize, cameraPosition);
+ var lineWidth = currentLineWidthData.pixels;
+ if (lineWidth < 0) return;
+
+ var byteSize = steps * 4 * UnsafeUtility.SizeOf<Vertex>();
+ Reserve(&buffers->vertices, byteSize);
+ var ptr = (Vertex*)(buffers->vertices.Ptr + buffers->vertices.Length);
+ buffers->vertices.Length += byteSize;
+ math.sincos(circle.startAngle, out float sin0, out float cos0);
+ var prev = PerspectiveDivide(math.mul(m, new float4(cos0, 0, sin0, 1)));
+ var prevTangent = math.normalizesafe(math.mul(m, new float4(-sin0, 0, cos0, 0)).xyz) * lineWidth;
+ var invSteps = math.rcp(steps);
+
+ for (int i = 1; i <= steps; i++) {
+ var t = math.lerp(circle.startAngle, circle.endAngle, i * invSteps);
+ math.sincos(t, out float sin, out float cos);
+ var next = PerspectiveDivide(math.mul(m, new float4(cos, 0, sin, 1)));
+ var tangent = math.normalizesafe(math.mul(m, new float4(-sin, 0, cos, 0)).xyz) * lineWidth;
+ *ptr++ = new Vertex {
+ position = prev,
+ color = currentColor,
+ uv = new float2(0, 0),
+ uv2 = prevTangent,
+ };
+ *ptr++ = new Vertex {
+ position = prev,
+ color = currentColor,
+ uv = new float2(1, 0),
+ uv2 = prevTangent,
+ };
+ *ptr++ = new Vertex {
+ position = next,
+ color = currentColor,
+ uv = new float2(0, 1),
+ uv2 = tangent,
+ };
+ *ptr++ = new Vertex {
+ position = next,
+ color = currentColor,
+ uv = new float2(1, 1),
+ uv2 = tangent,
+ };
+
+ prev = next;
+ prevTangent = tangent;
+ }
+
+ // Update the global bounds with the bounding box of the circle
+ var b0 = PerspectiveDivide(math.mul(m, new float4(-1, 0, 0, 1)));
+ var b1 = PerspectiveDivide(math.mul(m, new float4(0, -1, 0, 1)));
+ var b2 = PerspectiveDivide(math.mul(m, new float4(+1, 0, 0, 1)));
+ var b3 = PerspectiveDivide(math.mul(m, new float4(0, +1, 0, 1)));
+ minBounds = math.min(math.min(math.min(math.min(b0, b1), b2), b3), minBounds);
+ maxBounds = math.max(math.max(math.max(math.max(b0, b1), b2), b3), maxBounds);
+ }
+ }
+
+ void AddDisc (CircleXZData circle) {
+ var steps = CircleSteps(circle.center, circle.radius, maxPixelError, ref currentMatrix, cameraDepthToPixelSize, cameraPosition);
+
+ circle.endAngle = math.clamp(circle.endAngle, circle.startAngle - Mathf.PI * 2, circle.startAngle + Mathf.PI * 2);
+
+ float invSteps = 1.0f / steps;
+
+ unsafe {
+ var solidVertices = &buffers->solidVertices;
+ var solidTriangles = &buffers->solidTriangles;
+ Reserve(solidVertices, (2+steps) * UnsafeUtility.SizeOf<Vertex>());
+ Reserve(solidTriangles, 3*steps * UnsafeUtility.SizeOf<int>());
+
+ var matrix = math.mul(currentMatrix, Matrix4x4.Translate(circle.center) * Matrix4x4.Scale(new Vector3(circle.radius, circle.radius, circle.radius)));
+
+ var worldCenter = PerspectiveDivide(math.mul(matrix, new float4(0, 0, 0, 1)));
+ Add(solidVertices, new Vertex {
+ position = worldCenter,
+ color = currentColor,
+ uv = new float2(0, 0),
+ uv2 = new float3(0, 0, 0),
+ });
+
+ var mn = math.min(minBounds, worldCenter);
+ var mx = math.max(maxBounds, worldCenter);
+ int vertexCount = solidVertices->Length / UnsafeUtility.SizeOf<Vertex>();
+
+ for (int i = 0; i <= steps; i++) {
+ var t = math.lerp(circle.startAngle, circle.endAngle, i * invSteps);
+ math.sincos(t, out float sin, out float cos);
+
+ var p = PerspectiveDivide(math.mul(matrix, new float4(cos, 0, sin, 1)));
+ // Update the bounding box
+ mn = math.min(mn, p);
+ mx = math.max(mx, p);
+
+ Add(solidVertices, new Vertex {
+ position = p,
+ color = currentColor,
+ uv = new float2(0, 0),
+ uv2 = new float3(0, 0, 0),
+ });
+ }
+
+ minBounds = mn;
+ maxBounds = mx;
+
+ for (int i = 0; i < steps; i++) {
+ // Center vertex
+ Add(solidTriangles, vertexCount - 1);
+ Add(solidTriangles, vertexCount + i + 0);
+ Add(solidTriangles, vertexCount + i + 1);
+ }
+ }
+ }
+
+ void AddSolidTriangle (TriangleData triangle) {
+ unsafe {
+ var solidVertices = &buffers->solidVertices;
+ var solidTriangles = &buffers->solidTriangles;
+ Reserve(solidVertices, 3 * UnsafeUtility.SizeOf<Vertex>());
+ Reserve(solidTriangles, 3 * UnsafeUtility.SizeOf<int>());
+ var matrix = currentMatrix;
+ var a = PerspectiveDivide(math.mul(matrix, new float4(triangle.a, 1)));
+ var b = PerspectiveDivide(math.mul(matrix, new float4(triangle.b, 1)));
+ var c = PerspectiveDivide(math.mul(matrix, new float4(triangle.c, 1)));
+ int startVertex = solidVertices->Length / UnsafeUtility.SizeOf<Vertex>();
+
+ minBounds = math.min(math.min(math.min(minBounds, a), b), c);
+ maxBounds = math.max(math.max(math.max(maxBounds, a), b), c);
+
+ Add(solidVertices, new Vertex {
+ position = a,
+ color = currentColor,
+ uv = new float2(0, 0),
+ uv2 = new float3(0, 0, 0),
+ });
+ Add(solidVertices, new Vertex {
+ position = b,
+ color = currentColor,
+ uv = new float2(0, 0),
+ uv2 = new float3(0, 0, 0),
+ });
+ Add(solidVertices, new Vertex {
+ position = c,
+ color = currentColor,
+ uv = new float2(0, 0),
+ uv2 = new float3(0, 0, 0),
+ });
+
+ Add(solidTriangles, startVertex + 0);
+ Add(solidTriangles, startVertex + 1);
+ Add(solidTriangles, startVertex + 2);
+ }
+ }
+
+ void AddWireBox (BoxData box) {
+ var min = box.center - box.size * 0.5f;
+ var max = box.center + box.size * 0.5f;
+ AddLine(new LineData { a = new float3(min.x, min.y, min.z), b = new float3(max.x, min.y, min.z) });
+ AddLine(new LineData { a = new float3(max.x, min.y, min.z), b = new float3(max.x, min.y, max.z) });
+ AddLine(new LineData { a = new float3(max.x, min.y, max.z), b = new float3(min.x, min.y, max.z) });
+ AddLine(new LineData { a = new float3(min.x, min.y, max.z), b = new float3(min.x, min.y, min.z) });
+
+ AddLine(new LineData { a = new float3(min.x, max.y, min.z), b = new float3(max.x, max.y, min.z) });
+ AddLine(new LineData { a = new float3(max.x, max.y, min.z), b = new float3(max.x, max.y, max.z) });
+ AddLine(new LineData { a = new float3(max.x, max.y, max.z), b = new float3(min.x, max.y, max.z) });
+ AddLine(new LineData { a = new float3(min.x, max.y, max.z), b = new float3(min.x, max.y, min.z) });
+
+ AddLine(new LineData { a = new float3(min.x, min.y, min.z), b = new float3(min.x, max.y, min.z) });
+ AddLine(new LineData { a = new float3(max.x, min.y, min.z), b = new float3(max.x, max.y, min.z) });
+ AddLine(new LineData { a = new float3(max.x, min.y, max.z), b = new float3(max.x, max.y, max.z) });
+ AddLine(new LineData { a = new float3(min.x, min.y, max.z), b = new float3(min.x, max.y, max.z) });
+ }
+
+ void AddPlane (PlaneData plane) {
+ var oldMatrix = currentMatrix;
+
+ currentMatrix = math.mul(currentMatrix, float4x4.TRS(plane.center, plane.rotation, new float3(plane.size.x * 0.5f, 1, plane.size.y * 0.5f)));
+
+ AddLine(new LineData { a = new float3(-1, 0, -1), b = new float3(1, 0, -1) });
+ AddLine(new LineData { a = new float3(1, 0, -1), b = new float3(1, 0, 1) });
+ AddLine(new LineData { a = new float3(1, 0, 1), b = new float3(-1, 0, 1) });
+ AddLine(new LineData { a = new float3(-1, 0, 1), b = new float3(-1, 0, -1) });
+
+ currentMatrix = oldMatrix;
+ }
+
+ internal static readonly float4[] BoxVertices = {
+ new float4(-1, -1, -1, 1),
+ new float4(-1, -1, +1, 1),
+ new float4(-1, +1, -1, 1),
+ new float4(-1, +1, +1, 1),
+ new float4(+1, -1, -1, 1),
+ new float4(+1, -1, +1, 1),
+ new float4(+1, +1, -1, 1),
+ new float4(+1, +1, +1, 1),
+ };
+
+ internal static readonly int[] BoxTriangles = {
+ // Bottom two triangles
+ 0, 1, 5,
+ 0, 5, 4,
+
+ // Top
+ 7, 3, 2,
+ 7, 2, 6,
+
+ // -X
+ 0, 1, 3,
+ 0, 3, 2,
+
+ // +X
+ 4, 5, 7,
+ 4, 7, 6,
+
+ // +Z
+ 1, 3, 7,
+ 1, 7, 5,
+
+ // -Z
+ 0, 2, 6,
+ 0, 6, 4,
+ };
+
+ void AddBox (BoxData box) {
+ unsafe {
+ var solidVertices = &buffers->solidVertices;
+ var solidTriangles = &buffers->solidTriangles;
+ Reserve(solidVertices, BoxVertices.Length * UnsafeUtility.SizeOf<Vertex>());
+ Reserve(solidTriangles, BoxTriangles.Length * UnsafeUtility.SizeOf<int>());
+
+ var scale = box.size * 0.5f;
+ var matrix = math.mul(currentMatrix, new float4x4(
+ new float4(scale.x, 0, 0, 0),
+ new float4(0, scale.y, 0, 0),
+ new float4(0, 0, scale.z, 0),
+ new float4(box.center, 1)
+ ));
+
+ var mn = minBounds;
+ var mx = maxBounds;
+ int vertexOffset = solidVertices->Length / UnsafeUtility.SizeOf<Vertex>();
+ var ptr = (Vertex*)(solidVertices->Ptr + solidVertices->Length);
+ for (int i = 0; i < BoxVertices.Length; i++) {
+ var p = PerspectiveDivide(math.mul(matrix, BoxVertices[i]));
+ // Update the bounding box
+ mn = math.min(mn, p);
+ mx = math.max(mx, p);
+
+ *ptr++ = new Vertex {
+ position = p,
+ color = currentColor,
+ uv = new float2(0, 0),
+ uv2 = new float3(0, 0, 0),
+ };
+ }
+ solidVertices->Length += BoxVertices.Length * UnsafeUtility.SizeOf<Vertex>();
+
+ minBounds = mn;
+ maxBounds = mx;
+
+ var triPtr = (int*)(solidTriangles->Ptr + solidTriangles->Length);
+ for (int i = 0; i < BoxTriangles.Length; i++) {
+ *triPtr++ = vertexOffset + BoxTriangles[i];
+ }
+ solidTriangles->Length += BoxTriangles.Length * UnsafeUtility.SizeOf<int>();
+ }
+ }
+
+ // AggressiveInlining because this is only called from a single location, and burst doesn't inline otherwise
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ public void Next (ref UnsafeAppendBuffer.Reader reader, ref NativeArray<float4x4> matrixStack, ref NativeArray<Color32> colorStack, ref NativeArray<LineWidthData> lineWidthStack, ref int matrixStackSize, ref int colorStackSize, ref int lineWidthStackSize) {
+ var fullCmd = reader.ReadNext<Command>();
+ var cmd = fullCmd & (Command)0xFF;
+ Color32 oldColor = default;
+
+ if ((fullCmd & Command.PushColorInline) != 0) {
+ oldColor = currentColor;
+ currentColor = reader.ReadNext<Color32>();
+ }
+
+ switch (cmd) {
+ case Command.PushColor:
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (colorStackSize >= colorStack.Length) throw new System.Exception("Too deeply nested PushColor calls");
+#else
+ if (colorStackSize >= colorStack.Length) colorStackSize--;
+#endif
+ colorStack[colorStackSize] = currentColor;
+ colorStackSize++;
+ currentColor = reader.ReadNext<Color32>();
+ break;
+ case Command.PopColor:
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (colorStackSize <= 0) throw new System.Exception("PushColor and PopColor are not matched");
+#else
+ if (colorStackSize <= 0) break;
+#endif
+ colorStackSize--;
+ currentColor = colorStack[colorStackSize];
+ break;
+ case Command.PushMatrix:
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (matrixStackSize >= matrixStack.Length) throw new System.Exception("Too deeply nested PushMatrix calls");
+#else
+ if (matrixStackSize >= matrixStack.Length) matrixStackSize--;
+#endif
+ matrixStack[matrixStackSize] = currentMatrix;
+ matrixStackSize++;
+ currentMatrix = math.mul(currentMatrix, reader.ReadNext<float4x4>());
+ break;
+ case Command.PushSetMatrix:
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (matrixStackSize >= matrixStack.Length) throw new System.Exception("Too deeply nested PushMatrix calls");
+#else
+ if (matrixStackSize >= matrixStack.Length) matrixStackSize--;
+#endif
+ matrixStack[matrixStackSize] = currentMatrix;
+ matrixStackSize++;
+ currentMatrix = reader.ReadNext<float4x4>();
+ break;
+ case Command.PopMatrix:
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (matrixStackSize <= 0) throw new System.Exception("PushMatrix and PopMatrix are not matched");
+#else
+ if (matrixStackSize <= 0) break;
+#endif
+ matrixStackSize--;
+ currentMatrix = matrixStack[matrixStackSize];
+ break;
+ case Command.PushLineWidth:
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (lineWidthStackSize >= lineWidthStack.Length) throw new System.Exception("Too deeply nested PushLineWidth calls");
+#else
+ if (lineWidthStackSize >= lineWidthStack.Length) lineWidthStackSize--;
+#endif
+ lineWidthStack[lineWidthStackSize] = currentLineWidthData;
+ lineWidthStackSize++;
+ currentLineWidthData = reader.ReadNext<LineWidthData>();
+ currentLineWidthData.pixels *= lineWidthMultiplier;
+ break;
+ case Command.PopLineWidth:
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (lineWidthStackSize <= 0) throw new System.Exception("PushLineWidth and PopLineWidth are not matched");
+#else
+ if (lineWidthStackSize <= 0) break;
+#endif
+ lineWidthStackSize--;
+ currentLineWidthData = lineWidthStack[lineWidthStackSize];
+ break;
+ case Command.Line:
+ AddLine(reader.ReadNext<LineData>());
+ break;
+ case Command.SphereOutline:
+ AddSphereOutline(reader.ReadNext<SphereData>());
+ break;
+ case Command.CircleXZ:
+ AddCircle(reader.ReadNext<CircleXZData>());
+ break;
+ case Command.Circle:
+ AddCircle(reader.ReadNext<CircleData>());
+ break;
+ case Command.DiscXZ:
+ AddDisc(reader.ReadNext<CircleXZData>());
+ break;
+ case Command.Disc:
+ AddDisc(reader.ReadNext<CircleData>());
+ break;
+ case Command.Box:
+ AddBox(reader.ReadNext<BoxData>());
+ break;
+ case Command.WirePlane:
+ AddPlane(reader.ReadNext<PlaneData>());
+ break;
+ case Command.WireBox:
+ AddWireBox(reader.ReadNext<BoxData>());
+ break;
+ case Command.SolidTriangle:
+ AddSolidTriangle(reader.ReadNext<TriangleData>());
+ break;
+ case Command.PushPersist:
+ // This command does not need to be handled by the builder
+ reader.ReadNext<PersistData>();
+ break;
+ case Command.PopPersist:
+ // This command does not need to be handled by the builder
+ break;
+ case Command.Text:
+ var data = reader.ReadNext<TextData>();
+ unsafe {
+ System.UInt16* ptr = (System.UInt16*)reader.ReadNext(UnsafeUtility.SizeOf<System.UInt16>() * data.numCharacters);
+ AddText(ptr, data, currentColor);
+ }
+ break;
+ case Command.Text3D:
+ var data2 = reader.ReadNext<TextData3D>();
+ unsafe {
+ System.UInt16* ptr = (System.UInt16*)reader.ReadNext(UnsafeUtility.SizeOf<System.UInt16>() * data2.numCharacters);
+ AddText3D(ptr, data2, currentColor);
+ }
+ break;
+ case Command.CaptureState:
+ unsafe {
+ buffers->capturedState.Add(new ProcessedBuilderData.CapturedState {
+ color = this.currentColor,
+ matrix = this.currentMatrix,
+ });
+ }
+ break;
+ default:
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ throw new System.Exception("Unknown command");
+#else
+ break;
+#endif
+ }
+
+ if ((fullCmd & Command.PushColorInline) != 0) {
+ currentColor = oldColor;
+ }
+ }
+
+ void CreateTriangles () {
+ // Create triangles for all lines
+ // A triangle consists of 3 indices
+ // A line (4 vertices) consists of 2 triangles, so 6 triangle indices
+ unsafe {
+ var outlineVertices = &buffers->vertices;
+ var outlineTriangles = &buffers->triangles;
+ var vertexCount = outlineVertices->Length / UnsafeUtility.SizeOf<Vertex>();
+ // Each line is made out of 4 vertices
+ var lineCount = vertexCount / 4;
+ var trianglesSizeInBytes = lineCount * 6 * UnsafeUtility.SizeOf<int>();
+ if (trianglesSizeInBytes >= outlineTriangles->Capacity) {
+ outlineTriangles->SetCapacity(math.ceilpow2(trianglesSizeInBytes));
+ }
+
+ int* ptr = (int*)outlineTriangles->Ptr;
+ for (int i = 0, vi = 0; i < lineCount; i++, vi += 4) {
+ // First triangle
+ *ptr++ = vi + 0;
+ *ptr++ = vi + 1;
+ *ptr++ = vi + 2;
+
+ // Second triangle
+ *ptr++ = vi + 1;
+ *ptr++ = vi + 3;
+ *ptr++ = vi + 2;
+ }
+ outlineTriangles->Length = trianglesSizeInBytes;
+ }
+ }
+
+ public const int MaxStackSize = 32;
+
+ public void Execute () {
+ unsafe {
+ buffers->vertices.Reset();
+ buffers->triangles.Reset();
+ buffers->solidVertices.Reset();
+ buffers->solidTriangles.Reset();
+ buffers->textVertices.Reset();
+ buffers->textTriangles.Reset();
+ buffers->capturedState.Reset();
+ }
+
+ currentLineWidthData.pixels *= lineWidthMultiplier;
+
+ minBounds = new float3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
+ maxBounds = new float3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
+
+ var matrixStack = new NativeArray<float4x4>(MaxStackSize, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ var colorStack = new NativeArray<Color32>(MaxStackSize, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ var lineWidthStack = new NativeArray<LineWidthData>(MaxStackSize, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
+ int matrixStackSize = 0;
+ int colorStackSize = 0;
+ int lineWidthStackSize = 0;
+
+ CommandBuilderSamplers.MarkerProcessCommands.Begin();
+ unsafe {
+ var reader = buffers->splitterOutput.AsReader();
+ while (reader.Offset < reader.Size) Next(ref reader, ref matrixStack, ref colorStack, ref lineWidthStack, ref matrixStackSize, ref colorStackSize, ref lineWidthStackSize);
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ if (reader.Offset != reader.Size) throw new Exception("Didn't reach the end of the buffer");
+#endif
+ }
+ CommandBuilderSamplers.MarkerProcessCommands.End();
+
+ CommandBuilderSamplers.MarkerCreateTriangles.Begin();
+ CreateTriangles();
+ CommandBuilderSamplers.MarkerCreateTriangles.End();
+
+ unsafe {
+ var outBounds = &buffers->bounds;
+ *outBounds = new Bounds((minBounds + maxBounds) * 0.5f, maxBounds - minBounds);
+
+ if (math.any(math.isnan(outBounds->min)) && (buffers->vertices.Length > 0 || buffers->solidTriangles.Length > 0)) {
+ // Fall back to a bounding box that covers everything
+ *outBounds = new Bounds(Vector3.zero, new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity));
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ throw new Exception("NaN bounds. A Draw.* command may have been given NaN coordinates.");
+#endif
+ }
+ }
+ }
+ }
+}