From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / glx / rotator.c
1 /* xscreensaver, Copyright (c) 1998-2016 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #include <math.h>
13
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
17
18 #include <stdlib.h>
19 #include <stdio.h>
20 #include "rotator.h"
21 #include "yarandom.h"
22
23 struct rotator {
24
25   double spin_x_speed, spin_y_speed, spin_z_speed; /* scaling factors >= 0. */
26   double wander_speed;
27
28   double rotx, roty, rotz;      /* current object rotation, -1 to +1.
29                                    Sign indicates direction of motion.
30                                    0.25 means +90 deg, positive velocity.
31                                    -0.25 means +90 deg, negative velocity
32                                    (not +270 deg or -90 deg!)
33                                    Yes, this is stupid.
34                                  */
35   double dx, dy, dz;            /* current rotational velocity, >= 0. */
36   double ddx, ddy, ddz;         /* current rotational acceleration, +/-. */
37   double d_max;                 /* max rotational velocity, > 0. */
38
39   int wander_frame;             /* position in the wander cycle, >= 0. */
40 };
41
42
43 #undef ABS
44 #define ABS(x) ((x)<0?-(x):(x))
45
46 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
47 #define RANDSIGN() ((random() & 1) ? 1 : -1)
48
49 /* Stay in the range [0-1). 
50     1.01 => 0.01.
51    -0.01 => 0.99
52  */
53 #define CLAMP(i) do {   \
54     while ((i) <  0) (i)++; \
55     while ((i) >= 1) (i)--; \
56   } while (0)
57
58 #undef EPSILON
59 #define EPSILON 0.000001
60
61
62 static void
63 rotate_1 (double *pos, double *v, double *dv, double speed, double max_v)
64 {
65   /* Sign of *pos is direction of motion.
66      Sign of *v is always positive.
67      It would make way more sense for *v to indicate direction of motion.
68      What was I thinking?
69    */
70
71   double ppos = *pos;
72
73   if (speed == 0) return;
74
75   /* tick position */
76   if (ppos < 0)
77     /* Ignore but preserve the sign on ppos.  It's kind of like: 
78        ppos = old_sign * (abs(ppos) + (v * old_sign))
79        This is why it would make more sense for that sign bit to be on v.
80      */
81     ppos = -(ppos + *v);
82   else
83     ppos += *v;
84
85   CLAMP (ppos);
86   *pos = (*pos > 0 ? ppos : -ppos);  /* preserve old sign bit on pos. */
87
88   /* accelerate */
89   *v += *dv;
90
91   /* clamp velocity */
92   if (*v > max_v || *v < -max_v)
93     {
94       *dv = -*dv;
95     }
96   /* If it stops, start it going in the other direction. */
97   /* Since *v is always positive, <= 0 means stopped. */
98   else if (*v < 0)
99     {
100       if (random() % 4)
101         {
102           *v = 0;            /* don't let velocity be negative */
103
104           if (random() % 2)  /* stay stopped, and kill acceleration */
105             *dv = 0;
106           else if (*dv < 0)  /* was decelerating, accelerate instead */
107             *dv = -*dv;
108         }
109       else
110         {
111           *v = -*v;      /* switch to tiny positive velocity, or zero */
112           *dv = -*dv;    /* toggle acceleration */
113           *pos = -*pos;  /* reverse direction of motion */
114         }
115     }
116
117   /* Alter direction of rotational acceleration randomly. */
118   if (! (random() % 120))
119     *dv = -*dv;
120
121   /* Change acceleration very occasionally. */
122   if (! (random() % 200))
123     {
124 #if 0 /* this might make more sense: */
125       if (*dv > -EPSILON && *dv < EPSILON)
126         *dv += 10 * (*dv < 0 ? -EPSILON : EPSILON);
127 #else
128       if (*dv == 0)
129         *dv = 0.00001;
130 #endif
131       else if (random() & 1)
132         *dv *= 1.2;
133       else
134         *dv *= 0.8;
135     }
136 }
137
138
139 /* Returns a rotator object, which encapsulates rotation and motion state.
140
141    spin_[xyz]_speed indicates the relative speed of rotation.
142    Specify 0 if you don't want any rotation around that axis.
143
144    spin_accel specifies a scaling factor for the acceleration that is
145    randomly applied to spin: if you want the speed to change faster,
146    make this > 1.
147
148    wander_speed indicates the relative speed through space.
149
150    If randomize_initial_state_p is true, then the initial position and
151    rotation will be randomized (even if the spin speeds are 0.)  If it
152    is false, then all values will be initially zeroed.
153  */
154 rotator *
155 make_rotator (double spin_x_speed,
156               double spin_y_speed,
157               double spin_z_speed,
158               double spin_accel,
159               double wander_speed,
160               int randomize_initial_state_p)
161 {
162   rotator *r = (rotator *) calloc (1, sizeof(*r));
163   double d, dd;
164
165   if (!r) return 0;
166
167   if (spin_x_speed < 0 || spin_y_speed < 0 || spin_z_speed < 0 ||
168       wander_speed < 0)
169     abort();
170
171   r->spin_x_speed = spin_x_speed;
172   r->spin_y_speed = spin_y_speed;
173   r->spin_z_speed = spin_z_speed;
174   r->wander_speed = wander_speed;
175
176   if (randomize_initial_state_p)
177     {
178       /* Sign on position is direction of travel. Stripped before returned. */
179       r->rotx = frand(1.0) * RANDSIGN();
180       r->roty = frand(1.0) * RANDSIGN();
181       r->rotz = frand(1.0) * RANDSIGN();
182
183       r->wander_frame = random() % 0xFFFF;
184     }
185   else
186     {
187       r->rotx = r->roty = r->rotz = 0;
188       r->wander_frame = 0;
189     }
190
191   d  = 0.006;
192   dd = 0.00006;
193
194   r->dx = BELLRAND(d * r->spin_x_speed);
195   r->dy = BELLRAND(d * r->spin_y_speed);
196   r->dz = BELLRAND(d * r->spin_z_speed);
197
198   r->d_max = r->dx * 2;
199
200   r->ddx = (dd + frand(dd+dd)) * r->spin_x_speed * spin_accel;
201   r->ddy = (dd + frand(dd+dd)) * r->spin_y_speed * spin_accel;
202   r->ddz = (dd + frand(dd+dd)) * r->spin_z_speed * spin_accel;
203
204 # if 0
205   fprintf (stderr, "rotator:\n");
206   fprintf (stderr, "   wander: %3d %6.2f\n", r->wander_frame, r->wander_speed);
207   fprintf (stderr, "    speed: %6.2f %6.2f %6.2f\n",
208            r->spin_x_speed, r->spin_y_speed, r->spin_z_speed);
209   fprintf (stderr, "      rot: %6.2f %6.2f %6.2f\n",
210            r->rotx, r->roty, r->rotz);
211   fprintf (stderr, "        d: %6.2f %6.2f %6.2f, %6.2f\n",
212            r->dx, r->dy, r->dz,
213            r->d_max);
214   fprintf (stderr, "       dd: %6.2f %6.2f %6.2f\n",
215            r->ddx, r->ddy, r->ddz);
216 # endif
217
218   return r;
219 }
220
221
222 void
223 free_rotator (rotator *r)
224 {
225   free (r);
226 }
227
228 void
229 get_rotation (rotator *rot, double *x_ret, double *y_ret, double *z_ret,
230               int update_p)
231 {
232   double x, y, z;
233
234   if (update_p) {
235     rotate_1 (&rot->rotx, &rot->dx, &rot->ddx, rot->spin_x_speed, rot->d_max);
236     rotate_1 (&rot->roty, &rot->dy, &rot->ddy, rot->spin_y_speed, rot->d_max);
237     rotate_1 (&rot->rotz, &rot->dz, &rot->ddz, rot->spin_z_speed, rot->d_max);
238   }
239
240   x = rot->rotx;
241   y = rot->roty;
242   z = rot->rotz;
243   if (x < 0) x = -x;
244   if (y < 0) y = -y;
245   if (z < 0) z = -z;
246
247   if (x_ret) *x_ret = x;
248   if (y_ret) *y_ret = y;
249   if (z_ret) *z_ret = z;
250 }
251
252
253 void
254 get_position (rotator *rot, double *x_ret, double *y_ret, double *z_ret,
255               int update_p)
256 {
257   double x = 0.5, y = 0.5, z = 0.5;
258
259   if (rot->wander_speed != 0)
260     {
261       if (update_p)
262         rot->wander_frame++;
263
264 # define SINOID(F) ((1 + sin((rot->wander_frame * (F)) / 2 * M_PI)) / 2.0)
265       x = SINOID (0.71 * rot->wander_speed);
266       y = SINOID (0.53 * rot->wander_speed);
267       z = SINOID (0.37 * rot->wander_speed);
268 # undef SINOID
269     }
270
271   if (x_ret) *x_ret = x;
272   if (y_ret) *y_ret = y;
273   if (z_ret) *z_ret = z;
274 }