/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2018 * * * * 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 namespace { constexpr const char* _loggerCat = "RenderableModelProjection"; constexpr const char* keyGeometry = "Geometry"; constexpr const char* keyProjection = "Projection"; constexpr const char* keyBoundingSphereRadius = "BoundingSphereRadius"; constexpr const char* DestinationFrame = "GALACTIC"; static const openspace::properties::Property::PropertyInfo ColorTextureInfo = { "ColorTexture", "Color Base Texture", "This is the path to a local image file that is used as the base texture for the " "model on which the image projections are layered." }; static const openspace::properties::Property::PropertyInfo PerformShadingInfo = { "PerformShading", "Perform Shading", "If this value is enabled, the model will be shaded based on the relative " "location to the Sun. If this value is disabled, shading is disabled and the " "entire model is rendered brightly." }; } // namespace namespace openspace { documentation::Documentation RenderableModelProjection::Documentation() { using namespace documentation; return { "Renderable Model Projection", "newhorizons_renderable_modelprojection", { { "Type", new StringEqualVerifier("RenderableModelProjection"), Optional::No }, { keyGeometry, new ReferencingVerifier("base_geometry_model"), Optional::No, "The geometry that is used for rendering this model." }, { keyProjection, new ReferencingVerifier("newhorizons_projectioncomponent"), Optional::No, "Contains information about projecting onto this planet." }, { ColorTextureInfo.identifier, new StringVerifier, Optional::No, ColorTextureInfo.description }, { PerformShadingInfo.identifier, new BoolVerifier, Optional::Yes, PerformShadingInfo.description }, { keyBoundingSphereRadius, new DoubleVerifier, Optional::Yes, "The radius of the bounding sphere of this object. This has to be a " "radius that is larger than anything that is rendered by it. It has to " "be at least as big as the convex hull of the object. The default value " "is 10e9 meters." } } }; } RenderableModelProjection::RenderableModelProjection(const ghoul::Dictionary& dictionary) : Renderable(dictionary) , _colorTexturePath(ColorTextureInfo) , _programObject(nullptr) , _fboProgramObject(nullptr) , _baseTexture(nullptr) , _geometry(nullptr) , _performShading(PerformShadingInfo, true) { documentation::testSpecificationAndThrow( Documentation(), dictionary, "RenderableModelProjection" ); std::string name; [[ maybe_unused ]] bool success = dictionary.getValue(SceneGraphNode::KeyName, name); ghoul_assert(success, "Name was not passed to RenderableModelProjection"); using ghoul::Dictionary; Dictionary geometryDictionary = dictionary.value(keyGeometry); using modelgeometry::ModelGeometry; geometryDictionary.setValue(SceneGraphNode::KeyName, name); _geometry = ModelGeometry::createFromDictionary(geometryDictionary); _colorTexturePath = absPath(dictionary.value( ColorTextureInfo.identifier )); addPropertySubOwner(_geometry.get()); addPropertySubOwner(_projectionComponent); addProperty(_colorTexturePath); _colorTexturePath.onChange(std::bind(&RenderableModelProjection::loadTextures, this)); _projectionComponent.initialize( dictionary.value(keyProjection) ); float boundingSphereRadius = 1.0e9; dictionary.getValue(keyBoundingSphereRadius, boundingSphereRadius); setBoundingSphere(boundingSphereRadius); if (dictionary.hasKey(PerformShadingInfo.identifier)) { _performShading = dictionary.value(PerformShadingInfo.identifier); } Renderable::addProperty(_performShading); } // This empty method needs to be here in order to use forward declaration with // std::unique_ptr RenderableModelProjection::~RenderableModelProjection() {} bool RenderableModelProjection::isReady() const { bool ready = true; ready &= (_programObject != nullptr); ready &= (_baseTexture != nullptr); ready &= (_projectionComponent.isReady()); return ready; } void RenderableModelProjection::initializeGL() { RenderEngine& renderEngine = OsEng.renderEngine(); _programObject = renderEngine.buildRenderProgram( "ModelShader", absPath("${MODULE_SPACECRAFTINSTRUMENTS}/shaders/renderableModel_vs.glsl"), absPath("${MODULE_SPACECRAFTINSTRUMENTS}/shaders/renderableModel_fs.glsl") ); _mainUniformCache.performShading = _programObject->uniformLocation("_performShading"); _mainUniformCache.directionToSunViewSpace = _programObject->uniformLocation( "directionToSunViewSpace" ); _mainUniformCache.modelViewTransform = _programObject->uniformLocation( "modelViewTransform" ); _mainUniformCache.projectionTransform = _programObject->uniformLocation( "projectionTransform" ); _mainUniformCache.projectionFading = _programObject->uniformLocation( "_projectionFading" ); _mainUniformCache.baseTexture = _programObject->uniformLocation( "baseTexture" ); _mainUniformCache.projectionTexture = _programObject->uniformLocation( "projectionTexture" ); _fboProgramObject = ghoul::opengl::ProgramObject::Build( "ProjectionPass", absPath( "${MODULE_SPACECRAFTINSTRUMENTS}/shaders/renderableModelProjection_vs.glsl" ), absPath( "${MODULE_SPACECRAFTINSTRUMENTS}/shaders/renderableModelProjection_fs.glsl" ) ); _fboUniformCache.projectionTexture = _fboProgramObject->uniformLocation( "projectionTexture" ); _fboUniformCache.needShadowMap = _fboProgramObject->uniformLocation("needShadowMap"); _fboUniformCache.ProjectorMatrix = _fboProgramObject->uniformLocation( "ProjectorMatrix" ); _fboUniformCache.ModelTransform = _fboProgramObject->uniformLocation("ModelTransform"); _fboUniformCache.boresight = _fboProgramObject->uniformLocation("boresight"); _depthFboProgramObject = ghoul::opengl::ProgramObject::Build( "DepthPass", absPath("${MODULE_SPACECRAFTINSTRUMENTS}/shaders/renderableModelDepth_vs.glsl"), absPath("${MODULE_SPACECRAFTINSTRUMENTS}/shaders/renderableModelDepth_fs.glsl") ); _depthFboUniformCache.ProjectorMatrix = _depthFboProgramObject->uniformLocation( "ProjectorMatrix" ); _depthFboUniformCache.ModelTransform = _depthFboProgramObject->uniformLocation( "ModelTransform" ); loadTextures(); _projectionComponent.initializeGL(); float bs = boundingSphere(); _geometry->initialize(this); setBoundingSphere(bs); // ignore bounding sphere set by geometry. } void RenderableModelProjection::deinitializeGL() { if (_geometry) { _geometry->deinitialize(); } _geometry = nullptr; _baseTexture = nullptr; _projectionComponent.deinitialize(); OsEng.renderEngine().removeRenderProgram(_programObject); _programObject = nullptr; } ghoul::opengl::Texture& RenderableModelProjection::baseTexture() const { return _projectionComponent.projectionTexture(); } void RenderableModelProjection::render(const RenderData& data, RendererTasks&) { if (_projectionComponent.needsClearProjection()) { _projectionComponent.clearAllProjections(); } _up = data.camera.lookUpVectorCameraSpace(); if (_capture && _projectionComponent.doesPerformProjection()) { project(); } _programObject->activate(); attitudeParameters(_time); _imageTimes.clear(); // Calculate variables to be used as uniform variables in shader glm::dvec3 bodyPosition = data.modelTransform.translation; // Model transform and view transform needs to be in double precision glm::dmat4 modelTransform = glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * // Translation glm::dmat4(data.modelTransform.rotation) * // Rotation glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale)); // Scale glm::dmat4 modelViewTransform = data.camera.combinedViewMatrix() * modelTransform; glm::vec3 directionToSun = glm::normalize( _sunPosition.vec3() - glm::vec3(bodyPosition) ); glm::vec3 directionToSunViewSpace = glm::mat3( data.camera.combinedViewMatrix() ) * directionToSun; _programObject->setUniform(_mainUniformCache.performShading, _performShading); _programObject->setUniform( _mainUniformCache.directionToSunViewSpace, directionToSunViewSpace ); _programObject->setUniform( _mainUniformCache.modelViewTransform, glm::mat4(modelViewTransform) ); _programObject->setUniform( _mainUniformCache.projectionTransform, data.camera.projectionMatrix() ); _programObject->setUniform( _mainUniformCache.projectionFading, _projectionComponent.projectionFading() ); _geometry->setUniforms(*_programObject); ghoul::opengl::TextureUnit unit[2]; unit[0].activate(); _baseTexture->bind(); _programObject->setUniform(_mainUniformCache.baseTexture, unit[0]); unit[1].activate(); _projectionComponent.projectionTexture().bind(); _programObject->setUniform(_mainUniformCache.projectionTexture, unit[1]); _geometry->render(); _programObject->deactivate(); } void RenderableModelProjection::update(const UpdateData& data) { if (_programObject->isDirty()) { _programObject->rebuildFromFile(); _mainUniformCache.performShading = _programObject->uniformLocation( "_performShading" ); _mainUniformCache.directionToSunViewSpace = _programObject->uniformLocation( "directionToSunViewSpace" ); _mainUniformCache.modelViewTransform = _programObject->uniformLocation( "modelViewTransform" ); _mainUniformCache.projectionTransform = _programObject->uniformLocation( "projectionTransform" ); _mainUniformCache.projectionFading = _programObject->uniformLocation( "_projectionFading" ); _mainUniformCache.baseTexture = _programObject->uniformLocation( "baseTexture" ); _mainUniformCache.projectionTexture = _programObject->uniformLocation( "projectionTexture" ); } if (_fboProgramObject->isDirty()) { _fboProgramObject->rebuildFromFile(); _fboUniformCache.projectionTexture = _fboProgramObject->uniformLocation( "projectionTexture" ); _fboUniformCache.needShadowMap = _fboProgramObject->uniformLocation( "needShadowMap" ); _fboUniformCache.ProjectorMatrix = _fboProgramObject->uniformLocation( "ProjectorMatrix" ); _fboUniformCache.ModelTransform = _fboProgramObject->uniformLocation( "ModelTransform" ); _fboUniformCache.boresight = _fboProgramObject->uniformLocation("boresight"); } _projectionComponent.update(); if (_depthFboProgramObject->isDirty()) { _depthFboProgramObject->rebuildFromFile(); _depthFboUniformCache.ProjectorMatrix = _depthFboProgramObject->uniformLocation( "ProjectorMatrix" ); _depthFboUniformCache.ModelTransform = _depthFboProgramObject->uniformLocation( "ModelTransform" ); } const double time = data.time.j2000Seconds(); // Only project new images if time changed since last update. if (time != _time) { if (openspace::ImageSequencer::ref().isReady()) { openspace::ImageSequencer::ref().updateSequencer(time); if (_projectionComponent.doesPerformProjection()) { _capture = openspace::ImageSequencer::ref().getImagePaths( _imageTimes, _projectionComponent.projecteeId(), _projectionComponent.instrumentId(), _time ); } } _time = time; } _stateMatrix = data.modelTransform.rotation; glm::dvec3 p = OsEng.renderEngine().scene()->sceneGraphNode("Sun")->worldPosition() - data.modelTransform.translation; _sunPosition = PowerScaledCoordinate::CreatePowerScaledCoordinate(p.x, p.y, p.z); } void RenderableModelProjection::imageProjectGPU( std::shared_ptr projectionTexture) { if (_projectionComponent.needsShadowMap()) { _projectionComponent.depthMapRenderBegin(); _depthFboProgramObject->activate(); _depthFboProgramObject->setUniform( _depthFboUniformCache.ProjectorMatrix, _projectorMatrix ); _depthFboProgramObject->setUniform( _depthFboUniformCache.ModelTransform, _transform ); _geometry->setUniforms(*_fboProgramObject); _geometry->render(); _depthFboProgramObject->deactivate(); _projectionComponent.depthMapRenderEnd(); } _projectionComponent.imageProjectBegin(); _fboProgramObject->activate(); ghoul::opengl::TextureUnit unitFbo; unitFbo.activate(); projectionTexture->bind(); _fboProgramObject->setUniform(_fboUniformCache.projectionTexture, unitFbo); _fboProgramObject->setUniform( _fboUniformCache.needShadowMap, _projectionComponent.needsShadowMap() ); ghoul::opengl::TextureUnit unitDepthFbo; if (_projectionComponent.needsShadowMap()) { unitDepthFbo.activate(); _projectionComponent.depthTexture().bind(); _fboProgramObject->setUniform("depthTexture", unitDepthFbo); } _fboProgramObject->setUniform(_fboUniformCache.ProjectorMatrix, _projectorMatrix); _fboProgramObject->setUniform(_fboUniformCache.ModelTransform, _transform); _fboProgramObject->setUniform(_fboUniformCache.boresight, _boresight); _geometry->setUniforms(*_fboProgramObject); _geometry->render(); _fboProgramObject->deactivate(); _projectionComponent.imageProjectEnd(); } void RenderableModelProjection::attitudeParameters(double time) { try { _instrumentMatrix = SpiceManager::ref().positionTransformMatrix( _projectionComponent.instrumentId(), DestinationFrame, time ); } catch (const SpiceManager::SpiceException&) { return; } for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { _transform[i][j] = static_cast(_stateMatrix[i][j]); } } glm::dvec3 boresight; try { SpiceManager::FieldOfViewResult res = SpiceManager::ref().fieldOfView( _projectionComponent.instrumentId() ); boresight = std::move(res.boresightVector); } catch (const SpiceManager::SpiceException&) { return; } double lightTime; glm::dvec3 p = SpiceManager::ref().targetPosition( _projectionComponent.projectorId(), _projectionComponent.projecteeId(), DestinationFrame, _projectionComponent.aberration(), time, lightTime ); psc position = PowerScaledCoordinate::CreatePowerScaledCoordinate(p.x, p.y, p.z); position[3] += 4; glm::vec3 cpos = position.vec3(); float distance = glm::length(cpos); float radius = boundingSphere(); _projectorMatrix = _projectionComponent.computeProjectorMatrix( cpos, boresight, _up, _instrumentMatrix, _projectionComponent.fieldOfViewY(), _projectionComponent.aspectRatio(), distance - radius, distance + radius, _boresight ); } void RenderableModelProjection::project() { for (auto img : _imageTimes) { attitudeParameters(img.timeRange.start); auto projTexture = _projectionComponent.loadProjectionTexture( img.path, img.isPlaceholder ); imageProjectGPU(projTexture); } _capture = false; } bool RenderableModelProjection::loadTextures() { _baseTexture = nullptr; if (_colorTexturePath.value() != "") { _baseTexture = ghoul::io::TextureReader::ref().loadTexture( absPath(_colorTexturePath) ); if (_baseTexture) { LDEBUG("Loaded texture from '" << absPath(_colorTexturePath) << "'"); _baseTexture->uploadTexture(); _baseTexture->setFilter(ghoul::opengl::Texture::FilterMode::LinearMipMap); } } return _baseTexture != nullptr; } } // namespace openspace