/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2019 * * * * 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 namespace { constexpr const char* KeyType = "Type"; constexpr const char* KeyTag = "Tag"; constexpr const std::array UniformNames = { "Alpha", "ModelTransform", "ViewProjectionMatrix", "texture1" }; 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 AlphaInfo = { "Alpha", "Transparency", "This value determines the transparency 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 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." }; float wrap(float value, float min, float max) { return glm::mod(value - min, max - min) + min; } glm::vec3 sanitizeSphericalCoordinates(glm::vec3 spherical) { 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 sphericalToCartesian(glm::vec3 spherical) { // 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 cartesianToSpherical(const glm::vec3& cartesian) { // 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 raeToSpherical(glm::vec3 rae) { //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 sphericalToRae(glm::vec3 spherical) { //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()) ); } } // namespace namespace openspace { documentation::Documentation ScreenSpaceRenderable::Documentation() { using namespace openspace::documentation; return { "Screenspace Renderable", "core_screenspacerenderable", { { KeyType, new StringAnnotationVerifier("Must name a valid Screenspace renderable"), Optional::No, "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." }, { KeyName, new StringVerifier, Optional::Yes, "Specifies the name of this screenspace renderable. This does not have " "to be unique to the scene, but it is recommended to be." }, { KeyIdentifier, new StringVerifier, Optional::Yes, "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. The identifier is not allowed to have any " "whitespace or '.' and must not be empty." }, { EnabledInfo.identifier, new BoolVerifier, Optional::Yes, EnabledInfo.description }, { UseRadiusAzimuthElevationInfo.identifier, new BoolVerifier, Optional::Yes, UseRadiusAzimuthElevationInfo.description }, { FaceCameraInfo.identifier, new BoolVerifier, Optional::Yes, FaceCameraInfo.description }, { CartesianPositionInfo.identifier, new DoubleVector3Verifier, Optional::Yes, CartesianPositionInfo.description }, { RadiusAzimuthElevationInfo.identifier, new DoubleVector3Verifier, Optional::Yes, RadiusAzimuthElevationInfo.description }, { ScaleInfo.identifier, new DoubleVerifier, Optional::Yes, ScaleInfo.description }, { AlphaInfo.identifier, new DoubleVerifier, Optional::Yes, AlphaInfo.description }, { KeyTag, new OrVerifier({ new StringVerifier, new StringListVerifier }), Optional::Yes, "Defines either a single or multiple tags that apply to this " "ScreenSpaceRenderable, thus making it possible to address multiple, " "seprate Renderables with a single property change." } } }; } std::unique_ptr ScreenSpaceRenderable::createFromDictionary( const ghoul::Dictionary& dictionary) { documentation::testSpecificationAndThrow( Documentation(), dictionary, "ScreenSpaceRenderable" ); const std::string& renderableType = dictionary.value(KeyType); return FactoryManager::ref().factory()->create( renderableType, dictionary ); } std::string ScreenSpaceRenderable::makeUniqueIdentifier(std::string name) { std::vector r = global::renderEngine.screenSpaceRenderables(); auto nameTaken = [&r](const std::string& name) { bool nameTaken = std::any_of( r.begin(), r.end(), [&name](ScreenSpaceRenderable* r) { return r->identifier() == name; } ); return nameTaken; }; 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) , _alpha(AlphaInfo, 1.f, 0.f, 1.f) , _delete(DeleteInfo) { if (dictionary.hasKey(KeyIdentifier)) { setIdentifier(dictionary.value(KeyIdentifier)); } if (dictionary.hasKey(KeyName)) { setGuiName(dictionary.value(KeyName)); } addProperty(_enabled); addProperty(_useRadiusAzimuthElevation); addProperty(_usePerspectiveProjection); addProperty(_faceCamera); addProperty(_cartesianPosition); addProperty(_raePosition); // Setting spherical/euclidean onchange handler _useRadiusAzimuthElevation.onChange([this]() { if (_useRadiusAzimuthElevation) { _raePosition = sphericalToRae(cartesianToSpherical(_cartesianPosition)); } else { _cartesianPosition = sphericalToCartesian(raeToSpherical(_raePosition)); } }); addProperty(_scale); addProperty(_alpha); addProperty(_localRotation); if (dictionary.hasKey(EnabledInfo.identifier)) { _enabled = dictionary.value(EnabledInfo.identifier); } if (dictionary.hasKey(UseRadiusAzimuthElevationInfo.identifier)) { _useRadiusAzimuthElevation = dictionary.value( UseRadiusAzimuthElevationInfo.identifier ); } if (_useRadiusAzimuthElevation) { if (dictionary.hasKey(RadiusAzimuthElevationInfo.identifier)) { _raePosition = dictionary.value( RadiusAzimuthElevationInfo.identifier ); } } else { if (dictionary.hasKey(CartesianPositionInfo.identifier)) { _cartesianPosition = dictionary.value( CartesianPositionInfo.identifier ); } } if (dictionary.hasKey(ScaleInfo.identifier)) { _scale = static_cast(dictionary.value(ScaleInfo.identifier)); } if (dictionary.hasKey(AlphaInfo.identifier)) { _alpha = static_cast(dictionary.value(AlphaInfo.identifier)); } if (dictionary.hasKey(UsePerspectiveProjectionInfo.identifier)) { _usePerspectiveProjection = static_cast( dictionary.value(UsePerspectiveProjectionInfo.identifier) ); } if (dictionary.hasKey(FaceCameraInfo.identifier)) { _faceCamera = static_cast( dictionary.value(FaceCameraInfo.identifier) ); } if (dictionary.hasKeyAndValue(KeyTag)) { std::string tagName = dictionary.value(KeyTag); if (!tagName.empty()) { addTag(std::move(tagName)); } } else if (dictionary.hasKeyAndValue(KeyTag)) { const ghoul::Dictionary& tagNames = dictionary.value(KeyTag); const std::vector& keys = tagNames.keys(); for (const std::string& key : keys) { std::string tagName = tagNames.value(key); if (!tagName.empty()) { addTag(std::move(tagName)); } } } _delete.onChange([this](){ std::string script = "openspace.removeScreenSpaceRenderable('" + identifier() + "');"; global::scriptEngine.queueScript( script, scripting::ScriptEngine::RemoteScripting::No ); }); addProperty(_delete); } ScreenSpaceRenderable::~ScreenSpaceRenderable() {} // NOLINT 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; } float ScreenSpaceRenderable::depth() { return _useRadiusAzimuthElevation ? _raePosition.value().x : cartesianToSpherical(_cartesianPosition).x; } void ScreenSpaceRenderable::createShaders() { ghoul::Dictionary dict = ghoul::Dictionary(); auto res = global::windowDelegate.currentDrawBufferResolution(); ghoul::Dictionary rendererData = { { "fragmentRendererPath", "${SHADERS}/framebuffer/renderframebuffer.frag" }, { "windowWidth" , res.x }, { "windowHeight" , res.y }, { "hdrExposure", global::renderEngine.hdrExposure() }, { "disableHDR", global::renderEngine.isHdrDisabled() } }; dict.setValue("rendererData", rendererData); dict.setValue("fragmentPath", "${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() { glm::vec2 resolution = global::windowDelegate.currentDrawBufferResolution(); //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, _scale * textureRatio, 1.f) ); // Simulate orthographic projection by distance to plane. if (!_usePerspectiveProjection) { float distance = _useRadiusAzimuthElevation ? _raePosition.value().x : -_cartesianPosition.value().z; scale = glm::scale(scale, glm::vec3(distance)); } return scale; } 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::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.alpha, _alpha); _shader->setUniform(_uniformCache.modelTransform, modelTransform); _shader->setUniform( _uniformCache.viewProj, global::renderEngine.scene()->camera()->viewProjectionMatrix() ); 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() { } } // namespace openspace