mirror of
https://github.com/OpenMW/openmw.git
synced 2025-03-27 12:40:25 +00:00
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
This commit is contained in:
parent
bb6fdc1e21
commit
40313019ef
2 changed files with 77 additions and 51 deletions
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#include <components/nif/data.hpp>
|
#include <components/nif/data.hpp>
|
||||||
#include <components/nif/extra.hpp>
|
#include <components/nif/extra.hpp>
|
||||||
|
#include <components/nif/nifstream.hpp>
|
||||||
#include <components/nif/node.hpp>
|
#include <components/nif/node.hpp>
|
||||||
#include <components/nif/parent.hpp>
|
#include <components/nif/parent.hpp>
|
||||||
|
|
||||||
|
@ -162,18 +163,17 @@ namespace NifBullet
|
||||||
if (node)
|
if (node)
|
||||||
roots.emplace_back(node);
|
roots.emplace_back(node);
|
||||||
}
|
}
|
||||||
const std::string filename = Files::pathToUnicodeString(nif.getFilename());
|
mShape->mFileName = Files::pathToUnicodeString(nif.getFilename());
|
||||||
mShape->mFileName = filename;
|
|
||||||
if (roots.empty())
|
if (roots.empty())
|
||||||
{
|
{
|
||||||
warn("Found no root nodes in NIF file " + filename);
|
warn("Found no root nodes in NIF file " + mShape->mFileName);
|
||||||
return mShape;
|
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)
|
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 extents = Misc::Convert::toBullet(mShape->mCollisionBox.mExtents);
|
||||||
const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter);
|
const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter);
|
||||||
|
@ -188,29 +188,18 @@ namespace NifBullet
|
||||||
return mShape;
|
return mShape;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HandleNodeArgs args;
|
||||||
|
|
||||||
// files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see
|
// 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
|
// Animation::addAnimSource). assume all nodes in the file will be animated
|
||||||
// TODO: investigate whether this should and could be optimized.
|
// 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
|
// If there's no bounding box, we'll have to generate a Bullet collision shape
|
||||||
// from the collision data present in every root node.
|
// from the collision data present in every root node.
|
||||||
for (const Nif::NiAVObject* node : roots)
|
for (const Nif::NiAVObject* node : roots)
|
||||||
{
|
handleRoot(nif, *node, args);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mCompoundShape)
|
if (mCompoundShape)
|
||||||
mShape->mCollisionShape = std::move(mCompoundShape);
|
mShape->mCollisionShape = std::move(mCompoundShape);
|
||||||
|
@ -223,7 +212,7 @@ namespace NifBullet
|
||||||
|
|
||||||
// Find a boundingBox in the node hierarchy.
|
// Find a boundingBox in the node hierarchy.
|
||||||
// Return: use bounding box for collision?
|
// 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;
|
unsigned int type = node.mBounds.mType;
|
||||||
switch (type)
|
switch (type)
|
||||||
|
@ -238,7 +227,7 @@ namespace NifBullet
|
||||||
{
|
{
|
||||||
std::stringstream warning;
|
std::stringstream warning;
|
||||||
warning << "Unsupported BoundingVolume type " << type << " in node " << node.recIndex;
|
warning << "Unsupported BoundingVolume type " << type << " in node " << node.recIndex;
|
||||||
warning << " in file " << filename;
|
warning << " in file " << mShape->mFileName;
|
||||||
warn(warning.str());
|
warn(warning.str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,27 +238,70 @@ namespace NifBullet
|
||||||
if (const Nif::NiNode* ninode = dynamic_cast<const Nif::NiNode*>(&node))
|
if (const Nif::NiNode* ninode = dynamic_cast<const Nif::NiNode*>(&node))
|
||||||
{
|
{
|
||||||
for (const auto& child : ninode->mChildren)
|
for (const auto& child : ninode->mChildren)
|
||||||
if (!child.empty() && findBoundingBox(child.get(), filename))
|
if (!child.empty() && findBoundingBox(child.get()))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
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 (!child.empty() && child.getPtr()->recType == Nif::RC_RootCollisionNode)
|
if (e->recType == Nif::RC_BSXFlags)
|
||||||
return static_cast<const Nif::NiNode*>(child.getPtr());
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
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,
|
void BulletNifLoader::handleNode(const Nif::NiAVObject& node, const Nif::Parent* parent, HandleNodeArgs args)
|
||||||
const Nif::Parent* parent, HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType)
|
|
||||||
{
|
{
|
||||||
// TODO: allow on-the fly collision switching via toggling this flag
|
// TODO: allow on-the fly collision switching via toggling this flag
|
||||||
if (node.recType == Nif::RC_NiCollisionSwitch && !node.collisionActive())
|
if (node.recType == Nif::RC_NiCollisionSwitch && !node.collisionActive())
|
||||||
|
@ -295,20 +327,23 @@ namespace NifBullet
|
||||||
|
|
||||||
if (node.recType == Nif::RC_RootCollisionNode)
|
if (node.recType == Nif::RC_RootCollisionNode)
|
||||||
{
|
{
|
||||||
args.mIsCollisionNode = true;
|
|
||||||
if (args.mAutogenerated)
|
if (args.mAutogenerated)
|
||||||
{
|
{
|
||||||
// Encountered a RootCollisionNode inside an autogenerated mesh.
|
// Encountered a RootCollisionNode inside an autogenerated mesh.
|
||||||
|
|
||||||
// We treat empty RootCollisionNodes as NCC flag (set collisionType to `Camera`)
|
// We treat empty RootCollisionNodes as NCC flag (set collisionType to `Camera`)
|
||||||
// and generate the camera collision shape based on rendered geometry.
|
// and generate the camera collision shape based on rendered geometry.
|
||||||
if (visualCollisionType == Resource::VisualCollisionType::Camera)
|
if (mShape->mVisualCollisionType == Resource::VisualCollisionType::Camera)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Otherwise we'll want to notify the user.
|
// 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.";
|
<< ". Treating it as a common NiTriShape.";
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
args.mIsCollisionNode = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't collide with AvoidNode shapes
|
// Don't collide with AvoidNode shapes
|
||||||
|
@ -330,10 +365,10 @@ namespace NifBullet
|
||||||
// uppercase
|
// uppercase
|
||||||
if (sd->mData.length() > 2 && sd->mData[2] == 'C')
|
if (sd->mData.length() > 2 && sd->mData[2] == 'C')
|
||||||
// Collide only with camera.
|
// Collide only with camera.
|
||||||
visualCollisionType = Resource::VisualCollisionType::Camera;
|
mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera;
|
||||||
else
|
else
|
||||||
// No collision.
|
// No collision.
|
||||||
visualCollisionType = Resource::VisualCollisionType::Default;
|
mShape->mVisualCollisionType = Resource::VisualCollisionType::Default;
|
||||||
}
|
}
|
||||||
// Don't autogenerate collision if MRK is set.
|
// Don't autogenerate collision if MRK is set.
|
||||||
// FIXME: verify if this covers the entire subtree
|
// FIXME: verify if this covers the entire subtree
|
||||||
|
@ -342,15 +377,9 @@ namespace NifBullet
|
||||||
return;
|
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!
|
// NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape!
|
||||||
// It must be ignored completely.
|
// It must be ignored completely.
|
||||||
|
@ -369,7 +398,7 @@ namespace NifBullet
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
assert(std::find(child->mParents.begin(), child->mParents.end(), ninode) != child->mParents.end());
|
assert(std::find(child->mParents.begin(), child->mParents.end(), ninode) != child->mParents.end());
|
||||||
handleNode(fileName, child.get(), ¤tParent, args, visualCollisionType);
|
handleNode(child.get(), ¤tParent, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ namespace NifBullet
|
||||||
osg::ref_ptr<Resource::BulletShape> load(Nif::FileView file);
|
osg::ref_ptr<Resource::BulletShape> load(Nif::FileView file);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool findBoundingBox(const Nif::NiAVObject& node, const std::string& filename);
|
bool findBoundingBox(const Nif::NiAVObject& node);
|
||||||
|
|
||||||
struct HandleNodeArgs
|
struct HandleNodeArgs
|
||||||
{
|
{
|
||||||
|
@ -59,11 +59,8 @@ namespace NifBullet
|
||||||
bool mAvoid{ false };
|
bool mAvoid{ false };
|
||||||
};
|
};
|
||||||
|
|
||||||
void handleNode(const std::string& fileName, const Nif::NiAVObject& node, const Nif::Parent* parent,
|
void handleRoot(Nif::FileView nif, const Nif::NiAVObject& node, HandleNodeArgs args);
|
||||||
HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType);
|
void handleNode(const Nif::NiAVObject& node, const Nif::Parent* parent, HandleNodeArgs args);
|
||||||
|
|
||||||
const Nif::NiNode* findRootCollisionNode(const Nif::NiAVObject& rootNode) const;
|
|
||||||
|
|
||||||
void handleNiTriShape(const Nif::NiGeometry& nifNode, 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;
|
std::unique_ptr<btCompoundShape, Resource::DeleteCollisionShape> mCompoundShape;
|
||||||
|
|
Loading…
Reference in a new issue