server-configuration/src/assets.zig

395 lines
12 KiB
Zig

pub const Suit = enum(u2) {
clubs = 0b00,
spades = 0b01,
hearts = 0b10,
diamonds = 0b11,
pub fn color(this: @This()) u1 {
return @intCast((@intFromEnum(this) & 0b10) >> 1);
}
};
pub const Card = packed struct(u6) {
suit: Suit,
rank: Rank,
pub const Rank = u4;
};
/// A texture with a regular grid of sprites
pub const TileSheet = struct {
texture: seizer.Texture,
tile_offset: [2]u32,
tile_size: [2]u32,
tile_stride: [2]u32,
pub const InitOptions = struct {
allocator: std.mem.Allocator,
image_file_contents: []const u8,
tile_offset: [2]u32,
tile_size: [2]u32,
tile_stride: [2]u32,
texture_options: seizer.Texture.InitFromFileOptions = .{},
};
pub fn init(options: InitOptions) !@This() {
const texture = try seizer.Texture.initFromFileContents(
options.allocator,
options.image_file_contents,
options.texture_options,
);
return @This(){
.texture = texture,
.tile_offset = options.tile_offset,
.tile_size = options.tile_size,
.tile_stride = options.tile_stride,
};
}
pub fn deinit(this: *@This()) void {
this.texture.deinit();
}
pub fn uvCoordinatesFromTileId(this: @This(), tile_id: u32) seizer.geometry.AABB(f32) {
const texture_sizef = [2]f32{
@floatFromInt(this.texture.size[0]),
@floatFromInt(this.texture.size[1]),
};
const tile_offsetf = [2]f32{
@floatFromInt(this.tile_offset[0]),
@floatFromInt(this.tile_offset[1]),
};
const tile_stridef = [2]f32{
@floatFromInt(this.tile_stride[0]),
@floatFromInt(this.tile_stride[1]),
};
const tile_sizef = [2]f32{
@floatFromInt(this.tile_size[0]),
@floatFromInt(this.tile_size[1]),
};
// add a half to round up
const size_in_tiles = [2]u32{
(@as(u32, @intCast(this.texture.size[0])) + (this.tile_stride[0] / 2)) / this.tile_stride[0],
(@as(u32, @intCast(this.texture.size[1])) + (this.tile_stride[1] / 2)) / this.tile_stride[1],
};
const pos_in_tiles = [2]u32{
tile_id % size_in_tiles[0],
tile_id / size_in_tiles[0],
};
const pos_in_tilesf = [2]f32{
@floatFromInt(pos_in_tiles[0]),
@floatFromInt(pos_in_tiles[1]),
};
const pixel_pos = [2]f32{
pos_in_tilesf[0] * tile_stridef[0] + tile_offsetf[0],
pos_in_tilesf[1] * tile_stridef[1] + tile_offsetf[1],
};
return seizer.geometry.AABB(f32){
.min = .{
pixel_pos[0] / texture_sizef[0],
pixel_pos[1] / texture_sizef[1],
},
.max = .{
(pixel_pos[0] + tile_sizef[0]) / texture_sizef[0],
(pixel_pos[1] + tile_sizef[1]) / texture_sizef[1],
},
};
}
pub fn renderTile(this: @This(), canvas: *seizer.Canvas, tile_id: u32, pos: [2]f32, options: struct {
size: ?[2]f32 = null,
color: [4]u8 = [4]u8{ 0xFF, 0xFF, 0xFF, 0xFF },
}) void {
const uv = this.uvCoordinatesFromTileId(tile_id);
canvas.rect(pos, options.size orelse [2]f32{ @floatFromInt(this.tile_size[0]), @floatFromInt(this.tile_size[1]) }, .{
.texture = this.texture.glTexture,
.uv = uv,
.color = options.color,
});
}
};
test "tilesheet math is exact" {
const tilesheet = TileSheet{
.texture = .{
.glTexture = undefined,
.size = .{ 494, 329 },
},
.tile_offset = .{ 6, 2 },
.tile_size = .{ 20, 29 },
.tile_stride = .{ 33, 33 },
};
try std.testing.expectEqualDeep(seizer.geometry.AABB(f32){
.min = .{
468.0 / 494.0,
35.0 / 329.0,
},
.max = .{
(468.0 + 20.0) / 494.0,
(35.0 + 29.0) / 329.0,
},
}, tilesheet.uvCoordinatesFromTileId(29));
}
/// A texture with a regular grid of sprites
pub const DeckSprites = struct {
tilesheet: TileSheet,
/// Return the tile index for a given card,
mapping: std.AutoHashMapUnmanaged(Card, u32),
blank: u32,
back: u32,
margin: u32,
hand_offset: [2]u32,
pub fn deinit(this: *@This(), gpa: std.mem.Allocator) void {
this.tilesheet.deinit();
this.mapping.deinit(gpa);
}
pub fn getTileForCard(this: @This(), card: Card) u32 {
return this.mapping.get(card) orelse 0;
}
};
pub fn loadSmallCards(gpa: std.mem.Allocator) !DeckSprites {
var tilesheet = try TileSheet.init(.{
.allocator = gpa,
.image_file_contents = @embedFile("./cardsSmall_tilemap.png"),
.tile_offset = .{ 0, 0 },
.tile_size = .{ 16, 16 },
.tile_stride = .{ 17, 17 },
.texture_options = .{
.min_filter = .nearest,
.mag_filter = .nearest,
},
});
errdefer tilesheet.deinit();
var mapping = std.AutoHashMap(Card, u32).init(gpa);
errdefer mapping.deinit();
try mapping.ensureTotalCapacity(52);
const hearts_start_index: u32 = 0;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .hearts, .rank = @intCast(rank + 1) },
hearts_start_index + @as(u32, @intCast(rank)),
);
}
const diamonds_start_index: u32 = 14;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .diamonds, .rank = @intCast(rank + 1) },
diamonds_start_index + @as(u32, @intCast(rank)),
);
}
const clubs_start_index: u32 = 28;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .clubs, .rank = @intCast(rank + 1) },
clubs_start_index + @as(u32, @intCast(rank)),
);
}
const spades_start_index: u32 = 42;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .spades, .rank = @intCast(rank + 1) },
spades_start_index + @as(u32, @intCast(rank)),
);
}
return DeckSprites{
.tilesheet = tilesheet,
.mapping = mapping.unmanaged,
// TODO: add better graphic for blank card
.blank = 59,
.back = 59,
};
}
pub fn loadMediumCards(gpa: std.mem.Allocator) !DeckSprites {
var tilesheet = try TileSheet.init(.{
.allocator = gpa,
.image_file_contents = @embedFile("./cardsMedium_tilemap.png"),
.tile_offset = .{ 6, 2 },
.tile_size = .{ 20, 29 },
.tile_stride = .{ 33, 33 },
.texture_options = .{
.min_filter = .nearest,
.mag_filter = .nearest,
},
});
errdefer tilesheet.deinit();
var mapping = std.AutoHashMap(Card, u32).init(gpa);
errdefer mapping.deinit();
try mapping.ensureTotalCapacity(52);
const hearts_start_index: u32 = 0;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .hearts, .rank = @intCast(rank + 1) },
hearts_start_index + @as(u32, @intCast(rank)),
);
}
const diamonds_start_index: u32 = 15;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .diamonds, .rank = @intCast(rank + 1) },
diamonds_start_index + @as(u32, @intCast(rank)),
);
}
const clubs_start_index: u32 = 30;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .clubs, .rank = @intCast(rank + 1) },
clubs_start_index + @as(u32, @intCast(rank)),
);
}
const spades_start_index: u32 = 45;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .spades, .rank = @intCast(rank + 1) },
spades_start_index + @as(u32, @intCast(rank)),
);
}
return DeckSprites{
.tilesheet = tilesheet,
.mapping = mapping.unmanaged,
.blank = 14,
.back = 29,
};
}
pub fn loadLargeCards(gpa: std.mem.Allocator) !DeckSprites {
var tilesheet = try TileSheet.init(.{
.allocator = gpa,
.image_file_contents = @embedFile("./cardsLarge_tilemap.png"),
.tile_offset = .{ 11, 2 },
.tile_size = .{ 41, 60 },
.tile_stride = .{ 65, 65 },
.texture_options = .{
.min_filter = .nearest,
.mag_filter = .nearest,
},
});
errdefer tilesheet.deinit();
var mapping = std.AutoHashMap(Card, u32).init(gpa);
errdefer mapping.deinit();
try mapping.ensureTotalCapacity(52);
const hearts_start_index: u32 = 0;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .hearts, .rank = @intCast(rank + 1) },
hearts_start_index + @as(u32, @intCast(rank)),
);
}
const diamonds_start_index: u32 = 14;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .diamonds, .rank = @intCast(rank + 1) },
diamonds_start_index + @as(u32, @intCast(rank)),
);
}
const clubs_start_index: u32 = 28;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .clubs, .rank = @intCast(rank + 1) },
clubs_start_index + @as(u32, @intCast(rank)),
);
}
const spades_start_index: u32 = 42;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .spades, .rank = @intCast(rank + 1) },
spades_start_index + @as(u32, @intCast(rank)),
);
}
return DeckSprites{
.tilesheet = tilesheet,
.mapping = mapping.unmanaged,
.blank = 13,
.back = 27,
};
}
pub fn loadSolitaireCards(gpa: std.mem.Allocator) !DeckSprites {
var tilesheet = try TileSheet.init(.{
.allocator = gpa,
.image_file_contents = @embedFile("./solitaire-cards.png"),
.tile_offset = .{ 0, 0 },
.tile_size = .{ 32, 40 },
.tile_stride = .{ 32, 40 },
.texture_options = .{
.min_filter = .nearest,
.mag_filter = .nearest,
},
});
errdefer tilesheet.deinit();
var mapping = std.AutoHashMap(Card, u32).init(gpa);
errdefer mapping.deinit();
try mapping.ensureTotalCapacity(52);
const hearts_start_index: u32 = 0;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .hearts, .rank = @intCast(rank + 1) },
hearts_start_index + @as(u32, @intCast(rank)),
);
}
const diamonds_start_index: u32 = 14;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .diamonds, .rank = @intCast(rank + 1) },
diamonds_start_index + @as(u32, @intCast(rank)),
);
}
const clubs_start_index: u32 = 28;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .clubs, .rank = @intCast(rank + 1) },
clubs_start_index + @as(u32, @intCast(rank)),
);
}
const spades_start_index: u32 = 42;
for (0..13) |rank| {
mapping.putAssumeCapacityNoClobber(
Card{ .suit = .spades, .rank = @intCast(rank + 1) },
spades_start_index + @as(u32, @intCast(rank)),
);
}
return DeckSprites{
.tilesheet = tilesheet,
.mapping = mapping.unmanaged,
.blank = 13,
.back = 27,
.margin = 4,
.hand_offset = .{ 10, 12 },
};
}
const seizer = @import("seizer");
const gl = seizer.gl;
const std = @import("std");