diff --git a/build.zig b/build.zig index 8b4e186..3eacfae 100644 --- a/build.zig +++ b/build.zig @@ -29,9 +29,6 @@ pub fn build(b: *std.Build) !void { exe_mod.linkLibrary(tracy_dependency.artifact("tracy")); exe_mod.addImport("tracy", tracy_dependency.module("tracy")); - const stb_dependency = b.dependency("stb", .{}); - exe_mod.addIncludePath(stb_dependency.path(".")); - const sokol_c_dependency = b.dependency("sokol_c", .{}); exe_mod.addIncludePath(sokol_c_dependency.path("util")); @@ -41,6 +38,9 @@ pub fn build(b: *std.Build) !void { const tiled_dependency = b.dependency("tiled", .{}); exe_mod.addImport("tiled", tiled_dependency.module("tiled")); + const stb_image_dependency = b.dependency("stb_image", .{}); + exe_mod.addImport("stb_image", stb_image_dependency.module("stb_image")); + const sokol_dependency = b.dependency("sokol", .{ .target = target, .optimize = optimize, @@ -66,11 +66,6 @@ pub fn build(b: *std.Build) !void { .flags = cflags.items }); - exe_mod.addCSourceFile(.{ - .file = b.path("src/libs/stb_image.c"), - .flags = &.{} - }); - if (has_imgui) { if (b.lazyDependency("cimgui", .{ .target = target, diff --git a/build.zig.zon b/build.zig.zon index 0f6b31e..3106708 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -17,10 +17,6 @@ .url = "git+https://github.com/sagehane/zig-tracy.git#80933723efe9bf840fe749b0bfc0d610f1db1669", .hash = "zig_tracy-0.0.5-aOIqsX1tAACKaRRB-sraMLuNiMASXi_y-4FtRuw4cTpx", }, - .stb = .{ - .url = "git+https://github.com/nothings/stb.git#f1c79c02822848a9bed4315b12c8c8f3761e1296", - .hash = "N-V-__8AABQ7TgCnPlp8MP4YA8znrjd6E-ZjpF1rvrS8J_2I", - }, .sokol_c = .{ .url = "git+https://github.com/floooh/sokol.git#c66a1f04e6495d635c5e913335ab2308281e0492", .hash = "N-V-__8AAC3eYABB1DVLb4dkcEzq_xVeEZZugVfQ6DoNQBDN", @@ -31,6 +27,9 @@ }, .tiled = .{ .path = "./libs/tiled" + }, + .stb_image = .{ + .path = "./libs/stb_image" } }, .paths = .{ diff --git a/libs/stb_image/build.zig b/libs/stb_image/build.zig new file mode 100644 index 0000000..76e2e37 --- /dev/null +++ b/libs/stb_image/build.zig @@ -0,0 +1,37 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("stb_image", .{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/root.zig"), + }); + + const stb_dependency = b.dependency("stb", .{}); + mod.addIncludePath(stb_dependency.path(".")); + + mod.addCSourceFile(.{ + .file = b.path("src/stb_image_impl.c"), + .flags = &.{} + }); + + const lib = b.addLibrary(.{ + .name = "stb_image", + .root_module = mod + }); + b.installArtifact(lib); + + { + const tests = b.addTest(.{ + .root_module = mod + }); + + const run_tests = b.addRunArtifact(tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_tests.step); + } +} diff --git a/libs/stb_image/build.zig.zon b/libs/stb_image/build.zig.zon new file mode 100644 index 0000000..867deaa --- /dev/null +++ b/libs/stb_image/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = .stb_image, + .version = "0.0.0", + .fingerprint = 0xe5d3607840482046, // Changing this has security and trust implications. + .minimum_zig_version = "0.15.2", + .dependencies = .{ + .stb = .{ + .url = "git+https://github.com/nothings/stb.git#f1c79c02822848a9bed4315b12c8c8f3761e1296", + .hash = "N-V-__8AABQ7TgCnPlp8MP4YA8znrjd6E-ZjpF1rvrS8J_2I", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/libs/stb_image/src/root.zig b/libs/stb_image/src/root.zig new file mode 100644 index 0000000..aa7f7af --- /dev/null +++ b/libs/stb_image/src/root.zig @@ -0,0 +1,30 @@ +const std = @import("std"); + +const c = @cImport({ + @cInclude("stb_image.h"); +}); + +const STBImage = @This(); + +rgba8_pixels: [*c]u8, +width: u32, +height: u32, + +pub fn load(png_data: []const u8) !STBImage { + var width: c_int = undefined; + var height: c_int = undefined; + const pixels = c.stbi_load_from_memory(png_data.ptr, @intCast(png_data.len), &width, &height, null, 4); + if (pixels == null) { + return error.InvalidPng; + } + + return STBImage{ + .rgba8_pixels = pixels, + .width = @intCast(width), + .height = @intCast(height) + }; +} + +pub fn deinit(self: *const STBImage) void { + c.stbi_image_free(self.rgba8_pixels); +} diff --git a/src/libs/stb_image.c b/libs/stb_image/src/stb_image_impl.c similarity index 100% rename from src/libs/stb_image.c rename to libs/stb_image/src/stb_image_impl.c diff --git a/src/assets.zig b/src/assets.zig index 274a6cb..0170bae 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -1,6 +1,10 @@ const std = @import("std"); -const Font = @import("./engine/font.zig"); -const Gfx = @import("./engine/graphics.zig"); +const Engine = @import("./engine/root.zig"); +const Gfx = Engine.Graphics; +const Font = Gfx.Font; + +const Math = @import("./engine/math.zig"); +const Vec2 = Math.Vec2; const Assets = @This(); @@ -12,17 +16,48 @@ pub const FontVariant = enum { const Map = std.EnumArray(FontVariant, Font.Id); }; -font_map: FontVariant.Map = .initFill(.invalid), +pub const ImageId = enum { + tilemap +}; + +pub const TileId = enum { + player, + open_door, + locked_door +}; + +font_map: FontVariant.Map, +tile_coords: std.EnumArray(TileId, Vec2), + +tilemap: Gfx.Image, +tile_size: Vec2, +tilemap_size: Vec2, +tilemap_view: Gfx.View, pub fn init() !Assets { - const font_map: FontVariant.Map = .init(.{ - .regular = try Gfx.fonts.add("regular", @embedFile("./assets/roboto-font/Roboto-Regular.ttf")), - .italic = try Gfx.fonts.add("italic", @embedFile("./assets/roboto-font/Roboto-Italic.ttf")), - .bold = try Gfx.fonts.add("bold", @embedFile("./assets/roboto-font/Roboto-Bold.ttf")), + const tilemap_data = try Gfx.STBImage.load(@embedFile("./assets/kenney-micro-roguelike/colored_tilemap_packed.png")); + defer tilemap_data.deinit(); + + const tilemap = try Gfx.makeImageWithMipMaps(&.{ + tilemap_data }); + const tilemap_view = try Gfx.makeView(tilemap); return Assets{ - .font_map = font_map + .font_map = .init(.{ + .regular = try Gfx.fonts.add("regular", @embedFile("./assets/roboto-font/Roboto-Regular.ttf")), + .italic = try Gfx.fonts.add("italic", @embedFile("./assets/roboto-font/Roboto-Italic.ttf")), + .bold = try Gfx.fonts.add("bold", @embedFile("./assets/roboto-font/Roboto-Bold.ttf")), + }), + .tile_coords = .init(.{ + .player = .init(4, 0), + .locked_door = .init(7, 2), + .open_door = .init(5, 2) + }), + .tilemap = tilemap, + .tilemap_view = tilemap_view, + .tile_size = .init(8, 8), + .tilemap_size = .init(@floatFromInt(tilemap_data.width), @floatFromInt(tilemap_data.height)) }; } diff --git a/src/engine/graphics.zig b/src/engine/graphics.zig index afe8c16..ddf8aaf 100644 --- a/src/engine/graphics.zig +++ b/src/engine/graphics.zig @@ -10,12 +10,15 @@ const imgui = @import("imgui.zig"); const std = @import("std"); const assert = std.debug.assert; +pub const STBImage = @import("stb_image"); +pub const Image = sg.Image; +pub const View = sg.View; + const c = @cImport({ @cInclude("sokol/sokol_gfx.h"); @cInclude("sokol/sokol_gl.h"); @cInclude("fontstash.h"); @cInclude("sokol_fontstash.h"); - @cInclude("stb_image.h"); }); const Allocator = std.mem.Allocator; @@ -64,16 +67,6 @@ const DrawFrame = struct { } }; -pub const ImageId = enum { - tilemap -}; - -pub const TileId = enum { - player, - open_door, - locked_door -}; - pub const Borders = struct { size: f32 = 0, left: Vec4 = rgba(0, 0, 0, 0), @@ -114,16 +107,9 @@ pub var circle_quality: f32 = 6; pub var draw_frame: DrawFrame = undefined; var main_pipeline: sgl.Pipeline = .{}; -var image_map: std.EnumArray(ImageId, sg.Image) = .initFill(.{}); -var image_view_map: std.EnumArray(ImageId, sg.View) = .initFill(.{}); - var linear_sampler: sg.Sampler = .{}; var nearest_sampler: sg.Sampler = .{}; -var tile_coords: std.EnumArray(TileId, Vec2) = .initUndefined(); -const tile_size: Vec2 = .init(8, 8); -var tilemap_size: Vec2 = .init(0, 0); - pub var fonts: Font.Context = undefined; const Options = struct { @@ -135,63 +121,29 @@ inline fn structCast(T: type, value: anytype) T { return @as(*T, @ptrFromInt(@intFromPtr(&value))).*; } -const ImageData = struct { - rgba8_pixels: [*c]u8, - width: u32, - height: u32, +pub fn makeImageWithMipMaps(image_datas: []const STBImage) !sg.Image { + assert(image_datas.len > 0); - fn load(png_data: []const u8) !ImageData { - var width: c_int = undefined; - var height: c_int = undefined; - const pixels = c.stbi_load_from_memory(png_data.ptr, @intCast(png_data.len), &width, &height, null, 4); - if (pixels == null) { - return error.InvalidPng; - } - - return ImageData{ - .rgba8_pixels = pixels, - .width = @intCast(width), - .height = @intCast(height) - }; - } - - fn deinit(self: *const ImageData) void { - c.stbi_image_free(self.rgba8_pixels); - } -}; - -fn makeImageWithMipMaps(image_datas: []const ImageData) !sg.Image { - var mip_levels_buffer = [_]sg.Range{.{}} ** 16; - var mip_levels: std.ArrayListUnmanaged(sg.Range) = .initBuffer(&mip_levels_buffer); - - var image_width: c_int = -1; - var image_height: c_int = -1; + var data: sg.ImageData = .{}; + var mip_levels: std.ArrayListUnmanaged(sg.Range) = .initBuffer(&data.mip_levels); for (image_datas) |mipmap_image| { - if (image_height == -1) { - image_width = @intCast(mipmap_image.width); - image_height = @intCast(mipmap_image.height); - } - try mip_levels.appendBounded(.{ .ptr = mipmap_image.rgba8_pixels, .size = mipmap_image.width * mipmap_image.height * 4 }); } - assert(image_width > 0); - assert(image_height > 0); - // TODO: Should error be checked? const image = sg.makeImage(.{ - .width = image_width, - .height = image_height, + .width = @intCast(image_datas[0].width), + .height = @intCast(image_datas[0].height), .pixel_format = .RGBA8, .usage = .{ .immutable = true }, .num_mipmaps = @intCast(mip_levels.items.len), - .data = sg.ImageData{ .mip_levels = mip_levels_buffer }, + .data = data }); if (image.id == sg.invalid_id) { return error.InvalidImage; @@ -200,25 +152,14 @@ fn makeImageWithMipMaps(image_datas: []const ImageData) !sg.Image { return image; } -fn makeImageFromMemory(image_datas: []const []const u8) !sg.Image { - var stbi_images_buffer: [16]ImageData = undefined; - var stbi_images: std.ArrayListUnmanaged(ImageData) = .initBuffer(&stbi_images_buffer); - defer { - for (stbi_images.items) |image| { - image.deinit(); - } +pub fn makeView(image: sg.Image) !sg.View { + const image_view = sg.makeView(.{ + .texture = .{ .image = image } + }); + if (image_view.id == sg.invalid_id) { + return error.InvalidView; } - - if (image_datas.len > stbi_images.capacity) { - return error.OutOfMemory; - } - - for (image_datas) |image_data| { - const mipmap_image = try ImageData.load(image_data); - stbi_images.appendAssumeCapacity(mipmap_image); - } - - return try makeImageWithMipMaps(stbi_images.items); + return image_view; } pub fn init(options: Options) !void { @@ -253,6 +194,7 @@ pub fn init(options: Options) !void { imgui.setup(options.allocator, .{ .logger = structCast(simgui.Logger, options.logger), + .no_default_font = true, // TODO: Figure out a way to make imgui play nicely with UI // Ideally when mouse is inside a Imgui window, then the imgui cursor should be used. @@ -260,37 +202,11 @@ pub fn init(options: Options) !void { .disable_set_mouse_cursor = true }); + // TODO: Move this font loading to assets imgui.addFont(@embedFile("../assets/roboto-font/Roboto-Regular.ttf"), 16); fonts = try Font.Context.init(512); - const tilemap = try ImageData.load(@embedFile("../assets/kenney-micro-roguelike/colored_tilemap_packed.png")); - defer tilemap.deinit(); - - image_map = .init(.{ - .tilemap = try makeImageWithMipMaps(&.{ - tilemap - }) - }); - - tilemap_size = Vec2.init(@floatFromInt(tilemap.width), @floatFromInt(tilemap.height)); - - tile_coords = .init(.{ - .player = .init(4, 0), - .locked_door = .init(7, 2), - .open_door = .init(5, 2) - }); - - var image_iter = image_map.iterator(); - while (image_iter.next()) |entry| { - const image_view = sg.makeView(.{ - .texture = .{ .image = entry.value.* } - }); - assert(image_view.id != sg.invalid_id); - - image_view_map.set(entry.key, image_view); - } - linear_sampler = sg.makeSampler(.{ .min_filter = .LINEAR, .mag_filter = .LINEAR, @@ -362,22 +278,21 @@ pub fn drawRectangle(pos: Vec2, size: Vec2, color: Vec4) void { ); } -pub fn drawTile(tile_coord: Vec2, pos: Vec2, size: Vec2, tint: Vec4) void { +pub fn drawTexturedRectangle( + pos: Vec2, + size: Vec2, + color: Vec4, + view: sg.View, + view_quad: Rect, +) void { sgl.enableTexture(); defer sgl.disableTexture(); sgl.texture( - image_view_map.get(.tilemap), + view, nearest_sampler ); - var tile_quad = Rect.init( - tile_coord.x, - tile_coord.y, - 1, - 1 - ).multiply(tile_size).divide(tilemap_size); - const top_left = pos; const top_right = pos.add(.{ .x = size.x, .y = 0 }); const bottom_right = pos.add(size); @@ -386,42 +301,33 @@ pub fn drawTile(tile_coord: Vec2, pos: Vec2, size: Vec2, tint: Vec4) void { sgl.beginQuads(); defer sgl.end(); - sgl.c4f(tint.x, tint.y, tint.z, tint.w); + sgl.c4f(color.x, color.y, color.z, color.w); sgl.v2fT2f( top_left.x, top_left.y, - tile_quad.left(), - tile_quad.top(), + view_quad.left(), + view_quad.top(), ); sgl.v2fT2f( top_right.x, top_right.y, - tile_quad.right(), - tile_quad.top(), + view_quad.right(), + view_quad.top(), ); sgl.v2fT2f( bottom_right.x, bottom_right.y, - tile_quad.right(), - tile_quad.bottom(), + view_quad.right(), + view_quad.bottom(), ); sgl.v2fT2f( bottom_left.x, bottom_left.y, - tile_quad.left(), - tile_quad.bottom(), + view_quad.left(), + view_quad.bottom(), ); } -pub fn drawTileById(tile_id: TileId, pos: Vec2, size: Vec2, tint: Vec4) void { - const tile_coord = tile_coords.get(tile_id); - drawTile(tile_coord, pos, size, tint); -} - -pub fn getTileCoords(tile_id: TileId) Vec2 { - return tile_coords.get(tile_id); -} - pub fn drawRectanglOutline(pos: Vec2, size: Vec2, color: Vec4, width: f32) void { // TODO: Don't use line segments drawLine(pos, pos.add(.{ .x = size.x, .y = 0 }), color, width); diff --git a/src/entity.zig b/src/entity.zig index c85e682..adfca3f 100644 --- a/src/entity.zig +++ b/src/entity.zig @@ -4,6 +4,8 @@ const Engine = @import("./engine/root.zig"); const Vec2 = Engine.Math.Vec2; const Gfx = Engine.Graphics; +const Assets = @import("./assets.zig"); + const Entity = @This(); pub const List = GenerationalArrayList(Entity); @@ -26,5 +28,5 @@ locked: bool = false, render_tile: ?union(enum) { position: Vec2, - id: Gfx.TileId + id: Assets.TileId } = null diff --git a/src/game.zig b/src/game.zig index b569ddb..b3496a2 100644 --- a/src/game.zig +++ b/src/game.zig @@ -7,6 +7,7 @@ const imgui = Engine.imgui; const Gfx = Engine.Graphics; const Vec2 = Engine.Math.Vec2; const Vec4 = Engine.Math.Vec4; +const Rect = Engine.Math.Rect; const rgb = Engine.Math.rgb; const rgb_hex = Engine.Math.rgb_hex; @@ -341,22 +342,21 @@ pub fn getInput(self: *Game, frame: Engine.Frame) Input { } fn drawEntity(self: *Game, entity: *Entity) void { - _ = self; // autofix if (entity.render_tile) |render_tile| { var tile_coord = switch (render_tile) { - .id => |tile_id| Gfx.getTileCoords(tile_id), + .id => |tile_id| self.assets.tile_coords.get(tile_id), .position => |position| position }; if (entity.type == .door) { if (entity.locked) { - tile_coord = Gfx.getTileCoords(.locked_door); + tile_coord = self.assets.tile_coords.get(.locked_door); } else { - tile_coord = Gfx.getTileCoords(.open_door); + tile_coord = self.assets.tile_coords.get(.open_door); } } - Gfx.drawTile(tile_coord, entity.position, .init(1,1), rgb(255, 255, 255)); + self.drawTile(tile_coord, entity.position); } } @@ -573,6 +573,21 @@ pub fn tick(self: *Game, frame: Engine.Frame) !void { } } +fn drawTile(self: *Game, tile_coord: Vec2, pos: Vec2) void { + Gfx.drawTexturedRectangle( + pos, + .init(1, 1), + Gfx.white, + self.assets.tilemap_view, + Rect.init( + tile_coord.x, + tile_coord.y, + 1, + 1 + ).multiply(self.assets.tile_size).divide(self.assets.tilemap_size) + ); +} + pub fn debug(self: *Game) !void { if (!imgui.beginWindow(.{ .name = "Debug",