LCOV - code coverage report
Current view: top level - shumate/vector - shumate-vector-collision.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 0 289 0.0 %
Date: 2024-05-11 21:41:31 Functions: 0 21 0.0 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 0 198 0.0 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * Copyright (C) 2022 James Westman <james@jwestman.net>
       3                 :            :  *
       4                 :            :  * This library is free software; you can redistribute it and/or
       5                 :            :  * modify it under the terms of the GNU Lesser General Public
       6                 :            :  * License as published by the Free Software Foundation; either
       7                 :            :  * version 2.1 of the License, or (at your option) any later version.
       8                 :            :  *
       9                 :            :  * This library is distributed in the hope that it will be useful,
      10                 :            :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11                 :            :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12                 :            :  * Lesser General Public License for more details.
      13                 :            :  *
      14                 :            :  * You should have received a copy of the GNU Lesser General Public
      15                 :            :  * License along with this library; if not, see <https://www.gnu.org/licenses/>.
      16                 :            :  */
      17                 :            : 
      18                 :            : /* This is a simple implementation of an R-tree, used to detect overlapping
      19                 :            :  * markers. An R-tree is a spatial data structure which stores nodes by their
      20                 :            :  * bounding boxes. There are lots of fancy heuristics for R-trees to efficiently
      21                 :            :  * insert new nodes into the tree, but this implementation uses a fixed
      22                 :            :  * structure for simplicity.
      23                 :            :  *
      24                 :            :  * See <https://en.wikipedia.org/wiki/R-tree>. */
      25                 :            : 
      26                 :            : #include <math.h>
      27                 :            : 
      28                 :            : #include "shumate-vector-collision-private.h"
      29                 :            : 
      30                 :            : #define NODES 4
      31                 :            : /* Doesn't need to match the actual tile size */
      32                 :            : #define BUCKET_SIZE 256
      33                 :            : 
      34                 :            : #define LEN_SQ(x, y) (x*x + y*y)
      35                 :            : #define MAX4(a, b, c, d) (MAX (MAX (a, b), MAX (c, d)))
      36                 :            : #define MIN4(a, b, c, d) (MIN (MIN (a, b), MIN (c, d)))
      37                 :            : 
      38                 :            : #define EMPTY_BOX ((Box){0, 0, 0, 0, 0, 0, 0})
      39                 :            : 
      40                 :            : typedef struct {
      41                 :            :   gpointer tag;
      42                 :            :   double x;
      43                 :            :   double y;
      44                 :            :   double xextent;
      45                 :            :   double yextent;
      46                 :            :   double rotation;
      47                 :            :   double aaxextent;
      48                 :            :   double aayextent;
      49                 :            :   uint8_t overlap_never : 1;
      50                 :            : } Box;
      51                 :            : 
      52                 :            : typedef struct {
      53                 :            :   GArray *boxes;
      54                 :            :   Box bbox;
      55                 :            : } RTreeCol;
      56                 :            : 
      57                 :            : typedef struct {
      58                 :            :   RTreeCol cols[NODES];
      59                 :            :   Box bbox;
      60                 :            : } RTreeRow;
      61                 :            : 
      62                 :            : typedef struct {
      63                 :            :   RTreeRow rows[NODES];
      64                 :            :   Box bbox;
      65                 :            :   int n_boxes;
      66                 :            : } RTreeBucketCol;
      67                 :            : 
      68                 :            : typedef struct {
      69                 :            :   GHashTable *bucket_cols;
      70                 :            :   GPtrArray *bucket_cols_array;
      71                 :            :   Box bbox;
      72                 :            : } RTreeBucketRow;
      73                 :            : 
      74                 :            : 
      75                 :            : static RTreeBucketCol *
      76                 :          0 : bucket_col_new ()
      77                 :            : {
      78                 :          0 :   RTreeBucketCol *bucket_col = g_new0 (RTreeBucketCol, 1);
      79                 :          0 :   bucket_col->bbox = EMPTY_BOX;
      80                 :          0 :   return bucket_col;
      81                 :            : }
      82                 :            : 
      83                 :            : 
      84                 :            : static void
      85                 :          0 : bucket_col_free (RTreeBucketCol *tile_col)
      86                 :            : {
      87         [ #  # ]:          0 :   for (int x = 0; x < NODES; x ++)
      88         [ #  # ]:          0 :     for (int y = 0; y < NODES; y ++)
      89         [ #  # ]:          0 :       g_clear_pointer (&tile_col->rows[y].cols[x].boxes, g_array_unref);
      90                 :            : 
      91                 :          0 :   g_free (tile_col);
      92                 :          0 : }
      93                 :            : 
      94                 :            : 
      95                 :            : static RTreeBucketRow *
      96                 :          0 : bucket_row_new ()
      97                 :            : {
      98                 :          0 :   RTreeBucketRow *bucket_row = g_new0 (RTreeBucketRow, 1);
      99                 :          0 :   bucket_row->bucket_cols = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) bucket_col_free);
     100                 :          0 :   bucket_row->bucket_cols_array = g_ptr_array_new ();
     101                 :          0 :   bucket_row->bbox = EMPTY_BOX;
     102                 :          0 :   return bucket_row;
     103                 :            : }
     104                 :            : 
     105                 :            : 
     106                 :            : static void
     107                 :          0 : bucket_row_free (RTreeBucketRow *bucket_row)
     108                 :            : {
     109                 :          0 :   g_hash_table_unref (bucket_row->bucket_cols);
     110                 :          0 :   g_ptr_array_unref (bucket_row->bucket_cols_array);
     111                 :          0 :   g_free (bucket_row);
     112                 :          0 : }
     113                 :            : 
     114                 :            : 
     115                 :            : ShumateVectorCollision *
     116                 :          0 : shumate_vector_collision_new ()
     117                 :            : {
     118                 :          0 :   ShumateVectorCollision *self = g_new0 (ShumateVectorCollision, 1);
     119                 :            : 
     120                 :          0 :   self->bucket_rows = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) bucket_row_free);
     121                 :          0 :   self->bucket_rows_array = g_ptr_array_new ();
     122                 :          0 :   self->pending_boxes = g_array_new (FALSE, FALSE, sizeof (Box));
     123                 :            : 
     124                 :          0 :   return self;
     125                 :            : }
     126                 :            : 
     127                 :            : 
     128                 :            : void
     129                 :          0 : shumate_vector_collision_free (ShumateVectorCollision *self)
     130                 :            : {
     131                 :          0 :   g_hash_table_unref (self->bucket_rows);
     132                 :          0 :   g_array_unref (self->pending_boxes);
     133                 :          0 :   g_ptr_array_unref (self->bucket_rows_array);
     134                 :          0 :   g_free (self);
     135                 :          0 : }
     136                 :            : 
     137                 :            : 
     138                 :            : static double
     139                 :          0 : dot (ShumateVectorPoint *a,
     140                 :            :      ShumateVectorPoint *b)
     141                 :            : {
     142                 :          0 :   return a->x * b->x + a->y * b->y;
     143                 :            : }
     144                 :            : 
     145                 :            : 
     146                 :            : static double
     147                 :          0 : project (ShumateVectorPoint *point,
     148                 :            :          ShumateVectorPoint *axis)
     149                 :            : {
     150                 :          0 :   ShumateVectorPoint tmp;
     151                 :          0 :   double s = dot (point, axis) / LEN_SQ (axis->x, axis->y);
     152                 :          0 :   tmp = *axis;
     153                 :          0 :   tmp.x *= s;
     154                 :          0 :   tmp.y *= s;
     155                 :          0 :   return dot (&tmp, axis);
     156                 :            : }
     157                 :            : 
     158                 :            : 
     159                 :            : static ShumateVectorPoint
     160                 :          0 : corner (double x,
     161                 :            :         double y,
     162                 :            :         double xextent,
     163                 :            :         double yextent,
     164                 :            :         double rot_cos,
     165                 :            :         double rot_sin)
     166                 :            : {
     167                 :          0 :   return (ShumateVectorPoint) {
     168                 :          0 :     .x = xextent * rot_cos - yextent * rot_sin + x,
     169                 :          0 :     .y = xextent * rot_sin + yextent * rot_cos + y,
     170                 :            :   };
     171                 :            : }
     172                 :            : 
     173                 :            : 
     174                 :            : static gboolean
     175                 :          0 : rects_intersect_aa (Box *a,
     176                 :            :                     Box *b)
     177                 :            : {
     178                 :            :   /* Test whether the boxes' axis-aligned bounding boxes intersect. */
     179         [ #  # ]:          0 :   return !((a->x + a->aaxextent < b->x - b->aaxextent)
     180         [ #  # ]:          0 :           || (b->x + b->aaxextent < a->x - a->aaxextent)
     181         [ #  # ]:          0 :           || (a->y + a->aayextent < b->y - b->aayextent)
     182         [ #  # ]:          0 :           || (b->y + b->aayextent < a->y - a->aayextent));
     183                 :            : }
     184                 :            : 
     185                 :            : 
     186                 :            : static gboolean
     187                 :          0 : rects_intersect (Box *a,
     188                 :            :                  Box *b)
     189                 :            : {
     190                 :          0 :   double cos_a, sin_a, cos_b, sin_b;
     191                 :          0 :   ShumateVectorPoint axes[4], corners_a[4], corners_b[4];
     192                 :            : 
     193         [ #  # ]:          0 :   if (!rects_intersect_aa (a, b))
     194                 :            :     return FALSE;
     195                 :            : 
     196   [ #  #  #  # ]:          0 :   if (a->rotation == 0 && b->rotation == 0)
     197                 :            :     {
     198                 :            :       /* If both boxes' rotation is 0, then rects_intersect_aa is equivalent
     199                 :            :          to rects_intersect and would have returned FALSE already */
     200                 :            :       return TRUE;
     201                 :            :     }
     202                 :            : 
     203                 :            :   /* See <https://www.gamedev.net/articles/programming/general-and-gameplay-programming/2d-rotated-rectangle-collision-r2604/> */
     204                 :            : 
     205                 :          0 :   cos_a = cosf (a->rotation);
     206                 :          0 :   sin_a = sinf (a->rotation);
     207                 :          0 :   cos_b = cosf (b->rotation);
     208                 :          0 :   sin_b = sinf (b->rotation);
     209                 :            : 
     210                 :            :   /* Calculate the four axes of the two rectangles */
     211                 :          0 :   axes[0] = (ShumateVectorPoint) { cos_a, sin_a };
     212                 :          0 :   axes[1] = (ShumateVectorPoint) { -sin_a, cos_a };
     213                 :          0 :   axes[2] = (ShumateVectorPoint) { cos_b, sin_b };
     214                 :          0 :   axes[3] = (ShumateVectorPoint) { -sin_b, cos_b };
     215                 :            : 
     216                 :          0 :   corners_a[0] = corner (a->x, a->y, a->xextent, a->yextent, cos_a, sin_a);
     217                 :          0 :   corners_a[1] = corner (a->x, a->y, -a->xextent, a->yextent, cos_a, sin_a);
     218                 :          0 :   corners_a[2] = corner (a->x, a->y, a->xextent, -a->yextent, cos_a, sin_a);
     219                 :          0 :   corners_a[3] = corner (a->x, a->y, -a->xextent, -a->yextent, cos_a, sin_a);
     220                 :            : 
     221                 :          0 :   corners_b[0] = corner (b->x, b->y, b->xextent, b->yextent, cos_b, sin_b);
     222                 :          0 :   corners_b[1] = corner (b->x, b->y, -b->xextent, b->yextent, cos_b, sin_b);
     223                 :          0 :   corners_b[2] = corner (b->x, b->y, b->xextent, -b->yextent, cos_b, sin_b);
     224                 :          0 :   corners_b[3] = corner (b->x, b->y, -b->xextent, -b->yextent, cos_b, sin_b);
     225                 :            : 
     226         [ #  # ]:          0 :   for (int i = 0; i < 4; i ++)
     227                 :            :     {
     228                 :          0 :       ShumateVectorPoint *axis = &axes[i];
     229                 :            : 
     230                 :          0 :       double proj_a[4], proj_b[4];
     231                 :            : 
     232                 :            :       /* Project the corners of the rectangles onto the axis */
     233         [ #  # ]:          0 :       for (int j = 0; j < 4; j ++)
     234                 :            :         {
     235                 :          0 :           proj_a[j] = project (&corners_a[j], axis);
     236                 :          0 :           proj_b[j] = project (&corners_b[j], axis);
     237                 :            :         }
     238                 :            : 
     239                 :            :       /* If the projected points don't overlap, the rectangles don't overlap
     240                 :            :        * (i.e. either every item in proj_a is greater than or is less than every
     241                 :            :        * item in proj_b). */
     242   [ #  #  #  #  :          0 :       double min_a = MIN4 (proj_a[0], proj_a[1], proj_a[2], proj_a[3]);
                   #  # ]
     243   [ #  #  #  #  :          0 :       double max_a = MAX4 (proj_a[0], proj_a[1], proj_a[2], proj_a[3]);
                   #  # ]
     244   [ #  #  #  #  :          0 :       double min_b = MIN4 (proj_b[0], proj_b[1], proj_b[2], proj_b[3]);
                   #  # ]
     245   [ #  #  #  #  :          0 :       double max_b = MAX4 (proj_b[0], proj_b[1], proj_b[2], proj_b[3]);
                   #  # ]
     246                 :            : 
     247   [ #  #  #  # ]:          0 :       if (min_a >= max_b || min_b >= max_a)
     248                 :          0 :         return FALSE;
     249                 :            :     }
     250                 :            : 
     251                 :            :   return TRUE;
     252                 :            : }
     253                 :            : 
     254                 :            : 
     255                 :            : static void
     256                 :          0 : expand_rect (Box       *a,
     257                 :            :              const Box *b)
     258                 :            : {
     259   [ #  #  #  #  :          0 :   if (a->x == 0 && a->y == 0 && a->xextent == 0 && a->yextent == 0)
             #  #  #  # ]
     260                 :          0 :     *a = *b;
     261                 :            :   else
     262                 :            :     {
     263         [ #  # ]:          0 :       double left = MIN (a->x - a->aaxextent, b->x - b->aaxextent);
     264         [ #  # ]:          0 :       double right = MAX (a->x + a->aaxextent, b->x + b->aaxextent);
     265         [ #  # ]:          0 :       double top = MIN (a->y - a->aayextent, b->y - b->aayextent);
     266         [ #  # ]:          0 :       double bottom = MAX (a->y + a->aayextent, b->y + b->aayextent);
     267                 :          0 :       a->x = (left + right) / 2.0;
     268                 :          0 :       a->y = (top + bottom) / 2.0;
     269                 :          0 :       a->xextent = (right - left) / 2.0;
     270                 :          0 :       a->yextent = (bottom - top) / 2.0;
     271                 :          0 :       a->aaxextent  = a->xextent;
     272                 :          0 :       a->aayextent  = a->yextent;
     273                 :            :     }
     274                 :          0 : }
     275                 :            : 
     276                 :            : 
     277                 :            : static void
     278                 :          0 : axis_align (Box *box)
     279                 :            : {
     280         [ #  # ]:          0 :   if (box->rotation == 0)
     281                 :            :     {
     282                 :          0 :       box->aaxextent = box->xextent;
     283                 :          0 :       box->aayextent = box->yextent;
     284                 :            :     }
     285                 :            :   else
     286                 :            :     {
     287   [ #  #  #  #  :          0 :       box->aaxextent = MAX (
                   #  # ]
     288                 :            :         ABS (cos (box->rotation) *  box->xextent - sin (box->rotation) * box->yextent),
     289                 :            :         ABS (cos (box->rotation) * -box->xextent - sin (box->rotation) * box->yextent)
     290                 :            :       );
     291   [ #  #  #  #  :          0 :       box->aayextent = MAX (
                   #  # ]
     292                 :            :         ABS (sin (box->rotation) *  box->xextent + cos (box->rotation) * box->yextent),
     293                 :            :         ABS (sin (box->rotation) * -box->xextent + cos (box->rotation) * box->yextent)
     294                 :            :       );
     295                 :            :     }
     296                 :          0 : }
     297                 :            : 
     298                 :            : 
     299                 :            : static int
     300                 :          0 : positive_mod (int i, int n)
     301                 :            : {
     302                 :          0 :   return (i % n + n) % n;
     303                 :            : }
     304                 :            : 
     305                 :            : 
     306                 :            : static int
     307                 :          0 : row_for_position (double coordinate)
     308                 :            : {
     309                 :          0 :   return positive_mod (coordinate, BUCKET_SIZE) * NODES / BUCKET_SIZE;
     310                 :            : }
     311                 :            : 
     312                 :            : 
     313                 :            : static gboolean
     314                 :          0 : detect_collision (ShumateVectorCollision *self,
     315                 :            :                   Box                    *bbox)
     316                 :            : {
     317         [ #  # ]:          0 :   for (int i = 0; i < self->bucket_rows_array->len; i ++)
     318                 :            :     {
     319                 :          0 :       RTreeBucketRow *bucket_row = g_ptr_array_index (self->bucket_rows_array, i);
     320                 :            : 
     321         [ #  # ]:          0 :       if (!rects_intersect_aa (bbox, &bucket_row->bbox))
     322                 :          0 :         continue;
     323                 :            : 
     324         [ #  # ]:          0 :       for (int j = 0; j < bucket_row->bucket_cols_array->len; j ++)
     325                 :            :         {
     326                 :          0 :           RTreeBucketCol *bucket_col = g_ptr_array_index (bucket_row->bucket_cols_array, j);
     327                 :            : 
     328         [ #  # ]:          0 :           if (bucket_col->n_boxes == 0)
     329                 :          0 :             continue;
     330                 :            : 
     331         [ #  # ]:          0 :           if (!rects_intersect_aa (bbox, &bucket_col->bbox))
     332                 :          0 :             continue;
     333                 :            : 
     334         [ #  # ]:          0 :           for (int y = 0; y < NODES; y ++)
     335                 :            :             {
     336                 :          0 :               RTreeRow *row = &bucket_col->rows[y];
     337                 :            : 
     338         [ #  # ]:          0 :               if (!rects_intersect_aa (bbox, &row->bbox))
     339                 :          0 :                 continue;
     340                 :            : 
     341         [ #  # ]:          0 :               for (int x = 0; x < NODES; x ++)
     342                 :            :                 {
     343                 :          0 :                   RTreeCol *col = &row->cols[x];
     344                 :            : 
     345   [ #  #  #  # ]:          0 :                   if (col->boxes == NULL || !rects_intersect_aa (bbox, &col->bbox))
     346                 :          0 :                     continue;
     347                 :            : 
     348         [ #  # ]:          0 :                   for (int i = 0; i < col->boxes->len; i ++)
     349                 :            :                     {
     350                 :          0 :                       Box *b = &((Box*)col->boxes->data)[i];
     351                 :            : 
     352   [ #  #  #  # ]:          0 :                       if (b->overlap_never || bbox->overlap_never)
     353                 :            :                         {
     354         [ #  # ]:          0 :                           if (rects_intersect (bbox, b))
     355                 :            :                             return TRUE;
     356                 :            :                         }
     357                 :            :                     }
     358                 :            :                 }
     359                 :            :             }
     360                 :            :         }
     361                 :            :     }
     362                 :            : 
     363                 :            :   return FALSE;
     364                 :            : }
     365                 :            : 
     366                 :            : 
     367                 :            : gboolean
     368                 :          0 : shumate_vector_collision_check (ShumateVectorCollision *self,
     369                 :            :                                 double                  x,
     370                 :            :                                 double                  y,
     371                 :            :                                 double                  xextent,
     372                 :            :                                 double                  yextent,
     373                 :            :                                 double                  rotation,
     374                 :            :                                 ShumateVectorOverlap    overlap,
     375                 :            :                                 gboolean                ignore_placement,
     376                 :            :                                 gpointer                tag)
     377                 :            : {
     378                 :          0 :   Box new_bbox = {
     379                 :            :     tag, x, y, xextent, yextent, rotation,
     380                 :          0 :     .overlap_never = overlap == SHUMATE_VECTOR_OVERLAP_NEVER
     381                 :            :   };
     382                 :            : 
     383                 :          0 :   axis_align (&new_bbox);
     384                 :            : 
     385         [ #  # ]:          0 :   if (overlap != SHUMATE_VECTOR_OVERLAP_ALWAYS)
     386                 :            :     {
     387         [ #  # ]:          0 :       if (detect_collision (self, &new_bbox))
     388                 :            :         return FALSE;
     389                 :            :     }
     390                 :            : 
     391         [ #  # ]:          0 :   if (!ignore_placement)
     392                 :          0 :     g_array_append_val (self->pending_boxes, new_bbox);
     393                 :            : 
     394                 :            :   return TRUE;
     395                 :            : }
     396                 :            : 
     397                 :            : int
     398                 :          0 : shumate_vector_collision_save_pending (ShumateVectorCollision *self)
     399                 :            : {
     400                 :          0 :   return self->pending_boxes->len;
     401                 :            : }
     402                 :            : 
     403                 :            : void
     404                 :          0 : shumate_vector_collision_rollback_pending (ShumateVectorCollision *self,
     405                 :            :                                            int                     save)
     406                 :            : {
     407                 :          0 :   g_array_set_size (self->pending_boxes, save);
     408                 :          0 : }
     409                 :            : 
     410                 :            : void
     411                 :          0 : shumate_vector_collision_commit_pending (ShumateVectorCollision *self,
     412                 :            :                                          graphene_rect_t        *bounds_out)
     413                 :            : {
     414                 :          0 :   int i;
     415                 :            : 
     416         [ #  # ]:          0 :   for (i = 0; i < self->pending_boxes->len; i ++)
     417                 :            :     {
     418                 :          0 :       Box bbox = g_array_index (self->pending_boxes, Box, i);
     419                 :          0 :       RTreeBucketRow *bucket_row;
     420                 :          0 :       RTreeBucketCol *bucket_col;
     421                 :          0 :       RTreeRow *row;
     422                 :          0 :       RTreeCol *col;
     423                 :          0 :       gsize bucket_x = floor (bbox.x / BUCKET_SIZE);
     424                 :          0 :       gsize bucket_y = floor (bbox.y / BUCKET_SIZE);
     425                 :          0 :       graphene_rect_t bounds;
     426                 :            : 
     427                 :          0 :       bucket_row = g_hash_table_lookup (self->bucket_rows, (gpointer) bucket_y);
     428         [ #  # ]:          0 :       if (bucket_row == NULL)
     429                 :            :         {
     430                 :          0 :           bucket_row = bucket_row_new ();
     431                 :          0 :           g_hash_table_insert (self->bucket_rows, (gpointer) bucket_y, bucket_row);
     432                 :          0 :           g_ptr_array_add (self->bucket_rows_array, bucket_row);
     433                 :            :         }
     434                 :            : 
     435                 :          0 :       bucket_col = g_hash_table_lookup (bucket_row->bucket_cols, (gpointer) bucket_x);
     436         [ #  # ]:          0 :       if (bucket_col == NULL)
     437                 :            :         {
     438                 :          0 :           bucket_col = bucket_col_new ();
     439                 :          0 :           g_hash_table_insert (bucket_row->bucket_cols, (gpointer) bucket_x, bucket_col);
     440                 :          0 :           g_ptr_array_add (bucket_row->bucket_cols_array, bucket_col);
     441                 :            :         }
     442                 :            : 
     443                 :          0 :       row = &bucket_col->rows[row_for_position (bbox.y)];
     444                 :          0 :       col = &row->cols[row_for_position (bbox.x)];
     445                 :            : 
     446         [ #  # ]:          0 :       if (col->boxes == NULL)
     447                 :          0 :         col->boxes = g_array_new (FALSE, FALSE, sizeof (Box));
     448                 :          0 :       g_array_append_val (col->boxes, bbox);
     449                 :            : 
     450                 :          0 :       bucket_col->n_boxes ++;
     451                 :            : 
     452                 :            :       /* Expand the parents to fit the new marker */
     453                 :          0 :       expand_rect (&bucket_row->bbox, &bbox);
     454                 :          0 :       expand_rect (&bucket_col->bbox, &bbox);
     455                 :          0 :       expand_rect (&row->bbox, &bbox);
     456                 :          0 :       expand_rect (&col->bbox, &bbox);
     457                 :            : 
     458                 :          0 :       bounds = GRAPHENE_RECT_INIT (bbox.x - bbox.aaxextent,
     459                 :            :                                    bbox.y - bbox.aayextent,
     460                 :            :                                    bbox.aaxextent * 2,
     461                 :            :                                    bbox.aayextent * 2);
     462         [ #  # ]:          0 :       if (i == 0)
     463                 :          0 :         *bounds_out = bounds;
     464                 :            :       else
     465                 :          0 :         graphene_rect_union (bounds_out, &bounds, bounds_out);
     466                 :            :     }
     467                 :            : 
     468                 :            :   /* clear the pending boxes */
     469                 :          0 :   g_array_set_size (self->pending_boxes, 0);
     470                 :          0 : }
     471                 :            : 
     472                 :            : 
     473                 :            : static gboolean
     474                 :          0 : point_intersects_rect_aa (Box    *box,
     475                 :            :                           double  x,
     476                 :            :                           double  y)
     477                 :            : {
     478                 :          0 :   return (x >= box->x - box->aaxextent &&
     479         [ #  # ]:          0 :           x <= box->x + box->aaxextent &&
     480   [ #  #  #  # ]:          0 :           y >= box->y - box->aayextent &&
     481         [ #  # ]:          0 :           y <= box->y + box->aayextent);
     482                 :            : }
     483                 :            : 
     484                 :            : 
     485                 :            : static gboolean
     486                 :          0 : point_intersects_rect (Box    *box,
     487                 :            :                        double  x,
     488                 :            :                        double  y)
     489                 :            : {
     490                 :          0 :   double x2, y2;
     491                 :          0 :   x -= box->x;
     492                 :          0 :   y -= box->y;
     493                 :            : 
     494                 :          0 :   x2 = cosf (-box->rotation) * x - sinf (-box->rotation) * y;
     495                 :          0 :   y2 = sinf (-box->rotation) * x + cosf (-box->rotation) * y;
     496                 :            : 
     497         [ #  # ]:          0 :   return (x2 >= -box->xextent &&
     498                 :          0 :           x2 <= box->xextent &&
     499   [ #  #  #  #  :          0 :           y2 >= -box->yextent &&
                   #  # ]
     500                 :            :           y2 <= box->yextent);
     501                 :            : }
     502                 :            : 
     503                 :            : 
     504                 :            : gboolean
     505                 :          0 : shumate_vector_collision_query_point (ShumateVectorCollision *self,
     506                 :            :                                       double                 x,
     507                 :            :                                       double                 y,
     508                 :            :                                       gpointer              tag)
     509                 :            : {
     510         [ #  # ]:          0 :   for (int i = 0; i < self->bucket_rows_array->len; i ++)
     511                 :            :     {
     512                 :          0 :       RTreeBucketRow *bucket_row = g_ptr_array_index (self->bucket_rows_array, i);
     513                 :            : 
     514         [ #  # ]:          0 :       if (!point_intersects_rect_aa (&bucket_row->bbox, x, y))
     515                 :          0 :         continue;
     516                 :            : 
     517         [ #  # ]:          0 :       for (int j = 0; j < bucket_row->bucket_cols_array->len; j ++)
     518                 :            :         {
     519                 :          0 :           RTreeBucketCol *bucket_col = g_ptr_array_index (bucket_row->bucket_cols_array, j);
     520                 :            : 
     521         [ #  # ]:          0 :           if (bucket_col->n_boxes == 0)
     522                 :          0 :             continue;
     523                 :            : 
     524         [ #  # ]:          0 :           if (!point_intersects_rect_aa (&bucket_col->bbox, x, y))
     525                 :          0 :             continue;
     526                 :            : 
     527         [ #  # ]:          0 :           for (int r = 0; r < NODES; r ++)
     528                 :            :             {
     529                 :          0 :               RTreeRow *row = &bucket_col->rows[r];
     530                 :            : 
     531         [ #  # ]:          0 :               if (!point_intersects_rect_aa (&row->bbox, x, y))
     532                 :          0 :                 continue;
     533                 :            : 
     534         [ #  # ]:          0 :               for (int c = 0; c < NODES; c ++)
     535                 :            :                 {
     536                 :          0 :                   RTreeCol *col = &row->cols[c];
     537                 :            : 
     538   [ #  #  #  # ]:          0 :                   if (col->boxes == NULL || !point_intersects_rect_aa (&col->bbox, x, y))
     539                 :          0 :                     continue;
     540                 :            : 
     541         [ #  # ]:          0 :                   for (int i = 0; i < col->boxes->len; i ++)
     542                 :            :                     {
     543                 :          0 :                       Box *b = &((Box*)col->boxes->data)[i];
     544                 :            : 
     545   [ #  #  #  #  :          0 :                       if (point_intersects_rect (b, x, y) && (tag == NULL || tag == b->tag))
                   #  # ]
     546                 :            :                         return TRUE;
     547                 :            :                     }
     548                 :            :                 }
     549                 :            :             }
     550                 :            :         }
     551                 :            :     }
     552                 :            : 
     553                 :            :   return FALSE;
     554                 :            : }
     555                 :            : 
     556                 :            : 
     557                 :            : void
     558                 :          0 : shumate_vector_collision_clear (ShumateVectorCollision *self)
     559                 :            : {
     560                 :          0 :   GHashTableIter bucket_rows;
     561                 :          0 :   RTreeBucketRow *bucket_row;
     562                 :            : 
     563                 :          0 :   g_hash_table_iter_init (&bucket_rows, self->bucket_rows);
     564                 :            : 
     565         [ #  # ]:          0 :   while (g_hash_table_iter_next (&bucket_rows, NULL, (gpointer*) &bucket_row))
     566                 :            :     {
     567                 :          0 :       GHashTableIter bucket_cols;
     568                 :          0 :       RTreeBucketCol *bucket_col;
     569                 :            : 
     570                 :          0 :       bucket_row->bbox = EMPTY_BOX;
     571                 :            : 
     572                 :          0 :       g_hash_table_iter_init (&bucket_cols, bucket_row->bucket_cols);
     573                 :            : 
     574         [ #  # ]:          0 :       while (g_hash_table_iter_next (&bucket_cols, NULL, (gpointer*) &bucket_col))
     575                 :            :         {
     576         [ #  # ]:          0 :           if (bucket_col->n_boxes == 0)
     577                 :            :             {
     578                 :          0 :               g_ptr_array_remove_fast (bucket_row->bucket_cols_array, bucket_col);
     579                 :          0 :               g_hash_table_iter_remove (&bucket_cols);
     580                 :          0 :               continue;
     581                 :            :             }
     582                 :            : 
     583                 :          0 :           bucket_col->n_boxes = 0;
     584                 :          0 :           bucket_col->bbox = EMPTY_BOX;
     585                 :            : 
     586         [ #  # ]:          0 :           for (int y = 0; y < NODES; y ++)
     587                 :            :             {
     588                 :          0 :               RTreeRow *row = &bucket_col->rows[y];
     589                 :          0 :               row->bbox = EMPTY_BOX;
     590                 :            : 
     591         [ #  # ]:          0 :               for (int x = 0; x < NODES; x ++)
     592                 :            :                 {
     593                 :          0 :                   RTreeCol *col = &row->cols[x];
     594                 :          0 :                   col->bbox = EMPTY_BOX;
     595                 :            : 
     596         [ #  # ]:          0 :                   if (col->boxes != NULL)
     597                 :          0 :                     g_array_set_size (col->boxes, 0);
     598                 :            :                 }
     599                 :            :             }
     600                 :            :         }
     601                 :            : 
     602         [ #  # ]:          0 :       if (g_hash_table_size (bucket_row->bucket_cols) == 0)
     603                 :            :         {
     604                 :          0 :           g_ptr_array_remove_fast (self->bucket_rows_array, bucket_row);
     605                 :          0 :           g_hash_table_iter_remove (&bucket_rows);
     606                 :            :         }
     607                 :            :     }
     608                 :          0 : }
     609                 :            : 
     610                 :            : 
     611                 :            : void
     612                 :          0 : shumate_vector_collision_visualize (ShumateVectorCollision *self,
     613                 :            :                                     GtkSnapshot            *snapshot)
     614                 :            : {
     615                 :          0 :   GHashTableIter bucket_rows;
     616                 :          0 :   RTreeBucketRow *bucket_row;
     617                 :            : 
     618                 :          0 :   float width[4] = { 1, 1, 1, 1 };
     619                 :          0 :   GdkRGBA color[4], color2[4];
     620                 :            : 
     621                 :          0 :   gdk_rgba_parse (&color[0], "#FF0000");
     622                 :          0 :   gdk_rgba_parse (&color[1], "#FF0000");
     623                 :          0 :   gdk_rgba_parse (&color[2], "#FF0000");
     624                 :          0 :   gdk_rgba_parse (&color[3], "#FF0000");
     625                 :            : 
     626                 :          0 :   gdk_rgba_parse (&color2[0], "#00FF00");
     627                 :          0 :   gdk_rgba_parse (&color2[1], "#00FF00");
     628                 :          0 :   gdk_rgba_parse (&color2[2], "#00FF00");
     629                 :          0 :   gdk_rgba_parse (&color2[3], "#00FF00");
     630                 :            : 
     631                 :          0 :   g_hash_table_iter_init (&bucket_rows, self->bucket_rows);
     632                 :            : 
     633         [ #  # ]:          0 :   while (g_hash_table_iter_next (&bucket_rows, NULL, (gpointer*) &bucket_row))
     634                 :            :     {
     635                 :          0 :       GHashTableIter tile_cols;
     636                 :          0 :       RTreeBucketCol *bucket_col;
     637                 :            : 
     638                 :          0 :       gtk_snapshot_append_border (snapshot,
     639                 :          0 :                                   &GSK_ROUNDED_RECT_INIT (bucket_row->bbox.x - bucket_row->bbox.aaxextent,
     640                 :            :                                                           bucket_row->bbox.y - bucket_row->bbox.aayextent,
     641                 :            :                                                           bucket_row->bbox.aaxextent * 2,
     642                 :            :                                                           bucket_row->bbox.aayextent),
     643                 :            :                                   width,
     644                 :            :                                   color);
     645                 :            : 
     646                 :          0 :       g_hash_table_iter_init (&tile_cols, bucket_row->bucket_cols);
     647                 :            : 
     648         [ #  # ]:          0 :       while (g_hash_table_iter_next (&tile_cols, NULL, (gpointer*) &bucket_col))
     649                 :            :         {
     650         [ #  # ]:          0 :           if (bucket_col->n_boxes == 0)
     651                 :          0 :             continue;
     652                 :            : 
     653                 :          0 :           gtk_snapshot_append_border (snapshot,
     654                 :          0 :                                       &GSK_ROUNDED_RECT_INIT (bucket_col->bbox.x - bucket_col->bbox.aaxextent,
     655                 :            :                                                               bucket_col->bbox.y - bucket_col->bbox.aayextent,
     656                 :            :                                                               bucket_col->bbox.aaxextent * 2,
     657                 :            :                                                               bucket_col->bbox.aayextent * 2),
     658                 :            :                                       width,
     659                 :            :                                       color);
     660                 :            : 
     661         [ #  # ]:          0 :           for (int y = 0; y < NODES; y ++)
     662                 :            :             {
     663                 :          0 :               RTreeRow *row = &bucket_col->rows[y];
     664                 :            : 
     665                 :          0 :               gtk_snapshot_append_border (snapshot,
     666                 :          0 :                                           &GSK_ROUNDED_RECT_INIT (row->bbox.x - row->bbox.aaxextent,
     667                 :            :                                                                   row->bbox.y - row->bbox.aayextent,
     668                 :            :                                                                   row->bbox.aaxextent * 2,
     669                 :            :                                                                   row->bbox.aayextent * 2),
     670                 :            :                                           width,
     671                 :            :                                           color);
     672                 :            : 
     673         [ #  # ]:          0 :               for (int x = 0; x < NODES; x ++)
     674                 :            :                 {
     675                 :          0 :                   RTreeCol *col = &row->cols[x];
     676                 :            : 
     677         [ #  # ]:          0 :                   if (col->boxes != NULL)
     678                 :            :                     {
     679                 :            : 
     680                 :          0 :                       gtk_snapshot_append_border (snapshot,
     681                 :          0 :                                                   &GSK_ROUNDED_RECT_INIT (col->bbox.x - col->bbox.aaxextent,
     682                 :            :                                                                           col->bbox.y - col->bbox.aayextent,
     683                 :            :                                                                           col->bbox.aaxextent * 2,
     684                 :            :                                                                           col->bbox.aayextent * 2),
     685                 :            :                                                   width,
     686                 :            :                                                   color);
     687                 :            : 
     688         [ #  # ]:          0 :                       for (int i = 0; i < col->boxes->len; i ++)
     689                 :            :                         {
     690                 :          0 :                           Box *b = &((Box*)col->boxes->data)[i];
     691                 :            : 
     692                 :          0 :                           gtk_snapshot_save (snapshot);
     693                 :          0 :                           gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (b->x, b->y));
     694                 :          0 :                           gtk_snapshot_rotate (snapshot, b->rotation * 180 / G_PI);
     695                 :            : 
     696                 :          0 :                           gtk_snapshot_append_border (snapshot,
     697                 :          0 :                                                       &GSK_ROUNDED_RECT_INIT (-b->xextent,
     698                 :            :                                                                               -b->yextent,
     699                 :            :                                                                               b->xextent * 2,
     700                 :            :                                                                               b->yextent * 2),
     701                 :            :                                                       width,
     702                 :            :                                                       color2);
     703                 :            : 
     704                 :          0 :                           gtk_snapshot_restore (snapshot);
     705                 :            :                         }
     706                 :            :                     }
     707                 :            :                 }
     708                 :            :             }
     709                 :            :         }
     710                 :            :     }
     711                 :          0 : }

Generated by: LCOV version 1.14