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 #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

@ -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)

@ -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
};
}

@ -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();

@ -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);

@ -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,

@ -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),

@ -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>({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.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<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_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<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);
}

@ -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;

@ -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)
{

@ -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);

@ -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());
}

@ -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
};
};

@ -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

@ -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

@ -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`.

Loading…
Cancel
Save