/***************************************************************************************** * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr std::array UniformNames = { "color", "opacity", "mvpMatrix", "tex", "backgroundColor", "gamma" }; constexpr openspace::properties::Property::PropertyInfo EnabledInfo = { "Enabled", "Is Enabled", "This setting determines whether this sceen space plane will be visible or not" }; constexpr openspace::properties::Property::PropertyInfo UseRadiusAzimuthElevationInfo = { "UseRadiusAzimuthElevation", "Use Radius Azimuth and Elevation", "This value determines whether the location of this screen space plane will be " "specified using radius, azimuth and elevation (if this is set to 'true') or " "using cartesian coordinates. By switching this value, the correct property will " "be shown or hidden. The Cartesian coordinate system is useful if a regular " "rendering is applied, whereas the radius azimuth elevation are most useful in a " "planetarium environment" }; constexpr openspace::properties::Property::PropertyInfo UsePerspectiveProjectionInfo = { "UsePerspectiveProjection", "Use Perspective Projection", "Determines whetether the z/radius values affects the size of the plane or not" }; constexpr openspace::properties::Property::PropertyInfo CartesianPositionInfo = { "CartesianPosition", "Cartesian Coordinates", "This value determines the position of this screen space plane in Cartesian " "three-dimensional coordinates (meters)" }; constexpr openspace::properties::Property::PropertyInfo RadiusAzimuthElevationInfo = { "RadiusAzimuthElevation", "Radius Azimuth Elevation", "This value determines the position of this screen space plane in a " "coordinate system based on radius (meters), azimuth (radians) and elevation " "(radians)" }; constexpr openspace::properties::Property::PropertyInfo ScaleInfo = { "Scale", "Scale Value", "This value determines a scale factor for the plane. The default size of a plane " "is determined by the concrete instance and reflects, for example, the size of " "the image being displayed" }; constexpr openspace::properties::Property::PropertyInfo LocalRotationInfo = { "Rotation", "Local Rotation", "An euler rotation (x, y, z) to apply to the plane" }; constexpr openspace::properties::Property::PropertyInfo MultiplyColorInfo = { "MultiplyColor", "Multiply Color", "If set, the plane's texture is multiplied with this color. Useful for applying " "a color grayscale images" }; constexpr openspace::properties::Property::PropertyInfo BackgroundColorInfo = { "BackgroundColor", "Background Color", "The fixed color that is combined with the screen space renderable to create the " "final color. The actual color of the screen space renderable is alpha-blended " "with the background color to produce the final result" }; constexpr openspace::properties::Property::PropertyInfo OpacityInfo = { "Opacity", "Opacity", "This value determines the opacity of the screen space plane. If this value " "is 1, the plane is completely opaque, if this value is 0, the plane is " "completely transparent" }; constexpr openspace::properties::Property::PropertyInfo FadeInfo = { "Fade", "Fade", "This value is used by the system to be able to fade out renderables " "independently from the Opacity value selected by the user. This value should " "not be directly manipulated through a user interface, but instead used by other " "components of the system programmatically", // The Developer mode should be used once the properties in the UI listen to this // openspace::properties::Property::Visibility::Developer openspace::properties::Property::Visibility::Hidden }; constexpr openspace::properties::Property::PropertyInfo DeleteInfo = { "Delete", "Delete", "If this property is triggered, this screen space plane is removed from the " "scene" }; constexpr openspace::properties::Property::PropertyInfo FaceCameraInfo = { "FaceCamera", "Face Camera", "If enabled, the local rotation is applied after the plane is rotated to face " "the camera" }; constexpr openspace::properties::Property::PropertyInfo GammaInfo = { "Gamma", "Gamma Correction", "Sets the gamma correction of the texture" }; float wrap(float value, float min, float max) { return glm::mod(value - min, max - min) + min; } struct [[codegen::Dictionary(ScreenSpaceRenderable)]] Parameters { // The type of the Screenspace renderable that is to be created. The available // types of Screenspace renderable depend on the configuration of the application // and can be written to disk on application startup into the FactoryDocumentation std::string type [[codegen::annotation("Must name a valid Screenspace renderable")]]; // Specifies the name of this screenspace renderable. This does not have to be // unique to the scene, but it is recommended to be std::optional name; // This is the unique identifier for this screenspace renderable. It has to be // unique amongst all existing screenspace nodes that already have been added to // the scene. std::optional identifier [[codegen::identifier()]]; // [[codegen::verbatim(EnabledInfo.description)]] std::optional enabled; // [[codegen::verbatim(UseRadiusAzimuthElevationInfo.description)]] std::optional useRadiusAzimuthElevation; // [[codegen::verbatim(FaceCameraInfo.description)]] std::optional faceCamera; // [[codegen::verbatim(CartesianPositionInfo.description)]] std::optional cartesianPosition; // [[codegen::verbatim(GammaInfo.description)]] std::optional radiusAzimuthElevation; // [[codegen::verbatim(ScaleInfo.description)]] std::optional scale; // [[codegen::verbatim(UseRadiusAzimuthElevationInfo.description)]] std::optional gamma; // [[codegen::verbatim(UsePerspectiveProjectionInfo.description)]] std::optional usePerspectiveProjection; // [[codegen::verbatim(MultiplyColorInfo.description)]] std::optional multiplyColor [[codegen::color()]]; // [[codegen::verbatim(BackgroundColorInfo.description)]] std::optional backgroundColor [[codegen::color()]]; // [codegen::verbatim(OpacityInfo.description)]] std::optional opacity [[codegen::inrange(0.f, 1.f)]]; // Defines either a single or multiple tags that apply to this // ScreenSpaceRenderable, thus making it possible to address multiple, separate // Renderables with a single property change std::optional>> tag; }; #include "screenspacerenderable_codegen.cpp" } // namespace namespace openspace { documentation::Documentation ScreenSpaceRenderable::Documentation() { return codegen::doc("core_screenspacerenderable"); } std::unique_ptr ScreenSpaceRenderable::createFromDictionary( const ghoul::Dictionary& dictionary) { const Parameters p = codegen::bake(dictionary); ScreenSpaceRenderable* ssr = FactoryManager::ref().factory()->create( p.type, dictionary ); return std::unique_ptr(ssr); } std::string ScreenSpaceRenderable::makeUniqueIdentifier(std::string name) { std::vector rs = global::renderEngine->screenSpaceRenderables(); auto nameTaken = [&rs](const std::string& n) { const bool taken = std::any_of( rs.cbegin(), rs.cend(), [&n](ScreenSpaceRenderable* r) { return r->identifier() == n; } ); return taken; }; std::string baseName = name; int i = 1; while (nameTaken(name)) { name = baseName + std::to_string(i); i++; } return name; } ScreenSpaceRenderable::ScreenSpaceRenderable(const ghoul::Dictionary& dictionary) : properties::PropertyOwner({ "" }) , _enabled(EnabledInfo, true) , _usePerspectiveProjection(UsePerspectiveProjectionInfo, false) , _useRadiusAzimuthElevation(UseRadiusAzimuthElevationInfo, false) , _faceCamera(FaceCameraInfo, true) , _cartesianPosition( CartesianPositionInfo, glm::vec3(0.f, 0.f, -2.f), glm::vec3(-4.f, -4.f, -10.f), glm::vec3(4.f, 4.f, 0.f) ) , _raePosition( RadiusAzimuthElevationInfo, glm::vec3(2.f, 0.f, 0.f), glm::vec3(0.f, -glm::pi(), -glm::half_pi()), glm::vec3(10.f, glm::pi(), glm::half_pi()) ) , _localRotation( LocalRotationInfo, glm::vec3(0.f), glm::vec3(-glm::pi()), glm::vec3(glm::pi()) ) , _scale(ScaleInfo, 0.25f, 0.f, 2.f) , _gamma(GammaInfo, 1.f, 0.000001f, 10.f) , _multiplyColor(MultiplyColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f)) , _backgroundColor( BackgroundColorInfo, glm::vec4(0.f), glm::vec4(0.f), glm::vec4(1.f) ) , _opacity(OpacityInfo, 1.f, 0.f, 1.f) , _fade(FadeInfo, 1.f, 0.f, 1.f) , _delete(DeleteInfo) { const Parameters p = codegen::bake(dictionary); if (p.identifier.has_value()) { setIdentifier(*p.identifier); } if (p.name.has_value()) { setGuiName(*p.name); } addProperty(_enabled); addProperty(_useRadiusAzimuthElevation); addProperty(_usePerspectiveProjection); addProperty(_faceCamera); addProperty(_cartesianPosition); addProperty(_raePosition); addProperty(_gamma); // Setting spherical/euclidean onchange handler _useRadiusAzimuthElevation.onChange([this]() { if (_useRadiusAzimuthElevation) { _raePosition = sphericalToRae(cartesianToSpherical(_cartesianPosition)); } else { _cartesianPosition = sphericalToCartesian(raeToSpherical(_raePosition)); } }); addProperty(_scale); addProperty(_multiplyColor); addProperty(_backgroundColor); addProperty(_opacity); addProperty(_fade); addProperty(_localRotation); _multiplyColor = p.multiplyColor.value_or(_multiplyColor); _multiplyColor.setViewOption(properties::Property::ViewOptions::Color); _backgroundColor = p.backgroundColor.value_or(_backgroundColor); _backgroundColor.setViewOption(properties::Property::ViewOptions::Color); _enabled = p.enabled.value_or(_enabled); _gamma = p.gamma.value_or(_gamma); _useRadiusAzimuthElevation = p.useRadiusAzimuthElevation.value_or(_useRadiusAzimuthElevation); if (_useRadiusAzimuthElevation) { _raePosition = p.radiusAzimuthElevation.value_or(_raePosition); } else { _cartesianPosition = p.cartesianPosition.value_or(_cartesianPosition); } _scale = p.scale.value_or(_scale); _opacity = p.opacity.value_or(_opacity); _usePerspectiveProjection = p.usePerspectiveProjection.value_or(_usePerspectiveProjection); _faceCamera = p.faceCamera.value_or(_faceCamera); if (p.tag.has_value()) { if (std::holds_alternative(*p.tag)) { addTag(std::get(*p.tag)); } else if (std::holds_alternative>(*p.tag)) { for (const std::string& t : std::get>(*p.tag)) { if (!t.empty()) { addTag(t); } } } else { throw ghoul::MissingCaseException(); } } _delete.onChange([this](){ std::string script = "openspace.removeScreenSpaceRenderable('" + identifier() + "');"; global::scriptEngine->queueScript( script, scripting::ScriptEngine::RemoteScripting::No ); }); addProperty(_delete); } ScreenSpaceRenderable::~ScreenSpaceRenderable() {} bool ScreenSpaceRenderable::initialize() { return true; } bool ScreenSpaceRenderable::initializeGL() { createShaders(); return isReady(); } bool ScreenSpaceRenderable::deinitialize() { return true; } bool ScreenSpaceRenderable::deinitializeGL() { if (_shader) { global::renderEngine->removeRenderProgram(_shader.get()); _shader = nullptr; } return true; } void ScreenSpaceRenderable::render() { ZoneScoped; draw( globalRotationMatrix() * translationMatrix() * localRotationMatrix() * scaleMatrix() ); } bool ScreenSpaceRenderable::isReady() const { return _shader != nullptr; } void ScreenSpaceRenderable::update() {} bool ScreenSpaceRenderable::isEnabled() const { return _enabled; } bool ScreenSpaceRenderable::isUsingRaeCoords() const { return _useRadiusAzimuthElevation; } bool ScreenSpaceRenderable::isFacingCamera() const { return _faceCamera; } void ScreenSpaceRenderable::setEnabled(bool isEnabled) { _enabled = isEnabled; } float ScreenSpaceRenderable::depth() { return _useRadiusAzimuthElevation ? _raePosition.value().x : cartesianToSpherical(_cartesianPosition).x; } float ScreenSpaceRenderable::scale() const { return _scale; } void ScreenSpaceRenderable::createShaders() { ghoul::Dictionary dict = ghoul::Dictionary(); auto res = global::windowDelegate->currentDrawBufferResolution(); ghoul::Dictionary rendererData; rendererData.setValue( "fragmentRendererPath", std::string("${SHADERS}/framebuffer/renderframebuffer.frag") ); rendererData.setValue("windowWidth", res.x); rendererData.setValue("windowHeight", res.y); rendererData.setValue( "hdrExposure", static_cast(global::renderEngine->hdrExposure()) ); rendererData.setValue("disableHDR", global::renderEngine->isHdrDisabled()); dict.setValue("rendererData", rendererData); dict.setValue( "fragmentPath", std::string("${MODULE_BASE}/shaders/screenspace_fs.glsl") ); _shader = ghoul::opengl::ProgramObject::Build( "ScreenSpaceProgram", absPath("${MODULE_BASE}/shaders/screenspace_vs.glsl"), absPath("${SHADERS}/render.frag"), dict ); ghoul::opengl::updateUniformLocations(*_shader, _uniformCache, UniformNames); } glm::mat4 ScreenSpaceRenderable::scaleMatrix() { // to scale the plane float textureRatio = static_cast(_objectSize.y) / static_cast(_objectSize.x); glm::mat4 scale = glm::scale( glm::mat4(1.f), glm::vec3(_scale, textureRatio*_scale, 1.f) ); return scale; } glm::vec2 ScreenSpaceRenderable::screenSpacePosition() { return glm::vec2(_cartesianPosition.value()); } glm::vec2 ScreenSpaceRenderable::screenSpaceDimensions() { float ratio = static_cast(_objectSize.x) / static_cast(_objectSize.y); return glm::vec2(2.f * _scale * ratio, 2.f * _scale); } glm::vec2 ScreenSpaceRenderable::upperRightCornerScreenSpace() { return screenSpacePosition() + (screenSpaceDimensions() / 2.0f); } glm::vec2 ScreenSpaceRenderable::lowerLeftCornerScreenSpace() { return screenSpacePosition() - (screenSpaceDimensions() / 2.0f); } bool ScreenSpaceRenderable::isIntersecting(glm::vec2 coord) { bool isUnderTopBorder = coord.x < upperRightCornerScreenSpace().x; bool isLeftToRightBorder = coord.y < upperRightCornerScreenSpace().y; bool isRightToLeftBorder = coord.x > lowerLeftCornerScreenSpace().x; bool isOverBottomBorder = coord.y > lowerLeftCornerScreenSpace().y; return isUnderTopBorder && isLeftToRightBorder && isRightToLeftBorder && isOverBottomBorder; } void ScreenSpaceRenderable::translate(glm::vec2 translation, glm::vec2 position) { glm::mat4 translationMatrix = glm::translate( glm::mat4(1.f), glm::vec3(translation, 0.0f) ); glm::vec4 origin = glm::vec4(position, _cartesianPosition.value().z, 1.0f); _cartesianPosition = translationMatrix * origin; } void ScreenSpaceRenderable::setCartesianPosition(const glm::vec3& position) { _cartesianPosition = position; } void ScreenSpaceRenderable::setRaeFromCartesianPosition(const glm::vec3& position) { _raePosition = cartesianToRae(position); } glm::vec3 ScreenSpaceRenderable::raePosition() const { return _raePosition; } glm::mat4 ScreenSpaceRenderable::globalRotationMatrix() { // We do not want the screen space planes to be affected by // 1) The global rotation of the view applied in the render engine // 2) sgct's scene matrix (also called model matrix by sgct) glm::mat4 inverseRotation = glm::inverse( global::renderEngine->globalRotation() * global::windowDelegate->modelMatrix() ); // The rotation of all screen space renderables is adjustable in the render engine: return global::renderEngine->screenSpaceRotation() * inverseRotation; } glm::mat4 ScreenSpaceRenderable::localRotationMatrix() { glm::mat4 rotation = glm::mat4(1.f); if (_faceCamera) { glm::vec3 translation = _useRadiusAzimuthElevation ? sphericalToCartesian(raeToSpherical(_raePosition)) : _cartesianPosition; rotation = glm::inverse(glm::lookAt( glm::vec3(0.f), glm::normalize(translation), glm::vec3(0.f, 1.f, 0.f) )); } float roll = _localRotation.value().x; float pitch = _localRotation.value().y; float yaw = _localRotation.value().z; return rotation * glm::mat4(glm::quat(glm::vec3(pitch, yaw, roll))); } glm::vec3 ScreenSpaceRenderable::raeToCartesian(const glm::vec3& rae) const { return sphericalToCartesian(raeToSpherical(rae)); } glm::vec3 ScreenSpaceRenderable::cartesianToRae(const glm::vec3& cartesian) const { return sphericalToRae(cartesianToSpherical(cartesian)); } glm::mat4 ScreenSpaceRenderable::translationMatrix() { glm::vec3 translation = _useRadiusAzimuthElevation ? sphericalToCartesian(raeToSpherical(_raePosition)) : _cartesianPosition; return glm::translate(glm::mat4(1.f), translation); } void ScreenSpaceRenderable::draw(glm::mat4 modelTransform) { glDisable(GL_CULL_FACE); _shader->activate(); _shader->setUniform(_uniformCache.color, _multiplyColor); _shader->setUniform(_uniformCache.opacity, opacity()); _shader->setUniform(_uniformCache.backgroundColor, _backgroundColor); _shader->setUniform(_uniformCache.gamma, _gamma); _shader->setUniform( _uniformCache.mvp, global::renderEngine->scene()->camera()->viewProjectionMatrix() * modelTransform ); ghoul::opengl::TextureUnit unit; unit.activate(); bindTexture(); _shader->setUniform(_uniformCache.texture, unit); glBindVertexArray(rendering::helper::vertexObjects.square.vao); glDrawArrays(GL_TRIANGLES, 0, 6); glEnable(GL_CULL_FACE); _shader->deactivate(); unbindTexture(); } void ScreenSpaceRenderable::unbindTexture() {} glm::vec3 ScreenSpaceRenderable::sanitizeSphericalCoordinates(glm::vec3 spherical) const { const float r = spherical.x; float phi = spherical.z; // Sanitize coordinates. float theta = wrap(spherical.y, 0.f, glm::two_pi()); if (theta > glm::pi()) { theta = glm::two_pi() - theta; phi += glm::pi(); } return glm::vec3(r, theta, phi); } glm::vec3 ScreenSpaceRenderable::sphericalToCartesian(glm::vec3 spherical) const { // First convert to ISO convention spherical coordinates according to // https://en.wikipedia.org/wiki/Spherical_coordinate_system // (radius, theta, phi), where theta is the polar angle from the z axis, // and phi is the azimuth. const glm::vec3 sanitized = sanitizeSphericalCoordinates(std::move(spherical)); const float x = sanitized[0] * sin(sanitized[1]) * cos(sanitized[2]); const float y = sanitized[0] * sin(sanitized[1]) * sin(sanitized[2]); const float z = sanitized[0] * cos(sanitized[1]); // Now, convert rotate the coordinate system, so that z maps to y, // and y maps to -z. We want the pole to be in y instead of z. return glm::vec3(x, -z, y); } glm::vec3 ScreenSpaceRenderable::cartesianToSpherical(const glm::vec3& cartesian) const { // Rotate cartesian coordinates. glm::vec3 rotated = glm::vec3(cartesian.x, cartesian.z, -cartesian.y); const float r = sqrt( pow(rotated.x, 2.f) + pow(rotated.y, 2.f) + pow(rotated.z, 2.f) ); const float theta = acos(rotated.z / r); const float phi = atan2(rotated.y, rotated.x); return sanitizeSphericalCoordinates(glm::vec3(r, theta, phi)); } // Radius, azimiuth, elevation to spherical coordinates. glm::vec3 ScreenSpaceRenderable::raeToSpherical(glm::vec3 rae) const { //return rae; const float r = rae.x; // Polar angle, theta, is elevation + pi/2. const float theta = rae.z + glm::half_pi(); // Azimuth in ISO spherical coordiantes (phi) is angle from x, // as opposed to from negative y on screen. const float phi = rae.y - glm::half_pi(); return glm::vec3(r, theta, phi); } // Spherical coordinates to radius, azimuth and elevation. glm::vec3 ScreenSpaceRenderable::sphericalToRae(glm::vec3 spherical) const { //return spherical; const float r = spherical.x; // Azimuth on screen is angle from negative y, as opposed to from x. float azimuth = spherical.z + glm::half_pi(); // Elevation is polar angle - pi/2 float elevation = wrap( spherical.y - glm::half_pi(), -glm::pi(), glm::pi() ); return glm::vec3( r, wrap(azimuth, -glm::pi(), glm::pi()), wrap(elevation, -glm::pi(), glm::pi()) ); } float ScreenSpaceRenderable::opacity() const { return _opacity * _fade; } } // namespace openspace