11#include <unordered_map>
12#include <unordered_set>
26 bool _add(
const std::shared_ptr<T>& item) {
29 std::cerr <<
"FxNamedRegistry: Cannot add null item.\n";
33 std::cerr <<
"FxNamedRegistry: Items limit exceeded.\n";
37 const std::string& name = item->get_name();
39 std::cerr <<
"FxNamedRegistry: Item '" << name <<
"' already exists.\n";
53 std::cerr <<
"FxNamedRegistry: Item '" << name <<
"' not found.\n";
57 size_t idx = it->second;
62 const std::string& moved_name =
m_items_vec[idx]->get_name();
87 std::cerr <<
"FxNamedRegistry: clamping max_size " << n <<
" to limit.\n";
94 virtual bool add(
const std::shared_ptr<T>& item) {
return _add(item); }
97 std::shared_ptr<T>
get(
const std::string& name)
const {
101 std::cerr <<
"FxNamedRegistry: Item '" << name <<
"' not found.\n";
116 for (
int i = 1;; ++i) {
117 std::string candidate = base +
"_" + std::to_string(i);
139 std::vector<T*> m_raw_items;
140 bool m_raw_items_in_use =
false;
142 void fill_raw(std::vector<T*>& out)
const {
146 out.push_back(item.get());
156 if (m_registry.m_raw_items_in_use) {
157 m_registry.fill_raw(m_local);
161 m_registry.m_raw_items_in_use =
true;
162 m_owns_shared =
true;
163 m_registry.fill_raw(m_registry.m_raw_items);
164 m_items = &m_registry.m_raw_items;
167 if (m_owns_shared) m_registry.m_raw_items_in_use =
false;
169 RawSweep(
const RawSweep&) =
delete;
170 RawSweep& operator=(
const RawSweep&) =
delete;
172 std::vector<T*>& items()
const {
return *m_items; }
176 std::vector<T*> m_local;
177 std::vector<T*>* m_items =
nullptr;
178 bool m_owns_shared =
false;
182 template<
typename ExecPolicy,
typename Func>
186 RawSweep sweep(*
this);
187 std::vector<T*>& raw = sweep.items();
189#if FX2D_HAS_EXECUTION_POLICIES
190 std::for_each(std::forward<ExecPolicy>(policy), raw.begin(), raw.end(),
191 std::forward<Func>(func));
194 std::for_each(raw.begin(), raw.end(), std::forward<Func>(func));
198 template<
typename ExecPolicy,
typename Func>
200 std::vector<std::invoke_result_t<Func, std::shared_ptr<T>>>& results) {
205 RawSweep sweep(*
this);
206 std::vector<T*>& raw = sweep.items();
208#if FX2D_HAS_EXECUTION_POLICIES
209 std::transform(std::forward<ExecPolicy>(policy), raw.begin(), raw.end(), results.begin(),
210 std::forward<Func>(func));
213 std::transform(raw.begin(), raw.end(), results.begin(), std::forward<Func>(func));
223 std::unordered_set<uint64_t> m_no_collision_pairs;
224 uint32_t m_next_entity_id = 0;
229 std::vector<int32_t> m_id_to_index;
232 mutable std::vector<std::pair<int32_t, int32_t>> m_tree_pairs;
234 void remove_entity_from_tree(
FxEntity& entity)
const {
237 if (node < 0)
return;
242 int32_t index_of_id(uint32_t entity_id)
const {
243 if (entity_id >= m_id_to_index.size())
return -1;
244 return m_id_to_index[entity_id];
247 void erase_collision_pairs_for(uint32_t entity_id) {
250 for (
auto it = m_no_collision_pairs.begin(); it != m_no_collision_pairs.end();) {
251 uint32_t a =
static_cast<uint32_t
>(*it >> 32);
252 uint32_t b =
static_cast<uint32_t
>(*it & 0xffffffffULL);
253 if (a == entity_id || b == entity_id) {
254 it = m_no_collision_pairs.erase(it);
267 m_no_collision_pairs.max_load_factor(0.7f);
270 bool add(
const std::shared_ptr<FxEntity>& entity)
override {
271 if (m_next_entity_id == std::numeric_limits<uint32_t>::max()) {
272 std::cerr <<
"FxEntityRegistry: Entity ID limit exceeded.\n";
277 entity->set_entity_id(m_next_entity_id);
278 bool success =
_add(entity);
282 const int32_t packed =
static_cast<int32_t
>(
m_items_vec.size() - 1);
283 if (m_next_entity_id >= m_id_to_index.size()) {
284 m_id_to_index.resize(
static_cast<size_t>(m_next_entity_id) + 1, -1);
286 m_id_to_index[m_next_entity_id] = packed;
287 entity->set_packed_index(packed);
293 bool remove(
const std::string& name)
override {
296 std::cerr <<
"FxEntityRegistry: Item '" << name <<
"' not found.\n";
300 size_t idx = it->second;
302 uint32_t removed_id =
m_items_vec[idx]->get_entity_id();
303 bool moved_last = (idx != last);
304 uint32_t moved_id = moved_last ?
m_items_vec[last]->get_entity_id() : 0;
309 erase_collision_pairs_for(removed_id);
311 m_id_to_index[removed_id] = -1;
314 if (success && moved_last) {
317 m_id_to_index[moved_id] =
static_cast<int32_t
>(idx);
318 m_items_vec[idx]->set_packed_index(
static_cast<int32_t
>(idx));
328 e->set_broad_phase_node(-1);
329 e->set_packed_index(-1);
333 m_no_collision_pairs.clear();
334 m_id_to_index.clear();
337 m_next_entity_id = 0;
343 if (e1 && e2 && e1 != e2) {
344 uint64_t pair_id = pack_id_pair(e1->get_entity_id(), e2->get_entity_id());
345 m_no_collision_pairs.erase(pair_id);
352 if (e1 && e2 && e1 != e2) {
353 uint64_t pair_id = pack_id_pair(e1->get_entity_id(), e2->get_entity_id());
354 m_no_collision_pairs.insert(pair_id);
361 std::vector<std::pair<size_t, size_t>> pairs;
370 bool sweep_all_movers =
false,
371 bool skip_sleeping_pairs =
true)
const {
380 bool tree_changed =
false;
383 const int32_t node = e->broad_phase_node();
384 const bool in_tree = (node >= 0);
386 if (!e->enabled || !e->collision_geometry()) {
389 remove_entity_from_tree(*e);
395 const auto& bb = e->bounding_box();
396 FxAABB tight{bb[0], bb[1], bb[2], bb[3]};
400 constexpr float kMinProxyExtent = 1e-3f;
401 if (tight.maxX - tight.minX < kMinProxyExtent) {
402 tight.
minX -= kMinProxyExtent;
403 tight.maxX += kMinProxyExtent;
405 if (tight.maxY - tight.minY < kMinProxyExtent) {
406 tight.minY -= kMinProxyExtent;
407 tight.maxY += kMinProxyExtent;
409 if (!tight.is_valid())
continue;
414 FxAABB query_aabb = tight;
415 if ((sweep_all_movers || e->enable_ccd) && sweep_dt > 0.0f) {
416 float dx = e->velocity.x() * sweep_dt;
417 float dy = e->velocity.y() * sweep_dt;
418 if (dx != 0.0f || dy != 0.0f) {
419 FxAABB swept{tight.
minX + dx, tight.minY + dy, tight.maxX + dx,
427 e->set_broad_phase_node(
428 m_aabb_tree.
insert(
static_cast<int32_t
>(e->get_entity_id()), query_aabb));
430 }
else if (m_aabb_tree.
update(node, query_aabb)) {
440 bool skip_sleeping_pairs =
true)
const {
446 pairs.reserve(m_tree_pairs.size());
447 for (
const auto& [eid_a, eid_b] : m_tree_pairs) {
448 const int32_t ia = index_of_id(
static_cast<uint32_t
>(eid_a));
449 const int32_t ib = index_of_id(
static_cast<uint32_t
>(eid_b));
450 if (ia < 0 || ib < 0)
continue;
452 size_t i =
static_cast<size_t>(ia);
453 size_t j =
static_cast<size_t>(ib);
455 if (skip_sleeping_pairs &&
m_items_vec[i]->is_sleeping() &&
459 if (!is_collision_pair(
static_cast<uint32_t
>(eid_a),
static_cast<uint32_t
>(eid_b)))
469 if (i > j) std::swap(i, j);
470 pairs.emplace_back(i, j);
475 static uint64_t pack_id_pair(uint32_t a, uint32_t b) {
476 if (a > b) std::swap(a, b);
477 return static_cast<uint64_t
>(a) << 32 |
static_cast<uint64_t
>(b);
480 bool is_collision_pair(uint32_t entity1_id, uint32_t entity2_id)
const {
481 uint64_t pair_id = pack_id_pair(entity1_id, entity2_id);
482 return m_no_collision_pairs.find(pair_id) == m_no_collision_pairs.end();
Dynamic broad-phase tree used to find candidate pairs.
void query_pairs(std::vector< std::pair< int32_t, int32_t > > &out) const
bool update(int32_t node_idx, const FxAABB &new_tight)
int32_t insert(int32_t entity_id, const FxAABB &tight)
void remove(int32_t node_idx)
void collect_broad_phase_pairs(std::vector< std::pair< size_t, size_t > > &pairs, bool skip_sleeping_pairs=true) const
bool add(const std::shared_ptr< FxEntity > &entity) override
bool sync_broad_phase(float sweep_dt=0.0f, bool sweep_all_movers=false) const
FxEntityRegistry()=default
FxEntityRegistry(size_t max_size)
std::vector< std::pair< size_t, size_t > > get_broad_phase_pairs(float sweep_dt=0.0f) const
bool remove(const std::string &name) override
void get_broad_phase_pairs(std::vector< std::pair< size_t, size_t > > &pairs, float sweep_dt=0.0f, bool sweep_all_movers=false, bool skip_sleeping_pairs=true) const
void enable_collision(const std::string &entity1_name, const std::string &entity2_name)
void disable_collision(const std::string &entity1_name, const std::string &entity2_name)
A named rigid body with state, material properties, geometry, and forces.
int32_t broad_phase_node() const
void set_broad_phase_node(int32_t node)
virtual bool add(const std::shared_ptr< T > &item)
bool _remove(const std::string &name)
std::string make_unique_name(const std::string &base) const
size_t size() const noexcept
const std::vector< std::shared_ptr< T > > & items() const noexcept
std::shared_ptr< T > get(const std::string &name) const
void set_max_size(size_t n)
std::unordered_map< std::string, size_t > m_name_map
std::vector< std::shared_ptr< T > > m_items_vec
bool _add(const std::shared_ptr< T > &item)
virtual bool remove(const std::string &name)
T * get_rawptr(const std::string &name) const noexcept
std::vector< std::shared_ptr< T > > & items() noexcept
FxNamedRegistry()=default
bool empty() const noexcept
void for_each(ExecPolicy &&policy, Func &&func)
void transform(ExecPolicy &&policy, Func &&func, std::vector< std::invoke_result_t< Func, std::shared_ptr< T > > > &results)
FxNamedRegistry(size_t max_size)
Axis-aligned bounding box used by broad-phase operations.
static FxAABB combine(const FxAABB &a, const FxAABB &b)