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.
pull/3226/head
fredzio 4 years ago
parent 68799cfd2b
commit d05a2facf3

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

@ -54,7 +54,10 @@ namespace MWLua
{"HeightMap", MWPhysics::CollisionType_HeightMap}, {"HeightMap", MWPhysics::CollisionType_HeightMap},
{"Projectile", MWPhysics::CollisionType_Projectile}, {"Projectile", MWPhysics::CollisionType_Projectile},
{"Water", MWPhysics::CollisionType_Water}, {"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) api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional<sol::table> options)

@ -11,7 +11,10 @@ enum CollisionType {
CollisionType_HeightMap = 1<<3, CollisionType_HeightMap = 1<<3,
CollisionType_Projectile = 1<<4, CollisionType_Projectile = 1<<4,
CollisionType_Water = 1<<5, 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
}; };
} }

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

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

@ -490,6 +490,17 @@ namespace MWPhysics
assert(!getObject(ptr)); 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()); auto obj = std::make_shared<Object>(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get());
mObjects.emplace(ptr.mRef, obj); mObjects.emplace(ptr.mRef, obj);
@ -905,7 +916,7 @@ namespace MWPhysics
const auto aabbMin = bulletPosition - btVector3(radius, radius, radius); const auto aabbMin = bulletPosition - btVector3(radius, radius, radius);
const auto aabbMax = bulletPosition + btVector3(radius, radius, radius); const auto aabbMax = bulletPosition + btVector3(radius, radius, radius);
const int mask = MWPhysics::CollisionType_Actor; const int mask = MWPhysics::CollisionType_Actor;
const int group = 0xff; const int group = MWPhysics::CollisionType_AnyPhysical;
if (occupyingActors == nullptr) if (occupyingActors == nullptr)
{ {
HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter, HasSphereCollisionCallback callback(bulletPosition, radius, mask, group, ignoreFilter,

@ -53,7 +53,7 @@ namespace MWRender
Camera::Camera (osg::Camera* camera) Camera::Camera (osg::Camera* camera)
: mHeightScale(1.f), : 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), mCamera(camera),
mAnimation(nullptr), mAnimation(nullptr),
mFirstPersonView(true), mFirstPersonView(true),

@ -1009,7 +1009,53 @@ namespace
EXPECT_EQ(*result, expected); 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>({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::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>({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::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.string = "NC___";
mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
@ -1022,12 +1068,16 @@ namespace
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile); 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; Resource::BulletShape expected;
expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true));
expected.mCollisionType = Resource::BulletShape::CollisionType::None;
EXPECT_EQ(*result, expected); 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); mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2);
mNiStringExtraData2.string = "NC___"; mNiStringExtraData2.string = "NC___";
@ -1041,7 +1091,11 @@ namespace
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile); 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; Resource::BulletShape expected;
expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true));
expected.mCollisionType = Resource::BulletShape::CollisionType::None;
EXPECT_EQ(*result, expected); EXPECT_EQ(*result, expected);
} }

@ -55,9 +55,10 @@ struct NiTextKeyExtraData : public Extra
struct NiStringExtraData : public Extra struct NiStringExtraData : public Extra
{ {
/* Two known meanings: /* Known meanings:
"MRK" - marker, only visible in the editor, not rendered in-game "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; std::string string;

@ -17,6 +17,8 @@
#include <components/nif/extra.hpp> #include <components/nif/extra.hpp>
#include <components/nif/parent.hpp> #include <components/nif/parent.hpp>
#include <components/settings/settings.hpp>
namespace namespace
{ {
@ -215,7 +217,7 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
for (const Nif::Node* node : roots) for (const Nif::Node* node : roots)
{ {
bool autogenerated = hasAutoGeneratedCollision(*node); 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) 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, 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 // TODO: allow on-the fly collision switching via toggling this flag
if (node.recType == Nif::RC_NiCollisionSwitch && !(node.flags & Nif::NiNode::Flag_ActiveCollision)) 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) if (Misc::StringUtils::ciCompareLen(sd->string, "NC", 2) == 0)
{ {
// No collision. Use an internal flag setting to mark this. // NCC flag in vanilla is partly case sensitive: prefix NC is case insensitive but second C needs be uppercase
flags |= 0x800; 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) 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_NiTriStrips
|| node.recType == Nif::RC_BSLODTriShape)) || 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; continue;
assert(std::find(list[i]->parents.begin(), list[i]->parents.end(), ninode) != list[i]->parents.end()); 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, void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent,
const osg::Matrixf &transform, bool isAnimated, bool avoid) const osg::Matrixf &transform, bool isAnimated, bool avoid)
{ {

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

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

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

@ -24,7 +24,7 @@ local noHeadBobbing = 0
local noZoom = 0 local noZoom = 0
local function init() 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.setFieldOfView(camera.getBaseFieldOfView())
camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation')) camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation'))
if camera.getMode() == MODE.FirstPerson then if camera.getMode() == MODE.FirstPerson then

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

@ -34,7 +34,10 @@
-- @field [parent=#COLLISION_TYPE] #number HeightMap -- @field [parent=#COLLISION_TYPE] #number HeightMap
-- @field [parent=#COLLISION_TYPE] #number Projectile -- @field [parent=#COLLISION_TYPE] #number Projectile
-- @field [parent=#COLLISION_TYPE] #number Water -- @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`. -- Collision types that are used in `castRay`.

Loading…
Cancel
Save