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