From 9fbeff582d59118e50d6a94f20ae53c78e791d54 Mon Sep 17 00:00:00 2001 From: codereader Date: Sat, 11 Dec 2021 18:24:51 +0100 Subject: [PATCH] #5584: Working on sphere rendering --- radiantcore/entity/speaker/SpeakerNode.cpp | 12 ++ radiantcore/entity/speaker/SpeakerNode.h | 1 + .../entity/speaker/SpeakerRenderables.cpp | 192 ++++++++++-------- .../entity/speaker/SpeakerRenderables.h | 63 ++++-- 4 files changed, 165 insertions(+), 103 deletions(-) diff --git a/radiantcore/entity/speaker/SpeakerNode.cpp b/radiantcore/entity/speaker/SpeakerNode.cpp index b1558e166a..ef71bcdd6e 100644 --- a/radiantcore/entity/speaker/SpeakerNode.cpp +++ b/radiantcore/entity/speaker/SpeakerNode.cpp @@ -20,6 +20,7 @@ SpeakerNode::SpeakerNode(const IEntityClassPtr& eclass) : m_origin(ORIGINKEY_IDENTITY), _renderableBox(m_aabb_local, worldAABB().getOrigin()), _renderableRadiiWireframe(m_origin, _radiiTransformed), + _renderableRadiiFill(m_origin, _radiiTransformed), _showRadiiWhenUnselected(EntitySettings::InstancePtr()->getShowAllSpeakerRadii()), m_useSpeakerRadii(true), m_minIsSet(false), @@ -37,6 +38,7 @@ SpeakerNode::SpeakerNode(const SpeakerNode& other) : m_origin(ORIGINKEY_IDENTITY), _renderableBox(m_aabb_local, worldAABB().getOrigin()), _renderableRadiiWireframe(m_origin, _radiiTransformed), + _renderableRadiiFill(m_origin, _radiiTransformed), _showRadiiWhenUnselected(EntitySettings::InstancePtr()->getShowAllSpeakerRadii()), m_useSpeakerRadii(true), m_minIsSet(false), @@ -249,10 +251,12 @@ void SpeakerNode::onPreRender(const VolumeTest& volume) if (_showRadiiWhenUnselected || isSelected()) { _renderableRadiiWireframe.update(getWireShader()); + _renderableRadiiFill.update(getFillShader()); } else { _renderableRadiiWireframe.clear(); + _renderableRadiiFill.clear(); } } @@ -291,6 +295,10 @@ void SpeakerNode::renderHighlights(IRenderableCollector& collector, const Volume { collector.addHighlightRenderable(_renderableRadiiWireframe, Matrix4::getIdentity()); } + else + { + collector.addHighlightRenderable(_renderableRadiiFill, Matrix4::getIdentity()); + } EntityNode::renderHighlights(collector, volume); } @@ -302,6 +310,7 @@ void SpeakerNode::setRenderSystem(const RenderSystemPtr& renderSystem) // Clear the geometry from any previous shader _renderableBox.clear(); _renderableRadiiWireframe.clear(); + _renderableRadiiFill.clear(); } void SpeakerNode::translate(const Vector3& translation) @@ -316,6 +325,7 @@ void SpeakerNode::updateTransform() _renderableBox.queueUpdate(); _renderableRadiiWireframe.queueUpdate(); + _renderableRadiiFill.queueUpdate(); } void SpeakerNode::updateAABB() @@ -460,6 +470,7 @@ void SpeakerNode::onSelectionStatusChange(bool changeGroupStatus) // Radius renderable is not always prepared for rendering, queue an update _renderableRadiiWireframe.queueUpdate(); + _renderableRadiiFill.queueUpdate(); } @@ -469,6 +480,7 @@ void SpeakerNode::onEntitySettingsChanged() _showRadiiWhenUnselected = EntitySettings::InstancePtr()->getShowAllSpeakerRadii(); _renderableRadiiWireframe.queueUpdate(); + _renderableRadiiFill.queueUpdate(); } } // namespace entity diff --git a/radiantcore/entity/speaker/SpeakerNode.h b/radiantcore/entity/speaker/SpeakerNode.h index 7e0975cf3a..13fda7d84e 100644 --- a/radiantcore/entity/speaker/SpeakerNode.h +++ b/radiantcore/entity/speaker/SpeakerNode.h @@ -46,6 +46,7 @@ class SpeakerNode final : // Renderable speaker radii RenderableSpeakerRadiiWireframe _renderableRadiiWireframe; + RenderableSpeakerRadiiFill _renderableRadiiFill; bool _showRadiiWhenUnselected; diff --git a/radiantcore/entity/speaker/SpeakerRenderables.cpp b/radiantcore/entity/speaker/SpeakerRenderables.cpp index eeefcd6d52..dfae7b17de 100644 --- a/radiantcore/entity/speaker/SpeakerRenderables.cpp +++ b/radiantcore/entity/speaker/SpeakerRenderables.cpp @@ -1,89 +1,7 @@ #include "SpeakerRenderables.h" -#include "igl.h" #include "render.h" -void sphereDrawFill(const Vector3& origin, float radius, int sides) -{ - if (radius <= 0) - return; - - const double dt = c_2pi / static_cast(sides); - const double dp = math::PI / static_cast(sides); - - glBegin(GL_TRIANGLES); - for (int i = 0; i <= sides - 1; ++i) - { - for (int j = 0; j <= sides - 2; ++j) - { - const double t = i * dt; - const double p = (j * dp) - (math::PI / 2.0); - - { - Vector3 v(origin + Vector3::createForSpherical(t, p) * radius); - glVertex3dv(v); - } - - { - Vector3 v(origin + Vector3::createForSpherical(t, p + dp) * radius); - glVertex3dv(v); - } - - { - Vector3 v(origin + Vector3::createForSpherical(t + dt, p + dp) * radius); - glVertex3dv(v); - } - - { - Vector3 v(origin + Vector3::createForSpherical(t, p) * radius); - glVertex3dv(v); - } - - { - Vector3 v(origin + Vector3::createForSpherical(t + dt, p + dp) * radius); - glVertex3dv(v); - } - - { - Vector3 v(origin + Vector3::createForSpherical(t + dt, p) * radius); - glVertex3dv(v); - } - } - } - - { - const double p = (sides - 1) * dp - (static_cast(math::PI) / 2.0f); - for (int i = 0; i <= sides - 1; ++i) - { - const double t = i * dt; - - { - Vector3 v(origin + Vector3::createForSpherical(t, p) * radius); - glVertex3dv(v); - } - - { - Vector3 v(origin + Vector3::createForSpherical(t + dt, p + dp) * radius); - glVertex3dv(v); - } - - { - Vector3 v(origin + Vector3::createForSpherical(t + dt, p) * radius); - glVertex3dv(v); - } - } - } - glEnd(); -} - -void speakerDrawRadiiFill(const Vector3& origin, const SoundRadii& rad) -{ - if(rad.getMin() > 0) - sphereDrawFill(origin, rad.getMin(), 16); - if(rad.getMax() > 0) - sphereDrawFill(origin, rad.getMax(), 16); -} - namespace entity { @@ -109,6 +27,8 @@ inline std::vector generateWireframeCircleIndices(std::size_t numV return indices; } +// ---- Wireframe Variant ---- + void RenderableSpeakerRadiiWireframe::updateGeometry() { if (!_needsUpdate) return; @@ -149,4 +69,112 @@ void RenderableSpeakerRadiiWireframe::updateGeometry() RenderableGeometry::updateGeometry(render::GeometryType::Lines, vertices, CircleIndices); } +// ---- Fill Variant ---- + +// Generates the draw indices for a sphere with N circles plus two pole vertices at the back +inline std::vector generateSphereIndices(std::size_t numVertices, unsigned int numCircles) +{ + assert(numVertices > 2); + + std::vector indices; + + const auto numVerticesPerCircle = static_cast(numVertices - 2) / numCircles; + + indices.reserve((numCircles + 1) * numVerticesPerCircle * 4); // 4 indices per quad + + for (unsigned int circle = 0; circle < numCircles - 1; ++circle) + { + unsigned int offset = circle * numVerticesPerCircle; + + for (unsigned int i = 0; i < numVerticesPerCircle; ++i) + { + indices.push_back(offset + i); + indices.push_back(offset + (i + 1) % numVerticesPerCircle); // wrap + indices.push_back(offset + numVerticesPerCircle + (i + 1) % numVerticesPerCircle); // wrap + indices.push_back(offset + numVerticesPerCircle + i); + } + } + + // Connect the topmost circle to the top pole vertex + // These are tris, but we cannot mix, so let's form a degenerate quad + auto topPoleIndex = static_cast(numVertices) - 2; + auto bottomPoleIndex = static_cast(numVertices) - 1; + + for (unsigned int i = 0; i < numVerticesPerCircle; ++i) + { + indices.push_back(topPoleIndex); + indices.push_back(topPoleIndex); + indices.push_back((i + 1) % numVerticesPerCircle); // wrap + indices.push_back(i); + } + + // Connect the most southern circle to the south pole vertex + auto bottomCircleOffset = (numCircles - 1) * numVerticesPerCircle; + + for (unsigned int i = 0; i < numVerticesPerCircle; ++i) + { + indices.push_back(bottomCircleOffset + i); + indices.push_back(bottomCircleOffset + (i + 1) % numVerticesPerCircle); // wrap + indices.push_back(bottomPoleIndex); + indices.push_back(bottomPoleIndex); + } + + assert((numCircles + 1) * numVerticesPerCircle * 4 == indices.size()); + + return indices; +} + +void RenderableSpeakerRadiiFill::updateGeometry() +{ + if (!_needsUpdate) return; + + _needsUpdate = false; + + constexpr std::size_t ThetaDivisions = 8; // Inclination divisions + constexpr std::size_t PhiDivisions = 16; // Azimuth divisions + + const double ThetaStep = math::PI / ThetaDivisions; + const double PhiStep = 2 * math::PI / PhiDivisions; + + // We have (divisions-1) vertex circles between the top and bottom vertex + constexpr auto NumCircles = ThetaDivisions - 1; + + std::vector vertices; + + // Reserve the vertices for the two pole vertices and the circles + vertices.reserve(NumCircles * PhiDivisions + 2); + + auto radius = _radii.getMax(); + + for (auto strip = 0; strip < NumCircles; ++strip) + { + auto theta = ThetaStep * (strip + 1); + auto z = cos(theta); + + for (auto p = 0; p < PhiDivisions; ++p) + { + auto phi = PhiStep * p; + auto sinTheta = sin(theta); + auto cosTheta = cos(theta); + + // We use the unit direction for the normal at that vertex + auto unit = Vector3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); + + // Move the points to their world position + vertices.push_back(ArbitraryMeshVertex(unit * radius + _origin, unit, {0, 0})); + } + } + + // The north and south pole vertices + vertices.push_back(ArbitraryMeshVertex(Vector3(0, 0, radius) + _origin, { 0,0,1 }, { 0,0 })); + vertices.push_back(ArbitraryMeshVertex(Vector3(0, 0, -radius) + _origin, { 0,0,-1 }, { 0,0 })); + + assert(vertices.size() == NumCircles * PhiDivisions + 2); + + // Generate the triangle indices + static auto SphereIndices = generateSphereIndices(vertices.size(), NumCircles); + + RenderableGeometry::updateGeometry(render::GeometryType::Quads, vertices, SphereIndices); +} + } // namespace diff --git a/radiantcore/entity/speaker/SpeakerRenderables.h b/radiantcore/entity/speaker/SpeakerRenderables.h index 41ece2f0e6..246af68c18 100644 --- a/radiantcore/entity/speaker/SpeakerRenderables.h +++ b/radiantcore/entity/speaker/SpeakerRenderables.h @@ -7,40 +7,61 @@ namespace entity { -/** - * \brief - * Renderable speaker radius class. - * - * This OpenGLRenderable renders the two spherical radii of a speaker, - * representing the s_min and s_max values. - */ -class RenderableSpeakerRadiiWireframe : +class RenderableSpeakerRadiiBase : public render::RenderableGeometry { -private: +protected: bool _needsUpdate; - const Vector3& _origin; + const Vector3& _origin; // SoundRadii reference containing min and max radius values - // (the actual instance resides in the SpeakerNode) - const SoundRadii& _radii; + // (the actual instance resides in the SpeakerNode) + const SoundRadii& _radii; -public: - - /** - * \brief - * Construct an instance with the given origin. - */ - RenderableSpeakerRadiiWireframe(const Vector3& origin, const SoundRadii& radii) : - _origin(origin), - _radii(radii) +protected: + RenderableSpeakerRadiiBase(const Vector3& origin, const SoundRadii& radii) : + _origin(origin), + _radii(radii) {} +public: void queueUpdate() { _needsUpdate = true; } +}; + +/** + * \brief + * Renderable speaker radius class (wireframe mode). + * Draws 3 axis-aligned circles per radius. + */ +class RenderableSpeakerRadiiWireframe : + public RenderableSpeakerRadiiBase +{ +public: + // Construct an instance with the given origin and radius. + RenderableSpeakerRadiiWireframe(const Vector3& origin, const SoundRadii& radii) : + RenderableSpeakerRadiiBase(origin, radii) + {} + + void updateGeometry() override; +}; + +/** + * \brief + * Renderable speaker radius class (camera mode). + * Draws a quad-based sphere with a fixed number of subdivisions. + */ +class RenderableSpeakerRadiiFill : + public RenderableSpeakerRadiiBase +{ +public: + // Construct an instance with the given origin and radius. + RenderableSpeakerRadiiFill(const Vector3& origin, const SoundRadii& radii) : + RenderableSpeakerRadiiBase(origin, radii) + {} void updateGeometry() override; };