diff --git a/CHANGELOG.md b/CHANGELOG.md index 525cd5807b..846aaea290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 6ce78e569f..cf1458c883 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -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 options) diff --git a/apps/openmw/mwphysics/collisiontype.hpp b/apps/openmw/mwphysics/collisiontype.hpp index e69534cf68..b51a22a2f5 100644 --- a/apps/openmw/mwphysics/collisiontype.hpp +++ b/apps/openmw/mwphysics/collisiontype.hpp @@ -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 }; } diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index fd8a7f2b05..da07d5975b 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -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(); diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 0f05ccdbb1..cb8e520384 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -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); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 06c2420df1..0dd177e4b8 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -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(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, diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 7e83a08493..ea499a64bc 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -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), diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index f84fbe2508..f2f6cb3985 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -1009,7 +1009,53 @@ 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 = "NCC__"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parents.push_back(&mNiNode); + mNiNode.children = Nif::NodeList(std::vector({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 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_equal_ncc_should_return_shape_with_cameraonly_collision) + { + 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(&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 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; @@ -1022,12 +1068,16 @@ namespace EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); + std::unique_ptr 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_null_collision_shape) + 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___"; @@ -1041,7 +1091,11 @@ namespace EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); + std::unique_ptr 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); } diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 8eb14f9b01..cbcb3cabe6 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -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; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 3b0eacfbd7..52be8d4a07 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -17,6 +17,8 @@ #include #include +#include + namespace { @@ -215,7 +217,7 @@ osg::ref_ptr 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(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(), ¤tParent, flags, isCollisionNode, isAnimated, autogenerated, avoid); + handleNode(fileName, list[i].get(), ¤tParent, 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(nifNode), parent, transform, isAnimated, avoid); -} - void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, const osg::Matrixf &transform, bool isAnimated, bool avoid) { diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 01a17a5aa1..e90c882bc3 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -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); diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 74515691c6..2d7fd87aed 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -96,6 +96,7 @@ BulletShapeInstance::BulletShapeInstance(osg::ref_ptr source) { mCollisionBox = mSource->mCollisionBox; mAnimatedShapes = mSource->mAnimatedShapes; + mCollisionType = mSource->mCollisionType; mCollisionShape = duplicateCollisionShape(mSource->mCollisionShape.get()); mAvoidCollisionShape = duplicateCollisionShape(mSource->mAvoidCollisionShape.get()); } diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index cd8922ec8e..63db8ec482 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -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 + }; }; diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 04252e1b47..56e7f52487 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -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 diff --git a/files/builtin_scripts/scripts/omw/third_person.lua b/files/builtin_scripts/scripts/omw/third_person.lua index 1b833d2322..311dbde48b 100644 --- a/files/builtin_scripts/scripts/omw/third_person.lua +++ b/files/builtin_scripts/scripts/omw/third_person.lua @@ -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 diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index ba27269700..f290992f33 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -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`.