Add OSG nifloader, currently supports geometry, materials, basic texturing, skinning, morphing, and most controllers.
parent
6b36e55a4e
commit
8e01d8cb19
@ -0,0 +1,7 @@
|
||||
set (FILES
|
||||
test.cpp
|
||||
)
|
||||
|
||||
add_executable (test ${FILES})
|
||||
|
||||
target_link_libraries (test ${OPENSCENEGRAPH_LIBRARIES} "components")
|
@ -0,0 +1,100 @@
|
||||
#include <osgViewer/Viewer>
|
||||
|
||||
#include <components/bsa/bsa_file.hpp>
|
||||
#include <components/nif/niffile.hpp>
|
||||
|
||||
#include <components/nifosg/nifloader.hpp>
|
||||
|
||||
#include <osgGA/TrackballManipulator>
|
||||
|
||||
#include <osgDB/Registry>
|
||||
#include <osgDB/WriteFile>
|
||||
|
||||
#include <osg/PolygonMode>
|
||||
|
||||
// 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] << " <BSA file> <NIF file>" << 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<osg::Group> 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; i<loader.mControllers.size(); ++i)
|
||||
loader.mControllers[i].update();
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
#include "controlled.hpp"
|
||||
|
||||
#include "data.hpp"
|
||||
|
||||
namespace Nif
|
||||
{
|
||||
|
||||
void NiSourceTexture::read(NIFStream *nif)
|
||||
{
|
||||
Named::read(nif);
|
||||
|
||||
external = !!nif->getChar();
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -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; i<keynum; i++)
|
||||
{
|
||||
list[i].time = nif->getFloat();
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,275 @@
|
||||
#include "controller.hpp"
|
||||
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/TexMat>
|
||||
#include <osgAnimation/MorphGeometry>
|
||||
|
||||
#include <components/nif/data.hpp>
|
||||
|
||||
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<osg::Vec3f>* 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<osg::Vec3f>* 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<osg::MatrixTransform*>(mNode);
|
||||
return trans->getMatrix().getTrans();
|
||||
}
|
||||
|
||||
void KeyframeController::Value::setValue(float time)
|
||||
{
|
||||
osg::MatrixTransform* trans = static_cast<osg::MatrixTransform*>(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<ControllerSource> src, boost::shared_ptr<ControllerValue> dest, boost::shared_ptr<ControllerFunction> 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<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));
|
||||
|
||||
mGeom->setWeight(i, val);
|
||||
}
|
||||
}
|
||||
|
||||
UVController::Value::Value(osg::StateSet *target, const Nif::NiUVData *data, std::set<int> 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<int>::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);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
#ifndef COMPONENTS_NIFOSG_CONTROLLER_H
|
||||
#define COMPONENTS_NIFOSG_CONTROLLER_H
|
||||
|
||||
#include <components/nif/niffile.hpp>
|
||||
#include <components/nif/nifkey.hpp>
|
||||
#include <components/nif/controller.hpp>
|
||||
#include <components/nif/data.hpp>
|
||||
|
||||
#include <components/nifcache/nifcache.hpp>
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <set> //UVController
|
||||
|
||||
#include <osg/Timer>
|
||||
|
||||
|
||||
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<ControllerSource> src, boost::shared_ptr<ControllerValue> dest,
|
||||
boost::shared_ptr<ControllerFunction> function);
|
||||
|
||||
virtual void update();
|
||||
|
||||
boost::shared_ptr<ControllerSource> mSource;
|
||||
boost::shared_ptr<ControllerValue> mDestValue;
|
||||
|
||||
// The source value gets passed through this function before it's passed on to the DestValue.
|
||||
boost::shared_ptr<ControllerFunction> 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<Nif::NiMorphData::MorphData> 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<int> mTextureUnits;
|
||||
|
||||
public:
|
||||
Value(osg::StateSet* target, const Nif::NiUVData *data, std::set<int> textureUnits);
|
||||
|
||||
virtual void setValue(float value);
|
||||
};
|
||||
};
|
||||
|
||||
class VisController
|
||||
{
|
||||
public:
|
||||
class Value : public NodeTargetValue
|
||||
{
|
||||
private:
|
||||
std::vector<Nif::NiVisData::VisData> 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
|
@ -0,0 +1,759 @@
|
||||
#include "nifloader.hpp"
|
||||
|
||||
#include <osg/Matrixf>
|
||||
#include <osg/MatrixTransform>
|
||||
#include <osg/Geode>
|
||||
#include <osg/Geometry>
|
||||
#include <osg/Array>
|
||||
|
||||
// resource
|
||||
#include <components/bsa/bsa_file.hpp>
|
||||
#include <osgDB/Registry>
|
||||
#include <osg/io_utils>
|
||||
#include <components/misc/stringops.hpp>
|
||||
|
||||
// skel
|
||||
#include <osgAnimation/Skeleton>
|
||||
#include <osgAnimation/Bone>
|
||||
#include <osgAnimation/RigGeometry>
|
||||
#include <osgAnimation/MorphGeometry>
|
||||
|
||||
#include <osg/BlendFunc>
|
||||
#include <osg/AlphaFunc>
|
||||
#include <osg/Depth>
|
||||
#include <osg/PolygonMode>
|
||||
#include <osg/FrontFace>
|
||||
#include <osg/Stencil>
|
||||
#include <osg/Material>
|
||||
#include <osg/Texture2D>
|
||||
|
||||
#include <components/nif/node.hpp>
|
||||
|
||||
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<const Nif::Property*>& out)
|
||||
{
|
||||
const Nif::PropertyList& props = nifNode->props;
|
||||
for (size_t i = 0; i <props.length();++i)
|
||||
{
|
||||
if (!props[i].empty())
|
||||
{
|
||||
switch (props[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<const Nif::Property*>& 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 Nif::Property*>::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<const Nif::NiMaterialProperty*>(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<const Nif::NiVertexColorProperty*>(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<osgAnimation::Bone*>(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<osg::MatrixTransform*>(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<osg::Geometry> handleMorphGeometry(const Nif::NiGeomMorpherController* morpher)
|
||||
{
|
||||
osg::ref_ptr<osgAnimation::MorphGeometry> morphGeom = new osgAnimation::MorphGeometry;
|
||||
morphGeom->setMethod(osgAnimation::MorphGeometry::RELATIVE);
|
||||
// NIF format doesn't specify morphed normals
|
||||
morphGeom->setMorphNormals(false);
|
||||
|
||||
const std::vector<Nif::NiMorphData::MorphData>& 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<osg::Geometry> 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<const Nif::Node*>(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<int, int>());
|
||||
}
|
||||
|
||||
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<const Nif::Node*>(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<int, int>());
|
||||
}
|
||||
|
||||
void Loader::applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, std::map<int, int>& boundTextures)
|
||||
{
|
||||
const Nif::PropertyList& props = nifNode->props;
|
||||
for (size_t i = 0; i <props.length();++i)
|
||||
{
|
||||
if (!props[i].empty())
|
||||
handleProperty(props[i].getPtr(), nifNode, applyTo, boundTextures);
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::createController(const Nif::Controller *ctrl, boost::shared_ptr<ControllerValue> value, int animflags)
|
||||
{
|
||||
// FIXME animflags currently not passed to this function
|
||||
//bool autoPlay = animflags & Nif::NiNode::AnimFlag_AutoPlay;
|
||||
boost::shared_ptr<ControllerSource> src(new FrameTimeSource); // if autoPlay
|
||||
|
||||
boost::shared_ptr<ControllerFunction> 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<int, int> boundTextures)
|
||||
{
|
||||
osg::ref_ptr<osg::MatrixTransform> 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<const Nif::NiTriShape*>(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<const Nif::NiNode*>(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<int, int> &boundTextures)
|
||||
{
|
||||
for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
|
||||
{
|
||||
if (ctrl->recType == Nif::RC_NiUVController)
|
||||
{
|
||||
const Nif::NiUVController *uvctrl = static_cast<const Nif::NiUVController*>(ctrl.getPtr());
|
||||
std::set<int> texUnits;
|
||||
for (std::map<int, int>::const_iterator it = boundTextures.begin(); it != boundTextures.end(); ++it)
|
||||
texUnits.insert(it->first);
|
||||
boost::shared_ptr<ControllerValue> 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<const Nif::NiKeyframeController*>(ctrl.getPtr());
|
||||
if(!key->data.empty())
|
||||
{
|
||||
if (seenKeyframeCtrl)
|
||||
{
|
||||
std::cerr << "Warning: multiple KeyframeControllers on the same node" << std::endl;
|
||||
continue;
|
||||
}
|
||||
boost::shared_ptr<ControllerValue> 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<const Nif::NiVisController*>(ctrl.getPtr());
|
||||
boost::shared_ptr<ControllerValue> 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<int, int>& 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<osg::Vec3Array> newVerts (new osg::Vec3Array(data->vertices.size()));
|
||||
osg::ref_ptr<osg::Vec3Array> 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<Nif::NiSkinData::VertWeight> &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;i<newNormals->size();++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<int, int>::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<int, int>& boundTextures)
|
||||
{
|
||||
osg::ref_ptr<osg::Geometry> 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<const Nif::NiGeomMorpherController*>(ctrl.getPtr()));
|
||||
boost::shared_ptr<ControllerValue> value(
|
||||
new GeomMorpherController::Value(static_cast<osgAnimation::MorphGeometry*>(geometry.get()),
|
||||
static_cast<const Nif::NiGeomMorpherController*>(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<osg::Geode> 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<int, int>& boundTextures)
|
||||
{
|
||||
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
|
||||
triShapeToGeometry(triShape, geometry.get(), boundTextures);
|
||||
|
||||
osg::ref_ptr<osgAnimation::RigGeometry> 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<osgAnimation::VertexInfluenceMap> 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<Nif::NiSkinData::VertWeight> &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<osg::MatrixTransform> trans(new osg::MatrixTransform);
|
||||
trans->setUpdateCallback(new InvertBoneMatrix(mSkeleton));
|
||||
|
||||
osg::ref_ptr<osg::Geode> 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<int, int>& boundTextures)
|
||||
{
|
||||
osg::StateSet* stateset = node->getOrCreateStateSet();
|
||||
|
||||
switch (property->recType)
|
||||
{
|
||||
case Nif::RC_NiStencilProperty:
|
||||
{
|
||||
const Nif::NiStencilProperty* stencilprop = static_cast<const Nif::NiStencilProperty*>(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<const Nif::NiWireframeProperty*>(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<const Nif::NiZBufferProperty*>(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<const Nif::Property*> materialProps;
|
||||
collectMaterialProperties(nifNode, materialProps);
|
||||
updateMaterialProperties(stateset, materialProps);
|
||||
break;
|
||||
}
|
||||
case Nif::RC_NiAlphaProperty:
|
||||
{
|
||||
const Nif::NiAlphaProperty* alphaprop = static_cast<const Nif::NiAlphaProperty*>(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<const Nif::NiTexturingProperty*>(property);
|
||||
for (int i=0; i<Nif::NiTexturingProperty::NumTextures; ++i)
|
||||
{
|
||||
if (i != Nif::NiTexturingProperty::BaseTexture)
|
||||
continue; // FIXME: implement other textures
|
||||
if (texprop->textures[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<unsigned int>(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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
#ifndef OPENMW_COMPONENTS_NIFOSG_LOADER
|
||||
#define OPENMW_COMPONENTS_NIFOSG_LOADER
|
||||
|
||||
#include <components/nif/niffile.hpp>
|
||||
|
||||
#include <components/nifcache/nifcache.hpp> // NIFFilePtr
|
||||
|
||||
#include <osg/Group>
|
||||
|
||||
#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<Controller> 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<int, int> boundTextures);
|
||||
|
||||
void handleMeshControllers(const Nif::Node* nifNode, osg::MatrixTransform* transformNode, const std::map<int, int>& 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<int, int>& 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<int, int>& 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<int, int>& 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<int, int>& 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<int, int>& boundTextures);
|
||||
|
||||
void createController(const Nif::Controller* ctrl, boost::shared_ptr<ControllerValue> value, int animflags);
|
||||
|
||||
Nif::NIFFilePtr mNif;
|
||||
|
||||
osg::Group* mRootNode;
|
||||
osg::Group* mSkeleton;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue