@ -17,6 +17,7 @@
# include <components/nif/data.hpp>
# include <components/nif/extra.hpp>
# include <components/nif/nifstream.hpp>
# include <components/nif/node.hpp>
# include <components/nif/parent.hpp>
@ -162,18 +163,17 @@ namespace NifBullet
if ( node )
roots . emplace_back ( node ) ;
}
const std : : string filename = Files : : pathToUnicodeString ( nif . getFilename ( ) ) ;
mShape - > mFileName = filename ;
mShape - > mFileName = Files : : pathToUnicodeString ( nif . getFilename ( ) ) ;
if ( roots . empty ( ) )
{
warn ( " Found no root nodes in NIF file " + filen ame) ;
warn ( " Found no root nodes in NIF file " + mShape- > mFileN ame) ;
return mShape ;
}
// Try to find a valid bounding box first. If one's found for any root node, use that.
for ( const Nif : : NiAVObject * node : roots )
{
if ( findBoundingBox ( * node , filename ) )
// Try to find a valid bounding box first. If one's found for any root node, use that.
if ( findBoundingBox ( * node ) )
{
const btVector3 extents = Misc : : Convert : : toBullet ( mShape - > mCollisionBox . mExtents ) ;
const btVector3 center = Misc : : Convert : : toBullet ( mShape - > mCollisionBox . mCenter ) ;
@ -188,29 +188,18 @@ namespace NifBullet
return mShape ;
}
}
HandleNodeArgs args ;
// files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see
// Animation::addAnimSource). assume all nodes in the file will be animated
// TODO: investigate whether this should and could be optimized.
const bool isAnimated = pathFileNameStartsWithX ( filen ame) ;
args . mAnimated = pathFileNameStartsWithX ( mShape - > mFileN ame) ;
// If there's no bounding box, we'll have to generate a Bullet collision shape
// from the collision data present in every root node.
for ( const Nif : : NiAVObject * node : roots )
{
const Nif : : NiNode * colNode = findRootCollisionNode ( * node ) ;
bool hasCollisionShape = false ;
if ( colNode ! = nullptr )
{
if ( colNode - > mBounds . mType = = Nif : : BoundingVolume : : Type : : BASE_BV & & ! colNode - > mChildren . empty ( ) )
hasCollisionShape = true ;
else
mShape - > mVisualCollisionType = Resource : : VisualCollisionType : : Camera ;
}
HandleNodeArgs args ;
args . mAutogenerated = args . mIsCollisionNode = ! hasCollisionShape ;
args . mAnimated = isAnimated ;
handleNode ( filename , * node , nullptr , args , mShape - > mVisualCollisionType ) ;
}
handleRoot ( nif , * node , args ) ;
if ( mCompoundShape )
mShape - > mCollisionShape = std : : move ( mCompoundShape ) ;
@ -223,7 +212,7 @@ namespace NifBullet
// Find a boundingBox in the node hierarchy.
// Return: use bounding box for collision?
bool BulletNifLoader : : findBoundingBox ( const Nif : : NiAVObject & node , const std : : string & filename )
bool BulletNifLoader : : findBoundingBox ( const Nif : : NiAVObject & node )
{
unsigned int type = node . mBounds . mType ;
switch ( type )
@ -238,7 +227,7 @@ namespace NifBullet
{
std : : stringstream warning ;
warning < < " Unsupported BoundingVolume type " < < type < < " in node " < < node . recIndex ;
warning < < " in file " < < filen ame;
warning < < " in file " < < mShape- > mFileN ame;
warn ( warning . str ( ) ) ;
}
}
@ -249,27 +238,70 @@ namespace NifBullet
if ( const Nif : : NiNode * ninode = dynamic_cast < const Nif : : NiNode * > ( & node ) )
{
for ( const auto & child : ninode - > mChildren )
if ( ! child . empty ( ) & & findBoundingBox ( child . get ( ) , filename ))
if ( ! child . empty ( ) & & findBoundingBox ( child . get ( ) ))
return true ;
}
return false ;
}
const Nif : : NiNode * BulletNifLoader : : findRootCollisionNode ( const Nif : : NiAVObject & rootNode ) const
void BulletNifLoader : : handleRoot ( Nif : : FileView nif , const Nif : : NiAVObject & node , HandleNodeArgs args )
{
if ( const Nif : : NiNode * ninode = dynamic_cast < const Nif : : NiNode * > ( & rootNode ) )
// Gamebryo/Bethbryo meshes
if ( nif . getVersion ( ) > = Nif : : NIFStream : : generateVersion ( 10 , 0 , 1 , 0 ) )
{
// Handle BSXFlags
const Nif : : NiIntegerExtraData * bsxFlags = nullptr ;
for ( const auto & e : node . getExtraList ( ) )
{
if ( e - > recType = = Nif : : RC_BSXFlags )
{
bsxFlags = static_cast < const Nif : : NiIntegerExtraData * > ( e . getPtr ( ) ) ;
break ;
}
}
// Collision flag
if ( ! bsxFlags | | ! ( bsxFlags - > mData & 2 ) )
return ;
// Editor marker flag
if ( bsxFlags - > mData & 32 )
args . mHasMarkers = true ;
// FIXME: hack, using rendered geometry instead of Bethesda Havok data
args . mAutogenerated = true ;
}
// Pre-Gamebryo meshes
else
{
// Handle RootCollisionNode
const Nif : : NiNode * colNode = nullptr ;
if ( const Nif : : NiNode * ninode = dynamic_cast < const Nif : : NiNode * > ( & node ) )
{
for ( const auto & child : ninode - > mChildren )
{
if ( ! child . empty ( ) & & child . getPtr ( ) - > recType = = Nif : : RC_RootCollisionNode )
return static_cast < const Nif : : NiNode * > ( child . getPtr ( ) ) ;
{
colNode = static_cast < const Nif : : NiNode * > ( child . getPtr ( ) ) ;
break ;
}
}
return nullptr ;
}
void BulletNifLoader : : handleNode ( const std : : string & fileName , const Nif : : NiAVObject & node ,
const Nif : : Parent * parent , HandleNodeArgs args , Resource : : VisualCollisionType & visualCollisionType )
args . mAutogenerated = colNode = = nullptr ;
// FIXME: BulletNifLoader should never have to provide rendered geometry for camera collision
if ( colNode & & colNode - > mChildren . empty ( ) )
{
args . mAutogenerated = true ;
mShape - > mVisualCollisionType = Resource : : VisualCollisionType : : Camera ;
}
}
handleNode ( node , nullptr , args ) ;
}
void BulletNifLoader : : handleNode ( const Nif : : NiAVObject & node , const Nif : : Parent * parent , HandleNodeArgs args )
{
// TODO: allow on-the fly collision switching via toggling this flag
if ( node . recType = = Nif : : RC_NiCollisionSwitch & & ! node . collisionActive ( ) )
@ -295,20 +327,23 @@ namespace NifBullet
if ( node . recType = = Nif : : RC_RootCollisionNode )
{
args . mIsCollisionNode = true ;
if ( args . mAutogenerated )
{
// Encountered a RootCollisionNode inside an autogenerated mesh.
// We treat empty RootCollisionNodes as NCC flag (set collisionType to `Camera`)
// and generate the camera collision shape based on rendered geometry.
if ( v isualCollisionType = = Resource : : VisualCollisionType : : Camera )
if ( mShape- > mV isualCollisionType = = Resource : : VisualCollisionType : : Camera )
return ;
// Otherwise we'll want to notify the user.
Log ( Debug : : Info ) < < " RootCollisionNode is not attached to the root node in " < < f ileName
Log ( Debug : : Info ) < < " RootCollisionNode is not attached to the root node in " < < mShape- > mF ileName
< < " . Treating it as a common NiTriShape. " ;
}
else
{
args . mIsCollisionNode = true ;
}
}
// Don't collide with AvoidNode shapes
@ -330,10 +365,10 @@ namespace NifBullet
// uppercase
if ( sd - > mData . length ( ) > 2 & & sd - > mData [ 2 ] = = ' C ' )
// Collide only with camera.
v isualCollisionType = Resource : : VisualCollisionType : : Camera ;
mShape- > mV isualCollisionType = Resource : : VisualCollisionType : : Camera ;
else
// No collision.
v isualCollisionType = Resource : : VisualCollisionType : : Default ;
mShape- > mV isualCollisionType = Resource : : VisualCollisionType : : Default ;
}
// Don't autogenerate collision if MRK is set.
// FIXME: verify if this covers the entire subtree
@ -342,15 +377,9 @@ namespace NifBullet
return ;
}
}
else if ( e - > recType = = Nif : : RC_BSXFlags )
{
auto bsxFlags = static_cast < const Nif : : NiIntegerExtraData * > ( e . getPtr ( ) ) ;
if ( bsxFlags - > mData & 32 ) // Editor marker flag
args . mHasMarkers = true ;
}
}
if ( args . m IsCollisionNode)
if ( args . mAutogenerated | | args . mIsCollisionNode )
{
// NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape!
// It must be ignored completely.
@ -369,7 +398,7 @@ namespace NifBullet
continue ;
assert ( std : : find ( child - > mParents . begin ( ) , child - > mParents . end ( ) , ninode ) ! = child - > mParents . end ( ) ) ;
handleNode ( fileName, child. get ( ) , & currentParent , args , visualCollisionType ) ;
handleNode ( child. get ( ) , & currentParent , args ) ;
}
}
}