From 61e5edb8cf02b1ea87ba0455659fedc4747dd74e Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sun, 14 Dec 2025 14:56:30 +0200 Subject: [PATCH] add tilemap --- src/assets/kenney-micro-roguelike/License.txt | 23 + .../colored_tilemap_packed.png | Bin 0 -> 2699 bytes src/game.zig | 6 +- src/graphics.zig | 516 +++++------------- src/math.zig | 14 + 5 files changed, 166 insertions(+), 393 deletions(-) create mode 100644 src/assets/kenney-micro-roguelike/License.txt create mode 100644 src/assets/kenney-micro-roguelike/colored_tilemap_packed.png diff --git a/src/assets/kenney-micro-roguelike/License.txt b/src/assets/kenney-micro-roguelike/License.txt new file mode 100644 index 0000000..34d31ea --- /dev/null +++ b/src/assets/kenney-micro-roguelike/License.txt @@ -0,0 +1,23 @@ + + + Micro Roguelike (1.4) + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 01-11-2021 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/src/assets/kenney-micro-roguelike/colored_tilemap_packed.png b/src/assets/kenney-micro-roguelike/colored_tilemap_packed.png new file mode 100644 index 0000000000000000000000000000000000000000..981332f0630c224e476d0341fd3a01f34a9ee107 GIT binary patch literal 2699 zcmV;63Uu{}P)yR!d#phw`RghNtad zU~r5-UAkdbetwT+cId*w(IO)woSTmH00001bW%=J06^y0W&i*Q?MXyIRA}CnT8VNVt^xz;nHA_zu1Q zB+P(44P5~Hf;Th&8-QH(RB%`tGv#^C3CS;&Pbp`nukYi4I8G%1h^1>hb7?r4tPLt8 zd>1mYj2QEr*L5Zy#yF#jL`2y|pFAOe&+ zeFnl|=CKVZ1S^OLVC4sOt;Z0OS^czMt$*8C1n~?DL1&Or!X$g^B|z>e@Q%K+=A&oh=6F zCDmGinc7>2>|pR%zxHDjDY7y7?}7EnOv4pm_QNbD@u**4jGT#IUXkPUO3AkmXWv>Y7(G7Iv*02q{zI3P0M z_9F}cRffC@0MW84ED*coHGpvnQd2_h?*Lqwp=g;Ke-$1P|5+_m2TZuD8pdKPq4^JT zZA~9$_$45)xS;E6+?(t>Eo$vO`yt=ccMO2rPqFpay@t?l&B6O6L{+MV7cxEx7<5S) zf(uqI8@Mbiqep3(Pd5jBe8fZXp$&jUss(N>4xl&=HFW7nAM9|fc*c|tD`KV0U(oPg*SXM;PmWh0DvtKanwcx#lJ9$*BZx3v)g0~Zq+O+`8EQuVBrKno*MwXQ@RZR zKJ@^go;mQc%YOzS$_+K5@eY8Ti}*%LiP!_(aUfj-7&UnWU;y!~A_yH203cYvd@iOj z!GRPUFaU!R-UE=CGx?%0QY4zmygmCp!~+&P69DHx3XMvoBn#weG_!)^sJ9&Gk|$5c z{UqzX#Sy6nfQ|!dhWfdsFn zOM24*Xa($*fp}!}>#QI>-r;Hl;Ydn3M-+n~RM)s00LX31*8mz2?z~^sq|mS_i{1Om z;`B*4_W%+nWpqKl;yVXgIjn4SN2AEXNg}?Dav-`%{89tJY9-78?E3luevM-Q&{zQ2 zJE$7|$5No&%X0 zGc-3OIU@r&B>{sUu)JdpJHRVU|M-ZOQXTXESTm9w@k-v`?>(OZ)C>ATSreqyltJ%3 zNliaLKK$eWlC~_mV)~nv{+ODbs)i+!PX)5dr9V%<)AXBq4ovtbWjn1yr~)cfq`tQR zzE}Y#+d`jf6{`SPDLcgLJpKMs`K$i&ztMLQ7=TFBk^KAJir@|a-^9!t00K!?B?B-i z>yaPTN-(JvfqMd^`~kqxGvHtbFg@w?TC|QwRS`@+_vuOrbEkdp-L&w+4?yGu;s;E& z($6t^s;v?#>0bkI=0HO~_W&9{P|t3_(C6GcfZY26WWHHI8!u?!k_Yp15qLqu9L1+F z;G&Q*_Ob5EFp4^h|(g0?4`!NMvu#j)?9`Tsd$9VB3~$+hV3%Qj&i88vsHB zY_0*|-7(=S08b`?07#GY>vsT5%_$q=ZI>BQ7(v?z=VtqM62MUvhau$KlpJj1U z!nRDe-6$HgY4+GxP0-XDE|icjObBN)YyzRrZ74iOE}@+ab8UvZ-Xnky6hs0M&$Bo4 zEV(H;KwD+2z26X83EjqXg-Q;b&?#98dyBlZ#P{FAKKnN7TW&k*{Bm}lb5FE+F zp){r3v~lvAblLB$j+TJl2=4%F9RO}p??N>&B~4zT0jt6vTveoRW!i4s2cLtIPxZ7G zT#29o;5v{vRDs0er!s*?ya7O~Z`?3RkY%^W&^e<0P6_>9dS4}rE>%)b8Ua`YD1Hz><8Ek%ZJv; zy6-PqzaNnI_t^}aI3RT3^Y4i2X``WBl``ASN2{lvv~VX8a+j= literal 0 HcmV?d00001 diff --git a/src/game.zig b/src/game.zig index 46eec30..4ffa4c9 100644 --- a/src/game.zig +++ b/src/game.zig @@ -23,7 +23,7 @@ pub const Input = struct { move_right: Window.KeyState, }; -const tile_size = Vec2.init(10, 10); +const tile_size = Vec2.init(8, 8); canvas_size: Vec2, entities: Entity.List, @@ -37,7 +37,7 @@ last_right_repeat_at: ?f64 = null, pub fn init(gpa: Allocator) !Game { var game = Game{ - .canvas_size = .init(320, 180), + .canvas_size = (Vec2.init(20, 15)).multiply(tile_size), .entities = .empty, .timers = .empty, .last_move = .init(0, 0) @@ -129,7 +129,7 @@ pub fn tick(self: *Game, input: Input) !void { // entity.position = entity.position.add(velocity.multiplyScalar(input.dt)); entity.position = entity.position.add(move.multiply(tile_size)); - Gfx.drawRectangle(entity.position, .init(10, 10), rgb(255, 0, 0)); + Gfx.drawTileById(.player, entity.position, tile_size, rgb(255, 255, 255)); } } } diff --git a/src/graphics.zig b/src/graphics.zig index 036f6f5..fa89429 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -451,8 +451,11 @@ pub const Font = struct { }; pub const ImageId = enum { - // package, - // trash + tilemap +}; + +pub const TileId = enum { + player }; pub const Borders = struct { @@ -659,7 +662,13 @@ var font_id_map: std.EnumArray(FontVariant, c_int) = .initFill(c.FONS_INVALID); 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); const Options = struct { allocator: std.mem.Allocator, @@ -679,38 +688,47 @@ fn loadEmbededFont(fs: ?*c.FONScontext, name: [*c]const u8, comptime path: []co return font_id; } -fn makeImageFromMemory(image_datas: []const []const u8) !sg.Image { - var stbi_images_buffer: [16][*c]u8 = undefined; - var stbi_images: std.ArrayListUnmanaged([*c]u8) = .initBuffer(&stbi_images_buffer); - defer { - for (stbi_images.items) |stbi_image| { - c.stbi_image_free(stbi_image); +const ImageData = struct { + rgba8_pixels: [*c]u8, + width: u32, + height: u32, + + 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; - for (image_datas) |image_data| { - var width: c_int = undefined; - var height: c_int = undefined; - const pixels = c.stbi_load_from_memory(image_data.ptr, @intCast(image_data.len), &width, &height, null, 4); - if (pixels == null) { - return error.InvalidPng; - } - + for (image_datas) |mipmap_image| { if (image_height == -1) { - image_width = width; - image_height = height; + image_width = @intCast(mipmap_image.width); + image_height = @intCast(mipmap_image.height); } - try stbi_images.appendBounded(pixels); try mip_levels.appendBounded(.{ - .ptr = pixels, - .size = @intCast(width * height * 4) + .ptr = mipmap_image.rgba8_pixels, + .size = mipmap_image.width * mipmap_image.height * 4 }); } @@ -735,6 +753,31 @@ fn makeImageFromMemory(image_datas: []const []const u8) !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(); + } + } + + 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); +} + +fn tileCoordToQuad(coord: Vec2) Rect { + _ = coord; // autofix +} + pub fn init(options: Options) !void { dpi_scale = sapp.dpiScale(); @@ -792,32 +835,30 @@ pub fn init(options: Options) !void { .bold = loadEmbededFont(g_fons_context, "bold", "./assets/roboto-font/Roboto-Bold.ttf"), }); - // TODO: Generate mipmap from SVG. - // image_map = .init(.{ - // .package = try makeImageFromMemory(&.{ - // // https://www.iconfinder.com/icons/9026684/package_thin_icon - // @embedFile("../assets/icons/package-512x512.png"), - // @embedFile("../assets/icons/package-256x256.png"), - // @embedFile("../assets/icons/package-128x128.png"), - // @embedFile("../assets/icons/package-64x64.png"), - // @embedFile("../assets/icons/package-32x32.png"), - // }), - // .trash = try makeImageFromMemory(&.{ - // // TODO: Provide attribution for image - // // https://www.iconfinder.com/icons/9027022/trash_thin_icon - // @embedFile("../assets/icons/trash-32x32.png"), - // }) - // }); + const tilemap = try ImageData.load(@embedFile("./assets/kenney-micro-roguelike/colored_tilemap_packed.png")); + defer tilemap.deinit(); - // 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); - // } + image_map = .init(.{ + .tilemap = try makeImageWithMipMaps(&.{ + tilemap + }) + }); + + tilemap_size = Vec2.init(@floatFromInt(tilemap.width), @floatFromInt(tilemap.height)); + + tile_coords = .init(.{ + .player = .init(4, 0) + }); + + 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, @@ -825,6 +866,13 @@ pub fn init(options: Options) !void { .mipmap_filter = .LINEAR, .label = "linear-sampler", }); + + nearest_sampler = sg.makeSampler(.{ + .min_filter = .NEAREST, + .mag_filter = .NEAREST, + .mipmap_filter = .NEAREST, + .label = "nearest-sampler", + }); } pub fn deinit() void { @@ -883,16 +931,17 @@ pub fn drawRectangle(pos: Vec2, size: Vec2, color: Vec4) void { ); } -pub fn drawImage(image_id: ImageId, pos: Vec2, size: Vec2, tint: Vec4) void { +pub fn drawTile(tile_coord: Vec2, pos: Vec2, size: Vec2, tint: Vec4) void { sgl.enableTexture(); defer sgl.disableTexture(); - sgl.texture( - image_view_map.get(image_id), - linear_sampler + image_view_map.get(.tilemap), + nearest_sampler ); + const 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); @@ -901,352 +950,39 @@ pub fn drawImage(image_id: ImageId, pos: Vec2, size: Vec2, tint: Vec4) void { sgl.beginQuads(); defer sgl.end(); - v2fT2Color(top_left.x, top_left.y, 0, 0, tint); - v2fT2Color(top_right.x, top_right.y, 1, 0, tint); - v2fT2Color(bottom_right.x, bottom_right.y, 1, 1, tint); - v2fT2Color(bottom_left.x, bottom_left.y, 0, 1, tint); -} - -fn getCircleSegmentCount(radius: f32, from_angle: f32, to_angle: f32) usize { - const pi2 = 2 * std.math.pi; - - const C = pi2 * radius; - const C_segment = C * (@abs(to_angle - from_angle) / pi2); - - return @intFromFloat(C_segment / circle_quality); -} - -fn drawCorner(pos: Vec2, radius: f32, color: Vec4, from_angle: f32, to_angle: f32) void { - sgl.beginTriangles(); - defer sgl.end(); - - const detail = getCircleSegmentCount(radius, from_angle, to_angle); - - // TODO: Use precomputed angles - for (0..detail) |i| { - const angle = std.math.lerp(from_angle, to_angle, @as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(detail))); - const angle1 = std.math.lerp(from_angle, to_angle, @as(f32, @floatFromInt(i + 1)) / @as(f32, @floatFromInt(detail))); - const x = @cos(angle); - const y = @sin(angle); - const x1 = @cos(angle1); - const y1 = @sin(angle1); - - v2fColor(pos.x , pos.y , color); - v2fColor(pos.x + x * radius, pos.y + y * radius, color); - v2fColor(pos.x + x1 * radius, pos.y + y1 * radius, color); - } -} - -fn drawOutlineCornerSegment( - pos: Vec2, - color: Vec4, - outer_radius: f32, inner_radius: f32, - from_angle: f32, to_angle: f32 -) void { - const zone = tracy.initZone(@src(), .{ }); - defer zone.deinit(); - - var detail = getCircleSegmentCount(outer_radius, from_angle, to_angle); - detail = @max(detail, 1); - - // TODO: Use precomputed angles - for (0..detail) |i| { - const angle = std.math.lerp(from_angle, to_angle, @as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(detail))); - const angle1 = std.math.lerp(from_angle, to_angle, @as(f32, @floatFromInt(i + 1)) / @as(f32, @floatFromInt(detail))); - - const circle_point0 = Vec2.init(@cos(angle), @sin(angle)); - const circle_point1 = Vec2.init(@cos(angle1), @sin(angle1)); - - drawQuad( - .{ - pos.add(circle_point1.multiplyScalar(outer_radius)), - pos.add(circle_point0.multiplyScalar(outer_radius)), - pos.add(circle_point0.multiplyScalar(@max(0, inner_radius))), - pos.add(circle_point1.multiplyScalar(@max(0, inner_radius))), - }, - color - ); - } -} - -pub fn drawRoundedRectangle(pos: Vec2, size: Vec2, color: Vec4, corners: Corners) void { - const zone = tracy.initZone(@src(), .{ }); - defer zone.deinit(); - - const left = pos.x; - const right = pos.x + size.x; - const top = pos.y; - const bottom = pos.y + size.y; - - const tr = corners.top_right; - const tl = corners.top_left; - const bl = corners.bottom_left; - const br = corners.bottom_right; - - { - sgl.beginQuads(); - defer sgl.end(); - - vertexesQuad( - Vec2.init(left + tl, top), - Vec2.init(right - tr, top), - Vec2.init(right - tr, top + tr), - Vec2.init(left + tl, top + tl), - color - ); - - vertexesQuad( - Vec2.init(right - tr, top + tr), - Vec2.init(right, top + tr), - Vec2.init(right, bottom - br), - Vec2.init(right - br, bottom - br), - color - ); - - vertexesQuad( - Vec2.init(left + bl, bottom - bl), - Vec2.init(right - br, bottom - br), - Vec2.init(right - br, bottom), - Vec2.init(left + bl, bottom), - color - ); - - vertexesQuad( - Vec2.init(left, top + tl), - Vec2.init(left + tl, top + tl), - Vec2.init(left + bl, bottom - bl), - Vec2.init(left, bottom - bl), - color - ); - - vertexesQuad( - Vec2.init(left + tl, top + tl), - Vec2.init(right - tr, top + tr), - Vec2.init(right - br, bottom - br), - Vec2.init(left + bl, bottom - bl), - color - ); - } - - drawCorner( - Vec2.init(right - tr, top + tr), - tr, - color, - std.math.pi * (3.0 / 2.0), - std.math.pi * 2.0, + v2fT2Color( + top_left.x, + top_left.y, + tile_quad.left(), + tile_quad.top(), + tint ); - - drawCorner( - Vec2.init(left + tl, top + tl), - tl, - color, - std.math.pi, - std.math.pi * (3.0 / 2.0) + v2fT2Color( + top_right.x, + top_right.y, + tile_quad.right(), + tile_quad.top(), + tint ); - - drawCorner( - Vec2.init(left + bl, bottom - bl), - bl, - color, - std.math.pi, - std.math.pi * (1.0 / 2.0), + v2fT2Color( + bottom_right.x, + bottom_right.y, + tile_quad.right(), + tile_quad.bottom(), + tint ); - - drawCorner( - Vec2.init(right - br, bottom - br), - br, - color, - 0, - std.math.pi * (1.0 / 2.0) + v2fT2Color( + bottom_left.x, + bottom_left.y, + tile_quad.left(), + tile_quad.bottom(), + tint ); } -const OutlineCorner = struct { - point: Vec2, - corner_radius: f32, - dir_to_center: Vec2, - - pub fn getCircleCenter(self: OutlineCorner) Vec2 { - return self.point.add(self.dir_to_center.multiplyScalar(self.corner_radius)); - } - - pub fn getEdge(self: OutlineCorner, segment: OutlineSegment, border: f32) [2]Vec2 { - const inward_sign = switch (segment.axis) { - .X => self.dir_to_center.x, - .Y => self.dir_to_center.y, - }; - - var bottom_left = self.point; - if (segment.axis == .X) { - bottom_left.x += self.corner_radius * inward_sign; - } else { - bottom_left.y += self.corner_radius * inward_sign; - } - - var top_left = bottom_left.add(segment.inward_direction.multiplyScalar(border)); - if (segment.axis == .X) { - top_left.x += @max(border - self.corner_radius, 0) * inward_sign; - } else { - top_left.y += @max(border - self.corner_radius, 0) * inward_sign; - } - - return .{ bottom_left, top_left }; - } - - fn draw(self: OutlineCorner, segment: OutlineSegment, border_size: f32, corner_angle_sign: f32, edge: [2]Vec2,) void { - if (self.corner_radius > 0) { - const center = self.getCircleCenter(); - - const inner_radius = self.corner_radius - border_size; - if (inner_radius < 0) { - drawTriangle( - .{ - edge[0], - edge[1], - center - }, - segment.color - ); - } - - drawOutlineCornerSegment( - center, - segment.color, - self.corner_radius, - inner_radius, - segment.outward_angle, - segment.outward_angle + corner_angle_sign * std.math.pi / 4.0, - ); - } - } -}; - -const OutlineSegment = struct { - // TODO: Figure out a way to remove `lower_side_angle_dir` and `upper_side_angle_dir`? - - lower_side: OutlineCorner, - lower_side_angle_dir: f32, - upper_side: OutlineCorner, - upper_side_angle_dir: f32, - - outward_angle: f32, - color: Vec4, - axis: enum { X, Y }, - inward_direction: Vec2, - - pub fn getLowerEdge(self: OutlineSegment, border: f32) [2]Vec2 { - return self.lower_side.getEdge(self, border); - } - - pub fn getUpperEdge(self: OutlineSegment, border: f32) [2]Vec2 { - return self.upper_side.getEdge(self, border); - } - - fn draw(self: OutlineSegment, border: f32) void { - if (self.color.w != 0) { - const lower_edge = self.getLowerEdge(border); - const upper_edge = self.getUpperEdge(border); - - drawQuad( - .{ - lower_edge[0], - lower_edge[1], - - upper_edge[1], - upper_edge[0], - }, - self.color - ); - - self.lower_side.draw(self, border, self.lower_side_angle_dir, lower_edge); - self.upper_side.draw(self, border, self.upper_side_angle_dir, upper_edge); - } - } -}; - -pub fn drawRectangleOutlineRounded(pos: Vec2, size: Vec2, corners: Corners, borders: Borders) void { - const zone = tracy.initZone(@src(), .{ }); - defer zone.deinit(); - - if (borders.size == 0) { - return; - } - - const left = pos.x; - const right = pos.x+size.x; - const top = pos.y; - const bottom = pos.y+size.y; - - const bottom_left_corner = OutlineCorner{ - .point = Vec2.init(left, bottom), - .corner_radius = corners.bottom_left, - .dir_to_center = Vec2.init(1, -1), - }; - - const bottom_right_corner = OutlineCorner{ - .point = Vec2.init(right, bottom), - .corner_radius = corners.bottom_right, - .dir_to_center = Vec2.init(-1, -1), - }; - - const top_right_corner = OutlineCorner{ - .point = Vec2.init(right, top), - .corner_radius = corners.top_right, - .dir_to_center = Vec2.init(-1, 1), - }; - - const top_left_corner = OutlineCorner{ - .point = Vec2.init(left, top), - .corner_radius = corners.top_left, - .dir_to_center = Vec2.init(1, 1), - }; - - const segments = .{ - OutlineSegment{ - .lower_side = bottom_left_corner, - .lower_side_angle_dir = 1, - .upper_side = bottom_right_corner, - .upper_side_angle_dir = -1, - .outward_angle = std.math.pi * 1.0 / 2.0, - .color = borders.bottom, - .axis = .X, - .inward_direction = Vec2.init(0, -1) - }, - OutlineSegment{ - .lower_side = top_left_corner, - .lower_side_angle_dir = -1, - .upper_side = top_right_corner, - .upper_side_angle_dir = 1, - .outward_angle = std.math.pi * 3.0 / 2.0, - .color = borders.top, - .axis = .X, - .inward_direction = Vec2.init(0, 1) - }, - OutlineSegment{ - .lower_side = top_left_corner, - .lower_side_angle_dir = 1, - .upper_side = bottom_left_corner, - .upper_side_angle_dir = -1, - .outward_angle = std.math.pi, - .color = borders.left, - .axis = .Y, - .inward_direction = Vec2.init(1, 0) - }, - OutlineSegment{ - .lower_side = top_right_corner, - .lower_side_angle_dir = -1, - .upper_side = bottom_right_corner, - .upper_side_angle_dir = 1, - .outward_angle = 0, - .color = borders.right, - .axis = .Y, - .inward_direction = Vec2.init(-1, 0) - } - }; - - inline for (segments) |segment| { - segment.draw(borders.size); - } +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 drawRectanglOutline(pos: Vec2, size: Vec2, color: Vec4, width: f32) void { diff --git a/src/math.zig b/src/math.zig index 0f35670..f620d92 100644 --- a/src/math.zig +++ b/src/math.zig @@ -351,6 +351,20 @@ pub const Rect = struct { return self.pos.y + self.size.y; } + pub fn multiply(self: Rect, xy: Vec2) Rect { + return Rect{ + .pos = self.pos.multiply(xy), + .size = self.size.multiply(xy), + }; + } + + pub fn divide(self: Rect, xy: Vec2) Rect { + return Rect{ + .pos = self.pos.divide(xy), + .size = self.size.divide(xy), + }; + } + pub fn isInside(self: Rect, pos: Vec2) bool { const x_overlap = self.pos.x <= pos.x and pos.x < self.pos.x + self.size.x; const y_overlap = self.pos.y <= pos.y and pos.y < self.pos.y + self.size.y;