diff -Nru simgear-2020.3.16+dfsg/debian/changelog simgear-2020.3.18+dfsg/debian/changelog --- simgear-2020.3.16+dfsg/debian/changelog 2022-12-10 22:27:56.000000000 +0000 +++ simgear-2020.3.18+dfsg/debian/changelog 2023-07-07 21:34:24.000000000 +0000 @@ -1,8 +1,23 @@ -simgear (1:2020.3.16+dfsg-1ubuntu1) lunar; urgency=medium +simgear (1:2020.3.18+dfsg-2) unstable; urgency=medium + * Team upload. * Fix build on riscv64. + Thanks to Gianfranco Costamagna + (Closes: #1025905) - -- Gianfranco Costamagna Sat, 10 Dec 2022 23:27:56 +0100 + -- Dr. Tobias Quathamer Fri, 07 Jul 2023 23:34:24 +0200 + +simgear (1:2020.3.18+dfsg-1) unstable; urgency=medium + + * Team upload. + * New upstream version 2020.3.18+dfsg + * Replace Build-Depends on obsolete libgl1-mesa-dev with current libgl-dev + * Update Standards-Version to 4.6.2, no changed needed + * Update d/copyright + * Add gbp.conf for DEP-14 repository layout + * Remove myself from Uploaders + + -- Dr. Tobias Quathamer Fri, 07 Jul 2023 23:04:01 +0200 simgear (1:2020.3.16+dfsg-1) unstable; urgency=medium diff -Nru simgear-2020.3.16+dfsg/debian/control simgear-2020.3.18+dfsg/debian/control --- simgear-2020.3.16+dfsg/debian/control 2022-10-26 08:52:48.000000000 +0000 +++ simgear-2020.3.18+dfsg/debian/control 2023-07-07 21:02:44.000000000 +0000 @@ -3,14 +3,13 @@ Priority: optional Maintainer: Debian FlightGear Crew Uploaders: Ove Kaaven , - Markus Wanner , - Dr. Tobias Quathamer + Markus Wanner Build-Depends: cmake, debhelper-compat (= 13), libboost-dev, libcurl4-gnutls-dev, libexpat1-dev, - libgl1-mesa-dev, + libgl-dev, libglu1-mesa-dev, liblzma-dev, libopenal-dev, @@ -19,7 +18,7 @@ libutfcpp-dev, netbase, zlib1g-dev -Standards-Version: 4.6.1 +Standards-Version: 4.6.2 Rules-Requires-Root: no Homepage: https://home.flightgear.org/ Vcs-Browser: https://salsa.debian.org/debian/simgear diff -Nru simgear-2020.3.16+dfsg/debian/copyright simgear-2020.3.18+dfsg/debian/copyright --- simgear-2020.3.16+dfsg/debian/copyright 2022-10-24 11:20:15.000000000 +0000 +++ simgear-2020.3.18+dfsg/debian/copyright 2023-07-07 20:49:37.000000000 +0000 @@ -180,7 +180,7 @@ Copyright: 2001-2012 Ove Kaaven 2013 Scott Howard 2013-2016 Markus Wanner - 2017-2020 Dr. Tobias Quathamer + 2017-2023 Dr. Tobias Quathamer License: LGPL-2+ License: BSL-1.0 diff -Nru simgear-2020.3.16+dfsg/debian/gbp.conf simgear-2020.3.18+dfsg/debian/gbp.conf --- simgear-2020.3.16+dfsg/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ simgear-2020.3.18+dfsg/debian/gbp.conf 2023-07-07 20:50:11.000000000 +0000 @@ -0,0 +1,3 @@ +[DEFAULT] +debian-branch = debian/latest +upstream-branch = upstream/latest diff -Nru simgear-2020.3.16+dfsg/debian/patches/fix-atomic-build-riscv64.patch simgear-2020.3.18+dfsg/debian/patches/fix-atomic-build-riscv64.patch --- simgear-2020.3.16+dfsg/debian/patches/fix-atomic-build-riscv64.patch 2022-12-10 22:27:54.000000000 +0000 +++ simgear-2020.3.18+dfsg/debian/patches/fix-atomic-build-riscv64.patch 2023-07-07 21:31:55.000000000 +0000 @@ -1,12 +1,12 @@ Description: Don't use atomic_bool, but atomic_int, the latter doesn't need external latomic linking on riscv64 Author: Gianfranco Costamagna -Origin: https://github.com/deepmind/mujoco/pull/632 +Origin: https://sourceforge.net/p/flightgear/simgear/merge-requests/119/ Last-Update: 2022-12-10 ---- simgear-2020.3.16+dfsg.orig/simgear/threads/SGThread.hxx -+++ simgear-2020.3.16+dfsg/simgear/threads/SGThread.hxx -@@ -171,10 +171,10 @@ private: +--- a/simgear/threads/SGThread.hxx ++++ b/simgear/threads/SGThread.hxx +@@ -171,10 +171,10 @@ bool _terminated; int last_await_time; diff -Nru simgear-2020.3.16+dfsg/simgear/environment/test_metar.cxx simgear-2020.3.18+dfsg/simgear/environment/test_metar.cxx --- simgear-2020.3.16+dfsg/simgear/environment/test_metar.cxx 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/environment/test_metar.cxx 2023-03-14 07:49:42.000000000 +0000 @@ -123,6 +123,14 @@ #endif } +void test_EDQM_failure() +{ + // + + SGMetar m1("2022/12/11 19:50 EDQM 111950Z AUTO VRB03KT 2700 // ///026/// M06/M07 Q1006"); + +} + int main(int argc, char* argv[]) { try { @@ -134,6 +142,7 @@ test_clouds_without_height(); test_GLRB_failure(); test_LOWK_failure(); + test_EDQM_failure(); } catch (sg_exception& e) { cerr << "got exception:" << e.getMessage() << endl; return -1; diff -Nru simgear-2020.3.16+dfsg/simgear/io/sg_binobj.hxx simgear-2020.3.18+dfsg/simgear/io/sg_binobj.hxx --- simgear-2020.3.16+dfsg/simgear/io/sg_binobj.hxx 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/io/sg_binobj.hxx 2023-03-14 07:49:42.000000000 +0000 @@ -150,6 +150,7 @@ 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,10 @@ 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 -Nru simgear-2020.3.16+dfsg/simgear/misc/sg_path.cxx simgear-2020.3.18+dfsg/simgear/misc/sg_path.cxx --- simgear-2020.3.16+dfsg/simgear/misc/sg_path.cxx 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/misc/sg_path.cxx 2023-03-14 07:49:42.000000000 +0000 @@ -484,7 +484,7 @@ } //------------------------------------------------------------------------------ -void SGPath::updateAttrsIfNull() const +void SGPath::updateCachedAttributes() const { if (_cached && _cacheEnabled) { return; @@ -570,7 +570,7 @@ return; } - updateAttrsIfNull(); + updateCachedAttributes(); _rwCached = true; } @@ -589,7 +589,7 @@ return _exists; } #endif - updateAttrsIfNull(); + updateCachedAttributes(); return _exists; } @@ -609,13 +609,13 @@ bool SGPath::isDir() const { - updateAttrsIfNull(); + updateCachedAttributes(); return _exists && _isDir; } bool SGPath::isFile() const { - updateAttrsIfNull(); + updateCachedAttributes(); return _exists && _isFile; } @@ -807,13 +807,13 @@ time_t SGPath::modTime() const { - updateAttrsIfNull(); + updateCachedAttributes(); return _modTime; } size_t SGPath::sizeInBytes() const { - updateAttrsIfNull(); + updateCachedAttributes(); return _size; } diff -Nru simgear-2020.3.16+dfsg/simgear/misc/sg_path.hxx simgear-2020.3.18+dfsg/simgear/misc/sg_path.hxx --- simgear-2020.3.16+dfsg/simgear/misc/sg_path.hxx 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/misc/sg_path.hxx 2023-03-14 07:49:42.000000000 +0000 @@ -372,7 +372,7 @@ void fix(); - void updateAttrsIfNull() const; + void updateCachedAttributes() const; void checkAccess() const; bool permissionsAllowsWrite() const; diff -Nru simgear-2020.3.16+dfsg/simgear/scene/model/SGText.cxx simgear-2020.3.18+dfsg/simgear/scene/model/SGText.cxx --- simgear-2020.3.16+dfsg/simgear/scene/model/SGText.cxx 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/scene/model/SGText.cxx 2023-03-14 07:49:42.000000000 +0000 @@ -23,6 +23,13 @@ #include "SGText.hxx" +#include +#include +#include +#include +#include +#include + #include #include #include @@ -96,9 +103,16 @@ SGConstPropertyNode_ptr p; osgText::Text * text = new osgText::Text(); - osg::Geode * g = new osg::Geode; + simgear::EffectGeode * g = new simgear::EffectGeode; g->addDrawable( text ); + SGPropertyNode_ptr effectProp = new SGPropertyNode; + makeChild(effectProp, "inherits-from")->setStringValue("Effects/text-default"); + simgear::Effect* effect = simgear::makeEffect( + effectProp, true, dynamic_cast(options)); + if (effect) + g->setEffect(effect); + const std::string requestedFont = configNode->getStringValue("font","Helvetica"); const SGPath fontPath = simgear::ResourceManager::instance()->findPath("Fonts/" + requestedFont); if ( !fontPath.isNull() ) { diff -Nru simgear-2020.3.16+dfsg/simgear/scene/tgdb/obj.cxx simgear-2020.3.18+dfsg/simgear/scene/tgdb/obj.cxx --- simgear-2020.3.16+dfsg/simgear/scene/tgdb/obj.cxx 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/scene/tgdb/obj.cxx 2023-03-14 07:49:42.000000000 +0000 @@ -26,10 +26,19 @@ # 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 +63,7 @@ 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 +79,7 @@ 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 +88,39 @@ 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 +135,22 @@ 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_DEBUG, "Applying satellite orthophoto to terrain object with path " << path); + } + } + if (node && simplifyDistant) { osgUtil::Simplifier simplifier(ratio, maxError, maxLength); node->accept(simplifier); diff -Nru simgear-2020.3.16+dfsg/simgear/scene/tgdb/obj.hxx simgear-2020.3.18+dfsg/simgear/scene/tgdb/obj.hxx --- simgear-2020.3.16+dfsg/simgear/scene/tgdb/obj.hxx 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/scene/tgdb/obj.hxx 2023-03-14 07:49:42.000000000 +0000 @@ -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 -Nru simgear-2020.3.16+dfsg/simgear/scene/tgdb/SGTexturedTriangleBin.hxx simgear-2020.3.18+dfsg/simgear/scene/tgdb/SGTexturedTriangleBin.hxx --- simgear-2020.3.16+dfsg/simgear/scene/tgdb/SGTexturedTriangleBin.hxx 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/scene/tgdb/SGTexturedTriangleBin.hxx 2023-03-14 07:49:42.000000000 +0000 @@ -89,10 +89,15 @@ } 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; }; @@ -432,6 +437,8 @@ 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)); @@ -453,7 +460,9 @@ geometry->setTexCoordArray(1, secTexCoords); } else { geometry->setTexCoordArray(0, priTexCoords); - } + } + geometry->setVertexAttribArray(14, overlayCoords.get(), osg::Array::BIND_PER_VERTEX); + const unsigned invalid = ~unsigned(0); std::vector indexMap(getNumVertices(), invalid); @@ -469,6 +478,7 @@ 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]]); @@ -480,6 +490,7 @@ 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]]); @@ -491,6 +502,7 @@ 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 -Nru simgear-2020.3.16+dfsg/simgear/scene/tgdb/SGTileGeometryBin.hxx simgear-2020.3.18+dfsg/simgear/scene/tgdb/SGTileGeometryBin.hxx --- simgear-2020.3.16+dfsg/simgear/scene/tgdb/SGTileGeometryBin.hxx 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/scene/tgdb/SGTileGeometryBin.hxx 2023-03-14 07:49:42.000000000 +0000 @@ -58,6 +58,7 @@ 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 @@ 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 @@ 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,8 @@ 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 +175,7 @@ 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 +199,8 @@ 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 +209,8 @@ 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 +220,8 @@ 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 -Nru simgear-2020.3.16+dfsg/simgear/scene/util/CMakeLists.txt simgear-2020.3.18+dfsg/simgear/scene/util/CMakeLists.txt --- simgear-2020.3.16+dfsg/simgear/scene/util/CMakeLists.txt 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/scene/util/CMakeLists.txt 2023-03-14 07:49:42.000000000 +0000 @@ -7,6 +7,7 @@ NodeAndDrawableVisitor.hxx Noise.hxx OptionsReadFileCallback.hxx + OrthophotoManager.hxx OsgDebug.hxx OsgMath.hxx OsgSingleton.hxx @@ -41,6 +42,7 @@ NodeAndDrawableVisitor.cxx Noise.cxx OptionsReadFileCallback.cxx + OrthophotoManager.cxx OsgDebug.cxx parse_color.cxx PrimitiveUtils.cxx diff -Nru simgear-2020.3.16+dfsg/simgear/scene/util/OrthophotoManager.cxx simgear-2020.3.18+dfsg/simgear/scene/util/OrthophotoManager.cxx --- simgear-2020.3.16+dfsg/simgear/scene/util/OrthophotoManager.cxx 1970-01-01 00:00:00.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/scene/util/OrthophotoManager.cxx 2023-03-14 07:49:42.000000000 +0000 @@ -0,0 +1,402 @@ +// 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" +#include "SGSceneFeatures.hxx" +#include + +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); + break; + 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; + } + } + + Texture2DRef textureFromImage(const ImageRef& image) { + Texture2DRef 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; + } + + 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()); + + SGPath dds_path = path; + dds_path.concat(".dds"); + if (dds_path.exists()) { + ImageRef image = osgDB::readRefImageFile(dds_path.str()); + if (image) { + if (!image->isCompressed()) { + SG_LOG(SG_OSG, SG_WARN, "Loading uncompressed DDS orthophoto. This is known to cause problems on some systems."); + } + const Texture2DRef texture = textureFromImage(image); + const OrthophotoBounds bbox = OrthophotoBounds::fromBucket(bucket); + return new Orthophoto(texture, bbox); + } + } + + SGPath png_path = path; + png_path.concat(".png"); + if (png_path.exists()) { + ImageRef image = osgDB::readRefImageFile(png_path.str()); + if (image) { + image->flipVertical(); + const Texture2DRef texture = textureFromImage(image); + const OrthophotoBounds bbox = OrthophotoBounds::fromBucket(bucket); + return new Orthophoto(texture, bbox); + } + } + } + + return nullptr; + } + + Orthophoto::Orthophoto(const std::vector& orthophotos) { + + for (const auto& orthophoto : orthophotos) { + _bbox.expandToInclude(orthophoto->getBbox()); + } + + const OrthophotoRef& some_orthophoto = orthophotos[0]; + const ImageRef& some_image = some_orthophoto->_texture->getImage(); + const OrthophotoBounds& some_bbox = some_orthophoto->getBbox(); + const double degs_to_pixels_x = some_image->s() / some_bbox.getWidth(); + const double degs_to_pixels_y = some_image->t() / some_bbox.getHeight(); + + const int total_width = degs_to_pixels_x * _bbox.getWidth(); + const int total_height = degs_to_pixels_y * _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 composite_image = new osg::Image(); + composite_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_x * bounds.getWidth(); + const int height = degs_to_pixels_y * bounds.getHeight(); + const int s_offset = degs_to_pixels_x * _bbox.getLonOffset(bounds); + const int t_offset = degs_to_pixels_y * _bbox.getLatOffset(bounds); + + ImageRef sub_image = orthophoto->_texture->getImage(); + + if (sub_image->s() != width || sub_image->t() != height) { + SG_LOG(SG_OSG, SG_INFO, "Orthophoto resolution mismatch. Automatic scaling will be performed."); + ImageRef scaled_image; + bool success = ImageUtils::resizeImage(sub_image, width, height, scaled_image); + if (success) { + sub_image = scaled_image; + } else { + SG_LOG(SG_OSG, SG_ALERT, "Failed to scale part of composite orthophoto. The image on the airport may be distorted."); + } + } + + if (sub_image->getPixelFormat() != pixel_format || sub_image->getDataType() != data_type) { + SG_LOG(SG_OSG, SG_INFO, "Pixel format or data type mismatch. Attempting to convert component of composite orthophoto."); + if (ImageUtils::canConvert(sub_image, pixel_format, data_type)) { + sub_image = ImageUtils::convert(sub_image, pixel_format, data_type); + } else { + SG_LOG(SG_OSG, SG_ALERT, "Failed to convert component of composite orthophoto. Part of the image on the airport may be missing."); + } + } + + composite_image->copySubImage(s_offset, t_offset, 0, sub_image); + } + + int max_texture_size = SGSceneFeatures::instance()->getMaxTextureSize(); + int new_width = total_width; + int new_height = total_height; + if (new_width > max_texture_size) { + int factor = new_width / max_texture_size; + new_width /= factor; + new_height /= factor; + } + if (new_height > max_texture_size) { + int factor = new_height / max_texture_size; + new_width /= factor; + new_height /= factor; + } + if (total_width != new_width || total_height != new_height) { + SG_LOG(SG_OSG, SG_INFO, "Composite orthophoto exceeds the maximum texture size of your GPU. Automatic scaling will be performed."); + ImageRef scaled_image; + bool success = ImageUtils::resizeImage(composite_image, new_width, new_height, scaled_image); + if (success) { + composite_image = scaled_image; + } else { + SG_LOG(SG_OSG, SG_ALERT, "Failed to scale composite orthophoto. You may encounter errors due to the oversize texture."); + } + } + + _texture = textureFromImage(composite_image); + } + + OrthophotoManager* OrthophotoManager::instance() { + return SingletonRefPtr::instance(); + } + + void OrthophotoManager::registerOrthophoto(const long bucket_idx, const OrthophotoRef& orthophoto) { + OrthophotoWeakRef& entry = _orthophotos[bucket_idx]; + + if (entry.valid()) { + 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); + } + + OrthophotoRef OrthophotoManager::getOrthophoto(const long bucket_idx) { + OrthophotoWeakRef weak_ref = _orthophotos[bucket_idx]; + OrthophotoRef ref; + if (weak_ref.valid()) { + weak_ref.lock(ref); + } + return ref; + } + + 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.empty()) { + return nullptr; + } else if (orthophotos.size() == 1) { + return orthophotos[0]; + } else { + return new Orthophoto(orthophotos); + } + } +} diff -Nru simgear-2020.3.16+dfsg/simgear/scene/util/OrthophotoManager.hxx simgear-2020.3.18+dfsg/simgear/scene/util/OrthophotoManager.hxx --- simgear-2020.3.16+dfsg/simgear/scene/util/OrthophotoManager.hxx 1970-01-01 00:00:00.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear/scene/util/OrthophotoManager.hxx 2023-03-14 07:49:42.000000000 +0000 @@ -0,0 +1,112 @@ +// 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 +#include +#include "SGSceneFeatures.hxx" +#include "OsgSingleton.hxx" +#include "SGImageUtils.hxx" + +namespace simgear { + + using ImageRef = osg::ref_ptr; + using Texture2DRef = osg::ref_ptr; + + class Orthophoto; + using OrthophotoRef = osg::ref_ptr; + using OrthophotoWeakRef = osg::observer_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: + Texture2DRef _texture; + OrthophotoBounds _bbox; + + public: + static OrthophotoRef fromBucket(const SGBucket& bucket, const PathList& scenery_paths); + + Orthophoto(const Texture2DRef& texture, const OrthophotoBounds& bbox) { _texture = texture; _bbox = bbox; } + Orthophoto(const std::vector& orthophotos); + + Texture2DRef getTexture() const { return _texture; }; + 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); + + /** + * 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 diff -Nru simgear-2020.3.16+dfsg/simgear-version simgear-2020.3.18+dfsg/simgear-version --- simgear-2020.3.16+dfsg/simgear-version 2022-10-18 07:27:02.000000000 +0000 +++ simgear-2020.3.18+dfsg/simgear-version 2023-03-14 07:49:42.000000000 +0000 @@ -1 +1 @@ -2020.3.16 +2020.3.18