Rebase photoscenery patch on next

This commit is contained in:
Nathaniel Warner
2020-11-23 10:35:09 -08:00
committed by Scott Giese
parent 59809ab61f
commit d4d149f3c0
8 changed files with 605 additions and 2 deletions

View File

@@ -150,6 +150,7 @@ private:
std::vector<SGVec4f> colors; // color list
std::vector<SGVec3f> normals; // normal list
std::vector<SGVec2f> texcoords; // texture coordinate list
std::vector<SGVec2f> overlaycoords; // overlay texture coordinate list
std::vector<float> va_flt; // vertex attribute list (floats)
std::vector<int> va_int; // vertex attribute list (ints)
@@ -226,6 +227,9 @@ public:
inline const std::vector<SGVec2f>& get_texcoords() const { return texcoords; }
inline void set_texcoords( const std::vector<SGVec2f>& t ) { texcoords = t; }
inline const std::vector<SGVec2f>& get_overlaycoords() const { return overlaycoords; }
inline void set_overlaycoords( const std::vector<SGVec2f>& t ) { overlaycoords = t; }
// Points API
bool add_point( const SGBinObjectPoint& pt );

View File

@@ -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<osg::Vec3Array> normals = new osg::Vec3Array;
osg::ref_ptr<osg::Vec2Array> priTexCoords = new osg::Vec2Array;
osg::ref_ptr<osg::Vec2Array> secTexCoords = new osg::Vec2Array;
osg::ref_ptr<osg::Vec2Array> overlayCoords = new osg::Vec2Array;
osg::ref_ptr<osg::Vec4Array> 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<unsigned> 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]]);
}

View File

@@ -58,6 +58,7 @@ public:
const std::vector<SGVec3d>& vertices(obj.get_wgs84_nodes());
const std::vector<SGVec3f>& normals(obj.get_normals());
const std::vector<SGVec2f>& texCoords(obj.get_texcoords());
const std::vector<SGVec2f>& 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<SGVec3d>& vertices(obj.get_wgs84_nodes());
const std::vector<SGVec3f>& normals(obj.get_normals());
const std::vector<SGVec2f>& texCoords(obj.get_texcoords());
const std::vector<SGVec2f>& 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;
}

View File

@@ -26,10 +26,18 @@
# include <simgear_config.h>
#endif
#include <osgDB/FileNameUtils>
#include <osgDB/FileUtils>
#include <osgDB/ReadFile>
#include <osg/Texture2D>
#include <osg/TexEnv>
#include "obj.hxx"
#include <simgear/debug/logstream.hxx>
#include <simgear/io/sg_binobj.hxx>
#include <simgear/bucket/newbucket.hxx>
#include <simgear/scene/util/OrthophotoManager.hxx>
#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<SGVec3d> nodes = tile.get_wgs84_nodes();
std::vector<SGVec2f> satellite_overlay_coords;
osg::ref_ptr<Orthophoto> orthophoto = nullptr;
if (usePhotoscenery) {
try {
const long index = lexical_cast<long>(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<SGVec3d> 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<SGVec3f> 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<osg::Uniform> 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);

View File

@@ -29,6 +29,8 @@
# error This library requires C++
#endif
#include <boost/lexical_cast.hpp>
#include <simgear/compiler.h>
#include <string>
@@ -38,6 +40,9 @@
using std::string;
using boost::lexical_cast;
using boost::bad_lexical_cast;
class SGMaterialLib;
namespace simgear {
class SGReaderWriterOptions;

View File

@@ -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

View File

@@ -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; layer<image->r(); ++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<OrthophotoRef>& 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<osg::Texture2D> Orthophoto::getTexture() {
osg::ref_ptr<osg::Texture2D> 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<OrthophotoManager>::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<SGVec3d>& nodes, const SGVec3d& center) {
std::unordered_map<long, bool> orthophotos_attempted;
std::vector<OrthophotoRef> 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);
}
}

View File

@@ -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 <unordered_map>
#include <osg/Referenced>
#include <osg/Image>
#include <osg/Texture2D>
#include <osgDB/ReaderWriter>
#include <osgDB/ReadFile>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/bucket/newbucket.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/SGLimits.hxx>
#include "SGSceneFeatures.hxx"
#include "OsgSingleton.hxx"
namespace simgear {
using ImageRef = osg::ref_ptr<osg::Image>;
class Orthophoto;
using OrthophotoRef = osg::ref_ptr<Orthophoto>;
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<OrthophotoRef>& orthophotos, const OrthophotoBounds& needed_bbox);
osg::ref_ptr<osg::Texture2D> getTexture();
OrthophotoBounds getBbox() const { return _bbox; };
};
class OrthophotoManager : public osg::Referenced {
private:
std::unordered_map<long, OrthophotoRef> _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<SGVec3d>& nodes, const SGVec3d& center);
};
}
#endif