1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-03-03 07:09:40 +00:00

Handle NCC flag in Nif files. Objects with this flag will collide only with camera.

Expose objects with NC flag to be used by Lua mods.
This commit is contained in:
fredzio 2021-04-11 10:28:56 +02:00
parent 68799cfd2b
commit d05a2facf3
16 changed files with 114 additions and 38 deletions

View file

@ -140,6 +140,7 @@
Feature #6380: Commas are treated as whitespace in vanilla
Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference
Feature #6443: Support NiStencilProperty
Feature #6496: NCC flag isn't handled properly
Feature #6534: Shader-based object texture blending
Feature #6541: Gloss-mapping
Feature #6592: Missing support for NiTriShape particle emitters

View file

@ -54,7 +54,10 @@ namespace MWLua
{"HeightMap", MWPhysics::CollisionType_HeightMap},
{"Projectile", MWPhysics::CollisionType_Projectile},
{"Water", MWPhysics::CollisionType_Water},
{"Default", MWPhysics::CollisionType_Default}
{"Default", MWPhysics::CollisionType_Default},
{"AnyPhysical", MWPhysics::CollisionType_AnyPhysical},
{"Camera", MWPhysics::CollisionType_CameraOnly},
{"VisualOnly", MWPhysics::CollisionType_VisualOnly},
}));
api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional<sol::table> options)

View file

@ -11,7 +11,10 @@ enum CollisionType {
CollisionType_HeightMap = 1<<3,
CollisionType_Projectile = 1<<4,
CollisionType_Water = 1<<5,
CollisionType_Default = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door
CollisionType_Default = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door,
CollisionType_AnyPhysical = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door|CollisionType_Projectile|CollisionType_Water,
CollisionType_CameraOnly = 1<<6,
CollisionType_VisualOnly = 1<<7
};
}

View file

@ -101,7 +101,7 @@ namespace MWPhysics
btVector3 to = from - btVector3(0,0,maxHeight);
btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to);
resultCallback1.m_collisionFilterGroup = 0xff;
resultCallback1.m_collisionFilterGroup = CollisionType_AnyPhysical;
resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap;
collisionWorld->rayTest(from, to, resultCallback1);
@ -426,7 +426,7 @@ namespace MWPhysics
return;
ProjectileConvexCallback resultCallback(projectile.mCaster, projectile.mCollisionObject, btFrom, btTo, projectile.mProjectile);
resultCallback.m_collisionFilterMask = 0xff;
resultCallback.m_collisionFilterMask = CollisionType_AnyPhysical;
resultCallback.m_collisionFilterGroup = CollisionType_Projectile;
const btQuaternion btrot = btQuaternion::getIdentity();

View file

@ -661,7 +661,7 @@ namespace MWPhysics
btVector3 pos2 = Misc::Convert::toBullet(actor2->getCollisionObjectPosition() + osg::Vec3f(0,0,actor2->getHalfExtents().z() * 0.9));
btCollisionWorld::ClosestRayResultCallback resultCallback(pos1, pos2);
resultCallback.m_collisionFilterGroup = 0xFF;
resultCallback.m_collisionFilterGroup = CollisionType_AnyPhysical;
resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door;
MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads);

View file

