From e4baa0810748bd3de93d1eaf216288ad67a86295 Mon Sep 17 00:00:00 2001 From: tslil clingman <> Date: Fri, 3 Sep 2021 21:09:07 -0400 Subject: Working on floor casting, but it's not quite right The way floors are currently rendered the textures will always appear ``under'' the walls. This is not intended for shorter walls, the floor should be atop them. I might be able to fix this by switching to vertical scan-line rendering of floors -- although i understand that that is less efficient -- and doing this at the same time as wall rendering. --- src/raycast.zig | 353 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 209 insertions(+), 144 deletions(-) (limited to 'src/raycast.zig') diff --git a/src/raycast.zig b/src/raycast.zig index 608bb10..aca7db0 100644 --- a/src/raycast.zig +++ b/src/raycast.zig @@ -19,8 +19,12 @@ const std = @import("std"); const RenderWindow = @import("sfml").graphics.RenderWindow; const Sprite = @import("sfml").graphics.Sprite; +const Texture = @import("sfml").graphics.Texture; +const Image = @import("sfml").graphics.Image; +const Colour = @import("sfml").graphics.Color; usingnamespace @import("map.zig"); +usingnamespace @import("renderConstants.zig"); pub const RenderWallFunction: type = fn ( window: RenderWindow, @@ -33,165 +37,226 @@ pub const RenderWallFunction: type = fn ( texture: u8, // which texture index ) void; -pub const Player = struct { - pos_x: f32, - pos_y: f32, - ang: f32, - vel_x: f32 = 0, - vel_y: f32 = 0, - acc_x: f32 = 0, - acc_y: f32 = 0, - fov: f32 = std.math.pi / 3.0, - height: f32 = 1.7, // TODO - - plane_height: f32, - plane_width: i32, - plane_dist: f32, - - pub fn new(pos_x: f32, pos_y: f32, ang: f32, plane_width: i32, plane_height: i32) Player { - const fov: f32 = std.math.pi / 3.0; - return Player{ - // standing still at the given location, looking in direction ang, - .pos_x = pos_x, - .pos_y = pos_y, - .ang = ang, - // plane of projection - .plane_width = plane_width, - .plane_height = @intToFloat(f32, plane_height), - // given the desired width of the image, how far away must - // the projection plane be from the camera? - .plane_dist = @intToFloat(f32, plane_width) / (2 * std.math.tan(fov / 2)), - }; - } - - pub fn tick(self: *Player) void { - const dt = 1 / 30.0; - const v_min = 0.8; - const v_decay = 1.25; - - self.pos_x += self.vel_x * dt; - self.pos_y += self.vel_y * dt; - - self.vel_x /= v_decay; - if (std.math.fabs(self.vel_x) < v_min) self.vel_x = 0; - self.vel_y /= v_decay; - if (std.math.fabs(self.vel_y) < v_min) self.vel_y = 0; - - self.vel_x += self.acc_x * dt; - self.vel_y += self.acc_y * dt; - } - - pub fn renderMapUsing( - self: Player, - window: RenderWindow, - wallSprite: Sprite, - map: Map, - // the abstract the rendering call - renderWall: RenderWallFunction, - ) void { - self.renderWalls(window, wallSprite, map, renderWall); - } - - fn renderWalls(self: Player, window: RenderWindow, wallSprite: Sprite, map: Map, renderWall: RenderWallFunction) void { - const floor = std.math.floor; - - var col: i32 = 0; - while (col < self.plane_width) : (col += 1) { - const horiz_frac = @intToFloat(f32, col) / (@intToFloat(f32, self.plane_width) - 1); - - const ra = (0.5 - horiz_frac) * self.fov + self.ang; - - const cosra = std.math.cos(ra); - const sinra = std.math.sin(ra); - - // Observe that sqrt(1+tan^2) = abs(1/cos) sqrt(cos^2+sin^2) = - // abs(1/cos). Similarly so for cot, hence we obtain the following - // lengths for the hypotenuses assuming that x (respectively y) are - // unit length and the angle is ra. - const dy_for_x_step = std.math.fabs(1 / cosra); - const dx_for_y_step = std.math.fabs(1 / sinra); - - var step_x: i32 = -1; - var step_y: i32 = -1; - - var dist_x: f32 = undefined; - var dist_y: f32 = undefined; - - var ipos_x: i32 = @floatToInt(i32, floor(self.pos_x)); - var ipos_y: i32 = @floatToInt(i32, floor(self.pos_y)); - - // looking right - if (cosra >= 0) { - step_x = 1; - // assuming unit size grid cells - dist_y = (@intToFloat(f32, ipos_x) + 1 - self.pos_x) * dy_for_x_step; - } else { - dist_y = (self.pos_x - @intToFloat(f32, ipos_x)) * dy_for_x_step; - } +pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { + return struct { + pos_x: f32, + pos_y: f32, + ang: f32, + vel_x: f32 = 0, + vel_y: f32 = 0, + acc_x: f32 = 0, + acc_y: f32 = 0, + fov: f32 = std.math.pi / 3.0, + height: f32 = 1.8, // TODO + // z_buffer: std.BoundedArray(f32, PlaneWidth * PlaneHeight), + + const FOV: f32 = std.math.pi / 3.0; + const PlanePixels = PlaneWidth * PlaneHeight; + // given the desired width of the image, how far away must + // the projection plane be from the camera? + const PlaneDist = PlaneWidth / (2 * std.math.tan(FOV / 2)); + + // standing still at the given location, looking in direction ang, + pub fn new(pos_x: f32, pos_y: f32, ang: f32) !@This() { + // const infs = [_]f32{std.math.inf(f32)} ** PlanePixels; + return Player(PlaneWidth, PlaneHeight){ + .pos_x = pos_x, + .pos_y = pos_y, + .ang = ang, + // TODO: is there some clever way to avoid this long name? + // .z_buffer = try std.BoundedArray(f32, PlanePixels).fromSlice(&infs), + }; + } - if (sinra >= 0) { - step_y = 1; - dist_x = (@intToFloat(f32, ipos_y) + 1 - self.pos_y) * dx_for_y_step; - } else { - dist_x = (self.pos_y - @intToFloat(f32, ipos_y)) * dx_for_y_step; - } + pub fn tick(self: *@This()) void { + const dt = 1 / 30.0; + const v_min = 0.8; + const v_decay = 1.25; + + self.pos_x += self.vel_x * dt; + self.pos_y += self.vel_y * dt; - var distance: f32 = 0; - var still_drawing = true; - var highest_point: f32 = 0; - var horizontal_hit: bool = undefined; - while (still_drawing) { - - // Find the next cell on our path - if (dist_y < dist_x) { - horizontal_hit = false; - distance = dist_y; - dist_y += dy_for_x_step; - ipos_x += step_x; + self.vel_x /= v_decay; + if (std.math.fabs(self.vel_x) < v_min) self.vel_x = 0; + self.vel_y /= v_decay; + if (std.math.fabs(self.vel_y) < v_min) self.vel_y = 0; + + self.vel_x += self.acc_x * dt; + self.vel_y += self.acc_y * dt; + } + + pub fn renderWorld( + self: *@This(), + window: RenderWindow, + walls_sprite: Sprite, + map: Map, + // the abstract the rendering call + renderWall: RenderWallFunction, + ) void { + // var i: usize = 0; + // while (i < self.z_buffer.len) : (i += 1) { + // self.z_buffer.set(i, std.math.inf(f32)); + // } + self.renderWalls(window, walls_sprite, map, renderWall); + } + + fn renderWalls( + self: *@This(), + window: RenderWindow, + walls_sprite: Sprite, + map: Map, + renderWall: RenderWallFunction, + ) void { + const floor = std.math.floor; + + var col: i32 = 0; + var ra: f32 = 0.5 * FOV + self.ang; + const ra_step = FOV / PlaneWidth; + while (col < PlaneWidth) : ({ + col += 1; + ra -= ra_step; + }) { + const cosra = std.math.cos(ra); + const sinra = std.math.sin(ra); + + // Observe that sqrt(1+tan^2) = abs(1/cos) sqrt(cos^2+sin^2) = + // abs(1/cos). Similarly so for cot, hence we obtain the following + // lengths for the hypotenuses assuming that x (respectively y) are + // unit length and the angle is ra. + const dy_for_x_step = std.math.fabs(1 / cosra); + const dx_for_y_step = std.math.fabs(1 / sinra); + + var step_x: i32 = -1; + var step_y: i32 = -1; + + var dist_x: f32 = undefined; + var dist_y: f32 = undefined; + + var ipos_x: i32 = @floatToInt(i32, floor(self.pos_x)); + var ipos_y: i32 = @floatToInt(i32, floor(self.pos_y)); + + // looking right + if (cosra >= 0) { + step_x = 1; + // assuming unit size grid cells + dist_y = (@intToFloat(f32, ipos_x) + 1 - self.pos_x) * dy_for_x_step; } else { - horizontal_hit = true; - distance = dist_x; - dist_x += dx_for_y_step; - ipos_y += step_y; + dist_y = (self.pos_x - @intToFloat(f32, ipos_x)) * dy_for_x_step; } - if (!map.inBounds(ipos_x, ipos_y)) break; + if (sinra >= 0) { + step_y = 1; + dist_x = (@intToFloat(f32, ipos_y) + 1 - self.pos_y) * dx_for_y_step; + } else { + dist_x = (self.pos_y - @intToFloat(f32, ipos_y)) * dx_for_y_step; + } - var cell = map.lookup(ipos_x, ipos_y); + var distance: f32 = 0; + var still_drawing = true; + var highest_point: f32 = 0; + var horizontal_hit: bool = undefined; + while (still_drawing and map.inBounds(ipos_x, ipos_y)) : ({ + // Find the next cell on our path + if (dist_y < dist_x) { + horizontal_hit = false; + distance = dist_y; + dist_y += dy_for_x_step; + ipos_x += step_x; + } else { + horizontal_hit = true; + distance = dist_x; + dist_x += dx_for_y_step; + ipos_y += step_y; + } + }) { + var cell = map.lookup(ipos_x, ipos_y); + + // the correct distance is the shortest distance from the plane + // of projection to the point, that is, perpendicular distance + const perp_distance = distance * std.math.cos(self.ang - ra); + + // project the top of the wall + const top = PlaneHeight / 2 + PlaneDist * (cell.height - self.height) / perp_distance; + + // We have a wall to draw if it protrudes above what we have so far drawn + if (top > highest_point) { + + // did we extend beyond the top of the plane? + if (top > PlaneHeight) still_drawing = false; + + // compute the height of this wall + const total_length = PlaneDist * cell.height / perp_distance; + + // as well as the fraction we'll be drawing + const draw_length = top - highest_point; + const draw_frac = std.math.clamp(draw_length / total_length, 0, 1); + + // we need the raw Euclidean distance to calculate the + // fractional part of the relevant coordinate for texture + // mapping of the walls + const hit_coordinate = if (horizontal_hit) distance * cosra + self.pos_x else distance * sinra + self.pos_y; + var texfrac = std.math.modf(hit_coordinate).fpart; + // we also want to be sure that we're consistently orienting + // textures, in this case clockwise + if ((horizontal_hit and sinra < 0) or (!horizontal_hit and cosra > 0)) texfrac = 1 - texfrac; + + // draw the wall + renderWall(window, walls_sprite, col, top, total_length, draw_frac, texfrac, cell.wall_texture); + + // record that there's a wall here in the z_buffer + // var y = @floatToInt(i32, top); + // while (y > @floatToInt(i32, highest_point)) : (y -= 1) { + // const index = @intCast(usize, col * @floatToInt(i32, PlaneHeight) + y); + // self.z_buffer.set(index, perp_distance); + // } + + highest_point = top; + } + } + } + } - // the correct distance is the shortest distance from the plane - // of projection to the point, that is, perpendicular distance - const perp_distance = distance * std.math.cos(self.ang - ra); + pub fn renderFloorsToTexture(self: @This(), floors_image: Image, rendered_floors_texture: Texture, map: Map) !void { + var pixels = [_]Colour{Colour.Black} ** (PlaneWidth * PlaneHeight / 2); - // project the top of the wall - const top = self.plane_height / 2 + self.plane_dist * (cell.height - self.height) / perp_distance; + const ang_step = FOV / PlaneWidth; + var row: usize = 0; + while (row < PlaneHeight / 2) : (row += 1) { + const row_dist = self.height * PlaneDist / @intToFloat(f32, row + 1); - // We have a wall to draw if it protrudes above what we have so far drawn - if (top > highest_point) { + var col: usize = 0; + var ang = 0.5 * FOV + self.ang; + var ang_diff: f32 = 0.5 * FOV; - // did we extend beyond the top of the plane? - if (top > self.plane_height) still_drawing = false; + while (col < PlaneWidth) : ({ + col += 1; + ang -= ang_step; + ang_diff -= ang_step; + }) { + const perp_dist = row_dist / std.math.cos(ang_diff); + const x = self.pos_x + perp_dist * std.math.cos(ang); + const y = self.pos_y + perp_dist * std.math.sin(ang); - // compute the height of this wall - const total_length = self.plane_dist * cell.height / perp_distance; + const sx = std.math.modf(x); + const sy = std.math.modf(y); - // as well as the fraction we'll be drawing - const draw_frac = std.math.min(1, (top - highest_point) / total_length); + const ix = @floatToInt(i32, sx.ipart); + const iy = @floatToInt(i32, sy.ipart); - // we need the raw Euclidean distance to calculate the - // fractional part of the relevant coordinate for texture - // mapping of the walls - const hit_coordinate = if (horizontal_hit) distance * cosra + self.pos_x else distance * sinra + self.pos_y; - var texfrac = std.math.modf(hit_coordinate).fpart; - // we also want to be sure that we're consistently orienting - // textures, in this case clockwise - if ((horizontal_hit and sinra < 0) or (!horizontal_hit and cosra > 0)) texfrac = 1 - texfrac; + if (map.inBounds(ix, iy)) { + const tex = @as(c_uint, map.lookup(ix, iy).floor_texture); + const toff = tex * @floatToInt(c_uint, 1 + TextureDim); + const px = @floatToInt(c_uint, TextureDim * std.math.fabs(sx.fpart)); + const py = @floatToInt(c_uint, TextureDim * std.math.fabs(sy.fpart)); - renderWall(window, wallSprite, col, top, total_length, draw_frac, texfrac, cell.wall_texture); + const val = floors_image.getPixel(.{ .x = toff + px, .y = py }); - highest_point = top; + pixels[row * @floatToInt(usize, PlaneWidth) + col] = val; + } } } + + try rendered_floors_texture.updateFromPixels(&pixels, null); } - } -}; + }; +} -- cgit v1.3.1