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