#include "KingSystem/Physics/System/physEntityGroupFilter.h" #include #include #include #include #include #include #include #include #include #include #include #include "KingSystem/Physics/RigidBody/physRigidBody.h" #include "KingSystem/Physics/System/physContactMgr.h" #include "KingSystem/Physics/System/physSystem.h" #include "KingSystem/Physics/physConversions.h" #include "KingSystem/Utils/BitField.h" #include "KingSystem/Utils/HeapUtil.h" namespace ksys::phys { constexpr int NumEntityHandlersInList0 = 0x10; constexpr int NumEntityHandlers = 0x400; void receiverMaskEnableLayer(SensorCollisionMask* mask, ContactLayer layer) { mask->raw |= 1 << getContactLayerBaseRelativeValue(layer); } bool receiverMaskGetSensorLayerMaskForType(SensorCollisionMask* mask, const sead::SafeString& receiver_type) { return System::instance()->getContactMgr()->getSensorLayerMask(mask, receiver_type); } void receiverMaskSetSensorLayerMask(SensorCollisionMask* mask, u32 layer_mask) { *mask = {}; mask->custom_receiver_data.layer_mask = layer_mask; mask->is_custom_receiver = true; } EntityGroupFilter* EntityGroupFilter::make(ContactLayer::ValueType first, ContactLayer::ValueType last, sead::Heap* heap) { auto* filter = util::alloc(heap, first, last); filter->initFilter(heap); return filter; } EntityGroupFilter::EntityGroupFilter(ContactLayer::ValueType first, ContactLayer::ValueType last) : GroupFilter(ContactLayerType::Entity, first, last) {} EntityGroupFilter::~EntityGroupFilter() = default; void EntityGroupFilter::doInit_(sead::Heap* heap) { // Allow any layer pairs to be ignored by a rigid body by default. mLayersThatCanBeIgnored.fill(0xffffffff); } hkBool EntityGroupFilter::shouldHandleGroundCollision(u32 infoA, u32 infoB, ContactLayer::ValueType layerA, ContactLayer::ValueType layerB) const { const EntityCollisionMask a{infoA}; const EntityCollisionMask b{infoB}; if (EntityCollisionMask(infoA | infoB).ground_col_mode != GroundCollisionMode::Normal) { if (a.ground_col_mode != GroundCollisionMode::Normal) { bool ground = isEntityGroundLayer(layerB); if (a.ground_col_mode == GroundCollisionMode::IgnoreNonGround && !ground) return false; if (a.ground_col_mode == GroundCollisionMode::IgnoreGround && ground) return false; } if (b.ground_col_mode != GroundCollisionMode::Normal) { bool ground = isEntityGroundLayer(layerA); if (b.ground_col_mode == GroundCollisionMode::IgnoreNonGround && !ground) return false; if (b.ground_col_mode == GroundCollisionMode::IgnoreGround && ground) return false; } } return true; } hkBool EntityGroupFilter::shouldHandleWaterCollision(u32 infoA, u32 infoB, ContactLayer::ValueType layerA, ContactLayer::ValueType layerB) const { const EntityCollisionMask a{infoA}; const EntityCollisionMask b{infoB}; if (EntityCollisionMask(infoA | infoB).water_col_mode != WaterCollisionMode::Normal) { if (a.water_col_mode == WaterCollisionMode::IgnoreWater && layerB == ContactLayer::EntityWater) { return false; } if (b.water_col_mode == WaterCollisionMode::IgnoreWater && layerA == ContactLayer::EntityWater) { return false; } } return true; } // XXX: find a better name static bool testHandler(u32 idx) { return idx != 0 && idx <= 15; } // NON_MATCHING: deduplicated branch: `return a.data.query_custom_receiver_layer_mask & (1 << ...)` hkBool EntityGroupFilter::testCollisionForEntities(u32 infoA, u32 infoB) const { if (mInhibitCollisions) return false; const EntityCollisionMask a{infoA}; const EntityCollisionMask b{infoB}; if (!EntityCollisionMask(infoA | infoB).is_ground_hit_mask) { constexpr auto GroupHandlerIdxMask = decltype(a.regular.group_handler_index)::GetMask(); constexpr auto GroupHandlerIdxShift = decltype(a.regular.group_handler_index)::StartBit(); if (a.is_ragdoll && b.is_ragdoll) { if (((infoA ^ infoB) & GroupHandlerIdxMask) != 0) { if (testHandler(a.regular.group_handler_index) || testHandler(b.regular.group_handler_index)) { return false; } } else if ((infoA & GroupHandlerIdxMask) >> GroupHandlerIdxShift != 0) { if (a.regular.ragdoll_bone_index == b.regular.ragdoll_parent_bone_index || b.regular.ragdoll_bone_index == a.regular.ragdoll_parent_bone_index) return false; } return true; } const auto layerA = static_cast(a.regular.layer.Value()); const auto layerB = static_cast(b.regular.layer.Value()); if (layerA != ContactLayer::EntityQueryCustomReceiver && layerB != ContactLayer::EntityQueryCustomReceiver) { if (!a.is_ragdoll && !b.is_ragdoll) { if (!shouldHandleGroundCollision(infoA, infoB, layerA, layerB)) return false; if (!shouldHandleWaterCollision(infoA, infoB, layerA, layerB)) return false; } if (((infoA ^ infoB) & GroupHandlerIdxMask) != 0) { if (testHandler(a.regular.group_handler_index) || testHandler(b.regular.group_handler_index)) { return false; } } else if (((infoA & GroupHandlerIdxMask) >> GroupHandlerIdxShift) > 15) { return false; } return testLayerCollision(layerA, layerB); } if (layerA == ContactLayer::EntityQueryCustomReceiver && layerB == ContactLayer::EntityQueryCustomReceiver) { return false; } if (layerA == ContactLayer::EntityQueryCustomReceiver) return a.regular.query_custom_receiver_layer_mask & (1 << layerB); else return b.regular.query_custom_receiver_layer_mask & (1 << layerA); } if (a.is_ground_hit_mask && b.is_ground_hit_mask) { const auto layerA = static_cast(a.ground_hit.layer.Value()); const auto layerB = static_cast(b.ground_hit.layer.Value()); if (!shouldHandleGroundCollision(infoA, infoB, layerA, layerB)) return false; if (!shouldHandleWaterCollision(infoA, infoB, layerA, layerB)) return false; if (!testLayerCollision(layerA, layerB)) return false; return !a.ground_hit.unk23 && !b.ground_hit.unk23; } EntityCollisionMask entity_mask, ground_hit_mask; if (a.is_ground_hit_mask && !b.is_ground_hit_mask) { const auto layerA = static_cast(a.ground_hit.layer.Value()); const auto layerB = static_cast(b.regular.layer.Value()); entity_mask = b; ground_hit_mask = a; if (layerB == ContactLayer::EntityQueryCustomReceiver) return b.regular.query_custom_receiver_layer_mask & (1 << layerA); if (!b.is_ragdoll && !shouldHandleGroundCollision(infoA, infoB, layerA, layerB)) return false; if (!b.is_ragdoll && !shouldHandleWaterCollision(infoA, infoB, layerA, layerB)) return false; if (!testLayerCollision(layerA, layerB)) return false; } else /* A entity, B ground hit */ { const auto layerA = static_cast(a.regular.layer.Value()); const auto layerB = static_cast(b.ground_hit.layer.Value()); entity_mask = a; ground_hit_mask = b; if (layerA == ContactLayer::EntityQueryCustomReceiver) return a.regular.query_custom_receiver_layer_mask & (1 << layerB); if (!a.is_ragdoll && !shouldHandleGroundCollision(infoA, infoB, layerA, layerB)) return false; if (!a.is_ragdoll && !shouldHandleWaterCollision(infoA, infoB, layerA, layerB)) return false; if (!testLayerCollision(layerB, layerA)) return false; } return !(ground_hit_mask.ground_hit.ground_hit_types & (1 << entity_mask.regular.ground_hit)); } hkBool EntityGroupFilter::testCollisionForPhantom(u32 infoPhantom, u32 infoB) const { if (mInhibitCollisions) return false; EntityQueryCollisionMask infoPhantomData{infoPhantom}; const EntityCollisionMask info{infoB}; if (info.is_ground_hit_mask) return infoPhantomData.raw & (1 << info.ground_hit.getLayer()); return infoPhantomData.layer_mask & (1 << info.regular.layer); } hkBool EntityGroupFilter::isCollisionEnabled(const hkpCollidable& a, const hkpCollidable& b) const { if (a.getType() == hkpWorldObject::BROAD_PHASE_PHANTOM && b.getType() == hkpWorldObject::BROAD_PHASE_PHANTOM) { return false; } if (a.getType() == hkpWorldObject::BROAD_PHASE_PHANTOM) { if (a.getShape() != nullptr) return testCollisionForEntities(a.getCollisionFilterInfo(), b.getCollisionFilterInfo()); return testCollisionForPhantom(a.getCollisionFilterInfo(), b.getCollisionFilterInfo()); } if (b.getType() == hkpWorldObject::BROAD_PHASE_PHANTOM) { if (b.getShape() != nullptr) return testCollisionForEntities(a.getCollisionFilterInfo(), b.getCollisionFilterInfo()); return testCollisionForPhantom(b.getCollisionFilterInfo(), a.getCollisionFilterInfo()); } return testCollisionForEntities(a.getCollisionFilterInfo(), b.getCollisionFilterInfo()); } hkBool EntityGroupFilter::isCollisionEnabled(const hkpCollisionInput& input, const hkpCdBody& collectionBodyA, const hkpCdBody& collectionBodyB, const hkpShapeContainer& containerShapeA, const hkpShapeContainer& containerShapeB, hkpShapeKey keyA, hkpShapeKey keyB) const { auto infoA = containerShapeA.getCollisionFilterInfo(keyA); if (infoA == 0xffffffff) infoA = collectionBodyA.getRootCollidable()->getCollisionFilterInfo(); auto infoB = containerShapeB.getCollisionFilterInfo(keyB); if (infoB == 0xffffffff) infoB = collectionBodyB.getRootCollidable()->getCollisionFilterInfo(); return testCollisionForEntities(infoA, infoB); } hkBool EntityGroupFilter::isCollisionEnabled(const hkpCollisionInput& input, const hkpCdBody& a, const hkpCdBody& b, const hkpShapeContainer& bContainer, hkpShapeKey bKey) const { u32 infoB = bContainer.getCollisionFilterInfo(bKey); if (infoB == 0xffffffff) infoB = b.getRootCollidable()->getCollisionFilterInfo(); u32 infoA = static_cast(a).getCollisionFilterInfo(); if (a.getParent() != nullptr) { if (mInhibitCollisions) return false; hkpCollisionDispatcher* dispatcher = input.m_dispatcher; auto* collidable = &a; auto* parent = collidable->getParent(); while (parent) { auto* shape = parent->m_shape; if (dispatcher->hasAlternateType(shape->m_type, hkcdShapeType::COLLECTION)) { auto* collection = static_cast(shape); infoA = collection->getCollisionFilterInfo(collidable->getShapeKey()); goto end; } if (dispatcher->hasAlternateType(shape->m_type, hkcdShapeType::BV_TREE)) { auto* container = shape->getContainer(); infoA = container->getCollisionFilterInfo(collidable->getShapeKey()); goto end; } if (dispatcher->hasAlternateType(shape->m_type, hkcdShapeType::MULTI_SPHERE)) { infoA = a.getRootCollidable()->getCollisionFilterInfo(); goto end; } if (dispatcher->hasAlternateType(shape->m_type, hkcdShapeType::CONVEX_LIST)) { return true; } collidable = parent; parent = collidable->getParent(); infoA = static_cast(collidable)->getCollisionFilterInfo(); } } end: return testCollisionForEntities(infoA, infoB); } static hkBool checkCollisionWithGroundHitMask(EntityCollisionMask::GroundHitMask ground_hit_mask, EntityQueryCollisionMask ray_cast) { if (!(ray_cast.layer_mask & (1 << ground_hit_mask.getLayer()))) return false; if (ground_hit_mask.unk & ray_cast.unk) return false; if (ground_hit_mask.ground_hit_types & (1 << ray_cast.ground_hit_type)) return false; return true; } hkBool EntityGroupFilter::testCollisionForRayCasting(u32 infoRayCast, u32 info) const { if (mInhibitCollisions) return false; EntityQueryCollisionMask a{infoRayCast}; EntityCollisionMask b{info}; if (b.is_ground_hit_mask) return checkCollisionWithGroundHitMask(b.ground_hit, a); const u32 bHandlerIdx = b.regular.group_handler_index; const u32 aHandlerIdx = a.group_handler_index; if (aHandlerIdx == bHandlerIdx) { if (bHandlerIdx > 15) return false; return a.layer_mask & (1 << b.regular.layer); } if (testHandler(aHandlerIdx) || testHandler(bHandlerIdx)) return false; return a.layer_mask & (1 << b.regular.layer); } KSYS_ALWAYS_INLINE hkBool EntityGroupFilter::isCollisionEnabled(const hkpShapeRayCastInput& aInput, const hkpShapeContainer& bContainer, hkpShapeKey bKey) const { u32 bInfo = bContainer.getCollisionFilterInfo(bKey); if (bInfo == 0) return true; if (bInfo == 0xffffffff) bInfo = aInput.m_collidable->getCollisionFilterInfo(); return testCollisionForRayCasting(aInput.m_filterInfo, bInfo); } hkBool EntityGroupFilter::isCollisionEnabled(const hkpWorldRayCastInput& inputA, const hkpCollidable& collidableB) const { auto* body = getRigidBody(collidableB); if (body && body->hasFlag(RigidBody::Flag::_200)) return false; return testCollisionForRayCasting(inputA.m_filterInfo, collidableB.getCollisionFilterInfo()); } void EntityGroupFilter::doInitSystemGroupHandlerLists_(sead::Heap* heap) { for (auto& list : mFreeLists) list.initOffset(SystemGroupHandler::getFreeListNodeOffset()); mUsedList.initOffset(SystemGroupHandler::getUsedListNodeOffset()); for (int i = 1; i < NumEntityHandlers; ++i) { auto& list = mFreeLists[i < NumEntityHandlersInList0]; list.pushBack(new (heap) EntitySystemGroupHandler(i)); } } int EntityGroupFilter::getFreeListIndex(const SystemGroupHandler* handler) { return handler->getIndex() < NumEntityHandlersInList0; } u32 orEntityGroundHitMask(u32 mask, GroundHit type) { EntityCollisionMask info{mask}; info.ground_hit.ground_hit_types |= 1 << type; return info.raw; } u32 orEntityGroundHitMask(u32 mask, const sead::SafeString& type) { return orEntityGroundHitMask(mask, groundHitFromText(type)); } template static EntityCollisionMask makeEntityGroundHitMaskImpl(ContactLayer layer, u32 mask) { const EntityCollisionMask current{mask}; EntityCollisionMask info{}; info.ground_hit.layer.SetUnsafe(layer); info.ground_hit.ground_hit_types = current.ground_hit.ground_hit_types; info.is_ground_hit_mask = true; if constexpr (WithUnk) info.ground_hit.unk = current.ground_hit.unk & 1; return info; } u32 makeEntityGroundHitMask(ContactLayer layer, u32 mask) { return makeEntityGroundHitMaskImpl(layer, mask).raw; } u32 makeEntityCollisionMask(ContactLayer layer, u32 mask) { EntityCollisionMask current{mask}; if (current.is_ground_hit_mask) { return makeEntityGroundHitMaskImpl(layer, mask).raw; } else { current.regular.layer.SetUnsafe(layer); return current.raw; } } u32 setEntityCollisionMaskGroundHit(GroundHit ground_hit, u32 mask) { EntityCollisionMask current{mask}; if (current.is_ground_hit_mask) { // This shouldn't happen: this function is not supposed to be called on ground hit masks. } else { current.regular.ground_hit.SetUnsafe(ground_hit); } return current.raw; } } // namespace ksys::phys