1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-30 16:20:21 +00:00

Merge branch 'metermarker' into 'master'

Don't discard collision for the entire tree if BSXFlags marker flag is set

See merge request OpenMW/openmw!3216
This commit is contained in:
psi29a 2023-07-11 07:35:51 +00:00
commit 1268dde8fe
3 changed files with 53 additions and 37 deletions

View File

@ -1158,12 +1158,13 @@ namespace
EXPECT_EQ(*result, expected); EXPECT_EQ(*result, expected);
} }
TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision) TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers)
{ {
mNiIntegerExtraData.data = 32; // BSX flag "editor marker" mNiIntegerExtraData.data = 32; // BSX flag "editor marker"
mNiIntegerExtraData.recType = Nif::RC_BSXFlags; mNiIntegerExtraData.recType = Nif::RC_BSXFlags;
mNiTriShape.extralist.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiTriShape.extralist.push_back(Nif::ExtraPtr(&mNiIntegerExtraData));
mNiTriShape.parents.push_back(&mNiNode); mNiTriShape.parents.push_back(&mNiNode);
mNiTriShape.name = "EditorMarker";
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({ Nif::NodePtr(&mNiTriShape) })); mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({ Nif::NodePtr(&mNiTriShape) }));
Nif::NIFFile file("test.nif"); Nif::NIFFile file("test.nif");

View File

