/* Copyright 2022-2026 Kurt Nienhaus
 *
 * This file is part of VgaGames.
 * VgaGames is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * VgaGames 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 General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with VgaGames.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include "collision.h"

#define STD_RECT   10
#define STD_LEVEL   5

#if QUADTREE_QUAD != 4
# error "QUADTREE_QUAD != 4"
#endif

void quadtree_init(struct vgi_quadtree *, unsigned int, const struct VG_Rect *, int, int);
void quadtree_dest(struct vgi_quadtree *);
void quadtree_clear(struct vgi_quadtree *);
void quadtree_insert(struct vgi_quadtree *, unsigned int, const struct VG_Rect *, int);
void quadtree_remove(struct vgi_quadtree *, unsigned int);
int quadtree_setpos(struct vgi_quadtree *, unsigned int, const struct VG_Rect *, struct VG_Coll **);
void quadtree_mark(struct vgi_quadtree *, struct VG_Image *, int, const char *);
void quadtree_dump(FILE *, struct vgi_quadtree *);

static int find_instance(struct vgi_quadtree *, unsigned int);
static const char * get_objid(struct vgi_quadtree *, unsigned int, const char *);
static void qt_insert(struct vgi_quadtree *, int);
static void qt_split(struct vgi_quadtree *);
static int qt_getindex(struct VG_Rect *, const struct VG_Rect *, int *);
static void ins_obj(struct vgi_quadtree *, const struct VG_Rect *, struct vgi_bitfld *);
static void qt_find(struct vgi_quadtree *, const struct VG_Rect *, struct vgi_bitfld *);
static void qt_findsub(struct vgi_quadtree *, const struct VG_Rect *, struct vgi_bitfld *);
static VG_BOOL qt_remove(struct vgi_quadtree *, int);
static int collside(const struct VG_Rect *, const struct VG_Rect *, const struct VG_Rect *);
static int collpercent(const struct VG_Rect *, const struct VG_Rect *);
static void qt_confirm(struct vgi_quadtree *, unsigned int, const struct VG_Coll *);


/* return bit-position of an instanceid, or -1 = not found */
static int
find_instance(struct vgi_quadtree *qdtr, unsigned int instanceid)
{
  int bpos;

  if (qdtr == NULL || instanceid == 0) { return -1; }

  for (bpos = bitfld_next(qdtr->bfall, 0); bpos >= 0; bpos = bitfld_next(qdtr->bfall, bpos + 1)) {
    if (qdtr->objs->obj[bpos].instanceid == instanceid) { break; }
  }

  return bpos;
} /* Ende find_instance */


/* get object-ID of instance-ID */
static const char *
get_objid(struct vgi_quadtree *qdtr, unsigned int instanceid, const char *objid)
{
  struct SML3_hashelem *he1;

  if (objid == NULL) {
    struct VG_Object *objp = vg4->object->instance_getobj(instanceid);
    if (objp != NULL) { objid = objp->objid; } else { objid = ""; }
  }

  he1 = SML3_hashset(qdtr->hobjid, objid, strlen(objid) + 1);

  return (const char *)SML3_hashelem_keyget(he1, NULL);
} 


/* insert rectangle-bit-position into quadtree */
static void
qt_insert(struct vgi_quadtree *qdtr, int bpos)
{
  int banz, qidx;

  if (qdtr == NULL || bpos < 0) { return; }

  if (qdtr->nodes[0] != NULL) {  /* sub-quadtrees available */
    qidx = qt_getindex(&qdtr->bounds, &qdtr->objs->obj[bpos].rect, NULL);
    if (qidx > 0) {  /* rectangle fits into sub-quadtree */
      qt_insert(qdtr->nodes[qidx - 1], bpos);
      return;
    }
  }

  /* set rectangle-bit-position into quadtree */
  bitfld_set(&qdtr->bf, bpos);

  /* insert evtl. sub-quadtrees to quadtree and distribute rectangle-bit-positions into them */
  banz = bitfld_anz(&qdtr->bf);
  if (banz > qdtr->max_rect && qdtr->level < qdtr->max_level) {
    if (qdtr->nodes[0] == NULL) { qt_split(qdtr); }
    if (qdtr->nodes[0] != NULL) {
      for (bpos = bitfld_next(&qdtr->bf, 0); bpos >= 0; bpos = bitfld_next(&qdtr->bf, bpos + 1)) {
        qidx = qt_getindex(&qdtr->bounds, &qdtr->objs->obj[bpos].rect, NULL);
        if (qidx > 0) {  /* rectangle fits into new sub-quadtree */
          bitfld_del(&qdtr->bf, bpos);
          qt_insert(qdtr->nodes[qidx - 1], bpos);
        }
      }
    }
  }
} /* Ende qt_insert */


