mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-01-05 11:09:37 -06:00
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:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
39
data/assets/examples/geojson/geojson_lines.asset
Normal file
39
data/assets/examples/geojson/geojson_lines.asset
Normal 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"
|
||||
}
|
||||
58
data/assets/examples/geojson/geojson_multiple_polygons.asset
Normal file
58
data/assets/examples/geojson/geojson_multiple_polygons.asset
Normal 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"
|
||||
}
|
||||
40
data/assets/examples/geojson/geojson_points.asset
Normal file
40
data/assets/examples/geojson/geojson_points.asset
Normal 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"
|
||||
}
|
||||
41
data/assets/examples/geojson/geojson_points_outfacing.asset
Normal file
41
data/assets/examples/geojson/geojson_points_outfacing.asset
Normal 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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
41
data/assets/examples/geojson/geojson_roverpath.asset
Normal file
41
data/assets/examples/geojson/geojson_roverpath.asset
Normal 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"
|
||||
}
|
||||
@@ -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
BIN
data/globe_pin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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")
|
||||
|
||||
1
modules/globebrowsing/ext/geos
Submodule
1
modules/globebrowsing/ext/geos
Submodule
Submodule modules/globebrowsing/ext/geos added at fe36f23c2b
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
76
modules/globebrowsing/shaders/geojson_fs.glsl
Normal file
76
modules/globebrowsing/shaders/geojson_fs.glsl
Normal 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;
|
||||
}
|
||||
62
modules/globebrowsing/shaders/geojson_points_fs.glsl
Normal file
62
modules/globebrowsing/shaders/geojson_points_fs.glsl
Normal 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;
|
||||
}
|
||||
158
modules/globebrowsing/shaders/geojson_points_gs.glsl
Normal file
158
modules/globebrowsing/shaders/geojson_points_gs.glsl
Normal 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();
|
||||
}
|
||||
38
modules/globebrowsing/shaders/geojson_points_vs.glsl
Normal file
38
modules/globebrowsing/shaders/geojson_points_vs.glsl
Normal 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;
|
||||
}
|
||||
64
modules/globebrowsing/shaders/geojson_vs.glsl
Normal file
64
modules/globebrowsing/shaders/geojson_vs.glsl
Normal 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;
|
||||
}
|
||||
797
modules/globebrowsing/src/geojson/geojsoncomponent.cpp
Normal file
797
modules/globebrowsing/src/geojson/geojsoncomponent.cpp
Normal 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
|
||||
163
modules/globebrowsing/src/geojson/geojsoncomponent.h
Normal file
163
modules/globebrowsing/src/geojson/geojsoncomponent.h
Normal 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__
|
||||
146
modules/globebrowsing/src/geojson/geojsonmanager.cpp
Normal file
146
modules/globebrowsing/src/geojson/geojsonmanager.cpp
Normal 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
|
||||
74
modules/globebrowsing/src/geojson/geojsonmanager.h
Normal file
74
modules/globebrowsing/src/geojson/geojsonmanager.h
Normal 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__
|
||||
617
modules/globebrowsing/src/geojson/geojsonproperties.cpp
Normal file
617
modules/globebrowsing/src/geojson/geojsonproperties.cpp
Normal 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
|
||||
151
modules/globebrowsing/src/geojson/geojsonproperties.h
Normal file
151
modules/globebrowsing/src/geojson/geojsonproperties.h
Normal 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__
|
||||
914
modules/globebrowsing/src/geojson/globegeometryfeature.cpp
Normal file
914
modules/globebrowsing/src/geojson/globegeometryfeature.cpp
Normal 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
|
||||
220
modules/globebrowsing/src/geojson/globegeometryfeature.h
Normal file
220
modules/globebrowsing/src/geojson/globegeometryfeature.h
Normal 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__
|
||||
360
modules/globebrowsing/src/geojson/globegeometryhelper.cpp
Normal file
360
modules/globebrowsing/src/geojson/globegeometryhelper.cpp
Normal 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
|
||||
109
modules/globebrowsing/src/geojson/globegeometryhelper.h
Normal file
109
modules/globebrowsing/src/geojson/globegeometryhelper.h
Normal 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__
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user