@ -182,8 +182,10 @@ namespace NifBullet
if (hasCollisionNode && !hasCollisionShape) if (hasCollisionNode && !hasCollisionShape)
mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera;
bool generateCollisionShape = !hasCollisionShape; bool generateCollisionShape = !hasCollisionShape;
handleNode(filename, *node, nullptr, 0, generateCollisionShape, isAnimated, generateCollisionShape, false, HandleNodeArgs args;
mShape->mVisualCollisionType); args.mAutogenerated = args.mIsCollisionNode = generateCollisionShape;
args.mAnimated = isAnimated;
handleNode(filename, *node, nullptr, args, mShape->mVisualCollisionType);
} }
if (mCompoundShape) if (mCompoundShape)
@ -269,36 +271,37 @@ namespace NifBullet
} }
void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent,
int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType)
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())
return; return;
// If RootCollisionNode is empty we treat it as NCC flag and autogenerate collision shape as there was no
// RootCollisionNode. So ignoring it here if `autogenerated` is true and collisionType was set to `Camera`.
if (node.recType == Nif::RC_RootCollisionNode && autogenerated
&& visualCollisionType == Resource::VisualCollisionType::Camera)
return;
// Accumulate the flags from all the child nodes. This works for all
// the flags we currently use, at least.
flags |= node.flags;
if (!node.controller.empty() && node.controller->recType == Nif::RC_NiKeyframeController if (!node.controller.empty() && node.controller->recType == Nif::RC_NiKeyframeController
&& node.controller->isActive()) && node.controller->isActive())
isAnimated = true; args.mAnimated = true;
isCollisionNode = isCollisionNode || (node.recType == Nif::RC_RootCollisionNode); 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)
return;
// Otherwise we'll want to notify the user.
Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << fileName
<< ". Treating it as a common NiTriShape.";
}
}
// Don't collide with AvoidNode shapes // Don't collide with AvoidNode shapes
avoid = avoid || (node.recType == Nif::RC_AvoidNode); if (node.recType == Nif::RC_AvoidNode)
args.mAvoid = true;
// We encountered a RootCollisionNode inside autogenerated mesh. It is not right.
if (node.recType == Nif::RC_RootCollisionNode && autogenerated)
Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << fileName
<< ". Treating it as a common NiTriShape.";
// Check for extra data // Check for extra data
std::vector<Nif::ExtraPtr> extraCollection; std::vector<Nif::ExtraPtr> extraCollection;
@ -326,7 +329,7 @@ namespace NifBullet
// No collision. // No collision.
visualCollisionType = Resource::VisualCollisionType::Default; visualCollisionType = Resource::VisualCollisionType::Default;
} }
else if (sd->string == "MRK" && autogenerated) else if (sd->string == "MRK" && args.mAutogenerated)
{ {
// Marker can still have collision if the model explicitely specifies it via a RootCollisionNode. // Marker can still have collision if the model explicitely specifies it via a RootCollisionNode.
return; return;
@ -336,11 +339,11 @@ namespace NifBullet
{ {
auto bsxFlags = static_cast<const Nif::NiIntegerExtraData*>(e.getPtr()); auto bsxFlags = static_cast<const Nif::NiIntegerExtraData*>(e.getPtr());
if (bsxFlags->data & 32) // Editor marker flag if (bsxFlags->data & 32) // Editor marker flag
return; args.mHasMarkers = true;
} }
} }
if (isCollisionNode) if (args.mIsCollisionNode)
{ {
// NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape!
// It must be ignored completely. // It must be ignored completely.
@ -349,7 +352,7 @@ namespace NifBullet
&& (node.recType == Nif::RC_NiTriShape || node.recType == Nif::RC_NiTriStrips && (node.recType == Nif::RC_NiTriShape || node.recType == Nif::RC_NiTriStrips
|| node.recType == Nif::RC_BSLODTriShape)) || node.recType == Nif::RC_BSLODTriShape))
{ {
handleNiTriShape(static_cast<const Nif::NiGeometry&>(node), parent, isAnimated, avoid); handleNiTriShape(static_cast<const Nif::NiGeometry&>(node), parent, args);
} }
} }
@ -364,20 +367,24 @@ namespace NifBullet
continue; continue;
assert(std::find(child->parents.begin(), child->parents.end(), ninode) != child->parents.end()); assert(std::find(child->parents.begin(), child->parents.end(), ninode) != child->parents.end());
handleNode(fileName, child.get(), &currentParent, flags, isCollisionNode, isAnimated, autogenerated, handleNode(fileName, child.get(), &currentParent, args, visualCollisionType);
avoid, visualCollisionType);
} }
} }
} }
void BulletNifLoader::handleNiTriShape( void BulletNifLoader::handleNiTriShape(
const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, bool isAnimated, bool avoid) const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, HandleNodeArgs args)
{ {
// mHasMarkers is specifically BSXFlags editor marker flag.
// If this changes, the check must be corrected.
if (args.mHasMarkers && Misc::StringUtils::ciStartsWith(niGeometry.name, "EditorMarker"))
return;
if (niGeometry.data.empty() || niGeometry.data->vertices.empty()) if (niGeometry.data.empty() || niGeometry.data->vertices.empty())
return; return;
if (!niGeometry.skin.empty()) if (!niGeometry.skin.empty())
isAnimated = false; args.mAnimated = false;
std::unique_ptr<btTriangleMesh> childMesh = makeChildMesh(niGeometry); std::unique_ptr<btTriangleMesh> childMesh = makeChildMesh(niGeometry);
if (childMesh == nullptr || childMesh->getNumTriangles() == 0) if (childMesh == nullptr || childMesh->getNumTriangles() == 0)
@ -398,12 +405,12 @@ namespace NifBullet
for (int j = 0; j < 3; ++j) for (int j = 0; j < 3; ++j)
trans.getBasis()[i][j] = transform(j, i); trans.getBasis()[i][j] = transform(j, i);
if (!avoid) if (!args.mAvoid)
{ {
if (!mCompoundShape) if (!mCompoundShape)
mCompoundShape.reset(new btCompoundShape); mCompoundShape.reset(new btCompoundShape);
if (isAnimated) if (args.mAnimated)
mShape->mAnimatedShapes.emplace(niGeometry.recIndex, mCompoundShape->getNumChildShapes()); mShape->mAnimatedShapes.emplace(niGeometry.recIndex, mCompoundShape->getNumChildShapes());
mCompoundShape->addChildShape(trans, childShape.get()); mCompoundShape->addChildShape(trans, childShape.get());
} }

View File

@ -52,14 +52,22 @@ namespace NifBullet
private: private:
bool findBoundingBox(const Nif::Node& node, const std::string& filename); bool findBoundingBox(const Nif::Node& node, const std::string& filename);
void handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, int flags, struct HandleNodeArgs
bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, {
Resource::VisualCollisionType& visualCollisionType); bool mHasMarkers{ false };
bool mAnimated{ false };
bool mIsCollisionNode{ false };
bool mAutogenerated{ false };
bool mAvoid{ false };
};
void handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent,
HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType);
bool hasRootCollisionNode(const Nif::Node& rootNode) const; bool hasRootCollisionNode(const Nif::Node& rootNode) const;
bool collisionShapeIsEmpty(const Nif::Node& rootNode) const; bool collisionShapeIsEmpty(const Nif::Node& rootNode) const;
void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, bool isAnimated, bool avoid); 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;
std::unique_ptr<btCompoundShape, Resource::DeleteCollisionShape> mAvoidCompoundShape; std::unique_ptr<btCompoundShape, Resource::DeleteCollisionShape> mAvoidCompoundShape;