diff options
| author | tslil clingman <> | 2021-09-11 23:43:51 -0400 |
|---|---|---|
| committer | tslil clingman <> | 2021-09-11 23:43:51 -0400 |
| commit | 842d498f81e4733452e03f01d1c570ab5bb02771 (patch) | |
| tree | 73dfd262b2057003b195e35ec90f47826ca4bed2 /src | |
| parent | a0c7e2508df1cb0a094b9311ce90d4c25c6c31fd (diff) | |
Render tops of low walls!
But it's very inefficient right now
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.zig | 2 | ||||
| -rw-r--r-- | src/raycast.zig | 140 |
2 files changed, 96 insertions, 46 deletions
diff --git a/src/main.zig b/src/main.zig index f51c3a3..218ba92 100644 --- a/src/main.zig +++ b/src/main.zig @@ -55,7 +55,7 @@ pub fn main() !void { } 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 / 3, .wall_texture = 1, .floor_texture = 1 }; + map.cells.items[16 * 7 + 6] = level.Cell{ .height = constants.MAX_HEIGHT / 4, .wall_texture = 1, .floor_texture = 0 }; 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 }); diff --git a/src/raycast.zig b/src/raycast.zig index a5bb731..1b78983 100644 --- a/src/raycast.zig +++ b/src/raycast.zig @@ -26,6 +26,41 @@ const Colour = @import("sfml").graphics.Color; const level = @import("level.zig"); const constants = @import("constants.zig"); +fn renderSlice( + window: RenderWindow, + sprite: Sprite, + col: i32, // which column + top: f32, // top of wall + height: f32, // how tall + draw_frac: f32, // how much to draw, as a fraction; > 1 means repeat texture + texfrac: f32, // how far along the texture + texture: u8, // which texture index +) void { + // we need ceil here so that we draw always to or past the edge of the screen + const draw_height = @floatToInt(c_int, std.math.ceil(draw_frac * constants.TextureDim)); + const total_height = height * constants.VFact; + + const xpos = @intToFloat(f32, col) * constants.HFact; + const ypos = constants.ScreenHeight / 2 + (constants.PlaneHeight / 2 - top) * constants.VFact; + + const tind = texture * @floatToInt(c_int, constants.TextureDim); + const left = tind + @floatToInt(c_int, texfrac * constants.TextureDim); + + sprite.setPosition(.{ .x = xpos, .y = ypos }); + sprite.setScale(.{ .x = constants.HFact, .y = total_height / constants.TextureDim }); + sprite.setTextureRect(.{ .top = 0, .left = left, .width = 1, .height = draw_height }); + window.draw(sprite, null); +} + +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); +} + pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { const FOV: f32 = std.math.pi / 3.0; const PlanePixels = PlaneWidth * PlaneHeight; @@ -84,6 +119,11 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { rendered_floors_sprite: Sprite, map: level.Map, ) !void { + // TODO: this is wasteful -- we render the floors and draw the + // texture, draw over that for the walls, then change the floor + // texture for the tops of things and blank everywhere else, then + // redraw the floor texture. + // First render the floors try self.renderFloorsToTexture(floors_image, rendered_floors_texture, map); window.draw(rendered_floors_sprite, null); @@ -95,21 +135,13 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { } // then render all the walls and populate the z_buffer - self.renderWalls(window, walls_sprite, map); + try self.renderCells(window, walls_sprite, floors_image, rendered_floors_texture, map); + window.draw(rendered_floors_sprite, null); // use the z_buffer to render sprites self.renderObjects(window, objects_sprite, map); } - 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 renderObjects( self: @This(), window: RenderWindow, @@ -184,12 +216,16 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { } } - fn renderWalls( + fn renderCells( self: *@This(), window: RenderWindow, walls_sprite: Sprite, + floors_image : Image, + rendered_floors_texture : Texture, map: level.Map, - ) void { + ) !void { + var pixels = [_]Colour{Colour.fromRGBA(0, 0, 0, 0)} ** (PlaneWidth * PlaneHeight); + // This is a TERRIBLE hack: for whatever reason *linearly* // interpolating on the direction vectors gives // perspective-correct-seeming walls! @@ -201,7 +237,7 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { const cos_step = (cos_last - cos_first) / PlaneWidth; const sin_step = (sin_last - sin_first) / PlaneWidth; - var col: i32 = 0; + var col: u16 = 0; var cosra = cos_first; var sinra = sin_first; while (col < PlaneWidth) : ({ @@ -267,10 +303,11 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { if (cell.height > 0) { // project the top of the wall var top = PlaneHeight / 2 + PlaneDist * (cell.height - self.height) / distance; - // We have a wall to draw if it protrudes above what we have so far drawn + + // Does the wall extend above what we've draw? if (top > highest_point) { - // did we extend beyond the top of the plane? + // If we reach the top we have to stop! if (top > PlaneHeight) { still_drawing = false; } @@ -301,12 +338,53 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { const index = @intCast(usize, col * @floatToInt(i32, PlaneHeight) + y); self.z_buffer.set(index, distance); } - highest_point = top; } + + // do we potentially draw the top of this cell? + if (highest_point < 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; + + // 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 the correct pixel + 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.floor_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 = floors_image.getPixel(.{ .x = toff + px, .y = py }); + pixels[itop * @floatToInt(usize, PlaneWidth) + col] = val; + + // record in the z_buffer + const index = col * @floatToInt(usize, PlaneHeight) + ptop; + self.z_buffer.set(index, row_dist); + } + highest_point = top; + } + } } } } + try rendered_floors_texture.updateFromPixels(&pixels, null); } fn renderFloorsToTexture( @@ -354,7 +432,7 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { if (map.inBounds(ix, iy)) { const cell = map.lookup(ix, iy); - const tex = @as(c_uint, if (row > PlaneHeight / 2) cell.floor_texture else cell.ceiling_texture); + const tex = if (row > PlaneHeight / 2) cell.floor_texture else cell.ceiling_texture; const toff = tex * @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)); @@ -367,33 +445,5 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type { } try rendered_floors_texture.updateFromPixels(&pixels, null); } - - // TODO: is there anyway i can say fn renderSlice : RenderSliceFunction? - fn renderSlice( - window: RenderWindow, - sprite: Sprite, - col: i32, // which column - top: f32, // top of wall - total_height: f32, // how tall - draw_frac: f32, - texfrac: f32, // how far along the texture - texture: u8, // which texture index - ) void { - - // we need ceil here so that we draw always to or past the edge of the screen - const draw_height = @floatToInt(c_int, std.math.ceil(draw_frac * constants.TextureDim)); - const total_length = total_height * constants.VFact; - - const xpos = @intToFloat(f32, col) * constants.HFact; - const ypos = constants.ScreenHeight / 2 + (constants.PlaneHeight / 2 - top) * constants.VFact; - - const tind = @as(c_int, texture) * @floatToInt(c_int, constants.TextureDim); - const left = tind + @floatToInt(c_int, texfrac * constants.TextureDim); - - sprite.setPosition(.{ .x = xpos, .y = ypos }); - sprite.setScale(.{ .x = constants.HFact, .y = total_length / constants.TextureDim }); - sprite.setTextureRect(.{ .top = 0, .left = left, .width = 1, .height = draw_height }); - window.draw(sprite, null); - } }; } |
