/* ZOOM_BASE is the 100th root of 1.1 - that is, 100 pixels of motion produces a 10% change in the value in question. */ #define ZOOM_BASE 1.000953556143896449683201493952576973930477873740054000269709732 /* Dammit. hijacks fmax and fmin on 5.2. */ #define fmax math_h_fmax #define fmin math_h_fmin #include #include #include #include #include #include #undef fmax #undef fmin #include #include #include #include #include #include #include #include "model.h" extern const char *__progname; static XrmDatabase db; static const char *defaults = "\ *Foreground: white\n\ *Background: black\n\ *BorderColor: white\n\ *BorderWidth: 1\n\ *TextMargin: 2\n\ *MenuTolerance: 25\n\ *Name: editmodel\n\ *IconName: editmodel\n\ "; static char *displayname = 0; static char *geomspec_wire = 0; static char *geomspec_solid = 0; static char *foreground = 0; static char *background = 0; static char *bordercstr = 0; static char *borderwstr = 0; static char *textmstr = 0; static char *menutstr = 0; static char *name = 0; static char *iconname = 0; static char *fontname = 0; static char *vis_str_bw = 0; static char *vis_str_col = 0; static int synch = 0; static int argc; static char **argv; static Display *disp; static Screen *scr; static int width; static int height; static Window rootwin; static Colormap defcmap; static int (*preverr)(Display *, XErrorEvent *); static int (*prevIOerr)(Display *); static XColor fgcolour_bw; static XColor fgcolour_col; static XColor bgcolour_bw; static XColor bgcolour_col; static XColor bdcolour_bw; static XColor bdcolour_col; static Pixmap grey50; typedef struct tpl TPL; typedef struct menu MENU; typedef struct menuent MENUENT; typedef struct vis VIS; typedef struct scrpt SCRPT; typedef struct wire WIRE; typedef struct pix PIX; typedef struct light LIGHT; typedef struct irgb IRGB; typedef struct spp SPP; typedef struct swatch SWATCH; struct irgb { unsigned char r; unsigned char g; unsigned char b; } ; struct tpl { const char *name; unsigned long int eventmask; int (*ctl)(TPL *, int, ...); #define TPL_CTL_EVENT 1 #define TPL_CTL_INIT 2 #define TPL_CTL_RESIZE 3 TPL *link; unsigned int flags; #define TPL_F_CREATED 0x00000001 #define TPL_F_UP 0x00000002 Window top; VIS *vis; Window win; Cursor curcurs; int winw; int winh; } ; struct menuent { const char *text; void (*handler)(Time, void *, int, void (*)(void)); int flags; #define ME_F_UNSELECTABLE 0x00000001 #define ME_F_DIRTY 0x00000002 void *arg_ptr; int arg_int; void (*arg_fxn)(void); MENU *menu; int textx; int texty; XCharStruct textbound; } ; #define MENU_FN_DECL(name) static void name(Time, void *, int, void (*)(void)) #define MENU_FN_DEFN(name) static void name(Time t __attribute__((__unused__)), void *arg_v __attribute__((__unused__)), int arg_i __attribute__((__unused__)), void (*arg_fn)(void) __attribute__((__unused__))) #define ENTRY_BLANK { "", 0, ME_F_UNSELECTABLE, 0, 0, 0 } #define ENTRY_TEXT(s) { (s), 0, ME_F_UNSELECTABLE, 0, 0, 0 } #define ENTRY_VOID(s,fn) { (s), (&fn), 0, 0, 0, 0 } #define ENTRY_PTR(s,fn,arg) { (s), (&fn), 0, (arg), 0, 0 } #define ENTRY_INT(s,fn,arg) { (s), (&fn), 0, 0, (arg), 0 } #define ENTRY_FXN(s,fn,arg) { (s), (&fn), 0, 0, 0, (void (*)(void))(&arg) } #define ENTRY_ALL(s,fn,p,i,f) { (s), (&fn), 0, (p), (i), (void (*)(void))(f) } struct menu { MENUENT *entries; int nentries; int flags; #define MENU_F_DIDINIT 0x00000001 int w; int h; } ; #define MENU_INIT(ents) &ents[0], sizeof(ents)/sizeof(ents[0]) struct vis { const char *name; XVisualInfo *vi; Colormap cm; } ; struct scrpt { int clip; /* The code makes some assumptions about these values: - CLIP_NEAR != 0 - CLIP_FAR != 0 - CLIP_NEAR & CLIP_FAR == 0 - CLIP_NEAR != CLIP_FAR (which is implied by the other three) */ #define CLIP_NEAR 1 #define CLIP_FAR 2 XYZ p; double fx; double fy; int ix; int iy; } ; struct spp { XYZ n; XYZ l; SCRPT sp; } ; struct wire { XYZ end1; XYZ end2; SCRPT *sp1; SCRPT *sp2; } ; struct swatch { Window parent; Window win; IRGB *col; void (*changed)(void *); void (*current)(void *, int); void *arg; } ; struct light { LIGHT *link; XYZ dir; IRGB col; SWATCH swatch; } ; struct pix { unsigned char r; unsigned char g; unsigned char b; double z; } ; static VIS bw = { "bw" }; static VIS col = { "colour" }; static int col_bits[3]; static int col_shift[3]; static TPL *tpls; #define EACH_TPL(var) var=tpls;var;var=var->link static GC fwdgc; static GC revgc; static GC colgc; static int wire_ctl(TPL *, int, ...); static int render_ctl(TPL *, int, ...); static int status_ctl(TPL *, int, ...); static TPL wire = { "wireframe", ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | PointerMotionHintMask, wire_ctl }; static TPL render = { "image", ExposureMask | KeyPressMask | ButtonPressMask, render_ctl }; static TPL status = { "status", ExposureMask | KeyPressMask, status_ctl }; static int draw_w_w; static int draw_w_h; static int draw_r_w; static int draw_r_h; static double draw_w_cx; static double draw_w_cy; static double draw_r_cx; static double draw_r_cy; static Cursor arrowcurs; static Cursor barcurs; static Cursor xhaircurs; static int text_margin; static int menu_tolerance; static int borderwidth; static XFontStruct *font; static int deffont; static int baselineskip; static unsigned int want_redraw = 0; #define REDRAW_WIRE 0x00000001 #define REDRAW_RENDER 0x00000002 #define REDRAW_STATUS 0x00000004 static char *filename; static MODEL *model; static XYZ model_min; static XYZ model_max; static XYZ xhair_min; static XYZ xhair_max; static XYZ origin; static XYZ eye; static XYZ fwd; static XYZ up; static XYZ rt; static LIGHT *lights; static IRGB ambient; static SWATCH ambient_swatch; static double bright; static double dispscale_w; static double dispscale_r; static double axislen; static int showorgaxes; static int showwireframe; static int showtrinums; static int showbackfaces; static int shownormals; static int showlightdirs; static IRGB swatchslide_col; static Window swatchslide_parent; static Window swatchslide_swatchwin; static Window swatchslide_rgbwin[3]; static int swatchslide_sliding; static int swatchslide_pos; static SWATCH *swatchslide_swatch; static LIGHT *curlight; static int nwires; static WIRE **wires; static SCRPT *scrpts_w; static SCRPT *scrpts_r; static int scrpts_w_valid; static int scrpts_r_valid; static int moving; typedef enum { MOVE_ROLL = 1, MOVE_PAN, MOVE_SPIN, MOVE_ZOOM, MOVE_NEAR, MOVE_FAR, MOVE_EYETRK, MOVE_ORGTRK, MOVE_RLIGHT, MOVE_SLIGHT, MOVE_PICK, } MOVETYPE; static MOVETYPE movetype = MOVE_ROLL; static int vertex_pick_x; static int vertex_pick_y; static int vertex_pick_pt; static XYZ vertex_pick_pt_at; static int move_curx; static int move_cury; static int move_cx; static int move_cy; static double move_mul; static double clip_near; static double clip_far; static int render_w; static int render_h; static RGB minrgb; static RGB maxrgb; static PIX *dispbuf; static unsigned char *imgbuf; static XImage *img; static int img_r_s; static int img_r_b; static int img_g_s; static int img_g_b; static int img_b_s; static int img_b_b; static void (*rendering)(void); static int render_i; static int render_x; static int render_y; static int render_dbg; static Window menu_top; static Window menu_win; static int curitem; static int drawn_curitem; static int menu_wantquery; static int items_dirty; static MENU *curmenu; #if 0 MENU_FN_DECL(any_menu_new_menu); static int *op_menu_var; static void (*op_menu_fn)(void); MENU_FN_DECL(op_menu_setop); static MENUENT full_op_menu_ents[] = { ENTRY_TEXT("Choose an operation"), ENTRY_BLANK, ENTRY_INT("Clear",op_menu_setop,ROP_CLR), ENTRY_INT("NOR",op_menu_setop,ROP_SRC_NOR_DST), ENTRY_INT("src & ~dst",op_menu_setop,ROP_SRC_AND_NDST), ENTRY_INT("Negate dst",op_menu_setop,ROP_NDST), ENTRY_INT("~src & dst",op_menu_setop,ROP_NSRC_AND_DST), ENTRY_INT("Negated copy",op_menu_setop,ROP_NSRC), ENTRY_INT("XOR",op_menu_setop,ROP_SRC_XOR_DST), ENTRY_INT("NAND",op_menu_setop,ROP_SRC_NAND_DST), ENTRY_INT("AND",op_menu_setop,ROP_SRC_AND_DST), ENTRY_INT("XNOR",op_menu_setop,ROP_SRC_NXOR_DST), ENTRY_INT("Copy",op_menu_setop,ROP_SRC), ENTRY_INT("src | ~dst",op_menu_setop,ROP_SRC_OR_NDST), ENTRY_INT("Noop",op_menu_setop,ROP_DST), ENTRY_INT("~src | dst",op_menu_setop,ROP_NSRC_OR_DST), ENTRY_INT("OR",op_menu_setop,ROP_SRC_OR_DST), ENTRY_INT("Set",op_menu_setop,ROP_SET) }; static MENU full_op_menu = { MENU_INIT(full_op_menu_ents) }; static MENUENT half_op_menu_ents[] = { ENTRY_TEXT("Choose an operation"), ENTRY_BLANK, ENTRY_INT("Clear",op_menu_setop,ROP_CLR), ENTRY_INT("Flip",op_menu_setop,ROP_NDST), ENTRY_INT("Noop",op_menu_setop,ROP_DST), ENTRY_INT("Set",op_menu_setop,ROP_SET) }; static MENU half_op_menu = { MENU_INIT(half_op_menu_ents) }; MENU_FN_DECL(choose_op_menu_buttons); MENU_FN_DECL(choose_op_menu_draw); MENU_FN_DECL(choose_op_menu_copy); static MENUENT choose_op_menu_ents[] = { ENTRY_TEXT("Change which operation"), ENTRY_BLANK, ENTRY_VOID("Buttons",choose_op_menu_buttons), ENTRY_VOID("Drawing",choose_op_menu_draw), ENTRY_VOID("Copy",choose_op_menu_copy) }; static MENU choose_op_menu = { MENU_INIT(choose_op_menu_ents) }; MENU_FN_DECL(polygon_menu_abort); MENU_FN_DECL(polygon_menu_done); MENU_FN_DECL(polygon_menu_del_last); #define FP_EO 1 #define FP_NZ 2 static MENUENT polygon_menu_ents[] = { ENTRY_TEXT("Filled polygon menu"), ENTRY_BLANK, ENTRY_VOID("Delete last point",polygon_menu_del_last), ENTRY_BLANK, ENTRY_VOID("Abort",polygon_menu_abort), ENTRY_BLANK, ENTRY_INT("Done (even-odd)",polygon_menu_done,FP_EO), ENTRY_INT("Done (non-zero)",polygon_menu_done,FP_NZ) }; static MENU polygon_menu = { MENU_INIT(polygon_menu_ents) }; MENU_FN_DECL(paste_flip_menu_rotate); MENU_FN_DECL(paste_flip_menu_reflect_x); MENU_FN_DECL(paste_flip_menu_reflect_y); MENU_FN_DECL(paste_flip_menu_reflect_xy); MENU_FN_DECL(paste_flip_menu_reflect_nxy); static MENUENT paste_flip_menu_ents[] = { ENTRY_TEXT("Paste flip menu"), ENTRY_BLANK, ENTRY_INT("Rotate clockwise",paste_flip_menu_rotate,1), ENTRY_INT("Rotate 180°",paste_flip_menu_rotate,2), ENTRY_INT("Rotate counterclockwise",paste_flip_menu_rotate,3), ENTRY_BLANK, ENTRY_VOID("Reflect about |",paste_flip_menu_reflect_x), ENTRY_VOID("Reflect about -",paste_flip_menu_reflect_y), ENTRY_VOID("Reflect about \\",paste_flip_menu_reflect_xy), ENTRY_VOID("Reflect about /",paste_flip_menu_reflect_nxy) }; static MENU paste_flip_menu = { MENU_INIT(paste_flip_menu_ents) }; MENU_FN_DECL(paste_display_set_alu); MENU_FN_DECL(paste_display_set_hr); static MENUENT paste_display_menu_ents[] = { ENTRY_TEXT("Paste display menu"), ENTRY_BLANK, ENTRY_ALL("Display as ALU(paste-result,dst) xor dst",paste_display_set_alu,0,ROP_DST,paste_disp_resalu), ENTRY_ALL("Display as ALU(paste-result,dst)",paste_display_set_alu,0,0,paste_disp_resalu), ENTRY_ALL("Display as ALU(paste-src,dst) xor dst",paste_display_set_alu,0,ROP_DST,paste_disp_alu), ENTRY_ALL("Display as ALU(paste-src,dst)",paste_display_set_alu,0,0,paste_disp_alu), ENTRY_VOID("Display as hollow rectangle",paste_display_set_hr) }; static MENU paste_display_menu = { MENU_INIT(paste_display_menu_ents) }; MENU_FN_DECL(paste_menu_abort); MENU_FN_DECL(paste_menu_reanchor); MENU_FN_DECL(paste_menu_setop_one); MENU_FN_DECL(paste_menu_setop_all); MENU_FN_DECL(paste_menu_setdisp_one); MENU_FN_DECL(paste_menu_setdisp_all); static MENUENT paste_menu_ents[] = { ENTRY_TEXT("Paste menu"), ENTRY_BLANK, ENTRY_PTR("Reflect/rotate",any_menu_new_menu,&paste_flip_menu), ENTRY_BLANK, ENTRY_VOID("Change display (once)",paste_menu_setdisp_one), ENTRY_VOID("Change display (sticky)",paste_menu_setdisp_all), ENTRY_BLANK, ENTRY_VOID("Re-anchor",paste_menu_reanchor), ENTRY_VOID("Change operation (once)",paste_menu_setop_one), ENTRY_VOID("Change operation (sticky)",paste_menu_setop_all), ENTRY_BLANK, ENTRY_VOID("Abort paste",paste_menu_abort) }; static MENU paste_menu = { MENU_INIT(paste_menu_ents) }; MENU_FN_DECL(select_menu_abort); MENU_FN_DECL(select_menu_regrab); MENU_FN_DECL(select_menu_display_one); MENU_FN_DECL(select_menu_display_all); static MENUENT select_menu_ents[] = { ENTRY_TEXT("Select menu"), ENTRY_BLANK, ENTRY_VOID("Pick corner/side to move",select_menu_regrab), ENTRY_BLANK, ENTRY_INT("Display as solid rectangle (once)",select_menu_display_one,SELR_SR), ENTRY_INT("Display as solid rectangle (sticky)",select_menu_display_all,SELR_SR), ENTRY_BLANK, ENTRY_INT("Display as hollow rectangle (once)",select_menu_display_one,SELR_HR), ENTRY_INT("Display as hollow rectangle (sticky)",select_menu_display_all,SELR_HR), ENTRY_BLANK, ENTRY_VOID("Abort select",select_menu_abort) }; static MENU select_menu = { MENU_INIT(select_menu_ents) }; #endif MENU_FN_DECL(main_menu_quit); MENU_FN_DECL(main_menu_save); MENU_FN_DECL(main_menu_move); MENU_FN_DECL(main_menu_toggle_redraw); MENU_FN_DECL(main_menu_new_light); MENU_FN_DECL(main_menu_del_light); static MENUENT main_menu_wire_ents[] = { ENTRY_VOID("Quit",main_menu_quit), ENTRY_BLANK, ENTRY_INT("Roll",main_menu_move,MOVE_ROLL), ENTRY_INT("Pan",main_menu_move,MOVE_PAN), ENTRY_INT("Spin",main_menu_move,MOVE_SPIN), ENTRY_INT("Zoom",main_menu_move,MOVE_ZOOM), ENTRY_INT("Near clip",main_menu_move,MOVE_NEAR), ENTRY_INT("Far clip",main_menu_move,MOVE_FAR), ENTRY_INT("Eye track",main_menu_move,MOVE_EYETRK), ENTRY_INT("Origin track",main_menu_move,MOVE_ORGTRK), ENTRY_INT("Roll light",main_menu_move,MOVE_RLIGHT), ENTRY_INT("Spin light",main_menu_move,MOVE_SLIGHT), ENTRY_BLANK, ENTRY_ALL("Toggle axes",main_menu_toggle_redraw,&showorgaxes,REDRAW_WIRE,0), ENTRY_ALL("Toggle wireframe display",main_menu_toggle_redraw,&showwireframe,REDRAW_WIRE,0), ENTRY_ALL("Toggle triangle numbers",main_menu_toggle_redraw,&showtrinums,REDRAW_WIRE,0), ENTRY_ALL("Toggle backface removal",main_menu_toggle_redraw,&showbackfaces,REDRAW_WIRE,0), ENTRY_ALL("Toggle normal markers",main_menu_toggle_redraw,&shownormals,REDRAW_WIRE,0), ENTRY_ALL("Toggle light directions",main_menu_toggle_redraw,&showlightdirs,REDRAW_WIRE,0), ENTRY_BLANK, ENTRY_VOID("New light",main_menu_new_light), ENTRY_BLANK, ENTRY_VOID("Delete current light",main_menu_del_light), ENTRY_BLANK, ENTRY_VOID("Save",main_menu_save) }; static MENU main_menu_wire = { MENU_INIT(main_menu_wire_ents) }; MENU_FN_DECL(main_menu_debug_render); static MENUENT main_menu_render_ents[] = { ENTRY_VOID("Quit",main_menu_quit), ENTRY_BLANK, ENTRY_VOID("Debug render",main_menu_debug_render), ENTRY_BLANK, ENTRY_VOID("Save",main_menu_save) }; static MENU main_menu_render = { MENU_INIT(main_menu_render_ents) }; static int junk_direction; static int junk_ascent; static int junk_descent; #define XTE_JUNK &junk_direction,&junk_ascent,&junk_descent static __inline__ int clip(int, int, int) __attribute__((__const__)); static __inline__ int clip(int v, int l, int h) { return((vh)?h:v); } static __inline__ int imin(int, int) __attribute__((__const__)); static __inline__ int imin(int a, int b) { return((ab)?a:b); } static __inline__ double fmin(double, double) __attribute__((__const__)); static __inline__ double fmin(double a, double b) { return((a clip_far) return((SCRPT){.clip=CLIP_FAR}); } u = dot(p,up) * dispscale_w / d; r = dot(p,rt) * dispscale_w / d; return((SCRPT){ .clip = 0, .p = (XYZ) { .x=r, .y=u, .z=d }, .fx = r + draw_w_cx, .fy = draw_w_h-1 - (u + draw_w_cy), .ix = (int)(r + draw_w_cx + 1) - 1, .iy = draw_w_h-1 - ((int)(u + draw_w_cy + 1) - 1) }); } static __inline__ SCRPT project_r(XYZ p, int clipit) { double d; double u; double r; p = sub3(p,eye); d = dot(p,fwd); if (clipit) { if (d < clip_near) return((SCRPT){.clip=CLIP_NEAR}); if (d > clip_far) return((SCRPT){.clip=CLIP_FAR}); } u = dot(p,up) * dispscale_r / d; r = dot(p,rt) * dispscale_r / d; return((SCRPT){ .clip = 0, .p = (XYZ) { .x=r, .y=u, .z=d }, .ix = (int)(r + draw_r_cx + 1) - 1, .iy = draw_r_h-1 - ((int)(u + draw_r_cy + 1) - 1) }); } static void *deconst(const void *cvp) { return((((const char *)cvp)-(const char *)0)+(char *)0); } static void panic_(const char *, int, const char *, const char *, ...) __attribute__((__format__(__printf__,4,5),__noreturn__)); static void panic_(const char *file, int line, const char *fn, const char *fmt, ...) { va_list ap; fprintf(stderr,"PANIC (%s(), \"%s\", line %d): ",fn,file,line); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); abort(); } #define panic(fmt,args...) panic_(__FILE__,__LINE__,__FUNCTION__,fmt , ##args) #ifdef MALLOC_TRACE static FILE *lprf = 0; static void lpr(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void lpr(const char *fmt, ...) { va_list ap; if (lprf == 0) { lprf = fopen("editmodel.malloc","w"); if (lprf == 0) { fprintf(stderr,"%s: editmodel.malloc: %s\n",__progname,strerror(errno)); exit(1); } } va_start(ap,fmt); vfprintf(lprf,fmt,ap); va_end(ap); fflush(lprf); } static void *em_malloc(int, int) __attribute__((__unused__)); static void *em_malloc(int nb, int l) { void *p; lpr("line %d: malloc(%d) -> ",l,nb); p = malloc(nb); lpr("%p\n",p); return(p); } static void *em_realloc(void *, int, int) __attribute__((__unused__)); static void *em_realloc(void *a, int nb, int l) { void *p; lpr("line %d: realloc(%p,%d) -> ",l,a,nb); p = realloc(a,nb); lpr("%p\n",p); return(p); } static void em_free(void *, int) __attribute__((__unused__)); static void em_free(void *a, int l) { lpr("line %d: free(%p)",l,a); free(a); lpr("\n"); } #define malloc(x) em_malloc((x),__LINE__) #define realloc(a,b) em_realloc((a),(b),__LINE__) #define free(x) em_free((x),__LINE__) #endif static void saveargv(int ac, char **av) { int i; int nc; char *abuf; argc = ac; argv = (char **) malloc((ac+1)*sizeof(char *)); nc = 1; for (i=0;i 0) { skip --; continue; } if (**av != '-') { errs += specify_file(av[skip]); continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-display")) { WANTARG(); displayname = av[skip]; continue; } if (!strcmp(*av,"-geom-wire")) { WANTARG(); geomspec_wire = av[skip]; continue; } if (!strcmp(*av,"-geom-solid")) { WANTARG(); geomspec_solid = av[skip]; continue; } if (!strcmp(*av,"-foreground") || !strcmp(*av,"-fg")) { WANTARG(); foreground = av[skip]; continue; } if (!strcmp(*av,"-background") || !strcmp(*av,"-bg")) { WANTARG(); background = av[skip]; continue; } if (!strcmp(*av,"-bordercolor") || !strcmp(*av,"-bd")) { WANTARG(); bordercstr = av[skip]; continue; } if (!strcmp(*av,"-borderwidth") || !strcmp(*av,"-bw")) { WANTARG(); borderwstr = av[skip]; continue; } if (!strcmp(*av,"-margin") || !strcmp(*av,"-m")) { WANTARG(); textmstr = av[skip]; continue; } if (!strcmp(*av,"-menutolerance") || !strcmp(*av,"-mt")) { WANTARG(); menutstr = av[skip]; continue; } if (!strcmp(*av,"-name")) { WANTARG(); name = av[skip]; continue; } if (!strcmp(*av,"-iconname")) { WANTARG(); iconname = av[skip]; continue; } if (!strcmp(*av,"-font") || !strcmp(*av,"-fn")) { WANTARG(); fontname = av[skip]; continue; } if (!strcmp(*av,"-sync")) { synch = 1; continue; } if (!strcmp(*av,"-file")) { WANTARG(); errs += specify_file(av[skip]); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void realloc_wires(void) { int i; nwires = n_locations(model); nwires = nwires * (nwires - 1) / 2; free(wires); wires = 0; if (nwires < 1) return; wires = malloc(nwires*sizeof(*wires)); for (i=nwires-1;i>=0;i--) wires[i] = 0; } static void compute_model_limits(void) { int i; XYZ *l; int first; first = 1; for (i=n_locations(model)-1;i>=0;i--) { l = get_location(model,i); if (first) { model_min = *l; model_max = *l; first = 0; } else { if (l->x < model_min.x) model_min.x = l->x; if (l->y < model_min.y) model_min.y = l->y; if (l->z < model_min.z) model_min.z = l->z; if (l->x > model_max.x) model_max.x = l->x; if (l->y > model_max.y) model_max.y = l->y; if (l->z > model_max.z) model_max.z = l->z; } } xhair_min.x = (2 * model_min.x) - model_max.x; xhair_min.y = (2 * model_min.y) - model_max.y; xhair_min.z = (2 * model_min.z) - model_max.z; xhair_max.x = (2 * model_max.x) - model_min.x; xhair_max.y = (2 * model_max.y) - model_min.y; xhair_max.z = (2 * model_max.z) - model_min.z; } static void read_model(void) { FILE *f; int c; static void errf(const char *s) { const char *nl; while (1) { nl = index(s,'\n'); if (! nl) break; fprintf(stderr,"%s: %s [offset %lu]: %.*s\n",__progname,filename,ftell(f),(int)(nl-s),s); s = nl + 1; } if (s[0]) { fprintf(stderr,"%s: %s [offset %lu]: %s\n",__progname,filename,ftell(f),s); } } if (! filename) { fprintf(stderr,"%s: no filename specified\n",__progname); exit(1); } f = fopen(filename,"r"); if (f == 0) { fprintf(stderr,"%s: %s: %s\n",__progname,filename,strerror(errno)); exit(1); } c = getc(f); if (c == EOF) { model = new_model(); } else { ungetc(c,f); model = load_model(f,&errf); if (! model) exit(1); } fclose(f); compute_model_limits(); scrpts_w = malloc(n_locations(model)*sizeof(*scrpts_w)); scrpts_w_valid = 0; scrpts_r = malloc(n_locations(model)*sizeof(*scrpts_r)); scrpts_r_valid = 0; wires = 0; realloc_wires(); } /* We want axislen to be a power of 10 times one of these numbers: 1 1.5 2 3 5 7.5 We work out the "ideal" axislen, then choose based on logarithms. */ static void pick_axis_len(void) { double l; double ll; int ill; double fll; l = (norm(sub3(origin,eye)) * draw_w_w) / (20 * dispscale_w); if (l < 1e-30) { axislen = 0; return; } ll = log10(l); ill = floor(ll); fll = ll - ill; axislen = 1; while (ill < 0) { axislen /= 10; ill ++; } while (ill > 0) { axislen *= 10; ill --; } /* The magic numbers here are the logarithms of the geometric-mean breakpoints between the above ideal numbers. They were computed to only the precision of a double; the decimal forms here may well be more precise than accurate. */ if (fll < 0.08804562952784056761) /* log10(sqrt(1*1.5)) */ { /* axislen *= 1; */ } else if (fll < 0.23856062735983116773) /* log10(sqrt(1.5*2)) */ { axislen *= 1.5; } else if (fll < 0.38907562519182175009) /* log10(sqrt(2*3)) */ { axislen *= 2; } else if (fll < 0.58804562952784058538) /* log10(sqrt(3*5)) */ { axislen *= 3; } else if (fll < 0.78701563386385933185) /* log10(sqrt(5*7.5)) */ { axislen *= 5; } else if (fll < 0.93753063169585004743) /* log10(sqrt(7.5*10)) */ { axislen *= 7.5; } else { axislen *= 10; } } static void init_view(void) { int i; XYZ min; XYZ max; XYZ l; double s; min = (XYZ){.x=0,.y=0,.z=0}; max = (XYZ){.x=0,.y=0,.z=0}; for (i=n_locations(model)-1;i>=0;i--) { l = *get_location(model,i); if (l.x < min.x) min.x = l.x; else if (l.x > max.x) max.x = l.x; if (l.y < min.y) min.y = l.y; else if (l.y > max.y) max.y = l.y; if (l.z < min.z) min.z = l.z; else if (l.z > max.z) max.z = l.z; } /* since we know max >= 0 and min <= 0, don't bother with fabs()... */ s = fmax(fmax(fmax(max.x,max.y),max.z),fmax(fmax(-min.x,-min.y),-min.z)); origin = (XYZ){.x=0,.y=0,.z=0}; eye = scale(s,(XYZ){.x=3,.y=1,.z=2}); fwd = unit(sub3(origin,eye)); up = unit(sub3((XYZ){.x=0,.y=0,.z=1},scale(fwd.z,fwd))); rt = cross(fwd,up); lights = 0; bright = 1; dispscale_w = 1; dispscale_r = 1; pick_axis_len(); clip_near = (sqrt(9+4+1) - 1.5) * s; clip_far = (sqrt(9+4+1) + 1.5) * s; } static Display *open_display(char *disp) { Display *rv; rv = XOpenDisplay(disp); if (rv == 0) { fprintf(stderr,"%s: can't open display %s\n",__progname,XDisplayName(disp)); exit(1); } return(rv); } static void setup_db(void) { char *str; char *home; XrmDatabase db2; db = XrmGetStringDatabase(defaults); str = XResourceManagerString(disp); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } else { home = getenv("HOME"); if (home) { str = malloc(strlen(home)+1+10+1); sprintf(str,"%s/.Xdefaults",home); db2 = XrmGetFileDatabase(str); if (db2) { XrmMergeDatabases(db2,&db); } free(str); } } } static int err(Display *d, XErrorEvent *ee) { return((*preverr)(d,ee)); } static int ioerr(Display *d) { return((*prevIOerr)(d)); } static void setup_font(void) { deffont = 0; font = fontname ? XLoadQueryFont(disp,fontname) : 0; if (! font) { if (fontname) fprintf(stderr,"%s: can't load font %s, using server default\n",__progname,fontname); font = XQueryFont(disp,XGContextFromGC(XDefaultGCOfScreen(scr))); deffont = 1; } baselineskip = font->ascent + font->descent; } static void pick_visual(VIS *vi, const char *str, int (*pickfn)(XVisualInfo *, int)) { char *cp; XVisualInfo *xvi; int nvi; XVisualInfo template; long int mask; int i; int vpref; template.screen = XScreenNumberOfScreen(scr); if (!str || !*str) { xvi = XGetVisualInfo(disp,VisualScreenMask,&template,&nvi); if (xvi == 0) { fprintf(stderr,"%s: no visuals available?\n",__progname); exit(1); } vi->vi = &xvi[(*pickfn)(xvi,nvi)]; } else { mask = VisualScreenMask | VisualClassMask; if (!strcasecmp(str,"staticgray")) template.class = StaticGray; else if (!strcasecmp(str,"grayscale")) template.class = GrayScale; else if (!strcasecmp(str,"staticcolor")) template.class = StaticColor; else if (!strcasecmp(str,"pseudocolor")) template.class = PseudoColor; else if (!strcasecmp(str,"directcolor")) template.class = DirectColor; else if (!strcasecmp(str,"truecolor")) template.class = TrueColor; else { unsigned long int id; id = strtol(str,&cp,0); if (*cp || (cp == str)) { fprintf(stderr,"%s: %s: invalid visual option\n",__progname,str); exit(1); } template.visualid = (VisualID) id; mask = VisualScreenMask | VisualIDMask; } xvi = XGetVisualInfo(disp,mask,&template,&nvi); if (xvi == 0) { fprintf(stderr,"%s: %s: no matching visual found\n",__progname,str); exit(1); } if (nvi == 0) { fprintf(stderr,"%s: what? XGetVisualInfo returned non-nil but zero count?\n",__progname); exit(1); } vpref = 0; for (i=1;i xvi[vpref].depth) vpref = i; vi->vi = &xvi[vpref]; } } static int pick_visual_bw(XVisualInfo *xvi, int nvi) { int i; int p; Visual *v; v = XDefaultVisualOfScreen(scr); p = -1; for (i=0;i>= 16; } if (! (m & 0x000000ff)) { s += 8; m >>= 8; } if (! (m & 0x0000000f)) { s += 4; m >>= 4; } if (! (m & 0x00000003)) { s += 2; m >>= 2; } if (! (m & 0x00000001)) { s += 1; m >>= 1; } b = 1; if (m & 0xffff0000) { b += 16; m >>= 16; } if (m & 0xffffff00) { b += 8; m >>= 8; } if (m & 0xfffffff0) { b += 4; m >>= 4; } if (m & 0xfffffffc) { b += 2; m >>= 2; } if (m & 0xfffffffe) { b += 1; m >>= 1; } col_bits[x] = b; col_shift[x] = s; } pick_visual(&bw,vis_str_bw,&pick_visual_bw); pick_visual(&col,vis_str_col,&pick_visual_col); if (col.vi->class != TrueColor) { fprintf(stderr,"%s: colour visual isn't TrueColor\n",__progname); exit(1); } setup_colour_values(0,col.vi->red_mask); setup_colour_values(1,col.vi->green_mask); setup_colour_values(2,col.vi->blue_mask); } static void setup_colour(Colormap *cm, char *str, XColor *col) { if (XParseColor(disp,*cm,str,col) == 0) { fprintf(stderr,"%s: bad colour `%s'\n",__progname,str); exit(1); } while (1) { if (XAllocColor(disp,*cm,col) == 0) { if (*cm != defcmap) { fprintf(stderr,"%s: can't allocate colourmap cell for colour `%s'\n",__progname,str); exit(1); } *cm = XCopyColormapAndFree(disp,*cm); continue; } break; } } static void setup_colours(void) { bw.cm = (bw.vi->visual == XDefaultVisualOfScreen(scr)) ? defcmap : XCreateColormap(disp,rootwin,bw.vi->visual,AllocNone); col.cm = (col.vi->visual == XDefaultVisualOfScreen(scr)) ? defcmap : XCreateColormap(disp,rootwin,col.vi->visual,AllocNone); setup_colour(&bw.cm,foreground,&fgcolour_bw); setup_colour(&bw.cm,background,&bgcolour_bw); setup_colour(&bw.cm,bordercstr,&bdcolour_bw); setup_colour(&col.cm,foreground,&fgcolour_col); setup_colour(&col.cm,background,&bgcolour_col); setup_colour(&col.cm,bordercstr,&bdcolour_col); } static void maybeset(char **strp, char *str) { if (str && !*strp) *strp = str; } static char *get_default_value(const char *name, const char *class) { char *type; XrmValue value; if (XrmGetResource(db,name,class,&type,&value) == False) return(0); return(value.addr); } static void setup_numbers(void) { if (textmstr) text_margin = atoi(textmstr); if (menutstr) menu_tolerance = atoi(menutstr); if (borderwstr) borderwidth = atoi(borderwstr); } static void setup_cursor(void) { Pixmap pic; Pixmap mask; GC gc; unsigned int siz; unsigned int h; arrowcurs = XCreateFontCursor(disp,XC_left_ptr); XRecolorCursor(disp,arrowcurs,&fgcolour_col,&bgcolour_col); /* wish we could count on XC_crosshair to have a decent mask! */ XQueryBestSize(disp,CursorShape,rootwin,width,height,&siz,&h); if (h < siz) siz = h; if (width/40 < siz) siz = width / 40; if (height/40 < siz) siz = height / 40; if (! (siz & 1)) siz --; if (siz < 11) siz = 11; pic = XCreatePixmap(disp,rootwin,siz,siz,1); mask = XCreatePixmap(disp,rootwin,siz,siz,1); gc = XCreateGC(disp,pic,0L,(XGCValues *)0); XFillRectangle(disp,mask,gc,0,0,siz,siz); XSetForeground(disp,gc,1L); XFillRectangle(disp,mask,gc,0,(siz/2)-1,siz,3); XFillRectangle(disp,mask,gc,(siz/2)-1,0,3,siz); XSetForeground(disp,gc,0L); XFillRectangle(disp,mask,gc,(siz/2)-1,(siz/2)-1,3,3); XFillRectangle(disp,pic,gc,0,0,siz,siz); XSetForeground(disp,gc,1L); XDrawLine(disp,pic,gc,1,siz/2,siz-2,siz/2); XDrawLine(disp,pic,gc,siz/2,1,siz/2,siz-2); xhaircurs = XCreatePixmapCursor(disp,pic,mask,&fgcolour_col,&bgcolour_col,siz/2,siz/2); XFreePixmap(disp,pic); XFreePixmap(disp,mask); pic = XCreatePixmap(disp,rootwin,3,baselineskip+2,1); XSetForeground(disp,gc,0L); XFillRectangle(disp,pic,gc,0,0,3,baselineskip+2); XSetForeground(disp,gc,1L); XDrawLine(disp,pic,gc,1,1,1,baselineskip); barcurs = XCreatePixmapCursor(disp,pic,None,&fgcolour_col,&bgcolour_col,1,(baselineskip/2)+1); XFreePixmap(disp,pic); XFreeGC(disp,gc); } static void init_tpl(TPL *t, VIS *v) { t->flags = 0; t->vis = v; t->curcurs = arrowcurs; t->winw = -1; t->winh = -1; t->link = tpls; tpls = t; } static void setup_tpl(TPL *tpl, int w, int h, int x, int y) { char *nbuf; unsigned long int attrmask; XSetWindowAttributes attr; XTextProperty wn_prop; XTextProperty in_prop; XSizeHints normal_hints; XWMHints wm_hints; XClassHint class_hints; attrmask = 0; attr.background_pixmap = grey50; attrmask |= CWBackPixmap; attr.border_pixel = bdcolour_col.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = StructureNotifyMask; attrmask |= CWEventMask; attr.colormap = col.cm; attrmask |= CWColormap; attr.cursor = arrowcurs; attrmask |= CWCursor; tpl->top = XCreateWindow(disp,rootwin,x,y,w,h,borderwidth,col.vi->depth,InputOutput,col.vi->visual,attrmask,&attr); asprintf(&nbuf,"%s %s",name,tpl->name); wn_prop.value = (unsigned char *) nbuf; wn_prop.encoding = XA_STRING; wn_prop.format = 8; wn_prop.nitems = strlen(nbuf); asprintf(&nbuf,"%s %s",iconname,tpl->name); in_prop.value = (unsigned char *) nbuf; in_prop.encoding = XA_STRING; in_prop.format = 8; in_prop.nitems = strlen(nbuf); normal_hints.flags = PMinSize | PResizeInc | PSize; normal_hints.min_width = 1; normal_hints.min_height = 1; normal_hints.width_inc = 1; normal_hints.height_inc = 1; normal_hints.width = w; normal_hints.height = h; wm_hints.flags = InputHint; wm_hints.input = True; class_hints.res_name = deconst("editmodel"); class_hints.res_class = deconst("Editor"); XSetWMProperties(disp,tpl->top,&wn_prop,&in_prop,argv,argc,&normal_hints,&wm_hints,&class_hints); free(wn_prop.value); free(in_prop.value); attrmask = 0; attr.background_pixmap = None; attrmask |= CWBackPixmap; attr.border_pixel = 0; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = tpl->eventmask; attrmask |= CWEventMask; attr.colormap = tpl->vis->cm; attrmask |= CWColormap; attr.cursor = None; attrmask |= CWCursor; tpl->win = XCreateWindow(disp,tpl->top,0,0,1,1,0,tpl->vis->vi->depth,InputOutput,tpl->vis->vi->visual,attrmask,&attr); XMapRaised(disp,tpl->win); tpl->flags |= TPL_F_CREATED; (*tpl->ctl)(tpl,TPL_CTL_INIT,w,h); } static GC setup_gc(int fxn, Pixmap p, unsigned long int fg, unsigned long int bg) { unsigned long int gcvalmask; XGCValues gcval; GC gc; gcvalmask = 0; if (fxn != GXcopy) { gcval.function = fxn; gcvalmask |= GCFunction; } if (fg != 0) { gcval.foreground = fg; gcvalmask |= GCForeground; } if (bg != 1) { gcval.background = bg; gcvalmask |= GCBackground; } if (! deffont) { gcval.font = font->fid; gcvalmask |= GCFont; } gc = XCreateGC(disp,p,gcvalmask,&gcval); return(gc); } static void setup_windows(void) { int w; int h; Pixmap p; p = XCreatePixmap(disp,rootwin,1,1,bw.vi->depth); fwdgc = setup_gc(GXcopy,p,fgcolour_bw.pixel,bgcolour_bw.pixel); revgc = setup_gc(GXcopy,p,bgcolour_bw.pixel,fgcolour_bw.pixel); if (bw.vi->depth != col.vi->depth) { XFreePixmap(disp,p); p = XCreatePixmap(disp,rootwin,1,1,col.vi->depth); } colgc = setup_gc(GXcopy,p,0,0); XFreePixmap(disp,p); grey50 = XCreatePixmap(disp,rootwin,2,2,col.vi->depth); { GC gc; gc = XCreateGC(disp,grey50,0,0); XSetForeground(disp,gc,bgcolour_col.pixel); XDrawPoint(disp,grey50,gc,0,0); XDrawPoint(disp,grey50,gc,1,1); XSetForeground(disp,gc,fgcolour_col.pixel); XDrawPoint(disp,grey50,gc,0,1); XDrawPoint(disp,grey50,gc,1,0); XFreeGC(disp,gc); } init_tpl(&wire,&bw); init_tpl(&render,&col); init_tpl(&status,&bw); w = width / 3; h = height / 3; if (w < h) h = w; else w = h; draw_w_w = -1; draw_w_h = -1; draw_r_w = -1; draw_r_h = -1; setup_tpl( &render, w, h, (width-(w+(2*borderwidth)))/4, (height-(h+(2*borderwidth)))/2 ); setup_tpl( &wire, w, h, ((width-(w+(2*borderwidth)))*3)/4, (height-(h+(2*borderwidth)))/2 ); setup_tpl(&status,1,1,0,0); } static Time cur_time(void) { static Window w = None; XEvent e; if (w == None) { XSetWindowAttributes attr; attr.event_mask = PropertyChangeMask; w = XCreateWindow(disp,rootwin,0,0,1,1,0,0,InputOnly,CopyFromParent,CWEventMask,&attr); } XChangeProperty(disp,w,XA_PRIMARY,XA_PRIMARY,8,PropModeAppend,0,0); XWindowEvent(disp,w,PropertyChangeMask,&e); return(e.xproperty.time); } static void init_menu(MENU *m) { int i; int maxw; int y; MENUENT *me; y = 0; maxw = 0; for (i=0;inentries;i++) { me = &m->entries[i]; me->menu = m; me->texty = y + font->ascent; y += baselineskip; XTextExtents(font,me->text,strlen(me->text),XTE_JUNK,&me->textbound); if (me->textbound.width > maxw) maxw = me->textbound.width; } m->w = maxw; m->h = y; for (i=0;inentries;i++) { me = &m->entries[i]; me->textx = (maxw - me->textbound.width) / 2; } m->flags |= MENU_F_DIDINIT; } static void menu_popdown(void) { XUnmapWindow(disp,menu_top); curmenu = 0; } static void set_curitem_xy(int x, int y) { if ( (x < 0) || (y < 0) || (x >= curmenu->w) || (y >= curmenu->h) ) { curitem = -1; #define M (text_margin+borderwidth+menu_tolerance) if ( (x < -M) || (y < -M) || (x >= curmenu->w+M) || (y >= curmenu->h+M) ) { menu_popdown(); } #undef M } else { curitem = y / baselineskip; } } static void menu_choose(MENU *m, Time time) { int x; int y; int w; int h; Window ret_root; Window ret_child; int ret_rootx; int ret_rooty; int ret_winx; int ret_winy; unsigned int ret_mask; if (curmenu) panic("recursive call to menu_choose"); if (time == CurrentTime) time = cur_time(); if (XGrabPointer(disp,rootwin,False,ButtonPressMask|ButtonReleaseMask|PointerMotionMask|PointerMotionHintMask|EnterWindowMask|LeaveWindowMask,GrabModeSync,GrabModeAsync,None,None,time) != GrabSuccess) { curmenu = 0; return; } if (XQueryPointer(disp,rootwin,&ret_root,&ret_child,&ret_rootx,&ret_rooty,&ret_winx,&ret_winy,&ret_mask) != True) { XUngrabPointer(disp,CurrentTime); curmenu = 0; return; } if (! (m->flags & MENU_F_DIDINIT)) init_menu(m); curmenu = m; XResizeWindow(disp,menu_win,m->w,m->h); w = m->w + (2 * text_margin) + (2 * borderwidth); h = m->h + (2 * text_margin) + (2 * borderwidth); x = ret_rootx - (w/2); y = ret_rooty - (h/2); if (x+w > width) x = width - w; if (y+h > height) y = height - h; if (x < 0) x = 0; if (y < 0) y = 0; w -= 2 * borderwidth; h -= 2 * borderwidth; XMoveResizeWindow(disp,menu_top,x,y,w,h); XMapRaised(disp,menu_top); if (XGrabPointer(disp,menu_win,False,ButtonPressMask|ButtonReleaseMask|PointerMotionMask|PointerMotionHintMask|EnterWindowMask|LeaveWindowMask,GrabModeSync,GrabModeAsync,None,None,time) != GrabSuccess) { XUnmapWindow(disp,menu_top); XUngrabPointer(disp,CurrentTime); curmenu = 0; return; } XAllowEvents(disp,AsyncPointer,time); drawn_curitem = -1; menu_wantquery = 1; items_dirty = 1; set_curitem_xy(ret_rootx-x,ret_rooty-y); } static WIRE *new_wire_2(void) { WIRE *w; char *p; p = malloc(((sizeof(WIRE)+(sizeof(SCRPT)*3)-1)/sizeof(SCRPT))*sizeof(SCRPT)); w = (WIRE *) p; w->sp1 = ((SCRPT *)p) + ((sizeof(WIRE)+sizeof(SCRPT)-1)/sizeof(SCRPT)); w->sp2 = w->sp1 + 1; return(w); } static WIRE *new_wire_1(void) { WIRE *w; char *p; p = malloc(((sizeof(WIRE)+(sizeof(SCRPT)*2)-1)/sizeof(SCRPT))*sizeof(SCRPT)); w = (WIRE *) p; w->sp1 = ((SCRPT *)p) + ((sizeof(WIRE)+sizeof(SCRPT)-1)/sizeof(SCRPT)); return(w); } static WIRE *new_wire_0(void) { return(malloc(sizeof(WIRE))); } static XYZ clipped_wire_point(int a, int b, double clipat) { XYZ pa; XYZ pb; double da; double db; pa = *get_location(model,a); pb = *get_location(model,b); da = dot(sub3(pa,eye),fwd); db = dot(sub3(pb,eye),fwd); return(interp3(pa,(clipat-da)/(db-da),pb)); } static void get_scrpts_w(void) { int i; if (scrpts_w_valid) return; for (i=n_locations(model)-1;i>=0;i--) scrpts_w[i] = project_w(*get_location(model,i),1); scrpts_w_valid = 1; } static int find_closest_scrpt(int x, int y) { int i; SCRPT *p; double d; int best; double bestd; best = -1; for (i=n_locations(model)-1;i>=0;i--) { p = &scrpts_w[i]; if (p->clip) continue; d = hypot(p->fx-x,p->fy-y); if ((best < 0) || (d < bestd)) { best = i; bestd = d; } } return(best); } static double clip_plane(int clip) { switch (clip) { case CLIP_NEAR: return(clip_near); break; case CLIP_FAR: return(clip_far); break; } abort(); } static void redraw_wire(void) { int i; int j; int l[3]; XYZ c[3]; int x; static void simple_line(XYZ a, XYZ b) { SCRPT pa; SCRPT pb; pa = project_w(a,0); pb = project_w(b,0); XDrawLine(disp,wire.win,fwdgc,pa.ix,pa.iy,pb.ix,pb.iy); } static void text_tag(const char *s, XYZ p) { SCRPT sp; XCharStruct sz; int l; sp = project_w(p,0); l = strlen(s); XTextExtents(font,s,l,XTE_JUNK,&sz); XDrawString(disp,wire.win,fwdgc,sp.ix-(sz.width/2),sp.iy+(baselineskip/2),s,l); } XClearWindow(disp,wire.win); for (i=nwires-1;i>=0;i--) { free(wires[i]); wires[i] = 0; } get_scrpts_w(); switch (movetype) { default: vertex_pick_pt = -1; break; case MOVE_PICK: vertex_pick_pt = find_closest_scrpt(vertex_pick_x,vertex_pick_y); if (vertex_pick_pt >= 0) vertex_pick_pt_at = *get_location(model,vertex_pick_pt); want_redraw |= REDRAW_STATUS; break; } get_scrpts_w(); for (i=n_triangles(model)-1;i>=0;i--) { TRIANGLE *t; static void fn(int a, int b) { int x; WIRE *w; if (a == b) abort(); if (a > b) { int t; t = a; a = b; b = t; } x = ((b * (b-1)) / 2) + a; if (wires[x]) return; /* * Four cases: * a) Both points clipped by the same plane: * Entirely clipped away. * b) Both points clipped, not by the same plane: * Clip both ends to a new line. * c) One point clipped, one unclipped: * Clip the clipped end to a new line. * d) Not clipped at all: * Draw as-is. */ if (scrpts_w[a].clip & scrpts_w[b].clip) { /* case a */ w = 0; } else if (scrpts_w[a].clip && scrpts_w[b].clip) { /* case b */ w = new_wire_2(); w->end1 = clipped_wire_point(a,b,clip_near); w->end2 = clipped_wire_point(a,b,clip_far); *w->sp1 = project_w(w->end1,0); *w->sp2 = project_w(w->end2,0); } else if (scrpts_w[a].clip) { /* case c, point a clipped */ w = new_wire_1(); w->end1 = clipped_wire_point(a,b,clip_plane(scrpts_w[a].clip)); *w->sp1 = project_w(w->end1,0); w->end2 = *get_location(model,b); w->sp2 = &scrpts_w[b]; } else if (scrpts_w[b].clip) { /* case c, point b clipped */ w = new_wire_1(); w->end1 = clipped_wire_point(a,b,clip_plane(scrpts_w[b].clip)); *w->sp1 = project_w(w->end1,0); w->end2 = *get_location(model,a); w->sp2 = &scrpts_w[a]; } else { /* case d */ w = new_wire_0(); w->end1 = *get_location(model,a); w->sp1 = &scrpts_w[a]; w->end2 = *get_location(model,b); w->sp2 = &scrpts_w[b]; } wires[x] = w; } t = get_triangle(model,i); l[0] = get_point(model,t->corner[0])->loc; l[1] = get_point(model,t->corner[1])->loc; l[2] = get_point(model,t->corner[2])->loc; if (scrpts_w[l[0]].clip & scrpts_w[l[1]].clip & scrpts_w[l[2]].clip) { /* all three points clipped by the same plane - clip away the whole triangle. In particular, don't even check showtrinums. */ continue; } c[0] = *get_location(model,l[0]); c[1] = *get_location(model,l[1]); c[2] = *get_location(model,l[2]); if ( !showbackfaces && ( dot( sub3(c[0],eye), cross(sub3(c[0],c[1]),sub3(c[2],c[1])) ) > 0 ) ) continue; fn(l[0],l[1]); fn(l[1],l[2]); fn(l[2],l[0]); if (showtrinums) { char nbuf[16]; sprintf(&nbuf[0],"%d",i); text_tag(&nbuf[0],scale(1/3.0,add3(add3(c[0],c[1]),c[2]))); } } if (showwireframe) { for (i=n_locations(model)-1;i>=0;i--) { SCRPT *p; WIRE *w; p = &scrpts_w[i]; if (i == vertex_pick_pt) { } if ( (p->ix >= 0) && (p->ix < draw_w_w) && (p->iy >= 0) && (p->iy < draw_w_h) && !p->clip ) { XDrawPoint(disp,wire.win,fwdgc,p->ix,p->iy); } for (j=i-1;j>=0;j--) { x = ((i * (i-1)) / 2) + j; w = wires[x]; if (! w) continue; XDrawLine(disp,wire.win,fwdgc,w->sp1->ix,w->sp1->iy,w->sp2->ix,w->sp2->iy); } } } if (shownormals) { for (i=n_points(model)-1;i>=0;i--) { POINT *p; p = get_point(model,i); if (scrpts_w[p->loc].clip) continue; switch (p->type) { case PT_SURFACE: { XYZ l; l = *get_location(model,p->loc); simple_line(l,add3(l,scale(axislen/2,p->normal))); } break; case PT_CORNER: break; default: panic("bad type %d for point #%d\n",p->type,i); break; } } } if (showlightdirs) { LIGHT *l; for (l=lights;l;l=l->link) { simple_line(origin,add3(origin,scale(axislen,l->dir))); if (l == curlight) text_tag("*",add3(origin,scale(axislen*1.5,l->dir))); } } if (showorgaxes) { simple_line(origin,add3(origin,(XYZ){.x=axislen,.y=0,.z=0})); simple_line(origin,add3(origin,(XYZ){.x=0,.y=axislen,.z=0})); simple_line(origin,add3(origin,(XYZ){.x=0,.y=0,.z=axislen})); text_tag("X",add3(origin,(XYZ){.x=1.5*axislen,.y=0,.z=0})); text_tag("Y",add3(origin,(XYZ){.x=0,.y=1.5*axislen,.z=0})); text_tag("Z",add3(origin,(XYZ){.x=0,.y=0,.z=1.5*axislen})); } if (vertex_pick_pt >= 0) { XYZ *l; l = get_location(model,vertex_pick_pt); simple_line( (XYZ){ .x = xhair_min.x, .y = l->y, .z = l->z}, (XYZ){ .x = xhair_max.x, .y = l->y, .z = l->z} ); simple_line( (XYZ){ .x = l->x, .y = xhair_min.y, .z = l->z}, (XYZ){ .x = l->x, .y = xhair_max.y, .z = l->z} ); simple_line( (XYZ){ .x = l->x, .y = l->y, .z = xhair_min.z}, (XYZ){ .x = l->x, .y = l->y, .z = xhair_max.z} ); } } static const char *movetype_name(MOVETYPE t) { static char badbuf[64]; switch (t) { case MOVE_ROLL: return("Roll"); break; case MOVE_PAN: return("Pan"); break; case MOVE_SPIN: return("Spin"); break; case MOVE_ZOOM: return("Zoom"); break; case MOVE_NEAR: return("Near clip"); break; case MOVE_FAR: return("Far clip"); break; case MOVE_EYETRK: return("Eye track"); break; case MOVE_ORGTRK: return("Origin track"); break; case MOVE_RLIGHT: return("Roll light"); break; case MOVE_SLIGHT: return("Spin light"); break; case MOVE_PICK: return("Pick vertex"); break; } sprintf(&badbuf[0],"?%d",(int)t); return(&badbuf[0]); } static void redraw_status(void) { typedef struct s S; struct s { char *s; int l; XCharStruct tb; int w; } ; static S *sv; static int sa = 0; int sn; int i; int curw; int curh; LIGHT *l; auto int vsetstr(const char *, va_list) __attribute__((__format__(__printf__,1,0))); auto int vsetstr(const char *fmt, va_list ap) { char *s; vasprintf(&s,fmt,ap); if (sn >= sa) sv = realloc(sv,(sa=sn+8)*sizeof(*sv)); sv[sn].s = s; sv[sn].l = strlen(s); XTextExtents(font,s,sv[sn].l,XTE_JUNK,&sv[sn].tb); sv[sn].w = sv[sn].tb.width; return(sn++); } auto int setstr(const char *, ...) __attribute__((__format__(__printf__,1,2))); auto int setstr(const char *fmt, ...) { va_list ap; int rv; va_start(ap,fmt); rv = vsetstr(fmt,ap); va_end(ap); return(rv); } auto int setstr_swatch(SWATCH *, const char *, ...) __attribute__((__format__(__printf__,2,3))); auto int setstr_swatch(SWATCH *s, const char *fmt, ...) { va_list ap; int i; va_start(ap,fmt); i = vsetstr(fmt,ap); va_end(ap); XMoveWindow(disp,s->parent,text_margin+sv[i].tb.width+2,(i*baselineskip)+text_margin); sv[i].w += 1 + (baselineskip * 2) + 1; return(i); } sn = 0; setstr("Origin: (%g,%g,%g)",origin.x,origin.y,origin.z); setstr("Eye: (%g,%g,%g)",eye.x,eye.y,eye.z); setstr("Zoom: %g",dispscale_r); setstr("Clip: %g..%g",clip_near,clip_far); setstr("Axis length: %g",axislen); setstr("Brightness: %g",bright); setstr("Move type: %s",movetype_name(movetype)); switch (movetype) { default: break; case MOVE_PICK: if (vertex_pick_pt >= 0) setstr("Picked vertex: %d (%g,%g,%g)",vertex_pick_pt,vertex_pick_pt_at.x,vertex_pick_pt_at.y,vertex_pick_pt_at.z); break; } setstr_swatch(&ambient_swatch,"Ambient: "); for (l=lights;l;l=l->link) { setstr_swatch(&l->swatch,"Light: (%g,%g,%g) ",l->dir.x,l->dir.y,l->dir.z); } curw = 0; curh = sn * baselineskip; if (swatchslide_swatch) { XMoveWindow(disp,swatchslide_parent,text_margin,curh+text_margin); XMapWindow(disp,swatchslide_parent); curw = (baselineskip*3) + 256; curh += baselineskip * 3; } else { XUnmapWindow(disp,swatchslide_parent); } for (i=0;i curw) curw = sv[i].w; curw += 2 * text_margin; curh += 2 * text_margin; if ((curw > status.winw) || (curh > status.winh)) { XWindowChanges c; c.width = curw; c.height = curh; XReconfigureWMWindow(disp,status.top,XScreenNumberOfScreen(scr),CWWidth|CWHeight,&c); } XClearWindow(disp,status.win); for (i=0;iascent+text_margin,sv[i].s,sv[i].l); free(sv[i].s); } #undef N } static unsigned long int irgb_to_colpix(IRGB rgb) { return( (((rgb.r*0x0101)>>(16-col_bits[0])) << col_shift[0]) | (((rgb.g*0x0101)>>(16-col_bits[1])) << col_shift[1]) | (((rgb.b*0x0101)>>(16-col_bits[2])) << col_shift[2]) ); } static void nil_current(void *arg __attribute__((__unused__)), int cur __attribute__((__unused__))) { } static void setup_swatch(SWATCH *s, IRGB *cp, void (*changed)(void *), void (*current)(void *, int), void *arg) { unsigned long int attrmask; XSetWindowAttributes attr; s->col = cp; s->changed = changed; s->current = current ? : &nil_current; s->arg = arg; attrmask = 0; attr.background_pixel = bgcolour_bw.pixel; attrmask |= CWBackPixel; attr.border_pixel = bdcolour_bw.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = ButtonPressMask; attrmask |= CWEventMask; attr.colormap = bw.cm; attrmask |= CWColormap; attr.cursor = None; attrmask |= CWCursor; s->parent = XCreateWindow(disp,status.win,0,0,baselineskip*2,baselineskip-2,1,bw.vi->depth,InputOutput,bw.vi->visual,attrmask,&attr); XMapWindow(disp,s->parent); attrmask = 0; attr.background_pixel = irgb_to_colpix(*cp); attrmask |= CWBackPixel; attr.border_pixel = bdcolour_col.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = 0; attrmask |= CWEventMask; attr.colormap = col.cm; attrmask |= CWColormap; attr.cursor = None; attrmask |= CWCursor; s->win = XCreateWindow(disp,s->parent,1,1,(baselineskip*2)-2,baselineskip-4,0,col.vi->depth,InputOutput,col.vi->visual,attrmask,&attr); XMapWindow(disp,s->win); } static void destroy_swatch(SWATCH *s) { XDestroyWindow(disp,s->win); XDestroyWindow(disp,s->parent); } static void trigger_render(void *arg __attribute__((__unused__))) { want_redraw |= REDRAW_RENDER; } static void curswatch_light(void *lvp, int cur) { LIGHT *l; l = lvp; curlight = cur ? l : 0; } static LIGHT *add_light(XYZ dir, IRGB colour) { LIGHT *l; l = malloc(sizeof(LIGHT)); l->link = lights; lights = l; l->dir = unit(dir); l->col = colour; setup_swatch(&l->swatch,&l->col,&trigger_render,&curswatch_light,l); return(l); } static void set_slide_swatch(SWATCH *s) { if (swatchslide_swatch) { XSetWindowBackground(disp,swatchslide_swatch->parent,bgcolour_bw.pixel); XClearWindow(disp,swatchslide_swatch->parent); (*swatchslide_swatch->current)(swatchslide_swatch->arg,0); } if (swatchslide_swatch == s) { swatchslide_swatch = 0; } else { swatchslide_swatch = s; swatchslide_col = *s->col; XSetWindowBackground(disp,swatchslide_swatchwin,irgb_to_colpix(swatchslide_col)); XClearWindow(disp,swatchslide_swatchwin); XClearArea(disp,swatchslide_rgbwin[0],0,0,0,0,True); XClearArea(disp,swatchslide_rgbwin[1],0,0,0,0,True); XClearArea(disp,swatchslide_rgbwin[2],0,0,0,0,True); XSetWindowBackground(disp,s->parent,bdcolour_bw.pixel); XClearWindow(disp,s->parent); (*swatchslide_swatch->current)(swatchslide_swatch->arg,1); } want_redraw |= REDRAW_STATUS; } MENU_FN_DEFN(main_menu_quit) { XSync(disp,True); exit(0); } MENU_FN_DEFN(main_menu_move) { if (movetype == (MOVETYPE)arg_i) return; switch (movetype) { default: break; case MOVE_PICK: want_redraw |= REDRAW_WIRE | REDRAW_STATUS; break; } movetype = (MOVETYPE) arg_i; switch (movetype) { default: break; case MOVE_PICK: want_redraw |= REDRAW_WIRE | REDRAW_STATUS; break; } want_redraw |= REDRAW_STATUS; } MENU_FN_DEFN(main_menu_debug_render) { render_dbg = 1; want_redraw |= REDRAW_RENDER; } MENU_FN_DEFN(main_menu_save) { FILE *f; f = fopen(filename,"w"); if (f == 0) { fprintf(stderr,"%s: can't write %s: %s\n",__progname,filename,strerror(errno)); XBell(disp,0); return; } save_model(model,f); fclose(f); } MENU_FN_DEFN(main_menu_toggle_redraw) { *(int *)arg_v = ! *(int *)arg_v; want_redraw |= arg_i; } MENU_FN_DEFN(main_menu_new_light) { curlight = add_light((XYZ){.x=1,.y=1,.z=1},(IRGB){.r=0,.g=0,.b=0}); set_slide_swatch(&curlight->swatch); want_redraw |= REDRAW_STATUS; } MENU_FN_DEFN(main_menu_del_light) { LIGHT **lp; LIGHT *l; LIGHT *cl; cl = curlight; if (! curlight) { XBell(disp,0); return; } set_slide_swatch(&ambient_swatch); lp = &lights; while ((l = *lp)) { if (l == cl) { *lp = l->link; destroy_swatch(&l->swatch); } else { lp = &l->link; } } want_redraw |= REDRAW_STATUS; } static void move_move(int x, int y) { switch (movetype) { case MOVE_ROLL: { double r; XYZ e; e = sub3(eye,origin); r = norm(e); e = add3(e,add3(scale((move_curx-x)*2*move_mul/dispscale_w,rt), scale((y-move_cury)*2*move_mul/dispscale_w,up))); move_curx = x; move_cury = y; e = scale(r/norm(e),e); eye = add3(e,origin); fwd = unit(scale(-1,e)); up = unit(sub3(up,scale(dot(up,fwd),fwd))); rt = cross(fwd,up); } want_redraw |= REDRAW_STATUS | REDRAW_WIRE | REDRAW_RENDER; scrpts_w_valid = 0; scrpts_r_valid = 0; break; case MOVE_PAN: { double r; XYZ e; e = sub3(origin,eye); r = norm(e); e = add3(e,add3(scale((move_curx-x)*2*move_mul/dispscale_w,rt), scale((y-move_cury)*2*move_mul/dispscale_w,up))); move_curx = x; move_cury = y; e = scale(r/norm(e),e); origin = add3(eye,e); fwd = unit(e); up = unit(sub3(up,scale(dot(up,fwd),fwd))); rt = cross(fwd,up); } want_redraw |= REDRAW_STATUS | REDRAW_WIRE | REDRAW_RENDER; scrpts_w_valid = 0; scrpts_r_valid = 0; break; case MOVE_SPIN: { double a; if ((x == move_curx) && (y == move_cury)) return; if ((x == move_cx) && (y == move_cy)) return; if ((move_curx != move_cx) || (move_cury != move_cy)) { a = atan2(move_cy-move_cury,move_curx-move_cx) - atan2(move_cy-y,x-move_cx); rt = unit(add3(scale(cos(a),rt),scale(sin(a),up))); up = cross(rt,fwd); } move_curx = x; move_cury = y; } want_redraw |= REDRAW_WIRE | REDRAW_RENDER; scrpts_w_valid = 0; scrpts_r_valid = 0; break; case MOVE_ZOOM: { double f; f = pow(ZOOM_BASE,(move_cury-y)*move_mul); dispscale_r *= f; dispscale_w *= f; move_cury = y; pick_axis_len(); } want_redraw |= REDRAW_STATUS | REDRAW_WIRE | REDRAW_RENDER; scrpts_w_valid = 0; scrpts_r_valid = 0; break; case MOVE_NEAR: { clip_near *= pow(ZOOM_BASE,(move_cury-y)*move_mul); move_cury = y; if (clip_near > clip_far) { double t; t = clip_near; clip_near = clip_far; clip_far = t; movetype = MOVE_FAR; } } want_redraw |= REDRAW_STATUS | REDRAW_WIRE | REDRAW_RENDER; scrpts_w_valid = 0; scrpts_r_valid = 0; break; case MOVE_FAR: { clip_far *= pow(ZOOM_BASE,(move_cury-y)*move_mul); move_cury = y; if (clip_far < clip_near) { double t; t = clip_near; clip_near = clip_far; clip_far = t; movetype = MOVE_NEAR; } } want_redraw |= REDRAW_STATUS | REDRAW_WIRE | REDRAW_RENDER; scrpts_w_valid = 0; scrpts_r_valid = 0; break; case MOVE_EYETRK: eye = add3(origin,scale(pow(ZOOM_BASE,(y-move_cury)*move_mul),sub3(eye,origin))); move_curx = x; move_cury = y; want_redraw |= REDRAW_STATUS | REDRAW_WIRE | REDRAW_RENDER; scrpts_w_valid = 0; scrpts_r_valid = 0; break; case MOVE_ORGTRK: origin = add3(eye,scale(pow(ZOOM_BASE,(move_cury-y)*move_mul),sub3(origin,eye))); move_curx = x; move_cury = y; want_redraw |= REDRAW_STATUS; if (showorgaxes) want_redraw |= REDRAW_WIRE; break; case MOVE_RLIGHT: if (curlight) { XYZ e0; XYZ e; XYZ axis; double c; double s; e0 = unit(sub3(eye,origin)); e = unit(add3( e0, add3( scale((move_curx-x)*2*move_mul/dispscale_w,rt), scale((y-move_cury)*2*move_mul/dispscale_w,up) ) )); axis = cross(e0,e); s = norm(axis); if (s > 1e-5) { double d_p; double d_x; double d_y; XYZ yhat; axis = scale(1/s,axis); e0 = unit(sub3(e0,scale(dot(e0,axis),axis))); yhat = unit(cross(axis,e0)); c = sqrt(1-(s*s)); d_p = dot(curlight->dir,axis); d_x = dot(curlight->dir,e0); d_y = dot(curlight->dir,yhat); curlight->dir = unit(add3( scale(d_p,axis), add3( scale((c*d_x)+(s*d_y),e0), scale((c*d_y)-(s*d_x),yhat) ) )); want_redraw |= REDRAW_STATUS | REDRAW_WIRE | REDRAW_RENDER; } move_curx = x; move_cury = y; } break; case MOVE_SLIGHT: if (curlight) { if ((x == move_curx) && (y == move_cury)) return; if ((x == move_cx) && (y == move_cy)) return; if ((move_curx != move_cx) || (move_cury != move_cy)) { double a; double lx; double ly; double lz; double c; double s; a = atan2(move_cy-move_cury,move_curx-move_cx) - atan2(move_cy-y,x-move_cx); s = sin(a); c = cos(a); lx = dot(curlight->dir,rt); ly = dot(curlight->dir,up); lz = dot(curlight->dir,fwd); curlight->dir = unit(add3( scale(lz,fwd), add3( scale((c*lx)+(s*ly),rt), scale((c*ly)-(s*lx),up) ) )); want_redraw |= REDRAW_STATUS | REDRAW_WIRE | REDRAW_RENDER; } move_curx = x; move_cury = y; } break; case MOVE_PICK: abort(); break; } } static void typein(XKeyEvent *e) { KeySym ks; static XComposeStatus cs; XLookupString(e,0,0,&ks,&cs); switch (ks) { case XK_r: printf("Roll\n"); main_menu_move(0,0,MOVE_ROLL,0); break; case XK_p: printf("Pan\n"); main_menu_move(0,0,MOVE_PAN,0); break; case XK_s: printf("Spin\n"); main_menu_move(0,0,MOVE_SPIN,0); break; case XK_z: printf("Zoom\n"); main_menu_move(0,0,MOVE_ZOOM,0); break; case XK_x: printf("Pick\n"); main_menu_move(0,0,MOVE_PICK,0); break; } } static void check_draw_size(void) { int nww; int nwh; if (render.winw*wire.winh > render.winh*wire.winw) { nww = wire.winw; nwh = (render.winh * wire.winw) / render.winw; } else { nww = (render.winw * wire.winh) / render.winh; nwh = wire.winh; } if ((nww != draw_w_w) || (nwh != draw_w_h)) { if (draw_w_w < 0) draw_w_w = 1; dispscale_w *= nww / (double)draw_w_w; draw_w_w = nww; draw_w_h = nwh; draw_w_cx = draw_w_w * .5; draw_w_cy = draw_w_h * .5; pick_axis_len(); XMoveResizeWindow(disp,wire.win,(wire.winw-draw_w_w)/2,(wire.winh-draw_w_h)/2,draw_w_w,draw_w_h); want_redraw |= REDRAW_WIRE; scrpts_w_valid = 0; } if ((render.winw != draw_r_w) || (render.winh != draw_r_h)) { if (draw_r_w < 0) draw_r_w = 1; dispscale_r *= render.winw / (double)draw_r_w; draw_r_w = render.winw; draw_r_h = render.winh; draw_r_cx = draw_r_w * .5; draw_r_cy = draw_r_h * .5; XMoveResizeWindow(disp,render.win,0,0,draw_r_w,draw_r_h); want_redraw |= REDRAW_RENDER; scrpts_r_valid = 0; } } static void wire_button_down(XEvent *e) { if (e->type != ButtonPress) abort(); switch (movetype) { case MOVE_ROLL: case MOVE_PAN: case MOVE_SPIN: case MOVE_ZOOM: case MOVE_NEAR: case MOVE_FAR: case MOVE_EYETRK: case MOVE_ORGTRK: case MOVE_RLIGHT: case MOVE_SLIGHT: switch (e->xbutton.button) { case Button1: move_mul = 1; break; case Button2: move_mul = 5; break; default: abort(); break; } moving = 1; move_curx = e->xbutton.x_root; move_cury = e->xbutton.y_root; move_cx = (e->xbutton.x_root - e->xbutton.x) + (draw_w_w / 2); move_cy = (e->xbutton.y_root - e->xbutton.y) + (draw_w_h / 2); break; case MOVE_PICK: break; default: abort(); break; } } static void wire_move(int rootx, int rooty, int winx, int winy) { switch (movetype) { case MOVE_ROLL: case MOVE_PAN: case MOVE_SPIN: case MOVE_ZOOM: case MOVE_NEAR: case MOVE_FAR: case MOVE_EYETRK: case MOVE_ORGTRK: case MOVE_RLIGHT: case MOVE_SLIGHT: if (moving) move_move(rootx,rooty); break; case MOVE_PICK: vertex_pick_x = winx; vertex_pick_y = winy; want_redraw |= REDRAW_WIRE; break; default: abort(); break; } } static int wire_ctl(TPL *t, int op, ...) { va_list ap; int rv; rv = 0; va_start(ap,op); switch (op) { case TPL_CTL_EVENT: { XEvent *e; e = va_arg(ap,XEvent *); switch (e->type) { case Expose: if (e->xexpose.window == t->win) { if (e->xexpose.count == 0) want_redraw |= REDRAW_WIRE; rv = 1; } break; case ButtonPress: if (e->xbutton.window == t->win) { switch (e->xbutton.button) { case Button1: case Button2: wire_button_down(e); break; case Button3: menu_choose(&main_menu_wire,e->xbutton.time); break; } rv = 1; } break; case ButtonRelease: if (e->xbutton.window == t->win) { switch (e->xbutton.button) { case Button1: case Button2: wire_move( e->xbutton.x_root, e->xbutton.y_root, e->xbutton.x, e->xbutton.y ); moving = 0; break; } rv = 1; } break; case MotionNotify: if (e->xmotion.window == t->win) { Window root_ret; Window child_ret; int rootx_ret; int rooty_ret; int winx_ret; int winy_ret; unsigned int mask_ret; if (XQueryPointer(disp,t->win,&root_ret,&child_ret,&rootx_ret,&rooty_ret,&winx_ret,&winy_ret,&mask_ret) == True) { wire_move(rootx_ret,rooty_ret,winx_ret,winy_ret); } } break; } } break; case TPL_CTL_INIT: { int w; int h; w = va_arg(ap,int); h = va_arg(ap,int); XSetWindowBackground(disp,t->win,bgcolour_bw.pixel); (*t->ctl)(t,TPL_CTL_RESIZE,w,h); XMapRaised(disp,t->top); t->flags |= TPL_F_UP; } break; case TPL_CTL_RESIZE: { int w; int h; w = va_arg(ap,int); h = va_arg(ap,int); if ((w != t->winw) || (h != t->winh)) { t->winw = w; t->winh = h; check_draw_size(); } } break; } va_end(ap); return(rv); } static int render_ctl(TPL *t, int op, ...) { va_list ap; int rv; rv = 0; va_start(ap,op); switch (op) { case TPL_CTL_EVENT: { XEvent *e; e = va_arg(ap,XEvent *); switch (e->type) { case Expose: if (e->xexpose.window == t->win) { if (img) XPutImage(disp,render.win,colgc,img,e->xexpose.x,e->xexpose.y,e->xexpose.x,e->xexpose.y,e->xexpose.width,e->xexpose.height); rv = 1; } break; case ButtonPress: if (e->xbutton.window == t->win) { if (e->xbutton.button == Button3) { menu_choose(&main_menu_render,e->xbutton.time); } rv = 1; } break; case MotionNotify: if (e->xmotion.window == t->win) { rv = 1; } break; } } break; case TPL_CTL_INIT: { int w; int h; w = va_arg(ap,int); h = va_arg(ap,int); (*t->ctl)(t,TPL_CTL_RESIZE,w,h); XMapRaised(disp,t->top); t->flags |= TPL_F_UP; } break; case TPL_CTL_RESIZE: { int w; int h; w = va_arg(ap,int); h = va_arg(ap,int); if ((w != t->winw) || (h != t->winh)) { t->winw = w; t->winh = h; check_draw_size(); } } break; } va_end(ap); return(rv); } static unsigned long int float_to_16bits(double v) { int r; if (v < 0) v = 0; else if (v > 1) v = 1; r = v * 65536; if (r < 0) r = 0; else if (r > 65535) r = 65535; return(r); } static unsigned char irgb_primary(IRGB rgb, int primary) { switch (primary) { case 0: return(rgb.r); break; case 1: return(rgb.g); break; case 2: return(rgb.b); break; } abort(); } static void set_irgb_primary(IRGB *rgb, int primary, unsigned char val) { switch (primary) { case 0: rgb->r = val; break; case 1: rgb->g = val; break; case 2: rgb->b = val; break; default: abort(); break; } } static void slide_swatch(int x) { x = clip(x,0,255); XClearArea(disp,swatchslide_rgbwin[swatchslide_sliding],swatchslide_pos,0,1,0,False); swatchslide_pos = x; set_irgb_primary(&swatchslide_col,swatchslide_sliding,x); XSetForeground(disp,colgc,~0UL); XDrawLine(disp,swatchslide_rgbwin[swatchslide_sliding],colgc,x,0,x,baselineskip-1); XSetWindowBackground(disp,swatchslide_swatchwin,irgb_to_colpix(swatchslide_col)); XClearWindow(disp,swatchslide_swatchwin); } static int swatch_press(XButtonEvent *e, SWATCH *s) { if (e->window == s->parent) { set_slide_swatch(s); return(1); } return(0); } static int status_ctl(TPL *t, int op, ...) { va_list ap; int i; int x; va_start(ap,op); switch (op) { case TPL_CTL_EVENT: { XEvent *e; e = va_arg(ap,XEvent *); switch (e->type) { case Expose: if (e->xexpose.window == t->win) { if (e->xexpose.count == 0) want_redraw |= REDRAW_STATUS; return(1); } else { for (i=0;i<3;i++) { if (e->xexpose.window == swatchslide_rgbwin[i]) { x = irgb_primary(swatchslide_col,i); if ( (x >= e->xexpose.x) && (x < e->xexpose.x+e->xexpose.width) ) { XSetForeground(disp,colgc,~0UL); XDrawLine(disp,swatchslide_rgbwin[i],colgc,x,0,x,baselineskip-1); } return(1); } } } break; case ButtonPress: { LIGHT *l; for (l=lights;l;l=l->link) { if (swatch_press(&e->xbutton,&l->swatch)) return(1); } if (swatch_press(&e->xbutton,&ambient_swatch)) return(1); for (i=0;i<3;i++) { if (e->xbutton.window == swatchslide_rgbwin[i]) { swatchslide_sliding = i; swatchslide_pos = clip(irgb_primary(swatchslide_col,i),0,255); slide_swatch(e->xbutton.x); return(1); } } if (e->xbutton.window == swatchslide_swatchwin) { if (swatchslide_sliding >= 0) swatchslide_sliding = -1; *swatchslide_swatch->col = swatchslide_col; (*swatchslide_swatch->changed)(swatchslide_swatch->arg); XSetWindowBackground(disp,swatchslide_swatch->win,irgb_to_colpix(swatchslide_col)); XClearWindow(disp,swatchslide_swatch->win); } } break; case ButtonRelease: for (i=0;i<3;i++) { if (e->xbutton.window == swatchslide_rgbwin[i]) { if (swatchslide_sliding == i) { slide_swatch(e->xbutton.x); swatchslide_sliding = -1; } return(1); } } break; case MotionNotify: for (i=0;i<3;i++) { if (e->xmotion.window == swatchslide_rgbwin[i]) { if (swatchslide_sliding == i) { Window root_ret; Window child_ret; int rootx_ret; int rooty_ret; int winx_ret; int winy_ret; unsigned int mask_ret; if (XQueryPointer(disp,swatchslide_rgbwin[i],&root_ret,&child_ret,&rootx_ret,&rooty_ret,&winx_ret,&winy_ret,&mask_ret) == True) { slide_swatch(winx_ret); } } return(1); } } } } break; case TPL_CTL_INIT: { int w; int h; w = va_arg(ap,int); h = va_arg(ap,int); XSetWindowBackground(disp,t->win,bgcolour_bw.pixel); (*t->ctl)(t,TPL_CTL_RESIZE,w,h); XMapRaised(disp,t->top); t->flags |= TPL_F_UP; } break; case TPL_CTL_RESIZE: { int w; int h; w = va_arg(ap,int); h = va_arg(ap,int); if ((w != t->winw) || (h != t->winh)) { t->winw = w; t->winh = h; XMoveResizeWindow(disp,t->win,0,0,w,h); want_redraw |= REDRAW_STATUS; } } break; } va_end(ap); return(0); } static void menu_choose_curitem(XEvent *e) { MENUENT *me; if (curitem < 0) { menu_popdown(); return; } me = &curmenu->entries[curitem]; if (me->flags & ME_F_UNSELECTABLE) { menu_popdown(); return; } menu_popdown(); (*me->handler)(e->xbutton.time,me->arg_ptr,me->arg_int,me->arg_fxn); } static int menu_event(XEvent *e) { int rv; rv = 0; switch (e->type) { case Expose: if (e->xexpose.window == menu_win) { int i; int y1; int y2; MENUENT *me; y1 = e->xexpose.y; y2 = e->xexpose.y + e->xexpose.height - 1; for (i=0;inentries;i++) { me = &curmenu->entries[i]; if ( (y1 < me->texty+me->textbound.descent) && (y2 >= me->texty-me->textbound.ascent) ) { me->flags |= ME_F_DIRTY; items_dirty = 1; } } rv = 1; } break; case MotionNotify: if (e->xmotion.window == menu_win) { menu_wantquery = 1; rv = 1; } break; case EnterNotify: if (e->xcrossing.window == menu_win) { set_curitem_xy(e->xcrossing.x,e->xcrossing.y); rv = 1; } break; case LeaveNotify: if (e->xcrossing.window == menu_win) { if (! e->xcrossing.same_screen) { menu_popdown(); } else { set_curitem_xy(e->xcrossing.x,e->xcrossing.y); } rv = 1; } break; case ButtonPress: if (e->xbutton.window == menu_win) { if (! e->xbutton.same_screen) { menu_popdown(); } else { set_curitem_xy(e->xbutton.x,e->xbutton.y); if (curmenu) menu_choose_curitem(e); } rv = 1; } break; } return(rv); } static void handle_event(XEvent *e) { TPL *t; switch (e->type) { case ConfigureNotify: /* XConfigureEvent - xconfigure */ for (EACH_TPL(t)) { if ((t->flags & TPL_F_CREATED) && (e->xconfigure.window == t->top)) { (*t->ctl)(t,TPL_CTL_RESIZE,e->xconfigure.width,e->xconfigure.height); break; } } break; case MappingNotify: XRefreshKeyboardMapping(&e->xmapping); break; case KeyPress: typein(&e->xkey); break; default: if (curmenu && menu_event(e)) break; for (EACH_TPL(t)) if ((t->flags & TPL_F_CREATED) && (*t->ctl)(t,TPL_CTL_EVENT,e)) break; break; } } static void menu_query(void) { Window ret_root; Window ret_child; int ret_rootx; int ret_rooty; int ret_winx; int ret_winy; unsigned int ret_mask; menu_wantquery = 0; if (! curmenu) return; if (XQueryPointer(disp,menu_win,&ret_root,&ret_child,&ret_rootx,&ret_rooty,&ret_winx,&ret_winy,&ret_mask) != True) { menu_popdown(); return; } set_curitem_xy(ret_winx,ret_winy); } static void redraw_curitem(void) { if (drawn_curitem >= 0) curmenu->entries[drawn_curitem].flags |= ME_F_DIRTY; if (curitem >= 0) curmenu->entries[curitem].flags |= ME_F_DIRTY; items_dirty = 1; drawn_curitem = curitem; } static void redraw_item(int n) { GC *gcp; if ((n == curitem) && !(curmenu->entries[n].flags & ME_F_UNSELECTABLE)) { gcp = &revgc; } else { gcp = &fwdgc; } XDrawImageString(disp,menu_win,*gcp,curmenu->entries[n].textx,curmenu->entries[n].texty,curmenu->entries[n].text,strlen(curmenu->entries[n].text)); curmenu->entries[n].flags &= ~ME_F_DIRTY; } static void redraw_dirty_items(void) { int i; for (i=0;inentries;i++) { if (curmenu->entries[i].flags & ME_F_DIRTY) { redraw_item(i); } } items_dirty = 0; } static int print_scrpt(SCRPT sp) { switch (sp.clip) { case 0: printf("(%g,%g,%g)->(%d,%d)",sp.p.x,sp.p.y,sp.p.z,sp.ix,sp.iy); return(1); break; case CLIP_NEAR: printf("(clip near)"); break; case CLIP_FAR: printf("(clip far)"); break; default: printf("(?clip %d)",sp.clip); break; } return(0); } static void print_spp(SPP spp) { if (print_scrpt(spp.sp)) printf(" n=(%g,%g,%g) l=(%g,%g,%g)", spp.n.x,spp.n.y,spp.n.z, spp.l.x,spp.l.y,spp.l.z); } static SPP scr_interp(SPP a, double f, SPP b) { XYZ yu; double ya; double yb; double yf; XYZ s; XYZ l; if (render_dbg) { printf("scr_interp:\n a = "); print_spp(a); printf("\n f = %g\n b = ",f); print_spp(b); printf("\n"); } yu = unit(sub3( add3(scale(a.sp.p.x,rt),scale(a.sp.p.y,up)), add3(scale(b.sp.p.x,rt),scale(b.sp.p.y,up)) )); if (render_dbg) printf(" yu = (%g,%g,%g)\n",yu.x,yu.y,yu.z); ya = dot( add3( scale(a.sp.p.z,fwd), add3( scale((a.sp.p.x*a.sp.p.z)/dispscale_r,rt), scale((a.sp.p.y*a.sp.p.z)/dispscale_r,up) ) ), yu ); if (render_dbg) printf(" ya = %g\n",ya); yb = dot( add3( scale(b.sp.p.z,fwd), add3( scale((b.sp.p.x*b.sp.p.z)/dispscale_r,rt), scale((b.sp.p.y*b.sp.p.z)/dispscale_r,up) ) ), yu ); if (render_dbg) printf(" yb = %g\n",yb); yf = interp1(ya/a.sp.p.z,f,yb/b.sp.p.z); if (render_dbg) printf(" yf = %g\n",yf); s.x = interp1(a.sp.p.x,f,b.sp.p.x); s.y = interp1(a.sp.p.y,f,b.sp.p.y), s.z = ((a.sp.p.z*yb) - (b.sp.p.z*ya)) / ((yf*(a.sp.p.z-b.sp.p.z)) + yb - ya); if (render_dbg) printf(" s = (%g,%g,%g)\n",s.x,s.y,s.z); l = add3( add3(eye,scale(s.z,fwd)), add3( scale((s.x*s.z)/dispscale_r,rt), scale((s.y*s.z)/dispscale_r,up) ) ); if (render_dbg) printf(" l = (%g,%g,%g)\n",l.x,l.y,l.z); /* scale factor in interp3 was (s.z-a.sp.p.z) / (b.sp.p.z-a.sp.p.z) */ return((SPP){ .n = unit(interp3(a.n,f,b.n)), .l = l, .sp = project_r(l,0) }); } static RGB compute_colour(MATERIAL *m, XYZ loc, XYZ normal) { LIGHT *l; RGB v; double rp; double dc; XYZ e; static void addv(double r, double g, double b) { v.r += r; v.g += g; v.b += b; } v = m->selflum; addv(ambient.r*m->reflect.r,ambient.g*m->reflect.g,ambient.b*m->reflect.b); e = unit(sub3(eye,loc)); for (l=lights;l;l=l->link) { double t1; XYZ t2; dc = dot(normal,l->dir); if (dc < 0) continue; t2 = scale(2*dc,normal); t2 = sub3(t2,l->dir); t1 = dot(t2,e); rp = (t1 < 0) ? 0 : (m->spec_mul > 0) ? m->spec_mul*pow(t1,m->spec_exp) : 0; addv( (rp+(m->reflect.r*dc)) * l->col.r, (rp+(m->reflect.g*dc)) * l->col.g, (rp+(m->reflect.b*dc)) * l->col.b ); } return((RGB){ .r = v.r*bright, .g = v.g*bright, .b = v.b*bright }); } static PIX makepix(RGB c, double z) { PIX p; int i; if (c.r < minrgb.r) minrgb.r = c.r; if (c.g < minrgb.g) minrgb.g = c.g; if (c.b < minrgb.b) minrgb.b = c.b; if (c.r > maxrgb.r) maxrgb.r = c.r; if (c.g > maxrgb.g) maxrgb.g = c.g; if (c.b > maxrgb.b) maxrgb.b = c.b; i = ((int)(c.r + 1)) - 1; p.r = (i<0) ? 0 : (i>255) ? 255 : i; i = ((int)(c.g + 1)) - 1; p.g = (i<0) ? 0 : (i>255) ? 255 : i; i = ((int)(c.b + 1)) - 1; p.b = (i<0) ? 0 : (i>255) ? 255 : i; p.z = z; return(p); } static void render_rows(MATERIAL *m, SPP t1, SPP t2, SPP b1, SPP b2) { int y1; int y2; int y; SPP e1; SPP e2; int x1; int x2; int x; double xi; SPP ep; PIX *pp; if (render_dbg) { printf("render_rows:\nt1 "); print_spp(t1); printf("\nt2 "); print_spp(t2); printf("\nb1 "); print_spp(b1); printf("\nb2 "); print_spp(b2); printf("\n"); } y1 = ceil(t1.sp.p.y+draw_r_cy); y2 = floor(b1.sp.p.y+draw_r_cy); if (y1 < 0) y1 = 0; if (y2 >= (int)render_h) y2 = render_h - 1; if (y1 > y2) return; for (y=y1;y<=y2;y++) { e1 = scr_interp(t1,(y-draw_r_cy-t1.sp.p.y)/(b1.sp.p.y-t1.sp.p.y),b1); e2 = scr_interp(t2,(y-draw_r_cy-t1.sp.p.y)/(b1.sp.p.y-t1.sp.p.y),b2); if (e1.sp.p.x == e2.sp.p.x) continue; x1 = ceil(e1.sp.p.x+draw_r_cx); x2 = floor(e2.sp.p.x+draw_r_cx); if (x1 < 0) x1 = 0; if (x2 >= (int)render_w) x2 = render_w - 1; if (x1 > x2) continue; pp = &dispbuf[((render_h-1-y)*render_w)+x1]; if (render_dbg) printf("render row [%d..%d],%d\n",x1,x2,y); for (x=x1;x<=x2;x++,pp++) { xi = (x - draw_r_cx - e1.sp.p.x) / (e2.sp.p.x - e1.sp.p.x); ep = scr_interp(e1,xi,e2); if ((ep.sp.p.z < 0) || (ep.sp.p.z > pp->z)) continue; *pp = makepix(compute_colour(m,ep.l,ep.n),ep.sp.p.z); } } } static void render_t_up(MATERIAL *m, SPP top, SPP b1, SPP b2) { if (b2.sp.p.x < b1.sp.p.x) { if (render_dbg) printf("render_t_up: swap\n"); render_rows(m,top,top,b2,b1); } else { if (render_dbg) printf("render_t_up: no swap\n"); render_rows(m,top,top,b1,b2); } } static void render_t_dn(MATERIAL *m, SPP t1, SPP t2, SPP bot) { if (t2.sp.p.x < t1.sp.p.x) { if (render_dbg) printf("render_t_dn: swap\n"); render_rows(m,t2,t1,bot,bot); } else { if (render_dbg) printf("render_t_dn: no swap\n"); render_rows(m,t1,t2,bot,bot); } } static void draw_triangle(MATERIAL *m, SPP a, SPP b, SPP c) { SPP t; if ((b.sp.p.y <= a.sp.p.y) && (b.sp.p.y <= c.sp.p.y)) { t = a; a = b; b = t; } else if ((c.sp.p.y <= a.sp.p.y) && (c.sp.p.y <= b.sp.p.y)) { t = a; a = c; c = t; } if ((b.sp.p.y >= a.sp.p.y) && (b.sp.p.y >= c.sp.p.y)) { t = c; c = b; b = t; } else if ((a.sp.p.y >= c.sp.p.y) && (a.sp.p.y >= b.sp.p.y)) { t = c; c = a; a = t; } if (render_dbg) { printf("draw_triangle:\n "); print_spp(a); printf("\n "); print_spp(b); printf("\n "); print_spp(c); printf("\n"); } if (a.sp.p.y < b.sp.p.y) { if (render_dbg) printf("draw_triangle: up\n"); render_t_up( m, a, scr_interp( a, (b.sp.p.y-a.sp.p.y)/(c.sp.p.y-a.sp.p.y), c ), b ); } if (c.sp.p.y > b.sp.p.y) { if (render_dbg) printf("draw_triangle: dn\n"); render_t_dn( m, scr_interp( a, (b.sp.p.y-a.sp.p.y)/(c.sp.p.y-a.sp.p.y), c ), b, c ); } } static void render_show(void) { if (render_i >= render_h) { if (render_dbg) printf("Render done\n"); rendering = 0; render_dbg = 0; return; } XPutImage(disp,render.win,colgc,img,0,render_i,0,render_i,render_w,1); render_i ++; } static void render_transfer(void) { int i; PIX *dbp; dbp = dispbuf + render_i; for (i=0;i<1000;i++) { if (render_i <= 0) { render_i = 0; rendering = &render_show; return; } if (render_x) render_x --; else { render_x = render_w-1; render_y --; } dbp --; render_i --; XPutPixel( img, render_x, render_y, (((dbp->r*0x0101)>>(16-img_r_b))<g*0x0101)>>(16-img_g_b))<b*0x0101)>>(16-img_b_b))<material); for (i=0;i<3;i++) { p[i].p = get_point(model,t->corner[i]); p[i].lx = p[i].p->loc; p[i].l = get_location(model,p[i].lx); } if (render_dbg) { printf("Rendering triangle #%d\n",render_i+1); printf(" (%g,%g,%g) ",p[0].l->x,p[0].l->y,p[0].l->z); printf(" "); print_scrpt(scrpts_r[p[0].lx]); printf("\n"); printf(" (%g,%g,%g) ",p[1].l->x,p[1].l->y,p[1].l->z); printf(" "); print_scrpt(scrpts_r[p[1].lx]); printf("\n"); printf(" (%g,%g,%g) ",p[2].l->x,p[2].l->y,p[2].l->z); printf(" "); print_scrpt(scrpts_r[p[2].lx]); printf("\n"); } n = unit(cross(sub3(*p[0].l,*p[1].l),sub3(*p[2].l,*p[1].l))); for (i=0;i<3;i++) { switch (p[i].p->type) { case PT_SURFACE: p[i].n = p[i].p->normal; break; case PT_CORNER: p[i].n = n; break; default: abort(); break; } } if (dot(sub3(*p[0].l,eye),n) > 0) { if (render_dbg) printf("Backfacing\n"); return; } /* * The triangle can interact with the clipping planes in various ways: * a) All three points clipped by the same plane: * Entirely clipped away. * b) All three points clipped, not by the same plane: * Convert to quadrilateral and triangulate to two triangles. * c) One point unclipped, the other two clipped by the same plane: * Reduce to the smaller triangle. * d) One point unclipped, the other two clipped by different planes: * Convert to pentagon and triangulate to three triangles. * e) Two points unclipped, the third clipped: * Convert to quadrilateral and triangulate to two triangles. * f) No points clipped: * Just draw it. */ #define C(x) scrpts_r[p[(x)].lx].clip if (C(0) & C(1) & C(2)) { /* case a */ if (render_dbg) printf("Case a\n"); return; } else if (C(0) && C(1) && C(2)) { /* case b */ if (render_dbg) printf("Case b\n"); if (C(1) == C(2)) { i = 0; j = 1; k = 2; } else if (C(0) == C(1)) { i = 2; j = 0; k = 1; } else if (C(2) == C(0)) { i = 1; j = 2; k = 0; } else abort(); r[0] = spp_clip(i,clip_far,j); r[1] = spp_clip(i,clip_far,k); r[2] = spp_clip(i,clip_near,j); r[3] = spp_clip(i,clip_near,k); draw_triangle(m,r[0],r[1],r[2]); draw_triangle(m,r[1],r[2],r[3]); } else if ((C(0) & C(1)) || (C(1) & C(2)) || (C(2) & C(0))) { /* case c */ if (render_dbg) printf("Case c\n"); if (! C(0)) { i = 0; j = 1; k = 2; } else if (! C(1)) { i = 1; j = 2; k = 0; } else if (! C(2)) { i = 2; j = 0; k = 1; } else abort(); plane = clip_plane(C(j)); draw_triangle( m, copyp(i), spp_clip(i,plane,j), spp_clip(i,plane,k) ); } else if ((C(0) && C(1)) || (C(1) && C(2)) || (C(2) && C(0))) { /* case d */ if (render_dbg) printf("Case d\n"); if (! C(0)) { i = 0; j = 1; k = 2; } else if (! C(1)) { i = 1; j = 2; k = 0; } else if (! C(2)) { i = 2; j = 0; k = 1; } else abort(); r[0] = copyp(i); plane = clip_plane(C(j)); r[1] = spp_clip(i,plane,j); r[2] = spp_clip(k,plane,j); plane = clip_plane(C(k)); r[3] = spp_clip(j,plane,k); r[4] = spp_clip(i,plane,k); draw_triangle(m,r[0],r[1],r[2]); draw_triangle(m,r[0],r[2],r[3]); draw_triangle(m,r[0],r[3],r[4]); } else if (C(0) || C(1) || C(2)) { /* case e */ if (render_dbg) printf("Case e\n"); if (C(0)) { i = 0; j = 1; k = 2; } else if (C(1)) { i = 1; j = 2; k = 0; } else if (C(2)) { i = 2; j = 0; k = 1; } else abort(); plane = clip_plane(C(i)); r[0] = copyp(j); r[1] = copyp(k); r[2] = spp_clip(i,plane,j); r[3] = spp_clip(i,plane,k); draw_triangle(m,r[0],r[1],r[2]); draw_triangle(m,r[1],r[2],r[3]); } else { /* case f */ if (render_dbg) printf("Case f\n"); draw_triangle(m,copyp(0),copyp(1),copyp(2)); } #undef C } static void render_start_triangles(void) { render_i = n_triangles(model) - 1; rendering = &render_triangle; } static void render_get_scrpts(void) { int i; for (i=100;i>0;i--) { if (render_i < 0) { rendering = &render_start_triangles; scrpts_r_valid = 1; return; } else { scrpts_r[render_i] = project_r(*get_location(model,render_i),1); render_i --; } } } static void render_clear(void) { int i; for (i=1000;i>0;i--) { if (render_i < 0) { if (scrpts_r_valid) { rendering = &render_start_triangles; } else { render_i = n_locations(model) - 1; rendering = &render_get_scrpts; } return; } dispbuf[render_i--] = (PIX){.r=0,.g=0,.b=0,.z=1e30}; } } static void render_init(void) { if (render_dbg) printf("Initializing for render\n"); render_i = (render_w * render_h) - 1; rendering = &render_clear; } static void process_image_mask(int *sp, int *bp, unsigned long int m) { int s; int b; s = 0; if (! (m & 0x0000ffff)) { s += 16; m >>= 16; } if (! (m & 0x000000ff)) { s += 8; m >>= 8; } if (! (m & 0x0000000f)) { s += 4; m >>= 4; } if (! (m & 0x00000003)) { s += 2; m >>= 2; } if (! (m & 0x00000001)) { s += 1; m >>= 1; } b = 1; if (m & 0xffff0000) { b += 16; m >>= 16; } if (m & 0xffffff00) { b += 8; m >>= 8; } if (m & 0xfffffff0) { b += 4; m >>= 4; } if (m & 0xfffffffc) { b += 2; m >>= 2; } if (m & 0xfffffffe) { b += 1; m >>= 1; } *sp = s; *bp = b; } static void start_render(void) { if (render_dbg) printf("Starting to render (%dx%d)\n",render_w,render_h); if ((draw_r_w != render_w) || (draw_r_h != render_h)) { free(dispbuf); free(imgbuf); if (img) { img->data = 0; XDestroyImage(img); } if (render_dbg) printf("Resizing to %dx%d\n",draw_r_w,draw_r_h); render_w = draw_r_w; render_h = draw_r_h; dispbuf = malloc(render_w*render_h*sizeof(PIX)); imgbuf = malloc(render_w*render_h*4); img = XCreateImage(disp,col.vi->visual,col.vi->depth,ZPixmap,0,(void *)imgbuf,render_w,render_h,32,render_w*4); process_image_mask(&img_r_s,&img_r_b,img->red_mask); process_image_mask(&img_g_s,&img_g_b,img->green_mask); process_image_mask(&img_b_s,&img_b_b,img->blue_mask); scrpts_r_valid = 0; } rendering = &render_init; } static void run(void) __attribute__((__noreturn__)); static void run(void) { XEvent e; int xfd; xfd = XConnectionNumber(disp); while (1) { while (XEventsQueued(disp,QueuedAfterReading) > 0) { XNextEvent(disp,&e); handle_event(&e); } XFlush(disp); if (curmenu && menu_wantquery) { menu_query(); } else if (curmenu && (curitem != drawn_curitem)) { redraw_curitem(); } else if (curmenu && items_dirty) { redraw_dirty_items(); } else if (want_redraw) { if (want_redraw & REDRAW_WIRE) redraw_wire(); if (want_redraw & REDRAW_STATUS) redraw_status(); if (want_redraw & REDRAW_RENDER) start_render(); want_redraw = 0; } else if (rendering) { (*rendering)(); } else { XNextEvent(disp,&e); handle_event(&e); } } } static void setup_menu(void) { unsigned long int attrmask; XSetWindowAttributes attr; attrmask = 0; attr.background_pixel = bgcolour_bw.pixel; attrmask |= CWBackPixel; attr.border_pixel = bdcolour_bw.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = 0; attrmask |= CWEventMask; attr.override_redirect = True; attrmask |= CWOverrideRedirect; attr.colormap = bw.cm; attrmask |= CWColormap; attr.cursor = arrowcurs; attrmask |= CWCursor; menu_top = XCreateWindow(disp,rootwin,0,0,1,1,borderwidth,bw.vi->depth,InputOutput,CopyFromParent,attrmask,&attr); attrmask = 0; attr.background_pixel = bgcolour_bw.pixel; attrmask |= CWBackPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = ExposureMask|ButtonPressMask|ButtonReleaseMask|EnterWindowMask|LeaveWindowMask; attrmask |= CWEventMask; menu_win = XCreateWindow(disp,menu_top,text_margin,text_margin,1,1,0,bw.vi->depth,InputOutput,CopyFromParent,attrmask,&attr); XMapRaised(disp,menu_win); curmenu = 0; } static void setup_swatches(void) { int i; int j; unsigned long int attrmask; XSetWindowAttributes attr; Pixmap bg; GC gc; swatchslide_swatch = 0; swatchslide_sliding = -1; attrmask = 0; attr.background_pixel = bgcolour_bw.pixel; attrmask |= CWBackPixel; attr.border_pixel = 0; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = 0; attrmask |= CWEventMask; attr.colormap = bw.cm; attrmask |= CWColormap; attr.cursor = None; attrmask |= CWCursor; swatchslide_parent = XCreateWindow(disp,status.win,0,0,(baselineskip*3)+256,baselineskip*3,0,bw.vi->depth,InputOutput,bw.vi->visual,attrmask,&attr); attrmask = 0; attr.background_pixel = 0; attrmask |= CWBackPixel; attr.border_pixel = 0; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = ButtonPressMask; attrmask |= CWEventMask; attr.colormap = col.cm; attrmask |= CWColormap; attr.cursor = None; attrmask |= CWCursor; swatchslide_swatchwin = XCreateWindow(disp,swatchslide_parent,0,0,baselineskip*3,baselineskip*3,0,col.vi->depth,InputOutput,col.vi->visual,attrmask,&attr); XMapWindow(disp,swatchslide_swatchwin); bg = XCreatePixmap(disp,rootwin,256,1,col.vi->depth); gc = XCreateGC(disp,bg,0,0); for (j=0;j<3;j++) { if (j) bg = XCreatePixmap(disp,rootwin,256,1,col.vi->depth); for (i=0;i<256;i++) { XSetForeground(disp,gc,(float_to_16bits(i/256.0)>>col_bits[j])<depth,InputOutput,col.vi->visual,attrmask,&attr); XFreePixmap(disp,bg); XMapWindow(disp,swatchslide_rgbwin[j]); } XFreeGC(disp,gc); } static void setup_ambient(void) { ambient.r = 0; ambient.r = 0; ambient.b = 110; setup_swatch(&ambient_swatch,&ambient,&trigger_render,0,0); } static void setup_lights(void) { add_light((XYZ){.x=1,.y=2,.z=3},(IRGB){.r=255,.g=255,.b=170}); curlight = 0; } static void setup_render(void) { render_w = -1; render_h = -1; dispbuf = 0; imgbuf = 0; img = 0; rendering = 0; render_dbg = 0; } static void setup_toggles(void) { showorgaxes = 0; showwireframe = 1; showtrinums = 0; showbackfaces = 0; shownormals = 0; showlightdirs = 0; } int main(int, char **); int main(int ac, char **av) { saveargv(ac,av); handleargs(ac,av); read_model(); init_view(); disp = open_display(displayname); if (synch) XSynchronize(disp,True); preverr = XSetErrorHandler(err); prevIOerr = XSetIOErrorHandler(ioerr); scr = XDefaultScreenOfDisplay(disp); width = XWidthOfScreen(scr); height = XHeightOfScreen(scr); rootwin = XRootWindowOfScreen(scr); defcmap = XDefaultColormapOfScreen(scr); setup_db(); maybeset(&geomspec_wire,get_default_value("editmodel.wireframe.geometry","Editor.Geometry")); maybeset(&geomspec_solid,get_default_value("editmodel.rendered.geometry","Editor.Geometry")); maybeset(&foreground,get_default_value("editmodel.foreground","Editor.Foreground")); maybeset(&background,get_default_value("editmodel.background","Editor.Background")); maybeset(&bordercstr,get_default_value("editmodel.borderColor","Editor.BorderColor")); maybeset(&borderwstr,get_default_value("editmodel.borderWidth","Editor.BorderWidth")); maybeset(&textmstr,get_default_value("editmodel.textMargin","Editor.TextMargin")); maybeset(&menutstr,get_default_value("editmodel.menuTolerance","Editor.MenuTolerance")); maybeset(&name,get_default_value("editmodel.name","Editor.Name")); maybeset(&iconname,get_default_value("editmodel.iconName","Editor.IconName")); maybeset(&fontname,get_default_value("editmodel.font","Editor.Font")); maybeset(&vis_str_bw,get_default_value("editmodel.bw.visual","Editor.Visual")); maybeset(&vis_str_col,get_default_value("editmodel.colour.visual","Editor.Visual")); setup_visuals(); setup_font(); setup_colours(); setup_numbers(); setup_cursor(); setup_render(); setup_toggles(); setup_windows(); setup_swatches(); setup_ambient(); setup_lights(); setup_menu(); run(); }