]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/ModelRegistry.cxx
Improved tile cache priority scheme.
[simgear.git] / simgear / scene / model / ModelRegistry.cxx
1 // ModelRegistry.hxx -- interface to the OSG model registry
2 //
3 // Copyright (C) 2005-2007 Mathias Froehlich 
4 // Copyright (C) 2007  Tim Moore <timoore@redhat.com>
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License as
8 // published by the Free Software Foundation; either version 2 of the
9 // License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful, but
12 // WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19
20 #ifdef HAVE_CONFIG_H
21 #  include <simgear_config.h>
22 #endif
23
24 #include "ModelRegistry.hxx"
25
26 #include <algorithm>
27 #include <utility>
28 #include <vector>
29
30 #include <OpenThreads/ScopedLock>
31
32 #include <osg/ref_ptr>
33 #include <osg/Group>
34 #include <osg/NodeCallback>
35 #include <osg/Switch>
36 #include <osg/Material>
37 #include <osg/MatrixTransform>
38 #include <osgDB/Archive>
39 #include <osgDB/FileNameUtils>
40 #include <osgDB/FileUtils>
41 #include <osgDB/ReadFile>
42 #include <osgDB/WriteFile>
43 #include <osgDB/Registry>
44 #include <osgDB/SharedStateManager>
45 #include <osgUtil/Optimizer>
46
47 #include <simgear/scene/util/SGSceneFeatures.hxx>
48 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
49 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
50 #include <simgear/scene/util/NodeAndDrawableVisitor.hxx>
51
52 #include <simgear/structure/exception.hxx>
53 #include <simgear/props/props.hxx>
54 #include <simgear/props/props_io.hxx>
55 #include <simgear/props/condition.hxx>
56
57 #include "BoundingVolumeBuildVisitor.hxx"
58 #include "model.hxx"
59 #include "SGReaderWriterXMLOptions.hxx"
60
61 using namespace std;
62 using namespace osg;
63 using namespace osgUtil;
64 using namespace osgDB;
65 using namespace simgear;
66
67 // Little helper class that holds an extra reference to a
68 // loaded 3d model.
69 // Since we clone all structural nodes from our 3d models,
70 // the database pager will only see one single reference to
71 // top node of the model and expire it relatively fast.
72 // We attach that extra reference to every model cloned from
73 // a base model in the pager. When that cloned model is deleted
74 // this extra reference is deleted too. So if there are no
75 // cloned models left the model will expire.
76 namespace {
77 class SGDatabaseReference : public Observer {
78 public:
79   SGDatabaseReference(Referenced* referenced) :
80     mReferenced(referenced)
81   { }
82   virtual void objectDeleted(void*)
83   {
84     mReferenced = 0;
85   }
86 private:
87   ref_ptr<Referenced> mReferenced;
88 };
89
90 // Set the name of a Texture to the simple name of its image
91 // file. This can be used to do livery substitution after the image
92 // has been deallocated.
93 class TextureNameVisitor  : public NodeAndDrawableVisitor {
94 public:
95     TextureNameVisitor(NodeVisitor::TraversalMode tm = NodeVisitor::TRAVERSE_ALL_CHILDREN) :
96         NodeAndDrawableVisitor(tm)
97     {
98     }
99
100     virtual void apply(Node& node)
101     {
102         nameTextures(node.getStateSet());
103         traverse(node);
104     }
105
106     virtual void apply(Drawable& drawable)
107     {
108         nameTextures(drawable.getStateSet());
109     }
110 protected:
111     void nameTextures(StateSet* stateSet)
112     {
113         if (!stateSet)
114             return;
115         int numUnits = stateSet->getTextureAttributeList().size();
116         for (int i = 0; i < numUnits; ++i) {
117             StateAttribute* attr
118                 = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE);
119             Texture2D* texture = dynamic_cast<Texture2D*>(attr);
120             if (!texture || !texture->getName().empty())
121                 continue;
122             const Image *image = texture->getImage();
123             if (!image)
124                 continue;
125             texture->setName(image->getFileName());
126         }
127     }
128 };
129
130
131 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
132 public:
133   virtual void apply(int, StateSet::RefAttributePair& refAttr)
134   {
135     Texture2D* texture;
136     texture = dynamic_cast<Texture2D*>(refAttr.first.get());
137     if (!texture)
138       return;
139
140     // Do not touch dynamically generated textures.
141     if (texture->getReadPBuffer())
142       return;
143     if (texture->getDataVariance() == osg::Object::DYNAMIC)
144       return;
145
146     // If no image attached, we assume this one is dynamically generated
147     Image* image = texture->getImage(0);
148     if (!image)
149       return;
150
151     int s = image->s();
152     int t = image->t();
153
154     if (s <= t && 32 <= s) {
155       SGSceneFeatures::instance()->setTextureCompression(texture);
156     } else if (t < s && 32 <= t) {
157       SGSceneFeatures::instance()->setTextureCompression(texture);
158     }
159   }
160 };
161
162 class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
163 public:
164   virtual void apply(int, StateSet::RefAttributePair& refAttr)
165   {
166     Texture* texture;
167     texture = dynamic_cast<Texture*>(refAttr.first.get());
168     if (!texture)
169       return;
170
171     // Cannot be static if this is a render to texture thing
172     if (texture->getReadPBuffer())
173       return;
174     if (texture->getDataVariance() == osg::Object::DYNAMIC)
175       return;
176     // If no image attached, we assume this one is dynamically generated
177     Image* image = texture->getImage(0);
178     if (!image)
179       return;
180     
181     texture->setDataVariance(Object::STATIC);
182   }
183
184   virtual void apply(StateSet* stateSet)
185   {
186     if (!stateSet)
187       return;
188     stateSet->setDataVariance(Object::STATIC);
189     SGTextureStateAttributeVisitor::apply(stateSet);
190   }
191 };
192
193 } // namespace
194
195 Node* DefaultProcessPolicy::process(Node* node, const string& filename,
196                                     const ReaderWriter::Options* opt)
197 {
198     TextureNameVisitor nameVisitor;
199     node->accept(nameVisitor);
200     return node;
201 }
202
203 ReaderWriter::ReadResult
204 ModelRegistry::readImage(const string& fileName,
205                          const ReaderWriter::Options* opt)
206 {
207     CallbackMap::iterator iter
208         = imageCallbackMap.find(getFileExtension(fileName));
209     {
210         if (iter != imageCallbackMap.end() && iter->second.valid())
211             return iter->second->readImage(fileName, opt);
212         string absFileName = SGModelLib::findDataFile(fileName, opt);
213         if (!fileExists(absFileName)) {
214             SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
215                    << fileName << "\"");
216             return ReaderWriter::ReadResult::FILE_NOT_FOUND;
217         }
218
219         Registry* registry = Registry::instance();
220         ReaderWriter::ReadResult res;
221         res = registry->readImageImplementation(absFileName, opt);
222         if (!res.success()) {
223           SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message());
224           return res;
225         }
226         
227         if (res.loadedFromCache())
228             SG_LOG(SG_IO, SG_BULK, "Returning cached image \""
229                    << res.getImage()->getFileName() << "\"");
230         else
231             SG_LOG(SG_IO, SG_BULK, "Reading image \""
232                    << res.getImage()->getFileName() << "\"");
233
234         return res;
235     }
236 }
237
238
239 osg::Node* DefaultCachePolicy::find(const string& fileName,
240                                     const ReaderWriter::Options* opt)
241 {
242     Registry* registry = Registry::instance();
243     osg::Node* cached
244         = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
245     if (cached)
246         SG_LOG(SG_IO, SG_BULK, "Got cached model \""
247                << fileName << "\"");
248     else
249         SG_LOG(SG_IO, SG_BULK, "Reading model \""
250                << fileName << "\"");
251     return cached;
252 }
253
254 void DefaultCachePolicy::addToCache(const string& fileName,
255                                     osg::Node* node)
256 {
257     Registry::instance()->addEntryToObjectCache(fileName, node);
258 }
259
260 // Optimizations we don't use:
261 // Don't use this one. It will break animation names ...
262 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
263 //
264 // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
265 // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
266 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
267 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
268 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
269 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
270 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
271
272 OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
273     _osgOptions(Optimizer::SHARE_DUPLICATE_STATE
274                 | Optimizer::MERGE_GEOMETRY
275                 | Optimizer::FLATTEN_STATIC_TRANSFORMS
276                 | Optimizer::TRISTRIP_GEOMETRY)
277 {
278 }
279
280 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
281                                          const string& fileName,
282                                          const osgDB::ReaderWriter::Options* opt)
283 {
284     osgUtil::Optimizer optimizer;
285     optimizer.optimize(node, _osgOptions);
286
287     // Make sure the data variance of sharable objects is set to
288     // STATIC so that textures will be globally shared.
289     SGTexDataVarianceVisitor dataVarianceVisitor;
290     node->accept(dataVarianceVisitor);
291
292     SGTexCompressionVisitor texComp;
293     node->accept(texComp);
294     return node;
295 }
296
297 string OSGSubstitutePolicy::substitute(const string& name,
298                                        const ReaderWriter::Options* opt)
299 {
300     string fileSansExtension = getNameLessExtension(name);
301     string osgFileName = fileSansExtension + ".osg";
302     string absFileName = SGModelLib::findDataFile(osgFileName, opt);
303     return absFileName;
304 }
305
306
307 void
308 BuildLeafBVHPolicy::buildBVH(const std::string& fileName, osg::Node* node)
309 {
310     SG_LOG(SG_IO, SG_BULK, "Building leaf attached boundingvolume tree for \""
311            << fileName << "\".");
312     BoundingVolumeBuildVisitor bvBuilder(true);
313     node->accept(bvBuilder);
314 }
315
316 void
317 BuildGroupBVHPolicy::buildBVH(const std::string& fileName, osg::Node* node)
318 {
319     SG_LOG(SG_IO, SG_BULK, "Building group attached boundingvolume tree for \""
320            << fileName << "\".");
321     BoundingVolumeBuildVisitor bvBuilder(false);
322     node->accept(bvBuilder);
323 }
324
325 void
326 NoBuildBVHPolicy::buildBVH(const std::string& fileName, osg::Node*)
327 {
328     SG_LOG(SG_IO, SG_BULK, "Omitting boundingvolume tree for \""
329            << fileName << "\".");
330 }
331
332 ModelRegistry::ModelRegistry() :
333     _defaultCallback(new DefaultCallback(""))
334 {
335 }
336
337 void
338 ModelRegistry::addImageCallbackForExtension(const string& extension,
339                                             Registry::ReadFileCallback* callback)
340 {
341     imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
342 }
343
344 void
345 ModelRegistry::addNodeCallbackForExtension(const string& extension,
346                                            Registry::ReadFileCallback* callback)
347 {
348     nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
349 }
350
351 ReaderWriter::ReadResult
352 ModelRegistry::readNode(const string& fileName,
353                         const ReaderWriter::Options* opt)
354 {
355     ReaderWriter::ReadResult res;
356     CallbackMap::iterator iter
357         = nodeCallbackMap.find(getFileExtension(fileName));
358     ReaderWriter::ReadResult result;
359     if (iter != nodeCallbackMap.end() && iter->second.valid())
360         result = iter->second->readNode(fileName, opt);
361     else
362         result = _defaultCallback->readNode(fileName, opt);
363
364     return result;
365 }
366
367 class SGReadCallbackInstaller {
368 public:
369   SGReadCallbackInstaller()
370   {
371     // XXX I understand why we want this, but this seems like a weird
372     // place to set this option.
373     Referenced::setThreadSafeReferenceCounting(true);
374
375     Registry* registry = Registry::instance();
376     ReaderWriter::Options* options = new ReaderWriter::Options;
377     int cacheOptions = ReaderWriter::Options::CACHE_ALL;
378     options->
379       setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
380     registry->setOptions(options);
381     registry->getOrCreateSharedStateManager()->
382       setShareMode(SharedStateManager::SHARE_STATESETS);
383     registry->setReadFileCallback(ModelRegistry::instance());
384   }
385 };
386
387 static SGReadCallbackInstaller readCallbackInstaller;
388
389 // we get optimal geometry from the loader (Hah!).
390 struct ACOptimizePolicy : public OptimizeModelPolicy {
391     ACOptimizePolicy(const string& extension)  :
392         OptimizeModelPolicy(extension)
393     {
394         _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
395     }
396     Node* optimize(Node* node, const string& fileName,
397                    const ReaderWriter::Options* opt)
398     {
399         ref_ptr<Node> optimized
400             = OptimizeModelPolicy::optimize(node, fileName, opt);
401         Group* group = dynamic_cast<Group*>(optimized.get());
402         MatrixTransform* transform
403             = dynamic_cast<MatrixTransform*>(optimized.get());
404         if (((transform && transform->getMatrix().isIdentity()) || group)
405             && group->getName().empty()
406             && group->getNumChildren() == 1) {
407             optimized = static_cast<Node*>(group->getChild(0));
408             group = dynamic_cast<Group*>(optimized.get());
409             if (group && group->getName().empty()
410                 && group->getNumChildren() == 1)
411                 optimized = static_cast<Node*>(group->getChild(0));
412         }
413         const SGReaderWriterXMLOptions* sgopt
414             = dynamic_cast<const SGReaderWriterXMLOptions*>(opt);
415         if (sgopt && sgopt->getInstantiateEffects())
416             optimized = instantiateEffects(optimized.get(), sgopt);
417         return optimized.release();
418     }
419 };
420
421 struct ACProcessPolicy {
422     ACProcessPolicy(const string& extension) {}
423     Node* process(Node* node, const string& filename,
424                   const ReaderWriter::Options* opt)
425     {
426         Matrix m(1, 0, 0, 0,
427                  0, 0, 1, 0,
428                  0, -1, 0, 0,
429                  0, 0, 0, 1);
430         // XXX Does there need to be a Group node here to trick the
431         // optimizer into optimizing the static transform?
432         osg::Group* root = new Group;
433         MatrixTransform* transform = new MatrixTransform;
434         root->addChild(transform);
435         
436         transform->setDataVariance(Object::STATIC);
437         transform->setMatrix(m);
438         transform->addChild(node);
439
440         return root;
441     }
442 };
443
444 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
445                               ACOptimizePolicy,
446                               OSGSubstitutePolicy, BuildLeafBVHPolicy>
447 ACCallback;
448
449 namespace
450 {
451 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");
452 }