You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
441 lines
18 KiB
C++
441 lines
18 KiB
C++
/*
|
|
OpenMW - The completely unofficial reimplementation of Morrowind
|
|
Copyright (C) 2008-2010 Nicolay Korslund
|
|
Email: < korslund@gmail.com >
|
|
WWW: http://openmw.sourceforge.net/
|
|
|
|
This file (nif_file.cpp) is part of the OpenMW package.
|
|
|
|
OpenMW is distributed as free software: you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License
|
|
version 3, as published by the Free Software Foundation.
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
version 3 along with this program. If not, see
|
|
http://www.gnu.org/licenses/ .
|
|
|
|
*/
|
|
|
|
#include "niffile.hpp"
|
|
#include "record.hpp"
|
|
#include "components/misc/stringops.hpp"
|
|
|
|
#include "extra.hpp"
|
|
#include "controlled.hpp"
|
|
#include "node.hpp"
|
|
#include "property.hpp"
|
|
#include "data.hpp"
|
|
#include "effect.hpp"
|
|
#include "controller.hpp"
|
|
|
|
#include <iostream>
|
|
|
|
//TODO: when threading is needed, enable these
|
|
//#include <boost/mutex.hpp>
|
|
#include <boost/thread/locks.hpp>
|
|
|
|
namespace Nif
|
|
{
|
|
|
|
class NIFFile::LoadedCache
|
|
{
|
|
//TODO: enable this to make cache thread safe...
|
|
//typedef boost::mutex mutex;
|
|
|
|
struct mutex
|
|
{
|
|
void lock () {};
|
|
void unlock () {}
|
|
};
|
|
|
|
typedef boost::lock_guard <mutex> lock_guard;
|
|
typedef std::map < std::string, boost::weak_ptr <NIFFile> > loaded_map;
|
|
typedef std::vector < boost::shared_ptr <NIFFile> > locked_files;
|
|
|
|
static int sLockLevel;
|
|
static mutex sProtector;
|
|
static loaded_map sLoadedMap;
|
|
static locked_files sLockedFiles;
|
|
|
|
public:
|
|
|
|
static ptr create (const std::string &name)
|
|
{
|
|
lock_guard _ (sProtector);
|
|
|
|
ptr result;
|
|
|
|
// lookup the resource
|
|
loaded_map::iterator i = sLoadedMap.find (name);
|
|
|
|
if (i == sLoadedMap.end ()) // it doesn't existing currently,
|
|
{ // or hasn't in the very near past
|
|
|
|
// create it now, for smoother threading if needed, the
|
|
// loading should be performed outside of the sLoaderMap
|
|
// lock and an alternate mechanism should be used to
|
|
// synchronize threads competing to load the same resource
|
|
result = boost::make_shared <NIFFile> (name, psudo_private_modifier());
|
|
|
|
// if we are locking the cache add an extra reference
|
|
// to keep the file in memory
|
|
if (sLockLevel > 0)
|
|
sLockedFiles.push_back (result);
|
|
|
|
// stash a reference to the resource so that future
|
|
// calls can benefit
|
|
sLoadedMap [name] = boost::weak_ptr <NIFFile> (result);
|
|
}
|
|
else // it may (probably) still exists
|
|
{
|
|
// attempt to get the reference
|
|
result = i->second.lock ();
|
|
|
|
if (!result) // resource is in the process of being destroyed
|
|
{
|
|
// create a new instance, to replace the one that has
|
|
// begun the irreversible process of being destroyed
|
|
result = boost::make_shared <NIFFile> (name, psudo_private_modifier());
|
|
|
|
// respect the cache lock...
|
|
if (sLockLevel > 0)
|
|
sLockedFiles.push_back (result);
|
|
|
|
// we potentially overwrite an expired pointer here
|
|
// but the other thread performing the delete on
|
|
// the previous copy of this resource will detect it
|
|
// and make sure not to erase the new reference
|
|
sLoadedMap [name] = boost::weak_ptr <NIFFile> (result);
|
|
}
|
|
}
|
|
|
|
// we made it!
|
|
return result;
|
|
}
|
|
|
|
static void release (NIFFile * file)
|
|
{
|
|
lock_guard _ (sProtector);
|
|
|
|
loaded_map::iterator i = sLoadedMap.find (file->filename);
|
|
|
|
// its got to be in here, it just might not be us...
|
|
assert (i != sLoadedMap.end ());
|
|
|
|
// if weak_ptr is still expired, this resource hasn't been recreated
|
|
// between the initiation of the final release due to destruction
|
|
// of the last shared pointer and this thread acquiring the lock on
|
|
// the loader map
|
|
if (i->second.expired ())
|
|
sLoadedMap.erase (i);
|
|
}
|
|
|
|
static void lockCache ()
|
|
{
|
|
lock_guard _ (sProtector);
|
|
|
|
sLockLevel++;
|
|
}
|
|
|
|
static void unlockCache ()
|
|
{
|
|
locked_files resetList;
|
|
|
|
{
|
|
lock_guard _ (sProtector);
|
|
|
|
if (--sLockLevel)
|
|
sLockedFiles.swap(resetList);
|
|
}
|
|
|
|
// this not necessary, but makes it clear that the
|
|
// deletion of the locked cache entries is being done
|
|
// outside the protection of sProtector
|
|
resetList.clear ();
|
|
}
|
|
};
|
|
|
|
int NIFFile::LoadedCache::sLockLevel = 0;
|
|
NIFFile::LoadedCache::mutex NIFFile::LoadedCache::sProtector;
|
|
NIFFile::LoadedCache::loaded_map NIFFile::LoadedCache::sLoadedMap;
|
|
NIFFile::LoadedCache::locked_files NIFFile::LoadedCache::sLockedFiles;
|
|
|
|
// these three calls are forwarded to the cache implementation...
|
|
void NIFFile::lockCache () { LoadedCache::lockCache (); }
|
|
void NIFFile::unlockCache () { LoadedCache::unlockCache (); }
|
|
NIFFile::ptr NIFFile::create (const std::string &name) { return LoadedCache::create (name); }
|
|
|
|
/// Open a NIF stream. The name is used for error messages.
|
|
NIFFile::NIFFile(const std::string &name, psudo_private_modifier)
|
|
: filename(name)
|
|
{
|
|
parse();
|
|
}
|
|
|
|
NIFFile::~NIFFile()
|
|
{
|
|
LoadedCache::release (this);
|
|
|
|
for(std::size_t i=0; i<records.size(); i++)
|
|
delete records[i];
|
|
}
|
|
|
|
template <typename NodeType> static Record* construct() { return new NodeType; }
|
|
|
|
struct RecordFactoryEntry {
|
|
|
|
typedef Record* (*create_t) ();
|
|
|
|
char const * mName;
|
|
create_t mCreate;
|
|
RecordType mType;
|
|
|
|
};
|
|
|
|
/* These are all the record types we know how to read.
|
|
|
|
This can be heavily optimized later if needed. For example, a
|
|
hash table or a FSM-based parser could be used to look up
|
|
node names.
|
|
*/
|
|
|
|
static const RecordFactoryEntry recordFactories [] = {
|
|
|
|
{ "NiNode", &construct <NiNode >, RC_NiNode },
|
|
{ "AvoidNode", &construct <NiNode >, RC_AvoidNode },
|
|
{ "NiBSParticleNode", &construct <NiNode >, RC_NiBSParticleNode },
|
|
{ "NiBSAnimationNode", &construct <NiNode >, RC_NiBSAnimationNode },
|
|
{ "NiBillboardNode", &construct <NiNode >, RC_NiNode },
|
|
{ "NiTriShape", &construct <NiTriShape >, RC_NiTriShape },
|
|
{ "NiRotatingParticles", &construct <NiRotatingParticles >, RC_NiRotatingParticles },
|
|
{ "NiAutoNormalParticles", &construct <NiAutoNormalParticles >, RC_NiAutoNormalParticles },
|
|
{ "NiCamera", &construct <NiCamera >, RC_NiCamera },
|
|
{ "RootCollisionNode", &construct <NiNode >, RC_RootCollisionNode },
|
|
{ "NiTexturingProperty", &construct <NiTexturingProperty >, RC_NiTexturingProperty },
|
|
{ "NiMaterialProperty", &construct <NiMaterialProperty >, RC_NiMaterialProperty },
|
|
{ "NiZBufferProperty", &construct <NiZBufferProperty >, RC_NiZBufferProperty },
|
|
{ "NiAlphaProperty", &construct <NiAlphaProperty >, RC_NiAlphaProperty },
|
|
{ "NiVertexColorProperty", &construct <NiVertexColorProperty >, RC_NiVertexColorProperty },
|
|
{ "NiShadeProperty", &construct <NiShadeProperty >, RC_NiShadeProperty },
|
|
{ "NiDitherProperty", &construct <NiDitherProperty >, RC_NiDitherProperty },
|
|
{ "NiWireframeProperty", &construct <NiWireframeProperty >, RC_NiWireframeProperty },
|
|
{ "NiSpecularProperty", &construct <NiSpecularProperty >, RC_NiSpecularProperty },
|
|
{ "NiStencilProperty", &construct <NiStencilProperty >, RC_NiStencilProperty },
|
|
{ "NiVisController", &construct <NiVisController >, RC_NiVisController },
|
|
{ "NiGeomMorpherController", &construct <NiGeomMorpherController >, RC_NiGeomMorpherController },
|
|
{ "NiKeyframeController", &construct <NiKeyframeController >, RC_NiKeyframeController },
|
|
{ "NiAlphaController", &construct <NiAlphaController >, RC_NiAlphaController },
|
|
{ "NiUVController", &construct <NiUVController >, RC_NiUVController },
|
|
{ "NiPathController", &construct <NiPathController >, RC_NiPathController },
|
|
{ "NiMaterialColorController", &construct <NiMaterialColorController >, RC_NiMaterialColorController },
|
|
{ "NiBSPArrayController", &construct <NiBSPArrayController >, RC_NiBSPArrayController },
|
|
{ "NiParticleSystemController", &construct <NiParticleSystemController >, RC_NiParticleSystemController },
|
|
{ "NiFlipController", &construct <NiFlipController >, RC_NiFlipController },
|
|
{ "NiAmbientLight", &construct <NiLight >, RC_NiLight },
|
|
{ "NiDirectionalLight", &construct <NiLight >, RC_NiLight },
|
|
{ "NiTextureEffect", &construct <NiTextureEffect >, RC_NiTextureEffect },
|
|
{ "NiVertWeightsExtraData", &construct <NiVertWeightsExtraData >, RC_NiVertWeightsExtraData },
|
|
{ "NiTextKeyExtraData", &construct <NiTextKeyExtraData >, RC_NiTextKeyExtraData },
|
|
{ "NiStringExtraData", &construct <NiStringExtraData >, RC_NiStringExtraData },
|
|
{ "NiGravity", &construct <NiGravity >, RC_NiGravity },
|
|
{ "NiPlanarCollider", &construct <NiPlanarCollider >, RC_NiPlanarCollider },
|
|
{ "NiParticleGrowFade", &construct <NiParticleGrowFade >, RC_NiParticleGrowFade },
|
|
{ "NiParticleColorModifier", &construct <NiParticleColorModifier >, RC_NiParticleColorModifier },
|
|
{ "NiParticleRotation", &construct <NiParticleRotation >, RC_NiParticleRotation },
|
|
{ "NiFloatData", &construct <NiFloatData >, RC_NiFloatData },
|
|
{ "NiTriShapeData", &construct <NiTriShapeData >, RC_NiTriShapeData },
|
|
{ "NiVisData", &construct <NiVisData >, RC_NiVisData },
|
|
{ "NiColorData", &construct <NiColorData >, RC_NiColorData },
|
|
{ "NiPixelData", &construct <NiPixelData >, RC_NiPixelData },
|
|
{ "NiMorphData", &construct <NiMorphData >, RC_NiMorphData },
|
|
{ "NiKeyframeData", &construct <NiKeyframeData >, RC_NiKeyframeData },
|
|
{ "NiSkinData", &construct <NiSkinData >, RC_NiSkinData },
|
|
{ "NiUVData", &construct <NiUVData >, RC_NiUVData },
|
|
{ "NiPosData", &construct <NiPosData >, RC_NiPosData },
|
|
{ "NiRotatingParticlesData", &construct <NiRotatingParticlesData >, RC_NiRotatingParticlesData },
|
|
{ "NiAutoNormalParticlesData", &construct <NiAutoNormalParticlesData >, RC_NiAutoNormalParticlesData },
|
|
{ "NiSequenceStreamHelper", &construct <NiSequenceStreamHelper >, RC_NiSequenceStreamHelper },
|
|
{ "NiSourceTexture", &construct <NiSourceTexture >, RC_NiSourceTexture },
|
|
{ "NiSkinInstance", &construct <NiSkinInstance >, RC_NiSkinInstance },
|
|
};
|
|
|
|
static RecordFactoryEntry const * recordFactories_begin = &recordFactories [0];
|
|
static RecordFactoryEntry const * recordFactories_end = &recordFactories [sizeof (recordFactories) / sizeof (recordFactories[0])];
|
|
|
|
RecordFactoryEntry const * lookupRecordFactory (char const * name)
|
|
{
|
|
RecordFactoryEntry const * i;
|
|
|
|
for (i = recordFactories_begin; i != recordFactories_end; ++i)
|
|
if (strcmp (name, i->mName) == 0)
|
|
break;
|
|
|
|
if (i == recordFactories_end)
|
|
return NULL;
|
|
|
|
return i;
|
|
}
|
|
|
|
/* This file implements functions from the NIFFile class. It is also
|
|
where we stash all the functions we couldn't add as inline
|
|
definitions in the record types.
|
|
*/
|
|
|
|
void NIFFile::parse()
|
|
{
|
|
NIFStream nif (this, Ogre::ResourceGroupManager::getSingleton().openResource(filename));
|
|
|
|
// Check the header string
|
|
std::string head = nif.getString(40);
|
|
if(head.compare(0, 22, "NetImmerse File Format") != 0)
|
|
fail("Invalid NIF header");
|
|
|
|
// Get BCD version
|
|
ver = nif.getInt();
|
|
if(ver != VER_MW)
|
|
fail("Unsupported NIF version");
|
|
|
|
// Number of records
|
|
size_t recNum = nif.getInt();
|
|
records.resize(recNum);
|
|
|
|
/* The format for 10.0.1.0 seems to be a bit different. After the
|
|
header, it contains the number of records, r (int), just like
|
|
4.0.0.2, but following that it contains a short x, followed by x
|
|
strings. Then again by r shorts, one for each record, giving
|
|
which of the above strings to use to identify the record. After
|
|
this follows two ints (zero?) and then the record data. However
|
|
we do not support or plan to support other versions yet.
|
|
*/
|
|
|
|
for(size_t i = 0;i < recNum;i++)
|
|
{
|
|
Record *r = NULL;
|
|
|
|
std::string rec = nif.getString();
|
|
|
|
RecordFactoryEntry const * entry = lookupRecordFactory (rec.c_str ());
|
|
|
|
if (entry != NULL)
|
|
{
|
|
r = entry->mCreate ();
|
|
r->recType = entry->mType;
|
|
}
|
|
else
|
|
fail("Unknown record type " + rec);
|
|
|
|
assert(r != NULL);
|
|
assert(r->recType != RC_MISSING);
|
|
r->recName = rec;
|
|
r->recIndex = i;
|
|
records[i] = r;
|
|
r->read(&nif);
|
|
|
|
// Discard tranformations for the root node, otherwise some meshes
|
|
// occasionally get wrong orientation. Only for NiNode-s for now, but
|
|
// can be expanded if needed.
|
|
// This should be rewritten when the method is cleaned up.
|
|
if (0 == i && rec == "NiNode")
|
|
{
|
|
static_cast<Nif::Node*>(r)->trafo = Nif::Transformation::getIdentity();
|
|
}
|
|
}
|
|
|
|
size_t rootNum = nif.getUInt();
|
|
roots.resize(rootNum);
|
|
|
|
for(size_t i = 0;i < rootNum;i++)
|
|
{
|
|
intptr_t idx = nif.getInt();
|
|
roots[i] = ((idx >= 0) ? records.at(idx) : NULL);
|
|
}
|
|
|
|
// Once parsing is done, do post-processing.
|
|
for(size_t i=0; i<recNum; i++)
|
|
records[i]->post(this);
|
|
}
|
|
|
|
/// \todo move to the write cpp file
|
|
|
|
void NiSkinInstance::post(NIFFile *nif)
|
|
{
|
|
data.post(nif);
|
|
root.post(nif);
|
|
bones.post(nif);
|
|
|
|
if(data.empty() || root.empty())
|
|
nif->fail("NiSkinInstance missing root or data");
|
|
|
|
size_t bnum = bones.length();
|
|
if(bnum != data->bones.size())
|
|
nif->fail("Mismatch in NiSkinData bone count");
|
|
|
|
root->makeRootBone(&data->trafo);
|
|
|
|
for(size_t i=0; i<bnum; i++)
|
|
{
|
|
if(bones[i].empty())
|
|
nif->fail("Oops: Missing bone! Don't know how to handle this.");
|
|
bones[i]->makeBone(i, data->bones[i]);
|
|
}
|
|
}
|
|
|
|
|
|
void Node::getProperties(const Nif::NiTexturingProperty *&texprop,
|
|
const Nif::NiMaterialProperty *&matprop,
|
|
const Nif::NiAlphaProperty *&alphaprop,
|
|
const Nif::NiVertexColorProperty *&vertprop,
|
|
const Nif::NiZBufferProperty *&zprop,
|
|
const Nif::NiSpecularProperty *&specprop,
|
|
const Nif::NiWireframeProperty *&wireprop) const
|
|
{
|
|
if(parent)
|
|
parent->getProperties(texprop, matprop, alphaprop, vertprop, zprop, specprop, wireprop);
|
|
|
|
for(size_t i = 0;i < props.length();i++)
|
|
{
|
|
// Entries may be empty
|
|
if(props[i].empty())
|
|
continue;
|
|
|
|
const Nif::Property *pr = props[i].getPtr();
|
|
if(pr->recType == Nif::RC_NiTexturingProperty)
|
|
texprop = static_cast<const Nif::NiTexturingProperty*>(pr);
|
|
else if(pr->recType == Nif::RC_NiMaterialProperty)
|
|
matprop = static_cast<const Nif::NiMaterialProperty*>(pr);
|
|
else if(pr->recType == Nif::RC_NiAlphaProperty)
|
|
alphaprop = static_cast<const Nif::NiAlphaProperty*>(pr);
|
|
else if(pr->recType == Nif::RC_NiVertexColorProperty)
|
|
vertprop = static_cast<const Nif::NiVertexColorProperty*>(pr);
|
|
else if(pr->recType == Nif::RC_NiZBufferProperty)
|
|
zprop = static_cast<const Nif::NiZBufferProperty*>(pr);
|
|
else if(pr->recType == Nif::RC_NiSpecularProperty)
|
|
specprop = static_cast<const Nif::NiSpecularProperty*>(pr);
|
|
else if(pr->recType == Nif::RC_NiWireframeProperty)
|
|
wireprop = static_cast<const Nif::NiWireframeProperty*>(pr);
|
|
else
|
|
std::cerr<< "Unhandled property type: "<<pr->recName <<std::endl;
|
|
}
|
|
}
|
|
|
|
Ogre::Matrix4 Node::getLocalTransform() const
|
|
{
|
|
Ogre::Matrix4 mat4 = Ogre::Matrix4(Ogre::Matrix4::IDENTITY);
|
|
mat4.makeTransform(trafo.pos, Ogre::Vector3(trafo.scale), Ogre::Quaternion(trafo.rotation));
|
|
return mat4;
|
|
}
|
|
|
|
Ogre::Matrix4 Node::getWorldTransform() const
|
|
{
|
|
if(parent != NULL)
|
|
return parent->getWorldTransform() * getLocalTransform();
|
|
return getLocalTransform();
|
|
}
|
|
|
|
}
|