examples/gkurve: initial commit for resizable_label
This commit is contained in:
parent
ade26b24b1
commit
98138dd2fa
10 changed files with 4899 additions and 20 deletions
1
examples/gkurve/LICENSE.tesselator
Normal file
1
examples/gkurve/LICENSE.tesselator
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
TODO: add license, the tesselator implementation comes from https://github.com/fubark/cosmic/blob/master/graphics/src/tessellator.zig
|
||||||
|
|
@ -25,7 +25,7 @@ const Node = struct {
|
||||||
width: u32,
|
width: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Error = error{
|
pub const Error = error{
|
||||||
/// Atlas cannot fit the desired region. You must enlarge the atlas.
|
/// Atlas cannot fit the desired region. You must enlarge the atlas.
|
||||||
AtlasFull,
|
AtlasFull,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
67
examples/gkurve/data_structures/bit_array_list.zig
Normal file
67
examples/gkurve/data_structures/bit_array_list.zig
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// std.DynamicBitSet doesn't behave like std.ArrayList, it will realloc on every resize.
|
||||||
|
// For now provide a bitset api and use std.ArrayList(bool) as the implementation.
|
||||||
|
pub const BitArrayList = struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
buf: std.ArrayList(bool),
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) Self {
|
||||||
|
return .{
|
||||||
|
.buf = std.ArrayList(bool).init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
self.buf.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearRetainingCapacity(self: *Self) void {
|
||||||
|
self.buf.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn appendUnset(self: *Self) !void {
|
||||||
|
try self.buf.append(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn appendSet(self: *Self) !void {
|
||||||
|
try self.buf.append(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isSet(self: Self, idx: usize) bool {
|
||||||
|
return self.buf.items[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(self: *Self, idx: usize) void {
|
||||||
|
self.buf.items[idx] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unset(self: *Self, idx: usize) void {
|
||||||
|
self.buf.items[idx] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setRange(self: *Self, start: usize, end: usize) void {
|
||||||
|
std.mem.set(bool, self.buf.items[start..end], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unsetRange(self: *Self, start: usize, end: usize) void {
|
||||||
|
std.mem.set(bool, self.buf.items[start..end], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(self: *Self, size: usize) !void {
|
||||||
|
try self.buf.resize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resizeFillNew(self: *Self, size: usize, comptime fill: bool) !void {
|
||||||
|
const start = self.buf.items.len;
|
||||||
|
try self.resize(size);
|
||||||
|
if (self.buf.items.len > start) {
|
||||||
|
if (fill) {
|
||||||
|
self.setRange(start, self.buf.items.len);
|
||||||
|
} else {
|
||||||
|
self.unsetRange(start, self.buf.items.len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
780
examples/gkurve/data_structures/compact.zig
Normal file
780
examples/gkurve/data_structures/compact.zig
Normal file
|
|
@ -0,0 +1,780 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const t = std.testing;
|
||||||
|
const BitArrayList = @import("bit_array_list.zig").BitArrayList;
|
||||||
|
const log = std.log.scoped(.compact);
|
||||||
|
|
||||||
|
/// Useful for keeping elements closer together in memory when you're using a bunch of insert/delete,
|
||||||
|
/// while keeping realloc to a minimum and preserving the element's initial insert index.
|
||||||
|
/// Backed by std.ArrayList.
|
||||||
|
/// Item ids are reused once removed.
|
||||||
|
/// Items are assigned an id and have O(1) access time by id.
|
||||||
|
/// TODO: Iterating can be just as fast as a dense array if CompactIdGenerator kept a sorted list of freed id ranges.
|
||||||
|
/// Although that also means delete ops would need to be O(logn).
|
||||||
|
pub fn CompactUnorderedList(comptime Id: type, comptime T: type) type {
|
||||||
|
if (@typeInfo(Id).Int.signedness != .unsigned) {
|
||||||
|
@compileError("Unsigned id type required.");
|
||||||
|
}
|
||||||
|
return struct {
|
||||||
|
id_gen: CompactIdGenerator(Id),
|
||||||
|
|
||||||
|
// TODO: Rename to buf.
|
||||||
|
data: std.ArrayList(T),
|
||||||
|
|
||||||
|
// Keep track of whether an item exists at id in order to perform iteration.
|
||||||
|
// TODO: Rename to exists.
|
||||||
|
// TODO: Maybe the user should provide this if it's important. It would also simplify the api and remove optional return types. It also means iteration won't be possible.
|
||||||
|
data_exists: BitArrayList,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
const Iterator = struct {
|
||||||
|
// The current id should reflect the id of the value returned from next or nextPtr.
|
||||||
|
cur_id: Id,
|
||||||
|
list: *const Self,
|
||||||
|
|
||||||
|
fn init(list: *const Self) @This() {
|
||||||
|
return .{
|
||||||
|
.cur_id = std.math.maxInt(Id),
|
||||||
|
.list = list,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(self: *@This()) void {
|
||||||
|
self.idx = std.math.maxInt(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nextPtr(self: *@This()) ?*T {
|
||||||
|
self.cur_id +%= 1;
|
||||||
|
while (true) {
|
||||||
|
if (self.cur_id < self.list.data.items.len) {
|
||||||
|
if (!self.list.data_exists.isSet(self.cur_id)) {
|
||||||
|
self.cur_id += 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return &self.list.data.items[self.cur_id];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *@This()) ?T {
|
||||||
|
self.cur_id +%= 1;
|
||||||
|
while (true) {
|
||||||
|
if (self.cur_id < self.list.data.items.len) {
|
||||||
|
if (!self.list.data_exists.isSet(self.cur_id)) {
|
||||||
|
self.cur_id += 1;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return self.list.data.items[self.cur_id];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) @This() {
|
||||||
|
const new = @This(){
|
||||||
|
.id_gen = CompactIdGenerator(Id).init(alloc, 0),
|
||||||
|
.data = std.ArrayList(T).init(alloc),
|
||||||
|
.data_exists = BitArrayList.init(alloc),
|
||||||
|
};
|
||||||
|
return new;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
self.id_gen.deinit();
|
||||||
|
self.data.deinit();
|
||||||
|
self.data_exists.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iterator(self: *const Self) Iterator {
|
||||||
|
return Iterator.init(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the id of the item.
|
||||||
|
pub fn add(self: *Self, item: T) !Id {
|
||||||
|
const new_id = self.id_gen.getNextId();
|
||||||
|
errdefer self.id_gen.deleteId(new_id);
|
||||||
|
|
||||||
|
if (new_id >= self.data.items.len) {
|
||||||
|
try self.data.resize(new_id + 1);
|
||||||
|
try self.data_exists.resize(new_id + 1);
|
||||||
|
}
|
||||||
|
self.data.items[new_id] = item;
|
||||||
|
self.data_exists.set(new_id);
|
||||||
|
return new_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(self: *Self, id: Id, item: T) void {
|
||||||
|
self.data.items[id] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(self: *Self, id: Id) void {
|
||||||
|
self.data_exists.unset(id);
|
||||||
|
self.id_gen.deleteId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearRetainingCapacity(self: *Self) void {
|
||||||
|
self.data_exists.clearRetainingCapacity();
|
||||||
|
self.id_gen.clearRetainingCapacity();
|
||||||
|
self.data.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: Self, id: Id) ?T {
|
||||||
|
if (self.has(id)) {
|
||||||
|
return self.data.items[id];
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNoCheck(self: Self, id: Id) T {
|
||||||
|
return self.data.items[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPtr(self: *const Self, id: Id) ?*T {
|
||||||
|
if (self.has(id)) {
|
||||||
|
return &self.data.items[id];
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPtrNoCheck(self: Self, id: Id) *T {
|
||||||
|
return &self.data.items[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has(self: Self, id: Id) bool {
|
||||||
|
return self.data_exists.isSet(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(self: Self) usize {
|
||||||
|
return self.data.items.len - self.id_gen.next_ids.count;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "CompactUnorderedList" {
|
||||||
|
{
|
||||||
|
// General test.
|
||||||
|
var arr = CompactUnorderedList(u32, u8).init(t.allocator);
|
||||||
|
defer arr.deinit();
|
||||||
|
|
||||||
|
_ = try arr.add(1);
|
||||||
|
const id = try arr.add(2);
|
||||||
|
_ = try arr.add(3);
|
||||||
|
arr.remove(id);
|
||||||
|
// Test adding to a removed slot.
|
||||||
|
_ = try arr.add(4);
|
||||||
|
const id2 = try arr.add(5);
|
||||||
|
// Test iterator skips removed slot.
|
||||||
|
arr.remove(id2);
|
||||||
|
|
||||||
|
var iter = arr.iterator();
|
||||||
|
try t.expectEqual(iter.next(), 1);
|
||||||
|
try t.expectEqual(iter.next(), 4);
|
||||||
|
try t.expectEqual(iter.next(), 3);
|
||||||
|
try t.expectEqual(iter.next(), null);
|
||||||
|
try t.expectEqual(arr.size(), 3);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Empty test.
|
||||||
|
var arr = CompactUnorderedList(u32, u8).init(t.allocator);
|
||||||
|
defer arr.deinit();
|
||||||
|
var iter = arr.iterator();
|
||||||
|
try t.expectEqual(iter.next(), null);
|
||||||
|
try t.expectEqual(arr.size(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Buffer is a CompactUnorderedList.
|
||||||
|
pub fn CompactSinglyLinkedList(comptime Id: type, comptime T: type) type {
|
||||||
|
const Null = CompactNull(Id);
|
||||||
|
const Node = CompactSinglyLinkedListNode(Id, T);
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
first: Id,
|
||||||
|
nodes: CompactUnorderedList(Id, Node),
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) Self {
|
||||||
|
return .{
|
||||||
|
.first = Null,
|
||||||
|
.nodes = CompactUnorderedList(Id, Node).init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
self.nodes.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insertAfter(self: *Self, id: Id, data: T) !Id {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
const new = try self.nodes.add(.{
|
||||||
|
.next = self.nodes.getNoCheck(id).next,
|
||||||
|
.data = data,
|
||||||
|
});
|
||||||
|
self.nodes.getPtrNoCheck(id).next = new;
|
||||||
|
return new;
|
||||||
|
} else return error.NoElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeNext(self: *Self, id: Id) !bool {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
const at = self.nodes.getPtrNoCheck(id);
|
||||||
|
if (at.next != Null) {
|
||||||
|
const next = at.next;
|
||||||
|
at.next = self.nodes.getNoCheck(next).next;
|
||||||
|
self.nodes.remove(next);
|
||||||
|
return true;
|
||||||
|
} else return false;
|
||||||
|
} else return error.NoElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNode(self: *const Self, id: Id) ?Node {
|
||||||
|
return self.nodes.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNodeAssumeExists(self: *const Self, id: Id) Node {
|
||||||
|
return self.nodes.getNoCheck(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *const Self, id: Id) ?T {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
return self.nodes.getNoCheck(id).data;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNoCheck(self: *const Self, id: Id) T {
|
||||||
|
return self.nodes.getNoCheck(id).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getAt(self: *const Self, idx: usize) Id {
|
||||||
|
var i: u32 = 0;
|
||||||
|
var cur = self.first.?;
|
||||||
|
while (i != idx) : (i += 1) {
|
||||||
|
cur = self.getNext(cur).?;
|
||||||
|
}
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getFirst(self: *const Self) Id {
|
||||||
|
return self.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNext(self: Self, id: Id) ?Id {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
return self.nodes.getNoCheck(id).next;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepend(self: *Self, data: T) !Id {
|
||||||
|
const node = Node{
|
||||||
|
.next = self.first,
|
||||||
|
.data = data,
|
||||||
|
};
|
||||||
|
self.first = try self.nodes.add(node);
|
||||||
|
return self.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeFirst(self: *Self) bool {
|
||||||
|
if (self.first != Null) {
|
||||||
|
const next = self.getNodeAssumeExists(self.first).next;
|
||||||
|
self.nodes.remove(self.first);
|
||||||
|
self.first = next;
|
||||||
|
return true;
|
||||||
|
} else return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "CompactSinglyLinkedList" {
|
||||||
|
const Null = CompactNull(u32);
|
||||||
|
{
|
||||||
|
// General test.
|
||||||
|
var list = CompactSinglyLinkedList(u32, u8).init(t.allocator);
|
||||||
|
defer list.deinit();
|
||||||
|
|
||||||
|
const first = try list.prepend(1);
|
||||||
|
var last = first;
|
||||||
|
last = try list.insertAfter(last, 2);
|
||||||
|
last = try list.insertAfter(last, 3);
|
||||||
|
// Test remove next.
|
||||||
|
_ = try list.removeNext(first);
|
||||||
|
// Test remove first.
|
||||||
|
_ = list.removeFirst();
|
||||||
|
|
||||||
|
var id = list.getFirst();
|
||||||
|
try t.expectEqual(list.get(id), 3);
|
||||||
|
id = list.getNext(id).?;
|
||||||
|
try t.expectEqual(id, Null);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Empty test.
|
||||||
|
var list = CompactSinglyLinkedList(u32, u8).init(t.allocator);
|
||||||
|
defer list.deinit();
|
||||||
|
try t.expectEqual(list.getFirst(), Null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Id should be an unsigned integer type.
|
||||||
|
/// Max value of Id is used to indicate null. (An optional would increase the struct size.)
|
||||||
|
pub fn CompactSinglyLinkedListNode(comptime Id: type, comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
next: Id,
|
||||||
|
data: T,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn CompactNull(comptime Id: type) Id {
|
||||||
|
return comptime std.math.maxInt(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores multiple linked lists together in memory.
|
||||||
|
pub fn CompactManySinglyLinkedList(comptime ListId: type, comptime Index: type, comptime T: type) type {
|
||||||
|
const Node = CompactSinglyLinkedListNode(Index, T);
|
||||||
|
const Null = CompactNull(Index);
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
const List = struct {
|
||||||
|
head: ?Index,
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes: CompactUnorderedList(Index, Node),
|
||||||
|
lists: CompactUnorderedList(ListId, List),
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) Self {
|
||||||
|
return .{
|
||||||
|
.nodes = CompactUnorderedList(Index, Node).init(alloc),
|
||||||
|
.lists = CompactUnorderedList(ListId, List).init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
self.nodes.deinit();
|
||||||
|
self.lists.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns detached item.
|
||||||
|
pub fn detachAfter(self: *Self, id: Index) !Index {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
const item = self.getNodePtrAssumeExists(id);
|
||||||
|
const detached = item.next;
|
||||||
|
item.next = Null;
|
||||||
|
return detached;
|
||||||
|
} else return error.NoElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insertAfter(self: *Self, id: Index, data: T) !Index {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
const new = try self.nodes.add(.{
|
||||||
|
.next = self.nodes.getNoCheck(id).next,
|
||||||
|
.data = data,
|
||||||
|
});
|
||||||
|
self.nodes.getPtrNoCheck(id).next = new;
|
||||||
|
return new;
|
||||||
|
} else return error.NoElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setDetachedToEnd(self: *Self, id: Index, detached_id: Index) void {
|
||||||
|
const item = self.nodes.getPtr(id).?;
|
||||||
|
item.next = detached_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addListWithDetachedHead(self: *Self, id: Index) !ListId {
|
||||||
|
return self.lists.add(.{ .head = id });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addListWithHead(self: *Self, data: T) !ListId {
|
||||||
|
const item_id = try self.addDetachedItem(data);
|
||||||
|
return self.addListWithDetachedHead(item_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addEmptyList(self: *Self) !ListId {
|
||||||
|
return self.lists.add(.{ .head = Null });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addDetachedItem(self: *Self, data: T) !Index {
|
||||||
|
return try self.nodes.add(.{
|
||||||
|
.next = Null,
|
||||||
|
.data = data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepend(self: *Self, list_id: ListId, data: T) !Index {
|
||||||
|
const list = self.getList(list_id);
|
||||||
|
const item = Node{
|
||||||
|
.next = list.first,
|
||||||
|
.data = data,
|
||||||
|
};
|
||||||
|
list.first = try self.nodes.add(item);
|
||||||
|
return list.first.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeFirst(self: *Self, list_id: ListId) bool {
|
||||||
|
const list = self.getList(list_id);
|
||||||
|
if (list.first == null) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
const next = self.getNext(list.first.?);
|
||||||
|
self.nodes.remove(list.first.?);
|
||||||
|
list.first = next;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeNext(self: *Self, id: Index) !bool {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
const at = self.nodes.getPtrNoCheck(id);
|
||||||
|
if (at.next != Null) {
|
||||||
|
const next = at.next;
|
||||||
|
at.next = self.nodes.getNoCheck(next).next;
|
||||||
|
self.nodes.remove(next);
|
||||||
|
return true;
|
||||||
|
} else return false;
|
||||||
|
} else return error.NoElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeDetached(self: *Self, id: Index) void {
|
||||||
|
self.nodes.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getListPtr(self: *const Self, id: ListId) *List {
|
||||||
|
return self.lists.getPtr(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getListHead(self: *const Self, id: ListId) ?Index {
|
||||||
|
if (self.lists.has(id)) {
|
||||||
|
return self.lists.getNoCheck(id).head;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn findInList(self: Self, list_id: ListId, ctx: anytype, pred: fn (ctx: @TypeOf(ctx), buf: Self, item_id: Index) bool) ?Index {
|
||||||
|
var id = self.getListHead(list_id) orelse return null;
|
||||||
|
while (id != Null) {
|
||||||
|
if (pred(ctx, self, id)) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
id = self.getNextIdNoCheck(id);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has(self: Self, id: Index) bool {
|
||||||
|
return self.nodes.has(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNode(self: Self, id: Index) ?Node {
|
||||||
|
return self.nodes.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNodeAssumeExists(self: Self, id: Index) Node {
|
||||||
|
return self.nodes.getNoCheck(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNodePtr(self: Self, id: Index) ?*Node {
|
||||||
|
return self.nodes.getPtr(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNodePtrAssumeExists(self: Self, id: Index) *Node {
|
||||||
|
return self.nodes.getPtrNoCheck(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: Self, id: Index) ?T {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
return self.nodes.getNoCheck(id).data;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNoCheck(self: Self, id: Index) T {
|
||||||
|
return self.nodes.getNoCheck(id).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getIdAt(self: Self, list_id: ListId, idx: usize) Index {
|
||||||
|
var i: u32 = 0;
|
||||||
|
var cur: Index = self.getListHead(list_id).?;
|
||||||
|
while (i != idx) : (i += 1) {
|
||||||
|
cur = self.getNextId(cur).?;
|
||||||
|
}
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPtr(self: Self, id: Index) ?*T {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
return &self.nodes.getPtrNoCheck(id).data;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPtrNoCheck(self: Self, id: Index) *T {
|
||||||
|
return &self.nodes.getPtrNoCheck(id).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNextId(self: Self, id: Index) ?Index {
|
||||||
|
if (self.nodes.get(id)) |node| {
|
||||||
|
return node.next;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNextIdNoCheck(self: Self, id: Index) Index {
|
||||||
|
return self.nodes.getNoCheck(id).next;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNextNode(self: Self, id: Index) ?Node {
|
||||||
|
if (self.getNext(id)) |next| {
|
||||||
|
return self.getNode(next);
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNextData(self: *const Self, id: Index) ?T {
|
||||||
|
if (self.getNext(id)) |next| {
|
||||||
|
return self.get(next);
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "CompactManySinglyLinkedList" {
|
||||||
|
const Null = CompactNull(u32);
|
||||||
|
var lists = CompactManySinglyLinkedList(u32, u32, u32).init(t.allocator);
|
||||||
|
defer lists.deinit();
|
||||||
|
|
||||||
|
const list_id = try lists.addListWithHead(10);
|
||||||
|
const head = lists.getListHead(list_id).?;
|
||||||
|
|
||||||
|
// Test detachAfter.
|
||||||
|
const after = try lists.insertAfter(head, 20);
|
||||||
|
try t.expectEqual(lists.getNextIdNoCheck(head), after);
|
||||||
|
try t.expectEqual(lists.detachAfter(head), after);
|
||||||
|
try t.expectEqual(lists.getNextIdNoCheck(head), Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reuses deleted ids.
|
||||||
|
/// Uses a fifo id buffer to get the next id if not empty, otherwise it uses the next id counter.
|
||||||
|
pub fn CompactIdGenerator(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
start_id: T,
|
||||||
|
next_default_id: T,
|
||||||
|
next_ids: std.fifo.LinearFifo(T, .Dynamic),
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator, start_id: T) Self {
|
||||||
|
return .{
|
||||||
|
.start_id = start_id,
|
||||||
|
.next_default_id = start_id,
|
||||||
|
.next_ids = std.fifo.LinearFifo(T, .Dynamic).init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peekNextId(self: Self) T {
|
||||||
|
if (self.next_ids.readableLength() == 0) {
|
||||||
|
return self.next_default_id;
|
||||||
|
} else {
|
||||||
|
return self.next_ids.peekItem(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNextId(self: *Self) T {
|
||||||
|
if (self.next_ids.readableLength() == 0) {
|
||||||
|
defer self.next_default_id += 1;
|
||||||
|
return self.next_default_id;
|
||||||
|
} else {
|
||||||
|
return self.next_ids.readItem().?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearRetainingCapacity(self: *Self) void {
|
||||||
|
self.next_default_id = self.start_id;
|
||||||
|
self.next_ids.head = 0;
|
||||||
|
self.next_ids.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deleteId(self: *Self, id: T) void {
|
||||||
|
self.next_ids.writeItem(id) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
self.next_ids.deinit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "CompactIdGenerator" {
|
||||||
|
var gen = CompactIdGenerator(u16).init(t.allocator, 1);
|
||||||
|
defer gen.deinit();
|
||||||
|
try t.expectEqual(gen.getNextId(), 1);
|
||||||
|
try t.expectEqual(gen.getNextId(), 2);
|
||||||
|
gen.deleteId(1);
|
||||||
|
try t.expectEqual(gen.getNextId(), 1);
|
||||||
|
try t.expectEqual(gen.getNextId(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds linked lists in a compact buffer. Does not keep track of list heads.
|
||||||
|
/// This might replace CompactManySinglyLinkedList.
|
||||||
|
pub fn CompactSinglyLinkedListBuffer(comptime Id: type, comptime T: type) type {
|
||||||
|
const Null = comptime CompactNull(Id);
|
||||||
|
const OptId = Id;
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Node = CompactSinglyLinkedListNode(Id, T);
|
||||||
|
|
||||||
|
nodes: CompactUnorderedList(Id, Node),
|
||||||
|
|
||||||
|
pub fn init(alloc: std.mem.Allocator) Self {
|
||||||
|
return .{
|
||||||
|
.nodes = CompactUnorderedList(Id, Node).init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
self.nodes.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearRetainingCapacity(self: *Self) void {
|
||||||
|
self.nodes.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNode(self: Self, idx: Id) ?Node {
|
||||||
|
return self.nodes.get(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNodeNoCheck(self: Self, idx: Id) Node {
|
||||||
|
return self.nodes.getNoCheck(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNodePtrNoCheck(self: Self, idx: Id) *Node {
|
||||||
|
return self.nodes.getPtrNoCheck(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iterator(self: Self) CompactUnorderedList(Id, Node).Iterator {
|
||||||
|
return self.nodes.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iterFirstNoCheck(self: Self) Id {
|
||||||
|
var iter = self.nodes.iterator();
|
||||||
|
_ = iter.next();
|
||||||
|
return iter.cur_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iterFirstValueNoCheck(self: Self) T {
|
||||||
|
var iter = self.nodes.iterator();
|
||||||
|
return iter.next().?.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(self: Self) usize {
|
||||||
|
return self.nodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getLast(self: Self, id: Id) ?Id {
|
||||||
|
if (id == Null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
var cur = id;
|
||||||
|
while (cur != Null) {
|
||||||
|
const next = self.getNextNoCheck(cur);
|
||||||
|
if (next == Null) {
|
||||||
|
return cur;
|
||||||
|
}
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
unreachable;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: Self, id: Id) ?T {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
return self.nodes.getNoCheck(id).data;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNoCheck(self: Self, idx: Id) T {
|
||||||
|
return self.nodes.getNoCheck(idx).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPtrNoCheck(self: Self, idx: Id) *T {
|
||||||
|
return &self.nodes.getPtrNoCheck(idx).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNextNoCheck(self: Self, id: Id) OptId {
|
||||||
|
return self.nodes.getNoCheck(id).next;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNext(self: Self, id: Id) ?OptId {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
return self.nodes.getNoCheck(id).next;
|
||||||
|
} else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new head node.
|
||||||
|
pub fn add(self: *Self, data: T) !Id {
|
||||||
|
return try self.nodes.add(.{
|
||||||
|
.next = Null,
|
||||||
|
.data = data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insertBeforeHead(self: *Self, head_id: Id, data: T) !Id {
|
||||||
|
if (self.nodes.has(head_id)) {
|
||||||
|
return try self.nodes.add(.{
|
||||||
|
.next = head_id,
|
||||||
|
.data = data,
|
||||||
|
});
|
||||||
|
} else return error.NoElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insertBeforeHeadNoCheck(self: *Self, head_id: Id, data: T) !Id {
|
||||||
|
return try self.nodes.add(.{
|
||||||
|
.next = head_id,
|
||||||
|
.data = data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insertAfter(self: *Self, id: Id, data: T) !Id {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
const new = try self.nodes.add(.{
|
||||||
|
.next = self.nodes.getNoCheck(id).next,
|
||||||
|
.data = data,
|
||||||
|
});
|
||||||
|
self.nodes.getPtrNoCheck(id).next = new;
|
||||||
|
return new;
|
||||||
|
} else return error.NoElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeAfter(self: *Self, id: Id) !void {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
const next = self.getNextNoCheck(id);
|
||||||
|
if (next != Null) {
|
||||||
|
const next_next = self.getNextNoCheck(next);
|
||||||
|
self.nodes.getNoCheck(id).next = next_next;
|
||||||
|
self.nodes.remove(next);
|
||||||
|
}
|
||||||
|
} else return error.NoElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeAssumeNoPrev(self: *Self, id: Id) !void {
|
||||||
|
if (self.nodes.has(id)) {
|
||||||
|
self.nodes.remove(id);
|
||||||
|
} else return error.NoElement;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "CompactSinglyLinkedListBuffer" {
|
||||||
|
var buf = CompactSinglyLinkedListBuffer(u32, u32).init(t.allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
|
||||||
|
const head = try buf.add(1);
|
||||||
|
try t.expectEqual(buf.get(head).?, 1);
|
||||||
|
try t.expectEqual(buf.getNoCheck(head), 1);
|
||||||
|
try t.expectEqual(buf.getNode(head).?.data, 1);
|
||||||
|
try t.expectEqual(buf.getNodeNoCheck(head).data, 1);
|
||||||
|
|
||||||
|
const second = try buf.insertAfter(head, 2);
|
||||||
|
try t.expectEqual(buf.getNodeNoCheck(head).next, second);
|
||||||
|
try t.expectEqual(buf.getNoCheck(second), 2);
|
||||||
|
|
||||||
|
try buf.removeAssumeNoPrev(head);
|
||||||
|
try t.expectEqual(buf.get(head), null);
|
||||||
|
}
|
||||||
1390
examples/gkurve/data_structures/rb_tree.zig
Normal file
1390
examples/gkurve/data_structures/rb_tree.zig
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,7 @@ const std = @import("std");
|
||||||
const ft = @import("freetype");
|
const ft = @import("freetype");
|
||||||
const zigimg = @import("zigimg");
|
const zigimg = @import("zigimg");
|
||||||
const Atlas = @import("atlas.zig").Atlas;
|
const Atlas = @import("atlas.zig").Atlas;
|
||||||
|
const AtlasErr = @import("atlas.zig").Error;
|
||||||
const UVData = @import("atlas.zig").UVData;
|
const UVData = @import("atlas.zig").UVData;
|
||||||
const App = @import("main.zig").App;
|
const App = @import("main.zig").App;
|
||||||
const draw = @import("draw.zig");
|
const draw = @import("draw.zig");
|
||||||
|
|
@ -29,7 +30,8 @@ const WriterContext = struct {
|
||||||
position: Vec2,
|
position: Vec2,
|
||||||
text_color: Vec4,
|
text_color: Vec4,
|
||||||
};
|
};
|
||||||
const Writer = std.io.Writer(WriterContext, ft.Error, write);
|
const WriterError = ft.Error || std.mem.Allocator.Error || AtlasErr;
|
||||||
|
const Writer = std.io.Writer(WriterContext, WriterError, write);
|
||||||
|
|
||||||
pub fn writer(label: *Label, app: *App, position: Vec2, text_color: Vec4) Writer {
|
pub fn writer(label: *Label, app: *App, position: Vec2, text_color: Vec4) Writer {
|
||||||
return Writer{
|
return Writer{
|
||||||
|
|
@ -56,8 +58,7 @@ pub fn deinit(label: *Label) void {
|
||||||
label.char_map.deinit();
|
label.char_map.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: union ft.error and hashmap error
|
fn write(ctx: WriterContext, bytes: []const u8) WriterError!usize {
|
||||||
fn write(ctx: WriterContext, bytes: []const u8) ft.Error!usize {
|
|
||||||
var offset = Vec2{ 0, 0 };
|
var offset = Vec2{ 0, 0 };
|
||||||
for (bytes) |char| {
|
for (bytes) |char| {
|
||||||
switch (char) {
|
switch (char) {
|
||||||
|
|
@ -66,7 +67,7 @@ fn write(ctx: WriterContext, bytes: []const u8) ft.Error!usize {
|
||||||
offset[1] -= @intToFloat(f32, ctx.label.face.size().metrics().height >> 6);
|
offset[1] -= @intToFloat(f32, ctx.label.face.size().metrics().height >> 6);
|
||||||
},
|
},
|
||||||
' ' => {
|
' ' => {
|
||||||
const v = ctx.label.char_map.getOrPut(char) catch unreachable;
|
const v = try ctx.label.char_map.getOrPut(char);
|
||||||
if (!v.found_existing) {
|
if (!v.found_existing) {
|
||||||
try ctx.label.face.setCharSize(ctx.label.size * 64, 0, 50, 0);
|
try ctx.label.face.setCharSize(ctx.label.size * 64, 0, 50, 0);
|
||||||
try ctx.label.face.loadChar(char, .{ .render = true });
|
try ctx.label.face.loadChar(char, .{ .render = true });
|
||||||
|
|
@ -79,7 +80,7 @@ fn write(ctx: WriterContext, bytes: []const u8) ft.Error!usize {
|
||||||
offset[0] += @intToFloat(f32, v.value_ptr.metrics.horiAdvance >> 6);
|
offset[0] += @intToFloat(f32, v.value_ptr.metrics.horiAdvance >> 6);
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
const v = ctx.label.char_map.getOrPut(char) catch unreachable;
|
const v = try ctx.label.char_map.getOrPut(char);
|
||||||
if (!v.found_existing) {
|
if (!v.found_existing) {
|
||||||
try ctx.label.face.setCharSize(ctx.label.size * 64, 0, 50, 0);
|
try ctx.label.face.setCharSize(ctx.label.size * 64, 0, 50, 0);
|
||||||
try ctx.label.face.loadChar(char, .{ .render = true });
|
try ctx.label.face.loadChar(char, .{ .render = true });
|
||||||
|
|
@ -87,31 +88,47 @@ fn write(ctx: WriterContext, bytes: []const u8) ft.Error!usize {
|
||||||
const glyph_bitmap = glyph.bitmap();
|
const glyph_bitmap = glyph.bitmap();
|
||||||
const glyph_width = glyph_bitmap.width();
|
const glyph_width = glyph_bitmap.width();
|
||||||
const glyph_height = glyph_bitmap.rows();
|
const glyph_height = glyph_bitmap.rows();
|
||||||
var glyph_data = ctx.label.allocator.alloc(zigimg.color.Rgba32, glyph_width * glyph_height) catch unreachable;
|
|
||||||
|
// Add 1 pixel padding to texture to avoid bleeding over other textures
|
||||||
|
var glyph_data = try ctx.label.allocator.alloc(zigimg.color.Rgba32, (glyph_width + 2) * (glyph_height + 2));
|
||||||
defer ctx.label.allocator.free(glyph_data);
|
defer ctx.label.allocator.free(glyph_data);
|
||||||
const glyph_buffer = glyph_bitmap.buffer();
|
const glyph_buffer = glyph_bitmap.buffer();
|
||||||
for (glyph_data) |*data, i| {
|
for (glyph_data) |*data, i| {
|
||||||
const x = i % glyph_width;
|
const x = i % (glyph_width + 2);
|
||||||
const y = i / glyph_width;
|
const y = i / (glyph_width + 2);
|
||||||
const glyph_col = glyph_buffer[y * glyph_width + x];
|
|
||||||
|
// zig fmt: off
|
||||||
|
const glyph_col =
|
||||||
|
if (x == 0 or x == (glyph_width + 1) or y == 0 or y == (glyph_height + 1))
|
||||||
|
0
|
||||||
|
else
|
||||||
|
glyph_buffer[(y - 1) * glyph_width + (x - 1)];
|
||||||
|
// zig fmt: on
|
||||||
|
|
||||||
data.* = zigimg.color.Rgba32.initRGB(glyph_col, glyph_col, glyph_col);
|
data.* = zigimg.color.Rgba32.initRGB(glyph_col, glyph_col, glyph_col);
|
||||||
}
|
}
|
||||||
const glyph_atlas_region = ctx.app.texture_atlas_data.reserve(ctx.label.allocator, glyph_width, glyph_height) catch unreachable;
|
var glyph_atlas_region = try ctx.app.texture_atlas_data.reserve(ctx.label.allocator, glyph_width + 2, glyph_height + 2);
|
||||||
const glyph_uv_data = glyph_atlas_region.getUVData(@intToFloat(f32, ctx.app.texture_atlas_data.size));
|
|
||||||
ctx.app.texture_atlas_data.set(glyph_atlas_region, glyph_data);
|
ctx.app.texture_atlas_data.set(glyph_atlas_region, glyph_data);
|
||||||
|
|
||||||
|
glyph_atlas_region.x += 1;
|
||||||
|
glyph_atlas_region.y += 1;
|
||||||
|
glyph_atlas_region.width -= 2;
|
||||||
|
glyph_atlas_region.height -= 2;
|
||||||
|
const glyph_uv_data = glyph_atlas_region.getUVData(@intToFloat(f32, ctx.app.texture_atlas_data.size));
|
||||||
|
|
||||||
v.value_ptr.* = GlyphInfo{
|
v.value_ptr.* = GlyphInfo{
|
||||||
.uv_data = glyph_uv_data,
|
.uv_data = glyph_uv_data,
|
||||||
.metrics = glyph.metrics(),
|
.metrics = glyph.metrics(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
draw.quad(
|
|
||||||
|
try draw.quad(
|
||||||
ctx.app,
|
ctx.app,
|
||||||
ctx.position + offset + Vec2{ @intToFloat(f32, v.value_ptr.metrics.horiBearingX >> 6), @intToFloat(f32, (v.value_ptr.metrics.horiBearingY - v.value_ptr.metrics.height) >> 6) },
|
ctx.position + offset + Vec2{ @intToFloat(f32, v.value_ptr.metrics.horiBearingX >> 6), @intToFloat(f32, (v.value_ptr.metrics.horiBearingY - v.value_ptr.metrics.height) >> 6) },
|
||||||
.{ @intToFloat(f32, v.value_ptr.metrics.width >> 6), @intToFloat(f32, v.value_ptr.metrics.height >> 6) },
|
.{ @intToFloat(f32, v.value_ptr.metrics.width >> 6), @intToFloat(f32, v.value_ptr.metrics.height >> 6) },
|
||||||
.{ .blend_color = ctx.text_color },
|
.{ .blend_color = ctx.text_color },
|
||||||
v.value_ptr.uv_data,
|
v.value_ptr.uv_data,
|
||||||
) catch unreachable;
|
);
|
||||||
offset[0] += @intToFloat(f32, v.value_ptr.metrics.horiAdvance >> 6);
|
offset[0] += @intToFloat(f32, v.value_ptr.metrics.horiAdvance >> 6);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,8 @@ const glfw = @import("glfw");
|
||||||
const draw = @import("draw.zig");
|
const draw = @import("draw.zig");
|
||||||
const Atlas = @import("atlas.zig").Atlas;
|
const Atlas = @import("atlas.zig").Atlas;
|
||||||
const ft = @import("freetype");
|
const ft = @import("freetype");
|
||||||
const Label = @import("text.zig");
|
const Label = @import("label.zig");
|
||||||
|
const ResizableLabel = @import("resizable_label.zig");
|
||||||
|
|
||||||
pub const App = @This();
|
pub const App = @This();
|
||||||
|
|
||||||
|
|
@ -75,7 +76,11 @@ pub fn init(app: *App, engine: *mach.Engine) !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
const white_tex_scale = 80;
|
const white_tex_scale = 80;
|
||||||
const atlas_white_region = try app.texture_atlas_data.reserve(engine.allocator, white_tex_scale, white_tex_scale);
|
var atlas_white_region = try app.texture_atlas_data.reserve(engine.allocator, white_tex_scale, white_tex_scale);
|
||||||
|
atlas_white_region.x += 1;
|
||||||
|
atlas_white_region.y += 1;
|
||||||
|
atlas_white_region.width -= 2;
|
||||||
|
atlas_white_region.height -= 2;
|
||||||
const white_texture_uv_data = atlas_white_region.getUVData(atlas_float_size);
|
const white_texture_uv_data = atlas_white_region.getUVData(atlas_float_size);
|
||||||
var white_tex_data = try engine.allocator.alloc(zigimg.color.Rgba32, white_tex_scale * white_tex_scale);
|
var white_tex_data = try engine.allocator.alloc(zigimg.color.Rgba32, white_tex_scale * white_tex_scale);
|
||||||
std.mem.set(zigimg.color.Rgba32, white_tex_data, zigimg.color.Rgba32.initRGB(0xff, 0xff, 0xff));
|
std.mem.set(zigimg.color.Rgba32, white_tex_data, zigimg.color.Rgba32.initRGB(0xff, 0xff, 0xff));
|
||||||
|
|
@ -88,10 +93,16 @@ pub fn init(app: *App, engine: *mach.Engine) !void {
|
||||||
const lib = try ft.Library.init();
|
const lib = try ft.Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var label = try Label.init(lib, "freetype/upstream/assets/FiraSans-Regular.ttf", 0, 40, engine.allocator);
|
const size_multiplier = 5;
|
||||||
|
const character = 'e';
|
||||||
|
var label = try Label.init(lib, "freetype/upstream/assets/FiraSans-Regular.ttf", 0, 110 * size_multiplier, engine.allocator);
|
||||||
defer label.deinit();
|
defer label.deinit();
|
||||||
|
try label.print(app, &.{character}, .{}, @Vector(2, f32){ 50 * size_multiplier, 40 }, @Vector(4, f32){ 1, 1, 1, 1 });
|
||||||
|
|
||||||
try label.print(app, "All your game's bases are belong to us", .{}, @Vector(2, f32){ 0, 420 }, @Vector(4, f32){ 1, 1, 1, 1 });
|
var resizable_label: ResizableLabel = undefined;
|
||||||
|
try resizable_label.init(lib, "freetype/upstream/assets/FiraSans-Regular.ttf", 0, engine.allocator, white_texture_uv_data);
|
||||||
|
defer resizable_label.deinit();
|
||||||
|
try resizable_label.print(app, &.{character}, .{}, @Vector(4, f32){ 0, 40, 0, 0 }, @Vector(4, f32){ 1, 1, 1, 1 }, 80 * size_multiplier);
|
||||||
|
|
||||||
queue.writeTexture(
|
queue.writeTexture(
|
||||||
&.{ .texture = texture },
|
&.{ .texture = texture },
|
||||||
|
|
@ -206,8 +217,8 @@ pub fn init(app: *App, engine: *mach.Engine) !void {
|
||||||
});
|
});
|
||||||
|
|
||||||
const sampler = engine.device.createSampler(&.{
|
const sampler = engine.device.createSampler(&.{
|
||||||
.mag_filter = .linear,
|
// .mag_filter = .linear,
|
||||||
.min_filter = .linear,
|
// .min_filter = .linear,
|
||||||
});
|
});
|
||||||
|
|
||||||
const bind_group = engine.device.createBindGroup(
|
const bind_group = engine.device.createBindGroup(
|
||||||
|
|
|
||||||
442
examples/gkurve/resizable_label.zig
Normal file
442
examples/gkurve/resizable_label.zig
Normal file
|
|
@ -0,0 +1,442 @@
|
||||||
|
//! TODO: Refactor the API, maybe use a handle that contains the lib and other things and controls init and deinit of ft.Lib and other things
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const ft = @import("freetype");
|
||||||
|
const zigimg = @import("zigimg");
|
||||||
|
const Atlas = @import("atlas.zig").Atlas;
|
||||||
|
const AtlasErr = @import("atlas.zig").Error;
|
||||||
|
const UVData = @import("atlas.zig").UVData;
|
||||||
|
const App = @import("main.zig").App;
|
||||||
|
const draw = @import("draw.zig");
|
||||||
|
const Vertex = draw.Vertex;
|
||||||
|
const Tessellator = @import("tessellator.zig").Tessellator;
|
||||||
|
|
||||||
|
// If true, show the filled triangles green, the concave beziers blue and the convex ones red
|
||||||
|
const debug_colors = false;
|
||||||
|
|
||||||
|
pub const ResizableLabel = @This();
|
||||||
|
|
||||||
|
const Vec2 = @Vector(2, f32);
|
||||||
|
const Vec4 = @Vector(4, f32);
|
||||||
|
const VertexList = std.ArrayList(Vertex);
|
||||||
|
|
||||||
|
// All the data that a single character needs to be rendered
|
||||||
|
// TODO: hori/vert advance, write file format
|
||||||
|
const CharVertices = struct {
|
||||||
|
filled_vertices: VertexList,
|
||||||
|
filled_vertices_indices: std.ArrayList(u16),
|
||||||
|
// Concave vertices belong to the filled_vertices list, so just index them
|
||||||
|
concave_vertices: std.ArrayList(u16),
|
||||||
|
// The point outside of the convex bezier, doesn't belong to the filled vertices,
|
||||||
|
// But the other two points do, so put those in the indices
|
||||||
|
convex_vertices: VertexList,
|
||||||
|
convex_vertices_indices: std.ArrayList(u16),
|
||||||
|
};
|
||||||
|
|
||||||
|
face: ft.Face,
|
||||||
|
char_map: std.AutoHashMap(u8, CharVertices),
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
tessellator: Tessellator,
|
||||||
|
white_texture: UVData,
|
||||||
|
|
||||||
|
// The data that the write function needs
|
||||||
|
// TODO: move twxture here, don't limit to just white_texture
|
||||||
|
const WriterContext = struct {
|
||||||
|
label: *ResizableLabel,
|
||||||
|
app: *App,
|
||||||
|
position: Vec4,
|
||||||
|
text_color: Vec4,
|
||||||
|
text_size: u32,
|
||||||
|
};
|
||||||
|
const WriterError = ft.Error || std.mem.Allocator.Error || AtlasErr;
|
||||||
|
const Writer = std.io.Writer(WriterContext, WriterError, write);
|
||||||
|
|
||||||
|
pub fn writer(label: *ResizableLabel, app: *App, position: Vec4, text_color: Vec4, text_size: u32) Writer {
|
||||||
|
return Writer{
|
||||||
|
.context = .{
|
||||||
|
.label = label,
|
||||||
|
.app = app,
|
||||||
|
.position = position,
|
||||||
|
.text_color = text_color,
|
||||||
|
.text_size = text_size,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(self: *ResizableLabel, lib: ft.Library, font_path: []const u8, face_index: i32, allocator: std.mem.Allocator, white_texture: UVData) !void {
|
||||||
|
self.* = ResizableLabel{
|
||||||
|
.face = try lib.newFace(font_path, face_index),
|
||||||
|
.char_map = std.AutoHashMap(u8, CharVertices).init(allocator),
|
||||||
|
.allocator = allocator,
|
||||||
|
.tessellator = undefined,
|
||||||
|
.white_texture = white_texture,
|
||||||
|
};
|
||||||
|
self.tessellator.init(self.allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(label: *ResizableLabel) void {
|
||||||
|
label.face.deinit();
|
||||||
|
label.tessellator.deinit();
|
||||||
|
// FIXME:
|
||||||
|
// std.debug.todo("valueIterator() doesn't stop? How do we deallocate the values?");
|
||||||
|
// while (label.char_map.valueIterator().next()) |value| {
|
||||||
|
// _ = value;
|
||||||
|
// value.filled_vertices.deinit();
|
||||||
|
// value.filled_vertices_indices.deinit();
|
||||||
|
// value.convex_vertices.deinit();
|
||||||
|
// value.convex_vertices_indices.deinit();
|
||||||
|
// value.concave_vertices.deinit();
|
||||||
|
// }
|
||||||
|
label.char_map.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle offsets
|
||||||
|
// FIXME: many useless allocations for the arraylists
|
||||||
|
fn write(ctx: WriterContext, bytes: []const u8) WriterError!usize {
|
||||||
|
var offset = Vec4{ 0, 0, 0, 0 };
|
||||||
|
for (bytes) |char| {
|
||||||
|
switch (char) {
|
||||||
|
'\n' => {
|
||||||
|
offset[0] = 0;
|
||||||
|
offset[1] -= @intToFloat(f32, ctx.label.face.sizeMetrics().?.height >> 6);
|
||||||
|
std.debug.todo("New line not implemented yet");
|
||||||
|
},
|
||||||
|
' ' => {
|
||||||
|
std.debug.todo("Space character not implemented yet");
|
||||||
|
// const v = try ctx.label.char_map.getOrPut(char);
|
||||||
|
// if (!v.found_existing) {
|
||||||
|
// try ctx.label.face.setCharSize(ctx.label.size * 64, 0, 50, 0);
|
||||||
|
// try ctx.label.face.loadChar(char, .{ .render = true });
|
||||||
|
// const glyph = ctx.label.face.glyph;
|
||||||
|
// v.value_ptr.* = GlyphInfo{
|
||||||
|
// .uv_data = undefined,
|
||||||
|
// .metrics = glyph.metrics(),
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// offset[0] += @intToFloat(f32, v.value_ptr.metrics.horiAdvance >> 6);
|
||||||
|
},
|
||||||
|
else => {
|
||||||
|
const v = try ctx.label.char_map.getOrPut(char);
|
||||||
|
if (!v.found_existing) {
|
||||||
|
try ctx.label.face.loadChar(char, .{ .no_scale = true, .no_bitmap = true });
|
||||||
|
const glyph = ctx.label.face.glyph;
|
||||||
|
|
||||||
|
// Use a big scale and then scale to the actual text size
|
||||||
|
const multiplier = 1024 << 6;
|
||||||
|
const matrix = ft.Matrix{
|
||||||
|
.xx = 1 * multiplier,
|
||||||
|
.xy = 0 * multiplier,
|
||||||
|
.yx = 0 * multiplier,
|
||||||
|
.yy = 1 * multiplier,
|
||||||
|
};
|
||||||
|
glyph.outline().?.transform(matrix);
|
||||||
|
|
||||||
|
v.value_ptr.* = CharVertices{
|
||||||
|
.filled_vertices = VertexList.init(ctx.label.allocator),
|
||||||
|
.filled_vertices_indices = std.ArrayList(u16).init(ctx.label.allocator),
|
||||||
|
.concave_vertices = std.ArrayList(u16).init(ctx.label.allocator),
|
||||||
|
.convex_vertices = VertexList.init(ctx.label.allocator),
|
||||||
|
.convex_vertices_indices = std.ArrayList(u16).init(ctx.label.allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
var outline_ctx = OutlineContext{
|
||||||
|
.outline_verts = std.ArrayList(std.ArrayList(Vec2)).init(ctx.label.allocator),
|
||||||
|
.inside_verts = std.ArrayList(Vec2).init(ctx.label.allocator),
|
||||||
|
.concave_vertices = std.ArrayList(Vec2).init(ctx.label.allocator),
|
||||||
|
.convex_vertices = std.ArrayList(Vec2).init(ctx.label.allocator),
|
||||||
|
};
|
||||||
|
defer outline_ctx.outline_verts.deinit();
|
||||||
|
defer {
|
||||||
|
for (outline_ctx.outline_verts.items) |*item| {
|
||||||
|
item.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer outline_ctx.inside_verts.deinit();
|
||||||
|
defer outline_ctx.concave_vertices.deinit();
|
||||||
|
defer outline_ctx.convex_vertices.deinit();
|
||||||
|
|
||||||
|
const callbacks = ft.Outline.OutlineFuncs(*OutlineContext){
|
||||||
|
.move_to = moveToFunction,
|
||||||
|
.line_to = lineToFunction,
|
||||||
|
.conic_to = conicToFunction,
|
||||||
|
.cubic_to = cubicToFunction,
|
||||||
|
.shift = 0,
|
||||||
|
.delta = 0,
|
||||||
|
};
|
||||||
|
try ctx.label.face.glyph.outline().?.decompose(&outline_ctx, callbacks);
|
||||||
|
|
||||||
|
uniteOutsideAndInsideVertices(&outline_ctx);
|
||||||
|
|
||||||
|
// Tessellator.triangulatePolygons() doesn't seem to work, so just
|
||||||
|
// call triangulatePolygon() for each polygon, and put the results all
|
||||||
|
// in all_outlines and all_indices
|
||||||
|
var all_outlines = std.ArrayList(Vec2).init(ctx.label.allocator);
|
||||||
|
defer all_outlines.deinit();
|
||||||
|
var all_indices = std.ArrayList(u16).init(ctx.label.allocator);
|
||||||
|
defer all_indices.deinit();
|
||||||
|
var idx_offset: u16 = 0;
|
||||||
|
for (outline_ctx.outline_verts.items) |item| {
|
||||||
|
ctx.label.tessellator.triangulatePolygon(item.items);
|
||||||
|
defer ctx.label.tessellator.clearBuffers();
|
||||||
|
try all_outlines.appendSlice(ctx.label.tessellator.out_verts.items);
|
||||||
|
for (ctx.label.tessellator.out_idxes.items) |idx| {
|
||||||
|
try all_indices.append(idx + idx_offset);
|
||||||
|
}
|
||||||
|
idx_offset += @intCast(u16, ctx.label.tessellator.out_verts.items.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (all_outlines.items) |item| {
|
||||||
|
// FIXME: The uv_data is wrong, should be pushed up by the lowest a character can be
|
||||||
|
const vertex_uv = item / @splat(2, @as(f32, 1024 << 6));
|
||||||
|
const vertex_pos = Vec4{ item[0], item[1], 0, 1 };
|
||||||
|
try v.value_ptr.filled_vertices.append(Vertex{ .pos = vertex_pos, .uv = vertex_uv });
|
||||||
|
}
|
||||||
|
try v.value_ptr.filled_vertices_indices.appendSlice(all_indices.items);
|
||||||
|
|
||||||
|
// FIXME: instead of finding the closest vertex and use its index maybe use indices directly in the moveTo,... functions
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < outline_ctx.concave_vertices.items.len) : (i += 1) {
|
||||||
|
for (all_outlines.items) |item, j| {
|
||||||
|
const dist = @reduce(.Add, (item - outline_ctx.concave_vertices.items[i]) * (item - outline_ctx.concave_vertices.items[i]));
|
||||||
|
if (dist < 0.1) {
|
||||||
|
try v.value_ptr.concave_vertices.append(@truncate(u16, j));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
while (i < outline_ctx.convex_vertices.items.len) : (i += 3) {
|
||||||
|
const vert = outline_ctx.convex_vertices.items[i];
|
||||||
|
const vertex_uv = vert / @splat(2, @as(f32, 1024 << 6));
|
||||||
|
const vertex_pos = Vec4{ vert[0], vert[1], 0, 1 };
|
||||||
|
try v.value_ptr.convex_vertices.append(Vertex{ .pos = vertex_pos, .uv = vertex_uv });
|
||||||
|
|
||||||
|
for (all_outlines.items) |item, j| {
|
||||||
|
const dist1 = @reduce(.Add, (item - outline_ctx.convex_vertices.items[i + 1]) * (item - outline_ctx.convex_vertices.items[i + 1]));
|
||||||
|
if (dist1 < 0.1) {
|
||||||
|
try v.value_ptr.convex_vertices_indices.append(@truncate(u16, j));
|
||||||
|
}
|
||||||
|
|
||||||
|
const dist2 = @reduce(.Add, (item - outline_ctx.convex_vertices.items[i + 2]) * (item - outline_ctx.convex_vertices.items[i + 2]));
|
||||||
|
if (dist2 < 0.1) {
|
||||||
|
try v.value_ptr.convex_vertices_indices.append(@truncate(u16, j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.label.tessellator.clearBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the data and apply resizing of pos and uv
|
||||||
|
var filled_vertices_after_offset = try ctx.label.allocator.alloc(Vertex, v.value_ptr.filled_vertices.items.len);
|
||||||
|
defer ctx.label.allocator.free(filled_vertices_after_offset);
|
||||||
|
for (filled_vertices_after_offset) |*vert, i| {
|
||||||
|
vert.* = v.value_ptr.filled_vertices.items[i];
|
||||||
|
vert.pos *= Vec4{ @intToFloat(f32, ctx.text_size) / 1024, @intToFloat(f32, ctx.text_size) / 1024, 0, 1 };
|
||||||
|
vert.pos += ctx.position + offset;
|
||||||
|
vert.uv = vert.uv * ctx.label.white_texture.width_and_height + ctx.label.white_texture.bottom_left;
|
||||||
|
}
|
||||||
|
|
||||||
|
var actual_filled_vertices_to_use = try ctx.label.allocator.alloc(Vertex, v.value_ptr.filled_vertices_indices.items.len);
|
||||||
|
defer ctx.label.allocator.free(actual_filled_vertices_to_use);
|
||||||
|
for (actual_filled_vertices_to_use) |*vert, i| {
|
||||||
|
vert.* = filled_vertices_after_offset[v.value_ptr.filled_vertices_indices.items[i]];
|
||||||
|
}
|
||||||
|
try ctx.app.vertices.appendSlice(actual_filled_vertices_to_use);
|
||||||
|
|
||||||
|
if (debug_colors) {
|
||||||
|
try ctx.app.fragment_uniform_list.appendNTimes(.{ .blend_color = .{ 0, 1, 0, 1 } }, actual_filled_vertices_to_use.len / 3);
|
||||||
|
} else {
|
||||||
|
try ctx.app.fragment_uniform_list.appendNTimes(.{ .blend_color = ctx.text_color }, actual_filled_vertices_to_use.len / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
var convex_vertices_after_offset = try ctx.label.allocator.alloc(Vertex, v.value_ptr.convex_vertices.items.len + v.value_ptr.convex_vertices_indices.items.len);
|
||||||
|
defer ctx.label.allocator.free(convex_vertices_after_offset);
|
||||||
|
var j: u16 = 0;
|
||||||
|
var k: u16 = 0;
|
||||||
|
while (j < convex_vertices_after_offset.len) : (j += 3) {
|
||||||
|
convex_vertices_after_offset[j] = v.value_ptr.convex_vertices.items[j / 3];
|
||||||
|
convex_vertices_after_offset[j].pos *= Vec4{ @intToFloat(f32, ctx.text_size) / 1024, @intToFloat(f32, ctx.text_size) / 1024, 0, 1 };
|
||||||
|
convex_vertices_after_offset[j].pos += ctx.position + offset;
|
||||||
|
convex_vertices_after_offset[j].uv = convex_vertices_after_offset[j].uv * ctx.label.white_texture.width_and_height + ctx.label.white_texture.bottom_left;
|
||||||
|
|
||||||
|
convex_vertices_after_offset[j + 1] = filled_vertices_after_offset[v.value_ptr.convex_vertices_indices.items[k]];
|
||||||
|
convex_vertices_after_offset[j + 2] = filled_vertices_after_offset[v.value_ptr.convex_vertices_indices.items[k + 1]];
|
||||||
|
k += 2;
|
||||||
|
}
|
||||||
|
try ctx.app.vertices.appendSlice(convex_vertices_after_offset);
|
||||||
|
|
||||||
|
if (debug_colors) {
|
||||||
|
try ctx.app.fragment_uniform_list.appendNTimes(.{ .type = .convex, .blend_color = .{ 1, 0, 0, 1 } }, convex_vertices_after_offset.len / 3);
|
||||||
|
} else {
|
||||||
|
try ctx.app.fragment_uniform_list.appendNTimes(.{ .type = .convex, .blend_color = ctx.text_color }, convex_vertices_after_offset.len / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
var concave_vertices_after_offset = try ctx.label.allocator.alloc(Vertex, v.value_ptr.concave_vertices.items.len);
|
||||||
|
defer ctx.label.allocator.free(concave_vertices_after_offset);
|
||||||
|
for (concave_vertices_after_offset) |*vert, i| {
|
||||||
|
vert.* = filled_vertices_after_offset[v.value_ptr.concave_vertices.items[i]];
|
||||||
|
}
|
||||||
|
try ctx.app.vertices.appendSlice(concave_vertices_after_offset);
|
||||||
|
|
||||||
|
if (debug_colors) {
|
||||||
|
try ctx.app.fragment_uniform_list.appendNTimes(.{ .type = .concave, .blend_color = .{ 0, 0, 1, 1 } }, concave_vertices_after_offset.len / 3);
|
||||||
|
} else {
|
||||||
|
try ctx.app.fragment_uniform_list.appendNTimes(.{ .type = .concave, .blend_color = ctx.text_color }, concave_vertices_after_offset.len / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.app.update_vertex_buffer = true;
|
||||||
|
ctx.app.update_frag_uniform_buffer = true;
|
||||||
|
|
||||||
|
// offset[0] += @intToFloat(f32, v.value_ptr.metrics.horiAdvance >> 6);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First move to initialize the outline, (first point),
|
||||||
|
// After many Q L or C, we will come back to the first point and then call M again if we need to hollow
|
||||||
|
// On the second M, we instead use an L to connect the first point to the start of the hollow path.
|
||||||
|
// We then follow like normal and at the end of the hollow path we use another L to close the path.
|
||||||
|
|
||||||
|
// This is basically how an o would be drawn, each ┌... character is a Vertex
|
||||||
|
// ┌--------┐
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// | ┌----┐ |
|
||||||
|
// └-┘ | | Consider the vertices here and below to be at the same height, they are coincident
|
||||||
|
// ┌-┐ | |
|
||||||
|
// | └----┘ |
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// └--------┘
|
||||||
|
|
||||||
|
const OutlineContext = struct {
|
||||||
|
// There may be more than one polygon (for example with 'i' we have the polygon of the base and another for the circle)
|
||||||
|
outline_verts: std.ArrayList(std.ArrayList(Vec2)),
|
||||||
|
|
||||||
|
// The internal outline, used for carving the shape (for example in a, we would first get the outline of the a, but if we stopped there, it woul
|
||||||
|
// be filled, so we need another outline for carving the filled polygon)
|
||||||
|
inside_verts: std.ArrayList(Vec2),
|
||||||
|
|
||||||
|
// For the concave and convex beziers
|
||||||
|
concave_vertices: std.ArrayList(Vec2),
|
||||||
|
convex_vertices: std.ArrayList(Vec2),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If there are elements in inside_verts, unite them with the outline_verts, effectively carving the shape
|
||||||
|
fn uniteOutsideAndInsideVertices(ctx: *OutlineContext) void {
|
||||||
|
if (ctx.inside_verts.items.len != 0) {
|
||||||
|
// Check which point of outline is closer to the first of inside
|
||||||
|
var last_outline = &ctx.outline_verts.items[ctx.outline_verts.items.len - 1];
|
||||||
|
const closest_to_inside: usize = blk: {
|
||||||
|
const first_point_inside = ctx.inside_verts.items[0];
|
||||||
|
var min: f32 = std.math.f32_max;
|
||||||
|
var closest_index: usize = undefined;
|
||||||
|
|
||||||
|
for (last_outline.items) |item, i| {
|
||||||
|
const dist = @reduce(.Add, (item - first_point_inside) * (item - first_point_inside));
|
||||||
|
if (dist < min) {
|
||||||
|
min = dist;
|
||||||
|
closest_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break :blk closest_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.inside_verts.append(last_outline.items[closest_to_inside]) catch unreachable;
|
||||||
|
last_outline.insertSlice(closest_to_inside + 1, ctx.inside_verts.items) catch unreachable;
|
||||||
|
ctx.inside_verts.clearRetainingCapacity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Return also allocation error
|
||||||
|
fn moveToFunction(ctx: *OutlineContext, _to: ft.Vector) ft.Error!void {
|
||||||
|
// std.log.info("M {} {}", .{ to.x, to.y });
|
||||||
|
uniteOutsideAndInsideVertices(ctx);
|
||||||
|
|
||||||
|
const to = Vec2{ @intToFloat(f32, _to.x), @intToFloat(f32, _to.y) };
|
||||||
|
|
||||||
|
// TODO: Use raycasting of the edges for better accuracy on wether a point is inside the outline or not
|
||||||
|
const new_point_is_inside = blk: {
|
||||||
|
if (ctx.outline_verts.items.len == 0) {
|
||||||
|
break :blk false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minx: f32 = std.math.f32_max;
|
||||||
|
var maxx: f32 = std.math.f32_min;
|
||||||
|
var miny: f32 = std.math.f32_max;
|
||||||
|
var maxy: f32 = std.math.f32_min;
|
||||||
|
for (ctx.outline_verts.items[ctx.outline_verts.items.len - 1].items) |item| {
|
||||||
|
minx = @minimum(item[0], minx);
|
||||||
|
maxx = @maximum(item[0], maxx);
|
||||||
|
miny = @minimum(item[1], miny);
|
||||||
|
maxy = @maximum(item[1], maxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
break :blk (to[0] >= minx) and (to[0] <= maxx) and (to[1] >= miny) and (to[1] <= maxy);
|
||||||
|
};
|
||||||
|
|
||||||
|
// If the point is inside, put it in the inside verts
|
||||||
|
if (new_point_is_inside) {
|
||||||
|
ctx.inside_verts.append(to) catch unreachable;
|
||||||
|
} else {
|
||||||
|
// Otherwise create a new polygon
|
||||||
|
var new_outline_list = std.ArrayList(Vec2).init(ctx.outline_verts.allocator);
|
||||||
|
new_outline_list.append(to) catch unreachable;
|
||||||
|
ctx.outline_verts.append(new_outline_list) catch unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lineToFunction(ctx: *OutlineContext, to: ft.Vector) ft.Error!void {
|
||||||
|
// std.log.info("L {} {}", .{ to.x, to.y });
|
||||||
|
|
||||||
|
// If inside_verts is not empty, we need to fill it
|
||||||
|
if (ctx.inside_verts.items.len != 0) {
|
||||||
|
ctx.inside_verts.append(.{ @intToFloat(f32, to.x), @intToFloat(f32, to.y) }) catch unreachable;
|
||||||
|
} else {
|
||||||
|
// Otherwise append the new point to the last polygon
|
||||||
|
ctx.outline_verts.items[ctx.outline_verts.items.len - 1].append(.{ @intToFloat(f32, to.x), @intToFloat(f32, to.y) }) catch unreachable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn conicToFunction(ctx: *OutlineContext, _control: ft.Vector, _to: ft.Vector) ft.Error!void {
|
||||||
|
// std.log.info("C {} {} {} {}", .{ control.x, control.y, to.x, to.y });
|
||||||
|
const control = Vec2{ @intToFloat(f32, _control.x), @intToFloat(f32, _control.y) };
|
||||||
|
const to = Vec2{ @intToFloat(f32, _to.x), @intToFloat(f32, _to.y) };
|
||||||
|
|
||||||
|
// Either the inside verts or the outine ones
|
||||||
|
var verts_to_write = if (ctx.inside_verts.items.len != 0) &ctx.inside_verts else &ctx.outline_verts.items[ctx.outline_verts.items.len - 1];
|
||||||
|
const previous_point = verts_to_write.items[verts_to_write.items.len - 1];
|
||||||
|
|
||||||
|
const vertices = [_]Vec2{ control, to, previous_point };
|
||||||
|
|
||||||
|
const vec1 = control - previous_point;
|
||||||
|
const vec2 = to - control;
|
||||||
|
|
||||||
|
// if ccw, it's concave, else it's convex
|
||||||
|
if ((vec1[0] * vec2[1] - vec1[1] * vec2[0]) > 0) {
|
||||||
|
ctx.concave_vertices.appendSlice(&vertices) catch unreachable;
|
||||||
|
verts_to_write.append(control) catch unreachable;
|
||||||
|
} else {
|
||||||
|
ctx.convex_vertices.appendSlice(&vertices) catch unreachable;
|
||||||
|
}
|
||||||
|
verts_to_write.append(to) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doesn't seem to be used much
|
||||||
|
fn cubicToFunction(ctx: *OutlineContext, control_0: ft.Vector, control_1: ft.Vector, to: ft.Vector) ft.Error!void {
|
||||||
|
_ = ctx;
|
||||||
|
_ = control_0;
|
||||||
|
_ = control_1;
|
||||||
|
_ = to;
|
||||||
|
@panic("TODO: search how to approximate cubic bezier with quadratic ones");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print(label: *ResizableLabel, app: *App, comptime fmt: []const u8, args: anytype, position: Vec4, text_color: Vec4, text_size: u32) !void {
|
||||||
|
const w = writer(label, app, position, text_color, text_size);
|
||||||
|
try w.print(fmt, args);
|
||||||
|
}
|
||||||
1857
examples/gkurve/tessellator.zig
Normal file
1857
examples/gkurve/tessellator.zig
Normal file
File diff suppressed because it is too large
Load diff
314
examples/gkurve/tracy.zig
Normal file
314
examples/gkurve/tracy.zig
Normal file
|
|
@ -0,0 +1,314 @@
|
||||||
|
// Copied from zig/src/tracy.zig
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
// TODO: integrate with tracy?
|
||||||
|
// const build_options = @import("build_options");
|
||||||
|
|
||||||
|
// pub const enable = if (builtin.is_test) false else build_options.enable_tracy;
|
||||||
|
// pub const enable_allocation = enable and build_options.enable_tracy_allocation;
|
||||||
|
// pub const enable_callstack = enable and build_options.enable_tracy_callstack;
|
||||||
|
pub const enable = false;
|
||||||
|
pub const enable_allocation = enable and false;
|
||||||
|
pub const enable_callstack = enable and false;
|
||||||
|
|
||||||
|
// TODO: make this configurable
|
||||||
|
const callstack_depth = 10;
|
||||||
|
|
||||||
|
const ___tracy_c_zone_context = extern struct {
|
||||||
|
id: u32,
|
||||||
|
active: c_int,
|
||||||
|
|
||||||
|
pub inline fn end(self: @This()) void {
|
||||||
|
___tracy_emit_zone_end(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn addText(self: @This(), text: []const u8) void {
|
||||||
|
___tracy_emit_zone_text(self, text.ptr, text.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn setName(self: @This(), name: []const u8) void {
|
||||||
|
___tracy_emit_zone_name(self, name.ptr, name.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn setColor(self: @This(), color: u32) void {
|
||||||
|
___tracy_emit_zone_color(self, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn setValue(self: @This(), value: u64) void {
|
||||||
|
___tracy_emit_zone_value(self, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Ctx = if (enable) ___tracy_c_zone_context else struct {
|
||||||
|
pub inline fn end(self: @This()) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn addText(self: @This(), text: []const u8) void {
|
||||||
|
_ = self;
|
||||||
|
_ = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn setName(self: @This(), name: []const u8) void {
|
||||||
|
_ = self;
|
||||||
|
_ = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn setColor(self: @This(), color: u32) void {
|
||||||
|
_ = self;
|
||||||
|
_ = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn setValue(self: @This(), value: u64) void {
|
||||||
|
_ = self;
|
||||||
|
_ = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub inline fn trace(comptime src: std.builtin.SourceLocation) Ctx {
|
||||||
|
if (!enable) return .{};
|
||||||
|
|
||||||
|
if (enable_callstack) {
|
||||||
|
return ___tracy_emit_zone_begin_callstack(&.{
|
||||||
|
.name = null,
|
||||||
|
.function = src.fn_name.ptr,
|
||||||
|
.file = src.file.ptr,
|
||||||
|
.line = src.line,
|
||||||
|
.color = 0,
|
||||||
|
}, callstack_depth, 1);
|
||||||
|
} else {
|
||||||
|
return ___tracy_emit_zone_begin(&.{
|
||||||
|
.name = null,
|
||||||
|
.function = src.fn_name.ptr,
|
||||||
|
.file = src.file.ptr,
|
||||||
|
.line = src.line,
|
||||||
|
.color = 0,
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn traceNamed(comptime src: std.builtin.SourceLocation, comptime name: [:0]const u8) Ctx {
|
||||||
|
if (!enable) return .{};
|
||||||
|
|
||||||
|
if (enable_callstack) {
|
||||||
|
return ___tracy_emit_zone_begin_callstack(&.{
|
||||||
|
.name = name.ptr,
|
||||||
|
.function = src.fn_name.ptr,
|
||||||
|
.file = src.file.ptr,
|
||||||
|
.line = src.line,
|
||||||
|
.color = 0,
|
||||||
|
}, callstack_depth, 1);
|
||||||
|
} else {
|
||||||
|
return ___tracy_emit_zone_begin(&.{
|
||||||
|
.name = name.ptr,
|
||||||
|
.function = src.fn_name.ptr,
|
||||||
|
.file = src.file.ptr,
|
||||||
|
.line = src.line,
|
||||||
|
.color = 0,
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tracyAllocator(allocator: std.mem.Allocator) TracyAllocator(null) {
|
||||||
|
return TracyAllocator(null).init(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn TracyAllocator(comptime name: ?[:0]const u8) type {
|
||||||
|
return struct {
|
||||||
|
parent_allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn init(parent_allocator: std.mem.Allocator) Self {
|
||||||
|
return .{
|
||||||
|
.parent_allocator = parent_allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocator(self: *Self) std.mem.Allocator {
|
||||||
|
return std.mem.Allocator.init(self, allocFn, resizeFn, freeFn);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocFn(self: *Self, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) std.mem.Allocator.Error![]u8 {
|
||||||
|
const result = self.parent_allocator.rawAlloc(len, ptr_align, len_align, ret_addr);
|
||||||
|
if (result) |data| {
|
||||||
|
if (data.len != 0) {
|
||||||
|
if (name) |n| {
|
||||||
|
allocNamed(data.ptr, data.len, n);
|
||||||
|
} else {
|
||||||
|
alloc(data.ptr, data.len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else |_| {
|
||||||
|
messageColor("allocation failed", 0xFF0000);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resizeFn(self: *Self, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) ?usize {
|
||||||
|
if (self.parent_allocator.rawResize(buf, buf_align, new_len, len_align, ret_addr)) |resized_len| {
|
||||||
|
if (name) |n| {
|
||||||
|
freeNamed(buf.ptr, n);
|
||||||
|
allocNamed(buf.ptr, resized_len, n);
|
||||||
|
} else {
|
||||||
|
free(buf.ptr);
|
||||||
|
alloc(buf.ptr, resized_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resized_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// during normal operation the compiler hits this case thousands of times due to this
|
||||||
|
// emitting messages for it is both slow and causes clutter
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn freeFn(self: *Self, buf: []u8, buf_align: u29, ret_addr: usize) void {
|
||||||
|
self.parent_allocator.rawFree(buf, buf_align, ret_addr);
|
||||||
|
// this condition is to handle free being called on an empty slice that was never even allocated
|
||||||
|
// example case: `std.process.getSelfExeSharedLibPaths` can return `&[_][:0]u8{}`
|
||||||
|
if (buf.len != 0) {
|
||||||
|
if (name) |n| {
|
||||||
|
freeNamed(buf.ptr, n);
|
||||||
|
} else {
|
||||||
|
free(buf.ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function only accepts comptime known strings, see `messageCopy` for runtime strings
|
||||||
|
pub inline fn message(comptime msg: [:0]const u8) void {
|
||||||
|
if (!enable) return;
|
||||||
|
___tracy_emit_messageL(msg.ptr, if (enable_callstack) callstack_depth else 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function only accepts comptime known strings, see `messageColorCopy` for runtime strings
|
||||||
|
pub inline fn messageColor(comptime msg: [:0]const u8, color: u32) void {
|
||||||
|
if (!enable) return;
|
||||||
|
___tracy_emit_messageLC(msg.ptr, color, if (enable_callstack) callstack_depth else 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn messageCopy(msg: []const u8) void {
|
||||||
|
if (!enable) return;
|
||||||
|
___tracy_emit_message(msg.ptr, msg.len, if (enable_callstack) callstack_depth else 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn messageColorCopy(msg: [:0]const u8, color: u32) void {
|
||||||
|
if (!enable) return;
|
||||||
|
___tracy_emit_messageC(msg.ptr, msg.len, color, if (enable_callstack) callstack_depth else 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn frameMark() void {
|
||||||
|
if (!enable) return;
|
||||||
|
___tracy_emit_frame_mark(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn frameMarkNamed(comptime name: [:0]const u8) void {
|
||||||
|
if (!enable) return;
|
||||||
|
___tracy_emit_frame_mark(name.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn namedFrame(comptime name: [:0]const u8) Frame(name) {
|
||||||
|
frameMarkStart(name);
|
||||||
|
return .{};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Frame(comptime name: [:0]const u8) type {
|
||||||
|
return struct {
|
||||||
|
pub fn end(_: @This()) void {
|
||||||
|
frameMarkEnd(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn frameMarkStart(comptime name: [:0]const u8) void {
|
||||||
|
if (!enable) return;
|
||||||
|
___tracy_emit_frame_mark_start(name.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn frameMarkEnd(comptime name: [:0]const u8) void {
|
||||||
|
if (!enable) return;
|
||||||
|
___tracy_emit_frame_mark_end(name.ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern fn ___tracy_emit_frame_mark_start(name: [*:0]const u8) void;
|
||||||
|
extern fn ___tracy_emit_frame_mark_end(name: [*:0]const u8) void;
|
||||||
|
|
||||||
|
inline fn alloc(ptr: [*]u8, len: usize) void {
|
||||||
|
if (!enable) return;
|
||||||
|
|
||||||
|
if (enable_callstack) {
|
||||||
|
___tracy_emit_memory_alloc_callstack(ptr, len, callstack_depth, 0);
|
||||||
|
} else {
|
||||||
|
___tracy_emit_memory_alloc(ptr, len, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn allocNamed(ptr: [*]u8, len: usize, comptime name: [:0]const u8) void {
|
||||||
|
if (!enable) return;
|
||||||
|
|
||||||
|
if (enable_callstack) {
|
||||||
|
___tracy_emit_memory_alloc_callstack_named(ptr, len, callstack_depth, 0, name.ptr);
|
||||||
|
} else {
|
||||||
|
___tracy_emit_memory_alloc_named(ptr, len, 0, name.ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn free(ptr: [*]u8) void {
|
||||||
|
if (!enable) return;
|
||||||
|
|
||||||
|
if (enable_callstack) {
|
||||||
|
___tracy_emit_memory_free_callstack(ptr, callstack_depth, 0);
|
||||||
|
} else {
|
||||||
|
___tracy_emit_memory_free(ptr, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fn freeNamed(ptr: [*]u8, comptime name: [:0]const u8) void {
|
||||||
|
if (!enable) return;
|
||||||
|
|
||||||
|
if (enable_callstack) {
|
||||||
|
___tracy_emit_memory_free_callstack_named(ptr, callstack_depth, 0, name.ptr);
|
||||||
|
} else {
|
||||||
|
___tracy_emit_memory_free_named(ptr, 0, name.ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern fn ___tracy_emit_zone_begin(
|
||||||
|
srcloc: *const ___tracy_source_location_data,
|
||||||
|
active: c_int,
|
||||||
|
) ___tracy_c_zone_context;
|
||||||
|
extern fn ___tracy_emit_zone_begin_callstack(
|
||||||
|
srcloc: *const ___tracy_source_location_data,
|
||||||
|
depth: c_int,
|
||||||
|
active: c_int,
|
||||||
|
) ___tracy_c_zone_context;
|
||||||
|
extern fn ___tracy_emit_zone_text(ctx: ___tracy_c_zone_context, txt: [*]const u8, size: usize) void;
|
||||||
|
extern fn ___tracy_emit_zone_name(ctx: ___tracy_c_zone_context, txt: [*]const u8, size: usize) void;
|
||||||
|
extern fn ___tracy_emit_zone_color(ctx: ___tracy_c_zone_context, color: u32) void;
|
||||||
|
extern fn ___tracy_emit_zone_value(ctx: ___tracy_c_zone_context, value: u64) void;
|
||||||
|
extern fn ___tracy_emit_zone_end(ctx: ___tracy_c_zone_context) void;
|
||||||
|
extern fn ___tracy_emit_memory_alloc(ptr: *const anyopaque, size: usize, secure: c_int) void;
|
||||||
|
extern fn ___tracy_emit_memory_alloc_callstack(ptr: *const anyopaque, size: usize, depth: c_int, secure: c_int) void;
|
||||||
|
extern fn ___tracy_emit_memory_free(ptr: *const anyopaque, secure: c_int) void;
|
||||||
|
extern fn ___tracy_emit_memory_free_callstack(ptr: *const anyopaque, depth: c_int, secure: c_int) void;
|
||||||
|
extern fn ___tracy_emit_memory_alloc_named(ptr: *const anyopaque, size: usize, secure: c_int, name: [*:0]const u8) void;
|
||||||
|
extern fn ___tracy_emit_memory_alloc_callstack_named(ptr: *const anyopaque, size: usize, depth: c_int, secure: c_int, name: [*:0]const u8) void;
|
||||||
|
extern fn ___tracy_emit_memory_free_named(ptr: *const anyopaque, secure: c_int, name: [*:0]const u8) void;
|
||||||
|
extern fn ___tracy_emit_memory_free_callstack_named(ptr: *const anyopaque, depth: c_int, secure: c_int, name: [*:0]const u8) void;
|
||||||
|
extern fn ___tracy_emit_message(txt: [*]const u8, size: usize, callstack: c_int) void;
|
||||||
|
extern fn ___tracy_emit_messageL(txt: [*:0]const u8, callstack: c_int) void;
|
||||||
|
extern fn ___tracy_emit_messageC(txt: [*]const u8, size: usize, color: u32, callstack: c_int) void;
|
||||||
|
extern fn ___tracy_emit_messageLC(txt: [*:0]const u8, color: u32, callstack: c_int) void;
|
||||||
|
extern fn ___tracy_emit_frame_mark(name: ?[*:0]const u8) void;
|
||||||
|
|
||||||
|
const ___tracy_source_location_data = extern struct {
|
||||||
|
name: ?[*:0]const u8,
|
||||||
|
function: [*:0]const u8,
|
||||||
|
file: [*:0]const u8,
|
||||||
|
line: u32,
|
||||||
|
color: u32,
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue