X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=simgear%2Fscene%2Fmodel%2Fmodel.cxx;h=749b45c3f7d2a023a85f9b908f01a0c0c00e8db8;hb=084a24e958bbd7a040c3dafd09f74bb9e1c334ce;hp=b6394fb75bae4a952d3676cc16d8c92ed737a74c;hpb=d8a75897526f2ba2a030225a22dfbd93c7c39a7e;p=simgear.git diff --git a/simgear/scene/model/model.cxx b/simgear/scene/model/model.cxx index b6394fb7..749b45c3 100644 --- a/simgear/scene/model/model.cxx +++ b/simgear/scene/model/model.cxx @@ -7,256 +7,602 @@ #include #endif -#include - -#include // for strcmp() - -#include - -#include -#include -#include - -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include #include #include -#include +#include +#include "animation.hxx" #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 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(refAttr.first.get()); + if (!texture) + return 0; + + osg::ref_ptr 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(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(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(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(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; + } - -//////////////////////////////////////////////////////////////////////// -// Static utility functions. -//////////////////////////////////////////////////////////////////////// - -/** - * Callback to update an animation. - */ -static int -animation_callback (ssgEntity * entity, int mask) -{ - ((SGAnimation *)entity->getUserData())->update(); - return true; -} - + 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; + } -/** - * Locate a named SSG node in a branch. - */ -static ssgEntity * -find_named_node (ssgEntity * node, const char * name) -{ - char * node_name = node->getName(); - if (node_name != 0 && !strcmp(name, node_name)) - return node; - else if (node->isAKindOf(ssgTypeBranch())) { - int nKids = node->getNumKids(); - for (int i = 0; i < nKids; i++) { - ssgEntity * result = - find_named_node(((ssgBranch*)node)->getKid(i), name); - if (result != 0) - return result; + 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); } - } - return 0; -} + // 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(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 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); + } -/** - * Splice a branch in between all child nodes and their parents. - */ -static void -splice_branch (ssgBranch * branch, ssgEntity * child) -{ - int nParents = child->getNumParents(); - branch->addKid(child); - for (int i = 0; i < nParents; i++) { - ssgBranch * parent = child->getParent(i); - parent->replaceKid(child, branch); + 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; } -} - -/** - * Make an offset matrix from rotations and position offset. - */ -void -sgMakeOffsetsMatrix( sgMat4 * result, double h_rot, double p_rot, double r_rot, - double x_off, double y_off, double z_off ) -{ - sgMat4 rot_matrix; - sgMat4 pos_matrix; - sgMakeRotMat4(rot_matrix, h_rot, p_rot, r_rot); - sgMakeTransMat4(pos_matrix, x_off, y_off, z_off); - sgMultMat4(*result, pos_matrix, rot_matrix); -} +}; + +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; -void -sgMakeAnimation( ssgBranch * model, - const char * name, - vector &name_nodes, - SGPropertyNode *prop_root, - SGPropertyNode_ptr node, - double sim_time_sec ) +osg::Texture2D* +SGLoadTexture2D(const std::string& path, bool wrapu, bool wrapv, int) { - SGAnimation * animation = 0; - const char * type = node->getStringValue("type", "none"); - if (!strcmp("none", type)) { - animation = new SGNullAnimation(node); - } else if (!strcmp("range", type)) { - animation = new SGRangeAnimation(node); - } else if (!strcmp("billboard", type)) { - animation = new SGBillboardAnimation(node); - } else if (!strcmp("select", type)) { - animation = new SGSelectAnimation(prop_root, node); - } else if (!strcmp("spin", type)) { - animation = new SGSpinAnimation(prop_root, node, sim_time_sec ); - } else if (!strcmp("timed", type)) { - animation = new SGTimedAnimation(node); - } else if (!strcmp("rotate", type)) { - animation = new SGRotateAnimation(prop_root, node); - } else if (!strcmp("translate", type)) { - animation = new SGTranslateAnimation(prop_root, node); - } else { - animation = new SGNullAnimation(node); - SG_LOG(SG_INPUT, SG_WARN, "Unknown animation type " << type); - } - - if (name != 0) - animation->setName((char *)name); - - ssgEntity * object; - if (name_nodes.size() > 0) { - object = find_named_node(model, name_nodes[0]->getStringValue()); - if (object == 0) { - SG_LOG(SG_INPUT, SG_WARN, "Object " << name_nodes[0]->getStringValue() - << " not found"); - delete animation; - animation = 0; + osg::Image* image = osgDB::readImageFile(path); + osg::ref_ptr texture = new osg::Texture2D; + texture->setImage(image); + texture->setDataVariance(osg::Object::STATIC); + if (wrapu) + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT); + else + texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP); + if (wrapv) + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT); + else + texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP); + + if (image) { + int s = image->s(); + int t = image->t(); + + if (s <= t && 32 <= s) { + SGSceneFeatures::instance()->setTextureCompression(texture.get()); + } else if (t < s && 32 <= t) { + SGSceneFeatures::instance()->setTextureCompression(texture.get()); } - } else { - object = model; } - - ssgBranch * branch = animation->getBranch(); - splice_branch(branch, object); - - for (unsigned int i = 1; i < name_nodes.size(); i++) { - const char * name = name_nodes[i]->getStringValue(); - object = find_named_node(model, name); - if (object == 0) { - SG_LOG(SG_INPUT, SG_WARN, "Object " << name << " not found"); - delete animation; - animation = 0; - } - ssgBranch * oldParent = object->getParent(0); - branch->addKid(object); - oldParent->removeKid(object); + + // Make sure the texture is shared if we already have the same texture + // somewhere ... + { + osg::ref_ptr tmpNode = new osg::Node; + osg::StateSet* stateSet = tmpNode->getOrCreateStateSet(); + stateSet->setTextureAttribute(0, texture.get()); + + // OSGFIXME: don't forget that mutex here + osgDB::Registry* registry = osgDB::Registry::instance(); + registry->getSharedStateManager()->share(tmpNode.get(), 0); + + // should be the same, but be paranoid ... + stateSet = tmpNode->getStateSet(); + osg::StateAttribute* stateAttr; + stateAttr = stateSet->getTextureAttribute(0, osg::StateAttribute::TEXTURE); + osg::Texture2D* texture2D = dynamic_cast(stateAttr); + if (texture2D) + texture = texture2D; } - animation->init(); - branch->setUserData(animation); - branch->setTravCallback(SSG_CALLBACK_PRETRAV, animation_callback); + return texture.release(); } +class SGSwitchUpdateCallback : public osg::NodeCallback { +public: + SGSwitchUpdateCallback(SGCondition* condition) : + mCondition(condition) {} + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + assert(dynamic_cast(node)); + osg::Switch* s = static_cast(node); + + if (mCondition && mCondition->test()) { + s->setAllChildrenOn(); + // note, callback is responsible for scenegraph traversal so + // should always include call traverse(node,nv) to ensure + // that the rest of cullbacks and the scene graph are traversed. + traverse(node, nv); + } else + s->setAllChildrenOff(); + } +private: + SGSharedPtr mCondition; +}; //////////////////////////////////////////////////////////////////////// // Global functions. //////////////////////////////////////////////////////////////////////// -ssgBranch * +osg::Node * sgLoad3DModel( const string &fg_root, const string &path, SGPropertyNode *prop_root, - double sim_time_sec ) + double sim_time_sec, osg::Node *(*load_panel)(SGPropertyNode *), + SGModelData *data, + const SGPath& externalTexturePath ) { - ssgBranch * model = 0; + osg::ref_ptr model; SGPropertyNode props; - // Load the 3D aircraft object itself - SGPath xmlpath; - SGPath modelpath = path; - if ( ulIsAbsolutePathName( path.c_str() ) ) { - xmlpath = modelpath; - } - else { - xmlpath = fg_root; - xmlpath.append(modelpath.str()); + // Load the 3D aircraft object itself + SGPath modelpath = path, texturepath = path; + if ( !ulIsAbsolutePathName( path.c_str() ) ) { + SGPath tmp = fg_root; + tmp.append(modelpath.str()); + modelpath = texturepath = tmp; } - // Check for an XML wrapper - if (xmlpath.str().substr(xmlpath.str().size() - 4, 4) == ".xml") { - readProperties(xmlpath.str(), &props); + // Check for an XML wrapper + if (modelpath.str().substr(modelpath.str().size() - 4, 4) == ".xml") { + readProperties(modelpath.str(), &props); if (props.hasValue("/path")) { modelpath = modelpath.dir(); modelpath.append(props.getStringValue("/path")); + if (props.hasValue("/texture-path")) { + texturepath = texturepath.dir(); + texturepath.append(props.getStringValue("/texture-path")); + } } else { - if (model == 0) - model = new ssgBranch; + if (!model) + model = new osg::Switch; } } - // Assume that textures are in - // the same location as the XML file. - if (model == 0) { - ssgTexturePath((char *)xmlpath.dir().c_str()); - model = (ssgBranch *)ssgLoad((char *)modelpath.c_str()); - if (model == 0) - throw sg_exception("Failed to load 3D model"); - } + osgDB::FilePathList pathList = osgDB::getDataFilePathList(); + osgDB::Registry::instance()->initFilePathLists(); + + // Assume that textures are in + // the same location as the XML file. + if (!model) { + if (texturepath.extension() != "") + texturepath = texturepath.dir(); + + osgDB::Registry::instance()->getDataFilePathList().push_front(texturepath.str()); - // Set up the alignment node - ssgTransform * alignmainmodel = new ssgTransform; - alignmainmodel->addKid(model); - sgMat4 res_matrix; - sgMakeOffsetsMatrix(&res_matrix, - props.getFloatValue("/offsets/heading-deg", 0.0), - props.getFloatValue("/offsets/roll-deg", 0.0), - props.getFloatValue("/offsets/pitch-deg", 0.0), - props.getFloatValue("/offsets/x-m", 0.0), - props.getFloatValue("/offsets/y-m", 0.0), - props.getFloatValue("/offsets/z-m", 0.0)); - alignmainmodel->setTransform(res_matrix); - - unsigned int i; - - // Load animations - vector animation_nodes = props.getChildren("animation"); - for (i = 0; i < animation_nodes.size(); i++) { - const char * name = animation_nodes[i]->getStringValue("name", 0); - vector name_nodes = - animation_nodes[i]->getChildren("object-name"); - sgMakeAnimation( model, name, name_nodes, prop_root, animation_nodes[i], - sim_time_sec); + model = osgDB::readNodeFile(modelpath.str()); + if (model == 0) + throw sg_io_exception("Failed to load 3D model", + sg_location(modelpath.str())); } - // Load sub-models + osgDB::Registry::instance()->getDataFilePathList().push_front(externalTexturePath.str()); + + // Set up the alignment node + osg::ref_ptr alignmainmodel = new osg::MatrixTransform; + alignmainmodel->addChild(model.get()); + osg::Matrix res_matrix; + res_matrix.makeRotate( + props.getFloatValue("/offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS, + osg::Vec3(0, 1, 0), + props.getFloatValue("/offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS, + osg::Vec3(1, 0, 0), + props.getFloatValue("/offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS, + osg::Vec3(0, 0, 1)); + + osg::Matrix tmat; + tmat.makeTranslate(props.getFloatValue("/offsets/x-m", 0.0), + props.getFloatValue("/offsets/y-m", 0.0), + props.getFloatValue("/offsets/z-m", 0.0)); + alignmainmodel->setMatrix(res_matrix*tmat); + + // Load sub-models vector model_nodes = props.getChildren("model"); - for (i = 0; i < model_nodes.size(); i++) { + for (unsigned i = 0; i < model_nodes.size(); i++) { SGPropertyNode_ptr node = model_nodes[i]; - ssgTransform * align = new ssgTransform; - sgMat4 res_matrix; - sgMakeOffsetsMatrix(&res_matrix, - node->getFloatValue("offsets/heading-deg", 0.0), - node->getFloatValue("offsets/roll-deg", 0.0), - node->getFloatValue("offsets/pitch-deg", 0.0), - node->getFloatValue("offsets/x-m", 0.0), - node->getFloatValue("offsets/y-m", 0.0), - node->getFloatValue("offsets/z-m", 0.0)); - align->setTransform(res_matrix); - - ssgBranch * kid = sgLoad3DModel( fg_root, node->getStringValue("path"), - prop_root, sim_time_sec ); - align->addKid(kid); - model->addKid(align); + osg::ref_ptr align = new osg::MatrixTransform; + res_matrix.makeIdentity(); + res_matrix.makeRotate( + node->getDoubleValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS, + osg::Vec3(0, 1, 0), + node->getDoubleValue("offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS, + osg::Vec3(1, 0, 0), + node->getDoubleValue("offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS, + osg::Vec3(0, 0, 1)); + + tmat.makeIdentity(); + tmat.makeTranslate(node->getDoubleValue("offsets/x-m", 0), + node->getDoubleValue("offsets/y-m", 0), + node->getDoubleValue("offsets/z-m", 0)); + align->setMatrix(res_matrix*tmat); + + osg::ref_ptr kid; + const char* submodel = node->getStringValue("path"); + try { + kid = sgLoad3DModel( fg_root, submodel, prop_root, sim_time_sec, load_panel ); + + } catch (const sg_throwable &t) { + SG_LOG(SG_INPUT, SG_ALERT, "Failed to load submodel: " << t.getFormattedMessage()); + throw; + } + align->addChild(kid.get()); + + align->setName(node->getStringValue("name", "")); + + SGPropertyNode *cond = node->getNode("condition", false); + if (cond) { + osg::ref_ptr sw = new osg::Switch; + sw->setUpdateCallback(new SGSwitchUpdateCallback(sgReadCondition(prop_root, cond))); + alignmainmodel->addChild(sw.get()); + sw->addChild(align.get()); + sw->setName("submodel condition switch"); + } else { + alignmainmodel->addChild(align.get()); + } } - return alignmainmodel; -} + if ( load_panel ) { + // Load panels + vector panel_nodes = props.getChildren("panel"); + for (unsigned i = 0; i < panel_nodes.size(); i++) { + SG_LOG(SG_INPUT, SG_DEBUG, "Loading a panel"); + osg::ref_ptr panel = load_panel(panel_nodes[i]); + if (panel_nodes[i]->hasValue("name")) + panel->setName((char *)panel_nodes[i]->getStringValue("name")); + alignmainmodel->addChild(panel.get()); + } + } + + if (data) { + alignmainmodel->setUserData(data); + data->modelLoaded(path, &props, alignmainmodel.get()); + } + + std::vector animation_nodes; + animation_nodes = props.getChildren("animation"); + for (unsigned i = 0; i < animation_nodes.size(); ++i) + /// OSGFIXME: duh, why not only model????? + SGAnimation::animate(alignmainmodel.get(), animation_nodes[i], prop_root); + // restore old path list + osgDB::setDataFilePathList(pathList); + + if (props.hasChild("debug-outfile")) { + std::string outputfile = props.getStringValue("debug-outfile", + "debug-model.osg"); + osgDB::writeNodeFile(*alignmainmodel, outputfile); + } + + return alignmainmodel.release(); +} // end of model.cxx