game-2026-01-30/libs/tiled/src/tilemap.zig
2026-01-30 23:28:18 +02:00

267 lines
7.9 KiB
Zig

const std = @import("std");
const xml = @import("./xml.zig");
const Io = std.Io;
const assert = std.debug.assert;
const Property = @import("./property.zig");
const Layer = @import("./layer.zig");
const Object = @import("./object.zig");
const Position = @import("./position.zig");
const Tileset = @import("./tileset.zig");
const GlobalTileId = @import("./global_tile_id.zig");
const Tilemap = @This();
pub const Orientation = enum {
orthogonal,
staggered,
hexagonal,
isometric,
const map: std.StaticStringMap(Orientation) = .initComptime(.{
.{ "orthogonal", .orthogonal },
.{ "staggered", .staggered },
.{ "hexagonal", .hexagonal },
.{ "isometric", .isometric }
});
};
pub const RenderOrder = enum {
right_down,
right_up,
left_down,
left_up,
const map: std.StaticStringMap(RenderOrder) = .initComptime(.{
.{ "right-down", .right_down },
.{ "right-up", .right_up },
.{ "left-down", .left_down },
.{ "left-up", .left_up },
});
};
pub const StaggerAxis = enum {
x,
y,
const map: std.StaticStringMap(StaggerAxis) = .initComptime(.{
.{ "x", .x },
.{ "y", .y }
});
};
pub const TilesetReference = struct {
source: []const u8,
first_gid: u32,
pub fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !TilesetReference {
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin("tileset");
const source = try attrs.getDupe(arena, "source") orelse return error.MissingFirstGid;
const first_gid = try attrs.getNumber(u32, "firstgid") orelse return error.MissingFirstGid;
try iter.finish("tileset");
return TilesetReference{
.source = source,
.first_gid = first_gid
};
}
};
pub const Tile = struct {
tileset: *const Tileset,
id: u32,
pub fn getProperties(self: Tile) Property.List {
return self.tileset.getTileProperties(self.id) orelse .empty;
}
};
arena: std.heap.ArenaAllocator,
version: []const u8,
tiled_version: ?[]const u8,
orientation: Orientation,
render_order: RenderOrder,
width: u32,
height: u32,
tile_width: u32,
tile_height: u32,
infinite: bool,
stagger_axis: ?StaggerAxis,
next_layer_id: u32,
next_object_id: u32,
tilesets: []TilesetReference,
layers: []Layer,
pub fn initFromBuffer(
gpa: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
xml_buffers: *xml.Lexer.Buffers,
buffer: []const u8
) !Tilemap {
var reader = Io.Reader.fixed(buffer);
return initFromReader(gpa, scratch, xml_buffers, &reader);
}
pub fn initFromReader(
gpa: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
xml_buffers: *xml.Lexer.Buffers,
reader: *Io.Reader,
) !Tilemap {
var lexer = xml.Lexer.init(reader, xml_buffers);
return initFromXml(gpa, scratch, &lexer);
}
// Map specification:
// https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#map
pub fn initFromXml(
gpa: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
lexer: *xml.Lexer,
) !Tilemap {
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
errdefer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var iter = xml.TagParser.init(lexer);
const map_attrs = try iter.begin("map");
const version = try map_attrs.getDupe(arena, "version") orelse return error.MissingVersion;
const tiled_version = try map_attrs.getDupe(arena, "tiledversion");
const orientation = try map_attrs.getEnum(Orientation, "orientation", Orientation.map) orelse return error.MissingOrientation;
const render_order = try map_attrs.getEnum(RenderOrder, "renderorder", RenderOrder.map) orelse return error.MissingRenderOrder;
// TODO: compressionlevel
const width = try map_attrs.getNumber(u32, "width") orelse return error.MissingWidth;
const height = try map_attrs.getNumber(u32, "height") orelse return error.MissingHeight;
const tile_width = try map_attrs.getNumber(u32, "tilewidth") orelse return error.MissingTileWidth;
const tile_height = try map_attrs.getNumber(u32, "tileheight") orelse return error.MissingTileHeight;
// TODO: hexidelength
const infinite_int = try map_attrs.getNumber(u32, "infinite") orelse 0;
const infinite = infinite_int != 0;
const next_layer_id = try map_attrs.getNumber(u32, "nextlayerid") orelse return error.MissingLayerId;
const next_object_id = try map_attrs.getNumber(u32, "nextobjectid") orelse return error.MissingObjectId;
// TODO: parallaxoriginx
// TODO: parallaxoriginy
// TODO: backgroundcolor
var stagger_axis: ?StaggerAxis = null;
if (orientation == .hexagonal or orientation == .staggered) {
stagger_axis = try map_attrs.getEnum(StaggerAxis, "staggeraxis", StaggerAxis.map) orelse return error.MissingRenderOrder;
// TODO: staggerindex
}
var tileset_list: std.ArrayList(TilesetReference) = .empty;
var layer_list: std.ArrayList(Layer) = .empty;
while (try iter.next()) |node| {
if (node.isTag("tileset")) {
try tileset_list.append(scratch.allocator(), try TilesetReference.initFromXml(arena, lexer));
continue;
} else if (Layer.isLayerNode(node)) {
const layer = try Layer.initFromXml(
arena,
scratch,
lexer,
);
try layer_list.append(scratch.allocator(), layer);
continue;
}
try iter.skip();
}
try iter.finish("map");
const tilesets = try arena.dupe(TilesetReference, tileset_list.items);
const layers = try arena.dupe(Layer, layer_list.items);
return Tilemap{
.arena = arena_allocator,
.version = version,
.tiled_version = tiled_version,
.orientation = orientation,
.render_order = render_order,
.width = width,
.height = height,
.tile_width = tile_width,
.tile_height = tile_height,
.infinite = infinite,
.stagger_axis = stagger_axis,
.next_object_id = next_object_id,
.next_layer_id = next_layer_id,
.tilesets = tilesets,
.layers = layers,
};
}
fn getTilesetByGid(self: *const Tilemap, gid: u32) ?TilesetReference {
var result: ?TilesetReference = null;
for (self.tilesets) |tileset| {
if (gid < tileset.first_gid) {
continue;
}
if (result != null and result.?.first_gid < tileset.first_gid) {
continue;
}
result = tileset;
}
return result;
}
pub fn getTile(self: *const Tilemap, tilesets: Tileset.List, gid: u32) ?Tile {
const tileset_ref = self.getTilesetByGid(gid & GlobalTileId.Flag.clear) orelse return null;
const tileset = tilesets.get(tileset_ref.source) orelse return null;
const id = gid - tileset_ref.first_gid;
return Tile{
.tileset = tileset,
.id = id
};
}
pub fn getTileByPosition(self: *const Tilemap, layer: *const Layer, tilesets: Tileset.List, x: usize, y: usize) ?Tile {
assert(layer.variant == .tile);
const tile_variant = layer.variant.tile;
const gid = tile_variant.get(x, y) orelse return null;
const tileset_ref = self.getTilesetByGid(gid & GlobalTileId.Flag.clear) orelse return null;
const tileset = tilesets.get(tileset_ref.source) orelse return null;
const id = gid - tileset_ref.first_gid;
return Tile{
.tileset = tileset,
.id = id
};
}
pub fn getTileBounds(self: *const Tilemap) Layer.Bounds {
var result: ?Layer.Bounds = null;
for (self.layers) |layer| {
if (layer.variant != .tile) {
continue;
}
const layer_bounds = layer.variant.tile.getBounds();
if (result == null) {
result = layer_bounds;
} else {
result = layer_bounds.combine(result.?);
}
}
return result orelse .zero;
}
pub fn deinit(self: *const Tilemap) void {
self.arena.deinit();
}