diff --git a/CMakeLists.txt b/CMakeLists.txt index cbe142fcc..d5b37a05b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -221,7 +221,7 @@ if (${OGRE_VERSION} VERSION_LESS "1.9") message(FATAL_ERROR "OpenMW requires Ogre 1.9 or later, please install the latest stable version from http://ogre3d.org") endif() -find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB) +find_package(OpenSceneGraph 3.2.0 REQUIRED osgDB osgViewer osgGA osgAnimation) include_directories(${OPENSCENEGRAPH_INCLUDE_DIRS}) find_package(MyGUI REQUIRED) @@ -592,6 +592,8 @@ if (BUILD_NIFTEST) endif(BUILD_NIFTEST) # Apps and tools +add_subdirectory( apps/nifosgtest ) + if (BUILD_OPENMW) add_subdirectory( apps/openmw ) endif() diff --git a/apps/nifosgtest/CMakeLists.txt b/apps/nifosgtest/CMakeLists.txt new file mode 100644 index 000000000..265577d98 --- /dev/null +++ b/apps/nifosgtest/CMakeLists.txt @@ -0,0 +1,7 @@ +set (FILES + test.cpp +) + +add_executable (test ${FILES}) + +target_link_libraries (test ${OPENSCENEGRAPH_LIBRARIES} "components") diff --git a/apps/nifosgtest/test.cpp b/apps/nifosgtest/test.cpp new file mode 100644 index 000000000..fc82059fc --- /dev/null +++ b/apps/nifosgtest/test.cpp @@ -0,0 +1,100 @@ +#include + +#include +#include + +#include + +#include + +#include +#include + +#include + +// EventHandler to toggle wireframe when 'w' key is pressed +class EventHandler : public osgGA::GUIEventHandler +{ +public: + EventHandler(osg::Node* node) + : mWireframe(false) + , mNode(node) + { + + } + + virtual bool handle(const osgGA::GUIEventAdapter& adapter,osgGA::GUIActionAdapter& action) + { + switch (adapter.getEventType()) + { + case osgGA::GUIEventAdapter::KEYDOWN: + if (adapter.getKey() == osgGA::GUIEventAdapter::KEY_W) + { + mWireframe = !mWireframe; + osg::PolygonMode* mode = new osg::PolygonMode; + mode->setMode(osg::PolygonMode::FRONT_AND_BACK, + mWireframe ? osg::PolygonMode::LINE : osg::PolygonMode::FILL); + mNode->getOrCreateStateSet()->setAttributeAndModes(mode, osg::StateAttribute::ON); + mNode->getOrCreateStateSet()->setMode(GL_CULL_FACE, mWireframe ? osg::StateAttribute::OFF + : osg::StateAttribute::ON); + return true; + } + default: + break; + } + return false; + } + +private: + bool mWireframe; + osg::Node* mNode; +}; + +int main(int argc, char** argv) +{ + if (argc < 3) + { + std::cout << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + + Bsa::BSAFile bsa; + bsa.open(argv[1]); + + Nif::NIFFilePtr nif(new Nif::NIFFile(bsa.getFile(argv[2]), std::string(argv[2]))); + + osgViewer::Viewer viewer; + + osg::ref_ptr root(new osg::Group()); + root->getOrCreateStateSet()->setMode(GL_CULL_FACE, osg::StateAttribute::ON); + // To prevent lighting issues with scaled meshes + root->getOrCreateStateSet()->setMode(GL_NORMALIZE, osg::StateAttribute::ON); + + osg::Group* newNode = new osg::Group; + NifOsg::Loader loader; + loader.resourceManager = &bsa; + loader.loadAsSkeleton(nif, newNode); + + //osgDB::writeNodeFile(*newNode, "out.osg"); + + for (int x=0; x<1;++x) + { + root->addChild(newNode); + } + + viewer.setSceneData(root); + + viewer.setUpViewInWindow(0, 0, 800, 600); + viewer.realize(); + viewer.setCameraManipulator(new osgGA::TrackballManipulator()); + viewer.addEventHandler(new EventHandler(root)); + + while (!viewer.done()) + { + viewer.frame(); + + for (unsigned int i=0; igetChar(); + if(external) + filename = nif->getString(); + else + { + nif->getChar(); // always 1 + data.read(nif); + } + + pixel = nif->getInt(); + mipmap = nif->getInt(); + alpha = nif->getInt(); + + nif->getChar(); // always 1 + } + + void NiSourceTexture::post(NIFFile *nif) + { + Named::post(nif); + data.post(nif); + } + + void NiParticleGrowFade::read(NIFStream *nif) + { + Controlled::read(nif); + growTime = nif->getFloat(); + fadeTime = nif->getFloat(); + } + + void NiParticleColorModifier::read(NIFStream *nif) + { + Controlled::read(nif); + data.read(nif); + } + + void NiParticleColorModifier::post(NIFFile *nif) + { + Controlled::post(nif); + data.post(nif); + } + + void NiGravity::read(NIFStream *nif) + { + Controlled::read(nif); + + /*unknown*/nif->getFloat(); + mForce = nif->getFloat(); + mType = nif->getUInt(); + mPosition = nif->getVector3(); + mDirection = nif->getVector3(); + } + + void NiPlanarCollider::read(NIFStream *nif) + { + Controlled::read(nif); + + // (I think) 4 floats + 4 vectors + nif->skip(4*16); + } + + void NiParticleRotation::read(NIFStream *nif) + { + Controlled::read(nif); + + /* + byte (0 or 1) + float (1) + float*3 + */ + nif->skip(17); + } + + + + + +} diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp new file mode 100644 index 000000000..376a1fe12 --- /dev/null +++ b/components/nif/controller.cpp @@ -0,0 +1,203 @@ +#include "controller.hpp" + +#include "node.hpp" +#include "data.hpp" + +namespace Nif +{ + + void Controller::read(NIFStream *nif) + { + next.read(nif); + + flags = nif->getUShort(); + + frequency = nif->getFloat(); + phase = nif->getFloat(); + timeStart = nif->getFloat(); + timeStop = nif->getFloat(); + + target.read(nif); + } + + void Controller::post(NIFFile *nif) + { + Record::post(nif); + next.post(nif); + target.post(nif); + } + + void NiParticleSystemController::read(NIFStream *nif) + { + Controller::read(nif); + + velocity = nif->getFloat(); + velocityRandom = nif->getFloat(); + verticalDir = nif->getFloat(); + verticalAngle = nif->getFloat(); + horizontalDir = nif->getFloat(); + horizontalAngle = nif->getFloat(); + /*normal?*/ nif->getVector3(); + /*color?*/ nif->getVector4(); + size = nif->getFloat(); + startTime = nif->getFloat(); + stopTime = nif->getFloat(); + nif->getChar(); + emitRate = nif->getFloat(); + lifetime = nif->getFloat(); + lifetimeRandom = nif->getFloat(); + + emitFlags = nif->getUShort(); + offsetRandom = nif->getVector3(); + + emitter.read(nif); + + /* Unknown Short, 0? + * Unknown Float, 1.0? + * Unknown Int, 1? + * Unknown Int, 0? + * Unknown Short, 0? + */ + nif->skip(16); + + numParticles = nif->getUShort(); + activeCount = nif->getUShort(); + + particles.resize(numParticles); + for(size_t i = 0;i < particles.size();i++) + { + particles[i].velocity = nif->getVector3(); + nif->getVector3(); /* unknown */ + particles[i].lifetime = nif->getFloat(); + particles[i].lifespan = nif->getFloat(); + particles[i].timestamp = nif->getFloat(); + nif->getUShort(); /* unknown */ + particles[i].vertex = nif->getUShort(); + } + + nif->getUInt(); /* -1? */ + extra.read(nif); + nif->getUInt(); /* -1? */ + nif->getChar(); + } + + void NiParticleSystemController::post(NIFFile *nif) + { + Controller::post(nif); + emitter.post(nif); + extra.post(nif); + } + + void NiMaterialColorController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + } + + void NiMaterialColorController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiPathController::read(NIFStream *nif) + { + Controller::read(nif); + + /* + int = 1 + 2xfloat + short = 0 or 1 + */ + nif->skip(14); + posData.read(nif); + floatData.read(nif); + } + + void NiPathController::post(NIFFile *nif) + { + Controller::post(nif); + + posData.post(nif); + floatData.post(nif); + } + + void NiUVController::read(NIFStream *nif) + { + Controller::read(nif); + + nif->getUShort(); // always 0 + data.read(nif); + } + + void NiUVController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiKeyframeController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + } + + void NiKeyframeController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiAlphaController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + } + + void NiAlphaController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiGeomMorpherController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + nif->getChar(); // always 0 + } + + void NiGeomMorpherController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiVisController::read(NIFStream *nif) + { + Controller::read(nif); + data.read(nif); + } + + void NiVisController::post(NIFFile *nif) + { + Controller::post(nif); + data.post(nif); + } + + void NiFlipController::read(NIFStream *nif) + { + Controller::read(nif); + mTexSlot = nif->getUInt(); + /*unknown=*/nif->getUInt();/*0?*/ + mDelta = nif->getFloat(); + mSources.read(nif); + } + + void NiFlipController::post(NIFFile *nif) + { + Controller::post(nif); + mSources.post(nif); + } + +} diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp new file mode 100644 index 000000000..41dcb09de --- /dev/null +++ b/components/nif/effect.cpp @@ -0,0 +1,61 @@ +#include "effect.hpp" + +#include "node.hpp" + +namespace Nif +{ + +void NiLight::SLight::read(NIFStream *nif) +{ + dimmer = nif->getFloat(); + ambient = nif->getVector3(); + diffuse = nif->getVector3(); + specular = nif->getVector3(); +} + +void NiLight::read(NIFStream *nif) +{ + Effect::read(nif); + + nif->getInt(); // 1 + nif->getInt(); // 1? + light.read(nif); +} + +void NiTextureEffect::read(NIFStream *nif) +{ + Effect::read(nif); + + int tmp = nif->getInt(); + if(tmp) nif->getInt(); // always 1? + + /* + 3 x Vector4 = [1,0,0,0] + int = 2 + int = 0 or 3 + int = 2 + int = 2 + */ + nif->skip(16*4); + + texture.read(nif); + + /* + byte = 0 + vector4 = [1,0,0,0] + short = 0 + short = -75 + short = 0 + */ + nif->skip(23); +} + +void NiTextureEffect::post(NIFFile *nif) +{ + Effect::post(nif); + texture.post(nif); +} + + + +} diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp new file mode 100644 index 000000000..b7e221668 --- /dev/null +++ b/components/nif/extra.cpp @@ -0,0 +1,43 @@ +#include "extra.hpp" + +namespace Nif +{ + +void NiStringExtraData::read(NIFStream *nif) +{ + Extra::read(nif); + + nif->getInt(); // size of string + 4. Really useful... + string = nif->getString(); +} + +void NiTextKeyExtraData::read(NIFStream *nif) +{ + Extra::read(nif); + + nif->getInt(); // 0 + + int keynum = nif->getInt(); + list.resize(keynum); + for(int i=0; igetFloat(); + list[i].text = nif->getString(); + } +} + +void NiVertWeightsExtraData::read(NIFStream *nif) +{ + Extra::read(nif); + + // We should have s*4+2 == i, for some reason. Might simply be the + // size of the rest of the record, unhelpful as that may be. + /*int i =*/ nif->getInt(); + int s = nif->getUShort(); + + nif->skip(s * sizeof(float)); // vertex weights I guess +} + + + +} diff --git a/components/nif/property.cpp b/components/nif/property.cpp new file mode 100644 index 000000000..47c6d35b3 --- /dev/null +++ b/components/nif/property.cpp @@ -0,0 +1,111 @@ +#include "property.hpp" + +#include "data.hpp" +#include "controlled.hpp" + +namespace Nif +{ + +void Property::read(NIFStream *nif) +{ + Named::read(nif); + flags = nif->getUShort(); +} + +void NiTexturingProperty::Texture::read(NIFStream *nif) +{ + inUse = !!nif->getInt(); + if(!inUse) return; + + texture.read(nif); + clamp = nif->getInt(); + filter = nif->getInt(); + uvSet = nif->getInt(); + + // I have no idea, but I think these are actually two + // PS2-specific shorts (ps2L and ps2K), followed by an unknown + // short. + nif->skip(6); +} + +void NiTexturingProperty::Texture::post(NIFFile *nif) +{ + texture.post(nif); +} + +void NiTexturingProperty::read(NIFStream *nif) +{ + Property::read(nif); + apply = nif->getInt(); + + // Unknown, always 7. Probably the number of textures to read + // below + nif->getInt(); + + textures[0].read(nif); // Base + textures[1].read(nif); // Dark + textures[2].read(nif); // Detail + textures[3].read(nif); // Gloss (never present) + textures[4].read(nif); // Glow + textures[5].read(nif); // Bump map + if(textures[5].inUse) + { + // Ignore these at the moment + /*float lumaScale =*/ nif->getFloat(); + /*float lumaOffset =*/ nif->getFloat(); + /*const Vector4 *lumaMatrix =*/ nif->getVector4(); + } + textures[6].read(nif); // Decal +} + +void NiTexturingProperty::post(NIFFile *nif) +{ + Property::post(nif); + for(int i = 0;i < 7;i++) + textures[i].post(nif); +} + +void NiFogProperty::read(NIFStream *nif) +{ + Property::read(nif); + + mFogDepth = nif->getFloat(); + mColour = nif->getVector3(); +} + +void S_MaterialProperty::read(NIFStream *nif) +{ + ambient = nif->getVector3(); + diffuse = nif->getVector3(); + specular = nif->getVector3(); + emissive = nif->getVector3(); + glossiness = nif->getFloat(); + alpha = nif->getFloat(); +} + +void S_VertexColorProperty::read(NIFStream *nif) +{ + vertmode = nif->getInt(); + lightmode = nif->getInt(); +} + +void S_AlphaProperty::read(NIFStream *nif) +{ + threshold = nif->getChar(); +} + +void S_StencilProperty::read(NIFStream *nif) +{ + enabled = nif->getChar(); + compareFunc = nif->getInt(); + stencilRef = nif->getUInt(); + stencilMask = nif->getUInt(); + failAction = nif->getInt(); + zFailAction = nif->getInt(); + zPassAction = nif->getInt(); + drawMode = nif->getInt(); +} + + + +} diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp new file mode 100644 index 000000000..4e600c3a1 --- /dev/null +++ b/components/nifosg/controller.cpp @@ -0,0 +1,275 @@ +#include "controller.hpp" + +#include +#include +#include + +#include + +namespace NifOsg +{ + +float ValueInterpolator::interpKey(const Nif::FloatKeyMap::MapType &keys, float time, float def) const +{ + if (keys.size() == 0) + return def; + + if(time <= keys.begin()->first) + return keys.begin()->second.mValue; + + Nif::FloatKeyMap::MapType::const_iterator it = keys.lower_bound(time); + if (it != keys.end()) + { + float aTime = it->first; + const Nif::FloatKey* aKey = &it->second; + + assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function + + Nif::FloatKeyMap::MapType::const_iterator last = --it; + float aLastTime = last->first; + const Nif::FloatKey* aLastKey = &last->second; + + float a = (time - aLastTime) / (aTime - aLastTime); + return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); + } + else + return keys.rbegin()->second.mValue; +} + +osg::Vec3f ValueInterpolator::interpKey(const Nif::Vector3KeyMap::MapType &keys, float time) const +{ + if(time <= keys.begin()->first) + return keys.begin()->second.mValue; + + Nif::Vector3KeyMap::MapType::const_iterator it = keys.lower_bound(time); + if (it != keys.end()) + { + float aTime = it->first; + const Nif::KeyT* aKey = &it->second; + + assert (it != keys.begin()); // Shouldn't happen, was checked at beginning of this function + + Nif::Vector3KeyMap::MapType::const_iterator last = --it; + float aLastTime = last->first; + const Nif::KeyT* aLastKey = &last->second; + + float a = (time - aLastTime) / (aTime - aLastTime); + return aLastKey->mValue + ((aKey->mValue - aLastKey->mValue) * a); + } + else + return keys.rbegin()->second.mValue; +} + +ControllerFunction::ControllerFunction(const Nif::Controller *ctrl, bool deltaInput) + : mDeltaInput(deltaInput) + , mFrequency(ctrl->frequency) + , mPhase(ctrl->phase) + , mStartTime(ctrl->timeStart) + , mStopTime(ctrl->timeStop) + , mDeltaCount(0.f) +{ + if(mDeltaInput) + mDeltaCount = mPhase; +} + +float ControllerFunction::calculate(float value) +{ + if(mDeltaInput) + { + if (mStopTime - mStartTime == 0.f) + return 0.f; + + mDeltaCount += value*mFrequency; + if(mDeltaCount < mStartTime) + mDeltaCount = mStopTime - std::fmod(mStartTime - mDeltaCount, + mStopTime - mStartTime); + mDeltaCount = std::fmod(mDeltaCount - mStartTime, + mStopTime - mStartTime) + mStartTime; + return mDeltaCount; + } + + value = std::min(mStopTime, std::max(mStartTime, value+mPhase)); + return value; +} + +osg::Quat KeyframeController::Value::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); + + osg::Quat v1 = aLastKey->mValue; + osg::Quat v2 = aKey->mValue; + // don't take the long path + if (v1.x()*v2.x() + v1.y()*v2.y() + v1.z()*v2.z() + v1.w()*v2.w() < 0) // dotProduct(v1,v2) + v1 = -v1; + + osg::Quat result; + result.slerp(a, v1, v2); + return result; + } + else + return keys.rbegin()->second.mValue; +} + +osg::Quat KeyframeController::Value::getXYZRotation(float time) const +{ + float xrot = interpKey(mXRotations->mKeys, time); + float yrot = interpKey(mYRotations->mKeys, time); + float zrot = interpKey(mZRotations->mKeys, time); + osg::Quat xr(xrot, osg::Vec3f(1,0,0)); + osg::Quat yr(yrot, osg::Vec3f(0,1,0)); + osg::Quat zr(zrot, osg::Vec3f(0,0,1)); + return (zr*yr*xr); +} + +KeyframeController::Value::Value(osg::Node *target, const Nif::NIFFilePtr &nif, const Nif::NiKeyframeData *data, + osg::Quat initialQuat, float initialScale) + : NodeTargetValue(target) + , mRotations(&data->mRotations) + , mXRotations(&data->mXRotations) + , mYRotations(&data->mYRotations) + , mZRotations(&data->mZRotations) + , mTranslations(&data->mTranslations) + , mScales(&data->mScales) + , mNif(nif) + , mInitialQuat(initialQuat) + , mInitialScale(initialScale) +{ } + +osg::Vec3f KeyframeController::Value::getTranslation(float time) const +{ + if(mTranslations->mKeys.size() > 0) + return interpKey(mTranslations->mKeys, time); + osg::MatrixTransform* trans = static_cast(mNode); + return trans->getMatrix().getTrans(); +} + +void KeyframeController::Value::setValue(float time) +{ + osg::MatrixTransform* trans = static_cast(mNode); + osg::Matrix mat = trans->getMatrix(); + + if(mRotations->mKeys.size() > 0) + mat.setRotate(interpKey(mRotations->mKeys, time)); + else if (!mXRotations->mKeys.empty() || !mYRotations->mKeys.empty() || !mZRotations->mKeys.empty()) + mat.setRotate(getXYZRotation(time)); + else + mat.setRotate(mInitialQuat); + + // let's hope no one's using multiple KeyframeControllers on the same node (not that would make any sense...) + float scale = mInitialScale; + if(mScales->mKeys.size() > 0) + scale = interpKey(mScales->mKeys, time); + + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + mat(i,j) *= scale; + + if(mTranslations->mKeys.size() > 0) + mat.setTrans(interpKey(mTranslations->mKeys, time)); + trans->setMatrix(mat); +} + +Controller::Controller(boost::shared_ptr src, boost::shared_ptr dest, boost::shared_ptr function) + : mSource(src) + , mDestValue(dest) + , mFunction(function) +{ + +} + +void Controller::update() +{ + if (mSource.get()) + { + mDestValue->setValue(mFunction->calculate(mSource->getValue())); + } +} + +GeomMorpherController::Value::Value(osgAnimation::MorphGeometry *geom, const Nif::NiMorphData* morphData) + : mGeom(geom) + , mMorphs(morphData->mMorphs) +{ + +} + +void GeomMorpherController::Value::setValue(float time) +{ + if (mMorphs.size() <= 1) + return; + int i = 0; + for (std::vector::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)); + + mGeom->setWeight(i, val); + } +} + +UVController::Value::Value(osg::StateSet *target, const Nif::NiUVData *data, std::set textureUnits) + : mStateSet(target) + , mUTrans(data->mKeyList[0]) + , mVTrans(data->mKeyList[1]) + , mUScale(data->mKeyList[2]) + , mVScale(data->mKeyList[3]) + , mTextureUnits(textureUnits) +{ +} + +void UVController::Value::setValue(float 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); + + osg::Matrixf mat = osg::Matrixf::scale(uScale, vScale, 1); + mat.setTrans(uTrans, vTrans, 0); + + osg::TexMat* texMat = new osg::TexMat; + texMat->setMatrix(mat); + + for (std::set::const_iterator it = mTextureUnits.begin(); it != mTextureUnits.end(); ++it) + { + mStateSet->setTextureAttributeAndModes(*it, texMat, osg::StateAttribute::ON); + } +} + +bool VisController::Value::calculate(float 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; +} + +void VisController::Value::setValue(float time) +{ + bool vis = calculate(time); + mNode->setNodeMask(vis ? ~0 : 0); +} + + +} diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp new file mode 100644 index 000000000..e45bc7fea --- /dev/null +++ b/components/nifosg/controller.hpp @@ -0,0 +1,214 @@ +#ifndef COMPONENTS_NIFOSG_CONTROLLER_H +#define COMPONENTS_NIFOSG_CONTROLLER_H + +#include +#include +#include +#include + +#include + +#include + +#include //UVController + +#include + + +namespace osg +{ + class Node; + class StateSet; +} + +namespace osgAnimation +{ + class MorphGeometry; +} + +namespace NifOsg +{ + + // FIXME: Should not be here. We might also want to use this for non-NIF model formats + class ValueInterpolator + { + protected: + float interpKey(const Nif::FloatKeyMap::MapType &keys, float time, float def=0.f) const; + + osg::Vec3f interpKey(const Nif::Vector3KeyMap::MapType &keys, float time) const; + }; + + // FIXME: Should not be here. We might also want to use this for non-NIF model formats + class ControllerFunction + { + private: + float mFrequency; + float mPhase; + float mStartTime; + bool mDeltaInput; + float mDeltaCount; + public: + float mStopTime; + + public: + ControllerFunction(const Nif::Controller *ctrl, bool deltaInput); + + float calculate(float value); + }; + typedef ControllerFunction DefaultFunction; + + class ControllerSource + { + public: + virtual float getValue() const = 0; + }; + + // FIXME: Should return a dt instead of time + class FrameTimeSource : public ControllerSource + { + public: + + virtual float getValue() const + { + return mTimer.time_s(); + } + + private: + osg::Timer mTimer; + }; + + class ControllerValue + { + public: + virtual void setValue(float value) = 0; + }; + + class Controller + { + public: + Controller (boost::shared_ptr src, boost::shared_ptr dest, + boost::shared_ptr function); + + virtual void update(); + + boost::shared_ptr mSource; + boost::shared_ptr mDestValue; + + // The source value gets passed through this function before it's passed on to the DestValue. + boost::shared_ptr mFunction; + }; + + // FIXME: Should be with other general extensions. + class NodeTargetValue : public ControllerValue + { + protected: + osg::Node *mNode; + + public: + NodeTargetValue(osg::Node *target) : mNode(target) + { } + + virtual osg::Vec3f getTranslation(float value) const = 0; + + osg::Node *getNode() const + { return mNode; } + }; + + class GeomMorpherController + { + public: + class Value : public ControllerValue, public ValueInterpolator + { + public: + // FIXME: don't copy the morph data? + Value(osgAnimation::MorphGeometry* geom, const Nif::NiMorphData *data); + + virtual void setValue(float time); + + private: + osgAnimation::MorphGeometry* mGeom; + std::vector mMorphs; + }; + }; + + class KeyframeController + { + public: + class Value : public NodeTargetValue, 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 + + osg::Quat mInitialQuat; + float mInitialScale; + + using ValueInterpolator::interpKey; + + + osg::Quat interpKey(const Nif::QuaternionKeyMap::MapType &keys, float time); + + osg::Quat getXYZRotation(float time) const; + + public: + /// @note The NiKeyFrameData must be valid as long as this KeyframeController exists. + Value(osg::Node *target, const Nif::NIFFilePtr& nif, const Nif::NiKeyframeData *data, + osg::Quat initialQuat, float initialScale); + + virtual osg::Vec3f getTranslation(float time) const; + + virtual void setValue(float time); + }; + }; + + class UVController + { + public: + class Value : public ControllerValue, ValueInterpolator + { + private: + osg::StateSet* mStateSet; + Nif::FloatKeyMap mUTrans; + Nif::FloatKeyMap mVTrans; + Nif::FloatKeyMap mUScale; + Nif::FloatKeyMap mVScale; + std::set mTextureUnits; + + public: + Value(osg::StateSet* target, const Nif::NiUVData *data, std::set textureUnits); + + virtual void setValue(float value); + }; + }; + + class VisController + { + public: + class Value : public NodeTargetValue + { + private: + std::vector mData; + + bool calculate(float time) const; + + public: + Value(osg::Node *target, const Nif::NiVisData *data) + : NodeTargetValue(target) + , mData(data->mVis) + { } + + virtual osg::Vec3f getTranslation(float time) const + { return osg::Vec3f(); } + + virtual void setValue(float time); + }; + }; + +} + +#endif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp new file mode 100644 index 000000000..2eb57a4f8 --- /dev/null +++ b/components/nifosg/nifloader.cpp @@ -0,0 +1,759 @@ +#include "nifloader.hpp" + +#include +#include +#include +#include +#include + +// resource +#include +#include +#include +#include + +// skel +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ + osg::Matrixf toMatrix(const Nif::Transformation& nifTrafo) + { + osg::Matrixf transform; + transform.setTrans(nifTrafo.pos); + + for (int i=0;i<3;++i) + for (int j=0;j<3;++j) + transform(j,i) = nifTrafo.rotation.mValues[i][j] * nifTrafo.scale; // NB column/row major difference + + return transform; + } + + osg::Matrixf getWorldTransform(const Nif::Node* node) + { + if(node->parent != NULL) + return toMatrix(node->trafo) * getWorldTransform(node->parent); + return toMatrix(node->trafo); + } + + osg::BlendFunc::BlendFuncMode getBlendMode(int mode) + { + switch(mode) + { + case 0: return osg::BlendFunc::ONE; + case 1: return osg::BlendFunc::ZERO; + case 2: return osg::BlendFunc::SRC_COLOR; + case 3: return osg::BlendFunc::ONE_MINUS_SRC_COLOR; + case 4: return osg::BlendFunc::DST_COLOR; + case 5: return osg::BlendFunc::ONE_MINUS_DST_COLOR; + case 6: return osg::BlendFunc::SRC_ALPHA; + case 7: return osg::BlendFunc::ONE_MINUS_SRC_ALPHA; + case 8: return osg::BlendFunc::DST_ALPHA; + case 9: return osg::BlendFunc::ONE_MINUS_DST_ALPHA; + case 10: return osg::BlendFunc::SRC_ALPHA_SATURATE; + default: + std::cerr<< "Unexpected blend mode: "<< mode << std::endl; + return osg::BlendFunc::SRC_ALPHA; + } + } + + osg::AlphaFunc::ComparisonFunction getTestMode(int mode) + { + switch (mode) + { + case 0: return osg::AlphaFunc::ALWAYS; + case 1: return osg::AlphaFunc::LESS; + case 2: return osg::AlphaFunc::EQUAL; + case 3: return osg::AlphaFunc::LEQUAL; + case 4: return osg::AlphaFunc::GREATER; + case 5: return osg::AlphaFunc::NOTEQUAL; + case 6: return osg::AlphaFunc::GEQUAL; + case 7: return osg::AlphaFunc::NEVER; + default: + std::cerr << "Unexpected blend mode: " << mode << std::endl; + return osg::AlphaFunc::LEQUAL; + } + } + + // Collect all properties affecting the given node that should be applied to an osg::Material. + void collectMaterialProperties(const Nif::Node* nifNode, std::vector& out) + { + const Nif::PropertyList& props = nifNode->props; + for (size_t i = 0; i recType) + { + case Nif::RC_NiMaterialProperty: + case Nif::RC_NiVertexColorProperty: + case Nif::RC_NiSpecularProperty: + out.push_back(props[i].getPtr()); + break; + default: + break; + } + } + } + if (nifNode->parent) + collectMaterialProperties(nifNode->parent, out); + } + + void updateMaterialProperties(osg::StateSet* stateset, const std::vector& properties) + { + int specFlags = 0; // Specular is disabled by default, even if there's a specular color in the NiMaterialProperty + osg::Material* mat = new osg::Material; + // FIXME: color mode should be disabled if the TriShape has no vertex colors + mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + for (std::vector::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it) + { + const Nif::Property* property = *it; + switch (property->recType) + { + case Nif::RC_NiSpecularProperty: + { + specFlags = property->flags; + break; + } + case Nif::RC_NiMaterialProperty: + { + const Nif::NiMaterialProperty* matprop = static_cast(property); + + mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.diffuse, matprop->data.alpha)); + mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.ambient, 1.f)); + mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.emissive, 1.f)); + + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.specular, 1.f)); + mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->data.glossiness); + + break; + } + case Nif::RC_NiVertexColorProperty: + { + const Nif::NiVertexColorProperty* vertprop = static_cast(property); + switch (vertprop->flags) + { + case 0: + mat->setColorMode(osg::Material::OFF); + break; + case 1: + mat->setColorMode(osg::Material::EMISSION); + break; + case 2: + mat->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE); + break; + } + } + } + } + + if (specFlags == 0) + mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f,0.f,0.f,0.f)); + + stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); + } + + // NodeCallback used to update the bone matrices in skeleton space as needed for skinning. + class UpdateBone : public osg::NodeCallback + { + public: + // Callback method called by the NodeVisitor when visiting a node. + void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) + { + osgAnimation::Bone* b = dynamic_cast(node); + if (!b) + { + OSG_WARN << "Warning: UpdateBone set on non-Bone object." << std::endl; + return; + } + + osgAnimation::Bone* parent = b->getBoneParent(); + if (parent) + b->setMatrixInSkeletonSpace(b->getMatrixInBoneSpace() * parent->getMatrixInSkeletonSpace()); + else + b->setMatrixInSkeletonSpace(b->getMatrixInBoneSpace()); + } + traverse(node,nv); + } + }; + + // NodeCallback used to set the inverse of the parent bone's matrix in skeleton space + // on the MatrixTransform that the NodeCallback is attached to. This is used so we can + // attach skinned meshes to their actual parent node, while still having the skinning + // work in skeleton space as expected. + class InvertBoneMatrix : public osg::NodeCallback + { + public: + InvertBoneMatrix(osg::Node* skelRootNode) + : mSkelRoot(skelRootNode) + { + } + + void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) + { + osg::NodePath path = nv->getNodePath(); + path.pop_back(); + + osg::MatrixTransform* trans = dynamic_cast(node); + + osg::NodePath::iterator found = std::find(path.begin(), path.end(), mSkelRoot); + if (found != path.end()) + { + path.erase(path.begin(),found+1); + + osg::Matrix worldMat = osg::computeLocalToWorld( path ); + trans->setMatrix(osg::Matrix::inverse(worldMat)); + } + } + traverse(node,nv); + } + private: + osg::Node* mSkelRoot; + }; + + osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher) + { + osg::ref_ptr morphGeom = new osgAnimation::MorphGeometry; + morphGeom->setMethod(osgAnimation::MorphGeometry::RELATIVE); + // NIF format doesn't specify morphed normals + morphGeom->setMorphNormals(false); + + const std::vector& morphs = morpher->data.getPtr()->mMorphs; + // Note we are not interested in morph 0, which just contains the original vertices + for (unsigned int i = 1; i < morphs.size(); ++i) + { + osg::ref_ptr morphTarget = new osg::Geometry; + morphTarget->setVertexArray(new osg::Vec3Array(morphs[i].mVertices.size(), &morphs[i].mVertices[0])); + morphGeom->addMorphTarget(morphTarget, 0.f); + } + return morphGeom; + } +} + +namespace NifOsg +{ + + void Loader::load(Nif::NIFFilePtr nif, osg::Group *parentNode) + { + mNif = nif; + + if (nif->numRoots() < 1) + { + nif->warn("Found no root nodes"); + return; + } + + const Nif::Record* r = nif->getRoot(0); + assert(r != NULL); + + const Nif::Node* nifNode = dynamic_cast(r); + if (nifNode == NULL) + { + nif->warn("First root was not a node, but a " + r->recName); + return; + } + + mRootNode = parentNode; + handleNode(nifNode, parentNode, false, std::map()); + } + + void Loader::loadAsSkeleton(Nif::NIFFilePtr nif, osg::Group *parentNode) + { + mNif = nif; + + if (nif->numRoots() < 1) + { + nif->warn("Found no root nodes"); + return; + } + + const Nif::Record* r = nif->getRoot(0); + assert(r != NULL); + + const Nif::Node* nifNode = dynamic_cast(r); + if (nifNode == NULL) + { + nif->warn("First root was not a node, but a " + r->recName); + return; + } + + mRootNode = parentNode; + + osgAnimation::Skeleton* skel = new osgAnimation::Skeleton; + mSkeleton = skel; + mRootNode->addChild(mSkeleton); + + handleNode(nifNode, mSkeleton, true, std::map()); + } + + void Loader::applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, std::map& boundTextures) + { + const Nif::PropertyList& props = nifNode->props; + for (size_t i = 0; i value, int animflags) + { + // FIXME animflags currently not passed to this function + //bool autoPlay = animflags & Nif::NiNode::AnimFlag_AutoPlay; + boost::shared_ptr src(new FrameTimeSource); // if autoPlay + + boost::shared_ptr function (new ControllerFunction(ctrl + , 0/*autoPlay*/)); + //scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + + mControllers.push_back(Controller(src, value, function)); + } + + void Loader::handleNode(const Nif::Node* nifNode, osg::Group* parentNode, bool createSkeleton, + std::map boundTextures) + { + osg::ref_ptr transformNode; + if (createSkeleton) + { + osgAnimation::Bone* bone = new osgAnimation::Bone; + transformNode = bone; + bone->setMatrix(toMatrix(nifNode->trafo)); + bone->setName(nifNode->name); + bone->setUpdateCallback(new UpdateBone); + bone->setInvBindMatrixInSkeletonSpace(osg::Matrixf::inverse(getWorldTransform(nifNode))); + } + else + { + transformNode = new osg::MatrixTransform; + transformNode->setMatrix(toMatrix(nifNode->trafo)); + } + + // Hide collision shapes, but don't skip the subgraph + // We still need to animate the hidden bones so the physics system can access them + // FIXME: skip creation of the TriShapes + if (nifNode->recType == Nif::RC_RootCollisionNode) + transformNode->setNodeMask(0); + + // We could probably skip hidden nodes entirely if they don't have a VisController that + // might make them visible later + if (nifNode->flags & Nif::NiNode::Flag_Hidden) + transformNode->setNodeMask(0); + + // Insert bones at position 0 to prevent update order problems (see comment in osg Skeleton.cpp) + parentNode->insertChild(0, transformNode); + + applyNodeProperties(nifNode, transformNode, boundTextures); + + if (nifNode->recType == Nif::RC_NiTriShape) + { + const Nif::NiTriShape* triShape = static_cast(nifNode); + if (!createSkeleton || triShape->skin.empty()) + handleTriShape(triShape, transformNode, boundTextures); + else + handleSkinnedTriShape(triShape, transformNode, boundTextures); + + if (!nifNode->controller.empty()) + handleMeshControllers(nifNode, transformNode, boundTextures); + } + + if (!nifNode->controller.empty()) + handleNodeControllers(nifNode, transformNode); + + const Nif::NiNode *ninode = dynamic_cast(nifNode); + if(ninode) + { + const Nif::NodeList &children = ninode->children; + for(size_t i = 0;i < children.length();++i) + { + if(!children[i].empty()) + handleNode(children[i].getPtr(), transformNode, createSkeleton, boundTextures); + } + } + } + + void Loader::handleMeshControllers(const Nif::Node *nifNode, osg::MatrixTransform *transformNode, const std::map &boundTextures) + { + for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + { + if (ctrl->recType == Nif::RC_NiUVController) + { + const Nif::NiUVController *uvctrl = static_cast(ctrl.getPtr()); + std::set texUnits; + for (std::map::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it) + texUnits.insert(it->first); + boost::shared_ptr dest(new UVController::Value(transformNode->getOrCreateStateSet() + , uvctrl->data.getPtr(), texUnits)); + createController(uvctrl, dest, 0); + } + } + } + + void Loader::handleNodeControllers(const Nif::Node* nifNode, osg::MatrixTransform* transformNode) + { + bool seenKeyframeCtrl = false; + for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) + { + if (ctrl->recType == Nif::RC_NiKeyframeController) + { + const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); + if(!key->data.empty()) + { + if (seenKeyframeCtrl) + { + std::cerr << "Warning: multiple KeyframeControllers on the same node" << std::endl; + continue; + } + boost::shared_ptr dest(new KeyframeController::Value(transformNode, mNif, key->data.getPtr(), + transformNode->getMatrix().getRotate(), nifNode->trafo.scale)); + + createController(key, dest, 0); + seenKeyframeCtrl = true; + } + } + else if (ctrl->recType == Nif::RC_NiVisController) + { + const Nif::NiVisController* visctrl = static_cast(ctrl.getPtr()); + boost::shared_ptr dest(new VisController::Value(transformNode, visctrl->data.getPtr())); + createController(visctrl, dest, 0); + } + } + } + + void Loader::triShapeToGeometry(const Nif::NiTriShape *triShape, osg::Geometry *geometry, const std::map& boundTextures) + { + const Nif::NiTriShapeData* data = triShape->data.getPtr(); + + const Nif::NiSkinInstance *skin = (triShape->skin.empty() ? NULL : triShape->skin.getPtr()); + if (skin) + { + // Convert vertices and normals to bone space from bind position. It would be + // better to transform the bones into bind position, but there doesn't seem to + // be a reliable way to do that. + osg::ref_ptr newVerts (new osg::Vec3Array(data->vertices.size())); + osg::ref_ptr newNormals (new osg::Vec3Array(data->normals.size())); + + const Nif::NiSkinData *skinData = skin->data.getPtr(); + const Nif::NodeList &bones = skin->bones; + for(size_t b = 0;b < bones.length();b++) + { + osg::Matrixf mat = toMatrix(skinData->bones[b].trafo); + + mat = mat * getWorldTransform(bones[b].getPtr()); + + const std::vector &weights = skinData->bones[b].weights; + for(size_t i = 0;i < weights.size();i++) + { + size_t index = weights[i].vertex; + float weight = weights[i].weight; + + osg::Vec4f mult = (osg::Vec4f(data->vertices.at(index),1.f) * mat) * weight; + (*newVerts)[index] += osg::Vec3f(mult.x(),mult.y(),mult.z()); + if(newNormals->size() > index) + { + osg::Vec4 normal(data->normals[index].x(), data->normals[index].y(), data->normals[index].z(), 0.f); + normal = (normal * mat) * weight; + (*newNormals)[index] += osg::Vec3f(normal.x(),normal.y(),normal.z()); + } + } + } + // Interpolating normalized normals doesn't necessarily give you a normalized result + // Currently we're using GL_NORMALIZE, so this isn't needed + //for (unsigned int i=0;isize();++i) + // (*newNormals)[i].normalize(); + + geometry->setVertexArray(newVerts); + if (!data->normals.empty()) + geometry->setNormalArray(newNormals, osg::Array::BIND_PER_VERTEX); + } + else + { + geometry->setVertexArray(new osg::Vec3Array(data->vertices.size(), &data->vertices[0])); + if (!data->normals.empty()) + geometry->setNormalArray(new osg::Vec3Array(data->normals.size(), &data->normals[0]), osg::Array::BIND_PER_VERTEX); + } + + for (std::map::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it) + { + int textureStage = it->first; + int uvSet = it->second; + if (uvSet >= (int)data->uvlist.size()) + { + // Occurred in "ascendedsleeper.nif", but only for hidden Shadow nodes, apparently + //std::cerr << "Warning: using an undefined UV set " << uvSet << " on TriShape " << triShape->name << std::endl; + continue; + } + + geometry->setTexCoordArray(textureStage, new osg::Vec2Array(data->uvlist[uvSet].size(), &data->uvlist[uvSet][0]), osg::Array::BIND_PER_VERTEX); + } + + // FIXME: material ColorMode should be disabled if the TriShape has no vertex colors + if (!data->colors.empty()) + geometry->setColorArray(new osg::Vec4Array(data->colors.size(), &data->colors[0]), osg::Array::BIND_PER_VERTEX); + + geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, + data->triangles.size(), + (unsigned short*)&data->triangles[0])); + } + + void Loader::handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, const std::map& boundTextures) + { + osg::ref_ptr geometry; + if(!triShape->controller.empty()) + { + Nif::ControllerPtr ctrl = triShape->controller; + do { + if(ctrl->recType == Nif::RC_NiGeomMorpherController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) + { + geometry = handleMorphGeometry(static_cast(ctrl.getPtr())); + boost::shared_ptr value( + new GeomMorpherController::Value(static_cast(geometry.get()), + static_cast(ctrl.getPtr())->data.getPtr())); + createController(ctrl.getPtr(), value, 0); + break; + } + } while(!(ctrl=ctrl->next).empty()); + } + + if (!geometry.get()) + geometry = new osg::Geometry; + triShapeToGeometry(triShape, geometry.get(), boundTextures); + + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(geometry.get()); + + parentNode->addChild(geode.get()); + } + + void Loader::handleSkinnedTriShape(const Nif::NiTriShape *triShape, osg::Group *parentNode, const std::map& boundTextures) + { + osg::ref_ptr geometry (new osg::Geometry); + triShapeToGeometry(triShape, geometry.get(), boundTextures); + + osg::ref_ptr rig(new osgAnimation::RigGeometry); + rig->setSourceGeometry(geometry); + // Slightly expand the bounding box to account for movement of the bones + // For more accuracy the skinning should be relative to the parent of the first skinned bone, + // rather than the root bone. + osg::BoundingBox box = geometry->getBound(); + box.expandBy(box._min-(box._max-box._min)/2); + box.expandBy(box._max+(box._max-box._min)/2); + rig->setInitialBound(box); + + const Nif::NiSkinInstance *skin = triShape->skin.getPtr(); + + // Assign bone weights + osg::ref_ptr map (new osgAnimation::VertexInfluenceMap); + + const Nif::NiSkinData *data = skin->data.getPtr(); + const Nif::NodeList &bones = skin->bones; + for(size_t i = 0;i < bones.length();i++) + { + std::string boneName = bones[i].getPtr()->name; + + osgAnimation::VertexInfluence influence; + influence.setName(boneName); + const std::vector &weights = data->bones[i].weights; + influence.reserve(weights.size()); + for(size_t j = 0;j < weights.size();j++) + { + osgAnimation::VertexIndexWeight indexWeight = std::make_pair(weights[j].vertex, weights[j].weight); + influence.push_back(indexWeight); + } + + map->insert(std::make_pair(boneName, influence)); + } + rig->setInfluenceMap(map.get()); + + osg::ref_ptr trans(new osg::MatrixTransform); + trans->setUpdateCallback(new InvertBoneMatrix(mSkeleton)); + + osg::ref_ptr geode (new osg::Geode); + geode->addDrawable(rig.get()); + + trans->addChild(geode.get()); + parentNode->addChild(trans.get()); + } + + void Loader::handleProperty(const Nif::Property *property, const Nif::Node* nifNode, + osg::Node *node, std::map& boundTextures) + { + osg::StateSet* stateset = node->getOrCreateStateSet(); + + switch (property->recType) + { + case Nif::RC_NiStencilProperty: + { + const Nif::NiStencilProperty* stencilprop = static_cast(property); + osg::FrontFace* frontFace = new osg::FrontFace; + switch (stencilprop->data.drawMode) + { + case 1: + frontFace->setMode(osg::FrontFace::CLOCKWISE); + break; + case 0: + case 2: + default: + frontFace->setMode(osg::FrontFace::COUNTER_CLOCKWISE); + break; + } + + stateset->setAttribute(frontFace, osg::StateAttribute::ON); + stateset->setMode(GL_CULL_FACE, stencilprop->data.drawMode == 3 ? osg::StateAttribute::OFF + : osg::StateAttribute::ON); + + // Stencil settings not enabled yet, not sure if the original engine is actually using them, + // since they might conflict with Morrowind's stencil shadows. + /* + osg::Stencil* stencil = new osg::Stencil; + stencil->setFunction(func, stencilprop->data.stencilRef, stencilprop->data.stencilMask); + + stateset->setMode(GL_STENCIL_TEST, stencilprop->data.enabled != 0 ? osg::StateAttribute::ON + : osg::StateAttribute::OFF); + */ + } + case Nif::RC_NiWireframeProperty: + { + const Nif::NiWireframeProperty* wireprop = static_cast(property); + osg::PolygonMode* mode = new osg::PolygonMode; + mode->setMode(osg::PolygonMode::FRONT_AND_BACK, wireprop->flags == 0 ? osg::PolygonMode::FILL + : osg::PolygonMode::LINE); + stateset->setAttributeAndModes(mode, osg::StateAttribute::ON); + break; + } + case Nif::RC_NiZBufferProperty: + { + const Nif::NiZBufferProperty* zprop = static_cast(property); + // VER_MW doesn't support a DepthFunction according to NifSkope + osg::Depth* depth = new osg::Depth; + depth->setWriteMask((zprop->flags>>1)&1); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + break; + } + // OSG groups the material properties that NIFs have separate, so we have to parse them all again when one changed + case Nif::RC_NiMaterialProperty: + case Nif::RC_NiVertexColorProperty: + case Nif::RC_NiSpecularProperty: + { + // TODO: handle these in handleTriShape so we know whether vertex colors are available + std::vector materialProps; + collectMaterialProperties(nifNode, materialProps); + updateMaterialProperties(stateset, materialProps); + break; + } + case Nif::RC_NiAlphaProperty: + { + const Nif::NiAlphaProperty* alphaprop = static_cast(property); + osg::BlendFunc* blendfunc = new osg::BlendFunc; + if (alphaprop->flags&1) + { + blendfunc->setFunction(getBlendMode((alphaprop->flags>>1)&0xf), + getBlendMode((alphaprop->flags>>5)&0xf)); + stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON); + + bool noSort = (alphaprop->flags>>13)&1; + if (!noSort) + { + stateset->setNestRenderBins(false); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + } + } + else + { + stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::OFF); + stateset->setNestRenderBins(false); + stateset->setRenderingHint(osg::StateSet::OPAQUE_BIN); + } + + osg::AlphaFunc* alphafunc = new osg::AlphaFunc; + if((alphaprop->flags>>9)&1) + { + alphafunc->setFunction(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f); + stateset->setAttributeAndModes(alphafunc, osg::StateAttribute::ON); + } + else + stateset->setAttributeAndModes(alphafunc, osg::StateAttribute::OFF); + break; + } + case Nif::RC_NiTexturingProperty: + { + const Nif::NiTexturingProperty* texprop = static_cast(property); + for (int i=0; itextures[i].inUse) + { + const Nif::NiTexturingProperty::Texture& tex = texprop->textures[i]; + if(tex.texture.empty()) + { + std::cerr << "Warning: texture layer " << i << " is in use but empty " << std::endl; + continue; + } + const Nif::NiSourceTexture *st = tex.texture.getPtr(); + std::string filename (st->filename); + Misc::StringUtils::toLower(filename); + filename = "textures\\" + filename; + size_t found = filename.find(".tga"); + if (found == std::string::npos) + found = filename.find(".bmp"); + if (found != std::string::npos) + filename.replace(found, 4, ".dds"); + + // tx_creature_werewolf.dds isn't loading in the correct format without this option + osgDB::Options* opts = new osgDB::Options; + opts->setOptionString("dds_dxt1_detect_rgba"); + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension("dds"); + osgDB::ReaderWriter::ReadResult result = reader->readImage(*resourceManager->getFile(filename.c_str()), opts); + osg::Image* image = result.getImage(); + osg::Texture2D* texture2d = new osg::Texture2D; + texture2d->setImage(image); + + unsigned int clamp = static_cast(tex.clamp); + int wrapT = (clamp) & 0x1; + int wrapS = (clamp >> 1) & 0x1; + + texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP); + texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP); + + stateset->setTextureAttributeAndModes(i, texture2d, osg::StateAttribute::ON); + + boundTextures[i] = tex.uvSet; + } + else if (boundTextures.find(i) != boundTextures.end()) + { + stateset->setTextureAttributeAndModes(i, new osg::Texture2D, osg::StateAttribute::OFF); + boundTextures.erase(i); + } + } + break; + } + case Nif::RC_NiDitherProperty: + { + stateset->setMode(GL_DITHER, property->flags != 0 ? osg::StateAttribute::ON + : osg::StateAttribute::OFF); + break; + } + default: + std::cerr << "Unhandled " << property->recName << std::endl; + break; + } + } +} diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp new file mode 100644 index 000000000..a089f7651 --- /dev/null +++ b/components/nifosg/nifloader.hpp @@ -0,0 +1,84 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_LOADER +#define OPENMW_COMPONENTS_NIFOSG_LOADER + +#include + +#include // NIFFilePtr + +#include + +#include "controller.hpp" + +namespace osg +{ + class Geometry; +} +namespace osgAnimation +{ + class Bone; +} + +namespace Nif +{ + class Node; + class NiTriShape; + class Property; +} +namespace Bsa +{ + class BSAFile; +} + +namespace NifOsg +{ + + /// The main class responsible for loading NIF files into an OSG-Scenegraph. + class Loader + { + public: + /// @param node The parent of the root node for the created NIF file. + void load(Nif::NIFFilePtr file, osg::Group* parentNode); + + void loadAsSkeleton(Nif::NIFFilePtr file, osg::Group* parentNode); + + // FIXME replace with resource system + Bsa::BSAFile* resourceManager; + + // FIXME move + std::vector mControllers; + + private: + + /// @param createSkeleton If true, use an osgAnimation::Bone for NIF nodes, otherwise an osg::MatrixTransform. + void handleNode(const Nif::Node* nifNode, osg::Group* parentNode, bool createSkeleton, std::map boundTextures); + + void handleMeshControllers(const Nif::Node* nifNode, osg::MatrixTransform* transformNode, const std::map& boundTextures); + + void handleNodeControllers(const Nif::Node* nifNode, osg::MatrixTransform* transformNode); + + void handleProperty (const Nif::Property* property, const Nif::Node* nifNode, + osg::Node* node, std::map& boundTextures); + + // Creates an osg::Geometry object for the given TriShape, populates it, and attaches it to the given node. + void handleTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, const std::map& boundTextures); + + // Fills the vertex data for the given TriShape into the given Geometry. + void triShapeToGeometry(const Nif::NiTriShape* triShape, osg::Geometry* geom, const std::map& boundTextures); + + // Creates a skinned osg::Geometry object for the given TriShape, populates it, and attaches it to the given node. + void handleSkinnedTriShape(const Nif::NiTriShape* triShape, osg::Group* parentNode, const std::map& boundTextures); + + // Applies the Properties of the given nifNode onto the StateSet of the given OSG node. + void applyNodeProperties(const Nif::Node* nifNode, osg::Node* applyTo, std::map& boundTextures); + + void createController(const Nif::Controller* ctrl, boost::shared_ptr value, int animflags); + + Nif::NIFFilePtr mNif; + + osg::Group* mRootNode; + osg::Group* mSkeleton; + }; + +} + +#endif