/* * 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 "util.h" #include "face.h" #include "solid.h" #include "style.h" #include "overlap.h" #define UNDEF_F HUGE_VAL static int has_osd; static int sx(const struct solid *s) { return (s->a->sx > s->b->sx ? s->a->sx : s->b->sx)+2*OVERLAP_BORDER; } static int sy(const struct solid *s) { return (s->a->sy > s->b->sy ? s->a->sy : s->b->sy)+2*OVERLAP_BORDER; } static double r_center(const struct solid *s) { return hypot(sx(s), sy(s))/OVERLAP_CENTER_DIV; } 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); } /* * Coordinate transformations, on the example of the x coordinate: * * - the x coordinate runs from 0 to sx(s)-1 * - since we work relative to the screen center, this becomes x-sx(s)/2 * This is what we perform the coordinate transform on. * - our model runs from min_x to max_x. Its center is at cx. */ static void point(const struct solid *s, int x, int y, guchar *p) { double za, zb, z; int xa, xb, ya, yb; double xaf, xbf, yaf, ybf; xa = x-sx(s)/2; ya = y-sy(s)/2; xaf = xa*s->a->m.a[0][0]+ya*s->a->m.a[0][1]+s->a->m.b[0]+s->a->cx; yaf = xa*s->a->m.a[1][0]+ya*s->a->m.a[1][1]+s->a->m.b[1]+s->a->cy; za = zmix(s->a, xaf, yaf); xb = x-sx(s)/2; yb = (sy(s)-1)/2-y; xbf = xb*s->b->m.a[0][0]+yb*s->b->m.a[0][1]+s->b->m.b[0]+s->b->cx; ybf = xb*s->b->m.a[1][0]+yb*s->b->m.a[1][1]+s->b->m.b[1]+s->b->cy; zb = zmix(s->b, xbf, ybf); 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, xaf, yaf); zb -= face_z0(s->b, xbf, ybf); 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_map(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); } static void draw_image(GtkWidget *widget, struct solid *s, int osd) { int cx = sx(s)/2; int cy = sy(s)/2; int p; draw_map(widget, s); has_osd = osd; if (!osd) return; draw_circle(widget->window, gc_osd, cx, cy, r_center(s)); p = r_center(s)/sqrt(2); gdk_draw_line(widget->window, gc_osd, cx-p, cy-p, cx+p, cy+p); gdk_draw_line(widget->window, gc_osd, cx-p, cy+p, cx+p, cy-p); } /* * 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 int osd_proximity(const struct solid *s, int dx, int dy) { double r = hypot(dx, dy); double rc = r_center(s); if (fabs(r-rc) < OSD_PROXIMITY) return 1; if (r > rc) return 0; if (abs(abs(dx)-abs(dy)) < OSD_PROXIMITY) return 1; return 0; } 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); double rc = r_center(s); int center = r < rc; int osd = osd_proximity(s, dx, dy); 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, osd); break; case GDK_SCROLL_DOWN: if (center) shift(&s->a->m, dx, dy, -1); else rotate(&s->a->m, r); draw_image(darea, s, osd); break; default: /* ignore */; } return TRUE; } static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) { draw_image(widget, user_data, has_osd); return TRUE; } static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) { struct solid *s = data; int dx = event->x-sx(s)/2; int dy = event->y-sy(s)/2; int osd = osd_proximity(s, dx, dy); if (osd != has_osd) draw_image(widget, s, osd); return FALSE; } void overlap(GtkWidget *canvas, struct solid *s) { GtkWidget *evbox, *darea; evbox = gtk_event_box_new(); darea = gtk_drawing_area_new(); gtk_widget_set_events(darea, GDK_EXPOSE | GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SCROLL | GDK_POINTER_MOTION_MASK); 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, 0); 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); g_signal_connect(G_OBJECT(darea), "motion-notify-event", G_CALLBACK(motion_notify_event), s); }