diff --git a/include/imanipulator.h b/include/imanipulator.h index 893c353fdf..273a589380 100644 --- a/include/imanipulator.h +++ b/include/imanipulator.h @@ -11,7 +11,7 @@ typedef BasicVector2 Vector2; class Matrix4; class VolumeTest; class SelectionTest; -class RenderableCollector; +class IRenderableCollector; namespace selection { @@ -118,7 +118,7 @@ class ISceneManipulator : virtual ~ISceneManipulator() {} // Renders the manipulator's visual representation to the scene - virtual void render(RenderableCollector& collector, const VolumeTest& volume) = 0; + virtual void render(IRenderableCollector& collector, const VolumeTest& volume) = 0; // Manipulators should indicate whether component editing is supported or not virtual bool supportsComponentManipulation() const = 0; diff --git a/include/imousetool.h b/include/imousetool.h index b831185c06..bc194448d8 100644 --- a/include/imousetool.h +++ b/include/imousetool.h @@ -6,7 +6,7 @@ #include "imousetoolevent.h" class RenderSystem; -class RenderableCollector; +class IRenderableCollector; class VolumeTest; namespace ui @@ -128,7 +128,7 @@ class MouseTool // For in-scene rendering of active mousetools they need implement this method. // Any needed shaders should be acquired on-demand from the attached rendersystem. // Renderable objects need to be submitted to the given RenderableCollector. - virtual void render(RenderSystem& renderSystem, RenderableCollector& collector, const VolumeTest& volume) + virtual void render(RenderSystem& renderSystem, IRenderableCollector& collector, const VolumeTest& volume) {} }; typedef std::shared_ptr MouseToolPtr; diff --git a/include/irender.h b/include/irender.h index 279d792186..8c41a298e1 100644 --- a/include/irender.h +++ b/include/irender.h @@ -1,10 +1,14 @@ #pragma once #include "imodule.h" +#include "iwindingrenderer.h" +#include "isurfacerenderer.h" #include +#include #include "math/Vector3.h" #include "math/AABB.h" +#include "render/ArbitraryMeshVertex.h" #include "ishaderlayer.h" #include @@ -117,6 +121,8 @@ typedef BasicVector3 Vector3; class Shader; typedef std::shared_ptr ShaderPtr; +struct RenderableGeometry; + /** * A RenderEntity represents a map entity as seen by the renderer. * It provides up to 12 numbered parameters to the renderer: @@ -384,7 +390,9 @@ typedef std::shared_ptr MaterialPtr; * which use it -- the actual rendering is performed by traversing a list of * Shaders and rendering the geometry attached to each one. */ -class Shader +class Shader : + public render::IWindingRenderer, + public render::ISurfaceRenderer { public: // Observer interface to get notified on (un-)realisation @@ -423,6 +431,10 @@ class Shader const LightSources* lights = nullptr, const IRenderEntity* entity = nullptr) = 0; +#ifdef RENDERABLE_GEOMETRY + virtual void addGeometry(RenderableGeometry& geometry) = 0; +#endif + /** * \brief * Control the visibility of this shader. @@ -467,6 +479,14 @@ typedef std::shared_ptr ShaderPtr; const char* const MODULE_RENDERSYSTEM("ShaderCache"); +// Render view type enumeration, is used to select (or leave out) +// Shader objects during rendering +enum class RenderViewType +{ + Camera = 1 << 0, + OrthoView = 1 << 1, +}; + /** * \brief * The main interface for the backend renderer. @@ -514,7 +534,8 @@ class RenderSystem * \param viewer * Location of the viewer in world space. */ - virtual void render(RenderStateFlags globalFlagsMask, + virtual void render(RenderViewType renderViewType, + RenderStateFlags globalFlagsMask, const Matrix4& modelview, const Matrix4& projection, const Vector3& viewer) = 0; diff --git a/include/irenderable.h b/include/irenderable.h index 111dbd69fd..25d125bcae 100644 --- a/include/irenderable.h +++ b/include/irenderable.h @@ -1,7 +1,9 @@ #pragma once #include +#include "math/Vector3.h" +class ArbitraryMeshVertex; class Shader; typedef std::shared_ptr ShaderPtr; @@ -14,22 +16,50 @@ class Matrix4; class IRenderEntity; class RendererLight; class LitObject; +class Renderable; +class VolumeTest; + +#ifdef RENDERABLE_GEOMETRY +// Contains the vertex and index data to render geometry of the given type +struct RenderableGeometry +{ + enum class Type + { + Triangles, + Quads, + Polygons, + }; + + // The primitive type which will be translated to openGL enums + virtual Type getType() const = 0; + + // Data as needed by glDrawArrays + + virtual const Vector3& getFirstVertex() = 0; + virtual std::size_t getVertexStride() = 0; + virtual const unsigned int& getFirstIndex() = 0; + virtual std::size_t getNumIndices() = 0; +}; +#endif /** * \brief Class which accepts OpenGLRenderable objects during the first pass of * rendering. * - * Each Renderable in the scenegraph is passed a reference to a - * RenderableCollector, to which the Renderable submits its OpenGLRenderable(s) + * Each Renderable in the scenegraph is passed a reference to an + * IRenderableCollector, to which the Renderable submits its OpenGLRenderable(s) * for later rendering. A single Renderable may submit more than one * OpenGLRenderable, with different options each time -- for instance a * Renderable model class may submit each of its material surfaces separately * with different shaders. */ -class RenderableCollector +class IRenderableCollector { public: - virtual ~RenderableCollector() {} + virtual ~IRenderableCollector() {} + + // Process the given renderable object + virtual void processRenderable(const Renderable& renderable, const VolumeTest& volume) = 0; /** * \brief Submit a renderable object. @@ -71,6 +101,13 @@ class RenderableCollector const LitObject* litObject = nullptr, const IRenderEntity* entity = nullptr) = 0; + /** + * Submits a renderable object that is used for highlighting an object. + * Depending on the view, this might be a coloured, transparent overlay + * or a wireframe outline. + */ + virtual void addHighlightRenderable(const OpenGLRenderable& renderable, const Matrix4& localToWorld) = 0; + /** * \brief Submit a light source for the render operation. * @@ -108,9 +145,17 @@ class RenderableCollector }; virtual void setHighlightFlag(Highlight::Flags flags, bool enabled) = 0; -}; -class VolumeTest; + // Returns true if the current set of highlight flags is not empty + virtual bool hasHighlightFlags() const = 0; + +#ifdef RENDERABLE_GEOMETRY + // Submits renderable geometry to the collector, it will only rendered in the current frame + // Flags are a combination of Highlight::Flags + virtual void addGeometry(RenderableGeometry& geometry, std::size_t flags) + {} +#endif +}; /** * \brief @@ -133,18 +178,31 @@ class Renderable */ virtual void setRenderSystem(const RenderSystemPtr& renderSystem) = 0; + // Called in preparation of rendering this node + virtual void onPreRender(const VolumeTest& volume) + {} + + // Returns true if this renderable makes use of a non-identity model matrix, + // or submit their geometry in final world coordinates. + // Geometry of renderables returning true will not be streamlined into a larger buffer + virtual bool isOriented() const + { + return false; // by default, renderables render in world coordinates + } + /// Submit renderable geometry when rendering in Solid mode. - virtual void renderSolid(RenderableCollector& collector, + virtual void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const = 0; /// Submit renderable geometry when rendering in Wireframe mode. - virtual void renderWireframe(RenderableCollector& collector, + virtual void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const = 0; - virtual void renderComponents(RenderableCollector&, const VolumeTest&) const - { } + // Submit renderable geometry for highlighting the object + virtual void renderHighlights(IRenderableCollector& collector, + const VolumeTest& volume) = 0; - virtual void viewChanged() const + virtual void renderComponents(IRenderableCollector&, const VolumeTest&) const { } struct Highlight diff --git a/include/isurfacerenderer.h b/include/isurfacerenderer.h new file mode 100644 index 0000000000..31b43e6489 --- /dev/null +++ b/include/isurfacerenderer.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include "render/ArbitraryMeshVertex.h" + +namespace render +{ + +enum class SurfaceIndexingType +{ + Triangles, + Quads, +}; + +/** + * A surface renderer accepts a variable number of indexed surfaces and arranges + * them into one or more continuous blocks of vertices for efficient rendering. + * + * The internal arrangement has the goal of reducing the amount of draw calls for + * suraces sharing a single material. Allocating a surface slot yields a handle which + * allows for later update or deallocation of the slot. + */ +class ISurfaceRenderer +{ +public: + virtual ~ISurfaceRenderer() {} + + using Slot = std::uint64_t; + static constexpr Slot InvalidSlot = std::numeric_limits::max(); + + // Allocate a slot to hold the given surface data of the given size + // Returns the handle which can be used to update or deallocate the data later + // The indexType determines the primitive GLenum that is chosen to render this surface + virtual Slot addSurface(SurfaceIndexingType indexType, + const std::vector& vertices, + const std::vector& indices) = 0; + + // Releases a previously allocated slot. This invalidates the handle. + virtual void removeSurface(Slot slot) = 0; + + // Sets the surface data + virtual void updateSurface(Slot slot, const std::vector& vertices, + const std::vector& indices) = 0; + + // Submits the geometry of a single surface slot to GL + virtual void renderSurface(Slot slot) = 0; +}; + +} diff --git a/include/iwindingrenderer.h b/include/iwindingrenderer.h new file mode 100644 index 0000000000..520b7d07bd --- /dev/null +++ b/include/iwindingrenderer.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include "render/ArbitraryMeshVertex.h" + +namespace render +{ + +/** + * A winding renderer accepts a variable number of windings and arranges them into + * one or more continuous blocks of vertices for efficient rendering. + * + * The internal arrangement has the goal of reducing the amount of draw calls for + * winding sharing a single material. Allocating a winding slot yields a handle which + * allows for later update or deallocation of the slot. + * + * Only the vertex data (XYZ, UV, Normals, Colour) needs to be submitted, + * the render indices of each winding slot are handled internally. + */ +class IWindingRenderer +{ +public: + virtual ~IWindingRenderer() {} + + using Slot = std::uint64_t; + static constexpr Slot InvalidSlot = std::numeric_limits::max(); + + // Allocate a slot to hold the vertex data of a winding of the given size + // Returns the handle which can be used to update or deallocate the data later + virtual Slot addWinding(const std::vector& vertices) = 0; + + // Releases a previously allocated winding slot. This invalidates the handle. + virtual void removeWinding(Slot slot) = 0; + + // Sets the winding data + virtual void updateWinding(Slot slot, const std::vector& vertices) = 0; + + // Mode used to specify how to render a single winding + enum class RenderMode + { + Triangles, + Polygon, + }; + + // Submits a single winding to GL + virtual void renderWinding(RenderMode mode, Slot slot) = 0; +}; + +} diff --git a/libs/debugging/ScopedDebugTimer.h b/libs/debugging/ScopedDebugTimer.h index 73cbe870c5..8aa6bf8598 100644 --- a/libs/debugging/ScopedDebugTimer.h +++ b/libs/debugging/ScopedDebugTimer.h @@ -91,10 +91,8 @@ class ScopedDebugTimer ScopedDebugTimer(const std::string& name, bool showFps = false) : _op(name), _fps(showFps) { -#ifndef NDEBUG // Save start time gettimeofday(&_s, NULL); -#endif } /** @@ -102,7 +100,6 @@ class ScopedDebugTimer */ ~ScopedDebugTimer() { -#ifndef NDEBUG // Get the current time timeval end; gettimeofday(&end, NULL); @@ -121,6 +118,5 @@ class ScopedDebugTimer } stream << std::endl; -#endif } }; diff --git a/libs/render.h b/libs/render.h index bb31dbc5ba..ba878bef42 100644 --- a/libs/render.h +++ b/libs/render.h @@ -281,6 +281,12 @@ class RenderablePointVector : void push_back(const VertexCb& point) { _vector.push_back(point); } + + template + VertexCb& emplace_back(Args&&... args) + { + return _vector.emplace_back(std::forward(args)...); + } }; diff --git a/libs/render/CamRenderer.h b/libs/render/CamRenderer.h index d725ec4346..d6d6c15366 100644 --- a/libs/render/CamRenderer.h +++ b/libs/render/CamRenderer.h @@ -1,19 +1,19 @@ #pragma once -#include "irenderable.h" +#include "render/RenderableCollectorBase.h" #include "imap.h" #include "ivolumetest.h" #include "VectorLightList.h" -#include +#include namespace render { /// RenderableCollector for use with 3D camera views or preview widgets class CamRenderer : - public RenderableCollector + public RenderableCollectorBase { public: struct HighlightShaders @@ -62,7 +62,7 @@ class CamRenderer : // Renderables added with addLitObject() need to be stored until their // light lists can be calculated, which can't happen until all the lights // are submitted too. - std::map _litRenderables; + std::unordered_map _litRenderables; // Intersect all received renderables wiith lights void calculateLightIntersections() @@ -93,6 +93,33 @@ class CamRenderer : _shaders(shaders) {} + void prepare() + { + _totalLights = 0; + _visibleLights = 0; + _editMode = GlobalMapModule().getEditMode(); + } + + void cleanup() + { + // Keep the shader map intact, but clear the renderables vectors, + // so that we don't have to re-allocate the whole memory every frame + // Purge the ones that have not been used in this render round + for (auto i = _litRenderables.begin(); i != _litRenderables.end();) + { + if (i->second.empty()) + { + // This shader has not been used at all in the last frame, free the memory + _litRenderables.erase(i++); + continue; + } + + (i++)->second.clear(); + } + + _sceneLights.clear(); + } + /** * \brief * Instruct the CamRenderer to push its sorted renderables to their @@ -113,13 +140,12 @@ class CamRenderer : } // Render objects with calculated light lists - for (auto i = _litRenderables.begin(); i != _litRenderables.end(); ++i) + for (const auto& pair : _litRenderables) { - Shader* shader = i->first; - wxASSERT(shader); - for (auto j = i->second.begin(); j != i->second.end(); ++j) + Shader* shader = pair.first; + assert(shader); + for (const LitRenderable& lr : pair.second) { - const LitRenderable& lr = *j; shader->addRenderable(lr.renderable, lr.local2World, useLights ? &lr.lights : nullptr, lr.entity); @@ -137,6 +163,11 @@ class CamRenderer : bool supportsFullMaterials() const override { return true; } + bool hasHighlightFlags() const override + { + return _flags != 0; + } + void setHighlightFlag(Highlight::Flags flags, bool enabled) override { if (enabled) @@ -166,57 +197,92 @@ class CamRenderer : ++_totalLights; } + void processRenderable(const Renderable& renderable, const VolumeTest& volume) override + { + renderable.renderSolid(*this, volume); + } + void addRenderable(Shader& shader, const OpenGLRenderable& renderable, const Matrix4& localToWorld, const LitObject* litObject = nullptr, const IRenderEntity* entity = nullptr) override + { + addHighlightRenderable(renderable, localToWorld); + + // Construct an entry for this shader in the map if it is the first + // time we've seen it + auto iter = _litRenderables.find(&shader); + if (iter == _litRenderables.end()) + { + // Add an entry for this shader, and pre-allocate some space in the + // vector to avoid too many expansions during scenegraph traversal. + LitRenderables emptyList; + emptyList.reserve(1024); + + auto result = _litRenderables.emplace(&shader, std::move(emptyList)); + assert(result.second); + iter = result.first; + } + assert(iter != _litRenderables.end()); + assert(iter->first == &shader); + + // Store a LitRenderable object for this renderable + LitRenderable lr { renderable, litObject, localToWorld, entity }; + iter->second.emplace_back(std::move(lr)); + } + + void addHighlightRenderable(const OpenGLRenderable& renderable, const Matrix4& localToWorld) override { if (_editMode == IMap::EditMode::Merge && (_flags & Highlight::Flags::MergeAction) != 0) { const auto& mergeShader = (_flags & Highlight::Flags::MergeActionAdd) != 0 ? _shaders.mergeActionShaderAdd : - (_flags & Highlight::Flags::MergeActionRemove) != 0 ? _shaders.mergeActionShaderRemove : + (_flags & Highlight::Flags::MergeActionRemove) != 0 ? _shaders.mergeActionShaderRemove : (_flags & Highlight::Flags::MergeActionConflict) != 0 ? _shaders.mergeActionShaderConflict : _shaders.mergeActionShaderChange; - + if (mergeShader) { - mergeShader->addRenderable(renderable, localToWorld, nullptr, entity); + mergeShader->addRenderable(renderable, localToWorld, nullptr, nullptr); } } if ((_flags & Highlight::Flags::Primitives) != 0 && _shaders.primitiveHighlightShader) { - _shaders.primitiveHighlightShader->addRenderable(renderable, localToWorld, nullptr, entity); + _shaders.primitiveHighlightShader->addRenderable(renderable, localToWorld, nullptr, nullptr); } if ((_flags & Highlight::Flags::Faces) != 0 && _shaders.faceHighlightShader) { - _shaders.faceHighlightShader->addRenderable(renderable, localToWorld, nullptr, entity); + _shaders.faceHighlightShader->addRenderable(renderable, localToWorld, nullptr, nullptr); } + } - // Construct an entry for this shader in the map if it is the first - // time we've seen it - auto iter = _litRenderables.find(&shader); - if (iter == _litRenderables.end()) +#ifdef RENDERABLE_GEOMETRY + void addGeometry(RenderableGeometry& geometry, std::size_t flags) override + { + if (_editMode == IMap::EditMode::Merge && (flags & Highlight::Flags::MergeAction) != 0) { - // Add an entry for this shader, and pre-allocate some space in the - // vector to avoid too many expansions during scenegraph traversal. - LitRenderables emptyList; - emptyList.reserve(1024); + const auto& mergeShader = (flags & Highlight::Flags::MergeActionAdd) != 0 ? _shaders.mergeActionShaderAdd : + (flags & Highlight::Flags::MergeActionRemove) != 0 ? _shaders.mergeActionShaderRemove : + (flags & Highlight::Flags::MergeActionConflict) != 0 ? _shaders.mergeActionShaderConflict : _shaders.mergeActionShaderChange; - auto result = _litRenderables.insert( - std::make_pair(&shader, std::move(emptyList)) - ); - wxASSERT(result.second); - iter = result.first; + if (mergeShader) + { + mergeShader->addGeometry(geometry); + } } - wxASSERT(iter != _litRenderables.end()); - wxASSERT(iter->first == &shader); - // Store a LitRenderable object for this renderable - LitRenderable lr { renderable, litObject, localToWorld, entity }; - iter->second.push_back(std::move(lr)); + if ((flags & Highlight::Flags::Primitives) != 0 && _shaders.primitiveHighlightShader) + { + _shaders.primitiveHighlightShader->addGeometry(geometry); + } + + if ((flags & Highlight::Flags::Faces) != 0 && _shaders.faceHighlightShader) + { + _shaders.faceHighlightShader->addGeometry(geometry); + } } +#endif }; diff --git a/libs/render/CompactWindingVertexBuffer.h b/libs/render/CompactWindingVertexBuffer.h new file mode 100644 index 0000000000..10633d6aa2 --- /dev/null +++ b/libs/render/CompactWindingVertexBuffer.h @@ -0,0 +1,223 @@ +#pragma once + +#include +#include + +namespace render +{ + +// Winding index provider. Generates render indices for a single winding suitable for rendering triangles. +class WindingIndexer_Triangles +{ +public: + constexpr static std::size_t GetNumberOfIndicesPerWinding(const std::size_t windingSize) + { + return 3 * (windingSize - 2); + } + + // Generate indices for a single winding of the given size, insert it in the target container using the given output iterator + // each index is shifted by the given offset + static void GenerateAndAssignIndices(std::back_insert_iterator> outputIt, + std::size_t windingSize, const unsigned int offset) + { + for (auto n = static_cast(windingSize) - 1; n - 1 > 0; --n) + { + outputIt = offset + 0; + outputIt = offset + n; + outputIt = offset + n - 1; + } + } +}; + +// Winding index provider. Generates render indices for a single winding suitable for rendering lines. +class WindingIndexer_Lines +{ +public: + constexpr static std::size_t GetNumberOfIndicesPerWinding(const std::size_t windingSize) + { + return windingSize * 2; // 2 indices per winding + } + + // Generate indices for a single winding of the given size, insert it in the target container using the given output iterator + // each index is shifted by the given offset + static void GenerateAndAssignIndices(std::back_insert_iterator> outputIt, + std::size_t windingSize, const unsigned int offset) + { + for (unsigned int n = 0; n < windingSize - 1; ++n) + { + outputIt = offset + n + 0; + outputIt = offset + n + 1; + } + + outputIt = offset + static_cast(windingSize) - 1; + outputIt = offset; // the last index points at the first vertex + } +}; + +// Winding index provider. Generates render indices for a single winding suitable for drawing a single polygon. +class WindingIndexer_Polygon +{ +public: + constexpr static std::size_t GetNumberOfIndicesPerWinding(const std::size_t windingSize) + { + return windingSize; + } + + // Generate indices for a single winding of the given size, insert it in the target container using the given output iterator + // each index is shifted by the given offset + static void GenerateAndAssignIndices(std::back_insert_iterator> outputIt, + std::size_t windingSize, const unsigned int offset) + { + for (auto n = static_cast(windingSize); n > 0; --n) + { + outputIt = offset + n - 1; + } + } +}; + +template +class CompactWindingVertexBuffer +{ +private: + std::size_t _size; + + std::vector _vertices; + + // The indices suitable for rendering triangles + std::vector _indices; + +public: + using Slot = std::uint32_t; + + explicit CompactWindingVertexBuffer(std::size_t size) : + _size(size) + {} + + CompactWindingVertexBuffer(const CompactWindingVertexBuffer& other) = delete; + CompactWindingVertexBuffer& operator=(const CompactWindingVertexBuffer& other) = delete; + + // Move ctor + CompactWindingVertexBuffer(CompactWindingVertexBuffer&& other) noexcept : + _size(other._size), + _vertices(std::move(other._vertices)), + _indices(std::move(other._indices)) + {} + + std::size_t getWindingSize() const + { + return _size; + } + + std::size_t getNumIndicesPerWinding() const + { + return WindingIndexerT::GetNumberOfIndicesPerWinding(_size); + } + + const std::vector& getVertices() const + { + return _vertices; + } + + const std::vector& getIndices() const + { + return _indices; + } + + // Appends the given winding data to the end of the buffer, returns the position in the array + Slot pushWinding(const std::vector& winding) + { + assert(winding.size() == _size); + + const auto currentSize = _vertices.size(); + + std::copy(winding.begin(), winding.end(), std::back_inserter(_vertices)); + + WindingIndexerT::GenerateAndAssignIndices(std::back_inserter(_indices), _size, static_cast(currentSize)); + + auto position = currentSize / _size; + return static_cast(position); + } + + // Replaces the winding in the given slot with the given data + void replaceWinding(Slot slot, const std::vector& winding) + { + assert(winding.size() == _size); + + // Copy the incoming data to the target slot + std::copy(winding.begin(), winding.end(), _vertices.begin() + (slot * _size)); + + // Indices remain unchanged + } + + // Removes the winding from the given slot. All slots greater than the given one + // will be shifted towards the left, their values are shifted by -1 + // Invalid slot indices will result in a std::logic_error + void removeWinding(Slot slot) + { + const auto currentSize = _vertices.size(); + + if (slot >= currentSize / _size) throw std::logic_error("Slot index out of bounds"); + + // Remove _size elements at the given position + auto firstVertexToRemove = _vertices.begin() + (slot * _size); + _vertices.erase(firstVertexToRemove, firstVertexToRemove + _size); + + // Since all the windings have the same structure, the index array will always look the same + // after shifting the index values of the remaining windings. + // So just cut off one winding from the end of the index array + _indices.resize(_indices.size() - getNumIndicesPerWinding()); + } + + // Removes multiple slots from this buffer in one sweep. The given array of slots must be sorted in ascending order + void removeWindings(const std::vector& slotsToRemove) + { + if (slotsToRemove.empty()) return; + + auto highestPossibleSlotNumber = static_cast(_vertices.size() / _size); + + auto s = slotsToRemove.begin(); + auto gapStart = *s; // points at the first position that can be overwritten + + while (s != slotsToRemove.end()) + { + auto slotToRemove = *s; + + if (slotToRemove >= highestPossibleSlotNumber) throw std::logic_error("Slot index out of bounds"); + + // Move forward until we hit the next unremoved slot + auto firstSlotToKeep = slotToRemove + 1; + ++s; + + while (s != slotsToRemove.end() && *s == firstSlotToKeep) + { + ++firstSlotToKeep; + ++s; + } + + auto lastSlotToKeep = s == slotsToRemove.end() ? highestPossibleSlotNumber - 1 : *s - 1; + auto numSlotsToMove = lastSlotToKeep + 1 - firstSlotToKeep; + + if (numSlotsToMove == 0) break; + + // We move all vertices to the gap + auto target = _vertices.begin() + (gapStart * _size); + + auto sourceStart = _vertices.begin() + (firstSlotToKeep * _size); + auto sourceEnd = sourceStart + (numSlotsToMove * _size); + + std::move(sourceStart, sourceEnd, target); + + gapStart += numSlotsToMove; + } + + // Cut off the now unused range at the end + _vertices.resize(_vertices.size() - slotsToRemove.size() * _size); + + // Since all the windings have the same structure, the index array will always look the same + // after shifting the index values of the remaining windings. + // So just cut off one winding from the end of the index array + _indices.resize(_indices.size() - slotsToRemove.size() * getNumIndicesPerWinding()); + } +}; + +} diff --git a/libs/render/IndexedVertexBuffer.h b/libs/render/IndexedVertexBuffer.h index 970fdcf2c9..f2c2210e29 100644 --- a/libs/render/IndexedVertexBuffer.h +++ b/libs/render/IndexedVertexBuffer.h @@ -1,6 +1,8 @@ #pragma once +#include "render.h" #include +#include "render/VBO.h" #include "GLProgramAttributes.h" @@ -17,7 +19,7 @@ namespace render * raw vertex geometry. */ template -class IndexedVertexBuffer +class IndexedVertexBuffer final { public: typedef std::vector Vertices; @@ -80,6 +82,11 @@ class IndexedVertexBuffer std::copy(begin, end, std::back_inserter(_vertices)); } + typename Vertices::size_type getNumVertices() const + { + return _vertices.size(); + } + /// Add a batch of indices template void addIndexBatch(Iter_T begin, std::size_t count) @@ -101,6 +108,37 @@ class IndexedVertexBuffer } } + /// Add a batch of indices + template + void addIndicesToLastBatch(Iter_T begin, std::size_t count, Indices::value_type offset = 0) + { + if (_batches.empty()) + { + addIndexBatch(begin, count); + return; + } + + if (count < 1) + { + throw std::logic_error("Batch must contain at least one index"); + } + + auto& batch = *_batches.rbegin(); + batch.size += count; + + // Append the indices + _indices.reserve(_indices.size() + count); + + for (Iter_T i = begin; i < begin + count; ++i) + { + _indices.push_back(*i + offset); + } + + // Invalidate the vertex and index buffers + deleteVBO(_vertexVBO); + deleteVBO(_indexVBO); + } + /** * \brief * Replace all data with that from another IndexedVertexBuffer diff --git a/libs/render/RenderableCollectionWalker.h b/libs/render/RenderableCollectionWalker.h index 0e4f8a120b..8c1461c88b 100644 --- a/libs/render/RenderableCollectionWalker.h +++ b/libs/render/RenderableCollectionWalker.h @@ -1,11 +1,9 @@ #pragma once #include "iselection.h" -#include "imapmerge.h" -#include "ientity.h" -#include "ieclass.h" #include "iscenegraph.h" #include +#include "render/RenderableCollectorBase.h" namespace render { @@ -13,166 +11,30 @@ namespace render /** * \brief * Scenegraph walker class that finds all renderable objects and adds them to a - * contained RenderableCollector. - * - * Also provides support for highlighting selected objects by activating the - * RenderableCollector's "highlight" flags based on the renderable object's - * selection state. + * given RenderableCollector. */ -class RenderableCollectionWalker : - public scene::Graph::Walker +class RenderableCollectionWalker { -private: - // The collector which is sorting our renderables - RenderableCollector& _collector; - - // The view we're using for culling - const VolumeTest& _volume; - - // Construct with RenderableCollector to receive renderables - RenderableCollectionWalker(RenderableCollector& collector, const VolumeTest& volume) : - _collector(collector), - _volume(volume) - {} - public: - void dispatchRenderable(const Renderable& renderable) - { - if (_collector.supportsFullMaterials()) - { - renderable.renderSolid(_collector, _volume); - } - else - { - renderable.renderWireframe(_collector, _volume); - } - } - - // scene::Graph::Walker implementation - bool visit(const scene::INodePtr& node) - { - // greebo: Highlighting propagates to child nodes - scene::INodePtr parent = node->getParent(); - - node->viewChanged(); - - std::size_t highlightFlags = node->getHighlightFlags(); - - auto nodeType = node->getNodeType(); - - // Particle nodes shouldn't inherit the highlight flags from their parent, - // as it obstructs the view when their wireframe gets rendered (#5682) - if (parent && nodeType != scene::INode::Type::Particle) - { - highlightFlags |= parent->getHighlightFlags(); - } - - if (nodeType == scene::INode::Type::MergeAction) - { - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeAction, true); - - auto mergeActionNode = std::dynamic_pointer_cast(node); - assert(mergeActionNode); - - switch (mergeActionNode->getActionType()) - { - case scene::merge::ActionType::AddChildNode: - case scene::merge::ActionType::AddEntity: - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionAdd, true); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionChange, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionRemove, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionConflict, false); - break; - - case scene::merge::ActionType::AddKeyValue: - case scene::merge::ActionType::ChangeKeyValue: - case scene::merge::ActionType::RemoveKeyValue: - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionChange, true); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionAdd, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionRemove, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionConflict, false); - break; - - case scene::merge::ActionType::RemoveChildNode: - case scene::merge::ActionType::RemoveEntity: - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionRemove, true); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionAdd, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionChange, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionConflict, false); - break; - - case scene::merge::ActionType::ConflictResolution: - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionConflict, true); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionAdd, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionChange, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionRemove, false); - break; - } - } - else - { - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeAction, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionAdd, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionChange, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionRemove, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::MergeActionConflict, false); - } - - if (highlightFlags & Renderable::Highlight::Selected) - { - if (GlobalSelectionSystem().Mode() != selection::SelectionSystem::eComponent) - { - _collector.setHighlightFlag(RenderableCollector::Highlight::Faces, true); - } - else - { - _collector.setHighlightFlag(RenderableCollector::Highlight::Faces, false); - node->renderComponents(_collector, _volume); - } - - _collector.setHighlightFlag(RenderableCollector::Highlight::Primitives, true); - - // Pass on the info about whether we have a group member selected - if (highlightFlags & Renderable::Highlight::GroupMember) - { - _collector.setHighlightFlag(RenderableCollector::Highlight::GroupMember, true); - } - else - { - _collector.setHighlightFlag(RenderableCollector::Highlight::GroupMember, false); - } - } - else - { - _collector.setHighlightFlag(RenderableCollector::Highlight::Primitives, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::Faces, false); - _collector.setHighlightFlag(RenderableCollector::Highlight::GroupMember, false); - } - - dispatchRenderable(*node); - - return true; - } - /** * \brief * Use a RenderableCollectionWalker to find all renderables in the global * scenegraph. */ - static void CollectRenderablesInScene(RenderableCollector& collector, const VolumeTest& volume) + static void CollectRenderablesInScene(RenderableCollectorBase& collector, const VolumeTest& volume) { - // Instantiate a new walker class - RenderableCollectionWalker renderHighlightWalker(collector, volume); - // Submit renderables from scene graph - GlobalSceneGraph().foreachVisibleNodeInVolume(volume, renderHighlightWalker); + GlobalSceneGraph().foreachVisibleNodeInVolume(volume, [&](const scene::INodePtr& node) + { + collector.processNode(node, volume); + return true; + }); // Submit any renderables that have been directly attached to the RenderSystem // without belonging to an actual scene object - RenderableCollectionWalker walker(collector, volume); GlobalRenderSystem().forEachRenderable([&](const Renderable& renderable) { - walker.dispatchRenderable(renderable); + collector.processRenderable(renderable, volume); }); } }; diff --git a/libs/render/RenderableCollectorBase.h b/libs/render/RenderableCollectorBase.h new file mode 100644 index 0000000000..87001f21a7 --- /dev/null +++ b/libs/render/RenderableCollectorBase.h @@ -0,0 +1,137 @@ +#pragma once + +#include "irenderable.h" +#include "imapmerge.h" +#include "inode.h" +#include "iselection.h" + +namespace render +{ + +/** + * Front end renderer base implementation shared by CamRenderer and XYRenderer + * + * Provides support for highlighting selected objects by activating the + * RenderableCollector's "highlight" flags based on the renderable object's + * selection state. + */ +class RenderableCollectorBase : + public IRenderableCollector +{ +public: + virtual void processNode(const scene::INodePtr& node, const VolumeTest& volume) + { + node->onPreRender(volume); + + // greebo: Highlighting propagates to child nodes + scene::INodePtr parent = node->getParent(); + + std::size_t highlightFlags = node->getHighlightFlags(); + + auto nodeType = node->getNodeType(); + + // Particle nodes shouldn't inherit the highlight flags from their parent, + // as it obstructs the view when their wireframe gets rendered (#5682) + if (parent && nodeType != scene::INode::Type::Particle) + { + highlightFlags |= parent->getHighlightFlags(); + } + + if (nodeType == scene::INode::Type::MergeAction) + { + setHighlightFlag(Highlight::MergeAction, true); + + auto mergeActionNode = std::dynamic_pointer_cast(node); + assert(mergeActionNode); + + switch (mergeActionNode->getActionType()) + { + case scene::merge::ActionType::AddChildNode: + case scene::merge::ActionType::AddEntity: + setHighlightFlag(Highlight::MergeActionAdd, true); + setHighlightFlag(Highlight::MergeActionChange, false); + setHighlightFlag(Highlight::MergeActionRemove, false); + setHighlightFlag(Highlight::MergeActionConflict, false); + break; + + case scene::merge::ActionType::AddKeyValue: + case scene::merge::ActionType::ChangeKeyValue: + case scene::merge::ActionType::RemoveKeyValue: + setHighlightFlag(Highlight::MergeActionChange, true); + setHighlightFlag(Highlight::MergeActionAdd, false); + setHighlightFlag(Highlight::MergeActionRemove, false); + setHighlightFlag(Highlight::MergeActionConflict, false); + break; + + case scene::merge::ActionType::RemoveChildNode: + case scene::merge::ActionType::RemoveEntity: + setHighlightFlag(Highlight::MergeActionRemove, true); + setHighlightFlag(Highlight::MergeActionAdd, false); + setHighlightFlag(Highlight::MergeActionChange, false); + setHighlightFlag(Highlight::MergeActionConflict, false); + break; + + case scene::merge::ActionType::ConflictResolution: + setHighlightFlag(Highlight::MergeActionConflict, true); + setHighlightFlag(Highlight::MergeActionAdd, false); + setHighlightFlag(Highlight::MergeActionChange, false); + setHighlightFlag(Highlight::MergeActionRemove, false); + break; + } + } + else + { + setHighlightFlag(Highlight::MergeAction, false); + setHighlightFlag(Highlight::MergeActionAdd, false); + setHighlightFlag(Highlight::MergeActionChange, false); + setHighlightFlag(Highlight::MergeActionRemove, false); + setHighlightFlag(Highlight::MergeActionConflict, false); + } + + if (highlightFlags & Renderable::Highlight::Selected) + { + if (GlobalSelectionSystem().Mode() != selection::SelectionSystem::eComponent) + { + setHighlightFlag(Highlight::Faces, true); + } + else + { + setHighlightFlag(Highlight::Faces, false); + node->renderComponents(*this, volume); + } + + setHighlightFlag(Highlight::Primitives, true); + + // Pass on the info about whether we have a group member selected + if (highlightFlags & Renderable::Highlight::GroupMember) + { + setHighlightFlag(Highlight::GroupMember, true); + } + else + { + setHighlightFlag(Highlight::GroupMember, false); + } + } + else + { + setHighlightFlag(Highlight::Primitives, false); + setHighlightFlag(Highlight::Faces, false); + setHighlightFlag(Highlight::GroupMember, false); + } + + // Oriented nodes are submitting every frame. The ones without parent matrix + // like Brushes and Patches are maintaining their geometry in the shader, on their own + if (node->isOriented()) + { + processRenderable(*node, volume); + } + + // If this node should be highlighted, ask it to submit the corresponding geometry + if (hasHighlightFlags()) + { + node->renderHighlights(*this, volume); + } + } +}; + +} diff --git a/libs/render/RenderablePivot.h b/libs/render/RenderablePivot.h index 8428f6909a..0ca735da3a 100644 --- a/libs/render/RenderablePivot.h +++ b/libs/render/RenderablePivot.h @@ -94,7 +94,7 @@ class RenderablePivot : glDisableClientState(GL_COLOR_ARRAY); } - void render(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const + void render(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { // greebo: Commented this out to avoid the point from being moved along with the view. //Pivot2World_worldSpace(m_localToWorld, localToWorld, volume.GetModelview(), volume.GetProjection(), volume.GetViewport()); diff --git a/libs/render/RenderableSpacePartition.h b/libs/render/RenderableSpacePartition.h index 05f0b598cd..01f61b9863 100644 --- a/libs/render/RenderableSpacePartition.h +++ b/libs/render/RenderableSpacePartition.h @@ -37,7 +37,7 @@ class RenderableSpacePartition : _spacePartition = spacePartition; } - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override { if (_shader != NULL) { @@ -45,7 +45,7 @@ class RenderableSpacePartition : } } - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override { if (_shader != NULL) { @@ -53,6 +53,9 @@ class RenderableSpacePartition : } } + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override + {} + void setRenderSystem(const RenderSystemPtr& renderSystem) override { if (renderSystem) diff --git a/libs/render/SceneRenderWalker.h b/libs/render/SceneRenderWalker.h index a6939a8270..7ae5206116 100644 --- a/libs/render/SceneRenderWalker.h +++ b/libs/render/SceneRenderWalker.h @@ -11,7 +11,7 @@ class SceneRenderWalker : public scene::Graph::Walker { // The collector which is sorting our renderables - RenderableCollector& _collector; + IRenderableCollector& _collector; // The view we're using for culling const VolumeTest& _volume; @@ -28,7 +28,7 @@ class SceneRenderWalker : public: /// Initialise with a RenderableCollector to populate and a view volume - SceneRenderWalker(RenderableCollector& collector, const VolumeTest& volume) : + SceneRenderWalker(IRenderableCollector& collector, const VolumeTest& volume) : _collector(collector), _volume(volume) {} @@ -36,8 +36,6 @@ class SceneRenderWalker : // scene::Graph::Walker implementation, tells each node to submit its OpenGLRenderables bool visit(const scene::INodePtr& node) { - node->viewChanged(); - render(*node); return true; diff --git a/libs/render/WindingRenderer.h b/libs/render/WindingRenderer.h new file mode 100644 index 0000000000..3bb5791f6f --- /dev/null +++ b/libs/render/WindingRenderer.h @@ -0,0 +1,369 @@ +#pragma once + +#include "igl.h" +#include +#include "iwindingrenderer.h" +#include "CompactWindingVertexBuffer.h" +#include "debugging/gl.h" + +namespace render +{ + +class IBackendWindingRenderer : + public IWindingRenderer +{ +public: + virtual ~IBackendWindingRenderer() + {} + + // Returns true if the vertex buffers are empty + virtual bool empty() const = 0; + + // Issues the openGL calls to render the vertex buffers + virtual void renderAllWindings() = 0; +}; + +// Traits class to retrieve the GLenum render mode based on the indexer type +template struct RenderingTraits +{}; + +template<> +struct RenderingTraits +{ + constexpr static GLenum Mode() { return GL_LINES; } +}; + +template<> +struct RenderingTraits +{ + constexpr static GLenum Mode() { return GL_TRIANGLES; } +}; + +template<> +struct RenderingTraits +{ + constexpr static GLenum Mode() { return GL_POLYGON; } +}; + +template +class WindingRenderer : + public IBackendWindingRenderer +{ +private: + using VertexBuffer = CompactWindingVertexBuffer; + + struct Bucket + { + Bucket(std::size_t size) : + buffer(size) + {} + + VertexBuffer buffer; + std::vector pendingDeletions; + }; + + // Maintain one bucket per winding size, allocated on demand + std::vector _buckets; + + using BucketIndex = std::uint16_t; + static constexpr BucketIndex InvalidBucketIndex = std::numeric_limits::max(); + static constexpr typename VertexBuffer::Slot InvalidVertexBufferSlot = std::numeric_limits::max(); + + // Stores the indices to a winding slot into a bucket, client code receives an index to a SlotMapping + struct SlotMapping + { + BucketIndex bucketIndex = InvalidBucketIndex; + typename VertexBuffer::Slot slotNumber = InvalidVertexBufferSlot; + }; + + std::vector _slots; + static constexpr std::size_t InvalidSlotMapping = std::numeric_limits::max(); + std::size_t _freeSlotMappingHint; + + std::size_t _windings; + +public: + WindingRenderer() : + _windings(0), + _freeSlotMappingHint(InvalidSlotMapping) + {} + + bool empty() const + { + return _windings == 0; + } + + Slot addWinding(const std::vector& vertices) override + { + auto windingSize = vertices.size(); + + if (windingSize >= std::numeric_limits::max()) throw std::logic_error("Winding too large"); + + // Get the Bucket this Slot is referring to + auto bucketIndex = getBucketIndexForWindingSize(windingSize); + auto& bucket = ensureBucketForWindingSize(windingSize); + + // Allocate a new slot descriptor, we can't hand out absolute indices to clients + auto slotMappingIndex = allocateSlotMapping(); + + auto& slotMapping = _slots[slotMappingIndex]; + slotMapping.bucketIndex = bucketIndex; + + // Check if we have a free slot in this buffer (marked for deletion) + if (!bucket.pendingDeletions.empty()) + { + slotMapping.slotNumber = bucket.pendingDeletions.back(); + bucket.pendingDeletions.pop_back(); + + // Use the replace method to load the data + bucket.buffer.replaceWinding(slotMapping.slotNumber, vertices); + } + else + { + // No deleted slot available, allocate a new one + slotMapping.slotNumber = bucket.buffer.pushWinding(vertices); + } + + ++_windings; + + return slotMappingIndex; + } + + void removeWinding(Slot slot) override + { + assert(slot < _slots.size()); + auto& slotMapping = _slots[slot]; + + auto bucketIndex = slotMapping.bucketIndex; + assert(bucketIndex != InvalidBucketIndex); + +#if 1 + // Mark this winding slot as pending for deletion + _buckets[bucketIndex].pendingDeletions.push_back(slotMapping.slotNumber); +#else + // Remove the winding from the bucket + _buckets[bucketIndex].removeWinding(slotMapping.slotNumber); + + // Update the value in other slot mappings, now that the bucket shrunk + for (auto& mapping : _slots) + { + // Every index in the same bucket beyond the removed winding needs to be shifted to left + if (mapping.bucketIndex == bucketIndex && mapping.slotNumber > slotMapping.slotNumber) + { + --mapping.slotNumber; + } + } +#endif + + // Invalidate the slot mapping + slotMapping.bucketIndex = InvalidBucketIndex; + slotMapping.slotNumber = InvalidVertexBufferSlot; + + // Update the free slot hint, for the next round we allocate one + if (slot < _freeSlotMappingHint) + { + _freeSlotMappingHint = slot; + } + + --_windings; + } + + void updateWinding(Slot slot, const std::vector& vertices) override + { + assert(slot < _slots.size()); + auto& slotMapping = _slots[slot]; + + assert(slotMapping.bucketIndex != InvalidBucketIndex); + + auto& bucket = _buckets[slotMapping.bucketIndex]; + + if (bucket.buffer.getWindingSize() != vertices.size()) + { + throw std::logic_error("Winding size changes are not supported through updateWinding."); + } + + bucket.buffer.replaceWinding(slotMapping.slotNumber, vertices); + } + + void renderAllWindings() override + { + for (auto bucketIndex = 0; bucketIndex < _buckets.size(); ++bucketIndex) + { + auto& bucket = _buckets[bucketIndex]; + commitDeletions(bucketIndex); + + if (bucket.buffer.getVertices().empty()) continue; + + const auto& vertices = bucket.buffer.getVertices(); + const auto& indices = bucket.buffer.getIndices(); + + glDisableClientState(GL_COLOR_ARRAY); + + glVertexPointer(3, GL_DOUBLE, sizeof(ArbitraryMeshVertex), &vertices.front().vertex); + glTexCoordPointer(2, GL_DOUBLE, sizeof(ArbitraryMeshVertex), &vertices.front().texcoord); + glNormalPointer(GL_DOUBLE, sizeof(ArbitraryMeshVertex), &vertices.front().normal); + + auto primitiveMode = RenderingTraits::Mode(); + glDrawElements(primitiveMode, static_cast(indices.size()), GL_UNSIGNED_INT, &indices.front()); + + debug::checkGLErrors(); + } + } + + void renderWinding(IWindingRenderer::RenderMode mode, IWindingRenderer::Slot slot) override + { + assert(slot < _slots.size()); + auto& slotMapping = _slots[slot]; + + assert(slotMapping.bucketIndex != InvalidBucketIndex); + auto& bucket = _buckets[slotMapping.bucketIndex]; + + commitDeletions(slotMapping.bucketIndex); + + const auto& vertices = bucket.buffer.getVertices(); + const auto& indices = bucket.buffer.getIndices(); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glFrontFace(GL_CCW); + + glVertexPointer(3, GL_DOUBLE, sizeof(ArbitraryMeshVertex), &vertices.front().vertex); + glTexCoordPointer(2, GL_DOUBLE, sizeof(ArbitraryMeshVertex), &vertices.front().texcoord); + glNormalPointer(GL_DOUBLE, sizeof(ArbitraryMeshVertex), &vertices.front().normal); + + if (mode == IWindingRenderer::RenderMode::Triangles) + { + renderElements(bucket.buffer, slotMapping.slotNumber); + } + else if (mode == IWindingRenderer::RenderMode::Polygon) + { + renderElements(bucket.buffer, slotMapping.slotNumber); + } + + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } + +private: + void commitDeletions(std::size_t bucketIndex) + { + auto& bucket = _buckets[bucketIndex]; + + if (bucket.pendingDeletions.empty()) return; + + std::sort(bucket.pendingDeletions.begin(), bucket.pendingDeletions.end()); + +#if 1 + // Remove the winding from the bucket + bucket.buffer.removeWindings(bucket.pendingDeletions); + + // A mapping to quickly know which mapping has been shifted by how many positions + std::map offsets; + auto offsetToApply = 0; + + for (auto removed : bucket.pendingDeletions) + { + offsets[removed] = offsetToApply++; + } + + auto maxOffsetToApply = offsetToApply; + + for (auto& mapping : _slots) + { + // Every index in the same bucket beyond the first removed winding needs to be shifted + if (mapping.bucketIndex != bucketIndex || mapping.slotNumber == InvalidVertexBufferSlot) + { + continue; + } + + // lower_bound yields the item that is equal to or larger + auto offset = offsets.lower_bound(mapping.slotNumber); + + if (offset != offsets.end()) + { + mapping.slotNumber -= offset->second; + } + else + { + mapping.slotNumber -= maxOffsetToApply; + } + } +#else + for (auto s = bucket.pendingDeletions.rbegin(); s != bucket.pendingDeletions.rend(); ++s) + { + auto slotNumber = *s; + + // Remove the winding from the bucket + bucket.buffer.removeWinding(slotNumber); + + // Update the value in other slot mappings, now that the bucket shrank + for (auto& mapping : _slots) + { + // Every index in the same bucket beyond the removed winding needs to be shifted to left + if (mapping.bucketIndex == bucketIndex && mapping.slotNumber > slotNumber) + { + --mapping.slotNumber; + } + } + } +#endif + bucket.pendingDeletions.clear(); + } + + template + void renderElements(const VertexBuffer& buffer, typename VertexBuffer::Slot slotNumber) const + { + std::vector indices; + indices.reserve(CustomWindingIndexerT::GetNumberOfIndicesPerWinding(buffer.getWindingSize())); + + auto firstVertex = static_cast(buffer.getWindingSize() * slotNumber); + CustomWindingIndexerT::GenerateAndAssignIndices(std::back_inserter(indices), buffer.getWindingSize(), firstVertex); + auto mode = RenderingTraits::Mode(); + + glDrawElements(mode, static_cast(indices.size()), GL_UNSIGNED_INT, &indices.front()); + } + + IWindingRenderer::Slot allocateSlotMapping() + { + auto numSlots = static_cast(_slots.size()); + + for (auto i = _freeSlotMappingHint; i < numSlots; ++i) + { + if (_slots[i].bucketIndex == InvalidBucketIndex) + { + _freeSlotMappingHint = i + 1; // start searching here next time + return i; + } + } + + _slots.emplace_back(); + return numSlots; // == the size before we emplaced the new slot + } + + inline static BucketIndex getBucketIndexForWindingSize(std::size_t windingSize) + { + // Since there are no windings with sizes 0, 1, 2, we can deduct 3 to get the bucket index + if (windingSize < 3) throw std::logic_error("No winding sizes < 3 are supported"); + + return static_cast(windingSize - 3); + } + + Bucket& ensureBucketForWindingSize(std::size_t windingSize) + { + auto bucketIndex = getBucketIndexForWindingSize(windingSize); + + // Keep adding buckets until we have the matching one + while (bucketIndex >= _buckets.size()) + { + auto nextWindingSize = _buckets.size() + 3; + _buckets.emplace_back(nextWindingSize); + } + + return _buckets.at(bucketIndex); + } +}; + +} diff --git a/libs/scene/BasicRootNode.h b/libs/scene/BasicRootNode.h index 46f2e9658d..2d81b6c3e8 100644 --- a/libs/scene/BasicRootNode.h +++ b/libs/scene/BasicRootNode.h @@ -94,10 +94,13 @@ class BasicRootNode final : } // Renderable implementation (empty) - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override {} - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override + {} + + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override {} std::size_t getHighlightFlags() override diff --git a/libs/scene/Node.cpp b/libs/scene/Node.cpp index ced85b149d..6e812f745e 100644 --- a/libs/scene/Node.cpp +++ b/libs/scene/Node.cpp @@ -76,12 +76,28 @@ void Node::setIsRoot(bool isRoot) void Node::enable(unsigned int state) { + bool wasVisible = visible(); + _state |= state; + + // After setting a flag, this node may have changed to invisible + if (wasVisible && _state != eVisible) + { + onVisibilityChanged(false); + } } void Node::disable(unsigned int state) { + bool wasVisible = visible(); + _state &= ~state; + + // After clearing a flag, this node can only switch from invisible to visible + if (!wasVisible && visible()) + { + onVisibilityChanged(true); + } } bool Node::checkStateFlag(unsigned int state) const @@ -98,7 +114,7 @@ bool Node::visible() const { // Only instantiated nodes can be considered visible // The force visible flag is allowed to override the regular status - return (_state == eVisible && _instantiated) || _forceVisible; + return _instantiated && (_state == eVisible || _forceVisible); } bool Node::excluded() const @@ -276,13 +292,29 @@ void Node::onChildRemoved(const INodePtr& child) void Node::onInsertIntoScene(IMapRootNode& root) { _instantiated = true; + + // The node was 100% not visible before, check if it is now + if (visible()) + { + onVisibilityChanged(true); + } + connectUndoSystem(root.getUndoSystem()); } void Node::onRemoveFromScene(IMapRootNode& root) { disconnectUndoSystem(root.getUndoSystem()); + + bool wasVisible = visible(); + _instantiated = false; + + // The node is 100% not visible after removing from the scene + if (wasVisible) + { + onVisibilityChanged(false); + } } void Node::connectUndoSystem(IUndoSystem& undoSystem) @@ -481,8 +513,17 @@ void Node::setRenderSystem(const RenderSystemPtr& renderSystem) void Node::setForcedVisibility(bool forceVisible, bool includeChildren) { + bool wasVisible = visible(); + _forceVisible = forceVisible; + bool isVisible = visible(); + + if (wasVisible ^ isVisible) + { + onVisibilityChanged(isVisible); + } + if (includeChildren) { _children.foreachNode([&](const INodePtr& node) diff --git a/libs/scene/Node.h b/libs/scene/Node.h index 5b5e80ea7e..e7d5de41f5 100644 --- a/libs/scene/Node.h +++ b/libs/scene/Node.h @@ -140,11 +140,13 @@ class Node : */ virtual void setFiltered(bool filtered) override { - if (filtered) { - _state |= eFiltered; + if (filtered) + { + enable(eFiltered); } - else { - _state &= ~eFiltered; + else + { + disable(eFiltered); } } @@ -196,11 +198,15 @@ class Node : protected: // Set the "forced visible" flag, only to be used internally by subclasses - void setForcedVisibility(bool forceVisible, bool includeChildren) override; + virtual void setForcedVisibility(bool forceVisible, bool includeChildren) override; // Method for subclasses to check whether this node is forcedly visible bool isForcedVisible() const; + // Overridable method to get notified on visibility changes of this node + virtual void onVisibilityChanged(bool isVisibleNow) + {} + // Fills in the ancestors and self (in this order) into the given targetPath. void getPathRecursively(scene::Path& targetPath); diff --git a/libs/scene/merge/MergeActionNode.cpp b/libs/scene/merge/MergeActionNode.cpp index 9c12b34d2f..5c9e083042 100644 --- a/libs/scene/merge/MergeActionNode.cpp +++ b/libs/scene/merge/MergeActionNode.cpp @@ -98,30 +98,38 @@ const Matrix4& MergeActionNodeBase::localToWorld() const return identity; } -void MergeActionNodeBase::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void MergeActionNodeBase::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { - _affectedNode->viewChanged(); _affectedNode->renderSolid(collector, volume); _affectedNode->foreachNode([&](const INodePtr& child) { - child->viewChanged(); child->renderSolid(collector, volume); return true; }); } -void MergeActionNodeBase::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void MergeActionNodeBase::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { - _affectedNode->viewChanged(); _affectedNode->renderWireframe(collector, volume); _affectedNode->foreachNode([&](const INodePtr& child) { - child->viewChanged(); child->renderWireframe(collector, volume); return true; }); } +void MergeActionNodeBase::renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) +{ + if (collector.supportsFullMaterials()) + { + renderSolid(collector, volume); + } + else + { + renderWireframe(collector, volume); + } +} + std::size_t MergeActionNodeBase::getHighlightFlags() { return isSelected() ? Highlight::Selected : Highlight::NoHighlight; diff --git a/libs/scene/merge/MergeActionNode.h b/libs/scene/merge/MergeActionNode.h index 1bfa03535a..4f1605c6b8 100644 --- a/libs/scene/merge/MergeActionNode.h +++ b/libs/scene/merge/MergeActionNode.h @@ -50,8 +50,9 @@ class MergeActionNodeBase : const AABB& localAABB() const override; const Matrix4& localToWorld() const override; - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override; std::size_t getHighlightFlags() override; diff --git a/libs/wxutil/preview/RenderPreview.cpp b/libs/wxutil/preview/RenderPreview.cpp index 8ed9e340ba..17681c2d60 100644 --- a/libs/wxutil/preview/RenderPreview.cpp +++ b/libs/wxutil/preview/RenderPreview.cpp @@ -500,7 +500,7 @@ bool RenderPreview::drawPreview() // Launch the back end rendering renderer.submitToShaders(); - _renderSystem->render(flags, _volumeTest.GetModelview(), projection, _viewOrigin); + _renderSystem->render(RenderViewType::Camera, flags, _volumeTest.GetModelview(), projection, _viewOrigin); // Give subclasses an opportunity to render their own on-screen stuff onPostRender(); @@ -525,7 +525,7 @@ void RenderPreview::renderWireFrame() // Launch the back end rendering renderer.submitToShaders(); - _renderSystem->render(flags, _volumeTest.GetModelview(), projection, _viewOrigin); + _renderSystem->render(RenderViewType::Camera, flags, _volumeTest.GetModelview(), projection, _viewOrigin); } void RenderPreview::onGLMouseClick(wxMouseEvent& ev) diff --git a/radiant/CMakeLists.txt b/radiant/CMakeLists.txt index 3519d432b8..9cbd494472 100644 --- a/radiant/CMakeLists.txt +++ b/radiant/CMakeLists.txt @@ -39,7 +39,6 @@ add_executable(darkradiant ui/common/ImageFilePopulator.cpp ui/common/ImageFileSelector.cpp ui/common/MapPreview.cpp - ui/common/RenderableAABB.cpp ui/common/ShaderChooser.cpp ui/common/ShaderSelector.cpp ui/common/SoundShaderDefinitionView.cpp diff --git a/radiant/camera/CamWnd.cpp b/radiant/camera/CamWnd.cpp index 28e3d2b9e7..cf6c96ea31 100644 --- a/radiant/camera/CamWnd.cpp +++ b/radiant/camera/CamWnd.cpp @@ -127,6 +127,8 @@ CamWnd::CamWnd(wxWindow* parent) : radiant::IMessage::Type::TextureChanged, radiant::TypeListener( sigc::mem_fun(this, &CamWnd::handleTextureChanged))); + + _renderer.reset(new render::CamRenderer(_view, _shaders)); } wxWindow* CamWnd::getMainWidget() const @@ -784,27 +786,30 @@ void CamWnd::Cam_Draw() // Main scene render { + _renderer->prepare(); + // Front end (renderable collection from scene) - render::CamRenderer renderer(_view, _shaders); - render::RenderableCollectionWalker::CollectRenderablesInScene(renderer, _view); + render::RenderableCollectionWalker::CollectRenderablesInScene(*_renderer, _view); // Accumulate render statistics - _renderStats.setLightCount(renderer.getVisibleLights(), - renderer.getTotalLights()); + _renderStats.setLightCount(_renderer->getVisibleLights(), + _renderer->getTotalLights()); _renderStats.frontEndComplete(); // Render any active mousetools for (const ActiveMouseTools::value_type& i : _activeMouseTools) { - i.second->render(GlobalRenderSystem(), renderer, _view); + i.second->render(GlobalRenderSystem(), *_renderer, _view); } // Back end (submit to shaders and do the actual render) - renderer.submitToShaders( + _renderer->submitToShaders( getCameraSettings()->getRenderMode() == RENDER_MODE_LIGHTING ); - GlobalRenderSystem().render(allowedRenderFlags, _camera->getModelView(), - _camera->getProjection(), _view.getViewer()); + GlobalRenderSystem().render(RenderViewType::Camera, allowedRenderFlags, + _camera->getModelView(), _camera->getProjection(), _view.getViewer()); + + _renderer->cleanup(); } // greebo: Draw the clipper's points (skipping the depth-test) diff --git a/radiant/camera/CamWnd.h b/radiant/camera/CamWnd.h index ed9fba777a..4fb7430645 100644 --- a/radiant/camera/CamWnd.h +++ b/radiant/camera/CamWnd.h @@ -37,6 +37,8 @@ const int CAMWND_MINSIZE_Y = 200; class SelectionTest; +namespace render { class CamRenderer; }; + namespace ui { @@ -63,6 +65,7 @@ class CamWnd : // The contained camera camera::ICameraView::Ptr _camera; + std::unique_ptr _renderer; static render::CamRenderer::HighlightShaders _shaders; wxutil::FreezePointer _freezePointer; diff --git a/radiant/ui/aas/RenderableAasFile.cpp b/radiant/ui/aas/RenderableAasFile.cpp index f92b945be6..b43aafc7ef 100644 --- a/radiant/ui/aas/RenderableAasFile.cpp +++ b/radiant/ui/aas/RenderableAasFile.cpp @@ -36,7 +36,7 @@ void RenderableAasFile::setRenderSystem(const RenderSystemPtr& renderSystem) _renderSystem = renderSystem; } -void RenderableAasFile::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void RenderableAasFile::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { if (!_aasFile) return; @@ -60,7 +60,7 @@ void RenderableAasFile::renderSolid(RenderableCollector& collector, const Volume } } -void RenderableAasFile::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void RenderableAasFile::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { // Do nothing in wireframe mode } diff --git a/radiant/ui/aas/RenderableAasFile.h b/radiant/ui/aas/RenderableAasFile.h index 5f86c2cbdd..7e5ba7016d 100644 --- a/radiant/ui/aas/RenderableAasFile.h +++ b/radiant/ui/aas/RenderableAasFile.h @@ -40,8 +40,11 @@ class RenderableAasFile : RenderableAasFile(); void setRenderSystem(const RenderSystemPtr& renderSystem) override; - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override + {} + std::size_t getHighlightFlags() override; void setAasFile(const IAasFilePtr& aasFile); diff --git a/radiant/ui/common/RenderableAABB.cpp b/radiant/ui/common/RenderableAABB.cpp deleted file mode 100644 index c614356723..0000000000 --- a/radiant/ui/common/RenderableAABB.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "RenderableAABB.h" - -#include - -namespace ui -{ - -// Virtual render function from OpenGLRenderable - -// OpenGL render function - -void RenderableAABB::render(const RenderInfo& info) const { - - // Wireframe cuboid - glBegin(GL_LINES); - glVertex3d(_aabb.extents.x(), _aabb.extents.y(), _aabb.extents.z()); - glVertex3d(_aabb.extents.x(), _aabb.extents.y(), -_aabb.extents.z()); - - glVertex3d(_aabb.extents.x(), _aabb.extents.y(), _aabb.extents.z()); - glVertex3d(-_aabb.extents.x(), _aabb.extents.y(), _aabb.extents.z()); - - glVertex3d(_aabb.extents.x(), _aabb.extents.y(), -_aabb.extents.z()); - glVertex3d(-_aabb.extents.x(), _aabb.extents.y(), -_aabb.extents.z()); - - glVertex3d(_aabb.extents.x(), _aabb.extents.y(), _aabb.extents.z()); - glVertex3d(_aabb.extents.x(), -_aabb.extents.y(), _aabb.extents.z()); - - glVertex3d(-_aabb.extents.x(), _aabb.extents.y(), _aabb.extents.z()); - glVertex3d(-_aabb.extents.x(), -_aabb.extents.y(), _aabb.extents.z()); - - glVertex3d(-_aabb.extents.x(), _aabb.extents.y(), -_aabb.extents.z()); - glVertex3d(-_aabb.extents.x(), -_aabb.extents.y(), -_aabb.extents.z()); - - glVertex3d(_aabb.extents.x(), _aabb.extents.y(), -_aabb.extents.z()); - glVertex3d(_aabb.extents.x(), -_aabb.extents.y(), -_aabb.extents.z()); - - glVertex3d(_aabb.extents.x(), -_aabb.extents.y(), _aabb.extents.z()); - glVertex3d(-_aabb.extents.x(), -_aabb.extents.y(), _aabb.extents.z()); - - glVertex3d(_aabb.extents.x(), -_aabb.extents.y(), _aabb.extents.z()); - glVertex3d(_aabb.extents.x(), -_aabb.extents.y(), -_aabb.extents.z()); - - glVertex3d(-_aabb.extents.x(), _aabb.extents.y(), _aabb.extents.z()); - glVertex3d(-_aabb.extents.x(), _aabb.extents.y(), -_aabb.extents.z()); - - glVertex3d(-_aabb.extents.x(), -_aabb.extents.y(), _aabb.extents.z()); - glVertex3d(-_aabb.extents.x(), -_aabb.extents.y(), -_aabb.extents.z()); - - glVertex3d(_aabb.extents.x(), -_aabb.extents.y(), -_aabb.extents.z()); - glVertex3d(-_aabb.extents.x(), -_aabb.extents.y(), -_aabb.extents.z()); - glEnd(); -} - - -} diff --git a/radiant/ui/common/RenderableAABB.h b/radiant/ui/common/RenderableAABB.h deleted file mode 100644 index 59d7488c9c..0000000000 --- a/radiant/ui/common/RenderableAABB.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef RENDERABLEAABB_H_ -#define RENDERABLEAABB_H_ - -#include "irender.h" -#include "math/AABB.h" - -namespace ui -{ - -/** Adapter class that allows the rendering of an AABB as a wireframe cuboid. The - * class implements the OpenGLRenderable interface and accepts an AABB as a - * construction parameter. - */ - -class RenderableAABB -: public OpenGLRenderable -{ - // The AABB to render - AABB _aabb; - -public: - - /** Construct a RenderableAABB to render the provided AABB. - */ - RenderableAABB(const AABB& aabb) - : _aabb(aabb) {} - - /** Render function from OpenGLRenderable interface. - */ - void render(const RenderInfo& info) const; -}; - -} - -#endif /*RENDERABLEAABB_H_*/ diff --git a/radiant/xyview/GlobalXYWnd.cpp b/radiant/xyview/GlobalXYWnd.cpp index a388412197..21e1bc299b 100644 --- a/radiant/xyview/GlobalXYWnd.cpp +++ b/radiant/xyview/GlobalXYWnd.cpp @@ -20,6 +20,7 @@ #include "tools/CameraMoveTool.h" #include "tools/MoveViewTool.h" #include "tools/MeasurementTool.h" +#include "debugging/ScopedDebugTimer.h" #include @@ -171,6 +172,8 @@ void XYWndManager::registerCommands() GlobalCommandSystem().addCommand("CenterXYViews", std::bind(&XYWndManager::splitViewFocus, this, std::placeholders::_1)); GlobalCommandSystem().addCommand("CenterXYView", std::bind(&XYWndManager::focusActiveView, this, std::placeholders::_1)); GlobalCommandSystem().addCommand("Zoom100", std::bind(&XYWndManager::zoom100, this, std::placeholders::_1)); + GlobalCommandSystem().addCommand("RunBenchmark", std::bind(&XYWndManager::runBenchmark, this, std::placeholders::_1), + { cmd::ARGTYPE_INT | cmd::ARGTYPE_OPTIONAL }); GlobalEventManager().addRegistryToggle("ToggleCrosshairs", RKEY_SHOW_CROSSHAIRS); GlobalEventManager().addRegistryToggle("ToggleGrid", RKEY_SHOW_GRID); @@ -758,6 +761,22 @@ void XYWndManager::foreachMouseTool(const std::functiongetCamera().setCameraOrigin({ 2517, -4511, 713 }); + cam->getCamera().setCameraAngles({ -2.1, 123.91, 0 }); + + int numRuns = args.empty() ? 1 : args[0].getInt(); + + ScopedDebugTimer timer("Camera refresh, " + string::to_string(numRuns) + " runs"); + for (int i = 0; i < numRuns; ++i) + { + cam->getCamera().forceRedraw(); + } +} + // Define the static GlobalXYWnd module module::StaticModule xyWndModule; diff --git a/radiant/xyview/GlobalXYWnd.h b/radiant/xyview/GlobalXYWnd.h index ae154c7b36..b05c7ba123 100644 --- a/radiant/xyview/GlobalXYWnd.h +++ b/radiant/xyview/GlobalXYWnd.h @@ -109,6 +109,7 @@ class XYWndManager : void splitViewFocus(const cmd::ArgumentList& args); // Re-position all available views void zoom100(const cmd::ArgumentList& args); // Sets the scale of all windows to 1 void focusActiveView(const cmd::ArgumentList& args); // sets the focus of the active view + void runBenchmark(const cmd::ArgumentList& args); // Sets the origin of all available views void setOrigin(const Vector3& origin) override; diff --git a/radiant/xyview/XYRenderer.h b/radiant/xyview/XYRenderer.h index 7d8db75def..ff5eb9ca04 100644 --- a/radiant/xyview/XYRenderer.h +++ b/radiant/xyview/XYRenderer.h @@ -1,12 +1,12 @@ #pragma once #include "irender.h" -#include "irenderable.h" +#include "render/RenderableCollectorBase.h" #include "imap.h" /// RenderableCollector implementation for the ortho view class XYRenderer : - public RenderableCollector + public render::RenderableCollectorBase { public: struct HighlightShaders @@ -41,6 +41,11 @@ class XYRenderer : return false; } + bool hasHighlightFlags() const override + { + return _flags != 0; + } + void setHighlightFlag(Highlight::Flags flags, bool enabled) override { if (enabled) @@ -56,11 +61,23 @@ class XYRenderer : // Ortho view never processes lights void addLight(const RendererLight&) override {} + void processRenderable(const Renderable& renderable, const VolumeTest& volume) override + { + renderable.renderWireframe(*this, volume); + } + void addRenderable(Shader& shader, const OpenGLRenderable& renderable, const Matrix4& localToWorld, const LitObject* /* litObject */, const IRenderEntity* entity = nullptr) override + { + addHighlightRenderable(renderable, localToWorld); + + shader.addRenderable(renderable, localToWorld, nullptr, entity); + } + + void addHighlightRenderable(const OpenGLRenderable& renderable, const Matrix4& localToWorld) override { if (_editMode == IMap::EditMode::Merge) { @@ -68,47 +85,45 @@ class XYRenderer : { // This is a merge-relevant node that should be rendered in a special colour const auto& mergeShader = (_flags & Highlight::Flags::MergeActionAdd) != 0 ? _shaders.mergeActionShaderAdd : - (_flags & Highlight::Flags::MergeActionRemove) != 0 ? _shaders.mergeActionShaderRemove : + (_flags & Highlight::Flags::MergeActionRemove) != 0 ? _shaders.mergeActionShaderRemove : (_flags & Highlight::Flags::MergeActionConflict) != 0 ? _shaders.mergeActionShaderConflict : _shaders.mergeActionShaderChange; if (mergeShader) { - mergeShader->addRenderable(renderable, localToWorld, nullptr, entity); + mergeShader->addRenderable(renderable, localToWorld, nullptr, nullptr); } } else { // Everything else is using the shader for non-merge-affected nodes - _shaders.nonMergeActionNodeShader->addRenderable(renderable, localToWorld, nullptr, entity); + _shaders.nonMergeActionNodeShader->addRenderable(renderable, localToWorld, nullptr, nullptr); } // Elements can still be selected in merge mode if ((_flags & Highlight::Flags::Primitives) != 0) { - _shaders.selectedShader->addRenderable(renderable, localToWorld, nullptr, entity); + _shaders.selectedShader->addRenderable(renderable, localToWorld, nullptr, nullptr); } return; } - + // Regular editing mode, add all highlighted nodes to the corresponding shader if ((_flags & Highlight::Flags::Primitives) != 0) { if ((_flags & Highlight::Flags::GroupMember) != 0) { - _shaders.selectedShaderGroup->addRenderable(renderable, localToWorld, nullptr, entity); + _shaders.selectedShaderGroup->addRenderable(renderable, localToWorld, nullptr, nullptr); } else { - _shaders.selectedShader->addRenderable(renderable, localToWorld, nullptr, entity); + _shaders.selectedShader->addRenderable(renderable, localToWorld, nullptr, nullptr); } } - - shader.addRenderable(renderable, localToWorld, nullptr, entity); } void render(const Matrix4& modelview, const Matrix4& projection) { - GlobalRenderSystem().render(_globalstate, modelview, projection, Vector3(0,0,0)); + GlobalRenderSystem().render(RenderViewType::OrthoView, _globalstate, modelview, projection, Vector3(0,0,0)); } }; // class XYRenderer diff --git a/radiant/xyview/tools/MeasurementTool.cpp b/radiant/xyview/tools/MeasurementTool.cpp index bda1f7cfa5..32751b1d15 100644 --- a/radiant/xyview/tools/MeasurementTool.cpp +++ b/radiant/xyview/tools/MeasurementTool.cpp @@ -162,7 +162,7 @@ void MeasurementTool::ensureShaders(RenderSystem& renderSystem) } } -void MeasurementTool::render(RenderSystem& renderSystem, RenderableCollector& collector, const VolumeTest& volume) +void MeasurementTool::render(RenderSystem& renderSystem, IRenderableCollector& collector, const VolumeTest& volume) { ensureShaders(renderSystem); diff --git a/radiant/xyview/tools/MeasurementTool.h b/radiant/xyview/tools/MeasurementTool.h index 1f7e6fcdaf..c85080ba87 100644 --- a/radiant/xyview/tools/MeasurementTool.h +++ b/radiant/xyview/tools/MeasurementTool.h @@ -40,7 +40,7 @@ class MeasurementTool : Result onCancel(IInteractiveView& view) override; void onMouseCaptureLost(IInteractiveView& view) override; - void render(RenderSystem& renderSystem, RenderableCollector& collector, const VolumeTest& volume) override; + void render(RenderSystem& renderSystem, IRenderableCollector& collector, const VolumeTest& volume) override; private: void ensureShaders(RenderSystem& renderSystem); diff --git a/radiantcore/brush/Brush.cpp b/radiantcore/brush/Brush.cpp index f33520fc62..d88a2200f2 100644 --- a/radiantcore/brush/Brush.cpp +++ b/radiantcore/brush/Brush.cpp @@ -263,7 +263,7 @@ const AABB& Brush::localAABB() const { return m_aabb_local; } -void Brush::renderComponents(selection::ComponentSelectionMode mode, RenderableCollector& collector, +void Brush::renderComponents(selection::ComponentSelectionMode mode, IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { switch (mode) @@ -581,6 +581,7 @@ void Brush::windingForClipPlane(Winding& winding, const Plane3& plane) const { buffer[swap].writeToWinding(winding); } +#if 0 void Brush::update_wireframe(RenderableWireframe& wire, const bool* faces_visible) const { wire.m_faceVertex.resize(_edgeIndices.size()); @@ -595,6 +596,7 @@ void Brush::update_wireframe(RenderableWireframe& wire, const bool* faces_visibl } } } +#endif void Brush::update_faces_wireframe(RenderablePointVector& wire, const std::size_t* visibleFaceIndices, diff --git a/radiantcore/brush/Brush.h b/radiantcore/brush/Brush.h index 49654ed972..f7950e1683 100644 --- a/radiantcore/brush/Brush.h +++ b/radiantcore/brush/Brush.h @@ -10,7 +10,7 @@ #include #include "util/Noncopyable.h" -class RenderableCollector; +class IRenderableCollector; class Ray; /// \brief Returns true if 'self' takes priority when building brush b-rep. @@ -211,7 +211,7 @@ class Brush : const AABB& localAABB() const override; - void renderComponents(selection::ComponentSelectionMode mode, RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; + void renderComponents(selection::ComponentSelectionMode mode, IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; void transform(const Matrix4& matrix); @@ -270,7 +270,9 @@ class Brush : /// \brief Constructs \p winding from the intersection of \p plane with the other planes of the brush. void windingForClipPlane(Winding& winding, const Plane3& plane) const; +#if 0 void update_wireframe(RenderableWireframe& wire, const bool* faces_visible) const; +#endif void update_faces_wireframe(RenderablePointVector& wire, const std::size_t* visibleFaceIndices, diff --git a/radiantcore/brush/BrushClipPlane.h b/radiantcore/brush/BrushClipPlane.h index 22d3efa287..05ad47fecd 100644 --- a/radiantcore/brush/BrushClipPlane.h +++ b/radiantcore/brush/BrushClipPlane.h @@ -56,7 +56,7 @@ class BrushClipPlane : } } - void render(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const + void render(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { collector.addRenderable(*_shader, *this, localToWorld); } diff --git a/radiantcore/brush/BrushNode.cpp b/radiantcore/brush/BrushNode.cpp index f1837162cf..428e24f968 100644 --- a/radiantcore/brush/BrushNode.cpp +++ b/radiantcore/brush/BrushNode.cpp @@ -15,9 +15,9 @@ BrushNode::BrushNode() : scene::SelectableNode(), m_brush(*this), + _faceCentroidPointsNeedUpdate(true), _selectedPoints(GL_POINTS), - _faceCentroidPointsCulled(GL_POINTS), - m_viewChanged(false), + _visibleFaceCentroidPoints(GL_POINTS), _renderableComponentsNeedUpdate(true), _untransformedOriginChanged(true) { @@ -42,9 +42,9 @@ BrushNode::BrushNode(const BrushNode& other) : LitObject(other), Transformable(other), m_brush(*this, other.m_brush), + _faceCentroidPointsNeedUpdate(true), _selectedPoints(GL_POINTS), - _faceCentroidPointsCulled(GL_POINTS), - m_viewChanged(false), + _visibleFaceCentroidPoints(GL_POINTS), _renderableComponentsNeedUpdate(true), _untransformedOriginChanged(true) { @@ -347,16 +347,37 @@ bool BrushNode::intersectsLight(const RendererLight& light) const { return light.lightAABB().intersects(worldAABB()); } -void BrushNode::renderComponents(RenderableCollector& collector, const VolumeTest& volume) const +void BrushNode::onPreRender(const VolumeTest& volume) { - m_brush.evaluateBRep(); + m_brush.evaluateBRep(); + + assert(_renderEntity); + // Every face is asked to run the rendering preparations + // to link/unlink their geometry to/from the active shader + for (auto& faceInstance : m_faceInstances) + { + auto& face = faceInstance.getFace(); + + if (volume.fill()) + { + face.getWindingSurfaceSolid().update(face.getFaceShader().getGLShader()); + } + else + { + face.getWindingSurfaceWireframe().update(_renderEntity->getWireShader()); + } + } +} + +void BrushNode::renderComponents(IRenderableCollector& collector, const VolumeTest& volume) const +{ const Matrix4& l2w = localToWorld(); if (volume.fill() && GlobalSelectionSystem().ComponentMode() == selection::ComponentSelectionMode::Face) { - evaluateViewDependent(volume, l2w); - collector.addRenderable(*m_brush.m_state_point, _faceCentroidPointsCulled, l2w); + updateFaceCentroidPoints(); + collector.addRenderable(*m_brush.m_state_point, _visibleFaceCentroidPoints, l2w); } else { @@ -364,22 +385,59 @@ void BrushNode::renderComponents(RenderableCollector& collector, const VolumeTes } } -void BrushNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void BrushNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { - m_brush.evaluateBRep(); - - renderClipPlane(collector, volume); - +#if 0 renderSolid(collector, volume, localToWorld()); +#endif } -void BrushNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void BrushNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { - m_brush.evaluateBRep(); +#if 0 + renderWireframe(collector, volume, localToWorld()); +#endif +} + +void BrushNode::renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) +{ + // Check for the override status of this brush + bool forceVisible = isForcedVisible(); + bool wholeBrushSelected = isSelected(); - renderClipPlane(collector, volume); + collector.setHighlightFlag(IRenderableCollector::Highlight::Primitives, wholeBrushSelected); - renderWireframe(collector, volume, localToWorld()); + // Submit the renderable geometry for each face + for (auto& faceInstance : m_faceInstances) + { + // Skip invisible faces before traversing further + if (!forceVisible && !faceInstance.faceIsVisible()) continue; + + Face& face = faceInstance.getFace(); + if (face.intersectVolume(volume)) + { + bool highlight = wholeBrushSelected || faceInstance.selectedComponents(); + + if (!highlight) continue; + + collector.setHighlightFlag(IRenderableCollector::Highlight::Faces, true); + + // Submit the RenderableWinding as reference, it will render the winding in polygon mode + collector.addHighlightRenderable(face.getWindingSurfaceSolid(), Matrix4::getIdentity()); + + collector.setHighlightFlag(IRenderableCollector::Highlight::Faces, false); + } + } + + if (wholeBrushSelected && GlobalClipper().clipMode()) + { + m_clipPlane.render(collector, volume, Matrix4::getIdentity()); + } + + collector.setHighlightFlag(IRenderableCollector::Highlight::Primitives, false); + + // Render any selected points (vertices, edges, faces) + renderSelectedPoints(collector, volume, Matrix4::getIdentity()); } void BrushNode::setRenderSystem(const RenderSystemPtr& renderSystem) @@ -399,30 +457,55 @@ void BrushNode::setRenderSystem(const RenderSystemPtr& renderSystem) m_clipPlane.setRenderSystem(renderSystem); } -void BrushNode::renderClipPlane(RenderableCollector& collector, const VolumeTest& volume) const +std::size_t BrushNode::getHighlightFlags() { - if (GlobalClipper().clipMode() && isSelected()) - { - m_clipPlane.render(collector, volume, localToWorld()); - } + if (!isSelected() && !isSelectedComponents()) return Highlight::NoHighlight; + + return isGroupMember() ? (Highlight::Selected | Highlight::GroupMember) : Highlight::Selected; } -void BrushNode::viewChanged() const { - m_viewChanged = true; +void BrushNode::onFaceVisibilityChanged() +{ + _faceCentroidPointsNeedUpdate = true; } -std::size_t BrushNode::getHighlightFlags() +void BrushNode::setForcedVisibility(bool forceVisible, bool includeChildren) { - if (!isSelected()) return Highlight::NoHighlight; + Node::setForcedVisibility(forceVisible, includeChildren); + + _faceCentroidPointsNeedUpdate = true; +} - return isGroupMember() ? (Highlight::Selected | Highlight::GroupMember) : Highlight::Selected; +void BrushNode::updateFaceCentroidPoints() const +{ + if (!_faceCentroidPointsNeedUpdate) return; + + static const auto& colourSetting = GlobalBrushCreator().getSettings().getVertexColour(); + const Colour4b vertexColour( + static_cast(colourSetting[0] * 255), + static_cast(colourSetting[1] * 255), + static_cast(colourSetting[2] * 255), + 255); + + _faceCentroidPointsNeedUpdate = false; + + _visibleFaceCentroidPoints.clear(); + + for (const auto& faceInstance: m_faceInstances) + { + if (faceInstance.faceIsVisible()) + { + _visibleFaceCentroidPoints.emplace_back(faceInstance.centroid(), vertexColour); + } + } } -void BrushNode::evaluateViewDependent(const VolumeTest& volume, const Matrix4& localToWorld) const +#if 0 +void BrushNode::updateWireframeVisibility() const { - if (!m_viewChanged) return; + if (!_faceCentroidPointsNeedUpdate) return; - m_viewChanged = false; + _faceCentroidPointsNeedUpdate = false; // Array of booleans to indicate which faces are visible static bool faces_visible[brush::c_brush_maxFaces]; @@ -434,6 +517,8 @@ void BrushNode::evaluateViewDependent(const VolumeTest& volume, const Matrix4& l bool* j = faces_visible; bool forceVisible = isForcedVisible(); + _visibleFaceCentroidPoints.clear(); + // Iterator to an index of a visible face std::size_t* visibleFaceIter = visibleFaceIndices; std::size_t curFaceIndex = 0; @@ -447,6 +532,8 @@ void BrushNode::evaluateViewDependent(const VolumeTest& volume, const Matrix4& l // Don't cull backfacing planes to make those faces visible in orthoview (#5465) if (forceVisible || i->faceIsVisible()) { + _visibleFaceCentroidPoints.push_back(i->centroid()); +#if 0 *j = true; // Store the index of this visible face in the array @@ -456,23 +543,46 @@ void BrushNode::evaluateViewDependent(const VolumeTest& volume, const Matrix4& l else { *j = false; +#endif } } - +#if 0 m_brush.update_wireframe(m_render_wireframe, faces_visible); - m_brush.update_faces_wireframe(_faceCentroidPointsCulled, visibleFaceIndices, numVisibleFaces); +#endif +#if 0 + m_brush.update_faces_wireframe(_visibleFaceCentroidPoints, visibleFaceIndices, numVisibleFaces); +#endif } +#endif -void BrushNode::renderSolid(RenderableCollector& collector, +#if 0 +void BrushNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { assert(_renderEntity); // brushes rendered without parent entity - no way! +#ifdef RENDERABLE_GEOMETRY + if (isSelected()) + { + for (FaceInstance& faceInst : const_cast(*this).m_faceInstances) + { + // Send the winding geometry for rendering highlights + auto& winding = faceInst.getFace().getWinding(); + + if (!winding.empty()) + { + collector.addGeometry(winding, IRenderableCollector::Highlight::Primitives|IRenderableCollector::Highlight::Flags::Faces); + } + } + } +#endif + +#if 0 // The faces already sent their geomtry in onPreRender() // Check for the override status of this brush bool forceVisible = isForcedVisible(); - // Submit the lights and renderable geometry for each face + // Submit the renderable geometry for each face for (const FaceInstance& faceInst : m_faceInstances) { // Skip invisible faces before traversing further @@ -483,62 +593,68 @@ void BrushNode::renderSolid(RenderableCollector& collector, { bool highlight = faceInst.selectedComponents(); if (highlight) - collector.setHighlightFlag(RenderableCollector::Highlight::Faces, true); + collector.setHighlightFlag(IRenderableCollector::Highlight::Faces, true); +#if 1 // greebo: BrushNodes have always an identity l2w, don't do any transforms collector.addRenderable( *face.getFaceShader().getGLShader(), face.getWinding(), Matrix4::getIdentity(), this, _renderEntity ); - +#endif if (highlight) - collector.setHighlightFlag(RenderableCollector::Highlight::Faces, false); + collector.setHighlightFlag(IRenderableCollector::Highlight::Faces, false); } } - +#endif +#if 0 renderSelectedPoints(collector, volume, localToWorld); +#endif } -void BrushNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const +void BrushNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { //renderCommon(collector, volume); - evaluateViewDependent(volume, localToWorld); - + //updateWireframeVisibility(); +#if 0 if (m_render_wireframe.m_size != 0) { collector.addRenderable(*_renderEntity->getWireShader(), m_render_wireframe, localToWorld); } - +#endif +#if 0 renderSelectedPoints(collector, volume, localToWorld); +#endif } +#endif -void BrushNode::update_selected() const +void BrushNode::updateSelectedPointsArray() const { - if (!_renderableComponentsNeedUpdate) return; + if (!_renderableComponentsNeedUpdate) return; - _renderableComponentsNeedUpdate = false; + _renderableComponentsNeedUpdate = false; - _selectedPoints.clear(); + _selectedPoints.clear(); - for (FaceInstances::const_iterator i = m_faceInstances.begin(); i != m_faceInstances.end(); ++i) { - if (i->getFace().contributes()) { - i->iterate_selected(_selectedPoints); - } - } + for (const auto& faceInstance : m_faceInstances) + { + if (faceInstance.getFace().contributes()) + { + faceInstance.iterate_selected(_selectedPoints); + } + } } -void BrushNode::renderSelectedPoints(RenderableCollector& collector, +void BrushNode::renderSelectedPoints(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { - m_brush.evaluateBRep(); - - update_selected(); + updateSelectedPointsArray(); if (!_selectedPoints.empty()) { - collector.setHighlightFlag(RenderableCollector::Highlight::Primitives, false); + collector.setHighlightFlag(IRenderableCollector::Highlight::Primitives, false); collector.addRenderable(*m_state_selpoint, _selectedPoints, localToWorld); } } @@ -637,6 +753,7 @@ void BrushNode::_onTransformationChanged() m_brush.transformChanged(); _renderableComponentsNeedUpdate = true; + _faceCentroidPointsNeedUpdate = true; } void BrushNode::_applyTransformation() @@ -647,3 +764,14 @@ void BrushNode::_applyTransformation() _untransformedOriginChanged = true; } + +void BrushNode::onVisibilityChanged(bool isVisibleNow) +{ + SelectableNode::onVisibilityChanged(isVisibleNow); + + // Let each face know about the change + forEachFaceInstance([=](FaceInstance& face) + { + face.getFace().onBrushVisibilityChanged(isVisibleNow); + }); +} diff --git a/radiantcore/brush/BrushNode.h b/radiantcore/brush/BrushNode.h index 587ba7ff85..2aea450b11 100644 --- a/radiantcore/brush/BrushNode.h +++ b/radiantcore/brush/BrushNode.h @@ -42,14 +42,16 @@ class BrushNode : typedef std::vector VertexInstances; VertexInstances m_vertexInstances; - mutable RenderableWireframe m_render_wireframe; + mutable bool _faceCentroidPointsNeedUpdate; +#if 0 + mutable RenderableWireframe m_render_wireframe; +#endif // Renderable array of vertex and edge points mutable RenderablePointVector _selectedPoints; mutable AABB m_aabb_component; - mutable RenderablePointVector _faceCentroidPointsCulled; - mutable bool m_viewChanged; // requires re-evaluation of view-dependent cached data + mutable RenderablePointVector _visibleFaceCentroidPoints; BrushClipPlane m_clipPlane; @@ -140,12 +142,13 @@ class BrushNode : bool intersectsLight(const RendererLight& light) const override; // Renderable implementation - void renderComponents(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void onPreRender(const VolumeTest& volume) override; + void renderComponents(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override; void setRenderSystem(const RenderSystemPtr& renderSystem) override; - void viewChanged() const override; std::size_t getHighlightFlags() override; void evaluateTransform(); @@ -168,10 +171,17 @@ class BrushNode : // Should only be used by the internal Brush object bool facesAreForcedVisible(); + // Will be invoked if one of this brush's faces updates its visibility status + void onFaceVisibilityChanged(); + void onPostUndo() override; void onPostRedo() override; protected: + virtual void onVisibilityChanged(bool isVisibleNow) override; + + virtual void setForcedVisibility(bool forceVisible, bool includeChildren) override; + // Gets called by the Transformable implementation whenever // scale, rotation or translation is changed. void _onTransformationChanged() override; @@ -183,16 +193,17 @@ class BrushNode : private: void transformComponents(const Matrix4& matrix); - void renderSolid(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; +#if 0 + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; +#endif - void update_selected() const; - void renderSelectedPoints(RenderableCollector& collector, + void updateSelectedPointsArray() const; + void renderSelectedPoints(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; - void renderClipPlane(RenderableCollector& collector, const VolumeTest& volume) const; - void evaluateViewDependent(const VolumeTest& volume, const Matrix4& localToWorld) const; + void updateFaceCentroidPoints() const; }; // class BrushNode typedef std::shared_ptr BrushNodePtr; diff --git a/radiantcore/brush/Face.cpp b/radiantcore/brush/Face.cpp index a2fa7dbec7..cb581bf55f 100644 --- a/radiantcore/brush/Face.cpp +++ b/radiantcore/brush/Face.cpp @@ -35,7 +35,9 @@ Face::Face(Brush& owner) : _owner(owner), _shader(texdef_name_default(), _owner.getBrushNode().getRenderSystem()), _undoStateSaver(nullptr), - _faceIsVisible(true) + _faceIsVisible(true), + _windingSurfaceSolid(m_winding), + _windingSurfaceWireframe(m_winding) { setupSurfaceShader(); @@ -58,7 +60,9 @@ Face::Face( _shader(shader, _owner.getBrushNode().getRenderSystem()), _texdef(projection), _undoStateSaver(nullptr), - _faceIsVisible(true) + _faceIsVisible(true), + _windingSurfaceSolid(m_winding), + _windingSurfaceWireframe(m_winding) { setupSurfaceShader(); m_plane.initialiseFromPoints(p0, p1, p2); @@ -70,7 +74,9 @@ Face::Face(Brush& owner, const Plane3& plane) : _owner(owner), _shader("", _owner.getBrushNode().getRenderSystem()), _undoStateSaver(nullptr), - _faceIsVisible(true) + _faceIsVisible(true), + _windingSurfaceSolid(m_winding), + _windingSurfaceWireframe(m_winding) { setupSurfaceShader(); m_plane.setPlane(plane); @@ -82,7 +88,9 @@ Face::Face(Brush& owner, const Plane3& plane, const Matrix3& textureProjection, _owner(owner), _shader(material, _owner.getBrushNode().getRenderSystem()), _undoStateSaver(nullptr), - _faceIsVisible(true) + _faceIsVisible(true), + _windingSurfaceSolid(m_winding), + _windingSurfaceWireframe(m_winding) { setupSurfaceShader(); m_plane.setPlane(plane); @@ -101,7 +109,9 @@ Face::Face(Brush& owner, const Face& other) : _shader(other._shader.getMaterialName(), _owner.getBrushNode().getRenderSystem()), _texdef(other.getProjection()), _undoStateSaver(nullptr), - _faceIsVisible(other._faceIsVisible) + _faceIsVisible(other._faceIsVisible), + _windingSurfaceSolid(m_winding), + _windingSurfaceWireframe(m_winding) { setupSurfaceShader(); planepts_assign(m_move_planepts, other.m_move_planepts); @@ -113,6 +123,10 @@ Face::~Face() _surfaceShaderRealised.disconnect(); _sigDestroyed.emit(); _sigDestroyed.clear(); + + // Deallocate the winding surface + _windingSurfaceSolid.clear(); + _windingSurfaceWireframe.clear(); } sigc::signal& Face::signal_faceDestroyed() @@ -144,6 +158,9 @@ Brush& Face::getBrushInternal() void Face::planeChanged() { + _windingSurfaceSolid.queueUpdate(); + _windingSurfaceWireframe.queueUpdate(); + revertTransform(); _owner.onFacePlaneChanged(); } @@ -159,6 +176,8 @@ void Face::connectUndoSystem(IUndoSystem& undoSystem) _shader.setInUse(true); + _windingSurfaceSolid.queueUpdate(); + _windingSurfaceWireframe.queueUpdate(); _undoStateSaver = undoSystem.getStateSaver(*this); } @@ -168,6 +187,9 @@ void Face::disconnectUndoSystem(IUndoSystem& undoSystem) _undoStateSaver = nullptr; undoSystem.releaseStateSaver(*this); + // Disconnect the renderable winding vertices from the scene + _windingSurfaceSolid.clear(); + _windingSurfaceWireframe.clear(); _shader.setInUse(false); } @@ -233,7 +255,7 @@ bool Face::intersectVolume(const VolumeTest& volume, const Matrix4& localToWorld } } -void Face::renderWireframe(RenderableCollector& collector, const Matrix4& localToWorld, +void Face::renderWireframe(IRenderableCollector& collector, const Matrix4& localToWorld, const IRenderEntity& entity) const { collector.addRenderable(*entity.getWireShader(), m_winding, localToWorld, @@ -247,14 +269,10 @@ void Face::setRenderSystem(const RenderSystemPtr& renderSystem) // Update the visibility flag, we might have switched shaders const ShaderPtr& shader = _shader.getGLShader(); - if (shader) - { - _faceIsVisible = shader->getMaterial()->isVisible(); - } - else - { - _faceIsVisible = false; // no shader => not visible - } + _faceIsVisible = shader && shader->getMaterial()->isVisible(); + + _windingSurfaceSolid.clear(); + _windingSurfaceWireframe.clear(); } void Face::transformTexDefLocked(const Matrix4& transform) @@ -337,7 +355,10 @@ void Face::freezeTransform() updateWinding(); } -void Face::updateWinding() { +void Face::updateWinding() +{ + _windingSurfaceSolid.queueUpdate(); + _windingSurfaceWireframe.queueUpdate(); m_winding.updateNormals(m_plane.getPlane().normal()); } @@ -385,16 +406,10 @@ void Face::shaderChanged() // Update the visibility flag, but leave out the contributes() check const ShaderPtr& shader = getFaceShader().getGLShader(); + _faceIsVisible = shader && shader->getMaterial()->isVisible(); - if (shader) - { - _faceIsVisible = shader->getMaterial()->isVisible(); - } - else - { - _faceIsVisible = false; // no shader => not visible - } - + _windingSurfaceSolid.queueUpdate(); + _windingSurfaceWireframe.queueUpdate(); planeChanged(); SceneChangeNotify(); } @@ -681,6 +696,16 @@ Winding& Face::getWinding() { return m_winding; } +render::RenderableWinding& Face::getWindingSurfaceSolid() +{ + return _windingSurfaceSolid; +} + +render::RenderableWinding& Face::getWindingSurfaceWireframe() +{ + return _windingSurfaceWireframe; +} + const Plane3& Face::plane3() const { _owner.onFaceEvaluateTransform(); @@ -729,9 +754,32 @@ bool Face::isVisible() const return _faceIsVisible; } +void Face::onBrushVisibilityChanged(bool visible) +{ + if (!visible) + { + // Disconnect our renderable when the owning brush goes invisible + _windingSurfaceSolid.clear(); + _windingSurfaceWireframe.clear(); + } + else + { + // Update the vertex buffers next time we need to render + _windingSurfaceSolid.queueUpdate(); + _windingSurfaceWireframe.queueUpdate(); + } +} + void Face::updateFaceVisibility() { - _faceIsVisible = contributes() && getFaceShader().getGLShader()->getMaterial()->isVisible(); + auto newValue = contributes() && getFaceShader().getGLShader()->getMaterial()->isVisible(); + + // Notify the owning brush if the value changes + if (newValue != _faceIsVisible) + { + _faceIsVisible = newValue; + _owner.getBrushNode().onFaceVisibilityChanged(); + } } sigc::signal& Face::signal_texdefChanged() diff --git a/radiantcore/brush/Face.h b/radiantcore/brush/Face.h index 99d592e4f0..4b3d18181b 100644 --- a/radiantcore/brush/Face.h +++ b/radiantcore/brush/Face.h @@ -15,6 +15,7 @@ #include "util/Noncopyable.h" #include #include "selection/algorithm/Shader.h" +#include "RenderableWinding.h" const double GRID_MIN = 0.125; @@ -60,6 +61,9 @@ class Face : // Cached visibility flag, queried during front end rendering bool _faceIsVisible; + render::RenderableWinding _windingSurfaceSolid; + render::RenderableWinding _windingSurfaceWireframe; + sigc::signal _sigDestroyed; public: @@ -108,7 +112,7 @@ class Face : bool intersectVolume(const VolumeTest& volume, const Matrix4& localToWorld) const; // Frontend render methods for submitting the face winding - void renderWireframe(RenderableCollector& collector, const Matrix4& localToWorld, + void renderWireframe(IRenderableCollector& collector, const Matrix4& localToWorld, const IRenderEntity& entity) const; void setRenderSystem(const RenderSystemPtr& renderSystem); @@ -192,6 +196,9 @@ class Face : const Winding& getWinding() const override; Winding& getWinding() override; + render::RenderableWinding& getWindingSurfaceSolid(); + render::RenderableWinding& getWindingSurfaceWireframe(); + const Plane3& plane3() const; // Returns the Doom 3 plane @@ -211,6 +218,9 @@ class Face : bool isVisible() const override; + // Called when the owning brush changes its visibility status + void onBrushVisibilityChanged(bool visible); + void updateFaceVisibility(); // Signal for external code to get notified each time the texdef of any face changes diff --git a/radiantcore/brush/FaceInstance.cpp b/radiantcore/brush/FaceInstance.cpp index 78fda4a8c7..2e0a507cbd 100644 --- a/radiantcore/brush/FaceInstance.cpp +++ b/radiantcore/brush/FaceInstance.cpp @@ -169,14 +169,14 @@ bool FaceInstance::intersectVolume(const VolumeTest& volume, const Matrix4& loca return m_face->intersectVolume(volume, localToWorld); } -void FaceInstance::renderWireframe(RenderableCollector& collector, const VolumeTest& volume, +void FaceInstance::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume, const IRenderEntity& entity) const { if (m_face->intersectVolume(volume)) { if (selectedComponents()) { - collector.setHighlightFlag(RenderableCollector::Highlight::Faces, true); + collector.setHighlightFlag(IRenderableCollector::Highlight::Faces, true); } m_face->renderWireframe(collector, Matrix4::getIdentity(), entity); diff --git a/radiantcore/brush/FaceInstance.h b/radiantcore/brush/FaceInstance.h index 4f028c254f..650616df92 100644 --- a/radiantcore/brush/FaceInstance.h +++ b/radiantcore/brush/FaceInstance.h @@ -13,7 +13,7 @@ typedef const Plane3* PlanePointer; typedef PlanePointer* PlanesIterator; -class RenderableCollector; +class IRenderableCollector; class FaceInstance; typedef std::list FaceInstanceSet; @@ -109,7 +109,7 @@ class FaceInstance bool intersectVolume(const VolumeTest& volume, const Matrix4& localToWorld) const; // Frontend render methods for submitting the face to the given collector - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume, + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume, const IRenderEntity& entity) const; void testSelect(SelectionTest& test, SelectionIntersection& best); diff --git a/radiantcore/brush/RenderableWinding.h b/radiantcore/brush/RenderableWinding.h new file mode 100644 index 0000000000..6311ac830f --- /dev/null +++ b/radiantcore/brush/RenderableWinding.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include "irender.h" +#include "Winding.h" + +namespace render +{ + +class RenderableWinding : + public OpenGLRenderable +{ +private: + const Winding& _winding; + ShaderPtr _shader; + bool _needsUpdate; + + IWindingRenderer::Slot _slot; + Winding::size_type _windingSize; + +public: + RenderableWinding(const Winding& winding) : + _winding(winding), + _needsUpdate(true), + _slot(IWindingRenderer::InvalidSlot), + _windingSize(0) + {} + + void queueUpdate() + { + _needsUpdate = true; + } + + void update(const ShaderPtr& shader) + { + bool shaderChanged = _shader != shader; + + if (!_needsUpdate && !shaderChanged) return; + + _needsUpdate = false; + + auto numPoints = _winding.size(); + + if (numPoints < 3 || !shader) + { + clear(); + return; + } + + std::vector vertices; + vertices.reserve(numPoints); + + for (const auto& vertex : _winding) + { + vertices.emplace_back(ArbitraryMeshVertex(vertex.vertex, vertex.normal, vertex.texcoord, { 1, 1, 1 })); + } + + if (_shader && _slot != IWindingRenderer::InvalidSlot && (shaderChanged || numPoints != _windingSize)) + { + _shader->removeWinding(_slot); + _slot = IWindingRenderer::InvalidSlot; + _windingSize = 0; + } + + _shader = shader; + _windingSize = numPoints; + + if (_slot == IWindingRenderer::InvalidSlot) + { + _slot = shader->addWinding(vertices); + } + else + { + shader->updateWinding(_slot, vertices); + } + } + + void clear() + { + if (!_shader || _slot == IWindingRenderer::InvalidSlot) return; + + _shader->removeWinding(_slot); + _shader.reset(); + _slot = IWindingRenderer::InvalidSlot; + _windingSize = 0; + } + + void render(const RenderInfo& info) const override + { + if (_slot != render::IWindingRenderer::InvalidSlot && _shader) + { + _shader->renderWinding(render::IWindingRenderer::RenderMode::Polygon, _slot); + } + } +}; + +} diff --git a/radiantcore/brush/RenderableWireFrame.h b/radiantcore/brush/RenderableWireFrame.h index 7fe63f8c4b..b8ac5ef645 100644 --- a/radiantcore/brush/RenderableWireFrame.h +++ b/radiantcore/brush/RenderableWireFrame.h @@ -17,6 +17,7 @@ struct EdgeRenderIndices {} }; +#if 0 class RenderableWireframe : public OpenGLRenderable { @@ -48,5 +49,6 @@ class RenderableWireframe : virtual ~RenderableWireframe() {} }; +#endif #endif /*RENDERABLEWIREFRAME_H_*/ diff --git a/radiantcore/brush/Winding.cpp b/radiantcore/brush/Winding.cpp index 8ca018a956..04fbd2f9fa 100644 --- a/radiantcore/brush/Winding.cpp +++ b/radiantcore/brush/Winding.cpp @@ -297,3 +297,50 @@ void Winding::printConnectivity() << " adjacent: " << i->adjacent << std::endl; } } + +#ifdef RENDERABLE_GEOMETRY +Winding::Type Winding::getType() const +{ + return Type::Polygons; +} + +const Vector3& Winding::getFirstVertex() +{ + return front().vertex; +} + +std::size_t Winding::getVertexStride() +{ + return sizeof(WindingVertex); +} + +const unsigned int& Winding::getFirstIndex() +{ + updateIndices(); + return _indices.front(); +} + +std::size_t Winding::getNumIndices() +{ + updateIndices(); + return _indices.size(); +} + +void Winding::updateIndices() +{ + auto windingSize = size(); + + if (_indices.size() == windingSize) return; + + if (_indices.size() > windingSize) + { + _indices.resize(windingSize); + return; + } + + while (_indices.size() < windingSize) + { + _indices.push_back(static_cast(_indices.size())); + } +} +#endif diff --git a/radiantcore/brush/Winding.h b/radiantcore/brush/Winding.h index 0a2277e39b..f48cb4347d 100644 --- a/radiantcore/brush/Winding.h +++ b/radiantcore/brush/Winding.h @@ -1,26 +1,4 @@ -/* -Copyright (C) 1999-2006 Id Software, Inc. and contributors. -For a list of contributors, see the accompanying CONTRIBUTORS file. - -This file is part of GtkRadiant. - -GtkRadiant 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. - -GtkRadiant 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 GtkRadiant; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#if !defined(INCLUDED_WINDING_H) -#define INCLUDED_WINDING_H +#pragma once #include "debugging/debugging.h" @@ -28,6 +6,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include "iclipper.h" #include "irender.h" +#include "irenderable.h" #include "iselectiontest.h" #include "ibrush.h" @@ -40,10 +19,16 @@ class SelectionIntersection; // The Winding structure extends the abstract IWinding class // by a few methods for rendering and selection tests. -class Winding : +class Winding final : public IWinding, public OpenGLRenderable +#ifdef RENDERABLE_GEOMETRY + , public RenderableGeometry +#endif { +private: + std::vector _indices; + public: /** greebo: Calculates the AABB of this winding */ @@ -96,6 +81,14 @@ class Winding : /// \brief Returns true if any point in \p w1 is in front of plane2, or any point in \p w2 is in front of plane1 static bool planesConcave(const Winding& w1, const Winding& w2, const Plane3& plane1, const Plane3& plane2); -}; +#ifdef RENDERABLE_GEOMETRY + Type getType() const override; + const Vector3& getFirstVertex() override; + std::size_t getVertexStride() override; + const unsigned int& getFirstIndex() override; + std::size_t getNumIndices() override; + + void updateIndices(); #endif +}; diff --git a/radiantcore/entity/EntityNode.cpp b/radiantcore/entity/EntityNode.cpp index 5c5e522182..bd4de60a30 100644 --- a/radiantcore/entity/EntityNode.cpp +++ b/radiantcore/entity/EntityNode.cpp @@ -357,7 +357,12 @@ scene::INode::Type EntityNode::getNodeType() const return Type::Entity; } -void EntityNode::renderSolid(RenderableCollector& collector, +bool EntityNode::isOriented() const +{ + return true; +} + +void EntityNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { // Render any attached entities @@ -366,7 +371,7 @@ void EntityNode::renderSolid(RenderableCollector& collector, ); } -void EntityNode::renderWireframe(RenderableCollector& collector, +void EntityNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { // Submit renderable text name if required @@ -382,6 +387,18 @@ void EntityNode::renderWireframe(RenderableCollector& collector, ); } +void EntityNode::renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) +{ + if (collector.supportsFullMaterials()) + { + renderSolid(collector, volume); + } + else + { + renderWireframe(collector, volume); + } +} + void EntityNode::acquireShaders() { acquireShaders(getRenderSystem()); diff --git a/radiantcore/entity/EntityNode.h b/radiantcore/entity/EntityNode.h index e8f70ab502..b08597c951 100644 --- a/radiantcore/entity/EntityNode.h +++ b/radiantcore/entity/EntityNode.h @@ -144,8 +144,10 @@ class EntityNode : Type getNodeType() const override; // Renderable implementation, can be overridden by subclasses - virtual void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - virtual void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + virtual bool isOriented() const override; + virtual void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + virtual void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + virtual void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override; virtual void setRenderSystem(const RenderSystemPtr& renderSystem) override; virtual std::size_t getHighlightFlags() override; diff --git a/radiantcore/entity/VertexInstance.h b/radiantcore/entity/VertexInstance.h index 7829a62848..d806fa9afb 100644 --- a/radiantcore/entity/VertexInstance.h +++ b/radiantcore/entity/VertexInstance.h @@ -83,12 +83,12 @@ class VertexInstance : } // Front-end render function - void render(RenderableCollector& collector, + void render(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { - collector.setHighlightFlag(RenderableCollector::Highlight::Primitives, false); - collector.setHighlightFlag(RenderableCollector::Highlight::Faces, false); + collector.setHighlightFlag(IRenderableCollector::Highlight::Primitives, false); + collector.setHighlightFlag(IRenderableCollector::Highlight::Faces, false); collector.addRenderable(*_shader, *this, localToWorld); } diff --git a/radiantcore/entity/curve/Curve.cpp b/radiantcore/entity/curve/Curve.cpp index 9a380c478c..3eee18b814 100644 --- a/radiantcore/entity/curve/Curve.cpp +++ b/radiantcore/entity/curve/Curve.cpp @@ -69,7 +69,7 @@ ControlPoints& Curve::getControlPoints() { return _controlPoints; } -void Curve::submitRenderables(const ShaderPtr& shader, RenderableCollector& collector, const VolumeTest& volume, +void Curve::submitRenderables(const ShaderPtr& shader, IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { collector.addRenderable(*shader, _renderCurve, localToWorld); diff --git a/radiantcore/entity/curve/Curve.h b/radiantcore/entity/curve/Curve.h index da62be3f19..dc16f4c7e2 100644 --- a/radiantcore/entity/curve/Curve.h +++ b/radiantcore/entity/curve/Curve.h @@ -80,7 +80,7 @@ class Curve : virtual void saveToEntity(Entity& target) = 0; // Front-end render method - void submitRenderables(const ShaderPtr& shader, RenderableCollector& collector, + void submitRenderables(const ShaderPtr& shader, IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; // Performs a selection test on the point vertices of this curve diff --git a/radiantcore/entity/curve/CurveEditInstance.cpp b/radiantcore/entity/curve/CurveEditInstance.cpp index 77a8827733..4baad7f132 100644 --- a/radiantcore/entity/curve/CurveEditInstance.cpp +++ b/radiantcore/entity/curve/CurveEditInstance.cpp @@ -167,13 +167,13 @@ void CurveEditInstance::updateSelected() const { forEachSelected(adder); } -void CurveEditInstance::renderComponents(RenderableCollector& collector, +void CurveEditInstance::renderComponents(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { collector.addRenderable(*_shaders.controlsShader, m_controlsRender, localToWorld); } -void CurveEditInstance::renderComponentsSelected(RenderableCollector& collector, +void CurveEditInstance::renderComponentsSelected(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { updateSelected(); diff --git a/radiantcore/entity/curve/CurveEditInstance.h b/radiantcore/entity/curve/CurveEditInstance.h index 9b707648f1..80a85fbb97 100644 --- a/radiantcore/entity/curve/CurveEditInstance.h +++ b/radiantcore/entity/curve/CurveEditInstance.h @@ -81,9 +81,9 @@ class CurveEditInstance: public sigc::trackable void updateSelected() const; - void renderComponents(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; + void renderComponents(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; - void renderComponentsSelected(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; + void renderComponentsSelected(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; void curveChanged(); diff --git a/radiantcore/entity/doom3group/Doom3GroupNode.cpp b/radiantcore/entity/doom3group/Doom3GroupNode.cpp index 452ff5a4db..0c66ddf054 100644 --- a/radiantcore/entity/doom3group/Doom3GroupNode.cpp +++ b/radiantcore/entity/doom3group/Doom3GroupNode.cpp @@ -260,7 +260,7 @@ void Doom3GroupNode::testSelect(Selector& selector, SelectionTest& test) } } -void Doom3GroupNode::renderCommon(RenderableCollector& collector, const VolumeTest& volume) const +void Doom3GroupNode::renderCommon(IRenderableCollector& collector, const VolumeTest& volume) const { if (isSelected()) { m_renderOrigin.render(collector, volume, localToWorld()); @@ -277,23 +277,25 @@ void Doom3GroupNode::renderCommon(RenderableCollector& collector, const VolumeTe } } -void Doom3GroupNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void Doom3GroupNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { EntityNode::renderSolid(collector, volume); renderCommon(collector, volume); // Render curves always relative to the absolute map origin - _nurbsEditInstance.renderComponentsSelected(collector, volume, Matrix4::getIdentity()); - _catmullRomEditInstance.renderComponentsSelected(collector, volume, Matrix4::getIdentity()); + static Matrix4 identity = Matrix4::getIdentity(); + _nurbsEditInstance.renderComponentsSelected(collector, volume, identity); + _catmullRomEditInstance.renderComponentsSelected(collector, volume, identity); } -void Doom3GroupNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void Doom3GroupNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { EntityNode::renderWireframe(collector, volume); renderCommon(collector, volume); - _nurbsEditInstance.renderComponentsSelected(collector, volume, Matrix4::getIdentity()); - _catmullRomEditInstance.renderComponentsSelected(collector, volume, Matrix4::getIdentity()); + static Matrix4 identity = Matrix4::getIdentity(); + _nurbsEditInstance.renderComponentsSelected(collector, volume, identity); + _catmullRomEditInstance.renderComponentsSelected(collector, volume, identity); } void Doom3GroupNode::setRenderSystem(const RenderSystemPtr& renderSystem) @@ -307,7 +309,7 @@ void Doom3GroupNode::setRenderSystem(const RenderSystemPtr& renderSystem) _originInstance.setRenderSystem(renderSystem); } -void Doom3GroupNode::renderComponents(RenderableCollector& collector, const VolumeTest& volume) const +void Doom3GroupNode::renderComponents(IRenderableCollector& collector, const VolumeTest& volume) const { if (GlobalSelectionSystem().ComponentMode() == selection::ComponentSelectionMode::Vertex) { diff --git a/radiantcore/entity/doom3group/Doom3GroupNode.h b/radiantcore/entity/doom3group/Doom3GroupNode.h index 70835ea8cf..fd5a37a4fc 100644 --- a/radiantcore/entity/doom3group/Doom3GroupNode.h +++ b/radiantcore/entity/doom3group/Doom3GroupNode.h @@ -126,11 +126,11 @@ class Doom3GroupNode : void removeOriginFromChildren() override; // Renderable implementation - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; void setRenderSystem(const RenderSystemPtr& renderSystem) override; - void renderComponents(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderComponents(IRenderableCollector& collector, const VolumeTest& volume) const override; void transformComponents(const Matrix4& matrix); @@ -160,7 +160,7 @@ class Doom3GroupNode : void destroy(); void setIsModel(bool newValue); - void renderCommon(RenderableCollector& collector, const VolumeTest& volume) const; + void renderCommon(IRenderableCollector& collector, const VolumeTest& volume) const; /** Determine if this Doom3Group is a model (func_static) or a * brush-containing entity. If the "model" key is equal to the diff --git a/radiantcore/entity/eclassmodel/EclassModelNode.cpp b/radiantcore/entity/eclassmodel/EclassModelNode.cpp index e43cefd5a2..aae397495b 100644 --- a/radiantcore/entity/eclassmodel/EclassModelNode.cpp +++ b/radiantcore/entity/eclassmodel/EclassModelNode.cpp @@ -68,7 +68,7 @@ const AABB& EclassModelNode::localAABB() const return _localAABB; } -void EclassModelNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void EclassModelNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { EntityNode::renderSolid(collector, volume); @@ -78,7 +78,7 @@ void EclassModelNode::renderSolid(RenderableCollector& collector, const VolumeTe } } -void EclassModelNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void EclassModelNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { EntityNode::renderWireframe(collector, volume); diff --git a/radiantcore/entity/eclassmodel/EclassModelNode.h b/radiantcore/entity/eclassmodel/EclassModelNode.h index 222e4f329b..ea7e9f145f 100644 --- a/radiantcore/entity/eclassmodel/EclassModelNode.h +++ b/radiantcore/entity/eclassmodel/EclassModelNode.h @@ -63,8 +63,8 @@ class EclassModelNode : scene::INodePtr clone() const override; // Renderable implementation - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; void setRenderSystem(const RenderSystemPtr& renderSystem) override; // Returns the original "origin" value diff --git a/radiantcore/entity/generic/GenericEntityNode.cpp b/radiantcore/entity/generic/GenericEntityNode.cpp index 5bc540a916..061b4b9170 100644 --- a/radiantcore/entity/generic/GenericEntityNode.cpp +++ b/radiantcore/entity/generic/GenericEntityNode.cpp @@ -127,7 +127,7 @@ GenericEntityNode::SolidAAABBRenderMode GenericEntityNode::getSolidAABBRenderMod return _solidAABBRenderMode; } -void GenericEntityNode::renderArrow(const ShaderPtr& shader, RenderableCollector& collector, +void GenericEntityNode::renderArrow(const ShaderPtr& shader, IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { if (EntitySettings::InstancePtr()->getShowEntityAngles()) @@ -136,7 +136,7 @@ void GenericEntityNode::renderArrow(const ShaderPtr& shader, RenderableCollector } } -void GenericEntityNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void GenericEntityNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { EntityNode::renderSolid(collector, volume); @@ -147,7 +147,7 @@ void GenericEntityNode::renderSolid(RenderableCollector& collector, const Volume renderArrow(shader, collector, volume, localToWorld()); } -void GenericEntityNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void GenericEntityNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { EntityNode::renderWireframe(collector, volume); diff --git a/radiantcore/entity/generic/GenericEntityNode.h b/radiantcore/entity/generic/GenericEntityNode.h index 9e317d0b03..e8f7d91e86 100644 --- a/radiantcore/entity/generic/GenericEntityNode.h +++ b/radiantcore/entity/generic/GenericEntityNode.h @@ -99,10 +99,10 @@ class GenericEntityNode: public EntityNode, public Snappable scene::INodePtr clone() const override; // Renderable implementation - void renderArrow(const ShaderPtr& shader, RenderableCollector& collector, + void renderArrow(const ShaderPtr& shader, IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; SolidAAABBRenderMode getSolidAABBRenderMode() const; diff --git a/radiantcore/entity/light/Light.cpp b/radiantcore/entity/light/Light.cpp index 05d787cd5d..280c228f0a 100644 --- a/radiantcore/entity/light/Light.cpp +++ b/radiantcore/entity/light/Light.cpp @@ -447,13 +447,13 @@ Doom3LightRadius& Light::getDoom3Radius() { return m_doom3Radius; } -void Light::renderProjectionPoints(RenderableCollector& collector, +void Light::renderProjectionPoints(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { // Add the renderable light target - collector.setHighlightFlag(RenderableCollector::Highlight::Primitives, false); - collector.setHighlightFlag(RenderableCollector::Highlight::Faces, false); + collector.setHighlightFlag(IRenderableCollector::Highlight::Primitives, false); + collector.setHighlightFlag(IRenderableCollector::Highlight::Faces, false); collector.addRenderable(*_rRight.getShader(), _rRight, localToWorld); collector.addRenderable(*_rUp.getShader(), _rUp, localToWorld); @@ -471,7 +471,7 @@ void Light::renderProjectionPoints(RenderableCollector& collector, } // Adds the light centre renderable to the given collector -void Light::renderLightCentre(RenderableCollector& collector, +void Light::renderLightCentre(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { diff --git a/radiantcore/entity/light/Light.h b/radiantcore/entity/light/Light.h index 3118b09d7d..8004e9507e 100644 --- a/radiantcore/entity/light/Light.h +++ b/radiantcore/entity/light/Light.h @@ -198,7 +198,7 @@ class Light: public RendererLight mutable Matrix4 m_projectionOrientation; // Renderable submission functions - void renderWireframe(RenderableCollector& collector, + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld, bool selected) const; @@ -206,8 +206,8 @@ class Light: public RendererLight void setRenderSystem(const RenderSystemPtr& renderSystem); // Adds the light centre renderable to the given collector - void renderLightCentre(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; - void renderProjectionPoints(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; + void renderLightCentre(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; + void renderProjectionPoints(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; // Returns a reference to the member class Doom3LightRadius (used to set colours) Doom3LightRadius& getDoom3Radius(); diff --git a/radiantcore/entity/light/LightNode.cpp b/radiantcore/entity/light/LightNode.cpp index 3ab2f3975f..8ef68becb9 100644 --- a/radiantcore/entity/light/LightNode.cpp +++ b/radiantcore/entity/light/LightNode.cpp @@ -273,7 +273,7 @@ void LightNode::selectedChangedComponent(const ISelectable& selectable) { GlobalSelectionSystem().onComponentSelection(Node::getSelf(), selectable); } -void LightNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void LightNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { // Submit self to the renderer as an actual light source collector.addLight(_light); @@ -286,7 +286,7 @@ void LightNode::renderSolid(RenderableCollector& collector, const VolumeTest& vo renderInactiveComponents(collector, volume, lightIsSelected); } -void LightNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void LightNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { EntityNode::renderWireframe(collector, volume); @@ -295,7 +295,7 @@ void LightNode::renderWireframe(RenderableCollector& collector, const VolumeTest renderInactiveComponents(collector, volume, lightIsSelected); } -void LightNode::renderLightVolume(RenderableCollector& collector, +void LightNode::renderLightVolume(IRenderableCollector& collector, const Matrix4& localToWorld, bool selected) const { @@ -351,7 +351,7 @@ void LightNode::setRenderSystem(const RenderSystemPtr& renderSystem) } // Renders the components of this light instance -void LightNode::renderComponents(RenderableCollector& collector, const VolumeTest& volume) const +void LightNode::renderComponents(IRenderableCollector& collector, const VolumeTest& volume) const { // Render the components (light center) as selected/deselected, if we are in the according mode if (GlobalSelectionSystem().ComponentMode() == selection::ComponentSelectionMode::Vertex) @@ -399,7 +399,7 @@ void LightNode::renderComponents(RenderableCollector& collector, const VolumeTes } } -void LightNode::renderInactiveComponents(RenderableCollector& collector, const VolumeTest& volume, const bool selected) const +void LightNode::renderInactiveComponents(IRenderableCollector& collector, const VolumeTest& volume, const bool selected) const { // greebo: We are not in component selection mode (and the light is still selected), // check if we should draw the center of the light anyway diff --git a/radiantcore/entity/light/LightNode.h b/radiantcore/entity/light/LightNode.h index 3625f8a0cc..8d626c14a0 100644 --- a/radiantcore/entity/light/LightNode.h +++ b/radiantcore/entity/light/LightNode.h @@ -114,10 +114,10 @@ class LightNode : void selectedChangedComponent(const ISelectable& selectable); // Renderable implementation - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; void setRenderSystem(const RenderSystemPtr& renderSystem) override; - void renderComponents(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderComponents(IRenderableCollector& collector, const VolumeTest& volume) const override; // OpenGLRenderable implementation void render(const RenderInfo& info) const override; @@ -140,11 +140,11 @@ class LightNode : void construct() override; private: - void renderInactiveComponents(RenderableCollector& collector, const VolumeTest& volume, const bool selected) const; + void renderInactiveComponents(IRenderableCollector& collector, const VolumeTest& volume, const bool selected) const; void evaluateTransform(); // Render the light volume including bounds and origin - void renderLightVolume(RenderableCollector& collector, + void renderLightVolume(IRenderableCollector& collector, const Matrix4& localToWorld, bool selected) const; // Update the bounds of the renderable radius box diff --git a/radiantcore/entity/speaker/SpeakerNode.cpp b/radiantcore/entity/speaker/SpeakerNode.cpp index 4caf63ea0e..857a679384 100644 --- a/radiantcore/entity/speaker/SpeakerNode.cpp +++ b/radiantcore/entity/speaker/SpeakerNode.cpp @@ -244,7 +244,7 @@ scene::INodePtr SpeakerNode::clone() const /* Renderable implementation */ -void SpeakerNode::renderSolid(RenderableCollector& collector, +void SpeakerNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { EntityNode::renderSolid(collector, volume); @@ -258,7 +258,7 @@ void SpeakerNode::renderSolid(RenderableCollector& collector, collector.addRenderable(*getFillShader(), _renderableRadii, localToWorld()); } } -void SpeakerNode::renderWireframe(RenderableCollector& collector, +void SpeakerNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { EntityNode::renderWireframe(collector, volume); diff --git a/radiantcore/entity/speaker/SpeakerNode.h b/radiantcore/entity/speaker/SpeakerNode.h index 46370f4544..6975608467 100644 --- a/radiantcore/entity/speaker/SpeakerNode.h +++ b/radiantcore/entity/speaker/SpeakerNode.h @@ -111,8 +111,8 @@ class SpeakerNode : scene::INodePtr clone() const override; // Renderable implementation - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; void selectedChangedComponent(const ISelectable& selectable); diff --git a/radiantcore/entity/target/RenderableTargetLines.h b/radiantcore/entity/target/RenderableTargetLines.h index 746493ecf2..be1f917200 100644 --- a/radiantcore/entity/target/RenderableTargetLines.h +++ b/radiantcore/entity/target/RenderableTargetLines.h @@ -39,7 +39,7 @@ class RenderableTargetLines : return !_targetKeys.empty(); } - void render(const ShaderPtr& shader, RenderableCollector& collector, const VolumeTest& volume, const Vector3& worldPosition) + void render(const ShaderPtr& shader, IRenderableCollector& collector, const VolumeTest& volume, const Vector3& worldPosition) { if (_targetKeys.empty()) { diff --git a/radiantcore/entity/target/TargetLineNode.cpp b/radiantcore/entity/target/TargetLineNode.cpp index aa17496a99..621bda9a2e 100644 --- a/radiantcore/entity/target/TargetLineNode.cpp +++ b/radiantcore/entity/target/TargetLineNode.cpp @@ -28,12 +28,12 @@ const AABB& TargetLineNode::localAABB() const return _aabb; } -void TargetLineNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void TargetLineNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { renderWireframe(collector, volume); } -void TargetLineNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void TargetLineNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { // If the owner is hidden, the lines are hidden too if (!_targetLines.hasTargets() || !_owner.visible()) return; @@ -41,6 +41,11 @@ void TargetLineNode::renderWireframe(RenderableCollector& collector, const Volum _targetLines.render(_owner.getWireShader(), collector, volume, getOwnerPosition()); } +void TargetLineNode::renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) +{ + renderWireframe(collector, volume); +} + std::size_t TargetLineNode::getHighlightFlags() { // We don't need to return highlighting, since the render system will use diff --git a/radiantcore/entity/target/TargetLineNode.h b/radiantcore/entity/target/TargetLineNode.h index 84d97b93e4..771167bb9e 100644 --- a/radiantcore/entity/target/TargetLineNode.h +++ b/radiantcore/entity/target/TargetLineNode.h @@ -36,8 +36,9 @@ class TargetLineNode : const AABB& localAABB() const override; - void renderSolid(RenderableCollector& collector, const VolumeTest& volumeTest) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volumeTest) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volumeTest) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volumeTest) const override; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volumeTest) override; std::size_t getHighlightFlags() override; private: diff --git a/radiantcore/layers/LayerManager.cpp b/radiantcore/layers/LayerManager.cpp index f9c6277a33..d1cac05042 100644 --- a/radiantcore/layers/LayerManager.cpp +++ b/radiantcore/layers/LayerManager.cpp @@ -393,7 +393,7 @@ bool LayerManager::updateNodeVisibility(const scene::INodePtr& node) const auto& layers = node->getLayers(); // We start with the assumption that a node is hidden - node->enable(Node::eLayered); + bool isHidden = true; // Cycle through the Node's layers, and show the node as soon as // a visible layer is found. @@ -402,13 +402,22 @@ bool LayerManager::updateNodeVisibility(const scene::INodePtr& node) if (_layerVisibility[layerId]) { // The layer is visible, set the visibility to true and quit - node->disable(Node::eLayered); - return true; + isHidden = false; + break; } } - // Node is hidden, return FALSE - return false; + if (isHidden) + { + node->enable(Node::eLayered); + } + else + { + node->disable(Node::eLayered); + } + + // If node is hidden, return FALSE + return !isHidden; } void LayerManager::setSelected(int layerID, bool selected) diff --git a/radiantcore/map/PointFile.cpp b/radiantcore/map/PointFile.cpp index 6df2267718..272992dbcc 100644 --- a/radiantcore/map/PointFile.cpp +++ b/radiantcore/map/PointFile.cpp @@ -82,7 +82,7 @@ void PointFile::show(const fs::path& pointfile) SceneChangeNotify(); } -void PointFile::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void PointFile::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { if (isVisible()) { @@ -90,7 +90,7 @@ void PointFile::renderSolid(RenderableCollector& collector, const VolumeTest& vo } } -void PointFile::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void PointFile::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { renderSolid(collector, volume); } diff --git a/radiantcore/map/PointFile.h b/radiantcore/map/PointFile.h index a1585200d2..18e46df06b 100644 --- a/radiantcore/map/PointFile.h +++ b/radiantcore/map/PointFile.h @@ -36,12 +36,15 @@ class PointFile: public Renderable /* * Solid renderable submission function (front-end) */ - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; /* * Wireframe renderable submission function (front-end). */ - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override + {} void setRenderSystem(const RenderSystemPtr& renderSystem) override {} diff --git a/radiantcore/map/RootNode.h b/radiantcore/map/RootNode.h index 79ab3ec4da..db7f6fecd1 100644 --- a/radiantcore/map/RootNode.h +++ b/radiantcore/map/RootNode.h @@ -71,12 +71,15 @@ class RootNode : IUndoSystem& getUndoSystem() override; // Renderable implementation (empty) - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override {} - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override {} + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override + {} + std::size_t getHighlightFlags() override { return Highlight::NoHighlight; // never highlighted diff --git a/radiantcore/model/NullModel.cpp b/radiantcore/model/NullModel.cpp index 9de66e2c3a..6dfd491a95 100644 --- a/radiantcore/model/NullModel.cpp +++ b/radiantcore/model/NullModel.cpp @@ -20,13 +20,13 @@ const AABB& NullModel::localAABB() const { return _aabbLocal; } -void NullModel::renderSolid(RenderableCollector& collector, +void NullModel::renderSolid(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { collector.addRenderable(*_state, _aabbSolid, localToWorld); } -void NullModel::renderWireframe(RenderableCollector& collector, +void NullModel::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { collector.addRenderable(*_state, _aabbWire, localToWorld); diff --git a/radiantcore/model/NullModel.h b/radiantcore/model/NullModel.h index e7f3611e29..f58839e737 100644 --- a/radiantcore/model/NullModel.h +++ b/radiantcore/model/NullModel.h @@ -23,8 +23,8 @@ class NullModel : const AABB& localAABB() const; - void renderSolid(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; void setRenderSystem(const RenderSystemPtr& renderSystem); void testSelect(Selector& selector, SelectionTest& test, const Matrix4& localToWorld); diff --git a/radiantcore/model/NullModelNode.cpp b/radiantcore/model/NullModelNode.cpp index 6bb40e522d..6b48401b28 100644 --- a/radiantcore/model/NullModelNode.cpp +++ b/radiantcore/model/NullModelNode.cpp @@ -58,14 +58,26 @@ void NullModelNode::testSelect(Selector& selector, SelectionTest& test) { _nullModel->testSelect(selector, test, localToWorld()); } -void NullModelNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const { +void NullModelNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { _nullModel->renderSolid(collector, volume, localToWorld()); } -void NullModelNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const { +void NullModelNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { _nullModel->renderWireframe(collector, volume, localToWorld()); } +void NullModelNode::renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) +{ + if (collector.supportsFullMaterials()) + { + renderSolid(collector, volume); + } + else + { + renderWireframe(collector, volume); + } +} + void NullModelNode::setRenderSystem(const RenderSystemPtr& renderSystem) { _nullModel->setRenderSystem(renderSystem); diff --git a/radiantcore/model/NullModelNode.h b/radiantcore/model/NullModelNode.h index 4949fc8499..a18b34584b 100644 --- a/radiantcore/model/NullModelNode.h +++ b/radiantcore/model/NullModelNode.h @@ -37,8 +37,9 @@ class NullModelNode : void testSelect(Selector& selector, SelectionTest& test) override; - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override; void setRenderSystem(const RenderSystemPtr& renderSystem) override; std::size_t getHighlightFlags() override diff --git a/radiantcore/model/StaticModel.cpp b/radiantcore/model/StaticModel.cpp index 4773d46eb8..aa71dca17b 100644 --- a/radiantcore/model/StaticModel.cpp +++ b/radiantcore/model/StaticModel.cpp @@ -79,7 +79,7 @@ void StaticModel::foreachVisibleSurface(const std::function StaticModelSurfacePtr; } -class RenderableCollector; +class IRenderableCollector; class RendererLight; class SelectionTest; class Selector; @@ -122,9 +122,9 @@ class StaticModel : // Delegated render methods called by StaticModelNode (not part of any // interface) - void renderSolid(RenderableCollector& rend, const Matrix4& localToWorld, + void renderSolid(IRenderableCollector& rend, const Matrix4& localToWorld, const IRenderEntity& entity, const LitObject& litObj) const; - void renderWireframe(RenderableCollector& rend, const Matrix4& localToWorld, + void renderWireframe(IRenderableCollector& rend, const Matrix4& localToWorld, const IRenderEntity& entity) const; void setRenderSystem(const RenderSystemPtr& renderSystem); diff --git a/radiantcore/model/StaticModelNode.cpp b/radiantcore/model/StaticModelNode.cpp index 8401567f71..d7feba3bff 100644 --- a/radiantcore/model/StaticModelNode.cpp +++ b/radiantcore/model/StaticModelNode.cpp @@ -91,32 +91,43 @@ bool StaticModelNode::intersectsLight(const RendererLight& light) const return light.lightAABB().intersects(worldAABB()); } -void StaticModelNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +bool StaticModelNode::isOriented() const +{ + return true; +} + +void StaticModelNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { assert(_renderEntity); const Matrix4& l2w = localToWorld(); - // Test the model's intersection volume, if it intersects pass on the - // render call - if (volume.TestAABB(_model->localAABB(), l2w) != VOLUME_OUTSIDE) + // The space partitioning system will consider this node also if the cell is only partially visible + // Do a quick bounds check against the world AABB to cull ourselves if we're not in the view + if (volume.TestAABB(worldAABB()) != VOLUME_OUTSIDE) { // Submit the model's geometry _model->renderSolid(collector, l2w, *_renderEntity, *this); } } -void StaticModelNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void StaticModelNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { assert(_renderEntity); - // Test the model's intersection volume, if it intersects pass on the render call - const Matrix4& l2w = localToWorld(); + // Submit the model's geometry + _model->renderWireframe(collector, localToWorld(), *_renderEntity); +} - if (volume.TestAABB(_model->localAABB(), l2w) != VOLUME_OUTSIDE) +void StaticModelNode::renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) +{ + if (collector.supportsFullMaterials()) { - // Submit the model's geometry - _model->renderWireframe(collector, l2w, *_renderEntity); + renderSolid(collector, volume); + } + else + { + renderWireframe(collector, volume); } } diff --git a/radiantcore/model/StaticModelNode.h b/radiantcore/model/StaticModelNode.h index 19e0a81fab..5835acc362 100644 --- a/radiantcore/model/StaticModelNode.h +++ b/radiantcore/model/StaticModelNode.h @@ -74,8 +74,10 @@ class StaticModelNode : bool intersectsLight(const RendererLight& light) const override; // Renderable implementation - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + bool isOriented() const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override; void setRenderSystem(const RenderSystemPtr& renderSystem) override; std::size_t getHighlightFlags() override diff --git a/radiantcore/model/md5/MD5ModelNode.cpp b/radiantcore/model/md5/MD5ModelNode.cpp index a315c7eec0..37da98767e 100644 --- a/radiantcore/model/md5/MD5ModelNode.cpp +++ b/radiantcore/model/md5/MD5ModelNode.cpp @@ -72,20 +72,25 @@ bool MD5ModelNode::intersectsLight(const RendererLight& light) const return light.lightAABB().intersects(worldAABB()); } -void MD5ModelNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void MD5ModelNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { assert(_renderEntity); render(collector, volume, localToWorld(), *_renderEntity); } -void MD5ModelNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void MD5ModelNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { assert(_renderEntity); render(collector, volume, localToWorld(), *_renderEntity); } +void MD5ModelNode::renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) +{ + render(collector, volume, localToWorld(), *_renderEntity); +} + void MD5ModelNode::setRenderSystem(const RenderSystemPtr& renderSystem) { Node::setRenderSystem(renderSystem); @@ -93,7 +98,7 @@ void MD5ModelNode::setRenderSystem(const RenderSystemPtr& renderSystem) _model->setRenderSystem(renderSystem); } -void MD5ModelNode::render(RenderableCollector& collector, const VolumeTest& volume, +void MD5ModelNode::render(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld, const IRenderEntity& entity) const { // Do some rough culling (per model, not per surface) diff --git a/radiantcore/model/md5/MD5ModelNode.h b/radiantcore/model/md5/MD5ModelNode.h index 28e435a084..684f767124 100644 --- a/radiantcore/model/md5/MD5ModelNode.h +++ b/radiantcore/model/md5/MD5ModelNode.h @@ -51,8 +51,9 @@ class MD5ModelNode : bool intersectsLight(const RendererLight& light) const override; // Renderable implementation - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override; void setRenderSystem(const RenderSystemPtr& renderSystem) override; std::size_t getHighlightFlags() override @@ -65,7 +66,7 @@ class MD5ModelNode : void skinChanged(const std::string& newSkinName) override; private: - void render(RenderableCollector& collector, const VolumeTest& volume, + void render(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld, const IRenderEntity& entity) const; }; typedef std::shared_ptr MD5ModelNodePtr; diff --git a/radiantcore/particles/ParticleNode.cpp b/radiantcore/particles/ParticleNode.cpp index 7673db4d4e..f15b42ee98 100644 --- a/radiantcore/particles/ParticleNode.cpp +++ b/radiantcore/particles/ParticleNode.cpp @@ -59,7 +59,12 @@ Matrix4 ParticleNode::localToParent() const return _local2Parent; } -void ParticleNode::renderSolid(RenderableCollector& collector, +bool ParticleNode::isOriented() const +{ + return true; +} + +void ParticleNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { if (!_renderableParticle) return; @@ -70,7 +75,7 @@ void ParticleNode::renderSolid(RenderableCollector& collector, _renderableParticle->renderSolid(collector, volume, localToWorld(), _renderEntity); } -void ParticleNode::renderWireframe(RenderableCollector& collector, +void ParticleNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { // greebo: For now, don't draw particles in ortho views they are too distracting @@ -84,6 +89,13 @@ void ParticleNode::renderWireframe(RenderableCollector& collector, #endif } +void ParticleNode::renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) +{ + if (!collector.supportsFullMaterials()) return; + + renderSolid(collector, volume); +} + void ParticleNode::setRenderSystem(const RenderSystemPtr& renderSystem) { Node::setRenderSystem(renderSystem); diff --git a/radiantcore/particles/ParticleNode.h b/radiantcore/particles/ParticleNode.h index 80c1beb44e..ded2b9beff 100644 --- a/radiantcore/particles/ParticleNode.h +++ b/radiantcore/particles/ParticleNode.h @@ -35,8 +35,10 @@ class ParticleNode : const AABB& localAABB() const override; std::size_t getHighlightFlags() override; - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + bool isOriented() const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override; void setRenderSystem(const RenderSystemPtr& renderSystem) override; diff --git a/radiantcore/particles/RenderableParticle.cpp b/radiantcore/particles/RenderableParticle.cpp index 8fdc491dff..9feb193300 100644 --- a/radiantcore/particles/RenderableParticle.cpp +++ b/radiantcore/particles/RenderableParticle.cpp @@ -50,7 +50,7 @@ void RenderableParticle::update(const Matrix4& viewRotation) } // Front-end render methods -void RenderableParticle::renderSolid(RenderableCollector& collector, +void RenderableParticle::renderSolid(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld, const IRenderEntity* entity) const @@ -71,24 +71,29 @@ void RenderableParticle::renderSolid(RenderableCollector& collector, } } -void RenderableParticle::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void RenderableParticle::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { renderSolid(collector, volume, Matrix4::getIdentity(), nullptr); } -void RenderableParticle::renderWireframe(RenderableCollector& collector, const VolumeTest& volume, +void RenderableParticle::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld, const IRenderEntity* entity) const { // Does the same thing as renderSolid renderSolid(collector, volume, localToWorld, entity); } -void RenderableParticle::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void RenderableParticle::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { // Does the same thing as renderSolid renderSolid(collector, volume); } +void RenderableParticle::renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) +{ + renderSolid(collector, volume); +} + void RenderableParticle::setRenderSystem(const RenderSystemPtr& renderSystem) { _renderSystem = renderSystem; diff --git a/radiantcore/particles/RenderableParticle.h b/radiantcore/particles/RenderableParticle.h index 1d017bac12..709fbbb7d7 100644 --- a/radiantcore/particles/RenderableParticle.h +++ b/radiantcore/particles/RenderableParticle.h @@ -63,14 +63,16 @@ class RenderableParticle : public IRenderableParticle, void update(const Matrix4& viewRotation) override; // Front-end render methods - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderSolid(RenderableCollector& collector, const VolumeTest& volume, + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld, const IRenderEntity* entity) const; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume, + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld, const IRenderEntity* entity) const; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override; + void setRenderSystem(const RenderSystemPtr& renderSystem) override; std::size_t getHighlightFlags() override diff --git a/radiantcore/patch/Patch.cpp b/radiantcore/patch/Patch.cpp index 5f821a65e3..4d7464d4c2 100644 --- a/radiantcore/patch/Patch.cpp +++ b/radiantcore/patch/Patch.cpp @@ -48,9 +48,6 @@ inline bool double_valid(double f) { Patch::Patch(PatchNode& node) : _node(node), _undoStateSaver(nullptr), - _solidRenderable(_mesh), - _wireframeRenderable(_mesh), - _fixedWireframeRenderable(_mesh), _renderableNTBVectors(_mesh), _renderableCtrlPoints(GL_POINTS, _ctrl_vertices), _renderableLattice(GL_LINES, _latticeIndices, _ctrl_vertices), @@ -69,9 +66,6 @@ Patch::Patch(const Patch& other, PatchNode& node) : IUndoable(other), _node(node), _undoStateSaver(nullptr), - _solidRenderable(_mesh), - _wireframeRenderable(_mesh), - _fixedWireframeRenderable(_mesh), _renderableNTBVectors(_mesh), _renderableCtrlPoints(GL_POINTS, _ctrl_vertices), _renderableLattice(GL_LINES, _latticeIndices, _ctrl_vertices), @@ -191,17 +185,8 @@ const AABB& Patch::localAABB() const return _localAABB; } -void Patch::renderWireframe(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld, const IRenderEntity& entity) const -{ - // Defer the tesselation calculation to the last minute - const_cast(*this).updateTesselation(); - - collector.addRenderable(*entity.getWireShader(), - _patchDef3 ? _fixedWireframeRenderable : _wireframeRenderable, localToWorld); -} - // greebo: This renders the patch components, namely the lattice and the corner controls -void Patch::submitRenderablePoints(RenderableCollector& collector, +void Patch::submitRenderablePoints(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { @@ -332,6 +317,7 @@ void Patch::controlPointsChanged() transformChanged(); evaluateTransform(); updateTesselation(); + _node.onControlPointsChanged(); for (Observers::iterator i = _observers.begin(); i != _observers.end();) { @@ -582,16 +568,7 @@ void Patch::updateTesselation(bool force) } } - _solidRenderable.queueUpdate(); - - if (_patchDef3) - { - _fixedWireframeRenderable.queueUpdate(); - } - else - { - _wireframeRenderable.queueUpdate(); - } + _node.onTesselationChanged(); } void Patch::invertMatrix() @@ -2698,7 +2675,9 @@ bool Patch::getIntersection(const Ray& ray, Vector3& intersection) void Patch::textureChanged() { - for (Observers::iterator i = _observers.begin(); i != _observers.end();) + _node.onMaterialChanged(); + + for (auto i = _observers.begin(); i != _observers.end();) { (*i++)->onPatchTextureChanged(); } diff --git a/radiantcore/patch/Patch.h b/radiantcore/patch/Patch.h index 72ff852026..a5262bdbf3 100644 --- a/radiantcore/patch/Patch.h +++ b/radiantcore/patch/Patch.h @@ -56,10 +56,6 @@ class Patch : // The tesselation for this patch PatchTesselation _mesh; - // The OpenGL renderables for three rendering modes - RenderablePatchSolid _solidRenderable; - RenderablePatchWireframe _wireframeRenderable; - RenderablePatchFixedWireframe _fixedWireframeRenderable; RenderablePatchVectorsNTB _renderableNTBVectors; // The shader states for the control points and the lattice @@ -118,12 +114,8 @@ class Patch : // Return the interally stored AABB const AABB& localAABB() const override; - // Render functions: wireframe mode and components - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume, - const Matrix4& localToWorld, const IRenderEntity& entity) const; - /// Submit renderable edge and face points - void submitRenderablePoints(RenderableCollector& collector, + void submitRenderablePoints(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; diff --git a/radiantcore/patch/PatchNode.cpp b/radiantcore/patch/PatchNode.cpp index 49ec97010a..9bfdabb080 100644 --- a/radiantcore/patch/PatchNode.cpp +++ b/radiantcore/patch/PatchNode.cpp @@ -13,7 +13,10 @@ PatchNode::PatchNode(patch::PatchDefType type) : m_dragPlanes(std::bind(&PatchNode::selectedChangedComponent, this, std::placeholders::_1)), m_render_selected(GL_POINTS), m_patch(*this), - _untransformedOriginChanged(true) + _untransformedOriginChanged(true), + _selectedControlVerticesNeedUpdate(true), + _renderableSurfaceSolid(m_patch.getTesselation()), + _renderableSurfaceWireframe(m_patch.getTesselation()) { m_patch.setFixedSubdivisions(type == patch::PatchDefType::Def3, Subdivisions(m_patch.getSubdivisions())); } @@ -34,12 +37,18 @@ PatchNode::PatchNode(const PatchNode& other) : m_dragPlanes(std::bind(&PatchNode::selectedChangedComponent, this, std::placeholders::_1)), m_render_selected(GL_POINTS), m_patch(other.m_patch, *this), // create the patch out of the one - _untransformedOriginChanged(true) + _untransformedOriginChanged(true), + _selectedControlVerticesNeedUpdate(true), + _renderableSurfaceSolid(m_patch.getTesselation()), + _renderableSurfaceWireframe(m_patch.getTesselation()) { } PatchNode::~PatchNode() -{} +{ + _renderableSurfaceSolid.clear(); + _renderableSurfaceWireframe.clear(); +} scene::INode::Type PatchNode::getNodeType() const { @@ -211,15 +220,18 @@ void PatchNode::invertSelectedComponents(selection::ComponentSelectionMode mode) } } -void PatchNode::testSelectComponents(Selector& selector, SelectionTest& test, selection::ComponentSelectionMode mode) { +void PatchNode::testSelectComponents(Selector& selector, SelectionTest& test, selection::ComponentSelectionMode mode) +{ test.BeginMesh(localToWorld()); // Only react to eVertex selection mode switch(mode) { - case selection::ComponentSelectionMode::Vertex: { + case selection::ComponentSelectionMode::Vertex: + { // Cycle through all the control instances and test them for selection - for (PatchControlInstances::iterator i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i) { - i->testSelect(selector, test); + for (auto& i : m_ctrl_instances) + { + i.testSelect(selector, test); } } break; @@ -252,7 +264,11 @@ bool PatchNode::hasVisibleMaterial() const return m_patch.getSurfaceShader().getGLShader()->getMaterial()->isVisible(); } -void PatchNode::selectedChangedComponent(const ISelectable& selectable) { +void PatchNode::selectedChangedComponent(const ISelectable& selectable) +{ + // We need to update our vertex colours next time we render them + _selectedControlVerticesNeedUpdate = true; + // Notify the selection system that this PatchNode was selected. The RadiantSelectionSystem adds // this to its internal list of selected nodes. GlobalSelectionSystem().onComponentSelection(SelectableNode::getSelf(), selectable); @@ -268,6 +284,8 @@ void PatchNode::onInsertIntoScene(scene::IMapRootNode& root) { // Mark the GL shader as used from now on, this is used by the TextureBrowser's filtering m_patch.getSurfaceShader().setInUse(true); + _renderableSurfaceSolid.queueUpdate(); + _renderableSurfaceWireframe.queueUpdate(); m_patch.connectUndoSystem(root.getUndoSystem()); GlobalCounters().getCounter(counterPatches).increment(); @@ -290,6 +308,8 @@ void PatchNode::onRemoveFromScene(scene::IMapRootNode& root) m_patch.disconnectUndoSystem(root.getUndoSystem()); + _renderableSurfaceSolid.clear(); + _renderableSurfaceWireframe.clear(); m_patch.getSurfaceShader().setInUse(false); SelectableNode::onRemoveFromScene(root); @@ -304,50 +324,79 @@ bool PatchNode::intersectsLight(const RendererLight& light) const { return light.lightAABB().intersects(worldAABB()); } -void PatchNode::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void PatchNode::onPreRender(const VolumeTest& volume) { - // Don't render invisible shaders - if (!isForcedVisible() && !m_patch.hasVisibleMaterial()) return; + // Don't do anything when invisible + if (!isForcedVisible() && !m_patch.hasVisibleMaterial()) return; // Defer the tesselation calculation to the last minute - const_cast(m_patch).evaluateTransform(); - const_cast(m_patch).updateTesselation(); + m_patch.evaluateTransform(); + m_patch.updateTesselation(); + + if (volume.fill()) + { + _renderableSurfaceSolid.update(m_patch._shader.getGLShader()); + } + else + { + _renderableSurfaceWireframe.update(_renderEntity->getWireShader()); + } +} + +void PatchNode::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const +{ + // Don't render invisible patches + if (!isForcedVisible() && !m_patch.hasVisibleMaterial()) return; - assert(_renderEntity); // patches rendered without parent - no way! +#ifdef RENDERABLE_GEOMETRY + if (isSelected()) + { + // Send the patch geometry for rendering highlights + collector.addGeometry(const_cast(m_patch)._solidRenderable, + IRenderableCollector::Highlight::Primitives | IRenderableCollector::Highlight::Flags::Faces); + } +#endif + assert(_renderEntity); // patches rendered without parent - no way! +#if 0 // Render the patch itself collector.addRenderable( *m_patch._shader.getGLShader(), m_patch._solidRenderable, localToWorld(), this, _renderEntity ); - +#endif #if DEBUG_PATCH_NTB_VECTORS m_patch._renderableVectors.render(collector, volume, localToWorld()); #endif - - // Render the selected components - renderComponentsSelected(collector, volume); } -void PatchNode::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void PatchNode::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { // Don't render invisible shaders if (!isForcedVisible() && !m_patch.hasVisibleMaterial()) return; const_cast(m_patch).evaluateTransform(); - // Pass the call to the patch instance, it adds the renderable - m_patch.renderWireframe(collector, volume, localToWorld(), *_renderEntity); - // Render the selected components renderComponentsSelected(collector, volume); } +void PatchNode::renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) +{ + // Overlay the selected node with the quadrangulated wireframe + collector.addHighlightRenderable(_renderableSurfaceWireframe, localToWorld()); + + // Render the selected components + renderComponentsSelected(collector, volume); +} + void PatchNode::setRenderSystem(const RenderSystemPtr& renderSystem) { SelectableNode::setRenderSystem(renderSystem); m_patch.setRenderSystem(renderSystem); + _renderableSurfaceSolid.clear(); + _renderableSurfaceWireframe.clear(); if (renderSystem) { @@ -360,7 +409,7 @@ void PatchNode::setRenderSystem(const RenderSystemPtr& renderSystem) } // Renders the components of this patch instance -void PatchNode::renderComponents(RenderableCollector& collector, const VolumeTest& volume) const +void PatchNode::renderComponents(IRenderableCollector& collector, const VolumeTest& volume) const { // Don't render invisible shaders if (!m_patch.getSurfaceShader().getGLShader()->getMaterial()->isVisible()) return; @@ -375,17 +424,22 @@ void PatchNode::renderComponents(RenderableCollector& collector, const VolumeTes } } -void PatchNode::update_selected() const { +void PatchNode::updateSelectedControlVertices() const +{ + if (!_selectedControlVerticesNeedUpdate) return; + + _selectedControlVerticesNeedUpdate = false; + // Clear the renderable point vector that represents the selection m_render_selected.clear(); // Cycle through the transformed patch vertices and set the colour of all selected control vertices to BLUE (hardcoded) - PatchControlConstIter ctrl = m_patch.getControlPointsTransformed().begin(); + auto ctrl = m_patch.getControlPointsTransformed().begin(); - for (PatchControlInstances::const_iterator i = m_ctrl_instances.begin(); - i != m_ctrl_instances.end(); ++i, ++ctrl) + for (auto i = m_ctrl_instances.begin(); i != m_ctrl_instances.end(); ++i, ++ctrl) { - if (i->isSelected()) { + if (i->isSelected()) + { const Colour4b colour_selected(0, 0, 0, 255); // Add this patch control instance to the render list m_render_selected.push_back(VertexCb(reinterpret_cast(ctrl->vertex), colour_selected)); @@ -393,18 +447,17 @@ void PatchNode::update_selected() const { } } -void PatchNode::renderComponentsSelected(RenderableCollector& collector, const VolumeTest& volume) const +void PatchNode::renderComponentsSelected(IRenderableCollector& collector, const VolumeTest& volume) const { - // greebo: Don't know yet, what evaluateTransform() is really doing const_cast(m_patch).evaluateTransform(); // Rebuild the array of selected control vertices - update_selected(); + updateSelectedControlVertices(); // If there are any selected components, add them to the collector if (!m_render_selected.empty()) { - collector.setHighlightFlag(RenderableCollector::Highlight::Primitives, false); + collector.setHighlightFlag(IRenderableCollector::Highlight::Primitives, false); collector.addRenderable(*m_state_selpoint, m_render_selected, localToWorld()); } } @@ -454,6 +507,7 @@ void PatchNode::transformComponents(const Matrix4& matrix) { // mark this patch transform as dirty m_patch.transformChanged(); + _selectedControlVerticesNeedUpdate = true; } // Also, check if there are any drag planes selected @@ -467,6 +521,8 @@ void PatchNode::transformComponents(const Matrix4& matrix) { void PatchNode::_onTransformationChanged() { m_patch.transformChanged(); + _renderableSurfaceSolid.queueUpdate(); + _renderableSurfaceWireframe.queueUpdate(); } void PatchNode::_applyTransformation() @@ -489,3 +545,39 @@ const Vector3& PatchNode::getUntransformedOrigin() return _untransformedOrigin; } + +void PatchNode::onTesselationChanged() +{ + _renderableSurfaceSolid.queueUpdate(); + _renderableSurfaceWireframe.queueUpdate(); +} + +void PatchNode::onControlPointsChanged() +{ + _renderableSurfaceSolid.queueUpdate(); + _renderableSurfaceWireframe.queueUpdate(); +} + +void PatchNode::onMaterialChanged() +{ + _renderableSurfaceSolid.queueUpdate(); + _renderableSurfaceWireframe.queueUpdate(); +} + +void PatchNode::onVisibilityChanged(bool visible) +{ + SelectableNode::onVisibilityChanged(visible); + + if (!visible) + { + // Disconnect our renderable when the node is hidden + _renderableSurfaceSolid.clear(); + _renderableSurfaceWireframe.clear(); + } + else + { + // Update the vertex buffers next time we need to render + _renderableSurfaceSolid.queueUpdate(); + _renderableSurfaceWireframe.queueUpdate(); + } +} diff --git a/radiantcore/patch/PatchNode.h b/radiantcore/patch/PatchNode.h index 2766d86f69..f4ccacd8bc 100644 --- a/radiantcore/patch/PatchNode.h +++ b/radiantcore/patch/PatchNode.h @@ -9,8 +9,9 @@ #include "scene/SelectableNode.h" #include "PatchControlInstance.h" #include "dragplanes.h" +#include "PatchRenderables.h" -class PatchNode : +class PatchNode final : public scene::SelectableNode, public scene::Cloneable, public Snappable, @@ -47,14 +48,18 @@ class PatchNode : // If true, the _untransformedOrigin member needs an update bool _untransformedOriginChanged; + mutable bool _selectedControlVerticesNeedUpdate; + + RenderablePatchTesselation _renderableSurfaceSolid; + RenderablePatchTesselation _renderableSurfaceWireframe; + public: - // Construct a PatchNode with no arguments PatchNode(patch::PatchDefType type); // Copy Constructor PatchNode(const PatchNode& other); - virtual ~PatchNode(); + ~PatchNode(); // Patch::Observer implementation void allocate(std::size_t size); @@ -92,8 +97,8 @@ class PatchNode : void testSelectComponents(Selector& selector, SelectionTest& test, selection::ComponentSelectionMode mode) override; // override scene::Inode::onRemoveFromScene to deselect the child components - virtual void onInsertIntoScene(scene::IMapRootNode& root) override; - virtual void onRemoveFromScene(scene::IMapRootNode& root) override; + void onInsertIntoScene(scene::IMapRootNode& root) override; + void onRemoveFromScene(scene::IMapRootNode& root) override; // Traceable implementation bool getIntersection(const Ray& ray, Vector3& intersection) override; @@ -130,12 +135,14 @@ class PatchNode : // Render functions, these make sure that all things get rendered properly. The calls are also passed on // to the contained patch - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void onPreRender(const VolumeTest& volume) override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override; void setRenderSystem(const RenderSystemPtr& renderSystem) override; // Renders the components of this patch instance, makes use of the Patch::render_component() method - void renderComponents(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderComponents(IRenderableCollector& collector, const VolumeTest& volume) const override; void evaluateTransform(); std::size_t getHighlightFlags() override; @@ -143,6 +150,10 @@ class PatchNode : // Returns the center of the untransformed world AABB const Vector3& getUntransformedOrigin() override; + void onControlPointsChanged(); + void onMaterialChanged(); + void onTesselationChanged(); + protected: // Gets called by the Transformable implementation whenever // scale, rotation or translation is changed. @@ -152,16 +163,18 @@ class PatchNode : // or when reverting transformations. void _applyTransformation() override; + void onVisibilityChanged(bool isVisibleNow) override; + private: // Transforms the patch components with the given transformation matrix void transformComponents(const Matrix4& matrix); // greebo: Updates the internal render array m_render_selected, that contains all control vertices that should be // rendered as highlighted. - void update_selected() const; + void updateSelectedControlVertices() const; // greebo: Renders the selected components. This is called by the above two render functions - void renderComponentsSelected(RenderableCollector& collector, const VolumeTest& volume) const; + void renderComponentsSelected(IRenderableCollector& collector, const VolumeTest& volume) const; }; typedef std::shared_ptr PatchNodePtr; typedef std::weak_ptr PatchNodeWeakPtr; diff --git a/radiantcore/patch/PatchRenderables.cpp b/radiantcore/patch/PatchRenderables.cpp index 0e7bdf5e5b..7801104429 100644 --- a/radiantcore/patch/PatchRenderables.cpp +++ b/radiantcore/patch/PatchRenderables.cpp @@ -1,45 +1,6 @@ #include "PatchRenderables.h" -void RenderablePatchWireframe::render(const RenderInfo& info) const -{ - // No colour changing - glDisableClientState(GL_COLOR_ARRAY); - if (info.checkFlag(RENDER_VERTEX_COLOUR)) - { - glColor3f(1, 1, 1); - } - - if (_tess.vertices.empty()) return; - - if (_needsUpdate) - { - _needsUpdate = false; - - // Create a VBO and add the vertex data - VertexBuffer_T currentVBuf; - currentVBuf.addVertices(_tess.vertices.begin(), _tess.vertices.end()); - - // Submit index batches - const RenderIndex* strip_indices = &_tess.indices.front(); - for (std::size_t i = 0; - i < _tess.numStrips; - i++, strip_indices += _tess.lenStrips) - { - currentVBuf.addIndexBatch(strip_indices, _tess.lenStrips); - } - - // Render all index batches - _vertexBuf.replaceData(currentVBuf); - } - - _vertexBuf.renderAllBatches(GL_QUAD_STRIP); -} - -void RenderablePatchWireframe::queueUpdate() -{ - _needsUpdate = true; -} - +#if 0 RenderablePatchSolid::RenderablePatchSolid(PatchTesselation& tess) : _tess(tess), _needsUpdate(true) @@ -95,6 +56,67 @@ void RenderablePatchSolid::queueUpdate() { _needsUpdate = true; } +#endif + +#ifdef RENDERABLE_GEOMETRY +RenderableGeometry::Type RenderablePatchSolid::getType() const +{ + return RenderableGeometry::Type::Quads; +} + +const Vector3& RenderablePatchSolid::getFirstVertex() +{ + return _tess.vertices.front().vertex; +} + +std::size_t RenderablePatchSolid::getVertexStride() +{ + return sizeof(ArbitraryMeshVertex); +} + +const unsigned int& RenderablePatchSolid::getFirstIndex() +{ + updateIndices(); + return _indices.front(); +} + +std::size_t RenderablePatchSolid::getNumIndices() +{ + updateIndices(); + return _indices.size(); +} + +void RenderablePatchSolid::updateIndices() +{ + // To render the patch mesh as quads, we need 4 indices per quad + auto numRequiredIndices = (_tess.height - 1) * (_tess.width - 1) * 4; + + if (_indices.size() == numRequiredIndices) + { + return; + } + + if (_tess.height == 0 || _tess.width == 0) + { + _indices.clear(); + return; + } + + _indices.resize(numRequiredIndices); + + auto index = 0; + for (auto h = 0; h < _tess.height - 1; ++h) + { + for (auto w = 0; w < _tess.width - 1; ++w) + { + _indices[index++] = static_cast(h * _tess.width + w + 0); + _indices[index++] = static_cast((h + 1) * _tess.width + w + 0); + _indices[index++] = static_cast((h + 1) * _tess.width + w + 1); + _indices[index++] = static_cast(h * _tess.width + w + 1); + } + } +} +#endif const ShaderPtr& RenderablePatchVectorsNTB::getShader() const { @@ -152,8 +174,8 @@ void RenderablePatchVectorsNTB::render(const RenderInfo& info) const glEnd(); } -void RenderablePatchVectorsNTB::render(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const +void RenderablePatchVectorsNTB::render(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const { - collector.setHighlightFlag(RenderableCollector::Highlight::Primitives, false); + collector.setHighlightFlag(IRenderableCollector::Highlight::Primitives, false); collector.addRenderable(*_shader, *this, localToWorld); } diff --git a/radiantcore/patch/PatchRenderables.h b/radiantcore/patch/PatchRenderables.h index 16cb8b804a..543d76e176 100644 --- a/radiantcore/patch/PatchRenderables.h +++ b/radiantcore/patch/PatchRenderables.h @@ -5,52 +5,22 @@ */ #pragma once +#include #include "igl.h" +#include "imodelsurface.h" + #include "PatchTesselation.h" #include "render/VertexBuffer.h" #include "render/IndexedVertexBuffer.h" -/// Helper class to render a PatchTesselation in wireframe mode -class RenderablePatchWireframe : - public OpenGLRenderable -{ -protected: - // Geometry source - const PatchTesselation& _tess; - - // VertexBuffer for rendering - typedef render::IndexedVertexBuffer VertexBuffer_T; - mutable VertexBuffer_T _vertexBuf; - - mutable bool _needsUpdate; - -public: - RenderablePatchWireframe(const PatchTesselation& tess) : - _tess(tess), - _needsUpdate(true) - { } - - virtual ~RenderablePatchWireframe() {} - - void render(const RenderInfo& info) const; - - void queueUpdate(); -}; - -/// Helper class to render a fixed geometry PatchTesselation in wireframe mode -class RenderablePatchFixedWireframe : - public RenderablePatchWireframe -{ -public: - RenderablePatchFixedWireframe(PatchTesselation& tess) : - RenderablePatchWireframe(tess) - {} -}; - +#if 0 /// Helper class to render a PatchTesselation in solid mode class RenderablePatchSolid : public OpenGLRenderable +#ifdef RENDERABLE_GEOMETRY + , public RenderableGeometry +#endif { // Geometry source PatchTesselation& _tess; @@ -61,13 +31,28 @@ class RenderablePatchSolid : mutable bool _needsUpdate; + // The render indices to render the mesh vertices as QUADS + std::vector _indices; + public: RenderablePatchSolid(PatchTesselation& tess); void render(const RenderInfo& info) const; void queueUpdate(); + +#ifdef RENDERABLE_GEOMETRY + Type getType() const override; + const Vector3& getFirstVertex() override; + std::size_t getVertexStride() override; + const unsigned int& getFirstIndex() override; + std::size_t getNumIndices() override; + +private: + void updateIndices(); +#endif }; +#endif // Renders a vertex' normal/tangent/bitangent vector (for debugging purposes) class RenderablePatchVectorsNTB : @@ -88,5 +73,166 @@ class RenderablePatchVectorsNTB : void render(const RenderInfo& info) const; - void render(RenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; + void render(IRenderableCollector& collector, const VolumeTest& volume, const Matrix4& localToWorld) const; +}; + +class ITesselationIndexer +{ +public: + virtual ~ITesselationIndexer() {} + + virtual render::SurfaceIndexingType getType() const = 0; + + // The number of indices generated by this indexer for the given tesselation + virtual std::size_t getNumIndices(const PatchTesselation& tess) const = 0; + + // Generate the indices for the given tesselation, assigning them to the given insert iterator + virtual void generateIndices(const PatchTesselation& tess, std::back_insert_iterator> outputIt) const = 0; +}; + +class TesselationIndexer_Triangles : + public ITesselationIndexer +{ +public: + render::SurfaceIndexingType getType() const override + { + return render::SurfaceIndexingType::Triangles; + } + + std::size_t getNumIndices(const PatchTesselation& tess) const override + { + return (tess.height - 1) * (tess.width - 1) * 6; // 6 => 2 triangles per quad + } + + void generateIndices(const PatchTesselation& tess, std::back_insert_iterator> outputIt) const override + { + // Generate the indices to define the triangles in clockwise order + for (std::size_t h = 0; h < tess.height - 1; ++h) + { + auto rowOffset = h * tess.width; + + for (std::size_t w = 0; w < tess.width - 1; ++w) + { + outputIt = static_cast(rowOffset + w + tess.width); + outputIt = static_cast(rowOffset + w + 1); + outputIt = static_cast(rowOffset + w); + + outputIt = static_cast(rowOffset + w + tess.width); + outputIt = static_cast(rowOffset + w + tess.width + 1); + outputIt = static_cast(rowOffset + w + 1); + } + } + } +}; + +class TesselationIndexer_Quads : + public ITesselationIndexer +{ +public: + render::SurfaceIndexingType getType() const override + { + return render::SurfaceIndexingType::Quads; + } + + std::size_t getNumIndices(const PatchTesselation& tess) const override + { + return (tess.height - 1) * (tess.width - 1) * 4; // 4 indices per quad + } + + void generateIndices(const PatchTesselation& tess, std::back_insert_iterator> outputIt) const override + { + for (std::size_t h = 0; h < tess.height - 1; ++h) + { + auto rowOffset = h * tess.width; + + for (std::size_t w = 0; w < tess.width - 1; ++w) + { + outputIt = static_cast(rowOffset + w); + outputIt = static_cast(rowOffset + w + tess.width); + outputIt = static_cast(rowOffset + w + tess.width + 1); + outputIt = static_cast(rowOffset + w + 1); + } + } + } +}; + +template +class RenderablePatchTesselation : + public OpenGLRenderable +{ +private: + static_assert(std::is_base_of_v, "Indexer must implement ITesselationIndexer"); + TesselationIndexerT _indexer; + + const PatchTesselation& _tess; + bool _needsUpdate; + ShaderPtr _shader; + std::size_t _size; + + render::ISurfaceRenderer::Slot _surfaceSlot; + +public: + RenderablePatchTesselation(const PatchTesselation& tess) : + _tess(tess), + _needsUpdate(true), + _surfaceSlot(render::ISurfaceRenderer::InvalidSlot), + _size(0) + {} + + void clear() + { + if (!_shader || _surfaceSlot == render::ISurfaceRenderer::InvalidSlot) return; + + _shader->removeSurface(_surfaceSlot); + _shader.reset(); + + _surfaceSlot = render::ISurfaceRenderer::InvalidSlot; + _size = 0; + } + + void queueUpdate() + { + _needsUpdate = true; + } + + void update(const ShaderPtr& shader) + { + bool shaderChanged = _shader != shader; + + if (!_needsUpdate && !shaderChanged) return; + + _needsUpdate = false; + auto sizeChanged = _tess.vertices.size() != _size; + + if (_shader && _surfaceSlot != render::ISurfaceRenderer::InvalidSlot && (shaderChanged || sizeChanged)) + { + clear(); + } + + _shader = shader; + _size = _tess.vertices.size(); + + // Generate the index array + std::vector indices; + indices.reserve(_indexer.getNumIndices(_tess)); + + _indexer.generateIndices(_tess, std::back_inserter(indices)); + + if (_surfaceSlot == render::ISurfaceRenderer::InvalidSlot) + { + _surfaceSlot = shader->addSurface(_indexer.getType(), _tess.vertices, indices); + } + else + { + shader->updateSurface(_surfaceSlot, _tess.vertices, indices); + } + } + + void render(const RenderInfo& info) const override + { + if (_surfaceSlot != render::ISurfaceRenderer::InvalidSlot && _shader) + { + _shader->renderSurface(_surfaceSlot); + } + } }; diff --git a/radiantcore/rendersystem/OpenGLRenderSystem.cpp b/radiantcore/rendersystem/OpenGLRenderSystem.cpp index 8761030d2d..f78a72bee5 100644 --- a/radiantcore/rendersystem/OpenGLRenderSystem.cpp +++ b/radiantcore/rendersystem/OpenGLRenderSystem.cpp @@ -117,10 +117,11 @@ ShaderPtr OpenGLRenderSystem::capture(const std::string& name) * Render all states in the ShaderCache along with their renderables. This * is where the actual OpenGL rendering starts. */ -void OpenGLRenderSystem::render(RenderStateFlags globalstate, - const Matrix4& modelview, - const Matrix4& projection, - const Vector3& viewer) +void OpenGLRenderSystem::render(RenderViewType renderViewType, + RenderStateFlags globalstate, + const Matrix4& modelview, + const Matrix4& projection, + const Vector3& viewer) { glPushAttrib(GL_ALL_ATTRIB_BITS); @@ -206,17 +207,25 @@ void OpenGLRenderSystem::render(RenderStateFlags globalstate, // OpenGLShaderPasses (containing the renderable geometry), and render the // contents of each bucket. Each pass is passed a reference to the "current" // state, which it can change. - for (OpenGLStates::iterator i = _state_sorted.begin(); - i != _state_sorted.end(); - ++i) + for (const auto& pair : _state_sorted) { // Render the OpenGLShaderPass - if (!i->second->empty()) + if (pair.second->empty()) continue; + + if (pair.second->isApplicableTo(renderViewType)) { - i->second->render(current, globalstate, viewer, _time); + pair.second->render(current, globalstate, viewer, _time); } + + pair.second->clearRenderables(); } +#ifdef RENDERABLE_GEOMETRY + for (auto& shader : _shaders) + { + shader.second->clearGeometry(); + } +#endif glPopAttrib(); } diff --git a/radiantcore/rendersystem/OpenGLRenderSystem.h b/radiantcore/rendersystem/OpenGLRenderSystem.h index acde5db919..32bb542e99 100644 --- a/radiantcore/rendersystem/OpenGLRenderSystem.h +++ b/radiantcore/rendersystem/OpenGLRenderSystem.h @@ -62,7 +62,7 @@ class OpenGLRenderSystem /* RenderSystem implementation */ ShaderPtr capture(const std::string& name) override; - void render(RenderStateFlags globalstate, + void render(RenderViewType renderViewType, RenderStateFlags globalstate, const Matrix4& modelview, const Matrix4& projection, const Vector3& viewer) override; diff --git a/radiantcore/rendersystem/backend/OpenGLShader.cpp b/radiantcore/rendersystem/backend/OpenGLShader.cpp index d58dcce24f..aa9cddb0e6 100644 --- a/radiantcore/rendersystem/backend/OpenGLShader.cpp +++ b/radiantcore/rendersystem/backend/OpenGLShader.cpp @@ -61,7 +61,9 @@ OpenGLShader::OpenGLShader(const std::string& name, OpenGLRenderSystem& renderSy _renderSystem(renderSystem), _isVisible(true), _useCount(0) -{} +{ + _windingRenderer.reset(new WindingRenderer()); +} OpenGLShader::~OpenGLShader() { @@ -75,6 +77,7 @@ OpenGLRenderSystem& OpenGLShader::getRenderSystem() void OpenGLShader::destroy() { + _vertexBuffer.reset(); _materialChanged.disconnect(); _material.reset(); _shaderPasses.clear(); @@ -110,6 +113,160 @@ void OpenGLShader::addRenderable(const OpenGLRenderable& renderable, } } +#if 0 +void OpenGLShader::addSurface(const std::vector& vertices, const std::vector& indices) +{ + if (!_vertexBuffer) + { + _vertexBuffer = std::make_unique>(); + } + + // Offset all incoming vertices with a given offset + auto indexOffset = static_cast(_vertexBuffer->getNumVertices()); + + // Pump the data to the VBO + _vertexBuffer->addVertices(vertices.begin(), vertices.end()); + _vertexBuffer->addIndicesToLastBatch(indices.begin(), indices.size(), indexOffset); +} +#endif +void OpenGLShader::drawSurfaces() +{ + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + + // Surfaces are using CW culling + glFrontFace(GL_CW); + + if (hasSurfaces()) + { + //_vertexBuffer->renderAllBatches(GL_TRIANGLES, false); + + SurfaceRenderer::render(); +#if 0 + // Render all triangles + + glBindBuffer(GL_VERTEX_ARRAY, _vbo); + + // Set the vertex pointer first + glVertexPointer(3, GL_DOUBLE, sizeof(ArbitraryMeshVertex), &_vertices.front().vertex); + glNormalPointer(GL_DOUBLE, sizeof(ArbitraryMeshVertex), &_vertices.front().normal); + glTexCoordPointer(2, GL_DOUBLE, sizeof(ArbitraryMeshVertex), &_vertices.front().texcoord); + //glVertexAttribPointer(ATTR_TEXCOORD, 2, GL_DOUBLE, 0, sizeof(ArbitraryMeshVertex), &vertices.front().texcoord); + //glVertexAttribPointer(ATTR_TANGENT, 3, GL_DOUBLE, 0, sizeof(ArbitraryMeshVertex), &vertices.front().tangent); + //glVertexAttribPointer(ATTR_BITANGENT, 3, GL_DOUBLE, 0, sizeof(ArbitraryMeshVertex), &vertices.front().bitangent); + + glDrawElements(GL_TRIANGLES, static_cast(_indices.size()), GL_UNSIGNED_INT, _indices.data()); + + //glDisableClientState(GL_NORMAL_ARRAY); + //glDisableClientState(GL_TEXTURE_COORD_ARRAY); + //glDisableClientState(GL_VERTEX_ARRAY); + + glBindBuffer(GL_VERTEX_ARRAY, 0); +#endif + } + + // Render all windings + glFrontFace(GL_CCW); + _windingRenderer->renderAllWindings(); + +#ifdef RENDERABLE_GEOMETRY + glFrontFace(GL_CW); + for (auto& geometry: _geometry) + { + GLenum mode = GL_TRIANGLES; + + switch (geometry.get().getType()) + { + case RenderableGeometry::Type::Quads: + mode = GL_QUADS; + break; + + case RenderableGeometry::Type::Polygons: + mode = GL_POLYGON; + break; + } + + glVertexPointer(3, GL_DOUBLE, static_cast(geometry.get().getVertexStride()), &geometry.get().getFirstVertex()); + glDrawElements(mode, static_cast(geometry.get().getNumIndices()), GL_UNSIGNED_INT, &geometry.get().getFirstIndex()); + } +#endif + + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); +} + +#ifdef RENDERABLE_GEOMETRY +void OpenGLShader::clearGeometry() +{ + _geometry.clear(); +} +#endif + +bool OpenGLShader::hasSurfaces() const +{ + return !SurfaceRenderer::empty() || _vertexBuffer && _vertexBuffer->getNumVertices() > 0; +} + +ISurfaceRenderer::Slot OpenGLShader::addSurface(SurfaceIndexingType indexType, + const std::vector& vertices, const std::vector& indices) +{ + return SurfaceRenderer::addSurface(indexType, vertices, indices); +} + +void OpenGLShader::removeSurface(ISurfaceRenderer::Slot slot) +{ + SurfaceRenderer::removeSurface(slot); +} + +void OpenGLShader::updateSurface(ISurfaceRenderer::Slot slot, const std::vector& vertices, + const std::vector& indices) +{ + SurfaceRenderer::updateSurface(slot, vertices, indices); +} + +void OpenGLShader::renderSurface(ISurfaceRenderer::Slot slot) +{ + SurfaceRenderer::renderSurface(slot); +} + +IWindingRenderer::Slot OpenGLShader::addWinding(const std::vector& vertices) +{ + return _windingRenderer->addWinding(vertices); +} + +void OpenGLShader::removeWinding(IWindingRenderer::Slot slot) +{ + _windingRenderer->removeWinding(slot); +} + +void OpenGLShader::updateWinding(IWindingRenderer::Slot slot, const std::vector& vertices) +{ + _windingRenderer->updateWinding(slot, vertices); +} + +bool OpenGLShader::hasWindings() const +{ + return !_windingRenderer->empty(); +} + +void OpenGLShader::renderWinding(IWindingRenderer::RenderMode mode, IWindingRenderer::Slot slot) +{ + _windingRenderer->renderWinding(mode, slot); +} + +#ifdef RENDERABLE_GEOMETRY +void OpenGLShader::addGeometry(RenderableGeometry& geometry) +{ + _geometry.emplace_back(std::ref(geometry)); +} + +bool OpenGLShader::hasGeometry() const +{ + return !_geometry.empty(); +} +#endif + void OpenGLShader::setVisible(bool visible) { // Control visibility by inserting or removing our shader passes from the GL @@ -128,7 +285,7 @@ void OpenGLShader::setVisible(bool visible) bool OpenGLShader::isVisible() const { - return _isVisible; + return _isVisible && (!_material || _material->isVisible()); } void OpenGLShader::incrementUsed() @@ -672,6 +829,8 @@ void OpenGLShader::construct() state.setRenderFlag(RENDER_CULLFACE); state.setRenderFlag(RENDER_DEPTHWRITE); state.setSortPosition(OpenGLState::SORT_FULLBRIGHT); + + enableViewType(RenderViewType::Camera); break; } @@ -692,11 +851,16 @@ void OpenGLShader::construct() state.setRenderFlag(RENDER_DEPTHWRITE); state.setRenderFlag(RENDER_BLEND); state.setSortPosition(OpenGLState::SORT_TRANSLUCENT); + + enableViewType(RenderViewType::Camera); break; } case '<': // wireframe shader { + // Wireframe renderer is using GL_LINES to display each winding + _windingRenderer.reset(new WindingRenderer()); + OpenGLState& state = appendDefaultPass(); state.setName(_name); @@ -710,6 +874,8 @@ void OpenGLShader::construct() state.setDepthFunc(GL_LESS); state.m_linewidth = 1; state.m_pointsize = 1; + + enableViewType(RenderViewType::OrthoView); break; } @@ -725,6 +891,9 @@ void OpenGLShader::construct() state.setSortPosition(OpenGLState::SORT_POINT_FIRST); state.m_pointsize = 4; + + enableViewType(RenderViewType::Camera); + enableViewType(RenderViewType::OrthoView); } else if (_name == "$SELPOINT") { @@ -733,6 +902,9 @@ void OpenGLShader::construct() state.setSortPosition(OpenGLState::SORT_POINT_LAST); state.m_pointsize = 4; + + enableViewType(RenderViewType::Camera); + enableViewType(RenderViewType::OrthoView); } else if (_name == "$BIGPOINT") { @@ -741,6 +913,9 @@ void OpenGLShader::construct() state.setSortPosition(OpenGLState::SORT_POINT_FIRST); state.m_pointsize = 6; + + enableViewType(RenderViewType::Camera); + enableViewType(RenderViewType::OrthoView); } else if (_name == "$PIVOT") { @@ -755,18 +930,26 @@ void OpenGLShader::construct() hiddenLine.setSortPosition(OpenGLState::SORT_GUI0); hiddenLine.m_linewidth = 2; hiddenLine.setDepthFunc(GL_GREATER); + + enableViewType(RenderViewType::Camera); + enableViewType(RenderViewType::OrthoView); } else if (_name == "$LATTICE") { state.setColour(1, 0.5, 0, 1); state.setRenderFlag(RENDER_DEPTHWRITE); state.setSortPosition(OpenGLState::SORT_POINT_FIRST); + + enableViewType(RenderViewType::Camera); + enableViewType(RenderViewType::OrthoView); } +#if 0 else if (_name == "$WIREFRAME") { state.setRenderFlags(RENDER_DEPTHTEST | RENDER_DEPTHWRITE); state.setSortPosition(OpenGLState::SORT_FULLBRIGHT); } +#endif else if (_name == "$CAM_HIGHLIGHT") { // This is the shader drawing a coloured overlay @@ -781,6 +964,8 @@ void OpenGLShader::construct() state.setSortPosition(OpenGLState::SORT_HIGHLIGHT); state.polygonOffset = 0.5f; state.setDepthFunc(GL_LEQUAL); + + enableViewType(RenderViewType::Camera); } else if (_name == "$CAM_OVERLAY") { @@ -802,6 +987,8 @@ void OpenGLShader::construct() hiddenLine.setSortPosition(OpenGLState::SORT_OVERLAY_FIRST); hiddenLine.setDepthFunc(GL_GREATER); hiddenLine.m_linestipple_factor = 2; + + enableViewType(RenderViewType::Camera); } else if (string::starts_with(_name, "$MERGE_ACTION_")) { @@ -849,6 +1036,8 @@ void OpenGLShader::construct() linesOverlay.setColour(colour); linesOverlay.setRenderFlags(RENDER_OFFSETLINE | RENDER_DEPTHTEST | RENDER_BLEND); linesOverlay.setSortPosition(lineSortPosition); + + enableViewType(RenderViewType::Camera); } else if (_name == "$XY_OVERLAY") { @@ -861,6 +1050,8 @@ void OpenGLShader::construct() state.setSortPosition(OpenGLState::SORT_HIGHLIGHT); state.m_linewidth = 2; state.m_linestipple_factor = 3; + + enableViewType(RenderViewType::OrthoView); } else if (_name == "$XY_OVERLAY_GROUP") { @@ -873,6 +1064,8 @@ void OpenGLShader::construct() state.setSortPosition(OpenGLState::SORT_HIGHLIGHT); state.m_linewidth = 2; state.m_linestipple_factor = 3; + + enableViewType(RenderViewType::OrthoView); } else if (string::starts_with(_name, "$XY_MERGE_ACTION_")) { @@ -904,6 +1097,8 @@ void OpenGLShader::construct() state.setColour(colour); state.setSortPosition(OpenGLState::SORT_OVERLAY_FIRST); state.m_linewidth = 2; + + enableViewType(RenderViewType::OrthoView); } else if (_name == "$XY_INACTIVE_NODE") { @@ -922,18 +1117,25 @@ void OpenGLShader::construct() state.m_linewidth = 1; state.m_pointsize = 1; + + enableViewType(RenderViewType::OrthoView); } +#if 0 else if (_name == "$DEBUG_CLIPPED") { state.setRenderFlag(RENDER_DEPTHWRITE); state.setSortPosition(OpenGLState::SORT_LAST); } +#endif else if (_name == "$POINTFILE") { state.setColour(1, 0, 0, 1); state.setRenderFlags(RENDER_DEPTHTEST | RENDER_DEPTHWRITE); state.setSortPosition(OpenGLState::SORT_FULLBRIGHT); state.m_linewidth = 4; + + enableViewType(RenderViewType::Camera); + enableViewType(RenderViewType::OrthoView); } else if (_name == "$WIRE_OVERLAY") { @@ -953,6 +1155,9 @@ void OpenGLShader::construct() | RENDER_VERTEX_COLOUR); hiddenLine.setSortPosition(OpenGLState::SORT_GUI0); hiddenLine.setDepthFunc(GL_GREATER); + + enableViewType(RenderViewType::Camera); + enableViewType(RenderViewType::OrthoView); } else if (_name == "$FLATSHADE_OVERLAY") { @@ -980,6 +1185,9 @@ void OpenGLShader::construct() | RENDER_POLYGONSTIPPLE); hiddenLine.setSortPosition(OpenGLState::SORT_GUI0); hiddenLine.setDepthFunc(GL_GREATER); + + enableViewType(RenderViewType::Camera); + enableViewType(RenderViewType::OrthoView); } else if (_name == "$CLIPPER_OVERLAY") { @@ -989,6 +1197,9 @@ void OpenGLShader::construct() | RENDER_FILL | RENDER_POLYGONSTIPPLE); state.setSortPosition(OpenGLState::SORT_OVERLAY_FIRST); + + enableViewType(RenderViewType::Camera); + enableViewType(RenderViewType::OrthoView); } else if (_name == "$AAS_AREA") { @@ -1007,6 +1218,8 @@ void OpenGLShader::construct() | RENDER_LINESTIPPLE); hiddenLine.setSortPosition(OpenGLState::SORT_OVERLAY_LAST); hiddenLine.setDepthFunc(GL_GREATER); + + enableViewType(RenderViewType::Camera); } else { @@ -1019,6 +1232,8 @@ void OpenGLShader::construct() { // This is not a hard-coded shader, construct from the shader system constructNormalShader(); + + enableViewType(RenderViewType::Camera); } } // switch (name[0]) } @@ -1035,5 +1250,15 @@ void OpenGLShader::onMaterialChanged() realise(); } +bool OpenGLShader::isApplicableTo(RenderViewType renderViewType) const +{ + return (_enabledViewTypes & static_cast(renderViewType)) != 0; +} + +void OpenGLShader::enableViewType(RenderViewType renderViewType) +{ + _enabledViewTypes |= static_cast(renderViewType); +} + } diff --git a/radiantcore/rendersystem/backend/OpenGLShader.h b/radiantcore/rendersystem/backend/OpenGLShader.h index d99eae75bc..c29ec1c916 100644 --- a/radiantcore/rendersystem/backend/OpenGLShader.h +++ b/radiantcore/rendersystem/backend/OpenGLShader.h @@ -5,6 +5,9 @@ #include "irender.h" #include "ishaders.h" #include "string/string.h" +#include "render/IndexedVertexBuffer.h" +#include "render/WindingRenderer.h" +#include "SurfaceRenderer.h" #include #include @@ -18,7 +21,8 @@ class OpenGLRenderSystem; * Implementation of the Shader class. */ class OpenGLShader final : - public Shader + public Shader, + protected SurfaceRenderer { private: // Name used to construct the shader @@ -44,6 +48,17 @@ class OpenGLShader final : typedef std::set Observers; Observers _observers; + std::unique_ptr> _vertexBuffer; + +#ifdef RENDERABLE_GEOMETRY + std::vector> _geometry; +#endif + + std::unique_ptr _windingRenderer; + + // Each shader can be used by either camera or orthoview, or both + std::size_t _enabledViewTypes; + private: // Start point for constructing shader passes from the shader name @@ -95,6 +110,29 @@ class OpenGLShader final : const Matrix4& modelview, const LightSources* lights, const IRenderEntity* entity) override; + + //void addSurface(const std::vector& vertices, const std::vector& indices) override; + bool hasSurfaces() const; + void drawSurfaces(); + + ISurfaceRenderer::Slot addSurface(SurfaceIndexingType indexType, + const std::vector& vertices, const std::vector& indices) override; + void removeSurface(ISurfaceRenderer::Slot slot) override; + void updateSurface(ISurfaceRenderer::Slot slot, const std::vector& vertices, + const std::vector& indices) override; + void renderSurface(ISurfaceRenderer::Slot slot) override; +#ifdef RENDERABLE_GEOMETRY + void addGeometry(RenderableGeometry& geometry) override; + bool hasGeometry() const; + void clearGeometry(); +#endif + + IWindingRenderer::Slot addWinding(const std::vector& vertices) override; + void removeWinding(IWindingRenderer::Slot slot) override; + void updateWinding(IWindingRenderer::Slot slot, const std::vector& vertices) override; + bool hasWindings() const; + void renderWinding(IWindingRenderer::RenderMode mode, IWindingRenderer::Slot slot) override; + void setVisible(bool visible) override; bool isVisible() const override; void incrementUsed() override; @@ -116,6 +154,9 @@ class OpenGLShader final : const MaterialPtr& getMaterial() const override; unsigned int getFlags() const override; + + bool isApplicableTo(RenderViewType renderViewType) const; + void enableViewType(RenderViewType renderViewType); }; typedef std::shared_ptr OpenGLShaderPtr; diff --git a/radiantcore/rendersystem/backend/OpenGLShaderPass.cpp b/radiantcore/rendersystem/backend/OpenGLShaderPass.cpp index 4b06abd570..37f8c670ae 100644 --- a/radiantcore/rendersystem/backend/OpenGLShaderPass.cpp +++ b/radiantcore/rendersystem/backend/OpenGLShaderPass.cpp @@ -567,6 +567,8 @@ void OpenGLShaderPass::render(OpenGLState& current, const Vector3& viewer, std::size_t time) { + if (!_owner.isVisible()) return; + // Reset the texture matrix glMatrixMode(GL_TEXTURE); glLoadMatrixd(Matrix4::getIdentity()); @@ -576,6 +578,8 @@ void OpenGLShaderPass::render(OpenGLState& current, // Apply our state to the current state object applyState(current, flagsMask, viewer, time, NULL); + _owner.drawSurfaces(); + if (!_renderablesWithoutEntity.empty()) { renderAllContained(_renderablesWithoutEntity, current, viewer, time); @@ -595,11 +599,29 @@ void OpenGLShaderPass::render(OpenGLState& current, renderAllContained(i->second, current, viewer, time); } +} +void OpenGLShaderPass::clearRenderables() +{ _renderablesWithoutEntity.clear(); _renderables.clear(); } +bool OpenGLShaderPass::empty() +{ + return _renderables.empty() && _renderablesWithoutEntity.empty() && + !_owner.hasSurfaces() && !_owner.hasWindings() +#ifdef RENDERABLE_GEOMETRY + && !_owner.hasGeometry() +#endif + ; +} + +bool OpenGLShaderPass::isApplicableTo(RenderViewType renderViewType) const +{ + return _owner.isApplicableTo(renderViewType); +} + bool OpenGLShaderPass::stateIsActive() { return ((_glState.stage0 == NULL || _glState.stage0->isVisible()) && diff --git a/radiantcore/rendersystem/backend/OpenGLShaderPass.h b/radiantcore/rendersystem/backend/OpenGLShaderPass.h index d3931ec894..513104f91d 100644 --- a/radiantcore/rendersystem/backend/OpenGLShaderPass.h +++ b/radiantcore/rendersystem/backend/OpenGLShaderPass.h @@ -194,10 +194,13 @@ class OpenGLShaderPass /** * Returns true if this shaderpass doesn't have anything to render. */ - bool empty() const - { - return _renderables.empty() && _renderablesWithoutEntity.empty(); - } + bool empty(); + + // Clear out all renderable references accumulated during this frame + void clearRenderables(); + + // Whether this shader pass is suitable for the give view type + bool isApplicableTo(RenderViewType renderViewType) const; friend std::ostream& operator<<(std::ostream& st, const OpenGLShaderPass& self); }; diff --git a/radiantcore/rendersystem/backend/SurfaceRenderer.h b/radiantcore/rendersystem/backend/SurfaceRenderer.h new file mode 100644 index 0000000000..63a0bb4428 --- /dev/null +++ b/radiantcore/rendersystem/backend/SurfaceRenderer.h @@ -0,0 +1,214 @@ +#pragma once + +#include "isurfacerenderer.h" + +namespace render +{ + +class SurfaceRenderer : + public ISurfaceRenderer +{ +private: + struct VertexBuffer + { + GLenum mode; + std::vector vertices; + std::vector indices; + }; + + VertexBuffer _triangleBuffer; + VertexBuffer _quadBuffer; + + static constexpr std::size_t InvalidVertexIndex = std::numeric_limits::max(); + + struct SlotInfo + { + std::uint8_t bucketIndex; + std::size_t firstVertex; + std::size_t numVertices; + std::size_t firstIndex; + std::size_t numIndices; + }; + std::vector _slots; + + static constexpr std::size_t InvalidSlotMapping = std::numeric_limits::max(); + std::size_t _freeSlotMappingHint; + +public: + SurfaceRenderer() : + _freeSlotMappingHint(InvalidSlotMapping) + { + _triangleBuffer.mode = GL_TRIANGLES; + _quadBuffer.mode = GL_QUADS; + } + + bool empty() const + { + return _triangleBuffer.vertices.empty() && _quadBuffer.vertices.empty(); + } + + Slot addSurface(SurfaceIndexingType indexType, const std::vector& vertices, + const std::vector& indices) override + { + auto bucketIndex = GetBucketIndexForIndexType(indexType); + auto& bucket = getBucketByIndex(bucketIndex); + + // Allocate a slot + auto oldVertexSize = bucket.vertices.size(); + auto oldIndexSize = bucket.indices.size(); + + auto newSlotIndex = getNextFreeSlotMapping(); + + auto& slot = _slots.at(newSlotIndex); + + slot.bucketIndex = bucketIndex; + slot.firstVertex = oldVertexSize; + slot.numVertices = vertices.size(); + slot.firstIndex = oldIndexSize; + slot.numIndices = indices.size(); + + std::copy(vertices.begin(), vertices.end(), std::back_inserter(bucket.vertices)); + + for (auto index : indices) + { + bucket.indices.push_back(index + static_cast(oldVertexSize)); + } + + return newSlotIndex; + } + + void removeSurface(Slot slot) override + { + auto& slotInfo = _slots.at(slot); + auto& bucket = getBucketByIndex(slotInfo.bucketIndex); + + // Cut out the vertices + auto firstVertexToRemove = bucket.vertices.begin() + slotInfo.firstVertex; + bucket.vertices.erase(firstVertexToRemove, firstVertexToRemove + slotInfo.numVertices); + + // Shift all indices to the left, offsetting their values by the number of removed vertices + auto offsetToApply = -static_cast(slotInfo.numVertices); + + auto targetIndex = bucket.indices.begin() + slotInfo.firstIndex; + auto indexToMove = targetIndex + slotInfo.numIndices; + + auto indexEnd = bucket.indices.end(); + while (indexToMove != indexEnd) + { + *targetIndex++ = *indexToMove++ + offsetToApply; + } + + // Cut off the tail of the indices + bucket.indices.resize(bucket.indices.size() - slotInfo.numIndices); + + // Adjust all offsets in other slots + for (auto& slot : _slots) + { + if (slot.firstVertex > slotInfo.firstVertex) + { + slot.firstVertex -= slotInfo.numVertices; + slot.firstIndex -= slotInfo.numIndices; + } + } + + // Invalidate the slot + slotInfo.numVertices = 0; + slotInfo.firstVertex = InvalidVertexIndex; + slotInfo.firstIndex = 0; + slotInfo.numIndices = 0; + + if (slot < _freeSlotMappingHint) + { + _freeSlotMappingHint = slot; + } + } + + void updateSurface(Slot slot, const std::vector& vertices, + const std::vector& indices) override + { + auto& slotInfo = _slots.at(slot); + auto& bucket = getBucketByIndex(slotInfo.bucketIndex); + + // Copy the data to the correct slot in the array + std::copy(vertices.begin(), vertices.end(), bucket.vertices.begin() + slotInfo.firstVertex); + + // Before assignment, the indices need to be shifted to match the array offset of the vertices + auto targetIndex = bucket.indices.begin() + slotInfo.firstIndex; + auto indexShift = static_cast(slotInfo.firstVertex); + + for (auto index : indices) + { + *targetIndex++ = index + indexShift; + } + } + + void render() + { + renderBuffer(_triangleBuffer); + renderBuffer(_quadBuffer); + } + + void renderSurface(Slot slot) override + { + auto& slotInfo = _slots.at(slot); + auto& buffer = getBucketByIndex(slotInfo.bucketIndex); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glFrontFace(GL_CW); + + glVertexPointer(3, GL_DOUBLE, sizeof(ArbitraryMeshVertex), &buffer.vertices.front().vertex); + glTexCoordPointer(2, GL_DOUBLE, sizeof(ArbitraryMeshVertex), &buffer.vertices.front().texcoord); + glNormalPointer(GL_DOUBLE, sizeof(ArbitraryMeshVertex), &buffer.vertices.front().normal); + + glDrawElements(buffer.mode, static_cast(slotInfo.numIndices), GL_UNSIGNED_INT, &buffer.indices[slotInfo.firstIndex]); + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } + +private: + + void renderBuffer(const VertexBuffer& buffer) + { + if (!buffer.indices.empty()) + { + glVertexPointer(3, GL_DOUBLE, sizeof(ArbitraryMeshVertex), &buffer.vertices.front().vertex); + glTexCoordPointer(2, GL_DOUBLE, sizeof(ArbitraryMeshVertex), &buffer.vertices.front().texcoord); + glNormalPointer(GL_DOUBLE, sizeof(ArbitraryMeshVertex), &buffer.vertices.front().normal); + + glDrawElements(buffer.mode, static_cast(buffer.indices.size()), GL_UNSIGNED_INT, &buffer.indices.front()); + } + } + + constexpr static std::uint8_t GetBucketIndexForIndexType(SurfaceIndexingType indexType) + { + return indexType == SurfaceIndexingType::Triangles ? 0 : 1; + } + + VertexBuffer& getBucketByIndex(std::uint8_t bucketIndex) + { + return bucketIndex == 0 ? _triangleBuffer : _quadBuffer; + } + + ISurfaceRenderer::Slot getNextFreeSlotMapping() + { + auto numSlots = _slots.size(); + + for (auto i = _freeSlotMappingHint; i < numSlots; ++i) + { + if (_slots[i].firstVertex == InvalidVertexIndex) + { + _freeSlotMappingHint = i + 1; // start searching here next time + return i; + } + } + + _slots.emplace_back(); + return numSlots; // == the size before we emplaced the new slot + } +}; + +} \ No newline at end of file diff --git a/radiantcore/selection/RadiantSelectionSystem.cpp b/radiantcore/selection/RadiantSelectionSystem.cpp index f3f2026701..77836505ae 100644 --- a/radiantcore/selection/RadiantSelectionSystem.cpp +++ b/radiantcore/selection/RadiantSelectionSystem.cpp @@ -850,7 +850,7 @@ void RadiantSelectionSystem::onManipulationCancelled() pivotChanged(); } -void RadiantSelectionSystem::renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const +void RadiantSelectionSystem::renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const { renderSolid(collector, volume); } @@ -927,12 +927,12 @@ Vector3 RadiantSelectionSystem::getCurrentSelectionCenter() /* greebo: Renders the currently active manipulator by setting the render state and * calling the manipulator's render method */ -void RadiantSelectionSystem::renderSolid(RenderableCollector& collector, const VolumeTest& volume) const +void RadiantSelectionSystem::renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const { if (!nothingSelected()) { - collector.setHighlightFlag(RenderableCollector::Highlight::Faces, false); - collector.setHighlightFlag(RenderableCollector::Highlight::Primitives, false); + collector.setHighlightFlag(IRenderableCollector::Highlight::Faces, false); + collector.setHighlightFlag(IRenderableCollector::Highlight::Primitives, false); _activeManipulator->render(collector, volume); } diff --git a/radiantcore/selection/RadiantSelectionSystem.h b/radiantcore/selection/RadiantSelectionSystem.h index c3ee0f6e34..0f535d3900 100644 --- a/radiantcore/selection/RadiantSelectionSystem.h +++ b/radiantcore/selection/RadiantSelectionSystem.h @@ -152,8 +152,10 @@ class RadiantSelectionSystem : const WorkZone& getWorkZone() override; Vector3 getCurrentSelectionCenter() override; - void renderSolid(RenderableCollector& collector, const VolumeTest& volume) const override; - void renderWireframe(RenderableCollector& collector, const VolumeTest& volume) const override; + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override; + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override + {} void setRenderSystem(const RenderSystemPtr& renderSystem) override {} diff --git a/radiantcore/selection/manipulators/ManipulatorBase.h b/radiantcore/selection/manipulators/ManipulatorBase.h index 0068e6240a..8b8907d41d 100644 --- a/radiantcore/selection/manipulators/ManipulatorBase.h +++ b/radiantcore/selection/manipulators/ManipulatorBase.h @@ -25,7 +25,7 @@ class ManipulatorBase : } // No visual representation by default - virtual void render(RenderableCollector& collector, const VolumeTest& volume) override + virtual void render(IRenderableCollector& collector, const VolumeTest& volume) override {} public: diff --git a/radiantcore/selection/manipulators/ModelScaleManipulator.cpp b/radiantcore/selection/manipulators/ModelScaleManipulator.cpp index 58b98cdbb1..cbf97c2840 100644 --- a/radiantcore/selection/manipulators/ModelScaleManipulator.cpp +++ b/radiantcore/selection/manipulators/ModelScaleManipulator.cpp @@ -65,7 +65,7 @@ bool ModelScaleManipulator::isSelected() const return _curManipulatable != nullptr; } -void ModelScaleManipulator::render(RenderableCollector& collector, const VolumeTest& volume) +void ModelScaleManipulator::render(IRenderableCollector& collector, const VolumeTest& volume) { _renderableAabbs.clear(); _renderableCornerPoints.clear(); diff --git a/radiantcore/selection/manipulators/ModelScaleManipulator.h b/radiantcore/selection/manipulators/ModelScaleManipulator.h index baadd83798..2a54ed344f 100644 --- a/radiantcore/selection/manipulators/ModelScaleManipulator.h +++ b/radiantcore/selection/manipulators/ModelScaleManipulator.h @@ -41,7 +41,7 @@ class ModelScaleManipulator : void testSelect(SelectionTest& test, const Matrix4& pivot2world) override; void setSelected(bool select) override; bool isSelected() const override; - void render(RenderableCollector& collector, const VolumeTest& volume) override; + void render(IRenderableCollector& collector, const VolumeTest& volume) override; private: void foreachSelectedTransformable( diff --git a/radiantcore/selection/manipulators/RotateManipulator.cpp b/radiantcore/selection/manipulators/RotateManipulator.cpp index b60fe6916a..8d4bdb75dc 100644 --- a/radiantcore/selection/manipulators/RotateManipulator.cpp +++ b/radiantcore/selection/manipulators/RotateManipulator.cpp @@ -136,7 +136,7 @@ void RotateManipulator::updateCircleTransforms() } } -void RotateManipulator::render(RenderableCollector& collector, const VolumeTest& volume) +void RotateManipulator::render(IRenderableCollector& collector, const VolumeTest& volume) { _pivot2World.update(_pivot.getMatrix4(), volume.GetModelview(), volume.GetProjection(), volume.GetViewport()); updateCircleTransforms(); diff --git a/radiantcore/selection/manipulators/RotateManipulator.h b/radiantcore/selection/manipulators/RotateManipulator.h index 43ef7d0369..045da4a7e8 100644 --- a/radiantcore/selection/manipulators/RotateManipulator.h +++ b/radiantcore/selection/manipulators/RotateManipulator.h @@ -68,7 +68,7 @@ class RotateManipulator : void UpdateColours(); void updateCircleTransforms(); - void render(RenderableCollector& collector, const VolumeTest& volume) override; + void render(IRenderableCollector& collector, const VolumeTest& volume) override; void render(const RenderInfo& info) const override; void testSelect(SelectionTest& view, const Matrix4& pivot2world) override; diff --git a/radiantcore/selection/manipulators/ScaleManipulator.cpp b/radiantcore/selection/manipulators/ScaleManipulator.cpp index 8456614297..1cd6275a13 100644 --- a/radiantcore/selection/manipulators/ScaleManipulator.cpp +++ b/radiantcore/selection/manipulators/ScaleManipulator.cpp @@ -28,7 +28,7 @@ void ScaleManipulator::UpdateColours() { _quadScreen.setColour(colourSelected(ManipulatorBase::COLOUR_SCREEN(), _selectableScreen.isSelected())); } -void ScaleManipulator::render(RenderableCollector& collector, const VolumeTest& volume) +void ScaleManipulator::render(IRenderableCollector& collector, const VolumeTest& volume) { _pivot2World.update(_pivot.getMatrix4(), volume.GetModelview(), volume.GetProjection(), volume.GetViewport()); diff --git a/radiantcore/selection/manipulators/ScaleManipulator.h b/radiantcore/selection/manipulators/ScaleManipulator.h index 201d699586..acd8911171 100644 --- a/radiantcore/selection/manipulators/ScaleManipulator.h +++ b/radiantcore/selection/manipulators/ScaleManipulator.h @@ -44,7 +44,7 @@ class ScaleManipulator : void UpdateColours(); - void render(RenderableCollector& collector, const VolumeTest& volume) override; + void render(IRenderableCollector& collector, const VolumeTest& volume) override; void testSelect(SelectionTest& test, const Matrix4& pivot2world) override; Component* getActiveComponent() override; diff --git a/radiantcore/selection/manipulators/TranslateManipulator.cpp b/radiantcore/selection/manipulators/TranslateManipulator.cpp index 22bee43263..c6adecaa0e 100644 --- a/radiantcore/selection/manipulators/TranslateManipulator.cpp +++ b/radiantcore/selection/manipulators/TranslateManipulator.cpp @@ -45,7 +45,7 @@ bool TranslateManipulator::manipulator_show_axis(const Pivot2World& pivot, const return fabs(pivot._axisScreen.dot(axis)) < 0.95; } -void TranslateManipulator::render(RenderableCollector& collector, const VolumeTest& volume) +void TranslateManipulator::render(IRenderableCollector& collector, const VolumeTest& volume) { _pivot2World.update(_pivot.getMatrix4(), volume.GetModelview(), volume.GetProjection(), volume.GetViewport()); diff --git a/radiantcore/selection/manipulators/TranslateManipulator.h b/radiantcore/selection/manipulators/TranslateManipulator.h index 5978150988..08a4bac2ce 100644 --- a/radiantcore/selection/manipulators/TranslateManipulator.h +++ b/radiantcore/selection/manipulators/TranslateManipulator.h @@ -49,7 +49,7 @@ class TranslateManipulator : void UpdateColours(); bool manipulator_show_axis(const Pivot2World& pivot, const Vector3& axis); - void render(RenderableCollector& collector, const VolumeTest& volume) override; + void render(IRenderableCollector& collector, const VolumeTest& volume) override; void testSelect(SelectionTest& test, const Matrix4& pivot2world) override; Component* getActiveComponent() override; diff --git a/radiantcore/selection/textool/TextureToolSelectionSystem.cpp b/radiantcore/selection/textool/TextureToolSelectionSystem.cpp index cdcfb5f9c9..ab659d1c54 100644 --- a/radiantcore/selection/textool/TextureToolSelectionSystem.cpp +++ b/radiantcore/selection/textool/TextureToolSelectionSystem.cpp @@ -780,6 +780,8 @@ void TextureToolSelectionSystem::shiftSelectionCmd(const cmd::ArgumentList& args node->commitTransformation(); return true; }); + + radiant::TextureChangedMessage::Send(); } void TextureToolSelectionSystem::scaleSelectionCmd(const cmd::ArgumentList& args) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e74b06af0f..f7725b32db 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(drtest PointTrace.cpp Prefabs.cpp Renderer.cpp + SceneNode.cpp SelectionAlgorithm.cpp Selection.cpp TextureManipulation.cpp diff --git a/test/Entity.cpp b/test/Entity.cpp index 3d217d2d87..ba418ce3c7 100644 --- a/test/Entity.cpp +++ b/test/Entity.cpp @@ -445,8 +445,15 @@ TEST_F(EntityTest, DestroySelectedEntity) namespace { // A simple RenderableCollector which just logs/stores whatever is submitted - struct TestRenderableCollector: public RenderableCollector + struct TestRenderableCollector : + public IRenderableCollector { + TestRenderableCollector(bool solid) : + renderSolid(solid) + {} + + bool renderSolid; + // Count of submitted renderables and lights int renderables = 0; int lights = 0; @@ -466,6 +473,10 @@ namespace renderablePtrs.push_back(std::make_pair(&shader, &renderable)); } + void addHighlightRenderable(const OpenGLRenderable& renderable, + const Matrix4& localToWorld) override + {} + void addLight(const RendererLight& light) override { ++lights; @@ -473,8 +484,24 @@ namespace } bool supportsFullMaterials() const override { return true; } + bool hasHighlightFlags() const override + { + return true; + } void setHighlightFlag(Highlight::Flags flags, bool enabled) override {} + + void processRenderable(const Renderable& renderable, const VolumeTest& volume) override + { + if (renderSolid) + { + renderable.renderSolid(*this, volume); + } + else + { + renderable.renderWireframe(*this, volume); + } + } }; // Collection of objects needed for rendering. Since not all tests require @@ -488,14 +515,12 @@ namespace render::NopVolumeTest volumeTest; TestRenderableCollector collector; - // Whether to render solid or wireframe - const bool renderSolid; - // Keep track of nodes visited int nodesVisited = 0; // Construct - RenderFixture(bool solid = false): renderSolid(solid) + RenderFixture(bool solid = false) : + collector(solid) {} // Convenience method to set render backend and traverse a node and its @@ -513,10 +538,7 @@ namespace ++nodesVisited; // Render the node in appropriate mode - if (renderSolid) - node->renderSolid(collector, volumeTest); - else - node->renderWireframe(collector, volumeTest); + collector.processRenderable(*node, volumeTest); // Continue traversing return true; diff --git a/test/SceneNode.cpp b/test/SceneNode.cpp new file mode 100644 index 0000000000..f8fd47a321 --- /dev/null +++ b/test/SceneNode.cpp @@ -0,0 +1,263 @@ +#include "RadiantTest.h" + +#include "scene/BasicRootNode.h" +#include "scene/Node.h" +#include "scenelib.h" + +namespace test +{ + +using SceneNodeTest = RadiantTest; + +// Custom Node class to test the protected onVisibilityChanged method behaviour +class VisibilityTestNode : + public scene::Node +{ +public: + VisibilityTestNode() : + visibilityMethodCalled(false) + {} + + Type getNodeType() const override + { + return Type::Unknown; + } + + const AABB& localAABB() const + { + static AABB dummy; + return dummy; + } + + void renderSolid(IRenderableCollector& collector, const VolumeTest& volume) const override + {} + + void renderWireframe(IRenderableCollector& collector, const VolumeTest& volume) const override + {} + + void renderHighlights(IRenderableCollector& collector, const VolumeTest& volume) override + {} + + std::size_t getHighlightFlags() override + { + return 0; + } + + // Wrapper to invoke the protected method + void setProtectedForcedVisibility(bool isForced) + { + setForcedVisibility(isForced, false); + } + + // Public test fields + bool visibilityMethodCalled = false; + bool passedVisibilityValue = false; + +protected: + void onVisibilityChanged(bool newState) override + { + visibilityMethodCalled = true; + passedVisibilityValue = newState; + } +}; + +auto allPossibleHideFlags = { scene::Node::eHidden, scene::Node::eFiltered, scene::Node::eExcluded, scene::Node::eLayered }; + +TEST_F(SceneNodeTest, InitialVisibility) +{ + auto node = std::make_shared(); + scene::addNodeToContainer(node, GlobalMapModule().getRoot()); + + EXPECT_TRUE(node->visible()) << "Should be visible after insertion as child node"; +} + +TEST_F(SceneNodeTest, InsertAndRemoveFromScene) +{ + auto node = std::make_shared(); + scene::addNodeToContainer(node, GlobalMapModule().getRoot()); + + EXPECT_TRUE(node->visible()) << "Should be visible after insertion as child node"; + + node->visibilityMethodCalled = false; + scene::removeNodeFromParent(node); + EXPECT_FALSE(node->visible()) << "Should be invisible after removing as child node"; + EXPECT_TRUE(node->visibilityMethodCalled) << "Method should be called after removing from the scene"; + EXPECT_FALSE(node->passedVisibilityValue) << "Wrong argument passed to onVisibilityChanged"; + + node->visibilityMethodCalled = false; + scene::addNodeToContainer(node, GlobalMapModule().getRoot()); + EXPECT_TRUE(node->visible()) << "Should be visible after re-adding as child node"; + EXPECT_TRUE(node->visibilityMethodCalled) << "Method should be called after inserting into scene"; + EXPECT_TRUE(node->passedVisibilityValue) << "Wrong argument passed to onVisibilityChanged"; +} + +TEST_F(SceneNodeTest, SetVisibleFlag) +{ + auto node = std::make_shared(); + scene::addNodeToContainer(node, GlobalMapModule().getRoot()); + + node->visibilityMethodCalled = false; + + node->enable(scene::Node::eVisible); // does nothing + + EXPECT_FALSE(node->visibilityMethodCalled) << "Should still visible after setting eVisible"; + EXPECT_TRUE(node->visible()) << "Should still visible after setting eVisible"; +} + +TEST_F(SceneNodeTest, SetSingleHideFlag) +{ + for (auto flag : allPossibleHideFlags) + { + auto node = std::make_shared(); + scene::addNodeToContainer(node, GlobalMapModule().getRoot()); + + node->visibilityMethodCalled = false; + node->enable(flag); + + EXPECT_TRUE(node->visibilityMethodCalled) << "Method should have been invoked"; + EXPECT_FALSE(node->visible()) << "Should be invisible after setting a flag"; + EXPECT_FALSE(node->passedVisibilityValue) << "Got the wrong visibility changed argument"; + + // Set the same flag again + node->visibilityMethodCalled = false; + node->enable(flag); + + EXPECT_FALSE(node->visibilityMethodCalled) << "Method shouldn't have been invoked, no change"; + EXPECT_FALSE(node->visible()) << "Should still be invisible"; + + // Clear the flag + node->visibilityMethodCalled = false; + node->disable(flag); + + EXPECT_TRUE(node->visibilityMethodCalled) << "Method should have been invoked"; + EXPECT_TRUE(node->visible()) << "Should be visible again after clearing the flag"; + EXPECT_TRUE(node->passedVisibilityValue) << "Got the wrong visibility changed argument"; + + // Clear the flag a second time + node->visibilityMethodCalled = false; + node->disable(flag); + + EXPECT_FALSE(node->visibilityMethodCalled) << "Method shouldn't have been invoked, no change"; + EXPECT_TRUE(node->visible()) << "Should still be visible"; + + scene::removeNodeFromParent(node); + } +} + +TEST_F(SceneNodeTest, SetMultipleHideFlags) +{ + auto node = std::make_shared(); + scene::addNodeToContainer(node, GlobalMapModule().getRoot()); + + std::vector allFlags(allPossibleHideFlags); + + // Set all possible flags to hide the node + for (auto flag : allFlags) + { + node->enable(flag); + } + + // Clear all but one flag, the node stays invisible throughout + for (auto flag = allFlags.begin(); flag != allFlags.end() - 1; ++flag) + { + node->visibilityMethodCalled = false; + node->disable(*flag); + + EXPECT_FALSE(node->visible()) << "Node should stay invisible since not all flags are cleared yet."; + EXPECT_FALSE(node->visibilityMethodCalled) << "No event should have been fired"; + } + + node->disable(allFlags.back()); + + EXPECT_TRUE(node->visible()) << "Should be visible after clearing all flags"; + EXPECT_TRUE(node->visibilityMethodCalled) << "The event should have been fired"; + EXPECT_TRUE(node->passedVisibilityValue) << "Got the wrong visibility changed argument"; +} + +TEST_F(SceneNodeTest, ForcedVisibility) +{ + std::vector allFlags(allPossibleHideFlags); + + // Try all possible hide flags + for (auto flag : allFlags) + { + auto node = std::make_shared(); + scene::addNodeToContainer(node, GlobalMapModule().getRoot()); + + node->enable(flag); + EXPECT_FALSE(node->visible()) << "Should be invisible after setting a flag"; + + // Set the forced visibility flag + node->visibilityMethodCalled = false; + node->setProtectedForcedVisibility(true); + + EXPECT_TRUE(node->visible()) << "Node is now forced visible"; + EXPECT_TRUE(node->visibilityMethodCalled) << "The event should have been fired"; + EXPECT_TRUE(node->passedVisibilityValue) << "Got the wrong visibility changed argument"; + + // Set the forced visibility flag again (should do nothing) + node->visibilityMethodCalled = false; + node->setProtectedForcedVisibility(true); + + EXPECT_TRUE(node->visible()) << "Node is still forced visible"; + EXPECT_FALSE(node->visibilityMethodCalled) << "The event shouldn't have been fired"; + + // Clear the forced visibility flag again + node->visibilityMethodCalled = false; + node->setProtectedForcedVisibility(false); + + EXPECT_FALSE(node->visible()) << "Node is no longer forced visible"; + EXPECT_TRUE(node->visibilityMethodCalled) << "The event should have been fired"; + EXPECT_FALSE(node->passedVisibilityValue) << "Got the wrong visibility changed argument"; + + // Clear the forced visibility flag a second time (should do nothing) + node->visibilityMethodCalled = false; + node->setProtectedForcedVisibility(false); + + EXPECT_FALSE(node->visible()) << "Node is still not forced visible"; + EXPECT_FALSE(node->visibilityMethodCalled) << "The event shouldn't have been fired"; + + scene::removeNodeFromParent(node); + } +} + +TEST_F(SceneNodeTest, SetFilterStatus) +{ + auto node = std::make_shared(); + scene::addNodeToContainer(node, GlobalMapModule().getRoot()); + + node->visibilityMethodCalled = false; + node->setFiltered(true); + + EXPECT_TRUE(node->visibilityMethodCalled) << "Method should have been invoked"; + EXPECT_FALSE(node->visible()) << "Should be invisible after filtering it"; + EXPECT_TRUE(node->isFiltered()) << "Node should report as filtered"; + EXPECT_FALSE(node->passedVisibilityValue) << "Got the wrong visibility changed argument"; + + // Set the filter status again + node->visibilityMethodCalled = false; + node->setFiltered(true); + + EXPECT_FALSE(node->visibilityMethodCalled) << "Method shouldn't have been invoked, no change"; + EXPECT_FALSE(node->visible()) << "Should still be invisible"; + EXPECT_TRUE(node->isFiltered()) << "Node should report as filtered"; + + // Clear the filter status + node->visibilityMethodCalled = false; + node->setFiltered(false); + + EXPECT_TRUE(node->visibilityMethodCalled) << "Method should have been invoked"; + EXPECT_TRUE(node->visible()) << "Should be visible again after un-filtering it"; + EXPECT_FALSE(node->isFiltered()) << "Node should report as unfiltered"; + EXPECT_TRUE(node->passedVisibilityValue) << "Got the wrong visibility changed argument"; + + // Clear the flag a second time + node->visibilityMethodCalled = false; + node->setFiltered(false); + + EXPECT_FALSE(node->visibilityMethodCalled) << "Method shouldn't have been invoked, no change"; + EXPECT_TRUE(node->visible()) << "Should still be visible"; + EXPECT_FALSE(node->isFiltered()) << "Node should report as unfiltered"; +} + +} diff --git a/test/WindingRendering.cpp b/test/WindingRendering.cpp new file mode 100644 index 0000000000..2c1fc3d1b5 --- /dev/null +++ b/test/WindingRendering.cpp @@ -0,0 +1,422 @@ +#include "gtest/gtest.h" +#include "render/ArbitraryMeshVertex.h" +#include "render/WindingRenderer.h" +#include "render/CompactWindingVertexBuffer.h" + +namespace test +{ + +constexpr int SmallestWindingSize = 3; +constexpr int LargestWindingSize = 12; + +using VertexBuffer = render::CompactWindingVertexBuffer; + +inline ArbitraryMeshVertex createNthVertexOfWinding(int n, int id, std::size_t size) +{ + auto offset = static_cast(n + size * id); + + return ArbitraryMeshVertex( + { offset + 0.0, offset + 0.5, offset + 0.3 }, + { 0, 0, offset + 0.0 }, + { offset + 0.0, -offset + 0.0 } + ); +} + +inline std::vector createWinding(int id, std::size_t size) +{ + std::vector winding; + + for (int i = 0; i < size; ++i) + { + winding.emplace_back(createNthVertexOfWinding(i, id, size)); + } + + return winding; +} + +inline void checkWindingIndices(const VertexBuffer& buffer, VertexBuffer::Slot slot) +{ + // Slot must be within range + auto windingSize = buffer.getWindingSize(); + + auto numWindingsInBuffer = buffer.getVertices().size() / windingSize; + EXPECT_LT(slot, numWindingsInBuffer) << "Slot out of bounds"; + + // Assume the indices are within bounds + auto indexStart = buffer.getNumIndicesPerWinding() * slot; + EXPECT_LE(indexStart + buffer.getNumIndicesPerWinding(), buffer.getIndices().size()); + + // Check the indices, they must be referencing vertices in that slot + for (auto i = indexStart; i < indexStart + buffer.getNumIndicesPerWinding(); ++i) + { + auto index = buffer.getIndices().at(i); + EXPECT_GE(index, slot * windingSize) << "Winding index out of lower bounds"; + EXPECT_LT(index, (slot + 1) * windingSize) << "Winding index out of upper bounds"; + } +} + +inline void checkWindingDataInSlot(const VertexBuffer& buffer, VertexBuffer::Slot slot, int expectedId) +{ + // Slot must be within range + auto windingSize = buffer.getWindingSize(); + + auto numWindingsInBuffer = buffer.getVertices().size() / windingSize; + EXPECT_LT(slot, numWindingsInBuffer) << "Slot out of bounds"; + + for (auto i = 0; i < windingSize; ++i) + { + auto position = slot * windingSize + i; + + auto expectedVertex = createNthVertexOfWinding(i, expectedId, windingSize); + auto vertex = buffer.getVertices().at(position); + + EXPECT_TRUE(math::isNear(vertex.vertex, expectedVertex.vertex, 0.01)) << "Vertex data mismatch"; + EXPECT_TRUE(math::isNear(vertex.texcoord, expectedVertex.texcoord, 0.01)) << "Texcoord data mismatch"; + EXPECT_TRUE(math::isNear(vertex.normal, expectedVertex.normal, 0.01)) << "Texcoord data mismatch"; + } +} + +TEST(CompactWindingVertexBuffer, NumIndicesPerWinding) +{ + for (auto i = 0; i < 10; ++i) + { + VertexBuffer buffer(i); + EXPECT_EQ(buffer.getWindingSize(), i); + EXPECT_EQ(buffer.getNumIndicesPerWinding(), 3 * (buffer.getWindingSize() - 2)); + } +} + +TEST(CompactWindingVertexBuffer, AddSingleWinding) +{ + for (auto size = SmallestWindingSize; size < LargestWindingSize; ++size) + { + VertexBuffer buffer(size); + + auto winding1 = createWinding(1, size); + auto slot = buffer.pushWinding(winding1); + + EXPECT_EQ(slot, 0) << "Wrong slot assignment"; + EXPECT_EQ(buffer.getVertices().size(), size); + EXPECT_EQ(buffer.getIndices().size(), buffer.getNumIndicesPerWinding()); + + // Assume that the indices have been correctly calculated + checkWindingIndices(buffer, slot); + checkWindingDataInSlot(buffer, slot, 1); // ID is the same as in createWinding + } +} + +TEST(CompactWindingVertexBuffer, AddMultipleWindings) +{ + for (auto size = SmallestWindingSize; size < LargestWindingSize; ++size) + { + VertexBuffer buffer(size); + + // Add 10 windings to the buffer + for (auto n = 1; n <= 10; ++n) + { + auto winding1 = createWinding(n, size); + auto slot = buffer.pushWinding(winding1); + + EXPECT_EQ(slot, n-1) << "Unexpected slot assignment"; + EXPECT_EQ(buffer.getVertices().size(), n * size); + EXPECT_EQ(buffer.getIndices().size(), n * buffer.getNumIndicesPerWinding()); + + // Assume that the indices have been correctly calculated + checkWindingIndices(buffer, slot); + checkWindingDataInSlot(buffer, slot, n); // ID is the same as in createWinding + } + } +} + +TEST(CompactWindingVertexBuffer, RemoveOneWinding) +{ + for (auto size = SmallestWindingSize; size < LargestWindingSize; ++size) + { + // We will work with a buffer containing 13 windings, + // the test will remove a single winding from every possible position + constexpr auto NumWindings = 13; + + for (int slotToRemove = 0; slotToRemove < NumWindings; ++slotToRemove) + { + VertexBuffer buffer(size); + + // Add the desired number of windings to the buffer + for (auto n = 1; n <= NumWindings; ++n) + { + buffer.pushWinding(createWinding(n, size)); + } + + // Remove a winding from the slot, this should move all + // windings in greater slot numbers towards the left + buffer.removeWinding(slotToRemove); + + // Check the resized vectors + EXPECT_EQ(buffer.getVertices().size(), (NumWindings - 1) * buffer.getWindingSize()) << "Vertex array not resized"; + EXPECT_EQ(buffer.getIndices().size(), (NumWindings - 1) * buffer.getNumIndicesPerWinding()) << "Index array not resized"; + + // All winding indices must still be correct + auto remainingSlots = NumWindings - 1; + for (auto slot = 0; slot < remainingSlots; ++slot) + { + checkWindingIndices(buffer, slot); + + // We expect the winding vertex data to be unchanged if the slot has not been touched + auto id = slot + 1; + checkWindingDataInSlot(buffer, slot, slot < slotToRemove ? id : id + 1); + } + } + } +} + +TEST(CompactWindingVertexBuffer, RemoveNonExistentSlot) +{ + VertexBuffer buffer(4); + + // Add a few windings to the buffer + for (auto n = 1; n <= 20; ++n) + { + auto winding1 = createWinding(n, 4); + buffer.pushWinding(winding1); + } + + // Pass a non-existent slot number to removeWinding(), it should throw + auto numSlots = static_cast(buffer.getVertices().size() / buffer.getWindingSize()); + + EXPECT_THROW(buffer.removeWinding(numSlots), std::logic_error); + EXPECT_THROW(buffer.removeWinding(numSlots + 1), std::logic_error); + EXPECT_THROW(buffer.removeWinding(numSlots + 200), std::logic_error); +} + +TEST(CompactWindingVertexBuffer, ReplaceWinding) +{ + for (auto size = SmallestWindingSize; size < LargestWindingSize; ++size) + { + // We will work with a buffer containing 13 windings, + // the test will replace a single winding from every possible position + constexpr auto NumWindings = 13; + constexpr auto IdForReplacement = NumWindings * 2; + + for (int slotToReplace = 0; slotToReplace < NumWindings; ++slotToReplace) + { + VertexBuffer buffer(size); + + // Add the desired number of windings to the buffer + for (auto n = 1; n <= NumWindings; ++n) + { + buffer.pushWinding(createWinding(n, size)); + } + + auto replacementWinding = createWinding(IdForReplacement, size); + + // Replace a winding in the desired slot + buffer.replaceWinding(slotToReplace, replacementWinding); + + // Check the unchanged vector sizes + EXPECT_EQ(buffer.getVertices().size(), NumWindings * buffer.getWindingSize()) << "Vertex array should remain unchanged"; + EXPECT_EQ(buffer.getIndices().size(), NumWindings * buffer.getNumIndicesPerWinding()) << "Index array should remain unchanged"; + + // Check the slot data + for (auto n = 1; n <= NumWindings; ++n) + { + auto slot = n - 1; + + checkWindingDataInSlot(buffer, slot, slot == slotToReplace ? IdForReplacement : n); + } + } + } +} + +TEST(CompactWindingVertexBuffer, RemoveMultipleWindings) +{ + for (auto size = SmallestWindingSize; size < LargestWindingSize; ++size) + { + // We will work with a buffer containing N windings, + // the test will remove 1 to N windings from every possible position (2^N - 1) + constexpr auto NumWindings = 9; + + for (int combination = 1; combination <= (1 << NumWindings) - 1; ++combination) + { + VertexBuffer buffer(size); + + // Add the desired number of windings to the buffer + for (auto n = 1; n <= NumWindings; ++n) + { + buffer.pushWinding(createWinding(n, size)); + } + + std::vector slotsToRemove; + + // Translate the bit combination to a set of slots to remove + for (int pos = 0; pos < NumWindings; ++pos) + { + if (combination & (1 << pos)) + { + slotsToRemove.push_back(pos); + } + } + + buffer.removeWindings(slotsToRemove); + + // The buffer should be smaller now + auto remainingWindings = NumWindings - slotsToRemove.size(); + + EXPECT_EQ(buffer.getVertices().size(), remainingWindings * buffer.getWindingSize()) << + "Winding vertex array has the wrong size after removal"; + EXPECT_EQ(buffer.getIndices().size(), remainingWindings * buffer.getNumIndicesPerWinding()) << + "Winding index array has the wrong size after removal"; + + // Check the winding data, the ones we removed should be missing now + for (int i = 0, slot = 0; i < NumWindings; ++i) + { + if (std::find(slotsToRemove.begin(), slotsToRemove.end(), i) != slotsToRemove.end()) + { + continue; // this id got removed, skip it + } + + checkWindingIndices(buffer, slot); + checkWindingDataInSlot(buffer, slot, i + 1); + ++slot; + } + } + } +} + +TEST(CompactWindingVertexBuffer, TriangleIndexerSize3) // Winding size == 3 +{ + render::CompactWindingVertexBuffer buffer(3); + + EXPECT_EQ(buffer.getNumIndicesPerWinding(), 3); + + // Generate winding indices and check the result + std::vector indices; + render::WindingIndexer_Triangles::GenerateAndAssignIndices(std::back_inserter(indices), buffer.getWindingSize(), 80); + + EXPECT_EQ(indices.size(), buffer.getNumIndicesPerWinding()) << "Wrong number of indices generated"; + + EXPECT_EQ(indices[0], 80) << "Index 0 mismatch"; + EXPECT_EQ(indices[1], 82) << "Index 1 mismatch"; + EXPECT_EQ(indices[2], 81) << "Index 2 mismatch"; +} + +TEST(CompactWindingVertexBuffer, TriangleIndexerSize4) // Winding size == 4 +{ + render::CompactWindingVertexBuffer buffer(4); + + EXPECT_EQ(buffer.getNumIndicesPerWinding(), 6); + + // Generate winding indices and check the result + std::vector indices; + render::WindingIndexer_Triangles::GenerateAndAssignIndices(std::back_inserter(indices), buffer.getWindingSize(), 80); + + EXPECT_EQ(indices.size(), buffer.getNumIndicesPerWinding()) << "Wrong number of indices generated"; + + EXPECT_EQ(indices[0], 80) << "Index 0 mismatch"; + EXPECT_EQ(indices[1], 83) << "Index 1 mismatch"; + EXPECT_EQ(indices[2], 82) << "Index 2 mismatch"; + + EXPECT_EQ(indices[3], 80) << "Index 3 mismatch"; + EXPECT_EQ(indices[4], 82) << "Index 4 mismatch"; + EXPECT_EQ(indices[5], 81) << "Index 5 mismatch"; +} + +TEST(CompactWindingVertexBuffer, TriangleIndexerSize5) // Winding size == 5 +{ + render::CompactWindingVertexBuffer buffer(5); + + EXPECT_EQ(buffer.getNumIndicesPerWinding(), 9); + + // Generate winding indices and check the result + std::vector indices; + render::WindingIndexer_Triangles::GenerateAndAssignIndices(std::back_inserter(indices), buffer.getWindingSize(), 80); + + EXPECT_EQ(indices.size(), buffer.getNumIndicesPerWinding()) << "Wrong number of indices generated"; + + EXPECT_EQ(indices[0], 80) << "Index 0 mismatch"; + EXPECT_EQ(indices[1], 84) << "Index 1 mismatch"; + EXPECT_EQ(indices[2], 83) << "Index 2 mismatch"; + + EXPECT_EQ(indices[3], 80) << "Index 3 mismatch"; + EXPECT_EQ(indices[4], 83) << "Index 4 mismatch"; + EXPECT_EQ(indices[5], 82) << "Index 5 mismatch"; + + EXPECT_EQ(indices[6], 80) << "Index 6 mismatch"; + EXPECT_EQ(indices[7], 82) << "Index 7 mismatch"; + EXPECT_EQ(indices[8], 81) << "Index 8 mismatch"; +} + +TEST(CompactWindingVertexBuffer, LineIndexerSize3) // Winding size == 3 +{ + render::CompactWindingVertexBuffer buffer(3); + + EXPECT_EQ(buffer.getNumIndicesPerWinding(), 6); + + // Generate winding indices and check the result + std::vector indices; + render::WindingIndexer_Lines::GenerateAndAssignIndices(std::back_inserter(indices), buffer.getWindingSize(), 80); + + EXPECT_EQ(indices.size(), buffer.getNumIndicesPerWinding()) << "Wrong number of indices generated"; + + EXPECT_EQ(indices[0], 80) << "Index 0 mismatch"; + EXPECT_EQ(indices[1], 81) << "Index 1 mismatch"; + + EXPECT_EQ(indices[2], 81) << "Index 2 mismatch"; + EXPECT_EQ(indices[3], 82) << "Index 3 mismatch"; + + EXPECT_EQ(indices[4], 82) << "Index 4 mismatch"; + EXPECT_EQ(indices[5], 80) << "Index 5 mismatch"; +} + +TEST(CompactWindingVertexBuffer, LineIndexerSize4) // Winding size == 4 +{ + render::CompactWindingVertexBuffer buffer(4); + + EXPECT_EQ(buffer.getNumIndicesPerWinding(), 8); + + // Generate winding indices and check the result + std::vector indices; + render::WindingIndexer_Lines::GenerateAndAssignIndices(std::back_inserter(indices), buffer.getWindingSize(), 80); + + EXPECT_EQ(indices.size(), buffer.getNumIndicesPerWinding()) << "Wrong number of indices generated"; + + EXPECT_EQ(indices[0], 80) << "Index 0 mismatch"; + EXPECT_EQ(indices[1], 81) << "Index 1 mismatch"; + + EXPECT_EQ(indices[2], 81) << "Index 2 mismatch"; + EXPECT_EQ(indices[3], 82) << "Index 3 mismatch"; + + EXPECT_EQ(indices[4], 82) << "Index 4 mismatch"; + EXPECT_EQ(indices[5], 83) << "Index 5 mismatch"; + + EXPECT_EQ(indices[6], 83) << "Index 6 mismatch"; + EXPECT_EQ(indices[7], 80) << "Index 7 mismatch"; +} + +TEST(CompactWindingVertexBuffer, LineIndexerSize5) // Winding size == 5 +{ + render::CompactWindingVertexBuffer buffer(5); + + EXPECT_EQ(buffer.getNumIndicesPerWinding(), 10); + + // Generate winding indices and check the result + std::vector indices; + render::WindingIndexer_Lines::GenerateAndAssignIndices(std::back_inserter(indices), buffer.getWindingSize(), 80); + + EXPECT_EQ(indices.size(), buffer.getNumIndicesPerWinding()) << "Wrong number of indices generated"; + + EXPECT_EQ(indices[0], 80) << "Index 0 mismatch"; + EXPECT_EQ(indices[1], 81) << "Index 1 mismatch"; + + EXPECT_EQ(indices[2], 81) << "Index 2 mismatch"; + EXPECT_EQ(indices[3], 82) << "Index 3 mismatch"; + + EXPECT_EQ(indices[4], 82) << "Index 4 mismatch"; + EXPECT_EQ(indices[5], 83) << "Index 5 mismatch"; + + EXPECT_EQ(indices[6], 83) << "Index 6 mismatch"; + EXPECT_EQ(indices[7], 84) << "Index 7 mismatch"; + + EXPECT_EQ(indices[8], 84) << "Index 8 mismatch"; + EXPECT_EQ(indices[9], 80) << "Index 9 mismatch"; +} + +} diff --git a/tools/msvc/DarkRadiant.vcxproj b/tools/msvc/DarkRadiant.vcxproj index a34a37a0f1..815e09233c 100644 --- a/tools/msvc/DarkRadiant.vcxproj +++ b/tools/msvc/DarkRadiant.vcxproj @@ -290,7 +290,6 @@ - @@ -500,7 +499,6 @@ - diff --git a/tools/msvc/DarkRadiant.vcxproj.filters b/tools/msvc/DarkRadiant.vcxproj.filters index 6be68b123e..ea25ccd115 100644 --- a/tools/msvc/DarkRadiant.vcxproj.filters +++ b/tools/msvc/DarkRadiant.vcxproj.filters @@ -223,9 +223,6 @@ src\ui\common - - src\ui\common - src\ui\common @@ -750,9 +747,6 @@ src\ui\common - - src\ui\common - src\ui\common diff --git a/tools/msvc/DarkRadiantCore.vcxproj b/tools/msvc/DarkRadiantCore.vcxproj index 5714a83872..df80d75ff1 100644 --- a/tools/msvc/DarkRadiantCore.vcxproj +++ b/tools/msvc/DarkRadiantCore.vcxproj @@ -730,6 +730,7 @@ + @@ -981,6 +982,7 @@ + diff --git a/tools/msvc/DarkRadiantCore.vcxproj.filters b/tools/msvc/DarkRadiantCore.vcxproj.filters index 036c4b020c..c1e7c9357a 100644 --- a/tools/msvc/DarkRadiantCore.vcxproj.filters +++ b/tools/msvc/DarkRadiantCore.vcxproj.filters @@ -2250,5 +2250,11 @@ src\selection\algorithm + + src\brush + + + src\rendersystem\backend + \ No newline at end of file diff --git a/tools/msvc/Tests/Tests.vcxproj b/tools/msvc/Tests/Tests.vcxproj index a03090cc4b..f7300f3183 100644 --- a/tools/msvc/Tests/Tests.vcxproj +++ b/tools/msvc/Tests/Tests.vcxproj @@ -107,6 +107,7 @@ + @@ -114,6 +115,7 @@ + diff --git a/tools/msvc/Tests/Tests.vcxproj.filters b/tools/msvc/Tests/Tests.vcxproj.filters index 9451b99e6b..ad76d4a847 100644 --- a/tools/msvc/Tests/Tests.vcxproj.filters +++ b/tools/msvc/Tests/Tests.vcxproj.filters @@ -52,6 +52,8 @@ + + diff --git a/tools/msvc/include.vcxproj b/tools/msvc/include.vcxproj index b290c063d0..05ec9503e6 100644 --- a/tools/msvc/include.vcxproj +++ b/tools/msvc/include.vcxproj @@ -188,6 +188,7 @@ + @@ -197,6 +198,7 @@ + diff --git a/tools/msvc/include.vcxproj.filters b/tools/msvc/include.vcxproj.filters index 385793a9b9..03e722fa3a 100644 --- a/tools/msvc/include.vcxproj.filters +++ b/tools/msvc/include.vcxproj.filters @@ -162,6 +162,8 @@ ui + + diff --git a/tools/msvc/libs.vcxproj b/tools/msvc/libs.vcxproj index 0e39261d0c..3c8c0de594 100644 --- a/tools/msvc/libs.vcxproj +++ b/tools/msvc/libs.vcxproj @@ -211,13 +211,17 @@ + + + + @@ -225,6 +229,7 @@ + diff --git a/tools/msvc/libs.vcxproj.filters b/tools/msvc/libs.vcxproj.filters index 07d809eebe..ac7bb6b82c 100644 --- a/tools/msvc/libs.vcxproj.filters +++ b/tools/msvc/libs.vcxproj.filters @@ -329,6 +329,21 @@ messages + + render + + + render + + + render + + + render + + + render +