1153 lines
42 KiB
C#
1153 lines
42 KiB
C#
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|