diff --git a/build.zig b/build.zig index e0f3b7c..37c67b7 100644 --- a/build.zig +++ b/build.zig @@ -54,6 +54,12 @@ pub fn build(b: *std.Build) void { .file = b.path(glad_folder ++ "/src/glad.c") }); exe.addIncludePath(b.path(glad_folder ++ "/include")); + + exe.addCSourceFile(.{ + .file = b.path("open-simplex/src/OpenSimplex2F.c") + }); + exe.addIncludePath(b.path("open-simplex/include")); + exe.linkLibC(); exe.linkSystemLibrary("glfw"); exe.linkSystemLibrary("GL"); diff --git a/open-simplex/include/OpenSimplex2F.h b/open-simplex/include/OpenSimplex2F.h new file mode 100644 index 0000000..1ade4c9 --- /dev/null +++ b/open-simplex/include/OpenSimplex2F.h @@ -0,0 +1,104 @@ +#ifndef OpenSimplex2F_h__ +#define OpenSimplex2F_h__ + + + +/** + * K.jpg's OpenSimplex 2, faster variant + * + * - 2D is standard simplex implemented using a lookup table. + * - 3D is "Re-oriented 4-point BCC noise" which constructs a + * congruent BCC lattice in a much different way than usual. + * - 4D constructs the lattice as a union of five copies of its + * reciprocal. It successively finds the closest point on each. + * + * Multiple versions of each function are provided. See the + * documentation above each, for more info. + * + * Ported from Java to C by Stephen M. Cameron + * + */ + +#include + +struct OpenSimplex2F_context { + int16_t *perm; + struct Grad2 *permGrad2; + struct Grad3 *permGrad3; + struct Grad4 *permGrad4; +}; + +/* Allocate and initialize OpenSimplex2F context */ +int OpenSimplex2F(int64_t seed, struct OpenSimplex2F_context **ctx); + +/* Free OpenSimplex2F context */ +void OpenSimplex2F_free(struct OpenSimplex2F_context * ctx); + +/* Free singleton lattice point data */ +void OpenSimplex2F_shutdown(void); + +/** + * 2D Simplex noise, standard lattice orientation. + */ +double OpenSimplex2F_noise2(struct OpenSimplex2F_context *ctx, double x, double y); + +/** + * 2D Simplex noise, with Y pointing down the main diagonal. + * Might be better for a 2D sandbox style game, where Y is vertical. + * Probably slightly less optimal for heightmaps or continent maps. + */ +double OpenSimplex2F_noise2_XBeforeY(struct OpenSimplex2F_context *ctx, double x, double y); + +/** + * 3D Re-oriented 4-point BCC noise, classic orientation. + * Proper substitute for 3D Simplex in light of Forbidden Formulae. + * Use noise3_XYBeforeZ or noise3_XZBeforeY instead, wherever appropriate. + */ +double OpenSimplex2F_noise3_Classic(struct OpenSimplex2F_context *ctx, double x, double y, double z); + +/** + * 3D Re-oriented 4-point BCC noise, with better visual isotropy in (X, Y). + * Recommended for 3D terrain and time-varied animations. + * The Z coordinate should always be the "different" coordinate in your use case. + * If Y is vertical in world coordinates, call noise3_XYBeforeZ(x, z, Y) or use noise3_XZBeforeY. + * If Z is vertical in world coordinates, call noise3_XYBeforeZ(x, y, Z). + * For a time varied animation, call noise3_XYBeforeZ(x, y, T). + */ +double OpenSimplex2F_noise3_XYBeforeZ(struct OpenSimplex2F_context *ctx, double x, double y, double z); + +/** + * 3D Re-oriented 4-point BCC noise, with better visual isotropy in (X, Z). + * Recommended for 3D terrain and time-varied animations. + * The Y coordinate should always be the "different" coordinate in your use case. + * If Y is vertical in world coordinates, call noise3_XZBeforeY(x, Y, z). + * If Z is vertical in world coordinates, call noise3_XZBeforeY(x, Z, y) or use noise3_XYBeforeZ. + * For a time varied animation, call noise3_XZBeforeY(x, T, y) or use noise3_XYBeforeZ. + */ +double OpenSimplex2F_noise3_XZBeforeY(struct OpenSimplex2F_context *ctx, double x, double y, double z); + +/** + * 4D OpenSimplex2F noise, classic lattice orientation. + */ +double OpenSimplex2F_noise4_Classic(struct OpenSimplex2F_context *ctx, double x, double y, double z, double w); + +/** + * 4D OpenSimplex2F noise, with XY and ZW forming orthogonal triangular-based planes. + * Recommended for 3D terrain, where X and Y (or Z and W) are horizontal. + * Recommended for noise(x, y, sin(time), cos(time)) trick. + */ +double OpenSimplex2F_noise4_XYBeforeZW(struct OpenSimplex2F_context *ctx, double x, double y, double z, double w); + +/** + * 4D OpenSimplex2F noise, with XZ and YW forming orthogonal triangular-based planes. + * Recommended for 3D terrain, where X and Z (or Y and W) are horizontal. + */ +double OpenSimplex2F_noise4_XZBeforeYW(struct OpenSimplex2F_context *ctx, double x, double y, double z, double w); + +/** + * 4D OpenSimplex2F noise, with XYZ oriented like noise3_Classic, + * and W for an extra degree of freedom. W repeats eventually. + * Recommended for time-varied animations which texture a 3D object (W=time) + */ +double OpenSimplex2F_noise4_XYZBeforeW(struct OpenSimplex2F_context *ctx, double x, double y, double z, double w); +#endif + diff --git a/src/noise.c b/open-simplex/src/OpenSimplex2F.c similarity index 99% rename from src/noise.c rename to open-simplex/src/OpenSimplex2F.c index ca56e09..5a24abd 100644 --- a/src/noise.c +++ b/open-simplex/src/OpenSimplex2F.c @@ -137,13 +137,6 @@ static struct LatticePoint4D *new_LatticePoint4D(int xsv, int ysv, int zsv, int return this; } -struct OpenSimplex2F_context { - int16_t *perm; - struct Grad2 *permGrad2; - struct Grad3 *permGrad3; - struct Grad4 *permGrad4; -}; - #define ARRAYSIZE(x) (sizeof(x) / sizeof((x)[0])) static struct Grad2 GRADIENTS_2D[PSIZE]; @@ -918,3 +911,4 @@ double OpenSimplex2F_noise4_XYZBeforeW(struct OpenSimplex2F_context *ctx, double return noise4_Base(ctx, xs, ys, zs, ws); } + diff --git a/src/Camera.zig b/src/Camera.zig new file mode 100644 index 0000000..88c28df --- /dev/null +++ b/src/Camera.zig @@ -0,0 +1,67 @@ +const Camera = @This(); +pub const TAG = @import("Tag.zig").create(Camera); + +const std = @import("std"); +const Entity = @import("Entity.zig"); +const Scene = @import("Scene.zig"); +const Engine = @import("Engine.zig"); +const Sprite = @import("Sprite.zig"); +const Layer = @import("Layer.zig"); +const Color = @import("Color.zig"); +const Vec2i = @import("geometry/Vec2i.zig"); +const Vec2f = @import("geometry/Vec2f.zig"); +const Recti = @import("geometry/Recti.zig"); + +// focus: Vec2i = Vec2i.EAST.scale(100), +focus: Vec2f = Vec2f.ZERO, +tile_size: i32 = 16, +window_size_offset_x: i32 = 0, +window_size_offset_y: i32 = 0, + +pub fn create() !*Camera { + const self = Camera {}; + return try Scene.allocate(self); +} + +pub fn resize(self: *Camera, width: i32, height: i32) void { + std.debug.print("[Camera:resize] {d} x {d}\n", .{ width, height }); + self.window_size_offset_x = @divFloor(width, 2); + self.window_size_offset_y = @divFloor(height, 2); +} + +pub fn destroy(self: *const Camera) void { + Scene.deallocate(self); +} + +pub fn entity(self: *Camera) Entity { + return Entity.init(self, .{ + .tag = TAG + }); +} + +inline fn world_to_screen_vec2i(self: *const Camera, coords: Vec2i) Vec2i { + const tile_size_f: f32 = @floatFromInt(self.tile_size); + const focus_x_screen: i32 = @intFromFloat(self.focus.x * tile_size_f); + const focus_y_screen: i32 = @intFromFloat(self.focus.y * tile_size_f); + + return Vec2i.create( + self.tile_size * coords.x - focus_x_screen + self.window_size_offset_x, + self.tile_size * coords.y - focus_y_screen + self.window_size_offset_y + ); +} + +inline fn world_to_screen_recti(self: *const Camera, box: Recti) Recti { + return Recti.from_ab( + self.world_to_screen_vec2i(box.a), + self.world_to_screen_vec2i(box.b), + ); +} + +pub fn draw_sprite_i(self: *const Camera, sprite: *const Sprite, world_pos: Recti, layer: Layer, color: Color) void { + const screen_pos = self.world_to_screen_recti(world_pos); + sprite.draw(screen_pos, layer, color); +} + +pub fn set_focus(self: *Camera, f: Vec2f) void { + self.focus = f; +} diff --git a/src/Color.zig b/src/Color.zig index 225ec68..0d55cb3 100644 --- a/src/Color.zig +++ b/src/Color.zig @@ -1,4 +1,4 @@ - +const std = @import("std"); const Color = @This(); r: f32, @@ -6,6 +6,16 @@ g: f32, b: f32, a: f32, +pub fn blend(a: *const Color, b: *const Color, t: f32) Color { + const real_t = 1 - (if (t < 0.0) 0.0 else if (t > 1.0) 1.0 else t); + return Color.rgba( + a.r + (b.r - a.r) * real_t, + a.g + (b.g - a.g) * real_t, + a.b + (b.b - a.b) * real_t, + a.a + (b.a - a.a) * real_t + ); +} + pub fn rgba(r: f32, g: f32, b: f32, a: f32) Color { return .{ .r = r, @@ -15,6 +25,59 @@ pub fn rgba(r: f32, g: f32, b: f32, a: f32) Color { }; } +pub fn with_opacity(self: *const Color, a: f32) Color { + return .{ + .r = self.r, + .g = self.g, + .b = self.b, + .a = a + }; +} + +// r: 0 - 360 +// g: 0 - 1 +// b: 0 - 1 +// a: 0 - 1 +// no idea how it REALLY works, i dont know color theory but chatgpt does +pub fn hsla(h: f32, s: f32, l: f32, a: f32) Color { + const c = (1.0 - @abs(2.0 * l - 1.0)) * s; + const one = (h / 60.0); + const x = c * (1.0 - @abs(@rem(one, 2.0) - 1.0)); + const m = l - c / 2.0; + + var r: f32 = 0.0; + var g: f32 = 0.0; + var b: f32 = 0.0; + + if (h < 60.0) { + r = c; + g = x; + b = 0.0; + } else if (h < 120.0) { + r = x; + g = c; + b = 0.0; + } else if (h < 180.0) { + r = 0.0; + g = c; + b = x; + } else if (h < 240.0) { + r = 0.0; + g = x; + b = c; + } else if (h < 300.0) { + r = x; + g = 0.0; + b = c; + } else { + r = c; + g = 0.0; + b = x; + } + + return Color.rgba(r + m, g + m, b + m, a); +} + pub const WHITE = rgba(1, 1, 1, 1); pub const RED = rgba(1, 0, 0, 1); pub const ORANGE = rgba(1, 0.5, 0, 1); @@ -28,3 +91,5 @@ pub const BLUE = rgba(0, 0, 1, 1); pub const INDIGO = rgba(0.5, 0, 1, 1); pub const MAGENTA = rgba(1, 0, 1, 1); pub const HOT_PINK = rgba(1, 0, 0.5, 1); + +pub const BROWN = rgba(0.4, 0.25, 0.1, 1); diff --git a/src/Engine.zig b/src/Engine.zig index 2a38801..9129ef4 100644 --- a/src/Engine.zig +++ b/src/Engine.zig @@ -15,14 +15,16 @@ const c = @cImport({ @cInclude("glad/glad.h"); @cInclude("GLFW/glfw3.h"); }); -const HashMap = std.HashMap; +const AutoHashMap = std.AutoHashMap; +var store: AutoHashMap(*c.GLFWwindow, *Engine) = AutoHashMap(*c.GLFWwindow, *Engine).init(std.heap.page_allocator); // export const IGame = struct { // render: fn () void, // update: fn (f32) void, // }; current_scene: Scene, +projection: Matrix4f = undefined, pub fn create(initial_scene: Scene) Engine { return .{ @@ -35,9 +37,22 @@ fn errorCallback(err: c_int, desc_c: [*c]const u8) callconv(.C) void { std.log.err("glfw error {x:0>8}: {s}", .{ err, desc }); } +fn on_resize (window: ?*c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void { + std.debug.print("[Engine:on_resize] {?}: {d} x {d}\n", .{ window, width, height }); + c.glViewport(0, 0, width, height); + const engine_instance = store.get(window.?).?; + engine_instance.set_matrices(width, height); + engine_instance.current_scene.resize(width, height); +} + +pub fn set_matrices(self: *Engine, width: i32, height: i32) void { + self.projection = Matrix4f.orthographic(0, @floatFromInt(width), @floatFromInt(height), 0, 0, 100); + shaders.set_projection_matrix(&self.projection); +} + // export fn run(game: *const IGame) !void { pub fn run(self: *Engine) !void { - + // in case of errors, set callback early! // for some reason this returns an error function as well?? _ = c.glfwSetErrorCallback(errorCallback); @@ -63,6 +78,8 @@ pub fn run(self: *Engine) !void { }; defer c.glfwDestroyWindow(window); + try store.put(window, self); + // load opengl into the window c.glfwMakeContextCurrent(window); if (c.gladLoadGL() != 1) { @@ -78,6 +95,8 @@ pub fn run(self: *Engine) !void { try assets.load(); + _ = c.glfwSetWindowSizeCallback(window, on_resize); + const clearBrightness: f32 = 0.09; c.glClearColor(clearBrightness, clearBrightness, clearBrightness, 1.0); @@ -92,16 +111,15 @@ pub fn run(self: *Engine) !void { var width: c_int = undefined; var height: c_int = undefined; c.glfwGetFramebufferSize(window, @ptrCast(&width), @ptrCast(&height)); - - const projection = Matrix4f.orthographic(0, @floatFromInt(width), @floatFromInt(height), 0, 0, 100); + std.debug.print("[Engine:run] window size {d} x {d}\n", .{ width, height }); + self.set_matrices(width, height); + try self.current_scene.start(); // run the main loop while (c.glfwWindowShouldClose(window) == 0) { - shaders.set_projection_matrix(&projection); c.glClear(c.GL_COLOR_BUFFER_BIT | c.GL_DEPTH_BUFFER_BIT); self.current_scene.draw(); - // game.render(); - // game.update(1.0); + assets.terrain[0][0].draw(Recti.from_xywh(-1000, -1000, 50000, 50000), Layer.FLOOR, Color.WHITE); c.glfwSwapBuffers(window); c.glfwPollEvents(); diff --git a/src/Entity.zig b/src/Entity.zig index 12ba754..89207a8 100644 --- a/src/Entity.zig +++ b/src/Entity.zig @@ -3,23 +3,29 @@ const Layer = @import("Layer.zig"); const Color = @import("Color.zig"); const Recti = @import("geometry/Recti.zig"); const std = @import("std"); +const Tag = @import("Tag.zig"); +const Scene = @import("Scene.zig"); const Entity = @This(); const EntityOptions = struct { - renders: bool = true, - updates: bool = true, - accepts_keyboard: bool = false, - accepts_mouse: bool = false, + tag: Tag = Tag.NONE, }; ptr: *anyopaque, -options: EntityOptions, -destroyFn: *const fn(*const anyopaque) void, -updateFn: *const fn(*anyopaque, f32) void, -drawFn: *const fn(*const anyopaque) void, +tag: Tag, +_destroy: *const fn(*const anyopaque) void, +_update: *const fn(*anyopaque, f32) void, +_draw: *const fn(*const anyopaque) void, +_draw_opacity: *const fn(*const anyopaque) void, +_start: *const fn(*anyopaque, *Scene) void, +_resize: *const fn(*anyopaque, i32, i32) void, pub fn init(ptr: anytype, options: EntityOptions) Entity { + std.debug.print("[Entity:init] creating entity {s} with tag \"{s}\"\n", .{ + @typeName(@typeInfo(@TypeOf(ptr)).Pointer.child), + options.tag.type + }); const MutablePointer = @TypeOf(ptr); // @compileLog("Compiling Entity type for " ++ @typeName(MutablePointer)); @@ -41,38 +47,86 @@ pub fn init(ptr: anytype, options: EntityOptions) Entity { // @compileLog("const pointer type: " ++ @typeName(ConstPointer)); const gen = struct { - pub fn destroyImpl(pointer: *const anyopaque) void { + pub fn _destroy(pointer: *const anyopaque) void { const self: ConstPointer = @ptrCast(@alignCast(pointer)); type_info.Pointer.child.destroy(self); } - pub fn updateImpl(pointer: *anyopaque, dt: f32) void { + pub fn _start(pointer: *anyopaque, scene: *Scene) void { const self: MutablePointer = @ptrCast(@alignCast(pointer)); - type_info.Pointer.child.update(self, dt); + if(@hasDecl(type_info.Pointer.child, "start")) { + type_info.Pointer.child.start(self, scene); + } } - pub fn drawImpl(pointer: *const anyopaque) void { + pub fn _resize(pointer: *anyopaque, width: i32, height: i32) void { + const self: MutablePointer = @ptrCast(@alignCast(pointer)); + if(@hasDecl(type_info.Pointer.child, "resize")) { + type_info.Pointer.child.resize(self, width, height); + } + } + pub fn _update(pointer: *anyopaque, dt: f32) void { + const self: MutablePointer = @ptrCast(@alignCast(pointer)); + if(@hasDecl(type_info.Pointer.child, "update")) { + type_info.Pointer.child.update(self, dt); + } + } + pub fn _draw(pointer: *const anyopaque) void { const self: ConstPointer = @ptrCast(@alignCast(pointer)); - type_info.Pointer.child.draw(self); + if(@hasDecl(type_info.Pointer.child, "draw")) { + type_info.Pointer.child.draw(self); + } + } + pub fn _draw_opacity(pointer: *const anyopaque) void { + const self: ConstPointer = @ptrCast(@alignCast(pointer)); + if(@hasDecl(type_info.Pointer.child, "draw_opacity")) { + type_info.Pointer.child.draw_opacity(self); + } } }; return .{ .ptr = ptr, - .options = options, - .destroyFn = gen.destroyImpl, - - .updateFn = gen.updateImpl, - .drawFn = gen.drawImpl, + .tag = options.tag, + ._destroy = gen._destroy, + ._start = gen._start, + ._resize = gen._resize, + ._update = gen._update, + ._draw = gen._draw, + ._draw_opacity = gen._draw_opacity, }; } pub fn update(self: *const Entity, dt: f32) void { - self.updateFn(self.ptr, dt); + self._update(self.ptr, dt); } pub fn draw(self: *const Entity) void { - self.drawFn(self.ptr); + self._draw(self.ptr); +} + +pub fn draw_opacity(self: *const Entity) void { + self._draw_opacity(self.ptr); } pub fn destroy(self: *const Entity) void { - self.destroyFn(self.ptr); + self._destroy(self.ptr); +} + +pub fn start(self: *const Entity, scene: *Scene) void { + self._start(self.ptr, scene); +} + +pub fn resize(self: *const Entity, width: i32, height: i32) void { + self._resize(self.ptr, width, height); +} + +pub fn allocate_array(comptime T: type, size: usize, default: T) ![]T { + const ptr = try std.heap.page_allocator.alloc(T, size); + for (0..size) |idx| { + ptr[idx] = default; + } + return ptr; +} + +pub fn free_array(ptr: anytype) void { + std.heap.page_allocator.free(ptr); } diff --git a/src/Noise.zig b/src/Noise.zig new file mode 100644 index 0000000..838d6bf --- /dev/null +++ b/src/Noise.zig @@ -0,0 +1,171 @@ +const c = @cImport({ + @cInclude("OpenSimplex2F.h"); +}); +const std = @import("std"); +const ArrayList = std.ArrayList; +const RealColor = @import("Color.zig"); + +const Noise = @This(); +const NoiseContext = c.OpenSimplex2F_context; +const NoiseContextPointer = ?*NoiseContext; + +octave_count: usize = 1, +scale: f32 = 1, +noise_context: NoiseContext = undefined, +noise_ptr: NoiseContextPointer = undefined, + +pub fn create(seed: i64, octaves: usize, scale: f32) Noise { + var self = Noise { + .octave_count = octaves, + .scale = scale, + }; + self.noise_context = NoiseContext {}; + self.noise_ptr = &self.noise_context; + _ = c.OpenSimplex2F(seed, @ptrCast(&self.noise_ptr)); + return self; +} + +pub fn get(self: *const Noise, x: f32, y: f32) f32 { + var total: f32 = 0; + var max: f32 = 0; + for (0..self.octave_count) |idx| { + // in a series / sum n = 1 -> inf + const n: f32 = @floatFromInt(idx + 1); + const amplitude: f32 = 1.0 / n; + // frequency is altered both by the scale of the while gen + // and the octave we're on. + const frequency: f32 = self.scale * n; + const sx = x * frequency + n; + const sy = y * frequency; + // -amplitude <= r <= amplitude + const r: f32 = @as(f32, @floatCast(c.OpenSimplex2F_noise2(self.noise_ptr, sx, sy))) * amplitude; + total += r; + max += amplitude; + } + const n: f32 = total / max; + return @min(@max((n + 1.0) / 2.0, 0), 1); +} + +pub fn destroy(self: *const Noise) void { + _ = c.OpenSimplex2F_free(self.noise_ptr); +} + +pub const Octave = struct { + frequency: f32, + amplitude: f32, +}; + +pub const Custom = struct { + octaves: ArrayList(Octave) = undefined, + context: NoiseContext = undefined, + context_ptr: NoiseContextPointer = undefined, + scale: f32 = 1, + + pub fn create(seed: i64, scale: f32, octaves: []const Octave) !Custom { + var self = Custom { + .scale = scale + }; + self.octaves = ArrayList(Octave).init(std.heap.page_allocator); + self.context = NoiseContext {}; + self.context_ptr = &self.context; + _ = c.OpenSimplex2F(seed, @ptrCast(&self.context_ptr)); + + for (octaves) |octave| { + try self.octaves.append(octave); + } + + return self; + } + + pub fn get(self: *const Custom, x: f32, y: f32) f32 { + var total: f32 = 0; + var max: f32 = 0; + for (self.octaves.items) |octave| { + // @compileLog(octave); + // octave + const sx = x * self.scale * octave.frequency; + const sy = y * self.scale * octave.frequency; + const r: f32 = @as(f32, @floatCast(c.OpenSimplex2F_noise2(self.context_ptr, sx, sy))) * octave.amplitude; + total += r; + max += octave.amplitude; + } + const n: f32 = total / max; + return @min(@max((n + 1.0) / 2.0, 0), 1); + } + + pub fn destroy(self: *const Custom) void { + _ = c.OpenSimplex2F_free(self.context_ptr); + self.octaves.deinit(); + } +}; + +pub const Color = struct { + noise_h: Noise.Custom, + noise_s: Noise.Custom, + noise_l: Noise.Custom, + range: ColorRange, + + const OCTAVES = [_]Octave{ + .{ .frequency = 1, .amplitude = 1.0 }, + .{ .frequency = 2, .amplitude = 0.5 }, + .{ .frequency = 4, .amplitude = 0.4 }, + .{ .frequency = 8, .amplitude = 0.3 }, + .{ .frequency = 16, .amplitude = 0.2 }, + .{ .frequency = 30, .amplitude = 1.0 }, + }; + + pub fn create(seed: i64, range: ColorRange) !Noise.Color { + return .{ + .noise_h = try Noise.Custom.create(seed + 0, 0.03, &OCTAVES), + .noise_s = try Noise.Custom.create(seed + 1, 0.03, &OCTAVES), + .noise_l = try Noise.Custom.create(seed + 2, 0.03, &OCTAVES), + .range = range, + }; + } + + pub fn get(self: *const Noise.Color, x: f32, y: f32) RealColor { + return RealColor.hsla( + self.noise_h.get(x, y) * (self.range.h[1] - self.range.h[0]) + self.range.h[0], + self.noise_s.get(x, y) * (self.range.s[1] - self.range.s[0]) + self.range.s[0], + self.noise_l.get(x, y) * (self.range.l[1] - self.range.l[0]) + self.range.l[0], + 1.0 + ); + } + + pub fn destroy(self: *const Noise.Color) void { + self.noise_h.destroy(); + self.noise_s.destroy(); + self.noise_l.destroy(); + } +}; + +pub const ColorRange = struct { + // min / max values + h: struct { f32, f32 }, + s: struct { f32, f32 }, + l: struct { f32, f32 }, + + pub const DIRT = Noise.ColorRange { + .h = .{ 30, 33 }, + .s = .{ 0.30, 0.45 }, + .l = .{ 0.12, 0.22 }, + }; + + pub const STONE = Noise.ColorRange { + .h = .{ 0, 360 }, + .s = .{ 0.00, 0.02 }, + .l = .{ 0.20, 0.30 }, + }; + + pub const GRASS = Noise.ColorRange { + .h = .{ 100, 110 }, + .s = .{ 0.72, 0.82 }, + .l = .{ 0.25, 0.40 }, + }; + + pub const GRASS_TUNDRA = Noise.ColorRange { + .h = .{ 115, 125 }, + .s = .{ 0.4, 0.5 }, + .l = .{ 0.2, 0.3 }, + }; +}; diff --git a/src/Pawn.zig b/src/Pawn.zig new file mode 100644 index 0000000..663d308 --- /dev/null +++ b/src/Pawn.zig @@ -0,0 +1,52 @@ +const Pawn = @This(); + +const std = @import("std"); +const Entity = @import("Entity.zig"); +const Scene = @import("Scene.zig"); +const Camera = @import("Camera.zig"); +const Terrain = @import("Terrain.zig"); +const Tag = @import("Tag.zig"); +const Vec2i = @import("geometry/Vec2i.zig"); +const assets = @import("assets.zig"); +const Layer = @import("Layer.zig"); +const Color = @import("Color.zig"); +var prng = std.rand.DefaultPrng.init(0); + +camera: *const Camera = undefined, +position: Vec2i = Vec2i.ZERO, + +pub fn random() !*Pawn { + var self: Pawn = Pawn {}; + self.position = Vec2i.create( + @intFromFloat(@floor(prng.random().float(f32) * @as(f32, @floatFromInt(Terrain.CHUNK_SIZE)))), + @intFromFloat(@floor(prng.random().float(f32) * @as(f32, @floatFromInt(Terrain.CHUNK_SIZE)))) + ); + return try Scene.allocate(self); +} + +pub fn create() !*Pawn { + const self: Pawn = Pawn {}; + return try Scene.allocate(self); +} + +pub fn start(self: *Pawn, scene: *const Scene) void { + self.camera = scene.get(Camera.TAG, Camera); +} + +pub fn destroy(self: *const Pawn) void { + Scene.deallocate(self); +} + +pub fn entity(self: *Pawn) Entity { + return Entity.init(self, .{}); +} + +// pub fn tile(self: *Pawn) Tile { +// return Tile.init(.{ +// .solid = true +// }); +// } + +pub fn draw(self: *const Pawn) void { + self.camera.draw_sprite_i(assets.pawn, self.position.to_unit_recti(), Layer.ENTITIES, Color.WHITE); +} diff --git a/src/Scene.zig b/src/Scene.zig index 6444789..ea56039 100644 --- a/src/Scene.zig +++ b/src/Scene.zig @@ -6,16 +6,19 @@ const Layer = @import("Layer.zig"); const Color = @import("Color.zig"); const Entity = @import("Entity.zig"); const Terrain = @import("Terrain.zig"); +const Tag = @import("Tag.zig"); const std = @import("std"); const ArrayList = std.ArrayList; +const AutoHashMap = std.AutoHashMap; -entities: ArrayList(Entity), +entities: ArrayList(Entity) = undefined, +tagged_entities: AutoHashMap(Tag.ID, *Entity) = undefined, pub fn create() Scene { - const e: ArrayList(Entity) = ArrayList(Entity).init(std.heap.page_allocator); - return .{ - .entities = e - }; + var self = Scene {}; + self.entities = ArrayList(Entity).init(std.heap.page_allocator); + self.tagged_entities = AutoHashMap(Tag.ID, *Entity).init(std.heap.page_allocator); + return self; } pub fn destroy(self: *Scene) void { @@ -26,11 +29,37 @@ pub fn destroy(self: *Scene) void { } pub fn start(self: *Scene) !void { - const terrain = try Terrain.create(); - try self.add(terrain); + std.debug.print("[Scene:start] Scene starting...\n", .{}); + std.debug.print("[Scene:start] entities: {}\n", .{ self.entities.items.len }); + std.debug.print("[Scene:start] tagged entities: {}\n", .{ self.tagged_entities.count() }); + for (self.entities.items) |entity| { + entity.start(self); + } } -// --- +pub fn get(self: *const Scene, tag: Tag, comptime T: type) *T { + const tag_id = tag.id; + const entity_ptr: *Entity = self.tagged_entities.get(tag_id) orelse @panic("Could not find entity"); + // @compileLog(entity_ptr); + const opaque_ptr = entity_ptr.ptr; + const real_ptr: *T = @ptrCast(@alignCast(opaque_ptr)); + return real_ptr; + // var iterator = self.tagged_entities.iterator(); + // while (iterator.next()) |entry| { + // const id_ptr: *Tag.ID = entry.key_ptr; + // const entity_ptr_ptr: **Entity = entry.value_ptr; + // const entity_ptr: *Entity = entity_ptr_ptr.*; + // + // const other_id = entity_ptr.tag.id; + // if (tag_id == other_id) { + // const opaque_ptr: *anyopaque = entity_ptr.ptr; + // const real_ptr: *T = @ptrCast(@alignCast(opaque_ptr)); + // // @compileLog(r); + // return real_ptr; + // } + // } + // @panic("Could not find entity"); +} pub fn add(self: *Scene, instance_ptr: anytype) !void { const instance_ptr_type = @TypeOf(instance_ptr); @@ -52,7 +81,14 @@ pub fn add(self: *Scene, instance_ptr: anytype) !void { if (!@hasDecl(instance_type, "entity")) @compileError("Pointer must be to a struct with fn entity() Entity"); - try self.entities.append(instance_ptr.entity()); + const entity: Entity = instance_ptr.entity(); + + const entity_ptr = try self.entities.addOne(); + entity_ptr.* = entity; + + if (entity_ptr.tag.id != Tag.NONE.id) { + try self.tagged_entities.put(entity.tag.id, entity_ptr); + } } pub fn update(self: *Scene, dt: f32) void { @@ -62,14 +98,18 @@ pub fn update(self: *Scene, dt: f32) void { } pub fn draw(self: *Scene) void { - for (self.entities.items) |entity| { entity.draw(); } + for (self.entities.items) |entity| { + entity.draw_opacity(); + } +} - assets.heart.draw(&Recti.from_xywh(120, 100, 32, 64), Layer.ENTITIES, Color.LIGHT_BLUE); - assets.heart.draw(&Recti.from_xywh(100, 100, 32, 64), Layer.FLOOR, Color.WHITE); - assets.heart.draw(&Recti.from_xywh(80, 100, 32, 64), Layer.ENTITIES, Color.INDIGO); +pub fn resize(self: *Scene, width: i32, height: i32) void { + for (self.entities.items) |entity| { + entity.resize(width, height); + } } // --- helpfer functions for entities themselves diff --git a/src/Sprite.zig b/src/Sprite.zig index 2432467..e8d196a 100644 --- a/src/Sprite.zig +++ b/src/Sprite.zig @@ -16,7 +16,7 @@ texture: *const Texture = undefined, pub fn create(tex: *const Texture, rect: Recti) Sprite { return .{ - .texture_area = rect.scalef(Vec2f.new( + .texture_area = rect.scalef(Vec2f.create( @floatFromInt(tex.width), @floatFromInt(tex.height) )), @@ -24,7 +24,7 @@ pub fn create(tex: *const Texture, rect: Recti) Sprite { }; } -pub fn draw(self: *const Sprite, screen_pos: *const Recti, layer: Layer, color: Color) void { +pub fn draw(self: *const Sprite, screen_pos: Recti, layer: Layer, color: Color) void { self.texture.bind(c.GL_TEXTURE0); c.glBegin(c.GL_QUADS); { diff --git a/src/Tag.zig b/src/Tag.zig new file mode 100644 index 0000000..68fa822 --- /dev/null +++ b/src/Tag.zig @@ -0,0 +1,47 @@ +const Tag = @This(); +const std = @import("std"); +pub const ID = *const anyopaque; + +id: ID, +type: []const u8, + +fn create_id_for_type(comptime T: type) ID { + // create a unique struct for each type this is called with. + const unique_struct = struct { + // this simply gives the struct a size, so the pointer is not + // optimized into oblivion. + var x: u8 = 0; + + // capture the type so zig knows not to make this + // struct the same for each call. if the type isnt captured + // zig will create one struct in memory for each unique call. + // ie: the function will be unique, but this struct wont be. + comptime { + _ = T; + } + }; + + // @compileLog(T); + // @compileLog(&unique_struct.x); + + // return the pointer to the only data within the struct, + // again so the compiler doesnt optimize this into non-existence. + return &unique_struct.x; +} + +pub const NONE = Tag { + .type = "None", + .id = create_id_for_type(struct {}), +}; + +pub fn create(comptime t: type) Tag { + // @compileLog(@typeName(t)); + return Tag { + .type = @typeName(t), + .id = create_id_for_type(t), + }; +} + +pub inline fn equals(self: *const Tag, other: Tag) bool { + return self.id == other.id; +} diff --git a/src/Terrain.zig b/src/Terrain.zig index ad97431..d20a17c 100644 --- a/src/Terrain.zig +++ b/src/Terrain.zig @@ -1,45 +1,206 @@ const Terrain = @This(); - const assets = @import("assets.zig"); const Layer = @import("Layer.zig"); const Color = @import("Color.zig"); const Recti = @import("geometry/Recti.zig"); +const Vec2f = @import("geometry/Vec2f.zig"); const Entity = @import("Entity.zig"); const Scene = @import("Scene.zig"); +const Camera = @import("Camera.zig"); const std = @import("std"); +const allocator = std.heap.page_allocator; var prng = std.rand.DefaultPrng.init(0); +const Noise = @import("Noise.zig"); -size: u16 = 24, +const TAG = @import("Tag.zig").create(Terrain); -pub fn entity(self: *Terrain) Entity { - return Entity.init(self, .{}); +pub const CHUNK_SIZE = 48; +const CHUNK_LENGTH = CHUNK_SIZE * CHUNK_SIZE; + +const tile_size: usize = 16; + +pub inline fn xy_to_index(x: i32, y: i32) i32 { + return x + y * CHUNK_SIZE; } -pub fn create() !*Terrain { - return try Scene.allocate(Terrain { - .size = 24, +pub inline fn index_to_xy(index: i32) struct { i32, i32 } { + const y: i32 = @divFloor(index, CHUNK_SIZE); + const x: i32 = @rem(index, CHUNK_SIZE); + return .{ x, y }; +} + +const GROWS = true; +const GROWTH_RATE: f32 = 0.5; + +pub const Tile = struct { + texture_idx: usize, + growth: f32, + stone_decoration: f32, + grass_color: Color, + dirt_color: Color, + stone_color: Color, + + pub const NULL = Tile { + .texture_idx = 0, + .growth = 0, + .stone_decoration = 0, + .grass_color = Color.WHITE, + .dirt_color = Color.WHITE, + .stone_color = Color.WHITE, + }; +}; + +tiles: []Tile = undefined, + +// the noise for deciding random textures +texture_noise: Noise, +// noise for deciding grass growth +growth_noise: Noise, +// noise for stone decoration placement +stone_decoration_noise: Noise.Custom, +// noise for colors +dirt_color_noise: Noise.Color, +grass_color_noise: Noise.Color, +stone_color_noise: Noise.Color, + + +camera: *Camera = undefined, + +pub fn entity(self: *Terrain) Entity { + return Entity.init(self, .{ + .tag = TAG, }); } +pub fn start(self: *Terrain, scene: *Scene) void { + self.camera = scene.get(Camera.TAG, Camera); + self.camera.set_focus(Vec2f.create(CHUNK_SIZE / 2, CHUNK_SIZE / 2)); +} + +fn contrast(n: f32, blend_strength: f32) f32 { + const pi_halves = std.math.pi / 2.0; + const two_piths = 2.0 / std.math.pi; + const true_blend = std.math.pow(f32, 1.2, blend_strength) - 1; + const numerator_qty = pi_halves * (2 * n - 1) * true_blend; + const numerator = two_piths * std.math.atan(numerator_qty); + const denominator = two_piths * std.math.atan(pi_halves * true_blend); + const unadjusted_total = numerator / denominator; + // const adjusted_total = (unadjusted_total + 1.0) / 2.0; + return (unadjusted_total + 1.0) / 2.0; +} + +fn generate_tiles(self: *Terrain) void { + for(0..CHUNK_LENGTH) |idx| { + const x, const y = index_to_xy(@intCast(idx)); + const fx: f32 = @floatFromInt(x); + const fy: f32 = @floatFromInt(y); + const texture_random = self.texture_noise.get(fx, fy); + self.tiles[idx] = Tile { + .texture_idx = @intFromFloat(@floor(texture_random * 4)), + .growth = contrast(self.growth_noise.get(fx, fy), 10), + .stone_decoration = self.stone_decoration_noise.get(fx, fy), + .dirt_color = self.dirt_color_noise.get(fx, fy), + .grass_color = self.grass_color_noise.get(fx, fy), + .stone_color = self.stone_color_noise.get(fx, fy), + }; + } +} + +pub fn create(seed: i64) !*Terrain { + var self: *Terrain = try Scene.allocate(Terrain { + .tiles = try Entity.allocate_array(Tile, CHUNK_LENGTH, Tile.NULL), + .growth_noise = Noise.create(seed, 10, 0.03), + .texture_noise = Noise.create(seed + 1, 1, 3), + .stone_decoration_noise = try Noise.Custom.create(seed + 2, 1, &.{ + .{ .frequency = 0.03, .amplitude = 1 }, + .{ .frequency = 1, .amplitude = 1 } + }), + .dirt_color_noise = try Noise.Color.create(seed + 3, Noise.ColorRange.DIRT), + .grass_color_noise = try Noise.Color.create(seed + 4, Noise.ColorRange.GRASS), + .stone_color_noise = try Noise.Color.create(seed + 5, Noise.ColorRange.STONE) + }); + + self.generate_tiles(); + return self; +} + pub fn destroy(self: *const Terrain) void { + self.texture_noise.destroy(); + self.growth_noise.destroy(); + self.stone_decoration_noise.destroy(); + + self.dirt_color_noise.destroy(); + self.grass_color_noise.destroy(); + self.stone_color_noise.destroy(); + + Entity.free_array(self.tiles); Scene.deallocate(self); } -pub fn update(self: *Terrain, _: f32) void { - if (prng.random().int(u8) == 34) { - self.size = self.size + 1; +fn chance(percent: f32) bool { + return prng.random().float(f32) < percent; +} + +fn grow(self: *Terrain, x: i32, y: i32, amt: f32) void { + if (x < 0 or x >= CHUNK_SIZE) return; + if (y < 0 or y >= CHUNK_SIZE) return; + const idx: usize = @intCast(xy_to_index(x, y)); + const tile = &self.tiles[idx]; + // @compileLog(tile); + tile.*.growth = @min(@max(tile.growth + amt, 0), 1); +} + +pub fn update(self: *Terrain, dt: f32) void { + if (GROWS) { + for (0..CHUNK_LENGTH) |idx| { + const x, const y = index_to_xy(@intCast(idx)); + const tile = self.tiles[idx]; + const should_grow = chance(dt * GROWTH_RATE) and chance(tile.growth); + if (should_grow) { + self.grow(x, y, 0.010); + + self.grow(x + 1, y, 0.008); + self.grow(x - 1, y, 0.008); + self.grow(x, y + 1, 0.008); + self.grow(x, y - 1, 0.008); + + self.grow(x + 1, y + 1, 0.004); + self.grow(x + 1, y - 1, 0.004); + self.grow(x - 1, y + 1, 0.004); + self.grow(x - 1, y - 1, 0.004); + } + } } } pub fn draw(self: *const Terrain) void { - for(0..self.size) |y| { - for(0..self.size) |x| { - const idx: usize = @intFromFloat(@floor(prng.random().float(f32) * 4)); - var grey = prng.random().float(f32); - grey /= 4.0; - grey += 3.0 / 4.0; - const rect = Recti.from_xywh(@intCast(100 + 16 * x), @intCast(100 + 16 * y), 16, 16); - assets.terrain[idx].draw(&rect, Layer.FLOOR, Color.rgba(grey, grey, grey, 1)); - } + for(0..CHUNK_LENGTH) |idx| { + const tile = self.tiles[idx]; + const x, const y = index_to_xy(@intCast(idx)); + const rect = Recti.from_xywh(x, y, 1, 1); + const sprite = assets.terrain[0][tile.texture_idx]; + self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, tile.grass_color); + // switch(tile.texture_idx) { + // 0 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.WHITE), + // 1 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.RED), + // 2 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.GREEN), + // 3 => self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, Color.BLUE), + // else => {} + // } } } + +// pub fn draw_opacity(self: *const Terrain) void { +// for(0..CHUNK_LENGTH) |idx| { +// const tile = self.tiles[idx]; +// const x, const y = index_to_xy(@intCast(idx)); +// // const grassiness_max: f32 = @floatFromInt(assets.terrain.len + 1); +// // const grassiness: usize = @intFromFloat(@floor(tile.growth * grassiness_max)); +// // if (grassiness == 0) continue; +// // const grassiness_idx = assets.terrain.len - @min(4, grassiness); +// const rect = Recti.from_xywh(x, y, 1, 1); +// const sprite = assets.terrain[0][tile.texture_idx]; +// const color = tile.grass_color.with_opacity(tile.growth); +// self.camera.draw_sprite_i(sprite, rect, Layer.FLOOR, color); +// } +// } diff --git a/src/Texture.zig b/src/Texture.zig index fc2853c..c87f3af 100644 --- a/src/Texture.zig +++ b/src/Texture.zig @@ -59,7 +59,7 @@ pub fn create(path: []const u8) !Texture { c.glTexImage2D(c.GL_TEXTURE_2D, 0, c.GL_RGBA, @intCast(ihdr.width), @intCast(ihdr.height), 0, c.GL_RGBA, c.GL_UNSIGNED_BYTE, @ptrCast(out_buf)); c.glBindTexture(c.GL_TEXTURE_2D, 0); - std.debug.print("texture generated: ID={} Path={s}\n", .{ tex_id, path }); + std.debug.print("[Texture:create] texture generated: ID={} Path={s}\n", .{ tex_id, path }); return .{ .handle = @intCast(tex_id), diff --git a/src/assets.zig b/src/assets.zig index 54f9cad..25c79c9 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -6,27 +6,77 @@ const std = @import("std"); const Assets = struct { tree: Sprite, heart: Sprite, - terrain: [4]Sprite, + pawn: Sprite, + terrain: [4][4]Sprite, + rocks: [4]Sprite, }; var assets: Assets = undefined; var texture: Texture = undefined; +inline fn make8 (x: i32, y: i32, w: i32, h: i32) Sprite { + return Sprite.create(&texture, Recti.from_xywh(x * 8, y * 8, w * 8, h * 8)); +} + pub fn load() !void { + + // @compileLog(@TypeOf(terrain)); + texture = try Texture.create("textures.png"); assets = .{ - .tree = Sprite.create(&texture, Recti.from_xywh(8*27, 8*4, 8, 16)), - .heart = Sprite.create(&texture, Recti.from_xywh(8*27, 8*4, 8, 16)), - .terrain = [_]Sprite { - Sprite.create(&texture, Recti.from_xywh(8*3, 8*8, 8, 8)), - Sprite.create(&texture, Recti.from_xywh(8*3, 8*9, 8, 8)), - Sprite.create(&texture, Recti.from_xywh(8*4, 8*8, 8, 8)), - Sprite.create(&texture, Recti.from_xywh(8*4, 8*9, 8, 8)), + .tree = make8(27, 4, 1, 2), + .heart = make8(27, 4, 1, 2), + .pawn = make8(6, 11, 1, 1), + .terrain = [4][4]Sprite { + [4]Sprite { make8(18, 8, 1, 1), make8(19, 8, 1, 1), make8(18, 9, 1, 1), make8(19, 9, 1, 1) }, + [4]Sprite { make8(20, 8, 1, 1), make8(21, 8, 1, 1), make8(20, 9, 1, 1), make8(21, 9, 1, 1) }, + [4]Sprite { make8(18, 10, 1, 1), make8(19, 10, 1, 1), make8(18, 11, 1, 1), make8(19, 11, 1, 1) }, + [4]Sprite { make8(20, 10, 1, 1), make8(21, 10, 1, 1), make8(20, 11, 1, 1), make8(21, 11, 1, 1) }, + }, + .rocks = [4]Sprite { + make8(18, 12, 1, 1), make8(19, 12, 1, 1), make8(20, 12, 1, 1), make8(21, 12, 1, 1) } }; } +fn make_pointers(comptime array_ptr: anytype) + [@typeInfo(@typeInfo(@TypeOf(array_ptr)).Pointer.child).Array.len] + *const @typeInfo(@typeInfo(@TypeOf(array_ptr)).Pointer.child).Array.child +{ + const len = @typeInfo(@typeInfo(@TypeOf(array_ptr)).Pointer.child).Array.len; + const item_type = @typeInfo(@typeInfo(@TypeOf(array_ptr)).Pointer.child).Array.child; + + comptime var ptr_array: [len]*const item_type = undefined; + comptime for (0..len) |idx| { + ptr_array[idx] = &(array_ptr.*[idx]); + }; + + return ptr_array; +} + pub const tree: *const Sprite = &assets.tree; pub const heart: *const Sprite = &assets.heart; -pub const terrain:[]const Sprite = &assets.terrain; +pub const pawn: *const Sprite = &assets.pawn; +pub const terrain = [4]*const [4]*const Sprite { + &make_pointers(&assets.terrain[0]), + &make_pointers(&assets.terrain[1]), + &make_pointers(&assets.terrain[2]), + &make_pointers(&assets.terrain[3]), +}; +pub const rocks: *const Sprite = make_pointers(&assets.rocks); + + + + + + + + + + + + + + + diff --git a/src/geometry/Matrix4f.zig b/src/geometry/Matrix4f.zig index e153665..93dc03a 100644 --- a/src/geometry/Matrix4f.zig +++ b/src/geometry/Matrix4f.zig @@ -1,6 +1,6 @@ const Matrix4f = @This(); -values: [16]f32 = identity, +values: [16]f32, const SIZE: i8 = 4 * 4; const identity = from_values([16]f32 { diff --git a/src/geometry/Rectf.zig b/src/geometry/Rectf.zig index 49209a1..d4b884b 100644 --- a/src/geometry/Rectf.zig +++ b/src/geometry/Rectf.zig @@ -17,8 +17,8 @@ pub fn from_xywh(x: f32, y: f32, w: f32, h: f32) Rectf { .y = y, .w = w, .h = h, - .a = Vec2f.new(x, y), - .b = Vec2f.new(x + w, y + h), + .a = Vec2f.create(x, y), + .b = Vec2f.create(x + w, y + h), }; } diff --git a/src/geometry/Recti.zig b/src/geometry/Recti.zig index 77cd44d..03365a9 100644 --- a/src/geometry/Recti.zig +++ b/src/geometry/Recti.zig @@ -1,6 +1,7 @@ const Vec2i = @import("./Vec2i.zig"); const Vec2f = @import("./Vec2f.zig"); const Rectf = @import("./Rectf.zig"); +const std = @import("std"); const Recti = @This(); @@ -17,8 +18,25 @@ pub fn from_xywh(x: i32, y: i32, w: i32, h: i32) Recti { .y = y, .w = w, .h = h, - .a = Vec2i.new(x, y), - .b = Vec2i.new(x + w, y + h), + .a = Vec2i.create(x, y), + .b = Vec2i.create(x + w, y + h), + }; +} + +pub fn from_ab(a: Vec2i, b: Vec2i) Recti { + const tlx = @min(a.x, b.x); + const tly = @min(a.y, b.y); + const brx = @max(a.x, b.x); + const bry = @max(a.y, b.y); + const width = brx - tlx; + const height = bry - tly; + return .{ + .x = tlx, + .y = tly, + .w = width, + .h = height, + .a = Vec2i.create(tlx, tly), + .b = Vec2i.create(brx, bry), }; } diff --git a/src/geometry/Vec2f.zig b/src/geometry/Vec2f.zig index 1d7630a..b01db3d 100644 --- a/src/geometry/Vec2f.zig +++ b/src/geometry/Vec2f.zig @@ -4,16 +4,16 @@ const Vec2f = @This(); x: f32, y: f32, -pub fn new(x: f32, y: f32) Vec2f { +pub fn create(x: f32, y: f32) Vec2f { return .{ .x = x, .y = y, }; } -pub const ZERO = new(0, 0); -pub const ONE = new(1, 1); -pub const NORTH = new(0, -1); -pub const SOUTH = new(0, 1); -pub const EAST = new(1, 0); -pub const WEST = new(-1, 0); +pub const ZERO = create(0, 0); +pub const ONE = create(1, 1); +pub const NORTH = create(0, -1); +pub const SOUTH = create(0, 1); +pub const EAST = create(1, 0); +pub const WEST = create(-1, 0); diff --git a/src/geometry/Vec2i.zig b/src/geometry/Vec2i.zig index 8bb81aa..01a9355 100644 --- a/src/geometry/Vec2i.zig +++ b/src/geometry/Vec2i.zig @@ -1,18 +1,31 @@ const Vec2i = @This(); +const Recti = @import("Recti.zig"); + x: i32, y: i32, -pub fn new(x: i32, y: i32) Vec2i { +pub fn create(x: i32, y: i32) Vec2i { return .{ .x = x, .y = y, }; } -pub const ZERO = new(0, 0); -pub const ONE = new(1, 1); -pub const NORTH = new(0, -1); -pub const SOUTH = new(0, 1); -pub const EAST = new(1, 0); -pub const WEST = new(-1, 0); +pub fn scale(self: *const Vec2i, s: i32) Vec2i { + return .{ + .x = self.x * s, + .y = self.y * s + }; +} + +pub fn to_unit_recti(self: *const Vec2i) Recti { + return Recti.from_xywh(self.x, self.y, 1, 1); +} + +pub const ZERO = create(0, 0); +pub const ONE = create(1, 1); +pub const NORTH = create(0, -1); +pub const SOUTH = create(0, 1); +pub const EAST = create(1, 0); +pub const WEST = create(-1, 0); diff --git a/src/scenes.zig b/src/scenes.zig index c65b3c2..5cb5457 100644 --- a/src/scenes.zig +++ b/src/scenes.zig @@ -1,13 +1,16 @@ const Scene = @import("Scene.zig"); const Terrain = @import("Terrain.zig"); - +const Pawn = @import("Pawn.zig"); +const Camera = @import("Camera.zig"); pub fn game() !Scene { var scene = Scene.create(); // first try is for allocating more memory in entities // second try is for allocating terrain on the heap... - try scene.add(try Terrain.create()); + try scene.add(try Camera.create()); + try scene.add(try Terrain.create(123)); + try scene.add(try Pawn.random()); return scene; } diff --git a/src/shaders.zig b/src/shaders.zig index 3b016b9..e36211d 100644 --- a/src/shaders.zig +++ b/src/shaders.zig @@ -55,7 +55,6 @@ pub const POSITION_ATTRIBUTE_ID = 0; pub const COLOR_ATTRIBUTE_ID = 1; pub const TEXCOORD_ATTRIBUTE_ID = 2; - var flat: Shader = undefined; var active_shader: *const Shader = undefined; @@ -67,6 +66,7 @@ pub fn load() !void { } pub fn set_projection_matrix(matrix: *const Matrix4f) void { + // std.debug.print("[shaders:set_projection_matrix] {}: {}\n", .{ active_shader, matrix }); active_shader.set_projection_matrix(matrix); } @@ -78,7 +78,6 @@ const Shader = struct { const Error = error { CompilationFailed }; - // cache: HashMap<*const u8, i32> = undefined, fn enable(self: *const Shader) void { c.glUseProgram(self.program_handle); @@ -88,18 +87,18 @@ const Shader = struct { } fn compile(vert_shader_text: []const u8, frag_shader_text: []const u8) !Shader { - const vertex_shader: c.GLuint= c.glCreateShader(c.GL_VERTEX_SHADER); + const vertex_shader: c.GLuint = c.glCreateShader(c.GL_VERTEX_SHADER); { c.glShaderSource(vertex_shader, 1, @ptrCast(&vert_shader_text), null); c.glCompileShader(vertex_shader); var compile_status: c.GLint = undefined; c.glGetShaderiv(vertex_shader, c.GL_COMPILE_STATUS, &compile_status); if (compile_status != c.GL_TRUE) { - std.log.err("Vertex shader compilation failed!", .{}); + std.log.err("[shaders:compile] Vertex shader compilation failed!", .{}); var error_text: [1000]u8 = undefined; var len: i32 = undefined; c.glGetShaderInfoLog(vertex_shader, 1000, &len, &error_text); - std.debug.print("{s}\n", .{ error_text[0..@intCast(len)] }); + std.log.err("{s}\n", .{ error_text[0..@intCast(len)] }); return Shader.Error.CompilationFailed; } } @@ -111,7 +110,7 @@ const Shader = struct { var compile_status: c.GLint = undefined; c.glGetShaderiv(fragment_shader, c.GL_COMPILE_STATUS, &compile_status); if (compile_status != c.GL_TRUE) { - std.log.err("Fragment shader compilation failed!", .{}); + std.log.err("[shaders:compile] Fragment shader compilation failed!", .{}); var error_text: [1000]u8 = undefined; var len: i32 = undefined; c.glGetShaderInfoLog(fragment_shader, 1000, &len, &error_text); diff --git a/textures.png b/textures.png index 14ad769..0f2565f 100644 Binary files a/textures.png and b/textures.png differ