const std = @import("std"); const builtin = @import("builtin"); const t = std.testing; const Vec2 = @Vector(2, f32); const zm = @import("zmath"); const RbTree = @import("data_structures/rb_tree.zig").RbTree; inline fn cross(v1: Vec2, v2: Vec2) f32 { return v1[0] * v2[1] - v1[1] * v2[0]; } pub fn dassert(pred: bool) void { if (builtin.mode == .Debug) { std.debug.assert(pred); } } const CompactSinglyLinkedListBuffer = @import("data_structures/compact.zig").CompactSinglyLinkedListBuffer; const trace = @import("tracy.zig").trace; const log_ = std.log.scoped(.tessellator); const DeferredVertexNodeId = u16; const NullId = @import("data_structures/compact.zig").CompactNull(DeferredVertexNodeId); const EventQueue = std.PriorityQueue(u32, *std.ArrayList(Event), compareEventIdx); const debug = false and builtin.mode == .Debug; pub fn log(comptime format: []const u8, args: anytype) void { if (debug) { log_.debug(format, args); } } pub const Tessellator = struct { /// Buffers. verts: std.ArrayList(InternalVertex), events: std.ArrayList(Event), event_q: EventQueue, sweep_edges: RbTree(u16, SweepEdge, Event, compareSweepEdge), deferred_verts: CompactSinglyLinkedListBuffer(DeferredVertexNodeId, DeferredVertexNode), /// Verts to output. /// No duplicate verts will be outputed to reduce size footprint. /// Since some verts won't be discovered until during the processing of events (edge intersections), /// verts are added as the events are processed. As a result, the verts are also in order y-asc and x-asc. out_verts: std.ArrayList(Vec2), /// Triangles to output are triplets of indexes that point to verts. They are in ccw direction. out_idxes: std.ArrayList(u16), cur_x: f32, cur_y: f32, cur_out_vert_idx: u16, cur_polys: []const []const Vec2, const Self = @This(); pub fn init(self: *Self, alloc: std.mem.Allocator) void { self.* = .{ .verts = std.ArrayList(InternalVertex).init(alloc), .events = std.ArrayList(Event).init(alloc), .event_q = undefined, .sweep_edges = RbTree(u16, SweepEdge, Event, compareSweepEdge).init(alloc, undefined), .deferred_verts = CompactSinglyLinkedListBuffer(DeferredVertexNodeId, DeferredVertexNode).init(alloc), .out_verts = std.ArrayList(Vec2).init(alloc), .out_idxes = std.ArrayList(u16).init(alloc), .cur_x = undefined, .cur_y = undefined, .cur_out_vert_idx = undefined, .cur_polys = undefined, }; self.event_q = EventQueue.init(alloc, &self.events); } pub fn deinit(self: *Self) void { self.verts.deinit(); self.events.deinit(); self.event_q.deinit(); self.sweep_edges.deinit(); self.deferred_verts.deinit(); self.out_verts.deinit(); self.out_idxes.deinit(); } pub fn clearBuffers(self: *Self) void { self.verts.clearRetainingCapacity(); self.events.clearRetainingCapacity(); self.event_q.len = 0; self.sweep_edges.clearRetainingCapacity(); self.deferred_verts.clearRetainingCapacity(); self.out_verts.clearRetainingCapacity(); self.out_idxes.clearRetainingCapacity(); } pub fn triangulatePolygon(self: *Self, polygon: []const Vec2) void { self.triangulatePolygons(&.{polygon}); } /// Perform a plane sweep to triangulate a complex polygon in one pass. /// The output returns ccw triangle vertices and indexes ready to be fed into the gpu. /// This uses Bentley-Ottmann to handle self intersecting edges. /// Rules are followed to partition into y-monotone polygons and triangulate them. /// This is ported from the JS implementation (tessellator.js) where it is easier to prototype. /// Since the number of verts and indexes is not known beforehand, the output is an ArrayList. /// TODO: See if inline callbacks would be faster to directly push data to the batcher buffer. pub fn triangulatePolygons(self: *Self, polygons: []const []const Vec2) void { // Construct the initial events by traversing the polygon. self.initEvents(polygons); self.cur_x = std.math.f32_min; self.cur_y = std.math.f32_min; self.cur_out_vert_idx = std.math.maxInt(u16); self.cur_polys = polygons; // Process events. while (self.event_q.removeOrNull()) |e_id| { self.processEvent(e_id); } } /// Initializes the events only. pub fn debugTriangulatePolygons(self: *Self, polygons: []const []const Vec2) void { self.initEvents(polygons); self.cur_x = std.math.f32_min; self.cur_y = std.math.f32_min; self.cur_out_vert_idx = std.math.maxInt(u16); self.cur_polys = polygons; } /// Process the next event. This can be used with debugTriangulatePolygons. pub fn debugProcessNext(self: *Self, alloc: std.mem.Allocator) ?DebugTriangulateStepResult { const e_id = self.event_q.removeOrNull() orelse return null; self.processEvent(e_id); return DebugTriangulateStepResult{ .has_result = true, .event = self.events.items[e_id], .sweep_edges = self.sweep_edges.allocValuesInOrder(alloc), .out_verts = alloc.dupe(Vec2, self.out_verts.items) catch unreachable, .out_idxes = alloc.dupe(u16, self.out_idxes.items) catch unreachable, .verts = alloc.dupe(InternalVertex, self.verts.items) catch unreachable, .deferred_verts = alloc.dupe(CompactSinglyLinkedListBuffer(DeferredVertexNodeId, DeferredVertexNode).Node, self.deferred_verts.nodes.data.items) catch unreachable, }; } fn processEvent(self: *Self, e_id: u32) void { const t_ = trace(@src()); defer t_.end(); const sweep_edges = &self.sweep_edges; const e = self.events.items[e_id]; // If the point changed, allocate a new out vertex index. if (e.vert_x != self.cur_x or e.vert_y != self.cur_y) { self.out_verts.append(Vec2{ e.vert_x, e.vert_y }) catch unreachable; self.cur_out_vert_idx +%= 1; self.cur_x = e.vert_x; self.cur_y = e.vert_y; } // Set the out vertex index on the event. self.verts.items[e.vert_idx].out_idx = self.cur_out_vert_idx; if (debug) { const tag_str: []const u8 = if (e.tag == .Start) "Start" else "End"; log("--process event {}, ({},{}) {s} {s}", .{ e.vert_idx, e.vert_x, e.vert_y, tag_str, edgeToString(e.edge) }); log("sweep edges: ", .{}); var mb_cur = sweep_edges.first(); while (mb_cur) |cur| { const se = sweep_edges.get(cur).?; log("{} -> {}", .{ se.edge.start_idx, se.edge.end_idx }); mb_cur = sweep_edges.getNext(cur); } } if (e.tag == .Start) { // Check to remove a previous left edge and continue it's sub-polygon vertex queue. sweep_edges.ctx = e; const new_id = sweep_edges.insert(SweepEdge.init(e, self.verts.items)) catch unreachable; const new = sweep_edges.getPtr(new_id).?; log("start new sweep edge {}", .{new_id}); var mb_left_id = sweep_edges.getPrev(new_id); // Update winding based on what is to the left. if (mb_left_id == null) { new.interior_is_left = false; } else { new.interior_is_left = !sweep_edges.get(mb_left_id.?).?.interior_is_left; } if (new.interior_is_left) { log("initially interior to the left", .{}); // Initially appears to be a right edge but if it connects to the previous left, it becomes a left edge. var left = sweep_edges.getPtrNoCheck(mb_left_id.?); const e_vert_out_idx = self.verts.items[e.vert_idx].out_idx; if (left.end_event_vert_uniq_idx == e_vert_out_idx) { // Remove the previous ended edge, and takes it's place as the left edge. defer sweep_edges.remove(mb_left_id.?) catch unreachable; new.interior_is_left = false; log("continuation from prev edge, interior to the right", .{}); // Previous left edge and this new edge forms a regular left angle. // Check for bad up cusp. if (left.bad_up_cusp_uniq_idx != NullId) { // This monotone polygon (a) should already have run it's triangulate steps from the left edge's end event. // \ / // a \/ b // ^ Bad cusp. // \_ // ^ End event from left edge happened before, currently processing start event for the new connected edge. // A line is connected from this vertex to the bad cusp to ensure that polygon (a) is monotone and polygon (b) is monotone. // Since polygon (a) already ran it's triangulate step, it's done from this side of the polygon. // This start event will create a new sweep edge, so transfer the deferred queue from the bad cusp's right side. (it is now the queue for this new left edge). const bad_right = sweep_edges.getNoCheck(left.bad_up_cusp_right_sweep_edge_id); bad_right.dumpQueue(self); new.deferred_queue = bad_right.deferred_queue; new.deferred_queue_size = bad_right.deferred_queue_size; new.cur_side = bad_right.cur_side; // Also run triangulate on polygon (b) for the new vertex since the end event was already run for polygon (a). self.triangulateLeftStep(new, self.verts.items[e.vert_idx]); left.bad_up_cusp_uniq_idx = NullId; sweep_edges.removeDetached(left.bad_up_cusp_right_sweep_edge_id); log("FIX BAD UP CUSP", .{}); new.dumpQueue(self); } else { new.deferred_queue = left.deferred_queue; new.deferred_queue_size = left.deferred_queue_size; new.cur_side = left.cur_side; } } else if (left.start_event_vert_uniq_idx == e_vert_out_idx) { // Down cusp. log("DOWN CUSP", .{}); } } else { log("initially interior to the right", .{}); // Initially appears to be a left edge but if it connects to a previous right, it becomes a right edge. if (mb_left_id != null) { const vert = self.verts.items[e.vert_idx]; // left edge has interior to the left. var left = sweep_edges.get(mb_left_id.?).?; if (left.end_event_vert_uniq_idx == vert.out_idx) { // Remove previous ended edge. sweep_edges.remove(mb_left_id.?) catch unreachable; new.interior_is_left = true; log("changed to interior is left", .{}); } else if (left.start_event_vert_uniq_idx == vert.out_idx) { // Linked to previous start event's vertex. // Handle bad down cusp. // \ \/ // \ b // \--a // \ v Bad down cusp. // \ /\ / // \ / \ / // The bad cusp is linked to the lowest visible vertex seen from the left edge. If vertex (a) exists that would be connected to the cusp. // If it didn't exist the next lowest would be (b) a bad up cusp. const left_left_id = sweep_edges.getPrev(mb_left_id.?).?; const left_left = sweep_edges.getPtrNoCheck(left_left_id); log("handle bad down cusp {}", .{left_left.lowest_right_vert_idx}); if (left_left.lowest_right_vert_idx == NullId) { log("expected lowest right vert", .{}); unreachable; } const low_poly_edge = sweep_edges.getPtrNoCheck(left_left.lowest_right_vert_sweep_edge_id); if (left_left_id == left_left.lowest_right_vert_sweep_edge_id) { // Lowest right point does NOT have a right side monotone polygon. if (left_left.lowest_right_vert_idx == left_left.vert_idx) { // Pass on the vertex queue. new.deferred_queue = low_poly_edge.deferred_queue; new.deferred_queue_size = low_poly_edge.deferred_queue_size; new.cur_side = low_poly_edge.cur_side; low_poly_edge.deferred_queue = NullId; low_poly_edge.deferred_queue_size = 0; self.triangulateLeftStep(new, vert); // Cut off the existing left monotone polygon. left_left.deferred_queue = NullId; left_left.deferred_queue_size = 0; left_left.cur_side = .Right; left_left.enqueueDeferred(self.verts.items[left_left.lowest_right_vert_idx], self); if (vert.out_idx != self.verts.items[left_left.lowest_right_vert_idx].out_idx) { left_left.enqueueDeferred(vert, self); } left_left.lowest_right_vert_idx = e.vert_idx; } else { // Initialize a new right side monotone polygon. new.enqueueDeferred(self.verts.items[left_left.lowest_right_vert_idx], self); new.enqueueDeferred(vert, self); new.cur_side = .Left; // Triangulate on the monotone polygon to the left of the lowest right point. self.triangulateRightStep(left_left, vert); left_left.lowest_right_vert_idx = e.vert_idx; } } else { // Lowest right point does have a right side monotone polygon. // Most likely a bad up cusp as well, so reset it since connecting to the lowest right fixes it. left_left.bad_up_cusp_uniq_idx = NullId; self.triangulateLeftStep(low_poly_edge, vert); // Pass on the vertex queue. new.deferred_queue = low_poly_edge.deferred_queue; new.deferred_queue_size = low_poly_edge.deferred_queue_size; new.cur_side = low_poly_edge.cur_side; low_poly_edge.deferred_queue = NullId; low_poly_edge.deferred_queue_size = 0; // Triangulate on the monotone polygon to the left of the lowest right point. self.triangulateRightStep(left_left, vert); left_left.lowest_right_vert_idx = e.vert_idx; left_left.lowest_right_vert_sweep_edge_id = left_left_id; } } } } if (!new.interior_is_left) { // Even-odd rule. // Interior is to the right. const vert = self.verts.items[e.vert_idx]; // Initialize the deferred queue. if (new.deferred_queue == NullId) { new.enqueueDeferred(vert, self); new.cur_side = .Left; log("initialize queue", .{}); new.dumpQueue(self); } // The lowest right vert is set initializes to itself. new.lowest_right_vert_idx = e.vert_idx; new.lowest_right_vert_sweep_edge_id = new_id; } else { // Interior is to the left. } // Check intersection with the left. Fetch left again since it could be removed from an up cusp. mb_left_id = sweep_edges.getPrev(new_id); if (mb_left_id != null) { log("check left intersect", .{}); var left = sweep_edges.getPtrNoCheck(mb_left_id.?); const res = computeTwoEdgeIntersect(new.edge, left.edge); log("{},{}->{},{} {},{}->{},{} {} {}", .{ new.edge.start_pos[0], new.edge.start_pos[1], new.edge.end_pos[0], new.edge.end_pos[1], left.edge.start_pos[0], left.edge.start_pos[1], left.edge.end_pos[0], left.edge.end_pos[1], res.has_intersect, res.t }); if (res.has_intersect and res.t > 0 and res.t < 1) { self.handleIntersectForStartEvent(new, left, res, Vec2{ e.vert_x, e.vert_y }); } } const mb_right_id = sweep_edges.getNext(new_id); if (mb_right_id != null) { // Check for intersection with the edge to the right. log("check right intersect", .{}); // TODO: Is there a preliminary check to avoid doing the math? One idea is to check the x_slopes but it would need to know // if the compared edge is pointing down or up. const right = sweep_edges.getPtrNoCheck(mb_right_id.?); const res = computeTwoEdgeIntersect(new.edge, right.edge); if (res.has_intersect and res.t > 0 and res.t < 1) { self.handleIntersectForStartEvent(new, right, res, Vec2{ e.vert_x, e.vert_y }); } } } else { // End event. if (e.invalidated) { // This end event was invalidated from an intersection event. return; } const active_id = findSweepEdgeForEndEvent(sweep_edges, e) orelse { log("polygons: {any}", .{self.cur_polys}); @panic("expected active edge"); }; const active = sweep_edges.getPtrNoCheck(active_id); log("active {} {}", .{ active.vert_idx, active.to_vert_idx }); const vert = self.verts.items[e.vert_idx]; if (active.interior_is_left) { // Interior is to the left. log("interior to the left {}", .{active_id}); const left_id = sweep_edges.getPrev(active_id).?; const left = sweep_edges.getPtrNoCheck(left_id); // Check if it has closed the polygon. (up cusp) if (vert.out_idx == left.end_event_vert_uniq_idx) { // Check for bad up cusp. if (left.bad_up_cusp_uniq_idx != NullId) { const bad_right = sweep_edges.getPtrNoCheck(left.bad_up_cusp_right_sweep_edge_id); // Close off monotone polygon to the right of the bad up cusp. self.triangulateLeftStep(bad_right, vert); sweep_edges.removeDetached(left.bad_up_cusp_right_sweep_edge_id); left.bad_up_cusp_uniq_idx = NullId; left.lowest_right_vert_idx = e.vert_idx; left.lowest_right_vert_sweep_edge_id = left_id; if (bad_right.deferred_queue_size >= 3) { log("{any}", .{self.out_idxes.items}); bad_right.dumpQueue(self); @panic("did not expect left over vertices"); } } if (left.deferred_queue_size >= 3) { log("{} {any}", .{ left_id, self.out_idxes.items }); left.dumpQueue(self); @panic("did not expect left over vertices"); } // Remove the left edge and this edge. sweep_edges.remove(left_id) catch unreachable; sweep_edges.remove(active_id) catch unreachable; } else { // Regular right side vertex. // Check for bad up cusp. if (left.bad_up_cusp_uniq_idx != NullId) { const bad_right = sweep_edges.getPtrNoCheck(left.bad_up_cusp_right_sweep_edge_id); // Close off monotone polygon to the right of the bad up cusp. self.triangulateRightStep(bad_right, vert); left.lowest_right_vert_idx = vert.idx; left.lowest_right_vert_sweep_edge_id = left_id; sweep_edges.removeDetached(left.bad_up_cusp_right_sweep_edge_id); left.bad_up_cusp_uniq_idx = NullId; if (bad_right.deferred_queue_size >= 3) { log("{any}", .{self.out_idxes.items}); bad_right.dumpQueue(self); @panic("did not expect left over vertices"); } } // Left belongs to the same monotone polygon. self.triangulateRightStep(left, vert); // Edge is only removed by the next connecting edge. active.end_event_vert_uniq_idx = vert.out_idx; } } else { // Interior is to the right. log("interior to the right {}", .{active_id}); const mb_left_id = sweep_edges.getPrev(active_id); // Check to fix a bad up cusp to the right. if (active.bad_up_cusp_uniq_idx != NullId) { const bad_right = sweep_edges.getPtrNoCheck(active.bad_up_cusp_right_sweep_edge_id); // Close off monotone polygon in between this active edge and the bad up cusp. self.triangulateLeftStep(active, vert); if (active.deferred_queue_size >= 3) { log("{any}", .{self.out_idxes.items}); active.dumpQueue(self); @panic("did not expect left over vertices"); } active.lowest_right_vert_idx = NullId; active.lowest_right_vert_sweep_edge_id = NullId; sweep_edges.removeDetached(active.bad_up_cusp_right_sweep_edge_id); active.bad_up_cusp_uniq_idx = NullId; // Extend the monotone polygon to the right of the bad up cusp to this vertex. self.triangulateLeftStep(bad_right, vert); active.deferred_queue = bad_right.deferred_queue; active.deferred_queue_size = bad_right.deferred_queue_size; } else { active.dumpQueue(self); self.triangulateLeftStep(active, vert); active.dumpQueue(self); } // Check if this forms a bad up cusp with the right edge to the left monotone polygon. var removed = false; if (mb_left_id != null) { log("check to set bad up cusp", .{}); const left = sweep_edges.getNoCheck(mb_left_id.?); // dump(edgeToString(left_right_edge.edge)) if (vert.out_idx == left.end_event_vert_uniq_idx) { const left_left_id = sweep_edges.getPrev(mb_left_id.?).?; const left_left = sweep_edges.getPtrNoCheck(left_left_id); // Bad up cusp. left_left.bad_up_cusp_uniq_idx = vert.out_idx; left_left.bad_up_cusp_right_sweep_edge_id = active_id; left_left.lowest_right_vert_idx = e.vert_idx; left_left.lowest_right_vert_sweep_edge_id = active_id; // Remove the left edge. sweep_edges.remove(mb_left_id.?) catch unreachable; // Detach this edge, remove it when the bad up cusp is fixed. sweep_edges.detach(active_id) catch unreachable; removed = true; // Continue. } } if (!removed) { // Don't remove the left edge of this sub-polygon yet. // Record the end event's vert so the next start event that continues from this vert can persist the deferred vertices and remove this sweep edge. // It can also be removed by a End right edge. active.end_event_vert_uniq_idx = vert.out_idx; } } } } inline fn addTriangle(self: *Self, v1_out: u16, v2_out: u16, v3_out: u16) void { log("triangle {} {} {}", .{ v1_out, v2_out, v3_out }); self.out_idxes.appendSlice(&.{ v1_out, v2_out, v3_out }) catch unreachable; } /// Parses the polygon pts and adds the initial events into the priority queue. fn initEvents(self: *Self, polygons: []const []const Vec2) void { const t_ = trace(@src()); defer t_.end(); for (polygons) |polygon| { // Find the starting point that is not equal to the last vertex point. // Since we are adding events to a priority queue, we need to make sure each add is final. var start_idx: u16 = 0; const last_pt = polygon[polygon.len - 1]; while (start_idx < polygon.len) : (start_idx += 1) { const pt = polygon[start_idx]; // Add internal vertex even though we are skipping events for it to keep the idxes consistent with the input. const v = InternalVertex{ .pos = pt, .idx = start_idx, }; self.verts.append(v) catch unreachable; if (@fabs(last_pt[0] - pt[0]) > 1e-4 or @fabs(last_pt[1] - pt[1]) > 1e-4) { break; } else { self.verts.items[self.verts.items.len - 1].idx = NullId; } } var last_v = self.verts.items[start_idx]; var last_v_idx = start_idx; var i: u16 = start_idx + 1; while (i < polygon.len) : (i += 1) { const v_idx = @intCast(u16, self.verts.items.len); const v = InternalVertex{ .pos = polygon[i], .idx = i, }; self.verts.append(v) catch unreachable; if (@fabs(last_v.pos[0] - v.pos[0]) < 1e-4 and @fabs(last_v.pos[1] - v.pos[1]) < 1e-4) { // Don't connect two vertices that are on top of each other. // Allowing this would require edge cases during event processing to make sure things don't break. // Push the vertex in anyway so there is consistency with the input. self.verts.items[self.verts.items.len - 1].idx = NullId; continue; } const prev_edge = Edge.init(last_v_idx, last_v, v_idx, v); const event1_idx = @intCast(u32, self.events.items.len); var event1: Event = undefined; var event2: Event = undefined; if (Event.isStartEvent(last_v_idx, prev_edge, self.verts.items)) { event1 = Event.init(last_v, prev_edge, .Start); event2 = Event.init(v, prev_edge, .End); event1.end_event_idx = event1_idx + 1; } else { event1 = Event.init(last_v, prev_edge, .End); event2 = Event.init(v, prev_edge, .Start); event2.end_event_idx = event1_idx; } self.events.append(event1) catch unreachable; self.event_q.add(event1_idx) catch unreachable; self.events.append(event2) catch unreachable; self.event_q.add(event1_idx + 1) catch unreachable; last_v = v; last_v_idx = v_idx; } // Link last pt to start pt. const edge = Edge.init(last_v_idx, last_v, start_idx, self.verts.items[start_idx]); const event1_idx = @intCast(u32, self.events.items.len); var event1: Event = undefined; var event2: Event = undefined; if (Event.isStartEvent(last_v_idx, edge, self.verts.items)) { event1 = Event.init(last_v, edge, .Start); event2 = Event.init(self.verts.items[start_idx], edge, .End); event1.end_event_idx = event1_idx + 1; } else { event1 = Event.init(last_v, edge, .End); event2 = Event.init(self.verts.items[start_idx], edge, .Start); event2.end_event_idx = event1_idx; } self.events.append(event1) catch unreachable; self.event_q.add(event1_idx) catch unreachable; self.events.append(event2) catch unreachable; self.event_q.add(event1_idx + 1) catch unreachable; } } fn triangulateLeftStep(self: *Self, left: *SweepEdge, vert: InternalVertex) void { if (left.cur_side == .Left) { log("same left side", .{}); left.dumpQueue(self); // Same side. if (left.deferred_queue_size >= 2) { var last_id = left.deferred_queue; var last = self.deferred_verts.getNoCheck(last_id); if (last.vert_out_idx == vert.out_idx) { // Ignore this point since it is the same as the last. return; } var cur_id = self.deferred_verts.getNextNoCheck(last_id); var i: u16 = 0; while (i < left.deferred_queue_size - 1) : (i += 1) { log("check to add inward tri {} {}", .{ last_id, cur_id }); const cur = self.deferred_verts.getNoCheck(cur_id); const cxp = cross(Vec2{ last.vert_x - cur.vert_x, last.vert_y - cur.vert_y }, Vec2{ vert.pos[0] - last.vert_x, vert.pos[1] - last.vert_y }); if (cxp < 0) { // Bends inwards. Fill triangles until we aren't bending inward. self.addTriangle(vert.out_idx, cur.vert_out_idx, last.vert_out_idx); self.deferred_verts.removeAssumeNoPrev(last_id) catch unreachable; } else { break; } last_id = cur_id; last = cur; cur_id = self.deferred_verts.getNextNoCheck(cur_id); } if (i > 0) { const d_vert = self.deferred_verts.insertBeforeHeadNoCheck(last_id, DeferredVertexNode.init(vert)) catch unreachable; left.deferred_queue = d_vert; left.deferred_queue_size = left.deferred_queue_size - i + 1; } else { left.enqueueDeferred(vert, self); } } else { left.enqueueDeferred(vert, self); } } else { log("changed to left side", .{}); // Changed to left side. // Automatically create queue size - 1 triangles. var last_id = left.deferred_queue; var last = self.deferred_verts.getNoCheck(last_id); var cur_id = self.deferred_verts.getNextNoCheck(last_id); var i: u32 = 0; while (i < left.deferred_queue_size - 1) : (i += 1) { const cur = self.deferred_verts.getNoCheck(cur_id); self.addTriangle(vert.out_idx, last.vert_out_idx, cur.vert_out_idx); last_id = cur_id; last = cur; cur_id = self.deferred_verts.getNextNoCheck(cur_id); // Delete last after it's assigned to current. self.deferred_verts.removeAssumeNoPrev(last_id) catch unreachable; } left.dumpQueue(self); self.deferred_verts.getNodePtrNoCheck(left.deferred_queue).next = NullId; left.deferred_queue_size = 1; left.enqueueDeferred(vert, self); left.cur_side = .Left; left.dumpQueue(self); } } fn triangulateRightStep(self: *Self, left: *SweepEdge, vert: InternalVertex) void { if (left.cur_side == .Right) { log("right side", .{}); // Same side. if (left.deferred_queue_size >= 2) { var last_id = left.deferred_queue; var last = self.deferred_verts.getNoCheck(last_id); if (last.vert_out_idx == vert.out_idx) { // Ignore this point since it is the same as the last. return; } var cur_id = self.deferred_verts.getNextNoCheck(last_id); var i: u16 = 0; while (i < left.deferred_queue_size - 1) : (i += 1) { const cur = self.deferred_verts.getNoCheck(cur_id); const cxp = cross(Vec2{ last.vert_x - cur.vert_x, last.vert_y - cur.vert_y }, Vec2{ vert.pos[0] - last.vert_x, vert.pos[1] - last.vert_y }); if (cxp > 0) { // Bends inwards. Fill triangles until we aren't bending inward. self.addTriangle(vert.out_idx, last.vert_out_idx, cur.vert_out_idx); self.deferred_verts.removeAssumeNoPrev(last_id) catch unreachable; } else { break; } last_id = cur_id; last = cur; cur_id = self.deferred_verts.getNextNoCheck(cur_id); } if (i > 0) { const d_vert = self.deferred_verts.insertBeforeHeadNoCheck(last_id, DeferredVertexNode.init(vert)) catch unreachable; left.deferred_queue = d_vert; left.deferred_queue_size = left.deferred_queue_size - i + 1; } else { left.enqueueDeferred(vert, self); } } else { left.enqueueDeferred(vert, self); } } else { log("changed to right side", .{}); var last_id = left.deferred_queue; var last = self.deferred_verts.getNoCheck(last_id); var cur_id = self.deferred_verts.getNextNoCheck(last_id); var i: u32 = 0; while (i < left.deferred_queue_size - 1) : (i += 1) { const cur = self.deferred_verts.getNoCheck(cur_id); self.addTriangle(vert.out_idx, cur.vert_out_idx, last.vert_out_idx); last_id = cur_id; last = cur; cur_id = self.deferred_verts.getNextNoCheck(cur_id); // Delete last after it's assigned to current. self.deferred_verts.removeAssumeNoPrev(last_id) catch unreachable; } self.deferred_verts.getNodePtrNoCheck(left.deferred_queue).next = NullId; left.deferred_queue_size = 1; left.enqueueDeferred(vert, self); left.cur_side = .Right; left.dumpQueue(self); } } /// Splits two edges at an intersect point. /// Assumes sweep edges have not processed their end events so they can be reinserted. /// Does not add new events if an event already exists to the intersect point. fn handleIntersectForStartEvent(self: *Self, sweep_edge_a: *SweepEdge, sweep_edge_b: *SweepEdge, intersect: IntersectResult, sweep_vert: Vec2) void { log("split intersect {}", .{intersect}); // The intersect point must lie after the sweep_vert. if (intersect.y < sweep_vert[1] or (intersect.y == sweep_vert[1] and intersect.x <= sweep_vert[0])) { return; } var added_events = false; // Create new intersect vertex. const intersect_idx = @intCast(u16, self.verts.items.len); const intersect_v = InternalVertex{ .pos = Vec2{ intersect.x, intersect.y }, .idx = intersect_idx, }; self.verts.append(intersect_v) catch unreachable; // TODO: Account for floating point error. const a_to_vert = self.verts.items[sweep_edge_a.to_vert_idx]; if (a_to_vert.pos[0] != intersect.x or a_to_vert.pos[1] != intersect.y) { // log(edgeToString(sweep_edge_a.edge)) log("adding edge a {} to {},{}", .{ sweep_edge_a.vert_idx, intersect.x, intersect.y }); added_events = true; // Invalidate sweep_edge_a's end event since the priority queue can not be modified. self.events.items[sweep_edge_a.end_event_idx].invalidated = true; // Keep original edge orientation when doing the split. var first_edge: Edge = undefined; var second_edge: Edge = undefined; const start = self.verts.items[sweep_edge_a.edge.start_idx]; const end = self.verts.items[sweep_edge_a.edge.end_idx]; if (sweep_edge_a.to_vert_idx == sweep_edge_a.edge.start_idx) { first_edge = Edge.init(intersect_idx, intersect_v, sweep_edge_a.edge.end_idx, end); second_edge = Edge.init(sweep_edge_a.edge.start_idx, start, intersect_idx, intersect_v); } else { first_edge = Edge.init(sweep_edge_a.edge.start_idx, start, intersect_idx, intersect_v); second_edge = Edge.init(intersect_idx, intersect_v, sweep_edge_a.edge.end_idx, end); } // Update sweep_edge_a to end at the intersect. sweep_edge_a.edge = first_edge; const a_orig_to_vert = sweep_edge_a.to_vert_idx; sweep_edge_a.to_vert_idx = intersect_idx; // Insert new sweep_edge_a end event. const evt_idx = @intCast(u32, self.events.items.len); var new_evt = Event.init(intersect_v, first_edge, .End); self.events.append(new_evt) catch unreachable; self.event_q.add(evt_idx) catch unreachable; // Insert start/end event from the intersect to the end of the original sweep_edge_a. var event1: Event = undefined; var event2: Event = undefined; if (Event.isStartEvent(intersect_idx, second_edge, self.verts.items)) { event1 = Event.init(intersect_v, second_edge, .Start); event2 = Event.init(self.verts.items[a_orig_to_vert], second_edge, .End); event1.end_event_idx = evt_idx + 2; } else { event1 = Event.init(intersect_v, second_edge, .End); event2 = Event.init(self.verts.items[a_orig_to_vert], second_edge, .Start); event2.end_event_idx = evt_idx + 1; } self.events.append(event1) catch unreachable; self.event_q.add(evt_idx + 1) catch unreachable; self.events.append(event2) catch unreachable; self.event_q.add(evt_idx + 2) catch unreachable; } const b_to_vert = self.verts.items[sweep_edge_b.to_vert_idx]; if (b_to_vert.pos[0] != intersect.x or b_to_vert.pos[1] != intersect.y) { log("adding edge b {} to {},{}", .{ sweep_edge_b.vert_idx, intersect.x, intersect.y }); added_events = true; // Invalidate sweep_edge_b's end event since the priority queue can not be modified. log("invalidate: {} {}", .{ sweep_edge_b.end_event_idx, self.events.items.len }); self.events.items[sweep_edge_b.end_event_idx].invalidated = true; // Keep original edge orientation when doing the split. var first_edge: Edge = undefined; var second_edge: Edge = undefined; const start = self.verts.items[sweep_edge_b.edge.start_idx]; const end = self.verts.items[sweep_edge_b.edge.end_idx]; if (sweep_edge_b.to_vert_idx == sweep_edge_b.edge.start_idx) { first_edge = Edge.init(intersect_idx, intersect_v, sweep_edge_b.edge.end_idx, end); second_edge = Edge.init(sweep_edge_b.edge.start_idx, start, intersect_idx, intersect_v); } else { first_edge = Edge.init(sweep_edge_b.edge.start_idx, start, intersect_idx, intersect_v); second_edge = Edge.init(intersect_idx, intersect_v, sweep_edge_b.edge.end_idx, end); } // Update sweep_edge_b to end at the intersect. sweep_edge_b.edge = first_edge; const b_orig_to_vert = sweep_edge_b.to_vert_idx; sweep_edge_b.to_vert_idx = intersect_idx; // Insert new sweep_edge_b end event. const evt_idx = @intCast(u32, self.events.items.len); var new_evt = Event.init(intersect_v, first_edge, .End); self.events.append(new_evt) catch unreachable; self.event_q.add(evt_idx) catch unreachable; // Insert start/end event from the intersect to the end of the original sweep_edge_b. var event1: Event = undefined; var event2: Event = undefined; if (Event.isStartEvent(intersect_idx, second_edge, self.verts.items)) { event1 = Event.init(intersect_v, second_edge, .Start); event2 = Event.init(self.verts.items[b_orig_to_vert], second_edge, .End); event1.end_event_idx = evt_idx + 2; } else { event1 = Event.init(intersect_v, second_edge, .End); event2 = Event.init(self.verts.items[b_orig_to_vert], second_edge, .Start); event2.end_event_idx = evt_idx + 1; } self.events.append(event1) catch unreachable; self.event_q.add(evt_idx + 1) catch unreachable; self.events.append(event2) catch unreachable; self.event_q.add(evt_idx + 2) catch unreachable; } if (!added_events) { // No events were added, revert adding intersect point. _ = self.verts.pop(); } } }; fn edgeToString(edge: Edge) []const u8 { const S = struct { var buf: [100]u8 = undefined; }; return std.fmt.bufPrint(&S.buf, "{} ({},{}) -> {} ({},{})", .{ edge.start_idx, edge.start_pos.x, edge.start_pos.y, edge.end_idx, edge.end_pos.x, edge.end_pos.y }) catch unreachable; } fn compareEventIdx(events: *std.ArrayList(Event), a: u32, b: u32) std.math.Order { return compareEvent(events.items[a], events.items[b]); } /// Sort verts by y asc then x asc. Resolve same position by looking at the edge's xslope and whether it is active. fn compareEvent(a: Event, b: Event) std.math.Order { if (a.vert_y < b.vert_y) { return .lt; } else if (a.vert_y > b.vert_y) { return .gt; } else { if (a.vert_x < b.vert_x) { return .lt; } else if (a.vert_x > b.vert_x) { return .gt; } else { if (a.tag == .End and b.tag == .Start) { return .lt; } else if (a.tag == .Start and b.tag == .End) { return .gt; } else if (a.tag == .End and b.tag == .End) { if (a.edge.x_slope > b.edge.x_slope) { return .lt; } else if (a.edge.x_slope < b.edge.x_slope) { return .gt; } else { return .eq; } } else { if (a.edge.x_slope < b.edge.x_slope) { return .lt; } else if (a.edge.x_slope > b.edge.x_slope) { return .gt; } else { return .eq; } } } } } /// Compare SweepEdges for insertion. Each sweep edge should be unique since the rb tree doesn't support duplicate values. /// The slopes from the current sweep edges are used to find their x-intersect along the event's y line. /// If compared sweep edge is a horizontal line, return gt so it's inserted after it. The horizontal edge can be assumed to intersect with the target event or it wouldn't be in the sweep edges. fn compareSweepEdge(_: SweepEdge, b: SweepEdge, evt: Event) std.math.Order { if (!b.edge.is_horiz) { const x_intersect = b.edge.x_slope * (evt.vert_y - b.edge.start_pos[1]) + b.edge.start_pos[0]; if (@fabs(evt.vert_x - x_intersect) < SweepEdgeApproxEpsilon) { // Since there is a chance of having floating point error, check with an epsilon. // Always return .gt so the left sweep edge can be reliably checked for a joining edge. return .gt; } else { if (evt.vert_x < x_intersect) { return .lt; } else if (evt.vert_x > x_intersect) { return .gt; } else { unreachable; } } } else { return .gt; } } fn findSweepEdgeForEndEvent(sweep_edges: *RbTree(u16, SweepEdge, Event, compareSweepEdge), e: Event) ?u16 { const target = Vec2{ e.vert_x, e.vert_y }; const dummy = SweepEdge{ .edge = undefined, .start_event_vert_uniq_idx = undefined, .vert_idx = undefined, .to_vert_idx = undefined, .end_event_vert_uniq_idx = undefined, .deferred_queue = undefined, .deferred_queue_size = undefined, .cur_side = undefined, .bad_up_cusp_uniq_idx = undefined, .bad_up_cusp_right_sweep_edge_id = undefined, .lowest_right_vert_idx = undefined, .lowest_right_vert_sweep_edge_id = undefined, .interior_is_left = undefined, .end_event_idx = undefined, }; log("find {},{}", .{ target[0], target[1] }); var mb_parent: ?u16 = undefined; var is_left: bool = undefined; var start_id: u16 = undefined; if (sweep_edges.lookupCustomLoc(dummy, target, compareSweepEdgeApprox, &mb_parent, &is_left)) |id| { start_id = id; } else { // It's possible that we can't find the sweep edge based on x intersect due to floating point error. // Start linear search at the parent. if (mb_parent) |parent| { start_id = parent; } else { return null; } } // Given a start index where a group of verts could have approx the same x-intersect value, find the one with the exact vert and to_vert. // The search ends on left/right when the x-intersect suddenly becomes greater than the epsilon. var se = sweep_edges.getNoCheck(start_id); if (se.to_vert_idx == e.vert_idx and se.vert_idx == e.to_vert_idx) { return start_id; } log("skip edge {} -> {}", .{ se.vert_idx, se.to_vert_idx }); // Search left. var mb_cur = sweep_edges.getPrev(start_id); while (mb_cur) |cur| { se = sweep_edges.getNoCheck(cur); const x_intersect = getXIntersect(se.edge, target); if (@fabs(e.vert_x - x_intersect) > SweepEdgeApproxEpsilon) { break; } else if (se.to_vert_idx == e.vert_idx and se.vert_idx == e.to_vert_idx) { return cur; } mb_cur = sweep_edges.getPrev(cur); } // Search right. mb_cur = sweep_edges.getNext(start_id); while (mb_cur) |cur| { se = sweep_edges.getNoCheck(cur); const x_intersect = getXIntersect(se.edge, target); if (@fabs(e.vert_x - x_intersect) > SweepEdgeApproxEpsilon) { break; } else if (se.to_vert_idx == e.vert_idx and se.vert_idx == e.to_vert_idx) { return cur; } mb_cur = sweep_edges.getNext(cur); } return null; } const SweepEdgeApproxEpsilon: f32 = 1e-4; /// Finds the first edge with x-intersect that approximates the provided target vert's x. /// This is needed since floating point error can lead to inconsistent divide and conquer for x-intersects that are close together (eg. two edges stemming from one vertex) /// A follow up routine to find the exact edge should be run afterwards. fn compareSweepEdgeApprox(_: SweepEdge, b: SweepEdge, target: Vec2) std.math.Order { const x_intersect = getXIntersect(b.edge, target); // Relax the epsilon since larger floating point error can happen here. In the end it's surroundings is verified by linear search. if (@fabs(target[0] - x_intersect) < SweepEdgeApproxEpsilon) { return .eq; } else if (target[0] < x_intersect) { return .lt; } else { return .gt; } } // Assumes there is an intersect. fn getXIntersect(edge: Edge, target: Vec2) f32 { if (!edge.is_horiz) { return edge.x_slope * (target[1] - edge.start_pos[1]) + edge.start_pos[0]; } else { return target[0]; } } const EventTag = enum(u1) { Start = 0, End = 1, }; /// Polygon verts and edges are reduced to events. /// Each event is centered on a vertex and has an outgoing or incoming edge. /// If the edge is above the vertex, it's considered a End event. /// If the edge is below the vertex, it's considered a Start event. /// Events are sorted by y asc and the x asc. /// To order events on the same vertex, the event with an active edge has priority. /// If both events have active edges (both End events), the one that has a greater x-slope comes first. This means an active edge to the left comes first. /// This helps end processing since the left edge still exists and contains state information about that fill region. /// If both events have non active edges (both Start events), the one that has a lesser x-slope comes first. const Event = struct { const Self = @This(); /// The idx of the internal vertex that this event fires on. vert_idx: u16, /// Duped vert pos for compare func. vert_x: f32, vert_y: f32, tag: EventTag, edge: Edge, to_vert_idx: u16, /// If this is a start event, end_event_idx will point to the corresponding end event. end_event_idx: u32, /// Since an intersection creates new events and can not modify the priority queue, /// this flag is used to invalid an existing end event. invalidated: bool, fn init(vert: InternalVertex, edge: Edge, tag: EventTag) Self { var new = Self{ .vert_idx = vert.idx, .vert_x = vert.pos[0], .vert_y = vert.pos[1], .edge = edge, .tag = tag, .to_vert_idx = undefined, .end_event_idx = std.math.maxInt(u32), .invalidated = false, }; if (edge.start_idx == vert.idx) { new.to_vert_idx = edge.end_idx; } else { new.to_vert_idx = edge.start_idx; } return new; } fn isStartEvent(vert_idx: u16, edge: Edge, verts: []const InternalVertex) bool { const vert = verts[vert_idx]; // The start and end vertex of an edge is not to be confused with the EventType. // It is used to determine if the edge is above or below the vertex point. if (edge.start_idx == vert_idx) { const end_v = verts[edge.end_idx]; if (end_v.pos[1] < vert.pos[1] or (end_v.pos[1] == vert.pos[1] and end_v.pos[0] < vert.pos[0])) { return false; } else { return true; } } else { const start_v = verts[edge.start_idx]; if (start_v.pos[1] < vert.pos[1] or (start_v.pos[1] == vert.pos[1] and start_v.pos[0] < vert.pos[0])) { return false; } else { return true; } } } }; const Side = enum(u1) { Left = 0, Right = 1, }; pub const SweepEdge = struct { const Self = @This(); edge: Edge, start_event_vert_uniq_idx: u16, vert_idx: u16, to_vert_idx: u16, end_event_idx: u32, /// The End event that marks this edge for removal. This is set in the End event. end_event_vert_uniq_idx: u16, /// Points to the head vertex. deferred_queue: DeferredVertexNodeId, /// Current size of the queue. deferred_queue_size: u16, /// The current side being processed for monotone triangulation. cur_side: Side, /// Last seen bad up cusp. bad_up_cusp_uniq_idx: u16, bad_up_cusp_right_sweep_edge_id: u16, lowest_right_vert_idx: u16, lowest_right_vert_sweep_edge_id: u16, /// Store the winding. This would be used by the fill rule to determine if the interior is to the left or right. interior_is_left: bool, /// A sweep edge is created from a Start event. fn init(e: Event, verts: []const InternalVertex) Self { const e_vert = verts[e.vert_idx]; return .{ .edge = e.edge, .start_event_vert_uniq_idx = e_vert.out_idx, .vert_idx = e.vert_idx, .to_vert_idx = e.to_vert_idx, .end_event_idx = e.end_event_idx, .end_event_vert_uniq_idx = std.math.maxInt(u16), .deferred_queue = NullId, .deferred_queue_size = 0, .cur_side = .Left, .bad_up_cusp_uniq_idx = NullId, .bad_up_cusp_right_sweep_edge_id = NullId, .lowest_right_vert_idx = NullId, .lowest_right_vert_sweep_edge_id = NullId, .interior_is_left = false, }; } fn enqueueDeferred(self: *Self, vert: InternalVertex, tess: *Tessellator) void { const node = DeferredVertexNode.init(vert); self.deferred_queue = tess.deferred_verts.insertBeforeHeadNoCheck(self.deferred_queue, node) catch unreachable; self.deferred_queue_size += 1; } fn verifySize(self: Self, tess: *Tessellator) void { var cur = self.deferred_queue; var act_size: u32 = 0; while (cur != NullId) { act_size += 1; cur = tess.deferred_verts.getNextNoCheck(cur); } if (self.deferred_queue_size != act_size) { log("expected {}, actual {}", .{ self.deferred_queue_size, act_size }); unreachable; } } fn dumpQueue(self: Self, tess: *Tessellator) void { var buf: [200]u8 = undefined; var buf_stream = std.io.fixedBufferStream(&buf); var writer = buf_stream.writer(); var cur_id = self.deferred_queue; while (cur_id != NullId) { const cur = tess.deferred_verts.getNoCheck(cur_id); std.fmt.format(writer, "{},", .{cur.vert_idx}) catch unreachable; const last = cur_id; cur_id = tess.deferred_verts.getNextNoCheck(cur_id); if (last == cur_id) { std.fmt.format(writer, "repeat pt - bad state", .{}) catch unreachable; break; } } var side_str: []const u8 = if (self.cur_side == .Right) "right" else "left"; log("size: {}, side: {s}, idxes: {s}", .{ self.deferred_queue_size, side_str, buf[0..buf_stream.pos] }); } }; /// This contains a vertex that still needs to be triangulated later when it can. /// It is linked together in a singly linked list queue and is designed to behave like the monotone triangulation queue. /// Since the triangulator does everything in one pass for complex polygons, every monotone polygon span in the sweep edges /// needs to keep track of their deferred vertices since the edges are short lived. pub const DeferredVertexNode = struct { const Self = @This(); vert_idx: u16, // Duped vars. vert_out_idx: u16, vert_x: f32, vert_y: f32, fn init(vert: InternalVertex) Self { return .{ .vert_idx = vert.idx, .vert_out_idx = vert.out_idx, .vert_x = vert.pos[0], .vert_y = vert.pos[1], }; } }; /// Contains start/end InternalVertex. pub const Edge = struct { const Self = @This(); /// Start vertex index. start_idx: u16, /// End vertex index. end_idx: u16, /// Duped start_pos for compareSweepEdge, getXIntersect. start_pos: Vec2, end_pos: Vec2, /// Vector from start to end pos. vec: Vec2, /// Change of x with respect to y. x_slope: f32, /// Whether the edge is horizontal. /// TODO: This may not be needed anymore. is_horiz: bool, fn init(start_idx: u16, start_v: InternalVertex, end_idx: u16, end_v: InternalVertex) Self { var new = Self{ .start_idx = start_idx, .end_idx = end_idx, .start_pos = start_v.pos, .end_pos = end_v.pos, .vec = end_v.pos - start_v.pos, .x_slope = undefined, .is_horiz = undefined, }; if (new.vec[1] != 0) { new.x_slope = new.vec[0] / new.vec[1]; new.is_horiz = false; } else { new.x_slope = std.math.f32_max; new.is_horiz = true; } return new; } }; /// Internal verts. During triangulation, these are using for event computations so there can be duplicates points. pub const InternalVertex = struct { pos: Vec2, /// This is the index of the vertex from the given polygon. idx: u16, /// The vertex index of the resulting buffer. Set during process event. out_idx: u16 = undefined, }; /// Avoids division by zero. /// https://stackoverflow.com/questions/563198 /// For segments: p, p + r, q, q + s /// To find t, u for p + tr, q + us /// t = (q − p) X s / (r X s) /// u = (q − p) X r / (r X s) fn computeTwoEdgeIntersect(p: Edge, q: Edge) IntersectResult { const t__ = trace(@src()); defer t__.end(); const r_s = cross(p.vec, q.vec); if (r_s == 0) { return IntersectResult.initNull(); } const qmp = Vec2{ q.start_pos[0] - p.start_pos[0], q.start_pos[1] - p.start_pos[1] }; const qmp_r = cross(qmp, p.vec); const u = qmp_r / r_s; if (u >= 0 and u <= 1) { // Must check intersect point is also on p. const qmp_s = cross(qmp, q.vec); const t_ = qmp_s / r_s; if (t_ >= 0 and t_ <= 1) { return .{ .x = q.start_pos[0] + q.vec[0] * u, .y = q.start_pos[1] + q.vec[1] * u, .t = t_, .u = u, .has_intersect = true, }; } else { return IntersectResult.initNull(); } } else { return IntersectResult.initNull(); } } const IntersectResult = struct { x: f32, y: f32, t: f32, u: f32, has_intersect: bool, fn initNull() IntersectResult { return .{ .x = undefined, .y = undefined, .t = undefined, .u = undefined, .has_intersect = false, }; } }; /// Just check triangle count. /// TODO: Verify all triangles take up the entire space. fn testLarge(polygon: []const f32, exp_tri_count: u32) !void { var polygon_buf = std.ArrayList(Vec2).init(t.allocator); defer polygon_buf.deinit(); var i: u32 = 0; while (i < polygon.len) : (i += 2) { polygon_buf.append(Vec2{ polygon[i], polygon[i + 1] }) catch unreachable; } var tessellator: Tessellator = undefined; tessellator.init(t.allocator); defer tessellator.deinit(); tessellator.triangulatePolygon(polygon_buf.items); try t.expectEqual(tessellator.out_idxes.items.len / 3, exp_tri_count); } fn testSimple(polygon: []const f32, exp_verts: []const f32, exp_idxes: []const u16) !void { var polygon_buf = std.ArrayList(Vec2).init(t.allocator); defer polygon_buf.deinit(); var i: u32 = 0; while (i < polygon.len) : (i += 2) { polygon_buf.append(Vec2{ polygon[i], polygon[i + 1] }) catch unreachable; } var tessellator: Tessellator = undefined; tessellator.init(t.allocator); defer tessellator.deinit(); tessellator.triangulatePolygon(polygon_buf.items); var exp_verts_buf = std.ArrayList(Vec2).init(t.allocator); defer exp_verts_buf.deinit(); i = 0; while (i < exp_verts.len) : (i += 2) { exp_verts_buf.append(Vec2{ exp_verts[i], exp_verts[i + 1] }) catch unreachable; } try t.expectEqualSlices(Vec2, tessellator.out_verts.items, exp_verts_buf.items); // log("{any}", .{tessellator.out_idxes.items}); try t.expectEqualSlices(u16, tessellator.out_idxes.items, exp_idxes); } test "One triangle ccw." { try testSimple(&.{ 100, 0, 0, 0, 0, 100, }, &.{ 0, 0, 100, 0, 0, 100, }, &.{ 2, 1, 0 }); } test "One triangle cw." { try testSimple(&.{ 100, 0, 0, 100, 0, 0, }, &.{ 0, 0, 100, 0, 0, 100, }, &.{ 2, 1, 0 }); } test "Square." { try testSimple(&.{ 0, 0, 100, 0, 100, 100, 0, 100, }, &.{ 0, 0, 100, 0, 0, 100, 100, 100, }, &.{ 2, 1, 0, 3, 1, 2, }); } test "Pentagon." { try testSimple(&.{ 100, 0, 200, 100, 200, 200, 0, 200, 0, 100, }, &.{ 100, 0, 0, 100, 200, 100, 0, 200, 200, 200, }, &.{ 2, 0, 1, 3, 2, 1, 4, 2, 3, }); } test "Hexagon." { try testSimple(&.{ 100, 0, 200, 100, 200, 200, 100, 300, 0, 200, 0, 100, }, &.{ 100, 0, 0, 100, 200, 100, 0, 200, 200, 200, 100, 300, }, &.{ 2, 0, 1, 3, 2, 1, 4, 2, 3, 5, 4, 3, }); } test "Octagon." { try testSimple(&.{ 100, 0, 200, 0, 300, 100, 300, 200, 200, 300, 100, 300, 0, 200, 0, 100, }, &.{ 100, 0, 200, 0, 0, 100, 300, 100, 0, 200, 300, 200, 100, 300, 200, 300, }, &.{ 2, 1, 0, 3, 1, 2, 4, 3, 2, 5, 3, 4, 6, 5, 4, 7, 5, 6, }); } test "Rhombus." { try testSimple(&.{ 100, 0, 200, 100, 100, 200, 0, 100, }, &.{ 100, 0, 0, 100, 200, 100, 100, 200, }, &.{ 2, 0, 1, 3, 2, 1, }); } // Tests monotone partition with bad up cusp and valid right angle. test "Square with concave top side." { try testSimple(&.{ 0, 0, 100, 100, 200, 0, 200, 200, 0, 200, }, &.{ 0, 0, 200, 0, 100, 100, 0, 200, 200, 200, }, &.{ 3, 2, 0, 4, 2, 3, 4, 1, 2, }); } // Tests monotone partition with bad down cusp and valid right angle. test "Square with concave bottom side." { try testSimple(&.{ 0, 0, 200, 0, 200, 200, 100, 100, 0, 200, }, &.{ 0, 0, 200, 0, 100, 100, 0, 200, 200, 200, }, &.{ 2, 1, 0, 3, 2, 0, 4, 1, 2, }); } // Tests monotone partition with bad up cusp and valid up cusp. test "V shape." { try testSimple(&.{ 0, 0, 100, 100, 200, 0, 100, 200, }, &.{ 0, 0, 200, 0, 100, 100, 100, 200, }, &.{ 3, 2, 0, 3, 1, 2, }); } // Tests monotone partition with bad down cusp and valid up cusp. test "Upside down V shape." { try testSimple(&.{ 100, 0, 200, 200, 100, 100, 0, 200, }, &.{ 100, 0, 100, 100, 0, 200, 200, 200, }, &.{ 2, 1, 0, 3, 0, 1, }); } // Tests the sweep line with alternating interior/exterior sides. test "Clockwise spiral." { try testSimple(&.{ 0, 0, 500, 0, 500, 500, 200, 500, 200, 200, 300, 200, 300, 400, 400, 400, 400, 100, 100, 100, 100, 500, 0, 500, }, &.{ 0, 0, 500, 0, 100, 100, 400, 100, 200, 200, 300, 200, 300, 400, 400, 400, 0, 500, 100, 500, 200, 500, 500, 500, }, &.{ 2, 1, 0, 3, 1, 2, 6, 5, 4, 7, 1, 3, 8, 2, 0, 9, 2, 8, 10, 7, 6, 10, 6, 4, 11, 7, 10, 11, 1, 7, }); } // Tests the sweep line with alternating interior/exterior sides. test "CCW spiral." { try testSimple(&.{ 0, 0, 500, 0, 500, 500, 400, 500, 400, 100, 100, 100, 100, 400, 200, 400, 200, 200, 300, 200, 300, 500, 0, 500, }, &.{ 0, 0, 500, 0, 100, 100, 400, 100, 200, 200, 300, 200, 100, 400, 200, 400, 0, 500, 300, 500, 400, 500, 500, 500, }, &.{ 2, 1, 0, 3, 1, 2, 6, 2, 0, 7, 5, 4, 8, 7, 6, 8, 6, 0, 9, 7, 8, 9, 5, 7, 10, 1, 3, 11, 1, 10, }); } test "Overlapping point." { try testSimple(&.{ 0, 0, 100, 100, 200, 0, 200, 200, 100, 100, 0, 200, }, &.{ 0, 0, 200, 0, 100, 100, 0, 200, 200, 200, }, &.{ 3, 2, 0, 4, 1, 2, }); } // Different windings on split polygons. test "Self intersecting polygon." { try testSimple(&.{ 0, 200, 0, 100, 200, 100, 200, 0, }, &.{ 200, 0, 0, 100, 100, 100, 200, 100, 0, 200, }, &.{ 3, 0, 2, 4, 2, 1, }); } // Test evenodd rule. test "Overlapping triangles." { try testSimple(&.{ 0, 100, 200, 0, 200, 200, 0, 100, 250, 75, 250, 125, 0, 100, }, &.{ 200, 0, 250, 75, 200, 80, 0, 100, 200, 120, 250, 125, 200, 200, }, &.{ 3, 2, 0, 4, 1, 2, 5, 1, 4, 6, 4, 3, }); } // Begin mapbox test cases. test "bad-diagonals.json" { try testSimple(&.{ 440, 4152, 440, 4208, 296, 4192, 368, 4192, 400, 4200, 400, 4176, 368, 4192, 296, 4192, 264, 4200, 288, 4160, 296, 4192, }, &.{ 440, 4152, 288, 4160, 400, 4176, 296, 4192, 368, 4192, 264, 4200, 400, 4200, 440, 4208, }, &.{ 3, 2, 0, 4, 2, 3, 5, 3, 1, 6, 4, 3, 6, 0, 2, 7, 6, 3, 7, 0, 6, }); } // TODO: dude.json // Case by case examples. test "Rectangle with bottom-left wedge." { try testSimple(&.{ 56, 22, 111, 22, 111, 44, 37, 44, 56, 32, }, &.{ 56, 22, 111, 22, 56, 32, 37, 44, 111, 44, }, &.{ 2, 1, 0, 3, 1, 2, 4, 1, 3, }); } test "Tiger big part." { try testLarge(&.{ -129.83, 103.06, -129.83, 103.06, -128.36, 113.62, -126.60, 118.80, -126.60, 118.80, -127.33, 125.99, -125.81, 135.32, -121.40, 144.40, -121.40, 144.40, -121.18, 151.03, -120.20, 154.80, -120.20, 154.80, -115.56, 161.53, -111.40, 164.00, -111.40, 164.00, -99.95, 166.93, -88.93, 169.12, -88.93, 169.12, -82.12, 176.08, -77.01, 183.74, -74.79, 190.23, -75.00, 196.00, -75.00, 196.00, -76.74, 210.10, -79.00, 214.00, -79.00, 214.00, -73.96, 210.34, -73.22, 211.25, -77.00, 219.60, -81.40, 238.40, -81.40, 238.40, -67.64, 228.08, -66.39, 228.12, -71.40, 235.20, -81.40, 261.20, -81.40, 261.20, -67.93, 249.24, -67.39, 249.03, -69.00, 251.20, -72.20, 260.00, -72.20, 260.00, -53.39, 249.64, -48.97, 248.73, -49.31, 250.85, -59.80, 262.40, -59.80, 262.40, -52.31, 260.54, -47.40, 261.60, -47.40, 261.60, -41.92, 261.27, -41.40, 262.00, -41.40, 262.00, -49.70, 267.43, -57.61, 274.87, -62.93, 282.61, -65.80, 290.80, -65.80, 290.80, -61.30, 286.73, -59.99, 287.03, -60.60, 291.60, -60.20, 303.20, -60.20, 303.20, -58.30, 297.14, -57.37, 298.26, -56.60, 319.20, -56.60, 319.20, -48.49, 312.82, -45.76, 312.03, -45.35, 313.82, -49.00, 322.00, -49.00, 338.80, -49.00, 338.80, -40.26, 330.76, -38.63, 330.61, -40.20, 335.20, -40.20, 335.20, -36.26, 332.85, -34.22, 332.98, -33.27, 335.13, -34.20, 341.60, -34.20, 341.60, -33.94, 345.73, -33.17, 345.79, -30.60, 340.80, -30.60, 340.80, -22.95, 328.26, -20.19, 325.79, -19.29, 327.04, -20.60, 336.40, -20.60, 336.40, -20.14, 345.51, -19.15, 346.31, -16.60, 340.80, -16.60, 340.80, -15.40, 346.50, -12.14, 353.01, -7.00, 358.40, -7.00, 358.40, -6.27, 339.53, -4.46, 332.02, -2.77, 330.70, -0.27, 332.77, 4.60, 343.60, 8.60, 360.00, 8.60, 360.00, 10.64, 351.18, 11.00, 345.60, 19.00, 353.60, 19.00, 353.60, 28.79, 341.06, 31.17, 340.03, 31.00, 344.00, 31.00, 344.00, 25.45, 358.75, 25.00, 364.80, 25.00, 364.80, 43.00, 328.40, 43.00, 328.40, 43.39, 345.38, 44.82, 349.24, 46.77, 347.92, 51.80, 334.80, 51.80, 334.80, 54.49, 342.22, 55.39, 347.96, 54.60, 351.20, 54.60, 351.20, 60.76, 343.58, 61.80, 340.00, 61.80, 340.00, 64.54, 337.57, 66.77, 338.71, 69.20, 345.40, 69.20, 345.40, 71.39, 352.02, 72.60, 351.60, 72.60, 351.60, 75.37, 362.09, 76.42, 362.30, 77.80, 352.80, 77.80, 352.80, 77.75, 344.98, 75.91, 335.70, 72.20, 327.60, 72.20, 327.60, 72.12, 324.56, 70.20, 320.40, 70.20, 320.40, 75.70, 327.30, 77.91, 328.09, 78.69, 325.45, 76.60, 313.20, 76.60, 313.20, 89.00, 321.20, 89.00, 321.20, 82.70, 308.82, 81.23, 303.45, 81.82, 302.23, 84.20, 302.80, 84.20, 302.80, 83.54, 299.86, 84.53, 298.67, 87.95, 299.28, 97.00, 304.40, 97.00, 304.40, 91.03, 297.34, 90.41, 295.37, 91.64, 294.95, 98.60, 298.00, 98.60, 298.00, 101.93, 300.03, 102.23, 299.50, 99.00, 294.40, 99.00, 294.40, 94.40, 288.21, 95.27, 288.03, 106.60, 296.40, 106.60, 296.40, 116.61, 311.24, 119.00, 315.60, 119.00, 315.60, 108.86, 290.10, 104.60, 283.60, 104.60, 283.60, 108.02, 275.28, 114.09, 267.22, 121.76, 261.79, 130.82, 259.23, 141.00, 259.30, 154.20, 262.80, 154.20, 262.80, 157.75, 268.68, 160.36, 270.08, 162.73, 268.60, 165.40, 261.60, 165.40, 261.60, 170.08, 261.04, 176.25, 263.49, 182.75, 270.16, 189.40, 282.80, 189.40, 282.80, 192.36, 270.67, 192.60, 266.40, 192.60, 266.40, 198.19, 266.89, 198.60, 266.40, 198.60, 266.40, 210.19, 269.77, 213.00, 270.00, 213.00, 270.00, 217.51, 273.62, 219.47, 274.23, 220.20, 273.20, 220.20, 273.20, 225.75, 274.22, 227.51, 273.79, 227.40, 272.40, 227.40, 272.40, 234.76, 286.58, 236.60, 291.60, 239.00, 277.60, 241.00, 280.40, 241.00, 280.40, 242.01, 273.69, 241.80, 271.60, 241.80, 271.60, 242.83, 271.72, 249.79, 275.48, 257.44, 282.09, 263.14, 289.99, 266.60, 299.20, 268.60, 307.60, 268.60, 307.60, 272.87, 294.06, 273.00, 288.80, 273.00, 288.80, 277.01, 290.73, 278.60, 294.00, 278.60, 294.00, 280.13, 278.97, 279.59, 269.28, 277.80, 264.80, 277.80, 264.80, 281.47, 265.30, 283.40, 267.60, 283.40, 260.40, 283.40, 260.40, 289.30, 260.14, 290.60, 258.80, 290.60, 258.80, 293.21, 257.36, 295.38, 257.54, 297.00, 259.60, 297.00, 259.60, 293.38, 246.31, 293.06, 239.85, 294.31, 238.04, 296.82, 238.39, 303.00, 243.60, 303.00, 243.60, 306.29, 246.69, 307.45, 245.38, 306.60, 235.60, 306.60, 235.60, 302.46, 219.68, 301.73, 215.52, 303.80, 214.80, 303.80, 214.80, 303.85, 211.76, 302.60, 209.60, 302.60, 209.60, 301.93, 208.90, 303.80, 209.60, 303.80, 209.60, 305.11, 209.64, 305.81, 206.07, 303.40, 191.60, 303.40, 191.60, 304.82, 190.48, 304.47, 183.66, 297.80, 164.00, 297.80, 164.00, 298.73, 160.69, 296.60, 153.20, 296.60, 153.20, 303.95, 156.14, 307.40, 156.00, 307.40, 156.00, 307.15, 155.40, 303.80, 150.40, 303.80, 150.40, 295.80, 126.68, 293.83, 115.79, 294.65, 112.78, 296.76, 112.66, 302.60, 117.60, 302.60, 117.60, 307.07, 121.27, 309.11, 121.32, 309.97, 118.48, 308.05, 108.35, 308.05, 108.35, 300.79, 86.65, 299.72, 80.04, -129.83, 103.06, }, 227); } // Test points that are close to each other with higher precision. test "Tiger whisker." { try testLarge(&.{ -109.01000, 110.07000, -109.01000, 110.07000, -108.34000, 111.97000, -108.34000, 111.97000, -123.56751, 104.91863, -141.35422, 98.68091, -153.21875, 96.99554, -161.26077, 98.11440, -166.87000, 101.68000, -166.87000, 101.68000, -163.45908, 98.55489, -156.28145, 96.28941, -145.48354, 96.58372, -130.09291, 100.65533, -109.00999, 110.07000 }, 9); } // Tests zig zag shape. test "Tiger part." { try testLarge(&.{ -54.20, 176.40, -54.20, 176.40, -51.54, 180.01, -50.04, 187.53, -51.51, 198.82, -57.40, 214.80, -51.00, 212.40, -51.00, 212.40, -52.75, 222.12, -55.00, 226.00, -47.80, 222.80, -47.80, 222.80, -45.85, 227.62, -45.49, 232.20, -47.00, 235.60, -47.00, 235.60, -37.04, 241.57, -32.01, 246.52, -31.00, 250.00, -31.00, 250.00, -28.25, 245.06, -27.27, 239.91, -28.60, 235.60, -28.60, 235.60, -32.06, 232.22, -36.59, 228.60, -38.53, 223.88, -39.00, 214.80, -47.80, 218.00, -47.80, 218.00, -43.55, 209.33, -42.20, 202.80, -50.20, 205.20, -50.20, 205.20, -43.67, 191.40, -41.68, 182.92, -42.47, 178.91, -45.40, 177.20, -45.40, 177.20, -53.55, 176.38, -54.20, 176.40 }, 29); } test "Tiger whisker #2." { try testLarge(&.{ 50.60, 84.00, 50.60, 84.00, 36.68, 72.16, 27.20, 65.81, 22.20, 64.00, 22.20, 64.00, 7.18, 63.89, -7.84, 66.44, -19.01, 71.16, -27.00, 78.00, -27.00, 78.00, -18.60, 70.90, -7.02, 64.99, 5.17, 62.33, 18.20, 63.20, 18.20, 63.20, 4.15, 61.23, -7.70, 60.89, -15.80, 62.00, -42.20, 76.00, -45.00, 80.80, -45.00, 80.80, -42.44, 75.17, -37.28, 68.75, -31.28, 64.00, -22.60, 60.00, -22.60, 60.00, -7.92, 58.05, 3.78, 58.19, 11.00, 60.00, 11.00, 60.00, -3.17, 56.40, -14.12, 54.88, -20.60, 55.20, -20.60, 55.20, -30.04, 55.69, -41.27, 58.57, -50.82, 63.73, -57.83, 70.06, -63.80, 79.20, -63.80, 79.20, -60.27, 71.59, -53.70, 63.52, -45.00, 57.60, -45.00, 57.60, -36.54, 53.82, -24.25, 51.26, -11.00, 51.60, -11.00, 51.60, 2.14, 54.95, 8.60, 57.20, 8.60, 57.20, 11.58, 58.03, 11.26, 56.98, 4.20, 52.00, 4.20, 52.00, 0.05, 47.36, -6.79, 43.57, -15.40, 42.40, -15.40, 42.40, -36.18, 45.32, -52.83, 49.44, -63.12, 53.75, -68.60, 58.00, -68.60, 58.00, -54.94, 48.57, -44.60, 44.00, -44.60, 44.00, -23.74, 37.92, -13.80, 36.80, -13.80, 36.80, 8.76, 36.15, 18.60, 33.80, 18.60, 33.80, 12.30, 37.54, 10.11, 40.15, 10.60, 42.00, 10.60, 42.00, 18.76, 51.11, 20.60, 54.00, 20.60, 54.00, 28.20, 61.89, 48.40, 81.70, 50.60, 84.00 }, 61); } test "Tiger big part #2." { try testLarge(&.{ 143.80, 259.60, 143.80, 259.60, 156.61, 257.34, 165.99, 254.14, 171.00, 250.80, 175.40, 254.40, 193.00, 216.00, 196.60, 221.20, 196.60, 221.20, 204.92, 211.13, 209.33, 203.27, 210.20, 198.40, 210.20, 198.40, 210.92, 196.01, 214.22, 196.97, 223.00, 204.40, 223.00, 204.40, 223.74, 198.82, 225.70, 197.45, 229.40, 199.60, 229.40, 199.60, 229.48, 191.67, 231.36, 189.69, 235.40, 192.00, 235.40, 192.00, 233.02, 182.21, 233.43, 178.04, 234.91, 177.19, 238.62, 178.91, 247.40, 187.60, 247.40, 187.60, 250.17, 190.36, 248.60, 187.20, 248.60, 187.20, 238.82, 166.34, 235.69, 155.55, 236.21, 151.81, 238.37, 150.90, 244.20, 153.60, 244.20, 153.60, 245.37, 133.23, 245.00, 126.40, 245.00, 126.40, 242.11, 109.37, 239.39, 98.81, 237.00, 94.40, 237.00, 94.40, 235.10, 91.16, 235.84, 89.80, 238.54, 89.93, 243.00, 92.80, 243.00, 92.80, 238.96, 81.92, 238.77, 78.05, 240.20, 77.49, 245.00, 80.80, 245.00, 80.80, 240.58, 67.71, 237.00, 62.80, 237.00, 62.80, 236.04, 56.18, 237.21, 53.39, 240.05, 52.95, 246.60, 56.40, 246.60, 56.40, 241.98, 45.41, 239.00, 40.80, 239.00, 40.80, 232.68, 22.48, 232.55, 17.75, 234.60, 18.00, 239.00, 21.60, 239.00, 21.60, 235.94, 13.32, 236.27, 11.21, 238.60, 12.00, 238.60, 12.00, 244.87, 15.98, 245.00, 16.00, 245.00, 16.00, 236.54, 0.65, 235.41, -4.10, 236.94, -4.65, 244.20, 0.40, 244.20, 0.40, 232.60, -20.40, 232.60, -20.40, 224.56, -30.47, 222.78, -34.51, 223.63, -35.63, 228.20, -34.40, 233.00, -32.80, 233.00, -32.80, 223.84, -40.87, 216.20, -44.40, 216.20, -44.40, 213.35, -45.94, 214.21, -47.98, 219.17, -50.41, 225.00, -50.40, 225.00, -50.40, 247.00, -40.80, 247.00, -40.80, 259.33, -24.93, 263.80, -21.60, 263.80, -21.60, 251.96, -24.82, 248.79, -24.16, 249.80, -21.20, 249.80, -21.20, 257.74, -11.96, 258.98, -8.44, 257.00, -7.60, 257.00, -7.60, 254.67, -3.13, 253.96, 2.58, 255.80, 8.40, 255.80, 8.40, 248.73, 2.56, 246.65, 2.15, 246.70, 4.49, 252.20, 15.60, 259.00, 32.00, 259.00, 32.00, 247.61, 21.93, 243.68, 20.11, 242.85, 21.48, 245.80, 29.20, 245.80, 29.20, 261.57, 49.78, 265.00, 53.20, 265.00, 53.20, 267.03, 55.03, 271.40, 62.40, 267.00, 60.40, 272.20, 69.20, 272.20, 69.20, 266.48, 64.35, 265.25, 64.72, 267.00, 70.40, 272.60, 84.80, 272.60, 84.80, 264.54, 77.74, 261.68, 77.09, 261.24, 79.97, 265.80, 92.40, 265.80, 92.40, 259.71, 91.77, 256.50, 93.34, 255.61, 96.92, 258.20, 104.40, 258.20, 104.40, 257.00, 125.60, 257.00, 125.60, 257.37, 146.74, 256.16, 160.73, 254.20, 167.20, 254.20, 167.20, 253.12, 172.65, 255.06, 182.19, 262.20, 198.40, 262.20, 198.40, 264.65, 206.19, 263.74, 207.59, 259.00, 204.00, 259.00, 204.00, 253.94, 199.29, 253.84, 200.28, 256.60, 209.20, 256.60, 209.20, 262.81, 231.18, 263.80, 239.20, 263.80, 239.20, 262.65, 239.33, 259.40, 236.80, 259.40, 236.80, 251.57, 226.52, 247.90, 223.71, 246.46, 224.22, 246.20, 228.40, 246.20, 228.40, 241.80, 245.20, 241.80, 245.20, 239.77, 250.25, 239.04, 250.56, 238.60, 247.20, 238.60, 247.20, 235.84, 237.79, 234.13, 236.00, 232.60, 238.00, 232.60, 238.00, 227.70, 248.46, 223.40, 254.00, 223.40, 254.00, 221.77, 253.32, 217.15, 243.71, 215.18, 241.23, 214.20, 244.00, 214.20, 244.00, 208.81, 240.31, 204.14, 239.64, 200.46, 241.80, 197.40, 248.00, 185.80, 264.40, 185.80, 264.40, 184.89, 256.37, 184.20, 258.00, 184.20, 258.00, 164.60, 260.78, 150.89, 261.06, 143.80, 259.60 }, 157); } pub const DebugTriangulateStepResult = struct { has_result: bool, sweep_edges: []const SweepEdge, event: Event, out_verts: []const Vec2, out_idxes: []const u16, verts: []const InternalVertex, deferred_verts: []const CompactSinglyLinkedListBuffer(DeferredVertexNodeId, DeferredVertexNode).Node, pub fn deinit(self: @This(), alloc: std.mem.Allocator) void { alloc.free(self.sweep_edges); alloc.free(self.out_verts); alloc.free(self.out_idxes); alloc.free(self.verts); alloc.free(self.deferred_verts); } }; // test "Simple breaking shape" { // var polygon_buf = std.ArrayList(Vec2).init(t.allocator); // defer polygon_buf.deinit(); // try polygon_buf.appendSlice(&[_]Vec2{ // Vec2{ 0, 0 }, // Vec2{ 10, 0 }, // Vec2{ 10, 10 }, // Vec2{ 0, 10 }, // Vec2{ 0, 0 }, // Vec2{ 0, 0 }, // Vec2{ 2, 2 }, // Vec2{ 8, 2 }, // Vec2{ 8, 8 }, // Vec2{ 2, 8 }, // }); // var tessellator: Tessellator = undefined; // tessellator.init(t.allocator); // defer tessellator.deinit(); // tessellator.triangulatePolygons(&.{ polygon_buf.items[0..5], polygon_buf.items[5..] }); // // tessellator.triangulatePolygons(&.{polygon_buf.items[0..4]}); // }