a0f6b76b25afe24ea608ef16a023b50f70f79537
[xscreensaver] / hacks / glx / jigglypuff.c
1 /* jigglypuff - a most, most, unfortunate screensaver.
2  *
3  * Copyright (c) 2003 Keith Macleod (kmacleod@primus.ca)
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or 
11  * implied warranty.
12  *
13  * Draws all varieties of obscene, spastic, puffy balls
14  * orbiting lazily about the screen. More of an accident
15  * than anything else.
16  *
17  * Apologies to anyone who thought they were getting a Pokemon
18  * out of this.
19  *
20  * Of course, if you modify it to do something interesting and/or
21  * funny, I'd appreciate receiving a copy.
22  *
23  * 04/06/2003 - Oops, figured out what was wrong with the sphere
24  *              mapping. I had assumed it was done in model space,
25  *              but of course I was totally wrong... Eye space you
26  *              say? Yup. km
27  *
28  * 03/31/2003 - Added chrome to the color options. The mapping
29  *              is anything but 'correct', but it's a pretty good
30  *              effect anyways, as long as the surface is jiggling
31  *              enough that you can't tell. Sure, it seems  kind of odd
32  *              that it's reflecting a sky that's obviously not there,
33  *              but who has time to worry about silly details like
34  *              that? Not me, ah rekkin'. km
35  *
36  */
37
38 #ifdef STANDALONE
39 # define DEFAULTS           "*delay: 20000\n" \
40                             "*showFPS: False\n" \
41                             "*wireframe: False\n" \
42
43 # define refresh_jigglypuff 0
44 # define release_jigglypuff 0
45 # include "xlockmore.h"
46 #else
47 # include "xlock.h"
48 #endif
49
50 #ifdef HAVE_CONFIG_H
51 # include "config.h"
52 #endif
53
54 #include "xpm-ximage.h"
55 #include "gltrackball.h"
56 #include "../images/jigglymap.xpm"
57
58 #ifdef USE_GL
59
60
61 #define DEF_COLOR           "cycle"
62 #define DEF_SHININESS       "100"
63 #define DEF_COMPLEXITY      "2"
64 #define DEF_SPEED           "500"
65 #define DEF_DISTANCE        "100"
66 #define DEF_HOLD            "800"
67 #define DEF_SPHERISM        "75"
68 #define DEF_DAMPING         "500"
69 #define DEF_RANDOM          "True"
70 #define DEF_TETRA           "False"
71 #define DEF_SPOOKY          "0"
72
73 #ifndef max
74 #define max(a,b) (((a)>(b))?(a):(b))
75 #define min(a,b) (((a)<(b))?(a):(b))
76 #endif
77
78 /* Why isn't RAND_MAX correct in the first place? */
79 #define REAL_RAND_MAX (2.0*(float)RAND_MAX)
80
81 static int spherism;
82 static int hold;
83 static int distance;
84 static int damping;
85
86 static int complexity;
87 static int speed;
88
89 static int do_tetrahedron;
90 static int spooky;
91 static char *color;
92 static int shininess;
93
94 static int random_parms;
95
96 typedef struct solid solid;
97
98 typedef struct {
99     float stable_distance;
100     float hold_strength;
101     float spherify_strength;
102     float damping_velocity;
103     float damping_factor;
104
105     int do_wireframe;
106     int spooky;
107     int color_style;
108     GLint shininess;
109     GLfloat jiggly_color[4];
110     GLfloat color_dir[3];
111
112     solid *shape;
113
114     trackball_state *trackball;
115     int button_down;
116
117     float angle;
118     float axis;
119     float speed;
120
121     GLXContext *glx_context;
122 } jigglystruct;
123
124 static jigglystruct *jss = NULL;
125
126 static XrmOptionDescRec opts[] = {
127     {"-random", ".Jigglypuff.random", XrmoptionNoArg, "true"},
128     {"+random", ".Jigglypuff.random", XrmoptionNoArg, "false"},
129     {"-tetra", ".Jigglypuff.tetra", XrmoptionNoArg, "true"},
130     {"+tetra", ".Jigglypuff.tetra", XrmoptionNoArg, "false"},
131     {"-spooky", ".Jigglypuff.spooky", XrmoptionSepArg, "0"},
132     {"-color", ".Jigglypuff.color", XrmoptionSepArg, DEF_COLOR},
133     {"-shininess", ".Jigglypuff.shininess", XrmoptionSepArg, DEF_SHININESS},
134     {"-complexity", ".Jigglypuff.complexity", XrmoptionSepArg, DEF_COMPLEXITY},
135     {"-speed", ".Jigglypuff.speed", XrmoptionSepArg, DEF_SPEED},
136     {"-spherism", ".Jigglypuff.spherism", XrmoptionSepArg, DEF_SPHERISM},
137     {"-hold", ".Jigglypuff.hold", XrmoptionSepArg, DEF_HOLD},
138     {"-distance", "Jigglypuff.distance", XrmoptionSepArg, DEF_DISTANCE},
139     {"-damping", "Jigglypuff.damping", XrmoptionSepArg, DEF_DAMPING}
140 };
141
142 static argtype vars[] = {
143     {&random_parms, "random", "Random", DEF_RANDOM, t_Bool},
144     {&do_tetrahedron, "tetra", "Tetra", DEF_TETRA, t_Bool},
145     {&spooky, "spooky", "Spooky", DEF_SPOOKY, t_Int},
146     {&color, "color", "Color", DEF_COLOR, t_String},
147     {&shininess, "shininess", "Shininess", DEF_SHININESS, t_Int},
148     {&complexity, "complexity", "Complexity", DEF_COMPLEXITY, t_Int},
149     {&speed, "speed", "Speed", DEF_SPEED, t_Int},
150     {&spherism, "spherism", "Spherism", DEF_SPHERISM, t_Int},
151     {&hold, "hold", "Hold", DEF_HOLD, t_Int},
152     {&distance, "distance", "Distance", DEF_DISTANCE, t_Int},
153     {&damping, "damping", "Damping", DEF_DAMPING, t_Int}
154 };
155
156 #undef countof
157 #define countof(x) ((int)(sizeof(x)/sizeof(*(x))))
158
159 ENTRYPOINT ModeSpecOpt jigglypuff_opts = {countof(opts), opts, countof(vars), vars, NULL};
160
161 #define COLOR_STYLE_NORMAL    0
162 #define COLOR_STYLE_CYCLE     1
163 #define COLOR_STYLE_CLOWNBARF 2
164 #define COLOR_STYLE_FLOWERBOX 3
165 #define COLOR_STYLE_CHROME    4
166
167 #define CLOWNBARF_NCOLORS 5
168
169 static const GLfloat clownbarf_colors[CLOWNBARF_NCOLORS][4] = {
170     {0.7, 0.7, 0.0, 1.0},
171     {0.8, 0.1, 0.1, 1.0},
172     {0.1, 0.1, 0.8, 1.0},
173     {0.9, 0.9, 0.9, 1.0},
174     {0.0, 0.0, 0.0, 1.0}
175 };
176
177 static const GLfloat flowerbox_colors[4][4] = {
178     {0.7, 0.7, 0.0, 1.0},
179     {0.9, 0.0, 0.0, 1.0},
180     {0.0, 0.9, 0.0, 1.0},
181     {0.0, 0.0, 0.9, 1.0},
182 };
183
184 # if 0 /* I am not even going to *try* and make this bullshit compile
185           without warning under gcc -std=c89 -pedantic.  -jwz. */
186 #ifdef DEBUG
187 # ifdef __GNUC__ /* GCC style */
188 #define _DEBUG(msg, args...) do { \
189     fprintf(stderr, "%s : %d : " msg ,__FILE__,__LINE__ ,##args); \
190 } while(0)
191 # else /* C99 standard style */
192 #define _DEBUG(msg, ...) do { \
193     fprintf(stderr, "%s : %d : " msg ,__FILE__,__LINE__,__VA_ARGS__); \
194 } while(0)
195 # endif
196 #else
197 # ifdef __GNUC__ /* GCC style */
198 #define _DEBUG(msg, args...)
199 # else /* C99 standard style */
200 #define _DEBUG(msg, ...)
201 # endif
202 #endif
203 #endif /* 0 */
204
205 /* This is all the half-edge b-rep code (as well as basic geometry) */
206 typedef struct face face;
207 typedef struct edge edge;
208 typedef struct hedge hedge;
209 typedef struct vertex vertex;
210 typedef GLfloat coord;
211 typedef coord vector[3];
212
213 struct solid {
214     face *faces;
215     edge *edges;
216     vertex *vertices;
217 };
218
219 struct face {
220     solid *s;
221     hedge *start;
222     const GLfloat *color;
223     face *next;
224 };
225
226 struct edge {
227     solid *s;
228     hedge *left;
229     hedge *right;
230     edge *next;
231 };
232
233 struct hedge {
234     face *f;
235     edge *e;
236     vertex *vtx;
237     hedge *next;
238     hedge *prev;
239 };
240
241 struct vertex {
242     solid *s;
243     hedge *h;
244     vector v;
245     vector n;
246     vector f;
247     vector vel;
248     vertex *next;
249 };
250
251 static inline void vector_init(vector v, coord x, coord y, coord z)
252 {
253     v[0] = x;
254     v[1] = y;
255     v[2] = z;
256 }    
257
258 static inline void vector_copy(vector d, vector s)
259 {
260     d[0] = s[0];
261     d[1] = s[1];
262     d[2] = s[2];
263 }
264
265 static inline void vector_add(vector v1, vector v2, vector v)
266 {
267     vector_init(v, v1[0]+v2[0], v1[1]+v2[1], v1[2]+v2[2]);
268 }
269
270 static inline void vector_add_to(vector v1, vector v2)
271 {
272     v1[0] += v2[0];
273     v1[1] += v2[1];
274     v1[2] += v2[2];
275 }
276
277 static inline void vector_sub(vector v1, vector v2, vector v)
278 {
279     vector_init(v, v1[0]-v2[0], v1[1]-v2[1], v1[2]-v2[2]);
280 }
281
282 static inline void vector_scale(vector v, coord s)
283 {
284     v[0] *= s;
285     v[1] *= s;
286     v[2] *= s;
287 }
288
289 static inline coord dot(vector v1, vector v2)
290 {
291     return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
292 }
293
294 static inline void cross(vector v1, vector v2, vector v)
295 {
296     vector_init(v,
297                 v1[1]*v2[2] - v2[1]*v1[2],
298                 v1[2]*v2[0] - v2[2]*v1[0],
299                 v1[0]*v2[1] - v2[0]*v1[1]);
300 }
301
302 static inline coord magnitude2(vector v)
303 {
304     return v[0]*v[0] + v[1]*v[1] + v[2]*v[2];
305 }
306
307 static inline coord magnitude(vector v)
308 {
309     return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
310 }
311
312 static inline void normalize(vector v)
313 {
314     coord mag = 1.0/sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
315
316     v[0] *= mag;
317     v[1] *= mag;
318     v[2] *= mag;
319 }
320
321 static inline void normalize_to(vector v, coord m)
322 {
323     coord mag = 1.0/sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2])/m;
324
325     v[0] *= mag;
326     v[1] *= mag;
327     v[2] *= mag;
328 }
329
330 static inline void midpoint(vector v1, vector v2, vector v)
331 {
332     vector_init(v,
333                 v1[0] + 0.5 * (v2[0] - v1[0]),
334                 v1[1] + 0.5 * (v2[1] - v1[1]),
335                 v1[2] + 0.5 * (v2[2] - v1[2]));
336 }
337
338 static inline hedge *partner(hedge *h) {
339     if(!h->e)
340         return NULL;
341     if(h == h->e->left) {
342         return h->e->right;
343     }
344     else if(h == h->e->right) {
345         return h->e->left;
346     }
347     else {
348 /*      _DEBUG("Inconsistent edge detected. Presumably, this is a bug. Exiting.\n", NULL); */
349         exit(-1);
350     }
351 }
352
353 static vertex *vertex_new(solid *s, vector v)
354 {
355     vertex *vtx = (vertex*)malloc(sizeof(vertex));
356
357     if(!vtx)
358         return NULL;
359     vtx->s = s;
360     vtx->next = s->vertices;
361     s->vertices = vtx;
362     vector_copy(vtx->v, v);
363     vector_init(vtx->f, 0, 0, 0);
364     vector_init(vtx->vel, 0, 0, 0);
365     return vtx;
366 }
367
368 /* insert a new halfedge after hafter. this is low-level,
369  * i.e. it is a helper for the split_* functions, which
370  * maintain the consistency of the solid.
371  */
372 static hedge *hedge_new(hedge *hafter, vertex *vtx)
373 {
374     hedge *h = (hedge*)malloc(sizeof(hedge));
375     
376     if(!h) {
377 /*      _DEBUG("Out of memory in hedge_new()\n",NULL); */
378         return NULL;
379     }
380     h->f = hafter->f;
381     h->vtx = vtx;
382     h->e = NULL;
383     h->prev = hafter;
384     h->next = hafter->next;
385     hafter->next = h;
386     h->next->prev = h;
387     return h;
388 }
389
390 static edge *edge_new(solid *s)
391 {
392     edge *e = (edge*)malloc(sizeof(edge));
393     if(!e) {
394 /*      _DEBUG("Out of memory in edge_new()\n",NULL);*/
395         exit(-1);
396     }
397     e->next = s->edges;
398     s->edges = e;
399     e-> s = s;
400     e->left = e->right = NULL;
401     return e;
402 }
403
404 static face *face_new(solid *s, hedge *h)
405 {
406     face *f = (face*)malloc(sizeof(face));
407     if(!f) {
408 /*      _DEBUG("Out of memory in face_new()",NULL);*/
409         exit(-1);
410     }
411     f->s = s;
412     f->start = h;
413     f->next = s->faces;
414     s->faces = f;
415     return f;
416 }
417
418 /* split vertex vtx, creating a new edge after v on f
419  * that goes to a new vertex at v, adjoining whatever
420  * face is on the other side of the halfedge attached to
421  * v on f. 
422  * Assumptions:
423  *    there are at least 2 faces.
424  *    partner(h)->next->vtx == vtx
425  * Post-assumptions:
426  *    the new halfedge will be inserted AFTER the indicated
427  *    halfedge. This means that f->start is guaranteed not to
428  *    change.
429  *    the vertex returned will have h==<the new halfedge>.
430  */
431
432 static vertex *vertex_split(hedge *h, vector v)
433 {
434     hedge *h2, *hn1, *hn2;
435     vertex *vtxn;
436     edge *en;
437     face *f1;
438
439     f1 = h->f;
440     h2 = partner(h);
441     
442     vtxn = vertex_new(f1->s, v);
443     hn1 = hedge_new(h, vtxn);
444     vtxn->h = hn1;
445     hn2 = hedge_new(h2, vtxn);
446     hn2->e = h->e;
447
448     if(h2->e->left == h2)
449         h2->e->left = hn2;
450     else
451         h2->e->right = hn2;
452
453     en = edge_new(f1->s);
454     en->left = hn1;
455     en->right = h2;
456     hn1->e = en;
457     h2->e = en;
458     return vtxn;
459 }
460
461 static face *face_split(face *f, hedge *h1, hedge *h2)
462 {
463     hedge *hn1, *hn2, *tmp;
464     edge *en;
465     face *fn;
466
467     if(h1->f != f || h2->f != f) {
468 /*      _DEBUG("Whoah, cap'n, yer usin' a bad halfedge!\n",NULL);*/
469         exit(-1);
470     }
471     if(h1 == h2) {
472 /*      _DEBUG("Trying to split a face at a single vertex\n",NULL);*/
473         exit(-1);
474     }
475     /* close the loops */
476     h1->prev->next = h2;
477     h2->prev->next = h1;
478     tmp = h1->prev;
479     h1->prev = h2->prev;
480     h2->prev = tmp;
481     /* insert halfedges & create edge */
482     hn1 = hedge_new(h2->prev, h1->vtx);
483     hn2 = hedge_new(h1->prev, h2->vtx);
484     en = edge_new(f->s);
485     en->left = hn1;
486     en->right = hn2;
487     hn1->e = en;
488     hn2->e = en;
489
490     /* make the new face, first find out which hedge is contained
491     * in the original face, then start the new face at the other */
492     tmp = f->start;
493     while(tmp != h1 && tmp != h2)
494         tmp = tmp->next;
495     tmp = (tmp == h1) ? h2 : h1 ;
496     fn = face_new(f->s, tmp);
497     do {
498         tmp->f = fn;
499         tmp = tmp->next;
500     } while(tmp != fn->start);
501     fn->color = f->color;
502     return fn;
503 }
504
505 static solid *solid_new(vector where) 
506 {
507     solid *s = (solid*)malloc(sizeof(solid));
508     face *f1, *f2;
509     edge *e;
510     vertex *vtx;
511     hedge *h1,*h2;
512
513     s->faces = NULL;
514     s->edges = NULL;
515     s->vertices = NULL;
516
517     h1 = (hedge*)malloc(sizeof(hedge));
518     h2 = (hedge*)malloc(sizeof(hedge));
519     h1->next = h1->prev = h1;
520     h2->next = h2->prev = h2;
521
522     vtx = vertex_new(s, where);
523     vtx->h = h1;
524     h1->vtx = vtx;
525     h2->vtx = vtx;
526
527     e = edge_new(s);
528     e->left = h1;
529     e->right = h2;
530     h1->e = e;
531     h2->e = e;
532
533     f1 = face_new(s, h1);
534     f2 = face_new(s, h2);
535     h1->f = f1;
536     h2->f = f2;
537
538     return s;
539 }
540
541 /* This is all the code directly related to constructing the jigglypuff */
542 static void face_tessel2(face *f)
543 {
544     hedge *h1=f->start->prev, *h2=f->start->next;
545     
546     if(h1->next == h1)
547         return;
548     while(h2 != h1 && h2->next != h1) {
549         f = face_split(f, h1, h2);
550         h1 = f->start;
551         h2 = f->start->next->next;
552     }
553 }
554
555 /* This will only work with solids composed entirely of
556  * triangular faces. It first add a vertex to the middle
557  * of each edge, then walks the faces, connecting the
558  * dots.
559  * I'm abusing the fact that new faces and edges are always
560  * added at the head of the list. If that ever changes,
561  * this is borked. 
562  */
563 static void solid_tesselate(solid *s) 
564 {
565     edge *e = s->edges;
566     face *f = s->faces;
567     
568     while(e) {
569         vector v;
570         midpoint(e->left->vtx->v, e->right->vtx->v, v);
571         vertex_split(e->left, v);
572         e = e->next;
573     }
574     while(f) {
575         face_tessel2(f);
576         f=f->next;
577     }
578 }
579                 
580 static void solid_spherify(solid * s, coord size) 
581 {
582     vertex *vtx = s->vertices;
583
584     while(vtx) {
585         normalize_to(vtx->v, size);
586         vtx = vtx->next;
587     }
588 }
589
590 static solid *tetrahedron(jigglystruct *js) 
591 {
592     solid *s;
593     vertex *vtx;
594     vector v;
595     hedge *h;
596     face *f;
597     int i;
598
599     vector_init(v, 1, 1, 1);
600     s = solid_new(v);
601     vector_init(v, -1, -1, 1);
602     h = s->faces->start;
603     vtx = vertex_split(h, v);
604     vector_init(v, -1, 1, -1);
605     vtx = vertex_split(vtx->h, v);
606     h = vtx->h;
607     f = face_split(s->faces, h, h->prev);
608     vector_init(v, 1, -1, -1);
609     vertex_split(f->start, v);
610     f = s->faces->next->next;
611     h = f->start;
612     face_split(f, h, h->next->next);
613
614     if(js->color_style == COLOR_STYLE_FLOWERBOX) {
615         f = s->faces;
616         for(i=0; i<4; i++) {
617             f->color = flowerbox_colors[i];
618             f = f->next;
619         }
620     }       
621
622     return s;
623 }
624
625 static solid *tesselated_tetrahedron(coord size, int iter, jigglystruct *js) {
626     solid *s = tetrahedron(js);
627     int i;
628
629     for(i=0; i<iter; i++) {
630         solid_tesselate(s);
631     }
632     return s;
633 }
634
635 static void clownbarf_colorize(solid *s) {
636     face *f = s->faces;
637     while(f) {
638         f->color = clownbarf_colors[random() % CLOWNBARF_NCOLORS];
639         f = f->next;
640     }
641 }
642
643 /* Here be the rendering code */
644
645 static inline void vertex_calcnormal(vertex *vtx, jigglystruct *js)
646 {
647     hedge *start = vtx->h, *h=start;
648     
649     vector_init(vtx->n, 0, 0, 0);
650     do {
651         vector u, v, norm;
652         vector_sub(h->prev->vtx->v, vtx->v, u);
653         vector_sub(h->next->vtx->v, vtx->v, v);
654         cross(u, v, norm);
655         vector_add_to(vtx->n, norm);
656         h = partner(h)->next;
657     } while(h != start);
658     if(!js->spooky)
659         normalize(vtx->n);
660     else
661         vector_scale(vtx->n, js->spooky);
662 }
663
664 static inline void vertex_render(vertex *vtx, jigglystruct *js)
665 {
666     glNormal3fv(vtx->n);
667     glVertex3fv(vtx->v);
668 }
669
670 /* This can be optimized somewhat due to the fact that all
671  * the faces are triangles. I haven't actually tested to
672  * see what the cost is of calling glBegin/glEnd for each
673  * triangle.
674  */
675 static inline int face_render(face *f, jigglystruct *js)
676 {
677     hedge *h1, *h2, *hend;
678     int polys = 0;
679
680     h1 = f->start;
681     hend = h1->prev;
682     h2 = h1->next;
683     
684     if(js->color_style == COLOR_STYLE_FLOWERBOX ||
685         js->color_style == COLOR_STYLE_CLOWNBARF)
686         glColor4fv(f->color);
687     glBegin(GL_TRIANGLES);
688     while(h1 != hend && h2 !=hend) {
689         vertex_render(h1->vtx, js);
690         vertex_render(h2->vtx, js);
691         vertex_render(hend->vtx, js);
692         h1 = h2;
693         h2 = h1->next;
694         polys++;
695     }
696     glEnd();
697     return polys;
698 }
699
700 static int jigglypuff_render(jigglystruct *js) 
701 {
702     int polys = 0;
703     face *f = js->shape->faces;
704     vertex *vtx = js->shape->vertices;
705
706     while(vtx) {
707         vertex_calcnormal(vtx, js);
708         vtx = vtx->next;
709     }
710     while(f) {
711         polys += face_render(f, js);
712         f=f->next;
713     }
714     return polys;
715 }
716
717 /* This is the jiggling code */
718
719 /* stable distance when subdivs == 4 */
720 #define STABLE_DISTANCE 0.088388347648
721
722 static void update_shape(jigglystruct *js)
723 {
724     vertex *vtx = js->shape->vertices;
725     edge *e = js->shape->edges;
726     vector zero;
727
728     vector_init(zero, 0, 0, 0);
729
730     /* sum all the vertex-vertex forces */
731     while(e) {
732         vector f;
733         coord mag;
734         vector_sub(e->left->vtx->v, e->right->vtx->v, f);
735         mag = js->stable_distance - magnitude(f);
736         vector_scale(f, mag);
737         vector_add_to(e->left->vtx->f, f);
738         vector_sub(zero, f, f);
739         vector_add_to(e->right->vtx->f, f);
740         e = e->next;
741     }
742
743     /* scale back the v-v force and add the spherical force
744      * then add the result to the vertex velocity, damping
745      * if necessary. Finally, move the vertex */
746     while(vtx) {
747         coord mag;
748         vector to_sphere;
749         vector_scale(vtx->f, js->hold_strength);
750         vector_copy(to_sphere, vtx->v);
751         mag = 1 - magnitude(to_sphere);
752         vector_scale(to_sphere, mag * js->spherify_strength);
753         vector_add_to(vtx->f, to_sphere);
754         vector_add_to(vtx->vel, vtx->f);
755         vector_init(vtx->f, 0, 0, 0);
756         mag = magnitude2(vtx->vel);
757         if(mag > js->damping_velocity)
758             vector_scale(vtx->vel, js->damping_factor);
759         vector_add_to(vtx->v, vtx->vel);
760         vtx = vtx->next;
761     }
762 }
763
764 /* These are the various initialization routines */
765
766 static void init_texture(ModeInfo *mi)
767 {
768     XImage *img = xpm_to_ximage(mi->dpy, mi->xgwa.visual,
769                                mi->xgwa.colormap, jigglymap_xpm);
770
771     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
772                  img->width, img->height, 0, GL_RGBA,
773                  GL_UNSIGNED_BYTE, img->data);
774
775     XDestroyImage(img);
776 }
777
778 static void setup_opengl(ModeInfo *mi, jigglystruct *js)
779 {
780     const GLfloat lpos0[4] = {-12, 8, 12, 0};
781     const GLfloat lpos1[4] = {7, -5, 0, 0};
782     const GLfloat lcol0[4] = {0.7f, 0.7f, 0.65f, 1};
783     const GLfloat lcol1[4] = {0.3f, 0.2f, 0.1f, 1};
784     const GLfloat scolor[4]= {0.9f, 0.9f, 0.9f, 0.5f};
785
786     glDrawBuffer(GL_BACK);
787     glShadeModel(GL_SMOOTH);
788     glEnable(GL_DEPTH_TEST);
789
790     if(js->do_wireframe) {
791         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
792     }
793     else {
794         glCullFace(GL_BACK);
795         glFrontFace(GL_CW);
796         glEnable(GL_CULL_FACE);
797     }
798
799     if(js->color_style != COLOR_STYLE_CHROME) {
800         glEnable(GL_LIGHTING);
801         glEnable(GL_LIGHT0);
802         glEnable(GL_LIGHT1);
803         
804         glLightfv(GL_LIGHT0, GL_POSITION, lpos0);
805         glLightfv(GL_LIGHT1, GL_POSITION, lpos1);
806         glLightfv(GL_LIGHT0, GL_DIFFUSE, lcol0);
807         glLightfv(GL_LIGHT1, GL_DIFFUSE, lcol1);
808
809         glEnable(GL_COLOR_MATERIAL);
810         glColor4fv(js->jiggly_color);
811
812         glMaterialfv(GL_FRONT, GL_SPECULAR, scolor);
813         glMateriali(GL_FRONT, GL_SHININESS, js->shininess);
814     }
815     else { /* chrome */
816         init_texture(mi);
817         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
818         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
819         glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
820         glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
821         glEnable(GL_TEXTURE_GEN_S);
822         glEnable(GL_TEXTURE_GEN_T);
823         glEnable(GL_TEXTURE_2D);
824         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
825         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
826         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
827     }
828 }
829
830 static int parse_color(jigglystruct *js)
831 {
832     unsigned int r, g, b;
833     if(!strcmp(color, "clownbarf")) {
834         js->color_style = COLOR_STYLE_CLOWNBARF;
835         return 1;
836     }
837     else if(!strcmp(color, "flowerbox")) {
838         js->color_style = COLOR_STYLE_FLOWERBOX;
839         return 1;
840     }
841 # ifndef HAVE_JWZGLES  /* SPHERE_MAP unimplemented */
842     else if(!strcmp(color, "chrome")) {
843         js->color_style = COLOR_STYLE_CHROME;
844         return 1;
845     }
846 # endif
847     else if(!strcmp(color, "cycle")) {
848         js->color_style = COLOR_STYLE_CYCLE;
849         js->jiggly_color[0] = ((float)random()) / REAL_RAND_MAX * 0.7 + 0.3;
850         js->jiggly_color[1] = ((float)random()) / REAL_RAND_MAX * 0.7 + 0.3;
851         js->jiggly_color[2] = ((float)random()) / REAL_RAND_MAX * 0.7 + 0.3;
852         js->jiggly_color[3] = 1.0f;
853         js->color_dir[0] = ((float)random()) / REAL_RAND_MAX / 100.0;
854         js->color_dir[1] = ((float)random()) / REAL_RAND_MAX / 100.0;
855         js->color_dir[2] = ((float)random()) / REAL_RAND_MAX / 100.0;
856         return 1;
857     }
858     else
859         js->color_style = 0;
860     if(strlen(color) != 7)
861         return 0;
862     if(sscanf(color,"#%02x%02x%02x", &r, &g, &b) != 3) {
863         return 0;
864     }
865     js->jiggly_color[0] = ((float)r)/255;
866     js->jiggly_color[1] = ((float)g)/255;
867     js->jiggly_color[2] = ((float)b)/255;
868     js->jiggly_color[3] = 1.0f;
869
870     return 1;
871 }
872
873 static void randomize_parameters(jigglystruct *js) {
874     do_tetrahedron = random() & 1;
875 # ifndef HAVE_JWZGLES /* #### glPolygonMode other than GL_FILL unimplemented */
876     js->do_wireframe = !(random() & 3);
877 # endif
878     js->color_style = random() % 5;
879 # ifdef HAVE_JWZGLES  /* #### SPHERE_MAP unimplemented */
880     while (js->color_style == COLOR_STYLE_CHROME)
881       js->color_style = random() % 5;;
882 # endif
883     if(js->color_style == COLOR_STYLE_NORMAL
884         || js->color_style == COLOR_STYLE_CYCLE) {
885         js->jiggly_color[0] = ((float)random()) / REAL_RAND_MAX * 0.5 + 0.5;
886         js->jiggly_color[1] = ((float)random()) / REAL_RAND_MAX * 0.5 + 0.5;
887         js->jiggly_color[2] = ((float)random()) / REAL_RAND_MAX * 0.5 + 0.5;
888         js->jiggly_color[3] = 1.0f;
889         if(js->color_style == COLOR_STYLE_CYCLE) {
890             js->color_dir[0] = ((float)random()) / REAL_RAND_MAX / 100.0;
891             js->color_dir[1] = ((float)random()) / REAL_RAND_MAX / 100.0;
892             js->color_dir[2] = ((float)random()) / REAL_RAND_MAX / 100.0;
893         }
894     }
895     if((js->color_style != COLOR_STYLE_CHROME) && (random() & 1))
896         js->spooky = (random() % 6) + 4;
897     else 
898         js->spooky = 0;
899     js->shininess = random() % 200;
900     speed = (random() % 700) + 50;
901     /* It' kind of dull if this is too high when it starts as a sphere */
902     spherism = do_tetrahedron ? (random() % 500) + 20 : (random() % 100) + 10;
903     hold = (random() % 800) + 100;
904     distance = (random() % 500) + 100;
905     damping = (random() % 800) + 50;
906 }    
907
908 static void calculate_parameters(jigglystruct *js, int subdivs) {
909     /* try to compensate for the inherent instability at
910      * low complexity. */
911     float dist_factor = (subdivs == 6) ? 2 : (subdivs == 5) ? 1 : 0.5;
912
913     js->stable_distance = ((float)distance / 500.0) 
914         * (STABLE_DISTANCE / dist_factor);
915     js->hold_strength = (float)hold / 10000.0;
916     js->spherify_strength = (float)spherism / 10000.0;
917     js->damping_velocity = (float)damping / 100000.0;
918     js->damping_factor = 
919         0.001/max(js->hold_strength, js->spherify_strength);
920
921     js->speed = (float)speed / 1000.0;
922 }
923
924 /* The screenhack related functions begin here */
925
926 ENTRYPOINT Bool jigglypuff_handle_event(ModeInfo *mi, XEvent *event)
927 {
928     jigglystruct *js = &jss[MI_SCREEN(mi)];
929     
930     if(event->xany.type == ButtonPress &&
931        event->xbutton.button == Button1) {
932         js->button_down = 1;
933         gltrackball_start(js->trackball, event->xbutton.x, event->xbutton.y,
934                           MI_WIDTH(mi), MI_HEIGHT(mi));
935         return True;
936     }
937     else if(event->xany.type == ButtonRelease &&
938             event->xbutton.button == Button1) {
939         js->button_down = 0;
940         return True;
941     }
942   else if (event->xany.type == ButtonPress &&
943            (event->xbutton.button == Button4 ||
944             event->xbutton.button == Button5 ||
945             event->xbutton.button == Button6 ||
946             event->xbutton.button == Button7))
947     {
948       gltrackball_mousewheel (js->trackball, event->xbutton.button, 10,
949                               !!event->xbutton.state);
950       return True;
951     }
952     else if(event->xany.type == MotionNotify && js->button_down) {
953         gltrackball_track(js->trackball, event->xmotion.x, event->xmotion.y,
954                           MI_WIDTH(mi), MI_HEIGHT(mi));
955         return True;
956     }
957     return False;
958 }
959
960 ENTRYPOINT void reshape_jigglypuff(ModeInfo *mi, int width, int height)
961 {
962     GLfloat aspect = (GLfloat)width / (GLfloat)height;
963
964     glViewport(0, 0, width, height);
965     glMatrixMode(GL_PROJECTION);
966     glLoadIdentity();
967     glFrustum(-0.5*aspect, 0.5*aspect, -0.5, 0.5, 1, 20);
968 /*    glTranslatef(0, 0, -10);*/
969 }
970
971 ENTRYPOINT void draw_jigglypuff(ModeInfo *mi)
972 {
973     jigglystruct *js = &jss[MI_SCREEN(mi)];
974     
975     glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(js->glx_context));
976     
977     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
978
979     glMatrixMode(GL_MODELVIEW);
980     glLoadIdentity();
981     glTranslatef(0,0,-10);
982
983     glRotatef(js->angle, sin(js->axis), cos(js->axis), -sin(js->axis));
984     glTranslatef(0, 0, 5);
985     if(!(js->button_down)) {
986         if((js->angle += js->speed) >= 360.0f ) {
987             js->angle -= 360.0f;
988         }
989         if((js->axis+=0.01f) >= 2*M_PI ) {
990             js->axis -= 2*M_PI;
991         }
992     }
993
994     /* Do it twice because we don't track the device's orientation. */
995     glRotatef( current_device_rotation(), 0, 0, 1);
996     gltrackball_rotate(js->trackball);
997     glRotatef(-current_device_rotation(), 0, 0, 1);
998
999     if(js->color_style == COLOR_STYLE_CYCLE) {
1000         int i;
1001         vector_add(js->jiggly_color, js->color_dir, js->jiggly_color);
1002         
1003         for(i=0; i<3; i++) {
1004             if(js->jiggly_color[i] > 1.0 || js->jiggly_color[i] < 0.3) {
1005                 js->color_dir[i] = (-js->color_dir[i]);
1006                 js->jiggly_color[i] += js->color_dir[i];
1007             }
1008         }
1009         glColor4fv(js->jiggly_color);
1010     }
1011     
1012     mi->polygon_count = jigglypuff_render(js);
1013     if(MI_IS_FPS(mi))
1014         do_fps(mi);
1015     glFinish();
1016     update_shape(js);
1017     glXSwapBuffers(MI_DISPLAY(mi), MI_WINDOW(mi));
1018 }
1019
1020 ENTRYPOINT void init_jigglypuff(ModeInfo *mi)
1021 {
1022     jigglystruct *js;
1023     int subdivs;
1024
1025     if(!jss) {
1026         jss = (jigglystruct*)
1027             calloc(MI_NUM_SCREENS(mi), sizeof(jigglystruct));
1028         if(!jss) {
1029             fprintf(stderr, "%s: No..memory...must...abort..\n", progname);
1030             exit(1);
1031         }
1032     }
1033
1034     js = &jss[MI_SCREEN(mi)];
1035
1036     js->do_wireframe = MI_IS_WIREFRAME(mi);
1037 # ifdef HAVE_JWZGLES
1038     js->do_wireframe = 0; /* GL_LINE unimplemented */
1039 # endif
1040
1041     js->shininess = shininess;
1042
1043     subdivs = (complexity==1) ? 4 : (complexity==2) ? 5
1044         : (complexity==3) ? 6 : 5;
1045
1046     js->spooky = spooky << (subdivs-3);
1047
1048     if(!parse_color(js)) {
1049         fprintf(stderr, "%s: Bad color specification: '%s'.\n", progname, color);
1050         exit(-1);
1051     }
1052     
1053     if(random_parms)
1054         randomize_parameters(js);
1055
1056     js->angle = frand(180);
1057     js->axis  = frand(M_PI);
1058
1059     js->shape = tesselated_tetrahedron(1, subdivs, js);
1060
1061     if(!do_tetrahedron)
1062         solid_spherify(js->shape, 1);
1063
1064     if(js->color_style == COLOR_STYLE_CLOWNBARF)
1065         clownbarf_colorize(js->shape);
1066
1067     calculate_parameters(js, subdivs);
1068
1069     if((js->glx_context = init_GL(mi)) != NULL) {
1070         glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(js->glx_context));
1071         setup_opengl(mi, js);
1072         reshape_jigglypuff(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1073     }
1074     else {
1075         MI_CLEARWINDOW(mi);
1076     }
1077     js->trackball = gltrackball_init();
1078 /*    _DEBUG("distance : %f\nhold : %f\nspherify : %f\ndamping : %f\ndfact : %f\n",
1079            js->stable_distance, js->hold_strength, js->spherify_strength,
1080            js->damping_velocity, js->damping_factor);
1081     _DEBUG("wire : %d\nspooky : %d\nstyle : %d\nshininess : %d\n",
1082            js->do_wireframe, js->spooky, js->color_style, js->shininess);*/
1083 }
1084
1085 XSCREENSAVER_MODULE ("JigglyPuff", jigglypuff)
1086
1087 #endif /* USE_GL */
1088
1089 /* This is the end of the file */