aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--objects.pngbin0 -> 302 bytes
-rw-r--r--src/main.zig24
-rw-r--r--src/map.zig14
-rw-r--r--src/raycast.zig132
-rw-r--r--walls.pngbin1050 -> 581 bytes
5 files changed, 138 insertions, 32 deletions
diff --git a/objects.png b/objects.png
new file mode 100644
index 0000000..bc33c52
--- /dev/null
+++ b/objects.png
Binary files differ
diff --git a/src/main.zig b/src/main.zig
index 2d12975..8129ae2 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -28,8 +28,8 @@ usingnamespace @import("raycast.zig");
usingnamespace @import("renderConstants.zig");
usingnamespace @import("map.zig");
-// TODO: is there anyway i can say fn renderWall : RenderWallFunction?
-fn renderWall(
+// TODO: is there anyway i can say fn renderSlice : RenderSliceFunction?
+fn renderSlice(
window: sf.graphics.RenderWindow,
sprite: sf.graphics.Sprite,
col: i32, // which column
@@ -77,13 +77,15 @@ pub fn main() !void {
var y: u32 = 0;
while (y < 32) : (y += 1) {
map.cells.items[y * 32 + 00] = Cell{ .height = MAX_HEIGHT };
- map.cells.items[y * 32 + 31] = Cell{ .height = MAX_HEIGHT };
+ map.cells.items[y * 32 + 31] = Cell{ .height = MAX_HEIGHT, .wall_texture = 2 };
map.cells.items[31 * 32 + y] = Cell{ .height = MAX_HEIGHT };
map.cells.items[00 * 32 + y] = Cell{ .height = MAX_HEIGHT };
}
- map.cells.items[y * 15 + 15] = Cell{ .height = MAX_HEIGHT, .wall_texture = 1 };
- map.cells.items[y * 15 + 14] = Cell{ .height = MAX_HEIGHT / 3, .wall_texture = 1, .floor_texture = 1 };
+ map.cells.items[32 * 15 + 15] = Cell{ .height = MAX_HEIGHT, .wall_texture = 1 };
+ map.cells.items[32 * 15 + 14] = Cell{ .height = MAX_HEIGHT / 3, .wall_texture = 1, .floor_texture = 1 };
+
+ try map.objects.append(Object{.pos_x = 14.5, .pos_y = 16.5, .texture = 0, .height = 1.5, .width = 1});
// Initialise SFML
//--------------------------------------------------------------------------
@@ -117,7 +119,15 @@ pub fn main() !void {
var walls_sprite = try sf.Sprite.createFromTexture(wall_textures);
defer walls_sprite.destroy();
- // Load floor textures
+ // Load sprite textures
+ var object_textures = try sf.Texture.createFromFile("objects.png");
+ defer object_textures.destroy();
+ object_textures.setSmooth(false);
+
+ var objects_sprite = try sf.Sprite.createFromTexture(object_textures);
+ defer objects_sprite.destroy();
+
+ // Load floor image
var floors_image = try sf.Image.createFromFile("floors.png");
defer floors_image.destroy();
@@ -187,7 +197,7 @@ pub fn main() !void {
window.draw(rendered_floors_sprite, null);
// Finally the world
- player.renderWorld(window, walls_sprite, map, renderWall);
+ player.renderWorld(window, walls_sprite, objects_sprite, renderSlice, map);
window.display();
diff --git a/src/map.zig b/src/map.zig
index 8093e21..92ca5be 100644
--- a/src/map.zig
+++ b/src/map.zig
@@ -20,6 +20,14 @@ const std = @import("std");
// Let's pretend that our units are meters
pub const MAX_HEIGHT: f32 = 2.5;
+pub const Object = struct {
+ height : f32,
+ width : f32,
+ texture : u8,
+ pos_x : f32,
+ pos_y : f32,
+};
+
pub const Cell = struct {
height: f32,
wall_texture: u8 = 0,
@@ -34,9 +42,11 @@ pub const Map = struct {
height: u32,
size: f32 = 64,
cells: std.ArrayList(Cell),
+ objects: std.ArrayList(Object),
pub fn deinit(self: Map) void {
self.cells.deinit();
+ self.objects.deinit();
}
pub fn new(width: u32, height: u32, alloc: *std.mem.Allocator) !Map {
@@ -44,7 +54,9 @@ pub const Map = struct {
try cells.ensureTotalCapacity(width * height);
try cells.appendNTimes(Cell.floor, width * height);
- return Map{ .width = width, .height = height, .cells = cells };
+ var objects = std.ArrayList(Object).init(alloc);
+
+ return Map{ .width = width, .height = height, .cells = cells, .objects = objects };
}
pub fn inBounds(self: Map, x: i32, y: i32) bool {
diff --git a/src/raycast.zig b/src/raycast.zig
index 3df8cef..20597e9 100644
--- a/src/raycast.zig
+++ b/src/raycast.zig
@@ -26,7 +26,7 @@ const Colour = @import("sfml").graphics.Color;
usingnamespace @import("map.zig");
usingnamespace @import("renderConstants.zig");
-pub const RenderWallFunction: type = fn (
+pub const RenderSliceFunction: type = fn (
window: RenderWindow,
sprite: Sprite,
col: i32, // which column we're in
@@ -48,7 +48,7 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
acc_y: f32 = 0,
fov: f32 = std.math.pi / 3.0,
height: f32 = 1.8, // TODO
- // z_buffer: std.BoundedArray(f32, PlaneWidth * PlaneHeight),
+ z_buffer: std.BoundedArray(f32, PlanePixels),
const FOV: f32 = std.math.pi / 3.0;
const PlanePixels = PlaneWidth * PlaneHeight;
@@ -58,13 +58,13 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
// 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;
+ 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),
+ .z_buffer = try std.BoundedArray(f32, PlanePixels).fromSlice(&infs),
};
}
@@ -89,23 +89,105 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
self: *@This(),
window: RenderWindow,
walls_sprite: Sprite,
+ objects_sprite: Sprite,
+ renderSlice: RenderSliceFunction,
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);
+ // reset the z_buffer
+ var i: usize = 0;
+ while (i < self.z_buffer.len) : (i += 1) {
+ self.z_buffer.set(i, std.math.inf(f32));
+ }
+
+ // then render all the walls and populate the z_buffer
+ self.renderWalls(window, walls_sprite, renderSlice, map);
+
+ // use the z_buffer to render sprites
+ self.renderObjects(window, objects_sprite, renderSlice, map);
+ }
+
+ fn playerDistComp(self: @This(), lhs: Object, rhs: Object) bool {
+ const lx = lhs.pos_x - self.pos_x;
+ const ly = lhs.pos_y - self.pos_y;
+ const rx = rhs.pos_x - self.pos_x;
+ const ry = rhs.pos_y - self.pos_y;
+
+ return (lx * lx + ly * ly < rx * rx + ry * ry);
+ }
+
+ // Assumes objects are sorted by proximity!
+ fn renderObjects(
+ self: @This(),
+ window: RenderWindow,
+ objects_sprite: Sprite,
+ renderSlice: RenderSliceFunction,
+ map: Map,
+ ) void {
+ //std.sort.sort(Object, map.objects.items, {}, self.playerDistComp);
+
+ const self_cos = std.math.cos(self.ang);
+ const self_sin = std.math.sin(self.ang);
+
+ for (map.objects.items) |obj| {
+ const ox = obj.pos_x - self.pos_x;
+ const oy = obj.pos_y - self.pos_y;
+ // we compute the two coordinates of rotating by -self.ang, the
+ // first of which gives the perpendicular distance to the plane
+ // of projection, and the second of which gives the
+ // (unprojected) centre of the object.
+ const perp_distance = self_cos * ox + self_sin * oy;
+ const centre = self_sin * ox - self_cos * oy;
+ const proj_centre = PlaneWidth / 2 + PlaneDist * centre / perp_distance;
+
+ const width = PlaneDist * obj.width / perp_distance;
+ const left = proj_centre - width / 2;
+
+ // TODO: prune before this?
+ if (left + width < 0 or left >= PlaneWidth) continue;
+
+ // TODO: Here's another fudge factor. I think this is something
+ // like the floors and walls aren't actually distance correct,
+ // so we have to toy with these exact calculations to fix it.
+ const height = PlaneDist * obj.height / perp_distance;
+ const top = PlaneHeight / 2 + PlaneDist * (obj.height + 0.2 - self.height) / perp_distance;
+
+ // TODO: likewise?
+ if (top < 0 or top - height >= PlaneHeight) continue;
+
+ // Something is on the screen, let's draw it!
+ const start = std.math.max(0, left);
+ const end = @floatToInt(i32, std.math.min(left + width, PlaneWidth));
+
+ var tex_frac: f32 = (start - left) / width;
+ var col: i32 = @floatToInt(i32, start);
+ const tex_frac_step = 1 / width;
+ while (col < end) : ({
+ col += 1;
+ tex_frac += tex_frac_step;
+ }) {
+ var bottom = std.math.min(top, PlaneHeight);
+ while (bottom > 0 and bottom > top - height) : (bottom -= 1) {
+ const index = @intCast(usize, col * @floatToInt(i32, PlaneHeight) + @floatToInt(i32, bottom));
+ // TODO: what is this fudge factor? Is this again
+ // because distances aren't actually correct in the
+ // z_buffer but they are in this computation?
+ if (self.z_buffer.get(index) < perp_distance + 1) {
+ bottom += 1;
+ break;
+ }
+ }
+ const draw_frac = std.math.clamp((top - bottom) / height, 0 , 1);
+ renderSlice(window, objects_sprite, col, top, height, draw_frac, tex_frac, obj.texture);
+ }
+ }
}
fn renderWalls(
self: *@This(),
window: RenderWindow,
walls_sprite: Sprite,
+ renderSlice: RenderSliceFunction,
map: Map,
- renderWall: RenderWallFunction,
) void {
// This is a TERRIBLE hack: for whatever reason *linearly*
// interpolating on the direction vectors gives
@@ -181,13 +263,15 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
var cell = map.lookup(ipos_x, ipos_y);
// project the top of the wall
- const top = PlaneHeight / 2 + PlaneDist * (cell.height - self.height) / distance;
+ 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
if (top > highest_point) {
// did we extend beyond the top of the plane?
- if (top > PlaneHeight) still_drawing = false;
+ if (top > PlaneHeight) {
+ still_drawing = false;
+ }
// compute the height of this wall
const total_length = PlaneDist * cell.height / distance;
@@ -206,14 +290,14 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
if ((horizontal_hit and sinra < 0) or (!horizontal_hit and cosra > 0)) texfrac = 1 - texfrac;
// draw the wall
- renderWall(window, walls_sprite, col, top, total_length, draw_frac, texfrac, cell.wall_texture);
+ renderSlice(window, walls_sprite, col, top, total_length, draw_frac, texfrac, cell.wall_texture);
// 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, distance);
- // }
+ var y = @floatToInt(i32, std.math.min(top, PlaneHeight - 1));
+ while (y > @floatToInt(i32, highest_point)) : (y -= 1) {
+ const index = @intCast(usize, col * @floatToInt(i32, PlaneHeight) + y);
+ self.z_buffer.set(index, distance);
+ }
highest_point = top;
}
@@ -226,10 +310,10 @@ pub fn Player(PlaneWidth: f32, PlaneHeight: f32) type {
// 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);
+ 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) {
diff --git a/walls.png b/walls.png
index b12a4ad..b8d0a72 100644
--- a/walls.png
+++ b/walls.png
Binary files differ