mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-20 07:31:30 -07:00
Files

The lack of this pair of parens triggered a compiler warning, which turned into an error at -Werror. Oops - I thought I'd folded that fix in before pushing Franklin's series.
303 lines
9.9 KiB
C
303 lines
9.9 KiB
C
/*
|
|
* draw-poly.c: Fallback polygon drawing function.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
|
|
#include "puzzles.h"
|
|
|
|
struct edge {
|
|
int x1, y1;
|
|
int x2, y2;
|
|
bool active;
|
|
|
|
/* whether y1 is a closed endpoint (i.e. this edge should be
|
|
* active when y == y1) */
|
|
bool closed_y1;
|
|
|
|
/* (x2 - x1) / (y2 - y1) as 16.16 signed fixed point; precomputed
|
|
* for speed */
|
|
long inverse_slope;
|
|
};
|
|
|
|
#define FRACBITS 16
|
|
#define ONEHALF (1 << (FRACBITS-1))
|
|
|
|
void draw_polygon_fallback(drawing *dr, const int *coords, int npoints,
|
|
int fillcolour, int outlinecolour)
|
|
{
|
|
struct edge *edges;
|
|
int min_y = INT_MAX, max_y = INT_MIN, i, y;
|
|
int n_edges = 0;
|
|
int *intersections;
|
|
|
|
if(npoints < 3)
|
|
return;
|
|
|
|
if(fillcolour < 0)
|
|
goto draw_outline;
|
|
|
|
/* This uses a basic scanline rasterization algorithm for polygon
|
|
* filling. First, an "edge table" is constructed for each pair of
|
|
* neighboring points. Horizontal edges are excluded. Then, the
|
|
* algorithm iterates a horizontal "scan line" over the vertical
|
|
* (Y) extent of the polygon. At each Y level, it maintains a set
|
|
* of "active" edges, which are those which intersect the scan
|
|
* line at the current Y level. The X coordinates where the scan
|
|
* line intersects each active edge are then computed via
|
|
* fixed-point arithmetic and stored. Finally, horizontal lines
|
|
* are drawn between each successive pair of intersection points,
|
|
* in the order of ascending X coordinate. This has the effect of
|
|
* "even-odd" filling when the polygon is self-intersecting.
|
|
*
|
|
* I (vaguely) based this implementation off the slides below:
|
|
*
|
|
* https://www.khoury.northeastern.edu/home/fell/CS4300/Lectures/CS4300F2012-9-ScanLineFill.pdf
|
|
*
|
|
* I'm fairly confident that this current implementation is
|
|
* correct (i.e. draws the right polygon, free from artifacts),
|
|
* but it isn't quite as efficient as it could be. Namely, it
|
|
* currently maintains the active edge set by setting the `active`
|
|
* flag in the `edge` array, which is quite inefficient. Perhaps
|
|
* future optimization could see this replaced with a tree
|
|
* set. Additionally, one could perhaps replace the current linear
|
|
* search for edge endpoints (i.e. the inner loop over `edges`) by
|
|
* sorting the edge table by upper and lower Y coordinate.
|
|
*
|
|
* A final caveat comes from the use of fixed point arithmetic,
|
|
* which is motivated by performance considerations on FPU-less
|
|
* platforms (e.g. most Rockbox devices, and maybe others?). I'm
|
|
* currently using 16 fractional bits to compute the edge
|
|
* intersections, which (in the case of a 32-bit int) limits the
|
|
* acceptable range of coordinates to roughly (-2^14, +2^14). This
|
|
* ought to be acceptable for the forseeable future, but
|
|
* ultra-high DPI screens might one day exceed this. In that case,
|
|
* options include switching to int64_t (but that comes with its
|
|
* own portability headaches), reducing the number of fractional
|
|
* bits, or just giving up and using floating point.
|
|
*/
|
|
|
|
/* Build edge table from coords. Horizontal edges are filtered
|
|
* out, so n_edges <= n_points in general. */
|
|
edges = smalloc(npoints * sizeof(struct edge));
|
|
|
|
for(i = 0; i < npoints; i++) {
|
|
int x1, y1, x2, y2;
|
|
|
|
x1 = coords[(2*i+0)];
|
|
y1 = coords[(2*i+1)];
|
|
x2 = coords[(2*i+2) % (npoints * 2)];
|
|
y2 = coords[(2*i+3) % (npoints * 2)];
|
|
|
|
if(y1 < min_y)
|
|
min_y = y1;
|
|
if(y1 > max_y)
|
|
max_y = y1;
|
|
|
|
#define COORD_LIMIT (1<<(sizeof(int)*CHAR_BIT-2 - FRACBITS))
|
|
/* Prevent integer overflow when computing `inverse_slope',
|
|
* which shifts the coordinates left by FRACBITS, and for
|
|
* which we'd like to avoid relying on `long long'. */
|
|
/* If this ever causes issues, see the above comment about
|
|
possible solutions. */
|
|
assert(x1 < COORD_LIMIT && y1 < COORD_LIMIT);
|
|
|
|
/* Only create non-horizontal edges, and require y1 < y2. */
|
|
if(y1 != y2) {
|
|
bool swap = y1 > y2;
|
|
int lower_endpoint = swap ? (i + 1) : i;
|
|
|
|
/* Compute index of the point adjacent to lower end of
|
|
* this edge (which is not the upper end of this edge). */
|
|
int lower_neighbor = swap ? (lower_endpoint + 1) % npoints : (lower_endpoint + npoints - 1) % npoints;
|
|
|
|
struct edge *edge = edges + (n_edges++);
|
|
|
|
edge->active = false;
|
|
edge->x1 = swap ? x2 : x1;
|
|
edge->y1 = swap ? y2 : y1;
|
|
edge->x2 = swap ? x1 : x2;
|
|
edge->y2 = swap ? y1 : y2;
|
|
edge->inverse_slope = ((edge->x2 - edge->x1) << FRACBITS) / (edge->y2 - edge->y1);
|
|
edge->closed_y1 = edge->y1 < coords[2*lower_neighbor+1];
|
|
}
|
|
}
|
|
|
|
/* a generous upper bound on number of intersections is n_edges */
|
|
intersections = smalloc(n_edges * sizeof(int));
|
|
|
|
for(y = min_y; y <= max_y; y++) {
|
|
int n_intersections = 0;
|
|
for(i = 0; i < n_edges; i++) {
|
|
struct edge *edge = edges + i;
|
|
/* Update active edge set. These conditions are mutually
|
|
* exclusive because of the invariant that y1 < y2. */
|
|
if(edge->y1 + (edge->closed_y1 ? 0 : 1) == y)
|
|
edge->active = true;
|
|
else if(edge->y2 + 1 == y)
|
|
edge->active = false;
|
|
|
|
if(edge->active) {
|
|
int x = edges[i].x1;
|
|
x += (edges[i].inverse_slope * (y - edges[i].y1) + ONEHALF) >> FRACBITS;
|
|
intersections[n_intersections++] = x;
|
|
}
|
|
}
|
|
|
|
qsort(intersections, n_intersections, sizeof(int), compare_integers);
|
|
|
|
assert(n_intersections % 2 == 0);
|
|
assert(n_intersections <= n_edges);
|
|
|
|
/* Draw horizontal lines between successive pairs of
|
|
* intersections of the scanline with active edges. */
|
|
for(i = 0; i + 1 < n_intersections; i += 2) {
|
|
draw_line(dr,
|
|
intersections[i], y,
|
|
intersections[i+1], y,
|
|
fillcolour);
|
|
}
|
|
}
|
|
|
|
sfree(intersections);
|
|
sfree(edges);
|
|
|
|
draw_outline:
|
|
assert(outlinecolour >= 0);
|
|
for (i = 0; i < 2 * npoints; i += 2)
|
|
draw_line(dr,
|
|
coords[i], coords[i+1],
|
|
coords[(i+2)%(2*npoints)], coords[(i+3)%(2*npoints)],
|
|
outlinecolour);
|
|
}
|
|
|
|
#ifdef STANDALONE_POLYGON
|
|
|
|
/*
|
|
* Standalone program to test draw_polygon_fallback(). By default,
|
|
* creates a window and allows clicking points to build a
|
|
* polygon. Optionally, can draw a randomly growing polygon in
|
|
* "screensaver" mode.
|
|
*/
|
|
|
|
#include <SDL.h>
|
|
|
|
void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour)
|
|
{
|
|
SDL_Renderer *renderer = GET_HANDLE_AS_TYPE(dr, SDL_Renderer);
|
|
SDL_RenderDrawLine(renderer, x1, y1, x2, y2);
|
|
}
|
|
|
|
#define WINDOW_WIDTH 800
|
|
#define WINDOW_HEIGHT 600
|
|
#define MAX_SCREENSAVER_POINTS 1000
|
|
|
|
int main(int argc, char *argv[]) {
|
|
SDL_Window* window = NULL;
|
|
SDL_Event event;
|
|
SDL_Renderer *renderer = NULL;
|
|
int running = 1;
|
|
int i;
|
|
drawing dr;
|
|
bool screensaver = false;
|
|
|
|
if(argc >= 2) {
|
|
if(!strcmp(argv[1], "--screensaver"))
|
|
screensaver = true;
|
|
else
|
|
printf("usage: %s [--screensaver]\n", argv[0]);
|
|
}
|
|
|
|
int *poly = NULL;
|
|
int n_points = 0;
|
|
|
|
/* Initialize SDL */
|
|
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
|
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
|
|
return 1;
|
|
}
|
|
|
|
/* Create window */
|
|
window = SDL_CreateWindow("Polygon Drawing Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN);
|
|
if (!window) {
|
|
printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
/* Create renderer */
|
|
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
|
if (!renderer) {
|
|
printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError());
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
return 1;
|
|
}
|
|
|
|
dr.handle = renderer;
|
|
|
|
if(!screensaver)
|
|
printf("Click points in the window to create vertices. Pressing C resets.\n");
|
|
|
|
while (running) {
|
|
while (SDL_PollEvent(&event) != 0) {
|
|
if (event.type == SDL_QUIT) {
|
|
running = 0;
|
|
}
|
|
else if (event.type == SDL_MOUSEBUTTONDOWN) {
|
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
|
int mouseX = event.button.x;
|
|
int mouseY = event.button.y;
|
|
|
|
poly = realloc(poly, ++n_points * 2 * sizeof(int));
|
|
poly[2 * (n_points - 1)] = mouseX;
|
|
poly[2 * (n_points - 1) + 1] = mouseY;
|
|
}
|
|
}
|
|
else if (event.type == SDL_KEYDOWN) {
|
|
if (event.key.keysym.sym == SDLK_c) {
|
|
free(poly);
|
|
poly = NULL;
|
|
n_points = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(screensaver) {
|
|
poly = realloc(poly, ++n_points * 2 * sizeof(int));
|
|
poly[2 * (n_points - 1)] = rand() % WINDOW_WIDTH;
|
|
poly[2 * (n_points - 1) + 1] = rand() % WINDOW_HEIGHT;
|
|
|
|
if(n_points >= MAX_SCREENSAVER_POINTS) {
|
|
free(poly);
|
|
poly = NULL;
|
|
n_points = 0;
|
|
}
|
|
}
|
|
|
|
/* Clear the screen with a black color */
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
|
SDL_RenderClear(renderer);
|
|
|
|
/* Set draw color to white */
|
|
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
|
|
|
|
draw_polygon_fallback(&dr, poly, n_points, 1, 1);
|
|
|
|
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
|
|
for(i = 0; i < 2*n_points; i+=2)
|
|
SDL_RenderDrawPoint(renderer, poly[i], poly[i+1]);
|
|
|
|
/* Present the back buffer */
|
|
SDL_RenderPresent(renderer);
|
|
}
|
|
|
|
/* Clean up and quit SDL */
|
|
SDL_DestroyRenderer(renderer);
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
|
|
return 0;
|
|
}
|
|
#endif
|