diff --git a/modules/base/CMakeLists.txt b/modules/base/CMakeLists.txt index 47e5dc2172..386e50515f 100644 --- a/modules/base/CMakeLists.txt +++ b/modules/base/CMakeLists.txt @@ -41,6 +41,7 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/rendering/multimodelgeometry.h ${CMAKE_CURRENT_SOURCE_DIR}/rendering/grids/renderableboxgrid.h ${CMAKE_CURRENT_SOURCE_DIR}/rendering/grids/renderablegrid.h + ${CMAKE_CURRENT_SOURCE_DIR}/rendering/grids/renderableradialgrid.h ${CMAKE_CURRENT_SOURCE_DIR}/rendering/grids/renderablesphericalgrid.h ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablecartesianaxes.h ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablelabels.h @@ -92,6 +93,7 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/rendering/multimodelgeometry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rendering/grids/renderableboxgrid.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rendering/grids/renderablegrid.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rendering/grids/renderableradialgrid.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rendering/grids/renderablesphericalgrid.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablecartesianaxes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rendering/renderablelabels.cpp diff --git a/modules/base/basemodule.cpp b/modules/base/basemodule.cpp index da6d254ee2..830d528117 100644 --- a/modules/base/basemodule.cpp +++ b/modules/base/basemodule.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -132,6 +133,7 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) { fRenderable->registerClass("RenderableNodeLine"); fRenderable->registerClass("RenderablePlaneImageLocal"); fRenderable->registerClass("RenderablePlaneImageOnline"); + fRenderable->registerClass("RenderableRadialGrid"); fRenderable->registerClass("RenderableSphere"); fRenderable->registerClass("RenderableSphericalGrid"); fRenderable->registerClass("RenderableTrailOrbit"); @@ -202,6 +204,7 @@ std::vector BaseModule::documentations() const { RenderableModel::Documentation(), RenderableNodeLine::Documentation(), RenderablePlane::Documentation(), + RenderableRadialGrid::Documentation(), RenderableSphere::Documentation(), RenderableSphericalGrid::Documentation(), RenderableTrailOrbit::Documentation(), diff --git a/modules/base/rendering/grids/renderableradialgrid.cpp b/modules/base/rendering/grids/renderableradialgrid.cpp new file mode 100644 index 0000000000..2bb782c7b1 --- /dev/null +++ b/modules/base/rendering/grids/renderableradialgrid.cpp @@ -0,0 +1,444 @@ + +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + constexpr const char* ProgramName = "GridProgram"; + + constexpr openspace::properties::Property::PropertyInfo GridColorInfo = { + "GridColor", + "Grid Color", + "This value determines the color of the grid lines that are rendered." + }; + + constexpr openspace::properties::Property::PropertyInfo GridSegmentsInfo = { + "GridSegments", + "Number of Grid Segments", + "Specifies the number of segments for the grid, in the radial and angular " + " direction respectively" + }; + + constexpr openspace::properties::Property::PropertyInfo CircleSegmentsInfo = { + "CircleSegments", + "Number of Circle Segments", + "This value specifies the number of segments that is used to render each circle " + "in the grid" + }; + + constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = { + "LineWidth", + "Line Width", + "This value specifies the line width of the spherical grid." + }; + + constexpr openspace::properties::Property::PropertyInfo OuterRadiusInfo = { + "OuterRadius", + "Outer Radius", + "The outer radius of the circular grid, i.e. its size." + }; + + constexpr openspace::properties::Property::PropertyInfo InnerRadiusInfo = { + "InnerRadius", + "Inner Radius", + "The inner radius of the circular grid, that is the radius of the inmost ring. " + "Must be smaller than the outer radius." + }; + +} // namespace + +namespace openspace { + +documentation::Documentation RenderableRadialGrid::Documentation() { + using namespace documentation; + return { + "RenderableRadialGrid", + "base_renderable_radialgrid", + { + { + GridColorInfo.identifier, + new DoubleVector3Verifier, + Optional::Yes, + GridColorInfo.description + }, + { + GridSegmentsInfo.identifier, + new DoubleVector2Verifier, + Optional::Yes, + GridSegmentsInfo.description + }, + { + CircleSegmentsInfo.identifier, + new IntVerifier, + Optional::Yes, + CircleSegmentsInfo.description + }, + { + LineWidthInfo.identifier, + new DoubleVerifier, + Optional::Yes, + LineWidthInfo.description + }, + { + OuterRadiusInfo.identifier, + new DoubleVerifier, + Optional::Yes, + OuterRadiusInfo.description + }, + { + InnerRadiusInfo.identifier, + new DoubleVerifier, + Optional::Yes, + InnerRadiusInfo.description + } + } + }; +} + + +RenderableRadialGrid::RenderableRadialGrid(const ghoul::Dictionary& dictionary) + : Renderable(dictionary) + , _gridProgram(nullptr) + , _gridColor( + GridColorInfo, + glm::vec3(0.5f, 0.5, 0.5f), + glm::vec3(0.f), + glm::vec3(1.f) + ) + , _gridSegments( + GridSegmentsInfo, + glm::ivec2(1, 1), // TODO: better default + glm::ivec2(1), + glm::ivec2(200) + ) + , _circleSegments(CircleSegmentsInfo, 36, 4, 200) + , _lineWidth(LineWidthInfo, 0.5f, 0.f, 20.f) + , _maxRadius(OuterRadiusInfo, 1.f, 0.f, 20.f) + , _minRadius(InnerRadiusInfo, 0.f, 0.f, 20.f) +{ + documentation::testSpecificationAndThrow( + Documentation(), + dictionary, + "RenderableRadialGrid" + ); + + addProperty(_opacity); + registerUpdateRenderBinFromOpacity(); + + if (dictionary.hasKey(GridColorInfo.identifier)) { + _gridColor = dictionary.value(GridColorInfo.identifier); + } + _gridColor.setViewOption(properties::Property::ViewOptions::Color); + addProperty(_gridColor); + + if (dictionary.hasKey(GridSegmentsInfo.identifier)) { + _gridSegments = static_cast( + dictionary.value(GridSegmentsInfo.identifier) + ); + } + _gridSegments.onChange([&]() { _gridIsDirty = true; }); + addProperty(_gridSegments); + + if (dictionary.hasKey(CircleSegmentsInfo.identifier)) { + _circleSegments = static_cast( + dictionary.value(CircleSegmentsInfo.identifier) + ); + } + _circleSegments.onChange([&]() { + if (_circleSegments.value() % 2 == 1) { + _circleSegments = _circleSegments - 1; + } + _gridIsDirty = true; + }); + addProperty(_circleSegments); + + if (dictionary.hasKey(LineWidthInfo.identifier)) { + _lineWidth = static_cast( + dictionary.value(LineWidthInfo.identifier) + ); + } + addProperty(_lineWidth); + + if (dictionary.hasKey(OuterRadiusInfo.identifier)) { + _maxRadius = static_cast( + dictionary.value(OuterRadiusInfo.identifier) + ); + } + + if (dictionary.hasKey(InnerRadiusInfo.identifier)) { + _minRadius = static_cast( + dictionary.value(InnerRadiusInfo.identifier) + ); + } + + _maxRadius.setMinValue(_minRadius); + _minRadius.setMaxValue(_maxRadius); + + _maxRadius.onChange([&]() { + _gridIsDirty = true; + _minRadius.setMaxValue(_maxRadius); + }); + + _minRadius.onChange([&]() { + _gridIsDirty = true; + _maxRadius.setMinValue(_minRadius); + }); + + addProperty(_maxRadius); + addProperty(_minRadius); +} + +bool RenderableRadialGrid::isReady() const { + return _gridProgram != nullptr; +} + +void RenderableRadialGrid::initializeGL() { + _gridProgram = BaseModule::ProgramObjectManager.request( + ProgramName, + []() -> std::unique_ptr { + return global::renderEngine.buildRenderProgram( + ProgramName, + absPath("${MODULE_BASE}/shaders/grid_vs.glsl"), + absPath("${MODULE_BASE}/shaders/grid_fs.glsl") + ); + } + ); + + _lines = std::make_unique(); +} + +void RenderableRadialGrid::deinitializeGL() { + BaseModule::ProgramObjectManager.release( + ProgramName, + [](ghoul::opengl::ProgramObject* p) { + global::renderEngine.removeRenderProgram(p); + } + ); + _gridProgram = nullptr; +} + +void RenderableRadialGrid::render(const RenderData& data, RendererTasks&){ + _gridProgram->activate(); + + _gridProgram->setUniform("opacity", _opacity); + + const glm::dmat4 modelTransform = + glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation + glm::dmat4(data.modelTransform.rotation) * // Spice rotation + glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale)); + + const glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * + modelTransform; + + _gridProgram->setUniform("modelViewTransform", modelViewTransform); + _gridProgram->setUniform( + "MVPTransform", + glm::dmat4(data.camera.projectionMatrix()) * modelViewTransform + ); + + _gridProgram->setUniform("gridColor", _gridColor); + + float adjustedLineWidth = 1.f; + +#ifndef __APPLE__ + adjustedLineWidth = _lineWidth; +#endif + + // Saves current state: + GLboolean isBlendEnabled = glIsEnabledi(GL_BLEND, 0); + GLfloat currentLineWidth; + glGetFloatv(GL_LINE_WIDTH, ¤tLineWidth); + GLboolean isLineSmoothEnabled = glIsEnabled(GL_LINE_SMOOTH); + + GLenum blendEquationRGB; + GLenum blendEquationAlpha; + GLenum blendDestAlpha; + GLenum blendDestRGB; + GLenum blendSrcAlpha; + GLenum blendSrcRGB; + glGetIntegerv(GL_BLEND_EQUATION_RGB, &blendEquationRGB); + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blendEquationAlpha); + glGetIntegerv(GL_BLEND_DST_ALPHA, &blendDestAlpha); + glGetIntegerv(GL_BLEND_DST_RGB, &blendDestRGB); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &blendSrcAlpha); + glGetIntegerv(GL_BLEND_SRC_RGB, &blendSrcRGB); + + // Changes GL state: + glLineWidth(adjustedLineWidth); + glEnablei(GL_BLEND, 0); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_LINE_SMOOTH); + + for (std::unique_ptr& c : _circles) { + c->render(); + } + + _lines->render(); + + _gridProgram->deactivate(); + + // Restores GL State + glLineWidth(currentLineWidth); + glBlendEquationSeparate(blendEquationRGB, blendEquationAlpha); + glBlendFuncSeparate(blendSrcRGB, blendDestRGB, blendSrcAlpha, blendDestAlpha); + + if (!isBlendEnabled) { + glDisablei(GL_BLEND, 0); + } + + if (!isLineSmoothEnabled) { + glDisable(GL_LINE_SMOOTH); + } +} + +void RenderableRadialGrid::update(const UpdateData&) { + if (_gridIsDirty) { + + // Circles + auto createRing = [](int nSegments, float radius) { + const int nVertices = nSegments + 1; + std::vector vertices(nVertices); + + const float fsegments = static_cast(nSegments); + + for (int i = 0; i <= nSegments; ++i) { + const float fi = static_cast(i); + + const float theta = fi * glm::pi() * 2.0f / fsegments; // 0 -> 2*PI + + const float x = radius * cos(theta); + const float y = radius * sin(theta); + const float z = 0.0f; + + vertices[i] = { x, y, z }; + } + return vertices; + }; + + const int nRadialSegments = _gridSegments.value()[0]; + const float fnCircles = static_cast(nRadialSegments); + const float deltaRadius = (_maxRadius - _minRadius) / fnCircles; + + const bool hasInnerRadius = _minRadius > 0; + const int nCircles = hasInnerRadius ? nRadialSegments : nRadialSegments + 1; + + _circles.clear(); + _circles.reserve(nCircles); + + // add an extra inmost circle + if (hasInnerRadius) { + _circles.push_back(std::make_unique()); + _circles.back()->varray = createRing(_circleSegments, _minRadius); + _circles.back()->update(); + } + + for (int i = 0; i < nRadialSegments; ++i) { + float ri = static_cast(i + 1) * deltaRadius; + ri += _minRadius; + _circles.push_back(std::make_unique()); + _circles.back()->varray = createRing(_circleSegments, ri); + _circles.back()->update(); + } + + // Lines + const int nLines = _gridSegments.value()[1]; + const float fsegments = static_cast(nLines); + + _lines->varray.clear(); + + if (nLines > 1) { + for (int i = 0; i < nLines; ++i) { + const float fi = static_cast(i); + + const float theta = fi * glm::pi() * 2.0f / fsegments; // 0 -> 2*PI + + float x = _maxRadius * cos(theta); + float y = _maxRadius * sin(theta); + float z = 0.0f; + + _lines->varray.push_back({ x, y, z }); + + x = _minRadius * cos(theta); + y = _minRadius * sin(theta); + + _lines->varray.push_back({ x, y, z }); + } + } + _lines->update(); + + _gridIsDirty = false; + } +} + +RenderableRadialGrid::GeometryData::GeometryData(GLenum renderMode) + : mode(renderMode) +{ + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glEnableVertexAttribArray(0); + glBindVertexArray(0); +} + +RenderableRadialGrid::GeometryData::~GeometryData() { + glDeleteVertexArrays(1, &vao); + vao = 0; + + glDeleteBuffers(1, &vbo); + vbo = 0; +} + +void RenderableRadialGrid::GeometryData::update() { + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData( + GL_ARRAY_BUFFER, + varray.size() * sizeof(Vertex), + varray.data(), + GL_STATIC_DRAW + ); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr); +} + +void RenderableRadialGrid::GeometryData::render() { + glBindVertexArray(vao); + glDrawArrays(mode, 0, static_cast(varray.size())); + glBindVertexArray(0); +} + +} // namespace openspace diff --git a/modules/base/rendering/grids/renderableradialgrid.h b/modules/base/rendering/grids/renderableradialgrid.h new file mode 100644 index 0000000000..9c36ac4da4 --- /dev/null +++ b/modules/base/rendering/grids/renderableradialgrid.h @@ -0,0 +1,100 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2020 * + * * + * 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___RENDERABLERADIALGRID___H__ +#define __OPENSPACE_MODULE_BASE___RENDERABLERADIALGRID___H__ + +#include + +#include +#include +#include +#include +#include +#include + +namespace ghoul::opengl { class ProgramObject; } + +namespace openspace::documentation { struct Documentation; } + +namespace openspace { + +class RenderableRadialGrid : public Renderable { +public: + RenderableRadialGrid(const ghoul::Dictionary& dictionary); + ~RenderableRadialGrid() = default; + + void initializeGL() override; + void deinitializeGL() override; + + bool isReady() const override; + + void render(const RenderData& data, RendererTasks& rendererTask) override; + void update(const UpdateData& data) override; + + static documentation::Documentation Documentation(); + +protected: + struct Vertex { + float location[3]; + }; + + struct GeometryData { + GeometryData(GLenum renderMode); + ~GeometryData(); + void update(); + void render(); + + std::vector varray; + GLuint vao = 0; + GLuint vbo = 0; + GLenum mode = GL_LINE_STRIP; + }; + + struct CircleData : public GeometryData { + CircleData() : GeometryData(GL_LINE_STRIP) {} + }; + + struct LineData : public GeometryData { + LineData() : GeometryData(GL_LINES) {} + }; + + ghoul::opengl::ProgramObject* _gridProgram; + + properties::Vec3Property _gridColor; + properties::IVec2Property _gridSegments; + properties::IntProperty _circleSegments; + properties::FloatProperty _lineWidth; + properties::FloatProperty _maxRadius; + properties::FloatProperty _minRadius; + + bool _gridIsDirty = true; + + std::vector> _circles; + std::unique_ptr _lines; +}; + +}// namespace openspace + +#endif // __OPENSPACE_MODULE_BASE___RENDERABLERADIALGRID___H__