From d4d149f3c092112a7a505e9f3ded036e9568c9c6 Mon Sep 17 00:00:00 2001 From: Nathaniel Warner Date: Mon, 23 Nov 2020 10:35:09 -0800 Subject: [PATCH] Rebase photoscenery patch on next --- simgear/io/sg_binobj.hxx | 4 + simgear/scene/tgdb/SGTexturedTriangleBin.hxx | 9 + simgear/scene/tgdb/SGTileGeometryBin.hxx | 13 + simgear/scene/tgdb/obj.cxx | 57 ++- simgear/scene/tgdb/obj.hxx | 5 + simgear/scene/util/CMakeLists.txt | 2 + simgear/scene/util/OrthophotoManager.cxx | 409 +++++++++++++++++++ simgear/scene/util/OrthophotoManager.hxx | 108 +++++ 8 files changed, 605 insertions(+), 2 deletions(-) create mode 100644 simgear/scene/util/OrthophotoManager.cxx create mode 100644 simgear/scene/util/OrthophotoManager.hxx diff --git a/simgear/io/sg_binobj.hxx b/simgear/io/sg_binobj.hxx index 9a2f4485..64c279a5 100644 --- a/simgear/io/sg_binobj.hxx +++ b/simgear/io/sg_binobj.hxx @@ -150,6 +150,7 @@ private: std::vector colors; // color list std::vector normals; // normal list std::vector texcoords; // texture coordinate list + std::vector overlaycoords; // overlay texture coordinate list std::vector va_flt; // vertex attribute list (floats) std::vector va_int; // vertex attribute list (ints) @@ -226,6 +227,9 @@ public: inline const std::vector& get_texcoords() const { return texcoords; } inline void set_texcoords( const std::vector& t ) { texcoords = t; } + + inline const std::vector& get_overlaycoords() const { return overlaycoords; } + inline void set_overlaycoords( const std::vector& t ) { overlaycoords = t; } // Points API bool add_point( const SGBinObjectPoint& pt ); diff --git a/simgear/scene/tgdb/SGTexturedTriangleBin.hxx b/simgear/scene/tgdb/SGTexturedTriangleBin.hxx index 60ad5286..663933c1 100644 --- a/simgear/scene/tgdb/SGTexturedTriangleBin.hxx +++ b/simgear/scene/tgdb/SGTexturedTriangleBin.hxx @@ -89,10 +89,14 @@ struct SGVertNormTex { } const SGVec2f& GetTexCoord( unsigned idx ) const { return texCoord[idx]; } + void SetOverlayCoord( const SGVec2f& ovc ) { overlayCoord = ovc; } + const SGVec2f& GetOverlayCoord() const { return overlayCoord; } + private: SGVec3f vertex; SGVec3f normal; SGVec2f texCoord[4]; + SGVec2f overlayCoord; unsigned tc_mask; }; @@ -434,6 +438,7 @@ public: osg::ref_ptr normals = new osg::Vec3Array; osg::ref_ptr priTexCoords = new osg::Vec2Array; osg::ref_ptr secTexCoords = new osg::Vec2Array; + osg::ref_ptr overlayCoords = new osg::Vec2Array; osg::ref_ptr colors = new osg::Vec4Array; colors->push_back(osg::Vec4(1, 1, 1, 1)); @@ -458,6 +463,7 @@ public: } else { geometry->setTexCoordArray(0, priTexCoords.get()); } + geometry->setVertexAttribArray(14, overlayCoords.get(), osg::Array::BIND_PER_VERTEX); const unsigned invalid = ~unsigned(0); std::vector indexMap(getNumVertices(), invalid); @@ -473,6 +479,7 @@ public: if ( has_sec_tcs ) { secTexCoords->push_back(toOsg(getVertex(triangle[0]).GetTexCoord(1))); } + overlayCoords->push_back(toOsg(getVertex(triangle[0]).GetOverlayCoord())); } deFacade.push_back(indexMap[triangle[0]]); @@ -484,6 +491,7 @@ public: if ( has_sec_tcs ) { secTexCoords->push_back(toOsg(getVertex(triangle[1]).GetTexCoord(1))); } + overlayCoords->push_back(toOsg(getVertex(triangle[1]).GetOverlayCoord())); } deFacade.push_back(indexMap[triangle[1]]); @@ -495,6 +503,7 @@ public: if ( has_sec_tcs ) { secTexCoords->push_back(toOsg(getVertex(triangle[2]).GetTexCoord(1))); } + overlayCoords->push_back(toOsg(getVertex(triangle[2]).GetOverlayCoord())); } deFacade.push_back(indexMap[triangle[2]]); } diff --git a/simgear/scene/tgdb/SGTileGeometryBin.hxx b/simgear/scene/tgdb/SGTileGeometryBin.hxx index 03c786d4..7e862ff8 100644 --- a/simgear/scene/tgdb/SGTileGeometryBin.hxx +++ b/simgear/scene/tgdb/SGTileGeometryBin.hxx @@ -58,6 +58,7 @@ public: const std::vector& vertices(obj.get_wgs84_nodes()); const std::vector& normals(obj.get_normals()); const std::vector& texCoords(obj.get_texcoords()); + const std::vector& overlayCoords(obj.get_overlaycoords()); const int_list& tris_v(obj.get_tris_v()[grp]); const int_list& tris_n(obj.get_tris_n()[grp]); const tci_list& tris_tc(obj.get_tris_tcs()[grp]); @@ -82,6 +83,8 @@ public: if (!tris_tc[1].empty()) { v0.SetTexCoord( 1, getTexCoord(texCoords, tris_tc[1], tc1Scale, i-2) ); } + v0.SetOverlayCoord(overlayCoords[tris_v[i-2]]); + SGVertNormTex v1; v1.SetVertex( toVec3f(vertices[tris_v[i-1]]) ); v1.SetNormal( num_norms_is_num_verts ? normals[tris_n[i-1]] : @@ -90,6 +93,8 @@ public: if (!tris_tc[1].empty()) { v1.SetTexCoord( 1, getTexCoord(texCoords, tris_tc[1], tc1Scale, i-1) ); } + v1.SetOverlayCoord(overlayCoords[tris_v[i-1]]); + SGVertNormTex v2; v2.SetVertex( toVec3f(vertices[tris_v[i]]) ); v2.SetNormal( num_norms_is_num_verts ? normals[tris_n[i]] : @@ -98,6 +103,7 @@ public: if (!tris_tc[1].empty()) { v2.SetTexCoord( 1, getTexCoord(texCoords, tris_tc[1], tc1Scale, i) ); } + v2.SetOverlayCoord(overlayCoords[tris_v[i]]); triangles.insert(v0, v1, v2); } @@ -168,6 +174,7 @@ public: const std::vector& vertices(obj.get_wgs84_nodes()); const std::vector& normals(obj.get_normals()); const std::vector& texCoords(obj.get_texcoords()); + const std::vector& overlayCoords(obj.get_overlaycoords()); const int_list& fans_v(obj.get_fans_v()[grp]); const int_list& fans_n(obj.get_fans_n()[grp]); const tci_list& fans_tc(obj.get_fans_tcs()[grp]); @@ -191,6 +198,8 @@ public: if (!fans_tc[1].empty()) { v0.SetTexCoord( 1, getTexCoord(texCoords, fans_tc[1], tc1Scale, 0) ); } + v0.SetOverlayCoord(overlayCoords[fans_v[0]]); + SGVertNormTex v1; v1.SetVertex( toVec3f(vertices[fans_v[1]]) ); v1.SetNormal( num_norms_is_num_verts ? normals[fans_n[1]] : @@ -199,6 +208,8 @@ public: if (!fans_tc[1].empty()) { v1.SetTexCoord( 1, getTexCoord(texCoords, fans_tc[1], tc1Scale, 1) ); } + v1.SetOverlayCoord(overlayCoords[fans_v[1]]); + for (unsigned i = 2; i < fans_v.size(); ++i) { SGVertNormTex v2; v2.SetVertex( toVec3f(vertices[fans_v[i]]) ); @@ -208,6 +219,8 @@ public: if (!fans_tc[1].empty()) { v2.SetTexCoord( 1, getTexCoord(texCoords, fans_tc[1], tc1Scale, i) ); } + v2.SetOverlayCoord(overlayCoords[fans_v[i]]); + triangles.insert(v0, v1, v2); v1 = v2; } diff --git a/simgear/scene/tgdb/obj.cxx b/simgear/scene/tgdb/obj.cxx index 5d3461f5..c39d2db5 100644 --- a/simgear/scene/tgdb/obj.cxx +++ b/simgear/scene/tgdb/obj.cxx @@ -26,10 +26,18 @@ # include #endif +#include +#include +#include +#include +#include + #include "obj.hxx" #include #include +#include +#include #include "SGTileGeometryBin.hxx" // for original tile loading #include "SGTileDetailsCallback.hxx" // for tile details ( random objects, and lighting ) @@ -54,6 +62,7 @@ SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options double maxError = SG_SIMPLIFIER_MAX_ERROR; double object_range = SG_OBJECT_RANGE_ROUGH; double tile_min_expiry = SG_TILE_MIN_EXPIRY; + bool usePhotoscenery = false; if (options) { matlib = options->getMaterialLib(); @@ -69,6 +78,7 @@ SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options maxError = propertyNode->getDoubleValue("/sim/rendering/terrain/simplifier/max-error", maxError); object_range = propertyNode->getDoubleValue("/sim/rendering/static-lod/rough", object_range); tile_min_expiry= propertyNode->getDoubleValue("/sim/rendering/plod-minimum-expiry-time-secs", tile_min_expiry); + usePhotoscenery = propertyNode->getBoolValue("/sim/rendering/photoscenery/enabled", usePhotoscenery); } SGVec3d center = tile.get_gbs_center(); @@ -77,12 +87,38 @@ SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options if (matlib) matcache = matlib->generateMatCache(geodPos); + std::vector nodes = tile.get_wgs84_nodes(); + + std::vector satellite_overlay_coords; + osg::ref_ptr orthophoto = nullptr; + + if (usePhotoscenery) { + try { + const long index = lexical_cast(osgDB::getSimpleFileName(osgDB::getNameLessExtension(path))); + orthophoto = OrthophotoManager::instance()->getOrthophoto(index); + } catch (bad_lexical_cast&) { + orthophoto = OrthophotoManager::instance()->getOrthophoto(nodes, center); + } + } + + // rotate the tiles so that the bounding boxes get nearly axis aligned. // this will help the collision tree's bounding boxes a bit ... - std::vector nodes = tile.get_wgs84_nodes(); - for (unsigned i = 0; i < nodes.size(); ++i) + for (unsigned i = 0; i < nodes.size(); ++i) { + if (orthophoto) { + // Generate TexCoords for Overlay + const SGGeod node_geod = SGGeod::fromCart(nodes[i] + center); + const OrthophotoBounds actual_bbox = orthophoto->getBbox(); + const SGVec2f coords = actual_bbox.getTexCoord(node_geod); + satellite_overlay_coords.push_back(coords); + } else { + satellite_overlay_coords.push_back(SGVec2f(0.0, 0.0)); + } + nodes[i] = hlOr.transform(nodes[i]); + } tile.set_wgs84_nodes(nodes); + tile.set_overlaycoords(satellite_overlay_coords); SGQuatf hlOrf(hlOr[0], hlOr[1], hlOr[2], hlOr[3]); std::vector normals = tile.get_normals(); @@ -97,6 +133,23 @@ SGLoadBTG(const std::string& path, const simgear::SGReaderWriterOptions* options return NULL; osg::Node* node = tileGeometryBin->getSurfaceGeometry(matcache, useVBOs); + + if (node) { + // Get base node stateset + osg::StateSet *stateSet = node->getOrCreateStateSet(); + + osg::ref_ptr orthophotoAvailable = new osg::Uniform("orthophotoAvailable", false); + stateSet->addUniform(orthophotoAvailable, osg::StateAttribute::ON); + + // Add satellite texture (if orthophoto exists) + if (usePhotoscenery && orthophoto) { + stateSet->setTextureAttributeAndModes(15, orthophoto->getTexture(), osg::StateAttribute::ON); + orthophotoAvailable->set(true); + + SG_LOG(SG_OSG, SG_INFO, "Applying satellite orthophoto to terrain object with path " << path); + } + } + if (node && simplifyDistant) { osgUtil::Simplifier simplifier(ratio, maxError, maxLength); node->accept(simplifier); diff --git a/simgear/scene/tgdb/obj.hxx b/simgear/scene/tgdb/obj.hxx index c78bb463..22ea92bd 100644 --- a/simgear/scene/tgdb/obj.hxx +++ b/simgear/scene/tgdb/obj.hxx @@ -29,6 +29,8 @@ # error This library requires C++ #endif +#include + #include #include @@ -38,6 +40,9 @@ using std::string; +using boost::lexical_cast; +using boost::bad_lexical_cast; + class SGMaterialLib; namespace simgear { class SGReaderWriterOptions; diff --git a/simgear/scene/util/CMakeLists.txt b/simgear/scene/util/CMakeLists.txt index 09e225c0..f5823550 100644 --- a/simgear/scene/util/CMakeLists.txt +++ b/simgear/scene/util/CMakeLists.txt @@ -7,6 +7,7 @@ set(HEADERS NodeAndDrawableVisitor.hxx Noise.hxx OptionsReadFileCallback.hxx + OrthophotoManager.hxx OsgDebug.hxx OsgMath.hxx OsgSingleton.hxx @@ -40,6 +41,7 @@ set(SOURCES NodeAndDrawableVisitor.cxx Noise.cxx OptionsReadFileCallback.cxx + OrthophotoManager.cxx OsgDebug.cxx parse_color.cxx PrimitiveUtils.cxx diff --git a/simgear/scene/util/OrthophotoManager.cxx b/simgear/scene/util/OrthophotoManager.cxx new file mode 100644 index 00000000..4363c55f --- /dev/null +++ b/simgear/scene/util/OrthophotoManager.cxx @@ -0,0 +1,409 @@ +// OrthophotoManager.cxx -- manages satellite orthophotos +// +// Copyright (C) 2020 Nathaniel MacArthur-Warner nathanielwarner77@gmail.com +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the +// Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301, USA. + +#include "OrthophotoManager.hxx" + +namespace simgear { + + OrthophotoBounds OrthophotoBounds::fromBucket(const SGBucket& bucket) { + OrthophotoBounds bounds; + bounds.expandToInclude(bucket); + return bounds; + } + + void OrthophotoBounds::_updateHemisphere() { + if (_minPosLon <= 180 && _maxPosLon >= 0 && _minNegLon < 0 && _maxNegLon >= -180) { + // We have negative and positive longitudes. + // Choose whether we're straddling the Prime Meridian or 180th meridian + if (_maxPosLon - _minNegLon < 180) { + _hemisphere = StraddlingPm; + } else { + _hemisphere = StraddlingIdl; + } + } + else if (_minPosLon <= 180.0 && _maxPosLon >= 0.0) { + _hemisphere = Eastern; + } + else if (_minNegLon < 0.0 && _maxNegLon >= -180.0) { + _hemisphere = Western; + } + else { + _hemisphere = Invalid; + } + } + + double OrthophotoBounds::getWidth() const { + switch (_hemisphere) { + case Eastern: + return _maxPosLon - _minPosLon; + case Western: + return _maxNegLon - _minNegLon; + case StraddlingPm: + return _maxPosLon - _minNegLon; + case StraddlingIdl: + return (180.0 - _minPosLon) + (_maxNegLon + 180.0); + default: + SG_LOG(SG_TERRAIN, SG_ALERT, "OrthophotoBounds::getWidth: My data is invalid. Returning 0."); + return 0.0; + } + } + + double OrthophotoBounds::getHeight() const { + return _maxLat - _minLat; + } + + SGVec2f OrthophotoBounds::getTexCoord(const SGGeod& geod) const { + + const double lon = geod.getLongitudeDeg(); + const double width = getWidth(); + float x = 0.0; + + switch (_hemisphere) { + case Eastern: + x = (lon - _minPosLon) / width; + break; + case Western: + x = (lon - _minNegLon) / width; + break; + case StraddlingPm: + x = (lon - _minNegLon) / width; + break; + case StraddlingIdl: + if (lon >= 0) { + // Geod is in the eastern hemisphere + x = (lon - _minPosLon) / width; + } else { + // Geod is in the western hemisphere + x = (180.0 - _minPosLon + lon) / width; + } + break; + default: + SG_LOG(SG_TERRAIN, SG_ALERT, "OrthophotoBounds::getTexCoord: My data is invalid."); + break; + } + + const float y = (_maxLat - geod.getLatitudeDeg()) / getHeight(); + + return SGVec2f(x, y); + } + + double OrthophotoBounds::getLonOffset(const OrthophotoBounds& other) const { + + std::string error_message = ""; + + switch (_hemisphere) { + case Eastern: + if (other._hemisphere == Eastern) + return other._minPosLon - _minPosLon; + else + error_message = "I'm not in the same hemisphere as other."; + break; + case Western: + if (other._hemisphere == Western) + return other._minNegLon - _minNegLon; + else + error_message = "I'm not in the same hemisphere as other."; + break; + case StraddlingPm: + if (other._hemisphere == Western || other._hemisphere == StraddlingPm) + return other._minNegLon - _minNegLon; + else if (other._hemisphere == Eastern) + return -_minNegLon + other._minPosLon; + else + error_message = "I'm not in the same hemisphere as other"; + break; + case StraddlingIdl: + if (other._hemisphere == Eastern || other._hemisphere == StraddlingIdl) { + return other._minPosLon - _minPosLon; + } else if (other._hemisphere == StraddlingIdl) { + return (180.0 - _minPosLon) + (other._minNegLon + 180.0); + } else { + error_message = "Other has invalid data."; + } + break; + default: + error_message = "My data is invalid."; + break; + } + + SG_LOG(SG_TERRAIN, SG_ALERT, "OrthophotoBounds::getLonOffset: " << error_message << " Returning 0."); + return 0.0; + } + + double OrthophotoBounds::getLatOffset(const OrthophotoBounds& other) const { + return _maxLat - other._maxLat; + } + + void OrthophotoBounds::expandToInclude(const SGBucket& bucket) { + double center_lon = bucket.get_center_lon(); + double center_lat = bucket.get_center_lat(); + double width = bucket.get_width(); + double height = bucket.get_height(); + + double left = center_lon - width / 2; + double right = center_lon + width / 2; + double bottom = center_lat - height / 2; + double top = center_lat + height / 2; + + expandToInclude(left, bottom); + expandToInclude(right, top); + } + + void OrthophotoBounds::expandToInclude(const double lon, const double lat) { + if (lon >= 0) { + if (lon < _minPosLon) + _minPosLon = lon; + if (lon > _maxPosLon) + _maxPosLon = lon; + } else { + if (lon < _minNegLon) + _minNegLon = lon; + if (lon > _maxNegLon) + _maxNegLon = lon; + } + + if (lat < _minLat) + _minLat = lat; + if (lat > _maxLat) + _maxLat = lat; + + _updateHemisphere(); + } + + void OrthophotoBounds::expandToInclude(const OrthophotoBounds& bounds) { + switch (bounds._hemisphere) { + case Eastern: + expandToInclude(bounds._minPosLon, bounds._minLat); + expandToInclude(bounds._maxPosLon, bounds._maxLat); + break; + case Western: + expandToInclude(bounds._minNegLon, bounds._minLat); + expandToInclude(bounds._maxNegLon, bounds._maxLat); + break; + case StraddlingPm: + expandToInclude(bounds._minNegLon, bounds._minLat); + expandToInclude(bounds._maxPosLon, bounds._maxLat); + case StraddlingIdl: + expandToInclude(bounds._minPosLon, bounds._minLat); + expandToInclude(bounds._maxNegLon, bounds._maxLat); + break; + case Invalid: + SG_LOG(SG_TERRAIN, SG_ALERT, "OrthophotoBounds::absorb: Data in bounds to absorb is invalid. Aborting."); + break; + } + } + + OrthophotoRef Orthophoto::fromBucket(const SGBucket& bucket, const PathList& scenery_paths) { + + const std::string bucket_path = bucket.gen_base_path(); + + for (const auto& scenery_path : scenery_paths) { + SGPath path = scenery_path / "Orthophotos" / bucket_path / std::to_string(bucket.gen_index()); + + path.concat(".png"); + if (path.exists()) { + ImageRef image = osgDB::readRefImageFile(path.str()); + image->flipVertical(); + OrthophotoBounds bbox = OrthophotoBounds::fromBucket(bucket); + return new Orthophoto(image, bbox); + } + } + + return nullptr; + } + + // Handy image cropping function from osgEarth + osg::Image* cropImage(const osg::Image* image, + double src_minx, double src_miny, double src_maxx, double src_maxy, + double &dst_minx, double &dst_miny, double &dst_maxx, double &dst_maxy) { + if ( image == 0L ) + return 0L; + + //Compute the desired cropping rectangle + int windowX = osg::clampBetween( (int)floor( (dst_minx - src_minx) / (src_maxx - src_minx) * (double)image->s()), 0, image->s()-1); + int windowY = osg::clampBetween( (int)floor( (dst_miny - src_miny) / (src_maxy - src_miny) * (double)image->t()), 0, image->t()-1); + int windowWidth = osg::clampBetween( (int)ceil( (dst_maxx - src_minx) / (src_maxx - src_minx) * (double)image->s()) - windowX, 0, image->s()); + int windowHeight = osg::clampBetween( (int)ceil( (dst_maxy - src_miny) / (src_maxy - src_miny) * (double)image->t()) - windowY, 0, image->t()); + + if (windowX + windowWidth > image->s()) + { + windowWidth = image->s() - windowX; + } + + if (windowY + windowHeight > image->t()) + { + windowHeight = image->t() - windowY; + } + + if ((windowWidth * windowHeight) == 0) + { + return NULL; + } + + //Compute the actual bounds of the area we are computing + double res_s = (src_maxx - src_minx) / (double)image->s(); + double res_t = (src_maxy - src_miny) / (double)image->t(); + + dst_minx = src_minx + (double)windowX * res_s; + dst_miny = src_miny + (double)windowY * res_t; + dst_maxx = dst_minx + (double)windowWidth * res_s; + dst_maxy = dst_miny + (double)windowHeight * res_t; + + //OE_NOTICE << "Copying from " << windowX << ", " << windowY << ", " << windowWidth << ", " << windowHeight << std::endl; + + //Allocate the croppped image + osg::Image* cropped = new osg::Image; + cropped->allocateImage(windowWidth, windowHeight, image->r(), image->getPixelFormat(), image->getDataType()); + cropped->setInternalTextureFormat( image->getInternalTextureFormat() ); + + for (int layer=0; layerr(); ++layer) + { + for (int src_row = windowY, dst_row=0; dst_row < windowHeight; src_row++, dst_row++) + { + if (src_row > image->t()-1) SG_LOG(SG_OSG, SG_INFO, "HeightBroke"); + const void* src_data = image->data(windowX, src_row, layer); + void* dst_data = cropped->data(0, dst_row, layer); + memcpy( dst_data, src_data, cropped->getRowSizeInBytes()); + } + } + return cropped; + } + + Orthophoto::Orthophoto(const std::vector& orthophotos, const OrthophotoBounds& needed_bbox) { + + OrthophotoBounds prelim_bbox; + for (const auto& orthophoto : orthophotos) { + prelim_bbox.expandToInclude(orthophoto->getBbox()); + } + + const OrthophotoRef& some_orthophoto = orthophotos[0]; + const ImageRef& some_image = some_orthophoto->_image; + const OrthophotoBounds& some_bbox = some_orthophoto->getBbox(); + const double degs_to_pixels = some_image->s() / some_bbox.getWidth(); + + const int total_width = degs_to_pixels * prelim_bbox.getWidth(); + const int total_height = degs_to_pixels * prelim_bbox.getHeight(); + + const int depth = some_image->r(); + GLenum pixel_format = some_image->getPixelFormat(); + GLenum data_type = some_image->getDataType(); + int packing = some_image->getPacking(); + + ImageRef prelim_image = new osg::Image(); + prelim_image->allocateImage(total_width, total_height, depth, pixel_format, data_type, packing); + + for (const auto& orthophoto : orthophotos) { + + const OrthophotoBounds& bounds = orthophoto->getBbox(); + const int width = degs_to_pixels * bounds.getWidth(); + const int height = degs_to_pixels * bounds.getHeight(); + const int s_offset = degs_to_pixels * prelim_bbox.getLonOffset(bounds); + const int t_offset = degs_to_pixels * prelim_bbox.getLatOffset(bounds); + + // Make a deep copy of the orthophoto's image so that we don't modify the original when scaling + ImageRef sub_image = new osg::Image(*orthophoto->_image, osg::CopyOp::DEEP_COPY_ALL); + + sub_image->scaleImage(width, height, depth); + + prelim_image->copySubImage(s_offset, t_offset, 0, sub_image); + } + + double x_min = prelim_bbox.getLonOffset(needed_bbox) * degs_to_pixels; + double y_min = prelim_bbox.getLatOffset(needed_bbox) * degs_to_pixels; + double x_max = x_min + needed_bbox.getWidth() * degs_to_pixels; + double y_max = y_min + needed_bbox.getHeight() * degs_to_pixels; + + _image = cropImage(prelim_image, 0.0, 0.0, total_width, total_height, x_min, y_min, x_max, y_max); + _bbox = needed_bbox; // Theoretically, it would be good to adjust from how the bounds have changed from cropping + } + + osg::ref_ptr Orthophoto::getTexture() { + osg::ref_ptr texture = new osg::Texture2D(_image); + texture->setWrap(osg::Texture::WrapParameter::WRAP_S, osg::Texture::WrapMode::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WrapParameter::WRAP_T, osg::Texture::WrapMode::CLAMP_TO_EDGE); + texture->setWrap(osg::Texture::WrapParameter::WRAP_R, osg::Texture::WrapMode::CLAMP_TO_EDGE); + texture->setMaxAnisotropy(SGSceneFeatures::instance()->getTextureFilter()); + return texture; + } + + OrthophotoManager* OrthophotoManager::instance() { + return SingletonRefPtr::instance(); + } + + void OrthophotoManager::registerOrthophoto(const long bucket_idx, const OrthophotoRef& orthophoto) { + OrthophotoRef& entry = _orthophotos[bucket_idx]; + + if (entry) { + SG_LOG(SG_TERRAIN, SG_WARN, "OrthophotoManager::registerOrthophoto(): Bucket index " << bucket_idx << " already has a registered orthophoto."); + } + + if (!orthophoto) { + SG_LOG(SG_TERRAIN, SG_WARN, "OrthophotoManager::registerOrthophoto(): Registering null orthophoto for bucket index " << bucket_idx); + } + + entry = orthophoto; + + SG_LOG(SG_TERRAIN, SG_INFO, "Registered orthophoto for bucket index " << bucket_idx); + } + + void OrthophotoManager::unregisterOrthophoto(const long bucket_idx) { + if (_orthophotos[bucket_idx]) { + _orthophotos.erase(bucket_idx); + SG_LOG(SG_TERRAIN, SG_INFO, "Unregistered orthophoto with bucket index " << bucket_idx); + } else { + SG_LOG(SG_TERRAIN, SG_WARN, "OrthophotoManager::unregisterOrthophoto(): Attempted to unregister orthophoto with bucket index that is not currently registered."); + } + } + + OrthophotoRef OrthophotoManager::getOrthophoto(const long bucket_idx) { + if (_orthophotos[bucket_idx]) { + return _orthophotos[bucket_idx]; + } else { + return nullptr; + } + } + + OrthophotoRef OrthophotoManager::getOrthophoto(const std::vector& nodes, const SGVec3d& center) { + + std::unordered_map orthophotos_attempted; + std::vector orthophotos; + OrthophotoBounds needed_bounds; + + for (const auto& node : nodes) { + const SGGeod node_geod = SGGeod::fromCart(node + center); + needed_bounds.expandToInclude(node_geod.getLongitudeDeg(), node_geod.getLatitudeDeg()); + const SGBucket bucket(node_geod); + const long bucket_idx = bucket.gen_index(); + bool& orthophoto_attempted = orthophotos_attempted[bucket_idx]; + if (!orthophoto_attempted) { + OrthophotoRef orthophoto = this->getOrthophoto(bucket_idx); + if (orthophoto) { + orthophotos.push_back(orthophoto); + } + orthophoto_attempted = true; + } + } + + if (orthophotos.size() == 0) { + return nullptr; + } + + return new Orthophoto(orthophotos, needed_bounds); + } +} diff --git a/simgear/scene/util/OrthophotoManager.hxx b/simgear/scene/util/OrthophotoManager.hxx new file mode 100644 index 00000000..45b7ba28 --- /dev/null +++ b/simgear/scene/util/OrthophotoManager.hxx @@ -0,0 +1,108 @@ +// OrthophotoManager.hxx -- manages satellite orthophotos +// +// Copyright (C) 2020 Nathaniel MacArthur-Warner nathanielwarner77@gmail.com +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the +// Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301, USA. + +#ifndef SG_SCENE_ORTHOPHOTO_MANAGER +#define SG_SCENE_ORTHOPHOTO_MANAGER + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SGSceneFeatures.hxx" +#include "OsgSingleton.hxx" + +namespace simgear { + + using ImageRef = osg::ref_ptr; + + class Orthophoto; + using OrthophotoRef = osg::ref_ptr; + + class OrthophotoBounds { + private: + double _minNegLon = SGLimitsd::max(); + double _minPosLon = SGLimitsd::max(); + double _maxNegLon = SGLimitsd::lowest(); + double _maxPosLon = SGLimitsd::lowest(); + double _minLat = SGLimitsd::max(); + double _maxLat = SGLimitsd::lowest(); + + enum Hemisphere {Eastern, Western, StraddlingPm, StraddlingIdl, Invalid} _hemisphere = Invalid; + void _updateHemisphere(); + + public: + static OrthophotoBounds fromBucket(const SGBucket& bucket); + + double getWidth() const; + double getHeight() const; + SGVec2f getTexCoord(const SGGeod& geod) const; + double getLonOffset(const OrthophotoBounds& other) const; + double getLatOffset(const OrthophotoBounds& other) const; + + void expandToInclude(const SGBucket& bucket); + void expandToInclude(const double lon, const double lat); + void expandToInclude(const OrthophotoBounds& bounds); + }; + + class Orthophoto : public osg::Referenced { + private: + ImageRef _image; + OrthophotoBounds _bbox; + + public: + static OrthophotoRef fromBucket(const SGBucket& bucket, const PathList& scenery_paths); + + Orthophoto(const ImageRef& image, const OrthophotoBounds& bbox) { _image = image; _bbox = bbox; } + Orthophoto(const std::vector& orthophotos, const OrthophotoBounds& needed_bbox); + + osg::ref_ptr getTexture(); + OrthophotoBounds getBbox() const { return _bbox; }; + }; + + class OrthophotoManager : public osg::Referenced { + private: + std::unordered_map _orthophotos; + public: + static OrthophotoManager* instance(); + + void registerOrthophoto(const long bucket_idx, const OrthophotoRef& orthophoto); + void unregisterOrthophoto(const long bucket_idx); + + /** + * Get an orthophoto by bucket index + **/ + OrthophotoRef getOrthophoto(const long bucket_idx); + + /** + * Get an orthophoto given a set of nodes. + * Used for airports, since they are not buckets. + **/ + OrthophotoRef getOrthophoto(const std::vector& nodes, const SGVec3d& center); + }; +} + +#endif \ No newline at end of file