Skip to content

Commit

Permalink
#5584: Working on sphere rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
codereader committed Dec 11, 2021
1 parent 5b4f017 commit 9fbeff5
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 103 deletions.
12 changes: 12 additions & 0 deletions radiantcore/entity/speaker/SpeakerNode.cpp
Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -249,10 +251,12 @@ void SpeakerNode::onPreRender(const VolumeTest& volume)
if (_showRadiiWhenUnselected || isSelected())
{
_renderableRadiiWireframe.update(getWireShader());
_renderableRadiiFill.update(getFillShader());
}
else
{
_renderableRadiiWireframe.clear();
_renderableRadiiFill.clear();
}
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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)
Expand All @@ -316,6 +325,7 @@ void SpeakerNode::updateTransform()

_renderableBox.queueUpdate();
_renderableRadiiWireframe.queueUpdate();
_renderableRadiiFill.queueUpdate();
}

void SpeakerNode::updateAABB()
Expand Down Expand Up @@ -460,6 +470,7 @@ void SpeakerNode::onSelectionStatusChange(bool changeGroupStatus)

// Radius renderable is not always prepared for rendering, queue an update
_renderableRadiiWireframe.queueUpdate();
_renderableRadiiFill.queueUpdate();
}


Expand All @@ -469,6 +480,7 @@ void SpeakerNode::onEntitySettingsChanged()

_showRadiiWhenUnselected = EntitySettings::InstancePtr()->getShowAllSpeakerRadii();
_renderableRadiiWireframe.queueUpdate();
_renderableRadiiFill.queueUpdate();
}

} // namespace entity
1 change: 1 addition & 0 deletions radiantcore/entity/speaker/SpeakerNode.h
Expand Up @@ -46,6 +46,7 @@ class SpeakerNode final :

// Renderable speaker radii
RenderableSpeakerRadiiWireframe _renderableRadiiWireframe;
RenderableSpeakerRadiiFill _renderableRadiiFill;

bool _showRadiiWhenUnselected;

Expand Down
192 changes: 110 additions & 82 deletions 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<float>(sides);
const double dp = math::PI / static_cast<float>(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<float>(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
{

Expand All @@ -109,6 +27,8 @@ inline std::vector<unsigned int> generateWireframeCircleIndices(std::size_t numV
return indices;
}

// ---- Wireframe Variant ----

void RenderableSpeakerRadiiWireframe::updateGeometry()
{
if (!_needsUpdate) return;
Expand Down Expand Up @@ -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<unsigned int> generateSphereIndices(std::size_t numVertices, unsigned int numCircles)
{
assert(numVertices > 2);

std::vector<unsigned int> indices;

const auto numVerticesPerCircle = static_cast<unsigned int>(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<unsigned int>(numVertices) - 2;
auto bottomPoleIndex = static_cast<unsigned int>(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<ArbitraryMeshVertex> 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
63 changes: 42 additions & 21 deletions radiantcore/entity/speaker/SpeakerRenderables.h
Expand Up @@ -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;
};
Expand Down

0 comments on commit 9fbeff5

Please sign in to comment.