summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoralaric <alaric@netmythos.org>2024-03-30 20:37:06 -0700
committeralaric <alaric@netmythos.org>2024-03-30 20:37:06 -0700
commit0399ae4d60005ff7d2f7c89c190deb9298520a2e (patch)
tree32aa14f0516a0e59b9da7da13eb1f99c0472e5e7
parente8c0b718c0f43204b5f4fd2c7be1ae94017843c1 (diff)
downloadcolordots-0399ae4d60005ff7d2f7c89c190deb9298520a2e.tar.gz
colordots-0399ae4d60005ff7d2f7c89c190deb9298520a2e.zip
Final quick cleanup
-rw-r--r--\552
-rw-r--r--src/wasm_ttd.zig389
2 files changed, 741 insertions, 200 deletions
diff --git a/\ b/\
new file mode 100644
index 0000000..a6126f8
--- /dev/null
+++ b/\
@@ -0,0 +1,552 @@
+const std = @import("std");
+const webgl = @import("webgl.zig");
+
+const Mat3 = @import("matrix.zig").Matrix(f32, 3, 3);
+
+extern fn consoleLog(ptr: [*c]const u8, len: u32) void;
+extern fn rand() f32;
+extern fn getScreenWidth() f32;
+extern fn getScreenHeight() f32;
+extern fn registerKeyInput(ptr: [*c]const u8, len: u32, out: *u32) void;
+
+const LoadTextureInfo = packed struct {
+ status: u32 = 0,
+ width: u32 = 0,
+ height: u32 = 0,
+};
+extern fn loadTexture(ptr: [*c]const u8, len: u32, texture: webgl.Texture, out_info: *LoadTextureInfo) void;
+
+const vs_source = @embedFile("vertex.glsl");
+const fs_source = @embedFile("fragment.glsl");
+
+fn formatLog(comptime fmt: []const u8, args: anytype) void {
+ var buf: [512]u8 = undefined;
+ const m = std.fmt.bufPrint(&buf, fmt, args) catch return;
+ consoleLog(m.ptr, m.len);
+}
+
+const pi = 3.1415926535;
+
+var timer: f32 = 0.0;
+var rects: [128]Rectangle = undefined;
+var cols: [128][3]f32 = undefined;
+
+fn assert(ok: bool) void {
+ if (!ok) unreachable;
+}
+
+pub const LinearCongruentialGenerator = struct {
+ mod: u64,
+ mul: u64,
+ inc: u64,
+ seed: u64,
+
+ const Self = @This();
+ /// Move to the next seed value internally and return that value
+ pub fn next(self: *Self) u64 {
+ const r = (self.seed *% self.mul +% self.inc) % self.mod;
+ self.seed = r;
+ return r;
+ }
+
+ /// Generate an int of type T with a value from min to max (inclusive)
+ pub fn randInt(self: *Self, comptime T: type, min: T, max: T) T {
+ assert(max > min);
+ const range: u64 = @as(u64, @intCast(max - min)) + 1;
+ assert(self.mod >= range);
+ const val: T = @intCast(self.next() % range);
+ return min + val;
+ }
+
+ pub fn randFloat(self: *Self, comptime T: type) T {
+ const pct: T = @as(T, @floatFromInt(self.next())) / @as(T, @floatFromInt(self.mod));
+ return pct;
+ }
+
+ pub fn randEnum(self: *Self, comptime T: type) T {
+ const info = @typeInfo(T);
+ if (info != .Enum) @compileError("Cannot call randEnum on type " ++ @typeName(T));
+ const fields = info.Enum.fields;
+ const vals = comptime blk: {
+ var result: [fields.len]T = undefined;
+ for (fields, 0..) |f, i| {
+ result[i] = @field(T, f.name);
+ }
+ break :blk result;
+ };
+ const i = self.randInt(usize, 0, fields.len - 1);
+ return vals[i];
+ }
+
+ pub fn randBool(self: *Self) bool {
+ return self.next() % 2 == 0;
+ }
+
+ pub fn ZX81(seed: u64) Self {
+ const mod = pow(u64, 2, 16) + 1;
+ return .{
+ .seed = seed,
+ .mod = mod,
+ .mul = 75,
+ .inc = 74,
+ };
+ }
+};
+
+pub fn pow(comptime T: type, base: T, exp: T) T {
+ if (exp == 0) {
+ return 1;
+ } else if (exp > 0) {
+ return base * pow(T, base, exp - 1);
+ } else if (exp < 0) {
+ return pow(T, base, exp + 1) / base;
+ }
+ unreachable;
+}
+
+const Star = struct {
+ dist: f32,
+ pdist: f32,
+ angle: f32,
+ initial_len: f32,
+ hue: f32,
+};
+const max_dist = 100;
+
+var rng: LinearCongruentialGenerator = undefined;
+
+const star_count = 100000;
+var stars: [star_count]Star = undefined;
+
+var right: u32 = 0;
+var pright: u32 = 0;
+var left: u32 = 0;
+var pleft: u32 = 0;
+var up: u32 = 0;
+var pup: u32 = 0;
+var down: u32 = 0;
+var pdown: u32 = 0;
+
+const circle = circlePoints(32);
+
+const LineProgram = struct {
+ prog: webgl.Program,
+ vao: webgl.VertexArrayObject,
+
+ res_uniform_location: i32,
+
+ pos_buffer: webgl.Buffer,
+ col_buffer: webgl.Buffer,
+
+ fn init() !LineProgram {
+ const vert = loadShader(.vertex_shader, @embedFile("line_vertex.glsl")) catch return error.FailedToLoadVertexShader;
+ const frag = loadShader(.fragment_shader, fs_source) catch return error.FailedToLoadFragmentShader;
+ const prog = createGLProgram(vert, frag) catch return error.FailedToCreateProgram;
+ webgl.useProgram(prog);
+
+ const attr_name = "in_pos";
+ const pos_attr_location = webgl.getAttribLocation(prog, attr_name.ptr, attr_name.len);
+
+ const col_attr = "color";
+ const col_attr_location = webgl.getAttribLocation(prog, col_attr.ptr, col_attr.len);
+
+ const res_uniform_location = getUniformLocation(prog, "u_resolution") catch unreachable;
+
+ const pos_buffer = webgl.createBuffer();
+ webgl.bindBuffer(.array_buffer, pos_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(&[_]f32{ 0, 0, 0, 0 }), 2, .static_draw);
+
+ const vao = webgl.createVertexArray();
+ webgl.bindVertexArray(vao);
+ webgl.enableVertexAttribArray(pos_attr_location);
+ webgl.vertexAttribPointer(@intCast(pos_attr_location), 4, .f32, .false, 0, 0);
+ webgl.vertexAttribDivisor(pos_attr_location, 1);
+
+ const col_buffer = webgl.createBuffer();
+ webgl.bindBuffer(.array_buffer, col_buffer);
+ webgl.enableVertexAttribArray(col_attr_location);
+ webgl.vertexAttribPointer(@intCast(col_attr_location), 1, .f32, .false, 0, 0);
+ webgl.vertexAttribDivisor(col_attr_location, 1);
+
+ return .{
+ .prog = prog,
+ .pos_buffer = pos_buffer,
+ .col_buffer = col_buffer,
+ .res_uniform_location = res_uniform_location,
+ .vao = vao,
+ };
+ }
+};
+
+const CircleProgram = struct {
+ prog: webgl.Program,
+ vao: webgl.VertexArrayObject,
+
+ res_uniform_location: i32,
+
+ translation_buffer: webgl.Buffer,
+ scale_buffer: webgl.Buffer,
+ col_buffer: webgl.Buffer,
+
+ fn init() !CircleProgram {
+ const vert = loadShader(.vertex_shader, @embedFile("vertex.glsl")) catch return error.FailedToLoadVertexShader;
+ const frag = loadShader(.fragment_shader, fs_source) catch return error.FailedToLoadFragmentShader;
+ const prog = createGLProgram(vert, frag) catch return error.FailedToCreateProgram;
+ webgl.useProgram(prog);
+
+ const attr_name = "in_pos";
+ const pos_attr_location = webgl.getAttribLocation(prog, attr_name.ptr, attr_name.len);
+
+ const col_attr = "color";
+ const col_attr_location = webgl.getAttribLocation(prog, col_attr.ptr, col_attr.len);
+
+ const translation_attr = "translation";
+ const translation_attr_location = webgl.getAttribLocation(
+ prog,
+ translation_attr.ptr,
+ translation_attr.len,
+ );
+
+ const scale_attr = "scale";
+ const scale_attr_location = webgl.getAttribLocation(
+ prog,
+ scale_attr.ptr,
+ scale_attr.len,
+ );
+
+ const res_uniform_location = getUniformLocation(prog, "u_resolution") catch unreachable;
+
+ const pos_buffer = webgl.createBuffer();
+ webgl.bindBuffer(.array_buffer, pos_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(&circle), circle.len * 2, .static_draw);
+
+ const vao = webgl.createVertexArray();
+ webgl.bindVertexArray(vao);
+ webgl.enableVertexAttribArray(pos_attr_location);
+ webgl.vertexAttribPointer(@intCast(pos_attr_location), 2, .f32, .false, 0, 0);
+
+ const col_buffer = webgl.createBuffer();
+ webgl.bindBuffer(.array_buffer, col_buffer);
+ webgl.enableVertexAttribArray(col_attr_location);
+ webgl.vertexAttribPointer(@intCast(col_attr_location), 1, .f32, .false, 0, 0);
+ webgl.vertexAttribDivisor(col_attr_location, 1);
+
+ const scale_buffer = webgl.createBuffer();
+ webgl.bindBuffer(.array_buffer, scale_buffer);
+ webgl.enableVertexAttribArray(scale_attr_location);
+ webgl.vertexAttribPointer(@intCast(scale_attr_location), 1, .f32, .false, 0, 0);
+ webgl.vertexAttribDivisor(scale_attr_location, 1);
+
+ const translation_buffer = webgl.createBuffer();
+ webgl.bindBuffer(.array_buffer, translation_buffer);
+ webgl.enableVertexAttribArray(translation_attr_location);
+ webgl.vertexAttribPointer(@intCast(translation_attr_location), 2, .f32, .false, 0, 0);
+ webgl.vertexAttribDivisor(translation_attr_location, 1);
+
+ return .{
+ .prog = prog,
+ .translation_buffer = translation_buffer,
+ .col_buffer = col_buffer,
+ .scale_buffer = scale_buffer,
+ .res_uniform_location = res_uniform_location,
+ .vao = vao,
+ };
+ }
+};
+
+var circle_program: CircleProgram = undefined;
+var line_program: LineProgram = undefined;
+
+export fn init() void {
+ const f_seed = rand() * (pow(f32, 2, 16) + 1);
+ const seed: u64 = @intFromFloat(f_seed);
+ rng = LinearCongruentialGenerator.ZX81(seed);
+
+ circle_program = CircleProgram.init() catch |err| {
+ formatLog("Failed to create CircleProgram. {}", .{err});
+ return;
+ };
+
+ line_program = LineProgram.init() catch |err| {
+ formatLog("Failed to create CircleProgram. {}", .{err});
+ return;
+ };
+
+ const height = getScreenHeight();
+
+ for (&stars) |*s| {
+ s.angle = lerp(0, 2 * pi, rng.randFloat(f32));
+ s.initial_len = lerp(0, height / 2, rng.randFloat(f32));
+ s.dist = max_dist;
+ s.pdist = max_dist;
+ s.hue = rng.randFloat(f32);
+ }
+
+ registerKey(.up, &up);
+ registerKey(.right, &right);
+ registerKey(.left, &left);
+ registerKey(.down, &down);
+}
+
+const KeyboardKey = enum {
+ up,
+ down,
+ left,
+ right,
+};
+
+fn registerKey(key: KeyboardKey, out: *u32) void {
+ const key_code = switch (key) {
+ .up => "ArrowUp",
+ .down => "ArrowDown",
+ .left => "ArrowLeft",
+ .right => "ArrowRight",
+ };
+ registerKeyInput(key_code.ptr, key_code.len, out);
+}
+
+fn rad2deg(rads: f32) f32 {
+ return rads * (180.0 / pi);
+}
+
+var frame_counter: u32 = 0;
+var speed: f32 = 0.1;
+var sub_count: u32 = star_count;
+
+const batch_capacity = 90000;
+var translation_data: [batch_capacity][2]f32 = undefined;
+var line_data: [batch_capacity][2][2]f32 = undefined;
+var scale_data: [batch_capacity]f32 = undefined;
+var color_data: [batch_capacity]f32 = undefined;
+
+const DrawBatch = struct {
+ translation: [][2]f32,
+ scales: []f32,
+ colors: []f32,
+ lines: [][2][2]f32,
+ len: u32 = 0,
+
+ fn addInstance(self: *DrawBatch, translation: [2]f32, past_translation: [2]f32, hue: f32, scale: f32) !void {
+ if (self.len == self.translation.len or self.len == self.colors.len or self.len == self.scales.len or self.len == self.lines.len) return error.OutOfSpace;
+
+ self.translation[self.len] = translation;
+ self.colors[self.len] = hue;
+ self.scales[self.len] = scale;
+ self.lines[self.len] = .{ past_translation, translation };
+ self.len += 1;
+ }
+
+ fn draw(self: DrawBatch) void {
+ const width = getScreenWidth();
+ const height = getScreenHeight();
+ webgl.bindVertexArray(circle_program.vao);
+ webgl.useProgram(circle_program.prog);
+ webgl.uniform2f(circle_program.res_uniform_location, width, height);
+ webgl.bindBuffer(.array_buffer, circle_program.translation_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(self.translation), self.len * 2, .dynamic_draw);
+
+ webgl.bindBuffer(.array_buffer, circle_program.scale_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(self.scales), self.len, .dynamic_draw);
+
+ webgl.bindBuffer(.array_buffer, circle_program.col_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(self.colors), self.len, .dynamic_draw);
+ webgl.drawArraysInstanced(.triangles, 0, circle.len, @intCast(self.len));
+
+ webgl.bindVertexArray(line_program.vao);
+ webgl.useProgram(line_program.prog);
+ webgl.uniform2f(line_program.res_uniform_location, width, height);
+
+ webgl.bindBuffer(.array_buffer, line_program.pos_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(self.lines), self.len * 2 * 2, .dynamic_draw);
+
+ webgl.bindBuffer(.array_buffer, line_program.col_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(self.colors), self.len, .dynamic_draw);
+ webgl.drawArraysInstanced(.lines, 0, 2, @intCast(self.len));
+ }
+};
+
+var anim_t: f32 = 0;
+
+export fn update(elapsed_time: f32) void {
+ const report_freq = 100;
+ frame_counter += 1;
+ timer += elapsed_time;
+ anim_t += elapsed_time;
+ if (frame_counter % report_freq == 0) {
+ const avg = timer / report_freq;
+ formatLog("{d:.2} Average FPS", .{1.0 / avg});
+ timer = 0;
+ }
+ //while (timer >= 2 * pi) {
+ // timer -= 2 * pi;
+ //}
+
+ const right_pressed = right != 0 and pright == 0;
+ const left_pressed = left != 0 and pleft == 0;
+
+ if (right_pressed) {
+ speed += 0.1;
+ }
+ if (left_pressed) {
+ speed -= 0.1;
+ }
+ speed = @min(1.0, @max(speed, -1.0));
+
+ if (up != 0) {
+ if (sub_count < star_count) {
+ sub_count += 100;
+ }
+ }
+
+ if (down != 0) {
+ if (sub_count > 0) {
+ sub_count -= 100;
+ }
+ }
+
+ pright = right;
+ pleft = left;
+ pup = up;
+ pdown = down;
+
+ webgl.viewport(0, 0, 1280, 720);
+ webgl.useProgram(circle_program.prog);
+ webgl.bindVertexArray(circle_program.vao);
+ webgl.uniform2f(circle_program.res_uniform_location, 1280, 720);
+
+ webgl.clearColor(0.0, 0.0, 0.0, 1.0);
+ webgl.clear(webgl.color_buffer_bit | webgl.depth_buffer_bit);
+
+ var batch: DrawBatch = .{
+ .translation = &translation_data,
+ .colors = &color_data,
+ .scales = &scale_data,
+ .lines = &line_data,
+ };
+
+ const width = getScreenWidth();
+ const height = getScreenHeight();
+
+ for (stars[0..sub_count]) |*s| {
+ s.pdist = s.dist;
+ s.dist -= speed;
+ s.angle += @cos(anim_t) * 0.05;
+ const plen = lerp(1000, s.initial_len, s.pdist / max_dist);
+ const psx = (@cos(s.angle) * plen) + width * 0.5;
+ const psy = (@sin(s.angle) * plen) + height * 0.5;
+
+ const len = lerp(1000, s.initial_len, s.dist / max_dist);
+ const sx = (@cos(s.angle) * len) + width * 0.5;
+ const sy = (@sin(s.angle) * len) + height * 0.5;
+ const scale = lerp(16, 0, s.dist / max_dist);
+
+ batch.addInstance(.{ sx, sy }, .{ psx, psy }, s.hue, scale) catch unreachable;
+ if (batch.len == batch_capacity) {
+ batch.draw();
+ batch.len = 0;
+ }
+
+ if (sx < 0 or sx > width or sy < 0 or sy > height + 32) {
+ s.angle = lerp(0, 8 * pi, rng.randFloat(f32));
+ s.initial_len = lerp(0, height, rng.randFloat(f32));
+ s.dist = max_dist;
+ s.pdist = max_dist;
+ }
+ }
+
+ batch.draw();
+}
+
+fn lerp(a: f32, b: f32, v: f32) f32 {
+ return a + (b - a) * v;
+}
+
+fn invLerp(a: f32, b: f32, v: f32) f32 {
+ return (v - a) / (b - a);
+}
+
+fn mapLerp(from_a: f32, from_b: f32, to_a: f32, to_b: f32, v: f32) f32 {
+ const pct = invLerp(from_a, from_b, v);
+ return lerp(to_a, to_b, pct);
+}
+
+const Rectangle = struct {
+ pos: @Vector(2, f32),
+ size: @Vector(2, f32),
+};
+
+const Color = struct {
+ r: f32 = 0.0,
+ g: f32 = 0.0,
+ b: f32 = 0.0,
+ a: f32 = 1.0,
+
+ const white: Color = .{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 };
+};
+
+fn circlePoints(comptime segments: u32) [segments * 3][2]f32 {
+ const segment_rads = (2 * pi) / @as(comptime_float, segments);
+ var r: [segments * 3][2]f32 = undefined;
+ for (0..segments) |s| {
+ const sf: f32 = @floatFromInt(s);
+ const start_angle = segment_rads * sf;
+ const start_x = @cos(start_angle);
+ const start_y = @sin(start_angle);
+ const start: @Vector(2, f32) = .{ start_x, start_y };
+
+ const end_angle = segment_rads * (sf + 1);
+ const end_x = @cos(end_angle);
+ const end_y = @sin(end_angle);
+ const end: @Vector(2, f32) = .{ end_x, end_y };
+
+ const i = s * 3;
+ r[i] = @Vector(2, f32){ 0, 0 };
+ r[i + 1] = start;
+ r[i + 2] = end;
+ }
+
+ return r;
+}
+
+fn getUniformLocation(program: webgl.Program, name: []const u8) !i32 {
+ const loc = webgl.getUniformLocation(program, name.ptr, name.len);
+ if (loc < 0) return error.FailedToGetLocation;
+
+ return loc;
+}
+
+fn loadShader(shader_type: webgl.GLShaderType, source: []const u8) !webgl.Shader {
+ const shader = webgl.createShader(shader_type);
+ errdefer webgl.deleteShader(shader);
+
+ webgl.shaderSource(shader, source.ptr, source.len);
+ webgl.compileShader(shader);
+
+ if (webgl.getShaderParameter(shader, .compile_status) == 0) {
+ var buf: [512]u8 = undefined;
+ const len = webgl.getShaderInfoLog(shader, &buf, buf.len);
+ const msg = buf[0..len];
+ consoleLog(msg.ptr, msg.len);
+ return error.CompilationFailed;
+ }
+
+ return shader;
+}
+
+fn createGLProgram(vert: webgl.Shader, frag: webgl.Shader) !webgl.Program {
+ const program = webgl.createProgram();
+ errdefer webgl.deleteProgram(program);
+
+ webgl.attachShader(program, vert);
+ webgl.attachShader(program, frag);
+ webgl.linkProgram(program);
+ if (webgl.getProgramParameter(program, .link_status) == 0) {
+ var buf: [512]u8 = undefined;
+ const len = webgl.getProgramInfoLog(program, &buf, buf.len);
+ const msg = buf[0..len];
+ consoleLog(msg.ptr, msg.len);
+ return error.LinkFailed;
+ }
+
+ return program;
+}
diff --git a/src/wasm_ttd.zig b/src/wasm_ttd.zig
index 0ccb5a0..967bb7c 100644
--- a/src/wasm_ttd.zig
+++ b/src/wasm_ttd.zig
@@ -9,125 +9,96 @@ extern fn getScreenWidth() f32;
extern fn getScreenHeight() f32;
extern fn registerKeyInput(ptr: [*c]const u8, len: u32, out: *u32) void;
-const LoadTextureInfo = packed struct {
- status: u32 = 0,
- width: u32 = 0,
- height: u32 = 0,
-};
-extern fn loadTexture(ptr: [*c]const u8, len: u32, texture: webgl.Texture, out_info: *LoadTextureInfo) void;
-
const vs_source = @embedFile("vertex.glsl");
const fs_source = @embedFile("fragment.glsl");
-fn formatLog(comptime fmt: []const u8, args: anytype) void {
- var buf: [512]u8 = undefined;
- const m = std.fmt.bufPrint(&buf, fmt, args) catch return;
- consoleLog(m.ptr, m.len);
-}
-
const pi = 3.1415926535;
+const max_dist = 100;
+const star_count = 100000;
+const circle = circlePoints(32);
+const max_speed = 2;
-var timer: f32 = 0.0;
-var rects: [128]Rectangle = undefined;
-var cols: [128][3]f32 = undefined;
+var rng: LinearCongruentialGenerator = undefined;
+var stars: [star_count]Star = undefined;
-fn assert(ok: bool) void {
- if (!ok) unreachable;
-}
+var right: u32 = 0;
+var left: u32 = 0;
+var up: u32 = 0;
+var down: u32 = 0;
+var c_key: u32 = 0;
+var c_was_pressed: u32 = 0;
-pub const LinearCongruentialGenerator = struct {
- mod: u64,
- mul: u64,
- inc: u64,
- seed: u64,
+var circle_program: CircleProgram = undefined;
+var line_program: LineProgram = undefined;
- const Self = @This();
- /// Move to the next seed value internally and return that value
- pub fn next(self: *Self) u64 {
- const r = (self.seed *% self.mul +% self.inc) % self.mod;
- self.seed = r;
- return r;
- }
+var frame_counter: u32 = 0;
- /// Generate an int of type T with a value from min to max (inclusive)
- pub fn randInt(self: *Self, comptime T: type, min: T, max: T) T {
- assert(max > min);
- const range: u64 = @as(u64, @intCast(max - min)) + 1;
- assert(self.mod >= range);
- const val: T = @intCast(self.next() % range);
- return min + val;
- }
+const batch_capacity = 90000;
+var translation_data: [batch_capacity][2]f32 = undefined;
+var line_data: [batch_capacity][2][2]f32 = undefined;
+var scale_data: [batch_capacity]f32 = undefined;
+var color_data: [batch_capacity]f32 = undefined;
- pub fn randFloat(self: *Self, comptime T: type) T {
- const pct: T = @as(T, @floatFromInt(self.next())) / @as(T, @floatFromInt(self.mod));
- return pct;
- }
+var timer: f32 = 0.0;
+var anim_t: f32 = 0;
- pub fn randEnum(self: *Self, comptime T: type) T {
- const info = @typeInfo(T);
- if (info != .Enum) @compileError("Cannot call randEnum on type " ++ @typeName(T));
- const fields = info.Enum.fields;
- const vals = comptime blk: {
- var result: [fields.len]T = undefined;
- for (fields, 0..) |f, i| {
- result[i] = @field(T, f.name);
- }
- break :blk result;
- };
- const i = self.randInt(usize, 0, fields.len - 1);
- return vals[i];
- }
+var speed: f32 = 1;
+var sub_count: u32 = star_count / 2;
+var color_mode: enum(u8) { rainbow, solid, angle, depth } = .rainbow;
- pub fn randBool(self: *Self) bool {
- return self.next() % 2 == 0;
- }
+const DrawBatch = struct {
+ translation: [][2]f32,
+ scales: []f32,
+ colors: []f32,
+ lines: [][2][2]f32,
+ len: u32 = 0,
- pub fn ZX81(seed: u64) Self {
- const mod = pow(u64, 2, 16) + 1;
- return .{
- .seed = seed,
- .mod = mod,
- .mul = 75,
- .inc = 74,
- };
- }
-};
+ fn addInstance(self: *DrawBatch, translation: [2]f32, past_translation: [2]f32, hue: f32, scale: f32) !void {
+ if (self.len == self.translation.len or self.len == self.colors.len or self.len == self.scales.len or self.len == self.lines.len) return error.OutOfSpace;
-pub fn pow(comptime T: type, base: T, exp: T) T {
- if (exp == 0) {
- return 1;
- } else if (exp > 0) {
- return base * pow(T, base, exp - 1);
- } else if (exp < 0) {
- return pow(T, base, exp + 1) / base;
+ self.translation[self.len] = translation;
+ self.colors[self.len] = hue;
+ self.scales[self.len] = scale;
+ self.lines[self.len] = .{ past_translation, translation };
+ self.len += 1;
}
- unreachable;
-}
-const Star = struct {
- dist: f32,
- pdist: f32,
- angle: f32,
- initial_len: f32,
- hue: f32,
-};
-const max_dist = 100;
+ fn draw(self: DrawBatch) void {
+ const width = getScreenWidth();
+ const height = getScreenHeight();
+ webgl.bindVertexArray(circle_program.vao);
+ webgl.useProgram(circle_program.prog);
+ webgl.uniform2f(circle_program.res_uniform_location, width, height);
+ webgl.bindBuffer(.array_buffer, circle_program.translation_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(self.translation), self.len * 2, .dynamic_draw);
-var rng: LinearCongruentialGenerator = undefined;
+ webgl.bindBuffer(.array_buffer, circle_program.scale_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(self.scales), self.len, .dynamic_draw);
-const star_count = 30000;
-var stars: [star_count]Star = undefined;
+ webgl.bindBuffer(.array_buffer, circle_program.col_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(self.colors), self.len, .dynamic_draw);
+ webgl.drawArraysInstanced(.triangles, 0, circle.len, @intCast(self.len));
-var right: u32 = 0;
-var pright: u32 = 0;
-var left: u32 = 0;
-var pleft: u32 = 0;
-var up: u32 = 0;
-var pup: u32 = 0;
-var down: u32 = 0;
-var pdown: u32 = 0;
+ webgl.bindVertexArray(line_program.vao);
+ webgl.useProgram(line_program.prog);
+ webgl.uniform2f(line_program.res_uniform_location, width, height);
-const circle = circlePoints(32);
+ webgl.bindBuffer(.array_buffer, line_program.pos_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(self.lines), self.len * 2 * 2, .dynamic_draw);
+
+ webgl.bindBuffer(.array_buffer, line_program.col_buffer);
+ webgl.bufferData(.array_buffer, @ptrCast(self.colors), self.len, .dynamic_draw);
+ webgl.drawArraysInstanced(.lines, 0, 2, @intCast(self.len));
+ }
+};
+
+const KeyboardKey = enum {
+ up,
+ down,
+ left,
+ right,
+ c,
+};
const LineProgram = struct {
prog: webgl.Program,
@@ -254,8 +225,92 @@ const CircleProgram = struct {
}
};
-var circle_program: CircleProgram = undefined;
-var line_program: LineProgram = undefined;
+const Star = struct {
+ dist: f32,
+ pdist: f32,
+ angle: f32,
+ initial_len: f32,
+ hue: f32,
+};
+
+pub const LinearCongruentialGenerator = struct {
+ mod: u64,
+ mul: u64,
+ inc: u64,
+ seed: u64,
+
+ const Self = @This();
+ /// Move to the next seed value internally and return that value
+ pub fn next(self: *Self) u64 {
+ const r = (self.seed *% self.mul +% self.inc) % self.mod;
+ self.seed = r;
+ return r;
+ }
+
+ /// Generate an int of type T with a value from min to max (inclusive)
+ pub fn randInt(self: *Self, comptime T: type, min: T, max: T) T {
+ assert(max > min);
+ const range: u64 = @as(u64, @intCast(max - min)) + 1;
+ assert(self.mod >= range);
+ const val: T = @intCast(self.next() % range);
+ return min + val;
+ }
+
+ pub fn randFloat(self: *Self, comptime T: type) T {
+ const pct: T = @as(T, @floatFromInt(self.next())) / @as(T, @floatFromInt(self.mod));
+ return pct;
+ }
+
+ pub fn randEnum(self: *Self, comptime T: type) T {
+ const info = @typeInfo(T);
+ if (info != .Enum) @compileError("Cannot call randEnum on type " ++ @typeName(T));
+ const fields = info.Enum.fields;
+ const vals = comptime blk: {
+ var result: [fields.len]T = undefined;
+ for (fields, 0..) |f, i| {
+ result[i] = @field(T, f.name);
+ }
+ break :blk result;
+ };
+ const i = self.randInt(usize, 0, fields.len - 1);
+ return vals[i];
+ }
+
+ pub fn randBool(self: *Self) bool {
+ return self.next() % 2 == 0;
+ }
+
+ pub fn ZX81(seed: u64) Self {
+ const mod = pow(u64, 2, 16) + 1;
+ return .{
+ .seed = seed,
+ .mod = mod,
+ .mul = 75,
+ .inc = 74,
+ };
+ }
+};
+
+fn formatLog(comptime fmt: []const u8, args: anytype) void {
+ var buf: [512]u8 = undefined;
+ const m = std.fmt.bufPrint(&buf, fmt, args) catch return;
+ consoleLog(m.ptr, m.len);
+}
+
+fn assert(ok: bool) void {
+ if (!ok) unreachable;
+}
+
+pub fn pow(comptime T: type, base: T, exp: T) T {
+ if (exp == 0) {
+ return 1;
+ } else if (exp > 0) {
+ return base * pow(T, base, exp - 1);
+ } else if (exp < 0) {
+ return pow(T, base, exp + 1) / base;
+ }
+ unreachable;
+}
export fn init() void {
const f_seed = rand() * (pow(f32, 2, 16) + 1);
@@ -286,21 +341,16 @@ export fn init() void {
registerKey(.right, &right);
registerKey(.left, &left);
registerKey(.down, &down);
+ registerKey(.c, &c_key);
}
-const KeyboardKey = enum {
- up,
- down,
- left,
- right,
-};
-
fn registerKey(key: KeyboardKey, out: *u32) void {
const key_code = switch (key) {
.up => "ArrowUp",
.down => "ArrowDown",
.left => "ArrowLeft",
.right => "ArrowRight",
+ .c => "KeyC",
};
registerKeyInput(key_code.ptr, key_code.len, out);
}
@@ -309,107 +359,55 @@ fn rad2deg(rads: f32) f32 {
return rads * (180.0 / pi);
}
-var frame_counter: u32 = 0;
-var speed: f32 = 0.1;
-var sub_count: u32 = star_count;
-
-const batch_capacity = 90000;
-var translation_data: [batch_capacity][2]f32 = undefined;
-var line_data: [batch_capacity][2][2]f32 = undefined;
-var scale_data: [batch_capacity]f32 = undefined;
-var color_data: [batch_capacity]f32 = undefined;
-
-const DrawBatch = struct {
- translation: [][2]f32,
- scales: []f32,
- colors: []f32,
- lines: [][2][2]f32,
- len: u32 = 0,
-
- fn addInstance(self: *DrawBatch, translation: [2]f32, past_translation: [2]f32, hue: f32, scale: f32) !void {
- if (self.len == self.translation.len or self.len == self.colors.len or self.len == self.scales.len or self.len == self.lines.len) return error.OutOfSpace;
-
- self.translation[self.len] = translation;
- self.colors[self.len] = hue;
- self.scales[self.len] = scale;
- self.lines[self.len] = .{ past_translation, translation };
- self.len += 1;
- }
-
- fn draw(self: DrawBatch) void {
- const width = getScreenWidth();
- const height = getScreenHeight();
- webgl.bindVertexArray(circle_program.vao);
- webgl.useProgram(circle_program.prog);
- webgl.uniform2f(circle_program.res_uniform_location, width, height);
- webgl.bindBuffer(.array_buffer, circle_program.translation_buffer);
- webgl.bufferData(.array_buffer, @ptrCast(self.translation), self.len * 2, .dynamic_draw);
-
- webgl.bindBuffer(.array_buffer, circle_program.scale_buffer);
- webgl.bufferData(.array_buffer, @ptrCast(self.scales), self.len, .dynamic_draw);
-
- webgl.bindBuffer(.array_buffer, circle_program.col_buffer);
- webgl.bufferData(.array_buffer, @ptrCast(self.colors), self.len, .dynamic_draw);
- webgl.drawArraysInstanced(.triangles, 0, circle.len, @intCast(self.len));
-
- webgl.bindVertexArray(line_program.vao);
- webgl.useProgram(line_program.prog);
- webgl.uniform2f(line_program.res_uniform_location, width, height);
-
- webgl.bindBuffer(.array_buffer, line_program.pos_buffer);
- webgl.bufferData(.array_buffer, @ptrCast(self.lines), self.len * 2 * 2, .dynamic_draw);
-
- webgl.bindBuffer(.array_buffer, line_program.col_buffer);
- webgl.bufferData(.array_buffer, @ptrCast(self.colors), self.len, .dynamic_draw);
- webgl.drawArraysInstanced(.lines, 0, 2, @intCast(self.len));
- }
-};
-
export fn update(elapsed_time: f32) void {
const report_freq = 100;
frame_counter += 1;
timer += elapsed_time;
+ anim_t += elapsed_time;
if (frame_counter % report_freq == 0) {
const avg = timer / report_freq;
formatLog("{d:.2} Average FPS", .{1.0 / avg});
timer = 0;
}
- //while (timer >= 2 * pi) {
- // timer -= 2 * pi;
- //}
- const right_pressed = right != 0 and pright == 0;
- const left_pressed = left != 0 and pleft == 0;
+ const next_col_mode = c_key != 0 and c_was_pressed == 0;
+ c_was_pressed = c_key;
+ if (next_col_mode) {
+ if (color_mode == .depth) {
+ color_mode = .rainbow;
+ } else {
+ color_mode = @enumFromInt(@intFromEnum(color_mode) + 1);
+ }
+ }
- if (right_pressed) {
+ if (right != 0) {
speed += 0.1;
}
- if (left_pressed) {
+ if (left != 0) {
speed -= 0.1;
}
- speed = @min(1.0, @max(speed, -1.0));
+ speed = @min(max_speed, @max(speed, -max_speed));
+ const count_delta = 500;
if (up != 0) {
- if (sub_count < star_count) {
- sub_count += 100;
+ if (sub_count + count_delta <= star_count) {
+ sub_count += count_delta;
}
}
if (down != 0) {
- if (sub_count > 0) {
- sub_count -= 100;
+ if (sub_count >= count_delta) {
+ sub_count -= count_delta;
}
}
- pright = right;
- pleft = left;
- pup = up;
- pdown = down;
+ const width = getScreenWidth();
+ const height = getScreenHeight();
- webgl.viewport(0, 0, 1280, 720);
+ webgl.viewport(0, 0, @intFromFloat(width), @intFromFloat(height));
webgl.useProgram(circle_program.prog);
webgl.bindVertexArray(circle_program.vao);
- webgl.uniform2f(circle_program.res_uniform_location, 1280, 720);
+ webgl.uniform2f(circle_program.res_uniform_location, height, height);
webgl.clearColor(0.0, 0.0, 0.0, 1.0);
webgl.clear(webgl.color_buffer_bit | webgl.depth_buffer_bit);
@@ -421,12 +419,10 @@ export fn update(elapsed_time: f32) void {
.lines = &line_data,
};
- const width = getScreenWidth();
- const height = getScreenHeight();
for (stars[0..sub_count]) |*s| {
s.pdist = s.dist;
s.dist -= speed;
- s.angle += 0.03;
+ s.angle += @cos(anim_t) * 0.05;
const plen = lerp(1000, s.initial_len, s.pdist / max_dist);
const psx = (@cos(s.angle) * plen) + width * 0.5;
const psy = (@sin(s.angle) * plen) + height * 0.5;
@@ -436,7 +432,14 @@ export fn update(elapsed_time: f32) void {
const sy = (@sin(s.angle) * len) + height * 0.5;
const scale = lerp(16, 0, s.dist / max_dist);
- batch.addInstance(.{ sx, sy }, .{ psx, psy }, s.hue, scale) catch unreachable;
+ const col = switch (color_mode) {
+ .rainbow => s.hue,
+ .solid => @sin(anim_t * 0.1) * 0.5 + 1,
+ .angle => (s.angle / (2 * pi)) + (@cos(anim_t) + 1) / 2,
+ .depth => s.dist / (max_dist * 0.5),
+ };
+
+ batch.addInstance(.{ sx, sy }, .{ psx, psy }, col, scale) catch unreachable;
if (batch.len == batch_capacity) {
batch.draw();
batch.len = 0;
@@ -466,20 +469,6 @@ fn mapLerp(from_a: f32, from_b: f32, to_a: f32, to_b: f32, v: f32) f32 {
return lerp(to_a, to_b, pct);
}
-const Rectangle = struct {
- pos: @Vector(2, f32),
- size: @Vector(2, f32),
-};
-
-const Color = struct {
- r: f32 = 0.0,
- g: f32 = 0.0,
- b: f32 = 0.0,
- a: f32 = 1.0,
-
- const white: Color = .{ .r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0 };
-};
-
fn circlePoints(comptime segments: u32) [segments * 3][2]f32 {
const segment_rads = (2 * pi) / @as(comptime_float, segments);
var r: [segments * 3][2]f32 = undefined;