From 76306196c0e226b894100d61dac929635d0d5aff Mon Sep 17 00:00:00 2001 From: trotFunky Date: Mon, 27 May 2019 13:50:49 +0200 Subject: [PATCH 01/44] First commit, world map creation --- .gitignore | 2 ++ CMakeLists.txt | 18 ++++++++++++ Player.cpp | 10 +++++++ Player.h | 21 ++++++++++++++ World.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ World.h | 36 ++++++++++++++++++++++++ main.cpp | 14 ++++++++++ 7 files changed, 176 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Player.cpp create mode 100644 Player.h create mode 100644 World.cpp create mode 100644 World.h create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..696c0c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/cmake-build-* +**/.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..caf14ed --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.14) +project(raycasting) + +set(CMAKE_CXX_STANDARD 14) + +# Detect and add SFML +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH}) +find_package(SFML COMPONENTS system window graphics network audio REQUIRED) + +if(NOT SFML_FOUND) + message(FATAL_ERROR "SFML could not be found") +endif() + +add_executable(raycasting main.cpp Player.cpp Player.h World.cpp World.h) + +target_link_libraries(raycasting + sfml-window + sfml-graphics) \ No newline at end of file diff --git a/Player.cpp b/Player.cpp new file mode 100644 index 0000000..7a8196d --- /dev/null +++ b/Player.cpp @@ -0,0 +1,10 @@ +// +// Created by trotfunky on 27/05/19. +// + +#include "Player.h" + + +Player::Player(float x, float y, float alpha) : x(x), y(y), orientation(alpha) +{} + diff --git a/Player.h b/Player.h new file mode 100644 index 0000000..8063e66 --- /dev/null +++ b/Player.h @@ -0,0 +1,21 @@ +// +// Created by trotfunky on 27/05/19. +// + +#ifndef RAYCASTING_PLAYER_H +#define RAYCASTING_PLAYER_H + + +class Player { +public: + Player(float x, float y, float alpha); + + float x; + float y; + float orientation; + + static constexpr float fov = 70; +}; + + +#endif //RAYCASTING_PLAYER_H diff --git a/World.cpp b/World.cpp new file mode 100644 index 0000000..30738b5 --- /dev/null +++ b/World.cpp @@ -0,0 +1,75 @@ +// +// Created by trotfunky on 27/05/19. +// + +#include "World.h" + + +World::World(int w, int h, std::vector worldMap) : w(w), h(h), map(std::move(worldMap)) +{ + map.resize(w*h,BlockType::WALL); +} + +int World::getW() const +{ + return w; +} + +int World::getH() const +{ + return h; +} + +BlockType World::getBlock(float x, float y) const +{ + return(map.at(static_cast(x)+w* static_cast(y))); +} + +void World::setBlock(BlockType block, int x, int y, int width, int height) +{ + for(int i = 0;i +#include + +enum class BlockType { + AIR, + WALL, + DOOR, + WINDOW, +}; + +class World { +public: + World(int w, int h, std::vector worldMap = {}); + + int getW() const; + int getH() const; + + BlockType getBlock(float x, float y) const; + void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); + + friend std::ostream& operator<<(std::ostream& ostream, World const & world); +private: + int w; + int h; + std::vector map; +}; + + +#endif //RAYCASTING_WORLD_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..3689d6d --- /dev/null +++ b/main.cpp @@ -0,0 +1,14 @@ +#include +#include "SFML/Graphics.hpp" + +#include "World.h" + +int main() +{ + std::cout << "Hello, World!" << std::endl; + World world(10,10); + world.setBlock(BlockType::AIR,1,1,8,8); + world.setBlock(BlockType::WALL,4,4,2,2); + std::cout << world << std::endl; + return 0; +} \ No newline at end of file From 52a261de58458a03ce4fff92a9ceee3eb65d8ed6 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Mon, 27 May 2019 13:50:49 +0200 Subject: [PATCH 02/44] First commit, world map creation --- .gitignore | 2 ++ CMakeLists.txt | 18 ++++++++++++ Player.cpp | 10 +++++++ Player.h | 21 ++++++++++++++ World.cpp | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ World.h | 36 ++++++++++++++++++++++++ main.cpp | 14 ++++++++++ 7 files changed, 176 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 Player.cpp create mode 100644 Player.h create mode 100644 World.cpp create mode 100644 World.h create mode 100644 main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..696c0c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/cmake-build-* +**/.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..caf14ed --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.14) +project(raycasting) + +set(CMAKE_CXX_STANDARD 14) + +# Detect and add SFML +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH}) +find_package(SFML COMPONENTS system window graphics network audio REQUIRED) + +if(NOT SFML_FOUND) + message(FATAL_ERROR "SFML could not be found") +endif() + +add_executable(raycasting main.cpp Player.cpp Player.h World.cpp World.h) + +target_link_libraries(raycasting + sfml-window + sfml-graphics) \ No newline at end of file diff --git a/Player.cpp b/Player.cpp new file mode 100644 index 0000000..7a8196d --- /dev/null +++ b/Player.cpp @@ -0,0 +1,10 @@ +// +// Created by trotfunky on 27/05/19. +// + +#include "Player.h" + + +Player::Player(float x, float y, float alpha) : x(x), y(y), orientation(alpha) +{} + diff --git a/Player.h b/Player.h new file mode 100644 index 0000000..8063e66 --- /dev/null +++ b/Player.h @@ -0,0 +1,21 @@ +// +// Created by trotfunky on 27/05/19. +// + +#ifndef RAYCASTING_PLAYER_H +#define RAYCASTING_PLAYER_H + + +class Player { +public: + Player(float x, float y, float alpha); + + float x; + float y; + float orientation; + + static constexpr float fov = 70; +}; + + +#endif //RAYCASTING_PLAYER_H diff --git a/World.cpp b/World.cpp new file mode 100644 index 0000000..30738b5 --- /dev/null +++ b/World.cpp @@ -0,0 +1,75 @@ +// +// Created by trotfunky on 27/05/19. +// + +#include "World.h" + + +World::World(int w, int h, std::vector worldMap) : w(w), h(h), map(std::move(worldMap)) +{ + map.resize(w*h,BlockType::WALL); +} + +int World::getW() const +{ + return w; +} + +int World::getH() const +{ + return h; +} + +BlockType World::getBlock(float x, float y) const +{ + return(map.at(static_cast(x)+w* static_cast(y))); +} + +void World::setBlock(BlockType block, int x, int y, int width, int height) +{ + for(int i = 0;i +#include + +enum class BlockType { + AIR, + WALL, + DOOR, + WINDOW, +}; + +class World { +public: + World(int w, int h, std::vector worldMap = {}); + + int getW() const; + int getH() const; + + BlockType getBlock(float x, float y) const; + void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); + + friend std::ostream& operator<<(std::ostream& ostream, World const & world); +private: + int w; + int h; + std::vector map; +}; + + +#endif //RAYCASTING_WORLD_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..3689d6d --- /dev/null +++ b/main.cpp @@ -0,0 +1,14 @@ +#include +#include "SFML/Graphics.hpp" + +#include "World.h" + +int main() +{ + std::cout << "Hello, World!" << std::endl; + World world(10,10); + world.setBlock(BlockType::AIR,1,1,8,8); + world.setBlock(BlockType::WALL,4,4,2,2); + std::cout << world << std::endl; + return 0; +} \ No newline at end of file From 59939d182bda690db0361141d27a0a08d41020ee Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sat, 1 Jun 2019 06:42:29 +0200 Subject: [PATCH 03/44] =?UTF-8?q?Ajout=20de=20la=20fonction=20render=20au?= =?UTF-8?q?=20monde=20Joueur=20d=C3=A9pla=C3=A7able=20d=C3=A9but=20du=20ca?= =?UTF-8?q?st=20de=20rayon=20Trop=20fatigu=C3=A9=20pour=20faire=20de=20la?= =?UTF-8?q?=20trigo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Player.cpp | 18 +++++++++++++++ Player.h | 5 ++++- World.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- World.h | 16 ++++++++++++- main.cpp | 16 +++++++++++++ 5 files changed, 117 insertions(+), 4 deletions(-) diff --git a/Player.cpp b/Player.cpp index 7a8196d..b23f8c5 100644 --- a/Player.cpp +++ b/Player.cpp @@ -8,3 +8,21 @@ Player::Player(float x, float y, float alpha) : x(x), y(y), orientation(alpha) {} +void Player::move(float dx, float dy) +{ + x += dx; + y += dy; +} + +void Player::rotate(int alpha) +{ + orientation += alpha%360; + if(orientation >= 360) + { + orientation -= 360; + } + if(orientation <= 360) + { + orientation += 360; + } +} diff --git a/Player.h b/Player.h index 8063e66..05426c5 100644 --- a/Player.h +++ b/Player.h @@ -14,7 +14,10 @@ public: float y; float orientation; - static constexpr float fov = 70; + float fov = 70; + + void move(float dx, float dy); + void rotate(int alpha); }; diff --git a/World.cpp b/World.cpp index 30738b5..ec29ffa 100644 --- a/World.cpp +++ b/World.cpp @@ -5,7 +5,8 @@ #include "World.h" -World::World(int w, int h, std::vector worldMap) : w(w), h(h), map(std::move(worldMap)) +World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : w(w), h(h), + groundColor(groundColor), ceilingColor(ceilingColor), map(std::move(worldMap)), player(0,0,0) { map.resize(w*h,BlockType::WALL); } @@ -51,7 +52,14 @@ std::ostream& operator<<(std::ostream& ostream, World const& world) { case BlockType::AIR: { - ostream << " "; + if(static_cast(world.player.x) == i%world.w && static_cast(world.player.y) == i/world.h) + { + ostream << "P"; + } + else + { + ostream << " "; + } break; } case BlockType::WALL: @@ -73,3 +81,57 @@ std::ostream& operator<<(std::ostream& ostream, World const& world) } return(ostream); } + +void World::fillColumn(sf::RenderWindow& window, int column, float scale, sf::Color wallColor) const +{ + float columnHeight = window.getSize().y*scale; + sf::RectangleShape pixelColumn(sf::Vector2f(1,columnHeight)); + pixelColumn.setPosition(column,(window.getSize().y-columnHeight)/2.0); + pixelColumn.setFillColor(wallColor); + + window.draw(pixelColumn); +} + +float World::castRay(float originX, float originY, float orientation) +{ + float deltaX; + float deltaY; + if(orientation < 45 || orientation > 315) + { + deltaX = ; + } + else if(orientation < 135) + { + + } + else if(orientation < 225) + { + + } + else + { + + } + + return(); +} + +void World::render(sf::RenderWindow& window) const +{ + sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(window.getSize().x,window.getSize().y/2.0)); + ground.setFillColor(groundColor); + ground.setPosition(0,window.getSize().y/2.0); + + sf::RectangleShape ceiling = sf::RectangleShape(sf::Vector2f(window.getSize().x,window.getSize().y/2.0)); + ceiling.setFillColor(ceilingColor); + + window.draw(ground); + window.draw(ceiling); + + for(int i = 0;i #include +#include +#include + +#include "Player.h" enum class BlockType { AIR, @@ -17,19 +21,29 @@ enum class BlockType { class World { public: - World(int w, int h, std::vector worldMap = {}); + Player player; + World(int w, int h, sf::Color groundColor = sf::Color::Green, sf::Color ceilingColor = sf::Color::Blue, + std::vector worldMap = {}); int getW() const; int getH() const; BlockType getBlock(float x, float y) const; void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); + void render(sf::RenderWindow&) const; + friend std::ostream& operator<<(std::ostream& ostream, World const & world); private: int w; int h; std::vector map; + + sf::Color groundColor; + sf::Color ceilingColor; + + void fillColumn(sf::RenderWindow&, int column, float scale, sf::Color wallColor = sf::Color(127,127,127)) const; + float castRay(float originX, float originY, float orientation); }; diff --git a/main.cpp b/main.cpp index 3689d6d..803c4d9 100644 --- a/main.cpp +++ b/main.cpp @@ -9,6 +9,22 @@ int main() World world(10,10); world.setBlock(BlockType::AIR,1,1,8,8); world.setBlock(BlockType::WALL,4,4,2,2); + world.player.move(2,2); std::cout << world << std::endl; + + sf::RenderWindow window(sf::VideoMode(800,600),"Da raycasting"); + world.render(window); + window.display(); + + while (window.isOpen()) + { + sf::Event event; + while (window.pollEvent(event)) + { + if (event.type == sf::Event::Closed) + window.close(); + } + } + return 0; } \ No newline at end of file From f21fc6f805f5b09bcfa841c566498e85cb4fd0f9 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sat, 1 Jun 2019 06:42:29 +0200 Subject: [PATCH 04/44] =?UTF-8?q?Ajout=20de=20la=20fonction=20render=20au?= =?UTF-8?q?=20monde=20Joueur=20d=C3=A9pla=C3=A7able=20d=C3=A9but=20du=20ca?= =?UTF-8?q?st=20de=20rayon=20Trop=20fatigu=C3=A9=20pour=20faire=20de=20la?= =?UTF-8?q?=20trigo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Player.cpp | 18 +++++++++++++++ Player.h | 5 ++++- World.cpp | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- World.h | 16 ++++++++++++- main.cpp | 16 +++++++++++++ 5 files changed, 117 insertions(+), 4 deletions(-) diff --git a/Player.cpp b/Player.cpp index 7a8196d..b23f8c5 100644 --- a/Player.cpp +++ b/Player.cpp @@ -8,3 +8,21 @@ Player::Player(float x, float y, float alpha) : x(x), y(y), orientation(alpha) {} +void Player::move(float dx, float dy) +{ + x += dx; + y += dy; +} + +void Player::rotate(int alpha) +{ + orientation += alpha%360; + if(orientation >= 360) + { + orientation -= 360; + } + if(orientation <= 360) + { + orientation += 360; + } +} diff --git a/Player.h b/Player.h index 8063e66..05426c5 100644 --- a/Player.h +++ b/Player.h @@ -14,7 +14,10 @@ public: float y; float orientation; - static constexpr float fov = 70; + float fov = 70; + + void move(float dx, float dy); + void rotate(int alpha); }; diff --git a/World.cpp b/World.cpp index 30738b5..ec29ffa 100644 --- a/World.cpp +++ b/World.cpp @@ -5,7 +5,8 @@ #include "World.h" -World::World(int w, int h, std::vector worldMap) : w(w), h(h), map(std::move(worldMap)) +World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : w(w), h(h), + groundColor(groundColor), ceilingColor(ceilingColor), map(std::move(worldMap)), player(0,0,0) { map.resize(w*h,BlockType::WALL); } @@ -51,7 +52,14 @@ std::ostream& operator<<(std::ostream& ostream, World const& world) { case BlockType::AIR: { - ostream << " "; + if(static_cast(world.player.x) == i%world.w && static_cast(world.player.y) == i/world.h) + { + ostream << "P"; + } + else + { + ostream << " "; + } break; } case BlockType::WALL: @@ -73,3 +81,57 @@ std::ostream& operator<<(std::ostream& ostream, World const& world) } return(ostream); } + +void World::fillColumn(sf::RenderWindow& window, int column, float scale, sf::Color wallColor) const +{ + float columnHeight = window.getSize().y*scale; + sf::RectangleShape pixelColumn(sf::Vector2f(1,columnHeight)); + pixelColumn.setPosition(column,(window.getSize().y-columnHeight)/2.0); + pixelColumn.setFillColor(wallColor); + + window.draw(pixelColumn); +} + +float World::castRay(float originX, float originY, float orientation) +{ + float deltaX; + float deltaY; + if(orientation < 45 || orientation > 315) + { + deltaX = ; + } + else if(orientation < 135) + { + + } + else if(orientation < 225) + { + + } + else + { + + } + + return(); +} + +void World::render(sf::RenderWindow& window) const +{ + sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(window.getSize().x,window.getSize().y/2.0)); + ground.setFillColor(groundColor); + ground.setPosition(0,window.getSize().y/2.0); + + sf::RectangleShape ceiling = sf::RectangleShape(sf::Vector2f(window.getSize().x,window.getSize().y/2.0)); + ceiling.setFillColor(ceilingColor); + + window.draw(ground); + window.draw(ceiling); + + for(int i = 0;i #include +#include +#include + +#include "Player.h" enum class BlockType { AIR, @@ -17,19 +21,29 @@ enum class BlockType { class World { public: - World(int w, int h, std::vector worldMap = {}); + Player player; + World(int w, int h, sf::Color groundColor = sf::Color::Green, sf::Color ceilingColor = sf::Color::Blue, + std::vector worldMap = {}); int getW() const; int getH() const; BlockType getBlock(float x, float y) const; void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); + void render(sf::RenderWindow&) const; + friend std::ostream& operator<<(std::ostream& ostream, World const & world); private: int w; int h; std::vector map; + + sf::Color groundColor; + sf::Color ceilingColor; + + void fillColumn(sf::RenderWindow&, int column, float scale, sf::Color wallColor = sf::Color(127,127,127)) const; + float castRay(float originX, float originY, float orientation); }; diff --git a/main.cpp b/main.cpp index 3689d6d..803c4d9 100644 --- a/main.cpp +++ b/main.cpp @@ -9,6 +9,22 @@ int main() World world(10,10); world.setBlock(BlockType::AIR,1,1,8,8); world.setBlock(BlockType::WALL,4,4,2,2); + world.player.move(2,2); std::cout << world << std::endl; + + sf::RenderWindow window(sf::VideoMode(800,600),"Da raycasting"); + world.render(window); + window.display(); + + while (window.isOpen()) + { + sf::Event event; + while (window.pollEvent(event)) + { + if (event.type == sf::Event::Closed) + window.close(); + } + } + return 0; } \ No newline at end of file From c7384ba9ba887cdddc92568f6efa8480c1159147 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sat, 30 Dec 2023 14:50:35 +0100 Subject: [PATCH 05/44] Pick back up and comment Clean placeholder code to get it running. Add comments to clarify working, add TODOs in main, add some references for the maths. --- World.cpp | 18 +++++++++++++++--- World.h | 7 +++++++ main.cpp | 13 ++++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/World.cpp b/World.cpp index ec29ffa..9c8ccb0 100644 --- a/World.cpp +++ b/World.cpp @@ -94,11 +94,15 @@ void World::fillColumn(sf::RenderWindow& window, int column, float scale, sf::Co float World::castRay(float originX, float originY, float orientation) { + /* + * Reference used for ray intersection computations : + * https://web.archive.org/web/20220628034315/https://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf + */ float deltaX; float deltaY; if(orientation < 45 || orientation > 315) { - deltaX = ; + } else if(orientation < 135) { @@ -113,11 +117,16 @@ float World::castRay(float originX, float originY, float orientation) } - return(); + return 0; } void World::render(sf::RenderWindow& window) const { + /* + * Draw ground and sky planes through half of the screen, as the walls + * will get drawn over them. + * This doesn't work if we support textures/levels. + */ sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(window.getSize().x,window.getSize().y/2.0)); ground.setFillColor(groundColor); ground.setPosition(0,window.getSize().y/2.0); @@ -128,10 +137,13 @@ void World::render(sf::RenderWindow& window) const window.draw(ground); window.draw(ceiling); + /* + * Throw rays and draw walls over the ceiling and ground. + * Only throws in the plane, which doesn't work for levels/3D. + */ for(int i = 0;i Date: Sat, 30 Dec 2023 14:50:35 +0100 Subject: [PATCH 06/44] Pick back up and comment Clean placeholder code to get it running. Add comments to clarify working, add TODOs in main, add some references for the maths. --- World.cpp | 18 +++++++++++++++--- World.h | 7 +++++++ main.cpp | 13 ++++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/World.cpp b/World.cpp index ec29ffa..9c8ccb0 100644 --- a/World.cpp +++ b/World.cpp @@ -94,11 +94,15 @@ void World::fillColumn(sf::RenderWindow& window, int column, float scale, sf::Co float World::castRay(float originX, float originY, float orientation) { + /* + * Reference used for ray intersection computations : + * https://web.archive.org/web/20220628034315/https://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf + */ float deltaX; float deltaY; if(orientation < 45 || orientation > 315) { - deltaX = ; + } else if(orientation < 135) { @@ -113,11 +117,16 @@ float World::castRay(float originX, float originY, float orientation) } - return(); + return 0; } void World::render(sf::RenderWindow& window) const { + /* + * Draw ground and sky planes through half of the screen, as the walls + * will get drawn over them. + * This doesn't work if we support textures/levels. + */ sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(window.getSize().x,window.getSize().y/2.0)); ground.setFillColor(groundColor); ground.setPosition(0,window.getSize().y/2.0); @@ -128,10 +137,13 @@ void World::render(sf::RenderWindow& window) const window.draw(ground); window.draw(ceiling); + /* + * Throw rays and draw walls over the ceiling and ground. + * Only throws in the plane, which doesn't work for levels/3D. + */ for(int i = 0;i Date: Sat, 30 Dec 2023 14:56:26 +0100 Subject: [PATCH 07/44] License as MIT --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e059819 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-2023 Téo-CD + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From bc9770e233a1f760e5bab872c9ae43cb324815d0 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sat, 30 Dec 2023 14:56:26 +0100 Subject: [PATCH 08/44] License as MIT --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e059819 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-2023 Téo-CD + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 389170eefe8936e40547c5b7a5cdd7592bae8574 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sun, 21 Jan 2024 20:31:51 +0000 Subject: [PATCH 09/44] Player: Fix rotate Rotation wasn't handled properly at all. Properly bound between 0 and 360, and use a proper modulo for floats. Switch to float to have a more fluid rotation between frames. --- Player.cpp | 8 ++++---- Player.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Player.cpp b/Player.cpp index b23f8c5..4f2024d 100644 --- a/Player.cpp +++ b/Player.cpp @@ -14,14 +14,14 @@ void Player::move(float dx, float dy) y += dy; } -void Player::rotate(int alpha) +void Player::rotate(float alpha) { - orientation += alpha%360; - if(orientation >= 360) + orientation += fmodf(alpha, 360); + if(orientation > 360) { orientation -= 360; } - if(orientation <= 360) + else if(orientation < 0) { orientation += 360; } diff --git a/Player.h b/Player.h index 05426c5..fd9cf84 100644 --- a/Player.h +++ b/Player.h @@ -17,7 +17,7 @@ public: float fov = 70; void move(float dx, float dy); - void rotate(int alpha); + void rotate(float alpha); }; From be78434a9c6d5db29f2b81ec324d1ab9e5fb77f5 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sun, 21 Jan 2024 20:31:51 +0000 Subject: [PATCH 10/44] Player: Fix rotate Rotation wasn't handled properly at all. Properly bound between 0 and 360, and use a proper modulo for floats. Switch to float to have a more fluid rotation between frames. --- Player.cpp | 8 ++++---- Player.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Player.cpp b/Player.cpp index b23f8c5..4f2024d 100644 --- a/Player.cpp +++ b/Player.cpp @@ -14,14 +14,14 @@ void Player::move(float dx, float dy) y += dy; } -void Player::rotate(int alpha) +void Player::rotate(float alpha) { - orientation += alpha%360; - if(orientation >= 360) + orientation += fmodf(alpha, 360); + if(orientation > 360) { orientation -= 360; } - if(orientation <= 360) + else if(orientation < 0) { orientation += 360; } diff --git a/Player.h b/Player.h index 05426c5..fd9cf84 100644 --- a/Player.h +++ b/Player.h @@ -17,7 +17,7 @@ public: float fov = 70; void move(float dx, float dy); - void rotate(int alpha); + void rotate(float alpha); }; From 34625a841dc24f7a5e21ee06a9d5ed532c4e4ea8 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sun, 21 Jan 2024 20:41:19 +0000 Subject: [PATCH 11/44] World: Use nicer custom colors --- World.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/World.h b/World.h index eeff8f5..c008cc0 100644 --- a/World.h +++ b/World.h @@ -23,8 +23,10 @@ class World { public: Player player; - World(int w, int h, sf::Color groundColor = sf::Color::Green, sf::Color ceilingColor = sf::Color::Blue, - std::vector worldMap = {}); + World(int w, int h, + sf::Color groundColor = sf::Color{67, 137, 39}, + sf::Color ceilingColor = sf::Color{39, 69, 137}, + std::vector worldMap = {}); int getW() const; int getH() const; @@ -42,7 +44,8 @@ private: sf::Color groundColor; sf::Color ceilingColor; - void fillColumn(sf::RenderWindow&, int column, float scale, sf::Color wallColor = sf::Color(127,127,127)) const; + void fillColumn(sf::RenderWindow&, int column, float scale, + sf::Color wallColor = sf::Color(84,56,34)) const; /** * Cast a ray from a given position and return the on-screen scale. * @param originX Ray X origin From c6b09d668ce58d2159082bc3b779c31eb86d11ed Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sun, 21 Jan 2024 20:41:19 +0000 Subject: [PATCH 12/44] World: Use nicer custom colors --- World.h | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/World.h b/World.h index eeff8f5..c008cc0 100644 --- a/World.h +++ b/World.h @@ -23,8 +23,10 @@ class World { public: Player player; - World(int w, int h, sf::Color groundColor = sf::Color::Green, sf::Color ceilingColor = sf::Color::Blue, - std::vector worldMap = {}); + World(int w, int h, + sf::Color groundColor = sf::Color{67, 137, 39}, + sf::Color ceilingColor = sf::Color{39, 69, 137}, + std::vector worldMap = {}); int getW() const; int getH() const; @@ -42,7 +44,8 @@ private: sf::Color groundColor; sf::Color ceilingColor; - void fillColumn(sf::RenderWindow&, int column, float scale, sf::Color wallColor = sf::Color(127,127,127)) const; + void fillColumn(sf::RenderWindow&, int column, float scale, + sf::Color wallColor = sf::Color(84,56,34)) const; /** * Cast a ray from a given position and return the on-screen scale. * @param originX Ray X origin From b359ff171f663663adc879d6936a507be683fbdf Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sun, 21 Jan 2024 22:08:44 +0000 Subject: [PATCH 13/44] World: Implement raycaster Implement the basic raycaster, checking every block intersection and the block linked to that position. Split the screen uniformally along the width. Add data needed for camera maths in the Player class Specify constraints on the raycast parameters. Render the screen using the raycasts. Call the renderer in the main loop. --- Player.h | 6 +++ World.cpp | 125 ++++++++++++++++++++++++++++++++++++++++++++++++------ World.h | 8 ++-- main.cpp | 15 +++---- 4 files changed, 126 insertions(+), 28 deletions(-) diff --git a/Player.h b/Player.h index fd9cf84..d39e571 100644 --- a/Player.h +++ b/Player.h @@ -5,6 +5,9 @@ #ifndef RAYCASTING_PLAYER_H #define RAYCASTING_PLAYER_H +#include + +static constexpr float deg_to_rad = 3.14159265/180; class Player { public: @@ -14,7 +17,10 @@ public: float y; float orientation; + /* View properties. */ float fov = 70; + float sensorSize = 0.035; /* 35mm, about equivalent to human eye ? */ + float focalLength = sensorSize / (2*tanf((fov*deg_to_rad)/2)); void move(float dx, float dy); void rotate(float alpha); diff --git a/World.cpp b/World.cpp index 9c8ccb0..84be37c 100644 --- a/World.cpp +++ b/World.cpp @@ -3,6 +3,7 @@ // #include "World.h" +#include World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : w(w), h(h), @@ -92,32 +93,119 @@ void World::fillColumn(sf::RenderWindow& window, int column, float scale, sf::Co window.draw(pixelColumn); } -float World::castRay(float originX, float originY, float orientation) +float World::castRay(float originX, float originY, float orientation) const { /* * Reference used for ray intersection computations : * https://web.archive.org/web/20220628034315/https://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf + * The logic is as follows : + * - This computes one set of point per edge crossings (horizontal/vertical) + * - The origin not being confined to the grid, offsets are computed to + * align the intersections properly + * - The intersections are at multiples of the tangent of the relevant + * angle for the axis of interest, and simply on successive edges of + * the grid for the other one + * - Depending on the orientation, signs must be taken into account + * to work 360° */ - float deltaX; - float deltaY; - if(orientation < 45 || orientation > 315) - { + /* Offsets to get back on the grid from the ray's origin. */ + float hOffsetX; + float hOffsetY; + float vOffsetX; + float vOffsetY; + /* Signs controlling the direction of travel. */ + float hDir; + float vDir; + /* Need offset for rounding in the right direction ? */ + float hRound; + float vRound; + + float rads = orientation * deg_to_rad; + /* Used for vertical intersections. */ + float rads_offset = (90 - orientation) * deg_to_rad; + + /* Tangents used for the different axes. */ + float hTan = tanf(rads); + float vTan = tanf(rads_offset); + + /* Check if cos > 0 for horizontal hits formulas. */ + if (orientation < 90 || orientation > 270) { + hOffsetX = ceilf(originY) - originY; + hOffsetY = ceilf(originY); + hDir = +1; + hRound = 0; + } else { + hOffsetX = originY - floorf(originY); + hOffsetY = floorf(originY); + hDir = -1; + hRound = -1; } - else if(orientation < 135) - { + hTan *= hDir; + hOffsetX *= hTan; + /* Check if sin > 0 for vertical hits formulas. */ + if (orientation < 180) { + vOffsetX = ceilf(originX); + vOffsetY = ceilf(originX) - originX; + vDir = +1; + vRound = 0; + } else { + vOffsetX = floorf(originX); + vOffsetY = originX - floorf(originX); + vDir = -1; + vRound = -1; } - else if(orientation < 225) - { + vTan *= vDir; + vOffsetY *= vTan; - } - else - { + /* + * Now we have all the constants and deltas to work with, cast the ray. + * Generated points follow the formulas : + * - h-intersect : (originX + hOffsetX + hTan*i, hOffsetY + hDir*i) + * - v-intersect : (vOffsetX + vDir*i, originY + vOffsetY + vTan*i) + */ + int i = 0; + float hCheckX = originX + hOffsetX; + float hCheckY = hOffsetY; + /* Bounds + sanity check. */ + while (hCheckX >= 0 && hCheckX <= w && hCheckY >= 0 && hCheckY <= h && i < h) { + if (getBlock(floorf(hCheckX), floorf(hCheckY) + hRound) == BlockType::WALL) { + break; + } + hCheckX += hTan; + hCheckY += hDir; + i++; } - return 0; + i = 0; + float vCheckX = vOffsetX; + float vCheckY = originY + vOffsetY; + + /* Bounds + sanity check. */ + while (vCheckX >= 0 && vCheckX < w && vCheckY >= 0 && vCheckY < h && i < w) { + if (getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)) == BlockType::WALL) { + break; + } + + vCheckX += vDir; + vCheckY += vTan; + i++; + } + + /* + * We may or may not have hit something. Check which coordinates are closest + * and use those for computing the apparent size on screen. + */ + float hDist = sqrtf((originX - hCheckX)*(originX - hCheckX) + + (originY - hCheckY)*(originY - hCheckY)); + float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + + (originY - vCheckY)*(originY - vCheckY)); + float finalDist = hDist > vDist ? vDist : hDist; + + /* 2 Is wall height in meters. */ + return player.focalLength*2/finalDist; } void World::render(sf::RenderWindow& window) const @@ -143,7 +231,16 @@ void World::render(sf::RenderWindow& window) const */ for(int i = 0;i 360) { + rayAngle -= 360; + } + float obstacleScale = castRay(player.x, player.y, rayAngle)/player.sensorSize; + fillColumn(window, i, obstacleScale); } +} } diff --git a/World.h b/World.h index c008cc0..91236c0 100644 --- a/World.h +++ b/World.h @@ -48,12 +48,12 @@ private: sf::Color wallColor = sf::Color(84,56,34)) const; /** * Cast a ray from a given position and return the on-screen scale. - * @param originX Ray X origin - * @param originY Ray Y origin - * @param orientation Angle to cast to, in degrees + * @param originX Ray X origin, strictly positive + * @param originY Ray Y origin, strictly positive + * @param orientation Angle to cast to, in degrees between 0 and 360 * @return Scale on the screen of the hit wall. */ - float castRay(float originX, float originY, float orientation); + float castRay(float originX, float originY, float orientation) const; }; diff --git a/main.cpp b/main.cpp index 7b883d3..19879b2 100644 --- a/main.cpp +++ b/main.cpp @@ -1,22 +1,13 @@ #include -#include "SFML/Graphics.hpp" +#include #include "World.h" // TODO: Handle inputs to move player -// TODO: Raycast from player position -// TODO: Fix raycasts and use it correctly -// TODO: Render world in event loop -// TODO: Understand what the code was doing (looks like scale from fill column is the output of the raycast) -// Ref: https://lodev.org/cgtutor/raycasting.html -// Ref: http://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf -// TODO: Update player for camera plane -// TODO: Camera plane represents screen, rays go through it proportional to the screen // TODO: Find a way to go to edges instead of just equally split (?) int main() { - std::cout << "Hello, World!" << std::endl; World world(10,10); world.setBlock(BlockType::AIR,1,1,8,8); world.setBlock(BlockType::WALL,4,4,2,2); @@ -35,6 +26,10 @@ int main() if (event.type == sf::Event::Closed) window.close(); } + window.clear(); + + world.render(window); + window.display(); } return 0; From cad775f88e006458a18e856c12f772c388644037 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sun, 21 Jan 2024 22:03:04 +0000 Subject: [PATCH 14/44] World: Implement raycaster Implement the basic raycaster, checking every block intersection and the block linked to that position. Split the screen uniformally along the width. Add data needed for camera maths in the Player class Specify constraints on the raycast parameters. Render the screen using the raycasts. Call the renderer in the main loop. --- Player.h | 6 +++ World.cpp | 125 ++++++++++++++++++++++++++++++++++++++++++++++++------ World.h | 8 ++-- main.cpp | 15 +++---- 4 files changed, 126 insertions(+), 28 deletions(-) diff --git a/Player.h b/Player.h index fd9cf84..d39e571 100644 --- a/Player.h +++ b/Player.h @@ -5,6 +5,9 @@ #ifndef RAYCASTING_PLAYER_H #define RAYCASTING_PLAYER_H +#include + +static constexpr float deg_to_rad = 3.14159265/180; class Player { public: @@ -14,7 +17,10 @@ public: float y; float orientation; + /* View properties. */ float fov = 70; + float sensorSize = 0.035; /* 35mm, about equivalent to human eye ? */ + float focalLength = sensorSize / (2*tanf((fov*deg_to_rad)/2)); void move(float dx, float dy); void rotate(float alpha); diff --git a/World.cpp b/World.cpp index 9c8ccb0..84be37c 100644 --- a/World.cpp +++ b/World.cpp @@ -3,6 +3,7 @@ // #include "World.h" +#include World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : w(w), h(h), @@ -92,32 +93,119 @@ void World::fillColumn(sf::RenderWindow& window, int column, float scale, sf::Co window.draw(pixelColumn); } -float World::castRay(float originX, float originY, float orientation) +float World::castRay(float originX, float originY, float orientation) const { /* * Reference used for ray intersection computations : * https://web.archive.org/web/20220628034315/https://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf + * The logic is as follows : + * - This computes one set of point per edge crossings (horizontal/vertical) + * - The origin not being confined to the grid, offsets are computed to + * align the intersections properly + * - The intersections are at multiples of the tangent of the relevant + * angle for the axis of interest, and simply on successive edges of + * the grid for the other one + * - Depending on the orientation, signs must be taken into account + * to work 360° */ - float deltaX; - float deltaY; - if(orientation < 45 || orientation > 315) - { + /* Offsets to get back on the grid from the ray's origin. */ + float hOffsetX; + float hOffsetY; + float vOffsetX; + float vOffsetY; + /* Signs controlling the direction of travel. */ + float hDir; + float vDir; + /* Need offset for rounding in the right direction ? */ + float hRound; + float vRound; + + float rads = orientation * deg_to_rad; + /* Used for vertical intersections. */ + float rads_offset = (90 - orientation) * deg_to_rad; + + /* Tangents used for the different axes. */ + float hTan = tanf(rads); + float vTan = tanf(rads_offset); + + /* Check if cos > 0 for horizontal hits formulas. */ + if (orientation < 90 || orientation > 270) { + hOffsetX = ceilf(originY) - originY; + hOffsetY = ceilf(originY); + hDir = +1; + hRound = 0; + } else { + hOffsetX = originY - floorf(originY); + hOffsetY = floorf(originY); + hDir = -1; + hRound = -1; } - else if(orientation < 135) - { + hTan *= hDir; + hOffsetX *= hTan; + /* Check if sin > 0 for vertical hits formulas. */ + if (orientation < 180) { + vOffsetX = ceilf(originX); + vOffsetY = ceilf(originX) - originX; + vDir = +1; + vRound = 0; + } else { + vOffsetX = floorf(originX); + vOffsetY = originX - floorf(originX); + vDir = -1; + vRound = -1; } - else if(orientation < 225) - { + vTan *= vDir; + vOffsetY *= vTan; - } - else - { + /* + * Now we have all the constants and deltas to work with, cast the ray. + * Generated points follow the formulas : + * - h-intersect : (originX + hOffsetX + hTan*i, hOffsetY + hDir*i) + * - v-intersect : (vOffsetX + vDir*i, originY + vOffsetY + vTan*i) + */ + int i = 0; + float hCheckX = originX + hOffsetX; + float hCheckY = hOffsetY; + /* Bounds + sanity check. */ + while (hCheckX >= 0 && hCheckX <= w && hCheckY >= 0 && hCheckY <= h && i < h) { + if (getBlock(floorf(hCheckX), floorf(hCheckY) + hRound) == BlockType::WALL) { + break; + } + hCheckX += hTan; + hCheckY += hDir; + i++; } - return 0; + i = 0; + float vCheckX = vOffsetX; + float vCheckY = originY + vOffsetY; + + /* Bounds + sanity check. */ + while (vCheckX >= 0 && vCheckX < w && vCheckY >= 0 && vCheckY < h && i < w) { + if (getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)) == BlockType::WALL) { + break; + } + + vCheckX += vDir; + vCheckY += vTan; + i++; + } + + /* + * We may or may not have hit something. Check which coordinates are closest + * and use those for computing the apparent size on screen. + */ + float hDist = sqrtf((originX - hCheckX)*(originX - hCheckX) + + (originY - hCheckY)*(originY - hCheckY)); + float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + + (originY - vCheckY)*(originY - vCheckY)); + float finalDist = hDist > vDist ? vDist : hDist; + + /* 2 Is wall height in meters. */ + return player.focalLength*2/finalDist; } void World::render(sf::RenderWindow& window) const @@ -143,7 +231,16 @@ void World::render(sf::RenderWindow& window) const */ for(int i = 0;i 360) { + rayAngle -= 360; + } + float obstacleScale = castRay(player.x, player.y, rayAngle)/player.sensorSize; + fillColumn(window, i, obstacleScale); } +} } diff --git a/World.h b/World.h index c008cc0..91236c0 100644 --- a/World.h +++ b/World.h @@ -48,12 +48,12 @@ private: sf::Color wallColor = sf::Color(84,56,34)) const; /** * Cast a ray from a given position and return the on-screen scale. - * @param originX Ray X origin - * @param originY Ray Y origin - * @param orientation Angle to cast to, in degrees + * @param originX Ray X origin, strictly positive + * @param originY Ray Y origin, strictly positive + * @param orientation Angle to cast to, in degrees between 0 and 360 * @return Scale on the screen of the hit wall. */ - float castRay(float originX, float originY, float orientation); + float castRay(float originX, float originY, float orientation) const; }; diff --git a/main.cpp b/main.cpp index 7b883d3..19879b2 100644 --- a/main.cpp +++ b/main.cpp @@ -1,22 +1,13 @@ #include -#include "SFML/Graphics.hpp" +#include #include "World.h" // TODO: Handle inputs to move player -// TODO: Raycast from player position -// TODO: Fix raycasts and use it correctly -// TODO: Render world in event loop -// TODO: Understand what the code was doing (looks like scale from fill column is the output of the raycast) -// Ref: https://lodev.org/cgtutor/raycasting.html -// Ref: http://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf -// TODO: Update player for camera plane -// TODO: Camera plane represents screen, rays go through it proportional to the screen // TODO: Find a way to go to edges instead of just equally split (?) int main() { - std::cout << "Hello, World!" << std::endl; World world(10,10); world.setBlock(BlockType::AIR,1,1,8,8); world.setBlock(BlockType::WALL,4,4,2,2); @@ -35,6 +26,10 @@ int main() if (event.type == sf::Event::Closed) window.close(); } + window.clear(); + + world.render(window); + window.display(); } return 0; From 804f0272f821eab56021e464a3b423fc69297dcb Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sun, 21 Jan 2024 22:11:46 +0000 Subject: [PATCH 15/44] Implement moving the player Player: Add rotation and linear speeds, as well as their current state. Player: Allow to move in the local coordinates and compute world-speed equivalent. Player: Update movement axis while rotating. World: Add a function to advance a step. Main: Handle key events, add world step in the main loop. --- Player.cpp | 17 +++++++++++++++++ Player.h | 9 +++++++++ World.cpp | 9 +++++++++ World.h | 8 +++++++- main.cpp | 40 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 80 insertions(+), 3 deletions(-) diff --git a/Player.cpp b/Player.cpp index 4f2024d..b6fa26c 100644 --- a/Player.cpp +++ b/Player.cpp @@ -25,4 +25,21 @@ void Player::rotate(float alpha) { orientation += 360; } + + /* + * Rotate the movement vector along the new angle, assumes that the only + * speed influencing the player is its own movement. + */ + float prevSpeedX = currentMoveSpeedX; + float prevSpeedY = currentMoveSpeedY; + currentMoveSpeedX = cosf(-alpha * deg_to_rad) * prevSpeedX + - sinf(-alpha * deg_to_rad) * prevSpeedY; + currentMoveSpeedY = sinf(-alpha * deg_to_rad) * prevSpeedX + + cosf(-alpha * deg_to_rad) * prevSpeedY; +} + +void Player::updateSpeed(float localX, float localY) { + // TODO: Support strafing. + currentMoveSpeedX = localX * sinf(orientation*deg_to_rad)*moveSpeed; + currentMoveSpeedY = localX * cosf(orientation*deg_to_rad)*moveSpeed; } diff --git a/Player.h b/Player.h index d39e571..eab2795 100644 --- a/Player.h +++ b/Player.h @@ -17,6 +17,14 @@ public: float y; float orientation; + float moveSpeed = 5; + float rotationSpeed = 180; + + float currentMoveSpeedX = 0; + float currentMoveSpeedY = 0; + + float currentRotationSpeed = 0; + /* View properties. */ float fov = 70; float sensorSize = 0.035; /* 35mm, about equivalent to human eye ? */ @@ -24,6 +32,7 @@ public: void move(float dx, float dy); void rotate(float alpha); + void updateSpeed(float localX, float localY); }; diff --git a/World.cpp b/World.cpp index 84be37c..f632b86 100644 --- a/World.cpp +++ b/World.cpp @@ -243,4 +243,13 @@ void World::render(sf::RenderWindow& window) const } } +void World::step(const float& stepTime) { + player.move(player.currentMoveSpeedX*stepTime, + player.currentMoveSpeedY*stepTime); + /* Undo last move if the player would end up in a wall. */ + if (getBlock((int)player.x, (int)player.y) != BlockType::AIR) { + player.move(-player.currentMoveSpeedX*stepTime, + -player.currentMoveSpeedY*stepTime); + } + player.rotate(player.currentRotationSpeed*stepTime); } diff --git a/World.h b/World.h index 91236c0..c098e68 100644 --- a/World.h +++ b/World.h @@ -30,11 +30,17 @@ public: int getW() const; int getH() const; - BlockType getBlock(float x, float y) const; + inline BlockType getBlock(float x, float y) const; void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); void render(sf::RenderWindow&) const; + /** + * Move the world one step forward. + * @param stepTime delta time since last step, in seconds + */ + void step(const float& stepTime); + friend std::ostream& operator<<(std::ostream& ostream, World const & world); private: int w; diff --git a/main.cpp b/main.cpp index 19879b2..73c4dfc 100644 --- a/main.cpp +++ b/main.cpp @@ -3,7 +3,6 @@ #include "World.h" -// TODO: Handle inputs to move player // TODO: Find a way to go to edges instead of just equally split (?) int main() @@ -18,16 +17,53 @@ int main() world.render(window); window.display(); + sf::Event event{}; + sf::Clock frameTime; while (window.isOpen()) { - sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); + else if (event.type == sf::Event::KeyPressed) { + switch (event.key.code) { + case sf::Keyboard::Key::Escape: + window.close(); + break; + case sf::Keyboard::Key::Left: + world.player.currentRotationSpeed = -world.player.rotationSpeed; + break; + case sf::Keyboard::Key::Right: + world.player.currentRotationSpeed = world.player.rotationSpeed; + break; + case sf::Keyboard::Key::Up: + world.player.updateSpeed(1, 0); + break; + case sf::Keyboard::Key::Down: + world.player.updateSpeed(-1, 0); + break; + default: + break; + } + } + else if (event.type == sf::Event::KeyReleased) { + switch (event.key.code) { + case sf::Keyboard::Key::Left: + case sf::Keyboard::Key::Right: + world.player.currentRotationSpeed = 0; + break; + case sf::Keyboard::Key::Up: + case sf::Keyboard::Key::Down: + world.player.updateSpeed(0, 0); + break; + default: + break; + } + } } window.clear(); + world.step(frameTime.restart().asSeconds()); world.render(window); window.display(); } From b47052aa4d3a48fdb5c17abe423c2fe9da60db69 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sun, 21 Jan 2024 22:11:46 +0000 Subject: [PATCH 16/44] Implement moving the player Player: Add rotation and linear speeds, as well as their current state. Player: Allow to move in the local coordinates and compute world-speed equivalent. Player: Update movement axis while rotating. World: Add a function to advance a step. Main: Handle key events, add world step in the main loop. --- Player.cpp | 17 +++++++++++++++++ Player.h | 9 +++++++++ World.cpp | 9 +++++++++ World.h | 8 +++++++- main.cpp | 40 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 80 insertions(+), 3 deletions(-) diff --git a/Player.cpp b/Player.cpp index 4f2024d..b6fa26c 100644 --- a/Player.cpp +++ b/Player.cpp @@ -25,4 +25,21 @@ void Player::rotate(float alpha) { orientation += 360; } + + /* + * Rotate the movement vector along the new angle, assumes that the only + * speed influencing the player is its own movement. + */ + float prevSpeedX = currentMoveSpeedX; + float prevSpeedY = currentMoveSpeedY; + currentMoveSpeedX = cosf(-alpha * deg_to_rad) * prevSpeedX + - sinf(-alpha * deg_to_rad) * prevSpeedY; + currentMoveSpeedY = sinf(-alpha * deg_to_rad) * prevSpeedX + + cosf(-alpha * deg_to_rad) * prevSpeedY; +} + +void Player::updateSpeed(float localX, float localY) { + // TODO: Support strafing. + currentMoveSpeedX = localX * sinf(orientation*deg_to_rad)*moveSpeed; + currentMoveSpeedY = localX * cosf(orientation*deg_to_rad)*moveSpeed; } diff --git a/Player.h b/Player.h index d39e571..eab2795 100644 --- a/Player.h +++ b/Player.h @@ -17,6 +17,14 @@ public: float y; float orientation; + float moveSpeed = 5; + float rotationSpeed = 180; + + float currentMoveSpeedX = 0; + float currentMoveSpeedY = 0; + + float currentRotationSpeed = 0; + /* View properties. */ float fov = 70; float sensorSize = 0.035; /* 35mm, about equivalent to human eye ? */ @@ -24,6 +32,7 @@ public: void move(float dx, float dy); void rotate(float alpha); + void updateSpeed(float localX, float localY); }; diff --git a/World.cpp b/World.cpp index 84be37c..f632b86 100644 --- a/World.cpp +++ b/World.cpp @@ -243,4 +243,13 @@ void World::render(sf::RenderWindow& window) const } } +void World::step(const float& stepTime) { + player.move(player.currentMoveSpeedX*stepTime, + player.currentMoveSpeedY*stepTime); + /* Undo last move if the player would end up in a wall. */ + if (getBlock((int)player.x, (int)player.y) != BlockType::AIR) { + player.move(-player.currentMoveSpeedX*stepTime, + -player.currentMoveSpeedY*stepTime); + } + player.rotate(player.currentRotationSpeed*stepTime); } diff --git a/World.h b/World.h index 91236c0..c098e68 100644 --- a/World.h +++ b/World.h @@ -30,11 +30,17 @@ public: int getW() const; int getH() const; - BlockType getBlock(float x, float y) const; + inline BlockType getBlock(float x, float y) const; void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); void render(sf::RenderWindow&) const; + /** + * Move the world one step forward. + * @param stepTime delta time since last step, in seconds + */ + void step(const float& stepTime); + friend std::ostream& operator<<(std::ostream& ostream, World const & world); private: int w; diff --git a/main.cpp b/main.cpp index 19879b2..73c4dfc 100644 --- a/main.cpp +++ b/main.cpp @@ -3,7 +3,6 @@ #include "World.h" -// TODO: Handle inputs to move player // TODO: Find a way to go to edges instead of just equally split (?) int main() @@ -18,16 +17,53 @@ int main() world.render(window); window.display(); + sf::Event event{}; + sf::Clock frameTime; while (window.isOpen()) { - sf::Event event; while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); + else if (event.type == sf::Event::KeyPressed) { + switch (event.key.code) { + case sf::Keyboard::Key::Escape: + window.close(); + break; + case sf::Keyboard::Key::Left: + world.player.currentRotationSpeed = -world.player.rotationSpeed; + break; + case sf::Keyboard::Key::Right: + world.player.currentRotationSpeed = world.player.rotationSpeed; + break; + case sf::Keyboard::Key::Up: + world.player.updateSpeed(1, 0); + break; + case sf::Keyboard::Key::Down: + world.player.updateSpeed(-1, 0); + break; + default: + break; + } + } + else if (event.type == sf::Event::KeyReleased) { + switch (event.key.code) { + case sf::Keyboard::Key::Left: + case sf::Keyboard::Key::Right: + world.player.currentRotationSpeed = 0; + break; + case sf::Keyboard::Key::Up: + case sf::Keyboard::Key::Down: + world.player.updateSpeed(0, 0); + break; + default: + break; + } + } } window.clear(); + world.step(frameTime.restart().asSeconds()); world.render(window); window.display(); } From 7fe17df587df778252abbc609b8d524589a108ec Mon Sep 17 00:00:00 2001 From: trotFunky Date: Wed, 24 Jan 2024 23:09:59 +0000 Subject: [PATCH 17/44] main: Clean up, handle resize Handle window resizes by resizing the view frame as well. Clean up the event handling to allow interleaving other code between different checks. Remove early first frame, handle it like the others. --- main.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/main.cpp b/main.cpp index 73c4dfc..5b9c8d3 100644 --- a/main.cpp +++ b/main.cpp @@ -14,8 +14,7 @@ int main() std::cout << world << std::endl; sf::RenderWindow window(sf::VideoMode(800,600),"Da raycasting"); - world.render(window); - window.display(); + sf::RenderWindow window(sf::VideoMode(1000,1000),"Da raycasting"); sf::Event event{}; sf::Clock frameTime; @@ -23,9 +22,19 @@ int main() { while (window.pollEvent(event)) { - if (event.type == sf::Event::Closed) - window.close(); - else if (event.type == sf::Event::KeyPressed) { + if (event.type == sf::Event::Closed) { + window.close(); + continue; + } + if (event.type == sf::Event::Resized) { + // Keep the view area fit to the window. + sf::FloatRect newView(0, 0, + static_cast(event.size.width), + static_cast(event.size.height)); + window.setView(sf::View(newView)); + continue; + } + if (event.type == sf::Event::KeyPressed) { switch (event.key.code) { case sf::Keyboard::Key::Escape: window.close(); From 4a95664342833931836b9b60c588e5bb8413b184 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Wed, 24 Jan 2024 23:09:59 +0000 Subject: [PATCH 18/44] main: Clean up, handle resize Handle window resizes by resizing the view frame as well. Clean up the event handling to allow interleaving other code between different checks. Remove early first frame, handle it like the others. --- main.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/main.cpp b/main.cpp index 73c4dfc..5b9c8d3 100644 --- a/main.cpp +++ b/main.cpp @@ -14,8 +14,7 @@ int main() std::cout << world << std::endl; sf::RenderWindow window(sf::VideoMode(800,600),"Da raycasting"); - world.render(window); - window.display(); + sf::RenderWindow window(sf::VideoMode(1000,1000),"Da raycasting"); sf::Event event{}; sf::Clock frameTime; @@ -23,9 +22,19 @@ int main() { while (window.pollEvent(event)) { - if (event.type == sf::Event::Closed) - window.close(); - else if (event.type == sf::Event::KeyPressed) { + if (event.type == sf::Event::Closed) { + window.close(); + continue; + } + if (event.type == sf::Event::Resized) { + // Keep the view area fit to the window. + sf::FloatRect newView(0, 0, + static_cast(event.size.width), + static_cast(event.size.height)); + window.setView(sf::View(newView)); + continue; + } + if (event.type == sf::Event::KeyPressed) { switch (event.key.code) { case sf::Keyboard::Key::Escape: window.close(); From fa75fe4bba0f555a915918296a42164fe2bc08cd Mon Sep 17 00:00:00 2001 From: trotFunky Date: Wed, 24 Jan 2024 23:16:59 +0000 Subject: [PATCH 19/44] World: Clean warnings, optimize GetBlock World has a lot of type conversion warnings. Take care of most of them, for a slight performance hit ( :( ). Re-order constructor parameters to match declaration. Move FillColumn to a more appropriate place. GetBlock gets called a lot : use direct memory access of the vector rather than going through bounds checks with .at(). Introduce an integer overload to remove warnings. --- World.cpp | 54 +++++++++++++++++++++++++++++++++--------------------- World.h | 7 ++++--- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/World.cpp b/World.cpp index f632b86..40120ed 100644 --- a/World.cpp +++ b/World.cpp @@ -6,8 +6,9 @@ #include -World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : w(w), h(h), - groundColor(groundColor), ceilingColor(ceilingColor), map(std::move(worldMap)), player(0,0,0) +World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : + player(0,0,0), w(w), h(h), map(std::move(worldMap)), + groundColor(groundColor), ceilingColor(ceilingColor) { map.resize(w*h,BlockType::WALL); } @@ -22,9 +23,14 @@ int World::getH() const return h; } +BlockType World::getBlock(int x, int y) const +{ + return map[x + w*y]; +} + BlockType World::getBlock(float x, float y) const { - return(map.at(static_cast(x)+w* static_cast(y))); + return map[static_cast(x) + w*static_cast(y)]; } void World::setBlock(BlockType block, int x, int y, int width, int height) @@ -83,16 +89,6 @@ std::ostream& operator<<(std::ostream& ostream, World const& world) return(ostream); } -void World::fillColumn(sf::RenderWindow& window, int column, float scale, sf::Color wallColor) const -{ - float columnHeight = window.getSize().y*scale; - sf::RectangleShape pixelColumn(sf::Vector2f(1,columnHeight)); - pixelColumn.setPosition(column,(window.getSize().y-columnHeight)/2.0); - pixelColumn.setFillColor(wallColor); - - window.draw(pixelColumn); -} - float World::castRay(float originX, float originY, float orientation) const { /* @@ -169,7 +165,8 @@ float World::castRay(float originX, float originY, float orientation) const float hCheckX = originX + hOffsetX; float hCheckY = hOffsetY; /* Bounds + sanity check. */ - while (hCheckX >= 0 && hCheckX <= w && hCheckY >= 0 && hCheckY <= h && i < h) { + while (hCheckX >= 0 && hCheckX <= static_cast(w) && + hCheckY >= 0 && hCheckY <= static_cast(h) && i < h) { if (getBlock(floorf(hCheckX), floorf(hCheckY) + hRound) == BlockType::WALL) { break; } @@ -184,7 +181,8 @@ float World::castRay(float originX, float originY, float orientation) const float vCheckY = originY + vOffsetY; /* Bounds + sanity check. */ - while (vCheckX >= 0 && vCheckX < w && vCheckY >= 0 && vCheckY < h && i < w) { + while (vCheckX >= 0 && vCheckX < static_cast(w) && + vCheckY >= 0 && vCheckY < static_cast(h) && i < w) { if (getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)) == BlockType::WALL) { break; } @@ -208,18 +206,32 @@ float World::castRay(float originX, float originY, float orientation) const return player.focalLength*2/finalDist; } +void World::fillColumn(sf::RenderWindow& window, unsigned int column, + float scale, sf::Color wallColor) const +{ + float columnHeight = static_cast(window.getSize().y)*scale; + sf::RectangleShape pixelColumn(sf::Vector2f(1,columnHeight)); + pixelColumn.setPosition(static_cast(column), + (static_cast(window.getSize().y)-columnHeight)/2.0f); + pixelColumn.setFillColor(wallColor); + + window.draw(pixelColumn); +} + void World::render(sf::RenderWindow& window) const { + float windowX = static_cast(window.getSize().x); + float windowY = static_cast(window.getSize().y); /* * Draw ground and sky planes through half of the screen, as the walls * will get drawn over them. * This doesn't work if we support textures/levels. */ - sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(window.getSize().x,window.getSize().y/2.0)); + sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); ground.setFillColor(groundColor); - ground.setPosition(0,window.getSize().y/2.0); + ground.setPosition(0,windowY/2.0f); - sf::RectangleShape ceiling = sf::RectangleShape(sf::Vector2f(window.getSize().x,window.getSize().y/2.0)); + sf::RectangleShape ceiling = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); ceiling.setFillColor(ceilingColor); window.draw(ground); @@ -229,9 +241,9 @@ void World::render(sf::RenderWindow& window) const * Throw rays and draw walls over the ceiling and ground. * Only throws in the plane, which doesn't work for levels/3D. */ - for(int i = 0;i(i)-windowX/2.0f); float rayAngle = player.orientation + deltaAngle; if (rayAngle < 0) { rayAngle += 360; @@ -247,7 +259,7 @@ void World::step(const float& stepTime) { player.move(player.currentMoveSpeedX*stepTime, player.currentMoveSpeedY*stepTime); /* Undo last move if the player would end up in a wall. */ - if (getBlock((int)player.x, (int)player.y) != BlockType::AIR) { + if (getBlock(player.x, player.y) != BlockType::AIR) { player.move(-player.currentMoveSpeedX*stepTime, -player.currentMoveSpeedY*stepTime); } diff --git a/World.h b/World.h index c098e68..7f716e3 100644 --- a/World.h +++ b/World.h @@ -30,8 +30,9 @@ public: int getW() const; int getH() const; - inline BlockType getBlock(float x, float y) const; - void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); + inline BlockType getBlock(int x, int y) const; + inline BlockType getBlock(float x, float y) const; + void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); void render(sf::RenderWindow&) const; @@ -50,7 +51,7 @@ private: sf::Color groundColor; sf::Color ceilingColor; - void fillColumn(sf::RenderWindow&, int column, float scale, + void fillColumn(sf::RenderWindow&, unsigned int column, float scale, sf::Color wallColor = sf::Color(84,56,34)) const; /** * Cast a ray from a given position and return the on-screen scale. From ddb01d05096772dbb2a0bb471f053148a8ad7df9 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Wed, 24 Jan 2024 23:16:44 +0000 Subject: [PATCH 20/44] World: Clean warnings, optimize GetBlock World has a lot of type conversion warnings. Take care of most of them, for a slight performance hit ( :( ). Re-order constructor parameters to match declaration. Move FillColumn to a more appropriate place. GetBlock gets called a lot : use direct memory access of the vector rather than going through bounds checks with .at(). Introduce an integer overload to remove warnings. --- World.cpp | 54 +++++++++++++++++++++++++++++++++--------------------- World.h | 7 ++++--- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/World.cpp b/World.cpp index f632b86..40120ed 100644 --- a/World.cpp +++ b/World.cpp @@ -6,8 +6,9 @@ #include -World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : w(w), h(h), - groundColor(groundColor), ceilingColor(ceilingColor), map(std::move(worldMap)), player(0,0,0) +World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : + player(0,0,0), w(w), h(h), map(std::move(worldMap)), + groundColor(groundColor), ceilingColor(ceilingColor) { map.resize(w*h,BlockType::WALL); } @@ -22,9 +23,14 @@ int World::getH() const return h; } +BlockType World::getBlock(int x, int y) const +{ + return map[x + w*y]; +} + BlockType World::getBlock(float x, float y) const { - return(map.at(static_cast(x)+w* static_cast(y))); + return map[static_cast(x) + w*static_cast(y)]; } void World::setBlock(BlockType block, int x, int y, int width, int height) @@ -83,16 +89,6 @@ std::ostream& operator<<(std::ostream& ostream, World const& world) return(ostream); } -void World::fillColumn(sf::RenderWindow& window, int column, float scale, sf::Color wallColor) const -{ - float columnHeight = window.getSize().y*scale; - sf::RectangleShape pixelColumn(sf::Vector2f(1,columnHeight)); - pixelColumn.setPosition(column,(window.getSize().y-columnHeight)/2.0); - pixelColumn.setFillColor(wallColor); - - window.draw(pixelColumn); -} - float World::castRay(float originX, float originY, float orientation) const { /* @@ -169,7 +165,8 @@ float World::castRay(float originX, float originY, float orientation) const float hCheckX = originX + hOffsetX; float hCheckY = hOffsetY; /* Bounds + sanity check. */ - while (hCheckX >= 0 && hCheckX <= w && hCheckY >= 0 && hCheckY <= h && i < h) { + while (hCheckX >= 0 && hCheckX <= static_cast(w) && + hCheckY >= 0 && hCheckY <= static_cast(h) && i < h) { if (getBlock(floorf(hCheckX), floorf(hCheckY) + hRound) == BlockType::WALL) { break; } @@ -184,7 +181,8 @@ float World::castRay(float originX, float originY, float orientation) const float vCheckY = originY + vOffsetY; /* Bounds + sanity check. */ - while (vCheckX >= 0 && vCheckX < w && vCheckY >= 0 && vCheckY < h && i < w) { + while (vCheckX >= 0 && vCheckX < static_cast(w) && + vCheckY >= 0 && vCheckY < static_cast(h) && i < w) { if (getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)) == BlockType::WALL) { break; } @@ -208,18 +206,32 @@ float World::castRay(float originX, float originY, float orientation) const return player.focalLength*2/finalDist; } +void World::fillColumn(sf::RenderWindow& window, unsigned int column, + float scale, sf::Color wallColor) const +{ + float columnHeight = static_cast(window.getSize().y)*scale; + sf::RectangleShape pixelColumn(sf::Vector2f(1,columnHeight)); + pixelColumn.setPosition(static_cast(column), + (static_cast(window.getSize().y)-columnHeight)/2.0f); + pixelColumn.setFillColor(wallColor); + + window.draw(pixelColumn); +} + void World::render(sf::RenderWindow& window) const { + float windowX = static_cast(window.getSize().x); + float windowY = static_cast(window.getSize().y); /* * Draw ground and sky planes through half of the screen, as the walls * will get drawn over them. * This doesn't work if we support textures/levels. */ - sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(window.getSize().x,window.getSize().y/2.0)); + sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); ground.setFillColor(groundColor); - ground.setPosition(0,window.getSize().y/2.0); + ground.setPosition(0,windowY/2.0f); - sf::RectangleShape ceiling = sf::RectangleShape(sf::Vector2f(window.getSize().x,window.getSize().y/2.0)); + sf::RectangleShape ceiling = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); ceiling.setFillColor(ceilingColor); window.draw(ground); @@ -229,9 +241,9 @@ void World::render(sf::RenderWindow& window) const * Throw rays and draw walls over the ceiling and ground. * Only throws in the plane, which doesn't work for levels/3D. */ - for(int i = 0;i(i)-windowX/2.0f); float rayAngle = player.orientation + deltaAngle; if (rayAngle < 0) { rayAngle += 360; @@ -247,7 +259,7 @@ void World::step(const float& stepTime) { player.move(player.currentMoveSpeedX*stepTime, player.currentMoveSpeedY*stepTime); /* Undo last move if the player would end up in a wall. */ - if (getBlock((int)player.x, (int)player.y) != BlockType::AIR) { + if (getBlock(player.x, player.y) != BlockType::AIR) { player.move(-player.currentMoveSpeedX*stepTime, -player.currentMoveSpeedY*stepTime); } diff --git a/World.h b/World.h index c098e68..7f716e3 100644 --- a/World.h +++ b/World.h @@ -30,8 +30,9 @@ public: int getW() const; int getH() const; - inline BlockType getBlock(float x, float y) const; - void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); + inline BlockType getBlock(int x, int y) const; + inline BlockType getBlock(float x, float y) const; + void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); void render(sf::RenderWindow&) const; @@ -50,7 +51,7 @@ private: sf::Color groundColor; sf::Color ceilingColor; - void fillColumn(sf::RenderWindow&, int column, float scale, + void fillColumn(sf::RenderWindow&, unsigned int column, float scale, sf::Color wallColor = sf::Color(84,56,34)) const; /** * Cast a ray from a given position and return the on-screen scale. From 81aaafd26d516c723a3b58459cab42fb87f8d730 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Thu, 25 Jan 2024 22:31:39 +0000 Subject: [PATCH 21/44] World: make CastRay generic Previously CastRay did the scale computation and used the player view statistics. This makes it quite specialized, and the rest of the computations are made outside. Move the computations in the render function and make castRay generic. --- World.cpp | 7 +++---- World.h | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/World.cpp b/World.cpp index 40120ed..1ca062c 100644 --- a/World.cpp +++ b/World.cpp @@ -200,10 +200,8 @@ float World::castRay(float originX, float originY, float orientation) const (originY - hCheckY)*(originY - hCheckY)); float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + (originY - vCheckY)*(originY - vCheckY)); - float finalDist = hDist > vDist ? vDist : hDist; - /* 2 Is wall height in meters. */ - return player.focalLength*2/finalDist; + return hDist > vDist ? vDist : hDist; } void World::fillColumn(sf::RenderWindow& window, unsigned int column, @@ -250,7 +248,8 @@ void World::render(sf::RenderWindow& window) const } else if (rayAngle > 360) { rayAngle -= 360; } - float obstacleScale = castRay(player.x, player.y, rayAngle)/player.sensorSize; + float obstacleScale = player.focalLength*2/(castRay(player.x, player.y, rayAngle)*player.sensorSize); + /* 2 Is wall height in meters. */ fillColumn(window, i, obstacleScale); } } diff --git a/World.h b/World.h index 7f716e3..6ecf4ab 100644 --- a/World.h +++ b/World.h @@ -54,11 +54,11 @@ private: void fillColumn(sf::RenderWindow&, unsigned int column, float scale, sf::Color wallColor = sf::Color(84,56,34)) const; /** - * Cast a ray from a given position and return the on-screen scale. + * Cast a ray from a given position and its distance to the origin. * @param originX Ray X origin, strictly positive * @param originY Ray Y origin, strictly positive * @param orientation Angle to cast to, in degrees between 0 and 360 - * @return Scale on the screen of the hit wall. + * @return Distance of the hit to the origin. */ float castRay(float originX, float originY, float orientation) const; }; From a0b65364325bd33fc54bfb1ae3c7cfd77318ed19 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Thu, 25 Jan 2024 22:30:38 +0000 Subject: [PATCH 22/44] World: make CastRay generic Previously CastRay did the scale computation and used the player view statistics. This makes it quite specialized, and the rest of the computations are made outside. Move the computations in the render function and make castRay generic. --- World.cpp | 7 +++---- World.h | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/World.cpp b/World.cpp index 40120ed..1ca062c 100644 --- a/World.cpp +++ b/World.cpp @@ -200,10 +200,8 @@ float World::castRay(float originX, float originY, float orientation) const (originY - hCheckY)*(originY - hCheckY)); float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + (originY - vCheckY)*(originY - vCheckY)); - float finalDist = hDist > vDist ? vDist : hDist; - /* 2 Is wall height in meters. */ - return player.focalLength*2/finalDist; + return hDist > vDist ? vDist : hDist; } void World::fillColumn(sf::RenderWindow& window, unsigned int column, @@ -250,7 +248,8 @@ void World::render(sf::RenderWindow& window) const } else if (rayAngle > 360) { rayAngle -= 360; } - float obstacleScale = castRay(player.x, player.y, rayAngle)/player.sensorSize; + float obstacleScale = player.focalLength*2/(castRay(player.x, player.y, rayAngle)*player.sensorSize); + /* 2 Is wall height in meters. */ fillColumn(window, i, obstacleScale); } } diff --git a/World.h b/World.h index 7f716e3..6ecf4ab 100644 --- a/World.h +++ b/World.h @@ -54,11 +54,11 @@ private: void fillColumn(sf::RenderWindow&, unsigned int column, float scale, sf::Color wallColor = sf::Color(84,56,34)) const; /** - * Cast a ray from a given position and return the on-screen scale. + * Cast a ray from a given position and its distance to the origin. * @param originX Ray X origin, strictly positive * @param originY Ray Y origin, strictly positive * @param orientation Angle to cast to, in degrees between 0 and 360 - * @return Scale on the screen of the hit wall. + * @return Distance of the hit to the origin. */ float castRay(float originX, float originY, float orientation) const; }; From 52fdd8328fe62b565883224361bce36acb95a3e4 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Fri, 26 Jan 2024 21:34:13 +0000 Subject: [PATCH 23/44] World: Rename wallColor to fillColor As this function will get more generic, make the names more generic. Fix some comments and indentation. --- World.cpp | 6 +++--- World.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/World.cpp b/World.cpp index 1ca062c..dea9d5b 100644 --- a/World.cpp +++ b/World.cpp @@ -205,13 +205,13 @@ float World::castRay(float originX, float originY, float orientation) const } void World::fillColumn(sf::RenderWindow& window, unsigned int column, - float scale, sf::Color wallColor) const + float scale, sf::Color fillColor) const { float columnHeight = static_cast(window.getSize().y)*scale; sf::RectangleShape pixelColumn(sf::Vector2f(1,columnHeight)); pixelColumn.setPosition(static_cast(column), (static_cast(window.getSize().y)-columnHeight)/2.0f); - pixelColumn.setFillColor(wallColor); + pixelColumn.setFillColor(fillColor); window.draw(pixelColumn); } @@ -249,7 +249,7 @@ void World::render(sf::RenderWindow& window) const rayAngle -= 360; } float obstacleScale = player.focalLength*2/(castRay(player.x, player.y, rayAngle)*player.sensorSize); - /* 2 Is wall height in meters. */ + /* 2 Is wall height in meters. */ fillColumn(window, i, obstacleScale); } } diff --git a/World.h b/World.h index 6ecf4ab..59a14b0 100644 --- a/World.h +++ b/World.h @@ -52,9 +52,9 @@ private: sf::Color ceilingColor; void fillColumn(sf::RenderWindow&, unsigned int column, float scale, - sf::Color wallColor = sf::Color(84,56,34)) const; + sf::Color fillColor = sf::Color(84,56,34)) const; /** - * Cast a ray from a given position and its distance to the origin. + * Cast a ray from a given position and return its distance to the origin. * @param originX Ray X origin, strictly positive * @param originY Ray Y origin, strictly positive * @param orientation Angle to cast to, in degrees between 0 and 360 From 92daf5551f2469d7b6407b7fa0738f61406cfe9e Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Fri, 26 Jan 2024 21:34:13 +0000 Subject: [PATCH 24/44] World: Rename wallColor to fillColor As this function will get more generic, make the names more generic. Fix some comments and indentation. --- World.cpp | 6 +++--- World.h | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/World.cpp b/World.cpp index 1ca062c..dea9d5b 100644 --- a/World.cpp +++ b/World.cpp @@ -205,13 +205,13 @@ float World::castRay(float originX, float originY, float orientation) const } void World::fillColumn(sf::RenderWindow& window, unsigned int column, - float scale, sf::Color wallColor) const + float scale, sf::Color fillColor) const { float columnHeight = static_cast(window.getSize().y)*scale; sf::RectangleShape pixelColumn(sf::Vector2f(1,columnHeight)); pixelColumn.setPosition(static_cast(column), (static_cast(window.getSize().y)-columnHeight)/2.0f); - pixelColumn.setFillColor(wallColor); + pixelColumn.setFillColor(fillColor); window.draw(pixelColumn); } @@ -249,7 +249,7 @@ void World::render(sf::RenderWindow& window) const rayAngle -= 360; } float obstacleScale = player.focalLength*2/(castRay(player.x, player.y, rayAngle)*player.sensorSize); - /* 2 Is wall height in meters. */ + /* 2 Is wall height in meters. */ fillColumn(window, i, obstacleScale); } } diff --git a/World.h b/World.h index 6ecf4ab..59a14b0 100644 --- a/World.h +++ b/World.h @@ -52,9 +52,9 @@ private: sf::Color ceilingColor; void fillColumn(sf::RenderWindow&, unsigned int column, float scale, - sf::Color wallColor = sf::Color(84,56,34)) const; + sf::Color fillColor = sf::Color(84,56,34)) const; /** - * Cast a ray from a given position and its distance to the origin. + * Cast a ray from a given position and return its distance to the origin. * @param originX Ray X origin, strictly positive * @param originY Ray Y origin, strictly positive * @param orientation Angle to cast to, in degrees between 0 and 360 From f25a6491443d0f0ba4572eb71680eab593d22bfc Mon Sep 17 00:00:00 2001 From: trotFunky Date: Fri, 26 Jan 2024 23:05:11 +0000 Subject: [PATCH 25/44] Debug: Introduce Dear ImGui interfaces Introduce Dear ImGui to the CMakeBuild, allow building without it. Add two debug interfaces : one FPS counter and frame time graph, one map visualizer and editor. --- CMakeLists.txt | 28 +++++++++++++++--- World.cpp | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++ main.cpp | 63 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 160 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index caf14ed..90b4bb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,28 @@ if(NOT SFML_FOUND) message(FATAL_ERROR "SFML could not be found") endif() -add_executable(raycasting main.cpp Player.cpp Player.h World.cpp World.h) - -target_link_libraries(raycasting +set(LIBS sfml-window - sfml-graphics) \ No newline at end of file + sfml-graphics) + +find_package(ImGui QUIET) +find_package(ImGui-SFML QUIET) + +if(NOT ImGui_FOUND OR NOT ImGui-SFML_FOUND OR NO_IMGUI) + message("*Not* building with ImGui") +else () + message("Building with ImGui") + add_compile_definitions(IMGUI) + set(LIBS ${LIBS} + ImGui-SFML::ImGui-SFML) +endif() + +add_compile_options(-Wall -Wextra) + +add_executable(raycasting + main.cpp + Player.cpp + World.cpp + ) + +target_link_libraries(raycasting ${LIBS}) diff --git a/World.cpp b/World.cpp index dea9d5b..3e7ecc9 100644 --- a/World.cpp +++ b/World.cpp @@ -5,6 +5,10 @@ #include "World.h" #include +#ifdef IMGUI +#include +#include +#endif World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : player(0,0,0), w(w), h(h), map(std::move(worldMap)), @@ -263,4 +267,77 @@ void World::step(const float& stepTime) { -player.currentMoveSpeedY*stepTime); } player.rotate(player.currentRotationSpeed*stepTime); + +#ifdef IMGUI + ImGui::Begin("MapEdit"); + static int blockToPlace = 1; + + /* + * Group all the select boxes together : makes it a big block in the + * Dear ImGui layout an allows adding things on the side. + */ + ImGui::BeginGroup(); + for (int y = 0 ; y < h ; y++) { + for (int x = 0 ; x < w ; x++) { + ImGui::PushID(x + y*w); + if (x > 0) + ImGui::SameLine(); + BlockType currentBlock = map[x + w*y]; + + ImVec4 hoverColor; + switch ((BlockType)blockToPlace) { + case BlockType::WALL: + hoverColor = (ImVec4)ImColor(84, 56, 34); + break; + case BlockType::DOOR: + hoverColor = (ImVec4)ImColor(186, 152, 107); + break; + case BlockType::WINDOW: + hoverColor = (ImVec4)ImColor(104, 123, 165); + break; + default: + /* Default header color, it seems ? */ + hoverColor = (ImVec4)ImColor(188, 120, 32); + break; + } + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hoverColor); + + ImVec4 blockColor; + switch (currentBlock) { + case BlockType::WALL: + blockColor = (ImVec4)ImColor(84, 56, 34); + break; + case BlockType::DOOR: + blockColor = (ImVec4)ImColor(186, 152, 107); + break; + case BlockType::WINDOW: + blockColor = (ImVec4)ImColor(104, 123, 165); + break; + default: + blockColor = (ImVec4)ImColor(188, 120, 32); + break; + } + ImGui::PushStyleColor(ImGuiCol_Header, blockColor); + + if(ImGui::Selectable("", currentBlock != BlockType::AIR, 0, ImVec2(10, 10))) { + map[x + w*y] = currentBlock == (BlockType)blockToPlace ? BlockType::AIR : (BlockType)blockToPlace; + } + ImGui::PopStyleColor(2); + ImGui::PopID(); + } + } + ImGui::EndGroup(); + + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Indent(); + ImGui::Text("Place :"); + ImGui::RadioButton("Wall", &blockToPlace, (int)BlockType::WALL); + ImGui::RadioButton("Door", &blockToPlace, (int)BlockType::DOOR); + ImGui::RadioButton("Window", &blockToPlace, (int)BlockType::WINDOW); + ImGui::Unindent(); + ImGui::EndGroup(); + + ImGui::End(); +#endif } diff --git a/main.cpp b/main.cpp index 5b9c8d3..af75df6 100644 --- a/main.cpp +++ b/main.cpp @@ -1,20 +1,35 @@ #include #include +#ifdef IMGUI +#include +#include +#endif + #include "World.h" // TODO: Find a way to go to edges instead of just equally split (?) int main() { - World world(10,10); - world.setBlock(BlockType::AIR,1,1,8,8); + World world(32,32); + world.setBlock(BlockType::AIR,1,1,30,30); world.setBlock(BlockType::WALL,4,4,2,2); world.player.move(2,2); std::cout << world << std::endl; - sf::RenderWindow window(sf::VideoMode(800,600),"Da raycasting"); sf::RenderWindow window(sf::VideoMode(1000,1000),"Da raycasting"); +#ifdef IMGUI + if (!ImGui::SFML::Init(window)) { + std::cout << "Failed to init Dear ImGui SFML" << std::endl; + return -1; + } + sf::Clock fpsDataClock; + float frameTimings[100]; + unsigned int frameCount = 0; +#endif + +// window.setFramerateLimit(60); sf::Event event{}; sf::Clock frameTime; @@ -34,6 +49,13 @@ int main() window.setView(sf::View(newView)); continue; } +#ifdef IMGUI + ImGui::SFML::ProcessEvent(window, event); + // Check if Dear ImGui should process the key events. + ImGuiIO& io = ImGui::GetIO(); + if (io.WantCaptureMouse || io.WantCaptureKeyboard) + continue; +#endif if (event.type == sf::Event::KeyPressed) { switch (event.key.code) { case sf::Keyboard::Key::Escape: @@ -71,11 +93,44 @@ int main() } } window.clear(); + const sf::Time& deltaT = frameTime.restart(); - world.step(frameTime.restart().asSeconds()); +#ifdef IMGUI + ImGui::SFML::Update(window, deltaT); +#endif + + world.step(deltaT.asSeconds()); world.render(window); + +#ifdef IMGUI +// ImGui::ShowDemoWindow(); + + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration + | ImGuiWindowFlags_AlwaysAutoResize + | ImGuiWindowFlags_NoSavedSettings + | ImGuiWindowFlags_NoFocusOnAppearing + | ImGuiWindowFlags_NoNav; + ImVec2 window_area = ImGui::GetMainViewport()->WorkSize; + ImGui::SetNextWindowPos({window_area.x - 20, 20}, ImGuiCond_Always, {1,0}); + ImGui::SetNextWindowBgAlpha(0.2f); + ImGui::Begin("FPS", nullptr ,window_flags); + if (fpsDataClock.getElapsedTime().asMilliseconds() > 20) { + frameTimings[frameCount%100] = static_cast(deltaT.asMicroseconds()); + frameCount++; + fpsDataClock.restart(); + } + ImGui::Text("FPS : %d", (int)(1.0/deltaT.asSeconds())); + ImGui::PlotLines("µs", frameTimings, frameCount >= 100 ? 100 : frameCount); + ImGui::End(); + + ImGui::SFML::Render(window); +#endif + window.display(); } +#ifdef IMGUI + ImGui::SFML::Shutdown(); +#endif return 0; } From b492def96e1466653145ebbd98ceb1d1e8a4fdb6 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Fri, 26 Jan 2024 23:00:26 +0000 Subject: [PATCH 26/44] Debug: Introduce Dear ImGui interfaces Introduce Dear ImGui to the CMakeBuild, allow building without it. Add two debug interfaces : one FPS counter and frame time graph, one map visualizer and editor. --- CMakeLists.txt | 28 +++++++++++++++--- World.cpp | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++ main.cpp | 63 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 160 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index caf14ed..90b4bb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,8 +11,28 @@ if(NOT SFML_FOUND) message(FATAL_ERROR "SFML could not be found") endif() -add_executable(raycasting main.cpp Player.cpp Player.h World.cpp World.h) - -target_link_libraries(raycasting +set(LIBS sfml-window - sfml-graphics) \ No newline at end of file + sfml-graphics) + +find_package(ImGui QUIET) +find_package(ImGui-SFML QUIET) + +if(NOT ImGui_FOUND OR NOT ImGui-SFML_FOUND OR NO_IMGUI) + message("*Not* building with ImGui") +else () + message("Building with ImGui") + add_compile_definitions(IMGUI) + set(LIBS ${LIBS} + ImGui-SFML::ImGui-SFML) +endif() + +add_compile_options(-Wall -Wextra) + +add_executable(raycasting + main.cpp + Player.cpp + World.cpp + ) + +target_link_libraries(raycasting ${LIBS}) diff --git a/World.cpp b/World.cpp index dea9d5b..3e7ecc9 100644 --- a/World.cpp +++ b/World.cpp @@ -5,6 +5,10 @@ #include "World.h" #include +#ifdef IMGUI +#include +#include +#endif World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : player(0,0,0), w(w), h(h), map(std::move(worldMap)), @@ -263,4 +267,77 @@ void World::step(const float& stepTime) { -player.currentMoveSpeedY*stepTime); } player.rotate(player.currentRotationSpeed*stepTime); + +#ifdef IMGUI + ImGui::Begin("MapEdit"); + static int blockToPlace = 1; + + /* + * Group all the select boxes together : makes it a big block in the + * Dear ImGui layout an allows adding things on the side. + */ + ImGui::BeginGroup(); + for (int y = 0 ; y < h ; y++) { + for (int x = 0 ; x < w ; x++) { + ImGui::PushID(x + y*w); + if (x > 0) + ImGui::SameLine(); + BlockType currentBlock = map[x + w*y]; + + ImVec4 hoverColor; + switch ((BlockType)blockToPlace) { + case BlockType::WALL: + hoverColor = (ImVec4)ImColor(84, 56, 34); + break; + case BlockType::DOOR: + hoverColor = (ImVec4)ImColor(186, 152, 107); + break; + case BlockType::WINDOW: + hoverColor = (ImVec4)ImColor(104, 123, 165); + break; + default: + /* Default header color, it seems ? */ + hoverColor = (ImVec4)ImColor(188, 120, 32); + break; + } + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hoverColor); + + ImVec4 blockColor; + switch (currentBlock) { + case BlockType::WALL: + blockColor = (ImVec4)ImColor(84, 56, 34); + break; + case BlockType::DOOR: + blockColor = (ImVec4)ImColor(186, 152, 107); + break; + case BlockType::WINDOW: + blockColor = (ImVec4)ImColor(104, 123, 165); + break; + default: + blockColor = (ImVec4)ImColor(188, 120, 32); + break; + } + ImGui::PushStyleColor(ImGuiCol_Header, blockColor); + + if(ImGui::Selectable("", currentBlock != BlockType::AIR, 0, ImVec2(10, 10))) { + map[x + w*y] = currentBlock == (BlockType)blockToPlace ? BlockType::AIR : (BlockType)blockToPlace; + } + ImGui::PopStyleColor(2); + ImGui::PopID(); + } + } + ImGui::EndGroup(); + + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Indent(); + ImGui::Text("Place :"); + ImGui::RadioButton("Wall", &blockToPlace, (int)BlockType::WALL); + ImGui::RadioButton("Door", &blockToPlace, (int)BlockType::DOOR); + ImGui::RadioButton("Window", &blockToPlace, (int)BlockType::WINDOW); + ImGui::Unindent(); + ImGui::EndGroup(); + + ImGui::End(); +#endif } diff --git a/main.cpp b/main.cpp index 5b9c8d3..af75df6 100644 --- a/main.cpp +++ b/main.cpp @@ -1,20 +1,35 @@ #include #include +#ifdef IMGUI +#include +#include +#endif + #include "World.h" // TODO: Find a way to go to edges instead of just equally split (?) int main() { - World world(10,10); - world.setBlock(BlockType::AIR,1,1,8,8); + World world(32,32); + world.setBlock(BlockType::AIR,1,1,30,30); world.setBlock(BlockType::WALL,4,4,2,2); world.player.move(2,2); std::cout << world << std::endl; - sf::RenderWindow window(sf::VideoMode(800,600),"Da raycasting"); sf::RenderWindow window(sf::VideoMode(1000,1000),"Da raycasting"); +#ifdef IMGUI + if (!ImGui::SFML::Init(window)) { + std::cout << "Failed to init Dear ImGui SFML" << std::endl; + return -1; + } + sf::Clock fpsDataClock; + float frameTimings[100]; + unsigned int frameCount = 0; +#endif + +// window.setFramerateLimit(60); sf::Event event{}; sf::Clock frameTime; @@ -34,6 +49,13 @@ int main() window.setView(sf::View(newView)); continue; } +#ifdef IMGUI + ImGui::SFML::ProcessEvent(window, event); + // Check if Dear ImGui should process the key events. + ImGuiIO& io = ImGui::GetIO(); + if (io.WantCaptureMouse || io.WantCaptureKeyboard) + continue; +#endif if (event.type == sf::Event::KeyPressed) { switch (event.key.code) { case sf::Keyboard::Key::Escape: @@ -71,11 +93,44 @@ int main() } } window.clear(); + const sf::Time& deltaT = frameTime.restart(); - world.step(frameTime.restart().asSeconds()); +#ifdef IMGUI + ImGui::SFML::Update(window, deltaT); +#endif + + world.step(deltaT.asSeconds()); world.render(window); + +#ifdef IMGUI +// ImGui::ShowDemoWindow(); + + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration + | ImGuiWindowFlags_AlwaysAutoResize + | ImGuiWindowFlags_NoSavedSettings + | ImGuiWindowFlags_NoFocusOnAppearing + | ImGuiWindowFlags_NoNav; + ImVec2 window_area = ImGui::GetMainViewport()->WorkSize; + ImGui::SetNextWindowPos({window_area.x - 20, 20}, ImGuiCond_Always, {1,0}); + ImGui::SetNextWindowBgAlpha(0.2f); + ImGui::Begin("FPS", nullptr ,window_flags); + if (fpsDataClock.getElapsedTime().asMilliseconds() > 20) { + frameTimings[frameCount%100] = static_cast(deltaT.asMicroseconds()); + frameCount++; + fpsDataClock.restart(); + } + ImGui::Text("FPS : %d", (int)(1.0/deltaT.asSeconds())); + ImGui::PlotLines("µs", frameTimings, frameCount >= 100 ? 100 : frameCount); + ImGui::End(); + + ImGui::SFML::Render(window); +#endif + window.display(); } +#ifdef IMGUI + ImGui::SFML::Shutdown(); +#endif return 0; } From a0bdfce61a4347b32ed4f7e7f60351ffa7cf9a14 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Fri, 26 Jan 2024 23:07:25 +0000 Subject: [PATCH 27/44] Colors: Introduce color type abstraction SFML and Dear ImGui have incompatible color types, abstract them through a new Color class which has conversions. Centralize color constants in a namespace as well. --- Color.h | 37 +++++++++++++++++++++++++++++++++++++ World.cpp | 12 ++++++------ World.h | 7 ++++--- 3 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 Color.h diff --git a/Color.h b/Color.h new file mode 100644 index 0000000..b62cf72 --- /dev/null +++ b/Color.h @@ -0,0 +1,37 @@ +// +// Created by trotfunky on 26/01/24. +// + +#ifndef RAYCASTING_COLOR_H +#define RAYCASTING_COLOR_H + +#include +#ifdef IMGUI +#include +#endif + + +class Color { +public: + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t alpha; + + constexpr Color(uint8_t r, uint8_t g, uint8_t b, uint8_t alpha = 255) : + r(r), g(g), b(b), alpha(alpha) {}; + + operator sf::Color() const { return {r, g, b, alpha}; } +#ifdef IMGUI + operator ImColor() const { return {r, g, b, alpha}; } +#endif +}; + +namespace Colors { + static constexpr Color Ground{67, 137, 39}; + static constexpr Color Ceiling{39, 69, 137}; + static constexpr Color Wall{84, 56, 34}; + static constexpr Color Door{186, 152, 107}; + static constexpr Color Window{104, 123, 165}; +} +#endif //RAYCASTING_COLOR_H diff --git a/World.cpp b/World.cpp index 3e7ecc9..d359063 100644 --- a/World.cpp +++ b/World.cpp @@ -287,13 +287,13 @@ void World::step(const float& stepTime) { ImVec4 hoverColor; switch ((BlockType)blockToPlace) { case BlockType::WALL: - hoverColor = (ImVec4)ImColor(84, 56, 34); + hoverColor = (ImVec4)Colors::Wall; break; case BlockType::DOOR: - hoverColor = (ImVec4)ImColor(186, 152, 107); + hoverColor = (ImVec4)Colors::Door; break; case BlockType::WINDOW: - hoverColor = (ImVec4)ImColor(104, 123, 165); + hoverColor = (ImVec4)Colors::Window; break; default: /* Default header color, it seems ? */ @@ -305,13 +305,13 @@ void World::step(const float& stepTime) { ImVec4 blockColor; switch (currentBlock) { case BlockType::WALL: - blockColor = (ImVec4)ImColor(84, 56, 34); + blockColor = (ImVec4)Colors::Wall; break; case BlockType::DOOR: - blockColor = (ImVec4)ImColor(186, 152, 107); + blockColor = (ImVec4)Colors::Door; break; case BlockType::WINDOW: - blockColor = (ImVec4)ImColor(104, 123, 165); + blockColor = (ImVec4)Colors::Window; break; default: blockColor = (ImVec4)ImColor(188, 120, 32); diff --git a/World.h b/World.h index 59a14b0..19c503a 100644 --- a/World.h +++ b/World.h @@ -10,6 +10,7 @@ #include #include +#include "Color.h" #include "Player.h" enum class BlockType { @@ -24,8 +25,8 @@ public: Player player; World(int w, int h, - sf::Color groundColor = sf::Color{67, 137, 39}, - sf::Color ceilingColor = sf::Color{39, 69, 137}, + sf::Color groundColor = Colors::Ground, + sf::Color ceilingColor = Colors::Ceiling, std::vector worldMap = {}); int getW() const; int getH() const; @@ -52,7 +53,7 @@ private: sf::Color ceilingColor; void fillColumn(sf::RenderWindow&, unsigned int column, float scale, - sf::Color fillColor = sf::Color(84,56,34)) const; + sf::Color fillColor = Colors::Wall) const; /** * Cast a ray from a given position and return its distance to the origin. * @param originX Ray X origin, strictly positive From e234dda860068e3d10a5da6ade1d9ec23cdbd41c Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Fri, 26 Jan 2024 22:54:13 +0000 Subject: [PATCH 28/44] Colors: Introduce color type abstraction SFML and Dear ImGui have incompatible color types, abstract them through a new Color class which has conversions. Centralize color constants in a namespace as well. --- Color.h | 37 +++++++++++++++++++++++++++++++++++++ World.cpp | 12 ++++++------ World.h | 7 ++++--- 3 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 Color.h diff --git a/Color.h b/Color.h new file mode 100644 index 0000000..b62cf72 --- /dev/null +++ b/Color.h @@ -0,0 +1,37 @@ +// +// Created by trotfunky on 26/01/24. +// + +#ifndef RAYCASTING_COLOR_H +#define RAYCASTING_COLOR_H + +#include +#ifdef IMGUI +#include +#endif + + +class Color { +public: + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t alpha; + + constexpr Color(uint8_t r, uint8_t g, uint8_t b, uint8_t alpha = 255) : + r(r), g(g), b(b), alpha(alpha) {}; + + operator sf::Color() const { return {r, g, b, alpha}; } +#ifdef IMGUI + operator ImColor() const { return {r, g, b, alpha}; } +#endif +}; + +namespace Colors { + static constexpr Color Ground{67, 137, 39}; + static constexpr Color Ceiling{39, 69, 137}; + static constexpr Color Wall{84, 56, 34}; + static constexpr Color Door{186, 152, 107}; + static constexpr Color Window{104, 123, 165}; +} +#endif //RAYCASTING_COLOR_H diff --git a/World.cpp b/World.cpp index 3e7ecc9..d359063 100644 --- a/World.cpp +++ b/World.cpp @@ -287,13 +287,13 @@ void World::step(const float& stepTime) { ImVec4 hoverColor; switch ((BlockType)blockToPlace) { case BlockType::WALL: - hoverColor = (ImVec4)ImColor(84, 56, 34); + hoverColor = (ImVec4)Colors::Wall; break; case BlockType::DOOR: - hoverColor = (ImVec4)ImColor(186, 152, 107); + hoverColor = (ImVec4)Colors::Door; break; case BlockType::WINDOW: - hoverColor = (ImVec4)ImColor(104, 123, 165); + hoverColor = (ImVec4)Colors::Window; break; default: /* Default header color, it seems ? */ @@ -305,13 +305,13 @@ void World::step(const float& stepTime) { ImVec4 blockColor; switch (currentBlock) { case BlockType::WALL: - blockColor = (ImVec4)ImColor(84, 56, 34); + blockColor = (ImVec4)Colors::Wall; break; case BlockType::DOOR: - blockColor = (ImVec4)ImColor(186, 152, 107); + blockColor = (ImVec4)Colors::Door; break; case BlockType::WINDOW: - blockColor = (ImVec4)ImColor(104, 123, 165); + blockColor = (ImVec4)Colors::Window; break; default: blockColor = (ImVec4)ImColor(188, 120, 32); diff --git a/World.h b/World.h index 59a14b0..19c503a 100644 --- a/World.h +++ b/World.h @@ -10,6 +10,7 @@ #include #include +#include "Color.h" #include "Player.h" enum class BlockType { @@ -24,8 +25,8 @@ public: Player player; World(int w, int h, - sf::Color groundColor = sf::Color{67, 137, 39}, - sf::Color ceilingColor = sf::Color{39, 69, 137}, + sf::Color groundColor = Colors::Ground, + sf::Color ceilingColor = Colors::Ceiling, std::vector worldMap = {}); int getW() const; int getH() const; @@ -52,7 +53,7 @@ private: sf::Color ceilingColor; void fillColumn(sf::RenderWindow&, unsigned int column, float scale, - sf::Color fillColor = sf::Color(84,56,34)) const; + sf::Color fillColor = Colors::Wall) const; /** * Cast a ray from a given position and return its distance to the origin. * @param originX Ray X origin, strictly positive From 0cd421de3c00c23463e8838786cb5cc90445f891 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sun, 28 Jan 2024 21:58:36 +0000 Subject: [PATCH 29/44] World: Move the scale factor to a constant Clear up the computation and split the result from the ray cast to the scale factor. Update the castRay explanations to add the issue of unmatched axes. --- World.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/World.cpp b/World.cpp index d359063..6bc5e35 100644 --- a/World.cpp +++ b/World.cpp @@ -107,6 +107,9 @@ float World::castRay(float originX, float originY, float orientation) const * the grid for the other one * - Depending on the orientation, signs must be taken into account * to work 360° + * - Those formulas consider regular axes (x→,y↑), however the world is + * built around left-handed axes (x→,y↓), so the rendered world is + * mirrored. This also explains some weird signs for rotations. */ /* Offsets to get back on the grid from the ray's origin. */ float hOffsetX; @@ -148,7 +151,7 @@ float World::castRay(float originX, float originY, float orientation) const if (orientation < 180) { vOffsetX = ceilf(originX); vOffsetY = ceilf(originX) - originX; - vDir = +1; + vDir = 1; vRound = 0; } else { vOffsetX = floorf(originX); @@ -239,6 +242,8 @@ void World::render(sf::RenderWindow& window) const window.draw(ground); window.draw(ceiling); + const float worldToCamera = (player.focalLength*2)/player.sensorSize; + /* * Throw rays and draw walls over the ceiling and ground. * Only throws in the plane, which doesn't work for levels/3D. @@ -252,7 +257,7 @@ void World::render(sf::RenderWindow& window) const } else if (rayAngle > 360) { rayAngle -= 360; } - float obstacleScale = player.focalLength*2/(castRay(player.x, player.y, rayAngle)*player.sensorSize); + float obstacleScale = worldToCamera / castRay(player.x, player.y, rayAngle); /* 2 Is wall height in meters. */ fillColumn(window, i, obstacleScale); } From 43970df494fc0e05571119c38e908c6f0697fc3c Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sun, 28 Jan 2024 21:58:32 +0000 Subject: [PATCH 30/44] World: Move the scale factor to a constant Clear up the computation and split the result from the ray cast to the scale factor. Update the castRay explanations to add the issue of unmatched axes. --- World.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/World.cpp b/World.cpp index d359063..6bc5e35 100644 --- a/World.cpp +++ b/World.cpp @@ -107,6 +107,9 @@ float World::castRay(float originX, float originY, float orientation) const * the grid for the other one * - Depending on the orientation, signs must be taken into account * to work 360° + * - Those formulas consider regular axes (x→,y↑), however the world is + * built around left-handed axes (x→,y↓), so the rendered world is + * mirrored. This also explains some weird signs for rotations. */ /* Offsets to get back on the grid from the ray's origin. */ float hOffsetX; @@ -148,7 +151,7 @@ float World::castRay(float originX, float originY, float orientation) const if (orientation < 180) { vOffsetX = ceilf(originX); vOffsetY = ceilf(originX) - originX; - vDir = +1; + vDir = 1; vRound = 0; } else { vOffsetX = floorf(originX); @@ -239,6 +242,8 @@ void World::render(sf::RenderWindow& window) const window.draw(ground); window.draw(ceiling); + const float worldToCamera = (player.focalLength*2)/player.sensorSize; + /* * Throw rays and draw walls over the ceiling and ground. * Only throws in the plane, which doesn't work for levels/3D. @@ -252,7 +257,7 @@ void World::render(sf::RenderWindow& window) const } else if (rayAngle > 360) { rayAngle -= 360; } - float obstacleScale = player.focalLength*2/(castRay(player.x, player.y, rayAngle)*player.sensorSize); + float obstacleScale = worldToCamera / castRay(player.x, player.y, rayAngle); /* 2 Is wall height in meters. */ fillColumn(window, i, obstacleScale); } From 9c9ec45304966a593ba2cbb70fa4c948f003202c Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sun, 28 Jan 2024 22:05:20 +0000 Subject: [PATCH 31/44] World/Debug: Properly check the MapEdit status If a Dear ImGui window is collapsed or closed, ImGui::Begin returns false. Check it properly to bypass rendering if is indeed not needed. --- World.cpp | 129 +++++++++++++++++++++++++++--------------------------- 1 file changed, 65 insertions(+), 64 deletions(-) diff --git a/World.cpp b/World.cpp index 6bc5e35..7880148 100644 --- a/World.cpp +++ b/World.cpp @@ -274,75 +274,76 @@ void World::step(const float& stepTime) { player.rotate(player.currentRotationSpeed*stepTime); #ifdef IMGUI - ImGui::Begin("MapEdit"); - static int blockToPlace = 1; + if (ImGui::Begin("MapEdit")) { + static int blockToPlace = 1; - /* - * Group all the select boxes together : makes it a big block in the - * Dear ImGui layout an allows adding things on the side. - */ - ImGui::BeginGroup(); - for (int y = 0 ; y < h ; y++) { - for (int x = 0 ; x < w ; x++) { - ImGui::PushID(x + y*w); - if (x > 0) - ImGui::SameLine(); - BlockType currentBlock = map[x + w*y]; + /* + * Group all the select boxes together : makes it a big block in the + * Dear ImGui layout an allows adding things on the side. + */ + ImGui::BeginGroup(); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + ImGui::PushID(x + y * w); + if (x > 0) + ImGui::SameLine(); + BlockType currentBlock = map[x + w * y]; - ImVec4 hoverColor; - switch ((BlockType)blockToPlace) { - case BlockType::WALL: - hoverColor = (ImVec4)Colors::Wall; - break; - case BlockType::DOOR: - hoverColor = (ImVec4)Colors::Door; - break; - case BlockType::WINDOW: - hoverColor = (ImVec4)Colors::Window; - break; - default: - /* Default header color, it seems ? */ - hoverColor = (ImVec4)ImColor(188, 120, 32); - break; + ImVec4 hoverColor; + switch ((BlockType) blockToPlace) { + case BlockType::WALL: + hoverColor = (ImVec4) Colors::Wall; + break; + case BlockType::DOOR: + hoverColor = (ImVec4) Colors::Door; + break; + case BlockType::WINDOW: + hoverColor = (ImVec4) Colors::Window; + break; + default: + /* Default header color, it seems ? */ + hoverColor = (ImVec4) ImColor(188, 120, 32); + break; + } + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hoverColor); + + ImVec4 blockColor; + switch (currentBlock) { + case BlockType::WALL: + blockColor = (ImVec4) Colors::Wall; + break; + case BlockType::DOOR: + blockColor = (ImVec4) Colors::Door; + break; + case BlockType::WINDOW: + blockColor = (ImVec4) Colors::Window; + break; + default: + blockColor = (ImVec4) ImColor(188, 120, 32); + break; + } + ImGui::PushStyleColor(ImGuiCol_Header, blockColor); + + if (ImGui::Selectable("", currentBlock != BlockType::AIR, 0, ImVec2(10, 10))) { + map[x + w * y] = + currentBlock == (BlockType) blockToPlace ? BlockType::AIR : (BlockType) blockToPlace; + } + ImGui::PopStyleColor(2); + ImGui::PopID(); } - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hoverColor); - - ImVec4 blockColor; - switch (currentBlock) { - case BlockType::WALL: - blockColor = (ImVec4)Colors::Wall; - break; - case BlockType::DOOR: - blockColor = (ImVec4)Colors::Door; - break; - case BlockType::WINDOW: - blockColor = (ImVec4)Colors::Window; - break; - default: - blockColor = (ImVec4)ImColor(188, 120, 32); - break; - } - ImGui::PushStyleColor(ImGuiCol_Header, blockColor); - - if(ImGui::Selectable("", currentBlock != BlockType::AIR, 0, ImVec2(10, 10))) { - map[x + w*y] = currentBlock == (BlockType)blockToPlace ? BlockType::AIR : (BlockType)blockToPlace; - } - ImGui::PopStyleColor(2); - ImGui::PopID(); } + ImGui::EndGroup(); + + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Indent(); + ImGui::Text("Place :"); + ImGui::RadioButton("Wall", &blockToPlace, (int) BlockType::WALL); + ImGui::RadioButton("Door", &blockToPlace, (int) BlockType::DOOR); + ImGui::RadioButton("Window", &blockToPlace, (int) BlockType::WINDOW); + ImGui::Unindent(); + ImGui::EndGroup(); } - ImGui::EndGroup(); - - ImGui::SameLine(); - ImGui::BeginGroup(); - ImGui::Indent(); - ImGui::Text("Place :"); - ImGui::RadioButton("Wall", &blockToPlace, (int)BlockType::WALL); - ImGui::RadioButton("Door", &blockToPlace, (int)BlockType::DOOR); - ImGui::RadioButton("Window", &blockToPlace, (int)BlockType::WINDOW); - ImGui::Unindent(); - ImGui::EndGroup(); - ImGui::End(); #endif } From 66ec111f5d12429c7fb557b201a29cc1bf659696 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sun, 28 Jan 2024 22:05:20 +0000 Subject: [PATCH 32/44] World/Debug: Properly check the MapEdit status If a Dear ImGui window is collapsed or closed, ImGui::Begin returns false. Check it properly to bypass rendering if is indeed not needed. --- World.cpp | 129 +++++++++++++++++++++++++++--------------------------- 1 file changed, 65 insertions(+), 64 deletions(-) diff --git a/World.cpp b/World.cpp index 6bc5e35..7880148 100644 --- a/World.cpp +++ b/World.cpp @@ -274,75 +274,76 @@ void World::step(const float& stepTime) { player.rotate(player.currentRotationSpeed*stepTime); #ifdef IMGUI - ImGui::Begin("MapEdit"); - static int blockToPlace = 1; + if (ImGui::Begin("MapEdit")) { + static int blockToPlace = 1; - /* - * Group all the select boxes together : makes it a big block in the - * Dear ImGui layout an allows adding things on the side. - */ - ImGui::BeginGroup(); - for (int y = 0 ; y < h ; y++) { - for (int x = 0 ; x < w ; x++) { - ImGui::PushID(x + y*w); - if (x > 0) - ImGui::SameLine(); - BlockType currentBlock = map[x + w*y]; + /* + * Group all the select boxes together : makes it a big block in the + * Dear ImGui layout an allows adding things on the side. + */ + ImGui::BeginGroup(); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + ImGui::PushID(x + y * w); + if (x > 0) + ImGui::SameLine(); + BlockType currentBlock = map[x + w * y]; - ImVec4 hoverColor; - switch ((BlockType)blockToPlace) { - case BlockType::WALL: - hoverColor = (ImVec4)Colors::Wall; - break; - case BlockType::DOOR: - hoverColor = (ImVec4)Colors::Door; - break; - case BlockType::WINDOW: - hoverColor = (ImVec4)Colors::Window; - break; - default: - /* Default header color, it seems ? */ - hoverColor = (ImVec4)ImColor(188, 120, 32); - break; + ImVec4 hoverColor; + switch ((BlockType) blockToPlace) { + case BlockType::WALL: + hoverColor = (ImVec4) Colors::Wall; + break; + case BlockType::DOOR: + hoverColor = (ImVec4) Colors::Door; + break; + case BlockType::WINDOW: + hoverColor = (ImVec4) Colors::Window; + break; + default: + /* Default header color, it seems ? */ + hoverColor = (ImVec4) ImColor(188, 120, 32); + break; + } + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hoverColor); + + ImVec4 blockColor; + switch (currentBlock) { + case BlockType::WALL: + blockColor = (ImVec4) Colors::Wall; + break; + case BlockType::DOOR: + blockColor = (ImVec4) Colors::Door; + break; + case BlockType::WINDOW: + blockColor = (ImVec4) Colors::Window; + break; + default: + blockColor = (ImVec4) ImColor(188, 120, 32); + break; + } + ImGui::PushStyleColor(ImGuiCol_Header, blockColor); + + if (ImGui::Selectable("", currentBlock != BlockType::AIR, 0, ImVec2(10, 10))) { + map[x + w * y] = + currentBlock == (BlockType) blockToPlace ? BlockType::AIR : (BlockType) blockToPlace; + } + ImGui::PopStyleColor(2); + ImGui::PopID(); } - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hoverColor); - - ImVec4 blockColor; - switch (currentBlock) { - case BlockType::WALL: - blockColor = (ImVec4)Colors::Wall; - break; - case BlockType::DOOR: - blockColor = (ImVec4)Colors::Door; - break; - case BlockType::WINDOW: - blockColor = (ImVec4)Colors::Window; - break; - default: - blockColor = (ImVec4)ImColor(188, 120, 32); - break; - } - ImGui::PushStyleColor(ImGuiCol_Header, blockColor); - - if(ImGui::Selectable("", currentBlock != BlockType::AIR, 0, ImVec2(10, 10))) { - map[x + w*y] = currentBlock == (BlockType)blockToPlace ? BlockType::AIR : (BlockType)blockToPlace; - } - ImGui::PopStyleColor(2); - ImGui::PopID(); } + ImGui::EndGroup(); + + ImGui::SameLine(); + ImGui::BeginGroup(); + ImGui::Indent(); + ImGui::Text("Place :"); + ImGui::RadioButton("Wall", &blockToPlace, (int) BlockType::WALL); + ImGui::RadioButton("Door", &blockToPlace, (int) BlockType::DOOR); + ImGui::RadioButton("Window", &blockToPlace, (int) BlockType::WINDOW); + ImGui::Unindent(); + ImGui::EndGroup(); } - ImGui::EndGroup(); - - ImGui::SameLine(); - ImGui::BeginGroup(); - ImGui::Indent(); - ImGui::Text("Place :"); - ImGui::RadioButton("Wall", &blockToPlace, (int)BlockType::WALL); - ImGui::RadioButton("Door", &blockToPlace, (int)BlockType::DOOR); - ImGui::RadioButton("Window", &blockToPlace, (int)BlockType::WINDOW); - ImGui::Unindent(); - ImGui::EndGroup(); - ImGui::End(); #endif } From bb20d4a5200c8e63022366695cef7e34aea0df30 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sun, 28 Jan 2024 22:08:35 +0000 Subject: [PATCH 33/44] Clean-up: Change indentation to tabs --- CMakeLists.txt | 24 ++-- Player.cpp | 26 ++-- Player.h | 32 ++--- World.cpp | 368 ++++++++++++++++++++++++------------------------- World.h | 66 ++++----- main.cpp | 86 ++++++------ 6 files changed, 301 insertions(+), 301 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 90b4bb5..0845878 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,31 +8,31 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH}) find_package(SFML COMPONENTS system window graphics network audio REQUIRED) if(NOT SFML_FOUND) - message(FATAL_ERROR "SFML could not be found") + message(FATAL_ERROR "SFML could not be found") endif() set(LIBS - sfml-window - sfml-graphics) + sfml-window + sfml-graphics) find_package(ImGui QUIET) find_package(ImGui-SFML QUIET) if(NOT ImGui_FOUND OR NOT ImGui-SFML_FOUND OR NO_IMGUI) - message("*Not* building with ImGui") + message("*Not* building with ImGui") else () - message("Building with ImGui") - add_compile_definitions(IMGUI) - set(LIBS ${LIBS} - ImGui-SFML::ImGui-SFML) + message("Building with ImGui") + add_compile_definitions(IMGUI) + set(LIBS ${LIBS} + ImGui-SFML::ImGui-SFML) endif() add_compile_options(-Wall -Wextra) add_executable(raycasting - main.cpp - Player.cpp - World.cpp - ) + main.cpp + Player.cpp + World.cpp + ) target_link_libraries(raycasting ${LIBS}) diff --git a/Player.cpp b/Player.cpp index b6fa26c..694636e 100644 --- a/Player.cpp +++ b/Player.cpp @@ -10,21 +10,21 @@ Player::Player(float x, float y, float alpha) : x(x), y(y), orientation(alpha) void Player::move(float dx, float dy) { - x += dx; - y += dy; + x += dx; + y += dy; } void Player::rotate(float alpha) { - orientation += fmodf(alpha, 360); - if(orientation > 360) - { - orientation -= 360; - } - else if(orientation < 0) - { - orientation += 360; - } + orientation += fmodf(alpha, 360); + if(orientation > 360) + { + orientation -= 360; + } + else if(orientation < 0) + { + orientation += 360; + } /* * Rotate the movement vector along the new angle, assumes that the only @@ -33,9 +33,9 @@ void Player::rotate(float alpha) float prevSpeedX = currentMoveSpeedX; float prevSpeedY = currentMoveSpeedY; currentMoveSpeedX = cosf(-alpha * deg_to_rad) * prevSpeedX - - sinf(-alpha * deg_to_rad) * prevSpeedY; + - sinf(-alpha * deg_to_rad) * prevSpeedY; currentMoveSpeedY = sinf(-alpha * deg_to_rad) * prevSpeedX - + cosf(-alpha * deg_to_rad) * prevSpeedY; + + cosf(-alpha * deg_to_rad) * prevSpeedY; } void Player::updateSpeed(float localX, float localY) { diff --git a/Player.h b/Player.h index eab2795..294e669 100644 --- a/Player.h +++ b/Player.h @@ -11,28 +11,28 @@ static constexpr float deg_to_rad = 3.14159265/180; class Player { public: - Player(float x, float y, float alpha); + Player(float x, float y, float alpha); - float x; - float y; - float orientation; + float x; + float y; + float orientation; - float moveSpeed = 5; - float rotationSpeed = 180; + float moveSpeed = 5; + float rotationSpeed = 180; - float currentMoveSpeedX = 0; - float currentMoveSpeedY = 0; + float currentMoveSpeedX = 0; + float currentMoveSpeedY = 0; - float currentRotationSpeed = 0; + float currentRotationSpeed = 0; - /* View properties. */ - float fov = 70; - float sensorSize = 0.035; /* 35mm, about equivalent to human eye ? */ - float focalLength = sensorSize / (2*tanf((fov*deg_to_rad)/2)); + /* View properties. */ + float fov = 70; + float sensorSize = 0.035; /* 35mm, about equivalent to human eye ? */ + float focalLength = sensorSize / (2*tanf((fov*deg_to_rad)/2)); - void move(float dx, float dy); - void rotate(float alpha); - void updateSpeed(float localX, float localY); + void move(float dx, float dy); + void rotate(float alpha); + void updateSpeed(float localX, float localY); }; diff --git a/World.cpp b/World.cpp index 7880148..b8a8eb6 100644 --- a/World.cpp +++ b/World.cpp @@ -14,22 +14,22 @@ World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::v player(0,0,0), w(w), h(h), map(std::move(worldMap)), groundColor(groundColor), ceilingColor(ceilingColor) { - map.resize(w*h,BlockType::WALL); + map.resize(w*h,BlockType::WALL); } int World::getW() const { - return w; + return w; } int World::getH() const { - return h; + return h; } BlockType World::getBlock(int x, int y) const { - return map[x + w*y]; + return map[x + w*y]; } BlockType World::getBlock(float x, float y) const @@ -39,176 +39,176 @@ BlockType World::getBlock(float x, float y) const void World::setBlock(BlockType block, int x, int y, int width, int height) { - for(int i = 0;i(world.player.x) == i%world.w && static_cast(world.player.y) == i/world.h) - { - ostream << "P"; - } - else - { - ostream << " "; - } - break; - } - case BlockType::WALL: - { - ostream << "W"; - break; - } - case BlockType::DOOR: - { - ostream << "D"; - break; - } - case BlockType::WINDOW: - { - ostream << "W"; - break; - } - } - } - return(ostream); + for(int i = 0;i(world.player.x) == i%world.w && static_cast(world.player.y) == i/world.h) + { + ostream << "P"; + } + else + { + ostream << " "; + } + break; + } + case BlockType::WALL: + { + ostream << "W"; + break; + } + case BlockType::DOOR: + { + ostream << "D"; + break; + } + case BlockType::WINDOW: + { + ostream << "W"; + break; + } + } + } + return(ostream); } float World::castRay(float originX, float originY, float orientation) const { - /* - * Reference used for ray intersection computations : - * https://web.archive.org/web/20220628034315/https://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf - * The logic is as follows : - * - This computes one set of point per edge crossings (horizontal/vertical) - * - The origin not being confined to the grid, offsets are computed to - * align the intersections properly - * - The intersections are at multiples of the tangent of the relevant - * angle for the axis of interest, and simply on successive edges of - * the grid for the other one - * - Depending on the orientation, signs must be taken into account - * to work 360° - * - Those formulas consider regular axes (x→,y↑), however the world is - * built around left-handed axes (x→,y↓), so the rendered world is - * mirrored. This also explains some weird signs for rotations. - */ - /* Offsets to get back on the grid from the ray's origin. */ - float hOffsetX; - float hOffsetY; - float vOffsetX; - float vOffsetY; + /* + * Reference used for ray intersection computations : + * https://web.archive.org/web/20220628034315/https://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf + * The logic is as follows : + * - This computes one set of point per edge crossings (horizontal/vertical) + * - The origin not being confined to the grid, offsets are computed to + * align the intersections properly + * - The intersections are at multiples of the tangent of the relevant + * angle for the axis of interest, and simply on successive edges of + * the grid for the other one + * - Depending on the orientation, signs must be taken into account + * to work 360° + * - Those formulas consider regular axes (x→,y↑), however the world is + * built around left-handed axes (x→,y↓), so the rendered world is + * mirrored. This also explains some weird signs for rotations. + */ + /* Offsets to get back on the grid from the ray's origin. */ + float hOffsetX; + float hOffsetY; + float vOffsetX; + float vOffsetY; - /* Signs controlling the direction of travel. */ - float hDir; - float vDir; - /* Need offset for rounding in the right direction ? */ - float hRound; - float vRound; + /* Signs controlling the direction of travel. */ + float hDir; + float vDir; + /* Need offset for rounding in the right direction ? */ + float hRound; + float vRound; - float rads = orientation * deg_to_rad; - /* Used for vertical intersections. */ - float rads_offset = (90 - orientation) * deg_to_rad; + float rads = orientation * deg_to_rad; + /* Used for vertical intersections. */ + float rads_offset = (90 - orientation) * deg_to_rad; - /* Tangents used for the different axes. */ - float hTan = tanf(rads); - float vTan = tanf(rads_offset); + /* Tangents used for the different axes. */ + float hTan = tanf(rads); + float vTan = tanf(rads_offset); - /* Check if cos > 0 for horizontal hits formulas. */ - if (orientation < 90 || orientation > 270) { - hOffsetX = ceilf(originY) - originY; - hOffsetY = ceilf(originY); - hDir = +1; - hRound = 0; - } else { - hOffsetX = originY - floorf(originY); - hOffsetY = floorf(originY); - hDir = -1; - hRound = -1; - } - hTan *= hDir; - hOffsetX *= hTan; + /* Check if cos > 0 for horizontal hits formulas. */ + if (orientation < 90 || orientation > 270) { + hOffsetX = ceilf(originY) - originY; + hOffsetY = ceilf(originY); + hDir = +1; + hRound = 0; + } else { + hOffsetX = originY - floorf(originY); + hOffsetY = floorf(originY); + hDir = -1; + hRound = -1; + } + hTan *= hDir; + hOffsetX *= hTan; - /* Check if sin > 0 for vertical hits formulas. */ - if (orientation < 180) { - vOffsetX = ceilf(originX); - vOffsetY = ceilf(originX) - originX; - vDir = 1; - vRound = 0; - } else { - vOffsetX = floorf(originX); - vOffsetY = originX - floorf(originX); - vDir = -1; - vRound = -1; - } - vTan *= vDir; - vOffsetY *= vTan; + /* Check if sin > 0 for vertical hits formulas. */ + if (orientation < 180) { + vOffsetX = ceilf(originX); + vOffsetY = ceilf(originX) - originX; + vDir = 1; + vRound = 0; + } else { + vOffsetX = floorf(originX); + vOffsetY = originX - floorf(originX); + vDir = -1; + vRound = -1; + } + vTan *= vDir; + vOffsetY *= vTan; - /* - * Now we have all the constants and deltas to work with, cast the ray. - * Generated points follow the formulas : - * - h-intersect : (originX + hOffsetX + hTan*i, hOffsetY + hDir*i) - * - v-intersect : (vOffsetX + vDir*i, originY + vOffsetY + vTan*i) - */ - int i = 0; - float hCheckX = originX + hOffsetX; - float hCheckY = hOffsetY; - /* Bounds + sanity check. */ - while (hCheckX >= 0 && hCheckX <= static_cast(w) && + /* + * Now we have all the constants and deltas to work with, cast the ray. + * Generated points follow the formulas : + * - h-intersect : (originX + hOffsetX + hTan*i, hOffsetY + hDir*i) + * - v-intersect : (vOffsetX + vDir*i, originY + vOffsetY + vTan*i) + */ + int i = 0; + float hCheckX = originX + hOffsetX; + float hCheckY = hOffsetY; + /* Bounds + sanity check. */ + while (hCheckX >= 0 && hCheckX <= static_cast(w) && hCheckY >= 0 && hCheckY <= static_cast(h) && i < h) { - if (getBlock(floorf(hCheckX), floorf(hCheckY) + hRound) == BlockType::WALL) { - break; - } + if (getBlock(floorf(hCheckX), floorf(hCheckY) + hRound) == BlockType::WALL) { + break; + } - hCheckX += hTan; - hCheckY += hDir; - i++; - } + hCheckX += hTan; + hCheckY += hDir; + i++; + } - i = 0; - float vCheckX = vOffsetX; - float vCheckY = originY + vOffsetY; + i = 0; + float vCheckX = vOffsetX; + float vCheckY = originY + vOffsetY; - /* Bounds + sanity check. */ - while (vCheckX >= 0 && vCheckX < static_cast(w) && + /* Bounds + sanity check. */ + while (vCheckX >= 0 && vCheckX < static_cast(w) && vCheckY >= 0 && vCheckY < static_cast(h) && i < w) { - if (getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)) == BlockType::WALL) { - break; - } + if (getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)) == BlockType::WALL) { + break; + } - vCheckX += vDir; - vCheckY += vTan; - i++; - } + vCheckX += vDir; + vCheckY += vTan; + i++; + } - /* - * We may or may not have hit something. Check which coordinates are closest - * and use those for computing the apparent size on screen. - */ - float hDist = sqrtf((originX - hCheckX)*(originX - hCheckX) + - (originY - hCheckY)*(originY - hCheckY)); - float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + - (originY - vCheckY)*(originY - vCheckY)); + /* + * We may or may not have hit something. Check which coordinates are closest + * and use those for computing the apparent size on screen. + */ + float hDist = sqrtf((originX - hCheckX)*(originX - hCheckX) + + (originY - hCheckY)*(originY - hCheckY)); + float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + + (originY - vCheckY)*(originY - vCheckY)); - return hDist > vDist ? vDist : hDist; + return hDist > vDist ? vDist : hDist; } void World::fillColumn(sf::RenderWindow& window, unsigned int column, @@ -227,51 +227,51 @@ void World::render(sf::RenderWindow& window) const { float windowX = static_cast(window.getSize().x); float windowY = static_cast(window.getSize().y); - /* - * Draw ground and sky planes through half of the screen, as the walls - * will get drawn over them. - * This doesn't work if we support textures/levels. - */ - sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); - ground.setFillColor(groundColor); - ground.setPosition(0,windowY/2.0f); + /* + * Draw ground and sky planes through half of the screen, as the walls + * will get drawn over them. + * This doesn't work if we support textures/levels. + */ + sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); + ground.setFillColor(groundColor); + ground.setPosition(0,windowY/2.0f); - sf::RectangleShape ceiling = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); - ceiling.setFillColor(ceilingColor); + sf::RectangleShape ceiling = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); + ceiling.setFillColor(ceilingColor); - window.draw(ground); - window.draw(ceiling); + window.draw(ground); + window.draw(ceiling); const float worldToCamera = (player.focalLength*2)/player.sensorSize; - /* - * Throw rays and draw walls over the ceiling and ground. - * Only throws in the plane, which doesn't work for levels/3D. - */ - for(unsigned int i = 0 ; i < window.getSize().x ; i++) - { - float deltaAngle = (player.fov/windowX) * (static_cast(i)-windowX/2.0f); - float rayAngle = player.orientation + deltaAngle; - if (rayAngle < 0) { - rayAngle += 360; - } else if (rayAngle > 360) { - rayAngle -= 360; - } - float obstacleScale = worldToCamera / castRay(player.x, player.y, rayAngle); - /* 2 Is wall height in meters. */ - fillColumn(window, i, obstacleScale); - } + /* + * Throw rays and draw walls over the ceiling and ground. + * Only throws in the plane, which doesn't work for levels/3D. + */ + for(unsigned int i = 0 ; i < window.getSize().x ; i++) + { + float deltaAngle = (player.fov/windowX) * (static_cast(i)-windowX/2.0f); + float rayAngle = player.orientation + deltaAngle; + if (rayAngle < 0) { + rayAngle += 360; + } else if (rayAngle > 360) { + rayAngle -= 360; + } + float obstacleScale = worldToCamera / castRay(player.x, player.y, rayAngle); + /* 2 Is wall height in meters. */ + fillColumn(window, i, obstacleScale); + } } void World::step(const float& stepTime) { - player.move(player.currentMoveSpeedX*stepTime, - player.currentMoveSpeedY*stepTime); + player.move(player.currentMoveSpeedX*stepTime, + player.currentMoveSpeedY*stepTime); /* Undo last move if the player would end up in a wall. */ if (getBlock(player.x, player.y) != BlockType::AIR) { player.move(-player.currentMoveSpeedX*stepTime, -player.currentMoveSpeedY*stepTime); } - player.rotate(player.currentRotationSpeed*stepTime); + player.rotate(player.currentRotationSpeed*stepTime); #ifdef IMGUI if (ImGui::Begin("MapEdit")) { diff --git a/World.h b/World.h index 19c503a..43b4b1e 100644 --- a/World.h +++ b/World.h @@ -14,54 +14,54 @@ #include "Player.h" enum class BlockType { - AIR, - WALL, - DOOR, - WINDOW, + AIR, + WALL, + DOOR, + WINDOW, }; class World { public: - Player player; + Player player; - World(int w, int h, - sf::Color groundColor = Colors::Ground, - sf::Color ceilingColor = Colors::Ceiling, - std::vector worldMap = {}); - int getW() const; - int getH() const; + World(int w, int h, + sf::Color groundColor = Colors::Ground, + sf::Color ceilingColor = Colors::Ceiling, + std::vector worldMap = {}); + int getW() const; + int getH() const; inline BlockType getBlock(int x, int y) const; inline BlockType getBlock(float x, float y) const; void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); - void render(sf::RenderWindow&) const; + void render(sf::RenderWindow&) const; - /** - * Move the world one step forward. - * @param stepTime delta time since last step, in seconds - */ - void step(const float& stepTime); + /** + * Move the world one step forward. + * @param stepTime delta time since last step, in seconds + */ + void step(const float& stepTime); - friend std::ostream& operator<<(std::ostream& ostream, World const & world); + friend std::ostream& operator<<(std::ostream& ostream, World const & world); private: - int w; - int h; - std::vector map; + int w; + int h; + std::vector map; - sf::Color groundColor; - sf::Color ceilingColor; + sf::Color groundColor; + sf::Color ceilingColor; - void fillColumn(sf::RenderWindow&, unsigned int column, float scale, - sf::Color fillColor = Colors::Wall) const; - /** - * Cast a ray from a given position and return its distance to the origin. - * @param originX Ray X origin, strictly positive - * @param originY Ray Y origin, strictly positive - * @param orientation Angle to cast to, in degrees between 0 and 360 - * @return Distance of the hit to the origin. - */ - float castRay(float originX, float originY, float orientation) const; + void fillColumn(sf::RenderWindow&, unsigned int column, float scale, + sf::Color fillColor = Colors::Wall) const; + /** + * Cast a ray from a given position and return its distance to the origin. + * @param originX Ray X origin, strictly positive + * @param originY Ray Y origin, strictly positive + * @param orientation Angle to cast to, in degrees between 0 and 360 + * @return Distance of the hit to the origin. + */ + float castRay(float originX, float originY, float orientation) const; }; diff --git a/main.cpp b/main.cpp index af75df6..3f91470 100644 --- a/main.cpp +++ b/main.cpp @@ -12,13 +12,13 @@ int main() { - World world(32,32); - world.setBlock(BlockType::AIR,1,1,30,30); - world.setBlock(BlockType::WALL,4,4,2,2); - world.player.move(2,2); - std::cout << world << std::endl; + World world(32,32); + world.setBlock(BlockType::AIR,1,1,30,30); + world.setBlock(BlockType::WALL,4,4,2,2); + world.player.move(2,2); + std::cout << world << std::endl; - sf::RenderWindow window(sf::VideoMode(1000,1000),"Da raycasting"); + sf::RenderWindow window(sf::VideoMode(1000,1000),"Da raycasting"); #ifdef IMGUI if (!ImGui::SFML::Init(window)) { std::cout << "Failed to init Dear ImGui SFML" << std::endl; @@ -31,12 +31,12 @@ int main() // window.setFramerateLimit(60); - sf::Event event{}; - sf::Clock frameTime; - while (window.isOpen()) - { - while (window.pollEvent(event)) - { + sf::Event event{}; + sf::Clock frameTime; + while (window.isOpen()) + { + while (window.pollEvent(event)) + { if (event.type == sf::Event::Closed) { window.close(); continue; @@ -56,43 +56,43 @@ int main() if (io.WantCaptureMouse || io.WantCaptureKeyboard) continue; #endif - if (event.type == sf::Event::KeyPressed) { - switch (event.key.code) { - case sf::Keyboard::Key::Escape: - window.close(); - break; - case sf::Keyboard::Key::Left: - world.player.currentRotationSpeed = -world.player.rotationSpeed; - break; - case sf::Keyboard::Key::Right: - world.player.currentRotationSpeed = world.player.rotationSpeed; - break; + if (event.type == sf::Event::KeyPressed) { + switch (event.key.code) { + case sf::Keyboard::Key::Escape: + window.close(); + break; + case sf::Keyboard::Key::Left: + world.player.currentRotationSpeed = -world.player.rotationSpeed; + break; + case sf::Keyboard::Key::Right: + world.player.currentRotationSpeed = world.player.rotationSpeed; + break; case sf::Keyboard::Key::Up: world.player.updateSpeed(1, 0); break; case sf::Keyboard::Key::Down: world.player.updateSpeed(-1, 0); break; - default: - break; - } - } - else if (event.type == sf::Event::KeyReleased) { - switch (event.key.code) { - case sf::Keyboard::Key::Left: - case sf::Keyboard::Key::Right: - world.player.currentRotationSpeed = 0; - break; + default: + break; + } + } + else if (event.type == sf::Event::KeyReleased) { + switch (event.key.code) { + case sf::Keyboard::Key::Left: + case sf::Keyboard::Key::Right: + world.player.currentRotationSpeed = 0; + break; case sf::Keyboard::Key::Up: case sf::Keyboard::Key::Down: world.player.updateSpeed(0, 0); break; - default: - break; - } - } - } - window.clear(); + default: + break; + } + } + } + window.clear(); const sf::Time& deltaT = frameTime.restart(); #ifdef IMGUI @@ -100,7 +100,7 @@ int main() #endif world.step(deltaT.asSeconds()); - world.render(window); + world.render(window); #ifdef IMGUI // ImGui::ShowDemoWindow(); @@ -126,11 +126,11 @@ int main() ImGui::SFML::Render(window); #endif - window.display(); - } + window.display(); + } #ifdef IMGUI ImGui::SFML::Shutdown(); #endif - return 0; + return 0; } From 41437cd25927741522aa2ab2f5d1733e009d9eee Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sun, 28 Jan 2024 22:08:35 +0000 Subject: [PATCH 34/44] Clean-up: Change indentation to tabs --- CMakeLists.txt | 24 ++-- Player.cpp | 26 ++-- Player.h | 32 ++--- World.cpp | 368 ++++++++++++++++++++++++------------------------- World.h | 66 ++++----- main.cpp | 86 ++++++------ 6 files changed, 301 insertions(+), 301 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 90b4bb5..0845878 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,31 +8,31 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake_modules" ${CMAKE_MODULE_PATH}) find_package(SFML COMPONENTS system window graphics network audio REQUIRED) if(NOT SFML_FOUND) - message(FATAL_ERROR "SFML could not be found") + message(FATAL_ERROR "SFML could not be found") endif() set(LIBS - sfml-window - sfml-graphics) + sfml-window + sfml-graphics) find_package(ImGui QUIET) find_package(ImGui-SFML QUIET) if(NOT ImGui_FOUND OR NOT ImGui-SFML_FOUND OR NO_IMGUI) - message("*Not* building with ImGui") + message("*Not* building with ImGui") else () - message("Building with ImGui") - add_compile_definitions(IMGUI) - set(LIBS ${LIBS} - ImGui-SFML::ImGui-SFML) + message("Building with ImGui") + add_compile_definitions(IMGUI) + set(LIBS ${LIBS} + ImGui-SFML::ImGui-SFML) endif() add_compile_options(-Wall -Wextra) add_executable(raycasting - main.cpp - Player.cpp - World.cpp - ) + main.cpp + Player.cpp + World.cpp + ) target_link_libraries(raycasting ${LIBS}) diff --git a/Player.cpp b/Player.cpp index b6fa26c..694636e 100644 --- a/Player.cpp +++ b/Player.cpp @@ -10,21 +10,21 @@ Player::Player(float x, float y, float alpha) : x(x), y(y), orientation(alpha) void Player::move(float dx, float dy) { - x += dx; - y += dy; + x += dx; + y += dy; } void Player::rotate(float alpha) { - orientation += fmodf(alpha, 360); - if(orientation > 360) - { - orientation -= 360; - } - else if(orientation < 0) - { - orientation += 360; - } + orientation += fmodf(alpha, 360); + if(orientation > 360) + { + orientation -= 360; + } + else if(orientation < 0) + { + orientation += 360; + } /* * Rotate the movement vector along the new angle, assumes that the only @@ -33,9 +33,9 @@ void Player::rotate(float alpha) float prevSpeedX = currentMoveSpeedX; float prevSpeedY = currentMoveSpeedY; currentMoveSpeedX = cosf(-alpha * deg_to_rad) * prevSpeedX - - sinf(-alpha * deg_to_rad) * prevSpeedY; + - sinf(-alpha * deg_to_rad) * prevSpeedY; currentMoveSpeedY = sinf(-alpha * deg_to_rad) * prevSpeedX - + cosf(-alpha * deg_to_rad) * prevSpeedY; + + cosf(-alpha * deg_to_rad) * prevSpeedY; } void Player::updateSpeed(float localX, float localY) { diff --git a/Player.h b/Player.h index eab2795..294e669 100644 --- a/Player.h +++ b/Player.h @@ -11,28 +11,28 @@ static constexpr float deg_to_rad = 3.14159265/180; class Player { public: - Player(float x, float y, float alpha); + Player(float x, float y, float alpha); - float x; - float y; - float orientation; + float x; + float y; + float orientation; - float moveSpeed = 5; - float rotationSpeed = 180; + float moveSpeed = 5; + float rotationSpeed = 180; - float currentMoveSpeedX = 0; - float currentMoveSpeedY = 0; + float currentMoveSpeedX = 0; + float currentMoveSpeedY = 0; - float currentRotationSpeed = 0; + float currentRotationSpeed = 0; - /* View properties. */ - float fov = 70; - float sensorSize = 0.035; /* 35mm, about equivalent to human eye ? */ - float focalLength = sensorSize / (2*tanf((fov*deg_to_rad)/2)); + /* View properties. */ + float fov = 70; + float sensorSize = 0.035; /* 35mm, about equivalent to human eye ? */ + float focalLength = sensorSize / (2*tanf((fov*deg_to_rad)/2)); - void move(float dx, float dy); - void rotate(float alpha); - void updateSpeed(float localX, float localY); + void move(float dx, float dy); + void rotate(float alpha); + void updateSpeed(float localX, float localY); }; diff --git a/World.cpp b/World.cpp index 7880148..b8a8eb6 100644 --- a/World.cpp +++ b/World.cpp @@ -14,22 +14,22 @@ World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::v player(0,0,0), w(w), h(h), map(std::move(worldMap)), groundColor(groundColor), ceilingColor(ceilingColor) { - map.resize(w*h,BlockType::WALL); + map.resize(w*h,BlockType::WALL); } int World::getW() const { - return w; + return w; } int World::getH() const { - return h; + return h; } BlockType World::getBlock(int x, int y) const { - return map[x + w*y]; + return map[x + w*y]; } BlockType World::getBlock(float x, float y) const @@ -39,176 +39,176 @@ BlockType World::getBlock(float x, float y) const void World::setBlock(BlockType block, int x, int y, int width, int height) { - for(int i = 0;i(world.player.x) == i%world.w && static_cast(world.player.y) == i/world.h) - { - ostream << "P"; - } - else - { - ostream << " "; - } - break; - } - case BlockType::WALL: - { - ostream << "W"; - break; - } - case BlockType::DOOR: - { - ostream << "D"; - break; - } - case BlockType::WINDOW: - { - ostream << "W"; - break; - } - } - } - return(ostream); + for(int i = 0;i(world.player.x) == i%world.w && static_cast(world.player.y) == i/world.h) + { + ostream << "P"; + } + else + { + ostream << " "; + } + break; + } + case BlockType::WALL: + { + ostream << "W"; + break; + } + case BlockType::DOOR: + { + ostream << "D"; + break; + } + case BlockType::WINDOW: + { + ostream << "W"; + break; + } + } + } + return(ostream); } float World::castRay(float originX, float originY, float orientation) const { - /* - * Reference used for ray intersection computations : - * https://web.archive.org/web/20220628034315/https://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf - * The logic is as follows : - * - This computes one set of point per edge crossings (horizontal/vertical) - * - The origin not being confined to the grid, offsets are computed to - * align the intersections properly - * - The intersections are at multiples of the tangent of the relevant - * angle for the axis of interest, and simply on successive edges of - * the grid for the other one - * - Depending on the orientation, signs must be taken into account - * to work 360° - * - Those formulas consider regular axes (x→,y↑), however the world is - * built around left-handed axes (x→,y↓), so the rendered world is - * mirrored. This also explains some weird signs for rotations. - */ - /* Offsets to get back on the grid from the ray's origin. */ - float hOffsetX; - float hOffsetY; - float vOffsetX; - float vOffsetY; + /* + * Reference used for ray intersection computations : + * https://web.archive.org/web/20220628034315/https://yunes.informatique.univ-paris-diderot.fr/wp-content/uploads/cours/INFOGRAPHIE/08-Raycasting.pdf + * The logic is as follows : + * - This computes one set of point per edge crossings (horizontal/vertical) + * - The origin not being confined to the grid, offsets are computed to + * align the intersections properly + * - The intersections are at multiples of the tangent of the relevant + * angle for the axis of interest, and simply on successive edges of + * the grid for the other one + * - Depending on the orientation, signs must be taken into account + * to work 360° + * - Those formulas consider regular axes (x→,y↑), however the world is + * built around left-handed axes (x→,y↓), so the rendered world is + * mirrored. This also explains some weird signs for rotations. + */ + /* Offsets to get back on the grid from the ray's origin. */ + float hOffsetX; + float hOffsetY; + float vOffsetX; + float vOffsetY; - /* Signs controlling the direction of travel. */ - float hDir; - float vDir; - /* Need offset for rounding in the right direction ? */ - float hRound; - float vRound; + /* Signs controlling the direction of travel. */ + float hDir; + float vDir; + /* Need offset for rounding in the right direction ? */ + float hRound; + float vRound; - float rads = orientation * deg_to_rad; - /* Used for vertical intersections. */ - float rads_offset = (90 - orientation) * deg_to_rad; + float rads = orientation * deg_to_rad; + /* Used for vertical intersections. */ + float rads_offset = (90 - orientation) * deg_to_rad; - /* Tangents used for the different axes. */ - float hTan = tanf(rads); - float vTan = tanf(rads_offset); + /* Tangents used for the different axes. */ + float hTan = tanf(rads); + float vTan = tanf(rads_offset); - /* Check if cos > 0 for horizontal hits formulas. */ - if (orientation < 90 || orientation > 270) { - hOffsetX = ceilf(originY) - originY; - hOffsetY = ceilf(originY); - hDir = +1; - hRound = 0; - } else { - hOffsetX = originY - floorf(originY); - hOffsetY = floorf(originY); - hDir = -1; - hRound = -1; - } - hTan *= hDir; - hOffsetX *= hTan; + /* Check if cos > 0 for horizontal hits formulas. */ + if (orientation < 90 || orientation > 270) { + hOffsetX = ceilf(originY) - originY; + hOffsetY = ceilf(originY); + hDir = +1; + hRound = 0; + } else { + hOffsetX = originY - floorf(originY); + hOffsetY = floorf(originY); + hDir = -1; + hRound = -1; + } + hTan *= hDir; + hOffsetX *= hTan; - /* Check if sin > 0 for vertical hits formulas. */ - if (orientation < 180) { - vOffsetX = ceilf(originX); - vOffsetY = ceilf(originX) - originX; - vDir = 1; - vRound = 0; - } else { - vOffsetX = floorf(originX); - vOffsetY = originX - floorf(originX); - vDir = -1; - vRound = -1; - } - vTan *= vDir; - vOffsetY *= vTan; + /* Check if sin > 0 for vertical hits formulas. */ + if (orientation < 180) { + vOffsetX = ceilf(originX); + vOffsetY = ceilf(originX) - originX; + vDir = 1; + vRound = 0; + } else { + vOffsetX = floorf(originX); + vOffsetY = originX - floorf(originX); + vDir = -1; + vRound = -1; + } + vTan *= vDir; + vOffsetY *= vTan; - /* - * Now we have all the constants and deltas to work with, cast the ray. - * Generated points follow the formulas : - * - h-intersect : (originX + hOffsetX + hTan*i, hOffsetY + hDir*i) - * - v-intersect : (vOffsetX + vDir*i, originY + vOffsetY + vTan*i) - */ - int i = 0; - float hCheckX = originX + hOffsetX; - float hCheckY = hOffsetY; - /* Bounds + sanity check. */ - while (hCheckX >= 0 && hCheckX <= static_cast(w) && + /* + * Now we have all the constants and deltas to work with, cast the ray. + * Generated points follow the formulas : + * - h-intersect : (originX + hOffsetX + hTan*i, hOffsetY + hDir*i) + * - v-intersect : (vOffsetX + vDir*i, originY + vOffsetY + vTan*i) + */ + int i = 0; + float hCheckX = originX + hOffsetX; + float hCheckY = hOffsetY; + /* Bounds + sanity check. */ + while (hCheckX >= 0 && hCheckX <= static_cast(w) && hCheckY >= 0 && hCheckY <= static_cast(h) && i < h) { - if (getBlock(floorf(hCheckX), floorf(hCheckY) + hRound) == BlockType::WALL) { - break; - } + if (getBlock(floorf(hCheckX), floorf(hCheckY) + hRound) == BlockType::WALL) { + break; + } - hCheckX += hTan; - hCheckY += hDir; - i++; - } + hCheckX += hTan; + hCheckY += hDir; + i++; + } - i = 0; - float vCheckX = vOffsetX; - float vCheckY = originY + vOffsetY; + i = 0; + float vCheckX = vOffsetX; + float vCheckY = originY + vOffsetY; - /* Bounds + sanity check. */ - while (vCheckX >= 0 && vCheckX < static_cast(w) && + /* Bounds + sanity check. */ + while (vCheckX >= 0 && vCheckX < static_cast(w) && vCheckY >= 0 && vCheckY < static_cast(h) && i < w) { - if (getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)) == BlockType::WALL) { - break; - } + if (getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)) == BlockType::WALL) { + break; + } - vCheckX += vDir; - vCheckY += vTan; - i++; - } + vCheckX += vDir; + vCheckY += vTan; + i++; + } - /* - * We may or may not have hit something. Check which coordinates are closest - * and use those for computing the apparent size on screen. - */ - float hDist = sqrtf((originX - hCheckX)*(originX - hCheckX) + - (originY - hCheckY)*(originY - hCheckY)); - float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + - (originY - vCheckY)*(originY - vCheckY)); + /* + * We may or may not have hit something. Check which coordinates are closest + * and use those for computing the apparent size on screen. + */ + float hDist = sqrtf((originX - hCheckX)*(originX - hCheckX) + + (originY - hCheckY)*(originY - hCheckY)); + float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + + (originY - vCheckY)*(originY - vCheckY)); - return hDist > vDist ? vDist : hDist; + return hDist > vDist ? vDist : hDist; } void World::fillColumn(sf::RenderWindow& window, unsigned int column, @@ -227,51 +227,51 @@ void World::render(sf::RenderWindow& window) const { float windowX = static_cast(window.getSize().x); float windowY = static_cast(window.getSize().y); - /* - * Draw ground and sky planes through half of the screen, as the walls - * will get drawn over them. - * This doesn't work if we support textures/levels. - */ - sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); - ground.setFillColor(groundColor); - ground.setPosition(0,windowY/2.0f); + /* + * Draw ground and sky planes through half of the screen, as the walls + * will get drawn over them. + * This doesn't work if we support textures/levels. + */ + sf::RectangleShape ground = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); + ground.setFillColor(groundColor); + ground.setPosition(0,windowY/2.0f); - sf::RectangleShape ceiling = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); - ceiling.setFillColor(ceilingColor); + sf::RectangleShape ceiling = sf::RectangleShape(sf::Vector2f(windowX,windowY/2.0f)); + ceiling.setFillColor(ceilingColor); - window.draw(ground); - window.draw(ceiling); + window.draw(ground); + window.draw(ceiling); const float worldToCamera = (player.focalLength*2)/player.sensorSize; - /* - * Throw rays and draw walls over the ceiling and ground. - * Only throws in the plane, which doesn't work for levels/3D. - */ - for(unsigned int i = 0 ; i < window.getSize().x ; i++) - { - float deltaAngle = (player.fov/windowX) * (static_cast(i)-windowX/2.0f); - float rayAngle = player.orientation + deltaAngle; - if (rayAngle < 0) { - rayAngle += 360; - } else if (rayAngle > 360) { - rayAngle -= 360; - } - float obstacleScale = worldToCamera / castRay(player.x, player.y, rayAngle); - /* 2 Is wall height in meters. */ - fillColumn(window, i, obstacleScale); - } + /* + * Throw rays and draw walls over the ceiling and ground. + * Only throws in the plane, which doesn't work for levels/3D. + */ + for(unsigned int i = 0 ; i < window.getSize().x ; i++) + { + float deltaAngle = (player.fov/windowX) * (static_cast(i)-windowX/2.0f); + float rayAngle = player.orientation + deltaAngle; + if (rayAngle < 0) { + rayAngle += 360; + } else if (rayAngle > 360) { + rayAngle -= 360; + } + float obstacleScale = worldToCamera / castRay(player.x, player.y, rayAngle); + /* 2 Is wall height in meters. */ + fillColumn(window, i, obstacleScale); + } } void World::step(const float& stepTime) { - player.move(player.currentMoveSpeedX*stepTime, - player.currentMoveSpeedY*stepTime); + player.move(player.currentMoveSpeedX*stepTime, + player.currentMoveSpeedY*stepTime); /* Undo last move if the player would end up in a wall. */ if (getBlock(player.x, player.y) != BlockType::AIR) { player.move(-player.currentMoveSpeedX*stepTime, -player.currentMoveSpeedY*stepTime); } - player.rotate(player.currentRotationSpeed*stepTime); + player.rotate(player.currentRotationSpeed*stepTime); #ifdef IMGUI if (ImGui::Begin("MapEdit")) { diff --git a/World.h b/World.h index 19c503a..43b4b1e 100644 --- a/World.h +++ b/World.h @@ -14,54 +14,54 @@ #include "Player.h" enum class BlockType { - AIR, - WALL, - DOOR, - WINDOW, + AIR, + WALL, + DOOR, + WINDOW, }; class World { public: - Player player; + Player player; - World(int w, int h, - sf::Color groundColor = Colors::Ground, - sf::Color ceilingColor = Colors::Ceiling, - std::vector worldMap = {}); - int getW() const; - int getH() const; + World(int w, int h, + sf::Color groundColor = Colors::Ground, + sf::Color ceilingColor = Colors::Ceiling, + std::vector worldMap = {}); + int getW() const; + int getH() const; inline BlockType getBlock(int x, int y) const; inline BlockType getBlock(float x, float y) const; void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); - void render(sf::RenderWindow&) const; + void render(sf::RenderWindow&) const; - /** - * Move the world one step forward. - * @param stepTime delta time since last step, in seconds - */ - void step(const float& stepTime); + /** + * Move the world one step forward. + * @param stepTime delta time since last step, in seconds + */ + void step(const float& stepTime); - friend std::ostream& operator<<(std::ostream& ostream, World const & world); + friend std::ostream& operator<<(std::ostream& ostream, World const & world); private: - int w; - int h; - std::vector map; + int w; + int h; + std::vector map; - sf::Color groundColor; - sf::Color ceilingColor; + sf::Color groundColor; + sf::Color ceilingColor; - void fillColumn(sf::RenderWindow&, unsigned int column, float scale, - sf::Color fillColor = Colors::Wall) const; - /** - * Cast a ray from a given position and return its distance to the origin. - * @param originX Ray X origin, strictly positive - * @param originY Ray Y origin, strictly positive - * @param orientation Angle to cast to, in degrees between 0 and 360 - * @return Distance of the hit to the origin. - */ - float castRay(float originX, float originY, float orientation) const; + void fillColumn(sf::RenderWindow&, unsigned int column, float scale, + sf::Color fillColor = Colors::Wall) const; + /** + * Cast a ray from a given position and return its distance to the origin. + * @param originX Ray X origin, strictly positive + * @param originY Ray Y origin, strictly positive + * @param orientation Angle to cast to, in degrees between 0 and 360 + * @return Distance of the hit to the origin. + */ + float castRay(float originX, float originY, float orientation) const; }; diff --git a/main.cpp b/main.cpp index af75df6..3f91470 100644 --- a/main.cpp +++ b/main.cpp @@ -12,13 +12,13 @@ int main() { - World world(32,32); - world.setBlock(BlockType::AIR,1,1,30,30); - world.setBlock(BlockType::WALL,4,4,2,2); - world.player.move(2,2); - std::cout << world << std::endl; + World world(32,32); + world.setBlock(BlockType::AIR,1,1,30,30); + world.setBlock(BlockType::WALL,4,4,2,2); + world.player.move(2,2); + std::cout << world << std::endl; - sf::RenderWindow window(sf::VideoMode(1000,1000),"Da raycasting"); + sf::RenderWindow window(sf::VideoMode(1000,1000),"Da raycasting"); #ifdef IMGUI if (!ImGui::SFML::Init(window)) { std::cout << "Failed to init Dear ImGui SFML" << std::endl; @@ -31,12 +31,12 @@ int main() // window.setFramerateLimit(60); - sf::Event event{}; - sf::Clock frameTime; - while (window.isOpen()) - { - while (window.pollEvent(event)) - { + sf::Event event{}; + sf::Clock frameTime; + while (window.isOpen()) + { + while (window.pollEvent(event)) + { if (event.type == sf::Event::Closed) { window.close(); continue; @@ -56,43 +56,43 @@ int main() if (io.WantCaptureMouse || io.WantCaptureKeyboard) continue; #endif - if (event.type == sf::Event::KeyPressed) { - switch (event.key.code) { - case sf::Keyboard::Key::Escape: - window.close(); - break; - case sf::Keyboard::Key::Left: - world.player.currentRotationSpeed = -world.player.rotationSpeed; - break; - case sf::Keyboard::Key::Right: - world.player.currentRotationSpeed = world.player.rotationSpeed; - break; + if (event.type == sf::Event::KeyPressed) { + switch (event.key.code) { + case sf::Keyboard::Key::Escape: + window.close(); + break; + case sf::Keyboard::Key::Left: + world.player.currentRotationSpeed = -world.player.rotationSpeed; + break; + case sf::Keyboard::Key::Right: + world.player.currentRotationSpeed = world.player.rotationSpeed; + break; case sf::Keyboard::Key::Up: world.player.updateSpeed(1, 0); break; case sf::Keyboard::Key::Down: world.player.updateSpeed(-1, 0); break; - default: - break; - } - } - else if (event.type == sf::Event::KeyReleased) { - switch (event.key.code) { - case sf::Keyboard::Key::Left: - case sf::Keyboard::Key::Right: - world.player.currentRotationSpeed = 0; - break; + default: + break; + } + } + else if (event.type == sf::Event::KeyReleased) { + switch (event.key.code) { + case sf::Keyboard::Key::Left: + case sf::Keyboard::Key::Right: + world.player.currentRotationSpeed = 0; + break; case sf::Keyboard::Key::Up: case sf::Keyboard::Key::Down: world.player.updateSpeed(0, 0); break; - default: - break; - } - } - } - window.clear(); + default: + break; + } + } + } + window.clear(); const sf::Time& deltaT = frameTime.restart(); #ifdef IMGUI @@ -100,7 +100,7 @@ int main() #endif world.step(deltaT.asSeconds()); - world.render(window); + world.render(window); #ifdef IMGUI // ImGui::ShowDemoWindow(); @@ -126,11 +126,11 @@ int main() ImGui::SFML::Render(window); #endif - window.display(); - } + window.display(); + } #ifdef IMGUI ImGui::SFML::Shutdown(); #endif - return 0; + return 0; } From c875adfcbbb3cad03aa582484bd024d8f3d4ede6 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Mon, 29 Jan 2024 00:21:14 +0000 Subject: [PATCH 35/44] main/debug: Make the frame timing graph a sliding window Use memmove to shift the whole array by one measure to the left, dropping the oldest one and adding a new one at the head. This creates a better visual effect of a graph being produced on the right and scrolling to the left, rather than a looping writing head that overwrites previous data. --- main.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index 3f91470..0aedcd7 100644 --- a/main.cpp +++ b/main.cpp @@ -115,7 +115,12 @@ int main() ImGui::SetNextWindowBgAlpha(0.2f); ImGui::Begin("FPS", nullptr ,window_flags); if (fpsDataClock.getElapsedTime().asMilliseconds() > 20) { - frameTimings[frameCount%100] = static_cast(deltaT.asMicroseconds()); + if (frameCount >= 100) { + memmove(frameTimings, frameTimings + 1, 99 * sizeof(float)); + frameTimings[99] = static_cast(deltaT.asMicroseconds()); + } else [[unlikely]] { + frameTimings[frameCount] = static_cast(deltaT.asMicroseconds()); + } frameCount++; fpsDataClock.restart(); } From 940c1d706bc5330eead7116d5e006b994f92b707 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Mon, 29 Jan 2024 00:21:14 +0000 Subject: [PATCH 36/44] main/debug: Make the frame timing graph a sliding window Use memmove to shift the whole array by one measure to the left, dropping the oldest one and adding a new one at the head. This creates a better visual effect of a graph being produced on the right and scrolling to the left, rather than a looping writing head that overwrites previous data. --- main.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index 3f91470..0aedcd7 100644 --- a/main.cpp +++ b/main.cpp @@ -115,7 +115,12 @@ int main() ImGui::SetNextWindowBgAlpha(0.2f); ImGui::Begin("FPS", nullptr ,window_flags); if (fpsDataClock.getElapsedTime().asMilliseconds() > 20) { - frameTimings[frameCount%100] = static_cast(deltaT.asMicroseconds()); + if (frameCount >= 100) { + memmove(frameTimings, frameTimings + 1, 99 * sizeof(float)); + frameTimings[99] = static_cast(deltaT.asMicroseconds()); + } else [[unlikely]] { + frameTimings[frameCount] = static_cast(deltaT.asMicroseconds()); + } frameCount++; fpsDataClock.restart(); } From 10d0dee35ddb4b0e6b013c48eaf6a022e1de2e1d Mon Sep 17 00:00:00 2001 From: trotFunky Date: Mon, 29 Jan 2024 00:25:57 +0000 Subject: [PATCH 37/44] World: Re-use RectangleShapes for rendering sf::RectangleShapes were created brand new each frame, which takes a lot of time. Instead, keep an array of them available and update them instead of creating new ones. This also allows a nice optimization of only updating them if needed, by checking their previous value. Update the available number of rects when the window resizes. --- World.cpp | 22 ++++++++++++++++------ World.h | 6 ++++-- main.cpp | 3 +++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/World.cpp b/World.cpp index b8a8eb6..a7f7bf1 100644 --- a/World.cpp +++ b/World.cpp @@ -27,6 +27,12 @@ int World::getH() const return h; } +void World::resizeWindow(const sf::FloatRect& resizedView) { + sf::RectangleShape defaultRectangle(sf::Vector2f(1,2)); + defaultRectangle.setFillColor(Colors::Wall); + renderColumns.resize(static_cast(resizedView.width), defaultRectangle); +} + BlockType World::getBlock(int x, int y) const { return map[x + w*y]; @@ -212,18 +218,22 @@ float World::castRay(float originX, float originY, float orientation) const } void World::fillColumn(sf::RenderWindow& window, unsigned int column, - float scale, sf::Color fillColor) const + float scale, sf::Color fillColor) { float columnHeight = static_cast(window.getSize().y)*scale; - sf::RectangleShape pixelColumn(sf::Vector2f(1,columnHeight)); - pixelColumn.setPosition(static_cast(column), - (static_cast(window.getSize().y)-columnHeight)/2.0f); - pixelColumn.setFillColor(fillColor); + sf::RectangleShape& pixelColumn = renderColumns[column]; + if (pixelColumn.getSize().y != columnHeight) { + pixelColumn.setSize({1, columnHeight}); + pixelColumn.setPosition(static_cast(column), + (static_cast(window.getSize().y) - columnHeight) / 2.0f); + } + if (pixelColumn.getFillColor() != fillColor) + pixelColumn.setFillColor(fillColor); window.draw(pixelColumn); } -void World::render(sf::RenderWindow& window) const +void World::render(sf::RenderWindow& window) { float windowX = static_cast(window.getSize().x); float windowY = static_cast(window.getSize().y); diff --git a/World.h b/World.h index 43b4b1e..7b24d70 100644 --- a/World.h +++ b/World.h @@ -30,12 +30,13 @@ public: std::vector worldMap = {}); int getW() const; int getH() const; + void resizeWindow(const sf::FloatRect& resizedView); inline BlockType getBlock(int x, int y) const; inline BlockType getBlock(float x, float y) const; void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); - void render(sf::RenderWindow&) const; + void render(sf::RenderWindow&); /** * Move the world one step forward. @@ -47,13 +48,14 @@ public: private: int w; int h; + std::vector renderColumns; std::vector map; sf::Color groundColor; sf::Color ceilingColor; void fillColumn(sf::RenderWindow&, unsigned int column, float scale, - sf::Color fillColor = Colors::Wall) const; + sf::Color fillColor = Colors::Wall); /** * Cast a ray from a given position and return its distance to the origin. * @param originX Ray X origin, strictly positive diff --git a/main.cpp b/main.cpp index 0aedcd7..83ef919 100644 --- a/main.cpp +++ b/main.cpp @@ -15,10 +15,12 @@ int main() World world(32,32); world.setBlock(BlockType::AIR,1,1,30,30); world.setBlock(BlockType::WALL,4,4,2,2); + world.setBlock(BlockType::WALL, 4, 6, 1, 10); world.player.move(2,2); std::cout << world << std::endl; sf::RenderWindow window(sf::VideoMode(1000,1000),"Da raycasting"); + world.resizeWindow(sf::FloatRect(0,0, 1000, 1000)); #ifdef IMGUI if (!ImGui::SFML::Init(window)) { std::cout << "Failed to init Dear ImGui SFML" << std::endl; @@ -47,6 +49,7 @@ int main() static_cast(event.size.width), static_cast(event.size.height)); window.setView(sf::View(newView)); + world.resizeWindow(newView); continue; } #ifdef IMGUI From 029f753bbf193eea2fcbe8a62c0b26f28929ebfa Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Mon, 29 Jan 2024 00:25:54 +0000 Subject: [PATCH 38/44] World: Re-use RectangleShapes for rendering sf::RectangleShapes were created brand new each frame, which takes a lot of time. Instead, keep an array of them available and update them instead of creating new ones. This also allows a nice optimization of only updating them if needed, by checking their previous value. Update the available number of rects when the window resizes. --- World.cpp | 22 ++++++++++++++++------ World.h | 6 ++++-- main.cpp | 3 +++ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/World.cpp b/World.cpp index b8a8eb6..a7f7bf1 100644 --- a/World.cpp +++ b/World.cpp @@ -27,6 +27,12 @@ int World::getH() const return h; } +void World::resizeWindow(const sf::FloatRect& resizedView) { + sf::RectangleShape defaultRectangle(sf::Vector2f(1,2)); + defaultRectangle.setFillColor(Colors::Wall); + renderColumns.resize(static_cast(resizedView.width), defaultRectangle); +} + BlockType World::getBlock(int x, int y) const { return map[x + w*y]; @@ -212,18 +218,22 @@ float World::castRay(float originX, float originY, float orientation) const } void World::fillColumn(sf::RenderWindow& window, unsigned int column, - float scale, sf::Color fillColor) const + float scale, sf::Color fillColor) { float columnHeight = static_cast(window.getSize().y)*scale; - sf::RectangleShape pixelColumn(sf::Vector2f(1,columnHeight)); - pixelColumn.setPosition(static_cast(column), - (static_cast(window.getSize().y)-columnHeight)/2.0f); - pixelColumn.setFillColor(fillColor); + sf::RectangleShape& pixelColumn = renderColumns[column]; + if (pixelColumn.getSize().y != columnHeight) { + pixelColumn.setSize({1, columnHeight}); + pixelColumn.setPosition(static_cast(column), + (static_cast(window.getSize().y) - columnHeight) / 2.0f); + } + if (pixelColumn.getFillColor() != fillColor) + pixelColumn.setFillColor(fillColor); window.draw(pixelColumn); } -void World::render(sf::RenderWindow& window) const +void World::render(sf::RenderWindow& window) { float windowX = static_cast(window.getSize().x); float windowY = static_cast(window.getSize().y); diff --git a/World.h b/World.h index 43b4b1e..7b24d70 100644 --- a/World.h +++ b/World.h @@ -30,12 +30,13 @@ public: std::vector worldMap = {}); int getW() const; int getH() const; + void resizeWindow(const sf::FloatRect& resizedView); inline BlockType getBlock(int x, int y) const; inline BlockType getBlock(float x, float y) const; void setBlock(BlockType block, int x, int y, int width = 1, int height = 1); - void render(sf::RenderWindow&) const; + void render(sf::RenderWindow&); /** * Move the world one step forward. @@ -47,13 +48,14 @@ public: private: int w; int h; + std::vector renderColumns; std::vector map; sf::Color groundColor; sf::Color ceilingColor; void fillColumn(sf::RenderWindow&, unsigned int column, float scale, - sf::Color fillColor = Colors::Wall) const; + sf::Color fillColor = Colors::Wall); /** * Cast a ray from a given position and return its distance to the origin. * @param originX Ray X origin, strictly positive diff --git a/main.cpp b/main.cpp index 0aedcd7..83ef919 100644 --- a/main.cpp +++ b/main.cpp @@ -15,10 +15,12 @@ int main() World world(32,32); world.setBlock(BlockType::AIR,1,1,30,30); world.setBlock(BlockType::WALL,4,4,2,2); + world.setBlock(BlockType::WALL, 4, 6, 1, 10); world.player.move(2,2); std::cout << world << std::endl; sf::RenderWindow window(sf::VideoMode(1000,1000),"Da raycasting"); + world.resizeWindow(sf::FloatRect(0,0, 1000, 1000)); #ifdef IMGUI if (!ImGui::SFML::Init(window)) { std::cout << "Failed to init Dear ImGui SFML" << std::endl; @@ -47,6 +49,7 @@ int main() static_cast(event.size.width), static_cast(event.size.height)); window.setView(sf::View(newView)); + world.resizeWindow(newView); continue; } #ifdef IMGUI From b0c9d2478770687911c4d8b2c57768eab26c5188 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Sun, 4 Feb 2024 21:04:07 +0000 Subject: [PATCH 39/44] World: Add projection correction factor Rays are all sent from the center of the camera plane, which makes rays with a greater angle longer than if they were properly sent perpendicularly from the camera plane. Correct this by projecting the ray to the look direction (which is perpendicular to the camera plane). --- World.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/World.cpp b/World.cpp index a7f7bf1..8f37c24 100644 --- a/World.cpp +++ b/World.cpp @@ -267,7 +267,10 @@ void World::render(sf::RenderWindow& window) } else if (rayAngle > 360) { rayAngle -= 360; } - float obstacleScale = worldToCamera / castRay(player.x, player.y, rayAngle); + float obstacleScale = worldToCamera / ( + castRay(player.x, player.y, rayAngle) * + cosf(deltaAngle*deg_to_rad) + ); /* 2 Is wall height in meters. */ fillColumn(window, i, obstacleScale); } From 3492cfa63dbd406875c792eb7a90002f1e104f53 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Sun, 4 Feb 2024 21:04:07 +0000 Subject: [PATCH 40/44] World: Add projection correction factor Rays are all sent from the center of the camera plane, which makes rays with a greater angle longer than if they were properly sent perpendicularly from the camera plane. Correct this by projecting the ray to the look direction (which is perpendicular to the camera plane). --- World.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/World.cpp b/World.cpp index a7f7bf1..8f37c24 100644 --- a/World.cpp +++ b/World.cpp @@ -267,7 +267,10 @@ void World::render(sf::RenderWindow& window) } else if (rayAngle > 360) { rayAngle -= 360; } - float obstacleScale = worldToCamera / castRay(player.x, player.y, rayAngle); + float obstacleScale = worldToCamera / ( + castRay(player.x, player.y, rayAngle) * + cosf(deltaAngle*deg_to_rad) + ); /* 2 Is wall height in meters. */ fillColumn(window, i, obstacleScale); } From 8199fd3449b1ff7096c70c8a790f98942eb27dd1 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Mon, 5 Feb 2024 00:33:01 +0000 Subject: [PATCH 41/44] World: Introduce a struct for raycast results In order to implement more complexe behaviors, more information than the distance is needed from a raycast. Add a new struct to store and return this information, currently hit position and hit type, and update castRay to return that instead. --- World.cpp | 20 +++++++++++++++++--- World.h | 12 ++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/World.cpp b/World.cpp index 8f37c24..d23aa1f 100644 --- a/World.cpp +++ b/World.cpp @@ -99,7 +99,7 @@ std::ostream& operator<<(std::ostream& ostream, World const& world) return(ostream); } -float World::castRay(float originX, float originY, float orientation) const +RaycastResult World::castRay(float originX, float originY, float orientation) const { /* * Reference used for ray intersection computations : @@ -214,7 +214,21 @@ float World::castRay(float originX, float originY, float orientation) const float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + (originY - vCheckY)*(originY - vCheckY)); - return hDist > vDist ? vDist : hDist; + + RaycastResult result{}; + if (hDist > vDist) { + result.distance = vDist; + result.hitX = vCheckX; + result.hitY = vCheckY; + } else { + result.distance = hDist; + result.hitX = hCheckX; + result.hitY = hCheckY; + } + result.hitBlock = getBlock(floorf(result.hitX) + vRound, + floorf(result.hitY)); + + return result; } void World::fillColumn(sf::RenderWindow& window, unsigned int column, @@ -268,7 +282,7 @@ void World::render(sf::RenderWindow& window) rayAngle -= 360; } float obstacleScale = worldToCamera / ( - castRay(player.x, player.y, rayAngle) * + castRay(player.x, player.y, rayAngle).distance * cosf(deltaAngle*deg_to_rad) ); /* 2 Is wall height in meters. */ diff --git a/World.h b/World.h index 7b24d70..5e3a7e2 100644 --- a/World.h +++ b/World.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "Color.h" #include "Player.h" @@ -20,6 +21,13 @@ enum class BlockType { WINDOW, }; +struct RaycastResult { + float distance; + float hitX; + float hitY; + BlockType hitBlock; +}; + class World { public: Player player; @@ -61,9 +69,9 @@ private: * @param originX Ray X origin, strictly positive * @param originY Ray Y origin, strictly positive * @param orientation Angle to cast to, in degrees between 0 and 360 - * @return Distance of the hit to the origin. + * @return Result struct containing information on the hit. */ - float castRay(float originX, float originY, float orientation) const; + RaycastResult castRay(float originX, float originY, float orientation) const; }; From 9678e8c286e9cea11de67cf7ea70391a39af14f9 Mon Sep 17 00:00:00 2001 From: Teo-CD Date: Mon, 5 Feb 2024 00:33:01 +0000 Subject: [PATCH 42/44] World: Introduce a struct for raycast results In order to implement more complexe behaviors, more information than the distance is needed from a raycast. Add a new struct to store and return this information, currently hit position and hit type, and update castRay to return that instead. --- World.cpp | 20 +++++++++++++++++--- World.h | 12 ++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/World.cpp b/World.cpp index 8f37c24..d23aa1f 100644 --- a/World.cpp +++ b/World.cpp @@ -99,7 +99,7 @@ std::ostream& operator<<(std::ostream& ostream, World const& world) return(ostream); } -float World::castRay(float originX, float originY, float orientation) const +RaycastResult World::castRay(float originX, float originY, float orientation) const { /* * Reference used for ray intersection computations : @@ -214,7 +214,21 @@ float World::castRay(float originX, float originY, float orientation) const float vDist = sqrtf((originX - vCheckX)*(originX - vCheckX) + (originY - vCheckY)*(originY - vCheckY)); - return hDist > vDist ? vDist : hDist; + + RaycastResult result{}; + if (hDist > vDist) { + result.distance = vDist; + result.hitX = vCheckX; + result.hitY = vCheckY; + } else { + result.distance = hDist; + result.hitX = hCheckX; + result.hitY = hCheckY; + } + result.hitBlock = getBlock(floorf(result.hitX) + vRound, + floorf(result.hitY)); + + return result; } void World::fillColumn(sf::RenderWindow& window, unsigned int column, @@ -268,7 +282,7 @@ void World::render(sf::RenderWindow& window) rayAngle -= 360; } float obstacleScale = worldToCamera / ( - castRay(player.x, player.y, rayAngle) * + castRay(player.x, player.y, rayAngle).distance * cosf(deltaAngle*deg_to_rad) ); /* 2 Is wall height in meters. */ diff --git a/World.h b/World.h index 7b24d70..5e3a7e2 100644 --- a/World.h +++ b/World.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "Color.h" #include "Player.h" @@ -20,6 +21,13 @@ enum class BlockType { WINDOW, }; +struct RaycastResult { + float distance; + float hitX; + float hitY; + BlockType hitBlock; +}; + class World { public: Player player; @@ -61,9 +69,9 @@ private: * @param originX Ray X origin, strictly positive * @param originY Ray Y origin, strictly positive * @param orientation Angle to cast to, in degrees between 0 and 360 - * @return Distance of the hit to the origin. + * @return Result struct containing information on the hit. */ - float castRay(float originX, float originY, float orientation) const; + RaycastResult castRay(float originX, float originY, float orientation) const; }; From 7241ed73216dde964cc612071673dfcd16996dbf Mon Sep 17 00:00:00 2001 From: trotFunky Date: Mon, 5 Feb 2024 13:55:27 +0000 Subject: [PATCH 43/44] Debug: Support frame tracing via Tracy Use CMake FetchContent to pull tracy sources and build it if frame tracing is enabled. As frame tracing requires annotations in the code, create a wrapper that replaces them with our own defines that are empty when tracing is not enabled. --- CMakeLists.txt | 16 ++++++++++++++++ FrameTracing.h | 24 ++++++++++++++++++++++++ World.cpp | 5 +++++ main.cpp | 5 +++++ 4 files changed, 50 insertions(+) create mode 100644 FrameTracing.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0845878..83dc7d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,22 @@ else () ImGui-SFML::ImGui-SFML) endif() +if(TRACE_FRAMES) + message("Building with Tracy") + add_compile_definitions(TRACE_FRAMES) + + include(FetchContent) + FetchContent_Declare( + Tracy + GIT_REPOSITORY https://github.com/wolfpld/tracy.git + GIT_TAG d46ffb4e9f132fc95bdd7d04207d8d669a9d4100 + ) + FetchContent_MakeAvailable(Tracy) + + set(LIBS ${LIBS} + TracyClient) +endif() + add_compile_options(-Wall -Wextra) add_executable(raycasting diff --git a/FrameTracing.h b/FrameTracing.h new file mode 100644 index 0000000..64ca952 --- /dev/null +++ b/FrameTracing.h @@ -0,0 +1,24 @@ +// +// Created by trotfunky on 05/02/24. +// + +#ifndef RAYCASTING_FRAMETRACING_H +#define RAYCASTING_FRAMETRACING_H + +/* + * Basic wrapper to allow enabling and disabling frame tracing using + * Tracy. + * https://github.com/wolfpld/tracy + */ +#ifdef TRACE_FRAMES + #include "tracy/Tracy.hpp" + #define FTrace_FrameMark FrameMark + #define FTrace_Scope ZoneScoped + #define FTrace_NamedScope(name) ZoneScopedN(name) +#else + #define FTrace_FrameMark + #define FTrace_Scope + #define FTrace_NamedScope(name) +#endif /* TRACE_FRAMES */ + +#endif //RAYCASTING_FRAMETRACING_H diff --git a/World.cpp b/World.cpp index d23aa1f..1300b7f 100644 --- a/World.cpp +++ b/World.cpp @@ -9,6 +9,7 @@ #include #include #endif +#include "FrameTracing.h" World::World(int w, int h, sf::Color groundColor, sf::Color ceilingColor, std::vector worldMap) : player(0,0,0), w(w), h(h), map(std::move(worldMap)), @@ -117,6 +118,7 @@ RaycastResult World::castRay(float originX, float originY, float orientation) co * built around left-handed axes (x→,y↓), so the rendered world is * mirrored. This also explains some weird signs for rotations. */ + FTrace_Scope; /* Offsets to get back on the grid from the ray's origin. */ float hOffsetX; float hOffsetY; @@ -234,6 +236,7 @@ RaycastResult World::castRay(float originX, float originY, float orientation) co void World::fillColumn(sf::RenderWindow& window, unsigned int column, float scale, sf::Color fillColor) { + FTrace_Scope; float columnHeight = static_cast(window.getSize().y)*scale; sf::RectangleShape& pixelColumn = renderColumns[column]; if (pixelColumn.getSize().y != columnHeight) { @@ -249,6 +252,7 @@ void World::fillColumn(sf::RenderWindow& window, unsigned int column, void World::render(sf::RenderWindow& window) { + FTrace_Scope; float windowX = static_cast(window.getSize().x); float windowY = static_cast(window.getSize().y); /* @@ -291,6 +295,7 @@ void World::render(sf::RenderWindow& window) } void World::step(const float& stepTime) { + FTrace_Scope; player.move(player.currentMoveSpeedX*stepTime, player.currentMoveSpeedY*stepTime); /* Undo last move if the player would end up in a wall. */ diff --git a/main.cpp b/main.cpp index 83ef919..a42b76b 100644 --- a/main.cpp +++ b/main.cpp @@ -5,6 +5,7 @@ #include #include #endif +#include "FrameTracing.h" #include "World.h" @@ -37,8 +38,11 @@ int main() sf::Clock frameTime; while (window.isOpen()) { + FTrace_NamedScope("MainLoop"); + while (window.pollEvent(event)) { + FTrace_NamedScope("EventLoop"); if (event.type == sf::Event::Closed) { window.close(); continue; @@ -135,6 +139,7 @@ int main() #endif window.display(); + FTrace_FrameMark; } #ifdef IMGUI From f7e1b3eab9976cdce081e9b199b09e2e38245ad5 Mon Sep 17 00:00:00 2001 From: trotFunky Date: Mon, 5 Feb 2024 23:04:19 +0000 Subject: [PATCH 44/44] World: Fix wrong block type being returned in castRay The GetBlock() call has specific offsets that are angle and axis dependent to get the correct square on the grid from the coordinates of a hit. This is something that I forgot when introducing the RaycastResult struct, and just used the first GetBlock() as a common call, returning wrong types in some cases. As with the hit coordinates, save the type of block hit and use that depending on the closest hit later, rather than a common and wrong GetBlock(). --- World.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/World.cpp b/World.cpp index 1300b7f..f06b00a 100644 --- a/World.cpp +++ b/World.cpp @@ -179,10 +179,12 @@ RaycastResult World::castRay(float originX, float originY, float orientation) co int i = 0; float hCheckX = originX + hOffsetX; float hCheckY = hOffsetY; + BlockType hHitBlock = BlockType::AIR; /* Bounds + sanity check. */ while (hCheckX >= 0 && hCheckX <= static_cast(w) && hCheckY >= 0 && hCheckY <= static_cast(h) && i < h) { - if (getBlock(floorf(hCheckX), floorf(hCheckY) + hRound) == BlockType::WALL) { + hHitBlock = getBlock(floorf(hCheckX), floorf(hCheckY) + hRound); + if (hHitBlock == BlockType::WALL) { break; } @@ -194,11 +196,12 @@ RaycastResult World::castRay(float originX, float originY, float orientation) co i = 0; float vCheckX = vOffsetX; float vCheckY = originY + vOffsetY; - + BlockType vHitBlock = BlockType::AIR; /* Bounds + sanity check. */ while (vCheckX >= 0 && vCheckX < static_cast(w) && vCheckY >= 0 && vCheckY < static_cast(h) && i < w) { - if (getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)) == BlockType::WALL) { + vHitBlock = getBlock(floorf(vCheckX) + vRound, floorf(vCheckY)); + if (vHitBlock == BlockType::WALL) { break; } @@ -222,13 +225,13 @@ RaycastResult World::castRay(float originX, float originY, float orientation) co result.distance = vDist; result.hitX = vCheckX; result.hitY = vCheckY; + result.hitBlock = vHitBlock; } else { result.distance = hDist; result.hitX = hCheckX; result.hitY = hCheckY; + result.hitBlock = hHitBlock; } - result.hitBlock = getBlock(floorf(result.hitX) + vRound, - floorf(result.hitY)); return result; }