diff --git a/src/main.zig b/src/main.zig index 2895d65..dd145f2 100644 --- a/src/main.zig +++ b/src/main.zig @@ -55,6 +55,7 @@ pub const App = @This(); gpa: GeneralPurposeAllocator, allocator: std.mem.Allocator, +random: std.rand.Random, app_timer: core.Timer, title_timer: core.Timer, @@ -62,8 +63,8 @@ title_timer: core.Timer, pipeline: *gpu.RenderPipeline, scene_uniform_buffer: *gpu.Buffer, scene_uniform_bind_group: *gpu.BindGroup, -object_data: []ObjectData, primitives: []PrimitiveData, +object_data: []ObjectData, pub fn init(app: *App) !void { try core.init(.{}); @@ -78,6 +79,11 @@ pub fn init(app: *App) !void { app.gpa = GeneralPurposeAllocator{}; app.allocator = app.gpa.allocator(); + // Create a pseudo-random number generator, but initialize it with + // a constant seed so we always get the same result when launching. + var prng = std.rand.DefaultPrng.init(0); + app.random = prng.random(); + app.app_timer = try core.Timer.start(); app.title_timer = try core.Timer.start(); @@ -122,55 +128,6 @@ pub fn init(app: *App) !void { app.scene_uniform_bind_group = result.bind_group; } - // Set up object related uniform buffers and bind groups. - // This uploads data to the GPU about all the object we - // want to render, such as their location and color. - { - const ObjectDescription = struct { pos: [3]f32, color: [3]f32, prim: usize }; - const object_desc = [_]ObjectDescription{ - // zig fmt: off - .{ .pos = .{ -1.25, 0.25, 0.0 }, .color = .{ 1.0, 0.0, 0.0 }, .prim = 0 }, - .{ .pos = .{ 0.0 , -0.25, 0.0 }, .color = .{ 0.0, 1.0, 0.0 }, .prim = 1 }, - .{ .pos = .{ 1.25, 0.0 , 0.0 }, .color = .{ 0.0, 0.0, 1.0 }, .prim = 0 }, - // zig fmt: on - }; - - // The objects are rotated 180° to face the camera, or else we - // would see the back side of the triangles, which are culled. - const rotation = zm.rotationY(std.math.tau / 2.0); - - // Allocate a slice to store as many ObjectData as we want to create. - // - // Using a slice instead of an array means that we could change how - // many object we want to render at compile time, however it requires - // allocating, and later freeing, memory to store the slice. - app.object_data = try app.allocator.alloc(ObjectData, object_desc.len); - - // Note that for loops in Zig are a little different than you might - // know from other languages. They only look over arrays, slices, - // tuples and ranges, potentially multiple at once. - for (object_desc, app.object_data) |desc, *object| { - const translation = zm.translation(desc.pos[0], desc.pos[1], desc.pos[2]); - const model_matrix = zm.mul(rotation, translation); - - const result = createAndWriteUniformBuffer( - app.pipeline.getBindGroupLayout(1), - ObjectUniformBuffer{ - .model_matrix = zm.transpose(model_matrix), - .color = desc.color, - }, - ); - - // The `*object` syntax gets us a pointer to each element in the - // `object_data` slice, allowing us to override it within the loop. - object.* = .{ - .uniform_buffer = result.buffer, - .uniform_bind_group = result.bind_group, - .primitive_index = desc.prim, - }; - } - } - // Set up the primitives we want to render. app.primitives = try app.allocator.alloc(PrimitiveData, 2); // Triangle @@ -214,6 +171,57 @@ pub fn init(app: *App) !void { 3, 2, 1, }, ); + + // Set up object related uniform buffers and bind groups. + // This uploads data to the GPU about all the object we + // want to render, such as their location and color. + { + const grid_size = 8; + + // Allocate a slice to store as many ObjectData as we want to create. + // + // Using a slice instead of an array means that we could change how + // many object we want to render at compile time, however it requires + // allocating, and later freeing, memory to store the slice. + app.object_data = try app.allocator.alloc(ObjectData, grid_size * grid_size); + + // Note that for loops in Zig are a little different than you might + // know from other languages. They only look over arrays, slices, + // tuples and ranges, potentially multiple at once. + for (app.object_data, 0..) |*object, i| { + const grid_max: f32 = @floatFromInt(grid_size - 1); + const x = @as(f32, @floatFromInt(i % grid_size)) / grid_max; + const z = @as(f32, @floatFromInt(i / grid_size)) / grid_max; + + const rotation = zm.rotationY(std.math.tau * (x + z) / 2.0); + const translation = zm.translation((x - 0.5) * grid_size, 0, (z - 0.5) * grid_size); + const model_matrix = zm.mul(rotation, translation); + + // Make the object have a color depending on its location in the grid. + // These values are layed out so each corner is red, green, blue and black. + const color = .{ + std.math.clamp(1.0 - x - z, 0.0, 1.0), + std.math.clamp(x - z, 0.0, 1.0), + std.math.clamp(z - x, 0.0, 1.0), + }; + + const result = createAndWriteUniformBuffer( + app.pipeline.getBindGroupLayout(1), + ObjectUniformBuffer{ + .model_matrix = zm.transpose(model_matrix), + .color = color, + }, + ); + + // The `*object` syntax gets us a pointer to each element in the + // `object_data` slice, allowing us to override it within the loop. + object.* = .{ + .uniform_buffer = result.buffer, + .uniform_bind_group = result.bind_group, + .primitive_index = app.random.int(usize) % app.primitives.len, + }; + } + } } /// Creates a buffer on the GPU to store uniform parameter information as @@ -278,16 +286,16 @@ pub fn deinit(app: *App) void { defer app.pipeline.release(); defer app.scene_uniform_buffer.release(); defer app.scene_uniform_bind_group.release(); - defer app.allocator.free(app.object_data); - defer for (app.object_data) |o| { - o.uniform_buffer.release(); - o.uniform_bind_group.release(); - }; defer app.allocator.free(app.primitives); defer for (app.primitives) |p| { p.vertex_buffer.release(); p.index_buffer.release(); }; + defer app.allocator.free(app.object_data); + defer for (app.object_data) |o| { + o.uniform_buffer.release(); + o.uniform_bind_group.release(); + }; } pub fn update(app: *App) !bool { @@ -304,16 +312,18 @@ pub fn update(app: *App) !bool { // TODO: Actually implement camera transform instead of hardcoding a look-at matrix. // const view_matrix = zm.inverse(app.camera_transform); const time = app.app_timer.read(); - const x = @cos(time * std.math.tau / 10); - const y = @sin(time * std.math.tau / 10); - const view_matrix = zm.lookAtLh(vec(x, y, -2, 1), vec(0, 0, 0, 1), vec(0, 1, 0, 1)); + const camera_distance = 8.0; + const x = @cos(time * std.math.tau / 20) * camera_distance; + const z = @sin(time * std.math.tau / 20) * camera_distance; + const camera_pos = vec(x, 2.0, z, 1.0); + const view_matrix = zm.lookAtLh(camera_pos, vec(0, 0, 0, 1), vec(0, 1, 0, 1)); // Set up a projection matrix using the size of the window. // The perspective projection will make things further away appear smaller. const width: f32 = @floatFromInt(core.descriptor.width); const height: f32 = @floatFromInt(core.descriptor.height); const field_of_view = std.math.degreesToRadians(f32, 45.0); - const proj_matrix = zm.perspectiveFovLh(field_of_view, width / height, 0.1, 10); + const proj_matrix = zm.perspectiveFovLh(field_of_view, width / height, 0.05, 80.0); const view_proj_matrix = zm.mul(view_matrix, proj_matrix);