1 // ModelRegistry.hxx -- interface to the OSG model registry
3 // Copyright (C) 2005-2007 Mathias Froehlich
4 // Copyright (C) 2007 Tim Moore <timoore@redhat.com>
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.
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.
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 #include "ModelRegistry.hxx"
21 #include <OpenThreads/ScopedLock>
23 #include <osg/observer_ptr>
24 #include <osg/ref_ptr>
26 #include <osg/NodeCallback>
28 #include <osg/Material>
29 #include <osg/MatrixTransform>
30 #include <osgDB/Archive>
31 #include <osgDB/FileNameUtils>
32 #include <osgDB/FileUtils>
33 #include <osgDB/ReadFile>
34 #include <osgDB/WriteFile>
35 #include <osgDB/Registry>
36 #include <osgDB/SharedStateManager>
37 #include <osgUtil/Optimizer>
39 #include <simgear/scene/util/SGSceneFeatures.hxx>
40 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
41 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
43 #include <simgear/structure/exception.hxx>
44 #include <simgear/props/props.hxx>
45 #include <simgear/props/props_io.hxx>
46 #include <simgear/props/condition.hxx>
50 using namespace osgUtil;
51 using namespace osgDB;
52 using namespace simgear;
54 // Little helper class that holds an extra reference to a
56 // Since we clone all structural nodes from our 3d models,
57 // the database pager will only see one single reference to
58 // top node of the model and expire it relatively fast.
59 // We attach that extra reference to every model cloned from
60 // a base model in the pager. When that cloned model is deleted
61 // this extra reference is deleted too. So if there are no
62 // cloned models left the model will expire.
64 class SGDatabaseReference : public Observer {
66 SGDatabaseReference(Referenced* referenced) :
67 mReferenced(referenced)
69 virtual void objectDeleted(void*)
74 ref_ptr<Referenced> mReferenced;
78 class SGTextureUpdateVisitor : public SGTextureStateAttributeVisitor {
80 SGTextureUpdateVisitor(const FilePathList& pathList) :
83 Texture2D* textureReplace(int unit,
84 StateSet::RefAttributePair& refAttr)
87 texture = dynamic_cast<Texture2D*>(refAttr.first.get());
91 ref_ptr<Image> image = texture->getImage(0);
95 // The currently loaded file name
96 string fullFilePath = image->getFileName();
98 string fileName = getSimpleFileName(fullFilePath);
99 // The name that should be found with the current database path
100 string fullLiveryFile = findFileInPath(fileName, mPathList);
101 // If they are identical then there is nothing to do
102 if (fullLiveryFile == fullFilePath)
105 image = readImageFile(fullLiveryFile);
109 CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
110 texture = static_cast<Texture2D*>(copyOp(texture));
113 texture->setImage(image.get());
116 virtual void apply(StateSet* stateSet)
121 // get a copy that we can safely modify the statesets values.
122 StateSet::TextureAttributeList attrList;
123 attrList = stateSet->getTextureAttributeList();
124 for (unsigned unit = 0; unit < attrList.size(); ++unit) {
125 StateSet::AttributeList::iterator i = attrList[unit].begin();
126 while (i != attrList[unit].end()) {
127 Texture2D* texture = textureReplace(unit, i->second);
129 stateSet->removeTextureAttribute(unit, i->second.first.get());
130 stateSet->setTextureAttribute(unit, texture, i->second.second);
131 stateSet->setTextureMode(unit, GL_TEXTURE_2D, StateAttribute::ON);
139 FilePathList mPathList;
142 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
144 virtual void apply(int, StateSet::RefAttributePair& refAttr)
147 texture = dynamic_cast<Texture2D*>(refAttr.first.get());
152 texture->setDataVariance(osg::Object::STATIC);
154 Image* image = texture->getImage(0);
161 if (s <= t && 32 <= s) {
162 SGSceneFeatures::instance()->setTextureCompression(texture);
163 } else if (t < s && 32 <= t) {
164 SGSceneFeatures::instance()->setTextureCompression(texture);
169 class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
171 virtual void apply(int, StateSet::RefAttributePair& refAttr)
174 texture = dynamic_cast<Texture*>(refAttr.first.get());
178 texture->setDataVariance(Object::STATIC);
181 virtual void apply(StateSet* stateSet)
185 SGTextureStateAttributeVisitor::apply(stateSet);
186 stateSet->setDataVariance(Object::STATIC);
190 class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
192 virtual void apply(StateSet::RefAttributePair& refAttr)
195 material = dynamic_cast<Material*>(refAttr.first.get());
198 material->setColorMode(Material::AMBIENT_AND_DIFFUSE);
202 // Work around an OSG bug - the file loaders don't use the file path
203 // in options while the file is being loaded.
205 struct OptionsPusher {
206 FilePathList localPathList;
208 OptionsPusher(const ReaderWriter::Options* options):
213 Registry* registry = Registry::instance();
214 localPathList = registry->getDataFilePathList();
215 const FilePathList& regPathList = registry->getDataFilePathList();
216 const FilePathList& optionsPathList = options->getDatabasePathList();
217 for (FilePathList::const_iterator iter = optionsPathList.begin();
218 iter != optionsPathList.end();
220 if (find(regPathList.begin(), regPathList.end(), *iter)
221 == regPathList.end())
222 localPathList.push_front(*iter);
224 // Save the current Registry path list and install the augmented one.
225 localPathList.swap(registry->getDataFilePathList());
230 // Restore the old path list
232 localPathList.swap(Registry::instance()->getDataFilePathList());
237 ReaderWriter::ReadResult
238 ModelRegistry::readImage(const string& fileName,
239 const ReaderWriter::Options* opt)
241 OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(readerMutex);
242 CallbackMap::iterator iter
243 = imageCallbackMap.find(getFileExtension(fileName));
244 // XXX Workaround for OSG plugin bug
245 OptionsPusher pusher(opt);
246 if (iter != imageCallbackMap.end() && iter->second.valid())
247 return iter->second->readImage(fileName, opt);
248 string absFileName = findDataFile(fileName);
249 if (!fileExists(absFileName)) {
250 SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
251 << fileName << "\"");
252 return ReaderWriter::ReadResult::FILE_NOT_FOUND;
255 Registry* registry = Registry::instance();
256 ReaderWriter::ReadResult res;
257 res = registry->readImageImplementation(absFileName, opt);
258 if (res.loadedFromCache())
259 SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
260 << res.getImage()->getFileName() << "\"");
262 SG_LOG(SG_IO, SG_INFO, "Reading image \""
263 << res.getImage()->getFileName() << "\"");
269 osg::Node* DefaultCachePolicy::find(const string& fileName,
270 const ReaderWriter::Options* opt)
272 Registry* registry = Registry::instance();
274 = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
276 SG_LOG(SG_IO, SG_INFO, "Got cached model \""
277 << fileName << "\"");
279 SG_LOG(SG_IO, SG_INFO, "Reading model \""
280 << fileName << "\"");
284 void DefaultCachePolicy::addToCache(const string& fileName,
287 Registry::instance()->addEntryToObjectCache(fileName, node);
290 // Optimizations we don't use:
291 // Don't use this one. It will break animation names ...
292 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
294 // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
295 // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
296 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
297 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
298 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
299 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
300 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
302 OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
303 _osgOptions(Optimizer::SHARE_DUPLICATE_STATE
304 | Optimizer::MERGE_GEOMETRY
305 | Optimizer::FLATTEN_STATIC_TRANSFORMS
306 | Optimizer::TRISTRIP_GEOMETRY)
310 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
311 const string& fileName,
312 const osgDB::ReaderWriter::Options* opt)
314 osgUtil::Optimizer optimizer;
315 optimizer.optimize(node, _osgOptions);
317 // Make sure the data variance of sharable objects is set to
318 // STATIC so that textures will be globally shared.
319 SGTexDataVarianceVisitor dataVarianceVisitor;
320 node->accept(dataVarianceVisitor);
322 SGTexCompressionVisitor texComp;
323 node->accept(texComp);
327 osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName,
328 const osgDB::ReaderWriter::Options* opt)
330 // Add an extra reference to the model stored in the database.
331 // That it to avoid expiring the object from the cache even if it is still
332 // in use. Note that the object cache will think that a model is unused
333 // if the reference count is 1. If we clone all structural nodes here
334 // we need that extra reference to the original object
335 SGDatabaseReference* databaseReference;
336 databaseReference = new SGDatabaseReference(model);
337 CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
338 flags &= ~CopyOp::DEEP_COPY_TEXTURES;
339 flags &= ~CopyOp::DEEP_COPY_IMAGES;
340 flags &= ~CopyOp::DEEP_COPY_STATESETS;
341 flags &= ~CopyOp::DEEP_COPY_STATEATTRIBUTES;
342 flags &= ~CopyOp::DEEP_COPY_ARRAYS;
343 flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
344 // This will safe display lists ...
345 flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
346 flags &= ~CopyOp::DEEP_COPY_SHAPES;
347 osg::Node* res = CopyOp(flags)(model);
348 res->addObserver(databaseReference);
351 SGTextureUpdateVisitor liveryUpdate(opt->getDatabasePathList());
352 res->accept(liveryUpdate);
356 string OSGSubstitutePolicy::substitute(const string& name,
357 const ReaderWriter::Options* opt)
359 string fileSansExtension = getNameLessExtension(name);
360 string osgFileName = fileSansExtension + ".osg";
361 string absFileName = findDataFile(osgFileName, opt);
365 ModelRegistry::ModelRegistry() :
366 _defaultCallback(new DefaultCallback(""))
371 ModelRegistry::addImageCallbackForExtension(const string& extension,
372 Registry::ReadFileCallback* callback)
374 imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
378 ModelRegistry::addNodeCallbackForExtension(const string& extension,
379 Registry::ReadFileCallback* callback)
381 nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
384 ref_ptr<ModelRegistry> ModelRegistry::instance;
386 ModelRegistry* ModelRegistry::getInstance()
389 if (!instance.valid())
390 instance = new ModelRegistry;
391 return instance.get();
394 ReaderWriter::ReadResult
395 ModelRegistry::readNode(const string& fileName,
396 const ReaderWriter::Options* opt)
398 OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(readerMutex);
399 // XXX Workaround for OSG plugin bug.
400 OptionsPusher pusher(opt);
401 Registry* registry = Registry::instance();
402 ReaderWriter::ReadResult res;
404 CallbackMap::iterator iter
405 = nodeCallbackMap.find(getFileExtension(fileName));
406 if (iter != nodeCallbackMap.end() && iter->second.valid())
407 return iter->second->readNode(fileName, opt);
408 return _defaultCallback->readNode(fileName, opt);
411 class SGReadCallbackInstaller {
413 SGReadCallbackInstaller()
415 // XXX I understand why we want this, but this seems like a weird
416 // place to set this option.
417 Referenced::setThreadSafeReferenceCounting(true);
419 Registry* registry = Registry::instance();
420 ReaderWriter::Options* options = new ReaderWriter::Options;
421 int cacheOptions = ReaderWriter::Options::CACHE_ALL;
423 setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
424 registry->setOptions(options);
425 registry->getOrCreateSharedStateManager()->
426 setShareMode(SharedStateManager::SHARE_STATESETS);
427 registry->setReadFileCallback(ModelRegistry::getInstance());
431 static SGReadCallbackInstaller readCallbackInstaller;
433 // we get optimal geometry from the loader.
434 struct ACOptimizePolicy : public OptimizeModelPolicy {
435 ACOptimizePolicy(const string& extension) :
436 OptimizeModelPolicy(extension)
438 _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
442 struct ACProcessPolicy {
443 ACProcessPolicy(const string& extension) {}
444 Node* process(Node* node, const string& filename,
445 const ReaderWriter::Options* opt)
451 // XXX Does there need to be a Group node here to trick the
452 // optimizer into optimizing the static transform?
453 osg::Group* root = new Group;
454 MatrixTransform* transform = new MatrixTransform;
455 root->addChild(transform);
457 transform->setDataVariance(Object::STATIC);
458 transform->setMatrix(m);
459 transform->addChild(node);
460 // Ok, this step is questionable.
461 // It is there to have the same visual appearance of ac objects for the
462 // first cut. Osg's ac3d loader will correctly set materials from the
463 // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
464 // materials that in effect igored the ambient part specified in the
465 // file. We emulate that for the first cut here by changing all
466 // ac models here. But in the long term we should use the
467 // unchanged model and fix the input files instead ...
468 SGAcMaterialCrippleVisitor matCriple;
469 root->accept(matCriple);
474 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
475 ACOptimizePolicy, DefaultCopyPolicy,
476 OSGSubstitutePolicy> ACCallback;
480 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");