]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/ModelRegistry.cxx
Add preliminary spot light animation
[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/SGReaderWriterOptions.hxx>
51 #include <simgear/scene/util/NodeAndDrawableVisitor.hxx>
52
53 #include <simgear/structure/exception.hxx>
54 #include <simgear/props/props.hxx>
55 #include <simgear/props/props_io.hxx>
56 #include <simgear/props/condition.hxx>
57
58 #include "BoundingVolumeBuildVisitor.hxx"
59 #include "model.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 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 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         // Check for precompressed textures that depend on an extension
235         switch (res.getImage()->getPixelFormat()) {
236
237             // GL_EXT_texture_compression_s3tc
238             // patented, no way to decompress these
239 #ifndef GL_EXT_texture_compression_s3tc
240 #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT   0x83F0
241 #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT  0x83F1
242 #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT  0x83F2
243 #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT  0x83F3
244 #endif
245         case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
246         case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
247         case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
248         case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
249             
250             // GL_EXT_texture_sRGB
251             // patented, no way to decompress these
252 #ifndef GL_EXT_texture_sRGB
253 #define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT  0x8C4C
254 #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D
255 #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E
256 #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F
257 #endif
258         case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
259         case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
260         case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
261         case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
262             
263             // GL_TDFX_texture_compression_FXT1
264             // can decompress these in software but
265             // no code present in simgear.
266 #ifndef GL_3DFX_texture_compression_FXT1
267 #define GL_COMPRESSED_RGB_FXT1_3DFX       0x86B0
268 #define GL_COMPRESSED_RGBA_FXT1_3DFX      0x86B1
269 #endif
270         case GL_COMPRESSED_RGB_FXT1_3DFX:
271         case GL_COMPRESSED_RGBA_FXT1_3DFX:
272             
273             // GL_EXT_texture_compression_rgtc
274             // can decompress these in software but
275             // no code present in simgear.
276 #ifndef GL_EXT_texture_compression_rgtc
277 #define GL_COMPRESSED_RED_RGTC1_EXT       0x8DBB
278 #define GL_COMPRESSED_SIGNED_RED_RGTC1_EXT 0x8DBC
279 #define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD
280 #define GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT 0x8DBE
281 #endif
282         case GL_COMPRESSED_RED_RGTC1_EXT:
283         case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT:
284         case GL_COMPRESSED_RED_GREEN_RGTC2_EXT:
285         case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
286
287             SG_LOG(SG_IO, SG_ALERT, "Image \"" << fileName << "\"\n"
288                    "uses compressed textures which cannot be supported on "
289                    "some systems.\n"
290                    "Please decompress this texture for improved portability.");
291             break;
292
293         default:
294             break;
295         }
296
297         return res;
298     }
299 }
300
301
302 osg::Node* DefaultCachePolicy::find(const string& fileName,
303                                     const Options* opt)
304 {
305     Registry* registry = Registry::instance();
306     osg::Node* cached
307         = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
308     if (cached)
309         SG_LOG(SG_IO, SG_BULK, "Got cached model \""
310                << fileName << "\"");
311     else
312         SG_LOG(SG_IO, SG_BULK, "Reading model \""
313                << fileName << "\"");
314     return cached;
315 }
316
317 void DefaultCachePolicy::addToCache(const string& fileName,
318                                     osg::Node* node)
319 {
320     Registry::instance()->addEntryToObjectCache(fileName, node);
321 }
322
323 // Optimizations we don't use:
324 // Don't use this one. It will break animation names ...
325 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
326 //
327 // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
328 // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
329 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
330 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
331 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
332 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
333 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
334
335 OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
336     _osgOptions(Optimizer::SHARE_DUPLICATE_STATE
337                 | Optimizer::MERGE_GEOMETRY
338                 | Optimizer::FLATTEN_STATIC_TRANSFORMS
339                 | Optimizer::TRISTRIP_GEOMETRY)
340 {
341 }
342
343 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
344                                          const string& fileName,
345                                          const osgDB::Options* opt)
346 {
347     osgUtil::Optimizer optimizer;
348     optimizer.optimize(node, _osgOptions);
349
350     // Make sure the data variance of sharable objects is set to
351     // STATIC so that textures will be globally shared.
352     SGTexDataVarianceVisitor dataVarianceVisitor;
353     node->accept(dataVarianceVisitor);
354
355     SGTexCompressionVisitor texComp;
356     node->accept(texComp);
357     return node;
358 }
359
360 string OSGSubstitutePolicy::substitute(const string& name,
361                                        const Options* opt)
362 {
363     string fileSansExtension = getNameLessExtension(name);
364     string osgFileName = fileSansExtension + ".osg";
365     string absFileName = SGModelLib::findDataFile(osgFileName, opt);
366     return absFileName;
367 }
368
369
370 void
371 BuildLeafBVHPolicy::buildBVH(const std::string& fileName, osg::Node* node)
372 {
373     SG_LOG(SG_IO, SG_BULK, "Building leaf attached boundingvolume tree for \""
374            << fileName << "\".");
375     BoundingVolumeBuildVisitor bvBuilder(true);
376     node->accept(bvBuilder);
377 }
378
379 void
380 BuildGroupBVHPolicy::buildBVH(const std::string& fileName, osg::Node* node)
381 {
382     SG_LOG(SG_IO, SG_BULK, "Building group attached boundingvolume tree for \""
383            << fileName << "\".");
384     BoundingVolumeBuildVisitor bvBuilder(false);
385     node->accept(bvBuilder);
386 }
387
388 void
389 NoBuildBVHPolicy::buildBVH(const std::string& fileName, osg::Node*)
390 {
391     SG_LOG(SG_IO, SG_BULK, "Omitting boundingvolume tree for \""
392            << fileName << "\".");
393 }
394
395 ModelRegistry::ModelRegistry() :
396     _defaultCallback(new DefaultCallback(""))
397 {
398 }
399
400 void
401 ModelRegistry::addImageCallbackForExtension(const string& extension,
402                                             Registry::ReadFileCallback* callback)
403 {
404     imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
405 }
406
407 void
408 ModelRegistry::addNodeCallbackForExtension(const string& extension,
409                                            Registry::ReadFileCallback* callback)
410 {
411     nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
412 }
413
414 ReaderWriter::ReadResult
415 ModelRegistry::readNode(const string& fileName,
416                         const Options* opt)
417 {
418     ReaderWriter::ReadResult res;
419     CallbackMap::iterator iter
420         = nodeCallbackMap.find(getFileExtension(fileName));
421     ReaderWriter::ReadResult result;
422     if (iter != nodeCallbackMap.end() && iter->second.valid())
423         result = iter->second->readNode(fileName, opt);
424     else
425         result = _defaultCallback->readNode(fileName, opt);
426
427     return result;
428 }
429
430 class SGReadCallbackInstaller {
431 public:
432   SGReadCallbackInstaller()
433   {
434     // XXX I understand why we want this, but this seems like a weird
435     // place to set this option.
436     Referenced::setThreadSafeReferenceCounting(true);
437
438     Registry* registry = Registry::instance();
439     Options* options = new Options;
440     int cacheOptions = Options::CACHE_ALL;
441     options->
442       setObjectCacheHint((Options::CacheHintOptions)cacheOptions);
443     registry->setOptions(options);
444     registry->getOrCreateSharedStateManager()->
445       setShareMode(SharedStateManager::SHARE_STATESETS);
446     registry->setReadFileCallback(ModelRegistry::instance());
447   }
448 };
449
450 static SGReadCallbackInstaller readCallbackInstaller;
451
452 // we get optimal geometry from the loader (Hah!).
453 struct ACOptimizePolicy : public OptimizeModelPolicy {
454     ACOptimizePolicy(const string& extension)  :
455         OptimizeModelPolicy(extension)
456     {
457         _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
458     }
459     Node* optimize(Node* node, const string& fileName,
460                    const Options* opt)
461     {
462         ref_ptr<Node> optimized
463             = OptimizeModelPolicy::optimize(node, fileName, opt);
464         Group* group = dynamic_cast<Group*>(optimized.get());
465         MatrixTransform* transform
466             = dynamic_cast<MatrixTransform*>(optimized.get());
467         if (((transform && transform->getMatrix().isIdentity()) || group)
468             && group->getName().empty()
469             && group->getNumChildren() == 1) {
470             optimized = static_cast<Node*>(group->getChild(0));
471             group = dynamic_cast<Group*>(optimized.get());
472             if (group && group->getName().empty()
473                 && group->getNumChildren() == 1)
474                 optimized = static_cast<Node*>(group->getChild(0));
475         }
476         const SGReaderWriterOptions* sgopt
477             = dynamic_cast<const SGReaderWriterOptions*>(opt);
478         if (sgopt && sgopt->getInstantiateEffects())
479             optimized = instantiateEffects(optimized.get(), sgopt);
480         return optimized.release();
481     }
482 };
483
484 struct ACProcessPolicy {
485     ACProcessPolicy(const string& extension) {}
486     Node* process(Node* node, const string& filename,
487                   const Options* opt)
488     {
489         Matrix m(1, 0, 0, 0,
490                  0, 0, 1, 0,
491                  0, -1, 0, 0,
492                  0, 0, 0, 1);
493         // XXX Does there need to be a Group node here to trick the
494         // optimizer into optimizing the static transform?
495         osg::Group* root = new Group;
496         MatrixTransform* transform = new MatrixTransform;
497         root->addChild(transform);
498         
499         transform->setDataVariance(Object::STATIC);
500         transform->setMatrix(m);
501         transform->addChild(node);
502
503         return root;
504     }
505 };
506
507 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
508                               ACOptimizePolicy,
509                               OSGSubstitutePolicy, BuildLeafBVHPolicy>
510 ACCallback;
511
512 namespace
513 {
514 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");
515 }