Root/solidify/overlap.c

1/*
2 * overlap.c - Overlap two parallel faces
3 *
4 * Written 2010 by Werner Almesberger
5 * Copyright 2010 by Werner Almesberger
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 */
12
13
14#include <stdlib.h>
15#include <stdio.h>
16#include <math.h>
17#include <limits.h>
18#include <gtk/gtk.h>
19
20#include "face.h"
21#include "solid.h"
22#include "gui_util.h"
23#include "style.h"
24#include "overlap.h"
25
26
27#define UNDEF_F HUGE_VAL
28
29
30static int has_osd;
31static int edit_top;
32
33
34static int sx(const struct solid *s)
35{
36    return (s->a->sx > s->b->sx ? s->a->sx : s->b->sx)+2*OVERLAP_BORDER;
37}
38
39
40static int sy(const struct solid *s)
41{
42    return (s->a->sy > s->b->sy ? s->a->sy : s->b->sy)+2*OVERLAP_BORDER;
43}
44
45
46static double r_center(const struct solid *s)
47{
48    return hypot(sx(s), sy(s))/OVERLAP_CENTER_DIV;
49}
50
51
52static double ramp(int z0, double w0, int z1, double w1)
53{
54    if (z0 != UNDEF && z1 != UNDEF)
55        return z0*w0+z1*w1;
56    if (z0 == UNDEF && z0 == UNDEF)
57        return UNDEF_F;
58    if (z0 == UNDEF && w0 < w1)
59        return z1;
60    if (z1 == UNDEF && w0 > w1)
61        return z0;
62    return UNDEF_F;
63}
64
65
66static double zmix(struct face *f, double x, double y)
67{
68    int xa, xb, ya, yb;
69    double zx0, zx1;
70
71    xa = floor(x);
72    xb = xa+1;
73    ya = floor(y);
74    yb = ya+1;
75
76    zx0 = ramp(
77        get_bounded(f->a, xa, ya), yb-y,
78        get_bounded(f->a, xa, yb), y-ya);
79    zx1 = ramp(
80        get_bounded(f->a, xb, ya), yb-y,
81        get_bounded(f->a, xb, yb), y-ya);
82
83    return ramp(zx0, xb-x, zx1, x-xa);
84}
85
86
87/*
88 * Coordinate transformations, on the example of the x coordinate:
89 *
90 * - the x coordinate runs from 0 to sx(s)-1
91 * - since we work relative to the screen center, this becomes x-sx(s)/2
92 * This is what we perform the coordinate transform on.
93 * - our model runs from min_x to max_x. Its center is at cx.
94 */
95
96static void point(const struct solid *s, int x, int y, guchar *p,
97    const struct matrix *ma, const struct matrix *mb)
98{
99    double za, zb, z;
100    double xaf, xbf, yaf, ybf;
101
102    matrix_map(x, y, ma, &xaf, &yaf);
103    matrix_map(x, y, mb, &xbf, &ybf);
104
105    za = zmix(s->a, xaf, yaf);
106    zb = zmix(s->b, xbf, ybf);
107
108    if (za == UNDEF_F && zb == UNDEF_F)
109        return;
110
111    if (za == UNDEF_F) {
112        z = 128.0*(zb-s->b->a->min_z)/(s->b->a->max_z-s->b->a->min_z);
113        if (z < 0)
114            z = 0;
115        if (z > 255)
116            z = 255;
117        p[0] = 255;
118        p[1] = z;
119        p[2] = z;
120        return;
121    }
122    if (zb == UNDEF_F) {
123        z = 128.0*(za-s->a->a->min_z)/(s->a->a->max_z-s->a->a->min_z);
124        if (z < 0)
125            z = 0;
126        if (z > 255)
127            z = 255;
128        p[0] = z;
129        p[1] = 255;
130        p[2] = z;
131        return;
132    }
133
134    z = za;
135    za -= face_z0(s->a, xaf, yaf);
136    zb -= face_z0(s->b, xbf, ybf);
137
138    if (za+zb < -s->dist) {
139        p[0] = 0;
140        p[1] = 0;
141        p[2] = 255;
142        return;
143    }
144
145    z = 256.0*(z-s->a->a->min_z)/(s->a->a->max_z-s->a->a->min_z);
146    if (z < 0)
147        z = 0;
148    if (z > 255)
149        z = 255;
150    p[0] = z;
151    p[1] = z;
152    p[2] = z;
153}
154
155
156static void merge_matrix(struct matrix *m, const struct solid *s,
157    const struct face *f)
158{
159    double tm[2][2], tm2[2][2];
160    double tv[2];
161    double f_x, f_y;
162    double z0s2 = z0_scale(f)*z0_scale(f);
163
164    /*
165     * Finally, we convert to model matrix coordinates.
166     *
167     * v' = v+c
168     */
169
170    m->b[0] += f->cx;
171    m->b[1] += f->cy;
172
173    /*
174     * Apply shrinkage caused by rotation out of z0.
175     * We need to divide by x = cos a. We have f = tan a.
176     * With sin^2 a + cos^2 a = 1, we get
177     *
178     * f = sqrt(1-cos^2 a)/cos a
179     * = sqrt(1-x^2)/x
180     * f^2 = 1/x^2-1
181     * 1/(f^2+1) = x^2
182     * cos a = sqrt(1/(f^2+1))
183     */
184
185    f_x = sqrt(f->fx*f->fx*z0s2+1);
186    f_y = sqrt(f->fy*f->fy*z0s2+1);
187
188    m->a[0][0] *= f_x;
189    m->a[0][1] *= f_x;
190// m->b[0] *= f_x;
191    m->a[1][0] *= f_y;
192    m->a[1][1] *= f_y;
193// m->b[1] *= f_y;
194
195    /*
196     * The transformation matrix f->m describes a transformation of
197     * (centered) model coordinates. We therefore have to reverse it:
198     *
199     * v = v'A+b
200     * v-b = v'A
201     * (v-b)A^-1 = v'
202     * vA^-1-bA^-1 = v'
203     */
204
205    matrix_invert(f->m.a, tm);
206    matrix_multv(f->m.b, tm, tv);
207    tv[0] = -tv[0];
208    tv[1] = -tv[1];
209
210    /*
211     * Merge with the transformations we have so far:
212     *
213     * v' = vA1+b1 the transformation we have so far
214     * v'' = v'A2+b2 the transformation we apply
215     *
216     * v'' = (vA1+b1)A2+b2
217     * v'' = vA1A2+b1A2+b2
218     */
219
220    /*
221     * So far, the theory. To make it really work, we have to calculate
222     * v'' = vA1A2+b1+b2
223     * duh ?!?
224     */
225
226    matrix_mult(m->a, tm, tm2); /* A1A2 */
227    matrix_copy(tm2, m->a);
228// matrix_multv(m->b, tm, m->b); /* b1A2 */
229    m->b[0] += tv[0]; /* b2 */
230    m->b[1] += tv[1];
231
232    /*
233     * Our input is a screen coordinate, its origin is in a corner so we
234     * first have to make it center-based:
235     *
236     * v' = (v-s/2)A+b
237     * v' = vA+(b-s/2*A)
238     */
239
240    tv[0] = sx(s)/2;
241    tv[1] = sy(s)/2;
242    matrix_multv(tv, m->a, tv);
243    m->b[0] -= tv[0];
244    m->b[1] -= tv[1];
245}
246
247
248static void draw_map(GtkWidget *widget, struct solid *s)
249{
250    guchar *rgbbuf, *p;
251    int x, y;
252    struct matrix ma = {
253        .a = { { 1, 0 }, { 0, 1 } },
254        .b = { 0, 0 },
255    };
256    struct matrix mb = {
257        .a = { { -1, 0 }, { 0, 1 } },
258        .b = { 0, 0 },
259    };
260
261    rgbbuf = p = calloc(sx(s)*sy(s), 3);
262    if (!rgbbuf) {
263        perror("calloc");
264        exit(1);
265    }
266
267    merge_matrix(&ma, s, s->a);
268    merge_matrix(&mb, s, s->b);
269
270    for (y = sy(s)-1; y >= 0; y--)
271        for (x = 0; x != sx(s) ; x++) {
272            point(s, x, y, p, &ma, &mb);
273            p += 3;
274        }
275    gdk_draw_rgb_image(widget->window,
276        widget->style->fg_gc[GTK_STATE_NORMAL],
277        0, 0, sx(s), sy(s), GDK_RGB_DITHER_MAX, rgbbuf, sx(s)*3);
278    free(rgbbuf);
279}
280
281
282static void draw_image(GtkWidget *widget, struct solid *s, int osd)
283{
284    int cx = sx(s)/2;
285    int cy = sy(s)/2;
286    int p;
287
288    draw_map(widget, s);
289    has_osd = osd;
290    if (!osd)
291        return;
292    draw_circle(widget->window, gc_osd, cx, cy, r_center(s));
293    p = r_center(s)/sqrt(2);
294    gdk_draw_line(widget->window, gc_osd, cx-p, cy-p, cx+p, cy+p);
295    gdk_draw_line(widget->window, gc_osd, cx-p, cy+p, cx+p, cy-p);
296}
297
298
299/*
300 * Rotate such that a point at distance "r" moves one unit. Rotate
301 * counter-clockwise for r > 1, clockwise for r < 0.
302 */
303
304static void rotate(struct matrix *m, double r)
305{
306    struct matrix t;
307    double s, c;
308
309    s = 1/r;
310    c = sqrt(1-s*s);
311    t.a[0][0] = m->a[0][0]*c-m->a[1][0]*s;
312    t.a[0][1] = m->a[0][1]*c-m->a[1][1]*s;
313    t.a[1][0] = m->a[1][0]*c+m->a[0][0]*s;
314    t.a[1][1] = m->a[1][1]*c+m->a[0][1]*s;
315    t.b[0] = m->b[0]*c-m->b[1]*s;
316    t.b[1] = m->b[0]*s+m->b[1]*c;
317    *m = t;
318}
319
320
321static void do_shift(struct matrix *m, double dx, double dy)
322{
323    m->b[0] += dx;
324    m->b[1] += dy;
325}
326
327
328static void shift(struct matrix *m, int dx, int dy, double dist)
329{
330    /*
331     * Wheeling "up" in each quadrant shifts in the respective direction,
332     * wheeling "down" in the opposite direction.
333     *
334     * No rule without exception: we treat the "down" quadrant like the
335     * "up" quadrant, because it would be extremely counter-intuitive to
336     * wheel "up" to move "down".
337     */
338
339    if (dx > 0 && dy < dx && dy > -dx)
340        do_shift(m, dist, 0);
341    if (dx < 0 && dy < -dx && dy > dx)
342        do_shift(m, -dist, 0);
343    if (dy > 0 && dx < dy && dx > -dy)
344        do_shift(m, 0, dist);
345    if (dy < 0 && dx < -dy && dx > dy)
346        do_shift(m, 0, dist); /* exception ! */
347}
348
349
350static int osd_proximity(const struct solid *s, int dx, int dy)
351{
352    double r = hypot(dx, dy);
353    double rc = r_center(s);
354
355    if (fabs(r-rc) < OSD_PROXIMITY)
356        return 1;
357    if (r > rc)
358        return 0;
359    if (abs(abs(dx)-abs(dy)) < OSD_PROXIMITY)
360        return 1;
361    return 0;
362}
363
364
365static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event,
366    gpointer data)
367{
368    GtkWidget *darea = gtk_bin_get_child(GTK_BIN(widget));
369    struct solid *s = data;
370    int dx = event->x-sx(s)/2;
371    int dy = event->y-sy(s)/2;
372    double r = hypot(dx, dy);
373    double rc = r_center(s);
374    double rs, rot, dist;
375    int center = r < rc;
376    int osd = osd_proximity(s, dx, dy);
377
378    if (r < 1)
379        return TRUE;
380
381    /*
382     * rot goes exponentially from SLOWEST_ROT*rs to FASTEST_ROT for
383     * r = rc to rs, with rs being half the canvas diagonal.
384     *
385     * The values are picked such that we achieve sufficient precision at
386     * a reasonably large distance from the circle (for accidently entering
387     * the circle would change the mode) but can also spin quickly, e.g.,
388     * when a 180 degrees rotation is needed.
389     *
390     * First, normalize to 0 ... 1
391     * Then, we start at exp(0) and end at
392     * exp(ln(SLOWEST_ROT*rs/FASTEST_ROT)))
393     */
394    rs = hypot(sx(s), sy(s))/2;
395    rot = (r-rc)/(rs-rc);
396    rot = SLOWEST_ROT*rs*exp(-rot*log(SLOWEST_ROT*rs/FASTEST_ROT));
397
398    /*
399     * dist stays at 1 from 0...rc/DIST_STEPS, then linearly goes up to
400     * DIST_STEPS from rc/DIST_STEPS...rc
401     */
402    dist = r/rc*DIST_STEPS;
403    if (dist < 0)
404        dist = 1;
405
406    switch (event->direction) {
407    case GDK_SCROLL_UP:
408        if (center)
409            shift(edit_top ? &s->a->m : &s->b->m,
410                edit_top ? dx : -dx, dy, dist);
411        else
412            rotate(edit_top ? &s->a->m : &s->b->m,
413                dx > 0 ? rot : -rot);
414        draw_image(darea, s, osd);
415        break;
416    case GDK_SCROLL_DOWN:
417        if (center)
418            shift(edit_top ? &s->a->m : &s->b->m,
419                edit_top ? dx : -dx, dy, -dist);
420        else
421            rotate(edit_top ? &s->a->m : &s->b->m,
422                dx > 0 ? -rot : rot);
423        draw_image(darea, s, osd);
424        break;
425    default:
426        /* ignore */;
427    }
428    return TRUE;
429}
430
431
432static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event,
433    gpointer user_data)
434{
435    draw_image(widget, user_data, has_osd);
436    return TRUE;
437}
438
439
440static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event,
441    gpointer data)
442{
443    struct solid *s = data;
444    int dx = event->x-sx(s)/2;
445    int dy = event->y-sy(s)/2;
446    int osd = osd_proximity(s, dx, dy);
447
448    if (osd != has_osd)
449        draw_image(widget, s, osd);
450    return FALSE;
451}
452
453
454void overlap_edit(int top)
455{
456    edit_top = top;
457}
458
459
460void overlap(GtkWidget *canvas, struct solid *s)
461{
462    GtkWidget *evbox, *darea;
463
464    evbox = gtk_event_box_new();
465    darea = gtk_drawing_area_new();
466
467    gtk_widget_set_events(darea,
468        GDK_EXPOSE | GDK_KEY_PRESS_MASK |
469        GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
470        GDK_SCROLL |
471        GDK_POINTER_MOTION_MASK);
472
473    gtk_widget_set_size_request(darea, sx(s), sy(s));
474    gtk_container_add(GTK_CONTAINER(canvas), evbox);
475    gtk_container_add(GTK_CONTAINER(evbox), darea);
476
477    draw_image(darea, s, 0);
478
479    g_signal_connect(G_OBJECT(evbox), "scroll-event",
480        G_CALLBACK(scroll_event), s);
481    g_signal_connect(G_OBJECT(darea), "expose-event",
482        G_CALLBACK(expose_event), s);
483    g_signal_connect(G_OBJECT(darea), "motion-notify-event",
484        G_CALLBACK(motion_notify_event), s);
485
486if (0) {
487int i;
488long t0 = time(NULL);
489gtk_widget_show_all(canvas);
490for (i = 0; i != 1000; i++) {
491    rotate(&s->a->m, 100);
492    draw_image(darea, s, 0);
493    while (gtk_events_pending())
494        gtk_main_iteration();
495}
496fprintf(stderr, "%lu\n", time(NULL)-t0);
497}
498
499}
500

Archive Download this file

Branches:
master



interactive