/* insert 4 sub-quadtrees into quadtree */
static void
qt_split(struct vgi_quadtree *qdtr)
{
  int iqn, divw, divh, modw, modh;
  struct VG_Rect rect;

  if (qdtr == NULL) { return; }
  if (qdtr->nodes[0] != NULL) { return; }

  divw = qdtr->bounds.w / 2;
  modw = qdtr->bounds.w % 2;
  divh = qdtr->bounds.h / 2;
  modh = qdtr->bounds.h % 2;
  if (divw < 5 || divh < 5) { return; }

  for (iqn = 0; iqn < QUADTREE_QUAD; iqn++) {
    qdtr->nodes[iqn] = SML3_malloc(sizeof(*qdtr->nodes[0]));
  }

  rect.x = qdtr->bounds.x;
  rect.y = qdtr->bounds.y;
  rect.w = divw;
  rect.h = divh;
  quadtree_init(qdtr->nodes[0], qdtr->tag, &rect, qdtr->max_rect, qdtr->max_level);
  qdtr->nodes[0]->level = qdtr->level + 1;
  SML3_hashfree(&qdtr->nodes[0]->hrel); qdtr->nodes[0]->hrel = qdtr->hrel;
  SML3_hashfree(&qdtr->nodes[0]->hobjid); qdtr->nodes[0]->hobjid = qdtr->hobjid;
  free(qdtr->nodes[0]->bfall); qdtr->nodes[0]->bfall = qdtr->bfall;
  free(qdtr->nodes[0]->objs); qdtr->nodes[0]->objs = qdtr->objs;

  rect.x = qdtr->bounds.x + qdtr->bounds.w / 2;
  rect.y = qdtr->bounds.y;
  rect.w = divw + modw;
  rect.h = divh;
  quadtree_init(qdtr->nodes[1], qdtr->tag, &rect, qdtr->max_rect, qdtr->max_level);
  qdtr->nodes[1]->level = qdtr->level + 1;
  SML3_hashfree(&qdtr->nodes[1]->hrel); qdtr->nodes[1]->hrel = qdtr->hrel;
  SML3_hashfree(&qdtr->nodes[1]->hobjid); qdtr->nodes[1]->hobjid = qdtr->hobjid;
  free(qdtr->nodes[1]->bfall); qdtr->nodes[1]->bfall = qdtr->bfall;
  free(qdtr->nodes[1]->objs); qdtr->nodes[1]->objs = qdtr->objs;

  rect.x = qdtr->bounds.x;
  rect.y = qdtr->bounds.y + qdtr->bounds.h / 2;
  rect.w = divw;
  rect.h = divh + modh;
  quadtree_init(qdtr->nodes[2], qdtr->tag, &rect, qdtr->max_rect, qdtr->max_level);
  qdtr->nodes[2]->level = qdtr->level + 1;
  SML3_hashfree(&qdtr->nodes[2]->hrel); qdtr->nodes[2]->hrel = qdtr->hrel;
  SML3_hashfree(&qdtr->nodes[2]->hobjid); qdtr->nodes[2]->hobjid = qdtr->hobjid;
  free(qdtr->nodes[2]->bfall); qdtr->nodes[2]->bfall = qdtr->bfall;
  free(qdtr->nodes[2]->objs); qdtr->nodes[2]->objs = qdtr->objs;

  rect.x = qdtr->bounds.x + qdtr->bounds.w / 2;
  rect.y = qdtr->bounds.y + qdtr->bounds.h / 2;
  rect.w = divw + modw;
  rect.h = divh + modh;
  quadtree_init(qdtr->nodes[3], qdtr->tag, &rect, qdtr->max_rect, qdtr->max_level);
  qdtr->nodes[3]->level = qdtr->level + 1;
  SML3_hashfree(&qdtr->nodes[3]->hrel); qdtr->nodes[3]->hrel = qdtr->hrel;
  SML3_hashfree(&qdtr->nodes[3]->hobjid); qdtr->nodes[3]->hobjid = qdtr->hobjid;
  free(qdtr->nodes[3]->bfall); qdtr->nodes[3]->bfall = qdtr->bfall;
  free(qdtr->nodes[3]->objs); qdtr->nodes[3]->objs = qdtr->objs;
} /* Ende qt_split */


/* returns where rectangle is located:
 *      0 = in the quadtree
 * 1 to 4 = in the specified sub-quadtree
 */
