BulletNifLoader updates

Refactor root node, visual collision type and filename handling
Only handle BSXFlags for the root, handle BSXFlags collision flag and absence
Properly distinguish collision node and autogenerated flag
macos_ci_fix
Alexei Kotov 1 year ago
parent bb6fdc1e21
commit 40313019ef

@ -17,6 +17,7 @@
#include <components/nif/data.hpp>
#include <components/nif/extra.hpp>
#include <components/nif/nifstream.hpp>
#include <components/nif/node.hpp>
#include <components/nif/parent.hpp>
@ -162,18 +163,17 @@ namespace NifBullet
if (node)
roots.emplace_back(node);
}
const std::string filename = Files::pathToUnicodeString(nif.getFilename());
mShape->mFileName = filename;
mShape->mFileName = Files::pathToUnicodeString(nif.getFilename());
if (roots.empty())
{
warn("Found no root nodes in NIF file " + filename);
warn("Found no root nodes in NIF file " + mShape->mFileName);
return mShape;
}
// Try to find a valid bounding box first. If one's found for any root node, use that.
for (const Nif::NiAVObject* node : roots)
{
if (findBoundingBox(*node, filename))
// Try to find a valid bounding box first. If one's found for any root node, use that.
if (findBoundingBox(*node))
{
const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.mExtents);
const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter);
@ -188,29 +188,18 @@ namespace NifBullet
return mShape;
}
}
HandleNodeArgs args;
// files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see
// Animation::addAnimSource). assume all nodes in the file will be animated
// TODO: investigate whether this should and could be optimized.
const bool isAnimated = pathFileNameStartsWithX(filename);
args.mAnimated = pathFileNameStartsWithX(mShape->mFileName);
// If there's no bounding box, we'll have to generate a Bullet collision shape
// from the collision data present in every root node.
for (const Nif::NiAVObject* node : roots)
{
const Nif::NiNode* colNode = findRootCollisionNode(*node);
bool hasCollisionShape = false;
if (colNode != nullptr)
{
if (colNode->mBounds.mType == Nif::BoundingVolume::Type::BASE_BV && !colNode->mChildren.empty())
hasCollisionShape = true;
else
mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera;
}
HandleNodeArgs args;
args.mAutogenerated = args.mIsCollisionNode = !hasCollisionShape;
args.mAnimated = isAnimated;
handleNode(filename, *node, nullptr, args, mShape->mVisualCollisionType);
}
handleRoot(nif, *node, args);
if (mCompoundShape)
mShape->mCollisionShape = std::move(mCompoundShape);
@ -223,7 +212,7 @@ namespace NifBullet
// Find a boundingBox in the node hierarchy.
// Return: use bounding box for collision?
bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node, const std::string& filename)
bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node)
{
unsigned int type = node.mBounds.mType;
switch (type)
@ -238,7 +227,7 @@ namespace NifBullet
{
std::stringstream warning;
warning << "Unsupported BoundingVolume type " << type << " in node " << node.recIndex;
warning << " in file " << filename;
warning << " in file " << mShape->mFileName;
warn(warning.str());
}
}
@ -249,27 +238,70 @@ namespace NifBullet
if (const Nif::NiNode* ninode = dynamic_cast<const Nif::NiNode*>(&node))
{
for (const auto& child : ninode->mChildren)
if (!child.empty() && findBoundingBox(child.get(), filename))
if (!child.empty() && findBoundingBox(child.get()))
return true;
}
return false;
}
const Nif::NiNode* BulletNifLoader::findRootCollisionNode(const Nif::NiAVObject& rootNode) const
void BulletNifLoader::handleRoot(Nif::FileView nif, const Nif::NiAVObject& node, HandleNodeArgs args)
{
if (const Nif::NiNode* ninode = dynamic_cast<const Nif::NiNode*>(&rootNode))
// Gamebryo/Bethbryo meshes
if (nif.getVersion() >= Nif::NIFStream::generateVersion(10, 0, 1, 0))
{
for (const auto& child : ninode->mChildren)
// Handle BSXFlags
const Nif::NiIntegerExtraData* bsxFlags = nullptr;
for (const auto& e : node.getExtraList())
{
if (e->recType == Nif::RC_BSXFlags)
{
bsxFlags = static_cast<const Nif::NiIntegerExtraData*>(e.getPtr());
break;
}
}
// Collision flag
if (!bsxFlags || !(bsxFlags->mData & 2))
return;
// Editor marker flag
if (bsxFlags->mData & 32)
args.mHasMarkers = true;
// FIXME: hack, using rendered geometry instead of Bethesda Havok data
args.mAutogenerated = true;
}
// Pre-Gamebryo meshes
else
{
// Handle RootCollisionNode
const Nif::NiNode* colNode = nullptr;
if (const Nif::NiNode* ninode = dynamic_cast<const Nif::NiNode*>(&node))
{
if (!child.empty() && child.getPtr()->recType == Nif::RC_RootCollisionNode)
return static_cast<const Nif::NiNode*>(child.getPtr());
for (const auto& child : ninode->mChildren)
{
if (!child.empty() && child.getPtr()->recType == Nif::RC_RootCollisionNode)
{
colNode = static_cast<const Nif::NiNode*>(child.getPtr());
break;
}
}
}
args.mAutogenerated = colNode == nullptr;
// FIXME: BulletNifLoader should never have to provide rendered geometry for camera collision
if (colNode && colNode->mChildren.empty())
{
args.mAutogenerated = true;
mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera;
}
}
return nullptr;
handleNode(node, nullptr, args);
}
void BulletNifLoader::handleNode(const std::string& fileName, const Nif::NiAVObject& node,
const Nif::Parent* parent, HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType)
void BulletNifLoader::handleNode(const Nif::NiAVObject& node, const Nif::Parent* parent, HandleNodeArgs args)
{
// TODO: allow on-the fly collision switching via toggling this flag
if (node.recType == Nif::RC_NiCollisionSwitch && !node.collisionActive())
@ -295,20 +327,23 @@ namespace NifBullet
if (node.recType == Nif::RC_RootCollisionNode)
{
args.mIsCollisionNode = true;
if (args.mAutogenerated)
{
// Encountered a RootCollisionNode inside an autogenerated mesh.
// We treat empty RootCollisionNodes as NCC flag (set collisionType to `Camera`)
// and generate the camera collision shape based on rendered geometry.
if (visualCollisionType == Resource::VisualCollisionType::Camera)
if (mShape->mVisualCollisionType == Resource::VisualCollisionType::Camera)
return;
// Otherwise we'll want to notify the user.
Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << fileName
Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << mShape->mFileName
<< ". Treating it as a common NiTriShape.";
}
else
{
args.mIsCollisionNode = true;
}
}
// Don't collide with AvoidNode shapes
@ -330,10 +365,10 @@ namespace NifBullet
// uppercase
if (sd->mData.length() > 2 && sd->mData[2] == 'C')
// Collide only with camera.
visualCollisionType = Resource::VisualCollisionType::Camera;
mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera;
else
// No collision.
visualCollisionType = Resource::VisualCollisionType::Default;
mShape->mVisualCollisionType = Resource::VisualCollisionType::Default;
}
// Don't autogenerate collision if MRK is set.
// FIXME: verify if this covers the entire subtree
@ -342,15 +377,9 @@ namespace NifBullet
return;
}
}
else if (e->recType == Nif::RC_BSXFlags)
{
auto bsxFlags = static_cast<const Nif::NiIntegerExtraData*>(e.getPtr());
if (bsxFlags->mData & 32) // Editor marker flag
args.mHasMarkers = true;
}
}
if (args.mIsCollisionNode)
if (args.mAutogenerated || args.mIsCollisionNode)
{
// NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape!
// It must be ignored completely.
@ -369,7 +398,7 @@ namespace NifBullet
continue;
assert(std::find(child->mParents.begin(), child->mParents.end(), ninode) != child->mParents.end());
handleNode(fileName, child.get(), &currentParent, args, visualCollisionType);
handleNode(child.get(), &currentParent, args);
}
}
}

@ -48,7 +48,7 @@ namespace NifBullet
osg::ref_ptr<Resource::BulletShape> load(Nif::FileView file);
private:
bool findBoundingBox(const Nif::NiAVObject& node, const std::string& filename);
bool findBoundingBox(const Nif::NiAVObject& node);
struct HandleNodeArgs
{
@ -59,11 +59,8 @@ namespace NifBullet
bool mAvoid{ false };
};
void handleNode(const std::string& fileName, const Nif::NiAVObject& node, const Nif::Parent* parent,
HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType);
const Nif::NiNode* findRootCollisionNode(const Nif::NiAVObject& rootNode) const;
void handleRoot(Nif::FileView nif, const Nif::NiAVObject& node, HandleNodeArgs args);
void handleNode(const Nif::NiAVObject& node, const Nif::Parent* parent, HandleNodeArgs args);
void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, HandleNodeArgs args);
std::unique_ptr<btCompoundShape, Resource::DeleteCollisionShape> mCompoundShape;

Loading…
Cancel
Save