1 // Copyright 2001-2019 Crytek GmbH / Crytek Group. All rights reserved.
4 #include "BaseEnv/Utils/BaseEnv_SpatialIndex.h"
6 #include <CryRenderer/IRenderAuxGeom.h>
8 SERIALIZATION_ENUM_BEGIN_NESTED(SchematycBaseEnv
, ESpatialVolumeShape
, "Spatial volume shape")
9 SERIALIZATION_ENUM(SchematycBaseEnv::ESpatialVolumeShape::Box
, "Box", "Box")
10 SERIALIZATION_ENUM(SchematycBaseEnv::ESpatialVolumeShape::Sphere
, "Sphere", "Sphere")
11 SERIALIZATION_ENUM(SchematycBaseEnv::ESpatialVolumeShape::Point
, "Point", "Point")
12 SERIALIZATION_ENUM_END()
14 namespace SchematycBaseEnv
16 const uint32
CSpatialIndex::s_volumeIdxMask
= 0x0000ffff;
17 const uint32
CSpatialIndex::s_volumeSaltShift
= 16;
18 const uint32
CSpatialIndex::s_volumeSaltMask
= 0xffff0000;
20 CSpatialVolumeBounds::CSpatialVolumeBounds()
21 : m_shape(ESpatialVolumeShape::None
)
24 CSpatialVolumeBounds::CSpatialVolumeBounds(const OBB
& rhs
)
25 : m_shape(ESpatialVolumeShape::Box
)
27 new (m_storage
) OBB(rhs
);
30 CSpatialVolumeBounds::CSpatialVolumeBounds(const Sphere
& rhs
)
31 : m_shape(ESpatialVolumeShape::Sphere
)
33 new (m_storage
) Sphere(rhs
);
36 CSpatialVolumeBounds::CSpatialVolumeBounds(const Vec3
& rhs
)
37 : m_shape(ESpatialVolumeShape::Point
)
39 new (m_storage
) Vec3(rhs
);
42 CSpatialVolumeBounds::CSpatialVolumeBounds(const CSpatialVolumeBounds
& rhs
)
43 : m_shape(rhs
.m_shape
)
48 CSpatialVolumeBounds::~CSpatialVolumeBounds()
53 CSpatialVolumeBounds
CSpatialVolumeBounds::CreateOBB(const Matrix34
& volumeTM
, const Vec3
& pos
, const Vec3
& size
, const Matrix33
& rot
)
55 const QuatT transform
= QuatT(volumeTM
) * QuatT(Quat(rot
), pos
);
56 return CSpatialVolumeBounds(OBB::CreateOBB(Matrix33(transform
.q
), size
*0.5f
, transform
.q
.GetInverted() * transform
.t
));
59 CSpatialVolumeBounds
CSpatialVolumeBounds::CreateSphere(const Matrix34
& volumeTM
, const Vec3
& pos
, float radius
)
61 return CSpatialVolumeBounds(Sphere(volumeTM
.TransformPoint(pos
), radius
));
64 CSpatialVolumeBounds
CSpatialVolumeBounds::CreatePoint(const Matrix34
& volumeTM
, const Vec3
& pos
)
66 return CSpatialVolumeBounds(volumeTM
.TransformPoint(pos
));
69 ESpatialVolumeShape
CSpatialVolumeBounds::GetShape() const
74 const OBB
& CSpatialVolumeBounds::AsBox() const
76 CRY_ASSERT(m_shape
== ESpatialVolumeShape::Box
);
77 return *reinterpret_cast<const OBB
*>(m_storage
);
80 const Sphere
& CSpatialVolumeBounds::AsSphere() const
82 CRY_ASSERT(m_shape
== ESpatialVolumeShape::Sphere
);
83 return *reinterpret_cast<const Sphere
*>(m_storage
);
86 const Vec3
& CSpatialVolumeBounds::AsPoint() const
88 CRY_ASSERT(m_shape
== ESpatialVolumeShape::Point
);
89 return *reinterpret_cast<const Vec3
*>(m_storage
);
92 AABB
CSpatialVolumeBounds::CalculateAABB() const
96 case ESpatialVolumeShape::Box
:
98 return AABB::CreateAABBfromOBB(Vec3(ZERO
), AsBox());
100 case ESpatialVolumeShape::Sphere
:
102 return AABB(AsSphere().center
, AsSphere().radius
);
104 case ESpatialVolumeShape::Point
:
106 return AABB(AsPoint(), AsPoint());
112 bool CSpatialVolumeBounds::Overlap(const CSpatialVolumeBounds
& rhs
) const
116 case ESpatialVolumeShape::Box
:
120 case ESpatialVolumeShape::Box
:
122 return Overlap::OBB_OBB(Vec3(ZERO
), AsBox(), Vec3(ZERO
), rhs
.AsBox());
124 case ESpatialVolumeShape::Sphere
:
126 return Overlap::Sphere_OBB(rhs
.AsSphere(), AsBox());
128 case ESpatialVolumeShape::Point
:
130 return Overlap::Point_OBB(rhs
.AsPoint(), Vec3(ZERO
), AsBox());
135 case ESpatialVolumeShape::Sphere
:
139 case ESpatialVolumeShape::Box
:
141 return Overlap::Sphere_OBB(AsSphere(), rhs
.AsBox());
143 case ESpatialVolumeShape::Sphere
:
145 return Overlap::Sphere_Sphere(AsSphere(), rhs
.AsSphere());
147 case ESpatialVolumeShape::Point
:
149 return Overlap::Point_Sphere(rhs
.AsPoint(), AsSphere());
154 case ESpatialVolumeShape::Point
:
158 case ESpatialVolumeShape::Box
:
160 return Overlap::Point_OBB(AsPoint(), Vec3(ZERO
), rhs
.AsBox());
162 case ESpatialVolumeShape::Sphere
:
164 return Overlap::Point_Sphere(AsPoint(), rhs
.AsSphere());
166 // N.B. No case for ESpatialVolumeShape::Point because we should never need to detect overlapping points.
174 void CSpatialVolumeBounds::operator = (const CSpatialVolumeBounds
& rhs
)
180 void CSpatialVolumeBounds::Copy(const CSpatialVolumeBounds
& rhs
)
182 m_shape
= rhs
.m_shape
;
185 case ESpatialVolumeShape::Box
:
187 new (m_storage
) OBB(rhs
.AsBox());
190 case ESpatialVolumeShape::Sphere
:
192 new (m_storage
) Sphere(rhs
.AsSphere());
195 case ESpatialVolumeShape::Point
:
197 new (m_storage
) Vec3(rhs
.AsPoint());
203 void CSpatialVolumeBounds::Release()
207 case ESpatialVolumeShape::Box
:
209 reinterpret_cast<const OBB
*>(m_storage
)->~OBB();
212 case ESpatialVolumeShape::Sphere
:
214 reinterpret_cast<const Sphere
*>(m_storage
)->~Sphere();
217 case ESpatialVolumeShape::Point
:
219 reinterpret_cast<const Vec3
*>(m_storage
)->~Vec3();
225 SSpatialVolumeTagName::SSpatialVolumeTagName() {}
227 SSpatialVolumeTagName::SSpatialVolumeTagName(const string
& _value
)
231 bool SSpatialVolumeTagName::operator == (const SSpatialVolumeTagName
& rhs
) const
233 return value
== rhs
.value
;
236 bool Serialize(Serialization::IArchive
& archive
, SSpatialVolumeTagName
& value
, const char* szName
, const char* szLabel
)
238 /*const Serialization::StringList& tagNames = g_pGame->GetSchematycEnv().GetEntityDetectionVolumeSystem().GetTagNames();
239 if(archive.isInput())
241 Serialization::StringListValue temp(tagNames, 0);
242 archive(temp, szName, szLabel);
243 value.value = temp.c_str();
245 else if(archive.isOutput())
247 const int pos = tagNames.find(value.value.c_str());
248 archive(Serialization::StringListValue(tagNames, pos), szName, szLabel);
253 SSpatialVolumeParams::SSpatialVolumeParams()
254 : tags(ESpatialVolumeTags::None
)
255 , monitorTags(ESpatialVolumeTags::None
)
259 SSpatialVolumeParams::SSpatialVolumeParams(const CSpatialVolumeBounds
& _bounds
, ESpatialVolumeTags _tags
, ESpatialVolumeTags _monitorTags
, EntityId _entityId
)
262 , monitorTags(_monitorTags
)
263 , entityId(_entityId
)
266 void CSpatialIndex::SSettings::Serialize(Serialization::IArchive
& archive
)
268 archive(userTags
, "userTags", "+User Tags");
269 if(archive
.isInput())
275 void CSpatialIndex::SSettings::RefreshTags()
280 const Serialization::EnumDescription
& tagsEnumDescription
= Serialization::getEnumDescription
<ESpatialVolumeTags
>();
281 UserTags::const_iterator itUserTag
= userTags
.begin();
282 UserTags::const_iterator itEndUserTag
= userTags
.end();
283 for(int tagValue
= static_cast<int>(ESpatialVolumeTags::Begin
); tagValue
!= static_cast<int>(ESpatialVolumeTags::End
); tagValue
<<= 1)
285 const int tagIdx
= tagsEnumDescription
.indexByValue(tagValue
);
288 tagNames
.push_back(tagsEnumDescription
.labelByIndex(tagIdx
));
289 tagValues
.push_back(tagValue
);
291 else if(itUserTag
!= itEndUserTag
)
293 tagNames
.push_back(*itUserTag
);
294 tagValues
.push_back(tagValue
);
300 CSpatialIndex::SVolume::SVolume()
301 : monitorTags(ESpatialVolumeTags::None
)
305 CSpatialIndex::SBroadPhaseVolume::SBroadPhaseVolume()
306 : tags(ESpatialVolumeTags::None
)
309 CSpatialIndex::CSpatialIndex()
310 : m_pSettings(new SSettings())
313 m_pSettings
->RefreshTags();
314 gEnv
->pSchematyc2
->GetEnvRegistry().RegisterSettings("spatial_index_settings", m_pSettings
);
316 m_volumes
.reserve(256);
317 m_broadPhaseVolumes
.reserve(256);
318 m_freeVolumes
.reserve(64);
319 m_broadPhaseResults
.reserve(64);
320 m_monitorResults
.reserve(16);
321 m_eventQueue
.reserve(64);
324 SpatialVolumeId
CSpatialIndex::CreateVolume(const SSpatialVolumeParams
& params
, const SpatialIndexEventCallback
& eventCallback
)
327 if(m_freeVolumes
.empty())
329 const uint32 volumeCount
= m_volumes
.size();
330 if(volumeCount
> s_volumeIdxMask
)
332 return SpatialVolumeId();
336 volumeIdx
= volumeCount
;
337 m_volumes
.push_back(SVolume());
338 m_broadPhaseVolumes
.push_back(SBroadPhaseVolume());
343 volumeIdx
= m_freeVolumes
.back();
344 m_freeVolumes
.pop_back();
347 SVolume
& volume
= m_volumes
[volumeIdx
];
348 volume
.bounds
= params
.bounds
;
349 volume
.monitorTags
= params
.monitorTags
;
350 volume
.id
= GenerateVolumeId(volumeIdx
);
351 volume
.entityId
= params
.entityId
;
352 volume
.eventCallback
= eventCallback
;
354 volume
.monitorCache
.clear();
355 if(params
.monitorTags
!= ESpatialVolumeTags::None
)
357 volume
.monitorCache
.reserve(16);
360 SBroadPhaseVolume
& broadPhaseVolume
= m_broadPhaseVolumes
[volumeIdx
];
361 broadPhaseVolume
.tags
= params
.tags
;
362 broadPhaseVolume
.aabb
= params
.bounds
.CalculateAABB();
367 void CSpatialIndex::DestroyVolume(const SpatialVolumeId
& id
)
369 if(id
!= SpatialVolumeId::s_invalid
)
371 const uint32 volumeCount
= m_volumes
.size();
372 const uint32 volumeIdx
= VolumeIdxFromId(id
);
373 CRY_ASSERT(volumeIdx
< volumeCount
);
374 if(volumeIdx
< volumeCount
)
376 SVolume
& volume
= m_volumes
[volumeIdx
];
377 CRY_ASSERT(volume
.id
== id
);
380 volume
.bounds
= CSpatialVolumeBounds();
381 volume
.monitorTags
= ESpatialVolumeTags::None
;
382 volume
.eventCallback
= SpatialIndexEventCallback();
383 m_broadPhaseVolumes
[volumeIdx
].tags
= ESpatialVolumeTags::None
;
384 m_freeVolumes
.push_back(volumeIdx
);
390 void CSpatialIndex::UpdateVolumeBounds(const SpatialVolumeId
& id
, const CSpatialVolumeBounds
& bounds
)
392 if(id
!= SpatialVolumeId::s_invalid
)
394 CRY_PROFILE_FUNCTION(PROFILE_GAME
);
396 const uint32 volumeCount
= m_volumes
.size();
397 const uint32 volumeIdx
= VolumeIdxFromId(id
);
398 CRY_ASSERT(volumeIdx
< volumeCount
);
399 if(volumeIdx
< volumeCount
)
401 SVolume
& volume
= m_volumes
[volumeIdx
];
402 CRY_ASSERT(volume
.id
== id
);
405 volume
.bounds
= bounds
;
406 m_broadPhaseVolumes
[volumeIdx
].aabb
= bounds
.CalculateAABB();
412 void CSpatialIndex::SetVolumeTags(const SpatialVolumeId
& id
, ESpatialVolumeTags tags
, bool bValue
)
414 if(id
!= SpatialVolumeId::s_invalid
)
416 const uint32 volumeCount
= m_volumes
.size();
417 const uint32 volumeIdx
= VolumeIdxFromId(id
);
418 CRY_ASSERT(volumeIdx
< volumeCount
);
419 if(volumeIdx
< volumeCount
)
421 SVolume
& volume
= m_volumes
[volumeIdx
];
422 CRY_ASSERT(volume
.id
== id
);
427 m_broadPhaseVolumes
[volumeIdx
].tags
|= tags
;
431 m_broadPhaseVolumes
[volumeIdx
].tags
&= ~tags
;
438 void CSpatialIndex::SetVolumeMonitorTags(const SpatialVolumeId
& id
, ESpatialVolumeTags tags
, bool bValue
)
440 if(id
!= SpatialVolumeId::s_invalid
)
442 const uint32 volumeCount
= m_volumes
.size();
443 const uint32 volumeIdx
= VolumeIdxFromId(id
);
444 CRY_ASSERT(volumeIdx
< volumeCount
);
445 if(volumeIdx
< volumeCount
)
447 SVolume
& volume
= m_volumes
[volumeIdx
];
448 CRY_ASSERT(volume
.id
== id
);
453 volume
.monitorTags
|= tags
;
457 volume
.monitorTags
&= ~tags
;
464 SSpatialVolumeParams
CSpatialIndex::GetVolumeParams(const SpatialVolumeId
& id
) const
466 if(id
!= SpatialVolumeId::s_invalid
)
468 const uint32 volumeCount
= m_volumes
.size();
469 const uint32 volumeIdx
= VolumeIdxFromId(id
);
470 CRY_ASSERT(volumeIdx
< volumeCount
);
471 if(volumeIdx
< volumeCount
)
473 const SVolume
& volume
= m_volumes
[volumeIdx
];
474 CRY_ASSERT(volume
.id
== id
);
477 SSpatialVolumeParams volumeParams
;
478 volumeParams
.bounds
= volume
.bounds
;
479 volumeParams
.tags
= m_broadPhaseVolumes
[volumeIdx
].tags
;
480 volumeParams
.monitorTags
= volume
.monitorTags
;
481 volumeParams
.entityId
= volume
.entityId
;
486 return SSpatialVolumeParams();
489 void CSpatialIndex::Query(const SpatialVolumeId
& id
, ESpatialVolumeTags tags
, SpatialVolumeIds
& results
)
491 CRY_PROFILE_FUNCTION(PROFILE_GAME
);
492 if(id
!= SpatialVolumeId::s_invalid
)
494 const uint32 volumeCount
= m_volumes
.size();
495 const uint32 volumeIdx
= VolumeIdxFromId(id
);
496 CRY_ASSERT(volumeIdx
< volumeCount
);
497 if(volumeIdx
< volumeCount
)
499 SVolume
& volume
= m_volumes
[volumeIdx
];
500 CRY_ASSERT(volume
.id
== id
);
503 // Perform broad phase query.
504 m_broadPhaseResults
.clear();
505 BroadPhaseQuery(volumeIdx
, tags
, m_broadPhaseResults
);
506 // Perform narrow phase tests.
507 for(uint32 otherVolumeIdx
: m_broadPhaseResults
)
509 SVolume
& otherVolume
= m_volumes
[otherVolumeIdx
];
510 if(volume
.bounds
.Overlap(otherVolume
.bounds
))
512 results
.push_back(otherVolume
.id
);
515 // Sort results by index.
516 std::sort(results
.begin(), results
.end(), SCompareVolumeIds());
522 void CSpatialIndex::Update()
524 CRY_PROFILE_FUNCTION(PROFILE_GAME
);
525 UpdateMonitoredVolumes();
526 ProcessEventQueue(m_eventQueue
);
527 /*if(EnvCVars::sc_DebugDrawDetectionVolumes == 1)
533 ESpatialVolumeTags
CSpatialIndex::CreateTag(const SSpatialVolumeTagName
& tagName
) const
535 const int tagIdx
= m_pSettings
->tagNames
.find(tagName
.value
.c_str());
536 if(tagIdx
!= Serialization::StringList::npos
)
538 return static_cast<ESpatialVolumeTags
>(m_pSettings
->tagValues
[tagIdx
]);
540 return ESpatialVolumeTags::None
;
543 ESpatialVolumeTags
CSpatialIndex::CreateTags(const SpatialVolumeTagNames
& tagNames
) const
545 ESpatialVolumeTags result
= ESpatialVolumeTags::None
;
546 for(const SSpatialVolumeTagName
& tagName
: tagNames
)
548 result
|= CreateTag(tagName
);
553 const Serialization::StringList
& CSpatialIndex::GetTagNames() const
555 return m_pSettings
->tagNames
;
558 const SpatialVolumeTagValues
& CSpatialIndex::GetTagValues() const
560 return m_pSettings
->tagValues
;
563 SpatialVolumeId
CSpatialIndex::GenerateVolumeId(uint32 volumeIdx
)
565 const uint32 salt
= m_salt
++;
566 const SpatialVolumeId
volumeId((volumeIdx
& s_volumeIdxMask
) | ((salt
<< s_volumeSaltShift
) & s_volumeSaltMask
));
567 CRY_ASSERT(VolumeIdxFromId(volumeId
) == volumeIdx
);
571 uint32
CSpatialIndex::VolumeIdxFromId(const SpatialVolumeId
& id
) const
573 return id
.GetValue() & s_volumeIdxMask
;
576 inline bool CSpatialIndex::CompareVolumeIds(const SpatialVolumeId
& lhs
, const SpatialVolumeId
& rhs
) const
578 return (lhs
.GetValue() & s_volumeIdxMask
) < (rhs
.GetValue() & s_volumeIdxMask
);
581 void CSpatialIndex::BroadPhaseQuery(uint32 volumeIdx
, ESpatialVolumeTags tags
, VolumeIdxs
& results
) const
583 const SBroadPhaseVolume broadPhaseVolume
= m_broadPhaseVolumes
[volumeIdx
];
584 for(uint32 otherVolumeIdx
= 0, volumeCount
= m_volumes
.size(); otherVolumeIdx
< volumeCount
; ++ otherVolumeIdx
)
586 if(otherVolumeIdx
!= volumeIdx
)
588 const SBroadPhaseVolume
& otherBroadPhaseVolume
= m_broadPhaseVolumes
[otherVolumeIdx
];
589 if((tags
& otherBroadPhaseVolume
.tags
) != 0)
591 if(Overlap::AABB_AABB(broadPhaseVolume
.aabb
, otherBroadPhaseVolume
.aabb
))
593 results
.push_back(otherVolumeIdx
);
600 void CSpatialIndex::UpdateMonitoredVolumes()
602 CRY_PROFILE_FUNCTION(PROFILE_GAME
);
603 // Update monitored volumes.
604 for(SVolume
& volume
: m_volumes
)
606 // Query for overlapping volumes?
607 m_monitorResults
.clear();
608 if(volume
.monitorTags
!= ESpatialVolumeTags::None
)
610 Query(volume
.id
, volume
.monitorTags
, m_monitorResults
);
612 // Check for volumes leaving monitored volume.
613 for(uint32 cacheVolumeIdx
= 0, cacheVolumeCount
= volume
.monitorCache
.size(); cacheVolumeIdx
< cacheVolumeCount
; )
615 const SpatialVolumeId cacheVolumeId
= volume
.monitorCache
[cacheVolumeIdx
];
618 SpatialVolumeIds::iterator itLowerBoundVolume
= std::lower_bound(m_monitorResults
.begin(), m_monitorResults
.end(), cacheVolumeId
, SCompareVolumeIds());
620 if((itLowerBoundVolume
== m_monitorResults
.end()) || (*itLowerBoundVolume
!= cacheVolumeId
))
622 SSpatialIndexEvent event
;
623 event
.id
= ESpatialIndexEventId::Leaving
;
624 event
.volumeIds
[0] = volume
.id
;
625 event
.volumeIds
[1] = cacheVolumeId
;
626 m_eventQueue
.push_back(event
);
628 volume
.monitorCache
.erase(volume
.monitorCache
.begin() + cacheVolumeIdx
);
636 // Check for volumes entering monitored volume.
637 for(uint32 newVolumeIdx
= 0, newVolumeCount
= m_monitorResults
.size(); newVolumeIdx
< newVolumeCount
; ++ newVolumeIdx
)
639 const SpatialVolumeId newVolumeId
= m_monitorResults
[newVolumeIdx
];
640 SpatialVolumeIds::iterator itLowerBoundVolume
= std::lower_bound(volume
.monitorCache
.begin(), volume
.monitorCache
.end(), newVolumeId
);
641 if((itLowerBoundVolume
== volume
.monitorCache
.end()) || (*itLowerBoundVolume
!= newVolumeId
))
643 volume
.monitorCache
.insert(itLowerBoundVolume
, newVolumeId
);
645 SSpatialIndexEvent event
;
646 event
.id
= ESpatialIndexEventId::Entering
;
647 event
.volumeIds
[0] = volume
.id
;
648 event
.volumeIds
[1] = newVolumeId
;
649 m_eventQueue
.push_back(event
);
655 void CSpatialIndex::ProcessEventQueue(EventQueue
& eventQueue
) const
657 CRY_PROFILE_FUNCTION(PROFILE_GAME
);
658 // Send out all events in queue. N.B. This is performed after the update logic in order to reduce cache misses.
659 for(const SSpatialIndexEvent
& event
: eventQueue
)
661 const SVolume
& volume
= m_volumes
[VolumeIdxFromId(event
.volumeIds
[0])];
662 if(volume
.eventCallback
)
664 volume
.eventCallback(event
);
670 void CSpatialIndex::DebugDraw() const
672 IRenderAuxGeom
& renderAuxGeom
= *gEnv
->pRenderer
->GetIRenderAuxGeom();
673 for(const SVolume
& volume
: m_volumes
)
675 ColorB color
= ColorB(0, 150, 0);
676 if(!volume
.monitorCache
.empty())
678 color
= ColorB(150, 0, 0);
680 else if(volume
.monitorTags
!= ESpatialVolumeTags::None
)
682 color
= ColorB(150, 150, 0);
684 switch(volume
.bounds
.GetShape())
686 case ESpatialVolumeShape::Box
:
688 renderAuxGeom
.DrawOBB(volume
.bounds
.AsBox(), Matrix34(IDENTITY
), false, color
, eBBD_Faceted
);
691 case ESpatialVolumeShape::Sphere
:
693 SAuxGeomRenderFlags prevRenderFlags
= renderAuxGeom
.GetRenderFlags();
694 renderAuxGeom
.SetRenderFlags(e_Def3DPublicRenderflags
| e_AlphaBlended
);
695 renderAuxGeom
.DrawSphere(volume
.bounds
.AsSphere().center
, volume
.bounds
.AsSphere().radius
, ColorB(color
.r
, color
.g
, color
.b
, 64), false);
696 renderAuxGeom
.SetRenderFlags(prevRenderFlags
);
699 case ESpatialVolumeShape::Point
:
701 renderAuxGeom
.DrawSphere(volume
.bounds
.AsPoint(), 0.1f
, color
, false);