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_LOCK;
1113  rq->alr_actor_instance_id = ar_instance_id;
1114  rq->alr_hook_group = -2;
1116  }
1117 
1118  this->CalcHooks();
1119  this->CalcRopes();
1120 
1121  return true;
1122 }
1123 
1124 template <size_t L>
1125 void LogNodeId(RoR::Str<L>& msg, node_t* node) // Internal helper
1126 {
1127  msg << " (index: " << node->pos << ")";
1128 }
1129 
1130 template <size_t L>
1131 void LogBeamNodes(RoR::Str<L>& msg, beam_t& beam) // Internal helper
1132 {
1133  msg << "It was between nodes ";
1134  LogNodeId(msg, beam.p1);
1135  msg << " and ";
1136  LogNodeId(msg, beam.p2);
1137  msg << ".";
1138 }
1139 
1140 void Actor::CalcBeams(bool trigger_hooks)
1141 {
1142  for (int i = 0; i < ar_num_beams; i++)
1143  {
1144  if (!ar_beams[i].bm_disabled && !ar_beams[i].bm_inter_actor)
1145  {
1146  // Calculate beam length
1147  Vector3 dis = ar_beams[i].p1->RelPosition - ar_beams[i].p2->RelPosition;
1148 
1149  Real dislen = dis.squaredLength();
1150  Real inverted_dislen = fast_invSqrt(dislen);
1151 
1152  dislen *= inverted_dislen;
1153 
1154  // Calculate beam's deviation from normal
1155  Real difftoBeamL = dislen - ar_beams[i].L;
1156 
1157  Real k = ar_beams[i].k;
1158  Real d = ar_beams[i].d;
1159 
1160  // Calculate beam's rate of change
1161  float v = (ar_beams[i].p1->Velocity - ar_beams[i].p2->Velocity).dotProduct(dis) * inverted_dislen;
1162 
1163  if (ar_beams[i].bounded == SHOCK1)
1164  {
1165  float interp_ratio = 0.0f;
1166 
1167  // Following code interpolates between defined beam parameters and default beam parameters
1168  if (difftoBeamL > ar_beams[i].longbound * ar_beams[i].L)
1169  interp_ratio = difftoBeamL - ar_beams[i].longbound * ar_beams[i].L;
1170  else if (difftoBeamL < -ar_beams[i].shortbound * ar_beams[i].L)
1171  interp_ratio = -difftoBeamL - ar_beams[i].shortbound * ar_beams[i].L;
1172 
1173  if (interp_ratio != 0.0f)
1174  {
1175  // Hard (normal) shock bump
1176  float tspring = DEFAULT_SPRING;
1177  float tdamp = DEFAULT_DAMP;
1178 
1179  // Skip camera, wheels or any other shocks which are not generated in a shocks or shocks2 section
1180  if (ar_beams[i].bm_type == BEAM_HYDRO)
1181  {
1182  tspring = ar_beams[i].shock->sbd_spring;
1183  tdamp = ar_beams[i].shock->sbd_damp;
1184  }
1185 
1186  k += (tspring - k) * interp_ratio;
1187  d += (tdamp - d) * interp_ratio;
1188  }
1189  }
1190  else if (ar_beams[i].bounded == TRIGGER)
1191  {
1192  this->CalcTriggers(i, difftoBeamL, trigger_hooks);
1193  }
1194  else if (ar_beams[i].bounded == SHOCK2)
1195  {
1196  this->CalcShocks2(i, difftoBeamL, k, d, v);
1197  }
1198  else if (ar_beams[i].bounded == SHOCK3)
1199  {
1200  this->CalcShocks3(i, difftoBeamL, k, d, v);
1201  }
1202  else if (ar_beams[i].bounded == SUPPORTBEAM)
1203  {
1204  if (difftoBeamL > 0.0f)
1205  {
1206  k = 0.0f;
1207  d *= 0.1f;
1208  float break_limit = SUPPORT_BEAM_LIMIT_DEFAULT;
1209  if (ar_beams[i].longbound > 0.0f)
1210  {
1211  // This is a supportbeam with a user set break limit, get the user set limit
1212  break_limit = ar_beams[i].longbound;
1213  }
1214 
1215  // If support beam is extended the originallength * break_limit, break and disable it
1216  if (difftoBeamL > ar_beams[i].L * break_limit)
1217  {
1218  ar_beams[i].bm_broken = true;
1219  ar_beams[i].bm_disabled = true;
1220  if (m_beam_break_debug_enabled)
1221  {
1222  RoR::Str<300> msg;
1223  msg << "[RoR|Diag] XXX Support-Beam " << i << " limit extended and broke. "
1224  << "Length: " << difftoBeamL << " / max. Length: " << (ar_beams[i].L*break_limit) << ". ";
1225  LogBeamNodes(msg, ar_beams[i]);
1226  App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE, msg.ToCStr());
1227  }
1228  }
1229  }
1230  }
1231  else if (ar_beams[i].bounded == ROPE)
1232  {
1233  if (difftoBeamL < 0.0f)
1234  {
1235  k = 0.0f;
1236  d *= 0.1f;
1237  }
1238  }
1239 
1240  if (trigger_hooks && ar_beams[i].bounded && ar_beams[i].bm_type == BEAM_HYDRO)
1241  {
1242  ar_beams[i].debug_k = k * std::abs(difftoBeamL);
1243  ar_beams[i].debug_d = d * std::abs(v);
1244  ar_beams[i].debug_v = std::abs(v);
1245  }
1246 
1247  float slen = -k * difftoBeamL - d * v;
1248  ar_beams[i].stress = slen;
1249 
1250  // Fast test for deformation
1251  float len = std::abs(slen);
1252  if (len > ar_beams[i].minmaxposnegstress)
1253  {
1254  if (ar_beams[i].bm_type == BEAM_NORMAL && ar_beams[i].bounded != SHOCK1 && k != 0.0f)
1255  {
1256  // Actual deformation tests
1257  if (slen > ar_beams[i].maxposstress && difftoBeamL < 0.0f) // compression
1258  {
1259  Real yield_length = ar_beams[i].maxposstress / k;
1260  Real deform = difftoBeamL + yield_length * (1.0f - ar_beams[i].plastic_coef);
1261  Real Lold = ar_beams[i].L;
1262  ar_beams[i].L += deform;
1263  ar_beams[i].L = std::max(MIN_BEAM_LENGTH, ar_beams[i].L);
1264  slen = slen - (slen - ar_beams[i].maxposstress) * 0.5f;
1265  len = slen;
1266  if (ar_beams[i].L > 0.0f && Lold > ar_beams[i].L)
1267  {
1268  ar_beams[i].maxposstress *= Lold / ar_beams[i].L;
1269  ar_beams[i].minmaxposnegstress = std::min(ar_beams[i].maxposstress, -ar_beams[i].maxnegstress);
1270  ar_beams[i].minmaxposnegstress = std::min(ar_beams[i].minmaxposnegstress, ar_beams[i].strength);
1271  }
1272  // For the compression case we do not remove any of the beam's
1273  // strength for structure stability reasons
1274  //ar_beams[i].strength += deform * k * 0.5f;
1275  if (m_beam_deform_debug_enabled)
1276  {
1277  RoR::Str<300> msg;
1278  msg << "[RoR|Diag] YYY Beam " << i << " just deformed with extension force "
1279  << len << " / " << ar_beams[i].strength << ". ";
1280  LogBeamNodes(msg, ar_beams[i]);
1281  RoR::Log(msg.ToCStr());
1282  }
1283  }
1284  else if (slen < ar_beams[i].maxnegstress && difftoBeamL > 0.0f) // expansion
1285  {
1286  Real yield_length = ar_beams[i].maxnegstress / k;
1287  Real deform = difftoBeamL + yield_length * (1.0f - ar_beams[i].plastic_coef);
1288  Real Lold = ar_beams[i].L;
1289  ar_beams[i].L += deform;
1290  slen = slen - (slen - ar_beams[i].maxnegstress) * 0.5f;
1291  len = -slen;
1292  if (Lold > 0.0f && ar_beams[i].L > Lold)
1293  {
1294  ar_beams[i].maxnegstress *= ar_beams[i].L / Lold;
1295  ar_beams[i].minmaxposnegstress = std::min(ar_beams[i].maxposstress, -ar_beams[i].maxnegstress);
1296  ar_beams[i].minmaxposnegstress = std::min(ar_beams[i].minmaxposnegstress, ar_beams[i].strength);
1297  }
1298  ar_beams[i].strength -= deform * k;
1299  if (m_beam_deform_debug_enabled)
1300  {
1301  RoR::Str<300> msg;
1302  msg << "[RoR|Diag] YYY Beam " << i << " just deformed with extension force "
1303  << len << " / " << ar_beams[i].strength << ". ";
1304  LogBeamNodes(msg, ar_beams[i]);
1305  RoR::Log(msg.ToCStr());
1306  }
1307  }
1308  }
1309 
1310  // Test if the beam should break
1311  if (len > ar_beams[i].strength)
1312  {
1313  // Sound effect.
1314  // Sound volume depends on springs stored energy
1315  SOUND_MODULATE(ar_instance_id, SS_MOD_BREAK, 0.5 * k * difftoBeamL * difftoBeamL);
1316  SOUND_PLAY_ONCE(ar_instance_id, SS_TRIG_BREAK);
1317 
1318  //Break the beam only when it is not connected to a node
1319  //which is a part of a collision triangle and has 2 "live" beams or less
1320  //connected to it.
1321  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)))
1322  {
1323  slen = 0.0f;
1324  ar_beams[i].bm_broken = true;
1325  ar_beams[i].bm_disabled = true;
1326 
1327  if (m_beam_break_debug_enabled)
1328  {
1329  RoR::Str<200> msg;
1330  msg << "[RoR|Diag] XXX Beam " << i << " just broke with force " << len << " / " << ar_beams[i].strength << ". ";
1331  LogBeamNodes(msg, ar_beams[i]);
1332  App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE, msg.ToCStr());
1333  }
1334 
1335  // detachergroup check: beam[i] is already broken, check detacher group# == 0/default skip the check ( performance bypass for beams with default setting )
1336  // only perform this check if this is a master detacher beams (positive detacher group id > 0)
1337  if (ar_beams[i].detacher_group > 0)
1338  {
1339  // cycle once through the other beams
1340  for (int j = 0; j < ar_num_beams; j++)
1341  {
1342  // beam[i] detacher group# == checked beams detacher group# -> delete & disable checked beam
1343  // do this with all master(positive id) and minor(negative id) beams of this detacher group
1344  if (abs(ar_beams[j].detacher_group) == ar_beams[i].detacher_group)
1345  {
1346  ar_beams[j].bm_broken = true;
1347  ar_beams[j].bm_disabled = true;
1348  if (m_beam_break_debug_enabled)
1349  {
1350  App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE,
1351  "Deleting Detacher BeamID: " + TOSTRING(j) + ", Detacher Group: " + TOSTRING(ar_beams[i].detacher_group)+ ", actor ID: " + TOSTRING(ar_instance_id));
1352  }
1353  }
1354  }
1355  // cycle once through all wheeldetachers
1356  for (wheeldetacher_t const& wheeldetacher: ar_wheeldetachers)
1357  {
1358  if (wheeldetacher.wd_detacher_group == ar_beams[i].detacher_group)
1359  {
1360  ar_wheels[wheeldetacher.wd_wheel_id].wh_is_detached = true;
1361  if (m_beam_break_debug_enabled)
1362  {
1363  App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE,
1364  "Detaching wheel ID: " + TOSTRING(wheeldetacher.wd_wheel_id) + ", Detacher Group: " + TOSTRING(ar_beams[i].detacher_group)+ ", actor ID: " + TOSTRING(ar_instance_id));
1365  }
1366  }
1367  }
1368  }
1369  }
1370  else
1371  {
1372  ar_beams[i].strength = 2.0f * ar_beams[i].minmaxposnegstress;
1373  }
1374 
1375  // something broke, check buoyant hull
1376  for (int mk = 0; mk < ar_num_buoycabs; mk++)
1377  {
1378  int tmpv = ar_buoycabs[mk] * 3;
1379  if (ar_buoycab_types[mk] == Buoyance::BUOY_DRAGONLY)
1380  continue;
1381  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]]) &&
1382  (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]]))
1383  {
1384  m_buoyance->sink = true;
1385  }
1386  }
1387  }
1388  }
1389 
1390  // At last update the beam forces
1391  Vector3 f = dis;
1392  f *= (slen * inverted_dislen);
1393  ar_beams[i].p1->Forces += f;
1394  ar_beams[i].p2->Forces -= f;
1395  }
1396  }
1397 }
1398 
1399 void Actor::CalcBeamsInterActor()
1400 {
1401  for (int i = 0; i < static_cast<int>(ar_inter_beams.size()); i++)
1402  {
1403  if (!ar_inter_beams[i]->bm_disabled && ar_inter_beams[i]->bm_inter_actor)
1404  {
1405  // Calculate beam length
1406  Vector3 dis = ar_inter_beams[i]->p1->AbsPosition - ar_inter_beams[i]->p2->AbsPosition;
1407 
1408  Real dislen = dis.squaredLength();
1409  Real inverted_dislen = fast_invSqrt(dislen);
1410 
1411  dislen *= inverted_dislen;
1412 
1413  // Calculate beam's deviation from normal
1414  Real difftoBeamL = dislen - ar_inter_beams[i]->L;
1415 
1416  Real k = ar_inter_beams[i]->k;
1417  Real d = ar_inter_beams[i]->d;
1418 
1419  if (ar_inter_beams[i]->bounded == ROPE && difftoBeamL < 0.0f)
1420  {
1421  k = 0.0f;
1422  d *= 0.1f;
1423  }
1424 
1425  // Calculate beam's rate of change
1426  Vector3 v = ar_inter_beams[i]->p1->Velocity - ar_inter_beams[i]->p2->Velocity;
1427 
1428  float slen = -k * (difftoBeamL) - d * v.dotProduct(dis) * inverted_dislen;
1429  ar_inter_beams[i]->stress = slen;
1430 
1431  // Fast test for deformation
1432  float len = std::abs(slen);
1433  if (len > ar_inter_beams[i]->minmaxposnegstress)
1434  {
1435  if (ar_inter_beams[i]->bm_type == BEAM_NORMAL && ar_inter_beams[i]->bounded != SHOCK1 && k != 0.0f)
1436  {
1437  // Actual deformation tests
1438  if (slen > ar_inter_beams[i]->maxposstress && difftoBeamL < 0.0f) // compression
1439  {
1440  Real yield_length = ar_inter_beams[i]->maxposstress / k;
1441  Real deform = difftoBeamL + yield_length * (1.0f - ar_inter_beams[i]->plastic_coef);
1442  Real Lold = ar_inter_beams[i]->L;
1443  ar_inter_beams[i]->L += deform;
1444  ar_inter_beams[i]->L = std::max(MIN_BEAM_LENGTH, ar_inter_beams[i]->L);
1445  slen = slen - (slen - ar_inter_beams[i]->maxposstress) * 0.5f;
1446  len = slen;
1447  if (ar_inter_beams[i]->L > 0.0f && Lold > ar_inter_beams[i]->L)
1448  {
1449  ar_inter_beams[i]->maxposstress *= Lold / ar_inter_beams[i]->L;
1450  ar_inter_beams[i]->minmaxposnegstress = std::min(ar_inter_beams[i]->maxposstress, -ar_inter_beams[i]->maxnegstress);
1451  ar_inter_beams[i]->minmaxposnegstress = std::min(ar_inter_beams[i]->minmaxposnegstress, ar_inter_beams[i]->strength);
1452  }
1453  // For the compression case we do not remove any of the beam's
1454  // strength for structure stability reasons
1455  //ar_inter_beams[i]->strength += deform * k * 0.5f;
1456  if (m_beam_deform_debug_enabled)
1457  {
1458  RoR::Str<300> msg;
1459  msg << "[RoR|Diag] YYY Beam " << i << " just deformed with extension force "
1460  << len << " / " << ar_inter_beams[i]->strength << ". ";
1461  LogBeamNodes(msg, (*ar_inter_beams[i]));
1462  RoR::Log(msg.ToCStr());
1463  }
1464  }
1465  else if (slen < ar_inter_beams[i]->maxnegstress && difftoBeamL > 0.0f) // expansion
1466  {
1467  Real yield_length = ar_inter_beams[i]->maxnegstress / k;
1468  Real deform = difftoBeamL + yield_length * (1.0f - ar_inter_beams[i]->plastic_coef);
1469  Real Lold = ar_inter_beams[i]->L;
1470  ar_inter_beams[i]->L += deform;
1471  slen = slen - (slen - ar_inter_beams[i]->maxnegstress) * 0.5f;
1472  len = -slen;
1473  if (Lold > 0.0f && ar_inter_beams[i]->L > Lold)
1474  {
1475  ar_inter_beams[i]->maxnegstress *= ar_inter_beams[i]->L / Lold;
1476  ar_inter_beams[i]->minmaxposnegstress = std::min(ar_inter_beams[i]->maxposstress, -ar_inter_beams[i]->maxnegstress);
1477  ar_inter_beams[i]->minmaxposnegstress = std::min(ar_inter_beams[i]->minmaxposnegstress, ar_inter_beams[i]->strength);
1478  }
1479  ar_inter_beams[i]->strength -= deform * k;
1480  if (m_beam_deform_debug_enabled)
1481  {
1482  RoR::Str<300> msg;
1483  msg << "[RoR|Diag] YYY Beam " << i << " just deformed with extension force "
1484  << len << " / " << ar_inter_beams[i]->strength << ". ";
1485  LogBeamNodes(msg, (*ar_inter_beams[i]));
1486  RoR::Log(msg.ToCStr());
1487  }
1488  }
1489  }
1490 
1491  // Test if the beam should break
1492  if (len > ar_inter_beams[i]->strength)
1493  {
1494  // Sound effect.
1495  // Sound volume depends on springs stored energy
1496  SOUND_MODULATE(ar_instance_id, SS_MOD_BREAK, 0.5 * k * difftoBeamL * difftoBeamL);
1497  SOUND_PLAY_ONCE(ar_instance_id, SS_TRIG_BREAK);
1498 
1499  //Break the beam only when it is not connected to a node
1500  //which is a part of a collision triangle and has 2 "live" beams or less
1501  //connected to it.
1502  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)))
1503  {
1504  slen = 0.0f;
1505  ar_inter_beams[i]->bm_broken = true;
1506  ar_inter_beams[i]->bm_disabled = true;
1507 
1508  if (m_beam_break_debug_enabled)
1509  {
1510  RoR::Str<200> msg;
1511  msg << "Beam " << i << " just broke with force " << len << " / " << ar_inter_beams[i]->strength << ". ";
1512  LogBeamNodes(msg, (*ar_inter_beams[i]));
1513  App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_NOTICE, msg.ToCStr());
1514  }
1515  }
1516  else
1517  {
1518  ar_inter_beams[i]->strength = 2.0f * ar_inter_beams[i]->minmaxposnegstress;
1519  }
1520  }
1521  }
1522 
1523  // At last update the beam forces
1524  Vector3 f = dis;
1525  f *= (slen * inverted_dislen);
1526  ar_inter_beams[i]->p1->Forces += f;
1527  ar_inter_beams[i]->p2->Forces -= f;
1528  }
1529  }
1530 }
1531 
1532 void Actor::CalcNodes()
1533 {
1534  const auto water = App::GetGameContext()->GetTerrain()->getWater();
1535  const float gravity = App::GetGameContext()->GetTerrain()->getGravity();
1536  m_water_contact = false;
1537 
1538  for (NodeNum_t i = 0; i < ar_num_nodes; i++)
1539  {
1540  // COLLISION
1541  if (!ar_nodes[i].nd_no_ground_contact)
1542  {
1543  Vector3 oripos = ar_nodes[i].AbsPosition;
1544  bool contacted = App::GetGameContext()->GetTerrain()->GetCollisions()->groundCollision(&ar_nodes[i], PHYSICS_DT);
1545  contacted = contacted | App::GetGameContext()->GetTerrain()->GetCollisions()->nodeCollision(&ar_nodes[i], PHYSICS_DT);
1546  ar_nodes[i].nd_has_ground_contact = contacted;
1547  if (ar_nodes[i].nd_has_ground_contact || ar_nodes[i].nd_has_mesh_contact)
1548  {
1549  ar_last_fuzzy_ground_model = ar_nodes[i].nd_last_collision_gm;
1550  // Reverts: commit/d11a88142f737528638bd357c38d717c85cebba6#diff-4003254e55aec2c60d21228f375f2a2dL1153
1551  // Fixes: Gavril Omega Six sliding on ground on the simple2 spawn
1552  // ar_nodes[i].AbsPosition - oripos is always zero ... dark floating point magic
1553  ar_nodes[i].RelPosition += ar_nodes[i].AbsPosition - oripos;
1554  }
1555  }
1556 
1557  if (i == ar_main_camera_node_pos)
1558  {
1559  // record g forces on cameras
1560  m_camera_gforces_accu += ar_nodes[i].Forces / ar_nodes[i].mass;
1561  }
1562 
1563  // integration
1564  if (!ar_nodes[i].nd_immovable)
1565  {
1566  ar_nodes[i].Velocity += ar_nodes[i].Forces / ar_nodes[i].mass * PHYSICS_DT;
1567  ar_nodes[i].RelPosition += ar_nodes[i].Velocity * PHYSICS_DT;
1568  ar_nodes[i].AbsPosition = ar_origin;
1569  ar_nodes[i].AbsPosition += ar_nodes[i].RelPosition;
1570  }
1571 
1572  // prepare next loop (optimisation)
1573  // we start forces from zero
1574  // start with gravity
1575  ar_nodes[i].Forces = Vector3(0, ar_nodes[i].mass * gravity, 0);
1576 
1577  Real approx_speed = approx_sqrt(ar_nodes[i].Velocity.squaredLength());
1578 
1579  // anti-explsion guard (mach 20)
1580  if (approx_speed > 6860 && !m_ongoing_reset)
1581  {
1582  ActorModifyRequest* rq = new ActorModifyRequest; // actor exploded, schedule reset
1583  rq->amr_actor = this->ar_instance_id;
1584  rq->amr_type = ActorModifyRequest::Type::RESET_ON_SPOT;
1586  m_ongoing_reset = true;
1587  }
1588 
1589  if (m_fusealge_airfoil)
1590  {
1591  // aerodynamics on steroids!
1592  ar_nodes[i].Forces += ar_fusedrag;
1593  }
1594  else if (!ar_disable_aerodyn_turbulent_drag)
1595  {
1596  // add viscous drag (turbulent model)
1597  Real defdragxspeed = DEFAULT_DRAG * approx_speed;
1598  Vector3 drag = -defdragxspeed * ar_nodes[i].Velocity;
1599  // plus: turbulences
1600  Real maxtur = defdragxspeed * approx_speed * 0.005f;
1601  drag += maxtur * Vector3(frand_11(), frand_11(), frand_11());
1602  ar_nodes[i].Forces += drag;
1603  }
1604 
1605  if (water)
1606  {
1607  const bool is_under_water = water->IsUnderWater(ar_nodes[i].AbsPosition);
1608  if (is_under_water)
1609  {
1610  m_water_contact = true;
1611  if (ar_num_buoycabs == 0)
1612  {
1613  // water drag (turbulent)
1614  ar_nodes[i].Forces -= (DEFAULT_WATERDRAG * approx_speed) * ar_nodes[i].Velocity;
1615  // basic buoyance
1616  ar_nodes[i].Forces += ar_nodes[i].buoyancy * Vector3::UNIT_Y;
1617  }
1618  // engine stall
1619  if (i == ar_cinecam_node[0] && ar_engine)
1620  {
1621  ar_engine->StopEngine();
1622  }
1623  }
1624  ar_nodes[i].nd_under_water = is_under_water;
1625  }
1626  }
1627 }
1628 
1630 {
1631  // Test eventbox collision and extra 'only wheel nodes' filtering condition
1632  // ------------------------------------------------------------------------
1633 
1635  && (cbox->event_filter != EVENT_TRUCK_WHEELS || node.nd_tyre_node);
1636 }
1637 
1638 void Actor::CalcEventBoxes()
1639 {
1640  // Assumption: node positions and bounding boxes are up to date.
1641  // First, find all collision boxes which this actor's bounding box touches (potential collisions)
1642  // For each potential collision box:
1643  // * if a collision was already recorded, test the recorded node. If still colliding, do nothing.
1644  // * otherwise loop nodes until collision is found. If not, clear the collision record.
1645  // ----------------------------------------------------------------------------------------------
1646 
1647  m_potential_eventboxes.clear();
1648  App::GetGameContext()->GetTerrain()->GetCollisions()->findPotentialEventBoxes(this, m_potential_eventboxes);
1649 
1650  for (collision_box_t* cbox : m_potential_eventboxes)
1651  {
1652  // Find existing collision record
1653  bool has_collision = false;
1654  bool do_callback_enter = true;
1655  bool do_callback_exit = false;
1656  auto itor = m_active_eventboxes.begin();
1657  while (itor != m_active_eventboxes.end())
1658  {
1659  if (itor->first == cbox)
1660  {
1661  // Existing record found - check if the node still collides
1662  has_collision = TestNodeEventBoxCollision(ar_nodes[itor->second], cbox);
1663  if (!has_collision)
1664  {
1665  // Erase the collision record
1666  itor = m_active_eventboxes.erase(itor);
1667  // Prevent invoking the same ENTER callback again
1668  do_callback_enter = false;
1669  do_callback_exit = true;
1670  }
1671  break;
1672  }
1673  else
1674  {
1675  itor++;
1676  }
1677  }
1678 
1679  if (!has_collision)
1680  {
1681  // Find if any node collides
1682  for (NodeNum_t i = 0; i < ar_num_nodes; i++)
1683  {
1684  has_collision = TestNodeEventBoxCollision(ar_nodes[i], cbox);
1685  if (has_collision)
1686  {
1687  do_callback_exit = false;
1688  // Add new collision record
1689  m_active_eventboxes.push_back(std::make_pair(cbox, i));
1690  // Do the script callback
1691  if (do_callback_enter)
1692  {
1693  // IMPORTANT - this function is executed under physics thread pool, do not run script callbacks synchronously!
1694  eventsource_t& eventsource = App::GetGameContext()->GetTerrain()->GetCollisions()->getEventSource(cbox->eventsourcenum);
1695 
1696  // The classic optional per-object script handler.
1697  ScriptCallbackArgs* args = new ScriptCallbackArgs( &eventsource, i );
1699 
1700  // The new EVENTBOX_ENTER event.
1701  TRIGGER_EVENT_ASYNC(SE_EVENTBOX_ENTER, 0, ar_instance_id, ar_nodes[i].pos, 0, eventsource.es_instance_name, eventsource.es_box_name, "", "");
1702  }
1703  break;
1704  }
1705  }
1706  }
1707 
1708  if (do_callback_exit)
1709  {
1710  // IMPORTANT - this function is executed under physics thread pool, do not run script callbacks synchronously!
1711  const eventsource_t& eventsource = App::GetGameContext()->GetTerrain()->GetCollisions()->getEventSource(cbox->eventsourcenum);
1712  TRIGGER_EVENT_ASYNC(SE_EVENTBOX_EXIT, 0, ar_instance_id, 0, 0, eventsource.es_instance_name, eventsource.es_box_name, "", "");
1713  }
1714  }
1715 }
1716 
1717 void Actor::CalcHooks()
1718 {
1719  //locks - this is not active in network mode
1720  for (std::vector<hook_t>::iterator it = ar_hooks.begin(); it != ar_hooks.end(); it++)
1721  {
1722  //we need to do this here to avoid countdown speedup by triggers
1723  it->hk_timer = std::max(0.0f, it->hk_timer - PHYSICS_DT);
1724 
1725  if (it->hk_lock_node && it->hk_locked == PRELOCK)
1726  {
1727  if (it->hk_beam->L < it->hk_min_length)
1728  {
1729  //shortlimit reached -> status LOCKED
1730  it->hk_locked = LOCKED;
1731  }
1732  else
1733  {
1734  //shorten the connecting beam slowly to locking minrange
1735  if (it->hk_beam->L > it->hk_lockspeed && fabs(it->hk_beam->stress) < it->hk_maxforce)
1736  {
1737  it->hk_beam->L = (it->hk_beam->L - it->hk_lockspeed);
1738  }
1739  else
1740  {
1741  if (fabs(it->hk_beam->stress) < it->hk_maxforce)
1742  {
1743  it->hk_beam->L = 0.001f;
1744  //locking minrange or stress exeeded -> status LOCKED
1745  it->hk_locked = LOCKED;
1746  }
1747  else
1748  {
1749  if (it->hk_nodisable)
1750  {
1751  //force exceed, but beam is set to nodisable, just lock it in this position
1752  it->hk_locked = LOCKED;
1753  }
1754  else
1755  {
1756  //force exceeded, reset the hook node
1758  rq->alr_actor_instance_id = ar_instance_id;
1759  rq->alr_type = ActorLinkingRequestType::HOOK_UNLOCK;
1761  }
1762  }
1763  }
1764  }
1765  }
1766  }
1767 }
1768 
1769 void Actor::CalcRopes()
1770 {
1771  for (auto r : ar_ropes)
1772  {
1773  if (r.rp_locked == LOCKED && r.rp_locked_ropable)
1774  {
1775  auto locked_node = r.rp_locked_ropable->node;
1776  r.rp_beam->p2->AbsPosition = locked_node->AbsPosition;
1777  r.rp_beam->p2->RelPosition = locked_node->AbsPosition - ar_origin;
1778  r.rp_beam->p2->Velocity = locked_node->Velocity;
1779  locked_node->Forces += r.rp_beam->p2->Forces;
1780  r.rp_beam->p2->Forces = Vector3::ZERO;
1781  }
1782  }
1783 }
GameContext.h
Game state manager and message-queue provider.
RoR::ActorLinkingRequest::alr_hook_group
int alr_hook_group
Definition: SimData.h:922
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
TestNodeEventBoxCollision
bool TestNodeEventBoxCollision(const node_t &node, collision_box_t *cbox)
Definition: ActorForcesEuler.cpp:1629
RoR::App::GetSoundScriptManager
SoundScriptManager * GetSoundScriptManager()
Definition: Application.cpp:277
RoR::SHOCK3
@ SHOCK3
shock3
Definition: SimData.h:110
RoR::commandbeam_t::cmb_is_force_restricted
bool cmb_is_force_restricted
Attribute defined in truckfile.
Definition: SimData.h:564
RoR::node_t::Velocity
Ogre::Vector3 Velocity
Definition: SimData.h:295
RoR::HYDRO_FLAG_DIR
@ HYDRO_FLAG_DIR
Definition: SimData.h:132
RoR::MSG_SIM_MODIFY_ACTOR_REQUESTED
@ MSG_SIM_MODIFY_ACTOR_REQUESTED
Payload = RoR::ActorModifyRequest* (owner)
Definition: Application.h:120
RoR::MACHINE
@ MACHINE
its a machine
Definition: SimData.h:96
RoR::MSG_SIM_SCRIPT_CALLBACK_QUEUED
@ MSG_SIM_SCRIPT_CALLBACK_QUEUED
Payload = RoR::ScriptCallbackArgs* (owner)
Definition: Application.h:127
RoR::HYDRO_FLAG_REV_AILERON
@ HYDRO_FLAG_REV_AILERON
Definition: SimData.h:136
RoR::HYDRO_FLAG_SPEED
@ HYDRO_FLAG_SPEED
Definition: SimData.h:131
RoR::SS_TRIG_ALB_ACTIVE
@ SS_TRIG_ALB_ACTIVE
Definition: SoundScriptManager.h:103
RoR::node_t::AbsPosition
Ogre::Vector3 AbsPosition
absolute position in the world (shaky)
Definition: SimData.h:294
RoR::EVENT_TRUCK_WHEELS
@ EVENT_TRUCK_WHEELS
'truck_wheels' ~ Triggered only by wheel nodes of land vehicle (ActorType::TRUCK)
Definition: SimData.h:52
RoR::ActorLinkingRequest
Estabilishing a physics linkage between 2 actors modifies a global linkage table and triggers immedia...
Definition: SimData.h:917
RoR::beam_t::p1
node_t * p1
Definition: SimData.h:335
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::hydrobeam_t::hb_ref_length
float hb_ref_length
Idle length in meters.
Definition: SimData.h:591
RoR::NODENUM_INVALID
static const NodeNum_t NODENUM_INVALID
Definition: ForwardDeclarations.h:53
RoR::wheel_t
Definition: SimData.h:421
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:1212
RoR::BEAM_NORMAL
@ BEAM_NORMAL
Definition: SimData.h:71
RoR::ActorLinkingRequest::alr_type
ActorLinkingRequestType alr_type
Definition: SimData.h:920
RoR::Collisions::findPotentialEventBoxes
void findPotentialEventBoxes(Actor *actor, CollisionBoxPtrVec &out_boxes)
Definition: Collisions.cpp:1088
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:557
RoR::SS_MOD_PUMP
@ SS_MOD_PUMP
Definition: SoundScriptManager.h:138
RoR::App::io_analog_sensitivity
CVar * io_analog_sensitivity
Definition: Application.cpp:190
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:1125
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:85
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:293
Differentials.h
RoR::commandbeam_t::cmb_state
std::shared_ptr< commandbeam_state_t > cmb_state
Definition: SimData.h:571
RoR::collision_box_t
Definition: SimData.h:714
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:344
RoR::hydrobeam_t::hb_anim_flags
int hb_anim_flags
Animators (beams updating length based on simulation variables)
Definition: SimData.h:594
EngineSim.h
RoR::beam_t::refL
float refL
reference length
Definition: SimData.h:356
RoR::HYDRO_FLAG_REV_RUDDER
@ HYDRO_FLAG_REV_RUDDER
Definition: SimData.h:137
RoR::ROPE
@ ROPE
Definition: SimData.h:113
Replay.h
RoR::DifferentialData::delta_rotation
float delta_rotation
Definition: Differentials.h:37
RoR::ActorModifyRequest::amr_actor
ActorInstanceID_t amr_actor
Definition: SimData.h:886
RoR::SHOCK_FLAG_LACTIVE
@ SHOCK_FLAG_LACTIVE
Definition: SimData.h:198
RoR::collision_box_t::event_filter
CollisionEventFilter event_filter
Definition: SimData.h:721
TOSTRING
#define TOSTRING(x)
Definition: Application.h:56
RoR::beam_t
Simulation: An edge in the softbody structure.
Definition: SimData.h:330
ScrewProp.h
RoR::PRELOCK
@ PRELOCK
prelocking, attraction forces in action
Definition: SimData.h:84
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:449
CmdKeyInertia.h
RoR::commandbeam_t::cmb_is_contraction
bool cmb_is_contraction
Attribute defined at spawn.
Definition: SimData.h:563
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:133
RoR::Str
Wrapper for classic c-string (local buffer) Refresher: strlen() excludes '\0' terminator; strncat() A...
Definition: Str.h:35
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:870
RoR::commandbeam_t::cmb_center_length
float cmb_center_length
Attr computed at spawn.
Definition: SimData.h:558
RoR::commandbeam_t::cmb_needs_engine
bool cmb_needs_engine
Attribute defined in truckfile.
Definition: SimData.h:565
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:65
RoR::hydrobeam_t::hb_inertia
RoR::CmdKeyInertia hb_inertia
Definition: SimData.h:596
RoR::hydrobeam_t
Definition: SimData.h:588
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:559
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:304
RoR::node_t::nd_tyre_node
bool nd_tyre_node
Attr; This node is part of a tyre (note some wheel types don't use rim nodes at all)
Definition: SimData.h:311
SOUND_START
#define SOUND_START(_ACTOR_, _TRIG_)
Definition: SoundScriptManager.h:35
RoR::SHOCK2
@ SHOCK2
shock2
Definition: SimData.h:109
Application.h
Central state/object manager and communications hub.
RoR::App::GetConsole
Console * GetConsole()
Definition: Application.cpp:270
SoundScriptManager.h
RoR::node_t
Physics: A vertex in the softbody structure.
Definition: SimData.h:286
RoR::commandbeam_t::cmb_boundary_length
float cmb_boundary_length
Attr; Maximum/minimum length proportional to orig. len.
Definition: SimData.h:560
RoR::App::GetGameContext
GameContext * GetGameContext()
Definition: Application.cpp:280
RoR::ScriptCallbackArgs
Definition: ScriptEngine.h:98
RoR::HYDRO_FLAG_ELEVATOR
@ HYDRO_FLAG_ELEVATOR
Definition: SimData.h:135
RoR::wheel_t::wh_speed
Ogre::Real wh_speed
Current wheel speed in m/s.
Definition: SimData.h:444
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:568
SOUND_STOP
#define SOUND_STOP(_ACTOR_, _TRIG_)
Definition: SoundScriptManager.h:36
RoR::SHOCK_FLAG_RACTIVE
@ SHOCK_FLAG_RACTIVE
Definition: SimData.h:199
RoR::SUPPORTBEAM
@ SUPPORTBEAM
Definition: SimData.h:112
RoR::wheeldetacher_t
Definition: SimData.h:465
RoR::hydrobeam_t::hb_beam_index
uint16_t hb_beam_index
Index to Actor::ar_beams array.
Definition: SimData.h:590
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:593
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:926
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:134
Buoyance.h
RoR::BEAM_HYDRO
@ BEAM_HYDRO
Definition: SimData.h:72
RoR::MSG_SIM_ACTOR_LINKING_REQUESTED
@ MSG_SIM_ACTOR_LINKING_REQUESTED
Payload = RoR::ActorLinkingRequest* (owner)
Definition: Application.h:128
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:111
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:189
RoR::commandbeam_t
Definition: SimData.h:554
RoR::HYDRO_FLAG_REV_ELEVATOR
@ HYDRO_FLAG_REV_ELEVATOR
Definition: SimData.h:138
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:919
RoR::Terrain::getGravity
float getGravity() const
Definition: Terrain.h:96
RoR::beam_t::p2
node_t * p2
Definition: SimData.h:336
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:569
RoR::Collisions::isInside
bool isInside(Ogre::Vector3 pos, const Ogre::String &inst, const Ogre::String &box, float border=0)
Definition: Collisions.cpp:1169
AeroEngine.h
RoR::hydrobeam_t::hb_speed
float hb_speed
Rate of change.
Definition: SimData.h:592
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:567
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:108
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:556
RoR::commandbeam_t::cmb_is_autocentering
bool cmb_is_autocentering
Attribute defined in truckfile.
Definition: SimData.h:566
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:887
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:419
Water.h
LogBeamNodes
void LogBeamNodes(RoR::Str< L > &msg, beam_t &beam)
Definition: ActorForcesEuler.cpp:1131
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:352
RoR::GameContext::GetTerrain
const TerrainPtr & GetTerrain()
Definition: GameContext.h:117
RoR::App::io_hydro_coupling
CVar * io_hydro_coupling
Definition: Application.cpp:199