@ -490,6 +490,17 @@ namespace MWPhysics
assert(!getObject(ptr));
// Override collision type based on shape content.
switch (shapeInstance->mCollisionType)
{
case Resource::BulletShape::CollisionType::Camera:
collisionType = CollisionType_CameraOnly;
break;
case Resource::BulletShape::CollisionType::None:
collisionType = CollisionType_VisualOnly;
break;
}
auto obj = std::make_shared<Object>(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get());
mObjects.emplace(ptr.mRef, obj);
@ -905,7 +916,7 @@ namespace MWPhysics
const auto aabbMin = bulletPosition - btVector3(radius, radius, radius);
const auto aabbMax = bulletPosition + btVector3(radius, radius, radius);
const int mask = MWPhysics::CollisionType_Actor;
const int group = 0xff;
const int group = MWPhysics::CollisionType_AnyPhysical;
if (occupyingActors == nullptr)
{
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter,

View file

@ -53,7 +53,7 @@ namespace MWRender
Camera::Camera (osg::Camera* camera)
: mHeightScale(1.f),
mCollisionType(MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor),
mCollisionType((MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor) | MWPhysics::CollisionType_CameraOnly),
mCamera(camera),
mAnimation(nullptr),
mFirstPersonView(true),

View file

@ -1009,9 +1009,9 @@ namespace
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape)
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision)
{
mNiStringExtraData.string = "NC___";
mNiStringExtraData.string = "NCC__";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.parents.push_back(&mNiNode);
@ -1022,12 +1022,16 @@ namespace
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile);
std::unique_ptr<btTriangleMesh> triangles(new btTriangleMesh(false));
triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0));
Resource::BulletShape expected;
expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true));
expected.mCollisionType = Resource::BulletShape::CollisionType::Camera;
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape)
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision)
{
mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.string = "NC___";
@ -1041,7 +1045,57 @@ namespace
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile);
std::unique_ptr<btTriangleMesh> triangles(new btTriangleMesh(false));
triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0));
Resource::BulletShape expected;
expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true));
expected.mCollisionType = Resource::BulletShape::CollisionType::Camera;
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision)
{
mNiStringExtraData.string = "NC___";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.parents.push_back(&mNiNode);
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile);
std::unique_ptr<btTriangleMesh> triangles(new btTriangleMesh(false));
triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0));
Resource::BulletShape expected;
expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true));
expected.mCollisionType = Resource::BulletShape::CollisionType::None;
EXPECT_EQ(*result, expected);
}
TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision)
{
mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.string = "NC___";
mNiStringExtraData2.recType = Nif::RC_NiStringExtraData;
mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
mNiTriShape.parents.push_back(&mNiNode);
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile);
std::unique_ptr<btTriangleMesh> triangles(new btTriangleMesh(false));
triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0));
Resource::BulletShape expected;
expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true));
expected.mCollisionType = Resource::BulletShape::CollisionType::None;
EXPECT_EQ(*result, expected);
}

View file

@ -55,9 +55,10 @@ struct NiTextKeyExtraData : public Extra
struct NiStringExtraData : public Extra
{
/* Two known meanings:
/* Known meanings:
"MRK" - marker, only visible in the editor, not rendered in-game
"NCO" - no collision
"NCC" - no collision except with the camera
Anything else starting with "NC" - no collision
*/
std::string string;

View file

@ -17,6 +17,8 @@
#include <components/nif/extra.hpp>
#include <components/nif/parent.hpp>
#include <components/settings/settings.hpp>
namespace
{
@ -215,7 +217,7 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
for (const Nif::Node* node : roots)
{
bool autogenerated = hasAutoGeneratedCollision(*node);
handleNode(filename, *node, nullptr, 0, autogenerated, isAnimated, autogenerated);
handleNode(filename, *node, nullptr, 0, autogenerated, isAnimated, autogenerated, false, mShape->mCollisionType);
}
if (mCompoundShape)
@ -307,7 +309,7 @@ bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node& rootNode)
}
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)
int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, unsigned int& collisionType)
{
// TODO: allow on-the fly collision switching via toggling this flag
if (node.recType == Nif::RC_NiCollisionSwitch && !(node.flags & Nif::NiNode::Flag_ActiveCollision))
@ -341,8 +343,13 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n
if (Misc::StringUtils::ciCompareLen(sd->string, "NC", 2) == 0)
{
// No collision. Use an internal flag setting to mark this.
flags |= 0x800;
// NCC flag in vanilla is partly case sensitive: prefix NC is case insensitive but second C needs be uppercase
if (sd->string.length() > 2 && sd->string[2] == 'C')
// Collide only with camera.
collisionType = Resource::BulletShape::CollisionType::Camera;
else
// No collision.
collisionType = Resource::BulletShape::CollisionType::None;
}
else if (sd->string == "MRK" && autogenerated)
{
@ -362,7 +369,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n
|| node.recType == Nif::RC_NiTriStrips
|| node.recType == Nif::RC_BSLODTriShape))
{
handleNiTriShape(node, parent, flags, getWorldTransform(node, parent), isAnimated, avoid);
handleNiTriShape(static_cast<const Nif::NiGeometry&>(node), parent, getWorldTransform(node, parent), isAnimated, avoid);
}
}
@ -377,22 +384,11 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n
continue;
assert(std::find(list[i]->parents.begin(), list[i]->parents.end(), ninode) != list[i]->parents.end());
handleNode(fileName, list[i].get(), &currentParent, flags, isCollisionNode, isAnimated, autogenerated, avoid);
handleNode(fileName, list[i].get(), &currentParent, flags, isCollisionNode, isAnimated, autogenerated, avoid, collisionType);
}
}
}
void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, const Nif::Parent* parent, int flags,
const osg::Matrixf &transform, bool isAnimated, bool avoid)
{
// If the object was marked "NCO" earlier, it shouldn't collide with
// anything. So don't do anything.
if ((flags & 0x800))
return;
handleNiTriShape(static_cast<const Nif::NiGeometry&>(nifNode), parent, transform, isAnimated, avoid);
}
void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent,
const osg::Matrixf &transform, bool isAnimated, bool avoid)
{

View file

@ -56,14 +56,11 @@ public:
private:
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,
bool isCollisionNode, bool isAnimated=false, bool autogenerated=false, bool avoid=false);
void handleNode(const std::string& fileName, const Nif::Node& node,const Nif::Parent* parent, int flags,
bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, unsigned int& cameraOnlyCollision);
bool hasAutoGeneratedCollision(const Nif::Node& rootNode);
void handleNiTriShape(const Nif::Node& nifNode, const Nif::Parent* parent, int flags, const osg::Matrixf& transform,
bool isAnimated, bool avoid);
void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, const osg::Matrixf& transform,
bool isAnimated, bool avoid);

