Feature/geojson (#2595)

Add the option to add geojson components to globes, from geojson files. One geojson file creates one GeoJsonComponent, which in turn may contain multiple GlobeGeometryFeatures

Geojson is a format that supports points, lines, and polygons. In addition to the basic functionality, extra features have been added that will long-term allow rendering the geometry needed to represent KML files (another format for geospatial geometry data). Here are links to references for both formats:

    Geojson: https://geojson.org/
    KML: https://developers.google.com/kml/documentation/kmlreference

data/assets/examples/geojson includes some example files that I have used for testing. Any geojson file can also be added through drag-n-drop. Note however that you might need to change the AltitudeMode or HeightOffset properties for the feature to be visible.
This commit is contained in:
Emma Broman
2023-04-15 11:35:28 +02:00
committed by GitHub
parent adbe0a93f2
commit c714a7f57d
39 changed files with 4583 additions and 7 deletions

3
.gitmodules vendored
View File

@@ -32,6 +32,9 @@
[submodule "support/coding/codegen"]
path = support/coding/codegen
url = https://github.com/OpenSpace/codegen
[submodule "modules/globebrowsing/ext/geos"]
path = modules/globebrowsing/ext/geos
url = https://github.com/OpenSpace/geos.git
[submodule "documentation"]
path = documentation
url = https://github.com/OpenSpace/OpenSpace-Documentation-Dist.git

View File

@@ -0,0 +1,49 @@
local earth = asset.require('scene/solarsystem/planets/earth/earth')
local earthIdentifier = earth.Earth.Identifier
local sun = asset.require("scene/solarsystem/sun/sun")
local data = asset.syncedResource({
Name = "GeoJSON Example Africa",
Type = "UrlSynchronization",
Identifier = "geojson_example_polygon_extruded_africa",
Url = "http://liu-se.cdn.openspaceproject.com/files/examples/geojson/polygon_extruded_africa.geojson"
})
local Example_Polygon = {
Identifier = "Earth-Polygon-withLights",
File = data .. "polygon_extruded_africa.geojson",
HeightOffset = 20000.0,
DefaultProperties = {
Color = { 0.0, 1.0, 0.0 },
FillColor = { 0.5, 0.6, 0.5 },
LineWidth = 0.5,
FillOpacity = 1.0,
PerformShading = true
},
LightSources = {
sun.LightSource
},
Name = "Extruded and Shaded Polygon (lit by Sun)"
}
asset.onInitialize(function()
openspace.globebrowsing.addGeoJson(earthIdentifier, Example_Polygon)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteGeoJson(earthIdentifier, Example_Polygon)
end)
asset.export(Example_Polygon)
asset.meta = {
Name = "GeoJson Example - Extruded and Shaded Polygon",
Version = "1.0",
Description = [[GeoJson example asset demonstrating how to apply shading from light
sources on polygons]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -0,0 +1,39 @@
local earth = asset.require('scene/solarsystem/planets/earth/earth')
local earthIdentifier = earth.Earth.Identifier
local data = asset.syncedResource({
Name = "GeoJSON Example Lines",
Type = "UrlSynchronization",
Identifier = "geojson_example_lines",
Url = "http://liu-se.cdn.openspaceproject.com/files/examples/geojson/lines.geojson"
})
local Example_Lines = {
Identifier = "Lines-Example",
File = data .. "lines.geojson",
HeightOffset = 20000.0,
DefaultProperties = {
LineWidth = 2.0
},
Name = "Example Lines"
}
asset.onInitialize(function()
openspace.globebrowsing.addGeoJson(earthIdentifier, Example_Lines)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteGeoJson(earthIdentifier, Example_Lines)
end)
asset.export(Example_Lines)
asset.meta = {
Name = "GeoJson Example - Lines",
Version = "1.0",
Description = [[GeoJson example asset with lines]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -0,0 +1,58 @@
local earth = asset.require('scene/solarsystem/planets/earth/earth')
local earthIdentifier = earth.Earth.Identifier
local data = asset.syncedResource({
Name = "GeoJSON Example Polygon Multiple",
Type = "UrlSynchronization",
Identifier = "geojson_example_polygon_multiple",
Url = {
"http://liu-se.cdn.openspaceproject.com/files/examples/geojson/polygon_multiple.geojson",
"http://liu-se.cdn.openspaceproject.com/files/examples/geojson/polygon_different_heights.geojson"
}
})
local Example_Polygon = {
Identifier = "Earth-Polygon",
File = data .. "polygon_multiple.geojson",
HeightOffset = 20000.0,
DefaultProperties = {
Color = { 1.0, 0.0, 0.0 },
LineWidth = 2.0
},
Name = "Polygon (Multiple)"
}
local Example_Polygon_Diff_Heights = {
Identifier = "Earth-Polygon-Different-Heights",
File = data .. "polygon_different_heights.geojson",
HeightOffset = 20000.0,
DefaultProperties = {
Color = { 0.5, 0.0, 1.0 },
LineWidth = 2.0
},
Name = "Polygon (Different heights)",
Description = "A GeoJSON test layer with some different heights"
}
asset.onInitialize(function()
openspace.globebrowsing.addGeoJson(earthIdentifier, Example_Polygon)
openspace.globebrowsing.addGeoJson(earthIdentifier, Example_Polygon_Diff_Heights)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteGeoJson(earthIdentifier, Example_Polygon)
openspace.globebrowsing.deleteGeoJson(earthIdentifier, Example_Polygon_Diff_Heights)
end)
asset.export(Example_Polygon)
asset.export(Example_Polygon_Diff_Heights)
asset.meta = {
Name = "GeoJson Example - Multiple Polygons",
Version = "1.0",
Description = [[GeoJson example asset with multiple polygons]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -0,0 +1,40 @@
local earth = asset.require('scene/solarsystem/planets/earth/earth')
local earthIdentifier = earth.Earth.Identifier
local data = asset.syncedResource({
Name = "GeoJSON Example Outfacing",
Type = "UrlSynchronization",
Identifier = "geojson_example_points",
Url = "http://liu-se.cdn.openspaceproject.com/files/examples/geojson/points.geojson"
})
local Example_Points = {
Identifier = "Points-Example",
File = data .. "points.geojson",
HeightOffset = 20000.0,
DefaultProperties = {
PointSize = 10.0
},
Name = "Example Points"
}
asset.onInitialize(function()
openspace.globebrowsing.addGeoJson(earthIdentifier, Example_Points)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteGeoJson(earthIdentifier, Example_Points)
end)
asset.export(Example_Points)
asset.meta = {
Name = "GeoJson Example - Points",
Version = "1.0",
Description = [[GeoJson example asset with points that are facing the camera
(default)]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -0,0 +1,41 @@
local earth = asset.require('scene/solarsystem/planets/earth/earth')
local earthIdentifier = earth.Earth.Identifier
local data = asset.syncedResource({
Name = "GeoJSON Example Outfacing",
Type = "UrlSynchronization",
Identifier = "geojson_example_points",
Url = "http://liu-se.cdn.openspaceproject.com/files/examples/geojson/points.geojson"
})
local Example_Points = {
Identifier = "Points-Example-outfacing",
File = data .. "points.geojson",
HeightOffset = 20000.0,
DefaultProperties = {
PointSize = 10.0
},
PointRenderMode = "Globe Normal",
Name = "Example Points (align to globe normal)"
}
asset.onInitialize(function()
openspace.globebrowsing.addGeoJson(earthIdentifier, Example_Points)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteGeoJson(earthIdentifier, Example_Points)
end)
asset.export(Example_Points)
asset.meta = {
Name = "GeoJson Example - Outfacing Points",
Version = "1.0",
Description = [[GeoJson example asset with point that are aligned to "stick out" of
the globe, i.e. face out of the planet]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -0,0 +1,39 @@
local earth = asset.require('scene/solarsystem/planets/earth/earth')
local earthIdentifier = earth.Earth.Identifier
local data = asset.syncedResource({
Name = "GeoJSON Example Polygon with holes",
Type = "UrlSynchronization",
Identifier = "geojson_example_polygon_with_holes",
Url = "http://liu-se.cdn.openspaceproject.com/files/examples/geojson/polygon_with_holes.geojson"
})
local Example_Holes = {
Identifier = "PolygonWithHoles",
File = data .. "polygon_with_holes.geojson",
HeightOffset = 2000.0,
DefaultProperties = {
Color = { 0.0, 1.0, 1.0 }
},
Name = "Example Polygon (holes)"
}
asset.onInitialize(function()
openspace.globebrowsing.addGeoJson(earthIdentifier, Example_Holes)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteGeoJson(earthIdentifier, Example_Holes)
end)
asset.export(Example_Holes)
asset.meta = {
Name = "GeoJson Example - Polygon with holes",
Version = "1.0",
Description = [[GeoJson example asset with polygon that has holes in it]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -0,0 +1,41 @@
local mars = asset.require('scene/solarsystem/planets/mars/mars')
local marsIdentifier = mars.Mars.Identifier
local data = asset.syncedResource({
Name = "GeoJSON Example Path Perseverance",
Type = "UrlSynchronization",
Identifier = "geojson_example_path_perseverance",
Url = "http://liu-se.cdn.openspaceproject.com/files/examples/geojson/path_perseverance.geojson"
})
local Example_RoverPath = {
Identifier = "Mars-Perseverance-Path",
File = data .. "path_perseverance.geojson",
HeightOffset = 10.0,
IgnoreHeights = true, -- Ignores height values from the file itself
DefaultProperties = {
LineWidth = 2.0
},
Name = "Perseverance Rover Path"
}
asset.onInitialize(function()
openspace.globebrowsing.addGeoJson(marsIdentifier, Example_RoverPath)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteGeoJson(marsIdentifier, Example_RoverPath)
end)
asset.export(Example_RoverPath)
asset.meta = {
Name = "GeoJson Example - Rover path",
Version = "1.0",
Description = [[GeoJson example asset that renderes a snapshot of the path of the
Perseverance Rover on Mars. Data from: https://mars.nasa.gov/mars2020/mission/where-is-the-rover/]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

View File

@@ -0,0 +1,42 @@
local earth = asset.require('scene/solarsystem/planets/earth/earth')
local earthIdentifier = earth.Earth.Identifier
local data = asset.syncedResource({
Name = "GeoJSON Example Toronto Neighborhoods",
Type = "UrlSynchronization",
Identifier = "geojson_example_toronto_neighborhoods",
Url = "http://liu-se.cdn.openspaceproject.com/files/examples/geojson/toronto_neighborhoods.geojson"
})
local Example_Toronto = {
Identifier = "Toronto-Neighborhoods",
File = data .. "toronto_neighborhoods.geojson",
HeightOffset = 1000.0,
DefaultProperties = {
Color = { 0.0, 1.0, 0.0 },
FillColor = { 0.2, 0.33, 0.2 },
LineWidth = 2.0,
},
Name = "Toronto Neighbourhoods"
}
asset.onInitialize(function()
openspace.globebrowsing.addGeoJson(earthIdentifier, Example_Toronto)
end)
asset.onDeinitialize(function()
openspace.globebrowsing.deleteGeoJson(earthIdentifier, Example_Toronto)
end)
asset.export(Example_Toronto)
asset.meta = {
Name = "GeoJson Example - Toronto neighborhoods",
Version = "1.0",
Description = [[GeoJson example asset that shows the neighborhoods of the city Toronto,
Canada, as polygons. Data source: https://handsondataviz.org/geojsonio.html]],
Author = "OpenSpace Team",
URL = "http://openspaceproject.com",
License = "MIT license"
}

BIN
data/globe_pin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -32,7 +32,7 @@ namespace openspace {
/**
* This class is an interface for all things fadeable in the software; things that need
* a fade and opacity property, which will be combined into a final opacity value
*
*
* A Fadeable can also be dependent on the fade value from a specified parent fadeable,
* so that it fades out together with the parent
*/

View File

@@ -32,6 +32,11 @@ namespace ghoul::opengl {
class Texture;
} // namespace ghoul::opengl
namespace openspace {
class LightSource;
struct RenderData;
} // namespace openspace
namespace openspace::rendering::helper {
enum class Anchor {
@@ -109,6 +114,11 @@ struct VertexXYZ {
GLfloat xyz[3];
};
struct VertexXYZNormal {
GLfloat xyz[3];
GLfloat normal[3];
};
VertexXYZ convertToXYZ(const Vertex& v);
std::vector<VertexXYZ> convert(std::vector<Vertex> v);
@@ -119,6 +129,20 @@ std::vector<Vertex> createRing(int nSegments, float radius,
std::pair<std::vector<Vertex>, std::vector<GLushort>>
createSphere(int nSegments, glm::vec3 radii, glm::vec4 colors = glm::vec4(1.f));
/**
* Data structure that can be used for rendering using multiple light directions
*/
struct LightSourceRenderData {
unsigned int nLightSources = 0;
// Buffers for uniform uploading
std::vector<float> intensitiesBuffer;
std::vector<glm::vec3> directionsViewSpaceBuffer;
void updateBasedOnLightSources(const RenderData& renderData,
const std::vector<std::unique_ptr<LightSource>>& sources);
};
} // namespace openspace::rendering::helper
#endif // __OPENSPACE_CORE___HELPER___H__

View File

@@ -60,6 +60,11 @@ set(HEADER_FILES
src/tiletextureinitdata.h
src/tilecacheproperties.h
src/timequantizer.h
src/geojson/geojsoncomponent.h
src/geojson/geojsonmanager.h
src/geojson/geojsonproperties.h
src/geojson/globegeometryfeature.h
src/geojson/globegeometryhelper.h
src/tileprovider/defaulttileprovider.h
src/tileprovider/imagesequencetileprovider.h
src/tileprovider/singleimagetileprovider.h
@@ -101,6 +106,11 @@ set(SOURCE_FILES
src/tileloadjob.cpp
src/tiletextureinitdata.cpp
src/timequantizer.cpp
src/geojson/geojsoncomponent.cpp
src/geojson/geojsonmanager.cpp
src/geojson/geojsonproperties.cpp
src/geojson/globegeometryfeature.cpp
src/geojson/globegeometryhelper.cpp
src/tileprovider/defaulttileprovider.cpp
src/tileprovider/imagesequencetileprovider.cpp
src/tileprovider/singleimagetileprovider.cpp
@@ -119,6 +129,11 @@ set(SHADER_FILES
shaders/advanced_rings_vs.glsl
shaders/advanced_rings_fs.glsl
shaders/blending.glsl
shaders/geojson_fs.glsl
shaders/geojson_points_fs.glsl
shaders/geojson_points_gs.glsl
shaders/geojson_points_vs.glsl
shaders/geojson_vs.glsl
shaders/globalrenderer_vs.glsl
shaders/localrenderer_vs.glsl
shaders/renderer_fs.glsl
@@ -163,3 +178,8 @@ else (WIN32)
target_link_libraries(openspace-module-globebrowsing PRIVATE ${GDAL_LIBRARY})
mark_as_advanced(GDAL_CONFIG GDAL_INCLUDE_DIR GDAL_LIBRARY)
endif () # WIN32
begin_dependency("GEOS")
add_subdirectory(ext/geos SYSTEM)
target_link_libraries(openspace-module-globebrowsing PRIVATE geos)
end_dependency("GEOS")

View File

@@ -28,6 +28,9 @@
#include <modules/globebrowsing/src/dashboarditemglobelocation.h>
#include <modules/globebrowsing/src/gdalwrapper.h>
#include <modules/globebrowsing/src/geodeticpatch.h>
#include <modules/globebrowsing/src/geojson/geojsoncomponent.h>
#include <modules/globebrowsing/src/geojson/geojsonmanager.h>
#include <modules/globebrowsing/src/geojson/geojsonproperties.h>
#include <modules/globebrowsing/src/globelabelscomponent.h>
#include <modules/globebrowsing/src/globetranslation.h>
#include <modules/globebrowsing/src/globerotation.h>
@@ -98,6 +101,14 @@ namespace {
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo
DefaultGeoPointTextureInfo =
{
"DefaultGeoPointTexture",
"Default Geo Point Texture",
"A path to a texture to use as default for GeoJson points"
};
constexpr openspace::properties::Property::PropertyInfo MRFCacheEnabledInfo = {
"MRFCacheEnabled",
"MRF Cache Enabled",
@@ -173,6 +184,9 @@ namespace {
// [[codegen::verbatim(TileCacheSizeInfo.description)]]
std::optional<int> tileCacheSize;
// [[codegen::verbatim(DefaultGeoPointTextureInfo.description)]]
std::optional<std::string> defaultGeoPointTexture;
// [[codegen::verbatim(MRFCacheEnabledInfo.description)]]
std::optional<bool> mrfCacheEnabled [[codegen::key("MRFCacheEnabled")]];
@@ -187,10 +201,14 @@ namespace openspace {
GlobeBrowsingModule::GlobeBrowsingModule()
: OpenSpaceModule(Name)
, _tileCacheSizeMB(TileCacheSizeInfo, 1024)
, _defaultGeoPointTexturePath(DefaultGeoPointTextureInfo)
, _mrfCacheEnabled(MRFCacheEnabledInfo, false)
, _mrfCacheLocation(MRFCacheLocationInfo, "${BASE}/cache_mrf")
{
addProperty(_tileCacheSizeMB);
addProperty(_defaultGeoPointTexturePath);
addProperty(_mrfCacheEnabled);
addProperty(_mrfCacheLocation);
}
@@ -200,6 +218,28 @@ void GlobeBrowsingModule::internalInitialize(const ghoul::Dictionary& dict) {
const Parameters p = codegen::bake<Parameters>(dict);
_tileCacheSizeMB = p.tileCacheSize.value_or(_tileCacheSizeMB);
_defaultGeoPointTexturePath.onChange([this]() {
if (_defaultGeoPointTexturePath.value().empty()) {
_hasDefaultGeoPointTexture = false;
return;
}
std::filesystem::path path = _defaultGeoPointTexturePath.value();
if (std::filesystem::exists(path)) {
_hasDefaultGeoPointTexture = true;
}
else {
LWARNINGC("GlobeBrowsingModule", fmt::format(
"The provided texture file {} for the default geo point texture "
"does not exist", path
));
}
});
if (p.defaultGeoPointTexture.has_value()) {
_defaultGeoPointTexturePath = absPath(*p.defaultGeoPointTexture).string();
}
_mrfCacheEnabled = p.mrfCacheEnabled.value_or(_mrfCacheEnabled);
_mrfCacheLocation = p.mrfCacheLocation.value_or(_mrfCacheLocation);
@@ -298,6 +338,9 @@ std::vector<documentation::Documentation> GlobeBrowsingModule::documentations()
globebrowsing::TemporalTileProvider::Documentation(),
globebrowsing::TileProviderByIndex::Documentation(),
globebrowsing::TileProviderByLevel::Documentation(),
globebrowsing::GeoJsonManager::Documentation(),
globebrowsing::GeoJsonComponent::Documentation(),
globebrowsing::GeoJsonProperties::Documentation(),
GlobeLabelsComponent::Documentation(),
RingsComponent::Documentation(),
ShadowComponent::Documentation()
@@ -634,6 +677,14 @@ const std::string GlobeBrowsingModule::mrfCacheLocation() const {
return _mrfCacheLocation;
}
bool GlobeBrowsingModule::hasDefaultGeoPointTexture() const {
return _hasDefaultGeoPointTexture;
}
std::string_view GlobeBrowsingModule::defaultGeoPointTexture() const {
return _defaultGeoPointTexturePath;
}
scripting::LuaLibrary GlobeBrowsingModule::luaLibrary() const {
return {
.name = "globebrowsing",
@@ -651,7 +702,10 @@ scripting::LuaLibrary GlobeBrowsingModule::luaLibrary() const {
codegen::lua::GetGeoPositionForCamera,
codegen::lua::LoadWMSCapabilities,
codegen::lua::RemoveWMSServer,
codegen::lua::CapabilitiesWMS
codegen::lua::CapabilitiesWMS,
codegen::lua::AddGeoJson,
codegen::lua::DeleteGeoJson,
codegen::lua::AddGeoJsonFromFile
},
.scripts = {
absPath("${MODULE_GLOBEBROWSING}/scripts/layer_support.lua")

View File

@@ -95,6 +95,9 @@ public:
bool isMRFCachingEnabled() const;
const std::string mrfCacheLocation() const;
bool hasDefaultGeoPointTexture() const;
std::string_view defaultGeoPointTexture() const;
protected:
void internalInitialize(const ghoul::Dictionary&) override;
@@ -113,6 +116,7 @@ private:
properties::UIntProperty _tileCacheSizeMB;
properties::StringProperty _defaultGeoPointTexturePath;
properties::BoolProperty _mrfCacheEnabled;
properties::StringProperty _mrfCacheLocation;
@@ -124,6 +128,8 @@ private:
std::map<std::string, Capabilities> _capabilitiesMap;
std::multimap<std::string, UrlInfo> _urlList;
bool _hasDefaultGeoPointTexture = false;
};
} // namespace openspace

View File

@@ -530,6 +530,122 @@ getGeoPositionForCamera(bool useEyePosition = false)
return res;
}
/**
* Add a GeoJson layer specified by the given table to the globe specified by the
* 'globeName' argument
*/
[[codegen::luawrap]] void addGeoJson(std::string globeName, ghoul::Dictionary table)
{
using namespace openspace;
using namespace globebrowsing;
// Get the node and make sure it exists
SceneGraphNode* n = global::renderEngine->scene()->sceneGraphNode(globeName);
if (!n) {
throw ghoul::lua::LuaError("Unknown globe name: " + globeName);
}
// Get the renderable globe
RenderableGlobe* globe = dynamic_cast<RenderableGlobe*>(n->renderable());
if (!globe) {
throw ghoul::lua::LuaError("Renderable is not a globe: " + globeName);
}
// Get the dictionary defining the layer
globe->geoJsonManager().addGeoJsonLayer(table);
}
/**
* Remove the GeoJson layer specified by the given table or string identifier from the
* globe specified by the 'globeName' argument
*/
[[codegen::luawrap]] void deleteGeoJson(std::string globeName,
std::variant<std::string, ghoul::Dictionary> tableOrIdentifier)
{
using namespace openspace;
using namespace globebrowsing;
// Get the node and make sure it exists
SceneGraphNode* n = global::renderEngine->scene()->sceneGraphNode(globeName);
if (!n) {
throw ghoul::lua::LuaError("Unknown globe name: " + globeName);
}
// Get the renderable globe
RenderableGlobe* globe = dynamic_cast<RenderableGlobe*>(n->renderable());
if (!globe) {
throw ghoul::lua::LuaError("Renderable is not a globe: " + globeName);
}
std::string identifier;
if (std::holds_alternative<std::string>(tableOrIdentifier)) {
identifier = std::get<std::string>(tableOrIdentifier);
}
else {
ghoul::Dictionary d = std::get<ghoul::Dictionary>(tableOrIdentifier);
if (!d.hasValue<std::string>("Identifier")) {
throw ghoul::lua::LuaError(
"Table passed to deleteLayer does not contain an Identifier"
);
}
identifier = d.value<std::string>("Identifier");
}
globe->geoJsonManager().deleteLayer(identifier);
}
/**
* Add a GeoJson layer from the given file name and add it to the current anchor node,
* if it is a globe. Note that you might have to increase the height offset for the
* added feature to be visible on the globe, if using a height map
*/
[[codegen::luawrap]] void addGeoJsonFromFile(std::string filename,
std::optional<std::string> name)
{
using namespace openspace;
using namespace globebrowsing;
std::filesystem::path path = absPath(filename);
if (!std::filesystem::is_regular_file(path)) {
throw ghoul::lua::LuaError(fmt::format(
"Could not find the provided file: '{}'", filename
));
}
if (path.extension() != ".geojson") {
throw ghoul::lua::LuaError(fmt::format(
"Unexpected file type: '{}'. Expected '.geojson' file", filename
));
}
SceneGraphNode* n = global::renderEngine->scene()->sceneGraphNode(
global::navigationHandler->anchorNode()->identifier()
);
if (!n) {
throw ghoul::lua::LuaError("Invalid anchor node");
}
RenderableGlobe* globe = dynamic_cast<RenderableGlobe*>(n->renderable());
if (!globe) {
throw ghoul::lua::LuaError(
"Current anchor is not a globe (Expected 'RenderableGlobe')"
);
}
// Make a minimal dictionary to represent the geojson component
ghoul::Dictionary d;
std::string identifier = makeIdentifier(name.value_or(path.stem().string()));
d.setValue("Identifier", identifier);
d.setValue("File", path.string());
if (name.has_value()) {
d.setValue("Name", *name);
}
// Get the dictionary defining the layer
globe->geoJsonManager().addGeoJsonLayer(d);
}
#include "globebrowsingmodule_lua_codegen.cpp"
} // namespace

View File

@@ -0,0 +1,76 @@
/*****************************************************************************************
* *
* 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 "fragment.glsl"
in float vs_depth;
flat in vec3 vs_normal;
in vec4 vs_positionViewSpace;
uniform vec3 color;
uniform float opacity;
uniform float ambientIntensity = 0.2;
uniform float diffuseIntensity = 0.8;
uniform bool performShading = true;
uniform unsigned int nLightSources;
uniform vec3 lightDirectionsViewSpace[8];
uniform float lightIntensities[8];
const vec3 LightColor = vec3(1.0);
Fragment getFragment() {
Fragment frag;
if (opacity == 0.0) {
discard;
}
frag.color = vec4(color, opacity);
// Simple diffuse phong shading based on light sources
if (performShading && nLightSources > 0) {
// @TODO: Fix faulty triangle normals. This should not have to be inverted
vec3 n = -normalize(vs_normal);
// Ambient color
vec3 shadedColor = ambientIntensity * color;
for (int i = 0; i < nLightSources; ++i) {
vec3 l = lightDirectionsViewSpace[i];
// Diffuse
vec3 diffuseColor = diffuseIntensity * max(dot(n,l), 0.0) * color;
// Light contribution
shadedColor += lightIntensities[i] * (LightColor * diffuseColor);
}
frag.color.xyz = shadedColor;
}
frag.depth = vs_depth;
frag.gPosition = vs_positionViewSpace;
frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0);
return frag;
}

View File

@@ -0,0 +1,62 @@
/*****************************************************************************************
* *
* 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 "fragment.glsl"
flat in float vs_screenSpaceDepth;
in vec4 vs_positionViewSpace;
flat in vec3 vs_normal; // TODO: not needed for shading, remove somehow
in vec2 texCoord;
uniform sampler2D pointTexture;
uniform bool hasTexture;
uniform vec3 color;
uniform float opacity;
// Can be used to preserve the whites in a point texture
bool preserveWhite = true;
Fragment getFragment() {
Fragment frag;
if (hasTexture) {
frag.color = texture(pointTexture, texCoord);
if (!preserveWhite || frag.color.r * frag.color.g * frag.color.b < 0.95) {
frag.color.rgb *= color;
}
frag.color.a *= opacity;
}
else {
frag.color = vec4(color * vs_normal, opacity);
}
if (frag.color.a < 0.01) {
discard;
}
frag.depth = vs_screenSpaceDepth;
frag.gPosition = vs_positionViewSpace;
frag.gNormal = vec4(0.0, 0.0, 0.0, 1.0);
return frag;
}

View File

@@ -0,0 +1,158 @@
/*****************************************************************************************
* *
* 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. *
****************************************************************************************/
#version __CONTEXT__
#include "PowerScaling/powerScalingMath.hglsl"
layout(points) in;
flat in vec3 normal[]; // Point normals correspond to globe out direction, model space
flat in float dynamicHeight[];
layout(triangle_strip, max_vertices = 4) out;
out vec2 texCoord;
flat out float vs_screenSpaceDepth;
out vec4 vs_positionViewSpace;
flat out vec3 vs_normal;
// General settings
uniform dmat4 modelTransform;
uniform dmat4 viewTransform;
uniform dmat4 projectionTransform;
uniform float heightOffset;
uniform bool useHeightMapData;
// Camera information
uniform vec3 cameraUp;
uniform vec3 cameraRight;
uniform dvec3 cameraPosition; // world coordinates
uniform vec3 cameraLookUp;
// Render mode
uniform int renderMode;
// OBS! Keep in sync with option property options
const int RenderOptionCameraDir = 0;
const int RenderOptionCameraPos = 1;
const int RenderOptionGlobeNormal = 2;
const int RenderOptionGlobeSurface = 3;
uniform float pointSize;
uniform float textureWidthFactor;
// If false, use the center
uniform bool useBottomAnchorPoint = true;
const vec2 corners[4] = vec2[4](
vec2(0.0, 0.0),
vec2(1.0, 0.0),
vec2(1.0, 1.0),
vec2(0.0, 1.0)
);
void main() {
vec4 pos = gl_in[0].gl_Position;
vs_normal = normal[0];
dvec4 dpos = dvec4(dvec3(pos.xyz), 1.0);
// Offset position based on height information
if (length(pos.xyz) > 0) {
dvec3 outDirection = normalize(dvec3(dpos));
float height = heightOffset;
if (useHeightMapData) {
height += dynamicHeight[0];
}
dpos += dvec4(outDirection * double(height), 0.0);
}
// World coordinates
dpos = modelTransform * dpos;
vec3 worldNormal = normalize(mat3(modelTransform) * vs_normal);
// Set up and right directions based on render mode.
// renderMode 0 is default
vec3 right = cameraRight;
vec3 up = cameraUp;
vec3 cameraToPosDir = vec3(normalize(cameraPosition - dpos.xyz));
// Update right and up based on render mode
if (renderMode == RenderOptionCameraPos) {
right = normalize(cross(cameraLookUp, cameraToPosDir));
up = normalize(cross(cameraToPosDir, right));
}
else if (renderMode == RenderOptionGlobeNormal) {
up = worldNormal;
right = normalize(cross(up, cameraToPosDir));
}
else if (renderMode == RenderOptionGlobeSurface) {
// Compute up to be orthogonal to globe normal and camera right direction
up = normalize(cross(worldNormal, right));
// Recompute right to be orthognal to globe normal
right = cross(up, worldNormal);
}
dvec4 scaledRight = pointSize * dvec4(right, 0.0) * 0.5;
dvec4 scaledUp = pointSize * dvec4(up, 0.0) * 0.5;
dmat4 cameraViewProjectionMatrix = projectionTransform * viewTransform;
vec4 dposClip = vec4(cameraViewProjectionMatrix * dpos);
vec4 scaledRightClip = textureWidthFactor * vec4(cameraViewProjectionMatrix * scaledRight);
vec4 scaledUpClip = vec4(cameraViewProjectionMatrix * scaledUp);
// Place anchor point at the bottom
vec4 bottomLeft = z_normalization(dposClip - scaledRightClip);
vec4 bottomRight = z_normalization(dposClip + scaledRightClip);
vec4 topRight = z_normalization(dposClip + 2 * scaledUpClip + scaledRightClip);
vec4 topLeft = z_normalization(dposClip + 2 * scaledUpClip - scaledRightClip);
if (!useBottomAnchorPoint) {
// Place anchor point at the center
bottomLeft = z_normalization(dposClip - scaledRightClip - scaledUpClip);
bottomRight = z_normalization(dposClip + scaledRightClip - scaledUpClip);
topRight = z_normalization(dposClip + scaledUpClip + scaledRightClip);
topLeft = z_normalization(dposClip + scaledUpClip - scaledRightClip);
}
vs_screenSpaceDepth = bottomLeft.w;
vs_positionViewSpace = vec4(viewTransform * dpos);
// Build primitive
texCoord = corners[0];
gl_Position = bottomLeft;
EmitVertex();
texCoord = corners[1];
gl_Position = bottomRight;
EmitVertex();
texCoord = corners[3];
gl_Position = topLeft;
EmitVertex();
texCoord = corners[2];
gl_Position = topRight;
EmitVertex();
EndPrimitive();
}

View File

@@ -0,0 +1,38 @@
/*****************************************************************************************
* *
* 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. *
****************************************************************************************/
#version __CONTEXT__
layout(location = 0) in vec3 in_position;
layout(location = 1) in vec3 in_normal;
layout(location = 2) in float in_height;
out vec3 normal;
out float dynamicHeight;
void main() {
gl_Position = vec4(in_position, 1.0);
normal = in_normal;
dynamicHeight = in_height;
}

View File

@@ -0,0 +1,64 @@
/*****************************************************************************************
* *
* 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. *
****************************************************************************************/
#version __CONTEXT__
layout(location = 0) in vec3 in_position;
layout(location = 1) in vec3 in_normal;
layout(location = 2) in float in_height;
out float vs_depth;
out vec3 vs_normal;
out vec4 vs_positionViewSpace;
uniform dmat4 modelTransform;
uniform dmat4 viewTransform;
uniform dmat4 projectionTransform;
uniform mat3 normalTransform;
uniform float heightOffset;
uniform bool useHeightMapData;
void main() {
dvec4 modelPos = dvec4(in_position, 1.0);
// Offset model pos based on height info
if (length(in_position) > 0) {
dvec3 outDirection = normalize(dvec3(in_position));
float height = heightOffset;
if (useHeightMapData) {
height += in_height;
}
modelPos += dvec4(outDirection * double(height), 0.0);
}
vs_positionViewSpace = vec4(viewTransform * modelTransform * modelPos);
vec4 positionScreenSpace = vec4(projectionTransform * vs_positionViewSpace);
vs_depth = positionScreenSpace.w;
vs_normal = normalize(normalTransform * in_normal);
gl_Position = positionScreenSpace;
// Set z to 0 to disable near and far plane, unique handling for perspective in space
gl_Position.z = 0.0;
}

View File

@@ -0,0 +1,797 @@
/*****************************************************************************************
* *
* 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 <modules/globebrowsing/src/geojson/geojsoncomponent.h>
#include <modules/globebrowsing/src/geojson/globegeometryhelper.h>
#include <modules/globebrowsing/src/renderableglobe.h>
#include <openspace/documentation/documentation.h>
#include <openspace/engine/globals.h>
#include <openspace/json.h>
#include <openspace/query/query.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/lightsource.h>
#include <openspace/scene/scene.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/scripting/scriptengine.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/fmt.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/opengl/openglstatecache.h>
#include <ghoul/opengl/programobject.h>
#include <filesystem>
#include <fstream>
#include <functional>
#include <optional>
namespace geos_nlohmann = nlohmann;
#include <geos/geom/Geometry.h>
#include <geos/io/GeoJSON.h>
#include <geos/io/GeoJSONReader.h>
namespace {
constexpr std::string_view _loggerCat = "GeoJsonComponent";
constexpr std::string_view KeyIdentifier = "Identifier";
constexpr std::string_view KeyName = "Name";
constexpr std::string_view KeyDesc = "Description";
constexpr openspace::properties::Property::PropertyInfo EnabledInfo = {
"Enabled",
"Is Enabled",
"This setting determines whether this object will be visible or not",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo FileInfo = {
"File",
"File",
"Path to the GeoJSON file to base the rendering on",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo HeightOffsetInfo = {
"HeightOffset",
"Height Offset",
"A height offset value, in meters. Useful for moving a feature closer to or "
"farther away from the surface",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo CoordinateOffsetInfo = {
"CoordinateOffset",
"Geographic Coordinate Offset",
"A latitude and longitude offset value, in decimal degrees. Can be used to "
"move the object on the surface and correct potential mismatches with other "
"renderings. Note that changing it during runtime leads to all positions being "
"recomputed",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo DrawWireframeInfo = {
"DrawWireframe",
"Wireframe",
"If true, draw the wire frame of the polygons. Used for testing and to "
"investigate tessellation results",
openspace::properties::Property::Visibility::Developer
};
constexpr openspace::properties::Property::PropertyInfo PreventHeightUpdateInfo = {
"PreventHeightUpdate",
"Prevent Update From Heightmap",
"If true, the polygon mesh will not be automatically updated based on the "
"heightmap, even if the 'RelativeToGround' altitude option is set and the "
"heightmap updates. The data can still be force updated",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo ForceUpdateHeightDataInfo = {
"ForceUpdateHeightData",
"Force Update Height Data",
"Triggering this leads to a recomputation of the heights based on the globe "
"height map value at the geometry's positions",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo PointRenderModeInfo = {
"PointRenderMode",
"Points Aligned to",
"Decides how the billboards for the points should be rendered in terms of up "
"direction and whether the plane should face the camera. See details on the "
"different options in the wiki",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo FlyToFeatureInfo = {
"FlyToFeature",
"Fly To Feature",
"Triggering this leads to the camera flying to a position that show the GeoJson "
"feature. The flight will account for any lat, long or height offset",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo CentroidCoordinateInfo = {
"CentroidCoordinate",
"Centroid Coordinate",
"The lat long coordinate of the centroid position of the read geometry. Note "
"that this value does not incude the offset",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo BoundingBoxInfo = {
"BoundingBox",
"Bounding Box",
"The lat long coordinates of the lower and upper corner of the bounding box of "
"the read geometry. Note that this value does not incude the offset",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo PointSizeScaleInfo = {
"PointSizeScale",
"Point Size Scale",
"An extra scale value that can be used to increase or decrease the scale of any "
"rendered points in the component, even if a value is set from the GeoJson file",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo LineWidthScaleInfo = {
"LineWidthScale",
"Line Width Scale",
"An extra scale value that can be used to increase or decrease the width of any "
"rendered lines in the component, even if a value is set from the GeoJson file. "
"Note that there is a max limit for how wide lines can be.",
openspace::properties::Property::Visibility::NoviceUser
};
struct [[codegen::Dictionary(GeoJsonComponent)]] Parameters {
// The unique identifier for this layer. May not contain '.' or spaces
std::string identifier;
// A human-readable name for the user interface. If this is omitted, the
// identifier is used instead
std::optional<std::string> name;
// [[codegen::verbatim(EnabledInfo.description)]]
std::optional<bool> enabled;
// The opacity of the component
std::optional<float> opacity [[codegen::inrange(0.0, 1.0)]];
// A human-readable description of the layer to be used in informational texts
// presented to the user
std::optional<std::string> description;
// If true, ignore any height values that are given in the file. Coordinates with
// three values will then be treated as coordinates with only two values
std::optional<bool> ignoreHeights;
// [[codegen::verbatim(PreventHeightUpdateInfo.description)]]
std::optional<bool> preventHeightUpdate;
// [[codegen::verbatim(FileInfo.description)]]
std::filesystem::path file;
// [[codegen::verbatim(HeightOffsetInfo.description)]]
std::optional<float> heightOffset;
// [[codegen::verbatim(PointSizeScaleInfo.description)]]
std::optional<float> pointSizeScale;
// [[codegen::verbatim(LineWidthScaleInfo.description)]]
std::optional<float> lineWidthScale;
// [[codegen::verbatim(CoordinateOffsetInfo.description)]]
std::optional<glm::vec2> coordinateOffset;
enum class [[codegen::map(openspace::globebrowsing::GlobeGeometryFeature::PointRenderMode)]] PointRenderMode {
AlignToCameraDir [[codegen::key("Camera Direction")]],
AlignToCameraPos [[codegen::key("Camera Position")]],
AlignToGlobeNormal [[codegen::key("Globe Normal")]],
AlignToGlobeSurface [[codegen::key("Globe Surface")]]
};
// [[codegen::verbatim(PointRenderModeInfo.description)]]
std::optional<PointRenderMode> pointRenderMode;
// [[codegen::verbatim(DrawWireframeInfo.description)]]
std::optional<bool> drawWireframe;
// These properties will be used as default values for the geoJson rendering,
// meaning that they will be used when there is no value given for the
// individual geoJson features
std::optional<ghoul::Dictionary> defaultProperties
[[codegen::reference("globebrowsing_geojsonproperties")]];
// A list of light sources that this object should accept light from
std::optional<std::vector<ghoul::Dictionary>> lightSources
[[codegen::reference("core_light_source")]];
};
#include "geojsoncomponent_codegen.cpp"
} // namespace
namespace openspace::globebrowsing {
documentation::Documentation GeoJsonComponent::Documentation() {
return codegen::doc<Parameters>("globebrowsing_geojsoncomponent");
}
GeoJsonComponent::SubFeatureProps::SubFeatureProps(
properties::PropertyOwner::PropertyOwnerInfo info)
: properties::PropertyOwner(info)
, enabled(EnabledInfo, true)
, flyToFeature(FlyToFeatureInfo)
, centroidLatLong(
CentroidCoordinateInfo,
glm::vec2(0.f),
glm::vec2(-90.f, -180.f),
glm::vec2(90.f, 180.f)
)
, boundingboxLatLong(
BoundingBoxInfo,
glm::vec4(0.f),
glm::vec4(-90.f, -180.f, -90.f, -180.f),
glm::vec4(90.f, 180.f, 90.f, 180.f)
)
{
_opacity.setVisibility(openspace::properties::Property::Visibility::AdvancedUser);
addProperty(Fadeable::_opacity);
addProperty(Fadeable::_fade);
addProperty(enabled);
addProperty(flyToFeature);
centroidLatLong.setReadOnly(true);
addProperty(centroidLatLong);
boundingboxLatLong.setReadOnly(true);
addProperty(boundingboxLatLong);
}
GeoJsonComponent::GeoJsonComponent(const ghoul::Dictionary& dictionary,
RenderableGlobe& globe)
: properties::PropertyOwner({
dictionary.value<std::string>(KeyIdentifier),
dictionary.hasKey(KeyName) ? dictionary.value<std::string>(KeyName) : "",
dictionary.hasKey(KeyDesc) ? dictionary.value<std::string>(KeyDesc) : ""
})
, _enabled(EnabledInfo, true)
, _globeNode(globe)
, _geoJsonFile(FileInfo)
, _heightOffset(HeightOffsetInfo, 10.f, -1e12f, 1e12f)
, _latLongOffset(
CoordinateOffsetInfo,
glm::vec2(0.f),
glm::vec2(-90.0),
glm::vec2(90.f)
)
, _pointSizeScale(PointSizeScaleInfo, 1.f, 0.01f, 100.f)
, _lineWidthScale(LineWidthScaleInfo, 1.f, 0.01f, 10.f)
, _drawWireframe(DrawWireframeInfo, false)
, _preventUpdatesFromHeightMap(PreventHeightUpdateInfo, false)
, _forceUpdateHeightData(ForceUpdateHeightDataInfo)
, _lightSourcePropertyOwner({ "LightSources", "Light Sources" })
, _featuresPropertyOwner({ "Features", "Features" })
, _pointRenderModeOption(
PointRenderModeInfo,
properties::OptionProperty::DisplayType::Dropdown
)
, _centerLatLong(
CentroidCoordinateInfo,
glm::vec2(0.f),
glm::vec2(-90.f, -180.f),
glm::vec2(90.f, 180.f)
)
, _flyToFeature(FlyToFeatureInfo)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
_enabled = p.enabled.value_or(_enabled);
addProperty(_enabled);
_opacity = p.opacity.value_or(_opacity);
addProperty(_opacity);
addProperty(_fade);
_geoJsonFile = p.file.string();
_geoJsonFile.setReadOnly(true);
addProperty(_geoJsonFile);
_ignoreHeightsFromFile = p.ignoreHeights.value_or(_ignoreHeightsFromFile);
const float minGlobeRadius = static_cast<float>(
_globeNode.ellipsoid().minimumRadius()
);
_heightOffset = p.heightOffset.value_or(_heightOffset);
_heightOffset.onChange([this]() { _heightOffsetIsDirty = true; });
constexpr float MinRadiusFactor = -0.9f;
constexpr float MaxRadiusFactor = 5.f;
_heightOffset.setMinValue(MinRadiusFactor * minGlobeRadius);
_heightOffset.setMaxValue(MaxRadiusFactor * minGlobeRadius);
addProperty(_heightOffset);
_latLongOffset = p.coordinateOffset.value_or(_latLongOffset);
_latLongOffset.onChange([this]() { _dataIsDirty = true; });
addProperty(_latLongOffset);
_pointSizeScale = p.pointSizeScale.value_or(_pointSizeScale);
addProperty(_pointSizeScale);
_lineWidthScale = p.lineWidthScale.value_or(_lineWidthScale);
addProperty(_lineWidthScale);
if (p.defaultProperties.has_value()) {
_defaultProperties.createFromDictionary(*p.defaultProperties, _globeNode);
}
addPropertySubOwner(_defaultProperties);
_defaultProperties.pointTexture.onChange([this]() {
std::filesystem::path texturePath = _defaultProperties.pointTexture.value();
// Not ethat an empty texture is also valid => use default texture from module
if (std::filesystem::is_regular_file(texturePath) || texturePath.empty()) {
_textureIsDirty = true;
}
else {
LERROR(fmt::format(
"Provided texture file does not exist: '{}'",
_defaultProperties.pointTexture
));
}
});
_defaultProperties.tessellation.enabled.onChange([this]() { _dataIsDirty = true; });
_defaultProperties.tessellation.useLevel.onChange([this]() { _dataIsDirty = true; });
_defaultProperties.tessellation.level.onChange([this]() { _dataIsDirty = true; });
_defaultProperties.tessellation.distance.onChange([this]() {
_dataIsDirty = true;
});
_forceUpdateHeightData.onChange([this]() {
for (GlobeGeometryFeature& f : _geometryFeatures) {
f.updateHeightsFromHeightMap();
}
});
addProperty(_forceUpdateHeightData);
_preventUpdatesFromHeightMap =
p.preventHeightUpdate.value_or(_preventUpdatesFromHeightMap);
addProperty(_preventUpdatesFromHeightMap);
_drawWireframe = p.drawWireframe.value_or(_drawWireframe);
addProperty(_drawWireframe);
using PointRenderMode = GlobeGeometryFeature::PointRenderMode;
_pointRenderModeOption.addOptions({
{ static_cast<int>(PointRenderMode::AlignToCameraDir), "Camera Direction"},
{ static_cast<int>(PointRenderMode::AlignToCameraPos), "Camera Position"},
{ static_cast<int>(PointRenderMode::AlignToGlobeNormal), "Globe Normal"},
{ static_cast<int>(PointRenderMode::AlignToGlobeSurface), "Globe Surface"}
});
if (p.pointRenderMode.has_value()) {
_pointRenderModeOption =
static_cast<int>(codegen::map<PointRenderMode>(*p.pointRenderMode));
}
addProperty(_pointRenderModeOption);
_centerLatLong.setReadOnly(true);
addProperty(_centerLatLong);
_flyToFeature.onChange([this]() { flyToFeature(); });
addProperty(_flyToFeature);
readFile();
if (p.lightSources.has_value()) {
std::vector<ghoul::Dictionary> lightsources = *p.lightSources;
for (const ghoul::Dictionary& lsDictionary : lightsources) {
std::unique_ptr<LightSource> lightSource =
LightSource::createFromDictionary(lsDictionary);
_lightSourcePropertyOwner.addPropertySubOwner(lightSource.get());
_lightSources.push_back(std::move(lightSource));
}
}
else {
// If no light source provided, add a deafult light source from the camera
using namespace std::string_literals;
ghoul::Dictionary defaultLightSourceDict;
defaultLightSourceDict.setValue("Identifier", "Camera"s);
defaultLightSourceDict.setValue("Type", "CameraLightSource"s);
defaultLightSourceDict.setValue("Intensity", 1.0);
_lightSources.push_back(
LightSource::createFromDictionary(defaultLightSourceDict)
);
_lightSourcePropertyOwner.addPropertySubOwner(_lightSources.back().get());
}
addPropertySubOwner(_lightSourcePropertyOwner);
addPropertySubOwner(_featuresPropertyOwner);
}
bool GeoJsonComponent::enabled() const {
return _enabled;
}
void GeoJsonComponent::initialize() {
ZoneScoped;
for (const std::unique_ptr<LightSource>& ls : _lightSources) {
ls->initialize();
}
}
void GeoJsonComponent::initializeGL() {
ZoneScoped;
_linesAndPolygonsProgram = global::renderEngine->buildRenderProgram(
"GeoLinesAndPolygonProgram",
absPath("${MODULE_GLOBEBROWSING}/shaders/geojson_vs.glsl"),
absPath("${MODULE_GLOBEBROWSING}/shaders/geojson_fs.glsl")
);
_pointsProgram = global::renderEngine->buildRenderProgram(
"GeoPointsProgram",
absPath("${MODULE_GLOBEBROWSING}/shaders/geojson_points_vs.glsl"),
absPath("${MODULE_GLOBEBROWSING}/shaders/geojson_points_fs.glsl"),
absPath("${MODULE_GLOBEBROWSING}/shaders/geojson_points_gs.glsl")
);
for (GlobeGeometryFeature& g : _geometryFeatures) {
g.initializeGL(_pointsProgram.get(), _linesAndPolygonsProgram.get());
}
}
void GeoJsonComponent::deinitializeGL() {
for (GlobeGeometryFeature& g : _geometryFeatures) {
g.deinitializeGL();
}
global::renderEngine->removeRenderProgram(_linesAndPolygonsProgram.get());
_linesAndPolygonsProgram = nullptr;
global::renderEngine->removeRenderProgram(_pointsProgram.get());
_pointsProgram = nullptr;
}
bool GeoJsonComponent::isReady() const {
bool isReady = std::all_of(
std::begin(_geometryFeatures),
std::end(_geometryFeatures),
std::mem_fn(&GlobeGeometryFeature::isReady)
);
return isReady && _linesAndPolygonsProgram && _pointsProgram;
}
void GeoJsonComponent::render(const RenderData& data) {
if (!_enabled || !isVisible()) {
return;
}
// @TODO (2023-03-17, emmbr): Once the light source for the globe can be configured,
// this code should use the same light source as the globe
_lightsourceRenderData.updateBasedOnLightSources(data, _lightSources);
// Change GL state:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
if (_drawWireframe) {
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
using PointRenderMode = GlobeGeometryFeature::PointRenderMode;
PointRenderMode pointRenderMode =
static_cast<PointRenderMode>(_pointRenderModeOption.value());
// Compose extra data from relevant properties to pass to the individual features
const GlobeGeometryFeature::ExtraRenderData extraRenderdata = {
_pointSizeScale,
_lineWidthScale,
pointRenderMode,
_lightsourceRenderData
};
// Do two render passes, to properly render opacity of overlaying objects
for (int renderPass = 0; renderPass < 2; ++renderPass) {
for (size_t i = 0; i < _geometryFeatures.size(); ++i) {
if (_features[i]->enabled && _features[i]->isVisible()) {
_geometryFeatures[i].render(
data,
renderPass,
opacity() * _features[i]->opacity(),
extraRenderdata
);
}
}
}
if (_drawWireframe) {
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
glBindVertexArray(0);
// Restore GL State
global::renderEngine->openglStateCache().resetPolygonAndClippingState();
global::renderEngine->openglStateCache().resetBlendState();
global::renderEngine->openglStateCache().resetDepthState();
global::renderEngine->openglStateCache().resetLineState();
}
void GeoJsonComponent::update() {
if (!_enabled || !isVisible()) {
return;
}
glm::vec3 offsets = glm::vec3(_latLongOffset.value(), _heightOffset);
for (size_t i = 0; i < _geometryFeatures.size(); ++i) {
if (!_features[i]->enabled) {
continue;
}
GlobeGeometryFeature& g = _geometryFeatures[i];
if (_dataIsDirty || _heightOffsetIsDirty) {
g.setOffsets(offsets);
}
if (_textureIsDirty) {
g.updateTexture();
}
g.update(_dataIsDirty, _preventUpdatesFromHeightMap);
}
_textureIsDirty = false;
_dataIsDirty = false;
}
void GeoJsonComponent::readFile() {
std::ifstream file(_geoJsonFile);
if (!file.good()) {
LERROR(fmt::format("Failed to open GeoJSON file: {}", _geoJsonFile));
return;
}
_geometryFeatures.clear();
std::string content(
(std::istreambuf_iterator<char>(file)),
(std::istreambuf_iterator<char>())
);
// Parse GeoJSON string into GeoJSON objects
using namespace geos::io;
GeoJSONReader reader;
try {
GeoJSONFeatureCollection fc = reader.readFeatures(content);
for (const GeoJSONFeature& feature : fc.getFeatures()) {
parseSingleFeature(feature);
}
if (_geometryFeatures.empty()) {
LWARNING(fmt::format(
"No GeoJson features could be successfully created for GeoJson layer "
"with identifier '{}'. Disabling layer.", identifier()
));
_enabled = false;
}
}
catch (const geos::util::GEOSException& e) {
LERROR(fmt::format(
"Error creating GeoJson layer with identifier '{}'. Problem reading "
"GeoJson file '{}'. Error: '{}'", identifier(), _geoJsonFile, e.what()
));
}
computeMainFeatureMetaPropeties();
}
void GeoJsonComponent::parseSingleFeature(const geos::io::GeoJSONFeature& feature) {
// Read the geometry
const geos::geom::Geometry* geom = feature.getGeometry();
ghoul_assert(geom, "No geometry found");
// Read the properties
GeoJsonOverrideProperties propsFromFile = propsFromGeoJson(feature);
std::vector<const geos::geom::Geometry*> geomsToAdd;
if (geom->isPuntal()) {
// If points, handle all point features as one feature, even multi-points
geomsToAdd = { geom };
}
else {
size_t nGeom = geom->getNumGeometries();
geomsToAdd.reserve(nGeom);
for (size_t i = 0; i < nGeom; ++i) {
geomsToAdd.push_back(geom->getGeometryN(i));
}
}
// Split other collection features into multiple individual rendered components
for (const geos::geom::Geometry* geometry : geomsToAdd) {
const int index = static_cast<int>(_geometryFeatures.size());
try {
GlobeGeometryFeature g(_globeNode, _defaultProperties, propsFromFile);
g.createFromSingleGeosGeometry(geometry, index, _ignoreHeightsFromFile);
g.initializeGL(_pointsProgram.get(), _linesAndPolygonsProgram.get());
_geometryFeatures.push_back(std::move(g));
std::string name = _geometryFeatures.back().key();
properties::PropertyOwner::PropertyOwnerInfo info = {
makeIdentifier(name),
name
// @TODO: Use description from file, if any
};
_features.push_back(std::make_unique<SubFeatureProps>(info));
addMetaPropertiesToFeature(*_features.back(), index, geometry);
_featuresPropertyOwner.addPropertySubOwner(_features.back().get());
}
catch (const ghoul::MissingCaseException&) {
LERROR(fmt::format(
"Error creating GeoJson layer with identifier '{}'. Problem reading "
"feature {} in GeoJson file '{}'.", identifier(), index, _geoJsonFile
));
// Do nothing
}
}
}
void GeoJsonComponent::addMetaPropertiesToFeature(SubFeatureProps& feature, int index,
const geos::geom::Geometry* geometry)
{
std::unique_ptr<geos::geom::Point> centroid = geometry->getCentroid();
geos::geom::CoordinateXY centroidCoord = *centroid->getCoordinate();
glm::vec2 centroidLatLong = glm::vec2(centroidCoord.y, centroidCoord.x);
feature.centroidLatLong = centroidLatLong;
std::unique_ptr<geos::geom::Geometry> boundingbox = geometry->getEnvelope();
std::unique_ptr<geos::geom::CoordinateSequence> coords = boundingbox->getCoordinates();
glm::vec4 boundingboxLatLong;
if (boundingbox->isRectangle()) {
// A rectangle has 5 coordinates, where the first and third are two corners
boundingboxLatLong = glm::vec4(
(*coords)[0].y,
(*coords)[0].x,
(*coords)[2].y,
(*coords)[2].x
);
}
else {
// Invalid boundingbox. Can happen e.g. for single points.
// Just add a degree to every direction from the centroid
boundingboxLatLong = glm::vec4(
centroidLatLong.x - 1.f,
centroidLatLong.y - 1.f,
centroidLatLong.x + 1.f,
centroidLatLong.y + 1.f
);
}
feature.boundingboxLatLong = boundingboxLatLong;
// Compute the diagonal distance of the bounding box
Geodetic2 pos0 = {
glm::radians(boundingboxLatLong.x),
glm::radians(boundingboxLatLong.y)
};
Geodetic2 pos1 = {
glm::radians(boundingboxLatLong.z),
glm::radians(boundingboxLatLong.w)
};
feature.boundingBoxDiagonal = static_cast<float>(
std::abs(_globeNode.ellipsoid().greatCircleDistance(pos0, pos1))
);
feature.flyToFeature.onChange([this, index]() { flyToFeature(index); });
}
void GeoJsonComponent::computeMainFeatureMetaPropeties() {
if (_features.empty()) {
return;
}
glm::vec2 bboxLowerCorner = {
_features.front()->boundingboxLatLong.value().x,
_features.front()->boundingboxLatLong.value().y
};
glm::vec2 bboxUpperCorner = {
_features.front()->boundingboxLatLong.value().z,
_features.front()->boundingboxLatLong.value().w
};
for (const std::unique_ptr<SubFeatureProps>& f : _features) {
// Update bbox corners
if (f->boundingboxLatLong.value().x < bboxLowerCorner.x) {
bboxLowerCorner.x = f->boundingboxLatLong.value().x;
}
if (f->boundingboxLatLong.value().y < bboxLowerCorner.y) {
bboxLowerCorner.y = f->boundingboxLatLong.value().y;
}
if (f->boundingboxLatLong.value().z > bboxUpperCorner.x) {
bboxUpperCorner.x = f->boundingboxLatLong.value().z;
}
if (f->boundingboxLatLong.value().w > bboxUpperCorner.y) {
bboxUpperCorner.y = f->boundingboxLatLong.value().w;
}
}
// Identify the bounding box midpoints
_centerLatLong = 0.5f * (bboxLowerCorner + bboxUpperCorner);
// Compute the diagonal distance (size) of the bounding box
Geodetic2 pos0 = {
glm::radians(bboxLowerCorner.x),
glm::radians(bboxLowerCorner.y)
};
Geodetic2 pos1 = {
glm::radians(bboxUpperCorner.x),
glm::radians(bboxUpperCorner.y)
};
_bboxDiagonalSize = static_cast<float>(
std::abs(_globeNode.ellipsoid().greatCircleDistance(pos0, pos1))
);
}
void GeoJsonComponent::flyToFeature(std::optional<int> index) const {
// General size properties
float diagonal = _bboxDiagonalSize;
float centroidLat = _centerLatLong.value().x;
float centroidLon = _centerLatLong.value().y;
if (index.has_value()) {
const SubFeatureProps* f = _features[*index].get();
diagonal = f->boundingBoxDiagonal;
centroidLat = f->centroidLatLong.value().x;
centroidLon = f->centroidLatLong.value().y;
}
// Compute a good distance to travel to based on the feature's size.
// Assumes 80 degree FOV
constexpr float Angle = glm::radians(40.f);
float d = diagonal / glm::tan(Angle);
d += _heightOffset;
float lat = centroidLat + _latLongOffset.value().x;
float lon = centroidLon + _latLongOffset.value().y;
global::scriptEngine->queueScript(
fmt::format(
"openspace.globebrowsing.flyToGeo(\"{}\", {}, {}, {})",
_globeNode.owner()->identifier(), lat, lon, d
),
scripting::ScriptEngine::RemoteScripting::Yes
);
}
} // namespace openspace::globebrowsing

View File

@@ -0,0 +1,163 @@
/*****************************************************************************************
* *
* 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. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_GLOBEBROWSING___GEOJSONCOMPONENT___H__
#define __OPENSPACE_MODULE_GLOBEBROWSING___GEOJSONCOMPONENT___H__
#include <openspace/properties/propertyowner.h>
#include <openspace/rendering/fadeable.h>
#include <modules/globebrowsing/src/basictypes.h>
#include <modules/globebrowsing/src/geojson/geojsonproperties.h>
#include <modules/globebrowsing/src/geojson/globegeometryfeature.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/selectionproperty.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/triggerproperty.h>
#include <openspace/properties/vector/vec2property.h>
#include <openspace/properties/vector/vec4property.h>
#include <openspace/rendering/helper.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <ghoul/glm.h>
#include <optional>
#include <vector>
namespace openspace {
struct RenderData;
class LightSource;
namespace documentation { struct Documentation; }
namespace rendering::helper { struct VertexXYZNormal; }
} // namespace::openspace
namespace ghoul::opengl { class ProgramObject; }
namespace geos::io { class GeoJSONFeature; }
namespace openspace::globebrowsing {
class RenderableGlobe;
/**
* A component representing a collection of globe geometry features, whose details
* are read from a GeoJson file
*/
class GeoJsonComponent : public properties::PropertyOwner, public Fadeable {
public:
GeoJsonComponent(const ghoul::Dictionary& dictionary, RenderableGlobe& globe);
void initialize();
void initializeGL();
void deinitializeGL();
bool isReady() const;
bool enabled() const;
void render(const RenderData& data);
void update();
static documentation::Documentation Documentation();
private:
/**
* Small helper class whose purpose is to encapsulate properties related to a
* specific geomoetry feature, and allow things like flying to or fadin out
* individual subfeatures
*/
class SubFeatureProps : public properties::PropertyOwner, public Fadeable {
public:
SubFeatureProps(properties::PropertyOwner::PropertyOwnerInfo info);
properties::BoolProperty enabled;
properties::Vec2Property centroidLatLong;
properties::Vec4Property boundingboxLatLong;
properties::TriggerProperty flyToFeature;
float boundingBoxDiagonal = 0.f;
};
void readFile();
void parseSingleFeature(const geos::io::GeoJSONFeature& feature);
/**
* Add meta properties to the feature, to allow things like flying to it,
* identifying its location, etc
*/
void addMetaPropertiesToFeature(SubFeatureProps& feature, int index,
const geos::geom::Geometry* geometry);
void computeMainFeatureMetaPropeties();
/**
* Trigger a flight to a feature in the collection. No index means to fly to an
* overview of all features in the collection.
*/
void flyToFeature(std::optional<int> index = std::nullopt) const;
std::vector<GlobeGeometryFeature> _geometryFeatures;
properties::BoolProperty _enabled;
properties::StringProperty _geoJsonFile;
properties::FloatProperty _heightOffset;
properties::Vec2Property _latLongOffset;
properties::FloatProperty _pointSizeScale;
properties::FloatProperty _lineWidthScale;
GeoJsonProperties _defaultProperties;
properties::OptionProperty _pointRenderModeOption;
properties::BoolProperty _drawWireframe;
properties::BoolProperty _preventUpdatesFromHeightMap;
properties::TriggerProperty _forceUpdateHeightData;
RenderableGlobe& _globeNode;
bool _ignoreHeightsFromFile = false;
bool _dataIsDirty = true;
bool _heightOffsetIsDirty = false;
bool _dataIsInitialized = false;
bool _textureIsDirty = false;
properties::Vec2Property _centerLatLong;
float _bboxDiagonalSize = 0.f;
properties::TriggerProperty _flyToFeature;
std::vector<std::unique_ptr<LightSource>> _lightSources;
std::unique_ptr<LightSource> _defaultLightSource;
rendering::helper::LightSourceRenderData _lightsourceRenderData;
properties::PropertyOwner _lightSourcePropertyOwner;
properties::PropertyOwner _featuresPropertyOwner;
std::vector<std::unique_ptr<SubFeatureProps>> _features;
std::unique_ptr<ghoul::opengl::ProgramObject> _linesAndPolygonsProgram = nullptr;
std::unique_ptr<ghoul::opengl::ProgramObject> _pointsProgram = nullptr;
};
} // namespace openspace::globebrowsing
#endif // __OPENSPACE_MODULE_GLOBEBROWSING___GEOJSONCOMPONENT___H__

View File

@@ -0,0 +1,146 @@
/*****************************************************************************************
* *
* 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 <modules/globebrowsing/src/geojson/geojsonmanager.h>
#include <openspace/documentation/documentation.h>
#include <openspace/documentation/verifier.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/misc/profiling.h>
namespace {
constexpr std::string_view _loggerCat = "GeoJsonManager";
} // namespace
namespace openspace::globebrowsing {
documentation::Documentation GeoJsonManager::Documentation() {
using namespace documentation;
return {
"GeoJsonManager",
"globebrowsing_geojsonmanager",
{}
};
}
// TODO: Gui name and description
GeoJsonManager::GeoJsonManager() : properties::PropertyOwner({ "GeoJson" }) {}
void GeoJsonManager::initialize(RenderableGlobe* globe) {
ghoul_assert(globe, "No globe provided");
_parentGlobe = globe;
}
void GeoJsonManager::deinitializeGL() {
for (const std::unique_ptr<GeoJsonComponent>& g : _geoJsonObjects) {
g->deinitializeGL();
}
}
bool GeoJsonManager::isReady() const {
bool isReady = std::all_of(
std::begin(_geoJsonObjects),
std::end(_geoJsonObjects),
[](const std::unique_ptr<GeoJsonComponent>& g) {
return g->isReady();
}
);
return isReady;
}
void GeoJsonManager::addGeoJsonLayer(const ghoul::Dictionary& layerDict) {
ZoneScoped
try {
// Parse dictionary
documentation::testSpecificationAndThrow(
GeoJsonComponent::Documentation(),
layerDict,
"GeoJsonComponent"
);
std::string identifier = layerDict.value<std::string>("Identifier");
if (hasPropertySubOwner(identifier)) {
LERROR("GeoJson layer with identifier '" + identifier + "' already exists");
return;
}
// TODO: use owner instead of explicit parent?
std::unique_ptr<GeoJsonComponent> geo =
std::make_unique<GeoJsonComponent>(layerDict, *_parentGlobe);
geo->initializeGL();
GeoJsonComponent* ptr = geo.get();
_geoJsonObjects.push_back(std::move(geo));
addPropertySubOwner(ptr);
}
catch (const documentation::SpecificationError& e) {
logError(e);
}
catch (const ghoul::RuntimeError& e) {
LERRORC(e.component, e.message);
}
}
void GeoJsonManager::deleteLayer(const std::string& layerIdentifier) {
ZoneScoped
for (auto it = _geoJsonObjects.begin(); it != _geoJsonObjects.end(); ++it) {
if (it->get()->identifier() == layerIdentifier) {
// we need to make a copy as the layerIdentifier is only a reference
// which will no longer be valid once it is deleted
std::string id = layerIdentifier;
removePropertySubOwner(it->get());
(*it)->deinitializeGL();
_geoJsonObjects.erase(it);
LINFO("Deleted GeoJson layer " + id);
return;
}
}
LERROR("Could not find GeoJson layer " + layerIdentifier);
}
void GeoJsonManager::update() {
ZoneScoped
for (std::unique_ptr<GeoJsonComponent>& obj : _geoJsonObjects) {
if (obj->enabled()) {
obj->update();
}
}
}
void GeoJsonManager::render(const RenderData& data) {
ZoneScoped
for (std::unique_ptr<GeoJsonComponent>& obj : _geoJsonObjects) {
if (obj->enabled()) {
obj->render(data);
}
}
}
} // namespace openspace::globebrowsing

View File

@@ -0,0 +1,74 @@
/*****************************************************************************************
* *
* 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. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_GLOBEBROWSING___GEOJSONMANAGER___H__
#define __OPENSPACE_MODULE_GLOBEBROWSING___GEOJSONMANAGER___H__
#include <openspace/properties/propertyowner.h>
#include <modules/globebrowsing/src/geojson/geojsoncomponent.h>
#include <functional>
#include <memory>
#include <vector>
namespace ghoul { class Dictionary; }
namespace openspace { struct RenderData; }
namespace openspace::documentation { struct Documentation; }
namespace openspace::globebrowsing {
class RenderableGlobe;
/**
* Manages multiple GeoJSON objects of a globe
*/
class GeoJsonManager : public properties::PropertyOwner {
public:
GeoJsonManager();
void initialize(RenderableGlobe* globe);
void deinitializeGL();
bool isReady() const;
void addGeoJsonLayer(const ghoul::Dictionary& layerDict);
//void addGeoJsonLayer(const std::string& filePath); // TODO: just add from file
void deleteLayer(const std::string& layerIdentifier);
void update();
void render(const RenderData& data);
//void onChange(std::function<void(Layer* l)> callback);
static documentation::Documentation Documentation();
private:
std::vector<std::unique_ptr<GeoJsonComponent>> _geoJsonObjects;
RenderableGlobe* _parentGlobe = nullptr;
};
} // namespace openspace::globebrowsing
#endif // __OPENSPACE_MODULE_GLOBEBROWSING___GEOJSONMANAGER___H__

View File

@@ -0,0 +1,617 @@
/*****************************************************************************************
* *
* 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 <modules/globebrowsing/src/geojson/geojsonproperties.h>
#include <modules/globebrowsing/src/renderableglobe.h>
#include <openspace/documentation/documentation.h>
#include <ghoul/logging/logmanager.h>
#include <geos/io/GeoJSON.h>
#include <algorithm>
#include <cstdio>
// Keys used to read properties from GeoJson files
namespace geojson::propertykeys {
constexpr std::string_view Name = "name";
constexpr std::string_view Description = "description";
constexpr std::string_view Opacity = "opacity";
constexpr std::array<std::string_view, 2> Color = { "color", "stroke" };
constexpr std::array<std::string_view, 2> FillColor = { "fill", "fill-color" };
constexpr std::string_view FillOpacity = "fill-opacity";
constexpr std::string_view LineWidth = "stroke-width";
constexpr std::string_view PointSize = "point-size";
constexpr std::array<std::string_view, 3> Texture = { "texture", "sprite", "point-texture" };
constexpr std::array<std::string_view, 2> PointTextureAnchor = { "point-anchor", "anchor" };
constexpr std::string_view PointTextureAnchorBottom = "bottom";
constexpr std::string_view PointTextureAnchorCenter = "center";
constexpr std::string_view Extrude = "extrude";
constexpr std::string_view PerformShading = "performShading";
constexpr std::string_view Tessellate = "tessellate";
constexpr std::string_view TessellationLevel = "tessellationLevel";
constexpr std::string_view TessellationMaxDistance = "tessellationDistance";
constexpr std::string_view AltitudeMode = "altitudeMode";
constexpr std::string_view AltitudeModeClamp = "clampToGround";
constexpr std::string_view AltitudeModeAbsolute = "absolute";
constexpr std::string_view AltitudeModeRelative = "relativeToGround";
} // namespace geojson::propertykeys
namespace {
using PropertyInfo = openspace::properties::Property::PropertyInfo;
template <std::size_t SIZE>
bool keyMatches(const std::string_view key,
const std::array<std::string_view, SIZE>& keyAlternativesArray,
std::optional<const PropertyInfo> propInfo = std::nullopt)
{
auto it = std::find(keyAlternativesArray.begin(), keyAlternativesArray.end(), key);
if (it != keyAlternativesArray.end()) {
return true;
}
if (propInfo.has_value() && key == (*propInfo).identifier) {
return true;
}
return false;
}
bool keyMatches(const std::string_view key, const std::string_view keyAlternative,
std::optional<const PropertyInfo> propInfo = std::nullopt)
{
const std::array<std::string_view, 1> array = { keyAlternative };
return keyMatches(key, array, propInfo);
}
glm::vec3 hexToRbg(std::string_view hexColor) {
int r, g, b;
// @TODO: Consider using scn::scan instead of sscanf
int ret = std::sscanf(hexColor.data(), "#%02x%02x%02x", &r, &g, &b);
// @TODO: Handle return value to validate color
return (1.f / 255.f) * glm::vec3(r, g, b);
}
glm::vec3 getColorValue(const geos::io::GeoJSONValue& value) {
glm::vec3 color;
if (value.isArray()) {
const std::vector<geos::io::GeoJSONValue>& val = value.getArray();
if (val.size() != 3) {
// @TODO: Should add some more information on which file the reading failed for
LERRORC("GeoJson", fmt::format(
"Failed reading color property. Expected 3 values, got {}", val.size()
));
}
// @TODO Use verifiers to verify color values
// @TODO Parse values given in RGB in ranges 0-255?
color = glm::vec3(
static_cast<float>(val.at(0).getNumber()),
static_cast<float>(val.at(1).getNumber()),
static_cast<float>(val.at(2).getNumber())
);
}
else if (value.isString()) {
const std::string hex = value.getString();
// @TODO Verify color
color = hexToRbg(hex);
}
return color;
};
constexpr openspace::properties::Property::PropertyInfo OpacityInfo = {
"Opacity",
"Opacity",
"This value determines the opacity of this object. A value of 0 means "
"completely transparent",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo ColorInfo = {
"Color",
"Color",
"The color of the rendered geometry. For points it will be used as a multiply"
"color for any provided texture",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo FillOpacityInfo = {
"FillOpacity",
"Fill Opacity",
"This value determines the opacity of the filled portion of a polygon. Will "
"also be used for extruded features",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo FillColorInfo = {
"FillColor",
"Fill Color",
"The color of the filled portion of a rendered polygon. Will also be used for "
"extruded features",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo LineWidthInfo = {
"LineWidth",
"Line Width",
"The width of any rendered lines",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo PointSizeInfo = {
"PointSize",
"Point Size",
"The size of any rendered points. The size will be scaled based on the "
"bounding sphere of the globe",
openspace::properties::Property::Visibility::NoviceUser
};
constexpr openspace::properties::Property::PropertyInfo PointTextureInfo = {
"PointTexture",
"Point Texture",
"A texture to be used for rendering points. No value means to use the default "
"texture provided by the GlobeBrowsing module. If no texture is provided there "
"either, the point will be rendered as a plane and colored by the color value.",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo ExtrudeInfo = {
"Extrude",
"Extrude",
"If true, extrude the mesh or line to intersect the globe",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo PerformShadingInfo = {
"PerformShading",
"Perform Shading",
"If true, perform shading on any create meshes, either from polygons or "
"extruded lines. The shading will be computed based on any light sources of the "
"GeoJson component",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo AltitudeModeInfo = {
"AltitudeMode",
"Altitude Mode",
"The altitude mode decides how any height values of the geo coordinates should "
"be interpreted. Absolute means that the height is interpreted as the height "
"above the reference ellipsoid, while RelativeToGround takes the height map "
"into account. For coordinates with only two values (latitude and longitude), "
"the height is considered to be equal to zero",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo PointAnchorOptionInfo = {
"PointTextureAnchor",
"Point Texture Anchor",
"Decides the placement of the point texture in relation to the position. "
"Default is a the bottom of the texture, but it can also be put at the center",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo TessellationEnabledInfo = {
"Enabled",
"Enabled",
"If false, no tessellation to bend the geometry based on the curvature of the "
"planet is performed. This leads to increased performance, but tessellation is "
"neccessary for large geometry that spans a big portion of the globe. Otherwise "
"it may intersect the surface",
openspace::properties::Property::Visibility::User
};
constexpr openspace::properties::Property::PropertyInfo UseTessellationLevelInfo = {
"UseTessellationLevel",
"Use Tessellation Level",
"If true, use the 'Tesselation Level' to control the level of detail for the "
"tessellation. The distance used will be the 'Tesselation Distance' divided by "
"the 'Tesselation Level', so the higher the level value, the smaller each "
"segment in the geomoetry will be",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo TessellationLevelInfo = {
"TessellationLevel",
"Tessellation Level",
"When manual tessellation is enabled, this value will be used to determine how "
"much tessellation to apply. The resulting distance used for subdividing the "
"geometry will be the 'Tessellation Distance' divided by this value. Zero means "
"to use the 'Tessellation Distance' as is.",
openspace::properties::Property::Visibility::AdvancedUser
};
constexpr openspace::properties::Property::PropertyInfo TessellationDistanceInfo = {
"TessellationDistance",
"Tessellation Distance",
"Defult distance to use for tessellation of line and polygon geometry. Anything "
"larger than this distance will be automatically subdivided into smaller pieces "
"matching this distance, while anything smaller will not be subdivided. Per "
"default this will be set to a distance corresponding to about 1 degree "
"longitude on the globe",
openspace::properties::Property::Visibility::AdvancedUser
};
struct [[codegen::Dictionary(GeoJsonProperties)]] Parameters {
// [[codegen::verbatim(OpacityInfo.description)]]
std::optional<float> opacity [[codegen::inrange(0.0, 1.0)]];
// [[codegen::verbatim(ColorInfo.description)]]
std::optional<glm::vec3> color [[codegen::color()]];
// [[codegen::verbatim(FillOpacityInfo.description)]]
std::optional<float> fillOpacity [[codegen::inrange(0.0, 1.0)]];
// [[codegen::verbatim(FillColorInfo.description)]]
std::optional<glm::vec3> fillColor [[codegen::color()]];
// [[codegen::verbatim(LineWidthInfo.description)]]
std::optional<float> lineWidth [[codegen::greater(0.0)]];
// [[codegen::verbatim(PointSizeInfo.description)]]
std::optional<float> pointSize [[codegen::greater(0.0)]];
// [[codegen::verbatim(PointTextureInfo.description)]]
std::optional<std::string> pointTexture;
// [[codegen::verbatim(ExtrudeInfo.description)]]
std::optional<bool> extrude;
// [[codegen::verbatim(PerformShadingInfo.description)]]
std::optional<bool> performShading;
enum class [[codegen::map(openspace::globebrowsing::GeoJsonProperties::AltitudeMode)]] AltitudeMode {
Absolute,
RelativeToGround
};
// [[codegen::verbatim(AltitudeModeInfo.description)]]
std::optional<AltitudeMode> altitudeMode;
enum class [[codegen::map(openspace::globebrowsing::GeoJsonProperties::PointTextureAnchor)]] PointTextureAnchor {
Bottom,
Center
};
// [[codegen::verbatim(PointAnchorOptionInfo.description)]]
std::optional<PointTextureAnchor> pointTextureAnchor;
struct Tessellation {
// [[codegen::verbatim(TessellationEnabledInfo.description)]]
std::optional<bool> ebabled;
// [[codegen::verbatim(UseTessellationLevelInfo.description)]]
std::optional<bool> useTessellationLevel;
// [[codegen::verbatim(TessellationLevelInfo.description)]]
std::optional<int> tessellationLevel;
// [[codegen::verbatim(TessellationDistanceInfo.description)]]
std::optional<float> tessellationDistance;
};
std::optional<Tessellation> tessellation;
};
#include "geojsonproperties_codegen.cpp"
} // namespace
namespace openspace::globebrowsing {
documentation::Documentation GeoJsonProperties::Documentation() {
return codegen::doc<Parameters>("globebrowsing_geojsonproperties");
}
GeoJsonProperties::Tessellation::Tessellation()
: properties::PropertyOwner({ "Tessellation" })
, enabled(TessellationEnabledInfo, true)
, useLevel(UseTessellationLevelInfo, false)
, level(TessellationLevelInfo, 10, 0, 100)
, distance(TessellationDistanceInfo, 100000.f, 0.f, 1e12f)
{
addProperty(enabled);
addProperty(distance);
addProperty(level);
addProperty(useLevel);
}
GeoJsonProperties::GeoJsonProperties()
: properties::PropertyOwner({ "DefaultProperties" })
, opacity(OpacityInfo, 1.f, 0.f, 1.f)
, color(ColorInfo, glm::vec3(1.f), glm::vec3(0.f), glm::vec3(1.f))
, fillOpacity(FillOpacityInfo, 0.7f, 0.f, 1.f)
, fillColor(FillColorInfo, glm::vec3(0.5f), glm::vec3(0.f), glm::vec3(1.f))
, lineWidth(LineWidthInfo, 2.f, 0.01f, 10.f)
, pointSize(PointSizeInfo, 10.f, 0.01f, 100.f)
, pointTexture(PointTextureInfo)
, extrude(ExtrudeInfo, false)
, performShading(PerformShadingInfo, false)
, altitudeModeOption(
AltitudeModeInfo,
properties::OptionProperty::DisplayType::Dropdown
)
, pointAnchorOption(
PointAnchorOptionInfo,
properties::OptionProperty::DisplayType::Dropdown
)
{
addProperty(opacity);
color.setViewOption(properties::Property::ViewOptions::Color);
addProperty(color);
addProperty(fillOpacity);
fillColor.setViewOption(properties::Property::ViewOptions::Color);
addProperty(fillColor);
addProperty(lineWidth);
addProperty(pointSize);
addProperty(pointTexture);
addProperty(extrude);
addProperty(performShading);
altitudeModeOption.addOptions({
{ static_cast<int>(AltitudeMode::Absolute), "Absolute"},
{ static_cast<int>(AltitudeMode::RelativeToGround), "Relative to Ground" }
//{ static_cast<int>(AltitudeMode::ClampToGround), "Clamp to Ground" } / TODO: add ClampToGround
});
addProperty(altitudeModeOption);
pointAnchorOption.addOptions({
{ static_cast<int>(PointTextureAnchor::Bottom), "Bottom"},
{ static_cast<int>(PointTextureAnchor::Center), "Center" }
});
addProperty(pointAnchorOption);
addPropertySubOwner(tessellation);
}
void GeoJsonProperties::createFromDictionary(const ghoul::Dictionary& dictionary,
const RenderableGlobe& globe)
{
const Parameters p = codegen::bake<Parameters>(dictionary);
opacity = p.opacity.value_or(opacity);
color = p.color.value_or(color);
fillOpacity = p.fillOpacity.value_or(fillOpacity);
fillColor = p.fillColor.value_or(fillColor);
lineWidth = p.lineWidth.value_or(lineWidth);
pointSize = p.pointSize.value_or(pointSize);
if (p.pointTexture.has_value()) {
pointTexture = *p.pointTexture;
}
if (p.pointTextureAnchor.has_value()) {
pointAnchorOption = static_cast<int>(codegen::map<PointTextureAnchor>(
*p.pointTextureAnchor
));
}
extrude = p.extrude.value_or(extrude);
performShading = p.performShading.value_or(performShading);
if (p.altitudeMode.has_value()) {
altitudeModeOption = static_cast<int>(codegen::map<AltitudeMode>(*p.altitudeMode));
}
// Set up default value and max value for tessellation distance based on globe size.
// Distances are computed based on a certain lat/long angle size
constexpr float DefaultAngle = glm::radians(1.f);
constexpr float MaxAngle = glm::radians(45.f);
float defaultDistance = globe.ellipsoid().longitudalDistance(0.f, 0.f, DefaultAngle);
float maxDistance = globe.ellipsoid().longitudalDistance(0.f, 0.f, MaxAngle);
tessellation.distance = defaultDistance;
tessellation.distance.setMaxValue(maxDistance);
if (p.tessellation.has_value()) {
const Parameters::Tessellation pTess = (*p.tessellation);
tessellation.enabled = pTess.useTessellationLevel.value_or(tessellation.enabled);
tessellation.useLevel =
pTess.useTessellationLevel.value_or(tessellation.useLevel);
tessellation.level = pTess.tessellationLevel.value_or(tessellation.level);
tessellation.distance =
pTess.tessellationDistance.value_or(tessellation.distance);
}
}
GeoJsonProperties::AltitudeMode GeoJsonProperties::altitudeMode() const {
return static_cast<GeoJsonProperties::AltitudeMode>(altitudeModeOption.value());
}
GeoJsonProperties::PointTextureAnchor GeoJsonProperties::pointTextureAnchor() const {
return static_cast<GeoJsonProperties::PointTextureAnchor>(pointAnchorOption.value());
}
GeoJsonOverrideProperties propsFromGeoJson(const geos::io::GeoJSONFeature& feature) {
const std::map<std::string, geos::io::GeoJSONValue>& props = feature.getProperties();
GeoJsonOverrideProperties result;
auto parseProperty = [&result](const std::string_view key,
const geos::io::GeoJSONValue& value)
{
using namespace geojson;
if (keyMatches(key, propertykeys::Name)) {
result.name = value.getString();
}
else if (keyMatches(key, propertykeys::Opacity, OpacityInfo)) {
result.opacity = static_cast<float>(value.getNumber());
}
else if (keyMatches(key, propertykeys::Color, ColorInfo)) {
result.color = getColorValue(value);
}
else if (keyMatches(key, propertykeys::FillOpacity, FillOpacityInfo)) {
result.fillOpacity = static_cast<float>(value.getNumber());
}
else if (keyMatches(key, propertykeys::FillColor, FillColorInfo)) {
result.fillColor = getColorValue(value);
}
else if (keyMatches(key, propertykeys::LineWidth, LineWidthInfo)) {
result.lineWidth = static_cast<float>(value.getNumber());
}
else if (keyMatches(key, propertykeys::PointSize, PointSizeInfo)) {
result.pointSize = static_cast<float>(value.getNumber());
}
else if (keyMatches(key, propertykeys::Texture, PointTextureInfo)) {
result.pointTexture = value.getString();
}
else if (keyMatches(key, propertykeys::PointTextureAnchor, PointAnchorOptionInfo))
{
std::string mode = value.getString();
if (mode == propertykeys::PointTextureAnchorBottom) {
result.pointTextureAnchor = GeoJsonProperties::PointTextureAnchor::Bottom;
}
else if (mode == propertykeys::PointTextureAnchorCenter) {
result.pointTextureAnchor = GeoJsonProperties::PointTextureAnchor::Center;
}
else {
LERRORC("GeoJson", fmt::format(
"Point texture anchor mode '{}' not supported", mode
));
}
}
else if (keyMatches(key, propertykeys::Extrude, ExtrudeInfo)) {
result.extrude = value.getBoolean();
}
else if (keyMatches(key, propertykeys::AltitudeMode, AltitudeModeInfo)) {
std::string mode = value.getString();
if (mode == propertykeys::AltitudeModeAbsolute) {
result.altitudeMode = GeoJsonProperties::AltitudeMode::Absolute;
}
else if (mode == propertykeys::AltitudeModeRelative) {
result.altitudeMode = GeoJsonProperties::AltitudeMode::RelativeToGround;
}
// @TODO Include when support is implemented
//else if (mode == propertykeys::AltitudeModeClamp) {
// result.altitudeMode = GeoJsonProperties::AltitudeMode::ClampToGround;
//}
else {
LERRORC("GeoJson", fmt::format(
"Altitude mode '{}' not supported", mode
));
}
}
else if (keyMatches(key, propertykeys::Tessellate, TessellationEnabledInfo)) {
result.tessellationEnabled = value.getBoolean();
}
else if (keyMatches(key, propertykeys::TessellationLevel, TessellationLevelInfo)) {
result.useTessellationLevel = true;
result.tessellationLevel = static_cast<float>(value.getNumber());
}
else if (keyMatches(key, propertykeys::TessellationMaxDistance, TessellationDistanceInfo)) {
result.tessellationDistance = static_cast<float>(value.getNumber());
}
};
for (auto const& [key, value] : props) {
try {
parseProperty(key, value);
}
catch (const geos::io::GeoJSONValue::GeoJSONTypeError&) {
// @TODO: Should add some more information on which file the reading failed for
LERRORC("GeoJson", fmt::format(
"Error reading GeoJson property '{}'. Value has wrong type", key
));
}
}
return result;
}
float PropertySet::opacity() const {
return overrideValues.opacity.value_or(defaultValues.opacity);
}
glm::vec3 PropertySet::color() const {
return overrideValues.color.value_or(defaultValues.color);
}
float PropertySet::fillOpacity() const {
return overrideValues.fillOpacity.value_or(defaultValues.fillOpacity);
}
glm::vec3 PropertySet::fillColor() const {
return overrideValues.fillColor.value_or(defaultValues.fillColor);
}
float PropertySet::lineWidth() const {
return overrideValues.lineWidth.value_or(defaultValues.lineWidth);
}
float PropertySet::pointSize() const {
return overrideValues.pointSize.value_or(defaultValues.pointSize);
}
std::string PropertySet::pointTexture() const {
return overrideValues.pointTexture.value_or(defaultValues.pointTexture);
}
GeoJsonProperties::PointTextureAnchor PropertySet::pointTextureAnchor() const {
return overrideValues.pointTextureAnchor.value_or(
defaultValues.pointTextureAnchor()
);
}
bool PropertySet::extrude() const {
return overrideValues.extrude.value_or(defaultValues.extrude);
}
bool PropertySet::performShading() const {
return overrideValues.performShading.value_or(defaultValues.performShading);
}
GeoJsonProperties::AltitudeMode PropertySet::altitudeMode() const {
return overrideValues.altitudeMode.value_or(defaultValues.altitudeMode());
}
bool PropertySet::tessellationEnabled() const {
return overrideValues.tessellationEnabled.value_or(defaultValues.tessellation.enabled);
}
bool PropertySet::useTessellationLevel() const {
return overrideValues.useTessellationLevel.value_or(
defaultValues.tessellation.useLevel
);
}
int PropertySet::tessellationLevel() const {
return overrideValues.tessellationLevel.value_or(defaultValues.tessellation.level);
}
float PropertySet::tessellationDistance() const {
return overrideValues.tessellationDistance.value_or(
defaultValues.tessellation.distance
);
}
bool PropertySet::hasOverrideTexture() const {
return overrideValues.pointTexture.has_value();
}
} // namespace openspace::globebrowsing

View File

@@ -0,0 +1,151 @@
/*****************************************************************************************
* *
* 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. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_GLOBEBROWSING___GEOJSONPROPERTIES___H__
#define __OPENSPACE_MODULE_GLOBEBROWSING___GEOJSONPROPERTIES___H__
#include <openspace/properties/propertyowner.h>
#include <openspace/properties/optionproperty.h>
#include <openspace/properties/scalar/boolproperty.h>
#include <openspace/properties/scalar/floatproperty.h>
#include <openspace/properties/scalar/intproperty.h>
#include <openspace/properties/stringproperty.h>
#include <openspace/properties/vector/vec3property.h>
#include <ghoul/glm.h>
#include <optional>
#include <string_view>
namespace geos::io { class GeoJSONFeature; }
namespace openspace::globebrowsing {
class RenderableGlobe;
struct GeoJsonProperties : public properties::PropertyOwner {
GeoJsonProperties();
void createFromDictionary(const ghoul::Dictionary& dictionary,
const RenderableGlobe& globe);
static documentation::Documentation Documentation();
// These are based on the KML specification
enum class AltitudeMode {
// Compute position as an altitude above the reference ellipsoid
Absolute = 0,
// Compute position using altitude above the height map
RelativeToGround
// Stick to planet surface (TODO: use GDAL to render layer instead and use as default)
//ClampToGround
};
enum class PointTextureAnchor {
Bottom = 0,
Center
};
struct Tessellation : public properties::PropertyOwner {
Tessellation();
properties::BoolProperty enabled;
properties::BoolProperty useLevel;
properties::IntProperty level;
properties::FloatProperty distance;
} tessellation;
AltitudeMode altitudeMode() const;
PointTextureAnchor pointTextureAnchor() const;
properties::FloatProperty opacity;
properties::Vec3Property color;
properties::FloatProperty fillOpacity;
properties::Vec3Property fillColor;
properties::FloatProperty lineWidth;
properties::FloatProperty pointSize;
properties::StringProperty pointTexture;
properties::OptionProperty pointAnchorOption;
properties::BoolProperty extrude;
properties::BoolProperty performShading;
properties::OptionProperty altitudeModeOption;
};
// Optional versions of all the properties above, that can be read from a geoJson file
// and used to override any default values
struct GeoJsonOverrideProperties {
std::optional<std::string> name;
std::optional<float> opacity;
std::optional<glm::vec3> color;
std::optional<float> fillOpacity;
std::optional<glm::vec3> fillColor;
std::optional<float> lineWidth;
std::optional<float> pointSize;
std::optional<std::string> pointTexture;
std::optional<GeoJsonProperties::PointTextureAnchor> pointTextureAnchor;
std::optional<bool> extrude;
std::optional<bool> performShading;
std::optional<GeoJsonProperties::AltitudeMode> altitudeMode;
std::optional<bool> tessellationEnabled;
std::optional<bool> useTessellationLevel;
std::optional<int> tessellationLevel;
std::optional<float> tessellationDistance;
};
GeoJsonOverrideProperties propsFromGeoJson(const geos::io::GeoJSONFeature& feature);
struct PropertySet {
// This value set should be a reference to the main component's propertyowner
GeoJsonProperties& defaultValues;
// This is a unique set of properties to use for overriding the default values
GeoJsonOverrideProperties overrideValues;
float opacity() const;
glm::vec3 color() const;
float fillOpacity() const;
glm::vec3 fillColor() const;
float lineWidth() const;
float pointSize() const;
std::string pointTexture() const;
GeoJsonProperties::PointTextureAnchor pointTextureAnchor() const;
bool extrude() const;
bool performShading() const;
GeoJsonProperties::AltitudeMode altitudeMode() const;
bool tessellationEnabled() const;
bool useTessellationLevel() const;
int tessellationLevel() const;
float tessellationDistance() const;
bool hasOverrideTexture() const;
};
} // namespace openspace::globebrowsing
#endif // __OPENSPACE_MODULE_GLOBEBROWSING___GEOJSONPROPERTIES___H__

View File

@@ -0,0 +1,914 @@
/*****************************************************************************************
* *
* 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 <modules/globebrowsing/src/geojson/globegeometryfeature.h>
#include <modules/globebrowsing/globebrowsingmodule.h>
#include <modules/globebrowsing/src/geojson/globegeometryhelper.h>
#include <modules/globebrowsing/src/renderableglobe.h>
#include <openspace/documentation/documentation.h>
#include <openspace/engine/globals.h>
#include <openspace/engine/moduleengine.h>
#include <openspace/query/query.h>
#include <openspace/rendering/renderengine.h>
#include <openspace/scene/scenegraphnode.h>
#include <openspace/util/updatestructures.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/fmt.h>
#include <ghoul/logging/logmanager.h>
#include <ghoul/opengl/openglstatecache.h>
#include <ghoul/opengl/programobject.h>
#include <geos/util/GEOSException.h>
#include <geos/triangulate/polygon/ConstrainedDelaunayTriangulator.h>
#include <geos/util/IllegalStateException.h>
#include <algorithm>
#include <filesystem>
#include <fstream>
namespace {
constexpr const char* _loggerCat = "GlobeGeometryFeature";
constexpr std::chrono::milliseconds HeightUpdateInterval(10000);
} // namespace
namespace openspace::globebrowsing {
void GlobeGeometryFeature::RenderFeature::initializeBuffers() {
if (vaoId == 0) {
glGenVertexArrays(1, &vaoId);
}
if (vboId == 0) {
glGenBuffers(1, &vboId);
}
}
GlobeGeometryFeature::GlobeGeometryFeature(const RenderableGlobe& globe,
GeoJsonProperties& defaultProperties,
GeoJsonOverrideProperties& overrideProperties)
: _globe(globe)
, _properties({ defaultProperties, overrideProperties })
, _lastHeightUpdateTime(std::chrono::system_clock::now())
{}
std::string GlobeGeometryFeature::key() const {
return _key;
}
void GlobeGeometryFeature::setOffsets(const glm::vec3& value) {
_offsets = value;
}
void GlobeGeometryFeature::initializeGL(ghoul::opengl::ProgramObject* pointsProgram,
ghoul::opengl::ProgramObject* linesAndPolygonsProgram)
{
_pointsProgram = pointsProgram;
_linesAndPolygonsProgram = linesAndPolygonsProgram;
if (isPoints()) {
updateTexture(true);
}
}
void GlobeGeometryFeature::deinitializeGL() {
for (const RenderFeature& r : _renderFeatures) {
glDeleteVertexArrays(1, &r.vaoId);
glDeleteBuffers(1, &r.vboId);
}
_pointTexture = nullptr;
}
bool GlobeGeometryFeature::isReady() const {
bool shadersAreReady = _linesAndPolygonsProgram && _pointsProgram;
bool textureIsReady = (!_hasTexture) || _pointTexture;
return shadersAreReady && textureIsReady;
}
bool GlobeGeometryFeature::isPoints() const {
return _type == GeometryType::Point;
}
bool GlobeGeometryFeature::useHeightMap() const {
return _properties.altitudeMode() ==
GeoJsonProperties::AltitudeMode::RelativeToGround;
}
void GlobeGeometryFeature::updateTexture(bool isInitializeStep) {
std::string texture;
GlobeBrowsingModule* m = global::moduleEngine->module<GlobeBrowsingModule>();
if (!isInitializeStep && _properties.hasOverrideTexture()) {
// Here we don't necessarily have to update, since it should have been
// created at initialization. Do nothing
return;
}
else if (!_properties.pointTexture().empty()) {
texture = _properties.pointTexture();
}
else if (m->hasDefaultGeoPointTexture()) {
texture = m->defaultGeoPointTexture();
}
else {
// No texture => render without texture
_hasTexture = false;
_pointTexture = nullptr;
return;
}
if (isInitializeStep || !_pointTexture) {
_pointTexture = std::make_unique<TextureComponent>(2);
_pointTexture->setFilterMode(ghoul::opengl::Texture::FilterMode::AnisotropicMipMap);
_pointTexture->setWrapping(ghoul::opengl::Texture::WrappingMode::ClampToEdge);
}
std::filesystem::path texturePath = absPath(texture);
if (std::filesystem::is_regular_file(texturePath)) {
_hasTexture = true;
_pointTexture->loadFromFile(texture);
_pointTexture->uploadToGpu();
}
else {
LERROR(fmt::format(
"Trying to use texture file that does not exist: {} ", texturePath
));
}
}
void GlobeGeometryFeature::createFromSingleGeosGeometry(const geos::geom::Geometry* geo,
int index, bool ignoreHeights)
{
ghoul_assert(geo, "No geometry provided");
ghoul_assert(
geo->isPuntal() || !geo->isCollection(),
"Non-point geometry can not be a collection"
);
switch (geo->getGeometryTypeId()) {
case geos::geom::GEOS_POINT:
case geos::geom::GEOS_MULTIPOINT: {
_geoCoordinates.push_back(geometryhelper::geometryCoordsAsGeoVector(geo));
_type = GeometryType::Point;
break;
}
case geos::geom::GEOS_LINESTRING: {
_geoCoordinates.push_back(geometryhelper::geometryCoordsAsGeoVector(geo));
_type = GeometryType::LineString;
break;
}
case geos::geom::GEOS_POLYGON: {
try {
const geos::geom::Polygon* p = dynamic_cast<const geos::geom::Polygon*>(geo);
// Triangles
// Note that Constrained Delaunay triangulation supports polygons with holes :)
std::vector<geos::geom::Coordinate> triCoords;
TriList<Tri> triangles;
using geos::triangulate::polygon::ConstrainedDelaunayTriangulator;
ConstrainedDelaunayTriangulator::triangulatePolygon(p, triangles);
triCoords.reserve(3 * triangles.size());
// Add three coordinates per triangle. Note flipped winding order
// (want counter clockwise, but GEOS provides clockwise)
for (const Tri* t : triangles) {
triCoords.push_back(t->getCoordinate(0));
triCoords.push_back(t->getCoordinate(2));
triCoords.push_back(t->getCoordinate(1));
}
_triangleCoordinates = geometryhelper::coordsToGeodetic(triCoords);
// Boundaries / Lines
// Normalize to make sure rings have correct orientation
std::unique_ptr<Polygon> pNormalized = p->clone();
pNormalized->normalize();
const geos::geom::LinearRing* outerRing = pNormalized->getExteriorRing();
std::vector<Geodetic3> outerBoundsGeoCoords =
geometryhelper::geometryCoordsAsGeoVector(outerRing);
if (!outerBoundsGeoCoords.empty()) {
int nHoles = static_cast<int>(pNormalized->getNumInteriorRing());
_geoCoordinates.reserve(nHoles + 1);
// Outer bounds
_geoCoordinates.push_back(outerBoundsGeoCoords);
// Inner bounds (holes)
for (int i = 0; i < nHoles; ++i) {
const geos::geom::LinearRing* hole = pNormalized->getInteriorRingN(i);
std::vector<Geodetic3> ringGeoCoords =
geometryhelper::geometryCoordsAsGeoVector(hole);
_geoCoordinates.push_back(ringGeoCoords);
}
}
_type = GeometryType::Polygon;
}
catch (geos::util::IllegalStateException&) {
LERROR("Non-simple (e.g. self-intersecting) polygons not supported yet");
throw ghoul::MissingCaseException();
// TODO: handle self-intersections points
// https://www.sciencedirect.com/science/article/pii/S0304397520304199
}
break;
}
default:
throw ghoul::MissingCaseException();
}
// Reset height values if we don't care about them
if (ignoreHeights) {
for (std::vector<Geodetic3>& vec : _geoCoordinates) {
for (Geodetic3& coord : vec) {
coord.height = 0.0;
}
}
}
// Compute reference positions to use for checking if height map changes
geos::geom::Coordinate centroid;
geo->getCentroid(centroid);
Geodetic3 geoCentroid = geometryhelper::coordsToGeodetic({ centroid }).front();
_heightUpdateReferencePoints.push_back(std::move(geoCentroid));
;
std::vector<Geodetic3> envelopeGeoCoords =
geometryhelper::geometryCoordsAsGeoVector(geo->getEnvelope().get());
_heightUpdateReferencePoints.insert(
_heightUpdateReferencePoints.end(),
envelopeGeoCoords.begin(),
envelopeGeoCoords.end()
);
if (_properties.overrideValues.name.has_value()) {
_key = *_properties.overrideValues.name;
}
else {
_key = fmt::format("Feature {} - {}", index, geo->getGeometryType());
}
}
void GlobeGeometryFeature::render(const RenderData& renderData, int pass,
float mainOpacity,
const ExtraRenderData& extraRenderData)
{
ghoul_assert(pass >= 0 && pass < 2, "Render pass variable out of accepted range");
float opacity = mainOpacity * _properties.opacity();
float fillOpacity = mainOpacity * _properties.fillOpacity();
const glm::dmat4 globeModelTransform = _globe.modelTransform();
const glm::dmat4 modelViewTransform =
renderData.camera.combinedViewMatrix() * globeModelTransform;
const glm::mat3 normalTransform = glm::mat3(
glm::transpose(glm::inverse(modelViewTransform))
);
const glm::dmat4 projectionTransform = renderData.camera.projectionMatrix();
#ifndef __APPLE__
glLineWidth(_properties.lineWidth() * extraRenderData.lineWidthScale);
#else
glLineWidth(1.f);
#endif
for (const RenderFeature& r : _renderFeatures) {
if (r.isExtrusionFeature && !_properties.extrude()) {
continue;
}
bool shouldRenderTwice = r.type == RenderType::Polygon &&
fillOpacity < 1.f && _properties.extrude();
if (pass > 0 && !shouldRenderTwice) {
continue;
}
ghoul::opengl::ProgramObject* shader = (r.type == RenderType::Points) ?
_pointsProgram : _linesAndPolygonsProgram;
shader->activate();
shader->setUniform("modelTransform", globeModelTransform);
shader->setUniform("viewTransform", renderData.camera.combinedViewMatrix());
shader->setUniform("projectionTransform", projectionTransform);
shader->setUniform("heightOffset", _offsets.z);
shader->setUniform("useHeightMapData", useHeightMap());
if (shader == _linesAndPolygonsProgram) {
const rendering::helper::LightSourceRenderData& ls =
extraRenderData.lightSourceData;
shader->setUniform("normalTransform", normalTransform);
shader->setUniform("nLightSources", ls.nLightSources);
shader->setUniform("lightIntensities", ls.intensitiesBuffer);
shader->setUniform("lightDirectionsViewSpace", ls.directionsViewSpaceBuffer);
}
glBindVertexArray(r.vaoId);
switch (r.type) {
case RenderType::Lines:
shader->setUniform(
"opacity",
r.isExtrusionFeature ? fillOpacity : opacity
);
renderLines(r);
break;
case RenderType::Points: {
shader->setUniform("opacity", opacity);
float scale = extraRenderData.pointSizeScale;
renderPoints(r, renderData, extraRenderData.pointRenderMode, scale);
break;
}
case RenderType::Polygon: {
shader->setUniform("opacity", fillOpacity);
renderPolygons(r, shouldRenderTwice, pass);
break;
}
default:
throw ghoul::MissingCaseException();
break;
}
shader->deactivate();
}
glBindVertexArray(0);
// Reset when we're done rendering all the polygon features
global::renderEngine->openglStateCache().resetPolygonAndClippingState();
}
void GlobeGeometryFeature::renderPoints(const RenderFeature& feature,
const RenderData& renderData,
const PointRenderMode& renderMode,
float sizeScale) const
{
ghoul_assert(feature.type == RenderType::Points, "Trying to render faulty geometry");
_pointsProgram->setUniform("color", _properties.color());
float bs = static_cast<float>(_globe.boundingSphere());
float size = 0.001f * sizeScale * _properties.pointSize() * bs;
_pointsProgram->setUniform("pointSize", size);
_pointsProgram->setUniform("renderMode", static_cast<int>(renderMode));
using TextureAnchor = GeoJsonProperties::PointTextureAnchor;
_pointsProgram->setUniform(
"useBottomAnchorPoint",
_properties.pointTextureAnchor() != TextureAnchor::Center
);
// Points are rendered as billboards
glm::dvec3 cameraViewDirectionWorld = -renderData.camera.viewDirectionWorldSpace();
glm::dvec3 cameraUpDirectionWorld = renderData.camera.lookUpVectorWorldSpace();
glm::dvec3 orthoRight = glm::normalize(
glm::cross(cameraUpDirectionWorld, cameraViewDirectionWorld)
);
if (orthoRight == glm::dvec3(0.0)) {
// For some reason, the up vector and camera view vecter were the same. Use a
// slightly different vector
glm::dvec3 otherVector = glm::vec3(
cameraUpDirectionWorld.y,
cameraUpDirectionWorld.x,
cameraUpDirectionWorld.z
);
orthoRight = glm::normalize(glm::cross(otherVector, cameraViewDirectionWorld));
}
glm::dvec3 orthoUp = glm::normalize(glm::cross(cameraViewDirectionWorld, orthoRight));
_pointsProgram->setUniform("cameraUp", glm::vec3(orthoUp));
_pointsProgram->setUniform("cameraRight", glm::vec3(orthoRight));
glm::dvec3 cameraPositionWorld = renderData.camera.positionVec3();
_pointsProgram->setUniform("cameraPosition", cameraPositionWorld);
_pointsProgram->setUniform("cameraLookUp", glm::vec3(cameraUpDirectionWorld));
if (_pointTexture && _hasTexture) {
ghoul::opengl::TextureUnit unit;
unit.activate();
_pointTexture->bind();
_pointsProgram->setUniform("pointTexture", unit);
_pointsProgram->setUniform("hasTexture", true);
float widthHeightRatio = static_cast<float>(_pointTexture->texture()->width()) /
static_cast<float>(_pointTexture->texture()->height());
_pointsProgram->setUniform("textureWidthFactor", widthHeightRatio);
}
else {
glBindTexture(GL_TEXTURE_2D, 0);
_pointsProgram->setUniform("hasTexture", false);
}
glEnable(GL_PROGRAM_POINT_SIZE);
glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(feature.nVertices));
glDisable(GL_PROGRAM_POINT_SIZE);
}
void GlobeGeometryFeature::renderLines(const RenderFeature& feature) const {
ghoul_assert(feature.type == RenderType::Lines, "Trying to render faulty geometry");
const glm::vec3 color = feature.isExtrusionFeature ?
_properties.fillColor() : _properties.color();
_linesAndPolygonsProgram->setUniform("color", color);
_linesAndPolygonsProgram->setUniform("performShading", false);
glEnable(GL_LINE_SMOOTH);
glDrawArrays(GL_LINE_STRIP, 0, static_cast<GLsizei>(feature.nVertices));
glDisable(GL_LINE_SMOOTH);
}
void GlobeGeometryFeature::renderPolygons(const RenderFeature& feature,
bool shouldRenderTwice, int renderPass) const
{
ghoul_assert(renderPass == 0 || renderPass == 1, "Invalid render pass");
ghoul_assert(
feature.type == RenderType::Polygon,
"Trying to render faulty geometry"
);
_linesAndPolygonsProgram->setUniform("color", _properties.fillColor());
_linesAndPolygonsProgram->setUniform("performShading", _properties.performShading());
if (shouldRenderTwice) {
glEnable(GL_CULL_FACE);
// First draw back faces, then front faces
glCullFace(renderPass == 0 ? GL_FRONT : GL_BACK);
}
else {
glDisable(GL_CULL_FACE);
}
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(feature.nVertices));
}
bool GlobeGeometryFeature::shouldUpdateDueToHeightMapChange() const {
if (_properties.altitudeMode() == GeoJsonProperties::AltitudeMode::RelativeToGround) {
// Cap the update to a given time interval
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
if (now - _lastHeightUpdateTime < HeightUpdateInterval) {
return false;
}
// TODO: Change computation so that we return true immediately if even one height value is different
// Check if last height values for the control positions have changed
std::vector<double> newHeights = getCurrentReferencePointsHeights();
bool isSame = std::equal(
_lastControlHeights.begin(),
_lastControlHeights.end(),
newHeights.begin(),
newHeights.end(),
[](double a, double b) {
return std::abs(a - b) < std::numeric_limits<double>::epsilon();
}
);
if (!isSame) {
return true;
}
}
return false;
}
void GlobeGeometryFeature::update(bool dataIsDirty, bool preventHeightUpdates) {
if (!preventHeightUpdates && shouldUpdateDueToHeightMapChange()) {
updateHeightsFromHeightMap();
}
else if (dataIsDirty) {
updateGeometry();
}
if (_pointTexture) {
_pointTexture->update();
}
}
void GlobeGeometryFeature::updateGeometry() {
// Update vertex data and compute model coordinates based on globe
_renderFeatures.clear();
if (_type == GeometryType::Point) {
createPointGeometry();
}
else {
std::vector<std::vector<glm::vec3>> edgeVertices = createLineGeometry();
createExtrudedGeometry(edgeVertices);
createPolygonGeometry();
}
// Compute new heights - to see if height map changed
_lastControlHeights = getCurrentReferencePointsHeights();
}
void GlobeGeometryFeature::updateHeightsFromHeightMap() {
// @TODO: do the updating piece by piece, not all in one frame
for (RenderFeature& f : _renderFeatures) {
f.heights = geometryhelper::heightMapHeightsFromGeodetic2List(
_globe,
f.vertices
);
bufferDynamicHeightData(f);
}
_lastHeightUpdateTime = std::chrono::system_clock::now();
}
std::vector<std::vector<glm::vec3>> GlobeGeometryFeature::createLineGeometry() {
std::vector<std::vector<glm::vec3>> resultPositions;
resultPositions.reserve(_geoCoordinates.size());
for (int i = 0; i < _geoCoordinates.size(); ++i) {
std::vector<Vertex> vertices;
std::vector<glm::vec3> positions;
vertices.reserve(_geoCoordinates[i].size() * 3); // TODO: this is not correct anymore
positions.reserve(_geoCoordinates[i].size() * 3); // TODO: this is not correct anymore
glm::dvec3 lastPos = glm::dvec3(0.0);
double lastHeightValue = 0.0;
bool isFirst = true;
for (const Geodetic3& geodetic : _geoCoordinates[i]) {
glm::dvec3 v = geometryhelper::computeOffsetedModelCoordinate(
geodetic,
_globe,
_offsets.x,
_offsets.y
);
auto addLinePos = [&vertices, &positions](glm::vec3 pos) {
vertices.push_back({ pos.x, pos.y, pos.z, 0.f, 0.f, 0.f });
positions.push_back(pos);
};
if (isFirst) {
lastPos = v;
lastHeightValue = geodetic.height;
isFirst = false;
addLinePos(glm::vec3(v));
continue;
}
float length = static_cast<float>(glm::distance(lastPos, v));
if (_properties.tessellationEnabled()) {
// Tessellate.
// But first, determine the step size for the tessellation (larger
// features will not be tesselated)
float stepSize = tessellationStepSize();
std::vector<geometryhelper::PosHeightPair> subdividedPositions =
geometryhelper::subdivideLine(
lastPos,
v,
lastHeightValue,
geodetic.height,
stepSize
);
// Don't add the first position. Has been added as last in previous step
for (int si = 1; si < subdividedPositions.size(); ++si) {
const geometryhelper::PosHeightPair& pair = subdividedPositions[si];
addLinePos(glm::vec3(pair.position));
}
}
else {
// Just add the line point
addLinePos(glm::vec3(v));
}
lastPos = v;
lastHeightValue = geodetic.height;
}
vertices.shrink_to_fit();
RenderFeature feature;
feature.nVertices = vertices.size();
feature.type = RenderType::Lines;
initializeRenderFeature(feature, vertices);
_renderFeatures.push_back(std::move(feature));
positions.shrink_to_fit();
resultPositions.push_back(std::move(positions));
}
resultPositions.shrink_to_fit();
return resultPositions;
}
void GlobeGeometryFeature::createPointGeometry() {
if (_type != GeometryType::Point) {
return;
}
for (size_t i = 0; i < _geoCoordinates.size(); ++i) {
std::vector<Vertex> vertices;
vertices.reserve(_geoCoordinates[i].size());
std::vector<Vertex> extrudedLineVertices;
extrudedLineVertices.reserve(2 * _geoCoordinates[i].size());
for (const Geodetic3& geodetic : _geoCoordinates[i]) {
glm::dvec3 v = geometryhelper::computeOffsetedModelCoordinate(
geodetic,
_globe,
_offsets.x,
_offsets.y
);
glm::vec3 vf = static_cast<glm::vec3>(v);
// Normal is the out direction
glm::vec3 normal = glm::normalize(vf);
vertices.push_back({ vf.x, vf.y, vf.z, normal.x, normal.y, normal.z });
// Lines from center of the globe out to the point
extrudedLineVertices.push_back({ 0.f, 0.f, 0.f, 0.f, 0.f, 0.f });
extrudedLineVertices.push_back({ vf.x, vf.y, vf.z, 0.f, 0.f, 0.f });
}
vertices.shrink_to_fit();
extrudedLineVertices.shrink_to_fit();
RenderFeature feature;
feature.nVertices = vertices.size();
feature.type = RenderType::Points;
initializeRenderFeature(feature, vertices);
_renderFeatures.push_back(std::move(feature));
// Create extrusion feature
RenderFeature extrudeFeature;
extrudeFeature.nVertices = extrudedLineVertices.size();
extrudeFeature.type = RenderType::Lines;
extrudeFeature.isExtrusionFeature = true;
initializeRenderFeature(extrudeFeature, extrudedLineVertices);
_renderFeatures.push_back(std::move(extrudeFeature));
}
}
void GlobeGeometryFeature::createExtrudedGeometry(
const std::vector<std::vector<glm::vec3>>& edgeVertices)
{
if (edgeVertices.empty()) {
return;
}
std::vector<Vertex> vertices =
geometryhelper::createExtrudedGeometryVertices(edgeVertices);
RenderFeature feature;
feature.type = RenderType::Polygon;
feature.nVertices = vertices.size();
feature.isExtrusionFeature = true;
initializeRenderFeature(feature, vertices);
_renderFeatures.push_back(std::move(feature));
}
void GlobeGeometryFeature::createPolygonGeometry() {
if (_triangleCoordinates.empty()) {
return;
}
std::vector<Vertex> polyVertices;
// Create polygon vertices from the triangle coordinates
int triIndex = 0;
std::array<glm::vec3, 3> triPositions;
std::array<double, 3> triHeights;
for (const Geodetic3& geodetic : _triangleCoordinates) {
const glm::vec3 vert = geometryhelper::computeOffsetedModelCoordinate(
geodetic,
_globe,
_offsets.x,
_offsets.y
);
triPositions[triIndex] = vert;
triHeights[triIndex] = geodetic.height;
triIndex++;
// Once we have a triangle, start subdividing
if (triIndex == 3) {
triIndex = 0;
const glm::vec3 v0 = triPositions[0];
const glm::vec3 v1 = triPositions[1];
const glm::vec3 v2 = triPositions[2];
double h0 = triHeights[0];
double h1 = triHeights[1];
double h2 = triHeights[2];
if (_properties.tessellationEnabled()) {
// First determine the step size for the tessellation (larger features
// will not be tesselated)
float stepSize = tessellationStepSize();
std::vector<Vertex> verts = geometryhelper::subdivideTriangle(
v0, v1, v2,
h0, h1, h2,
stepSize,
_globe
);
polyVertices.insert(polyVertices.end(), verts.begin(), verts.end());
}
else {
// Just add a triangle consisting of the three vertices
const glm::vec3 n = -glm::normalize(glm::cross(v1 - v0, v2 - v0));
polyVertices.push_back({ v0.x, v0.y, v0.z, n.x, n.y, n.z });
polyVertices.push_back({ v1.x, v1.y, v1.z, n.x, n.y, n.z });
polyVertices.push_back({ v2.x, v2.y, v2.z, n.x, n.y, n.z });
}
}
}
RenderFeature triFeature;
triFeature.type = RenderType::Polygon;
triFeature.nVertices = polyVertices.size();
initializeRenderFeature(triFeature, polyVertices);
_renderFeatures.push_back(std::move(triFeature));
}
void GlobeGeometryFeature::initializeRenderFeature(RenderFeature& feature,
const std::vector<Vertex>& vertices)
{
// Get height map heights
feature.vertices = geometryhelper::geodetic2FromVertexList(_globe, vertices);
feature.heights = geometryhelper::heightMapHeightsFromGeodetic2List(
_globe,
feature.vertices
);
// Generate buffers and buffer data
feature.initializeBuffers();
bufferVertexData(feature, vertices);
}
float GlobeGeometryFeature::tessellationStepSize() const {
float distance = _properties.tessellationDistance();
bool shouldDivideDistance = _properties.useTessellationLevel() &&
_properties.tessellationLevel() > 0;
if (shouldDivideDistance) {
distance /= static_cast<float>(_properties.tessellationLevel());
}
return distance;
}
std::vector<double> GlobeGeometryFeature::getCurrentReferencePointsHeights() const {
std::vector<double> newHeights;
newHeights.reserve(_heightUpdateReferencePoints.size());
for (const Geodetic3& geo : _heightUpdateReferencePoints) {
const glm::dvec3 p = geometryhelper::computeOffsetedModelCoordinate(
geo,
_globe,
_offsets.x,
_offsets.y
);
const SurfacePositionHandle handle = _globe.calculateSurfacePositionHandle(p);
newHeights.push_back(handle.heightToSurface);
}
return newHeights;
}
void GlobeGeometryFeature::bufferVertexData(const RenderFeature& feature,
const std::vector<Vertex>& vertexData)
{
ghoul_assert(_pointsProgram, "Shader program must be initialized");
ghoul_assert(_linesAndPolygonsProgram, "Shader program must be initialized");
ghoul::opengl::ProgramObject* program = _linesAndPolygonsProgram;
if (feature.type == RenderType::Points) {
program = _pointsProgram;
}
// Reserve space for both vertex and dynamic height information
auto fullBufferSize = vertexData.size() * (sizeof(Vertex) + sizeof(float));
glBindVertexArray(feature.vaoId);
glBindBuffer(GL_ARRAY_BUFFER, feature.vboId);
glBufferData(
GL_ARRAY_BUFFER,
fullBufferSize,
nullptr,
GL_STATIC_DRAW
);
glBufferSubData(
GL_ARRAY_BUFFER,
0, // offset
vertexData.size() * sizeof(Vertex), // size
vertexData.data()
);
GLint positionAttrib = program->attributeLocation("in_position");
glEnableVertexAttribArray(positionAttrib);
glVertexAttribPointer(
positionAttrib,
3,
GL_FLOAT,
GL_FALSE,
sizeof(Vertex),
nullptr
);
GLint normalAttrib = program->attributeLocation("in_normal");
glEnableVertexAttribArray(normalAttrib);
glVertexAttribPointer(
normalAttrib,
3,
GL_FLOAT,
GL_FALSE,
sizeof(Vertex),
reinterpret_cast<void*>(3 * sizeof(float))
);
// Put height data after all vertex data in buffer
unsigned long long endOfVertexData = vertexData.size() * sizeof(Vertex);
glBindVertexArray(feature.vaoId);
glBindBuffer(GL_ARRAY_BUFFER, feature.vboId);
glBufferSubData(
GL_ARRAY_BUFFER,
endOfVertexData, // offset
feature.heights.size() * sizeof(float), // size of all height values
feature.heights.data()
);
GLint heightAttrib = program->attributeLocation("in_height");
glEnableVertexAttribArray(heightAttrib);
glVertexAttribPointer(
heightAttrib,
1,
GL_FLOAT,
GL_FALSE,
1 * sizeof(float), // stride (one height value)
reinterpret_cast<void*>(endOfVertexData) // start position
);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
};
void GlobeGeometryFeature::bufferDynamicHeightData(const RenderFeature& feature) {
ghoul_assert(_pointsProgram, "Shader program must be initialized");
ghoul_assert(_linesAndPolygonsProgram, "Shader program must be initialized");
ghoul::opengl::ProgramObject* program = _linesAndPolygonsProgram;
if (feature.type == RenderType::Points) {
program = _pointsProgram;
}
// Just update the height data
glBindVertexArray(feature.vaoId);
glBindBuffer(GL_ARRAY_BUFFER, feature.vboId);
glBufferSubData(
GL_ARRAY_BUFFER,
feature.nVertices * sizeof(Vertex), // offset
feature.heights.size() * sizeof(float), // size
feature.heights.data()
);
GLint heightAttrib = program->attributeLocation("in_height");
glEnableVertexAttribArray(heightAttrib);
glVertexAttribPointer(
heightAttrib,
1,
GL_FLOAT,
GL_FALSE,
1 * sizeof(float), // stride
reinterpret_cast<void*>(feature.nVertices * sizeof(Vertex)) // start position
);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
};
} // namespace openspace::globebrowsing

View File

@@ -0,0 +1,220 @@
/*****************************************************************************************
* *
* 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. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEGEOMETRYFEATURE___H__
#define __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEGEOMETRYFEATURE___H__
#include <openspace/properties/propertyowner.h>
#include <modules/globebrowsing/src/basictypes.h>
#include <modules/globebrowsing/src/geojson/geojsonproperties.h>
#include <openspace/rendering/helper.h>
#include <openspace/rendering/texturecomponent.h>
#include <ghoul/glm.h>
#include <ghoul/opengl/ghoul_gl.h>
#include <chrono>
#include <vector>
namespace openspace::documentation { struct Documentation; }
namespace rendering::helper {
struct LightSourceRenderData;
struct VertexXYZNormal;
} // namespace rendering::helper
namespace geos::geom { class Geometry; }
namespace openspace::globebrowsing {
class RenderableGlobe;
/**
* This class is responsible for rendering the geomoetry features of globes,
* created e.g. from GeoJson files
*/
class GlobeGeometryFeature {
public:
GlobeGeometryFeature(const RenderableGlobe& globe,
GeoJsonProperties& defaultProperties,
GeoJsonOverrideProperties& overrideProperties);
using Vertex = rendering::helper::VertexXYZNormal;
// TODO: Use instead of numbers
//enum class RenderPass {
// GeometryOnly,
// GeometryAndShading
//};
enum class PointRenderMode {
AlignToCameraDir = 0,
AlignToCameraPos,
AlignToGlobeNormal,
AlignToGlobeSurface
};
enum class GeometryType {
LineString = 0,
Point,
Polygon,
Error
};
enum class RenderType {
Lines = 0,
Points,
Polygon,
Uninitialized
};
// Each geometry feature might translate into several render features
struct RenderFeature {
void initializeBuffers();
RenderType type = RenderType::Uninitialized;
GLuint vaoId = 0;
GLuint vboId = 0;
size_t nVertices = 0;
bool isExtrusionFeature = false;
// Store the geodetic lat long coordinates of each vertex, so we can quickly recompute
// the height values for these points
std::vector<Geodetic2> vertices;
// Keep the heights around
std::vector<float> heights;
};
// Some extra data that we need for doing the rendering
struct ExtraRenderData {
float pointSizeScale;
float lineWidthScale;
PointRenderMode& pointRenderMode;
rendering::helper::LightSourceRenderData& lightSourceData;
};
std::string key() const;
void setOffsets(const glm::vec3& offsets);
void initializeGL(ghoul::opengl::ProgramObject* pointsProgram,
ghoul::opengl::ProgramObject* linesAndPolygonsProgram);
void deinitializeGL();
bool isReady() const;
bool isPoints() const;
bool useHeightMap() const;
void updateTexture(bool isInitializeStep = false);
void createFromSingleGeosGeometry(const geos::geom::Geometry* geo, int index,
bool ignoreHeights);
// 2 pass rendering to get correct culling for polygons
void render(const RenderData& renderData, int pass, float mainOpacity,
const ExtraRenderData& extraRenderData);
bool shouldUpdateDueToHeightMapChange() const;
void update(bool dataIsDirty, bool preventHeightUpdates);
void updateGeometry();
void updateHeightsFromHeightMap();
private:
void renderPoints(const RenderFeature& feature, const RenderData& renderData,
const PointRenderMode& renderMode, float sizeScale) const;
void renderLines(const RenderFeature& feature) const;
void renderPolygons(const RenderFeature& feature, bool shouldRenderTwice,
int renderPass) const;
/**
* Create the vertex information for any line parts of the feature.
* Returns the resulting vertex positions, so we can use them for extrusion
*/
std::vector<std::vector<glm::vec3>> createLineGeometry();
/**
* Create the vertex information for any point parts of the feature. Also creates
* the features for extruded lines for the points
*/
void createPointGeometry();
/**
* Create the triangle geometry for the extruded edges of lines/polygons
*/
void createExtrudedGeometry(const std::vector<std::vector<glm::vec3>>& edgeVertices);
/**
* Create the triangle geometry for the polygon part of the feature (the area
* contained by the shape)
*/
void createPolygonGeometry();
void initializeRenderFeature(RenderFeature& feature,
const std::vector<Vertex>& vertices);
/// Get the distance that shall be used for tesselation, based on the properties
float tessellationStepSize() const;
/// Compute the heights to the surface at the reference points
std::vector<double> getCurrentReferencePointsHeights() const;
/// Buffer the static data for the vertices
void bufferVertexData(const RenderFeature& feature,
const std::vector<Vertex>& vertexData);
/// Buffer the dynamic height data for the vertices, based on the height map
void bufferDynamicHeightData(const RenderFeature& feature);
GeometryType _type = GeometryType::Error;
const RenderableGlobe& _globe;
// Coordinates for geometry. For polygons, the first is always the outer ring
// and any following are the inner rings (holes)
std::vector<std::vector<Geodetic3>> _geoCoordinates;
// Coordinates for any triangles representing the geometry (only relevant for polygons)
std::vector<Geodetic3> _triangleCoordinates;
std::vector<RenderFeature> _renderFeatures;
glm::vec3 _offsets = glm::vec3(0.f); // lat, long, distance (meters). Passed from parent on property change
std::string _key;
const PropertySet _properties;
std::vector<Geodetic3> _heightUpdateReferencePoints;
std::vector<double> _lastControlHeights;
std::chrono::system_clock::time_point _lastHeightUpdateTime;
bool _hasTexture = false;
std::unique_ptr<TextureComponent> _pointTexture;
ghoul::opengl::ProgramObject* _linesAndPolygonsProgram = nullptr;
ghoul::opengl::ProgramObject* _pointsProgram = nullptr;
};
} // namespace openspace::globebrowsing
#endif // __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEGEOMETRYFEATURE___H__

View File

@@ -0,0 +1,360 @@
/*****************************************************************************************
* *
* 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 <modules/globebrowsing/src/geojson/globegeometryhelper.h>
#include <modules/globebrowsing/src/renderableglobe.h>
#include <openspace/rendering/helper.h>
#include <openspace/util/updatestructures.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/GeometryFactory.h>
#include <geos/triangulate/DelaunayTriangulationBuilder.h>
#include <geos/triangulate/quadedge/QuadEdgeSubdivision.h>
namespace openspace::globebrowsing::geometryhelper {
Geodetic3 toGeodetic(const geos::geom::Coordinate& c) {
Geodetic3 gd;
gd.geodetic2.lon = glm::radians(c.x);
gd.geodetic2.lat = glm::radians(c.y);
gd.height = std::isnan(c.z) ? 0.0 : c.z;
return gd;
}
geos::geom::Coordinate toGeosCoord(const Geodetic3& gd) {
geos::geom::Coordinate c;
c.x = glm::degrees(gd.geodetic2.lon);
c.y = glm::degrees(gd.geodetic2.lat);
c.z = gd.height;
return c;
}
std::vector<Geodetic3>
coordsToGeodetic(const std::vector<geos::geom::Coordinate>& coords)
{
std::vector<Geodetic3> res;
res.reserve(coords.size());
for (const geos::geom::Coordinate& c : coords) {
res.push_back(toGeodetic(c));
}
return res;
}
std::vector<Geodetic3> geometryCoordsAsGeoVector(const geos::geom::Geometry* geometry) {
std::vector<geos::geom::Coordinate> coords;
geometry->getCoordinates()->toVector(coords);
return geometryhelper::coordsToGeodetic(coords);
}
std::vector<Geodetic2> geodetic2FromVertexList(const RenderableGlobe& globe,
const std::vector<rendering::helper::VertexXYZNormal>& verts)
{
std::vector<Geodetic2> res;
res.reserve(verts.size());
for (const rendering::helper::VertexXYZNormal& v : verts) {
glm::dvec3 cartesian = glm::dvec3(v.xyz[0], v.xyz[1], v.xyz[2]);
res.push_back(globe.ellipsoid().cartesianToGeodetic2(cartesian));
}
return res;
}
std::vector<float> heightMapHeightsFromGeodetic2List(const RenderableGlobe& globe,
const std::vector<Geodetic2>& list)
{
std::vector<float> res;
res.reserve(list.size());
for (const Geodetic2& geo : list) {
float h = static_cast<float>(getHeightToReferenceSurface(geo, globe));
res.push_back(h);
}
return res;
}
std::vector<rendering::helper::VertexXYZNormal>
createExtrudedGeometryVertices(const std::vector<std::vector<glm::vec3>>& edgeVertices)
{
std::vector<rendering::helper::VertexXYZNormal> vertices;
size_t nVerts = 0;
for (const std::vector<glm::vec3>& edge : edgeVertices) {
nVerts += edge.size();
}
vertices.reserve(nVerts * 3);
// Extrude polygon
for (size_t nBound = 0; nBound < edgeVertices.size(); ++nBound) {
const std::vector<glm::vec3>& boundary = edgeVertices[nBound];
for (int i = 1; i < boundary.size(); ++i) {
glm::vec3 v0 = boundary[i - 1];
glm::vec3 v1 = boundary[i ];
// Vertices close to globe (Based on origin which is the zero point here)
// For now, use center of globe (TODO: allow setting the height)
glm::vec3 vOrigin = glm::vec3(0.f);
// Outer boundary is the first one
if (nBound == 0) {
glm::vec3 n = glm::vec3(glm::normalize(glm::cross(v1 - vOrigin, v0 - vOrigin)));
vertices.push_back({ vOrigin.x, vOrigin.y, vOrigin.z, n.x, n.y, n.z });
vertices.push_back({ v1.x, v1.y, v1.z, n.x, n.y, n.z });
vertices.push_back({ v0.x, v0.y, v0.z, n.x, n.y, n.z });
}
// Inner boundary
else {
// Flipped winding order and normal for inner rings
glm::vec3 n = glm::normalize(glm::cross(v0 - vOrigin, v1 - vOrigin));
vertices.push_back({ vOrigin.x, vOrigin.y, vOrigin.z, n.x, n.y, n.z });
vertices.push_back({ v0.x, v0.y, v0.z, n.x, n.y, n.z });
vertices.push_back({ v1.x, v1.y, v1.z, n.x, n.y, n.z });
}
// TODO: Fix faulty triangle directions and draw triangles with correct shading on
// both sides of the mesh
}
}
vertices.shrink_to_fit();
return vertices;
// TODO: extrude lines as a box shape
}
double getHeightToReferenceSurface(const Geodetic2& geo, const RenderableGlobe& globe) {
// Compute model space coordinate, and potentially account for height map
glm::dvec3 posModelSpaceZeroHeight =
globe.ellipsoid().cartesianSurfacePosition(geo);
const SurfacePositionHandle posHandle =
globe.calculateSurfacePositionHandle(posModelSpaceZeroHeight);
return posHandle.heightToSurface;
}
glm::dvec3 computeOffsetedModelCoordinate(const Geodetic3& geo,
const RenderableGlobe& globe,
float latOffset, float lonOffset)
{
// Account for lat long offset
double offsetLatRadians = glm::radians(latOffset);
double offsetLonRadians = glm::radians(lonOffset);
Geodetic3 adjusted = geo;
adjusted.geodetic2.lat += offsetLatRadians;
adjusted.geodetic2.lon += offsetLonRadians;
return globe.ellipsoid().cartesianPosition(adjusted);
}
std::vector<PosHeightPair> subdivideLine(const glm::dvec3& v0, const glm::dvec3& v1,
double h0, double h1, double maxDistance)
{
double edgeLength = glm::distance(v1, v0);
int nSegments = static_cast<int>(std::ceil(edgeLength / maxDistance));
std::vector<PosHeightPair> positions;
positions.reserve(nSegments + 1);
// If step distance is too big, just add first position
if (nSegments == 0) {
positions.push_back({ glm::vec3(v0), h0 });
}
for (int seg = 0; seg < nSegments; ++seg) {
double t = static_cast<double>(seg) / static_cast<double>(nSegments);
// Interpolate both position and height value
glm::dvec3 newV = glm::mix(v0, v1, t);
double newHeight = glm::mix(h0, h1, t);
double heightDiff = newHeight - h0;
// Compute position along arc between v0 and v1, with adjusted height value
glm::vec3 newVf = static_cast<glm::vec3>(
(glm::length(v0) + heightDiff) * glm::normalize(newV)
);
positions.push_back({ newVf, newHeight });
}
// Add final position
positions.push_back({ static_cast<glm::vec3>(v1), h1 });
positions.shrink_to_fit();
return positions;
}
std::vector<rendering::helper::VertexXYZNormal>
subdivideTriangle(const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2,
double h0, double h1, double h2, double maxDistance,
const RenderableGlobe& globe)
{
std::vector<rendering::helper::VertexXYZNormal> vertices;
// Subdivide edges
std::vector<PosHeightPair> edge01 = geometryhelper::subdivideLine(
v0, v1,
h0, h1,
maxDistance
);
std::vector<PosHeightPair> edge02 = geometryhelper::subdivideLine(
v0, v2,
h0, h2,
maxDistance
);
std::vector<PosHeightPair> edge12 = geometryhelper::subdivideLine(
v1, v2,
h1, h2,
maxDistance
);
size_t nSteps01 = edge01.size();
size_t nSteps02 = edge02.size();
size_t nSteps12 = edge12.size();
size_t maxSteps = std::max(std::max(nSteps01, nSteps02), nSteps12);
vertices.reserve(maxSteps * maxSteps);
// Add points inside the triangle
std::vector<Coordinate> pointCoords;
pointCoords.reserve(3 * maxSteps + 1);
const float lengthEdge01 = glm::length(v1 - v0);
const float lengthEdge02 = glm::length(v2 - v0);
for (size_t i = 1; i < nSteps01; ++i) {
for (size_t j = 1; j < nSteps02; ++j) {
glm::vec3 comp01 = edge01[i].position - v0;
glm::vec3 comp02 = edge02[j].position - v0;
double hComp01 = edge01[i].height - h0;
double hComp02 = edge02[j].height - h0;
float w1 = glm::length(comp01) / lengthEdge01;
float w2 = glm::length(comp02) / lengthEdge02;
if (w1 + w2 > 1.f - std::numeric_limits<float>::epsilon()) {
continue; // Sum larger than 1.0 => Outside of triangle
}
glm::vec3 pos = v0 + comp01 + comp02;
double height = h0 + hComp01 + hComp02;
Geodetic2 geo2 = globe.ellipsoid().cartesianToGeodetic2(pos);
Geodetic3 geo3 = { geo2, height };
pointCoords.push_back(geometryhelper::toGeosCoord(geo3));
}
}
// Add egde positions
for (size_t i = 0; i < maxSteps; ++i) {
if (i < edge01.size() - 1) {
Geodetic2 geo2 = globe.ellipsoid().cartesianToGeodetic2(edge01[i].position);
Geodetic3 geo3 = { geo2, edge01[i].height };
pointCoords.push_back(geometryhelper::toGeosCoord(geo3));
}
if (i < edge02.size() - 1) {
Geodetic2 geo2 = globe.ellipsoid().cartesianToGeodetic2(edge02[i].position);
Geodetic3 geo3 = { geo2, edge02[i].height };
pointCoords.push_back(geometryhelper::toGeosCoord(geo3));
}
if (i < edge12.size() - 1) {
Geodetic2 geo2 = globe.ellipsoid().cartesianToGeodetic2(edge12[i].position);
Geodetic3 geo3 = { geo2, edge12[i].height };
pointCoords.push_back(geometryhelper::toGeosCoord(geo3));
}
}
// Also add the final position (not part of the subdivide step above)
Geodetic2 geo2 = globe.ellipsoid().cartesianToGeodetic2(v2);
glm::dvec3 centerToEllipsoidSurface = globe.ellipsoid().geodeticSurfaceProjection(v2);
double height = glm::length(glm::dvec3(v2) - centerToEllipsoidSurface);
Geodetic3 geo3 = { geo2, height };
pointCoords.push_back(geometryhelper::toGeosCoord(geo3));
pointCoords.shrink_to_fit();
using namespace geos::geom;
GeometryFactory::Ptr geometryFactory = GeometryFactory::create();
std::unique_ptr<MultiPoint> points = geometryFactory->createMultiPoint(pointCoords);
// Create triangulation of points
geos::triangulate::DelaunayTriangulationBuilder builder;
builder.setSites(*points->getCoordinates());
// Returns a list of triangles, as geos polygons
GeometryCollection* triangleGeoms = builder.getTriangles(*geometryFactory).release();
std::vector<Coordinate> triCoords;
triangleGeoms->getCoordinates()->toVector(triCoords);
vertices.reserve(vertices.size() + triCoords.size() + 1);
int count = 0;
for (const Coordinate& coord : triCoords) {
count++;
if (count == 4) {
// Skip every 4th coord, as polygons have one extra coord per triangle.
// Also, reset the counting at this point.
count = 0;
continue;
}
Geodetic3 geodetic = geometryhelper::toGeodetic(coord);
// Note that offset should already have been applied to the coordinates. Use
// zero offset => just get model coordinate
glm::vec3 v =
geometryhelper::computeOffsetedModelCoordinate(geodetic, globe, 0.f, 0.f);
vertices.push_back({ v.x, v.y, v.z, 0.f, 0.f, 0.f });
// Every third set of coordinates is a triangle => update normal of previous
// triangle vertices
if (count == 3) {
// Find previous vertices
size_t lastIndex = vertices.size() - 1;
rendering::helper::VertexXYZNormal& vert0 = vertices[lastIndex - 2];
rendering::helper::VertexXYZNormal& vert1 = vertices[lastIndex - 1];
rendering::helper::VertexXYZNormal& vert2 = vertices[lastIndex];
const glm::vec3 v0_pos = glm::vec3(vert0.xyz[0], vert0.xyz[1], vert0.xyz[2]);
const glm::vec3 v1_pos = glm::vec3(vert1.xyz[0], vert1.xyz[1], vert1.xyz[2]);
const glm::vec3 n = -glm::normalize(glm::cross(v1_pos - v0_pos, v - v0_pos));
vert0.normal[0] = n.x;
vert0.normal[1] = n.y;
vert0.normal[2] = n.z;
vert1.normal[0] = n.x;
vert1.normal[1] = n.y;
vert1.normal[2] = n.z;
vert2.normal[0] = n.x;
vert2.normal[1] = n.y;
vert2.normal[2] = n.z;
}
}
vertices.shrink_to_fit();
return vertices;
}
} // namespace openspace::globebrowsing::geometryhelper

View File

@@ -0,0 +1,109 @@
/*****************************************************************************************
* *
* 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. *
****************************************************************************************/
#ifndef __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEGEOMETRYHELPER___H__
#define __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEGEOMETRYHELPER___H__
#include <glm/glm.hpp>
#include <vector>
namespace openspace::globebrowsing {
struct Geodetic2;
struct Geodetic3;
class RenderableGlobe;
} // namespace openspace::globebrowsing
namespace openspace::rendering::helper {
struct VertexXYZNormal;
} // namespace openspace::rendering::helper
namespace geos::geom {
class Coordinate;
class Geometry;
} // namespace geos::geom
namespace openspace::globebrowsing::geometryhelper {
Geodetic3 toGeodetic(const geos::geom::Coordinate& c);
geos::geom::Coordinate toGeosCoord(const Geodetic3& gd);
std::vector<Geodetic3> coordsToGeodetic(
const std::vector<geos::geom::Coordinate>& coords);
std::vector<Geodetic3> geometryCoordsAsGeoVector(const geos::geom::Geometry* geometry);
std::vector<Geodetic2> geodetic2FromVertexList(const RenderableGlobe& globe,
const std::vector<rendering::helper::VertexXYZNormal>& verts);
std::vector<float> heightMapHeightsFromGeodetic2List(const RenderableGlobe& globe,
const std::vector<Geodetic2>& list);
/**
* Create triangle geometry for the extruded edge, given the provided edge vertices
*/
std::vector<rendering::helper::VertexXYZNormal> createExtrudedGeometryVertices(
const std::vector<std::vector<glm::vec3>>& edgeVertices);
/**
* Get height contribution from reference surface of the globe, based on the height map
*/
double getHeightToReferenceSurface(const Geodetic2& geo, const RenderableGlobe& globe);
/**
* Compute model space cordinate from geodetic coordinate, and account for lat, long
* offsets
*/
glm::dvec3 computeOffsetedModelCoordinate(const Geodetic3& geo,
const RenderableGlobe& globe, float latOffset, float lonOffset);
struct PosHeightPair {
glm::vec3 position;
double height;
};
/**
* Subdivide line between position v0 and v1 so that it fullfils the maxDistance
* criteria. Interpolate the height value from * h0 to h1, as well as add the
* given offset and account for the height map if that should be done.
*
* Returns pairs of position and height values
*/
std::vector<PosHeightPair> subdivideLine(const glm::dvec3& v0, const glm::dvec3& v1,
double h0, double h1, double maxDistance);
/**
* Subdivide triangle consisting of vertex positions v0, v1 and v2, with height values
* h0, h1 and h2 into smaller triangles. maxDistance specifies tha maximum distance
* between two vertices in the subdivided mesh
*/
std::vector<rendering::helper::VertexXYZNormal> subdivideTriangle(
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2,
double h0, double h1, double h2, double maxDistance, const RenderableGlobe& globe);
} // namespace openspace::globebrowsing::geometryhelper
#endif // __OPENSPACE_MODULE_GLOBEBROWSING___GLOBEGEOMETRYHELPER___H__

View File

@@ -677,6 +677,9 @@ RenderableGlobe::RenderableGlobe(const ghoul::Dictionary& dictionary)
_labelsDictionary = p.labels.value_or(_labelsDictionary);
// Init geojson manager
_geoJsonManager.initialize(this);
addPropertySubOwner(_geoJsonManager);
// Components
_hasRings = p.rings.has_value();
@@ -740,6 +743,8 @@ void RenderableGlobe::deinitializeGL() {
_globalRenderer.program = nullptr;
}
_geoJsonManager.deinitializeGL();
_grid.deinitializeGL();
if (_hasRings) {
@@ -856,6 +861,10 @@ void RenderableGlobe::renderSecondary(const RenderData& data, RendererTasks&) {
catch (const ghoul::opengl::TextureUnit::TextureUnitError& e) {
LERROR(fmt::format("Error on drawing globe labels: '{}'", e.message));
}
if (_geoJsonManager.isReady()) {
_geoJsonManager.render(data);
}
}
void RenderableGlobe::update(const UpdateData& data) {
@@ -936,6 +945,8 @@ void RenderableGlobe::update(const UpdateData& data) {
// RenderableGlobe::render() // rendering with the new number of layers but the
// // LayerManager hasn't updated yet :o
_layerManagerDirty = true;
_geoJsonManager.update();
}
bool RenderableGlobe::renderedWithDesiredData() const {
@@ -950,6 +961,14 @@ LayerManager& RenderableGlobe::layerManager() {
return _layerManager;
}
const GeoJsonManager& RenderableGlobe::geoJsonManager() const {
return _geoJsonManager;
}
GeoJsonManager& RenderableGlobe::geoJsonManager() {
return _geoJsonManager;
}
const Ellipsoid& RenderableGlobe::ellipsoid() const {
return _ellipsoid;
}

View File

@@ -29,6 +29,7 @@
#include <modules/globebrowsing/src/ellipsoid.h>
#include <modules/globebrowsing/src/geodeticpatch.h>
#include <modules/globebrowsing/src/geojson/geojsonmanager.h>
#include <modules/globebrowsing/src/globelabelscomponent.h>
#include <modules/globebrowsing/src/gpulayergroup.h>
#include <modules/globebrowsing/src/layermanager.h>
@@ -114,6 +115,9 @@ public:
const Ellipsoid& ellipsoid() const;
const LayerManager& layerManager() const;
LayerManager& layerManager();
const GeoJsonManager& geoJsonManager() const;
GeoJsonManager& geoJsonManager();
const glm::dmat4& modelTransform() const;
static documentation::Documentation Documentation();
@@ -252,6 +256,8 @@ private:
SkirtedGrid _grid;
LayerManager _layerManager;
GeoJsonManager _geoJsonManager;
glm::dmat4 _cachedModelTransform = glm::dmat4(1.0);
glm::dmat4 _cachedInverseModelTransform = glm::dmat4(1.0);

View File

@@ -6049,7 +6049,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
ImGui::EndTooltip();
}
ImGui::SameLine();
HelpMarker("When drawing circle primitives with \"num_segments == 0\" tesselation will be calculated automatically.");
HelpMarker("When drawing circle primitives with \"num_segments == 0\" tessellation will be calculated automatically.");
ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero.
ImGui::PopItemWidth();

View File

@@ -161,7 +161,8 @@ ModuleConfigurations = {
GlobeBrowsing = {
TileCacheSize = 2048, -- for all globes (CPU and GPU memory)
MRFCacheEnabled = false,
MRFCacheLocation = "${BASE}/mrf_cache"
MRFCacheLocation = "${BASE}/mrf_cache",
DefaultGeoPointTexture = "${DATA}/globe_pin.png"
},
Sync = {
SynchronizationRoot = "${SYNC}",

View File

@@ -46,4 +46,6 @@ elseif extension == ".asset" then
openspace.asset.add("]] .. filename .. '");' .. ReloadUIScript
elseif extension == ".osrec" or extension == ".osrectxt" then
return 'openspace.sessionRecording.startPlayback("' .. filename .. '")'
elseif extension == ".geojson" then
return 'openspace.globebrowsing.addGeoJsonFromFile("' .. filename .. '")' .. ReloadUIScript
end

View File

@@ -26,6 +26,7 @@
#include <openspace/engine/globals.h>
#include <openspace/engine/windowdelegate.h>
#include <openspace/scene/lightsource.h>
#include <ghoul/filesystem/filesystem.h>
#include <ghoul/misc/assert.h>
#include <ghoul/misc/profiling.h>
@@ -504,4 +505,26 @@ std::pair<std::vector<Vertex>, std::vector<GLushort>> createSphere(int nSegments
return { vertices, indices };
}
void LightSourceRenderData::updateBasedOnLightSources(const RenderData& renderData,
const std::vector<std::unique_ptr<LightSource>>& sources)
{
unsigned int nEnabledLightSources = 0;
intensitiesBuffer.resize(sources.size());
directionsViewSpaceBuffer.resize(sources.size());
// Get intensities and view space direction for the given light sources,
// given the provided render data information
for (const std::unique_ptr<LightSource>& lightSource : sources) {
if (!lightSource->isEnabled()) {
continue;
}
intensitiesBuffer[nEnabledLightSources] = lightSource->intensity();
directionsViewSpaceBuffer[nEnabledLightSources] =
lightSource->directionViewSpace(renderData);
++nEnabledLightSources;
}
nLightSources = nEnabledLightSources;
}
} // namespace openspace::rendering::helper

View File

@@ -90,16 +90,19 @@ void TextureComponent::loadFromFile(const std::filesystem::path& path) {
if (!path.empty()) {
using namespace ghoul::io;
using namespace ghoul::opengl;
std::filesystem::path absolutePath = absPath(path);
std::unique_ptr<Texture> texture = TextureReader::ref().loadTexture(
absPath(path.string()).string(),
absolutePath.string(),
_nDimensions
);
if (texture) {
LDEBUG(fmt::format("Loaded texture from {}", absPath(path.string())));
LDEBUG(fmt::format("Loaded texture from {}", absolutePath));
_texture = std::move(texture);
_textureFile = std::make_unique<ghoul::filesystem::File>(path);
_textureFile = std::make_unique<ghoul::filesystem::File>(absolutePath);
if (_shouldWatchFile) {
_textureFile->setCallback([this]() { _fileIsDirty = true; });
}