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);
203 ReaderWriter::ReadResult
204 ModelRegistry::readImage(const string& fileName,
205 const ReaderWriter::Options* opt)
207 OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(readerMutex);
208 CallbackMap::iterator iter
209 = imageCallbackMap.find(getFileExtension(fileName));
210 if (iter != imageCallbackMap.end() && iter->second.valid())
211 return iter->second->readImage(fileName, opt);
212 string absFileName = 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;
219 Registry* registry = Registry::instance();
220 ReaderWriter::ReadResult res;
221 res = registry->readImageImplementation(absFileName, opt);
222 if (res.loadedFromCache())
223 SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
224 << res.getImage()->getFileName() << "\"");
226 SG_LOG(SG_IO, SG_INFO, "Reading image \""
227 << res.getImage()->getFileName() << "\"");
233 osg::Node* DefaultCachePolicy::find(const string& fileName,
234 const ReaderWriter::Options* opt)
236 Registry* registry = Registry::instance();
238 = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
240 SG_LOG(SG_IO, SG_INFO, "Got cached model \""
241 << fileName << "\"");
243 SG_LOG(SG_IO, SG_INFO, "Reading model \""
244 << fileName << "\"");
248 void DefaultCachePolicy::addToCache(const string& fileName,
251 Registry::instance()->addEntryToObjectCache(fileName, node);
254 // Optimizations we don't use:
255 // Don't use this one. It will break animation names ...
256 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
258 // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
259 // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
260 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
261 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
262 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
263 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
264 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
266 OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
267 _osgOptions(Optimizer::SHARE_DUPLICATE_STATE
268 | Optimizer::MERGE_GEOMETRY
269 | Optimizer::FLATTEN_STATIC_TRANSFORMS
270 | Optimizer::TRISTRIP_GEOMETRY)
274 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
275 const string& fileName,
276 const osgDB::ReaderWriter::Options* opt)
278 osgUtil::Optimizer optimizer;
279 optimizer.optimize(node, _osgOptions);
281 // Make sure the data variance of sharable objects is set to
282 // STATIC so that textures will be globally shared.
283 SGTexDataVarianceVisitor dataVarianceVisitor;
284 node->accept(dataVarianceVisitor);
286 SGTexCompressionVisitor texComp;
287 node->accept(texComp);
291 osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName,
292 const osgDB::ReaderWriter::Options* opt)
294 // Add an extra reference to the model stored in the database.
295 // That it to avoid expiring the object from the cache even if it is still
296 // in use. Note that the object cache will think that a model is unused
297 // if the reference count is 1. If we clone all structural nodes here
298 // we need that extra reference to the original object
299 SGDatabaseReference* databaseReference;
300 databaseReference = new SGDatabaseReference(model);
301 CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
302 flags &= ~CopyOp::DEEP_COPY_TEXTURES;
303 flags &= ~CopyOp::DEEP_COPY_IMAGES;
304 flags &= ~CopyOp::DEEP_COPY_STATESETS;
305 flags &= ~CopyOp::DEEP_COPY_STATEATTRIBUTES;
306 flags &= ~CopyOp::DEEP_COPY_ARRAYS;
307 flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
308 // This will safe display lists ...
309 flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
310 flags &= ~CopyOp::DEEP_COPY_SHAPES;
311 osg::Node* res = CopyOp(flags)(model);
312 res->addObserver(databaseReference);
315 SGTextureUpdateVisitor liveryUpdate(opt->getDatabasePathList());
316 res->accept(liveryUpdate);
320 string OSGSubstitutePolicy::substitute(const string& name,
321 const ReaderWriter::Options* opt)
323 string fileSansExtension = getNameLessExtension(name);
324 string osgFileName = fileSansExtension + ".osg";
325 string absFileName = findDataFile(osgFileName, opt);
329 ModelRegistry::ModelRegistry() :
330 _defaultCallback(new DefaultCallback(""))
335 ModelRegistry::addImageCallbackForExtension(const string& extension,
336 Registry::ReadFileCallback* callback)
338 imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
342 ModelRegistry::addNodeCallbackForExtension(const string& extension,
343 Registry::ReadFileCallback* callback)
345 nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
348 ref_ptr<ModelRegistry> ModelRegistry::instance;
350 ModelRegistry* ModelRegistry::getInstance()
353 if (!instance.valid())
354 instance = new ModelRegistry;
355 return instance.get();
358 ReaderWriter::ReadResult
359 ModelRegistry::readNode(const string& fileName,
360 const ReaderWriter::Options* opt)
362 OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(readerMutex);
363 Registry* registry = Registry::instance();
364 ReaderWriter::ReadResult res;
366 CallbackMap::iterator iter
367 = nodeCallbackMap.find(getFileExtension(fileName));
368 if (iter != nodeCallbackMap.end() && iter->second.valid())
369 return iter->second->readNode(fileName, opt);
370 return _defaultCallback->readNode(fileName, opt);
373 class SGReadCallbackInstaller {
375 SGReadCallbackInstaller()
377 // XXX I understand why we want this, but this seems like a weird
378 // place to set this option.
379 Referenced::setThreadSafeReferenceCounting(true);
381 Registry* registry = Registry::instance();
382 ReaderWriter::Options* options = new ReaderWriter::Options;
383 int cacheOptions = ReaderWriter::Options::CACHE_ALL;
385 setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
386 registry->setOptions(options);
387 registry->getOrCreateSharedStateManager()->
388 setShareMode(SharedStateManager::SHARE_STATESETS);
389 registry->setReadFileCallback(ModelRegistry::getInstance());
393 static SGReadCallbackInstaller readCallbackInstaller;
395 // we get optimal geometry from the loader.
396 struct ACOptimizePolicy : public OptimizeModelPolicy {
397 ACOptimizePolicy(const string& extension) :
398 OptimizeModelPolicy(extension)
400 _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
404 struct ACProcessPolicy {
405 ACProcessPolicy(const string& extension) {}
406 Node* process(Node* node, const string& filename,
407 const ReaderWriter::Options* opt)
413 // XXX Does there need to be a Group node here to trick the
414 // optimizer into optimizing the static transform?
415 osg::Group* root = new Group;
416 MatrixTransform* transform = new MatrixTransform;
417 root->addChild(transform);
419 transform->setDataVariance(Object::STATIC);
420 transform->setMatrix(m);
421 transform->addChild(node);
422 // Ok, this step is questionable.
423 // It is there to have the same visual appearance of ac objects for the
424 // first cut. Osg's ac3d loader will correctly set materials from the
425 // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
426 // materials that in effect igored the ambient part specified in the
427 // file. We emulate that for the first cut here by changing all
428 // ac models here. But in the long term we should use the
429 // unchanged model and fix the input files instead ...
430 SGAcMaterialCrippleVisitor matCriple;
431 root->accept(matCriple);
436 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
437 ACOptimizePolicy, DefaultCopyPolicy,
438 OSGSubstitutePolicy> ACCallback;
442 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");