aboutsummaryrefslogtreecommitdiff
path: root/src/raycast.zig
diff options
context:
space:
mode:
authortslil clingman <>2021-09-01 12:59:50 -0400
committertslil clingman <>2021-09-01 12:59:50 -0400
commit6a2d132873ce7b40405a3dc000a12539fffd969b (patch)
tree3b730360b348900fbc937bd44b89c293c39ca00c /src/raycast.zig
Init
Diffstat (limited to 'src/raycast.zig')
-rw-r--r--src/raycast.zig165
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;
+ }
+ }
+ }
+ }
+};