diff --git a/include/QF/vrect.h b/include/QF/vrect.h index e26e1f739..61ad31ab2 100644 --- a/include/QF/vrect.h +++ b/include/QF/vrect.h @@ -137,4 +137,33 @@ vrect_t *VRect_VSplit (const vrect_t *r, int x); */ vrect_t *VRect_Difference (const vrect_t *r, const vrect_t *s); +/** Return the union of the two rectangles. + + The union rectangle will be big enough to entirely contain both \a r1 + and \a r2. An empty rectangle will be returned if both rectangles are + empty. + + \param r1 The first rectangle. + \param r2 The second rectangle. + \return The union of the two rectangles. + \note It is the caller's responsibility to delete the returned rectangle. +*/ +vrect_t *VRect_Union (const vrect_t *r1, const vrect_t *r2); + +/** Return the rectangle representing the merge of the two given rectangles. + + If the two given rectangles cannot be perfectly joined (either they + intesect or subtracting them from the merged rectangle would result + in scrap rectangles, null will be returned. Two empty rectangles will + result in null being returned, but only one empty rectangle will result + in a copy of the non-empty rectangle. + + \param r1 The first rectangle to be merged. + \param r2 The second rectangle to be merged. + \return The merged rectangle, or null if the given rectangles + cannot be merged. + \note It is the caller's responsibility to delete the returned rectangle. +*/ +vrect_t *VRect_Merge (const vrect_t *r, const vrect_t *s); + #endif//__QF_vrect_h diff --git a/libs/util/test/test-vrect.c b/libs/util/test/test-vrect.c index 6874f43cb..b1ba859bc 100644 --- a/libs/util/test/test-vrect.c +++ b/libs/util/test/test-vrect.c @@ -95,6 +95,31 @@ struct { {VRect_Difference, VR (3,3,2,2), VR (1,1,3,3), &de_15_0}, {VRect_Difference, VR (4,4,2,2), VR (1,1,3,3), &de_16_0}, + + {VRect_Union, VR (0, 0, 0, 0), VR (1, 1, -1, 1), 0, VR (1, 1, 1, -1), 1}, + {VRect_Union, VR (0, 0, 0, 0), VR (1, 1, 3, 3), 0, VR (1, 1, 3, 3), 1}, + {VRect_Union, VR (0, 0, 2, 5), VR (1, 1, -1, 1), 0, VR (0, 0, 2, 5), 1}, + {VRect_Union, VR (0, 0, 2, 5), VR (1, 1, 3, 3), 0, VR (0, 0, 4, 5), 1}, + + {VRect_Merge, VR (0, 0, 0, 0), VR (1, 1, -1, 1), 0}, + {VRect_Merge, VR (0, 0, 0, 0), VR (1, 1, 3, 3), 0, VR (1, 1, 3, 3), 1}, + {VRect_Merge, VR (0, 0, 2, 5), VR (1, 1, -1, 1), 0, VR (0, 0, 2, 5), 1}, + {VRect_Merge, VR (0, 0, 2, 5), VR (1, 1, 3, 3), 0}, + + {VRect_Merge, VR (0,0,2,2), VR (1,1,3,3), 0}, + {VRect_Merge, VR (0,0,5,2), VR (1,1,3,3), 0}, + {VRect_Merge, VR (2,0,2,2), VR (1,1,3,3), 0}, + {VRect_Merge, VR (3,0,2,2), VR (1,1,3,3), 0}, + + {VRect_Merge, VR (0,0,4,1), VR (1,1,3,3), 0}, + {VRect_Merge, VR (1,-1,3,1), VR (1,1,3,3), 0}, + {VRect_Merge, VR (0,4,4,1), VR (1,1,3,3), 0}, + {VRect_Merge, VR (1,5,3,1), VR (1,1,3,3), 0}, + + {VRect_Merge, VR (1,0,3,1), VR (1,1,3,3), 0, VR (1,0,3,4), 1}, + {VRect_Merge, VR (1,4,3,1), VR (1,1,3,3), 0, VR (1,1,3,4), 1}, + {VRect_Merge, VR (0,1,1,3), VR (1,1,3,3), 0, VR (0,1,4,3), 1}, + {VRect_Merge, VR (4,1,1,3), VR (1,1,3,3), 0, VR (1,1,4,3), 1}, }; #define num_tests (sizeof (tests) / sizeof (tests[0])) @@ -116,6 +141,9 @@ compare_rects (vrect_t *r1, vrect_t *r2) return 1; if (!r1 || !r2) return 0; + // when both rects are empty, their exact values don't matter. + if (VRect_IsEmpty (r1) && VRect_IsEmpty (r2)) + return 1; if (r1->x != r2->x || r1->y != r2->y || r1->width != r2->width || r1->height != r2->height) return 0; diff --git a/libs/util/vrect.c b/libs/util/vrect.c index c1c7f1ac7..7edb1defb 100644 --- a/libs/util/vrect.c +++ b/libs/util/vrect.c @@ -38,14 +38,18 @@ static __attribute__ ((used)) const char rcsid[] = "$Id$"; #include "QF/mathlib.h" #include "QF/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; @@ -56,6 +60,9 @@ VRect_New (int x, int y, int width, int height) } r = free_rects; free_rects = free_rects->next; +#else + r = malloc (sizeof (vrect_t)); +#endif r->next = 0; r->x = x; r->y = y; @@ -67,8 +74,12 @@ VRect_New (int x, int y, int width, int height) VISIBLE void VRect_Delete (vrect_t *rect) { +#ifndef TEST_MEMORY rect->next = free_rects; free_rects = rect; +#else + free (rect); +#endif } VISIBLE vrect_t * @@ -178,3 +189,72 @@ VRect_Difference (const vrect_t *r, const vrect_t *s) 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) { + 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; +}