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