diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index 7e413d03cd..d74ba3ec39 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -35,8 +35,8 @@ namespace Nif::Testing inline void init(NiGeometry& value) { init(static_cast(value)); - value.data = NiGeometryDataPtr(nullptr); - value.skin = NiSkinInstancePtr(nullptr); + value.mData = NiGeometryDataPtr(nullptr); + value.mSkinInstance = NiSkinInstancePtr(nullptr); } inline void init(NiTriShape& value) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 25df366d23..1461f71dc7 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -326,20 +326,20 @@ namespace mNiTriShapeData.vertices = { osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0) }; mNiTriShapeData.mNumTriangles = 1; mNiTriShapeData.triangles = { 0, 1, 2 }; - mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); + mNiTriShape.mData = Nif::NiGeometryDataPtr(&mNiTriShapeData); mNiTriShapeData2.recType = Nif::RC_NiTriShapeData; mNiTriShapeData2.vertices = { osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1) }; mNiTriShapeData2.mNumTriangles = 1; mNiTriShapeData2.triangles = { 0, 1, 2 }; - mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2); + mNiTriShape2.mData = Nif::NiGeometryDataPtr(&mNiTriShapeData2); mNiTriStripsData.recType = Nif::RC_NiTriStripsData; mNiTriStripsData.vertices = { osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0), osg::Vec3f(0, 1, 0) }; mNiTriStripsData.mNumTriangles = 2; mNiTriStripsData.strips = { { 0, 1, 2, 3 } }; - mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); + mNiTriStrips.mData = Nif::NiGeometryDataPtr(&mNiTriStripsData); } }; @@ -716,7 +716,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_and_not_empty_skin_should_return_static_shape) { - mNiTriShape.skin = Nif::NiSkinInstancePtr(&mNiSkinInstance); + mNiTriShape.mSkinInstance = Nif::NiSkinInstancePtr(&mNiSkinInstance); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); @@ -959,7 +959,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) { - mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr); + mNiTriShape.mData = Nif::NiGeometryDataPtr(nullptr); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); @@ -977,7 +977,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape) { - auto data = static_cast(mNiTriShape.data.getPtr()); + auto data = static_cast(mNiTriShape.mData.getPtr()); data->triangles.clear(); mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape) })); @@ -1111,7 +1111,7 @@ namespace init(niTriShape); init(emptyCollisionNode); - niTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); + niTriShape.mData = Nif::NiGeometryDataPtr(&mNiTriShapeData); niTriShape.parents.push_back(&mNiNode); emptyCollisionNode.recType = Nif::RC_RootCollisionNode; @@ -1210,7 +1210,7 @@ namespace TEST_F(TestBulletNifLoader, should_ignore_tri_shape_data_with_mismatching_data_rec_type) { - mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); + mNiTriShape.mData = Nif::NiGeometryDataPtr(&mNiTriStripsData); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiTriShape); @@ -1245,7 +1245,7 @@ namespace TEST_F(TestBulletNifLoader, should_ignore_tri_strips_data_with_mismatching_data_rec_type) { - mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); + mNiTriStrips.mData = Nif::NiGeometryDataPtr(&mNiTriShapeData); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiTriStrips); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index cb3b74dc8b..5c408461bc 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -103,7 +103,7 @@ add_component_dir (sceneutil ) add_component_dir (nif - controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream physics + controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream physics particle ) add_component_dir (nifosg diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 9e93b31162..bf758e9873 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -3,6 +3,7 @@ #include "controlled.hpp" #include "data.hpp" #include "node.hpp" +#include "particle.hpp" #include "recordptr.hpp" namespace Nif diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 018e9fc603..fd0b2b9fc6 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -46,29 +46,47 @@ namespace Nif void NiGeometryData::read(NIFStream* nif) { + bool isBS202 = nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0; if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 114)) nif->getInt(); // Group ID. (Almost?) always 0. - int verts = nif->getUShort(); + unsigned short verts = 0; + if ((nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO3 && NiPSysDataFlag) || !NiPSysDataFlag) + { + nif->read(verts); + } + + unsigned short BSMaxVertices = 0; + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && NiPSysDataFlag + && nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO3) + { + nif->read(BSMaxVertices); + } if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) nif->skip(2); // Keep flags and compress flags - if (nif->getBoolean()) + bool hasVertices = true; + hasVertices = nif->getBoolean(); + if (hasVertices) nif->getVector3s(vertices, verts); - unsigned int dataFlags = 0; - if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) - dataFlags = nif->getUShort(); + unsigned short NiDataFlags = 0, BSDataFlags = 0; + if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0) && !isBS202) + nif->read(NiDataFlags); + if (isBS202) + nif->read(BSDataFlags); if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) nif->getUInt(); // Material CRC - if (nif->getBoolean()) + bool hasNormals; + hasNormals = nif->getBoolean(); + if (hasNormals) { nif->getVector3s(normals, verts); - if (dataFlags & 0x1000) + if ((NiDataFlags | BSDataFlags) & 0x1000) { nif->getVector3s(tangents, verts); nif->getVector3s(bitangents, verts); @@ -78,41 +96,36 @@ namespace Nif center = nif->getVector3(); radius = nif->getFloat(); - if (nif->getBoolean()) + bool hasVertexColors; + hasVertexColors = nif->getBoolean(); + if (hasVertexColors) nif->getVector4s(colors, verts); - unsigned int numUVs = dataFlags; if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0)) - numUVs = nif->getUShort(); + nif->read(NiDataFlags); + unsigned short numUVs = (NiDataFlags & 0x3F) | (BSDataFlags & 0x1); // In Morrowind this field only corresponds to the number of UV sets. // In later games only the first 6 bits are used as a count and the rest are flags. - if (nif->getVersion() > NIFFile::NIFVersion::VER_MW) - { - numUVs &= 0x3f; - if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0) - numUVs &= 0x1; - } - bool hasUVs = true; if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) - hasUVs = nif->getBoolean(); - if (hasUVs) + // nif->read(hasUVs); + nif->getBoolean(); // hasUVs + + uvlist.resize(numUVs); + for (unsigned int i = 0; i < numUVs; i++) { - uvlist.resize(numUVs); - for (unsigned int i = 0; i < numUVs; i++) + nif->getVector2s(uvlist[i], verts); + for (auto& uv : uvlist[i]) { - nif->getVector2s(uvlist[i], verts); // flip the texture coordinates to convert them to the OpenGL convention of bottom-left image origin - for (unsigned int uv = 0; uv < uvlist[i].size(); ++uv) - { - uvlist[i][uv] = osg::Vec2f(uvlist[i][uv].x(), 1.f - uvlist[i][uv].y()); - } + uv = osg::Vec2f(uv.x(), 1.f - uv.y()); } } + unsigned short consistencyFlag; if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) - nif->getUShort(); // Consistency flags + nif->read(consistencyFlag); if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) nif->skip(4); // Additional data @@ -197,43 +210,6 @@ namespace Nif } } - void NiParticlesData::read(NIFStream* nif) - { - NiGeometryData::read(nif); - - // Should always match the number of vertices - if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) - numParticles = nif->getUShort(); - - 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()) - 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) - { - NiParticlesData::read(nif); - - if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0) && nif->getBoolean()) - nif->getQuaternions(rotations, vertices.size()); - } - void NiPosData::read(NIFStream* nif) { mKeyList = std::make_shared(); @@ -561,5 +537,4 @@ namespace Nif mCenter = nif->getVector3(); mRadius = nif->getFloat(); } - } // Namespace diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 6f0ca25237..c90607c6bc 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -27,6 +27,7 @@ #include "nifkey.hpp" #include "niftypes.hpp" // Transformation #include "recordptr.hpp" +#include #include namespace Nif @@ -41,6 +42,8 @@ namespace Nif osg::Vec3f center; float radius; + bool NiPSysDataFlag = false; + void read(NIFStream* nif) override; }; @@ -76,24 +79,6 @@ namespace Nif void read(NIFStream* nif) override; }; - struct NiParticlesData : public NiGeometryData - { - int numParticles{ 0 }; - - int activeCount{ 0 }; - - std::vector particleRadii, sizes, rotationAngles; - std::vector rotations; - std::vector rotationAxes; - - void read(NIFStream* nif) override; - }; - - struct NiRotatingParticlesData : public NiParticlesData - { - void read(NIFStream* nif) override; - }; - struct NiPosData : public Record { Vector3KeyMapPtr mKeyList; @@ -329,6 +314,5 @@ namespace Nif void read(NIFStream* nif) override; }; - } // Namespace #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index c75d3f059f..e9b0a38cc8 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -17,6 +17,7 @@ #include "exception.hpp" #include "extra.hpp" #include "node.hpp" +#include "particle.hpp" #include "physics.hpp" #include "property.hpp" @@ -217,6 +218,26 @@ namespace Nif { "BSLightingShaderPropertyColorController", &construct }, { "BSBehaviorGraphExtraData", &construct }, + { "NiPSysData", &construct }, + { "NiParticleSystem", &construct }, + { "NiPSysEmitterCtlr", &construct }, + { "NiPSysUpdateCtlr", &construct }, + { "NiPSysAgeDeathModifier", &construct }, + { "BSPSysLODModifier", &construct }, + { "NiPSysCylinderEmitter", &construct }, + { "NiPSysSpawnModifier", &construct }, + { "BSPSysSimpleColorModifier", &construct }, + { "NiPSysRotationModifier", &construct }, + { "BSPSysScaleModifier", &construct }, + { "NiPSysGravityModifier", &construct }, + { "NiPSysPositionModifier", &construct }, + { "NiPSysBoundUpdateModifier", &construct }, + { "NiPSysModifierActiveCtlr", &construct }, + { "NiPSysMeshEmitter", &construct }, + { "BSPSysInheritVelocityModifier", + &construct }, + { "NiPSysBombModifier", &construct }, + { "NiPSysDragModifier", &construct }, }; } diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 500bb532da..0fc1cee608 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -88,6 +88,7 @@ namespace Nif data = readLittleEndianType(inp); } + void read(osg::Vec2f& data) { readLittleEndianBufferOfType<2, float>(inp, data._v); } void read(osg::Vec3f& data) { readLittleEndianBufferOfType<3, float>(inp, data._v); } void read(osg::Vec4f& data) { readLittleEndianBufferOfType<4, float>(inp, data._v); } diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 7e19c9d74f..a811d3fab5 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -171,25 +171,48 @@ namespace Nif void NiGeometry::read(NIFStream* nif) { Node::read(nif); - data.read(nif); - skin.read(nif); - material.read(nif); + + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && NiParticleSystemFlag) + { + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_SSE) + mBoundingVolume.read(nif); + + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) + nif->read(mBoundMinMax); + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_SSE) + mSkin.read(nif); + } + + bool SSE_flag = (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_SSE && !NiParticleSystemFlag + && nif->getVersion() == NIFFile::NIFVersion::VER_BGS); + if (nif->getBethVersion() < NIFFile::BethVersion::BETHVER_SSE || SSE_flag) + mData.read(nif); + if ((nif->getBethVersion() < NIFFile::BethVersion::BETHVER_SSE + && nif->getVersion() > NIFStream::generateVersion(3, 3, 0, 13)) + || SSE_flag) + mSkinInstance.read(nif); + if ((nif->getBethVersion() < NIFFile::BethVersion::BETHVER_SSE + && nif->getVersion() > NIFStream::generateVersion(10, 0, 1, 0)) + || SSE_flag) + material.read(nif); if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) { - shaderprop.read(nif); - alphaprop.read(nif); + mShaderProperty.read(nif); + mAlphaProperty.read(nif); } } void NiGeometry::post(Reader& nif) { Node::post(nif); - data.post(nif); - skin.post(nif); - shaderprop.post(nif); - alphaprop.post(nif); - if (recType != RC_NiParticles && !skin.empty()) + mData.post(nif); + mSkin.post(nif); + mSkinInstance.post(nif); + mShaderProperty.post(nif); + mAlphaProperty.post(nif); + if (recType != RC_NiParticles && !mSkinInstance.empty()) nif.setUseSkinning(true); } @@ -387,7 +410,9 @@ namespace Nif nif->read(mParticleDataSize); if (mParticleDataSize > 0) { - throw Nif::Exception("Unhandled Particle Data in BSTriShape: ", nif->getFile().getFilename()); + nif->getVector3s(mParticleVerts, vertNum); + // nif->readVector(mParticleNormals, vertNum); //Documentation seems to be wrong about this one + nif->readVector(mParticleTriangles, triNum * 3); } } } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 38ff36049c..81fbdf7f06 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_NIF_NODE_HPP #include +#include #include #include @@ -151,11 +152,17 @@ namespace Nif void read(NIFStream* nif); }; - NiGeometryDataPtr data; - NiSkinInstancePtr skin; + NiGeometryDataPtr mData; + NiSkinInstancePtr mSkinInstance; + RecordPtr mSkin; MaterialData material; - BSShaderPropertyPtr shaderprop; - NiAlphaPropertyPtr alphaprop; + BSShaderPropertyPtr mShaderProperty; + NiAlphaPropertyPtr mAlphaProperty; + + NiBoundingVolume::NiSphereBV mBoundingVolume; + float mBoundMinMax; + + bool NiParticleSystemFlag = false; void read(NIFStream* nif) override; void post(Reader& nif) override; @@ -175,9 +182,6 @@ namespace Nif struct NiLines : NiGeometry { }; - struct NiParticles : NiGeometry - { - }; struct NiCamera : Node { diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp new file mode 100644 index 0000000000..67448c3929 --- /dev/null +++ b/components/nif/particle.cpp @@ -0,0 +1,483 @@ +#include "particle.hpp" +#include + +namespace Nif +{ + void NiParticlesData::read(NIFStream* nif) + { + bool isBS202 = nif->getVersion() == NIFFile::NIFVersion::VER_BGS && nif->getBethVersion() > 0; + // if (nif->getVersion() < NIFFile::NIFVersion::VER_BGS) + NiGeometryData::read(nif); + + // Should always match the number of vertices + if (nif->getVersion() <= NIFFile::NIFVersion::VER_MW) + nif->read(numParticles); + + float particleRadius; + if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 0)) + { + nif->read(particleRadius); + particleRadii.resize(vertices.size()); + std::fill(particleRadii.begin(), particleRadii.end(), particleRadius); + } + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + { + bool hasRadii = nif->getBoolean(); + // nif->read(hasRadii); + if (hasRadii && !isBS202) + nif->readVector(particleRadii, vertices.size()); + } + activeCount = nif->getUShort(); + + // Particle sizes + bool hasSizes; + hasSizes = nif->getBoolean(); + if (hasSizes && !isBS202) + nif->readVector(sizes, vertices.size()); + + bool hasRotations = false; + if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) + { + hasRotations = nif->getBoolean(); + if (hasRotations && !isBS202) + { + nif->getQuaternions(mRotations, vertices.size()); + } + } + + if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 4)) + { + bool hasRotationAngles, hasRotationAxes; + + // nif->read(hasRotationAngles); + hasRotationAngles = nif->getBoolean(); + if (hasRotationAngles && !isBS202) + nif->readVector(mRotationAngles, vertices.size()); + + // nif->read(hasRotationAxes); + hasRotationAxes = nif->getBoolean(); + if (hasRotationAxes && !isBS202) + { + nif->getVector3s(mRotationAxes, vertices.size()); + } + } + + bool hasTexIndices = false; + if (isBS202) + nif->read(hasTexIndices); + + unsigned int subtexOffsetNum = 0; + if (nif->getVersion() == NIFFile::NIFVersion::VER_BGS) + { + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + nif->read(subtexOffsetNum); + else if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3 && isBS202) + subtexOffsetNum = nif->get(); + } + + if (isBS202) + nif->getVector4s(mSubtexOffsets, subtexOffsetNum); + + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3 + && nif->getVersion() == NIFFile::NIFVersion::VER_BGS) + { + nif->read(mAspectRatio); + + nif->read(mAspectFlags); + + nif->read(mAspect2); + nif->read(mSpeed1); + nif->read(mSpeed2); + } + } + + void NiRotatingParticlesData::read(NIFStream* nif) + { + NiParticlesData::read(nif); + + if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0) && nif->getBoolean()) + nif->getQuaternions(mRotations, vertices.size()); + } + + void NiParticleInfo::read(NIFStream* nif) + { + nif->read(mVelocity); + if (nif->getVersion() <= NIFStream::generateVersion(10, 4, 0, 1)) + nif->read(mRotation); + nif->read(mAge); + nif->read(mLifeSpan); + nif->read(mLastUpdate); + nif->read(mSpawnGen); + nif->read(mCode); + } + + void NiPSysData::read(NIFStream* nif) + { + bool isBS202 = nif->getVersion() == NIFStream::generateVersion(20, 2, 0, 7) && nif->getBethVersion() > 0; + NiPSysDataFlag = true; + NiParticlesData::read(nif); + + if (nif->getVersion() != NIFStream::generateVersion(20, 2, 0, 7)) + { + mParticleInfo.resize(vertices.size()); + for (unsigned long i = 0; i < vertices.size(); i++) + { + NiParticleInfo temp; + temp.read(nif); + mParticleInfo[i] = temp; + } + } + + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) + nif->skip(sizeof(float) * 3); + + if (nif->getVersion() == NIFStream::generateVersion(20, 2, 4, 7)) + nif->skip(1); + + if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 2)) + { + bool hasRotationSpeed; + nif->read(hasRotationSpeed); + if (hasRotationSpeed && !isBS202) + { + mRotationSpeeds.resize(vertices.size()); + nif->readVector(mRotationSpeeds, vertices.size()); + } + } + + if (!isBS202) + { + nif->read(mParticlesAddedNum); + nif->read(mParticlesBase); + } + + if (nif->getVersion() == NIFStream::generateVersion(20, 2, 4, 7)) + nif->skip(1); + } + + void NiParticleSystem::read(NIFStream* nif) + { + NiParticleSystemFlag = true; + NiParticles::read(nif); + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_SSE) + mVertexDesc.read(nif); + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_SKY) + { + nif->read(mFarBegin); + nif->read(mFarEnd); + nif->read(mNearBegin); + nif->read(mNearEnd); + } + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_SSE) + mNiPSysData.read(nif); + + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + { + nif->read(mWorldSpace); + unsigned int modifierNum; + nif->read(modifierNum); + mModifiers.resize(modifierNum); + + for (auto& modifier : mModifiers) + { + modifier.read(nif); + } + } + } + + void NiParticleSystem::post(Reader& nif) + { + NiParticles::post(nif); + mNiPSysData.post(nif); + for (auto ptr : mModifiers) + { + ptr.post(nif); + } + } + + void NiPSysModifier::read(NIFStream* nif) + { + mName = nif->getString(); + nif->read(mOrder); + mTarget.read(nif); + nif->read(mActive); + } + + void NiPSysModifier::post(Reader& nif) + { + mTarget.post(nif); + } + + void NiPSysModifierCtlr::read(NIFStream* nif) + { + NiSingleInterpController::read(nif); + mModifierName = nif->getString(); + } + + void NiPSysEmitterCtlr::read(NIFStream* nif) + { + NiPSysModifierCtlr::read(nif); + if (nif->getVersion() > NIFStream::generateVersion(10, 1, 0, 103)) + mVisibilityInterpolator.read(nif); + // TODO: Handle pre 10.1.0.103 + } + + void NiPSysEmitterCtlr::post(Reader& nif) + { + NiPSysModifierCtlr::post(nif); + if (nif.getVersion() > NIFStream::generateVersion(10, 1, 0, 103)) + mVisibilityInterpolator.post(nif); + } + + void NiPSysAgeDeathModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mSpawnOnDeath); + mSpawnModifier.read(nif); + } + + void NiPSysAgeDeathModifier::post(Reader& nif) + { + mSpawnModifier.post(nif); + } + + void NiPSysSpawnModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mNumSpawnGens); + nif->read(mPercentSpawned); + nif->read(mMinSpawnNum); + nif->read(mMaxSpawnNum); + nif->read(mSpawnSpeedVariation); + nif->read(mSpawnDirVariation); + nif->read(mLifeSpan); + nif->read(mLifeSpanVariation); + } + + void BSPSysLODModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mBeginDist); + nif->read(mEndDist); + nif->read(mEndEmitScale); + nif->read(mEndSize); + } + + void NiPSysCylinderEmitter::read(NIFStream* nif) + { + NiPSysVolumeEmitter::read(nif); + + nif->read(mRadius); + nif->read(mHeight); + } + + void NiPSysVolumeEmitter::read(NIFStream* nif) + { + NiPSysEmitter::read(nif); + + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + mEmitterObject.read(nif); + } + + void NiPSysVolumeEmitter::post(Reader& nif) + { + NiPSysEmitter::post(nif); + + if (nif.getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + mEmitterObject.post(nif); + } + + void NiPSysEmitter::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mSpeed); + nif->read(mSpeedVariation); + nif->read(mDeclination); + nif->read(mDeclinationVariation); + nif->read(mPlanarAngle); + nif->read(mPlanarAngleVariation); + nif->read(mInitialColor); + nif->read(mInitialRadius); + if (nif->getVersion() >= NIFStream::generateVersion(10, 4, 0, 1)) + nif->read(mRadiusVariation); + nif->read(mLifespan); + nif->read(mLifespanVariation); + // nif->skip(sizeof(float) * 2); + } + + void BSPSysSimpleColorModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mFadeInPercent); + nif->read(mFadeOutPercent); + nif->read(mColor1EndPercent); + nif->read(mColor1StartPercent); + nif->read(mColor2EndPercent); + nif->read(mColor2StartPercent); + nif->getVector4s(mColors, 3); + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) + nif->skip(sizeof(unsigned short) * 26); + } + + void NiPSysRotationModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mRotationSpeed); + if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 2)) + nif->read(mRotationSpeedVariation); + + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) + throw Nif::Exception("Fallout76 is unsupported: ", nif->getFile().getFilename()); + + if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 2)) + { + nif->read(mRotationAngle); + nif->read(mRotationAngleVariation); + mRandRotSpeedSign = nif->getBoolean(); + } + + mRandAxis = nif->getBoolean(); + nif->read(mAxis); + } + + void BSPSysScaleModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + unsigned int numScales; + nif->read(numScales); + nif->readVector(mScales, numScales); + } + + void NiPSysGravityModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + mGravityObject.read(nif); + nif->read(mGravityAxis); + nif->read(mDecay); + nif->read(mStrength); + nif->read(mForceType); + nif->read(mTurbulence); + nif->read(mTurbulenceScale); + + if (nif->getBethVersion() > 16) + mWorldAligned = nif->getBoolean(); + } + + void NiPSysGravityModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mGravityObject.post(nif); + } + + void NiPSysBoundUpdateModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + nif->read(mUpdateSkip); + } + + void NiPSysModifierActiveCtlr::read(NIFStream* nif) + { + NiPSysModifierCtlr::read(nif); + + if (nif->getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) + mNiVisData.read(nif); + } + + void NiPSysModifierActiveCtlr::post(Reader& nif) + { + NiPSysModifierCtlr::post(nif); + + if (nif.getVersion() <= NIFStream::generateVersion(10, 1, 0, 103)) + mNiVisData.post(nif); + } + + void NiPSysMeshEmitter::read(NIFStream* nif) + { + NiPSysEmitter::read(nif); + + unsigned int meshNum; + nif->read(meshNum); + + mEmitterMeshes.resize(meshNum); + for (auto& mesh : mEmitterMeshes) + mesh.read(nif); + + nif->read(mInitialVelocityType); + nif->read(mEmissionType); + nif->read(mEmissionAxis); + } + + void NiPSysMeshEmitter::post(Reader& nif) + { + NiPSysEmitter::post(nif); + + for (auto& mesh : mEmitterMeshes) + mesh.post(nif); + } + + void BSPSysInheritVelocityModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + mInheritObject.read(nif); + nif->read(mInheritChance); + nif->read(mVelocityMult); + nif->read(mVelcoityVariation); + } + + void BSPSysInheritVelocityModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mInheritObject.post(nif); + } + + void NiPSysBombModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + mBombObj.read(nif); + nif->read(mBombAxis); + nif->read(mDecay); + nif->read(mDeltaV); + nif->read(mDecayType); + nif->read(mSymmetryType); + } + + void NiPSysBombModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mBombObj.post(nif); + } + + void NiPSysDragModifier::read(NIFStream* nif) + { + NiPSysModifier::read(nif); + + mDragObj.read(nif); + nif->read(mDragAxis); + nif->read(mPercentage); + nif->read(mRange); + nif->read(mRangeFalloff); + } + + void NiPSysDragModifier::post(Reader& nif) + { + NiPSysModifier::post(nif); + + mDragObj.post(nif); + } +} diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp new file mode 100644 index 0000000000..cff855ecfa --- /dev/null +++ b/components/nif/particle.hpp @@ -0,0 +1,289 @@ + +#ifndef OPENMW_COMPONENTS_NIF_PARTICLE_HPP +#define OPENMW_COMPONENTS_NIF_PARTICLE_HPP + +#include "nifkey.hpp" +#include "niftypes.hpp" // Transformation +#include "recordptr.hpp" +#include +#include +#include +#include + +namespace Nif +{ + struct NiParticles : NiGeometry + { + }; + + struct NiParticlesData : public NiGeometryData + { + unsigned short numParticles{ 0 }; + + int activeCount{ 0 }; + + std::vector particleRadii, sizes, mRotationAngles; + std::vector mRotations; + std::vector mRotationAxes; + std::vector mSubtexOffsets; + + float mAspectRatio; + unsigned short mAspectFlags; + float mAspect2, mSpeed1, mSpeed2; + + void read(NIFStream* nif) override; + }; + + struct NiRotatingParticlesData : public NiParticlesData + { + void read(NIFStream* nif) override; + }; + + struct NiParticleInfo + { + osg::Vec3f mVelocity; + osg::Vec3f mRotation; + float mAge; + float mLifeSpan; + float mLastUpdate; + unsigned short mSpawnGen; + unsigned short mCode; + + void read(NIFStream* nif); + }; + + struct NiPSysData : public NiParticlesData + { + std::vector mParticleInfo; + std::vector mRotationSpeeds; + unsigned short mParticlesAddedNum; + unsigned short mParticlesBase; + + void read(NIFStream* nif) override; + }; + + struct NiParticleSystem : public NiParticles + { + BSVertexDesc mVertexDesc; + unsigned short mFarBegin; + unsigned short mFarEnd; + unsigned short mNearBegin; + unsigned short mNearEnd; + NiPSysDataPtr mNiPSysData; + + bool mWorldSpace; + std::vector mModifiers; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysModifier : public Record + { + std::string mName; + unsigned int mOrder; + NiParticleSystemPtr mTarget; + bool mActive; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysModifierCtlr : public NiSingleInterpController + { + std::string mModifierName; + + void read(NIFStream* nif) override; + }; + + struct NiPSysEmitterCtlr : public NiPSysModifierCtlr + { + NiInterpolatorPtr mVisibilityInterpolator; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysAgeDeathModifier : public NiPSysModifier + { + bool mSpawnOnDeath; + NiPSysSpawnModifierPtr mSpawnModifier; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysSpawnModifier : public NiPSysModifier + { + unsigned short mNumSpawnGens; + float mPercentSpawned; + unsigned short mMinSpawnNum, mMaxSpawnNum; + float mSpawnSpeedVariation; + float mSpawnDirVariation; + float mLifeSpan; + float mLifeSpanVariation; + + void read(NIFStream* nif) override; + }; + + struct BSPSysLODModifier : public NiPSysModifier + { + float mBeginDist; + float mEndDist; + float mEndEmitScale; + float mEndSize; + + void read(NIFStream* nif) override; + }; + + struct NiPSysEmitter : public NiPSysModifier + { + float mSpeed; + float mSpeedVariation; + float mDeclination; + float mDeclinationVariation; + float mPlanarAngle; + float mPlanarAngleVariation; + + osg::Vec4f mInitialColor; + + float mInitialRadius; + float mRadiusVariation; + float mLifespan; + float mLifespanVariation; + + void read(NIFStream* nif) override; + }; + + struct NiPSysVolumeEmitter : public NiPSysEmitter + { + NiNodePtr mEmitterObject; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysCylinderEmitter : public NiPSysVolumeEmitter + { + float mRadius; + float mHeight; + + void read(NIFStream* nif) override; + }; + + struct BSPSysSimpleColorModifier : public NiPSysModifier + { + float mFadeInPercent; + float mFadeOutPercent; + float mColor1EndPercent; + float mColor1StartPercent; + float mColor2EndPercent; + float mColor2StartPercent; + + std::vector mColors; + + void read(NIFStream* nif) override; + }; + + struct NiPSysRotationModifier : public NiPSysModifier + { + float mRotationSpeed; + float mRotationSpeedVariation; + float mRotationAngle; + float mRotationAngleVariation; + + bool mRandRotSpeedSign; + bool mRandAxis; + osg::Vec3f mAxis; + + void read(NIFStream* nif) override; + }; + + struct BSPSysScaleModifier : public NiPSysModifier + { + std::vector mScales; + + void read(NIFStream* nif) override; + }; + + struct NiPSysGravityModifier : public NiPSysModifier + { + NamedPtr mGravityObject; + osg::Vec3f mGravityAxis; + float mDecay; + float mStrength; + unsigned int mForceType; + float mTurbulence; + float mTurbulenceScale; + + bool mWorldAligned; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysBoundUpdateModifier : public NiPSysModifier + { + unsigned short mUpdateSkip; + + void read(NIFStream* nif) override; + }; + + struct NiPSysModifierActiveCtlr : public NiPSysModifierCtlr + { + NiVisDataPtr mNiVisData; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysMeshEmitter : public NiPSysEmitter + { + std::vector mEmitterMeshes; + unsigned int mInitialVelocityType; + unsigned int mEmissionType; + osg::Vec3f mEmissionAxis; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct BSPSysInheritVelocityModifier : public NiPSysModifier + { + NamedPtr mInheritObject; + float mInheritChance; + float mVelocityMult; + float mVelcoityVariation; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysBombModifier : public NiPSysModifier + { + NiNodePtr mBombObj; + osg::Vec3f mBombAxis; + float mDecay; + float mDeltaV; + + unsigned int mDecayType; + unsigned int mSymmetryType; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiPSysDragModifier : public NiPSysModifier + { + NamedPtr mDragObj; + osg::Vec3f mDragAxis; + float mPercentage; + float mRange; + float mRangeFalloff; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; +} + +#endif diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 0650dfebf9..8bae83c694 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -170,11 +170,30 @@ namespace Nif RC_BSMultiBoundSphere, RC_BSInvMarker, RC_BSTriShape, + RC_NiPSysData, RC_BSEffectShaderPropertyFloatController, RC_BSEffectShaderPropertyColorController, RC_BSLightingShaderPropertyFloatController, RC_BSLightingShaderPropertyColorController, RC_BSBehaviorGraphExtraData, + RC_NiParticleSystem, + RC_NiPSysEmitterCtlr, + RC_Controller, + RC_NiPSysAgeDeathModifier, + RC_BSPSysLODModifier, + RC_NiPSysCylinderEmitter, + RC_NiPSysSpawnModifier, + RC_BSPSysSimpleColorModifier, + RC_NiPSysRotationModifier, + RC_BSPSysScaleModifier, + RC_NiPSysGravityModifier, + RC_NiPSysBoundUpdateModifier, + RC_NiPSysModifier, + RC_NiPSysModifierActiveCtlr, + RC_NiPSysMeshEmitter, + RC_BSPSysInheritVelocityModifier, + RC_NiPSysBombModifier, + RC_NiPSysDragModifier, }; /// Base class for all records diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index e2836d458d..dc09d2bed4 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -149,7 +149,13 @@ namespace Nif struct bhkCompressedMeshShapeData; struct BSMultiBound; struct BSMultiBoundData; + struct NiPSysData; + struct NiParticleSystem; + struct NiPSysModifier; + struct NiPSysSpawnModifier; + struct NiNode; + using RecordPtr = RecordPtrT; using NodePtr = RecordPtrT; using ExtraPtr = RecordPtrT; using NiUVDataPtr = RecordPtrT; @@ -187,6 +193,11 @@ namespace Nif using bhkCompressedMeshShapeDataPtr = RecordPtrT; using BSMultiBoundPtr = RecordPtrT; using BSMultiBoundDataPtr = RecordPtrT; + using NiPSysDataPtr = RecordPtrT; + using NiParticleSystemPtr = RecordPtrT; + using NiPSysModifierPtr = RecordPtrT; + using NiPSysSpawnModifierPtr = RecordPtrT; + using NiNodePtr = RecordPtrT; using NodeList = RecordListT; using PropertyList = RecordListT; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 251795eb21..281f78f512 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -79,14 +79,14 @@ namespace template auto handleNiGeometry(const Nif::NiGeometry& geometry, Function&& function) - -> decltype(function(static_cast(geometry.data.get()))) + -> decltype(function(static_cast(geometry.mData.get()))) { if (geometry.recType == Nif::RC_NiTriShape || geometry.recType == Nif::RC_BSLODTriShape) { - if (geometry.data->recType != Nif::RC_NiTriShapeData) + if (geometry.mData->recType != Nif::RC_NiTriShapeData) return {}; - auto data = static_cast(geometry.data.getPtr()); + auto data = static_cast(geometry.mData.getPtr()); if (data->triangles.empty()) return {}; @@ -95,10 +95,10 @@ namespace if (geometry.recType == Nif::RC_NiTriStrips) { - if (geometry.data->recType != Nif::RC_NiTriStripsData) + if (geometry.mData->recType != Nif::RC_NiTriStripsData) return {}; - auto data = static_cast(geometry.data.getPtr()); + auto data = static_cast(geometry.mData.getPtr()); if (data->strips.empty()) return {}; @@ -380,10 +380,10 @@ namespace NifBullet if (args.mHasMarkers && Misc::StringUtils::ciStartsWith(niGeometry.name, "EditorMarker")) return; - if (niGeometry.data.empty() || niGeometry.data->vertices.empty()) + if (niGeometry.mData.empty() || niGeometry.mData->vertices.empty()) return; - if (!niGeometry.skin.empty()) + if (!niGeometry.mSkinInstance.empty()) args.mAnimated = false; // TODO: handle NiSkinPartition diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 2e2d0293e6..bb259d3324 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1,6 +1,9 @@ #include "nifloader.hpp" +#include +#include #include +#include #include #include @@ -48,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -127,10 +131,10 @@ namespace auto geometry = dynamic_cast(nifNode); if (geometry) { - if (!geometry->shaderprop.empty()) - out.emplace_back(geometry->shaderprop.getPtr()); - if (!geometry->alphaprop.empty()) - out.emplace_back(geometry->alphaprop.getPtr()); + if (!geometry->mShaderProperty.empty()) + out.emplace_back(geometry->mShaderProperty.getPtr()); + if (!geometry->mAlphaProperty.empty()) + out.emplace_back(geometry->mAlphaProperty.getPtr()); } } @@ -436,8 +440,8 @@ namespace NifOsg auto geometry = dynamic_cast(nifNode); // NiGeometry's NiAlphaProperty doesn't get handled here because it's a drawable property - if (geometry && !geometry->shaderprop.empty()) - handleProperty(geometry->shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, + if (geometry && !geometry->mShaderProperty.empty()) + handleProperty(geometry->mShaderProperty.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, hasStencilProperty); } @@ -759,7 +763,7 @@ namespace NifOsg skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->name, "EditorMarker"); if (!skip) { - Nif::NiSkinInstancePtr skin = static_cast(nifNode)->skin; + Nif::NiSkinInstancePtr skin = static_cast(nifNode)->mSkinInstance; if (skin.empty()) handleGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); @@ -1120,13 +1124,13 @@ namespace NifOsg const Nif::Node* nifNode, ParticleSystem* partsys, const Nif::NiParticleSystemController* partctrl) { auto particleNode = static_cast(nifNode); - if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData) + if (particleNode->mData.empty() || particleNode->mData->recType != Nif::RC_NiParticlesData) { partsys->setQuota(partctrl->numParticles); return; } - auto particledata = static_cast(particleNode->data.getPtr()); + auto particledata = static_cast(particleNode->mData.getPtr()); partsys->setQuota(particledata->numParticles); osg::BoundingBox box; @@ -1376,13 +1380,13 @@ namespace NifOsg const std::vector& boundTextures, int animflags) { const Nif::NiGeometry* niGeometry = static_cast(nifNode); - if (niGeometry->data.empty()) + if (niGeometry->mData.empty()) return; bool hasPartitions = false; - if (!niGeometry->skin.empty()) + if (!niGeometry->mSkinInstance.empty()) { - const Nif::NiSkinInstance* skin = niGeometry->skin.getPtr(); + const Nif::NiSkinInstance* skin = niGeometry->mSkinInstance.getPtr(); const Nif::NiSkinData* data = nullptr; const Nif::NiSkinPartition* partitions = nullptr; if (!skin->data.empty()) @@ -1416,7 +1420,7 @@ namespace NifOsg } } - const Nif::NiGeometryData* niGeometryData = niGeometry->data.getPtr(); + const Nif::NiGeometryData* niGeometryData = niGeometry->mData.getPtr(); if (!hasPartitions) { if (niGeometry->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_BSLODTriShape) @@ -1542,7 +1546,7 @@ namespace NifOsg // Assign bone weights osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); - const Nif::NiSkinInstance* skin = static_cast(nifNode)->skin.getPtr(); + const Nif::NiSkinInstance* skin = static_cast(nifNode)->mSkinInstance.getPtr(); const Nif::NiSkinData* data = skin->data.getPtr(); const Nif::NodeList& bones = skin->bones; for (std::size_t i = 0, n = bones.size(); i < n; ++i) @@ -2676,5 +2680,4 @@ namespace NifOsg LoaderImpl impl(kf.getFilename(), kf.getVersion(), kf.getUserVersion(), kf.getBethVersion()); impl.loadKf(kf, target); } - }