shithub: arcs

ref: d64cf75cf23432793f4daad8e50961b9071bb44e
dir: /arcs.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <memdraw.h>

#define INTPATH

void
usage(void)
{
	fprint(2, "usage: [-c] [-x width] [-h toolliftheight] [-z tooldrawheight] %s\n", argv0);
	exits("usage");
}

typedef struct Position Position;
struct Position {
	float x;
	float y;
};

Position
Pos(float x, float y)
{
	Position r;
	r.x = x;
	r.y = y;
	return r;
}

void
Psortx(Position *a, Position *b)
{
	Position tmp;
	if (a->x <= b->x)
		return;
	tmp = *a;
	*a = *b;
	*b = tmp;
}

void
Psorty(Position *a, Position *b)
{
	Position tmp;
	if (a->y <= b->y)
		return;
	tmp = *a;
	*a = *b;
	*b = tmp;
}

int
Poseq(Position a, Position b)
{
	return a.x == b.x && a.y == b.y;
}

float
Pdist(Position a, Position b)
{
	return fabs(a.x - b.x) + fabs(a.y - b.y);
}

void
Pswiz(Position *a)
{
	float t;
	t = a->x;
	a->x = a->y;
	a->y = t;
}

Biobuf *bout;

float power = 1.0;
float toolliftheight = 5.;
float tooldrawheight = 2.;
float width = 100.;    /* default 10cm */
float radscale = 10.;  /* default 1cm */
Position offset = { 5., 5. };  /* default offset 5mm */

float scale; /* calculated from width */

float travelspeed = 250.; /* ender 5 max speed */
float drawspeed = 100.; /* guessed */
float zspeed = 50.; /* guessed */

float minx, maxx;
float miny, maxy;

int quiet = 0;
int printinfo = 0;
int numarcs = 0;
int numcircles = 0;
float traveldistance = 0.;
float drawdistance = 0.;
float zdistance = 0.;

Position lastpos = {0., 0.};

void
printheader(void)
{
	Bprint(bout, "; generated by arcs\n");
	Bprint(bout, "M203 X%g Y%g Z%g ; draw speed\n",
		drawspeed, drawspeed, zspeed);
	Bprint(bout, "G28 ; home\n");
	Bprint(bout, "G90 ; absolute positions\n");
	Bprint(bout, "G01 Z%g\n", toolliftheight);
}

float
rgb2val(uchar *abgr)
{
	float r, g, b;
	float rd, m;
	r = abgr[3] / 256.;
	r *= 0.2126;
	g = abgr[2] / 256.;
	g *= 0.7152;
	b = abgr[1] / 256.;
	b *= 0.0722;
	
	//return r + g + b;
	
	m = pow(r + g + b, power);
	
	/* dithering: cheap random dither for now */
	rd = nrand(256) / 255.;
	
	return rd > m ? 1. : 0.;
}

float a2rad(uchar a)
{
	return a / 255. * 2. - 1.;
}

int
calcarcs(Position o, float r, float y, Position *x1, Position *x2, int inv)
{
	float ds;
	float d;
	
	if (r < 0.)
		r *= -1.;
	
	if (inv)
		d = -(o.x * o.x) + 2*o.x*y + r*r - y*y;
	else
		d = -(o.y * o.y) + 2*o.y*y + r*r - y*y;
	
	if (d < 0)
		return 0;
	
	ds = sqrt(d);
	if (inv) {
		x1->x = y;
		x1->y = o.y + ds;
	} else {
		x1->x = o.x + ds;
		x1->y = y;
	}
	if (d == 0) {
		//fprint(2, "cut: tangent %g, %g (y=%g; d=%g)\n", o.x, o.y, y, d);
		return 1;
	}
	
	if (inv) {
		x2->x = y;
		x2->y = o.y - ds;
	} else {
		x2->x = o.x - ds;
		x2->y = y;
	}
	//fprint(2, "cut: secant %g, %g (y=%g; d=%g)\n", o.x, o.y, y, d);
	return 2;
}

int
inbox(Position p)
{
	if (p.x < minx)
		return 0;
	if (p.x > maxx)
		return 0;
	if (p.y < miny)
		return 0;
	if (p.y > maxy)
		return 0;
	return 1;
}