static int
qt_getindex(struct VG_Rect *bounds, const struct VG_Rect *rect, int *midx)
{
  int qidx, mx, my, topbottom;

  if (bounds == NULL || rect == NULL) { return 0; }
  if (midx != NULL) { *midx = 0; }

  mx = bounds->x + bounds->w / 2;
  my = bounds->y + bounds->h / 2;

  topbottom = 0;
  if (rect->y + rect->h <= my) {
    topbottom = 1;
  } else if (rect->y >= my) {
    topbottom = 2;
  } else {
    topbottom = 3;
  }

  qidx = 0;
  if (rect->x + rect->w <= mx) {
    if (topbottom == 1) {
      qidx = 1;
    } else if (topbottom == 2) {
      qidx = 3;
    } else if (midx != NULL) {
      *midx = (1 | 4);
    }
  } else if (rect->x >= mx) {
    if (topbottom == 1) {
      qidx = 2;
    } else if (topbottom == 2) {
      qidx = 4;
    } else if (midx != NULL) {
      *midx = (2 | 8);
    }
  } else if (midx != NULL) {
    if (topbottom == 1) {
      *midx = (1 | 2);
    } else if (topbottom == 2) {
      *midx = (4 | 8);
    } else {
      *midx = (1 | 2 | 4 | 8);
    }
  }

  return qidx;
} /* Ende qt_getindex */


/* insert rectangle-bit-positions from quadtree into bit-field */
static void
ins_obj(struct vgi_quadtree *qdtr, const struct VG_Rect *rect, struct vgi_bitfld *rbf)
{
  int bpos;
  struct VG_Rect *robj;

  if (qdtr == NULL || rect == NULL || rbf == NULL) { return; }

  for (bpos = bitfld_next(&qdtr->bf, 0); bpos >= 0; bpos = bitfld_next(&qdtr->bf, bpos + 1)) {
    robj = &qdtr->objs->obj[bpos].rect;
    if (rect->x + rect->w - 1 < robj->x || rect->x > robj->x + robj->w - 1) { continue; }
    if (rect->y + rect->h - 1 < robj->y || rect->y > robj->y + robj->h - 1) { continue; }
    bitfld_set(rbf, bpos);
  }
} /* Ende ins_obj */


/* search for rectangle-bit-positions with possible collision to rectangle */
static void
qt_find(struct vgi_quadtree *qdtr, const struct VG_Rect *rect, struct vgi_bitfld *rbf)
{
  int qidx;

  if (qdtr == NULL || rect == NULL || rbf == NULL) { return; }

  qidx = qt_getindex(&qdtr->bounds, rect, NULL);
  if (qidx > 0 && qdtr->nodes[0] != NULL) {
    qt_find(qdtr->nodes[qidx - 1], rect, rbf);
    ins_obj(qdtr, rect, rbf);
  } else {
    qt_findsub(qdtr, rect, rbf);
  }
} /* Ende qt_find */


/* search for sub-rectangle-bit-positions with possible collision to rectangle */
static void
qt_findsub(struct vgi_quadtree *qdtr, const struct VG_Rect *rect, struct vgi_bitfld *rbf)
{
  if (qdtr == NULL || rect == NULL || rbf == NULL) { return; }

  if (qdtr->bounds.x + qdtr->bounds.w - 1 < rect->x || qdtr->bounds.x > rect->x + rect->w - 1) { return; }
  if (qdtr->bounds.y + qdtr->bounds.h - 1 < rect->y || qdtr->bounds.y > rect->y + rect->h - 1) { return; }

  if (qdtr->nodes[0] != NULL) {
    int iqn;
    for (iqn = 0; iqn < QUADTREE_QUAD; iqn++) {
      qt_findsub(qdtr->nodes[iqn], rect, rbf);
    }
  }

  ins_obj(qdtr, rect, rbf);
} /* Ende qt_findsub */


/* removes bit-position from relevant bit-field */
static VG_BOOL
qt_remove(struct vgi_quadtree *qdtr, int bpos)
{
  int i1;

  if (qdtr == NULL || bpos < 0) { return VG_FALSE; }

  if (bitfld_get(&qdtr->bf, bpos)) { bitfld_del(&qdtr->bf, bpos); return VG_TRUE; }

  for (i1 = 0; i1 < QUADTREE_QUAD; i1++) {
    if (qt_remove(qdtr->nodes[i1], bpos)) { return VG_TRUE; }
  }

  return VG_FALSE;
} /* Ende qt_remove */


