├── src ├── opengl │ ├── shader │ │ ├── coords.frag │ │ ├── skybox.frag │ │ ├── main.vert │ │ ├── coords.vert │ │ ├── main.frag │ │ └── skybox.vert │ ├── opengl │ │ ├── VAO.h │ │ ├── Buffer.h │ │ ├── Texture.h │ │ ├── VAO.cpp │ │ ├── Buffer.cpp │ │ ├── Shader.h │ │ ├── Texture.cpp │ │ ├── Program.h │ │ ├── Shader.cpp │ │ └── Program.cpp │ ├── Renderer.h │ └── Renderer.cpp ├── Timer.h ├── IRenderable.h ├── Entity.h ├── HudRenderable.cpp ├── optimus.cpp ├── HudRenderable.h ├── Hud.h ├── IPSS.h ├── mathlib.cpp ├── global.h ├── Camera.h ├── directx11 │ ├── directx11 │ │ ├── Program.h │ │ ├── Program.cpp │ │ ├── Shader.h │ │ └── Shader.cpp │ ├── shader │ │ ├── coords.hlsl │ │ ├── main.hlsl │ │ └── skybox.hlsl │ └── Renderer.h ├── mathlib.h ├── Camera.cpp ├── Image.h ├── Timer.cpp ├── Entity.cpp ├── IO.h ├── Window.h ├── move.h ├── main.cpp ├── Image.cpp ├── Wad.h ├── GlfwWindow.h ├── BspRenderable.h ├── IRenderer.h ├── Hud.cpp ├── Bsp.h ├── GlfwWindow.cpp ├── bspdef.h ├── Window.cpp ├── Wad.cpp ├── BspRenderable.cpp └── Bsp.cpp ├── README.md ├── appveyor.yml ├── .clang-tidy ├── .clang-format ├── .travis.yml ├── thirdparty ├── imgui_impl_dx11.h ├── imgui_impl_glfw.h ├── imgui_impl_opengl3.h ├── imconfig.h ├── imgui_impl_glfw.cpp └── imstb_rectpack.h ├── LICENSE_1_0.txt └── CMakeLists.txt /src/opengl/shader/coords.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec3 coordColor; 4 | 5 | out vec4 color; 6 | 7 | void main() { 8 | color = vec4(coordColor, 1.0); 9 | } 10 | -------------------------------------------------------------------------------- /src/opengl/shader/skybox.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform samplerCube cubeSampler; 4 | 5 | in vec3 cubeCoord; 6 | 7 | out vec4 color; 8 | 9 | void main() { 10 | color = texture(cubeSampler, cubeCoord); 11 | } 12 | -------------------------------------------------------------------------------- /src/Timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Timer { 4 | public: 5 | Timer(); 6 | void Tick(); 7 | 8 | double TPS; // Ticks per second 9 | double interval; // The time passed since the last call of Tick() 10 | }; 11 | -------------------------------------------------------------------------------- /src/IRenderable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct RenderSettings { 6 | glm::mat4 projection; 7 | float pitch; 8 | float yaw; 9 | glm::mat4 view; 10 | }; 11 | 12 | class IRenderable { 13 | public: 14 | virtual ~IRenderable() = default; 15 | virtual void render(const RenderSettings& settings) = 0; 16 | }; 17 | -------------------------------------------------------------------------------- /src/Entity.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Entity { 7 | public: 8 | explicit Entity(const std::string& propertiesString); 9 | auto findProperty(const std::string& name) const -> const std::string*; 10 | 11 | private: 12 | std::unordered_map properties; 13 | }; 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/bernhardmgruber/hlbsp.svg?branch=master)](https://travis-ci.com/bernhardmgruber/hlbsp) 2 | [![Build status](https://ci.appveyor.com/api/projects/status/github/bernhardmgruber/hlbsp?svg=true)](https://ci.appveyor.com/project/bernhardmgruber/hlbsp) 3 | 4 | (c) Bernhard Manfred Gruber 5 | 6 | Released under the Boost Software License - Version 1.0, see "LICENSE_1_0.txt" for details. -------------------------------------------------------------------------------- /src/HudRenderable.cpp: -------------------------------------------------------------------------------- 1 | #include "HudRenderable.h" 2 | 3 | #include "Hud.h" 4 | #include "IRenderer.h" 5 | #include "global.h" 6 | 7 | HudRenderable::HudRenderable(render::IRenderer& renderer, const Hud& hud) 8 | : m_renderer(renderer), m_hud(hud) { 9 | } 10 | void HudRenderable::render(const RenderSettings& settings) { 11 | if (!global::renderHUD) 12 | return; 13 | m_renderer.renderImgui(m_hud.drawData()); 14 | } 15 | -------------------------------------------------------------------------------- /src/optimus.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Physically Based Rendering 3 | * Copyright (c) 2017-2018 Michał Siejak 4 | */ 5 | 6 | #if _WIN32 7 | #define EXPORT_SYMBOL __declspec(dllexport) 8 | #else 9 | #define EXPORT_SYMBOL 10 | #endif 11 | 12 | // Enable usage of more performant GPUs on laptops. 13 | extern "C" { 14 | EXPORT_SYMBOL int NvOptimusEnablement = 1; 15 | EXPORT_SYMBOL int AmdPowerXpressRequestHighPerformance = 1; 16 | } 17 | -------------------------------------------------------------------------------- /src/HudRenderable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "IRenderable.h" 4 | 5 | namespace render { 6 | class IRenderer; 7 | } 8 | class Hud; 9 | 10 | class HudRenderable : public IRenderable { 11 | public: 12 | HudRenderable(render::IRenderer& renderer, const Hud& hud); 13 | 14 | virtual void render(const RenderSettings& settings) override; 15 | 16 | private: 17 | render::IRenderer& m_renderer; 18 | const Hud& m_hud; 19 | }; -------------------------------------------------------------------------------- /src/opengl/opengl/VAO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace gl { 6 | class VAO { 7 | public: 8 | VAO(); 9 | VAO(const VAO&) = delete; 10 | auto operator=(const VAO&) -> VAO& = delete; 11 | VAO(VAO&& other); 12 | auto operator=(VAO&& other) -> VAO&; 13 | ~VAO(); 14 | 15 | auto id() const -> GLuint; 16 | void bind(); 17 | 18 | private: 19 | void swap(VAO& other); 20 | 21 | GLuint m_id = 0; 22 | }; 23 | } -------------------------------------------------------------------------------- /src/opengl/opengl/Buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace gl { 6 | class Buffer { 7 | public: 8 | Buffer(); 9 | Buffer(const Buffer&) = delete; 10 | auto operator=(const Buffer&) -> Buffer& = delete; 11 | Buffer(Buffer&& other); 12 | auto operator=(Buffer&& other) -> Buffer&; 13 | ~Buffer(); 14 | 15 | auto id() const -> GLuint; 16 | void bind(GLenum target); 17 | 18 | private: 19 | void swap(Buffer& other); 20 | 21 | GLuint m_id = 0; 22 | }; 23 | } -------------------------------------------------------------------------------- /src/opengl/opengl/Texture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace gl { 6 | class Texture { 7 | public: 8 | Texture(); 9 | Texture(const Texture&) = delete; 10 | auto operator=(const Texture&) -> Texture& = delete; 11 | Texture(Texture&& other); 12 | auto operator=(Texture&& other) -> Texture&; 13 | ~Texture(); 14 | 15 | auto id() const -> GLuint; 16 | void bind(GLenum target); 17 | 18 | private: 19 | void swap(Texture& other); 20 | 21 | GLuint m_id = 0; 22 | }; 23 | } -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2017 2 | cache: C:\tools\vcpkg\installed\ 3 | environment: 4 | VCPKG_DEFAULT_TRIPLET: x64-windows 5 | install: 6 | - ps: | 7 | pushd C:\tools\vcpkg 8 | git pull 9 | popd 10 | vcpkg update 11 | vcpkg install boost stb glfw3 glew glm freetype 12 | build_script: 13 | - ps: | 14 | mkdir build 15 | pushd build 16 | cmake -DCMAKE_TOOLCHAIN_FILE=C:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -G "Visual Studio 15 2017 Win64" .. 17 | cmake --build . --config "Release" 18 | -------------------------------------------------------------------------------- /src/opengl/shader/main.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform bool unit1Enabled; 4 | uniform bool unit2Enabled; 5 | 6 | uniform mat4 matrix; 7 | 8 | layout(location = 0) in vec3 inPosition; 9 | layout(location = 1) in vec3 inNormal; 10 | layout(location = 2) in vec2 inTexCoord; 11 | layout(location = 3) in vec2 inLightmapCoord; 12 | 13 | out vec2 texCoord; 14 | out vec2 lightmapCoord; 15 | 16 | void main() { 17 | gl_Position = matrix * vec4(inPosition, 1.0); 18 | texCoord = inTexCoord; 19 | lightmapCoord = inLightmapCoord; 20 | } 21 | -------------------------------------------------------------------------------- /src/Hud.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "mathlib.h" 7 | 8 | struct ImDrawData; 9 | class Camera; 10 | class Timer; 11 | 12 | class Hud { 13 | public: 14 | Hud(const Camera& camera, const Timer& timer); 15 | 16 | void print(std::string text); 17 | 18 | auto drawData() const -> ImDrawData*; 19 | auto fontHeight() const -> int; 20 | auto fontColor() const -> glm::vec3; 21 | 22 | private: 23 | const Camera& m_camera; 24 | const Timer& m_timer; 25 | 26 | std::vector m_console; 27 | }; 28 | -------------------------------------------------------------------------------- /src/IPSS.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class IPSS { 7 | public: 8 | IPSS() = default; 9 | IPSS(const IPSS&) = delete; 10 | IPSS(IPSS&&) noexcept = default; 11 | 12 | auto str() const -> std::string { 13 | return oss.str(); 14 | } 15 | 16 | operator std::string() const { 17 | return oss.str(); 18 | } 19 | 20 | template 21 | friend auto operator<<(IPSS&& ipss, T&& t) -> IPSS&& { 22 | ipss.oss << std::forward(t); 23 | return std::move(ipss); 24 | } 25 | 26 | private: 27 | std::ostringstream oss; 28 | }; 29 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: > 3 | *, 4 | -llvm-include-order, 5 | -llvm-header-guard, 6 | -google-readability-namespace-comments, 7 | -llvm-namespace-comment, 8 | -google-runtime-references, 9 | -google-readability-braces-around-statements, 10 | -hicpp-braces-around-statements, 11 | -readability-braces-around-statements, 12 | -fuchsia-default-arguments, 13 | -readability-implicit-bool-conversion, 14 | -fuchsia-trailing-return 15 | 16 | WarningsAsErrors: '' 17 | HeaderFilterRegex: '' 18 | AnalyzeTemporaryDtors: false 19 | FormatStyle: none 20 | User: bernh 21 | CheckOptions: 22 | ... 23 | -------------------------------------------------------------------------------- /src/opengl/opengl/VAO.cpp: -------------------------------------------------------------------------------- 1 | #include "VAO.h" 2 | 3 | #include 4 | 5 | namespace gl { 6 | VAO::VAO() { 7 | glGenVertexArrays(1, &m_id); 8 | } 9 | 10 | VAO::VAO(VAO&& other) { 11 | swap(other); 12 | } 13 | 14 | auto VAO::operator=(VAO&& other) -> VAO& { 15 | swap(other); 16 | return *this; 17 | } 18 | 19 | VAO::~VAO() { 20 | glDeleteVertexArrays(1, &m_id); 21 | } 22 | 23 | auto VAO::id() const -> GLuint { 24 | return m_id; 25 | } 26 | 27 | void VAO::bind() { 28 | glBindVertexArray(m_id); 29 | } 30 | 31 | void VAO::swap(VAO& other) { 32 | using std::swap; 33 | swap(m_id, other.m_id); 34 | } 35 | } -------------------------------------------------------------------------------- /src/mathlib.cpp: -------------------------------------------------------------------------------- 1 | #include "mathlib.h" 2 | 3 | namespace { 4 | constexpr auto EPSILON = 1 / 32.0f; 5 | } 6 | 7 | auto PointInBox(glm::vec3 point, const int16_t min[3], const int16_t max[3]) -> bool { 8 | return (min[0] <= point.x && point.x <= max[0] && min[1] <= point.y && point.y <= max[1] && min[2] <= point.z && point.z <= max[2]) || 9 | (min[0] >= point.x && point.x >= max[0] && min[1] >= point.y && point.y >= max[1] && min[2] >= point.z && point.z >= max[2]); 10 | } 11 | 12 | auto PointInPlane(glm::vec3 point, glm::vec3 normal, float dist) -> bool { 13 | return std::abs(glm::dot(point, normal) - dist) < EPSILON; 14 | } 15 | -------------------------------------------------------------------------------- /src/opengl/opengl/Buffer.cpp: -------------------------------------------------------------------------------- 1 | #include "Buffer.h" 2 | 3 | #include 4 | 5 | namespace gl { 6 | Buffer::Buffer() { 7 | glGenBuffers(1, &m_id); 8 | } 9 | 10 | Buffer::Buffer(Buffer&& other) { 11 | swap(other); 12 | } 13 | 14 | auto Buffer::operator=(Buffer&& other) -> Buffer& { 15 | swap(other); 16 | return *this; 17 | } 18 | 19 | Buffer::~Buffer() { 20 | glDeleteBuffers(1, &m_id); 21 | } 22 | 23 | auto Buffer::id() const -> GLuint { 24 | return m_id; 25 | } 26 | 27 | void Buffer::bind(GLenum target) { 28 | glBindBuffer(target, m_id); 29 | } 30 | 31 | void Buffer::swap(Buffer& other) { 32 | using std::swap; 33 | swap(m_id, other.m_id); 34 | } 35 | } -------------------------------------------------------------------------------- /src/opengl/opengl/Shader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace fs = std::filesystem; 8 | 9 | namespace gl { 10 | class Shader { 11 | public: 12 | Shader() = default; 13 | Shader(GLenum shaderType, const std::string& source, const std::string& filename = ""); 14 | Shader(GLenum shaderType, const fs::path& file); 15 | Shader(const Shader&) = delete; 16 | auto operator=(const Shader&) -> Shader& = delete; 17 | Shader(Shader&& other); 18 | auto operator=(Shader&& other) -> Shader&; 19 | ~Shader(); 20 | 21 | auto id() const -> GLuint; 22 | 23 | private: 24 | void swap(Shader& other); 25 | 26 | GLuint m_id = 0; 27 | }; 28 | } -------------------------------------------------------------------------------- /src/opengl/opengl/Texture.cpp: -------------------------------------------------------------------------------- 1 | #include "Texture.h" 2 | 3 | #include 4 | 5 | namespace gl { 6 | Texture::Texture() { 7 | glGenTextures(1, &m_id); 8 | } 9 | 10 | Texture::Texture(Texture&& other) { 11 | swap(other); 12 | } 13 | 14 | auto Texture::operator=(Texture&& other) -> Texture& { 15 | swap(other); 16 | return *this; 17 | } 18 | 19 | Texture::~Texture() { 20 | glDeleteTextures(1, &m_id); 21 | } 22 | 23 | auto Texture::id() const -> GLuint { 24 | return m_id; 25 | } 26 | 27 | void Texture::bind(GLenum target) { 28 | glBindTexture(target, m_id); 29 | } 30 | 31 | void Texture::swap(Texture& other) { 32 | using std::swap; 33 | swap(m_id, other.m_id); 34 | } 35 | } -------------------------------------------------------------------------------- /src/global.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum class RenderAPI : int { 4 | OpenGL, 5 | Direct3D 6 | }; 7 | 8 | namespace global { 9 | inline auto renderApi = RenderAPI::OpenGL; 10 | 11 | inline bool textures = true; 12 | inline bool lightmaps = true; 13 | inline bool polygons = false; 14 | 15 | inline bool renderStaticBSP = true; 16 | inline bool renderBrushEntities = true; 17 | inline bool renderSkybox = true; 18 | inline bool renderDecals = true; 19 | inline bool renderCoords = false; 20 | inline bool renderLeafOutlines = false; 21 | inline bool renderHUD = true; 22 | 23 | inline bool nightvision = false; 24 | inline bool flashlight = false; 25 | 26 | inline int moveType = 0; 27 | inline int hullIndex = 0; 28 | } 29 | -------------------------------------------------------------------------------- /src/Camera.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mathlib.h" 4 | #include "move.h" 5 | 6 | class Camera { 7 | public: 8 | Camera(PlayerMove& pmove); 9 | 10 | int viewportWidth = 0; 11 | int viewportHeight = 0; 12 | float fovy = 60; 13 | 14 | auto position() const { return pmove.origin; } 15 | auto& position() { return pmove.origin; } 16 | auto pitch() const { return pmove.angles.x; } 17 | auto& pitch() { return pmove.angles.x; } 18 | auto yaw() const { return pmove.angles.y; } 19 | auto& yaw() { return pmove.angles.y; } 20 | 21 | auto viewVector() const -> glm::vec3; 22 | auto viewMatrix() const -> glm::mat4; 23 | auto projectionMatrix() const -> glm::mat4; 24 | 25 | private: 26 | PlayerMove& pmove; 27 | }; 28 | -------------------------------------------------------------------------------- /src/directx11/directx11/Program.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Shader.h" 8 | 9 | namespace dx11 { 10 | class Program { 11 | public: 12 | Program() = default; 13 | Program(VertexShader vs, PixelShader ps); 14 | Program(const Program&) = delete; 15 | auto operator=(const Program&) -> Program& = delete; 16 | Program(Program&& other); 17 | auto operator=(Program&& other) -> Program&; 18 | ~Program(); 19 | 20 | auto vertexShader() -> VertexShader&; 21 | auto vertexShader() const -> const VertexShader&; 22 | auto pixelShader() -> PixelShader&; 23 | auto pixelShader() const -> const PixelShader&; 24 | 25 | private: 26 | void swap(Program& other); 27 | 28 | VertexShader m_vertexShader; 29 | PixelShader m_pixelShader; 30 | }; 31 | } -------------------------------------------------------------------------------- /src/mathlib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | template 17 | auto degToRad(T deg) -> T { 18 | return deg * boost::math::constants::pi() / T{180}; 19 | } 20 | 21 | template 22 | auto radToDeg(T rad) -> T { 23 | return rad * T{180} / boost::math::constants::pi(); 24 | } 25 | 26 | auto PointInBox(glm::vec3 point, const int16_t vMin[3], const int16_t vMax[3]) -> bool; 27 | auto PointInPlane(glm::vec3 point, glm::vec3 normal, float dist) -> bool; 28 | -------------------------------------------------------------------------------- /src/Camera.cpp: -------------------------------------------------------------------------------- 1 | #include "Camera.h" 2 | 3 | #include "global.h" 4 | #include "mathlib.h" 5 | 6 | Camera::Camera(PlayerMove& pmove) : pmove(pmove) {} 7 | 8 | auto Camera::viewVector() const -> glm::vec3 { 9 | return pmove.forward; 10 | } 11 | 12 | auto Camera::viewMatrix() const -> glm::mat4 { 13 | auto& pitch = pmove.angles.x; 14 | auto& yaw = pmove.angles.y; 15 | auto& position = pmove.origin; 16 | 17 | // in BSP v30 the z axis points up and we start looking parallel to x axis 18 | glm::mat4 mat = glm::eulerAngleXZ(degToRad(pitch - 90.0f), degToRad(-yaw + 90.0f)); 19 | mat = glm::translate(mat, -position); // move 20 | return mat; 21 | } 22 | 23 | auto Camera::projectionMatrix() const -> glm::mat4 { 24 | return glm::perspective(degToRad(fovy), static_cast(viewportWidth) / static_cast(viewportHeight), 1.0f, 4000.0f); 25 | } 26 | -------------------------------------------------------------------------------- /src/Image.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace fs = std::filesystem; 8 | 9 | class Image { 10 | public: 11 | Image() = default; 12 | Image(unsigned int width, unsigned int height, unsigned int channels); 13 | explicit Image(const fs::path& path); 14 | Image(const Image& img, unsigned int channels); 15 | Image(const Image&) = default; 16 | auto operator=(const Image&) -> Image& = default; 17 | Image(Image&&) = default; 18 | auto operator=(Image&&) -> Image& = default; 19 | 20 | auto operator()(unsigned int x, unsigned int y) -> std::uint8_t*; 21 | auto operator()(unsigned int x, unsigned int y) const -> const std::uint8_t*; 22 | 23 | void Save(const fs::path& path) const; 24 | 25 | std::vector data; 26 | unsigned int channels = 0; 27 | unsigned int width = 0; 28 | unsigned int height = 0; 29 | }; 30 | -------------------------------------------------------------------------------- /src/opengl/shader/coords.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform mat4 matrix; 4 | 5 | const vec3 points[12] = vec3[]( 6 | vec3( 0.0, 0.0, 0.0), 7 | vec3( 1.0, 0.0, 0.0), 8 | vec3( 0.0, 0.0, 0.0), 9 | vec3(-1.0, 0.0, 0.0), 10 | vec3( 0.0, 0.0, 0.0), 11 | vec3( 0.0, 1.0, 0.0), 12 | vec3( 0.0, 0.0, 0.0), 13 | vec3( 0.0, -1.0, 0.0), 14 | vec3( 0.0, 0.0, 0.0), 15 | vec3( 0.0, 0.0, 1.0), 16 | vec3( 0.0, 0.0, 0.0), 17 | vec3( 0.0, 0.0, -1.0) 18 | ); 19 | 20 | const vec3 colors[6] = vec3[]( 21 | vec3(1.0, 0.0, 0.0), 22 | vec3(0.5, 0.0, 0.0), 23 | vec3(0.0, 1.0, 0.0), 24 | vec3(0.0, 0.5, 0.0), 25 | vec3(0.0, 0.0, 1.0), 26 | vec3(0.0, 0.0, 0.5) 27 | ); 28 | 29 | out vec3 coordColor; 30 | 31 | void main() { 32 | const float axesLength = 4000; 33 | 34 | gl_Position = matrix * vec4(points[gl_VertexID] * axesLength, 1.0); 35 | coordColor = colors[gl_VertexID / 2]; 36 | } 37 | -------------------------------------------------------------------------------- /src/Timer.cpp: -------------------------------------------------------------------------------- 1 | #include "Timer.h" 2 | 3 | #include 4 | 5 | namespace { 6 | constexpr auto TPS_UPDATE_INTERVAL = 0.2; 7 | } 8 | 9 | Timer::Timer() { 10 | Tick(); 11 | } 12 | 13 | void Timer::Tick() { 14 | using time_point = decltype(std::chrono::high_resolution_clock::now()); 15 | static time_point lastTime; 16 | 17 | auto now = std::chrono::high_resolution_clock::now(); 18 | interval = std::chrono::duration(now - lastTime).count(); 19 | 20 | lastTime = now; 21 | 22 | static std::size_t nFrameCounter = 0; 23 | static time_point dLastTimeFPSUpdate = {}; 24 | 25 | nFrameCounter++; 26 | 27 | const auto timeSinceLastFPSUpdate = std::chrono::duration(now - dLastTimeFPSUpdate).count(); 28 | if (timeSinceLastFPSUpdate > TPS_UPDATE_INTERVAL) { 29 | TPS = nFrameCounter / timeSinceLastFPSUpdate; 30 | dLastTimeFPSUpdate = now; 31 | nFrameCounter = 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # see https://clang.llvm.org/docs/ClangFormatStyleOptions.html 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: DontAlign 5 | AllowShortBlocksOnASingleLine: true 6 | AllowShortCaseLabelsOnASingleLine: true 7 | AllowShortIfStatementsOnASingleLine: true 8 | AllowShortLoopsOnASingleLine: true 9 | AlwaysBreakBeforeMultilineStrings: true 10 | AlwaysBreakTemplateDeclarations: true 11 | BraceWrapping: 12 | SplitEmptyFunction: false 13 | SplitEmptyRecord: false 14 | SplitEmptyNamespace: false 15 | BreakBeforeBraces: Custom 16 | BreakStringLiterals: false 17 | ColumnLimit: 0 18 | FixNamespaceComments: false 19 | IndentCaseLabels: true 20 | IndentWidth: 4 21 | KeepEmptyLinesAtTheStartOfBlocks: false 22 | MaxEmptyLinesToKeep: 2 23 | NamespaceIndentation: All 24 | PointerAlignment: Left 25 | SpaceAfterTemplateKeyword: false 26 | TabWidth: 4 27 | UseTab: ForContinuationAndIndentation -------------------------------------------------------------------------------- /src/Entity.cpp: -------------------------------------------------------------------------------- 1 | #include "Entity.h" 2 | 3 | Entity::Entity(const std::string& propertiesString) { 4 | std::size_t pos = 0; 5 | while (true) { 6 | auto readQuotedString = [&] { 7 | pos++; 8 | auto end = propertiesString.find('"', pos); 9 | auto s = propertiesString.substr(pos, end - pos); 10 | pos = end + 1; 11 | return s; 12 | }; 13 | 14 | pos = propertiesString.find('"', pos); 15 | if (pos == std::string::npos) 16 | break; 17 | 18 | auto name = readQuotedString(); 19 | pos = propertiesString.find('"', pos); 20 | auto value = readQuotedString(); 21 | properties.emplace(std::move(name), std::move(value)); 22 | } 23 | } 24 | 25 | // TODO This will need to be modified later when there are more than one of the same type 26 | auto Entity::findProperty(const std::string& name) const -> const std::string* { 27 | const auto it = properties.find(name); 28 | if (it != end(properties)) 29 | return &it->second; 30 | return nullptr; 31 | } 32 | -------------------------------------------------------------------------------- /src/opengl/opengl/Program.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Shader.h" 8 | 9 | namespace gl { 10 | class Program { 11 | public: 12 | Program() = default; 13 | Program(std::initializer_list shaders); 14 | Program(const Program&) = delete; 15 | auto operator=(const Program&) -> Program& = delete; 16 | Program(Program&& other); 17 | auto operator=(Program&& other) -> Program&; 18 | ~Program(); 19 | 20 | auto id() const -> GLuint; 21 | void use() const; 22 | 23 | auto attributeLocation(const std::string& name) const -> GLint; 24 | auto uniformLocation(const std::string& name) const -> GLint; 25 | auto uniformBlockIndex(const std::string& name) const -> GLint; 26 | 27 | private: 28 | void swap(Program& other); 29 | 30 | GLuint m_id = 0; 31 | std::unordered_map m_attributes; 32 | std::unordered_map m_uniforms; 33 | std::unordered_map m_uniformBlocks; 34 | }; 35 | } -------------------------------------------------------------------------------- /src/directx11/directx11/Program.cpp: -------------------------------------------------------------------------------- 1 | #include "Program.h" 2 | 3 | #include 4 | 5 | #include "../../IO.h" 6 | 7 | namespace dx11 { 8 | Program::Program(VertexShader vs, PixelShader ps) 9 | : m_vertexShader(std::move(vs)), m_pixelShader(std::move(ps)) {} 10 | 11 | Program::Program(Program&& other) 12 | : m_vertexShader(std::move(other.m_vertexShader)), m_pixelShader(std::move(other.m_pixelShader)) {} 13 | 14 | Program& Program::operator=(Program&& other) { 15 | swap(other); 16 | return *this; 17 | } 18 | 19 | Program::~Program() = default; 20 | 21 | auto Program::vertexShader() -> VertexShader& { 22 | return m_vertexShader; 23 | } 24 | 25 | auto Program::vertexShader() const -> const VertexShader& { 26 | return m_vertexShader; 27 | } 28 | 29 | auto Program::pixelShader() -> PixelShader& { 30 | return m_pixelShader; 31 | } 32 | 33 | auto Program::pixelShader() const -> const PixelShader& { 34 | return m_pixelShader; 35 | } 36 | 37 | void Program::swap(Program& other) { 38 | using std::swap; 39 | swap(m_vertexShader, other.m_vertexShader); 40 | swap(m_pixelShader, other.m_pixelShader); 41 | } 42 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: bionic 2 | language: cpp 3 | cache: ccache 4 | 5 | addons: 6 | apt: 7 | sources: 8 | - sourceline: "ppa:ubuntu-toolchain-r/test" 9 | - sourceline: "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-9 main" 10 | key_url: "https://apt.llvm.org/llvm-snapshot.gpg.key" 11 | packages: 12 | - clang-9 13 | - clang-tidy-9 14 | - clang-format-9 15 | - g++-9 16 | - libboost-all-dev 17 | - libglfw3-dev 18 | - libglew-dev 19 | - libglm-dev 20 | - libfreetype6-dev 21 | - xorg-dev # needed by glfw3 22 | - libglu1-mesa-dev # needed by glfw3 23 | 24 | compiler: 25 | - gcc 26 | - clang 27 | 28 | install: 29 | - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 100 30 | - sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-9 100 31 | - sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-9 100 32 | - sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-9 100 33 | 34 | script: 35 | - mkdir build 36 | - cd build 37 | - cmake .. 38 | - make -j4 39 | -------------------------------------------------------------------------------- /thirdparty/imgui_impl_dx11.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Renderer for DirectX11 2 | // This needs to be used along with a Platform Binding (e.g. Win32) 3 | 4 | // Implemented features: 5 | // [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. 6 | 7 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. 8 | // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. 9 | // https://github.com/ocornut/imgui 10 | 11 | #pragma once 12 | 13 | struct ID3D11Device; 14 | struct ID3D11DeviceContext; 15 | 16 | IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context); 17 | IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown(); 18 | IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame(); 19 | IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data); 20 | 21 | // Use if you want to reset your rendering device without losing ImGui state. 22 | IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects(); 23 | IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects(); 24 | -------------------------------------------------------------------------------- /src/directx11/shader/coords.hlsl: -------------------------------------------------------------------------------- 1 | 2 | cbuffer TransformConstants : register(b0) { 3 | float4x4 mv; 4 | }; 5 | 6 | static const float3 points[12] = { 7 | float3( 0.0, 0.0, 0.0), 8 | float3( 1.0, 0.0, 0.0), 9 | float3( 0.0, 0.0, 0.0), 10 | float3(-1.0, 0.0, 0.0), 11 | float3( 0.0, 0.0, 0.0), 12 | float3( 0.0, 1.0, 0.0), 13 | float3( 0.0, 0.0, 0.0), 14 | float3( 0.0, -1.0, 0.0), 15 | float3( 0.0, 0.0, 0.0), 16 | float3( 0.0, 0.0, 1.0), 17 | float3( 0.0, 0.0, 0.0), 18 | float3( 0.0, 0.0, -1.0) 19 | }; 20 | 21 | static const float3 colors[6] = { 22 | float3(1.0, 0.0, 0.0), 23 | float3(0.5, 0.0, 0.0), 24 | float3(0.0, 1.0, 0.0), 25 | float3(0.0, 0.5, 0.0), 26 | float3(0.0, 0.0, 1.0), 27 | float3(0.0, 0.0, 0.5) 28 | }; 29 | 30 | struct PixelShaderInput { 31 | float4 pixelPosition : SV_POSITION; 32 | float3 coordColor : COLOR; 33 | }; 34 | 35 | PixelShaderInput main_vs(uint vertexID : SV_VertexID) { 36 | const float axesLength = 4000; 37 | 38 | PixelShaderInput r; 39 | r.pixelPosition = mul(mv, float4(points[vertexID] * axesLength, 1.0f)); 40 | r.coordColor = colors[vertexID / 2]; 41 | return r; 42 | } 43 | 44 | float4 main_ps(PixelShaderInput pin) : SV_Target { 45 | return float4(pin.coordColor, 1.0); 46 | } 47 | -------------------------------------------------------------------------------- /src/IO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace fs = std::filesystem; 10 | 11 | template 12 | void read(std::istream& is, T& t) { 13 | is.read(reinterpret_cast(&t), sizeof(T)); 14 | } 15 | 16 | template 17 | auto read(std::istream& is) { 18 | T t; 19 | is.read(reinterpret_cast(&t), sizeof(T)); 20 | return t; 21 | } 22 | 23 | template 24 | void readVector(std::istream& is, std::vector& v) { 25 | is.read(reinterpret_cast(v.data()), sizeof(T) * v.size()); 26 | } 27 | 28 | template 29 | auto readVector(std::istream& is, std::size_t count) { 30 | std::vector v(count); 31 | is.read(reinterpret_cast(v.data()), sizeof(T) * v.size()); 32 | return v; 33 | } 34 | 35 | inline auto readTextFile(const fs::path& filename) { 36 | std::string content; 37 | std::ifstream file(filename); 38 | if (!file) 39 | throw std::ios::failure("Failed to open file " + filename.string() + " for reading"); 40 | file.seekg(std::ios::end); 41 | content.reserve(file.tellg()); 42 | file.seekg(std::ios::beg); 43 | content.assign(std::istreambuf_iterator{file}, std::istreambuf_iterator{}); 44 | return content; 45 | } 46 | -------------------------------------------------------------------------------- /src/Window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "GlfwWindow.h" 4 | #include "Camera.h" 5 | #include "Hud.h" 6 | #include "Timer.h" 7 | #include "IRenderable.h" 8 | #include "move.h" 9 | 10 | namespace render { 11 | class IRenderer; 12 | } 13 | 14 | class Bsp; 15 | 16 | class Window : public GlfwWindow { 17 | public: 18 | Window(render::IPlatform& platform, Bsp& bsp); 19 | ~Window(); 20 | 21 | void update(); 22 | void draw(); 23 | 24 | private: 25 | virtual void onResize(int width, int height) override; 26 | virtual void onMouseButton(int button, int action, int modifiers) override; 27 | virtual void onMouseMove(double dx, double dy) override; 28 | virtual void onMouseWheel(double xOffset, double yOffset) override; 29 | virtual void onKey(int key, int scancode, int action, int mods) override; 30 | virtual void onChar(unsigned int codepoint) override; 31 | 32 | auto createMove() -> UserCommand; 33 | void mouseMove(UserCommand& cmd); 34 | 35 | Timer timer; 36 | PlayerMove pmove{}; 37 | Camera camera; 38 | Hud hud; 39 | Bsp& bsp; 40 | 41 | RenderSettings m_settings; 42 | std::vector> m_renderables; 43 | render::IPlatform& m_platform; 44 | std::unique_ptr m_renderer; 45 | 46 | bool m_captureMouse = false; 47 | glm::dvec2 m_mouseDownPos; 48 | }; 49 | -------------------------------------------------------------------------------- /src/opengl/shader/main.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform bool unit1Enabled; 4 | uniform bool unit2Enabled; 5 | 6 | uniform bool nightvision; 7 | uniform bool alphaTest; 8 | 9 | uniform sampler2D tex1; 10 | uniform sampler2D tex2; 11 | 12 | in vec2 texCoord; 13 | in vec2 lightmapCoord; 14 | 15 | out vec4 color; 16 | 17 | void Nightvision() { 18 | vec4 c1 = color / 2.0; 19 | c1 += texture2D(tex1, texCoord.st + 0.01); 20 | c1 += texture2D(tex1, texCoord.st + 0.02); 21 | c1 += texture2D(tex1, texCoord.st + 0.03); 22 | 23 | vec4 c2 = color / 2.0; 24 | c2 += texture2D(tex2, lightmapCoord.st + 0.01); 25 | c2 += texture2D(tex2, lightmapCoord.st + 0.02); 26 | c2 += texture2D(tex2, lightmapCoord.st + 0.03); 27 | 28 | vec4 c = c1 * c2; 29 | c.r *= 0.2; 30 | c.b *= 0.2; 31 | 32 | color = c; 33 | } 34 | 35 | void main() { 36 | vec4 texel1 = vec4(1.0); 37 | vec4 texel2 = vec4(1.0); 38 | 39 | if (unit1Enabled) { 40 | texel1 = texture2D(tex1, texCoord); 41 | if (alphaTest && texel1.a < 0.25) 42 | discard; 43 | } 44 | 45 | if (unit2Enabled) 46 | texel2 = texture2D(tex2, lightmapCoord); 47 | 48 | color = vec4(texel1.rgb * texel2.rgb, texel1.a); 49 | 50 | if (nightvision) 51 | Nightvision(); 52 | 53 | // brightness 54 | color *= 2; 55 | } 56 | -------------------------------------------------------------------------------- /LICENSE_1_0.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/move.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "Bsp.h" 6 | 7 | constexpr auto IN_JUMP = 1 << 1; 8 | constexpr auto IN_FORWARD = 1 << 3; 9 | constexpr auto IN_BACK = 1 << 4; 10 | constexpr auto IN_MOVELEFT = 1 << 9; 11 | constexpr auto IN_MOVERIGHT = 1 << 10; 12 | 13 | constexpr auto FL_DUCKING = (1 << 14); // Player flag -- Player is fully crouched 14 | 15 | struct UserCommand { 16 | float forwardmove; 17 | float sidemove; 18 | float upmove; 19 | int buttons; 20 | float frameTime; 21 | glm::vec3 viewangles; // [pitch, yaw, roll] 22 | }; 23 | 24 | enum class MoveType { 25 | walk, 26 | fly, 27 | noclip 28 | }; 29 | 30 | struct PlayerMove { 31 | glm::vec3 angles; // [pitch, yaw, roll] 32 | 33 | glm::vec3 forward; 34 | glm::vec3 right; 35 | glm::vec3 up; 36 | 37 | glm::vec3 origin; 38 | glm::vec3 velocity; 39 | 40 | glm::vec3 view_ofs; 41 | 42 | float frametime; 43 | int onground; 44 | int waterlevel; 45 | float friction; 46 | float waterjumptime; 47 | bool dead; 48 | UserCommand cmd; 49 | int oldbuttons; 50 | MoveType movetype; 51 | float gravity; 52 | int flags; // FL_ONGROUND, FL_DUCKING, etc. 53 | 54 | int usehull; // 0 = regular player hull, 1 = ducked player hull, 2 = point hull 55 | 56 | std::vector physents; // entities to clip against 57 | std::vector ladders; // called moveents in pm_shared.c 58 | }; 59 | 60 | void playerMove(PlayerMove& pmove); 61 | -------------------------------------------------------------------------------- /src/opengl/opengl/Shader.cpp: -------------------------------------------------------------------------------- 1 | #include "Shader.h" 2 | 3 | #include 4 | 5 | #include "../../IO.h" 6 | 7 | namespace gl { 8 | Shader::Shader(GLenum shaderType, const std::string& source, const std::string& filename) { 9 | m_id = glCreateShader(shaderType); 10 | const char* p = source.c_str(); 11 | glShaderSource(m_id, 1, &p, nullptr); 12 | glCompileShader(m_id); 13 | 14 | GLint status; 15 | glGetShaderiv(m_id, GL_COMPILE_STATUS, &status); 16 | GLint length; 17 | glGetShaderiv(m_id, GL_INFO_LOG_LENGTH, &length); 18 | std::string buildLog; 19 | buildLog.resize(length); 20 | glGetShaderInfoLog(m_id, length, nullptr, buildLog.data()); 21 | 22 | if (status != GL_TRUE) 23 | throw std::runtime_error("Failed to compile shader " + filename + ":\n" + buildLog); 24 | else 25 | std::clog << "Shader compile log:\n" 26 | << buildLog << "\n"; 27 | } 28 | 29 | Shader::Shader(GLenum shaderType, const fs::path& file) 30 | : Shader(shaderType, readTextFile(file), file.string()) {} 31 | 32 | Shader::Shader(Shader&& other) { 33 | swap(other); 34 | } 35 | 36 | auto Shader::operator=(Shader&& other) -> Shader& { 37 | swap(other); 38 | return *this; 39 | } 40 | 41 | Shader::~Shader() { 42 | if (m_id != 0) 43 | glDeleteShader(m_id); 44 | } 45 | 46 | auto Shader::id() const -> GLuint { 47 | return m_id; 48 | } 49 | 50 | void Shader::swap(Shader& other) { 51 | using std::swap; 52 | swap(m_id, other.m_id); 53 | } 54 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef _WIN32 4 | #include "directx11/Renderer.h" 5 | #endif 6 | #include "Bsp.h" 7 | #include "Window.h" 8 | #include "global.h" 9 | #include "opengl/Renderer.h" 10 | 11 | bool runWithPlatformAPI(const RenderAPI api, Bsp& bsp) { 12 | auto platform = [&] { 13 | switch (api) { 14 | case RenderAPI::OpenGL: return std::unique_ptr{new render::opengl::Platform}; 15 | case RenderAPI::Direct3D: return std::unique_ptr{new render::directx11::Platform}; 16 | } 17 | std::abort(); 18 | }(); 19 | 20 | Window window(*platform, bsp); 21 | 22 | while (true) { 23 | glfwPollEvents(); 24 | if (glfwGetKey(window.handle(), GLFW_KEY_ESCAPE) == GLFW_PRESS) 25 | break; 26 | if (window.shouldClose()) 27 | break; 28 | 29 | window.update(); 30 | window.draw(); 31 | platform->swapBuffers(); 32 | 33 | if (global::renderApi != api) 34 | return true; 35 | } 36 | 37 | return false; 38 | } 39 | 40 | auto main(const int argc, const char* argv[]) -> int try { 41 | if (argc != 2) 42 | throw std::runtime_error("Missing map name as command line argument"); 43 | 44 | Bsp bsp(argv[1]); 45 | 46 | while (runWithPlatformAPI(global::renderApi, bsp)) 47 | ; 48 | 49 | return 0; 50 | } catch (const std::exception& e) { 51 | std::cerr << "Exception: " << e.what() << "\n"; 52 | return 1; 53 | } catch (...) { 54 | std::cerr << "Unknown exception\n"; 55 | return 1; 56 | } -------------------------------------------------------------------------------- /src/Image.cpp: -------------------------------------------------------------------------------- 1 | #include "Image.h" 2 | 3 | #define STB_IMAGE_IMPLEMENTATION 4 | #include "stb_image.h" 5 | #define STB_IMAGE_WRITE_IMPLEMENTATION 6 | #include "stb_image_write.h" 7 | 8 | Image::Image(unsigned int width, unsigned int height, unsigned int channels) 9 | : width(width), height(height), channels(channels), data(width * height * channels) {} 10 | 11 | Image::Image(const Image& img, unsigned int channels) 12 | : Image(img.width, img.height, channels) { 13 | const auto count = std::min(img.channels, channels); 14 | for (auto y = 0; y < height; y++) { 15 | for (auto x = 0; x < width; x++) { 16 | auto src = img(x, y); 17 | auto dst = (*this)(x, y); 18 | std::copy(src, src + count, dst); 19 | } 20 | } 21 | } 22 | 23 | Image::Image(const fs::path& path) { 24 | int x, y, n; 25 | auto d = stbi_load(path.string().c_str(), &x, &y, &n, 0); 26 | if (!d) 27 | throw std::ios::failure("Failed to load image file: " + path.string()); 28 | 29 | width = x; 30 | height = y; 31 | channels = n; 32 | data.assign(d, d + x * y * n); 33 | 34 | stbi_image_free(d); 35 | } 36 | 37 | auto Image::operator()(unsigned int x, unsigned int y) -> std::uint8_t* { 38 | return &data[(y * width + x) * channels]; 39 | } 40 | 41 | auto Image::operator()(unsigned int x, unsigned int y) const -> const std::uint8_t* { 42 | return &data[(y * width + x) * channels]; 43 | } 44 | 45 | void Image::Save(const fs::path& path) const { 46 | if (!path.has_extension()) 47 | throw std::runtime_error("no extension"); 48 | 49 | stbi_write_bmp(path.string().c_str(), width, height, channels, data.data()); 50 | } 51 | -------------------------------------------------------------------------------- /src/Wad.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Image.h" 8 | #include "bspdef.h" 9 | 10 | struct WadHeader { 11 | char magic[4]; // should be WAD2/WAD3 12 | int32_t nDir; // number of directory entries 13 | int32_t dirOffset; // offset into directory 14 | }; 15 | 16 | // Directory entry structure 17 | struct WadDirEntry { 18 | int32_t nFilePos; // offset in WAD 19 | int32_t nDiskSize; // size in file 20 | int32_t nSize; // uncompressed size 21 | int8_t type; // type of entry 22 | bool compressed; // 0 if none 23 | int16_t nDummy; // not used 24 | char name[bsp30::MAXTEXTURENAME]; // must be null terminated 25 | }; 26 | 27 | // Structure for holding data for mipmap textires 28 | struct MipmapTexture { 29 | Image Img[bsp30::MIPLEVELS]; 30 | }; 31 | 32 | class Wad { 33 | public: 34 | explicit Wad(const fs::path& path); // Opens a WAD File and loads it's directory for texture searching 35 | 36 | auto loadTexture(const char* name) -> std::optional; 37 | auto LoadDecalTexture(const char* name) -> std::optional; 38 | static void CreateMipTexture(const std::vector& rawTexture, MipmapTexture& pMipTex); // Creates a Miptexture out of the raw texture data 39 | 40 | private: 41 | std::ifstream wadFile; 42 | std::vector dirEntries; 43 | 44 | void LoadDirectory(); // Loads the directory of the WAD file for further texture finding 45 | auto GetTexture(const char* name) -> std::vector; 46 | void CreateDecalTexture(const std::vector& rawTexture, MipmapTexture& pMipTex); 47 | }; 48 | -------------------------------------------------------------------------------- /src/opengl/shader/skybox.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform mat4 matrix; 4 | 5 | out vec3 cubeCoord; 6 | 7 | const vec3 points[36] = vec3[]( 8 | // x pos 9 | vec3( 1000.0, -1000.0, 1000.0), 10 | vec3( 1000.0, -1000.0, -1000.0), 11 | vec3( 1000.0, 1000.0, 1000.0), 12 | vec3( 1000.0, 1000.0, -1000.0), 13 | vec3( 1000.0, 1000.0, 1000.0), 14 | vec3( 1000.0, -1000.0, -1000.0), 15 | 16 | // x neg 17 | vec3(-1000.0, -1000.0, -1000.0), 18 | vec3(-1000.0, -1000.0, 1000.0), 19 | vec3(-1000.0, 1000.0, -1000.0), 20 | vec3(-1000.0, 1000.0, 1000.0), 21 | vec3(-1000.0, 1000.0, -1000.0), 22 | vec3(-1000.0, -1000.0, 1000.0), 23 | 24 | // y pos 25 | vec3( 1000.0, 1000.0, -1000.0), 26 | vec3(-1000.0, 1000.0, -1000.0), 27 | vec3( 1000.0, 1000.0, 1000.0), 28 | vec3(-1000.0, 1000.0, 1000.0), 29 | vec3( 1000.0, 1000.0, 1000.0), 30 | vec3(-1000.0, 1000.0, -1000.0), 31 | 32 | // y neg 33 | vec3(-1000.0, -1000.0, 1000.0), 34 | vec3(-1000.0, -1000.0, -1000.0), 35 | vec3( 1000.0, -1000.0, -1000.0), 36 | vec3(-1000.0, -1000.0, 1000.0), 37 | vec3( 1000.0, -1000.0, -1000.0), 38 | vec3( 1000.0, -1000.0, 1000.0), 39 | 40 | // z pos 41 | vec3(-1000.0, 1000.0, 1000.0), 42 | vec3(-1000.0, -1000.0, 1000.0), 43 | vec3( 1000.0, 1000.0, 1000.0), 44 | vec3( 1000.0, -1000.0, 1000.0), 45 | vec3( 1000.0, 1000.0, 1000.0), 46 | vec3(-1000.0, -1000.0, 1000.0), 47 | 48 | // z neg 49 | vec3(-1000.0, -1000.0, -1000.0), 50 | vec3(-1000.0, 1000.0, -1000.0), 51 | vec3( 1000.0, -1000.0, -1000.0), 52 | vec3( 1000.0, 1000.0, -1000.0), 53 | vec3( 1000.0, -1000.0, -1000.0), 54 | vec3(-1000.0, 1000.0, -1000.0) 55 | ); 56 | 57 | void main() { 58 | gl_Position = matrix * vec4(points[gl_VertexID], 1.0); 59 | cubeCoord = points[gl_VertexID]; 60 | cubeCoord.z = -cubeCoord.z; 61 | } 62 | -------------------------------------------------------------------------------- /src/GlfwWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace render { 10 | class IPlatform; 11 | } 12 | 13 | class GlfwWindow { 14 | public: 15 | GlfwWindow(std::string windowTitle, render::IPlatform& platform); 16 | ~GlfwWindow(); 17 | 18 | auto handle() const -> GLFWwindow*; 19 | auto shouldClose() const -> bool; 20 | 21 | protected: 22 | void toggleFullscreen(); 23 | void toggleStereo(); 24 | 25 | virtual void onResize(int width, int height) = 0; 26 | virtual void onMouseButton(int button, int action, int modifiers) = 0; 27 | virtual void onMouseMove(double dx, double dy) = 0; 28 | virtual void onMouseWheel(double xOffset, double yOffset) = 0; 29 | virtual void onKey(int key, int scancode, int action, int mods) = 0; 30 | virtual void onChar(unsigned int codepoint) = 0; 31 | 32 | bool m_fullscreen = false; 33 | bool m_stereo = false; 34 | int m_width = 1024; 35 | int m_height = 768; 36 | 37 | private: 38 | static void onError(int error, const char* description); 39 | static void onResize(GLFWwindow*, int width, int height); 40 | static void onMouseButton(GLFWwindow* window, int button, int action, int modifiers); 41 | static void onMouseMove(GLFWwindow* window, double dx, double dy); 42 | static void onMouseWheel(GLFWwindow* window, double xOffset, double yOffset); 43 | static void onKey(GLFWwindow* window, int key, int scancode, int action, int mods); 44 | static void onChar(GLFWwindow* window, unsigned int codepoint); 45 | 46 | void onWindowCreated(); 47 | 48 | // stored when going fullscreen to allow reverting back to same window 49 | int m_windowedX = 0; 50 | int m_windowedY = 0; 51 | int m_windowedWidth = 0; 52 | int m_windowedHeight = 0; 53 | 54 | GLFWwindow* m_window = nullptr; 55 | std::string m_windowTitle; 56 | }; -------------------------------------------------------------------------------- /src/directx11/directx11/Shader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #include 5 | #include 6 | using Microsoft::WRL::ComPtr; 7 | #endif 8 | 9 | #include 10 | 11 | namespace fs = std::filesystem; 12 | 13 | namespace dx11 { 14 | class Shader { 15 | public: 16 | Shader() = default; 17 | Shader(const std::string& profile, const std::string& entryPoint, const std::string& source, const std::string& filename = ""); 18 | Shader(const std::string& profile, const std::string& entryPoint, const fs::path& file); 19 | Shader(const Shader&) = delete; 20 | auto operator=(const Shader&) -> Shader& = delete; 21 | Shader(Shader&& other); 22 | auto operator=(Shader&& other) -> Shader&; 23 | ~Shader(); 24 | 25 | auto blob() -> ID3DBlob*; 26 | auto blob() const -> const ID3DBlob*; 27 | 28 | protected: 29 | void swap(Shader& other); 30 | 31 | ComPtr m_blob; 32 | }; 33 | 34 | class VertexShader : public Shader { 35 | public: 36 | VertexShader() = default; 37 | VertexShader(ID3D11Device* device, const std::string& profile, const std::string& entryPoint, const std::string& source, const std::string& filename = ""); 38 | VertexShader(ID3D11Device* device, const std::string& profile, const std::string& entryPoint, const fs::path& file); 39 | 40 | auto shader() -> ID3D11VertexShader*; 41 | 42 | private: 43 | ComPtr m_shader; 44 | }; 45 | 46 | class PixelShader : public Shader { 47 | public: 48 | PixelShader() = default; 49 | PixelShader(ID3D11Device* device, const std::string& profile, const std::string& entryPoint, const std::string& source, const std::string& filename = ""); 50 | PixelShader(ID3D11Device* device, const std::string& profile, const std::string& entryPoint, const fs::path& file); 51 | 52 | auto shader() -> ID3D11PixelShader*; 53 | 54 | private: 55 | ComPtr m_shader; 56 | }; 57 | } -------------------------------------------------------------------------------- /src/directx11/shader/main.hlsl: -------------------------------------------------------------------------------- 1 | 2 | cbuffer Whatever : register(b0) { 3 | float4x4 m; 4 | 5 | bool unit1Enabled; 6 | bool unit2Enabled; 7 | 8 | //bool flashlight; 9 | bool nightvision; 10 | 11 | bool alphaTest; 12 | }; 13 | 14 | struct VertexShaderInput { 15 | float3 position : POSITION; 16 | float3 normal : NORMAL; 17 | float2 texCoord : TEXCOORD0; 18 | float2 lightmapCoord : TEXCOORD1; 19 | }; 20 | 21 | struct PixelShaderInput { 22 | float4 pixelPosition : SV_Position; 23 | float2 texCoord : TEXCOORD0; 24 | float2 lightmapCoord : TEXCOORD1; 25 | }; 26 | 27 | PixelShaderInput main_vs(VertexShaderInput vin) { 28 | PixelShaderInput r; 29 | r.pixelPosition = mul(m, float4(vin.position, 1.0f)); 30 | r.texCoord = vin.texCoord; 31 | r.lightmapCoord = vin.lightmapCoord; 32 | return r; 33 | } 34 | 35 | SamplerState samp { 36 | Filter = MIN_MAG_MIP_LINEAR; 37 | AddressU = Repeat; 38 | AddressV = Repeat; 39 | }; 40 | 41 | Texture2D tex1 : register(t0); 42 | Texture2D tex2 : register(t1); 43 | 44 | float4 Nightvision(PixelShaderInput pin, float4 color) { 45 | float4 c1 = color / 2.0; 46 | 47 | c1 += tex1.Sample(samp, pin.texCoord + 0.01); 48 | c1 += tex1.Sample(samp, pin.texCoord + 0.02); 49 | c1 += tex1.Sample(samp, pin.texCoord + 0.03); 50 | 51 | float4 c2 = color / 2.0; 52 | c2 += tex2.Sample(samp, pin.texCoord + 0.01); 53 | c2 += tex2.Sample(samp, pin.texCoord + 0.02); 54 | c2 += tex2.Sample(samp, pin.texCoord + 0.03); 55 | 56 | float4 c = c1 * c2; 57 | c.r *= 0.2; 58 | c.b *= 0.2; 59 | 60 | return c; 61 | } 62 | 63 | float4 main_ps(PixelShaderInput pin) : SV_Target { 64 | float4 texel1 = 1.0f; 65 | float4 texel2 = 1.0f; 66 | 67 | if (unit1Enabled) { 68 | texel1 = tex1.Sample(samp, pin.texCoord); 69 | if (alphaTest && texel1.a < 0.25) 70 | discard; 71 | } 72 | 73 | if (unit2Enabled) 74 | texel2 = tex2.Sample(samp, pin.lightmapCoord); 75 | 76 | float4 color = float4(texel1.rgb * texel2.rgb, texel1.a); 77 | 78 | if (nightvision) 79 | color = Nightvision(pin, color); 80 | 81 | // brightness 82 | color *= 2; 83 | 84 | return color; 85 | } 86 | -------------------------------------------------------------------------------- /src/BspRenderable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "IRenderable.h" 8 | #include "bspdef.h" 9 | #include "mathlib.h" 10 | #include "IRenderer.h" 11 | 12 | class Bsp; 13 | class Camera; 14 | class Entity; 15 | 16 | class BspRenderable : public IRenderable { 17 | public: 18 | BspRenderable(render::IRenderer& renderer, const Bsp& bsp, const Camera& camera); 19 | ~BspRenderable(); 20 | 21 | virtual void render(const RenderSettings& settings) override; 22 | 23 | private: 24 | void loadTextures(); 25 | auto loadLightmaps() -> std::vector>; 26 | void loadSkyTextures(); 27 | 28 | void renderSkybox(); 29 | auto renderStaticGeometry(glm::vec3 pos) -> std::vector; 30 | //void renderLeafOutlines(); 31 | void renderLeaf(int iLeaf, std::vector& fri); // Renders a leaf of the BSP tree by rendering each face of the leaf by the given index 32 | void renderBSP(int node, const boost::dynamic_bitset& visList, glm::vec3 pos, std::vector& fri); // Recursively walks through the BSP tree and draws it 33 | 34 | void buildBuffers(std::vector>&& lmCoords); 35 | 36 | private: 37 | struct Vertex { 38 | glm::vec3 position; 39 | glm::vec3 normal; 40 | glm::vec2 texCoord; 41 | }; 42 | 43 | struct VertexWithLM : Vertex { 44 | glm::vec2 lightmapCoord; 45 | }; 46 | 47 | render::IRenderer& m_renderer; 48 | const Bsp* m_bsp; 49 | const Camera* m_camera; 50 | 51 | const RenderSettings* m_settings = nullptr; 52 | 53 | std::optional> m_skyboxTex; 54 | std::vector> m_textures; 55 | std::unique_ptr m_lightmapAtlas; 56 | 57 | std::unique_ptr m_staticGeometryVbo; 58 | std::unique_ptr m_decalVbo; 59 | 60 | std::unique_ptr m_staticGeometryVao; 61 | std::unique_ptr m_decalVao; 62 | 63 | std::vector vertexOffsets; 64 | 65 | mutable std::vector facesDrawn; 66 | }; -------------------------------------------------------------------------------- /src/directx11/shader/skybox.hlsl: -------------------------------------------------------------------------------- 1 | 2 | cbuffer TransformConstants : register(b0) { 3 | float4x4 m; 4 | }; 5 | 6 | static const float3 points[36] = { 7 | // x pos 8 | float3( 1000.0, -1000.0, 1000.0), 9 | float3( 1000.0, -1000.0, -1000.0), 10 | float3( 1000.0, 1000.0, 1000.0), 11 | float3( 1000.0, 1000.0, -1000.0), 12 | float3( 1000.0, 1000.0, 1000.0), 13 | float3( 1000.0, -1000.0, -1000.0), 14 | 15 | // x neg 16 | float3(-1000.0, -1000.0, -1000.0), 17 | float3(-1000.0, -1000.0, 1000.0), 18 | float3(-1000.0, 1000.0, -1000.0), 19 | float3(-1000.0, 1000.0, 1000.0), 20 | float3(-1000.0, 1000.0, -1000.0), 21 | float3(-1000.0, -1000.0, 1000.0), 22 | 23 | // y pos 24 | float3( 1000.0, 1000.0, -1000.0), 25 | float3(-1000.0, 1000.0, -1000.0), 26 | float3( 1000.0, 1000.0, 1000.0), 27 | float3(-1000.0, 1000.0, 1000.0), 28 | float3( 1000.0, 1000.0, 1000.0), 29 | float3(-1000.0, 1000.0, -1000.0), 30 | 31 | // y neg 32 | float3(-1000.0, -1000.0, 1000.0), 33 | float3(-1000.0, -1000.0, -1000.0), 34 | float3( 1000.0, -1000.0, -1000.0), 35 | float3(-1000.0, -1000.0, 1000.0), 36 | float3( 1000.0, -1000.0, -1000.0), 37 | float3( 1000.0, -1000.0, 1000.0), 38 | 39 | // z pos 40 | float3(-1000.0, 1000.0, 1000.0), 41 | float3(-1000.0, -1000.0, 1000.0), 42 | float3( 1000.0, 1000.0, 1000.0), 43 | float3( 1000.0, -1000.0, 1000.0), 44 | float3( 1000.0, 1000.0, 1000.0), 45 | float3(-1000.0, -1000.0, 1000.0), 46 | 47 | // z neg 48 | float3(-1000.0, -1000.0, -1000.0), 49 | float3(-1000.0, 1000.0, -1000.0), 50 | float3( 1000.0, -1000.0, -1000.0), 51 | float3( 1000.0, 1000.0, -1000.0), 52 | float3( 1000.0, -1000.0, -1000.0), 53 | float3(-1000.0, 1000.0, -1000.0) 54 | }; 55 | 56 | struct PixelShaderInput { 57 | float4 pixelPosition : SV_POSITION; 58 | float3 cubeCoord : TEXCOORD; 59 | }; 60 | 61 | PixelShaderInput main_vs(uint vertexID : SV_VertexID) { 62 | PixelShaderInput r; 63 | r.pixelPosition = mul(m, float4(points[vertexID], 1.0)); 64 | r.cubeCoord = saturate(points[vertexID]); 65 | r.cubeCoord.z = -r.cubeCoord.z; 66 | return r; 67 | } 68 | 69 | TextureCube cubeTexture : register(t0); 70 | 71 | SamplerState cubeSampler { 72 | Filter = MIN_MAG_MIP_LINEAR; 73 | AddressU = Clamp; 74 | AddressV = Clamp; 75 | }; 76 | 77 | float4 main_ps(PixelShaderInput pin) : SV_TARGET { 78 | return cubeTexture.SampleLevel(cubeSampler, pin.cubeCoord, 0); 79 | } 80 | -------------------------------------------------------------------------------- /src/opengl/Renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../IRenderer.h" 6 | #include "opengl/VAO.h" 7 | #include "opengl/Program.h" 8 | #include "opengl/Buffer.h" 9 | 10 | namespace render::opengl { 11 | class Renderer : public IRenderer { 12 | public: 13 | Renderer(); 14 | ~Renderer(); 15 | 16 | virtual void resizeViewport(int width, int height) override; 17 | virtual void clear() override; 18 | 19 | virtual auto createTexture(const std::vector& mipmaps) const -> std::unique_ptr override; 20 | virtual auto createCubeTexture(const std::array& sides) const -> std::unique_ptr override; 21 | virtual auto createBuffer(std::size_t size, const void* data) const -> std::unique_ptr override; 22 | virtual auto createInputLayout(IBuffer& buffer, const std::vector& layout) const -> std::unique_ptr override; 23 | 24 | virtual void renderCoords(const glm::mat4& matrix) override; 25 | virtual void renderSkyBox(ITexture& cubemap, const glm::mat4& matrix) override; 26 | virtual void renderStatic(std::vector entities, const std::vector& decals, IInputLayout& staticLayout, IInputLayout& decalLayout, std::vector>& textures, render::ITexture& lightmapAtlas, const RenderSettings& settings) override; 27 | virtual void renderImgui(ImDrawData* data) override; 28 | 29 | virtual auto screenshot() const -> Image override; 30 | 31 | private: 32 | void renderBrushEntity(std::vector fri, render::ITexture& lightmapAtlas, const RenderSettings& settings, glm::vec3 origin, float alpha, bsp30::RenderMode renderMode); 33 | void renderFri(std::vector fri, render::ITexture& lightmapAtlas); 34 | void renderDecals(const std::vector& decals, std::vector>& textures); 35 | 36 | struct Glew { 37 | Glew(); 38 | } m_glew; 39 | 40 | gl::VAO m_emptyVao; 41 | gl::Program m_skyboxProgram; 42 | gl::Program m_shaderProgram; 43 | gl::Program m_coordsProgram; 44 | }; 45 | 46 | class Platform : public IPlatform { 47 | public: 48 | virtual auto createWindowAndContext(int width, int height, const char * title, GLFWmonitor * monitor) -> GLFWwindow* override; 49 | virtual auto createRenderer() -> std::unique_ptr override; 50 | virtual void swapBuffers() override; 51 | 52 | private: 53 | GLFWwindow* m_window = nullptr; 54 | }; 55 | } -------------------------------------------------------------------------------- /thirdparty/imgui_impl_glfw.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Platform Binding for GLFW 2 | // This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan..) 3 | // (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) 4 | 5 | // Implemented features: 6 | // [X] Platform: Clipboard support. 7 | // [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. 8 | // [x] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: 3 cursors types are missing from GLFW. 9 | // [X] Platform: Keyboard arrays indexed using GLFW_KEY_* codes, e.g. ImGui::IsKeyPressed(GLFW_KEY_SPACE). 10 | 11 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. 12 | // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. 13 | // https://github.com/ocornut/imgui 14 | 15 | // About GLSL version: 16 | // The 'glsl_version' initialization parameter defaults to "#version 150" if NULL. 17 | // Only override if your GL version doesn't handle this GLSL version. Keep NULL if unsure! 18 | 19 | struct GLFWwindow; 20 | 21 | enum GlfwClientApi 22 | { 23 | GlfwClientApi_Unknown, 24 | GlfwClientApi_OpenGL, 25 | GlfwClientApi_Vulkan 26 | }; 27 | 28 | IMGUI_IMPL_API bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, GlfwClientApi client_api); 29 | IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks); 30 | IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks); 31 | IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown(); 32 | IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame(); 33 | 34 | // GLFW callbacks (installed by default if you enable 'install_callbacks' during initialization) 35 | // Provided here if you want to chain callbacks. 36 | // You can also handle inputs yourself and use those as a reference. 37 | IMGUI_IMPL_API void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); 38 | IMGUI_IMPL_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); 39 | IMGUI_IMPL_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); 40 | IMGUI_IMPL_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); 41 | -------------------------------------------------------------------------------- /src/IRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Image.h" 10 | #include "bspdef.h" 11 | 12 | struct GLFWwindow; 13 | struct GLFWmonitor; 14 | 15 | class Camera; 16 | struct Decal; 17 | struct ImDrawData; 18 | struct RenderSettings; 19 | 20 | namespace render { 21 | class ITexture { 22 | public: 23 | ~ITexture() = default; 24 | }; 25 | 26 | class IBuffer { 27 | public: 28 | ~IBuffer() = default; 29 | }; 30 | 31 | class IInputLayout { 32 | public: 33 | ~IInputLayout() = default; 34 | }; 35 | 36 | struct FaceRenderInfo { 37 | render::ITexture* tex; 38 | unsigned int offset; 39 | unsigned int count; 40 | }; 41 | 42 | struct AttributeLayout { 43 | const char* semantic; 44 | unsigned int semanticIndex; 45 | unsigned int size; 46 | enum class Type { 47 | Float 48 | } type; 49 | unsigned int stride; 50 | unsigned int offset; 51 | }; 52 | 53 | struct EntityData { 54 | std::vector fri; 55 | glm::vec3 origin; 56 | float alpha; 57 | bsp30::RenderMode renderMode; 58 | }; 59 | 60 | class IRenderer { 61 | public: 62 | virtual ~IRenderer() = default; 63 | 64 | virtual void resizeViewport(int width, int height) = 0; 65 | virtual void clear() = 0; 66 | 67 | virtual auto createTexture(const std::vector& mipmaps) const -> std::unique_ptr = 0; 68 | virtual auto createCubeTexture(const std::array& sides) const -> std::unique_ptr = 0; 69 | virtual auto createBuffer(std::size_t size, const void* data) const -> std::unique_ptr = 0; 70 | virtual auto createInputLayout(IBuffer& buffer, const std::vector& layout) const -> std::unique_ptr = 0; 71 | 72 | virtual void renderCoords(const glm::mat4& matrix) = 0; 73 | virtual void renderSkyBox(ITexture& cubemap, const glm::mat4& matrix) = 0; 74 | virtual void renderStatic(std::vector entities, const std::vector& decals, IInputLayout& staticLayout, IInputLayout& decalLayout, std::vector>& textures, render::ITexture& lightmapAtlas, const RenderSettings& settings) = 0; 75 | virtual void renderImgui(ImDrawData* data) = 0; 76 | 77 | virtual auto screenshot() const -> Image = 0; 78 | }; 79 | 80 | class IPlatform { 81 | public: 82 | virtual auto createWindowAndContext(int width, int height, const char* title, GLFWmonitor* monitor) -> GLFWwindow* = 0; 83 | virtual auto createRenderer() -> std::unique_ptr = 0; 84 | virtual void swapBuffers() = 0; 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /thirdparty/imgui_impl_opengl3.h: -------------------------------------------------------------------------------- 1 | // dear imgui: Renderer for OpenGL3 / OpenGL ES2 / OpenGL ES3 (modern OpenGL with shaders / programmatic pipeline) 2 | // This needs to be used along with a Platform Binding (e.g. GLFW, SDL, Win32, custom..) 3 | // (Note: We are using GL3W as a helper library to access OpenGL functions since there is no standard header to access modern OpenGL functions easily. Alternatives are GLEW, Glad, etc..) 4 | 5 | // Implemented features: 6 | // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID in imgui.cpp. 7 | 8 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. 9 | // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. 10 | // https://github.com/ocornut/imgui 11 | 12 | // About OpenGL function loaders: 13 | // About OpenGL function loaders: modern OpenGL doesn't have a standard header file and requires individual function pointers to be loaded manually. 14 | // Helper libraries are often used for this purpose! Here we are supporting a few common ones: gl3w, glew, glad. 15 | // You may use another loader/header of your choice (glext, glLoadGen, etc.), or chose to manually implement your own. 16 | 17 | // About GLSL version: 18 | // The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string. 19 | // On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" 20 | // Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. 21 | 22 | // Set default OpenGL loader to be gl3w 23 | #if !defined(IMGUI_IMPL_OPENGL_LOADER_GL3W) \ 24 | && !defined(IMGUI_IMPL_OPENGL_LOADER_GLEW) \ 25 | && !defined(IMGUI_IMPL_OPENGL_LOADER_GLAD) \ 26 | && !defined(IMGUI_IMPL_OPENGL_LOADER_CUSTOM) 27 | #define IMGUI_IMPL_OPENGL_LOADER_GL3W 28 | #endif 29 | 30 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_Init(const char* glsl_version = NULL); 31 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_Shutdown(); 32 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_NewFrame(); 33 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); 34 | 35 | // Called by Init/NewFrame/Shutdown 36 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture(); 37 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture(); 38 | IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); 39 | IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); 40 | -------------------------------------------------------------------------------- /src/Hud.cpp: -------------------------------------------------------------------------------- 1 | #include "Hud.h" 2 | 3 | #include 4 | 5 | #include "Camera.h" 6 | #include "IPSS.h" 7 | #include "Timer.h" 8 | #include "global.h" 9 | 10 | namespace { 11 | constexpr auto FONT_HUD_HEIGHT = 12; 12 | const glm::vec3 FONT_HUD_COLOR = {1.0f, 0.0f, 0.0f}; 13 | } 14 | 15 | Hud::Hud(const Camera& camera, const Timer& timer) 16 | : m_camera(camera), m_timer(timer) {} 17 | 18 | void Hud::print(std::string text) { 19 | m_console.push_back(std::move(text)); 20 | } 21 | 22 | auto Hud::drawData() const -> ImDrawData* { 23 | const auto& cameraPos = m_camera.position(); 24 | const auto& cameraView = m_camera.viewVector(); 25 | const auto& pitch = m_camera.pitch(); 26 | const auto& yaw = m_camera.yaw(); 27 | const auto& fps = m_timer.TPS; 28 | 29 | ImGui::NewFrame(); 30 | 31 | ImGui::Begin("Camera"); 32 | ImGui::LabelText("FPS", (IPSS() << std::fixed << std::setprecision(1) << fps).str().c_str()); 33 | ImGui::LabelText("cam pos", (IPSS() << std::fixed << std::setprecision(1) << cameraPos.x << "x " << cameraPos.y << "y " << cameraPos.z << "z").str().c_str()); 34 | ImGui::LabelText("cam view", (IPSS() << std::fixed << std::setprecision(1) << pitch << " pitch " << yaw << " yaw (vec: " << cameraView.x << "x " << cameraView.y << "y " << cameraView.z << "z)").str().c_str()); 35 | ImGui::Combo("Movetype", &global::moveType, " walk\0 fly\0 noclip\0"); 36 | ImGui::Combo("Hull", &global::hullIndex, "regular player (0)\0 ducked player (1)\0 point hull (2)\0 3\0"); 37 | ImGui::End(); 38 | 39 | ImGui::Begin("Log"); 40 | for (const auto& line : m_console) 41 | ImGui::Text(line.c_str()); 42 | ImGui::End(); 43 | 44 | ImGui::Begin("Render"); 45 | ImGui::Combo("render API", (int*)&global::renderApi, 46 | #ifdef WIN32 47 | "OpenGL\0Direct3D\0" 48 | #else 49 | "OpenGL\0" 50 | #endif 51 | ); 52 | 53 | ImGui::Checkbox("textures", &global::textures); 54 | ImGui::Checkbox("lightmaps", &global::lightmaps); 55 | ImGui::Checkbox("polygons", &global::polygons); 56 | 57 | ImGui::Checkbox("staticBSP", &global::renderStaticBSP); 58 | ImGui::Checkbox("brushEntities", &global::renderBrushEntities); 59 | ImGui::Checkbox("skybox", &global::renderSkybox); 60 | ImGui::Checkbox("decals", &global::renderDecals); 61 | ImGui::Checkbox("coords", &global::renderCoords); 62 | ImGui::Checkbox("leafOutlines", &global::renderLeafOutlines); 63 | ImGui::Checkbox("HUD", &global::renderHUD); 64 | ImGui::End(); 65 | 66 | ImGui::Render(); 67 | 68 | return ImGui::GetDrawData(); 69 | } 70 | 71 | auto Hud::fontHeight() const -> int { 72 | return FONT_HUD_HEIGHT; 73 | } 74 | 75 | auto Hud::fontColor() const -> glm::vec3 { 76 | return FONT_HUD_COLOR; 77 | } 78 | -------------------------------------------------------------------------------- /src/directx11/directx11/Shader.cpp: -------------------------------------------------------------------------------- 1 | #include "Shader.h" 2 | 3 | #include 4 | 5 | #include "../../IO.h" 6 | 7 | namespace dx11 { 8 | Shader::Shader(const std::string& profile, const std::string& entryPoint, const std::string& source, const std::string& filename) { 9 | UINT flags = D3DCOMPILE_ENABLE_STRICTNESS; 10 | #if _DEBUG 11 | flags |= D3DCOMPILE_DEBUG; 12 | flags |= D3DCOMPILE_SKIP_OPTIMIZATION; 13 | #endif 14 | 15 | ComPtr errorBlob; 16 | if (FAILED(D3DCompile(source.c_str(), source.size(), filename.c_str(), nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, entryPoint.c_str(), profile.c_str(), flags, 0, &m_blob, &errorBlob))) { 17 | auto msg = "Failed to compile shader " + filename + ":\n"; 18 | if (errorBlob) 19 | msg += static_cast(errorBlob->GetBufferPointer()); 20 | throw std::runtime_error(msg); 21 | } 22 | } 23 | 24 | Shader::Shader(const std::string& profile, const std::string& entryPoint, const fs::path& file) 25 | : Shader(profile, entryPoint, readTextFile(file), file.string()) {} 26 | 27 | Shader::Shader(Shader&& other) { 28 | swap(other); 29 | } 30 | 31 | Shader& Shader::operator=(Shader&& other) { 32 | swap(other); 33 | return *this; 34 | } 35 | 36 | Shader::~Shader() = default; 37 | 38 | auto Shader::blob() -> ID3DBlob* { 39 | return m_blob.Get(); 40 | } 41 | 42 | auto Shader::blob() const -> const ID3DBlob* { 43 | return m_blob.Get(); 44 | } 45 | 46 | void Shader::swap(Shader& other) { 47 | using std::swap; 48 | swap(m_blob, other.m_blob); 49 | } 50 | 51 | VertexShader::VertexShader(ID3D11Device* device, const std::string& profile, const std::string& entryPoint, const std::string& source, const std::string& filename) 52 | : Shader(profile, entryPoint, source, filename) { 53 | if (FAILED(device->CreateVertexShader(m_blob->GetBufferPointer(), m_blob->GetBufferSize(), nullptr, &m_shader))) 54 | throw std::runtime_error("Failed to create vertex shader from compiled bytecode"); 55 | } 56 | 57 | VertexShader::VertexShader(ID3D11Device* device, const std::string& profile, const std::string& entryPoint, const fs::path& file) 58 | : VertexShader(device, profile, entryPoint, readTextFile(file), file.string()) {} 59 | 60 | auto VertexShader::shader() -> ID3D11VertexShader* { 61 | return m_shader.Get(); 62 | } 63 | 64 | PixelShader::PixelShader(ID3D11Device* device, const std::string& profile, const std::string& entryPoint, const std::string& source, const std::string& filename) 65 | : Shader(profile, entryPoint, source, filename) { 66 | if (FAILED(device->CreatePixelShader(m_blob->GetBufferPointer(), m_blob->GetBufferSize(), nullptr, &m_shader))) 67 | throw std::runtime_error("Failed to create vertex shader from compiled bytecode"); 68 | } 69 | 70 | PixelShader::PixelShader(ID3D11Device* device, const std::string& profile, const std::string& entryPoint, const fs::path& file) 71 | : PixelShader(device, profile, entryPoint, readTextFile(file), file.string()) {} 72 | 73 | auto PixelShader::shader() -> ID3D11PixelShader* { 74 | return m_shader.Get(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(hlbsp) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | # find packages 9 | find_package(Boost REQUIRED COMPONENTS system filesystem) 10 | include_directories(${Boost_INCLUDE_DIRS}) 11 | 12 | find_package(OpenGL REQUIRED) 13 | include_directories(${OPENGL_INCLUDE_DIR}) 14 | 15 | find_package(GLEW REQUIRED) 16 | include_directories(${GLEW_INCLUDE_DIRS}) 17 | 18 | if (MSVC) 19 | find_package(glfw3 REQUIRED) 20 | else() 21 | find_package(PkgConfig REQUIRED) 22 | pkg_search_module(GLFW REQUIRED glfw3) 23 | endif() 24 | include_directories(${GLFW3_INCLUDE_DIRS}) 25 | 26 | find_package(glm) 27 | if (glm_FOUND) 28 | include_directories(${glm_INCLUDE_DIR}) 29 | else() 30 | find_package(GLM) # older versions 31 | include_directories(${GLM_INCLUDE_DIR}) 32 | endif() 33 | 34 | include_directories(thirdparty) 35 | 36 | # executable 37 | file(GLOB source_files src/* thirdparty/*) 38 | file(GLOB_RECURSE opengl_files src/opengl/*) 39 | list(APPEND source_files ${opengl_files}) 40 | if(WIN32) 41 | message("Including DirectX") 42 | file(GLOB_RECURSE directx_files src/directx11/*) 43 | foreach(directx_file ${directx_files}) 44 | if(${directx_file} MATCHES ".+\\.hlsl") 45 | set_source_files_properties(${directx_file} PROPERTIES VS_TOOL_OVERRIDE "None") 46 | endif() 47 | endforeach() 48 | list(APPEND source_files ${directx_files}) 49 | else() 50 | file(GLOB_RECURSE directx_imgui_files thirdparty/imgui_impl_dx11.*) 51 | list(REMOVE_ITEM source_files ${directx_imgui_files}) 52 | endif() 53 | 54 | add_executable(${PROJECT_NAME} ${source_files}) 55 | source_group("" FILES ${source_files}) 56 | source_group(TREE ${CMAKE_CURRENT_LIST_DIR} FILES ${source_files}) 57 | 58 | target_link_libraries(${PROJECT_NAME} PRIVATE 59 | GLEW::GLEW 60 | ${Boost_LIBRARIES} 61 | glfw 62 | OpenGL::GL 63 | ) 64 | 65 | target_compile_options(${PROJECT_NAME} PRIVATE 66 | -DGLFW_INCLUDE_NONE 67 | -DGLM_ENABLE_EXPERIMENTAL 68 | -DGLM_FORCE_RADIANS 69 | -DIMGUI_IMPL_OPENGL_LOADER_GLEW 70 | ) 71 | 72 | if(WIN32) 73 | target_compile_options(${PROJECT_NAME} PRIVATE -DGLFW_EXPOSE_NATIVE_WIN32 -DNOMINMAX) 74 | target_link_libraries(${PROJECT_NAME} PRIVATE D3D11.lib D3DCompiler.lib) 75 | endif() 76 | 77 | if(MSVC) 78 | target_compile_options(${PROJECT_NAME} PRIVATE /W4) 79 | else() 80 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) 81 | endif() 82 | 83 | # clang-format 84 | find_program(CLANG_FORMAT NAMES "clang-format") 85 | if(NOT CLANG_FORMAT) 86 | message("clang-format not found") 87 | else() 88 | message("clang-format found: ${CLANG_FORMAT}") 89 | add_custom_target(clang-format COMMAND ${CLANG_FORMAT} -style=file -i ${source_files}) 90 | endif() 91 | 92 | # clang-tidy 93 | find_program(CLANG_TIDY NAMES "clang-tidy") 94 | if(NOT CLANG_TIDY) 95 | message("clang-tidy not found") 96 | else() 97 | message("clang-tidy found: ${CLANG_TIDY}") 98 | set_target_properties(${PROJECT_NAME} PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY}") 99 | configure_file(.clang-tidy .clang-tidy COPYONLY) 100 | endif() 101 | -------------------------------------------------------------------------------- /src/opengl/opengl/Program.cpp: -------------------------------------------------------------------------------- 1 | #include "Program.h" 2 | 3 | #include 4 | 5 | #include "../../IO.h" 6 | 7 | namespace gl { 8 | namespace { 9 | template 10 | auto loadAttribUniformMap(GLuint program, GLenum countFlag, GetFunc getFunc, GetLocationFunc getLocationFunc) { 11 | std::unordered_map map; 12 | 13 | GLint count; 14 | glGetProgramiv(program, countFlag, &count); 15 | for (auto i = 0; i < count; i++) { 16 | GLint length; 17 | std::string name(255, '\0'); 18 | if constexpr (std::is_invocable_v) 19 | getFunc(program, i, static_cast(name.size()), &length, name.data()); 20 | else { 21 | GLsizei size; 22 | GLenum type; 23 | getFunc(program, i, static_cast(name.size()), &length, &size, &type, name.data()); 24 | } 25 | 26 | name.resize(length); 27 | if (const auto loc = getLocationFunc(program, name.c_str()); loc != -1) 28 | map.emplace(std::move(name), loc); 29 | } 30 | 31 | return map; 32 | } 33 | } 34 | 35 | Program::Program(std::initializer_list shaders) { 36 | m_id = glCreateProgram(); 37 | for (const auto& shader : shaders) 38 | glAttachShader(m_id, shader.id()); 39 | glLinkProgram(m_id); 40 | 41 | GLint status; 42 | glGetProgramiv(m_id, GL_LINK_STATUS, &status); 43 | GLint length; 44 | glGetProgramiv(m_id, GL_INFO_LOG_LENGTH, &length); 45 | std::string buildLog; 46 | buildLog.resize(length); 47 | glGetProgramInfoLog(m_id, length, nullptr, buildLog.data()); 48 | 49 | if (status != GL_TRUE) 50 | throw std::runtime_error("Failed to link program:\n" + buildLog); 51 | else 52 | std::clog << "Program link log:\n" 53 | << buildLog << "\n"; 54 | 55 | m_attributes = loadAttribUniformMap(m_id, GL_ACTIVE_ATTRIBUTES, glGetActiveAttrib, glGetAttribLocation); 56 | m_uniforms = loadAttribUniformMap(m_id, GL_ACTIVE_UNIFORMS, glGetActiveUniform, glGetUniformLocation); 57 | m_uniformBlocks = loadAttribUniformMap(m_id, GL_ACTIVE_UNIFORM_BLOCKS, glGetActiveUniformBlockName, glGetUniformBlockIndex); 58 | } 59 | 60 | Program::Program(Program&& other) { 61 | swap(other); 62 | } 63 | 64 | auto Program::operator=(Program&& other) -> Program& { 65 | swap(other); 66 | return *this; 67 | } 68 | 69 | Program::~Program() { 70 | if (m_id != 0) 71 | glDeleteProgram(m_id); 72 | } 73 | 74 | auto Program::id() const -> GLuint { 75 | return m_id; 76 | } 77 | 78 | void Program::use() const { 79 | glUseProgram(m_id); 80 | } 81 | 82 | auto Program::uniformLocation(const std::string& name) const -> GLint { 83 | return m_uniforms.at(name); 84 | } 85 | 86 | auto Program::uniformBlockIndex(const std::string& name) const -> GLint { 87 | return m_uniformBlocks.at(name); 88 | } 89 | 90 | auto Program::attributeLocation(const std::string& name) const -> GLint { 91 | return m_attributes.at(name); 92 | } 93 | 94 | void Program::swap(Program& other) { 95 | using std::swap; 96 | swap(m_id, other.m_id); 97 | swap(m_attributes, other.m_attributes); 98 | swap(m_uniforms, other.m_uniforms); 99 | } 100 | } -------------------------------------------------------------------------------- /src/directx11/Renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #include 5 | #include 6 | using Microsoft::WRL::ComPtr; 7 | #endif 8 | 9 | #include 10 | 11 | #include "../IRenderer.h" 12 | #include "directx11/Program.h" 13 | 14 | namespace render::directx11 { 15 | struct ConstantBufferData; 16 | 17 | class Platform; 18 | 19 | class Renderer : public IRenderer { 20 | public: 21 | Renderer(Platform& platform); 22 | ~Renderer(); 23 | 24 | virtual void resizeViewport(int width, int height) override; 25 | virtual void clear() override; 26 | 27 | virtual auto createTexture(const std::vector& mipmaps) const -> std::unique_ptr override; 28 | virtual auto createCubeTexture(const std::array& sides) const -> std::unique_ptr override; 29 | virtual auto createBuffer(std::size_t size, const void* data) const -> std::unique_ptr override; 30 | virtual auto createInputLayout(IBuffer& buffer, const std::vector& layout) const -> std::unique_ptr override; 31 | 32 | virtual void renderCoords(const glm::mat4& matrix) override; 33 | virtual void renderSkyBox(ITexture& cubemap, const glm::mat4& matrix) override; 34 | virtual void renderStatic(std::vector entities, const std::vector& decals, IInputLayout& staticLayout, IInputLayout& decalLayout, std::vector>& textures, render::ITexture& lightmapAtlas, const RenderSettings& settings) override; 35 | virtual void renderImgui(ImDrawData* data) override; 36 | 37 | virtual auto screenshot() const -> Image override; 38 | 39 | private: 40 | void renderBrushEntity(std::vector fri, render::ITexture& lightmapAtlas, const RenderSettings& settings, glm::vec3 origin, float alpha, bsp30::RenderMode renderMode, ConstantBufferData cbd); 41 | void renderFri(std::vector fri, render::ITexture& lightmapAtlas); 42 | void renderDecals(const std::vector& decals, std::vector>& textures); 43 | 44 | ComPtr& m_device; 45 | ComPtr& m_context; 46 | ComPtr& m_swapChain; 47 | ComPtr& m_backBufferRTV; 48 | 49 | //ComPtr& m_fbColorTexture; 50 | ComPtr& m_fbDepthStencilTexture; 51 | //ComPtr& m_fbRtv; 52 | //ComPtr& m_fbSrv; 53 | ComPtr& m_fbDsv; 54 | 55 | ComPtr m_defaultRasterizerState; 56 | ComPtr m_defaultDepthStencilState; 57 | ComPtr m_skyboxDepthStencilState; 58 | 59 | ComPtr m_wrapSampler; 60 | ComPtr m_skyboxSampler; 61 | 62 | ComPtr m_matrixCBuffer; 63 | ComPtr m_mainCBuffer; 64 | 65 | dx11::Program m_skyboxProgram; 66 | dx11::Program m_shaderProgram; 67 | dx11::Program m_coordsProgram; 68 | }; 69 | 70 | class Platform : public IPlatform { 71 | public: 72 | virtual auto createWindowAndContext(int width, int height, const char * title, GLFWmonitor * monitor) -> GLFWwindow* override; 73 | virtual auto createRenderer() -> std::unique_ptr override; 74 | virtual void swapBuffers() override; 75 | 76 | private: 77 | ComPtr m_device; 78 | ComPtr m_context; 79 | ComPtr m_swapChain; 80 | ComPtr m_backBufferRTV; 81 | 82 | //ComPtr m_fbColorTexture; 83 | ComPtr m_fbDepthStencilTexture; 84 | //ComPtr m_fbRtv; 85 | //ComPtr m_fbSrv; 86 | ComPtr m_fbDsv; 87 | 88 | friend class Renderer; 89 | }; 90 | } -------------------------------------------------------------------------------- /src/Bsp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "bspdef.h" 9 | #include "Entity.h" 10 | #include "Wad.h" 11 | #include "IO.h" 12 | 13 | struct FaceTexCoords { 14 | std::vector texCoords; 15 | std::vector lightmapCoords; 16 | }; 17 | 18 | struct Decal { 19 | uint32_t texIndex; 20 | glm::vec3 normal; 21 | glm::vec3 vec[4]; 22 | }; 23 | 24 | struct Hull { 25 | bsp30::ClipNode* clipnodes; 26 | bsp30::Plane* planes; 27 | int firstclipnode; 28 | int lastclipnode; 29 | glm::vec3 clipMins; 30 | glm::vec3 clipMaxs; 31 | }; 32 | 33 | struct Model : bsp30::Model { 34 | std::array hulls; 35 | }; 36 | 37 | class Bsp { 38 | public: 39 | explicit Bsp(const fs::path& filename); 40 | ~Bsp(); 41 | 42 | auto FindEntity(std::string_view name) -> Entity*; 43 | auto FindEntity(std::string_view name) const -> const Entity*; 44 | auto FindEntities(std::string_view name) -> std::vector; 45 | 46 | auto loadSkyBox() const -> std::optional>; 47 | 48 | bsp30::Header header{}; // Stores the header 49 | std::vector vertices; // Stores the vertices 50 | std::vector edges; // Stores the edges 51 | std::vector surfEdges; // Stores the surface edges 52 | std::vector nodes; // Stores the nodes 53 | std::vector leaves; // Stores the leafs 54 | std::vector markSurfaces; // Stores the marksurfaces 55 | std::vector planes; // Stores the planes 56 | std::vector faces; // Stores the faces 57 | std::vector clipNodes; 58 | bsp30::TextureHeader textureHeader{}; // Stores the texture header 59 | std::vector mipTextures; // Stores the miptextures 60 | std::vector mipTextureOffsets; // Stores the miptexture offsets 61 | std::vector textureInfos; // Stores the texture infos 62 | 63 | std::vector faceTexCoords; // Stores precalculated texture and lightmap coordinates for every vertex 64 | 65 | std::vector entities; 66 | std::vector brushEntities; // Indices of brush entities in entities 67 | std::vector specialEntities; // IndicUnloadWadFileses of special entities in entities 68 | std::vector wadFiles; 69 | std::vector decalWads; 70 | std::vector m_decals; 71 | std::vector> visLists; // Stores the vis lists for all faces 72 | 73 | std::vector m_textures; 74 | std::vector m_lightmaps; 75 | 76 | std::vector hull0ClipNodes; 77 | std::vector models; 78 | 79 | private: 80 | void LoadWadFiles(std::string wadStr); // Loads and prepares the wad files for further texture loading 81 | void UnloadWadFiles(); // Unloads all wad files and frees allocated memory 82 | void LoadTextures(std::ifstream& file); // Loads the textures either from the wad file or directly from the bsp file 83 | auto LoadTextureFromWads(const char* name) -> std::optional; // Finds and loads a texture from a wad file by the given name 84 | auto LoadDecalTexture(const char* name) -> std::optional; 85 | void LoadDecals(); 86 | void LoadLightMaps(const std::vector& pLightMapData); // Loads lightmaps and calculates extends and coordinates 87 | void LoadModels(std::ifstream& file); 88 | 89 | void ParseEntities(const std::string& entitiesString); // Parses the entity lump of the bsp file into single entity classes 90 | 91 | void CountVisLeafs(int iNode, int& count); // Counts the number of visLeaves recursively 92 | auto decompressVIS(int leaf, const std::vector& compressedVis) const -> boost::dynamic_bitset; // Get the PVS for a given leaf and return it in the form of a pointer to a bool array 93 | 94 | auto findLeaf(glm::vec3 pos, int node = 0) const -> std::optional; // Recursivly walks through the BSP tree to find the leaf where the camera is in 95 | 96 | friend class BspRenderable; 97 | }; 98 | -------------------------------------------------------------------------------- /thirdparty/imconfig.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // COMPILE-TIME OPTIONS FOR DEAR IMGUI 3 | // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. 4 | // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. 5 | //----------------------------------------------------------------------------- 6 | // A) You may edit imconfig.h (and not overwrite it when updating imgui, or maintain a patch/branch with your modifications to imconfig.h) 7 | // B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h" 8 | // If you do so you need to make sure that configuration settings are defined consistently _everywhere_ dear imgui is used, which include 9 | // the imgui*.cpp files but also _any_ of your code that uses imgui. This is because some compile-time options have an affect on data structures. 10 | // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. 11 | // Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. 12 | //----------------------------------------------------------------------------- 13 | 14 | #pragma once 15 | 16 | //---- Define assertion handler. Defaults to calling assert(). 17 | //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) 18 | //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts 19 | 20 | //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows. 21 | //#define IMGUI_API __declspec( dllexport ) 22 | //#define IMGUI_API __declspec( dllimport ) 23 | 24 | //---- Don't define obsolete functions/enums names. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. 25 | //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS 26 | 27 | //---- Don't implement demo windows functionality (ShowDemoWindow()/ShowStyleEditor()/ShowUserGuide() methods will be empty) 28 | //---- It is very strongly recommended to NOT disable the demo windows during development. Please read the comments in imgui_demo.cpp. 29 | //#define IMGUI_DISABLE_DEMO_WINDOWS 30 | 31 | //---- Don't implement some functions to reduce linkage requirements. 32 | //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. 33 | //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. 34 | //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function. 35 | //#define IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself if you don't want to link with vsnprintf. 36 | //#define IMGUI_DISABLE_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 wrapper so you can implement them yourself. Declare your prototypes in imconfig.h. 37 | //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). 38 | 39 | //---- Include imgui_user.h at the end of imgui.h as a convenience 40 | //#define IMGUI_INCLUDE_IMGUI_USER_H 41 | 42 | //---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) 43 | //#define IMGUI_USE_BGRA_PACKED_COLOR 44 | 45 | //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version 46 | // By default the embedded implementations are declared static and not available outside of imgui cpp files. 47 | //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" 48 | //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" 49 | //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION 50 | //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION 51 | 52 | //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. 53 | // This will be inlined as part of ImVec2 and ImVec4 class declarations. 54 | /* 55 | #define IM_VEC2_CLASS_EXTRA \ 56 | ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ 57 | operator MyVec2() const { return MyVec2(x,y); } 58 | 59 | #define IM_VEC4_CLASS_EXTRA \ 60 | ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ 61 | operator MyVec4() const { return MyVec4(x,y,z,w); } 62 | */ 63 | 64 | //---- Use 32-bit vertex indices (default is 16-bit) to allow meshes with more than 64K vertices. Render function needs to support it. 65 | //#define ImDrawIdx unsigned int 66 | 67 | //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. 68 | /* 69 | namespace ImGui 70 | { 71 | void MyFunction(const char* name, const MyMatrix44& v); 72 | } 73 | */ 74 | -------------------------------------------------------------------------------- /src/GlfwWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "GlfwWindow.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "IRenderer.h" 9 | 10 | namespace { 11 | std::atomic g_guiCount = 0; 12 | 13 | auto gui(GLFWwindow* w) -> GlfwWindow& { 14 | return *static_cast(glfwGetWindowUserPointer(w)); 15 | } 16 | } 17 | 18 | GlfwWindow::GlfwWindow(std::string windowTitle, render::IPlatform& platform) 19 | : m_windowTitle(std::move(windowTitle)) { 20 | // set GLFW error callback before any other GLFW function call 21 | glfwSetErrorCallback(onError); 22 | 23 | // if this is the first window, init GLFW 24 | { 25 | if (g_guiCount++ == 0) 26 | if (!glfwInit()) 27 | throw std::runtime_error("failed to init GLFW"); 28 | } 29 | 30 | m_window = platform.createWindowAndContext(m_width, m_height, windowTitle.c_str(), nullptr); 31 | 32 | onWindowCreated(); 33 | } 34 | 35 | GlfwWindow::~GlfwWindow() { 36 | glfwDestroyWindow(m_window); 37 | 38 | // if this is the last window, terminate GLFW 39 | if (g_guiCount-- == 1) 40 | glfwTerminate(); 41 | } 42 | 43 | auto GlfwWindow::handle() const -> GLFWwindow* { 44 | return m_window; 45 | } 46 | 47 | auto GlfwWindow::shouldClose() const -> bool { 48 | return glfwWindowShouldClose(m_window); 49 | } 50 | 51 | void GlfwWindow::toggleFullscreen() { 52 | // determine new monitor and window resolution 53 | GLFWmonitor* monitor = nullptr; 54 | int newWidth, newHeight; 55 | if (m_fullscreen) { 56 | // revert to window resolution 57 | newWidth = m_windowedWidth; 58 | newHeight = m_windowedHeight; 59 | } else { 60 | int x, y; 61 | glfwGetWindowPos(m_window, &x, &y); 62 | 63 | const GLFWvidmode* mode; 64 | std::tie(monitor, mode) = [&] { 65 | int count; 66 | auto monitors = glfwGetMonitors(&count); 67 | if (count < 1) 68 | throw std::runtime_error{"Failed to query monitors"}; 69 | 70 | // find monitor with biggest overlap with current window 71 | auto maxArea = 0; 72 | const GLFWvidmode* resultMode = nullptr; 73 | GLFWmonitor* resultMonitor = nullptr; 74 | 75 | for (auto i = 0; i < count; i++) { 76 | const auto& mon = monitors[i]; 77 | int mx, my; 78 | glfwGetMonitorPos(mon, &mx, &my); 79 | const auto mode = glfwGetVideoMode(mon); 80 | if (!mode) 81 | throw std::runtime_error{"Failed to query monitor video mode"}; 82 | const auto x1 = std::max(mx, x); 83 | const auto x2 = std::min(mx + mode->width, x + m_width); 84 | const auto y1 = std::max(my, y); 85 | const auto y2 = std::min(my + mode->height, y + m_height); 86 | const auto area = (x2 - x1) * (y2 - y1); 87 | if (area > maxArea) { 88 | maxArea = area; 89 | resultMode = mode; 90 | resultMonitor = mon; 91 | } 92 | } 93 | assert(resultMonitor); 94 | assert(resultMode); 95 | return std::make_pair(resultMonitor, resultMode); 96 | }(); 97 | 98 | // store original window resolution and position 99 | m_windowedWidth = m_width; 100 | m_windowedHeight = m_height; 101 | m_windowedX = x; 102 | m_windowedY = y; 103 | 104 | // apply monitor resolution 105 | newWidth = mode->width; 106 | newHeight = mode->height; 107 | } 108 | 109 | // create new window, sharing resources from previous window, and destroy old one 110 | const auto newWindow = glfwCreateWindow(newWidth, newHeight, m_windowTitle.c_str(), monitor, m_window); 111 | glfwSetWindowPos(newWindow, m_windowedX, m_windowedY); 112 | glfwDestroyWindow(m_window); 113 | m_window = newWindow; 114 | 115 | // we set m_width and m_height after the windows have been created and closed as these actions trigger resize events which may corrupt m_width and m_height 116 | m_width = newWidth; 117 | m_height = newHeight; 118 | 119 | onWindowCreated(); 120 | 121 | m_fullscreen = !m_fullscreen; 122 | } 123 | 124 | void GlfwWindow::toggleStereo() { 125 | glfwWindowHint(GLFW_STEREO, !m_stereo); 126 | 127 | // if we are fullscreen, create the new window in fullscreen mode on the same monitor again 128 | auto monitor = glfwGetWindowMonitor(m_window); 129 | 130 | // copy m_width and m_height as creating and closing the windows triggers resize events which change m_width and m_height 131 | const auto w = m_width; 132 | const auto h = m_height; 133 | 134 | const auto newWindow = glfwCreateWindow(w, h, m_windowTitle.c_str(), monitor, m_window); 135 | if (!m_stereo && !newWindow) 136 | throw std::runtime_error("Failed to switch to stereo mode. Maybe your system does not support stereo rendering?"); 137 | 138 | glfwSetWindowPos(newWindow, m_windowedX, m_windowedY); 139 | glfwDestroyWindow(m_window); 140 | 141 | m_width = w; 142 | m_height = h; 143 | 144 | m_stereo = !m_stereo; 145 | } 146 | 147 | void GlfwWindow::onError(int error, const char* description) { 148 | std::cerr << "GLFW error " << error << ": " << description << '\n'; 149 | } 150 | 151 | void GlfwWindow::onResize(GLFWwindow* window, int width, int height) { 152 | auto& g = gui(window); 153 | g.m_width = width; 154 | g.m_height = height; 155 | g.onResize(width, height); 156 | } 157 | 158 | void GlfwWindow::onMouseButton(GLFWwindow* window, int button, int action, int modifiers) { 159 | gui(window).onMouseButton(button, action, modifiers); 160 | } 161 | 162 | void GlfwWindow::onMouseMove(GLFWwindow* window, double dx, double dy) { 163 | gui(window).onMouseMove(dx, dy); 164 | } 165 | 166 | void GlfwWindow::onMouseWheel(GLFWwindow* window, double xOffset, double yOffset) { 167 | gui(window).onMouseWheel(xOffset, yOffset); 168 | } 169 | 170 | void GlfwWindow::onKey(GLFWwindow* window, int key, int scancode, int action, int mods) { 171 | gui(window).onKey(key, scancode, action, mods); 172 | } 173 | 174 | void GlfwWindow::onChar(GLFWwindow* window, unsigned int codepoint) { 175 | gui(window).onChar(codepoint); 176 | } 177 | 178 | void GlfwWindow::onWindowCreated() { 179 | if (m_window == nullptr) { 180 | throw std::runtime_error("failed to open window"); 181 | return; 182 | } 183 | 184 | glfwSetWindowUserPointer(m_window, this); 185 | 186 | // after the window has been created, we have a context 187 | glfwMakeContextCurrent(m_window); 188 | 189 | // configure GLFW context 190 | glfwSwapInterval(0); // vsync 191 | 192 | glfwSetWindowSizeCallback(m_window, onResize); 193 | glfwSetMouseButtonCallback(m_window, onMouseButton); 194 | glfwSetCursorPosCallback(m_window, onMouseMove); 195 | glfwSetScrollCallback(m_window, onMouseWheel); 196 | glfwSetKeyCallback(m_window, onKey); 197 | glfwSetCharCallback(m_window, onChar); 198 | } 199 | -------------------------------------------------------------------------------- /src/bspdef.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "mathlib.h" 4 | #include 5 | 6 | namespace bsp30 { 7 | constexpr auto MAX_MAP_HULLS = 4; 8 | 9 | constexpr auto MAX_MAP_MODELS = 400; 10 | constexpr auto MAX_MAP_BRUSHES = 4096; 11 | constexpr auto MAX_MAP_ENTITIES = 1024; 12 | constexpr auto MAX_MAP_ENTSTRING = (128 * 1024); 13 | 14 | constexpr auto MAX_MAP_PLANES = 32767; 15 | constexpr auto MAX_MAP_NODES = 32767; // because negative shorts are leaves 16 | constexpr auto MAX_MAP_CLIPNODES = 32767; 17 | constexpr auto MAX_MAP_LEAFS = 8192; 18 | constexpr auto MAX_MAP_VERTS = 65535; 19 | constexpr auto MAX_MAP_FACES = 65535; 20 | constexpr auto MAX_MAP_MARKSURFACES = 65535; 21 | constexpr auto MAX_MAP_TEXINFO = 8192; 22 | constexpr auto MAX_MAP_EDGES = 256000; 23 | constexpr auto MAX_MAP_SURFEDGES = 512000; 24 | constexpr auto MAX_MAP_TEXTURES = 512; 25 | constexpr auto MAX_MAP_MIPTEX = 0x200000; 26 | constexpr auto MAX_MAP_LIGHTING = 0x200000; 27 | constexpr auto MAX_MAP_VISIBILITY = 0x200000; 28 | 29 | constexpr auto MAX_MAP_PORTALS = 65536; 30 | 31 | constexpr auto MAX_KEY = 32; 32 | constexpr auto MAX_VALUE = 1024; 33 | 34 | // BSP-30 files contain these lumps 35 | enum LumpType { 36 | LUMP_ENTITIES = 0, 37 | LUMP_PLANES = 1, 38 | LUMP_TEXTURES = 2, 39 | LUMP_VERTEXES = 3, 40 | LUMP_VISIBILITY = 4, 41 | LUMP_NODES = 5, 42 | LUMP_TEXINFO = 6, 43 | LUMP_FACES = 7, 44 | LUMP_LIGHTING = 8, 45 | LUMP_CLIPNODES = 9, 46 | LUMP_LEAFS = 10, 47 | LUMP_MARKSURFACES = 11, 48 | LUMP_EDGES = 12, 49 | LUMP_SURFEDGES = 13, 50 | LUMP_MODELS = 14, 51 | HEADER_LUMPS = 15, 52 | }; 53 | 54 | // Leaf content values 55 | enum ContentType { 56 | CONTENTS_EMPTY = -1, 57 | CONTENTS_SOLID = -2, 58 | CONTENTS_WATER = -3, 59 | CONTENTS_SLIME = -4, 60 | CONTENTS_LAVA = -5, 61 | CONTENTS_SKY = -6, 62 | CONTENTS_ORIGIN = -7, 63 | CONTENTS_CLIP = -8, 64 | CONTENTS_CURRENT_0 = -9, 65 | CONTENTS_CURRENT_90 = -10, 66 | CONTENTS_CURRENT_180 = -11, 67 | CONTENTS_CURRENT_270 = -12, 68 | CONTENTS_CURRENT_UP = -13, 69 | CONTENTS_CURRENT_DOWN = -14, 70 | CONTENTS_TRANSLUCENT = -15, 71 | }; 72 | 73 | // plane types 74 | enum PlaneType { 75 | // plane is perpendicular to given axis 76 | PLANE_X = 0, 77 | PLANE_Y = 1, 78 | PLANE_Z = 2, 79 | // non-axial plane is snapped to the nearest 80 | PLANE_ANYX = 3, 81 | PLANE_ANYY = 4, 82 | PLANE_ANYZ = 5, 83 | }; 84 | 85 | // render modes 86 | enum RenderMode { 87 | RENDER_MODE_NORMAL = 0, 88 | RENDER_MODE_COLOR = 1, 89 | RENDER_MODE_TEXTURE = 2, 90 | RENDER_MODE_GLOW = 3, 91 | RENDER_MODE_SOLID = 4, 92 | RENDER_MODE_ADDITIVE = 5, 93 | }; 94 | 95 | /// @brief Describes a lump in the BSP file 96 | /// To read the different lumps from the given BSP file, every lump entry file states the beginning of each lump as an offset relativly to the beginning of the file. Additionally, the lump entry also gives the length of the addressed lump in bytes. 97 | struct Lump { 98 | int32_t offset; ///< File offset to data 99 | int32_t length; ///< Length of data 100 | }; 101 | 102 | /// @brief The BSP file header 103 | /// The file header begins with an 32bit integer containing the file version of the BSP file (the magic number). This should be 30 for a valid BSP file used by the Half-Life Engine. 104 | /// Subseqently, there is an array of entries for the so-called lumps. A lump is more or less a section of the file containing a specific type of data. The lump entries in the file header address these lumps, accessed by the 15 predefined indexes. 105 | struct Header { 106 | int32_t version; ///< Version number, must be 30 for a valid HL BSP file 107 | Lump lump[HEADER_LUMPS]; ///< Stores the directory of lumps. 108 | }; 109 | 110 | /// @brief Describes a node of the BSP Tree 111 | struct Node { 112 | uint32_t planeIndex; // Index into planes lump 113 | int16_t childIndex[2]; // If > 0, then indices into Nodes otherwise bitwise inverse indices into Leafs 114 | int16_t lower[3], upper[3]; // Defines bounding box 115 | uint16_t firstFace, faceCount; // Index and count into BSPFACES array 116 | }; 117 | 118 | // Leafs lump contains leaf structures 119 | struct Leaf { 120 | int32_t content; // Contents enumeration, see #defines 121 | int32_t visOffset; // Offset into the compressed visibility lump 122 | int16_t lower[3], upper[3]; // Defines bounding box 123 | uint16_t firstMarkSurface, markSurfaceCount; // Index and count into MarkSurface array 124 | uint8_t ambientLevels[4]; // Ambient sound levels 125 | }; 126 | 127 | // Leaves index into marksurfaces, which index into faces 128 | using MarkSurface = uint16_t; 129 | 130 | // Planes lump contains plane structures 131 | struct Plane { 132 | glm::vec3 normal; // The planes normal vector 133 | float dist{}; // Plane equation is: normal * X = dist 134 | int32_t type{}; // Plane type, see #defines 135 | }; 136 | 137 | // Vertex lump is an array of float triples (glm::vec3) 138 | using Vertex = glm::vec3; 139 | 140 | // Edge struct contains the begining and end vertex for each edge 141 | struct Edge { 142 | uint16_t vertexIndex[2]; // Indices into vertex array 143 | }; 144 | 145 | // Faces are equal to the polygons that make up the world 146 | struct Face { 147 | uint16_t planeIndex; // Index of the plane the face is parallel to 148 | uint16_t planeSide; // Set if different normals orientation 149 | uint32_t firstEdgeIndex; // Index of the first edge (in the surfedge array) 150 | uint16_t edgeCount; // Number of consecutive surfedges 151 | uint16_t textureInfo; // Index of the texture info structure 152 | uint8_t styles[4]; // Specify lighting styles 153 | // styles[0] // type of lighting, for the face 154 | // styles[1] // from 0xFF (dark) to 0 (bright) 155 | // styles[2], styles[3] // two additional light models 156 | uint32_t lightmapOffset; // Offsets into the raw lightmap data 157 | }; 158 | 159 | // Surfedges lump is array of signed int indices into edge lump, where a negative index indicates 160 | // using the referenced edge in the opposite direction. Faces index into surfEdges, which index 161 | // into edges, which finally index into vertices. 162 | using SurfEdge = int32_t; 163 | 164 | // Textures lump begins with a header, followed by offsets to MipTex structures, then MipTex structures 165 | struct TextureHeader { 166 | uint32_t mipTextureCount; // Number of MipTex structures 167 | }; 168 | 169 | // 32-bit offsets (within texture lump) to (mipTextureCount) MipTex structures 170 | using MipTexOffset = int32_t; 171 | 172 | // MipTex structures which defines a Texture 173 | constexpr auto MAXTEXTURENAME = 16; 174 | constexpr auto MIPLEVELS = 4; 175 | struct MipTex { 176 | char name[MAXTEXTURENAME]; // Name of texture, for reference from external WAD file 177 | uint32_t width, height; // Extends of the texture 178 | uint32_t offsets[MIPLEVELS]; // Offsets to MIPLEVELS texture mipmaps, if 0 texture data is stored in an external WAD file 179 | }; 180 | 181 | // Texinfo lump contains texinfo structures 182 | struct TextureInfo { 183 | glm::vec3 s; // 1st row of texture matrix 184 | float sShift{}; // Texture shift in s direction 185 | glm::vec3 t; // 2nd row of texture matrix - multiply 1st and 2nd by vertex to get texture coordinates 186 | float tShift{}; // Texture shift in t direction 187 | uint32_t miptexIndex{}; // Index into textures array 188 | uint32_t flags{}; // Texture flags, seems to always be 0 189 | }; 190 | 191 | struct Model { 192 | glm::vec3 lower, upper; // Defines bounding box 193 | glm::vec3 origin; // Coordinates to move the coordinate system before drawing the model 194 | int32_t headNodesIndex[MAX_MAP_HULLS]{}; // Index into nodes array 195 | int32_t visLeaves{}; // No idea, sometimes called numleafs in HLDS 196 | int32_t firstFace{}, faceCount{}; // Index and count into face array 197 | }; 198 | 199 | struct ClipNode { 200 | int32_t planeIndex; // Index into planes 201 | int16_t childIndex[2]; // negative numbers are contents behind and in front of the plane 202 | }; 203 | } 204 | -------------------------------------------------------------------------------- /src/Window.cpp: -------------------------------------------------------------------------------- 1 | #include "Window.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "Bsp.h" 9 | #include "BspRenderable.h" 10 | #include "HudRenderable.h" 11 | #include "Image.h" 12 | #include "global.h" 13 | 14 | namespace { 15 | constexpr auto WINDOW_CAPTION = "HL BSP"; 16 | 17 | constexpr auto cl_sidespeed = 400.0f; 18 | constexpr auto cl_forwardspeed = 400.0f; 19 | constexpr auto cl_downspeed = 400.0f; 20 | constexpr auto cl_pitchup = 89.0f; 21 | constexpr auto cl_pitchdown = 89.0f; 22 | 23 | constexpr auto m_yaw = 0.022f; 24 | constexpr auto m_pitch = 0.022f; 25 | constexpr auto sensitivity = 5.0f; 26 | } 27 | 28 | Window::Window(render::IPlatform& platform, Bsp& bsp) 29 | : GlfwWindow(WINDOW_CAPTION, platform), camera(pmove), hud(camera, timer), bsp(bsp), m_platform(platform) { 30 | 31 | pmove.friction = 4; 32 | for (auto* ladder : bsp.FindEntities("func_ladder")) { 33 | if (auto* modelStr = ladder->findProperty("model")) { 34 | const auto modelIndex = std::stoi(modelStr->substr(1)); 35 | const auto& model = bsp.models.at(modelIndex); 36 | pmove.ladders.push_back(&model); 37 | } 38 | } 39 | pmove.physents.push_back(&bsp.models.front()); // add at least the bsp model, TODO add other entities as well 40 | 41 | IMGUI_CHECKVERSION(); 42 | ImGui::CreateContext(); 43 | ImGui_ImplGlfw_Init(handle(), false, GlfwClientApi_Unknown); 44 | 45 | m_renderer = platform.createRenderer(); 46 | 47 | m_renderables.emplace_back(std::make_unique(*m_renderer, bsp, camera)); 48 | m_renderables.emplace_back(std::make_unique(*m_renderer, hud)); 49 | 50 | onResize(m_width, m_height); 51 | 52 | // place camera at spawn 53 | if (const auto info_player_start = bsp.FindEntity("info_player_start")) { 54 | if (auto origin = info_player_start->findProperty("origin")) { 55 | auto& o = camera.position(); 56 | std::istringstream(*origin) >> o.x >> o.y >> o.z; 57 | } 58 | 59 | if (auto angle = info_player_start->findProperty("angle")) { 60 | camera.yaw() = std::stof(*angle); 61 | } 62 | } 63 | } 64 | 65 | Window::~Window() { 66 | m_renderer = nullptr; 67 | ImGui_ImplGlfw_Shutdown(); 68 | ImGui::DestroyContext(); 69 | } 70 | 71 | auto Window::createMove() -> UserCommand { 72 | UserCommand cmd{}; 73 | if (glfwGetKey(handle(), GLFW_KEY_SPACE) == GLFW_PRESS) { 74 | //cmd.upmove += cl_downspeed; 75 | cmd.buttons |= IN_JUMP; 76 | } 77 | //if (glfwGetKey(handle(), GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) 78 | // cmd.upmove -= cl_downspeed; 79 | if (glfwGetKey(handle(), GLFW_KEY_W) == GLFW_PRESS) { 80 | cmd.forwardmove += cl_forwardspeed; 81 | cmd.buttons |= IN_FORWARD; 82 | } 83 | if (glfwGetKey(handle(), GLFW_KEY_S) == GLFW_PRESS) { 84 | cmd.forwardmove -= cl_forwardspeed; 85 | cmd.buttons |= IN_BACK; 86 | } 87 | if (glfwGetKey(handle(), GLFW_KEY_A) == GLFW_PRESS) { 88 | cmd.sidemove -= cl_sidespeed; 89 | cmd.buttons |= IN_MOVELEFT; 90 | } 91 | if (glfwGetKey(handle(), GLFW_KEY_D) == GLFW_PRESS) { 92 | cmd.sidemove += cl_sidespeed; 93 | cmd.buttons |= IN_MOVERIGHT; 94 | } 95 | 96 | return cmd; 97 | } 98 | 99 | void Window::mouseMove(UserCommand& cmd) { 100 | glm::dvec2 delta{0, 0}; 101 | if (m_captureMouse) { 102 | glm::dvec2 pos; 103 | glfwGetCursorPos(handle(), &pos.x, &pos.y); 104 | delta = m_mouseDownPos - pos; 105 | glfwSetCursorPos(handle(), m_mouseDownPos.x, m_mouseDownPos.y); 106 | } 107 | 108 | auto yaw = camera.yaw(); 109 | auto pitch = camera.pitch(); 110 | yaw += m_yaw * delta.x * sensitivity; 111 | pitch += m_pitch * -delta.y * sensitivity; // pitch in halflife is negative when looking upward. cf: https://www.jwchong.com/hl/player.html 112 | if (pitch > cl_pitchdown) 113 | pitch = cl_pitchdown; 114 | if (pitch < -cl_pitchup) 115 | pitch = -cl_pitchup; 116 | 117 | cmd.viewangles.x = pitch; 118 | cmd.viewangles.y = yaw; 119 | } 120 | 121 | void Window::update() { 122 | timer.Tick(); 123 | 124 | std::stringstream windowText; 125 | windowText << WINDOW_CAPTION << " - " << std::setprecision(1) << std::fixed << timer.TPS << " FPS"; 126 | glfwSetWindowTitle(handle(), windowText.str().c_str()); 127 | 128 | auto cmd = createMove(); 129 | mouseMove(cmd); 130 | cmd.frameTime = timer.interval; 131 | 132 | pmove.cmd = cmd; 133 | pmove.movetype = static_cast(global::moveType); 134 | pmove.usehull = global::hullIndex; 135 | playerMove(pmove); 136 | } 137 | 138 | void Window::draw() { 139 | ImGui_ImplGlfw_NewFrame(); 140 | 141 | m_settings.projection = camera.projectionMatrix(); 142 | m_settings.view = camera.viewMatrix(); 143 | m_settings.pitch = camera.pitch(); 144 | m_settings.yaw = camera.yaw(); 145 | 146 | m_renderer->clear(); 147 | for (auto& renderable : m_renderables) 148 | renderable->render(m_settings); 149 | if (global::renderCoords) 150 | m_renderer->renderCoords(m_settings.projection * m_settings.view); 151 | } 152 | 153 | void Window::onResize(int width, int height) { 154 | if (height == 0) 155 | height = 1; 156 | 157 | std::clog << "Window dimensions changed to " << width << " x " << height << "\n"; 158 | 159 | camera.viewportWidth = width; 160 | camera.viewportHeight = height; 161 | 162 | m_renderer->resizeViewport(width, height); 163 | } 164 | 165 | void Window::onMouseButton(int button, int action, int modifiers) { 166 | ImGui_ImplGlfw_MouseButtonCallback(handle(), button, action, modifiers); 167 | 168 | if (action == GLFW_PRESS) { 169 | switch (button) { 170 | case GLFW_MOUSE_BUTTON_RIGHT: 171 | m_captureMouse = true; 172 | glfwGetCursorPos(handle(), &m_mouseDownPos.x, &m_mouseDownPos.y); 173 | glfwSetInputMode(handle(), GLFW_CURSOR, GLFW_CURSOR_HIDDEN); 174 | break; 175 | } 176 | } 177 | 178 | if (action == GLFW_RELEASE) { 179 | switch (button) { 180 | case GLFW_MOUSE_BUTTON_RIGHT: 181 | m_captureMouse = false; 182 | glfwSetInputMode(handle(), GLFW_CURSOR, GLFW_CURSOR_NORMAL); 183 | break; 184 | } 185 | } 186 | } 187 | 188 | void Window::onMouseMove(double xOffset, double yOffset) {} 189 | 190 | void Window::onMouseWheel(double xOffset, double yOffset) { 191 | ImGui_ImplGlfw_ScrollCallback(handle(), xOffset, yOffset); 192 | } 193 | 194 | void Window::onKey(int key, int scancode, int action, int mods) { 195 | ImGui_ImplGlfw_KeyCallback(handle(), key, scancode, action, mods); 196 | 197 | if (action == GLFW_PRESS) { 198 | switch (key) { 199 | //case GLFW_KEY_TAB: 200 | // camera.moveSensitivity = CAMERA_MOVE_SENS * 3.0f; 201 | // break; 202 | //case GLFW_KEY_LEFT_SHIFT: 203 | // camera.moveSensitivity = CAMERA_MOVE_SENS / 3.0f; 204 | // break; 205 | case GLFW_KEY_F1: 206 | if (m_fullscreen) 207 | std::clog << "Changing to windowed mode ...\n"; 208 | else 209 | std::clog << "Changing to fullscreen ...\n"; 210 | toggleFullscreen(); 211 | break; 212 | 213 | case GLFW_KEY_F5: { 214 | const auto img = m_renderer->screenshot(); 215 | 216 | std::string filename; 217 | for (auto i = 1;; i++) 218 | if (!fs::exists(filename = "screenshots/Screenshot" + std::to_string(i) + ".bmp")) 219 | break; 220 | 221 | img.Save(filename); 222 | break; 223 | } 224 | 225 | case GLFW_KEY_C: 226 | global::renderCoords = !global::renderCoords; 227 | if (global::renderCoords) 228 | hud.print("coords enabled"); 229 | else 230 | hud.print("coords disabled"); 231 | break; 232 | 233 | case GLFW_KEY_H: 234 | global::renderHUD = !global::renderHUD; 235 | if (global::renderHUD) 236 | hud.print("hud enabled"); 237 | else 238 | hud.print("hud disabled"); 239 | break; 240 | 241 | case GLFW_KEY_L: 242 | global::lightmaps = !global::lightmaps; 243 | if (global::lightmaps) 244 | hud.print("lightmaps enabled"); 245 | else 246 | hud.print("lightmaps disabled"); 247 | break; 248 | 249 | case GLFW_KEY_N: 250 | global::nightvision = !global::nightvision; 251 | if (global::nightvision) 252 | hud.print("nightvision enabled"); 253 | else 254 | hud.print("nightvision disabled"); 255 | break; 256 | 257 | case GLFW_KEY_T: 258 | global::textures = !global::textures; 259 | if (global::textures) 260 | hud.print("textures enabled"); 261 | else 262 | hud.print("textures disabled"); 263 | break; 264 | 265 | case GLFW_KEY_O: 266 | global::renderLeafOutlines = !global::renderLeafOutlines; 267 | if (global::renderLeafOutlines) 268 | hud.print("leaf outlines enabled"); 269 | else 270 | hud.print("leaf outlines disabled"); 271 | break; 272 | 273 | //case GLFW_KEY_P: 274 | // global::polygons = !global::polygons; 275 | // if (global::polygons) { 276 | // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 277 | // hud.print("polygon mode set to line"); 278 | // } else { 279 | // glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 280 | // hud.print("polygon mode set to fill"); 281 | // } 282 | // break; 283 | 284 | case GLFW_KEY_V: 285 | global::flashlight = !global::flashlight; 286 | if (global::flashlight) 287 | hud.print("flashlight enabled"); 288 | else 289 | hud.print("flashlight disabled"); 290 | break; 291 | 292 | case GLFW_KEY_1: 293 | global::renderSkybox = !global::renderSkybox; 294 | if (global::renderSkybox) 295 | hud.print("skybox enabled"); 296 | else 297 | hud.print("skybox disabled"); 298 | break; 299 | 300 | case GLFW_KEY_2: 301 | global::renderStaticBSP = !global::renderStaticBSP; 302 | if (global::renderStaticBSP) 303 | hud.print("static geometry enabled"); 304 | else 305 | hud.print("static geometry disabled"); 306 | break; 307 | 308 | case GLFW_KEY_3: 309 | global::renderBrushEntities = !global::renderBrushEntities; 310 | if (global::renderBrushEntities) 311 | hud.print("entities enabled"); 312 | else 313 | hud.print("entities disabled"); 314 | break; 315 | 316 | case GLFW_KEY_4: 317 | global::renderDecals = !global::renderDecals; 318 | if (global::renderDecals) 319 | hud.print("decals enabled"); 320 | else 321 | hud.print("decals disabled"); 322 | break; 323 | } 324 | } 325 | 326 | //if (action == GLFW_RELEASE) { 327 | // switch (key) { 328 | // case GLFW_KEY_TAB: 329 | // case GLFW_KEY_LEFT_SHIFT: 330 | // camera.moveSensitivity = CAMERA_MOVE_SENS; 331 | // break; 332 | // } 333 | // return; 334 | //} 335 | } 336 | 337 | void Window::onChar(unsigned int codepoint) { 338 | ImGui_ImplGlfw_CharCallback(handle(), codepoint); 339 | } 340 | -------------------------------------------------------------------------------- /src/Wad.cpp: -------------------------------------------------------------------------------- 1 | #include "Wad.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "IO.h" 11 | 12 | namespace { 13 | const auto sqrt2 = std::sqrt(2.0); 14 | 15 | void ApplyAlphaSections(Image& pTex) { 16 | std::vector pRGBTexture(pTex.width * pTex.height * 4, 0x00); 17 | 18 | // Color pRGBTexture totally blue 19 | for (int i = 0; i < pTex.height * pTex.width; i++) 20 | pRGBTexture[i * 4 + 2] = 255; 21 | 22 | for (int y = 0; y < pTex.height; y++) { 23 | for (int x = 0; x < pTex.width; x++) { 24 | int index = y * pTex.width + x; 25 | 26 | if ((pTex.data[index * 4] == 0) && (pTex.data[index * 4 + 1] == 0) && (pTex.data[index * 4 + 2] == 255)) { 27 | // Blue color signifies a transparent portion of the texture. zero alpha for blending and 28 | // to get rid of blue edges choose the average color of the nearest non blue pixels 29 | 30 | //First set pixel black and transparent 31 | pTex.data[index * 4 + 2] = 0; 32 | pTex.data[index * 4 + 3] = 0; 33 | 34 | int count = 0; 35 | unsigned int RGBColorSum[3] = {0, 0, 0}; 36 | 37 | //left above pixel 38 | if ((x > 0) && (y > 0)) { 39 | int iPixel = ((y - 1) * pTex.width + (x - 1)) * 4; 40 | if (!((pTex.data[iPixel] == 0) && (pTex.data[iPixel + 1] == 0) && (pTex.data[iPixel + 2] == 255))) { 41 | RGBColorSum[0] += (unsigned int)((float)pTex.data[iPixel + 0] * sqrt2); 42 | RGBColorSum[1] += (unsigned int)((float)pTex.data[iPixel + 1] * sqrt2); 43 | RGBColorSum[2] += (unsigned int)((float)pTex.data[iPixel + 2] * sqrt2); 44 | count++; 45 | } 46 | } 47 | 48 | //above pixel 49 | if ((x >= 0) && (y > 0)) { 50 | int iPixel = ((y - 1) * pTex.width + x) * 4; 51 | if (!((pTex.data[iPixel] == 0) && (pTex.data[iPixel + 1] == 0) && (pTex.data[iPixel + 2] == 255))) { 52 | RGBColorSum[0] += pTex.data[iPixel]; 53 | RGBColorSum[1] += pTex.data[iPixel + 1]; 54 | RGBColorSum[2] += pTex.data[iPixel + 2]; 55 | count++; 56 | } 57 | } 58 | 59 | //right above pixel 60 | if ((x < pTex.width - 1) && (y > 0)) { 61 | int iPixel = ((y - 1) * pTex.width + (x + 1)) * 4; 62 | if (!((pTex.data[iPixel] == 0) && (pTex.data[iPixel + 1] == 0) && (pTex.data[iPixel + 2] == 255))) { 63 | RGBColorSum[0] += (unsigned int)((float)pTex.data[iPixel + 0] * sqrt2); 64 | RGBColorSum[1] += (unsigned int)((float)pTex.data[iPixel + 1] * sqrt2); 65 | RGBColorSum[2] += (unsigned int)((float)pTex.data[iPixel + 2] * sqrt2); 66 | count++; 67 | } 68 | } 69 | 70 | //left pixel 71 | if (x > 0) { 72 | int iPixel = (y * pTex.width + (x - 1)) * 4; 73 | if (!((pTex.data[iPixel] == 0) && (pTex.data[iPixel + 1] == 0) && (pTex.data[iPixel + 2] == 255))) { 74 | RGBColorSum[0] += pTex.data[iPixel]; 75 | RGBColorSum[1] += pTex.data[iPixel + 1]; 76 | RGBColorSum[2] += pTex.data[iPixel + 2]; 77 | count++; 78 | } 79 | } 80 | 81 | //right pixel 82 | if (x < pTex.width - 1) { 83 | int iPixel = (y * pTex.width + (x + 1)) * 4; 84 | if (!((pTex.data[iPixel] == 0) && (pTex.data[iPixel + 1] == 0) && (pTex.data[iPixel + 2] == 255))) { 85 | RGBColorSum[0] += pTex.data[iPixel]; 86 | RGBColorSum[1] += pTex.data[iPixel + 1]; 87 | RGBColorSum[2] += pTex.data[iPixel + 2]; 88 | count++; 89 | } 90 | } 91 | 92 | //left underneath pixel 93 | if ((x > 0) && (y < pTex.height - 1)) { 94 | int iPixel = ((y + 1) * pTex.width + (x - 1)) * 4; 95 | if (!((pTex.data[iPixel] == 0) && (pTex.data[iPixel + 1] == 0) && (pTex.data[iPixel + 2] == 255))) { 96 | RGBColorSum[0] += (unsigned int)((float)pTex.data[iPixel + 0] * sqrt2); 97 | RGBColorSum[1] += (unsigned int)((float)pTex.data[iPixel + 1] * sqrt2); 98 | RGBColorSum[2] += (unsigned int)((float)pTex.data[iPixel + 2] * sqrt2); 99 | count++; 100 | } 101 | } 102 | 103 | //underneath pixel 104 | if ((x >= 0) && (y < pTex.height - 1)) { 105 | int iPixel = ((y + 1) * pTex.width + x) * 4; 106 | if (!((pTex.data[iPixel] == 0) && (pTex.data[iPixel + 1] == 0) && (pTex.data[iPixel + 2] == 255))) { 107 | RGBColorSum[0] += pTex.data[iPixel]; 108 | RGBColorSum[1] += pTex.data[iPixel + 1]; 109 | RGBColorSum[2] += pTex.data[iPixel + 2]; 110 | count++; 111 | } 112 | } 113 | 114 | //right underneath pixel 115 | if ((x < pTex.width - 1) && (y < pTex.height - 1)) { 116 | int iPixel = ((y + 1) * pTex.width + (x + 1)) * 4; 117 | if (!((pTex.data[iPixel] == 0) && (pTex.data[iPixel + 1] == 0) && (pTex.data[iPixel + 2] == 255))) { 118 | RGBColorSum[0] += (unsigned int)((float)pTex.data[iPixel + 0] * sqrt2); 119 | RGBColorSum[1] += (unsigned int)((float)pTex.data[iPixel + 1] * sqrt2); 120 | RGBColorSum[2] += (unsigned int)((float)pTex.data[iPixel + 2] * sqrt2); 121 | count++; 122 | } 123 | } 124 | 125 | if (count > 0) { 126 | RGBColorSum[0] /= count; 127 | RGBColorSum[1] /= count; 128 | RGBColorSum[2] /= count; 129 | 130 | pRGBTexture[index * 4 + 0] = RGBColorSum[0]; 131 | pRGBTexture[index * 4 + 1] = RGBColorSum[1]; 132 | pRGBTexture[index * 4 + 2] = RGBColorSum[2]; 133 | } 134 | } 135 | } 136 | } 137 | 138 | //Merge pTex and pRGBTexture 139 | for (int y = 0; y < pTex.height; y++) { 140 | for (int x = 0; x < pTex.width; x++) { 141 | int index = y * pTex.width + x; 142 | 143 | if ((pRGBTexture[index * 4] != 0) || (pRGBTexture[index * 4 + 1] != 0) || (pRGBTexture[index * 4 + 2] != 255) || (pRGBTexture[index * 4 + 3] != 0)) 144 | memcpy(&pTex.data[index * 4], &pRGBTexture[index * 4], sizeof(unsigned char) * 4); 145 | } 146 | } 147 | } 148 | 149 | auto texNameLess(const char* a, const char* b) { 150 | #ifdef WIN32 151 | return _stricmp(a, b) < 0; 152 | #else 153 | return strcasecmp(a, b) < 0; 154 | #endif 155 | } 156 | 157 | auto texNameEqual(const char* a, const char* b) { 158 | #ifdef WIN32 159 | return _stricmp(a, b) == 0; 160 | #else 161 | return strcasecmp(a, b) == 0; 162 | #endif 163 | } 164 | } 165 | 166 | Wad::Wad(const fs::path& path) 167 | : wadFile(path, std::ios::binary) { 168 | if (!wadFile) 169 | throw std::ios::failure("Failed to open file " + path.string() + " for reading"); 170 | wadFile.exceptions(std::ios::badbit | std::ios::failbit); 171 | LoadDirectory(); 172 | } 173 | 174 | auto Wad::loadTexture(const char* name) -> std::optional { 175 | auto rawTex = GetTexture(name); 176 | if (rawTex.empty()) 177 | return {}; 178 | 179 | MipmapTexture tex; 180 | CreateMipTexture(rawTex, tex); 181 | return tex; 182 | } 183 | 184 | auto Wad::LoadDecalTexture(const char* name) -> std::optional { 185 | auto rawTex = GetTexture(name); 186 | if (rawTex.empty()) 187 | return {}; 188 | 189 | MipmapTexture tex; 190 | CreateDecalTexture(rawTex, tex); 191 | return tex; 192 | } 193 | 194 | void Wad::LoadDirectory() { 195 | auto header = read(wadFile); 196 | 197 | // check magic 198 | if (header.magic[0] != 'W' || header.magic[1] != 'A' || header.magic[2] != 'D' || (header.magic[3] != '2' && header.magic[3] != '3')) 199 | throw std::ios::failure("Unknown WAD magic number: " + std::string(header.magic, 4)); 200 | 201 | // read and sort directory 202 | dirEntries.resize(header.nDir); 203 | wadFile.seekg(header.dirOffset); 204 | readVector(wadFile, dirEntries); 205 | 206 | std::sort(begin(dirEntries), end(dirEntries), [](const WadDirEntry& a, const WadDirEntry& b) { 207 | return texNameLess(a.name, b.name); 208 | }); 209 | } 210 | 211 | auto Wad::GetTexture(const char* name) -> std::vector { 212 | const auto it = std::lower_bound(begin(dirEntries), end(dirEntries), name, [](const WadDirEntry& e, const char* name) { 213 | return texNameLess(e.name, name); 214 | }); 215 | 216 | if (it == end(dirEntries) || !texNameEqual(it->name, name)) 217 | return {}; 218 | 219 | // we can only handle uncompressed formats 220 | if (it->compressed) 221 | throw std::runtime_error("WAD texture cannot be loaded. Cannot read compressed items"); 222 | 223 | wadFile.seekg(it->nFilePos); 224 | return readVector(wadFile, it->nSize); 225 | } 226 | 227 | void Wad::CreateMipTexture(const std::vector& rawTexture, MipmapTexture& mipTex) { 228 | const auto* rawMipTex = (bsp30::MipTex*)rawTexture.data(); 229 | 230 | auto width = rawMipTex->width; 231 | auto height = rawMipTex->height; 232 | const auto palOffset = rawMipTex->offsets[3] + (width / 8) * (height / 8) + 2; 233 | const auto* palette = rawTexture.data() + palOffset; 234 | 235 | for (int level = 0; level < bsp30::MIPLEVELS; level++) { 236 | const auto* pixel = &(rawTexture[rawMipTex->offsets[level]]); 237 | 238 | auto& img = mipTex.Img[level]; 239 | img.channels = 4; 240 | img.width = width; 241 | img.height = height; 242 | img.data.resize(width * height * 4); 243 | 244 | for (int i = 0; i < height * width; i++) { 245 | int palIndex = pixel[i] * 3; 246 | 247 | img.data[i * 4 + 0] = palette[palIndex + 0]; 248 | img.data[i * 4 + 1] = palette[palIndex + 1]; 249 | img.data[i * 4 + 2] = palette[palIndex + 2]; 250 | img.data[i * 4 + 3] = 255; 251 | } 252 | 253 | ApplyAlphaSections(mipTex.Img[level]); 254 | 255 | width /= 2; 256 | height /= 2; 257 | } 258 | } 259 | 260 | void Wad::CreateDecalTexture(const std::vector& rawTexture, MipmapTexture& mipTex) { 261 | const auto* rawMipTex = (bsp30::MipTex*)rawTexture.data(); 262 | 263 | auto width = rawMipTex->width; 264 | auto height = rawMipTex->height; 265 | const auto palOffset = rawMipTex->offsets[3] + (width / 8) * (height / 8) + 2; 266 | const auto* palette = rawTexture.data() + palOffset; 267 | const auto* color = palette + 255 * 3; 268 | 269 | for (int level = 0; level < bsp30::MIPLEVELS; level++) { 270 | const auto* pixel = &(rawTexture[rawMipTex->offsets[level]]); 271 | 272 | auto& img = mipTex.Img[level]; 273 | img.channels = 4; 274 | img.width = width; 275 | img.height = height; 276 | img.data.resize(width * height * 4); 277 | 278 | for (int i = 0; i < height * width; i++) { 279 | int palIndex = pixel[i] * 3; 280 | 281 | img.data[i * 4 + 0] = color[0]; 282 | img.data[i * 4 + 1] = color[1]; 283 | img.data[i * 4 + 2] = color[2]; 284 | img.data[i * 4 + 3] = 255 - palette[palIndex]; 285 | } 286 | 287 | ApplyAlphaSections(mipTex.Img[level]); 288 | 289 | width /= 2; 290 | height /= 2; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/opengl/Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "Renderer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "opengl/Texture.h" 10 | #include "../IRenderable.h" 11 | #include "../Camera.h" 12 | #include "../Entity.h" 13 | #include "../mathlib.h" 14 | #include "../global.h" 15 | #include "../Bsp.h" 16 | 17 | namespace render::opengl { 18 | namespace { 19 | void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { 20 | const char* sourceStr = [&] { 21 | switch (source) { 22 | case GL_DEBUG_SOURCE_API_ARB: return "API"; 23 | case GL_DEBUG_SOURCE_SHADER_COMPILER_ARB: return "shader compiler"; 24 | case GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB: return "window system"; 25 | case GL_DEBUG_SOURCE_THIRD_PARTY_ARB: return "third party"; 26 | case GL_DEBUG_SOURCE_APPLICATION_ARB: return "application"; 27 | case GL_DEBUG_SOURCE_OTHER_ARB: return "other"; 28 | default: return "unknown"; 29 | } 30 | }(); 31 | 32 | const char* typeStr = [&] { 33 | switch (type) { 34 | case GL_DEBUG_TYPE_ERROR_ARB: return "error"; 35 | case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: return "deprecated behavior"; 36 | case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: return "undefined behavior"; 37 | case GL_DEBUG_TYPE_PERFORMANCE_ARB: return "performance"; 38 | case GL_DEBUG_TYPE_PORTABILITY_ARB: return "portability"; 39 | case GL_DEBUG_TYPE_OTHER_ARB: return "other"; 40 | default: return "unknown"; 41 | } 42 | }(); 43 | 44 | const char* severityStr = [&] { 45 | switch (severity) { 46 | case GL_DEBUG_SEVERITY_HIGH_ARB: return "high"; 47 | case GL_DEBUG_SEVERITY_MEDIUM_ARB: return "medium"; 48 | case GL_DEBUG_SEVERITY_LOW_ARB: return "low"; 49 | default: return "unknown"; 50 | } 51 | }(); 52 | 53 | std::cerr << "OpenGL debug callback: [" << severityStr << "|" << sourceStr << "|" << typeStr << "] " << std::string(message, length) << '\n'; 54 | 55 | #ifdef _WIN32 56 | if (type == GL_DEBUG_TYPE_ERROR_ARB) 57 | __debugbreak(); 58 | #endif 59 | } 60 | 61 | // some GLEW versions still have the last parameter mutable (looking at you travis) 62 | void debugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, void* userParam) { 63 | debugCallback(source, type, id, severity, length, message, static_cast(userParam)); 64 | } 65 | 66 | auto channelsToTextureType(const Image& img) { 67 | switch (img.channels) { 68 | case 1: return GL_RED; 69 | case 2: return GL_RG; 70 | case 3: return GL_RGB; 71 | case 4: return GL_RGBA; 72 | default: assert(false); 73 | } 74 | } 75 | 76 | auto convert(AttributeLayout::Type type) { 77 | switch (type) { 78 | case AttributeLayout::Type::Float: return GL_FLOAT; 79 | default: assert(false); 80 | } 81 | } 82 | } 83 | 84 | struct Texture : ITexture, gl::Texture {}; 85 | 86 | struct Buffer : IBuffer, gl::Buffer {}; 87 | 88 | struct InputLayout : IInputLayout, gl::VAO {}; 89 | 90 | Renderer::Glew::Glew() { 91 | if (glewInit() != GLEW_OK) 92 | throw std::runtime_error("glew failed to initialize"); 93 | } 94 | 95 | Renderer::Renderer() { 96 | std::cout << "OpenGL version: " << reinterpret_cast(glGetString(GL_VERSION)) << '\n'; 97 | std::cout << "OpenGL vendor: " << reinterpret_cast(glGetString(GL_VENDOR)) << '\n'; 98 | 99 | #ifndef NDEBUG 100 | // error callback 101 | if (GLEW_ARB_debug_output) { 102 | //glDebugMessageCallback(static_cast(debugCallback), nullptr); 103 | //glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); 104 | } 105 | #endif 106 | 107 | glShadeModel(GL_SMOOTH); 108 | glClearColor(0.0f, 0.0f, 0.0f, 0.0f); 109 | glClearDepth(1.0f); 110 | glDepthFunc(GL_LEQUAL); 111 | 112 | glEnable(GL_CULL_FACE); 113 | glCullFace(GL_FRONT); 114 | 115 | glEnable(GL_MULTISAMPLE); 116 | 117 | //// 118 | //// configure lighting for flashlight 119 | //// 120 | 121 | //glEnable(GL_LIGHT0); 122 | 123 | //GLfloat lightPos[] = {0.0f, 0.0f, 0.0f, 1.0f}; 124 | //glLightfv(GL_LIGHT0, GL_POSITION, lightPos); 125 | 126 | //GLfloat spotDir[] = {0.0f, 0.0f, -1.0f}; 127 | //glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir); 128 | //glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 25.0f); 129 | //glLightf(GL_LIGHT0, GL_SPOT_EXPONENT, 1.0f); 130 | //glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.01f); 131 | //glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.01f); 132 | //glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0001f); 133 | 134 | m_skyboxProgram = gl::Program{ 135 | gl::Shader(GL_VERTEX_SHADER, fs::path{"../src/opengl/shader/skybox.vert"}), 136 | gl::Shader(GL_FRAGMENT_SHADER, fs::path{"../src/opengl/shader/skybox.frag"}), 137 | }; 138 | 139 | m_shaderProgram = gl::Program{ 140 | gl::Shader(GL_VERTEX_SHADER, fs::path{"../src/opengl/shader/main.vert"}), 141 | gl::Shader(GL_FRAGMENT_SHADER, fs::path{"../src/opengl/shader/main.frag"}), 142 | }; 143 | 144 | m_coordsProgram = gl::Program{ 145 | gl::Shader(GL_VERTEX_SHADER, fs::path{"../src/opengl/shader/coords.vert"}), 146 | gl::Shader(GL_FRAGMENT_SHADER, fs::path{"../src/opengl/shader/coords.frag"}), 147 | }; 148 | 149 | ImGui_ImplOpenGL3_Init("#version 330"); 150 | ImGui_ImplOpenGL3_NewFrame(); // trigger building of some resources 151 | } 152 | 153 | Renderer::~Renderer() { 154 | ImGui_ImplOpenGL3_Shutdown(); 155 | } 156 | 157 | void Renderer::resizeViewport(int width, int height) { 158 | glViewport(0, 0, width, height); 159 | } 160 | 161 | void Renderer::clear() { 162 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 163 | } 164 | 165 | auto Renderer::createTexture(const std::vector& mipmaps) const -> std::unique_ptr { 166 | std::unique_ptr t(new Texture()); 167 | t->bind(GL_TEXTURE_2D); 168 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 169 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 170 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 171 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, static_cast(mipmaps.size() - 1)); 172 | for (int i = 0; i < mipmaps.size(); i++) 173 | glTexImage2D(GL_TEXTURE_2D, i, GL_RGBA, mipmaps[i].width, mipmaps[i].height, 0, channelsToTextureType(mipmaps[i]), GL_UNSIGNED_BYTE, mipmaps[i].data.data()); 174 | return t; 175 | } 176 | 177 | auto Renderer::createCubeTexture(const std::array& sides) const -> std::unique_ptr { 178 | std::unique_ptr t(new Texture()); 179 | t->bind(GL_TEXTURE_CUBE_MAP); 180 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 181 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 182 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); 183 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 184 | glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 185 | for (auto i = 0; i < 6; i++) 186 | glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, sides[i].width, sides[i].height, 0, sides[i].channels == 3 ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, sides[i].data.data()); 187 | return t; 188 | } 189 | 190 | auto Renderer::createBuffer(std::size_t size, const void* data) const -> std::unique_ptr { 191 | std::unique_ptr b(new Buffer()); 192 | b->bind(GL_ARRAY_BUFFER); 193 | glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); 194 | glBindBuffer(GL_ARRAY_BUFFER, 0); 195 | return b; 196 | } 197 | 198 | auto Renderer::createInputLayout(IBuffer& buffer, const std::vector& layout) const -> std::unique_ptr { 199 | std::unique_ptr l(new InputLayout()); 200 | l->bind(); 201 | static_cast(buffer).bind(GL_ARRAY_BUFFER); 202 | int i = 0; 203 | for (const auto& al : layout) { 204 | glVertexAttribPointer(i, al.size, convert(al.type), false, al.stride, reinterpret_cast(al.offset)); 205 | glEnableVertexAttribArray(i); 206 | i++; 207 | } 208 | glBindVertexArray(0); 209 | glBindBuffer(GL_ARRAY_BUFFER, 0); 210 | return l; 211 | } 212 | 213 | void Renderer::renderCoords(const glm::mat4& matrix) { 214 | m_emptyVao.bind(); 215 | m_coordsProgram.use(); 216 | glUniformMatrix4fv(m_coordsProgram.uniformLocation("matrix"), 1, false, glm::value_ptr(matrix)); 217 | glDrawArrays(GL_LINES, 0, 12); 218 | } 219 | 220 | void Renderer::renderSkyBox(ITexture& cubemap, const glm::mat4& matrix) { 221 | m_emptyVao.bind(); 222 | m_skyboxProgram.use(); 223 | glUniform1i(m_skyboxProgram.uniformLocation("cubeSampler"), 0); 224 | glUniformMatrix4fv(m_skyboxProgram.uniformLocation("matrix"), 1, false, glm::value_ptr(matrix)); 225 | 226 | glActiveTexture(GL_TEXTURE0); 227 | static_cast(cubemap).bind(GL_TEXTURE_CUBE_MAP); 228 | 229 | glDepthMask(GL_FALSE); 230 | glDrawArrays(GL_TRIANGLES, 0, 36); 231 | glDepthMask(GL_TRUE); 232 | } 233 | 234 | void Renderer::renderStatic(std::vector entities, const std::vector& decals, IInputLayout& staticLayout, IInputLayout& decalLayout, std::vector>& textures, render::ITexture& lightmapAtlas, const RenderSettings& settings) { 235 | static_cast(staticLayout).bind(); 236 | m_shaderProgram.use(); 237 | glUniform1i(m_shaderProgram.uniformLocation("tex1"), 0); 238 | glUniform1i(m_shaderProgram.uniformLocation("tex2"), 1); 239 | glUniform1i(m_shaderProgram.uniformLocation("nightvision"), static_cast(global::nightvision)); 240 | //glUniform1i(m_shaderProgram.uniformLocation("flashlight"), static_cast(settings.flashlight)); 241 | glUniform1i(m_shaderProgram.uniformLocation("unit1Enabled"), static_cast(global::textures)); 242 | glUniform1i(m_shaderProgram.uniformLocation("unit2Enabled"), static_cast(global::lightmaps)); 243 | 244 | glUniform1i(m_shaderProgram.uniformLocation("alphaTest"), 0); 245 | 246 | glEnable(GL_DEPTH_TEST); 247 | 248 | for (auto& ent : entities) 249 | renderBrushEntity(std::move(ent.fri), lightmapAtlas, settings, ent.origin, ent.alpha, ent.renderMode); 250 | 251 | glUniform1i(m_shaderProgram.uniformLocation("unit2Enabled"), 0); 252 | 253 | const auto matrix = settings.projection * settings.view; 254 | glUniformMatrix4fv(m_shaderProgram.uniformLocation("matrix"), 1, false, glm::value_ptr(matrix)); 255 | 256 | if (global::renderDecals) { 257 | static_cast(decalLayout).bind(); 258 | renderDecals(decals, textures); 259 | } 260 | 261 | glDisable(GL_DEPTH_TEST); 262 | 263 | glUseProgram(0); 264 | } 265 | 266 | void Renderer::renderBrushEntity(std::vector fri, render::ITexture& lightmapAtlas, const RenderSettings& settings, glm::vec3 origin, float alpha, bsp30::RenderMode renderMode) { 267 | const auto matrix = glm::translate(settings.projection * settings.view, origin); 268 | glUniformMatrix4fv(m_shaderProgram.uniformLocation("matrix"), 1, false, glm::value_ptr(matrix)); 269 | 270 | switch (renderMode) { 271 | case bsp30::RENDER_MODE_NORMAL: 272 | break; 273 | case bsp30::RENDER_MODE_TEXTURE: 274 | glEnable(GL_BLEND); 275 | glBlendFunc(GL_SRC_ALPHA, GL_ONE); 276 | glDepthMask(GL_FALSE); 277 | break; 278 | case bsp30::RENDER_MODE_SOLID: 279 | glUniform1i(m_shaderProgram.uniformLocation("alphaTest"), 1); 280 | break; 281 | case bsp30::RENDER_MODE_ADDITIVE: 282 | glEnable(GL_BLEND); 283 | glBlendFunc(GL_ONE, GL_ONE); 284 | glDepthMask(GL_FALSE); 285 | break; 286 | } 287 | 288 | renderFri(std::move(fri), lightmapAtlas); 289 | 290 | switch (renderMode) { 291 | case bsp30::RENDER_MODE_NORMAL: 292 | break; 293 | case bsp30::RENDER_MODE_TEXTURE: 294 | case bsp30::RENDER_MODE_ADDITIVE: 295 | glDisable(GL_BLEND); 296 | glDepthMask(GL_TRUE); 297 | break; 298 | case bsp30::RENDER_MODE_SOLID: 299 | glUniform1i(m_shaderProgram.uniformLocation("alphaTest"), 0); 300 | break; 301 | } 302 | } 303 | 304 | void Renderer::renderFri(std::vector fri, render::ITexture& lightmapAtlas) { 305 | // sort by texture id to avoid some rebinds 306 | //std::sort(begin(fri), end(fri), [](const FaceRenderInfo& a, const FaceRenderInfo& b) { 307 | // return a.tex < b.tex; 308 | //}); 309 | 310 | glActiveTexture(GL_TEXTURE1); 311 | glBindTexture(GL_TEXTURE_2D, static_cast(lightmapAtlas).id()); 312 | glActiveTexture(GL_TEXTURE0); 313 | ITexture* curId = nullptr; 314 | for (const auto& i : fri) { 315 | if (curId != i.tex) { 316 | glBindTexture(GL_TEXTURE_2D, static_cast(*i.tex).id()); 317 | curId = i.tex; 318 | } 319 | glDrawArrays(GL_TRIANGLES, i.offset, i.count); 320 | } 321 | } 322 | 323 | void Renderer::renderDecals(const std::vector& decals, std::vector>& textures) { 324 | glEnable(GL_POLYGON_OFFSET_FILL); 325 | glPolygonOffset(0.0f, -2.0f); 326 | glEnable(GL_BLEND); 327 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 328 | 329 | glActiveTexture(GL_TEXTURE0); 330 | 331 | for (auto i = 0; i < decals.size(); i++) { 332 | glBindTexture(GL_TEXTURE_2D, static_cast(*textures[decals[i].texIndex]).id()); 333 | glDrawArrays(GL_TRIANGLES, i * 4, 4); 334 | } 335 | 336 | glDisable(GL_BLEND); 337 | glDisable(GL_POLYGON_OFFSET_FILL); 338 | } 339 | 340 | void Renderer::renderImgui(ImDrawData* data) { 341 | ImGui_ImplOpenGL3_NewFrame(); 342 | ImGui_ImplOpenGL3_RenderDrawData(data); 343 | } 344 | 345 | auto Renderer::screenshot() const -> Image { 346 | GLint vp[4]; 347 | glGetIntegerv(GL_VIEWPORT, vp); 348 | Image img(vp[2], vp[3], 3); 349 | glReadPixels(vp[0], vp[1], vp[2], vp[3], GL_RGB, GL_UNSIGNED_BYTE, img.data.data()); 350 | return img; 351 | } 352 | 353 | auto Platform::createWindowAndContext(int width, int height, const char * title, GLFWmonitor * monitor) -> GLFWwindow* { 354 | // set window hints before creating window 355 | // list of available hints and their defaults: http://www.glfw.org/docs/3.0/window.html#window_hints 356 | glfwWindowHint(GLFW_DEPTH_BITS, 32); 357 | glfwWindowHint(GLFW_STENCIL_BITS, 0); 358 | glfwWindowHint(GLFW_FOCUSED, false); 359 | #ifndef NDEBUG 360 | glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true); 361 | #endif 362 | 363 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 364 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 365 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 366 | 367 | m_window = glfwCreateWindow(width, height, title, monitor, nullptr); 368 | 369 | return m_window; 370 | } 371 | 372 | auto Platform::createRenderer() -> std::unique_ptr { 373 | return std::make_unique(); 374 | } 375 | 376 | void Platform::swapBuffers() { 377 | glfwSwapBuffers(m_window); 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /thirdparty/imgui_impl_glfw.cpp: -------------------------------------------------------------------------------- 1 | // dear imgui: Platform Binding for GLFW 2 | // This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan..) 3 | // (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.) 4 | 5 | // Implemented features: 6 | // [X] Platform: Clipboard support. 7 | // [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. 8 | // [x] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: 3 cursors types are missing from GLFW. 9 | // [X] Platform: Keyboard arrays indexed using GLFW_KEY_* codes, e.g. ImGui::IsKeyPressed(GLFW_KEY_SPACE). 10 | 11 | // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. 12 | // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. 13 | // https://github.com/ocornut/imgui 14 | 15 | // CHANGELOG 16 | // (minor and older changes stripped away, please see git history for details) 17 | // 2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls. 18 | // 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor. 19 | // 2018-06-08: Misc: Extracted imgui_impl_glfw.cpp/.h away from the old combined GLFW+OpenGL/Vulkan examples. 20 | // 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag. 21 | // 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value, passed to glfwSetCursor()). 22 | // 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. 23 | // 2018-02-06: Inputs: Added mapping for ImGuiKey_Space. 24 | // 2018-01-25: Inputs: Added gamepad support if ImGuiConfigFlags_NavEnableGamepad is set. 25 | // 2018-01-25: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set). 26 | // 2018-01-20: Inputs: Added Horizontal Mouse Wheel support. 27 | // 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert. 28 | // 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1). 29 | // 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers. 30 | 31 | #include "imgui.h" 32 | #include "imgui_impl_glfw.h" 33 | 34 | // GLFW 35 | #include 36 | #ifdef _WIN32 37 | #undef APIENTRY 38 | #define GLFW_EXPOSE_NATIVE_WIN32 39 | #include // for glfwGetWin32Window 40 | #endif 41 | #define GLFW_HAS_WINDOW_TOPMOST (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3200) // 3.2+ GLFW_FLOATING 42 | #define GLFW_HAS_WINDOW_HOVERED (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ GLFW_HOVERED 43 | #define GLFW_HAS_WINDOW_ALPHA (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ glfwSetWindowOpacity 44 | #define GLFW_HAS_PER_MONITOR_DPI (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ glfwGetMonitorContentScale 45 | #define GLFW_HAS_VULKAN (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3200) // 3.2+ glfwCreateWindowSurface 46 | 47 | // Data 48 | static GLFWwindow* g_Window = NULL; 49 | static GlfwClientApi g_ClientApi = GlfwClientApi_Unknown; 50 | static double g_Time = 0.0; 51 | static bool g_MouseJustPressed[5] = { false, false, false, false, false }; 52 | static GLFWcursor* g_MouseCursors[ImGuiMouseCursor_COUNT] = { 0 }; 53 | 54 | static const char* ImGui_ImplGlfw_GetClipboardText(void* user_data) 55 | { 56 | return glfwGetClipboardString((GLFWwindow*)user_data); 57 | } 58 | 59 | static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text) 60 | { 61 | glfwSetClipboardString((GLFWwindow*)user_data, text); 62 | } 63 | 64 | void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow*, int button, int action, int /*mods*/) 65 | { 66 | if (action == GLFW_PRESS && button >= 0 && button < IM_ARRAYSIZE(g_MouseJustPressed)) 67 | g_MouseJustPressed[button] = true; 68 | } 69 | 70 | void ImGui_ImplGlfw_ScrollCallback(GLFWwindow*, double xoffset, double yoffset) 71 | { 72 | ImGuiIO& io = ImGui::GetIO(); 73 | io.MouseWheelH += (float)xoffset; 74 | io.MouseWheel += (float)yoffset; 75 | } 76 | 77 | void ImGui_ImplGlfw_KeyCallback(GLFWwindow*, int key, int, int action, int mods) 78 | { 79 | ImGuiIO& io = ImGui::GetIO(); 80 | if (action == GLFW_PRESS) 81 | io.KeysDown[key] = true; 82 | if (action == GLFW_RELEASE) 83 | io.KeysDown[key] = false; 84 | 85 | (void)mods; // Modifiers are not reliable across systems 86 | io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL]; 87 | io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT]; 88 | io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT]; 89 | io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER]; 90 | } 91 | 92 | void ImGui_ImplGlfw_CharCallback(GLFWwindow*, unsigned int c) 93 | { 94 | ImGuiIO& io = ImGui::GetIO(); 95 | if (c > 0 && c < 0x10000) 96 | io.AddInputCharacter((unsigned short)c); 97 | } 98 | 99 | void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window) 100 | { 101 | glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback); 102 | glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback); 103 | glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback); 104 | glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback); 105 | } 106 | 107 | bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, GlfwClientApi client_api) 108 | { 109 | g_Window = window; 110 | g_Time = 0.0; 111 | 112 | // Setup back-end capabilities flags 113 | ImGuiIO& io = ImGui::GetIO(); 114 | io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) 115 | io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) 116 | 117 | // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. 118 | io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; 119 | io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; 120 | io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; 121 | io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; 122 | io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; 123 | io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP; 124 | io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN; 125 | io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; 126 | io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; 127 | io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT; 128 | io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; 129 | io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; 130 | io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE; 131 | io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; 132 | io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; 133 | io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; 134 | io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; 135 | io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; 136 | io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; 137 | io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; 138 | io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; 139 | 140 | io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText; 141 | io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText; 142 | io.ClipboardUserData = g_Window; 143 | #if defined(_WIN32) 144 | io.ImeWindowHandle = (void*)glfwGetWin32Window(g_Window); 145 | #endif 146 | 147 | g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); 148 | g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); 149 | g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); // FIXME: GLFW doesn't have this. 150 | g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); 151 | g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); 152 | g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); // FIXME: GLFW doesn't have this. 153 | g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); // FIXME: GLFW doesn't have this. 154 | g_MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR); 155 | 156 | if (install_callbacks) 157 | ImGui_ImplGlfw_InstallCallbacks(window); 158 | 159 | g_ClientApi = client_api; 160 | return true; 161 | } 162 | 163 | bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks) 164 | { 165 | return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_OpenGL); 166 | } 167 | 168 | bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks) 169 | { 170 | return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Vulkan); 171 | } 172 | 173 | void ImGui_ImplGlfw_Shutdown() 174 | { 175 | for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) 176 | { 177 | glfwDestroyCursor(g_MouseCursors[cursor_n]); 178 | g_MouseCursors[cursor_n] = NULL; 179 | } 180 | g_ClientApi = GlfwClientApi_Unknown; 181 | } 182 | 183 | static void ImGui_ImplGlfw_UpdateMousePosAndButtons() 184 | { 185 | // Update buttons 186 | ImGuiIO& io = ImGui::GetIO(); 187 | for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) 188 | { 189 | // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. 190 | io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0; 191 | g_MouseJustPressed[i] = false; 192 | } 193 | 194 | // Update mouse position 195 | const ImVec2 mouse_pos_backup = io.MousePos; 196 | io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); 197 | #ifdef __EMSCRIPTEN__ 198 | const bool focused = true; // Emscripten 199 | #else 200 | const bool focused = glfwGetWindowAttrib(g_Window, GLFW_FOCUSED) != 0; 201 | #endif 202 | if (focused) 203 | { 204 | if (io.WantSetMousePos) 205 | { 206 | glfwSetCursorPos(g_Window, (double)mouse_pos_backup.x, (double)mouse_pos_backup.y); 207 | } 208 | else 209 | { 210 | double mouse_x, mouse_y; 211 | glfwGetCursorPos(g_Window, &mouse_x, &mouse_y); 212 | io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); 213 | } 214 | } 215 | } 216 | 217 | static void ImGui_ImplGlfw_UpdateMouseCursor() 218 | { 219 | ImGuiIO& io = ImGui::GetIO(); 220 | if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange) || glfwGetInputMode(g_Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED) 221 | return; 222 | 223 | ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); 224 | if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) 225 | { 226 | // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor 227 | glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); 228 | } 229 | else 230 | { 231 | // Show OS mouse cursor 232 | // FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here. 233 | glfwSetCursor(g_Window, g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor] : g_MouseCursors[ImGuiMouseCursor_Arrow]); 234 | glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); 235 | } 236 | } 237 | 238 | void ImGui_ImplGlfw_NewFrame() 239 | { 240 | ImGuiIO& io = ImGui::GetIO(); 241 | IM_ASSERT(io.Fonts->IsBuilt()); // Font atlas needs to be built, call renderer _NewFrame() function e.g. ImGui_ImplOpenGL3_NewFrame() 242 | 243 | // Setup display size 244 | int w, h; 245 | int display_w, display_h; 246 | glfwGetWindowSize(g_Window, &w, &h); 247 | glfwGetFramebufferSize(g_Window, &display_w, &display_h); 248 | io.DisplaySize = ImVec2((float)w, (float)h); 249 | io.DisplayFramebufferScale = ImVec2(w > 0 ? ((float)display_w / w) : 0, h > 0 ? ((float)display_h / h) : 0); 250 | 251 | // Setup time step 252 | double current_time = glfwGetTime(); 253 | io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f/60.0f); 254 | g_Time = current_time; 255 | 256 | ImGui_ImplGlfw_UpdateMousePosAndButtons(); 257 | ImGui_ImplGlfw_UpdateMouseCursor(); 258 | 259 | // Gamepad navigation mapping [BETA] 260 | memset(io.NavInputs, 0, sizeof(io.NavInputs)); 261 | if (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) 262 | { 263 | // Update gamepad inputs 264 | #define MAP_BUTTON(NAV_NO, BUTTON_NO) { if (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS) io.NavInputs[NAV_NO] = 1.0f; } 265 | #define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); if (v > 1.0f) v = 1.0f; if (io.NavInputs[NAV_NO] < v) io.NavInputs[NAV_NO] = v; } 266 | int axes_count = 0, buttons_count = 0; 267 | const float* axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count); 268 | const unsigned char* buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count); 269 | MAP_BUTTON(ImGuiNavInput_Activate, 0); // Cross / A 270 | MAP_BUTTON(ImGuiNavInput_Cancel, 1); // Circle / B 271 | MAP_BUTTON(ImGuiNavInput_Menu, 2); // Square / X 272 | MAP_BUTTON(ImGuiNavInput_Input, 3); // Triangle / Y 273 | MAP_BUTTON(ImGuiNavInput_DpadLeft, 13); // D-Pad Left 274 | MAP_BUTTON(ImGuiNavInput_DpadRight, 11); // D-Pad Right 275 | MAP_BUTTON(ImGuiNavInput_DpadUp, 10); // D-Pad Up 276 | MAP_BUTTON(ImGuiNavInput_DpadDown, 12); // D-Pad Down 277 | MAP_BUTTON(ImGuiNavInput_FocusPrev, 4); // L1 / LB 278 | MAP_BUTTON(ImGuiNavInput_FocusNext, 5); // R1 / RB 279 | MAP_BUTTON(ImGuiNavInput_TweakSlow, 4); // L1 / LB 280 | MAP_BUTTON(ImGuiNavInput_TweakFast, 5); // R1 / RB 281 | MAP_ANALOG(ImGuiNavInput_LStickLeft, 0, -0.3f, -0.9f); 282 | MAP_ANALOG(ImGuiNavInput_LStickRight,0, +0.3f, +0.9f); 283 | MAP_ANALOG(ImGuiNavInput_LStickUp, 1, +0.3f, +0.9f); 284 | MAP_ANALOG(ImGuiNavInput_LStickDown, 1, -0.3f, -0.9f); 285 | #undef MAP_BUTTON 286 | #undef MAP_ANALOG 287 | if (axes_count > 0 && buttons_count > 0) 288 | io.BackendFlags |= ImGuiBackendFlags_HasGamepad; 289 | else 290 | io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/BspRenderable.cpp: -------------------------------------------------------------------------------- 1 | #include "BspRenderable.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Bsp.h" 8 | #include "Camera.h" 9 | #include "mathlib.h" 10 | #include "global.h" 11 | 12 | namespace { 13 | class TextureAtlas { 14 | public: 15 | TextureAtlas(unsigned int width, unsigned int height, unsigned int channels = 3) 16 | : m_img(width, height, channels), allocated(width) {} 17 | 18 | auto store(const Image& image) -> glm::uvec2 { 19 | if (image.channels != m_img.channels) 20 | throw std::logic_error("image and atlas channel count mismatch"); 21 | 22 | const auto loc = allocLightmap(image.width, image.height); 23 | if (!loc) 24 | throw std::runtime_error("atlas is full"); 25 | 26 | for (auto y = 0u; y < image.height; y++) { 27 | const auto src = &image.data[(y * image.width) * image.channels]; 28 | const auto dst = &m_img.data[((loc->y + y) * m_img.width + loc->x) * image.channels]; 29 | std::copy(src, src + image.width * image.channels, dst); 30 | } 31 | 32 | return *loc; 33 | } 34 | 35 | auto convertCoord(const Image& image, glm::uvec2 storedPos, glm::vec2 coord) { 36 | return (glm::vec2(storedPos) + coord * glm::vec2(image.width, image.height)) / glm::vec2(m_img.width, m_img.height); 37 | } 38 | 39 | auto img() const -> const auto& { return m_img; } 40 | 41 | private: 42 | // from: http://fabiensanglard.net/quake2/quake2_opengl_renderer.php 43 | // based on Quake 2 44 | auto allocLightmap(unsigned int lmWidth, unsigned int lmHeight) -> std::optional { 45 | glm::uvec2 pos(0, 0); 46 | 47 | auto best = m_img.height; 48 | for (auto i = 0u; i < m_img.width - lmWidth; i++) { 49 | auto best2 = 0u; 50 | auto j = 0u; 51 | for (j = 0u; j < lmWidth; j++) { 52 | if (allocated[i + j] >= best) 53 | break; 54 | if (allocated[i + j] > best2) 55 | best2 = allocated[i + j]; 56 | } 57 | if (j == lmWidth) { 58 | pos.x = i; 59 | pos.y = best = best2; 60 | } 61 | } 62 | 63 | if (best + lmHeight > m_img.height) 64 | return {}; 65 | 66 | for (unsigned int i = 0; i < lmWidth; i++) 67 | allocated[pos.x + i] = best + lmHeight; 68 | 69 | return pos; 70 | } 71 | 72 | std::vector allocated; 73 | Image m_img; 74 | }; 75 | } 76 | 77 | BspRenderable::BspRenderable(render::IRenderer& renderer, const Bsp& bsp, const Camera& camera) 78 | : m_renderer(renderer), m_bsp(&bsp), m_camera(&camera) { 79 | loadSkyTextures(); 80 | loadTextures(); 81 | auto lmCoords = loadLightmaps(); 82 | buildBuffers(std::move(lmCoords)); 83 | 84 | facesDrawn.resize(bsp.faces.size()); 85 | } 86 | 87 | BspRenderable::~BspRenderable() = default; 88 | 89 | void BspRenderable::loadTextures() { 90 | const auto& mipTexs = m_bsp->m_textures; 91 | 92 | //// create texture atlas 93 | //TextureAtlas atlas(2048, 2048, 4); 94 | //std::vector lmPositions(mipTexs.size()); 95 | //for (auto i = 0u; i < mipTexs.size(); i++) { 96 | // const auto& lm = mipTexs[i].Img[0]; 97 | // if (lm.width == 0 || lm.height == 0) 98 | // continue; 99 | // lmPositions[i] = atlas.store(lm); 100 | //} 101 | //atlas.img().Save("tex_atlas.png"); 102 | 103 | m_textures.reserve(mipTexs.size()); 104 | for (const auto& mipTex : mipTexs) 105 | m_textures.emplace_back(m_renderer.createTexture(std::vector{mipTex.Img, mipTex.Img + 4})); 106 | } 107 | 108 | auto BspRenderable::loadLightmaps() -> std::vector> { 109 | const auto& lightmaps = m_bsp->m_lightmaps; 110 | 111 | // create lightmap atlas 112 | TextureAtlas atlas(1024, 1024, 3); 113 | std::vector lmPositions(lightmaps.size()); 114 | for (auto i = 0u; i < lightmaps.size(); i++) { 115 | const auto& lm = lightmaps[i]; 116 | if (lm.width == 0 || lm.height == 0) 117 | continue; 118 | lmPositions[i] = atlas.store(lm); 119 | } 120 | atlas.img().Save("lm_atlas.png"); 121 | 122 | // recompute lightmap coords 123 | std::vector> lmCoords(m_bsp->faces.size()); 124 | for (const auto& face : m_bsp->faces) { 125 | const auto faceIndex = &face - &m_bsp->faces.front(); 126 | auto& coords = m_bsp->faceTexCoords[faceIndex]; 127 | for (auto& coord : coords.lightmapCoords) 128 | lmCoords[faceIndex].push_back(atlas.convertCoord(lightmaps[faceIndex], lmPositions[faceIndex], coord)); 129 | } 130 | 131 | m_lightmapAtlas = m_renderer.createTexture({ atlas.img() }); 132 | return lmCoords; 133 | } 134 | 135 | void BspRenderable::loadSkyTextures() { 136 | const auto images = m_bsp->loadSkyBox(); 137 | if (!images) 138 | return; 139 | m_skyboxTex = m_renderer.createCubeTexture(*images); 140 | } 141 | 142 | void BspRenderable::render(const RenderSettings& settings) { 143 | m_settings = &settings; 144 | 145 | // render sky box 146 | if (m_skyboxTex && global::renderSkybox) 147 | renderSkybox(); 148 | 149 | const auto& cameraPos = m_camera->position(); 150 | 151 | if (global::renderStaticBSP || global::renderBrushEntities) 152 | for (auto&& b : facesDrawn) 153 | b = false; 154 | 155 | std::vector ents; 156 | if (global::renderStaticBSP) 157 | ents.push_back(render::EntityData{ renderStaticGeometry(cameraPos), glm::vec3{}, 1.0f, bsp30::RenderMode::RENDER_MODE_NORMAL }); 158 | 159 | if (global::renderBrushEntities) { 160 | for (const auto i : m_bsp->brushEntities) { 161 | const auto& ent = m_bsp->entities[i]; 162 | 163 | const int model = std::stoi(ent.findProperty("model")->substr(1)); 164 | 165 | const auto alpha = [&] { 166 | if (const auto renderamt = ent.findProperty("renderamt")) 167 | return std::stoi(*renderamt) / 255.0f; 168 | return 1.0f; 169 | }(); 170 | 171 | const auto renderMode = [&] { 172 | if (const auto pszRenderMode = ent.findProperty("rendermode")) 173 | return static_cast(std::stoi(*pszRenderMode)); 174 | else 175 | return bsp30::RENDER_MODE_NORMAL; 176 | }(); 177 | 178 | std::vector fri; 179 | renderBSP(m_bsp->models[model].headNodesIndex[0], boost::dynamic_bitset{}, cameraPos, fri); // for some odd reason, VIS does not work for entities ... 180 | 181 | ents.push_back(render::EntityData{ std::move(fri), m_bsp->models[model].origin, alpha, renderMode }); 182 | } 183 | } 184 | 185 | m_renderer.renderStatic(std::move(ents), m_bsp->m_decals, *m_staticGeometryVao, *m_decalVao, m_textures, *m_lightmapAtlas, settings); 186 | 187 | // Leaf outlines 188 | if (global::renderLeafOutlines) { 189 | std::cerr << "Rendering leaf outlines is currently disabled\n"; 190 | //glLoadMatrixf(glm::value_ptr(matrix)); 191 | //renderLeafOutlines(); 192 | } 193 | } 194 | 195 | void BspRenderable::renderSkybox() { 196 | // TODO: glm in WSL Ubuntu does not yet have this function 197 | auto matrix = m_settings->projection * glm::eulerAngleXZX(degToRad(m_settings->pitch - 90.0f), degToRad(-m_settings->yaw), degToRad(+90.0f)); 198 | //auto matrix = m_settings->projection * glm::eulerAngleX(degToRad(m_settings->pitch - 90.0f)) * glm::eulerAngleZ(degToRad(-m_settings->yaw)) * glm::eulerAngleX(degToRad(+90.0f)); 199 | 200 | m_renderer.renderSkyBox(**m_skyboxTex, matrix); 201 | } 202 | 203 | auto BspRenderable::renderStaticGeometry(glm::vec3 pos) -> std::vector { 204 | std::vector fri; 205 | const auto leaf = m_bsp->findLeaf(pos); 206 | renderBSP(0, !leaf || m_bsp->visLists.empty() ? boost::dynamic_bitset{} : m_bsp->visLists[*leaf - 1], pos, fri); 207 | return fri; 208 | } 209 | 210 | //void BspRenderable::renderLeafOutlines() { 211 | // std::mt19937 engine; 212 | // std::uniform_real_distribution dist(0.0f, 1.0f); 213 | // 214 | // glLineWidth(1.0f); 215 | // glLineStipple(1, 0xF0F0); 216 | // glEnable(GL_LINE_STIPPLE); 217 | // for (const auto& leaf : m_bsp->leaves) { 218 | // glColor3f(dist(engine), dist(engine), dist(engine)); 219 | // 220 | // glBegin(GL_LINES); 221 | // // Draw right face of bounding box 222 | // glVertex3f(leaf.upper[0], leaf.upper[1], leaf.upper[2]); 223 | // glVertex3f(leaf.upper[0], leaf.lower[1], leaf.upper[2]); 224 | // glVertex3f(leaf.upper[0], leaf.lower[1], leaf.upper[2]); 225 | // glVertex3f(leaf.upper[0], leaf.lower[1], leaf.lower[2]); 226 | // glVertex3f(leaf.upper[0], leaf.lower[1], leaf.lower[2]); 227 | // glVertex3f(leaf.upper[0], leaf.upper[1], leaf.lower[2]); 228 | // glVertex3f(leaf.upper[0], leaf.upper[1], leaf.lower[2]); 229 | // glVertex3f(leaf.upper[0], leaf.upper[1], leaf.upper[2]); 230 | // 231 | // // Draw left face of bounding box 232 | // glVertex3f(leaf.lower[0], leaf.lower[1], leaf.lower[2]); 233 | // glVertex3f(leaf.lower[0], leaf.upper[1], leaf.lower[2]); 234 | // glVertex3f(leaf.lower[0], leaf.upper[1], leaf.lower[2]); 235 | // glVertex3f(leaf.lower[0], leaf.upper[1], leaf.upper[2]); 236 | // glVertex3f(leaf.lower[0], leaf.upper[1], leaf.upper[2]); 237 | // glVertex3f(leaf.lower[0], leaf.lower[1], leaf.upper[2]); 238 | // glVertex3f(leaf.lower[0], leaf.lower[1], leaf.upper[2]); 239 | // glVertex3f(leaf.lower[0], leaf.lower[1], leaf.lower[2]); 240 | // 241 | // // Connect the faces 242 | // glVertex3f(leaf.lower[0], leaf.upper[1], leaf.upper[2]); 243 | // glVertex3f(leaf.upper[0], leaf.upper[1], leaf.upper[2]); 244 | // glVertex3f(leaf.lower[0], leaf.upper[1], leaf.lower[2]); 245 | // glVertex3f(leaf.upper[0], leaf.upper[1], leaf.lower[2]); 246 | // glVertex3f(leaf.lower[0], leaf.lower[1], leaf.lower[2]); 247 | // glVertex3f(leaf.upper[0], leaf.lower[1], leaf.lower[2]); 248 | // glVertex3f(leaf.lower[0], leaf.lower[1], leaf.upper[2]); 249 | // glVertex3f(leaf.upper[0], leaf.lower[1], leaf.upper[2]); 250 | // glEnd(); 251 | // } 252 | // glDisable(GL_LINE_STIPPLE); 253 | // glColor3f(1, 1, 1); 254 | //} 255 | 256 | void BspRenderable::renderLeaf(int leaf, std::vector& fris) { 257 | for (int i = 0; i < m_bsp->leaves[leaf].markSurfaceCount; i++) { 258 | const auto& faceIndex = m_bsp->markSurfaces[m_bsp->leaves[leaf].firstMarkSurface + i]; 259 | 260 | if (facesDrawn[faceIndex]) 261 | continue; 262 | facesDrawn[faceIndex] = true; 263 | 264 | const auto& face = m_bsp->faces[faceIndex]; 265 | 266 | if (face.styles[0] == 0xFF) 267 | continue; 268 | 269 | // if the light map offset is not -1 and the lightmap lump is not empty, there are lightmaps 270 | const bool lightmapAvailable = static_cast(face.lightmapOffset) != -1 && m_bsp->header.lump[bsp30::LumpType::LUMP_LIGHTING].length > 0; 271 | 272 | auto& fri = fris.emplace_back(); 273 | if (global::textures) 274 | fri.tex = m_textures[m_bsp->textureInfos[face.textureInfo].miptexIndex].get(); 275 | else 276 | fri.tex = nullptr; 277 | 278 | fri.offset = vertexOffsets[faceIndex]; 279 | fri.count = (face.edgeCount - 2) * 3; 280 | } 281 | } 282 | 283 | void BspRenderable::renderBSP(int node, const boost::dynamic_bitset& visList, glm::vec3 pos, std::vector& fri) { 284 | if (node < 0) { 285 | if (node == -1) 286 | return; 287 | 288 | const int leaf = ~node; 289 | if (!visList.empty() && !visList[leaf - 1]) 290 | return; 291 | 292 | renderLeaf(leaf, fri); 293 | 294 | return; 295 | } 296 | 297 | const auto dist = [&] { 298 | switch (m_bsp->planes[m_bsp->nodes[node].planeIndex].type) { 299 | case bsp30::PLANE_X: return pos.x - m_bsp->planes[m_bsp->nodes[node].planeIndex].dist; 300 | case bsp30::PLANE_Y: return pos.y - m_bsp->planes[m_bsp->nodes[node].planeIndex].dist; 301 | case bsp30::PLANE_Z: return pos.z - m_bsp->planes[m_bsp->nodes[node].planeIndex].dist; 302 | default: return glm::dot(m_bsp->planes[m_bsp->nodes[node].planeIndex].normal, pos) - m_bsp->planes[m_bsp->nodes[node].planeIndex].dist; 303 | } 304 | }(); 305 | 306 | const auto child1 = dist > 0 ? 1 : 0; 307 | const auto child2 = dist > 0 ? 0 : 1; 308 | renderBSP(m_bsp->nodes[node].childIndex[child1], visList, pos, fri); 309 | renderBSP(m_bsp->nodes[node].childIndex[child2], visList, pos, fri); 310 | } 311 | 312 | void BspRenderable::buildBuffers(std::vector>&& lmCoords) { 313 | { 314 | // static and brush geometry 315 | std::vector vertices; 316 | 317 | for (const auto& face : m_bsp->faces) { 318 | const auto faceIndex = &face - &m_bsp->faces.front(); 319 | const auto& coords = m_bsp->faceTexCoords[faceIndex]; 320 | const auto firstIndex = vertices.size(); 321 | for (int i = 0; i < face.edgeCount; i++) { 322 | if (i > 2) { 323 | auto first = vertices[firstIndex]; 324 | auto prev = vertices.back(); 325 | vertices.push_back(first); 326 | vertices.push_back(prev); 327 | } 328 | 329 | auto& v = vertices.emplace_back(); 330 | v.texCoord = coords.texCoords[i]; 331 | v.lightmapCoord = lmCoords[faceIndex].empty() ? glm::vec2{ 0.0 } : lmCoords[faceIndex][i]; 332 | 333 | v.normal = m_bsp->planes[face.planeIndex].normal; 334 | if (face.planeSide) 335 | v.normal = -v.normal; 336 | 337 | int edge = m_bsp->surfEdges[face.firstEdgeIndex + i]; 338 | if (edge > 0) 339 | v.position = m_bsp->vertices[m_bsp->edges[edge].vertexIndex[0]]; 340 | else 341 | v.position = m_bsp->vertices[m_bsp->edges[-edge].vertexIndex[1]]; 342 | } 343 | vertexOffsets.push_back(static_cast(firstIndex)); 344 | } 345 | 346 | m_staticGeometryVbo = m_renderer.createBuffer(vertices.size() * sizeof(VertexWithLM), vertices.data()); 347 | m_staticGeometryVao = m_renderer.createInputLayout(*m_staticGeometryVbo, { 348 | render::AttributeLayout{ "POSITION", 0, 3, render::AttributeLayout::Type::Float, sizeof(VertexWithLM), offsetof(VertexWithLM, position ) }, 349 | render::AttributeLayout{ "NORMAL" , 0, 3, render::AttributeLayout::Type::Float, sizeof(VertexWithLM), offsetof(VertexWithLM, normal ) }, 350 | render::AttributeLayout{ "TEXCOORD", 0, 2, render::AttributeLayout::Type::Float, sizeof(VertexWithLM), offsetof(VertexWithLM, texCoord ) }, 351 | render::AttributeLayout{ "TEXCOORD", 1, 2, render::AttributeLayout::Type::Float, sizeof(VertexWithLM), offsetof(VertexWithLM, lightmapCoord) } 352 | }); 353 | } 354 | 355 | { 356 | // decals 357 | std::vector vertices; 358 | 359 | for (const auto& decal : m_bsp->m_decals) { 360 | for (auto i = 0; i < 6; i++) { 361 | auto& v = vertices.emplace_back(); 362 | v.normal = decal.normal; 363 | if (i == 0 || i == 3) { v.position = decal.vec[0]; v.texCoord = glm::vec2(0, 0); } 364 | if (i == 1) { v.position = decal.vec[1]; v.texCoord = glm::vec2(1, 0); } 365 | if (i == 2 || i == 4) { v.position = decal.vec[2]; v.texCoord = glm::vec2(1, 1); } 366 | if (i == 5) { v.position = decal.vec[3]; v.texCoord = glm::vec2(0, 1); } 367 | } 368 | } 369 | 370 | m_decalVbo = m_renderer.createBuffer(vertices.size() * sizeof(Vertex), vertices.data()); 371 | m_decalVao = m_renderer.createInputLayout(*m_decalVbo, { 372 | render::AttributeLayout{ "POSITION", 0, 3, render::AttributeLayout::Type::Float, sizeof(Vertex), offsetof(Vertex, position) }, 373 | render::AttributeLayout{ "NORMAL" , 0, 3, render::AttributeLayout::Type::Float, sizeof(Vertex), offsetof(Vertex, normal ) }, 374 | render::AttributeLayout{ "TEXCOORD", 0, 2, render::AttributeLayout::Type::Float, sizeof(Vertex), offsetof(Vertex, texCoord) }, 375 | render::AttributeLayout{ "TEXCOORD", 1, 2, render::AttributeLayout::Type::Float, sizeof(Vertex), offsetof(Vertex, texCoord) }, // we do not need this one 376 | }); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /thirdparty/imstb_rectpack.h: -------------------------------------------------------------------------------- 1 | // stb_rect_pack.h - v0.11 - public domain - rectangle packing 2 | // Sean Barrett 2014 3 | // 4 | // Useful for e.g. packing rectangular textures into an atlas. 5 | // Does not do rotation. 6 | // 7 | // Not necessarily the awesomest packing method, but better than 8 | // the totally naive one in stb_truetype (which is primarily what 9 | // this is meant to replace). 10 | // 11 | // Has only had a few tests run, may have issues. 12 | // 13 | // More docs to come. 14 | // 15 | // No memory allocations; uses qsort() and assert() from stdlib. 16 | // Can override those by defining STBRP_SORT and STBRP_ASSERT. 17 | // 18 | // This library currently uses the Skyline Bottom-Left algorithm. 19 | // 20 | // Please note: better rectangle packers are welcome! Please 21 | // implement them to the same API, but with a different init 22 | // function. 23 | // 24 | // Credits 25 | // 26 | // Library 27 | // Sean Barrett 28 | // Minor features 29 | // Martins Mozeiko 30 | // github:IntellectualKitty 31 | // 32 | // Bugfixes / warning fixes 33 | // Jeremy Jaussaud 34 | // 35 | // Version history: 36 | // 37 | // 0.11 (2017-03-03) return packing success/fail result 38 | // 0.10 (2016-10-25) remove cast-away-const to avoid warnings 39 | // 0.09 (2016-08-27) fix compiler warnings 40 | // 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) 41 | // 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) 42 | // 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort 43 | // 0.05: added STBRP_ASSERT to allow replacing assert 44 | // 0.04: fixed minor bug in STBRP_LARGE_RECTS support 45 | // 0.01: initial release 46 | // 47 | // LICENSE 48 | // 49 | // See end of file for license information. 50 | 51 | ////////////////////////////////////////////////////////////////////////////// 52 | // 53 | // INCLUDE SECTION 54 | // 55 | 56 | #ifndef STB_INCLUDE_STB_RECT_PACK_H 57 | #define STB_INCLUDE_STB_RECT_PACK_H 58 | 59 | #define STB_RECT_PACK_VERSION 1 60 | 61 | #ifdef STBRP_STATIC 62 | #define STBRP_DEF static 63 | #else 64 | #define STBRP_DEF extern 65 | #endif 66 | 67 | #ifdef __cplusplus 68 | extern "C" { 69 | #endif 70 | 71 | typedef struct stbrp_context stbrp_context; 72 | typedef struct stbrp_node stbrp_node; 73 | typedef struct stbrp_rect stbrp_rect; 74 | 75 | #ifdef STBRP_LARGE_RECTS 76 | typedef int stbrp_coord; 77 | #else 78 | typedef unsigned short stbrp_coord; 79 | #endif 80 | 81 | STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); 82 | // Assign packed locations to rectangles. The rectangles are of type 83 | // 'stbrp_rect' defined below, stored in the array 'rects', and there 84 | // are 'num_rects' many of them. 85 | // 86 | // Rectangles which are successfully packed have the 'was_packed' flag 87 | // set to a non-zero value and 'x' and 'y' store the minimum location 88 | // on each axis (i.e. bottom-left in cartesian coordinates, top-left 89 | // if you imagine y increasing downwards). Rectangles which do not fit 90 | // have the 'was_packed' flag set to 0. 91 | // 92 | // You should not try to access the 'rects' array from another thread 93 | // while this function is running, as the function temporarily reorders 94 | // the array while it executes. 95 | // 96 | // To pack into another rectangle, you need to call stbrp_init_target 97 | // again. To continue packing into the same rectangle, you can call 98 | // this function again. Calling this multiple times with multiple rect 99 | // arrays will probably produce worse packing results than calling it 100 | // a single time with the full rectangle array, but the option is 101 | // available. 102 | // 103 | // The function returns 1 if all of the rectangles were successfully 104 | // packed and 0 otherwise. 105 | 106 | struct stbrp_rect 107 | { 108 | // reserved for your use: 109 | int id; 110 | 111 | // input: 112 | stbrp_coord w, h; 113 | 114 | // output: 115 | stbrp_coord x, y; 116 | int was_packed; // non-zero if valid packing 117 | 118 | }; // 16 bytes, nominally 119 | 120 | 121 | STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); 122 | // Initialize a rectangle packer to: 123 | // pack a rectangle that is 'width' by 'height' in dimensions 124 | // using temporary storage provided by the array 'nodes', which is 'num_nodes' long 125 | // 126 | // You must call this function every time you start packing into a new target. 127 | // 128 | // There is no "shutdown" function. The 'nodes' memory must stay valid for 129 | // the following stbrp_pack_rects() call (or calls), but can be freed after 130 | // the call (or calls) finish. 131 | // 132 | // Note: to guarantee best results, either: 133 | // 1. make sure 'num_nodes' >= 'width' 134 | // or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' 135 | // 136 | // If you don't do either of the above things, widths will be quantized to multiples 137 | // of small integers to guarantee the algorithm doesn't run out of temporary storage. 138 | // 139 | // If you do #2, then the non-quantized algorithm will be used, but the algorithm 140 | // may run out of temporary storage and be unable to pack some rectangles. 141 | 142 | STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); 143 | // Optionally call this function after init but before doing any packing to 144 | // change the handling of the out-of-temp-memory scenario, described above. 145 | // If you call init again, this will be reset to the default (false). 146 | 147 | 148 | STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); 149 | // Optionally select which packing heuristic the library should use. Different 150 | // heuristics will produce better/worse results for different data sets. 151 | // If you call init again, this will be reset to the default. 152 | 153 | enum 154 | { 155 | STBRP_HEURISTIC_Skyline_default=0, 156 | STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, 157 | STBRP_HEURISTIC_Skyline_BF_sortHeight 158 | }; 159 | 160 | 161 | ////////////////////////////////////////////////////////////////////////////// 162 | // 163 | // the details of the following structures don't matter to you, but they must 164 | // be visible so you can handle the memory allocations for them 165 | 166 | struct stbrp_node 167 | { 168 | stbrp_coord x,y; 169 | stbrp_node *next; 170 | }; 171 | 172 | struct stbrp_context 173 | { 174 | int width; 175 | int height; 176 | int align; 177 | int init_mode; 178 | int heuristic; 179 | int num_nodes; 180 | stbrp_node *active_head; 181 | stbrp_node *free_head; 182 | stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' 183 | }; 184 | 185 | #ifdef __cplusplus 186 | } 187 | #endif 188 | 189 | #endif 190 | 191 | ////////////////////////////////////////////////////////////////////////////// 192 | // 193 | // IMPLEMENTATION SECTION 194 | // 195 | 196 | #ifdef STB_RECT_PACK_IMPLEMENTATION 197 | #ifndef STBRP_SORT 198 | #include 199 | #define STBRP_SORT qsort 200 | #endif 201 | 202 | #ifndef STBRP_ASSERT 203 | #include 204 | #define STBRP_ASSERT assert 205 | #endif 206 | 207 | #ifdef _MSC_VER 208 | #define STBRP__NOTUSED(v) (void)(v) 209 | #define STBRP__CDECL __cdecl 210 | #else 211 | #define STBRP__NOTUSED(v) (void)sizeof(v) 212 | #define STBRP__CDECL 213 | #endif 214 | 215 | enum 216 | { 217 | STBRP__INIT_skyline = 1 218 | }; 219 | 220 | STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) 221 | { 222 | switch (context->init_mode) { 223 | case STBRP__INIT_skyline: 224 | STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); 225 | context->heuristic = heuristic; 226 | break; 227 | default: 228 | STBRP_ASSERT(0); 229 | } 230 | } 231 | 232 | STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) 233 | { 234 | if (allow_out_of_mem) 235 | // if it's ok to run out of memory, then don't bother aligning them; 236 | // this gives better packing, but may fail due to OOM (even though 237 | // the rectangles easily fit). @TODO a smarter approach would be to only 238 | // quantize once we've hit OOM, then we could get rid of this parameter. 239 | context->align = 1; 240 | else { 241 | // if it's not ok to run out of memory, then quantize the widths 242 | // so that num_nodes is always enough nodes. 243 | // 244 | // I.e. num_nodes * align >= width 245 | // align >= width / num_nodes 246 | // align = ceil(width/num_nodes) 247 | 248 | context->align = (context->width + context->num_nodes-1) / context->num_nodes; 249 | } 250 | } 251 | 252 | STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) 253 | { 254 | int i; 255 | #ifndef STBRP_LARGE_RECTS 256 | STBRP_ASSERT(width <= 0xffff && height <= 0xffff); 257 | #endif 258 | 259 | for (i=0; i < num_nodes-1; ++i) 260 | nodes[i].next = &nodes[i+1]; 261 | nodes[i].next = NULL; 262 | context->init_mode = STBRP__INIT_skyline; 263 | context->heuristic = STBRP_HEURISTIC_Skyline_default; 264 | context->free_head = &nodes[0]; 265 | context->active_head = &context->extra[0]; 266 | context->width = width; 267 | context->height = height; 268 | context->num_nodes = num_nodes; 269 | stbrp_setup_allow_out_of_mem(context, 0); 270 | 271 | // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) 272 | context->extra[0].x = 0; 273 | context->extra[0].y = 0; 274 | context->extra[0].next = &context->extra[1]; 275 | context->extra[1].x = (stbrp_coord) width; 276 | #ifdef STBRP_LARGE_RECTS 277 | context->extra[1].y = (1<<30); 278 | #else 279 | context->extra[1].y = 65535; 280 | #endif 281 | context->extra[1].next = NULL; 282 | } 283 | 284 | // find minimum y position if it starts at x1 285 | static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) 286 | { 287 | stbrp_node *node = first; 288 | int x1 = x0 + width; 289 | int min_y, visited_width, waste_area; 290 | 291 | STBRP__NOTUSED(c); 292 | 293 | STBRP_ASSERT(first->x <= x0); 294 | 295 | #if 0 296 | // skip in case we're past the node 297 | while (node->next->x <= x0) 298 | ++node; 299 | #else 300 | STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency 301 | #endif 302 | 303 | STBRP_ASSERT(node->x <= x0); 304 | 305 | min_y = 0; 306 | waste_area = 0; 307 | visited_width = 0; 308 | while (node->x < x1) { 309 | if (node->y > min_y) { 310 | // raise min_y higher. 311 | // we've accounted for all waste up to min_y, 312 | // but we'll now add more waste for everything we've visted 313 | waste_area += visited_width * (node->y - min_y); 314 | min_y = node->y; 315 | // the first time through, visited_width might be reduced 316 | if (node->x < x0) 317 | visited_width += node->next->x - x0; 318 | else 319 | visited_width += node->next->x - node->x; 320 | } else { 321 | // add waste area 322 | int under_width = node->next->x - node->x; 323 | if (under_width + visited_width > width) 324 | under_width = width - visited_width; 325 | waste_area += under_width * (min_y - node->y); 326 | visited_width += under_width; 327 | } 328 | node = node->next; 329 | } 330 | 331 | *pwaste = waste_area; 332 | return min_y; 333 | } 334 | 335 | typedef struct 336 | { 337 | int x,y; 338 | stbrp_node **prev_link; 339 | } stbrp__findresult; 340 | 341 | static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) 342 | { 343 | int best_waste = (1<<30), best_x, best_y = (1 << 30); 344 | stbrp__findresult fr; 345 | stbrp_node **prev, *node, *tail, **best = NULL; 346 | 347 | // align to multiple of c->align 348 | width = (width + c->align - 1); 349 | width -= width % c->align; 350 | STBRP_ASSERT(width % c->align == 0); 351 | 352 | node = c->active_head; 353 | prev = &c->active_head; 354 | while (node->x + width <= c->width) { 355 | int y,waste; 356 | y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); 357 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL 358 | // bottom left 359 | if (y < best_y) { 360 | best_y = y; 361 | best = prev; 362 | } 363 | } else { 364 | // best-fit 365 | if (y + height <= c->height) { 366 | // can only use it if it first vertically 367 | if (y < best_y || (y == best_y && waste < best_waste)) { 368 | best_y = y; 369 | best_waste = waste; 370 | best = prev; 371 | } 372 | } 373 | } 374 | prev = &node->next; 375 | node = node->next; 376 | } 377 | 378 | best_x = (best == NULL) ? 0 : (*best)->x; 379 | 380 | // if doing best-fit (BF), we also have to try aligning right edge to each node position 381 | // 382 | // e.g, if fitting 383 | // 384 | // ____________________ 385 | // |____________________| 386 | // 387 | // into 388 | // 389 | // | | 390 | // | ____________| 391 | // |____________| 392 | // 393 | // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned 394 | // 395 | // This makes BF take about 2x the time 396 | 397 | if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { 398 | tail = c->active_head; 399 | node = c->active_head; 400 | prev = &c->active_head; 401 | // find first node that's admissible 402 | while (tail->x < width) 403 | tail = tail->next; 404 | while (tail) { 405 | int xpos = tail->x - width; 406 | int y,waste; 407 | STBRP_ASSERT(xpos >= 0); 408 | // find the left position that matches this 409 | while (node->next->x <= xpos) { 410 | prev = &node->next; 411 | node = node->next; 412 | } 413 | STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); 414 | y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); 415 | if (y + height < c->height) { 416 | if (y <= best_y) { 417 | if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { 418 | best_x = xpos; 419 | STBRP_ASSERT(y <= best_y); 420 | best_y = y; 421 | best_waste = waste; 422 | best = prev; 423 | } 424 | } 425 | } 426 | tail = tail->next; 427 | } 428 | } 429 | 430 | fr.prev_link = best; 431 | fr.x = best_x; 432 | fr.y = best_y; 433 | return fr; 434 | } 435 | 436 | static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) 437 | { 438 | // find best position according to heuristic 439 | stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); 440 | stbrp_node *node, *cur; 441 | 442 | // bail if: 443 | // 1. it failed 444 | // 2. the best node doesn't fit (we don't always check this) 445 | // 3. we're out of memory 446 | if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { 447 | res.prev_link = NULL; 448 | return res; 449 | } 450 | 451 | // on success, create new node 452 | node = context->free_head; 453 | node->x = (stbrp_coord) res.x; 454 | node->y = (stbrp_coord) (res.y + height); 455 | 456 | context->free_head = node->next; 457 | 458 | // insert the new node into the right starting point, and 459 | // let 'cur' point to the remaining nodes needing to be 460 | // stiched back in 461 | 462 | cur = *res.prev_link; 463 | if (cur->x < res.x) { 464 | // preserve the existing one, so start testing with the next one 465 | stbrp_node *next = cur->next; 466 | cur->next = node; 467 | cur = next; 468 | } else { 469 | *res.prev_link = node; 470 | } 471 | 472 | // from here, traverse cur and free the nodes, until we get to one 473 | // that shouldn't be freed 474 | while (cur->next && cur->next->x <= res.x + width) { 475 | stbrp_node *next = cur->next; 476 | // move the current node to the free list 477 | cur->next = context->free_head; 478 | context->free_head = cur; 479 | cur = next; 480 | } 481 | 482 | // stitch the list back in 483 | node->next = cur; 484 | 485 | if (cur->x < res.x + width) 486 | cur->x = (stbrp_coord) (res.x + width); 487 | 488 | #ifdef _DEBUG 489 | cur = context->active_head; 490 | while (cur->x < context->width) { 491 | STBRP_ASSERT(cur->x < cur->next->x); 492 | cur = cur->next; 493 | } 494 | STBRP_ASSERT(cur->next == NULL); 495 | 496 | { 497 | int count=0; 498 | cur = context->active_head; 499 | while (cur) { 500 | cur = cur->next; 501 | ++count; 502 | } 503 | cur = context->free_head; 504 | while (cur) { 505 | cur = cur->next; 506 | ++count; 507 | } 508 | STBRP_ASSERT(count == context->num_nodes+2); 509 | } 510 | #endif 511 | 512 | return res; 513 | } 514 | 515 | static int STBRP__CDECL rect_height_compare(const void *a, const void *b) 516 | { 517 | const stbrp_rect *p = (const stbrp_rect *) a; 518 | const stbrp_rect *q = (const stbrp_rect *) b; 519 | if (p->h > q->h) 520 | return -1; 521 | if (p->h < q->h) 522 | return 1; 523 | return (p->w > q->w) ? -1 : (p->w < q->w); 524 | } 525 | 526 | static int STBRP__CDECL rect_original_order(const void *a, const void *b) 527 | { 528 | const stbrp_rect *p = (const stbrp_rect *) a; 529 | const stbrp_rect *q = (const stbrp_rect *) b; 530 | return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); 531 | } 532 | 533 | #ifdef STBRP_LARGE_RECTS 534 | #define STBRP__MAXVAL 0xffffffff 535 | #else 536 | #define STBRP__MAXVAL 0xffff 537 | #endif 538 | 539 | STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) 540 | { 541 | int i, all_rects_packed = 1; 542 | 543 | // we use the 'was_packed' field internally to allow sorting/unsorting 544 | for (i=0; i < num_rects; ++i) { 545 | rects[i].was_packed = i; 546 | #ifndef STBRP_LARGE_RECTS 547 | STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); 548 | #endif 549 | } 550 | 551 | // sort according to heuristic 552 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); 553 | 554 | for (i=0; i < num_rects; ++i) { 555 | if (rects[i].w == 0 || rects[i].h == 0) { 556 | rects[i].x = rects[i].y = 0; // empty rect needs no space 557 | } else { 558 | stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); 559 | if (fr.prev_link) { 560 | rects[i].x = (stbrp_coord) fr.x; 561 | rects[i].y = (stbrp_coord) fr.y; 562 | } else { 563 | rects[i].x = rects[i].y = STBRP__MAXVAL; 564 | } 565 | } 566 | } 567 | 568 | // unsort 569 | STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); 570 | 571 | // set was_packed flags and all_rects_packed status 572 | for (i=0; i < num_rects; ++i) { 573 | rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); 574 | if (!rects[i].was_packed) 575 | all_rects_packed = 0; 576 | } 577 | 578 | // return the all_rects_packed status 579 | return all_rects_packed; 580 | } 581 | #endif 582 | 583 | /* 584 | ------------------------------------------------------------------------------ 585 | This software is available under 2 licenses -- choose whichever you prefer. 586 | ------------------------------------------------------------------------------ 587 | ALTERNATIVE A - MIT License 588 | Copyright (c) 2017 Sean Barrett 589 | Permission is hereby granted, free of charge, to any person obtaining a copy of 590 | this software and associated documentation files (the "Software"), to deal in 591 | the Software without restriction, including without limitation the rights to 592 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 593 | of the Software, and to permit persons to whom the Software is furnished to do 594 | so, subject to the following conditions: 595 | The above copyright notice and this permission notice shall be included in all 596 | copies or substantial portions of the Software. 597 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 598 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 599 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 600 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 601 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 602 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 603 | SOFTWARE. 604 | ------------------------------------------------------------------------------ 605 | ALTERNATIVE B - Public Domain (www.unlicense.org) 606 | This is free and unencumbered software released into the public domain. 607 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 608 | software, either in source code form or as a compiled binary, for any purpose, 609 | commercial or non-commercial, and by any means. 610 | In jurisdictions that recognize copyright laws, the author or authors of this 611 | software dedicate any and all copyright interest in the software to the public 612 | domain. We make this dedication for the benefit of the public at large and to 613 | the detriment of our heirs and successors. We intend this dedication to be an 614 | overt act of relinquishment in perpetuity of all present and future rights to 615 | this software under copyright law. 616 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 617 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 618 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 619 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 620 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 621 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 622 | ------------------------------------------------------------------------------ 623 | */ 624 | -------------------------------------------------------------------------------- /src/Bsp.cpp: -------------------------------------------------------------------------------- 1 | #include "Bsp.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "IO.h" 11 | 12 | namespace { 13 | const auto WAD_DIR = fs::path("../data/wads"); 14 | const auto SKY_DIR = fs::path("../data/textures/sky"); 15 | } 16 | 17 | void Bsp::LoadWadFiles(std::string wadStr) { 18 | for (auto& c : wadStr) 19 | if (c == '\\') 20 | c = '/'; 21 | 22 | int nWadCount = 0; 23 | std::size_t pos = 0; 24 | while (true) { 25 | pos++; 26 | const auto next = wadStr.find(';', pos); 27 | if (next == std::string::npos) 28 | break; 29 | auto path = wadStr.substr(pos, next - pos); 30 | 31 | // remove leading path except the parent folder of the wad file 32 | auto it = path.rfind('/'); 33 | if (it != std::string::npos) { 34 | it = path.rfind('/', it - 1); 35 | if (it != std::string::npos) 36 | path.erase(0, it + 1); 37 | } 38 | 39 | wadFiles.emplace_back(WAD_DIR / path); 40 | std::clog << "#" << std::setw(2) << nWadCount++ << " Loaded " << path << "\n"; 41 | pos = next; 42 | } 43 | 44 | std::clog << "Loaded " << nWadCount << " WADs "; 45 | std::clog << "OK\n"; 46 | } 47 | 48 | void Bsp::UnloadWadFiles() { 49 | wadFiles.clear(); 50 | } 51 | 52 | void Bsp::LoadTextures(std::ifstream& file) { 53 | std::clog << "Loading WADs ...\n"; 54 | if (const auto worldSpawn = FindEntity("worldspawn")) 55 | if (const auto wad = worldSpawn->findProperty("wad")) 56 | LoadWadFiles(*wad); 57 | 58 | std::clog << "Loading textures ...\n"; 59 | 60 | // generate textures from OpenGL 61 | m_textures.reserve(textureHeader.mipTextureCount); 62 | 63 | std::size_t errors = 0; 64 | for (unsigned int i = 0; i < textureHeader.mipTextureCount; i++) { 65 | MipmapTexture& mipTexture = m_textures.emplace_back(); 66 | 67 | if (mipTextures[i].offsets[0] == 0) { 68 | // texture is stored externally 69 | if (auto tex = LoadTextureFromWads(mipTextures[i].name)) 70 | mipTexture = std::move(*tex); 71 | else { 72 | std::clog << "Failed to load texture " << mipTextures[i].name << " from WAD files\n"; 73 | continue; 74 | } 75 | } else { 76 | // internal texture 77 | const auto dataSize = sizeof(uint8_t) * (mipTextures[i].offsets[3] + (mipTextures[i].height / 8) * (mipTextures[i].width / 8) + 2 + 768); 78 | std::vector imgData(dataSize); 79 | 80 | file.seekg(header.lump[bsp30::LumpType::LUMP_TEXTURES].offset + mipTextureOffsets[i]); 81 | readVector(file, imgData); 82 | 83 | Wad::CreateMipTexture(imgData, mipTexture); 84 | } 85 | } 86 | 87 | UnloadWadFiles(); 88 | 89 | std::clog << "Loaded " << textureHeader.mipTextureCount << " textures, " << errors << " failed "; 90 | if (errors == 0) 91 | std::clog << "OK\n"; 92 | else 93 | std::clog << "ERRORS\n"; 94 | 95 | // calculate texture coordinates 96 | faceTexCoords.resize(faces.size()); 97 | for (int i = 0; i < faces.size(); ++i) { 98 | faceTexCoords[i].texCoords.resize(faces[i].edgeCount); 99 | 100 | const auto& curTexInfo = textureInfos[faces[i].textureInfo]; 101 | 102 | for (int j = 0; j < faces[i].edgeCount; j++) { 103 | int edgeIndex = surfEdges[faces[i].firstEdgeIndex + j]; // This gives the index into the edge lump 104 | if (edgeIndex > 0) { 105 | faceTexCoords[i].texCoords[j].s = (glm::dot(vertices[edges[edgeIndex].vertexIndex[0]], curTexInfo.s) + curTexInfo.sShift) / mipTextures[curTexInfo.miptexIndex].width; 106 | faceTexCoords[i].texCoords[j].t = (glm::dot(vertices[edges[edgeIndex].vertexIndex[0]], curTexInfo.t) + curTexInfo.tShift) / mipTextures[curTexInfo.miptexIndex].height; 107 | } else { 108 | edgeIndex *= -1; 109 | faceTexCoords[i].texCoords[j].s = (glm::dot(vertices[edges[edgeIndex].vertexIndex[1]], curTexInfo.s) + curTexInfo.sShift) / mipTextures[curTexInfo.miptexIndex].width; 110 | faceTexCoords[i].texCoords[j].t = (glm::dot(vertices[edges[edgeIndex].vertexIndex[1]], curTexInfo.t) + curTexInfo.tShift) / mipTextures[curTexInfo.miptexIndex].height; 111 | } 112 | } 113 | } 114 | } 115 | 116 | auto Bsp::LoadTextureFromWads(const char* name) -> std::optional { 117 | for (auto& wad : wadFiles) 118 | if (auto pMipMapTex = wad.loadTexture(name)) 119 | return pMipMapTex; 120 | return {}; 121 | } 122 | 123 | auto Bsp::LoadDecalTexture(const char* name) -> std::optional { 124 | for (auto& decalWad : decalWads) 125 | if (auto pMipMapTex = decalWad.LoadDecalTexture(name)) 126 | return pMipMapTex; 127 | return {}; 128 | } 129 | 130 | void Bsp::LoadDecals() { 131 | // load Decal WADs 132 | decalWads.emplace_back(WAD_DIR / "valve/decals.wad"); 133 | decalWads.emplace_back(WAD_DIR / "cstrike/decals.wad"); 134 | 135 | // Count decals 136 | const auto& infodecals = FindEntities("infodecal"); 137 | if (infodecals.empty()) { 138 | std::clog << "(no decals)\n"; 139 | return; 140 | } 141 | 142 | // Texture name table for texture loading 143 | std::unordered_map loadedTex; 144 | 145 | m_decals.reserve(infodecals.size()); 146 | 147 | // Process each decal 148 | for (const auto& infodecal : infodecals) { 149 | if (auto originStr = infodecal->findProperty("origin")) { 150 | int x, y, z; 151 | std::stringstream(*originStr) >> x >> y >> z; 152 | 153 | const glm::vec3 origin{x, y, z}; 154 | const auto leaf = findLeaf(origin); 155 | if (!leaf) { 156 | std::clog << "ERROR finding decal leaf\n"; 157 | continue; 158 | } 159 | 160 | // Loop through each face in this leaf 161 | for (int j = 0; j < leaves[*leaf].markSurfaceCount; j++) { 162 | // Find face 163 | const auto& face = faces[markSurfaces[leaves[*leaf].firstMarkSurface + j]]; 164 | 165 | // Find normal 166 | glm::vec3 normal = planes[face.planeIndex].normal; 167 | 168 | // Find a vertex on the face 169 | glm::vec3 vertex; 170 | const int iEdge = surfEdges[face.firstEdgeIndex]; // This gives the index into the edge lump 171 | if (iEdge > 0) 172 | vertex = vertices[edges[iEdge].vertexIndex[0]]; 173 | else 174 | vertex = vertices[edges[-iEdge].vertexIndex[1]]; 175 | 176 | // Check if decal origin is in this face 177 | if (PointInPlane(origin, normal, glm::dot(normal, vertex))) { 178 | // texture 179 | const auto texName = infodecal->findProperty("texture"); 180 | if (!texName) { 181 | std::clog << "ERROR retrieving texture name from decal\n"; 182 | break; 183 | } 184 | 185 | // Check if texture has already been loaded 186 | auto it = loadedTex.find(*texName); 187 | if (it == end(loadedTex)) { 188 | // Load new texture 189 | auto mipTex = LoadDecalTexture(texName->c_str()); 190 | if (!mipTex) { 191 | std::clog << "ERROR loading mipTexture " << texName << "\n"; 192 | break; 193 | } 194 | it = loadedTex.emplace(*texName, m_textures.size()).first; 195 | m_textures.emplace_back(std::move(*mipTex)); 196 | } 197 | 198 | const auto& texIndex = it->second; 199 | const auto& img0 = m_textures[texIndex].Img[0]; 200 | 201 | const float h2 = img0.height / 2.0f; 202 | const float w2 = img0.width / 2.0f; 203 | 204 | const auto& s = textureInfos[face.textureInfo].s; 205 | const auto& t = textureInfos[face.textureInfo].t; 206 | 207 | auto& decal = m_decals.emplace_back(); 208 | decal.normal = normal; 209 | decal.texIndex = it->second; 210 | decal.vec[0] = origin - t * h2 - s * w2; 211 | decal.vec[1] = origin - t * h2 + s * w2; 212 | decal.vec[2] = origin + t * h2 + s * w2; 213 | decal.vec[3] = origin + t * h2 - s * w2; 214 | 215 | break; 216 | } 217 | } 218 | } 219 | } 220 | 221 | std::clog << "Loaded " << m_decals.size() << " decals, " << loadedTex.size() << " decal textures\n"; 222 | } 223 | 224 | void Bsp::LoadLightMaps(const std::vector& pLightMapData) { 225 | std::int64_t loadedBytes = 0; 226 | std::size_t loadedLightmaps = 0; 227 | 228 | for (int i = 0; i < faces.size(); i++) { 229 | if (faces[i].styles[0] == 0 && static_cast(faces[i].lightmapOffset) >= -1) { 230 | faceTexCoords[i].lightmapCoords.resize(faces[i].edgeCount); 231 | 232 | /* *********** QRAD ********** */ 233 | 234 | float fMinU = 999999; 235 | float fMinV = 999999; 236 | float fMaxU = -99999; 237 | float fMaxV = -99999; 238 | 239 | const auto& texInfo = textureInfos[faces[i].textureInfo]; 240 | for (int j = 0; j < faces[i].edgeCount; j++) { 241 | int iEdge = surfEdges[faces[i].firstEdgeIndex + j]; 242 | const auto vertex = [&] { 243 | if (iEdge >= 0) 244 | return vertices[edges[iEdge].vertexIndex[0]]; 245 | else 246 | return vertices[edges[-iEdge].vertexIndex[1]]; 247 | }(); 248 | 249 | float fU = glm::dot(texInfo.s, vertex) + texInfo.sShift; 250 | if (fU < fMinU) 251 | fMinU = fU; 252 | if (fU > fMaxU) 253 | fMaxU = fU; 254 | 255 | float fV = glm::dot(texInfo.t, vertex) + texInfo.tShift; 256 | if (fV < fMinV) 257 | fMinV = fV; 258 | if (fV > fMaxV) 259 | fMaxV = fV; 260 | } 261 | 262 | auto fTexMinU = floor(fMinU / 16.0f); 263 | auto fTexMinV = floor(fMinV / 16.0f); 264 | auto fTexMaxU = ceil(fMaxU / 16.0f); 265 | auto fTexMaxV = ceil(fMaxV / 16.0f); 266 | 267 | int nWidth = static_cast(fTexMaxU - fTexMinU) + 1; 268 | int nHeight = static_cast(fTexMaxV - fTexMinV) + 1; 269 | 270 | /* *********** end QRAD ********* */ 271 | 272 | /* ********** http://www.gamedev.net/community/forums/topic.asp?topic_id=538713 (last refresh: 20.02.2010) ********** */ 273 | 274 | float fMidPolyU = (fMinU + fMaxU) / 2.0f; 275 | float fMidPolyV = (fMinV + fMaxV) / 2.0f; 276 | float fMidTexU = static_cast(nWidth) / 2.0f; 277 | float fMidTexV = static_cast(nHeight) / 2.0f; 278 | 279 | for (int j = 0; j < faces[i].edgeCount; ++j) { 280 | int iEdge = surfEdges[faces[i].firstEdgeIndex + j]; 281 | const auto vertex = [&] { 282 | if (iEdge >= 0) 283 | return vertices[edges[iEdge].vertexIndex[0]]; 284 | else 285 | return vertices[edges[-iEdge].vertexIndex[1]]; 286 | }(); 287 | 288 | float fU = glm::dot(texInfo.s, vertex) + texInfo.sShift; 289 | float fV = glm::dot(texInfo.t, vertex) + texInfo.tShift; 290 | 291 | float fLightMapU = fMidTexU + (fU - fMidPolyU) / 16.0f; 292 | float fLightMapV = fMidTexV + (fV - fMidPolyV) / 16.0f; 293 | 294 | faceTexCoords[i].lightmapCoords[j].s = fLightMapU / static_cast(nWidth); 295 | faceTexCoords[i].lightmapCoords[j].t = fLightMapV / static_cast(nHeight); 296 | } 297 | 298 | /* ********** end http://www.gamedev.net/community/forums/topic.asp?topic_id=538713 ********** */ 299 | 300 | Image& image = m_lightmaps.emplace_back(nWidth, nHeight, 3); 301 | memcpy(image.data.data(), &pLightMapData[faces[i].lightmapOffset], nWidth * nHeight * 3 * sizeof(unsigned char)); 302 | 303 | loadedLightmaps++; 304 | loadedBytes += nWidth * nHeight * 3; 305 | } else 306 | m_lightmaps.emplace_back(); 307 | } 308 | 309 | std::clog << "Loaded " << loadedLightmaps << " lightmaps, lightmapdatadiff: " << loadedBytes - header.lump[bsp30::LumpType::LUMP_LIGHTING].length << " bytes "; 310 | if ((loadedBytes - header.lump[bsp30::LumpType::LUMP_LIGHTING].length) == 0) 311 | std::clog << "OK\n"; 312 | else 313 | std::clog << "ERRORS\n"; 314 | } 315 | 316 | void Bsp::LoadModels(std::ifstream& file) { 317 | std::vector submodels; 318 | submodels.resize(header.lump[bsp30::LumpType::LUMP_MODELS].length / sizeof(bsp30::Model)); 319 | file.seekg(header.lump[bsp30::LumpType::LUMP_MODELS].offset); 320 | readVector(file, submodels); 321 | 322 | // hull 0 is created from normal BSP nodes 323 | { 324 | hull0ClipNodes.resize(nodes.size()); 325 | std::transform(begin(nodes), end(nodes), begin(hull0ClipNodes), [&](const bsp30::Node& n) { 326 | bsp30::ClipNode cn; 327 | cn.planeIndex = n.planeIndex; 328 | for (int j = 0; j < 2; j++) { 329 | if (n.childIndex[j] < 0) 330 | cn.childIndex[j] = leaves[~n.childIndex[j]].content; 331 | else 332 | cn.childIndex[j] = n.childIndex[j]; 333 | } 334 | return cn; 335 | }); 336 | } 337 | 338 | // prepare model 0 339 | auto& model0 = models.emplace_back(); 340 | { 341 | auto& hull = model0.hulls[0]; 342 | hull.clipnodes = hull0ClipNodes.data(); 343 | hull.firstclipnode = 0; 344 | hull.lastclipnode = hull0ClipNodes.size() - 1; 345 | hull.planes = planes.data(); 346 | 347 | } 348 | for (auto i : {1, 2, 3}) { 349 | auto& hull = model0.hulls[i]; 350 | hull.clipnodes = clipNodes.data(); 351 | hull.firstclipnode = 0; 352 | hull.lastclipnode = clipNodes.size() - 1; 353 | hull.planes = planes.data(); 354 | } 355 | 356 | { 357 | auto& hull = model0.hulls[1]; 358 | hull.clipMins[0] = -16; 359 | hull.clipMins[1] = -16; 360 | hull.clipMins[2] = -36; 361 | hull.clipMaxs[0] = 16; 362 | hull.clipMaxs[1] = 16; 363 | hull.clipMaxs[2] = 36; 364 | } 365 | { 366 | auto& hull = model0.hulls[2]; 367 | hull.clipMins[0] = -32; 368 | hull.clipMins[1] = -32; 369 | hull.clipMins[2] = -32; 370 | hull.clipMaxs[0] = 32; 371 | hull.clipMaxs[1] = 32; 372 | hull.clipMaxs[2] = 32; 373 | } 374 | { 375 | auto& hull = model0.hulls[3]; 376 | hull.clipMins[0] = -16; 377 | hull.clipMins[1] = -16; 378 | hull.clipMins[2] = -18; 379 | hull.clipMaxs[0] = 16; 380 | hull.clipMaxs[1] = 16; 381 | hull.clipMaxs[2] = 18; 382 | } 383 | 384 | // sub models after model 0 385 | for (auto i = 0; i < submodels.size(); i++) { 386 | if (i != 0) 387 | models.push_back(models.back()); // start with a copy of the last one 388 | assert(models.size() == i + 1); 389 | 390 | auto& mod = models.back(); 391 | (bsp30::Model&)mod = submodels[i]; 392 | mod.hulls[0].firstclipnode = mod.headNodesIndex[0]; 393 | for (int j = 1; j < bsp30::MAX_MAP_HULLS; j++) { 394 | mod.hulls[j].firstclipnode = mod.headNodesIndex[j]; 395 | mod.hulls[j].lastclipnode = clipNodes.size() - 1; 396 | } 397 | } 398 | } 399 | 400 | // Checks if an entity is a valid brush entity (has a model) 401 | auto IsBrushEntity(const Entity& e) -> bool { 402 | if (e.findProperty("model") != nullptr) { 403 | if (auto classname = e.findProperty("classname")) { 404 | if (*classname == "func_door_rotating" || 405 | *classname == "func_door" || 406 | *classname == "func_illusionary" || 407 | *classname == "func_wall" || 408 | *classname == "func_breakable" || 409 | *classname == "func_button") 410 | return true; 411 | } 412 | } 413 | 414 | return false; 415 | } 416 | 417 | void Bsp::ParseEntities(const std::string& entitiesString) { 418 | std::size_t pos = 0; 419 | while (true) { 420 | pos = entitiesString.find('{', pos); 421 | if (pos == std::string::npos) 422 | break; 423 | 424 | auto end = entitiesString.find('}', pos); 425 | entities.emplace_back(entitiesString.substr(pos + 1, end - pos - 1)); 426 | pos = end + 1; 427 | } 428 | } 429 | 430 | void Bsp::CountVisLeafs(int iNode, int& count) { 431 | if (iNode < 0) { 432 | if (iNode == -1) // leaf 0 433 | return; 434 | 435 | if (leaves[~iNode].content == bsp30::CONTENTS_SOLID) 436 | return; 437 | 438 | count++; 439 | return; 440 | } 441 | 442 | CountVisLeafs(nodes[iNode].childIndex[0], count); 443 | CountVisLeafs(nodes[iNode].childIndex[1], count); 444 | } 445 | 446 | auto Bsp::decompressVIS(int leaf, const std::vector& compressedVis) const -> boost::dynamic_bitset { 447 | boost::dynamic_bitset pvs; 448 | pvs.reserve(leaves.size() - 1); 449 | 450 | const auto* read = &compressedVis[leaves[leaf].visOffset]; 451 | //const auto* end = compressedVis.data() + (leaves[leaf + 1].visOffset == -1 ? header.lump[bsp30::LUMP_VISIBILITY].length : leaves[leaf + 1].visOffset); 452 | 453 | const auto row = (visLists.size() + 7) / 8; 454 | while (pvs.size() / 8 < row) { 455 | if (*read) 456 | pvs.append(*read); 457 | else { 458 | // run of 0s, number of 0s is stored in next byte 459 | read++; 460 | for (auto i = 0; i < *read; i++) { 461 | pvs.append(0x00); 462 | if (pvs.size() / 8 >= row) 463 | break; 464 | } 465 | } 466 | 467 | read++; 468 | } 469 | 470 | //std::clog << "PVS for leaf " << leaf << "\n"; 471 | //std::clog << "read: " << (void*)read << "\n"; 472 | //std::clog << "end: " << (void*)end << "\n"; 473 | //std::clog << "diff: " << (end - read) << "\n"; 474 | 475 | return pvs; 476 | } 477 | 478 | auto Bsp::findLeaf(glm::vec3 pos, int node) const -> std::optional { 479 | // Run once for each child 480 | for (const auto& childIndex : nodes[node].childIndex) { 481 | // If the index is positive it is an index into the nodes array 482 | if (childIndex >= 0) { 483 | if (PointInBox(pos, nodes[childIndex].lower, nodes[childIndex].upper)) 484 | return findLeaf(pos, childIndex); 485 | } 486 | // Else, bitwise inversed, it is an index into the leaf array 487 | // Do not test solid leaf 0 488 | else if (~childIndex != 0) { 489 | if (PointInBox(pos, leaves[~childIndex].lower, leaves[~childIndex].upper)) 490 | return ~childIndex; 491 | } 492 | } 493 | 494 | return {}; 495 | } 496 | 497 | Bsp::Bsp(const fs::path& filename) { 498 | std::ifstream file(filename, std::ios::binary); 499 | if (!file) 500 | throw std::ios::failure("Failed to open file " + filename.string() + " for reading"); 501 | 502 | std::clog << "LOADING BSP FILE: " << filename << "\n"; 503 | 504 | // Read in the header 505 | read(file, header); 506 | if (header.version != 30) 507 | throw std::runtime_error("Invalid BSP version (" + std::to_string(header.version) + ") instead of 30)"); 508 | 509 | // ================================================================= 510 | // Get number of elements and allocate memory for them 511 | // ================================================================= 512 | 513 | nodes.resize(header.lump[bsp30::LumpType::LUMP_NODES].length / sizeof(bsp30::Node)); 514 | leaves.resize(header.lump[bsp30::LumpType::LUMP_LEAFS].length / sizeof(bsp30::Leaf)); 515 | markSurfaces.resize(header.lump[bsp30::LumpType::LUMP_MARKSURFACES].length / sizeof(bsp30::MarkSurface)); 516 | faces.resize(header.lump[bsp30::LumpType::LUMP_FACES].length / sizeof(bsp30::Face)); 517 | clipNodes.resize(header.lump[bsp30::LumpType::LUMP_CLIPNODES].length / sizeof(bsp30::ClipNode)); 518 | surfEdges.resize(header.lump[bsp30::LumpType::LUMP_SURFEDGES].length / sizeof(bsp30::SurfEdge)); 519 | edges.resize(header.lump[bsp30::LumpType::LUMP_EDGES].length / sizeof(bsp30::Edge)); 520 | vertices.resize(header.lump[bsp30::LumpType::LUMP_VERTEXES].length / sizeof(bsp30::Vertex)); 521 | planes.resize(header.lump[bsp30::LumpType::LUMP_PLANES].length / sizeof(bsp30::Plane)); 522 | 523 | // ================================================================= 524 | // Seek to and read in the data 525 | // ================================================================= 526 | 527 | file.seekg(header.lump[bsp30::LumpType::LUMP_NODES].offset); 528 | readVector(file, nodes); 529 | 530 | file.seekg(header.lump[bsp30::LumpType::LUMP_LEAFS].offset); 531 | readVector(file, leaves); 532 | 533 | file.seekg(header.lump[bsp30::LumpType::LUMP_MARKSURFACES].offset); 534 | readVector(file, markSurfaces); 535 | 536 | file.seekg(header.lump[bsp30::LumpType::LUMP_FACES].offset); 537 | readVector(file, faces); 538 | 539 | file.seekg(header.lump[bsp30::LumpType::LUMP_CLIPNODES].offset); 540 | readVector(file, clipNodes); 541 | 542 | file.seekg(header.lump[bsp30::LumpType::LUMP_SURFEDGES].offset); 543 | readVector(file, surfEdges); 544 | 545 | file.seekg(header.lump[bsp30::LumpType::LUMP_EDGES].offset); 546 | readVector(file, edges); 547 | 548 | file.seekg(header.lump[bsp30::LumpType::LUMP_VERTEXES].offset); 549 | readVector(file, vertices); 550 | 551 | file.seekg(header.lump[bsp30::LumpType::LUMP_PLANES].offset); 552 | readVector(file, planes); 553 | 554 | LoadModels(file); 555 | 556 | // =========================== 557 | // Entity Operations 558 | // =========================== 559 | 560 | std::string entityBuffer; 561 | entityBuffer.resize(header.lump[bsp30::LumpType::LUMP_ENTITIES].length); 562 | file.seekg(header.lump[bsp30::LumpType::LUMP_ENTITIES].offset); 563 | file.read(entityBuffer.data(), entityBuffer.size()); 564 | ParseEntities(std::move(entityBuffer)); 565 | 566 | // =========================== 567 | // Texture Operations 568 | // =========================== 569 | 570 | textureInfos.resize(header.lump[bsp30::LumpType::LUMP_TEXINFO].length / sizeof(bsp30::TextureInfo)); 571 | file.seekg(header.lump[bsp30::LumpType::LUMP_TEXINFO].offset); 572 | readVector(file, textureInfos); 573 | 574 | file.seekg(header.lump[bsp30::LumpType::LUMP_TEXTURES].offset); 575 | read(file, textureHeader); 576 | 577 | mipTextures.resize(textureHeader.mipTextureCount); 578 | mipTextureOffsets.resize(textureHeader.mipTextureCount); 579 | readVector(file, mipTextureOffsets); 580 | 581 | for (unsigned int i = 0; i < textureHeader.mipTextureCount; i++) { 582 | file.seekg(header.lump[bsp30::LumpType::LUMP_TEXTURES].offset + mipTextureOffsets[i]); 583 | read(file, mipTextures[i]); 584 | } 585 | 586 | // Load in the texture images and calculate the texture cordinates for each vertex to save render time 587 | LoadTextures(file); 588 | 589 | // =========================== 590 | // Lightmap Operations 591 | // =========================== 592 | 593 | std::clog << "Loading lightmaps ...\n"; 594 | if (header.lump[bsp30::LumpType::LUMP_LIGHTING].length == 0) 595 | std::clog << "No lightmapdata found\n"; 596 | else { 597 | std::vector pLightMapData(header.lump[bsp30::LumpType::LUMP_LIGHTING].length); 598 | file.seekg(header.lump[bsp30::LumpType::LUMP_LIGHTING].offset); 599 | readVector(file, pLightMapData); 600 | 601 | LoadLightMaps(pLightMapData); 602 | } 603 | 604 | // =========================== 605 | // Decal Operations 606 | // =========================== 607 | 608 | std::clog << "Loading decals ...\n"; 609 | LoadDecals(); 610 | 611 | // =========================== 612 | // Visibility List Operations 613 | // =========================== 614 | 615 | if (header.lump[bsp30::LumpType::LUMP_VISIBILITY].length > 0) { 616 | // Allocate memory for the compressed vis lists 617 | std::vector compressedVis(header.lump[bsp30::LumpType::LUMP_VISIBILITY].length); 618 | 619 | file.seekg(header.lump[bsp30::LumpType::LUMP_VISIBILITY].offset); 620 | readVector(file, compressedVis); 621 | 622 | std::clog << "Decompressing VIS ...\n"; 623 | 624 | int count = 0; 625 | CountVisLeafs(0, count); 626 | 627 | visLists.resize(count); 628 | 629 | for (int i = 0; i < count; i++) { 630 | if (leaves[i + 1].visOffset >= 0) 631 | visLists[i] = decompressVIS(i + 1, compressedVis); 632 | } 633 | } else 634 | std::clog << "No VIS found\n"; 635 | 636 | file.close(); 637 | 638 | // create brush and special entities 639 | for (auto& e : entities) { 640 | if (IsBrushEntity(e)) { 641 | brushEntities.push_back(static_cast(&e - &entities[0])); 642 | 643 | // if entity has property "origin" apply to model struct for rendering 644 | if (auto szOrigin = e.findProperty("origin")) { 645 | const int iModel = std::atoi(&e.findProperty("model")->c_str()[1]); 646 | auto& origin = models[iModel].origin; // TODO 647 | sscanf(szOrigin->c_str(), "%f %f %f", &origin.x, &origin.y, &origin.z); 648 | } 649 | } else 650 | specialEntities.push_back(static_cast(&e - &entities[0])); 651 | } 652 | 653 | // order brush entities so that those with RENDER_MODE_TEXTURE are at the back 654 | std::partition(begin(brushEntities), end(brushEntities), [this](unsigned int i) { 655 | if (auto szRenderMode1 = entities[i].findProperty("rendermode")) 656 | if (static_cast(std::stoi(*szRenderMode1)) == bsp30::RENDER_MODE_TEXTURE) 657 | return false; 658 | return true; 659 | }); 660 | 661 | std::clog << "FINISHED LOADING BSP\n"; 662 | } 663 | 664 | Bsp::~Bsp() = default; 665 | 666 | auto Bsp::FindEntity(std::string_view name) -> Entity* { 667 | for (auto& e : entities) 668 | if (auto classname = e.findProperty("classname")) 669 | if (*classname == name) 670 | return &e; 671 | return nullptr; 672 | } 673 | 674 | auto Bsp::FindEntity(std::string_view name) const -> const Entity* { 675 | return const_cast(*this).FindEntity(name); 676 | } 677 | 678 | std::vector Bsp::FindEntities(std::string_view name) { 679 | std::vector result; 680 | for (auto& e : entities) 681 | if (auto classname = e.findProperty("classname")) 682 | if (*classname == name) 683 | result.push_back(&e); 684 | return result; 685 | } 686 | 687 | auto Bsp::loadSkyBox() const -> std::optional> { 688 | const auto worldspawn = FindEntity("worldspawn"); 689 | if (worldspawn == nullptr) 690 | return {}; 691 | const auto skyname = worldspawn->findProperty("skyname"); 692 | if (skyname == nullptr) 693 | return {}; // we don't have a sky texture 694 | 695 | char size[6][3] = {"ft", "bk", "up", "dn", "rt", "lf"}; 696 | std::array result; 697 | for (auto i = 0; i < 6; i++) 698 | result[i] = Image(SKY_DIR / (*skyname + size[i] + ".tga")); 699 | return result; 700 | } 701 | --------------------------------------------------------------------------------