shithub: etoys

ref: 5633da27ae4132550959fc7135f7d19afa519a70
dir: etoys/lineXcircle.c

View raw version
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <geometry.h>

enum {
	PCBg,
	PCFg,
	PCLine,
	PCLineseg,
	PCCin,
	PCCout,
	PCPt,
	NCOLORS
};

Rectangle UR = {0,0,1,1};	/* unit rectangle */
RFrame worldrf;
Image *pal[NCOLORS];
Point2 theline[2], thecircle[2];
int npts;
Point2 Xpts[2];
int nXpts;

void resized(void);


Point
toscreen(Point2 p)
{
	p = invrframexform(p, worldrf);
	return Pt(p.x,p.y);
}

Point2
fromscreen(Point p)
{
	return rframexform(Pt2(p.x,p.y,1), worldrf);
}

double
sgn(double n)
{
	return n < 0? -1: 1;
}

int
lineXcircle(Point2 *r)
{
	Point2 p0, p1, dp;
	double dr, D, cr, Δ;

	p0 = subpt2(theline[0], thecircle[0]);
	p1 = subpt2(theline[1], thecircle[0]);
	dp = subpt2(p1, p0);
	dr = vec2len(dp);
	D = p0.x*p1.y - p0.y*p1.x;
	cr = vec2len(subpt2(thecircle[1], thecircle[0]));
	Δ = cr*cr*dr*dr - D*D;

	if(Δ < 0)		/* imaginary */
		return 0;
	else if(Δ == 0){	/* tangent */
		r[0].x = (D*dp.y + sgn(dp.y)*dp.x*sqrt(Δ))/(dr*dr);
		r[0].y = (-D*dp.x + fabs(dp.y)*sqrt(Δ))/(dr*dr);
		r[0] = addpt2(r[0], thecircle[0]);
		return 1;
	}else{			/* secant */
		r[0].x = (D*dp.y + sgn(dp.y)*dp.x*sqrt(Δ))/(dr*dr);
		r[0].y = (-D*dp.x + fabs(dp.y)*sqrt(Δ))/(dr*dr);
		r[0] = addpt2(r[0], thecircle[0]);
		r[1].x = (D*dp.y - sgn(dp.y)*dp.x*sqrt(Δ))/(dr*dr);
		r[1].y = (-D*dp.x - fabs(dp.y)*sqrt(Δ))/(dr*dr);
		r[1] = addpt2(r[1], thecircle[0]);
		return 2;
	}
}

void
initpalette(void)
{
	pal[PCBg] = allocimage(display, UR, screen->chan, 1, DWhite);
	pal[PCFg] = allocimage(display, UR, screen->chan, 1, DBlack);
	pal[PCLine] = allocimage(display, UR, screen->chan, 1, 0xEEEEEEFF);
	pal[PCLineseg] = allocimage(display, UR, screen->chan, 1, DDarkblue);
	pal[PCCin] = allocimage(display, UR, screen->chan, 1, DPalebluegreen);
	pal[PCCout] = allocimage(display, UR, screen->chan, 1, DDarkyellow);
	pal[PCPt] = allocimage(display, UR, screen->chan, 1, DRed);
}

void
drawline(void)
{
	Point pts[2], dp;
	int i;

	for(i = 0; i < nelem(pts); i++) pts[i] = toscreen(theline[i]);
	dp = subpt(pts[1], pts[0]);

	line(screen, pts[0], pts[1], 0, 0, 1, pal[PCLineseg], ZP);
	for(i = 0; i < nelem(pts); i++){
		line(screen, pts[i], addpt(mulpt(dp, i == 0? -10: 10), pts[i]), 0, 0, 0, pal[PCLine], ZP);
		fillellipse(screen, pts[i], 2, 2, pal[PCPt], ZP);
	}
}

void
drawcircle(void)
{
	Point p;
	double r;

	p = toscreen(thecircle[0]);
	r = vec2len(subpt2(thecircle[1], thecircle[0]));

	fillellipse(screen, p, r, r, pal[PCCin], ZP);
	ellipse(screen, p, r, r, 0, pal[PCCout], ZP);
	fillellipse(screen, p, 2, 2, pal[PCPt], ZP);
}

void
drawX(void)
{
	int i;

	if(nXpts < 1)
		return;

	for(i = 0; i < nelem(Xpts); i++)
		fillellipse(screen, toscreen(Xpts[i]), 2, 2, pal[PCPt], ZP);
}

void
redraw(void)
{
	lockdisplay(display);
	draw(screen, screen->r, pal[PCBg], nil, ZP);
	if(npts >= 4){
		drawcircle();
		drawX();
	}
	if(npts >= 2)
		drawline();
	flushimage(display, 1);
	unlockdisplay(display);
}

void
rmb(Mousectl *mc)
{
	USED(mc);
}

void
lmb(Mousectl *mc)
{
	if(npts >= 4)
		npts = 0;
	if(npts >= 0 && npts < 2)
		theline[npts++] = fromscreen(mc->xy);
	else if(npts >= 2 && npts < 4)
		thecircle[npts++ & 1] = fromscreen(mc->xy);
	if(npts >= 4)
		nXpts = lineXcircle(Xpts);
}

void
mouse(Mousectl *mc)
{
	static Mouse om;

	if(mc->buttons == om.buttons)
		return;

	if((mc->buttons&1) != 0)
		lmb(mc);
	if((mc->buttons&4) != 0)
		rmb(mc);
	om = mc->Mouse;
}

void
key(Rune r)
{
	switch(r){
	case Kdel:
	case 'q':
		threadexitsall(nil);
	}
}

void
usage(void)
{
	fprint(2, "usage: %s\n", argv0);
	exits("usage");
}

void
threadmain(int argc, char *argv[])
{
	Mousectl *mc;
	Keyboardctl *kc;
	Rune r;

	ARGBEGIN{
	default: usage();
	}ARGEND;
	if(argc > 0)
		usage();

	if(initdraw(nil, nil, nil) < 0)
		sysfatal("initdraw: %r");
	if((mc = initmouse(nil, screen)) == nil)
		sysfatal("initmouse: %r");
	if((kc = initkeyboard(nil)) == nil)
		sysfatal("initkeyboard: %r");
	initpalette();

	worldrf.p = Pt2(screen->r.min.x,screen->r.min.y,1);
	worldrf.bx = Vec2(1,0);
	worldrf.by = Vec2(0,1);

	display->locking = 1;
	unlockdisplay(display);
	redraw();

	for(;;){
		enum { MOUSE, RESIZE, KEYBOARD };
		Alt a[] = {
			{mc->c, &mc->Mouse, CHANRCV},
			{mc->resizec, nil, CHANRCV},
			{kc->c, &r, CHANRCV},
			{nil, nil, CHANEND}
		};

		switch(alt(a)){
		case MOUSE:
			mouse(mc);
			break;
		case RESIZE:
			resized();
			break;
		case KEYBOARD:
			key(r);
			break;
		}

		redraw();
	}
}

void
resized(void)
{
	lockdisplay(display);
	if(getwindow(display, Refnone) < 0)
		sysfatal("couldn't resize");
	unlockdisplay(display);
	worldrf.p = Pt2(screen->r.min.x,screen->r.min.y,1);
	redraw();
}