ref: 1600d38f2374f2418c124feec565f50cf7c8f7c4
dir: /linetiler.c/
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <geometry.h>
#define CLAMP(n, min, max) ((n)<(min)?(min):(n)>(max)?(max):(n))
enum {
CLIPL = 1,
CLIPR = 2,
CLIPT = 4,
CLIPB = 8,
};
enum {
PCBg0,
PCBg1,
PCFg0,
PCFg1,
PCAux,
NCOLORS
};
typedef struct Line Line;
struct Line
{
Point2 pts[2];
};
Rectangle UR = {0,0,1,1}; /* unit rectangle */
RFrame worldrf;
Image *pal[NCOLORS];
Rectangle *tiles;
int ntiles;
Line *lines;
int nlines;
Line theline;
Point2 thepoint;
Point2 pts[2];
int npts;
uint paloff;
int thickness;
int showtheline;
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);
}
static void
swapi(int *a, int *b)
{
int t;
t = *a;
*a = *b;
*b = t;
}
static void
swappt(Point *a, Point *b)
{
Point t;
t = *a;
*a = *b;
*b = t;
}
static int
ptisinside(int code)
{
return !code;
}
static int
lineisinside(int code0, int code1)
{
return !(code0|code1);
}
static int
lineisoutside(int code0, int code1)
{
return code0 & code1;
}
static int
outcode(Point p, Rectangle r)
{
int code;
code = 0;
if(p.x < r.min.x) code |= CLIPL;
if(p.x > r.max.x) code |= CLIPR;
if(p.y < r.min.y) code |= CLIPT;
if(p.y > r.max.y) code |= CLIPB;
return code;
}
/*
* Cohen-Sutherland rectangle-line clipping
*/
int
rectclipline(Rectangle r, Point *p0, Point *p1)
{
int code0, code1;
int Δx, Δy;
double m;
Δx = p1->x - p0->x;
Δy = p1->y - p0->y;
m = Δx == 0? 0: (double)Δy/Δx;
for(;;){
code0 = outcode(*p0, r);
code1 = outcode(*p1, r);
if(lineisinside(code0, code1))
return 0;
else if(lineisoutside(code0, code1))
return -1;
if(ptisinside(code0)){
swappt(p0, p1);
swapi(&code0, &code1);
}
if(code0 & CLIPL){
p0->y += (r.min.x - p0->x)*m;
p0->x = r.min.x;
}else if(code0 & CLIPR){
p0->y += (r.max.x - p0->x)*m;
p0->x = r.max.x;
}else if(code0 & CLIPT){
if(p0->x != p1->x && m != 0)
p0->x += (r.min.y - p0->y)/m;
p0->y = r.min.y;
}else if(code0 & CLIPB){
if(p0->x != p1->x && m != 0)
p0->x += (r.max.y - p0->y)/m;
p0->y = r.max.y;
}
}
}
void
addline(Line l)
{
lines = realloc(lines, ++nlines*sizeof(*lines));
lines[nlines-1] = l;
}
void
initpalette(void)
{
pal[PCBg0] = allocimage(display, UR, screen->chan, 1, DWhite);
pal[PCBg1] = allocimage(display, UR, screen->chan, 1, 0x333333FF);
pal[PCFg0] = pal[PCBg1];
pal[PCFg1] = pal[PCBg0];
pal[PCAux] = allocimage(display, UR, screen->chan, 1, DRed);
}
void
redraw(void)
{
int i;
lockdisplay(display);
for(i = 0; i < ntiles; i++)
draw(screen, rectaddpt(tiles[i], screen->r.min), pal[PCBg0+(i%2)], nil, ZP);
for(i = 0; i < nlines; i++)
line(screen, toscreen(lines[i].pts[0]), toscreen(lines[i].pts[1]), 0, 0, thickness, pal[PCFg0+(i+paloff)%2], ZP);
if(showtheline)
line(screen, toscreen(theline.pts[0]), toscreen(theline.pts[1]), 0, 0, 0, pal[PCAux], ZP);
fillellipse(screen, toscreen(thepoint), 2, 2, pal[PCAux], ZP);
flushimage(display, 1);
unlockdisplay(display);
}
void
lmb(Mousectl *mc)
{
Line l;
Point p0, p1;
int i;
pts[npts] = thepoint = fromscreen(mc->xy);
if(++npts > 2-1){
theline.pts[0] = pts[0];
theline.pts[1] = pts[1];
npts = 0;
if(lines != nil){
free(lines);
lines = nil;
nlines = 0;
}
for(i = 0; i < ntiles; i++){
p0 = Pt(theline.pts[0].x,theline.pts[0].y);
p1 = Pt(theline.pts[1].x,theline.pts[1].y);
if(ptinrect(p0, tiles[i]))
paloff = i;
//fprint(2, "%P, %P ∩ %R = ", p0, p1, tiles[i]);
if(rectclipline(tiles[i], &p0, &p1) < 0)
continue;
//fprint(2, "%P, %P\n", p0, p1);
l.pts[0] = Pt2(p0.x,p0.y,1);
l.pts[1] = Pt2(p1.x,p1.y,1);
addline(l);
}
}
}
void
rmb(Mousectl *)
{
showtheline ^= 1;
}
void
mouse(Mousectl *mc)
{
static Mouse om;
if((om.buttons^mc->buttons) == 0)
return;
if((mc->buttons&1) != 0)
lmb(mc);
if((mc->buttons&4) != 0)
rmb(mc);
if((mc->buttons&8) != 0)
thickness = CLAMP(++thickness, 0, 10);
if((mc->buttons&16) != 0)
thickness = CLAMP(--thickness, 0, 10);
om = mc->Mouse;
}
void
key(Rune r)
{
switch(r){
case Kdel:
case 'q':
threadexitsall(nil);
}
}
void
usage(void)
{
fprint(2, "usage: %s tiles\n", argv0);
exits("usage");
}
void
threadmain(int argc, char *argv[])
{
Mousectl *mc;
Keyboardctl *kc;
Rune r;
int i, Δx, Δy;
ARGBEGIN{
default: usage();
}ARGEND;
if(argc != 1)
usage();
ntiles = strtoul(argv[0], nil, 10);
tiles = malloc(ntiles*sizeof(*tiles));
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);
Δx = Dx(screen->r);
Δy = Dy(screen->r)/ntiles;
for(i = 0; i < ntiles; i++)
tiles[i] = Rect(0,i*Δy,Δx,i == ntiles-1? Dy(screen->r): (i+1)*Δy);
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();
}