VibeGeometry/tools/realtimecsg_native_bridge/Program.cs
2026-05-07 03:28:33 +01:00

593 lines
31 KiB
C#

using System.Diagnostics;
using System.Runtime.InteropServices;
const int WarmupIterations = 8;
const int MeasureIterations = 64;
var debugNative = args.Any(arg => arg == "--debug-native");
var healthOnly = args.Any(arg => arg == "--health");
var outputArg = args.FirstOrDefault(arg => arg != "--debug-native" && arg != "--health");
var output = outputArg is not null
? outputArg
: Path.GetFullPath(Path.Combine("experiments", "generated", "realtimecsg-cpp-perf-latest.jsonl"));
Directory.CreateDirectory(Path.GetDirectoryName(output)!);
Native.RegisterNoopUnityMethods();
if (healthOnly)
{
using var healthWriter = new StreamWriter(output, false);
healthWriter.WriteLine(RunHealthProbe());
return;
}
var cases = new PerfCase[]
{
new("single_center_cut", 2, SingleCenterCut),
new("room_grid_8x8_doors", 192, RoomGrid8x8Doors),
new("rotated_cut_stack_64", 65, RotatedCutStack64),
new("common_box_chain_64", 64, CommonBoxChain64),
new("distant_cutters_512", 513, DistantCutters512),
};
using var writer = new StreamWriter(output, false);
foreach (var perfCase in cases)
{
writer.WriteLine(RunCase(perfCase));
}
string RunCase(PerfCase perfCase)
{
var timings = new List<long>(MeasureIterations);
var triangles = 0;
var vertices = 0;
var meshDescriptions = 0;
Native.ClearAllNodes();
var tree = perfCase.Build();
if (tree == 0)
throw new InvalidOperationException($"Failed to build native RealtimeCSG tree for {perfCase.Name}");
Native.RebuildAll();
if (debugNative)
DumpTree(perfCase.Name, tree);
if (Native.GetBrushCount() != perfCase.Brushes)
throw new InvalidOperationException($"Native brush count mismatch for {perfCase.Name}: expected {perfCase.Brushes}, got {Native.GetBrushCount()}, nodes={Native.GetNodeCount()}, trees={Native.GetTreeCount()}, brushMeshes={Native.GetBrushMeshCount()}");
for (var i = 0; i < WarmupIterations; i++)
MeasureOnce(tree, out triangles, out vertices, out meshDescriptions);
if (meshDescriptions == 0)
{
DumpTree(perfCase.Name, tree);
throw new InvalidOperationException($"Native generated no mesh descriptions for {perfCase.Name}: nodes={Native.GetNodeCount()}, brushes={Native.GetBrushCount()}, trees={Native.GetTreeCount()}, brushMeshes={Native.GetBrushMeshCount()}, treeBrushes={Native.GetNumberOfBrushesInTree(tree)}, treeChildren={Native.GetChildNodeCount(tree)}, treeDirty={Native.IsNodeDirty(tree)}");
}
for (var i = 0; i < MeasureIterations; i++)
{
var stopwatch = Stopwatch.StartNew();
MeasureOnce(tree, out triangles, out vertices, out meshDescriptions);
stopwatch.Stop();
timings.Add(stopwatch.ElapsedTicks);
}
Native.ClearAllNodes();
timings.Sort();
var mean = timings.Sum() / timings.Count;
return
$"{{\"kernel\":\"realtime_csg_cpp\",\"scenario\":\"{perfCase.Name}\",\"brushes\":{perfCase.Brushes},\"iterations\":{MeasureIterations},\"warmup_iterations\":{WarmupIterations},\"mean_ns\":{TicksToNs(mean)},\"min_ns\":{TicksToNs(timings[0])},\"p50_ns\":{TicksToNs(timings[(timings.Count - 1) * 50 / 100])},\"p95_ns\":{TicksToNs(timings[(timings.Count - 1) * 95 / 100])},\"max_ns\":{TicksToNs(timings[^1])},\"triangles\":{triangles},\"vertices\":{vertices},\"mesh_descriptions\":{meshDescriptions}}}";
}
static string RunHealthProbe()
{
Native.ClearAllNodes();
var tree = TreeFrom(() => new[]
{
Brush("native_health_cube", Vec3.Zero, Vec3.Splat(4), Mat4.Identity, Operation.Additive),
});
Native.RebuildAll();
var updateOk = Native.UpdateAllTreeMeshes();
var child = Native.GetChildNodeAtIndex(tree, 0);
var bounds = new Aabb();
var boundsOk = Native.GetBrushBounds(child, ref bounds);
var outlineOk = Native.GetBrushOutlineSizes(child, out var outlineVertices, out var visibleOuter, out var visibleInner, out var invisibleOuter, out var invisibleInner, out var invalid);
var rayStart = new Vec3(-8, 0, 0);
var rayEnd = new Vec3(8, 0, 0);
var matrix = Mat4.Identity;
var rayHits = Native.RayCastIntoTreeMultiCount(tree, ref rayStart, ref rayEnd, ref matrix, 0, false, IntPtr.Zero, 0);
var meshTypes = MeshQuery.RenderOnlyTypes;
var meshTypesHandle = GCHandle.Alloc(meshTypes, GCHandleType.Pinned);
var generated = Native.GenerateMeshDescriptions(tree, meshTypes.Length, meshTypesHandle.AddrOfPinnedObject(), VertexChannelFlags.All, out var meshDescriptions);
meshTypesHandle.Free();
var heartbeat = updateOk && Native.GetTreeEnabled(tree) && Native.GetChildNodeCount(tree) == 1 && boundsOk && outlineOk && rayHits > 0;
var meshExtractionReady = generated && meshDescriptions > 0;
var json =
$"{{\"kernel\":\"realtime_csg_cpp\",\"scenario\":\"native_health_cube\",\"heartbeat\":{JsonBool(heartbeat)},\"mesh_extraction_ready\":{JsonBool(meshExtractionReady)},\"update_ok\":{JsonBool(updateOk)},\"tree\":{tree},\"tree_enabled\":{JsonBool(Native.GetTreeEnabled(tree))},\"nodes\":{Native.GetNodeCount()},\"brushes\":{Native.GetBrushCount()},\"trees\":{Native.GetTreeCount()},\"brush_meshes\":{Native.GetBrushMeshCount()},\"tree_children\":{Native.GetChildNodeCount(tree)},\"tree_brushes\":{Native.GetNumberOfBrushesInTree(tree)},\"tree_dirty\":{JsonBool(Native.IsNodeDirty(tree))},\"child_type\":{Native.GetTypeOfNode(child)},\"child_tree\":{Native.GetTreeOfNode(child)},\"child_mesh\":{Native.GetBrushMeshID(child)},\"child_operation\":{Native.GetBrushOperationType(child)},\"bounds_ok\":{JsonBool(boundsOk)},\"bounds_min\":[{bounds.MinX},{bounds.MinY},{bounds.MinZ}],\"bounds_max\":[{bounds.MaxX},{bounds.MaxY},{bounds.MaxZ}],\"outline_ok\":{JsonBool(outlineOk)},\"outline_vertices\":{outlineVertices},\"visible_outer_lines\":{visibleOuter},\"visible_inner_lines\":{visibleInner},\"invisible_outer_lines\":{invisibleOuter},\"invisible_inner_lines\":{invisibleInner},\"invalid_lines\":{invalid},\"raycast_hits\":{rayHits},\"mesh_descriptions_generated\":{JsonBool(generated)},\"mesh_descriptions\":{meshDescriptions}}}";
Native.ClearAllNodes();
return json;
}
static void DumpTree(string name, int tree)
{
Native.RebuildAll();
var updated = Native.UpdateAllTreeMeshes();
Console.Error.WriteLine($"native debug {name}: updated={updated} tree={tree} type={Native.GetTypeOfNode(tree)} enabled={Native.GetTreeEnabled(tree)} children={Native.GetChildNodeCount(tree)} dirty={Native.IsNodeDirty(tree)} nodes={Native.GetNodeCount()} brushes={Native.GetBrushCount()} trees={Native.GetTreeCount()} brushMeshes={Native.GetBrushMeshCount()}");
for (var i = 0; i < Native.GetChildNodeCount(tree); i++)
{
var child = Native.GetChildNodeAtIndex(tree, i);
var bounds = new Aabb();
var boundsOk = Native.GetBrushBounds(child, ref bounds);
var outlineOk = Native.GetBrushOutlineSizes(child, out var outlineVertices, out var visibleOuter, out var visibleInner, out var invisibleOuter, out var invisibleInner, out var invalid);
Console.Error.WriteLine($" child[{i}] id={child} type={Native.GetTypeOfNode(child)} tree={Native.GetTreeOfNode(child)} mesh={Native.GetBrushMeshID(child)} op={Native.GetBrushOperationType(child)} dirty={Native.IsNodeDirty(child)} boundsOk={boundsOk} bounds=({bounds.MinX},{bounds.MinY},{bounds.MinZ})..({bounds.MaxX},{bounds.MaxY},{bounds.MaxZ}) outlineOk={outlineOk} outlineVertices={outlineVertices} visibleOuter={visibleOuter} visibleInner={visibleInner} invisibleOuter={invisibleOuter} invisibleInner={invisibleInner} invalid={invalid}");
}
var rayStart = new Vec3(-8, 0, 0);
var rayEnd = new Vec3(8, 0, 0);
var matrix = Mat4.Identity;
var rayHits = Native.RayCastIntoTreeMultiCount(tree, ref rayStart, ref rayEnd, ref matrix, 0, false, IntPtr.Zero, 0);
Console.Error.WriteLine($" raycast hits={rayHits}");
ProbeGeneratedMesh(tree);
}
static void ProbeGeneratedMesh(int tree)
{
var indices = new int[8192];
var positions = new Vec3[8192];
var indexHandle = GCHandle.Alloc(indices, GCHandleType.Pinned);
var positionHandle = GCHandle.Alloc(positions, GCHandleType.Pinned);
var ok = Native.GetGeneratedMesh(tree, 0, 0, indices.Length, indexHandle.AddrOfPinnedObject(), positions.Length, positionHandle.AddrOfPinnedObject(), IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out var center, out var size);
positionHandle.Free();
indexHandle.Free();
Console.Error.WriteLine($" direct mesh probe ok={ok} boundsCenter=({center.X},{center.Y},{center.Z}) boundsSize=({size.X},{size.Y},{size.Z}) firstIndex={indices[0]} firstPos=({positions[0].X},{positions[0].Y},{positions[0].Z})");
}
static long TicksToNs(long ticks) => (long)(ticks * (1_000_000_000.0 / Stopwatch.Frequency));
static string JsonBool(bool value) => value ? "true" : "false";
static void MeasureOnce(int tree, out int triangles, out int vertices, out int meshDescriptions)
{
Native.UpdateAllTreeMeshes();
var meshTypes = MeshQuery.RenderOnlyTypes;
var meshTypesHandle = GCHandle.Alloc(meshTypes, GCHandleType.Pinned);
var generated = Native.GenerateMeshDescriptions(tree, meshTypes.Length, meshTypesHandle.AddrOfPinnedObject(), VertexChannelFlags.All, out meshDescriptions);
if (!generated || meshDescriptions == 0)
{
meshTypesHandle.Free();
triangles = 0;
vertices = 0;
return;
}
meshTypesHandle.Free();
var descriptions = new GeneratedMeshDescription[meshDescriptions];
var descriptionsHandle = GCHandle.Alloc(descriptions, GCHandleType.Pinned);
var ok = Native.GetMeshDescriptions(tree, meshDescriptions, descriptionsHandle.AddrOfPinnedObject());
descriptionsHandle.Free();
if (!ok)
{
triangles = 0;
vertices = 0;
return;
}
triangles = 0;
vertices = 0;
foreach (var description in descriptions)
{
if (description.VertexCount <= 0 || description.IndexCount <= 0)
continue;
var indices = new int[description.IndexCount];
var positions = new Vec3[description.VertexCount];
var indexHandle = GCHandle.Alloc(indices, GCHandleType.Pinned);
var positionHandle = GCHandle.Alloc(positions, GCHandleType.Pinned);
ok = Native.GetGeneratedMesh(
tree,
description.MeshQueryIndex,
description.SubMeshQueryIndex,
description.IndexCount,
indexHandle.AddrOfPinnedObject(),
description.VertexCount,
positionHandle.AddrOfPinnedObject(),
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
out _,
out _);
positionHandle.Free();
indexHandle.Free();
if (ok)
{
triangles += description.IndexCount / 3;
vertices += description.VertexCount;
}
}
}
static int SingleCenterCut()
{
return TreeFrom(() => new[]
{
Brush("source", Vec3.Zero, Vec3.Splat(4), Mat4.Identity, Operation.Additive),
Brush("void", Vec3.Zero, Vec3.Splat(2), Mat4.Identity, Operation.Subtractive),
});
}
static int RoomGrid8x8Doors()
{
var nodes = new List<int>();
const float cell = 6.0f;
for (var y = 0; y < 8; y++)
for (var x = 0; x < 8; x++)
{
var center = new Vec3((x - 3.5f) * cell, (y - 3.5f) * cell, 0);
nodes.Add(Brush("floor", center + new Vec3(0, 0, -0.1f), new Vec3(5.6f, 5.6f, 0.2f), Mat4.Identity, Operation.Additive));
nodes.Add(Brush("north_wall", center + new Vec3(0, 2.8f, 1.5f), new Vec3(5.6f, 0.25f, 3.0f), Mat4.Identity, Operation.Additive));
nodes.Add(Brush("door", center + new Vec3(0, 2.8f, 1.0f), new Vec3(1.2f, 0.5f, 2.0f), Mat4.Identity, Operation.Subtractive));
}
return Tree(nodes.ToArray());
}
static int RotatedCutStack64()
{
var nodes = new List<int> { Brush("slab", Vec3.Zero, new Vec3(32, 32, 4), Mat4.Identity, Operation.Additive) };
for (var index = 0; index < 64; index++)
{
var angle = index * 0.173f;
var radius = 11.0f + (index % 7) * 0.35f;
var center = new Vec3(MathF.Cos(angle) * radius, MathF.Sin(angle) * radius, 0);
nodes.Add(Brush("rotated_void", center, new Vec3(1.0f + (index % 3) * 0.3f, 8.0f, 5.0f), Mat4.RotationZ(angle), Operation.Subtractive));
}
return Tree(nodes.ToArray());
}
static int CommonBoxChain64()
{
var nodes = new List<int> { Brush("source", Vec3.Zero, new Vec3(32, 16, 8), Mat4.Identity, Operation.Additive) };
for (var index = 1; index < 64; index++)
{
var t = index / 63.0f;
var center = new Vec3((t - 0.5f) * 6.0f, MathF.Sin(t * MathF.Tau) * 1.5f, 0);
nodes.Add(Brush("common", center, new Vec3(30.0f - t * 8.0f, 14.0f, 7.0f), Mat4.RotationZ(t * 0.2f), Operation.Intersecting));
}
return Tree(nodes.ToArray());
}
static int DistantCutters512()
{
var nodes = new List<int> { Brush("source", Vec3.Zero, Vec3.Splat(8), Mat4.Identity, Operation.Additive) };
for (var index = 0; index < 512; index++)
{
var row = index / 32;
var col = index % 32;
nodes.Add(Brush("far_void", new Vec3(1000.0f + col * 4.0f, 1000.0f + row * 4.0f, 0), Vec3.Splat(1), Mat4.Identity, Operation.Subtractive));
}
return Tree(nodes.ToArray());
}
static int Tree(params int[] children)
{
return TreeFrom(() => children);
}
static int TreeFrom(Func<int[]> buildChildren)
{
var children = buildChildren();
if (!Native.GenerateTree(0, out var tree))
return 0;
if (!Native.SetTreeEnabled(tree, true) || !Native.GetTreeEnabled(tree))
throw new InvalidOperationException($"SetTreeEnabled failed for tree {tree}");
if (!InsertChildNodes(tree, children))
throw new InvalidOperationException($"InsertChildNodes failed for tree {tree} with {children.Length} children");
foreach (var child in children)
Native.SetDirty(child);
Native.SetDirty(tree);
return tree;
}
static int Brush(string name, Vec3 center, Vec3 size, Mat4 rotation, Operation operation)
{
var mesh = Cube(size);
var meshId = CreateBrushMesh(mesh, name.GetHashCode());
if (meshId == 0 || !Native.GenerateBrush(name.GetHashCode(), out var brush))
throw new InvalidOperationException($"Failed to create brush {name}: meshId={meshId}");
if (!Native.IsBrushMeshIDValid(meshId))
throw new InvalidOperationException($"Native rejected brush mesh {name}/{meshId}");
var transform = rotation.WithTranslation(center);
if (!Native.SetNodeLocalTransformation(brush, ref transform))
throw new InvalidOperationException($"SetNodeLocalTransformation failed for brush {name}/{brush}");
if (!Native.SetBrushMeshID(brush, meshId))
throw new InvalidOperationException($"SetBrushMeshID failed for brush {name}/{brush}, mesh={meshId}");
if (operation != Operation.Additive && !Native.SetBrushOperationType(brush, operation))
throw new InvalidOperationException($"SetBrushOperationType failed for brush {name}/{brush}, op={operation}");
if (Native.GetBrushMeshID(brush) != meshId)
throw new InvalidOperationException($"Brush {name}/{brush} did not retain mesh id {meshId}");
Native.SetDirty(brush);
return brush;
}
static bool InsertChildNodes(int node, int[] children)
{
var handle = GCHandle.Alloc(children, GCHandleType.Pinned);
var ok = Native.InsertChildNodeRange(node, 0, children.Length, handle.AddrOfPinnedObject());
handle.Free();
return ok;
}
static int CreateBrushMesh(BrushMesh mesh, int userId)
{
var vh = GCHandle.Alloc(mesh.Vertices, GCHandleType.Pinned);
var eh = GCHandle.Alloc(mesh.HalfEdges, GCHandleType.Pinned);
var ph = GCHandle.Alloc(mesh.Polygons, GCHandleType.Pinned);
var id = Native.CreateBrushMesh(userId, mesh.Vertices.Length, vh.AddrOfPinnedObject(), mesh.HalfEdges.Length, eh.AddrOfPinnedObject(), mesh.Polygons.Length, ph.AddrOfPinnedObject());
ph.Free();
eh.Free();
vh.Free();
return id;
}
static BrushMesh Cube(Vec3 size)
{
var min = size * -0.5f;
var max = size * 0.5f;
var layers = new SurfaceLayers { LayerUsage = LayerUsageFlags.RenderReceiveCastShadows | LayerUsageFlags.Collidable };
var surfaces = new[]
{
new SurfaceDescription { UV0 = new UvMatrix(new Vec4(-1, 0, 0, -min.X), new Vec4(0, 1, 0, min.Y)) },
new SurfaceDescription { UV0 = new UvMatrix(new Vec4( 1, 0, 0, min.X), new Vec4(0, 1, 0, min.Y)) },
new SurfaceDescription { UV0 = new UvMatrix(new Vec4( 0, 0, 1, min.Z), new Vec4(0, 1, 0, min.Y)) },
new SurfaceDescription { UV0 = new UvMatrix(new Vec4( 0, 0, -1, -min.Z), new Vec4(0, 1, 0, min.Y)) },
new SurfaceDescription { UV0 = new UvMatrix(new Vec4(-1, 0, 0, -min.X), new Vec4(0, 0, -1, -min.Z)) },
new SurfaceDescription { UV0 = new UvMatrix(new Vec4( 1, 0, 0, min.X), new Vec4(0, 0, -1, -min.Z)) },
};
return new BrushMesh(
new[]
{
new Vec3(min.X, min.Y, min.Z), new Vec3(min.X, max.Y, min.Z),
new Vec3(max.X, max.Y, min.Z), new Vec3(max.X, min.Y, min.Z),
new Vec3(min.X, min.Y, max.Z), new Vec3(max.X, min.Y, max.Z),
new Vec3(max.X, max.Y, max.Z), new Vec3(min.X, max.Y, max.Z),
},
new[]
{
new HalfEdge(0,17), new HalfEdge(1,8), new HalfEdge(2,20), new HalfEdge(3,13),
new HalfEdge(4,10), new HalfEdge(5,19), new HalfEdge(6,15), new HalfEdge(7,22),
new HalfEdge(0,1), new HalfEdge(4,16), new HalfEdge(7,4), new HalfEdge(1,21),
new HalfEdge(3,18), new HalfEdge(2,3), new HalfEdge(6,23), new HalfEdge(5,6),
new HalfEdge(0,9), new HalfEdge(3,0), new HalfEdge(5,12), new HalfEdge(4,5),
new HalfEdge(1,2), new HalfEdge(7,11), new HalfEdge(6,7), new HalfEdge(2,14),
},
new[]
{
new Polygon(0,4,0,surfaces[0],layers), new Polygon(4,4,1,surfaces[1],layers),
new Polygon(8,4,2,surfaces[2],layers), new Polygon(12,4,3,surfaces[3],layers),
new Polygon(16,4,4,surfaces[4],layers), new Polygon(20,4,5,surfaces[5],layers),
});
}
record PerfCase(string Name, int Brushes, Func<int> Build);
record BrushMesh(Vec3[] Vertices, HalfEdge[] HalfEdges, Polygon[] Polygons);
enum Operation : byte { Additive = 0, Subtractive = 1, Intersecting = 2 }
enum BrushFlags : int { Default = 0, Infinite = 1 }
enum VertexChannelFlags : byte { Position = 0, Tangent = 2, Normal = 4, UV0 = 8, All = 14 }
enum LayerUsageFlags : int
{
None = 0,
Renderable = 1,
CastShadows = 2,
ReceiveShadows = 4,
RenderCastShadows = Renderable | CastShadows,
RenderReceiveShadows = Renderable | ReceiveShadows,
RenderReceiveCastShadows = Renderable | CastShadows | ReceiveShadows,
Collidable = 8,
Culled = 1 << 23,
}
enum LayerParameterIndex : byte { None = 0, LayerParameter1 = 1, LayerParameter2 = 2, LayerParameter3 = 3 }
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct Vec2 { public float X, Y; }
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct Vec3(float x, float y, float z)
{
public float X = x;
public float Y = y;
public float Z = z;
public static Vec3 Zero => new(0, 0, 0);
public static Vec3 Splat(float value) => new(value, value, value);
public static Vec3 operator +(Vec3 a, Vec3 b) => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
public static Vec3 operator *(Vec3 a, float s) => new(a.X * s, a.Y * s, a.Z * s);
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct Vec4(float x, float y, float z, float w) { public float X = x, Y = y, Z = z, W = w; }
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct Mat4
{
public float M00, M10, M20, M30;
public float M01, M11, M21, M31;
public float M02, M12, M22, M32;
public float M03, M13, M23, M33;
public static Mat4 Identity => new() { M00 = 1, M11 = 1, M22 = 1, M33 = 1 };
public static Mat4 RotationZ(float radians)
{
var c = MathF.Cos(radians);
var s = MathF.Sin(radians);
return new Mat4 { M00 = c, M10 = s, M01 = -s, M11 = c, M22 = 1, M33 = 1 };
}
public Mat4 WithTranslation(Vec3 t)
{
var m = this;
m.M03 = t.X; m.M13 = t.Y; m.M23 = t.Z; m.M33 = 1;
return m;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct UvMatrix(Vec4 u, Vec4 v) { public Vec4 U = u; public Vec4 V = v; }
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct SurfaceDescription
{
public uint SmoothingGroup;
public int SurfaceFlags;
public UvMatrix UV0;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct SurfaceLayers
{
public LayerUsageFlags LayerUsage;
public int LayerParameter1;
public int LayerParameter2;
public int LayerParameter3;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct Aabb
{
public float MinX, MaxX;
public float MinY, MaxY;
public float MinZ, MaxZ;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct Polygon(int firstEdge, int edgeCount, int polygonId, SurfaceDescription surface, SurfaceLayers layers)
{
public int FirstEdge = firstEdge;
public int EdgeCount = edgeCount;
public int PolygonId = polygonId;
public SurfaceDescription Surface = surface;
public SurfaceLayers Layers = layers;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct HalfEdge(int vertexIndex, int twinIndex)
{
public int VertexIndex = vertexIndex;
public int TwinIndex = twinIndex;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct MeshQuery
{
const int BitShift = 24;
public uint Layers;
public uint MaskAndChannels;
public static MeshQuery RenderableAll => new(LayerUsageFlags.Renderable, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All);
public static MeshQuery[] SimpleTypes => new[]
{
new MeshQuery(LayerUsageFlags.Renderable, LayerUsageFlags.Renderable, LayerParameterIndex.None, VertexChannelFlags.Position),
new MeshQuery(LayerUsageFlags.RenderReceiveCastShadows, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.None, VertexChannelFlags.Position),
new MeshQuery(LayerUsageFlags.Renderable, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.None, VertexChannelFlags.All),
new MeshQuery(LayerUsageFlags.RenderReceiveCastShadows, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
new MeshQuery(LayerUsageFlags.None, LayerUsageFlags.None, LayerParameterIndex.None, VertexChannelFlags.Position),
new MeshQuery(LayerUsageFlags.Culled, LayerUsageFlags.Culled, LayerParameterIndex.None, VertexChannelFlags.Position),
};
public static MeshQuery[] RenderOnlyTypes => new[]
{
new MeshQuery(LayerUsageFlags.CastShadows, LayerUsageFlags.RenderCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
new MeshQuery(LayerUsageFlags.Renderable, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
new MeshQuery(LayerUsageFlags.RenderCastShadows, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
new MeshQuery(LayerUsageFlags.RenderReceiveShadows, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
new MeshQuery(LayerUsageFlags.RenderReceiveCastShadows, LayerUsageFlags.RenderReceiveCastShadows, LayerParameterIndex.LayerParameter1, VertexChannelFlags.All),
new MeshQuery(LayerUsageFlags.None, LayerUsageFlags.Renderable, LayerParameterIndex.None, VertexChannelFlags.Position),
new MeshQuery(LayerUsageFlags.CastShadows, LayerUsageFlags.None, LayerParameterIndex.None, VertexChannelFlags.Position),
new MeshQuery(LayerUsageFlags.ReceiveShadows, LayerUsageFlags.None, LayerParameterIndex.None, VertexChannelFlags.Position),
new MeshQuery(LayerUsageFlags.Culled, LayerUsageFlags.None, LayerParameterIndex.None, VertexChannelFlags.Position),
};
public MeshQuery(LayerUsageFlags query, LayerUsageFlags mask, LayerParameterIndex parameterIndex, VertexChannelFlags vertexChannels)
{
if (mask == LayerUsageFlags.None)
mask = query;
Layers = (uint)query;
Layers |= (uint)parameterIndex << BitShift;
MaskAndChannels = (uint)mask | ((uint)vertexChannels << BitShift);
}
public int UsedVertexChannels => (int)(MaskAndChannels >> BitShift);
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
struct GeneratedMeshDescription
{
public MeshQuery MeshQuery;
public int SurfaceParameter;
public int MeshQueryIndex;
public int SubMeshQueryIndex;
public long GeometryHashValue;
public long SurfaceHashValue;
public int VertexCount;
public int IndexCount;
}
static partial class Native
{
const string Dll = "RealtimeCSG[1_559]";
static readonly StringLog Log = (message, userId) => Console.Error.WriteLine($"native[{userId}]: {message}");
static readonly ReturnString NameForUserId = _ => "<native-bridge>";
public static void RegisterNoopUnityMethods()
{
var methods = new UnityMethods
{
DebugLog = Log,
DebugLogError = Log,
DebugLogWarning = Log,
NameForUserID = NameForUserId,
};
RegisterMethods(ref methods);
}
public delegate void StringLog([MarshalAs(UnmanagedType.LPStr)] string text, int uniqueObjectID);
[return: MarshalAs(UnmanagedType.LPStr)]
public delegate string ReturnString(int uniqueObjectID);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 4)]
struct UnityMethods
{
public StringLog DebugLog;
public StringLog DebugLogError;
public StringLog DebugLogWarning;
public ReturnString NameForUserID;
}
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] static extern void RegisterMethods(ref UnityMethods unityMethods);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern void ClearAllNodes();
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool UpdateAllTreeMeshes();
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern void RebuildAll();
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetNodeCount();
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetBrushCount();
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetTreeCount();
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetBrushMeshCount();
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GenerateTree(int userID, out int generatedTreeNodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetNumberOfBrushesInTree(int nodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetDirty(int nodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool ClearDirty(int nodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool IsNodeDirty(int nodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetChildNodeCount(int nodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetChildNodeAtIndex(int nodeID, int index);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetTreeOfNode(int nodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern byte GetTypeOfNode(int nodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetTreeEnabled(int modelNodeID, bool isEnabled);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetTreeEnabled(int modelNodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GenerateBrush(int userID, out int generatedNodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GenerateBranch(int userID, out int generatedOperationNodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetBranchOperationType(int branchNodeID, Operation operation);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetBrushOperationType(int brushNodeID, Operation operation);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetBrushFlags(int brushNodeID, BrushFlags flags);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetBrushOperationType(int brushNodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetBrushMeshID(int brushNodeID, int brushMeshID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int GetBrushMeshID(int brushNodeID);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetBrushBounds(int brushNodeID, ref Aabb bounds);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetBrushOutlineSizes(int brushNodeID, out int vertexCount, out int visibleOuterLineCount, out int visibleInnerLineCount, out int invisibleOuterLineCount, out int invisibleInnerLineCount, out int invalidLineCount);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetNodeLocalTransformation(int brushNodeID, ref Mat4 brushToTreeSpace);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool SetChildNodes(int nodeID, int childCount, IntPtr childrenNodeIDs);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool InsertChildNodeRange(int nodeID, int index, int childCount, IntPtr childrenNodeIDs);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int CreateBrushMesh(int userID, int vertexCount, IntPtr vertices, int halfEdgeCount, IntPtr halfEdges, int polygonCount, IntPtr polygons);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool UpdateBrushMesh(int brushMeshIndex, int vertexCount, IntPtr vertices, int halfEdgeCount, IntPtr halfEdges, int polygonCount, IntPtr polygons);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool IsBrushMeshIDValid(int brushMeshIndex);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GenerateMeshDescriptions(int treeNodeID, int meshTypeCount, IntPtr meshTypes, VertexChannelFlags vertexChannelMask, out int meshDescriptionCount);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetMeshDescriptions(int treeNodeID, int meshDescriptionCount, IntPtr meshDescriptions);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern bool GetGeneratedMesh(int treeNodeID, int meshIndex, int subMeshIndex, int indexCount, IntPtr indices, int vertexCount, IntPtr positions, IntPtr tangents, IntPtr normals, IntPtr uvs, out Vec3 boundsCenter, out Vec3 boundsSize);
[DllImport(Dll, CallingConvention = CallingConvention.Cdecl)] public static extern int RayCastIntoTreeMultiCount(int modelNodeID, ref Vec3 worldRayStart, ref Vec3 worldRayEnd, ref Mat4 modelLocalToWorldMatrix, int inFilterLayerParameter0, bool ignoreInvisiblePolygons, IntPtr ignoreNodeIds, int ignoreNodeIdCount);
}