int
inboxarc(Position p, Position q)
{
	// TODO FIXME: top y border is weird (no new arcs started in that area)
	if (p.x == q.x && p.y == q.y)
		return inbox(p);
	if (p.x >= maxx && q.x >= maxx)
		if (p.y < q.y)
			return 0;
	if (p.x <= minx && q.x <= minx)
		if (p.y > q.y)
			return 0;
	if (p.y >= maxy && q.y >= maxy)
		if (p.x > q.x)
			return 0;
	if (p.y <= miny && q.y <= miny)
		if (p.x < q.x)
			return 0;
	return inbox(p); // needed? otherwise, 1
}

void
checknan(Position p, Position o, char *s)
{
	if (isNaN(p.x))
		fprint(2, "%s.x=NaN: %g, %g\n", s, o.x, o.y);
	if (isNaN(p.y))
		fprint(2, "%s.y=NaN; %g, %g\n", s, o.x, o.y);
}

int
genarcs(Position o, float r, Position *ps)
{
	Position x1, x2, x3, x4, y1, y2, y3, y4;
	int nx1, nx2, ny1, ny2;
	int i = 0;
	float or = r;
	if (r < 0.)
		r *= -1.;
	
	nx1 = calcarcs(o, r, maxy, &x1, &x2, 0);
	nx2 = calcarcs(o, r, miny, &x3, &x4, 0);
	ny1 = calcarcs(o, r, maxx, &y1, &y2, 1);
	ny2 = calcarcs(o, r, minx, &y3, &y4, 1);
	
	switch (nx1) {
	case 0:
		x1 = Pos(o.x, o.y + r);
	case 1:
		x2 = x1;
	}
	
	switch (nx2) {
	case 0:
		x3 = Pos(o.x, o.y - r);
	case 1:
		x4 = x3;
	}
	
	switch (ny1) {
	case 0:
		y1 = Pos(o.x + r, o.y);
	case 1:
		y2 = y1;
	}
	
	switch (ny2) {
	case 0:
		y3 = Pos(o.x - r, o.y);
	case 1:
		y4 = y3;
	}
	
	checknan(x1, o, "x1");
	checknan(x2, o, "x2");
	checknan(x3, o, "x3");
	checknan(x4, o, "x4");
	checknan(y1, o, "y1");
	checknan(y2, o, "y2");
	checknan(y3, o, "y3");
	checknan(y4, o, "y4");
	
	Psortx(&x1, &x2);
	Psortx(&x3, &x4);
	Psorty(&y2, &y1);
	Psorty(&y4, &y3);
	
	/*
	       x1   x2
	    +---x----x---+
	    |            |
	y3  x            x y1
	    |      .     |
	    |            |
	y4  x            x y2
	    |            |
	    +---x----x---+
	0,0    x3   x4
	
	*/
	
	if (or > 0.) {
		if (inboxarc(y1, x2)) {
			ps[i++] = y1;
			ps[i++] = x2;
		}
		if (inboxarc(x1, y3)) {
			ps[i++] = x1;
			ps[i++] = y3;
		}
	} else {
		if (inboxarc(y4, x3)) {
			ps[i++] = y4;
			ps[i++] = x3;
		}
		if (inboxarc(x4, y2)) {
			ps[i++] = x4;
			ps[i++] = y2;
		}
	}
	
	return i;
}

void
checkpos(Position p)
{
	if (p.x < minx)
		fprint(2, "Position out of range x=%g < %g\n", p.x, minx);
	if (p.x > maxx)
		fprint(2, "Position out of range x=%g > %g\n", p.x, maxx);
	if (p.y < miny)
		fprint(2, "Position out of range y=%g < %g\n", p.x, miny);
	if (p.y > maxy)
		fprint(2, "Position out of range y=%g > %g\n", p.x, maxy);
}

