aboutsummaryrefslogtreecommitdiff
path: root/src/raycast.zig
diff options
context:
space:
mode:
authortslil clingman <>2021-09-03 21:09:07 -0400
committertslil clingman <>2021-09-04 11:36:32 -0400
commite4baa0810748bd3de93d1eaf216288ad67a86295 (patch)
tree7f1117a7dd5299cffbeb98a3bfea28073c7d83a2 /src/raycast.zig
parentde255063b234871021da004d32964e25d549d3e4 (diff)
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.
Diffstat (limited to 'src/raycast.zig')
-rw-r--r--src/raycast.zig323
1 files changed, 194 insertions, 129 deletions
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
+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),
- plane_height: f32,
- plane_width: i32,
- plane_dist: f32,
+ 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));
- 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)),
- };
- }
+ // 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),
+ };
+ }
- pub fn tick(self: *Player) void {
- const dt = 1 / 30.0;
- const v_min = 0.8;
- const v_decay = 1.25;
+ 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;
+ 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 /= 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;
- }
+ 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);
- }
+ 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: Player, window: RenderWindow, wallSprite: Sprite, map: Map, renderWall: RenderWallFunction) void {
- const floor = std.math.floor;
+ fn renderWalls(
+ self: *@This(),
+ window: RenderWindow,
+ walls_sprite: 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);
+ 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);
- const ra = (0.5 - horiz_frac) * self.fov + self.ang;
+ // 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);
- const cosra = std.math.cos(ra);
- const sinra = std.math.sin(ra);
+ var step_x: i32 = -1;
+ var step_y: i32 = -1;
- // 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 dist_x: f32 = undefined;
+ var dist_y: f32 = undefined;
- var step_x: i32 = -1;
- var step_y: i32 = -1;
+ var ipos_x: i32 = @floatToInt(i32, floor(self.pos_x));
+ var ipos_y: i32 = @floatToInt(i32, floor(self.pos_y));
- var dist_x: f32 = undefined;
- var dist_y: f32 = undefined;
+ // 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;
+ }
- var ipos_x: i32 = @floatToInt(i32, floor(self.pos_x));
- var ipos_y: i32 = @floatToInt(i32, floor(self.pos_y));
+ 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;
+ }
- // 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;
- }
+ 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);
- 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;
- }
+ // 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);
- var distance: f32 = 0;
- var still_drawing = true;
- var highest_point: f32 = 0;
- var horizontal_hit: bool = undefined;
- while (still_drawing) {
+ // project the top of the wall
+ const top = PlaneHeight / 2 + PlaneDist * (cell.height - self.height) / perp_distance;
- // 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;
- }
+ // 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;
- if (!map.inBounds(ipos_x, ipos_y)) break;
+ // draw the wall
+ renderWall(window, walls_sprite, col, top, total_length, draw_frac, texfrac, cell.wall_texture);
- var cell = map.lookup(ipos_x, ipos_y);
+ // 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);
+ // }
- // 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);
+ highest_point = top;
+ }
+ }
+ }
+ }
- // project the top of the wall
- const top = self.plane_height / 2 + self.plane_dist * (cell.height - self.height) / perp_distance;
+ pub fn renderFloorsToTexture(self: @This(), floors_image: Image, rendered_floors_texture: Texture, map: Map) !void {
+ var pixels = [_]Colour{Colour.Black} ** (PlaneWidth * PlaneHeight / 2);
- // We have a wall to draw if it protrudes above what we have so far drawn
- if (top > highest_point) {
+ 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);
- // did we extend beyond the top of the plane?
- if (top > self.plane_height) still_drawing = false;
+ var col: usize = 0;
+ var ang = 0.5 * FOV + self.ang;
+ var ang_diff: f32 = 0.5 * FOV;
- // compute the height of this wall
- const total_length = self.plane_dist * cell.height / perp_distance;
+ 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);
- // as well as the fraction we'll be drawing
- const draw_frac = std.math.min(1, (top - highest_point) / total_length);
+ const sx = std.math.modf(x);
+ const sy = std.math.modf(y);
- // 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;
+ const ix = @floatToInt(i32, sx.ipart);
+ const iy = @floatToInt(i32, sy.ipart);
- renderWall(window, wallSprite, col, top, total_length, draw_frac, texfrac, cell.wall_texture);
+ 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));
- highest_point = top;
+ const val = floors_image.getPixel(.{ .x = toff + px, .y = py });
+
+ pixels[row * @floatToInt(usize, PlaneWidth) + col] = val;
+ }
}
}
+
+ try rendered_floors_texture.updateFromPixels(&pixels, null);
}
- }
-};
+ };
+}