You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1493 lines
60 KiB
C++
1493 lines
60 KiB
C++
/*
|
|
OpenMW - The completely unofficial reimplementation of Morrowind
|
|
Copyright (C) 2008-2010 Nicolay Korslund
|
|
Email: < korslund@gmail.com >
|
|
WWW: http://openmw.sourceforge.net/
|
|
|
|
This file (ogre_nif_loader.cpp) is part of the OpenMW package.
|
|
|
|
OpenMW is distributed as free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License
|
|
version 3, as published by the Free Software Foundation.
|
|
|
|
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
|
|
version 3 along with this program. If not, see
|
|
http://www.gnu.org/licenses/ .
|
|
|
|
*/
|
|
|
|
#include "ogrenifloader.hpp"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <OgreTechnique.h>
|
|
#include <OgreEntity.h>
|
|
#include <OgreSubEntity.h>
|
|
#include <OgreTagPoint.h>
|
|
#include <OgreParticleSystem.h>
|
|
#include <OgreParticleEmitter.h>
|
|
#include <OgreParticleAffector.h>
|
|
#include <OgreMeshManager.h>
|
|
#include <OgreSkeletonManager.h>
|
|
#include <OgreControllerManager.h>
|
|
#include <OgreMaterialManager.h>
|
|
#include <OgreCamera.h>
|
|
#include <OgreSceneManager.h>
|
|
#include <OgreSkeletonInstance.h>
|
|
#include <OgreSceneNode.h>
|
|
#include <OgreMesh.h>
|
|
|
|
#include <extern/shiny/Main/Factory.hpp>
|
|
|
|
#include <components/nif/node.hpp>
|
|
#include <components/nifcache/nifcache.hpp>
|
|
#include <components/misc/stringops.hpp>
|
|
#include <components/misc/resourcehelpers.hpp>
|
|
|
|
#include "skeleton.hpp"
|
|
#include "material.hpp"
|
|
#include "mesh.hpp"
|
|
#include "controller.hpp"
|
|
#include "particles.hpp"
|
|
|
|
namespace
|
|
{
|
|
|
|
void getAllNiNodes(const Nif::Node* node, std::vector<const Nif::NiNode*>& out)
|
|
{
|
|
const Nif::NiNode* ninode = dynamic_cast<const Nif::NiNode*>(node);
|
|
if (ninode)
|
|
{
|
|
out.push_back(ninode);
|
|
for (unsigned int i=0; i<ninode->children.length(); ++i)
|
|
if (!ninode->children[i].empty())
|
|
getAllNiNodes(ninode->children[i].getPtr(), out);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
namespace NifOgre
|
|
{
|
|
|
|
Ogre::MaterialPtr MaterialControllerManager::getWritableMaterial(Ogre::MovableObject *movable)
|
|
{
|
|
if (mClonedMaterials.find(movable) != mClonedMaterials.end())
|
|
return mClonedMaterials[movable];
|
|
|
|
else
|
|
{
|
|
Ogre::MaterialPtr mat;
|
|
if (Ogre::Entity* ent = dynamic_cast<Ogre::Entity*>(movable))
|
|
mat = ent->getSubEntity(0)->getMaterial();
|
|
else if (Ogre::ParticleSystem* partSys = dynamic_cast<Ogre::ParticleSystem*>(movable))
|
|
mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName());
|
|
|
|
static int count=0;
|
|
Ogre::String newName = mat->getName() + Ogre::StringConverter::toString(count++);
|
|
sh::Factory::getInstance().createMaterialInstance(newName, mat->getName());
|
|
// Make sure techniques are created
|
|
sh::Factory::getInstance()._ensureMaterial(newName, "Default");
|
|
mat = Ogre::MaterialManager::getSingleton().getByName(newName);
|
|
|
|
mClonedMaterials[movable] = mat;
|
|
|
|
if (Ogre::Entity* ent = dynamic_cast<Ogre::Entity*>(movable))
|
|
ent->getSubEntity(0)->setMaterial(mat);
|
|
else if (Ogre::ParticleSystem* partSys = dynamic_cast<Ogre::ParticleSystem*>(movable))
|
|
partSys->setMaterialName(mat->getName());
|
|
|
|
return mat;
|
|
}
|
|
}
|
|
|
|
MaterialControllerManager::~MaterialControllerManager()
|
|
{
|
|
for (std::map<Ogre::MovableObject*, Ogre::MaterialPtr>::iterator it = mClonedMaterials.begin(); it != mClonedMaterials.end(); ++it)
|
|
{
|
|
sh::Factory::getInstance().destroyMaterialInstance(it->second->getName());
|
|
}
|
|
}
|
|
|
|
ObjectScene::~ObjectScene()
|
|
{
|
|
for(size_t i = 0;i < mLights.size();i++)
|
|
{
|
|
Ogre::Light *light = mLights[i];
|
|
// If parent is a scene node, it was created specifically for this light. Destroy it now.
|
|
if(light->isAttached() && !light->isParentTagPoint())
|
|
mSceneMgr->destroySceneNode(light->getParentSceneNode());
|
|
mSceneMgr->destroyLight(light);
|
|
}
|
|
for(size_t i = 0;i < mParticles.size();i++)
|
|
mSceneMgr->destroyParticleSystem(mParticles[i]);
|
|
for(size_t i = 0;i < mEntities.size();i++)
|
|
mSceneMgr->destroyEntity(mEntities[i]);
|
|
mControllers.clear();
|
|
mLights.clear();
|
|
mParticles.clear();
|
|
mEntities.clear();
|
|
mSkelBase = NULL;
|
|
}
|
|
|
|
void ObjectScene::setVisibilityFlags (unsigned int flags)
|
|
{
|
|
for (std::vector<Ogre::Entity*>::iterator iter (mEntities.begin()); iter!=mEntities.end();
|
|
++iter)
|
|
(*iter)->setVisibilityFlags (flags);
|
|
|
|
for (std::vector<Ogre::ParticleSystem*>::iterator iter (mParticles.begin());
|
|
iter!=mParticles.end(); ++iter)
|
|
(*iter)->setVisibilityFlags (flags);
|
|
|
|
for (std::vector<Ogre::Light*>::iterator iter (mLights.begin()); iter!=mLights.end();
|
|
++iter)
|
|
(*iter)->setVisibilityFlags (flags);
|
|
}
|
|
|
|
void ObjectScene::rotateBillboardNodes(Ogre::Camera *camera)
|
|
{
|
|
for (std::vector<Ogre::Node*>::iterator it = mBillboardNodes.begin(); it != mBillboardNodes.end(); ++it)
|
|
{
|
|
assert(mSkelBase);
|
|
Ogre::Node* node = *it;
|
|
node->_setDerivedOrientation(mSkelBase->getParentNode()->_getDerivedOrientation().Inverse() *
|
|
camera->getRealOrientation());
|
|
}
|
|
}
|
|
|
|
void ObjectScene::_notifyAttached()
|
|
{
|
|
// convert initial particle positions to world space for world-space particle systems
|
|
// this can't be done on creation because the particle system is not in its correct world space position yet
|
|
for (std::vector<Ogre::ParticleSystem*>::iterator it = mParticles.begin(); it != mParticles.end(); ++it)
|
|
{
|
|
Ogre::ParticleSystem* psys = *it;
|
|
if (psys->getKeepParticlesInLocalSpace())
|
|
continue;
|
|
Ogre::ParticleIterator pi = psys->_getIterator();
|
|
while (!pi.end())
|
|
{
|
|
Ogre::Particle *p = pi.getNext();
|
|
|
|
#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
|
|
Ogre::Vector3& position = p->mPosition;
|
|
Ogre::Vector3& direction = p->mDirection;
|
|
#else
|
|
Ogre::Vector3& position = p->position;
|
|
Ogre::Vector3& direction = p->direction;
|
|
#endif
|
|
|
|
position =
|
|
(psys->getParentNode()->_getDerivedOrientation() *
|
|
(psys->getParentNode()->_getDerivedScale() * position))
|
|
+ psys->getParentNode()->_getDerivedPosition();
|
|
direction =
|
|
(psys->getParentNode()->_getDerivedOrientation() * direction);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Animates a texture
|
|
class FlipController
|
|
{
|
|
public:
|
|
class Value : public Ogre::ControllerValue<Ogre::Real>
|
|
{
|
|
private:
|
|
Ogre::MovableObject* mMovable;
|
|
int mTexSlot;
|
|
float mDelta;
|
|
std::vector<std::string> mTextures;
|
|
MaterialControllerManager* mMaterialControllerMgr;
|
|
|
|
public:
|
|
Value(Ogre::MovableObject *movable, const Nif::NiFlipController *ctrl, MaterialControllerManager* materialControllerMgr)
|
|
: mMovable(movable)
|
|
, mMaterialControllerMgr(materialControllerMgr)
|
|
{
|
|
mTexSlot = ctrl->mTexSlot;
|
|
mDelta = ctrl->mDelta;
|
|
for (unsigned int i=0; i<ctrl->mSources.length(); ++i)
|
|
{
|
|
const Nif::NiSourceTexture* tex = ctrl->mSources[i].getPtr();
|
|
if (!tex->external)
|
|
std::cerr << "Warning: Found internal texture, ignoring." << std::endl;
|
|
mTextures.push_back(Misc::ResourceHelpers::correctTexturePath(tex->filename));
|
|
}
|
|
}
|
|
|
|
virtual Ogre::Real getValue() const
|
|
{
|
|
// Should not be called
|
|
return 0.0f;
|
|
}
|
|
|
|
virtual void setValue(Ogre::Real time)
|
|
{
|
|
if (mDelta == 0)
|
|
return;
|
|
int curTexture = int(time / mDelta) % mTextures.size();
|
|
|
|
Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable);
|
|
Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator();
|
|
while(techs.hasMoreElements())
|
|
{
|
|
Ogre::Technique *tech = techs.getNext();
|
|
Ogre::Technique::PassIterator passes = tech->getPassIterator();
|
|
while(passes.hasMoreElements())
|
|
{
|
|
Ogre::Pass *pass = passes.getNext();
|
|
Ogre::Pass::TextureUnitStateIterator textures = pass->getTextureUnitStateIterator();
|
|
while (textures.hasMoreElements())
|
|
{
|
|
Ogre::TextureUnitState *texture = textures.getNext();
|
|
if ((texture->getName() == "diffuseMap" && mTexSlot == Nif::NiTexturingProperty::BaseTexture)
|
|
|| (texture->getName() == "normalMap" && mTexSlot == Nif::NiTexturingProperty::BumpTexture)
|
|
|| (texture->getName() == "detailMap" && mTexSlot == Nif::NiTexturingProperty::DetailTexture)
|
|
|| (texture->getName() == "darkMap" && mTexSlot == Nif::NiTexturingProperty::DarkTexture)
|
|
|| (texture->getName() == "emissiveMap" && mTexSlot == Nif::NiTexturingProperty::GlowTexture))
|
|
texture->setTextureName(mTextures[curTexture]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
typedef DefaultFunction Function;
|
|
};
|
|
|
|
class AlphaController
|
|
{
|
|
public:
|
|
class Value : public Ogre::ControllerValue<Ogre::Real>, public ValueInterpolator
|
|
{
|
|
private:
|
|
Ogre::MovableObject* mMovable;
|
|
Nif::FloatKeyMap mData;
|
|
MaterialControllerManager* mMaterialControllerMgr;
|
|
|
|
public:
|
|
Value(Ogre::MovableObject *movable, const Nif::NiFloatData *data, MaterialControllerManager* materialControllerMgr)
|
|
: mMovable(movable)
|
|
, mData(data->mKeyList)
|
|
, mMaterialControllerMgr(materialControllerMgr)
|
|
{
|
|
}
|
|
|
|
virtual Ogre::Real getValue() const
|
|
{
|
|
// Should not be called
|
|
return 0.0f;
|
|
}
|
|
|
|
virtual void setValue(Ogre::Real time)
|
|
{
|
|
float value = interpKey(mData.mKeys, time);
|
|
Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable);
|
|
Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator();
|
|
while(techs.hasMoreElements())
|
|
{
|
|
Ogre::Technique *tech = techs.getNext();
|
|
Ogre::Technique::PassIterator passes = tech->getPassIterator();
|
|
while(passes.hasMoreElements())
|
|
{
|
|
Ogre::Pass *pass = passes.getNext();
|
|
Ogre::ColourValue diffuse = pass->getDiffuse();
|
|
diffuse.a = value;
|
|
pass->setDiffuse(diffuse);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
typedef DefaultFunction Function;
|
|
};
|
|
|
|
class MaterialColorController
|
|
{
|
|
public:
|
|
class Value : public Ogre::ControllerValue<Ogre::Real>, public ValueInterpolator
|
|
{
|
|
private:
|
|
Ogre::MovableObject* mMovable;
|
|
Nif::Vector3KeyMap mData;
|
|
MaterialControllerManager* mMaterialControllerMgr;
|
|
|
|
public:
|
|
Value(Ogre::MovableObject *movable, const Nif::NiPosData *data, MaterialControllerManager* materialControllerMgr)
|
|
: mMovable(movable)
|
|
, mData(data->mKeyList)
|
|
, mMaterialControllerMgr(materialControllerMgr)
|
|
{
|
|
}
|
|
|
|
virtual Ogre::Real getValue() const
|
|
{
|
|
// Should not be called
|
|
return 0.0f;
|
|
}
|
|
|
|
virtual void setValue(Ogre::Real time)
|
|
{
|
|
Ogre::Vector3 value = interpKey(mData.mKeys, time);
|
|
Ogre::MaterialPtr mat = mMaterialControllerMgr->getWritableMaterial(mMovable);
|
|
Ogre::Material::TechniqueIterator techs = mat->getTechniqueIterator();
|
|
while(techs.hasMoreElements())
|
|
{
|
|
Ogre::Technique *tech = techs.getNext();
|
|
Ogre::Technique::PassIterator passes = tech->getPassIterator();
|
|
while(passes.hasMoreElements())
|
|
{
|
|
Ogre::Pass *pass = passes.getNext();
|
|
Ogre::ColourValue diffuse = pass->getDiffuse();
|
|
diffuse.r = value.x;
|
|
diffuse.g = value.y;
|
|
diffuse.b = value.z;
|
|
pass->setDiffuse(diffuse);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
typedef DefaultFunction Function;
|
|
};
|
|
|
|
class VisController
|
|
{
|
|
public:
|
|
class Value : public NodeTargetValue<Ogre::Real>
|
|
{
|
|
private:
|
|
std::vector<Nif::NiVisData::VisData> mData;
|
|
|
|
bool calculate(Ogre::Real time) const
|
|
{
|
|
if(mData.size() == 0)
|
|
return true;
|
|
|
|
for(size_t i = 1;i < mData.size();i++)
|
|
{
|
|
if(mData[i].time > time)
|
|
return mData[i-1].isSet;
|
|
}
|
|
return mData.back().isSet;
|
|
}
|
|
|
|
static void setVisible(Ogre::Node *node, int vis)
|
|
{
|
|
// Skinned meshes are attached to the scene node, not the bone.
|
|
// We use the Node's user data to connect it with the mesh.
|
|
Ogre::Any customData = node->getUserObjectBindings().getUserAny();
|
|
|
|
if (!customData.isEmpty())
|
|
Ogre::any_cast<Ogre::MovableObject*>(customData)->setVisible(vis);
|
|
|
|
Ogre::TagPoint *tag = dynamic_cast<Ogre::TagPoint*>(node);
|
|
if(tag != NULL)
|
|
{
|
|
Ogre::MovableObject *obj = tag->getChildObject();
|
|
if(obj != NULL)
|
|
obj->setVisible(vis);
|
|
}
|
|
|
|
Ogre::Node::ChildNodeIterator iter = node->getChildIterator();
|
|
while(iter.hasMoreElements())
|
|
{
|
|
node = iter.getNext();
|
|
setVisible(node, vis);
|
|
}
|
|
}
|
|
|
|
public:
|
|
Value(Ogre::Node *target, const Nif::NiVisData *data)
|
|
: NodeTargetValue<Ogre::Real>(target)
|
|
, mData(data->mVis)
|
|
{ }
|
|
|
|
virtual Ogre::Quaternion getRotation(float time) const
|
|
{ return Ogre::Quaternion(); }
|
|
|
|
virtual Ogre::Vector3 getTranslation(float time) const
|
|
{ return Ogre::Vector3(0.0f); }
|
|
|
|
virtual Ogre::Vector3 getScale(float time) const
|
|
{ return Ogre::Vector3(1.0f); }
|
|
|
|
virtual Ogre::Real getValue() const
|
|
{
|
|
// Should not be called
|
|
return 0.0f;
|
|
}
|
|
|
|
virtual void setValue(Ogre::Real time)
|
|
{
|
|
bool vis = calculate(time);
|
|
setVisible(mNode, vis);
|
|
}
|
|
};
|
|
|
|
typedef DefaultFunction Function;
|
|
};
|
|
|
|
class KeyframeController
|
|
{
|
|
public:
|
|
class Value : public NodeTargetValue<Ogre::Real>, public ValueInterpolator
|
|
{
|
|
private:
|
|
const Nif::QuaternionKeyMap* mRotations;
|
|
const Nif::FloatKeyMap* mXRotations;
|
|
const Nif::FloatKeyMap* mYRotations;
|
|
const Nif::FloatKeyMap* mZRotations;
|
|
const Nif::Vector3KeyMap* mTranslations;
|
|
const Nif::FloatKeyMap* mScales;
|
|
Nif::NIFFilePtr mNif; // Hold a SharedPtr to make sure key lists stay valid
|
|
|
|
using ValueInterpolator::interpKey;
|
|
|
|
static Ogre::Quaternion interpKey(const Nif::QuaternionKeyMap::MapType &keys, float time)
|
|
{
|
|
if(time <= keys.begin()->first)
|
|
return keys.begin()->second.mValue;
|
|
|
|
Nif::QuaternionKeyMap::MapType::const_iterator it = keys.lower_bound(time);
|
|
if (it != keys.end())
|
|
{
|
|
float aTime = it->first;
|
|
const Nif::QuaternionKey* aKey = &it->second;
|
|
|
|
assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function
|
|
|
|
Nif::QuaternionKeyMap::MapType::const_iterator last = --it;
|
|
float aLastTime = last->first;
|
|
const Nif::QuaternionKey* aLastKey = &last->second;
|
|
|
|
float a = (time - aLastTime) / (aTime - aLastTime);
|
|
return Ogre::Quaternion::nlerp(a, aLastKey->mValue, aKey->mValue);
|
|
}
|
|
else
|
|
return keys.rbegin()->second.mValue;
|
|
}
|
|
|
|
Ogre::Quaternion getXYZRotation(float time) const
|
|
{
|
|
float xrot = interpKey(mXRotations->mKeys, time);
|
|
float yrot = interpKey(mYRotations->mKeys, time);
|
|
float zrot = interpKey(mZRotations->mKeys, time);
|
|
Ogre::Quaternion xr(Ogre::Radian(xrot), Ogre::Vector3::UNIT_X);
|
|
Ogre::Quaternion yr(Ogre::Radian(yrot), Ogre::Vector3::UNIT_Y);
|
|
Ogre::Quaternion zr(Ogre::Radian(zrot), Ogre::Vector3::UNIT_Z);
|
|
return (zr*yr*xr);
|
|
}
|
|
|
|
public:
|
|
/// @note The NiKeyFrameData must be valid as long as this KeyframeController exists.
|
|
Value(Ogre::Node *target, const Nif::NIFFilePtr& nif, const Nif::NiKeyframeData *data)
|
|
: NodeTargetValue<Ogre::Real>(target)
|
|
, mRotations(&data->mRotations)
|
|
, mXRotations(&data->mXRotations)
|
|
, mYRotations(&data->mYRotations)
|
|
, mZRotations(&data->mZRotations)
|
|
, mTranslations(&data->mTranslations)
|
|
, mScales(&data->mScales)
|
|
, mNif(nif)
|
|
{ }
|
|
|
|
virtual Ogre::Quaternion getRotation(float time) const
|
|
{
|
|
if(mRotations->mKeys.size() > 0)
|
|
return interpKey(mRotations->mKeys, time);
|
|
else if (!mXRotations->mKeys.empty() || !mYRotations->mKeys.empty() || !mZRotations->mKeys.empty())
|
|
return getXYZRotation(time);
|
|
return mNode->getOrientation();
|
|
}
|
|
|
|
virtual Ogre::Vector3 getTranslation(float time) const
|
|
{
|
|
if(mTranslations->mKeys.size() > 0)
|
|
return interpKey(mTranslations->mKeys, time);
|
|
return mNode->getPosition();
|
|
}
|
|
|
|
virtual Ogre::Vector3 getScale(float time) const
|
|
{
|
|
if(mScales->mKeys.size() > 0)
|
|
return Ogre::Vector3(interpKey(mScales->mKeys, time));
|
|
return mNode->getScale();
|
|
}
|
|
|
|
virtual Ogre::Real getValue() const
|
|
{
|
|
// Should not be called
|
|
return 0.0f;
|
|
}
|
|
|
|
virtual void setValue(Ogre::Real time)
|
|
{
|
|
if(mRotations->mKeys.size() > 0)
|
|
mNode->setOrientation(interpKey(mRotations->mKeys, time));
|
|
else if (!mXRotations->mKeys.empty() || !mYRotations->mKeys.empty() || !mZRotations->mKeys.empty())
|
|
mNode->setOrientation(getXYZRotation(time));
|
|
if(mTranslations->mKeys.size() > 0)
|
|
mNode->setPosition(interpKey(mTranslations->mKeys, time));
|
|
if(mScales->mKeys.size() > 0)
|
|
mNode->setScale(Ogre::Vector3(interpKey(mScales->mKeys, time)));
|
|
}
|
|
};
|
|
|
|
typedef DefaultFunction Function;
|
|
};
|
|
|
|
class UVController
|
|
{
|
|
public:
|
|
class Value : public Ogre::ControllerValue<Ogre::Real>, public ValueInterpolator
|
|
{
|
|
private:
|
|
Ogre::MovableObject* mMovable;
|
|
Nif::FloatKeyMap mUTrans;
|
|
Nif::FloatKeyMap mVTrans;
|
|
Nif::FloatKeyMap mUScale;
|
|
Nif::FloatKeyMap mVScale;
|
|
MaterialControllerManager* mMaterialControllerMgr;
|
|
|
|
public:
|
|
Value(Ogre::MovableObject* movable, const Nif::NiUVData *data, MaterialControllerManager* materialControllerMgr)
|
|
: mMovable(movable)
|
|
, mUTrans(data->mKeyList[0])
|
|
, mVTrans(data->mKeyList[1])
|
|
, mUScale(data->mKeyList[2])
|
|
, mVScale(data->mKeyList[3])
|
|
, mMaterialControllerMgr(materialControllerMgr)
|
|
{ }
|
|
|
|
virtual Ogre::Real getValue() const
|
|
{
|
|
// Should not be called
|
|
return 1.0f;
|
|
}
|
|
|
|
virtual void setValue(Ogre::Real value)
|
|
{
|
|
float uTrans = interpKey(mUTrans.mKeys, value, 0.0f);
|
|
float vTrans = interpKey(mVTrans.mKeys, value, 0.0f);
|
|
float uScale = interpKey(mUScale.mKeys, value, 1.0f);
|
|
float vScale = interpKey(mVScale.mKeys, value, 1.0f);
|
|
|
|
Ogre::MaterialPtr material = mMaterialControllerMgr->getWritableMaterial(mMovable);
|
|
|
|
Ogre::Material::TechniqueIterator techs = material->getTechniqueIterator();
|
|
while(techs.hasMoreElements())
|
|
{
|
|
Ogre::Technique *tech = techs.getNext();
|
|
Ogre::Technique::PassIterator passes = tech->getPassIterator();
|
|
while(passes.hasMoreElements())
|
|
{
|
|
Ogre::Pass *pass = passes.getNext();
|
|
Ogre::TextureUnitState *tex = pass->getTextureUnitState(0);
|
|
tex->setTextureScroll(uTrans, vTrans);
|
|
tex->setTextureScale(uScale, vScale);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
typedef DefaultFunction Function;
|
|
};
|
|
|
|
class ParticleSystemController
|
|
{
|
|
public:
|
|
class Value : public Ogre::ControllerValue<Ogre::Real>
|
|
{
|
|
private:
|
|
Ogre::ParticleSystem *mParticleSys;
|
|
float mEmitStart;
|
|
float mEmitStop;
|
|
|
|
public:
|
|
Value(Ogre::ParticleSystem *psys, const Nif::NiParticleSystemController *pctrl)
|
|
: mParticleSys(psys)
|
|
, mEmitStart(pctrl->startTime)
|
|
, mEmitStop(pctrl->stopTime)
|
|
{
|
|
}
|
|
|
|
Ogre::Real getValue() const
|
|
{ return 0.0f; }
|
|
|
|
void setValue(Ogre::Real value)
|
|
{
|
|
mParticleSys->setEmitting(value >= mEmitStart && value < mEmitStop);
|
|
}
|
|
};
|
|
|
|
typedef DefaultFunction Function;
|
|
};
|
|
|
|
class GeomMorpherController
|
|
{
|
|
public:
|
|
class Value : public Ogre::ControllerValue<Ogre::Real>, public ValueInterpolator
|
|
{
|
|
private:
|
|
Ogre::Entity *mEntity;
|
|
std::vector<Nif::NiMorphData::MorphData> mMorphs;
|
|
size_t mControllerIndex;
|
|
|
|
std::vector<Ogre::Vector3> mVertices;
|
|
|
|
public:
|
|
Value(Ogre::Entity *ent, const Nif::NiMorphData *data, size_t controllerIndex)
|
|
: mEntity(ent)
|
|
, mMorphs(data->mMorphs)
|
|
, mControllerIndex(controllerIndex)
|
|
{
|
|
}
|
|
|
|
virtual Ogre::Real getValue() const
|
|
{
|
|
// Should not be called
|
|
return 0.0f;
|
|
}
|
|
|
|
virtual void setValue(Ogre::Real time)
|
|
{
|
|
if (mMorphs.size() <= 1)
|
|
return;
|
|
int i = 1;
|
|
for (std::vector<Nif::NiMorphData::MorphData>::iterator it = mMorphs.begin()+1; it != mMorphs.end(); ++it,++i)
|
|
{
|
|
float val = 0;
|
|
if (!it->mData.mKeys.empty())
|
|
val = interpKey(it->mData.mKeys, time);
|
|
val = std::max(0.f, std::min(1.f, val));
|
|
|
|
Ogre::String animationID = Ogre::StringConverter::toString(mControllerIndex)
|
|
+ "_" + Ogre::StringConverter::toString(i);
|
|
|
|
Ogre::AnimationState* state = mEntity->getAnimationState(animationID);
|
|
state->setEnabled(val > 0);
|
|
state->setWeight(val);
|
|
}
|
|
}
|
|
};
|
|
|
|
typedef DefaultFunction Function;
|
|
};
|
|
|
|
|
|
/** Object creator for NIFs. This is the main class responsible for creating
|
|
* "live" Ogre objects (entities, particle systems, controllers, etc) from
|
|
* their NIF equivalents.
|
|
*/
|
|
class NIFObjectLoader
|
|
{
|
|
static bool sShowMarkers;
|
|
public:
|
|
static void setShowMarkers(bool show)
|
|
{
|
|
sShowMarkers = show;
|
|
}
|
|
private:
|
|
|
|
static void warn(const std::string &msg)
|
|
{
|
|
std::cerr << "NIFObjectLoader: Warn: " << msg << std::endl;
|
|
}
|
|
|
|
static void createEntity(const std::string &name, const std::string &group,
|
|
Ogre::SceneManager *sceneMgr, ObjectScenePtr scene,
|
|
const Nif::Node *node, int flags, int animflags)
|
|
{
|
|
const Nif::NiTriShape *shape = static_cast<const Nif::NiTriShape*>(node);
|
|
|
|
std::string fullname = name+"@index="+Ogre::StringConverter::toString(shape->recIndex);
|
|
if(shape->name.length() > 0)
|
|
fullname += "@shape="+shape->name;
|
|
Misc::StringUtils::toLower(fullname);
|
|
|
|
Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton();
|
|
if(meshMgr.getByName(fullname).isNull())
|
|
NIFMeshLoader::createMesh(name, fullname, group, shape->recIndex);
|
|
|
|
Ogre::Entity *entity = sceneMgr->createEntity(fullname);
|
|
|
|
#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
|
|
// Enable skeleton-based bounding boxes. With the static bounding box,
|
|
// the animation may cause parts to go outside the box and cause culling problems.
|
|
if (entity->hasSkeleton())
|
|
entity->setUpdateBoundingBoxFromSkeleton(true);
|
|
#endif
|
|
|
|
entity->setVisible(!(flags&Nif::NiNode::Flag_Hidden));
|
|
|
|
scene->mEntities.push_back(entity);
|
|
if(scene->mSkelBase)
|
|
{
|
|
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex);
|
|
Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
|
|
trgtbone->getUserObjectBindings().setUserAny(Ogre::Any(static_cast<Ogre::MovableObject*>(entity)));
|
|
|
|
if(entity->hasSkeleton())
|
|
entity->shareSkeletonInstanceWith(scene->mSkelBase);
|
|
else
|
|
scene->mSkelBase->attachObjectToBone(trgtbone->getName(), entity);
|
|
}
|
|
|
|
Nif::ControllerPtr ctrl = node->controller;
|
|
while(!ctrl.empty())
|
|
{
|
|
if (ctrl->flags & Nif::NiNode::ControllerFlag_Active)
|
|
{
|
|
if(ctrl->recType == Nif::RC_NiUVController)
|
|
{
|
|
const Nif::NiUVController *uv = static_cast<const Nif::NiUVController*>(ctrl.getPtr());
|
|
|
|
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
|
|
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
|
|
Ogre::ControllerValueRealPtr());
|
|
Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(entity, uv->data.getPtr(), &scene->mMaterialControllerMgr));
|
|
|
|
UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
|
|
scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
|
|
Ogre::ControllerFunctionRealPtr func(function);
|
|
|
|
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
|
}
|
|
else if(ctrl->recType == Nif::RC_NiGeomMorpherController)
|
|
{
|
|
const Nif::NiGeomMorpherController *geom = static_cast<const Nif::NiGeomMorpherController*>(ctrl.getPtr());
|
|
|
|
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
|
|
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
|
|
Ogre::ControllerValueRealPtr());
|
|
Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value(
|
|
entity, geom->data.getPtr(), geom->recIndex));
|
|
|
|
GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
|
|
scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
|
|
Ogre::ControllerFunctionRealPtr func(function);
|
|
|
|
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
|
}
|
|
}
|
|
ctrl = ctrl->next;
|
|
}
|
|
|
|
createMaterialControllers(shape, entity, animflags, scene);
|
|
}
|
|
|
|
static void createMaterialControllers (const Nif::Node* node, Ogre::MovableObject* movable, int animflags, ObjectScenePtr scene)
|
|
{
|
|
const Nif::NiTexturingProperty *texprop = NULL;
|
|
const Nif::NiMaterialProperty *matprop = NULL;
|
|
const Nif::NiAlphaProperty *alphaprop = NULL;
|
|
const Nif::NiVertexColorProperty *vertprop = NULL;
|
|
const Nif::NiZBufferProperty *zprop = NULL;
|
|
const Nif::NiSpecularProperty *specprop = NULL;
|
|
const Nif::NiWireframeProperty *wireprop = NULL;
|
|
const Nif::NiStencilProperty *stencilprop = NULL;
|
|
node->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop);
|
|
|
|
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
|
|
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
|
|
Ogre::ControllerValueRealPtr());
|
|
|
|
if(matprop)
|
|
{
|
|
Nif::ControllerPtr ctrls = matprop->controller;
|
|
while(!ctrls.empty())
|
|
{
|
|
if (ctrls->recType == Nif::RC_NiAlphaController)
|
|
{
|
|
const Nif::NiAlphaController *alphaCtrl = static_cast<const Nif::NiAlphaController*>(ctrls.getPtr());
|
|
Ogre::ControllerValueRealPtr dstval(OGRE_NEW AlphaController::Value(movable, alphaCtrl->data.getPtr(), &scene->mMaterialControllerMgr));
|
|
AlphaController::Function* function = OGRE_NEW AlphaController::Function(alphaCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
|
|
scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
|
|
Ogre::ControllerFunctionRealPtr func(function);
|
|
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
|
}
|
|
else if (ctrls->recType == Nif::RC_NiMaterialColorController)
|
|
{
|
|
const Nif::NiMaterialColorController *matCtrl = static_cast<const Nif::NiMaterialColorController*>(ctrls.getPtr());
|
|
Ogre::ControllerValueRealPtr dstval(OGRE_NEW MaterialColorController::Value(movable, matCtrl->data.getPtr(), &scene->mMaterialControllerMgr));
|
|
MaterialColorController::Function* function = OGRE_NEW MaterialColorController::Function(matCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
|
|
scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
|
|
Ogre::ControllerFunctionRealPtr func(function);
|
|
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
|
}
|
|
|
|
ctrls = ctrls->next;
|
|
}
|
|
}
|
|
if (texprop)
|
|
{
|
|
Nif::ControllerPtr ctrls = texprop->controller;
|
|
while(!ctrls.empty())
|
|
{
|
|
if (ctrls->recType == Nif::RC_NiFlipController)
|
|
{
|
|
const Nif::NiFlipController *flipCtrl = static_cast<const Nif::NiFlipController*>(ctrls.getPtr());
|
|
|
|
|
|
Ogre::ControllerValueRealPtr dstval(OGRE_NEW FlipController::Value(
|
|
movable, flipCtrl, &scene->mMaterialControllerMgr));
|
|
FlipController::Function* function = OGRE_NEW FlipController::Function(flipCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
|
|
scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
|
|
Ogre::ControllerFunctionRealPtr func(function);
|
|
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
|
}
|
|
|
|
ctrls = ctrls->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void createParticleEmitterAffectors(Ogre::ParticleSystem *partsys,
|
|
const Nif::NiParticleSystemController *partctrl, Ogre::Bone* bone,
|
|
const std::string& skelBaseName)
|
|
{
|
|
Ogre::ParticleEmitter *emitter = partsys->addEmitter("Nif");
|
|
emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f,
|
|
partctrl->velocity + partctrl->velocityRandom*0.5f);
|
|
|
|
if (partctrl->emitFlags & Nif::NiParticleSystemController::NoAutoAdjust)
|
|
emitter->setEmissionRate(partctrl->emitRate);
|
|
else
|
|
emitter->setEmissionRate(partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom/2));
|
|
|
|
emitter->setTimeToLive(std::max(0.f, partctrl->lifetime),
|
|
std::max(0.f, partctrl->lifetime + partctrl->lifetimeRandom));
|
|
emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x));
|
|
emitter->setParameter("height", Ogre::StringConverter::toString(partctrl->offsetRandom.y));
|
|
emitter->setParameter("depth", Ogre::StringConverter::toString(partctrl->offsetRandom.z));
|
|
emitter->setParameter("vertical_direction", Ogre::StringConverter::toString(Ogre::Radian(partctrl->verticalDir).valueDegrees()));
|
|
emitter->setParameter("vertical_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->verticalAngle).valueDegrees()));
|
|
emitter->setParameter("horizontal_direction", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalDir).valueDegrees()));
|
|
emitter->setParameter("horizontal_angle", Ogre::StringConverter::toString(Ogre::Radian(partctrl->horizontalAngle).valueDegrees()));
|
|
|
|
Nif::ExtraPtr e = partctrl->extra;
|
|
while(!e.empty())
|
|
{
|
|
if(e->recType == Nif::RC_NiParticleGrowFade)
|
|
{
|
|
const Nif::NiParticleGrowFade *gf = static_cast<const Nif::NiParticleGrowFade*>(e.getPtr());
|
|
|
|
Ogre::ParticleAffector *affector = partsys->addAffector("GrowFade");
|
|
affector->setParameter("grow_time", Ogre::StringConverter::toString(gf->growTime));
|
|
affector->setParameter("fade_time", Ogre::StringConverter::toString(gf->fadeTime));
|
|
}
|
|
else if(e->recType == Nif::RC_NiGravity)
|
|
{
|
|
const Nif::NiGravity *gr = static_cast<const Nif::NiGravity*>(e.getPtr());
|
|
|
|
Ogre::ParticleAffector *affector = partsys->addAffector("Gravity");
|
|
affector->setParameter("force", Ogre::StringConverter::toString(gr->mForce));
|
|
affector->setParameter("force_type", (gr->mType==0) ? "wind" : "point");
|
|
affector->setParameter("direction", Ogre::StringConverter::toString(gr->mDirection));
|
|
affector->setParameter("position", Ogre::StringConverter::toString(gr->mPosition));
|
|
affector->setParameter("skelbase", skelBaseName);
|
|
affector->setParameter("bone", bone->getName());
|
|
}
|
|
else if(e->recType == Nif::RC_NiParticleColorModifier)
|
|
{
|
|
const Nif::NiParticleColorModifier *cl = static_cast<const Nif::NiParticleColorModifier*>(e.getPtr());
|
|
const Nif::NiColorData *clrdata = cl->data.getPtr();
|
|
|
|
Ogre::ParticleAffector *affector = partsys->addAffector("ColourInterpolator");
|
|
size_t num_colors = std::min<size_t>(6, clrdata->mKeyMap.mKeys.size());
|
|
unsigned int i=0;
|
|
for (Nif::Vector4KeyMap::MapType::const_iterator it = clrdata->mKeyMap.mKeys.begin(); it != clrdata->mKeyMap.mKeys.end() && i < num_colors; ++it,++i)
|
|
{
|
|
Ogre::ColourValue color;
|
|
color.r = it->second.mValue[0];
|
|
color.g = it->second.mValue[1];
|
|
color.b = it->second.mValue[2];
|
|
color.a = it->second.mValue[3];
|
|
affector->setParameter("colour"+Ogre::StringConverter::toString(i),
|
|
Ogre::StringConverter::toString(color));
|
|
affector->setParameter("time"+Ogre::StringConverter::toString(i),
|
|
Ogre::StringConverter::toString(it->first));
|
|
}
|
|
}
|
|
else if(e->recType == Nif::RC_NiParticleRotation)
|
|
{
|
|
// TODO: Implement (Ogre::RotationAffector?)
|
|
}
|
|
else
|
|
warn("Unhandled particle modifier "+e->recName);
|
|
e = e->extra;
|
|
}
|
|
}
|
|
|
|
static void createParticleSystem(const std::string &name, const std::string &group,
|
|
Ogre::SceneNode *sceneNode, ObjectScenePtr scene,
|
|
const Nif::Node *partnode, int flags, int partflags, int animflags)
|
|
{
|
|
const Nif::NiAutoNormalParticlesData *particledata = NULL;
|
|
if(partnode->recType == Nif::RC_NiAutoNormalParticles)
|
|
particledata = static_cast<const Nif::NiAutoNormalParticles*>(partnode)->data.getPtr();
|
|
else if(partnode->recType == Nif::RC_NiRotatingParticles)
|
|
particledata = static_cast<const Nif::NiRotatingParticles*>(partnode)->data.getPtr();
|
|
else
|
|
throw std::runtime_error("Unexpected particle node type");
|
|
|
|
std::string fullname = name+"@index="+Ogre::StringConverter::toString(partnode->recIndex);
|
|
if(partnode->name.length() > 0)
|
|
fullname += "@type="+partnode->name;
|
|
Misc::StringUtils::toLower(fullname);
|
|
|
|
Ogre::ParticleSystem *partsys = sceneNode->getCreator()->createParticleSystem();
|
|
|
|
const Nif::NiTexturingProperty *texprop = NULL;
|
|
const Nif::NiMaterialProperty *matprop = NULL;
|
|
const Nif::NiAlphaProperty *alphaprop = NULL;
|
|
const Nif::NiVertexColorProperty *vertprop = NULL;
|
|
const Nif::NiZBufferProperty *zprop = NULL;
|
|
const Nif::NiSpecularProperty *specprop = NULL;
|
|
const Nif::NiWireframeProperty *wireprop = NULL;
|
|
const Nif::NiStencilProperty *stencilprop = NULL;
|
|
bool needTangents = false;
|
|
|
|
partnode->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop, stencilprop);
|
|
partsys->setMaterialName(NIFMaterialLoader::getMaterial(particledata, fullname, group,
|
|
texprop, matprop, alphaprop,
|
|
vertprop, zprop, specprop,
|
|
wireprop, stencilprop, needTangents,
|
|
// MW doesn't light particles, but the MaterialProperty
|
|
// used still has lighting, so that must be ignored.
|
|
true));
|
|
|
|
partsys->setCullIndividually(false);
|
|
partsys->setParticleQuota(particledata->numParticles);
|
|
partsys->setKeepParticlesInLocalSpace(partflags & (Nif::NiNode::ParticleFlag_LocalSpace));
|
|
|
|
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partnode->recIndex);
|
|
Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
|
|
scene->mSkelBase->attachObjectToBone(trgtbone->getName(), partsys);
|
|
|
|
Nif::ControllerPtr ctrl = partnode->controller;
|
|
while(!ctrl.empty())
|
|
{
|
|
if((ctrl->recType == Nif::RC_NiParticleSystemController || ctrl->recType == Nif::RC_NiBSPArrayController)
|
|
&& ctrl->flags & Nif::NiNode::ControllerFlag_Active)
|
|
{
|
|
const Nif::NiParticleSystemController *partctrl = static_cast<const Nif::NiParticleSystemController*>(ctrl.getPtr());
|
|
|
|
float size = partctrl->size*2;
|
|
// HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail
|
|
size = std::max(size, 0.00001f);
|
|
partsys->setDefaultDimensions(size, size);
|
|
|
|
if(!partctrl->emitter.empty())
|
|
{
|
|
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, partctrl->emitter->recIndex);
|
|
Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
|
|
// Set the emitter bone(s) as user data on the particle system
|
|
// so the emitters/affectors can access it easily.
|
|
std::vector<Ogre::Bone*> bones;
|
|
if (partctrl->recType == Nif::RC_NiBSPArrayController)
|
|
{
|
|
std::vector<const Nif::NiNode*> nodes;
|
|
getAllNiNodes(partctrl->emitter.getPtr(), nodes);
|
|
if (nodes.empty())
|
|
throw std::runtime_error("Emitter for NiBSPArrayController must be a NiNode");
|
|
for (unsigned int i=0; i<nodes.size(); ++i)
|
|
{
|
|
bones.push_back(scene->mSkelBase->getSkeleton()->getBone(
|
|
NIFSkeletonLoader::lookupOgreBoneHandle(name, nodes[i]->recIndex)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bones.push_back(trgtbone);
|
|
}
|
|
NiNodeHolder holder;
|
|
holder.mBones = bones;
|
|
partsys->getUserObjectBindings().setUserAny(Ogre::Any(holder));
|
|
createParticleEmitterAffectors(partsys, partctrl, trgtbone, scene->mSkelBase->getName());
|
|
}
|
|
|
|
createParticleInitialState(partsys, particledata, partctrl);
|
|
|
|
Ogre::ControllerValueRealPtr srcval((partflags&Nif::NiNode::ParticleFlag_AutoPlay) ?
|
|
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
|
|
Ogre::ControllerValueRealPtr());
|
|
Ogre::ControllerValueRealPtr dstval(OGRE_NEW ParticleSystemController::Value(partsys, partctrl));
|
|
|
|
ParticleSystemController::Function* function =
|
|
OGRE_NEW ParticleSystemController::Function(partctrl, (partflags&Nif::NiNode::ParticleFlag_AutoPlay));
|
|
scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
|
|
Ogre::ControllerFunctionRealPtr func(function);
|
|
|
|
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
|
|
|
// Emitting state will be overwritten on frame update by the ParticleSystemController,
|
|
// but set up an initial value anyway so the user can fast-forward particle systems
|
|
// immediately after creation if desired.
|
|
partsys->setEmitting(partflags&Nif::NiNode::ParticleFlag_AutoPlay);
|
|
}
|
|
ctrl = ctrl->next;
|
|
}
|
|
|
|
partsys->setVisible(!(flags&Nif::NiNode::Flag_Hidden));
|
|
scene->mParticles.push_back(partsys);
|
|
|
|
createMaterialControllers(partnode, partsys, animflags, scene);
|
|
}
|
|
|
|
static void createParticleInitialState(Ogre::ParticleSystem* partsys, const Nif::NiAutoNormalParticlesData* particledata,
|
|
const Nif::NiParticleSystemController* partctrl)
|
|
{
|
|
partsys->_update(0.f); // seems to be required to allocate mFreeParticles. TODO: patch Ogre to handle this better
|
|
int i=0;
|
|
for (std::vector<Nif::NiParticleSystemController::Particle>::const_iterator it = partctrl->particles.begin();
|
|
i<particledata->activeCount && it != partctrl->particles.end(); ++it, ++i)
|
|
{
|
|
const Nif::NiParticleSystemController::Particle& particle = *it;
|
|
|
|
Ogre::Particle* created = partsys->createParticle();
|
|
if (!created)
|
|
break;
|
|
|
|
#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0)
|
|
Ogre::Vector3& position = created->mPosition;
|
|
Ogre::Vector3& direction = created->mDirection;
|
|
Ogre::ColourValue& colour = created->mColour;
|
|
float& totalTimeToLive = created->mTotalTimeToLive;
|
|
float& timeToLive = created->mTimeToLive;
|
|
#else
|
|
Ogre::Vector3& position = created->position;
|
|
Ogre::Vector3& direction = created->direction;
|
|
Ogre::ColourValue& colour = created->colour;
|
|
float& totalTimeToLive = created->totalTimeToLive;
|
|
float& timeToLive = created->timeToLive;
|
|
#endif
|
|
|
|
direction = particle.velocity;
|
|
position = particledata->vertices.at(particle.vertex);
|
|
|
|
if (particle.vertex < int(particledata->colors.size()))
|
|
{
|
|
Ogre::Vector4 partcolour = particledata->colors.at(particle.vertex);
|
|
colour = Ogre::ColourValue(partcolour.x, partcolour.y, partcolour.z, partcolour.w);
|
|
}
|
|
else
|
|
colour = Ogre::ColourValue(1.f, 1.f, 1.f, 1.f);
|
|
float size = particledata->sizes.at(particle.vertex);
|
|
created->setDimensions(size, size);
|
|
totalTimeToLive = std::max(0.f, particle.lifespan);
|
|
timeToLive = std::max(0.f, particle.lifespan - particle.lifetime);
|
|
}
|
|
partsys->_update(0.f); // now apparently needs another update, otherwise it won't render in the first frame. TODO: patch Ogre to handle this better
|
|
}
|
|
|
|
static void createNodeControllers(const Nif::NIFFilePtr& nif, const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags)
|
|
{
|
|
do {
|
|
if (ctrl->flags & Nif::NiNode::ControllerFlag_Active)
|
|
{
|
|
if(ctrl->recType == Nif::RC_NiVisController)
|
|
{
|
|
const Nif::NiVisController *vis = static_cast<const Nif::NiVisController*>(ctrl.getPtr());
|
|
|
|
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex);
|
|
Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
|
|
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
|
|
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
|
|
Ogre::ControllerValueRealPtr());
|
|
Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr()));
|
|
|
|
VisController::Function* function = OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
|
|
scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
|
|
Ogre::ControllerFunctionRealPtr func(function);
|
|
|
|
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
|
}
|
|
else if(ctrl->recType == Nif::RC_NiKeyframeController)
|
|
{
|
|
const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
|
|
if(!key->data.empty())
|
|
{
|
|
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex);
|
|
Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
|
|
// The keyframe controller will control this bone manually
|
|
trgtbone->setManuallyControlled(true);
|
|
Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ?
|
|
Ogre::ControllerManager::getSingleton().getFrameTimeSource() :
|
|
Ogre::ControllerValueRealPtr());
|
|
Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, nif, key->data.getPtr()));
|
|
KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay));
|
|
scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength);
|
|
Ogre::ControllerFunctionRealPtr func(function);
|
|
|
|
scene->mControllers.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
|
}
|
|
}
|
|
}
|
|
ctrl = ctrl->next;
|
|
} while(!ctrl.empty());
|
|
}
|
|
|
|
|
|
static void extractTextKeys(const Nif::NiTextKeyExtraData *tk, TextKeyMap &textkeys)
|
|
{
|
|
for(size_t i = 0;i < tk->list.size();i++)
|
|
{
|
|
const std::string &str = tk->list[i].text;
|
|
std::string::size_type pos = 0;
|
|
while(pos < str.length())
|
|
{
|
|
if(::isspace(str[pos]))
|
|
{
|
|
pos++;
|
|
continue;
|
|
}
|
|
|
|
std::string::size_type nextpos = std::min(str.find('\r', pos), str.find('\n', pos));
|
|
if(nextpos != std::string::npos)
|
|
{
|
|
do {
|
|
nextpos--;
|
|
} while(nextpos > pos && ::isspace(str[nextpos]));
|
|
nextpos++;
|
|
}
|
|
else if(::isspace(*str.rbegin()))
|
|
{
|
|
std::string::const_iterator last = str.end();
|
|
do {
|
|
--last;
|
|
} while(last != str.begin() && ::isspace(*last));
|
|
nextpos = std::distance(str.begin(), ++last);
|
|
}
|
|
std::string result = str.substr(pos, nextpos-pos);
|
|
textkeys.insert(std::make_pair(tk->list[i].time, Misc::StringUtils::toLower(result)));
|
|
|
|
pos = nextpos;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void createObjects(const Nif::NIFFilePtr& nif, const std::string &name, const std::string &group,
|
|
Ogre::SceneNode *sceneNode, const Nif::Node *node,
|
|
ObjectScenePtr scene, int flags, int animflags, int partflags, bool isRootCollisionNode=false)
|
|
{
|
|
// Do not create objects for the collision shape (includes all children)
|
|
if(node->recType == Nif::RC_RootCollisionNode)
|
|
isRootCollisionNode = true;
|
|
|
|
if(node->recType == Nif::RC_NiBSAnimationNode)
|
|
animflags |= node->flags;
|
|
else if(node->recType == Nif::RC_NiBSParticleNode)
|
|
partflags |= node->flags;
|
|
else
|
|
flags |= node->flags;
|
|
|
|
if (node->recType == Nif::RC_NiBillboardNode)
|
|
{
|
|
// TODO: figure out what the flags mean.
|
|
// NifSkope has names for them, but doesn't implement them.
|
|
// Change mBillboardNodes to map <Bone, billboard type>
|
|
int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, node->recIndex);
|
|
Ogre::Bone* bone = scene->mSkelBase->getSkeleton()->getBone(trgtid);
|
|
bone->setManuallyControlled(true);
|
|
scene->mBillboardNodes.push_back(bone);
|
|
}
|
|
|
|
Nif::ExtraPtr e = node->extra;
|
|
while(!e.empty())
|
|
{
|
|
if(e->recType == Nif::RC_NiTextKeyExtraData)
|
|
{
|
|
const Nif::NiTextKeyExtraData *tk = static_cast<const Nif::NiTextKeyExtraData*>(e.getPtr());
|
|
|
|
extractTextKeys(tk, scene->mTextKeys);
|
|
}
|
|
else if(e->recType == Nif::RC_NiStringExtraData)
|
|
{
|
|
const Nif::NiStringExtraData *sd = static_cast<const Nif::NiStringExtraData*>(e.getPtr());
|
|
// String markers may contain important information
|
|
// affecting the entire subtree of this obj
|
|
if(sd->string == "MRK" && !sShowMarkers)
|
|
{
|
|
// Marker objects. These meshes are only visible in the
|
|
// editor.
|
|
flags |= 0x80000000;
|
|
}
|
|
}
|
|
|
|
e = e->extra;
|
|
}
|
|
|
|
if(!node->controller.empty())
|
|
createNodeControllers(nif, name, node->controller, scene, animflags);
|
|
|
|
if (!isRootCollisionNode)
|
|
{
|
|
if(node->recType == Nif::RC_NiCamera)
|
|
{
|
|
/* Ignored */
|
|
}
|
|
|
|
if(node->recType == Nif::RC_NiTriShape && !(flags&0x80000000))
|
|
{
|
|
createEntity(name, group, sceneNode->getCreator(), scene, node, flags, animflags);
|
|
}
|
|
|
|
if((node->recType == Nif::RC_NiAutoNormalParticles ||
|
|
node->recType == Nif::RC_NiRotatingParticles) && !(flags&0x40000000))
|
|
{
|
|
createParticleSystem(name, group, sceneNode, scene, node, flags, partflags, animflags);
|
|
}
|
|
}
|
|
|
|
const Nif::NiNode *ninode = dynamic_cast<const Nif::NiNode*>(node);
|
|
if(ninode)
|
|
{
|
|
const Nif::NodeList &children = ninode->children;
|
|
for(size_t i = 0;i < children.length();i++)
|
|
{
|
|
if(!children[i].empty())
|
|
createObjects(nif, name, group, sceneNode, children[i].getPtr(), scene, flags, animflags, partflags, isRootCollisionNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void createSkelBase(const std::string &name, const std::string &group,
|
|
Ogre::SceneManager *sceneMgr, const Nif::Node *node,
|
|
ObjectScenePtr scene)
|
|
{
|
|
/* This creates an empty mesh to which a skeleton gets attached. This
|
|
* is to ensure we have an entity with a skeleton instance, even if all
|
|
* other entities are attached to bones and not skinned. */
|
|
Ogre::MeshManager &meshMgr = Ogre::MeshManager::getSingleton();
|
|
if(meshMgr.getByName(name).isNull())
|
|
NIFMeshLoader::createMesh(name, name, group, ~(size_t)0);
|
|
|
|
scene->mSkelBase = sceneMgr->createEntity(name);
|
|
scene->mEntities.push_back(scene->mSkelBase);
|
|
}
|
|
|
|
public:
|
|
static void load(Ogre::SceneNode *sceneNode, ObjectScenePtr scene, const std::string &name, const std::string &group, int flags=0)
|
|
{
|
|
Nif::NIFFilePtr nif = Nif::Cache::getInstance().load(name);
|
|
if(nif->numRoots() < 1)
|
|
{
|
|
nif->warn("Found no root nodes in "+name+".");
|
|
return;
|
|
}
|
|
|
|
const Nif::Record *r = nif->getRoot(0);
|
|
assert(r != NULL);
|
|
|
|
const Nif::Node *node = dynamic_cast<const Nif::Node*>(r);
|
|
if(node == NULL)
|
|
{
|
|
nif->warn("First root in "+name+" was not a node, but a "+
|
|
r->recName+".");
|
|
return;
|
|
}
|
|
|
|
if(Ogre::SkeletonManager::getSingleton().resourceExists(name) ||
|
|
!NIFSkeletonLoader::createSkeleton(name, group, node).isNull())
|
|
{
|
|
// Create a base skeleton entity if this NIF needs one
|
|
createSkelBase(name, group, sceneNode->getCreator(), node, scene);
|
|
}
|
|
createObjects(nif, name, group, sceneNode, node, scene, flags, 0, 0);
|
|
}
|
|
|
|
static void loadKf(Ogre::Skeleton *skel, const std::string &name,
|
|
TextKeyMap &textKeys, std::vector<Ogre::Controller<Ogre::Real> > &ctrls)
|
|
{
|
|
Nif::NIFFilePtr nif = Nif::Cache::getInstance().load(name);
|
|
if(nif->numRoots() < 1)
|
|
{
|
|
nif->warn("Found no root nodes in "+name+".");
|
|
return;
|
|
}
|
|
|
|
const Nif::Record *r = nif->getRoot(0);
|
|
assert(r != NULL);
|
|
|
|
if(r->recType != Nif::RC_NiSequenceStreamHelper)
|
|
{
|
|
nif->warn("First root was not a NiSequenceStreamHelper, but a "+
|
|
r->recName+".");
|
|
return;
|
|
}
|
|
const Nif::NiSequenceStreamHelper *seq = static_cast<const Nif::NiSequenceStreamHelper*>(r);
|
|
|
|
Nif::ExtraPtr extra = seq->extra;
|
|
if(extra.empty() || extra->recType != Nif::RC_NiTextKeyExtraData)
|
|
{
|
|
nif->warn("First extra data was not a NiTextKeyExtraData, but a "+
|
|
(extra.empty() ? std::string("nil") : extra->recName)+".");
|
|
return;
|
|
}
|
|
|
|
extractTextKeys(static_cast<const Nif::NiTextKeyExtraData*>(extra.getPtr()), textKeys);
|
|
|
|
extra = extra->extra;
|
|
Nif::ControllerPtr ctrl = seq->controller;
|
|
for(;!extra.empty() && !ctrl.empty();(extra=extra->extra),(ctrl=ctrl->next))
|
|
{
|
|
if(extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController)
|
|
{
|
|
nif->warn("Unexpected extra data "+extra->recName+" with controller "+ctrl->recName);
|
|
continue;
|
|
}
|
|
|
|
if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active))
|
|
continue;
|
|
|
|
const Nif::NiStringExtraData *strdata = static_cast<const Nif::NiStringExtraData*>(extra.getPtr());
|
|
const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
|
|
|
|
if(key->data.empty())
|
|
continue;
|
|
if(!skel->hasBone(strdata->string))
|
|
continue;
|
|
|
|
Ogre::Bone *trgtbone = skel->getBone(strdata->string);
|
|
Ogre::ControllerValueRealPtr srcval;
|
|
Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, nif, key->data.getPtr()));
|
|
Ogre::ControllerFunctionRealPtr func(OGRE_NEW KeyframeController::Function(key, false));
|
|
|
|
ctrls.push_back(Ogre::Controller<Ogre::Real>(srcval, dstval, func));
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string name, const std::string &group)
|
|
{
|
|
ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator()));
|
|
|
|
Misc::StringUtils::toLower(name);
|
|
NIFObjectLoader::load(parentNode, scene, name, group);
|
|
|
|
for(size_t i = 0;i < scene->mEntities.size();i++)
|
|
{
|
|
Ogre::Entity *entity = scene->mEntities[i];
|
|
if(!entity->isAttached())
|
|
parentNode->attachObject(entity);
|
|
}
|
|
|
|
scene->_notifyAttached();
|
|
|
|
return scene;
|
|
}
|
|
|
|
ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bonename,
|
|
const std::string& bonefilter,
|
|
Ogre::SceneNode *parentNode,
|
|
std::string name, const std::string &group)
|
|
{
|
|
ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator()));
|
|
|
|
Misc::StringUtils::toLower(name);
|
|
NIFObjectLoader::load(parentNode, scene, name, group);
|
|
|
|
bool isskinned = false;
|
|
for(size_t i = 0;i < scene->mEntities.size();i++)
|
|
{
|
|
Ogre::Entity *ent = scene->mEntities[i];
|
|
if(scene->mSkelBase != ent && ent->hasSkeleton())
|
|
{
|
|
isskinned = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Ogre::Vector3 scale(1.0f);
|
|
if(bonename.find("Left") != std::string::npos)
|
|
scale.x *= -1.0f;
|
|
|
|
if(isskinned)
|
|
{
|
|
// accepts anything named "filter*" or "tri filter*"
|
|
std::string filter = "@shape=tri "+bonefilter;
|
|
std::string filter2 = "@shape="+bonefilter;
|
|
Misc::StringUtils::toLower(filter);
|
|
Misc::StringUtils::toLower(filter2);
|
|
for(size_t i = 0;i < scene->mEntities.size();i++)
|
|
{
|
|
Ogre::Entity *entity = scene->mEntities[i];
|
|
if(entity->hasSkeleton())
|
|
{
|
|
if(entity == scene->mSkelBase ||
|
|
entity->getMesh()->getName().find(filter) != std::string::npos
|
|
|| entity->getMesh()->getName().find(filter2) != std::string::npos)
|
|
parentNode->attachObject(entity);
|
|
}
|
|
else
|
|
{
|
|
if(entity->getMesh()->getName().find(filter) == std::string::npos
|
|
|| entity->getMesh()->getName().find(filter2) == std::string::npos)
|
|
entity->detachFromParent();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(size_t i = 0;i < scene->mEntities.size();i++)
|
|
{
|
|
Ogre::Entity *entity = scene->mEntities[i];
|
|
if(!entity->isAttached())
|
|
{
|
|
Ogre::TagPoint *tag = parent->attachObjectToBone(bonename, entity);
|
|
tag->setScale(scale);
|
|
}
|
|
}
|
|
}
|
|
|
|
scene->_notifyAttached();
|
|
|
|
return scene;
|
|
}
|
|
|
|
|
|
ObjectScenePtr Loader::createObjectBase(Ogre::SceneNode *parentNode, std::string name, const std::string &group)
|
|
{
|
|
ObjectScenePtr scene = ObjectScenePtr (new ObjectScene(parentNode->getCreator()));
|
|
|
|
Misc::StringUtils::toLower(name);
|
|
NIFObjectLoader::load(parentNode, scene, name, group, 0xC0000000);
|
|
|
|
if(scene->mSkelBase)
|
|
parentNode->attachObject(scene->mSkelBase);
|
|
|
|
return scene;
|
|
}
|
|
|
|
|
|
void Loader::createKfControllers(Ogre::Entity *skelBase,
|
|
const std::string &name,
|
|
TextKeyMap &textKeys,
|
|
std::vector<Ogre::Controller<Ogre::Real> > &ctrls)
|
|
{
|
|
NIFObjectLoader::loadKf(skelBase->getSkeleton(), name, textKeys, ctrls);
|
|
}
|
|
|
|
bool Loader::sShowMarkers = false;
|
|
bool NIFObjectLoader::sShowMarkers = false;
|
|
|
|
void Loader::setShowMarkers(bool show)
|
|
{
|
|
sShowMarkers = show;
|
|
NIFObjectLoader::setShowMarkers(show);
|
|
}
|
|
|
|
|
|
} // namespace NifOgre
|