/* * 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 point(const struct solid *s, int x, int y, guchar *p) { double za, zb, z; int xa = x+s->a->a->min_x; int ya = y+s->a->a->min_y; int yb = sy(s)-1-y+s->a->a->min_y; za = zmix(s->a, xa*s->a->m.a[0][0]+ya*s->a->m.a[0][1]+s->a->m.b[0], xa*s->a->m.a[1][0]+ya*s->a->m.a[1][1]+s->a->m.b[1]); zb = zmix(s->b, xa*s->b->m.a[0][0]+yb*s->b->m.a[0][1]+s->b->m.b[0], xa*s->b->m.a[1][0]+yb*s->b->m.a[1][1]+s->b->m.b[1]); if (za == UNDEF_F && zb == UNDEF_F) return; if (za == UNDEF_F) { z = 128.0*(zb-s->b->a->min_z)/(s->b->a->max_z-s->b->a->min_z); if (z < 0) z = 0; if (z > 255) z = 255; p[0] = 255; p[1] = z; p[2] = z; return; } if (zb == UNDEF_F) { z = 128.0*(za-s->a->a->min_z)/(s->a->a->max_z-s->a->a->min_z); if (z < 0) z = 0; if (z > 255) z = 255; p[0] = z; p[1] = 255; p[2] = z; return; } z = za; za -= face_z0(s->a, xa, ya); zb -= face_z0(s->b, xa, yb); if (za+zb < -s->dist) { p[0] = 0; p[1] = 0; p[2] = 255; return; } z = 256.0*(z-s->a->a->min_z)/(s->a->a->max_z-s->a->a->min_z); if (z < 0) z = 0; if (z > 255) z = 255; p[0] = z; p[1] = z; p[2] = z; } static void draw_image(GtkWidget *widget, struct solid *s) { guchar *rgbbuf, *p; int x, y; 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++) { point(s, x, y, p); p += 3; } 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 void do_shift(struct matrix *m, int dx, int dy) { m->b[0] -= dx; m->b[1] += dy; } static void shift(struct matrix *m, int dx, int dy, int dir) { if (dx > 0 && dy < dx && dy > -dx) do_shift(m, dir, 0); if (dx < 0 && dy < -dx && dy > dx) do_shift(m, -dir, 0); if (dy > 0 && dx < dy && dx > -dy) do_shift(m, 0, dir); if (dy < 0 && dx < -dy && dx > dy) do_shift(m, 0, -dir); } static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) { GtkWidget *darea = gtk_bin_get_child(GTK_BIN(widget)); struct solid *s = data; int dx = event->x-sx(s)/2; int dy = event->y-sy(s)/2; double r = hypot(dx, dy); int center = r/hypot(sx(s), sy(s)) < 0.25; if (r < 1) return TRUE; switch (event->direction) { case GDK_SCROLL_UP: if (center) shift(&s->a->m, dx, dy, 1); else rotate(&s->a->m, r); draw_image(darea, s); break; case GDK_SCROLL_DOWN: if (center) shift(&s->a->m, dx, dy, -1); else rotate(&s->a->m, -r); draw_image(darea, 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, *darea; evbox = gtk_event_box_new(); darea = gtk_drawing_area_new(); gtk_widget_set_size_request(darea, sx(s), sy(s)); gtk_container_add(GTK_CONTAINER(canvas), evbox); gtk_container_add(GTK_CONTAINER(evbox), darea); draw_image(darea, s); g_signal_connect(G_OBJECT(evbox), "scroll-event", G_CALLBACK(scroll_event), s); g_signal_connect(G_OBJECT(darea), "expose-event", G_CALLBACK(expose_event), s); }