diff --git a/src/game.zig b/src/game.zig index f5e979c68..10ad1d468 100644 --- a/src/game.zig +++ b/src/game.zig @@ -52,102 +52,22 @@ pub const camera = struct { // MARK: camera }; pub const collision = struct { - pub fn triangleAABB(triangle: [3]Vec3d, box_center: Vec3d, box_extents: Vec3d) bool { - const X = 0; - const Y = 1; - const Z = 2; + pub const Box = struct { + min: Vec3d, + max: Vec3d, - // Translate triangle as conceptually moving AABB to origin - const v0 = triangle[0] - box_center; - const v1 = triangle[1] - box_center; - const v2 = triangle[2] - box_center; - - // Compute edge vectors for triangle - const f0 = triangle[1] - triangle[0]; - const f1 = triangle[2] - triangle[1]; - const f2 = triangle[0] - triangle[2]; - - // Test axis a00 - const a00 = Vec3d{0, -f0[Z], f0[Y]}; - if(!test_axis(a00, v0, v1, v2, box_extents[Y]*@abs(f0[Z]) + box_extents[Z]*@abs(f0[Y]))) { - return false; + pub fn center(self: Box) Vec3d { + return (self.min + self.max)*@as(Vec3d, @splat(0.5)); } - // Test axis a01 - const a01 = Vec3d{0, -f1[Z], f1[Y]}; - if(!test_axis(a01, v0, v1, v2, box_extents[Y]*@abs(f1[Z]) + box_extents[Z]*@abs(f1[Y]))) { - return false; + pub fn extent(self: Box) Vec3d { + return (self.max - self.min)*@as(Vec3d, @splat(0.5)); } - // Test axis a02 - const a02 = Vec3d{0, -f2[Z], f2[Y]}; - if(!test_axis(a02, v0, v1, v2, box_extents[Y]*@abs(f2[Z]) + box_extents[Z]*@abs(f2[Y]))) { - return false; + pub fn intersects(self: Box, other: Box) bool { + return @reduce(.And, (self.max >= other.min)) and @reduce(.And, (self.min <= other.max)); } - - // Test axis a10 - const a10 = Vec3d{f0[Z], 0, -f0[X]}; - if(!test_axis(a10, v0, v1, v2, box_extents[X]*@abs(f0[Z]) + box_extents[Z]*@abs(f0[X]))) { - return false; - } - - // Test axis a11 - const a11 = Vec3d{f1[Z], 0, -f1[X]}; - if(!test_axis(a11, v0, v1, v2, box_extents[X]*@abs(f1[Z]) + box_extents[Z]*@abs(f1[X]))) { - return false; - } - - // Test axis a12 - const a12 = Vec3d{f2[Z], 0, -f2[X]}; - if(!test_axis(a12, v0, v1, v2, box_extents[X]*@abs(f2[Z]) + box_extents[Z]*@abs(f2[X]))) { - return false; - } - - // Test axis a20 - const a20 = Vec3d{-f0[Y], f0[X], 0}; - if(!test_axis(a20, v0, v1, v2, box_extents[X]*@abs(f0[Y]) + box_extents[Y]*@abs(f0[X]))) { - return false; - } - - // Test axis a21 - const a21 = Vec3d{-f1[Y], f1[X], 0}; - if(!test_axis(a21, v0, v1, v2, box_extents[X]*@abs(f1[Y]) + box_extents[Y]*@abs(f1[X]))) { - return false; - } - - // Test axis a22 - const a22 = Vec3d{-f2[Y], f2[X], 0}; - if(!test_axis(a22, v0, v1, v2, box_extents[X]*@abs(f2[Y]) + box_extents[Y]*@abs(f2[X]))) { - return false; - } - - // Test the three axes corresponding to the face normals of AABB - if(@max(v0[X], @max(v1[X], v2[X])) < -box_extents[X] or @min(v0[X], @min(v1[X], v2[X])) > box_extents[X]) { - return false; - } - if(@max(v0[Y], @max(v1[Y], v2[Y])) < -box_extents[Y] or @min(v0[Y], @min(v1[Y], v2[Y])) > box_extents[Y]) { - return false; - } - if(@max(v0[Z], @max(v1[Z], v2[Z])) < -box_extents[Z] or @min(v0[Z], @min(v1[Z], v2[Z])) > box_extents[Z]) { - return false; - } - - // Test separating axis corresponding to triangle face normal - const plane_normal = vec.cross(f0, f1); - const plane_distance = @abs(vec.dot(plane_normal, v0)); - const r = box_extents[X]*@abs(plane_normal[X]) + box_extents[Y]*@abs(plane_normal[Y]) + box_extents[Z]*@abs(plane_normal[Z]); - - return plane_distance <= r; - } - - fn test_axis(axis: Vec3d, v0: Vec3d, v1: Vec3d, v2: Vec3d, r: f64) bool { - const p0 = vec.dot(v0, axis); - const p1 = vec.dot(v1, axis); - const p2 = vec.dot(v2, axis); - const min_p = @min(p0, @min(p1, p2)); - const max_p = @max(p0, @max(p1, p2)); - return @max(-max_p, min_p) <= r; - } + }; const Direction = enum(u2) {x = 0, y = 1, z = 2}; @@ -158,61 +78,21 @@ pub const collision = struct { const model = block.mode().model(block).model(); const pos = Vec3d{@floatFromInt(x), @floatFromInt(y), @floatFromInt(z)}; + const entityCollision = Box{.min = entityPosition - entityBoundingBoxExtent, .max = entityPosition + entityBoundingBoxExtent}; - for(model.neighborFacingQuads) |quads| { - for(quads) |quadIndex| { - const quad = quadIndex.quadInfo(); - if(triangleAABB(.{quad.cornerVec(0) + quad.normalVec() + pos, quad.cornerVec(2) + quad.normalVec() + pos, quad.cornerVec(1) + quad.normalVec() + pos}, entityPosition, entityBoundingBoxExtent)) { - const min = @min(@min(quad.cornerVec(0), quad.cornerVec(1)), @min(quad.cornerVec(2), quad.cornerVec(3))) + quad.normalVec() + pos; - const max = @max(@max(quad.cornerVec(0), quad.cornerVec(1)), @max(quad.cornerVec(2), quad.cornerVec(3))) + quad.normalVec() + pos; - const dist = @min(vec.dot(directionVector, min), vec.dot(directionVector, max)); - if(dist < minDistance) { - resultBox = .{.min = min, .max = max}; - minDistance = dist; - } else if(dist == minDistance) { - resultBox.?.min = @min(resultBox.?.min, min); - resultBox.?.max = @min(resultBox.?.max, max); - } - } - if(triangleAABB(.{quad.cornerVec(1) + quad.normalVec() + pos, quad.cornerVec(2) + quad.normalVec() + pos, quad.cornerVec(3) + quad.normalVec() + pos}, entityPosition, entityBoundingBoxExtent)) { - const min = @min(@min(quad.cornerVec(0), quad.cornerVec(1)), @min(quad.cornerVec(2), quad.cornerVec(3))) + quad.normalVec() + pos; - const max = @max(@max(quad.cornerVec(0), quad.cornerVec(1)), @max(quad.cornerVec(2), quad.cornerVec(3))) + quad.normalVec() + pos; - const dist = @min(vec.dot(directionVector, min), vec.dot(directionVector, max)); - if(dist < minDistance) { - resultBox = .{.min = min, .max = max}; - minDistance = dist; - } else if(dist == minDistance) { - resultBox.?.min = @min(resultBox.?.min, min); - resultBox.?.max = @min(resultBox.?.max, max); - } - } - } - } + for(model.collision) |relativeBlockCollision| { + const blockCollision = Box{.min = relativeBlockCollision.min + pos, .max = relativeBlockCollision.max + pos}; + if(blockCollision.intersects(entityCollision)) { + const dotMin = vec.dot(directionVector, blockCollision.min); + const dotMax = vec.dot(directionVector, blockCollision.max); - for(model.internalQuads) |quadIndex| { - const quad = quadIndex.quadInfo(); - if(triangleAABB(.{quad.cornerVec(0) + pos, quad.cornerVec(2) + pos, quad.cornerVec(1) + pos}, entityPosition, entityBoundingBoxExtent)) { - const min = @min(@min(quad.cornerVec(0), quad.cornerVec(1)), @min(quad.cornerVec(2), quad.cornerVec(3))) + pos; - const max = @max(@max(quad.cornerVec(0), quad.cornerVec(1)), @max(quad.cornerVec(2), quad.cornerVec(3))) + pos; - const dist = @min(vec.dot(directionVector, min), vec.dot(directionVector, max)); - if(dist < minDistance) { - resultBox = .{.min = min, .max = max}; - minDistance = dist; - } else if(dist == minDistance) { - resultBox.?.min = @min(resultBox.?.min, min); - resultBox.?.max = @min(resultBox.?.max, max); - } - } - if(triangleAABB(.{quad.cornerVec(1) + pos, quad.cornerVec(2) + pos, quad.cornerVec(3) + pos}, entityPosition, entityBoundingBoxExtent)) { - const min = @min(@min(quad.cornerVec(0), quad.cornerVec(1)), @min(quad.cornerVec(2), quad.cornerVec(3))) + pos; - const max = @max(@max(quad.cornerVec(0), quad.cornerVec(1)), @max(quad.cornerVec(2), quad.cornerVec(3))) + pos; - const dist = @min(vec.dot(directionVector, min), vec.dot(directionVector, max)); - if(dist < minDistance) { - resultBox = .{.min = min, .max = max}; - minDistance = dist; - } else if(dist == minDistance) { - resultBox.?.min = @min(resultBox.?.min, min); - resultBox.?.max = @min(resultBox.?.max, max); + const distance = @min(dotMin, dotMax); + + if(distance < minDistance) { + resultBox = blockCollision; + minDistance = distance; + } else if(distance == minDistance) { + resultBox = .{.min = @min(resultBox.?.min, blockCollision.min), .max = @max(resultBox.?.max, blockCollision.max)}; } } } @@ -459,18 +339,14 @@ pub const collision = struct { fn isBlockIntersecting(block: Block, posX: i32, posY: i32, posZ: i32, center: Vec3d, extent: Vec3d) bool { const model = block.mode().model(block).model(); const position = Vec3d{@floatFromInt(posX), @floatFromInt(posY), @floatFromInt(posZ)}; - for(model.neighborFacingQuads) |quads| { - for(quads) |quadIndex| { - const quad = quadIndex.quadInfo(); - if(triangleAABB(.{quad.cornerVec(0) + quad.normalVec() + position, quad.cornerVec(2) + quad.normalVec() + position, quad.cornerVec(1) + quad.normalVec() + position}, center, extent) or - triangleAABB(.{quad.cornerVec(1) + quad.normalVec() + position, quad.cornerVec(2) + quad.normalVec() + position, quad.cornerVec(3) + quad.normalVec() + position}, center, extent)) return true; + const entityBox = Box{.min = center - extent, .max = center + extent}; + for(model.collision) |relativeBlockCollision| { + const blockBox = Box{.min = position + relativeBlockCollision.min, .max = position + relativeBlockCollision.max}; + if(blockBox.intersects(entityBox)) { + return true; } } - for(model.internalQuads) |quadIndex| { - const quad = quadIndex.quadInfo(); - if(triangleAABB(.{quad.cornerVec(0) + position, quad.cornerVec(2) + position, quad.cornerVec(1) + position}, center, extent) or - triangleAABB(.{quad.cornerVec(1) + position, quad.cornerVec(2) + position, quad.cornerVec(3) + position}, center, extent)) return true; - } + return false; } @@ -510,19 +386,6 @@ pub const collision = struct { } } } - - pub const Box = struct { - min: Vec3d, - max: Vec3d, - - pub fn center(self: Box) Vec3d { - return (self.min + self.max)*@as(Vec3d, @splat(0.5)); - } - - pub fn extent(self: Box) Vec3d { - return (self.max - self.min)*@as(Vec3d, @splat(0.5)); - } - }; }; pub const Gamemode = enum(u8) {survival = 0, creative = 1}; diff --git a/src/models.zig b/src/models.zig index f62ee49d1..a5c8e9f5c 100644 --- a/src/models.zig +++ b/src/models.zig @@ -7,10 +7,13 @@ const main = @import("main"); const vec = @import("vec.zig"); const Vec3i = vec.Vec3i; const Vec3f = vec.Vec3f; +const Vec3d = vec.Vec3d; const Vec2f = vec.Vec2f; const Mat4f = vec.Mat4f; + const FaceData = main.renderer.chunk_meshing.FaceData; const NeverFailingAllocator = main.heap.NeverFailingAllocator; +const Box = main.game.collision.Box; var quadSSBO: graphics.SSBO = undefined; @@ -40,6 +43,8 @@ const ExtraQuadInfo = struct { }; const gridSize = 4096; +const collisionGridSize = 16; +const CollisionGridInteger = std.meta.Int(.unsigned, collisionGridSize); fn snapToGrid(x: anytype) @TypeOf(x) { const T = @TypeOf(x); @@ -92,6 +97,7 @@ pub const Model = struct { allNeighborsOccluded: bool, noNeighborsOccluded: bool, hasNeighborFacingQuads: bool, + collision: []Box, fn getFaceNeighbor(quad: *const QuadInfo) ?chunk.Neighbor { var allZero: @Vector(3, bool) = .{true, true, true}; @@ -195,9 +201,218 @@ pub const Model = struct { self.allNeighborsOccluded = self.allNeighborsOccluded and self.isNeighborOccluded[neighbor]; self.noNeighborsOccluded = self.noNeighborsOccluded and !self.isNeighborOccluded[neighbor]; } + generateCollision(self, adjustedQuads); return modelIndex; } + fn edgeInterp(y: f32, x0: f32, y0: f32, x1: f32, y1: f32) f32 { + if(y1 == y0) return x0; + return x0 + (x1 - x0)*(y - y0)/(y1 - y0); + } + + fn solveDepth(normal: Vec3f, v0: Vec3f, xIndex: usize, yIndex: usize, zIndex: usize, u: f32, v: f32) f32 { + const nX = normal[xIndex]; + const nY = normal[yIndex]; + const nZ = normal[zIndex]; + + const planeOffset = -vec.dot(v0, normal); + + return (-(nX*u + nY*v + planeOffset))/nZ; + } + + fn rasterize(triangle: [3]Vec3f, grid: *[collisionGridSize][collisionGridSize]CollisionGridInteger, normal: Vec3f) void { + var xIndex: usize = undefined; + var yIndex: usize = undefined; + var zIndex: usize = undefined; + + const v0 = triangle[0]*@as(Vec3f, @splat(@floatFromInt(collisionGridSize))); + const v1 = triangle[1]*@as(Vec3f, @splat(@floatFromInt(collisionGridSize))); + const v2 = triangle[2]*@as(Vec3f, @splat(@floatFromInt(collisionGridSize))); + + const absNormal = @abs(normal); + if(absNormal[0] >= absNormal[1] and absNormal[0] >= absNormal[2]) { + xIndex = 1; + yIndex = 2; + zIndex = 0; + } else if(absNormal[1] >= absNormal[0] and absNormal[1] >= absNormal[2]) { + xIndex = 0; + yIndex = 2; + zIndex = 1; + } else { + xIndex = 0; + yIndex = 1; + zIndex = 2; + } + + const min: Vec3f = @min(v0, v1, v2); + const max: Vec3f = @max(v0, v1, v2); + + const voxelMin: Vec3i = @max(@as(Vec3i, @intFromFloat(@floor(min))), @as(Vec3i, @splat(0))); + const voxelMax: Vec3i = @max(@as(Vec3i, @intFromFloat(@ceil(max))), @as(Vec3i, @splat(0))); + + var p0 = Vec2f{v0[xIndex], v0[yIndex]}; + var p1 = Vec2f{v1[xIndex], v1[yIndex]}; + var p2 = Vec2f{v2[xIndex], v2[yIndex]}; + + if(p0[1] > p1[1]) { + std.mem.swap(Vec2f, &p0, &p1); + } + if(p0[1] > p2[1]) { + std.mem.swap(Vec2f, &p0, &p2); + } + if(p1[1] > p2[1]) { + std.mem.swap(Vec2f, &p1, &p2); + } + + for(@intCast(voxelMin[yIndex])..@intCast(voxelMax[yIndex])) |y| { + if(y >= collisionGridSize) continue; + const yf = std.math.clamp(@as(f32, @floatFromInt(y)) + 0.5, min[yIndex], max[yIndex]); + var xa: f32 = undefined; + var xb: f32 = undefined; + if(yf < p1[1]) { + xa = edgeInterp(yf, p0[0], p0[1], p1[0], p1[1]); + xb = edgeInterp(yf, p0[0], p0[1], p2[0], p2[1]); + } else { + xa = edgeInterp(yf, p1[0], p1[1], p2[0], p2[1]); + xb = edgeInterp(yf, p0[0], p0[1], p2[0], p2[1]); + } + + const xStart: f32 = @min(xa, xb); + const xEnd: f32 = @max(xa, xb); + + const voxelXStart: usize = @intFromFloat(@max(@floor(xStart), 0.0)); + const voxelXEnd: usize = @intFromFloat(@max(@ceil(xEnd), 0.0)); + + for(voxelXStart..voxelXEnd) |x| { + if(x < 0 or x >= collisionGridSize) continue; + const xf = std.math.clamp(@as(f32, @floatFromInt(x)) + 0.5, xStart, xEnd); + + const zf = solveDepth(normal, v0, xIndex, yIndex, zIndex, xf, yf); + if(zf < 0.0) continue; + const z: usize = @intFromFloat(zf); + + if(z >= collisionGridSize) continue; + + const pos: [3]usize = .{x, y, z}; + var realPos: [3]usize = undefined; + realPos[xIndex] = pos[0]; + realPos[yIndex] = pos[1]; + realPos[zIndex] = pos[2]; + grid[realPos[0]][realPos[1]] |= @as(CollisionGridInteger, 1) << @intCast(realPos[2]); + } + } + } + + fn generateCollision(self: *Model, modelQuads: []QuadInfo) void { + var hollowGrid: [collisionGridSize][collisionGridSize]CollisionGridInteger = @splat(@splat(0)); + const voxelSize: Vec3f = @splat(1.0/@as(f32, collisionGridSize)); + + for(modelQuads) |quad| { + var shift = Vec3f{0, 0, 0}; + for(0..3) |i| { + if(@abs(quad.normalVec()[i]) == 1.0 and @floor(quad.corners[0][i]*collisionGridSize) == quad.corners[0][i]*collisionGridSize) { + shift = quad.normalVec()*voxelSize*@as(Vec3f, @splat(0.5)); + } + } + const triangle1: [3]Vec3f = .{ + quad.cornerVec(0) - shift, + quad.cornerVec(1) - shift, + quad.cornerVec(2) - shift, + }; + const triangle2: [3]Vec3f = .{ + quad.cornerVec(1) - shift, + quad.cornerVec(2) - shift, + quad.cornerVec(3) - shift, + }; + + rasterize(triangle1, &hollowGrid, quad.normalVec()); + rasterize(triangle2, &hollowGrid, quad.normalVec()); + } + + const allOnes = ~@as(CollisionGridInteger, 0); + var grid: [collisionGridSize][collisionGridSize]CollisionGridInteger = @splat(@splat(allOnes)); + + var floodfillQueue = main.utils.CircularBufferQueue(struct {x: usize, y: usize, val: CollisionGridInteger}).init(main.stackAllocator, 1024); + defer floodfillQueue.deinit(); + + for(0..collisionGridSize) |x| { + for(0..collisionGridSize) |y| { + var val = 1 | @as(CollisionGridInteger, 1) << (@bitSizeOf(CollisionGridInteger) - 1); + if(x == 0 or x == collisionGridSize - 1 or y == 0 or y == collisionGridSize - 1) val = allOnes; + + floodfillQueue.pushBack(.{.x = x, .y = y, .val = val}); + } + } + + while(floodfillQueue.popFront()) |elem| { + const oldValue = grid[elem.x][elem.y]; + const newValue = oldValue & ~(~hollowGrid[elem.x][elem.y] & elem.val); + if(oldValue == newValue) continue; + grid[elem.x][elem.y] = newValue; + + if(elem.x != 0) floodfillQueue.pushBack(.{.x = elem.x - 1, .y = elem.y, .val = ~newValue}); + if(elem.x != collisionGridSize - 1) floodfillQueue.pushBack(.{.x = elem.x + 1, .y = elem.y, .val = ~newValue}); + if(elem.y != 0) floodfillQueue.pushBack(.{.x = elem.x, .y = elem.y - 1, .val = ~newValue}); + if(elem.y != collisionGridSize - 1) floodfillQueue.pushBack(.{.x = elem.x, .y = elem.y + 1, .val = ~newValue}); + floodfillQueue.pushBack(.{.x = elem.x, .y = elem.y, .val = ~newValue << 1 | ~newValue >> 1}); + } + + var collision: main.List(Box) = .init(main.globalAllocator); + + for(0..collisionGridSize) |x| { + for(0..collisionGridSize) |y| { + while(grid[x][y] != 0) { + const startZ = @ctz(grid[x][y]); + const height = @min(@bitSizeOf(CollisionGridInteger) - startZ, @ctz(~grid[x][y] >> @intCast(startZ))); + const mask = allOnes << @intCast(startZ) & ~((allOnes << 1) << @intCast(height + startZ - 1)); + + const boxMin = Vec3i{@intCast(x), @intCast(y), startZ}; + var boxMax = Vec3i{@intCast(x + 1), @intCast(y + 1), startZ + height}; + + while(canExpand(&grid, boxMin, boxMax, .x, mask)) boxMax[0] += 1; + while(canExpand(&grid, boxMin, boxMax, .y, mask)) boxMax[1] += 1; + disableAll(&grid, boxMin, boxMax, mask); + + const min = @as(Vec3f, @floatFromInt(boxMin))/@as(Vec3f, @splat(collisionGridSize)); + const max = @as(Vec3f, @floatFromInt(boxMax))/@as(Vec3f, @splat(collisionGridSize)); + + collision.append(Box{.min = min, .max = max}); + } + } + } + + self.collision = collision.toOwnedSlice(); + } + + fn allTrue(grid: *const [collisionGridSize][collisionGridSize]CollisionGridInteger, min: Vec3i, max: Vec3i, mask: CollisionGridInteger) bool { + if(max[0] > collisionGridSize or max[1] > collisionGridSize) { + return false; + } + for(@intCast(min[0])..@intCast(max[0])) |x| { + for(@intCast(min[1])..@intCast(max[1])) |y| { + if((grid[x][y] & mask) != mask) { + return false; + } + } + } + return true; + } + + fn disableAll(grid: *[collisionGridSize][collisionGridSize]CollisionGridInteger, min: Vec3i, max: Vec3i, mask: CollisionGridInteger) void { + for(@intCast(min[0])..@intCast(max[0])) |x| { + for(@intCast(min[1])..@intCast(max[1])) |y| { + grid[x][y] &= ~mask; + } + } + } + + fn canExpand(grid: *const [collisionGridSize][collisionGridSize]CollisionGridInteger, min: Vec3i, max: Vec3i, dir: enum {x, y}, mask: CollisionGridInteger) bool { + return switch(dir) { + .x => allTrue(grid, Vec3i{max[0], min[1], min[2]}, Vec3i{max[0] + 1, max[1], max[2]}, mask), + .y => allTrue(grid, Vec3i{min[0], max[1], min[2]}, Vec3i{max[0], max[1] + 1, max[2]}, mask), + }; + } + fn addVert(vert: Vec3f, vertList: *main.List(Vec3f)) usize { const ind = for(vertList.*.items, 0..) |vertex, index| { if(vertex == vert) break index; @@ -386,6 +601,7 @@ pub const Model = struct { main.globalAllocator.free(self.neighborFacingQuads[i]); } main.globalAllocator.free(self.internalQuads); + main.globalAllocator.free(self.collision); } pub fn getRawFaces(model: Model, quadList: *main.List(QuadInfo)) void {