efl/src/lib/eina/eina_quadtree.c

936 lines
23 KiB
C

/* EINA - EFL data type library
* Copyright (C) 2010 Cedric Bail
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library;
* if not, see <http://www.gnu.org/licenses/>.
*/
/**
* @page tutorial_quadtree_page QuadTree Tutorial
*
* to be written...
*
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdlib.h>
#include <stdio.h>
#include "eina_quadtree.h"
#include "eina_magic.h"
#include "eina_mempool.h"
#include "eina_list.h"
#include "eina_inlist.h"
#include "eina_trash.h"
#include "eina_log.h"
#include "eina_rectangle.h"
#include "eina_private.h"
typedef struct _Eina_QuadTree_Root Eina_QuadTree_Root;
static const char EINA_MAGIC_QUADTREE_STR[] = "Eina QuadTree";
static const char EINA_MAGIC_QUADTREE_ROOT_STR[] = "Eina QuadTree Root";
static const char EINA_MAGIC_QUADTREE_ITEM_STR[] = "Eina QuadTree Item";
#define EINA_MAGIC_CHECK_QUADTREE(d, ...) \
do { \
if (!EINA_MAGIC_CHECK(d, EINA_MAGIC_QUADTREE)) \
{ \
EINA_MAGIC_FAIL(d, EINA_MAGIC_QUADTREE); \
return __VA_ARGS__; \
} \
} while(0);
#define EINA_MAGIC_CHECK_QUADTREE_ROOT(d, ...) \
do { \
if (!EINA_MAGIC_CHECK(d, EINA_MAGIC_QUADTREE_ROOT)) \
{ \
EINA_MAGIC_FAIL(d, EINA_MAGIC_QUADTREE_ROOT); \
return __VA_ARGS__; \
} \
} while(0);
#define EINA_MAGIC_CHECK_QUADTREE_ITEM(d, ...) \
do { \
if (!EINA_MAGIC_CHECK(d, EINA_MAGIC_QUADTREE_ITEM)) \
{ \
EINA_MAGIC_FAIL(d, EINA_MAGIC_QUADTREE_ITEM); \
return __VA_ARGS__; \
} \
} while(0);
struct _Eina_QuadTree
{
Eina_QuadTree_Root *root;
Eina_List *hidden;
size_t root_count;
size_t items_count;
Eina_Trash *items_trash;
Eina_Trash *root_trash;
Eina_Inlist *change;
Eina_Inlist *cached;
Eina_Rectangle target;
size_t index;
struct
{
Eina_Quad_Callback v;
Eina_Quad_Callback h;
} func;
struct
{
size_t w;
size_t h;
} geom;
Eina_Bool resize : 1;
Eina_Bool lost : 1;
EINA_MAGIC
};
struct _Eina_QuadTree_Root
{
Eina_QuadTree_Root *parent;
Eina_QuadTree_Root *left;
Eina_QuadTree_Root *right;
Eina_List *both;
Eina_Bool sorted : 1;
EINA_MAGIC
};
struct _Eina_QuadTree_Item
{
EINA_INLIST;
Eina_QuadTree *quad;
Eina_QuadTree_Root *root;
const void *object;
size_t index;
Eina_Bool change : 1;
Eina_Bool delete_me : 1;
Eina_Bool visible : 1;
Eina_Bool hidden : 1;
EINA_MAGIC
};
static int _eina_quadtree_log_dom = -1;
static Eina_Mempool *eina_quadtree_root_mp = NULL;
static Eina_Mempool *_eina_quadtree_items_mp = NULL;
#ifdef ERR
#undef ERR
#endif
#define ERR(...) EINA_LOG_DOM_ERR(_eina_quadtree_log_dom, __VA_ARGS__)
#ifdef DBG
#undef DBG
#endif
#define DBG(...) EINA_LOG_DOM_DBG(_eina_quadtree_log_dom, __VA_ARGS__)
static int
_eina_quadtree_item_cmp(const void *a, const void *b)
{
const Eina_QuadTree_Item *i = a;
const Eina_QuadTree_Item *j = b;
if (i->index == j->index) return 0;
if (i->index > j->index) return 1;
return -1;
}
static Eina_QuadTree_Root *
eina_quadtree_root_free(Eina_QuadTree *q, Eina_QuadTree_Root *root)
{
Eina_QuadTree_Item *item;
if (!root)
return NULL;
EINA_MAGIC_CHECK_QUADTREE_ROOT(root, NULL);
EINA_LIST_FREE(root->both, item)
eina_mempool_free(_eina_quadtree_items_mp, item);
root->left = eina_quadtree_root_free(q, root->left);
root->right = eina_quadtree_root_free(q, root->right);
EINA_MAGIC_SET(root, 0);
eina_mempool_free(eina_quadtree_root_mp, root);
return NULL;
}
static Eina_QuadTree_Root *
eina_quadtree_root_rebuild_pre(Eina_QuadTree *q,
Eina_Inlist **change,
Eina_QuadTree_Root *root)
{
Eina_QuadTree_Item *item;
if (!root)
return NULL;
EINA_LIST_FREE(root->both, item)
{
if (item->visible)
*change = eina_inlist_append(*change, EINA_INLIST_GET(item));
else if (!item->hidden)
{
q->hidden = eina_list_append(q->hidden, item);
item->hidden = EINA_TRUE;
item->root = NULL;
}
}
root->left = eina_quadtree_root_rebuild_pre(q, change, root->left);
root->right = eina_quadtree_root_rebuild_pre(q, change, root->right);
EINA_MAGIC_SET(root, 0);
if (q->root_count > 50)
eina_mempool_free(eina_quadtree_root_mp, root);
else
{
eina_trash_push(&q->root_trash, root);
q->root_count++;
}
return NULL;
}
static size_t
_eina_quadtree_split(Eina_Inlist *objects,
Eina_QuadTree_Root *root,
Eina_Inlist **left,
Eina_Inlist **right,
Eina_Quad_Callback func,
int border,
int middle)
{
Eina_QuadTree_Item *object;
middle /= 2;
if (middle <= 4)
while (objects)
{
object = EINA_INLIST_CONTAINER_GET(objects, Eina_QuadTree_Item);
objects = objects->next;
object->change = EINA_FALSE;
if (!object->visible)
{
if (!object->hidden)
{
object->hidden = EINA_TRUE;
object->quad->hidden = eina_list_append(
object->quad->hidden,
object);
}
continue;
}
if (object->hidden)
{
object->hidden = EINA_FALSE;
object->quad->hidden = eina_list_remove(object->quad->hidden,
object);
}
if (!object->delete_me)
{
if (root->sorted)
root->both = eina_list_sorted_insert(root->both,
_eina_quadtree_item_cmp,
object);
else
root->both = eina_list_append(root->both, object);
object->root = root;
}
else
eina_quadtree_del(object);
}
else
while (objects)
{
object = EINA_INLIST_CONTAINER_GET(objects, Eina_QuadTree_Item);
objects = objects->next;
object->change = EINA_FALSE;
if (!object->visible)
{
if (!object->hidden)
{
object->hidden = EINA_TRUE;
object->quad->hidden = eina_list_append(
object->quad->hidden,
object);
}
continue;
}
if (object->hidden)
{
object->hidden = EINA_FALSE;
object->quad->hidden = eina_list_remove(object->quad->hidden,
object);
}
if (!object->delete_me)
{
switch (func(object->object, border + middle))
{
case EINA_QUAD_LEFT:
*left = eina_inlist_append(*left, EINA_INLIST_GET(object));
break;
case EINA_QUAD_RIGHT:
*right =
eina_inlist_append(*right, EINA_INLIST_GET(object));
break;
case EINA_QUAD_BOTH:
root->both = eina_list_append(root->both, object);
object->root = root;
break;
default:
abort();
}
}
else
eina_quadtree_del(object);
}
return middle;
}
static Eina_QuadTree_Root *
_eina_quadtree_update(Eina_QuadTree *q, Eina_QuadTree_Root *parent,
Eina_QuadTree_Root *root, Eina_Inlist *objects,
Eina_Bool direction, Eina_Rectangle *size)
{
Eina_Inlist *right = NULL;
Eina_Inlist *left = NULL;
size_t w2;
size_t h2;
if (!objects)
return root;
if (!root)
{
root = eina_trash_pop(&q->root_trash);
if (!root)
root = eina_mempool_malloc(eina_quadtree_root_mp, sizeof (Eina_QuadTree_Root));
else
q->root_count--;
if (!root)
/* FIXME: NOT GOOD TIMING, WE ARE GOING TO LEAK MORE MEMORY */
return NULL;
root->parent = parent;
root->both = NULL;
root->left = NULL;
root->right = NULL;
root->sorted = EINA_TRUE;
EINA_MAGIC_SET(root, EINA_MAGIC_QUADTREE_ROOT);
}
w2 = 0;
h2 = 0;
if (direction)
w2 = _eina_quadtree_split(objects, root,
&left, &right,
q->func.h, size->x, size->w);
else
h2 = _eina_quadtree_split(objects, root,
&left, &right,
q->func.v, size->y, size->h);
size->w -= w2; size->h -= h2;
root->left = _eina_quadtree_update(q, root,
root->left, left,
!direction, size);
size->x += w2; size->y += h2;
root->right = _eina_quadtree_update(q, root,
root->right, right,
!direction, size);
size->x -= w2; size->y -= h2;
size->w += w2; size->h += h2;
return root;
}
static Eina_Inlist *
_eina_quadtree_merge(Eina_Inlist *result,
Eina_List *both)
{
Eina_QuadTree_Item *item;
Eina_QuadTree_Item *b;
Eina_Inlist *moving;
if (!both)
return result;
if (!result)
{
Eina_List *l;
EINA_LIST_FOREACH(both, l, item)
if (item->visible)
result = eina_inlist_append(result, EINA_INLIST_GET(item));
return result;
}
moving = result;
item = EINA_INLIST_CONTAINER_GET(moving, Eina_QuadTree_Item);
b = eina_list_data_get(both);
while (both && moving)
{
if (!b->visible)
{
both = eina_list_next(both);
b = eina_list_data_get(both);
continue;
}
if (_eina_quadtree_item_cmp(item, b) < 0)
{
/* moving is still lower than item, so we can continue to the next one. */
moving = moving->next;
item = EINA_INLIST_CONTAINER_GET(moving, Eina_QuadTree_Item);
}
else
{
/* we just get above the limit of both, so insert it */
result = eina_inlist_prepend_relative(result,
EINA_INLIST_GET(b),
moving);
both = eina_list_next(both);
b = eina_list_data_get(both);
}
}
item = EINA_INLIST_CONTAINER_GET(result->last, Eina_QuadTree_Item);
while (both)
{
b = eina_list_data_get(both);
if (b->visible)
{
if (_eina_quadtree_item_cmp(item, b) < 0)
break;
result = eina_inlist_prepend_relative(result,
EINA_INLIST_GET(b),
result->last);
}
both = eina_list_next(both);
}
while (both)
{
b = eina_list_data_get(both);
if (b->visible)
result = eina_inlist_append(result, EINA_INLIST_GET(b));
both = eina_list_next(both);
}
return result;
}
static Eina_Inlist *
_eina_quadtree_collide(Eina_Inlist *result,
Eina_QuadTree_Root *root,
Eina_Bool direction, Eina_Rectangle *size,
Eina_Rectangle *target)
{
if (!root)
return result;
if (!root->sorted)
{
root->both = eina_list_sort(root->both, -1, _eina_quadtree_item_cmp);
root->sorted = EINA_TRUE;
}
result = _eina_quadtree_merge(result, root->both);
DBG("%p: %i in both for (%i, %i - %i, %i)",
root, eina_list_count(root->both),
size->x, size->y, size->w, size->h);
if (direction)
{
int middle = size->w / 2;
size->w -= middle;
if (eina_spans_intersect(size->x, size->w, target->x, target->w))
result = _eina_quadtree_collide(result, root->left,
!direction, size,
target);
size->x += middle;
if (eina_spans_intersect(size->x, size->w, target->x, target->w))
result = _eina_quadtree_collide(result, root->right,
!direction, size,
target);
size->x -= middle;
size->w += middle;
}
else
{
int middle = size->h / 2;
size->h -= middle;
if (eina_spans_intersect(size->y, size->h, target->y, target->h))
result = _eina_quadtree_collide(result, root->left,
!direction, size,
target);
size->y += middle;
if (eina_spans_intersect(size->y, size->h, target->y, target->h))
result = _eina_quadtree_collide(result, root->right,
!direction, size,
target);
size->y -= middle;
size->h += middle;
}
return result;
}
static void
_eina_quadtree_remove(Eina_QuadTree_Item *object)
{
if (!object->root)
return;
object->root->both = eina_list_remove(object->root->both, object);
if (object->root->both)
goto end;
if (object->root->left)
goto end;
if (object->root->right)
goto end;
/* The root is not useful anymore... */
if (object->root->parent)
{
if (object->root->parent->left == object->root)
object->root->parent->left = NULL;
else
object->root->parent->right = NULL;
object->root->parent = NULL;
}
else
object->quad->root = NULL;
if (object->quad->root_count > 50)
eina_mempool_free(eina_quadtree_root_mp, object->root);
else
{
eina_trash_push(&object->quad->root_trash, object->root);
object->quad->root_count++;
}
end:
object->root = NULL;
}
EAPI Eina_QuadTree *
eina_quadtree_new(size_t w, size_t h,
Eina_Quad_Callback vertical, Eina_Quad_Callback horizontal)
{
Eina_QuadTree *result;
if (!vertical || !horizontal || h == 0 || w == 0)
return NULL;
result = calloc(1, sizeof (Eina_QuadTree));
if (!result)
return NULL;
result->func.v = vertical;
result->func.h = horizontal;
result->geom.w = w;
result->geom.h = h;
result->change = NULL;
result->lost = EINA_TRUE;
EINA_MAGIC_SET(result, EINA_MAGIC_QUADTREE);
return result;
}
EAPI void
eina_quadtree_free(Eina_QuadTree *q)
{
Eina_QuadTree_Item *item;
if (!q)
return;
EINA_MAGIC_CHECK_QUADTREE(q);
while (q->change)
{
item = EINA_INLIST_CONTAINER_GET(q->change, Eina_QuadTree_Item);
q->change = q->change->next;
if (!item->hidden)
eina_mempool_free(_eina_quadtree_items_mp, item);
}
EINA_LIST_FREE(q->hidden, item)
eina_mempool_free(_eina_quadtree_items_mp, item);
eina_quadtree_root_free(q, q->root);
while (q->items_trash)
{
item = eina_trash_pop(&q->items_trash);
eina_mempool_free(_eina_quadtree_items_mp, item);
}
while (q->root_trash)
{
Eina_QuadTree_Root *root;
root = eina_trash_pop(&q->root_trash);
eina_mempool_free(eina_quadtree_root_mp, root);
}
EINA_MAGIC_SET(q, 0);
free(q);
}
EAPI Eina_QuadTree_Item *
eina_quadtree_add(Eina_QuadTree *q, const void *object)
{
Eina_QuadTree_Item *result;
EINA_MAGIC_CHECK_QUADTREE(q, NULL);
if (!object)
return NULL;
result = eina_trash_pop(&q->items_trash);
if (!result)
result = eina_mempool_malloc(_eina_quadtree_items_mp, sizeof (Eina_QuadTree_Item));
else
q->items_count--;
if (!result)
return NULL;
result->quad = q;
result->root = NULL;
result->object = object;
result->index = q->index++;
result->change = EINA_TRUE;
result->delete_me = EINA_FALSE;
result->visible = EINA_TRUE;
result->hidden = EINA_FALSE;
EINA_MAGIC_SET(result, EINA_MAGIC_QUADTREE_ITEM);
/* Insertion is delayed until we really need to use it */
q->change = eina_inlist_append(q->change, EINA_INLIST_GET(result));
return result;
}
EAPI Eina_Bool
eina_quadtree_del(Eina_QuadTree_Item *object)
{
if (!object)
return EINA_FALSE;
EINA_MAGIC_CHECK_QUADTREE_ITEM(object, EINA_FALSE);
_eina_quadtree_remove(object);
if (object->change)
{
/* This object is still in the update array, delaying it's removal !*/
object->delete_me = EINA_TRUE;
object->visible = EINA_TRUE;
return EINA_TRUE;
}
if (object->hidden)
{
object->quad->hidden = eina_list_remove(object->quad->hidden, object);
object->hidden = EINA_TRUE;
}
/* This object is not anymore inside the tree, we can remove it now !*/
EINA_MAGIC_SET(object, 0);
if (object->quad->items_count > 256)
eina_mempool_free(_eina_quadtree_items_mp, object);
else
{
object->quad->items_count++;
eina_trash_push(&object->quad->items_trash, object);
}
return EINA_TRUE;
}
EAPI Eina_Bool
eina_quadtree_change(Eina_QuadTree_Item *object)
{
EINA_MAGIC_CHECK_QUADTREE_ITEM(object, EINA_FALSE);
if (object->delete_me || !object->visible)
return EINA_FALSE;
if (object->quad->resize)
return EINA_TRUE;
/* Delaying change until needed */
if (!object->change)
object->quad->change = eina_inlist_append(object->quad->change,
EINA_INLIST_GET(object));
object->change = EINA_TRUE;
_eina_quadtree_remove(object);
return EINA_TRUE;
}
EAPI Eina_Bool
eina_quadtree_hide(Eina_QuadTree_Item *object)
{
EINA_MAGIC_CHECK_QUADTREE_ITEM(object, EINA_FALSE);
object->visible = EINA_FALSE;
return EINA_TRUE;
}
EAPI Eina_Bool
eina_quadtree_show(Eina_QuadTree_Item *object)
{
EINA_MAGIC_CHECK_QUADTREE_ITEM(object, EINA_FALSE);
object->quad->lost = EINA_TRUE;
if (object->visible)
return EINA_TRUE;
object->visible = EINA_TRUE;
if (!object->change)
return eina_quadtree_change(object);
return EINA_TRUE;
}
EAPI Eina_Inlist *
eina_quadtree_collide(Eina_QuadTree *q, int x, int y, int w, int h)
{
Eina_Rectangle canvas;
EINA_MAGIC_CHECK_QUADTREE(q, NULL);
/* Now we need the tree to be up to date, so it's time */
if (q->resize) /* Full rebuild needed ! */
{
DBG("resizing quadtree");
q->root = eina_quadtree_root_rebuild_pre(q, &q->change, q->root);
q->resize = EINA_FALSE;
}
EINA_RECTANGLE_SET(&canvas, 0, 0, q->geom.w, q->geom.h);
if (q->change)
{
DBG("updating quadtree content");
q->root = _eina_quadtree_update(q, NULL, q->root, q->change,
EINA_FALSE, &canvas);
q->change = NULL;
q->lost = EINA_TRUE;
}
if (q->target.x != x
|| q->target.y != y
|| q->target.w != w
|| q->target.h != h)
{
DBG("new target");
EINA_RECTANGLE_SET(&q->target, x, y, w, h);
q->lost = EINA_TRUE;
}
if (q->lost)
{
DBG("computing collide");
q->cached = _eina_quadtree_collide(NULL, q->root,
EINA_FALSE, &canvas,
&q->target);
q->lost = EINA_FALSE;
}
return q->cached;
}
EAPI void *
eina_quadtree_object(Eina_Inlist *item)
{
Eina_QuadTree_Item *qi;
if (!item)
return NULL;
qi = EINA_INLIST_CONTAINER_GET(item, Eina_QuadTree_Item);
if (!qi)
return NULL;
EINA_MAGIC_CHECK_QUADTREE_ITEM(qi, NULL);
if (!qi->visible)
return NULL;
return (void *)qi->object;
}
EAPI void
eina_quadtree_resize(Eina_QuadTree *q, size_t w, size_t h)
{
EINA_MAGIC_CHECK_QUADTREE(q);
if (q->geom.w == w
&& q->geom.h == h)
return;
q->resize = EINA_TRUE;
q->geom.w = w;
q->geom.h = h;
}
EAPI void
eina_quadtree_cycle(Eina_QuadTree *q)
{
EINA_MAGIC_CHECK_QUADTREE(q);
q->index = 0;
}
EAPI void
eina_quadtree_increase(Eina_QuadTree_Item *object)
{
size_t tmp;
EINA_MAGIC_CHECK_QUADTREE_ITEM(object);
tmp = object->quad->index++;
if (object->index == tmp)
return;
object->index = tmp;
if (object->root)
object->root->sorted = EINA_FALSE;
}
Eina_Bool
eina_quadtree_init(void)
{
const char *choice, *tmp;
_eina_quadtree_log_dom = eina_log_domain_register("eina_quadtree",
EINA_LOG_COLOR_DEFAULT);
if (_eina_quadtree_log_dom < 0)
{
EINA_LOG_ERR("Could not register log domain: eina_quadtree");
return EINA_FALSE;
}
#define EMS(n) eina_magic_string_static_set(n, n ## _STR)
EMS(EINA_MAGIC_QUADTREE);
EMS(EINA_MAGIC_QUADTREE_ROOT);
EMS(EINA_MAGIC_QUADTREE_ITEM);
#undef EMS
#ifdef EINA_DEFAULT_MEMPOOL
choice = "pass_through";
#else
choice = "chained_mempool";
#endif
tmp = getenv("EINA_MEMPOOL");
if (tmp && tmp[0])
choice = tmp;
_eina_quadtree_items_mp = eina_mempool_add(choice, "QuadTree Item", NULL,
sizeof (Eina_QuadTree_Item), 32);
eina_quadtree_root_mp = eina_mempool_add(choice, "QuadTree Root", NULL,
sizeof (Eina_QuadTree_Root), 8);
return EINA_TRUE;
}
Eina_Bool
eina_quadtree_shutdown(void)
{
eina_mempool_del(eina_quadtree_root_mp);
eina_mempool_del(_eina_quadtree_items_mp);
eina_log_domain_unregister(_eina_quadtree_log_dom);
_eina_quadtree_log_dom = -1;
return EINA_TRUE;
}