/* return collision side(s) */
static int
collside(const struct VG_Rect *arect, const struct VG_Rect *rect1, const struct VG_Rect *rect2)
{
#define COLLAPP(A1,B1,A2,B2) \
  ((A1) + (B1) < (A2) + (B2) ? (A1) + (B1) : (A2) + (B2)) - ((A1) < (A2) ? (A2) : (A1))
  int side, cr, cl, ct, cb;

  if (rect1 == NULL || rect2 == NULL) { return 0; }

  if (rect1->x + rect1->w - 1 < rect2->x || rect1->x > rect2->x + rect2->w - 1) { return 0; }
  if (rect1->y + rect1->h - 1 < rect2->y || rect1->y > rect2->y + rect2->h - 1) { return 0; }

  cr = cl = ct = cb = 0;

  if (rect1->x + rect1->w - 1 >= rect2->x && rect1->x <= rect2->x) {
    cl = COLLAPP(rect1->y, rect1->h, rect2->y, rect2->h) + 1;
  }

  if (rect1->x <= rect2->x + rect2->w - 1 && rect1->x + rect1->w - 1 >= rect2->x + rect2->w - 1) {
    cr = COLLAPP(rect1->y, rect1->h, rect2->y, rect2->h) + 1;
  }

  if (rect1->y + rect1->h - 1 >= rect2->y && rect1->y <= rect2->y) {
    ct = COLLAPP(rect1->x, rect1->w, rect2->x, rect2->w) + 1;
  }

  if (rect1->y <= rect2->y + rect2->h - 1 && rect1->y + rect1->h - 1 >= rect2->y + rect2->h - 1) {
    cb = COLLAPP(rect1->x, rect1->w, rect2->x, rect2->w) + 1;
  }

  side = 0;
  if (cr >= cl && cr >= ct && cr >= cb) {
    if (arect == NULL || arect->x > rect1->x) { side |= VG_COLL_SIDE_RIGHT; }
  }
  if (cl >= cr && cl >= ct && cl >= cb) {
    if (arect == NULL || arect->x < rect1->x) { side |= VG_COLL_SIDE_LEFT; }
  }
  if (ct >= cr && ct >= cl && ct >= cb) {
    if (arect == NULL || arect->y < rect1->y) { side |= VG_COLL_SIDE_TOP; }
  }
  if (cb >= cr && cb >= ct && cb >= cl) {
    if (arect == NULL || arect->y > rect1->y) { side |= VG_COLL_SIDE_BOTTOM; }
  }

  return side;
} /* Ende collside */


/* return collision overlapping in percent */
static int
collpercent(const struct VG_Rect *rect1, const struct VG_Rect *rect2)
{
  int distmom, distmax, iw, ih, percent;

  if (rect1 == NULL || rect2 == NULL) { return 0; }

  if (rect1->x + rect1->w - 1 < rect2->x || rect1->x > rect2->x + rect2->w - 1) { return 0; }
  if (rect1->y + rect1->h - 1 < rect2->y || rect1->y > rect2->y + rect2->h - 1) { return 0; }

  /* get distance for x- and y-direction */
  iw = (rect1->x + rect1->w / 2) - (rect2->x + rect2->w / 2);
  if (iw < 0) { iw = -iw; }
  ih = (rect1->y + rect1->h / 2) - (rect2->y + rect2->h / 2);
  if (ih < 0) { ih = -ih; }

  distmom = iw;
  distmax = rect1->w / 2 + rect2->w / 2;
  if (distmax > 0) {
    percent = (distmax - distmom) * 100 / distmax;
  } else {
    percent = 100;
  }
  distmom = ih;
  distmax = rect1->h / 2 + rect2->h / 2;
  if (distmax > 0) {
    percent = percent * ((distmax - distmom) * 100 / distmax) / 100;
  }
  if (percent == 0) { percent = 1; }

  return percent;
} /* Ende collpercent */


/* confirm collision into quadtree */
static void
qt_confirm(struct vgi_quadtree *qdtr, unsigned int instanceid, const struct VG_Coll *collb)
{
  struct SML3_hash *hs1, *hs2;
  struct SML3_hashelem *he1;
  const char *objid;

  if (qdtr == NULL || instanceid == 0 || collb == NULL) { return; }

  hs1 = SML3_hashsubget(qdtr->hrel, (const void *)(size_t)instanceid, 0);
  assert(hs1 != NULL);

  hs2 = SML3_hashsubget(qdtr->hrel, (const void *)(size_t)collb->instanceid, 0);
  if (collb->type == VG_COLL_TYPE_ENTRY) {
    assert(hs2 != NULL);
    /* hrel[instanceid][collb->instanceid] = collb->instanceid.objid */
    he1 = SML3_hashset(hs1, (const void *)(size_t)collb->instanceid, 0);
    objid = get_objid(qdtr, collb->instanceid, NULL);
    SML3_hashelem_valset(he1, objid, strlen(objid) + 1);
    /* hrel[collb->instanceid][instanceid] = instanceid.objid */
    he1 = SML3_hashset(hs2, (const void *)(size_t)instanceid, 0);
    objid = get_objid(qdtr, instanceid, NULL);
    SML3_hashelem_valset(he1, objid, strlen(objid) + 1);
  } else if (collb->type == VG_COLL_TYPE_LEAVE) {
    he1 = SML3_hashget(hs1, (const void *)(size_t)collb->instanceid, 0);
    SML3_hashdel(he1);
    if (hs2 != NULL) {
      he1 = SML3_hashget(hs2, (const void *)(size_t)instanceid, 0);
      SML3_hashdel(he1);
    }
  }
} /* Ende qt_confirm */


