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:
parent
68799cfd2b
commit
d05a2facf3
16 changed files with 114 additions and 38 deletions
|
@ -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,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);
|
||||
}
|
||||
|
|
|
@ -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(), ¤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<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…
Reference in a new issue