1 #include "ModelRegistry.hxx"
3 #include <osg/observer_ptr>
6 #include <osg/NodeCallback>
8 #include <osg/Material>
9 #include <osg/MatrixTransform>
10 #include <osgDB/Archive>
11 #include <osgDB/FileNameUtils>
12 #include <osgDB/FileUtils>
13 #include <osgDB/ReadFile>
14 #include <osgDB/WriteFile>
15 #include <osgDB/Registry>
16 #include <osgDB/SharedStateManager>
17 #include <osgUtil/Optimizer>
19 #include <simgear/scene/util/SGSceneFeatures.hxx>
20 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
21 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
23 #include <simgear/structure/exception.hxx>
24 #include <simgear/props/props.hxx>
25 #include <simgear/props/props_io.hxx>
26 #include <simgear/props/condition.hxx>
30 using namespace osgDB;
31 using namespace simgear;
33 // Little helper class that holds an extra reference to a
35 // Since we clone all structural nodes from our 3d models,
36 // the database pager will only see one single reference to
37 // top node of the model and expire it relatively fast.
38 // We attach that extra reference to every model cloned from
39 // a base model in the pager. When that cloned model is deleted
40 // this extra reference is deleted too. So if there are no
41 // cloned models left the model will expire.
43 class SGDatabaseReference : public Observer {
45 SGDatabaseReference(Referenced* referenced) :
46 mReferenced(referenced)
48 virtual void objectDeleted(void*)
53 ref_ptr<Referenced> mReferenced;
57 class SGTextureUpdateVisitor : public SGTextureStateAttributeVisitor {
59 SGTextureUpdateVisitor(const FilePathList& pathList) :
62 Texture2D* textureReplace(int unit,
63 StateSet::RefAttributePair& refAttr)
66 texture = dynamic_cast<Texture2D*>(refAttr.first.get());
70 ref_ptr<Image> image = texture->getImage(0);
74 // The currently loaded file name
75 string fullFilePath = image->getFileName();
77 string fileName = getSimpleFileName(fullFilePath);
78 // The name that should be found with the current database path
79 string fullLiveryFile = findFileInPath(fileName, mPathList);
80 // If they are identical then there is nothing to do
81 if (fullLiveryFile == fullFilePath)
84 image = readImageFile(fullLiveryFile);
88 CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
89 texture = static_cast<Texture2D*>(copyOp(texture));
92 texture->setImage(image.get());
95 virtual void apply(StateSet* stateSet)
100 // get a copy that we can safely modify the statesets values.
101 StateSet::TextureAttributeList attrList;
102 attrList = stateSet->getTextureAttributeList();
103 for (unsigned unit = 0; unit < attrList.size(); ++unit) {
104 StateSet::AttributeList::iterator i = attrList[unit].begin();
105 while (i != attrList[unit].end()) {
106 Texture2D* texture = textureReplace(unit, i->second);
108 stateSet->removeTextureAttribute(unit, i->second.first.get());
109 stateSet->setTextureAttribute(unit, texture, i->second.second);
110 stateSet->setTextureMode(unit, GL_TEXTURE_2D, StateAttribute::ON);
118 FilePathList mPathList;
121 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
123 virtual void apply(int, StateSet::RefAttributePair& refAttr)
126 texture = dynamic_cast<Texture2D*>(refAttr.first.get());
131 texture->setDataVariance(osg::Object::STATIC);
133 Image* image = texture->getImage(0);
140 if (s <= t && 32 <= s) {
141 SGSceneFeatures::instance()->setTextureCompression(texture);
142 } else if (t < s && 32 <= t) {
143 SGSceneFeatures::instance()->setTextureCompression(texture);
148 class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
150 virtual void apply(int, StateSet::RefAttributePair& refAttr)
153 texture = dynamic_cast<Texture*>(refAttr.first.get());
157 texture->setDataVariance(Object::STATIC);
161 class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
163 virtual void apply(StateSet::RefAttributePair& refAttr)
166 material = dynamic_cast<Material*>(refAttr.first.get());
169 material->setColorMode(Material::AMBIENT_AND_DIFFUSE);
174 ReaderWriter::ReadResult
175 ModelRegistry::readImage(const string& fileName,
176 const ReaderWriter::Options* opt)
178 CallbackMap::iterator iter
179 = imageCallbackMap.find(getFileExtension(fileName));
180 if (iter != imageCallbackMap.end() && iter->second.valid())
181 return iter->second->readImage(fileName, opt);
182 string absFileName = findDataFile(fileName);
183 if (!fileExists(absFileName)) {
184 SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
185 << fileName << "\"");
186 return ReaderWriter::ReadResult::FILE_NOT_FOUND;
189 Registry* registry = Registry::instance();
190 ReaderWriter::ReadResult res;
191 res = registry->readImageImplementation(absFileName, opt);
192 if (res.loadedFromCache())
193 SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
194 << res.getImage()->getFileName() << "\"");
196 SG_LOG(SG_IO, SG_INFO, "Reading image \""
197 << res.getImage()->getFileName() << "\"");
202 ReaderWriter::ReadResult
203 ModelRegistry::readNode(const string& fileName,
204 const ReaderWriter::Options* opt)
206 Registry* registry = Registry::instance();
207 ReaderWriter::ReadResult res;
209 CallbackMap::iterator iter
210 = nodeCallbackMap.find(getFileExtension(fileName));
211 if (iter != nodeCallbackMap.end() && iter->second.valid())
212 return iter->second->readNode(fileName, opt);
213 // First, look for a file with the same name, and the extension
214 // ".osg" and, if it exists, load it instead. This allows for
215 // substitution of optimized models for ones named in the scenery.
216 bool optimizeModel = true;
217 string fileSansExtension = getNameLessExtension(fileName);
218 string osgFileName = fileSansExtension + ".osg";
219 string absFileName = findDataFile(osgFileName);
220 // The absolute file name is passed to the reader plugin, which
221 // calls findDataFile again... but that's OK, it should find the
222 // file by its absolute path straight away.
223 if (fileExists(absFileName)) {
224 optimizeModel = false;
226 absFileName = findDataFile(fileName);
228 if (!fileExists(absFileName)) {
229 SG_LOG(SG_IO, SG_ALERT, "Cannot find model file \""
230 << fileName << "\"");
231 return ReaderWriter::ReadResult::FILE_NOT_FOUND;
234 = dynamic_cast<Node*>(registry->getFromObjectCache(absFileName));
236 SG_LOG(SG_IO, SG_INFO, "Got cached model \""
237 << absFileName << "\"");
239 SG_LOG(SG_IO, SG_INFO, "Reading model \""
240 << absFileName << "\"");
241 res = registry->readNodeImplementation(absFileName, opt);
242 if (!res.validNode())
245 bool needTristrip = true;
246 if (getLowerCaseFileExtension(fileName) == "ac") {
247 // we get optimal geometry from the loader.
248 needTristrip = false;
254 ref_ptr<Group> root = new Group;
255 MatrixTransform* transform = new MatrixTransform;
256 root->addChild(transform);
258 transform->setDataVariance(Object::STATIC);
259 transform->setMatrix(m);
260 transform->addChild(res.getNode());
262 res = ReaderWriter::ReadResult(0);
265 osgUtil::Optimizer optimizer;
266 unsigned opts = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
267 optimizer.optimize(root.get(), opts);
270 // strip away unneeded groups
271 if (root->getNumChildren() == 1 && root->getName().empty()) {
272 res = ReaderWriter::ReadResult(root->getChild(0));
274 res = ReaderWriter::ReadResult(root.get());
276 // Ok, this step is questionable.
277 // It is there to have the same visual appearance of ac objects for the
278 // first cut. Osg's ac3d loader will correctly set materials from the
279 // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
280 // materials that in effect igored the ambient part specified in the
281 // file. We emulate that for the first cut here by changing all
282 // ac models here. But in the long term we should use the
283 // unchanged model and fix the input files instead ...
284 SGAcMaterialCrippleVisitor matCriple;
285 res.getNode()->accept(matCriple);
289 osgUtil::Optimizer optimizer;
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::SHARE_DUPLICATE_STATE;
297 opts |= osgUtil::Optimizer::MERGE_GEOMETRY;
298 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
299 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
300 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
301 opts |= osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
303 opts |= osgUtil::Optimizer::TRISTRIP_GEOMETRY;
304 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
305 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
306 optimizer.optimize(res.getNode(), opts);
308 // Make sure the data variance of sharable objects is set to STATIC ...
309 SGTexDataVarianceVisitor dataVarianceVisitor;
310 res.getNode()->accept(dataVarianceVisitor);
311 // ... so that textures are now globally shared
312 registry->getSharedStateManager()->share(res.getNode());
314 SGTexCompressionVisitor texComp;
315 res.getNode()->accept(texComp);
316 cached = res.getNode();
317 registry->addEntryToObjectCache(absFileName, cached);
319 // Add an extra reference to the model stored in the database.
320 // That it to avoid expiring the object from the cache even if it is still
321 // in use. Note that the object cache will think that a model is unused
322 // if the reference count is 1. If we clone all structural nodes here
323 // we need that extra reference to the original object
324 SGDatabaseReference* databaseReference;
325 databaseReference = new SGDatabaseReference(cached);
326 CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
327 flags &= ~CopyOp::DEEP_COPY_TEXTURES;
328 flags &= ~CopyOp::DEEP_COPY_IMAGES;
329 flags &= ~CopyOp::DEEP_COPY_ARRAYS;
330 flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
331 // This will safe display lists ...
332 flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
333 flags &= ~CopyOp::DEEP_COPY_SHAPES;
334 res = ReaderWriter::ReadResult(CopyOp(flags)(cached));
335 res.getNode()->addObserver(databaseReference);
338 SGTextureUpdateVisitor liveryUpdate(getDataFilePathList());
339 res.getNode()->accept(liveryUpdate);
341 // Make sure the data variance of sharable objects is set to STATIC ...
342 SGTexDataVarianceVisitor dataVarianceVisitor;
343 res.getNode()->accept(dataVarianceVisitor);
344 // ... so that textures are now globally shared
345 registry->getOrCreateSharedStateManager()->share(res.getNode(), 0);
351 ModelRegistry::addImageCallbackForExtension(const string& extension,
352 Registry::ReadFileCallback* callback)
354 imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
358 ModelRegistry::addNodeCallbackForExtension(const string& extension,
359 Registry::ReadFileCallback* callback)
361 nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
364 ref_ptr<ModelRegistry> ModelRegistry::instance;
366 ModelRegistry* ModelRegistry::getInstance()
369 if (!instance.valid())
370 instance = new ModelRegistry;
371 return instance.get();
374 class SGReadCallbackInstaller {
376 SGReadCallbackInstaller()
378 // XXX I understand why we want this, but this seems like a weird
379 // place to set this option.
380 Referenced::setThreadSafeReferenceCounting(true);
382 Registry* registry = Registry::instance();
383 ReaderWriter::Options* options = new ReaderWriter::Options;
384 // We manage node caching ourselves
385 int cacheOptions = ReaderWriter::Options::CACHE_ALL
386 & ~ReaderWriter::Options::CACHE_NODES;
388 setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
389 registry->setOptions(options);
390 registry->getOrCreateSharedStateManager()->
391 setShareMode(SharedStateManager::SHARE_TEXTURES);
392 registry->setReadFileCallback(ModelRegistry::getInstance());
396 static SGReadCallbackInstaller readCallbackInstaller;
398 ReaderWriter::ReadResult
399 OSGFileCallback::readImage(const string& fileName,
400 const ReaderWriter::Options* opt)
402 return Registry::instance()->readImageImplementation(fileName, opt);
405 ReaderWriter::ReadResult
406 OSGFileCallback::readNode(const string& fileName,
407 const ReaderWriter::Options* opt)
409 return Registry::instance()->readNodeImplementation(fileName, opt);