/* quadtree_init:
 * initialize a quadtree
 * @param qdtr       quadtree to initialize
 * @param tag        arbitrary number to specify the quadtree
 * @param rect       rectangle, which shall be covered by the quadtree, or NULL = whole window
 * @param max_rect   maximal number of rectangles before sub-quadtrees are created, or 0 = default
 * @param max_level  maximal number of sub-quadtree-levels, or 0 = default
 */
void
quadtree_init(struct vgi_quadtree *qdtr, unsigned int tag, const struct VG_Rect *rect, int max_rect, int max_level)
{
  if (qdtr == NULL) { return; }

  memset(qdtr, 0, sizeof(*qdtr));

  if (rect != NULL) {
    qdtr->bounds = *rect;
  } else {
    int w1, h1;
    vg4->window->getsize(&w1, &h1);
    qdtr->bounds.x = qdtr->bounds.y = 0;
    qdtr->bounds.w = w1;
    qdtr->bounds.h = h1;
  }

  qdtr->hrel = SML3_hashnew(NULL, 0);
  qdtr->hobjid = SML3_hashnew(NULL, 0);
  qdtr->tag = tag;

  if (max_rect <= 0) { max_rect = STD_RECT; }
  if (max_level <= 0) { max_level = STD_LEVEL; }
  qdtr->max_rect = max_rect;
  qdtr->max_level = max_level;

  qdtr->bfall = SML3_calloc(1, sizeof(*qdtr->bfall));
  qdtr->objs = SML3_calloc(1, sizeof(*qdtr->objs));
} /* quadtree_init */


/* quadtree_dest:
 * free quadtree
 * @param qdtr  quadtree
 */
void
quadtree_dest(struct vgi_quadtree *qdtr)
{
  if (qdtr == NULL) { return; }

  if (qdtr->nodes[0] != NULL) {  /* sub-quadtrees available */
    int iqn;
    for (iqn = 0; iqn < QUADTREE_QUAD; iqn++) {
      quadtree_dest(qdtr->nodes[iqn]);
      free(qdtr->nodes[iqn]);
    }
  }

  if (qdtr->bf.bfeld != NULL) { free(qdtr->bf.bfeld); }

  if (qdtr->level == 0) {
    SML3_hashfree(&qdtr->hrel);
    SML3_hashfree(&qdtr->hobjid);
    if (qdtr->bfall != NULL) {
      if (qdtr->bfall->bfeld != NULL) { free(qdtr->bfall->bfeld); }
      free(qdtr->bfall);
    }
    if (qdtr->objs != NULL) {
      if (qdtr->objs->obj != NULL) { free(qdtr->objs->obj); }
      free(qdtr->objs);
    }
  }

  memset(qdtr, 0, sizeof(*qdtr));
} /* Ende quadtree_dest */


/* quadtree_clear:
 * clear quadtree
 * @param qdtr  quadtree
 */
void
quadtree_clear(struct vgi_quadtree *qdtr)
{
  struct VG_Rect bounds;
  int max_rect, max_level;
  unsigned int tag;

  if (qdtr == NULL) { return; }

  tag = qdtr->tag;
  max_rect = qdtr->max_rect;
  max_level = qdtr->max_level;
  bounds = qdtr->bounds;

  quadtree_dest(qdtr);
  quadtree_init(qdtr, tag, &bounds, max_rect, max_level);
} /* Ende quadtree_clear */


/* quadtree_insert:
 * insert object-instance into quadtree
 * @param qdtr        quadtree
 * @param instanceid  instance-ID
 * @param rect        position of object-instance
 * @param percent     percent to use of rect from the center
 */
