1 /* ball.cc
2    The class ball is used by all animated objects representing balls of different radiuses
3 
4    Copyright (C) 2000  Mathias Broxvall
5                        Yannick Perret
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    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 */
21 
22 #include "ball.h"
23 
24 #include "animatedCollection.h"
25 #include "debris.h"
26 #include "forcefield.h"
27 #include "game.h"
28 #include "mainMode.h"
29 #include "map.h"
30 #include "pipe.h"
31 #include "pipeConnector.h"
32 #include "settings.h"
33 #include "sound.h"
34 #include "splash.h"
35 #include "trampoline.h"
36 
37 #define MAX_CONTACT_POINTS 24
38 
Ball(Game & game,int role)39 Ball::Ball(Game &game, int role) : Animated(game, role, 7) {
40   sink = 0.0;
41 
42   ballResolution = BALL_LORES;
43   friction = 1.0;
44   rotation[0] = rotation[1] = 0.0;
45   rotoacc[0] = rotoacc[1] = 0.0;
46 
47   dizzy_r = dizzy_p = 0.0;
48 
49   primaryColor = Color(0.8, 0.8, 0.8, 1.);
50   secondaryColor = Color(0.1, 0.1, 0.1, 1.);
51 
52   gravity = 8.0;
53   bounceFactor = 0.8;
54   crashTolerance = 7;
55   no_physics = false;
56   inTheAir = false;
57   inPipe = false;
58   nextJumpStrength = 0.0;
59   acceleration = 4.0;
60   radius = 0.49;
61   realRadius = 0.49;
62 
63   for (int i = 0; i < NUM_MODS; i++) {
64     modTimeLeft[i] = 0.0;
65     modTimePhaseIn[i] = 0.0;
66   }
67 
68   identityMatrix(rotations);
69   texture = loadTexture("ice.png");
70   nitroDebrisCount = 0.0;
71 
72   environmentTexture = 0;
73   reflectivity = 0.0;
74   metallic = 0;
75   dontReflectSelf = 0;
76 }
~Ball()77 Ball::~Ball() {
78   setReflectivity(0.0, 0);  // This effectivly deallocates all environment maps */
79 }
80 
generateBuffers(const GLuint * idxbufs,const GLuint * databufs,const GLuint * vaolist,bool) const81 void Ball::generateBuffers(const GLuint *idxbufs, const GLuint *databufs,
82                            const GLuint *vaolist, bool /*mustUpdate*/) const {
83   if (!is_on) return;
84 
85   Color color = primaryColor.toOpaque();
86   if (0) {
87     /* useful debug code */
88     if (inTheAir) {
89       for (int i = 0; i < 3; i++) { color.v[i] = 65535 - color.v[i]; }
90     }
91   }
92   GLfloat loc[3] = {(GLfloat)position[0], (GLfloat)position[1], (GLfloat)(position[2] - sink)};
93   if (modTimeLeft[MOD_GLASS]) {
94     double phase = std::min(modTimePhaseIn[MOD_GLASS] / 2.0, 1.0);
95     if (modTimeLeft[MOD_GLASS] > 0)
96       phase = std::min(modTimeLeft[MOD_GLASS] / 2.0, phase);
97     else
98       phase = 1.0;
99     color = Color::mix(phase, color, Color(0.8, 0.8, 0.8, 0.5));
100   } else if (modTimeLeft[MOD_FROZEN]) {
101     double phase = std::min(modTimePhaseIn[MOD_FROZEN] / 2.0, 1.0);
102     if (modTimeLeft[MOD_FROZEN] > 0)
103       phase = std::min(modTimeLeft[MOD_FROZEN] / 2.0, phase);
104     else
105       phase = 1.0;
106     color = Color::mix(phase, color, Color(0.4, 0.4, 0.9, 0.6));
107   }
108 
109   {
110     /* Construct VBO for main ball */
111     int ntries = 0;
112     int nverts = 0;
113     int detail;
114     switch (ballResolution) {
115     case BALL_HIRES:
116       detail = 20;
117       break;
118     default:
119     case BALL_NORMAL:
120       detail = 14;
121       break;
122     case BALL_LORES:
123       detail = 8;
124       break;
125     }
126     countObjectSpherePoints(&ntries, &nverts, detail);
127     GLfloat *data = new GLfloat[nverts * 8];
128     ushort *idxs = new ushort[ntries * 3];
129     Matrix3d rotation;
130     if (modTimeLeft[MOD_EXTRA_LIFE]) {
131       Matrix4d ref;
132       identityMatrix(ref);
133       rotateY(M_PI / 3, ref);
134       for (int i = 0; i < 3; i++)
135         for (int j = 0; j < 3; j++) rotation[i][j] = ref[i][j];
136     } else {
137       for (int i = 0; i < 3; i++)
138         for (int j = 0; j < 3; j++) rotation[j][i] = rotations[i][j];
139     }
140     placeObjectSphere(data, idxs, 0, loc, rotation, radius, detail, color);
141 
142     glBindVertexArray(vaolist[0]);
143     glBindBuffer(GL_ARRAY_BUFFER, databufs[0]);
144     glBufferData(GL_ARRAY_BUFFER, nverts * 8 * sizeof(GLfloat), data, GL_STATIC_DRAW);
145     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (idxbufs)[0]);
146     glBufferData(GL_ELEMENT_ARRAY_BUFFER, ntries * 3 * sizeof(ushort), idxs, GL_STATIC_DRAW);
147     configureObjectAttributes();
148     delete[] data;
149     delete[] idxs;
150   }
151 
152   if (modTimeLeft[MOD_SPIKE]) {
153     /* spikes correspond to icosahedral faces */
154     const GLfloat w = (1 + std::sqrt(5)) / 2;
155     double icoverts[12][3] = {
156         {-1, w, 0},  {1, w, 0},  {-1, -w, 0}, {1, -w, 0}, {0, -1, w},  {0, 1, w},
157         {0, -1, -w}, {0, 1, -w}, {w, 0, -1},  {w, 0, 1},  {-w, 0, -1}, {-w, 0, 1},
158     };
159     ushort icofaces[20][3] = {
160         {0, 11, 5},  {0, 5, 1},  {0, 1, 7},  {0, 7, 10}, {0, 10, 11}, {1, 5, 9}, {5, 11, 4},
161         {11, 10, 2}, {10, 7, 6}, {7, 1, 8},  {3, 9, 4},  {3, 4, 2},   {3, 2, 6}, {3, 6, 8},
162         {3, 8, 9},   {4, 9, 5},  {2, 4, 11}, {6, 2, 10}, {8, 6, 7},   {9, 8, 1},
163     };
164 
165     GLfloat phase = std::min(modTimePhaseIn[MOD_SPIKE] / 2.0f, 1.0f);
166     if (modTimeLeft[MOD_SPIKE] > 0)
167       phase = std::min(modTimeLeft[MOD_SPIKE] / 2.0f, phase);
168     else
169       phase = 1.0;
170     GLfloat scale = radius * (0.5 + 0.5 * phase);
171 
172     Color sco = secondaryColor.toOpaque();
173     GLfloat flat[3] = {0.f, 0.f, 0.f};
174     Matrix3d trot;
175     for (int i = 0; i < 3; i++)
176       for (int j = 0; j < 3; j++) { trot[i][j] = rotations[j][i]; }
177 
178     GLfloat data[20 * 4][8];
179     ushort idxs[20 * 3][3];
180     char *pos = (char *)data;
181     for (int i = 0; i < 20; i++) {
182       Coord3d centroid;
183       for (int j = 0; j < 3; j++) { centroid = centroid + Coord3d(icoverts[icofaces[i][j]]); }
184       centroid = centroid / 3.0;
185 
186       Coord3d spike = centroid;
187       spike = spike * 0.87 * scale;
188       Coord3d sub = useMatrix(trot, spike);
189       pos += packObjectVertex(pos, loc[0] + sub[0], loc[1] + sub[1], loc[2] + sub[2], 0., 0.,
190                               sco, flat);
191       for (int j = 2; j >= 0; j--) {
192         Coord3d edge(icoverts[icofaces[i][j]]);
193         edge = 0.3 * scale * (0.1 * centroid + 0.9 * edge);
194         sub = useMatrix(trot, edge);
195         pos += packObjectVertex(pos, loc[0] + sub[0], loc[1] + sub[1], loc[2] + sub[2], 0., 0.,
196                                 sco, flat);
197       }
198     }
199 
200     for (int i = 0; i < 20; i++) {
201       idxs[3 * i][0] = 4 * i;
202       idxs[3 * i][1] = 4 * i + 2;
203       idxs[3 * i][2] = 4 * i + 1;
204 
205       idxs[3 * i + 1][0] = 4 * i;
206       idxs[3 * i + 1][1] = 4 * i + 3;
207       idxs[3 * i + 1][2] = 4 * i + 2;
208 
209       idxs[3 * i + 2][0] = 4 * i;
210       idxs[3 * i + 2][1] = 4 * i + 1;
211       idxs[3 * i + 2][2] = 4 * i + 3;
212     }
213 
214     glBindVertexArray(vaolist[1]);
215     glBindBuffer(GL_ARRAY_BUFFER, databufs[1]);
216     glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
217     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (idxbufs)[1]);
218     glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idxs), idxs, GL_STATIC_DRAW);
219     configureObjectAttributes();
220   }
221 
222   if (modTimeLeft[MOD_SPEED]) {
223     const int nlines = 8;
224 
225     GLfloat data[2 * nlines][3];
226     ushort idxs[2 * nlines];
227     for (int i = 0; i < nlines; i++) {
228       double angle = i * M_PI / (nlines - 1) - M_PI / 2;
229 
230       Coord3d v = velocity;
231       v[2] = 0.0;
232       if (length(v) > 0.8) {
233         v = v / length(v);
234         double z = frandom();
235 
236         data[2 * i][0] = loc[0] + std::sin(angle) * radius * v[1];
237         data[2 * i][1] = loc[1] + std::sin(angle) * radius * v[1];
238         data[2 * i][2] = loc[2] + std::cos(angle) * radius * z;
239         data[2 * i + 1][0] =
240             loc[0] + std::sin(angle) * radius * v[1] - velocity[0] * 0.4 * std::cos(angle);
241         data[2 * i + 1][1] =
242             loc[1] + std::sin(angle) * radius * -v[0] - velocity[1] * 0.4 * std::cos(angle);
243         data[2 * i + 1][2] = loc[2] + std::cos(angle) * radius * z - velocity[2] * 0.2;
244       }
245 
246       idxs[2 * i] = 2 * i;
247       idxs[2 * i + 1] = 2 * i + 1;
248     }
249 
250     glBindVertexArray(vaolist[2]);
251     glBindBuffer(GL_ARRAY_BUFFER, databufs[2]);
252     glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
253     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, idxbufs[2]);
254     glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idxs), idxs, GL_STATIC_DRAW);
255     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void *)0);
256     glEnableVertexAttribArray(0);
257   }
258 
259   // Handle modifiers
260   if (modTimeLeft[MOD_FLOAT]) {
261     Color stripeA(1., 1., 1., 1.);
262     Color stripeB(1., 0.2, 0.2, 1.);
263 
264     GLfloat data[60][8];
265     ushort idxs[40][3];
266     char *pos = (char *)data;
267     for (int i = 0; i < 10; i++) {
268       int i0 = i, i1 = (i + 1) % 10;
269       const Color &ringcolor = i % 2 ? stripeA : stripeB;
270 
271       GLfloat ir = radius * 1.0f, mr = radius * 1.2f, lr = radius * 1.4f, h = radius * 0.2f;
272 
273       GLfloat inormal0[3] = {(GLfloat)-sin10[i0], (GLfloat)-cos10[i0], 0.f};
274       GLfloat inormal1[3] = {(GLfloat)-sin10[i1], (GLfloat)-cos10[i1], 0.f};
275       GLfloat onormal0[3] = {(GLfloat)sin10[i0], (GLfloat)cos10[i0], 0.f};
276       GLfloat onormal1[3] = {(GLfloat)sin10[i1], (GLfloat)cos10[i1], 0.f};
277       GLfloat unormal[3] = {0.f, 0.f, 1.f};
278 
279       pos += packObjectVertex(pos, loc[0] + sin10[i0] * ir, loc[1] + cos10[i0] * ir, loc[2],
280                               0., 0., ringcolor, inormal0);
281       pos += packObjectVertex(pos, loc[0] + sin10[i1] * ir, loc[1] + cos10[i1] * ir, loc[2],
282                               0., 0., ringcolor, inormal1);
283 
284       pos += packObjectVertex(pos, loc[0] + sin10[i0] * mr, loc[1] + cos10[i0] * mr,
285                               loc[2] + h, 0., 0., ringcolor, unormal);
286       pos += packObjectVertex(pos, loc[0] + sin10[i1] * mr, loc[1] + cos10[i1] * mr,
287                               loc[2] + h, 0., 0., ringcolor, unormal);
288 
289       pos += packObjectVertex(pos, loc[0] + sin10[i0] * lr, loc[1] + cos10[i0] * lr, loc[2],
290                               0., 0., ringcolor, onormal0);
291       pos += packObjectVertex(pos, loc[0] + sin10[i1] * lr, loc[1] + cos10[i1] * lr, loc[2],
292                               0., 0., ringcolor, onormal1);
293 
294       ushort pts[4][3] = {{1, 0, 3}, {0, 2, 3}, {2, 5, 3}, {2, 4, 5}};
295       for (int k = 0; k < 12; k++) { idxs[4 * i + k / 3][k % 3] = pts[k / 3][k % 3] + 6 * i; }
296     }
297     glBindVertexArray(vaolist[3]);
298     glBindBuffer(GL_ARRAY_BUFFER, databufs[3]);
299     glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
300     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (idxbufs)[3]);
301     glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idxs), idxs, GL_STATIC_DRAW);
302     configureObjectAttributes();
303   }
304 
305   if (modTimeLeft[MOD_EXTRA_LIFE]) {
306     int ntries = 0;
307     int nverts = 0;
308     int detail = 5;
309     countObjectSpherePoints(&ntries, &nverts, detail);
310     GLfloat *data = new GLfloat[nverts * 8];
311     ushort *idxs = new ushort[ntries * 3];
312     Color color = primaryColor;
313     color.v[3] = 32768;
314     Matrix3d identity = {{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}};
315     placeObjectSphere(data, idxs, 0, loc, identity, radius * 1.25, detail, color);
316 
317     glBindVertexArray(vaolist[4]);
318     glBindBuffer(GL_ARRAY_BUFFER, databufs[4]);
319     glBufferData(GL_ARRAY_BUFFER, nverts * 8 * sizeof(GLfloat), data, GL_STATIC_DRAW);
320     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (idxbufs)[4]);
321     glBufferData(GL_ELEMENT_ARRAY_BUFFER, ntries * 3 * sizeof(ushort), idxs, GL_STATIC_DRAW);
322     configureObjectAttributes();
323     delete[] data;
324     delete[] idxs;
325   }
326 
327   if (modTimeLeft[MOD_JUMP]) {
328     GLfloat data[9][8];
329     ushort idxs[3][3];
330     char *pos = (char *)data;
331     GLfloat z = 1.1 + 1.0 * std::fmod(game.gameTime, 1.0);
332     GLfloat frad = (GLfloat)radius;
333     GLfloat corners[9][2] = {
334         {frad * -.3f, frad * z},         {frad * .3f, frad * (z + .9f)},
335         {frad * .3f, frad * z},          {frad * .3f, frad * (z + .9f)},
336         {frad * -.3f, frad * z},         {frad * -.3f, frad * (z + .9f)},
337         {frad * -.6f, frad * (z + .9f)}, {0.f, frad * (z + 2.1f)},
338         {frad * .6f, frad * (z + .9f)},
339     };
340 
341     Color color(1., 1., 1., 0.5);
342     GLfloat flat[3] = {0.f, 0.f, 0.f};
343     GLfloat spin = game.gameTime * 0.15;
344     for (int i = 0; i < 9; i++) {
345       pos += packObjectVertex(pos, loc[0] + std::cos(spin) * corners[i][0],
346                               loc[1] + std::sin(spin) * corners[i][0], loc[2] + corners[i][1],
347                               0., 0., color, flat);
348       idxs[i / 3][i % 3] = i;
349     }
350 
351     glBindVertexArray(vaolist[5]);
352     glBindBuffer(GL_ARRAY_BUFFER, databufs[5]);
353     glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
354     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (idxbufs)[5]);
355     glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idxs), idxs, GL_STATIC_DRAW);
356     configureObjectAttributes();
357   }
358 
359   if (modTimeLeft[MOD_DIZZY]) {
360     GLfloat data[12][8];
361     char *pos = (char *)data;
362     Color color(1., 1., 1., 0.5);
363     GLfloat flat[3] = {0.f, 0.f, 0.f};
364     for (int i = 0; i < 3; i++) {
365       GLfloat angle = 0.5 * M_PI * game.gameTime + 2 * i * M_PI / 3;
366       GLfloat frad = radius;
367       GLfloat corners[4][3] = {{-0.6f * frad, 1.5f * frad, -0.1f * frad},
368                                {+0.6f * frad, 1.5f * frad, -0.1f * frad},
369                                {+0.6f * frad, 1.5f * frad, 1.1f * frad},
370                                {-0.6f * frad, 1.5f * frad, 1.1f * frad}};
371       GLfloat txco[4][2] = {{0.f, 1.f}, {1.f, 1.f}, {1.f, 0.f}, {0.f, 0.f}};
372       for (int k = 0; k < 4; k++) {
373         pos += packObjectVertex(
374             pos, loc[0] - std::sin(angle) * corners[k][0] + std::cos(angle) * corners[k][1],
375             loc[1] + std::cos(angle) * corners[k][0] + std::sin(angle) * corners[k][1],
376             loc[2] + corners[k][2], txco[k][0], txco[k][1], color, flat);
377       }
378     }
379 
380     ushort idxs[6][3] = {{0, 1, 2}, {0, 3, 2}, {4, 5, 6}, {4, 7, 6}, {8, 9, 10}, {8, 11, 10}};
381 
382     glBindVertexArray(vaolist[6]);
383     glBindBuffer(GL_ARRAY_BUFFER, databufs[6]);
384     glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
385     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (idxbufs)[6]);
386     glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(idxs), idxs, GL_STATIC_DRAW);
387     configureObjectAttributes();
388   }
389 }
390 
drawBuffers1(const GLuint * vaolist) const391 void Ball::drawBuffers1(const GLuint *vaolist) const {
392   if (!is_on) return;
393   if (dontReflectSelf) return;
394 
395   Color specular = specularColor.toOpaque();
396   float sharpness = 20.0f;
397 
398   // Handle primary ball..
399   if (modTimeLeft[MOD_GLASS]) {
400     double phase = std::min(modTimePhaseIn[MOD_GLASS] / 2.0, 1.0);
401     if (modTimeLeft[MOD_GLASS] > 0)
402       phase = std::min(modTimeLeft[MOD_GLASS] / 2.0, phase);
403     else
404       phase = 1.0;
405     specular = Color::mix(phase, specular, Color(1., 1., 1., 1.));
406     sharpness = 50.0 * phase + sharpness * (1.0 - phase);
407     glEnable(GL_BLEND);
408     glDisable(GL_CULL_FACE);
409   } else if (modTimeLeft[MOD_FROZEN]) {
410     double phase = std::min(modTimePhaseIn[MOD_FROZEN] / 2.0, 1.0);
411     if (modTimeLeft[MOD_FROZEN] > 0)
412       phase = std::min(modTimeLeft[MOD_FROZEN] / 2.0, phase);
413     else
414       phase = 1.0;
415     specular = Color::mix(phase, specular, Color(0.8, 0.8, 1., 1.));
416     sharpness = 50.0 * phase + sharpness * (1.0 - phase);
417     glEnable(GL_BLEND);
418     glDisable(GL_CULL_FACE);
419   } else {
420     glDisable(GL_BLEND);
421     glEnable(GL_CULL_FACE);
422   }
423 
424   {
425     int ntries = 0;
426     int nverts = 0;
427     int detail;
428     switch (ballResolution) {
429     case BALL_HIRES:
430       detail = 20;
431       break;
432     default:
433     case BALL_NORMAL:
434       detail = 14;
435       break;
436     case BALL_LORES:
437       detail = 8;
438       break;
439     }
440     countObjectSpherePoints(&ntries, &nverts, detail);
441 
442     // Draw the ball...
443     setActiveProgramAndUniforms(Shader_Object);
444     setObjectUniforms(specular, sharpness, Lighting_Regular);
445     if (modTimeLeft[MOD_EXTRA_LIFE]) {
446       glBindTexture(GL_TEXTURE_2D, textureTrack);
447     } else if (texture == 0) {
448       glBindTexture(GL_TEXTURE_2D, textureBlank);
449     } else {
450       glBindTexture(GL_TEXTURE_2D, textures[texture]);
451     }
452 
453     glBindVertexArray(vaolist[0]);
454     glDrawElements(GL_TRIANGLES, 3 * ntries, GL_UNSIGNED_SHORT, (void *)0);
455 
456     if (Settings::settings->doReflections && reflectivity > 0.0 && environmentTexture &&
457         !activeView.calculating_shadows) {
458       Color c;
459       if (metallic) {
460         c = Color(primaryColor.f0(), primaryColor.f1(), primaryColor.f2(), reflectivity);
461       } else {
462         c = Color(1., 1., 1., reflectivity);
463       }
464 
465       glEnable(GL_BLEND);
466       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
467 
468       setActiveProgramAndUniforms(Shader_Reflection);
469       glUniformC(uniformLocations.refl_color, c);
470       glUniform1i(uniformLocations.tex, 0);
471       glActiveTexture(GL_TEXTURE0 + 0);
472       glBindTexture(GL_TEXTURE_2D, environmentTexture);
473 
474       glBindVertexArray(vaolist[0]);
475       glDrawElements(GL_TRIANGLES, 3 * ntries, GL_UNSIGNED_SHORT, (void *)0);
476     }
477   }
478 
479   if (modTimeLeft[MOD_SPIKE]) {
480     glDisable(GL_BLEND);
481     glEnable(GL_CULL_FACE);
482 
483     // Transfer
484     setActiveProgramAndUniforms(Shader_Object);
485     setObjectUniforms(Color(0.1, 0.1, 0.1, 1.), 10.f, Lighting_Regular);
486     glBindTexture(GL_TEXTURE_2D, textureBlank);
487 
488     glBindVertexArray(vaolist[1]);
489     glDrawElements(GL_TRIANGLES, 20 * 3 * 3, GL_UNSIGNED_SHORT, (void *)0);
490   }
491 
492   if (modTimeLeft[MOD_SPEED] && !activeView.calculating_shadows) {
493     glEnable(GL_BLEND);
494     glLineWidth(1.0);
495     glEnable(GL_LINE_SMOOTH);
496     glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
497 
498     setActiveProgramAndUniforms(Shader_Line);
499     glUniformC(uniformLocations.line_color, Color(1., 1., 1., 0.5));
500 
501     const int nlines = 8;
502 
503     glBindVertexArray(vaolist[2]);
504     glDrawElements(GL_LINES, 2 * nlines, GL_UNSIGNED_SHORT, (void *)0);
505   }
506 
507   // Handle modifiers
508   if (modTimeLeft[MOD_FLOAT]) {
509     glDisable(GL_BLEND);
510     // In case we look from below
511     glDisable(GL_CULL_FACE);
512 
513     setActiveProgramAndUniforms(Shader_Object);
514     setObjectUniforms(Color(0.1, 0.1, 0.1, 1.), 10.f, Lighting_Regular);
515     glBindTexture(GL_TEXTURE_2D, textureBlank);
516 
517     glBindVertexArray(vaolist[3]);
518     glDrawElements(GL_TRIANGLES, 40 * 3, GL_UNSIGNED_SHORT, (void *)0);
519   }
520 }
521 
drawBuffers2(const GLuint * vaolist) const522 void Ball::drawBuffers2(const GLuint *vaolist) const {
523   if (!is_on) return;
524   if (dontReflectSelf) return;
525   if (activeView.calculating_shadows) return;
526 
527   if (modTimeLeft[MOD_EXTRA_LIFE]) {
528     glEnable(GL_BLEND);
529     glEnable(GL_CULL_FACE);
530 
531     int ntries = 0;
532     int nverts = 0;
533     int detail = 5;
534     countObjectSpherePoints(&ntries, &nverts, detail);
535     setActiveProgramAndUniforms(Shader_Object);
536     setObjectUniforms(Color(0., 0., 0., 0.), 0.f, Lighting_None);
537     glBindTexture(GL_TEXTURE_2D, textureBlank);
538 
539     glBindVertexArray(vaolist[4]);
540     glDrawElements(GL_TRIANGLES, 3 * ntries, GL_UNSIGNED_SHORT, (void *)0);
541   }
542   if (modTimeLeft[MOD_JUMP]) {
543     glEnable(GL_BLEND);
544     glDisable(GL_CULL_FACE);
545 
546     setActiveProgramAndUniforms(Shader_Object);
547     setObjectUniforms(Color(0., 0., 0., 0.), 0.f, Lighting_NoShadows);
548     glBindTexture(GL_TEXTURE_2D, textureBlank);
549 
550     glBindVertexArray(vaolist[5]);
551     glDrawElements(GL_TRIANGLES, 9, GL_UNSIGNED_SHORT, (void *)0);
552   }
553 
554   if (modTimeLeft[MOD_DIZZY]) {
555     glEnable(GL_BLEND);
556     glDisable(GL_CULL_FACE);
557 
558     setActiveProgramAndUniforms(Shader_Object);
559     setObjectUniforms(Color(0., 0., 0., 0.), 0.f, Lighting_NoShadows);
560     glBindTexture(GL_TEXTURE_2D, textureDizzy);
561 
562     glBindVertexArray(vaolist[6]);
563     glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_SHORT, (void *)0);
564   }
565 }
566 
doExpensiveComputations()567 void Ball::doExpensiveComputations() {
568   if (reflectivity <= 0.0 || !Settings::settings->doReflections) return;
569 
570   // Skip if far enough from camera
571   double dx = position[0] - MainMode::mainMode->camFocus[0];
572   double dy = position[1] - MainMode::mainMode->camFocus[1];
573   if (dx * dx + dy * dy > 50 * 50) { return; }
574 
575   Matrix4d mvp;
576   matrixMult(MainMode::mainMode->cameraModelView, MainMode::mainMode->cameraProjection, mvp);
577   int vis =
578       testBboxClip(position[0] + boundingBox[0][0], position[0] + boundingBox[1][0],
579                    position[1] + boundingBox[0][1], position[1] + boundingBox[1][1],
580                    position[2] + boundingBox[0][2], position[2] + boundingBox[1][2], mvp);
581   if (!vis) return;
582 
583   if (environmentTexture == 0) glGenTextures(1, &environmentTexture);
584   glBindTexture(GL_TEXTURE_2D, environmentTexture);
585 
586   dontReflectSelf = 1;
587   MainMode::mainMode->renderEnvironmentTexture(environmentTexture, position);
588   dontReflectSelf = 0;
589 }
590 
tick(Real time)591 void Ball::tick(Real time) {
592   Animated::tick(time);
593 
594   if (!is_on) return;
595 
596   for (int i = 0; i < NUM_MODS; i++) {
597     if (!modTimeLeft[i])
598       modTimePhaseIn[i] = 0.0;
599     else
600       modTimePhaseIn[i] += time;
601 
602     if (modTimeLeft[i] > 0.0) {
603       modTimeLeft[i] -= time;
604       if (modTimeLeft[i] < 0) modTimeLeft[i] = 0.0;
605     }
606   }
607 
608   /* Nitro balls create "debris" clouds constantly */
609   if (modTimeLeft[MOD_NITRO]) generateNitroDebris(time);
610 
611   radius = realRadius;
612   if (modTimeLeft[MOD_LARGE]) {
613     double phase = std::min(modTimePhaseIn[MOD_LARGE] / 5.0, 1.0);
614     if (modTimeLeft[MOD_LARGE] > 0)
615       phase = std::min(modTimeLeft[MOD_LARGE] / 3.0, phase);
616     else
617       phase = 1.0;
618     radius *= 1.0 + phase;
619   }
620   if (modTimeLeft[MOD_SMALL]) {
621     double phase = std::min(modTimePhaseIn[MOD_SMALL] / 5.0, 1.0);
622     if (modTimeLeft[MOD_SMALL] > 0)
623       phase = std::min(modTimeLeft[MOD_SMALL] / 3.0, phase);
624     else
625       phase = 1.0;
626     radius /= 1.0 + phase;
627   }
628   boundingBox[0][0] = -radius;
629   boundingBox[0][1] = -radius;
630   boundingBox[0][2] = -radius;
631   boundingBox[1][0] = radius;
632   boundingBox[1][1] = radius;
633   boundingBox[1][2] = radius;
634   physics(time);
635 }
636 
setReflectivity(double reflectivity,int metallic)637 void Ball::setReflectivity(double reflectivity, int metallic) {
638   this->reflectivity = reflectivity;
639   this->metallic = metallic;
640   if (reflectivity <= 0.0 && environmentTexture != 0) {
641     /* Deallocate environment texture */
642     glDeleteTextures(1, &environmentTexture);
643     environmentTexture = 0;
644   }
645 }
646 
physics(Real time)647 bool Ball::physics(Real time) {
648   Map *map = game.map;
649 
650   /* Every second, the dizzy direction vector changes */
651   if (game.gameTicks % (int)(1. / PHYSICS_RESOLUTION) == 0) {
652     dizzy_r = std::sqrt(game.frandom());
653     dizzy_p = M_PI2 * game.frandom();
654   }
655 
656   if (modTimeLeft[MOD_DIZZY]) {
657     /* We cast the address of the ball into an integer in order to get a unique random seed for
658      * every ball. */
659     rotation[0] += time * 7.0 * dizzy_r * std::cos(dizzy_p);
660     rotation[1] += time * 7.0 * dizzy_r * std::sin(dizzy_p);
661   }
662 
663   /* Ball self-drive */
664   double effective_acceleration = acceleration;
665   if (modTimeLeft[MOD_SPEED]) effective_acceleration *= 1.5;
666   if (modTimeLeft[MOD_DIZZY]) effective_acceleration /= 2.0;
667   if (modTimeLeft[MOD_NITRO]) {
668     /* Mod-nitro induces maximal acceleration in the direction of motion */
669     effective_acceleration *= 3.0;
670     double dlen = std::sqrt(rotoacc[0] * rotoacc[0] + rotoacc[1] * rotoacc[1]);
671     if (dlen <= 0.) {
672       double rlen = std::sqrt(rotation[0] * rotation[0] + rotation[1] * rotation[1]);
673       if (rlen <= 0.) {
674         /* if stopped, pick a random direction */
675         rotoacc[0] = std::sin(dizzy_p);
676         rotoacc[1] = std::cos(dizzy_p);
677       } else {
678         rotoacc[0] = rotation[0] / rlen;
679         rotoacc[1] = rotation[1] / rlen;
680       }
681     } else {
682       rotoacc[0] = rotoacc[0] / dlen;
683       rotoacc[1] = rotoacc[1] / dlen;
684     }
685   }
686   if (modTimeLeft[MOD_FROZEN]) effective_acceleration = 0.;
687   rotation[0] += effective_acceleration * time * rotoacc[0];
688   rotation[1] += effective_acceleration * time * rotoacc[1];
689 
690   rotateX(-rotation[1] * time * 2.0 * M_PI * 0.3 * 0.3 / radius, rotations);
691   rotateY(-rotation[0] * time * 2.0 * M_PI * 0.3 * 0.3 / radius, rotations);
692 
693   /* Interact with terrain */
694   Cell *cells[MAX_CONTACT_POINTS];
695   Coord3d hitpts[MAX_CONTACT_POINTS];
696   Coord3d normals[MAX_CONTACT_POINTS];
697   ICoord2d cellco[MAX_CONTACT_POINTS];
698   double dhs[MAX_CONTACT_POINTS];
699   double min_height_above_ground = 1.0;
700   int nhits =
701       locateContactPoints(map, cells, hitpts, normals, cellco, dhs, &min_height_above_ground);
702   Coord3d wall_normals[MAX_CONTACT_POINTS];
703   int nwalls = locateWallBounces(map, wall_normals);
704 
705   const double wall_thresh = 0.35;
706 
707   double max_dx = 0., min_dx = 0., max_dy = 0., min_dy = 0.;
708   for (int i = 0; i < nhits; i++) {
709     if (normals[i][2] < wall_thresh) {
710       if (dhs[i] < 0.) { /* Steep wall, bounce the normal */
711         if (nwalls < MAX_CONTACT_POINTS) {
712           wall_normals[nwalls] = normals[i];
713           nwalls++;
714         }
715       }
716     }
717     /* Greatest axis-aligned slopes contribute */
718     double dx = normals[i][0] / std::max(normals[i][2], wall_thresh);
719     double dy = normals[i][1] / std::max(normals[i][2], wall_thresh);
720     max_dx = std::max(dx, max_dx);
721     min_dx = std::min(dx, min_dx);
722     max_dy = std::max(dy, max_dy);
723     min_dy = std::min(dy, min_dy);
724   }
725   /* All effects of gravity */
726   if (inTheAir)
727     velocity[2] = velocity[2] - gravity * time;
728   else if (!inPipe) {
729     velocity[0] += gravity * time * (max_dx + min_dx);
730     velocity[1] += gravity * time * (max_dy + min_dy);
731   }
732 
733   /* Compute terrain interaction fractions */
734   int sand_count = 0, acid_count = 0, ice_count = 0, track_count = 0;
735   for (int i = 0; i < nhits; i++) {
736     if (cells[i]->flags & CELL_SAND) sand_count++;
737     if (cells[i]->flags & CELL_ACID) acid_count++;
738     if (cells[i]->flags & CELL_ICE) ice_count++;
739     if (cells[i]->flags & CELL_TRACK) track_count++;
740   }
741   double inh = nhits > 0 ? 1. / nhits : 0.;
742   double sand_frac = sand_count * inh;
743   double acid_frac = acid_count * inh;
744   double ice_frac = ice_count * inh;
745   double track_frac = track_count * inh;
746 
747   /* Execute possible planned jump */
748   if (nextJumpStrength > 0.) {
749     if (!inTheAir && acid_frac < 0.99) {
750       velocity[2] += nextJumpStrength * (1.0 - acid_frac);
751       position[2] += 0.10 * radius;
752       /* correct contact height estimates; see handleGround */
753       for (int i = 0; i < nhits; i++) { dhs[i] += 0.10 * radius; }
754       inTheAir = true;
755     }
756   }
757   nextJumpStrength = 0.;
758 
759   /* Sand - generate debris */
760   if (!inTheAir && radius > 0.2 && !inPipe) {
761     double speed2 =
762         velocity[0] * velocity[0] + velocity[1] * velocity[1] + velocity[2] * velocity[2];
763     if (sand_frac > 0.) {
764       if (game.frandom() < (speed2 - 0.3) * 0.08) {
765         /* lots of friction when we crash into sand */
766         double slowdown = std::pow(0.9, sand_frac);
767         velocity[0] *= slowdown;
768         velocity[1] *= slowdown;
769         velocity[2] *= slowdown;
770         generateSandDebris();
771       }
772     } else if (Settings::settings->difficulty > 0 && modTimeLeft[MOD_SPIKE]) {
773       if (game.frandom() < (speed2 - 0.3) * 0.0007 * Settings::settings->difficulty) {
774         velocity[0] *= 0.9;
775         velocity[1] *= 0.9;
776         velocity[2] *= 0.9;
777         Cell &c = map->cell((int)position[0], (int)position[1]);
778         generateDebris(c.colors[Cell::CENTER]);
779       }
780     }
781   }
782 
783   /* All effects of water */
784   Real mapHeight = map->getHeight(position[0], position[1]);
785   double waterHeight = map->getWaterHeight(position[0], position[1]);
786   {
787     /* Floating */
788     if (modTimeLeft[MOD_FLOAT] && !inPipe && waterHeight > position[2] &&
789         waterHeight > mapHeight + radius + 0.025) {
790       velocity[2] += gravity * time * 1.5;
791       velocity[2] *= 0.99;
792       // avoid sticking to the ground
793       double delta = position[2] - radius - mapHeight;
794       if (delta < 0.025) {
795         position[2] += 0.025 - delta;
796         if (velocity[2] < 0.0) velocity[2] = 0.0;
797       }
798     }
799 
800     if (position[2] - radius < waterHeight) {
801       double depth = waterHeight - (position[2] - radius);
802       // splashes caused by speed
803       double speed = velocity[0] * velocity[0] + velocity[1] * velocity[1] +
804                      velocity[2] * velocity[2] * 5.;
805       if (game.frandom() <
806           speed * 0.001 * (depth < 0.5 ? depth : 1.0 - depth) * radius / 0.3) {
807         Color waterColor(0.4, 0.4, 0.8, 0.5);
808         Coord3d center(position[0], position[1], waterHeight);
809         game.add(new Splash(game, center, velocity, waterColor, 30 * radius / 0.3,
810                             radius));  // speed*radius*(depth<0.5?depth:1.0-depth)*2.0,radius);
811       }
812       // splashes caused by rotation. eg "swimming"
813       speed = rotation[0] * rotation[0] + rotation[1] * rotation[1];
814       if (game.frandom() < speed * 0.001) {
815         Color waterColor(0.4, 0.4, 0.8, 0.5);
816         Coord3d center(position[0], position[1], waterHeight);
817         Coord3d vel;
818         vel[0] = -rotation[0] * radius;  // 0.3;
819         vel[1] = -rotation[1] * radius;  // 0.3;
820         vel[2] = 0.2;
821         rotation[0] *= 0.9;
822         rotation[1] *= 0.9;
823         velocity[0] += 0.01 * rotation[0];
824         velocity[1] += 0.01 * rotation[1];
825         game.add(new Splash(game, center, vel, waterColor, (int)speed * 0.5, radius));
826       }
827       /* cell velocity field extends to the water within the cell */
828       Cell &c = map->cell((int)position[0], (int)position[1]);
829       double fric = 0.004 * std::min(1.0, depth / (2. * radius));  // an extra water friction
830       velocity[0] = velocity[0] * (1. - fric) + c.velocity[0] * fric;
831       velocity[1] = velocity[1] * (1. - fric) + c.velocity[1] * fric;
832       fric = 0.008;
833       velocity[2] = velocity[2] * (1. - fric) + c.velocity[1] * fric;
834     }
835   }
836 
837   /* Sinking into floor material */
838   if (acid_frac > 0. && !inPipe && !inTheAir) {
839     sink += 0.8 * time * (acid_frac + sand_frac);
840     double speed2 =
841         velocity[0] * velocity[0] + velocity[1] * velocity[1] + velocity[2] * velocity[2];
842     if (game.frandom() < (speed2 - 0.2) * 0.05) {
843       Color acidColor(0.1, 0.5, 0.1, 0.5);
844       Coord3d center(position[0], position[1], mapHeight);
845       game.add(new Splash(game, center, velocity, acidColor, speed2 * radius, radius));
846     }
847     if (modTimeLeft[MOD_GLASS]) sink = std::min(sink, 0.3);
848     if (sink > radius * 2.0) {
849       die(DIE_ACID);
850       return false;
851     }
852   } else if (sand_frac > 0. && !inPipe && !inTheAir) {
853     sink += 0.8 * time * (acid_frac + sand_frac);
854     if (sink > 0.5 * sand_frac * radius) sink = 0.5 * sand_frac * radius;
855   } else
856     sink = std::max(0.0, sink - 2.0 * time);
857 
858   /*                                      */
859   /* Ground "grip" - Also works in pipes! */
860   /*                                      */
861   {
862     const double v_base = 0.08, r_base = 0.10;
863 
864     double v_fric = v_base;
865     double r_fric = r_base;
866 
867     v_fric += (0.008 - v_base) * acid_frac;
868     r_fric += (0.010 - r_base) * acid_frac;
869 
870     if (modTimeLeft[MOD_SPIKE]) {
871       v_fric += (0.008 - v_base) * ice_frac;
872       r_fric += (0.010 - r_base) * ice_frac;
873     } else {
874       v_fric += (0.0008 - v_base) * ice_frac;
875       r_fric += (0.0010 - r_base) * ice_frac;
876     }
877 
878     if (inTheAir) {
879       if (waterHeight > position[2] - radius) {
880         v_fric = 0.0005;
881         r_fric = 0.0005;
882       } else
883         v_fric = r_fric = 0.0;
884     }
885 
886     if (inPipe) {
887       v_fric = 0.08;
888       r_fric = 0.10;
889     }
890 
891     if (track_frac > 0.) {
892       double trackv[2] = {0., 0.};
893       for (int i = 0; i < nhits; i++) {
894         if (cells[i]->flags & CELL_TRACK) {
895           trackv[0] += cells[i]->velocity[0] / (double)nhits;
896           trackv[1] += cells[i]->velocity[1] / (double)nhits;
897         }
898       }
899       velocity[0] = velocity[0] * (1.0 - v_fric) + (rotation[0] + trackv[0]) * v_fric;
900       velocity[1] = velocity[1] * (1.0 - v_fric) + (rotation[1] + trackv[1]) * v_fric;
901       rotation[0] = rotation[0] * (1.0 - r_fric) + (velocity[0] - trackv[0]) * r_fric;
902       rotation[1] = rotation[1] * (1.0 - r_fric) + (velocity[1] - trackv[1]) * r_fric;
903     } else {
904       velocity[0] = velocity[0] * (1.0 - v_fric) + rotation[0] * v_fric;
905       velocity[1] = velocity[1] * (1.0 - v_fric) + rotation[1] * v_fric;
906       rotation[0] = rotation[0] * (1.0 - r_fric) + velocity[0] * r_fric;
907       rotation[1] = rotation[1] * (1.0 - r_fric) + velocity[1] * r_fric;
908     }
909   }
910 
911   /* rotational friction - limits the speed when on ice or in the air, water etc. */
912   rotation[0] *= 0.9995;
913   rotation[1] *= 0.9995;
914   double effective_friction = 0.001 * friction;
915   if (inTheAir && !(map->getWaterHeight(position[0], position[1]) > position[2] - radius))
916     effective_friction *= 0.1;
917   else if (!inTheAir) {
918     if (inPipe) {
919     } else {
920       double slowdown = 0.;
921       slowdown += std::log(2.0) * acid_frac;
922       slowdown += std::log(0.1) * ice_frac;
923       // sand+track is 20x boost, not 10x
924       slowdown += std::log(10.0) * sand_frac;
925       slowdown += std::log(4.0) * track_frac;
926       slowdown += std::log(0.5) * sand_frac * track_frac;
927 
928       effective_friction *= std::exp(slowdown);
929     }
930     if (modTimeLeft[MOD_SPIKE]) effective_friction *= 1.5;
931     if (modTimeLeft[MOD_SPEED]) effective_friction *= 0.5;
932   }
933   double cvel[3] = {0., 0., 0.};
934   for (int i = 0; i < nhits; i++) {
935     cvel[0] += normals[i][2] * cells[i]->velocity[0] / (double)nhits;
936     cvel[1] += normals[i][2] * cells[i]->velocity[1] / (double)nhits;
937     cvel[2] +=
938         -1 * (normals[i][0] * cells[i]->velocity[0] + normals[i][1] * cells[i]->velocity[1]) /
939         (double)nhits;
940   }
941   for (int i = 0; i < 3; i++)
942     velocity[i] = velocity[i] - (velocity[i] - cvel[i]) * effective_friction;
943   if (inTheAir && velocity[2] > 5.0) velocity[2] *= 0.995;
944 
945   for (int i = 0; i < 3; i++) position[i] += time * velocity[i];
946 
947   if (!inPipe) {
948     /* automatically force ball to map height if it is far enough down */
949     if (mapHeight > position[2] + radius) { position[2] = mapHeight + 0.75 * radius; }
950 
951     if (!handleGround(map, cells, hitpts, normals, cellco, dhs, nhits, time)) return false;
952 
953     if (!handleWalls(wall_normals, nwalls)) return false;
954   }
955 
956   /* Collisions with other balls */
957   handleBallCollisions();
958 
959   /* Collisions with forcefields */
960   handleForcefieldCollisions();
961 
962   if (inPipe || nhits <= 0) inTheAir = true;
963 
964   /* Pipes */
965   handlePipes(time);
966 
967   return true;
968 }
closestPointOnTriangle(const Coord3d & tricor0,const Coord3d & tricor1,const Coord3d & tricor2,const Coord3d & point,Coord3d * closest,Coord3d * normal)969 static int closestPointOnTriangle(const Coord3d &tricor0, const Coord3d &tricor1,
970                                   const Coord3d &tricor2, const Coord3d &point,
971                                   Coord3d *closest, Coord3d *normal) {
972   Coord3d dv0 = tricor2 - tricor0;
973   Coord3d dv1 = tricor2 - tricor1;
974   Coord3d baseoff = point - tricor2;
975   Coord3d nor = crossProduct(dv0, dv1);
976   nor = nor / length(nor);
977   double dist = dotProduct(baseoff, nor);
978   if (dist > 0) {
979     *normal = nor;
980   } else {
981     *normal = -nor;
982   }
983   Coord3d nearoff = baseoff - dist * nor;
984   double s = -dotProduct(nearoff, dv0);
985   double t = -dotProduct(nearoff, dv1);
986   Coord3d m1 = dv0 - dv1;
987   Coord3d nearoffm1 = nearoff + dv1;
988   double r = -dotProduct(nearoffm1, m1);
989   double mm = dotProduct(m1, m1);
990   double uu = dotProduct(dv0, dv0);
991   double vv = dotProduct(dv1, dv1);
992   double uv = dotProduct(dv0, dv1);
993   double idet = 1. / (uu * vv - uv * uv);
994   double a = idet * (vv * s - uv * t);
995   double b = idet * (-uv * s + uu * t);
996   double c = 1 - a - b;
997   if (0. <= a && a <= 1. && 0. <= b && b <= 1. && 0. <= c && c <= 1.) {
998     *closest = tricor0 * a + tricor1 * b + tricor2 * c;
999     return 0;
1000   } else if (0. <= s && s <= uu && b <= 0.) {
1001     double q = s / uu;
1002     *closest = tricor2 * (1. - q) + tricor0 * q;
1003     return 1;
1004   } else if (0. <= t && t <= vv && a <= 0.) {
1005     double q = t / vv;
1006     *closest = tricor2 * (1. - q) + tricor1 * q;
1007     return 2;
1008   } else if (0. <= r && r <= mm && c <= 0.) {
1009     double q = r / mm;
1010     *closest = tricor0 * q + tricor1 * (1. - q);
1011     return 3;
1012   } else if (s <= 0. && t <= 0.) {
1013     *closest = tricor2;
1014     return 4;
1015   } else if (t >= 0. && r <= 0.) {
1016     *closest = tricor1;
1017     return 5;
1018   } else if (s >= 0 && r >= mm) {
1019     *closest = tricor0;
1020     return 6;
1021   }
1022   warning("cPoT impossible case happened");
1023   return -1;
1024 }
locateContactPoints(Map * map,Cell ** cells,Coord3d * hitpts,Coord3d * normals,ICoord2d * cellco,double * dhs,double * min_height_above_ground)1025 int Ball::locateContactPoints(Map *map, Cell **cells, Coord3d *hitpts, Coord3d *normals,
1026                               ICoord2d *cellco, double *dhs, double *min_height_above_ground) {
1027   int nhits = 0;
1028   /* Construct a list of triangular facets the ball could interact with,
1029    * and locate their closest interaction points. */
1030   int xmin = std::floor(position[0] - radius), xmax = std::floor(position[0] + radius);
1031   int ymin = std::floor(position[1] - radius), ymax = std::floor(position[1] + radius);
1032   for (int x = xmin; x <= xmax; x++) {
1033     for (int y = ymin; y <= ymax; y++) {
1034       Cell &c = map->cell(x, y);
1035 
1036       double ch = c.heights[Cell::CENTER];
1037       Coord3d centerpoint(x + 0.5, y + 0.5, ch);
1038       Coord3d offset = position - centerpoint;
1039 
1040       Coord3d local_corners[4] = {
1041           Coord3d(-0.5, -0.5, c.heights[Cell::SW] - ch),
1042           Coord3d(0.5, -0.5, c.heights[Cell::SE] - ch),
1043           Coord3d(0.5, 0.5, c.heights[Cell::NE] - ch),
1044           Coord3d(-0.5, 0.5, c.heights[Cell::NW] - ch),
1045       };
1046 
1047       for (int i0 = 0; i0 < 4; i0++) {
1048         int i1 = (i0 + 1) % 4;
1049 
1050         Coord3d closest, normal;
1051         int ret = closestPointOnTriangle(local_corners[i1], local_corners[i0], Coord3d(),
1052                                          offset, &closest, &normal);
1053         closest = closest + centerpoint;
1054         if (ret < 0) continue;
1055 
1056         /* Only top sides of facets matter; and no getting pulled up */
1057         if (normal[2] < 0 || position[2] < closest[2]) continue;
1058 
1059         /* Ensure that ball is over the closest point */
1060         double dx = (closest[0] - position[0]), dy = (closest[1] - position[1]);
1061         double rad2 = dx * dx + dy * dy;
1062         if (rad2 > radius * radius) continue;
1063 
1064         /* Ball must be closer than 0.07*radius to closest point */
1065         Real dh = position[2] - std::sqrt(std::max(radius * radius - rad2, 0.)) - closest[2];
1066         *min_height_above_ground = std::min(*min_height_above_ground, dh);
1067         if (dh >= 0.07 * radius) continue;
1068 
1069         if (nhits >= MAX_CONTACT_POINTS) continue;
1070         cells[nhits] = &c;
1071         hitpts[nhits] = closest;
1072         normals[nhits] = normal;
1073         cellco[nhits][0] = x;
1074         cellco[nhits][1] = y;
1075         dhs[nhits] = dh;
1076         nhits++;
1077       }
1078     }
1079   }
1080   return nhits;
1081 }
handleGround(Map * map,Cell ** cells,Coord3d * hitpts,Coord3d * normals,ICoord2d * cellco,double * dhs,int nhits,Real time)1082 bool Ball::handleGround(Map *map, Cell **cells, Coord3d *hitpts, Coord3d *normals,
1083                         ICoord2d *cellco, double *dhs, int nhits, Real time) {
1084   /* If there are no points of contact, done */
1085   if (nhits == 0) return true;
1086 
1087   /* For each point of contact, compute interaction */
1088   double weight = 1. / nhits;
1089 
1090   if (inTheAir) {
1091     /* General contact friction */
1092     double v_fric = 0.2;
1093     double r_fric = 0.4;
1094     velocity[0] = velocity[0] * (1.0 - v_fric) + rotation[0] * v_fric;
1095     velocity[1] = velocity[1] * (1.0 - v_fric) + rotation[1] * v_fric;
1096     rotation[0] = rotation[0] * (1.0 - r_fric) + velocity[0] * r_fric;
1097     rotation[1] = rotation[1] * (1.0 - r_fric) + velocity[1] * r_fric;
1098 
1099     /* Crash handling */
1100     int trampcell[MAX_CONTACT_POINTS][2];
1101     double trampspeed[MAX_CONTACT_POINTS];
1102     int nspeeds[MAX_CONTACT_POINTS];
1103     int ntramp = 0;
1104 
1105     int nacidsplash = 0;
1106     int acidSpeed = 0.;
1107 
1108     int nsandcells = 0;
1109     double sandSpeed = 0;
1110 
1111     Coord3d velbounce;
1112     double max_crash_speed = 0.;
1113     for (int i = 0; i < nhits; i++) {
1114       Real speed = -dotProduct(velocity, normals[i]);
1115       if (speed > 0) {
1116         Cell &cell = *cells[i];
1117 
1118         double crash_speed = speed;
1119         if (cell.flags & (CELL_TRAMPOLINE | CELL_SAND)) crash_speed *= 0.4;
1120         if (modTimeLeft[MOD_JUMP]) crash_speed *= 0.8;
1121         max_crash_speed = std::max(crash_speed, max_crash_speed);
1122 
1123         double effective_bounceFactor = bounceFactor;
1124         if (cell.flags & CELL_ACID) effective_bounceFactor = 0.0;
1125         if (cell.flags & CELL_SAND) effective_bounceFactor = 0.1;
1126         if (cell.flags & CELL_TRAMPOLINE) {
1127           effective_bounceFactor += 0.6;
1128 
1129           /* Boost existing trampoline cell if possible */
1130           int ptramp = ntramp;
1131           for (int j = 0; j < ntramp; j++) {
1132             if (trampcell[j][0] == cellco[i][0] && trampcell[j][1] == cellco[i][1]) {
1133               ptramp = j;
1134               break;
1135             }
1136           }
1137           if (ptramp == ntramp) {
1138             ntramp++;
1139             trampspeed[ptramp] = 0.;
1140             nspeeds[ptramp] = 0;
1141           }
1142           trampcell[ptramp][0] = cellco[i][0];
1143           trampcell[ptramp][1] = cellco[i][1];
1144           trampspeed[ptramp] += speed;
1145           nspeeds[ptramp]++;
1146         }
1147         speed *= 1.0 + effective_bounceFactor;
1148         velbounce = velbounce + weight * normals[i] * speed;
1149 
1150         if (cell.flags & CELL_ACID) {
1151           nacidsplash++;
1152           acidSpeed += speed;
1153         }
1154         if (cell.flags & CELL_SAND) {
1155           nsandcells++;
1156           sandSpeed += speed;
1157         }
1158       }
1159     }
1160 
1161     if (!crash(max_crash_speed)) return false;
1162 
1163     /* Acid splash */
1164     if (nacidsplash) {
1165       Color acidColor(0.1, 0.5, 0.1, 0.5);
1166       Coord3d center(position[0], position[1], map->getHeight(position[0], position[1]));
1167       game.add(new Splash(game, center, velocity, acidColor,
1168                           (acidSpeed / nacidsplash) * radius * 20.0, radius));
1169     }
1170 
1171     /* Apply bounce */
1172     velocity = velocity + velbounce;
1173 
1174     /* Sand handling */
1175     if (nsandcells > 0) {
1176       /* lots of friction when we crash into sand */
1177       double slowdown = std::pow(0.5, nsandcells / (double)nhits);
1178       velocity[0] *= slowdown;
1179       velocity[1] *= slowdown;
1180       velocity[2] *= slowdown;
1181       sandSpeed /= nsandcells;
1182       if (radius > 0.2)
1183         for (int i = 0; i < 10; i++)
1184           if (game.frandom() < (sandSpeed - 1.0) * 0.2) generateSandDebris();
1185       if (sandSpeed > 4.0) playEffect(SFX_SAND_CRASH);
1186     }
1187 
1188     /* Activate trampolines */
1189     for (int i = 0; i < ntramp; i++) {
1190       Real speed = trampspeed[i] / nspeeds[i];
1191       Cell &cell = map->cell(trampcell[i][0], trampcell[i][1]);
1192       Real dh = 1.0 * speed * radius * radius * radius;
1193       for (int j = 0; j < 5; j++) cell.heights[j] -= dh;
1194       if (cell.sunken <= 0.0) game.add(new Trampoline(game, trampcell[i][0], trampcell[i][1]));
1195       cell.sunken += dh;
1196     }
1197   }
1198 
1199   /* Surface attachment & kill cell*/
1200   {
1201     double meandh = 0.;
1202     int ndh = 0;
1203     Coord3d meanNormal;
1204     for (int i = 0; i < nhits; i++) {
1205       Real dh = dhs[i];
1206       if (dh > 0.07 * radius) { continue; }
1207       /* contact with kill cells is death */
1208       if (dh < 0.001 && cells[i]->flags & CELL_KILL) {
1209         die(DIE_OTHER);
1210         return false;
1211       }
1212       meandh += dh;
1213       meanNormal = meanNormal + normals[i];
1214       ndh++;
1215     }
1216     double dhw = ndh > 0 ? 1. / ndh : 0.;
1217     meandh *= dhw;
1218     meanNormal = meanNormal * dhw;
1219     /* can't be pulled down faster than gravity + slope */
1220     double slopedv = -1 * (meanNormal[0] * velocity[0] + meanNormal[1] * velocity[1]);
1221 
1222     double vmin = velocity[2] - time * gravity;
1223     double pmin = position[2] + vmin * time;
1224 
1225     if (velocity[2] - slopedv > 2.0) {
1226       /* Escape */
1227       position[2] -= meandh;
1228       position[2] += 0.07 * radius;
1229       position[2] = std::max(position[2], pmin);
1230     } else {
1231       /* Last bounce not strong enough to pull away; stick to the surface */
1232       position[2] -= meandh;
1233       if (inTheAir) {
1234         position[2] = std::max(position[2], pmin);
1235         velocity[2] = slopedv; /* adopt surface vel */
1236       } else {
1237         double k = 1.0;
1238         velocity[2] -= k * meandh;
1239         position[2] = std::max(position[2], pmin);
1240         velocity[2] = std::min(velocity[2], slopedv);
1241         velocity[2] = std::max(velocity[2], vmin);
1242       }
1243       inTheAir = false;
1244     }
1245   }
1246 
1247   return true;
1248 }
sign(double v)1249 static double sign(double v) {
1250   if (v > 0.) return 1.;
1251   if (v < 0.) return -1.;
1252   return 0.;
1253 }
locateWallBounces(Map * map,Coord3d * wall_normals)1254 int Ball::locateWallBounces(Map *map, Coord3d *wall_normals) {
1255   /* individual bounce back for each wall segment intersecting the rim of
1256    * the ball */
1257   int xmin = std::floor(position[0] - radius), xmax = std::floor(position[0] + radius);
1258   int ymin = std::floor(position[1] - radius), ymax = std::floor(position[1] + radius);
1259   int nwalls = 0;
1260   double Z = position[2];
1261   for (int x = xmin; x <= xmax; x++) {
1262     for (int y = ymin; y <= ymax; y++) {
1263       Cell &c = map->cell(x, y);
1264       /* handle X walls */
1265       for (int ly = 0; ly < 2; ly++) {
1266         double yp = (y + ly);
1267         double h1 = c.heights[(ly ? Cell::NORTH : Cell::SOUTH) + Cell::WEST];
1268         double h2 = c.heights[(ly ? Cell::NORTH : Cell::SOUTH) + Cell::EAST];
1269         double s = 0., t = 1.;
1270         bool linethere = true;
1271         if (h1 > Z && h2 > Z) {
1272         } else if (h1 > Z) {
1273           t = (Z - h1) / (h2 - h1);
1274         } else if (h2 > Z) {
1275           s = (Z - h1) / (h2 - h1);
1276         } else {
1277           linethere = false;
1278         }
1279         double as = 0., at = 1.;
1280         Cell &op = map->cell(x, y + (ly * 2 - 1));
1281         double ah1 = op.heights[(ly ? Cell::SOUTH : Cell::NORTH) + Cell::WEST];
1282         double ah2 = op.heights[(ly ? Cell::SOUTH : Cell::NORTH) + Cell::EAST];
1283         if (ah1 < Z && ah2 < Z) {
1284         } else if (ah1 < Z) {
1285           at = (Z - ah1) / (ah2 - ah1);
1286         } else if (ah2 < Z) {
1287           as = (Z - ah1) / (ah2 - ah1);
1288         } else {
1289           linethere = false;
1290         }
1291         s = std::max(s, as);
1292         t = std::min(t, at);
1293         if (s > t) linethere = false;
1294 
1295         if (position[1] > yp && ly == 0) linethere = false;
1296         if (position[1] < yp && ly == 1) linethere = false;
1297         if (position[1] == yp) linethere = false;
1298 
1299         if (linethere) {
1300           int hit = 0;
1301           if (x + s <= position[0] && position[0] <= x + t) {
1302             if (position[1] > yp && position[1] - radius < yp) hit = 1;
1303             if (position[1] < yp && position[1] + radius > yp) hit = 2;
1304           }
1305           double dsx = (x + s - position[0]), dsy = (yp - position[1]);
1306           double dtx = (x + t - position[0]), dty = (yp - position[1]);
1307           if (dsx * dsx + dsy * dsy < radius * radius) hit = 3;
1308           if (dtx * dtx + dty * dty < radius * radius) hit = 4;
1309           if (hit) {
1310             wall_normals[nwalls][0] = 0.;
1311             wall_normals[nwalls][1] = sign(position[1] - yp);
1312             wall_normals[nwalls][2] = 0.;
1313             nwalls++;
1314           }
1315         }
1316       }
1317       /* handle Y walls */
1318       for (int lx = 0; lx < 2; lx++) {
1319         double xp = (x + lx);
1320         double h1 = c.heights[(lx ? Cell::EAST : Cell::WEST) + Cell::SOUTH];
1321         double h2 = c.heights[(lx ? Cell::EAST : Cell::WEST) + Cell::NORTH];
1322         double s = 0., t = 1.;
1323         bool linethere = true;
1324         if (h1 > Z && h2 > Z) {
1325         } else if (h1 > Z) {
1326           t = (Z - h1) / (h2 - h1);
1327         } else if (h2 > Z) {
1328           s = (Z - h1) / (h2 - h1);
1329         } else {
1330           linethere = false;
1331         }
1332         double as = 0., at = 1.;
1333         Cell &op = map->cell(x + (lx * 2 - 1), y);
1334         double ah1 = op.heights[(lx ? Cell::WEST : Cell::EAST) + Cell::SOUTH];
1335         double ah2 = op.heights[(lx ? Cell::WEST : Cell::EAST) + Cell::NORTH];
1336         if (ah1 < Z && ah2 < Z) {
1337         } else if (ah1 < Z) {
1338           at = (Z - ah1) / (ah2 - ah1);
1339         } else if (ah2 < Z) {
1340           as = (Z - ah1) / (ah2 - ah1);
1341         } else {
1342           linethere = false;
1343         }
1344         s = std::max(s, as);
1345         t = std::min(t, at);
1346         if (s > t) linethere = false;
1347 
1348         if (position[0] > xp && lx == 0) linethere = false;
1349         if (position[0] < xp && lx == 1) linethere = false;
1350         if (position[0] == xp) linethere = false;
1351 
1352         if (linethere) {
1353           int hit = 0;
1354           if (y + s <= position[1] && position[1] <= y + t) {
1355             if (position[0] > xp && position[0] - radius < xp) hit = 1;
1356             if (position[0] < xp && position[0] + radius > xp) hit = 2;
1357           }
1358           double dsx = (xp - position[0]), dsy = (y + s - position[1]);
1359           double dtx = (xp - position[0]), dty = (y + t - position[1]);
1360           if (dsx * dsx + dsy * dsy < radius * radius) hit = 3;
1361           if (dtx * dtx + dty * dty < radius * radius) hit = 4;
1362           if (hit) {
1363             wall_normals[nwalls][0] = sign(position[0] - xp);
1364             wall_normals[nwalls][1] = 0.;
1365             wall_normals[nwalls][2] = 0.;
1366             nwalls++;
1367           }
1368         }
1369       }
1370     }
1371   }
1372   return nwalls;
1373 }
handleWalls(Coord3d * wall_normals,int nwalls)1374 bool Ball::handleWalls(Coord3d *wall_normals, int nwalls) {
1375   Coord3d bounce_normal;
1376   Coord3d mean_normal;
1377   int nbounce = 0;
1378   for (int i = 0; i < nwalls; i++) {
1379     double crash_speed = -dotProduct(velocity, wall_normals[i]);
1380     if (modTimeLeft[MOD_SPEED]) crash_speed *= 0.5;
1381     if (modTimeLeft[MOD_NITRO]) crash_speed *= 0.5;
1382     if (modTimeLeft[MOD_JUMP]) crash_speed *= 0.8;
1383     crash_speed *= 0.5;
1384 
1385     if (crash_speed > 0) {
1386       if (!crash(crash_speed)) return false;
1387       nbounce++;
1388       bounce_normal = bounce_normal + wall_normals[i];
1389     }
1390     mean_normal = mean_normal + wall_normals[i];
1391   }
1392 
1393   if (nbounce) {
1394     for (int k = 0; k < 3; k++)
1395       velocity[k] -= (1 + bounceFactor) * velocity[k] * std::abs(bounce_normal[k]) / nbounce;
1396   }
1397 
1398   if (nwalls) velocity = velocity + 0.1 * mean_normal / nwalls;
1399   return true;
1400 }
1401 
crash(Real speed)1402 bool Ball::crash(Real speed) {
1403   if (modTimeLeft[MOD_GLASS]) speed *= 1.5;
1404 
1405   if (speed > crashTolerance) {
1406     die(DIE_CRASH);
1407     return false;
1408   }
1409 
1410   // Make ball dizzy if crash is above 60% of what we can
1411   // tolerate. If we already are very dizzy (>4s or permanent)
1412   // keep it that way.
1413   if (speed > crashTolerance * (modTimeLeft[MOD_JUMP] ? 0.9 : 0.6))
1414     if (modTimeLeft[MOD_DIZZY] >= 0.0 &&
1415         modTimeLeft[MOD_DIZZY] < 3.0 + 1.0 * Settings::settings->difficulty)
1416       modTimeLeft[MOD_DIZZY] = 3.0 + 1.0 * Settings::settings->difficulty;
1417 
1418   return true;
1419 }
1420 
queueJump(Real strength)1421 void Ball::queueJump(Real strength) {
1422   if (strength <= 0.) return;
1423   nextJumpStrength = strength;
1424 }
drive(Real x,Real y)1425 void Ball::drive(Real x, Real y) {
1426   rotoacc[0] = x;
1427   rotoacc[1] = y;
1428 }
1429 
generateSandDebris()1430 void Ball::generateSandDebris() {
1431   Coord3d pos, vel;
1432   Real a = game.frandom() * 2.0 * M_PI;
1433   double speed = std::sqrt(velocity[0] * velocity[0] + velocity[1] * velocity[1] +
1434                            velocity[2] * velocity[2]);
1435   pos[0] = position[0] + radius * 1.7 * std::sin(a);
1436   pos[1] = position[1] + radius * 1.7 * std::cos(a);
1437   pos[2] = position[2] - radius * .5;
1438   vel[0] =
1439       velocity[0] + 2.0 * speed * std::sin(a);  // + speed * 1/2048.0 * ((rand()%2048)-1024);
1440   vel[1] =
1441       velocity[1] + 2.0 * speed * std::cos(a);  // + speed * 1/2048.0 * ((rand()%2048)-1024);
1442   vel[2] = velocity[2];                         // + speed * 1/2048.0 * ((rand()%2048)-1024);
1443   Debris *d = new Debris(game, NULL, pos, vel, 0.5 + 1.0 * game.frandom());
1444   game.add(d);
1445   d->initialSize = 0.05;
1446   d->primaryColor = Color(0.6 + 0.3 * game.frandom(), 0.5 + 0.4 * game.frandom(),
1447                           0.1 + 0.3 * game.frandom(), 1.f);
1448   d->friction = 0.0;
1449   d->calcRadius();
1450 }
generateNitroDebris(Real time)1451 void Ball::generateNitroDebris(Real time) {
1452   nitroDebrisCount += time;
1453   while (nitroDebrisCount > 0.0) {
1454     nitroDebrisCount -= 0.25;
1455     Debris *d = new Debris(game, this, position, velocity, 1.0 + game.frandom() * 2.0);
1456     game.add(d);
1457     d->position[0] += (game.frandom() - 0.5) * radius;
1458     d->position[1] += (game.frandom() - 0.5) * radius;
1459     d->position[2] += game.frandom() * radius;
1460     d->velocity[2] += 0.2;
1461     d->gravity = -0.1;
1462     d->modTimeLeft[MOD_GLASS] = -1.0;
1463     d->primaryColor = Color(0.1, 0.6, 0.1, 1.0);
1464     d->no_physics = true;
1465   }
1466 }
generateDebris(const Color & color)1467 void Ball::generateDebris(const Color &color) {
1468   Coord3d pos, vel;
1469   Real a = game.frandom() * 2.0 * M_PI;
1470   double speed = std::sqrt(velocity[0] * velocity[0] + velocity[1] * velocity[1] +
1471                            velocity[2] * velocity[2]);
1472   pos[0] = position[0] + radius * 1.5 * std::sin(a);
1473   pos[1] = position[1] + radius * 1.5 * std::cos(a);
1474   pos[2] = position[2] - radius * .5;
1475   vel[0] =
1476       velocity[0] + 2.0 * speed * std::sin(a);  // + speed * 1/2048.0 * ((rand()%2048)-1024);
1477   vel[1] =
1478       velocity[1] + 2.0 * speed * std::cos(a);  // + speed * 1/2048.0 * ((rand()%2048)-1024);
1479   vel[2] = velocity[2];                         // + speed * 1/2048.0 * ((rand()%2048)-1024);
1480   Debris *d = new Debris(game, NULL, pos, vel, 0.5 + 1.0 * game.frandom());
1481   game.add(d);
1482   d->initialSize = 0.05;
1483   d->primaryColor = color.toOpaque();
1484   d->friction = 0.0;
1485   d->calcRadius();
1486 }
1487 
handleBallCollisions()1488 void Ball::handleBallCollisions() {
1489   if (no_physics) return;
1490 
1491   Animated **balls;
1492   int nballs = game.balls->bboxOverlapsWith(this, &balls);
1493   for (int i = 0; i < nballs; i++) {
1494     Ball *ball = (Ball *)balls[i];
1495     if (ball == this) continue;
1496     if (ball->no_physics) continue;
1497 
1498     Coord3d v = ball->position - position;
1499     double dist = length(v);
1500     if (dist < radius + ball->radius - 1e-3) {
1501       double err = radius + ball->radius - dist;
1502       position[0] -= err * v[0];
1503       position[1] -= err * v[1];
1504       position[2] -= err * v[2];
1505       v = v / length(v);
1506       double speed = dotProduct(v, velocity) - dotProduct(v, ball->velocity);
1507       if (speed < 1e-3) continue;
1508       double myWeight = radius * radius * radius,
1509              hisWeight = ball->radius * ball->radius * ball->radius,
1510              totWeight = myWeight + hisWeight;
1511       myWeight /= totWeight;
1512       hisWeight /= totWeight;
1513       this->crash(speed * hisWeight * 1.5 * (ball->modTimeLeft[MOD_SPIKE] ? 6.0 : 1.0));
1514       ball->crash(speed * myWeight * 1.5 * (this->modTimeLeft[MOD_SPIKE] ? 6.0 : 1.0));
1515       velocity = velocity - speed * v * 3.0 * hisWeight;
1516       ball->velocity = ball->velocity + speed * v * 3.0 * myWeight;
1517     }
1518   }
1519 }
handleForcefieldCollisions()1520 void Ball::handleForcefieldCollisions() {
1521   int n = game.hooks[Role_Forcefield].size();
1522   for (int i = 0; i < n; i++) {
1523     ForceField *ff = (ForceField *)game.hooks[Role_Forcefield][i];
1524     if (!ff->is_on) continue;
1525 
1526     /* Boundingbox test, continue if we cannot possibly be withing FF */
1527     if (ff->direction[0] >= 0.0) {
1528       if (this->position[0] + radius < ff->position[0]) continue;
1529       if (this->position[0] - radius > ff->position[0] + ff->direction[0]) continue;
1530     } else {
1531       if (this->position[0] + radius < ff->position[0] + ff->direction[0]) continue;
1532       if (this->position[0] - radius > ff->position[0]) continue;
1533     }
1534     if (ff->direction[1] >= 0.0) {
1535       if (this->position[1] + radius < ff->position[1]) continue;
1536       if (this->position[1] - radius > ff->position[1] + ff->direction[1]) continue;
1537     } else {
1538       if (this->position[1] + radius < ff->position[1] + ff->direction[1]) continue;
1539       if (this->position[1] - radius > ff->position[1]) continue;
1540     }
1541 
1542     /*                                */
1543     /* Detailed collision computation */
1544     /*                                */
1545     Coord3d v = this->position - ff->position;
1546     v[2] = 0.0;
1547     Coord3d ff_normal;
1548     ff_normal[0] = ff->direction[1];
1549     ff_normal[1] = ff->direction[0];
1550     ff_normal[2] = 0.0;
1551     ff_normal = ff_normal / length(ff_normal);
1552     double xy_dist = dotProduct(v, ff_normal);
1553     // if(xy_dist > this->radius) continue;
1554     ff_normal = ff->direction;
1555     ff_normal = ff_normal / length(ff_normal);
1556     double ff_where = dotProduct(v, ff_normal);  // how long along ff->direction the hit is
1557     double ff_len = length(ff->direction);       // the xy-length of the forcefield
1558     if (ff_where < 0) {
1559       xy_dist = std::sqrt(xy_dist * xy_dist + ff_where * ff_where);
1560       ff_where = 0.0;
1561     } else if (ff_where > ff_len) {
1562       double tmp = ff_where - ff_len;
1563       xy_dist = std::sqrt(xy_dist * xy_dist + tmp * tmp);
1564       ff_where = ff_len;
1565     }
1566     double ff_base =
1567         ff->position[2] +
1568         ff_where / ff_len * ff->direction[2];  // lower z of forcefield at point of impact
1569     double ff_where_h = position[2];           // z position where we hit the forcefield
1570     double z_dist = 0.0;
1571     if (position[2] < ff_base) {
1572       z_dist = ff_base - position[2];
1573       ff_where_h = ff_base;
1574     } else if (position[2] > ff_base + ff->height) {
1575       z_dist = position[2] - ff_base - ff->height;
1576       ff_where_h = ff_base + ff->height;
1577     }
1578     double dist = std::sqrt(xy_dist * xy_dist + z_dist * z_dist);
1579     if (dist < radius) {
1580       // normal of forcefield
1581       ff_normal[0] = ff->direction[1];
1582       ff_normal[1] = ff->direction[0];
1583       ff_normal[2] = 0.0;
1584       ff_normal = ff_normal / length(ff_normal);
1585 
1586       // the direction we are hitting it from
1587       Coord3d v;
1588       v[0] = ff->position[0] + ff_where / ff_len * ff->direction[0] - position[0];
1589       v[1] = ff->position[1] + ff_where / ff_len * ff->direction[1] - position[1];
1590       v[2] = ff_where_h - position[2];
1591       v = v / length(v);
1592 
1593       // sign for direction
1594       double sign = dotProduct(ff_normal, v);
1595       if (sign < 0)
1596         sign = -1.0;
1597       else
1598         sign = 1.0;
1599 
1600       double bounce = ff->bounceFactor * dotProduct(velocity, v);
1601       if (bounce < 0.0) continue;  // we are already heading *away* from the forcefield
1602 
1603       if (sign == 1.0) {
1604         if (ff->allow & FF_KILL1) {
1605           die(DIE_FF);
1606           return;
1607         } else if (!(ff->allow & FF_BOUNCE1))
1608           continue;
1609       } else if (sign == -1.0) {
1610         if (ff->allow & FF_KILL2) {
1611           die(DIE_FF);
1612           return;
1613         } else if (!(ff->allow & FF_BOUNCE2))
1614           continue;
1615       }
1616 
1617       if (ff_where <= 0.0 || ff_where >= ff_len) {
1618         // Hitting a forcefield side edge
1619         velocity[0] -= bounce * v[0];
1620         velocity[1] -= bounce * v[1];
1621       } else if (v[2] < 0.0) {
1622         // Hitting forcefield from above
1623         if (velocity[2] < 0.0) velocity[2] -= velocity[2] * ff->bounceFactor;
1624       } else if (v[2] > 0.0) {
1625         // Hitting forcefield from below (!)
1626         if (velocity[2] > 0.0) velocity[2] -= velocity[2] * ff->bounceFactor;
1627       } else {
1628         // Hitting forcefield from the side
1629         velocity[0] -= sign * bounce * ff_normal[0];
1630         velocity[1] -= sign * bounce * ff_normal[1];
1631       }
1632     }
1633   }
1634 }
computePipeCoordinates(const Pipe * pipe,const Coord3d & position,double * radial,double * axial,double * pipeLength,Coord3d * direction_to_axis,Coord3d * axis_direction)1635 static void computePipeCoordinates(const Pipe *pipe, const Coord3d &position, double *radial,
1636                                    double *axial, double *pipeLength,
1637                                    Coord3d *direction_to_axis, Coord3d *axis_direction) {
1638   Coord3d direction = pipe->to - pipe->from;
1639   double pipelen = length(direction);
1640   Coord3d dirNorm = direction / length(direction);  // normalized direction
1641   Coord3d v0 = position - pipe->from;               // pipe enterance -> ball position
1642   double l = std::max(0.0, std::min(1.0, dotProduct(v0, dirNorm) /
1643                                              pipelen));  // where along pipe ball is projected
1644   Coord3d proj = pipe->from + l * direction;  // where (as pos) the ball is projected
1645   Coord3d v1 = position - proj;               // projection point -> ball position
1646   double distance = length(v1);               // how far away from the pipe the ball is
1647   *radial = distance;
1648   *axial = l;
1649   *pipeLength = pipelen;
1650   if (length(v1) > 0) v1 = v1 / length(v1);
1651   *direction_to_axis = v1;
1652   *axis_direction = dirNorm;
1653 }
handlePipes(Real time)1654 void Ball::handlePipes(Real time) {
1655   inPipe = false;
1656 
1657   int n = game.hooks[Role_Pipe].size();
1658   for (int i = 0; i < n; i++) {
1659     Pipe *pipe = (Pipe *)game.hooks[Role_Pipe][i];
1660 
1661     double distance, l, pipeLength;
1662     Coord3d dirNorm, offset;
1663     computePipeCoordinates(pipe, position, &distance, &l, &pipeLength, &offset, &dirNorm);
1664     if (distance < pipe->radius && l > 0.0 && l < 1.0) inPipe = true;
1665   }
1666 
1667   /* Then we compute the interaction with all the pipes */
1668   for (int i = 0; i < n; i++) {
1669     Pipe *pipe = (Pipe *)game.hooks[Role_Pipe][i];
1670 
1671     double distance, l, pipeLength;
1672     Coord3d dirNorm, normal;
1673     computePipeCoordinates(pipe, position, &distance, &l, &pipeLength, &normal, &dirNorm);
1674     if (distance > pipe->radius || l == 0.0 || l == 1.0) {
1675       /* Ball is on the outside */
1676       // note. If <inPipe> then we are already inside another pipe. No collision!
1677       if (pipe->radius > 0.1 && pipe->radius < radius && distance < radius) {
1678         /* Added code to bounce of too small pipes, except for *realy* thin pipes which
1679          are meant to be lifts */
1680         double speed = -dotProduct(velocity, normal);
1681         if (speed > 0)
1682           for (int i = 0; i < 3; i++) velocity[i] += speed * normal[i] * 1.5;
1683         double correction = radius - distance;
1684         for (int i = 0; i < 3; i++) position[i] += correction * normal[i];
1685       } else if (distance < pipe->radius + radius && l != 0.0 && l != 1.0 && !inPipe) {
1686         /* Collision from outside */
1687         if ((pipe->flags & PIPE_SOFT_ENTER) && l < 0.2 / pipeLength) continue;
1688         if ((pipe->flags & PIPE_SOFT_EXIT) && l > 1.0 - 0.2 / pipeLength) continue;
1689 
1690         double speed = -dotProduct(velocity, normal);
1691         if (speed > 0)
1692           for (int i = 0; i < 3; i++) velocity[i] += speed * normal[i] * 1.5;
1693         double correction = pipe->radius + radius - distance;
1694         for (int i = 0; i < 3; i++) position[i] += correction * normal[i];
1695       }
1696     } else {
1697       if (distance > pipe->radius * 0.97 - radius && l != 0.0 && l != 1.0) {
1698         /* Collision from inside */
1699         double speed = dotProduct(velocity, normal);
1700         if (speed > 0)
1701           for (int i = 0; i < 3; i++) velocity[i] -= speed * normal[i] * 1.5;
1702         double correction = distance - (pipe->radius * 0.97 - radius);
1703         for (int i = 0; i < 3; i++) position[i] -= correction * normal[i];
1704       }
1705 
1706       double zHere = (1.0 - l) * pipe->from[2] + l * pipe->to[2];
1707       if (distance > pipe->radius * 0.94 - radius && position[2] < zHere) {
1708         /* Ball is touching lower part of pipe wall */
1709         inTheAir = false;
1710         normal[2] = std::min(0., normal[2]);
1711         double scale = gravity * time / (-normal[2] + 1e-3);
1712         velocity[0] -= normal[0] * scale;
1713         velocity[1] -= normal[1] * scale;
1714         // velocity[2] += normal[2] * gravity * time;
1715       }
1716 
1717       /* Wind */
1718       if (dotProduct(velocity, dirNorm) > 0.0)
1719         for (int i = 0; i < 3; i++) velocity[i] += pipe->windForward * time * dirNorm[i];
1720       else
1721         for (int i = 0; i < 3; i++) velocity[i] += pipe->windBackward * time * dirNorm[i];
1722     }
1723   }
1724 
1725   /* Check for pipeConnectors to support ball, only if we are not inside a pipe already */
1726   if (!inPipe) {
1727     int n = game.hooks[Role_PipeConnector].size();
1728     for (int i = 0; i < n; i++) {
1729       PipeConnector *connector = (PipeConnector *)game.hooks[Role_PipeConnector][i];
1730 
1731       Coord3d v0 = connector->position - position;  // ball -> connector
1732       double dist = length(v0);                     // Distance ball center, connector center
1733       if (dist > connector->radius) {
1734         /* Ball is outside connector */
1735         if (dist < connector->radius + radius) {
1736           /* Collision from outside */
1737           v0 = v0 / length(v0);
1738           double speed = dotProduct(velocity, v0);
1739           if (speed > 0) velocity = velocity - speed * v0 * 1.5;
1740           double correction = connector->radius + radius - dist;
1741           position = position - correction * v0;
1742         }
1743       } else {
1744         /* Ball is inside connector */
1745         inPipe = true;
1746 
1747         if (dist > connector->radius * 0.97 - radius) {
1748           /* Collision from inside */
1749           v0 = v0 / length(v0);
1750           double speed = dotProduct(velocity, v0);
1751           if (speed > 0) velocity = velocity + speed * v0 * 1.5;
1752           double correction = dist - (connector->radius * 0.97 - radius);
1753           position = position + correction * v0;
1754         }
1755         if (dist > connector->radius * 0.94 - radius && position[2] < connector->position[2]) {
1756           /* Ball is touching lower part of connector */
1757           inTheAir = false;
1758           v0 = v0 / length(v0);
1759           double scale = 0.5 * gravity * time / (-v0[2] + 1e-3);
1760           velocity[0] -= v0[0] * scale;
1761           velocity[1] -= v0[1] * scale;
1762         }
1763       }
1764     }
1765   }
1766 }
1767 
die(int how)1768 void Ball::die(int how) { Animated::die(how); }
1769