diff --git a/src/circuit.zig b/src/circuit.zig index b610f7c..4b2d37d 100644 --- a/src/circuit.zig +++ b/src/circuit.zig @@ -1,4 +1,8 @@ const std = @import("std"); +const util = @import("util.zig"); + +const Vec2 = util.Vec2; +const Cell = util.Cell; fn is_circuit(tile: u8) bool { return is_plug(tile) or is_conduit(tile) or is_switch(tile); @@ -95,8 +99,6 @@ fn get_signals(tile: u8) Signals { }; } -const Vec2 = std.meta.Vector(2, i32); -const Cell = Vec2; fn dir(s: Side) Cell { return switch (s) { .up => Vec2{ 0, -1 }, @@ -129,7 +131,7 @@ fn cell2index(c: Cell) ?usize { const CellState = struct { enabled: bool = false, tile: u8 }; const MAXCELLS = 400; -const CellMap = [MAXCELLS]CellState; // std.AutoHashMap(Cell, CellState); +const CellMap = [MAXCELLS]CellState; offset: Cell, cells: CellMap, diff --git a/src/ecs.zig b/src/ecs.zig index 1e5bec7..356c922 100644 --- a/src/ecs.zig +++ b/src/ecs.zig @@ -152,21 +152,18 @@ pub fn World(comptime ComponentBase: type) type { const Self = @This(); const Iterator = struct { world: *Self, - // lastIndex: ?usize, index: usize, query: ComponentQuery, pub fn init(w: *Self, q: ComponentQuery) @This() { return @This(){ .world = w, - // .lastComponent = null, .index = 0, .query = q, }; } pub fn next(this: *@This()) ?usize { - // if (this.lastIndex) |_| this.index += 1; if (this.index == this.world.components.len) return null; var match = false; while (!match) { diff --git a/src/main.zig b/src/main.zig index 853297a..0ca1e59 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,18 +3,13 @@ const w4 = @import("wasm4.zig"); const ecs = @import("ecs.zig"); const assets = @import("assets"); const input = @import("input.zig"); +const util = @import("util.zig"); const Circuit = @import("circuit.zig"); +const Map = @import("map.zig"); -const Vec2 = std.meta.Vector(2, i32); -const Vec2f = std.meta.Vector(2, f32); -const AABB = struct { - pos: Vec2f, - size: Vec2f, - - pub fn addv(this: @This(), vec2f: Vec2f) @This() { - return @This(){ .pos = this.pos + vec2f, .size = this.size }; - } -}; +const Vec2 = util.Vec2; +const Vec2f = util.Vec2f; +const AABB = util.AABB; const Anim = struct { time: usize = 0, currentOp: usize = 0, @@ -112,9 +107,10 @@ const World = ecs.World(Component); // Global vars const KB = 1024; -var heap: [8 * KB]u8 = undefined; +var heap: [16 * KB]u8 = undefined; var fba = std.heap.FixedBufferAllocator.init(&heap); var world: World = World.init(fba.allocator()); +var map: Map = undefined; var circuit = Circuit.init(Vec2{ 0, 0 }, &assets.conduit); const anim_store = struct { @@ -143,6 +139,7 @@ fn showErr(msg: []const u8) noreturn { } export fn start() void { + map = Map.init(Vec2{ 0, 0 }, &assets.solid, fba.allocator()) catch showErr("Couldn't init map"); _ = world.create(.{ .pos = Pos.init(Vec2f{ 100, 80 }), .control = .{ .controller = .player, .state = .stand }, @@ -215,13 +212,7 @@ export fn update() void { } } - w4.DRAW_COLORS.* = 0x0210; - for (assets.solid) |tilePlus, i| { - const tile = tilePlus - 1; - const t = w4.Vec2{ @intCast(i32, (tile % 16) * 8), @intCast(i32, (tile / 16) * 8) }; - const pos = w4.Vec2{ @intCast(i32, (i % 20) * 8), @intCast(i32, (i / 20) * 8) }; - w4.blitSub(&assets.tiles, pos, .{ 8, 8 }, t, 128, .{ .bpp = .b2 }); - } + map.draw(); for (circuit.cells) |cell, i| { const tilePlus = cell.tile; @@ -337,7 +328,8 @@ fn wireManipulationProcess(_: f32, id: usize, pos: *Pos, control: *Control) void } else { const interactDistance = 4; var minDistance: f32 = interactDistance; - var wireIter = world.iter(World.Query.require(&.{.wire})); + const q = World.Query.require(&.{.wire}); + var wireIter = world.iter(q); var interactWireID: ?usize = null; var which: usize = 0; while (wireIter.next()) |entityID| { @@ -346,15 +338,16 @@ fn wireManipulationProcess(_: f32, id: usize, pos: *Pos, control: *Control) void const nodes = wire.nodes.constSlice(); const begin = nodes[0].pos; const end = nodes[wire.nodes.len - 1].pos; - var beginDist = distancef(begin, pos.pos + offset); - var endDist = distancef(end, pos.pos + offset); - if (beginDist < minDistance) { - minDistance = beginDist; + var dist = distancef(begin, pos.pos + offset); + if (dist < minDistance) { + minDistance = dist; indicator = .{ .t = .wire, .pos = vec2ftovec2(begin) }; interactWireID = entityID; which = 0; - } else if (endDist < minDistance) { - minDistance = endDist; + } + dist = distancef(end, pos.pos + offset); + if (dist < minDistance) { + minDistance = dist; indicator = .{ .t = .wire, .pos = vec2ftovec2(end) }; interactWireID = entityID; which = wire.nodes.len - 1; @@ -384,13 +377,6 @@ fn distancef(a: Vec2f, b: Vec2f) f32 { return @reduce(.Max, subbed); } -fn is_solid(pos: Vec2) bool { - if (get_tile(pos[0], pos[1])) |tile| { - return tile != 1; - } - return true; -} - fn vec_length(vec: Vec2f) f32 { var squared = vec * vec; return @sqrt(@reduce(.Add, squared)); @@ -430,64 +416,19 @@ fn collideNode(node: *Pos) void { const tileSizef = vec2tovec2f(tileSize); const iPos = vec2ftovec2(node.pos); const mapPos = @divTrunc(iPos, tileSize); - if (is_solid(mapPos)) { + if (map.isSolid(mapPos)) { const velNorm = normalize(node.pos - node.last); var collideVec = node.last; - while (!is_solid(vec2ftovec2((collideVec + velNorm) / tileSizef))) { + while (!map.isSolid(vec2ftovec2((collideVec + velNorm) / tileSizef))) { collideVec += velNorm; } node.pos = collideVec; } } -const Hit = struct { - delta: Vec2f, - normal: Vec2f, - pos: Vec2f, -}; -const mapTileVecf = Vec2f{ 8, 8 }; -const mapTileVec = Vec2{ 8, 8 }; -/// Returns delta -fn collidePointMap(point: Vec2f) ?Hit { - const cell = vec2ftovec2(point / mapTileVecf); - const mapPos = vec2tovec2f(cell * mapTileVec); - const half = (mapTileVecf / @splat(2, @as(f32, 2))); - if (is_solid(cell)) { - const diff = mapPos - point; - const p = half - @fabs(diff); - var delta = Vec2f{ 0, 0 }; - var normal = Vec2f{ 0, 0 }; - var pos = Vec2f{ 0, 0 }; - if (p[0] > p[1]) { - const sx = std.math.copysign(f32, 1, delta[0]); - delta[0] = p[0] * sx; - normal[0] = sx; - pos = Vec2f{ point[0] + (half[0] * sx), point[1] }; - } else { - const sy = std.math.copysign(f32, 1, delta[1]); - delta[1] = p[1] * sy; - normal[1] = sy; - pos = Vec2f{ point[0], point[1] + (half[1] * sy) }; - } - return Hit{ - .delta = delta, - .normal = normal, - .pos = pos, - }; - } - return null; -} - const wireSegmentMaxLength = 4; const wireSegmentMaxLengthV = @splat(2, @as(f32, wireSegmentMaxLength)); -// fn constrainToAnchor(anchor: *Pos, node: *Pos) void { -// var diff = anchor.pos - node.pos; -// var dist = distancef(node.pos, anchor.pos); -// var wireLength = @maximum(wireSegmentMaxLength, dist); -// node.pos = anchor.pos - (normalize(diff) * @splat(2, @as(f32, wireLength))); -// } - fn wireMaxLength(wire: *Wire) f32 { return @intToFloat(f32, wire.nodes.len) * wireSegmentMaxLength; } @@ -600,13 +541,6 @@ fn controlProcess(_: f32, pos: *Pos, control: *Control, physics: *Physics, kinem pos.pos += move; } -/// pos should be in tile coordinates, not world coordinates -fn get_tile(x: i32, y: i32) ?u8 { - if (x < 0 or x > 19 or y < 0 or y > 19) return null; - const i = x + y * 20; - return assets.solid[@intCast(u32, i)]; -} - fn getTile(cell: Vec2) ?u8 { const x = cell[0]; const y = cell[1]; @@ -615,45 +549,10 @@ fn getTile(cell: Vec2) ?u8 { return assets.solid[@intCast(u32, i)]; } -fn cellCollider(cell: Vec2) AABB { - const tileSize = 8; - return AABB{ - .pos = vec2tovec2f(cell * tileSize), - .size = @splat(2, @as(f32, tileSize)), - }; -} - -/// rect should be absolutely positioned. Add pos to kinematic.collider -fn level_collide(rect: AABB) std.BoundedArray(AABB, 9) { - const tileSize = 8; - const top_left = rect.pos / @splat(2, @as(f32, tileSize)); - const bot_right = (rect.pos + rect.size) / @splat(2, @as(f32, tileSize)); - var collisions = std.BoundedArray(AABB, 9).init(0) catch unreachable; - - var i: isize = @floatToInt(i32, top_left[0]); - while (i <= @floatToInt(i32, bot_right[0])) : (i += 1) { - var a: isize = @floatToInt(i32, top_left[1]); - while (a <= @floatToInt(i32, bot_right[1])) : (a += 1) { - var tile = get_tile(i, a); - if (tile == null or tile.? != 1) { - collisions.append(AABB{ - .pos = Vec2f{ - @intToFloat(f32, i * tileSize), - @intToFloat(f32, a * tileSize), - }, - .size = Vec2f{ tileSize, tileSize }, - }) catch unreachable; - } - } - } - - return collisions; -} - fn kinematicProcess(_: f32, pos: *Pos, kinematic: *Kinematic) void { var next = pos.last; next[0] = pos.pos[0]; - var hcol = level_collide(kinematic.col.addv(next)); + var hcol = map.collide(kinematic.col.addv(next)); if (hcol.len > 0) { kinematic.lastCol[0] = next[0] - pos.last[0]; next[0] = pos.last[0]; @@ -662,7 +561,7 @@ fn kinematicProcess(_: f32, pos: *Pos, kinematic: *Kinematic) void { } next[1] = pos.pos[1]; - var vcol = level_collide(kinematic.col.addv(next)); + var vcol = map.collide(kinematic.col.addv(next)); if (vcol.len > 0) { kinematic.lastCol[1] = next[1] - pos.last[1]; next[1] = pos.last[1]; @@ -671,7 +570,7 @@ fn kinematicProcess(_: f32, pos: *Pos, kinematic: *Kinematic) void { } var colPosAbs = next + kinematic.lastCol; - var lastCol = level_collide(kinematic.col.addv(colPosAbs)); + var lastCol = map.collide(kinematic.col.addv(colPosAbs)); if (lastCol.len == 0) { kinematic.lastCol = Vec2f{ 0, 0 }; } diff --git a/src/map.zig b/src/map.zig new file mode 100644 index 0000000..4b8907d --- /dev/null +++ b/src/map.zig @@ -0,0 +1,140 @@ +const std = @import("std"); +const assets = @import("assets"); +const util = @import("util.zig"); +const w4 = @import("wasm4.zig"); + +const Vec2 = util.Vec2; +const Vec2f = util.Vec2f; +const Cell = util.Cell; + +const MAXCELLS = 400; + +const width = 20; +const height = 20; +const tile_width = 8; +const tile_height = 8; +const tile_size = Vec2{ 8, 8 }; +const tile_sizef = Vec2f{ 8, 8 }; +const tilemap_width = 16; +const tilemap_height = 16; +const tilemap_stride = 128; + +alloc: std.mem.Allocator, +tiles: []u8, +offset: Cell, + +pub fn init(offset: Cell, map: []const u8, alloc: std.mem.Allocator) !@This() { + var tiles = try alloc.alloc(u8, MAXCELLS); + var this = @This(){ + .alloc = alloc, + .offset = offset, + .tiles = tiles, + }; + var y: usize = 0; + while (y < height) : (y += 1) { + var x: usize = 0; + while (x < width) : (x += 1) { + const i = x + y * 20; + const a = (@intCast(usize, offset[0]) + x) + (@intCast(usize, offset[1]) + y) * 20; + this.tiles[i] = map[a]; + } + } + return this; +} + +pub fn deinit(this: @This()) void { + this.alloc.free(this.tiles); +} + +pub fn draw(this: @This()) void { + w4.DRAW_COLORS.* = 0x0210; + for (this.tiles) |tilePlus, i| { + const tile = tilePlus - 1; + const t = Vec2{ + @intCast(i32, (tile % tilemap_width) * tile_width), + @intCast(i32, (tile / tilemap_width) * tile_width), + }; + const pos = Vec2{ + @intCast(i32, (i % width) * tile_width), + @intCast(i32, (i / width) * tile_width), + }; + w4.blitSub( + &assets.tiles, + pos, + .{ tile_width, tile_height }, + t, + tilemap_stride, + .{ .bpp = .b2 }, + ); + } +} + +/// pos should be in tile coordinates, not world coordinates +fn getTile(this: @This(), x: i32, y: i32) ?u8 { + if (x < 0 or x > 19 or y < 0 or y > 19) return null; + const i = x + y * 20; + return this.tiles[@intCast(u32, i)]; +} + +pub fn collide(this: @This(), rect: util.AABB) std.BoundedArray(util.AABB, 9) { + const top_left = rect.pos / tile_sizef; + const bot_right = (rect.pos + rect.size) / tile_sizef; + var collisions = std.BoundedArray(util.AABB, 9).init(0) catch unreachable; + + var i: isize = @floatToInt(i32, top_left[0]); + while (i <= @floatToInt(i32, bot_right[0])) : (i += 1) { + var a: isize = @floatToInt(i32, top_left[1]); + while (a <= @floatToInt(i32, bot_right[1])) : (a += 1) { + var tile = this.getTile(i, a); + if (tile == null or tile.? != 1) { + collisions.append(util.AABB{ + .pos = Vec2f{ + @intToFloat(f32, i * tile_width), + @intToFloat(f32, a * tile_height), + }, + .size = tile_sizef, + }) catch unreachable; + } + } + } + + return collisions; +} + +pub fn isSolid(this: @This(), cell: Cell) bool { + if (this.getTile(cell[0], cell[1])) |tile| { + return tile != 1; + } + return true; +} + +// Debug functions + +pub fn trace(this: @This()) void { + var y: usize = 0; + while (y < height) : (y += 1) { + const i = y * width; + w4.trace("{any}", .{this.tiles[i .. i + width]}); + } +} + +pub fn traceDraw(this: @This()) void { + for (this.tiles) |tilePlus, i| { + const tile = tilePlus - 1; + const t = Vec2{ + @intCast(i32, (tile % tilemap_width) * tile_width), + @intCast(i32, (tile / tilemap_width) * tile_width), + }; + const pos = Vec2{ + @intCast(i32, (i % width) * tile_width), + @intCast(i32, (i / width) * tile_width), + }; + w4.trace("{}, {}, {}, {}, {}", .{ + pos, + .{ tile_width, tile_height }, + t, + tilemap_stride, + .{ .bpp = .b2 }, + }); + } +} diff --git a/src/util.zig b/src/util.zig new file mode 100644 index 0000000..bc42c80 --- /dev/null +++ b/src/util.zig @@ -0,0 +1,26 @@ +const std = @import("std"); + +pub const Vec2f = std.meta.Vector(2, f32); +pub const Vec2 = std.meta.Vector(2, i32); +pub const Cell = Vec2; + +pub fn world2cell(vec: Vec2f) Vec2 { + return vec2fToVec2(vec / @splat(2, @as(f32, 8))); +} + +fn vec2ToVec2f(vec2: Vec2) Vec2f { + return Vec2f{ @intToFloat(f32, vec2[0]), @intToFloat(f32, vec2[1]) }; +} + +fn vec2fToVec2(vec2f: Vec2f) Vec2 { + return Vec2{ @floatToInt(i32, vec2f[0]), @floatToInt(i32, vec2f[1]) }; +} + +pub const AABB = struct { + pos: Vec2f, + size: Vec2f, + + pub fn addv(this: @This(), vec2f: Vec2f) @This() { + return @This(){ .pos = this.pos + vec2f, .size = this.size }; + } +};