void
quadtree_insert(struct vgi_quadtree *qdtr, unsigned int instanceid, const struct VG_Rect *rect, int percent)
{
  int bpos;
  struct vgi_qt_obj qtobj;

  if (qdtr == NULL || instanceid == 0 || rect == NULL) { return; }

  bpos = find_instance(qdtr, instanceid);
  if (bpos >= 0) {  /* update instead of insert */
    struct vgi_qt_obj *qtobjp;
    qt_remove(qdtr, bpos);
    qtobjp = &qdtr->objs->obj[bpos];
    qtobjp->rect.w = rect->w * qtobjp->percent / 100;
    qtobjp->rect.h = rect->h * qtobjp->percent / 100;
    qtobjp->rect.x = rect->x + (rect->w - qtobjp->rect.w) / 2;
    qtobjp->rect.y = rect->y + (rect->h - qtobjp->rect.h) / 2;
    qt_insert(qdtr, bpos);
    return;
  }

  memset(&qtobj, 0, sizeof(qtobj));

  qtobj.instanceid = instanceid;
  qtobj.percent = percent;

  qtobj.rect.w = rect->w * percent / 100;
  qtobj.rect.h = rect->h * percent / 100;
  if (qtobj.rect.w <= 0 || qtobj.rect.h <= 0) { return; }

  qtobj.rect.x = rect->x + (rect->w - qtobj.rect.w) / 2;
  qtobj.rect.y = rect->y + (rect->h - qtobj.rect.h) / 2;

  /* set rectangle into quadtree */
  bpos = bitfld_unused(qdtr->bfall, 0);
  if (bpos < 0 || bpos >= qdtr->objs->objanz) {
    qdtr->objs->objanz++;
    if (qdtr->objs->objanz == 1) {
      qdtr->objs->obj = SML3_malloc(sizeof(*qdtr->objs->obj));
    } else {
      qdtr->objs->obj = SML3_realloc(qdtr->objs->obj, sizeof(*qdtr->objs->obj) * qdtr->objs->objanz);
    }
    bpos = qdtr->objs->objanz - 1;
  }
  memcpy(&qdtr->objs->obj[bpos], &qtobj, sizeof(*qdtr->objs->obj));

  SML3_hashsubset(qdtr->hrel, (const void *)(size_t)qtobj.instanceid, 0);

  /* set and insert rectangle-bit-position */
  bitfld_set(qdtr->bfall, bpos);
  qt_insert(qdtr, bpos);
} /* Ende quadtree_insert */


/* quadtree_remove:
 * remove object-instance from the quadtree
 * @param qdtr        quadtree
 * @param instanceid  instance-ID
 */
void
quadtree_remove(struct vgi_quadtree *qdtr, unsigned int instanceid)
{
  int bpos;

  if (qdtr == NULL || instanceid == 0) { return; }

  bpos = find_instance(qdtr, instanceid);
  if (bpos >= 0) {
    /* remove instanceid, but keep collision-relations for instanceid to let them receive a VG_COLL_TYPE_LEAVE */
    { struct SML3_hash *hs1;
      struct SML3_hashelem *he1;
      hs1 = SML3_hashsubget(qdtr->hrel, (const void *)(size_t)instanceid, 0);
      assert(hs1 != NULL);
      while ((he1 = SML3_hashlist(hs1, NULL, 0)) != NULL) { SML3_hashdel(he1); }
    }
    qt_remove(qdtr, bpos);
    bitfld_del(qdtr->bfall, bpos);
  }
} /* Ende quadtree_remove */


/* quadtree_setpos:
 * set position of an object-instance and get collisions
 * @param qdtr        quadtree
 * @param instanceid  instance-ID
 * @param nrect       position of object-instance
 * @param collp       for returning allocated array with collisions (must be freed with free()), may be NULL
 * @return  number of collisions in collp, even if collp is NULL
 */
