/***************************************************************************************** * * * 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 namespace { constexpr const char* keyFrame = "Frame"; constexpr const char* keyRadii = "Radii"; constexpr const char* keySegmentsPerPatch = "SegmentsPerPatch"; constexpr const char* keyLayers = "Layers"; constexpr const char* keyShadowGroup = "ShadowGroup"; constexpr const char* keyShadowSource = "Source"; constexpr const char* keyShadowCaster = "Caster"; constexpr openspace::properties::Property::PropertyInfo SaveOrThrowInfo = { "SaveOrThrowCamera", "Save or throw camera", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo ShowChunkEdgeInfo = { "ShowChunkEdges", "Show chunk edges", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo ShowChunkBoundsInfo = { "ShowChunkBounds", "Show chunk bounds", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo ShowChunkAABBInfo = { "ShowChunkAABB", "Show chunk AABB", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo HeightResolutionInfo = { "ShowHeightResolution", "Show height resolution", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo HeightIntensityInfo = { "ShowHeightIntensities", "Show height intensities", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo FrustumCullingInfo = { "PerformFrustumCulling", "Perform frustum culling", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo HorizonCullingInfo = { "PerformHorizonCulling", "Perform horizon culling", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo LevelProjectedAreaInfo = { "LevelByProjectedAreaElseDistance", "Level by projected area (else distance)", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo ResetTileProviderInfo = { "ResetTileProviders", "Reset tile providers", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo CollectStatsInfo = { "CollectStats", "Collect stats", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo LimitLevelInfo = { "LimitLevelByAvailableData", "Limit level by available data", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo ModelSpaceRenderingInfo = { "ModelSpaceRenderingCutoffLevel", "Model Space Rendering Cutoff Level", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo PerformShadingInfo = { "PerformShading", "Perform shading", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo AtmosphereInfo = { "Atmosphere", "Atmosphere", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo AccurateNormalsInfo = { "UseAccurateNormals", "Use Accurate Normals", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo EclipseInfo = { "Eclipse", "Eclipse", "Enables/Disable Eclipse shadows" }; constexpr openspace::properties::Property::PropertyInfo EclipseHardShadowsInfo = { "EclipseHardShadows", "Eclipse Hard Shadows", "Enables the rendering of eclipse shadows using hard shadows" }; constexpr openspace::properties::Property::PropertyInfo LodScaleFactorInfo = { "LodScaleFactor", "Level of Detail Scale Factor", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo CameraMinHeightInfo = { "CameraMinHeight", "Camera Minimum Height", "" // @TODO Missing documentation }; constexpr openspace::properties::Property::PropertyInfo OrenNayarRoughnessInfo = { "OrenNayarRoughness", "orenNayarRoughness", "" // @TODO Missing documentation }; } // namespace using namespace openspace::properties; namespace openspace::globebrowsing { RenderableGlobe::RenderableGlobe(const ghoul::Dictionary& dictionary) : Renderable(dictionary) , _debugProperties({ BoolProperty(SaveOrThrowInfo, false), BoolProperty(ShowChunkEdgeInfo, false), BoolProperty(ShowChunkBoundsInfo, false), BoolProperty(ShowChunkAABBInfo, false), BoolProperty(HeightResolutionInfo, false), BoolProperty(HeightIntensityInfo, false), BoolProperty(FrustumCullingInfo, true), BoolProperty(HorizonCullingInfo, true), BoolProperty(LevelProjectedAreaInfo, false), BoolProperty(ResetTileProviderInfo, false), BoolProperty(CollectStatsInfo, false), BoolProperty(LimitLevelInfo, true), IntProperty(ModelSpaceRenderingInfo, 10, 1, 22) }) , _generalProperties({ BoolProperty(PerformShadingInfo, true), BoolProperty(AtmosphereInfo, false), BoolProperty(AccurateNormalsInfo, false), BoolProperty(EclipseInfo, false), BoolProperty(EclipseHardShadowsInfo, false), FloatProperty(LodScaleFactorInfo, 10.f, 1.f, 50.f), FloatProperty(CameraMinHeightInfo, 100.f, 0.f, 1000.f), FloatProperty(OrenNayarRoughnessInfo, 0.f, 0.f, 1.f) }) , _debugPropertyOwner({ "Debug" }) { setIdentifier("RenderableGlobe"); dictionary.getValue(keyFrame, _frame); // Read the radii in to its own dictionary if (dictionary.hasKeyAndValue(keyRadii)) { const glm::dvec3 radii = dictionary.value(keyRadii); _ellipsoid = Ellipsoid(radii); setBoundingSphere(static_cast(_ellipsoid.maximumRadius())); } else if (dictionary.hasKeyAndValue(keyRadii)) { const double radius = dictionary.value(keyRadii); _ellipsoid = Ellipsoid({ radius, radius, radius }); setBoundingSphere(static_cast(_ellipsoid.maximumRadius())); } // Ghoul can't read ints from lua dictionaries... double patchSegmentsd; dictionary.getValue(keySegmentsPerPatch, patchSegmentsd); int patchSegments = static_cast(patchSegmentsd); if (dictionary.hasValue("PerformShading")) { _generalProperties.performShading = dictionary.value("PerformShading"); } // Init layer manager ghoul::Dictionary layersDictionary; if (!dictionary.getValue(keyLayers, layersDictionary)) { throw ghoul::RuntimeError(std::string(keyLayers) + " must be specified"); } _layerManager = std::make_shared(layersDictionary); _chunkedLodGlobe = std::make_unique( *this, patchSegments, _layerManager, _ellipsoid ); //_pointGlobe = std::make_shared(*this); //_distanceSwitch.addSwitchValue(_chunkedLodGlobe); //_distanceSwitch.addSwitchValue(_pointGlobe); addProperty(_generalProperties.atmosphereEnabled); addProperty(_generalProperties.performShading); addProperty(_generalProperties.useAccurateNormals); addProperty(_generalProperties.eclipseShadowsEnabled); addProperty(_generalProperties.eclipseHardShadows); addProperty(_generalProperties.lodScaleFactor); addProperty(_generalProperties.cameraMinHeight); addProperty(_generalProperties.orenNayarRoughness); _debugPropertyOwner.addProperty(_debugProperties.saveOrThrowCamera); _debugPropertyOwner.addProperty(_debugProperties.showChunkEdges); _debugPropertyOwner.addProperty(_debugProperties.showChunkBounds); _debugPropertyOwner.addProperty(_debugProperties.showChunkAABB); _debugPropertyOwner.addProperty(_debugProperties.showHeightResolution); _debugPropertyOwner.addProperty(_debugProperties.showHeightIntensities); _debugPropertyOwner.addProperty(_debugProperties.performFrustumCulling); _debugPropertyOwner.addProperty(_debugProperties.performHorizonCulling); _debugPropertyOwner.addProperty( _debugProperties.levelByProjectedAreaElseDistance ); _debugPropertyOwner.addProperty(_debugProperties.resetTileProviders); _debugPropertyOwner.addProperty(_debugProperties.collectStats); _debugPropertyOwner.addProperty(_debugProperties.limitLevelByAvailableData); _debugPropertyOwner.addProperty(_debugProperties.modelSpaceRenderingCutoffLevel); auto notifyShaderRecompilation = [&](){ _chunkedLodGlobe->notifyShaderRecompilation(); }; _generalProperties.atmosphereEnabled.onChange(notifyShaderRecompilation); _generalProperties.useAccurateNormals.onChange(notifyShaderRecompilation); _generalProperties.eclipseShadowsEnabled.onChange(notifyShaderRecompilation); _generalProperties.eclipseHardShadows.onChange(notifyShaderRecompilation); _generalProperties.performShading.onChange(notifyShaderRecompilation); _debugProperties.showChunkEdges.onChange(notifyShaderRecompilation); _debugProperties.showHeightResolution.onChange(notifyShaderRecompilation); _debugProperties.showHeightIntensities.onChange(notifyShaderRecompilation); _layerManager->onChange(notifyShaderRecompilation); addPropertySubOwner(_debugPropertyOwner); addPropertySubOwner(_layerManager.get()); //addPropertySubOwner(_pointGlobe.get()); //================================================================ //======== Reads Shadow (Eclipses) Entries in mod file =========== //================================================================ ghoul::Dictionary shadowDictionary; bool success = dictionary.getValue(keyShadowGroup, shadowDictionary); bool disableShadows = false; if (success) { std::vector> sourceArray; unsigned int sourceCounter = 1; while (success) { std::string sourceName; success = shadowDictionary.getValue(keyShadowSource + std::to_string(sourceCounter) + ".Name", sourceName); if (success) { double sourceRadius; success = shadowDictionary.getValue(keyShadowSource + std::to_string(sourceCounter) + ".Radius", sourceRadius); if (success) { sourceArray.emplace_back(sourceName, sourceRadius); } else { //LWARNING("No Radius value expecified for Shadow Source Name " // << sourceName << " from " << name // << " planet.\nDisabling shadows for this planet."); disableShadows = true; break; } } sourceCounter++; } if (!disableShadows && !sourceArray.empty()) { success = true; std::vector> casterArray; unsigned int casterCounter = 1; while (success) { std::string casterName; success = shadowDictionary.getValue(keyShadowCaster + std::to_string(casterCounter) + ".Name", casterName); if (success) { double casterRadius; success = shadowDictionary.getValue(keyShadowCaster + std::to_string(casterCounter) + ".Radius", casterRadius); if (success) { casterArray.emplace_back(casterName, casterRadius); } else { //LWARNING("No Radius value expecified for Shadow Caster Name " // << casterName << " from " << name // << " planet.\nDisabling shadows for this planet."); disableShadows = true; break; } } casterCounter++; } std::vector shadowConfArray; if (!disableShadows && (!sourceArray.empty() && !casterArray.empty())) { for (const auto & source : sourceArray) { for (const auto & caster : casterArray) { Ellipsoid::ShadowConfiguration sc; sc.source = source; sc.caster = caster; shadowConfArray.push_back(sc); } } _ellipsoid.setShadowConfigurationArray(shadowConfArray); } } } } void RenderableGlobe::initializeGL() { _layerManager->initialize(); _layerManager->update(); _chunkedLodGlobe->initializeGL(); // Recompile the shaders directly so that it is not done the first time the render // function is called. _chunkedLodGlobe->recompileShaders(); } void RenderableGlobe::deinitializeGL() { _chunkedLodGlobe->deinitializeGL(); _layerManager->deinitialize(); } bool RenderableGlobe::isReady() const { return true; } void RenderableGlobe::render(const RenderData& data, RendererTasks& rendererTask) { bool statsEnabled = _debugProperties.collectStats; #ifdef DEBUG_GLOBEBROWSING_STATSRECORD _chunkedLodGlobe->stats.setEnabled(statsEnabled); #else // DEBUG_GLOBEBROWSING_STATSRECORD if (statsEnabled) { LWARNINGC( "RenderableGlobe", "Stats collection was enabled, but ChunkedLodGlobe compiled without support" ); _debugProperties.collectStats = false; } #endif // DEBUG_GLOBEBROWSING_STATSRECORD if (_enabled) { if (_debugProperties.saveOrThrowCamera.value()) { _debugProperties.saveOrThrowCamera.setValue(false); if (savedCamera() == nullptr) { // save camera setSaveCamera(std::make_shared(data.camera)); } else { // throw camera setSaveCamera(nullptr); } } const double distanceToCamera = distance( data.camera.positionVec3(), data.modelTransform.translation ); // This distance will be enough to render the globe as one pixel if the field of // view is 'fov' radians and the screen resolution is 'res' pixels. const double fov = 2 * glm::pi() / 6; // 60 degrees const int res = 2880; const double distance = res * _chunkedLodGlobe->boundingSphere() / tan(fov / 2); if (distanceToCamera < distance) { _chunkedLodGlobe->render(data, rendererTask); } } if (_savedCamera != nullptr) { DebugRenderer::ref().renderCameraFrustum(data, *_savedCamera); } } void RenderableGlobe::update(const UpdateData& data) { _time = data.time.j2000Seconds(); _chunkedLodGlobe->update(data); glm::dmat4 translation = glm::translate(glm::dmat4(1.0), data.modelTransform.translation); glm::dmat4 rotation = glm::dmat4(data.modelTransform.rotation); glm::dmat4 scaling = glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale, data.modelTransform.scale, data.modelTransform.scale)); _cachedModelTransform = translation * rotation * scaling; _cachedInverseModelTransform = glm::inverse(_cachedModelTransform); if (_debugProperties.resetTileProviders) { _layerManager->reset(); _debugProperties.resetTileProviders = false; } _layerManager->update(); _chunkedLodGlobe->update(data); } glm::dvec3 RenderableGlobe::projectOnEllipsoid(glm::dvec3 position) { return _ellipsoid.geodeticSurfaceProjection(position); } float RenderableGlobe::getHeight(glm::dvec3 position) const { if (_chunkedLodGlobe) { return _chunkedLodGlobe->getHeight(position); } else { return 0; } } ChunkedLodGlobe* RenderableGlobe::chunkedLodGlobe() const{ return _chunkedLodGlobe.get(); } LayerManager* RenderableGlobe::layerManager() const { return _layerManager.get(); } const Ellipsoid& RenderableGlobe::ellipsoid() const{ return _ellipsoid; } const glm::dmat4& RenderableGlobe::modelTransform() const{ return _cachedModelTransform; } const glm::dmat4& RenderableGlobe::inverseModelTransform() const{ return _cachedInverseModelTransform; } const RenderableGlobe::DebugProperties& RenderableGlobe::debugProperties() const{ return _debugProperties; } const RenderableGlobe::GeneralProperties& RenderableGlobe::generalProperties() const{ return _generalProperties; } const std::shared_ptr RenderableGlobe::savedCamera() const { return _savedCamera; } SurfacePositionHandle RenderableGlobe::calculateSurfacePositionHandle( const glm::dvec3& targetModelSpace) const { glm::dvec3 centerToEllipsoidSurface = _ellipsoid.geodeticSurfaceProjection(targetModelSpace); glm::dvec3 ellipsoidSurfaceToTarget = targetModelSpace - centerToEllipsoidSurface; // ellipsoidSurfaceOutDirection will point towards the target, we want the outward // direction. Therefore it must be flipped in case the target is under the reference // ellipsoid so that it always points outwards glm::dvec3 ellipsoidSurfaceOutDirection = glm::normalize(ellipsoidSurfaceToTarget); if (glm::dot(ellipsoidSurfaceOutDirection, centerToEllipsoidSurface) < 0) { ellipsoidSurfaceOutDirection *= -1.0; } double heightToSurface = getHeight(targetModelSpace); heightToSurface = glm::isnan(heightToSurface) ? 0.0 : heightToSurface; centerToEllipsoidSurface = glm::isnan(glm::length(centerToEllipsoidSurface)) ? (glm::dvec3(0.0, 1.0, 0.0) * static_cast(boundingSphere())) : centerToEllipsoidSurface; ellipsoidSurfaceOutDirection = glm::isnan(glm::length(ellipsoidSurfaceOutDirection)) ? glm::dvec3(0.0, 1.0, 0.0) : ellipsoidSurfaceOutDirection; return { centerToEllipsoidSurface, ellipsoidSurfaceOutDirection, heightToSurface }; } void RenderableGlobe::setSaveCamera(std::shared_ptr camera) { _savedCamera = std::move(camera); } } // namespace openspace::globebrowsing