diff options
Diffstat (limited to 'src/draw/line.5c')
| -rw-r--r-- | src/draw/line.5c | 389 | 
1 files changed, 389 insertions, 0 deletions
| diff --git a/src/draw/line.5c b/src/draw/line.5c new file mode 100644 index 00000000..747768b0 --- /dev/null +++ b/src/draw/line.5c @@ -0,0 +1,389 @@ +#!/usr/bin/nickle + +autoimport Cairo; +autoload PRNG; + +int +sign(int x) +{ +	return x == 0 ? 0 : x < 0 ? -1 : 1; +} + +int X_AXIS = 0; +int Y_AXIS = 1; + +typedef struct { +	int	major; +	int	minor; +	int	sign_major; +	int	sign_minor; +	int	e; +	int	e1; +	int	e3; +	bool	first; +} clip_context; + +typedef struct { +	int	maj1, min1, maj2, min2; +} clip_box; + +typedef struct { +	int	x1, y1, x2, y2; +} box; + +typedef struct { +	int	x, y; +} point; + +typedef struct { +	int	x1, y1, x2, y2; +	box	b; +	point[]	clipped; +	point[]	run; +} test; + +box	bounds = { .x1 = 10, .x2 = 30, .y1 = 10, .y2 = 30 }; + +int +div_ceil(a, b) { +	a += b; +	assert(a >= 0 && b > 0, "bad divide args %d %d\n", a, b); +	return (a + b - 1) // b - 1; +} + +int +div_floor_plus_one(a, b) { +	a += b; +	assert(a >= 0 && b > 0, "bad divide args %d %d\n", a, b); +	return a // b; +} + +bool +clip(*clip_context c, *clip_box b) +{ +	int	adjust_major = 0, adjust_minor = 0; + +	/* Clip major axis */ +	if (c->major < b->maj1) { +		if (c->sign_major <= 0) +			return false; +		adjust_major = b->maj1 - c->major; +	} else if (c->major >= b->maj2) { +		if (c->sign_major >= 0) +			return false; +		adjust_major = c->major - (b->maj2-1); +	} + +	/* Clip minor axis */ +	if (c->minor < b->min1) { +		if (c->sign_minor <= 0) +			return false; +		adjust_minor = b->min1 - c->minor; +	} else if (c->minor >= b->min2) { +		if (c->sign_minor >= 0) +			return false; +		adjust_minor = c->minor - (b->min2-1); +	} + +	/* If unclipped, we're done */ +	if (adjust_major == 0 && adjust_minor == 0) +		return true; + +	/* See how much minor adjustment would happen during +	 * a major clip. This is a bit tricky because line drawing +	 * isn't symmetrical when the line passes exactly between +	 * two pixels, we have to pick which one gets drawn +	 */ +	int	adj_min; + +	if (!c->first) +		adj_min = div_ceil(c->e + adjust_major * c->e1, -c->e3); +	else +		adj_min = div_floor_plus_one(c->e + adjust_major * c->e1, -c->e3); + +	/* Compare that to the minor clip and pick +	 * the larger amount. +	 */ +	printf ("\tinitial major %d minor %d error %d e1 %d e3 %d\n", c->major, c->minor, c->e, c->e1, c->e3); + +	if (adj_min < adjust_minor) { +		printf("\tminor clip dominates %d < %d. adjust major %d -> ", +		       adj_min, adjust_minor, adjust_major); +		if (c->first) +			adjust_major = div_ceil(c->e - adjust_minor * c->e3, c->e1); +		else +			adjust_major = div_floor_plus_one(c->e - adjust_minor * c->e3, c->e1); +		printf("%d\n", adjust_major); +	} else { +		printf("\tminor clip dominates %d > %d. adjust minor %d -> ", +		       adj_min, adjust_minor, adjust_minor); +		adjust_minor = adj_min; +		printf("%d\n", adjust_minor); +	} + +	c->e += adjust_major * c->e1 + adjust_minor * c->e3; + +	c->major += c->sign_major * adjust_major; +	c->minor += c->sign_minor * adjust_minor; + +	printf ("\tadjust major %d adjust minor %d e %d e1 %d e3 %e\n", +		adjust_major, adjust_minor, c->e, c->e1, c->e3); + +	if (c->e >= 0) +		printf ("error positive e %d e1 %d e3 %d\n", +			c->e, c->e1, c->e3); +	if (c->e < c->e3) +		printf ("error magnitude too large e %d e1 %d e3 %d\n", c->e, c->e1, c->e3); + +	return true; +} + +test +line(int x1, int y1, int x2, int y2, *box b) { + +	int	dx = x2 - x1; +	int	dy = y2 - y1; +	int	signdx = sign(dx); +	int	signdy = sign(dy); +	int	adx = abs(dx); +	int	ady = abs(dy); +	int	axis; +	int	e, e1, e2, e3; +	int	len; +	clip_context	clip_1, clip_2; +	clip_box	c; +	bool		clipped = false; +	test		t = { +		.x1 = x1, +		.y1 = y1, +		.x2 = x2, +		.y2 = y2, +		.b = *b, +		.clipped = (point[...]) {}, +		.run = (point[...]) {} +	}; + +	if (adx >= ady) { +		axis = X_AXIS; +		e1 = ady << 1; +		e2 = e1 - (adx << 1); +		e = e1 - adx; +		len = adx; + +		clip_1.major = x1; +		clip_1.minor = y1; +		clip_2.major = x2; +		clip_2.minor = y2; +		clip_1.sign_major = signdx; +		clip_1.sign_minor = signdy; + +		c.maj1 = b->x1; +		c.maj2 = b->x2; +		c.min1 = b->y1; +		c.min2 = b->y2; +	} else { +		axis = Y_AXIS; +		e1 = adx << 1; +		e2 = e1 - (ady << 1); +		e = e1 - ady; +		len = ady; + +		clip_1.major = y1; +		clip_1.minor = x1; +		clip_2.major = y2; +		clip_2.minor = x2; +		clip_1.sign_major = signdy; +		clip_1.sign_minor = signdx; +		c.maj1 = b->y1; +		c.maj2 = b->y2; +		c.min1 = b->x1; +		c.min2 = b->x2; +	} + +	e3 = e2 - e1; +	e = e - e1; + +	clip_1.first = true; +	clip_2.first = false; +	clip_2.e = clip_1.e = e; +	clip_2.e1 = clip_1.e1 = e1; +	clip_2.e3 = clip_1.e3 = e3; +	clip_2.sign_major = -clip_1.sign_major; +	clip_2.sign_minor = -clip_1.sign_minor; + +	printf ("clip start:\n"); +	if (!clip(&clip_1, &c)) +		clipped = true; + +	printf("clip end:\n"); +	if (!clip(&clip_2, &c)) +		clipped = true; + +	int	clip_len; +	int	clip_x, clip_y; +	int	clip_e; +	int	x_major, x_minor; +	int	y_major, y_minor; + +	clip_len = clip_1.sign_major * (clip_2.major - clip_1.major); +	if (clip_len < 0) +		clipped = true; + +	int x, y; + +	if (axis == X_AXIS) { +		x = clip_1.major; +		y = clip_1.minor; +		x_major = clip_1.sign_major; +		x_minor = 0; +		y_major = 0; +		y_minor = clip_1.sign_minor; +	} else { +		x = clip_1.minor; +		y = clip_1.major; +		x_major = 0; +		x_minor = clip_1.sign_minor; +		y_major = clip_1.sign_major; +		y_minor = 0; +	} + +	clip_e = clip_1.e; + +	if (clipped) +		clip_len = -1; + +	while (clip_len-- >= 0) { +		t.clipped[dim(t.clipped)] = (point) { .x = x, .y = y }; +		x += x_major; +		y += y_major; +		clip_e += e1; +		if (clip_e >= 0) { +			x += x_minor; +			y += y_minor; +			clip_e += e3; +		} +	} + +	x = x1; +	y = y1; + +	while (len-- >= 0) { +		if (bounds.x1 <= x && x < bounds.x2 && +		    bounds.y1 <= y && y < bounds.y2) { +			t.run[dim(t.run)] = (point) { .x = x, .y = y }; +		} +		x += x_major; +		y += y_major; +		e += e1; +		if (e >= 0) { +			x += x_minor; +			y += y_minor; +			e += e3; +		} +	} +	return t; +} + +void read_events (Cairo::cairo_t cr) +{ +	file	event = Cairo::open_event(cr); + +	while (!File::end(event)) { +		string	event_line = File::fgets(event); +		if (String::index(event_line, "delete") >= 0) +			exit(0); +	} +} + +#for (int y = 0; y < 20; y++) + +void +show(cairo_t cr, test t) +{ +	rectangle(cr, 0, 0, 40, 40); +	set_source_rgba(cr, 1, 1, 1, 1); +	fill(cr); + +	set_source_rgba(cr, 0, 1, 0, .2); +	set_line_width(cr, 0.1); +	for (int x = 0; x < 40; x++) { +		move_to(cr, 0, x); +		line_to(cr, 40, x); +		move_to(cr, x, 0); +		line_to(cr, x, 40); +	} +	stroke(cr); + +	rectangle(cr, t.b.x1, t.b.y1, t.b.x2 - t.b.x1, t.b.y2 - t.b.y1); +	set_line_width(cr, 0.1); +	set_source_rgba(cr, 0, 0, 0, 1); +	stroke(cr); + +	move_to(cr, t.x1+.5, t.y1+.5); +	line_to(cr, t.x2+.5, t.y2+.5); +	move_to(cr, t.x2, t.y2); +	line_to(cr, t.x2+1, t.y2+1); +	move_to(cr, t.x2+1, t.y2); +	line_to(cr, t.x2, t.y2+1); +	stroke(cr); + +	void pixels(point[] pt) { +		for (int i = 0; i < dim(pt); i++) { +			rectangle(cr, pt[i].x, pt[i].y, 1, 1); +		} +		fill(cr); +	} + +	set_source_rgba(cr, 1, 0, 0, .5); +	pixels(t.clipped); + +	set_source_rgba(cr, 0, 0, 1, .5); +	pixels(t.run); +} + +bool +compare(test t) +{ +	if (dim(t.clipped) != dim(t.run)) +		return false; + +	for (int i = 0; i < dim(t.clipped); i++) +		if (t.clipped[i] != t.run[i]) +			return false; +	return true; +} + +void +doit(int i) +{ +	int	n; +	*box	b = &bounds; + +	cairo_t cr = new(800, 800); + +	scale(cr, 20, 20); + +	for (;;) { +		PRNG::srandom(i); +		int	x1 = PRNG::randint(40); +		int	x2 = PRNG::randint(40); +		int	y1 = PRNG::randint(40); +		int	y2 = PRNG::randint(40); + +		test t = line (x1, y1, x2, y2, &bounds); +		show(cr, t); +		if (!compare(t)) { +			printf("line %d -- %d x %d - %d x %d\n", i, x1, y1, x2, y2); +			gets(); +		} +		i++; +	} + +	read_events(cr); +} + +int i = 0; +if (dim(argv) > 1) +	i = atoi(argv[1]); + +doit(i); | 
