// 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 { usingnamespace @import("sfml"); usingnamespace sf.graphics; usingnamespace sf.window; }; const isKeyPressed = sf.window.keyboard.isKeyPressed; const std = @import("std"); const constants = @import("constants.zig"); const player = @import("player.zig"); const render = @import("render.zig"); const level = @import("level.zig"); pub fn main() !void { const PI = std.math.pi; // Setup player and map //-------------------------------------------------------------------------- var renderer = render.Renderer(constants.PlaneWidth, constants.PlaneHeight).new(); var plyr = player.Player.new( 7, // x coord 1.5, // y coord PI / 2.0, // angle ); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); var map = try level.Map.new(16, 16, gpa.allocator()); defer map.deinit(); var y: u32 = 0; while (y < 16) : (y += 1) { var x: usize = 0; while (x < 16) : (x += 1) { try map.cells.items[y * 16 + x].lower_layers.append(level.Layer{ .height = 0, .polygon = .square }); if (y == 0 or y == 15 or x == 0) { try map.cells.items[y * 16 + x].lower_layers.append(level.Layer{ .height = 2, .polygon = .square }); } else { if (x == 15) try map.cells.items[y * 16 + x].lower_layers.append(level.Layer{ .height = 2, .polygon = .square, .vertical_texture = 2 }); } if (y == 7 and x >= 5 and x <= 10) { const vert_coords0 = [_][2]f32{ .{ 0.50, 0.10 }, .{ 0.10, 0.80 }, .{ 0.90, 0.80 }, }; var verts = try std.BoundedArray([2]f32, level.Polygon.MAX_VERTS).fromSlice(vert_coords0[0..]); var layer = level.Layer{ .height = 0.5, .polygon = .{ .vertices = verts }, .vertical_texture = 3, .horizontal_texture = 1, .vertical_texture_scale = 3 }; try map.cells.items[y * 16 + x].lower_layers.append(layer); // cylinder const vert_coords1 = comptime blk: { const size = level.Polygon.MAX_VERTS; var shape: [size][2]f32 = [_][2]f32{.{ 0, 0 }} ** size; for (shape) |*pair, i| { const theta = @intToFloat(f32, i) / @intToFloat(f32, shape.len) * 2 * std.math.pi; pair.*[0] = 0.5 + 0.2 * std.math.cos(theta); pair.*[1] = 0.5 + 0.2 * std.math.sin(theta); } break :blk shape; }; verts = try std.BoundedArray([2]f32, level.Polygon.MAX_VERTS).fromSlice(vert_coords1[0..]); layer = level.Layer{ .height = 1, .polygon = .{ .vertices = verts }, .vertical_texture = 1, .horizontal_texture = 2, .vertical_texture_scale = 2 }; try map.cells.items[y * 16 + x].lower_layers.append(layer); } } } 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 }); // Initialise SFML //-------------------------------------------------------------------------- var window = try sf.RenderWindow.create(.{ .x = constants.ScreenWidth, .y = constants.ScreenHeight }, 32, "zirc", sf.window.Style.none, null); defer window.destroy(); window.setFramerateLimit(40); window.setMouseCursorGrabbed(true); window.setMouseCursorVisible(false); // 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 * constants.ScreenWidth / constants.BackTextureWidth, .y = constants.ScreenHeight / constants.BackTextureHeight }); // Load wall textures var walls_image = try sf.Image.createFromFile("walls.png"); defer walls_image.destroy(); // Load sprite textures var objects_image = try sf.Image.createFromFile("objects.png"); defer objects_image.destroy(); // Load floor image var surfaces_image = try sf.Image.createFromFile("surfaces.png"); defer surfaces_image.destroy(); var rendered_surfaces_texture = try sf.Texture.create(.{ .x = constants.PlaneWidth, .y = constants.PlaneHeight }); defer rendered_surfaces_texture.destroy(); rendered_surfaces_texture.setSmooth(false); var rendered_surfaces_sprite = try sf.Sprite.createFromTexture(rendered_surfaces_texture); defer rendered_surfaces_sprite.destroy(); rendered_surfaces_sprite.setPosition(.{ .x = 0, .y = 0 }); rendered_surfaces_sprite.setScale(.{ .x = constants.HFact, .y = constants.VFact }); // Start your engines! //-------------------------------------------------------------------------- while (window.isOpen()) { while (window.pollEvent()) |event| { if (event == .closed) window.close(); } if (isKeyPressed(.Escape)) window.close(); if (isKeyPressed(.C)) plyr.height -= 0.1; if (isKeyPressed(.V)) plyr.height += 0.1; plyr.ang -= @intToFloat(f32, sf.window.mouse.getPosition(window).x) / constants.ScreenWidth - 0.5; sf.window.mouse.setPosition(.{.x = constants.ScreenWidth / 2, .y = 0}, window); const rotation = 2.0 * PI / 200.0; if (isKeyPressed(.Left)) plyr.ang += rotation; if (isKeyPressed(.Right)) plyr.ang -= rotation; const cos = std.math.cos(plyr.ang); const sin = std.math.sin(plyr.ang); plyr.acc_x = 0; plyr.acc_y = 0; const acc_mag = 30; if (isKeyPressed(.R)) { plyr.acc_x -= sin; plyr.acc_y += cos; } if (isKeyPressed(.T)) { plyr.acc_x += sin; plyr.acc_y -= cos; } if (isKeyPressed(.Up) or isKeyPressed(.F)) { plyr.acc_x += cos; plyr.acc_y += sin; } if (isKeyPressed(.Down) or isKeyPressed(.S)) { plyr.acc_x -= cos; plyr.acc_y -= sin; } // normalise const a = std.math.sqrt(plyr.acc_x * plyr.acc_x + plyr.acc_y * plyr.acc_y); if (a > 0) { plyr.acc_x *= acc_mag / a; plyr.acc_y *= acc_mag / a; } // First the background const back_scroll = @floatToInt(i32, -constants.BackTextureWidth * plyr.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 = constants.BackTextureWidth / 4.0, // 1/4 of texture per screen .height = constants.BackTextureHeight, // full height of texture visible }); window.draw(back_sprite, null); try renderer.renderWorld( plyr, &window, objects_image, walls_image, surfaces_image, &rendered_surfaces_texture, rendered_surfaces_sprite, map, ); window.display(); plyr.tick(map); } }