// ZiRC, a Zig raycaster for fun // // Copyright (C) 2021, tslil clingman // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . const sf = struct { pub usingnamespace @import("sfml"); pub usingnamespace graphics; pub usingnamespace window; }; const isKeyPressed = sf.window.keyboard.isKeyPressed; const std = @import("std"); usingnamespace @import("raycast.zig"); usingnamespace @import("renderConstants.zig"); usingnamespace @import("map.zig"); // TODO: is there anyway i can say fn renderSlice : RenderSliceFunction? fn renderSlice( window: sf.graphics.RenderWindow, sprite: sf.graphics.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 * TextureDim)); const total_length = total_height * VFact; const xpos = @intToFloat(f32, col) * HFact; const ypos = ScreenHeight / 2 + (PlaneHeight / 2 - top) * VFact; const tind = @as(c_int, texture) * @floatToInt(c_int, TextureDim); const left = tind + @floatToInt(c_int, texfrac * TextureDim); sprite.setPosition(.{ .x = xpos, .y = ypos }); sprite.setScale(.{ .x = HFact, .y = total_length / TextureDim }); sprite.setTextureRect(.{ .top = 0, .left = left, .width = 1, .height = draw_height }); window.draw(sprite, null); } pub fn main() !void { const PI = std.math.pi; // Setup player and map //-------------------------------------------------------------------------- var player = try Player(PlaneWidth, PlaneHeight).new( 7, // x coord 1.5, // y coord PI / 2.0, // angle ); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); var map = try Map.new(16, 16, &gpa.allocator); defer map.deinit(); var y: u32 = 0; while (y < 16) : (y += 1) { map.cells.items[y * 16 + 00] = Cell{ .height = MAX_HEIGHT }; map.cells.items[y * 16 + 15] = Cell{ .height = MAX_HEIGHT, .wall_texture = 2 }; map.cells.items[15 * 16 + y] = Cell{ .height = MAX_HEIGHT }; map.cells.items[00 * 16 + y] = Cell{ .height = MAX_HEIGHT }; } map.cells.items[16 * 7 + 7] = Cell{ .height = MAX_HEIGHT, .wall_texture = 1 }; map.cells.items[16 * 7 + 6] = Cell{ .height = MAX_HEIGHT / 3, .wall_texture = 1, .floor_texture = 1 }; try map.objects.append(Object{.pos_x = 6.5, .pos_y = 8.5, .texture = 0, .height = 1, .width = 1}); try map.objects.append(Object{.pos_x = 4, .pos_y = 8.5, .texture = 1, .height = 1, .width = 1}); // Initialise SFML //-------------------------------------------------------------------------- var window = try sf.RenderWindow.create(.{ .x = ScreenWidth, .y = ScreenHeight }, 32, "zirc", sf.window.Style.none); defer window.destroy(); window.setFramerateLimit(60); // Load the background skybox, really `skycylinder', and assign it to a // sprite -- it's set to repeating so that it goes on forever. We need to // scale the sprite so that drawing 1/4 of backTextureWidth fills the Screen // horizontally, and drawing backTextureHeight fills half of the Screen // vertically. var back_texture = try sf.Texture.createFromFile("back.png"); defer back_texture.destroy(); back_texture.setSmooth(false); // looks worse => better! back_texture.setRepeated(true); var back_sprite = try sf.Sprite.createFromTexture(back_texture); defer back_sprite.destroy(); back_sprite.setScale(.{ .x = 4.0 * ScreenWidth / BackTextureWidth, .y = ScreenHeight / (2 * BackTextureHeight) }); // Load wall textures var wall_textures = try sf.Texture.createFromFile("walls.png"); defer wall_textures.destroy(); wall_textures.setSmooth(false); // looks worse => better! var walls_sprite = try sf.Sprite.createFromTexture(wall_textures); defer walls_sprite.destroy(); // 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(); var rendered_floors_texture = try sf.Texture.create(.{ .x = PlaneWidth, .y = PlaneHeight / 2 }); defer rendered_floors_texture.destroy(); rendered_floors_texture.setSmooth(false); var rendered_floors_sprite = try sf.Sprite.createFromTexture(rendered_floors_texture); defer rendered_floors_sprite.destroy(); rendered_floors_sprite.setPosition(.{ .x = 0, .y = ScreenHeight / 2 }); rendered_floors_sprite.setScale(.{ .x = HFact, .y = VFact }); // Start your engines! //-------------------------------------------------------------------------- while (window.isOpen()) { while (window.pollEvent()) |event| { if (event == .closed) window.close(); } if (isKeyPressed(.Escape)) window.close(); if (isKeyPressed(.C)) player.height -= 0.1; if (isKeyPressed(.V)) player.height += 0.1; const rotation = 2.0 * PI / 100.0; if (isKeyPressed(.R)) player.ang += rotation; if (isKeyPressed(.T)) player.ang -= rotation; player.acc_x = 0; player.acc_y = 0; const acc_mag = 50; if (isKeyPressed(.Right)) { player.acc_x += std.math.cos(player.ang - PI / 2.0) * acc_mag; player.acc_y += std.math.sin(player.ang - PI / 2.0) * acc_mag; } if (isKeyPressed(.Left)) { player.acc_x -= std.math.cos(player.ang - PI / 2.0) * acc_mag; player.acc_y -= std.math.sin(player.ang - PI / 2.0) * acc_mag; } if (isKeyPressed(.Up) or isKeyPressed(.F)) { player.acc_x += std.math.cos(player.ang) * acc_mag; player.acc_y += std.math.sin(player.ang) * acc_mag; } if (isKeyPressed(.Down) or isKeyPressed(.S)) { player.acc_x -= std.math.cos(player.ang) * acc_mag; player.acc_y -= std.math.sin(player.ang) * acc_mag; } window.clear(sf.Color.fromRGB(100, 100, 100)); // First the background const back_scroll = @floatToInt(i32, -BackTextureWidth * player.ang / (2 * PI)); back_sprite.setTextureRect(sf.IntRect{ .left = back_scroll, // fix location of texture in the sky .top = 0, // scaling ensures that the next two dimensions paint it across the // width of the Screen, and for half the height of the screen .width = BackTextureWidth / 4.0, // 1/4 of texture per screen .height = BackTextureHeight, // full height of texture visible }); window.draw(back_sprite, null); // Then render the walls try player.renderFloorsToTexture(floors_image, rendered_floors_texture, map); window.draw(rendered_floors_sprite, null); // Finally the world player.renderWorld(window, walls_sprite, objects_sprite, renderSlice, map); window.display(); player.tick(); } }