RigsofRods
Soft-body Physics Simulation
ActorForcesEuler.cpp
Go to the documentation of this file.
1 /*
2  This source file is part of Rigs of Rods
3  Copyright 2005-2012 Pierre-Michel Ricordel
4  Copyright 2007-2012 Thomas Fischer
5 
6  For more information, see http://www.rigsofrods.org/
7 
8  Rigs of Rods is free software: you can redistribute it and/or modify
9  it under the terms of the GNU General Public License version 3, as
10  published by the Free Software Foundation.
11 
12  Rigs of Rods 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 Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include "Application.h"
22 #include "AeroEngine.h"
23 #include "AirBrake.h"
24 #include "Airfoil.h"
25 #include "ApproxMath.h"
26 #include "Actor.h"
27 #include "ActorManager.h"
28 #include "Buoyance.h"
29 #include "CmdKeyInertia.h"
30 #include "Collisions.h"
31 #include "Console.h"
32 #include "Differentials.h"
33 #include "EngineSim.h"
34 #include "FlexAirfoil.h"
35 #include "GameContext.h"
36 #include "Replay.h"
37 #include "ScrewProp.h"
38 #include "ScriptEngine.h"
39 #include "SoundScriptManager.h"
40 #include "Terrain.h"
41 #include "Water.h"
42 
43 using namespace Ogre;
44 using namespace RoR;
45 
46 void Actor::CalcForcesEulerCompute(bool doUpdate, int num_steps)
47 {
48  this->CalcNodes(); // must be done directly after the inter truck collisions are handled
49  this->UpdateBoundingBoxes();
50  this->CalcEventBoxes();
51  this->CalcReplay();
52  this->CalcAircraftForces(doUpdate);
53  this->CalcFuseDrag();
54  this->CalcBuoyance(doUpdate);
55  this->CalcDifferentials();
56  this->CalcWheels(doUpdate, num_steps);
57  this->CalcShocks(doUpdate, num_steps);
58  this->CalcHydros();
59  this->CalcCommands(doUpdate);
60  this->CalcTies();
61  this->CalcTruckEngine(doUpdate); // must be done after the commands / engine triggers are updated
62  this->CalcMouse();
63  this->CalcBeams(doUpdate);
64  this->CalcCabCollisions();
65  this->updateSlideNodeForces(PHYSICS_DT); // must be done after the contacters are updated
66  this->CalcForceFeedback(doUpdate);
67 }
68 
69 void Actor::CalcForceFeedback(bool doUpdate)
70 {
71  if (this == App::GetGameContext()->GetPlayerActor().GetRef())
72  {
73  if (doUpdate)
74  {
75  m_force_sensors.Reset();
76  }
77 
78  if (ar_current_cinecam != -1)
79  {
80  m_force_sensors.accu_body_forces += ar_nodes[ar_camera_node_pos[ar_current_cinecam]].Forces;
81  }
82 
83  for (hydrobeam_t& hydrobeam: ar_hydros)
84  {
85  beam_t* beam = &ar_beams[hydrobeam.hb_beam_index];
86  if ((hydrobeam.hb_flags & (HYDRO_FLAG_DIR | HYDRO_FLAG_SPEED)) && !beam->bm_broken)
87  {
88  m_force_sensors.accu_hydros_forces += hydrobeam.hb_speed * beam->refL * beam->stress;
89  }
90  }
91  }
92 }
93 
94 void Actor::CalcMouse()
95 {
96  if (m_mouse_grab_node != NODENUM_INVALID)
97  {
98  Vector3 dir = m_mouse_grab_pos - ar_nodes[m_mouse_grab_node].AbsPosition;
99  ar_nodes[m_mouse_grab_node].Forces += m_mouse_grab_move_force * dir;
100  }
101 }
102 
103 void Actor::CalcAircraftForces(bool doUpdate)
104 {
105  //airbrake forces
106  for (Airbrake* ab: ar_airbrakes)
107  ab->applyForce();
108 
109  //turboprop forces
110  for (int i = 0; i < ar_num_aeroengines; i++)
111  if (ar_aeroengines[i])
112  ar_aeroengines[i]->updateForces(PHYSICS_DT, doUpdate);
113 
114  //screwprop forces
115  for (int i = 0; i < ar_num_screwprops; i++)
116  if (ar_screwprops[i])
117  ar_screwprops[i]->updateForces(doUpdate);
118 
119  //wing forces
120  for (int i = 0; i < ar_num_wings; i++)
121  if (ar_wings[i].fa)
122  ar_wings[i].fa->updateForces();
123 }
124 
125 void Actor::CalcFuseDrag()
126 {
127  if (m_fusealge_airfoil && m_fusealge_width > 0.0f)
128  {
129  Vector3 wind = -m_fusealge_front->Velocity;
130  float wspeed = wind.length();
131  Vector3 axis = m_fusealge_front->RelPosition - m_fusealge_back->RelPosition;
132  float s = axis.length() * m_fusealge_width;
133  float cz, cx, cm;
134  float v = axis.getRotationTo(wind).w;
135  float aoa = 0;
136  if (v < 1.0 && v > -1.0)
137  aoa = 2.0 * acos(v); //quaternion fun
138  m_fusealge_airfoil->getparams(aoa, 1.0, 0.0, &cz, &cx, &cm);
139 
140  //tropospheric model valid up to 11.000m (33.000ft)
141  float altitude = m_fusealge_front->AbsPosition.y;
142  float sea_level_pressure = 101325; //in Pa
143  float airpressure = sea_level_pressure * approx_pow(1.0 - 0.0065 * altitude / 288.1, 5.24947); //in Pa
144  float airdensity = airpressure * 0.0000120896f;//1.225 at sea level
145 
146  //fuselage as an airfoil + parasitic drag (half fuselage front surface almost as a flat plane!)
147  ar_fusedrag = ((cx * s + m_fusealge_width * m_fusealge_width * 0.5) * 0.5 * airdensity * wspeed / ar_num_nodes) * wind;
148  }
149 }
150 
151 void Actor::CalcBuoyance(bool doUpdate)
152 {
153  if (ar_num_buoycabs && App::GetGameContext()->GetTerrain()->getWater())
154  {
155  for (int i = 0; i < ar_num_buoycabs; i++)
156  {
157  int tmpv = ar_buoycabs[i] * 3;
158  m_buoyance->computeNodeForce(&ar_nodes[ar_cabs[tmpv]], &ar_nodes[ar_cabs[tmpv + 1]], &ar_nodes[ar_cabs[tmpv + 2]], doUpdate == 1, ar_buoycab_types[i]);
159  }
160  }
161 }
162 
163 void Actor::CalcDifferentials()
164 {
165  if (ar_engine && m_num_proped_wheels > 0)
166  {
167  float torque = ar_engine->GetTorque() / m_num_proped_wheels;
168  if (m_has_axles_section)
169  {
170  torque *= 2.0f; // Required to stay backwards compatible
171  }
172  for (int i = 0; i < ar_num_wheels; i++)
173  {
174  if (ar_wheels[i].wh_propulsed && !ar_wheels[i].wh_is_detached)
175  ar_wheels[i].wh_torque += torque;
176  }
177  }
178 
179  int num_axle_diffs = (m_transfer_case && m_transfer_case->tr_4wd_mode) ? m_num_axle_diffs + 1 : m_num_axle_diffs;
180 
181  // Handle detached wheels
182  for (int i = 0; i < num_axle_diffs; i++)
183  {
184  int a_1 = m_axle_diffs[i]->di_idx_1;
185  int a_2 = m_axle_diffs[i]->di_idx_2;
186  wheel_t* axle_1_wheels[2] = {&ar_wheels[m_wheel_diffs[a_1]->di_idx_1], &ar_wheels[m_wheel_diffs[a_1]->di_idx_2]};
187  wheel_t* axle_2_wheels[2] = {&ar_wheels[m_wheel_diffs[a_2]->di_idx_1], &ar_wheels[m_wheel_diffs[a_2]->di_idx_2]};
188  if (axle_1_wheels[0]->wh_is_detached && axle_1_wheels[1]->wh_is_detached)
189  {
190  axle_1_wheels[0]->wh_speed = axle_2_wheels[0]->wh_speed;
191  axle_1_wheels[1]->wh_speed = axle_2_wheels[1]->wh_speed;
192  }
193  if (axle_2_wheels[0]->wh_is_detached && axle_2_wheels[1]->wh_is_detached)
194  {
195  axle_2_wheels[0]->wh_speed = axle_1_wheels[0]->wh_speed;
196  axle_2_wheels[1]->wh_speed = axle_1_wheels[1]->wh_speed;
197  }
198  }
199  for (int i = 0; i < m_num_wheel_diffs; i++)
200  {
201  wheel_t* axle_wheels[2] = {&ar_wheels[m_wheel_diffs[i]->di_idx_1], &ar_wheels[m_wheel_diffs[i]->di_idx_2]};
202  if (axle_wheels[0]->wh_is_detached) axle_wheels[0]->wh_speed = axle_wheels[1]->wh_speed;
203  if (axle_wheels[1]->wh_is_detached) axle_wheels[1]->wh_speed = axle_wheels[0]->wh_speed;
204  }
205 
206  // loop through all interaxle differentials, this is the torsion to keep
207  // the axles aligned with each other as if they connected by a shaft
208  for (int i = 0; i < num_axle_diffs; i++)
209  {
210  int a_1 = m_axle_diffs[i]->di_idx_1;
211  int a_2 = m_axle_diffs[i]->di_idx_2;
212 
213  Ogre::Real axle_torques[2] = {0.0f, 0.0f};
214  DifferentialData diff_data =
215  {
216  {
217  (ar_wheels[m_wheel_diffs[a_1]->di_idx_1].wh_speed + ar_wheels[m_wheel_diffs[a_1]->di_idx_2].wh_speed) * 0.5f,
218  (ar_wheels[m_wheel_diffs[a_2]->di_idx_1].wh_speed + ar_wheels[m_wheel_diffs[a_2]->di_idx_2].wh_speed) * 0.5f
219  },
220  m_axle_diffs[i]->di_delta_rotation,
221  {axle_torques[0], axle_torques[1]},
222  ar_wheels[m_wheel_diffs[a_1]->di_idx_1].wh_torque + ar_wheels[m_wheel_diffs[a_1]->di_idx_2].wh_torque +
223  ar_wheels[m_wheel_diffs[a_2]->di_idx_1].wh_torque + ar_wheels[m_wheel_diffs[a_2]->di_idx_2].wh_torque,
224  PHYSICS_DT
225  };
226 
227  m_axle_diffs[i]->CalcAxleTorque(diff_data);
228 
229  m_axle_diffs[i]->di_delta_rotation = diff_data.delta_rotation;
230 
231  ar_wheels[m_wheel_diffs[a_1]->di_idx_1].wh_torque = diff_data.out_torque[0] * 0.5f;
232  ar_wheels[m_wheel_diffs[a_1]->di_idx_2].wh_torque = diff_data.out_torque[0] * 0.5f;
233  ar_wheels[m_wheel_diffs[a_2]->di_idx_1].wh_torque = diff_data.out_torque[1] * 0.5f;
234  ar_wheels[m_wheel_diffs[a_2]->di_idx_2].wh_torque = diff_data.out_torque[1] * 0.5f;
235  }
236 
237  // loop through all interwheel differentials, this is the torsion to keep
238  // the wheels aligned with each other as if they connected by a shaft
239  for (int i = 0; i < m_num_wheel_diffs; i++)
240  {
241  Ogre::Real axle_torques[2] = {0.0f, 0.0f};
242  wheel_t* axle_wheels[2] = {&ar_wheels[m_wheel_diffs[i]->di_idx_1], &ar_wheels[m_wheel_diffs[i]->di_idx_2]};
243 
244  DifferentialData diff_data =
245  {
246  {axle_wheels[0]->wh_speed, axle_wheels[1]->wh_speed},
247  m_wheel_diffs[i]->di_delta_rotation,
248  {axle_torques[0], axle_torques[1]},
249  axle_wheels[0]->wh_torque + axle_wheels[1]->wh_torque,
250  PHYSICS_DT
251  };
252 
253  m_wheel_diffs[i]->CalcAxleTorque(diff_data);
254 
255  m_wheel_diffs[i]->di_delta_rotation = diff_data.delta_rotation;
256 
257  ar_wheels[m_wheel_diffs[i]->di_idx_1].wh_torque = diff_data.out_torque[0];
258  ar_wheels[m_wheel_diffs[i]->di_idx_2].wh_torque = diff_data.out_torque[1];
259  }
260 }
261 
262 void Actor::CalcWheels(bool doUpdate, int num_steps)
263 {
264  // driving aids traction control & anti-lock brake pulse
265  tc_timer += PHYSICS_DT;
266  alb_timer += PHYSICS_DT;
267 
268  if (alb_timer >= alb_pulse_time)
269  {
270  alb_timer = 0.0f;
271  alb_pulse_state = !alb_pulse_state;
272  }
273  if (tc_timer >= tc_pulse_time)
274  {
275  tc_timer = 0.0f;
276  tc_pulse_state = !tc_pulse_state;
277  }
278 
279  m_antilockbrake = false;
280  m_tractioncontrol = false;
281 
282  ar_wheel_spin = 0.0f;
283  ar_wheel_speed = 0.0f;
284 
285  for (int i = 0; i < ar_num_wheels; i++)
286  {
287  if (doUpdate)
288  {
289  ar_wheels[i].debug_rpm = 0.0f;
290  ar_wheels[i].debug_torque = 0.0f;
291  ar_wheels[i].debug_vel = Vector3::ZERO;
292  ar_wheels[i].debug_slip = Vector3::ZERO;
293  ar_wheels[i].debug_force = Vector3::ZERO;
294  ar_wheels[i].debug_scaled_cforce = Vector3::ZERO;
295  }
296 
297  if (ar_wheels[i].wh_is_detached)
298  continue;
299 
300  float relspeed = ar_nodes[0].Velocity.dotProduct(getDirection());
301  float curspeed = fabs(relspeed);
302  float wheel_slip = fabs(ar_wheels[i].wh_speed - relspeed) / std::max(1.0f, curspeed);
303 
304  // traction control
305  if (tc_mode && fabs(ar_wheels[i].wh_torque) > 0.0f && fabs(ar_wheels[i].wh_speed) > curspeed && wheel_slip > 0.25f)
306  {
307  if (tc_pulse_state)
308  {
309  ar_wheels[i].wh_tc_coef = curspeed / fabs(ar_wheels[i].wh_speed);
310  ar_wheels[i].wh_tc_coef = pow(ar_wheels[i].wh_tc_coef, tc_ratio);
311  }
312  float tc_coef = pow(ar_wheels[i].wh_tc_coef, std::min(std::abs(ar_wheels[i].wh_speed) / 5.0f, 1.0f));
313  ar_wheels[i].wh_torque *= tc_coef;
314  m_tractioncontrol = true;
315  }
316  else
317  {
318  ar_wheels[i].wh_tc_coef = 1.0f;
319  }
320 
321  if (ar_wheels[i].wh_braking != wheel_t::BrakeCombo::NONE)
322  {
323  // footbrake
324  float abrake = ar_brake_force * ar_brake;
325 
326  // handbrake
327  float hbrake = 0.0f;
328  if (ar_parking_brake && (ar_wheels[i].wh_braking != wheel_t::BrakeCombo::FOOT_ONLY))
329  {
330  hbrake = m_handbrake_force;
331  }
332 
333  // directional braking
334  float dbrake = 0.0f;
335  if ((ar_wheels[i].wh_speed < 20.0f)
336  && (((ar_wheels[i].wh_braking == wheel_t::BrakeCombo::FOOT_HAND_SKID_LEFT) && (ar_hydro_dir_state > 0.0f))
337  || ((ar_wheels[i].wh_braking == wheel_t::BrakeCombo::FOOT_HAND_SKID_RIGHT) && (ar_hydro_dir_state < 0.0f))))
338  {
339  dbrake = ar_brake_force * abs(ar_hydro_dir_state);
340  }
341 
342  if (abrake != 0.0 || dbrake != 0.0 || hbrake != 0.0)
343  {
344  float adbrake = abrake + dbrake;
345 
346  // anti-lock braking
347  if (alb_mode && curspeed > alb_minspeed && curspeed > fabs(ar_wheels[i].wh_speed) && (adbrake > 0.0f) && wheel_slip > 0.25f)
348  {
349  if (alb_pulse_state)
350  {
351  ar_wheels[i].wh_alb_coef = fabs(ar_wheels[i].wh_speed) / curspeed;
352  ar_wheels[i].wh_alb_coef = pow(ar_wheels[i].wh_alb_coef, alb_ratio);
353  }
354  adbrake *= ar_wheels[i].wh_alb_coef;
355  m_antilockbrake = true;
356  }
357 
358  float force = -ar_wheels[i].wh_avg_speed * ar_wheels[i].wh_radius * ar_wheels[i].wh_mass / PHYSICS_DT;
359  force -= ar_wheels[i].wh_last_retorque;
360 
361  if (ar_wheels[i].wh_speed > 0)
362  ar_wheels[i].wh_torque += Math::Clamp(force, -(adbrake + hbrake), 0.0f);
363  else
364  ar_wheels[i].wh_torque += Math::Clamp(force, 0.0f, +(adbrake + hbrake));
365  }
366  else
367  {
368  ar_wheels[i].wh_alb_coef = 1.0f;
369  }
370  }
371 
372  ar_wheels[i].debug_torque += ar_wheels[i].wh_torque / (float)num_steps;
373 
374  // application to wheel
375  Vector3 axis = (ar_wheels[i].wh_axis_node_1->RelPosition - ar_wheels[i].wh_axis_node_0->RelPosition).normalisedCopy();
376  float axis_precalc = ar_wheels[i].wh_torque / (Real)(ar_wheels[i].wh_num_nodes);
377 
378  float expected_wheel_speed = ar_wheels[i].wh_speed;
379  ar_wheels[i].wh_speed = 0.0f;
380 
381  Real contact_counter = 0.0f;
382  Vector3 slip = Vector3::ZERO;
383  Vector3 force = Vector3::ZERO;
384  for (int j = 0; j < ar_wheels[i].wh_num_nodes; j++)
385  {
386  node_t* outer_node = ar_wheels[i].wh_nodes[j];
387  node_t* inner_node = (j % 2) ? ar_wheels[i].wh_axis_node_1 : ar_wheels[i].wh_axis_node_0;
388 
389  Vector3 radius = outer_node->RelPosition - inner_node->RelPosition;
390  float inverted_rlen = 1.0f / radius.length();
391 
392  if (ar_wheels[i].wh_propulsed == 2)
393  {
394  radius = -radius;
395  }
396 
397  Vector3 dir = axis.crossProduct(radius) * inverted_rlen;
398  ar_wheels[i].wh_nodes[j]->Forces += dir * axis_precalc * inverted_rlen;
399  ar_wheels[i].wh_speed += (outer_node->Velocity - inner_node->Velocity).dotProduct(dir);
400 
401  if (ar_wheels[i].wh_nodes[j]->nd_has_ground_contact || ar_wheels[i].wh_nodes[j]->nd_has_mesh_contact)
402  {
403  contact_counter += 1.0f;
404  float force_ratio = ar_wheels[i].wh_nodes[j]->nd_last_collision_force.length();
405  slip += ar_wheels[i].wh_nodes[j]->nd_last_collision_slip * force_ratio;
406  force += ar_wheels[i].wh_nodes[j]->nd_last_collision_force;
407  }
408  }
409  if (contact_counter > 0.0f && !force.isZeroLength())
410  {
411  slip /= force.length(); // slip vector weighted by down force
412  slip /= contact_counter; // average slip vector
413  force /= contact_counter; // average force vector
414  Vector3 normal = force.normalisedCopy(); // contact plane normal
415  Vector3 v = ar_wheels[i].wh_axis_node_0->Velocity.midPoint(ar_wheels[i].wh_axis_node_1->Velocity);
416  Vector3 vel = v - v.dotProduct(normal) * normal;
417  ar_wheels[i].debug_vel += vel / (float)num_steps;
418  ar_wheels[i].debug_slip += slip / (float)num_steps;
419  ar_wheels[i].debug_force += force / (float)num_steps;
420  }
421 
422  ar_wheels[i].wh_speed /= (Real)ar_wheels[i].wh_num_nodes;
423  ar_wheels[i].wh_net_rp += (ar_wheels[i].wh_speed / ar_wheels[i].wh_radius) * PHYSICS_DT;
424  // We overestimate the average speed on purpose in order to improve the quality of the braking force estimate
425  ar_wheels[i].wh_avg_speed = ar_wheels[i].wh_avg_speed * 0.99 + ar_wheels[i].wh_speed * 0.1;
426  ar_wheels[i].debug_rpm += RAD_PER_SEC_TO_RPM * ar_wheels[i].wh_speed / ar_wheels[i].wh_radius / (float)num_steps;
427  if (ar_wheels[i].wh_propulsed == 1)
428  {
429  float speedacc = ar_wheels[i].wh_speed / (float)m_num_proped_wheels;
430  ar_wheel_speed += speedacc; // Accumulate the average wheel speed (m/s)
431  ar_wheel_spin += speedacc / ar_wheels[i].wh_radius; // Accumulate the average wheel spin (radians)
432  }
433 
434  expected_wheel_speed += ((ar_wheels[i].wh_last_torque / ar_wheels[i].wh_radius) / ar_wheels[i].wh_mass) * PHYSICS_DT;
435  ar_wheels[i].wh_last_retorque = ar_wheels[i].wh_mass * (ar_wheels[i].wh_speed - expected_wheel_speed) / PHYSICS_DT;
436 
437  // reaction torque
438  Vector3 rradius = ar_wheels[i].wh_arm_node->RelPosition - ar_wheels[i].wh_near_attach_node->RelPosition;
439  Vector3 radius = Plane(axis, ar_wheels[i].wh_near_attach_node->RelPosition).projectVector(rradius);
440  float offset = (rradius - radius).length(); // length of the error arm
441  Real rlen = radius.normalise(); // length of the projected arm
442  // TODO: Investigate the offset length abort condition ~ ulteq 10/2018
443  if (rlen > 0.01 && offset * 2.0f < rlen && fabs(ar_wheels[i].wh_torque) > 0.01f)
444  {
445  Vector3 cforce = axis.crossProduct(radius);
446  // modulate the force according to induced torque error
447  cforce *= (0.5f * ar_wheels[i].wh_torque / rlen) * (1.0f - ((offset * 2.0f) / rlen)); // linear modulation
448  ar_wheels[i].wh_arm_node->Forces -= cforce;
449  ar_wheels[i].wh_near_attach_node->Forces += cforce;
450  ar_wheels[i].debug_scaled_cforce += cforce / m_total_mass / (float)num_steps;
451  }
452 
453  ar_wheels[i].wh_last_torque = ar_wheels[i].wh_torque;
454  ar_wheels[i].wh_torque = 0.0f;
455  }
456 
457  ar_avg_wheel_speed = ar_avg_wheel_speed * 0.995 + ar_wheel_speed * 0.005;
458 
459  if (ar_engine)
460  {
461  ar_engine->SetWheelSpin(ar_wheel_spin * RAD_PER_SEC_TO_RPM); // Update the driveshaft speed
462  }
463 
464  if (doUpdate)
465  {
466  if (!m_antilockbrake)
467  {
468  SOUND_STOP(ar_instance_id, SS_TRIG_ALB_ACTIVE);
469  }
470  else
471  {
472  SOUND_START(ar_instance_id, SS_TRIG_ALB_ACTIVE);
473  }
474 
475  if (!m_tractioncontrol)
476  {
477  SOUND_STOP(ar_instance_id, SS_TRIG_TC_ACTIVE);
478  }
479  else
480  {
481  SOUND_START(ar_instance_id, SS_TRIG_TC_ACTIVE);
482  }
483  }
484 
485  // calculate driven distance
486  float distance_driven = fabs(ar_wheel_speed * PHYSICS_DT);
487  m_odometer_total += distance_driven;
488  m_odometer_user += distance_driven;
489 }
490 
491 void Actor::CalcShocks(bool doUpdate, int num_steps)
492 {
493  //variable shocks for stabilization
494  if (this->ar_has_active_shocks && m_stabilizer_shock_request)
495  {
496  if ((m_stabilizer_shock_request == 1 && m_stabilizer_shock_ratio < 0.1) || (m_stabilizer_shock_request == -1 && m_stabilizer_shock_ratio > -0.1))
497  m_stabilizer_shock_ratio = m_stabilizer_shock_ratio + (float)m_stabilizer_shock_request * PHYSICS_DT * STAB_RATE;
498  for (int i = 0; i < ar_num_shocks; i++)
499  {
500  // active shocks now
501  if (ar_shocks[i].flags & SHOCK_FLAG_RACTIVE)
502  ar_beams[ar_shocks[i].beamid].L = ar_beams[ar_shocks[i].beamid].refL * (1.0 + m_stabilizer_shock_ratio);
503  else if (ar_shocks[i].flags & SHOCK_FLAG_LACTIVE)
504  ar_beams[ar_shocks[i].beamid].L = ar_beams[ar_shocks[i].beamid].refL * (1.0 - m_stabilizer_shock_ratio);
505  }
506  }
507  //auto shock adjust
508  if (this->ar_has_active_shocks && doUpdate)
509  {
510  m_stabilizer_shock_sleep -= PHYSICS_DT * num_steps;
511 
512  float roll = asin(GetCameraRoll().dotProduct(Vector3::UNIT_Y));
513  //mWindow->setDebugText("Roll:"+ TOSTRING(roll));
514  if (fabs(roll) > 0.2)
515  {
516  m_stabilizer_shock_sleep = -1.0; //emergency timeout stop
517  }
518  if (fabs(roll) > 0.01 && m_stabilizer_shock_sleep < 0.0)
519  {
520  if (roll > 0.0 && m_stabilizer_shock_request != -1)
521  {
522  m_stabilizer_shock_request = 1;
523  }
524  else if (roll < 0.0 && m_stabilizer_shock_request != 1)
525  {
526  m_stabilizer_shock_request = -1;
527  }
528  else
529  {
530  m_stabilizer_shock_request = 0;
531  m_stabilizer_shock_sleep = 3.0;
532  }
533  }
534  else
535  {
536  m_stabilizer_shock_request = 0;
537  }
538 
539  if (m_stabilizer_shock_request && fabs(m_stabilizer_shock_ratio) < 0.1)
540  SOUND_START(ar_instance_id, SS_TRIG_AIR);
541  else
542  SOUND_STOP(ar_instance_id, SS_TRIG_AIR);
543  }
544 }
545 
546 void Actor::CalcHydros()
547 {
548  //direction
549  if (ar_hydro_dir_state != 0 || ar_hydro_dir_command != 0)
550  {
551  if (!ar_hydro_speed_coupling)
552  {
553  // need a maximum rate for analog devices, otherwise hydro beams break
554  float smoothing = Math::Clamp(App::io_analog_smoothing->getFloat(), 0.5f, 2.0f);
555  float sensitivity = Math::Clamp(App::io_analog_sensitivity->getFloat(), 0.5f, 2.0f);
556  float diff = ar_hydro_dir_command - ar_hydro_dir_state;
557  float rate = std::exp(-std::min(std::abs(diff), 1.0f) / sensitivity) * diff;
558  ar_hydro_dir_state += (10.0f / smoothing) * PHYSICS_DT * rate;
559  }
560  else
561  {
562  if (ar_hydro_dir_command != 0)
563  {
564  if (!App::io_hydro_coupling->getBool())
565  {
566  float rate = std::max(1.2f, 30.0f / (10.0f));
567  if (ar_hydro_dir_state > ar_hydro_dir_command)
568  ar_hydro_dir_state -= PHYSICS_DT * rate;
569  else
570  ar_hydro_dir_state += PHYSICS_DT * rate;
571  }
572  else
573  {
574  // minimum rate: 20% --> enables to steer high velocity vehicles
575  float rate = std::max(1.2f, 30.0f / (10.0f + std::abs(ar_wheel_speed / 2.0f)));
576  if (ar_hydro_dir_state > ar_hydro_dir_command)
577  ar_hydro_dir_state -= PHYSICS_DT * rate;
578  else
579  ar_hydro_dir_state += PHYSICS_DT * rate;
580  }
581  }
582  float dirdelta = PHYSICS_DT;
583  if (ar_hydro_dir_state > dirdelta)
584  ar_hydro_dir_state -= dirdelta;
585  else if (ar_hydro_dir_state < -dirdelta)
586  ar_hydro_dir_state += dirdelta;
587  else
588  ar_hydro_dir_state = 0;
589  }
590  }
591  //aileron
592  if (ar_hydro_aileron_state != 0 || ar_hydro_aileron_command != 0)
593  {
594  if (ar_hydro_aileron_command != 0)
595  {
596  if (ar_hydro_aileron_state > ar_hydro_aileron_command)
597  ar_hydro_aileron_state -= PHYSICS_DT * 4.0;
598  else
599  ar_hydro_aileron_state += PHYSICS_DT * 4.0;
600  }
601  float delta = PHYSICS_DT;
602  if (ar_hydro_aileron_state > delta)
603  ar_hydro_aileron_state -= delta;
604  else if (ar_hydro_aileron_state < -delta)
605  ar_hydro_aileron_state += delta;
606  else
607  ar_hydro_aileron_state = 0;
608  }
609  //rudder
610  if (ar_hydro_rudder_state != 0 || ar_hydro_rudder_command != 0)
611  {
612  if (ar_hydro_rudder_command != 0)
613  {
614  if (ar_hydro_rudder_state > ar_hydro_rudder_command)
615  ar_hydro_rudder_state -= PHYSICS_DT * 4.0;
616  else
617  ar_hydro_rudder_state += PHYSICS_DT * 4.0;
618  }
619 
620  float delta = PHYSICS_DT;
621  if (ar_hydro_rudder_state > delta)
622  ar_hydro_rudder_state -= delta;
623  else if (ar_hydro_rudder_state < -delta)
624  ar_hydro_rudder_state += delta;
625  else
626  ar_hydro_rudder_state = 0;
627  }
628  //elevator
629  if (ar_hydro_elevator_state != 0 || ar_hydro_elevator_command != 0)
630  {
631  if (ar_hydro_elevator_command != 0)
632  {
633  if (ar_hydro_elevator_state > ar_hydro_elevator_command)
634  ar_hydro_elevator_state -= PHYSICS_DT * 4.0;
635  else
636  ar_hydro_elevator_state += PHYSICS_DT * 4.0;
637  }
638  float delta = PHYSICS_DT;
639  if (ar_hydro_elevator_state > delta)
640  ar_hydro_elevator_state -= delta;
641  else if (ar_hydro_elevator_state < -delta)
642  ar_hydro_elevator_state += delta;
643  else
644  ar_hydro_elevator_state = 0;
645  }
646  //update length, dirstate between -1.0 and 1.0
647  const int num_hydros = static_cast<int>(ar_hydros.size());
648  for (int i = 0; i < num_hydros; ++i)
649  {
650  hydrobeam_t& hydrobeam = ar_hydros[i];
651 
652  //compound hydro
653  float cstate = 0.0f;
654  int div = 0;
655  if (hydrobeam.hb_flags & HYDRO_FLAG_SPEED)
656  {
657  //special treatment for SPEED
658  if (ar_wheel_speed < 12.0f)
659  cstate += ar_hydro_dir_state * (12.0f - ar_wheel_speed) / 12.0f;
660  div++;
661  }
662  if (hydrobeam.hb_flags & HYDRO_FLAG_DIR)
663  {
664  cstate += ar_hydro_dir_state;
665  div++;
666  }
667  if (hydrobeam.hb_flags & HYDRO_FLAG_AILERON)
668  {
669  cstate += ar_hydro_aileron_state;
670  div++;
671  }
672  if (hydrobeam.hb_flags & HYDRO_FLAG_RUDDER)
673  {
674  cstate += ar_hydro_rudder_state;
675  div++;
676  }
677  if (hydrobeam.hb_flags & HYDRO_FLAG_ELEVATOR)
678  {
679  cstate += ar_hydro_elevator_state;
680  div++;
681  }
682  if (hydrobeam.hb_flags & HYDRO_FLAG_REV_AILERON)
683  {
684  cstate -= ar_hydro_aileron_state;
685  div++;
686  }
687  if (hydrobeam.hb_flags & HYDRO_FLAG_REV_RUDDER)
688  {
689  cstate -= ar_hydro_rudder_state;
690  div++;
691  }
692  if (hydrobeam.hb_flags & HYDRO_FLAG_REV_ELEVATOR)
693  {
694  cstate -= ar_hydro_elevator_state;
695  div++;
696  }
697 
698  const uint16_t beam_idx = hydrobeam.hb_beam_index;
699 
700  if (cstate > 1.0)
701  cstate = 1.0;
702  if (cstate < -1.0)
703  cstate = -1.0;
704 
705  // Animators
706  if (hydrobeam.hb_anim_flags)
707  {
708  this->CalcAnimators(hydrobeam, cstate, div);
709  }
710 
711  // Final composition
712  if (div)
713  {
714  cstate /= (float)div;
715 
716  cstate = hydrobeam.hb_inertia.CalcCmdKeyDelay(cstate, PHYSICS_DT);
717 
718  if (!(hydrobeam.hb_flags & HYDRO_FLAG_SPEED) && !hydrobeam.hb_anim_flags)
719  ar_hydro_dir_wheel_display = cstate;
720 
721  float factor = 1.0 - cstate * hydrobeam.hb_speed;
722 
723  // check and apply animators limits if set
724  if (hydrobeam.hb_anim_flags)
725  {
726  if (factor < 1.0f - ar_beams[beam_idx].shortbound)
727  factor = 1.0f - ar_beams[beam_idx].shortbound;
728  if (factor > 1.0f + ar_beams[beam_idx].longbound)
729  factor = 1.0f + ar_beams[beam_idx].longbound;
730  }
731 
732  ar_beams[beam_idx].L = hydrobeam.hb_ref_length * factor;
733  }
734  }
735 }
736 
737 void Actor::CalcCommands(bool doUpdate)
738 {
739  if (m_has_command_beams)
740  {
741  int active = 0;
742  bool requested = false;
743  float work = 0.0;
744 
745  // hydraulics ready?
746  if (ar_engine)
747  ar_engine_hydraulics_ready = ar_engine->GetEngineRpm() > ar_engine->getIdleRPM() * 0.95f;
748  else
749  ar_engine_hydraulics_ready = true;
750 
751  // crankfactor
752  float crankfactor = 1.0f;
753  if (ar_engine)
754  crankfactor = ar_engine->GetCrankFactor();
755 
756  // speed up machines
757  if (ar_driveable == MACHINE)
758  crankfactor = 2;
759 
760  for (int i = 1; i <= MAX_COMMANDS; i++) // BEWARE: commandkeys are indexed 1-MAX_COMMANDS!
761  {
762  for (int j = 0; j < (int)ar_command_key[i].beams.size(); j++)
763  {
764  ar_command_key[i].beams[j].cmb_state->auto_move_lock = false;
765  }
766  }
767 
768  for (int i = 1; i <= MAX_COMMANDS; i++) // BEWARE: commandkeys are indexed 1-MAX_COMMANDS!
769  {
770  float oldValue = ar_command_key[i].commandValue;
771 
772  ar_command_key[i].commandValue = std::max(ar_command_key[i].playerInputValue, ar_command_key[i].triggerInputValue);
773 
774  ar_command_key[i].triggerInputValue = 0.0f;
775 
776  if (ar_command_key[i].commandValue > 0.01f && oldValue < 0.01f)
777  {
778  // just started
779  ar_command_key[i].commandValueState = 1;
780  }
781  else if (ar_command_key[i].commandValue < 0.01f && oldValue > 0.01f)
782  {
783  // just stopped
784  ar_command_key[i].commandValueState = -1;
785  }
786 
787  for (int j = 0; j < (int)ar_command_key[i].beams.size(); j++)
788  {
789  if (ar_command_key[i].commandValue >= 0.5)
790  {
791  ar_command_key[i].beams[j].cmb_state->auto_move_lock = true;
792  if (ar_command_key[i].beams[j].cmb_is_autocentering)
793  {
794  ar_command_key[i].beams[j].cmb_state->auto_moving_mode = 0;
795  }
796  }
797  }
798  }
799 
800  // now process normal commands
801  for (int i = 1; i <= MAX_COMMANDS; i++) // BEWARE: commandkeys are indexed 1-MAX_COMMANDS!
802  {
803  bool requestpower = false;
804  for (int j = 0; j < (int)ar_command_key[i].beams.size(); j++)
805  {
806  commandbeam_t& cmd_beam = ar_command_key[i].beams[j];
807  int bbeam_dir = (cmd_beam.cmb_is_contraction) ? -1 : 1;
808  int bbeam = cmd_beam.cmb_beam_index;
809 
810  if (bbeam > ar_num_beams)
811  continue;
812 
813  // restrict forces
814  if (cmd_beam.cmb_is_force_restricted)
815  crankfactor = std::min(crankfactor, 1.0f);
816 
817  float v = ar_command_key[i].commandValue;
818  int& vst = ar_command_key[i].commandValueState;
819 
820  // self centering
821  if (cmd_beam.cmb_is_autocentering && !cmd_beam.cmb_state->auto_move_lock)
822  {
823  // check for some error
824  if (ar_beams[bbeam].refL == 0 || ar_beams[bbeam].L == 0)
825  continue;
826 
827  float current = (ar_beams[bbeam].L / ar_beams[bbeam].refL);
828 
829  if (fabs(current - cmd_beam.cmb_center_length) < 0.0001)
830  {
831  // hold condition
832  cmd_beam.cmb_state->auto_moving_mode = 0;
833  }
834  else
835  {
836  int mode = cmd_beam.cmb_state->auto_moving_mode;
837 
838  // determine direction
839  if (current > cmd_beam.cmb_center_length)
840  cmd_beam.cmb_state->auto_moving_mode = -1;
841  else
842  cmd_beam.cmb_state->auto_moving_mode = 1;
843 
844  // avoid overshooting
845  if (mode != 0 && mode != cmd_beam.cmb_state->auto_moving_mode)
846  {
847  ar_beams[bbeam].L = cmd_beam.cmb_center_length * ar_beams[bbeam].refL;
848  cmd_beam.cmb_state->auto_moving_mode = 0;
849  }
850  }
851  }
852 
853  if (ar_beams[bbeam].refL != 0 && ar_beams[bbeam].L != 0)
854  {
855  float clen = ar_beams[bbeam].L / ar_beams[bbeam].refL;
856  if ((bbeam_dir > 0 && clen < cmd_beam.cmb_boundary_length) || (bbeam_dir < 0 && clen > cmd_beam.cmb_boundary_length))
857  {
858  float dl = ar_beams[bbeam].L;
859 
860  if (cmd_beam.cmb_is_1press_center)
861  {
862  // one press + centering
863  if (bbeam_dir * cmd_beam.cmb_state->auto_moving_mode > 0 && bbeam_dir * clen > bbeam_dir * cmd_beam.cmb_center_length && !cmd_beam.cmb_state->pressed_center_mode)
864  {
865  cmd_beam.cmb_state->pressed_center_mode = true;
866  cmd_beam.cmb_state->auto_moving_mode = 0;
867  }
868  else if (bbeam_dir * cmd_beam.cmb_state->auto_moving_mode < 0 && bbeam_dir * clen > bbeam_dir * cmd_beam.cmb_center_length && cmd_beam.cmb_state->pressed_center_mode)
869  {
870  cmd_beam.cmb_state->pressed_center_mode = false;
871  }
872  }
873  if (cmd_beam.cmb_is_1press || cmd_beam.cmb_is_1press_center)
874  {
875  bool key = (v > 0.5);
876  if (bbeam_dir * cmd_beam.cmb_state->auto_moving_mode <= 0 && key)
877  {
878  cmd_beam.cmb_state->auto_moving_mode = bbeam_dir * 1;
879  }
880  else if (cmd_beam.cmb_state->auto_moving_mode == bbeam_dir * 1 && !key)
881  {
882  cmd_beam.cmb_state->auto_moving_mode = bbeam_dir * 2;
883  }
884  else if (cmd_beam.cmb_state->auto_moving_mode == bbeam_dir * 2 && key)
885  {
886  cmd_beam.cmb_state->auto_moving_mode = bbeam_dir * 3;
887  }
888  else if (cmd_beam.cmb_state->auto_moving_mode == bbeam_dir * 3 && !key)
889  {
890  cmd_beam.cmb_state->auto_moving_mode = 0;
891  }
892  }
893 
894  v = ar_command_key[i].command_inertia.CalcCmdKeyDelay(v, PHYSICS_DT);
895 
896  if (bbeam_dir * cmd_beam.cmb_state->auto_moving_mode > 0)
897  v = 1;
898 
899  if (cmd_beam.cmb_needs_engine && ((ar_engine && !ar_engine->isRunning()) || !ar_engine_hydraulics_ready))
900  continue;
901 
902  if (v > 0.0f && cmd_beam.cmb_engine_coupling > 0.0f)
903  requestpower = true;
904 
905 #ifdef USE_OPENAL
906  if (cmd_beam.cmb_plays_sound)
907  {
908  // command sounds
909  if (vst == 1)
910  {
911  // just started
914  vst = 0;
915  }
916  else if (vst == -1)
917  {
918  // just stopped
920  vst = 0;
921  }
922  else if (vst == 0)
923  {
924  // already running, modulate
926  }
927  }
928 #endif //USE_OPENAL
929  float cf = 1.0f;
930 
931  if (cmd_beam.cmb_engine_coupling > 0)
932  cf = crankfactor;
933 
934  if (bbeam_dir > 0)
935  ar_beams[bbeam].L *= (1.0 + cmd_beam.cmb_speed * v * cf * PHYSICS_DT / ar_beams[bbeam].L);
936  else
937  ar_beams[bbeam].L *= (1.0 - cmd_beam.cmb_speed * v * cf * PHYSICS_DT / ar_beams[bbeam].L);
938 
939  dl = fabs(dl - ar_beams[bbeam].L);
940  if (requestpower)
941  {
942  active++;
943  work += fabs(ar_beams[bbeam].stress) * dl * cmd_beam.cmb_engine_coupling;
944  }
945  }
946  else if ((cmd_beam.cmb_is_1press || cmd_beam.cmb_is_1press_center) && bbeam_dir * cmd_beam.cmb_state->auto_moving_mode > 0)
947  {
948  // beyond length
949  cmd_beam.cmb_state->auto_moving_mode = 0;
950  }
951  }
952  }
953  // also for rotators
954  for (int j = 0; j < (int)ar_command_key[i].rotators.size(); j++)
955  {
956  float v = 0.0f;
957  int rota = std::abs(ar_command_key[i].rotators[j]) - 1;
958 
959  if (ar_rotators[rota].needs_engine && ((ar_engine && !ar_engine->isRunning()) || !ar_engine_hydraulics_ready))
960  continue;
961 
962  v = ar_command_key[i].rotator_inertia.CalcCmdKeyDelay(ar_command_key[i].commandValue, PHYSICS_DT);
963 
964  if (v > 0.0f && ar_rotators[rota].engine_coupling > 0.0f)
965  requestpower = true;
966 
967  float cf = 1.0f;
968 
969  if (ar_rotators[rota].engine_coupling > 0.0f)
970  cf = crankfactor;
971 
972  if (ar_command_key[i].rotators[j] > 0)
973  ar_rotators[rota].angle += ar_rotators[rota].rate * v * cf * PHYSICS_DT;
974  else
975  ar_rotators[rota].angle -= ar_rotators[rota].rate * v * cf * PHYSICS_DT;
976 
977  if (doUpdate || v != 0.0f)
978  {
979  ar_rotators[rota].debug_rate = ar_rotators[rota].rate * v * cf;
980  }
981  }
982  if (requestpower)
983  requested=true;
984  }
985 
986  if (ar_engine)
987  {
988  ar_engine->SetHydroPumpWork(work);
989  ar_engine->SetEnginePriming(requested);
990  }
991 
992  if (doUpdate && this == App::GetGameContext()->GetPlayerActor().GetRef())
993  {
994 #ifdef USE_OPENAL
995  if (active > 0)
996  {
997  SOUND_START(ar_instance_id, SS_TRIG_PUMP);
998  float pump_rpm = 660.0f * (1.0f - (work / (float)active) / 100.0f);
999  SOUND_MODULATE(ar_instance_id, SS_MOD_PUMP, pump_rpm);
1000  }
1001  else
1002  {
1003  SOUND_STOP(ar_instance_id, SS_TRIG_PUMP);
1004  }
1005 #endif //USE_OPENAL
1006  }
1007  // rotators
1008  for (int i = 0; i < ar_num_rotators; i++)
1009  {
1010  // compute rotation axis
1011  Vector3 ax1 = ar_nodes[ar_rotators[i].axis1].RelPosition;
1012  Vector3 ax2 = ar_nodes[ar_rotators[i].axis2].RelPosition;
1013  Vector3 axis = ax1 - ax2;
1014  axis.normalise();
1015  // find the reference plane
1016  Plane pl = Plane(axis, 0);
1017  // for each pairar
1018  ar_rotators[i].debug_aerror = 0;
1019  for (int k = 0; k < 2; k++)
1020  {
1021  // find the reference vectors
1022  Vector3 ref1 = pl.projectVector(ax1 - ar_nodes[ar_rotators[i].nodes1[k]].RelPosition);
1023  Vector3 ref2 = pl.projectVector(ax2 - ar_nodes[ar_rotators[i].nodes2[k]].RelPosition);
1024  float ref1len = ref1.normalise();
1025  float ref2len = ref2.normalise();
1026  // theory vector
1027  Vector3 th1 = Quaternion(Radian(ar_rotators[i].angle + Math::HALF_PI), axis) * ref1;
1028  // find the angle error
1029  float aerror = asin(th1.dotProduct(ref2));
1030  ar_rotators[i].debug_aerror += 0.5f * aerror;
1031  // exert forces
1032  float rigidity = ar_rotators[i].force;
1033  Vector3 dir1 = ref1.crossProduct(axis);
1034  Vector3 dir2 = ref2.crossProduct(axis);
1035 
1036  // simple jitter fix
1037  if (ref1len <= ar_rotators[i].tolerance)
1038  ref1len = 0.0f;
1039  if (ref2len <= ar_rotators[i].tolerance)
1040  ref2len = 0.0f;
1041 
1042  ar_nodes[ar_rotators[i].nodes1[k ]].Forces += (aerror * ref1len * rigidity) * dir1;
1043  ar_nodes[ar_rotators[i].nodes2[k ]].Forces -= (aerror * ref2len * rigidity) * dir2;
1044  // symmetric
1045  ar_nodes[ar_rotators[i].nodes1[k + 2]].Forces -= (aerror * ref1len * rigidity) * dir1;
1046  ar_nodes[ar_rotators[i].nodes2[k + 2]].Forces += (aerror * ref2len * rigidity) * dir2;
1047  }
1048  }
1049  }
1050 }
1051 
1052 void Actor::CalcTies()
1053 {
1054  // go through all ties and process them
1055  for (std::vector<tie_t>::iterator it = ar_ties.begin(); it != ar_ties.end(); it++)
1056  {
1057  // only process tying ties
1058  if (!it->ti_tying)
1059  continue;
1060 
1061  // division through zero guard
1062  if (it->ti_beam->refL == 0 || it->ti_beam->L == 0)
1063  continue;
1064 
1065  float clen = it->ti_beam->L / it->ti_beam->refL;
1066  if (clen > it->ti_min_length)
1067  {
1068  it->ti_beam->L *= (1.0 - it->ti_contract_speed * PHYSICS_DT / it->ti_beam->L);
1069  }
1070  else
1071  {
1072  // tying finished, end reached
1073  it->ti_tying = false;
1074  }
1075 
1076  // check if we hit a certain force limit, then abort the tying process
1077  if (fabs(it->ti_beam->stress) > it->ti_max_stress)
1078  {
1079  it->ti_tying = false;
1080  }
1081  }
1082 }
1083 void Actor::CalcTruckEngine(bool doUpdate)
1084 {
1085  if (ar_engine)
1086  {
1087  ar_engine->UpdateEngineSim(PHYSICS_DT, doUpdate);
1088  }
1089 }
1090 
1091 void Actor::CalcReplay()
1092 {
1093  if (m_replay_handler && m_replay_handler->isValid())
1094  {
1095  m_replay_handler->onPhysicsStep();
1096  }
1097 }
1098 
1099 bool Actor::CalcForcesEulerPrepare(bool doUpdate)
1100 {
1101  if (m_ongoing_reset)
1102  return false;
1103  if (ar_physics_paused)
1104  return false;
1105  if (ar_state != ActorState::LOCAL_SIMULATED)
1106  return false;
1107 
1108  if (doUpdate)
1109  {
1110  //this->hookToggle(-2, HOOK_LOCK, -1);
1112  rq->alr_type = ActorLinkingRequestType::HOOK_ACTION;
1113  rq->alr_actor_instance_id = ar_instance_id;
1114  rq->alr_hook_action = HOOK_LOCK;
1115  rq->alr_hook_group = -2;
1117  }
1118 
1119  this->CalcHooks();
1120  this->CalcRopes();
1121 
1122  return true;
1123 }
1124 
1125 template <size_t L>
1126 void LogNodeId(RoR::Str<L>& msg, node_t* node) // Internal helper
1127 {
1128  msg << " (index: " << node->pos << ")";
1129 }
1130 
1131 template <size_t L>
1132 void LogBeamNodes(RoR::Str<L>& msg, beam_t& beam) // Internal helper
1133 {
1134  msg << "It was between nodes ";
1135  LogNodeId(msg, beam.p1);
1136  msg << " and ";
1137  LogNodeId(msg, beam.p2);
1138  msg << ".";
1139 }
1140 
1141 void Actor::CalcBeams(bool trigger_hooks)
1142 {
1143  for (int i = 0; i < ar_num_beams; i++)
1144  {
1145  if (!ar_beams[i].bm_disabled && !ar_beams[i].bm_inter_actor)
1146  {
1147  // Calculate beam length
1148  Vector3 dis = ar_beams[i].p1->RelPosition - ar_beams[i].p2->RelPosition;
1149 
1150  Real dislen = dis.squaredLength();
1151  Real inverted_dislen = fast_invSqrt(dislen);
1152 
1153  dislen *= inverted_dislen;
1154 
1155  // Calculate beam's deviation from normal
1156  Real difftoBeamL = dislen - ar_beams[i].L;
1157 
1158  Real k = ar_beams[i].k;
1159  Real d = ar_beams[i].d;
1160 
1161  // Calculate beam's rate of change
1162  float v = (ar_beams[i].p1->Velocity - ar_beams[i].p2->Velocity).dotProduct(dis) * inverted_dislen;
1163 
1164  if (ar_beams[i].bounded == SHOCK1)
1165  {
1166  float interp_ratio = 0.0f;
1167 
1168  // Following code interpolates between defined beam parameters and default beam parameters
1169  if (difftoBeamL > ar_beams[i].longbound * ar_beams[i].L)
1170  interp_ratio = difftoBeamL - ar_beams[i].longbound * ar_beams[i].L;
1171  else if (difftoBeamL < -ar_beams[i].shortbound * ar_beams[i].L)
1172  interp_ratio = -difftoBeamL - ar_beams[i].shortbound * ar_beams[i].L;
1173 
1174  if (interp_ratio != 0.0f)
1175  {
1176  // Hard (normal) shock bump
1177  float tspring = DEFAULT_SPRING;
1178  float tdamp = DEFAULT_DAMP;
1179 
1180  // Skip camera, wheels or any other shocks which are not generated in a shocks or shocks2 section
1181  if (ar_beams[i].bm_type == BEAM_HYDRO)
1182  {
1183  tspring = ar_beams[i].shock->sbd_spring;
1184  tdamp = ar_beams[i].shock->sbd_damp;
1185  }
1186 
1187  k += (tspring - k) * interp_ratio;
1188  d += (tdamp - d) * interp_ratio;
1189  }
1190  }
1191  else if (ar_beams[i].bounded == TRIGGER)
1192  {
1193  this->CalcTriggers(i, difftoBeamL, trigger_hooks);
1194  }
1195  else if (ar_beams[i].bounded == SHOCK2)
1196  {
1197  this->CalcShocks2(i, difftoBeamL, k, d, v);
1198  }
1199  else if (ar_beams[i].bounded == SHOCK3)
1200  {
1201  this->CalcShocks3(i, difftoBeamL, k, d, v);
1202  }
1203  else if (ar_beams[i].bounded == SUPPORTBEAM)
1204  {
1205  if (difftoBeamL > 0.0f)
1206  {
1207  k = 0.0f;
1208  d *= 0.1f;
1209  float break_limit = SUPPORT_BEAM_LIMIT_DEFAULT;
1210  if (ar_beams[i].longbound > 0.0f)
1211  {
1212  // This is a supportbeam with a user set break limit, get the user set limit
1213  break_limit = ar_beams[i].longbound;
1214  }
1215 
1216  // If support beam is extended the originallength * break_limit, break and disable it
1217  if (difftoBeamL > ar_beams[i].L * break_limit)
1218  {
1219  ar_beams[i].bm_broken = true;
1220  ar_beams[i].bm_disabled = true;
1221  if (m_beam_break_debug_enabled)
1222  {
1223  RoR::Str<300> msg;
1224  msg << "[RoR|Diag] XXX Support-Beam " << i << " limit extended and broke. "
1225  << "Length: " << difftoBeamL << " / max. Length: " << (ar_beams[i].L*break_limit) << ". ";
1226  LogBeamNodes(msg, ar_beams[i]);
1227  App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE, msg.ToCStr());
1228  }
1229  }
1230  }
1231  }
1232  else if (ar_beams[i].bounded == ROPE)
1233  {
1234  if (difftoBeamL < 0.0f)
1235  {
1236  k = 0.0f;
1237  d *= 0.1f;
1238  }
1239  }
1240 
1241  if (trigger_hooks && ar_beams[i].bounded && ar_beams[i].bm_type == BEAM_HYDRO)
1242  {
1243  ar_beams[i].debug_k = k * std::abs(difftoBeamL);
1244  ar_beams[i].debug_d = d * std::abs(v);
1245  ar_beams[i].debug_v = std::abs(v);
1246  }
1247 
1248  float slen = -k * difftoBeamL - d * v;
1249  ar_beams[i].stress = slen;
1250 
1251  // Fast test for deformation
1252  float len = std::abs(slen);
1253  if (len > ar_beams[i].minmaxposnegstress)
1254  {
1255  if (ar_beams[i].bm_type == BEAM_NORMAL && ar_beams[i].bounded != SHOCK1 && k != 0.0f)
1256  {
1257  // Actual deformation tests
1258  if (slen > ar_beams[i].maxposstress && difftoBeamL < 0.0f) // compression
1259  {
1260  Real yield_length = ar_beams[i].maxposstress / k;
1261  Real deform = difftoBeamL + yield_length * (1.0f - ar_beams[i].plastic_coef);
1262  Real Lold = ar_beams[i].L;
1263  ar_beams[i].L += deform;
1264  ar_beams[i].L = std::max(MIN_BEAM_LENGTH, ar_beams[i].L);
1265  slen = slen - (slen - ar_beams[i].maxposstress) * 0.5f;
1266  len = slen;
1267  if (ar_beams[i].L > 0.0f && Lold > ar_beams[i].L)
1268  {
1269  ar_beams[i].maxposstress *= Lold / ar_beams[i].L;
1270  ar_beams[i].minmaxposnegstress = std::min(ar_beams[i].maxposstress, -ar_beams[i].maxnegstress);
1271  ar_beams[i].minmaxposnegstress = std::min(ar_beams[i].minmaxposnegstress, ar_beams[i].strength);
1272  }
1273  // For the compression case we do not remove any of the beam's
1274  // strength for structure stability reasons
1275  //ar_beams[i].strength += deform * k * 0.5f;
1276  if (m_beam_deform_debug_enabled)
1277  {
1278  RoR::Str<300> msg;
1279  msg << "[RoR|Diag] YYY Beam " << i << " just deformed with extension force "
1280  << len << " / " << ar_beams[i].strength << ". ";
1281  LogBeamNodes(msg, ar_beams[i]);
1282  RoR::Log(msg.ToCStr());
1283  }
1284  }
1285  else if (slen < ar_beams[i].maxnegstress && difftoBeamL > 0.0f) // expansion
1286  {
1287  Real yield_length = ar_beams[i].maxnegstress / k;
1288  Real deform = difftoBeamL + yield_length * (1.0f - ar_beams[i].plastic_coef);
1289  Real Lold = ar_beams[i].L;
1290  ar_beams[i].L += deform;
1291  slen = slen - (slen - ar_beams[i].maxnegstress) * 0.5f;
1292  len = -slen;
1293  if (Lold > 0.0f && ar_beams[i].L > Lold)
1294  {
1295  ar_beams[i].maxnegstress *= ar_beams[i].L / Lold;
1296  ar_beams[i].minmaxposnegstress = std::min(ar_beams[i].maxposstress, -ar_beams[i].maxnegstress);
1297  ar_beams[i].minmaxposnegstress = std::min(ar_beams[i].minmaxposnegstress, ar_beams[i].strength);
1298  }
1299  ar_beams[i].strength -= deform * k;
1300  if (m_beam_deform_debug_enabled)
1301  {
1302  RoR::Str<300> msg;
1303  msg << "[RoR|Diag] YYY Beam " << i << " just deformed with extension force "
1304  << len << " / " << ar_beams[i].strength << ". ";
1305  LogBeamNodes(msg, ar_beams[i]);
1306  RoR::Log(msg.ToCStr());
1307  }
1308  }
1309  }
1310 
1311  // Test if the beam should break
1312  if (len > ar_beams[i].strength)
1313  {
1314  // Sound effect.
1315  // Sound volume depends on springs stored energy
1316  SOUND_MODULATE(ar_instance_id, SS_MOD_BREAK, 0.5 * k * difftoBeamL * difftoBeamL);
1317  SOUND_PLAY_ONCE(ar_instance_id, SS_TRIG_BREAK);
1318 
1319  //Break the beam only when it is not connected to a node
1320  //which is a part of a collision triangle and has 2 "live" beams or less
1321  //connected to it.
1322  if (!((ar_beams[i].p1->nd_cab_node && GetNumActiveConnectedBeams(ar_beams[i].p1->pos) < 3) || (ar_beams[i].p2->nd_cab_node && GetNumActiveConnectedBeams(ar_beams[i].p2->pos) < 3)))
1323  {
1324  slen = 0.0f;
1325  ar_beams[i].bm_broken = true;
1326  ar_beams[i].bm_disabled = true;
1327 
1328  if (m_beam_break_debug_enabled)
1329  {
1330  RoR::Str<200> msg;
1331  msg << "[RoR|Diag] XXX Beam " << i << " just broke with force " << len << " / " << ar_beams[i].strength << ". ";
1332  LogBeamNodes(msg, ar_beams[i]);
1333  App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE, msg.ToCStr());
1334  }
1335 
1336  // detachergroup check: beam[i] is already broken, check detacher group# == 0/default skip the check ( performance bypass for beams with default setting )
1337  // only perform this check if this is a master detacher beams (positive detacher group id > 0)
1338  if (ar_beams[i].detacher_group > 0)
1339  {
1340  // cycle once through the other beams
1341  for (int j = 0; j < ar_num_beams; j++)
1342  {
1343  // beam[i] detacher group# == checked beams detacher group# -> delete & disable checked beam
1344  // do this with all master(positive id) and minor(negative id) beams of this detacher group
1345  if (abs(ar_beams[j].detacher_group) == ar_beams[i].detacher_group)
1346  {
1347  ar_beams[j].bm_broken = true;
1348  ar_beams[j].bm_disabled = true;
1349  if (m_beam_break_debug_enabled)
1350  {
1351  App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE,
1352  "Deleting Detacher BeamID: " + TOSTRING(j) + ", Detacher Group: " + TOSTRING(ar_beams[i].detacher_group)+ ", actor ID: " + TOSTRING(ar_instance_id));
1353  }
1354  }
1355  }
1356  // cycle once through all wheeldetachers
1357  for (wheeldetacher_t const& wheeldetacher: ar_wheeldetachers)
1358  {
1359  if (wheeldetacher.wd_detacher_group == ar_beams[i].detacher_group)
1360  {
1361  ar_wheels[wheeldetacher.wd_wheel_id].wh_is_detached = true;
1362  if (m_beam_break_debug_enabled)
1363  {
1364  App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE,
1365  "Detaching wheel ID: " + TOSTRING(wheeldetacher.wd_wheel_id) + ", Detacher Group: " + TOSTRING(ar_beams[i].detacher_group)+ ", actor ID: " + TOSTRING(ar_instance_id));
1366  }
1367  }
1368  }
1369  }
1370  }
1371  else
1372  {
1373  ar_beams[i].strength = 2.0f * ar_beams[i].minmaxposnegstress;
1374  }
1375 
1376  // something broke, check buoyant hull
1377  for (int mk = 0; mk < ar_num_buoycabs; mk++)
1378  {
1379  int tmpv = ar_buoycabs[mk] * 3;
1380  if (ar_buoycab_types[mk] == Buoyance::BUOY_DRAGONLY)
1381  continue;
1382  if ((ar_beams[i].p1 == &ar_nodes[ar_cabs[tmpv]] || ar_beams[i].p1 == &ar_nodes[ar_cabs[tmpv + 1]] || ar_beams[i].p1 == &ar_nodes[ar_cabs[tmpv + 2]]) &&
1383  (ar_beams[i].p2 == &ar_nodes[ar_cabs[tmpv]] || ar_beams[i].p2 == &ar_nodes[ar_cabs[tmpv + 1]] || ar_beams[i].p2 == &ar_nodes[ar_cabs[tmpv + 2]]))
1384  {
1385  m_buoyance->sink = true;
1386  }
1387  }
1388  }
1389  }
1390 
1391  // At last update the beam forces
1392  Vector3 f = dis;
1393  f *= (slen * inverted_dislen);
1394  ar_beams[i].p1->Forces += f;
1395  ar_beams[i].p2->Forces -= f;
1396  }
1397  }
1398 }
1399 
1400 void Actor::CalcBeamsInterActor()
1401 {
1402  for (int i = 0; i < static_cast<int>(ar_inter_beams.size()); i++)
1403  {
1404  if (!ar_inter_beams[i]->bm_disabled && ar_inter_beams[i]->bm_inter_actor)
1405  {
1406  // Calculate beam length
1407  Vector3 dis = ar_inter_beams[i]->p1->AbsPosition - ar_inter_beams[i]->p2->AbsPosition;
1408 
1409  Real dislen = dis.squaredLength();
1410  Real inverted_dislen = fast_invSqrt(dislen);
1411 
1412  dislen *= inverted_dislen;
1413 
1414  // Calculate beam's deviation from normal
1415  Real difftoBeamL = dislen - ar_inter_beams[i]->L;
1416 
1417  Real k = ar_inter_beams[i]->k;
1418  Real d = ar_inter_beams[i]->d;
1419 
1420  if (ar_inter_beams[i]->bounded == ROPE && difftoBeamL < 0.0f)
1421  {
1422  k = 0.0f;
1423  d *= 0.1f;
1424  }
1425 
1426  // Calculate beam's rate of change
1427  Vector3 v = ar_inter_beams[i]->p1->Velocity - ar_inter_beams[i]->p2->Velocity;
1428 
1429  float slen = -k * (difftoBeamL) - d * v.dotProduct(dis) * inverted_dislen;
1430  ar_inter_beams[i]->stress = slen;
1431 
1432  // Fast test for deformation
1433  float len = std::abs(slen);
1434  if (len > ar_inter_beams[i]->minmaxposnegstress)
1435  {
1436  if (ar_inter_beams[i]->bm_type == BEAM_NORMAL && ar_inter_beams[i]->bounded != SHOCK1 && k != 0.0f)
1437  {
1438  // Actual deformation tests
1439  if (slen > ar_inter_beams[i]->maxposstress && difftoBeamL < 0.0f) // compression
1440  {
1441  Real yield_length = ar_inter_beams[i]->maxposstress / k;
1442  Real deform = difftoBeamL + yield_length * (1.0f - ar_inter_beams[i]->plastic_coef);
1443  Real Lold = ar_inter_beams[i]->L;
1444  ar_inter_beams[i]->L += deform;
1445  ar_inter_beams[i]->L = std::max(MIN_BEAM_LENGTH, ar_inter_beams[i]->L);
1446  slen = slen - (slen - ar_inter_beams[i]->maxposstress) * 0.5f;
1447  len = slen;
1448  if (ar_inter_beams[i]->L > 0.0f && Lold > ar_inter_beams[i]->L)
1449  {
1450  ar_inter_beams[i]->maxposstress *= Lold / ar_inter_beams[i]->L;
1451  ar_inter_beams[i]->minmaxposnegstress = std::min(ar_inter_beams[i]->maxposstress, -ar_inter_beams[i]->maxnegstress);
1452  ar_inter_beams[i]->minmaxposnegstress = std::min(ar_inter_beams[i]->minmaxposnegstress, ar_inter_beams[i]->strength);
1453  }
1454  // For the compression case we do not remove any of the beam's
1455  // strength for structure stability reasons
1456  //ar_inter_beams[i]->strength += deform * k * 0.5f;
1457  if (m_beam_deform_debug_enabled)
1458  {
1459  RoR::Str<300> msg;
1460  msg << "[RoR|Diag] YYY Beam " << i << " just deformed with extension force "
1461  << len << " / " << ar_inter_beams[i]->strength << ". ";
1462  LogBeamNodes(msg, (*ar_inter_beams[i]));
1463  RoR::Log(msg.ToCStr());
1464  }
1465  }
1466  else if (slen < ar_inter_beams[i]->maxnegstress && difftoBeamL > 0.0f) // expansion
1467  {
1468  Real yield_length = ar_inter_beams[i]->maxnegstress / k;
1469  Real deform = difftoBeamL + yield_length * (1.0f - ar_inter_beams[i]->plastic_coef);
1470  Real Lold = ar_inter_beams[i]->L;
1471  ar_inter_beams[i]->L += deform;
1472  slen = slen - (slen - ar_inter_beams[i]->maxnegstress) * 0.5f;
1473  len = -slen;
1474  if (Lold > 0.0f && ar_inter_beams[i]->L > Lold)
1475  {
1476  ar_inter_beams[i]->maxnegstress *= ar_inter_beams[i]->L / Lold;
1477  ar_inter_beams[i]->minmaxposnegstress = std::min(ar_inter_beams[i]->maxposstress, -ar_inter_beams[i]->maxnegstress);
1478  ar_inter_beams[i]->minmaxposnegstress = std::min(ar_inter_beams[i]->minmaxposnegstress, ar_inter_beams[i]->strength);
1479  }
1480  ar_inter_beams[i]->strength -= deform * k;
1481  if (m_beam_deform_debug_enabled)
1482  {
1483  RoR::Str<300> msg;
1484  msg << "[RoR|Diag] YYY Beam " << i << " just deformed with extension force "
1485  << len << " / " << ar_inter_beams[i]->strength << ". ";
1486  LogBeamNodes(msg, (*ar_inter_beams[i]));
1487  RoR::Log(msg.ToCStr());
1488  }
1489  }
1490  }
1491 
1492  // Test if the beam should break
1493  if (len > ar_inter_beams[i]->strength)
1494  {
1495  // Sound effect.
1496  // Sound volume depends on springs stored energy
1497  SOUND_MODULATE(ar_instance_id, SS_MOD_BREAK, 0.5 * k * difftoBeamL * difftoBeamL);
1498  SOUND_PLAY_ONCE(ar_instance_id, SS_TRIG_BREAK);
1499 
1500  //Break the beam only when it is not connected to a node
1501  //which is a part of a collision triangle and has 2 "live" beams or less
1502  //connected to it.
1503  if (!((ar_inter_beams[i]->p1->nd_cab_node && GetNumActiveConnectedBeams(ar_inter_beams[i]->p1->pos) < 3) || (ar_inter_beams[i]->p2->nd_cab_node && GetNumActiveConnectedBeams(ar_inter_beams[i]->p2->pos) < 3)))
1504  {
1505  slen = 0.0f;
1506  ar_inter_beams[i]->bm_broken = true;
1507  ar_inter_beams[i]->bm_disabled = true;
1508 
1509  if (m_beam_break_debug_enabled)
1510  {
1511  RoR::Str<200> msg;
1512  msg << "Beam " << i << " just broke with force " << len << " / " << ar_inter_beams[i]->strength << ". ";
1513  LogBeamNodes(msg, (*ar_inter_beams[i]));
1514  App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE, msg.ToCStr());
1515  }
1516  }
1517  else
1518  {
1519  ar_inter_beams[i]->strength = 2.0f * ar_inter_beams[i]->minmaxposnegstress;
1520  }
1521  }
1522  }
1523 
1524  // At last update the beam forces
1525  Vector3 f = dis;
1526  f *= (slen * inverted_dislen);
1527  ar_inter_beams[i]->p1->Forces += f;
1528  ar_inter_beams[i]->p2->Forces -= f;
1529  }
1530  }
1531 }
1532 
1533 void Actor::CalcNodes()
1534 {
1535  const auto water = App::GetGameContext()->GetTerrain()->getWater();
1536  const float gravity = App::GetGameContext()->GetTerrain()->getGravity();
1537  m_water_contact = false;
1538 
1539  for (NodeNum_t i = 0; i < ar_num_nodes; i++)
1540  {
1541  // COLLISION
1542  if (!ar_nodes[i].nd_no_ground_contact)
1543  {
1544  Vector3 oripos = ar_nodes[i].AbsPosition;
1545  bool contacted = App::GetGameContext()->GetTerrain()->GetCollisions()->groundCollision(&ar_nodes[i], PHYSICS_DT);
1546  contacted = contacted | App::GetGameContext()->GetTerrain()->GetCollisions()->nodeCollision(&ar_nodes[i], PHYSICS_DT);
1547  ar_nodes[i].nd_has_ground_contact = contacted;
1548  if (ar_nodes[i].nd_has_ground_contact || ar_nodes[i].nd_has_mesh_contact)
1549  {
1550  ar_last_fuzzy_ground_model = ar_nodes[i].nd_last_collision_gm;
1551  // Reverts: commit/d11a88142f737528638bd357c38d717c85cebba6#diff-4003254e55aec2c60d21228f375f2a2dL1153
1552  // Fixes: Gavril Omega Six sliding on ground on the simple2 spawn
1553  // ar_nodes[i].AbsPosition - oripos is always zero ... dark floating point magic
1554  ar_nodes[i].RelPosition += ar_nodes[i].AbsPosition - oripos;
1555  }
1556  }
1557 
1558  if (i == ar_main_camera_node_pos)
1559  {
1560  // record g forces on cameras
1561  m_camera_gforces_accu += ar_nodes[i].Forces / ar_nodes[i].mass;
1562  }
1563 
1564  // integration
1565  if (!ar_nodes[i].nd_immovable)
1566  {
1567  ar_nodes[i].Velocity += ar_nodes[i].Forces / ar_nodes[i].mass * PHYSICS_DT;
1568  ar_nodes[i].RelPosition += ar_nodes[i].Velocity * PHYSICS_DT;
1569  ar_nodes[i].AbsPosition = ar_origin;
1570  ar_nodes[i].AbsPosition += ar_nodes[i].RelPosition;
1571  }
1572 
1573  // prepare next loop (optimisation)
1574  // we start forces from zero
1575  // start with gravity
1576  ar_nodes[i].Forces = Vector3(0, ar_nodes[i].mass * gravity, 0);
1577 
1578  Real approx_speed = approx_sqrt(ar_nodes[i].Velocity.squaredLength());
1579 
1580  // anti-explsion guard (mach 20)
1581  if (approx_speed > 6860 && !m_ongoing_reset)
1582  {
1583  ActorModifyRequest* rq = new ActorModifyRequest; // actor exploded, schedule reset
1584  rq->amr_actor = this->ar_instance_id;
1585  rq->amr_type = ActorModifyRequest::Type::RESET_ON_SPOT;
1587  m_ongoing_reset = true;
1588  }
1589 
1590  if (m_fusealge_airfoil)
1591  {
1592  // aerodynamics on steroids!
1593  ar_nodes[i].Forces += ar_fusedrag;
1594  }
1595  else if (!ar_disable_aerodyn_turbulent_drag)
1596  {
1597  // add viscous drag (turbulent model)
1598  Real defdragxspeed = DEFAULT_DRAG * approx_speed;
1599  Vector3 drag = -defdragxspeed * ar_nodes[i].Velocity;
1600  // plus: turbulences
1601  Real maxtur = defdragxspeed * approx_speed * 0.005f;
1602  drag += maxtur * Vector3(frand_11(), frand_11(), frand_11());
1603  ar_nodes[i].Forces += drag;
1604  }
1605 
1606  if (water)
1607  {
1608  const bool is_under_water = water->IsUnderWater(ar_nodes[i].AbsPosition);
1609  if (is_under_water)
1610  {
1611  m_water_contact = true;
1612  if (ar_num_buoycabs == 0)
1613  {
1614  // water drag (turbulent)
1615  ar_nodes[i].Forces -= (DEFAULT_WATERDRAG * approx_speed) * ar_nodes[i].Velocity;
1616  // basic buoyance
1617  ar_nodes[i].Forces += ar_nodes[i].buoyancy * Vector3::UNIT_Y;
1618  }
1619  // engine stall
1620  if (i == ar_cinecam_node[0] && ar_engine)
1621  {
1622  ar_engine->StopEngine();
1623  }
1624  }
1625  ar_nodes[i].nd_under_water = is_under_water;
1626  }
1627  }
1628 }
1629 
1630 void Actor::CalcEventBoxes()
1631 {
1632  // Assumption: node positions and bounding boxes are up to date.
1633  // First, find all collision boxes which this actor's bounding box touches (potential collisions)
1634  // For each potential collision box:
1635  // * if a collision was already recorded, test the recorded node. If still colliding, do nothing.
1636  // * otherwise loop nodes until collision is found. If not, clear the collision record.
1637  // ----------------------------------------------------------------------------------------------
1638 
1639  m_potential_eventboxes.clear();
1640  App::GetGameContext()->GetTerrain()->GetCollisions()->findPotentialEventBoxes(this, m_potential_eventboxes);
1641 
1642  for (collision_box_t* cbox : m_potential_eventboxes)
1643  {
1644  // Find existing collision record
1645  bool has_collision = false;
1646  bool do_callback_enter = true;
1647  bool do_callback_exit = false;
1648  auto itor = m_active_eventboxes.begin();
1649  while (itor != m_active_eventboxes.end())
1650  {
1651  if (itor->first == cbox)
1652  {
1653  // Existing record found - check if the node still collides
1654  has_collision = App::GetGameContext()->GetTerrain()->GetCollisions()->isInside(ar_nodes[itor->second].AbsPosition, cbox);
1655  if (!has_collision)
1656  {
1657  // Erase the collision record
1658  itor = m_active_eventboxes.erase(itor);
1659  // Prevent invoking the same ENTER callback again
1660  do_callback_enter = false;
1661  do_callback_exit = true;
1662  }
1663  break;
1664  }
1665  else
1666  {
1667  itor++;
1668  }
1669  }
1670 
1671  if (!has_collision)
1672  {
1673  // Find if any node collides
1674  for (NodeNum_t i = 0; i < ar_num_nodes; i++)
1675  {
1676  has_collision = App::GetGameContext()->GetTerrain()->GetCollisions()->isInside(ar_nodes[i].AbsPosition, cbox);
1677  if (has_collision)
1678  {
1679  do_callback_exit = false;
1680  // Add new collision record
1681  m_active_eventboxes.push_back(std::make_pair(cbox, i));
1682  // Do the script callback
1683  if (do_callback_enter)
1684  {
1685  // IMPORTANT - this function is executed under physics thread pool, do not run script callbacks synchronously!
1686  eventsource_t& eventsource = App::GetGameContext()->GetTerrain()->GetCollisions()->getEventSource(cbox->eventsourcenum);
1687 
1688  // The classic optional per-object script handler.
1689  ScriptCallbackArgs* args = new ScriptCallbackArgs( &eventsource, i );
1691 
1692  // The new EVENTBOX_ENTER event.
1693  TRIGGER_EVENT_ASYNC(SE_EVENTBOX_ENTER, 0, ar_instance_id, ar_nodes[i].pos, 0, eventsource.es_instance_name, eventsource.es_box_name, "", "");
1694  }
1695  break;
1696  }
1697  }
1698  }
1699 
1700  if (do_callback_exit)
1701  {
1702  // IMPORTANT - this function is executed under physics thread pool, do not run script callbacks synchronously!
1703  const eventsource_t& eventsource = App::GetGameContext()->GetTerrain()->GetCollisions()->getEventSource(cbox->eventsourcenum);
1704  TRIGGER_EVENT_ASYNC(SE_EVENTBOX_EXIT, 0, ar_instance_id, 0, 0, eventsource.es_instance_name, eventsource.es_box_name, "", "");
1705  }
1706  }
1707 }
1708 
1709 void Actor::CalcHooks()
1710 {
1711  //locks - this is not active in network mode
1712  for (std::vector<hook_t>::iterator it = ar_hooks.begin(); it != ar_hooks.end(); it++)
1713  {
1714  //we need to do this here to avoid countdown speedup by triggers
1715  it->hk_timer = std::max(0.0f, it->hk_timer - PHYSICS_DT);
1716 
1717  if (it->hk_lock_node && it->hk_locked == PRELOCK)
1718  {
1719  if (it->hk_beam->L < it->hk_min_length)
1720  {
1721  //shortlimit reached -> status LOCKED
1722  it->hk_locked = LOCKED;
1723  }
1724  else
1725  {
1726  //shorten the connecting beam slowly to locking minrange
1727  if (it->hk_beam->L > it->hk_lockspeed && fabs(it->hk_beam->stress) < it->hk_maxforce)
1728  {
1729  it->hk_beam->L = (it->hk_beam->L - it->hk_lockspeed);
1730  }
1731  else
1732  {
1733  if (fabs(it->hk_beam->stress) < it->hk_maxforce)
1734  {
1735  it->hk_beam->L = 0.001f;
1736  //locking minrange or stress exeeded -> status LOCKED
1737  it->hk_locked = LOCKED;
1738  }
1739  else
1740  {
1741  if (it->hk_nodisable)
1742  {
1743  //force exceed, but beam is set to nodisable, just lock it in this position
1744  it->hk_locked = LOCKED;
1745  }
1746  else
1747  {
1748  //force exceeded, reset the hook node
1750  rq->alr_actor_instance_id = ar_instance_id;
1751  rq->alr_type = ActorLinkingRequestType::HOOK_ACTION;
1754  }
1755  }
1756  }
1757  }
1758  }
1759  }
1760 }
1761 
1762 void Actor::CalcRopes()
1763 {
1764  for (auto r : ar_ropes)
1765  {
1766  if (r.rp_locked == LOCKED && r.rp_locked_ropable)
1767  {
1768  auto locked_node = r.rp_locked_ropable->node;
1769  r.rp_beam->p2->AbsPosition = locked_node->AbsPosition;
1770  r.rp_beam->p2->RelPosition = locked_node->AbsPosition - ar_origin;
1771  r.rp_beam->p2->Velocity = locked_node->Velocity;
1772  locked_node->Forces += r.rp_beam->p2->Forces;
1773  r.rp_beam->p2->Forces = Vector3::ZERO;
1774  }
1775  }
1776 }
GameContext.h
Game state manager and message-queue provider.
RoR::ActorLinkingRequest::alr_hook_group
int alr_hook_group
Definition: SimData.h:868
MAX_COMMANDS
static const int MAX_COMMANDS
maximum number of commands per actor
Definition: SimConstants.h:28
RoR::SL_COMMAND
@ SL_COMMAND
Definition: SoundScriptManager.h:160
RoR::App::GetSoundScriptManager
SoundScriptManager * GetSoundScriptManager()
Definition: Application.cpp:276
RoR::SHOCK3
@ SHOCK3
shock3
Definition: SimData.h:121
RoR::commandbeam_t::cmb_is_force_restricted
bool cmb_is_force_restricted
Attribute defined in truckfile.
Definition: SimData.h:575
RoR::node_t::Velocity
Ogre::Vector3 Velocity
Definition: SimData.h:306
RoR::HYDRO_FLAG_DIR
@ HYDRO_FLAG_DIR
Definition: SimData.h:143
RoR::MSG_SIM_MODIFY_ACTOR_REQUESTED
@ MSG_SIM_MODIFY_ACTOR_REQUESTED
Payload = RoR::ActorModifyRequest* (owner)
Definition: Application.h:111
RoR::MACHINE
@ MACHINE
its a machine
Definition: SimData.h:107
RoR::MSG_SIM_SCRIPT_CALLBACK_QUEUED
@ MSG_SIM_SCRIPT_CALLBACK_QUEUED
Payload = RoR::ScriptCallbackArgs* (owner)
Definition: Application.h:118
RoR::HYDRO_FLAG_REV_AILERON
@ HYDRO_FLAG_REV_AILERON
Definition: SimData.h:147
RoR::HYDRO_FLAG_SPEED
@ HYDRO_FLAG_SPEED
Definition: SimData.h:142
RoR::SS_TRIG_ALB_ACTIVE
@ SS_TRIG_ALB_ACTIVE
Definition: SoundScriptManager.h:103
RoR::ActorLinkingRequest
Estabilishing a physics linkage between 2 actors modifies a global linkage table and triggers immedia...
Definition: SimData.h:863
RoR::beam_t::p1
node_t * p1
Definition: SimData.h:346
RoR::eventsource_t::es_instance_name
std::string es_instance_name
Specified by user when calling "GameScript::spawnObject()".
Definition: Collisions.h:42
RoR::eventsource_t
< Scripting
Definition: Collisions.h:40
RoR::HOOK_UNLOCK
@ HOOK_UNLOCK
Definition: SimData.h:70
RoR::hydrobeam_t::hb_ref_length
float hb_ref_length
Idle length in meters.
Definition: SimData.h:602
RoR::NODENUM_INVALID
static const NodeNum_t NODENUM_INVALID
Definition: ForwardDeclarations.h:53
RoR::wheel_t
Definition: SimData.h:432
fast_invSqrt
float fast_invSqrt(const float v)
Definition: ApproxMath.h:124
RoR::Collisions::groundCollision
bool groundCollision(node_t *node, float dt)
Definition: Collisions.cpp:1213
RoR::BEAM_NORMAL
@ BEAM_NORMAL
Definition: SimData.h:82
RoR::ActorLinkingRequest::alr_type
ActorLinkingRequestType alr_type
Definition: SimData.h:866
RoR::Collisions::findPotentialEventBoxes
void findPotentialEventBoxes(Actor *actor, CollisionBoxPtrVec &out_boxes)
Definition: Collisions.cpp:1089
RoR::TRIGGER_EVENT_ASYNC
void TRIGGER_EVENT_ASYNC(scriptEvents type, int arg1, int arg2ex=0, int arg3ex=0, int arg4ex=0, std::string arg5ex="", std::string arg6ex="", std::string arg7ex="", std::string arg8ex="")
Asynchronously (via MSG_SIM_SCRIPT_EVENT_TRIGGERED) invoke script function eventCallbackEx(),...
Definition: ScriptEngine.h:51
RoR::commandbeam_t::cmb_engine_coupling
float cmb_engine_coupling
Attr from truckfile.
Definition: SimData.h:568
RoR::SS_MOD_PUMP
@ SS_MOD_PUMP
Definition: SoundScriptManager.h:138
RoR::App::io_analog_sensitivity
CVar * io_analog_sensitivity
Definition: Application.cpp:189
RoR::SS_TRIG_TC_ACTIVE
@ SS_TRIG_TC_ACTIVE
Definition: SoundScriptManager.h:104
Console.h
LogNodeId
void LogNodeId(RoR::Str< L > &msg, node_t *node)
Definition: ActorForcesEuler.cpp:1126
RoR::Console::putMessage
void putMessage(MessageArea area, MessageType type, std::string const &msg, std::string icon="")
Definition: Console.cpp:97
STAB_RATE
static const float STAB_RATE
Definition: SimConstants.h:66
RoR::LOCKED
@ LOCKED
lock locked.
Definition: SimData.h:96
RoR::SS_TRIG_PUMP
@ SS_TRIG_PUMP
Definition: SoundScriptManager.h:61
Airfoil.h
RoR::node_t::RelPosition
Ogre::Vector3 RelPosition
relative to the local physics origin (one origin per actor) (shaky)
Definition: SimData.h:304
Differentials.h
RoR::commandbeam_t::cmb_state
std::shared_ptr< commandbeam_state_t > cmb_state
Definition: SimData.h:582
RoR::collision_box_t
Definition: SimData.h:715
ActorManager.h
RoR::SE_EVENTBOX_ENTER
@ SE_EVENTBOX_ENTER
Actor or person entered an eventbox; arguments: #1 type, #2 actorID (actor only), #3 node ID (actor o...
Definition: ScriptEvents.h:32
Actor.h
RoR::beam_t::stress
float stress
Definition: SimData.h:355
RoR::hydrobeam_t::hb_anim_flags
int hb_anim_flags
Animators (beams updating length based on simulation variables)
Definition: SimData.h:605
EngineSim.h
RoR::beam_t::refL
float refL
reference length
Definition: SimData.h:367
RoR::HYDRO_FLAG_REV_RUDDER
@ HYDRO_FLAG_REV_RUDDER
Definition: SimData.h:148
RoR::ROPE
@ ROPE
Definition: SimData.h:124
Replay.h
RoR::DifferentialData::delta_rotation
float delta_rotation
Definition: Differentials.h:37
RoR::ActorModifyRequest::amr_actor
ActorInstanceID_t amr_actor
Definition: SimData.h:843
RoR::SHOCK_FLAG_LACTIVE
@ SHOCK_FLAG_LACTIVE
Definition: SimData.h:209
RoR::ActorLinkingRequest::alr_hook_action
HookAction alr_hook_action
Definition: SimData.h:869
TOSTRING
#define TOSTRING(x)
Definition: Application.h:56
RoR::beam_t
Simulation: An edge in the softbody structure.
Definition: SimData.h:341
ScrewProp.h
RoR::PRELOCK
@ PRELOCK
prelocking, attraction forces in action
Definition: SimData.h:95
RoR::SS_TRIG_LINKED_COMMAND
@ SS_TRIG_LINKED_COMMAND
Definition: SoundScriptManager.h:118
RoR::DifferentialData::out_torque
float out_torque[2]
Definition: Differentials.h:38
RoR::wheel_t::wh_torque
Ogre::Real wh_torque
Internal physics state; Do not read from this.
Definition: SimData.h:460
CmdKeyInertia.h
RoR::commandbeam_t::cmb_is_contraction
bool cmb_is_contraction
Attribute defined at spawn.
Definition: SimData.h:574
RoR::NodeNum_t
uint16_t NodeNum_t
Node position within Actor::ar_nodes; use RoR::NODENUM_INVALID as empty value.
Definition: ForwardDeclarations.h:52
RoR::HYDRO_FLAG_AILERON
@ HYDRO_FLAG_AILERON
Definition: SimData.h:144
RoR::Str
Wrapper for classic c-string (local buffer) Refresher: strlen() excludes '\0' terminator; strncat() A...
Definition: Str.h:35
RoR::HOOK_LOCK
@ HOOK_LOCK
Definition: SimData.h:69
RoR::eventsource_t::es_box_name
std::string es_box_name
Specified in ODEF file as "event".
Definition: Collisions.h:43
approx_pow
float approx_pow(const float x, const float y)
Definition: ApproxMath.h:91
RAD_PER_SEC_TO_RPM
static const float RAD_PER_SEC_TO_RPM
Convert radian/second to RPM (60/2*PI)
Definition: SimConstants.h:37
RoR::ActorModifyRequest
Definition: SimData.h:827
RoR::commandbeam_t::cmb_center_length
float cmb_center_length
Attr computed at spawn.
Definition: SimData.h:569
RoR::commandbeam_t::cmb_needs_engine
bool cmb_needs_engine
Attribute defined in truckfile.
Definition: SimData.h:576
DEFAULT_WATERDRAG
static const float DEFAULT_WATERDRAG
Definition: SimConstants.h:58
ScriptEngine.h
RoR::GameContext::PushMessage
void PushMessage(Message m)
Doesn't guarantee order! Use ChainMessage() if order matters.
Definition: GameContext.cpp:66
RoR::hydrobeam_t::hb_inertia
RoR::CmdKeyInertia hb_inertia
Definition: SimData.h:607
RoR::hydrobeam_t
Definition: SimData.h:599
RoR::Str::ToCStr
const char * ToCStr() const
Definition: Str.h:46
RoR::SS_TRIG_AIR
@ SS_TRIG_AIR
Definition: SoundScriptManager.h:68
RoR::commandbeam_t::cmb_speed
float cmb_speed
Attr; Rate of contraction/extension.
Definition: SimData.h:570
RoR::Terrain::GetCollisions
Collisions * GetCollisions()
Definition: Terrain.h:83
RoR::node_t::pos
NodeNum_t pos
This node's index in Actor::ar_nodes array.
Definition: SimData.h:315
SOUND_START
#define SOUND_START(_ACTOR_, _TRIG_)
Definition: SoundScriptManager.h:35
RoR::SHOCK2
@ SHOCK2
shock2
Definition: SimData.h:120
Application.h
Central state/object manager and communications hub.
RoR::App::GetConsole
Console * GetConsole()
Definition: Application.cpp:269
SoundScriptManager.h
RoR::node_t
Physics: A vertex in the softbody structure.
Definition: SimData.h:297
RoR::commandbeam_t::cmb_boundary_length
float cmb_boundary_length
Attr; Maximum/minimum length proportional to orig. len.
Definition: SimData.h:571
RoR::App::GetGameContext
GameContext * GetGameContext()
Definition: Application.cpp:279
RoR::ScriptCallbackArgs
Definition: ScriptEngine.h:98
RoR::HYDRO_FLAG_ELEVATOR
@ HYDRO_FLAG_ELEVATOR
Definition: SimData.h:146
RoR::wheel_t::wh_speed
Ogre::Real wh_speed
Current wheel speed in m/s.
Definition: SimData.h:455
RoR::SS_MOD_LINKED_COMMANDRATE
@ SS_MOD_LINKED_COMMANDRATE
Definition: SoundScriptManager.h:153
RoR::commandbeam_t::cmb_is_1press
bool cmb_is_1press
Attribute defined in truckfile.
Definition: SimData.h:579
SOUND_STOP
#define SOUND_STOP(_ACTOR_, _TRIG_)
Definition: SoundScriptManager.h:36
RoR::SHOCK_FLAG_RACTIVE
@ SHOCK_FLAG_RACTIVE
Definition: SimData.h:210
RoR::SUPPORTBEAM
@ SUPPORTBEAM
Definition: SimData.h:123
RoR::wheeldetacher_t
Definition: SimData.h:476
RoR::hydrobeam_t::hb_beam_index
uint16_t hb_beam_index
Index to Actor::ar_beams array.
Definition: SimData.h:601
ApproxMath.h
FlexAirfoil.h
DEFAULT_DRAG
static const float DEFAULT_DRAG
Definition: SimConstants.h:51
RoR::hydrobeam_t::hb_flags
int hb_flags
Definition: SimData.h:604
SOUND_MODULATE
#define SOUND_MODULATE(_ACTOR_, _MOD_, _VALUE_)
Definition: SoundScriptManager.h:40
RoR::Collisions::nodeCollision
bool nodeCollision(node_t *node, float dt)
Definition: Collisions.cpp:927
RoR::DifferentialData
Definition: Differentials.h:34
RoR::SS_TRIG_BREAK
@ SS_TRIG_BREAK
Definition: SoundScriptManager.h:82
RoR::SoundScriptManager::trigStart
void trigStart(int actor_id, int trig, int linkType=SL_DEFAULT, int linkItemID=-1)
Definition: SoundScriptManager.cpp:142
approx_sqrt
float approx_sqrt(const float y)
Definition: ApproxMath.h:101
RoR::HYDRO_FLAG_RUDDER
@ HYDRO_FLAG_RUDDER
Definition: SimData.h:145
Buoyance.h
RoR::BEAM_HYDRO
@ BEAM_HYDRO
Definition: SimData.h:83
RoR::MSG_SIM_ACTOR_LINKING_REQUESTED
@ MSG_SIM_ACTOR_LINKING_REQUESTED
Payload = RoR::ActorLinkingRequest* (owner)
Definition: Application.h:119
RoR::Message
Unified game event system - all requests and state changes are reported using a message.
Definition: GameContext.h:51
RoR::TRIGGER
@ TRIGGER
trigger
Definition: SimData.h:122
PHYSICS_DT
#define PHYSICS_DT
Definition: SimConstants.h:20
SOUND_PLAY_ONCE
#define SOUND_PLAY_ONCE(_ACTOR_, _TRIG_)
Definition: SoundScriptManager.h:34
RoR::App::io_analog_smoothing
CVar * io_analog_smoothing
Definition: Application.cpp:188
RoR::commandbeam_t
Definition: SimData.h:565
RoR::HYDRO_FLAG_REV_ELEVATOR
@ HYDRO_FLAG_REV_ELEVATOR
Definition: SimData.h:149
DEFAULT_SPRING
static const float DEFAULT_SPRING
Definition: SimConstants.h:48
MIN_BEAM_LENGTH
static const float MIN_BEAM_LENGTH
minimum beam lenght is 10 centimeters
Definition: SimConstants.h:55
RoR::Collisions::getEventSource
eventsource_t & getEventSource(int pos)
Definition: Collisions.h:217
RoR::ActorLinkingRequest::alr_actor_instance_id
ActorInstanceID_t alr_actor_instance_id
Definition: SimData.h:865
RoR::Terrain::getGravity
float getGravity() const
Definition: Terrain.h:96
RoR::beam_t::p2
node_t * p2
Definition: SimData.h:347
DEFAULT_DAMP
static const float DEFAULT_DAMP
Definition: SimConstants.h:49
Terrain.h
RoR::commandbeam_t::cmb_is_1press_center
bool cmb_is_1press_center
Attribute defined in truckfile.
Definition: SimData.h:580
RoR::Collisions::isInside
bool isInside(Ogre::Vector3 pos, const Ogre::String &inst, const Ogre::String &box, float border=0)
Definition: Collisions.cpp:1170
AeroEngine.h
RoR::hydrobeam_t::hb_speed
float hb_speed
Rate of change.
Definition: SimData.h:603
RoR::SE_EVENTBOX_EXIT
@ SE_EVENTBOX_EXIT
Actor or person left an eventbox; arguments: #1 type, #2 actorID (actor only), #3 unused,...
Definition: ScriptEvents.h:33
Ogre
Definition: ExtinguishableFireAffector.cpp:35
RoR::commandbeam_t::cmb_plays_sound
bool cmb_plays_sound
Attribute defined in truckfile.
Definition: SimData.h:578
RoR::SoundScriptManager::modulate
void modulate(int actor_id, int mod, float value, int linkType=SL_DEFAULT, int linkItemID=-1)
Definition: SoundScriptManager.cpp:274
SUPPORT_BEAM_LIMIT_DEFAULT
static const float SUPPORT_BEAM_LIMIT_DEFAULT
Definition: SimConstants.h:71
RoR::SHOCK1
@ SHOCK1
shock1
Definition: SimData.h:119
RoR::SS_MOD_BREAK
@ SS_MOD_BREAK
Definition: SoundScriptManager.h:136
AirBrake.h
Collisions.h
RoR::commandbeam_t::cmb_beam_index
uint16_t cmb_beam_index
Index to Actor::ar_beams array.
Definition: SimData.h:567
RoR::commandbeam_t::cmb_is_autocentering
bool cmb_is_autocentering
Attribute defined in truckfile.
Definition: SimData.h:577
frand_11
float frand_11()
Definition: ApproxMath.h:55
RoR::CmdKeyInertia::CalcCmdKeyDelay
float CalcCmdKeyDelay(float cmd_input, float dt)
Definition: CmdKeyInertia.cpp:40
RoR::ActorModifyRequest::amr_type
Type amr_type
Definition: SimData.h:844
RoR
Definition: AppContext.h:36
RoR::Log
void Log(const char *msg)
The ultimate, application-wide logging function. Adds a line (any length) in 'RoR....
Definition: Application.cpp:362
Water.h
LogBeamNodes
void LogBeamNodes(RoR::Str< L > &msg, beam_t &beam)
Definition: ActorForcesEuler.cpp:1132
RoR::SoundScriptManager::trigStop
void trigStop(int actor_id, int trig, int linkType=SL_DEFAULT, int linkItemID=-1)
Definition: SoundScriptManager.cpp:173
RoR::Airbrake
Definition: AirBrake.h:35
RoR::Terrain::getWater
IWater * getWater()
Definition: Terrain.h:84
RoR::beam_t::bm_broken
bool bm_broken
Definition: SimData.h:363
RoR::GameContext::GetTerrain
const TerrainPtr & GetTerrain()
Definition: GameContext.h:117
RoR::App::io_hydro_coupling
CVar * io_hydro_coupling
Definition: Application.cpp:198