int
quadtree_setpos(struct vgi_quadtree *qdtr, unsigned int instanceid, const struct VG_Rect *nrect, struct VG_Coll **collp)
{
  struct vgi_bitfld rbf;
  int banz, bpos;
  struct VG_Rect rect, arect;
  struct VG_Coll *collb;

  if (collp != NULL) { *collp = NULL; }
  if (qdtr == NULL || instanceid == 0 || nrect == NULL) { return 0; }

  bpos = find_instance(qdtr, instanceid);
  if (bpos < 0) { return 0; }

  { struct vgi_qt_obj *qtobjp;
    qt_remove(qdtr, bpos);
    qtobjp = &qdtr->objs->obj[bpos];
    arect = qtobjp->rect;
    qtobjp->rect.w = nrect->w * qtobjp->percent / 100;
    qtobjp->rect.h = nrect->h * qtobjp->percent / 100;
    qtobjp->rect.x = nrect->x + (nrect->w - qtobjp->rect.w) / 2;
    qtobjp->rect.y = nrect->y + (nrect->h - qtobjp->rect.h) / 2;
    qt_insert(qdtr, bpos);
  }

  collb = NULL;
  rect = qdtr->objs->obj[bpos].rect;
  memset(&rbf, 0, sizeof(rbf));
  qt_find(qdtr, &rect, &rbf);

  banz = bitfld_anz(&rbf);
  if (banz > 0) {
    collb = SML3_calloc(banz, sizeof(*collb));
    banz = 0;
    for (bpos = bitfld_next(&rbf, 0); bpos >= 0; bpos = bitfld_next(&rbf, bpos + 1)) {
      if (qdtr->objs->obj[bpos].instanceid == instanceid) { continue; }
      /* new (or repeated) collision */
      collb[banz].type = VG_COLL_TYPE_ENTRY;
      collb[banz].tag = qdtr->tag;
      collb[banz].instanceid = qdtr->objs->obj[bpos].instanceid;
      collb[banz].objid = get_objid(qdtr, collb[banz].instanceid, NULL);
      collb[banz].side = collside(&arect, &rect, &qdtr->objs->obj[bpos].rect);
      collb[banz].percent = collpercent(&rect, &qdtr->objs->obj[bpos].rect);
      banz++;
    }
  }
  if (rbf.bfeld != NULL) { free(rbf.bfeld); }

  /* check for repeated and leaving collisions */
  { struct SML3_hash *hs1;
    struct SML3_hashelem *he1;
    unsigned int instanceid2;
    const char *objid2;
    hs1 = SML3_hashsubget(qdtr->hrel, (const void *)(size_t)instanceid, 0);
    assert(hs1 != NULL);
    for (he1 = SML3_hashlist(hs1, NULL, 0); he1 != NULL; he1 = SML3_hashlist(hs1, he1, 0)) {
      instanceid2 = (unsigned int)(size_t)SML3_hashelem_keyget(he1, NULL);
      objid2 = (const char *)SML3_hashelem_valget(he1, NULL);
      for (bpos = 0; bpos < banz; bpos++) {
        if (collb[bpos].instanceid == instanceid2) {  /* collision repeated */
          collb[bpos].type = VG_COLL_TYPE_REPEAT;
          break;
        }
      }
      if (bpos == banz) {  /* leaving collision */
        banz++;
        if (collb == NULL) {
          collb = SML3_malloc(banz * sizeof(*collb));
        } else {
          collb = SML3_realloc(collb, banz * sizeof(*collb));
        }
        bpos = banz - 1;
        memset(&collb[bpos], 0, sizeof(*collb));
        collb[bpos].type = VG_COLL_TYPE_LEAVE;
        collb[bpos].tag = qdtr->tag;
        collb[bpos].instanceid = instanceid2;
        collb[bpos].objid = get_objid(qdtr, collb[bpos].instanceid, objid2);
        collb[bpos].side = 0;
        collb[bpos].percent = 0;
      }
    }
  }

  /* confirm collisions into quadtree */
  for (bpos = 0; bpos < banz; bpos++) {
    qt_confirm(qdtr, instanceid, &collb[bpos]);
  }

  if (collp != NULL) { *collp = collb; } else { free(collb); }

  return banz;
} /* Ende quadtree_setpos */


/* quadtree_mark:
 * mark object-instance-IDs in the quadtree by drawing a rectangle around them
 * @param qdtr   quadtree
 * @param imgp   image or NULL = window
 * @param color  rectangle color (VG_COLOR_*)
 * @param objid  restriction to a specific object-ID, or NULL = all
 */
void
quadtree_mark(struct vgi_quadtree *qdtr, struct VG_Image *imgp, int color, const char *objid)
{
  int bpos;
  const char *oid;
  struct VG_Rect rect;
  struct VG_Position posi;

  if (qdtr == NULL) { return; }
  if (objid != NULL && *objid == '\0') { objid = NULL; }

  for (bpos = bitfld_next(qdtr->bfall, 0); bpos >= 0; bpos = bitfld_next(qdtr->bfall, bpos + 1)) {
    oid = get_objid(qdtr, qdtr->objs->obj[bpos].instanceid, NULL);
    if (objid == NULL || strcmp(objid, oid) == 0) { 
      if (imgp != NULL) {
        vg4->image->draw_rect(imgp, &qdtr->objs->obj[bpos].rect, color, VG_FALSE);
      } else {
        rect = qdtr->objs->obj[bpos].rect;
        posi.pos = VG_POS_UPPER_LEFT;
        posi.x = qdtr->objs->obj[bpos].rect.x;
        posi.y = qdtr->objs->obj[bpos].rect.y;
        posi = vg4->misc->bgposition_getobj(posi);
        rect.x = posi.x;
        rect.y = posi.y;
        vg4->window->draw_rect(&rect, color, VG_FALSE);
      }
    }
  }
} /* Ende quadtree_mark */


