17f948f144a0d8a3cef3b56122b1402428cbad8b
[xscreensaver] / hacks / glx / hypertorus.c
1 /* hypertorus --- Shows a hypertorus that rotates in 4d */
2
3 #if 0
4 static const char sccsid[] = "@(#)hypertorus.c  1.2 05/09/28 xlockmore";
5 #endif
6
7 /* Copyright (c) 2003-2007 Carsten Steger <carsten@mirsanmir.org>. */
8
9 /*
10  * Permission to use, copy, modify, and distribute this software and its
11  * documentation for any purpose and without fee is hereby granted,
12  * provided that the above copyright notice appear in all copies and that
13  * both that copyright notice and this permission notice appear in
14  * supporting documentation.
15  *
16  * This file is provided AS IS with no warranties of any kind.  The author
17  * shall have no liability with respect to the infringement of copyrights,
18  * trade secrets or any patents by this file or any part thereof.  In no
19  * event will the author be liable for any lost revenue or profits or
20  * other special, indirect and consequential damages.
21  *
22  * REVISION HISTORY:
23  * C. Steger - 03/05/18: Initial version
24  * C. Steger - 05/09/28: Added the spirals appearance mode
25  *                       and trackball support
26  * C. Steger - 07/01/23: Improved 4d trackball support
27  */
28
29 /*
30  * This program shows the Clifford torus as it rotates in 4d.  The Clifford
31  * torus is a torus lies on the "surface" of the hypersphere in 4d.  The
32  * program projects the 4d torus to 3d using either a perspective or an
33  * orthographic projection.  Of the two alternatives, the perspecitve
34  * projection looks much more appealing.  In orthographic projections the
35  * torus degenerates into a doubly covered cylinder for some angles.  The
36  * projected 3d torus can then be projected to the screen either perspectively
37  * or orthographically.  There are three display modes for the torus: mesh
38  * (wireframe), solid, or transparent.  Furthermore, the appearance of the
39  * torus can be as a solid object or as a set of see-through bands or
40  * see-through spirals.  Finally, the colors with with the torus is drawn can
41  * be set to either two-sided or to colorwheel.  In the first case, the torus
42  * is drawn with red on the outside and green on the inside.  This mode
43  * enables you to see that the torus turns inside-out as it rotates in 4d.
44  * The second mode draws the torus in a fully saturated color wheel.  This
45  * gives a very nice effect when combined with the see-through bands or
46  * see-through spirals mode.  The rotation speed for each of the six planes
47  * around which the torus rotates can be chosen.  This program is very much
48  * inspired by Thomas Banchoff's book "Beyond the Third Dimension: Geometry,
49  * Computer Graphics, and Higher Dimensions", Scientific American Library,
50  * 1990.
51  */
52
53 #ifndef M_PI
54 #define M_PI 3.14159265358979323846
55 #endif
56
57 #define DISP_WIREFRAME             0
58 #define DISP_WIREFRAME_STR        "0"
59 #define DISP_SURFACE               1
60 #define DISP_SURFACE_STR          "1"
61 #define DISP_TRANSPARENT           2
62 #define DISP_TRANSPARENT_STR      "2"
63
64 #define APPEARANCE_SOLID           0
65 #define APPEARANCE_SOLID_STR      "0"
66 #define APPEARANCE_BANDS           1
67 #define APPEARANCE_BANDS_STR      "1"
68 #define APPEARANCE_SPIRALS         2
69 #define APPEARANCE_SPIRALS_STR    "2"
70 #define APPEARANCE_SPIRALS_1       3
71 #define APPEARANCE_SPIRALS_1_STR  "3"
72 #define APPEARANCE_SPIRALS_2       4
73 #define APPEARANCE_SPIRALS_2_STR  "4"
74 #define APPEARANCE_SPIRALS_4       5
75 #define APPEARANCE_SPIRALS_4_STR  "5"
76 #define APPEARANCE_SPIRALS_8       6
77 #define APPEARANCE_SPIRALS_8_STR  "6"
78 #define APPEARANCE_SPIRALS_16      7
79 #define APPEARANCE_SPIRALS_16_STR "7"
80
81 #define COLORS_TWOSIDED            0
82 #define COLORS_TWOSIDED_STR       "0"
83 #define COLORS_COLORWHEEL          1
84 #define COLORS_COLORWHEEL_STR     "1"
85
86 #define DISP_3D_PERSPECTIVE        0
87 #define DISP_3D_PERSPECTIVE_STR   "0"
88 #define DISP_3D_ORTHOGRAPHIC       1
89 #define DISP_3D_ORTHOGRAPHIC_STR  "1"
90
91 #define DISP_4D_PERSPECTIVE        0
92 #define DISP_4D_PERSPECTIVE_STR   "0"
93 #define DISP_4D_ORTHOGRAPHIC       1
94 #define DISP_4D_ORTHOGRAPHIC_STR  "1"
95
96 #define DALPHA                     1.1
97 #define DALPHA_STR                "1.1"
98 #define DBETA                      1.3
99 #define DBETA_STR                 "1.3"
100 #define DDELTA                     1.5
101 #define DDELTA_STR                "1.5"
102 #define DZETA                      1.7
103 #define DZETA_STR                 "1.7"
104 #define DETA                       1.9
105 #define DETA_STR                  "1.9"
106 #define DTHETA                     2.1
107 #define DTHETA_STR                "2.1"
108
109 #define DEF_DISPLAY_MODE           DISP_TRANSPARENT_STR
110 #define DEF_APPEARANCE             APPEARANCE_BANDS_STR
111 #define DEF_COLORS                 COLORS_COLORWHEEL_STR
112 #define DEF_3D_PROJECTION          DISP_3D_PERSPECTIVE_STR
113 #define DEF_4D_PROJECTION          DISP_4D_PERSPECTIVE_STR
114 #define DEF_DALPHA                 DALPHA_STR
115 #define DEF_DBETA                  DBETA_STR
116 #define DEF_DDELTA                 DDELTA_STR
117 #define DEF_DZETA                  DZETA_STR
118 #define DEF_DETA                   DETA_STR
119 #define DEF_DTHETA                 DTHETA_STR
120
121 #ifdef STANDALONE
122 # define DEFAULTS           "*delay:      25000 \n" \
123                             "*showFPS:    False \n" \
124
125 # define refresh_hypertorus 0
126 # include "xlockmore.h"         /* from the xscreensaver distribution */
127 #else  /* !STANDALONE */
128 # include "xlock.h"             /* from the xlockmore distribution */
129 #endif /* !STANDALONE */
130
131 #ifdef USE_GL
132
133 #include <X11/keysym.h>
134
135 #include "gltrackball.h"
136
137
138 #ifdef USE_MODULES
139 ModStruct   hypertorus_description =
140 {"hypertorus", "init_hypertorus", "draw_hypertorus", "release_hypertorus",
141  "draw_hypertorus", "change_hypertorus", NULL, &hypertorus_opts,
142  25000, 1, 1, 1, 1.0, 4, "",
143  "Shows a hypertorus rotating in 4d", 0, NULL};
144
145 #endif
146
147
148 static int display_mode;
149 static int appearance;
150 static int num_spirals;
151 static int colors;
152 static int projection_3d;
153 static int projection_4d;
154 static float speed_wx;
155 static float speed_wy;
156 static float speed_wz;
157 static float speed_xy;
158 static float speed_xz;
159 static float speed_yz;
160
161 static const float offset4d[4] = {  0.0,  0.0,  0.0,  2.0 };
162 static const float offset3d[4] = {  0.0,  0.0, -2.0,  0.0 };
163
164
165 static XrmOptionDescRec opts[] =
166 {
167   {"-mode",            ".hypertorus.displayMode",  XrmoptionSepArg, 0 },
168   {"-wireframe",       ".hypertorus.displayMode",  XrmoptionNoArg,
169                        DISP_WIREFRAME_STR },
170   {"-surface",         ".hypertorus.displayMode",  XrmoptionNoArg,
171                        DISP_SURFACE_STR },
172   {"-transparent",     ".hypertorus.displayMode",  XrmoptionNoArg,
173                        DISP_TRANSPARENT_STR },
174
175   {"-appearance",      ".hypertorus.appearance",   XrmoptionSepArg, 0 },
176   {"-solid",           ".hypertorus.appearance",   XrmoptionNoArg,
177                        APPEARANCE_SOLID_STR },
178   {"-bands",           ".hypertorus.appearance",   XrmoptionNoArg,
179                        APPEARANCE_BANDS_STR },
180   {"-spirals-1",       ".hypertorus.appearance",   XrmoptionNoArg,
181                        APPEARANCE_SPIRALS_1_STR },
182   {"-spirals-2",       ".hypertorus.appearance",   XrmoptionNoArg,
183                        APPEARANCE_SPIRALS_2_STR },
184   {"-spirals-4",       ".hypertorus.appearance",   XrmoptionNoArg,
185                        APPEARANCE_SPIRALS_4_STR },
186   {"-spirals-8",       ".hypertorus.appearance",   XrmoptionNoArg,
187                        APPEARANCE_SPIRALS_8_STR },
188   {"-spirals-16",      ".hypertorus.appearance",   XrmoptionNoArg,
189                        APPEARANCE_SPIRALS_16_STR },
190   {"-twosided",        ".hypertorus.colors",       XrmoptionNoArg,
191                        COLORS_TWOSIDED_STR },
192   {"-colorwheel",      ".hypertorus.colors",       XrmoptionNoArg,
193                        COLORS_COLORWHEEL_STR },
194   {"-perspective-3d",  ".hypertorus.projection3d", XrmoptionNoArg,
195                        DISP_3D_PERSPECTIVE_STR },
196   {"-orthographic-3d", ".hypertorus.projection3d", XrmoptionNoArg,
197                        DISP_3D_ORTHOGRAPHIC_STR },
198   {"-perspective-4d",  ".hypertorus.projection4d", XrmoptionNoArg,
199                        DISP_4D_PERSPECTIVE_STR },
200   {"-orthographic-4d", ".hypertorus.projection4d", XrmoptionNoArg,
201                        DISP_4D_ORTHOGRAPHIC_STR },
202   {"-speed-wx",        ".hypertorus.speedwx",      XrmoptionSepArg, 0 },
203   {"-speed-wy",        ".hypertorus.speedwy",      XrmoptionSepArg, 0 },
204   {"-speed-wz",        ".hypertorus.speedwz",      XrmoptionSepArg, 0 },
205   {"-speed-xy",        ".hypertorus.speedxy",      XrmoptionSepArg, 0 },
206   {"-speed-xz",        ".hypertorus.speedxz",      XrmoptionSepArg, 0 },
207   {"-speed-yz",        ".hypertorus.speedyz",      XrmoptionSepArg, 0 }
208 };
209
210 static argtype vars[] =
211 {
212   { &display_mode,  "displayMode",  "DisplayMode",
213     DEF_DISPLAY_MODE,  t_Int },
214   { &appearance,    "appearance",   "Appearance",
215     DEF_APPEARANCE,    t_Int },
216   { &colors,        "colors",       "Colors",
217     DEF_COLORS,        t_Int },
218   { &projection_3d, "projection3d", "Projection3d",
219     DEF_3D_PROJECTION, t_Int },
220   { &projection_4d, "projection4d", "Projection4d",
221     DEF_4D_PROJECTION, t_Int },
222   { &speed_wx,      "speedwx",      "Speedwx",
223     DEF_DALPHA,        t_Float},
224   { &speed_wy,      "speedwy",      "Speedwy",
225     DEF_DBETA,         t_Float},
226   { &speed_wz,      "speedwz",      "Speedwz",
227     DEF_DDELTA,        t_Float},
228   { &speed_xy,      "speedxy",      "Speedxy",
229     DEF_DZETA,         t_Float},
230   { &speed_xz,      "speedxz",      "Speedxz",
231     DEF_DETA,          t_Float},
232   { &speed_yz,      "speedyz",      "Speedyz",
233     DEF_DTHETA,        t_Float}
234 };
235
236 static OptionStruct desc[] =
237 {
238   { "-wireframe",       "display the torus as a wireframe mesh" },
239   { "-surface",         "display the torus as a solid surface" },
240   { "-transparent",     "display the torus as a transparent surface" },
241   { "-solid",           "display the torus as a solid object" },
242   { "-bands",           "display the torus as see-through bands" },
243   { "-spirals-{1,2,4,8,16}", "display the torus as see-through spirals" },
244   { "-twosided",        "display the torus with two colors" },
245   { "-colorwheel",      "display the torus with a smooth color wheel" },
246   { "-perspective-3d",  "project the torus perspectively from 3d to 2d" },
247   { "-orthographic-3d", "project the torus orthographically from 3d to 2d" },
248   { "-perspective-4d",  "project the torus perspectively from 4d to 3d" },
249   { "-orthographic-4d", "project the torus orthographically from 4d to 3d" },
250   { "-speed-wx <arg>",  "rotation speed around the wx plane" },
251   { "-speed-wy <arg>",  "rotation speed around the wy plane" },
252   { "-speed-wz <arg>",  "rotation speed around the wz plane" },
253   { "-speed-xy <arg>",  "rotation speed around the xy plane" },
254   { "-speed-xz <arg>",  "rotation speed around the xz plane" },
255   { "-speed-yz <arg>",  "rotation speed around the yz plane" }
256 };
257
258 ENTRYPOINT ModeSpecOpt hypertorus_opts =
259 {sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};
260
261
262 typedef struct {
263   GLint      WindH, WindW;
264   GLXContext *glx_context;
265   /* 4D rotation angles */
266   float alpha, beta, delta, zeta, eta, theta;
267   /* Aspect ratio of the current window */
268   float aspect;
269   /* Trackball states */
270   trackball_state *trackballs[2];
271   int current_trackball;
272   Bool button_pressed;
273
274   float speed_scale;
275
276 } hypertorusstruct;
277
278 static hypertorusstruct *hyper = (hypertorusstruct *) NULL;
279
280
281 /* Add a rotation around the wx-plane to the matrix m. */
282 static void rotatewx(float m[4][4], float phi)
283 {
284   float c, s, u, v;
285   int i;
286
287   phi *= M_PI/180.0;
288   c = cos(phi);
289   s = sin(phi);
290   for (i=0; i<4; i++)
291   {
292     u = m[i][1];
293     v = m[i][2];
294     m[i][1] = c*u+s*v;
295     m[i][2] = -s*u+c*v;
296   }
297 }
298
299
300 /* Add a rotation around the wy-plane to the matrix m. */
301 static void rotatewy(float m[4][4], float phi)
302 {
303   float c, s, u, v;
304   int i;
305
306   phi *= M_PI/180.0;
307   c = cos(phi);
308   s = sin(phi);
309   for (i=0; i<4; i++)
310   {
311     u = m[i][0];
312     v = m[i][2];
313     m[i][0] = c*u-s*v;
314     m[i][2] = s*u+c*v;
315   }
316 }
317
318
319 /* Add a rotation around the wz-plane to the matrix m. */
320 static void rotatewz(float m[4][4], float phi)
321 {
322   float c, s, u, v;
323   int i;
324
325   phi *= M_PI/180.0;
326   c = cos(phi);
327   s = sin(phi);
328   for (i=0; i<4; i++)
329   {
330     u = m[i][0];
331     v = m[i][1];
332     m[i][0] = c*u+s*v;
333     m[i][1] = -s*u+c*v;
334   }
335 }
336
337
338 /* Add a rotation around the xy-plane to the matrix m. */
339 static void rotatexy(float m[4][4], float phi)
340 {
341   float c, s, u, v;
342   int i;
343
344   phi *= M_PI/180.0;
345   c = cos(phi);
346   s = sin(phi);
347   for (i=0; i<4; i++)
348   {
349     u = m[i][2];
350     v = m[i][3];
351     m[i][2] = c*u+s*v;
352     m[i][3] = -s*u+c*v;
353   }
354 }
355
356
357 /* Add a rotation around the xz-plane to the matrix m. */
358 static void rotatexz(float m[4][4], float phi)
359 {
360   float c, s, u, v;
361   int i;
362
363   phi *= M_PI/180.0;
364   c = cos(phi);
365   s = sin(phi);
366   for (i=0; i<4; i++)
367   {
368     u = m[i][1];
369     v = m[i][3];
370     m[i][1] = c*u-s*v;
371     m[i][3] = s*u+c*v;
372   }
373 }
374
375
376 /* Add a rotation around the yz-plane to the matrix m. */
377 static void rotateyz(float m[4][4], float phi)
378 {
379   float c, s, u, v;
380   int i;
381
382   phi *= M_PI/180.0;
383   c = cos(phi);
384   s = sin(phi);
385   for (i=0; i<4; i++)
386   {
387     u = m[i][0];
388     v = m[i][3];
389     m[i][0] = c*u-s*v;
390     m[i][3] = s*u+c*v;
391   }
392 }
393
394
395 /* Compute the rotation matrix m from the rotation angles. */
396 static void rotateall(float al, float be, float de, float ze, float et,
397                       float th, float m[4][4])
398 {
399   int i, j;
400
401   for (i=0; i<4; i++)
402     for (j=0; j<4; j++)
403       m[i][j] = (i==j);
404   rotatewx(m,al);
405   rotatewy(m,be);
406   rotatewz(m,de);
407   rotatexz(m,et);
408   rotatexy(m,ze);
409   rotateyz(m,th);
410 }
411
412
413 /* Multiply two rotation matrices: o=m*n. */
414 static void mult_rotmat(float m[4][4], float n[4][4], float o[4][4])
415 {
416   int i, j, k;
417
418   for (i=0; i<4; i++)
419   {
420     for (j=0; j<4; j++)
421     {
422       o[i][j] = 0.0;
423       for (k=0; k<4; k++)
424         o[i][j] += m[i][k]*n[k][j];
425     }
426   }
427 }
428
429
430 /* Compute a 4D rotation matrix from two unit quaternions. */
431 static void quats_to_rotmat(float p[4], float q[4], float m[4][4])
432 {
433   double al, be, de, ze, et, th;
434   double r00, r01, r02, r12, r22;
435
436   r00 = 1.0-2.0*(p[1]*p[1]+p[2]*p[2]);
437   r01 = 2.0*(p[0]*p[1]+p[2]*p[3]);
438   r02 = 2.0*(p[2]*p[0]-p[1]*p[3]);
439   r12 = 2.0*(p[1]*p[2]+p[0]*p[3]);
440   r22 = 1.0-2.0*(p[1]*p[1]+p[0]*p[0]);
441
442   al = atan2(-r12,r22)*180.0/M_PI;
443   be = atan2(r02,sqrt(r00*r00+r01*r01))*180.0/M_PI;
444   de = atan2(-r01,r00)*180.0/M_PI;
445
446   r00 = 1.0-2.0*(q[1]*q[1]+q[2]*q[2]);
447   r01 = 2.0*(q[0]*q[1]+q[2]*q[3]);
448   r02 = 2.0*(q[2]*q[0]-q[1]*q[3]);
449   r12 = 2.0*(q[1]*q[2]+q[0]*q[3]);
450   r22 = 1.0-2.0*(q[1]*q[1]+q[0]*q[0]);
451
452   et = atan2(-r12,r22)*180.0/M_PI;
453   th = atan2(r02,sqrt(r00*r00+r01*r01))*180.0/M_PI;
454   ze = atan2(-r01,r00)*180.0/M_PI;
455
456   rotateall(al,be,de,ze,et,-th,m);
457 }
458
459
460 /* Compute a fully saturated and bright color based on an angle. */
461 static void color(double angle)
462 {
463   int s;
464   double t;
465   float color[4];
466
467   if (colors != COLORS_COLORWHEEL)
468     return;
469
470   if (angle >= 0.0)
471     angle = fmod(angle,2*M_PI);
472   else
473     angle = fmod(angle,-2*M_PI);
474   s = floor(angle/(M_PI/3));
475   t = angle/(M_PI/3)-s;
476   if (s >= 6)
477     s = 0;
478   switch (s)
479   {
480     case 0:
481       color[0] = 1.0;
482       color[1] = t;
483       color[2] = 0.0;
484       break;
485     case 1:
486       color[0] = 1.0-t;
487       color[1] = 1.0;
488       color[2] = 0.0;
489       break;
490     case 2:
491       color[0] = 0.0;
492       color[1] = 1.0;
493       color[2] = t;
494       break;
495     case 3:
496       color[0] = 0.0;
497       color[1] = 1.0-t;
498       color[2] = 1.0;
499       break;
500     case 4:
501       color[0] = t;
502       color[1] = 0.0;
503       color[2] = 1.0;
504       break;
505     case 5:
506       color[0] = 1.0;
507       color[1] = 0.0;
508       color[2] = 1.0-t;
509       break;
510   }
511   if (display_mode == DISP_TRANSPARENT)
512     color[3] = 0.7;
513   else
514     color[3] = 1.0;
515   glColor3fv(color);
516   glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,color);
517 }
518
519
520 /* Draw a hypertorus projected into 3D.  Note that the spirals appearance
521    will only work correctly if numu and numv are set to 64 or any higher
522    power of 2.  Similarly, the banded appearance will only work correctly
523    if numu and numv are divisible by 4. */
524 static int hypertorus(ModeInfo *mi, double umin, double umax, double vmin,
525                        double vmax, int numu, int numv)
526 {
527   int polys = 0;
528   static const GLfloat mat_diff_red[]         = { 1.0, 0.0, 0.0, 1.0 };
529   static const GLfloat mat_diff_green[]       = { 0.0, 1.0, 0.0, 1.0 };
530   static const GLfloat mat_diff_trans_red[]   = { 1.0, 0.0, 0.0, 0.7 };
531   static const GLfloat mat_diff_trans_green[] = { 0.0, 1.0, 0.0, 0.7 };
532   float p[3], pu[3], pv[3], n[3], mat[4][4];
533   int i, j, k, l, m, b, skew;
534   double u, v, ur, vr;
535   double cu, su, cv, sv;
536   double xx[4], xxu[4], xxv[4], x[4], xu[4], xv[4];
537   double r, s, t;
538   float q1[4], q2[4], r1[4][4], r2[4][4];
539   hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
540
541   rotateall(hp->alpha,hp->beta,hp->delta,hp->zeta,hp->eta,hp->theta,r1);
542
543   gltrackball_get_quaternion(hp->trackballs[0],q1);
544   gltrackball_get_quaternion(hp->trackballs[1],q2);
545   quats_to_rotmat(q1,q2,r2);
546
547   mult_rotmat(r2,r1,mat);
548
549   if (colors != COLORS_COLORWHEEL)
550   {
551     glColor3fv(mat_diff_red);
552     if (display_mode == DISP_TRANSPARENT)
553     {
554       glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,mat_diff_trans_red);
555       glMaterialfv(GL_BACK,GL_AMBIENT_AND_DIFFUSE,mat_diff_trans_green);
556     }
557     else
558     {
559       glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,mat_diff_red);
560       glMaterialfv(GL_BACK,GL_AMBIENT_AND_DIFFUSE,mat_diff_green);
561     }
562   }
563
564   skew = num_spirals;
565   ur = umax-umin;
566   vr = vmax-vmin;
567   for (i=0; i<numu; i++)
568   {
569     if ((appearance == APPEARANCE_BANDS ||
570          appearance == APPEARANCE_SPIRALS) && ((i & 3) >= 2))
571       continue;
572     if (display_mode == DISP_WIREFRAME)
573       glBegin(GL_QUAD_STRIP);
574     else
575       glBegin(GL_TRIANGLE_STRIP);
576     for (j=0; j<=numv; j++)
577     {
578       for (k=0; k<=1; k++)
579       {
580         l = (i+k);
581         m = j;
582         u = ur*l/numu+umin;
583         v = vr*m/numv+vmin;
584         if (appearance == APPEARANCE_SPIRALS)
585         {
586           u += 4.0*skew/numv*v;
587           b = ((i/4)&(skew-1))*(numu/(4*skew));
588           color(ur*4*b/numu+umin);
589         }
590         else
591         {
592           color(u);
593         }
594         cu = cos(u);
595         su = sin(u);
596         cv = cos(v);
597         sv = sin(v);
598         xx[0] = cu;
599         xx[1] = su;
600         xx[2] = cv;
601         xx[3] = sv;
602         xxu[0] = -su;
603         xxu[1] = cu;
604         xxu[2] = 0.0;
605         xxu[3] = 0.0;
606         xxv[0] = 0.0;
607         xxv[1] = 0.0;
608         xxv[2] = -sv;
609         xxv[3] = cv;
610         for (l=0; l<4; l++)
611         {
612           r = 0.0;
613           s = 0.0;
614           t = 0.0;
615           for (m=0; m<4; m++)
616           {
617             r += mat[l][m]*xx[m];
618             s += mat[l][m]*xxu[m];
619             t += mat[l][m]*xxv[m];
620           }
621           x[l] = r;
622           xu[l] = s;
623           xv[l] = t;
624         }
625         if (projection_4d == DISP_4D_ORTHOGRAPHIC)
626         {
627           for (l=0; l<3; l++)
628           {
629             p[l] = (x[l]+offset4d[l])/1.5+offset3d[l];
630             pu[l] = xu[l];
631             pv[l] = xv[l];
632           }
633         }
634         else
635         {
636           s = x[3]+offset4d[3];
637           t = s*s;
638           for (l=0; l<3; l++)
639           {
640             r = x[l]+offset4d[l];
641             p[l] = r/s+offset3d[l];
642             pu[l] = (xu[l]*s-r*xu[3])/t;
643             pv[l] = (xv[l]*s-r*xv[3])/t;
644           }
645         }
646         n[0] = pu[1]*pv[2]-pu[2]*pv[1];
647         n[1] = pu[2]*pv[0]-pu[0]*pv[2];
648         n[2] = pu[0]*pv[1]-pu[1]*pv[0];
649         t = sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);
650         n[0] /= t;
651         n[1] /= t;
652         n[2] /= t;
653         glNormal3fv(n);
654         glVertex3fv(p);
655         polys++;
656       }
657     }
658     glEnd();
659   }
660   polys /= 2;
661   return polys;
662 }
663
664
665 static void init(ModeInfo *mi)
666 {
667   static const GLfloat light_ambient[]  = { 0.0, 0.0, 0.0, 1.0 };
668   static const GLfloat light_diffuse[]  = { 1.0, 1.0, 1.0, 1.0 };
669   static const GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
670   static const GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
671   static const GLfloat mat_specular[]   = { 1.0, 1.0, 1.0, 1.0 };
672   hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
673
674   if (appearance >= APPEARANCE_SPIRALS_1)
675   {
676     num_spirals = 1<<(appearance-APPEARANCE_SPIRALS_1);
677     appearance = APPEARANCE_SPIRALS;
678   }
679   else
680   {
681     num_spirals = 0;
682   }
683
684   hp->alpha = 0.0;
685   hp->beta = 0.0;
686   hp->delta = 0.0;
687   hp->zeta = 0.0;
688   hp->eta = 0.0;
689   hp->theta = 0.0;
690
691   glMatrixMode(GL_PROJECTION);
692   glLoadIdentity();
693   if (projection_3d == DISP_3D_PERSPECTIVE)
694     gluPerspective(60.0,1.0,0.1,100.0);
695   else
696     glOrtho(-1.0,1.0,-1.0,1.0,0.1,100.0);;
697   glMatrixMode(GL_MODELVIEW);
698   glLoadIdentity();
699
700   if (display_mode == DISP_WIREFRAME)
701   {
702     glDisable(GL_DEPTH_TEST);
703     glShadeModel(GL_FLAT);
704     glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
705     glDisable(GL_LIGHTING);
706     glDisable(GL_LIGHT0);
707     glDisable(GL_BLEND);
708   }
709   else if (display_mode == DISP_SURFACE)
710   {
711     glEnable(GL_DEPTH_TEST);
712     glDepthFunc(GL_LESS);
713     glShadeModel(GL_SMOOTH);
714     glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
715     glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
716     glEnable(GL_LIGHTING);
717     glEnable(GL_LIGHT0);
718     glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
719     glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
720     glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
721     glLightfv(GL_LIGHT0,GL_POSITION,light_position);
722     glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,mat_specular);
723     glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,50.0);
724     glDepthMask(GL_TRUE);
725     glDisable(GL_BLEND);
726   }
727   else if (display_mode == DISP_TRANSPARENT)
728   {
729     glDisable(GL_DEPTH_TEST);
730     glShadeModel(GL_SMOOTH);
731     glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
732     glLightModeli(GL_LIGHT_MODEL_TWO_SIDE,GL_TRUE);
733     glEnable(GL_LIGHTING);
734     glEnable(GL_LIGHT0);
735     glLightfv(GL_LIGHT0,GL_AMBIENT,light_ambient);
736     glLightfv(GL_LIGHT0,GL_DIFFUSE,light_diffuse);
737     glLightfv(GL_LIGHT0,GL_SPECULAR,light_specular);
738     glLightfv(GL_LIGHT0,GL_POSITION,light_position);
739     glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,mat_specular);
740     glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,50.0);
741     glDepthMask(GL_FALSE);
742     glEnable(GL_BLEND);
743     glBlendFunc(GL_SRC_ALPHA,GL_ONE);
744   }
745   else
746   {
747     glDisable(GL_DEPTH_TEST);
748     glShadeModel(GL_FLAT);
749     glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
750     glDisable(GL_LIGHTING);
751     glDisable(GL_LIGHT0);
752     glDisable(GL_BLEND);
753   }
754 }
755
756
757 /* Redisplay the hypertorus. */
758 static void display_hypertorus(ModeInfo *mi)
759 {
760   hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
761
762   if (!hp->button_pressed)
763   {
764     hp->alpha += speed_wx * hp->speed_scale;
765     if (hp->alpha >= 360.0)
766       hp->alpha -= 360.0;
767     hp->beta += speed_wy * hp->speed_scale;
768     if (hp->beta >= 360.0)
769       hp->beta -= 360.0;
770     hp->delta += speed_wz * hp->speed_scale;
771     if (hp->delta >= 360.0)
772       hp->delta -= 360.0;
773     hp->zeta += speed_xy * hp->speed_scale;
774     if (hp->zeta >= 360.0)
775       hp->zeta -= 360.0;
776     hp->eta += speed_xz * hp->speed_scale;
777     if (hp->eta >= 360.0)
778       hp->eta -= 360.0;
779     hp->theta += speed_yz * hp->speed_scale;
780     if (hp->theta >= 360.0)
781       hp->theta -= 360.0;
782   }
783
784   glMatrixMode(GL_PROJECTION);
785   glLoadIdentity();
786   if (projection_3d == DISP_3D_ORTHOGRAPHIC)
787   {
788     if (hp->aspect >= 1.0)
789       glOrtho(-hp->aspect,hp->aspect,-1.0,1.0,0.1,100.0);
790     else
791       glOrtho(-1.0,1.0,-1.0/hp->aspect,1.0/hp->aspect,0.1,100.0);
792   }
793   else
794   {
795     gluPerspective(60.0,hp->aspect,0.1,100.0);
796   }
797   glMatrixMode(GL_MODELVIEW);
798   glLoadIdentity();
799
800   mi->polygon_count = hypertorus(mi,0.0,2.0*M_PI,0.0,2.0*M_PI,64,64);
801 }
802
803
804 ENTRYPOINT void reshape_hypertorus(ModeInfo *mi, int width, int height)
805 {
806   hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
807
808   hp->WindW = (GLint)width;
809   hp->WindH = (GLint)height;
810   glViewport(0,0,width,height);
811   hp->aspect = (GLfloat)width/(GLfloat)height;
812 }
813
814
815 ENTRYPOINT Bool hypertorus_handle_event(ModeInfo *mi, XEvent *event)
816 {
817   Display *display = MI_DISPLAY(mi);
818   hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
819   KeySym  sym;
820
821   if (event->xany.type == ButtonPress &&
822       event->xbutton.button == Button1)
823   {
824     hp->button_pressed = True;
825     gltrackball_start(hp->trackballs[hp->current_trackball],
826                       event->xbutton.x, event->xbutton.y,
827                       MI_WIDTH(mi), MI_HEIGHT(mi));
828     return True;
829   }
830   else if (event->xany.type == ButtonRelease &&
831            event->xbutton.button == Button1)
832   {
833     hp->button_pressed = False;
834     return True;
835   }
836   else if (event->xany.type == KeyPress)
837   {
838     sym = XKeycodeToKeysym(display,event->xkey.keycode,0);
839     if (sym == XK_Shift_L || sym == XK_Shift_R)
840     {
841       hp->current_trackball = 1;
842       if (hp->button_pressed)
843         gltrackball_start(hp->trackballs[hp->current_trackball],
844                           event->xbutton.x, event->xbutton.y,
845                           MI_WIDTH(mi), MI_HEIGHT(mi));
846       return True;
847     }
848   }
849   else if (event->xany.type == KeyRelease)
850   {
851     sym = XKeycodeToKeysym(display,event->xkey.keycode,0);
852     if (sym == XK_Shift_L || sym == XK_Shift_R)
853     {
854       hp->current_trackball = 0;
855       if (hp->button_pressed)
856         gltrackball_start(hp->trackballs[hp->current_trackball],
857                           event->xbutton.x, event->xbutton.y,
858                           MI_WIDTH(mi), MI_HEIGHT(mi));
859       return True;
860     }
861   }
862   else if (event->xany.type == MotionNotify && hp->button_pressed)
863   {
864     gltrackball_track(hp->trackballs[hp->current_trackball],
865                       event->xmotion.x, event->xmotion.y,
866                       MI_WIDTH(mi), MI_HEIGHT(mi));
867     return True;
868   }
869
870   return False;
871 }
872
873
874 /*
875  *-----------------------------------------------------------------------------
876  *-----------------------------------------------------------------------------
877  *    Xlock hooks.
878  *-----------------------------------------------------------------------------
879  *-----------------------------------------------------------------------------
880  */
881
882 /*
883  *-----------------------------------------------------------------------------
884  *    Initialize hypertorus.  Called each time the window changes.
885  *-----------------------------------------------------------------------------
886  */
887
888 ENTRYPOINT void init_hypertorus(ModeInfo *mi)
889 {
890   hypertorusstruct *hp;
891
892   if (hyper == NULL)
893   {
894     hyper = (hypertorusstruct *)calloc(MI_NUM_SCREENS(mi),
895                                        sizeof(hypertorusstruct));
896     if (hyper == NULL)
897       return;
898   }
899   hp = &hyper[MI_SCREEN(mi)];
900
901   
902   hp->trackballs[0] = gltrackball_init();
903   hp->trackballs[1] = gltrackball_init();
904   hp->current_trackball = 0;
905   hp->button_pressed = False;
906
907   /* make multiple screens rotate at slightly different rates. */
908   hp->speed_scale = 0.9 + frand(0.3);
909
910   if ((hp->glx_context = init_GL(mi)) != NULL)
911   {
912     reshape_hypertorus(mi,MI_WIDTH(mi),MI_HEIGHT(mi));
913     glDrawBuffer(GL_BACK);
914     init(mi);
915   }
916   else
917   {
918     MI_CLEARWINDOW(mi);
919   }
920 }
921
922 /*
923  *-----------------------------------------------------------------------------
924  *    Called by the mainline code periodically to update the display.
925  *-----------------------------------------------------------------------------
926  */
927 ENTRYPOINT void draw_hypertorus(ModeInfo *mi)
928 {
929   Display          *display = MI_DISPLAY(mi);
930   Window           window = MI_WINDOW(mi);
931   hypertorusstruct *hp;
932
933   if (hyper == NULL)
934     return;
935   hp = &hyper[MI_SCREEN(mi)];
936
937   MI_IS_DRAWN(mi) = True;
938   if (!hp->glx_context)
939     return;
940
941   glXMakeCurrent(display,window,*(hp->glx_context));
942
943   glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
944   glLoadIdentity();
945
946   display_hypertorus(mi);
947
948   if (MI_IS_FPS(mi))
949     do_fps (mi);
950
951   glFlush();
952
953   glXSwapBuffers(display,window);
954 }
955
956
957 /*
958  *-----------------------------------------------------------------------------
959  *    The display is being taken away from us.  Free up malloc'ed 
960  *      memory and X resources that we've alloc'ed.  Only called
961  *      once, we must zap everything for every screen.
962  *-----------------------------------------------------------------------------
963  */
964
965 ENTRYPOINT void release_hypertorus(ModeInfo *mi)
966 {
967   if (hyper != NULL)
968   {
969     int screen;
970
971     for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
972     {
973       hypertorusstruct *hp = &hyper[screen];
974
975       if (hp->glx_context)
976         hp->glx_context = (GLXContext *)NULL;
977     }
978     (void) free((void *)hyper);
979     hyper = (hypertorusstruct *)NULL;
980   }
981   FreeAllGL(mi);
982 }
983
984 #ifndef STANDALONE
985 ENTRYPOINT void change_hypertorus(ModeInfo *mi)
986 {
987   hypertorusstruct *hp = &hyper[MI_SCREEN(mi)];
988
989   if (!hp->glx_context)
990     return;
991
992   glXMakeCurrent(MI_DISPLAY(mi),MI_WINDOW(mi),*(hp->glx_context));
993   init(mi);
994 }
995 #endif /* !STANDALONE */
996
997 XSCREENSAVER_MODULE ("Hypertorus", hypertorus)
998
999 #endif /* USE_GL */