diff options
| author | tslil clingman <> | 2021-10-17 16:28:45 -0400 |
|---|---|---|
| committer | tslil clingman <> | 2021-10-17 16:28:45 -0400 |
| commit | e371561831715146b42fa68ed16b5d792132cb99 (patch) | |
| tree | 99bd431c1a179386bbf60bc7b23642e1c29911af /src | |
| parent | c376543f5b2c9593bee3664b7c1cb67f8802f79c (diff) | |
Well, it's not quite right, but this is the general direction
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.zig | 40 | ||||
| -rw-r--r-- | src/render.zig | 372 |
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; } |
