From af24d3fd3c6a7fea57be4d3d078f0619a3cd86f2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Sep 2023 14:16:57 +0300 Subject: [PATCH] Nth revision of NifLoader geometry handling Handle BSSegmentedTriShape --- components/nifbullet/bulletnifloader.cpp | 34 +++- components/nifosg/nifloader.cpp | 242 +++++++++++------------ 2 files changed, 146 insertions(+), 130 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index df4155db7d..a72ec36cad 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -32,6 +32,32 @@ namespace return letterPos < path.size() && (path[letterPos] == 'x' || path[letterPos] == 'X'); } + bool isTypeNiGeometry(int type) + { + switch (type) + { + case Nif::RC_NiTriShape: + case Nif::RC_NiTriStrips: + case Nif::RC_BSLODTriShape: + case Nif::RC_BSSegmentedTriShape: + return true; + } + return false; + } + + bool isTypeTriShape(int type) + { + switch (type) + { + case Nif::RC_NiTriShape: + case Nif::RC_BSLODTriShape: + case Nif::RC_BSSegmentedTriShape: + return true; + } + + return false; + } + void prepareTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriBasedGeomData& data) { // FIXME: copying vertices/indices individually is unreasonable @@ -81,7 +107,7 @@ namespace auto handleNiGeometry(const Nif::NiGeometry& geometry, Function&& function) -> decltype(function(static_cast(geometry.mData.get()))) { - if (geometry.recType == Nif::RC_NiTriShape || geometry.recType == Nif::RC_BSLODTriShape) + if (isTypeTriShape(geometry.recType)) { auto data = static_cast(geometry.mData.getPtr()); if (data->mTriangles.empty()) @@ -329,12 +355,8 @@ namespace NifBullet // NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if (node.mBounds.mType == Nif::BoundingVolume::Type::BASE_BV - && (node.recType == Nif::RC_NiTriShape || node.recType == Nif::RC_NiTriStrips - || node.recType == Nif::RC_BSLODTriShape)) - { + if (node.mBounds.mType == Nif::BoundingVolume::Type::BASE_BV && isTypeNiGeometry(node.recType)) handleNiTriShape(static_cast(node), parent, args); - } } // For NiNodes, loop through children diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index fbcd2f52bd..a5c95fbd67 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -88,7 +88,7 @@ namespace } } - bool isTypeGeometry(int type) + bool isTypeNiGeometry(int type) { switch (type) { @@ -96,6 +96,19 @@ namespace case Nif::RC_NiTriStrips: case Nif::RC_NiLines: case Nif::RC_BSLODTriShape: + case Nif::RC_BSSegmentedTriShape: + return true; + } + return false; + } + + bool isTypeBSGeometry(int type) + { + switch (type) + { + case Nif::RC_BSTriShape: + case Nif::RC_BSDynamicTriShape: + case Nif::RC_BSMeshLODTriShape: return true; } return false; @@ -125,15 +138,6 @@ namespace } } } - - auto geometry = dynamic_cast(nifNode); - if (geometry) - { - if (!geometry->mShaderProperty.empty()) - out.emplace_back(geometry->mShaderProperty.getPtr()); - if (!geometry->mAlphaProperty.empty()) - out.emplace_back(geometry->mAlphaProperty.getPtr()); - } } // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale @@ -443,11 +447,16 @@ namespace NifOsg } } - auto geometry = dynamic_cast(nifNode); - // NiGeometry's NiAlphaProperty doesn't get handled here because it's a drawable property - if (geometry && !geometry->mShaderProperty.empty()) - handleProperty(geometry->mShaderProperty.getPtr(), applyTo, composite, imageManager, boundTextures, - animflags, hasStencilProperty); + // NiAlphaProperty is handled as a drawable property + Nif::BSShaderPropertyPtr shaderprop = nullptr; + if (isTypeNiGeometry(nifNode->recType)) + shaderprop = static_cast(nifNode)->mShaderProperty; + else if (isTypeBSGeometry(nifNode->recType)) + shaderprop = static_cast(nifNode)->mShaderProperty; + + if (!shaderprop.empty()) + handleProperty(shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags, + hasStencilProperty); } static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags) @@ -747,7 +756,9 @@ namespace NifOsg applyNodeProperties(nifNode, node, composite, args.mImageManager, args.mBoundTextures, args.mAnimFlags); - const bool isGeometry = isTypeGeometry(nifNode->recType); + const bool isNiGeometry = isTypeNiGeometry(nifNode->recType); + const bool isBSGeometry = isTypeBSGeometry(nifNode->recType); + const bool isGeometry = isNiGeometry || isBSGeometry; if (isGeometry && !args.mSkipMeshes) { @@ -762,12 +773,8 @@ namespace NifOsg skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker"); if (!skip) { - Nif::NiSkinInstancePtr skin = static_cast(nifNode)->mSkin; - - if (skin.empty()) - handleGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); - else - handleSkinnedGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); + if (isNiGeometry) + handleNiGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); if (!nifNode->mController.empty()) handleMeshControllers(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags); @@ -1342,41 +1349,7 @@ namespace NifOsg } } - void handleNiGeometryData(osg::Geometry* geometry, const Nif::NiGeometryData* data, - const std::vector& boundTextures, const std::string& name) - { - const auto& vertices = data->mVertices; - const auto& normals = data->mNormals; - const auto& colors = data->mColors; - if (!vertices.empty()) - geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); - if (!normals.empty()) - geometry->setNormalArray( - new osg::Vec3Array(normals.size(), normals.data()), osg::Array::BIND_PER_VERTEX); - if (!colors.empty()) - geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); - - const auto& uvlist = data->mUVList; - int textureStage = 0; - for (std::vector::const_iterator it = boundTextures.begin(); it != boundTextures.end(); - ++it, ++textureStage) - { - unsigned int uvSet = *it; - if (uvSet >= uvlist.size()) - { - Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << name << "\" in " - << mFilename; - if (uvlist.empty()) - continue; - uvSet = 0; - } - - geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[uvSet].size(), uvlist[uvSet].data()), - osg::Array::BIND_PER_VERTEX); - } - } - - void handleNiGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Geometry* geometry, + void handleNiGeometryData(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Geometry* geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { @@ -1458,7 +1431,36 @@ namespace NifOsg new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, line.size(), line.data())); } } - handleNiGeometryData(geometry, niGeometryData, boundTextures, nifNode->mName); + + const auto& vertices = niGeometryData->mVertices; + const auto& normals = niGeometryData->mNormals; + const auto& colors = niGeometryData->mColors; + if (!vertices.empty()) + geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); + if (!normals.empty()) + geometry->setNormalArray( + new osg::Vec3Array(normals.size(), normals.data()), osg::Array::BIND_PER_VERTEX); + if (!colors.empty()) + geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); + + const auto& uvlist = niGeometryData->mUVList; + int textureStage = 0; + for (std::vector::const_iterator it = boundTextures.begin(); it != boundTextures.end(); + ++it, ++textureStage) + { + unsigned int uvSet = *it; + if (uvSet >= uvlist.size()) + { + Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << nifNode->mName + << "\" in " << mFilename; + if (uvlist.empty()) + continue; + uvSet = 0; + } + + geometry->setTexCoordArray(textureStage, new osg::Vec2Array(uvlist[uvSet].size(), uvlist[uvSet].data()), + osg::Array::BIND_PER_VERTEX); + } // osg::Material properties are handled here for two reasons: // - if there are no vertex colors, we need to disable colorMode. @@ -1466,100 +1468,92 @@ namespace NifOsg // above the actual renderable would be tedious. std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); + if (!niGeometry->mShaderProperty.empty()) + drawableProps.emplace_back(niGeometry->mShaderProperty.getPtr()); + if (!niGeometry->mAlphaProperty.empty()) + drawableProps.emplace_back(niGeometry->mAlphaProperty.getPtr()); applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->mColors.empty(), animflags); } - void handleGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, + void handleNiGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { - assert(isTypeGeometry(nifNode->recType)); + assert(isTypeNiGeometry(nifNode->recType)); + osg::ref_ptr geom(new osg::Geometry); - handleNiGeometry(nifNode, parent, geom, parentNode, composite, boundTextures, animflags); + handleNiGeometryData(nifNode, parent, geom, parentNode, composite, boundTextures, animflags); // If the record had no valid geometry data in it, early-out if (geom->empty()) return; - osg::ref_ptr drawable; + + osg::ref_ptr drawable = geom; + + auto niGeometry = static_cast(nifNode); + if (!niGeometry->mSkin.empty()) + { + osg::ref_ptr rig(new SceneUtil::RigGeometry); + rig->setSourceGeometry(geom); + + // Assign bone weights + osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); + + const Nif::NiSkinInstance* skin = niGeometry->mSkin.getPtr(); + const Nif::NiSkinData* data = skin->mData.getPtr(); + const Nif::NiAVObjectList& bones = skin->mBones; + for (std::size_t i = 0; i < bones.size(); ++i) + { + std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); + + SceneUtil::RigGeometry::BoneInfluence influence; + influence.mWeights = data->mBones[i].mWeights; + influence.mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); + influence.mBoundSphere = data->mBones[i].mBoundSphere; + + map->mData.emplace_back(boneName, influence); + } + rig->setInfluenceMap(map); + + drawable = rig; + } + for (Nif::NiTimeControllerPtr ctrl = nifNode->mController; !ctrl.empty(); ctrl = ctrl->mNext) { if (!ctrl->isActive()) continue; if (ctrl->recType == Nif::RC_NiGeomMorpherController) { - const Nif::NiGeomMorpherController* nimorphctrl - = static_cast(ctrl.getPtr()); + if (!niGeometry->mSkin.empty()) + continue; + + auto nimorphctrl = static_cast(ctrl.getPtr()); if (nimorphctrl->mData.empty()) continue; - drawable = handleMorphGeometry(nimorphctrl, geom, parentNode, composite, boundTextures, animflags); + + const std::vector& morphs = nimorphctrl->mData.getPtr()->mMorphs; + if (morphs.empty() || morphs[0].mVertices.size() + != static_cast(geom->getVertexArray())->size()) + continue; + + osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; + morphGeom->setSourceGeometry(geom); + for (unsigned int i = 0; i < morphs.size(); ++i) + morphGeom->addMorphTarget( + new osg::Vec3Array(morphs[i].mVertices.size(), morphs[i].mVertices.data()), 0.f); osg::ref_ptr morphctrl = new GeomMorpherController(nimorphctrl); setupController(ctrl.getPtr(), morphctrl, animflags); - drawable->setUpdateCallback(morphctrl); + morphGeom->setUpdateCallback(morphctrl); + + drawable = morphGeom; break; } } - if (!drawable.get()) - drawable = geom; + drawable->setName(nifNode->mName); parentNode->addChild(drawable); } - osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, - osg::ref_ptr sourceGeometry, osg::Node* parentNode, - SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, - int animflags) - { - osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; - morphGeom->setSourceGeometry(sourceGeometry); - - const std::vector& morphs = morpher->mData.getPtr()->mMorphs; - if (morphs.empty()) - return morphGeom; - if (morphs[0].mVertices.size() - != static_cast(sourceGeometry->getVertexArray())->size()) - return morphGeom; - for (unsigned int i = 0; i < morphs.size(); ++i) - morphGeom->addMorphTarget( - new osg::Vec3Array(morphs[i].mVertices.size(), morphs[i].mVertices.data()), 0.f); - - return morphGeom; - } - - void handleSkinnedGeometry(const Nif::NiAVObject* nifNode, const Nif::Parent* parent, osg::Group* parentNode, - SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, - int animflags) - { - assert(isTypeGeometry(nifNode->recType)); - osg::ref_ptr geometry(new osg::Geometry); - handleNiGeometry(nifNode, parent, geometry, parentNode, composite, boundTextures, animflags); - if (geometry->empty()) - return; - osg::ref_ptr rig(new SceneUtil::RigGeometry); - rig->setSourceGeometry(geometry); - rig->setName(nifNode->mName); - - // Assign bone weights - osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); - - const Nif::NiSkinInstance* skin = static_cast(nifNode)->mSkin.getPtr(); - const Nif::NiSkinData* data = skin->mData.getPtr(); - const Nif::NiAVObjectList& bones = skin->mBones; - for (std::size_t i = 0; i < bones.size(); ++i) - { - std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); - - SceneUtil::RigGeometry::BoneInfluence influence; - influence.mWeights = data->mBones[i].mWeights; - influence.mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); - influence.mBoundSphere = data->mBones[i].mBoundSphere; - - map->mData.emplace_back(boneName, influence); - } - rig->setInfluenceMap(map); - - parentNode->addChild(rig); - } - osg::BlendFunc::BlendFuncMode getBlendMode(int mode) { switch (mode)