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.
21 # include <simgear_config.h>
24 #include "ModelRegistry.hxx"
30 #include <OpenThreads/ScopedLock>
32 #include <osg/ref_ptr>
34 #include <osg/NodeCallback>
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>
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>
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>
57 #include "BoundingVolumeBuildVisitor.hxx"
61 using namespace osgUtil;
62 using namespace osgDB;
63 using namespace simgear;
65 // Little helper class that holds an extra reference to a
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.
75 class SGDatabaseReference : public Observer {
77 SGDatabaseReference(Referenced* referenced) :
78 mReferenced(referenced)
80 virtual void objectDeleted(void*)
85 ref_ptr<Referenced> mReferenced;
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 {
93 TextureNameVisitor(NodeVisitor::TraversalMode tm = NodeVisitor::TRAVERSE_ALL_CHILDREN) :
94 NodeAndDrawableVisitor(tm)
98 virtual void apply(Node& node)
100 nameTextures(node.getStateSet());
104 virtual void apply(Drawable& drawable)
106 nameTextures(drawable.getStateSet());
109 void nameTextures(StateSet* stateSet)
113 int numUnits = stateSet->getTextureAttributeList().size();
114 for (int i = 0; i < numUnits; ++i) {
116 = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE);
117 Texture2D* texture = dynamic_cast<Texture2D*>(attr);
118 if (!texture || !texture->getName().empty())
120 const Image *image = texture->getImage();
123 texture->setName(image->getFileName());
129 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
131 virtual void apply(int, StateSet::RefAttributePair& refAttr)
134 texture = dynamic_cast<Texture2D*>(refAttr.first.get());
138 // Do not touch dynamically generated textures.
139 if (texture->getReadPBuffer())
141 if (texture->getDataVariance() == osg::Object::DYNAMIC)
144 // If no image attached, we assume this one is dynamically generated
145 Image* image = texture->getImage(0);
152 if (s <= t && 32 <= s) {
153 SGSceneFeatures::instance()->setTextureCompression(texture);
154 } else if (t < s && 32 <= t) {
155 SGSceneFeatures::instance()->setTextureCompression(texture);
160 class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
162 virtual void apply(int, StateSet::RefAttributePair& refAttr)
165 texture = dynamic_cast<Texture*>(refAttr.first.get());
169 // Cannot be static if this is a render to texture thing
170 if (texture->getReadPBuffer())
172 if (texture->getDataVariance() == osg::Object::DYNAMIC)
174 // If no image attached, we assume this one is dynamically generated
175 Image* image = texture->getImage(0);
179 texture->setDataVariance(Object::STATIC);
182 virtual void apply(StateSet* stateSet)
186 stateSet->setDataVariance(Object::STATIC);
187 SGTextureStateAttributeVisitor::apply(stateSet);
193 Node* DefaultProcessPolicy::process(Node* node, const string& filename,
194 const ReaderWriter::Options* opt)
196 TextureNameVisitor nameVisitor;
197 node->accept(nameVisitor);
201 ReaderWriter::ReadResult
202 ModelRegistry::readImage(const string& fileName,
203 const ReaderWriter::Options* opt)
205 CallbackMap::iterator iter
206 = imageCallbackMap.find(getFileExtension(fileName));
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;
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());
225 if (res.loadedFromCache())
226 SG_LOG(SG_IO, SG_BULK, "Returning cached image \""
227 << res.getImage()->getFileName() << "\"");
229 SG_LOG(SG_IO, SG_BULK, "Reading image \""
230 << res.getImage()->getFileName() << "\"");
237 osg::Node* DefaultCachePolicy::find(const string& fileName,
238 const ReaderWriter::Options* opt)
240 Registry* registry = Registry::instance();
242 = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
244 SG_LOG(SG_IO, SG_BULK, "Got cached model \""
245 << fileName << "\"");
247 SG_LOG(SG_IO, SG_BULK, "Reading model \""
248 << fileName << "\"");
252 void DefaultCachePolicy::addToCache(const string& fileName,
255 Registry::instance()->addEntryToObjectCache(fileName, node);
258 // Optimizations we don't use:
259 // Don't use this one. It will break animation names ...
260 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
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;
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)
278 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
279 const string& fileName,
280 const osgDB::ReaderWriter::Options* opt)
282 osgUtil::Optimizer optimizer;
283 optimizer.optimize(node, _osgOptions);
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);
290 SGTexCompressionVisitor texComp;
291 node->accept(texComp);
295 string OSGSubstitutePolicy::substitute(const string& name,
296 const ReaderWriter::Options* opt)
298 string fileSansExtension = getNameLessExtension(name);
299 string osgFileName = fileSansExtension + ".osg";
300 string absFileName = findDataFile(osgFileName, opt);
306 BuildLeafBVHPolicy::buildBVH(const std::string& fileName, osg::Node* node)
308 SG_LOG(SG_IO, SG_BULK, "Building leaf attached boundingvolume tree for \""
309 << fileName << "\".");
310 BoundingVolumeBuildVisitor bvBuilder(true);
311 node->accept(bvBuilder);
315 BuildGroupBVHPolicy::buildBVH(const std::string& fileName, osg::Node* node)
317 SG_LOG(SG_IO, SG_BULK, "Building group attached boundingvolume tree for \""
318 << fileName << "\".");
319 BoundingVolumeBuildVisitor bvBuilder(false);
320 node->accept(bvBuilder);
324 NoBuildBVHPolicy::buildBVH(const std::string& fileName, osg::Node*)
326 SG_LOG(SG_IO, SG_BULK, "Omitting boundingvolume tree for \""
327 << fileName << "\".");
330 ModelRegistry::ModelRegistry() :
331 _defaultCallback(new DefaultCallback(""))
336 ModelRegistry::addImageCallbackForExtension(const string& extension,
337 Registry::ReadFileCallback* callback)
339 imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
343 ModelRegistry::addNodeCallbackForExtension(const string& extension,
344 Registry::ReadFileCallback* callback)
346 nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
349 ReaderWriter::ReadResult
350 ModelRegistry::readNode(const string& fileName,
351 const ReaderWriter::Options* opt)
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);
360 result = _defaultCallback->readNode(fileName, opt);
365 class SGReadCallbackInstaller {
367 SGReadCallbackInstaller()
369 // XXX I understand why we want this, but this seems like a weird
370 // place to set this option.
371 Referenced::setThreadSafeReferenceCounting(true);
373 Registry* registry = Registry::instance();
374 ReaderWriter::Options* options = new ReaderWriter::Options;
375 int cacheOptions = ReaderWriter::Options::CACHE_ALL;
377 setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
378 registry->setOptions(options);
379 registry->getOrCreateSharedStateManager()->
380 setShareMode(SharedStateManager::SHARE_STATESETS);
381 registry->setReadFileCallback(ModelRegistry::instance());
385 static SGReadCallbackInstaller readCallbackInstaller;
387 // we get optimal geometry from the loader.
388 struct ACOptimizePolicy : public OptimizeModelPolicy {
389 ACOptimizePolicy(const string& extension) :
390 OptimizeModelPolicy(extension)
392 _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
394 Node* optimize(Node* node, const string& fileName,
395 const ReaderWriter::Options* opt)
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));
411 return optimized.release();
415 struct ACProcessPolicy {
416 ACProcessPolicy(const string& extension) {}
417 Node* process(Node* node, const string& filename,
418 const ReaderWriter::Options* opt)
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);
430 transform->setDataVariance(Object::STATIC);
431 transform->setMatrix(m);
432 transform->addChild(node);
438 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
440 OSGSubstitutePolicy, BuildLeafBVHPolicy>
445 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");