/* * level.c - Interactively align a nearly horizontal plane with a face * * 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 "util.h" #include "array.h" #include "face.h" #include "style.h" #include "level.h" #define NEAR 1 static int has_osd; static GtkWidget *xz, *zy; static double r_center(const struct face *f) { return hypot(f->sx, f->sy)/LEVEL_CENTER_DIV; } static void draw_map(GtkWidget *widget, struct face *f) { int x, y, z; double z0; guchar *rgbbuf, *p; rgbbuf = p = calloc(f->sx*f->sy, 3); if (!rgbbuf) { perror("calloc"); exit(1); } for (y = f->sy-1; y >= 0; y--) for (x = 0; x != f->sx ; x++) { z = get(f->a, x+f->a->min_x, y+f->a->min_y); if (z == UNDEF) { p += 3; continue; } z0 = face_z0(f, x, y); if (fabs(z-z0) < NEAR) { *p++ = 255*fabs(z-z0); *p++ = 255*fabs(z-z0); *p++ = 255; continue; } if (z < z0) { z = z > z0-2*NEAR ? 255*(z-z0)/NEAR : 255.0*(z-z0)/(z0-f->a->min_z); *p++ = 255; *p++ = z; *p++ = z; } else { z = z < z0+2*NEAR ? 255*(z0-z)/NEAR : 255.0*(z0-z)/(f->a->max_z-z0); *p++ = z; *p++ = 255; *p++ = z; } } gdk_draw_rgb_image(widget->window, widget->style->fg_gc[GTK_STATE_NORMAL], 0, 0, f->sx, f->sy, GDK_RGB_DITHER_MAX, rgbbuf, f->sx*3); free(rgbbuf); } static void draw_image(GtkWidget *widget, struct face *f, int osd) { draw_map(widget, f); has_osd = osd; if (osd) draw_circle(widget->window, gc_osd, f->sx/2, f->sy/2, r_center(f)); } static void draw_xz(GtkWidget *widget, struct face *f, int y) { int x, z, z0; guchar *rgbbuf, *p; rgbbuf = p = calloc(f->sx*f->sz, 3); if (!rgbbuf) { perror("calloc"); exit(1); } if (y != -1) for (x = 0; x != f->sx ; x++) { z = get(f->a, x+f->a->min_x, y+f->a->min_y); if (z != UNDEF) { p = rgbbuf+3*(x+f->sx*(f->sz-z+f->a->min_z-1)); p[0] = 0xff; p[1] = 0xff; p[2] = 0xff; } z0 = round(face_z0(f, x, y)); /* @@@ anti-alias */ if (z0 >= f->a->min_z && z0 <= f->a->max_z) { p = rgbbuf+3*(x+f->sx*(f->sz-z0+f->a->min_z-1)); p[0] = 0x80; p[1] = 0xc0; p[2] = 0xff; } } gdk_draw_rgb_image(widget->window, widget->style->fg_gc[GTK_STATE_NORMAL], 0, 0, f->sx, f->sz, GDK_RGB_DITHER_MAX, rgbbuf, f->sx*3); free(rgbbuf); } static void draw_zy(GtkWidget *widget, struct face *f, int x) { int y, z, z0; guchar *rgbbuf, *p; rgbbuf = p = calloc(f->sy*f->sz, 3); if (!rgbbuf) { perror("calloc"); exit(1); } if (x != -1) for (y = 0; y != f->sy ; y++) { z = get(f->a, x+f->a->min_x, y+f->a->min_y); if (z != UNDEF) { p = rgbbuf+3*(f->a->max_z-z+f->sz*(f->sy-y-1)); p[0] = 0xff; p[1] = 0xff; p[2] = 0xff; } z0 = round(face_z0(f, x, y)); /* @@@ anti-alias */ if (z0 >= f->a->min_z && z0 <= f->a->max_z) { p = rgbbuf+3*(f->a->max_z-z0+f->sz*(f->sy-y-1)); p[0] = 0x80; p[1] = 0xc0; p[2] = 0xff; } } gdk_draw_rgb_image(widget->window, widget->style->fg_gc[GTK_STATE_NORMAL], 0, 0, f->sz, f->sy, GDK_RGB_DITHER_MAX, rgbbuf, f->sz*3); free(rgbbuf); } static void scroll_z(GtkWidget *darea, struct face *f, int up, int osd) { if (up) { if (f->z_ref < f->a->max_z) f->z_ref++; } else { if (f->z_ref > f->a->min_z) f->z_ref--; } draw_image(darea, f, osd); } static void scroll_xy(GtkWidget *darea, struct face *f, int dx, int dy, int up, int osd) { double d; d = (double) (up ? 1 : -1)/(dx*dx+dy*dy)/2.0; f->fx += d*dx; f->fy += d*dy; draw_image(darea, f, osd); } static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) { GtkWidget *darea = gtk_bin_get_child(GTK_BIN(widget)); struct face *f = data; int dx = event->x-f->sx/2; int dy = event->y-f->sy/2; double r = hypot(dx, dy); double rc = r_center(f); int center = r < rc; int osd = fabs(r-rc) < OSD_PROXIMITY; switch (event->direction) { case GDK_SCROLL_UP: if (center) scroll_z(darea, f, 0, osd); else scroll_xy(darea, f, dx, dy, 1, osd); break; case GDK_SCROLL_DOWN: if (center) scroll_z(darea, f, 1, osd); else scroll_xy(darea, f, dx, dy, 0, osd); break; default: /* ignore */; } draw_xz(xz, f, f->sy-1-event->y); draw_zy(zy, f, event->x); 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 face *f = data; int dx = event->x-f->sx/2; int dy = event->y-f->sy/2; double r = hypot(dx, dy); double rc = r_center(f); int osd = fabs(r-rc) < OSD_PROXIMITY; if (osd != has_osd) draw_image(widget, f, osd); draw_xz(xz, f, f->sy-1-event->y); draw_zy(zy, f, event->x); return FALSE; } void level(GtkWidget *canvas, struct face *f) { GtkWidget *evbox, *tab, *xy; evbox = gtk_event_box_new(); tab = gtk_table_new(2, 2, FALSE); xy = gtk_drawing_area_new(); xz = gtk_drawing_area_new(); zy = gtk_drawing_area_new(); gtk_widget_set_events(xy, 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(xy, f->sx, f->sy); gtk_widget_set_size_request(xz, f->sx, f->sz); gtk_widget_set_size_request(zy, f->sz, f->sy); gtk_table_set_row_spacings(GTK_TABLE(tab), 2); gtk_table_set_col_spacings(GTK_TABLE(tab), 2); gtk_table_attach_defaults(GTK_TABLE(tab), evbox, 0, 1, 0, 1); gtk_table_attach_defaults(GTK_TABLE(tab), xz, 0, 1, 1, 2); gtk_table_attach_defaults(GTK_TABLE(tab), zy, 1, 2, 0, 1); gtk_container_add(GTK_CONTAINER(canvas), tab); gtk_container_add(GTK_CONTAINER(evbox), xy); gtk_widget_show_all(canvas); draw_image(xy, f, 0); draw_xz(xz, f, -1); draw_zy(zy, f, -1); g_signal_connect(G_OBJECT(evbox), "scroll-event", G_CALLBACK(scroll_event), f); g_signal_connect(G_OBJECT(xy), "expose-event", G_CALLBACK(expose_event), f); g_signal_connect(G_OBJECT(xy), "motion-notify-event", G_CALLBACK(motion_notify_event), f); }