View file

@ -96,6 +96,7 @@ BulletShapeInstance::BulletShapeInstance(osg::ref_ptr<const BulletShape> source)
{
mCollisionBox = mSource->mCollisionBox;
mAnimatedShapes = mSource->mAnimatedShapes;
mCollisionType = mSource->mCollisionType;
mCollisionShape = duplicateCollisionShape(mSource->mCollisionShape.get());
mAvoidCollisionShape = duplicateCollisionShape(mSource->mAvoidCollisionShape.get());
}

View file

@ -59,6 +59,13 @@ namespace Resource
void setLocalScaling(const btVector3& scale);
bool isAnimated() const { return !mAnimatedShapes.empty(); }
unsigned int mCollisionType = 0;
enum CollisionType
{
None = 0x1,
Camera = 0x2
};
};

View file

@ -24,7 +24,7 @@ local noHeadBobbing = 0
local noZoom = 0
local function init()
camera.setCollisionType(util.bitAnd(nearby.COLLISION_TYPE.Default, util.bitNot(nearby.COLLISION_TYPE.Actor)))
camera.setCollisionType(util.bitOr(util.bitAnd(nearby.COLLISION_TYPE.Default, util.bitNot(nearby.COLLISION_TYPE.Actor)), nearby.COLLISION_TYPE.Camera))
camera.setFieldOfView(camera.getBaseFieldOfView())
camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation'))
if camera.getMode() == MODE.FirstPerson then

View file

@ -28,10 +28,9 @@ local combatOffset = util.vector2(0, 15)
local state = defaultShoulder
local rayOptions = {collisionType = nearby.COLLISION_TYPE.Default - nearby.COLLISION_TYPE.Actor}
local function ray(from, angle, limit)
local to = from + util.transform.rotateZ(angle) * util.vector3(0, limit, 0)
local res = nearby.castRay(from, to, rayOptions)
local res = nearby.castRay(from, to, {collisionType = camera.getCollisionType()})
if res.hit then
return (res.hitPos - from):length()
else

View file

@ -34,7 +34,10 @@
-- @field [parent=#COLLISION_TYPE] #number HeightMap
-- @field [parent=#COLLISION_TYPE] #number Projectile
-- @field [parent=#COLLISION_TYPE] #number Water
-- @field [parent=#COLLISION_TYPE] #number Default Used by deafult: World+Door+Actor+HeightMap
-- @field [parent=#COLLISION_TYPE] #number Default Used by default: World+Door+Actor+HeightMap
-- @field [parent=#COLLISION_TYPE] #number AnyPhysical : World+Door+Actor+HeightMap+Projectile+Water
-- @field [parent=#COLLISION_TYPE] #number Camera Objects that should collide only with camera
-- @field [parent=#COLLISION_TYPE] #number VisualOnly Objects that were not intended to be part of the physics world
---
-- Collision types that are used in `castRay`.