diff options
| author | tslil clingman <> | 2021-09-01 12:59:50 -0400 |
|---|---|---|
| committer | tslil clingman <> | 2021-09-01 12:59:50 -0400 |
| commit | 6a2d132873ce7b40405a3dc000a12539fffd969b (patch) | |
| tree | 3b730360b348900fbc937bd44b89c293c39ca00c /src/raycast.zig | |
Init
Diffstat (limited to 'src/raycast.zig')
| -rw-r--r-- | src/raycast.zig | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/src/raycast.zig b/src/raycast.zig new file mode 100644 index 0000000..47377a5 --- /dev/null +++ b/src/raycast.zig @@ -0,0 +1,165 @@ +const std = @import("std"); + +const RenderWindow = @import("sfml").graphics.RenderWindow; +const Sprite = @import("sfml").graphics.Sprite; + +usingnamespace @import("map.zig"); + +pub const RenderWallFunction: type = fn ( + window: RenderWindow, + sprite: Sprite, + col: i32, // which column we're in + top: f32, // top of wall + length: f32, + texfrac: f32, + texture: u8, // which texture index +) anyerror!void; // TODO: narrow this error type. + +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, + sprite: Sprite, + map: Map, + // the abstract the rendering call + 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; + } + + 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 distance: f32 = 0; + var still_drawing = true; + 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; + } else { + horizontal_hit = true; + distance = dist_x; + dist_x += dx_for_y_step; + ipos_y += step_y; + } + + if (!map.inBounds(ipos_x, ipos_y)) break; + + var cell = map.lookup(ipos_x, ipos_y); + + // We have a wall to draw + if (cell.height > 0) { + // before we correct the distance to be the perpedicular + // distance from the plane of projection, we need to use it + // to calculate the fractional part of the relevant + // coordinate + 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 these, say clockwise + if ((horizontal_hit and sinra < 0) or (!horizontal_hit and cosra > 0)) texfrac = 1 - texfrac; + + // now correct the distance to be the shortest distance from + // the plane to the point, that is, perpendicular distance + distance *= std.math.cos(self.ang - ra); + + // project the top of the wall + const top = self.plane_height / 2 + self.plane_dist * (cell.height - self.height) / distance; + const height = self.plane_dist * cell.height / distance; + + try renderWall(window, sprite, col, top, height, texfrac, cell.wall_texture); + + still_drawing = false; + } + } + } + } +}; |
