├── images ├── screen1.png ├── screen2.png ├── screen3.png ├── screen4.gif └── screen5.png ├── rtdoom ├── pch.cpp ├── Painter.cpp ├── packages.config ├── GameState.h ├── pch.h ├── GLViewport.h ├── rtdoom.h ├── FrameBuffer.cpp ├── MapRenderer.h ├── WireframePainter.h ├── GLRenderer.h ├── Painter.h ├── SolidPainter.h ├── GameState.cpp ├── TexturePainter.h ├── Viewport.h ├── Helpers.cpp ├── MathCache.h ├── Renderer.h ├── WireframePainter.cpp ├── Projection.h ├── Helpers.h ├── FrameBuffer32.h ├── FrameBuffer.h ├── SolidPainter.cpp ├── GLViewport.cpp ├── GameLoop.h ├── Renderer.cpp ├── SoftwareRenderer.h ├── MapDef.h ├── MapRenderer.cpp ├── GLContext.h ├── MapStore.cpp ├── WADFile.h ├── Viewport.cpp ├── MathCache.cpp ├── GameLoop.cpp ├── MapStore.h ├── MapStructs.h ├── FrameBuffer32.cpp ├── rtdoom.vcxproj.filters ├── Frame.h ├── Projection.cpp ├── TexturePainter.cpp ├── Frame.cpp ├── main.cpp ├── GLContext.cpp ├── glad │ └── KHR │ │ └── khrplatform.h ├── rtdoom.vcxproj ├── MapDef.cpp ├── GLRenderer.cpp └── WADFile.cpp ├── LICENSE ├── rtdoom.sln ├── README.md ├── .gitattributes ├── .clang-format └── .gitignore /images/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mausimus/rtdoom/HEAD/images/screen1.png -------------------------------------------------------------------------------- /images/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mausimus/rtdoom/HEAD/images/screen2.png -------------------------------------------------------------------------------- /images/screen3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mausimus/rtdoom/HEAD/images/screen3.png -------------------------------------------------------------------------------- /images/screen4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mausimus/rtdoom/HEAD/images/screen4.gif -------------------------------------------------------------------------------- /images/screen5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mausimus/rtdoom/HEAD/images/screen5.png -------------------------------------------------------------------------------- /rtdoom/pch.cpp: -------------------------------------------------------------------------------- 1 | // pch.cpp: source file corresponding to pre-compiled header; necessary for compilation to succeed 2 | 3 | #include "pch.h" 4 | 5 | // In general, ignore this file, but keep it around if you are using pre-compiled headers. -------------------------------------------------------------------------------- /rtdoom/Painter.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Painter.h" 3 | 4 | namespace rtdoom 5 | { 6 | Painter::Painter(FrameBuffer& frameBuffer) : m_frameBuffer(frameBuffer) {} 7 | 8 | Painter::~Painter() {} 9 | } // namespace rtdoom 10 | -------------------------------------------------------------------------------- /rtdoom/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /rtdoom/GameState.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MapDef.h" 4 | 5 | namespace rtdoom 6 | { 7 | class GameState 8 | { 9 | public: 10 | std::unique_ptr m_mapDef; 11 | float m_step; 12 | Thing m_player; 13 | 14 | void Move(int m, int r, float step); 15 | void NewGame(const MapStore& mapStore); 16 | 17 | GameState(); 18 | ~GameState(); 19 | }; 20 | } // namespace rtdoom 21 | -------------------------------------------------------------------------------- /rtdoom/pch.h: -------------------------------------------------------------------------------- 1 | #ifndef PCH_H 2 | #define PCH_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #endif //PCH_H 24 | -------------------------------------------------------------------------------- /rtdoom/GLViewport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "GLRenderer.h" 7 | 8 | namespace rtdoom 9 | { 10 | class GLViewport 11 | { 12 | protected: 13 | SDL_Window* m_window; 14 | GLRenderer& m_renderer; 15 | 16 | bool m_glReady; 17 | bool m_mapReady; 18 | 19 | void Initialize(); 20 | 21 | public: 22 | GLViewport(SDL_Window* window, GLRenderer& glRenderer); 23 | void Resize(int width, int height); 24 | void Reset(); 25 | void Draw(); 26 | ~GLViewport(); 27 | }; 28 | } // namespace rtdoom 29 | -------------------------------------------------------------------------------- /rtdoom/rtdoom.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | using Angle = float; 4 | 5 | namespace rtdoom 6 | { 7 | // output display size (scaled framebuffer) 8 | constexpr int s_displayX = 1280; 9 | constexpr int s_displayY = 800; 10 | constexpr float s_multisamplingLevel = 0.5f; 11 | constexpr float s_minDistance = 1.0f; 12 | constexpr float s_minScale = 0.025f; 13 | constexpr float s_lightnessFactor = 1500.0f; 14 | 15 | constexpr Angle PI = 3.14159265359f; 16 | constexpr Angle PI2 = PI / 2.0f; 17 | constexpr Angle PI4 = PI / 4.0f; 18 | } // namespace rtdoom 19 | -------------------------------------------------------------------------------- /rtdoom/FrameBuffer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "FrameBuffer.h" 3 | 4 | namespace rtdoom 5 | { 6 | FrameBuffer::FrameBuffer(int width, int height, const Palette& palette) : m_width {width}, m_height {height}, m_palette {palette} {} 7 | 8 | unsigned char FrameBuffer::Gamma(float lightness) 9 | { 10 | if(lightness >= 1) 11 | { 12 | return 255; 13 | } 14 | else if(lightness <= 0) 15 | { 16 | return 38; 17 | } 18 | return static_cast(255.0f * (0.2f + lightness * 0.8f)); 19 | } 20 | 21 | FrameBuffer::~FrameBuffer() {} 22 | } // namespace rtdoom 23 | -------------------------------------------------------------------------------- /rtdoom/MapRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Renderer.h" 4 | 5 | namespace rtdoom 6 | { 7 | class MapRenderer : public Renderer 8 | { 9 | protected: 10 | const float s_mapScale = 5.0f; 11 | 12 | void DrawMapLine(const Vertex& startVertex, const Vertex& endVertex, float lightness, FrameBuffer& frameBuffer) const; 13 | void MapToScreenCoords(float mx, float my, int& sx, int& sy, FrameBuffer& frameBuffer) const; 14 | 15 | public: 16 | MapRenderer(const GameState& gameState); 17 | ~MapRenderer(); 18 | 19 | void RenderFrame(FrameBuffer& frameBuffer); 20 | }; 21 | } // namespace rtdoom 22 | -------------------------------------------------------------------------------- /rtdoom/WireframePainter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Painter.h" 4 | 5 | namespace rtdoom 6 | { 7 | class WireframePainter : public Painter 8 | { 9 | protected: 10 | const int s_wallColor = 1; 11 | 12 | public: 13 | void PaintWall(int x, const Frame::Span& span, const Frame::PainterContext& textureContext) const override; 14 | void PaintSprite(int x, int sy, std::vector occlusion, const Frame::PainterContext& textureContext) const override; 15 | void PaintPlane(const Frame::Plane& plane) const override; 16 | 17 | WireframePainter(FrameBuffer& frameBuffer); 18 | ~WireframePainter(); 19 | }; 20 | } // namespace rtdoom 21 | -------------------------------------------------------------------------------- /rtdoom/GLRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Renderer.h" 5 | #include "WADFile.h" 6 | #include "GLContext.h" 7 | 8 | namespace rtdoom 9 | { 10 | class GLRenderer : public RendererBase 11 | { 12 | protected: 13 | int m_width; 14 | int m_height; 15 | GLContext m_context; 16 | 17 | void RenderSegments() const; 18 | 19 | public: 20 | GLRenderer(const GameState& gameState, const WADFile& wadFile, int width, int height); 21 | ~GLRenderer(); 22 | 23 | void Initialize(); 24 | void Resize(int width, int height); 25 | void LoadMap(); 26 | void Reset(); 27 | 28 | void RenderFrame(); 29 | }; 30 | } // namespace rtdoom 31 | -------------------------------------------------------------------------------- /rtdoom/Painter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Frame.h" 4 | #include "Projection.h" 5 | #include "GameState.h" 6 | 7 | namespace rtdoom 8 | { 9 | class Painter 10 | { 11 | protected: 12 | FrameBuffer& m_frameBuffer; 13 | 14 | public: 15 | virtual void PaintWall(int x, const Frame::Span& span, const Frame::PainterContext& textureContext) const = 0; 16 | virtual void PaintSprite(int x, int sy, std::vector occlusion, const Frame::PainterContext& textureContext) const = 0; 17 | virtual void PaintPlane(const Frame::Plane& plane) const = 0; 18 | 19 | Painter(FrameBuffer& frameBuffer); 20 | virtual ~Painter(); 21 | }; 22 | } // namespace rtdoom 23 | -------------------------------------------------------------------------------- /rtdoom/SolidPainter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Painter.h" 4 | 5 | namespace rtdoom 6 | { 7 | class SolidPainter : public Painter 8 | { 9 | protected: 10 | const int s_wallColor = 1; 11 | const int s_planeColor = 5; 12 | 13 | const Projection& m_projection; 14 | 15 | public: 16 | void PaintWall(int x, const Frame::Span& span, const Frame::PainterContext& textureContext) const override; 17 | void PaintSprite(int x, int sy, std::vector occlusion, const Frame::PainterContext& textureContext) const override; 18 | void PaintPlane(const Frame::Plane& plane) const override; 19 | 20 | SolidPainter(FrameBuffer& frameBuffer, const Projection& projection); 21 | ~SolidPainter(); 22 | }; 23 | } // namespace rtdoom 24 | -------------------------------------------------------------------------------- /rtdoom/GameState.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "rtdoom.h" 3 | #include "GameState.h" 4 | #include "Projection.h" 5 | 6 | using std::string; 7 | 8 | namespace rtdoom 9 | { 10 | GameState::GameState() : m_player {0, 0, 0, 0}, m_mapDef {nullptr}, m_step {0} {} 11 | 12 | void GameState::NewGame(const MapStore& mapStore) 13 | { 14 | m_mapDef = std::make_unique(mapStore); 15 | m_player = m_mapDef->GetStartingPosition(); 16 | m_step = 0; 17 | } 18 | 19 | void GameState::Move(int m, int r, float step) 20 | { 21 | m_step += step; 22 | 23 | m_player.a += step * 16 * -0.15f * r; 24 | m_player.x += step * 32 * 10.0f * m * cos(m_player.a); 25 | m_player.y += step * 32 * 10.0f * m * sin(m_player.a); 26 | 27 | m_player.a = Projection::NormalizeAngle(m_player.a); 28 | } 29 | 30 | GameState::~GameState() {} 31 | } // namespace rtdoom 32 | -------------------------------------------------------------------------------- /rtdoom/TexturePainter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Painter.h" 4 | 5 | namespace rtdoom 6 | { 7 | class TexturePainter : public Painter 8 | { 9 | protected: 10 | const Thing& m_pov; 11 | const Projection& m_projection; 12 | const WADFile& m_wadFile; 13 | 14 | static std::list MergeSpans(const std::vector& spans); 15 | 16 | public: 17 | void PaintWall(int x, const Frame::Span& span, const Frame::PainterContext& textureContext) const override; 18 | void PaintSprite(int x, int sy, std::vector occlusion, const Frame::PainterContext& textureContext) const override; 19 | void PaintPlane(const Frame::Plane& plane) const override; 20 | 21 | TexturePainter(FrameBuffer& frameBuffer, const Thing& pov, const Projection& projection, const WADFile& wadFile); 22 | ~TexturePainter(); 23 | }; 24 | } // namespace rtdoom 25 | -------------------------------------------------------------------------------- /rtdoom/Viewport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Renderer.h" 7 | 8 | namespace rtdoom 9 | { 10 | class Viewport 11 | { 12 | protected: 13 | int m_width; 14 | int m_height; 15 | bool m_fillTarget; 16 | 17 | SDL_Renderer* m_sdlRenderer; 18 | SDL_Texture* m_screenTexture; 19 | Renderer& m_renderer; 20 | 21 | const Palette& m_palette; 22 | std::unique_ptr m_targetRect; 23 | std::unique_ptr m_frameBuffer; 24 | 25 | void Initialize(); 26 | void Uninitialize(); 27 | 28 | public: 29 | Viewport(SDL_Renderer* sdlRenderer, Renderer& renderer, int width, int height, const Palette& palette, bool fillTarget); 30 | void Resize(int width, int height); 31 | void Draw(); 32 | void DrawSteps(); 33 | ~Viewport(); 34 | }; 35 | } // namespace rtdoom 36 | -------------------------------------------------------------------------------- /rtdoom/Helpers.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Helpers.h" 3 | 4 | namespace rtdoom 5 | { 6 | std::string Helpers::MakeString(const char data[8]) 7 | { 8 | char fullName[9]; 9 | fullName[8] = 0; 10 | memcpy(fullName, data, 8); 11 | for(int i = 0; i < 8; i++) 12 | { 13 | fullName[i] = static_cast(toupper(fullName[i])); 14 | } 15 | return std::string(fullName); 16 | } 17 | 18 | int Helpers::Clip(int v, int max) 19 | { 20 | while(v < 0) 21 | { 22 | v += max; 23 | } 24 | return v % max; 25 | } 26 | 27 | float Helpers::Clip(float v, float max) 28 | { 29 | if(isfinite(v)) 30 | { 31 | while(v < 0) 32 | { 33 | v += max; 34 | } 35 | while(v > max) 36 | { 37 | v -= max; 38 | } 39 | } 40 | return v; 41 | } 42 | 43 | Helpers::Helpers() {} 44 | 45 | Helpers::~Helpers() {} 46 | } // namespace rtdoom 47 | -------------------------------------------------------------------------------- /rtdoom/MathCache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rtdoom.h" 4 | 5 | namespace rtdoom 6 | { 7 | // singleton with precalculated trigonometric functions 8 | class MathCache 9 | { 10 | protected: 11 | MathCache(); 12 | 13 | constexpr static bool s_useCache = true; 14 | constexpr static size_t s_precision = 16384; 15 | 16 | int sign(float v) const; 17 | float FastArcTan(float x) const; 18 | float _atan2f(float y, float x) const; 19 | 20 | std::array _tan; 21 | std::array _atan; 22 | std::array _cos; 23 | std::array _sin; 24 | 25 | public: 26 | static const MathCache& instance(); 27 | 28 | float ArcTan(float x) const; 29 | float ArcTan(float dy, float dx) const; 30 | float Cos(float x) const; 31 | float Sin(float x) const; 32 | float Tan(float x) const; 33 | }; 34 | } // namespace rtdoom 35 | -------------------------------------------------------------------------------- /rtdoom/Renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FrameBuffer.h" 4 | #include "GameState.h" 5 | 6 | namespace rtdoom 7 | { 8 | class RendererBase 9 | { 10 | protected: 11 | const GameState& m_gameState; 12 | 13 | public: 14 | enum class RenderingMode 15 | { 16 | Wireframe, 17 | Solid, 18 | Textured, 19 | OpenGL 20 | }; 21 | 22 | RendererBase(const GameState& gameState) : m_gameState {gameState} { } 23 | virtual ~RendererBase() { } 24 | }; 25 | 26 | class Renderer : public RendererBase 27 | { 28 | protected: 29 | static bool IsVisible(int x, int y, FrameBuffer& frameBuffer); 30 | 31 | void DrawLine(int sx, int sy, int dx, int dy, int color, float lightness, FrameBuffer& frameBuffer) const; 32 | 33 | public: 34 | virtual void RenderFrame(FrameBuffer& frameBuffer) = 0; 35 | 36 | Renderer(const GameState& gameState); 37 | virtual ~Renderer(); 38 | }; 39 | } // namespace rtdoom 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 mausimus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rtdoom/WireframePainter.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "WireframePainter.h" 3 | #include "Projection.h" 4 | #include "MathCache.h" 5 | 6 | namespace rtdoom 7 | { 8 | WireframePainter::WireframePainter(FrameBuffer& frameBuffer) : Painter(frameBuffer) 9 | { 10 | m_frameBuffer.Clear(); 11 | } 12 | 13 | void WireframePainter::PaintWall(int x, const Frame::Span& span, const Frame::PainterContext& textureContext) const 14 | { 15 | if(span.s > 0) 16 | { 17 | m_frameBuffer.VerticalLine(x, span.s, span.s, s_wallColor, textureContext.lightness); 18 | } 19 | if(span.e > 0) 20 | { 21 | m_frameBuffer.VerticalLine(x, span.e, span.e, s_wallColor, textureContext.lightness); 22 | } 23 | if(textureContext.textureName != "-" && textureContext.isEdge) 24 | { 25 | m_frameBuffer.VerticalLine(x, span.s + 1, span.e - 1, s_wallColor, textureContext.lightness / 2.0f); 26 | } 27 | } 28 | 29 | void WireframePainter::PaintSprite(int /*x*/, 30 | int /*sy*/, 31 | std::vector /*occlusion*/, 32 | const Frame::PainterContext& /*textureContext*/) const 33 | {} 34 | 35 | void WireframePainter::PaintPlane(const Frame::Plane& /*plane*/) const {} 36 | 37 | WireframePainter::~WireframePainter() {} 38 | } // namespace rtdoom 39 | -------------------------------------------------------------------------------- /rtdoom/Projection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MapDef.h" 4 | #include "FrameBuffer.h" 5 | 6 | namespace rtdoom 7 | { 8 | class Projection 9 | { 10 | protected: 11 | const Thing& m_player; 12 | const int m_viewWidth; 13 | const int m_viewHeight; 14 | const int m_midPointX; 15 | const int m_midPointY; 16 | 17 | public: 18 | static Angle AngleDist(Angle a1, Angle a2) noexcept; 19 | static Angle NormalizeAngle(Angle angle) noexcept; 20 | static bool NormalizeViewAngleSpan(Angle& a1, Angle& a2) noexcept; 21 | static float Distance(const Point& a, const Point& b); 22 | 23 | int ViewX(Angle viewAngle) const noexcept; 24 | int ViewY(float distance, float height) const noexcept; 25 | float TextureScale(float distance) const noexcept; 26 | Angle ViewAngle(int screenX) const noexcept; 27 | Vector NormalVector(const Line& l) const; 28 | float NormalOffset(const Line& l) const; 29 | float Distance(const Vector& normalVector, Angle viewAngle) const; 30 | float Offset(const Vector& normalVector, Angle viewAngle) const; 31 | float PlaneDistance(int y, float height) const noexcept; 32 | Angle AbsoluteAngle(const Point& p) const; 33 | Angle ProjectionAngle(const Point& p) const; 34 | float Lightness(float distance, const Segment* segment = nullptr) const; 35 | 36 | Projection(const Thing& player, const FrameBuffer& frameBuffer); 37 | ~Projection(); 38 | }; 39 | } // namespace rtdoom 40 | -------------------------------------------------------------------------------- /rtdoom/Helpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace rtdoom 4 | { 5 | class Helpers 6 | { 7 | public: 8 | template 9 | static std::vector LoadEntities(const std::string& fileName) 10 | { 11 | std::ifstream infile(fileName, std::ios::binary); 12 | if(!infile.good()) 13 | { 14 | throw std::runtime_error("Unable to open file " + fileName); 15 | } 16 | std::vector items; 17 | T v; 18 | while(infile.read((char*)&v, sizeof(T))) 19 | { 20 | items.push_back(v); 21 | }; 22 | return items; 23 | } 24 | 25 | template 26 | static std::vector LoadEntities(const std::vector& data, int skip = 0) 27 | { 28 | size_t dataIndex = skip; 29 | std::vector items; 30 | T v; 31 | while(dataIndex < data.size()) 32 | { 33 | memcpy(&v, data.data() + dataIndex, sizeof(T)); 34 | items.push_back(v); 35 | dataIndex += sizeof(T); 36 | } 37 | return items; 38 | } 39 | 40 | template 41 | static void LoadEntity(const std::vector& data, T* entity) 42 | { 43 | memcpy(entity, data.data(), sizeof(T)); 44 | } 45 | 46 | static std::string MakeString(const char data[8]); 47 | 48 | static int Clip(int v, int max); 49 | static float Clip(float v, float max); 50 | 51 | Helpers(); 52 | ~Helpers(); 53 | }; 54 | } // namespace rtdoom 55 | -------------------------------------------------------------------------------- /rtdoom/FrameBuffer32.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FrameBuffer.h" 4 | 5 | namespace rtdoom 6 | { 7 | #pragma pack(1) 8 | union Pixel32 9 | { 10 | struct ARGB 11 | { 12 | uint8_t b; 13 | uint8_t g; 14 | uint8_t r; 15 | uint8_t a; 16 | }; 17 | ARGB argb8; 18 | uint32_t argb32; 19 | }; 20 | #pragma pack() 21 | 22 | class FrameBuffer32 : public FrameBuffer 23 | { 24 | protected: 25 | Pixel32* m_pixels {nullptr}; 26 | 27 | const std::array s_colors {0x00ff000, 0x0000ff00, 0x000000ff, 0x00ff00ff, 0x0000ffff, 0x003f7f0f}; 28 | unsigned char *c_lightMap[256]; 29 | 30 | public: 31 | virtual void Attach(void* pixels, std::function stepCallback) override; 32 | virtual void Clear() override; 33 | virtual void SetPixel(int x, int y, int color, float lightness) noexcept override; 34 | virtual void VerticalLine(int x, int sy, int ey, int colorIndex, float lightness) noexcept override; 35 | virtual void VerticalLine(int x, int sy, const std::vector& texels, const std::vector& lightnesses) noexcept; 36 | virtual void VerticalLine(int x, int sy, const std::vector& texels, float lightness) noexcept; 37 | virtual void HorizontalLine(int sx, int y, const std::vector& texels, float lightness) noexcept; 38 | virtual void HorizontalLine(int sx, int ex, int y, int colorIndex, float lightness) noexcept; 39 | 40 | FrameBuffer32(int width, int height, const Palette& palette); 41 | ~FrameBuffer32(); 42 | }; 43 | } // namespace rtdoom 44 | -------------------------------------------------------------------------------- /rtdoom/FrameBuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "WADFile.h" 4 | 5 | namespace rtdoom 6 | { 7 | class FrameBuffer 8 | { 9 | public: 10 | FrameBuffer(int width, int height, const Palette& palette); 11 | 12 | const int m_width; 13 | const int m_height; 14 | const Palette& m_palette; 15 | std::function m_stepCallback; 16 | 17 | virtual void Attach(void* pixels, std::function stepCallback = nullptr) = 0; 18 | virtual void Clear() = 0; 19 | virtual void SetPixel(int x, int y, int color, float lightness) noexcept = 0; 20 | virtual void VerticalLine(int x, int sy, int ey, int colorIndex, float lightness) noexcept = 0; 21 | virtual void VerticalLine(int x, int sy, const std::vector& texels, const std::vector& lightnesses) noexcept = 0; 22 | virtual void VerticalLine(int x, int sy, const std::vector& texels, float lightness) noexcept = 0; 23 | virtual void HorizontalLine(int sx, int y, const std::vector& texels, float lightness) noexcept = 0; 24 | virtual void HorizontalLine(int sx, int ex, int y, int colorIndex, float lightness) noexcept = 0; 25 | 26 | static unsigned char Gamma(float lightness); 27 | 28 | virtual ~FrameBuffer(); 29 | }; 30 | } // namespace rtdoom 31 | -------------------------------------------------------------------------------- /rtdoom/SolidPainter.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "SolidPainter.h" 3 | #include "Projection.h" 4 | #include "MathCache.h" 5 | 6 | namespace rtdoom 7 | { 8 | SolidPainter::SolidPainter(FrameBuffer& frameBuffer, const Projection& projection) : Painter {frameBuffer}, m_projection {projection} {} 9 | 10 | void SolidPainter::PaintWall(int x, const Frame::Span& span, const Frame::PainterContext& textureContext) const 11 | { 12 | if(textureContext.textureName != "-") 13 | { 14 | m_frameBuffer.VerticalLine(x, span.s, span.e, s_wallColor, textureContext.lightness); 15 | } 16 | } 17 | 18 | void SolidPainter::PaintSprite(int /*x*/, 19 | int /*sy*/, 20 | std::vector /*occlusion*/, 21 | const Frame::PainterContext& /*textureContext*/) const 22 | {} 23 | 24 | void SolidPainter::PaintPlane(const Frame::Plane& plane) const 25 | { 26 | const bool isSky = plane.isSky(); 27 | 28 | for(size_t y = 0; y < plane.spans.size(); y++) 29 | { 30 | const auto& spans = plane.spans[y]; 31 | auto centerDistance = m_projection.PlaneDistance(y, plane.h); 32 | const float lightness = isSky ? 1 : m_projection.Lightness(centerDistance) * plane.lightLevel; 33 | 34 | for(auto span : spans) 35 | { 36 | const auto sx = std::max(0, span.s); 37 | const auto ex = std::min(m_frameBuffer.m_width - 1, span.e); 38 | const auto nx = ex - sx + 1; 39 | 40 | m_frameBuffer.HorizontalLine(sx, ex, y, s_planeColor, lightness); 41 | } 42 | } 43 | } 44 | 45 | SolidPainter::~SolidPainter() {} 46 | } // namespace rtdoom 47 | -------------------------------------------------------------------------------- /rtdoom/GLViewport.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "GLViewport.h" 3 | #include "glad/glad.h" 4 | 5 | #include 6 | 7 | namespace rtdoom 8 | { 9 | GLViewport::GLViewport(SDL_Window* sdlWindow, GLRenderer& glRenderer) : 10 | m_window {sdlWindow}, m_renderer {glRenderer}, m_glReady {false}, m_mapReady {false} 11 | { } 12 | 13 | void GLViewport::Initialize() 14 | { 15 | SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); 16 | SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); 17 | SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); 18 | SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); 19 | SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); 20 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 21 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); 22 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 23 | 24 | SDL_GL_CreateContext(m_window); 25 | 26 | if(!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) 27 | { 28 | throw new std::runtime_error("Failed to initialize GLAD"); 29 | } 30 | 31 | SDL_GL_SetSwapInterval(1); 32 | } 33 | 34 | void GLViewport::Reset() 35 | { 36 | m_renderer.Reset(); 37 | m_mapReady = false; 38 | } 39 | 40 | void GLViewport::Draw() 41 | { 42 | if(!m_glReady) 43 | { 44 | Initialize(); 45 | m_renderer.Initialize(); 46 | m_glReady = true; 47 | } 48 | if(!m_mapReady) 49 | { 50 | m_renderer.LoadMap(); 51 | m_mapReady = true; 52 | } 53 | 54 | m_renderer.RenderFrame(); 55 | 56 | SDL_GL_SwapWindow(m_window); 57 | } 58 | 59 | void GLViewport::Resize(int width, int height) 60 | { 61 | m_renderer.Resize(width, height); 62 | } 63 | 64 | GLViewport::~GLViewport() { } 65 | 66 | } // namespace rtdoom -------------------------------------------------------------------------------- /rtdoom.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rtdoom", "rtdoom\rtdoom.vcxproj", "{D27DCA69-01EE-4B18-BB15-8A829B41F088}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C6D7B329-5919-46EC-9A88-B31570DF81DD}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|x64 = Release|x64 18 | Release|x86 = Release|x86 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {D27DCA69-01EE-4B18-BB15-8A829B41F088}.Debug|x64.ActiveCfg = Debug|x64 22 | {D27DCA69-01EE-4B18-BB15-8A829B41F088}.Debug|x64.Build.0 = Debug|x64 23 | {D27DCA69-01EE-4B18-BB15-8A829B41F088}.Debug|x86.ActiveCfg = Debug|Win32 24 | {D27DCA69-01EE-4B18-BB15-8A829B41F088}.Debug|x86.Build.0 = Debug|Win32 25 | {D27DCA69-01EE-4B18-BB15-8A829B41F088}.Release|x64.ActiveCfg = Release|x64 26 | {D27DCA69-01EE-4B18-BB15-8A829B41F088}.Release|x64.Build.0 = Release|x64 27 | {D27DCA69-01EE-4B18-BB15-8A829B41F088}.Release|x86.ActiveCfg = Release|Win32 28 | {D27DCA69-01EE-4B18-BB15-8A829B41F088}.Release|x86.Build.0 = Release|Win32 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {D1E2921F-D1F9-4287-BFB6-CC729368DFCE} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /rtdoom/GameLoop.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "rtdoom.h" 7 | #include "Viewport.h" 8 | #include "GameState.h" 9 | #include "SoftwareRenderer.h" 10 | #include "GLRenderer.h" 11 | #include "GLViewport.h" 12 | #include "MapRenderer.h" 13 | 14 | namespace rtdoom 15 | { 16 | class GameLoop 17 | { 18 | protected: 19 | GameState m_gameState; 20 | SoftwareRenderer m_softwareRenderer; 21 | GLRenderer m_glRenderer; 22 | GLViewport m_glViewport; 23 | Viewport m_playerViewport; 24 | MapRenderer m_mapRenderer; 25 | Viewport m_mapViewport; 26 | bool m_isRunning; 27 | bool m_stepFrame; 28 | int m_moveDirection; 29 | int m_rotateDirection; 30 | Renderer::RenderingMode m_renderingMode; 31 | SDL_Renderer* m_sdlRenderer; 32 | 33 | constexpr int ViewScale(int windowSize) const; 34 | constexpr int MapScale(int windowSize) const; 35 | 36 | public: 37 | void Start(const MapStore& mapStore); 38 | void Stop(); 39 | bool isRunning() const 40 | { 41 | return m_isRunning; 42 | } 43 | 44 | void Move(int moveDirection); 45 | void Rotate(int rotateDirection); 46 | 47 | const Frame* RenderFrame(); 48 | void ClipPlayer(); 49 | void StepFrame(); 50 | void Tick(float seconds); 51 | void ResizeWindow(int width, int height); 52 | void SetRenderingMode(Renderer::RenderingMode renderingMode); 53 | 54 | const Thing& Player() const 55 | { 56 | return m_gameState.m_player; 57 | } 58 | 59 | GameLoop(SDL_Renderer* sdlRenderer, SDL_Window* window, const WADFile& wadFile); 60 | ~GameLoop(); 61 | }; 62 | } // namespace rtdoom 63 | -------------------------------------------------------------------------------- /rtdoom/Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Renderer.h" 3 | 4 | namespace rtdoom 5 | { 6 | Renderer::Renderer(const GameState& gameState) : RendererBase(gameState) {} 7 | 8 | bool Renderer::IsVisible(int x, int y, FrameBuffer& frameBuffer) 9 | { 10 | return (x >= 0 && y >= 0 && x < frameBuffer.m_width && y < frameBuffer.m_height); 11 | } 12 | 13 | void Renderer::DrawLine(int sx, int sy, int dx, int dy, int color, float lightness, FrameBuffer& frameBuffer) const 14 | { 15 | const auto rx = dx - sx; 16 | const auto ry = dy - sy; 17 | 18 | if(!IsVisible(sx, sy, frameBuffer) && !IsVisible(dx, dy, frameBuffer)) 19 | { 20 | // TODO: support partial lines 21 | return; 22 | } 23 | 24 | if(rx == 0 && ry == 0) 25 | { 26 | frameBuffer.SetPixel(sx, sy, color, lightness); 27 | return; 28 | } 29 | 30 | if(abs(rx) > abs(ry)) 31 | { 32 | if(sx > dx) 33 | { 34 | std::swap(sx, dx); 35 | std::swap(sy, dy); 36 | } 37 | 38 | auto y = static_cast(sy); 39 | const auto slope = static_cast(dy - sy) / static_cast(dx - sx); 40 | 41 | do 42 | { 43 | frameBuffer.SetPixel(sx, static_cast(y), color, lightness); 44 | y += slope; 45 | sx++; 46 | } while(sx <= dx); 47 | } 48 | else 49 | { 50 | if(sy > dy) 51 | { 52 | std::swap(sx, dx); 53 | std::swap(sy, dy); 54 | } 55 | 56 | auto x = static_cast(sx); 57 | const auto slope = static_cast(dx - sx) / static_cast(dy - sy); 58 | 59 | do 60 | { 61 | frameBuffer.SetPixel(static_cast(x), sy, color, lightness); 62 | x += slope; 63 | sy++; 64 | } while(sy <= dy); 65 | } 66 | } 67 | 68 | Renderer::~Renderer() {} 69 | } // namespace rtdoom 70 | -------------------------------------------------------------------------------- /rtdoom/SoftwareRenderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Renderer.h" 4 | #include "WADFile.h" 5 | #include "Projection.h" 6 | #include "Frame.h" 7 | #include "Painter.h" 8 | 9 | namespace rtdoom 10 | { 11 | class SoftwareRenderer : public Renderer 12 | { 13 | protected: 14 | struct VisibleSegment 15 | { 16 | VisibleSegment(const Segment& segment) : mapSegment {segment} { } 17 | 18 | const Segment& mapSegment; 19 | Vector normalVector; 20 | int startX; 21 | int endX; 22 | float startAngle; 23 | float endAngle; 24 | float normalOffset; 25 | }; 26 | 27 | const float s_skyHeight = NAN; 28 | 29 | void Initialize(FrameBuffer& frameBuffer); 30 | void RenderSegments() const; 31 | void RenderPlanes() const; 32 | void RenderSprites() const; 33 | void RenderOverlay() const; 34 | void RenderMapSegment(const Segment& segment) const; 35 | void RenderMapSegmentSpan(const Frame::Span& span, const VisibleSegment& visibleSegment) const; 36 | void RenderSpriteThing(Frame::SpriteThing* const thing) const; 37 | void RenderSpriteWall(Frame::SpriteWall* const wall) const; 38 | 39 | std::vector> ClipSprite(int startX, int startY, int spriteWidth, int spriteHeight, float spriteScale) const; 40 | Angle GetViewAngle(int x, const VisibleSegment& visibleSegment) const; 41 | 42 | FrameBuffer* m_frameBuffer; 43 | const WADFile& m_wadFile; 44 | std::unique_ptr m_projection; 45 | std::unique_ptr m_frame; 46 | std::unique_ptr m_painter; 47 | RendererBase::RenderingMode m_renderingMode; 48 | 49 | public: 50 | SoftwareRenderer(const GameState& gameState, const WADFile& wadFile); 51 | ~SoftwareRenderer(); 52 | 53 | virtual void RenderFrame(FrameBuffer& frameBuffer) override; 54 | Frame* GetLastFrame() const; 55 | void SetMode(RendererBase::RenderingMode renderingMode); 56 | }; 57 | } // namespace rtdoom 58 | -------------------------------------------------------------------------------- /rtdoom/MapDef.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MapStore.h" 4 | #include "MapStructs.h" 5 | 6 | namespace rtdoom 7 | { 8 | class MapDef 9 | { 10 | protected: 11 | MapStore m_store; 12 | 13 | static bool IsInFrontOf(const Point& pov, const MapStore::Node& node) noexcept; 14 | static bool IsInFrontOf(const Point& pov, const Vertex& sv, const Vertex& ev) noexcept; 15 | 16 | void LookupVertex(unsigned short vertexNo, float& x, float& y); 17 | void ProcessSegment(float sx, float sy, float ex, float ey, unsigned short lineDefNo, signed short direction, signed short offset); 18 | void ProcessNode(const Point& pov, const MapStore::Node& node, std::deque>& subSectors) const; 19 | void ProcessChildRef(unsigned short childRef, const Point& pov, std::deque>& subSectors) const; 20 | void ProcessSubsector(std::shared_ptr subSector, std::deque>& subSectors) const; 21 | void OpenDoors(); 22 | void Initialize(); 23 | void BuildWireframe(); 24 | void BuildSegments(); 25 | void BuildSectors(); 26 | void BuildSubSectors(); 27 | void BuildThings(); 28 | 29 | public: 30 | static bool IsInFrontOf(const Point& pov, const Line& line) noexcept; 31 | bool HasGL() const; 32 | std::vector m_wireframe; 33 | std::vector m_sectors; 34 | std::vector> m_things; 35 | std::vector> m_segments; 36 | std::vector> m_subSectors; 37 | 38 | Thing GetStartingPosition() const; 39 | std::optional GetSector(const Point& pov) const; 40 | std::deque> GetSegmentsToDraw(const Point& pov) const; 41 | std::deque> GetSubSectorsToDraw(const Point& pov) const; 42 | 43 | MapDef(const std::string& mapFolder); 44 | MapDef(const MapStore& mapStore); 45 | ~MapDef(); 46 | }; 47 | } // namespace rtdoom 48 | -------------------------------------------------------------------------------- /rtdoom/MapRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "rtdoom.h" 3 | #include "MapRenderer.h" 4 | 5 | namespace rtdoom 6 | { 7 | MapRenderer::MapRenderer(const GameState& gameState) : Renderer {gameState} {} 8 | 9 | void MapRenderer::MapToScreenCoords(float mx, float my, int& sx, int& sy, FrameBuffer& frameBuffer) const 10 | { 11 | auto rx = (mx - m_gameState.m_player.x) / s_mapScale; 12 | auto ry = (my - m_gameState.m_player.y) / s_mapScale; 13 | sx = static_cast(rx + frameBuffer.m_width / 2); 14 | sy = static_cast(ry + frameBuffer.m_height / 2); 15 | } 16 | 17 | void MapRenderer::DrawMapLine(const Vertex& startVertex, const Vertex& endVertex, float lightness, FrameBuffer& frameBuffer) const 18 | { 19 | int sx, sy, dx, dy; 20 | MapToScreenCoords(startVertex.x, startVertex.y, sx, sy, frameBuffer); 21 | MapToScreenCoords(endVertex.x, endVertex.y, dx, dy, frameBuffer); 22 | DrawLine(sx, sy, dx, dy, 3, lightness, frameBuffer); 23 | } 24 | 25 | void MapRenderer::RenderFrame(FrameBuffer& frameBuffer) 26 | { 27 | frameBuffer.Clear(); 28 | 29 | for(const auto& l : m_gameState.m_mapDef->m_wireframe) 30 | { 31 | DrawMapLine(l.s, l.e, 1.0f, frameBuffer); 32 | } 33 | 34 | // player position 35 | int px, py; 36 | MapToScreenCoords(m_gameState.m_player.x, m_gameState.m_player.y, px, py, frameBuffer); 37 | frameBuffer.SetPixel(px, py, 4, 1.0f); 38 | 39 | // player view cone 40 | int clx, cly, crx, cry; 41 | MapToScreenCoords(m_gameState.m_player.x + 100.0f * cos(m_gameState.m_player.a - PI4), 42 | m_gameState.m_player.y + 100.0f * sin(m_gameState.m_player.a - PI4), 43 | clx, 44 | cly, 45 | frameBuffer); 46 | MapToScreenCoords(m_gameState.m_player.x + 100.0f * cos(m_gameState.m_player.a + PI4), 47 | m_gameState.m_player.y + 100.0f * sin(m_gameState.m_player.a + PI4), 48 | crx, 49 | cry, 50 | frameBuffer); 51 | DrawLine(px, py, clx, cly, 4, 0.5f, frameBuffer); 52 | DrawLine(px, py, crx, cry, 4, 0.5f, frameBuffer); 53 | } 54 | 55 | MapRenderer::~MapRenderer() {} 56 | } // namespace rtdoom 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## rtdoom 2 | 3 | Implementation of a Doom-style graphics engine in C++ 17 4 | 5 | ### Features 6 | 7 | * from-scratch software-based rendering implementation (no 3D libraries used) 8 | * following originally used algorithms: BSP tree, occlusion maps instead of z-buffer, no overdraw 9 | * ability to load original Doom WAD files 10 | 11 | ### Screenshots 12 | 13 | ###### Wireframe mode 14 | 15 | ![screenshot](images/screen1.png) 16 | 17 | ###### Solid mode 18 | 19 | ![screenshot](images/screen2.png) 20 | 21 | ###### Textured mode 22 | 23 | ![screenshot](images/screen3.png) 24 | 25 | ###### Step frame mode 26 | 27 | ![screenshot](images/screen4.gif) 28 | 29 | ### Goal 30 | 31 | While I follow original algorithms which were designed for resource-constrained architectures 32 | the primary goal of this project is to provide an easily understandable implementation and 33 | performance is not a focus (for example floating-point is used, objects are not reused as much 34 | as they could, many values are not precalculated etc.) 35 | 36 | ### Algorithm 37 | 38 | See Fabien Sanglard's [Game Engine Black Book: DOOM](https://fabiensanglard.net/gebbdoom/) for a walkthrough 39 | of algorithms used in the original game, most of which are replicated here. 40 | 41 | ### Code 42 | 43 | * [SoftwareRenderer.cpp](rtdoom/SoftwareRenderer.cpp) contains the core of the frame rendering algorithm 44 | 45 | Test program uses SDL2 to render the raw framebuffer in a screen window, controls are: 46 | * arrow keys to move around 47 | * 1/2/3 to switch between render modes (Wireframe/Solid/Textured) 48 | * m to load the next map in the .wad file 49 | * s to slow down rendering of the next frame to see individual parts being drawn 50 | * Escape to exit 51 | 52 | Built on Windows / Visual Studio 2017 using C++ 17 profile. 53 | 54 | No assets included - you will need to drop off a .wad file from either the original Doom 55 | (1 or 2, shareware is ok) or the [Freedoom](https://freedoom.github.io/) project into the .exe directory! 56 | 57 | ### Bonus - OpenGL Renderer 58 | 59 | * [GLRenderer.cpp](rtdoom/GLRenderer.cpp) and [GLContext.cpp](rtdoom/GLContext.cpp) contain a very basic 60 | hardware-accelerated renderer implemented using OpenGL 3.3 core profile, press 4 to switch the view to OpenGL. 61 | You will need to supply a V2 .gwa file alongside the .wad containing GL node data, 62 | which can be generated using [glBSP](http://glbsp.sourceforge.net/). 63 | 64 | ![screenshot](images/screen5.png) 65 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /rtdoom/GLContext.h: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include "WADFile.h" 4 | #include "MapStructs.h" 5 | #include "glm/glm.hpp" 6 | 7 | namespace rtdoom 8 | { 9 | class GLContext 10 | { 11 | private: 12 | #pragma pack(1) 13 | struct VertexInfo 14 | { 15 | float x; // 3d coordinates 16 | float y; 17 | float z; 18 | float tx; // 2d texture coordinates 19 | float ty; 20 | float tz; // texture z coordinate (texture number within unit) 21 | float tn; // texture unit number 22 | float l; // lightness 23 | 24 | VertexInfo(float x, float y, float z, float tx, float ty, float tz, float tn, float l) : 25 | x {x}, y {y}, z {z}, tx {tx}, ty {ty}, tz {tz}, tn {tn}, l {l} 26 | { } 27 | }; 28 | #pragma pack() 29 | 30 | struct TextureUnit 31 | { 32 | int n; 33 | short w; 34 | short h; 35 | std::vector> textures; 36 | }; 37 | 38 | int m_maxTextureUnits; 39 | int m_vertexCounter; 40 | std::vector m_textureUnits; 41 | std::vector m_textures; 42 | std::vector m_vertices; 43 | int m_shaderProgram; 44 | int m_vertexShader; 45 | int m_fragmentShader; 46 | unsigned int m_VBO; 47 | unsigned int m_VAO; 48 | unsigned int m_EBO; 49 | const WADFile& m_wadFile; 50 | 51 | void LoadTextures(); 52 | void AddVertex(float x, float y, float z, float tx, float ty, float tn, float tz, float l); 53 | void LinkTriangle(int a, int b, int c); 54 | void LoadTexture(std::shared_ptr wtex, int i); 55 | 56 | public: 57 | void AddWallSegment( 58 | const Vertex& v0, float z0, const Vertex& v1, float z1, float tx0, float ty0, float tx1, float ty1, int tn, int ti, float l); 59 | void AddFloorCeiling(const Vertex& v0, const Vertex& v1, const Vertex& v2, float z, float tw, float th, int tn, int ti, float l); 60 | 61 | std::vector m_indices; 62 | std::map> m_subSectorOffsets; 63 | 64 | std::shared_ptr AllocateTexture(std::string name, int& textureNo, int& textureLayer); 65 | void Initialize(); 66 | void CompileShaders(const char* vertexShaderSource, const char* fragmentShaderSource); 67 | void BindMap(); 68 | void BindView(glm::f32* viewMat, glm::f32* projectionMat); 69 | void Reset(); 70 | 71 | GLContext(const WADFile& wadFile); 72 | ~GLContext(); 73 | }; 74 | } // namespace rtdoom 75 | -------------------------------------------------------------------------------- /rtdoom/MapStore.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "MapStore.h" 3 | #include "Helpers.h" 4 | 5 | using namespace std::literals::string_literals; 6 | 7 | namespace rtdoom 8 | { 9 | MapStore::MapStore() { } 10 | 11 | void MapStore::Load(const std::string& mapFolder) 12 | { 13 | m_vertexes = Helpers::LoadEntities(mapFolder + "\\vertexes.lmp"s); 14 | m_lineDefs = Helpers::LoadEntities(mapFolder + "\\linedefs.lmp"s); 15 | m_sideDefs = Helpers::LoadEntities(mapFolder + "\\sidedefs.lmp"s); 16 | m_sectors = Helpers::LoadEntities(mapFolder + "\\sectors.lmp"s); 17 | m_subSectors = Helpers::LoadEntities(mapFolder + "\\ssectors.lmp"s); 18 | m_nodes = Helpers::LoadEntities(mapFolder + "\\nodes.lmp"s); 19 | m_segments = Helpers::LoadEntities(mapFolder + "\\segs.lmp"s); 20 | m_things = Helpers::LoadEntities(mapFolder + "\\things.lmp"s); 21 | } 22 | 23 | void MapStore::Load(const std::map>& mapLumps) 24 | { 25 | m_vertexes = Helpers::LoadEntities(mapLumps.at("VERTEXES"s)); 26 | m_lineDefs = Helpers::LoadEntities(mapLumps.at("LINEDEFS"s)); 27 | m_sideDefs = Helpers::LoadEntities(mapLumps.at("SIDEDEFS"s)); 28 | m_sectors = Helpers::LoadEntities(mapLumps.at("SECTORS"s)); 29 | m_subSectors = Helpers::LoadEntities(mapLumps.at("SSECTORS"s)); 30 | m_nodes = Helpers::LoadEntities(mapLumps.at("NODES"s)); 31 | m_segments = Helpers::LoadEntities(mapLumps.at("SEGS"s)); 32 | m_things = Helpers::LoadEntities(mapLumps.at("THINGS"s)); 33 | } 34 | 35 | void MapStore::LoadGL(const std::map>& glLumps) 36 | { 37 | const auto& glVertexes = glLumps.at("GL_VERT"s); 38 | if(strncmp(glVertexes.data(), "gNd2", 4) != 0) 39 | { 40 | throw std::runtime_error("Only V2 glBSP format is supported."); 41 | } 42 | m_glVertexes = Helpers::LoadEntities(glVertexes, 4); 43 | m_glSegments = Helpers::LoadEntities(glLumps.at("GL_SEGS"s)); 44 | m_subSectors = Helpers::LoadEntities(glLumps.at("GL_SSECT"s)); 45 | m_nodes = Helpers::LoadEntities(glLumps.at("GL_NODES"s)); 46 | } 47 | 48 | void MapStore::GetStartingPosition(signed short& x, signed short& y, unsigned short& a) const 49 | { 50 | auto foundPlayer = find_if(m_things.cbegin(), m_things.cend(), [](const Thing& t) { return t.type == 1; }); 51 | if(foundPlayer == m_things.cend()) 52 | { 53 | throw std::runtime_error("No player starting location on map"); 54 | } 55 | const Thing& playerThing = *foundPlayer; 56 | x = playerThing.x; 57 | y = playerThing.y; 58 | a = playerThing.a; 59 | } 60 | 61 | MapStore::~MapStore() { } 62 | } // namespace rtdoom 63 | -------------------------------------------------------------------------------- /rtdoom/WADFile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "MapStore.h" 4 | 5 | namespace rtdoom 6 | { 7 | #pragma pack(1) 8 | struct Palette 9 | { 10 | struct Color24 11 | { 12 | uint8_t r; 13 | uint8_t g; 14 | uint8_t b; 15 | }; 16 | 17 | Color24 colors[256]; 18 | }; 19 | 20 | struct Texture 21 | { 22 | std::string name; 23 | int masked; 24 | short width; 25 | short height; 26 | std::unique_ptr pixels; 27 | }; 28 | #pragma pack() 29 | 30 | class WADFile 31 | { 32 | protected: 33 | #pragma pack(1) 34 | struct Header 35 | { 36 | char wadType[4]; 37 | int numEntries; 38 | int dirLocation; 39 | }; 40 | 41 | struct Lump 42 | { 43 | int dataOffset; 44 | int dataSize; 45 | char lumpName[8]; 46 | }; 47 | 48 | struct Patch 49 | { 50 | short width; 51 | short height; 52 | short left; 53 | short top; 54 | std::unique_ptr pixels; 55 | }; 56 | 57 | struct PatchInfo 58 | { 59 | short originx; 60 | short originy; 61 | short patch; 62 | short stepdir; 63 | short colormap; 64 | }; 65 | 66 | struct TextureInfo 67 | { 68 | char name[8]; 69 | int masked; 70 | short width; 71 | short height; 72 | int directory; 73 | short patchcount; 74 | }; 75 | #pragma pack() 76 | 77 | std::vector LoadLump(std::ifstream& infile, const Lump& lump); 78 | 79 | enum class LumpType 80 | { 81 | Unknown, 82 | MapMarker, 83 | Palette, 84 | Texture, 85 | PatchNames, 86 | PatchesStart, 87 | PatchesEnd, 88 | FlatsStart, 89 | FlatsEnd, 90 | SpritesStart, 91 | SpritesEnd 92 | }; 93 | 94 | LumpType GetLumpType(const Lump& lumpName) const; 95 | void TryLoadGWA(const std::string& fileName); 96 | 97 | std::map> m_patches; 98 | 99 | std::vector m_patchNames; 100 | 101 | void PastePatch(Texture* texture, const Patch* patch, int x, int y); 102 | 103 | std::shared_ptr FlipPatch(const std::shared_ptr& patch); 104 | 105 | std::shared_ptr LoadPatch(const std::vector& patchLump); 106 | 107 | public: 108 | Palette m_palette; 109 | 110 | static const std::map m_thingTypes; 111 | std::map m_maps; 112 | std::map> m_textures; 113 | std::map> m_sprites; 114 | 115 | WADFile(const std::string& fileName); 116 | ~WADFile(); 117 | }; 118 | } // namespace rtdoom 119 | -------------------------------------------------------------------------------- /rtdoom/Viewport.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Viewport.h" 3 | #include "FrameBuffer32.h" 4 | 5 | namespace rtdoom 6 | { 7 | Viewport::Viewport(SDL_Renderer* sdlRenderer, Renderer& renderer, int width, int height, const Palette& palette, bool fillTarget) : 8 | m_sdlRenderer(sdlRenderer), m_renderer(renderer), m_palette(palette), m_width(width), m_height(height), m_fillTarget(fillTarget) 9 | { 10 | Initialize(); 11 | } 12 | 13 | void Viewport::Initialize() 14 | { 15 | m_screenTexture = SDL_CreateTexture(m_sdlRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, m_width, m_height); 16 | 17 | if(m_screenTexture == nullptr) 18 | { 19 | throw std::runtime_error("Unable to create texture"); 20 | } 21 | 22 | m_frameBuffer.reset(new FrameBuffer32(m_width, m_height, m_palette)); 23 | 24 | if(!m_fillTarget) 25 | { 26 | m_targetRect.reset(new SDL_Rect()); 27 | m_targetRect->x = 0; 28 | m_targetRect->y = 0; 29 | m_targetRect->w = m_width; 30 | m_targetRect->h = m_height; 31 | } 32 | else 33 | { 34 | m_targetRect.reset(); 35 | } 36 | } 37 | 38 | void Viewport::Uninitialize() 39 | { 40 | SDL_DestroyTexture(m_screenTexture); 41 | } 42 | 43 | void Viewport::Resize(int width, int height) 44 | { 45 | Uninitialize(); 46 | m_width = width; 47 | m_height = height; 48 | Initialize(); 49 | } 50 | 51 | void Viewport::Draw() 52 | { 53 | void* pixelBuffer; 54 | int pitch; 55 | 56 | if(SDL_LockTexture(m_screenTexture, NULL, &pixelBuffer, &pitch)) 57 | { 58 | throw std::runtime_error("Unable to lock texture"); 59 | } 60 | 61 | m_frameBuffer->Attach(pixelBuffer, []() {}); 62 | 63 | m_renderer.RenderFrame(*m_frameBuffer); 64 | 65 | SDL_UnlockTexture(m_screenTexture); 66 | 67 | if(SDL_RenderCopy(m_sdlRenderer, m_screenTexture, NULL, m_targetRect.get())) 68 | { 69 | throw std::runtime_error("Unable to render texture"); 70 | } 71 | } 72 | 73 | void Viewport::DrawSteps() 74 | { 75 | void* pixelBuffer = new int[m_width * m_height]; 76 | memset(pixelBuffer, 0x00ffffff, sizeof(int) * m_width * m_height); 77 | int knt = 0; 78 | 79 | m_frameBuffer->Attach(pixelBuffer, [&]() { 80 | knt++; 81 | knt %= 2; 82 | if(knt != 1) 83 | { 84 | return; 85 | } 86 | void* sdlBuffer; 87 | int pitch; 88 | 89 | if(SDL_LockTexture(m_screenTexture, NULL, &sdlBuffer, &pitch)) 90 | { 91 | throw std::runtime_error("Unable to lock texture"); 92 | } 93 | 94 | memcpy(sdlBuffer, pixelBuffer, sizeof(int) * m_width * m_height); 95 | 96 | SDL_UnlockTexture(m_screenTexture); 97 | 98 | if(SDL_RenderCopy(m_sdlRenderer, m_screenTexture, NULL, m_targetRect.get())) 99 | { 100 | throw std::runtime_error("Unable to render texture"); 101 | } 102 | 103 | SDL_RenderPresent(m_sdlRenderer); 104 | }); 105 | 106 | m_renderer.RenderFrame(*m_frameBuffer); 107 | 108 | delete[] pixelBuffer; 109 | } 110 | 111 | Viewport::~Viewport() 112 | { 113 | Uninitialize(); 114 | } 115 | } // namespace rtdoom 116 | -------------------------------------------------------------------------------- /rtdoom/MathCache.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "MathCache.h" 3 | 4 | namespace rtdoom 5 | { 6 | MathCache::MathCache() 7 | { 8 | for(size_t i = 0; i <= s_precision; i++) 9 | { 10 | _tan[i] = tanf(i * PI4 / s_precision); 11 | _atan[i] = atanf(i * PI4 / s_precision); 12 | _cos[i] = cosf(i * 2.0f * PI / s_precision); 13 | _sin[i] = sinf(i * 2.0f * PI / s_precision); 14 | } 15 | } 16 | 17 | int MathCache::sign(float v) const 18 | { 19 | return v < 0 ? 1 : 0; 20 | } 21 | 22 | float MathCache::FastArcTan(float x) const 23 | { 24 | return PI4 * x - x * (x - 1) * (0.2447f + 0.0663f * x); 25 | } 26 | 27 | float MathCache::_atan2f(float y, float x) const 28 | { 29 | if(x == 1.0f) 30 | { 31 | return atanf(y); 32 | } 33 | 34 | uint32_t m = 2 * sign(x) + sign(y); 35 | if(y == 0) 36 | { 37 | switch(m) 38 | { 39 | case 0: 40 | case 1: 41 | return y; /* atan(+-0,+anything)=+-0 */ 42 | case 2: 43 | return PI; /* atan(+0,-anything) = pi */ 44 | case 3: 45 | return -PI; /* atan(-0,-anything) =-pi */ 46 | } 47 | } 48 | 49 | if(x == 0) 50 | { 51 | return m & 1 ? -PI2 : PI2; 52 | } 53 | 54 | float z; 55 | auto v = fabsf(y / x); 56 | if(v < 1) 57 | { 58 | z = FastArcTan(v); 59 | } 60 | else 61 | { 62 | z = atanf(v); 63 | } 64 | switch(m) 65 | { 66 | case 0: 67 | return z; /* atan(+,+) */ 68 | case 1: 69 | return -z; /* atan(-,+) */ 70 | case 2: 71 | return PI - z; /* atan(+,-) */ 72 | default: 73 | return z - PI; /* atan(-,-) */ 74 | } 75 | } 76 | 77 | float MathCache::ArcTan(float x) const 78 | { 79 | if constexpr(!s_useCache) 80 | { 81 | return atanf(x); 82 | } 83 | auto v = static_cast(fabsf(x) / PI4 * s_precision); 84 | auto s = x < 0 ? -1 : 1; 85 | return _atan[v % (s_precision + 1)] * s; 86 | } 87 | 88 | float MathCache::ArcTan(float dy, float dx) const 89 | { 90 | if constexpr(!s_useCache) 91 | { 92 | return atan2f(dy, dx); 93 | } 94 | return _atan2f(dy, dx); 95 | } 96 | 97 | float MathCache::Cos(float x) const 98 | { 99 | if constexpr(!s_useCache) 100 | { 101 | return cosf(x); 102 | } 103 | while(x < 2 * PI) 104 | { 105 | x += 2 * PI; 106 | } 107 | auto v = static_cast(x / (2 * PI) * s_precision); 108 | return _cos[v % (s_precision + 1)]; 109 | } 110 | 111 | float MathCache::Sin(float x) const 112 | { 113 | if constexpr(!s_useCache) 114 | { 115 | return sinf(x); 116 | } 117 | while(x < 2 * PI) 118 | { 119 | x += 2 * PI; 120 | } 121 | auto v = static_cast(x / (2 * PI) * s_precision); 122 | return _sin[v % (s_precision + 1)]; 123 | } 124 | 125 | float MathCache::Tan(float x) const 126 | { 127 | if constexpr(!s_useCache) 128 | { 129 | return tanf(x); 130 | } 131 | auto v = static_cast(fabsf(x) / PI4 * s_precision); 132 | auto s = x < 0 ? -1 : 1; 133 | return _tan[v % (s_precision + 1)] * s; 134 | } 135 | 136 | const MathCache& MathCache::instance() 137 | { 138 | const static MathCache cache; 139 | return cache; 140 | } 141 | } // namespace rtdoom 142 | -------------------------------------------------------------------------------- /rtdoom/GameLoop.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "GameLoop.h" 3 | 4 | namespace rtdoom 5 | { 6 | GameLoop::GameLoop(SDL_Renderer* sdlRenderer, SDL_Window* window, const WADFile& wadFile) : 7 | m_gameState {}, m_moveDirection {0}, m_rotateDirection {0}, m_isRunning {false}, m_stepFrame {false}, m_softwareRenderer {m_gameState, 8 | wadFile}, 9 | m_glRenderer {m_gameState, wadFile, s_displayX, s_displayY}, m_glViewport {window, m_glRenderer}, 10 | m_playerViewport {sdlRenderer, m_softwareRenderer, ViewScale(s_displayX), ViewScale(s_displayY), wadFile.m_palette, true}, 11 | m_mapRenderer {m_gameState}, 12 | m_mapViewport {sdlRenderer, m_mapRenderer, MapScale(s_displayX), MapScale(s_displayY), wadFile.m_palette, false}, 13 | m_sdlRenderer(sdlRenderer) 14 | { } 15 | 16 | constexpr int GameLoop::ViewScale(int windowSize) const 17 | { 18 | return static_cast(windowSize * s_multisamplingLevel); 19 | } 20 | 21 | constexpr int GameLoop::MapScale(int windowSize) const 22 | { 23 | return static_cast(windowSize * 0.25f); 24 | } 25 | 26 | void GameLoop::Move(int moveDirection) 27 | { 28 | m_moveDirection = moveDirection; 29 | } 30 | 31 | void GameLoop::Rotate(int rotateDirection) 32 | { 33 | m_rotateDirection = rotateDirection; 34 | } 35 | 36 | const Frame* GameLoop::RenderFrame() 37 | { 38 | if(m_renderingMode == Renderer::RenderingMode::OpenGL) 39 | { 40 | m_glViewport.Draw(); 41 | return NULL; 42 | } 43 | else 44 | { 45 | if(m_stepFrame) 46 | { 47 | m_playerViewport.DrawSteps(); 48 | StepFrame(); // disable stepping 49 | } 50 | else 51 | { 52 | m_playerViewport.Draw(); 53 | m_mapViewport.Draw(); 54 | } 55 | SDL_RenderPresent(m_sdlRenderer); 56 | return m_softwareRenderer.GetLastFrame(); 57 | } 58 | } 59 | 60 | void GameLoop::ClipPlayer() 61 | { 62 | const auto& sector = m_gameState.m_mapDef->GetSector(Point(m_gameState.m_player.x, m_gameState.m_player.y)); 63 | if(sector.has_value()) 64 | { 65 | m_gameState.m_player.z = sector.value().floorHeight + 45; 66 | } 67 | } 68 | 69 | void GameLoop::StepFrame() 70 | { 71 | m_stepFrame = !m_stepFrame; 72 | } 73 | 74 | void GameLoop::Tick(float seconds) 75 | { 76 | m_gameState.Move(m_moveDirection, m_rotateDirection, seconds); 77 | ClipPlayer(); 78 | } 79 | 80 | void GameLoop::ResizeWindow(int width, int height) 81 | { 82 | m_playerViewport.Resize(ViewScale(width), ViewScale(height)); 83 | m_mapViewport.Resize(MapScale(width), MapScale(height)); 84 | m_glViewport.Resize(width, height); 85 | } 86 | 87 | void GameLoop::SetRenderingMode(Renderer::RenderingMode renderingMode) 88 | { 89 | m_renderingMode = renderingMode; 90 | if(m_renderingMode != Renderer::RenderingMode::OpenGL) 91 | { 92 | m_softwareRenderer.SetMode(renderingMode); 93 | } 94 | } 95 | 96 | void GameLoop::Start(const MapStore& mapStore) 97 | { 98 | m_gameState.NewGame(mapStore); 99 | ClipPlayer(); 100 | m_isRunning = true; 101 | m_glViewport.Reset(); 102 | } 103 | 104 | void GameLoop::Stop() 105 | { 106 | m_isRunning = false; 107 | } 108 | 109 | GameLoop::~GameLoop() { } 110 | } // namespace rtdoom 111 | -------------------------------------------------------------------------------- /rtdoom/MapStore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace rtdoom 4 | { 5 | class MapStore 6 | { 7 | public: 8 | // serialized data structures from the original Doom format 9 | #pragma pack(1) 10 | struct Segment 11 | { 12 | unsigned short startVertex; 13 | unsigned short endVertex; 14 | signed short angle; 15 | unsigned short lineDef; 16 | signed short direction; 17 | signed short offset; 18 | }; 19 | 20 | struct GLSegment 21 | { 22 | unsigned short startVertex; 23 | unsigned short endVertex; 24 | unsigned short lineDef; 25 | unsigned short side; 26 | unsigned short partnerSeg; 27 | }; 28 | //#define VERT_IS_GL (1 << 15) 29 | 30 | struct SubSector 31 | { 32 | unsigned short numSegments; 33 | unsigned short firstSegment; 34 | }; 35 | 36 | /* struct GLSubSector : SubSector 37 | { };*/ 38 | 39 | struct Vertex 40 | { 41 | signed short x; 42 | signed short y; 43 | }; 44 | 45 | struct GLVertex 46 | { 47 | signed short xf; 48 | signed short x; 49 | signed short yf; 50 | signed short y; 51 | }; 52 | 53 | struct LineDef 54 | { 55 | unsigned short startVertex; 56 | unsigned short endVertex; 57 | unsigned short flags; 58 | unsigned short lineType; 59 | unsigned short sectorTag; 60 | unsigned short rightSideDef; 61 | unsigned short leftSideDef; 62 | }; 63 | 64 | struct SideDef 65 | { 66 | SideDef() { } 67 | 68 | signed short xOffset; 69 | signed short yOffset; 70 | char upperTexture[8]; 71 | char lowerTexture[8]; 72 | char middleTexture[8]; 73 | unsigned short sector; 74 | }; 75 | 76 | struct Sector 77 | { 78 | signed short floorHeight; 79 | signed short ceilingHeight; 80 | char floorTexture[8]; 81 | char ceilingTexture[8]; 82 | signed short lightLevel; 83 | unsigned short sectorSpecial; 84 | unsigned short sectorTag; 85 | }; 86 | 87 | struct Node 88 | { 89 | signed short partitionX; 90 | signed short partitionY; 91 | signed short deltaX; 92 | signed short deltaY; 93 | signed short rightBoxTop; 94 | signed short rightBoxBottom; 95 | signed short rightBoxLeft; 96 | signed short rightBoxRight; 97 | signed short leftBoxTop; 98 | signed short leftBoxBottom; 99 | signed short leftBoxLeft; 100 | signed short leftBoxRight; 101 | unsigned short rightChild; 102 | unsigned short leftChild; 103 | }; 104 | /* 105 | struct GLNode : Node 106 | { };*/ 107 | 108 | struct Thing 109 | { 110 | signed short x; 111 | signed short y; 112 | unsigned short a; 113 | unsigned short type; 114 | unsigned short flags; 115 | }; 116 | #pragma pack() 117 | 118 | std::vector m_segments; 119 | std::vector m_subSectors; 120 | std::vector m_vertexes; 121 | std::vector m_lineDefs; 122 | std::vector m_sideDefs; 123 | std::vector m_sectors; 124 | std::vector m_nodes; 125 | std::vector m_things; 126 | 127 | std::vector m_glVertexes; 128 | std::vector m_glSegments; 129 | /* std::vector m_glSubSectors; 130 | std::vector m_glNodes;*/ 131 | 132 | void Load(const std::string& mapFolder); 133 | void Load(const std::map>& mapLumps); 134 | void LoadGL(const std::map>& glLumps); 135 | void GetStartingPosition(signed short& x, signed short& y, unsigned short& a) const; 136 | 137 | MapStore(); 138 | ~MapStore(); 139 | }; 140 | } // namespace rtdoom 141 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Checkout config tool: https://zed0.co.uk/clang-format-configurator/ 2 | # Or http://cf.monofraps.net/ 3 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 4 | # https://github.com/01org/parameter-framework/blob/master/.clang-format 5 | 6 | # Tested on: clang-format version 6.0.1 7 | 8 | 9 | # Common settings 10 | BasedOnStyle: WebKit 11 | TabWidth: 4 12 | IndentWidth: 4 13 | UseTab: Never 14 | ColumnLimit: 140 15 | 16 | # Other languages JavaScript, Proto 17 | 18 | --- 19 | Language: Cpp 20 | 21 | # http://releases.llvm.org/6.0.1/tools/clang/docs/ClangFormatStyleOptions.html#disabling-formatting-on-a-piece-of-code 22 | # int formatted_code; 23 | # // clang-format off 24 | # void unformatted_code ; 25 | # // clang-format on 26 | # void formatted_code_again; 27 | 28 | DisableFormat: false 29 | Standard: Cpp11 30 | 31 | AccessModifierOffset: -4 32 | AlignAfterOpenBracket: true 33 | AlignConsecutiveAssignments: true 34 | AlignConsecutiveDeclarations: true 35 | AlignEscapedNewlinesLeft: false 36 | AlignOperands: true 37 | AlignTrailingComments: false 38 | AllowAllParametersOfDeclarationOnNextLine: true 39 | AllowShortBlocksOnASingleLine: false 40 | AllowShortCaseLabelsOnASingleLine: false 41 | AllowShortFunctionsOnASingleLine: Empty 42 | AllowShortIfStatementsOnASingleLine: false 43 | AllowShortLoopsOnASingleLine: false 44 | AlwaysBreakAfterDefinitionReturnType: false 45 | AlwaysBreakAfterReturnType: None 46 | AlwaysBreakBeforeMultilineStrings: false 47 | AlwaysBreakTemplateDeclarations: true 48 | BinPackArguments: false 49 | BinPackParameters: false 50 | 51 | # Configure each individual brace in BraceWrapping 52 | BreakBeforeBraces: Custom 53 | # Control of individual brace wrapping cases 54 | BraceWrapping: { 55 | AfterClass: 'true' 56 | AfterControlStatement: 'true' 57 | AfterEnum : 'true' 58 | AfterFunction : 'true' 59 | AfterNamespace : 'true' 60 | AfterStruct : 'true' 61 | AfterUnion : 'true' 62 | BeforeCatch : 'true' 63 | BeforeElse : 'true' 64 | IndentBraces : 'false' 65 | AfterExternBlock : 'true' 66 | SplitEmptyFunction : 'false' 67 | SplitEmptyRecord : 'false' 68 | SplitEmptyNamespace : 'true' 69 | } 70 | 71 | BreakAfterJavaFieldAnnotations: true 72 | BreakBeforeInheritanceComma: false 73 | BreakBeforeBinaryOperators: None 74 | BreakBeforeTernaryOperators: true 75 | BreakConstructorInitializers: AfterColon 76 | BreakStringLiterals: true 77 | 78 | CommentPragmas: '^ IWYU pragma:' 79 | CompactNamespaces: false 80 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 81 | ConstructorInitializerIndentWidth: 4 82 | ContinuationIndentWidth: 4 83 | Cpp11BracedListStyle: true 84 | DerivePointerAlignment: false 85 | ExperimentalAutoDetectBinPacking: false 86 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 87 | IndentCaseLabels: false 88 | FixNamespaceComments: true 89 | IndentWrappedFunctionNames: false 90 | KeepEmptyLinesAtTheStartOfBlocks: true 91 | MacroBlockBegin: '' 92 | MacroBlockEnd: '' 93 | JavaScriptQuotes: Double 94 | MaxEmptyLinesToKeep: 1 95 | NamespaceIndentation: None 96 | ObjCBlockIndentWidth: 4 97 | ObjCSpaceAfterProperty: true 98 | ObjCSpaceBeforeProtocolList: true 99 | PenaltyBreakBeforeFirstCallParameter: 19 100 | PenaltyBreakComment: 300 101 | PenaltyBreakFirstLessLess: 120 102 | PenaltyBreakString: 1000 103 | 104 | PenaltyExcessCharacter: 1000000 105 | PenaltyReturnTypeOnItsOwnLine: 60 106 | PointerAlignment: Left 107 | SpaceAfterCStyleCast: false 108 | SpaceBeforeAssignmentOperators: true 109 | SpaceBeforeParens: Never 110 | SpaceInEmptyParentheses: false 111 | SpacesBeforeTrailingComments: 1 112 | SpacesInAngles: false 113 | SpacesInContainerLiterals: true 114 | SpacesInCStyleCastParentheses: false 115 | SpacesInParentheses: false 116 | SpacesInSquareBrackets: false 117 | SpaceAfterTemplateKeyword: true 118 | 119 | SortUsingDeclarations: false 120 | SortIncludes: false 121 | 122 | # Comments are for developers, they should arrange them 123 | ReflowComments: false 124 | 125 | IncludeBlocks: Preserve 126 | IndentPPDirectives: AfterHash 127 | --- 128 | -------------------------------------------------------------------------------- /rtdoom/MapStructs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "rtdoom.h" 4 | #include "Helpers.h" 5 | #include "MapStore.h" 6 | 7 | namespace rtdoom 8 | { 9 | struct Point 10 | { 11 | constexpr Point(float x, float y) : x {x}, y {y} {} 12 | 13 | float x; 14 | float y; 15 | 16 | bool operator<(const Point& p) const 17 | { 18 | if(x == p.x) 19 | { 20 | return y < p.y; 21 | } 22 | return x < p.x; 23 | } 24 | 25 | bool operator==(Point& p) { 26 | return x == p.x && y == p.y; 27 | } 28 | }; 29 | 30 | struct Vertex : Point 31 | { 32 | constexpr Vertex(float x, float y) : Point {x, y} {} 33 | constexpr Vertex(int x, int y) : Point {static_cast(x), static_cast(y)} {} 34 | }; 35 | 36 | struct Location : Point 37 | { 38 | constexpr Location(float x, float y, float z) : Point {x, y}, z {z} {} 39 | 40 | float z; 41 | }; 42 | 43 | struct Thing : Location 44 | { 45 | Thing(float x, float y, float z, Angle a) : Location {x, y, z}, a {a}, type {-1}, sectorId {-1}, textureName {""} {} 46 | 47 | Angle a; 48 | int thingId; 49 | int sectorId; 50 | int type; 51 | std::string textureName; 52 | }; 53 | 54 | struct Line 55 | { 56 | constexpr Line(Vertex s, Vertex e) : s {s}, e {e} {} 57 | 58 | Vertex s; 59 | Vertex e; 60 | }; 61 | 62 | struct Sector 63 | { 64 | Sector() : sectorId {-1} {} 65 | 66 | Sector(int sectorId, const MapStore::Sector& s) : 67 | sectorId {sectorId}, floorHeight {static_cast(s.floorHeight)}, ceilingHeight {static_cast(s.ceilingHeight)}, 68 | ceilingTexture {Helpers::MakeString(s.ceilingTexture)}, floorTexture {Helpers::MakeString(s.floorTexture)}, 69 | lightLevel {s.lightLevel / 255.0f}, isSky {strcmp(s.ceilingTexture, "F_SKY1") == 0} 70 | {} 71 | 72 | int sectorId; 73 | std::string ceilingTexture; 74 | std::string floorTexture; 75 | float floorHeight; 76 | float ceilingHeight; 77 | float lightLevel; 78 | bool isSky; 79 | }; 80 | 81 | struct Side 82 | { 83 | Side(Sector sector, 84 | const std::string lowerTexture, 85 | const std::string middleTexture, 86 | const std::string upperTexture, 87 | int xOffset, 88 | int yOffset) : 89 | sideless(false), sector {sector}, 90 | lowerTexture {lowerTexture}, middleTexture {middleTexture}, upperTexture {upperTexture}, xOffset {xOffset}, yOffset {yOffset} 91 | {} 92 | 93 | Side() : sideless(true) { } 94 | 95 | bool sideless; 96 | Sector sector; 97 | int xOffset; 98 | int yOffset; 99 | std::string lowerTexture; 100 | std::string upperTexture; 101 | std::string middleTexture; 102 | }; 103 | 104 | struct Segment : Line 105 | { 106 | Segment(Vertex s, Vertex e, bool isSolid, Side frontSide, Side backSide, int xOffset, bool lowerUnpegged, bool upperUnpegged) : 107 | Line {s, e}, isSolid {isSolid}, frontSide {frontSide}, backSide {backSide}, xOffset {xOffset}, lowerUnpegged {lowerUnpegged}, 108 | upperUnpegged {upperUnpegged}, length {sqrt((e.x - s.x) * (e.x - s.x) + (e.y - s.y) * (e.y - s.y))} 109 | {} 110 | 111 | bool isSolid; 112 | Side frontSide; 113 | Side backSide; 114 | int xOffset; 115 | bool lowerUnpegged; 116 | bool upperUnpegged; 117 | float length; 118 | 119 | bool isHorizontal = s.y == e.y; 120 | bool isVertical = s.x == e.x; 121 | }; 122 | 123 | struct SubSector 124 | { 125 | SubSector(int subSectorId, int sectorId, std::vector> segments) : 126 | subSectorId {subSectorId}, sectorId {sectorId}, segments(segments) 127 | { } 128 | 129 | int subSectorId; 130 | int sectorId; 131 | std::vector> segments; 132 | }; 133 | 134 | struct Vector 135 | { 136 | constexpr Vector(Angle a, float d) : a {a}, d {d} {} 137 | constexpr Vector() : Vector {0, 0} {} 138 | 139 | Angle a; 140 | float d; 141 | }; 142 | } // namespace rtdoom 143 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | *.wad 263 | /rtdoom/freedoom1.WAx 264 | /rtdoom/GLRenderer-v1.cpp 265 | /rtdoom/GLRenderer-v2.cpp 266 | -------------------------------------------------------------------------------- /rtdoom/FrameBuffer32.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "FrameBuffer32.h" 3 | 4 | namespace rtdoom 5 | { 6 | FrameBuffer32::FrameBuffer32(int width, int height, const Palette& palette) : FrameBuffer {width, height, palette} 7 | { 8 | for(auto l = 0; l < 256; l++) 9 | { 10 | c_lightMap[l] = new unsigned char[256]; 11 | for(auto v = 0; v < 256; v++) 12 | { 13 | c_lightMap[l][v] = static_cast(static_cast(v) * static_cast(l) / 256.0f); 14 | } 15 | } 16 | } 17 | 18 | void FrameBuffer32::Attach(void* pixels, std::function stepCallback) 19 | { 20 | m_pixels = reinterpret_cast(pixels); 21 | m_stepCallback = stepCallback; 22 | } 23 | 24 | void FrameBuffer32::Clear() 25 | { 26 | memset(reinterpret_cast(m_pixels), 0, m_width * m_height * 4); 27 | m_stepCallback(); 28 | } 29 | 30 | void FrameBuffer32::VerticalLine(int x, int sy, int ey, int colorIndex, float lightness) noexcept 31 | { 32 | if(m_pixels != nullptr && x >= 0 && x < m_width) 33 | { 34 | sy = std::max(0, std::min(sy, m_height - 1)); 35 | ey = std::max(0, std::min(ey, m_height - 1)); 36 | if(sy > ey) 37 | { 38 | std::swap(sy, ey); 39 | } 40 | 41 | const auto color = s_colors.at(colorIndex % s_colors.size()); 42 | const auto lightMap = c_lightMap[Gamma(lightness)]; 43 | Pixel32 pixel; 44 | 45 | pixel.argb32 = color; 46 | pixel.argb8.r = lightMap[pixel.argb8.r]; 47 | pixel.argb8.g = lightMap[pixel.argb8.g]; 48 | pixel.argb8.b = lightMap[pixel.argb8.b]; 49 | 50 | auto offset = m_width * sy + (m_width - x - 1); 51 | for(auto y = sy; y <= ey; y++) 52 | { 53 | m_pixels[offset].argb32 = pixel.argb32; 54 | offset += m_width; 55 | } 56 | m_stepCallback(); 57 | } 58 | } 59 | 60 | void FrameBuffer32::VerticalLine(int x, int sy, const std::vector& texels, float lightness) noexcept 61 | { 62 | if(m_pixels != nullptr && x >= 0 && x < m_width && texels.size() && sy < m_height) 63 | { 64 | const auto lightMap = c_lightMap[Gamma(lightness)]; 65 | auto offset = m_width * sy + (m_width - x - 1); 66 | for(const auto t : texels) 67 | { 68 | if(t != 247 && sy >= 0) 69 | { 70 | const auto& color = m_palette.colors[t]; 71 | Pixel32& pixel = m_pixels[offset]; 72 | 73 | pixel.argb8.r = lightMap[color.r]; 74 | pixel.argb8.g = lightMap[color.g]; 75 | pixel.argb8.b = lightMap[color.b]; 76 | } 77 | offset += m_width; 78 | sy++; 79 | if(sy == m_height) 80 | { 81 | break; 82 | } 83 | } 84 | m_stepCallback(); 85 | } 86 | } 87 | 88 | void FrameBuffer32::HorizontalLine(int sx, int y, const std::vector& texels, float lightness) noexcept 89 | { 90 | if(m_pixels != nullptr && y >= 0 && y < m_height && texels.size()) 91 | { 92 | const auto lightMap = c_lightMap[Gamma(lightness)]; 93 | auto offset = m_width * y + (m_width - sx - 1); 94 | for(const auto t : texels) 95 | { 96 | const auto& color = m_palette.colors[t]; 97 | Pixel32& pixel = m_pixels[offset]; 98 | 99 | pixel.argb8.r = lightMap[color.r]; 100 | pixel.argb8.g = lightMap[color.g]; 101 | pixel.argb8.b = lightMap[color.b]; 102 | offset--; 103 | } 104 | m_stepCallback(); 105 | } 106 | } 107 | 108 | void FrameBuffer32::HorizontalLine(int sx, int ex, int y, int colorIndex, float lightness) noexcept 109 | { 110 | if(m_pixels != nullptr && y >= 0 && y < m_height && sx >= 0 && ex < m_width) 111 | { 112 | const auto lightMap = c_lightMap[Gamma(lightness)]; 113 | auto offset = m_width * y + (m_width - sx - 1); 114 | for(auto x = sx; x <= ex; x++) 115 | { 116 | const auto& color = s_colors[colorIndex]; 117 | Pixel32& pixel = m_pixels[offset]; 118 | 119 | pixel.argb32 = color; 120 | pixel.argb8.r = lightMap[pixel.argb8.r]; 121 | pixel.argb8.g = lightMap[pixel.argb8.g]; 122 | pixel.argb8.b = lightMap[pixel.argb8.b]; 123 | offset--; 124 | } 125 | m_stepCallback(); 126 | } 127 | } 128 | 129 | void FrameBuffer32::VerticalLine(int x, int sy, const std::vector& texels, const std::vector& lightnesses) noexcept 130 | { 131 | if(m_pixels != nullptr && x >= 0 && x < m_width && texels.size()) 132 | { 133 | auto offset = m_width * sy + (m_width - x - 1); 134 | auto lightnessIter = lightnesses.begin(); 135 | for(const auto t : texels) 136 | { 137 | const auto& color = m_palette.colors[t]; 138 | Pixel32& pixel = m_pixels[offset]; 139 | const auto lightMap = c_lightMap[Gamma(*lightnessIter++)]; 140 | 141 | pixel.argb8.r = lightMap[color.r]; 142 | pixel.argb8.g = lightMap[color.g]; 143 | pixel.argb8.b = lightMap[color.b]; 144 | offset += m_width; 145 | } 146 | m_stepCallback(); 147 | } 148 | } 149 | 150 | void FrameBuffer32::SetPixel(int x, int y, int colorIndex, float lightness) noexcept 151 | { 152 | if(m_pixels != nullptr && x >= 0 && y >= 0 && x < m_width && y < m_height) 153 | { 154 | const auto& color = m_palette.colors[colorIndex]; 155 | const auto offset = m_width * y + (m_width - x - 1); 156 | const auto lightMap = c_lightMap[Gamma(lightness)]; 157 | Pixel32& pixel = m_pixels[offset]; 158 | 159 | pixel.argb8.r = lightMap[color.r]; 160 | pixel.argb8.g = lightMap[color.g]; 161 | pixel.argb8.b = lightMap[color.b]; 162 | } 163 | } 164 | 165 | FrameBuffer32::~FrameBuffer32() 166 | { 167 | for(auto l = 0; l < 256; l++) 168 | { 169 | delete[] c_lightMap[l]; 170 | } 171 | } 172 | } // namespace rtdoom 173 | -------------------------------------------------------------------------------- /rtdoom/rtdoom.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | Header Files 38 | 39 | 40 | Header Files 41 | 42 | 43 | Header Files 44 | 45 | 46 | Header Files 47 | 48 | 49 | Header Files 50 | 51 | 52 | Header Files 53 | 54 | 55 | Header Files 56 | 57 | 58 | Header Files 59 | 60 | 61 | Header Files 62 | 63 | 64 | Header Files 65 | 66 | 67 | Header Files 68 | 69 | 70 | Header Files 71 | 72 | 73 | Header Files 74 | 75 | 76 | Header Files 77 | 78 | 79 | Header Files 80 | 81 | 82 | Header Files 83 | 84 | 85 | Header Files 86 | 87 | 88 | Header Files 89 | 90 | 91 | Header Files 92 | 93 | 94 | Header Files 95 | 96 | 97 | Header Files 98 | 99 | 100 | 101 | 102 | Source Files 103 | 104 | 105 | Source Files 106 | 107 | 108 | Source Files 109 | 110 | 111 | Source Files 112 | 113 | 114 | Source Files 115 | 116 | 117 | Source Files 118 | 119 | 120 | Source Files 121 | 122 | 123 | Source Files 124 | 125 | 126 | Source Files 127 | 128 | 129 | Source Files 130 | 131 | 132 | Source Files 133 | 134 | 135 | Source Files 136 | 137 | 138 | Source Files 139 | 140 | 141 | Source Files 142 | 143 | 144 | Source Files 145 | 146 | 147 | Source Files 148 | 149 | 150 | Source Files 151 | 152 | 153 | Source Files 154 | 155 | 156 | Source Files 157 | 158 | 159 | Source Files 160 | 161 | 162 | Source Files 163 | 164 | 165 | Source Files 166 | 167 | 168 | Source Files 169 | 170 | 171 | Source Files 172 | 173 | 174 | Source Files 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /rtdoom/Frame.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FrameBuffer.h" 4 | #include "MapDef.h" 5 | 6 | namespace rtdoom 7 | { 8 | // class holding working structures used in drawing a single frame 9 | class Frame 10 | { 11 | public: 12 | // general horizontal or vertical screen span (line) 13 | struct Span 14 | { 15 | constexpr Span(int s, int e) : s {s}, e {e} {} 16 | constexpr Span() : Span {0, 0} {} 17 | bool isVisible() const 18 | { 19 | return (s != 0 || e != 0); 20 | }; 21 | int s; 22 | int e; 23 | int length() const 24 | { 25 | return e - s; 26 | } 27 | 28 | bool operator<(const Span& rhs) const 29 | { 30 | if(s == rhs.s) 31 | { 32 | return e < rhs.e; 33 | } 34 | else 35 | { 36 | return s < rhs.s; 37 | } 38 | } 39 | }; 40 | 41 | // screen area covered by floor or ceiling 42 | struct Plane 43 | { 44 | Plane(float h, const std::string& textureName, float lightLevel, int height) : 45 | h {h}, textureName {textureName}, lightLevel {lightLevel}, spans(height) 46 | {} 47 | bool isSky() const 48 | { 49 | return isnan(h); 50 | } 51 | const std::string& textureName; 52 | const float lightLevel; 53 | const float h; 54 | std::vector> spans; 55 | 56 | void addSpan(int x, int sy, int ey); 57 | }; 58 | 59 | // texturing information 60 | struct PainterContext 61 | { 62 | PainterContext() : yPegging {0}, yOffset {0} {} 63 | std::string textureName; 64 | float yScale; 65 | float texelX; 66 | int yPegging; 67 | int yOffset; 68 | bool isEdge; 69 | float lightness; 70 | }; 71 | 72 | // silhouette of a drawn wall 73 | struct Clip 74 | { 75 | Clip(Span xSpan) : xSpan {xSpan}, topClips(), bottomClips(), texelXs(), yScales() {} 76 | 77 | void Add(int x, const PainterContext& painterContext, int topClip, int bottomClip) 78 | { 79 | if(x == xSpan.s) 80 | { 81 | yScaleStart = painterContext.yScale; 82 | } 83 | else if(x == xSpan.e) 84 | { 85 | yScaleEnd = painterContext.yScale; 86 | } 87 | topClips.push_back(topClip); 88 | bottomClips.push_back(bottomClip); 89 | texelXs.push_back(static_cast(painterContext.texelX)); 90 | yScales.push_back(painterContext.yScale); 91 | } 92 | 93 | Span xSpan; 94 | 95 | float yScaleStart; 96 | float yScaleEnd; 97 | 98 | std::vector topClips; 99 | std::vector bottomClips; 100 | std::vector texelXs; 101 | std::vector yScales; 102 | }; 103 | 104 | // overlay sprite drawn in last phase 105 | struct Sprite 106 | { 107 | Sprite(float distance) : distance(distance) {} 108 | 109 | float distance; 110 | 111 | virtual bool IsThing() const 112 | { 113 | return false; 114 | } 115 | 116 | virtual bool IsWall() const 117 | { 118 | return false; 119 | } 120 | 121 | virtual ~Sprite() {} 122 | }; 123 | 124 | // thing/object sprite 125 | struct SpriteThing : public Sprite, public Thing 126 | { 127 | SpriteThing(const Thing& thing, float distance) : Thing(thing), Sprite(distance) {} 128 | 129 | bool IsThing() const override 130 | { 131 | return true; 132 | } 133 | }; 134 | 135 | // semi-transparent wall sprite 136 | struct SpriteWall : public Sprite 137 | { 138 | SpriteWall(int x, const Span& span, PainterContext& textureContext, float distance) : 139 | Sprite(distance), x(x), span(span), textureContext(textureContext) 140 | {} 141 | 142 | bool IsWall() const override 143 | { 144 | return true; 145 | } 146 | 147 | int x; 148 | Span span; 149 | PainterContext textureContext; 150 | }; 151 | 152 | // viewport 153 | const int m_width; 154 | const int m_height; 155 | 156 | // list of horizontal screen spans where isSolid walls have already been drawn (completely occluded) 157 | std::list m_occlusion; 158 | 159 | // drawn walls that clip anything behind them 160 | std::list m_clips; 161 | 162 | // sprites (in-game objects and semi-transparent walls) 163 | std::vector> m_sprites; 164 | 165 | // screen height where the last floor/ceilings have been drawn up to so far 166 | std::vector m_floorClip; 167 | std::vector m_ceilClip; 168 | 169 | // numer of drawn segments 170 | int m_numSegments = 0; 171 | int m_numFloorPlanes = 0; 172 | int m_numCeilingPlanes = 0; 173 | int m_numVerticallyOccluded = 0; 174 | 175 | // spaces between walls with floors and ceilings 176 | std::deque m_floorPlanes; 177 | std::deque m_ceilingPlanes; 178 | 179 | // visible sectors 180 | std::unordered_set m_sectors; 181 | 182 | // add vertical span to existing planes 183 | void MergeIntoPlane(std::deque& planes, float height, const std::string& textureName, float lightLevel, int x, int sy, int ey); 184 | 185 | // returns horizontal screen spans where the mapSegment is visible and updates occlusion table 186 | std::vector ClipHorizontalSegment(int startX, int endX, bool isSolid); 187 | 188 | // returns the vertical screen span where the column is visible and updates occlussion table 189 | Span ClipVerticalSegment(int x, 190 | int ceilingProjection, 191 | int floorProjection, 192 | bool isSolid, 193 | const float* ceilingHeight, 194 | const float* floorHeight, 195 | const std::string& ceilingTexture, 196 | const std::string& floorTexture, 197 | float lightLevel); 198 | 199 | bool IsSpanVisible(int x, int sy, int ey) const; 200 | bool IsOccluded() const; 201 | bool IsVerticallyOccluded(int x) const; 202 | 203 | Frame(const FrameBuffer& frameBuffer); 204 | ~Frame(); 205 | }; 206 | } // namespace rtdoom 207 | -------------------------------------------------------------------------------- /rtdoom/Projection.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "rtdoom.h" 3 | #include "Projection.h" 4 | #include "MathCache.h" 5 | 6 | namespace rtdoom 7 | { 8 | Projection::Projection(const Thing& player, const FrameBuffer& frameBuffer) : 9 | m_player {player}, m_viewWidth {frameBuffer.m_width}, m_midPointX {frameBuffer.m_width / 2}, m_viewHeight {frameBuffer.m_height}, 10 | m_midPointY {frameBuffer.m_height / 2} 11 | {} 12 | 13 | // normal vector from a map line towards the player 14 | Vector Projection::NormalVector(const Line& l) const 15 | { 16 | auto lineAngle = 17 | MathCache::instance().ArcTan(l.e.y - l.s.y, l.e.x - l.s.x); // 0 is towards positive X axis, clockwise, (0,0) is top-left 18 | auto normalAngle = lineAngle + PI2; 19 | auto sx = l.s.x - m_player.x; 20 | auto sy = l.s.y - m_player.y; 21 | auto startDistance = Projection::Distance(l.s, m_player); 22 | auto startAngle = MathCache::instance().ArcTan(sy, sx); 23 | auto normalDistance = startDistance * MathCache::instance().Cos(normalAngle - startAngle); 24 | return Vector(normalAngle, abs(normalDistance)); 25 | } 26 | 27 | // distance the starting edge of the line to its normal vector start 28 | float Projection::NormalOffset(const Line& l) const 29 | { 30 | auto lineAngle = 31 | MathCache::instance().ArcTan(l.e.y - l.s.y, l.e.x - l.s.x); // 0 is towards positive X axis, clockwise, (0,0) is top-left 32 | auto normalAngle = lineAngle + PI2; 33 | auto sx = l.s.x - m_player.x; 34 | auto sy = l.s.y - m_player.y; 35 | auto startDistance = Projection::Distance(l.s, m_player); 36 | auto startAngle = MathCache::instance().ArcTan(sy, sx); 37 | auto normalOffset = fabs(startDistance * MathCache::instance().Sin(normalAngle - startAngle)); 38 | if(NormalizeAngle(normalAngle - startAngle) > 0) 39 | { 40 | normalOffset *= -1; 41 | } 42 | return normalOffset; 43 | } 44 | 45 | // absolute angle to a map point 46 | Angle Projection::AbsoluteAngle(const Point& p) const 47 | { 48 | return MathCache::instance().ArcTan(p.y - m_player.y, p.x - m_player.x); 49 | } 50 | 51 | // relative projected view a for a map point 52 | Angle Projection::ProjectionAngle(const Point& p) const 53 | { 54 | return NormalizeAngle(AbsoluteAngle(p) - m_player.a); 55 | } 56 | 57 | // projection d from player to the line specified by the normal vector at specific viewAngle 58 | float Projection::Distance(const Vector& normalVector, Angle viewAngle) const 59 | { 60 | auto inverseNormalAngle = normalVector.a - PI; 61 | auto viewRelativeAngle = NormalizeAngle(inverseNormalAngle - (m_player.a + viewAngle)); 62 | auto interceptDistance = normalVector.d / MathCache::instance().Cos(viewRelativeAngle); 63 | return abs(MathCache::instance().Cos(viewAngle) * interceptDistance); 64 | } 65 | 66 | // simple distance between two points 67 | float Projection::Distance(const Point& a, const Point& b) 68 | { 69 | const auto dx = b.x - a.x; 70 | const auto dy = b.y - a.y; 71 | return sqrt(dx * dx + dy * dy); 72 | } 73 | 74 | // texture offset vs normal intercept 75 | float Projection::Offset(const Vector& normalVector, Angle viewAngle) const 76 | { 77 | auto inverseNormalAngle = normalVector.a - PI; 78 | auto viewRelativeAngle = NormalizeAngle(inverseNormalAngle - (m_player.a + viewAngle)); 79 | auto interceptDistance = normalVector.d / MathCache::instance().Cos(viewRelativeAngle); 80 | auto offset = fabsf(interceptDistance * MathCache::instance().Sin(viewRelativeAngle)); 81 | if(viewRelativeAngle > 0) 82 | { 83 | offset *= -1; 84 | } 85 | return offset; 86 | } 87 | 88 | float Projection::PlaneDistance(int y, float height) const noexcept 89 | { 90 | auto dy = abs(y - m_midPointY); 91 | return (m_viewHeight * 30.0f) * fabsf(height / 23.0f) / static_cast(dy); 92 | } 93 | 94 | Angle Projection::AngleDist(Angle a1, Angle a2) noexcept 95 | { 96 | return abs(NormalizeAngle(a2) - NormalizeAngle(a1)); 97 | } 98 | 99 | // normalize a to -PI..PI 100 | Angle Projection::NormalizeAngle(Angle angle) noexcept 101 | { 102 | while(angle < -PI) 103 | { 104 | angle += 2 * PI; 105 | } 106 | while(angle > PI) 107 | { 108 | angle -= 2 * PI; 109 | } 110 | return angle; 111 | } 112 | 113 | // convert eye a (-PI4..PI4) view to screen X coordinate 114 | int Projection::ViewX(Angle viewAngle) const noexcept 115 | { 116 | viewAngle = NormalizeAngle(viewAngle); 117 | if(viewAngle <= -PI4) 118 | { 119 | return -1; 120 | } 121 | if(viewAngle >= PI4) 122 | { 123 | return m_viewWidth; 124 | } 125 | auto midDistance = MathCache::instance().Tan(viewAngle) / PI4; 126 | if(midDistance <= -1) 127 | { 128 | return -1; 129 | } 130 | if(midDistance >= 1) 131 | { 132 | return m_viewWidth; 133 | } 134 | return static_cast(m_midPointX * (1 + midDistance)); 135 | } 136 | 137 | // convert screen X coordinate to eye a (-PI4..PI4) 138 | Angle Projection::ViewAngle(int viewX) const noexcept 139 | { 140 | auto relativeX = (viewX - m_midPointX); 141 | auto fractionX = static_cast(relativeX) / m_midPointX; 142 | return MathCache::instance().ArcTan(fractionX * PI4); 143 | } 144 | 145 | // vertical screen position for given distance and height difference 146 | int Projection::ViewY(float distance, float height) const noexcept 147 | { 148 | auto dc = (m_viewHeight * 30.0f) / distance; 149 | auto hf = fabsf(height / 23.0f); 150 | auto dy = static_cast(dc * hf); 151 | if(height > 0) 152 | { 153 | return m_midPointY - dy; 154 | } 155 | return m_midPointY + dy; 156 | } 157 | 158 | // scaling factor for texturing 159 | float Projection::TextureScale(float distance) const noexcept 160 | { 161 | return distance / (m_viewHeight * 1.30434782f); 162 | } 163 | 164 | // trim angles for a visible span 165 | bool Projection::NormalizeViewAngleSpan(Angle& startAngle, Angle& endAngle) noexcept 166 | { 167 | if(abs(AngleDist(startAngle, endAngle)) > PI) 168 | { 169 | // crosses behind the player, wrap over the back edge 170 | if(startAngle < -PI2) 171 | { 172 | startAngle = PI2; 173 | } 174 | else if(startAngle > PI2) 175 | { 176 | startAngle = -PI2; 177 | } 178 | else if(endAngle < -PI2) 179 | { 180 | endAngle = PI2; 181 | } 182 | else if(endAngle > PI2) 183 | { 184 | endAngle = -PI2; 185 | } 186 | } 187 | 188 | if(startAngle < -PI4 && endAngle < -PI4) 189 | { 190 | return false; 191 | } 192 | if(endAngle > PI4 && startAngle > PI4) 193 | { 194 | return false; 195 | } 196 | 197 | return true; 198 | } 199 | 200 | // calculate the lightness of a mapSegment based on its distance 201 | float Projection::Lightness(float distance, const Segment* segment) const 202 | { 203 | auto lightness = 0.9f - (distance / s_lightnessFactor); 204 | 205 | if(segment) 206 | { 207 | // auto-shade 90-degree edges 208 | if(segment->isVertical) 209 | { 210 | lightness *= 1.1f; 211 | } 212 | else if(segment->isHorizontal) 213 | { 214 | lightness *= 0.9f; 215 | } 216 | } 217 | 218 | return lightness; 219 | } 220 | 221 | Projection::~Projection() {} 222 | } // namespace rtdoom 223 | -------------------------------------------------------------------------------- /rtdoom/TexturePainter.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "TexturePainter.h" 3 | #include "Projection.h" 4 | #include "MathCache.h" 5 | 6 | namespace rtdoom 7 | { 8 | TexturePainter::TexturePainter(FrameBuffer& frameBuffer, const Thing& pov, const Projection& projection, const WADFile& wadFile) : 9 | Painter {frameBuffer}, m_pov {pov}, m_projection {projection}, m_wadFile {wadFile} 10 | {} 11 | 12 | void TexturePainter::PaintWall(int x, const Frame::Span& span, const Frame::PainterContext& textureContext) const 13 | { 14 | if(textureContext.textureName.length() && textureContext.textureName[0] != '-') 15 | { 16 | const auto it = m_wadFile.m_textures.find(textureContext.textureName); 17 | if(it == m_wadFile.m_textures.end()) 18 | { 19 | return; 20 | } 21 | const auto& texture = it->second; 22 | const auto tx = Helpers::Clip(static_cast(textureContext.texelX), texture->width); 23 | 24 | const auto sy = std::max(0, span.s); 25 | const auto ey = std::min(m_frameBuffer.m_height - 1, span.e); 26 | const auto ny = ey - sy + 1; 27 | std::vector texels(ny); 28 | 29 | const float vStep = textureContext.yScale; 30 | float vs = (sy - textureContext.yPegging) * vStep; 31 | for(auto dy = sy; dy <= ey; dy++) 32 | { 33 | const auto ty = Helpers::Clip(static_cast(vs) + textureContext.yOffset, texture->height); 34 | texels[dy - sy] = texture->pixels[ty * texture->width + tx]; 35 | vs += vStep; 36 | } 37 | m_frameBuffer.VerticalLine(x, sy, texels, textureContext.lightness); 38 | } 39 | } 40 | 41 | void TexturePainter::PaintSprite(int x, int sy, std::vector occlusion, const Frame::PainterContext& textureContext) const 42 | { 43 | auto it = m_wadFile.m_sprites.find(textureContext.textureName); 44 | if(it == m_wadFile.m_sprites.end()) 45 | { 46 | return; 47 | } 48 | const auto& sprite = it->second; 49 | 50 | std::vector texels; 51 | texels.reserve(occlusion.size()); 52 | const float vStep = textureContext.yScale; 53 | const auto tx = Helpers::Clip(static_cast(textureContext.texelX), sprite->width); 54 | float vs = 0; 55 | for(auto o : occlusion) 56 | { 57 | if(!o) 58 | { 59 | const auto ty = Helpers::Clip(static_cast(vs) + textureContext.yOffset, sprite->height); 60 | texels.push_back(sprite->pixels[ty * sprite->width + tx]); 61 | } 62 | else 63 | { 64 | texels.push_back(247); 65 | } 66 | vs += vStep; 67 | } 68 | m_frameBuffer.VerticalLine(x, sy, texels, textureContext.lightness); 69 | } 70 | 71 | void TexturePainter::PaintPlane(const Frame::Plane& plane) const 72 | { 73 | const bool isSky = plane.isSky(); 74 | auto it = m_wadFile.m_textures.find(isSky ? "SKY1" : plane.textureName); 75 | if(it == m_wadFile.m_textures.end()) 76 | { 77 | return; 78 | } 79 | 80 | const auto& texture = it->second; 81 | const auto angle = Projection::NormalizeAngle(m_pov.a); 82 | const auto cosA = MathCache::instance().Cos(angle); 83 | const auto sinA = MathCache::instance().Sin(angle); 84 | const auto aStep = PI4 / (m_frameBuffer.m_width / 2); 85 | 86 | // floors/ceilings are most efficient painted in horizontal strips since distance to the player is constant 87 | for(size_t y = 0; y < plane.spans.size(); y++) 88 | { 89 | const auto& spans = plane.spans[y]; 90 | if(spans.empty()) 91 | { 92 | continue; 93 | } 94 | 95 | auto mergedSpans = MergeSpans(spans); 96 | if(!isSky) 97 | { 98 | const auto centerDistance = m_projection.PlaneDistance(y, plane.h); 99 | if(isfinite(centerDistance) && centerDistance > s_minDistance) 100 | { 101 | const float lightness = m_projection.Lightness(centerDistance) * plane.lightLevel; 102 | const auto ccosA = centerDistance * cosA; 103 | const auto csinA = centerDistance * sinA; 104 | 105 | // texel steps per horizontal pixel 106 | const auto stepX = -csinA * aStep; 107 | const auto stepY = ccosA * aStep; 108 | 109 | for(const auto& span : mergedSpans) 110 | { 111 | const auto sx = std::max(0, span.s); 112 | const auto ex = std::min(m_frameBuffer.m_width - 1, span.e); 113 | const auto nx = ex - sx + 1; 114 | const auto angleTan = aStep * (sx - m_frameBuffer.m_width / 2); 115 | 116 | // starting texel position 117 | auto texelX = Helpers::Clip(m_pov.x + ccosA - csinA * angleTan, static_cast(texture->width)); 118 | auto texelY = Helpers::Clip(m_pov.y + csinA + ccosA * angleTan, static_cast(texture->height)); 119 | 120 | std::vector texels(nx); 121 | for(auto x = sx; x <= ex; x++) 122 | { 123 | const auto tx = Helpers::Clip(static_cast(texelX), texture->width); 124 | const auto ty = Helpers::Clip(static_cast(texelY), texture->height); 125 | texels[x - sx] = texture->pixels[texture->width * ty + tx]; 126 | texelX += stepX; 127 | texelY += stepY; 128 | } 129 | m_frameBuffer.HorizontalLine(sx, y, texels, lightness); 130 | } 131 | } 132 | } 133 | else 134 | { 135 | const auto horizon = m_frameBuffer.m_height / 2.0f; 136 | const auto ty = Helpers::Clip(static_cast(y / horizon / 2.0f * texture->height), texture->height); 137 | const auto xScale = 1.0f / PI4 * texture->width; 138 | for(const auto& span : mergedSpans) 139 | { 140 | const auto sx = std::max(0, span.s); 141 | const auto ex = std::min(m_frameBuffer.m_width - 1, span.e); 142 | const auto nx = ex - sx + 1; 143 | 144 | std::vector texels(nx); 145 | for(auto x = sx; x <= ex; x++) 146 | { 147 | const auto viewAngle = m_projection.ViewAngle(x); 148 | const auto tx = Helpers::Clip(static_cast((m_pov.a + viewAngle) * xScale), texture->width); 149 | texels[x - sx] = texture->pixels[texture->width * ty + tx]; 150 | } 151 | m_frameBuffer.HorizontalLine(sx, y, texels, 1); 152 | } 153 | } 154 | } 155 | } 156 | 157 | std::list TexturePainter::MergeSpans(const std::vector& spans) 158 | { 159 | std::list spanlist(spans.begin(), spans.end()); 160 | 161 | spanlist.sort(); 162 | auto es = spanlist.begin(); 163 | do 164 | { 165 | auto ps = es++; 166 | if(es != spanlist.end() && (ps->e == es->s || ps->e == es->s - 1)) 167 | { 168 | es->s = ps->s; 169 | spanlist.erase(ps); 170 | } 171 | } while(es != spanlist.end()); 172 | 173 | return spanlist; 174 | } 175 | 176 | TexturePainter::~TexturePainter() {} 177 | } // namespace rtdoom 178 | -------------------------------------------------------------------------------- /rtdoom/Frame.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Frame.h" 3 | 4 | using std::vector; 5 | using std::deque; 6 | using std::string; 7 | 8 | namespace rtdoom 9 | { 10 | Frame::Frame(const FrameBuffer& frameBuffer) : 11 | m_width {frameBuffer.m_width}, m_height {frameBuffer.m_height}, m_floorClip(frameBuffer.m_width + 1, frameBuffer.m_height), 12 | m_ceilClip(frameBuffer.m_width + 1, -1) 13 | {} 14 | 15 | // for a horizontal mapSegment, determine which columns will be visible based on already occludded map 16 | // if the mapSegment is isSolid, update occlussion map otherwise only clip 17 | vector Frame::ClipHorizontalSegment(int startX, int endX, bool isSolid) 18 | { 19 | vector visibleSpans; 20 | 21 | startX = std::max(startX, 0); 22 | endX = std::max(endX, 0); 23 | startX = std::min(startX, m_width); 24 | endX = std::min(endX, m_width); 25 | if(startX == endX) 26 | { 27 | return visibleSpans; 28 | } 29 | 30 | if(m_occlusion.empty()) 31 | { 32 | visibleSpans.push_back(Frame::Span(startX, endX)); 33 | if(isSolid) 34 | { 35 | m_occlusion.push_front(Frame::Span(startX, endX)); 36 | } 37 | } 38 | else 39 | { 40 | auto eo = m_occlusion.begin(); 41 | do 42 | { 43 | if(eo->s >= endX) 44 | { 45 | // doesn't overlap 46 | visibleSpans.push_back(Frame::Span(startX, endX)); 47 | if(isSolid) 48 | { 49 | m_occlusion.insert(eo, Frame::Span(startX, endX)); 50 | } 51 | startX = endX; 52 | break; 53 | } 54 | if(eo->e < startX) 55 | { 56 | continue; 57 | } 58 | if(eo->s <= startX && eo->e >= endX) 59 | { 60 | // completely covered 61 | startX = endX; 62 | break; 63 | } 64 | if(eo->s < startX) 65 | { 66 | // occlusion starts before us, roll start point 67 | startX = std::min(endX, eo->e); 68 | } 69 | else if(eo->s > startX) 70 | { 71 | // show and occlude left part 72 | visibleSpans.push_back(Frame::Span(startX, eo->s)); 73 | if(isSolid) 74 | { 75 | eo->s = startX; 76 | } 77 | startX = std::min(endX, eo->e); 78 | } 79 | if(eo->e >= endX) 80 | { 81 | // completely covered 82 | startX = endX; 83 | break; 84 | } 85 | else if(eo->e < endX) 86 | { 87 | startX = eo->e; 88 | } 89 | } while(++eo != m_occlusion.end() && startX != endX); 90 | 91 | if(endX > startX) 92 | { 93 | visibleSpans.push_back(Frame::Span(startX, endX)); 94 | if(isSolid) 95 | { 96 | m_occlusion.push_back(Frame::Span(startX, endX)); 97 | } 98 | } 99 | 100 | // merge neighbouring segments 101 | if(isSolid && m_occlusion.size()) 102 | { 103 | eo = m_occlusion.begin(); 104 | do 105 | { 106 | auto pe = eo++; 107 | if(eo != m_occlusion.end() && pe->e == eo->s) 108 | { 109 | eo->s = pe->s; 110 | m_occlusion.erase(pe); 111 | } 112 | } while(eo != m_occlusion.end()); 113 | } 114 | } 115 | 116 | return visibleSpans; 117 | } 118 | 119 | bool Frame::IsOccluded() const 120 | { 121 | return (m_occlusion.size() == 1 && m_occlusion.begin()->s == 0 && m_occlusion.begin()->e == m_width) || 122 | m_numVerticallyOccluded >= m_width; 123 | } 124 | 125 | bool Frame::IsVerticallyOccluded(int x) const 126 | { 127 | return m_floorClip[x] <= m_ceilClip[x]; 128 | } 129 | 130 | // add a vertical span into planes list 131 | void Frame::MergeIntoPlane(deque& planes, float height, const std::string& textureName, float lightLevel, int x, int sy, int ey) 132 | { 133 | if(IsSpanVisible(x, sy, ey)) 134 | { 135 | auto plane = std::find_if(planes.begin(), planes.end(), [&](const Plane& plane) { 136 | return (plane.h == height || (!isfinite(plane.h) && !isfinite(height))) && plane.lightLevel == lightLevel && 137 | plane.textureName == textureName; 138 | }); 139 | if(plane == planes.end()) 140 | { 141 | planes.push_front(Plane(height, textureName, lightLevel, m_height)); 142 | plane = planes.begin(); 143 | } 144 | plane->addSpan(x, std::max(sy, 0), std::min(ey, m_height - 1)); 145 | } 146 | } 147 | 148 | // for a vertical mapSegment, determine which section is visible and update occlusion map 149 | Frame::Span Frame::ClipVerticalSegment(int x, 150 | int ceilingProjection, 151 | int floorProjection, 152 | bool isSolid, 153 | const float* ceilingHeight, 154 | const float* floorHeight, 155 | const std::string& ceilingTexture, 156 | const std::string& floorTexture, 157 | float lightLevel) 158 | { 159 | Frame::Span span; 160 | 161 | if(IsVerticallyOccluded(x)) 162 | { 163 | return span; 164 | } 165 | 166 | if(ceilingProjection > m_ceilClip[x]) 167 | { 168 | span.s = std::min(ceilingProjection, m_floorClip[x]); 169 | if(ceilingHeight) 170 | { 171 | MergeIntoPlane(m_ceilingPlanes, *ceilingHeight, ceilingTexture, lightLevel, x, m_ceilClip[x], span.s); 172 | } 173 | } 174 | else 175 | { 176 | span.s = m_ceilClip[x]; 177 | } 178 | if(floorProjection < m_floorClip[x]) 179 | { 180 | span.e = std::max(floorProjection, m_ceilClip[x]); 181 | if(floorHeight) 182 | { 183 | MergeIntoPlane(m_floorPlanes, *floorHeight, floorTexture, lightLevel, x, span.e, m_floorClip[x]); 184 | } 185 | } 186 | else 187 | { 188 | span.e = m_floorClip[x]; 189 | } 190 | 191 | if(isSolid) 192 | { 193 | m_floorClip[x] = m_ceilClip[x] = 0; 194 | } 195 | else 196 | { 197 | if(ceilingProjection > m_ceilClip[x]) 198 | { 199 | m_ceilClip[x] = ceilingProjection; 200 | } 201 | if(floorProjection < m_floorClip[x]) 202 | { 203 | m_floorClip[x] = floorProjection; 204 | } 205 | } 206 | 207 | if(IsVerticallyOccluded(x)) 208 | { 209 | m_numVerticallyOccluded++; 210 | } 211 | 212 | return span; 213 | } 214 | 215 | Frame::~Frame() {} 216 | 217 | bool Frame::IsSpanVisible(int x, int sy, int ey) const 218 | { 219 | return !(sy < 0 && ey < 0) && !(sy >= m_height && ey >= m_height) && x >= 0 && x < m_width; 220 | } 221 | 222 | // extend plane to include a vertical span 223 | void Frame::Plane::addSpan(int x, int sy, int ey) 224 | { 225 | for(auto y = sy; y <= ey; y++) 226 | { 227 | auto& span = spans[y]; 228 | if(span.empty()) 229 | { 230 | span.push_back(Span(x, x)); 231 | } 232 | else 233 | { 234 | // if last (most recently added) span can be extended, extend it 235 | auto& lastSpan = span[span.size() - 1]; 236 | if(lastSpan.e == x - 1) 237 | { 238 | lastSpan.e = x; 239 | } 240 | else 241 | { 242 | // create new span at the back so that's its found first next time 243 | span.push_back(Span(x, x)); 244 | } 245 | } 246 | } 247 | } 248 | } // namespace rtdoom 249 | -------------------------------------------------------------------------------- /rtdoom/main.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include 4 | #include 5 | #include "glad/glad.h" 6 | 7 | #include "GameLoop.h" 8 | 9 | #define ENABLE_GL 1 10 | 11 | using namespace rtdoom; 12 | using namespace std::string_literals; 13 | using std::cout, std::endl, std::string; 14 | 15 | void InitSDL(SDL_Renderer*& sdlRenderer, SDL_Window*& sdlWindow); 16 | void DestroySDL(SDL_Renderer* sdlRenderer, SDL_Window* sdlWindow); 17 | std::optional FindWAD(); 18 | 19 | #include "WADFile.h" 20 | 21 | int main(int /*argc*/, char** /*argv*/) 22 | { 23 | try 24 | { 25 | auto wadFileName = FindWAD(); 26 | if(!wadFileName.has_value()) 27 | { 28 | throw std::runtime_error("No WAD file found! Drop off a .wad from Doom or Freedoom to .exe directory."); 29 | } 30 | WADFile wadFile(wadFileName.value()); 31 | auto mapIter = wadFile.m_maps.begin(); 32 | 33 | SDL_Renderer* sdlRenderer; 34 | SDL_Window* sdlWindow; 35 | InitSDL(sdlRenderer, sdlWindow); 36 | 37 | GameLoop gameLoop {sdlRenderer, sdlWindow, wadFile}; 38 | gameLoop.Start(mapIter->second); 39 | 40 | SDL_Event event; 41 | const static auto tickFrequency = SDL_GetPerformanceFrequency(); 42 | auto tickCounter = SDL_GetPerformanceCounter(); 43 | 44 | while(gameLoop.isRunning()) 45 | { 46 | while(SDL_PollEvent(&event)) 47 | { 48 | if((SDL_QUIT == event.type) || (SDL_KEYDOWN == event.type && SDL_SCANCODE_ESCAPE == event.key.keysym.scancode)) 49 | { 50 | gameLoop.Stop(); 51 | break; 52 | } 53 | 54 | if(event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) 55 | { 56 | gameLoop.ResizeWindow(event.window.data1, event.window.data2); 57 | } 58 | 59 | if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) && event.key.repeat == 0) 60 | { 61 | auto k = event.key; 62 | auto p = event.type == SDL_KEYDOWN; 63 | switch(k.keysym.sym) 64 | { 65 | case SDLK_UP: 66 | gameLoop.Move(p ? 1 : 0); 67 | break; 68 | case SDLK_DOWN: 69 | gameLoop.Move(p ? -1 : 0); 70 | break; 71 | case SDLK_LEFT: 72 | gameLoop.Rotate(p ? -1 : 0); 73 | break; 74 | case SDLK_RIGHT: 75 | gameLoop.Rotate(p ? 1 : 0); 76 | break; 77 | case SDLK_1: 78 | gameLoop.SetRenderingMode(Renderer::RenderingMode::Wireframe); 79 | SDL_SetWindowTitle(sdlWindow, "rtdoom (Wireframe)"); 80 | break; 81 | case SDLK_2: 82 | gameLoop.SetRenderingMode(Renderer::RenderingMode::Solid); 83 | SDL_SetWindowTitle(sdlWindow, "rtdoom (Solid)"); 84 | break; 85 | case SDLK_3: 86 | gameLoop.SetRenderingMode(Renderer::RenderingMode::Textured); 87 | SDL_SetWindowTitle(sdlWindow, "rtdoom (Textured)"); 88 | break; 89 | #if(ENABLE_GL) 90 | case SDLK_4: 91 | gameLoop.SetRenderingMode(Renderer::RenderingMode::OpenGL); 92 | SDL_SetWindowTitle(sdlWindow, "rtdoom (OpenGL)"); 93 | break; 94 | #else 95 | case SDLK_4: 96 | std::cout << "OpenGL not enabled, recompile with ENABLE_GL = 1" << std::endl; 97 | break; 98 | #endif 99 | case SDLK_s: 100 | if(p) 101 | { 102 | gameLoop.StepFrame(); 103 | } 104 | break; 105 | case SDLK_m: 106 | // next map 107 | if(p) 108 | { 109 | mapIter++; 110 | if(mapIter == wadFile.m_maps.end()) 111 | { 112 | mapIter = wadFile.m_maps.begin(); 113 | } 114 | gameLoop.Start(mapIter->second); 115 | } 116 | break; 117 | case SDLK_d: 118 | cout << "Player position: (" << gameLoop.Player().x << ", " << gameLoop.Player().y << ", " << gameLoop.Player().a 119 | << ")" << endl; 120 | break; 121 | default: 122 | break; 123 | } 124 | } 125 | } 126 | 127 | const Frame* frame = gameLoop.RenderFrame(); 128 | 129 | const auto nextCounter = SDL_GetPerformanceCounter(); 130 | const auto seconds = (nextCounter - tickCounter) / static_cast(tickFrequency); 131 | tickCounter = nextCounter; 132 | gameLoop.Tick(seconds); 133 | 134 | if(frame != NULL) 135 | { 136 | cout << "Frame time: " << seconds * 1000.0 << "ms: " << frame->m_numSegments << " segs, " << frame->m_numFloorPlanes << "+" 137 | << frame->m_numCeilingPlanes << " planes, " << frame->m_sprites.size() << " sprites" << endl; 138 | } 139 | else 140 | { 141 | cout << "Frame time: " << seconds * 1000.0 << "ms" << endl; 142 | } 143 | } 144 | 145 | DestroySDL(sdlRenderer, sdlWindow); 146 | } 147 | catch(std::exception& ex) 148 | { 149 | cout << "Exception: " << ex.what() << endl; 150 | return EXIT_FAILURE; 151 | } 152 | return EXIT_SUCCESS; 153 | } 154 | 155 | void InitSDL(SDL_Renderer*& sdlRenderer, SDL_Window*& sdlWindow) 156 | { 157 | if(SDL_Init(SDL_INIT_EVERYTHING)) 158 | { 159 | throw std::runtime_error("Unable to initialize SDL: " + std::string(SDL_GetError())); 160 | } 161 | 162 | atexit(SDL_Quit); 163 | 164 | #if(ENABLE_GL) 165 | SDL_GL_LoadLibrary(NULL); 166 | #endif 167 | 168 | if constexpr(s_multisamplingLevel > 1.0f) 169 | { 170 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); 171 | } 172 | 173 | auto flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE; 174 | 175 | #if(ENABLE_GL) 176 | flags |= SDL_WINDOW_OPENGL; 177 | #endif 178 | 179 | sdlWindow = SDL_CreateWindow("rtdoom", 180 | SDL_WINDOWPOS_UNDEFINED, 181 | SDL_WINDOWPOS_UNDEFINED, 182 | s_displayX, 183 | s_displayY, 184 | flags); 185 | 186 | if(!sdlWindow) 187 | { 188 | throw std::runtime_error("Unable to create SDL window: " + std::string(SDL_GetError())); 189 | } 190 | 191 | sdlRenderer = SDL_CreateRenderer(sdlWindow, -1, SDL_RENDERER_ACCELERATED); 192 | 193 | if(!sdlRenderer) 194 | { 195 | throw std::runtime_error("Unable to create SDL renderer: " + std::string(SDL_GetError())); 196 | } 197 | } 198 | 199 | void DestroySDL(SDL_Renderer* sdlRenderer, SDL_Window* sdlWindow) 200 | { 201 | SDL_DestroyRenderer(sdlRenderer); 202 | SDL_DestroyWindow(sdlWindow); 203 | SDL_Quit(); 204 | } 205 | 206 | bool iequals(const string& a, const string& b) 207 | { 208 | return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); }); 209 | } 210 | 211 | std::optional FindWAD() 212 | { 213 | for(const auto& entry : std::filesystem::directory_iterator(".")) 214 | { 215 | if(entry.is_regular_file() && iequals(entry.path().extension().string(), ".wad")) 216 | { 217 | return entry.path().generic_string(); 218 | } 219 | } 220 | return std::nullopt; 221 | } 222 | -------------------------------------------------------------------------------- /rtdoom/GLContext.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "GLContext.h" 3 | 4 | #pragma warning(disable : 4201) 5 | 6 | #include "glad/glad.h" 7 | #include "glm/gtc/type_ptr.hpp" 8 | 9 | namespace rtdoom 10 | { 11 | 12 | GLContext::GLContext(const WADFile& wadFile) : 13 | m_wadFile {wadFile}, m_EBO {}, m_VBO {}, m_VAO {}, m_fragmentShader {}, m_shaderProgram {}, m_vertexShader {}, m_vertexCounter {}, 14 | m_maxTextureUnits {} 15 | { } 16 | 17 | void GLContext::Reset() 18 | { 19 | m_vertexCounter = 0; 20 | m_vertices.clear(); 21 | m_indices.clear(); 22 | m_textures.clear(); 23 | m_textureUnits.clear(); 24 | m_subSectorOffsets.clear(); 25 | } 26 | 27 | void GLContext::AddVertex(float x, float y, float z, float tx, float ty, float tn, float tz, float l) 28 | { 29 | m_vertices.emplace_back(VertexInfo {x, y, z, tx, ty, tz, tn, l}); 30 | } 31 | 32 | void GLContext::LinkTriangle(int a, int b, int c) 33 | { 34 | m_indices.push_back(a); 35 | m_indices.push_back(b); 36 | m_indices.push_back(c); 37 | } 38 | 39 | void GLContext::AddWallSegment( 40 | const Vertex& v0, float z0, const Vertex& v1, float z1, float tx0, float ty0, float tx1, float ty1, int tn, int ti, float l) 41 | { 42 | AddVertex(v0.x, v0.y, z0, tx0, ty0, static_cast(tn), static_cast(ti), l); 43 | AddVertex(v1.x, v1.y, z0, tx1, ty0, static_cast(tn), static_cast(ti), l); 44 | AddVertex(v1.x, v1.y, z1, tx1, ty1, static_cast(tn), static_cast(ti), l); 45 | AddVertex(v0.x, v0.y, z1, tx0, ty1, static_cast(tn), static_cast(ti), l); 46 | LinkTriangle(m_vertexCounter, m_vertexCounter + 1, m_vertexCounter + 2); 47 | LinkTriangle(m_vertexCounter, m_vertexCounter + 2, m_vertexCounter + 3); 48 | m_vertexCounter += 4; 49 | } 50 | 51 | void GLContext::AddFloorCeiling(const Vertex& v0, const Vertex& v1, const Vertex& v2, float z, float tw, float th, int tn, int ti, float l) 52 | { 53 | AddVertex(v0.x, v0.y, z, v0.x / tw, v0.y / th, static_cast(tn), static_cast(ti), l); 54 | AddVertex(v1.x, v1.y, z, v1.x / tw, v1.y / th, static_cast(tn), static_cast(ti), l); 55 | AddVertex(v2.x, v2.y, z, v2.x / tw, v2.y / th, static_cast(tn), static_cast(ti), l); 56 | LinkTriangle(m_vertexCounter, m_vertexCounter + 1, m_vertexCounter + 2); 57 | m_vertexCounter += 3; 58 | } 59 | 60 | void GLContext::LoadTexture(std::shared_ptr wtex, int i) 61 | { 62 | int x, y; 63 | unsigned char* data = new unsigned char[wtex->width * wtex->height * 3]; 64 | for(y = 0; y < wtex->height; y++) 65 | for(x = 0; x < wtex->width; x++) 66 | { 67 | const auto& c = m_wadFile.m_palette.colors[wtex->pixels[x + y * wtex->width]]; 68 | const size_t ofs = 3 * (x + y * wtex->width); 69 | data[ofs] = c.r; 70 | data[ofs + 1] = c.g; 71 | data[ofs + 2] = c.b; 72 | } 73 | 74 | glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, wtex->width, wtex->height, 1, GL_RGB, GL_UNSIGNED_BYTE, data); 75 | delete[] data; 76 | } 77 | 78 | void GLContext::Initialize() 79 | { 80 | glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &m_maxTextureUnits); 81 | if(m_maxTextureUnits > 16) 82 | { 83 | m_maxTextureUnits = 16; 84 | } 85 | } 86 | 87 | void GLContext::LoadTextures() 88 | { 89 | m_textures.resize(m_textureUnits.size()); 90 | glGenTextures(m_textureUnits.size(), m_textures.data()); 91 | for(size_t tu = 0; tu < m_textureUnits.size(); tu++) 92 | { 93 | TextureUnit& unit = m_textureUnits.at(tu); 94 | 95 | glBindTexture(GL_TEXTURE_2D_ARRAY, m_textures[tu]); 96 | glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGB, unit.w, unit.h, unit.textures.size(), 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); 97 | 98 | for(size_t tn = 0; tn < unit.textures.size(); tn++) 99 | { 100 | LoadTexture(unit.textures[tn], tn); 101 | } 102 | 103 | glGenerateMipmap(GL_TEXTURE_2D_ARRAY); 104 | glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT); 105 | glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT); 106 | glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 107 | glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 108 | } 109 | 110 | glUseProgram(m_shaderProgram); 111 | 112 | std::vector textureNos; 113 | textureNos.resize(m_textureUnits.size()); 114 | for(size_t i = 0; i < textureNos.size(); i++) 115 | { 116 | textureNos[i] = i; 117 | } 118 | glUniform1iv(glGetUniformLocation(m_shaderProgram, "_textures"), m_textureUnits.size(), textureNos.data()); 119 | } 120 | 121 | void GLContext::BindView(glm::f32* viewMat, glm::f32* projectionMat) 122 | { 123 | unsigned int viewLoc = glGetUniformLocation(m_shaderProgram, "_view"); 124 | glUniformMatrix4fv(viewLoc, 1, GL_FALSE, viewMat); 125 | 126 | unsigned int projectionLoc = glGetUniformLocation(m_shaderProgram, "_projection"); 127 | glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, projectionMat); 128 | 129 | for(size_t tu = 0; tu < m_textureUnits.size(); tu++) 130 | { 131 | glActiveTexture(GL_TEXTURE0 + tu); 132 | glBindTexture(GL_TEXTURE_2D_ARRAY, m_textures[tu]); 133 | } 134 | 135 | glUseProgram(m_shaderProgram); 136 | glBindVertexArray(m_VAO); 137 | } 138 | 139 | std::shared_ptr GLContext::AllocateTexture(std::string name, int& textureUnit, int& textureNo) 140 | { 141 | // decide which texture unit and number the texture will go to, but create them later? 142 | if(name == "-") 143 | { 144 | textureUnit = 0; 145 | textureNo = 0; 146 | return NULL; 147 | } 148 | auto texture = m_wadFile.m_textures.find(name)->second; 149 | size_t tui; 150 | for(tui = 0; tui < m_textureUnits.size(); tui++) 151 | { 152 | if(m_textureUnits[tui].w == texture->width && m_textureUnits[tui].h == texture->height) 153 | { 154 | textureUnit = tui; 155 | for(size_t tn = 0; tn < m_textureUnits[tui].textures.size(); tn++) 156 | { 157 | if(m_textureUnits[tui].textures[tn]->name == name) 158 | { 159 | textureNo = tn; 160 | return texture; 161 | } 162 | } 163 | textureNo = m_textureUnits[tui].textures.size(); 164 | m_textureUnits[tui].textures.push_back(texture); 165 | return texture; 166 | } 167 | } 168 | 169 | // create another TU and retry 170 | if(m_textureUnits.size() >= static_cast(m_maxTextureUnits)) 171 | { 172 | // TODO: create "missing texture" image at (0,0) 173 | textureUnit = 0; 174 | textureNo = 0; 175 | return texture; 176 | } 177 | 178 | TextureUnit tu; 179 | tu.w = texture->width; 180 | tu.h = texture->height; 181 | tu.n = m_textureUnits.size(); 182 | m_textureUnits.push_back(tu); 183 | return AllocateTexture(name, textureUnit, textureNo); 184 | } 185 | 186 | void GLContext::CompileShaders(const char* vertexShaderSource, const char* fragmentShaderSource) 187 | { 188 | m_vertexShader = glCreateShader(GL_VERTEX_SHADER); 189 | glShaderSource(m_vertexShader, 1, &vertexShaderSource, NULL); 190 | glCompileShader(m_vertexShader); 191 | int success; 192 | char infoLog[512]; 193 | glGetShaderiv(m_vertexShader, GL_COMPILE_STATUS, &success); 194 | if(!success) 195 | { 196 | glGetShaderInfoLog(m_vertexShader, 512, NULL, infoLog); 197 | std::cout << infoLog << std::endl; 198 | throw std::runtime_error("Vertex shader compilation failed."); 199 | } 200 | 201 | m_fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); 202 | glShaderSource(m_fragmentShader, 1, &fragmentShaderSource, NULL); 203 | glCompileShader(m_fragmentShader); 204 | glGetShaderiv(m_fragmentShader, GL_COMPILE_STATUS, &success); 205 | if(!success) 206 | { 207 | glGetShaderInfoLog(m_fragmentShader, 512, NULL, infoLog); 208 | std::cout << infoLog << std::endl; 209 | throw std::runtime_error("Fragment shader compilation failed."); 210 | } 211 | 212 | m_shaderProgram = glCreateProgram(); 213 | glAttachShader(m_shaderProgram, m_vertexShader); 214 | glAttachShader(m_shaderProgram, m_fragmentShader); 215 | glLinkProgram(m_shaderProgram); 216 | glGetProgramiv(m_shaderProgram, GL_LINK_STATUS, &success); 217 | if(!success) 218 | { 219 | glGetProgramInfoLog(m_shaderProgram, 512, NULL, infoLog); 220 | std::cout << infoLog << std::endl; 221 | throw std::runtime_error("Shader linking failed."); 222 | } 223 | glDeleteShader(m_vertexShader); 224 | glDeleteShader(m_fragmentShader); 225 | } 226 | 227 | GLContext::~GLContext() 228 | { 229 | if(m_VAO) 230 | { 231 | glDeleteVertexArrays(1, &m_VAO); 232 | glDeleteBuffers(1, &m_VBO); 233 | glDeleteBuffers(1, &m_EBO); 234 | glDeleteProgram(m_shaderProgram); 235 | } 236 | } 237 | 238 | void GLContext::BindMap() 239 | { 240 | glGenVertexArrays(1, &m_VAO); 241 | glGenBuffers(1, &m_VBO); 242 | glGenBuffers(1, &m_EBO); 243 | glBindVertexArray(m_VAO); 244 | glBindBuffer(GL_ARRAY_BUFFER, m_VBO); 245 | glBufferData(GL_ARRAY_BUFFER, sizeof(VertexInfo) * m_vertices.size(), m_vertices.data(), GL_STATIC_DRAW); 246 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_EBO); 247 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * m_indices.size(), m_indices.data(), GL_STATIC_DRAW); 248 | glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); 249 | glEnableVertexAttribArray(0); 250 | glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); 251 | glEnableVertexAttribArray(1); 252 | glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); 253 | glEnableVertexAttribArray(2); 254 | glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(7 * sizeof(float))); 255 | glEnableVertexAttribArray(3); 256 | glBindBuffer(GL_ARRAY_BUFFER, 0); 257 | glBindVertexArray(0); 258 | 259 | LoadTextures(); 260 | } 261 | } // namespace rtdoom 262 | -------------------------------------------------------------------------------- /rtdoom/glad/KHR/khrplatform.h: -------------------------------------------------------------------------------- 1 | #ifndef __khrplatform_h_ 2 | #define __khrplatform_h_ 3 | 4 | /* 5 | ** Copyright (c) 2008-2018 The Khronos Group Inc. 6 | ** 7 | ** Permission is hereby granted, free of charge, to any person obtaining a 8 | ** copy of this software and/or associated documentation files (the 9 | ** "Materials"), to deal in the Materials without restriction, including 10 | ** without limitation the rights to use, copy, modify, merge, publish, 11 | ** distribute, sublicense, and/or sell copies of the Materials, and to 12 | ** permit persons to whom the Materials are furnished to do so, subject to 13 | ** the following conditions: 14 | ** 15 | ** The above copyright notice and this permission notice shall be included 16 | ** in all copies or substantial portions of the Materials. 17 | ** 18 | ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 25 | */ 26 | 27 | /* Khronos platform-specific types and definitions. 28 | * 29 | * The master copy of khrplatform.h is maintained in the Khronos EGL 30 | * Registry repository at https://github.com/KhronosGroup/EGL-Registry 31 | * The last semantic modification to khrplatform.h was at commit ID: 32 | * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 33 | * 34 | * Adopters may modify this file to suit their platform. Adopters are 35 | * encouraged to submit platform specific modifications to the Khronos 36 | * group so that they can be included in future versions of this file. 37 | * Please submit changes by filing pull requests or issues on 38 | * the EGL Registry repository linked above. 39 | * 40 | * 41 | * See the Implementer's Guidelines for information about where this file 42 | * should be located on your system and for more details of its use: 43 | * http://www.khronos.org/registry/implementers_guide.pdf 44 | * 45 | * This file should be included as 46 | * #include 47 | * by Khronos client API header files that use its types and defines. 48 | * 49 | * The types in khrplatform.h should only be used to define API-specific types. 50 | * 51 | * Types defined in khrplatform.h: 52 | * khronos_int8_t signed 8 bit 53 | * khronos_uint8_t unsigned 8 bit 54 | * khronos_int16_t signed 16 bit 55 | * khronos_uint16_t unsigned 16 bit 56 | * khronos_int32_t signed 32 bit 57 | * khronos_uint32_t unsigned 32 bit 58 | * khronos_int64_t signed 64 bit 59 | * khronos_uint64_t unsigned 64 bit 60 | * khronos_intptr_t signed same number of bits as a pointer 61 | * khronos_uintptr_t unsigned same number of bits as a pointer 62 | * khronos_ssize_t signed size 63 | * khronos_usize_t unsigned size 64 | * khronos_float_t signed 32 bit floating point 65 | * khronos_time_ns_t unsigned 64 bit time in nanoseconds 66 | * khronos_utime_nanoseconds_t unsigned time interval or absolute time in 67 | * nanoseconds 68 | * khronos_stime_nanoseconds_t signed time interval in nanoseconds 69 | * khronos_boolean_enum_t enumerated boolean type. This should 70 | * only be used as a base type when a client API's boolean type is 71 | * an enum. Client APIs which use an integer or other type for 72 | * booleans cannot use this as the base type for their boolean. 73 | * 74 | * Tokens defined in khrplatform.h: 75 | * 76 | * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. 77 | * 78 | * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. 79 | * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. 80 | * 81 | * Calling convention macros defined in this file: 82 | * KHRONOS_APICALL 83 | * KHRONOS_APIENTRY 84 | * KHRONOS_APIATTRIBUTES 85 | * 86 | * These may be used in function prototypes as: 87 | * 88 | * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( 89 | * int arg1, 90 | * int arg2) KHRONOS_APIATTRIBUTES; 91 | */ 92 | 93 | #if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) 94 | # define KHRONOS_STATIC 1 95 | #endif 96 | 97 | /*------------------------------------------------------------------------- 98 | * Definition of KHRONOS_APICALL 99 | *------------------------------------------------------------------------- 100 | * This precedes the return type of the function in the function prototype. 101 | */ 102 | #if defined(KHRONOS_STATIC) 103 | /* If the preprocessor constant KHRONOS_STATIC is defined, make the 104 | * header compatible with static linking. */ 105 | # define KHRONOS_APICALL 106 | #elif defined(_WIN32) 107 | # define KHRONOS_APICALL __declspec(dllimport) 108 | #elif defined (__SYMBIAN32__) 109 | # define KHRONOS_APICALL IMPORT_C 110 | #elif defined(__ANDROID__) 111 | # define KHRONOS_APICALL __attribute__((visibility("default"))) 112 | #else 113 | # define KHRONOS_APICALL 114 | #endif 115 | 116 | /*------------------------------------------------------------------------- 117 | * Definition of KHRONOS_APIENTRY 118 | *------------------------------------------------------------------------- 119 | * This follows the return type of the function and precedes the function 120 | * name in the function prototype. 121 | */ 122 | #if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) 123 | /* Win32 but not WinCE */ 124 | # define KHRONOS_APIENTRY __stdcall 125 | #else 126 | # define KHRONOS_APIENTRY 127 | #endif 128 | 129 | /*------------------------------------------------------------------------- 130 | * Definition of KHRONOS_APIATTRIBUTES 131 | *------------------------------------------------------------------------- 132 | * This follows the closing parenthesis of the function prototype arguments. 133 | */ 134 | #if defined (__ARMCC_2__) 135 | #define KHRONOS_APIATTRIBUTES __softfp 136 | #else 137 | #define KHRONOS_APIATTRIBUTES 138 | #endif 139 | 140 | /*------------------------------------------------------------------------- 141 | * basic type definitions 142 | *-----------------------------------------------------------------------*/ 143 | #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) 144 | 145 | 146 | /* 147 | * Using 148 | */ 149 | #include 150 | typedef int32_t khronos_int32_t; 151 | typedef uint32_t khronos_uint32_t; 152 | typedef int64_t khronos_int64_t; 153 | typedef uint64_t khronos_uint64_t; 154 | #define KHRONOS_SUPPORT_INT64 1 155 | #define KHRONOS_SUPPORT_FLOAT 1 156 | 157 | #elif defined(__VMS ) || defined(__sgi) 158 | 159 | /* 160 | * Using 161 | */ 162 | #include 163 | typedef int32_t khronos_int32_t; 164 | typedef uint32_t khronos_uint32_t; 165 | typedef int64_t khronos_int64_t; 166 | typedef uint64_t khronos_uint64_t; 167 | #define KHRONOS_SUPPORT_INT64 1 168 | #define KHRONOS_SUPPORT_FLOAT 1 169 | 170 | #elif defined(_WIN32) && !defined(__SCITECH_SNAP__) 171 | 172 | /* 173 | * Win32 174 | */ 175 | typedef __int32 khronos_int32_t; 176 | typedef unsigned __int32 khronos_uint32_t; 177 | typedef __int64 khronos_int64_t; 178 | typedef unsigned __int64 khronos_uint64_t; 179 | #define KHRONOS_SUPPORT_INT64 1 180 | #define KHRONOS_SUPPORT_FLOAT 1 181 | 182 | #elif defined(__sun__) || defined(__digital__) 183 | 184 | /* 185 | * Sun or Digital 186 | */ 187 | typedef int khronos_int32_t; 188 | typedef unsigned int khronos_uint32_t; 189 | #if defined(__arch64__) || defined(_LP64) 190 | typedef long int khronos_int64_t; 191 | typedef unsigned long int khronos_uint64_t; 192 | #else 193 | typedef long long int khronos_int64_t; 194 | typedef unsigned long long int khronos_uint64_t; 195 | #endif /* __arch64__ */ 196 | #define KHRONOS_SUPPORT_INT64 1 197 | #define KHRONOS_SUPPORT_FLOAT 1 198 | 199 | #elif 0 200 | 201 | /* 202 | * Hypothetical platform with no float or int64 support 203 | */ 204 | typedef int khronos_int32_t; 205 | typedef unsigned int khronos_uint32_t; 206 | #define KHRONOS_SUPPORT_INT64 0 207 | #define KHRONOS_SUPPORT_FLOAT 0 208 | 209 | #else 210 | 211 | /* 212 | * Generic fallback 213 | */ 214 | #include 215 | typedef int32_t khronos_int32_t; 216 | typedef uint32_t khronos_uint32_t; 217 | typedef int64_t khronos_int64_t; 218 | typedef uint64_t khronos_uint64_t; 219 | #define KHRONOS_SUPPORT_INT64 1 220 | #define KHRONOS_SUPPORT_FLOAT 1 221 | 222 | #endif 223 | 224 | 225 | /* 226 | * Types that are (so far) the same on all platforms 227 | */ 228 | typedef signed char khronos_int8_t; 229 | typedef unsigned char khronos_uint8_t; 230 | typedef signed short int khronos_int16_t; 231 | typedef unsigned short int khronos_uint16_t; 232 | 233 | /* 234 | * Types that differ between LLP64 and LP64 architectures - in LLP64, 235 | * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears 236 | * to be the only LLP64 architecture in current use. 237 | */ 238 | #ifdef _WIN64 239 | typedef signed long long int khronos_intptr_t; 240 | typedef unsigned long long int khronos_uintptr_t; 241 | typedef signed long long int khronos_ssize_t; 242 | typedef unsigned long long int khronos_usize_t; 243 | #else 244 | typedef signed long int khronos_intptr_t; 245 | typedef unsigned long int khronos_uintptr_t; 246 | typedef signed long int khronos_ssize_t; 247 | typedef unsigned long int khronos_usize_t; 248 | #endif 249 | 250 | #if KHRONOS_SUPPORT_FLOAT 251 | /* 252 | * Float type 253 | */ 254 | typedef float khronos_float_t; 255 | #endif 256 | 257 | #if KHRONOS_SUPPORT_INT64 258 | /* Time types 259 | * 260 | * These types can be used to represent a time interval in nanoseconds or 261 | * an absolute Unadjusted System Time. Unadjusted System Time is the number 262 | * of nanoseconds since some arbitrary system event (e.g. since the last 263 | * time the system booted). The Unadjusted System Time is an unsigned 264 | * 64 bit value that wraps back to 0 every 584 years. Time intervals 265 | * may be either signed or unsigned. 266 | */ 267 | typedef khronos_uint64_t khronos_utime_nanoseconds_t; 268 | typedef khronos_int64_t khronos_stime_nanoseconds_t; 269 | #endif 270 | 271 | /* 272 | * Dummy value used to pad enum types to 32 bits. 273 | */ 274 | #ifndef KHRONOS_MAX_ENUM 275 | #define KHRONOS_MAX_ENUM 0x7FFFFFFF 276 | #endif 277 | 278 | /* 279 | * Enumerated boolean type 280 | * 281 | * Values other than zero should be considered to be true. Therefore 282 | * comparisons should not be made against KHRONOS_TRUE. 283 | */ 284 | typedef enum { 285 | KHRONOS_FALSE = 0, 286 | KHRONOS_TRUE = 1, 287 | KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM 288 | } khronos_boolean_enum_t; 289 | 290 | #endif /* __khrplatform_h_ */ 291 | -------------------------------------------------------------------------------- /rtdoom/rtdoom.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {D27DCA69-01EE-4B18-BB15-8A829B41F088} 24 | Win32Proj 25 | rtdoom 26 | 10.0 27 | rtdoom 28 | 29 | 30 | 31 | Application 32 | true 33 | v142 34 | Unicode 35 | 36 | 37 | Application 38 | false 39 | v142 40 | true 41 | Unicode 42 | 43 | 44 | Application 45 | true 46 | v142 47 | Unicode 48 | 49 | 50 | Application 51 | false 52 | v142 53 | true 54 | Unicode 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | true 79 | 80 | 81 | false 82 | 83 | 84 | false 85 | 86 | 87 | 88 | Use 89 | Level4 90 | Disabled 91 | true 92 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 93 | true 94 | pch.h 95 | false 96 | stdcpp17 97 | Default 98 | 99 | 100 | Console 101 | DebugFull 102 | 103 | 104 | 105 | 106 | Use 107 | Level4 108 | Disabled 109 | true 110 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 111 | true 112 | pch.h 113 | true 114 | stdcpp17 115 | Default 116 | 117 | 118 | Console 119 | DebugFull 120 | 121 | 122 | 123 | 124 | Use 125 | Level4 126 | MaxSpeed 127 | true 128 | true 129 | true 130 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 131 | true 132 | pch.h 133 | false 134 | stdcpp17 135 | 136 | 137 | Console 138 | true 139 | true 140 | true 141 | 142 | 143 | 144 | 145 | Use 146 | Level4 147 | MaxSpeed 148 | true 149 | true 150 | true 151 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 152 | true 153 | pch.h 154 | true 155 | stdcpp17 156 | 157 | 158 | Console 159 | true 160 | true 161 | true 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | Create 210 | Create 211 | Create 212 | Create 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 237 | 238 | 239 | 240 | 241 | 242 | -------------------------------------------------------------------------------- /rtdoom/MapDef.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "rtdoom.h" 3 | #include "MapDef.h" 4 | #include "WADFile.h" 5 | 6 | using std::vector; 7 | using std::deque; 8 | using std::string; 9 | using std::shared_ptr; 10 | 11 | namespace rtdoom 12 | { 13 | MapDef::MapDef(const MapStore& mapStore) : m_store {mapStore} 14 | { 15 | Initialize(); 16 | } 17 | 18 | MapDef::MapDef(const string& mapFolder) 19 | { 20 | m_store.Load(mapFolder); 21 | Initialize(); 22 | } 23 | 24 | void MapDef::Initialize() 25 | { 26 | OpenDoors(); 27 | BuildWireframe(); 28 | BuildSegments(); 29 | BuildSectors(); 30 | BuildSubSectors(); 31 | BuildThings(); 32 | } 33 | 34 | void MapDef::OpenDoors() 35 | { 36 | // open all doors on the map 37 | std::set doorSectors; 38 | for(const auto& lineDef : m_store.m_lineDefs) 39 | { 40 | if((lineDef.lineType == 1 || lineDef.lineType == 31) && lineDef.leftSideDef < 32000) // TODO: all door linedefs 41 | { 42 | const auto& backSide = m_store.m_sideDefs[lineDef.leftSideDef]; 43 | doorSectors.insert(backSide.sector); 44 | } 45 | } 46 | for(auto doorSector : doorSectors) 47 | { 48 | m_store.m_sectors[doorSector].ceilingHeight = 49 | m_store.m_sectors[doorSector].floorHeight + 72; // TODO: 4 less than the lowest neighbouring sector 50 | } 51 | } 52 | 53 | // traverse the BSP tree stored with the map depth-first 54 | deque> MapDef::GetSegmentsToDraw(const Point& pov) const 55 | { 56 | deque> segments; 57 | const auto& subSectors = GetSubSectorsToDraw(pov); 58 | for(auto& subSector : subSectors) 59 | { 60 | for(auto& segment : subSector->segments) 61 | { 62 | segments.push_back(segment); 63 | } 64 | } 65 | return segments; 66 | } 67 | 68 | deque> MapDef::GetSubSectorsToDraw(const Point& pov) const 69 | { 70 | deque> subSectors; 71 | ProcessNode(pov, *m_store.m_nodes.rbegin(), subSectors); 72 | return subSectors; 73 | } 74 | 75 | // find the sector where this point is located 76 | std::optional MapDef::GetSector(const Point& pov) const 77 | { 78 | auto treeIndex = m_store.m_nodes.size() - 1; 79 | while(true) 80 | { 81 | const auto& treeNode = m_store.m_nodes[treeIndex]; 82 | unsigned short childRef = IsInFrontOf(pov, treeNode) ? treeNode.rightChild : treeNode.leftChild; 83 | if(childRef & 0x8000) 84 | { 85 | const SubSector& subSector = *m_subSectors.at(childRef & 0x7fff); 86 | // for(auto i = 0; i < m_store.m_subSectors[childRef & 0x7fff].numSegments; i++) 87 | for(auto segment : subSector.segments) 88 | { 89 | // const auto& ld = m_store.m_lineDefs[m_store.m_segments[m_store.m_subSectors[childRef & 0x7fff].firstSegment + i].lineDef]; 90 | // const bool isInFront = IsInFrontOf(pov, m_store.m_vertexes[ld.startVertex], m_store.m_vertexes[ld.endVertex]); 91 | const bool isInFront = IsInFrontOf(pov, segment->s, segment->e); 92 | 93 | // if(ld.leftSideDef < 32000 && !isInFront) 94 | if(!segment->frontSide.sideless && !isInFront) 95 | { 96 | const auto sectorId = subSector.sectorId;// m_store.m_sideDefs[ld.leftSideDef].sector; 97 | return m_sectors[sectorId]; 98 | } 99 | // else if(ld.rightSideDef < 32000 && isInFront) 100 | else if(!segment->backSide.sideless && isInFront) 101 | { 102 | const auto sectorId = subSector.sectorId;// m_store.m_sideDefs[ld.rightSideDef].sector; 103 | return m_sectors[sectorId]; 104 | } 105 | } 106 | return std::nullopt; 107 | } 108 | else 109 | { 110 | treeIndex = childRef; 111 | } 112 | } 113 | } 114 | 115 | // process tree node and go left or right depending on the locationo of player vs the BSP division line 116 | void MapDef::ProcessNode(const Point& pov, const MapStore::Node& node, deque>& subSectors) const 117 | { 118 | if(IsInFrontOf(pov, node)) 119 | { 120 | ProcessChildRef(node.rightChild, pov, subSectors); 121 | ProcessChildRef(node.leftChild, pov, subSectors); 122 | } 123 | else 124 | { 125 | ProcessChildRef(node.leftChild, pov, subSectors); 126 | ProcessChildRef(node.rightChild, pov, subSectors); 127 | } 128 | } 129 | 130 | // process a child node depending on whether it's an inner node or a leaf (subsector) 131 | void MapDef::ProcessChildRef(unsigned short childRef, const Point& pov, deque>& subSectors) const 132 | { 133 | if(childRef & 0x8000) 134 | { 135 | // ref is a subsector 136 | ProcessSubsector(m_subSectors[childRef & 0x7fff], subSectors); 137 | } 138 | else 139 | { 140 | // ref is another node 141 | ProcessNode(pov, m_store.m_nodes[childRef], subSectors); 142 | } 143 | } 144 | 145 | Thing MapDef::GetStartingPosition() const 146 | { 147 | signed short px, py; 148 | unsigned short pa; 149 | m_store.GetStartingPosition(px, py, pa); 150 | 151 | Thing player(static_cast(px), static_cast(py), 0, static_cast(pa / 180.0f * PI)); 152 | 153 | return player; 154 | } 155 | 156 | bool MapDef::IsInFrontOf(const Point& pov, const Line& line) noexcept 157 | { 158 | const auto deltaX = line.e.x - line.s.x; 159 | const auto deltaY = line.e.y - line.s.y; 160 | 161 | if(deltaX == 0) 162 | { 163 | if(pov.x <= line.s.x) 164 | { 165 | return deltaY < 0; 166 | } 167 | return deltaY > 0; 168 | } 169 | if(deltaY == 0) 170 | { 171 | if(pov.y <= line.s.y) 172 | { 173 | return deltaX > 0; 174 | } 175 | return deltaX < 0; 176 | } 177 | 178 | // use cross-product to determine which side the point is on 179 | const auto dx = pov.x - line.s.x; 180 | const auto dy = pov.y - line.s.y; 181 | const auto left = deltaY * dx; 182 | const auto right = dy * deltaX; 183 | return right < left; 184 | } 185 | 186 | bool MapDef::IsInFrontOf(const Point& pov, const MapStore::Node& node) noexcept 187 | { 188 | return IsInFrontOf( 189 | pov, Line(Vertex {node.partitionX, node.partitionY}, Vertex {node.partitionX + node.deltaX, node.partitionY + node.deltaY})); 190 | } 191 | 192 | bool MapDef::IsInFrontOf(const Point& pov, const Vertex& sv, const Vertex& ev) noexcept 193 | { 194 | return IsInFrontOf(pov, Line(sv, ev)); 195 | } 196 | 197 | void MapDef::BuildWireframe() 198 | { 199 | for(const auto& l : m_store.m_lineDefs) 200 | { 201 | const auto& sv = m_store.m_vertexes[l.startVertex]; 202 | const auto& ev = m_store.m_vertexes[l.endVertex]; 203 | m_wireframe.push_back(Line(Vertex(sv.x, sv.y), Vertex(ev.x, ev.y))); 204 | } 205 | } 206 | 207 | void MapDef::ProcessSegment(float sx, float sy, float ex, float ey, unsigned short lineDefNo, signed short direction, signed short offset) 208 | { 209 | const Vertex s {sx, sy}; 210 | const Vertex e {ex, ey}; 211 | 212 | if(lineDefNo != 65535) 213 | { 214 | const auto& lineDef = m_store.m_lineDefs[lineDefNo]; 215 | bool isSolid = lineDef.leftSideDef > 32000 || lineDef.rightSideDef > 32000; 216 | Sector frontSector, backSector; 217 | MapStore::SideDef frontSide, backSide; 218 | if(direction == 1 && lineDef.leftSideDef < 32000) 219 | { 220 | frontSide = m_store.m_sideDefs[lineDef.leftSideDef]; 221 | frontSector = Sector {frontSide.sector, m_store.m_sectors[frontSide.sector]}; 222 | if(lineDef.rightSideDef < 32000) 223 | { 224 | backSide = m_store.m_sideDefs[lineDef.rightSideDef]; 225 | backSector = Sector {backSide.sector, m_store.m_sectors[backSide.sector]}; 226 | } 227 | } 228 | else if(direction == 0 && lineDef.rightSideDef < 32000) 229 | { 230 | frontSide = m_store.m_sideDefs[lineDef.rightSideDef]; 231 | frontSector = Sector {frontSide.sector, m_store.m_sectors[frontSide.sector]}; 232 | if(lineDef.leftSideDef < 32000) 233 | { 234 | backSide = m_store.m_sideDefs[lineDef.leftSideDef]; 235 | backSector = Sector {backSide.sector, m_store.m_sectors[backSide.sector]}; 236 | } 237 | } 238 | 239 | Side front {frontSector, 240 | Helpers::MakeString(frontSide.lowerTexture), 241 | Helpers::MakeString(frontSide.middleTexture), 242 | Helpers::MakeString(frontSide.upperTexture), 243 | frontSide.xOffset, 244 | frontSide.yOffset}; 245 | Side back {backSector, 246 | Helpers::MakeString(backSide.lowerTexture), 247 | Helpers::MakeString(backSide.middleTexture), 248 | Helpers::MakeString(backSide.upperTexture), 249 | frontSide.xOffset, 250 | frontSide.yOffset}; 251 | 252 | m_segments.push_back( 253 | std::make_shared(s, e, isSolid, front, back, offset, (bool)(lineDef.flags & 0x0010), (bool)(lineDef.flags & 0x0008))); 254 | } 255 | else 256 | { 257 | m_segments.push_back(std::make_shared(s, e, false, Side(), Side(), offset, false, false)); 258 | } 259 | } 260 | 261 | void MapDef::LookupVertex(unsigned short vertexNo, float& x, float& y) 262 | { 263 | if(vertexNo & (1 << 15)) 264 | { 265 | const auto& vertex = m_store.m_glVertexes[vertexNo ^ (1 << 15)]; 266 | x = vertex.x; 267 | y = vertex.y; 268 | } 269 | else 270 | { 271 | const auto& vertex = m_store.m_vertexes[vertexNo]; 272 | x = vertex.x; 273 | y = vertex.y; 274 | } 275 | } 276 | 277 | void MapDef::BuildSegments() 278 | { 279 | if(m_store.m_glSegments.size()) 280 | { 281 | m_segments.reserve(m_store.m_glSegments.size()); 282 | for(const auto& mapSegment : m_store.m_glSegments) 283 | { 284 | float sx, sy, ex, ey; 285 | LookupVertex(mapSegment.startVertex, sx, sy); 286 | LookupVertex(mapSegment.endVertex, ex, ey); 287 | ProcessSegment(sx, 288 | sy, 289 | ex, 290 | ey, 291 | mapSegment.lineDef, 292 | mapSegment.side, 293 | 0 /*mapSegment.offset*/); 294 | } 295 | } 296 | else 297 | { 298 | m_segments.reserve(m_store.m_segments.size()); 299 | for(const auto& mapSegment : m_store.m_segments) 300 | { 301 | if(mapSegment.startVertex < m_store.m_vertexes.size() && mapSegment.endVertex < m_store.m_vertexes.size()) 302 | { 303 | const auto& mapStartVertex = m_store.m_vertexes[mapSegment.startVertex]; 304 | const auto& mapEndVertex = m_store.m_vertexes[mapSegment.endVertex]; 305 | ProcessSegment(mapStartVertex.x, 306 | mapStartVertex.y, 307 | mapEndVertex.x, 308 | mapEndVertex.y, 309 | mapSegment.lineDef, 310 | mapSegment.direction, 311 | mapSegment.offset); 312 | } 313 | } 314 | } 315 | } 316 | 317 | // process serialized map format into usable structures 318 | void MapDef::ProcessSubsector(std::shared_ptr subSector, deque>& subSectors) const 319 | { 320 | subSectors.push_back(subSector); 321 | } 322 | 323 | void MapDef::BuildSectors() 324 | { 325 | int s = 0; 326 | for(const auto& sector : m_store.m_sectors) 327 | { 328 | m_sectors.emplace_back(Sector(s++, sector)); 329 | } 330 | } 331 | 332 | bool MapDef::HasGL() const { 333 | return m_store.m_glSegments.size(); 334 | } 335 | 336 | void MapDef::BuildSubSectors() 337 | { 338 | int subSectorId = 0; 339 | for(const auto& subSector : m_store.m_subSectors) 340 | { 341 | int sectorId = -1; 342 | vector> segments; 343 | for(int i = 0; i < subSector.numSegments; i++) 344 | { 345 | shared_ptr segment = m_segments[subSector.firstSegment + i]; 346 | if(!segment->frontSide.sideless) 347 | { 348 | sectorId = segment->frontSide.sector.sectorId; 349 | } 350 | segments.push_back(segment); 351 | } 352 | m_subSectors.push_back(std::make_shared(subSectorId++, sectorId, segments)); 353 | } 354 | } 355 | 356 | void MapDef::BuildThings() 357 | { 358 | int id = 0; 359 | m_things.resize(m_sectors.size()); 360 | for(const auto& thing : m_store.m_things) 361 | { 362 | Thing t(static_cast(thing.x), static_cast(thing.y), 0, static_cast(thing.a / 180.0f * PI)); 363 | const auto sector = GetSector(t); 364 | if(sector.has_value()) 365 | { 366 | t.sectorId = sector->sectorId; 367 | t.thingId = id++; 368 | t.type = thing.type; 369 | const auto& tt = WADFile::m_thingTypes.find(thing.type); 370 | if(tt != WADFile::m_thingTypes.end()) 371 | { 372 | t.textureName = tt->second; 373 | } 374 | t.z = m_store.m_sectors[t.sectorId].floorHeight; 375 | m_things[t.sectorId].emplace_back(t); 376 | } 377 | } 378 | } 379 | 380 | MapDef::~MapDef() { } 381 | } // namespace rtdoom 382 | -------------------------------------------------------------------------------- /rtdoom/GLRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "rtdoom.h" 3 | #include "GLRenderer.h" 4 | #include "Projection.h" 5 | 6 | #pragma warning(disable : 4201) 7 | 8 | #include "glad/glad.h" 9 | #include "glm/gtc/matrix_transform.hpp" 10 | #include "glm/gtc/type_ptr.hpp" 11 | 12 | namespace rtdoom 13 | { 14 | 15 | const char* s_vertexShaderSource = "#version 330 core\n" 16 | "layout (location = 0) in vec3 aPos;\n" 17 | "layout (location = 1) in vec3 aTexCoord;\n" 18 | "layout (location = 2) in float aTexNo;\n" 19 | "layout (location = 3) in float aLight;\n" 20 | "out vec3 TexCoord;\n" 21 | "out float TexNo;\n" 22 | "out float Light;\n" 23 | "uniform mat4 _view;\n" 24 | "uniform mat4 _projection;\n" 25 | "void main()\n" 26 | "{\n" 27 | " gl_Position = _projection * _view * vec4(aPos.x, aPos.y, aPos.z, 1.0);\n" 28 | " TexCoord = aTexCoord;" 29 | " TexNo = aTexNo;" 30 | " Light = aLight;" 31 | "}\0"; 32 | 33 | const char* s_fragmentShaderSource = "#version 330 core\n" 34 | "out vec4 FragColor;\n" 35 | "in vec3 TexCoord;\n" 36 | "in float TexNo;\n" 37 | "in float Light;\n" 38 | "uniform sampler2DArray _textures[16];\n" 39 | "void main()\n" 40 | "{\n" 41 | " int tn = int(TexNo + 0.05);\n" 42 | " FragColor = vec4(0, 0, 0, 0);\n" 43 | " FragColor = tn == 0 ? texture(_textures[0], TexCoord) : FragColor;\n" 44 | " FragColor = tn == 1 ? texture(_textures[1], TexCoord) : FragColor;\n" 45 | " FragColor = tn == 2 ? texture(_textures[2], TexCoord) : FragColor;\n" 46 | " FragColor = tn == 3 ? texture(_textures[3], TexCoord) : FragColor;\n" 47 | " FragColor = tn == 4 ? texture(_textures[4], TexCoord) : FragColor;\n" 48 | " FragColor = tn == 5 ? texture(_textures[5], TexCoord) : FragColor;\n" 49 | " FragColor = tn == 6 ? texture(_textures[6], TexCoord) : FragColor;\n" 50 | " FragColor = tn == 7 ? texture(_textures[7], TexCoord) : FragColor;\n" 51 | " FragColor = tn == 8 ? texture(_textures[8], TexCoord) : FragColor;\n" 52 | " FragColor = tn == 9 ? texture(_textures[9], TexCoord) : FragColor;\n" 53 | " FragColor = tn == 10 ? texture(_textures[10], TexCoord) : FragColor;\n" 54 | " FragColor = tn == 11 ? texture(_textures[11], TexCoord) : FragColor;\n" 55 | " FragColor = tn == 12 ? texture(_textures[12], TexCoord) : FragColor;\n" 56 | " FragColor = tn == 13 ? texture(_textures[13], TexCoord) : FragColor;\n" 57 | " FragColor = tn == 14 ? texture(_textures[14], TexCoord) : FragColor;\n" 58 | " FragColor = tn == 15 ? texture(_textures[15], TexCoord) : FragColor;\n" 59 | " FragColor = FragColor * Light;\n" 60 | " FragColor = FragColor * min(1.0f, (gl_FragCoord.w + 1.25f) / 5.0f);\n" 61 | " FragColor.w = 1.0f;\n" 62 | "}\0"; 63 | 64 | GLRenderer::GLRenderer(const GameState& gameState, const WADFile& wadFile, int width, int height) : 65 | RendererBase {gameState}, m_context {wadFile}, m_width {width}, m_height {height} 66 | { } 67 | 68 | void GLRenderer::LoadMap() 69 | { 70 | if(!m_gameState.m_mapDef->HasGL()) 71 | { 72 | throw std::runtime_error("No GL data found! Drop off a matching .gwa file with GL nodes (generated by glBSP) alongside the .wad."); 73 | } 74 | 75 | for(const auto& subSector : m_gameState.m_mapDef->m_subSectors) 76 | { 77 | int startIndex = m_context.m_indices.size(); 78 | for(const auto& segment : subSector->segments) 79 | { 80 | const auto& frontSector = segment->frontSide.sector; 81 | int textureUnit = 0, textureNo = 0; 82 | float lightness = frontSector.lightLevel; 83 | std::shared_ptr texture; 84 | // auto-shade 90-degree edges 85 | if(segment->isVertical) 86 | { 87 | lightness *= 1.1f; 88 | } 89 | else if(segment->isHorizontal) 90 | { 91 | lightness *= 0.9f; 92 | } 93 | if(segment->isSolid) 94 | { 95 | texture = m_context.AllocateTexture(segment->frontSide.middleTexture, textureUnit, textureNo); 96 | const auto texXOffset = (segment->xOffset + segment->frontSide.xOffset) / (float)texture->width; 97 | const auto texYOffset = (segment->frontSide.yOffset) / (float)texture->height; 98 | const auto segW = Projection::Distance(segment->s, segment->e) / (float)texture->width; 99 | const auto segH = (frontSector.ceilingHeight - frontSector.floorHeight) / (float)texture->height; 100 | 101 | m_context.AddWallSegment(segment->s, 102 | frontSector.floorHeight, 103 | segment->e, 104 | frontSector.ceilingHeight, 105 | texXOffset, 106 | texYOffset + segH, 107 | texXOffset + segW, 108 | texYOffset, 109 | textureUnit, 110 | textureNo, 111 | lightness); 112 | } 113 | else 114 | { 115 | const auto& backSector = segment->backSide.sector; 116 | 117 | if(segment->frontSide.lowerTexture != "-" && segment->frontSide.lowerTexture != "") 118 | { 119 | texture = m_context.AllocateTexture(segment->frontSide.lowerTexture, textureUnit, textureNo); 120 | const auto texXOffset = (segment->xOffset + segment->frontSide.xOffset) / (float)texture->width; 121 | auto texYOffset = (segment->frontSide.yOffset) / (float)texture->height; 122 | const auto segW = Projection::Distance(segment->s, segment->e) / (float)texture->width; 123 | const auto segH = abs(frontSector.floorHeight - backSector.floorHeight) / (float)texture->height; 124 | 125 | if(segment->lowerUnpegged) 126 | { 127 | texYOffset += (frontSector.ceilingHeight - backSector.floorHeight) / (float)texture->height; 128 | } 129 | 130 | m_context.AddWallSegment(segment->s, 131 | frontSector.floorHeight, 132 | segment->e, 133 | backSector.floorHeight, 134 | texXOffset, 135 | texYOffset + segH, 136 | texXOffset + segW, 137 | texYOffset, 138 | textureUnit, 139 | textureNo, 140 | lightness); 141 | } 142 | 143 | if(segment->frontSide.upperTexture != "-" && segment->frontSide.lowerTexture != "" && !segment->backSide.sector.isSky) 144 | { 145 | texture = m_context.AllocateTexture(segment->frontSide.upperTexture, textureUnit, textureNo); 146 | const auto texXOffset = (segment->xOffset + segment->frontSide.xOffset) / (float)texture->width; 147 | auto texYOffset = (segment->frontSide.yOffset) / (float)texture->height; 148 | const auto segW = Projection::Distance(segment->s, segment->e) / (float)texture->width; 149 | const auto segH = abs(backSector.ceilingHeight - frontSector.ceilingHeight) / (float)texture->height; 150 | 151 | if(segment->upperUnpegged) 152 | { 153 | texYOffset += (frontSector.ceilingHeight - backSector.floorHeight) / (float)texture->height; 154 | } 155 | 156 | m_context.AddWallSegment(segment->s, 157 | backSector.ceilingHeight, 158 | segment->e, 159 | frontSector.ceilingHeight, 160 | texXOffset, 161 | texYOffset + segH, 162 | texXOffset + segW, 163 | texYOffset, 164 | textureUnit, 165 | textureNo, 166 | lightness); 167 | } 168 | } 169 | } 170 | 171 | const auto& sector = m_gameState.m_mapDef->m_sectors[subSector->sectorId]; 172 | if(subSector->segments.size()) 173 | { 174 | // create triangle fan from current (convex) subsector 175 | auto curr = subSector->segments.begin(); 176 | Vertex fanStart((*curr)->s); 177 | while(++curr != subSector->segments.end()) 178 | { 179 | std::shared_ptr texture; 180 | int textureUnit, textureNo; 181 | const auto& fanLine = *curr; 182 | if(sector.floorTexture != "-") 183 | { 184 | texture = m_context.AllocateTexture(sector.floorTexture, textureUnit, textureNo); 185 | m_context.AddFloorCeiling(fanLine->e, 186 | fanLine->s, 187 | fanStart, 188 | sector.floorHeight, 189 | texture->width, 190 | texture->height, 191 | textureUnit, 192 | textureNo, 193 | sector.lightLevel); 194 | } 195 | if(sector.ceilingTexture != "-" && !sector.isSky) 196 | { 197 | texture = m_context.AllocateTexture(sector.ceilingTexture, textureUnit, textureNo); 198 | m_context.AddFloorCeiling(fanStart, 199 | fanLine->s, 200 | fanLine->e, 201 | sector.ceilingHeight, 202 | texture->width, 203 | texture->height, 204 | textureUnit, 205 | textureNo, 206 | sector.lightLevel); 207 | } 208 | } 209 | } 210 | 211 | m_context.m_subSectorOffsets[subSector->subSectorId] = std::make_pair(startIndex, m_context.m_indices.size() - startIndex); 212 | } 213 | 214 | m_context.BindMap(); 215 | } 216 | 217 | void GLRenderer::Resize(int width, int height) 218 | { 219 | glViewport(0, 0, width, height); 220 | m_width = width; 221 | m_height = height; 222 | } 223 | 224 | void GLRenderer::Reset() 225 | { 226 | m_context.Reset(); 227 | } 228 | 229 | void GLRenderer::RenderFrame() 230 | { 231 | glClearColor(0.15f, 0.15f, 0.15f, 1.0f); // TODO: replace with skybox 232 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 233 | 234 | glm::mat4 viewMat = glm::mat4(1.0f); 235 | viewMat = glm::rotate(viewMat, glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); 236 | viewMat = glm::scale(viewMat, glm::vec3(0.001f, 0.001f, 0.001f)); 237 | viewMat = glm::rotate(viewMat, -m_gameState.m_player.a + glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); 238 | viewMat = glm::translate(viewMat, glm::vec3(-m_gameState.m_player.x, -m_gameState.m_player.y, -m_gameState.m_player.z)); 239 | glm::mat4 projectionMat = glm::perspective(glm::radians(53.75f) / 1.27f, 1.27f * m_width / (float)m_height, 0.01f, 100.0f); 240 | 241 | m_context.BindView(glm::value_ptr(viewMat), glm::value_ptr(projectionMat)); 242 | 243 | RenderSegments(); 244 | } 245 | 246 | void GLRenderer::Initialize() 247 | { 248 | if(!gladLoadGL()) 249 | { 250 | printf("Error initializing GL!\n"); 251 | abort(); 252 | } 253 | 254 | glEnable(GL_DEPTH_TEST); 255 | glEnable(GL_CULL_FACE); 256 | 257 | m_context.Initialize(); 258 | m_context.CompileShaders(s_vertexShaderSource, s_fragmentShaderSource); 259 | } 260 | 261 | void GLRenderer::RenderSegments() const 262 | { 263 | // iterate through all segments (map lines) in visibility order returned by traversing the map's BSP tree 264 | for(const auto& ss : m_gameState.m_mapDef->GetSubSectorsToDraw(m_gameState.m_player)) 265 | { 266 | const auto& subSec = m_context.m_subSectorOffsets.at(ss->subSectorId); 267 | glDrawElements(GL_TRIANGLES, subSec.second, GL_UNSIGNED_INT, (void*)(subSec.first * sizeof(int))); 268 | } 269 | } 270 | 271 | GLRenderer::~GLRenderer() { } 272 | } // namespace rtdoom 273 | -------------------------------------------------------------------------------- /rtdoom/WADFile.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "WADFile.h" 3 | #include "Helpers.h" 4 | 5 | namespace rtdoom 6 | { 7 | WADFile::WADFile(const std::string& fileName) 8 | { 9 | std::ifstream infile(fileName, std::ios::binary); 10 | if(!infile.good()) 11 | { 12 | throw std::runtime_error("Unable to open file " + fileName); 13 | } 14 | 15 | Header header; 16 | infile.read(reinterpret_cast(&header), sizeof(header)); 17 | infile.clear(); 18 | infile.seekg(header.dirLocation); 19 | 20 | std::vector lumps; 21 | for(auto i = 0; i < header.numEntries; i++) 22 | { 23 | Lump lump; 24 | infile.read(reinterpret_cast(&lump), sizeof(lump)); 25 | lumps.push_back(lump); 26 | } 27 | 28 | // find and load maps and patches 29 | for(size_t i = 0; i < lumps.size(); i++) 30 | { 31 | const Lump& lump = lumps.at(i); 32 | switch(GetLumpType(lump)) 33 | { 34 | case LumpType::MapMarker: { 35 | std::map> mapLumps; 36 | for(auto j = 1; j <= 10; j++) 37 | { 38 | std::string lumpName = Helpers::MakeString(lumps[i + j].lumpName); 39 | mapLumps.insert(make_pair(lumpName, LoadLump(infile, lumps[i + j]))); 40 | } 41 | i += 10; 42 | MapStore mapStore; 43 | mapStore.Load(mapLumps); 44 | m_maps.insert(make_pair(std::string(lump.lumpName), mapStore)); 45 | break; 46 | } 47 | case LumpType::Palette: { 48 | Helpers::LoadEntity(LoadLump(infile, lump), &m_palette); 49 | break; 50 | } 51 | case LumpType::PatchNames: { 52 | auto lumpData = LoadLump(infile, lump); 53 | int numPatches; 54 | memcpy(&numPatches, lumpData.data(), sizeof(numPatches)); 55 | for(int pi = 0; pi < numPatches; pi++) 56 | { 57 | char patchName[8]; 58 | memcpy(patchName, lumpData.data() + sizeof(numPatches) + pi * sizeof(char[8]), sizeof(char[8])); 59 | m_patchNames.push_back(Helpers::MakeString(patchName)); 60 | } 61 | break; 62 | } 63 | case LumpType::PatchesStart: { 64 | int j = 1; 65 | while(GetLumpType(lumps.at(i + j)) != LumpType::PatchesEnd) 66 | { 67 | const Lump& patchLump = lumps.at(i + j); 68 | auto patchData = LoadLump(infile, patchLump); 69 | auto p = LoadPatch(patchData); 70 | m_patches.insert(make_pair(Helpers::MakeString(patchLump.lumpName), p)); 71 | j++; 72 | } 73 | break; 74 | } 75 | case LumpType::SpritesStart: { 76 | int j = 1; 77 | while(GetLumpType(lumps.at(i + j)) != LumpType::SpritesEnd) 78 | { 79 | const Lump& patchLump = lumps.at(i + j); 80 | auto patchData = LoadLump(infile, patchLump); 81 | auto p = LoadPatch(patchData); 82 | const auto& patchName = Helpers::MakeString(patchLump.lumpName); 83 | if(patchName.length() == 8 && isdigit(patchName[5]) && isdigit(patchName[7])) 84 | { 85 | // split into two patches 86 | m_sprites.insert(make_pair(patchName.substr(0, 6), p)); 87 | m_sprites.insert(make_pair(patchName.substr(0, 4) + patchName.substr(6, 2), FlipPatch(p))); 88 | } 89 | else 90 | { 91 | m_sprites.insert(make_pair(patchName, p)); 92 | } 93 | j++; 94 | } 95 | break; 96 | } 97 | case LumpType::FlatsStart: { 98 | int j = 1; 99 | while(GetLumpType(lumps.at(i + j)) != LumpType::FlatsEnd) 100 | { 101 | const Lump& patchLump = lumps.at(i + j); 102 | auto patchData = LoadLump(infile, patchLump); 103 | 104 | auto t = std::make_shared(); 105 | t->width = 64; 106 | t->height = 64; 107 | t->name = Helpers::MakeString(patchLump.lumpName); 108 | t->masked = 0; 109 | t->pixels = std::make_unique(t->width * t->height); 110 | 111 | memcpy(t->pixels.get(), patchData.data(), 64 * 64); 112 | 113 | m_textures.insert(make_pair(t->name, t)); 114 | j++; 115 | } 116 | break; 117 | } 118 | default: 119 | break; 120 | } 121 | } 122 | 123 | // build textures 124 | for(const auto& lump : lumps) 125 | { 126 | switch(GetLumpType(lump)) 127 | { 128 | case LumpType::Texture: { 129 | auto data = LoadLump(infile, lump); 130 | signed int numTextures; 131 | memcpy(&numTextures, data.data(), sizeof(numTextures)); 132 | std::vector textureOffsets; 133 | for(auto i = 0; i < numTextures; i++) 134 | { 135 | int textureOffset; 136 | memcpy(&textureOffset, data.data() + sizeof(numTextures) + i * sizeof(textureOffset), sizeof(textureOffset)); 137 | 138 | TextureInfo textureInfo; 139 | memcpy(&textureInfo, data.data() + textureOffset, sizeof(TextureInfo)); 140 | std::vector patches; 141 | for(auto j = 0; j < textureInfo.patchcount; j++) 142 | { 143 | PatchInfo p; 144 | memcpy(&p, data.data() + textureOffset + sizeof(TextureInfo) + j * sizeof(PatchInfo), sizeof(PatchInfo)); 145 | patches.push_back(p); 146 | } 147 | 148 | auto t = std::make_shared(); 149 | t->width = textureInfo.width; 150 | t->height = textureInfo.height; 151 | t->name = Helpers::MakeString(textureInfo.name); 152 | t->masked = textureInfo.masked; 153 | t->pixels = std::make_unique(t->width * t->height); 154 | memset(t->pixels.get(), static_cast(247), t->width * t->height); 155 | 156 | for(const auto& p : patches) 157 | { 158 | const auto pi = m_patchNames[p.patch]; 159 | if(m_patches.find(pi) != m_patches.end()) 160 | { 161 | PastePatch(t.get(), m_patches[pi].get(), p.originx, p.originy); 162 | } 163 | } 164 | 165 | m_textures.insert(make_pair(t->name, t)); 166 | } 167 | } 168 | break; 169 | } 170 | } 171 | 172 | TryLoadGWA(fileName); 173 | } 174 | 175 | void WADFile::TryLoadGWA(const std::string& fileName) 176 | { 177 | std::string glName(fileName); 178 | glName.replace(glName.find_last_of('.'), glName.length(), ".gwa"); 179 | 180 | std::ifstream infile(glName, std::ios::binary); 181 | if(!infile.good()) 182 | { 183 | return; 184 | } 185 | 186 | Header header; 187 | infile.read(reinterpret_cast(&header), sizeof(header)); 188 | infile.clear(); 189 | infile.seekg(header.dirLocation); 190 | 191 | std::vector lumps; 192 | for(auto i = 0; i < header.numEntries; i++) 193 | { 194 | Lump lump; 195 | infile.read(reinterpret_cast(&lump), sizeof(lump)); 196 | lumps.push_back(lump); 197 | } 198 | 199 | // find and load maps and patches 200 | for(size_t i = 0; i < lumps.size(); i++) 201 | { 202 | const Lump& lump = lumps.at(i); 203 | switch(GetLumpType(lump)) 204 | { 205 | case LumpType::MapMarker: { 206 | std::map> mapLumps; 207 | for(auto j = 1; j <= 4; j++) 208 | { 209 | std::string lumpName = Helpers::MakeString(lumps[i + j].lumpName); 210 | mapLumps.insert(make_pair(lumpName, LoadLump(infile, lumps[i + j]))); 211 | } 212 | i += 4; 213 | const std::string mapName(lump.lumpName + 3); 214 | auto map = m_maps.find(mapName); 215 | if(map != m_maps.end()) 216 | { 217 | map->second.LoadGL(mapLumps); 218 | } 219 | break; 220 | } 221 | } 222 | } 223 | } 224 | std::shared_ptr WADFile::LoadPatch(const std::vector& patchData) 225 | { 226 | auto p = std::make_shared(); 227 | memcpy(p.get(), patchData.data(), 4 * sizeof(short)); 228 | p->pixels = std::make_unique(p->width * p->height); 229 | memset(p->pixels.get(), static_cast(247), p->width * p->height); 230 | 231 | std::vector columnArray(p->width); 232 | memcpy(columnArray.data(), patchData.data() + 4 * sizeof(short), sizeof(int) * p->width); 233 | 234 | for(auto c = 0; c < p->width; c++) 235 | { 236 | auto columnOffset = columnArray[c]; 237 | unsigned char rowstart = 0; 238 | while(rowstart != 255) 239 | { 240 | memcpy(&rowstart, patchData.data() + columnOffset++, sizeof(unsigned char)); 241 | if(rowstart == 255) 242 | { 243 | break; 244 | } 245 | unsigned char pixelCount; 246 | unsigned char dummy; 247 | memcpy(&pixelCount, patchData.data() + columnOffset++, sizeof(unsigned char)); 248 | memcpy(&dummy, patchData.data() + columnOffset++, sizeof(unsigned char)); 249 | for(unsigned char pj = 0; pj < pixelCount; pj++) 250 | { 251 | unsigned char pixelColor; 252 | memcpy(&pixelColor, patchData.data() + columnOffset++, sizeof(unsigned char)); 253 | p->pixels[(pj + rowstart) * p->width + c] = pixelColor; 254 | } 255 | memcpy(&dummy, patchData.data() + columnOffset++, sizeof(unsigned char)); 256 | } 257 | } 258 | return p; 259 | } 260 | 261 | std::shared_ptr WADFile::FlipPatch(const std::shared_ptr& patch) 262 | { 263 | auto p = std::make_shared(); 264 | p->height = patch->height; 265 | p->width = patch->width; 266 | p->left = patch->width - patch->left - 1; 267 | p->top = patch->top; 268 | p->pixels = std::make_unique(p->width * p->height); 269 | for(int y = 0; y < patch->height; y++) 270 | { 271 | for(int x = 0; x < patch->width; x++) 272 | { 273 | p->pixels[p->width * y + x] = patch->pixels[patch->width * y + patch->width - 1 - x]; 274 | } 275 | } 276 | return p; 277 | } 278 | 279 | WADFile::LumpType WADFile::GetLumpType(const Lump& lump) const 280 | { 281 | if(lump.dataSize == 0) 282 | { 283 | if((lump.lumpName[0] == 'E' && lump.lumpName[2] == 'M') || 284 | (lump.lumpName[0] == 'M' && lump.lumpName[1] == 'A' && lump.lumpName[2] == 'P')) 285 | { 286 | return LumpType::MapMarker; 287 | } 288 | if(lump.lumpName[0] == 'P' && isdigit(lump.lumpName[1]) && lump.lumpName[2] == '_') 289 | { 290 | if(lump.lumpName[3] == 'S') 291 | { 292 | return LumpType::PatchesStart; 293 | } 294 | if(lump.lumpName[3] == 'E') 295 | { 296 | return LumpType::PatchesEnd; 297 | } 298 | } 299 | if(lump.lumpName[0] == 'F' && isdigit(lump.lumpName[1]) && lump.lumpName[2] == '_') 300 | { 301 | if(lump.lumpName[3] == 'S') 302 | { 303 | return LumpType::FlatsStart; 304 | } 305 | if(lump.lumpName[3] == 'E') 306 | { 307 | return LumpType::FlatsEnd; 308 | } 309 | } 310 | if(lump.lumpName[0] == 'S' && lump.lumpName[1] == '_') 311 | { 312 | if(lump.lumpName[2] == 'S') 313 | { 314 | return LumpType::SpritesStart; 315 | } 316 | if(lump.lumpName[2] == 'E') 317 | { 318 | return LumpType::SpritesEnd; 319 | } 320 | } 321 | return LumpType::Unknown; 322 | } 323 | if(lump.lumpName[0] == 'G' && lump.lumpName[1] == 'L' && lump.lumpName[2] == '_') 324 | { 325 | return LumpType::MapMarker; 326 | } 327 | if(strncmp(lump.lumpName, "PLAYPAL", 7) == 0) 328 | { 329 | return LumpType::Palette; 330 | } 331 | if(strncmp(lump.lumpName, "TEXTURE", 7) == 0) 332 | { 333 | return LumpType::Texture; 334 | } 335 | if(strncmp(lump.lumpName, "PNAMES", 7) == 0) 336 | { 337 | return LumpType::PatchNames; 338 | } 339 | return LumpType::Unknown; 340 | } 341 | 342 | void WADFile::PastePatch(Texture* texture, const Patch* patch, int px, int py) 343 | { 344 | for(auto x = 0; x < patch->width; x++) 345 | { 346 | for(auto y = 0; y < patch->height; y++) 347 | { 348 | const auto colorIndex = patch->pixels[y * patch->width + x]; 349 | if(colorIndex != 247) 350 | { 351 | const auto color = m_palette.colors[colorIndex]; 352 | auto dx = px + x; 353 | auto dy = py + y; 354 | if(dx < 0 || dx >= texture->width || dy < 0 || dy >= texture->height) 355 | { 356 | continue; 357 | } 358 | 359 | texture->pixels[dy * texture->width + dx] = patch->pixels[y * patch->width + x]; 360 | } 361 | } 362 | } 363 | } 364 | 365 | std::vector WADFile::LoadLump(std::ifstream& infile, const Lump& lump) 366 | { 367 | std::vector data; 368 | infile.clear(); 369 | infile.seekg(lump.dataOffset); 370 | data.resize(lump.dataSize); 371 | infile.read(data.data(), lump.dataSize); 372 | return data; 373 | } 374 | 375 | WADFile::~WADFile() { } 376 | 377 | const std::map WADFile::m_thingTypes { 378 | {2023, "PSTRA0"}, {2026, "PMAPA0"}, {2014, "BON1A0"}, {2024, "PINSA0"}, {2022, "PINVA0"}, {2045, "PVISA0"}, {83, "MEGAA0"}, 379 | {2013, "SOULA0"}, {2015, "BON2A0"}, {8, "BPAKA0"}, {2019, "ARM2A0"}, {2018, "ARM1A0"}, {2012, "MEDIA0"}, {2025, "SUITA0"}, 380 | {2011, "STIMA0"}, {2006, "BFUGA0"}, {2002, "MGUNA0"}, {2005, "CSAWA0"}, {2004, "PLASA0"}, {2003, "LAUNA0"}, {2001, "SHOTA0"}, 381 | {82, "SGN2A0"}, {2007, "CLIPA0"}, {2048, "AMMOA0"}, {2046, "BROKA0"}, {2049, "SBOXA0"}, {2047, "CELLA0"}, {17, "CELPA0"}, 382 | {2010, "ROCKA0"}, {2008, "SHELA0"}, {5, "BKEYA0"}, {40, "BSKUA0"}, {13, "RKEYA0"}, {38, "RSKUA0"}, {6, "YKEYA0"}, 383 | {39, "YSKUA0"}, {68, "BSPIA1"}, {64, "VILEA1"}, {3003, "BOSSA1"}, {3005, "HEADA1"}, {65, "CPOSA1"}, {72, "KEENA1"}, 384 | {16, "CYBRA1"}, {3002, "SARGA1"}, {3004, "POSSA1"}, {9, "SPOSA1"}, {69, "BOS2A1"}, {3001, "TROOA1"}, {3006, "SKULA1"}, 385 | {67, "FATTA1"}, {71, "PAINA1"}, {66, "SKELA1"}, {58, "SARGA1"}, {7, "SPIDA1"}, {84, "SSWVA1"}, {2035, "BAR1A0"}, 386 | {70, "FCANA0"}, {43, "TRE1A0"}, {35, "CBRAA0"}, {41, "CEYEA0"}, {28, "POL2A0"}, {42, "FSKUA0"}, {2028, "COLUA0"}, 387 | {53, "GOR5A0"}, {52, "GOR4A0"}, {78, "HDB6A0"}, {75, "HDB3A0"}, {77, "HDB5A0"}, {76, "HDB4A0"}, {50, "GOR2A0"}, 388 | {74, "HDB2A0"}, {73, "HDB1A0"}, {51, "GOR3A0"}, {49, "GOR1A0"}, {25, "POL1A0"}, {54, "TRE2A0"}, {29, "POL3A0"}, 389 | {55, "SMBTA0"}, {56, "SMGTA0"}, {31, "COL2A0"}, {36, "COL5A0"}, {57, "SMRTA0"}, {33, "COL4A0"}, {37, "COL6A0"}, 390 | {86, "TLP2A0"}, {27, "POL4A0"}, {47, "SMITA0"}, {44, "TBLUA0"}, {45, "TGRNA0"}, {30, "COL1A0"}, {46, "TREDA0"}, 391 | {32, "COL3A0"}, {85, "TLMPA0"}, {48, "ELECA0"}, {26, "POL6A0"}, {10, "PLAYW0"}, {12, "PLAYW0"}, {34, "CANDA0"}, 392 | {22, "HEADL0"}, {21, "SARGN0"}, {18, "POSSL0"}, {19, "SPOSL0"}, {20, "TROOM0"}, {23, "SKULK0"}, {15, "PLAYN0"}, 393 | {62, "GOR5A0"}, {60, "GOR4A0"}, {59, "GOR2A0"}, {61, "GOR3A0"}, {63, "GOR1A0"}, {79, "POB1A0"}, {80, "POB2A0"}, 394 | {24, "POL5A0"}, {81, "BRS1A0"}}; 395 | } // namespace rtdoom 396 | --------------------------------------------------------------------------------