Feature/arrow renderable (#2219)

* Create a RenderableNodeLine example asset

* First version of node direction hint renderable (arrow)

* Add possibility to set length as a multiplier of the bounding sphere

* Draw arrow cylinder using index array

* Update exponents and min max values for lengths

* Draw arrow head

* Only update arrow geometry when positions change

* Add some properties to control visuals of arrow

* Implement invert option

* Add normals and shading

* Set arrow head size by length percentage instead of angle

* Add bottom circle to cone

* Cleanup and update examples

* Remove non-existing property from example asset

* Fix vertices not updating if anchor node was changed

And some missing updates on property change

* Start cleaning up some shape rendering helper functions

* Cleanup code and move cylinder function to helper class

* Refactor cylinder creation code (fewer loops over same vector)

* Update transformations to correctly scale and place arrow

* Add the cone to make the arrowhead

* Update faulty triangle normals

* Add property visibilities

* Rename NodeDirectionHint to NodeArrow

* Apply suggestions from code review

Co-authored-by: Alexander Bock <alexander.bock@liu.se>


---------

Co-authored-by: Alexander Bock <alexander.bock@liu.se>
This commit is contained in:
Emma Broman
2023-09-11 11:04:46 +02:00
committed by GitHub
parent 7bfedbdf12
commit d77836d910
11 changed files with 1176 additions and 58 deletions

View File

@@ -0,0 +1,77 @@
local earth = asset.require("scene/solarsystem/planets/earth/earth")
local moon = asset.require("scene/solarsystem/planets/earth/moon/moon")
local mars = asset.require("scene/solarsystem/planets/mars/mars")
local EarthRadius = 6371000.0
local ArrowExample = {
Identifier = "RenderableNodeArrowExample",
Parent = earth.Earth.Identifier,
Renderable = {
Type = "RenderableNodeArrow",
StartNode = earth.Earth.Identifier,
EndNode = mars.Mars.Identifier,
Color = { 0.8, 1.0, 0.8 },
Offset = 2 * EarthRadius,
Length = 5 * EarthRadius,
Width = 900000.0
},
GUI = {
Name = "Node Arrow Example",
Path = "/Example",
Description = [[Example node direction arrow, using absolute sizes]]
}
}
-- Relative values: Multiplied with bounding sphere
local ArrowExample_RelativeUnits = {
Identifier = "RenderableNodeArrowExample_Relative",
Parent = earth.Earth.Identifier,
Renderable = {
Type = "RenderableNodeArrow",
StartNode = earth.Earth.Identifier,
EndNode = moon.Moon.Identifier,
Color = { 0.78, 0.0, 1.0 },
UseRelativeOffset = true,
UseRelativeLength = true,
Offset = 2.0,
Length = 5.0,
Width = 900000.0,
Invert = true -- Point to start node instead of end node
},
GUI = {
Name = "Node Arrow Example (relative units)",
Path = "/Example",
Description = [[Example node direction arrow, using relative sizes]]
}
}
asset.onInitialize(function()
openspace.addSceneGraphNode(ArrowExample)
openspace.addSceneGraphNode(ArrowExample_RelativeUnits)
end)
asset.onDeinitialize(function()
openspace.removeSceneGraphNode(ArrowExample_RelativeUnits)
openspace.removeSceneGraphNode(ArrowExample)
end)
asset.export(ArrowExample)
asset.export(ArrowExample_RelativeUnits)
asset.meta = {
Name = "RenderableNodeArrow Example asset",
Version = "1.0",
Description = [[Examples of the RenderableNodeArrow renderable, that can be used to draw
an arrow pointing from one scene graph node in the direction of another. Note that
the arrows are generated as objects in 3D space and need to have a size that is
suitable for their 3D context.]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -0,0 +1,43 @@
local earth = asset.require("scene/solarsystem/planets/earth/earth")
local mars = asset.require("scene/solarsystem/planets/mars/mars")
local RenderableNodeLineExample = {
Identifier = "RenderableNodeLineExample",
Renderable = {
Type = "RenderableNodeLine",
StartNode = earth.Earth.Identifier,
EndNode = mars.Mars.Identifier,
Color = { 0.5, 0.5, 0.5 },
LineWidth = 2
},
GUI = {
Name = "RenderableNodeLine Example",
Path = "/Example",
Description = [[Draws a line between two nodes in the scene.]]
}
}
asset.onInitialize(function()
openspace.addSceneGraphNode(RenderableNodeLineExample)
end)
asset.onDeinitialize(function()
openspace.removeSceneGraphNode(RenderableNodeLineExample)
end)
asset.export(RenderableNodeLineExample)
asset.meta = {
Name = "RenderableNodeLine Example asset",
Version = "1.0",
Description = [[Example of a RenderableNodeLine, that can be used to draw an line
between two scene graph nodes.]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -89,6 +89,22 @@ struct VertexObjects {
int nElements = 64;
} sphere;
struct {
GLuint vao = 0;
GLuint vbo = 0;
GLuint ibo = 0;
int nElements = 64;
} cylinder;
struct {
GLuint vao = 0;
GLuint vbo = 0;
GLuint ibo = 0;
int nElements = 64;
} cone;
struct {
GLuint vao = 0;
} empty;
@@ -119,6 +135,12 @@ struct VertexXYZNormal {
GLfloat normal[3];
};
template <typename V>
struct VertexIndexListCombo {
std::vector<V> vertices;
std::vector<GLushort> indices;
};
VertexXYZ convertToXYZ(const Vertex& v);
std::vector<VertexXYZ> convert(std::vector<Vertex> v);
@@ -126,9 +148,17 @@ std::vector<VertexXYZ> convert(std::vector<Vertex> v);
std::vector<Vertex> createRing(int nSegments, float radius,
glm::vec4 colors = glm::vec4(1.f));
std::pair<std::vector<Vertex>, std::vector<GLushort>>
std::vector<VertexXYZ> createRingXYZ(int nSegments, float radius);
VertexIndexListCombo<Vertex>
createSphere(int nSegments, glm::vec3 radii, glm::vec4 colors = glm::vec4(1.f));
VertexIndexListCombo<VertexXYZNormal> createCylinder(unsigned int nSegments,
float radius, float height);
VertexIndexListCombo<VertexXYZNormal> createCone(unsigned int nSegments, float radius,
float height);
/**
* Data structure that can be used for rendering using multiple light directions
*/

View File

@@ -48,6 +48,7 @@ set(HEADER_FILES
rendering/renderabledisc.h
rendering/renderablelabel.h
rendering/renderablemodel.h
rendering/renderablenodearrow.h
rendering/renderablenodeline.h
rendering/renderableplane.h
rendering/renderableplaneimagelocal.h
@@ -106,6 +107,7 @@ set(SOURCE_FILES
rendering/renderabledisc.cpp
rendering/renderablelabel.cpp
rendering/renderablemodel.cpp
rendering/renderablenodearrow.cpp
rendering/renderablenodeline.cpp
rendering/renderableplane.cpp
rendering/renderableplaneimagelocal.cpp
@@ -142,6 +144,8 @@ set(SOURCE_FILES
source_group("Source Files" FILES ${SOURCE_FILES})
set(SHADER_FILES
shaders/arrow_fs.glsl
shaders/arrow_vs.glsl
shaders/axes_fs.glsl
shaders/axes_vs.glsl
shaders/disc_fs.glsl

View File

@@ -47,6 +47,7 @@
#include <modules/base/rendering/renderabledisc.h>
#include <modules/base/rendering/renderablelabel.h>
#include <modules/base/rendering/renderablemodel.h>
#include <modules/base/rendering/renderablenodearrow.h>
#include <modules/base/rendering/renderablenodeline.h>
#include <modules/base/rendering/renderablesphereimagelocal.h>
#include <modules/base/rendering/renderablesphereimageonline.h>
@@ -136,6 +137,7 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) {
fRenderable->registerClass<RenderableGrid>("RenderableGrid");
fRenderable->registerClass<RenderableLabel>("RenderableLabel");
fRenderable->registerClass<RenderableModel>("RenderableModel");
fRenderable->registerClass<RenderableNodeArrow>("RenderableNodeArrow");
fRenderable->registerClass<RenderableNodeLine>("RenderableNodeLine");
fRenderable->registerClass<RenderablePlaneImageLocal>("RenderablePlaneImageLocal");
fRenderable->registerClass<RenderablePlaneImageOnline>("RenderablePlaneImageOnline");
@@ -222,6 +224,7 @@ std::vector<documentation::Documentation> BaseModule::documentations() const {
RenderableGrid::Documentation(),
RenderableLabel::Documentation(),
RenderableModel::Documentation(),
RenderableNodeArrow::Documentation(),
RenderableNodeLine::Documentation(),
RenderablePlane::Documentation(),
RenderablePlaneImageLocal::Documentation(),

View File

@@ -0,0 +1,515 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2023 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include <modules/base/rendering/renderablenodearrow.h>
#include <modules/base/basemodule.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/navigation/navigationhandler.h>
#include <openspace/navigation/orbitalnavigator.h>
#include <openspace/query/query.h>
#include <openspace/rendering/helper.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scene.h>
#include <openspace/scene/translation.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/opengl/openglstatecache.h>
#include <ghoul/opengl/programobject.h>
#include <glm/gtx/projection.hpp>
#include <glm/gtx/transform.hpp>
namespace {
constexpr std::string_view _loggerCat = "RenderableNodeArrow";
constexpr openspace::properties::Property::PropertyInfo StartNodeInfo = {
"StartNode",
"Start Node",
"The identifier of the node the arrow starts from",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo EndNodeInfo = {
"EndNode",
"End Node",
"The identifier of the node the arrow should point towards",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
"Color",
"Color",
"This value determines the RGB color for the arrow",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo SegmentsInfo = {
"Segments",
"Number of Segments",
"This value specifies the number of segments that the shapes for the arrow are "
"separated in. A higher number leads to a higher resolution",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo InvertInfo = {
"Invert",
"Invert Direction",
"If set to true, the arrow direction is inverted so that it points to the "
"start node instead of the end node",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo ArrowHeadSizeInfo = {
"ArrowHeadSize",
"Arrow Head Size",
"This size of the arrow head, given in relative value of the entire length of "
"the arrow. For example, 0.1 makes the arrow head length be 10% of the full "
"arrow length",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo ArrowHeadWidthInfo = {
"ArrowHeadWidthFactor",
"Arrow Head Width Factor",
"A factor that is multiplied with the width, or the arrow itself, to determine "
"the width of the base of the arrow head",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo OffsetDistanceInfo = {
"Offset",
"Offset Distance",
"The distance from the center of the start node where the arrow starts. "
"If 'UseRelativeOffset' is true, the value should be given as a factor to "
"multiply with the bounding sphere of the node. Otherwise, the value is "
"specified in meters",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo RelativeOffsetInfo = {
"UseRelativeOffset",
"Use Relative Offset Distance",
"Decide whether to use relative distances (in units of start node bounding "
"sphere) for the offset distance. If false, meters is used",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo LengthInfo = {
"Length",
"Length",
"The length of the arrow, given either in meters or as a factor to be "
"multiplied with the bounding sphere of the start node (if "
"'UseRelativeLength' is true)",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo RelativeLengthInfo = {
"UseRelativeLength",
"Use Relative Length",
"Decide whether to use relative size (in units of start node bounding "
"sphere) for the length of the arrow. If false, meters is used",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo WidthInfo = {
"Width",
"Width",
"This value specifies the width of the arrow shape",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo AmbientIntensityInfo = {
"AmbientIntensity",
"Ambient Intensity",
"A multiplier for ambient lighting for the shading of the arrow",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo DiffuseIntensityInfo = {
"DiffuseIntensity",
"Diffuse Intensity",
"A multiplier for diffuse lighting for the shading of the arrow",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo SpecularIntensityInfo = {
"SpecularIntensity",
"Specular Intensity",
"A multiplier for specular lighting for the shading of the arrow",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ShadingEnabledInfo = {
"PerformShading",
"Perform Shading",
"This value determines whether shading should be applied to the arrow model",
openspace::properties::Property::Visibility::User
};
struct [[codegen::Dictionary(RenderableNodeArrow)]] Parameters {
// [[codegen::verbatim(StartNodeInfo.description)]]
std::string startNode [[codegen::notempty]];
// [[codegen::verbatim(EndNodeInfo.description)]]
std::string endNode [[codegen::notempty]];
// [[codegen::verbatim(ColorInfo.description)]]
std::optional<glm::vec3> color [[codegen::color()]];
// [[codegen::verbatim(SegmentsInfo.description)]]
std::optional<int> segments [[codegen::greaterequal(3)]];
// [[codegen::verbatim(InvertInfo.description)]]
std::optional<bool> invert;
// [[codegen::verbatim(ArrowHeadSizeInfo.description)]]
std::optional<float> arrowHeadSize [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(ArrowHeadWidthInfo.description)]]
std::optional<float> arrowHeadWidthFactor [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(OffsetDistanceInfo.description)]]
std::optional<float> offset;
// [[codegen::verbatim(RelativeOffsetInfo.description)]]
std::optional<bool> useRelativeOffset;
// [[codegen::verbatim(LengthInfo.description)]]
std::optional<float> length [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(RelativeLengthInfo.description)]]
std::optional<bool> useRelativeLength;
// [[codegen::verbatim(WidthInfo.description)]]
std::optional<float> width [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(ShadingEnabledInfo.description)]]
std::optional<float> performShading;
// [[codegen::verbatim(AmbientIntensityInfo.description)]]
std::optional<float> ambientIntensity [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(DiffuseIntensityInfo.description)]]
std::optional<float> diffuseIntensity [[codegen::greaterequal(0.f)]];
// [[codegen::verbatim(SpecularIntensityInfo.description)]]
std::optional<float> specularIntensity [[codegen::greaterequal(0.f)]];
};
#include "renderablenodearrow_codegen.cpp"
void updateDistanceBasedOnRelativeValues(const std::string& nodeName,
bool useRelative,
openspace::properties::FloatProperty& prop)
{
using namespace::openspace;
SceneGraphNode* startNode = sceneGraphNode(nodeName);
if (!startNode) {
LERROR(fmt::format("Could not find start node '{}'", nodeName));
return;
}
const double boundingSphere = startNode->boundingSphere();
if (!useRelative) {
// Recompute distance (previous value was relative)
prop = static_cast<float>(prop * boundingSphere);
prop.setExponent(11.f);
prop.setMaxValue(1e20f);
}
else {
// Recompute distance (previous value was in meters)
if (boundingSphere < std::numeric_limits<double>::epsilon()) {
LERROR(fmt::format(
"Start node '{}' has invalid bounding sphere", nodeName
));
return;
}
prop = static_cast<float>(prop / boundingSphere);
prop.setExponent(3.f);
prop.setMaxValue(1000.f);
}
// @TODO (emmbr, 2022-08-22): make GUI update when min/max value is updated
};
} // namespace
namespace openspace {
documentation::Documentation RenderableNodeArrow::Documentation() {
return codegen::doc<Parameters>("base_renderable_renderablenodearrow");
}
RenderableNodeArrow::Shading::Shading()
: properties::PropertyOwner({ "Shading" })
, enabled(ShadingEnabledInfo, true)
, ambientIntensity(AmbientIntensityInfo, 0.2f, 0.f, 1.f)
, diffuseIntensity(DiffuseIntensityInfo, 0.7f, 0.f, 1.f)
, specularIntensity(SpecularIntensityInfo, 0.f, 0.f, 1.f)
{
addProperty(enabled);
addProperty(ambientIntensity);
addProperty(diffuseIntensity);
addProperty(specularIntensity);
}
RenderableNodeArrow::RenderableNodeArrow(const ghoul::Dictionary& dictionary)
: Renderable(dictionary)
, _start(StartNodeInfo)
, _end(EndNodeInfo)
, _color(ColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
, _segments(SegmentsInfo, 10, 3, 100)
, _invertArrowDirection(InvertInfo, false)
, _arrowHeadSize(ArrowHeadSizeInfo, 0.1f, 0.f, 1.f)
, _arrowHeadWidthFactor(ArrowHeadWidthInfo, 2.f, 1.f, 100.f)
, _offsetDistance(OffsetDistanceInfo, 0.f, 0.f, 1e20f)
, _useRelativeOffset(RelativeOffsetInfo, false)
, _length(LengthInfo, 100.f, 0.f, 1e20f)
, _useRelativeLength(RelativeLengthInfo, false)
, _width(WidthInfo, 10.f, 0.f, 1e11f)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_shading.enabled = p.performShading.value_or(_shading.enabled);
_shading.ambientIntensity = p.ambientIntensity.value_or(_shading.ambientIntensity);
_shading.diffuseIntensity = p.diffuseIntensity.value_or(_shading.diffuseIntensity);
_shading.specularIntensity = p.specularIntensity.value_or(_shading.specularIntensity);
addPropertySubOwner(_shading);
addProperty(Fadeable::_opacity);
_color = p.color.value_or(_color);
_color.setViewOption(properties::Property::ViewOptions::Color);
addProperty(_color);
_start = p.startNode;
addProperty(_start);
_end = p.endNode;
addProperty(_end);
_segments = p.segments.value_or(_segments);
addProperty(_segments);
_invertArrowDirection = p.invert.value_or(_invertArrowDirection);
addProperty(_invertArrowDirection);
_arrowHeadSize = p.arrowHeadSize.value_or(_arrowHeadSize);
addProperty(_arrowHeadSize);
_arrowHeadWidthFactor = p.arrowHeadWidthFactor.value_or(_arrowHeadWidthFactor);
addProperty(_arrowHeadWidthFactor);
_width = p.width.value_or(_width);
_width.setExponent(10.f);
addProperty(_width);
_useRelativeLength.onChange([this]() {
updateDistanceBasedOnRelativeValues(_start, _useRelativeLength, _length);
});
_useRelativeLength = p.useRelativeLength.value_or(_useRelativeLength);
_length = p.length.value_or(_length);
if (!_useRelativeLength) {
_length.setExponent(11.f);
}
addProperty(_length);
_useRelativeOffset.onChange([this]() {
updateDistanceBasedOnRelativeValues(_start, _useRelativeOffset, _offsetDistance);
});
_useRelativeOffset = p.useRelativeOffset.value_or(_useRelativeOffset);
_offsetDistance = p.offset.value_or(_offsetDistance);
if (!_useRelativeOffset) {
_offsetDistance.setExponent(11.f);
}
addProperty(_offsetDistance);
addProperty(_useRelativeLength);
addProperty(_useRelativeOffset);
}
void RenderableNodeArrow::initializeGL() {
_shaderProgram = BaseModule::ProgramObjectManager.request(
"NodeDirectionLineProgram",
[]() -> std::unique_ptr<ghoul::opengl::ProgramObject> {
return global::renderEngine->buildRenderProgram(
"NodeDirectionLineProgram",
absPath("${MODULE_BASE}/shaders/arrow_vs.glsl"),
absPath("${MODULE_BASE}/shaders/arrow_fs.glsl")
);
}
);
}
void RenderableNodeArrow::deinitializeGL() {
BaseModule::ProgramObjectManager.release(
"NodeDirectionLineProgram",
[](ghoul::opengl::ProgramObject* p) {
global::renderEngine->removeRenderProgram(p);
}
);
_shaderProgram = nullptr;
}
bool RenderableNodeArrow::isReady() const {
return _shaderProgram;
}
void RenderableNodeArrow::updateShapeTransforms(const RenderData& data) {
SceneGraphNode* startNode = sceneGraphNode(_start);
SceneGraphNode* endNode = sceneGraphNode(_end);
if (!startNode) {
LERROR(fmt::format("Could not find start node '{}'", _start.value()));
return;
}
if (!endNode) {
LERROR(fmt::format("Could not find end node '{}'", _end.value()));
return;
}
const double boundingSphere = startNode->boundingSphere();
bool hasNoBoundingSphere = boundingSphere < std::numeric_limits<double>::epsilon();
if (hasNoBoundingSphere && (_useRelativeLength || _useRelativeOffset)) {
LERROR(fmt::format(
"Node '{}' has no valid bounding sphere. Can not use relative values",
_end.value()
));
return;
}
double offset = static_cast<double>(_offsetDistance);
if (_useRelativeOffset) {
offset *= boundingSphere;
}
double length = static_cast<double>(_length);
if (_useRelativeLength) {
length *= boundingSphere;
}
// Take additional transformation scale into account
const glm::dmat4 s = glm::scale(
glm::dmat4(1.0),
glm::dvec3(data.modelTransform.scale)
);
// Update the position based on the arrowDirection of the nodes
glm::dvec3 startNodePos = startNode->worldPosition();
glm::dvec3 endNodePos = endNode->worldPosition();
glm::dvec3 arrowDirection = glm::normalize(endNodePos - startNodePos);
glm::dvec3 startPos = glm::dvec3(startNodePos + offset * arrowDirection);
glm::dvec3 endPos = glm::dvec3(startPos + length * arrowDirection);
if (_invertArrowDirection) {
std::swap(startPos, endPos);
arrowDirection *= -1.0;
}
double coneLength = _arrowHeadSize * length;
double cylinderLength = length - coneLength;
double arrowHeadWidth = _width * _arrowHeadWidthFactor;
// Create transformation matrices to reshape to size and position
_cylinderTranslation = glm::translate(glm::dmat4(1.0), startPos);
glm::dvec3 cylinderScale = glm::dvec3(s * glm::dvec4(_width, _width, cylinderLength, 0.0));
_cylinderScale = glm::scale(glm::dmat4(1.0), cylinderScale);
// Adapt arrow head start to scaled size
glm::dvec3 arrowHeadStartPos = startPos + cylinderScale.z * arrowDirection;
_coneTranslation = glm::translate(glm::dmat4(1.0), arrowHeadStartPos);
glm::dvec3 coneScale = glm::dvec3(arrowHeadWidth, arrowHeadWidth, coneLength);
_coneScale = s * glm::scale(glm::dmat4(1.0), coneScale);
// Rotation to point at the end node
glm::quat rotQuat = glm::rotation(glm::dvec3(0.0, 0.0, 1.0), arrowDirection);
_pointDirectionRotation = glm::dmat4(glm::toMat4(rotQuat));
}
void RenderableNodeArrow::render(const RenderData& data, RendererTasks&) {
updateShapeTransforms(data);
// Cylinder transforms
glm::dmat4 modelTransform =
_cylinderTranslation * _pointDirectionRotation * _cylinderScale;
glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform;
glm::dmat4 normalTransform = glm::transpose(glm::inverse(modelViewTransform));
_shaderProgram->activate();
_shaderProgram->setUniform("modelViewTransform", glm::mat4(modelViewTransform));
_shaderProgram->setUniform("projectionTransform", data.camera.projectionMatrix());
_shaderProgram->setUniform("normalTransform", glm::mat3(normalTransform));
_shaderProgram->setUniform("color", _color);
_shaderProgram->setUniform("opacity", opacity());
_shaderProgram->setUniform("ambientIntensity", _shading.ambientIntensity);
_shaderProgram->setUniform("diffuseIntensity", _shading.diffuseIntensity);
_shaderProgram->setUniform("specularIntensity", _shading.specularIntensity);
_shaderProgram->setUniform("performShading", _shading.enabled);
// Change GL state:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnablei(GL_BLEND, 0);
// Draw cylinder
glBindVertexArray(rendering::helper::vertexObjects.cylinder.vao);
glDrawElements(
GL_TRIANGLES,
rendering::helper::vertexObjects.cylinder.nElements,
GL_UNSIGNED_SHORT,
nullptr
);
// Update transforms and render cone
modelTransform = _coneTranslation * _pointDirectionRotation * _coneScale;
modelViewTransform = data.camera.combinedViewMatrix() * modelTransform;
normalTransform = glm::transpose(glm::inverse(modelViewTransform));
_shaderProgram->setUniform("modelViewTransform", glm::mat4(modelViewTransform));
_shaderProgram->setUniform("normalTransform", glm::mat3(normalTransform));
glBindVertexArray(rendering::helper::vertexObjects.cone.vao);
glDrawElements(
GL_TRIANGLES,
rendering::helper::vertexObjects.cone.nElements,
GL_UNSIGNED_SHORT,
nullptr
);
// Restore GL State
glBindVertexArray(0);
global::renderEngine->openglStateCache().resetBlendState();
_shaderProgram->deactivate();
}
} // namespace openspace

View File

@@ -0,0 +1,103 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2023 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_BASE___RENDERABLENODEARROW___H__
#define __OPENSPACE_MODULE_BASE___RENDERABLENODEARROW___H__
#include <openspace/rendering/renderable.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/scalar/uintproperty.h>
#include <openspace/properties/vector/vec3property.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <ghoul/glm.h>
namespace ghoul::opengl { class ProgramObject; }
namespace openspace {
namespace documentation { struct Documentation; }
class Translation;
/**
* Generates an arrow shape that points from the start node to the
* end node
*/
class RenderableNodeArrow : public Renderable {
public:
RenderableNodeArrow(const ghoul::Dictionary& dictionary);
~RenderableNodeArrow() override = default;
void initializeGL() override;
void deinitializeGL() override;
bool isReady() const override;
void render(const RenderData& data, RendererTasks& rendererTask) override;
static documentation::Documentation Documentation();
private:
struct Shading : properties::PropertyOwner {
Shading();
properties::BoolProperty enabled;
properties::FloatProperty ambientIntensity;
properties::FloatProperty diffuseIntensity;
properties::FloatProperty specularIntensity;
};
void updateShapeTransforms(const RenderData& data);
Shading _shading;
ghoul::opengl::ProgramObject* _shaderProgram;
properties::StringProperty _start;
properties::StringProperty _end;
properties::Vec3Property _color;
properties::UIntProperty _segments;
properties::BoolProperty _invertArrowDirection;
properties::FloatProperty _arrowHeadSize;
properties::FloatProperty _arrowHeadWidthFactor;
properties::FloatProperty _offsetDistance;
properties::BoolProperty _useRelativeOffset;
properties::FloatProperty _length;
properties::BoolProperty _useRelativeLength;
properties::FloatProperty _width;
glm::dmat4 _cylinderTranslation = glm::dmat4(1.0);
glm::dmat4 _cylinderScale = glm::dmat4(1.0);
glm::dmat4 _coneTranslation = glm::dmat4(1.0);
glm::dmat4 _coneScale = glm::dmat4(1.0);
glm::dmat4 _pointDirectionRotation = glm::dmat4(1.0);
};
} // namespace openspace
#endif // __OPENSPACE_MODULE_BASE___RENDERABLENODEARROW___H__

View File

@@ -27,6 +27,7 @@
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <openspace/engine/globals.h>
#include <openspace/rendering/helper.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/filesystem/filesystem.h>
@@ -98,20 +99,6 @@ namespace {
openspace::properties::Property::Visibility::User
};
// Generate vertices around the unit circle on the XY-plane
std::vector<float> unitCircleVertices(int sectorCount) {
std::vector<float> vertices;
vertices.reserve(2 * sectorCount);
float sectorStep = glm::two_pi<float>() / sectorCount;
for (int i = 0; i < sectorCount; ++i) {
float sectorAngle = i * sectorStep;
vertices.push_back(cos(sectorAngle)); // x
vertices.push_back(sin(sectorAngle)); // y
}
return vertices;
}
struct [[codegen::Dictionary(RenderablePrism)]] Parameters {
// [[codegen::verbatim(SegmentsInfo.description)]]
int segments;
@@ -236,17 +223,16 @@ void RenderablePrism::updateVertexData() {
_vertexArray.clear();
_indexArray.clear();
using namespace rendering::helper;
// Get unit circle vertices on the XY-plane
std::vector<float> unitVertices = unitCircleVertices(_nShapeSegments);
std::vector<float> unitVerticesLines = unitCircleVertices(_nLines);
std::vector<VertexXYZ> unitVertices = createRingXYZ(_nShapeSegments.value(), 1.f);
std::vector<VertexXYZ> unitVerticesLines = createRingXYZ(_nLines.value(), 1.f);
// Put base vertices into array
for (int j = 0, k = 0;
j < _nShapeSegments && k < static_cast<int>(unitVertices.size());
++j, k += 2)
{
float ux = unitVertices[k];
float uy = unitVertices[k + 1];
for (int j = 0; j < _nShapeSegments; ++j) {
float ux = unitVertices[j].xyz[0];
float uy = unitVertices[j].xyz[1];
_vertexArray.push_back(ux * _baseRadius); // x
_vertexArray.push_back(uy * _baseRadius); // y
@@ -254,12 +240,9 @@ void RenderablePrism::updateVertexData() {
}
// Put top shape vertices into array
for (int j = 0, k = 0;
j < _nShapeSegments && k < static_cast<int>(unitVertices.size());
++j, k += 2)
{
float ux = unitVertices[k];
float uy = unitVertices[k + 1];
for (int j = 0; j < _nShapeSegments; ++j) {
float ux = unitVertices[j].xyz[0];
float uy = unitVertices[j].xyz[1];
_vertexArray.push_back(ux * _radius); // x
_vertexArray.push_back(uy * _radius); // y
@@ -280,12 +263,9 @@ void RenderablePrism::updateVertexData() {
_vertexArray.push_back(_length);
}
else {
for (int j = 0, k = 0;
j < _nLines && k < static_cast<int>(unitVerticesLines.size());
++j, k += 2)
{
float ux = unitVerticesLines[k];
float uy = unitVerticesLines[k + 1];
for (int j = 0; j < _nLines; ++j) {
float ux = unitVerticesLines[j].xyz[0];
float uy = unitVerticesLines[j].xyz[1];
// Base
_vertexArray.push_back(ux * _baseRadius); // x

View File

@@ -0,0 +1,75 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2023 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#include "fragment.glsl"
in float vs_depth;
in vec3 vs_normal;
in vec4 vs_positionViewSpace;
uniform vec3 color;
uniform float opacity;
uniform float ambientIntensity = 0.2;
uniform float diffuseIntensity = 1.0;
uniform float specularIntensity = 1.0;
uniform bool performShading = true;
const vec3 LightColor = vec3(1.0);
Fragment getFragment() {
Fragment frag;
frag.color = vec4(color, opacity);
// Simple phong shading (same color for diffuse and ambient. White specular)
if (performShading) {
const vec3 lightDirectionViewSpace = vec3(0.0, 0.0, 1.0);
const float lightIntensity = 1.0;
const float specularPower = 100.0;
// Ambient color
vec3 shadedColor = ambientIntensity * color;
// Diffuse
vec3 n = normalize(vs_normal);
vec3 l = lightDirectionViewSpace;
shadedColor += diffuseIntensity * max(dot(n,l), 0.0) * color;
// Specular
vec3 viewDir = normalize(vs_positionViewSpace.xyz);
vec3 reflectDir = reflect(l, n);
shadedColor +=
specularIntensity * pow(max(dot(viewDir,reflectDir), 0), specularPower) * color;
// Light contribution (one light soruce)
shadedColor *= lightIntensity * LightColor;
frag.color.xyz = shadedColor;
}
frag.depth = vs_depth;
frag.gPosition = vs_positionViewSpace;
frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0);
return frag;
}

View File

@@ -0,0 +1,47 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2023 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#version __CONTEXT__
layout(location = 0) in vec3 in_position;
layout(location = 1) in vec3 in_normal;
out float vs_depth;
out vec3 vs_normal;
out vec4 vs_positionViewSpace;
uniform mat4 modelViewTransform;
uniform mat4 projectionTransform;
uniform mat3 normalTransform;
void main() {
vs_positionViewSpace = vec4(modelViewTransform * dvec4(in_position, 1));
vec4 positionScreenSpace = projectionTransform * vs_positionViewSpace;
vs_depth = positionScreenSpace.w;
vs_normal = normalize(normalTransform * in_normal);
gl_Position = positionScreenSpace;
// Set z to 0 to disable near and far plane, unique handling for perspective in space
gl_Position.z = 0.f;
}

View File

@@ -33,6 +33,7 @@
#include <ghoul/opengl/ghoul_gl.h>
#include <ghoul/opengl/texture.h>
#include <ghoul/opengl/textureunit.h>
#include <glm/gtx/closest_point.hpp>
#include <algorithm>
#include <filesystem>
#include <fstream>
@@ -246,10 +247,11 @@ void initialize() {
reinterpret_cast<GLvoid*>(offsetof(VertexXYUVRGBA, rgba)));
glBindVertexArray(0);
//
// Sphere vertex array object
//
std::pair<std::vector<Vertex>, std::vector<GLushort>> sphereData = createSphere(
VertexIndexListCombo<Vertex> sphereData = createSphere(
64, glm::vec3(1.f, 1.f, 1.f), glm::vec4(1.f, 1.f, 1.f, 1.f)
);
@@ -261,16 +263,16 @@ void initialize() {
glBindBuffer(GL_ARRAY_BUFFER, vertexObjects.sphere.vbo);
glBufferData(
GL_ARRAY_BUFFER,
sphereData.first.size() * sizeof(Vertex),
sphereData.first.data(),
sphereData.vertices.size() * sizeof(Vertex),
sphereData.vertices.data(),
GL_STATIC_DRAW
);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexObjects.sphere.ibo);
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
sphereData.second.size() * sizeof(GLushort),
sphereData.second.data(),
sphereData.indices.size() * sizeof(GLushort),
sphereData.indices.data(),
GL_STATIC_DRAW
);
glEnableVertexAttribArray(0);
@@ -282,7 +284,75 @@ void initialize() {
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
reinterpret_cast<GLvoid*>(offsetof(Vertex, rgba)));
glBindVertexArray(0);
vertexObjects.sphere.nElements = static_cast<int>(sphereData.second.size());
vertexObjects.sphere.nElements = static_cast<int>(sphereData.indices.size());
//
// Cylinder vertex array object
//
VertexIndexListCombo<VertexXYZNormal> cylinderData = createCylinder(64, 1.f, 1.f);
glGenVertexArrays(1, &vertexObjects.cylinder.vao);
glGenBuffers(1, &vertexObjects.cylinder.vbo);
glGenBuffers(1, &vertexObjects.cylinder.ibo);
glBindVertexArray(vertexObjects.cylinder.vao);
glBindBuffer(GL_ARRAY_BUFFER, vertexObjects.cylinder.vbo);
glBufferData(
GL_ARRAY_BUFFER,
cylinderData.vertices.size() * sizeof(VertexXYZNormal),
cylinderData.vertices.data(),
GL_STATIC_DRAW
);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexObjects.cylinder.ibo);
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
cylinderData.indices.size() * sizeof(GLushort),
cylinderData.indices.data(),
GL_STATIC_DRAW
);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexXYZNormal), nullptr);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexXYZNormal),
reinterpret_cast<GLvoid*>(offsetof(VertexXYZNormal, normal)));
glBindVertexArray(0);
vertexObjects.cylinder.nElements = static_cast<int>(cylinderData.indices.size());
//
// Cone vertex array object
//
VertexIndexListCombo<VertexXYZNormal> coneData = createCone(64, 1.f, 1.f);
glGenVertexArrays(1, &vertexObjects.cone.vao);
glGenBuffers(1, &vertexObjects.cone.vbo);
glGenBuffers(1, &vertexObjects.cone.ibo);
glBindVertexArray(vertexObjects.cone.vao);
glBindBuffer(GL_ARRAY_BUFFER, vertexObjects.cone.vbo);
glBufferData(
GL_ARRAY_BUFFER,
coneData.vertices.size() * sizeof(VertexXYZNormal),
coneData.vertices.data(),
GL_STATIC_DRAW
);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertexObjects.cone.ibo);
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
coneData.indices.size() * sizeof(GLushort),
coneData.indices.data(),
GL_STATIC_DRAW
);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(VertexXYZNormal), nullptr);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(VertexXYZNormal),
reinterpret_cast<GLvoid*>(offsetof(VertexXYZNormal, normal)));
glBindVertexArray(0);
vertexObjects.cone.nElements = static_cast<int>(coneData.indices.size());
//
@@ -320,6 +390,14 @@ void deinitialize() {
glDeleteBuffers(1, &vertexObjects.sphere.vbo);
glDeleteBuffers(1, &vertexObjects.sphere.ibo);
glDeleteVertexArrays(1, &vertexObjects.cylinder.vao);
glDeleteBuffers(1, &vertexObjects.cylinder.vbo);
glDeleteBuffers(1, &vertexObjects.cylinder.ibo);
glDeleteVertexArrays(1, &vertexObjects.cone.vao);
glDeleteBuffers(1, &vertexObjects.cone.vbo);
glDeleteBuffers(1, &vertexObjects.cone.ibo);
glDeleteVertexArrays(1, &vertexObjects.empty.vao);
isInitialized = false;
@@ -423,32 +501,48 @@ std::vector<VertexXYZ> convert(std::vector<Vertex> v) {
return result;
}
Vertex computeCircleVertex(int i, int nSegments, float radius,
glm::vec4 color = glm::vec4(1.f))
{
const float fsegments = static_cast<float>(nSegments);
const float fi = static_cast<float>(i);
const float theta = fi * glm::two_pi<float>() / fsegments; // 0 -> 2*PI
const float x = radius * std::cos(theta);
const float y = radius * std::sin(theta);
const float z = 0.f;
const float u = std::cos(theta);
const float v = std::sin(theta);
return { x, y, z, u, v, color.r, color.g, color.b, color.a };
}
std::vector<Vertex> createRing(int nSegments, float radius, glm::vec4 colors) {
const int nVertices = nSegments + 1;
std::vector<Vertex> vertices(nVertices);
const float fsegments = static_cast<float>(nSegments);
for (int i = 0; i <= nSegments; ++i) {
const float fi = static_cast<float>(i);
const float theta = fi * glm::pi<float>() * 2.f / fsegments; // 0 -> 2*PI
const float x = radius * std::cos(theta);
const float y = radius * std::sin(theta);
const float z = 0.f;
const float u = std::cos(theta);
const float v = std::sin(theta);
vertices[i] = { x, y, z, u, v, colors.r, colors.g, colors.b, colors.a };
vertices[i] = computeCircleVertex(i, nSegments, radius, colors);
}
return vertices;
}
std::pair<std::vector<Vertex>, std::vector<GLushort>> createSphere(int nSegments,
glm::vec3 radii,
glm::vec4 colors)
std::vector<VertexXYZ> createRingXYZ(int nSegments, float radius) {
const int nVertices = nSegments + 1;
std::vector<VertexXYZ> vertices(nVertices);
for (int i = 0; i <= nSegments; ++i) {
Vertex fullVertex = computeCircleVertex(i, nSegments, radius);
vertices[i] = { fullVertex.xyz[0], fullVertex.xyz[1], fullVertex.xyz[2] };
}
return vertices;
}
VertexIndexListCombo<Vertex> createSphere(int nSegments, glm::vec3 radii,
glm::vec4 colors)
{
std::vector<Vertex> vertices;
vertices.reserve(nSegments * nSegments);
@@ -505,6 +599,153 @@ std::pair<std::vector<Vertex>, std::vector<GLushort>> createSphere(int nSegments
return { vertices, indices };
}
VertexIndexListCombo<VertexXYZNormal> createConicalCylinder(unsigned int nSegments,
float bottomRadius,
float topRadius,
float height)
{
// Create a ring for the top and bottom vertices (XY plane)
std::vector<VertexXYZ> bottomVertices = createRingXYZ(nSegments, bottomRadius);
std::vector<VertexXYZ> topVertices = createRingXYZ(nSegments, topRadius);
// Build the 4 rings of vertices (with different normals), that will make up the
// shape for the cylinder
std::vector<VertexXYZNormal> vertices;
vertices.reserve(4 * bottomVertices.size() + 2);
// Center bottom vertex
vertices.push_back({
.xyz = { 0.f, 0.f, 0.f },
.normal = { 0.f, 0.f, -1.f }
});
std::vector<VertexXYZNormal> verts0;
verts0.reserve(bottomVertices.size());
std::vector<VertexXYZNormal> verts1;
verts1.reserve(bottomVertices.size());
std::vector<VertexXYZNormal> verts2;
verts2.reserve(bottomVertices.size());
std::vector<VertexXYZNormal> verts3;
verts3.reserve(bottomVertices.size());
for (size_t i = 0; i < bottomVertices.size(); i++) {
const VertexXYZ& vBot = bottomVertices[i];
VertexXYZ& vTop = topVertices[i];
vTop.xyz[2] += height;
glm::vec3 sideNormal;
if (std::abs(bottomRadius - topRadius) < std::numeric_limits<float>::epsilon()) {
sideNormal = glm::normalize(
glm::vec3(vBot.xyz[0], vBot.xyz[1], vBot.xyz[2])
);
}
else {
glm::vec3 p = glm::closestPointOnLine(
glm::vec3(0.f),
glm::vec3(vBot.xyz[0], vBot.xyz[1], vBot.xyz[2]),
glm::vec3(vTop.xyz[0], vTop.xyz[1], vTop.xyz[2])
);
sideNormal = glm::normalize(p);
}
// Ring 0 - vertices of bottom circle, with normals pointing down
verts0.push_back({
.xyz = { vBot.xyz[0], vBot.xyz[1], vBot.xyz[2] },
.normal = { 0.f, 0.f, -1.f }
});
// Ring 1 - bottom vertices of cylider sides with normals pointing outwards
verts1.push_back({
.xyz = { vBot.xyz[0], vBot.xyz[1], vBot.xyz[2] },
.normal = { sideNormal.x, sideNormal.y, sideNormal.z }
});
// Ring 2 - top vertices of cylinder side, normals pointing outwards
// Note that only difference between top and bottom is the height added to Z
verts2.push_back({
.xyz = { vTop.xyz[0], vTop.xyz[1], vTop.xyz[2] },
.normal = { sideNormal.x, sideNormal.y, sideNormal.z }
});
// Ring 3 - vertices of top circle, normals pointing up
verts3.push_back({
.xyz = { vTop.xyz[0], vTop.xyz[1], vTop.xyz[2] },
.normal = { 0.f, 0.f, 1.f }
});
}
vertices.insert(vertices.end(), verts0.begin(), verts0.end());
vertices.insert(vertices.end(), verts1.begin(), verts1.end());
vertices.insert(vertices.end(), verts2.begin(), verts2.end());
vertices.insert(vertices.end(), verts3.begin(), verts3.end());
// Center top vertex
vertices.push_back({
.xyz = { 0.f, 0.f, height },
.normal = { 0.f, 0.f, 1.f }
});
// Contruct the index list, based on the above vertex rings
std::vector<GLushort> indexArray;
indexArray.reserve(4 * 3 * nSegments);
auto ringVerticeIndex = [&nSegments](unsigned int ringIndex, unsigned int i) {
return static_cast<GLushort>(1 + ringIndex * (nSegments + 1) + i);
};
GLushort botCenterIndex = 0;
GLushort topCenterIndex = static_cast<GLushort>(vertices.size()) - 1;
for (unsigned int i = 0; i < nSegments; ++i) {
bool isLast = (i == nSegments - 1);
GLushort v0, v1, v2, v3;
// Bottom triangle
v0 = ringVerticeIndex(0, i);
v1 = ringVerticeIndex(0, isLast ? 0 : i + 1);
indexArray.push_back(botCenterIndex);
indexArray.push_back(v1);
indexArray.push_back(v0);
// Side of cylinder
// Bottom ring
v0 = ringVerticeIndex(1, i);
v1 = ringVerticeIndex(1, isLast ? 0 : i + 1);
// Top ring
v2 = ringVerticeIndex(2, i);
v3 = ringVerticeIndex(2, isLast ? 0 : i + 1);
indexArray.push_back(v0);
indexArray.push_back(v1);
indexArray.push_back(v2);
indexArray.push_back(v1);
indexArray.push_back(v3);
indexArray.push_back(v2);
// Top triangle
v0 = ringVerticeIndex(3, i);
v1 = ringVerticeIndex(3, isLast ? 0 : i + 1);
indexArray.push_back(topCenterIndex);
indexArray.push_back(v0);
indexArray.push_back(v1);
}
return { vertices, indexArray };
}
VertexIndexListCombo<VertexXYZNormal> createCylinder(unsigned int nSegments,
float radius, float height)
{
return createConicalCylinder(nSegments, radius, radius, height);
}
VertexIndexListCombo<VertexXYZNormal> createCone(unsigned int nSegments, float radius,
float height)
{
return createConicalCylinder(nSegments, radius, 0.f, height);
}
void LightSourceRenderData::updateBasedOnLightSources(const RenderData& renderData,
const std::vector<std::unique_ptr<LightSource>>& sources)
{