Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
# donut

🍩 Bake a rotating 3D donut in pure text

# References

[Signed Distance Functions](https://iquilezles.org/articles/distfunctions/)
14 changes: 14 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ pub fn build(b: *std.Build) void {
}),
});

const zlm = b.dependency("zlm", .{
.target = target,
.optimize = optimize,
});

exe.root_module.addImport("zlm", zlm.module("zlm"));

const vaxis = b.dependency("vaxis", .{
.target = target,
.optimize = optimize,
});

exe.root_module.addImport("vaxis", vaxis.module("vaxis"));

b.installArtifact(exe);

const run_step = b.step("run", "Run the app");
Expand Down
12 changes: 11 additions & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@
.version = "0.0.0",
.fingerprint = 0x8ebc1f793621bd75, // Changing this has security and trust implications.
.minimum_zig_version = "0.15.1",
.dependencies = .{},
.dependencies = .{
.zlm = .{
// TODO: change this to upstream once merged https://github.com/ziglibs/zlm/pull/33
.url = "git+https://github.com/theMagicalKarp/zlm?ref=fix-format#2587468c37d50732f086dec2ec690e72aa26a19b",
.hash = "zlm-0.1.0-LoZ6yOquAADMCJBFg5QNcgBIcj1cXrLAzkAp5x2sDNS1",
},
.vaxis = .{
.url = "git+https://github.com/rockorager/libvaxis.git#7dbb9fd3122e4ffad262dd7c151d80d863b68558",
.hash = "vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
Expand Down
17 changes: 17 additions & 0 deletions src/geometry/box.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const math = @import("zlm").as(f64);

pub const Octahedron = struct {
dimensions: math.vec3,

const Self = @This();

pub fn distance(self: Self, point: math.Vec3) f64 {
const q = point.abs().sub(self.dimensions);

return math.vec3(
@max(q.x, 0.0),
@max(q.y, 0.0),
@max(q.z, 0.0),
).length() + @min(@max(q.x, @max(q.y, q.z)), 0.0);
}
};
18 changes: 18 additions & 0 deletions src/geometry/intersection.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const math = @import("zlm").as(f64);

pub fn Intersection(comptime T: type, comptime U: type) type {
return struct {
a: T,
b: U,

const Self = @This();

pub fn new(a: T, b: U) Self {
return Self{ .a = a, .b = b };
}

pub fn distance(self: Self, point: math.Vec3) f64 {
return @max(self.a.distance(point), self.b.distance(point));
}
};
}
12 changes: 12 additions & 0 deletions src/geometry/octahedron.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const math = @import("zlm").as(f64);

pub const Octahedron = struct {
size: f64,

const Self = @This();

pub fn distance(self: Self, point: math.Vec3) f64 {
const abs_point = point.abs();
return (abs_point.x + abs_point.y + abs_point.z - self.size) * 0.57735027;
}
};
21 changes: 21 additions & 0 deletions src/geometry/rotate.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const math = @import("zlm").as(f64);

pub fn Rotate(comptime T: type) type {
return struct {
transformation: math.Mat4,
geometry: T,

const Self = @This();

pub fn new(geometry: T, axis: math.Vec3, angle: f64) Self {
return Self{
.geometry = geometry,
.transformation = math.Mat4.createAngleAxis(axis, angle),
};
}

pub fn distance(self: Self, point: math.Vec3) f64 {
return self.geometry.distance(point.transformPosition(self.transformation));
}
};
}
19 changes: 19 additions & 0 deletions src/geometry/scale.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const math = @import("zlm").as(f64);

pub fn Scale(comptime T: type) type {
return struct {
geometry: T,
amount: f64,
amount_inv: f64,

const Self = @This();

pub fn new(geometry: T, amount: f64) Self {
return Self{ .geometry = geometry, .amount = amount, .amount_inv = 1.0 / amount };
}

pub fn distance(self: Self, point: math.Vec3) f64 {
return self.geometry.distance(point.scale(self.amount_inv)) * self.amount;
}
};
}
11 changes: 11 additions & 0 deletions src/geometry/sphere.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const math = @import("zlm").as(f64);

pub const Sphere = struct {
radius: f64,

const Self = @This();

pub fn distance(self: Self, point: math.Vec3) f64 {
return point.length() - self.radius;
}
};
18 changes: 18 additions & 0 deletions src/geometry/subtraction.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const math = @import("zlm").as(f64);

pub fn Subtraction(comptime T: type, comptime U: type) type {
return struct {
a: T,
b: U,

const Self = @This();

pub fn new(a: T, b: U) Self {
return Self{ .a = a, .b = b };
}

pub fn distance(self: Self, point: math.Vec3) f64 {
return @max(-self.a.distance(point), self.b.distance(point));
}
};
}
13 changes: 13 additions & 0 deletions src/geometry/torus.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const math = @import("zlm").as(f64);

pub const Torus = struct {
inner: f64,
outer: f64,

const Self = @This();

pub fn distance(self: Self, point: math.Vec3) f64 {
var q = math.vec2(point.swizzle("xz").length() - self.outer, point.y);
return q.length() - self.inner;
}
};
18 changes: 18 additions & 0 deletions src/geometry/union.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const math = @import("zlm").as(f64);

pub fn Union(comptime T: type, comptime U: type) type {
return struct {
a: T,
b: U,

const Self = @This();

pub fn new(a: T, b: U) Self {
return Self{ .a = a, .b = b };
}

pub fn distance(self: Self, point: math.Vec3) f64 {
return @min(self.a.distance(point), self.b.distance(point));
}
};
}
29 changes: 29 additions & 0 deletions src/geometry/union_smooth.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const std = @import("std");
const math = @import("zlm").as(f64);

fn mix(x: f64, y: f64, a: f64) f64 {
return @mulAdd(f64, a, y - x, x);
}

pub fn UnionSmooth(comptime T: type, comptime U: type) type {
return struct {
smooth: f64,
a: T,
b: U,

const Self = @This();

pub fn new(a: T, b: U, smooth: f64) Self {
return Self{ .a = a, .b = b, .smooth = smooth };
}

pub fn distance(self: Self, point: math.Vec3) f64 {
const d1 = self.a.distance(point);
const d2 = self.b.distance(point);

const h = std.math.clamp(0.5 + 0.5 * (d2 - d1) / self.smooth, 0.0, 1.0);

return mix(d2, d1, h) - self.smooth * h * (1.0 - h);
}
};
}
93 changes: 92 additions & 1 deletion src/main.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,96 @@
const std = @import("std");
const vaxis = @import("vaxis");
const math = @import("zlm").as(f64);

const Scene = @import("./scene.zig").Scene;
const Rotate = @import("./geometry/rotate.zig").Rotate;
const Torus = @import("./geometry/torus.zig").Torus;
const UnionSmooth = @import("./geometry/union_smooth.zig").UnionSmooth;
const Sphere = @import("./geometry/sphere.zig").Sphere;
const Scale = @import("./geometry/scale.zig").Scale;

const Event = union(enum) {
key_press: vaxis.Key,
winsize: vaxis.Winsize,
focus_in,
};

pub fn main() !void {
std.debug.print("Hello world\n", .{});
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(gpa.deinit() == .ok);
const allocator = gpa.allocator();

var buffer: [1024]u8 = undefined;
var tty = try vaxis.Tty.init(&buffer);
defer tty.deinit();

var screen_buffer = std.io.Writer.Allocating.init(allocator);
defer screen_buffer.deinit();

var vx = try vaxis.init(allocator, .{});
defer vx.deinit(allocator, tty.writer());

var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx };
try loop.init();
try loop.start();
defer loop.stop();

try vx.enterAltScreen(tty.writer());
try vx.queryTerminal(tty.writer(), 1 * std.time.ns_per_s);

const gamma: f32 = 2.4;
const light_position = math.vec3(1.0, -1.0, -1.0).normalize();

const t1 = Torus{ .inner = 0.2, .outer = 1.0 };
const rt1 = Rotate(Torus).new(t1, math.vec3(0.0, 0.0, 1.0), 90.0);
const t2 = Torus{ .inner = 0.2, .outer = 1.0 };
const t3 = UnionSmooth(Rotate(Torus), Torus).new(rt1, t2, 0.3);
const st3 = Scale(UnionSmooth(Rotate(Torus), Torus)).new(t3, 1.2);

const ThisScene = Scene(Scale(UnionSmooth(Rotate(Torus), Torus)));
var scene = ThisScene.new(200.0, 200.0, light_position, gamma);

while (true) {
while (loop.tryEvent()) |event| {
switch (event) {
.key_press => |key| {
if (key.matches('c', .{ .ctrl = true })) {
return;
} else if (key.matches('q', .{})) {
return;
}
},

.winsize => |ws| {
try vx.resize(allocator, tty.writer(), ws);

scene = ThisScene.new(ws.cols - 2, ws.rows - 2, light_position, gamma);
},
else => {},
}
}

const win = vx.window();
win.clear();

const child = win.child(.{
.x_off = 0,
.y_off = 0,
.width = win.width,
.height = win.height,
.border = .{
.where = .all,
.style = .{
.fg = .{ .index = 5 },
},
},
});

screen_buffer.clearRetainingCapacity();
try scene.render(0.0, st3, &screen_buffer.writer);
_ = child.printSegment(.{ .text = screen_buffer.written() }, .{ .wrap = .grapheme });

try vx.render(tty.writer());
std.Thread.sleep(30 * std.time.ns_per_ms);
}
}
Loading