267 lines
7.9 KiB
Zig
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();
|
|
}
|