/* * overlap.c - Overlap two parallel faces * * Written 2010 by Werner Almesberger * Copyright 2010 by Werner Almesberger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ #include #include #include #include #include #include "face.h" #include "solid.h" #include "overlap.h" #define UNDEF_F HUGE_VAL #define BORDER 10 /* pixels around the minimum drawing area */ static int sx(const struct solid *s) { return (s->a->sx > s->b->sx ? s->a->sx : s->b->sx)+2*BORDER; } static int sy(const struct solid *s) { return (s->a->sy > s->b->sy ? s->a->sy : s->b->sy)+2*BORDER; } static double ramp(int z0, double w0, int z1, double w1) { if (z0 != UNDEF && z1 != UNDEF) return w0 == 0 && w1 == 0 ? z0 : z0*w0+z1*w1; if (z0 == UNDEF && z0 == UNDEF) return UNDEF_F; if (z0 == UNDEF && w0 < w1) return z1; if (z1 == UNDEF && w0 > w1) return z0; return UNDEF_F; } static double zmix(struct face *f, double x, double y) { int xa, xb, ya, yb; double zx0, zx1; xa = floor(x); xb = ceil(x); ya = floor(y); yb = ceil(y); zx0 = ramp( get_bounded(f->a, xa, ya), yb-y, get_bounded(f->a, xa, yb), y-ya); zx1 = ramp( get_bounded(f->a, xb, ya), yb-y, get_bounded(f->a, xb, yb), y-ya); return ramp(zx0, xb-x, zx1, x-xa); } static void draw_image(GtkWidget *widget, struct solid *s) { guchar *rgbbuf, *p; int x, y; double z; struct face *f = s->a; rgbbuf = p = calloc(sx(s)*sy(s), 3); if (!rgbbuf) { perror("calloc"); exit(1); } for (y = sy(s)-1; y >= 0; y--) for (x = 0; x != sx(s) ; x++) { int xa = x+f->a->min_x; int ya = y+f->a->min_y; z = zmix(f, xa*f->m.a[0][0]+ya*f->m.a[0][1]+f->m.b[0], xa*f->m.a[1][0]+ya*f->m.a[1][1]+f->m.b[1]); if (z == UNDEF_F) { p += 3; continue; } z = 256.0*(z-f->a->min_z)/(f->a->max_z-f->a->min_z); if (z < 0) z = 0; if (z > 255) z = 255; *p++ = z; *p++ = z; *p++ = z; } gdk_draw_rgb_image(widget->window, widget->style->fg_gc[GTK_STATE_NORMAL], 0, 0, sx(s), sy(s), GDK_RGB_DITHER_MAX, rgbbuf, sx(s)*3); free(rgbbuf); } /* * Rotate such that a point at distance "r" moves one unit. Rotate * counter-clockwise for r > 1, clockwise for r < 0. */ static void rotate(struct matrix *m, double r) { struct matrix t; double s, c; s = 1/r; c = sqrt(1-s*s); t.a[0][0] = m->a[0][0]*c-m->a[1][0]*s; t.a[0][1] = m->a[0][1]*c-m->a[1][1]*s; t.a[1][0] = m->a[1][0]*c+m->a[0][0]*s; t.a[1][1] = m->a[1][1]*c+m->a[0][1]*s; t.b[0] = m->b[0]*c-m->b[1]*s; t.b[1] = m->b[0]*s+m->b[1]*c; *m = t; } static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) { GtkWidget *da = gtk_bin_get_child(GTK_BIN(widget)); struct solid *s = data; struct face *f = s->a; int dx = event->x-f->sx/2; int dy = event->y-f->sy/2; double r = hypot(dx, dy); if (r < 1) return TRUE; switch (event->direction) { case GDK_SCROLL_UP: rotate(&f->m, r); draw_image(da, s); break; case GDK_SCROLL_DOWN: rotate(&f->m, -r); draw_image(da, s); break; default: /* ignore */; } return TRUE; } static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { draw_image(widget, user_data); return TRUE; } void overlap(GtkWidget *canvas, struct solid *s) { GtkWidget *evbox, *da; evbox = gtk_event_box_new(); da = gtk_drawing_area_new(); gtk_widget_set_size_request(da, sx(s), sy(s)); gtk_container_add(GTK_CONTAINER(canvas), evbox); gtk_container_add(GTK_CONTAINER(evbox), da); draw_image(da, s); g_signal_connect(G_OBJECT(evbox), "scroll-event", G_CALLBACK(scroll_event), s); g_signal_connect(G_OBJECT(da), "expose-event", G_CALLBACK(expose_event), s); }