lineclip test code

This commit is contained in:
Irmen de Jong committed 2026-04-12 16:36:10 +02:00
1 parent d836f48f38
commit 0be336c8a8
5 files changed
+755

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
# Line clipping library and test programs
CC = gcc
CFLAGS = -Wall -Wextra -O2
LDFLAGS =
LIBS =
SDL_CFLAGS = $(shell pkg-config --cflags sdl3 2>/dev/null)
SDL_LIBS = $(shell pkg-config --libs sdl3 2>/dev/null || echo "-lSDL3")
# Sources
LIB_SRCS = lineclip.c
CS_SRCS = test_clip.c
VIS_SRCS = clip_visual.c
# Targets
all: test_clip clip_visual
test_clip: $(CS_SRCS) $(LIB_SRCS)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(CS_SRCS) $(LIB_SRCS) $(LIBS)
clip_visual: $(VIS_SRCS) $(LIB_SRCS)
$(CC) $(CFLAGS) $(SDL_CFLAGS) $(LDFLAGS) -o $@ $(VIS_SRCS) $(LIB_SRCS) $(SDL_LIBS)
clean:
rm -f test_clip clip_visual
.PHONY: all clean
+264
View File
@@ -0,0 +1,264 @@
/* clip_visual.c - SDL3 visual test for line clipping algorithms
*
* Draws the clip window and all test cases.
* Gray = original line. Green = clipped result (visible). Red = rejected.
* Press SPACE to cycle algorithms. Press Q to quit.
*
* Build: gcc -Wall -Wextra -O2 -o clip_visual clip_visual.c lineclip.c -lSDL3
*/
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <stdint.h>
#include "lineclip.h"
/* ----------------------------------------------------------------------- */
#define WIDTH 1200
#define HEIGHT 900
/* World coordinate range: 0..2000 mapped to screen */
#define WORLD_MIN 0
#define WORLD_MAX 2000
/* Clip window: (400,300) to (1600,1400) */
#define CLIP_X1 400
#define CLIP_Y1 300
#define CLIP_X2 1600
#define CLIP_Y2 1400
/* Map world coords to screen coords */
static float sx(int16_t x) {
return 80.0f + ((float)(x - WORLD_MIN) * (float)(WIDTH - 160) / (float)(WORLD_MAX - WORLD_MIN));
}
static float sy(int16_t y) {
return (float)(HEIGHT - 80) - ((float)(y - WORLD_MIN) * (float)(HEIGHT - 160) / (float)(WORLD_MAX - WORLD_MIN));
}
typedef struct {
int16_t x1, y1, x2, y2;
const char *name;
} TestCase;
static const TestCase tests[] = {
/* Inside the clip window */
{ 800, 600, 1200, 1000, "T1 inside" },
{ 420, 320, 500, 400, "T2 near TL" },
{ 1580, 1380, 1600, 1400, "T3 near BR" },
{ 1000, 850, 1000, 850, "T4 point inside" },
/* Completely outside -- far away */
{ -500, -200, -300, 100, "T5 far left" },
{ 2200, 1500, 2500, 1800, "T6 far right" },
{ 800, -500, 1000, -200, "T7 far above" },
{ 1000, 1700, 1200, 2000, "T8 far below" },
{ -300, -300, 200, 100, "T9 far TL" },
{ 1800, 1600, 2200, 1700, "T10 far BR" },
{ -400, 1500, 100, 1800, "T11 far BL" },
{ 1800, -300, 2000, 100, "T12 far TR" },
/* Crossing from outside into window */
{ 200, 800, 600, 800, "T13 enter left" },
{ 1400, 800, 1800, 800, "T14 enter right" },
{ 1000, 100, 1000, 500, "T15 enter top" },
{ 1000, 1200, 1000, 1600, "T16 enter bottom" },
/* Diagonals crossing the window */
{ 100, 200, 1800, 1700, "T17 diag TL-BR" },
{ 100, 1700, 1800, 200, "T18 diag BL-TR" },
{ 1800, 200, 100, 1700, "T19 diag TR-BL" },
{ 1800, 1700, 100, 200, "T20 diag BR-TL" },
/* Very long lines crossing the world */
{ 0, 1000, 2000, 1000, "T21 horiz world" },
{ 1000, 0, 1000, 2000, "T22 vert world" },
{ 0, 0, 2000, 2000, "T23 full world diag" },
{ 2000, 0, 0, 2000, "T24 full world diag2" },
/* Shallow diagonals */
{ 200, 800, 1800, 810, "T25 shallow up" },
{ 200, 900, 1800, 890, "T26 shallow down" },
/* Steep diagonals */
{ 1000, 100, 1010, 1900, "T27 steep right" },
{ 990, 100, 1000, 1900, "T28 steep left" },
/* Long rejects: parallel but far outside */
{ 200, 100, 350, 100, "T29 above parallel" },
{ 200, 1550, 350, 1550, "T30 below parallel" },
{ 100, 800, 100, 1100, "T31 left parallel" },
{ 1800, 800, 1800, 1100, "T32 right parallel" },
/* Extreme coordinates (within int16 range) */
{ 0, 1000, 0, 1001, "T33 x=0 tiny" },
{ 2000, 1000, 2000, 1001, "T34 x=2000 tiny" },
{ 1000, 0, 1001, 0, "T35 y=0 tiny" },
{ 1000, 2000, 1001, 2000, "T36 y=2000 tiny" },
/* Lines grazing the clip window edges */
{ 200, 300, 1800, 300, "T37 graze top edge" },
{ 200, 1400, 1800, 1400, "T38 graze bottom" },
{ 400, 100, 400, 1600, "T39 graze left edge" },
{ 1600, 100, 1600, 1600, "T40 graze right edge" },
/* Points on clip window corners */
{ 400, 300, 400, 300, "T41 pt TL corner" },
{ 1600, 300, 1600, 300, "T42 pt TR corner" },
{ 400, 1400, 400, 1400, "T43 pt BL corner" },
{ 1600, 1400, 1600, 1400, "T44 pt BR corner" },
/* Lines connecting two distant corners through the window */
{ 0, 0, 2000, 2000, "T45 origin to far BR" },
{ 2000, 0, 0, 2000, "T46 far TL to far BR" },
/* Short lines just outside each edge */
{ 390, 800, 395, 800, "T47 near left edge" },
{ 1605, 800, 1610, 800, "T48 near right edge" },
{ 1000, 295, 1005, 295, "T49 near top edge" },
{ 1000, 1405, 1005, 1405, "T50 near bottom edge" },
};
#define NUM_TESTS (sizeof(tests) / sizeof(tests[0]))
typedef struct {
const char *name;
bool (*clip)(int16_t*, int16_t*, int16_t*, int16_t*,
int16_t, int16_t, int16_t, int16_t);
} Algo;
static const Algo algos[] = {
{ "CS-float", clip_cohen_sutherland },
{ "CS-int16", clip_cohen_sutherland_int16 },
{ "Elite-style", clip_elite },
};
#define NUM_ALGOS (sizeof(algos) / sizeof(algos[0]))
/* Draw a line */
static void draw_line(SDL_Renderer *r, float x1, float y1, float x2, float y2,
SDL_FColor c)
{
SDL_FPoint pts[2] = { { x1, y1 }, { x2, y2 } };
SDL_SetRenderDrawColorFloat(r, c.r, c.g, c.b, c.a);
SDL_RenderLines(r, pts, 2);
}
/* ----------------------------------------------------------------------- */
int main(void)
{
SDL_Window *win;
SDL_Renderer *ren;
SDL_Event ev;
int algo_idx = 0;
int quit = 0;
if (!SDL_Init(SDL_INIT_VIDEO)) {
SDL_Log("SDL_Init failed: %s", SDL_GetError());
return 1;
}
win = SDL_CreateWindow("Line Clipping Visual Test", WIDTH, HEIGHT, 0);
ren = SDL_CreateRenderer(win, NULL);
if (!win || !ren) {
SDL_Log("Window/Renderer failed: %s", SDL_GetError());
return 1;
}
/* Clip window rect */
SDL_FRect clip_rect = {
sx(CLIP_X1), sy(CLIP_Y2),
sx(CLIP_X2) - sx(CLIP_X1),
sy(CLIP_Y1) - sy(CLIP_Y2)
};
/* Grid lines */
SDL_SetRenderDrawColor(ren, 50, 50, 60, 255);
for (int i = 0; i <= 2000; i += 200) {
SDL_RenderLine(ren, sx(i), sy(WORLD_MIN), sx(i), sy(WORLD_MAX));
SDL_RenderLine(ren, sx(WORLD_MIN), sy(i), sx(WORLD_MAX), sy(i));
}
while (!quit) {
while (SDL_PollEvent(&ev)) {
if (ev.type == SDL_EVENT_QUIT) quit = 1;
if (ev.type == SDL_EVENT_KEY_DOWN) {
switch (ev.key.key) {
case SDLK_ESCAPE: case SDLK_Q: quit = 1; break;
case SDLK_SPACE:
algo_idx = (algo_idx + 1) % NUM_ALGOS;
break;
}
}
}
/* Clear */
SDL_SetRenderDrawColor(ren, 15, 15, 25, 255);
SDL_RenderClear(ren);
/* Grid */
SDL_SetRenderDrawColor(ren, 40, 40, 55, 255);
for (int i = 0; i <= WORLD_MAX; i += 200) {
SDL_RenderLine(ren, sx(i), sy(WORLD_MIN), sx(i), sy(WORLD_MAX));
SDL_RenderLine(ren, sx(WORLD_MIN), sy(i), sx(WORLD_MAX), sy(i));
}
/* Fill clip window */
SDL_FColor fill = { 0.1f, 0.12f, 0.18f, 0.7f };
SDL_SetRenderDrawColorFloat(ren, fill.r, fill.g, fill.b, fill.a);
SDL_RenderFillRect(ren, &clip_rect);
/* Draw clip window border */
SDL_SetRenderDrawColor(ren, 100, 100, 130, 255);
SDL_RenderRect(ren, &clip_rect);
const Algo *algo = &algos[algo_idx];
/* Draw every test */
for (int i = 0; i < (int)NUM_TESTS; i++) {
int16_t ox1 = tests[i].x1, oy1 = tests[i].y1;
int16_t ox2 = tests[i].x2, oy2 = tests[i].y2;
int16_t rx1 = ox1, ry1 = oy1, rx2 = ox2, ry2 = oy2;
int vis = algo->clip(&rx1, &ry1, &rx2, &ry2,
CLIP_X1, CLIP_Y1, CLIP_X2, CLIP_Y2);
/* Original line in dim gray */
SDL_FColor gray = { 0.3f, 0.3f, 0.35f, 0.6f };
draw_line(ren, sx(ox1), sy(oy1), sx(ox2), sy(oy2), gray);
if (vis) {
/* Clipped result in green */
SDL_FColor green = { 0.15f, 0.9f, 0.3f, 1.0f };
draw_line(ren, sx(rx1), sy(ry1), sx(rx2), sy(ry2), green);
/* Endpoint markers */
SDL_FColor marker = { 0.3f, 1.0f, 0.4f, 1.0f };
SDL_FRect m = { sx(rx1)-3, sy(ry1)-3, 6, 6 };
SDL_SetRenderDrawColorFloat(ren, marker.r, marker.g, marker.b, marker.a);
SDL_RenderFillRect(ren, &m);
m.x = sx(rx2)-3; m.y = sy(ry2)-3;
SDL_RenderFillRect(ren, &m);
} else {
/* Rejected: red X at midpoint */
SDL_FColor red = { 0.8f, 0.15f, 0.15f, 1.0f };
float mx = (sx(ox1) + sx(ox2)) / 2;
float my = (sy(oy1) + sy(oy2)) / 2;
SDL_SetRenderDrawColorFloat(ren, red.r, red.g, red.b, red.a);
SDL_FPoint xa[2] = { { mx-5, my-5 }, { mx+5, my+5 } };
SDL_FPoint xb[2] = { { mx-5, my+5 }, { mx+5, my-5 } };
SDL_RenderLines(ren, xa, 2);
SDL_RenderLines(ren, xb, 2);
}
}
/* Algorithm name in title bar */
char title[128];
SDL_snprintf(title, sizeof(title), "%s (%d tests)", algo->name, (int)NUM_TESTS);
SDL_SetWindowTitle(win, title);
SDL_RenderPresent(ren);
}
SDL_DestroyRenderer(ren);
SDL_DestroyWindow(win);
SDL_Quit();
return 0;
}
+266
View File
@@ -0,0 +1,266 @@
/* lineclip.c - Line clipping algorithms
*
* CURRENT implementation: optimized for SMALLNESS using C's native float.
*
* FUTURE constraints for a planned integer-only 6502 target implementation:
* (These are NOT enforced in the current code!)
* -----------------------------------------------------
* 1. NO floating point arithmetic.
* 2. NO signed modulo operator (%).
* 3. Results MUST be strictly inside the clip window.
* 4. Return true if visible, false if fully clipped.
* 5. Parametric t is scaled by 256.
* 6. Keep intermediates in int16_t; use int32_t only when necessary.
* Input coords are int16_t (-32767..32767). Clip window is [0,0]-[639,479].
* 7. NO recursion — iterative only.
* 8. 16-bit signed division IS available. No 32-bit/64-bit division.
* Division by 256 is a right shift.
*/
#include "lineclip.h"
#include <stdbool.h>
#include <stddef.h>
/* (dist * num) / den — 32-bit intermediate, 16-bit result.
* Used by both CS-int16 and Elite-style clipping.
*
* 6502 Implementation Notes:
* ---------------------------
* On a plain 6502 this expands to a 16x16→32 multiply followed by a
* 32/16→16 division. Neither operation is natively supported, so both
* require software routines:
*
* MUL16x16: shift-add multiply, ~160 cycles
* DIV32x16: restoring division, ~200 cycles
* TOTAL: ~360 cycles per call
*
* Optimisation opportunities for 6502:
* -------------------------------------
* 1. When den is a power of two (e.g. 256), replace the division
* with a simple byte swap or right-shift. This saves the entire
* DIV32x16 routine (~200 cycles).
*
* 2. When num is 256 (common in frac_mul(dx, t0, 256)), the 32-bit
* multiply reduces to a 16-bit left-shift by 8 (i.e. store dx in
* the high word). The result is simply the high byte of the
* 32-bit product — no actual multiply loop needed.
*
* 3. When |dist| < 256 (common for short clip distances), the 16x16
* multiply can be reduced to an 8x16 multiply, saving ~40 cycles.
*
* 4. Sign handling: track the sign separately, do unsigned math on
* absolute values, then negate if needed. This avoids signed
* multiplication and division entirely, using simpler unsigned
* routines.
*
* 5. Zero-page placement: keep all 6 working variables in zero-page
* ($00-$09) to save 2 cycles per access.
*
* Combined, these optimizations can reduce a single frac_mul call
* from ~360 cycles down to ~60-120 cycles depending on the inputs.
*/
static inline int16_t frac_mul(int16_t dist, int16_t num, int16_t den)
{
return (int16_t)((int32_t)dist * num / den);
}
/* ========================================================================== */
/* Cohen-Sutherland: iterative region-code clipping (float) */
/* ========================================================================== */
static inline int _oc(int16_t x, int16_t y, int16_t x1, int16_t y1, int16_t x2, int16_t y2)
{
int c = 0;
if (x < x1) c |= 1;
if (x > x2) c |= 2;
if (y < y1) c |= 4;
if (y > y2) c |= 8;
return c;
}
/* After clipping against LEFT/RIGHT edge: x is exactly cx1 or cx2, so
* LEFT=RIGHT=0. Only need to test the new y against cy1/cy2. */
static inline int _oc_after_x(int16_t y, int16_t cy1, int16_t cy2)
{
int c = 0;
if (y < cy1) c |= 4;
if (y > cy2) c |= 8;
return c;
}
/* After clipping against TOP/BOTTOM edge: y is exactly cy1 or cy2, so
* TOP=BOTTOM=0. Only need to test the new x against cx1/cx2. */
static inline int _oc_after_y(int16_t x, int16_t cx1, int16_t cx2)
{
int c = 0;
if (x < cx1) c |= 1;
if (x > cx2) c |= 2;
return c;
}
bool clip_cohen_sutherland(int16_t *x1, int16_t *y1, int16_t *x2, int16_t *y2,
int16_t cx1, int16_t cy1, int16_t cx2, int16_t cy2)
{
int16_t ax = *x1, ay = *y1, bx = *x2, by = *y2;
int ca = _oc(ax, ay, cx1, cy1, cx2, cy2);
int cb = _oc(bx, by, cx1, cy1, cx2, cy2);
float dx = bx - ax, dy = by - ay;
for (;;) {
if ((ca | cb) == 0) { *x1 = ax; *y1 = ay; *x2 = bx; *y2 = by; return true; }
if ((ca & cb) != 0) return false;
int co = ca ? ca : cb;
int16_t x = 0, y = 0;
if (co & 8) { x = ax + (int16_t)(dx * (cy2 - ay) / dy); y = cy2; }
else if (co & 4) { x = ax + (int16_t)(dx * (cy1 - ay) / dy); y = cy1; }
else if (co & 2) { y = ay + (int16_t)(dy * (cx2 - ax) / dx); x = cx2; }
else { y = ay + (int16_t)(dy * (cx1 - ax) / dx); x = cx1; }
if (co == ca) {
dx -= x - ax;
dy -= y - ay;
ax = x; ay = y;
ca = (co & 0xC) ? _oc_after_y(ax, cx1, cx2) : _oc_after_x(ay, cy1, cy2);
} else {
dx -= bx - x;
dy -= by - y;
bx = x; by = y;
cb = (co & 0xC) ? _oc_after_y(bx, cx1, cx2) : _oc_after_x(by, cy1, cy2);
}
}
}
/* ========================================================================== */
/* Cohen-Sutherland: iterative region-code clipping, pure int16_t */
/* Uses frac_mul for the intersection math -- no float operations at all. */
/* ========================================================================== */
bool clip_cohen_sutherland_int16(int16_t *x1, int16_t *y1, int16_t *x2, int16_t *y2,
int16_t cx1, int16_t cy1, int16_t cx2, int16_t cy2)
{
int16_t ax = *x1, ay = *y1, bx = *x2, by = *y2;
int ca = _oc(ax, ay, cx1, cy1, cx2, cy2);
int cb = _oc(bx, by, cx1, cy1, cx2, cy2);
int16_t dx = bx - ax, dy = by - ay;
for (;;) {
if ((ca | cb) == 0) { *x1 = ax; *y1 = ay; *x2 = bx; *y2 = by; return true; }
if ((ca & cb) != 0) return false;
int co = ca ? ca : cb;
int16_t x = 0, y = 0;
if (co & 8) { x = ax + frac_mul(cy2 - ay, dx, dy); y = cy2; }
else if (co & 4) { x = ax + frac_mul(cy1 - ay, dx, dy); y = cy1; }
else if (co & 2) { y = ay + frac_mul(cx2 - ax, dy, dx); x = cx2; }
else { y = ay + frac_mul(cx1 - ax, dy, dx); x = cx1; }
if (co == ca) {
dx -= x - ax;
dy -= y - ay;
ax = x; ay = y;
ca = (co & 0xC) ? _oc_after_y(ax, cx1, cx2) : _oc_after_x(ay, cy1, cy2);
} else {
dx -= bx - x;
dy -= by - y;
bx = x; by = y;
cb = (co & 0xC) ? _oc_after_y(bx, cx1, cx2) : _oc_after_x(by, cy1, cy2);
}
}
}
/* ========================================================================== */
/* Elite-inspired line clipping (BBC Micro, 6502) */
/* Based on the two-stage algorithm from Mark Moxon's Elite deep dive: */
/* https://elite.bbcelite.com/deep_dives/line-clipping.html */
/* */
/* Uses the line equation directly: x = ax + dx*t, y = ay + dy*t. */
/* At an X edge: t = (edge - ax) / dx → Δy = dy * t */
/* At a Y edge: t = (edge - ay) / dy → Δx = dx * t */
/* ========================================================================== */
/* Test whether a point is inside the clip window */
static inline bool elite_inside(int16_t x, int16_t y, int16_t cx1, int16_t cy1,
int16_t cx2, int16_t cy2)
{
return x >= cx1 && x <= cx2 && y >= cy1 && y <= cy2;
}
/* Clip one off-screen endpoint toward the other endpoint.
* A moves toward B, B moves toward A (signs handled by caller).
* Loops to handle secondary clipping after the first axis is fixed. */
static void elite_clip_one(int16_t *ax, int16_t *ay,
int16_t cx1, int16_t cy1,
int16_t cx2, int16_t cy2,
int16_t odx, int16_t ody)
{
for (int iter = 0; iter < 2; iter++) {
int16_t x = *ax, y = *ay;
int did_clip = 0;
if (x < cx1 && odx > 0) {
y += frac_mul(cx1 - x, ody, odx);
x = cx1;
did_clip = 1;
} else if (x > cx2 && odx < 0) {
y += frac_mul(cx2 - x, ody, odx);
x = cx2;
did_clip = 1;
}
if (y < cy1 && ody > 0) {
x += frac_mul(cy1 - y, odx, ody);
y = cy1;
did_clip = 1;
} else if (y > cy2 && ody < 0) {
x += frac_mul(cy2 - y, odx, ody);
y = cy2;
did_clip = 1;
}
*ax = x;
*ay = y;
if (!did_clip) break;
}
}
/* Elite-style two-stage clipping.
* Returns true if any part of the line is visible, false otherwise.
* Source: https://elite.bbcelite.com/deep_dives/line-clipping.html */
bool clip_elite(int16_t *x1, int16_t *y1, int16_t *x2, int16_t *y2,
int16_t cx1, int16_t cy1, int16_t cx2, int16_t cy2)
{
int16_t ax = *x1, ay = *y1, bx = *x2, by = *y2;
bool a_in = elite_inside(ax, ay, cx1, cy1, cx2, cy2);
bool b_in = elite_inside(bx, by, cx1, cy1, cx2, cy2);
/* Fast accept */
if (a_in && b_in) return true;
/* Quick reject: both off-screen on the same side */
int a_code = 0, b_code = 0;
if (ax < cx1) a_code |= 1;
if (ax > cx2) a_code |= 2;
if (ay < cy1) a_code |= 4;
if (ay > cy2) a_code |= 8;
if (bx < cx1) b_code |= 1;
if (bx > cx2) b_code |= 2;
if (by < cy1) b_code |= 4;
if (by > cy2) b_code |= 8;
if ((a_code & b_code) != 0) return false;
int16_t ddx = bx - ax;
int16_t ddy = by - ay;
/* Clip off-screen endpoints */
if (!a_in) elite_clip_one(&ax, &ay, cx1, cy1, cx2, cy2, ddx, ddy);
if (!b_in) elite_clip_one(&bx, &by, cx1, cy1, cx2, cy2, -ddx, -ddy);
/* Final validation */
if (!elite_inside(ax, ay, cx1, cy1, cx2, cy2)) return false;
if (!elite_inside(bx, by, cx1, cy1, cx2, cy2)) return false;
*x1 = ax; *y1 = ay; *x2 = bx; *y2 = by;
return true;
}
+23
View File
@@ -0,0 +1,23 @@
/* lineclip.h - Line clipping algorithms (Cohen-Sutherland, Liang-Barsky, Elite-inspired) */
#ifndef LINECLIP_H
#define LINECLIP_H
#include <stdint.h>
#include <stdbool.h>
/* All coordinates are signed to support off-screen positions */
/* Cohen-Sutherland line clipping (float intersection math) */
bool clip_cohen_sutherland(int16_t *x1, int16_t *y1, int16_t *x2, int16_t *y2,
int16_t clipx1, int16_t clipy1, int16_t clipx2, int16_t clipy2);
/* Cohen-Sutherland line clipping (pure int16_t, uses frac_mul internally) */
bool clip_cohen_sutherland_int16(int16_t *x1, int16_t *y1, int16_t *x2, int16_t *y2,
int16_t clipx1, int16_t clipy1, int16_t clipx2, int16_t clipy2);
/* Elite-inspired two-stage clipping (BBC Micro, 6502) */
/* Based on: https://elite.bbcelite.com/deep_dives/line-clipping.html */
bool clip_elite(int16_t *x1, int16_t *y1, int16_t *x2, int16_t *y2,
int16_t clipx1, int16_t clipy1, int16_t clipx2, int16_t clipy2);
#endif /* LINECLIP_H */
+176
View File
@@ -0,0 +1,176 @@
/* test_clip.c - Console test program for line clipping algorithms */
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "lineclip.h"
/* Clip window: (400,300) to (1600,1400) */
#define CLIP_X1 400
#define CLIP_Y1 300
#define CLIP_X2 1600
#define CLIP_Y2 1400
typedef struct {
int16_t x1, y1, x2, y2;
int16_t ex1, ey1, ex2, ey2;
const char *name;
} TestCase;
static const TestCase tests[] = {
/* Inside the clip window */
{ 800, 600, 1200, 1000, 800, 600, 1200, 1000, "T1 inside" },
{ 420, 320, 500, 400, 420, 320, 500, 400, "T2 near TL" },
{ 1580, 1380, 1600, 1400, 1580, 1380, 1600, 1400, "T3 near BR" },
{ 1000, 850, 1000, 850, 1000, 850, 1000, 850, "T4 point inside" },
/* Completely outside -- far away */
{ -500, -200, -300, 100, 0, 0, 0, 0, "T5 far left" },
{ 2200, 1500, 2500, 1800, 0, 0, 0, 0, "T6 far right" },
{ 800, -500, 1000, -200, 0, 0, 0, 0, "T7 far above" },
{ 1000, 1700, 1200, 2000, 0, 0, 0, 0, "T8 far below" },
{ -300, -300, 200, 100, 0, 0, 0, 0, "T9 far TL" },
{ 1800, 1600, 2200, 1700, 0, 0, 0, 0, "T10 far BR" },
{ -400, 1500, 100, 1800, 0, 0, 0, 0, "T11 far BL" },
{ 1800, -300, 2000, 100, 0, 0, 0, 0, "T12 far TR" },
/* Crossing from outside into window */
{ 200, 800, 600, 800, 400, 800, 600, 800, "T13 enter left" },
{ 1400, 800, 1800, 800, 1400, 800, 1600, 800, "T14 enter right" },
{ 1000, 100, 1000, 500, 1000, 300, 1000, 500, "T15 enter top" },
{ 1000, 1200, 1000, 1600, 1000, 1200, 1000, 1400, "T16 enter bottom" },
/* Diagonals crossing the window */
{ 100, 200, 1800, 1700, 381, 300, 1600, 1425, "T17 diag TL-BR" },
{ 100, 1700, 1800, 200, 381, 1400, 1600, 283, "T18 diag BL-TR" },
{ 1800, 200, 100, 1700, 1600, 283, 381, 1400, "T19 diag TR-BL" },
{ 1800, 1700, 100, 200, 1600, 1425, 381, 300, "T20 diag BR-TL" },
/* Very long lines crossing the world */
{ 0, 1000, 2000, 1000, 400, 1000, 1600, 1000, "T21 horiz world" },
{ 1000, 0, 1000, 2000, 1000, 300, 1000, 1400, "T22 vert world" },
{ 0, 0, 2000, 2000, 400, 400, 1600, 1600, "T23 full world diag" },
{ 2000, 0, 0, 2000, 1600, 400, 400, 1600, "T24 full world diag2" },
/* Shallow diagonals */
{ 200, 800, 1800, 810, 400, 801, 1600, 809, "T25 shallow up" },
{ 200, 900, 1800, 890, 400, 899, 1600, 891, "T26 shallow down" },
/* Steep diagonals */
{ 1000, 100, 1010, 1900, 1000, 300, 1001, 1400, "T27 steep right" },
{ 990, 100, 1000, 1900, 999, 300, 1000, 1400, "T28 steep left" },
/* Long rejects: parallel but far outside */
{ 200, 100, 350, 100, 0, 0, 0, 0, "T29 above parallel" },
{ 200, 1550, 350, 1550, 0, 0, 0, 0, "T30 below parallel" },
{ 100, 800, 100, 1100, 0, 0, 0, 0, "T31 left parallel" },
{ 1800, 800, 1800, 1100, 0, 0, 0, 0, "T32 right parallel" },
/* Extreme coordinates (within int16 range) */
{ 0, 1000, 0, 1001, 0, 0, 0, 0, "T33 x=0 tiny" },
{ 2000, 1000, 2000, 1001, 0, 0, 0, 0, "T34 x=2000 tiny" },
{ 1000, 0, 1001, 0, 0, 0, 0, 0, "T35 y=0 tiny" },
{ 1000, 2000, 1001, 2000, 0, 0, 0, 0, "T36 y=2000 tiny" },
/* Lines grazing the clip window edges */
{ 200, 300, 1800, 300, 400, 300, 1600, 300, "T37 graze top edge" },
{ 200, 1400, 1800, 1400, 400, 1400, 1600, 1400, "T38 graze bottom" },
{ 400, 100, 400, 1600, 400, 300, 400, 1400, "T39 graze left edge" },
{ 1600, 100, 1600, 1600, 1600, 300, 1600, 1400, "T40 graze right edge" },
/* Points on clip window corners */
{ 400, 300, 400, 300, 400, 300, 400, 300, "T41 pt TL corner" },
{ 1600, 300, 1600, 300, 1600, 300, 1600, 300, "T42 pt TR corner" },
{ 400, 1400, 400, 1400, 400, 1400, 400, 1400, "T43 pt BL corner" },
{ 1600, 1400, 1600, 1400, 1600, 1400, 1600, 1400, "T44 pt BR corner" },
/* Lines connecting two distant corners through the window */
{ 0, 0, 2000, 2000, 400, 400, 1600, 1600, "T45 origin to far BR" },
{ 2000, 0, 0, 2000, 1600, 400, 400, 1600, "T46 far TL to far BR" },
/* Short lines just outside each edge */
{ 390, 800, 395, 800, 0, 0, 0, 0, "T47 near left edge" },
{ 1605, 800, 1610, 800, 0, 0, 0, 0, "T48 near right edge" },
{ 1000, 295, 1005, 295, 0, 0, 0, 0, "T49 near top edge" },
{ 1000, 1405, 1005, 1405, 0, 0, 0, 0, "T50 near bottom edge" },
};
#define NUM_TESTS (sizeof(tests) / sizeof(tests[0]))
static bool run_test(const TestCase *tc)
{
int16_t cs_x1 = tc->x1, cs_y1 = tc->y1, cs_x2 = tc->x2, cs_y2 = tc->y2;
int16_t ci_x1 = tc->x1, ci_y1 = tc->y1, ci_x2 = tc->x2, ci_y2 = tc->y2;
int16_t el_x1 = tc->x1, el_y1 = tc->y1, el_x2 = tc->x2, el_y2 = tc->y2;
bool cs_vis = clip_cohen_sutherland(&cs_x1, &cs_y1, &cs_x2, &cs_y2,
CLIP_X1, CLIP_Y1, CLIP_X2, CLIP_Y2);
bool ci_vis = clip_cohen_sutherland_int16(&ci_x1, &ci_y1, &ci_x2, &ci_y2,
CLIP_X1, CLIP_Y1, CLIP_X2, CLIP_Y2);
bool el_vis = clip_elite(&el_x1, &el_y1, &el_x2, &el_y2,
CLIP_X1, CLIP_Y1, CLIP_X2, CLIP_Y2);
bool pass = true;
/* CS-float is the reference. Check visibility agrees. */
if (ci_vis != cs_vis || el_vis != cs_vis) {
printf(" VISIBILITY MISMATCH: CS=%d CSi=%d Elite=%d\n",
cs_vis, ci_vis, el_vis);
pass = false;
}
/* CS-int16 must match CS-float within ±1 pixel. */
if (cs_vis) {
if (ci_x1 < cs_x1 - 1 || ci_x1 > cs_x1 + 1 ||
ci_y1 < cs_y1 - 1 || ci_y1 > cs_y1 + 1 ||
ci_x2 < cs_x2 - 1 || ci_x2 > cs_x2 + 1 ||
ci_y2 < cs_y2 - 1 || ci_y2 > cs_y2 + 1) {
printf(" CSi!=CS: (%d,%d)-(%d,%d) vs (%d,%d)-(%d,%d)\n",
ci_x1, ci_y1, ci_x2, ci_y2, cs_x1, cs_y1, cs_x2, cs_y2);
pass = false;
}
}
/* Elite must match CS within ±2 pixels (integer truncation vs float rounding). */
if (cs_vis) {
if (el_x1 < cs_x1 - 2 || el_x1 > cs_x1 + 2 ||
el_y1 < cs_y1 - 2 || el_y1 > cs_y1 + 2 ||
el_x2 < cs_x2 - 2 || el_x2 > cs_x2 + 2 ||
el_y2 < cs_y2 - 2 || el_y2 > cs_y2 + 2) {
printf(" Elite!=CS: (%d,%d)-(%d,%d) vs (%d,%d)-(%d,%d)\n",
el_x1, el_y1, el_x2, el_y2, cs_x1, cs_y1, cs_x2, cs_y2);
pass = false;
}
/* Elite results MUST be strictly inside the clip window. */
if (el_x1 < CLIP_X1 || el_x1 > CLIP_X2 ||
el_y1 < CLIP_Y1 || el_y1 > CLIP_Y2 ||
el_x2 < CLIP_X1 || el_x2 > CLIP_X2 ||
el_y2 < CLIP_Y1 || el_y2 > CLIP_Y2) {
printf(" Elite OUTSIDE window: (%d,%d)-(%d,%d) window=(%d,%d)-(%d,%d)\n",
el_x1, el_y1, el_x2, el_y2, CLIP_X1, CLIP_Y1, CLIP_X2, CLIP_Y2);
pass = false;
}
}
printf("%s: %s\n", tc->name, pass ? "PASS" : "FAIL");
return pass;
}
int main(void)
{
int passed = 0, failed = 0;
printf("Line clipping algorithm tests\n");
printf("Clip window: (%d,%d)-(%d,%d)\n\n", CLIP_X1, CLIP_Y1, CLIP_X2, CLIP_Y2);
for (int i = 0; i < (int)NUM_TESTS; i++) {
if (run_test(&tests[i])) {
passed++;
} else {
failed++;
}
}
printf("\n%d passed, %d failed out of %d tests\n",
passed, failed, (int)NUM_TESTS);
return failed > 0 ? 1 : 0;
}