implement collisions

This commit is contained in:
Rokas Puzonas 2026-01-30 03:48:02 +02:00
parent 1220b36531
commit 880cf8ab4e
11 changed files with 841 additions and 181 deletions

View File

@ -8,6 +8,46 @@ const Position = @import("./position.zig");
const Layer = @This();
pub const Bounds = struct {
left: i32,
right: i32,
top: i32,
bottom: i32,
pub const zero = Bounds{
.left = 0,
.right = 0,
.top = 0,
.bottom = 0,
};
pub fn initFromRect(x: i32, y: i32, width: i32, height: i32) Bounds {
return Bounds{
.left = x,
.right = x + width,
.top = y,
.bottom = y + height,
};
}
pub fn getWidth(self: Bounds) u32 {
return @intCast(self.right - self.left + 1);
}
pub fn getHeight(self: Bounds) u32 {
return @intCast(self.bottom - self.top + 1);
}
pub fn combine(lhs: Bounds, rhs: Bounds) Bounds {
return Bounds{
.left = @min(lhs.left, rhs.left),
.right = @max(lhs.right, rhs.right),
.top = @min(lhs.top, rhs.top),
.bottom = @max(lhs.bottom, rhs.bottom)
};
}
};
pub const TileVariant = struct {
pub const Chunk = struct {
x: i32,
@ -15,6 +55,10 @@ pub const TileVariant = struct {
width: u32,
height: u32,
data: []u32,
pub fn getBounds(self: Chunk) Bounds {
return Bounds.initFromRect(self.x, self.y, @intCast(self.width), @intCast(self.height));
}
};
pub const Data = union(enum) {
@ -143,48 +187,18 @@ pub const TileVariant = struct {
return result;
}
const Bounds = struct {
left: i32,
right: i32,
top: i32,
bottom: i32,
pub fn width(self: Bounds) u32 {
return @intCast(self.right - self.left);
}
pub fn height(self: Bounds) u32 {
return @intCast(self.bottom - self.top);
}
};
pub fn getBounds(self: TileVariant) Bounds {
if (self.data == .fixed) {
return Bounds{
.left = 0,
.right = @intCast(self.width),
.top = 0,
.bottom = @intCast(self.height),
};
return Bounds.initFromRect(0, 0, @intCast(self.width), @intCast(self.height));
} else if (self.data == .chunks) {
const chunks = self.data.chunks;
var result = Bounds{
.left = 0,
.right = 0,
.top = 0,
.bottom = 0
};
var result: Bounds = .zero;
if (chunks.len > 0) {
result.left = chunks[0].x;
result.right = chunks[0].x + @as(i32, @intCast(chunks[0].width));
result.top = chunks[0].y;
result.bottom = chunks[0].y + @as(i32, @intCast(chunks[0].height));
result = chunks[0].getBounds();
for (chunks[1..]) |chunk| {
result.left = @min(result.left, chunk.x);
result.right = @max(result.right, chunk.x + @as(i32, @intCast(chunk.width)));
result.top = @min(result.top, chunk.y);
result.bottom = @max(result.bottom, chunk.y + @as(i32, @intCast(chunk.height)));
result = result.combine(chunk.getBounds());
}
}

View File

@ -74,6 +74,15 @@ pub const List = struct {
}
return null;
}
pub fn getBool(self: List, name: []const u8) ?bool {
if (self.get(name)) |value| {
if (value == .bool) {
return value.bool;
}
}
return null;
}
};
pub fn init(name: []const u8, value: Value) Property {

View File

@ -241,6 +241,26 @@ pub fn getTileByPosition(self: *const Tilemap, layer: *const Layer, tilesets: Ti
};
}
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();
}

View File

@ -84,7 +84,7 @@ pub fn init(gpa: std.mem.Allocator) !Assets {
);
var tilesets: tiled.Tileset.List = .empty;
try tilesets.add(gpa, "tileset.tsx", tileset);
try tilesets.add(gpa, "tilemap.tsx", tileset);
const players_image = try STBImage.load(@embedFile("assets/kenney_desert-shooter-pack_1.0/PNG/Players/tilemap_packed.png"));
defer players_image.deinit();

View File

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="16" tileheight="16" infinite="1" nextlayerid="3" nextobjectid="2">
<tileset firstgid="1" source="tileset.tsx"/>
<tileset firstgid="235" source="tilemap.tsx"/>
<layer id="1" name="Tile Layer 1" width="30" height="20">
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="16" tileheight="16" infinite="1" nextlayerid="5" nextobjectid="2">
<tileset firstgid="1" source="tilemap.tsx"/>
<layer id="1" name="ground" width="30" height="20">
<data encoding="csv">
<chunk x="-32" y="-32" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -18,9 +17,9 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,325,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,343,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,343
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,91,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,109,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,109
</chunk>
<chunk x="-16" y="-32" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -35,15 +34,15 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
326,326,326,326,326,326,326,327,0,0,0,0,0,0,0,0,
344,254,344,344,344,344,344,345,0,0,0,0,0,0,0,0,
344,344,344,344,254,344,344,345,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,65,65,0,0,0,0,0,0,
92,92,92,92,92,92,92,93,65,65,0,0,0,0,0,0,
110,20,110,110,110,110,110,112,110,65,0,0,0,0,0,0,
110,110,110,110,20,110,110,110,110,65,65,0,0,0,0,0
</chunk>
<chunk x="-32" y="-16" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,343,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,343,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,361,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,109,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,109,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -59,20 +58,20 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
<chunk x="-16" y="-16" width="16" height="16">
344,344,254,344,344,344,344,345,0,0,0,0,0,0,0,0,
344,254,344,344,344,344,254,345,0,0,0,0,0,0,0,0,
362,362,362,362,362,362,362,363,0,0,0,0,299,299,0,0,
0,0,0,299,299,299,299,299,0,299,299,299,429,299,0,0,
0,0,0,299,299,299,299,299,299,299,299,299,299,299,0,0,
0,0,299,299,299,299,299,299,299,299,299,299,300,299,0,0,
0,0,299,299,299,299,300,299,299,300,299,299,299,0,0,0,
0,0,0,299,299,299,299,299,299,299,300,299,299,0,0,0,
0,0,0,299,299,429,299,299,299,299,299,299,299,299,0,299,
0,0,0,299,299,299,299,300,299,299,299,299,299,299,299,299,
0,0,0,299,299,299,299,299,299,300,299,299,429,299,299,299,
0,0,0,299,299,299,299,299,299,299,299,299,299,299,300,299,
0,299,299,299,299,299,300,299,299,299,299,299,299,299,299,299,
0,0,299,299,299,299,299,299,299,299,299,299,299,299,299,299,
110,110,20,110,110,110,110,94,110,65,65,0,0,0,65,0,
110,20,110,110,110,110,20,111,65,65,65,0,0,0,65,0,
128,128,128,128,128,128,128,129,65,65,65,0,65,65,65,0,
0,0,0,65,65,65,65,65,65,65,65,65,195,65,65,0,
0,0,0,65,65,65,65,65,65,65,65,65,65,65,65,0,
0,0,65,65,65,65,65,65,65,65,65,65,66,65,65,65,
0,0,65,65,65,65,66,65,65,66,65,65,65,65,65,65,
0,0,0,65,65,65,65,65,65,65,66,65,65,65,65,65,
0,0,0,65,65,195,65,65,65,65,65,65,65,65,65,65,
0,0,0,65,65,65,65,66,65,65,65,65,65,65,65,65,
0,0,0,65,65,65,65,65,65,66,65,65,195,65,65,65,
0,0,0,65,65,65,65,65,65,65,65,65,65,65,66,65,
0,65,65,65,65,65,66,65,65,65,65,65,65,65,65,65,
0,0,65,65,65,65,65,65,65,65,65,65,65,65,65,65,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
@ -82,15 +81,137 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,65,65,65,65,65,0,0,0,0,0,0,0,0,0,0,
65,65,65,65,65,65,0,0,0,0,0,0,0,0,0,0,
65,65,65,65,65,65,65,0,0,0,0,0,0,0,0,0,
65,65,65,65,65,65,65,0,0,0,0,0,0,0,0,0,
65,65,65,65,65,65,0,0,0,0,0,0,0,0,0,0,
65,65,65,65,65,0,0,0,0,0,0,0,0,0,0,0,
65,65,65,0,0,0,0,0,0,0,0,0,0,0,0,0,
65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
</data>
</layer>
<layer id="3" name="walls" width="30" height="20">
<properties>
<property name="solid" type="bool" value="true"/>
</properties>
<data encoding="csv">
<chunk x="-16" y="-32" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,1,2,2,2,2,3,0,0,
0,0,0,0,0,0,0,0,19,110,20,110,20,21,0,0,
0,0,0,0,0,0,0,0,19,110,110,20,20,21,0,0,
0,0,0,0,0,0,0,0,37,38,38,38,38,39,0,0,
0,0,0,0,0,0,0,0,73,56,174,174,174,57,0,0,
0,0,0,0,0,0,0,0,73,174,174,56,174,57,0,0,
0,0,0,0,0,0,0,0,73,174,174,174,174,57,0,0
</chunk>
<chunk x="-16" y="-16" width="16" height="16">
0,0,0,0,0,0,0,0,73,174,56,56,174,57,0,0,
0,0,0,0,0,0,0,0,73,174,174,174,56,57,0,0,
0,0,0,0,0,0,0,0,73,74,74,74,74,75,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
</data>
</layer>
<layer id="4" name="walls 2" width="30" height="20">
<properties>
<property name="solid" type="bool" value="true"/>
</properties>
<data encoding="csv">
<chunk x="-16" y="-32" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,2,
0,0,0,0,0,0,0,0,0,0,0,0,0,37,38,38
</chunk>
<chunk x="0" y="-32" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2,2,2,3,0,0,0,0,0,0,0,0,0,0,0,0,
38,38,38,39,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
<chunk x="-16" y="-16" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,73,174,174,
0,0,0,0,0,0,0,0,0,0,0,0,0,73,174,174,
0,0,0,0,0,0,0,0,0,0,0,0,0,73,56,174,
0,0,0,0,0,0,0,0,0,0,0,0,0,73,56,56,
0,0,0,0,0,0,0,0,0,0,0,0,0,73,74,74,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
<chunk x="0" y="-16" width="16" height="16">
56,56,174,75,0,0,0,0,0,0,0,0,0,0,0,0,
174,174,174,75,0,0,0,0,0,0,0,0,0,0,0,0,
174,174,56,75,0,0,0,0,0,0,0,0,0,0,0,0,
174,56,56,75,0,0,0,0,0,0,0,0,0,0,0,0,
74,74,74,75,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,299,299,299,0,0,0,0,0,0,0,0,0,0,
299,299,299,299,299,299,0,0,0,0,0,0,0,0,0,0,
0,0,299,299,299,299,299,0,0,0,0,0,0,0,0,0,
299,299,299,299,299,299,299,0,0,0,0,0,0,0,0,0,
299,299,299,299,299,299,0,0,0,0,0,0,0,0,0,0,
299,299,299,299,299,0,0,0,0,0,0,0,0,0,0,0,
299,299,299,0,0,0,0,0,0,0,0,0,0,0,0,0,
299,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>

View File

@ -4,7 +4,7 @@ const log = std.log.scoped(.engine);
const InputSystem = @import("./input.zig");
const KeyCode = InputSystem.KeyCode;
const Mouse = InputSystem.Mouse;
const MouseButton = InputSystem.MouseButton;
const AudioSystem = @import("./audio/root.zig");
const AudioData = AudioSystem.Data;
@ -27,19 +27,14 @@ pub const Nanoseconds = u64;
const Frame = @This();
pub const Input = struct {
down_keys: std.EnumSet(KeyCode),
pressed_keys: std.EnumSet(KeyCode),
released_keys: std.EnumSet(KeyCode),
pressed_keys_at: std.EnumMap(KeyCode, Nanoseconds),
mouse: Mouse,
keyboard: InputSystem.ButtonStateSet(KeyCode),
mouse_button: InputSystem.ButtonStateSet(MouseButton),
mouse_position: ?Vec2,
pub const empty = Input{
.down_keys = .initEmpty(),
.pressed_keys = .initEmpty(),
.released_keys = .initEmpty(),
.pressed_keys_at = .init(.{}),
.mouse = .empty
.keyboard = .empty,
.mouse_button = .empty,
.mouse_position = null,
};
};
@ -139,7 +134,8 @@ pub fn time(self: Frame) f64 {
}
pub fn isKeyDown(self: Frame, key_code: KeyCode) bool {
return self.input.down_keys.contains(key_code);
const keyboard = &self.input.keyboard;
return keyboard.down.contains(key_code);
}
pub fn getKeyDownDuration(self: Frame, key_code: KeyCode) ?f32 {
@ -147,18 +143,21 @@ pub fn getKeyDownDuration(self: Frame, key_code: KeyCode) ?f32 {
return null;
}
const pressed_at_ns = self.input.pressed_keys_at.get(key_code).?;
const keyboard = &self.input.keyboard;
const pressed_at_ns = keyboard.pressed_at.get(key_code).?;
const duration_ns = self.time_ns - pressed_at_ns;
return @as(f32, @floatFromInt(duration_ns)) / std.time.ns_per_s;
}
pub fn isKeyPressed(self: Frame, key_code: KeyCode) bool {
return self.input.pressed_keys.contains(key_code);
const keyboard = &self.input.keyboard;
return keyboard.pressed.contains(key_code);
}
pub fn isKeyReleased(self: Frame, key_code: KeyCode) bool {
return self.input.released_keys.contains(key_code);
const keyboard = &self.input.keyboard;
return keyboard.released.contains(key_code);
}
pub fn getKeyState(self: Frame, key_code: KeyCode) KeyState {
@ -170,6 +169,14 @@ pub fn getKeyState(self: Frame, key_code: KeyCode) KeyState {
};
}
pub fn isMousePressed(self: Frame, button: MouseButton) bool {
return self.input.mouse_button.pressed.contains(button);
}
pub fn isMouseDown(self: Frame, button: MouseButton) bool {
return self.input.mouse_button.down.contains(button);
}
fn pushAudioCommand(self: *Frame, command: AudioCommand) void {
const arena = self.arena.allocator();

View File

@ -19,38 +19,59 @@ pub const KeyCode = @Type(.{
}
});
pub const Mouse = struct {
pub const Button = enum {
left,
right,
middle,
pub const MouseButton = enum(i3) {
left = @intFromEnum(sokol.app.Mousebutton.LEFT),
right = @intFromEnum(sokol.app.Mousebutton.RIGHT),
middle = @intFromEnum(sokol.app.Mousebutton.MIDDLE),
_
};
pub fn fromSokol(mouse_button: sokol.app.Mousebutton) ?Button {
return switch(mouse_button) {
.LEFT => Button.left,
.RIGHT => Button.right,
.MIDDLE => Button.middle,
else => null
pub fn ButtonStateSet(E: type) type {
return struct {
const Self = @This();
down: std.EnumSet(E),
pressed: std.EnumSet(E),
released: std.EnumSet(E),
pressed_at: std.EnumMap(E, Nanoseconds),
pub const empty = Self{
.down = .initEmpty(),
.pressed = .initEmpty(),
.released = .initEmpty(),
.pressed_at = .init(.{}),
};
fn press(self: *Self, button: E, now: Nanoseconds) void {
self.pressed_at.put(button, now);
self.pressed.insert(button);
self.down.insert(button);
}
fn release(self: *Self, button: E) void {
self.down.remove(button);
self.released.insert(button);
self.pressed_at.remove(button);
}
fn releaseAll(self: *Self) void {
var iter = self.down.iterator();
while (iter.next()) |key_code| {
self.released.insert(key_code);
}
self.down = .initEmpty();
self.pressed_at = .init(.{});
}
};
position: ?Vec2,
buttons: std.EnumSet(Button),
pub const empty = Mouse{
.position = null,
.buttons = .initEmpty()
};
};
}
pub const Event = union(enum) {
mouse_pressed: struct {
button: Mouse.Button,
button: MouseButton,
position: Vec2,
},
mouse_released: struct {
button: Mouse.Button,
button: MouseButton,
position: Vec2,
},
mouse_move: Vec2,
@ -72,39 +93,31 @@ pub fn processEvent(frame: *Frame, event: Event) void {
switch (event) {
.key_pressed => |opts| {
if (!opts.repeat) {
input.pressed_keys_at.put(opts.code, frame.time_ns);
input.pressed_keys.insert(opts.code);
input.down_keys.insert(opts.code);
input.keyboard.press(opts.code, frame.time_ns);
}
},
.key_released => |key_code| {
input.down_keys.remove(key_code);
input.released_keys.insert(key_code);
input.pressed_keys_at.remove(key_code);
input.keyboard.release(key_code);
},
.mouse_leave => {
var iter = input.down_keys.iterator();
while (iter.next()) |key_code| {
input.released_keys.insert(key_code);
}
input.down_keys = .initEmpty();
input.pressed_keys_at = .init(.{});
input.keyboard.releaseAll();
input.mouse = .empty;
input.mouse_position = null;
input.mouse_button = .empty;
},
.mouse_enter => |pos| {
input.mouse.position = pos;
input.mouse_position = pos;
},
.mouse_move => |pos| {
input.mouse.position = pos;
input.mouse_position = pos;
},
.mouse_pressed => |opts| {
input.mouse.position = opts.position;
input.mouse.buttons.insert(opts.button);
input.mouse_position = opts.position;
input.mouse_button.press(opts.button, frame.time_ns);
},
.mouse_released => |opts| {
input.mouse.position = opts.position;
input.mouse.buttons.remove(opts.button);
input.mouse_position = opts.position;
input.mouse_button.release(opts.button);
},
else => {}
}

View File

@ -124,14 +124,22 @@ pub const Vec2 = extern struct {
);
}
pub fn lengthSqr(self: Vec2) f32 {
return self.x*self.x + self.y*self.y;
}
pub fn length(self: Vec2) f32 {
return @sqrt(self.x*self.x + self.y*self.y);
return @sqrt(self.lengthSqr());
}
pub fn distance(self: Vec2, other: Vec2) f32 {
return self.sub(other).length();
}
pub fn distanceSqr(self: Vec2, other: Vec2) f32 {
return self.sub(other).lengthSqr();
}
pub fn limitLength(self: Vec2, max_length: f32) Vec2 {
const self_length = self.length();
if (self_length > max_length) {
@ -395,6 +403,10 @@ pub const Rect = struct {
return self.pos.y + self.size.y;
}
pub fn center(self: Rect) Vec2 {
return self.pos.add(self.size.multiplyScalar(0.5));
}
pub fn multiply(self: Rect, xy: Vec2) Rect {
return Rect{
.pos = self.pos.multiply(xy),

View File

@ -165,11 +165,11 @@ fn sokolFrame(self: *Engine) !void {
var revert_mouse_position: ?Vec2 = null;
if (self.canvas_size) |canvas_size| {
if (self.frame.input.mouse.position) |mouse| {
if (self.frame.input.mouse_position) |mouse| {
const transform = ScreenScalar.init(screen_size, canvas_size);
revert_mouse_position = mouse;
self.frame.input.mouse.position = mouse.sub(transform.translation).divideScalar(transform.scale);
self.frame.input.mouse_position = mouse.sub(transform.translation).divideScalar(transform.scale);
}
}
@ -195,8 +195,10 @@ fn sokolFrame(self: *Engine) !void {
try self.game.tick(&self.frame);
frame.input.pressed_keys = .initEmpty();
frame.input.released_keys = .initEmpty();
frame.input.keyboard.pressed = .initEmpty();
frame.input.keyboard.released = .initEmpty();
frame.input.mouse_button.pressed = .initEmpty();
frame.input.mouse_button.released = .initEmpty();
}
if (self.canvas_size) |canvas_size| {
@ -234,7 +236,7 @@ fn sokolFrame(self: *Engine) !void {
}
if (revert_mouse_position) |pos| {
self.frame.input.mouse.position = pos;
self.frame.input.mouse_position = pos;
}
}
@ -262,7 +264,6 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
defer zone.deinit();
const e = e_ptr.*;
const MouseButton = Input.Mouse.Button;
if (imgui.handleEvent(e)) {
if (self.mouse_inside) {
@ -274,13 +275,11 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true;
}
blk: switch (e.type) {
switch (e.type) {
.MOUSE_DOWN => {
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
Input.processEvent(&self.frame, .{
.mouse_pressed = .{
.button = mouse_button,
.button = @enumFromInt(@intFromEnum(e.mouse_button)),
.position = Vec2.init(e.mouse_x, e.mouse_y)
}
});
@ -288,11 +287,9 @@ fn sokolEvent(self: *Engine, e_ptr: [*c]const sapp.Event) !bool {
return true;
},
.MOUSE_UP => {
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
Input.processEvent(&self.frame, .{
.mouse_released = .{
.button = mouse_button,
.button = @enumFromInt(@intFromEnum(e.mouse_button)),
.position = Vec2.init(e.mouse_x, e.mouse_y)
}
});

View File

@ -2,6 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const clamp = std.math.clamp;
const log = std.log.scoped(.game);
const Assets = @import("./assets.zig");
const Tilemap = Assets.Tilemap;
@ -17,6 +18,9 @@ const rgba = Engine.Math.rgba;
const Range = Engine.Math.Range;
const TextureId = Engine.Graphics.TextureId;
const AudioId = Engine.Audio.Data.Id;
const Sprite = Engine.Graphics.Sprite;
const RaycastTileIterator = @import("./raycast_tile_iterator.zig");
const Game = @This();
@ -94,6 +98,69 @@ const AudioBundle = struct {
}
};
const Bullet = struct {
position: Vec2,
velocity: Vec2
};
const Tile = struct {
sprites_buffer: [4]Sprite,
sprites_len: usize,
solid: bool,
const empty = Tile{
.sprites_buffer = undefined,
.sprites_len = 0,
.solid = false
};
fn sprites(self: *Tile) []Sprite {
return self.sprites_buffer[0..self.sprites_len];
}
fn appendSprite(self: *Tile, sprite: Sprite) void {
var list: std.ArrayList(Sprite) = .{
.items = self.sprites(),
.capacity = self.sprites_buffer.len
};
if (list.items.len == list.capacity) {
_ = list.orderedRemove(0);
log.warn("Too many sprites on a single tile", .{});
}
list.appendAssumeCapacity(sprite);
self.sprites_len = list.items.len;
}
};
const TileGrid = struct {
origin: Vec2,
width: usize,
height: usize,
tile_size: Vec2,
tiles: []Tile,
pub fn get(self: TileGrid, x: usize, y: usize) *Tile {
assert(0 <= x and x < self.width);
assert(0 <= y and y < self.height);
return &self.tiles[y * self.width + x];
}
pub fn isInBounds(self: TileGrid, x: i32, y: i32) bool {
return (0 <= x and x < @as(i32, @intCast(self.width))) and (0 <= y and y < @as(i32, @intCast(self.height)));
}
pub fn toTileSpace(self: TileGrid, pos: Vec2) Vec2 {
return pos.sub(self.origin).divide(self.tile_size);
}
pub fn fromTileSpace(self: TileGrid, pos: Vec2) Vec2 {
return pos.multiply(self.tile_size).add(self.origin);
}
};
arena: std.heap.ArenaAllocator,
gpa: Allocator,
rng: RNGState,
@ -105,6 +172,9 @@ last_faced_left: bool = false,
player_walk_sound: AudioBundle = .empty,
hand_offset: Vec2 = .zero,
bullets: std.ArrayList(Bullet) = .empty,
tilegrid: TileGrid,
player_anim: Animation,
pub fn init(gpa: Allocator, seed: u64, assets: *Assets) !Game {
@ -125,18 +195,76 @@ pub fn init(gpa: Allocator, seed: u64, assets: *Assets) !Game {
}),
};
const tilemap = assets.terrain_tilemap;
const map_bounds = assets.map.getTileBounds();
const map_width = map_bounds.getWidth();
const map_height = map_bounds.getHeight();
const tilegrid = TileGrid{
.origin = Vec2.initFromInt(i32, map_bounds.left, map_bounds.top).multiply(tilemap.tile_size),
.width = map_width,
.height = map_height,
.tile_size = tilemap.tile_size,
.tiles = try arena.allocator().alloc(Tile, map_width * map_height),
};
@memset(tilegrid.tiles, .empty);
{
const texture_info = Engine.Graphics.getTextureInfo(tilemap.texture);
const tilemap_size = Vec2.initFromInt(u32, texture_info.width, texture_info.height);
for (assets.map.layers) |layer| {
if (layer.variant != .tile) {
continue;
}
const is_layer_solid = layer.properties.getBool("solid") orelse false;
const tile_layer = layer.variant.tile;
const layer_bounds = tile_layer.getBounds();
for (0..layer_bounds.getHeight()) |oy| {
const y = layer_bounds.top + @as(i32, @intCast(oy));
for (0..layer_bounds.getWidth()) |ox| {
const x = layer_bounds.left + @as(i32, @intCast(ox));
const tile_gid = tile_layer.get(x, y) orelse continue;
if (tile_gid == 0) continue;
const tile = assets.map.getTile(assets.tilesets, tile_gid) orelse continue;
const tile_id_f32: f32 = @floatFromInt(tile.id);
const width_in_tiles = tilemap_size.x / tilemap.tile_size.x;
const tile_x = @rem(tile_id_f32, width_in_tiles);
const tile_y =@divFloor(tile_id_f32, width_in_tiles);
const tilegrid_tile = tilegrid.get(
@intCast(x - map_bounds.left),
@intCast(y - map_bounds.top)
);
tilegrid_tile.solid |= is_layer_solid;
tilegrid_tile.appendSprite(.{
.texture = tilemap.texture,
.uv = tilemap.getTileUV(tile_x, tile_y)
});
}
}
}
}
return Game{
.arena = arena,
.gpa = gpa,
.assets = assets,
.player = findSpawnpoint(assets) orelse .init(0, 0),
.player_anim = player_anim,
.rng = RNGState.init(seed)
.rng = RNGState.init(seed),
.tilegrid = tilegrid
};
}
pub fn deinit(self: *Game) void {
self.arena.deinit();
self.bullets.deinit(self.gpa);
}
fn findSpawnpoint(assets: *Assets) ?Vec2 {
@ -187,42 +315,181 @@ fn drawTile(frame: *Engine.Frame, opts: DrawTileOptions) void {
}
fn drawTilemap(self: *Game, frame: *Engine.Frame) void {
const tilemap = self.assets.terrain_tilemap;
const texture_info = Engine.Graphics.getTextureInfo(tilemap.texture);
const tilemap_size = Vec2.initFromInt(u32,texture_info.width, texture_info.height);
for (0..self.tilegrid.height) |y| {
for (0..self.tilegrid.width) |x| {
const tile = self.tilegrid.get(x, y);
if (tile.sprites_len == 0) continue;
const map = self.assets.map;
const tile_pos = Vec2.initFromInt(usize, x, y).multiply(self.tilegrid.tile_size);
const tile_rect = Rect{
.pos = self.tilegrid.origin.add(tile_pos),
.size = self.tilegrid.tile_size
};
for (map.layers) |layer| {
if (layer.variant != .tile) {
continue;
}
const tile_layer = layer.variant.tile;
const layer_bounds = tile_layer.getBounds();
for (0..layer_bounds.height()) |oy| {
const y = layer_bounds.top + @as(i32, @intCast(oy));
for (0..layer_bounds.width()) |ox| {
const x = layer_bounds.left + @as(i32, @intCast(ox));
const tile_gid = tile_layer.get(x, y) orelse continue;
const tile = map.getTile(self.assets.tilesets, tile_gid) orelse continue;
const tile_id_f32: f32 = @floatFromInt(tile.id);
const width_in_tiles = tilemap_size.x / tilemap.tile_size.x;
const tile_x = @rem(tile_id_f32, width_in_tiles);
const tile_y = @divFloor(tile_id_f32, width_in_tiles);
drawTile(frame, .{
.pos = Vec2.initFromInt(i32, x, y).multiply(tilemap.tile_size),
.tilemap = tilemap,
.tile = .init(tile_x, tile_y)
const color = rgb(255, 255, 255);
for (tile.sprites()) |sprite| {
frame.drawRectangle(.{
.rect = tile_rect,
.color = color,
.sprite = sprite
});
}
}
}
}
const CollisionResult = struct {
time: f32,
normal: Vec2,
};
fn sweptAABB(b1: Rect, v1: Vec2, b2: Rect) ?CollisionResult {
var entry_x: f32 = undefined;
var exit_x: f32 = undefined;
if (v1.x > 0) {
entry_x = b2.left() - b1.right();
exit_x = b2.right() - b1.left();
} else {
entry_x = b2.right() - b1.left();
exit_x = b2.left() - b1.right();
}
var entry_y: f32 = undefined;
var exit_y: f32 = undefined;
if (v1.y > 0) {
entry_y = b2.top() - b1.bottom();
exit_y = b2.bottom() - b1.top();
} else {
entry_y = b2.bottom() - b1.top();
exit_y = b2.top() - b1.bottom();
}
const inf = std.math.inf(f32);
var entry_x_time: f32 = undefined;
var exit_x_time: f32 = undefined;
if (v1.x == 0) {
entry_x_time = -inf;
exit_x_time = inf;
} else {
entry_x_time = entry_x / v1.x;
exit_x_time = exit_x / v1.x;
}
var entry_y_time: f32 = undefined;
var exit_y_time: f32 = undefined;
if (v1.y == 0) {
entry_y_time = -inf;
exit_y_time = inf;
} else {
entry_y_time = entry_y / v1.y;
exit_y_time = exit_y / v1.y;
}
const entry_time = @max(entry_x_time, entry_y_time);
const exit_time = @min(exit_x_time, exit_y_time);
if (entry_time > exit_time or
(entry_x_time < 0 and entry_y_time < 0) or
entry_x_time > 1.0 or
entry_y_time > 1.0
) {
return null;
}
var result = CollisionResult{
.time = entry_time,
.normal = .zero
};
if (entry_x_time > entry_y_time) {
result.normal.x = if ((exit_x - entry_x) < 0.0) 1 else -1;
} else {
result.normal.y = if ((exit_y - entry_y) < 0.0) 1 else -1;
}
return result;
}
fn getStepBounds(rect: Rect, step: Vec2) Rect {
var result = rect;
if (step.x >= 0) {
result.size.x += step.x;
} else {
result.pos.x += step.x;
result.size.x -= step.x;
}
if (step.y >= 0) {
result.size.y += step.y;
} else {
result.pos.y += step.y;
result.size.y -= step.y;
}
return result;
}
fn collisionResponseSlide(step: *Vec2, collision: CollisionResult) void {
var new_step = step.multiplyScalar(collision.time);
const remaining_time = 1 - collision.time;
const dotprod = (step.x * collision.normal.y + step.y * collision.normal.x) * remaining_time;
new_step.x += dotprod * collision.normal.y;
new_step.y += dotprod * collision.normal.x;
step.* = new_step;
}
fn listNearestSolids(self: *Game, gpa: Allocator, bounds: Rect) ![]Rect {
var result: std.ArrayList(Rect) = .empty;
errdefer result.deinit(gpa);
var distances: std.ArrayList(f32) = .empty;
defer distances.deinit(gpa);
const top_left = self.tilegrid.toTileSpace(Vec2.init(bounds.left(), bounds.top()));
const bottom_right = self.tilegrid.toTileSpace(Vec2.init(bounds.right(), bounds.bottom()));
var y = @floor(top_left.y);
while (y < bottom_right.y) : (y += 1) {
var x = @floor(top_left.x);
while (x < bottom_right.x) : (x += 1) {
const x_i32: i32 = @intFromFloat(x);
const y_i32: i32 = @intFromFloat(y);
if (!self.tilegrid.isInBounds(x_i32, y_i32)) continue;
const tile = self.tilegrid.get(@intCast(x_i32), @intCast(y_i32));
if (tile.solid) {
const tile_collider = Rect{
.pos = self.tilegrid.fromTileSpace(Vec2.init(x, y)),
.size = self.tilegrid.tile_size
};
try result.append(gpa, tile_collider);
try distances.append(gpa, Vec2.distanceSqr(tile_collider.center(), bounds.center()));
}
}
}
const Context = struct {
bounds_center: Vec2,
fn lessThanFn(ctx: @This(), lhs: Rect, rhs: Rect) bool {
const lhs_distance = Vec2.distanceSqr(lhs.center(), ctx.bounds_center);
const rhs_distance = Vec2.distanceSqr(rhs.center(), ctx.bounds_center);
return lhs_distance < rhs_distance;
}
};
const ctx = Context{
.bounds_center = bounds.center()
};
std.mem.sort(Rect, result.items, ctx, Context.lessThanFn);
return try result.toOwnedSlice(gpa);
}
pub fn tick(self: *Game, frame: *Engine.Frame) !void {
const dt = frame.deltaTime();
@ -242,6 +509,8 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void {
frame.pushTransform(camera_offset, .init(1, 1));
defer frame.popTransform();
self.drawTilemap(frame);
var dir = Vec2.init(0, 0);
if (frame.isKeyDown(.W)) {
dir.y -= 1;
@ -269,11 +538,59 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void {
self.player_anim_state.frame_index = 0;
}
self.player = self.player.add(dir.multiplyScalar(50 * dt));
const velocity = dir.multiplyScalar(50);
var step = velocity.multiplyScalar(dt);
const regular_font = self.assets.font_id.get(.regular);
const player_collider_size = Vec2.init(20, 20);
const player_collider = Rect{
.pos = self.player.sub(player_collider_size.divideScalar(2)),
.size = player_collider_size,
};
self.drawTilemap(frame);
{
// const solids = try self.listNearestSolids(frame.arena.allocator(), getStepBounds(player_collider, step));
// for (solids) |solid| {
// if (sweptAABB(player_collider, step, solid)) |collision| {
// collisionResponseSlide(&step, collision);
// }
// }
inline for (.{
// Vec2.init(0, 0),
// Vec2.init(1, 0),
Vec2.init(0, 1),
// Vec2.init(1, 1),
}) |corner_offset| {
const corner = player_collider.pos.add(player_collider.size.multiply(corner_offset));
var iter = RaycastTileIterator.init(
self.tilegrid.toTileSpace(corner),
self.tilegrid.toTileSpace(corner.add(step)),
);
while (iter.next()) |collision| {
if (!self.tilegrid.isInBounds(collision.tile_x, collision.tile_y)) break;
const tile = self.tilegrid.get(@intCast(collision.tile_x), @intCast(collision.tile_y));
if (tile.solid) {
const time = collision.distance / step.divide(self.tilegrid.tile_size).length();
collisionResponseSlide(&step, .{
.normal = .initFromInt(i32, collision.normal_x, collision.normal_y),
.time = time
});
const pos = Vec2.initFromInt(i32, collision.tile_x, collision.tile_y).multiply(self.tilegrid.tile_size).add(self.tilegrid.origin);
frame.drawRectangle(.{
.rect = Rect{
.pos = pos,
.size = self.tilegrid.tile_size,
},
.color = rgba(200, 20, 20, 0.5)
});
break;
}
}
}
}
self.player = self.player.add(step);
if (dir.x < 0) {
self.last_faced_left = true;
@ -297,8 +614,14 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void {
}
});
frame.drawRectangle(.{
.rect = player_collider,
.color = rgba(20, 255, 255, 0.5)
});
const max_hand_length = 32;
if (frame.input.mouse.position) |mouse_screen| {
if (frame.input.mouse_position) |mouse_screen| {
const mouse = mouse_screen.sub(camera_offset);
const player_to_mouse = mouse.sub(self.player);
self.hand_offset = mouse.sub(self.player).limitLength(max_hand_length);
@ -311,6 +634,38 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void {
.tile = .init(4, 2)
});
frame.hide_cursor = true;
if (false) {
var iter = RaycastTileIterator.init(
self.tilegrid.toTileSpace(self.player),
self.tilegrid.toTileSpace(mouse),
);
while (iter.next()) |item| {
if (!self.tilegrid.isInBounds(item.tile_x, item.tile_y)) break;
const tile = self.tilegrid.get(@intCast(item.tile_x), @intCast(item.tile_y));
const pos = Vec2.initFromInt(i32, item.tile_x, item.tile_y).multiply(self.tilegrid.tile_size).add(self.tilegrid.origin);
frame.drawRectangle(.{
.rect = Rect{
.pos = pos,
.size = self.tilegrid.tile_size,
},
.color = rgba(200, 20, 20, 0.5)
});
frame.drawRectangle(.{
.rect = Rect{
.pos = pos.add(self.tilegrid.tile_size.multiplyScalar(0.25)).add(self.tilegrid.tile_size.multiplyScalar(0.25).multiply(.initFromInt(i32, item.normal_x, item.normal_y))),
.size = self.tilegrid.tile_size.divideScalar(2),
},
.color = rgba(20, 200, 20, 0.5)
});
if (tile.solid) {
break;
}
}
}
}
const hand = self.player.add(self.hand_offset);
@ -329,10 +684,25 @@ pub fn tick(self: *Game, frame: *Engine.Frame) !void {
.rotation = self.hand_offset.getAngle()
});
frame.drawText(self.player, "Player", .{
.font = regular_font,
.size = 16
if (frame.isMousePressed(.left)) {
try self.bullets.append(self.gpa, .{
.position = hand,
.velocity = self.hand_offset.normalized().multiplyScalar(100)
});
}
for (self.bullets.items) |*bullet| {
bullet.position = bullet.position.add(bullet.velocity.multiplyScalar(dt));
const bullet_size = Vec2.init(16, 16);
frame.drawRectangle(.{
.rect = .{
.pos = bullet.position.sub(bullet_size.multiplyScalar(0.5)),
.size = bullet_size
},
.color = rgb(200, 10, 10)
});
}
}
pub fn debug(self: *Game) !void {

View File

@ -0,0 +1,97 @@
const Engine = @import("./engine/root.zig");
const Vec2 = Engine.Vec2;
// DDA Algorithm: https://lodev.org/cgtutor/raycasting.html
const RaycastTileIterator = @This();
max_distance: f32,
ray_length_1d: Vec2,
check_x: i32,
check_y: i32,
step_x: i32,
step_y: i32,
unit_step_size: Vec2,
const Item = struct {
tile_x: i32,
tile_y: i32,
normal_x: i32,
normal_y: i32,
distance: f32,
};
pub fn init(origin: Vec2, target: Vec2) RaycastTileIterator {
const target_origin = target.sub(origin);
const max_distance = target_origin.length();
const dir = target_origin.normalized();
var unit_step_size_x: f32 = 0;
if (dir.x != 0) {
unit_step_size_x = dir.y / dir.x;
}
var unit_step_size_y: f32= 0;
if (dir.y != 0) {
unit_step_size_y = dir.x / dir.y;
}
const unit_step_size = Vec2{
.x = Vec2.init(1, unit_step_size_x).length(),
.y = Vec2.init(unit_step_size_y, 1).length()
};
const step_x: i32 = if (dir.x < 0) -1 else 1;
const step_y: i32 = if (dir.y < 0) -1 else 1;
const step_x_f32: f32 = @floatFromInt(step_x);
const step_y_f32: f32 = @floatFromInt(step_y);
const ray_length_1d = (Vec2{
.x = ((step_x_f32 + 1)/2 - step_x_f32 * @mod(origin.x, 1)),
.y = ((step_y_f32 + 1)/2 - step_y_f32 * @mod(origin.y, 1))
}).multiply(unit_step_size);
const check_x: i32 = @intFromFloat(origin.x);
const check_y: i32 = @intFromFloat(origin.y);
return RaycastTileIterator{
.max_distance = max_distance,
.ray_length_1d = ray_length_1d,
.check_x = check_x,
.check_y = check_y,
.step_x = step_x,
.step_y = step_y,
.unit_step_size = unit_step_size
};
}
pub fn next(self: *RaycastTileIterator) ?Item {
if (self.ray_length_1d.x > self.max_distance and self.ray_length_1d.y > self.max_distance) {
return null;
}
var normal_x: i32 = 0;
var normal_y: i32 = 0;
var distance: f32 = undefined;
if (self.ray_length_1d.x < self.ray_length_1d.y) {
distance = self.ray_length_1d.x;
normal_x = -self.step_x;
self.check_x += self.step_x;
self.ray_length_1d.x += self.unit_step_size.x;
} else {
distance = self.ray_length_1d.y;
normal_y = -self.step_y;
self.check_y += self.step_y;
self.ray_length_1d.y += self.unit_step_size.y;
}
return .{
.distance = distance,
.tile_x = self.check_x,
.tile_y = self.check_y,
.normal_x = normal_x,
.normal_y = normal_y
};
}