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