]> git.mxchange.org Git - simgear.git/blob - simgear/scene/tgdb/ReaderWriterSPT.cxx
spt: Disable particle systems under a PagedLOD.
[simgear.git] / simgear / scene / tgdb / ReaderWriterSPT.cxx
1 // ReaderWriterSPT.cxx -- Provide a paged database for flightgear scenery.
2 //
3 // Copyright (C) 2010 - 2011  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/Texture2D>
31
32 #include <osgDB/FileNameUtils>
33 #include <osgDB/ReadFile>
34
35 #include <simgear/scene/util/OsgMath.hxx>
36
37 #include "BucketBox.hxx"
38
39 namespace simgear {
40
41 // Cull away tiles that we watch from downside
42 struct ReaderWriterSPT::CullCallback : public osg::NodeCallback {
43     virtual ~CullCallback()
44     { }
45     virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
46     {
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())
50             return;
51
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.
61         //
62         //    d1   d2
63         //  x----x----x
64         //  r\  rmin /rmax
65         //    \  |  /
66         //     \ | /
67         //      \|/
68         //
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
79         // d = d1 + d2
80
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;
89
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();
96         if (r2 <= rmin2)
97             return;
98
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)))
106             return;
107
108         traverse(node, nv);
109     }
110 };
111
112 ReaderWriterSPT::ReaderWriterSPT()
113 {
114     supportsExtension("spt", "SimGear paged terrain meta database.");
115 }
116
117 ReaderWriterSPT::~ReaderWriterSPT()
118 {
119 }
120
121 const char*
122 ReaderWriterSPT::className() const
123 {
124     return "simgear::ReaderWriterSPT";
125 }
126
127 osgDB::ReaderWriter::ReadResult
128 ReaderWriterSPT::readObject(const std::string& fileName, const osgDB::Options* options) const
129 {
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);
136
137     osg::StateSet* stateSet = new osg::StateSet;
138     stateSet->setAttributeAndModes(new osg::CullFace);
139
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");
146     }
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);
153     }
154
155     return stateSet;
156 }
157
158 osgDB::ReaderWriter::ReadResult
159 ReaderWriterSPT::readNode(const std::string& fileName, const osgDB::Options* options) const
160 {
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);
165
166     std::stringstream ss(strippedFileName);
167     BucketBox bucketBox;
168     ss >> bucketBox;
169     if (ss.fail())
170         return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
171
172     BucketBox bucketBoxList[2];
173     unsigned bucketBoxListSize = bucketBox.periodicSplit(bucketBoxList);
174     if (bucketBoxListSize == 0)
175         return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
176
177     if (bucketBoxListSize == 1)
178         return createTree(bucketBoxList[0], options, true);
179
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();
185 }
186
187 osg::Node*
188 ReaderWriterSPT::createTree(const BucketBox& bucketBox, const osgDB::Options* options, bool topLevel) const
189 {
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);
195     } else {
196         BucketBox bucketBoxList[100];
197         unsigned numTiles = bucketBox.getSubDivision(bucketBoxList, 100);
198         if (numTiles == 0)
199             return 0;
200
201         if (numTiles == 1)
202             return createTree(bucketBoxList[0], options, false);
203
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);
207             if (!node)
208                 continue;
209             group->addChild(node);
210         }
211         if (!group->getNumChildren())
212             return 0;
213
214         return group.release();
215     }
216 }
217
218 osg::Node*
219 ReaderWriterSPT::createPagedLOD(const BucketBox& bucketBox, const osgDB::Options* options) const
220 {
221     osg::PagedLOD* pagedLOD = new osg::PagedLOD;
222
223     pagedLOD->setCenterMode(osg::PagedLOD::USER_DEFINED_CENTER);
224     SGSpheref sphere = bucketBox.getBoundingSphere();
225     pagedLOD->setCenter(toOsg(sphere.getCenter()));
226     pagedLOD->setRadius(sphere.getRadius());
227
228     pagedLOD->setCullCallback(new CullCallback);
229
230     osg::ref_ptr<osgDB::Options> localOptions;
231     localOptions = static_cast<osgDB::Options*>(options->clone(osg::CopyOp()));
232     // FIXME:
233     // The particle systems have nodes with culling disabled.
234     // PagedLOD nodes with childnodes like this will never expire.
235     // So, for now switch them off.
236     localOptions->setPluginStringData("SimGear::PARTICLESYSTEM", "OFF");
237     pagedLOD->setDatabaseOptions(localOptions.get());
238         
239     float range;
240     if (bucketBox.getIsBucketSize())
241         range = 200e3;
242     else
243         range = 1e6;
244
245     // Add the static sea level textured shell
246     if (osg::Node* tile = createSeaLevelTile(bucketBox, options))
247         pagedLOD->addChild(tile, range, std::numeric_limits<float>::max());
248
249     // Add the paged file name that creates the subtrees on demand
250     if (bucketBox.getIsBucketSize()) {
251         std::string fileName;
252         fileName = bucketBox.getBucket().gen_index_str() + std::string(".stg");
253         pagedLOD->setFileName(pagedLOD->getNumChildren(), fileName);
254     } else {
255         std::stringstream ss;
256         ss << bucketBox << ".spt";
257         pagedLOD->setFileName(pagedLOD->getNumChildren(), ss.str());
258     }
259     pagedLOD->setRange(pagedLOD->getNumChildren(), 0.0, range);
260
261     return pagedLOD;
262 }
263
264 osg::Node*
265 ReaderWriterSPT::createSeaLevelTile(const BucketBox& bucketBox, const osgDB::Options* options) const
266 {
267     if (options->getPluginStringData("SimGear::FG_EARTH") != "ON")
268         return 0;
269
270     osg::Vec3Array* vertices = new osg::Vec3Array;
271     osg::Vec3Array* normals = new osg::Vec3Array;
272     osg::Vec2Array* texCoords = new osg::Vec2Array;
273         
274     unsigned widthLevel = bucketBox.getWidthLevel();
275     unsigned heightLevel = bucketBox.getHeightLevel();
276
277     unsigned incx = bucketBox.getWidthIncrement(widthLevel + 2);
278     incx = std::min(incx, bucketBox.getSize(0));
279     for (unsigned i = 0; incx != 0;) {
280         unsigned incy = bucketBox.getHeightIncrement(heightLevel + 2);
281         incy = std::min(incy, bucketBox.getSize(1));
282         for (unsigned j = 0; incy != 0;) {
283             SGVec3f v[6], n[6];
284             SGVec2f t[6];
285             unsigned num = bucketBox.getTileTriangles(i, j, incx, incy, v, n, t);
286             for (unsigned k = 0; k < num; ++k) {
287                 vertices->push_back(toOsg(v[k]));
288                 normals->push_back(toOsg(n[k]));
289                 texCoords->push_back(toOsg(t[k]));
290             }
291             j += incy;
292             incy = std::min(incy, bucketBox.getSize(1) - j);
293         }
294         i += incx;
295         incx = std::min(incx, bucketBox.getSize(0) - i);
296     }
297         
298     osg::Vec4Array* colors = new osg::Vec4Array;
299     colors->push_back(osg::Vec4(1, 1, 1, 1));
300         
301     osg::Geometry* geometry = new osg::Geometry;
302     geometry->setVertexArray(vertices);
303     geometry->setNormalArray(normals);
304     geometry->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
305     geometry->setColorArray(colors);
306     geometry->setColorBinding(osg::Geometry::BIND_OVERALL);
307     geometry->setTexCoordArray(0, texCoords);
308         
309     geometry->addPrimitiveSet(new osg::DrawArrays(osg::DrawArrays::TRIANGLES, 0, vertices->size()));
310         
311     osg::Geode* geode = new osg::Geode;
312     geode->addDrawable(geometry);
313     geode->setStateSet(getLowLODStateSet(options));
314
315     return geode;
316 }
317
318 osg::StateSet*
319 ReaderWriterSPT::getLowLODStateSet(const osgDB::Options* options) const
320 {
321     osg::ref_ptr<osgDB::Options> localOptions;
322     localOptions = static_cast<osgDB::Options*>(options->clone(osg::CopyOp()));
323     localOptions->setObjectCacheHint(osgDB::Options::CACHE_ALL);
324
325     osg::ref_ptr<osg::Object> object = osgDB::readObjectFile("state.spt", localOptions.get());
326     if (!dynamic_cast<osg::StateSet*>(object.get()))
327         return 0;
328
329     return static_cast<osg::StateSet*>(object.release());
330 }
331
332 } // namespace simgear
333