From 945a9e0ac236716b1f490e1ccc4a494cfbd00403 Mon Sep 17 00:00:00 2001 From: Mathias Froehlich Date: Sun, 11 Dec 2011 21:11:18 +0100 Subject: [PATCH] Implement osg native scenery paging. Add an in memory osg scenery loader that provides paged scenery loading using PagedLOD nodes. --- simgear/scene/tgdb/BucketBox.hxx | 568 +++++++++++++++++++++++++ simgear/scene/tgdb/BucketBoxTest.cxx | 65 +++ simgear/scene/tgdb/CMakeLists.txt | 17 + simgear/scene/tgdb/ReaderWriterSPT.cxx | 243 +++++++++++ simgear/scene/tgdb/ReaderWriterSPT.hxx | 48 +++ simgear/scene/tgdb/TileEntry.cxx | 4 + 6 files changed, 945 insertions(+) create mode 100644 simgear/scene/tgdb/BucketBox.hxx create mode 100644 simgear/scene/tgdb/BucketBoxTest.cxx create mode 100644 simgear/scene/tgdb/ReaderWriterSPT.cxx create mode 100644 simgear/scene/tgdb/ReaderWriterSPT.hxx diff --git a/simgear/scene/tgdb/BucketBox.hxx b/simgear/scene/tgdb/BucketBox.hxx new file mode 100644 index 00000000..8e67f291 --- /dev/null +++ b/simgear/scene/tgdb/BucketBox.hxx @@ -0,0 +1,568 @@ +// BucketBox.cxx -- Helper for on demand database paging. +// +// Copyright (C) 2010 - 2011 Mathias Froehlich +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program 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 +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#ifndef _BUCKETBOX_HXX +#define _BUCKETBOX_HXX + +#include +#include +#include + +#include +#include + +namespace simgear { + +#define Elements(x) (sizeof(x)/sizeof((x)[0])) + +// 3*5*3 * 8 = 360 +static const unsigned _lonFactors[] = { 3, 5, 3, 2, 2, 2, /* sub degree */ 2, 2, 2 }; +// 5*3*3 * 4 = 180 +static const unsigned _latFactors[] = { 5, 3, 3, 2, 2, /* sub degree */ 2, 2, 2, 1 }; + +static unsigned product(const unsigned* factors, unsigned count) +{ + unsigned index = 1; + while (count--) + index *= *factors++; + return index; +} + +/// /Rectangular/ sub part of the earths surface. +/// Stored with offset point in lon/lat and width and height. +/// The values are stored in a fixed point format having 3 fractional +/// bits which matches the SGBuckets maximum tile resolution. +/// +/// Notable /design/ decision: +/// * The longitude maps to the interval [0,360[ which appears to be +/// counter productive for the file/directory names and the +/// texture coordinates which map [-180,180[. +/// The reason is that the buckets at 89deg longitude are 8deg +/// latitude width. So there is a bunch of buckets that range from +/// [176, -184[ longitude. So the wrap happens at 0deg instead +/// of 180deg since we have a cut edge for all latitudes. +/// * This is not meant to be an API class for simgear. This is +/// just an internal tool that I would like to keep in the SPT loader. +/// But I want to have coverage somehow tested with the usual unit +/// tests, which is the reason to have this file split out. +/// +class BucketBox { +public: + BucketBox() + { _offset[0] = 0; _offset[1] = 0; _size[0] = 0; _size[1] = 0; } + BucketBox(double lon, double lat, double width, double height) + { + _offset[0] = _longitudeDegToOffset(lon); + _offset[1] = _latitudeDegToOffset(lat); + _size[0] = _degToSize(width); + _size[1] = _degToSize(height); + } + BucketBox(const BucketBox& bucketBox) + { + _offset[0] = bucketBox._offset[0]; + _offset[1] = bucketBox._offset[1]; + _size[0] = bucketBox._size[0]; + _size[1] = bucketBox._size[1]; + } + + BucketBox& operator=(const BucketBox& bucketBox) + { + _offset[0] = bucketBox._offset[0]; + _offset[1] = bucketBox._offset[1]; + _size[0] = bucketBox._size[0]; + _size[1] = bucketBox._size[1]; + return *this; + } + + bool empty() const + { return _size[0] == 0 || _size[1] == 0; } + + unsigned getOffset(unsigned i) const + { return _offset[i]; } + void setOffset(unsigned i, unsigned offset) + { _offset[i] = offset; } + + unsigned getSize(unsigned i) const + { return _size[i]; } + void setSize(unsigned i, unsigned size) + { _size[i] = size; } + + double getLongitudeDeg() const + { return _offsetToLongitudeDeg(_offset[0]); } + void setLongitudeDeg(double lon) + { _offset[0] = _longitudeDegToOffset(lon); } + + double getLatitudeDeg() const + { return _offsetToLatitudeDeg(_offset[1]); } + void setLatitudeDeg(double lat) + { _offset[1] = _latitudeDegToOffset(lat); } + + double getWidthDeg() const + { return _sizeToDeg(_size[0]); } + void setWidthDeg(double width) + { _size[0] = _degToSize(width); } + + double getHeightDeg() const + { return _sizeToDeg(_size[1]); } + void setHeightDeg(double height) + { _size[1] = _degToSize(height); } + + bool getWidthIsBucketSize() const + { + if (_size[0] <= _bucketSpanAtOffset(_offset[1])) + return true; + return _size[0] <= _bucketSpanAtOffset(_offset[1] + _size[1] - 1); + } + + bool getHeightIsBucketSize() const + { return _size[1] == 1; } + bool getIsBucketSize() const + { return getHeightIsBucketSize() && getWidthIsBucketSize(); } + + SGBucket getBucket() const + { + // left align longitude offsets + unsigned offset = _offset[0] - _offset[0] % _bucketSpanAtOffset(_offset[1]); + return SGBucket(_offsetToLongitudeDeg(offset), _offsetToLatitudeDeg(_offset[1])); + } + + BucketBox getSubBoxHeight(unsigned j, unsigned level) const + { + assert(0 < level); + BucketBox box; + unsigned plat = product(_latFactors + level, Elements(_latFactors) - level); + unsigned plat1 = plat*_latFactors[level - 1]; + box._offset[0] = _offset[0]; + box._offset[1] = _offset[1] - _offset[1] % plat1 + j*plat; + box._size[0] = _size[0]; + box._size[1] = plat; + + return box; + } + + BucketBox getSubBoxWidth(unsigned i, unsigned level) const + { + assert(0 < level); + BucketBox box; + unsigned plon = product(_lonFactors + level, Elements(_lonFactors) - level); + unsigned plon1 = plon*_lonFactors[level - 1]; + box._offset[0] = _offset[0] - _offset[0] % plon1 + i*plon; + box._offset[1] = _offset[1]; + box._size[0] = plon; + box._size[1] = _size[1]; + + box._offset[0] = _normalizeLongitude(box._offset[0]); + + return box; + } + + unsigned getWidthLevel() const + { return _getLevel(_lonFactors, Elements(_lonFactors), _offset[0], _offset[0] + _size[0]); } + unsigned getHeightLevel() const + { return _getLevel(_latFactors, Elements(_latFactors), _offset[1], _offset[1] + _size[1]); } + + unsigned getWidthIncrement(unsigned level) const + { + level = SGMisc::clip(level, 5, Elements(_lonFactors)); + return product(_lonFactors + level, Elements(_lonFactors) - level); + } + unsigned getHeightIncrement(unsigned level) const + { + level = SGMisc::clip(level, 5, Elements(_latFactors)); + return product(_latFactors + level, Elements(_latFactors) - level); + } + + unsigned getStartLevel() const + { + if (getWidthIsBucketSize()) + return getHeightLevel(); + return std::min(getWidthLevel(), getHeightLevel()); + } + + SGSpheref getBoundingSphere() const + { + SGSpheref sphere; + unsigned incx = 10*8; + for (unsigned i = 0; incx != 0; i += incx) { + unsigned incy = 10*8; + for (unsigned j = 0; incy != 0; j += incy) { + sphere.expandBy(SGVec3f::fromGeod(_offsetToGeod(_offset[0] + i, _offset[1] + j, -1000))); + sphere.expandBy(SGVec3f::fromGeod(_offsetToGeod(_offset[0] + i, _offset[1] + j, 10000))); + incy = std::min(incy, _size[1] - j); + } + incx = std::min(incx, _size[0] - i); + } + return SGSpheref(sphere.getCenter(), sphere.getRadius() + 5000); + } + + // Split the current box into up to two boxes that do not cross the 360 deg border. + unsigned periodicSplit(BucketBox bucketBoxList[2]) const + { + if (empty()) + return 0; + + bucketBoxList[0] = *this; + bucketBoxList[0]._offset[0] = _normalizeLongitude(bucketBoxList[0]._offset[0]); + if (bucketBoxList[0]._offset[0] + bucketBoxList[0]._size[0] <= 360*8) + return 1; + + bucketBoxList[1] = bucketBoxList[0]; + bucketBoxList[0]._size[0] = 360*8 - bucketBoxList[0]._offset[0]; + + bucketBoxList[1]._offset[0] = 0; + bucketBoxList[1]._size[0] = _size[0] - bucketBoxList[0]._size[0]; + + return 2; + } + + unsigned getSubDivision(BucketBox bucketBoxList[], unsigned bucketBoxListSize) const + { + unsigned numTiles = 0; + + // Quad tree like structure in x and y direction + unsigned widthLevel = getWidthLevel(); + unsigned heightLevel = getHeightLevel(); + + unsigned level; + if (getWidthIsBucketSize()) { + level = heightLevel; + } else { + level = std::min(widthLevel, heightLevel); + } + for (unsigned j = 0; j < _latFactors[level]; ++j) { + BucketBox heightSplitBox = getSubBoxHeight(j, level + 1); + + heightSplitBox = _intersection(*this, heightSplitBox); + if (heightSplitBox.empty()) + continue; + + if (heightSplitBox.getWidthIsBucketSize()) { + bucketBoxList[numTiles++] = heightSplitBox; + assert(numTiles <= bucketBoxListSize); + } else { + for (unsigned i = 0; i < _lonFactors[widthLevel]; ++i) { + BucketBox childBox = _intersection(heightSplitBox, heightSplitBox.getSubBoxWidth(i, widthLevel + 1)); + if (childBox.empty()) + continue; + + bucketBoxList[numTiles++] = childBox; + assert(numTiles <= bucketBoxListSize); + } + } + } + return numTiles; + } + + unsigned getTileTriangles(unsigned i, unsigned j, unsigned width, unsigned height, + SGVec3f points[6], SGVec3f normals[6], SGVec2f texCoords[6]) const + { + unsigned numPoints = 0; + + unsigned x0 = _offset[0] + i; + unsigned x1 = x0 + width; + + unsigned y0 = _offset[1] + j; + unsigned y1 = y0 + height; + + SGGeod p00 = _offsetToGeod(x0, y0, 0); + SGVec3f v00 = SGVec3f::fromGeod(p00); + SGVec3f n00 = SGQuatf::fromLonLat(p00).backTransform(SGVec3f(0, 0, -1)); + SGVec2f t00(x0*1.0/(360*8) + 0.5, y0*1.0/(180*8)); + + SGGeod p10 = _offsetToGeod(x1, y0, 0); + SGVec3f v10 = SGVec3f::fromGeod(p10); + SGVec3f n10 = SGQuatf::fromLonLat(p10).backTransform(SGVec3f(0, 0, -1)); + SGVec2f t10(x1*1.0/(360*8) + 0.5, y0*1.0/(180*8)); + + SGGeod p11 = _offsetToGeod(x1, y1, 0); + SGVec3f v11 = SGVec3f::fromGeod(p11); + SGVec3f n11 = SGQuatf::fromLonLat(p11).backTransform(SGVec3f(0, 0, -1)); + SGVec2f t11(x1*1.0/(360*8) + 0.5, y1*1.0/(180*8)); + + SGGeod p01 = _offsetToGeod(x0, y1, 0); + SGVec3f v01 = SGVec3f::fromGeod(p01); + SGVec3f n01 = SGQuatf::fromLonLat(p01).backTransform(SGVec3f(0, 0, -1)); + SGVec2f t01(x0*1.0/(360*8) + 0.5, y1*1.0/(180*8)); + + if (y0 != 0) { + points[numPoints] = v00; + normals[numPoints] = n00; + texCoords[numPoints] = t00; + ++numPoints; + + points[numPoints] = v10; + normals[numPoints] = n10; + texCoords[numPoints] = t10; + ++numPoints; + + points[numPoints] = v01; + normals[numPoints] = n01; + texCoords[numPoints] = t01; + ++numPoints; + } + if (y1 != 180*8) { + points[numPoints] = v11; + normals[numPoints] = n11; + texCoords[numPoints] = t11; + ++numPoints; + + points[numPoints] = v01; + normals[numPoints] = n01; + texCoords[numPoints] = t01; + ++numPoints; + + points[numPoints] = v10; + normals[numPoints] = n10; + texCoords[numPoints] = t10; + ++numPoints; + } + return numPoints; + } + +private: + static unsigned _normalizeLongitude(unsigned offset) + { return offset - (360*8)*(offset/(360*8)); } + + static unsigned _longitudeDegToOffset(double lon) + { + lon = SGMiscd::normalizePeriodic(0, 360, lon); + unsigned offset = (unsigned)(8*lon + 0.5); + return _normalizeLongitude(offset); + } + static double _offsetToLongitudeDeg(unsigned offset) + { + if (180*8 <= offset) + return offset*0.125 - 360; + else + return offset*0.125; + } + + static unsigned _latitudeDegToOffset(double lat) + { + if (lat < -90) + return 0; + unsigned offset = (unsigned)(8*(lat + 90) + 0.5); + if (8*180 < offset) + return 8*180; + return offset; + } + static double _offsetToLatitudeDeg(unsigned offset) + { return offset*0.125 - 90; } + + static unsigned _degToSize(double deg) + { + if (deg <= 0) + return 0; + return (unsigned)(8*deg + 0.5); + } + static double _sizeToDeg(unsigned size) + { return size*0.125; } + + static unsigned _bucketSpanAtOffset(unsigned offset) + { return (unsigned)(8*sg_bucket_span(_offsetToLatitudeDeg(offset) + 0.0625) + 0.5); } + + static SGGeod _offsetToGeod(unsigned offset0, unsigned offset1, double elev) + { return SGGeod::fromDegM(_offsetToLongitudeDeg(offset0), _offsetToLatitudeDeg(offset1), elev); } + + static unsigned _getLevel(const unsigned factors[], unsigned nFactors, unsigned begin, unsigned end) + { + unsigned rbegin = end - 1; + for (; 0 < nFactors;) { + if (begin == rbegin) + break; + --nFactors; + begin /= factors[nFactors]; + rbegin /= factors[nFactors]; + } + + return nFactors; + } + + static BucketBox _intersection(const BucketBox& box0, const BucketBox& box1) + { + BucketBox box; + for (unsigned i = 0; i < 2; ++i) { + box._offset[i] = std::max(box0._offset[i], box1._offset[i]); + unsigned m = std::min(box0._offset[i] + box0._size[i], box1._offset[i] + box1._size[i]); + if (m <= box._offset[i]) + box._size[i] = 0; + else + box._size[i] = m - box._offset[i]; + } + + box._offset[0] = _normalizeLongitude(box._offset[0]); + + return box; + } + + unsigned _offset[2]; + unsigned _size[2]; +}; + +inline bool +operator==(const BucketBox& bucketBox0, const BucketBox& bucketBox1) +{ + if (bucketBox0.getOffset(0) != bucketBox1.getOffset(0)) + return false; + if (bucketBox0.getOffset(1) != bucketBox1.getOffset(1)) + return false; + if (bucketBox0.getSize(0) != bucketBox1.getSize(0)) + return false; + if (bucketBox0.getSize(1) != bucketBox1.getSize(1)) + return false; + return true; +} + +inline bool +operator!=(const BucketBox& bucketBox0, const BucketBox& bucketBox1) +{ return !operator==(bucketBox0, bucketBox1); } + +inline bool +operator<(const BucketBox& bucketBox0, const BucketBox& bucketBox1) +{ + if (bucketBox0.getOffset(0) < bucketBox1.getOffset(0)) return true; + else if (bucketBox1.getOffset(0) < bucketBox0.getOffset(0)) return false; + else if (bucketBox0.getOffset(1) < bucketBox1.getOffset(1)) return true; + else if (bucketBox1.getOffset(1) < bucketBox0.getOffset(1)) return false; + else if (bucketBox0.getSize(0) < bucketBox1.getSize(0)) return true; + else if (bucketBox1.getSize(0) < bucketBox0.getSize(0)) return false; + else return bucketBox0.getSize(1) < bucketBox1.getSize(1); +} + +inline bool +operator>(const BucketBox& bucketBox0, const BucketBox& bucketBox1) +{ return operator<(bucketBox1, bucketBox0); } + +inline bool +operator<=(const BucketBox& bucketBox0, const BucketBox& bucketBox1) +{ return !operator>(bucketBox0, bucketBox1); } + +inline bool +operator>=(const BucketBox& bucketBox0, const BucketBox& bucketBox1) +{ return !operator<(bucketBox0, bucketBox1); } + +/// Stream output operator. +/// Note that this is not only used for pretty printing but also for +/// generating the meta file names for on demand paging. +/// So, don't modify unless you know where else this is used. +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const BucketBox& bucketBox) +{ + double lon = bucketBox.getLongitudeDeg(); + if (lon < 0) + os << "w" << -lon; + else + os << "e" << lon; + + double lat = bucketBox.getLatitudeDeg(); + if (lat < -90) + lat = -90; + if (90 < lat) + lat = 90; + if (lat < 0) + os << "s" << -lat; + else + os << "n" << lat; + + os << "-" << bucketBox.getWidthDeg() << "x" << bucketBox.getHeightDeg(); + return os; +} + +/// Stream inout operator. +/// Note that this is used for reading the meta file names for on demand paging. +/// So, don't modify unless you know where else this is used. +template +std::basic_istream& +operator>>(std::basic_istream& is, BucketBox& bucketBox) +{ + char c; + is >> c; + if (is.fail()) + return is; + + int sign = 0; + if (c == 'w') + sign = -1; + else if (c == 'e') + sign = 1; + else { + is.setstate(std::ios::failbit | is.rdstate()); + return is; + } + + double num; + is >> num; + if (is.fail()) { + is.setstate(std::ios::failbit | is.rdstate()); + return is; + } + bucketBox.setLongitudeDeg(sign*num); + + is >> c; + if (is.fail()) + return is; + + sign = 0; + if (c == 's') + sign = -1; + else if (c == 'n') + sign = 1; + else { + is.setstate(std::ios::failbit | is.rdstate()); + return is; + } + + is >> num; + if (is.fail()) + return is; + bucketBox.setLatitudeDeg(sign*num); + + is >> c; + if (is.fail()) + return is; + if (c != '-'){ + is.setstate(std::ios::failbit | is.rdstate()); + return is; + } + + is >> num; + if (is.fail()) + return is; + bucketBox.setWidthDeg(SGMiscd::min(num, 360)); + + is >> c; + if (is.fail()) + return is; + if (c != 'x'){ + is.setstate(std::ios::failbit | is.rdstate()); + return is; + } + + is >> num; + if (is.fail()) + return is; + bucketBox.setHeightDeg(SGMiscd::min(num, 90 - bucketBox.getLatitudeDeg())); + + return is; +} + +} // namespace simgear + +#endif diff --git a/simgear/scene/tgdb/BucketBoxTest.cxx b/simgear/scene/tgdb/BucketBoxTest.cxx new file mode 100644 index 00000000..460f9967 --- /dev/null +++ b/simgear/scene/tgdb/BucketBoxTest.cxx @@ -0,0 +1,65 @@ +// ReaderWriterSPT.cxx -- Provide a paged database for flightgear scenery. +// +// Copyright (C) 2010 - 2011 Mathias Froehlich +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program 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 +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +#include "BucketBox.hxx" + +namespace simgear { + +static std::set boxes; +static std::set buckets; + +void createTestTree(const BucketBox& bucketBox) +{ + if (!boxes.insert(bucketBox).second) { + std::cerr << "Duplicate BucketBox: " << bucketBox << std::endl; + exit(EXIT_FAILURE); + } + + if (bucketBox.getIsBucketSize()) { + // We have a leaf node + if (!buckets.insert(bucketBox.getBucket().gen_index()).second) { + std::cerr << "Duplicate BucketBox: " << bucketBox << std::endl; + exit(EXIT_FAILURE); + } + } else { + BucketBox bucketBoxList[100]; + unsigned numTiles = bucketBox.getSubDivision(bucketBoxList, 100); + + for (unsigned i = 0; i < numTiles; ++i) { + createTestTree(bucketBoxList[i]); + } + } +} + +} + +int +main() +{ + simgear::createTestTree(simgear::BucketBox(0, -90, 360, 180)); + return EXIT_SUCCESS; +} diff --git a/simgear/scene/tgdb/CMakeLists.txt b/simgear/scene/tgdb/CMakeLists.txt index 59d096ce..042ea6e8 100644 --- a/simgear/scene/tgdb/CMakeLists.txt +++ b/simgear/scene/tgdb/CMakeLists.txt @@ -2,6 +2,7 @@ include (SimGearComponent) set(HEADERS GroundLightManager.hxx + ReaderWriterSPT.hxx ReaderWriterSTG.hxx SGDirectionalLightBin.hxx SGLightBin.hxx @@ -24,6 +25,7 @@ set(HEADERS set(SOURCES GroundLightManager.cxx + ReaderWriterSPT.cxx ReaderWriterSTG.cxx SGOceanTile.cxx SGReaderWriterBTG.cxx @@ -39,3 +41,18 @@ set(SOURCES ) simgear_scene_component(tgdb scene/tgdb "${SOURCES}" "${HEADERS}") + +if(ENABLE_TESTS) + + if (SIMGEAR_SHARED) + set(TEST_LIBS SimGearCore) + else() + set(TEST_LIBS sgbucket sgmisc sgmath sgdebug) + endif() + + + add_executable(BucketBoxTest BucketBoxTest.cxx) + target_link_libraries(BucketBoxTest ${TEST_LIBS}) + add_test(BucketBoxTest ${EXECUTABLE_OUTPUT_PATH}/BucketBoxTest) + +endif(ENABLE_TESTS) diff --git a/simgear/scene/tgdb/ReaderWriterSPT.cxx b/simgear/scene/tgdb/ReaderWriterSPT.cxx new file mode 100644 index 00000000..09edea54 --- /dev/null +++ b/simgear/scene/tgdb/ReaderWriterSPT.cxx @@ -0,0 +1,243 @@ +// ReaderWriterSPT.cxx -- Provide a paged database for flightgear scenery. +// +// Copyright (C) 2010 - 2011 Mathias Froehlich +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program 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 +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "ReaderWriterSPT.hxx" + +#include + +#include +#include +#include + +#include +#include + +#include "BucketBox.hxx" + +namespace simgear { + +ReaderWriterSPT::ReaderWriterSPT() +{ + supportsExtension("spt", "SimGear paged terrain meta database."); +} + +ReaderWriterSPT::~ReaderWriterSPT() +{ +} + +const char* +ReaderWriterSPT::className() const +{ + return "simgear::ReaderWriterSPT"; +} + +osgDB::ReaderWriter::ReadResult +ReaderWriterSPT::readObject(const std::string& fileName, const osgDB::Options* options) const +{ + if (fileName != "state.spt") + return ReadResult(osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND); + + osg::StateSet* stateSet = new osg::StateSet; + stateSet->setAttributeAndModes(new osg::CullFace); + + std::string imageFileName = options->getPluginStringData("SimGear::FG_WORLD_TEXTURE"); + if (osg::Image* image = osgDB::readImageFile(imageFileName, options)) { + osg::Texture2D* texture = new osg::Texture2D; + texture->setImage(image); + texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT); + texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP); + stateSet->setTextureAttributeAndModes(0, texture); + } + + return stateSet; +} + +osgDB::ReaderWriter::ReadResult +ReaderWriterSPT::readNode(const std::string& fileName, const osgDB::Options* options) const +{ + // The file name without path and without the spt extension + std::string strippedFileName = osgDB::getStrippedName(fileName); + if (strippedFileName == "earth") + return createTree(BucketBox(0, -90, 360, 180), options, true); + + std::stringstream ss(strippedFileName); + BucketBox bucketBox; + ss >> bucketBox; + if (ss.fail()) + return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND; + + BucketBox bucketBoxList[2]; + unsigned bucketBoxListSize = bucketBox.periodicSplit(bucketBoxList); + if (bucketBoxListSize == 0) + return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND; + + if (bucketBoxListSize == 1) + return createTree(bucketBoxList[0], options, true); + + assert(bucketBoxListSize == 2); + osg::ref_ptr group = new osg::Group; + group->addChild(createTree(bucketBoxList[0], options, true)); + group->addChild(createTree(bucketBoxList[1], options, true)); + return group.release(); +} + +osg::Node* +ReaderWriterSPT::createTree(const BucketBox& bucketBox, const osgDB::Options* options, bool topLevel) const +{ + if (bucketBox.getIsBucketSize()) { + return createPagedLOD(bucketBox, options); + } else if (!topLevel && bucketBox.getStartLevel() == 4) { + // We want an other level of indirection for paging + return createPagedLOD(bucketBox, options); + } else { + BucketBox bucketBoxList[100]; + unsigned numTiles = bucketBox.getSubDivision(bucketBoxList, 100); + if (numTiles == 0) + return 0; + + if (numTiles == 1) + return createTree(bucketBoxList[0], options, false); + + osg::ref_ptr group = new osg::Group; + for (unsigned i = 0; i < numTiles; ++i) { + osg::Node* node = createTree(bucketBoxList[i], options, false); + if (!node) + continue; + group->addChild(node); + } + if (!group->getNumChildren()) + return 0; + + return group.release(); + } +} + +osg::Node* +ReaderWriterSPT::createPagedLOD(const BucketBox& bucketBox, const osgDB::Options* options) const +{ + osg::PagedLOD* pagedLOD = new osg::PagedLOD; + + pagedLOD->setCenterMode(osg::PagedLOD::USER_DEFINED_CENTER); + SGSpheref sphere = bucketBox.getBoundingSphere(); + pagedLOD->setCenter(toOsg(sphere.getCenter())); + pagedLOD->setRadius(sphere.getRadius()); + + osg::ref_ptr localOptions; + localOptions = static_cast(options->clone(osg::CopyOp())); + pagedLOD->setDatabaseOptions(localOptions.get()); + + float range; + if (bucketBox.getIsBucketSize()) + range = 200e3; + else + range = 1e6; + + // Add the static sea level textured shell + if (osg::Node* tile = createSeaLevelTile(bucketBox, options)) + pagedLOD->addChild(tile, range, std::numeric_limits::max()); + + // Add the paged file name that creates the subtrees on demand + if (bucketBox.getIsBucketSize()) { + std::string fileName; + fileName = bucketBox.getBucket().gen_index_str() + std::string(".stg"); + pagedLOD->setFileName(pagedLOD->getNumChildren(), fileName); + } else { + std::stringstream ss; + ss << bucketBox << ".spt"; + pagedLOD->setFileName(pagedLOD->getNumChildren(), ss.str()); + } + pagedLOD->setRange(pagedLOD->getNumChildren(), 0.0, range); + + return pagedLOD; +} + +osg::Node* +ReaderWriterSPT::createSeaLevelTile(const BucketBox& bucketBox, const osgDB::Options* options) const +{ + if (options->getPluginStringData("SimGear::FG_EARTH") != "ON") + return 0; + + osg::Vec3Array* vertices = new osg::Vec3Array; + osg::Vec3Array* normals = new osg::Vec3Array; + osg::Vec2Array* texCoords = new osg::Vec2Array; + + unsigned widthLevel = bucketBox.getWidthLevel(); + unsigned heightLevel = bucketBox.getHeightLevel(); + + unsigned incx = bucketBox.getWidthIncrement(widthLevel + 2); + incx = std::min(incx, bucketBox.getSize(0)); + for (unsigned i = 0; incx != 0;) { + unsigned incy = bucketBox.getHeightIncrement(heightLevel + 2); + incy = std::min(incy, bucketBox.getSize(1)); + for (unsigned j = 0; incy != 0;) { + SGVec3f v[6], n[6]; + SGVec2f t[6]; + unsigned num = bucketBox.getTileTriangles(i, j, incx, incy, v, n, t); + for (unsigned k = 0; k < num; ++k) { + vertices->push_back(toOsg(v[k])); + normals->push_back(toOsg(n[k])); + texCoords->push_back(toOsg(t[k])); + } + j += incy; + incy = std::min(incy, bucketBox.getSize(1) - j); + } + i += incx; + incx = std::min(incx, bucketBox.getSize(0) - i); + } + + osg::Vec4Array* colors = new osg::Vec4Array; + colors->push_back(osg::Vec4(1, 1, 1, 1)); + + osg::Geometry* geometry = new osg::Geometry; + geometry->setVertexArray(vertices); + geometry->setNormalArray(normals); + geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX); + geometry->setColorArray(colors); + geometry->setColorBinding(osg::Geometry::BIND_OVERALL); + geometry->setTexCoordArray(0, texCoords); + + geometry->addPrimitiveSet(new osg::DrawArrays(osg::DrawArrays::TRIANGLES, 0, vertices->size())); + + osg::Geode* geode = new osg::Geode; + geode->addDrawable(geometry); + geode->setStateSet(getLowLODStateSet(options)); + + return geode; +} + +osg::StateSet* +ReaderWriterSPT::getLowLODStateSet(const osgDB::Options* options) const +{ + osg::ref_ptr localOptions; + localOptions = static_cast(options->clone(osg::CopyOp())); + localOptions->setObjectCacheHint(osgDB::Options::CACHE_ALL); + + osg::ref_ptr object = osgDB::readObjectFile("state.spt", localOptions.get()); + if (!dynamic_cast(object.get())) + return 0; + + return static_cast(object.release()); +} + +} // namespace simgear + diff --git a/simgear/scene/tgdb/ReaderWriterSPT.hxx b/simgear/scene/tgdb/ReaderWriterSPT.hxx new file mode 100644 index 00000000..c4c823d7 --- /dev/null +++ b/simgear/scene/tgdb/ReaderWriterSPT.hxx @@ -0,0 +1,48 @@ +// ReaderWriterSPT.cxx -- Provide a paged database for flightgear scenery. +// +// Copyright (C) 2010 - 2011 Mathias Froehlich +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program 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 +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#ifndef _READERWRITERSPT_HXX +#define _READERWRITERSPT_HXX + +#include + +namespace simgear { + +class BucketBox; + +class ReaderWriterSPT : public osgDB::ReaderWriter { +public: + ReaderWriterSPT(); + virtual ~ReaderWriterSPT(); + + virtual const char* className() const; + + virtual osgDB::ReaderWriter::ReadResult readObject(const std::string& fileName, const osgDB::Options* options) const; + virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& fileName, const osgDB::Options* options) const; + +protected: + osg::Node* createTree(const BucketBox& bucketBox, const osgDB::Options* options, bool topLevel) const; + osg::Node* createPagedLOD(const BucketBox& bucketBox, const osgDB::Options* options) const; + osg::Node* createSeaLevelTile(const BucketBox& bucketBox, const osgDB::Options* options) const; + osg::StateSet* getLowLODStateSet(const osgDB::Options* options) const; +}; + +} // namespace simgear + +#endif diff --git a/simgear/scene/tgdb/TileEntry.cxx b/simgear/scene/tgdb/TileEntry.cxx index 1c219bfb..a04b0164 100644 --- a/simgear/scene/tgdb/TileEntry.cxx +++ b/simgear/scene/tgdb/TileEntry.cxx @@ -51,6 +51,7 @@ #include #include +#include "ReaderWriterSPT.hxx" #include "ReaderWriterSTG.hxx" #include "TileEntry.hxx" @@ -62,6 +63,9 @@ ModelLoadHelper *TileEntry::_modelLoader=0; namespace { osgDB::RegisterReaderWriterProxy g_readerWriterSTGProxy; ModelRegistryCallbackProxy g_stgCallbackProxy("stg"); + +osgDB::RegisterReaderWriterProxy g_readerWriterSPTProxy; +ModelRegistryCallbackProxy g_sptCallbackProxy("spt"); } -- 2.39.2