3333
3434#include < algorithm>
3535#include < memory>
36- #include < ranges>
3736#include < sstream>
3837#include < stdexcept>
3938#include < string>
@@ -52,6 +51,7 @@ namespace NavMeshTool
5251 using DetourNavigator::HeightfieldSurface;
5352 using DetourNavigator::ObjectId;
5453 using DetourNavigator::ObjectTransform;
54+ using DetourNavigator::TilesPositionsRange;
5555
5656 struct CellRef
5757 {
@@ -72,6 +72,25 @@ namespace NavMeshTool
7272 }
7373 };
7474
75+ struct AddedCellRef
76+ {
77+ std::string mCell ;
78+ CellRef mCellRef ;
79+ TilesPositionsRange mRange ;
80+ };
81+
82+ struct WriteArray
83+ {
84+ const float (&mValue )[3];
85+
86+ friend inline std::ostream& operator <<(std::ostream& stream, const WriteArray& value)
87+ {
88+ for (std::size_t i = 0 ; i < 2 ; ++i)
89+ stream << value.mValue [i] << " , " ;
90+ return stream << value.mValue [2 ];
91+ }
92+ };
93+
7594 ESM::RecNameInts getType (const EsmLoader::EsmData& esmData, const ESM::RefId& refId)
7695 {
7796 const auto it = std::lower_bound (
@@ -120,7 +139,7 @@ namespace NavMeshTool
120139
121140 Log (Debug::Debug) << " Prepared " << cellRefs.size () << " unique cell refs" ;
122141
123- for (CellRef& cellRef : cellRefs)
142+ for (const CellRef& cellRef : cellRefs)
124143 {
125144 VFS::Path::Normalized model (getModel (esmData, cellRef.mRefId , cellRef.mType ));
126145 if (model.empty ())
@@ -154,7 +173,7 @@ namespace NavMeshTool
154173 case ESM::REC_CONT:
155174 case ESM::REC_DOOR:
156175 case ESM::REC_STAT:
157- f (BulletObject (std::move (shapeInstance), cellRef.mPos , cellRef.mScale ));
176+ f (BulletObject (std::move (shapeInstance), cellRef.mPos , cellRef.mScale ), cellRef );
158177 break ;
159178 default :
160179 break ;
@@ -233,6 +252,88 @@ namespace NavMeshTool
233252 << " fileHash=" << Misc::StringUtils::toHex (shape.getInstance ()->mFileHash );
234253 return stream.str ();
235254 }
255+
256+ void detectDisconnectedTileGroups (ESM::RefId worldspace,
257+ const std::map<osg::Vec2i, DetourNavigator::ChangeType>& changedTiles,
258+ std::span<const AddedCellRef> cellRefs)
259+ {
260+ if (changedTiles.empty ())
261+ return ;
262+
263+ std::deque<osg::Vec2i> queue;
264+ std::map<osg::Vec2i, std::size_t > positionToComponent;
265+ std::size_t componentIndex = 0 ;
266+
267+ for (const auto & [initial, changeType] : changedTiles)
268+ {
269+ if (positionToComponent.contains (initial))
270+ continue ;
271+
272+ queue.push_back (initial);
273+ positionToComponent.emplace (initial, componentIndex);
274+
275+ while (!queue.empty ())
276+ {
277+ const osg::Vec2i position = queue.front ();
278+ queue.pop_front ();
279+
280+ for (int x = position.x () - 1 ; x <= position.x () + 1 ; ++x)
281+ {
282+ for (int y = position.y () - 1 ; y <= position.y () + 1 ; ++y)
283+ {
284+ const osg::Vec2i candidate (x, y);
285+
286+ if (candidate == position)
287+ continue ;
288+
289+ if (!changedTiles.contains (candidate))
290+ continue ;
291+
292+ const auto it = positionToComponent.find (candidate);
293+
294+ if (it != positionToComponent.end ())
295+ continue ;
296+
297+ queue.push_back (candidate);
298+ positionToComponent.emplace_hint (it, candidate, componentIndex);
299+ }
300+ }
301+ }
302+
303+ ++componentIndex;
304+ }
305+
306+ if (componentIndex <= 1 )
307+ return ;
308+
309+ Log (Debug::Warning) << " Found " << componentIndex << " disconnected tile groups" ;
310+
311+ std::vector<std::size_t > cellRefsPerComponent (componentIndex);
312+
313+ for (const AddedCellRef& v : cellRefs)
314+ ++cellRefsPerComponent[positionToComponent.at (v.mRange .mBegin )];
315+
316+ const std::size_t largestComponent
317+ = std::max_element (cellRefsPerComponent.begin (), cellRefsPerComponent.end ())
318+ - cellRefsPerComponent.begin ();
319+
320+ for (const AddedCellRef& v : cellRefs)
321+ {
322+ const std::size_t component = positionToComponent.at (v.mRange .mBegin );
323+
324+ if (component == largestComponent)
325+ continue ;
326+
327+ Log (Debug::Warning) << " CellRef belongs to not largest disconnected tile group:"
328+ << " worldspace=" << worldspace << " cell=\" " << v.mCell << " \" "
329+ << " ref_num=" << v.mCellRef .mRefNum << " ref_id=" << v.mCellRef .mRefId
330+ << " scale=" << v.mCellRef .mScale << " pos=" << WriteArray{ v.mCellRef .mPos .pos }
331+ << " rot=" << WriteArray{ v.mCellRef .mPos .rot }
332+ << " begin_tile=" << v.mRange .mBegin .x () << " , " << v.mRange .mBegin .y ()
333+ << " end_tile=" << v.mRange .mEnd .x () << " , " << v.mRange .mEnd .y ()
334+ << " component=" << component;
335+ }
336+ }
236337 }
237338
238339 WorldspaceData::WorldspaceData (ESM::RefId worldspace, const DetourNavigator::RecastSettings& settings)
@@ -301,6 +402,7 @@ namespace NavMeshTool
301402 manager.setWorldspace (worldspace, guard.get ());
302403
303404 std::size_t objectsCounter = 0 ;
405+ std::vector<AddedCellRef> addedCellRefs;
304406
305407 for (std::size_t i = 0 ; i < cells.size (); ++i)
306408 {
@@ -333,36 +435,44 @@ namespace NavMeshTool
333435 manager.addWater (cellPosition, std::numeric_limits<int >::max (), cell.mWater , guard.get ());
334436 }
335437
336- forEachObject (cell, esmData, vfs, bulletShapeManager, readers, [&](BulletObject object) {
337- if (object.getShapeInstance ()->mVisualCollisionType != Resource::VisualCollisionType::None)
338- return ;
438+ forEachObject (
439+ cell, esmData, vfs, bulletShapeManager, readers, [&](BulletObject object, const CellRef& cellRef) {
440+ if (object.getShapeInstance ()->mVisualCollisionType != Resource::VisualCollisionType::None)
441+ return ;
339442
340- const btTransform& transform = object.getCollisionObject ().getWorldTransform ();
341- const btAABB aabb = BulletHelpers::getAabb (*object.getCollisionObject ().getCollisionShape (), transform);
342- mergeOrAssign (aabb, data.mAabb , data.mAabbInitialized );
343- if (const btCollisionShape* avoid = object.getShapeInstance ()->mAvoidCollisionShape .get ())
344- data.mAabb .merge (BulletHelpers::getAabb (*avoid, transform));
443+ const btTransform& transform = object.getCollisionObject ().getWorldTransform ();
444+ const btAABB aabb
445+ = BulletHelpers::getAabb (*object.getCollisionObject ().getCollisionShape (), transform);
446+ mergeOrAssign (aabb, data.mAabb , data.mAabbInitialized );
447+ if (const btCollisionShape* avoid = object.getShapeInstance ()->mAvoidCollisionShape .get ())
448+ data.mAabb .merge (BulletHelpers::getAabb (*avoid, transform));
345449
346- const ObjectId objectId (++objectsCounter);
347- const CollisionShape shape (object.getShapeInstance (), *object. getCollisionObject (). getCollisionShape (),
348- object.getObjectTransform ());
450+ const ObjectId objectId (++objectsCounter);
451+ const CollisionShape shape (object.getShapeInstance (),
452+ *object. getCollisionObject (). getCollisionShape (), object.getObjectTransform ());
349453
350- if (!manager.addObject (objectId, shape, transform, DetourNavigator::AreaType_ground, guard.get ()))
351- throw std::logic_error (
352- makeAddObjectErrorMessage (objectId, DetourNavigator::AreaType_ground, shape));
353-
354- if (const btCollisionShape* avoid = object.getShapeInstance ()->mAvoidCollisionShape .get ())
355- {
356- const ObjectId avoidObjectId (++objectsCounter);
357- const CollisionShape avoidShape (object.getShapeInstance (), *avoid, object.getObjectTransform ());
358- if (!manager.addObject (
359- avoidObjectId, avoidShape, transform, DetourNavigator::AreaType_null, guard.get ()))
454+ if (!manager.addObject (objectId, shape, transform, DetourNavigator::AreaType_ground, guard.get ()))
360455 throw std::logic_error (
361- makeAddObjectErrorMessage (avoidObjectId, DetourNavigator::AreaType_null, avoidShape));
362- }
456+ makeAddObjectErrorMessage (objectId, DetourNavigator::AreaType_ground, shape));
363457
364- data.mObjects .emplace_back (std::move (object));
365- });
458+ addedCellRefs.push_back (AddedCellRef{
459+ .mCell = cell.getDescription (),
460+ .mCellRef = cellRef,
461+ .mRange = makeTilesPositionsRange (shape.getShape (), transform, settings.mRecast ),
462+ });
463+
464+ if (const btCollisionShape* avoid = object.getShapeInstance ()->mAvoidCollisionShape .get ())
465+ {
466+ const ObjectId avoidObjectId (++objectsCounter);
467+ const CollisionShape avoidShape (object.getShapeInstance (), *avoid, object.getObjectTransform ());
468+ if (!manager.addObject (
469+ avoidObjectId, avoidShape, transform, DetourNavigator::AreaType_null, guard.get ()))
470+ throw std::logic_error (
471+ makeAddObjectErrorMessage (avoidObjectId, DetourNavigator::AreaType_null, avoidShape));
472+ }
473+
474+ data.mObjects .emplace_back (std::move (object));
475+ });
366476
367477 if (writeBinaryLog)
368478 serializeToStderr (ProcessedCells{ static_cast <std::uint64_t >(i + 1 ) });
@@ -373,6 +483,10 @@ namespace NavMeshTool
373483 }
374484
375485 const std::map<osg::Vec2i, DetourNavigator::ChangeType> changedTiles = manager.takeChangedTiles (guard.get ());
486+
487+ if (worldspace != ESM::Cell::sDefaultWorldspaceId )
488+ detectDisconnectedTileGroups (worldspace, changedTiles, addedCellRefs);
489+
376490 data.mTiles .reserve (changedTiles.size ());
377491 std::ranges::transform (changedTiles, std::back_inserter (data.mTiles ), [](const auto & v) { return v.first ; });
378492
0 commit comments