Move SGReadFileCallback from model.cxx to public class ModelRegistry
Move SGReadFileCallback and all its help classes into a new ModelRegistry class that also provides an interface to add custom callbacks for specific file extensions. SGReaderWriterBTG uses that to keep any further processing from being done on .btg files. Various namespace-releated cleanup was done on this code too.
This commit is contained in:
parent
cafcecf03d
commit
2fbaddbecf
@ -9,6 +9,7 @@ include_HEADERS = \
|
||||
location.hxx \
|
||||
model.hxx \
|
||||
modellib.hxx \
|
||||
ModelRegistry.hxx \
|
||||
persparam.hxx \
|
||||
placement.hxx \
|
||||
placementtrans.hxx \
|
||||
@ -23,6 +24,7 @@ libsgmodel_a_SOURCES = \
|
||||
location.cxx \
|
||||
model.cxx \
|
||||
modellib.cxx \
|
||||
ModelRegistry.cxx \
|
||||
persparam.cxx \
|
||||
placement.cxx \
|
||||
placementtrans.cxx \
|
||||
|
410
simgear/scene/model/ModelRegistry.cxx
Normal file
410
simgear/scene/model/ModelRegistry.cxx
Normal file
@ -0,0 +1,410 @@
|
||||
#include "ModelRegistry.hxx"
|
||||
|
||||
#include <osg/observer_ptr>
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Group>
|
||||
#include <osg/NodeCallback>
|
||||
#include <osg/Switch>
|
||||
#include <osg/Material>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osgDB/Archive>
|
||||
#include <osgDB/FileNameUtils>
|
||||
#include <osgDB/FileUtils>
|
||||
#include <osgDB/ReadFile>
|
||||
#include <osgDB/WriteFile>
|
||||
#include <osgDB/Registry>
|
||||
#include <osgDB/SharedStateManager>
|
||||
#include <osgUtil/Optimizer>
|
||||
|
||||
#include <simgear/scene/util/SGSceneFeatures.hxx>
|
||||
#include <simgear/scene/util/SGStateAttributeVisitor.hxx>
|
||||
#include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/props/condition.hxx>
|
||||
|
||||
using namespace std;
|
||||
using namespace osg;
|
||||
using namespace osgDB;
|
||||
using namespace simgear;
|
||||
|
||||
// Little helper class that holds an extra reference to a
|
||||
// loaded 3d model.
|
||||
// Since we clone all structural nodes from our 3d models,
|
||||
// the database pager will only see one single reference to
|
||||
// top node of the model and expire it relatively fast.
|
||||
// We attach that extra reference to every model cloned from
|
||||
// a base model in the pager. When that cloned model is deleted
|
||||
// this extra reference is deleted too. So if there are no
|
||||
// cloned models left the model will expire.
|
||||
namespace {
|
||||
class SGDatabaseReference : public Observer {
|
||||
public:
|
||||
SGDatabaseReference(Referenced* referenced) :
|
||||
mReferenced(referenced)
|
||||
{ }
|
||||
virtual void objectDeleted(void*)
|
||||
{
|
||||
mReferenced = 0;
|
||||
}
|
||||
private:
|
||||
ref_ptr<Referenced> mReferenced;
|
||||
};
|
||||
|
||||
// Visitor for
|
||||
class SGTextureUpdateVisitor : public SGTextureStateAttributeVisitor {
|
||||
public:
|
||||
SGTextureUpdateVisitor(const FilePathList& pathList) :
|
||||
mPathList(pathList)
|
||||
{ }
|
||||
Texture2D* textureReplace(int unit,
|
||||
StateSet::RefAttributePair& refAttr)
|
||||
{
|
||||
Texture2D* texture;
|
||||
texture = dynamic_cast<Texture2D*>(refAttr.first.get());
|
||||
if (!texture)
|
||||
return 0;
|
||||
|
||||
ref_ptr<Image> image = texture->getImage(0);
|
||||
if (!image)
|
||||
return 0;
|
||||
|
||||
// The currently loaded file name
|
||||
string fullFilePath = image->getFileName();
|
||||
// The short name
|
||||
string fileName = getSimpleFileName(fullFilePath);
|
||||
// The name that should be found with the current database path
|
||||
string fullLiveryFile = findFileInPath(fileName, mPathList);
|
||||
// If they are identical then there is nothing to do
|
||||
if (fullLiveryFile == fullFilePath)
|
||||
return 0;
|
||||
|
||||
image = readImageFile(fullLiveryFile);
|
||||
if (!image)
|
||||
return 0;
|
||||
|
||||
CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
|
||||
texture = static_cast<Texture2D*>(copyOp(texture));
|
||||
if (!texture)
|
||||
return 0;
|
||||
texture->setImage(image.get());
|
||||
return texture;
|
||||
}
|
||||
virtual void apply(StateSet* stateSet)
|
||||
{
|
||||
if (!stateSet)
|
||||
return;
|
||||
|
||||
// get a copy that we can safely modify the statesets values.
|
||||
StateSet::TextureAttributeList attrList;
|
||||
attrList = stateSet->getTextureAttributeList();
|
||||
for (unsigned unit = 0; unit < attrList.size(); ++unit) {
|
||||
StateSet::AttributeList::iterator i = attrList[unit].begin();
|
||||
while (i != attrList[unit].end()) {
|
||||
Texture2D* texture = textureReplace(unit, i->second);
|
||||
if (texture) {
|
||||
stateSet->removeTextureAttribute(unit, i->second.first.get());
|
||||
stateSet->setTextureAttribute(unit, texture, i->second.second);
|
||||
stateSet->setTextureMode(unit, GL_TEXTURE_2D, StateAttribute::ON);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
FilePathList mPathList;
|
||||
};
|
||||
|
||||
class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
|
||||
public:
|
||||
virtual void apply(int, StateSet::RefAttributePair& refAttr)
|
||||
{
|
||||
Texture2D* texture;
|
||||
texture = dynamic_cast<Texture2D*>(refAttr.first.get());
|
||||
if (!texture)
|
||||
return;
|
||||
|
||||
// Hmm, true??
|
||||
texture->setDataVariance(osg::Object::STATIC);
|
||||
|
||||
Image* image = texture->getImage(0);
|
||||
if (!image)
|
||||
return;
|
||||
|
||||
int s = image->s();
|
||||
int t = image->t();
|
||||
|
||||
if (s <= t && 32 <= s) {
|
||||
SGSceneFeatures::instance()->setTextureCompression(texture);
|
||||
} else if (t < s && 32 <= t) {
|
||||
SGSceneFeatures::instance()->setTextureCompression(texture);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
|
||||
public:
|
||||
virtual void apply(int, StateSet::RefAttributePair& refAttr)
|
||||
{
|
||||
Texture* texture;
|
||||
texture = dynamic_cast<Texture*>(refAttr.first.get());
|
||||
if (!texture)
|
||||
return;
|
||||
|
||||
texture->setDataVariance(Object::STATIC);
|
||||
}
|
||||
};
|
||||
|
||||
class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
|
||||
public:
|
||||
virtual void apply(StateSet::RefAttributePair& refAttr)
|
||||
{
|
||||
Material* material;
|
||||
material = dynamic_cast<Material*>(refAttr.first.get());
|
||||
if (!material)
|
||||
return;
|
||||
material->setColorMode(Material::AMBIENT_AND_DIFFUSE);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
ReaderWriter::ReadResult
|
||||
ModelRegistry::readImage(const string& fileName,
|
||||
const ReaderWriter::Options* opt)
|
||||
{
|
||||
CallbackMap::iterator iter
|
||||
= imageCallbackMap.find(getFileExtension(fileName));
|
||||
if (iter != imageCallbackMap.end() && iter->second.valid())
|
||||
return iter->second->readImage(fileName, opt);
|
||||
string absFileName = findDataFile(fileName);
|
||||
if (!fileExists(absFileName)) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
|
||||
<< fileName << "\"");
|
||||
return ReaderWriter::ReadResult::FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
Registry* registry = Registry::instance();
|
||||
ReaderWriter::ReadResult res;
|
||||
res = registry->readImageImplementation(absFileName, opt);
|
||||
if (res.loadedFromCache())
|
||||
SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
|
||||
<< res.getImage()->getFileName() << "\"");
|
||||
else
|
||||
SG_LOG(SG_IO, SG_INFO, "Reading image \""
|
||||
<< res.getImage()->getFileName() << "\"");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ReaderWriter::ReadResult
|
||||
ModelRegistry::readNode(const string& fileName,
|
||||
const ReaderWriter::Options* opt)
|
||||
{
|
||||
Registry* registry = Registry::instance();
|
||||
ReaderWriter::ReadResult res;
|
||||
Node* cached = 0;
|
||||
CallbackMap::iterator iter
|
||||
= nodeCallbackMap.find(getFileExtension(fileName));
|
||||
if (iter != nodeCallbackMap.end() && iter->second.valid())
|
||||
return iter->second->readNode(fileName, opt);
|
||||
// First, look for a file with the same name, and the extension
|
||||
// ".osg" and, if it exists, load it instead. This allows for
|
||||
// substitution of optimized models for ones named in the scenery.
|
||||
bool optimizeModel = true;
|
||||
string fileSansExtension = getNameLessExtension(fileName);
|
||||
string osgFileName = fileSansExtension + ".osg";
|
||||
string absFileName = findDataFile(osgFileName);
|
||||
// The absolute file name is passed to the reader plugin, which
|
||||
// calls findDataFile again... but that's OK, it should find the
|
||||
// file by its absolute path straight away.
|
||||
if (fileExists(absFileName)) {
|
||||
optimizeModel = false;
|
||||
} else {
|
||||
absFileName = findDataFile(fileName);
|
||||
}
|
||||
if (!fileExists(absFileName)) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Cannot find model file \""
|
||||
<< fileName << "\"");
|
||||
return ReaderWriter::ReadResult::FILE_NOT_FOUND;
|
||||
}
|
||||
cached
|
||||
= dynamic_cast<Node*>(registry->getFromObjectCache(absFileName));
|
||||
if (cached) {
|
||||
SG_LOG(SG_IO, SG_INFO, "Got cached model \""
|
||||
<< absFileName << "\"");
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_INFO, "Reading model \""
|
||||
<< absFileName << "\"");
|
||||
res = registry->readNodeImplementation(absFileName, opt);
|
||||
if (!res.validNode())
|
||||
return res;
|
||||
|
||||
bool needTristrip = true;
|
||||
if (getLowerCaseFileExtension(fileName) == "ac") {
|
||||
// we get optimal geometry from the loader.
|
||||
needTristrip = false;
|
||||
Matrix m(1, 0, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, -1, 0, 0,
|
||||
0, 0, 0, 1);
|
||||
|
||||
ref_ptr<Group> root = new Group;
|
||||
MatrixTransform* transform = new MatrixTransform;
|
||||
root->addChild(transform);
|
||||
|
||||
transform->setDataVariance(Object::STATIC);
|
||||
transform->setMatrix(m);
|
||||
transform->addChild(res.getNode());
|
||||
|
||||
res = ReaderWriter::ReadResult(0);
|
||||
|
||||
if (optimizeModel) {
|
||||
osgUtil::Optimizer optimizer;
|
||||
unsigned opts = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
|
||||
optimizer.optimize(root.get(), opts);
|
||||
}
|
||||
|
||||
// strip away unneeded groups
|
||||
if (root->getNumChildren() == 1 && root->getName().empty()) {
|
||||
res = ReaderWriter::ReadResult(root->getChild(0));
|
||||
} else
|
||||
res = ReaderWriter::ReadResult(root.get());
|
||||
|
||||
// Ok, this step is questionable.
|
||||
// It is there to have the same visual appearance of ac objects for the
|
||||
// first cut. Osg's ac3d loader will correctly set materials from the
|
||||
// ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
|
||||
// materials that in effect igored the ambient part specified in the
|
||||
// file. We emulate that for the first cut here by changing all
|
||||
// ac models here. But in the long term we should use the
|
||||
// unchanged model and fix the input files instead ...
|
||||
SGAcMaterialCrippleVisitor matCriple;
|
||||
res.getNode()->accept(matCriple);
|
||||
}
|
||||
|
||||
if (optimizeModel) {
|
||||
osgUtil::Optimizer optimizer;
|
||||
unsigned opts = 0;
|
||||
// Don't use this one. It will break animation names ...
|
||||
// opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
|
||||
|
||||
// opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
|
||||
// opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
|
||||
// opts |= osgUtil::Optimizer::SHARE_DUPLICATE_STATE;
|
||||
opts |= osgUtil::Optimizer::MERGE_GEOMETRY;
|
||||
// opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
|
||||
// opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
|
||||
// opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
|
||||
opts |= osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
|
||||
if (needTristrip)
|
||||
opts |= osgUtil::Optimizer::TRISTRIP_GEOMETRY;
|
||||
// opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
|
||||
// opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
|
||||
optimizer.optimize(res.getNode(), opts);
|
||||
}
|
||||
// Make sure the data variance of sharable objects is set to STATIC ...
|
||||
SGTexDataVarianceVisitor dataVarianceVisitor;
|
||||
res.getNode()->accept(dataVarianceVisitor);
|
||||
// ... so that textures are now globally shared
|
||||
registry->getSharedStateManager()->share(res.getNode());
|
||||
|
||||
SGTexCompressionVisitor texComp;
|
||||
res.getNode()->accept(texComp);
|
||||
cached = res.getNode();
|
||||
registry->addEntryToObjectCache(absFileName, cached);
|
||||
}
|
||||
// Add an extra reference to the model stored in the database.
|
||||
// That it to avoid expiring the object from the cache even if it is still
|
||||
// in use. Note that the object cache will think that a model is unused
|
||||
// if the reference count is 1. If we clone all structural nodes here
|
||||
// we need that extra reference to the original object
|
||||
SGDatabaseReference* databaseReference;
|
||||
databaseReference = new SGDatabaseReference(cached);
|
||||
CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
|
||||
flags &= ~CopyOp::DEEP_COPY_TEXTURES;
|
||||
flags &= ~CopyOp::DEEP_COPY_IMAGES;
|
||||
flags &= ~CopyOp::DEEP_COPY_ARRAYS;
|
||||
flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
|
||||
// This will safe display lists ...
|
||||
flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
|
||||
flags &= ~CopyOp::DEEP_COPY_SHAPES;
|
||||
res = ReaderWriter::ReadResult(CopyOp(flags)(cached));
|
||||
res.getNode()->addObserver(databaseReference);
|
||||
|
||||
// Update liveries
|
||||
SGTextureUpdateVisitor liveryUpdate(getDataFilePathList());
|
||||
res.getNode()->accept(liveryUpdate);
|
||||
|
||||
// Make sure the data variance of sharable objects is set to STATIC ...
|
||||
SGTexDataVarianceVisitor dataVarianceVisitor;
|
||||
res.getNode()->accept(dataVarianceVisitor);
|
||||
// ... so that textures are now globally shared
|
||||
registry->getOrCreateSharedStateManager()->share(res.getNode(), 0);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
ModelRegistry::addImageCallbackForExtension(const string& extension,
|
||||
Registry::ReadFileCallback* callback)
|
||||
{
|
||||
imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
|
||||
}
|
||||
|
||||
void
|
||||
ModelRegistry::addNodeCallbackForExtension(const string& extension,
|
||||
Registry::ReadFileCallback* callback)
|
||||
{
|
||||
nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
|
||||
}
|
||||
|
||||
ref_ptr<ModelRegistry> ModelRegistry::instance;
|
||||
|
||||
ModelRegistry* ModelRegistry::getInstance()
|
||||
|
||||
{
|
||||
if (!instance.valid())
|
||||
instance = new ModelRegistry;
|
||||
return instance.get();
|
||||
}
|
||||
|
||||
class SGReadCallbackInstaller {
|
||||
public:
|
||||
SGReadCallbackInstaller()
|
||||
{
|
||||
// XXX I understand why we want this, but this seems like a weird
|
||||
// place to set this option.
|
||||
Referenced::setThreadSafeReferenceCounting(true);
|
||||
|
||||
Registry* registry = Registry::instance();
|
||||
ReaderWriter::Options* options = new ReaderWriter::Options;
|
||||
// We manage node caching ourselves
|
||||
int cacheOptions = ReaderWriter::Options::CACHE_ALL
|
||||
& ~ReaderWriter::Options::CACHE_NODES;
|
||||
options->
|
||||
setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
|
||||
registry->setOptions(options);
|
||||
registry->getOrCreateSharedStateManager()->
|
||||
setShareMode(SharedStateManager::SHARE_TEXTURES);
|
||||
registry->setReadFileCallback(ModelRegistry::getInstance());
|
||||
}
|
||||
};
|
||||
|
||||
static SGReadCallbackInstaller readCallbackInstaller;
|
||||
|
||||
ReaderWriter::ReadResult
|
||||
OSGFileCallback::readImage(const string& fileName,
|
||||
const ReaderWriter::Options* opt)
|
||||
{
|
||||
return Registry::instance()->readImageImplementation(fileName, opt);
|
||||
}
|
||||
|
||||
ReaderWriter::ReadResult
|
||||
OSGFileCallback::readNode(const string& fileName,
|
||||
const ReaderWriter::Options* opt)
|
||||
{
|
||||
return Registry::instance()->readNodeImplementation(fileName, opt);
|
||||
}
|
82
simgear/scene/model/ModelRegistry.hxx
Normal file
82
simgear/scene/model/ModelRegistry.hxx
Normal file
@ -0,0 +1,82 @@
|
||||
// ModelRegistry.hxx -- interface to the OSG model registry
|
||||
//
|
||||
// Copyright (C) 2007 Tim Moore <timoore@redhat.com>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#ifndef _SG_MODELREGISTRY_HXX
|
||||
#define _SG_MODELREGISTRY_HXX 1
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
#include <osgDB/ReaderWriter>
|
||||
#include <osgDB/Registry>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include STL_STRING
|
||||
#include <map>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
class ModelRegistry : public osgDB::Registry::ReadFileCallback {
|
||||
public:
|
||||
virtual osgDB::ReaderWriter::ReadResult
|
||||
readImage(const std::string& fileName,
|
||||
const osgDB::ReaderWriter::Options* opt);
|
||||
virtual osgDB::ReaderWriter::ReadResult
|
||||
readNode(const std::string& fileName,
|
||||
const osgDB::ReaderWriter::Options* opt);
|
||||
void addImageCallbackForExtension(const std::string& extension,
|
||||
osgDB::Registry::ReadFileCallback*
|
||||
callback);
|
||||
void addNodeCallbackForExtension(const std::string& extension,
|
||||
osgDB::Registry::ReadFileCallback*
|
||||
callback);
|
||||
static ModelRegistry* getInstance();
|
||||
protected:
|
||||
static osg::ref_ptr<ModelRegistry> instance;
|
||||
typedef std::map<std::string, osg::ref_ptr<osgDB::Registry::ReadFileCallback> >
|
||||
CallbackMap;
|
||||
CallbackMap imageCallbackMap;
|
||||
CallbackMap nodeCallbackMap;
|
||||
};
|
||||
|
||||
// Proxy for registering extension-based callbacks
|
||||
|
||||
template<typename T>
|
||||
class ModelRegistryCallbackProxy
|
||||
{
|
||||
public:
|
||||
ModelRegistryCallbackProxy(std::string extension)
|
||||
{
|
||||
ModelRegistry::getInstance()->addNodeCallbackForExtension(extension,
|
||||
new T);
|
||||
}
|
||||
};
|
||||
|
||||
// Callback for file extensions that load files using the default OSG
|
||||
// implementation.
|
||||
|
||||
class OSGFileCallback : public osgDB::Registry::ReadFileCallback {
|
||||
public:
|
||||
virtual osgDB::ReaderWriter::ReadResult
|
||||
readImage(const std::string& fileName,
|
||||
const osgDB::ReaderWriter::Options* opt);
|
||||
virtual osgDB::ReaderWriter::ReadResult
|
||||
readNode(const std::string& fileName,
|
||||
const osgDB::ReaderWriter::Options* opt);
|
||||
};
|
||||
|
||||
}
|
||||
#endif // _SG_MODELREGISTRY_HXX
|
@ -35,346 +35,6 @@
|
||||
#include "model.hxx"
|
||||
|
||||
SG_USING_STD(vector);
|
||||
SG_USING_STD(set);
|
||||
|
||||
// Little helper class that holds an extra reference to a
|
||||
// loaded 3d model.
|
||||
// Since we clone all structural nodes from our 3d models,
|
||||
// the database pager will only see one single reference to
|
||||
// top node of the model and expire it relatively fast.
|
||||
// We attach that extra reference to every model cloned from
|
||||
// a base model in the pager. When that cloned model is deleted
|
||||
// this extra reference is deleted too. So if there are no
|
||||
// cloned models left the model will expire.
|
||||
class SGDatabaseReference : public osg::Observer {
|
||||
public:
|
||||
SGDatabaseReference(osg::Referenced* referenced) :
|
||||
mReferenced(referenced)
|
||||
{ }
|
||||
virtual void objectDeleted(void*)
|
||||
{
|
||||
mReferenced = 0;
|
||||
}
|
||||
private:
|
||||
osg::ref_ptr<osg::Referenced> mReferenced;
|
||||
};
|
||||
|
||||
// Visitor for
|
||||
class SGTextureUpdateVisitor : public SGTextureStateAttributeVisitor {
|
||||
public:
|
||||
SGTextureUpdateVisitor(const osgDB::FilePathList& pathList) :
|
||||
mPathList(pathList)
|
||||
{ }
|
||||
osg::Texture2D* textureReplace(int unit,
|
||||
osg::StateSet::RefAttributePair& refAttr)
|
||||
{
|
||||
osg::Texture2D* texture;
|
||||
texture = dynamic_cast<osg::Texture2D*>(refAttr.first.get());
|
||||
if (!texture)
|
||||
return 0;
|
||||
|
||||
osg::ref_ptr<osg::Image> image = texture->getImage(0);
|
||||
if (!image)
|
||||
return 0;
|
||||
|
||||
// The currently loaded file name
|
||||
std::string fullFilePath = image->getFileName();
|
||||
// The short name
|
||||
std::string fileName = osgDB::getSimpleFileName(fullFilePath);
|
||||
// The name that should be found with the current database path
|
||||
std::string fullLiveryFile = osgDB::findFileInPath(fileName, mPathList);
|
||||
// If they are identical then there is nothing to do
|
||||
if (fullLiveryFile == fullFilePath)
|
||||
return 0;
|
||||
|
||||
image = osgDB::readImageFile(fullLiveryFile);
|
||||
if (!image)
|
||||
return 0;
|
||||
|
||||
osg::CopyOp copyOp(osg::CopyOp::DEEP_COPY_ALL &
|
||||
~osg::CopyOp::DEEP_COPY_IMAGES);
|
||||
texture = static_cast<osg::Texture2D*>(copyOp(texture));
|
||||
if (!texture)
|
||||
return 0;
|
||||
texture->setImage(image.get());
|
||||
return texture;
|
||||
}
|
||||
virtual void apply(osg::StateSet* stateSet)
|
||||
{
|
||||
if (!stateSet)
|
||||
return;
|
||||
|
||||
// get a copy that we can safely modify the statesets values.
|
||||
osg::StateSet::TextureAttributeList attrList;
|
||||
attrList = stateSet->getTextureAttributeList();
|
||||
for (unsigned unit = 0; unit < attrList.size(); ++unit) {
|
||||
osg::StateSet::AttributeList::iterator i;
|
||||
i = attrList[unit].begin();
|
||||
while (i != attrList[unit].end()) {
|
||||
osg::Texture2D* texture = textureReplace(unit, i->second);
|
||||
if (texture) {
|
||||
stateSet->removeTextureAttribute(unit, i->second.first.get());
|
||||
stateSet->setTextureAttribute(unit, texture, i->second.second);
|
||||
stateSet->setTextureMode(unit, GL_TEXTURE_2D,
|
||||
osg::StateAttribute::ON);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
osgDB::FilePathList mPathList;
|
||||
};
|
||||
|
||||
class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
|
||||
public:
|
||||
virtual void apply(int, osg::StateSet::RefAttributePair& refAttr)
|
||||
{
|
||||
osg::Texture2D* texture;
|
||||
texture = dynamic_cast<osg::Texture2D*>(refAttr.first.get());
|
||||
if (!texture)
|
||||
return;
|
||||
|
||||
// Hmm, true??
|
||||
texture->setDataVariance(osg::Object::STATIC);
|
||||
|
||||
osg::Image* image = texture->getImage(0);
|
||||
if (!image)
|
||||
return;
|
||||
|
||||
int s = image->s();
|
||||
int t = image->t();
|
||||
|
||||
if (s <= t && 32 <= s) {
|
||||
SGSceneFeatures::instance()->setTextureCompression(texture);
|
||||
} else if (t < s && 32 <= t) {
|
||||
SGSceneFeatures::instance()->setTextureCompression(texture);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
|
||||
public:
|
||||
virtual void apply(int, osg::StateSet::RefAttributePair& refAttr)
|
||||
{
|
||||
osg::Texture* texture;
|
||||
texture = dynamic_cast<osg::Texture*>(refAttr.first.get());
|
||||
if (!texture)
|
||||
return;
|
||||
|
||||
texture->setDataVariance(osg::Object::STATIC);
|
||||
}
|
||||
};
|
||||
|
||||
class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
|
||||
public:
|
||||
virtual void apply(osg::StateSet::RefAttributePair& refAttr)
|
||||
{
|
||||
osg::Material* material;
|
||||
material = dynamic_cast<osg::Material*>(refAttr.first.get());
|
||||
if (!material)
|
||||
return;
|
||||
material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
|
||||
}
|
||||
};
|
||||
|
||||
class SGReadFileCallback :
|
||||
public osgDB::Registry::ReadFileCallback {
|
||||
public:
|
||||
virtual osgDB::ReaderWriter::ReadResult
|
||||
readImage(const std::string& fileName,
|
||||
const osgDB::ReaderWriter::Options* opt)
|
||||
{
|
||||
std::string absFileName = osgDB::findDataFile(fileName);
|
||||
if (!osgDB::fileExists(absFileName)) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
|
||||
<< fileName << "\"");
|
||||
return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
osgDB::Registry* registry = osgDB::Registry::instance();
|
||||
osgDB::ReaderWriter::ReadResult res;
|
||||
res = registry->readImageImplementation(absFileName, opt);
|
||||
if (res.loadedFromCache())
|
||||
SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
|
||||
<< res.getImage()->getFileName() << "\"");
|
||||
else
|
||||
SG_LOG(SG_IO, SG_INFO, "Reading image \""
|
||||
<< res.getImage()->getFileName() << "\"");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
virtual osgDB::ReaderWriter::ReadResult
|
||||
readNode(const std::string& fileName,
|
||||
const osgDB::ReaderWriter::Options* opt)
|
||||
{
|
||||
osgDB::Registry* registry = osgDB::Registry::instance();
|
||||
osgDB::ReaderWriter::ReadResult res;
|
||||
osg::Node* cached = 0;
|
||||
// The BTG loader automatically looks for ".btg.gz" if a file with
|
||||
// the .btg extension doesn't exist. Also, we don't want to add
|
||||
// nodes, run the optimizer, etc. on the btg model.So, let it do
|
||||
// its thing.
|
||||
if (osgDB::equalCaseInsensitive(osgDB::getFileExtension(fileName), "btg")) {
|
||||
return registry->readNodeImplementation(fileName, opt);
|
||||
}
|
||||
// First, look for a file with the same name, and the extension
|
||||
// ".osg" and, if it exists, load it instead. This allows for
|
||||
// substitution of optimized models for ones named in the scenery.
|
||||
bool optimizeModel = true;
|
||||
std::string fileSansExtension = osgDB::getNameLessExtension(fileName);
|
||||
std::string osgFileName = fileSansExtension + ".osg";
|
||||
std::string absFileName = osgDB::findDataFile(osgFileName);
|
||||
if (osgDB::fileExists(absFileName)) {
|
||||
optimizeModel = false;
|
||||
} else {
|
||||
absFileName = osgDB::findDataFile(fileName);
|
||||
}
|
||||
if (!osgDB::fileExists(absFileName)) {
|
||||
SG_LOG(SG_IO, SG_ALERT, "Cannot find model file \""
|
||||
<< fileName << "\"");
|
||||
return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
|
||||
}
|
||||
cached
|
||||
= dynamic_cast<osg::Node*>(registry->getFromObjectCache(absFileName));
|
||||
if (cached) {
|
||||
SG_LOG(SG_IO, SG_INFO, "Got cached model \""
|
||||
<< absFileName << "\"");
|
||||
} else {
|
||||
SG_LOG(SG_IO, SG_INFO, "Reading model \""
|
||||
<< absFileName << "\"");
|
||||
res = registry->readNodeImplementation(absFileName, opt);
|
||||
if (!res.validNode())
|
||||
return res;
|
||||
|
||||
bool needTristrip = true;
|
||||
if (osgDB::getLowerCaseFileExtension(fileName) == "ac") {
|
||||
// we get optimal geometry from the loader.
|
||||
needTristrip = false;
|
||||
osg::Matrix m(1, 0, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, -1, 0, 0,
|
||||
0, 0, 0, 1);
|
||||
|
||||
osg::ref_ptr<osg::Group> root = new osg::Group;
|
||||
osg::MatrixTransform* transform = new osg::MatrixTransform;
|
||||
root->addChild(transform);
|
||||
|
||||
transform->setDataVariance(osg::Object::STATIC);
|
||||
transform->setMatrix(m);
|
||||
transform->addChild(res.getNode());
|
||||
|
||||
res = osgDB::ReaderWriter::ReadResult(0);
|
||||
|
||||
if (optimizeModel) {
|
||||
osgUtil::Optimizer optimizer;
|
||||
unsigned opts = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
|
||||
optimizer.optimize(root.get(), opts);
|
||||
}
|
||||
|
||||
// strip away unneeded groups
|
||||
if (root->getNumChildren() == 1 && root->getName().empty()) {
|
||||
res = osgDB::ReaderWriter::ReadResult(root->getChild(0));
|
||||
} else
|
||||
res = osgDB::ReaderWriter::ReadResult(root.get());
|
||||
|
||||
// Ok, this step is questionable.
|
||||
// It is there to have the same visual appearance of ac objects for the
|
||||
// first cut. Osg's ac3d loader will correctly set materials from the
|
||||
// ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
|
||||
// materials that in effect igored the ambient part specified in the
|
||||
// file. We emulate that for the first cut here by changing all
|
||||
// ac models here. But in the long term we should use the
|
||||
// unchanged model and fix the input files instead ...
|
||||
SGAcMaterialCrippleVisitor matCriple;
|
||||
res.getNode()->accept(matCriple);
|
||||
}
|
||||
|
||||
if (optimizeModel) {
|
||||
osgUtil::Optimizer optimizer;
|
||||
unsigned opts = 0;
|
||||
// Don't use this one. It will break animation names ...
|
||||
// opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
|
||||
|
||||
// opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
|
||||
// opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
|
||||
// opts |= osgUtil::Optimizer::SHARE_DUPLICATE_STATE;
|
||||
opts |= osgUtil::Optimizer::MERGE_GEOMETRY;
|
||||
// opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
|
||||
// opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
|
||||
// opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
|
||||
opts |= osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
|
||||
if (needTristrip)
|
||||
opts |= osgUtil::Optimizer::TRISTRIP_GEOMETRY;
|
||||
// opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
|
||||
// opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
|
||||
optimizer.optimize(res.getNode(), opts);
|
||||
}
|
||||
// Make sure the data variance of sharable objects is set to STATIC ...
|
||||
SGTexDataVarianceVisitor dataVarianceVisitor;
|
||||
res.getNode()->accept(dataVarianceVisitor);
|
||||
// ... so that textures are now globally shared
|
||||
registry->getSharedStateManager()->share(res.getNode());
|
||||
|
||||
SGTexCompressionVisitor texComp;
|
||||
res.getNode()->accept(texComp);
|
||||
cached = res.getNode();
|
||||
registry->addEntryToObjectCache(absFileName, cached);
|
||||
}
|
||||
// Add an extra reference to the model stored in the database.
|
||||
// That it to avoid expiring the object from the cache even if it is still
|
||||
// in use. Note that the object cache will think that a model is unused
|
||||
// if the reference count is 1. If we clone all structural nodes here
|
||||
// we need that extra reference to the original object
|
||||
SGDatabaseReference* databaseReference;
|
||||
databaseReference = new SGDatabaseReference(cached);
|
||||
osg::CopyOp::CopyFlags flags = osg::CopyOp::DEEP_COPY_ALL;
|
||||
flags &= ~osg::CopyOp::DEEP_COPY_TEXTURES;
|
||||
flags &= ~osg::CopyOp::DEEP_COPY_IMAGES;
|
||||
flags &= ~osg::CopyOp::DEEP_COPY_ARRAYS;
|
||||
flags &= ~osg::CopyOp::DEEP_COPY_PRIMITIVES;
|
||||
// This will safe display lists ...
|
||||
flags &= ~osg::CopyOp::DEEP_COPY_DRAWABLES;
|
||||
flags &= ~osg::CopyOp::DEEP_COPY_SHAPES;
|
||||
res = osgDB::ReaderWriter::ReadResult(osg::CopyOp(flags)(cached));
|
||||
res.getNode()->addObserver(databaseReference);
|
||||
|
||||
// Update liveries
|
||||
SGTextureUpdateVisitor liveryUpdate(osgDB::getDataFilePathList());
|
||||
res.getNode()->accept(liveryUpdate);
|
||||
|
||||
// Make sure the data variance of sharable objects is set to STATIC ...
|
||||
SGTexDataVarianceVisitor dataVarianceVisitor;
|
||||
res.getNode()->accept(dataVarianceVisitor);
|
||||
// ... so that textures are now globally shared
|
||||
registry->getOrCreateSharedStateManager()->share(res.getNode(), 0);
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
class SGReadCallbackInstaller {
|
||||
public:
|
||||
SGReadCallbackInstaller()
|
||||
{
|
||||
osg::Referenced::setThreadSafeReferenceCounting(true);
|
||||
|
||||
osgDB::Registry* registry = osgDB::Registry::instance();
|
||||
osgDB::ReaderWriter::Options* options = new osgDB::ReaderWriter::Options;
|
||||
// We manage node caching ourselves
|
||||
int cacheOptions = osgDB::ReaderWriter::Options::CACHE_ALL
|
||||
& ~osgDB::ReaderWriter::Options::CACHE_NODES;
|
||||
options->
|
||||
setObjectCacheHint((osgDB::ReaderWriter::Options::CacheHintOptions)cacheOptions);
|
||||
registry->setOptions(options);
|
||||
registry->getOrCreateSharedStateManager()->setShareMode(osgDB::SharedStateManager::SHARE_TEXTURES);
|
||||
registry->setReadFileCallback(new SGReadFileCallback);
|
||||
}
|
||||
};
|
||||
|
||||
static SGReadCallbackInstaller readCallbackInstaller;
|
||||
|
||||
osg::Texture2D*
|
||||
SGLoadTexture2D(const std::string& path, bool wrapu, bool wrapv, int)
|
||||
|
@ -17,10 +17,15 @@
|
||||
|
||||
#include <osgDB/FileNameUtils>
|
||||
#include <osgDB/Registry>
|
||||
|
||||
#include <simgear/scene/model/ModelRegistry.hxx>
|
||||
|
||||
#include "SGReaderWriterBTGOptions.hxx"
|
||||
#include "SGReaderWriterBTG.hxx"
|
||||
#include "obj.hxx"
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
const char* SGReaderWriterBTG::className() const
|
||||
{
|
||||
return "BTG Database reader";
|
||||
@ -64,4 +69,7 @@ SGReaderWriterBTG::readNode(const std::string& fileName,
|
||||
return ReadResult::FILE_NOT_HANDLED;
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
ModelRegistryCallbackProxy<OSGFileCallback> g_btgCallbackProxy("btg");
|
||||
}
|
||||
|
@ -31,5 +31,6 @@ public:
|
||||
const osgDB::ReaderWriter::Options* options)
|
||||
const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user