1 // ReaderWriterSPT.cxx -- Provide a paged database for flightgear scenery.
3 // Copyright (C) 2010 - 2011 Mathias Froehlich
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 # include <simgear_config.h>
24 #include "ReaderWriterSPT.hxx"
28 #include <osg/CullFace>
29 #include <osg/PagedLOD>
30 #include <osg/Texture2D>
32 #include <osgDB/FileNameUtils>
33 #include <osgDB/ReadFile>
35 #include <simgear/scene/util/OsgMath.hxx>
37 #include "BucketBox.hxx"
41 // Cull away tiles that we watch from downside
42 struct ReaderWriterSPT::CullCallback : public osg::NodeCallback {
43 virtual ~CullCallback()
45 virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
47 const osg::BoundingSphere& nodeBound = node->getBound();
48 // If the bounding sphere of the node is empty, there is nothing to do
49 if (!nodeBound.valid())
52 // Culling away tiles that we look at from the downside.
53 // This is done by computing the maximum distance we can
54 // see something from the current eyepoint. If the sphere
55 // that is defined by this radius does no intersects the
56 // nodes sphere, then this tile is culled away.
57 // Computing this radius happens by two rectangular triangles:
58 // Let r be the view point. rmin is the minimum radius we find
59 // a ground surface we need to look above. rmax is the
60 // maximum object radius we expect any object.
69 // The distance from the eyepoint to the point
70 // where the line of sight is perpandicular to
71 // the radius vector with minimal height is
72 // d1 = sqrt(r^2 - rmin^2).
73 // The distance from the point where the line of sight
74 // is perpandicular to the radius vector with minimal height
75 // to the highest possible object on earth with radius rmax is
76 // d2 = sqrt(rmax^2 - rmin^2).
77 // So the maximum distance we can see something on the earth
78 // from a viewpoint r is
81 // This is the equatorial earth radius minus 450m,
82 // little lower than Dead Sea.
83 float rmin = 6378137 - 450;
84 float rmin2 = rmin*rmin;
85 // This is the equatorial earth radius plus 9000m,
86 // little higher than Mount Everest.
87 float rmax = 6378137 + 9000;
88 float rmax2 = rmax*rmax;
90 // Check if we are looking from below any ground
91 osg::Vec3 viewPoint = nv->getViewPoint();
92 // blow the viewpoint up to a spherical earth with equatorial radius:
93 osg::Vec3 sphericViewPoint = viewPoint;
94 sphericViewPoint[2] *= 1.0033641;
95 float r2 = sphericViewPoint.length2();
99 // Due to this line of sight computation, the visible tiles
100 // are limited to be within a sphere with radius d1 + d2.
101 float d1 = sqrtf(r2 - rmin2);
102 float d2 = sqrtf(rmax2 - rmin2);
103 // Note that we again base the sphere around elliptic view point,
104 // but use the radius from the spherical computation.
105 if (!nodeBound.intersects(osg::BoundingSphere(viewPoint, d1 + d2)))
112 ReaderWriterSPT::ReaderWriterSPT()
114 supportsExtension("spt", "SimGear paged terrain meta database.");
117 ReaderWriterSPT::~ReaderWriterSPT()
122 ReaderWriterSPT::className() const
124 return "simgear::ReaderWriterSPT";
127 osgDB::ReaderWriter::ReadResult
128 ReaderWriterSPT::readObject(const std::string& fileName, const osgDB::Options* options) const
130 // We get called with different extensions. To make sure search continues,
131 // we need to return FILE_NOT_HANDLED in this case.
132 if (osgDB::getLowerCaseFileExtension(fileName) != "spt")
133 return ReadResult(osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED);
134 if (fileName != "state.spt")
135 return ReadResult(osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND);
137 osg::StateSet* stateSet = new osg::StateSet;
138 stateSet->setAttributeAndModes(new osg::CullFace);
140 std::string imageFileName = options->getPluginStringData("SimGear::FG_WORLD_TEXTURE");
141 if (imageFileName.empty()) {
142 imageFileName = options->getPluginStringData("SimGear::FG_ROOT");
143 imageFileName = osgDB::concatPaths(imageFileName, "Textures");
144 imageFileName = osgDB::concatPaths(imageFileName, "Globe");
145 imageFileName = osgDB::concatPaths(imageFileName, "world.topo.bathy.200407.3x4096x2048.png");
147 if (osg::Image* image = osgDB::readImageFile(imageFileName, options)) {
148 osg::Texture2D* texture = new osg::Texture2D;
149 texture->setImage(image);
150 texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT);
151 texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP);
152 stateSet->setTextureAttributeAndModes(0, texture);
158 osgDB::ReaderWriter::ReadResult
159 ReaderWriterSPT::readNode(const std::string& fileName, const osgDB::Options* options) const
161 // The file name without path and without the spt extension
162 std::string strippedFileName = osgDB::getStrippedName(fileName);
163 if (strippedFileName == "earth")
164 return createTree(BucketBox(0, -90, 360, 180), options, true);
166 std::stringstream ss(strippedFileName);
170 return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
172 BucketBox bucketBoxList[2];
173 unsigned bucketBoxListSize = bucketBox.periodicSplit(bucketBoxList);
174 if (bucketBoxListSize == 0)
175 return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
177 if (bucketBoxListSize == 1)
178 return createTree(bucketBoxList[0], options, true);
180 assert(bucketBoxListSize == 2);
181 osg::ref_ptr<osg::Group> group = new osg::Group;
182 group->addChild(createTree(bucketBoxList[0], options, true));
183 group->addChild(createTree(bucketBoxList[1], options, true));
184 return group.release();
188 ReaderWriterSPT::createTree(const BucketBox& bucketBox, const osgDB::Options* options, bool topLevel) const
190 if (bucketBox.getIsBucketSize()) {
191 return createPagedLOD(bucketBox, options);
192 } else if (!topLevel && bucketBox.getStartLevel() == 4) {
193 // We want an other level of indirection for paging
194 return createPagedLOD(bucketBox, options);
196 BucketBox bucketBoxList[100];
197 unsigned numTiles = bucketBox.getSubDivision(bucketBoxList, 100);
202 return createTree(bucketBoxList[0], options, false);
204 osg::ref_ptr<osg::Group> group = new osg::Group;
205 for (unsigned i = 0; i < numTiles; ++i) {
206 osg::Node* node = createTree(bucketBoxList[i], options, false);
209 group->addChild(node);
211 if (!group->getNumChildren())
214 return group.release();
219 ReaderWriterSPT::createPagedLOD(const BucketBox& bucketBox, const osgDB::Options* options) const
221 osg::PagedLOD* pagedLOD = new osg::PagedLOD;
223 pagedLOD->setCenterMode(osg::PagedLOD::USER_DEFINED_CENTER);
224 SGSpheref sphere = bucketBox.getBoundingSphere();
225 pagedLOD->setCenter(toOsg(sphere.getCenter()));
226 pagedLOD->setRadius(sphere.getRadius());
228 pagedLOD->setCullCallback(new CullCallback);
230 osg::ref_ptr<osgDB::Options> localOptions;
231 localOptions = static_cast<osgDB::Options*>(options->clone(osg::CopyOp()));
232 pagedLOD->setDatabaseOptions(localOptions.get());
235 if (bucketBox.getIsBucketSize())
240 // Add the static sea level textured shell
241 if (osg::Node* tile = createSeaLevelTile(bucketBox, options))
242 pagedLOD->addChild(tile, range, std::numeric_limits<float>::max());
244 // Add the paged file name that creates the subtrees on demand
245 if (bucketBox.getIsBucketSize()) {
246 std::string fileName;
247 fileName = bucketBox.getBucket().gen_index_str() + std::string(".stg");
248 pagedLOD->setFileName(pagedLOD->getNumChildren(), fileName);
250 std::stringstream ss;
251 ss << bucketBox << ".spt";
252 pagedLOD->setFileName(pagedLOD->getNumChildren(), ss.str());
254 pagedLOD->setRange(pagedLOD->getNumChildren(), 0.0, range);
260 ReaderWriterSPT::createSeaLevelTile(const BucketBox& bucketBox, const osgDB::Options* options) const
262 if (options->getPluginStringData("SimGear::FG_EARTH") != "ON")
265 osg::Vec3Array* vertices = new osg::Vec3Array;
266 osg::Vec3Array* normals = new osg::Vec3Array;
267 osg::Vec2Array* texCoords = new osg::Vec2Array;
269 unsigned widthLevel = bucketBox.getWidthLevel();
270 unsigned heightLevel = bucketBox.getHeightLevel();
272 unsigned incx = bucketBox.getWidthIncrement(widthLevel + 2);
273 incx = std::min(incx, bucketBox.getSize(0));
274 for (unsigned i = 0; incx != 0;) {
275 unsigned incy = bucketBox.getHeightIncrement(heightLevel + 2);
276 incy = std::min(incy, bucketBox.getSize(1));
277 for (unsigned j = 0; incy != 0;) {
280 unsigned num = bucketBox.getTileTriangles(i, j, incx, incy, v, n, t);
281 for (unsigned k = 0; k < num; ++k) {
282 vertices->push_back(toOsg(v[k]));
283 normals->push_back(toOsg(n[k]));
284 texCoords->push_back(toOsg(t[k]));
287 incy = std::min(incy, bucketBox.getSize(1) - j);
290 incx = std::min(incx, bucketBox.getSize(0) - i);
293 osg::Vec4Array* colors = new osg::Vec4Array;
294 colors->push_back(osg::Vec4(1, 1, 1, 1));
296 osg::Geometry* geometry = new osg::Geometry;
297 geometry->setVertexArray(vertices);
298 geometry->setNormalArray(normals);
299 geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
300 geometry->setColorArray(colors);
301 geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
302 geometry->setTexCoordArray(0, texCoords);
304 geometry->addPrimitiveSet(new osg::DrawArrays(osg::DrawArrays::TRIANGLES, 0, vertices->size()));
306 osg::Geode* geode = new osg::Geode;
307 geode->addDrawable(geometry);
308 geode->setStateSet(getLowLODStateSet(options));
314 ReaderWriterSPT::getLowLODStateSet(const osgDB::Options* options) const
316 osg::ref_ptr<osgDB::Options> localOptions;
317 localOptions = static_cast<osgDB::Options*>(options->clone(osg::CopyOp()));
318 localOptions->setObjectCacheHint(osgDB::Options::CACHE_ALL);
320 osg::ref_ptr<osg::Object> object = osgDB::readObjectFile("state.spt", localOptions.get());
321 if (!dynamic_cast<osg::StateSet*>(object.get()))
324 return static_cast<osg::StateSet*>(object.release());
327 } // namespace simgear