aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.zig40
-rw-r--r--src/render.zig372
2 files changed, 175 insertions, 237 deletions
diff --git a/src/main.zig b/src/main.zig
index e542a0a..794a3e3 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -57,7 +57,6 @@ pub fn main() !void {
.{ 1.00, 0.00 },
.{ 1.00, 1.00 },
.{ 0.00, 1.00 },
- .{ 0.00, 0.00 },
};
var verts = try std.BoundedArray([2]f32, level.Layer.MAX_VERTS).fromSlice(vert_coords0[0..]);
var layer = level.Layer{ .height = 0.5, .vertices = verts };
@@ -79,50 +78,13 @@ pub fn main() !void {
.{ 0.5772543, 0.26223588 },
.{ 0.6672827, 0.3142138 },
.{ 0.7283864, 0.39831588 },
- .{ 0.75, 0.50 },
};
verts = try std.BoundedArray([2]f32, level.Layer.MAX_VERTS).fromSlice(vert_coords1[0..]);
- layer = level.Layer{ .height = 1, .vertices = verts, .vertical_texture = 3 };
+ layer = level.Layer{ .height = 1, .vertices = verts, .vertical_texture = 3, .horizontal_texture = 2 };
try map.cells.items[07 * 16 + y].lower_layers.append(layer);
- // 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{
- // .floor_height = 0.2,
- // .ceiling_height = 2,
- // .lower_texture = 1,
- // .upper_texture = 1,
- // .floor_texture = 1,
- // .ceiling_texture = 2,
- // .draw_down = true,
- // .vertices = &[_][2]f32{
- // .{ 0.00, 0.50 },
- // .{ 0.25, 0.66 },
- // .{ 0.25, 1.00 },
- // .{ 0.50, 0.66 },
- // .{ 0.75, 1.00 },
- // .{ 0.75, 0.66 },
- // .{ 1.00, 0.50 },
- // .{ 0.75, 0.33 },
- // .{ 0.75, 0.00 },
- // .{ 0.50, 0.33 },
- // .{ 0.25, 0.00 },
- // .{ 0.25, 0.33 },
- // .{ 0.00, 0.50 },
- // },
- // };
-
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 });
diff --git a/src/render.zig b/src/render.zig
index f81ce4f..20b879f 100644
--- a/src/render.zig
+++ b/src/render.zig
@@ -27,126 +27,6 @@ const level = @import("level.zig");
const constants = @import("constants.zig");
const player = @import("player.zig");
-fn playerDistComp(pos: [2]f32, lhs: level.Object, rhs: level.Object) bool {
- const lx = lhs.pos_x - pos[0];
- const ly = lhs.pos_y - pos[1];
- const rx = rhs.pos_x - pos[0];
- const ry = rhs.pos_y - pos[1];
-
- return (lx * lx + ly * ly > rx * rx + ry * ry);
-}
-
-fn fasterColourBlend(onto: Colour, from: Colour) Colour {
- const af: u16 = from.a;
- const of: u16 = onto.a;
-
- const ablend: u16 = @divTrunc(of * (255 - af), 255);
- const na: u16 = af + ablend;
- if (na == 0) return Colour.Black;
-
- const rf: u16 = from.r;
- const ro: u16 = onto.r;
- const gf: u16 = from.g;
- const go: u16 = onto.g;
- const bf: u16 = from.b;
- const bo: u16 = onto.b;
-
- // The most accurate i've found is
- const nr = @divTrunc(ro * ablend + rf * af, na);
- const ng = @divTrunc(go * ablend + gf * af, na);
- const nb = @divTrunc(bo * ablend + bf * af, na);
-
- // These computations are incorrect, but faster
- // const nr = (af * rf + (255 - af) * ro) / 255;
- // const ng = (af * gf + (255 - af) * go) / 255;
- // const nb = (af * bf + (255 - af) * bo) / 255;
-
- return Colour{
- .a = @intCast(u8, na),
- .r = @intCast(u8, nr),
- .g = @intCast(u8, ng),
- .b = @intCast(u8, nb),
- };
-}
-
-const LocalCoordHit = struct {
- local_dist: f32,
- texture_frac: f32,
-};
-
-// The primary observation is: if a line segment AB disconnects the unit square,
-// then it intersects another line segment CD in that square precisely when the
-// C and D are on opposite sides of AB---cross product! We can calculate the
-// intersection point using the usual matrix inversion/determinant story.
-
-// TODO: handle hitting vertices
-fn hitDistLocalCoords(
- ray0_x: f32,
- ray0_y: f32,
- ray1_x: f32,
- ray1_y: f32,
- vertices: []const [2]f32,
-) ?LocalCoordHit {
- const rdy = ray1_y - ray0_y;
- const rdx = ray1_x - ray0_x;
- const rdist = std.math.sqrt(rdx * rdx + rdy * rdy);
-
- var vp = vertices[0];
- var crossp: f32 = rdy * (vp[0] - ray0_x) - rdx * (vp[1] - ray0_y);
-
- // if (crossp == 0) {
- // // hit a vertex exactly
- // const dx = vp[0] - ray0[0];
- // const dy = vp[1] - ray0[1];
- // return .{ std.math.sqrt(dx * dx + dy * dy), 0};
- // }
-
- var ret_val: ?LocalCoordHit = null;
- var v: [2]f32 = undefined;
- var cross: f32 = 0;
- var i: usize = 1;
- while (i < vertices.len) : ({
- vp = v;
- crossp = cross;
- i += 1;
- }) {
- v = vertices[i];
- cross = rdy * (v[0] - ray0_x) - rdx * (v[1] - ray0_y);
- // if (cross == 0) {
- // const dx = v[0] - ray0[0];
- // const dy = v[1] - ray0[1];
- // const new_distance = std.math.sqrt(dx * dx + dy * dy);
- // if (return_val) |local_distance| {
- // if (new_distance < local_distance) return_val = new_distance;
- // } else {
- // return_val = new_distance;
- // }
- // } else
- {
- if (crossp * cross < 0) {
- const vdx = v[0] - vp[0];
- const vdy = v[1] - vp[1];
-
- // both t and frac here index the intersection point, but t does
- // so along ray0->ray1 which we use for fast distance, and frac
- // indexes it along vp->v which we use for texture mapping.
- const denom = (rdx * vdy - vdx * rdy);
- const t = (vdx * (ray0_y - vp[1]) - vdy * (ray0_x - vp[0])) / denom;
- const side_frac = crossp / denom;
- const new_distance = rdist * t;
-
- if (ret_val == null or (new_distance < ret_val.?.local_dist)) {
- ret_val = LocalCoordHit{
- .local_dist = new_distance,
- .texture_frac = (side_frac + @intToFloat(f32, i - 1)) / @intToFloat(f32, vertices.len),
- };
- }
- }
- }
- }
- return ret_val;
-}
-
pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type {
const FOV: f32 = std.math.pi / 3.0;
const PlanePixels = PlaneWidth * PlaneHeight;
@@ -445,10 +325,7 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type {
// project the top of the bottom and the bottom of the top
top_of_floor = PlaneHeight / 2 + PlaneDist * (layer.height - pheight) / adj_distance;
- // bottom_of_ceiling = PlaneHeight / 2 + PlaneDist * (cell.ceiling_height - pheight) / adj_distance;
-
const draw_lower = layer.height > 0 and 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_lower) {
@@ -489,97 +366,196 @@ pub fn Renderer(PlaneWidth: f32, PlaneHeight: f32) type {
}
}
previous_layer_height = layer.height;
- }
+ // do we potentially draw horizontal surfaces?
+ if (highest_drawn < PlaneHeight / 2) {
+ // 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 * (layer.height - pheight) / next_distance;
+ if (true) { // next_top > highest_drawn
+ const toff = layer.horizontal_texture * @floatToInt(c_uint, constants.TextureDim);
- // do we potentially draw horizontal surfaces?
- if (highest_drawn < PlaneHeight / 2) {
- // 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 * (layer.height - pheight) / next_distance;
- if (false and next_top > highest_drawn) {
- const toff = cell.floor_texture * @floatToInt(c_uint, constants.TextureDim);
+ top_of_floor = std.math.ceil(std.math.min(std.math.min(next_top, lowest_drawn), PlaneHeight / 2 - 1));
+ const thresh = std.math.ceil(highest_drawn);
- top_of_floor = std.math.ceil(std.math.min(std.math.min(next_top, lowest_drawn), PlaneHeight / 2 - 1));
- const thresh = std.math.ceil(highest_drawn);
-
- const itop = @floatToInt(usize, std.math.max(top_of_floor, 0));
- const ptop = @floatToInt(usize, PlaneHeight) - itop - 1;
- var pix_index = ptop * @floatToInt(usize, PlaneWidth) + col;
- while (top_of_floor > thresh) : ({
- top_of_floor -= 1;
- pix_index += @floatToInt(usize, PlaneWidth);
- }) {
- const row_dist = (pheight - cell.floor_height) * PlaneDist / (PlaneHeight / 2 - top_of_floor);
- // draw the correct pixel
- const sx = std.math.modf(ppos_x + row_dist * cosra).fpart;
- const sy = std.math.modf(ppos_y + row_dist * sinra).fpart;
- const px = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sx));
- const py = @floatToInt(c_uint, constants.TextureDim * std.math.fabs(sy));
- const val = surfaces_image.getPixel(.{ .x = toff + px, .y = py });
- pixels[pix_index] = val;
-
- // record in the z_buffer only if we're above the floor!
- if (cell.floor_height > 0) self.z_buffer[pix_index] = row_dist;
+ const itop = @floatToInt(usize, std.math.max(top_of_floor, 0));
+ const ptop = @floatToInt(usize, PlaneHeight) - itop - 1;
+ var pix_index = ptop * @floatToInt(usize, PlaneWidth) + col - 1;
+ while (top_of_floor > thresh) : ({
+ top_of_floor -= 1;
+ pix_index += @floatToInt(usize, PlaneWidth);
+ }) {
+ const row_dist = (pheight - layer.height) * PlaneDist / (PlaneHeight / 2 - top_of_floor);
+ // draw the correct pixel
+ const sx = ppos_x + row_dist * cosra;
+ const sy = ppos_y + row_dist * sinra;
+ const rx = std.math.modf(sx).fpart;
+ const ry = std.math.modf(sy).fpart;
+ if (@floatToInt(i32, sx) == ipos_x and @floatToInt(i32, sy) == ipos_y and pointInPoly(rx, ry, layer.vertices.constSlice())) {
+ const px = @floatToInt(c_uint, constants.TextureDim * rx);
+ const py = @floatToInt(c_uint, constants.TextureDim * ry);
+ const val = surfaces_image.getPixel(.{ .x = toff + px, .y = py });
+ pixels[pix_index] = val;
+ // TODO ???
+ // if (top_of_floor > highest_drawn) highest_drawn = top_of_floor;
+ // if (layer.height > 0) self.z_buffer[pix_index] = row_dist;
+ }
+ }
}
- // OVERDRAW
- // highest_drawn = next_top;
}
}
}
}
- // // do we potentially draw floor and or ceiling for this cell?
- // if (highest_drawn < PlaneHeight / 2 or (cell.draw_down and lowest_drawn > PlaneHeight / 2)) {
- // // 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 - pheight) / next_distance;
- // const next_bottom = PlaneHeight / 2 + PlaneDist * (cell.ceiling_height - pheight) / next_distance;
+ // Have we filled this column?
+ if (top_of_floor > lowest_drawn or bottom_of_ceiling < highest_drawn) {
+ still_drawing = false;
+ }
+ }
+ }
+ }
+ };
+}
+
+fn playerDistComp(pos: [2]f32, lhs: level.Object, rhs: level.Object) bool {
+ const lx = lhs.pos_x - pos[0];
+ const ly = lhs.pos_y - pos[1];
+ const rx = rhs.pos_x - pos[0];
+ const ry = rhs.pos_y - pos[1];
- // // draw ceiling?
- // if (false and cell.draw_down and next_bottom < lowest_drawn) {
- // const toff = cell.ceiling_texture * @floatToInt(c_uint, constants.TextureDim);
+ return (lx * lx + ly * ly > rx * rx + ry * ry);
+}
- // bottom_of_ceiling = std.math.ceil(lowest_drawn);
- // const thresh = std.math.max(std.math.max(next_bottom, highest_drawn), PlaneHeight / 2 - 1);
+fn fasterColourBlend(onto: Colour, from: Colour) Colour {
+ const af: u16 = from.a;
+ const of: u16 = onto.a;
+
+ const ablend: u16 = @divTrunc(of * (255 - af), 255);
+ const na: u16 = af + ablend;
+ if (na == 0) return Colour.Black;
+
+ const rf: u16 = from.r;
+ const ro: u16 = onto.r;
+ const gf: u16 = from.g;
+ const go: u16 = onto.g;
+ const bf: u16 = from.b;
+ const bo: u16 = onto.b;
+
+ // The most accurate i've found is
+ const nr = @divTrunc(ro * ablend + rf * af, na);
+ const ng = @divTrunc(go * ablend + gf * af, na);
+ const nb = @divTrunc(bo * ablend + bf * af, na);
- // const itop = @floatToInt(usize, bottom_of_ceiling);
- // const ptop = @floatToInt(usize, PlaneHeight) - itop - 1;
- // var pix_index = ptop * @floatToInt(usize, PlaneWidth) + col;
+ // These computations are incorrect, but faster
+ // const nr = (af * rf + (255 - af) * ro) / 255;
+ // const ng = (af * gf + (255 - af) * go) / 255;
+ // const nb = (af * bf + (255 - af) * bo) / 255;
- // while (bottom_of_ceiling > thresh) : ({
- // bottom_of_ceiling -= 1;
- // pix_index += @floatToInt(usize, PlaneWidth);
- // }) {
- // const row_dist = (cell.ceiling_height - pheight) * PlaneDist / (bottom_of_ceiling - PlaneHeight / 2);
+ return Colour{
+ .a = @intCast(u8, na),
+ .r = @intCast(u8, nr),
+ .g = @intCast(u8, ng),
+ .b = @intCast(u8, nb),
+ };
+}
- // const sx = std.math.modf(ppos_x + row_dist * cosra);
- // const sy = std.math.modf(ppos_y + row_dist * sinra);
- // 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[pix_index] = val;
+const LocalCoordHit = struct {
+ local_dist: f32,
+ texture_frac: f32,
+};
- // if (cell.draw_down) self.z_buffer[pix_index] = row_dist;
- // }
- // lowest_drawn = next_bottom;
- // }
- // }
- // }
+// An implementation of the classic Point-in-polygon algorithm by W. Randolph
+// Franklin: https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html
+fn pointInPoly(testx: f32, testy: f32, verts: []const [2]f32) bool {
+ var inside: bool = false;
+ var i: usize = 0;
+ var j: usize = verts.len - 1;
+ while (i < verts.len) : ({
+ j = i;
+ i += 1;
+ }) {
+ if ((verts[i][1] > testy) != (verts[j][1] > testy)) {
+ if (testx < (verts[j][0] - verts[i][0]) * (testy - verts[i][1]) / (verts[j][1] - verts[i][1]) + verts[i][0]) {
+ inside = !inside;
+ }
+ }
+ }
+ return inside;
+}
- // Have we filled this column?
- if (top_of_floor > lowest_drawn or bottom_of_ceiling < highest_drawn) {
- still_drawing = false;
- }
+// The primary observation is: if a line segment AB disconnects the unit square,
+// then it intersects another line segment CD in that square precisely when the
+// C and D are on opposite sides of AB---cross product! We can calculate the
+// intersection point using the usual matrix inversion/determinant story.
+
+// TODO: handle hitting vertices
+fn hitDistLocalCoords(
+ ray0_x: f32,
+ ray0_y: f32,
+ ray1_x: f32,
+ ray1_y: f32,
+ vertices: []const [2]f32,
+) ?LocalCoordHit {
+ const rdy = ray1_y - ray0_y;
+ const rdx = ray1_x - ray0_x;
+ const rdist = std.math.sqrt(rdx * rdx + rdy * rdy);
+
+ var vp = vertices[vertices.len - 1];
+ var crossp = rdy * (vp[0] - ray0_x) - rdx * (vp[1] - ray0_y);
+
+ // if (crossp == 0) {
+ // // hit a vertex exactly
+ // const dx = vp[0] - ray0[0];
+ // const dy = vp[1] - ray0[1];
+ // return .{ std.math.sqrt(dx * dx + dy * dy), 0};
+ // }
+
+ var i: usize = 0;
+ var v: [2]f32 = undefined;
+ var cross: f32 = undefined;
+ var ret_val: ?LocalCoordHit = null;
+ while (i < vertices.len) : ({
+ vp = v;
+ crossp = cross;
+ i += 1;
+ }) {
+ v = vertices[i];
+ cross = rdy * (v[0] - ray0_x) - rdx * (v[1] - ray0_y);
+
+ // if (cross == 0) {
+ // const dx = v[0] - ray0[0];
+ // const dy = v[1] - ray0[1];
+ // const new_distance = std.math.sqrt(dx * dx + dy * dy);
+ // if (return_val) |local_distance| {
+ // if (new_distance < local_distance) return_val = new_distance;
+ // } else {
+ // return_val = new_distance;
+ // }
+ // } else
+ {
+ if (crossp * cross < 0) {
+ const vdx = v[0] - vp[0];
+ const vdy = v[1] - vp[1];
+
+ // both t and frac here index the intersection point, but t does
+ // so along ray0->ray1 which we use for fast distance, and frac
+ // indexes it along vp->v which we use for texture mapping.
+ const denom = (rdx * vdy - vdx * rdy);
+ const t = (vdx * (ray0_y - vp[1]) - vdy * (ray0_x - vp[0])) / denom;
+ const side_frac = crossp / denom;
+ const new_distance = rdist * t;
+
+ if (ret_val == null or (new_distance < ret_val.?.local_dist)) {
+ ret_val = LocalCoordHit{
+ .local_dist = new_distance,
+ .texture_frac = (side_frac + @intToFloat(f32, i)) / @intToFloat(f32, vertices.len),
+ };
}
}
}
- };
+ }
+ return ret_val;
}