Rigs of Rods 2023.09
Soft-body Physics Simulation
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
Loading...
Searching...
No Matches
TurboProp.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 "TurboProp.h"
22
23#include "Actor.h"
24#include "Airfoil.h"
25#include "GfxActor.h"
26#include "GfxScene.h"
27#include "ScriptEngine.h"
28#include "SoundScriptManager.h"
29#include "SimData.h"
30
31#include <fmt/format.h>
32#include <Ogre.h>
33
34using namespace Ogre;
35using namespace RoR;
36
38 ActorPtr a,
39 const char* propname,
40 NodeNum_t nr,
41 NodeNum_t nb,
42 NodeNum_t np1,
43 NodeNum_t np2,
44 NodeNum_t np3,
45 NodeNum_t np4,
46 NodeNum_t tqn,
47 float power,
48 Ogre::String const& propfoilname,
49 bool disable_smoke,
50 bool ispiston,
51 float fpitch
52):
53 m_actor(a)
54{
59 // np3, np4, tqn ~ can be invalid
60
61 failed = false;
62 failedold = false;
63#ifdef USE_OPENAL
65 {
75 }
76#endif //OPENAL
77 is_piston = ispiston;
78 fixed_pitch = fpitch;
79 torquenode = tqn;
80 rotenergy = 0;
81 pitchspeed = 5.0; //in degree/sec
82 numblades = 4;
83 twistmap[0] = 2;
84 twistmap[1] = 6;
85 twistmap[2] = 10;
86 twistmap[3] = 19;
87 twistmap[4] = 32;
88 noderef = nr;
89 nodeback = nb;
90 nodep[0] = np1;
91 nodep[1] = np2;
93 {
94 Plane pplane = Plane((m_actor->ar_nodes[nr].RelPosition - m_actor->ar_nodes[nb].RelPosition).normalisedCopy(), 0.0);
95 Vector3 apos = pplane.projectVector(m_actor->ar_nodes[nr].RelPosition);
96 Vector3 tpos = pplane.projectVector(m_actor->ar_nodes[tqn].RelPosition);
97 torquedist = (apos - tpos).length();
98 }
99 else
100 torquedist = 1.0;
101 if (np3 == NODENUM_INVALID)
102 numblades = 2;
103 else
104 {
105 nodep[2] = np3;
106 if (np4 == NODENUM_INVALID)
107 numblades = 3;
108 else
109 nodep[3] = np4;
110 }
111 propwash = 0;
112 timer = 0;
113 lastflip = 0;
114 warmupstart = 0.0;
115 warmuptime = 14.0;
116 warmup = false;
117 airfoil = new Airfoil(propfoilname);
118 fullpower = power;
119 max_torque = 9549.3 * fullpower / 1000.0;
120 indicated_torque = 0.0;
122 //bladewidth=radius/5.75;
123 bladewidth = 0.4;
124 proparea = 3.14159 * radius * radius;
125 airdensity = 1.225;
126 // regspeed=245.0*60.0/(2.0*3.14159*radius);//1010.0 //compute regulated speed for a tip speed of 245m/s
127 regspeed = 1010;
128 maxrevpitch = -5.0;
129 //smoke visual
130 if (disable_smoke)
131 {
132 smokeNode = 0;
133 smokePS = 0;
134 }
135 else
136 {
137 char dename[256];
138 sprintf(dename, "%s-smoke", propname);
139 smokeNode = App::GetGfxScene()->GetSceneManager()->getRootSceneNode()->createChildSceneNode();
140 smokePS = App::GetGfxScene()->GetSceneManager()->createParticleSystem(dename, "tracks/TurbopropSmoke");
141 if (smokePS)
142 {
143 smokePS->setVisibilityFlags(DEPTHMAP_DISABLED); // disable particles in depthmap
144 smokeNode->attachObject(smokePS);
145 smokePS->setCastShadows(false);
146 }
147 }
148
149 reset();
150}
151
153{
154 //A fast work around
155 //
159
160 if (airfoil != nullptr)
161 {
162 delete airfoil;
163 }
164
165 if (smokePS != nullptr)
166 {
167 smokePS->removeAllEmitters();
168 }
169}
170
172{
173 RoR::NodeSB* node_buf = gfx_m_actor->GetSimNodeBuffer();
174
175 //smoke
176 if (smokeNode)
177 {
179 smokeNode->setPosition(node_buf[nodeback].AbsPosition);
180 ParticleEmitter* emit = smokePS->getEmitter(0);
181 Vector3 dir = node_buf[nodeback].AbsPosition - node_buf[noderef].AbsPosition;
182 emit->setDirection(dir);
183 emit->setParticleVelocity(propwash - propwash / 10, propwash + propwash / 10);
184 if (!failed)
185 {
186 if (ignition)
187 {
188 emit->setEnabled(true);
189 emit->setColour(ColourValue(0.0, 0.0, 0.0, 0.03 + throtle * 0.05));
190 emit->setTimeToLive((0.03 + throtle * 0.05) / 0.1);
191 }
192 else
193 {
194 emit->setEnabled(false);
195 }
196 }
197 else
198 {
199 emit->setDirection(Vector3(0, 1, 0));
200 emit->setParticleVelocity(5, 9);
201 emit->setEnabled(true);
202 emit->setColour(ColourValue(0.0, 0.0, 0.0, 0.1));
203 emit->setTimeToLive(0.1 / 0.1);
204 }
205 }
206
207#ifdef USE_ANGELSCRIPT
208 if (failed != failedold)
209 {
212 }
213#endif
214}
215
216void Turboprop::setVisible(bool visible)
217{
218 if (smokePS)
219 smokePS->setVisible(visible);
220}
221
222void Turboprop::updateForces(float dt, int doUpdate)
223{
224 if (doUpdate)
225 {
226 //tropospheric model valid up to 11.000m (33.000ft)
227 float altitude = m_actor->ar_nodes[noderef].AbsPosition.y;
228 //float sea_level_temperature=273.15+15.0; //in Kelvin
229 float sea_level_pressure = 101325; //in Pa
230 //float airtemperature=sea_level_temperature-altitude*0.0065; //in Kelvin
231 float airpressure = sea_level_pressure * pow(1.0 - 0.0065 * altitude / 288.15, 5.24947); //in Pa
232 airdensity = airpressure * 0.0000120896;//1.225 at sea level
234 }
235
236 timer += dt;
237 //evaluate the rotation speed
238 float velacc = 0;
239 for (int i = 0; i < numblades; i++)
240 {
241 velacc += (m_actor->ar_nodes[nodep[i]].Velocity - m_actor->ar_nodes[noderef].Velocity).length();
242 }
243 rpm = (velacc / numblades) * RAD_PER_SEC_TO_RPM / radius;
244 //check for broken prop
245 Vector3 avg = Vector3::ZERO;
246 for (int i = 0; i < numblades; i++)
247 {
248 avg += m_actor->ar_nodes[nodep[i]].RelPosition;
249 }
250 avg = avg / numblades;
251 if ((avg - m_actor->ar_nodes[noderef].RelPosition).length() > 0.4)
252 {
253 failed = true;
254 }
255 //evaluate engine power
256 float enginepower = 0; //in kilo-Watt
257 float warmupfm_actor = 1.0;
258 if (warmup)
259 {
260 warmupfm_actor = (timer - warmupstart) / warmuptime;
261 if (warmupfm_actor >= 1.0)
262 warmup = false;
263 }
264 float revpenalty = 1.0;
265 if (reverse)
266 revpenalty = 0.5;
267 if (!failed && ignition)
268 enginepower = (0.0575 + throtle * revpenalty * 0.9425) * fullpower * warmupfm_actor;
269 //the magic formula
270 float enginecouple = 0.0; //in N.m
271 if (rpm > 10.0)
272 enginecouple = 9549.3 * enginepower / rpm;
273 else
274 enginecouple = 9549.3 * enginepower / 10.0;
275 indicated_torque = enginecouple;
276
278 {
280 Plane ppl = Plane(along, 0);
281 Vector3 orth = ppl.projectVector(m_actor->ar_nodes[noderef].RelPosition) - ppl.projectVector(m_actor->ar_nodes[torquenode].RelPosition);
282 Vector3 cdir = orth.crossProduct(along);
283 cdir.normalise();
284 m_actor->ar_nodes[torquenode].Forces += (enginecouple / torquedist) * cdir;
285 }
286
287 float tipforce = (enginecouple / radius) / numblades;
288 //okay, now we know the contribution from the engine
289
290 //pitch
291 if (fixed_pitch > 0)
293 else
294 {
295 if (!reverse)
296 {
297 if (throtle < 0.01)
298 {
299 //beta range
300 if (pitch > 0 && rpm < regspeed * 1.4)
301 pitch -= pitchspeed * dt;
302 if (rpm > regspeed * 1.4)
303 pitch += pitchspeed * dt;
304 }
305 else
306 {
307 float dpitch = rpm - regspeed;
308 if (dpitch > pitchspeed)
309 dpitch = pitchspeed;
310 if (dpitch < -pitchspeed)
311 dpitch = -pitchspeed;
312 if (!(dpitch < 0 && pitch < 0) && !(dpitch > 0 && pitch > 45))
313 pitch += dpitch * dt;
314 }
315 }
316 else
317 {
318 if (rpm < regspeed * 1.1)
319 {
320 if (pitch < -4.0)
321 pitch += pitchspeed * dt;
322 else
323 pitch -= pitchspeed * dt;
324 }
325 if (rpm > regspeed * 1.11)
326 {
327 pitch -= pitchspeed * dt;
328 }
329 }
330 }
331 if (!failed)
332 {
334 axis.normalise();
335 }
336 //estimate amount of energy
337 float estrotenergy = 0.5 * numblades * m_actor->ar_nodes[nodep[0]].mass * radius * radius * (rpm / RAD_PER_SEC_TO_RPM) * (rpm / RAD_PER_SEC_TO_RPM);
338 //for each blade
339 float totthrust = 0;
340 float tottorque = 0;
341 for (int i = 0; i < numblades; i++)
342 {
343 if (!failed && ignition)
344 {
345 Vector3 totaltipforce = Vector3::ZERO;
346 //span vector, left to right
348 spanv.normalise();
349 //chord vector, front to back
350 Vector3 refchordv = -axis.crossProduct(spanv);
351 //grab this for propulsive forces
352 Vector3 tipf = -refchordv;
353 totaltipforce += (tipforce - rpm / 10.0) * tipf; //add a bit of mechanical friction
354 //for each blade segment (there are 6 elements)
355 for (int j = 0; j < 5; j++) //outer to inner, the 6th blade element is ignored
356 {
357 //proportion
358 float proport = ((float)j + 0.5) / 6.0;
359 //evaluate wind direction
360 Vector3 wind = -(m_actor->ar_nodes[nodep[i]].Velocity * (1.0 - proport) + m_actor->ar_nodes[noderef].Velocity * proport);
361 float wspeed = wind.length();
362
363 Vector3 liftv = spanv.crossProduct(-wind);
364 liftv.normalise();
365 //rotate according to pitch
366 Vector3 chordv = Quaternion(Degree(pitch + twistmap[j] - 7.0), spanv) * refchordv;
367 //wing normal
368 Vector3 normv = chordv.crossProduct(spanv);
369 //calculate angle of attack
370 Vector3 pwind = Plane(Vector3::ZERO, normv, chordv).projectVector(wind);
371 Vector3 dumb;
372 Degree daoa;
373 chordv.getRotationTo(pwind).ToAngleAxis(daoa, dumb);
374 float aoa = daoa.valueDegrees();
375 float raoa = daoa.valueRadians();
376 if (dumb.dotProduct(spanv) > 0)
377 {
378 aoa = -aoa;
379 raoa = -raoa;
380 };
381 //get airfoil data
382 float cz, cx, cm;
383 airfoil->getparams(aoa, 1.0, 0.0, &cz, &cx, &cm);
384 //surface computation
385 float s = radius * bladewidth / 6.0;
386
387 //drag
388 //wforce=(8.0*cx+cx*cx/(3.14159*radius/0.4))*0.5*airdensity*wspeed*s*wind;
389 Vector3 eforce = (4.0 * cx + cx * cx / (3.14159 * radius / bladewidth)) * 0.5 * airdensity * wspeed * s * wind;
390 //lift
391 float lift = cz * 0.5 * airdensity * wspeed * wspeed * s;
392 eforce += lift * liftv;
393 totthrust += eforce.dotProduct(axis);
394
395 //apply forces
396 m_actor->ar_nodes[noderef].Forces += eforce * proport;
397 totaltipforce += eforce * (1.0 - proport);
398 }
399 tottorque += tipf.dotProduct(totaltipforce) * radius;
400 //correct amount of energy
401 float correctfm_actor = 0;
402 if (rpm > 100)
403 correctfm_actor = (rotenergy - estrotenergy) / (numblades * radius * dt * rpm / RAD_PER_SEC_TO_RPM);
404 if (correctfm_actor > 1000.0)
405 correctfm_actor = 1000.0;
406 if (correctfm_actor < -1000.0)
407 correctfm_actor = -1000.0;
408 m_actor->ar_nodes[nodep[i]].Forces += totaltipforce + correctfm_actor * tipf;
409 }
410 else if (!m_actor->ar_nodes[noderef].Velocity.isZeroLength())
411 {
412 //failed case
413 //add drag
414 Vector3 wind = -m_actor->ar_nodes[nodep[i]].Velocity;
415 // determine nodes speed and divide by engines speed (with some magic numbers for tuning) to keep it rotating longer when shutoff in flight and stop after a while when plane is stopped (on the ground)
416 float wspeed = (wind.length() / 15.0f) / (m_actor->ar_nodes[noderef].Velocity.length() / 2.0f);
417 m_actor->ar_nodes[nodep[i]].Forces += airdensity * wspeed * wind;
418 }
419 }
420 //compute the next energy level
421 rotenergy += (double)tottorque * dt * rpm / RAD_PER_SEC_TO_RPM;
422 // sprintf(debug, "pitch %i thrust %i totenergy=%i apparentenergy=%i", (int)pitch, (int)totthrust, (int)rotenergy, (int)estrotenergy);
423 //prop wash
424 float speed = m_actor->ar_nodes[noderef].Velocity.length();
425 float thrsign = 1.0;
426 if (totthrust < 0)
427 {
428 thrsign = -0.1;
429 totthrust = -totthrust;
430 };
431 if (!failed)
432 propwash = thrsign * sqrt(totthrust / (0.5 * airdensity * proparea) + speed * speed) - speed;
433 else
434 propwash = 0;
435 if (propwash < 0)
436 propwash = 0;
437}
438
440{
441 if (val > 1.0)
442 val = 1.0;
443 if (val < 0.0)
444 val = 0.0;
445 throtle = val;
447}
448
450{
451 return throtle;
452}
453
454void Turboprop::setRPM(float _rpm)
455{
456 rpm = _rpm;
457}
458
460{
461 rpm = 0;
462 throtle = 0;
463 failed = false;
464 ignition = false;
465 reverse = false;
466 pitch = 0;
467 rotenergy = 0;
468}
469
471{
472 throtle = 0;
473 reverse = !reverse;
474 pitch = 0;
475}
476
478{
479 reverse = val;
480}
481
483{
484 if (timer - lastflip < 0.3)
485 return;
487 if (ignition && !failed)
488 {
489 warmup = true;
492 }
493 else
494 {
496 }
497 lastflip = timer;
498}
#define ROR_ASSERT(_EXPR)
Definition Application.h:40
Manager for all visuals belonging to a single actor.
static const float RAD_PER_SEC_TO_RPM
Convert radian/second to RPM (60/2*PI)
Core data structures for simulation; Everything affected by by either physics, network or user intera...
#define SOUND_START(_ACTOR_, _TRIG_)
#define SOUND_STOP(_ACTOR_, _TRIG_)
#define SOUND_MODULATE(_ACTOR_, _MOD_, _VALUE_)
node_t * ar_nodes
Definition Actor.h:330
int ar_num_aeroengines
Definition Actor.h:389
ActorInstanceID_t ar_instance_id
Static attr; session-unique ID.
Definition Actor.h:429
Represents an airfoil http://en.wikipedia.org/wiki/Airfoil.
Definition Airfoil.h:32
void getparams(float a, float cratio, float cdef, float *ocl, float *ocd, float *ocm)
Definition Airfoil.cpp:105
NodeSB * GetSimNodeBuffer()
Definition GfxActor.h:129
void AdjustParticleSystemTimeFactor(Ogre::ParticleSystem *psys)
Definition GfxScene.cpp:435
Ogre::SceneManager * GetSceneManager()
Definition GfxScene.h:83
NodeNum_t torquenode
Definition TurboProp.h:141
float fixed_pitch
Definition TurboProp.h:119
Ogre::SceneNode * smokeNode
Definition TurboProp.h:116
float max_torque
Definition TurboProp.h:45
Airfoil * airfoil
Definition TurboProp.h:102
NodeNum_t nodep[4]
Definition TurboProp.h:140
NodeNum_t noderef
Definition TurboProp.h:139
float twistmap[5]
Definition TurboProp.h:117
Ogre::Vector3 axis
Definition TurboProp.h:131
void setReverse(bool val)
Ogre::ParticleSystem * smokePS
Definition TurboProp.h:115
float getThrottle()
void toggleReverse()
virtual ~Turboprop() override
void updateForces(float dt, int doUpdate)
double rotenergy
Definition TurboProp.h:118
float indicated_torque
Definition TurboProp.h:44
float maxrevpitch
Definition TurboProp.h:113
void setVisible(bool visible) override
float fullpower
in kW
Definition TurboProp.h:103
void setThrottle(float val)
float warmupstart
Definition TurboProp.h:108
NodeNum_t nodeback
Definition TurboProp.h:138
ActorPtr m_actor
Definition TurboProp.h:137
void updateVisuals(RoR::GfxActor *gfx_actor) override
Turboprop(ActorPtr a, const char *propname, NodeNum_t nr, NodeNum_t nb, NodeNum_t np1, NodeNum_t np2, NodeNum_t np3, NodeNum_t np4, NodeNum_t tqn, float power, Ogre::String const &propfoilname, bool disable_smoke, bool ispiston, float fpitch)
Definition TurboProp.cpp:37
void setRPM(float _rpm)
@ SS_TRIG_AEROENGINE7
@ SS_TRIG_AEROENGINE2
@ SS_TRIG_AEROENGINE3
@ SS_TRIG_AEROENGINE1
@ SS_TRIG_AEROENGINE5
@ SS_TRIG_AEROENGINE6
@ SS_TRIG_AEROENGINE8
@ SS_TRIG_AEROENGINE4
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(),...
GfxScene * GetGfxScene()
static const NodeNum_t NODENUM_INVALID
@ SE_TRUCK_ENGINE_FIRE
triggered when the planes engines start to get on fire, the argument refers to the actor ID of the ve...
@ DEPTHMAP_DISABLED
uint16_t NodeNum_t
Node position within Actor::ar_nodes; use RoR::NODENUM_INVALID as empty value.
Ogre::Vector3 AbsPosition
Definition SimBuffers.h:69
Ogre::Vector3 AbsPosition
absolute position in the world (shaky)
Definition SimData.h:267
Ogre::Real mass
Definition SimData.h:271
Ogre::Vector3 Velocity
Definition SimData.h:268
Ogre::Vector3 Forces
Definition SimData.h:269
Ogre::Vector3 RelPosition
relative to the local physics origin (one origin per actor) (shaky)
Definition SimData.h:266