Implement osg native scenery paging.
Add an in memory osg scenery loader that provides paged scenery loading using PagedLOD nodes.
This commit is contained in:
parent
a92ea7f822
commit
945a9e0ac2
568
simgear/scene/tgdb/BucketBox.hxx
Normal file
568
simgear/scene/tgdb/BucketBox.hxx
Normal file
@ -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 <cassert>
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
|
||||
#include <simgear/bucket/newbucket.hxx>
|
||||
#include <simgear/math/SGGeometry.hxx>
|
||||
|
||||
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<unsigned>::clip(level, 5, Elements(_lonFactors));
|
||||
return product(_lonFactors + level, Elements(_lonFactors) - level);
|
||||
}
|
||||
unsigned getHeightIncrement(unsigned level) const
|
||||
{
|
||||
level = SGMisc<unsigned>::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<typename char_type, typename traits_type>
|
||||
std::basic_ostream<char_type, traits_type>&
|
||||
operator<<(std::basic_ostream<char_type, traits_type>& 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<typename char_type, typename traits_type>
|
||||
std::basic_istream<char_type, traits_type>&
|
||||
operator>>(std::basic_istream<char_type, traits_type>& 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
|
65
simgear/scene/tgdb/BucketBoxTest.cxx
Normal file
65
simgear/scene/tgdb/BucketBoxTest.cxx
Normal file
@ -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 <simgear_config.h>
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
|
||||
#include "BucketBox.hxx"
|
||||
|
||||
namespace simgear {
|
||||
|
||||
static std::set<BucketBox> boxes;
|
||||
static std::set<long> 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;
|
||||
}
|
@ -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)
|
||||
|
243
simgear/scene/tgdb/ReaderWriterSPT.cxx
Normal file
243
simgear/scene/tgdb/ReaderWriterSPT.cxx
Normal file
@ -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 <simgear_config.h>
|
||||
#endif
|
||||
|
||||
#include "ReaderWriterSPT.hxx"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <osg/CullFace>
|
||||
#include <osg/PagedLOD>
|
||||
#include <osg/Texture2D>
|
||||
|
||||
#include <osgDB/FileNameUtils>
|
||||
#include <osgDB/ReadFile>
|
||||
|
||||
#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<osg::Group> 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<osg::Group> 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<osgDB::Options> localOptions;
|
||||
localOptions = static_cast<osgDB::Options*>(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<float>::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<osgDB::Options> localOptions;
|
||||
localOptions = static_cast<osgDB::Options*>(options->clone(osg::CopyOp()));
|
||||
localOptions->setObjectCacheHint(osgDB::Options::CACHE_ALL);
|
||||
|
||||
osg::ref_ptr<osg::Object> object = osgDB::readObjectFile("state.spt", localOptions.get());
|
||||
if (!dynamic_cast<osg::StateSet*>(object.get()))
|
||||
return 0;
|
||||
|
||||
return static_cast<osg::StateSet*>(object.release());
|
||||
}
|
||||
|
||||
} // namespace simgear
|
||||
|
48
simgear/scene/tgdb/ReaderWriterSPT.hxx
Normal file
48
simgear/scene/tgdb/ReaderWriterSPT.hxx
Normal file
@ -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 <osgDB/ReaderWriter>
|
||||
|
||||
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
|
@ -51,6 +51,7 @@
|
||||
#include <simgear/scene/tgdb/obj.hxx>
|
||||
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
|
||||
|
||||
#include "ReaderWriterSPT.hxx"
|
||||
#include "ReaderWriterSTG.hxx"
|
||||
#include "TileEntry.hxx"
|
||||
|
||||
@ -62,6 +63,9 @@ ModelLoadHelper *TileEntry::_modelLoader=0;
|
||||
namespace {
|
||||
osgDB::RegisterReaderWriterProxy<ReaderWriterSTG> g_readerWriterSTGProxy;
|
||||
ModelRegistryCallbackProxy<LoadOnlyCallback> g_stgCallbackProxy("stg");
|
||||
|
||||
osgDB::RegisterReaderWriterProxy<ReaderWriterSPT> g_readerWriterSPTProxy;
|
||||
ModelRegistryCallbackProxy<LoadOnlyCallback> g_sptCallbackProxy("spt");
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user