aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md9
-rw-r--r--screenshot.pngbin42650 -> 97917 bytes
-rw-r--r--src/constants.zig2
-rw-r--r--src/level.zig11
-rw-r--r--src/main.zig34
-rw-r--r--src/player.zig233
-rw-r--r--walls.pngbin568 -> 677 bytes
7 files changed, 158 insertions, 131 deletions
diff --git a/README.md b/README.md
index fff984c..25bd71f 100644
--- a/README.md
+++ b/README.md
@@ -18,10 +18,13 @@ In particular alpha blending is implemented in software. This was the result of
- [X] Static sprite objects
- [X] Skyclinder*
- [X] No overdraw (without sprites, skyclinder)
-- [ ] Variable height ceilings
-- [ ] Walls extending from ceiling instead of floor
+- [X] Variable height ceilings
+- [X] Walls extending from ceiling instead of floor
- [ ] Sprites look different from different directions
- [ ] Animated textures
+- [ ] Fluid-like floors
+- [ ] Procedurally generated textures
+
*It's topologically a cylinder, not a box; hardware rendered for now
@@ -37,4 +40,4 @@ The default keybinds are FRST for Forward, Left Strafe, Backward, Right Strafe a
The code is GPLv3+, the artwork is CC-BY-SA 4.0
-I make use of the zig-sfml library, which is Copyright (C) 2021 Guigui220D under the terms of the MIT license. For details see `src/sfml/LICENCE` \ No newline at end of file
+I make use of the zig-sfml library, which is Copyright (C) 2021 Guigui220D under the terms of the MIT license. For details see `src/sfml/LICENCE`
diff --git a/screenshot.png b/screenshot.png
index c5c6444..6026f7f 100644
--- a/screenshot.png
+++ b/screenshot.png
Binary files differ
diff --git a/src/constants.zig b/src/constants.zig
index d363af5..450155f 100644
--- a/src/constants.zig
+++ b/src/constants.zig
@@ -28,5 +28,3 @@ pub const BackTextureWidth: f32 = 1280;
pub const BackTextureHeight: f32 = 240;
pub const TextureDim: f32 = 32.0;
-
-pub const MAX_HEIGHT: f32 = 2;
diff --git a/src/level.zig b/src/level.zig
index 1d61a02..a5d7330 100644
--- a/src/level.zig
+++ b/src/level.zig
@@ -27,12 +27,17 @@ pub const Object = struct {
};
pub const Cell = struct {
- height: f32,
- wall_texture: u8 = 0,
+ floor_height: f32 = 0,
+ ceiling_height : f32 = DEFAULT_HEIGHT,
+ draw_down: bool = false,
+ lower_texture: u8 = 0,
+ upper_texture: u8 = 0,
floor_texture: u8 = 0,
ceiling_texture: u8 = 2,
- const floor = Cell{ .height = 0 };
+ pub const floor = Cell{ };
+
+ pub const DEFAULT_HEIGHT : f32 = 4;
};
pub const Map = struct {
diff --git a/src/main.zig b/src/main.zig
index 5754793..d76e424 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -48,18 +48,34 @@ pub fn main() !void {
var y: u32 = 0;
while (y < 16) : (y += 1) {
- map.cells.items[y * 16 + 00] = level.Cell{ .height = constants.MAX_HEIGHT };
- map.cells.items[y * 16 + 15] = level.Cell{ .height = constants.MAX_HEIGHT, .wall_texture = 2 };
- map.cells.items[15 * 16 + y] = level.Cell{ .height = constants.MAX_HEIGHT };
- map.cells.items[00 * 16 + y] = level.Cell{ .height = constants.MAX_HEIGHT };
+ map.cells.items[y * 16 + 00] = level.Cell{ .floor_height = level.Cell.DEFAULT_HEIGHT };
+ map.cells.items[y * 16 + 15] = level.Cell{ .floor_height = level.Cell.DEFAULT_HEIGHT, .lower_texture = 2 };
+ map.cells.items[15 * 16 + y] = level.Cell{ .floor_height = level.Cell.DEFAULT_HEIGHT };
+ map.cells.items[00 * 16 + y] = level.Cell{ .floor_height = level.Cell.DEFAULT_HEIGHT };
+
+ if (y < 15) {
+ map.cells.items[y * 16 + 01] = level.Cell{
+ .ceiling_height = 3,
+ .upper_texture = 3,
+ .draw_down = true,
+ };
+ }
}
- map.cells.items[16 * 7 + 7] = level.Cell{ .height = constants.MAX_HEIGHT, .wall_texture = 1 };
- map.cells.items[16 * 7 + 6] = level.Cell{ .height = constants.MAX_HEIGHT / 4, .wall_texture = 1, .floor_texture = 1 };
+ map.cells.items[16 * 7 + 7] = level.Cell{ .floor_height = level.Cell.DEFAULT_HEIGHT, .lower_texture = 1 };
+ map.cells.items[16 * 7 + 6] = level.Cell{
+ .floor_height = 0.5,
+ .ceiling_height = 2,
+ .lower_texture = 1,
+ .upper_texture = 1,
+ .floor_texture = 1,
+ .ceiling_texture = 1,
+ .draw_down = true,
+ };
- try map.objects.append(level.Object{ .pos_x = 5.5, .pos_y = 7.5, .pos_z = 1, .texture = 2, .height = 1, .width = 1 });
- try map.objects.append(level.Object{ .pos_x = 4, .pos_y = 8.5, .texture = 1, .height = 1, .width = 1 });
- try map.objects.append(level.Object{ .pos_x = 6.5, .pos_y = 8.5, .texture = 0, .height = 1, .width = 1 });
+ try map.objects.append(level.Object{ .pos_x = 1.5, .pos_y = 7.5, .pos_z = 2, .texture = 2, .height = 1, .width = 1 });
+ try map.objects.append(level.Object{ .pos_x = 4, .pos_y = 8.5, .texture = 0, .height = 1, .width = 1 });
+ try map.objects.append(level.Object{ .pos_x = 6, .pos_y = 8.5, .texture = 1, .height = 1, .width = 1 });
// Initialise SFML
//--------------------------------------------------------------------------
diff --git a/src/player.zig b/src/player.zig
index f6b82b1..c8fc4df 100644
--- a/src/player.zig
+++ b/src/player.zig
@@ -115,13 +115,19 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
const nix = @floatToInt(i32, std.math.floor(next_x + if (self.vel_x > 0) min_dist else -min_dist));
const niy = @floatToInt(i32, std.math.floor(next_y + if (self.vel_y > 0) min_dist else -min_dist));
- if (!map.inBounds(nix, iy) or map.lookup(nix, iy).height > 0) {
+ if (!map.inBounds(nix, iy) or
+ blk: {
+ const cell = map.lookup(nix, iy);
+ if (cell.floor_height > 0) break :blk true;
+ if (cell.ceiling_height < self.height) break :blk true;
+ break :blk false;
+ }) {
next_x = fx + if (self.vel_x > 0) 1 - min_dist else min_dist;
self.vel_x = 0;
self.acc_x = 0;
}
- if (!map.inBounds(ix, niy) or map.lookup(ix, niy).height > 0) {
+ if (!map.inBounds(ix, niy) or map.lookup(ix, niy).floor_height > 0) {
next_y = fy + if (self.vel_y > 0) 1 - min_dist else min_dist;
self.vel_y = 0;
self.acc_y = 0;
@@ -167,13 +173,9 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
var pixels = [_]Colour{Colour.Transparent} ** (PlaneWidth * PlaneHeight);
- // then draw all the walls and populate the z_buffer, while also
- // rendering the surfaces below the horizon to the pixel array
+ // Draw all vertical and horizontal surfaces, and populate the z-buffer
self.renderCells(walls_image, surfaces_image, map, &pixels);
- // then render the ceilings to our pixel array
- self.renderCeilingsToTexture(surfaces_image, map, &pixels);
-
// use the z_buffer to render sprites
self.renderObjects(objects_image, map, &pixels);
@@ -244,7 +246,7 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
tex_frac += tex_frac_step;
}) {
var bottom = std.math.min(top, PlaneHeight);
- var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - bottom, 0)));
+ var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - bottom - 1, 0)));
var texel_y = (top - bottom) / height;
while (pix_y < PlaneHeight and bottom >= top - height) : ({
bottom -= 1;
@@ -252,9 +254,7 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
texel_y += inv_height;
}) {
const index = col * @floatToInt(usize, PlaneHeight) + @floatToInt(usize, bottom);
- if (self.z_buffer[index] < scaled_perp_distance) {
- break;
- } else {
+ if (self.z_buffer[index] > scaled_perp_distance) {
const tx = @floatToInt(c_uint, tex_frac * constants.TextureDim);
const toff = obj.texture * @floatToInt(c_uint, constants.TextureDim);
const ty = @floatToInt(c_uint, texel_y * constants.TextureDim);
@@ -328,10 +328,12 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
dist_x = (self.pos_y - @intToFloat(f32, ipos_y)) * dx_for_y_step;
}
- var top: f32 = undefined;
+ var top_of_floor: f32 = undefined;
+ var bottom_of_ceiling: f32 = undefined;
var distance: f32 = 0;
var still_drawing = true;
- var highest_point: f32 = 0;
+ var highest_drawn: f32 = 0;
+ var lowest_drawn: f32 = PlaneHeight - 1;
var horizontal_hit: bool = undefined;
while (still_drawing and map.inBounds(ipos_x, ipos_y)) : ({
// Find the next cell on our path
@@ -349,19 +351,17 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
}) {
const cell = map.lookup(ipos_x, ipos_y);
- // Is there a wall?
- if (cell.height > 0) {
- // project the top of the wall
- top = PlaneHeight / 2 + PlaneDist * (cell.height - self.height) / distance;
+ // Are we drawing vertical surfaces?
+ if (cell.floor_height > 0 or cell.draw_down) {
+ // project the top of the bottom and the bottom of the top
+ top_of_floor = PlaneHeight / 2 + PlaneDist * (cell.floor_height - self.height) / distance;
+ bottom_of_ceiling = PlaneHeight / 2 + PlaneDist * (cell.ceiling_height - self.height) / distance;
- // Does the wall extend above what we've draw?
- if (top > highest_point) {
-
- // If we reach the top we have to stop!
- if (top > PlaneHeight) {
- still_drawing = false;
- }
+ const draw_lower = top_of_floor > highest_drawn;
+ const draw_upper = cell.draw_down and bottom_of_ceiling < lowest_drawn;
+ // Are we able to see any vertical faces?
+ if (draw_upper or draw_lower) {
// we need the distance to calculate the fractional
// part of the relevant coordinate for texture
// mapping of the walls
@@ -374,60 +374,90 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
if (texfrac == 1) texfrac = 0.9999; // i think this caused a crash at one point
const texstrip = @floatToInt(c_uint, constants.TextureDim * texfrac);
- // which texture index?
- const toff = cell.wall_texture * @floatToInt(c_uint, constants.TextureDim);
-
// height of a unit-height wall at this distance
const nominal_length = PlaneDist / distance;
const inv_nom_len = distance / PlaneDist;
+ // used for texel indexing
- // now we have what we need to draw the wall, and
- // update the z-buffer
- const constrained_top = std.math.min(top, PlaneHeight - 1);
- var zb_y = @floatToInt(i32, constrained_top);
- var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - top, 0)));
- var texel_y = (top - constrained_top) / nominal_length;
- while (zb_y > @floatToInt(i32, highest_point)) : ({
- zb_y -= 1;
- pix_y += 1;
- texel_y += inv_nom_len;
- }) {
- const ty = @floatToInt(c_uint, std.math.modf(texel_y).fpart * constants.TextureDim);
- const texel = walls_image.getPixel(.{ .x = toff + texstrip, .y = ty });
+ const td = @floatToInt(c_uint, constants.TextureDim);
+ if (draw_lower) {
+ // which texture index?
+ const t_lower_off = cell.lower_texture * td;
+ // now we have what we need to draw the face, and
+ // update the z-buffer
+ const constrained_top = std.math.min(top_of_floor, lowest_drawn);
+ var zb_y = @floatToInt(i32, constrained_top);
+ var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_top, 0)));
+ var texel_y = (top_of_floor - constrained_top) / nominal_length;
+ while (zb_y > @floatToInt(i32, highest_drawn)) : ({
+ zb_y -= 1;
+ pix_y += 1;
+ texel_y += inv_nom_len;
+ }) {
+ const ty = @floatToInt(c_uint, std.math.modf(texel_y).fpart * (constants.TextureDim - 1));
+ const texel = walls_image.getPixel(.{ .x = t_lower_off + texstrip, .y = ty });
- const pix_index = pix_y * @floatToInt(usize, PlaneWidth) + col;
- pixels[pix_index] = texel;
+ const pix_index = pix_y * @floatToInt(usize, PlaneWidth) + col;
+ pixels[pix_index] = texel;
- const index = @intCast(usize, col * @floatToInt(i32, PlaneHeight) + zb_y);
- self.z_buffer[index] = distance;
+ const index = @intCast(usize, col * @floatToInt(i32, PlaneHeight) + zb_y);
+ self.z_buffer[index] = distance;
+ }
+ highest_drawn = top_of_floor;
+ }
+
+ if (draw_upper) {
+ const proj_default_end = PlaneHeight / 2 + PlaneDist * (level.Cell.DEFAULT_HEIGHT - self.height) / distance;
+ const stop = @floatToInt(i32, std.math.min(lowest_drawn, proj_default_end));
+ const t_upper_off = cell.upper_texture * td;
+ const constrained_bottom = std.math.max(bottom_of_ceiling, highest_drawn);
+ var zb_y = @floatToInt(i32, constrained_bottom);
+ var pix_y = @floatToInt(usize, std.math.ceil(std.math.max(PlaneHeight - constrained_bottom - 1, 0)));
+ var texel_y: f32 = 0;
+ while (zb_y < stop) : ({
+ zb_y += 1;
+ pix_y -= 1;
+ texel_y += inv_nom_len;
+ }) {
+ const ty = @floatToInt(c_uint, (1 - std.math.modf(texel_y).fpart) * (constants.TextureDim - 1));
+ const texel = walls_image.getPixel(.{ .x = t_upper_off + texstrip, .y = ty });
+
+ const pix_index = pix_y * @floatToInt(usize, PlaneWidth) + col;
+ pixels[pix_index] = texel;
+
+ const index = @intCast(usize, col * @floatToInt(i32, PlaneHeight) + zb_y);
+ self.z_buffer[index] = distance;
+ }
+ lowest_drawn = bottom_of_ceiling;
}
- highest_point = top;
}
}
- // do we potentially draw the top of this cell?
- if (highest_point < PlaneHeight / 2) {
+ // do we potentially draw floor for this cell?
+ if (highest_drawn < PlaneHeight / 2 or (cell.draw_down and lowest_drawn > PlaneHeight / 2)) {
if (dist_y < dist_x) {
distance = dist_y;
} else {
distance = dist_x;
}
- // Note: next_top can never exceed PlaneHeight / 2
- // in the body of the next block. If the wall is
- // taller than us the back edge is lower than the
- // front one so this check will fail as we just drew
- // it (or higher than it). If the wall is shorter
- // then the back edge is at most the horizon.
- const next_top = PlaneHeight / 2 + PlaneDist * (cell.height - self.height) / distance;
+ // Note: next_top can never exceed PlaneHeight / 2 in
+ // the body of the next block. If the wall is taller
+ // than us the back edge is lower than the front one so
+ // this check will fail as we just drew it (or higher
+ // than it). If the wall is shorter then the back edge
+ // is at most the horizon. Similarly so for next_bottom
+ const next_top = PlaneHeight / 2 + PlaneDist * (cell.floor_height - self.height) / distance;
+ const next_bottom = PlaneHeight / 2 + PlaneDist * (cell.ceiling_height - self.height) / distance;
- // only if we can see some part of it
- if (next_top > highest_point) {
- top = highest_point;
- while (top <= next_top and top < PlaneHeight / 2) : (top += 1) {
- const row_dist = (self.height - cell.height) * PlaneDist / (PlaneHeight / 2 - top);
- const ptop = @floatToInt(usize, top + 1);
- const itop = @floatToInt(usize, PlaneHeight) - ptop;
+ // draw floor?
+ if (next_top > highest_drawn) {
+ top_of_floor = std.math.floor(highest_drawn);
+ const thresh = std.math.min(std.math.min(next_top, lowest_drawn), PlaneHeight / 2) - 1;
+ while (top_of_floor <= thresh) : (top_of_floor += 1) {
+ const row_dist = (self.height - cell.floor_height) * PlaneDist / (PlaneHeight / 2 - top_of_floor);
+ const ptop = @floatToInt(usize, top_of_floor);
+ const itop = @floatToInt(usize, PlaneHeight) - ptop - 1;
// draw the correct pixel
const sx = std.math.modf(self.pos_x + row_dist * cosra);
@@ -439,69 +469,44 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
pixels[itop * @floatToInt(usize, PlaneWidth) + col] = val;
// record in the z_buffer only if we're above the floor!
- if (cell.height > 0) {
+ if (cell.floor_height > 0) {
const index = col * @floatToInt(usize, PlaneHeight) + ptop;
self.z_buffer[index] = row_dist;
}
}
- highest_point = next_top;
+ highest_drawn = next_top;
}
- }
- }
- }
- }
-
- fn renderCeilingsToTexture(
- self: @This(),
- surfaces_image: Image,
- map: level.Map,
- pixels: []Colour,
- ) void {
- // Again, another TERRIBLE hack: we do the same nasty linear
- // interpolation trick and for whatever reason the floors look fine.
- const cos_first = std.math.cos(self.ang + 0.5 * FOV);
- const sin_first = std.math.sin(self.ang + 0.5 * FOV);
- const cos_last = std.math.cos(self.ang - 0.5 * FOV);
- const sin_last = std.math.sin(self.ang - 0.5 * FOV);
-
- var row: usize = 0;
- while (row < PlaneHeight / 2) : (row += 1) {
- const frow = (PlaneHeight / 2 - @intToFloat(f32, row));
- const row_dist = (constants.MAX_HEIGHT - self.height) * PlaneDist / frow;
-
- const dx_step = row_dist * (cos_last - cos_first) / PlaneWidth;
- const dy_step = row_dist * (sin_last - sin_first) / PlaneWidth;
-
- var col: usize = 0;
- var dx = row_dist * cos_first;
- var dy = row_dist * sin_first;
- while (col < PlaneWidth) : ({
- col += 1;
- dx += dx_step;
- dy += dy_step;
- }) {
- const x = self.pos_x + dx;
- const y = self.pos_y + dy;
-
- const sx = std.math.modf(x);
- const sy = std.math.modf(y);
-
- const ix = @floatToInt(i32, sx.ipart);
- const iy = @floatToInt(i32, sy.ipart);
-
- const index = col * @floatToInt(usize, PlaneHeight) + @floatToInt(usize, PlaneHeight - 1) - row;
- if (map.inBounds(ix, iy) and row_dist < self.z_buffer[index]) {
- const cell = map.lookup(ix, iy);
- const toff = cell.ceiling_texture * @floatToInt(c_uint, constants.TextureDim);
- const px = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sx.fpart));
- const py = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sy.fpart));
+ // draw ceiling?
+ if (cell.draw_down and next_bottom < lowest_drawn) {
+ bottom_of_ceiling = std.math.ceil(lowest_drawn);
+ const thresh = std.math.max(std.math.max(next_bottom, highest_drawn), PlaneHeight / 2) - 1;
+ while (bottom_of_ceiling >= thresh) : (bottom_of_ceiling -= 1) {
+ const row_dist = (cell.ceiling_height - self.height) * PlaneDist / (bottom_of_ceiling - PlaneHeight / 2);
+ const ptop = @floatToInt(usize, bottom_of_ceiling);
+ const itop = @floatToInt(usize, PlaneHeight) - ptop;
- const val = surfaces_image.getPixel(.{ .x = toff + px, .y = py });
+ const sx = std.math.modf(self.pos_x + row_dist * cosra);
+ const sy = std.math.modf(self.pos_y + row_dist * sinra);
+ const toff = cell.ceiling_texture * @floatToInt(c_uint, constants.TextureDim);
+ const px = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sx.fpart));
+ const py = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sy.fpart));
+ const val = surfaces_image.getPixel(.{ .x = toff + px, .y = py });
+ pixels[itop * @floatToInt(usize, PlaneWidth) + col] = val;
- pixels[row * @floatToInt(usize, PlaneWidth) + col] = val;
+ if (cell.draw_down) {
+ const index = col * @floatToInt(usize, PlaneHeight) + ptop;
+ self.z_buffer[index] = row_dist;
+ }
+ }
+ lowest_drawn = next_bottom;
+ }
}
}
+ // Have we filled this column?
+ if (top_of_floor > lowest_drawn or bottom_of_ceiling < highest_drawn) {
+ still_drawing = false;
+ }
}
}
};
diff --git a/walls.png b/walls.png
index a4ff902..ca89c86 100644
--- a/walls.png
+++ b/walls.png
Binary files differ