/* quadtree_dump:
 * dump quadtree
 * @param outfp  filepointer to dump to, or NULL = stdout
 * @param qdtr   quadtree
 */
void
quadtree_dump(FILE *outfp, struct vgi_quadtree *qdtr)
{
  static int qpos = 0;
  char einruck[512];
  int i1, bpos, qidx, midx;
  struct vgi_qt_obj *qtobjp;
  const char *objid;

  if (qdtr == NULL) { return; }
  if (outfp == NULL) { outfp = stdout; }

  i1 = (int)sizeof(einruck) / 2 - 1;
  if (qdtr->level < i1) { i1 = qdtr->level; }
  i1 *= 2;
  memset(einruck, ' ', i1); einruck[i1] = '\0';

  if (qdtr->level == 0) {
    fprintf(outfp, "%s- Quadtree: tag=%u\n", einruck, qdtr->tag);
  } else {
    fprintf(outfp, "%s- Sub-Quadtree[%d]: level=%d\n", einruck, qpos, qdtr->level);
  }

  fprintf(outfp, "%s  - covered-by=[%d+%d,%d+%d]\n", einruck,
          qdtr->bounds.x, qdtr->bounds.w,
          qdtr->bounds.y, qdtr->bounds.h);
  fprintf(outfp, "%s  - contains %d object-instances of %d total\n", einruck,
          bitfld_anz(&qdtr->bf), bitfld_anz(qdtr->bfall));

  fprintf(outfp, "%s  - objects\n", einruck);
  for (bpos = bitfld_next(&qdtr->bf, 0); bpos >= 0; bpos = bitfld_next(&qdtr->bf, bpos + 1)) {
    qtobjp = &qdtr->objs->obj[bpos];
    objid = get_objid(qdtr, qtobjp->instanceid, NULL);
    if (*objid == '\0') { objid = "?"; }
    fprintf(outfp, "%s    - %s[%u]\n", einruck, objid, qtobjp->instanceid);
    fprintf(outfp, "%s      - rect=[%d+%d,%d+%d], percent=%d\n", einruck,
            qtobjp->rect.x, qtobjp->rect.w,
            qtobjp->rect.y, qtobjp->rect.h,
            qtobjp->percent);
    fprintf(outfp, "%s      - located at sub-quadtree-positions=", einruck);
    qidx = qt_getindex(&qdtr->bounds, &qdtr->objs->obj[bpos].rect, &midx);
    if (qidx > 0) {
      if (qdtr->nodes[0] == NULL) { qidx = 0; }
      if (qidx == 0) { fprintf(outfp, "none\n"); }
      if (qidx == 1) { fprintf(outfp, "upper-left\n"); }
      if (qidx == 2) { fprintf(outfp, "upper-right\n"); }
      if (qidx == 3) { fprintf(outfp, "lower-left\n"); }
      if (qidx == 4) { fprintf(outfp, "lower-right\n"); }
    } else {
      int nix = 1;
      if (midx & 1) { fprintf(outfp, "%supper-left", (nix ? "" : ",")); nix = 0; }
      if (midx & 2) { fprintf(outfp, "%supper-right", (nix ? "" : ",")); nix = 0; }
      if (midx & 4) { fprintf(outfp, "%slower-left", (nix ? "" : ",")); nix = 0; }
      if (midx & 8) { fprintf(outfp, "%slower-right", (nix ? "" : ",")); nix = 0; }
      fprintf(outfp, "\n");
    }
    fprintf(outfp, "%s      - collisions-with=", einruck);
    { struct SML3_hash *hs1;
      struct SML3_hashelem *he1;
      unsigned int instanceid2;
      int nix = 1;
      hs1 = SML3_hashsubget(qdtr->hrel, (const void *)(size_t)qtobjp->instanceid, 0);
      assert(hs1 != NULL);
      for (he1 = SML3_hashlist(hs1, NULL, 0); he1 != NULL; he1 = SML3_hashlist(hs1, he1, 0)) {
        instanceid2 = (unsigned int)(size_t)SML3_hashelem_keyget(he1, NULL);
        objid = (const char *)SML3_hashelem_valget(he1, NULL);
        if (*objid == '\0') { objid = "?"; }
        fprintf(outfp, "%s%s[%u]", (nix ? "" : ","), objid, instanceid2);
        nix = 0;
      }
    }
    fprintf(outfp, "\n");
  }

  for (i1 = 0; i1 < QUADTREE_QUAD; i1++) {
    qpos = i1 + 1;
    quadtree_dump(outfp, qdtr->nodes[i1]);
    qpos = 0;
  }
} /* Ende quadtree_dump */
