diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 30903b897..72dcd3066 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -244,6 +244,7 @@ namespace void init(Nif::Named& value) { value.extra = Nif::ExtraPtr(nullptr); + value.extralist = Nif::ExtraList(); value.controller = Nif::ControllerPtr(nullptr); } diff --git a/components/nif/base.hpp b/components/nif/base.hpp index 6e26f525e..0d42131e5 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -14,12 +14,18 @@ namespace Nif class Extra : public Record { public: + std::string name; ExtraPtr next; // Next extra data record in the list void read(NIFStream *nif) { - next.read(nif); - nif->getUInt(); // Size of the record + if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) + name = nif->getString(); + else if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) + { + next.read(nif); + nif->getUInt(); // Size of the record + } } void post(NIFFile *nif) { next.post(nif); } @@ -44,18 +50,23 @@ class Named : public Record public: std::string name; ExtraPtr extra; + ExtraList extralist; ControllerPtr controller; void read(NIFStream *nif) { name = nif->getString(); - extra.read(nif); + if (nif->getVersion() < NIFStream::generateVersion(10,0,1,0)) + extra.read(nif); + else + extralist.read(nif); controller.read(nif); } void post(NIFFile *nif) { extra.post(nif); + extralist.post(nif); controller.post(nif); } }; diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp index 0b5c32a10..ab2b8dc17 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/controlled.cpp @@ -14,16 +14,31 @@ namespace Nif if (external) filename = nif->getString(); else - internal = nif->getChar(); - - if (!external && internal) + { + if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,3)) + internal = nif->getChar(); + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + filename = nif->getString(); // Original file path of the internal texture + } + if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,3)) + { + if (!external && internal) + data.read(nif); + } + else + { data.read(nif); + } pixel = nif->getUInt(); mipmap = nif->getUInt(); alpha = nif->getUInt(); nif->getChar(); // always 1 + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,103)) + nif->getBoolean(); // Direct rendering + if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,4)) + nif->getBoolean(); // NiPersistentSrcTextureRendererData is used instead of NiPixelData } void NiSourceTexture::post(NIFFile *nif) @@ -79,6 +94,12 @@ namespace Nif NiParticleModifier::read(nif); mBounceFactor = nif->getFloat(); + if (nif->getVersion() >= NIFStream::generateVersion(4,2,2,0)) + { + // Unused in NifSkope. Need to figure out what these do. + /*bool spawnOnCollision = */nif->getBoolean(); + /*bool dieOnCollision = */nif->getBoolean(); + } } void NiPlanarCollider::read(NIFStream *nif) diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index c63c83676..8d943b58a 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -97,7 +97,10 @@ namespace Nif // 01: Diffuse // 10: Specular // 11: Emissive - targetColor = (flags >> 4) & 3; + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + targetColor = nif->getUShort() & 3; + else + targetColor = (flags >> 4) & 3; data.read(nif); } @@ -110,6 +113,8 @@ namespace Nif void NiLookAtController::read(NIFStream *nif) { Controller::read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + lookAtFlags = nif->getUShort(); target.read(nif); } @@ -192,6 +197,8 @@ namespace Nif void NiGeomMorpherController::read(NIFStream *nif) { Controller::read(nif); + if (nif->getVersion() >= NIFFile::NIFVersion::VER_OB_OLD) + /*bool updateNormals = !!*/nif->getUShort(); data.read(nif); if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW) /*bool alwaysActive = */nif->getChar(); // Always 0 @@ -219,8 +226,11 @@ namespace Nif { Controller::read(nif); mTexSlot = nif->getUInt(); - /*unknown=*/nif->getUInt();/*0?*/ - mDelta = nif->getFloat(); + if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103)) + { + timeStart = nif->getFloat(); + mDelta = nif->getFloat(); + } mSources.read(nif); } diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 41dd14fac..bf043fbdb 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -118,6 +118,7 @@ class NiLookAtController : public Controller { public: NodePtr target; + unsigned short lookAtFlags{0}; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nif/data.cpp b/components/nif/data.cpp index afb304bad..e76541d5c 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -33,13 +33,33 @@ void NiSkinInstance::post(NIFFile *nif) void NiGeometryData::read(NIFStream *nif) { + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,114)) + nif->getInt(); // Group ID. (Almost?) always 0. + int verts = nif->getUShort(); + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + nif->skip(2); // Keep flags and compress flags + if (nif->getBoolean()) nif->getVector3s(vertices, verts); + unsigned int dataFlags = 0; + if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) + dataFlags = nif->getUShort(); + + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + nif->getUInt(); // Material CRC + if (nif->getBoolean()) + { nif->getVector3s(normals, verts); + if (dataFlags & 0x1000) + { + nif->getVector3s(tangents, verts); + nif->getVector3s(bitangents, verts); + } + } center = nif->getVector3(); radius = nif->getFloat(); @@ -47,14 +67,27 @@ void NiGeometryData::read(NIFStream *nif) if (nif->getBoolean()) nif->getVector4s(colors, verts); - // In Morrowind this field only corresponds to the number of UV sets. - // NifTools research is inaccurate. - int uvs = nif->getUShort(); + // Only the first 6 bits are used as a count. I think the rest are + // flags of some sort. + unsigned int numUVs = dataFlags; + if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) + { + numUVs = nif->getUShort(); + // In Morrowind this field only corresponds to the number of UV sets. + // NifTools research is inaccurate. + if (nif->getVersion() > NIFFile::NIFVersion::VER_MW) + numUVs &= 0x3f; + } + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0) + numUVs &= 0x1; - if(nif->getInt()) + bool hasUVs = true; + if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) + hasUVs = nif->getBoolean(); + if (hasUVs) { - uvlist.resize(uvs); - for(int i = 0;i < uvs;i++) + uvlist.resize(numUVs); + for (unsigned int i = 0; i < numUVs; i++) { nif->getVector2s(uvlist[i], verts); // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin @@ -64,6 +97,12 @@ void NiGeometryData::read(NIFStream *nif) } } } + + if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) + nif->getUShort(); // Consistency flags + + if (nif->getVersion() >= NIFStream::generateVersion(20,0,0,4)) + nif->skip(4); // Additional data } void NiTriShapeData::read(NIFStream *nif) @@ -75,13 +114,17 @@ void NiTriShapeData::read(NIFStream *nif) // We have three times as many vertices as triangles, so this // is always equal to tris*3. int cnt = nif->getInt(); - nif->getUShorts(triangles, cnt); + bool hasTriangles = true; + if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) + hasTriangles = nif->getBoolean(); + if (hasTriangles) + nif->getUShorts(triangles, cnt); // Read the match list, which lists the vertices that are equal to // vertices. We don't actually need need this for anything, so // just skip it. - int verts = nif->getUShort(); - for(int i=0;i < verts;i++) + unsigned short verts = nif->getUShort(); + for (unsigned short i=0; i < verts; i++) { // Number of vertices matching vertex 'i' int num = nif->getUShort(); @@ -101,7 +144,11 @@ void NiTriStripsData::read(NIFStream *nif) std::vector lengths; nif->getUShorts(lengths, numStrips); - if (!numStrips) + // "Has Strips" flag. Exceptionally useful. + bool hasStrips = false; + if (nif->getVersion() > NIFFile::NIFVersion::VER_OB_OLD) + hasStrips = nif->getBoolean(); + if (!hasStrips || !numStrips) return; strips.resize(numStrips); @@ -140,27 +187,37 @@ void NiAutoNormalParticlesData::read(NIFStream *nif) NiGeometryData::read(nif); // Should always match the number of vertices - numParticles = nif->getUShort(); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) + numParticles = nif->getUShort(); - particleRadius = nif->getFloat(); + if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) + std::fill(particleRadii.begin(), particleRadii.end(), nif->getFloat()); + else if (nif->getBoolean()) + nif->getFloats(particleRadii, vertices.size()); activeCount = nif->getUShort(); + // Particle sizes if (nif->getBoolean()) - { - // Particle sizes nif->getFloats(sizes, vertices.size()); + + if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0) && nif->getBoolean()) + nif->getQuaternions(rotations, vertices.size()); + if (nif->getVersion() >= NIFStream::generateVersion(20,0,0,4)) + { + if (nif->getBoolean()) + nif->getFloats(rotationAngles, vertices.size()); + if (nif->getBoolean()) + nif->getVector3s(rotationAxes, vertices.size()); } + } void NiRotatingParticlesData::read(NIFStream *nif) { NiAutoNormalParticlesData::read(nif); - if (nif->getBoolean()) - { - // Rotation quaternions. + if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0) && nif->getBoolean()) nif->getQuaternions(rotations, vertices.size()); - } } void NiPosData::read(NIFStream *nif) @@ -188,12 +245,27 @@ void NiPixelData::read(NIFStream *nif) { fmt = (Format)nif->getUInt(); - for (unsigned int i = 0; i < 4; ++i) - colorMask[i] = nif->getUInt(); - bpp = nif->getUInt(); + if (nif->getVersion() < NIFStream::generateVersion(10,4,0,2)) + { + for (unsigned int i = 0; i < 4; ++i) + colorMask[i] = nif->getUInt(); + bpp = nif->getUInt(); + nif->skip(8); // "Old Fast Compare". Whatever that means. + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + pixelTiling = nif->getUInt(); + } + else // TODO: see if anything from here needs to be implemented + { + bpp = nif->getChar(); + nif->skip(4); // Renderer hint + nif->skip(4); // Extra data + nif->skip(4); // Flags + pixelTiling = nif->getUInt(); + if (nif->getVersion() >= NIFStream::generateVersion(20,3,0,4)) + sRGB = nif->getBoolean(); + nif->skip(4*10); // Channel data + } - // 8 bytes of "Old Fast Compare". Whatever that means. - nif->skip(8); palette.read(nif); numberOfMipmaps = nif->getUInt(); @@ -213,8 +285,10 @@ void NiPixelData::read(NIFStream *nif) // Read the data unsigned int numPixels = nif->getUInt(); - if (numPixels) - nif->getUChars(data, numPixels); + bool hasFaces = nif->getVersion() >= NIFStream::generateVersion(10,4,0,2); + unsigned int numFaces = hasFaces ? nif->getUInt() : 1; + if (numPixels && numFaces) + nif->getUChars(data, numPixels * numFaces); } void NiPixelData::post(NIFFile *nif) @@ -249,6 +323,10 @@ void NiSkinData::read(NIFStream *nif) if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,1,0,0)) nif->skip(4); // NiSkinPartition link + // Has vertex weights flag + if (nif->getVersion() > NIFStream::generateVersion(4,2,1,0) && !nif->getBoolean()) + return; + bones.resize(boneNum); for (BoneInfo &bi : bones) { @@ -272,7 +350,7 @@ void NiMorphData::read(NIFStream *nif) { int morphCount = nif->getInt(); int vertCount = nif->getInt(); - /*relative targets?*/nif->getChar(); + nif->getChar(); // Relative targets, always 1 mMorphs.resize(morphCount); for(int i = 0;i < morphCount;i++) @@ -290,7 +368,8 @@ void NiKeyframeData::read(NIFStream *nif) if(mRotations->mInterpolationType == InterpolationType_XYZ) { //Chomp unused float - nif->getFloat(); + if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,0)) + nif->getFloat(); mXRotations = std::make_shared(); mYRotations = std::make_shared(); mZRotations = std::make_shared(); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 66b3f693a..1519bd1b3 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -35,7 +35,7 @@ namespace Nif class NiGeometryData : public Record { public: - std::vector vertices, normals; + std::vector vertices, normals, tangents, bitangents; std::vector colors; std::vector< std::vector > uvlist; osg::Vec3f center; @@ -73,13 +73,15 @@ struct NiLinesData : public NiGeometryData class NiAutoNormalParticlesData : public NiGeometryData { public: - int numParticles; + int numParticles{0}; float particleRadius; int activeCount; - std::vector sizes; + std::vector particleRadii, sizes, rotationAngles; + std::vector rotations; + std::vector rotationAxes; void read(NIFStream *nif); }; @@ -87,8 +89,6 @@ public: class NiRotatingParticlesData : public NiAutoNormalParticlesData { public: - std::vector rotations; - void read(NIFStream *nif); }; @@ -133,7 +133,8 @@ public: Format fmt; unsigned int colorMask[4]; - unsigned int bpp; + unsigned int bpp, pixelTiling{0}; + bool sRGB{false}; NiPalettePtr palette; unsigned int numberOfMipmaps; diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index 7947e301d..c12eb6c1b 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -28,6 +28,10 @@ void NiTextureEffect::read(NIFStream *nif) // Texture Filtering nif->skip(4); + // Max anisotropy samples + if (nif->getVersion() >= NIFStream::generateVersion(20,5,0,4)) + nif->skip(2); + clamp = nif->getUInt(); textureType = (TextureType)nif->getUInt(); @@ -36,14 +40,12 @@ void NiTextureEffect::read(NIFStream *nif) texture.read(nif); - /* - byte = 0 - vector4 = [1,0,0,0] - short = 0 - short = -75 - short = 0 - */ - nif->skip(23); + nif->skip(1); // Use clipping plane + nif->skip(16); // Clipping plane dimensions vector + if (nif->getVersion() <= NIFStream::generateVersion(10,2,0,0)) + nif->skip(4); // PS2-specific shorts + if (nif->getVersion() <= NIFStream::generateVersion(4,1,0,12)) + nif->skip(2); // Unknown short } void NiTextureEffect::post(NIFFile *nif) diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 453e4b04c..818df90a2 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -34,6 +34,9 @@ struct NiDynamicEffect : public Node void read(NIFStream *nif) { Node::read(nif); + if (nif->getVersion() >= nif->generateVersion(10,1,0,106) + && nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) + nif->getBoolean(); // Switch state unsigned int numAffectedNodes = nif->getUInt(); for (unsigned int i=0; igetUInt(); // ref to another Node diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 118a58299..31c959964 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -139,15 +139,77 @@ void NIFFile::parse(Files::IStreamPtr stream) // It's not used by Morrowind assets but Morrowind supports it. if(ver != NIFStream::generateVersion(4,0,0,0) && ver != VER_MW) fail("Unsupported NIF version: " + printVersion(ver)); + + // NIF data endianness + if (ver >= NIFStream::generateVersion(20,0,0,4)) + { + unsigned char endianness = nif.getChar(); + if (endianness == 0) + fail("Big endian NIF files are unsupported"); + } + + // User version + if (ver > NIFStream::generateVersion(10,0,1,8)) + userVer = nif.getUInt(); + // Number of records size_t recNum = nif.getUInt(); records.resize(recNum); + // Bethesda stream header + // It contains Bethesda format version and (useless) export information + if (ver == VER_OB_OLD || + (userVer >= 3 && ((ver == VER_OB || ver == VER_BGS) + || (ver >= NIFStream::generateVersion(10,1,0,0) && ver <= NIFStream::generateVersion(20,0,0,4) && userVer <= 11)))) + { + bethVer = nif.getUInt(); + nif.getExportString(); // Author + if (bethVer > BETHVER_FO4) + nif.getUInt(); // Unknown + nif.getExportString(); // Process script + nif.getExportString(); // Export script + if (bethVer == BETHVER_FO4) + nif.getExportString(); // Max file path + } + + std::vector recTypes; + std::vector recTypeIndices; + + const bool hasRecTypeListings = ver >= NIFStream::generateVersion(5,0,0,1); + if (hasRecTypeListings) + { + unsigned short recTypeNum = nif.getUShort(); + if (recTypeNum) // Record type list + nif.getSizedStrings(recTypes, recTypeNum); + if (recNum) // Record type mapping for each record + nif.getUShorts(recTypeIndices, recNum); + if (ver >= NIFStream::generateVersion(5,0,0,6)) // Groups + { + if (ver >= NIFStream::generateVersion(20,1,0,1)) // String table + { + if (ver >= NIFStream::generateVersion(20,2,0,5) && recNum) // Record sizes + { + std::vector recSizes; // Currently unused + nif.getUInts(recSizes, recNum); + } + unsigned int stringNum = nif.getUInt(); + nif.getUInt(); // Max string length + if (stringNum) + nif.getSizedStrings(strings, stringNum); + } + std::vector groups; // Currently unused + unsigned int groupNum = nif.getUInt(); + if (groupNum) + nif.getUInts(groups, groupNum); + } + } + + const bool hasRecordSeparators = ver >= NIFStream::generateVersion(10,0,0,0) && ver < NIFStream::generateVersion(10,2,0,0); for(size_t i = 0;i < recNum;i++) { Record *r = nullptr; - std::string rec = nif.getString(); + std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString(); if(rec.empty()) { std::stringstream error; @@ -155,6 +217,17 @@ void NIFFile::parse(Files::IStreamPtr stream) fail(error.str()); } + // Record separator. Some Havok records in Oblivion do not have it. + if (hasRecordSeparators && rec.compare(0, 3, "bhk")) + { + if (nif.getInt()) + { + std::stringstream warning; + warning << "Record number " << i << " out of " << recNum << " is preceded by a non-zero separator."; + warn(warning.str()); + } + } + std::map::const_iterator entry = factories.find(rec); if (entry != factories.end()) diff --git a/components/nif/nifstream.cpp b/components/nif/nifstream.cpp index 44be4b241..69f1a905b 100644 --- a/components/nif/nifstream.cpp +++ b/components/nif/nifstream.cpp @@ -25,18 +25,19 @@ namespace Nif return t; } - ///Currently specific for 4.0.0.2 and earlier + ///Booleans in 4.0.0.2 (Morrowind format) and earlier are 4 byte, while in 4.1.0.0+ they're 1 byte. bool NIFStream::getBoolean() { - return getInt() != 0; + return getVersion() < generateVersion(4,1,0,0) ? getInt() != 0 : getChar() != 0; } - ///Read in a string, either from the string table using the index (currently absent) or from the stream using the specified length + ///Read in a string, either from the string table using the index or from the stream using the specified length std::string NIFStream::getString() { - return getSizedString(); + return getVersion() < generateVersion(20,1,0,1) ? getSizedString() : file->getString(getUInt()); } + // Convenience utility functions: get the versions of the currently read file unsigned int NIFStream::getVersion() const { return file->getVersion(); } unsigned int NIFStream::getUserVersion() const { return file->getBethVersion(); } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index e605df32a..4c57b2e81 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -30,7 +30,7 @@ public: PropertyList props; // Bounding box info - bool hasBounds; + bool hasBounds{false}; osg::Vec3f boundPos; Matrix3 boundRot; osg::Vec3f boundXYZ; // Box size @@ -39,12 +39,15 @@ public: { Named::read(nif); - flags = nif->getUShort(); + flags = nif->getBethVersion() <= 26 ? nif->getUShort() : nif->getUInt(); trafo = nif->getTrafo(); - velocity = nif->getVector3(); - props.read(nif); + if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) + velocity = nif->getVector3(); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + props.read(nif); - hasBounds = nif->getBoolean(); + if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) + hasBounds = nif->getBoolean(); if(hasBounds) { nif->getInt(); // always 1 @@ -52,6 +55,9 @@ public: boundRot = nif->getMatrix3(); boundXYZ = nif->getVector3(); } + // Reference to the collision object in Gamebryo files. + if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) + nif->skip(4); parent = nullptr; @@ -102,7 +108,8 @@ struct NiNode : Node { Node::read(nif); children.read(nif); - effects.read(nif); + if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) + effects.read(nif); // Discard transformations for the root node, otherwise some meshes // occasionally get wrong orientation. Only for NiNode-s for now, but @@ -130,7 +137,39 @@ struct NiNode : Node struct NiGeometry : Node { + struct MaterialData + { + std::vector materialNames; + std::vector materialExtraData; + unsigned int activeMaterial{0}; + bool materialNeedsUpdate{false}; + void read(NIFStream *nif) + { + if (nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) + return; + unsigned int numMaterials = 0; + if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,3)) + numMaterials = nif->getBoolean(); // Has Shader + else if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) + numMaterials = nif->getUInt(); + if (numMaterials) + { + nif->getStrings(materialNames, numMaterials); + nif->getInts(materialExtraData, numMaterials); + } + if (nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) + activeMaterial = nif->getUInt(); + if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) + { + materialNeedsUpdate = nif->getBoolean(); + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + nif->skip(8); + } + } + }; + NiSkinInstancePtr skin; + MaterialData materialData; }; struct NiTriShape : NiGeometry @@ -149,6 +188,7 @@ struct NiTriShape : NiGeometry Node::read(nif); data.read(nif); skin.read(nif); + materialData.read(nif); } void post(NIFFile *nif) @@ -170,6 +210,7 @@ struct NiTriStrips : NiGeometry Node::read(nif); data.read(nif); skin.read(nif); + materialData.read(nif); } void post(NIFFile *nif) @@ -207,6 +248,8 @@ struct NiCamera : Node { struct Camera { + unsigned short cameraFlags{0}; + // Camera frustrum float left, right, top, bottom, nearDist, farDist; @@ -216,15 +259,21 @@ struct NiCamera : Node // Level of detail modifier float LOD; + // Orthographic projection usage flag + bool orthographic{false}; + void read(NIFStream *nif) { + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + cameraFlags = nif->getUShort(); left = nif->getFloat(); right = nif->getFloat(); top = nif->getFloat(); bottom = nif->getFloat(); nearDist = nif->getFloat(); farDist = nif->getFloat(); - + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + orthographic = nif->getBoolean(); vleft = nif->getFloat(); vright = nif->getFloat(); vtop = nif->getFloat(); @@ -243,6 +292,8 @@ struct NiCamera : Node nif->getInt(); // -1 nif->getInt(); // 0 + if (nif->getVersion() >= NIFStream::generateVersion(4,2,1,0)) + nif->getInt(); // 0 } }; @@ -285,11 +336,14 @@ struct NiRotatingParticles : Node // A node used as the base to switch between child nodes, such as for LOD levels. struct NiSwitchNode : public NiNode { + unsigned int switchFlags{0}; unsigned int initialIndex; void read(NIFStream *nif) { NiNode::read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + switchFlags = nif->getUShort(); initialIndex = nif->getUInt(); } }; @@ -310,6 +364,12 @@ struct NiLODNode : public NiSwitchNode NiSwitchNode::read(nif); if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,0,1,0)) lodCenter = nif->getVector3(); + else if (nif->getVersion() > NIFStream::generateVersion(10,0,1,0)) + { + nif->skip(4); // NiLODData, unsupported at the moment + return; + } + unsigned int numLodLevels = nif->getUInt(); for (unsigned int i=0; igetUShort(); -} - void NiTexturingProperty::Texture::read(NIFStream *nif) { inUse = nif->getBoolean(); if(!inUse) return; texture.read(nif); - clamp = nif->getUInt(); - nif->skip(4); // Filter mode. Ignoring because global filtering settings are more sensible - uvSet = nif->getUInt(); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + { + clamp = nif->getInt(); + nif->skip(4); // Filter mode. Ignoring because global filtering settings are more sensible + } + else + { + clamp = nif->getUShort() & 0xF; + } + // Max anisotropy. I assume we'll always only use the global anisotropy setting. + if (nif->getVersion() >= NIFStream::generateVersion(20,5,0,4)) + nif->getUShort(); + + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + uvSet = nif->getUInt(); // Two PS2-specific shorts. - nif->skip(4); - nif->skip(2); // Unknown short + if (nif->getVersion() < NIFStream::generateVersion(10,4,0,2)) + nif->skip(4); + if (nif->getVersion() <= NIFStream::generateVersion(4,1,0,18)) + nif->skip(2); // Unknown short + else if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0)) + { + if (nif->getBoolean()) // Has texture transform + { + nif->getVector2(); // UV translation + nif->getVector2(); // UV scale + nif->getFloat(); // W axis rotation + nif->getUInt(); // Transform method + nif->getVector2(); // Texture rotation origin + } + } } void NiTexturingProperty::Texture::post(NIFFile *nif) @@ -35,7 +54,10 @@ void NiTexturingProperty::Texture::post(NIFFile *nif) void NiTexturingProperty::read(NIFStream *nif) { Property::read(nif); - apply = nif->getUInt(); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD || nif->getVersion() >= NIFStream::generateVersion(20,1,0,2)) + flags = nif->getUShort(); + if (nif->getVersion() <= NIFStream::generateVersion(20,1,0,1)) + apply = nif->getUInt(); unsigned int numTextures = nif->getUInt(); @@ -51,32 +73,53 @@ void NiTexturingProperty::read(NIFStream *nif) envMapLumaBias = nif->getVector2(); bumpMapMatrix = nif->getVector4(); } + else if (i == 7 && textures[7].inUse && nif->getVersion() >= NIFStream::generateVersion(20,2,0,5)) + /*float parallaxOffset = */nif->getFloat(); + } + + if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) + { + unsigned int numShaderTextures = nif->getUInt(); + shaderTextures.resize(numShaderTextures); + for (unsigned int i = 0; i < numShaderTextures; i++) + { + shaderTextures[i].read(nif); + if (shaderTextures[i].inUse) + nif->getUInt(); // Unique identifier + } } } void NiTexturingProperty::post(NIFFile *nif) { Property::post(nif); - for(int i = 0;i < 7;i++) + for (size_t i = 0; i < textures.size(); i++) textures[i].post(nif); + for (size_t i = 0; i < shaderTextures.size(); i++) + shaderTextures[i].post(nif); } void NiFogProperty::read(NIFStream *nif) { Property::read(nif); - + mFlags = nif->getUShort(); mFogDepth = nif->getFloat(); mColour = nif->getVector3(); } void S_MaterialProperty::read(NIFStream *nif) { - ambient = nif->getVector3(); - diffuse = nif->getVector3(); + if (nif->getBethVersion() < 26) + { + ambient = nif->getVector3(); + diffuse = nif->getVector3(); + } specular = nif->getVector3(); emissive = nif->getVector3(); glossiness = nif->getFloat(); alpha = nif->getFloat(); + if (nif->getBethVersion() > 21) + emissive *= nif->getFloat(); } void S_VertexColorProperty::read(NIFStream *nif) @@ -92,14 +135,29 @@ void S_AlphaProperty::read(NIFStream *nif) 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(); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + { + enabled = nif->getChar(); + compareFunc = nif->getInt(); + stencilRef = nif->getUInt(); + stencilMask = nif->getUInt(); + failAction = nif->getInt(); + zFailAction = nif->getInt(); + zPassAction = nif->getInt(); + drawMode = nif->getInt(); + } + else + { + unsigned short flags = nif->getUShort(); + enabled = flags & 0x1; + failAction = (flags >> 1) & 0x7; + zFailAction = (flags >> 4) & 0x7; + zPassAction = (flags >> 7) & 0x7; + drawMode = (flags >> 10) & 0x3; + compareFunc = (flags >> 12) & 0x7; + stencilRef = nif->getUInt(); + stencilMask = nif->getUInt(); + } } diff --git a/components/nif/property.hpp b/components/nif/property.hpp index c72dbf6ba..e20d948f0 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -29,18 +29,13 @@ namespace Nif { -class Property : public Named -{ -public: - // The meaning of these depends on the actual property type. - unsigned int flags; - - void read(NIFStream *nif); -}; +class Property : public Named { }; class NiTexturingProperty : public Property { public: + unsigned short flags{0u}; + // A sub-texture struct Texture { @@ -92,6 +87,7 @@ public: }; std::vector textures; + std::vector shaderTextures; osg::Vec2f envMapLumaBias; osg::Vec4f bumpMapMatrix; @@ -103,28 +99,81 @@ public: class NiFogProperty : public Property { public: + unsigned short mFlags; float mFogDepth; osg::Vec3f mColour; void read(NIFStream *nif); }; -// These contain no other data than the 'flags' field in Property -class NiShadeProperty : public Property { }; -class NiDitherProperty : public Property { }; -class NiZBufferProperty : public Property { }; -class NiSpecularProperty : public Property { }; -class NiWireframeProperty : public Property { }; +// These contain no other data than the 'flags' field +struct NiShadeProperty : public Property +{ + unsigned short flags{0u}; + void read(NIFStream *nif) + { + Property::read(nif); + if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) + flags = nif->getUShort(); + } +}; + +struct NiDitherProperty : public Property +{ + unsigned short flags; + void read(NIFStream* nif) + { + Property::read(nif); + flags = nif->getUShort(); + } +}; + +struct NiZBufferProperty : public Property +{ + unsigned short flags; + unsigned int testFunction; + void read(NIFStream *nif) + { + Property::read(nif); + flags = nif->getUShort(); + testFunction = (flags >> 2) & 0x7; + if (nif->getVersion() >= NIFStream::generateVersion(4,1,0,12) && nif->getVersion() <= NIFFile::NIFVersion::VER_OB) + testFunction = nif->getUInt(); + } +}; + +struct NiSpecularProperty : public Property +{ + unsigned short flags; + void read(NIFStream* nif) + { + Property::read(nif); + flags = nif->getUShort(); + } +}; + +struct NiWireframeProperty : public Property +{ + unsigned short flags; + void read(NIFStream* nif) + { + Property::read(nif); + flags = nif->getUShort(); + } +}; + // The rest are all struct-based template struct StructPropT : Property { T data; + unsigned short flags; void read(NIFStream *nif) { Property::read(nif); + flags = nif->getUShort(); data.read(nif); } }; @@ -132,7 +181,8 @@ struct StructPropT : Property struct S_MaterialProperty { // The vector components are R,G,B - osg::Vec3f ambient, diffuse, specular, emissive; + osg::Vec3f ambient{1.f,1.f,1.f}, diffuse{1.f,1.f,1.f}; + osg::Vec3f specular, emissive; float glossiness, alpha; void read(NIFStream *nif); @@ -246,9 +296,35 @@ struct S_StencilProperty }; class NiAlphaProperty : public StructPropT { }; -class NiMaterialProperty : public StructPropT { }; class NiVertexColorProperty : public StructPropT { }; -class NiStencilProperty : public StructPropT { }; +struct NiStencilProperty : public Property +{ + S_StencilProperty data; + unsigned short flags{0u}; + + void read(NIFStream *nif) + { + Property::read(nif); + if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) + flags = nif->getUShort(); + data.read(nif); + } +}; + +struct NiMaterialProperty : public Property +{ + S_MaterialProperty data; + unsigned short flags{0u}; + + void read(NIFStream *nif) + { + Property::read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3,0,0,0) + && nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) + flags = nif->getUShort(); + data.read(nif); + } +}; } // Namespace #endif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f9274cebf..61a276dc1 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1732,7 +1732,7 @@ namespace NifOsg osg::StateSet* stateset = node->getOrCreateStateSet(); // Specular lighting is enabled by default, but there's a quirk... - int specFlags = 1; + bool specEnabled = true; osg::ref_ptr mat (new osg::Material); mat->setColorMode(hasVertexColors ? osg::Material::AMBIENT_AND_DIFFUSE : osg::Material::OFF); @@ -1751,7 +1751,8 @@ namespace NifOsg case Nif::RC_NiSpecularProperty: { // Specular property can turn specular lighting off. - specFlags = property->flags; + auto specprop = static_cast(property); + specEnabled = specprop->flags & 1; break; } case Nif::RC_NiMaterialProperty: @@ -1835,7 +1836,7 @@ namespace NifOsg } // While NetImmerse and Gamebryo support specular lighting, Morrowind has its support disabled. - if (mVersion <= Nif::NIFFile::NIFVersion::VER_MW || specFlags == 0) + if (mVersion <= Nif::NIFFile::NIFVersion::VER_MW || !specEnabled) mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f,0.f,0.f,0.f)); if (lightmode == 0)