Fix is_matrix_symmetry() to correctly choose between c1 and s1.
[gliv.git] / src / matrix.c
blob531e2523e6d90ea67c2f7d0bb6230e86de54410b
1 /*
2 * This program is free software; you can redistribute it and/or
3 * modify it under the terms of the GNU General Public License
4 * as published by the Free Software Foundation; either version 2
5 * of the License, or (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <guichaz@gmail.com>
21 /************************
22 * The MODELVIEW matrix *
23 ************************/
25 #include <string.h> /* memcpy() */
27 #include "gliv.h"
28 #include "matrix.h"
29 #include "math_floats.h" /* cosf(), sinf(), ... */
30 #include "options.h"
31 #include "opengl.h"
32 #include "gliv-image.h"
34 extern rt_struct *rt;
35 extern options_struct *options;
36 extern GlivImage *current_image;
39 * OpenGL uses a transposed matrix, we use a 'normal' one,
40 * we transpose it just before glLoadMatrix().
42 * Value: Index:
43 * c1 s1 0 x | 0 1 2 3
44 * s2 c2 0 y | 4 5 6 7
45 * 0 0 1 0 | 8 9 10 11 \ constant so
46 * 0 0 0 1 | 12 13 14 15 / unused.
49 #define MATRIX_C1 matrix[0]
50 #define MATRIX_S1 matrix[1]
51 #define MATRIX_X matrix[3]
52 #define MATRIX_S2 matrix[4]
53 #define MATRIX_C2 matrix[5]
54 #define MATRIX_Y matrix[7]
56 static gfloat matrix[8] = {
57 /* We only use two rows. */
58 1.0, 0.0, 0.0, 0.0,
59 0.0, 1.0, 0.0, 0.0
62 /* Used to know if the OpenGL matrix and this one are in sync. */
63 static gboolean matrix_changed = TRUE;
65 static void touch_matrix(void)
67 matrix_changed = TRUE;
70 G_GNUC_PURE gfloat get_matrix_zoom(void)
73 * c1 == zoom*cos, s1 == zoom*sin
74 * cos² + sin² == 1 => c1² + s1² == zoom²
76 return hypotf(MATRIX_C1, MATRIX_S1);
79 /* To be displayed in the status bar. */
80 G_GNUC_PURE gfloat get_matrix_angle(void)
82 gfloat cosine, angle;
84 cosine = MATRIX_C1 / get_matrix_zoom();
85 angle = acosf(cosine);
87 if (MATRIX_S1 < 0)
88 /* Negative sine => negative angle. */
89 angle *= -1.0;
91 return angle;
94 /* OpenGL coordinates to window coordinates. */
95 static void point_coord(gfloat x, gfloat y, gfloat * res_x, gfloat * res_y)
97 /* OpenGL coordinates through the modelview matrix. */
98 *res_x = MATRIX_C1 * x + MATRIX_S1 * y + MATRIX_X;
99 *res_y = MATRIX_S2 * x + MATRIX_C2 * y + MATRIX_Y;
101 /* And now through the projection matrix. */
102 *res_x += rt->wid_size->width / 2.0;
103 *res_y += rt->wid_size->height / 2.0;
106 static gfloat min4(gfloat a, gfloat b, gfloat c, gfloat d)
108 if (a > b)
109 a = b;
111 if (c > d)
112 c = d;
114 return MIN(a, c);
117 static gfloat max4(gfloat a, gfloat b, gfloat c, gfloat d)
119 if (a < b)
120 a = b;
122 if (c < d)
123 c = d;
125 return MAX(a, c);
128 /* Convenient function: a == b ? */
129 G_GNUC_PURE gboolean float_equal(gfloat a, gfloat b)
131 return fabsf(a - b) < 1e-3;
134 void get_matrix_bounding_box(gfloat * min_x, gfloat * max_x,
135 gfloat * min_y, gfloat * max_y)
137 gfloat x0, y0, x1, y1;
138 gfloat x2, y2, x3, y3;
139 gfloat half_w, half_h;
141 half_w = current_image->width / 2.0;
142 half_h = current_image->height / 2.0;
144 point_coord(-half_w, -half_h, &x0, &y0);
145 point_coord(half_w, -half_h, &x1, &y1);
146 point_coord(-half_w, half_h, &x2, &y2);
147 point_coord(half_w, half_h, &x3, &y3);
149 *min_x = min4(x0, x1, x2, x3);
150 *max_x = max4(x0, x1, x2, x3);
151 *min_y = min4(y0, y1, y2, y3);
152 *max_y = max4(y0, y1, y2, y3);
155 /*** Input, output. ***/
157 void write_gl_matrix(void)
159 /* *INDENT-OFF* */
160 static gfloat transposed[16] = {
161 1.0, 0.0, 0.0, 0.0,
162 0.0, 1.0, 0.0, 0.0,
163 0.0, 0.0, 1.0, 0.0,
164 0.0, 0.0, 0.0, 1.0
166 /* *INDENT-ON* */
168 if (matrix_changed) {
169 transposed[0] = MATRIX_C1;
170 transposed[5] = MATRIX_C2;
171 transposed[4] = MATRIX_S1;
172 transposed[1] = MATRIX_S2;
173 transposed[12] = MATRIX_X;
174 transposed[13] = MATRIX_Y;
176 glLoadMatrixf(transposed);
177 matrix_changed = FALSE;
181 /* In parameters dest and src, NULL is the current matrix. */
182 void matrix_cpy(gfloat * dest, gfloat * src)
184 if (dest == NULL) {
185 dest = matrix;
186 touch_matrix();
187 } else if (src == NULL)
188 src = matrix;
190 memcpy(dest, src, sizeof(matrix));
193 /*** Informations gathering about the matrix. ***/
195 G_GNUC_PURE static gboolean angle_is_right(void)
197 gfloat modulo;
199 modulo = fmodf(get_matrix_angle(), PI / 2.0);
201 return float_equal(modulo, 0.0);
204 gboolean matrix_tile_visible(tile_dim * tile)
206 static guint select_buffer[4];
208 glSelectBuffer(4, select_buffer);
209 glRenderMode(GL_SELECT);
211 glInitNames();
212 glPushName(0);
214 glRectf(tile->x0, tile->y0, tile->x1, tile->y1);
216 return glRenderMode(GL_RENDER) > 0;
219 G_GNUC_PURE gboolean is_matrix_symmetry(void)
222 * c1 == c2 => rotation, c1 == -c2 => symmetry.
223 * s1 == -s2 => rotation, s1 == s2 => symmetry.
225 * Since (c1/zoom)² + (s1/zoom)² == 1, we use c1 only if far enough from 0.
228 return fabsf(MATRIX_C1) > fabsf(MATRIX_S1) ?
229 !float_equal(MATRIX_C1, MATRIX_C2) :
230 !float_equal(MATRIX_S1, -MATRIX_S2);
233 G_GNUC_PURE gboolean get_matrix_has_changed(void)
235 return matrix_changed;
238 G_GNUC_PURE gboolean is_filtering_needed(void)
240 return (float_equal(get_matrix_zoom(), 1.0) == FALSE) ||
241 (angle_is_right() == FALSE);
244 /*** Operations on the matrix. ***/
246 /* Returns FALSE if the image is already maximised. */
247 gboolean matrix_set_max_zoom(gint width, gint height, gboolean do_it)
249 gfloat min_x, max_x, min_y, max_y, zoom;
251 if (current_image == NULL)
252 return TRUE;
254 if (do_it == FALSE && (float_equal(MATRIX_X, 0.0) == FALSE ||
255 float_equal(MATRIX_Y, 0.0) == FALSE))
256 /* Image not centered. */
257 return TRUE;
259 if (width < 0)
260 width = rt->wid_size->width;
262 if (height < 0)
263 height = rt->wid_size->height;
265 if ((options->maximize == FALSE &&
266 (current_image->width < width && current_image->height < height)) ||
267 (options->scaledown == FALSE &&
268 (current_image->width > width || current_image->height > height)))
269 return TRUE;
271 get_matrix_bounding_box(&min_x, &max_x, &min_y, &max_y);
272 zoom = MIN(width / (max_x - min_x), height / (max_y - min_y));
274 if (float_equal(zoom, 1.0) == FALSE ||
275 float_equal(MATRIX_X, 0.0) == FALSE ||
276 float_equal(MATRIX_Y, 0.0) == FALSE) {
278 if (do_it) {
279 matrix_zoom(zoom, 0.0, 0.0);
280 MATRIX_X = MATRIX_Y = 0.0;
282 return TRUE;
285 return FALSE;
288 void matrix_fit(gboolean fit_w, gboolean fit_h)
290 gfloat min_x, max_x, min_y, max_y, zoom = INFINITY;
292 get_matrix_bounding_box(&min_x, &max_x, &min_y, &max_y);
294 if (fit_w) {
295 MATRIX_X = 0.0;
296 zoom = MIN(zoom, (gfloat) rt->wid_size->width / (max_x - min_x));
299 if (fit_h) {
300 MATRIX_Y = 0.0;
301 zoom = MIN(zoom, (gfloat) rt->wid_size->height / (max_y - min_y));
304 matrix_zoom(zoom, rt->wid_size->width / 2.0, rt->wid_size->height / 2.0);
308 static void reset_matrix(gfloat * mat)
310 mat[0] = 1.0;
311 mat[1] = 0.0;
312 mat[2] = 0.0;
313 mat[3] = 0.0;
314 mat[4] = 0.0;
315 mat[5] = 1.0;
316 mat[6] = 0.0;
317 mat[7] = 0.0;
320 void matrix_reset(void)
322 reset_matrix(matrix);
323 touch_matrix();
327 * Rotation: Product:
328 * cos sin 0 0 | c1*cos+s2*sin s1*cos+c2*sin 0 x*cos+y*sin
329 * -sin cos 0 0 | -c1*sin+s2*cos -s1*sin+c2*cos 0 -x*sin+y*cos
330 * 0 0 1 0 | 0 0 1 0
331 * 0 0 0 1 | 0 0 0 1
333 void matrix_rotate(gfloat angle)
335 gfloat cosine, sine;
336 gfloat c1, s1, c2, s2, x, y;
337 gboolean zoom;
339 /* Do we maximize after rotating? */
340 zoom = (options->maximize || options->scaledown) &&
341 (matrix_set_max_zoom(-1, -1, FALSE) == FALSE);
343 cosine = cosf(angle);
344 sine = sinf(angle);
346 /* Backup, as we'll modify them. */
347 c1 = MATRIX_C1;
348 c2 = MATRIX_C2;
349 s1 = MATRIX_S1;
350 s2 = MATRIX_S2;
351 x = MATRIX_X;
352 y = MATRIX_Y;
354 MATRIX_C1 = c1 * cosine + s2 * sine;
355 MATRIX_S1 = s1 * cosine + c2 * sine;
356 MATRIX_S2 = -c1 * sine + s2 * cosine;
357 MATRIX_C2 = -s1 * sine + c2 * cosine;
359 MATRIX_X = x * cosine + y * sine;
360 MATRIX_Y = -x * sine + y * cosine;
362 if (zoom)
363 matrix_set_max_zoom(-1, -1, TRUE);
365 touch_matrix();
368 void matrix_move(gfloat x, gfloat y)
370 MATRIX_X += x;
371 MATRIX_Y += y;
373 touch_matrix();
376 /* (x, y): zoom center. */
377 void matrix_zoom(gfloat ratio, gfloat x, gfloat y)
379 gfloat offset_x, offset_y;
381 offset_x = rt->wid_size->width / 2.0 - x;
382 offset_y = rt->wid_size->height / 2.0 - y;
384 matrix_move(offset_x, offset_y);
386 MATRIX_C1 *= ratio;
387 MATRIX_S1 *= ratio;
388 MATRIX_X *= ratio;
389 MATRIX_S2 *= ratio;
390 MATRIX_C2 *= ratio;
391 MATRIX_Y *= ratio;
393 matrix_move(-offset_x, -offset_y);
396 void flip_matrix(gfloat * mat, gboolean h_flip)
398 /* Flip either the x or y row. */
399 gint id = h_flip ? 4 : 0;
401 mat[id] *= -1.0;
402 mat[id + 1] *= -1.0;
403 /* mat[id + 2] is 0.0. */
404 mat[id + 3] *= -1.0;
407 void matrix_flip_h(void)
409 /* Flip y. */
410 flip_matrix(matrix, TRUE);
411 touch_matrix();
414 void matrix_flip_v(void)
416 /* Flip x. */
417 flip_matrix(matrix, FALSE);
418 touch_matrix();
421 void matrix_set_initial_position(void)
423 gfloat min_x, max_x, min_y, max_y;
424 enum image_position pos = options->initial_pos;
426 if (pos == POSITION_KEEP)
427 return;
429 get_matrix_bounding_box(&min_x, &max_x, &min_y, &max_y);
431 if (pos == POSITION_CENTER) {
432 matrix_move((rt->wid_size->width - min_x - max_x) / 2.0,
433 (rt->wid_size->height - min_y - max_y) / 2.0);
434 return;
437 if (min_x < 0.0 || max_x > (gfloat) rt->wid_size->width) {
438 if (pos == POSITION_TOP_LEFT || pos == POSITION_BOTTOM_LEFT)
439 matrix_move(-min_x, 0.0);
440 else
441 matrix_move((gfloat) rt->wid_size->width - max_x, 0.0);
444 if (min_y < 0.0 || max_y > (gfloat) rt->wid_size->height) {
445 if (pos == POSITION_TOP_LEFT || pos == POSITION_TOP_RIGHT)
446 matrix_move(0.0, -min_y);
447 else
448 matrix_move(0.0, (gfloat) rt->wid_size->height - max_y);
452 void configure_matrix(GlivImage * im)
454 GlivImage *old = current_image;
456 current_image = im;
457 if (im->first_image || options->keep_transfo == FALSE) {
458 matrix_reset();
459 if (options->maximize || options->scaledown)
460 matrix_set_max_zoom(-1, -1, TRUE);
463 matrix_set_initial_position();
465 current_image = old;
468 gfloat *new_matrix(void)
470 gfloat *new_mat = g_new(gfloat, 8);
472 reset_matrix(new_mat);
473 return new_mat;
476 gfloat *get_matrix_for_image(GlivImage * im)
478 gfloat current_matrix[8];
479 gfloat *new_mat;
481 matrix_cpy(current_matrix, NULL);
482 configure_matrix(im);
484 new_mat = new_matrix();
485 matrix_cpy(new_mat, NULL);
487 matrix_cpy(NULL, current_matrix);
489 return new_mat;