static volatile int debug_setxy = 0; static volatile int debug_recomp = 0; #include #include #include #include #include extern const char *__progname; #include "2dmath.h" #define SNAP_RANGE 20 #define EPSILON (1e-10) typedef enum { DRAG_NONE = 1, DRAG_SEGMENT, DRAG_EXTEND_A, DRAG_EXTEND_B, DRAG_CIRCLE, DRAG_SETCOMPASS, DRAG_SETCIRCLE, } DRAG_MODE; typedef enum { OT_POINT = 1, OT_LINE, OT_ARCS, } OBJ_TYPE; typedef enum { DK_NONE = 1, DK_POINT, DK_LINE_END, DK_LINE_BODY, DK_ARC_CENTRE, DK_ARC_END, DK_ARC_BODY, } DISTKIND; typedef enum { QLOC_NONE = 1, QLOC_INPROGRESS, QLOC_REPEAT, } QLOC_STATE; typedef struct ends ENDS; typedef struct line LINE; typedef struct arcs ARCS; typedef struct tool TOOL; typedef struct ds_seg DS_SEG; typedef struct ds_ext DS_EXT; typedef struct ds_arc DS_ARC; typedef struct segment_state SEGMENT_STATE; typedef struct extend_state EXTEND_STATE; typedef struct circle_state CIRCLE_STATE; typedef struct setcompass_state SETCOMPASS_STATE; typedef struct drawtool DRAWTOOL; typedef struct obj OBJ; typedef struct objtype OBJTYPE; typedef struct ipoint IPOINT; typedef struct dragpoint DRAGPOINT; typedef struct distret DISTRET; typedef struct llintersect LLINTERSECT; struct llintersect { int good; double l1x; double l2x; } ; struct distret { double d; FPOINT p; DISTKIND kind; int pri; // The numerical order of these is important! #define PRI_NONE 0 #define PRI_LINE 1 #define PRI_POINT 2 void *data; } ; struct ipoint { int x; int y; } ; struct dragpoint { int havef; FPOINT f; IPOINT i; } ; struct objtype { OBJ_TYPE t; DISTRET (*dist)(const OBJ *, FPOINT); } ; struct obj { const OBJTYPE *type; int inx; } ; struct drawtool { TOOL *t; LX_XID unselwin; LX_XID selwin; void (*click)(const LX_EVENT_ButtonPress *); void (*onselect)(void); void (*ondeselect)(void); } ; struct segment_state { DRAWTOOL dt; } ; struct extend_state { DRAWTOOL dt; } ; struct circle_state { DRAWTOOL dt; } ; struct setcompass_state { int stage; #define SETC_NONE 0 #define SETC_FIRST 1 #define SETC_SECOND 2 #define SETC_LOCKED 3 LX_XID win[3]; FPOINT p1; FPOINT p2; void (*prev_arena_click)(const LX_EVENT_ButtonPress *); } ; struct tool { void (* const setup)(TOOL *); void (* const click)(const LX_EVENT_ButtonPress *); const unsigned char * const bits; int inx; int winx; LX_XID pm; LX_XID inwin; } ; struct ends { double e1; double e2; } ; struct line { FPOINT p; FPOINT d; int nsegs; int asegs; ENDS *segs; } ; // for a full circle, narc is -1, aarcs is 0, and arcs is nil struct arcs { FPOINT c; double r; int narc; int aarc; ENDS *arcs; } ; struct ds_seg { DRAGPOINT fix; DRAGPOINT cur; } ; struct ds_ext { LINE *l; double x1; } ; struct ds_arc { DRAGPOINT ctr; DRAGPOINT rad; DRAGPOINT ang1; DRAGPOINT ang2; } ; static LX_CONN *disp; static int scr; static LX_XID root; static int depth; static int cmap; static unsigned int wpix; static unsigned int bpix; static LX_RGB wrgb; static LX_RGB brgb; static LX_XID topwin; static LX_XID toolcwin; static LX_XID toolowin; static LX_XID tooliwin; static LX_XID drawwin; static LX_XID xhhwin; static LX_XID xhvwin; static LX_XID inwin; static LX_XID nilcurs; static LX_XID pluscurs; static const LX_VISINFO *vi; static unsigned int rw; static unsigned int rh; static unsigned int tw; static unsigned int th; static LX_SGC wingc; static LX_SGC bitgc; static int xhfound; static DISTRET xhbest; static int xhx; static int xhy; static int outside; static QLOC_STATE qloc_state = QLOC_NONE; static LX_QUERYPOINTER_STATUS qloc_qps; static DRAG_MODE drag; static LX_XID drawpm; static DS_SEG ds_seg; static DS_EXT ds_ext; static DS_ARC ds_arc; static int drag_up; static int (*arena_vet_dist)(const DISTRET *); static FPOINT winmin; static FPOINT winmax; static int zoom; static double units_per_pixel; static double pixels_per_unit; static double snap_range; #define MINZOOM (-200) #define MAXZOOM (200) static int end_batch; static int want_rerender; static int npoints; static int apoints; static FPOINT *points; static int nlines; static int alines; static LINE *lines; static int narcs; static int aarcs; static ARCS *arcs; static int nobjs; static int aobjs; static OBJ *objs; static DRAWTOOL *drawtool; void (*arena_click)(const LX_EVENT_ButtonPress *); static SEGMENT_STATE s_segment; static EXTEND_STATE s_extend; static CIRCLE_STATE s_circle; static SETCOMPASS_STATE s_setcompass; #include "tools.inc" #define N_TOOLS (sizeof(tools) / sizeof(tools[0])) #define TOOL_ROW_BYTES ((TOOLSIZE + 7) >> 3) static DISTRET dist_point(const OBJ *, FPOINT); static const OBJTYPE ot_point = { OT_POINT, &dist_point }; static DISTRET dist_line(const OBJ *, FPOINT); static const OBJTYPE ot_line = { OT_LINE, &dist_line }; static DISTRET dist_arcs(const OBJ *, FPOINT); static const OBJTYPE ot_arcs = { OT_ARCS, &dist_arcs }; static const OBJTYPE * const objtypes[] = { &ot_point, &ot_line, &ot_arcs }; #define N_OBJTYPES (sizeof(objtypes)/sizeof(objtypes[0])); #define XHAT ((FPOINT){.x=1,.y=0}) #define YHAT ((FPOINT){.x=0,.y=1}) static void beep(void) { lx_Bell(disp,0); } static int check_visual(void *vvv, const LX_VISINFO *vi) { void **vv; vv = vvv; if (vi->id == *(LX_XID *)vv[0]) { *(const LX_VISINFO **)vv[1] = vi; return(-1); } return(0); } static void find_visual(void) { LX_XID id; void *v[2]; id = lx_root_visual(disp,scr); v[0] = &id; v[1] = &vi; if (! lx_map_visuals(disp,&check_visual,&v[0])) { fprintf(stderr,"%s: can't find root visual\n",__progname); exit(1); } } static FPOINT screen_to_world(IPOINT p) { return((FPOINT){ .x = winmin.x + ((p.x / (tw-1.0)) * (winmax.x - winmin.x)), .y = winmin.y + ((p.y / (th-1.0)) * (winmax.y - winmin.y)) }); } static IPOINT world_to_screen(FPOINT p) { return((IPOINT){ .x = rint(((p.x - winmin.x) * (tw - 1)) / (winmax.x - winmin.x)), .y = rint(((p.y - winmin.y) * (th - 1)) / (winmax.y - winmin.y)) }); } static void set_xhx(int x) { if (x == xhx) return; xhx = x; lx_ConfigureWindow_va(disp,xhvwin,LX_CWV_X(x),LX_CWV_END); } static void set_xhy(int y) { if (y == xhy) return; xhy = y; lx_ConfigureWindow_va(disp,xhhwin,LX_CWV_Y(y),LX_CWV_END); } static void draw_drag_circle(IPOINT ctr, double radius) { int r; LX_ARC xa; r = rint(radius); xa.x = ctr.x - r; xa.y = ctr.y - r; xa.w = (2 * r) + 1; xa.h = (2 * r) + 1; xa.a1 = 0; xa.a2 = 360 * 64; lx_PolyArc(disp,drawwin,lx_SGC_GC(disp,wingc),1,&xa); } /* * The math behind this: * * Let ap and da be for line a, bp and db for line b. Then the * intersection can be expressed as an equation in 2-vectors (where fa * and fb are the indications of where on lines a and b the * intersection point is): * * ap + (fa da) = bp + (fb db) * * which expands into * * apx + fa dax = bpx + fb dbx * apy + fa day = bpy + fb dby * * which transforms successively into * * fa dax = bpx - apx + fb dbx * fa day = bpy - apy + fb dby * * fa = (bpx - apx + fb dbx) / dax * * (bpx - apx + fb dbx) day / dax = bpy - apy + fb * * bpx day - apx day + fb dbx day = bpy dax - apy dax + fb dby dax * * bpx day - apx day - bpy dax + apy dax = fb (dby dax - dbx day) * * fb = (bpx day - apx day - bpy dax + apy dax) / (dby dax - dbx day) * * Then, we can either substitute fb into the equation for fa, above, * which gives * * fa = (bpx - apx + [(bpx day - apx day - bpy dax + apy dax) dbx) / (dby dax - dbx day)]) / dax * * and simplify, or we can notice that the original is invariant under * exchanging a and b and get it directly. Reassuringly, both * approaches result in the same formula: * * fa = (apx dby - bpx dby - apy dbx + bpy dbx) / (day dbx - dax dby) */ static LLINTERSECT intersect_lines(FPOINT ap, FPOINT da, FPOINT bp, FPOINT db) { double c; c = cross2(da,db); if (fabs(c) < EPSILON) { // Numerically parallel - no intersection return((LLINTERSECT){.good=0}); } return((LLINTERSECT) { .good = 1, .l1x = ((ap.x * db.y) + (bp.y * db.x) - (bp.x * db.y) - (ap.y * db.x)) / -c, .l2x = ((bp.x * da.y) + (ap.y * da.x) - (ap.x * da.y) - (bp.y * da.x)) / c }); } static void draw_drag_extend_line(void) { LLINTERSECT il; LLINTERSECT it; LLINTERSECT ir; LLINTERSECT ib; double e[4]; int ne; int i; double t; int any; IPOINT ip1; IPOINT ip2; il = intersect_lines(ds_ext.l->p,ds_ext.l->d,winmin,(FPOINT){.x=0,.y=1}); it = intersect_lines(ds_ext.l->p,ds_ext.l->d,winmin,(FPOINT){.x=1,.y=0}); ir = intersect_lines(ds_ext.l->p,ds_ext.l->d,winmax,(FPOINT){.x=0,.y=-1}); ib = intersect_lines(ds_ext.l->p,ds_ext.l->d,winmax,(FPOINT){.x=-1,.y=0}); switch ((il.good?1:0)|(it.good?2:0)|(ir.good?4:0)|(ib.good?8:0)) { default: abort(); break; case 5: case 7: case 13: e[0] = il.l1x; e[1] = ir.l1x; ne = 2; case 10: case 11: case 14: e[0] = it.l1x; e[1] = ib.l1x; ne = 2; break; case 15: e[0] = il.l1x; e[1] = it.l1x; e[2] = ir.l1x; e[3] = ib.l1x; ne = 4; break; } do { any = 0; for (i=ne-1;i>0;i--) { if (e[i] < e[i-1]) { t = e[i]; e[i] = e[i-1]; e[i-1] = t; any = 1; } } } while (any); switch (ne) { case 2: ip1 = world_to_screen(add2(ds_ext.l->p,scale2(ds_ext.l->d,e[0]))); ip2 = world_to_screen(add2(ds_ext.l->p,scale2(ds_ext.l->d,e[1]))); break; case 4: ip1 = world_to_screen(add2(ds_ext.l->p,scale2(ds_ext.l->d,e[1]))); ip2 = world_to_screen(add2(ds_ext.l->p,scale2(ds_ext.l->d,e[2]))); break; default: abort(); break; } lx_ChangeSGC_va(disp,wingc,LX_GCV_LineStyle(LX_GCLINESTYLE_OnOffDash),LX_GCV_Dashes(2),LX_GCV_END); lx_draw_line(disp,drawwin,lx_SGC_GC(disp,wingc),ip1.x,ip1.y,ip2.x,ip2.y); lx_ChangeSGC_va(disp,wingc,LX_GCV_LineStyle(LX_GCLINESTYLE_Solid),LX_GCV_END); } static void draw_drag_extend_cross(double x) { FPOINT xp; FPOINT xd; IPOINT ip1; IPOINT ip2; xp = add2(ds_ext.l->p,scale2(ds_ext.l->d,x)); xd = r90(ds_ext.l->d); ip1 = world_to_screen(add2(xp,scale2(xd,20))); ip2 = world_to_screen(add2(xp,scale2(xd,-20))); lx_ChangeSGC_va(disp,wingc,LX_GCV_LineStyle(LX_GCLINESTYLE_OnOffDash),LX_GCV_Dashes(2),LX_GCV_END); lx_draw_line(disp,drawwin,lx_SGC_GC(disp,wingc),ip1.x,ip1.y,ip2.x,ip2.y); lx_ChangeSGC_va(disp,wingc,LX_GCV_LineStyle(LX_GCLINESTYLE_Solid),LX_GCV_END); } static double extend_cross_x(FPOINT p) { return(dot2(sub2(p,ds_ext.l->p),ds_ext.l->d)); } static void draw_drag(void) { lx_ChangeSGC_va(disp,wingc,LX_GCV_Function(LX_GCFUNCTION_Xor),LX_GCV_Foreground(wpix^bpix),LX_GCV_END); switch (drag) { case DRAG_NONE: break; case DRAG_SEGMENT: lx_draw_line(disp,drawwin,lx_SGC_GC(disp,wingc),ds_seg.fix.i.x,ds_seg.fix.i.y,ds_seg.cur.i.x,ds_seg.cur.i.y); break; case DRAG_CIRCLE: draw_drag_circle(ds_arc.ctr.i,rint(hypot(ds_arc.ctr.i.x-ds_arc.rad.i.x,ds_arc.ctr.i.y-ds_arc.rad.i.y))); break; case DRAG_SETCOMPASS: { IPOINT ip1; IPOINT ip2; ip1 = world_to_screen(s_setcompass.p1); ip2 = world_to_screen(s_setcompass.p2); lx_ChangeSGC_va(disp,wingc,LX_GCV_LineStyle(LX_GCLINESTYLE_OnOffDash),LX_GCV_Dashes(2),LX_GCV_END); lx_draw_line(disp,drawwin,lx_SGC_GC(disp,wingc),ip1.x,ip1.y,ip2.x,ip2.y); lx_ChangeSGC_va(disp,wingc,LX_GCV_LineStyle(LX_GCLINESTYLE_Solid),LX_GCV_END); } break; case DRAG_SETCIRCLE: draw_drag_circle(ds_arc.ctr.i,len2(sub2(s_setcompass.p1,s_setcompass.p2))); break; case DRAG_EXTEND_A: draw_drag_extend_line(); draw_drag_extend_cross(extend_cross_x(screen_to_world((IPOINT){.x=xhx,.y=xhy}))); break; case DRAG_EXTEND_B: draw_drag_extend_line(); draw_drag_extend_cross(ds_ext.x1); draw_drag_extend_cross(extend_cross_x(screen_to_world((IPOINT){.x=xhx,.y=xhy}))); break; } lx_ChangeSGC_va(disp,wingc,LX_GCV_Function(LX_GCFUNCTION_Copy),LX_GCV_END); end_batch = 1; } static void hide_drag(void) { if (drag_up) { draw_drag(); drag_up = 0; } } static void show_drag(void) { if (! drag_up) { draw_drag(); drag_up = 1; } } static void arena_setxy(int x, int y) { FPOINT xyp; IPOINT wp; DISTRET best; IPOINT bestwp; int ox; OBJ *o; int dbg; DISTRET r; dbg = debug_setxy; if (dbg & 1) debug_setxy = dbg - 1; xyp = screen_to_world((IPOINT){.x=x,.y=y}); if (dbg) { printf("setxy: (%d,%d) -> (%g,%g)\n",x,y,xyp.x,xyp.y); for (ox=nobjs-1;ox>=0;ox--) { o = &objs[ox]; printf(" obj %d: ",ox); switch (o->type->t) { case OT_POINT: printf("POINT (%g,%g)\n",points[o->inx].x,points[o->inx].y); break; case OT_LINE: { LINE *l; int i; ENDS *e; l = &lines[o->inx]; printf("LINE (%g,%g)+(%g,%g): [%d]",l->p.x,l->p.y,l->d.x,l->d.y,l->nsegs); for (i=0;insegs;i++) { FPOINT e1; FPOINT e2; e = &l->segs[i]; e1 = add2(l->p,scale2(l->d,e->e1)); e2 = add2(l->p,scale2(l->d,e->e2)); printf(" %g(%g,%g)-%g(%g,%g)",e->e1,e1.x,e1.y,e->e2,e2.x,e2.y); } printf("\n"); } break; case OT_ARCS: { ARCS *a; int i; ENDS *e; a = &arcs[o->inx]; printf("ARCS (%g,%g)+%g: [%d]",a->c.x,a->c.y,a->r,a->narc); for (i=0;inarc;i++) { e = &a->arcs[i]; printf(" %g-%g",e->e1,e->e2); } printf("\n"); } break; default: abort(); break; } } } best.pri = PRI_NONE; for (ox=nobjs-1;ox>=0;ox--) { o = &objs[ox]; r = (*o->type->dist)(o,xyp); if (r.kind == DK_NONE) continue; if (r.pri <= PRI_NONE) abort(); if (arena_vet_dist && !(*arena_vet_dist)(&r)) continue; if ( (r.pri > best.pri) || ( (r.pri == best.pri) && (r.d < best.d) ) ) { wp = world_to_screen(r.p); if ((wp.x < 0) || (wp.x >= tw) || (wp.y < 0) || (wp.y >= th)) continue; best = r; bestwp = wp; } } if (dbg) { printf("bestd %g",best.d); if (best.d >= 0) printf(", bestp (%g,%g), bestwp (%d,%d)",best.p.x,best.p.y,bestwp.x,bestwp.y); putchar('\n'); } if (best.pri == PRI_NONE) { xhfound = 0; set_xhx(x); set_xhy(y); } else { xhfound = 1; xhbest = best; set_xhx(bestwp.x); set_xhy(bestwp.y); } } static void arena_move(int x, int y) { hide_drag(); arena_setxy(x,y); switch (drag) { case DRAG_NONE: break; case DRAG_SEGMENT: if (xhfound) { ds_seg.cur.havef = 1; ds_seg.cur.f = xhbest.p; } else { ds_seg.cur.havef = 0; } ds_seg.cur.i.x = xhx; ds_seg.cur.i.y = xhy; break; case DRAG_CIRCLE: if (xhfound) { ds_arc.rad.havef = 1; ds_arc.rad.f = xhbest.p; } else { ds_arc.rad.havef = 0; } ds_arc.rad.i.x = xhx; ds_arc.rad.i.y = xhy; break; case DRAG_SETCOMPASS: if (xhfound) { s_setcompass.p2 = xhbest.p; } else { s_setcompass.p2 = screen_to_world((IPOINT){.x=xhx,.y=xhy}); } break; case DRAG_SETCIRCLE: if (xhfound) { ds_arc.ctr.havef = 1; ds_arc.ctr.f = xhbest.p; } else { ds_arc.ctr.havef = 0; } ds_arc.ctr.i.x = xhx; ds_arc.ctr.i.y = xhy; break; case DRAG_EXTEND_A: break; case DRAG_EXTEND_B: break; } show_drag(); } static void arena_hide_xhair(void) { set_xhx(-1); set_xhy(-1); } static void qloc_query(void); // forward static void qloc_qdone(void *arg __attribute__((__unused__))) { if (outside || !qloc_qps.samescreen) { arena_hide_xhair(); qloc_state = QLOC_NONE; return; } switch (qloc_state) { case QLOC_NONE: abort(); break; case QLOC_INPROGRESS: arena_move(qloc_qps.winx,qloc_qps.winy); qloc_state = QLOC_NONE; break; case QLOC_REPEAT: qloc_state = QLOC_NONE; qloc_query(); break; } } static void qloc_query(void) { switch (qloc_state) { case QLOC_NONE: qloc_state = QLOC_INPROGRESS; lx_op_callback(lx_QueryPointer_status(disp,inwin,&qloc_qps),&qloc_qdone,0,0); break; case QLOC_INPROGRESS: qloc_state = QLOC_REPEAT; break; case QLOC_REPEAT: break; default: abort(); break; } } static void resize_top(unsigned int w, unsigned int h) { FPOINT c; if ((w == tw) && (h == th)) return; if (w < 2) w = 2; if (h < 2) h = 2; c = scale2(add2(winmin,winmax),.5); winmin.x = c.x + (((winmin.x - c.x) * (w-1)) / (tw-1)); winmin.y = c.y + (((winmin.y - c.y) * (h-1)) / (th-1)); winmax.x = c.x + (((winmax.x - c.x) * (w-1)) / (tw-1)); winmax.y = c.y + (((winmax.y - c.y) * (h-1)) / (th-1)); tw = w; th = h; want_rerender = 1; qloc_query(); } static void button_press(const LX_EVENT_ButtonPress *e) { int i; TOOL *t; if (e->eventw == inwin) { arena_setxy(e->eventx,e->eventy); (*arena_click)(e); return; } for (i=N_TOOLS-1;i>=0;i--) { t = &tools[i]; if (e->eventw == t->inwin) { (*t->click)(e); return; } } } static void enter_notify(const LX_EVENT_EnterNotify *e) { if (e->eventw != inwin) return; outside = 0; arena_move(e->eventx,e->eventy); } static void leave_notify(const LX_EVENT_EnterNotify *e) { if (e->eventw != inwin) return; outside = 1; arena_hide_xhair(); } static void motion_notify(const LX_EVENT_MotionNotify *e) { if (e->eventw != inwin) return; if (outside) return; qloc_query(); } static void configure_notify(const LX_EVENT_ConfigureNotify *e) { if (e->eventw != topwin) return; resize_top(e->w,e->h); } static void x_event(LX_CONN *xc, const LX_EVENT *e) { if (xc != disp) abort(); switch (e->type) { default: break; case LX_EV_ButtonPress: button_press(&e->u.ButtonPress); break; case LX_EV_EnterNotify: enter_notify(&e->u.EnterNotify); break; case LX_EV_LeaveNotify: leave_notify(&e->u.EnterNotify); break; case LX_EV_MotionNotify: motion_notify(&e->u.MotionNotify); break; case LX_EV_ConfigureNotify: configure_notify(&e->u.ConfigureNotify); break; } } static void raise_window(LX_XID w) { lx_ConfigureWindow_va(disp,w,LX_CWV_StackMode(LX_STACKMODE_Above),LX_CWV_END); } static void use_drawtool(DRAWTOOL *dt) { if (drawtool == dt) return; if (drawtool) { raise_window(drawtool->unselwin); if (drawtool->ondeselect) (*drawtool->ondeselect)(); } drawtool = dt; raise_window(drawtool->selwin); if (drawtool->onselect) (*drawtool->onselect)(); } static int maybe_end_batch(void *arg __attribute__((__unused__))) { if (! end_batch) return(AIO_BLOCK_NIL); end_batch = 0; return(lx_end_batch(disp)?AIO_BLOCK_LOOP:AIO_BLOCK_NIL); } static void render_segment(LINE *l, int sx) { ENDS *e; FPOINT e1; FPOINT e2; IPOINT e1i; IPOINT e2i; e = &l->segs[sx]; e1 = add2(l->p,scale2(l->d,e->e1)); e2 = add2(l->p,scale2(l->d,e->e2)); #define CLIP(end,oend,coord,test,minmax) do {\ if (end.coord test win##minmax.coord) \ { if (oend.coord test win##minmax.coord) return; \ end = add2(end,scale2(sub2(oend,end),(win##minmax.coord-end.coord)/(oend.coord-end.coord)));\ } \ } while (0) CLIP(e1,e2,x,<,min); CLIP(e1,e2,x,>,max); CLIP(e2,e1,x,<,min); CLIP(e2,e1,x,>,max); CLIP(e1,e2,y,<,min); CLIP(e1,e2,y,>,max); CLIP(e2,e1,y,<,min); CLIP(e2,e1,y,>,max); #undef CLIP e1i = world_to_screen(e1); e2i = world_to_screen(e2); lx_draw_line(disp,drawpm,lx_SGC_GC(disp,wingc),e1i.x,e1i.y,e2i.x,e2i.y); end_batch = 1; } static void render_arcs(ARCS *a) { IPOINT ci; double r2; int narc; ENDS *arcs; ENDS circlearc; LX_ARC xa; FPOINT bbmin; FPOINT bbmax; IPOINT ibbmin; IPOINT ibbmax; FPOINT backbbmin; FPOINT backbbmax; double dpix; // Entirely off one side of visible area? if ( (a->c.x + a->r < winmin.x) || (a->c.x - a->r > winmax.x) || (a->c.y + a->r < winmin.y) || (a->c.y - a->r > winmax.y) ) return; ci = world_to_screen(a->c); r2 = a->r * a->r; // Visible area entirely inside circle? if ( (len22(sub2(a->c,winmin)) < r2) && (len22(sub2(a->c,winmax)) < r2) && (len22(sub2(a->c,(FPOINT){.x=winmin.x,.y=winmax.y})) < r2) && (len22(sub2(a->c,(FPOINT){.x=winmax.x,.y=winmin.y})) < r2) ) return; // Radius too large to use X circle-drawing? if (a->r*pixels_per_unit > 16384) { // XXX fix this } // Radius too small to be a real circle? else if (a->r*pixels_per_unit < 2) { lx_draw_point(disp,drawpm,lx_SGC_GC(disp,wingc),ci.x,ci.y); } else { bbmin.x = a->c.x - a->r; bbmin.y = a->c.y - a->r; bbmax.x = a->c.x + a->r; bbmax.y = a->c.y + a->r; ibbmin = world_to_screen(bbmin); ibbmax = world_to_screen(bbmax); if (ibbmax.x-ibbmin.x != ibbmax.y-ibbmin.y) { dpix = 2 * a->r * pixels_per_unit; backbbmin = screen_to_world(ibbmin); backbbmax = screen_to_world(ibbmax); // Which dimension is closer to dpix? if (fabs((ibbmax.x-ibbmin.x)-dpix) < fabs((ibbmax.y-ibbmin.y)-dpix)) { // Adjust ibbmax.y or ibbmin.y? if (fabs(backbbmin.y-bbmin.y) < fabs(backbbmax.y-bbmax.y)) { ibbmax.y = ibbmin.y + (ibbmax.x - ibbmin.x); } else { ibbmin.y = ibbmax.y - (ibbmax.x - ibbmin.x); } } else { // Adjust ibbmax.x or ibbmin.x? if (fabs(backbbmin.x-bbmin.x) < fabs(backbbmax.x-bbmax.x)) { ibbmax.x = ibbmin.x + (ibbmax.y - ibbmin.y); } else { ibbmin.x = ibbmax.x - (ibbmax.y - ibbmin.y); } } } if (ibbmax.x-ibbmin.x != ibbmax.y-ibbmin.y) abort(); if (a->narc < 0) { narc = 1; arcs = &circlearc; circlearc.e1 = 0; circlearc.e2 = 2 * M_PI; } else { narc = a->narc; arcs = a->arcs; } for (;narc>0;narc--,arcs++) { xa.x = ibbmin.x; xa.y = ibbmin.y; xa.w = ibbmax.x - ibbmin.x; xa.h = ibbmax.y - ibbmin.y; xa.a1 = rint((arcs->e1*180*64)/M_PI); xa.a2 = xa.a1 - rint((arcs->e2*180*64)/M_PI); lx_PolyArc(disp,drawpm,lx_SGC_GC(disp,wingc),1,&xa); } } end_batch = 1; } static int maybe_rerender(void *arg __attribute__((__unused__))) { int i; if (! want_rerender) return(AIO_BLOCK_NIL); want_rerender = 0; lx_fill_rectangle(disp,drawpm,lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(bpix),LX_GCV_PUSH),0,0,tw,th); lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(wpix),LX_GCV_END); for (i=nlines-1;i>=0;i--) { LINE *l; int j; l = &lines[i]; for (j=l->nsegs-1;j>=0;j--) render_segment(l,j); } for (i=narcs-1;i>=0;i--) { render_arcs(&arcs[i]); } for (i=npoints-1;i>=0;i--) { IPOINT sp; sp = world_to_screen(points[i]); if ((sp.x < -10) || (sp.x >= tw+10) || (sp.y < -10) || (sp.y >= th+10)) continue; lx_fill_rectangle(disp,drawpm,lx_SGC_GC(disp,wingc),sp.x-3,sp.y-3,7,7); } lx_ChangeWindowAttributes_va(disp,drawwin,LX_CWV_BackPixmap(drawpm),LX_CWV_END); lx_ClearArea(disp,drawwin,0,0,0,0,0); return(AIO_BLOCK_LOOP); } static void reset_units(void) { pixels_per_unit = pow(.9,zoom); units_per_pixel = 1 / pixels_per_unit; snap_range = SNAP_RANGE * units_per_pixel; } static void arena_click_drawtool(const LX_EVENT_ButtonPress *e) { if (drawtool) (*drawtool->click)(e); } static void x_step2(void *vvv) { int i; TOOL *t; int x; int y; LX_XID cipm; LX_XID cmpm; wrgb = ((LX_RGB *)(((void **)vvv)[1]))[0]; brgb = ((LX_RGB *)(((void **)vvv)[1]))[1]; free(((void **)vvv)[0]); free(((void **)vvv)[1]); free(vvv); wingc = lx_CreateSGC_va(disp,root,LX_GCV_END); cipm = lx_CreatePixmap(disp,root,1,1,1); bitgc = lx_CreateSGC_va(disp,cipm,LX_GCV_Foreground(0),LX_GCV_END); lx_draw_point(disp,cipm,lx_SGC_GC(disp,bitgc),0,0); nilcurs = lx_CreateCursor(disp,cipm,cipm,0,0,0,0,0,0,0,0); lx_FreePixmap(disp,cipm); cmpm = lx_CreatePixmap(disp,root,1,21,21); cipm = lx_CreatePixmap(disp,root,1,21,21); lx_fill_rectangle(disp,cmpm,lx_SGC_GC(disp,bitgc),0,0,21,21); lx_fill_rectangle(disp,cipm,lx_SGC_GC(disp,bitgc),0,0,21,21); lx_ChangeSGC_va(disp,bitgc,LX_GCV_Foreground(1),LX_GCV_END); lx_fill_rectangle(disp,cipm,lx_SGC_GC(disp,bitgc),10,1,1,19); lx_fill_rectangle(disp,cipm,lx_SGC_GC(disp,bitgc),1,10,19,1); lx_fill_rectangle(disp,cmpm,lx_SGC_GC(disp,bitgc),9,0,3,21); lx_fill_rectangle(disp,cmpm,lx_SGC_GC(disp,bitgc),0,9,21,3); pluscurs = lx_CreateCursor_rgb(disp,cipm,cmpm,wrgb,brgb,10,10); lx_FreePixmap(disp,cipm); lx_FreePixmap(disp,cmpm); find_visual(); tw = (rw * 2) / 3; th = (rh * 2) / 3; if (tw < 2) tw = 2; if (th < 2) th = 2; topwin = lx_CreateWindow_va(disp,root,(rw-tw-2)/2,(rh-th-2)/2,tw,th,1,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(bpix), LX_CWV_BorderPixel(wpix), LX_CWV_EventMask(LX_EM_StructureNotify), LX_CWV_Colormap(cmap), LX_CWV_END); drawwin = lx_CreateWindow_va(disp,topwin,0,0,tw,th,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(bpix), LX_CWV_EventMask(LX_EM_Exposure), LX_CWV_Colormap(cmap), LX_CWV_END); xhhwin = lx_CreateWindow_va(disp,drawwin,-1,-1,65535,1,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(wpix), LX_CWV_Colormap(cmap), LX_CWV_END); xhvwin = lx_CreateWindow_va(disp,drawwin,-1,-1,1,65535,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(wpix), LX_CWV_Colormap(cmap), LX_CWV_END); xhx = -1; xhy = -1; outside = 1; inwin = lx_CreateWindow_va(disp,drawwin,0,0,tw,th,0,0,LX_WCLASS_InputOnly,vi->id, LX_CWV_EventMask( LX_EM_ButtonPress | LX_EM_EnterWindow | LX_EM_LeaveWindow | LX_EM_PointerMotion | LX_EM_PointerMotionHint ), LX_CWV_Cursor(pluscurs), LX_CWV_END); toolcwin = lx_CreateWindow_va(disp,topwin,-1,-1,((TOOLSIZE+1)*N_TOOLS)-1,TOOLSIZE,1,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(wpix), LX_CWV_Colormap(cmap), LX_CWV_END); toolowin = lx_CreateWindow_va(disp,toolcwin,0,0,((TOOLSIZE+1)*N_TOOLS)-1,TOOLSIZE,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixel(wpix), LX_CWV_Colormap(cmap), LX_CWV_END); tooliwin = lx_CreateWindow_va(disp,toolcwin,0,0,((TOOLSIZE+1)*N_TOOLS)-1,TOOLSIZE,0,0,LX_WCLASS_InputOnly,vi->id,LX_CWV_END); lx_ChangeSGC_va(disp,wingc,LX_GCV_Function(LX_GCFUNCTION_Copy),LX_GCV_END); for (i=N_TOOLS-1;i>=0;i--) { t = &tools[i]; t->pm = lx_CreatePixmap(disp,topwin,depth,TOOLSIZE,TOOLSIZE); lx_fill_rectangle(disp,t->pm,lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(bpix),LX_GCV_PUSH),0,0,TOOLSIZE,TOOLSIZE); lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(wpix),LX_GCV_END); for (y=TOOLSIZE-1;y>=0;y--) for (x=TOOLSIZE-1;x>=0;x--) { if ((t->bits[(y*TOOL_ROW_BYTES)+(x>>3)] << (x & 7)) & 0x80) { lx_draw_point(disp,t->pm,lx_SGC_GC(disp,wingc),x,y); } } t->inx = i; t->winx = i * (TOOLSIZE + 1); (*t->setup)(t); t->inwin = lx_CreateWindow_va(disp,tooliwin,t->winx,0,TOOLSIZE,TOOLSIZE,0,0,LX_WCLASS_InputOnly,vi->id, LX_CWV_EventMask(LX_EM_ButtonPress), LX_CWV_END); } lx_MapSubwindows(disp,tooliwin); lx_MapSubwindows(disp,toolowin); lx_MapSubwindows(disp,toolcwin); lx_MapSubwindows(disp,drawwin); lx_MapSubwindows(disp,topwin); lx_MapWindow(disp,topwin); drag = DRAG_NONE; drawtool = 0; use_drawtool(&s_segment.dt); drawpm = lx_CreatePixmap(disp,topwin,depth,tw,th); npoints = 0; apoints = 0; points = 0; nlines = 0; alines = 0; lines = 0; narcs = 0; aarcs = 0; arcs = 0; nobjs = 0; aobjs = 0; objs = 0; winmin.x = 0; winmin.y = 0; winmax.x = tw - 1; winmax.y = th - 1; zoom = 0; reset_units(); aio_add_block(&maybe_end_batch,0); aio_add_block(&maybe_rerender,0); arena_click = &arena_click_drawtool; } static void x_open_done(LX_CONN *xc, void *arg __attribute__((__unused__))) { void *vvv; disp = xc; lx_set_event_handler(disp,&x_event); scr = lx_default_screen(disp); root = lx_root(disp,scr); depth = lx_root_depth(disp,scr); cmap = lx_root_colormap(disp,scr); wpix = lx_white_pixel(disp,scr); bpix = lx_black_pixel(disp,scr); rw = lx_root_width(disp,scr); rh = lx_root_height(disp,scr); vvv = malloc(2*sizeof(void *)); ((void **)vvv)[0] = malloc(2*sizeof(unsigned int)); ((void **)vvv)[1] = malloc(2*sizeof(LX_RGB)); ((unsigned int *)(((void **)vvv)[0]))[0] = wpix; ((unsigned int *)(((void **)vvv)[0]))[1] = bpix; lx_op_callback(lx_QueryColors(disp,cmap,2,((void **)vvv)[0],((void **)vvv)[1]),&x_step2,vvv,0); } static int angle_within(double ang, double e1, double e2) { return( ((ang >= e1) && (ang <= e2)) || ((ang+(2*M_PI) <= e2) && (ang+(2*M_PI) >= e1)) || ((ang-(2*M_PI) >= e1) && (ang-(2*M_PI) <= e2)) ); } static void add_object(const OBJTYPE *ot, int inx) { OBJ *o; if (nobjs >= aobjs) { aobjs = nobjs + 8; objs = realloc(objs,aobjs*sizeof(OBJ)); } o = &objs[nobjs++]; o->type = ot; o->inx = inx; } static void clear_points(void) { OBJ *o; int i; int j; j = 0; for (i=0;itype != &ot_point) { if (i != j) objs[j] = objs[i]; j ++; } } nobjs = j; npoints = 0; } static void add_point(FPOINT n) { int i; FPOINT *p; if (debug_recomp) printf("add_point (%g,%g): ",n.x,n.y); for (i=npoints-1;i>=0;i--) { p = &points[i]; if ( (fabs(p->x-n.x) < EPSILON) && (fabs(p->y-n.y) < EPSILON) ) { if (debug_recomp) printf("duplicate of [%d]\n",i); return; } } if (npoints >= apoints) { apoints = npoints + 8; points = realloc(points,apoints*sizeof(FPOINT)); } i = npoints ++; points[i] = n; add_object(&ot_point,i); if (debug_recomp) printf("added as points[%d]\n",i); } static void intersect_line_line(LINE *a, LINE *b) { LLINTERSECT lli; int ex; ENDS *e; lli = intersect_lines(a->p,a->d,b->p,b->d); do <"found"> { for (ex=a->nsegs-1;ex>=0;ex--) { e = &a->segs[ex]; if ((lli.l1x >= e->e1) && (lli.l1x <= e->e2)) break <"found">; } return; } while (0); do <"found"> { for (ex=b->nsegs-1;ex>=0;ex--) { e = &b->segs[ex]; if ((lli.l2x >= e->e1) && (lli.l2x <= e->e2)) break <"found">; } return; } while (0); add_point(scale2(add2( add2(a->p,scale2(a->d,lli.l1x)), add2(b->p,scale2(b->d,lli.l2x)) ),.5)); } /* * The math behind this: * * A line intersects a circle in 0, 1, or 2 points. 0 if the line * misses the circle entirely, 1 if it's just tangent to the circle, * and 2 otherwise. * * We work out the distance from c, the circle's centre, to l, the * line. If this is greater than r, the circle's radius, there is no * intersection; if equal, one; if less, two. * * To compute the distance from a point (c, the circle's centre) to a * line in the form we keep it (a point, p, and a direction, d), we * dot v, the vector from p to c, with d, multiply d by that, and * subtract that from v. The magnitude of the difference is the * distance from c to l. * * If we have two points, we compute them by working out the distaince * from the point of closest approach to the points of intersection * (Pythagoras, hypotenuse = radius, one leg = distance from l to c) * and then take the distance from */ static void intersect_line_arcs(LINE *a, ARCS *b) { FPOINT v; FPOINT dv; double lx; double dist; double offset; int i; int x; FPOINT pt; int j; ENDS *le; double ang; int k; v = sub2(b->c,a->p); lx = dot2(v,a->d); dv = scale2(a->d,lx); dist = len2(sub2(v,dv)); offset = (b->r * b->r) - (dist * dist); if (offset < 0) return; if (offset < EPSILON) { add_point(add2(a->p,dv)); return; } offset = sqrt(offset); for <"nextpt"> (i=1;i>=0;i--) { x = i ? lx - offset : lx + offset; for (j=a->nsegs-1;j>=0;j--) { le = &a->segs[j]; pt = add2(a->p,scale2(a->d,x)); if ((x >= le->e1) && (x <= le->e2)) { if (b->narc >= 0) { do <"found"> { ang = atan2(pt.y-b->c.y,pt.x-b->c.x); for (k=b->narc-1;k>=0;k--) { if (angle_within(ang,b->arcs[k].e1,b->arcs[k].e2)) break <"found">; } continue <"nextpt">; } while (0); } add_point(pt); continue <"nextpt">; } } } } /* * The math behind this: * * Two circles intersect in zero, one, or two points. If the smaller * circle's radius is r, the larger's is R, and the distance between * their centres is d, then * * d < R-r * No intersection * d == R-r * One point * d > R-r and d < R+r * One point * d == R+r * One point * d > R+r * No intersection * * To compute the points of intersection when there are two of them, * let one point of intersection be P, the smaller circle have centre * c radius r, and the larger, centre C radius R. Then consider the * triangle c-C-P. All three sides are known (d, r, and R). The * cosine law says that, if the sides are x, y, and z, and X is the * angle opposite x, cos(X) = (y^2 + z^2 - x^2) / 2yz. We work this * out for the angle at C, then offset the C-to-c direction by that * angle, plus and minus, to get the directions from C to the points * of intersection. We then offset by R from C in those directions. * * But, to avoid doing a lot of trig function calls, we take advantage * of the way both angles are derived. Let's write dy for c.y-C.y, dx * for c.x-C.x, dd for hypot(dx,dy), CA for the angle of the vector * from C to c, that is, atan2(dy,dx), and CB for the offset angle, * the angle we get from the cosine law. Then we want sin(CA±CB) and * cos(CA±CB) in order to multiply them by R and add the result to C. * * The sum identities are * * sin a+b = sin a cos b + sin b cos a * cos a+b = cos a cos b - sin a sin b * * The difference identities are obtained by negating b, above, which * changes the sign of the terms involving sin b: * * sin a-b = sin a cos b - sin b cos a * cos a-b = cos a cos b + sin a sin b * * Because CA is atan2(dy,dx) and CB is acos(ac), sin(CA) is dy/dd, * cos(CA) is dx/dd, sin(CB) is sqrt(1-(ac*ac)), and cos(CB) is ac. */ static void intersect_arcs_arcs(ARCS *a, ARCS *b) { FPOINT cd; double d; double sum; double dif; double ca; double sa; double cb; double sb; double s; double c; FPOINT p1; FPOINT p2; if (a->r > b->r) { ARCS *t; t = a; a = b; b = t; } cd = sub2(a->c,b->c); d = len2(cd); sum = b->r + a->r; dif = b->r - a->r; if ((d < dif) || (d > sum)) return; if ((d == dif) || (d == sum)) { add_point(add2(b->c,scale2(cd,b->r/dif))); return; } cb = ((b->r * b->r) + (d * d) - (a->r * a->r)) / (2 * b->r * d); if ((cb < -1) || (cb > 1)) { // Should have been caught above // Must be a numerical glitch return; } sb = sqrt(1-(cb*cb)); sa = cd.y / d; ca = cd.x / d; s = (sa * cb) + (sb * ca); c = (ca * cb) - (sa * sb); p1 = add2(b->c,(FPOINT){.x=c*b->r,.y=s*b->r}); s = (sa * cb) - (sb * ca); c = (ca * cb) + (sa * sb); p2 = add2(b->c,(FPOINT){.x=c*b->r,.y=s*b->r}); if (len2(sub2(p1,p2)) < EPSILON) { add_point(scale2(add2(p1,p2),.5)); } else { add_point(p1); add_point(p2); } } static void intersect(OBJ *a, OBJ *b) { switch (a->type->t) { case OT_LINE: switch (b->type->t) { case OT_LINE: intersect_line_line(&lines[a->inx],&lines[b->inx]); break; case OT_ARCS: intersect_line_arcs(&lines[a->inx],&arcs[b->inx]); break; default: abort(); break; } break; case OT_ARCS: switch (b->type->t) { case OT_LINE: intersect_line_arcs(&lines[b->inx],&arcs[a->inx]); break; case OT_ARCS: intersect_arcs_arcs(&arcs[a->inx],&arcs[b->inx]); break; default: abort(); break; } break; default: abort(); break; } } static void add_line_points(const LINE *l) { int ex; ENDS *e; for (ex=l->nsegs-1;ex>=0;ex--) { e = &l->segs[ex]; add_point(add2(l->p,scale2(l->d,e->e1))); add_point(add2(l->p,scale2(l->d,e->e2))); } } static void add_arcs_points(const ARCS *a) { int ex; ENDS *e; add_point(a->c); for (ex=a->narc-1;ex>=0;ex--) { e = &a->arcs[ex]; add_point(add2(a->c,scale2(sincos2(e->e1),a->r))); add_point(add2(a->c,scale2(sincos2(e->e2),a->r))); } } static void recompute_points(void) { int ox1; OBJ *o1; int ox2; OBJ *o2; clear_points(); for (ox1=nobjs-1;ox1>=0;ox1--) { o1 = &objs[ox1]; switch (o1->type->t) { case OT_POINT: abort(); break; case OT_LINE: add_line_points(&lines[o1->inx]); break; case OT_ARCS: add_arcs_points(&arcs[o1->inx]); break; } for (ox2=nobjs-1;ox2>ox1;ox2--) { o2 = &objs[ox2]; if (o2->type->t == OT_POINT) continue; intersect(o1,o2); } } if (debug_recomp & 1) debug_recomp --; } static DISTRET dist_point(const OBJ *o, FPOINT topt) { DISTRET r; if (o->type != &ot_point) abort(); r.p = points[o->inx]; r.d = len2(sub2(r.p,topt)); if (r.d > snap_range) return((DISTRET){.kind=DK_NONE}); r.kind = DK_POINT; r.pri = PRI_POINT; return(r); } static DISTRET dist_line(const OBJ *o, FPOINT topt) { LINE *l; double x; double y; int ex; ENDS *e; double d; double bestd; double beste; FPOINT sd; DISTRET r; int inseg; if (o->type != &ot_line) abort(); l = &lines[o->inx]; topt = sub2(topt,l->p); x = dot2(l->d,topt); y = len2(sub2(topt,scale2(l->d,x))); if (y > snap_range) return((DISTRET){.kind=DK_NONE}); inseg = 0; bestd = -1; for (ex=l->nsegs-1;ex>=0;ex--) { e = &l->segs[ex]; d = fabs(e->e1-x); if ((bestd < 0) || (d < bestd)) { bestd = d; beste = e->e1; } d = fabs(e->e2-x); if (d < bestd) { bestd = d; beste = e->e2; } if ((x >= e->e1) && (x <= e->e2)) inseg = 1; } if (bestd < 0) abort(); if (bestd < snap_range) { sd = scale2(l->d,beste); r.p = add2(l->p,sd); r.d = bestd; r.kind = DK_LINE_END; r.pri = PRI_POINT; r.data = l; } else if (inseg) { r.d = y; r.p = add2(l->p,scale2(l->d,x)); r.kind = DK_LINE_BODY; r.pri = PRI_LINE; r.data = l; } else { r.kind = DK_NONE; } return(r); } static DISTRET dist_arcs(const OBJ *o, FPOINT topt) { ARCS *a; double d; DISTRET r; a = &arcs[o->inx]; d = len2(sub2(a->c,topt)); r.kind = DK_NONE; if (d*2 < a->r) { if (d < snap_range) { r.d = d; r.p = a->c; r.kind = DK_ARC_CENTRE; r.pri = PRI_POINT; } } else { r.d = fabs(a->r-d); if (r.d < snap_range) { r.p = add2(a->c,scale2(sub2(topt,a->c),a->r/d)); r.kind = DK_ARC_BODY; r.pri = PRI_LINE; } } return(r); } static void add_segment(void) { LINE *l; FPOINT d; double h; FPOINT cp; if ((ds_seg.fix.i.x == ds_seg.cur.i.x) && (ds_seg.fix.i.y == ds_seg.cur.i.y)) return; if (nlines >= alines) { alines = nlines + 8; lines = realloc(lines,alines*sizeof(LINE)); } l = &lines[nlines]; if (ds_seg.fix.havef) { l->p = ds_seg.fix.f; } else { l->p = screen_to_world(ds_seg.fix.i); } if (ds_seg.cur.havef) { cp = ds_seg.cur.f; } else { cp = screen_to_world(ds_seg.cur.i); } d = sub2(cp,l->p); h = len2(d); if (h < EPSILON) return; l->d = scale2(d,1/h); l->nsegs = 1; l->asegs = 8; l->segs = malloc(8*sizeof(ENDS)); l->segs[0].e1 = 0; l->segs[0].e2 = h; nlines ++; add_object(&ot_line,nlines-1); recompute_points(); want_rerender = 1; } static void drawclick_segment(const LX_EVENT_ButtonPress *e) { switch (drag) { case DRAG_NONE: if (e->button != 1) return; drag = DRAG_SEGMENT; if (xhfound) { ds_seg.fix.havef = 1; ds_seg.fix.f = xhbest.p; } else { ds_seg.fix.havef = 0; } ds_seg.fix.i.x = xhx; ds_seg.fix.i.y = xhy; drag_up = 0; break; case DRAG_SEGMENT: hide_drag(); drag = DRAG_NONE; if (e->button == 1) add_segment(); break; default: abort(); break; } } static void include_segment(LINE *l, double e1, double e2) { int i; int j; if (e1 > e2) { double t; t = e1; e1 = e2; e2 = t; } if (l->nsegs >= l->asegs) { l->asegs = l->nsegs + 8; l->segs = realloc(l->segs,l->asegs*sizeof(ENDS)); } for (i=l->nsegs;(i>0)&&(e1segs[i-1].e1);i--) ; if (i < l->nsegs) bcopy(&l->segs[i],&l->segs[i+1],(l->nsegs-i)*sizeof(ENDS)); l->segs[i].e1 = e1; l->segs[i].e2 = e2; l->nsegs ++; for (i=0;insegs-1;i++) { e2 = l->segs[i].e2; for (j=i+1;(jnsegs)&&(e2>=l->segs[j].e1);j++) { if (l->segs[j].e2 > e2) e2 = l->segs[j].e2; } if (j > i+1) { if (l->segs[j-1].e2 > l->segs[i].e2) l->segs[i].e2 = l->segs[j-1].e2; if (j < l->nsegs) bcopy(&l->segs[j],&l->segs[i+1],(l->nsegs-j)*sizeof(ENDS)); l->nsegs -= j - (i+1); } } recompute_points(); want_rerender = 1; } static void drawclick_extend(const LX_EVENT_ButtonPress *e) { FPOINT p; switch (drag) { case DRAG_NONE: if (e->button != 1) return; if (! xhfound) { beep(); break; } switch (xhbest.kind) { case DK_LINE_END: case DK_LINE_BODY: break; default: abort(); break; } drag = DRAG_EXTEND_A; ds_ext.l = xhbest.data; drag_up = 0; show_drag(); break; case DRAG_EXTEND_A: hide_drag(); if (e->button != 1) { drag = DRAG_NONE; break; } p = screen_to_world((IPOINT){.x=xhx,.y=xhy}); ds_ext.x1 = dot2(sub2(p,ds_ext.l->p),ds_ext.l->d); drag = DRAG_EXTEND_B; show_drag(); break; case DRAG_EXTEND_B: hide_drag(); if (e->button != 1) { drag = DRAG_NONE; break; } p = screen_to_world((IPOINT){.x=xhx,.y=xhy}); include_segment(ds_ext.l,ds_ext.x1,dot2(sub2(p,ds_ext.l->p),ds_ext.l->d)); drag = DRAG_NONE; break; default: abort(); break; } arena_setxy(e->eventx,e->eventy); } static void add_circle(FPOINT ctr, double rad) { ARCS *a; if ((ds_arc.ctr.i.x == ds_arc.rad.i.x) && (ds_arc.ctr.i.y == ds_arc.rad.i.y)) return; if (narcs >= aarcs) { aarcs = narcs + 8; arcs = realloc(arcs,aarcs*sizeof(ARCS)); } a = &arcs[narcs]; a->c = ctr; a->r = rad; a->narc = -1; a->aarc = 0; a->arcs = 0; narcs ++; add_object(&ot_arcs,narcs-1); recompute_points(); want_rerender = 1; } static void add_circle_free(void) { FPOINT ctr; FPOINT rp; if ((ds_arc.ctr.i.x == ds_arc.rad.i.x) && (ds_arc.ctr.i.y == ds_arc.rad.i.y)) return; if (ds_arc.ctr.havef) { ctr = ds_arc.ctr.f; } else { ctr = screen_to_world(ds_arc.ctr.i); } if (ds_arc.rad.havef) { rp = ds_arc.rad.f; } else { rp = screen_to_world(ds_arc.rad.i); } add_circle(ctr,len2(sub2(rp,ctr))); } static void add_circle_set(void) { FPOINT ctr; if (ds_arc.ctr.havef) { ctr = ds_arc.ctr.f; } else { ctr = screen_to_world(ds_arc.ctr.i); } add_circle(ctr,len2(sub2(s_setcompass.p1,s_setcompass.p2))); } static void drawclick_circle(const LX_EVENT_ButtonPress *e) { switch (drag) { case DRAG_NONE: if (e->button != 1) return; drag = (s_setcompass.stage == SETC_LOCKED) ? DRAG_SETCIRCLE : DRAG_CIRCLE; if (xhfound) { ds_arc.ctr.havef = 1; ds_arc.ctr.f = xhbest.p; } else { ds_arc.ctr.havef = 0; } ds_arc.ctr.i.x = xhx; ds_arc.ctr.i.y = xhy; drag_up = 0; break; case DRAG_CIRCLE: hide_drag(); drag = DRAG_NONE; if (e->button == 1) add_circle_free(); break; case DRAG_SETCIRCLE: hide_drag(); drag = DRAG_NONE; if (e->button == 1) add_circle_set(); break; default: abort(); break; } } static void drawtool_init(DRAWTOOL *dt, TOOL *t, void (*click)(const LX_EVENT_ButtonPress *)) { LX_XID spm; spm = lx_CreatePixmap(disp,topwin,depth,TOOLSIZE,TOOLSIZE); lx_CopyArea(disp,t->pm,spm,lx_SGC_GC(disp,wingc),0,0,0,0,TOOLSIZE,TOOLSIZE); lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(wpix),LX_GCV_END); lx_fill_rectangle(disp,spm,lx_SGC_GC(disp,wingc),0,0,2,TOOLSIZE-2); lx_fill_rectangle(disp,spm,lx_SGC_GC(disp,wingc),0,TOOLSIZE-2,TOOLSIZE-2,2); lx_fill_rectangle(disp,spm,lx_SGC_GC(disp,wingc),TOOLSIZE-2,2,2,TOOLSIZE-2); lx_fill_rectangle(disp,spm,lx_SGC_GC(disp,wingc),2,0,TOOLSIZE-2,2); dt->t = t; dt->selwin = lx_CreateWindow_va(disp,toolowin,t->winx,0,TOOLSIZE,TOOLSIZE,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixmap(spm), LX_CWV_END); dt->unselwin = lx_CreateWindow_va(disp,toolowin,t->winx,0,TOOLSIZE,TOOLSIZE,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixmap(t->pm), LX_CWV_END); dt->click = click; dt->onselect = 0; dt->ondeselect = 0; } static void setup_segment(TOOL *t) { drawtool_init(&s_segment.dt,t,&drawclick_segment); } static void click_segment(const LX_EVENT_ButtonPress *e) { if (e->button != 1) return; use_drawtool(&s_segment.dt); } static int extend_vet_dist(const DISTRET *r) { switch (drag) { case DRAG_NONE: switch (r->kind) { case DK_LINE_END: case DK_LINE_BODY: return(1); break; default: return(0); break; } break; case DRAG_EXTEND_A: case DRAG_EXTEND_B: return(0); break; default: abort(); break; } } static void extend_select(void) { arena_vet_dist = &extend_vet_dist; } static void extend_deselect(void) { arena_vet_dist = 0; } static void setup_extend(TOOL *t) { drawtool_init(&s_extend.dt,t,&drawclick_extend); s_extend.dt.onselect = &extend_select; s_extend.dt.ondeselect = &extend_deselect; } static void click_extend(const LX_EVENT_ButtonPress *e) { if (e->button != 1) return; use_drawtool(&s_extend.dt); } static void setup_circle(TOOL *t) { drawtool_init(&s_circle.dt,t,&drawclick_circle); } static void click_circle(const LX_EVENT_ButtonPress *e) { if (e->button != 1) return; use_drawtool(&s_circle.dt); } static void setup_setcompass(TOOL *t) { LX_XID pm; s_setcompass.stage = SETC_NONE; lx_ChangeSGC_va(disp,wingc,LX_GCV_Foreground(wpix),LX_GCV_END); pm = lx_CreatePixmap(disp,topwin,depth,TOOLSIZE,TOOLSIZE); lx_CopyArea(disp,t->pm,pm,lx_SGC_GC(disp,wingc),0,0,0,0,TOOLSIZE,TOOLSIZE); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),0,0,2,8); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),0,0,8,2); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),TOOLSIZE-8,0,8,2); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),TOOLSIZE-2,0,2,8); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),0,TOOLSIZE-8,2,8); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),0,TOOLSIZE-2,8,2); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),TOOLSIZE-8,TOOLSIZE-2,8,2); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),TOOLSIZE-2,TOOLSIZE-8,2,8); s_setcompass.win[1] = lx_CreateWindow_va(disp,toolowin,t->winx,0,TOOLSIZE,TOOLSIZE,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixmap(pm), LX_CWV_END); pm = lx_CreatePixmap(disp,topwin,depth,TOOLSIZE,TOOLSIZE); lx_CopyArea(disp,t->pm,pm,lx_SGC_GC(disp,wingc),0,0,0,0,TOOLSIZE,TOOLSIZE); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),0,0,2,TOOLSIZE-2); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),0,TOOLSIZE-2,TOOLSIZE-2,2); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),TOOLSIZE-2,2,2,TOOLSIZE-2); lx_fill_rectangle(disp,pm,lx_SGC_GC(disp,wingc),2,0,TOOLSIZE-2,2); s_setcompass.win[2] = lx_CreateWindow_va(disp,toolowin,t->winx,0,TOOLSIZE,TOOLSIZE,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixmap(pm), LX_CWV_END); s_setcompass.win[0] = lx_CreateWindow_va(disp,toolowin,t->winx,0,TOOLSIZE,TOOLSIZE,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixmap(t->pm), LX_CWV_END); } static void setcompass_abort(void) { s_setcompass.stage = SETC_NONE; drag = DRAG_NONE; arena_click = s_setcompass.prev_arena_click; } static void arena_click_setcompass_2(const LX_EVENT_ButtonPress *e) { hide_drag(); if (e->button != 1) { setcompass_abort(); return; } if (xhfound) { s_setcompass.p2 = xhbest.p; } else { s_setcompass.p2 = screen_to_world((IPOINT){.x=xhx,.y=xhy}); } arena_click = s_setcompass.prev_arena_click; s_setcompass.stage = SETC_LOCKED; drag = DRAG_NONE; raise_window(s_setcompass.win[2]); } static void arena_click_setcompass_1(const LX_EVENT_ButtonPress *e) { if (e->button != 1) { setcompass_abort(); return; } if (xhfound) { s_setcompass.p1 = xhbest.p; } else { s_setcompass.p1 = screen_to_world((IPOINT){.x=xhx,.y=xhy}); } arena_click = &arena_click_setcompass_2; s_setcompass.stage = SETC_SECOND; s_setcompass.p2 = s_setcompass.p1; drag = DRAG_SETCOMPASS; drag_up = 0; } static void click_setcompass(const LX_EVENT_ButtonPress *e) { if (e->button != 1) return; if (drag != DRAG_NONE) { beep(); return; } switch (s_setcompass.stage) { case SETC_NONE: s_setcompass.stage = SETC_FIRST; s_setcompass.prev_arena_click = arena_click; arena_click = &arena_click_setcompass_1; break; case SETC_FIRST: case SETC_SECOND: setcompass_abort(); break; case SETC_LOCKED: s_setcompass.stage = SETC_NONE; break; default: abort(); break; } raise_window(s_setcompass.win[(s_setcompass.stage==SETC_NONE)?0:1]); } static void setup_zoomin(TOOL *t) { lx_CreateWindow_va(disp,toolowin,t->winx,0,TOOLSIZE,TOOLSIZE,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixmap(t->pm), LX_CWV_END); } static void click_zoomin(const LX_EVENT_ButtonPress *e) { FPOINT c; double f; int steps; int nz; switch (e->button) { case 1: steps = 5; break; case 2: steps = 3; break; case 3: steps = 1; break; default: return; break; } nz = zoom - steps; if (nz < MINZOOM) nz = MINZOOM; if (nz == zoom) { beep(); return; } f = pow(.9,zoom-nz); c = scale2(add2(winmin,winmax),.5); winmin = add2(c,scale2(sub2(winmin,c),f)); winmax = add2(c,scale2(sub2(winmax,c),f)); zoom = nz; reset_units(); want_rerender = 1; } static void setup_zoomout(TOOL *t) { lx_CreateWindow_va(disp,toolowin,t->winx,0,TOOLSIZE,TOOLSIZE,0,depth,LX_WCLASS_InputOutput,vi->id, LX_CWV_BackPixmap(t->pm), LX_CWV_END); } static void click_zoomout(const LX_EVENT_ButtonPress *e) { FPOINT c; double f; int steps; int nz; switch (e->button) { case 1: steps = 5; break; case 2: steps = 3; break; case 3: steps = 1; break; } nz = zoom + steps; if (nz > MAXZOOM) nz = MAXZOOM; if (nz == zoom) { beep(); return; } f = pow(.9,zoom-nz); c = scale2(add2(winmin,winmax),.5); winmin = add2(c,scale2(sub2(winmin,c),f)); winmax = add2(c,scale2(sub2(winmax,c),f)); zoom = nz; reset_units(); want_rerender = 1; } int main(void); int main(void) { aio_poll_init(); lx_open(0,0,&x_open_done,0,0,0); aio_event_loop(); return(0); }