1 /* --------------------------------------------------------------------
2 EXTREME TUXRACER
3
4 Copyright (C) 1999-2001 Jasmin F. Patry (Tuxracer)
5 Copyright (C) 2010 Extreme Tux Racer Team
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (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
18 #ifdef HAVE_CONFIG_H
19 #include <etr_config.h>
20 #endif
21
22 #include "physics.h"
23 #include "course.h"
24 #include "tux.h"
25 #include "audio.h"
26 #include "particles.h"
27 #include "game_ctrl.h"
28 #include "game_over.h"
29 #include <algorithm>
30
CControl()31 CControl::CControl() :
32 cnet_force(0, 0, 0) {
33 minSpeed = 0;
34 minFrictspeed = 0;
35 turn_fact = 0;
36 turn_animation = 0;
37 is_braking = false;
38 jump_amt = 0;
39 is_paddling = false;
40 jumping = false;
41 jump_charging = false;
42 last_pos = cpos;
43 orientation_initialized = false;
44 cairborne = false;
45 way = 0.0;
46 front_flip = false;
47 back_flip = false;
48 roll_left = false;
49 roll_right = false;
50 roll_factor = 0;
51 flip_factor = 0;
52
53 ode_time_step = -1;
54 jump_start_time = 0;
55 begin_jump = false;
56 paddle_time = 0;
57 view_init = false;
58 finish_speed = 0;
59
60 viewmode = ABOVE;
61 }
62
63 // --------------------------------------------------------------------
64 // init
65 // --------------------------------------------------------------------
66
Init()67 void CControl::Init() {
68 TVector3d nml = Course.FindCourseNormal(cpos.x, cpos.z);
69 TMatrix<4, 4> rotMat;
70 rotMat.SetRotationMatrix(-90.0, 'x');
71 TVector3d init_vel = TransformVector(rotMat, nml);
72 init_vel *= INIT_TUX_SPEED;
73
74 turn_fact = 0;
75 turn_animation = 0;
76 is_braking = false;
77 jump_amt = 0;
78 is_paddling = false;
79 jumping = false;
80 jump_charging = false;
81 cpos.y = Course.FindYCoord(cpos.x, cpos.z);
82 cvel = init_vel;
83 last_pos = cpos;
84 cnet_force = TVector3d(0, 0, 0);
85 orientation_initialized = false;
86 plane_nml = nml;
87 cdirection = init_vel;
88 cairborne = false;
89 way = 0.0;
90
91 // tricks
92 front_flip = false;
93 back_flip = false;
94 roll_left = false;
95 roll_right = false;
96 roll_factor = 0;
97 flip_factor = 0;
98
99 ode_time_step = -1;
100 }
101 // --------------------------------------------------------------------
102 // collision
103 // --------------------------------------------------------------------
104
CheckTreeCollisions(const TVector3d & pos,TVector3d * tree_loc) const105 bool CControl::CheckTreeCollisions(const TVector3d& pos, TVector3d *tree_loc) const {
106 // These variables are used to cache collision detection results
107 static bool last_collision = false;
108 static TVector3d last_collision_tree_loc(-999, -999, -999);
109 static TVector3d last_collision_pos(-999, -999, -999);
110
111 TVector3d dist_vec = pos - last_collision_pos;
112 if (MAG_SQD(dist_vec) < COLL_TOLERANCE) {
113 if (last_collision && !cairborne) {
114 if (tree_loc != nullptr) *tree_loc = last_collision_tree_loc;
115 return true;
116 } else return false;
117 }
118
119 TVector3d loc(0, 0, 0);
120 bool hit = false;
121 TMatrix<4, 4> mat;
122
123 for (std::size_t i = 0; i<Course.CollArr.size(); i++) {
124 double diam = Course.CollArr[i].diam;
125 double height = Course.CollArr[i].height;
126 loc = Course.CollArr[i].pt;
127 TVector3d distvec(loc.x - pos.x, 0.0, loc.z - pos.z);
128
129 // check distance from tree; .6 is the radius of a bounding sphere
130 double squared_dist = (diam / 2.0 + 0.6);
131 squared_dist *= squared_dist;
132 if (MAG_SQD(distvec) > squared_dist) continue;
133
134 TPolyhedron ph2 = Course.GetPoly(Course.CollArr[i].tree_type);
135 mat.SetScalingMatrix(diam, height, diam);
136 TransPolyhedron(mat, ph2);
137 mat.SetTranslationMatrix(loc.x, loc.y, loc.z);
138 TransPolyhedron(mat, ph2);
139
140 hit = g_game.character->shape->Collision(pos, ph2);
141 if (hit == true) {
142 if (tree_loc != nullptr) *tree_loc = loc;
143 Sound.Play("tree_hit", 0);
144 break;
145 }
146 }
147
148 last_collision_tree_loc = loc;
149 last_collision_pos = pos;
150 last_collision = hit;
151
152 return hit;
153 }
154
AdjustTreeCollision(const TVector3d & pos,TVector3d * vel) const155 void CControl::AdjustTreeCollision(const TVector3d& pos, TVector3d *vel) const {
156 TVector3d treeLoc;
157
158 if (CheckTreeCollisions(pos, &treeLoc)) {
159 TVector3d treeNml(
160 pos.x - treeLoc.x,
161 0,
162 pos.z - treeLoc.z);
163 treeNml.Norm();
164
165 double speed = vel->Norm();
166 speed *= 0.8; // original 0.7
167
168 double costheta = DotProduct(*vel, treeNml);
169 if (costheta < 0) {
170 double factor;
171 if (cairborne) factor = 0.5;
172 else factor = 1.5;
173 *vel += (-factor * costheta) * treeNml;
174 vel->Norm();
175 }
176 speed = std::max(speed, minSpeed);
177 *vel *= speed;
178 }
179 }
180
CheckItemCollection(const TVector3d & pos)181 void CControl::CheckItemCollection(const TVector3d& pos) {
182 std::size_t num_items = Course.NocollArr.size();
183
184 for (std::size_t i=0; i<num_items; i++) {
185 if (Course.NocollArr[i].collectable != 1) continue;
186
187 double diam = Course.NocollArr[i].diam;
188 const TVector3d& loc = Course.NocollArr[i].pt;
189
190 TVector3d distvec(loc.x - pos.x, loc.y - pos.y, loc.z - pos.z);
191 double squared_dist = (diam / 2. + 0.7);
192 squared_dist *= squared_dist;
193 if (MAG_SQD(distvec) <= squared_dist) { // Check collision using a bounding sphere
194 Course.NocollArr[i].collectable = 0;
195 g_game.herring += 1;
196 Sound.Play("pickup1", 0);
197 Sound.Play("pickup2", 0);
198 Sound.Play("pickup3", 0);
199 }
200 }
201 }
202 // --------------------------------------------------------------------
203 // position and velocity ***
204 // --------------------------------------------------------------------
205
AdjustVelocity()206 void CControl::AdjustVelocity() {
207 double speed = cvel.Norm();
208 speed = std::max(minSpeed, speed);
209 cvel *= speed;
210
211 if (g_game.finish == true) {
212 /// --------------- finish ------------------------------------
213 if (speed < 3) State::manager.RequestEnterState(GameOver);
214 /// -----------------------------------------------------------
215 }
216 }
217
AdjustPosition(const TPlane & surf_plane,double dist_from_surface)218 void CControl::AdjustPosition(const TPlane& surf_plane, double dist_from_surface) {
219 if (dist_from_surface < -MAX_SURF_PEN) {
220 double displace = -MAX_SURF_PEN - dist_from_surface;
221 cpos += displace * surf_plane.nml;
222 }
223 }
224
SetTuxPosition(double speed)225 void CControl::SetTuxPosition(double speed) {
226 CCharShape *shape = g_game.character->shape;
227
228 TVector2d playSize = Course.GetPlayDimensions();
229 TVector2d courseSize = Course.GetDimensions();
230 double boundaryWidth = (courseSize.x - playSize.x) / 2;
231 if (cpos.x < boundaryWidth) cpos.x = boundaryWidth;
232 if (cpos.x > courseSize.x - boundaryWidth) cpos.x = courseSize.x - boundaryWidth;
233 if (cpos.z > 0) cpos.z = 0;
234
235 if (g_game.finish == false) {
236 /// ------------------- finish --------------------------------
237 if (-cpos.z >= playSize.y) {
238 if (g_game.use_keyframe) {
239 g_game.finish = true;
240 finish_speed = speed;
241 // SetStationaryCamera (true);
242 } else State::manager.RequestEnterState(GameOver);
243 }
244 /// -----------------------------------------------------------
245 }
246 double disp_y = cpos.y + TUX_Y_CORR;
247 shape->ResetNode(0);
248 shape->TranslateNode(0, TVector3d(cpos.x, disp_y, cpos.z));
249 }
250 // --------------------------------------------------------------------
251 // forces ***
252 // --------------------------------------------------------------------
253
CalcRollNormal(double speed)254 TVector3d CControl::CalcRollNormal(double speed) {
255 TVector3d vel = ProjectToPlane(ff.surfnml, ff.vel);
256 vel.Norm();
257
258 double roll_angle = MAX_ROLL_ANGLE;
259 if (is_braking) roll_angle = BRAKING_ROLL_ANGLE;
260
261 double angle = turn_fact * roll_angle *
262 std::min(1.0, std::max(0.0, ff.frict_coeff) / IDEAL_ROLL_FRIC) *
263 std::min(1.0, std::max(0.0, speed - minSpeed) / (IDEAL_ROLL_SPEED - minSpeed));
264
265 TMatrix<4, 4> rot_mat = RotateAboutVectorMatrix(vel, angle);
266 return TransformVector(rot_mat, ff.surfnml);
267 }
268
269 const double airlog[] = {-1, 0, 1, 2, 3, 4, 5, 6};
270 const double airdrag[] = {2.25, 1.35, 0.6, 0, -0.35, -0.45, -0.33, -0.9};
271
CalcAirForce()272 TVector3d CControl::CalcAirForce() {
273 TVector3d windvec = -ff.vel;
274 if (g_game.wind_id > 0)
275 windvec += WIND_FACTOR * Wind.WindDrift();
276
277 double windspeed = windvec.Length();
278 double re = 34600 * windspeed;
279 int tablesize = sizeof(airdrag) / sizeof(airdrag[0]);
280 double interpol = LinearInterp(airlog, airdrag, std::log10(re), tablesize);
281 double dragcoeff = std::pow(10.0, interpol);
282 double airfact = 0.104 * dragcoeff * windspeed;
283 return airfact * windvec;
284 }
285
CalcSpringForce()286 TVector3d CControl::CalcSpringForce() {
287 double springvel = DotProduct(ff.vel, ff.rollnml);
288 double springfact = std::min(ff.compression, 0.05) * 1500;
289 springfact += clamp(0.0, ff.compression - 0.05, 0.12) * 3000;
290 springfact += std::max(0.0, ff.compression - 0.12 - 0.05) * 10000;
291 springfact -= springvel * (ff.compression <= 0.05 ? 1500 : 500);
292 springfact = clamp(0.0, springfact, 3000.0);
293 return springfact * ff.rollnml;
294 }
295
CalcNormalForce()296 TVector3d CControl::CalcNormalForce() {
297 if (ff.surfdistance <= -ff.comp_depth) {
298 ff.compression = -ff.surfdistance - ff.comp_depth;
299 return CalcSpringForce();
300 }
301 return TVector3d(0, 0, 0);
302 }
303
CalcJumpForce()304 TVector3d CControl::CalcJumpForce() {
305 TVector3d jumpforce;
306 if (begin_jump == true) {
307 begin_jump = false;
308 if (cairborne == false) {
309 jumping = true;
310 jump_start_time = g_game.time;
311 } else jumping = false;
312 }
313 if ((jumping) && (g_game.time - jump_start_time < JUMP_FORCE_DURATION)) {
314 double y = 294 + jump_amt * 294; // jump_amt goes from 0 to 1
315 jumpforce.y = y;
316
317 } else {
318 jumping = false;
319 }
320 return jumpforce; // normally scaled with 1.0
321 }
322
CalcFrictionForce(double speed,const TVector3d & nmlforce)323 TVector3d CControl::CalcFrictionForce(double speed, const TVector3d& nmlforce) {
324 if ((cairborne == false && speed > minFrictspeed) || g_game.finish) {
325 double fric_f_mag = nmlforce.Length() * ff.frict_coeff;
326 fric_f_mag = std::min(MAX_FRICT_FORCE, fric_f_mag);
327 TVector3d frictforce = fric_f_mag * ff.frictdir;
328
329 double steer_angle = turn_fact * MAX_TURN_ANGLE;
330
331 if (std::fabs(fric_f_mag * std::sin(steer_angle * M_PI / 180)) > MAX_TURN_PERP) {
332 steer_angle = RADIANS_TO_ANGLES(std::asin(MAX_TURN_PERP / fric_f_mag)) *
333 turn_fact / std::fabs(turn_fact);
334 }
335 TMatrix<4, 4> fric_rot_mat = RotateAboutVectorMatrix(ff.surfnml, steer_angle);
336 frictforce = TransformVector(fric_rot_mat, frictforce);
337 return (1.0 + MAX_TURN_PEN) * frictforce;
338 }
339 return TVector3d(0, 0, 0);
340 }
341
CalcBrakeForce(double speed)342 TVector3d CControl::CalcBrakeForce(double speed) {
343 if (g_game.finish == false) {
344 if (cairborne == false && speed > minFrictspeed) {
345 if (speed > minSpeed && is_braking) {
346 return ff.frict_coeff * BRAKE_FORCE * ff.frictdir;
347 }
348 }
349 return TVector3d(0, 0, 0);
350 } else {
351 /// ------------------- finish --------------------------------
352 if (cairborne == false) {
353 is_braking = true;
354 return finish_speed * g_game.finish_brake * ff.frictdir;
355 } else {
356 return finish_speed * FIN_AIR_BRAKE * ff.frictdir;
357 }
358 /// -----------------------------------------------------------
359 }
360 return TVector3d(0, 0, 0);
361 }
362
CalcPaddleForce(double speed)363 TVector3d CControl::CalcPaddleForce(double speed) {
364 TVector3d paddleforce(0, 0, 0);
365 if (is_paddling)
366 if (g_game.time - paddle_time >= PADDLING_DURATION) is_paddling = false;
367
368 if (is_paddling) {
369 if (cairborne) {
370 paddleforce.z = -TUX_MASS * EARTH_GRAV / 4.0;
371 paddleforce = RotateVector(corientation, paddleforce);
372 } else {
373 double factor = -std::min(MAX_PADD_FORCE, MAX_PADD_FORCE
374 * (MAX_PADDLING_SPEED - speed) / MAX_PADDLING_SPEED
375 * std::min(1.0, ff.frict_coeff / IDEAL_PADD_FRIC));
376 paddleforce = factor * ff.frictdir;
377 }
378 } else return paddleforce;
379 return PADDLE_FACT * paddleforce;
380 }
381
CalcGravitationForce()382 TVector3d CControl::CalcGravitationForce() {
383 if (g_game.finish == false) {
384 return TVector3d(0, -EARTH_GRAV * TUX_MASS, 0);
385 } else {
386 /// ---------------- finish -----------------------------------
387 if (cairborne) return TVector3d(0, -FIN_AIR_GRAV, 0);
388 else return TVector3d(0, -FIN_GRAV, 0);
389 /// -----------------------------------------------------------
390 }
391 }
392
CalcNetForce(const TVector3d & pos,const TVector3d & vel)393 TVector3d CControl::CalcNetForce(const TVector3d& pos, const TVector3d& vel) {
394 // pos and vel are temporary, see ODE solver
395
396 ff.pos = pos;
397 ff.vel = vel;
398
399 ff.frictdir = ff.vel;
400 double speed = ff.frictdir.Norm();
401 ff.frictdir *= -1.0;
402
403 static std::vector<double> surfweights;
404 if (surfweights.size() != Course.TerrList.size())
405 surfweights.resize(Course.TerrList.size());
406 Course.GetSurfaceType(ff.pos.x, ff.pos.z, &surfweights[0]);
407 ff.frict_coeff = ff.comp_depth = 0;
408 for (std::size_t i=0; i<Course.TerrList.size(); i++) {
409 ff.frict_coeff += surfweights[i] * Course.TerrList[i].friction;
410 ff.comp_depth += surfweights[i] * Course.TerrList[i].depth;
411 }
412
413 TPlane surfplane = Course.GetLocalCoursePlane(ff.pos);
414 ff.surfnml = surfplane.nml;
415 ff.rollnml = CalcRollNormal(speed);
416 ff.surfdistance = DistanceToPlane(surfplane, ff.pos);
417 cairborne = (bool)(ff.surfdistance > 0);
418
419 // don't change this order:
420 TVector3d gravforce = CalcGravitationForce();
421 TVector3d nmlforce = CalcNormalForce();
422 TVector3d jumpforce = CalcJumpForce();
423 TVector3d frictforce = CalcFrictionForce(speed, nmlforce);
424 TVector3d brakeforce = CalcBrakeForce(speed);
425 TVector3d airforce = CalcAirForce();
426 TVector3d paddleforce = CalcPaddleForce(speed);
427
428 return jumpforce + gravforce + nmlforce + frictforce + airforce + brakeforce + paddleforce;
429 }
430
431 // --------------------------------------------------------------------
432 // ODE solver
433 // --------------------------------------------------------------------
434
AdjustTimeStep(double h,const TVector3d & vel)435 double CControl::AdjustTimeStep(double h, const TVector3d& vel) {
436 double speed = vel.Length();
437 h = clamp(MIN_TIME_STEP, h, MAX_STEP_DIST / speed);
438 h = std::min(h, MAX_TIME_STEP);
439 return h;
440 }
441
SolveOdeSystem(double timestep)442 void CControl::SolveOdeSystem(double timestep) {
443 double pos_err[3], vel_err[3], tot_pos_err, tot_vel_err;
444 double err=0, tol=0;
445
446 static const TOdeSolver solver;
447 double h = ode_time_step;
448 if (h < 0 || solver.EstimateError == nullptr)
449 h = AdjustTimeStep(timestep, cvel);
450 double t = 0;
451 double tfinal = timestep;
452
453 TOdeData x;
454 TOdeData y;
455 TOdeData z;
456 TOdeData vx;
457 TOdeData vy;
458 TOdeData vz;
459
460 TVector3d new_pos = cpos;
461 TVector3d new_vel = cvel;
462 TVector3d new_f = cnet_force;
463
464 bool done = false;
465 while (!done) {
466 if (t >= tfinal) {
467 Message("t >= tfinal in ode_system()");
468 break;
469 }
470 if (1.1 * h > tfinal - t) {
471 h = tfinal-t;
472 done = true;
473 }
474 TVector3d saved_pos = new_pos;
475 TVector3d saved_vel = new_vel;
476 TVector3d saved_f = new_f;
477
478 bool failed = false;
479 for (;;) {
480 solver.InitOdeData(&x, new_pos.x, h);
481 solver.InitOdeData(&y, new_pos.y, h);
482 solver.InitOdeData(&z, new_pos.z, h);
483 solver.InitOdeData(&vx, new_vel.x, h);
484 solver.InitOdeData(&vy, new_vel.y, h);
485 solver.InitOdeData(&vz, new_vel.z, h);
486
487 solver.UpdateEstimate(&x, 0, new_vel.x);
488 solver.UpdateEstimate(&y, 0, new_vel.y);
489 solver.UpdateEstimate(&z, 0, new_vel.z);
490 solver.UpdateEstimate(&vx, 0, new_f.x / TUX_MASS);
491 solver.UpdateEstimate(&vy, 0, new_f.y / TUX_MASS);
492 solver.UpdateEstimate(&vz, 0, new_f.z / TUX_MASS);
493
494 for (int i=1; i < solver.NumEstimates(); i++) {
495 new_pos.x = solver.NextValue(&x, i);
496 new_pos.y = solver.NextValue(&y, i);
497 new_pos.z = solver.NextValue(&z, i);
498 new_vel.x = solver.NextValue(&vx, i);
499 new_vel.y = solver.NextValue(&vy, i);
500 new_vel.z = solver.NextValue(&vz, i);
501
502 solver.UpdateEstimate(&x, i, new_vel.x);
503 solver.UpdateEstimate(&y, i, new_vel.y);
504 solver.UpdateEstimate(&z, i, new_vel.z);
505
506 new_f = CalcNetForce(new_pos, new_vel);
507
508 solver.UpdateEstimate(&vx, i, new_f.x / TUX_MASS);
509 solver.UpdateEstimate(&vy, i, new_f.y / TUX_MASS);
510 solver.UpdateEstimate(&vz, i, new_f.z / TUX_MASS);
511 }
512
513 new_pos.x = solver.FinalEstimate(&x);
514 new_pos.y = solver.FinalEstimate(&y);
515 new_pos.z = solver.FinalEstimate(&z);
516
517 new_vel.x = solver.FinalEstimate(&vx);
518 new_vel.y = solver.FinalEstimate(&vy);
519 new_vel.z = solver.FinalEstimate(&vz);
520
521 if (solver.EstimateError != nullptr) {
522 pos_err[0] = solver.EstimateError(&x);
523 pos_err[1] = solver.EstimateError(&y);
524 pos_err[2] = solver.EstimateError(&z);
525
526 vel_err[0] = solver.EstimateError(&vx);
527 vel_err[1] = solver.EstimateError(&vy);
528 vel_err[2] = solver.EstimateError(&vz);
529
530 tot_pos_err = 0.;
531 tot_vel_err = 0.;
532
533 for (int i=0; i<3; i++) {
534 pos_err[i] *= pos_err[i];
535 tot_pos_err += pos_err[i];
536 vel_err[i] *= vel_err[i];
537 tot_vel_err += vel_err[i];
538 }
539 tot_pos_err = std::sqrt(tot_pos_err);
540 tot_vel_err = std::sqrt(tot_vel_err);
541 if (tot_pos_err / MAX_POS_ERR > tot_vel_err / MAX_VEL_ERR) {
542 err = tot_pos_err;
543 tol = MAX_POS_ERR;
544 } else {
545 err = tot_vel_err;
546 tol = MAX_VEL_ERR;
547 }
548
549 if (err > tol && h > MIN_TIME_STEP + EPS) {
550 done = false;
551 if (!failed) {
552 failed = true;
553 h *= std::max(0.5, 0.8 * std::pow(tol/err, solver.TimestepExponent()));
554 } else h *= 0.5;
555
556 h = AdjustTimeStep(h, saved_vel);
557 new_pos = saved_pos;
558 new_vel = saved_vel;
559 new_f = saved_f;
560 } else break;
561 } else break;
562 }
563
564 t = t + h;
565 double speed = new_vel.Length();
566 if (param.perf_level > 2) generate_particles(this, h, new_pos, speed);
567
568 new_f = CalcNetForce(new_pos, new_vel);
569
570 if (!failed && solver.EstimateError != nullptr) {
571 double temp = 1.25 * std::pow(err / tol, solver.TimestepExponent());
572 if (temp > 0.2) h = h / temp;
573 else h = 5.0 * h;
574 }
575 h = AdjustTimeStep(h, new_vel);
576 AdjustTreeCollision(new_pos, &new_vel);
577 // if (g_game.finish) new_vel = ScaleVector (0.99,new_vel);
578 CheckItemCollection(new_pos);
579 }
580 ode_time_step = h;
581 cnet_force = new_f;
582
583 cvel = new_vel;
584 last_pos = cpos;
585 cpos = new_pos;
586
587 double step = (cpos - last_pos).Length();
588 way += step;
589 }
590
591 // --------------------------------------------------------------------
592 // update tux position
593 // --------------------------------------------------------------------
594
UpdatePlayerPos(float timestep)595 void CControl::UpdatePlayerPos(float timestep) {
596 CCharShape *shape = g_game.character->shape;
597 double paddling_factor;
598 double flap_factor;
599 double dist_from_surface;
600
601 if (g_game.finish) {
602 /// --------------------- finish ------------------------------
603 minSpeed = 0;
604 minFrictspeed = 0;
605 /// -----------------------------------------------------------
606 } else {
607 minSpeed = MIN_TUX_SPEED;
608 minFrictspeed = MIN_FRICT_SPEED;
609 }
610
611 if (timestep > 2 * EPS) SolveOdeSystem(timestep);
612
613 TPlane surf_plane = Course.GetLocalCoursePlane(cpos);
614 TVector3d surf_nml = surf_plane.nml; // normal vector of terrain
615 dist_from_surface = DistanceToPlane(surf_plane, cpos);
616
617 double speed = cvel.Length();
618 AdjustVelocity();
619 AdjustPosition(surf_plane, dist_from_surface);
620 SetTuxPosition(speed); // speed only to set finish_speed
621 shape->AdjustOrientation(this, timestep, dist_from_surface, surf_nml);
622
623 flap_factor = 0;
624 if (is_paddling) {
625 double factor;
626 factor = (g_game.time - paddle_time) / PADDLING_DURATION;
627 if (cairborne) {
628 paddling_factor = 0;
629 flap_factor = factor;
630 } else {
631 paddling_factor = factor;
632 flap_factor = 0;
633 }
634 } else {
635 paddling_factor = 0;
636 }
637
638 TVector3d local_force = RotateVector
639 (ConjugateQuaternion(corientation), cnet_force);
640
641 if (jumping)
642 flap_factor = (g_game.time - jump_start_time) / JUMP_FORCE_DURATION;
643
644 shape->AdjustJoints(turn_animation, is_braking, paddling_factor, speed,
645 local_force, flap_factor);
646 }
647