More NIF adjustments

Constant interpolation support
pull/2649/head
capostrophic 5 years ago
parent a5289035e2
commit e654a52b70

@ -35,16 +35,16 @@ void ShapeData::read(NIFStream *nif)
{
int verts = nif->getUShort();
if(nif->getInt())
if (nif->getBoolean())
nif->getVector3s(vertices, verts);
if(nif->getInt())
if (nif->getBoolean())
nif->getVector3s(normals, verts);
center = nif->getVector3();
radius = nif->getFloat();
if(nif->getInt())
if (nif->getBoolean())
nif->getVector4s(colors, verts);
// Only the first 6 bits are used as a count. I think the rest are
@ -120,7 +120,7 @@ void NiAutoNormalParticlesData::read(NIFStream *nif)
particleRadius = nif->getFloat();
activeCount = nif->getUShort();
if(nif->getInt())
if (nif->getBoolean())
{
// Particle sizes
nif->getFloats(sizes, vertices.size());
@ -131,7 +131,7 @@ void NiRotatingParticlesData::read(NIFStream *nif)
{
NiAutoNormalParticlesData::read(nif);
if(nif->getInt())
if (nif->getBoolean())
{
// Rotation quaternions.
nif->getQuaternions(rotations, vertices.size());
@ -176,7 +176,7 @@ void NiPixelData::read(NIFStream *nif)
numberOfMipmaps = nif->getUInt();
// Bytes per pixel, should be bpp * 8
// Bytes per pixel, should be bpp / 8
/* int bytes = */ nif->getUInt();
for(unsigned int i=0; i<numberOfMipmaps; i++)
@ -228,10 +228,8 @@ void NiSkinData::read(NIFStream *nif)
nif->getInt(); // -1
bones.resize(boneNum);
for(int i=0;i<boneNum;i++)
for (BoneInfo &bi : bones)
{
BoneInfo &bi = bones[i];
bi.trafo.rotation = nif->getMatrix3();
bi.trafo.pos = nif->getVector3();
bi.trafo.scale = nif->getFloat();
@ -267,7 +265,7 @@ void NiKeyframeData::read(NIFStream *nif)
{
mRotations = std::make_shared<QuaternionKeyMap>();
mRotations->read(nif);
if(mRotations->mInterpolationType == Vector3KeyMap::sXYZInterpolation)
if(mRotations->mInterpolationType == Vector3KeyMap::XYZ)
{
//Chomp unused float
nif->getFloat();

@ -9,9 +9,7 @@ namespace Nif
/// Open a NIF stream. The name is used for error messages.
NIFFile::NIFFile(Files::IStreamPtr stream, const std::string &name)
: ver(0)
, filename(name)
, mUseSkinning(false)
: filename(name)
{
parse(stream);
}
@ -139,27 +137,18 @@ void NIFFile::parse(Files::IStreamPtr stream)
// Check the header string
std::string head = nif.getVersionString();
if(head.compare(0, 22, "NetImmerse File Format") != 0)
fail("Invalid NIF header: " + head);
fail("Invalid NIF header: " + head);
// Get BCD version
ver = nif.getUInt();
// 4.0.0.0 is an older, practically identical version of the format.
// It's not used by Morrowind assets but Morrowind supports it.
if(ver != 0x04000000 && ver != VER_MW)
if(ver != VER_4_0_0_0 && ver != VER_MW)
fail("Unsupported NIF version: " + printVersion(ver));
// 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 = nullptr;

@ -26,21 +26,27 @@ struct File
virtual size_t numRoots() const = 0;
virtual std::string getString(size_t index) const = 0;
virtual void setUseSkinning(bool skinning) = 0;
virtual bool getUseSkinning() const = 0;
virtual std::string getFilename() const = 0;
virtual unsigned int getVersion() const = 0;
virtual unsigned int getUserVersion() const = 0;
virtual unsigned int getBethVersion() const = 0;
};
class NIFFile final : public File
{
enum NIFVersion {
VER_MW = 0x04000002 // Morrowind NIFs
};
/// Nif file version
unsigned int ver;
/// File version, user version, Bethesda version
unsigned int ver = 0;
unsigned int userVer = 0;
unsigned int bethVer = 0;
/// File name, used for error messages and opening the file
std::string filename;
@ -51,7 +57,10 @@ class NIFFile final : public File
/// Root list. This is a select portion of the pointers from records
std::vector<Record*> roots;
bool mUseSkinning;
/// String table
std::vector<std::string> strings;
bool mUseSkinning = false;
/// Parse the file
void parse(Files::IStreamPtr stream);
@ -66,6 +75,34 @@ class NIFFile final : public File
void operator = (NIFFile const &);
public:
enum NIFVersion
{
// Feature-relevant
VER_4_1_0_0 = 0x04010000, // 1-byte booleans (previously 4-byte)
VER_5_0_0_1 = 0x05000001, // Optimized record type listings
VER_5_0_0_6 = 0x05000006, // Record groups
VER_10_0_1_8 = 0x0A000108, // The last version without user version
VER_20_1_0_1 = 0x14010001, // String tables
VER_20_2_0_5 = 0x14020005, // Record sizes
// Game-relevant
VER_4_0_0_0 = 0x04000000, // Freedom Force NIFs, supported by Morrowind
VER_MW = 0x04000002, // 4.0.0.2. Morrowind and Freedom Force NIFs
VER_4_2_1_0 = 0x04020100, // Used in Civ4 and Dark Age of Camelot
VER_CI = 0x04020200, // 4.2.2.0. Main Culpa Innata NIF version, also used in Civ4
VER_ZT2 = 0x0A000100, // 10.0.1.0. Main Zoo Tycoon 2 NIF version, also used in Oblivion and Civ4
VER_OB_OLD = 0x0A000102, // 10.0.1.2. Main older Oblivion NIF version
VER_GAMEBRYO = 0x0A010000, // 10.1.0.0. Lots of games use it. The first version that has Gamebryo File Format header.
VER_10_2_0_0 = 0x0A020000, // Lots of games use this version as well.
VER_CIV4 = 0x14000004, // 20.0.0.4. Main Civilization IV NIF version.
VER_OB = 0x14000005, // 20.0.0.5. Main Oblivion NIF version
VER_BGS = 0x14020007 // 20.2.0.7. Main Fallout 3/4/76/New Vegas and Skyrim/SkyrimSE NIF version.
};
enum BethVersion
{
BETHVER_FO3 = 34, // Fallout 3
BETHVER_FO4 = 130 // Fallout 4
};
/// Used if file parsing fails
void fail(const std::string &msg) const
{
@ -101,6 +138,12 @@ public:
/// Number of roots
size_t numRoots() const override { return roots.size(); }
/// Get a given string from the file's string table
std::string getString(size_t index) const override
{
return strings.at(index);
}
/// Set whether there is skinning contained in this NIF file.
/// @note This is just a hint for users of the NIF file and has no effect on the loading procedure.
void setUseSkinning(bool skinning) override;
@ -109,8 +152,17 @@ public:
/// Get the name of the file
std::string getFilename() const override { return filename; }
/// Get the version of the NIF format used
unsigned int getVersion() const override { return ver; }
/// Get the user version of the NIF format used
unsigned int getUserVersion() const override { return userVer; }
/// Get the Bethesda version of the NIF format used
unsigned int getBethVersion() const override { return bethVer; }
};
typedef std::shared_ptr<const Nif::NIFFile> NIFFilePtr;
using NIFFilePtr = std::shared_ptr<const Nif::NIFFile>;

@ -26,34 +26,37 @@ struct KeyT {
float mContinuity; // Only for TBC interpolation
*/
};
typedef KeyT<float> FloatKey;
typedef KeyT<osg::Vec3f> Vector3Key;
typedef KeyT<osg::Vec4f> Vector4Key;
typedef KeyT<osg::Quat> QuaternionKey;
using FloatKey = KeyT<float>;
using Vector3Key = KeyT<osg::Vec3f>;
using Vector4Key = KeyT<osg::Vec4f>;
using QuaternionKey = KeyT<osg::Quat>;
template<typename T, T (NIFStream::*getValue)()>
struct KeyMapT {
typedef std::map< float, KeyT<T> > MapType;
using MapType = std::map<float, KeyT<T>>;
typedef T ValueType;
typedef KeyT<T> KeyType;
using ValueType = T;
using KeyType = KeyT<T>;
static const unsigned int sLinearInterpolation = 1;
static const unsigned int sQuadraticInterpolation = 2;
static const unsigned int sTBCInterpolation = 3;
static const unsigned int sXYZInterpolation = 4;
unsigned int mInterpolationType;
enum InterpolationType
{
Unknown = 0,
Linear = 1,
Quadratic = 2,
TBC = 3,
XYZ = 4,
Constant = 5
};
unsigned int mInterpolationType = Linear;
MapType mKeys;
KeyMapT() : mInterpolationType(sLinearInterpolation) {}
//Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html)
void read(NIFStream *nif, bool force=false)
{
assert(nif);
mInterpolationType = 0;
mInterpolationType = Unknown;
size_t count = nif->getUInt();
if(count == 0 && !force)
@ -66,7 +69,7 @@ struct KeyMapT {
KeyT<T> key;
NIFStream &nifReference = *nif;
if(mInterpolationType == sLinearInterpolation)
if (mInterpolationType == Linear || mInterpolationType == Constant)
{
for(size_t i = 0;i < count;i++)
{
@ -75,7 +78,7 @@ struct KeyMapT {
mKeys[time] = key;
}
}
else if(mInterpolationType == sQuadraticInterpolation)
else if (mInterpolationType == Quadratic)
{
for(size_t i = 0;i < count;i++)
{
@ -84,7 +87,7 @@ struct KeyMapT {
mKeys[time] = key;
}
}
else if(mInterpolationType == sTBCInterpolation)
else if (mInterpolationType == TBC)
{
for(size_t i = 0;i < count;i++)
{
@ -98,7 +101,7 @@ struct KeyMapT {
// Eats a floating point number, then
// Re-runs the read function 3 more times.
// When it does that it's reading in a bunch of sLinearInterpolation keys, not sXYZInterpolation.
else if(mInterpolationType == sXYZInterpolation)
else if(mInterpolationType == XYZ)
{
//Don't try to read XYZ keys into the wrong part
if ( count != 1 )
@ -109,7 +112,7 @@ struct KeyMapT {
nif->file->fail(error.str());
}
}
else if (0 == mInterpolationType)
else if (mInterpolationType == Unknown)
{
if (count != 0)
nif->file->fail("Interpolation type 0 doesn't work with keys");
@ -149,15 +152,17 @@ private:
/*key.mContinuity = */nif.getFloat();
}
};
typedef KeyMapT<float,&NIFStream::getFloat> FloatKeyMap;
typedef KeyMapT<osg::Vec3f,&NIFStream::getVector3> Vector3KeyMap;
typedef KeyMapT<osg::Vec4f,&NIFStream::getVector4> Vector4KeyMap;
typedef KeyMapT<osg::Quat,&NIFStream::getQuaternion> QuaternionKeyMap;
typedef std::shared_ptr<FloatKeyMap> FloatKeyMapPtr;
typedef std::shared_ptr<Vector3KeyMap> Vector3KeyMapPtr;
typedef std::shared_ptr<Vector4KeyMap> Vector4KeyMapPtr;
typedef std::shared_ptr<QuaternionKeyMap> QuaternionKeyMapPtr;
using FloatKeyMap = KeyMapT<float,&NIFStream::getFloat>;
using Vector3KeyMap = KeyMapT<osg::Vec3f,&NIFStream::getVector3>;
using Vector4KeyMap = KeyMapT<osg::Vec4f,&NIFStream::getVector4>;
using QuaternionKeyMap = KeyMapT<osg::Quat,&NIFStream::getQuaternion>;
using ByteKeyMap = KeyMapT<char,&NIFStream::getChar>;
using FloatKeyMapPtr = std::shared_ptr<FloatKeyMap>;
using Vector3KeyMapPtr = std::shared_ptr<Vector3KeyMap>;
using Vector4KeyMapPtr = std::shared_ptr<Vector4KeyMap>;
using QuaternionKeyMapPtr = std::shared_ptr<QuaternionKeyMap>;
using ByteKeyMapPtr = std::shared_ptr<ByteKeyMap>;
} // Namespace
#endif //#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP

@ -24,4 +24,21 @@ namespace Nif
t.scale = getFloat();
return t;
}
///Currently specific for 4.0.0.2 and earlier
bool NIFStream::getBoolean()
{
return !!getInt();
}
///Read in a string, either from the string table using the index (currently absent) or from the stream using the specified length
std::string NIFStream::getString()
{
return getSizedString();
}
// Convenience utility functions: get the versions of the currently read file
unsigned int NIFStream::getVersion() { return file->getVersion(); }
unsigned int NIFStream::getUserVersion() { return file->getBethVersion(); }
unsigned int NIFStream::getBethVersion() { return file->getBethVersion(); }
}

@ -155,8 +155,16 @@ public:
Transformation getTrafo();
bool getBoolean();
std::string getString();
unsigned int getVersion();
unsigned int getUserVersion();
unsigned int getBethVersion();
///Read in a string of the given length
std::string getString(size_t length)
std::string getSizedString(size_t length)
{
std::vector<char> str(length + 1, 0);
@ -165,11 +173,19 @@ public:
return str.data();
}
///Read in a string of the length specified in the file
std::string getString()
std::string getSizedString()
{
size_t size = readLittleEndianType<uint32_t,uint32_t>(inp);
return getString(size);
return getSizedString(size);
}
///Specific to Bethesda headers, uses a byte for length
std::string getExportString()
{
size_t size = static_cast<size_t>(readLittleEndianType<uint8_t,uint8_t>(inp));
return getSizedString(size);
}
///This is special since the version string doesn't start with a number, and ends with "\n"
std::string getVersionString()
{
@ -190,6 +206,18 @@ public:
readLittleEndianDynamicBufferOfType<float,uint32_t>(inp, vec.data(), size);
}
void getInts(std::vector<int> &vec, size_t size)
{
vec.resize(size);
readLittleEndianDynamicBufferOfType<int,int>(inp, vec.data(), size);
}
void getUInts(std::vector<unsigned int> &vec, size_t size)
{
vec.resize(size);
readLittleEndianDynamicBufferOfType<unsigned int,unsigned int>(inp, vec.data(), size);
}
void getVector2s(std::vector<osg::Vec2f> &vec, size_t size)
{
vec.resize(size);
@ -217,6 +245,20 @@ public:
for (size_t i = 0;i < quat.size();i++)
quat[i] = getQuaternion();
}
void getStrings(std::vector<std::string> &vec, size_t size)
{
vec.resize(size);
for (size_t i = 0; i < vec.size(); i++)
vec[i] = getString();
}
/// We need to use this when the string table isn't actually initialized.
void getSizedStrings(std::vector<std::string> &vec, size_t size)
{
vec.resize(size);
for (size_t i = 0; i < vec.size(); i++)
vec[i] = getSizedString();
}
};
}

@ -24,7 +24,7 @@ class Node : public Named
{
public:
// Node flags. Interpretation depends somewhat on the type of node.
int flags;
unsigned int flags;
Transformation trafo;
osg::Vec3f velocity; // Unused? Might be a run-time game state
PropertyList props;

@ -14,7 +14,7 @@ void Property::read(NIFStream *nif)
void NiTexturingProperty::Texture::read(NIFStream *nif)
{
inUse = !!nif->getInt();
inUse = nif->getBoolean();
if(!inUse) return;
texture.read(nif);

@ -167,6 +167,7 @@ using NiParticleModifierPtr = RecordPtrT<NiParticleModifier>;
using NodeList = RecordListT<Node>;
using PropertyList = RecordListT<Property>;
using ExtraList = RecordListT<Extra>;
using NiSourceTextureList = RecordListT<NiSourceTexture>;
} // Namespace

@ -35,17 +35,33 @@ namespace NifOsg
{
// interpolation of keyframes
template <typename MapT, typename InterpolationFunc>
template <typename MapT>
class ValueInterpolator
{
public:
typedef typename MapT::ValueType ValueT;
ValueInterpolator()
: mDefaultVal(ValueT())
typename MapT::MapType::const_iterator retrieveKey(float time) const
{
// retrieve the current position in the map, optimized for the most common case
// where time moves linearly along the keyframe track
if (mLastHighKey != mKeys->mKeys.end())
{
if (time > mLastHighKey->first)
{
// try if we're there by incrementing one
++mLastLowKey;
++mLastHighKey;
}
if (mLastHighKey != mKeys->mKeys.end() && time >= mLastLowKey->first && time <= mLastHighKey->first)
return mLastHighKey;
}
return mKeys->mKeys.lower_bound(time);
}
public:
using ValueT = typename MapT::ValueType;
ValueInterpolator() = default;
ValueInterpolator(std::shared_ptr<const MapT> keys, ValueT defaultVal = ValueT())
: mKeys(keys)
, mDefaultVal(defaultVal)
@ -67,44 +83,21 @@ namespace NifOsg
if(time <= keys.begin()->first)
return keys.begin()->second.mValue;
// retrieve the current position in the map, optimized for the most common case
// where time moves linearly along the keyframe track
typename MapT::MapType::const_iterator it = mLastHighKey;
if (mLastHighKey != keys.end())
{
if (time > mLastHighKey->first)
{
// try if we're there by incrementing one
++mLastLowKey;
++mLastHighKey;
it = mLastHighKey;
}
if (mLastHighKey == keys.end() || (time < mLastLowKey->first || time > mLastHighKey->first))
it = keys.lower_bound(time); // still not there, reorient by performing lower_bound check on the whole map
}
else
it = keys.lower_bound(time);
typename MapT::MapType::const_iterator it = retrieveKey(time);
// now do the actual interpolation
if (it != keys.end())
{
float aTime = it->first;
const typename MapT::KeyType* aKey = &it->second;
// cache for next time
mLastHighKey = it;
mLastLowKey = --it;
typename MapT::MapType::const_iterator last = --it;
mLastLowKey = last;
float aLastTime = last->first;
const typename MapT::KeyType* aLastKey = &last->second;
float a = (time - aLastTime) / (aTime - aLastTime);
float a = (time - mLastLowKey->first) / (mLastHighKey->first - mLastLowKey->first);
return InterpolationFunc()(aLastKey->mValue, aKey->mValue, a);
return interpolate(mLastLowKey->second, mLastHighKey->second, a, mKeys->mInterpolationType);
}
else
return keys.rbegin()->second.mValue;
return keys.rbegin()->second.mValue;
}
bool empty() const
@ -113,36 +106,44 @@ namespace NifOsg
}
private:
mutable typename MapT::MapType::const_iterator mLastLowKey;
mutable typename MapT::MapType::const_iterator mLastHighKey;
std::shared_ptr<const MapT> mKeys;
ValueT mDefaultVal;
};
struct LerpFunc
{
template <typename ValueType>
inline ValueType operator()(const ValueType& a, const ValueType& b, float fraction)
ValueType interpolate(const Nif::KeyT<ValueType>& a, const Nif::KeyT<ValueType>& b, float fraction, unsigned int type) const
{
return a + ((b - a) * fraction);
switch (type)
{
case 5:
return fraction > 0.5f ? b.mValue : a.mValue;
default:
return a.mValue + ((b.mValue - a.mValue) * fraction);
}
}
};
struct QuaternionSlerpFunc
{
inline osg::Quat operator()(const osg::Quat& a, const osg::Quat& b, float fraction)
osg::Quat interpolate(const Nif::KeyT<osg::Quat>& a, const Nif::KeyT<osg::Quat>& b, float fraction, unsigned int type) const
{
osg::Quat result;
result.slerp(fraction, a, b);
return result;
switch (type)
{
case 5:
return fraction > 0.5f ? b.mValue : a.mValue;
default:
{
osg::Quat result;
result.slerp(fraction, a.mValue, b.mValue);
return result;
}
}
}
mutable typename MapT::MapType::const_iterator mLastLowKey;
mutable typename MapT::MapType::const_iterator mLastHighKey;
std::shared_ptr<const MapT> mKeys;
ValueT mDefaultVal = ValueT();
};
typedef ValueInterpolator<Nif::QuaternionKeyMap, QuaternionSlerpFunc> QuaternionInterpolator;
typedef ValueInterpolator<Nif::FloatKeyMap, LerpFunc> FloatInterpolator;
typedef ValueInterpolator<Nif::Vector3KeyMap, LerpFunc> Vec3Interpolator;
using QuaternionInterpolator = ValueInterpolator<Nif::QuaternionKeyMap>;
using FloatInterpolator = ValueInterpolator<Nif::FloatKeyMap>;
using Vec3Interpolator = ValueInterpolator<Nif::Vector3KeyMap>;
using Vec4Interpolator = ValueInterpolator<Nif::Vector4KeyMap>;
class ControllerFunction : public SceneUtil::ControllerFunction
{

@ -184,14 +184,16 @@ namespace NifOsg
{
public:
/// @param filename used for warning messages.
LoaderImpl(const std::string& filename)
: mFilename(filename), mFirstRootTextureIndex(-1), mFoundFirstRootTexturingProperty(false)
LoaderImpl(const std::string& filename, unsigned int ver, unsigned int userver, unsigned int bethver)
: mFilename(filename), mVersion(ver), mUserVersion(userver), mBethVersion(bethver)
{
}
std::string mFilename;
size_t mFirstRootTextureIndex;
bool mFoundFirstRootTexturingProperty;
unsigned int mVersion, mUserVersion, mBethVersion;
size_t mFirstRootTextureIndex = -1;
bool mFoundFirstRootTexturingProperty = false;
static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target)
{
@ -1846,13 +1848,13 @@ namespace NifOsg
osg::ref_ptr<osg::Node> Loader::load(Nif::NIFFilePtr file, Resource::ImageManager* imageManager)
{
LoaderImpl impl(file->getFilename());
LoaderImpl impl(file->getFilename(), file->getVersion(), file->getUserVersion(), file->getBethVersion());
return impl.load(file, imageManager);
}
void Loader::loadKf(Nif::NIFFilePtr kf, KeyframeHolder& target)
{
LoaderImpl impl(kf->getFilename());
LoaderImpl impl(kf->getFilename(), kf->getVersion(), kf->getUserVersion(), kf->getBethVersion());
impl.loadKf(kf, target);
}

@ -151,7 +151,6 @@ namespace NifOsg
float mCachedDefaultSize;
};
typedef ValueInterpolator<Nif::Vector4KeyMap, LerpFunc> Vec4Interpolator;
class ParticleColorAffector : public osgParticle::Operator
{
public:

Loading…
Cancel
Save