void
writearc(int x, int y, uchar *abgr)
{
	float radius;
	Position p;
	Position points[8];
	int npoints;
	float h = rgb2val(abgr);
	
	if (h < 0.5)
		return;
	
	p.x = x * scale + offset.x;
	p.y = y * scale + offset.y;
	
	radius = a2rad(abgr[0]) * scale * radscale;
	
	if (!quiet) {
		int ispos = 0; /* skip z movement if not needed */
		
		Bprint(bout, "; PX %d, %d: %d\n", x, y, abgr[0]);
		// TODO: back and forth mode: for fewer travel paths
		
		npoints = genarcs(p, radius, points);
		for (int i = 0; i < npoints; i += 2) {
			Position from;
			Position to;
			Position lookahead;
			int hasla;
			from = points[i];
			to = points[i+1];
			
			checkpos(from);
			checkpos(to);
			
			hasla = i+2 < npoints;
			if (hasla)
				lookahead = points[i+2];
			
			if (!ispos) {
				Bprint(bout, "G01 X%f Y%f F%f\n", from.x, from.y, travelspeed);
				Bprint(bout, "G01 Z%f\n", tooldrawheight);
				
				traveldistance += Pdist(lastpos, from);
				zdistance += abs(tooldrawheight - toolliftheight);
			}
			Bprint(bout, "G03 X%f Y%f R%f ; %d\n", to.x, to.y, radius, i);
			numarcs++;
			// TODO: drawdistance
			
			/* skip z movement if not needed */
			if (hasla && !Poseq(lookahead, to)) {
				Bprint(bout, "G01 Z%f\n", toolliftheight);
				zdistance += abs(tooldrawheight - toolliftheight);
			}
			
			ispos = hasla && Poseq(lookahead, to);
		}
		
		Bprint(bout, "G01 Z%f\n", toolliftheight);
		// TODO: only add if needed
		zdistance += abs(tooldrawheight - toolliftheight);
	}
	
	if (!printinfo)
		return;
	
	numcircles++;
	lastpos = p;
	// TODO: replace with arc calculation
	drawdistance += 2 * PI * radius;
}

#pragma varargck type "X" double
int
fmttime(Fmt *f)
{
	double seconds;
	int hours;
	int minutes;
	
	seconds = va_arg(f->args, double);
	
	hours = seconds / 3600;
	seconds -= hours * 3600;
	minutes = seconds / 60;
	seconds -= minutes * 60;
	return fmtprint(f, "%dh %dm %.2fs", hours, minutes, seconds);
}

float
printstat(int fd, char *name, float distance, float time)
{
	fprint(fd, "%10s: %f (est: %X)\n", name, distance, time);
	return time;
}

void
main(int argc, char **argv)
{
	Memimage *img;
	int readcompressed = 0;
	uchar *b;
	float f;
	
	double t;
	double loadtime;
	double proctime;
	
	ARGBEGIN{
	case 'c':
		readcompressed++;
		break;
	case 'x':
		width = atof(EARGF(usage()));
		break;
	case 'p':
		power = atof(EARGF(usage()));
		break;
	case 'r':
		radscale = atof(EARGF(usage()));
		break;
	case 'h':
		toolliftheight = atof(EARGF(usage()));
		break;
	case 'z':
		tooldrawheight = atof(EARGF(usage()));
		break;
	case 'i':
		printinfo++;
		break;
	case 'q':
		quiet++;
		break;
	default:
		usage();
	}ARGEND;
	
	t = cputime();
	
	if (memimageinit())
		sysfatal("%r");
	
	img = readcompressed ? creadmemimage(0) : readmemimage(0);
	if (!img)
		sysfatal("cannot read memimage: %r");
	
	loadtime = cputime() - t;
	
	if (img->chan != RGBA32)
		sysfatal("unsupported image format");
	
	bout = Bfdopen(1, OWRITE);
	if (!bout)
		sysfatal("%r");
	
	fmtinstall('X', fmttime);
	
	scale = width / Dx(img->r);
	
	if (!quiet)
		printheader();
	
	minx = img->r.min.x + offset.x;
	maxx = img->r.max.x + offset.x;
	miny = img->r.min.y + offset.y;
	maxy = img->r.max.y + offset.y;
	
	t = cputime();
	
	for (ulong x = img->r.min.x; x < img->r.max.x; x++) {
		for (ulong y = img->r.min.y; y < img->r.max.y; y++) {
			b = byteaddr(img, Pt(x, y));
			writearc(x, y, b);
		}
	}
	
	proctime = cputime() - t;
	
	if (printinfo) {
		fprint(2, "statistics:\n\n");
		f = 0.;
		fprint(2, "%10s: %X\n", "loadtime", loadtime);
		fprint(2, "%10s: %X\n", "proctime", proctime);
		fprint(2, "\n");
		fprint(2, "%10s: %d\n", "numcircles", numcircles);
		fprint(2, "%10s: %d\n", "numarcs", numarcs);
		f += printstat(2, "travel", traveldistance,
			traveldistance / travelspeed);
		f += printstat(2, "draw", drawdistance,
			drawdistance / drawspeed);
		f += printstat(2, "zdist", zdistance,
			zdistance / zspeed);
		fprint(2, "\n%10s: %X\n", "total", f);
	}
	exits(nil);
}