Initial. Added Loose/Tight Double Grid C++ implementation

This commit is contained in:
paladin 2026-09-09 09:49:16 +01:00
commit 65206162a6
2 changed files with 1866 additions and 0 deletions

933
loose_tight_double_grid.txt Normal file
View file

@ -0,0 +1,933 @@
// https://stackoverflow.com/questions/41946007/efficient-and-well-explained-implementation-of-a-quadtree-for-2d-collision-det/48330314#48330314
// https://pastebin.com/swvwTgnd
// ************************************************************************************
// SmallList.hpp
// ************************************************************************************
#ifndef SMALL_LIST_HPP
#define SMALL_LIST_HPP
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <vector>
// Stores a random-access sequence of elements similar to vector, but avoids
// heap allocations for small lists. T must be trivially constructible and
// destructible.
template <class T>
class SmallList
{
public:
// Creates an empty list.
SmallList();
// Creates a copy of the specified list.
SmallList(const SmallList& other);
// Copies the specified list.
SmallList& operator=(const SmallList& other);
// Destroys the list.
~SmallList();
// Returns the number of agents in the list.
int size() const;
// Returns the nth element.
T& operator[](int n);
// Returns the nth element in the list.
const T& operator[](int n) const;
// Returns an index to a matching element in the list or -1
// if the element is not found.
int find_index(const T& element) const;
// Clears the list.
void clear();
// Reserves space for n elements.
void reserve(int n);
// Inserts an element to the back of the list.
void push_back(const T& element);
/// Pops an element off the back of the list.
T pop_back();
// Swaps the contents of this list with the other.
void swap(SmallList& other);
// Returns a pointer to the underlying buffer.
T* data();
// Returns a pointer to the underlying buffer.
const T* data() const;
private:
enum {fixed_cap = 256};
struct ListData
{
ListData();
T buf[fixed_cap];
T* data;
int num;
int cap;
};
ListData ld;
};
/// Provides an indexed free list with constant-time removals from anywhere
/// in the list without invalidating indices. T must be trivially constructible
/// and destructible.
template <class T>
class FreeList
{
public:
/// Creates a new free list.
FreeList();
/// Inserts an element to the free list and returns an index to it.
int insert(const T& element);
// Removes the nth element from the free list.
void erase(int n);
// Removes all elements from the free list.
void clear();
// Returns the range of valid indices.
int range() const;
// Returns the nth element.
T& operator[](int n);
// Returns the nth element.
const T& operator[](int n) const;
// Reserves space for n elements.
void reserve(int n);
// Swaps the contents of the two lists.
void swap(FreeList& other);
private:
union FreeElement
{
T element;
int next;
};
SmallList<FreeElement> data;
int first_free;
};
// ---------------------------------------------------------------------------------
// SmallList Implementation
// ---------------------------------------------------------------------------------
template <class T>
SmallList<T>::ListData::ListData(): data(buf), num(0), cap(fixed_cap)
{
}
template <class T>
SmallList<T>::SmallList()
{
}
template <class T>
SmallList<T>::SmallList(const SmallList& other)
{
if (other.ld.cap == fixed_cap)
{
ld = other.ld;
ld.data = ld.buf;
}
else
{
reserve(other.ld.num);
for (int j=0; j < other.size(); ++j)
ld.data[j] = other.ld.data[j];
ld.num = other.ld.num;
ld.cap = other.ld.cap;
}
}
template <class T>
SmallList<T>& SmallList<T>::operator=(const SmallList<T>& other)
{
SmallList(other).swap(*this);
return *this;
}
template <class T>
SmallList<T>::~SmallList()
{
if (ld.data != ld.buf)
free(ld.data);
}
template <class T>
int SmallList<T>::size() const
{
return ld.num;
}
template <class T>
T& SmallList<T>::operator[](int n)
{
assert(n >= 0 && n < ld.num);
return ld.data[n];
}
template <class T>
const T& SmallList<T>::operator[](int n) const
{
assert(n >= 0 && n < ld.num);
return ld.data[n];
}
template <class T>
int SmallList<T>::find_index(const T& element) const
{
for (int j=0; j < ld.num; ++j)
{
if (ld.data[j] == element)
return j;
}
return -1;
}
template <class T>
void SmallList<T>::clear()
{
ld.num = 0;
}
template <class T>
void SmallList<T>::reserve(int n)
{
enum {type_size = sizeof(T)};
if (n > ld.cap)
{
if (ld.cap == fixed_cap)
{
ld.data = static_cast<T*>(malloc(n * type_size));
memcpy(ld.data, ld.buf, sizeof(ld.buf));
}
else
ld.data = static_cast<T*>(realloc(ld.data, n * type_size));
ld.cap = n;
}
}
template <class T>
void SmallList<T>::push_back(const T& element)
{
if (ld.num >= ld.cap)
reserve(ld.cap * 2);
ld.data[ld.num++] = element;
}
template <class T>
T SmallList<T>::pop_back()
{
return ld.data[--ld.num];
}
template <class T>
void SmallList<T>::swap(SmallList& other)
{
ListData& ld1 = ld;
ListData& ld2 = other.ld;
const int use_fixed1 = ld1.data == ld1.buf;
const int use_fixed2 = ld2.data == ld2.buf;
const ListData temp = ld1;
ld1 = ld2;
ld2 = temp;
if (use_fixed1)
ld2.data = ld2.buf;
if (use_fixed2)
ld1.data = ld1.buf;
}
template <class T>
T* SmallList<T>::data()
{
return ld.data;
}
template <class T>
const T* SmallList<T>::data() const
{
return ld.data;
}
// ---------------------------------------------------------------------------------
// FreeList Implementation
// ---------------------------------------------------------------------------------
template <class T>
FreeList<T>::FreeList(): first_free(-1)
{
}
template <class T>
int FreeList<T>::insert(const T& element)
{
if (first_free != -1)
{
const int index = first_free;
first_free = data[first_free].next;
data[index].element = element;
return index;
}
else
{
FreeElement fe;
fe.element = element;
data.push_back(fe);
return data.size() - 1;
}
}
template <class T>
void FreeList<T>::erase(int n)
{
assert(n >= 0 && n < data.size());
data[n].next = first_free;
first_free = n;
}
template <class T>
void FreeList<T>::clear()
{
data.clear();
first_free = -1;
}
template <class T>
int FreeList<T>::range() const
{
return data.size();
}
template <class T>
T& FreeList<T>::operator[](int n)
{
return data[n].element;
}
template <class T>
const T& FreeList<T>::operator[](int n) const
{
return data[n].element;
}
template <class T>
void FreeList<T>::reserve(int n)
{
data.reserve(n);
}
template <class T>
void FreeList<T>::swap(FreeList& other)
{
const int temp = first_free;
data.swap(other.data);
first_free = other.first_free;
other.first_free = temp;
}
#endif
// ************************************************************************************
// LGrid.hpp
// ************************************************************************************
#ifndef LGRID_HPP
#define LGRID_HPP
#include "SmallList.hpp"
struct LGridQuery4
{
// Stores the resulting elements of the SIMD query.
SmallList<int> elements[4];
};
struct LGridElt
{
// Stores the index to the next element in the loose cell using an indexed SLL.
int next;
// Stores the ID of the element. This can be used to associate external
// data to the element.
int id;
// Stores the center of the element.
float mx, my;
// Stores the half-size of the element relative to the upper-left corner
// of the grid.
float hx, hy;
};
struct LGridLooseCell
{
// Stores the extents of the grid cell relative to the upper-left corner
// of the grid which expands and shrinks with the elements inserted and
// removed.
float rect[4];
// Stores the index to the first element using an indexed SLL.
int head;
};
struct LGridLoose
{
// Stores all the cells in the loose grid.
LGridLooseCell* cells;
// Stores the number of columns, rows, and cells in the loose grid.
int num_cols, num_rows, num_cells;
// Stores the inverse size of a loose cell.
float inv_cell_w, inv_cell_h;
};
struct LGridTightCell
{
// Stores the index to the next loose cell in the grid cell.
int next;
// Stores the position of the loose cell in the grid.
int lcell;
};
struct LGridTight
{
// Stores all the tight cell nodes in the grid.
FreeList<LGridTightCell> cells;
// Stores the tight cell heads.
int* heads;
// Stores the number of columns, rows, and cells in the tight grid.
int num_cols, num_rows, num_cells;
// Stores the inverse size of a tight cell.
float inv_cell_w, inv_cell_h;
};
struct LGrid
{
// Stores the tight cell data for the grid.
LGridTight tight;
// Stores the loose cell data for the grid.
LGridLoose loose;
// Stores all the elements in the grid.
FreeList<LGridElt> elts;
// Stores the number of elements in the grid.
int num_elts;
// Stores the upper-left corner of the grid.
float x, y;
// Stores the size of the grid.
float w, h;
};
// Creates a loose grid encompassing the specified extents using the specified cell
// size. Elements inserted to the loose grid are only inserted in one cell, but the
// extents of each cell are allowed to expand and shrink. To avoid requiring every
// loose cell to be checked during a search, a second grid of tight cells referencing
// the loose cells is stored.
LGrid* lgrid_create(float lcell_w, float lcell_h, float tcell_w, float tcell_h,
float l, float t, float r, float b);
// Destroys the grid.
void lgrid_destroy(LGrid* grid);
// Returns the grid cell index for the specified position.
int lgrid_lcell_idx(LGrid* grid, float x, float y);
// Inserts an element to the grid.
void lgrid_insert(LGrid* grid, int id, float mx, float my, float hx, float hy);
// Removes an element from the grid.
void lgrid_remove(LGrid* grid, int id, float mx, float my);
// Moves an element in the grid from the former position to the new one.
void lgrid_move(LGrid* grid, int id, float prev_mx, float prev_my, float mx, float my);
// Returns all the element IDs that intersect the specified rectangle excluding elements
// with the specified ID to omit.
SmallList<int> lgrid_query(const LGrid* grid, float mx, float my, float hx, float hy, int omit_id);
// Returns all the element IDs that intersect the specified 4 rectangles excluding elements
// with the specified IDs to omit.
LGridQuery4 lgrid_query4(const LGrid* grid, const SimdVec4f* mx4, const SimdVec4f* my4,
const SimdVec4f* hx4, const SimdVec4f* hy4, const SimdVec4i* omit_id4);
// Returns true if the specified rectangle is inside the grid boundaries.
bool lgrid_in_bounds(const LGrid* grid, float mx, float my, float hx, float hy);
// Optimizes the grid, shrinking bounding boxes in response to removed elements and
// rearranging the memory of the grid to allow cache-friendly cell traversal.
void lgrid_optimize(LGrid* grid);
#endif
// ************************************************************************************
// LGrid.cpp
// ************************************************************************************
#include "LGrid.hpp"
#include <cstdlib>
#include <cfloat>
#include <utility>
static int ceil_div(float value, float divisor)
{
// Returns the value divided by the divisor rounded up.
const float resultf = value / divisor;
const int result = (int)resultf;
return result < resultf ? result+1: result;
}
static int min_int(int a, int b)
{
assert(sizeof(int) == 4);
a -= b;
a &= a >> 31;
return a + b;
}
static int max_int(int a, int b)
{
assert(sizeof(int) == 4);
a -= b;
a &= (~a) >> 31;
return a + b;
}
static float min_flt(float a, float b)
{
return std::min(a, b);
}
static float max_flt(float a, float b)
{
return std::max(a, b);
}
static int to_cell_idx(float val, float inv_cell_size, int num_cells)
{
const int cell_pos = (int)(val * inv_cell_size);
return min_int(max_int(cell_pos, 0), num_cells - 1);
}
static SimdVec4i to_tcell_idx4(const LGrid* grid, __m128 rect)
{
__m128 inv_cell_size_vec = simd_create4f(grid->tight.inv_cell_w, grid->tight.inv_cell_h,
grid->tight.inv_cell_w, grid->tight.inv_cell_h);
__m128 cell_xyf_vec = simd_mul4f(rect, inv_cell_size_vec);
__m128i clamp_vec = simd_create4i(grid->tight.num_cols-1, grid->tight.num_rows-1,
grid->tight.num_cols-1, grid->tight.num_rows-1);
__m128i cell_xy_vec = simd_clamp4i(simd_ftoi4f(cell_xyf_vec), simd_zero4i(), clamp_vec);
return simd_store4i(cell_xy_vec);
}
static void grid_optimize(LGrid* grid)
{
FreeList<LGridElt> new_elts;
new_elts.reserve(grid->num_elts);
for (int c=0; c < grid->loose.num_cells; ++c)
{
// Replace links to the old elements list to links in the new
// cache-friendly element list.
SmallList<int> new_elt_idxs;
LGridLooseCell* lcell = &grid->loose.cells[c];
while (lcell->head != -1)
{
const LGridElt* elt = &grid->elts[lcell->head];
new_elt_idxs.push_back(new_elts.insert(*elt));
lcell->head = elt->next;
}
for (int j=0; j < new_elt_idxs.size(); ++j)
{
const int new_elt_idx = new_elt_idxs[j];
new_elts[new_elt_idx].next = lcell->head;
lcell->head = new_elt_idx;
}
}
// Swap the new element list with the old one.
grid->elts.swap(new_elts);
}
static void expand_aabb(LGrid* grid, int cell_idx, float mx, float my, float hx, float hy)
{
LGridLooseCell* lcell = &grid->loose.cells[cell_idx];
const SimdVec4f prev_rect = {lcell->rect[0], lcell->rect[1], lcell->rect[2], lcell->rect[3]};
lcell->rect[0] = min_flt(lcell->rect[0], mx - hx);
lcell->rect[1] = min_flt(lcell->rect[1], my - hx);
lcell->rect[2] = max_flt(lcell->rect[2], mx + hx);
lcell->rect[3] = max_flt(lcell->rect[3], my + hy);
// Determine the cells occupied by the loose cell in the tight grid.
const SimdVec4f elt_rect = {mx-hx, my-hy, mx+hx, my+hy};
const SimdVec4i trect = to_tcell_idx4(grid, simd_load4f(&elt_rect));
if (prev_rect.data[0] > prev_rect.data[2])
{
// If the loose cell was empty, simply insert the loose cell
// to all the tight cells it occupies. We don't need to check
// to see if it was already inserted.
for (int ty = trect.data[1]; ty <= trect.data[3]; ++ty)
{
int* tight_row = grid->tight.heads + ty*grid->tight.num_cols;
for (int tx = trect.data[0]; tx <= trect.data[2]; ++tx)
{
const LGridTightCell new_tcell = {tight_row[tx], cell_idx};
tight_row[tx] = grid->tight.cells.insert(new_tcell);
}
}
}
else
{
// Only perform the insertion if the loose cell overlaps new tight cells.
const SimdVec4i prev_trect = to_tcell_idx4(grid, simd_load4f(&prev_rect));
if (trect.data[0] != prev_trect.data[0] || trect.data[1] != prev_trect.data[1] ||
trect.data[2] != prev_trect.data[2] || trect.data[3] != prev_trect.data[3])
{
for (int ty = trect.data[1]; ty <= trect.data[3]; ++ty)
{
int* tight_row = grid->tight.heads + ty*grid->tight.num_cols;
for (int tx = trect.data[0]; tx <= trect.data[2]; ++tx)
{
if (tx < prev_trect.data[0] || tx > prev_trect.data[2] ||
ty < prev_trect.data[1] || ty > prev_trect.data[3])
{
const LGridTightCell new_tcell = {tight_row[tx], cell_idx};
tight_row[tx] = grid->tight.cells.insert(new_tcell);
}
}
}
}
}
}
static __m128 element_rect(const LGridElt* elt)
{
return simd_create4f(elt->mx-elt->hx, elt->my-elt->hy,
elt->mx+elt->hx, elt->my+elt->hy);
}
LGrid* lgrid_create(float lcell_w, float lcell_h, float tcell_w, float tcell_h,
float l, float t, float r, float b)
{
const float w = r - l, h = b - t;
const int num_lcols = ceil_div(w, lcell_w), num_lrows = ceil_div(h, lcell_h);
const int num_tcols = ceil_div(w, tcell_w), num_trows = ceil_div(h, tcell_h);
LGrid* grid = new LGrid;
grid->num_elts = 0;
grid->x = l;
grid->y = t;
grid->h = w;
grid->w = h;
grid->loose.num_cols = num_lcols;
grid->loose.num_rows = num_lrows;
grid->loose.num_cells = grid->loose.num_cols * grid->loose.num_rows;
grid->loose.inv_cell_w = 1.0f / lcell_w;
grid->loose.inv_cell_h = 1.0f / lcell_h;
grid->tight.num_cols = num_tcols;
grid->tight.num_rows = num_trows;
grid->tight.num_cells = grid->tight.num_cols * grid->tight.num_rows;
grid->tight.inv_cell_w = 1.0f / tcell_w;
grid->tight.inv_cell_h = 1.0f / tcell_h;
// Initialize tight cell heads with -1 to indicate empty indexed SLLs.
grid->tight.heads = new int[grid->tight.num_cells];
for (int j=0; j < grid->tight.num_cells; ++j)
grid->tight.heads[j] = -1;
// Initialize all the loose cells.
grid->loose.cells = new LGridLooseCell[grid->loose.num_cells];
for (int c=0; c < grid->loose.num_cells; ++c)
{
grid->loose.cells[c].head = -1;
grid->loose.cells[c].rect[0] = FLT_MAX;
grid->loose.cells[c].rect[1] = FLT_MAX;
grid->loose.cells[c].rect[2] = -FLT_MAX;
grid->loose.cells[c].rect[3] = -FLT_MAX;
}
return grid;
}
void lgrid_destroy(LGrid* grid)
{
delete[] grid->loose.cells;
delete[] grid->tight.heads;
delete grid;
}
int lgrid_lcell_idx(LGrid* grid, float x, float y)
{
const int cell_x = to_cell_idx(x - grid->x, grid->loose.inv_cell_w, grid->loose.num_cols);
const int cell_y = to_cell_idx(y - grid->y, grid->loose.inv_cell_h, grid->loose.num_rows);
return cell_y * grid->loose.num_cols + cell_x;
}
void lgrid_insert(LGrid* grid, int id, float mx, float my, float hx, float hy)
{
const int cell_idx = lgrid_lcell_idx(grid, mx, my);
LGridLooseCell* lcell = &grid->loose.cells[cell_idx];
// Insert the element to the appropriate loose cell and row.
const LGridElt new_elt = {lcell->head, id, mx - grid->x, my - grid->y, hx, hy};
lcell->head = grid->elts.insert(new_elt);
++grid->num_elts;
// Expand the loose cell's bounding box to fit the new element.
expand_aabb(grid, cell_idx, mx, my, hx, hy);
}
void lgrid_remove(LGrid* grid, int id, float mx, float my)
{
// Find the element in the loose cell.
LGridLooseCell* lcell = &grid->loose.cells[lgrid_lcell_idx(grid, mx, my)];
int* link = &lcell->head;
while (grid->elts[*link].id != id)
link = &grid->elts[*link].next;
// Remove the element from the loose cell and row.
const int elt_idx = *link;
*link = grid->elts[elt_idx].next;
grid->elts.erase(elt_idx);
--grid->num_elts;
}
void lgrid_move(LGrid* grid, int id, float prev_mx, float prev_my, float mx, float my)
{
const int prev_cell_idx = lgrid_lcell_idx(grid, prev_mx, prev_my);
const int new_cell_idx = lgrid_lcell_idx(grid, mx, my);
LGridLooseCell* lcell = &grid->loose.cells[prev_cell_idx];
if (prev_cell_idx == new_cell_idx)
{
// Find the element in the loose cell.
int elt_idx = lcell->head;
while (grid->elts[elt_idx].id != id)
elt_idx = grid->elts[elt_idx].next;
// Since the element is still inside the same cell, we can simply overwrite
// its position and expand the loose cell's AABB.
mx -= grid->x;
my -= grid->y;
grid->elts[elt_idx].mx = mx;
grid->elts[elt_idx].my = my;
expand_aabb(grid, prev_cell_idx, mx, my, grid->elts[elt_idx].hx, grid->elts[elt_idx].hy);
}
else
{
// Find the element in the loose cell.
int* link = &lcell->head;
while (grid->elts[*link].id != id)
link = &grid->elts[*link].next;
const int elt_idx = *link;
const float hx = grid->elts[elt_idx].hx;
const float hy = grid->elts[elt_idx].hy;
// If the element has moved into a different loose cell, remove
// remove the element from the previous loose cell and row.
*link = grid->elts[elt_idx].next;
grid->elts.erase(elt_idx);
--grid->num_elts;
// Now insert the element to its new position.
lgrid_insert(grid, id, mx, my, hx, hy);
}
}
SmallList<int> lgrid_query(const LGrid* grid, float mx, float my, float hx, float hy, int omit_id)
{
mx -= grid->x;
my -= grid->y;
// Compute the tight cell extents [min_tx, min_ty, max_tx, max_ty].
const SimdVec4f qrect = {mx-hx, my-hy, mx+hx, my+hy};
__m128 qrect_vec = simd_load4f(&qrect);
const SimdVec4i trect = to_tcell_idx4(grid, qrect_vec);
// Gather the intersecting loose cells in the tight cells that intersect.
SmallList<int> lcell_idxs;
for (int ty = trect.data[1]; ty <= trect.data[3]; ++ty)
{
const int* tight_row = grid->tight.heads + ty*grid->tight.num_cols;
for (int tx = trect.data[0]; tx <= trect.data[2]; ++tx)
{
// Iterate through the loose cells that intersect the tight cells.
int tcell_idx = tight_row[tx];
while (tcell_idx != -1)
{
const LGridTightCell* tcell = &grid->tight.cells[tcell_idx];
const LGridLooseCell* lcell = &grid->loose.cells[tcell->lcell];
if (lcell_idxs.find_index(tcell->lcell) == -1 && simd_rect_intersect4f(qrect_vec, simd_loadu4f(lcell->rect)))
lcell_idxs.push_back(tcell->lcell);
tcell_idx = tcell->next;
}
}
}
// For each loose cell, determine what elements intersect.
SmallList<int> res;
for (int j=0; j < lcell_idxs.size(); ++j)
{
const LGridLooseCell* lcell = &grid->loose.cells[lcell_idxs[j]];
int elt_idx = lcell->head;
while (elt_idx != -1)
{
// If the element intersects the search rectangle, add it to the
// resulting elements unless it has an ID that should be omitted.
const LGridElt* elt = &grid->elts[elt_idx];
if (elt->id != omit_id && simd_rect_intersect4f(qrect_vec, element_rect(elt)))
res.push_back(elt->id);
elt_idx = elt->next;
}
}
return res;
}
LGridQuery4 lgrid_query4(const LGrid* grid, const SimdVec4f* mx4, const SimdVec4f* my4,
const SimdVec4f* hx4, const SimdVec4f* hy4, const SimdVec4i* omit_id4)
{
__m128 hx_vec = simd_load4f(hx4), hy_vec = simd_load4f(hy4);
__m128 mx_vec = simd_sub4f(simd_load4f(mx4), simd_scalar4f(grid->x));
__m128 my_vec = simd_sub4f(simd_load4f(my4), simd_scalar4f(grid->y));
__m128 ql_vec = simd_sub4f(mx_vec, hx_vec), qt_vec = simd_sub4f(my_vec, hy_vec);
__m128 qr_vec = simd_add4f(mx_vec, hx_vec), qb_vec = simd_add4f(my_vec, hy_vec);
__m128 inv_cell_w_vec = simd_scalar4f(grid->tight.inv_cell_w), inv_cell_h_vec = simd_scalar4f(grid->tight.inv_cell_h);
__m128i max_x_vec = simd_scalar4i(grid->tight.num_cols-1), max_y_vec = simd_scalar4i(grid->tight.num_rows-1);
__m128i tmin_x_vec = simd_clamp4i(simd_ftoi4f(simd_mul4f(ql_vec, inv_cell_w_vec)), simd_zero4i(), max_x_vec);
__m128i tmin_y_vec = simd_clamp4i(simd_ftoi4f(simd_mul4f(qt_vec, inv_cell_h_vec)), simd_zero4i(), max_y_vec);
__m128i tmax_x_vec = simd_clamp4i(simd_ftoi4f(simd_mul4f(qr_vec, inv_cell_w_vec)), simd_zero4i(), max_x_vec);
__m128i tmax_y_vec = simd_clamp4i(simd_ftoi4f(simd_mul4f(qb_vec, inv_cell_h_vec)), simd_zero4i(), max_y_vec);
const SimdVec4i tmin_x4 = simd_store4i(tmin_x_vec), tmin_y4 = simd_store4i(tmin_y_vec);
const SimdVec4i tmax_x4 = simd_store4i(tmax_x_vec), tmax_y4 = simd_store4i(tmax_y_vec);
const SimdVec4f ql4 = simd_store4f(ql_vec), qt4 = simd_store4f(qt_vec);
const SimdVec4f qr4 = simd_store4f(qr_vec), qb4 = simd_store4f(qb_vec);
LGridQuery4 res4;
for (int k=0; k < 4; ++k)
{
const int trect[4] = {tmin_x4.data[k], tmin_y4.data[k], tmax_x4.data[k], tmax_y4.data[k]};
const int omit_id = omit_id4->data[k];
// Gather the intersecting loose cells in the tight cells that intersect.
SmallList<int> lcell_idxs;
__m128 qrect_vec = simd_create4f(ql4.data[k], qt4.data[k], qr4.data[k], qb4.data[k]);
for (int ty = trect[1]; ty <= trect[3]; ++ty)
{
const int* tight_row = grid->tight.heads + ty*grid->tight.num_cols;
for (int tx = trect[0]; tx <= trect[2]; ++tx)
{
// Iterate through the loose cells that intersect the tight cells.
int tcell_idx = tight_row[tx];
while (tcell_idx != -1)
{
const LGridTightCell* tcell = &grid->tight.cells[tcell_idx];
if (lcell_idxs.find_index(tcell->lcell) && simd_rect_intersect4f(qrect_vec, simd_loadu4f(grid->loose.cells[tcell->lcell].rect)))
lcell_idxs.push_back(tcell->lcell);
tcell_idx = tcell->next;
}
}
}
// For each loose cell, determine what elements intersect.
for (int j=0; j < lcell_idxs.size(); ++j)
{
const LGridLooseCell* lcell = &grid->loose.cells[lcell_idxs[j]];
int elt_idx = lcell->head;
while (elt_idx != -1)
{
// If the element intersects the search rectangle, add it to the
// resulting elements unless it has an ID that should be omitted.
const LGridElt* elt = &grid->elts[elt_idx];
if (elt->id != omit_id && simd_rect_intersect4f(qrect_vec, element_rect(elt)))
res4.elements[k].push_back(elt->id);
elt_idx = elt->next;
}
}
}
return res4;
}
bool lgrid_in_bounds(const LGrid* grid, float mx, float my, float hx, float hy)
{
mx -= grid->x;
my -= grid->y;
const float x1 = mx-hx, y1 = my-hy, x2 = mx+hx, y2 = my+hy;
return x1 >= 0.0f && x2 < grid->w && y1 >= 0.0f && y2 < grid->h;
}
void lgrid_optimize(LGrid* grid)
{
// Clear all the tight cell data.
for (int j=0; j < grid->tight.num_cells; ++j)
grid->tight.heads[j] = -1;
grid->tight.cells.clear();
// Optimize the memory layout of the grid.
grid_optimize(grid);
#pragma omp parallel for
for (int c=0; c < grid->loose.num_cells; ++c)
{
// Empty the loose cell's bounding box.
LGridLooseCell* lcell = &grid->loose.cells[c];
lcell->rect[0] = FLT_MAX;
lcell->rect[1] = FLT_MAX;
lcell->rect[2] = -FLT_MAX;
lcell->rect[3] = -FLT_MAX;
// Expand the bounding box by each element's extents in
// the loose cell.
int elt_idx = lcell->head;
while (elt_idx != -1)
{
const LGridElt* elt = &grid->elts[elt_idx];
lcell->rect[0] = min_flt(lcell->rect[0], elt->mx - elt->hx);
lcell->rect[1] = min_flt(lcell->rect[1], elt->my - elt->hy);
lcell->rect[2] = max_flt(lcell->rect[2], elt->mx + elt->hx);
lcell->rect[3] = max_flt(lcell->rect[3], elt->my + elt->hy);
elt_idx = elt->next;
}
}
for (int c=0; c < grid->loose.num_cells; ++c)
{
// Insert the loose cell to all the tight cells in which
// it now belongs.
LGridLooseCell* lcell = &grid->loose.cells[c];
const SimdVec4i trect = to_tcell_idx4(grid, simd_loadu4f(lcell->rect));
for (int ty = trect.data[1]; ty <= trect.data[3]; ++ty)
{
int* tight_row = grid->tight.heads + ty*grid->tight.num_cols;
for (int tx = trect.data[0]; tx <= trect.data[2]; ++tx)
{
const LGridTightCell new_tcell = {tight_row[tx], c};
tight_row[tx] = grid->tight.cells.insert(new_tcell);
}
}
}
}