diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 2434343c9f..f21e8dd4da 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -60,7 +60,7 @@ void CSVRender::Object::update() Nif::NIFFilePtr file(new Nif::NIFFile(mVFS->get(path), path)); - loader.load(file, mBaseNode); + loader.loadAsSkeleton(file, mBaseNode); //mObject->setVisibilityFlags (Element_Reference); diff --git a/apps/opencs/view/render/previewwidget.cpp b/apps/opencs/view/render/previewwidget.cpp index ee43ac13c6..8802b5cf33 100644 --- a/apps/opencs/view/render/previewwidget.cpp +++ b/apps/opencs/view/render/previewwidget.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "../../model/world/data.hpp" #include "../../model/world/idtable.hpp" @@ -13,6 +15,8 @@ CSVRender::PreviewWidget::PreviewWidget (const VFS::Manager* vfs, CSMWorld::Data { //setNavigation (&mOrbit); + setCameraManipulator(new osgGA::TrackballManipulator); + QAbstractItemModel *referenceables = mData.getTableModel (CSMWorld::UniversalId::Type_Referenceables); diff --git a/apps/opencs/view/render/scenewidget.cpp b/apps/opencs/view/render/scenewidget.cpp index 2f28c06df2..2dc5bdbc69 100644 --- a/apps/opencs/view/render/scenewidget.cpp +++ b/apps/opencs/view/render/scenewidget.cpp @@ -15,7 +15,6 @@ #include #include -#include #include namespace CSVRender @@ -72,14 +71,16 @@ SceneWidget::SceneWidget(QWidget *parent, Qt::WindowFlags f) // Press S to reveal profiling stats addEventHandler(new osgViewer::StatsHandler); - setCameraManipulator(new osgGA::TrackballManipulator); - // Only render when the camera position changed, or content flagged dirty //setRunFrameScheme(osgViewer::ViewerBase::ON_DEMAND); setRunFrameScheme(osgViewer::ViewerBase::CONTINUOUS); + getCamera()->setCullMask(~(0x1)); + connect( &mTimer, SIGNAL(timeout()), this, SLOT(update()) ); mTimer.start( 10 ); + + realize(); } void SceneWidget::paintEvent(QPaintEvent *event) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index dcab807079..27cce16fd3 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -64,6 +64,18 @@ namespace return toMatrix(node->trafo); } + void getAllNiNodes(const Nif::Node* node, std::vector& outIndices) + { + const Nif::NiNode* ninode = dynamic_cast(node); + if (ninode) + { + outIndices.push_back(ninode->recIndex); + for (unsigned int i=0; ichildren.length(); ++i) + if (!ninode->children[i].empty()) + getAllNiNodes(ninode->children[i].getPtr(), outIndices); + } + } + osg::BlendFunc::BlendFuncMode getBlendMode(int mode) { switch(mode) @@ -328,13 +340,6 @@ namespace NifOsg toSetup->mFunction = boost::shared_ptr(new ControllerFunction(ctrl, 1 /*autoPlay*/)); } - class RecIndexHolder : public osg::Referenced - { - public: - RecIndexHolder(int index) : mIndex(index) {} - int mIndex; - }; - void Loader::handleNode(const Nif::Node* nifNode, osg::Group* parentNode, bool createSkeleton, std::map boundTextures, int animflags, int particleflags, bool collisionNode) { @@ -356,6 +361,10 @@ namespace NifOsg transformNode = new osg::MatrixTransform(toMatrix(nifNode->trafo)); } + // UserData used for a variety of features: + // - finding the correct emitter node for a particle system + // - establishing connections to the animated collision shapes, which are handled in a separate loader + // - finding a random child NiNode in NiBspArrayController transformNode->setUserData(new RecIndexHolder(nifNode->recIndex)); if (nifNode->recType == Nif::RC_NiBSAnimationNode) @@ -543,32 +552,6 @@ namespace NifOsg } } - class FindEmitterNode : public osg::NodeVisitor - { - public: - FindEmitterNode(int recIndex) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mFound(NULL) - , mRecIndex(recIndex) - { - } - - virtual void apply(osg::Node &searchNode) - { - if (searchNode.getUserData()) - { - RecIndexHolder* holder = static_cast(searchNode.getUserData()); - if (holder->mIndex == mRecIndex) - mFound = static_cast(&searchNode); - } - traverse(searchNode); - } - - osg::Group* mFound; - private: - int mRecIndex; - }; - void Loader::handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, int animflags, int particleflags) { osg::ref_ptr partsys (new ParticleSystem); @@ -582,8 +565,6 @@ namespace NifOsg else return; - // TODO: add special handling for NiBSPArrayController - const Nif::NiParticleSystemController* partctrl = NULL; for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { @@ -598,6 +579,12 @@ namespace NifOsg return; } + std::vector targets; + if (partctrl->recType == Nif::RC_NiBSPArrayController) + { + getAllNiNodes(partctrl->emitter.getPtr(), targets); + } + osgParticle::ParticleProcessor::ReferenceFrame rf = (particleflags & Nif::NiNode::ParticleFlag_LocalSpace) ? osgParticle::ParticleProcessor::RELATIVE_RF : osgParticle::ParticleProcessor::ABSOLUTE_RF; @@ -638,7 +625,7 @@ namespace NifOsg // ---- emitter - osg::ref_ptr emitter = new osgParticle::ModularEmitter; + osg::ref_ptr emitter = new Emitter(targets); emitter->setParticleSystem(partsys); emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF); @@ -668,7 +655,7 @@ namespace NifOsg // This seems to be true for all NIF files in the game that I've checked, suggesting that NIFs work similar to OSG with regards to update order. // If something ever violates this assumption, the worst that could happen is the culling being one frame late, which wouldn't be a disaster. - FindEmitterNode find (partctrl->emitter->recIndex); + FindRecIndexVisitor find (partctrl->emitter->recIndex); mRootNode->accept(find); if (!find.mFound) { @@ -676,6 +663,9 @@ namespace NifOsg return; } osg::Group* emitterNode = find.mFound; + + // Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node + // actually causes the emitter to stop firing. Convenient, because MW behaves this way too! emitterNode->addChild(emitter); osg::ref_ptr callback(new ParticleSystemController(partctrl)); @@ -852,6 +842,7 @@ namespace NifOsg geometry = new osg::Geometry; osg::ref_ptr geode (new osg::Geode); + geode->setName(triShape->name); // name will be used for part filtering triShapeToGeometry(triShape, geometry, geode, boundTextures, animflags); geode->addDrawable(geometry); @@ -862,6 +853,7 @@ namespace NifOsg void Loader::handleSkinnedTriShape(const Nif::NiTriShape *triShape, osg::Group *parentNode, const std::map& boundTextures, int animflags) { osg::ref_ptr geode (new osg::Geode); + geode->setName(triShape->name); // name will be used for part filtering osg::ref_ptr geometry (new osg::Geometry); triShapeToGeometry(triShape, geometry, geode, boundTextures, animflags); @@ -969,7 +961,7 @@ namespace NifOsg case Nif::RC_NiVertexColorProperty: case Nif::RC_NiSpecularProperty: { - // TODO: handle these in handleTriShape so we know whether vertex colors are available + // Handled in handleTriShape so we know whether vertex colors are available break; } case Nif::RC_NiAlphaProperty: diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 77e5f16df6..934ef51ebe 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -191,4 +191,92 @@ void GravityAffector::operate(osgParticle::Particle *particle, double dt) } } +Emitter::Emitter() + : osgParticle::Emitter() +{ +} + +Emitter::Emitter(const Emitter ©, const osg::CopyOp ©op) + : osgParticle::Emitter(copy, copyop) + , mTargets(copy.mTargets) +{ +} + +Emitter::Emitter(const std::vector &targets) + : mTargets(targets) +{ +} + +void Emitter::setShooter(osgParticle::Shooter *shooter) +{ + mShooter = shooter; +} + +void Emitter::setPlacer(osgParticle::Placer *placer) +{ + mPlacer = placer; +} + +void Emitter::setCounter(osgParticle::Counter *counter) +{ + mCounter = counter; +} + +void Emitter::emitParticles(double dt) +{ + osg::Matrix worldToPs; + osg::MatrixList worldMats = getParticleSystem()->getWorldMatrices(); + if (!worldMats.empty()) + { + const osg::Matrix psToWorld = worldMats[0]; + worldToPs = osg::Matrix::inverse(psToWorld); + } + + const osg::Matrix& ltw = getLocalToWorldMatrix(); + const osg::Matrix& previous_ltw = getPreviousLocalToWorldMatrix(); + const osg::Matrix emitterToPs = ltw * worldToPs; + const osg::Matrix prevEmitterToPs = previous_ltw * worldToPs; + + int n = mCounter->numParticlesToCreate(dt); + + osg::Matrix transform; + if (!mTargets.empty()) + { + int randomRecIndex = mTargets[(std::rand() / (static_cast(RAND_MAX)+1.0)) * mTargets.size()]; + + // we could use a map here for faster lookup + FindRecIndexVisitor visitor(randomRecIndex); + getParent(0)->accept(visitor); + + if (!visitor.mFound) + { + std::cerr << "Emitter: Can't find emitter node" << randomRecIndex << std::endl; + return; + } + + osg::NodePath path = visitor.mFoundPath; + path.erase(path.begin()); + transform = osg::computeLocalToWorld(path); + } + + for (int i=0; icreateParticle(0); + if (P) + { + mPlacer->place(P); + + P->transformPositionVelocity(transform); + + mShooter->shoot(P); + + // Now need to transform the position and velocity because we having a moving model. + // (is this actually how MW works?) + float r = ((float)rand()/(float)RAND_MAX); + P->transformPositionVelocity(emitterToPs, prevEmitterToPs, r); + //P->transformPositionVelocity(ltw); + } + } +} + } diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index 1f39050897..1c8174e8a4 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -151,6 +152,71 @@ namespace NifOsg osg::Vec3f mCachedWorldPositionDirection; }; + + class RecIndexHolder : public osg::Referenced + { + public: + RecIndexHolder(int index) : mIndex(index) {} + int mIndex; + }; + + // NodeVisitor to find a child node with the given record index, stored in the node's user data. + class FindRecIndexVisitor : public osg::NodeVisitor + { + public: + FindRecIndexVisitor(int recIndex) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mFound(NULL) + , mRecIndex(recIndex) + { + } + + virtual void apply(osg::Node &searchNode) + { + if (searchNode.getUserData()) + { + RecIndexHolder* holder = static_cast(searchNode.getUserData()); + if (holder->mIndex == mRecIndex) + { + mFound = static_cast(&searchNode); + mFoundPath = getNodePath(); + return; + } + } + traverse(searchNode); + } + + osg::Group* mFound; + osg::NodePath mFoundPath; + private: + int mRecIndex; + }; + + // Subclass emitter to support randomly choosing one of the child node's transforms for the emit position of new particles. + class Emitter : public osgParticle::Emitter + { + public: + Emitter(const std::vector& targets); + Emitter(); + Emitter(const Emitter& copy, const osg::CopyOp& copyop); + + META_Object(NifOsg, NifOsg::Emitter) + + virtual void emitParticles(double dt); + + void setShooter(osgParticle::Shooter* shooter); + void setPlacer(osgParticle::Placer* placer); + void setCounter(osgParticle::Counter* counter); + + private: + // NIF Record indices + std::vector mTargets; + + osg::ref_ptr mPlacer; + osg::ref_ptr mShooter; + osg::ref_ptr mCounter; + }; + } #endif