From 76d599d28435a6a6c4bf267fdc250e1a7b71f480 Mon Sep 17 00:00:00 2001 From: Emma Broman Date: Thu, 7 Jan 2021 13:33:28 +0100 Subject: [PATCH] Add a basic disc renderable --- data/assets/examples/discs.asset | 22 ++ modules/base/CMakeLists.txt | 5 +- modules/base/basemodule.cpp | 3 + modules/base/rendering/renderabledisc.cpp | 296 ++++++++++++++++++++++ modules/base/rendering/renderabledisc.h | 81 ++++++ modules/base/shaders/disc_fs.glsl | 60 +++++ modules/base/shaders/disc_vs.glsl | 44 ++++ 7 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 data/assets/examples/discs.asset create mode 100644 modules/base/rendering/renderabledisc.cpp create mode 100644 modules/base/rendering/renderabledisc.h create mode 100644 modules/base/shaders/disc_fs.glsl create mode 100644 modules/base/shaders/disc_vs.glsl diff --git a/data/assets/examples/discs.asset b/data/assets/examples/discs.asset new file mode 100644 index 0000000000..9b90c51c9e --- /dev/null +++ b/data/assets/examples/discs.asset @@ -0,0 +1,22 @@ +local assetHelper = asset.require('util/asset_helper') + +local singeColorTexturePath = openspace.createPixelImage("example_ring_color", {0.0, 1.0, 1.0}) + +local BasicDisc = { + Identifier = "BasicDisc", + Parent = "Root", + Renderable = { + Type = "RenderableDisc", + Texture = singeColorTexturePath, + Size = 1e10, + Width = 0.5 + }, + GUI = { + Name = "Basic Disc", + Path = "/Examples/Discs" + } +} + +assetHelper.registerSceneGraphNodesAndExport(asset, { + BasicDisc +}) \ No newline at end of file diff --git a/modules/base/CMakeLists.txt b/modules/base/CMakeLists.txt index 55519e8c01..521cf3f950 100644 --- a/modules/base/CMakeLists.txt +++ b/modules/base/CMakeLists.txt @@ -45,6 +45,7 @@ set(HEADER_FILES rendering/grids/renderableradialgrid.h rendering/grids/renderablesphericalgrid.h rendering/renderablecartesianaxes.h + rendering/renderabledisc.h rendering/renderablelabels.h rendering/renderablemodel.h rendering/renderablenodeline.h @@ -73,7 +74,6 @@ set(HEADER_FILES translation/luatranslation.h translation/statictranslation.h translation/timelinetranslation.h - ) source_group("Header Files" FILES ${HEADER_FILES}) @@ -98,6 +98,7 @@ set(SOURCE_FILES rendering/grids/renderableradialgrid.cpp rendering/grids/renderablesphericalgrid.cpp rendering/renderablecartesianaxes.cpp + rendering/renderabledisc.cpp rendering/renderablelabels.cpp rendering/renderablemodel.cpp rendering/renderablenodeline.cpp @@ -132,6 +133,8 @@ source_group("Source Files" FILES ${SOURCE_FILES}) set(SHADER_FILES shaders/axes_fs.glsl shaders/axes_vs.glsl + shaders/disc_fs.glsl + shaders/disc_vs.glsl shaders/grid_vs.glsl shaders/grid_fs.glsl shaders/imageplane_fs.glsl diff --git a/modules/base/basemodule.cpp b/modules/base/basemodule.cpp index 6a9ebdce24..09ef673631 100644 --- a/modules/base/basemodule.cpp +++ b/modules/base/basemodule.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -129,6 +130,7 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) { fRenderable->registerClass("RenderableBoxGrid"); fRenderable->registerClass("RenderableCartesianAxes"); + fRenderable->registerClass("RenderableDisc"); fRenderable->registerClass("RenderableGrid"); fRenderable->registerClass("RenderableLabels"); fRenderable->registerClass("RenderableModel"); @@ -206,6 +208,7 @@ std::vector BaseModule::documentations() const { RenderableNodeLine::Documentation(), RenderablePlane::Documentation(), RenderableRadialGrid::Documentation(), + RenderableDisc::Documentation(), RenderableSphere::Documentation(), RenderableSphericalGrid::Documentation(), RenderableTrailOrbit::Documentation(), diff --git a/modules/base/rendering/renderabledisc.cpp b/modules/base/rendering/renderabledisc.cpp new file mode 100644 index 0000000000..f5052e33fe --- /dev/null +++ b/modules/base/rendering/renderabledisc.cpp @@ -0,0 +1,296 @@ +/***************************************************************************************** + * * + * 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 +#include +#include +#include +#include + +namespace { + constexpr const std::array UniformNames = { + "modelViewProjectionTransform", "opacity", "width", "colorTexture" + }; + + constexpr openspace::properties::Property::PropertyInfo TextureInfo = { + "Texture", + "Texture", + "This value is the path to a texture on disk that contains a one-dimensional " + "texture to be used for the color." + }; + + constexpr openspace::properties::Property::PropertyInfo SizeInfo = { + "Size", + "Size", + "This value specifies the outer radius of the disc in meter." + }; + + constexpr openspace::properties::Property::PropertyInfo WidthInfo = { + "Width", + "Width", + "This value is used to set the width of the disc. The actual width is set " + "based on the given size and this value should be set between 0 and 1. A value " + "of 1 results in a full circle and 0.5 a disc with an inner radius of 0.5*size." + }; +} // namespace + +namespace openspace { + +documentation::Documentation RenderableDisc::Documentation() { + using namespace documentation; + return { + "Renderable Disc", + "renderable_disc", + { + { + "Type", + new StringEqualVerifier("RenderableDisc"), + Optional::No + }, + { + TextureInfo.identifier, + new StringVerifier, + Optional::No, + TextureInfo.description + }, + { + SizeInfo.identifier, + new DoubleVerifier, + Optional::No, + SizeInfo.description + }, + { + WidthInfo.identifier, + new DoubleVerifier, + Optional::Yes, + WidthInfo.description + } + } + }; +} + +RenderableDisc::RenderableDisc(const ghoul::Dictionary& dictionary) + : Renderable(dictionary) + , _texturePath(TextureInfo) + , _size(SizeInfo, 1.f, 0.f, 1e13f) + , _width(WidthInfo, 0.5f, 0.f, 1.f) +{ + using ghoul::filesystem::File; + + documentation::testSpecificationAndThrow( + Documentation(), + dictionary, + "RenderableDisc" + ); + + _size = static_cast(dictionary.value(SizeInfo.identifier)); + setBoundingSphere(_size); + _size.onChange([&]() { _planeIsDirty = true; }); + addProperty(_size); + + _texturePath = absPath(dictionary.value(TextureInfo.identifier)); + _textureFile = std::make_unique(_texturePath); + + if (dictionary.hasKey(WidthInfo.identifier)) { + _width = dictionary.value(WidthInfo.identifier); + } + addProperty(_width); + + _texturePath.onChange([&]() { loadTexture(); }); + addProperty(_texturePath); + + _textureFile->setCallback([&](const File&) { _textureIsDirty = true; }); + + addProperty(_opacity); +} + +bool RenderableDisc::isReady() const { + return _shader && _texture; +} + +void RenderableDisc::initializeGL() { + _shader = global::renderEngine->buildRenderProgram( + "RingProgram", + absPath("${MODULE_BASE}/shaders/disc_vs.glsl"), + absPath("${MODULE_BASE}/shaders/disc_fs.glsl") + ); + + ghoul::opengl::updateUniformLocations(*_shader, _uniformCache, UniformNames); + + glGenVertexArrays(1, &_quad); + glGenBuffers(1, &_vertexPositionBuffer); + + createPlane(); + loadTexture(); +} + +void RenderableDisc::deinitializeGL() { + glDeleteVertexArrays(1, &_quad); + _quad = 0; + + glDeleteBuffers(1, &_vertexPositionBuffer); + _vertexPositionBuffer = 0; + + _textureFile = nullptr; + _texture = nullptr; + + global::renderEngine->removeRenderProgram(_shader.get()); + _shader = nullptr; +} + +void RenderableDisc::render(const RenderData& data, RendererTasks&) { + _shader->activate(); + + glm::dmat4 modelTransform = + glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * + glm::dmat4(data.modelTransform.rotation) * + glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale)); + + glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform; + + _shader->setUniform( + _uniformCache.modelViewProjection, + data.camera.projectionMatrix() * glm::mat4(modelViewTransform) + ); + _shader->setUniform(_uniformCache.width, _width); + + _shader->setUniform(_uniformCache.opacity, _opacity); + + ghoul::opengl::TextureUnit unit; + unit.activate(); + _texture->bind(); + _shader->setUniform(_uniformCache.texture, unit); + + glEnablei(GL_BLEND, 0); + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glDepthMask(false); + glDisable(GL_CULL_FACE); + + glBindVertexArray(_quad); + glDrawArrays(GL_TRIANGLES, 0, 6); + + _shader->deactivate(); + + // Restores GL State + global::renderEngine->openglStateCache().resetBlendState(); + global::renderEngine->openglStateCache().resetDepthState(); + global::renderEngine->openglStateCache().resetPolygonAndClippingState(); +} + +void RenderableDisc::update(const UpdateData&) { + if (_shader->isDirty()) { + _shader->rebuildFromFile(); + ghoul::opengl::updateUniformLocations(*_shader, _uniformCache, UniformNames); + } + + if (_planeIsDirty) { + createPlane(); + _planeIsDirty = false; + } + + if (_textureIsDirty) { + loadTexture(); + _textureIsDirty = false; + } +} + +void RenderableDisc::loadTexture() { + if (!_texturePath.value().empty()) { + using namespace ghoul::io; + using namespace ghoul::opengl; + std::unique_ptr texture = TextureReader::ref().loadTexture( + absPath(_texturePath) + ); + + if (texture) { + LDEBUGC( + "RenderableDisc", + fmt::format("Loaded texture from '{}'", absPath(_texturePath)) + ); + _texture = std::move(texture); + + _texture->uploadTexture(); + _texture->setFilter(ghoul::opengl::Texture::FilterMode::AnisotropicMipMap); + + _textureFile = std::make_unique(_texturePath); + _textureFile->setCallback( + [&](const ghoul::filesystem::File&) { _textureIsDirty = true; } + ); + } + } +} + +void RenderableDisc::createPlane() { + const GLfloat size = _size; + + struct VertexData { + GLfloat x; + GLfloat y; + GLfloat s; + GLfloat t; + }; + + VertexData data[] = { + { -size, -size, 0.f, 0.f }, + { size, size, 1.f, 1.f }, + { -size, size, 0.f, 1.f }, + { -size, -size, 0.f, 0.f }, + { size, -size, 1.f, 0.f }, + { size, size, 1.f, 1.f }, + }; + + glBindVertexArray(_quad); + glBindBuffer(GL_ARRAY_BUFFER, _vertexPositionBuffer); + glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer( + 0, + 2, + GL_FLOAT, + GL_FALSE, + sizeof(VertexData), + nullptr + ); + glEnableVertexAttribArray(1); + glVertexAttribPointer( + 1, + 2, + GL_FLOAT, + GL_FALSE, + sizeof(VertexData), + reinterpret_cast(offsetof(VertexData, s)) // NOLINT + ); +} + +} // namespace openspace diff --git a/modules/base/rendering/renderabledisc.h b/modules/base/rendering/renderabledisc.h new file mode 100644 index 0000000000..e767b2239c --- /dev/null +++ b/modules/base/rendering/renderabledisc.h @@ -0,0 +1,81 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2021 * + * * + * 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___RENDERABLEDISC___H__ +#define __OPENSPACE_MODULE_BASE___RENDERABLEDISC___H__ + +#include + +#include +#include + +#include +#include + +namespace ghoul::filesystem { class File; } +namespace ghoul::opengl { + class ProgramObject; + class Texture; +} // namespace ghoul::opengl + +namespace openspace { + +namespace documentation { struct Documentation; } + +class RenderableDisc : public Renderable { +public: + RenderableDisc(const ghoul::Dictionary& dictionary); + + 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(); + +private: + void loadTexture(); + void createPlane(); + + properties::StringProperty _texturePath; + properties::FloatProperty _size; + properties::FloatProperty _width; + + std::unique_ptr _shader; + UniformCache(modelViewProjection, opacity, width, texture) _uniformCache; + std::unique_ptr _texture; + std::unique_ptr _textureFile; + + bool _textureIsDirty = false; + GLuint _quad = 0; + GLuint _vertexPositionBuffer = 0; + bool _planeIsDirty = false; +}; + +} // namespace openspace + +#endif // __OPENSPACE_MODULE_BASE___RENDERABLEDISC___H__ diff --git a/modules/base/shaders/disc_fs.glsl b/modules/base/shaders/disc_fs.glsl new file mode 100644 index 0000000000..7d0d15dc1d --- /dev/null +++ b/modules/base/shaders/disc_fs.glsl @@ -0,0 +1,60 @@ +/***************************************************************************************** + * * + * 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 "fragment.glsl" + +in vec2 vs_st; +in vec4 vs_position; + +uniform sampler1D colorTexture; +uniform float width; +uniform float opacity; + +Fragment getFragment() { + // Moving the origin to the center + vec2 st = (vs_st - vec2(0.5)) * 2.0; + + // The length of the texture coordinates vector is our distance from the center + float radius = length(st); + + // We only want to consider ring-like objects so we need to discard everything else + if (radius > 1.0) + discard; + + // Remapping the texture coordinates + // Radius \in [0,1], + float inner = 1.0 - width; + float texCoord = (radius - inner) / (1.0 - inner); + if (texCoord < 0.0 || texCoord > 1.0) { + discard; + } + + vec4 diffuse = texture(colorTexture, texCoord); + diffuse.a *= opacity; + + Fragment frag; + frag.color = diffuse; + frag.depth = vs_position.w; + return frag; +} diff --git a/modules/base/shaders/disc_vs.glsl b/modules/base/shaders/disc_vs.glsl new file mode 100644 index 0000000000..a3f6cbedb6 --- /dev/null +++ b/modules/base/shaders/disc_vs.glsl @@ -0,0 +1,44 @@ +/***************************************************************************************** + * * + * 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. * + ****************************************************************************************/ + +#version __CONTEXT__ + +#include "PowerScaling/powerScaling_vs.hglsl" + +layout(location = 0) in vec2 in_position; +layout(location = 1) in vec2 in_st; + +out vec2 vs_st; +out vec4 vs_position; + +uniform mat4 modelViewProjectionTransform; + +void main() { + vs_st = in_st; + + vs_position = z_normalization( + modelViewProjectionTransform * vec4(in_position.xy, 0.0, 1.0) + ); + gl_Position = vs_position; +}