From 2195f5e66915ab3bbb4bdb0672f43bcc88278438 Mon Sep 17 00:00:00 2001 From: Simon Judd Date: Fri, 24 Jan 2025 16:52:49 +1030 Subject: [PATCH] 3d mode WIP - Currently only basic flats are rendered - Trying out a different control scheme with the cursor unlocked to select flats/walls, and can hold right click to mouselook around. An 'always mouselook' style like before will probably also be an option, but the new scheme will probably be required in certain cases like wayland where we can't warp the mouse pointer (yet) - Also includes other various fixes/updates and refactoring --- CMakeLists.txt | 2 +- dist/res/shaders/default3d.frag | 19 + dist/res/shaders/default3d.vert | 19 + src/Audio/AudioTags.cpp | 2 +- src/Game/Game.cpp | 2 +- src/MainEditor/EntryOperations.cpp | 10 +- src/MapEditor/Edit/Input.cpp | 16 +- src/MapEditor/MapEditContext.cpp | 23 +- src/MapEditor/MapEditContext.h | 13 +- src/MapEditor/Renderer/Camera.h | 43 - src/MapEditor/Renderer/Flat3D.h | 23 + src/MapEditor/Renderer/MCAnimations.cpp | 4 +- src/MapEditor/Renderer/MapGeometry.cpp | 213 ++ src/MapEditor/Renderer/MapGeometry.h | 31 + src/MapEditor/Renderer/MapRenderer2D.cpp | 2 + src/MapEditor/Renderer/MapRenderer3D.cpp | 3392 +---------------- src/MapEditor/Renderer/MapRenderer3D.h | 238 +- src/MapEditor/Renderer/Renderer.cpp | 121 +- src/MapEditor/Renderer/Renderer.h | 3 + src/MapEditor/UI/MapCanvas.cpp | 82 +- src/MapEditor/UI/MapCanvas.h | 2 + src/{MapEditor/Renderer => OpenGL}/Camera.cpp | 131 +- src/OpenGL/Camera.h | 72 + src/OpenGL/IndexBuffer.cpp | 2 +- src/OpenGL/OpenGL.cpp | 4 +- src/OpenGL/VertexBuffer3D.cpp | 18 +- src/OpenGL/VertexBuffer3D.h | 11 +- src/SLADEMap/MapObject/MapLine.cpp | 10 +- src/SLADEMap/MapObject/MapLine.h | 10 +- src/SLADEMap/MapObject/MapObject.cpp | 8 +- src/SLADEMap/MapObject/MapObject.h | 10 +- src/SLADEMap/MapObject/MapSector.cpp | 12 +- src/SLADEMap/MapObject/MapSector.h | 28 +- src/SLADEMap/MapObject/MapSide.cpp | 6 +- src/SLADEMap/MapObject/MapSide.h | 6 +- src/SLADEMap/MapObject/MapThing.cpp | 4 +- src/SLADEMap/MapObject/MapThing.h | 4 +- src/SLADEMap/MapObject/MapVertex.cpp | 6 +- src/SLADEMap/MapObject/MapVertex.h | 6 +- src/TextEditor/UI/SCallTip.cpp | 2 +- src/Utility/Polygon.cpp | 27 - src/Utility/Polygon.h | 23 +- 42 files changed, 805 insertions(+), 3855 deletions(-) create mode 100644 dist/res/shaders/default3d.frag create mode 100644 dist/res/shaders/default3d.vert delete mode 100644 src/MapEditor/Renderer/Camera.h create mode 100644 src/MapEditor/Renderer/Flat3D.h create mode 100644 src/MapEditor/Renderer/MapGeometry.cpp create mode 100644 src/MapEditor/Renderer/MapGeometry.h rename src/{MapEditor/Renderer => OpenGL}/Camera.cpp (60%) create mode 100644 src/OpenGL/Camera.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7485e688c..68fd70c09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ if(BUILD_WX) FetchContent_Declare( wxWidgets GIT_REPOSITORY https://github.com/wxWidgets/wxWidgets.git - GIT_TAG 181f7a1d5533a3b135de1fd8f0467ba15a8a9f69 # master (3.3.0) as of 2024-11-26 + GIT_TAG 01e7c6bc919369601533c84d10d6664aaa4220c3 # master (3.3.0) as of 2025-01-14 ) FetchContent_GetProperties(wxwidgets) if(NOT wxwidgets_POPULATED) diff --git a/dist/res/shaders/default3d.frag b/dist/res/shaders/default3d.frag new file mode 100644 index 000000000..add704c07 --- /dev/null +++ b/dist/res/shaders/default3d.frag @@ -0,0 +1,19 @@ +in VertexData +{ + vec2 tex_coord; + vec3 normal; +} vertex_in; + +out vec4 f_colour; + +uniform vec4 colour = vec4(1.0); +//uniform vec2 viewport_size; + +uniform sampler2D tex_unit; + +void main() +{ + vec4 col = colour * texture(tex_unit, vertex_in.tex_coord); + + f_colour = col; +} diff --git a/dist/res/shaders/default3d.vert b/dist/res/shaders/default3d.vert new file mode 100644 index 000000000..cf1e0640a --- /dev/null +++ b/dist/res/shaders/default3d.vert @@ -0,0 +1,19 @@ +layout (location = 0) in vec3 in_position; +layout (location = 1) in vec2 in_tex_coord; +layout (location = 2) in vec3 in_normal; + +out VertexData +{ + vec2 tex_coord; + vec3 normal; +} vertex_out; + +uniform mat4 mvp; + +void main() +{ + vertex_out.tex_coord = in_tex_coord; + vertex_out.normal = in_normal; + + gl_Position = mvp * vec4(in_position, 1.0); +} diff --git a/src/Audio/AudioTags.cpp b/src/Audio/AudioTags.cpp index 8343d90e4..fa5515633 100644 --- a/src/Audio/AudioTags.cpp +++ b/src/Audio/AudioTags.cpp @@ -380,7 +380,7 @@ wxString parseID3v1Tag(const MemChunk& mc, size_t start) album = wxString::FromAscii(tag.album, 30); comment = wxString::FromAscii(tag.comment, 30); year = wxString::FromAscii(tag.year, 4); - genre = (tag.genre < 192) ? wxString::FromAscii(ID3_V1_GENRES[tag.genre]) : ""; + genre = (tag.genre < 192) ? wxString::FromAscii(ID3_V1_GENRES[tag.genre]) : wxString{}; if (tag.comment[28] == 0 && tag.comment[29] != 0) { version = "ID3v1.1"; diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index b40d92fdc..b2a41cecd 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -428,7 +428,7 @@ void game::init() if (!zscript_entry) { // Bail out if no entry is found. - log::warning(1, "Could not find \'zscript.txt\' in " + zdoom_pk3_path); + log::warning(1, "Could not find \'zscript.txt\' in " + zdoom_pk3_path.value); } else { diff --git a/src/MainEditor/EntryOperations.cpp b/src/MainEditor/EntryOperations.cpp index 318da1b1f..8a6560af4 100644 --- a/src/MainEditor/EntryOperations.cpp +++ b/src/MainEditor/EntryOperations.cpp @@ -1395,7 +1395,7 @@ bool entryoperations::compileACS(ArchiveEntry* entry, bool hexen, ArchiveEntry* entry->exportFile(srcfile); // Execute acc - wxString command = "\"" + path_acc + "\"" + " " + opt + " \"" + srcfile + "\" \"" + ofile + "\""; + wxString command = "\"" + path_acc.value + "\"" + " " + opt + " \"" + srcfile + "\" \"" + ofile + "\""; wxArrayString output; wxArrayString errout; wxGetApp().SetTopWindow(parent); @@ -1590,7 +1590,7 @@ bool entryoperations::compileDECOHack(ArchiveEntry* entry, ArchiveEntry* target, entry->exportFile(srcfile); // Execute DECOHack - wxString command = "\"" + path_java + "\" -cp \"" + path_decohack + "\"" + wxString command = "\"" + path_java.value + "\" -cp \"" + path_decohack.value + "\"" + " -Xms64M -Xmx4G net.mtrop.doom.tools.DecoHackMain \"" + srcfile + "\" -o \"" + dehfile + "\""; wxArrayString output; wxArrayString errout; @@ -1757,7 +1757,7 @@ bool entryoperations::optimizePNG(ArchiveEntry* entry) wxString optfile = fn.GetFullPath(); entry->exportFile(pngfile.ToStdString()); - wxString command = path_pngcrush + " -brute \"" + pngfile + "\" \"" + optfile + "\""; + wxString command = path_pngcrush.value + " -brute \"" + pngfile + "\" \"" + optfile + "\""; output.Empty(); errors.Empty(); wxExecute(command, output, errors, wxEXEC_SYNC); @@ -1809,7 +1809,7 @@ bool entryoperations::optimizePNG(ArchiveEntry* entry) wxString optfile = fn.GetFullPath(); entry->exportFile(pngfile.ToStdString()); - wxString command = path_pngout + " /y \"" + pngfile + "\" \"" + optfile + "\""; + wxString command = path_pngout.value + " /y \"" + pngfile + "\" \"" + optfile + "\""; output.Empty(); errors.Empty(); wxExecute(command, output, errors, wxEXEC_SYNC); @@ -1860,7 +1860,7 @@ bool entryoperations::optimizePNG(ArchiveEntry* entry) wxString pngfile = fn.GetFullPath(); entry->exportFile(pngfile.ToStdString()); - wxString command = path_deflopt + " /sf \"" + pngfile + "\""; + wxString command = path_deflopt.value + " /sf \"" + pngfile + "\""; output.Empty(); errors.Empty(); wxExecute(command, output, errors, wxEXEC_SYNC); diff --git a/src/MapEditor/Edit/Input.cpp b/src/MapEditor/Edit/Input.cpp index 2c3dddd1c..b8dccc920 100644 --- a/src/MapEditor/Edit/Input.cpp +++ b/src/MapEditor/Edit/Input.cpp @@ -41,7 +41,6 @@ #include "MapEditor/ItemSelection.h" #include "MapEditor/MapEditContext.h" #include "MapEditor/MapEditor.h" -#include "MapEditor/Renderer/Camera.h" #include "MapEditor/Renderer/MCAnimations.h" #include "MapEditor/Renderer/MapRenderer3D.h" #include "MapEditor/Renderer/Overlays/MCOverlay.h" @@ -50,6 +49,7 @@ #include "MapEditor/UI/ObjectEditPanel.h" #include "MoveObjects.h" #include "ObjectEdit.h" +#include "OpenGL/Camera.h" #include "OpenGL/View.h" #include "SLADEMap/MapObject/MapThing.h" #include "UI/UI.h" @@ -244,9 +244,9 @@ bool Input::mouseDown(MouseButton button, bool double_click) if (context_->editMode() == Mode::Visual) { // If the mouse is unlocked, lock the mouse - if (!context_->mouseLocked()) - context_->lockMouse(true); - else + // if (!context_->mouseLocked()) + // context_->lockMouse(true); + // else { // Shift down, select all matching adjacent structures if (shift_down_) @@ -1146,8 +1146,7 @@ bool Input::updateCamera3d(double mult) const // --- Check for held-down keys --- bool moving = false; double speed = shift_down_ ? mult * 8 : mult * 4; - auto& r3d = context_->renderer().renderer3D(); - auto& camera = r3d.camera(); + auto& camera = context_->renderer().camera(); // Camera forward if (KeyBind::isPressed("me3d_camera_forward")) @@ -1206,8 +1205,9 @@ bool Input::updateCamera3d(double mult) const } // Apply gravity to camera if needed - if (camera_3d_gravity) - r3d.cameraApplyGravity(mult); + // TODO: 3dmode + // if (camera_3d_gravity) + // r3d.cameraApplyGravity(mult); return moving; } diff --git a/src/MapEditor/MapEditContext.cpp b/src/MapEditor/MapEditContext.cpp index 074ace58e..9ea6afa05 100644 --- a/src/MapEditor/MapEditContext.cpp +++ b/src/MapEditor/MapEditContext.cpp @@ -58,8 +58,8 @@ #include "MapEditor/UI/Dialogs/SectorSpecialDialog.h" #include "MapEditor/UI/Dialogs/ShowItemDialog.h" #include "MapTextureManager.h" +#include "OpenGL/Camera.h" #include "OpenGL/Draw2D.h" -#include "Renderer/Camera.h" #include "Renderer/MapRenderer3D.h" #include "Renderer/Overlays/LineInfoOverlay.h" #include "Renderer/Overlays/SectorInfoOverlay.h" @@ -166,7 +166,7 @@ void MapEditContext::setEditMode(Mode mode) // Clear 3d mode undo manager etc on exiting 3d mode if (edit_mode_ == Mode::Visual && mode != Mode::Visual) { - info_3d_.reset(); + info_3d_->reset(); undo_manager_->createMergedLevel(edit_3d_->undoManager(), "3D Mode Editing"); edit_3d_->undoManager()->clear(); } @@ -248,8 +248,9 @@ void MapEditContext::setEditMode(Mode mode) { SAction::fromId("mapw_mode_3d")->setChecked(); KeyBind::releaseAll(); - lockMouse(true); - renderer_->renderer3D().refresh(); + // lockMouse(true); + // TODO: 3dmode + // renderer_->renderer3D().refresh(); } mapeditor::window()->refreshToolBar(); } @@ -321,7 +322,7 @@ bool MapEditContext::update(double frametime) next_frame_length_ = 2; // Update status bar - auto pos = camera3d().position(); + auto pos = renderer_->camera().position(); mapeditor::setStatusText( fmt::format( "Position: ({}, {}, {})", static_cast(pos.x), static_cast(pos.y), static_cast(pos.z)), @@ -332,7 +333,8 @@ bool MapEditContext::update(double frametime) if (!selection_->hilightLocked()) { auto old_hl = selection_->hilight(); - hl = renderer_->renderer3D().determineHilight(); + // TODO: 3dmode + // hl = renderer_->renderer3D().determineHilight(); if (selection_->setHilight(hl)) { // Update 3d info overlay @@ -547,8 +549,9 @@ void MapEditContext::forceRefreshRenderer() const // Update 3d mode info overlay if needed if (edit_mode_ == Mode::Visual) { - auto hl = renderer_->renderer3D().determineHilight(); - info_3d_->update({ hl.index, hl.type }, map_.get()); + // TODO: 3dmode + // auto hl = renderer_->renderer3D().determineHilight(); + // info_3d_->update({ hl.index, hl.type }, map_.get()); } if (!canvas_->activateContext()) @@ -1596,9 +1599,9 @@ void MapEditContext::resetPlayerStart() const // ----------------------------------------------------------------------------- // Returns the 3d renderer's camera // ----------------------------------------------------------------------------- -Camera& MapEditContext::camera3d() const +gl::Camera& MapEditContext::camera3d() const { - return renderer_->renderer3D().camera(); + return renderer_->camera(); } // ----------------------------------------------------------------------------- diff --git a/src/MapEditor/MapEditContext.h b/src/MapEditor/MapEditContext.h index 38c7c77c6..2dd3344aa 100644 --- a/src/MapEditor/MapEditContext.h +++ b/src/MapEditor/MapEditContext.h @@ -6,7 +6,6 @@ // Forward declarations namespace slade { -class Camera; class InfoOverlay3D; class ItemSelection; class LineInfoOverlay; @@ -17,10 +16,14 @@ class ThingInfoOverlay; class UndoManager; class UndoStep; class VertexInfoOverlay; -namespace gl::draw2d +namespace gl { - struct Context; -} + class Camera; + namespace draw2d + { + struct Context; + } +} // namespace gl namespace ui { enum class MouseCursor; @@ -150,7 +153,7 @@ class MapEditContext : public SActionHandler void resetPlayerStart() const; // Renderer - Camera& camera3d() const; + gl::Camera& camera3d() const; // Misc string modeString(bool plural = true) const; diff --git a/src/MapEditor/Renderer/Camera.h b/src/MapEditor/Renderer/Camera.h deleted file mode 100644 index ac272995f..000000000 --- a/src/MapEditor/Renderer/Camera.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "Geometry/RectFwd.h" - -namespace slade -{ -class Camera -{ -public: - Camera() = default; - - double pitch() const { return pitch_; } - const Vec3d& position() const { return position_; } - const Vec2d& direction() const { return direction_; } - const Vec3d& strafeVector() const { return strafe_; } - const Vec3d& directionVector() const { return dir3d_; } - - void setPitch(double pitch); - void setPosition(const Vec3d& position); - void setDirection(const Vec2d& direction); - void set(const Vec3d& position, const Vec2d& direction); - - Vec3d upVector() const; - Seg2d strafeLine() const; - - void move(double distance, bool z = true); - void turn(double angle); - void moveUp(double distance); - void strafe(double distance); - void pitch(double amount); - void look(double xrel, double yrel); - void applyGravity(double floor_height, double mult); - -private: - Vec3d position_; - Vec2d direction_; - double pitch_ = 0.; - Vec3d dir3d_; - Vec3d strafe_; - - void updateVectors(); -}; -} // namespace slade diff --git a/src/MapEditor/Renderer/Flat3D.h b/src/MapEditor/Renderer/Flat3D.h new file mode 100644 index 000000000..09af79824 --- /dev/null +++ b/src/MapEditor/Renderer/Flat3D.h @@ -0,0 +1,23 @@ +#pragma once + +namespace slade::mapeditor +{ +struct Flat3D +{ + // Origin + const MapSector* sector = nullptr; + // const MapSector* control_sector = nullptr; // Maybe for 3d floors? + bool ceiling = false; + + // Vertex buffer info + unsigned vertex_offset = 0; + unsigned vertex_count = 0; + + // Render info + glm::vec4 colour = glm::vec4{ 1.0f }; + glm::vec3 normal = glm::vec3{ 0.0f, 0.0f, 1.0f }; + unsigned texture = 0; + + long updated_time = 0; +}; +} // namespace slade::mapeditor diff --git a/src/MapEditor/Renderer/MCAnimations.cpp b/src/MapEditor/Renderer/MCAnimations.cpp index 30d977742..0c7199077 100644 --- a/src/MapEditor/Renderer/MCAnimations.cpp +++ b/src/MapEditor/Renderer/MCAnimations.cpp @@ -37,7 +37,6 @@ #include "General/ColourConfiguration.h" #include "MapEditor/Item.h" #include "MapRenderer2D.h" -#include "MapRenderer3D.h" #include "OpenGL/Draw2D.h" #include "OpenGL/GLTexture.h" #include "OpenGL/OpenGL.h" @@ -611,5 +610,6 @@ bool MCAHilightFade3D::update(long time) // ----------------------------------------------------------------------------- void MCAHilightFade3D::draw() { - renderer_->renderHilight({ item_index_, item_type_ }, fade_); + // TODO: 3dmode + // renderer_->renderHilight({ item_index_, item_type_ }, fade_); } diff --git a/src/MapEditor/Renderer/MapGeometry.cpp b/src/MapEditor/Renderer/MapGeometry.cpp new file mode 100644 index 000000000..662063e24 --- /dev/null +++ b/src/MapEditor/Renderer/MapGeometry.cpp @@ -0,0 +1,213 @@ + +// ----------------------------------------------------------------------------- +// SLADE - It's a Doom Editor +// Copyright(C) 2008 - 2025 Simon Judd +// +// Email: sirjuddington@gmail.com +// Web: http://slade.mancubus.net +// Filename: MapGeometry.cpp +// Description: Map geometry calculation and helper functions for the 2D and 3D +// renderers +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 2 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301, USA. +// ----------------------------------------------------------------------------- + + +// ----------------------------------------------------------------------------- +// +// Includes +// +// ----------------------------------------------------------------------------- +#include "Main.h" + +#include "App.h" +#include "Flat3D.h" +#include "Game/Configuration.h" +#include "MapEditor/MapEditContext.h" +#include "MapEditor/MapEditor.h" +#include "MapEditor/MapTextureManager.h" +#include "MapGeometry.h" +#include "OpenGL/GLTexture.h" +#include "OpenGL/VertexBuffer3D.h" +#include "SLADEMap/MapObject/MapSector.h" +#include "Utility/Polygon.h" + +using namespace slade; +using namespace mapeditor; + + +namespace slade::mapeditor +{ +// ----------------------------------------------------------------------------- +// Get texture scaling/offset/rotation info for a sector texture +// ----------------------------------------------------------------------------- +TexTransformInfo getTextureTransformInfo(const MapSector& sector, bool ceiling, Vec2d tex_scale) +{ + using namespace game; + + TexTransformInfo info; + info.sx = tex_scale.x; + info.sy = tex_scale.y; + + auto& game_config = game::configuration(); + + // Get scaling/offset info + // Check for various UDMF extensions + if (mapeditor::editContext().mapDesc().format == MapFormat::UDMF) + { + // Ceiling + if (ceiling) + { + if (game_config.featureSupported(UDMFFeature::FlatPanning)) + { + info.ox = sector.floatProperty("xpanningceiling"); + info.oy = sector.floatProperty("ypanningceiling"); + } + if (game_config.featureSupported(UDMFFeature::FlatScaling)) + { + info.sx *= (1.0 / sector.floatProperty("xscaleceiling")); + info.sy *= (1.0 / sector.floatProperty("yscaleceiling")); + } + if (game_config.featureSupported(UDMFFeature::FlatRotation)) + info.rot = sector.floatProperty("rotationceiling"); + } + // Floor + else + { + if (game_config.featureSupported(UDMFFeature::FlatPanning)) + { + info.ox = sector.floatProperty("xpanningfloor"); + info.oy = sector.floatProperty("ypanningfloor"); + } + if (game_config.featureSupported(UDMFFeature::FlatScaling)) + { + info.sx *= (1.0 / sector.floatProperty("xscalefloor")); + info.sy *= (1.0 / sector.floatProperty("yscalefloor")); + } + if (game_config.featureSupported(UDMFFeature::FlatRotation)) + info.rot = sector.floatProperty("rotationfloor"); + } + } + // Scaling applies to offsets as well. + // Note for posterity: worldpanning only applies to textures, not flats + info.ox /= info.sx; + info.oy /= info.sy; + + return info; +} + +static void generateFlatVertices( + const Flat3D& flat, + const MapTextureManager::Texture& tex, + vector& vertices) +{ + auto& sector = *flat.sector; + auto& tex_info = gl::Texture::info(flat.texture); + auto ttf = getTextureTransformInfo(sector, flat.ceiling, tex.scale); + + if (flat.ceiling) + { + for (auto& vertex : sector.polygonVertices()) + { + vertices.emplace_back( + glm::vec3(vertex, sector.ceiling().plane.heightAt(vertex)), + polygon::calculateTexCoords( + vertex.x, vertex.y, tex_info.size.x, tex_info.size.y, ttf.sx, ttf.sy, ttf.ox, ttf.oy, ttf.rot)); + } + } + else + { + for (auto i = flat.vertex_count; i > 0; --i) // Floor polygons need to be flipped + { + auto& vertex = sector.polygonVertices()[i - 1]; + vertices.emplace_back( + glm::vec3(vertex, sector.floor().plane.heightAt(vertex)), + polygon::calculateTexCoords( + vertex.x, vertex.y, tex_info.size.x, tex_info.size.y, ttf.sx, ttf.sy, ttf.ox, ttf.oy, ttf.rot)); + } + } +} + +static Flat3D generateFlat3D( + const MapSector& sector, + bool ceiling, + unsigned vertex_index, + vector& vertices) +{ + Flat3D flat; + flat.sector = §or; + flat.ceiling = ceiling; + flat.vertex_offset = vertex_index; + flat.vertex_count = sector.polygonVertices().size(); + + // Colour + flat.colour = sector.colourAt(ceiling ? 2 : 1); + + // Normal + flat.normal = ceiling ? sector.ceiling().plane.normal() : sector.floor().plane.normal(); + + // Texture + bool mix_tex_flats = game::configuration().featureSupported(game::Feature::MixTexFlats); + auto& texture = ceiling ? textureManager().flat(sector.ceiling().texture, mix_tex_flats) + : textureManager().flat(sector.floor().texture, mix_tex_flats); + flat.texture = texture.gl_id; + + // Vertices + generateFlatVertices(flat, texture, vertices); + + flat.updated_time = app::runTimer(); + + return flat; +} + +// ----------------------------------------------------------------------------- +// Generates 3D flats and vertices for [sector] +// ----------------------------------------------------------------------------- +std::tuple, vector> generateSectorFlats(const MapSector& sector, unsigned vertex_index) +{ + vector flats; + vector vertices; + + // Floor + flats.push_back(generateFlat3D(sector, false, vertex_index, vertices)); + vertex_index += sector.polygonVertices().size(); + + // Ceiling + flats.push_back(generateFlat3D(sector, true, vertex_index, vertices)); + + // TODO: 3d floors + + return { flats, vertices }; +} + +// ----------------------------------------------------------------------------- +// Updates [flat] and generates new [vertices] for it +// ----------------------------------------------------------------------------- +void updateFlat(Flat3D& flat, vector& vertices) +{ + // Update flat + auto& sector = *flat.sector; + auto& texture = flat.ceiling ? textureManager().flat(sector.ceiling().texture, true) + : textureManager().flat(sector.floor().texture, true); + flat.texture = texture.gl_id; + flat.colour = sector.colourAt(flat.ceiling ? 2 : 1); + + // Generate new vertices + generateFlatVertices(flat, texture, vertices); + + flat.updated_time = app::runTimer(); +} + +} // namespace slade::mapeditor diff --git a/src/MapEditor/Renderer/MapGeometry.h b/src/MapEditor/Renderer/MapGeometry.h new file mode 100644 index 000000000..c8cc81811 --- /dev/null +++ b/src/MapEditor/Renderer/MapGeometry.h @@ -0,0 +1,31 @@ +#pragma once + +// Forward declarations +namespace slade +{ +namespace gl +{ + struct Vertex3D; +} +namespace mapeditor +{ + struct Flat3D; +} +} // namespace slade + + +namespace slade::mapeditor +{ +struct TexTransformInfo +{ + double ox = 0.; + double oy = 0.; + double sx = 1.; + double sy = 1.; + double rot = 0.; +}; +TexTransformInfo getTextureTransformInfo(const MapSector& sector, bool ceiling, Vec2d tex_scale); + +std::tuple, vector> generateSectorFlats(const MapSector& sector, unsigned vertex_index); +void updateFlat(Flat3D& flat, vector& vertices); +} // namespace slade::mapeditor diff --git a/src/MapEditor/Renderer/MapRenderer2D.cpp b/src/MapEditor/Renderer/MapRenderer2D.cpp index 0d0aa24a1..6d839f048 100644 --- a/src/MapEditor/Renderer/MapRenderer2D.cpp +++ b/src/MapEditor/Renderer/MapRenderer2D.cpp @@ -1639,6 +1639,8 @@ void MapRenderer2D::updateFlatsBuffer(bool ceilings) flat_groups_.clear(); } + gl::bindVAO(0); + if (flat_groups_.empty()) { vector flats_processed(flats_.size()); diff --git a/src/MapEditor/Renderer/MapRenderer3D.cpp b/src/MapEditor/Renderer/MapRenderer3D.cpp index dca214e3e..38fc088bd 100644 --- a/src/MapEditor/Renderer/MapRenderer3D.cpp +++ b/src/MapEditor/Renderer/MapRenderer3D.cpp @@ -1,65 +1,21 @@ -// ----------------------------------------------------------------------------- -// SLADE - It's a Doom Editor -// Copyright(C) 2008 - 2024 Simon Judd -// -// Email: sirjuddington@gmail.com -// Web: http://slade.mancubus.net -// Filename: MapRenderer3D.cpp -// Description: MapRenderer3D class - handles all rendering related stuff for -// 3d mode -// -// This program is free software; you can redistribute it and/or modify it -// under the terms of the GNU General Public License as published by the Free -// Software Foundation; either version 2 of the License, or (at your option) -// any later version. -// -// This program is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -// more details. -// -// You should have received a copy of the GNU General Public License along with -// this program; if not, write to the Free Software Foundation, Inc., -// 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301, USA. -// ----------------------------------------------------------------------------- - - -// ----------------------------------------------------------------------------- -// -// Includes -// -// ----------------------------------------------------------------------------- #include "Main.h" -#include "MapRenderer3D.h" + #include "App.h" -#include "Camera.h" -#include "Game/Configuration.h" -#include "Game/ThingType.h" -#include "General/ColourConfiguration.h" -#include "General/ResourceManager.h" -#include "Geometry/Geometry.h" -#include "MainEditor/MainEditor.h" -#include "MainEditor/UI/MainWindow.h" -#include "MapEditor/ItemSelection.h" -#include "MapEditor/MapEditContext.h" -#include "MapEditor/MapEditor.h" -#include "MapEditor/MapTextureManager.h" +#include "Flat3D.h" +#include "MapGeometry.h" +#include "MapRenderer3D.h" +#include "OpenGL/Camera.h" #include "OpenGL/GLTexture.h" -#include "SLADEMap/MapObject/MapLine.h" +#include "OpenGL/Shader.h" +#include "OpenGL/VertexBuffer3D.h" +#include "OpenGL/View.h" #include "SLADEMap/MapObject/MapSector.h" -#include "SLADEMap/MapObject/MapSide.h" -#include "SLADEMap/MapObject/MapThing.h" #include "SLADEMap/MapObjectList/SectorList.h" -#include "SLADEMap/MapSpecials.h" #include "SLADEMap/SLADEMap.h" -#include "UI/Controls/PaletteChooser.h" -#include "Utility/MathStuff.h" -#include "Utility/StringUtils.h" -#include +#include "Utility/Vector.h" using namespace slade; -using ExtraFloor = MapSector::ExtraFloor; // ----------------------------------------------------------------------------- @@ -81,51 +37,6 @@ CVAR(Float, render_3d_brightness, 1, CVar::Flag::Save) CVAR(Float, render_fog_distance, 1500, CVar::Flag::Save) CVAR(Bool, render_fog_new_formula, true, CVar::Flag::Save) CVAR(Bool, render_shade_orthogonal_lines, true, CVar::Flag::Save) -CVAR(Int, render_fov, 90, CVar::Flag::Save) - - -// ----------------------------------------------------------------------------- -// -// External Variables -// -// ----------------------------------------------------------------------------- -EXTERN_CVAR(Bool, flats_use_vbo) -EXTERN_CVAR(Bool, use_zeth_icons) - - -// ----------------------------------------------------------------------------- -// -// Functions -// -// ----------------------------------------------------------------------------- -namespace -{ -// ----------------------------------------------------------------------------- -// (Helper for updateLine) Fetches the per-wall-section offset and scale and -// adjusts the existing offsets to match. -// ----------------------------------------------------------------------------- -void applyZDoomPerSectionOffsets( - MapSide* side, - const string& section_name, - double* xoff, - double* yoff, - double* sx, - double* sy) -{ - if (side->hasProp("offsetx_" + section_name)) - *xoff += side->floatProperty("offsetx_" + section_name); - if (side->hasProp("offsety_" + section_name)) - *yoff += side->floatProperty("offsety_" + section_name); - - if (side->hasProp("scalex_" + section_name)) - *sx = 1.0 / side->floatProperty("scalex_" + section_name); - if (side->hasProp("scaley_" + section_name)) - *sy = 1.0 / side->floatProperty("scaley_" + section_name); - - *xoff *= fabs(*sx); - *yoff *= fabs(*sy); -} -} // namespace // ----------------------------------------------------------------------------- @@ -134,343 +45,36 @@ void applyZDoomPerSectionOffsets( // // ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// MapRenderer3D class constructor -// ----------------------------------------------------------------------------- -MapRenderer3D::MapRenderer3D(SLADEMap* map) : map_{ map }, camera_{ new Camera() } -{ - // Build skybox circle - buildSkyCircle(); - - // Init other - init(); - - // Refresh textures when resources are updated or the main palette is changed - sc_resources_updated_ = app::resources().signals().resources_updated.connect([this] { refreshTextures(); }); - sc_palette_changed_ = theMainWindow->paletteChooser()->signals().palette_changed.connect([this] - { refreshTextures(); }); -} - -// ----------------------------------------------------------------------------- -// MapRenderer3D class destructor -// ----------------------------------------------------------------------------- -MapRenderer3D::~MapRenderer3D() -{ - if (vbo_flats_ > 0) - glDeleteBuffers(1, &vbo_flats_); - if (vbo_walls_ > 0) - glDeleteBuffers(1, &vbo_walls_); -} - -// ----------------------------------------------------------------------------- -// Initialises the 3d renderer -// ----------------------------------------------------------------------------- -bool MapRenderer3D::init() -{ - // Init camera - auto bbox = map_->bounds(); - camera_->set( - { bbox.min.x + (bbox.max.x - bbox.min.x) * 0.5, bbox.min.y + (bbox.max.y - bbox.min.y) * 0.5, 64 }, { 0, 1 }); - - refresh(); - - return true; -} - -// ----------------------------------------------------------------------------- -// Clears VBOs and cached data -// ----------------------------------------------------------------------------- -void MapRenderer3D::refresh() -{ - // Clear any existing map data - dist_sectors_.clear(); - quads_.clear(); - flats_.clear(); - - // Clear VBOs - if (vbo_flats_ != 0) - { - glDeleteBuffers(1, &vbo_flats_); - vbo_flats_ = 0; - } - - sector_flats_.clear(); - - // Set sky texture - auto minf = game::configuration().mapInfo(map_->mapName()); - skytex1_ = minf.sky1; - skytex2_ = minf.sky2; - skycol_top_.a = 0; -} - -// ----------------------------------------------------------------------------- -// Clears texture related data -// ----------------------------------------------------------------------------- -void MapRenderer3D::refreshTextures() -{ - // Refresh lines - for (auto& line : lines_) - { - for (auto& quad : line.quads) - quad.texture = 0; - - line.updated_time = 0; - } - - // Refresh flats - for (auto& sector : sector_flats_) - { - for (auto& flat : sector) - { - flat.texture = 0; - flat.updated_time = 0; - } - } - - // Refresh things - for (auto& thing : things_) - { - thing.sprite = 0; - thing.updated_time = 0; - } -} - -// ----------------------------------------------------------------------------- -// Clears all cached rendering data -// ----------------------------------------------------------------------------- -void MapRenderer3D::clearData() -{ - // Clear map structures - lines_.clear(); - things_.clear(); - sector_flats_.clear(); - - // Clear everything else - refresh(); -} - -// ----------------------------------------------------------------------------- -// Generates the array of points (circular) used for the sky -// ----------------------------------------------------------------------------- -void MapRenderer3D::buildSkyCircle() -{ - double rot = 0; - for (auto& pos : sky_circle_) - { - pos = { sin(rot), -cos(rot) }; - rot -= (math::PI * 2) / 32.0; - } -} - -// ----------------------------------------------------------------------------- -// Returns the wall quad for wall selection [item] -// ----------------------------------------------------------------------------- -MapRenderer3D::Quad* MapRenderer3D::getQuad(mapeditor::Item item) -{ - // Check item type - if (item.type != mapeditor::ItemType::WallBottom && item.type != mapeditor::ItemType::WallMiddle - && item.type != mapeditor::ItemType::WallTop) - return nullptr; - - // Get side - auto side = map_->side(item.real_index >= 0 ? item.real_index : item.index); - if (!side) - return nullptr; - - // Find matching quad - int line = side->parentLine()->index(); - for (unsigned a = 0; a < lines_[line].quads.size(); a++) - { - Quad* quad = &lines_[line].quads[a]; - - // Check side - if (side == side->parentLine()->s1() && quad->flags & BACK) - continue; - if (side == side->parentLine()->s2() && (quad->flags & BACK) == 0) - continue; - - // Check 3D floor - if (item.real_index >= 0) - { - for (unsigned m = a; m < lines_[line].quads.size(); m++) - { - if (lines_[line].quads[m].control_line == item.control_line) - return &lines_[line].quads[m]; - } - } - - // Check part - if (item.type == mapeditor::ItemType::WallBottom) - { - if (quad->flags & LOWER) - return quad; - } - if (item.type == mapeditor::ItemType::WallTop) - { - if (quad->flags & UPPER) - return quad; - } - if (item.type == mapeditor::ItemType::WallMiddle) - { - if ((quad->flags & UPPER) == 0 && (quad->flags & LOWER) == 0) - return quad; - } - } - - // Not found - return nullptr; -} - -// ----------------------------------------------------------------------------- -// Returns the flat for sector flat selection [item] -// ----------------------------------------------------------------------------- -MapRenderer3D::Flat* MapRenderer3D::getFlat(mapeditor::Item item) -{ - // Check index - if (static_cast(item.index) >= sector_flats_.size()) - return nullptr; - - int index = (item.real_index == -1) ? item.index : item.real_index; - - // Floor - if (item.type == mapeditor::ItemType::Floor && index < sector_flats_.size()) - return §or_flats_[index][0]; +MapRenderer3D::MapRenderer3D(SLADEMap* map) : map_{ map }, vb_flats_{ new gl::VertexBuffer3D } {} +MapRenderer3D::~MapRenderer3D() = default; - // Ceiling - else if (item.type == mapeditor::ItemType::Ceiling && index < sector_flats_.size()) - return §or_flats_[index][1]; - - // Wrong type - else - return nullptr; -} - -// ----------------------------------------------------------------------------- -// Applies gravity to the camera -// ----------------------------------------------------------------------------- -void MapRenderer3D::cameraApplyGravity(double mult) const +bool MapRenderer3D::fogEnabled() const { - // Get current sector - auto cam2d = camera_->position().xy(); - auto sector = map_->sectors().atPos(cam2d); - if (!sector) - return; - - // Get target height from nearest floor down, including 3D floors - auto view_height = game::configuration().playerEyeHeight(); - int fheight = static_cast(sector->floor().plane.heightAt(cam2d)) + view_height; - for (const auto& extra : sector->extraFloors()) - { - // Only check solid floors - if (extra.floor_type != ExtraFloor::SOLID) - continue; - - // Allow stepping up from one 3D floor to another by the default Doom step height of 24 - auto* control_sector = map_->sector(extra.control_sector_index); - int this_height = control_sector->ceiling().plane.heightAt(cam2d) + view_height; - if (this_height <= camera_->position().z + 24 && this_height > fheight) - fheight = this_height; - } - int cheight = sector->ceiling().plane.heightAt(cam2d); - if (fheight > cheight - 4) - fheight = cheight - 4; - - // Apply to camera - camera_->applyGravity(fheight, mult); + return false; } -// ----------------------------------------------------------------------------- -// Sets up the OpenGL view/projection for rendering -// ----------------------------------------------------------------------------- -void MapRenderer3D::setupView(int width, int height) const +bool MapRenderer3D::fullbrightEnabled() const { - // Calculate aspect ratio - float aspect = (1.6f / 1.333333f) * (static_cast(width) / static_cast(height)); - float fovy = 2 * geometry::radToDeg(atan(tan(geometry::degToRad(render_fov) / 2) / aspect)); - - // Setup projection - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - float max = render_max_dist * 1.5f; - if (max < 100) - max = 20000; - gluPerspective(fovy, aspect, 0.5f, max); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - // Get camera info - auto pos = camera_->position(); - auto dir = pos + camera_->directionVector(); - auto up = camera_->upVector(); - - // Setup camera view - gluLookAt(pos.x, pos.y, pos.z, dir.x, dir.y, dir.z, up.x, up.y, up.z); + return false; } -// ----------------------------------------------------------------------------- -// Sets the OpenGL colour for rendering an object using [colour] and [light] -// level -// ----------------------------------------------------------------------------- -void MapRenderer3D::setLight(const ColRGBA& colour, uint8_t light, float alpha) const -{ - // Force 255 light in fullbright mode - if (fullbright_) - light = 255; - - // Apply brightness - else - light = math::clamp(light * render_3d_brightness, 0, 255); - - // If we have a non-coloured light, darken it a bit to - // closer resemble the software renderer light level - float mult = static_cast(light) / 255.0f; - mult *= (mult * 1.3f); - glColor4f(colour.fr() * mult, colour.fg() * mult, colour.fb() * mult, colour.fa() * alpha); -} +void MapRenderer3D::enableHilight(bool enable) {} +void MapRenderer3D::enableSelection(bool enable) {} +void MapRenderer3D::enableFog(bool enable) {} +void MapRenderer3D::enableFullbright(bool enable) {} -// ----------------------------------------------------------------------------- -// Sets the OpenGL fog for rendering an object using [fogcol] -// ----------------------------------------------------------------------------- -void MapRenderer3D::setFog(const ColRGBA& fogcol, uint8_t light) +void MapRenderer3D::render(const gl::Camera& camera) { - if (!fog_) - return; - - // Setup fog colour - GLfloat fogColor[3] = { fogcol.fr(), fogcol.fg(), fogcol.fb() }; - if (fog_colour_last_.r != fogcol.r || fog_colour_last_.g != fogcol.g || fog_colour_last_.b != fogcol.b) - { - glFogfv(GL_FOG_COLOR, fogColor); - fog_colour_last_ = fogcol; - } - - - // Setup fog depth - float depth; - - // check if fog color is default - if (!render_fog_new_formula || (fogColor[0] == 0 && fogColor[1] == 0 && fogColor[2] == 0)) + // Create default 3d shader if needed + if (!shader_3d_) { - float lm = light / 170.0f; - depth = (lm * lm * 3000.0f); + shader_3d_ = std::make_unique("map_3d"); + shader_3d_->loadResourceEntries("default3d.vert", "default3d.frag"); } - else - depth = render_fog_distance; - if (fog_depth_last_ != depth) - { - glFogf(GL_FOG_END, depth); - fog_depth_last_ = depth; - } -} + // Set ModelViewProjection matrix uniform from camera + shader_3d_->setUniform("mvp", camera.projectionMatrix() * camera.viewMatrix()); -// ----------------------------------------------------------------------------- -// Renders the map in 3d -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderMap() -{ // Setup GL stuff glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); @@ -479,2941 +83,63 @@ void MapRenderer3D::renderMap() glDepthMask(GL_TRUE); glAlphaFunc(GL_GREATER, 0.0f); - // Create flats array if needed - if (sector_flats_.size() != map_->nSectors()) - sector_flats_.resize(map_->nSectors()); - - // Create lines array if empty - if (lines_.size() != map_->nLines()) - lines_.resize(map_->nLines()); - - // Create things array if empty - if (things_.size() != map_->nThings()) - things_.resize(map_->nThings()); - - // Init VBO stuff - // if (gl::vboSupport()) - //{ - // // Check if any polygon vertex data has changed (in this case we need to refresh the entire vbo) - // bool vbo_updated = false; - // /*for (unsigned a = 0; a < map_->nSectors(); a++) - // { - // auto poly = map_->sector(a)->polygon(); - // if (poly && poly->vboUpdate() > 1) - // { - // updateFlatsVBO(); - // vbo_updated = true; - // break; - // } - // }*/ - - // // Create VBO if necessary - // if (!vbo_updated && vbo_flats_ == 0) - // updateFlatsVBO(); - - // glEnableClientState(GL_VERTEX_ARRAY); - // glEnableClientState(GL_TEXTURE_COORD_ARRAY); - //} - - // Quick distance vis check - sf::Clock clock; - quickVisDiscard(); - - // Build lists of quads and flats to render - checkVisibleFlats(); - checkVisibleQuads(); - - // Render sky - if (render_3d_sky) - renderSky(); - // gl::setColour(ColRGBA::WHITE); - - if (fog_) - { - glEnable(GL_FOG); - - glFogi(GL_FOG_MODE, GL_LINEAR); - glFogf(GL_FOG_DENSITY, 1.0f); - glFogf(GL_FOG_START, 0.0f); - - if (render_fog_quality) - glHint(GL_FOG_HINT, GL_NICEST); - else - glHint(GL_FOG_HINT, GL_FASTEST); - } - - // Render walls - renderWalls(); - - // Render flats renderFlats(); - // Render things - if (render_3d_things > 0) - renderThings(); - - // Render transparent stuff - renderTransparentWalls(); - - // Check elapsed time - if (render_max_dist_adaptive) - { - long ms = clock.getElapsedTime().asMilliseconds(); - if (ms > render_adaptive_ms) - { - render_max_dist = render_max_dist - 100; - if (render_max_dist < 1000) - render_max_dist = 1000; - } - else if (ms < render_adaptive_ms - 5) - { - render_max_dist = render_max_dist + 100; - if (render_max_dist > 20000) - render_max_dist = 20000; - } - } - // Cleanup gl state glDisable(GL_ALPHA_TEST); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); - glDisable(GL_FOG); } -// ----------------------------------------------------------------------------- -// Renders a cylindrical 'slice' of the sky between [top] and [bottom] on the z -// axis -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderSkySlice(float top, float bottom, float atop, float abottom, float size, float tx, float ty) - const +void MapRenderer3D::clearData() { - float tc_x = 0.0f; - float tc_y1 = (-top + 1.0f) * (ty * 0.5f); - float tc_y2 = (-bottom + 1.0f) * (ty * 0.5f); - - glBegin(GL_QUADS); - - // Go through circular points - const auto& cam_pos = camera_->position(); - for (unsigned a = 0; a < 31; a++) - { - // Top - glColor4f(1.0f, 1.0f, 1.0f, atop); - glTexCoord2f(tc_x + tx, tc_y1); - glVertex3f( - cam_pos.x + (sky_circle_[a + 1].x * size), - cam_pos.y - (sky_circle_[a + 1].y * size), - cam_pos.z + (top * size)); - glTexCoord2f(tc_x, tc_y1); - glVertex3f( - cam_pos.x + (sky_circle_[a].x * size), cam_pos.y - (sky_circle_[a].y * size), cam_pos.z + (top * size)); - - // Bottom - glColor4f(1.0f, 1.0f, 1.0f, abottom); - glTexCoord2f(tc_x, tc_y2); - glVertex3f( - cam_pos.x + (sky_circle_[a].x * size), cam_pos.y - (sky_circle_[a].y * size), cam_pos.z + (bottom * size)); - glTexCoord2f(tc_x + tx, tc_y2); - glVertex3f( - cam_pos.x + (sky_circle_[a + 1].x * size), - cam_pos.y - (sky_circle_[a + 1].y * size), - cam_pos.z + (bottom * size)); - - tc_x += tx; - } - - // Link last point -> first - // Top - glColor4f(1.0f, 1.0f, 1.0f, atop); - glTexCoord2f(tc_x + tx, tc_y1); - glVertex3f(cam_pos.x + (sky_circle_[0].x * size), cam_pos.y - (sky_circle_[0].y * size), cam_pos.z + (top * size)); - glTexCoord2f(tc_x, tc_y1); - glVertex3f( - cam_pos.x + (sky_circle_[31].x * size), cam_pos.y - (sky_circle_[31].y * size), cam_pos.z + (top * size)); - - // Bottom - glColor4f(1.0f, 1.0f, 1.0f, abottom); - glTexCoord2f(tc_x, tc_y2); - glVertex3f( - cam_pos.x + (sky_circle_[31].x * size), cam_pos.y - (sky_circle_[31].y * size), cam_pos.z + (bottom * size)); - glTexCoord2f(tc_x + tx, tc_y2); - glVertex3f( - cam_pos.x + (sky_circle_[0].x * size), cam_pos.y - (sky_circle_[0].y * size), cam_pos.z + (bottom * size)); - - glEnd(); + vb_flats_->buffer().clear(); + flats_.clear(); } -// ----------------------------------------------------------------------------- -// Renders the sky -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderSky() +unsigned MapRenderer3D::flatsBufferSize() const { - // gl::setColour(ColRGBA::WHITE); - glDisable(GL_CULL_FACE); - glDisable(GL_FOG); - glDisable(GL_DEPTH_TEST); - glDepthMask(GL_FALSE); - glEnable(GL_TEXTURE_2D); - - // Center skybox a bit below the camera view - glPushMatrix(); - glTranslatef(0.0f, 0.0f, -10.0f); - - // Get sky texture - unsigned sky; - if (!skytex2_.empty()) - sky = mapeditor::textureManager().texture(skytex2_, false).gl_id; - else - sky = mapeditor::textureManager().texture(skytex1_, false).gl_id; - if (sky) - { - // Bind texture - gl::Texture::bind(sky); - - // Get average colour if needed - auto& tex_info = gl::Texture::info(sky); - if (skycol_top_.a == 0) - { - int theight = tex_info.size.y * 0.4; - skycol_top_ = gl::Texture::averageColour(sky, { 0, 0, tex_info.size.x, theight }); - skycol_bottom_ = gl::Texture::averageColour( - sky, { 0, tex_info.size.y - theight, tex_info.size.x, tex_info.size.y }); - } - - // Render top cap - const auto& cam_pos = camera_->position(); - float size = 64.0f; - glDisable(GL_TEXTURE_2D); - // gl::setColour(skycol_top_); - glBegin(GL_QUADS); - glVertex3f(cam_pos.x - (size * 10), cam_pos.y - (size * 10), cam_pos.z + size); - glVertex3f(cam_pos.x - (size * 10), cam_pos.y + (size * 10), cam_pos.z + size); - glVertex3f(cam_pos.x + (size * 10), cam_pos.y + (size * 10), cam_pos.z + size); - glVertex3f(cam_pos.x + (size * 10), cam_pos.y - (size * 10), cam_pos.z + size); - glEnd(); - - // Render bottom cap - // gl::setColour(skycol_bottom_); - glBegin(GL_QUADS); - glVertex3f(cam_pos.x - (size * 10), cam_pos.y - (size * 10), cam_pos.z - size); - glVertex3f(cam_pos.x - (size * 10), cam_pos.y + (size * 10), cam_pos.z - size); - glVertex3f(cam_pos.x + (size * 10), cam_pos.y + (size * 10), cam_pos.z - size); - glVertex3f(cam_pos.x + (size * 10), cam_pos.y - (size * 10), cam_pos.z - size); - glEnd(); - - // Render skybox sides - glDisable(GL_ALPHA_TEST); - glEnable(GL_TEXTURE_2D); - - // Check for odd sky sizes - float tx = 0.125f; - float ty = 2.0f; - if (tex_info.size.x > 256) - tx = 0.125f / (static_cast(tex_info.size.x) / 256.0f); - if (tex_info.size.y > 128) - ty = 1.0f; - - renderSkySlice(1.0f, 0.5f, 0.0f, 1.0f, size, tx, ty); // Top - renderSkySlice(0.5f, -0.5f, 1.0f, 1.0f, size, tx, ty); // Middle - renderSkySlice(-0.5f, -1.0f, 1.0f, 0.0f, size, tx, ty); // Bottom - } - - glPopMatrix(); - glDepthMask(GL_TRUE); - glEnable(GL_CULL_FACE); - glEnable(GL_DEPTH_TEST); - glEnable(GL_ALPHA_TEST); - - // Render all sky quads - glDisable(GL_TEXTURE_2D); - for (unsigned a = 0; a < n_quads_; a++) - { - // Ignore if not sky - if ((quads_[a]->flags & SKY) == 0) - continue; - - // Render quad - renderQuad(quads_[a]); - quads_[a] = quads_[n_quads_ - 1]; - n_quads_--; - a--; - } - - // Render all sky flats - flat_last_ = 0; - for (unsigned a = 0; a < n_flats_; a++) - { - if (!flats_[a]) - continue; - - // Ignore if not sky - if ((flats_[a]->flags & SKY) == 0) - continue; - - // Render quad - renderFlat(flats_[a]); - flats_[a] = flats_[n_flats_ - 1]; - n_flats_--; - a--; - } - glEnable(GL_TEXTURE_2D); + return vb_flats_->buffer().size() * (sizeof(gl::Vertex3D) + sizeof(glm::vec4)); } -// ----------------------------------------------------------------------------- -// Updates the vertex texture coordinates of all polygons for sector [index] -// ----------------------------------------------------------------------------- -void MapRenderer3D::updateFlatTexCoords(unsigned index, unsigned flat_index) const +void MapRenderer3D::renderFlats() { - using game::UDMFFeature; - - // Check index - if (index >= map_->nSectors()) - return; - - // Get sector - auto sector = map_->sector(index); - - // Get scaling/offset info - double ox = 0.; - double oy = 0.; - // TODO 3dfloors - double sx = sector_flats_[index][flat_index].scale.x; - double sy = sector_flats_[index][flat_index].scale.y; - double rot = 0.; - - // Check for UDMF + panning/scaling/rotation - if (mapeditor::editContext().mapDesc().format == MapFormat::UDMF) + // Clear flats to be rebuilt if map geometry has been updated + if (map_->geometryUpdated() > flats_updated_) { - if (!(sector_flats_[index][flat_index].flags & CEIL)) - { - if (game::configuration().featureSupported(UDMFFeature::FlatPanning)) - { - ox = sector->floatProperty("xpanningfloor"); - oy = sector->floatProperty("ypanningfloor"); - } - if (game::configuration().featureSupported(UDMFFeature::FlatScaling)) - { - sx *= (1.0 / sector->floatProperty("xscalefloor")); - sy *= (1.0 / sector->floatProperty("yscalefloor")); - } - if (game::configuration().featureSupported(UDMFFeature::FlatRotation)) - rot = sector->floatProperty("rotationfloor"); - } - else - { - if (game::configuration().featureSupported(UDMFFeature::FlatPanning)) - { - ox = sector->floatProperty("xpanningceiling"); - oy = sector->floatProperty("ypanningceiling"); - } - if (game::configuration().featureSupported(UDMFFeature::FlatScaling)) - { - sx *= (1.0 / sector->floatProperty("xscaleceiling")); - sy *= (1.0 / sector->floatProperty("yscaleceiling")); - } - if (game::configuration().featureSupported(UDMFFeature::FlatRotation)) - rot = sector->floatProperty("rotationceiling"); - } + vb_flats_->buffer().clear(); + flats_.clear(); } - // Scaling applies to offsets as well. - // Note for posterity: worldpanning only applies to textures, not flats - ox /= sx; - oy /= sy; - - // Update polygon texture coordinates - // sector->polygon()->setTexture(sector_flats_[index][flat_index].texture); - // sector->polygon()->updateTextureCoords(sx, sy, ox, oy, rot); -} - -// ----------------------------------------------------------------------------- -// Updates cached rendering data for sector [index] -// ----------------------------------------------------------------------------- -void MapRenderer3D::updateSector(unsigned index) -{ - updateSectorFlats(index); - updateSectorVBOs(index); -} - -// ----------------------------------------------------------------------------- -// Updates flat structures for sector [index] -// ----------------------------------------------------------------------------- -void MapRenderer3D::updateSectorFlats(unsigned index) -{ - // Check index - if (index >= map_->nSectors()) - return; - - auto* sector = map_->sector(index); - sector_flats_[index].resize(2 * (1 + sector->extraFloors().size())); + // Generate flats if needed + if (flats_.empty()) + { + unsigned vertex_index = 0; + for (auto* sector : map_->sectors()) + { + auto [flats, vertices] = mapeditor::generateSectorFlats(*sector, vertex_index); - // Update floor - bool mix_tex_flats = game::configuration().featureSupported(game::Feature::MixTexFlats); - Flat& floor_flat = sector_flats_[index][0]; - auto& ftex = mapeditor::textureManager().flat(sector->floor().texture, mix_tex_flats); - floor_flat.sector = sector; - floor_flat.control_sector = sector; - floor_flat.extra_floor_index = -1; - floor_flat.texture = ftex.gl_id; - floor_flat.scale = ftex.scale; - floor_flat.colour = sector->colourAt(1, true); - floor_flat.fogcolour = sector->fogColour(); - floor_flat.light = sector->lightAt(1); - floor_flat.flags = 0; - floor_flat.plane = sector->floor().plane; - floor_flat.base_alpha = 1.0f; - if (strutil::equalCI(sector->floor().texture, game::configuration().skyFlat())) - floor_flat.flags |= SKY; + vectorConcat(flats_, flats); + vb_flats_->add(vertices); + vertex_index += vertices.size(); + } - // Update ceiling - Flat& ceiling_flat = sector_flats_[index][1]; - auto& ctex = mapeditor::textureManager().flat(sector->ceiling().texture, mix_tex_flats); - ceiling_flat.sector = sector; - ceiling_flat.control_sector = sector; - ceiling_flat.extra_floor_index = -1; - ceiling_flat.texture = ctex.gl_id; - ceiling_flat.scale = ctex.scale; - ceiling_flat.colour = sector->colourAt(2, true); - ceiling_flat.fogcolour = sector->fogColour(); - ceiling_flat.light = sector->lightAt(2); - ceiling_flat.flags = CEIL; - ceiling_flat.plane = sector->ceiling().plane; - ceiling_flat.base_alpha = 1.0f; - if (strutil::equalCI(sector->ceiling().texture, game::configuration().skyFlat())) - ceiling_flat.flags |= SKY; + vb_flats_->push(); + flats_updated_ = app::runTimer(); + } - // Deal with 3D floors - for (unsigned a = 0; a < sector->extraFloors().size(); a++) + // Render flats + for (auto& flat : flats_) { - auto& extra = sector->extraFloors()[a]; - MapSector* control_sector = map_->sector(extra.control_sector_index); - - // TODO which of these is really a floor and which is really a ceiling? - // TODO BOTH sides of the inner flat are drawn sometimes! - // Bottom plane (defined by the floor of the control sector, but acts - // like a ceiling) - Flat& xf_floor = sector_flats_[index][2 * (a + 1)]; - auto& xftex = mapeditor::textureManager().flat(control_sector->floor().texture, mix_tex_flats); - xf_floor.sector = sector; - xf_floor.control_sector = control_sector; - xf_floor.extra_floor_index = a; - xf_floor.texture = xftex.gl_id; - xf_floor.scale = xftex.scale; - // TODO wrong. maybe? does it inherit from parent? - xf_floor.colour = control_sector->colourAt(1, true); - // TODO 3d floors have no fog color... right? - xf_floor.fogcolour = control_sector->fogColour(); - // TODO oughta support screen blends too!! - // TODO this probably comes from the control sector, unless there's a flag, yadda... - // TODO more importantly, it propagates downwards to the next floor - // TODO are the light levels of the inside and outside different? christ - // TODO shouldn't this be 1? - xf_floor.light = sector->lightAt(1, a); - xf_floor.flags = FLATFLIP; - if (extra.draw_inside) - xf_floor.flags |= DRAWBOTH; - xf_floor.plane = extra.floor_plane; - xf_floor.base_alpha = extra.alpha; - - if (extra.additiveTransparency()) + if (flat.updated_time < flat.sector->modifiedTime()) { - xf_floor.flags |= TRANSADD; + vector vertices; + mapeditor::updateFlat(flat, vertices); + vb_flats_->buffer().update(flat.vertex_offset, vertices); } - // Top plane (defined by the ceiling of the control sector, but acts - // like a floor) - Flat& xf_ceiling = sector_flats_[index][2 * (a + 1) + 1]; - auto& xctex = mapeditor::textureManager().flat(control_sector->ceiling().texture, mix_tex_flats); - xf_ceiling.sector = sector; - xf_ceiling.control_sector = control_sector; - xf_ceiling.extra_floor_index = a; - xf_ceiling.texture = xctex.gl_id; - xf_ceiling.scale = xctex.scale; - // TODO chump hack to use the real sector's light, which is wrong; fix the method to take this into account - xf_ceiling.colour = sector->colourAt(2, true); - // TODO again, maybe? - xf_ceiling.fogcolour = control_sector->fogColour(); - // TODO this probably comes from the control sector, unless there's a flag, yadda... - xf_ceiling.light = sector->lightAt(2, a); - xf_ceiling.flags = CEIL | FLATFLIP; - if (extra.draw_inside) - xf_ceiling.flags |= DRAWBOTH; - xf_ceiling.plane = extra.ceiling_plane; - xf_ceiling.base_alpha = extra.alpha; - - if (extra.additiveTransparency()) - { - xf_ceiling.flags |= TRANSADD; - } - } - - // Finish up - for (auto& flat : sector_flats_[index]) - flat.updated_time = app::runTimer(); - - // if (gl::vboSupport()) - //{ - // glBindBuffer(GL_ARRAY_BUFFER, 0); - // sector->polygon()->setZ(0); - // } -} - -// ----------------------------------------------------------------------------- -// Updates VBOs for sector [index] -// ----------------------------------------------------------------------------- -void MapRenderer3D::updateSectorVBOs(unsigned index) const -{ - // if (!gl::vboSupport()) - // return; - - //// Check index - // if (index >= map_->nSectors()) - // return; - // MapSector* sector = map_->sector(index); - // Polygon2D* poly = sector->polygon(); - - //// Update VBOs - // glBindBuffer(GL_ARRAY_BUFFER, vbo_flats_); - // Polygon2D::setupVBOPointers(); - - // for (unsigned a = 0; a < sector_flats_[index].size(); a++) - //{ - // updateFlatTexCoords(index, a); - // poly->setZ(sector_flats_[index][a].plane); - // poly->writeToVBO(sector_flats_[index][a].vbo_offset); - // } - - // glBindBuffer(GL_ARRAY_BUFFER, 0); - // poly->setZ(0); -} - -// ----------------------------------------------------------------------------- -// Returns whether the sector at [index] needs to be updated. -// ----------------------------------------------------------------------------- -bool MapRenderer3D::isSectorStale(unsigned index) const -{ - MapSector* sector = map_->sector(index); - if (!sector) - return false; - - return sector_flats_[index].empty() || sector_flats_[index][0].updated_time < sector->modifiedTime() - || sector_flats_[index][0].updated_time < sector->geometryUpdatedTime(); -} - -// ----------------------------------------------------------------------------- -// Renders [flat] -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderFlat(const Flat* flat) -{ - // Skip if no sector (for whatever reason) - if (!flat->sector) - return; - - // Setup special rendering options - float alpha = flat->alpha * flat->base_alpha; - if (flat->flags & SKY && render_3d_sky) - { - alpha = 0; - glDisable(GL_ALPHA_TEST); - } - if (flat->flags & TRANSADD) - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - else - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Setup colour/light - setLight(flat->colour, flat->light, alpha); - - // Setup fog colour - setFog(flat->fogcolour, flat->light); - - // Render flat - if (true) // gl::vboSupport() && flats_use_vbo) - { - // Setup for floor or ceiling - if (flat->flags & CEIL) - { - glCullFace((flat->flags & FLATFLIP) ? GL_FRONT : GL_BACK); - flat_last_ = 2; - } - else - { - glCullFace((flat->flags & FLATFLIP) ? GL_BACK : GL_FRONT); - flat_last_ = 1; - } - - // glBindBuffer(GL_ARRAY_BUFFER, vbo_flats_); - // Polygon2D::setupVBOPointers(); - - if (flat->flags & DRAWBOTH) - glDisable(GL_CULL_FACE); - - // Render - // flat->sector->polygon()->renderVBO(flat->vbo_offset); - - if (flat->flags & DRAWBOTH) - glEnable(GL_CULL_FACE); - } - else - { - glPushMatrix(); - - // Setup for floor or ceiling - if (flat->flags & CEIL) - { - glCullFace((flat->flags & FLATFLIP) ? GL_FRONT : GL_BACK); - glTranslated(0, 0, flat->sector->ceiling().height); - } - else - { - glCullFace((flat->flags & FLATFLIP) ? GL_BACK : GL_FRONT); - glTranslated(0, 0, flat->sector->floor().height); - } - - if (flat->flags & DRAWBOTH) - glDisable(GL_CULL_FACE); - - // Render - // flat->sector->polygon()->render(); - - if (flat->flags & DRAWBOTH) - glEnable(GL_CULL_FACE); - - glPopMatrix(); - } - - // Reset settings - if (flat->flags & SKY && render_3d_sky) - glEnable(GL_ALPHA_TEST); -} - -// ----------------------------------------------------------------------------- -// Renders all currently visible flats -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderFlats() -{ - // Check for map - if (!map_) - return; - - // Init textures - glEnable(GL_TEXTURE_2D); - - // Render all visible opaque flats, ordered by texture - unsigned a = 0; - unsigned tex_last = 0; - flat_last_ = 0; - while (n_flats_ > 0) - { - a = 0; - tex_last = 0; - while (a < n_flats_) - { - if (flats_[a]) - { - // Skip different textures on this loop, and save all translucent - // flats for last - if ((tex_last && flats_[a]->texture != tex_last) || flats_[a]->base_alpha < 1.0f) - { - a++; - continue; - } - - // Check texture - if (!tex_last) - tex_last = flats_[a]->texture; - if (tex_last) - gl::Texture::bind(flats_[a]->texture); - - // Render flat - renderFlat(flats_[a]); - } - - flats_[a] = flats_[--n_flats_]; - } - if (!tex_last) - break; - } - - // Render any remaining translucent flats, ordered by depth - // TODO order by depth? how?? sector distance, then plane height? (note - // that sloped and translucent is forbidden even in gzdoom) - for (unsigned b = 0; b < n_flats_; b++) - { - if (!flats_[b]) - continue; - - if (tex_last != flats_[b]->texture) - { - tex_last = flats_[b]->texture; - gl::Texture::bind(tex_last); - } - renderFlat(flats_[b]); - } - n_flats_ = 0; - - // Reset gl stuff - glDisable(GL_TEXTURE_2D); - if (true) // gl::vboSupport()) - { - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glBindBuffer(GL_ARRAY_BUFFER, 0); - } -} - -// ----------------------------------------------------------------------------- -// Renders selection overlay for all selected flats -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderFlatSelection(const ItemSelection& selection, float alpha) const -{ - if (!render_selection_) - return; - - // Setup gl stuff - glLineWidth(2.0f); - glDisable(GL_TEXTURE_2D); - glDisable(GL_FOG); - glDisable(GL_DEPTH_TEST); - glEnable(GL_LINE_SMOOTH); - glEnable(GL_CULL_FACE); - - // Setup colour - auto& def = colourconfig::colDef("map_3d_selection"); - auto col1 = def.colour; - col1.a *= alpha; - // gl::setColour(col1, def.blendMode()); - auto col2 = col1; - col2.a *= 0.5; - - // Go through selection - for (auto& item : selection) - { - // TODO this code is awfully similar to the renderHilight code - // Ignore if not a sector hilight - if (item.type != mapeditor::ItemType::Ceiling && item.type != mapeditor::ItemType::Floor) - continue; - - // Get sector - auto sector = map_->sector(item.real_index >= 0 ? item.real_index : item.index); - if (!sector) - return; - - // Get plane - Plane plane; - if (item.real_index >= 0) - { - for (const auto& extra : sector->extraFloors()) - { - if (extra.control_sector_index == item.index) - { - if (item.type == mapeditor::ItemType::Floor) - plane = extra.floor_plane; - else - plane = extra.ceiling_plane; - } - } - } - else - { - if (item.type == mapeditor::ItemType::Floor) - plane = sector->floor().plane; - else - plane = sector->ceiling().plane; - } - - // Draw sector outline - vector lines; - sector->putLines(lines); - // gl::setColour(col1); - glBegin(GL_LINES); - for (auto& line : lines) - { - glVertex3d(line->x1(), line->y1(), plane.heightAt(line->x1(), line->y1())); - glVertex3d(line->x2(), line->y2(), plane.heightAt(line->x2(), line->y2())); - } - glEnd(); - - // Render fill - // gl::setColour(col2); - glDisable(GL_CULL_FACE); - // sector->polygon()->setZ(plane); - // sector->polygon()->render(); - // sector->polygon()->setZ(0); - glEnable(GL_CULL_FACE); - } - - glCullFace(GL_BACK); -} - -// ----------------------------------------------------------------------------- -// Sets up coordinates for a quad -// ----------------------------------------------------------------------------- -void MapRenderer3D::setupQuad(Quad* quad, const Seg2d& seg, double top, double bottom) const -{ - // Left - quad->points[0].x = quad->points[1].x = seg.x1(); - quad->points[0].y = quad->points[1].y = seg.y1(); - - // Right - quad->points[2].x = quad->points[3].x = seg.x2(); - quad->points[2].y = quad->points[3].y = seg.y2(); - - // Top/bottom - quad->points[0].z = quad->points[3].z = top; - quad->points[1].z = quad->points[2].z = bottom; -} - -// ----------------------------------------------------------------------------- -// Sets up coordinates for a quad -// ----------------------------------------------------------------------------- -void MapRenderer3D::setupQuad(Quad* quad, const Seg2d& seg, const Plane& top, const Plane& bottom) const -{ - // Left - quad->points[0].x = quad->points[1].x = seg.x1(); - quad->points[0].y = quad->points[1].y = seg.y1(); - - // Right - quad->points[2].x = quad->points[3].x = seg.x2(); - quad->points[2].y = quad->points[3].y = seg.y2(); - - // Top/bottom - quad->points[0].z = top.heightAt(quad->points[0].x, quad->points[0].y); - quad->points[1].z = bottom.heightAt(quad->points[1].x, quad->points[1].y); - quad->points[2].z = bottom.heightAt(quad->points[2].x, quad->points[2].y); - quad->points[3].z = top.heightAt(quad->points[3].x, quad->points[3].y); -} - -// ----------------------------------------------------------------------------- -// Calculates texture coordinates for a quad -// ----------------------------------------------------------------------------- -void MapRenderer3D::setupQuadTexCoords( - MapRenderer3D::Quad* quad, - int length, - double o_left, - double o_top, - double h_top, - double h_bottom, - bool pegbottom, - double sx, - double sy) const -{ - // Check texture - if (!quad->texture) - return; - - // Determine integral height - int height = math::round(h_top - h_bottom); - - // Initial offsets - double y1 = o_top; - double y2 = o_top + height; - auto& tex_info = gl::Texture::info(quad->texture); - if (pegbottom) - { - y2 = o_top + tex_info.size.y * sy; - y1 = y2 - height; - } - - double x_mult = 1.0 / (tex_info.size.x * sx); - double y_mult = 1.0 / (tex_info.size.y * sy); - - // Set texture coordinates - quad->points[0].tx = o_left * x_mult; - quad->points[0].ty = (y1 * y_mult) + ((h_top - quad->points[0].z) * y_mult); - quad->points[1].tx = o_left * x_mult; - quad->points[1].ty = (y2 * y_mult) + ((h_bottom - quad->points[1].z) * y_mult); - quad->points[2].tx = (o_left + length) * x_mult; - quad->points[2].ty = (y2 * y_mult) + ((h_bottom - quad->points[2].z) * y_mult); - quad->points[3].tx = (o_left + length) * x_mult; - quad->points[3].ty = (y1 * y_mult) + ((h_top - quad->points[3].z) * y_mult); -} - -// ----------------------------------------------------------------------------- -// Updates cached rendering data for line [index] -// ----------------------------------------------------------------------------- -void MapRenderer3D::updateLine(unsigned index) -{ - using game::Feature; - using game::UDMFFeature; - - // Check index - if (index > lines_.size()) - return; - - // Clear current line data - lines_[index].quads.clear(); - - // Skip invalid line - auto line = map_->line(index); - if (!line->s1()) - return; - - // Process line special - map_->mapSpecials()->processLineSpecial(line); - bool line_translucent = map_->mapSpecials()->lineIsTranslucent(line); - - // Get relevant line info - auto map_format = mapeditor::editContext().mapDesc().format; - bool upeg = game::configuration().lineBasicFlagSet("dontpegtop", line, map_format); - bool lpeg = game::configuration().lineBasicFlagSet("dontpegbottom", line, map_format); - double xoff, yoff, sx, sy, lsx, lsy; - bool mixed = game::configuration().featureSupported(Feature::MixTexFlats); - lines_[index].line = line; - double alpha = 1.0; - if (line->hasProp("alpha")) - alpha = line->floatProperty("alpha"); - else if (line_translucent) // TranslucentLine special - alpha = map_->mapSpecials()->translucentLineAlpha(line); - - int line_shading = 0; - if (render_shade_orthogonal_lines) - { - // Increase light level for N/S facing lines - if (line->x1() == line->x2()) - line_shading = +16; - // Decrease light level for E/W facing lines - else if (line->y1() == line->y2()) - line_shading = -16; - } - - // Get first side info - int floor1 = line->frontSector()->floor().height; - int ceiling1 = line->frontSector()->ceiling().height; - auto fp1 = line->frontSector()->floor().plane; - auto cp1 = line->frontSector()->ceiling().plane; - auto colour1 = line->frontSector()->colourAt(0, true); - auto fogcolour1 = line->frontSector()->fogColour(); - auto light1 = line->s1()->light(); - int xoff1 = line->s1()->texOffsetX(); - int yoff1 = line->s1()->texOffsetY(); - - if (render_shade_orthogonal_lines) - { - colour1.r = math::clamp(colour1.r + line_shading, 0, 255); - colour1.g = math::clamp(colour1.g + line_shading, 0, 255); - colour1.b = math::clamp(colour1.b + line_shading, 0, 255); - light1 = math::clamp(light1 + line_shading, 0, 255); - } - - // --- One-sided line --- - lsx = 1; - lsy = 1; - int length = math::round(line->length()); - if (line->s1() && !line->s2()) - { - Quad quad; - - // Determine offsets - xoff = xoff1; - yoff = yoff1; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureOffsets)) - { - if (line->s1()->hasProp("offsetx_mid")) - xoff += line->s1()->floatProperty("offsetx_mid"); - if (line->s1()->hasProp("offsety_mid")) - yoff += line->s1()->floatProperty("offsety_mid"); - } - - // Texture scale - auto& tex = mapeditor::textureManager().texture(line->s1()->texMiddle(), mixed); - quad.texture = tex.gl_id; - sx = tex.scale.x; - sy = tex.scale.y; - if (game::configuration().featureSupported(UDMFFeature::TextureScaling)) - { - if (line->s1()->hasProp("scalex_mid")) - lsx = 1.0 / line->s1()->floatProperty("scalex_mid"); - if (line->s1()->hasProp("scaley_mid")) - lsy = 1.0 / line->s1()->floatProperty("scaley_mid"); - } - if (!tex.world_panning) - { - xoff *= fabs(sx); - yoff *= fabs(sy); - } - sx *= lsx; - sy *= lsy; - xoff *= fabs(lsx); - yoff *= fabs(lsy); - - // Create quad - setupQuad(&quad, line->seg(), cp1, fp1); - quad.colour = colour1; - quad.fogcolour = fogcolour1; - quad.light = light1; - setupQuadTexCoords(&quad, length, xoff, yoff, ceiling1, floor1, lpeg, sx, sy); - - // Add middle quad and finish - lines_[index].quads.push_back(quad); - lines_[index].updated_time = app::runTimer(); - return; - } - - // --- Two-sided line --- - - // TODO middle parts may show 3D floors! - - // Get second side info - int floor2 = line->backSector()->floor().height; - int ceiling2 = line->backSector()->ceiling().height; - auto fp2 = line->backSector()->floor().plane; - auto cp2 = line->backSector()->ceiling().plane; - auto colour2 = line->backSector()->colourAt(0, true); - auto fogcolour2 = line->backSector()->fogColour(); - auto light2 = line->s2()->light(); - int xoff2 = line->s2()->texOffsetX(); - int yoff2 = line->s2()->texOffsetY(); - int lowceil = glm::min(ceiling1, ceiling2); - int highfloor = glm::max(floor1, floor2); - string sky_flat = game::configuration().skyFlat(); - string hidden_tex = map_->currentFormat() == MapFormat::Doom64 ? "?" : "-"; - bool show_midtex = (map_->currentFormat() != MapFormat::Doom64) || (line->flagSet(512)); - // Heights at both endpoints, for both planes, on both sides - double f1h1 = fp1.heightAt(line->x1(), line->y1()); - double f1h2 = fp1.heightAt(line->x2(), line->y2()); - double f2h1 = fp2.heightAt(line->x1(), line->y1()); - double f2h2 = fp2.heightAt(line->x2(), line->y2()); - double c1h1 = cp1.heightAt(line->x1(), line->y1()); - double c1h2 = cp1.heightAt(line->x2(), line->y2()); - double c2h1 = cp2.heightAt(line->x1(), line->y1()); - double c2h2 = cp2.heightAt(line->x2(), line->y2()); - - if (render_shade_orthogonal_lines) - { - colour2.r = math::clamp(colour2.r + line_shading, 0, 255); - colour2.g = math::clamp(colour2.g + line_shading, 0, 255); - colour2.b = math::clamp(colour2.b + line_shading, 0, 255); - light2 = math::clamp(light2 + line_shading, 0, 255); - } - - // Front lower - lsx = 1; - lsy = 1; - if (f2h1 > f1h1 || f2h2 > f1h2) - { - Quad quad; - - // Determine offsets - xoff = xoff1; - yoff = yoff1; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureOffsets)) - { - // UDMF extra offsets - if (line->s1()->hasProp("offsetx_bottom")) - xoff += line->s1()->floatProperty("offsetx_bottom"); - if (line->s1()->hasProp("offsety_bottom")) - yoff += line->s1()->floatProperty("offsety_bottom"); - } - - // Texture scale - auto& tex = mapeditor::textureManager().texture(line->s1()->texLower(), mixed); - quad.texture = tex.gl_id; - sx = tex.scale.x; - sy = tex.scale.y; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureScaling)) - { - if (line->s1()->hasProp("scalex_bottom")) - lsx = 1.0 / line->s1()->floatProperty("scalex_bottom"); - if (line->s1()->hasProp("scaley_bottom")) - lsy = 1.0 / line->s1()->floatProperty("scaley_bottom"); - } - if (!tex.world_panning) - { - xoff *= fabs(sx); - yoff *= fabs(sy); - } - sx *= lsx; - sy *= lsy; - xoff *= fabs(lsx); - yoff *= fabs(lsy); - - if (lpeg) // Lower unpegged - yoff += (ceiling1 - floor2); - - // Create quad - setupQuad(&quad, line->seg(), fp2, fp1); - quad.colour = colour1; - quad.fogcolour = fogcolour1; - quad.light = light1; - setupQuadTexCoords(&quad, length, xoff, yoff, floor2, floor1, false, sx, sy); - quad.flags |= LOWER; - - // Add quad - lines_[index].quads.push_back(quad); - } - - // Front middle - lsx = 1; - lsy = 1; - auto midtex1 = line->stringProperty("side1.texturemiddle"); - if (!midtex1.empty() && midtex1 != hidden_tex && show_midtex) - { - Quad quad; - - // Get texture - auto& tex = mapeditor::textureManager().texture(line->s1()->texMiddle(), mixed); - quad.texture = tex.gl_id; - - // Determine offsets - xoff = xoff1; - yoff = yoff1; - double ytex = 0; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureOffsets)) - { - if (line->s1()->hasProp("offsetx_mid")) - xoff += line->s1()->floatProperty("offsetx_mid"); - if (line->s1()->hasProp("offsety_mid")) - yoff += line->s1()->floatProperty("offsety_mid"); - } - - // Texture scale - sx = tex.scale.x; - sy = tex.scale.y; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureScaling)) - { - if (line->s1()->hasProp("scalex_mid")) - lsx = 1.0 / line->s1()->floatProperty("scalex_mid"); - if (line->s1()->hasProp("scaley_mid")) - lsy = 1.0 / line->s1()->floatProperty("scaley_mid"); - } - if (!tex.world_panning) - { - xoff *= fabs(sx); - yoff *= fabs(sy); - } - sx *= lsx; - sy *= lsy; - xoff *= fabs(lsx); - yoff *= fabs(lsy); - - // Setup quad coordinates - double top, bottom; - if ((map_->currentFormat() == MapFormat::Doom64) - || (( - map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::SideMidtexWrapping) - && line->boolProperty("wrapmidtex")))) - { - top = lowceil; - bottom = highfloor; - ytex = yoff; - if (lpeg) - ytex -= lowceil - highfloor; - } - else if (lpeg) - { - auto& tex_info = gl::Texture::info(quad.texture); - bottom = highfloor + yoff; - top = bottom + (tex_info.size.y * sy); - } - else - { - auto& tex_info = gl::Texture::info(quad.texture); - top = lowceil + yoff; - bottom = top - (tex_info.size.y * sy); - } - // The "correct" thing here is to allow textures to run into the floor - // unless clipmidtex is on, but OpenGL is designed not to allow that to - // happen, and GZDoom doesn't support it either. - if (bottom < highfloor) - bottom = highfloor; - if (top > lowceil) - { - ytex = top - lowceil; - top = lowceil; - } - - // Create quad - setupQuad(&quad, line->seg(), top, bottom); - quad.colour = colour1.ampf(1.0f, 1.0f, 1.0f, alpha); - quad.fogcolour = fogcolour1; - quad.light = light1; - setupQuadTexCoords(&quad, length, xoff, ytex, top, bottom, false, sx, sy); - quad.flags |= MIDTEX; - if (line->hasProp("renderstyle") && line->stringProperty("renderstyle") == "add") - quad.flags |= TRANSADD; - else if (line_translucent && map_->mapSpecials()->translucentLineAdditive(line)) // TranslucentLine special - quad.flags |= TRANSADD; - - // Add quad - lines_[index].quads.push_back(quad); - } - - // Front upper - lsx = 1; - lsy = 1; - if (c1h1 > c2h1 || c1h2 > c2h2) - { - Quad quad; - - // Determine offsets - xoff = xoff1; - yoff = yoff1; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureOffsets)) - { - // UDMF extra offsets - if (line->s1()->hasProp("offsetx_top")) - xoff += line->s1()->floatProperty("offsetx_top"); - if (line->s1()->hasProp("offsety_top")) - yoff += line->s1()->floatProperty("offsety_top"); - } - - // Texture scale - auto& tex = mapeditor::textureManager().texture(line->s1()->texUpper(), mixed); - quad.texture = tex.gl_id; - sx = tex.scale.x; - sy = tex.scale.y; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureScaling)) - { - if (line->s1()->hasProp("scalex_top")) - lsx = 1.0 / line->s1()->floatProperty("scalex_top"); - if (line->s1()->hasProp("scaley_top")) - lsy = 1.0 / line->s1()->floatProperty("scaley_top"); - } - if (!tex.world_panning) - { - xoff *= fabs(sx); - yoff *= fabs(sy); - } - sx *= lsx; - sy *= lsy; - xoff *= fabs(lsx); - yoff *= fabs(lsy); - - // Create quad - setupQuad(&quad, line->seg(), cp1, cp2); - quad.colour = colour1; - quad.fogcolour = fogcolour1; - quad.light = light1; - setupQuadTexCoords(&quad, length, xoff, yoff, ceiling1, ceiling2, !upeg, sx, sy); - // Sky hack only applies if both sectors have a sky ceiling - if (strutil::equalCI(sky_flat, line->frontSector()->ceiling().texture) - && strutil::equalCI(sky_flat, line->backSector()->ceiling().texture)) - quad.flags |= SKY; - quad.flags |= UPPER; - - // Add quad - lines_[index].quads.push_back(quad); - } - - // Back lower - lsx = 1; - lsy = 1; - if (f1h1 > f2h1 || f1h2 > f2h2) - { - Quad quad; - - // Determine offsets - xoff = xoff2; - yoff = yoff2; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureOffsets)) - { - // UDMF extra offsets - if (line->s2()->hasProp("offsetx_bottom")) - xoff += line->s2()->floatProperty("offsetx_bottom"); - if (line->s2()->hasProp("offsety_bottom")) - yoff += line->s2()->floatProperty("offsety_bottom"); - } - - // Texture scale - auto& tex = mapeditor::textureManager().texture(line->s2()->texLower(), mixed); - quad.texture = tex.gl_id; - sx = tex.scale.x; - sy = tex.scale.y; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureScaling)) - { - if (line->s2()->hasProp("scalex_bottom")) - lsx = 1.0 / line->s2()->floatProperty("scalex_bottom"); - if (line->s2()->hasProp("scaley_bottom")) - lsy = 1.0 / line->s2()->floatProperty("scaley_bottom"); - } - if (!tex.world_panning) - { - xoff *= fabs(sx); - yoff *= fabs(sy); - } - sx *= lsx; - sy *= lsy; - xoff *= fabs(lsx); - yoff *= fabs(lsy); - - if (lpeg) // Lower unpegged - yoff += (ceiling2 - floor1); - - // Create quad - setupQuad(&quad, line->seg().flip(), fp1, fp2); - quad.colour = colour2; - quad.fogcolour = fogcolour2; - quad.light = light2; - setupQuadTexCoords(&quad, length, xoff, yoff, floor1, floor2, false, sx, sy); - if (strutil::equalCI(sky_flat, line->frontSector()->floor().texture)) - quad.flags |= SKY; - quad.flags |= BACK; - quad.flags |= LOWER; - - // Add quad - lines_[index].quads.push_back(quad); - } - - // Back middle - lsx = 1; - lsy = 1; - auto midtex2 = line->stringProperty("side2.texturemiddle"); - if (!midtex2.empty() && midtex2 != hidden_tex && show_midtex) - { - Quad quad; - - // Get texture - auto& tex = mapeditor::textureManager().texture(midtex2, mixed); - quad.texture = tex.gl_id; - - // Determine offsets - xoff = xoff2; - yoff = yoff2; - double ytex = 0; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureOffsets)) - { - if (line->s2()->hasProp("offsetx_mid")) - xoff += line->s2()->floatProperty("offsetx_mid"); - if (line->s2()->hasProp("offsety_mid")) - yoff += line->s2()->floatProperty("offsety_mid"); - } - - // Texture scale - sx = tex.scale.x; - sy = tex.scale.y; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureScaling)) - { - if (line->s2()->hasProp("scalex_mid")) - lsx = 1.0 / line->s2()->floatProperty("scalex_mid"); - if (line->s2()->hasProp("scaley_mid")) - lsy = 1.0 / line->s2()->floatProperty("scaley_mid"); - } - if (!tex.world_panning) - { - xoff *= fabs(sx); - yoff *= fabs(sy); - } - sx *= lsx; - sy *= lsy; - xoff *= fabs(lsx); - yoff *= fabs(lsy); - - // Setup quad coordinates - double top, bottom; - if ((map_->currentFormat() == MapFormat::Doom64) - || (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::SideMidtexWrapping) - && line->boolProperty("wrapmidtex"))) - { - top = lowceil; - bottom = highfloor; - ytex = yoff; - if (lpeg) - ytex -= lowceil - highfloor; - } - else if (lpeg) - { - auto& tex_info = gl::Texture::info(quad.texture); - bottom = highfloor + yoff; - top = bottom + (tex_info.size.y * sy); - } - else - { - auto& tex_info = gl::Texture::info(quad.texture); - top = lowceil + yoff; - bottom = top - (tex_info.size.y * sy); - } - // The "correct" thing here is to allow textures to run into the floor - // unless clipmidtex is on, but OpenGL is designed not to allow that to - // happen, and GZDoom doesn't support it either. - if (bottom < highfloor) - bottom = highfloor; - if (top > lowceil) - { - ytex = top - lowceil; - top = lowceil; - } - - // Create quad - setupQuad(&quad, line->seg().flip(), top, bottom); - quad.colour = colour2.ampf(1.0f, 1.0f, 1.0f, alpha); - quad.fogcolour = fogcolour2; - quad.light = light2; - setupQuadTexCoords(&quad, length, xoff, ytex, top, bottom, false, sx, sy); - quad.flags |= BACK; - quad.flags |= MIDTEX; - if (line->hasProp("renderstyle") && line->stringProperty("renderstyle") == "add") - quad.flags |= TRANSADD; - else if (line_translucent && map_->mapSpecials()->translucentLineAdditive(line)) // TranslucentLine special - quad.flags |= TRANSADD; - - // Add quad - lines_[index].quads.push_back(quad); - } - - // Back upper - lsx = 1; - lsy = 1; - if (c2h1 > c1h1 || c2h2 > c1h2) - { - Quad quad; - - // Determine offsets - xoff = xoff2; - yoff = yoff2; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureOffsets)) - { - // UDMF extra offsets - if (line->s2()->hasProp("offsetx_top")) - xoff += line->s2()->floatProperty("offsetx_top"); - if (line->s2()->hasProp("offsety_top")) - yoff += line->s2()->floatProperty("offsety_top"); - } - - // Texture scale - auto& tex = mapeditor::textureManager().texture(line->s2()->texUpper(), mixed); - quad.texture = tex.gl_id; - sx = tex.scale.x; - sy = tex.scale.y; - if (map_->currentFormat() == MapFormat::UDMF - && game::configuration().featureSupported(UDMFFeature::TextureScaling)) - { - if (line->s2()->hasProp("scalex_top")) - lsx = 1.0 / line->s2()->floatProperty("scalex_top"); - if (line->s2()->hasProp("scaley_top")) - lsy = 1.0 / line->s2()->floatProperty("scaley_top"); - } - if (!tex.world_panning) - { - xoff *= fabs(sx); - yoff *= fabs(sy); - } - sx *= lsx; - sy *= lsy; - xoff *= fabs(lsx); - yoff *= fabs(lsy); - - // Create quad - setupQuad(&quad, line->seg().flip(), cp2, cp1); - quad.colour = colour2; - quad.fogcolour = fogcolour2; - quad.light = light2; - setupQuadTexCoords(&quad, length, xoff, yoff, ceiling2, ceiling1, !upeg, sx, sy); - if (strutil::equalCI(sky_flat, line->frontSector()->ceiling().texture)) - quad.flags |= SKY; - quad.flags |= BACK; - quad.flags |= UPPER; - - // Add quad - lines_[index].quads.push_back(quad); - } - - // Add any middle lines created by 3D floors - // TODO this code is so, so duplicated, oh dear - // TODO going to have to deal with vertical overlaps (which already do - // weird things when sloped floors meet in the middle of a line) - // TODO relatedly, need to be able to SPLIT a quad even when there's no - // texture, because 3d floors can change the lighting - // TODO will one-sided lines ever have to worry about this? - // TODO need to do back sector too, but with some of the logic reversed - MapSide* sides[2] = { line->s2(), line->s1() }; - for (unsigned front = 0; front < 2; front++) - { - MapSide* side = sides[front]; - MapSector* sector = side->sector(); - - for (const auto& extra : sector->extraFloors()) - { - if (extra.ceilingOnly()) - continue; // A floor that's a flat plane can't possibly have any sides - - // Don't draw a texture if the same 3D floor is on both sides - // TODO a bit clumsy and inefficient - // TODO unclear what happens if /different/ 3d floors are on both sides, or if the line itself also has a - // midtex, or if there's an EF_LO affecting the outside of the 3d floor... - bool shared = false; - for (auto& extra2 : sides[1 - front]->sector()->extraFloors()) - { - if (extra2.control_sector_index == extra.control_sector_index) - { - shared = true; - break; - } - } - if (shared) - continue; - - MapSector* control_sector = map_->sector(extra.control_sector_index); - MapLine* control_line = map_->line(extra.control_line_index); - - xoff = control_line->s1()->texOffsetX() + line->s1()->texOffsetX(); - yoff = control_line->s1()->texOffsetY() + line->s1()->texOffsetY(); - sx = sy = 1; - if (map_->currentFormat() == MapFormat::UDMF) - applyZDoomPerSectionOffsets(control_line->s1(), "mid", &xoff, &yoff, &sx, &sy); - - // TODO missing texture check should look for this! - string texname; - // Not documented, but in practice, when both flags are set, upper wins - if (extra.useUpperTexture()) - texname = side->texUpper(); - else if (extra.useLowerTexture()) - texname = side->texLower(); - else - texname = control_line->s1()->texMiddle(); - - Quad quad; - auto seg = line->seg(); - // If the 3D floor is on the front side of the line, then the - // outside wall is on the back, so the line segment must be flipped - if (front) - seg = seg.flip(); - setupQuad(&quad, seg, control_sector->ceiling().plane, control_sector->floor().plane); - // TODO all this nonsense - quad.colour = colour1.ampf(1.0f, 1.0f, 1.0f, extra.alpha); - quad.fogcolour = fogcolour1; - quad.light = light1; - quad.texture = mapeditor::textureManager().texture(texname, mixed).gl_id; - - setupQuadTexCoords( - &quad, - length, - xoff, - yoff, - control_sector->ceiling().height, - control_sector->floor().height, - false, - sx, - sy); - quad.flags |= MIDTEX; - if (extra.draw_inside) - { - quad.flags |= DRAWBOTH; - } - if (extra.additiveTransparency()) - { - quad.flags |= TRANSADD; - } - quad.control_line = extra.control_line_index; - quad.control_side = control_line->s1()->index(); - // TODO other flags? - // TODO probably need to remember which extra-floor this came from - // too, which is slightly more complicated since it might be the - // sector on either side of the line - // TODO TRANSADD if that one flag is set! - - lines_[index].quads.push_back(quad); - } - } - - // Finished - lines_[index].updated_time = app::runTimer(); -} - -// ----------------------------------------------------------------------------- -// Renders [quad] -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderQuad(const Quad* quad, float alpha) -{ - // Setup special rendering options - if (quad->colour.a == 255) - { - if (quad->flags & SKY && render_3d_sky) - { - alpha = 0; - glDisable(GL_ALPHA_TEST); - } - else if (quad->flags & MIDTEX) - glAlphaFunc(GL_GREATER, 0.9f * alpha); - } - - // Checking for additive renderstyle - if (quad->flags & TRANSADD) - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - else - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Setup colour/light - setLight(quad->colour, quad->light, alpha); - - // Setup fog - setFog(quad->fogcolour, quad->light); - - // Setup DRAWBOTH - if (quad->flags & DRAWBOTH) - glDisable(GL_CULL_FACE); - - // Draw quad - glBegin(GL_QUADS); - glTexCoord2f(quad->points[0].tx, quad->points[0].ty); - glVertex3f(quad->points[0].x, quad->points[0].y, quad->points[0].z); - glTexCoord2f(quad->points[1].tx, quad->points[1].ty); - glVertex3f(quad->points[1].x, quad->points[1].y, quad->points[1].z); - glTexCoord2f(quad->points[2].tx, quad->points[2].ty); - glVertex3f(quad->points[2].x, quad->points[2].y, quad->points[2].z); - glTexCoord2f(quad->points[3].tx, quad->points[3].ty); - glVertex3f(quad->points[3].x, quad->points[3].y, quad->points[3].z); - glEnd(); - - // Reset settings - if (quad->colour.a == 255) - { - if (quad->flags & SKY && render_3d_sky) - glEnable(GL_ALPHA_TEST); - else if (quad->flags & MIDTEX) - glAlphaFunc(GL_GREATER, 0.0f); - } - if (quad->flags & DRAWBOTH) - glEnable(GL_CULL_FACE); -} - -// ----------------------------------------------------------------------------- -// Renders all currently visible wall quads -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderWalls() -{ - // Init - quads_transparent_.clear(); - glEnable(GL_TEXTURE_2D); - glCullFace(GL_BACK); - - // Render all visible quads, ordered by texture - unsigned a = 0; - unsigned tex_last = 0; - while (n_quads_ > 0) - { - tex_last = 0; - a = 0; - while (a < n_quads_) - { - // Check alpha - if (quads_[a]->colour.a < 255) - { - quads_transparent_.push_back(quads_[a]); - quads_[a] = quads_[--n_quads_]; - continue; - } - - // Check texture - if (!tex_last && quads_[a]->texture) - { - tex_last = quads_[a]->texture; - gl::Texture::bind(quads_[a]->texture); - } - if (quads_[a]->texture != tex_last) - { - a++; - continue; - } - - // Render quad - renderQuad(quads_[a], quads_[a]->alpha); - quads_[a] = quads_[--n_quads_]; - } - } - - glDisable(GL_TEXTURE_2D); -} - -// ----------------------------------------------------------------------------- -// Renders all currently visible transparent wall quads -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderTransparentWalls() -{ - // Init - glEnable(GL_TEXTURE_2D); - glDepthMask(GL_FALSE); - glDisable(GL_ALPHA_TEST); - glCullFace(GL_BACK); - - // Render all transparent quads - for (auto& quad : quads_transparent_) - { - // Bind texture - gl::Texture::bind(quad->texture, false); - - // Render quad - renderQuad(quad, quad->alpha); - } - - glDisable(GL_TEXTURE_2D); - glDepthMask(GL_TRUE); - glEnable(GL_ALPHA_TEST); -} - -// ----------------------------------------------------------------------------- -// Renders selection overlay for all selected wall quads -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderWallSelection(const ItemSelection& selection, float alpha) -{ - if (!render_selection_) - return; - - // Setup gl stuff - glLineWidth(2.0f); - glDisable(GL_TEXTURE_2D); - glDisable(GL_FOG); - glDisable(GL_DEPTH_TEST); - glEnable(GL_LINE_SMOOTH); - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - - // Setup colour - auto& def = colourconfig::colDef("map_3d_selection"); - auto col1 = def.colour; - col1.a *= alpha; - // gl::setColour(col1); - auto col2 = col1; - col2.a *= 0.5; - - // Go through selection - for (auto item : selection) - { - // Ignore if not a wall selection - if (item.type != mapeditor::ItemType::WallBottom && item.type != mapeditor::ItemType::WallMiddle - && item.type != mapeditor::ItemType::WallTop /* && selection[a].type != MapEditor::ItemType::Wall3DFloor*/) - continue; - - // Get side - auto side = map_->side(item.real_index >= 0 ? item.real_index : item.index); - if (!side) - continue; - - // Get parent line index - int line = side->parentLine()->index(); - - // Get appropriate quad - Quad* quad = nullptr; - for (unsigned q = 0; q < lines_[line].quads.size(); q++) - { - // Check quad is correct side - if (map_->line(line)->s1() == side && lines_[line].quads[q].flags & BACK) - continue; - if (map_->line(line)->s2() == side && (lines_[line].quads[q].flags & BACK) == 0) - continue; - - // Check every quad in this line - if (item.real_index >= 0) - { - for (unsigned m = q; m < lines_[line].quads.size(); m++) - { - if (item.control_line == lines_[line].quads[m].control_line) - { - quad = &lines_[line].quads[m]; - break; - } - } - } - else if (lines_[line].quads[q].flags & UPPER) - { - if (item.type == mapeditor::ItemType::WallTop) - { - quad = &lines_[line].quads[q]; - break; - } - } - else if (lines_[line].quads[q].flags & LOWER) - { - if (item.type == mapeditor::ItemType::WallBottom) - { - quad = &lines_[line].quads[q]; - break; - } - } - else if (item.type == mapeditor::ItemType::WallMiddle) - { - quad = &lines_[line].quads[q]; - break; - } /* - else if (selection[a].type == MapEditor::ItemType::Wall3DFloor && selection[a].control_line == - lines[line].quads[a].control_line) - { - quad = &lines[line].quads[a]; - break; - }*/ - } - - if (!quad) - continue; - - // Render quad outline - // gl::setColour(col1); - glBegin(GL_LINE_LOOP); - for (auto& point : quad->points) - glVertex3f(point.x, point.y, point.z); - glEnd(); - - // Render quad fill - // gl::setColour(col2); - glBegin(GL_QUADS); - for (auto& point : quad->points) - glVertex3f(point.x, point.y, point.z); - glEnd(); - } -} - -// ----------------------------------------------------------------------------- -// Updates cached data for [thing] (at [index]) -// ----------------------------------------------------------------------------- -void MapRenderer3D::updateThing(unsigned index, const MapThing* thing) -{ - // Check index - if (index >= things_.size() || !thing) - return; - - // Setup thing info - things_[index].type = &(game::configuration().thingType(thing->type())); - things_[index].sector = map_->sectors().atPos(thing->position()); - - // Get sprite texture - uint32_t theight = render_thing_icon_size; - things_[index].sprite = mapeditor::textureManager() - .sprite( - things_[index].type->sprite(), - things_[index].type->translation(), - things_[index].type->palette()) - .gl_id; - if (!things_[index].sprite) - { - // Sprite not found, try an icon - if (use_zeth_icons && things_[index].type->zethIcon() >= 0) - { - things_[index].sprite = mapeditor::textureManager() - .editorImage( - fmt::format("zethicons/zeth{:02d}", things_[index].type->zethIcon())) - .gl_id; - things_[index].flags |= ZETH; - } - if (!things_[index].sprite) - things_[index].sprite = mapeditor::textureManager() - .editorImage(fmt::format("thing/{}", things_[index].type->icon())) - .gl_id; - things_[index].flags |= ICON; - } - else - theight = things_[index].type->scaleY() * gl::Texture::info(things_[index].sprite).size.y; - if (!things_[index].sprite) - { - // Icon not found either, use unknown icon - things_[index].sprite = mapeditor::textureManager().editorImage("thing/unknown").gl_id; + gl::Texture::bind(flat.texture); + shader_3d_->setUniform("colour", flat.colour); + vb_flats_->draw(gl::Primitive::Triangles, nullptr, nullptr, flat.vertex_offset, flat.vertex_count); } - - // Determine z position - if (things_[index].type->zHeightAbsolute()) - things_[index].z = thing->zPos(); - else if (things_[index].sector) - { - // true = thing's Z relative to floor | false = thing's Z relative to ceiling - bool gravity = !things_[index].type->hanging(); - - if (game::configuration().currentGame() == "srb2" && things_[index].flags & 2) - { - // Sonic robo blast 2 things contain a "Flip" flag that inverts the thing's gravity if set - gravity = !gravity; - } - - // Get sector floor (or ceiling) height - int sheight; - float zheight = thing->zPos(); - if (!gravity) - { - sheight = things_[index].sector->ceiling().plane.heightAt(thing->xPos(), thing->yPos()); - sheight -= theight; - zheight = -zheight; - } - else - { - sheight = things_[index].sector->floor().plane.heightAt(thing->xPos(), thing->yPos()); - } - - // Set height - things_[index].z = sheight; - if (things_[index].type->shrinkOnZoom()) - things_[index].z -= render_thing_icon_size * 0.5; - if (things_[index].z < sheight) - things_[index].z = sheight; - things_[index].z += zheight; - } - - // Adjust height by sprite Y offset if needed - things_[index].z += mapeditor::textureManager().verticalOffset(things_[index].type->sprite()); - - things_[index].updated_time = app::runTimer(); -} - -// ----------------------------------------------------------------------------- -// Renders all currently visible things -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderThings() -{ - // Init - glEnable(GL_TEXTURE_2D); - glCullFace(GL_BACK); - unsigned tex = 0; - - // Go through things - double dist, halfwidth, theight; - double mdist = render_max_thing_dist; - if (mdist <= 0 || mdist > render_max_dist) - mdist = render_max_dist; - ColRGBA col; - uint8_t light; - float x1, y1, x2, y2; - unsigned update = 0; - auto strafe = camera_->strafeLine(); - auto cam_pos_2d = camera_->position().xy(); - const auto& cam_strafe = camera_->strafeVector(); - for (unsigned a = 0; a < map_->nThings(); a++) - { - auto thing = map_->thing(a); - things_[a].flags = things_[a].flags & ~DRAWN; - - // Check side of camera - if (camera_->pitch() > -0.9 && camera_->pitch() < 0.9) - { - if (geometry::lineSide(thing->position(), strafe) > 0) - continue; - } - - // Check thing distance if needed - dist = glm::distance(cam_pos_2d, thing->position()); - if (mdist > 0 && dist > mdist) - continue; - - // Update thing if needed - if (things_[a].updated_time < thing->modifiedTime() - || (things_[a].sector - && (things_[a].updated_time < things_[a].sector->modifiedTime() - || things_[a].updated_time < things_[a].sector->geometryUpdatedTime()))) - { - updateThing(a, thing); - update++; - if (update > 500) - break; - } - - // Skip if not shown - if (!things_[a].type->decoration() && render_3d_things == 2) - continue; - - // Get thing sprite - tex = things_[a].sprite; - - // Bind texture if needed - gl::Texture::bind(tex, false); - - // Determine coordinates - auto& tex_info = gl::Texture::info(tex); - halfwidth = things_[a].type->scaleX() * tex_info.size.x * 0.5; - theight = things_[a].type->scaleY() * tex_info.size.y; - if (things_[a].flags & ICON) - { - halfwidth = render_thing_icon_size * 0.5; - theight = render_thing_icon_size; - } - x1 = thing->xPos() - cam_strafe.x * halfwidth; - y1 = thing->yPos() - cam_strafe.y * halfwidth; - x2 = thing->xPos() + cam_strafe.x * halfwidth; - y2 = thing->yPos() + cam_strafe.y * halfwidth; - things_[a].height = theight; - - // Set colour/brightness - light = 255; - // If a thing is defined as fullbright but the sprite is missing, - // we'll fallback on the icon, which needs to be colored as appropriate. - if (things_[a].type->fullbright() && !(things_[a].flags & ICON)) - col.set(255, 255, 255, 255); - else - { - // Get light level from sector - if (things_[a].sector) - light = things_[a].sector->lightAt(); - - // Icon, use thing icon colour (not for Zeth icons, though) - if (things_[a].flags & ICON) - { - if (things_[a].flags & ZETH) - col.set(255, 255, 255, 255); - else - col.set(things_[a].type->colour()); - } - - // Otherwise use sector colour - else if (things_[a].sector) - col.set(things_[a].sector->colourAt(0, true)); - } - setLight(col, light, calcDistFade(dist, mdist)); - auto fogcol = ColRGBA(0, 0, 0, 0); - if (things_[a].sector) - fogcol = things_[a].sector->fogColour(); - - setFog(fogcol, light); - - // Draw thing - glBegin(GL_QUADS); - glTexCoord2f(0.0f, 0.0f); - glVertex3f(x1, y1, things_[a].z + theight); - glTexCoord2f(0.0f, 1.0f); - glVertex3f(x1, y1, things_[a].z); - glTexCoord2f(1.0f, 1.0f); - glVertex3f(x2, y2, things_[a].z); - glTexCoord2f(1.0f, 0.0f); - glVertex3f(x2, y2, things_[a].z + theight); - glEnd(); - - things_[a].flags |= DRAWN; - } - - // Draw thing borders if needed - if (render_3d_things_style >= 1) - { - glDisable(GL_TEXTURE_2D); - glDepthMask(GL_FALSE); - glAlphaFunc(GL_GREATER, 0.2f); - glDisable(GL_CULL_FACE); - glLineWidth(3.5f); - - for (unsigned a = 0; a < map_->nThings(); a++) - { - // Skip if hidden - if (!(things_[a].flags & DRAWN)) - continue; - - auto thing = map_->thing(a); - col.set(things_[a].type->colour()); - float radius = things_[a].type->radius(); - float bottom = things_[a].z + 0.5f; - float top = things_[a].z; - if (things_[a].type->height() < 0) - top += things_[a].height; - else - top += things_[a].type->height(); - - // Fill - glColor4f(col.fr(), col.fg(), col.fb(), 0.21f); - uint8_t light2 = 255; - auto fogcol2 = ColRGBA(0, 0, 0, 0); - if (things_[a].sector) - { - light2 = things_[a].sector->lightAt(); - fogcol2 = things_[a].sector->fogColour(); - } - setFog(fogcol2, light2); - glBegin(GL_QUADS); - // Bottom - glVertex3f(thing->xPos() - radius, thing->yPos() - radius, bottom); - glVertex3f(thing->xPos() + radius, thing->yPos() - radius, bottom); - glVertex3f(thing->xPos() + radius, thing->yPos() + radius, bottom); - glVertex3f(thing->xPos() - radius, thing->yPos() + radius, bottom); - if (render_3d_things_style == 2) - { - // Top - glVertex3f(thing->xPos() + radius, thing->yPos() - radius, top); - glVertex3f(thing->xPos() - radius, thing->yPos() - radius, top); - glVertex3f(thing->xPos() - radius, thing->yPos() + radius, top); - glVertex3f(thing->xPos() + radius, thing->yPos() + radius, top); - // North - glVertex3f(thing->xPos() - radius, thing->yPos() - radius, top); - glVertex3f(thing->xPos() - radius, thing->yPos() - radius, bottom); - glVertex3f(thing->xPos() + radius, thing->yPos() - radius, bottom); - glVertex3f(thing->xPos() + radius, thing->yPos() - radius, top); - // South - glVertex3f(thing->xPos() + radius, thing->yPos() + radius, top); - glVertex3f(thing->xPos() + radius, thing->yPos() + radius, bottom); - glVertex3f(thing->xPos() - radius, thing->yPos() + radius, bottom); - glVertex3f(thing->xPos() - radius, thing->yPos() + radius, top); - // East - glVertex3f(thing->xPos() + radius, thing->yPos() - radius, top); - glVertex3f(thing->xPos() + radius, thing->yPos() - radius, bottom); - glVertex3f(thing->xPos() + radius, thing->yPos() + radius, bottom); - glVertex3f(thing->xPos() + radius, thing->yPos() + radius, top); - // West - glVertex3f(thing->xPos() - radius, thing->yPos() + radius, top); - glVertex3f(thing->xPos() - radius, thing->yPos() + radius, bottom); - glVertex3f(thing->xPos() - radius, thing->yPos() - radius, bottom); - glVertex3f(thing->xPos() - radius, thing->yPos() - radius, top); - } - glEnd(); - - // Outline - glColor4f(col.fr(), col.fg(), col.fb(), 0.6f); - // Bottom - glBegin(GL_LINE_LOOP); - glVertex3f(thing->xPos() - radius, thing->yPos() - radius, bottom); - glVertex3f(thing->xPos() + radius, thing->yPos() - radius, bottom); - glVertex3f(thing->xPos() + radius, thing->yPos() + radius, bottom); - glVertex3f(thing->xPos() - radius, thing->yPos() + radius, bottom); - glEnd(); - if (render_3d_things_style == 2) - { - // Top - glBegin(GL_LINE_LOOP); - glVertex3f(thing->xPos() - radius, thing->yPos() - radius, top); - glVertex3f(thing->xPos() + radius, thing->yPos() - radius, top); - glVertex3f(thing->xPos() + radius, thing->yPos() + radius, top); - glVertex3f(thing->xPos() - radius, thing->yPos() + radius, top); - glEnd(); - // Corners - glBegin(GL_LINES); - glVertex3f(thing->xPos() - radius, thing->yPos() - radius, bottom); - glVertex3f(thing->xPos() - radius, thing->yPos() - radius, top); - glVertex3f(thing->xPos() + radius, thing->yPos() - radius, bottom); - glVertex3f(thing->xPos() + radius, thing->yPos() - radius, top); - glVertex3f(thing->xPos() + radius, thing->yPos() + radius, bottom); - glVertex3f(thing->xPos() + radius, thing->yPos() + radius, top); - glVertex3f(thing->xPos() - radius, thing->yPos() + radius, bottom); - glVertex3f(thing->xPos() - radius, thing->yPos() + radius, top); - glEnd(); - } - - // Direction - glPushMatrix(); - glTranslatef(thing->xPos(), thing->yPos(), bottom); - glRotated(thing->angle(), 0, 0, 1); - glBegin(GL_LINES); - glVertex3f(0.0f, 0.0f, 0.0f); - glVertex3f(radius, 0.0f, 0.0f); - glVertex3f(radius, 0.0f, 0.0f); - glVertex3f(radius - (radius * 0.2f), -radius * 0.2f, 0.0f); - glVertex3f(radius, 0.0f, 0.0f); - glVertex3f(radius - (radius * 0.2f), radius * 0.2f, 0.0f); - glEnd(); - glPopMatrix(); - } - - glDepthMask(GL_TRUE); - glEnable(GL_CULL_FACE); - } -} - -// ----------------------------------------------------------------------------- -// Renders selection overlay for all selected things -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderThingSelection(const ItemSelection& selection, float alpha) -{ - // Do nothing if no things visible - if (render_3d_things == 0 || !render_selection_) - return; - - // Setup gl stuff - glLineWidth(2.0f); - glDisable(GL_TEXTURE_2D); - glDisable(GL_FOG); - glDisable(GL_DEPTH_TEST); - glEnable(GL_LINE_SMOOTH); - - // Setup colour - auto col1 = colourconfig::colour("map_3d_selection"); - col1.a *= alpha; - // gl::setColour(col1); - auto col2 = col1; - col2.a *= 0.5; - - // Go through selection - double halfwidth, theight, x1, y1, x2, y2; - const auto& cam_strafe = camera_->strafeVector(); - for (auto item : selection) - { - // Ignore if not a thing selection - if (item.type != mapeditor::ItemType::Thing) - continue; - - // Get thing - auto thing = item.asThing(*map_); - if (!thing) - return; - - // Update if required - if (things_[item.index].type == nullptr) - updateThing(item.index, thing); - - // Skip if not shown - if (!things_[item.index].type->decoration() && render_3d_things == 2) - continue; - - // Determine coordinates - auto& tex_info = gl::Texture::info(things_[item.index].sprite); - halfwidth = tex_info.size.x * 0.5; - theight = tex_info.size.y; - if (things_[item.index].flags & ICON) - { - halfwidth = render_thing_icon_size * 0.5; - theight = render_thing_icon_size; - } - x1 = thing->xPos() - cam_strafe.x * halfwidth; - y1 = thing->yPos() - cam_strafe.y * halfwidth; - x2 = thing->xPos() + cam_strafe.x * halfwidth; - y2 = thing->yPos() + cam_strafe.y * halfwidth; - - // Render outline - double z = things_[item.index].z; - // gl::setColour(col1); - glBegin(GL_LINE_LOOP); - glVertex3f(x1, y1, z + theight); - glVertex3f(x1, y1, z); - glVertex3f(x2, y2, z); - glVertex3f(x2, y2, z + theight); - glEnd(); - - // Render fill - // gl::setColour(col2); - glBegin(GL_QUADS); - glVertex3f(x1, y1, z + theight); - glVertex3f(x1, y1, z); - glVertex3f(x2, y2, z); - glVertex3f(x2, y2, z + theight); - glEnd(); - } -} - -// ----------------------------------------------------------------------------- -// (Re)builds the flats Vertex Buffer Object -// ----------------------------------------------------------------------------- -void MapRenderer3D::updateFlatsVBO() -{ - if (!flats_use_vbo) - return; - - // Create VBOs if needed - if (vbo_flats_ == 0) - glGenBuffers(1, &vbo_flats_); - - // Get total size needed - unsigned totalsize = 0; - /*for (unsigned a = 0; a < map_->nSectors(); a++) - { - // Create the sector flats structure, but don't try to update VBOs yet - // (since this function is recreating them) - if (isSectorStale(a)) - updateSectorFlats(a); - - MapSector* sector = map_->sector(a); - Polygon2D* poly = sector->polygon(); - totalsize += poly->vboDataSize() * sector_flats_[a].size(); - }*/ - - // Allocate buffer data - glBindBuffer(GL_ARRAY_BUFFER, vbo_flats_); - // Polygon2D::setupVBOPointers(); - glBufferData(GL_ARRAY_BUFFER, totalsize, nullptr, GL_STATIC_DRAW); - - // Write polygon data to VBO - // for (unsigned a = 0; a < sector_flats_.size(); a++) - //{ - // MapSector* sector = sector_flats_[a][0].sector; - // Polygon2D* poly = sector->polygon(); - - // // TODO i realize we'll have to do this if any 3d floors are /added/, too - // for (unsigned b = 0; b < sector_flats_[a].size(); b++) - // { - // // Write flat to VBO - // sector_flats_[a][b].vbo_offset = offset; - // updateFlatTexCoords(a, b); - // poly->setZ(sector_flats_[a][b].plane); - // offset = poly->writeToVBO(offset); - // } - - // // Reset polygon z - // poly->setZ(0.0f); - //} - - // Clean up - glBindBuffer(GL_ARRAY_BUFFER, 0); -} - -// ----------------------------------------------------------------------------- -// (Re)builds the walls Vertex Buffer Object -// (or would, if it were used for wall rendering) -// ----------------------------------------------------------------------------- -void MapRenderer3D::updateWallsVBO() const {} - -// ----------------------------------------------------------------------------- -// Runs a quick check of all sector bounding boxes against the current view to -// hide any that are outside it -// ----------------------------------------------------------------------------- -void MapRenderer3D::quickVisDiscard() -{ - // Create sector distance array if needed - if (dist_sectors_.size() != map_->nSectors()) - dist_sectors_.resize(map_->nSectors()); - - // Go through all sectors - auto cam = camera_->position().xy(); - double min_dist, dist; - auto strafe = camera_->strafeLine(); - for (unsigned a = 0; a < map_->nSectors(); a++) - { - // Get sector bbox - auto bbox = map_->sector(a)->boundingBox(); - - // Init to visible - dist_sectors_[a] = 0.0f; - - // Check if within bbox - if (bbox.contains(cam)) - continue; - - // Check side of camera - if (camera_->pitch() > -0.9 && camera_->pitch() < 0.9) - { - if (geometry::lineSide(bbox.min, strafe) > 0 - && geometry::lineSide(Vec2d(bbox.max.x, bbox.min.y), strafe) > 0 - && geometry::lineSide(bbox.max, strafe) > 0 - && geometry::lineSide(Vec2d(bbox.min.x, bbox.max.y), strafe) > 0) - { - // Behind camera, invisible - dist_sectors_[a] = -1.0f; - continue; - } - } - - // Check distance to bbox - if (render_max_dist > 0) - { - min_dist = 9999999; - dist = geometry::distanceToLine(cam, bbox.leftSide()); - if (dist < min_dist) - min_dist = dist; - dist = geometry::distanceToLine(cam, bbox.topSide()); - if (dist < min_dist) - min_dist = dist; - dist = geometry::distanceToLine(cam, bbox.rightSide()); - if (dist < min_dist) - min_dist = dist; - dist = geometry::distanceToLine(cam, bbox.bottomSide()); - if (dist < min_dist) - min_dist = dist; - - dist_sectors_[a] = min_dist; - } - } - - // Set all lines that are part of invisible sectors to invisible - for (unsigned a = 0; a < map_->nSides(); a++) - { - dist = dist_sectors_[map_->side(a)->sector()->index()]; - - lines_[map_->side(a)->parentLine()->index()].visible = !( - dist < 0 || (render_max_dist > 0 && dist > render_max_dist)); - } -} - -// ----------------------------------------------------------------------------- -// Calculates and returns the faded alpha value for [distance] from the camera -// ----------------------------------------------------------------------------- -float MapRenderer3D::calcDistFade(double distance, double max) const -{ - if (max <= 0) - return 1.0f; - - float faderange = max * 0.2f; - if (distance > max - faderange) - return 1.0f - ((distance - (max - faderange)) / faderange); - else - return 1.0f; -} - -// ----------------------------------------------------------------------------- -// Checks and hides any quads that are not currently in view -// ----------------------------------------------------------------------------- -void MapRenderer3D::checkVisibleQuads() -{ - // Create quads array if empty - // if (!quads_) - // quads_ = new Quad*[map_->nLines() * 4]; - if (quads_.empty()) - quads_.resize(map_->nLines() * 4); - - // Go through lines - MapLine* line; - float distfade; - n_quads_ = 0; - unsigned updates = 0; - bool update = false; - auto strafe = camera_->strafeLine(); - auto cam_pos = camera_->position().xy(); - for (unsigned a = 0; a < lines_.size(); a++) - { - line = map_->line(a); - - // Skip if not visible - if (!lines_[a].visible) - continue; - - // Check side of camera - if (camera_->pitch() > -0.9 && camera_->pitch() < 0.9) - { - if (geometry::lineSide(line->start(), strafe) > 0 && geometry::lineSide(line->end(), strafe) > 0) - continue; - } - - // Check for distance fade - if (render_max_dist > 0) - distfade = calcDistFade(geometry::distanceToLine(cam_pos, line->seg()), render_max_dist); - else - distfade = 1.0f; - - // Update line if needed - update = false; - if (lines_[a].updated_time < line->modifiedTime()) // Check line modified - update = true; - if (lines_[a].line != line) - update = true; - if (!update && line->s1()) - { - // Check front side/sector modified - if (lines_[a].updated_time < line->s1()->modifiedTime() - || lines_[a].updated_time < line->frontSector()->modifiedTime() - || lines_[a].updated_time < line->frontSector()->geometryUpdatedTime()) - update = true; - - MapSector* sector = line->frontSector(); - for (const auto& extra_floor : sector->extraFloors()) - { - MapLine* control_line = map_->line(extra_floor.control_line_index); - if (lines_[a].updated_time < control_line->s1()->modifiedTime() - || lines_[a].updated_time < control_line->frontSector()->modifiedTime() - || lines_[a].updated_time < control_line->frontSector()->geometryUpdatedTime()) - update = true; - } - } - if (!update && line->s2()) - { - // Check back side/sector modified - if (lines_[a].updated_time < line->s2()->modifiedTime() - || lines_[a].updated_time < line->backSector()->modifiedTime() - || lines_[a].updated_time < line->backSector()->geometryUpdatedTime()) - update = true; - - MapSector* sector = line->backSector(); - for (const auto& extra_floor : sector->extraFloors()) - { - MapLine* control_line = map_->line(extra_floor.control_line_index); - if (lines_[a].updated_time < control_line->s1()->modifiedTime() - || lines_[a].updated_time < control_line->frontSector()->modifiedTime() - || lines_[a].updated_time < control_line->frontSector()->geometryUpdatedTime()) - update = true; - } - } - if (update) - { - updateLine(a); - // updates++; - // if (updates > 500) - // break; - } - - // Determine quads to be drawn - for (auto& quad : lines_[a].quads) - { - // Check we're on the right side of the quad - if (!(quad.flags & DRAWBOTH) - && geometry::lineSide( - cam_pos, Seg2d(quad.points[0].x, quad.points[0].y, quad.points[2].x, quad.points[2].y)) - < 0) - continue; - - quads_[n_quads_] = &quad; - quad.alpha = distfade; - n_quads_++; - } - } -} - -// ----------------------------------------------------------------------------- -// Checks and hides any flats that are not currently in view -// ----------------------------------------------------------------------------- -void MapRenderer3D::checkVisibleFlats() -{ - // Update flats array - flats_.clear(); - n_flats_ = 0; - for (unsigned a = 0; a < sector_flats_.size(); a++) - { - // Skip if invisible - if (dist_sectors_[a] < 0) - continue; - - // Update sector info if needed - if (isSectorStale(a)) - updateSector(a); - - n_flats_ += sector_flats_[a].size(); - } - flats_.resize(n_flats_); - - // Go through sectors - float alpha; - unsigned flat_idx = 0; - auto cam = camera_->position().xy(); - for (unsigned a = 0; a < map_->nSectors(); a++) - { - const auto sector = map_->sector(a); - - // Skip if invisible - if (dist_sectors_[a] < 0) - continue; - - // Check distance if needed - if (render_max_dist > 0) - { - if (dist_sectors_[a] > render_max_dist) - continue; - - // Double-check distance - dist_sectors_[a] = sector->distanceTo(cam, render_max_dist); - if (dist_sectors_[a] > render_max_dist && !sector->boundingBox().contains(cam)) - { - dist_sectors_[a] = -1; - continue; - } - } - - // Set distance fade alpha - if (render_max_dist > 0) - alpha = calcDistFade(dist_sectors_[a], render_max_dist); - else - alpha = 1.0f; - - // TODO this used to add all the floors, then all the ceilings, which would reduce the number of GL state calls - // -- can probably fix that - - // Add flats - for (unsigned b = 0; b < sector_flats_[a].size(); b++) - { - Flat& flat = sector_flats_[a][b]; - flat.alpha = alpha; - flats_[flat_idx++] = ♭ - - // For two-sided flats, update which plane is currently visible - /*if (flat.flags & DRAWBOTH) - { - if (cam_position_.z < flat.plane.height_at(cam_position_.x, cam_position_.y)) - flat.flags |= CEIL; - else - flat.flags &= ~CEIL; - }*/ - } - } -} - -// ----------------------------------------------------------------------------- -// Finds the closest wall/flat/thing to the camera along the view vector -// ----------------------------------------------------------------------------- -mapeditor::Item MapRenderer3D::determineHilight() -{ - // Init - double min_dist = 9999999; - mapeditor::Item current; - auto strafe = camera_->strafeLine(); - const auto& cam_pos = camera_->position(); - const auto& cam_dir = camera_->directionVector(); - - // Check for required map structures - if (!map_ || lines_.size() != map_->nLines() || sector_flats_.size() != map_->nSectors() - || things_.size() != map_->nThings()) - return current; - - // Check lines - double height, dist; - for (unsigned a = 0; a < map_->nLines(); a++) - { - // Ignore if not visible - if (!lines_[a].visible) - continue; - - auto line = map_->line(a); - - // Find (2d) distance to line - dist = geometry::distanceRayLine(cam_pos.xy(), (cam_pos + cam_dir).xy(), line->start(), line->end()); - - // Ignore if no intersection or something was closer - if (dist < 0 || dist >= min_dist) - continue; - - // Find quad intersect if any - auto intersection = cam_pos + cam_dir * dist; - for (auto& quad : lines_[a].quads) - { - // Check side of camera - if (!(quad.flags & DRAWBOTH) - && geometry::lineSide( - cam_pos.xy(), Seg2d(quad.points[0].x, quad.points[0].y, quad.points[2].x, quad.points[2].y)) - < 0) - continue; - - // Check intersection height - // Need to handle slopes by finding the floor and ceiling height of - // the quad at the intersection point - Vec2d seg_left = Vec2d(quad.points[1].x, quad.points[1].y); - Vec2d seg_right = Vec2d(quad.points[2].x, quad.points[2].y); - double dist_along_segment = glm::length(intersection.xy() - seg_left) / glm::length(seg_right - seg_left); - double top = quad.points[0].z + (quad.points[3].z - quad.points[0].z) * dist_along_segment; - double bottom = quad.points[1].z + (quad.points[2].z - quad.points[1].z) * dist_along_segment; - if (bottom <= intersection.z && intersection.z <= top) - { - // Determine selected item from quad flags - - // Side index - if (quad.flags & BACK) - current.index = line->s2Index(); - else - current.index = line->s1Index(); - - // Side part - if (quad.control_side >= 0) - { - current.type = mapeditor::ItemType::WallMiddle; - current.real_index = current.index; - current.control_line = quad.control_line; - current.index = quad.control_side; - } - else if (quad.flags & UPPER) - current.type = mapeditor::ItemType::WallTop; - else if (quad.flags & LOWER) - current.type = mapeditor::ItemType::WallBottom; - else - current.type = mapeditor::ItemType::WallMiddle; - - min_dist = dist; - } - } - } - - // Check sectors - for (unsigned a = 0; a < map_->nSectors(); a++) - { - // Ignore if not visible - if (dist_sectors_[a] < 0) - continue; - - // Check distance to sector planes - for (unsigned b = 0; b < sector_flats_[a].size(); b++) - { - const Flat& flat = sector_flats_[a][b]; - - dist = geometry::distanceRayPlane(cam_pos, cam_dir, sector_flats_[a][b].plane); - if (dist < 0 || dist >= min_dist) - continue; - - // Check if on the correct side of the plane - double flat_z = sector_flats_[a][b].plane.heightAt(cam_pos.x, cam_pos.y); - if (!(flat.flags & DRAWBOTH)) - { - if (flat.flags & FLATFLIP) - { - if (flat.flags & CEIL && cam_pos.z <= flat_z) - continue; - if (!(flat.flags & CEIL) && cam_pos.z >= flat_z) - continue; - } - else - { - if (flat.flags & CEIL && cam_pos.z >= flat_z) - continue; - if (!(flat.flags & CEIL) && cam_pos.z <= flat_z) - continue; - } - } - - // Check if intersection is within sector - if (!map_->sector(a)->containsPoint((cam_pos + cam_dir * dist).xy())) - continue; - - if (flat.extra_floor_index < 0) - current.index = a; - else - { - current.index = flat.control_sector->index(); - current.real_index = a; - } - - min_dist = dist; - if (flat.flags & CEIL) - current.type = mapeditor::ItemType::Ceiling; - else - current.type = mapeditor::ItemType::Floor; - } - } - - // Update item distance - if (min_dist >= 9999999 || min_dist < 0) - item_dist_ = -1; - else - item_dist_ = math::round(min_dist); - - // Check things (if visible) - if (render_3d_things == 0) - return current; - double halfwidth, theight; - auto cam_strafe_2d = camera_->strafeVector().xy(); - for (unsigned a = 0; a < map_->nThings(); a++) - { - // Ignore if no sprite - if (!things_[a].sprite) - continue; - - // Ignore if not visible - auto thing = map_->thing(a); - if (geometry::lineSide(thing->position(), strafe) > 0) - continue; - - // Ignore if not shown - if (!things_[a].type->decoration() && render_3d_things == 2) - continue; - - // Find distance to thing sprite - auto& tex_info = gl::Texture::info(things_[a].sprite); - halfwidth = tex_info.size.x * 0.5; - if (things_[a].flags & ICON) - halfwidth = render_thing_icon_size * 0.5; - dist = geometry::distanceRayLine( - cam_pos.xy(), - (cam_pos + cam_dir).xy(), - thing->position() - cam_strafe_2d * halfwidth, - thing->position() + cam_strafe_2d * halfwidth); - - // Ignore if no intersection or something was closer - if (dist < 0 || dist >= min_dist) - continue; - - // Check intersection height - theight = tex_info.size.y; - height = cam_pos.z + cam_dir.z * dist; - if (things_[a].flags & ICON) - theight = render_thing_icon_size; - if (height >= things_[a].z && height <= things_[a].z + theight) - { - current.index = a; - current.type = mapeditor::ItemType::Thing; - min_dist = dist; - } - } - - // Update item distance - if (min_dist >= 9999999 || min_dist < 0) - item_dist_ = -1; - else - item_dist_ = math::round(min_dist); - - return current; -} - -// ----------------------------------------------------------------------------- -// Renders the hilight overlay for the currently hilighted object -// ----------------------------------------------------------------------------- -void MapRenderer3D::renderHilight(mapeditor::Item hilight, float alpha) -{ - // Do nothing if no item hilighted - if (hilight.index < 0 || render_3d_hilight == 0 || !render_hilight_) - return; - - // Setup gl stuff - glLineWidth(3.0f); - glDisable(GL_TEXTURE_2D); - glDisable(GL_FOG); - glDisable(GL_DEPTH_TEST); - glEnable(GL_LINE_SMOOTH); - auto& def = colourconfig::colDef("map_3d_hilight"); - auto col_hilight = def.colour; - col_hilight.a *= alpha; - // gl::setColour(col_hilight); - - // Quad hilight - if (hilight.type == mapeditor::ItemType::WallBottom || hilight.type == mapeditor::ItemType::WallMiddle - || hilight.type == mapeditor::ItemType::WallTop /*|| hilight.type == MapEditor::ItemType::Wall3DFloor*/) - { - // Get side - auto side = map_->side(hilight.real_index >= 0 ? hilight.real_index : hilight.index); - if (!side) - return; - - // Get parent line index - int line = side->parentLine()->index(); - - // Get appropriate quad - Quad* quad = nullptr; - for (unsigned a = 0; a < lines_[line].quads.size(); a++) - { - // Check quad is correct side - if (map_->line(line)->s1() == side && lines_[line].quads[a].flags & BACK) - continue; - - // Check quad is correct part - if (hilight.real_index >= 0) - { - for (unsigned m = a; m < lines_[line].quads.size(); m++) - { - if (hilight.control_line == lines_[line].quads[a].control_line) - { - quad = &lines_[line].quads[a]; - break; - } - } - } - else if (lines_[line].quads[a].flags & UPPER) - { - if (hilight.type == mapeditor::ItemType::WallTop) - { - quad = &lines_[line].quads[a]; - break; - } - } - else if (lines_[line].quads[a].flags & LOWER) - { - if (hilight.type == mapeditor::ItemType::WallBottom) - { - quad = &lines_[line].quads[a]; - break; - } - } - else if (hilight.type == mapeditor::ItemType::WallMiddle) - { - quad = &lines_[line].quads[a]; - break; - } - } - - if (!quad) - return; - - // Render outline - glBegin(GL_LINE_LOOP); - for (auto& point : quad->points) - glVertex3f(point.x, point.y, point.z); - glEnd(); - - // Render fill (if needed) - if (render_3d_hilight > 1) - { - glCullFace(GL_BACK); - col_hilight.a *= 0.3; - // gl::setColour(col_hilight); - glBegin(GL_QUADS); - for (auto& point : quad->points) - glVertex3f(point.x, point.y, point.z); - glEnd(); - } - } - - // Sector hilight - if (hilight.type == mapeditor::ItemType::Floor || hilight.type == mapeditor::ItemType::Ceiling) - { - // Get sector - auto sector = map_->sector(hilight.real_index >= 0 ? hilight.real_index : hilight.index); - if (!sector) - return; - - // Get plane - Plane plane; - if (hilight.real_index >= 0) - { - for (const auto& extra_floor : sector->extraFloors()) - { - if (extra_floor.control_sector_index == hilight.index) - { - auto& extra = extra_floor; - if (hilight.type == mapeditor::ItemType::Floor) - plane = extra.floor_plane; - else - plane = extra.ceiling_plane; - } - } - } - else - { - if (hilight.type == mapeditor::ItemType::Floor) - plane = sector->floor().plane; - else - plane = sector->ceiling().plane; - } - - // Render sector outline - vector lines; - sector->putLines(lines); - glBegin(GL_LINES); - for (auto& line : lines) - { - glVertex3d(line->x1(), line->y1(), plane.heightAt(line->x1(), line->y1())); - glVertex3d(line->x2(), line->y2(), plane.heightAt(line->x2(), line->y2())); - } - glEnd(); - - // Render fill if needed - if (render_3d_hilight > 1) - { - col_hilight.a *= 0.3; - // gl::setColour(col_hilight); - glDisable(GL_CULL_FACE); - // sector->polygon()->setZ(plane); - // sector->polygon()->render(); - // sector->polygon()->setZ(0); - glEnable(GL_CULL_FACE); - } - } - - // Thing hilight - double x1, y1, x2, y2; - auto cam_strafe = camera_->strafeVector(); - if (hilight.type == mapeditor::ItemType::Thing) - { - // Get thing - auto thing = hilight.asThing(*map_); - if (!thing) - return; - - // Determine coordinates - auto& tex_info = gl::Texture::info(things_[hilight.index].sprite); - double halfwidth = things_[hilight.index].type->scaleX() * tex_info.size.x * 0.5; - double theight = things_[hilight.index].type->scaleY() * tex_info.size.y; - if (things_[hilight.index].flags & ICON) - { - halfwidth = render_thing_icon_size * 0.5; - theight = render_thing_icon_size; - } - x1 = thing->xPos() - cam_strafe.x * halfwidth; - y1 = thing->yPos() - cam_strafe.y * halfwidth; - x2 = thing->xPos() + cam_strafe.x * halfwidth; - y2 = thing->yPos() + cam_strafe.y * halfwidth; - - // Render outline of sprite - double z = things_[hilight.index].z; - glBegin(GL_LINE_LOOP); - glVertex3f(x1, y1, z + theight); - glVertex3f(x1, y1, z); - glVertex3f(x2, y2, z); - glVertex3f(x2, y2, z + theight); - glEnd(); - - // Render fill if needed - if (render_3d_hilight > 1) - { - glCullFace(GL_BACK); - col_hilight.a *= 0.3; - // gl::setColour(col_hilight); - glBegin(GL_QUADS); - glVertex3f(x1, y1, z + theight); - glVertex3f(x1, y1, z); - glVertex3f(x2, y2, z); - glVertex3f(x2, y2, z + theight); - glEnd(); - } - } - - // glEnable(GL_DEPTH_TEST); - // gl::setColour(ColRGBA::WHITE); } diff --git a/src/MapEditor/Renderer/MapRenderer3D.h b/src/MapEditor/Renderer/MapRenderer3D.h index cf5d8e240..06d9caa72 100644 --- a/src/MapEditor/Renderer/MapRenderer3D.h +++ b/src/MapEditor/Renderer/MapRenderer3D.h @@ -1,22 +1,19 @@ #pragma once -#include "Geometry/Plane.h" -#include "Geometry/RectFwd.h" -#include "MapEditor/Item.h" - // Forward declarations -#include "SLADEMap/SLADEMapFwd.h" namespace slade { -class ItemSelection; -class Camera; -namespace game +namespace gl { - class ThingType; -} + struct Vertex3D; + class Camera; + class View; + class VertexBuffer3D; + class Shader; +} // namespace gl namespace mapeditor { - struct Item; + struct Flat3D; } } // namespace slade @@ -26,219 +23,34 @@ namespace slade class MapRenderer3D { public: - // Structs - enum - { - // Common flags - TRANSADD = 2, - - // Quad/flat flags - SKY = 4, - DRAWBOTH = 128, - - // Quad flags - BACK = 8, - UPPER = 16, - LOWER = 32, - MIDTEX = 64, - - // Flat flags - CEIL = 8, - FLATFLIP = 16, - - // Thing flags - ICON = 4, - DRAWN = 8, - ZETH = 16, - }; - struct GLVertex - { - float x = 0.f, y = 0.f, z = 0.f; - float tx = 0.f, ty = 0.f; - }; - struct Quad - { - GLVertex points[4] = { {}, {}, {}, {} }; - ColRGBA colour; - ColRGBA fogcolour; - uint8_t light = 0; - unsigned texture = 0; - uint8_t flags = 0; - float alpha = 1.f; - int control_line = -1; - int control_side = -1; - - Quad() : colour{ 255, 255, 255, 255, 0 } {} - }; - struct Line - { - vector quads; - long updated_time = 0; - bool visible = true; - MapLine* line = nullptr; - }; - struct Thing - { - uint8_t flags = 0; - game::ThingType const* type = nullptr; - MapSector* sector = nullptr; - float z = 0.f; - float height = 0.f; - unsigned sprite = 0; - long updated_time = 0; - }; - struct Flat - { - uint8_t flags = 0; - uint8_t light = 255; - ColRGBA colour; - ColRGBA fogcolour; - unsigned texture = 0; - Vec2d scale; - Plane plane; - float base_alpha = 1.f; - float alpha = 1.f; - MapSector* sector = nullptr; - MapSector* control_sector = nullptr; - int extra_floor_index = -1; - long updated_time = 0; - unsigned vbo_offset = 0; - }; - - MapRenderer3D(SLADEMap* map = nullptr); + MapRenderer3D(SLADEMap* map); ~MapRenderer3D(); - bool fullbrightEnabled() const { return fullbright_; } - bool fogEnabled() const { return fog_; } - void enableFullbright(bool enable = true) { fullbright_ = enable; } - void enableFog(bool enable = true) { fog_ = enable; } - int itemDistance() const { return item_dist_; } - void enableHilight(bool render) { render_hilight_ = render; } - void enableSelection(bool render) { render_selection_ = render; } - - bool init(); - void refresh(); - void refreshTextures(); - void clearData(); - void buildSkyCircle(); - - Quad* getQuad(mapeditor::Item item); - Flat* getFlat(mapeditor::Item item); - vector>& getSectorFlats() { return sector_flats_; } - - // Camera - Camera& camera() const { return *camera_; } - void cameraApplyGravity(double mult) const; - - // -- Rendering -- - void setupView(int width, int height) const; - void setLight(const ColRGBA& colour, uint8_t light, float alpha = 1.0f) const; - void setFog(const ColRGBA& fogcol, uint8_t light); - void renderMap(); - void renderSkySlice( - float top, - float bottom, - float atop, - float abottom, - float size, - float tx = 0.125f, - float ty = 2.0f) const; - void renderSky(); - - // Flats - void updateFlatTexCoords(unsigned index, unsigned flat_index) const; - void updateSector(unsigned index); - void updateSectorFlats(unsigned index); - void updateSectorVBOs(unsigned index) const; - bool isSectorStale(unsigned index) const; - void renderFlat(const Flat* flat); - void renderFlats(); - void renderFlatSelection(const ItemSelection& selection, float alpha = 1.0f) const; + bool fogEnabled() const; + bool fullbrightEnabled() const; - // Walls - void setupQuad(Quad* quad, const Seg2d& seg, double top, double bottom) const; - void setupQuad(Quad* quad, const Seg2d& seg, const Plane& top, const Plane& bottom) const; - void setupQuadTexCoords( - Quad* quad, - int length, - double o_left, - double o_top, - double h_top, - double h_bottom, - bool pegbottom = false, - double sx = 1, - double sy = 1) const; - void updateLine(unsigned index); - void renderQuad(const Quad* quad, float alpha = 1.0f); - void renderWalls(); - void renderTransparentWalls(); - void renderWallSelection(const ItemSelection& selection, float alpha = 1.0f); + void enableHilight(bool enable = true); + void enableSelection(bool enable = true); + void enableFog(bool enable = true); + void enableFullbright(bool enable = true); - // Things - void updateThing(unsigned index, const MapThing* thing); - void renderThings(); - void renderThingSelection(const ItemSelection& selection, float alpha = 1.0f); + void render(const gl::Camera& camera); - // VBO stuff - void updateFlatsVBO(); - void updateWallsVBO() const; + void clearData(); - // Visibility checking - void quickVisDiscard(); - float calcDistFade(double distance, double max = -1) const; - void checkVisibleQuads(); - void checkVisibleFlats(); - // Hilight - mapeditor::Item determineHilight(); - void renderHilight(mapeditor::Item hilight, float alpha = 1.0f); + // Testing + unsigned flatsBufferSize() const; private: - SLADEMap* map_; - bool fullbright_ = false; - bool fog_ = true; - unsigned n_quads_ = 0; - unsigned n_flats_ = 0; - int flat_last_ = 0; - bool render_hilight_ = true; - bool render_selection_ = true; - ColRGBA fog_colour_last_; - float fog_depth_last_ = 0.f; - - // Visibility - vector dist_sectors_; - - // Camera - unique_ptr camera_; - int item_dist_ = 0; + SLADEMap* map_ = nullptr; + unique_ptr shader_3d_; - // Map Structures - vector lines_; - vector quads_; - vector quads_transparent_; - vector things_; - vector> sector_flats_; - vector flats_; + vector flats_; + unique_ptr vb_flats_; // Vertex buffer for all flats + long flats_updated_ = 0; - // VBOs - unsigned vbo_flats_ = 0; - unsigned vbo_walls_ = 0; - // Sky - struct GLVertexEx - { - float x = 0.f, y = 0.f, z = 0.f; - float tx = 0.f, ty = 0.f; - float alpha = 1.f; - }; - string skytex1_ = "SKY1"; - string skytex2_; - ColRGBA skycol_top_; - ColRGBA skycol_bottom_; - Vec2d sky_circle_[32]; - - // Signal connections - sigslot::scoped_connection sc_resources_updated_; - sigslot::scoped_connection sc_palette_changed_; + void renderFlats(); }; } // namespace slade diff --git a/src/MapEditor/Renderer/Renderer.cpp b/src/MapEditor/Renderer/Renderer.cpp index 5c48315ce..eb7a4d793 100644 --- a/src/MapEditor/Renderer/Renderer.cpp +++ b/src/MapEditor/Renderer/Renderer.cpp @@ -33,9 +33,9 @@ #include "Main.h" #include "Renderer.h" #include "App.h" -#include "Camera.h" #include "General/Clipboard.h" #include "General/ColourConfiguration.h" +#include "General/Misc.h" #include "Geometry/Geometry.h" #include "MCAnimations.h" #include "MapEditor/ClipboardItems.h" @@ -48,6 +48,7 @@ #include "MapEditor/MapEditor.h" #include "MapRenderer2D.h" #include "MapRenderer3D.h" +#include "OpenGL/Camera.h" #include "OpenGL/Draw2D.h" #include "OpenGL/LineBuffer.h" #include "OpenGL/OpenGL.h" @@ -95,6 +96,7 @@ CVAR(Bool, map_show_help, true, CVar::Flag::Save) CVAR(Int, map_crosshair, 0, CVar::Flag::Save) CVAR(Bool, map_show_selection_numbers, true, CVar::Flag::Save) CVAR(Int, map_max_selection_numbers, 1000, CVar::Flag::Save) +CVAR(Int, render_fov, 90, CVar::Flag::Save) // ----------------------------------------------------------------------------- @@ -126,6 +128,7 @@ Renderer::Renderer(MapEditContext& context) : context_{ &context } vb_grid_ = std::make_unique(); lb_crosshair_ = std::make_unique(); lb_objectedit_box_ = std::make_unique(); + camera_ = std::make_unique(Vec3f{ 0, 0, 1 }); } // ----------------------------------------------------------------------------- @@ -140,6 +143,7 @@ void Renderer::forceUpdate(bool update_2d, bool update_3d) const { if (update_2d) renderer_2d_->forceUpdate(context_->sectorEditMode() == SectorMode::Ceiling); + if (update_3d) renderer_3d_->clearData(); } @@ -337,7 +341,7 @@ void Renderer::setCameraThing(const MapThing* thing) const pos.z += sector->floor().plane.heightAt(pos.x, pos.y); // Set camera position & direction - renderer_3d_->camera().set(pos, geometry::vectorAngle(geometry::degToRad(thing->angle()))); + camera_->set(pos, geometry::vectorAngle(geometry::degToRad(thing->angle()))); } // ----------------------------------------------------------------------------- @@ -345,7 +349,7 @@ void Renderer::setCameraThing(const MapThing* thing) const // ----------------------------------------------------------------------------- Vec2d Renderer::cameraPos2D() const { - return renderer_3d_->camera().position().xy(); + return camera_->position().xy(); } // ----------------------------------------------------------------------------- @@ -353,7 +357,7 @@ Vec2d Renderer::cameraPos2D() const // ----------------------------------------------------------------------------- Vec2d Renderer::cameraDir2D() const { - return renderer_3d_->camera().direction(); + return camera_->direction(); } // ----------------------------------------------------------------------------- @@ -1143,24 +1147,8 @@ void Renderer::drawMap2d(draw2d::Context& dc) const // ----------------------------------------------------------------------------- void Renderer::drawMap3d() const { - // Setup 3d renderer view - renderer_3d_->setupView(view_->size().x, view_->size().y); - - // Render 3d map - renderer_3d_->renderMap(); - - // Draw selection if any - auto selection = context_->selection(); - renderer_3d_->renderFlatSelection(selection); - renderer_3d_->renderWallSelection(selection); - renderer_3d_->renderThingSelection(selection); - - // Draw hilight if any - if (context_->selection().hasHilight()) - renderer_3d_->renderHilight(context_->selection().hilight(), anim_flash_level_); - - // Draw animations - // drawAnimations(); + camera_->setProjection(view_->size().x, view_->size().y, 0.5f, 20000.0f, render_fov); + renderer_3d_->render(*camera_); } // ----------------------------------------------------------------------------- @@ -1179,7 +1167,7 @@ void Renderer::draw() const // Draw 2d or 3d map depending on mode if (context_->editMode() == Mode::Visual) - drawMap2d(dc); // drawMap3d(); + drawMap3d(); else drawMap2d(dc); @@ -1291,6 +1279,31 @@ void Renderer::draw() const dc.colour = ColRGBA::WHITE; dc.drawText( fmt::format("{:1.2f}ms - {} draw calls", static_cast(avg_frame) / 1000.0, draw_calls), { 0.0f, 0.0f }); + + // TESTING: Camera info + if (context_->editMode() == Mode::Visual) + { + dc.text_alignment = draw2d::Align::Right; + dc.drawText( + fmt::format( + "Position: {:1.2f},{:1.2f},{:1.2f} | Pitch: {:1.2f} | Direction: {:1.2f},{:1.2f},{:1.2f} | Up: " + "{:1.2f},{:1.2f},{:1.2f}", + camera_->position().x, + camera_->position().y, + camera_->position().z, + camera_->pitch(), + camera_->directionVector().x, + camera_->directionVector().y, + camera_->directionVector().z, + camera_->upVector().x, + camera_->upVector().y, + camera_->upVector().z), + { view_->size().x, 0.0f }); + + dc.drawText( + fmt::format("Flats vertex buffer: {}", misc::sizeAsString(renderer_3d_->flatsBufferSize())), + { view_->size().x, dc.textLineHeight() }); + } } namespace @@ -1530,22 +1543,10 @@ bool Renderer::update2dModeCrossfade(double mult) } // Clamp - if (fade_vertices_ < fa_vertices) - fade_vertices_ = fa_vertices; - if (fade_vertices_ > 1.0f) - fade_vertices_ = 1.0f; - if (fade_lines_ < fa_lines) - fade_lines_ = fa_lines; - if (fade_lines_ > 1.0f) - fade_lines_ = 1.0f; - if (fade_flats_ < fa_flats) - fade_flats_ = fa_flats; - if (fade_flats_ > 1.0f) - fade_flats_ = 1.0f; - if (fade_things_ < fa_things) - fade_things_ = fa_things; - if (fade_things_ > 1.0f) - fade_things_ = 1.0f; + fade_vertices_ = std::clamp(fade_vertices_, fa_vertices, 1.0f); + fade_lines_ = std::clamp(fade_lines_, fa_lines, 1.0f); + fade_flats_ = std::clamp(fade_flats_, fa_flats, 1.0f); + fade_things_ = std::clamp(fade_things_, fa_things, 1.0f); return anim_mode_crossfade; } @@ -1560,31 +1561,33 @@ void Renderer::animateSelectionChange(const mapeditor::Item& item, bool selected // 3d mode wall if (mapeditor::baseItemType(item.type) == ItemType::Side) { - // Get quad - auto quad = renderer_3d_->getQuad(item); - - if (quad) - { - // Get quad points - Vec3f points[4]; - for (unsigned a = 0; a < 4; a++) - points[a] = { quad->points[a].x, quad->points[a].y, quad->points[a].z }; - - // Start animation - animations_.push_back(std::make_unique(app::runTimer(), points, selected)); - } + // TODO: 3dmode + //// Get quad + // auto quad = renderer_3d_->getQuad(item); + + // if (quad) + //{ + // // Get quad points + // Vec3f points[4]; + // for (unsigned a = 0; a < 4; a++) + // points[a] = { quad->points[a].x, quad->points[a].y, quad->points[a].z }; + + // // Start animation + // animations_.push_back(std::make_unique(app::runTimer(), points, selected)); + //} } // 3d mode flat else if (item.type == ItemType::Ceiling || item.type == ItemType::Floor) { - // Get flat - auto flat = renderer_3d_->getFlat(item); - - // Start animation - if (flat) - animations_.push_back( - std::make_unique(app::runTimer(), flat->sector, flat->plane, selected)); + // TODO: 3dmode + //// Get flat + // auto flat = renderer_3d_->getFlat(item); + + //// Start animation + // if (flat) + // animations_.push_back( + // std::make_unique(app::runTimer(), flat->sector, flat->plane, selected)); } // 2d mode thing diff --git a/src/MapEditor/Renderer/Renderer.h b/src/MapEditor/Renderer/Renderer.h index 4117565ae..aee7851fe 100644 --- a/src/MapEditor/Renderer/Renderer.h +++ b/src/MapEditor/Renderer/Renderer.h @@ -10,6 +10,7 @@ class MapRenderer3D; class MCOverlay; namespace gl { + class Camera; class LineBuffer; class VertexBuffer2D; class View; @@ -35,6 +36,7 @@ namespace mapeditor MapRenderer3D& renderer3D() const { return *renderer_3d_; } gl::View& view() const { return *view_; } float uiScale() const { return ui_scale_; } + gl::Camera& camera() const { return *camera_; } void setUIScale(float scale) { ui_scale_ = scale; } void forceUpdate(bool update_2d = true, bool update_3d = true) const; @@ -75,6 +77,7 @@ namespace mapeditor unique_ptr view_; unique_ptr view_screen_; float ui_scale_ = 1.0f; + unique_ptr camera_; // OpenGL unique_ptr vb_grid_; diff --git a/src/MapEditor/UI/MapCanvas.cpp b/src/MapEditor/UI/MapCanvas.cpp index 339cbfa16..a3e8b1bf5 100644 --- a/src/MapEditor/UI/MapCanvas.cpp +++ b/src/MapEditor/UI/MapCanvas.cpp @@ -38,10 +38,10 @@ #include "MapEditor/Edit/Input.h" #include "MapEditor/MapEditContext.h" #include "MapEditor/MapEditor.h" -#include "MapEditor/Renderer/Camera.h" #include "MapEditor/Renderer/Overlays/MCOverlay.h" #include "MapEditor/Renderer/Renderer.h" #include "MapEditor/SectorBuilder.h" +#include "OpenGL/Camera.h" #include "SLADEMap/MapObject/MapLine.h" #include "SLADEMap/MapObject/MapSector.h" #include "SLADEMap/MapObjectList/LineList.h" @@ -101,7 +101,7 @@ MapCanvas::MapCanvas(wxWindow* parent, MapEditContext* context) : Bind(wxEVT_ENTER_WINDOW, &MapCanvas::onMouseEnter, this); Bind(wxEVT_SET_FOCUS, &MapCanvas::onFocus, this); Bind(wxEVT_KILL_FOCUS, &MapCanvas::onFocus, this); - Bind(wxEVT_TIMER, &MapCanvas::onRTimer, this); + timer_.Bind(wxEVT_TIMER, &MapCanvas::onRTimer, this); Bind(wxEVT_IDLE, &MapCanvas::onIdle, this); timer_.Start(map_bg_ms); @@ -141,6 +141,10 @@ void MapCanvas::lockMouse(bool lock) { if (lock) { + // Save current mouse position + mouse_locked_pos_.x = sf::Mouse::getPosition().x; + mouse_locked_pos_.y = sf::Mouse::getPosition().y; + // Center mouse mouseToCenter(); @@ -149,11 +153,21 @@ void MapCanvas::lockMouse(bool lock) img.SetMask(true); img.SetMaskColour(0, 0, 0); SetCursor(wxCursor(img)); + + log::info("Mouse locked, cursor was at ({}, {})", mouse_locked_pos_.x, mouse_locked_pos_.y); } else { // Show cursor SetCursor(wxNullCursor); + + // Move mouse back to original position (if it was initially moved to lock) + if (mouse_locked_pos_.x != -1 && mouse_locked_pos_.y != -1) + { + log::info("Mouse unlocked, moving cursor back to ({}, {})", mouse_locked_pos_.x, mouse_locked_pos_.y); + sf::Mouse::setPosition(sf::Vector2i(mouse_locked_pos_.x, mouse_locked_pos_.y)); + mouse_locked_pos_ = { -1, -1 }; + } } } @@ -163,33 +177,33 @@ void MapCanvas::lockMouse(bool lock) void MapCanvas::mouseLook3d() { // Check for 3d mode - if (context_->editMode() == Mode::Visual && context_->mouseLocked()) + if (context_->editMode() != Mode::Visual /* || !context_->mouseLocked()*/) + return; + + auto overlay_current = context_->currentOverlay(); + if (!overlay_current || !overlay_current->isActive() || (overlay_current && overlay_current->allow3dMlook())) { - auto overlay_current = context_->currentOverlay(); - if (!overlay_current || !overlay_current->isActive() || (overlay_current && overlay_current->allow3dMlook())) - { - // Get relative mouse movement (scale with dpi on macOS and Linux) - const bool useScaleFactor = (app::platform() == app::MacOS || app::platform() == app::Linux); - const double scale = useScaleFactor ? GetContentScaleFactor() : 1.; - const double threshold = scale - 1.0; + // Get relative mouse movement (scale with dpi on macOS and Linux) + const bool useScaleFactor = (app::platform() == app::MacOS || app::platform() == app::Linux); + const double scale = useScaleFactor ? GetContentScaleFactor() : 1.; + const double threshold = scale - 1.0; - wxRealPoint mouse_pos = wxGetMousePosition(); - mouse_pos.x *= scale; - mouse_pos.y *= scale; + wxRealPoint mouse_pos = wxGetMousePosition(); + mouse_pos.x *= scale; + mouse_pos.y *= scale; - const wxRealPoint screen_pos = GetScreenPosition(); - const double xpos = mouse_pos.x - screen_pos.x; - const double ypos = mouse_pos.y - screen_pos.y; + const wxRealPoint screen_pos = GetScreenPosition(); + const double xpos = mouse_pos.x - screen_pos.x; + const double ypos = mouse_pos.y - screen_pos.y; - const wxSize size = GetSize(); - const double xrel = xpos - floor(size.x * 0.5); - const double yrel = ypos - floor(size.y * 0.5); + const wxSize size = GetSize(); + const double xrel = xpos - floor(size.x * 0.5); + const double yrel = ypos - floor(size.y * 0.5); - if (fabs(xrel) > threshold || fabs(yrel) > threshold) - { - context_->camera3d().look(xrel, yrel); - mouseToCenter(); - } + if (fabs(xrel) > threshold || fabs(yrel) > threshold) + { + context_->camera3d().look(xrel, yrel); + mouseToCenter(); } } } @@ -248,7 +262,8 @@ void MapCanvas::onKeyBindPress(string_view name) void MapCanvas::update() { // Handle 3d mode mouselook - mouseLook3d(); + if (mouse_looking_) + mouseLook3d(); // Get time since last redraw auto frametime = sf_clock_->getElapsedTime().asSeconds() * 1000.0; @@ -393,7 +408,11 @@ void MapCanvas::onMouseDown(wxMouseEvent& e) else if (e.LeftDClick()) skip = context_->input().mouseDown(Input::MouseButton::Left, true); else if (e.RightDown()) - skip = context_->input().mouseDown(Input::MouseButton::Right); + { + lockMouse(true); + mouse_looking_ = true; + } + // skip = context_->input().mouseDown(Input::MouseButton::Right); else if (e.RightDClick()) skip = context_->input().mouseDown(Input::MouseButton::Right, true); else if (e.MiddleDown()) @@ -431,7 +450,11 @@ void MapCanvas::onMouseUp(wxMouseEvent& e) if (e.LeftUp()) skip = context_->input().mouseUp(Input::MouseButton::Left); else if (e.RightUp()) - skip = context_->input().mouseUp(Input::MouseButton::Right); + { + lockMouse(false); + mouse_looking_ = false; + } + // skip = context_->input().mouseUp(Input::MouseButton::Right); else if (e.MiddleUp()) skip = context_->input().mouseUp(Input::MouseButton::Middle); else if (e.Aux1Up()) @@ -495,6 +518,7 @@ void MapCanvas::onMouseWheel(wxMouseEvent& e) void MapCanvas::onMouseLeave(wxMouseEvent& e) { context_->input().mouseLeave(); + mouse_looking_ = false; e.Skip(); } @@ -533,8 +557,8 @@ void MapCanvas::onFocus(wxFocusEvent& e) { if (e.GetEventType() == wxEVT_SET_FOCUS) { - if (context_->editMode() == Mode::Visual) - context_->lockMouse(true); + // if (context_->editMode() == Mode::Visual) + // context_->lockMouse(true); } else if (e.GetEventType() == wxEVT_KILL_FOCUS) context_->lockMouse(false); diff --git a/src/MapEditor/UI/MapCanvas.h b/src/MapEditor/UI/MapCanvas.h index 6d50e8725..e42107eff 100644 --- a/src/MapEditor/UI/MapCanvas.h +++ b/src/MapEditor/UI/MapCanvas.h @@ -40,6 +40,8 @@ class MapCanvas : public GLCanvas, public KeyBindHandler unique_ptr sf_clock_; wxTimer timer_; long last_wheel_timestamp_ = -1; + bool mouse_looking_ = false; + Vec2i mouse_locked_pos_{ -1, -1 }; void update(); diff --git a/src/MapEditor/Renderer/Camera.cpp b/src/OpenGL/Camera.cpp similarity index 60% rename from src/MapEditor/Renderer/Camera.cpp rename to src/OpenGL/Camera.cpp index 8b3852b0a..b586cecc4 100644 --- a/src/MapEditor/Renderer/Camera.cpp +++ b/src/OpenGL/Camera.cpp @@ -4,18 +4,30 @@ #include "Geometry/Geometry.h" #include "Geometry/Rect.h" #include "Utility/MathStuff.h" +#include +#include using namespace slade; +using namespace gl; CVAR(Bool, mlook_invert_y, false, CVar::Flag::Save) CVAR(Float, camera_3d_sensitivity_x, 1.0f, CVar::Flag::Save) CVAR(Float, camera_3d_sensitivity_y, 1.0f, CVar::Flag::Save) +// ----------------------------------------------------------------------------- +// Camera class constructor +// ----------------------------------------------------------------------------- +Camera::Camera(const Vec3f& world_up) : world_up_{ world_up } +{ + updateVectors(); + updateProjection(); +} + // ----------------------------------------------------------------------------- // Sets the camera's [pitch] // ----------------------------------------------------------------------------- -void Camera::setPitch(double pitch) +void Camera::setPitch(float pitch) { pitch_ = pitch; @@ -26,7 +38,7 @@ void Camera::setPitch(double pitch) // ----------------------------------------------------------------------------- // Moves the camera to [position] // ----------------------------------------------------------------------------- -void Camera::setPosition(const Vec3d& position) +void Camera::setPosition(const Vec3f& position) { position_ = position; } @@ -34,7 +46,7 @@ void Camera::setPosition(const Vec3d& position) // ----------------------------------------------------------------------------- // Sets the camera [direction] // ----------------------------------------------------------------------------- -void Camera::setDirection(const Vec2d& direction) +void Camera::setDirection(const Vec2f& direction) { direction_ = direction; @@ -45,7 +57,7 @@ void Camera::setDirection(const Vec2d& direction) // ----------------------------------------------------------------------------- // Sets the camera position to [position], facing [direction] // ----------------------------------------------------------------------------- -void Camera::set(const Vec3d& position, const Vec2d& direction) +void Camera::set(const Vec3f& position, const Vec2f& direction) { // Set camera position/direction position_ = position; @@ -57,18 +69,47 @@ void Camera::set(const Vec3d& position, const Vec2d& direction) } // ----------------------------------------------------------------------------- -// Calculates and returns the camera's up vector +// Sets the camera's horizontal field of view to [fov] +// ----------------------------------------------------------------------------- +void Camera::setFov(float fov) +{ + fov_ = fov; + updateProjection(); +} + +// ----------------------------------------------------------------------------- +// Sets the camera's aspect ratio to [aspect] +// ----------------------------------------------------------------------------- +void Camera::setAspectRatio(float aspect) +{ + aspect_ = aspect; + updateProjection(); +} + +// ----------------------------------------------------------------------------- +// Sets the camera's far clipping plane to [far] // ----------------------------------------------------------------------------- -Vec3d Camera::upVector() const +void Camera::setFar(float far) { - return glm::normalize(glm::cross(strafe_, dir3d_)); + far_ = far; + updateProjection(); +} + +void Camera::setProjection(float width, float height, float near, float far, float fov_h) +{ + aspect_ = (1.6f / 1.333333f) * (width / height); + fov_ = 2 * atan(tan(geometry::degToRad(fov_h) / 2) / aspect_); + near_ = near; + far_ = far; + + updateProjection(); } // ----------------------------------------------------------------------------- // Calculates and returns a 'strafe line' from the camera position along the // strafe vector (length 1) // ----------------------------------------------------------------------------- -Seg2d Camera::strafeLine() const +Seg2f Camera::strafeLine() const { return { position_.xy(), (position_ + strafe_).xy() }; } @@ -77,7 +118,7 @@ Seg2d Camera::strafeLine() const // Moves the camera the direction it is facing by [distance]. // If [z] is false it will only be moved along x/y axes // ----------------------------------------------------------------------------- -void Camera::move(double distance, bool z) +void Camera::move(float distance, bool z) { // Move along direction vector if (z) @@ -91,16 +132,18 @@ void Camera::move(double distance, bool z) position_.x += direction_.x * distance; position_.y += direction_.y * distance; } + + updateView(); } // ----------------------------------------------------------------------------- // Rotates the camera by [angle] around the z axis // ----------------------------------------------------------------------------- -void Camera::turn(double angle) +void Camera::turn(float angle) { // Find rotated view point - Vec2d cp2d(position_.x, position_.y); - Vec2d nd = geometry::rotatePoint(cp2d, cp2d + direction_, angle); + Vec2f cp2d(position_.x, position_.y); + Vec2f nd = geometry::rotatePoint(cp2d, cp2d + direction_, angle); // Update direction direction_.x = nd.x - position_.x; @@ -113,34 +156,35 @@ void Camera::turn(double angle) // ----------------------------------------------------------------------------- // Moves the camera along the z axis by [distance] // ----------------------------------------------------------------------------- -void Camera::moveUp(double distance) +void Camera::moveUp(float distance) { position_.z += distance; + + updateView(); } // ----------------------------------------------------------------------------- // Moves the camera along the strafe axis by [distance] // ----------------------------------------------------------------------------- -void Camera::strafe(double distance) +void Camera::strafe(float distance) { position_.x += strafe_.x * distance; position_.y += strafe_.y * distance; + + updateView(); } // ----------------------------------------------------------------------------- // Rotates the camera view around the strafe axis by [amount] // ----------------------------------------------------------------------------- -void Camera::pitch(double amount) +void Camera::pitch(float amount) { // Pitch camera pitch_ += amount; // Clamp - double rad90 = math::PI * 0.5; - if (pitch_ > rad90) - pitch_ = rad90; - if (pitch_ < -rad90) - pitch_ = -rad90; + float rad90 = math::PI * 0.5f; + pitch_ = glm::clamp(pitch_, -rad90, rad90); // Update vectors updateVectors(); @@ -149,39 +193,39 @@ void Camera::pitch(double amount) // ----------------------------------------------------------------------------- // Moves the camera direction/pitch based on [xrel],[yrel] // ----------------------------------------------------------------------------- -void Camera::look(double xrel, double yrel) +void Camera::look(float xrel, float yrel) { - turn(-xrel * 0.1 * camera_3d_sensitivity_x); + turn(-xrel * 0.1f * camera_3d_sensitivity_x); if (mlook_invert_y) - pitch(yrel * 0.003 * camera_3d_sensitivity_y); + pitch(yrel * 0.003f * camera_3d_sensitivity_y); else - pitch(-yrel * 0.003 * camera_3d_sensitivity_y); + pitch(-yrel * 0.003f * camera_3d_sensitivity_y); } // ----------------------------------------------------------------------------- // Applies gravity to the camera, with the 'floor' at [floor_height] // ----------------------------------------------------------------------------- -void Camera::applyGravity(double floor_height, double mult) +void Camera::applyGravity(float floor_height, float mult) { if (position_.z > floor_height) { - double diff = position_.z - floor_height; - position_.z -= (diff * 0.3 * mult); - if (position_.z < floor_height) - position_.z = floor_height; + auto diff = position_.z - floor_height; + position_.z -= diff * 0.3f * mult; + position_.z = glm::max(position_.z, floor_height); } else if (position_.z < floor_height) { - double diff = floor_height - position_.z; - position_.z += (diff * 0.5 * mult); - if (position_.z > floor_height) - position_.z = floor_height; + auto diff = floor_height - position_.z; + position_.z += diff * 0.5f * mult; + position_.z = glm::min(position_.z, floor_height); } + + updateView(); } // ----------------------------------------------------------------------------- -// Updates the strafe and direction vectors for the camera +// Updates the strafe, direction and up vectors for the camera // ----------------------------------------------------------------------------- void Camera::updateVectors() { @@ -189,8 +233,23 @@ void Camera::updateVectors() direction_ = glm::normalize(direction_); // Calculate strafe vector - strafe_ = glm::normalize(glm::cross(Vec3d(direction_, 0), Vec3d(0, 0, 1))); + strafe_ = glm::normalize(glm::cross(Vec3f(direction_, 0), world_up_)); // Calculate 3d direction vector - dir3d_ = glm::normalize(geometry::rotateVector3D(Vec3d(direction_, 0), strafe_, pitch_)); + dir3d_ = glm::normalize(geometry::rotateVector3D(Vec3f(direction_, 0), strafe_, pitch_)); + + // Calculate up vector + up_ = glm::normalize(glm::cross(strafe_, dir3d_)); + + updateView(); +} + +void Camera::updateView() +{ + view_ = glm::lookAt(position_, position_ + dir3d_, up_); +} + +void Camera::updateProjection() +{ + projection_ = glm::perspective(fov_, aspect_, near_, far_); } diff --git a/src/OpenGL/Camera.h b/src/OpenGL/Camera.h new file mode 100644 index 000000000..cc6ef4798 --- /dev/null +++ b/src/OpenGL/Camera.h @@ -0,0 +1,72 @@ +#pragma once + +#include "Geometry/RectFwd.h" + +// These unnecessary macros are defined in windows headers somewhere... +#undef near +#undef far + +namespace slade::gl +{ +class Camera +{ +public: + Camera(const Vec3f& world_up = { 0.0f, 1.0f, 0.0f }); + + float pitch() const { return pitch_; } + const Vec3f& position() const { return position_; } + const Vec2f& direction() const { return direction_; } + float fov() const { return fov_; } + float aspect() const { return aspect_; } + float near() const { return near_; } + float far() const { return far_; } + const Vec3f& strafeVector() const { return strafe_; } + const Vec3f& directionVector() const { return dir3d_; } + const Vec3f& upVector() const { return up_; } + const glm::mat4& viewMatrix() const { return view_; } + const glm::mat4& projectionMatrix() const { return projection_; } + + void setPitch(float pitch); + void setPosition(const Vec3f& position); + void setDirection(const Vec2f& direction); + void set(const Vec3f& position, const Vec2f& direction); + void setFov(float fov); + void setAspectRatio(float aspect); + void setFar(float far); + void setProjection(float width, float height, float near, float far, float fov_h); + + Seg2f strafeLine() const; + + void move(float distance, bool z = true); + void turn(float angle); + void moveUp(float distance); + void strafe(float distance); + void pitch(float amount); + void look(float xrel, float yrel); + void applyGravity(float floor_height, float mult); + +private: + // View + Vec3f position_ = { 0.0f, 0.0f, 0.0f }; + Vec2f direction_ = { 0.0f, 1.0f }; + float pitch_ = 0.; + + // Projection + float fov_ = 0.9817477f; // Vertical fov (is 90 degrees horizontal) + float aspect_ = 1.6f; + float near_ = 0.5f; + float far_ = 20000.0f; + + // Vectors & Matrices + Vec3f dir3d_; + Vec3f strafe_; + Vec3f up_; + Vec3f world_up_; + glm::mat4 view_; + glm::mat4 projection_; + + void updateVectors(); + void updateView(); + void updateProjection(); +}; +} // namespace slade::gl diff --git a/src/OpenGL/IndexBuffer.cpp b/src/OpenGL/IndexBuffer.cpp index 0f8ebe107..93e96b4f1 100644 --- a/src/OpenGL/IndexBuffer.cpp +++ b/src/OpenGL/IndexBuffer.cpp @@ -9,7 +9,7 @@ using namespace gl; IndexBuffer::~IndexBuffer() { if (ebo_ > 0) - deleteBuffer(ebo_); + deleteEBO(ebo_); } bool IndexBuffer::bind() diff --git a/src/OpenGL/OpenGL.cpp b/src/OpenGL/OpenGL.cpp index 1d04c6a1a..7138f6c23 100644 --- a/src/OpenGL/OpenGL.cpp +++ b/src/OpenGL/OpenGL.cpp @@ -388,7 +388,7 @@ unsigned gl::currentVBO() void gl::bindVBO(unsigned id) { - if (!initialised || vbo_current == id) + if (!initialised) return; glBindBuffer(GL_ARRAY_BUFFER, id); @@ -419,7 +419,7 @@ unsigned gl::currentEBO() void gl::bindEBO(unsigned id) { - if (!initialised || ebo_current == id) + if (!initialised) return; glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); diff --git a/src/OpenGL/VertexBuffer3D.cpp b/src/OpenGL/VertexBuffer3D.cpp index 4065879bd..8a01c7d5e 100644 --- a/src/OpenGL/VertexBuffer3D.cpp +++ b/src/OpenGL/VertexBuffer3D.cpp @@ -18,16 +18,16 @@ unsigned initVAO(Buffer& buffer) buffer.bind(); // Position - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // Texture Coordinates - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); // Normal - glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5 * sizeof(float))); - glEnableVertexAttribArray(2); + // glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5 * sizeof(float))); + // glEnableVertexAttribArray(2); bindVAO(0); @@ -51,13 +51,16 @@ void VertexBuffer3D::add(const vector& vertices) vectorConcat(vertices_, vertices); } -void VertexBuffer3D::add(const glm::vec3& position, const glm::vec2& uv, const glm::vec3& normal) +void VertexBuffer3D::add(const glm::vec3& position, const glm::vec2& uv) { - vertices_.emplace_back(position, uv, normal); + vertices_.emplace_back(position, uv); } void VertexBuffer3D::push() { + if (!getContext()) + return; + // Init VAO if needed if (!vao_) vao_ = initVAO(buffer_); @@ -69,6 +72,9 @@ void VertexBuffer3D::push() void VertexBuffer3D::draw(Primitive primitive, const Shader* shader, const View* view, unsigned first, unsigned count) const { + if (!getContext()) + return; + // Check we have anything to draw if (buffer_.empty()) return; diff --git a/src/OpenGL/VertexBuffer3D.h b/src/OpenGL/VertexBuffer3D.h index 6e6d935ae..c214b0a75 100644 --- a/src/OpenGL/VertexBuffer3D.h +++ b/src/OpenGL/VertexBuffer3D.h @@ -11,9 +11,9 @@ struct Vertex3D { glm::vec3 position; glm::vec2 uv; - glm::vec3 normal; + // glm::vec3 normal; - Vertex3D(glm::vec3 position, glm::vec2 uv, glm::vec3 normal) : position{ position }, uv{ uv }, normal{ normal } {} + Vertex3D(glm::vec3 position, glm::vec2 uv) : position{ position }, uv{ uv } {} }; class VertexBuffer3D @@ -29,10 +29,7 @@ class VertexBuffer3D void add(const Vertex3D& vertex); void add(const vector& vertices); - void add( - const glm::vec3& position, - const glm::vec2& uv = glm::vec2{ 0.0f }, - const glm::vec3& normal = glm::vec3{ 0.0f }); + void add(const glm::vec3& position, const glm::vec2& uv = glm::vec2{ 0.0f }); void push(); @@ -46,6 +43,6 @@ class VertexBuffer3D private: vector vertices_; Buffer buffer_; - unsigned vao_; + unsigned vao_ = 0; }; } // namespace slade::gl diff --git a/src/SLADEMap/MapObject/MapLine.cpp b/src/SLADEMap/MapObject/MapLine.cpp index 19cdd645b..512c16885 100644 --- a/src/SLADEMap/MapObject/MapLine.cpp +++ b/src/SLADEMap/MapObject/MapLine.cpp @@ -235,7 +235,7 @@ bool MapLine::hasProp(string_view key) const // Can be prefixed with 'side1.' or 'side2.' to get bool properties from the // front and back sides respectively // ----------------------------------------------------------------------------- -bool MapLine::boolProperty(string_view key) +bool MapLine::boolProperty(string_view key) const { if (strutil::startsWith(key, "side1.") && side1_) return side1_->boolProperty(key.substr(6)); @@ -250,7 +250,7 @@ bool MapLine::boolProperty(string_view key) // Can be prefixed with 'side1.' or 'side2.' to get int properties from the // front and back sides respectively // ----------------------------------------------------------------------------- -int MapLine::intProperty(string_view key) +int MapLine::intProperty(string_view key) const { if (strutil::startsWith(key, "side1.") && side1_) return side1_->intProperty(key.substr(6)); @@ -290,7 +290,7 @@ int MapLine::intProperty(string_view key) // Can be prefixed with 'side1.' or 'side2.' to get float properties from the // front and back sides respectively // ----------------------------------------------------------------------------- -double MapLine::floatProperty(string_view key) +double MapLine::floatProperty(string_view key) const { if (strutil::startsWith(key, "side1.") && side1_) return side1_->floatProperty(key.substr(6)); @@ -305,7 +305,7 @@ double MapLine::floatProperty(string_view key) // Can be prefixed with 'side1.' or 'side2.' to get string properties from the // front and back sides respectively // ----------------------------------------------------------------------------- -string MapLine::stringProperty(string_view key) +string MapLine::stringProperty(string_view key) const { if (strutil::startsWith(key, "side1.") && side1_) return side1_->stringProperty(key.substr(6)); @@ -489,7 +489,7 @@ void MapLine::setStringProperty(string_view key, string_view value) // ----------------------------------------------------------------------------- // Returns true if the property [key] can be modified via script // ----------------------------------------------------------------------------- -bool MapLine::scriptCanModifyProp(string_view key) +bool MapLine::scriptCanModifyProp(string_view key) const { return !(key == PROP_V1 || key == PROP_V2 || key == PROP_S1 || key == PROP_S2); } diff --git a/src/SLADEMap/MapObject/MapLine.h b/src/SLADEMap/MapObject/MapLine.h index 6264dedae..2a2af503b 100644 --- a/src/SLADEMap/MapObject/MapLine.h +++ b/src/SLADEMap/MapObject/MapLine.h @@ -75,15 +75,15 @@ class MapLine : public MapObject int s2Index() const; bool hasProp(string_view key) const override; - bool boolProperty(string_view key) override; - int intProperty(string_view key) override; - double floatProperty(string_view key) override; - string stringProperty(string_view key) override; + bool boolProperty(string_view key) const override; + int intProperty(string_view key) const override; + double floatProperty(string_view key) const override; + string stringProperty(string_view key) const override; void setBoolProperty(string_view key, bool value) override; void setIntProperty(string_view key, int value) override; void setFloatProperty(string_view key, double value) override; void setStringProperty(string_view key, string_view value) override; - bool scriptCanModifyProp(string_view key) override; + bool scriptCanModifyProp(string_view key) const override; void setS1(MapSide* side); void setS2(MapSide* side); diff --git a/src/SLADEMap/MapObject/MapObject.cpp b/src/SLADEMap/MapObject/MapObject.cpp index 80996261f..3635405d0 100644 --- a/src/SLADEMap/MapObject/MapObject.cpp +++ b/src/SLADEMap/MapObject/MapObject.cpp @@ -136,7 +136,7 @@ void MapObject::copy(MapObject* c) // ----------------------------------------------------------------------------- // Returns the value of the boolean property matching [key] // ----------------------------------------------------------------------------- -bool MapObject::boolProperty(string_view key) +bool MapObject::boolProperty(string_view key) const { // If the property exists already, return it if (auto val = properties_.getIf(key)) @@ -152,7 +152,7 @@ bool MapObject::boolProperty(string_view key) // ----------------------------------------------------------------------------- // Returns the value of the integer property matching [key] // ----------------------------------------------------------------------------- -int MapObject::intProperty(string_view key) +int MapObject::intProperty(string_view key) const { // If the property exists already (as int or float), return it if (auto ival = properties_.getIf(key)) @@ -170,7 +170,7 @@ int MapObject::intProperty(string_view key) // ----------------------------------------------------------------------------- // Returns the value of the float property matching [key] // ----------------------------------------------------------------------------- -double MapObject::floatProperty(string_view key) +double MapObject::floatProperty(string_view key) const { // If the property exists already (as float or int), return it if (auto fval = properties_.getIf(key)) @@ -188,7 +188,7 @@ double MapObject::floatProperty(string_view key) // ----------------------------------------------------------------------------- // Returns the value of the string property matching [key] // ----------------------------------------------------------------------------- -string MapObject::stringProperty(string_view key) +string MapObject::stringProperty(string_view key) const { // If the property exists already, return it if (auto val = properties_.getIf(key)) diff --git a/src/SLADEMap/MapObject/MapObject.h b/src/SLADEMap/MapObject/MapObject.h index e85d826be..5d753f1fd 100644 --- a/src/SLADEMap/MapObject/MapObject.h +++ b/src/SLADEMap/MapObject/MapObject.h @@ -52,15 +52,15 @@ class MapObject virtual bool hasProp(string_view key) const { return properties_.contains(key); } // Generic property modification - virtual bool boolProperty(string_view key); - virtual int intProperty(string_view key); - virtual double floatProperty(string_view key); - virtual string stringProperty(string_view key); + virtual bool boolProperty(string_view key) const; + virtual int intProperty(string_view key) const; + virtual double floatProperty(string_view key) const; + virtual string stringProperty(string_view key) const; virtual void setBoolProperty(string_view key, bool value); virtual void setIntProperty(string_view key, int value); virtual void setFloatProperty(string_view key, double value); virtual void setStringProperty(string_view key, string_view value); - virtual bool scriptCanModifyProp(string_view key) { return true; } + virtual bool scriptCanModifyProp(string_view key) const { return true; } virtual Vec2d getPoint(Point point) { return { 0, 0 }; } diff --git a/src/SLADEMap/MapObject/MapSector.cpp b/src/SLADEMap/MapObject/MapSector.cpp index 8be95e9f8..4fc2bcadd 100644 --- a/src/SLADEMap/MapObject/MapSector.cpp +++ b/src/SLADEMap/MapObject/MapSector.cpp @@ -161,7 +161,7 @@ void MapSector::copy(MapObject* obj) // ----------------------------------------------------------------------------- // Update the last time the sector geometry changed // ----------------------------------------------------------------------------- -void MapSector::setGeometryUpdated() +void MapSector::setGeometryUpdated() const { geometry_updated_ = app::runTimer(); } @@ -169,7 +169,7 @@ void MapSector::setGeometryUpdated() // ----------------------------------------------------------------------------- // Returns the value of the string property matching [key] // ----------------------------------------------------------------------------- -string MapSector::stringProperty(string_view key) +string MapSector::stringProperty(string_view key) const { if (key == PROP_TEXFLOOR) return floor_.texture; @@ -182,7 +182,7 @@ string MapSector::stringProperty(string_view key) // ----------------------------------------------------------------------------- // Returns the value of the integer property matching [key] // ----------------------------------------------------------------------------- -int MapSector::intProperty(string_view key) +int MapSector::intProperty(string_view key) const { if (key == PROP_HEIGHTFLOOR) return floor_.height; @@ -404,7 +404,7 @@ BBox MapSector::boundingBox() // ----------------------------------------------------------------------------- // Returns the sector polygon vertices (as triangles), updating if necessary // ----------------------------------------------------------------------------- -const vector& MapSector::polygonVertices() +const vector& MapSector::polygonVertices() const { if (poly_needsupdate_) { @@ -667,7 +667,7 @@ void MapSector::changeLight(int amount, int where) // Returns the colour of the sector at [where] - 1 = floor, 2 = ceiling. // If [fullbright] is true, light level is ignored // ----------------------------------------------------------------------------- -ColRGBA MapSector::colourAt(int where, bool fullbright) +ColRGBA MapSector::colourAt(int where, bool fullbright) const { using game::UDMFFeature; @@ -778,7 +778,7 @@ ColRGBA MapSector::colourAt(int where, bool fullbright) // ----------------------------------------------------------------------------- // Returns the fog colour of the sector // ----------------------------------------------------------------------------- -ColRGBA MapSector::fogColour() +ColRGBA MapSector::fogColour() const { ColRGBA color(0, 0, 0, 0); diff --git a/src/SLADEMap/MapObject/MapSector.h b/src/SLADEMap/MapObject/MapSector.h index 7141e0534..4ac14f257 100644 --- a/src/SLADEMap/MapObject/MapSector.h +++ b/src/SLADEMap/MapObject/MapSector.h @@ -103,8 +103,8 @@ class MapSector : public MapObject short tag() const { return id_; } short id() const { return id_; } - string stringProperty(string_view key) override; - int intProperty(string_view key) override; + string stringProperty(string_view key) const override; + int intProperty(string_view key) const override; void setStringProperty(string_view key, string_view value) override; void setFloatProperty(string_view key, double value) override; @@ -129,7 +129,7 @@ class MapSector : public MapObject BBox boundingBox(); vector& connectedSides() { return connected_sides_; } const vector& connectedSides() const { return connected_sides_; } - const vector& polygonVertices(); + const vector& polygonVertices() const; void resetPolygon() { poly_needsupdate_ = true; } bool containsPoint(const Vec2d& point); double distanceTo(const Vec2d& point, double maxdist = -1); @@ -138,8 +138,8 @@ class MapSector : public MapObject bool putVertices(vector& list) const; uint8_t lightAt(int where = 0, int extra_floor_index = -1); void changeLight(int amount, int where = 0); - ColRGBA colourAt(int where = 0, bool fullbright = false); - ColRGBA fogColour(); + ColRGBA colourAt(int where = 0, bool fullbright = false) const; + ColRGBA fogColour() const; long geometryUpdatedTime() const { return geometry_updated_; } void findTextPoint(); @@ -170,15 +170,15 @@ class MapSector : public MapObject short id_ = 0; // Internal info - vector connected_sides_; - BBox bbox_; - vector polygon_triangles_; - bool poly_needsupdate_ = true; - long geometry_updated_ = 0; - Vec2d text_point_ = {}; - vector extra_floors_; - - void setGeometryUpdated(); + vector connected_sides_; + BBox bbox_; + mutable vector polygon_triangles_; + mutable bool poly_needsupdate_ = true; + mutable long geometry_updated_ = 0; + Vec2d text_point_ = {}; + vector extra_floors_; + + void setGeometryUpdated() const; }; // Note: these MUST be inline, or the linker will complain diff --git a/src/SLADEMap/MapObject/MapSide.cpp b/src/SLADEMap/MapObject/MapSide.cpp index e4e03f24b..c52458a29 100644 --- a/src/SLADEMap/MapObject/MapSide.cpp +++ b/src/SLADEMap/MapObject/MapSide.cpp @@ -264,7 +264,7 @@ void MapSide::setSector(MapSector* sector) // ----------------------------------------------------------------------------- // Returns the value of the integer property matching [key] // ----------------------------------------------------------------------------- -int MapSide::intProperty(string_view key) +int MapSide::intProperty(string_view key) const { if (key == PROP_SECTOR) { @@ -302,7 +302,7 @@ void MapSide::setIntProperty(string_view key, int value) // ----------------------------------------------------------------------------- // Returns the value of the string property matching [key] // ----------------------------------------------------------------------------- -string MapSide::stringProperty(string_view key) +string MapSide::stringProperty(string_view key) const { if (key == PROP_TEXUPPER) return tex_upper_; @@ -335,7 +335,7 @@ void MapSide::setStringProperty(string_view key, string_view value) // ----------------------------------------------------------------------------- // Returns true if the property [key] can be modified via script // ----------------------------------------------------------------------------- -bool MapSide::scriptCanModifyProp(string_view key) +bool MapSide::scriptCanModifyProp(string_view key) const { return key != PROP_SECTOR; } diff --git a/src/SLADEMap/MapObject/MapSide.h b/src/SLADEMap/MapObject/MapSide.h index 33d9320e5..c7b44e73b 100644 --- a/src/SLADEMap/MapObject/MapSide.h +++ b/src/SLADEMap/MapObject/MapSide.h @@ -53,11 +53,11 @@ class MapSide : public MapObject void setTexOffsetX(int offset); void setTexOffsetY(int offset); - int intProperty(string_view key) override; + int intProperty(string_view key) const override; void setIntProperty(string_view key, int value) override; - string stringProperty(string_view key) override; + string stringProperty(string_view key) const override; void setStringProperty(string_view key, string_view value) override; - bool scriptCanModifyProp(string_view key) override; + bool scriptCanModifyProp(string_view key) const override; void writeBackup(Backup* backup) override; void readBackup(Backup* backup) override; diff --git a/src/SLADEMap/MapObject/MapThing.cpp b/src/SLADEMap/MapObject/MapThing.cpp index a7494ad4c..2536fb3b3 100644 --- a/src/SLADEMap/MapObject/MapThing.cpp +++ b/src/SLADEMap/MapObject/MapThing.cpp @@ -124,7 +124,7 @@ Vec2d MapThing::getPoint(Point point) // ----------------------------------------------------------------------------- // Returns the value of the integer property matching [key] // ----------------------------------------------------------------------------- -int MapThing::intProperty(string_view key) +int MapThing::intProperty(string_view key) const { if (key == PROP_TYPE) return type_; @@ -159,7 +159,7 @@ int MapThing::intProperty(string_view key) // ----------------------------------------------------------------------------- // Returns the value of the float property matching [key] // ----------------------------------------------------------------------------- -double MapThing::floatProperty(string_view key) +double MapThing::floatProperty(string_view key) const { if (key == PROP_X) return position_.x; diff --git a/src/SLADEMap/MapObject/MapThing.h b/src/SLADEMap/MapObject/MapThing.h index 0cf78ec3e..db94e5f70 100644 --- a/src/SLADEMap/MapObject/MapThing.h +++ b/src/SLADEMap/MapObject/MapThing.h @@ -54,8 +54,8 @@ class MapThing : public MapObject Vec2d getPoint(Point point) override; - int intProperty(string_view key) override; - double floatProperty(string_view key) override; + int intProperty(string_view key) const override; + double floatProperty(string_view key) const override; void setIntProperty(string_view key, int value) override; void setFloatProperty(string_view key, double value) override; diff --git a/src/SLADEMap/MapObject/MapVertex.cpp b/src/SLADEMap/MapObject/MapVertex.cpp index ce01ad51b..bf75030ab 100644 --- a/src/SLADEMap/MapObject/MapVertex.cpp +++ b/src/SLADEMap/MapObject/MapVertex.cpp @@ -99,7 +99,7 @@ void MapVertex::move(double nx, double ny) // ----------------------------------------------------------------------------- // Returns the value of the integer property matching [key] // ----------------------------------------------------------------------------- -int MapVertex::intProperty(string_view key) +int MapVertex::intProperty(string_view key) const { if (key == PROP_X) return static_cast(position_.x); @@ -112,7 +112,7 @@ int MapVertex::intProperty(string_view key) // ----------------------------------------------------------------------------- // Returns the value of the float property matching [key] // ----------------------------------------------------------------------------- -double MapVertex::floatProperty(string_view key) +double MapVertex::floatProperty(string_view key) const { if (key == PROP_X) return position_.x; @@ -165,7 +165,7 @@ void MapVertex::setFloatProperty(string_view key, double value) // ----------------------------------------------------------------------------- // Returns true if the property [key] can be modified via script // ----------------------------------------------------------------------------- -bool MapVertex::scriptCanModifyProp(string_view key) +bool MapVertex::scriptCanModifyProp(string_view key) const { if (key == PROP_X || key == PROP_Y) return false; diff --git a/src/SLADEMap/MapObject/MapVertex.h b/src/SLADEMap/MapObject/MapVertex.h index 279019ac9..2471864aa 100644 --- a/src/SLADEMap/MapObject/MapVertex.h +++ b/src/SLADEMap/MapObject/MapVertex.h @@ -28,11 +28,11 @@ class MapVertex : public MapObject void move(double nx, double ny); - int intProperty(string_view key) override; - double floatProperty(string_view key) override; + int intProperty(string_view key) const override; + double floatProperty(string_view key) const override; void setIntProperty(string_view key, int value) override; void setFloatProperty(string_view key, double value) override; - bool scriptCanModifyProp(string_view key) override; + bool scriptCanModifyProp(string_view key) const override; void connectLine(MapLine* line); void disconnectLine(const MapLine* line); diff --git a/src/TextEditor/UI/SCallTip.cpp b/src/TextEditor/UI/SCallTip.cpp index 67d4d3e98..691d242e0 100644 --- a/src/TextEditor/UI/SCallTip.cpp +++ b/src/TextEditor/UI/SCallTip.cpp @@ -316,7 +316,7 @@ wxRect SCallTip::drawArgs( // Type if (!arg.type.empty()) { - wxString arg_type = arg.type == "void" ? "void" : wxString::Format("%s ", arg.type); + wxString arg_type = arg.type == "void" ? wxString("void") : wxString::Format("%s ", arg.type); if (a != arg_current_) dc.SetTextForeground(wxcol_type); left = drawText(dc, arg_type, left, top, &rect); diff --git a/src/Utility/Polygon.cpp b/src/Utility/Polygon.cpp index 56bc27a9e..cc1ef530f 100644 --- a/src/Utility/Polygon.cpp +++ b/src/Utility/Polygon.cpp @@ -35,7 +35,6 @@ #include "MathStuff.h" #include "OpenGL/GLTexture.h" #include "OpenGL/VertexBuffer2D.h" -#include "OpenGL/VertexBuffer3D.h" #include "SLADEMap/MapObject/MapLine.h" #include "SLADEMap/MapObject/MapSector.h" #include "SLADEMap/MapObject/MapSide.h" @@ -779,30 +778,4 @@ bool generateTextureCoords( return true; } - -bool generateTextureCoords( - vector& vertices, - unsigned texture, - float scale_x, - float scale_y, - float offset_x, - float offset_y, - float rotation) -{ - // Can't do this if there is no texture - if (!texture) - return false; - - // Get texture info - auto& tex_info = gl::Texture::info(texture); - auto width = static_cast(tex_info.size.x); - auto height = static_cast(tex_info.size.y); - - // Calculate texture coords - for (auto& v : vertices) - v.uv = calculateTexCoords( - v.position.x, v.position.y, width, height, scale_x, scale_y, offset_x, offset_y, rotation); - - return true; -} } // namespace slade::polygon diff --git a/src/Utility/Polygon.h b/src/Utility/Polygon.h index a74d7357d..075915862 100644 --- a/src/Utility/Polygon.h +++ b/src/Utility/Polygon.h @@ -7,22 +7,25 @@ class MapSector; namespace gl { struct Vertex2D; - struct Vertex3D; } // namespace gl namespace polygon { vector generateSectorTriangles(const MapSector& sector); - bool generateTextureCoords( - vector& vertices, - unsigned texture, - float scale_x = 1.0f, - float scale_y = 1.0f, - float offset_x = 0.0f, - float offset_y = 0.0f, - float rotation = 0.0f); + + glm::vec2 calculateTexCoords( + float x, + float y, + float tex_width, + float tex_height, + float scale_x, + float scale_y, + float offset_x, + float offset_y, + float rotation); + bool generateTextureCoords( - vector& vertices, + vector& vertices, unsigned texture, float scale_x = 1.0f, float scale_y = 1.0f,