diff options
author | alaric <alaric@netmythos.org> | 2024-03-30 20:37:06 -0700 |
---|---|---|
committer | alaric <alaric@netmythos.org> | 2024-03-30 20:37:06 -0700 |
commit | 0399ae4d60005ff7d2f7c89c190deb9298520a2e (patch) | |
tree | 32aa14f0516a0e59b9da7da13eb1f99c0472e5e7 | |
parent | e8c0b718c0f43204b5f4fd2c7be1ae94017843c1 (diff) | |
download | colordots-0399ae4d60005ff7d2f7c89c190deb9298520a2e.tar.gz colordots-0399ae4d60005ff7d2f7c89c190deb9298520a2e.zip |
Final quick cleanup
-rw-r--r-- | \ | 552 | ||||
-rw-r--r-- | src/wasm_ttd.zig | 389 |
2 files changed, 741 insertions, 200 deletions
@@ -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; |