]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tgdb/ReaderWriterSPT.cxx
Reset: Terrasync root can change, can be shutdown.
[simgear.git] / simgear / scene / tgdb / ReaderWriterSPT.cxx
1 // ReaderWriterSPT.cxx -- Provide a paged database for flightgear scenery.
2 //
3 // Copyright (C) 2010 - 2013  Mathias Froehlich
4 //
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.
9 //
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.
14 //
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.
18 //
19
20 #ifdef HAVE_CONFIG_H
21 #  include <simgear_config.h>
22 #endif
23
24 #include "ReaderWriterSPT.hxx"
25
26 #include <cassert>
27
28 #include <osg/CullFace>
29 #include <osg/PagedLOD>
30 #include <osg/MatrixTransform>
31 #include <osg/Texture2D>
32
33 #include <osgDB/FileNameUtils>
34 #include <osgDB/FileUtils>
35 #include <osgDB/ReadFile>
36
37 #include <simgear/scene/util/OsgMath.hxx>
38
39 #include "BucketBox.hxx"
40
41 namespace simgear {
42
43 // Cull away tiles that we watch from downside
44 struct ReaderWriterSPT::CullCallback : public osg::NodeCallback {
45     virtual ~CullCallback()
46     { }
47     virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
48     {
49         const osg::BoundingSphere& nodeBound = node->getBound();
50         // If the bounding sphere of the node is empty, there is nothing to do
51         if (!nodeBound.valid())
52             return;
53
54         // Culling away tiles that we look at from the downside.
55         // This is done by computing the maximum distance we can
56         // see something from the current eyepoint. If the sphere
57         // that is defined by this radius does no intersects the
58         // nodes sphere, then this tile is culled away.
59         // Computing this radius happens by two rectangular triangles:
60         // Let r be the view point. rmin is the minimum radius we find
61         // a ground surface we need to look above. rmax is the
62         // maximum object radius we expect any object.
63         //
64         //    d1   d2
65         //  x----x----x
66         //  r\  rmin /rmax
67         //    \  |  /
68         //     \ | /
69         //      \|/
70         //
71         // The distance from the eyepoint to the point
72         // where the line of sight is perpandicular to
73         // the radius vector with minimal height is
74         // d1 = sqrt(r^2 - rmin^2).
75         // The distance from the point where the line of sight
76         // is perpandicular to the radius vector with minimal height
77         // to the highest possible object on earth with radius rmax is 
78         // d2 = sqrt(rmax^2 - rmin^2).
79         // So the maximum distance we can see something on the earth
80         // from a viewpoint r is
81         // d = d1 + d2
82
83         // This is the equatorial earth radius minus 450m,
84         // little lower than Dead Sea.
85         float rmin = 6378137 - 450;
86         float rmin2 = rmin*rmin;
87         // This is the equatorial earth radius plus 9000m,
88         // little higher than Mount Everest.
89         float rmax = 6378137 + 9000;
90         float rmax2 = rmax*rmax;
91
92         // Check if we are looking from below any ground
93         osg::Vec3 viewPoint = nv->getViewPoint();
94         // blow the viewpoint up to a spherical earth with equatorial radius:
95         osg::Vec3 sphericViewPoint = viewPoint;
96         sphericViewPoint[2] *= 1.0033641;
97         float r2 = sphericViewPoint.length2();
98         if (r2 <= rmin2)
99             return;
100
101         // Due to this line of sight computation, the visible tiles
102         // are limited to be within a sphere with radius d1 + d2.
103         float d1 = sqrtf(r2 - rmin2);
104         float d2 = sqrtf(rmax2 - rmin2);
105         // Note that we again base the sphere around elliptic view point,
106         // but use the radius from the spherical computation.
107         if (!nodeBound.intersects(osg::BoundingSphere(viewPoint, d1 + d2)))
108             return;
109
110         traverse(node, nv);
111     }
112 };
113
114 struct ReaderWriterSPT::LocalOptions {
115     LocalOptions(const osgDB::Options* options) :
116         _options(options)
117     {
118         std::string pageLevelsString;
119         if (_options)
120             pageLevelsString = _options->getPluginStringData("SimGear::SPT_PAGE_LEVELS");
121
122         // Get the default if nothing given from outside
123         if (pageLevelsString.empty()) {
124             // We want an other level of indirection for paging
125             // Here we get about 12x12 deg tiles
126             _pageLevels.push_back(3);
127             // We want an other level of indirection for paging
128             // Here we get about 2x2 deg tiles
129             _pageLevels.push_back(5);
130             // We want an other level of indirection for paging
131             // Here we get about 0.5x0.5 deg tiles
132             _pageLevels.push_back(7);
133         } else {
134             // If configured from outside
135             std::stringstream ss(pageLevelsString);
136             while (ss.good()) {
137                 unsigned level = ~0u;
138                 ss >> level;
139                 _pageLevels.push_back(level);
140             }
141         }
142     }
143
144     bool isPageLevel(unsigned level) const
145     {
146         return std::find(_pageLevels.begin(), _pageLevels.end(), level) != _pageLevels.end();
147     }
148
149     std::string getLodPathForBucketBox(const BucketBox& bucketBox) const
150     {
151         std::stringstream ss;
152         ss << "LOD/";
153         for (std::vector<unsigned>::const_iterator i = _pageLevels.begin(); i != _pageLevels.end(); ++i) {
154             if (bucketBox.getStartLevel() <= *i)
155                 break;
156             ss << bucketBox.getParentBox(*i) << "/";
157         }
158         ss << bucketBox;
159         return ss.str();
160     }
161
162     float getRangeMultiplier() const
163     {
164         float rangeMultiplier = 2;
165         if (!_options)
166             return rangeMultiplier;
167         std::stringstream ss(_options->getPluginStringData("SimGear::SPT_RANGE_MULTIPLIER"));
168         ss >> rangeMultiplier;
169         return rangeMultiplier;
170     }
171
172     const osgDB::Options* _options;
173     std::vector<unsigned> _pageLevels;
174 };
175
176 ReaderWriterSPT::ReaderWriterSPT()
177 {
178     supportsExtension("spt", "SimGear paged terrain meta database.");
179 }
180
181 ReaderWriterSPT::~ReaderWriterSPT()
182 {
183 }
184
185 const char*
186 ReaderWriterSPT::className() const
187 {
188     return "simgear::ReaderWriterSPT";
189 }
190
191 osgDB::ReaderWriter::ReadResult
192 ReaderWriterSPT::readObject(const std::string& fileName, const osgDB::Options* options) const
193 {
194     // We get called with different extensions. To make sure search continues,
195     // we need to return FILE_NOT_HANDLED in this case.
196     if (osgDB::getLowerCaseFileExtension(fileName) != "spt")
197         return ReadResult(ReadResult::FILE_NOT_HANDLED);
198     if (fileName != "state.spt")
199         return ReadResult(ReadResult::FILE_NOT_FOUND);
200
201     osg::StateSet* stateSet = new osg::StateSet;
202     stateSet->setAttributeAndModes(new osg::CullFace);
203
204     std::string imageFileName = options->getPluginStringData("SimGear::FG_WORLD_TEXTURE");
205     if (imageFileName.empty()) {
206         imageFileName = options->getPluginStringData("SimGear::FG_ROOT");
207         imageFileName = osgDB::concatPaths(imageFileName, "Textures");
208         imageFileName = osgDB::concatPaths(imageFileName, "Globe");
209         imageFileName = osgDB::concatPaths(imageFileName, "world.topo.bathy.200407.3x4096x2048.png");
210     }
211     if (osg::Image* image = osgDB::readImageFile(imageFileName, options)) {
212         osg::Texture2D* texture = new osg::Texture2D;
213         texture->setImage(image);
214         texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT);
215         texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP);
216         stateSet->setTextureAttributeAndModes(0, texture);
217     }
218
219     return stateSet;
220 }
221
222 osgDB::ReaderWriter::ReadResult
223 ReaderWriterSPT::readNode(const std::string& fileName, const osgDB::Options* options) const
224 {
225     LocalOptions localOptions(options);
226
227     // The file name without path and without the spt extension
228     std::string strippedFileName = osgDB::getStrippedName(fileName);
229     if (strippedFileName == "earth")
230         return ReadResult(createTree(BucketBox(-180, -90, 360, 180), localOptions, true));
231
232     std::stringstream ss(strippedFileName);
233     BucketBox bucketBox;
234     ss >> bucketBox;
235     if (ss.fail())
236         return ReadResult::FILE_NOT_FOUND;
237
238     BucketBox bucketBoxList[2];
239     unsigned bucketBoxListSize = bucketBox.periodicSplit(bucketBoxList);
240     if (bucketBoxListSize == 0)
241         return ReadResult::FILE_NOT_FOUND;
242
243     if (bucketBoxListSize == 1)
244         return ReadResult(createTree(bucketBoxList[0], localOptions, true));
245
246     assert(bucketBoxListSize == 2);
247     osg::ref_ptr<osg::Group> group = new osg::Group;
248     group->addChild(createTree(bucketBoxList[0], localOptions, true));
249     group->addChild(createTree(bucketBoxList[1], localOptions, true));
250     return ReadResult(group);
251 }
252
253 osg::ref_ptr<osg::Node>
254 ReaderWriterSPT::createTree(const BucketBox& bucketBox, const LocalOptions& options, bool topLevel) const
255 {
256     if (bucketBox.getIsBucketSize()) {
257         std::string fileName;
258         fileName = bucketBox.getBucket().gen_index_str() + std::string(".stg");
259         return osgDB::readRefNodeFile(fileName, options._options);
260     } else if (!topLevel && options.isPageLevel(bucketBox.getStartLevel())) {
261         return createPagedLOD(bucketBox, options);
262     } else {
263         BucketBox bucketBoxList[100];
264         unsigned numTiles = bucketBox.getSubDivision(bucketBoxList, 100);
265         if (numTiles == 0)
266             return 0;
267
268         if (numTiles == 1)
269             return createTree(bucketBoxList[0], options, false);
270
271         osg::ref_ptr<osg::Group> group = new osg::Group;
272         for (unsigned i = 0; i < numTiles; ++i) {
273             osg::ref_ptr<osg::Node> node = createTree(bucketBoxList[i], options, false);
274             if (!node.valid())
275                 continue;
276             group->addChild(node.get());
277         }
278         if (!group->getNumChildren())
279             return 0;
280
281         return group;
282     }
283 }
284
285 osg::ref_ptr<osg::Node>
286 ReaderWriterSPT::createPagedLOD(const BucketBox& bucketBox, const LocalOptions& options) const
287 {
288     osg::PagedLOD* pagedLOD = new osg::PagedLOD;
289
290     pagedLOD->setCenterMode(osg::PagedLOD::USER_DEFINED_CENTER);
291     SGSpheref sphere = bucketBox.getBoundingSphere();
292     pagedLOD->setCenter(toOsg(sphere.getCenter()));
293     pagedLOD->setRadius(sphere.getRadius());
294
295     pagedLOD->setCullCallback(new CullCallback);
296
297     osg::ref_ptr<osgDB::Options> localOptions;
298     localOptions = static_cast<osgDB::Options*>(options._options->clone(osg::CopyOp()));
299     // FIXME:
300     // The particle systems have nodes with culling disabled.
301     // PagedLOD nodes with childnodes like this will never expire.
302     // So, for now switch them off.
303     localOptions->setPluginStringData("SimGear::PARTICLESYSTEM", "OFF");
304     pagedLOD->setDatabaseOptions(localOptions.get());
305
306     // The break point for the low level of detail to the high level of detail
307     float rangeMultiplier = options.getRangeMultiplier();
308     float range = rangeMultiplier*sphere.getRadius();
309
310     // Look for a low level of detail tile
311     std::string lodPath = options.getLodPathForBucketBox(bucketBox);
312     const char* extensions[] = { ".btg.gz", ".flt" };
313     for (unsigned i = 0; i < sizeof(extensions)/sizeof(extensions[0]); ++i) {
314         std::string fileName = osgDB::findDataFile(lodPath + extensions[i], options._options);
315         if (fileName.empty())
316             continue;
317         osg::ref_ptr<osg::Node> node = osgDB::readRefNodeFile(fileName, options._options);
318         if (!node.valid())
319             continue;
320         pagedLOD->addChild(node.get(), range, std::numeric_limits<float>::max());
321         break;
322     }
323     // Add the static sea level textured shell if there is nothing found
324     if (pagedLOD->getNumChildren() == 0) {
325         osg::ref_ptr<osg::Node> node = createSeaLevelTile(bucketBox, options._options);
326         if (node.valid())
327             pagedLOD->addChild(node.get(), range, std::numeric_limits<float>::max());
328     }
329
330     // Add the paged file name that creates the subtrees on demand
331     std::stringstream ss;
332     ss << bucketBox << ".spt";
333     pagedLOD->setFileName(pagedLOD->getNumChildren(), ss.str());
334     pagedLOD->setRange(pagedLOD->getNumChildren(), 0.0, range);
335
336     return pagedLOD;
337 }
338
339 osg::ref_ptr<osg::Node>
340 ReaderWriterSPT::createSeaLevelTile(const BucketBox& bucketBox, const LocalOptions& options) const
341 {
342     if (options._options->getPluginStringData("SimGear::FG_EARTH") != "ON")
343         return 0;
344
345     SGSpheref sphere = bucketBox.getBoundingSphere();
346     osg::Matrixd transform;
347     transform.makeTranslate(toOsg(-sphere.getCenter()));
348
349     osg::Vec3Array* vertices = new osg::Vec3Array;
350     osg::Vec3Array* normals = new osg::Vec3Array;
351     osg::Vec2Array* texCoords = new osg::Vec2Array;
352         
353     unsigned widthLevel = bucketBox.getWidthLevel();
354     unsigned heightLevel = bucketBox.getHeightLevel();
355
356     unsigned incx = bucketBox.getWidthIncrement(widthLevel + 2);
357     incx = std::min(incx, bucketBox.getSize(0));
358     for (unsigned i = 0; incx != 0;) {
359         unsigned incy = bucketBox.getHeightIncrement(heightLevel + 2);
360         incy = std::min(incy, bucketBox.getSize(1));
361         for (unsigned j = 0; incy != 0;) {
362             SGVec3f v[6], n[6];
363             SGVec2f t[6];
364             unsigned num = bucketBox.getTileTriangles(i, j, incx, incy, v, n, t);
365             for (unsigned k = 0; k < num; ++k) {
366                 vertices->push_back(transform.preMult(toOsg(v[k])));
367                 normals->push_back(toOsg(n[k]));
368                 texCoords->push_back(toOsg(t[k]));
369             }
370             j += incy;
371             incy = std::min(incy, bucketBox.getSize(1) - j);
372         }
373         i += incx;
374         incx = std::min(incx, bucketBox.getSize(0) - i);
375     }
376         
377     osg::Vec4Array* colors = new osg::Vec4Array;
378     colors->push_back(osg::Vec4(1, 1, 1, 1));
379         
380     osg::Geometry* geometry = new osg::Geometry;
381     geometry->setDataVariance(osg::Object::STATIC);
382     geometry->setUseVertexBufferObjects(true);
383     geometry->setVertexArray(vertices);
384     geometry->setNormalArray(normals);
385     geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
386     geometry->setColorArray(colors);
387     geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
388     geometry->setTexCoordArray(0, texCoords);
389         
390     osg::DrawArrays* drawArrays = new osg::DrawArrays(osg::DrawArrays::TRIANGLES, 0, vertices->size());
391     drawArrays->setDataVariance(osg::Object::STATIC);
392     geometry->addPrimitiveSet(drawArrays);
393         
394     osg::Geode* geode = new osg::Geode;
395     geode->setDataVariance(osg::Object::STATIC);
396     geode->addDrawable(geometry);
397     osg::ref_ptr<osg::StateSet> stateSet = getLowLODStateSet(options);
398     geode->setStateSet(stateSet.get());
399
400     transform.makeTranslate(toOsg(sphere.getCenter()));
401     osg::MatrixTransform* matrixTransform = new osg::MatrixTransform(transform);
402     matrixTransform->setDataVariance(osg::Object::STATIC);
403     matrixTransform->addChild(geode);
404
405     return matrixTransform;
406 }
407
408 osg::ref_ptr<osg::StateSet>
409 ReaderWriterSPT::getLowLODStateSet(const LocalOptions& options) const
410 {
411     osg::ref_ptr<osgDB::Options> localOptions;
412     localOptions = static_cast<osgDB::Options*>(options._options->clone(osg::CopyOp()));
413     localOptions->setObjectCacheHint(osgDB::Options::CACHE_ALL);
414
415     osg::ref_ptr<osg::Object> object = osgDB::readRefObjectFile("state.spt", localOptions.get());
416     if (!dynamic_cast<osg::StateSet*>(object.get()))
417         return 0;
418
419     return static_cast<osg::StateSet*>(object.get());
420 }
421
422 } // namespace simgear
423