/* vrect.c Rectangle manipulation Copyright (C) 2012 Bill Currie Author: Bill Currie Date: 2012/1/6 This program 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. This program 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 this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "QF/mathlib.h" #include "QF/ui/vrect.h" //#define TEST_MEMORY #define RECT_BLOCK 128 #ifndef TEST_MEMORY static vrect_t *free_rects; #endif VISIBLE vrect_t * VRect_New (int x, int y, int width, int height) { vrect_t *r; #ifndef TEST_MEMORY if (!free_rects) { int i; free_rects = malloc (RECT_BLOCK * sizeof (vrect_t)); for (i = 0; i < RECT_BLOCK - 1; i++) free_rects[i].next = &free_rects[i + 1]; free_rects[i].next = 0; } r = free_rects; free_rects = free_rects->next; #else r = malloc (sizeof (vrect_t)); #endif r->next = 0; r->x = x; r->y = y; r->width = width; r->height = height; return r; } VISIBLE void VRect_Delete (vrect_t *rect) { #ifndef TEST_MEMORY rect->next = free_rects; free_rects = rect; #else free (rect); #endif } VISIBLE vrect_t * VRect_Intersect (const vrect_t *r1, const vrect_t *r2) { int minx = max (VRect_MinX (r1), VRect_MinX (r2)); int miny = max (VRect_MinY (r1), VRect_MinY (r2)); int maxx = min (VRect_MaxX (r1), VRect_MaxX (r2)); int maxy = min (VRect_MaxY (r1), VRect_MaxY (r2)); return VRect_New (minx, miny, maxx - minx, maxy - miny); } VISIBLE vrect_t * VRect_HSplit (const vrect_t *r, int y) { vrect_t *r1, *r2; int r1miny = VRect_MinY (r); int r1maxy = min (VRect_MaxY (r), y); int r2miny = max (VRect_MinY (r), y); int r2maxy = VRect_MaxY (r); r1 = VRect_New (VRect_MinX (r), r1miny, r->width, r1maxy - r1miny); r2 = VRect_New (VRect_MinX (r), r2miny, r->width, r2maxy - r2miny); r1->next = r2; return r1; } VISIBLE vrect_t * VRect_VSplit (const vrect_t *r, int x) { vrect_t *r1, *r2; int r1minx = VRect_MinX (r); int r1maxx = min (VRect_MaxX (r), x); int r2minx = max (VRect_MinX (r), x); int r2maxx = VRect_MaxX (r); r1 = VRect_New (r1minx, VRect_MinY (r), r1maxx - r1minx, r->height); r2 = VRect_New (r2minx, VRect_MinY (r), r2maxx - r2minx, r->height); r1->next = r2; return r1; } VISIBLE vrect_t * VRect_Difference (const vrect_t *r, const vrect_t *s) { vrect_t *i, *t, *_r; vrect_t *rects = 0; #define STASH(t) \ do { \ if (!VRect_IsEmpty (t)) { \ t->next = rects; \ rects = t; \ } else { \ VRect_Delete (t); \ } \ } while (0) // Find the intersection of r (original rect) and s (the rect being // subtracted from r). The intersection rect is used to carve the original // rect. If there is no intersection (the intersection rect is empty), // then there is nothing to subtract and the original rect (actually, a // copy of it) is returned. i = VRect_Intersect (r, s); if (VRect_IsEmpty (i)) { // copy r's shape to i, and return i. i->x = r->x; i->y = r->y; i->width = r->width; i->height = r->height; return i; } // Split r along the top of the intersection rect and stash the top // section if it is not empty. t = VRect_HSplit (r, VRect_MinY (i)); // can't delete r: we don't own it _r = t->next; STASH (t); // maybe stash the top section // r now represents the portion of the original rect below the top of // the intersection rect. // Split r along the bottom of the intersection rect and stash the bottom // section if it is not empty. t = VRect_HSplit (_r, VRect_MaxY (i)); VRect_Delete (_r); // finished with that copy of _r _r = t; STASH (t->next); // maybe stash the bottom section // r now represents the horizontal section that is common with the // intersection rect. // Split r along the left side of tht intersection rect and stash the // left section if it is not empty. t = VRect_VSplit (_r, VRect_MinX (i)); VRect_Delete (_r); // finished with that copy of _r _r = t->next; STASH (t); // maybe stash the left section // r now represets the section of the original rect that is between the // top and bottom, and to the right of the left side of the intersection // rect. // Split r along the right side of the intersection rect and stash the // right section if it is not empty. The left section is discarded. t = VRect_VSplit (_r, VRect_MaxX (i)); VRect_Delete (_r); // finished with that copy of _r STASH (t->next); // maybe stash the right section VRect_Delete (t); // discard the left section VRect_Delete (i); // finished with the intersection rect return rects; } VISIBLE vrect_t * VRect_Union (const vrect_t *r1, const vrect_t *r2) { int minx, miny; int maxx, maxy; if (VRect_IsEmpty (r1)) return VRect_New (r2->x, r2->y, r2->width, r2->height); if (VRect_IsEmpty (r2)) return VRect_New (r1->x, r1->y, r1->width, r1->height); minx = min (VRect_MinX (r1), VRect_MinX (r2)); miny = min (VRect_MinY (r1), VRect_MinY (r2)); maxx = max (VRect_MaxX (r1), VRect_MaxX (r2)); maxy = max (VRect_MaxY (r1), VRect_MaxY (r2)); return VRect_New (minx, miny, maxx - minx, maxy - miny); } VISIBLE vrect_t * VRect_Merge (const vrect_t *r1, const vrect_t *r2) { vrect_t *t, *u = 0; vrect_t *merge; // cannot merge intersecting rectangles t = VRect_Intersect (r1, r2); if (!VRect_IsEmpty (t)) { VRect_Delete (t); return 0; } VRect_Delete (t); merge = VRect_Union (r1, r2); if (VRect_IsEmpty (merge)) { VRect_Delete (merge); return 0; } // If the subtracting r1 from the union results in more than one rectangle // then r1 and r2 cannot be merged. t = VRect_Difference (merge, r1); if (t && t->next) goto cleanup; // If subtracting r2 from the result of the previous difference results in // any rectangles, then r1 and r2 cannot be merged. if (t) u = VRect_Difference (t, r2); if (!u) { if (t) VRect_Delete (t); return merge; } cleanup: VRect_Delete (merge); // merge is used as a temp while freeing t and u while (u) { merge = u->next; VRect_Delete (u); u = merge; } while (t) { merge = t->next; VRect_Delete (t); t = merge; } return 0; } VISIBLE vrect_t * VRect_SubRect (const vrect_t *rect, int width, int height) { if (width > rect->width) { width = rect->width; } if (height > rect->height) { height = rect->height; } // First check for exact fits as no heuristics are necessary if (width == rect->width) { if (height == rect->height) { // Exact fit, return a new rect of the same size return VRect_New (rect->x, rect->y, rect->width, rect->height); } vrect_t *r = VRect_HSplit (rect, rect->y + height); if (VRect_IsEmpty (r->next)) { VRect_Delete (r->next); r->next = 0; } return r; } else if (height == rect->height) { // Because rect's width ks known to be greater than the requested // width, the split is guaranteed to produce two non-empty rectangles. return VRect_VSplit (rect, rect->x + width); } int a = rect->height - height; int b = rect->width - width; vrect_t *r, *s; if (b * height <= a * width || 16 * a > 15 * rect->height) { // horizontal cuts produce better memory locality so favor them r = VRect_HSplit (rect, rect->y + height); if (VRect_IsEmpty (r->next)) { VRect_Delete (r->next); r->next = 0; } s = VRect_VSplit (r, r->x + width); } else { // a horizontal cut produces a larger off-cut rectangle than a vertical // cut, so do a vertical cut first so the off-cut is as small as // possible r = VRect_VSplit (rect, rect->x + width); if (VRect_IsEmpty (r->next)) { VRect_Delete (r->next); r->next = 0; } s = VRect_HSplit (r, r->y + height); } if (VRect_IsEmpty (s->next)) { VRect_Delete (s->next); s->next = r->next; } else { s->next->next = r->next; } VRect_Delete (r); return s; }