quakeforge/libs/ui/vrect.c

322 lines
8.3 KiB
C

/*
vrect.c
Rectangle manipulation
Copyright (C) 2012 Bill Currie <bill@taniwha.org>
Author: Bill Currie <bill@taniwha.org>
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 <stdlib.h>
#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;
}