quakeforge/libs/ui/vrect.c
Bill Currie 28e9a0a9ba [ui] Add a specialized function for subrect allocation
While VRect_Difference worked for subrect allocation, it wasn't ideal as
it tended to produce a lot of long, narrow strips that were difficult to
reuse and thus wasted a lot of the super-rectangle's area. This is
because it always does horizontal splits first. However, rewriting
VRect_Difference didn't seem to be the best option.

VRect_SubRect (the new function) takes only width and height, and splits
the given rectangle such that if there are two off-cuts, they will be
both the minimum and maximum possible area. This does seem to make for
much better utilization of the available area. It's also faster as it
does only the two splits, rather than four.
2022-09-02 17:47:27 +09:00

301 lines
7.6 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;
}
int a = rect->height - height;
int b = rect->width - width;
vrect_t *r, *s;
if (b * height > a * width) {
// 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);
} else {
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);
}
if (VRect_IsEmpty (s->next)) {
VRect_Delete (s->next);
s->next = r->next;
} else {
s->next->next = r->next;
}
VRect_Delete (r);
return s;
}