Rigs of Rods 2026.01
Soft-body Physics Simulation
Loading...
Searching...
No Matches
GfxActor.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 Copyright 2016-2020 Petr Ohlidal
6
7 For more information, see http://www.rigsofrods.org/
8
9 Rigs of Rods is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License version 3, as
11 published by the Free Software Foundation.
12
13 Rigs of Rods is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
20*/
21
22#include "GfxActor.h"
23
24#include "ApproxMath.h"
25#include "AirBrake.h"
26#include "Actor.h"
27#include "Buoyance.h"
28#include "Collisions.h"
29#include "DashBoardManager.h"
30#include "DustPool.h" // General particle gfx
31#include "Engine.h"
32#include "GameContext.h"
33#include "GfxScene.h"
34#include "GUIManager.h"
35#include "GUIUtils.h"
36#include "HydraxWater.h"
37#include "FlexAirfoil.h"
38#include "FlexBody.h"
39#include "FlexMeshWheel.h"
40#include "FlexObj.h"
41#include "InputEngine.h" // TODO: Keys shouldn't be queried from here, but buffered in sim. loop ~ only_a_ptr, 06/2018
42#include "MeshObject.h"
43#include "MovableText.h"
44#include "OgreImGui.h"
45#include "RoRnet.h"
46#include "ActorSpawner.h"
47#include "SlideNode.h"
48#include "SkyManager.h"
49#include "SoundScriptManager.h"
50#include "Terrain.h"
51#include "TurboJet.h"
52#include "TurboProp.h"
53#include "Utils.h"
54
55#include "imgui_internal.h"
56
57#include <Ogre.h>
58
59using namespace RoR;
60
61RoR::GfxActor::GfxActor(ActorPtr actor, ActorSpawner* spawner, std::string ogre_resource_group):
62 m_actor(actor),
63 m_custom_resource_group(ogre_resource_group)
64{
65 // Setup particles
66 m_particles_drip = new DustPool(App::GetGfxScene()->GetSceneManager(), "tracks/Drip", 50, spawner->ComposeName("Drip"));
67 m_particles_dust = new DustPool(App::GetGfxScene()->GetSceneManager(), "tracks/Dust", 20, spawner->ComposeName("Dust"));
68 m_particles_splash = new DustPool(App::GetGfxScene()->GetSceneManager(), "tracks/Splash", 20, spawner->ComposeName("Splash"));
69 m_particles_ripple = new DustPool(App::GetGfxScene()->GetSceneManager(), "tracks/Ripple", 20, spawner->ComposeName("Ripple"));
70 m_particles_sparks = new DustPool(App::GetGfxScene()->GetSceneManager(), "tracks/Sparks", 10, spawner->ComposeName("Sparks"));
71 m_particles_clump = new DustPool(App::GetGfxScene()->GetSceneManager(), "tracks/Clump", 20, spawner->ComposeName("Clump"));
72}
73
74std::string WhereFrom(GfxActor* gfx_actor, const std::string& doing_what)
75{
76 return fmt::format("GfxActor::~GfxActor(); instanceID:{}, streamID:{}, filename:{}; {}",
77 gfx_actor->getOwningActor()->ar_instance_id,
78 gfx_actor->getOwningActor()->ar_net_stream_id,
79 gfx_actor->getOwningActor()->ar_filename,
80 doing_what);
81}
82
84{
85 // Dispose videocameras
86 this->SetVideoCamState(VideoCamState::VCSTATE_DISABLED);
87 while (!m_videocameras.empty())
88 {
89 try
90 {
91 VideoCamera& vcam = m_videocameras.back();
92 Ogre::TextureManager::getSingleton().remove(vcam.vcam_render_tex->getHandle());
93 vcam.vcam_render_tex.reset();
94 vcam.vcam_render_target = nullptr; // Invalidated with parent texture
95 App::GetGfxScene()->GetSceneManager()->destroyCamera(vcam.vcam_ogre_camera);
96 }
97 catch (...)
98 {
99 HandleGenericException(WhereFrom(this, fmt::format("destroying videocamera {}", m_videocameras.size())), HANDLEGENERICEXCEPTION_LOGFILE);
100 }
101
102 m_videocameras.pop_back();
103 }
104
105 // Dispose rods
106 if (m_gfx_beams_parent_scenenode != nullptr)
107 {
108 try
109 {
110 for (BeamGfx& rod: m_gfx_beams)
111 {
112 Ogre::MovableObject* ogre_object = rod.rod_scenenode->getAttachedObject(0);
113 rod.rod_scenenode->detachAllObjects();
114 App::GetGfxScene()->GetSceneManager()->destroyEntity(static_cast<Ogre::Entity*>(ogre_object));
115 }
116 m_gfx_beams.clear();
117
118 m_gfx_beams_parent_scenenode->removeAndDestroyAllChildren();
119 App::GetGfxScene()->GetSceneManager()->destroySceneNode(m_gfx_beams_parent_scenenode);
120 m_gfx_beams_parent_scenenode = nullptr;
121 }
122 catch (...)
123 {
125 }
126 }
127
128 // delete meshwheels
129 for (size_t i = 0; i < m_wheels.size(); i++)
130 {
131 try
132 {
133 if (m_wheels[i].wx_flex_mesh != nullptr)
134 {
135 delete m_wheels[i].wx_flex_mesh;
136 }
137 if (m_wheels[i].wx_scenenode != nullptr)
138 {
139 m_wheels[i].wx_scenenode->removeAndDestroyAllChildren();
140 App::GetGfxScene()->GetSceneManager()->destroySceneNode(m_wheels[i].wx_scenenode);
141 }
142 }
143 catch (...)
144 {
145 HandleGenericException(WhereFrom(this, fmt::format("destroying wheel {}", i)), HANDLEGENERICEXCEPTION_LOGFILE);
146 }
147 }
148
149 // delete airbrakes
150 int i_airbrake = 0;
151 for (AirbrakeGfx& abx: m_gfx_airbrakes)
152 {
153 try
154 {
155 // scene node
156 abx.abx_scenenode->detachAllObjects();
157 App::GetGfxScene()->GetSceneManager()->destroySceneNode(abx.abx_scenenode);
158 // entity
159 App::GetGfxScene()->GetSceneManager()->destroyEntity(abx.abx_entity);
160 // mesh
161 Ogre::MeshManager::getSingleton().remove(abx.abx_mesh);
162
163 i_airbrake++;
164 }
165 catch (...)
166 {
167 HandleGenericException(WhereFrom(this, fmt::format("destroying airbrake {}", i_airbrake)), HANDLEGENERICEXCEPTION_LOGFILE);
168 }
169 }
170 m_gfx_airbrakes.clear();
171
172 // Delete props
173 int i_prop = 0;
174 for (Prop & prop: m_props)
175 {
176 try
177 {
178 for (int k = 0; k < 4; ++k)
179 {
180 if (prop.pp_beacon_scene_node[k])
181 {
182 Ogre::SceneNode* scene_node = prop.pp_beacon_scene_node[k];
183 scene_node->removeAndDestroyAllChildren();
184 App::GetGfxScene()->GetSceneManager()->destroySceneNode(scene_node);
185 }
186 if (prop.pp_beacon_light[k])
187 {
188 App::GetGfxScene()->GetSceneManager()->destroyLight(prop.pp_beacon_light[k]);
189 }
190 }
191 }
192 catch (...)
193 {
194 HandleGenericException(WhereFrom(this, fmt::format("destroying beacon {} beacons", i_prop)), HANDLEGENERICEXCEPTION_LOGFILE);
195 }
196
197 try
198 {
199 if (prop.pp_scene_node)
200 {
201 prop.pp_scene_node->removeAndDestroyAllChildren();
202 App::GetGfxScene()->GetSceneManager()->destroySceneNode(prop.pp_scene_node);
203 }
204 if (prop.pp_wheel_scene_node)
205 {
206 prop.pp_wheel_scene_node->removeAndDestroyAllChildren();
207 App::GetGfxScene()->GetSceneManager()->destroySceneNode(prop.pp_wheel_scene_node);
208 }
209 }
210 catch (...)
211 {
212 HandleGenericException(WhereFrom(this, fmt::format("destroying prop {} scene nodes", i_prop)), HANDLEGENERICEXCEPTION_LOGFILE);
213 }
214
215 try
216 {
217 if (prop.pp_mesh_obj)
218 {
219 delete prop.pp_mesh_obj;
220 }
221 if (prop.pp_wheel_mesh_obj)
222 {
223 delete prop.pp_wheel_mesh_obj;
224 }
225 }
226 catch (...)
227 {
228 HandleGenericException(WhereFrom(this, fmt::format("destroying prop {} mesh objects", i_prop)), HANDLEGENERICEXCEPTION_LOGFILE);
229 }
230
231 i_prop++;
232 }
233 m_props.clear();
234
235 // Delete flexbodies
236 int i_flexbody = 0;
237 for (FlexBody* fb: m_flexbodies)
238 {
239 try
240 {
241 fb->destroyOgreObjects();
242 delete fb;
243 }
244 catch (...)
245 {
246 HandleGenericException(WhereFrom(this, fmt::format("destroying flexbody {}", i_flexbody)), HANDLEGENERICEXCEPTION_LOGFILE);
247 }
248 i_flexbody++;
249 }
250
251 // Delete old cab mesh
252 if (m_cab_mesh != nullptr)
253 {
254 try
255 {
256 m_cab_scene_node->detachAllObjects();
257 m_cab_scene_node->getParentSceneNode()->removeAndDestroyChild(m_cab_scene_node);
258 m_cab_scene_node = nullptr;
259
260 m_cab_entity->_getManager()->destroyEntity(m_cab_entity);
261 m_cab_entity = nullptr;
262
263 delete m_cab_mesh; // Unloads the ManualMesh resource; do this last
264 m_cab_mesh = nullptr;
265 }
266 catch (...)
267 {
269 }
270 }
271
272 // delete exhausts
273 for (std::vector<Exhaust>::iterator it = m_exhausts.begin(); it != m_exhausts.end(); it++)
274 {
275 try
276 {
277 if (it->smokeNode)
278 {
279 it->smokeNode->removeAndDestroyAllChildren();
280 App::GetGfxScene()->GetSceneManager()->destroySceneNode(it->smokeNode);
281 }
282 if (it->smoker)
283 {
284 it->smoker->removeAllAffectors();
285 it->smoker->removeAllEmitters();
286 App::GetGfxScene()->GetSceneManager()->destroyParticleSystem(it->smoker);
287 }
288 }
289 catch (...)
290 {
291 HandleGenericException(fmt::format("GfxActor::~GfxActor(); instanceID:{}, streamID:{}, filename:{}; deleting exhaust {}/{}.",
292 m_actor->ar_instance_id, m_actor->ar_net_stream_id, m_actor->ar_filename, std::distance(m_exhausts.begin(), it), m_exhausts.size()), HANDLEGENERICEXCEPTION_LOGFILE);
293 }
294 }
295
296 // delete custom particles
297 for (int i = 0; i < (int)m_cparticles.size(); i++)
298 {
299 try
300 {
301 if (m_cparticles[i].snode)
302 {
303 m_cparticles[i].snode->removeAndDestroyAllChildren();
304 App::GetGfxScene()->GetSceneManager()->destroySceneNode(m_cparticles[i].snode);
305 }
306 if (m_cparticles[i].psys)
307 {
308 m_cparticles[i].psys->removeAllAffectors();
309 m_cparticles[i].psys->removeAllEmitters();
310 App::GetGfxScene()->GetSceneManager()->destroyParticleSystem(m_cparticles[i].psys);
311 }
312 }
313 catch (...)
314 {
315 HandleGenericException(fmt::format("Actor::dispose(); instanceID:{}, streamID:{}, filename:{}; deleting custom particle {}/{}.",
316 m_actor->ar_instance_id, m_actor->ar_net_stream_id, m_actor->ar_filename, i, m_cparticles.size()), HANDLEGENERICEXCEPTION_LOGFILE);
317 }
318 }
319 try
320 {
321 m_particles_drip->Discard(App::GetGfxScene()->GetSceneManager()); delete m_particles_drip;
322 m_particles_dust->Discard(App::GetGfxScene()->GetSceneManager()); delete m_particles_dust;
323 m_particles_splash->Discard(App::GetGfxScene()->GetSceneManager()); delete m_particles_splash;
324 m_particles_ripple->Discard(App::GetGfxScene()->GetSceneManager()); delete m_particles_ripple;
325 m_particles_sparks->Discard(App::GetGfxScene()->GetSceneManager()); delete m_particles_sparks;
326 m_particles_clump->Discard(App::GetGfxScene()->GetSceneManager()); delete m_particles_clump;
327 }
328 catch (...)
329 {
330 HandleGenericException(WhereFrom(this, "deleting per-actor particle pools (spawned with 'gfx_particles_mode=2')"), HANDLEGENERICEXCEPTION_LOGFILE);
331 }
332}
333
335{
336 return m_actor;
337}
338
339void RoR::GfxActor::SetMaterialFlareOn(int flare_index, bool state_on)
340{
341 for (FlareMaterial& entry: m_flare_materials)
342 {
343 if (entry.flare_index != flare_index)
344 {
345 continue;
346 }
347
348 const int num_techniques = static_cast<int>(entry.mat_instance->getNumTechniques());
349 for (int i = 0; i < num_techniques; i++)
350 {
351 Ogre::Technique* tech = entry.mat_instance->getTechnique(i);
352 if (!tech)
353 continue;
354
355 if (tech->getSchemeName() == "glow")
356 {
357 // glowing technique
358 // set the ambient value as glow amount
359 Ogre::Pass* p = tech->getPass(0);
360 if (!p)
361 continue;
362
363 if (state_on)
364 {
365 p->setSelfIllumination(entry.emissive_color);
366 p->setAmbient(Ogre::ColourValue::White);
367 p->setDiffuse(Ogre::ColourValue::White);
368 }
369 else
370 {
371 p->setSelfIllumination(Ogre::ColourValue::ZERO);
372 p->setAmbient(Ogre::ColourValue::Black);
373 p->setDiffuse(Ogre::ColourValue::Black);
374 }
375 }
376 else
377 {
378 // normal technique
379 Ogre::Pass* p = tech->getPass(0);
380 if (!p)
381 continue;
382
383 Ogre::TextureUnitState* tus = p->getTextureUnitState(0);
384 if (!tus)
385 continue;
386
387 if (tus->getNumFrames() < 2)
388 continue;
389
390 int frame = state_on ? 1 : 0;
391
392 tus->setCurrentFrame(frame);
393
394 if (state_on)
395 p->setSelfIllumination(entry.emissive_color);
396 else
397 p->setSelfIllumination(Ogre::ColourValue::ZERO);
398 }
399 } // for each technique
400 }
401}
402
403void RoR::GfxActor::RegisterCabMaterial(Ogre::MaterialPtr mat, Ogre::MaterialPtr mat_trans)
404{
405 // Material instances of this actor
406 m_cab_mat_visual = mat;
407 m_cab_mat_visual_trans = mat_trans;
408
409 if (mat->getTechnique(0)->getNumPasses() == 1)
410 return; // No emissive pass -> nothing to do.
411
412 m_cab_mat_template_emissive = mat->clone("CabMaterialEmissive-" + mat->getName(), true, m_custom_resource_group);
413
414 m_cab_mat_template_plain = mat->clone("CabMaterialPlain-" + mat->getName(), true, m_custom_resource_group);
415 m_cab_mat_template_plain->getTechnique(0)->removePass(1);
416 m_cab_mat_template_plain->compile();
417}
418
420{
421 if (!m_cab_mat_template_emissive) // Both this and '_plain' are only set when emissive pass is present.
422 return;
423
424 // NOTE: Updating material in-place like this is probably inefficient,
425 // but in order to maintain all the existing material features working together,
426 // we need to avoid any material swapping on runtime. ~ only_a_ptr, 05/2017
427 Ogre::MaterialPtr template_mat = (state_on) ? m_cab_mat_template_emissive : m_cab_mat_template_plain;
428 Ogre::Technique* dest_tech = m_cab_mat_visual->getTechnique(0);
429 Ogre::Technique* templ_tech = template_mat->getTechnique(0);
430 dest_tech->removeAllPasses();
431 for (unsigned short i = 0; i < templ_tech->getNumPasses(); ++i)
432 {
433 Ogre::Pass* templ_pass = templ_tech->getPass(i);
434 Ogre::Pass* dest_pass = dest_tech->createPass();
435 *dest_pass = *templ_pass; // Copy the pass! Reference: http://www.ogre3d.org/forums/viewtopic.php?f=2&t=83453
436 }
437 m_cab_mat_visual->compile();
438}
439
441{
442 if (state == m_vidcam_state)
443 {
444 return; // Nothing to do.
445 }
446
447 const bool enable = (state == VideoCamState::VCSTATE_ENABLED_ONLINE);
448 for (VideoCamera vidcam: m_videocameras)
449 {
450 if (vidcam.vcam_render_target != nullptr)
451 {
452 vidcam.vcam_render_target->setActive(enable);
453 if (enable)
454 vidcam.vcam_material->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(vidcam.vcam_render_tex->getName());
455 else
456 vidcam.vcam_material->getTechnique(0)->getPass(0)->getTextureUnitState(0)->setTextureName(vidcam.vcam_off_tex_name);
457 continue;
458 }
459
460 if (vidcam.vcam_render_window != nullptr)
461 {
462 vidcam.vcam_render_window->setActive(enable);
463 }
464 }
465 m_vidcam_state = state;
466}
467
469{
470 if (m_vidcam_state != VideoCamState::VCSTATE_ENABLED_ONLINE)
471 return;
472
473 for (VideoCamera& vidcam: m_videocameras)
474 {
475#ifdef USE_CAELUM
476 // caelum needs to know that we changed the cameras
477 SkyManager* sky = App::GetGameContext()->GetTerrain()->getSkyManager();
478 if ((sky != nullptr) && (RoR::App::app_state->getEnum<AppState>() == RoR::AppState::SIMULATION))
479 {
480 sky->NotifySkyCameraChanged(vidcam.vcam_ogre_camera);
481 }
482#endif // USE_CAELUM
483
484 if ((vidcam.vcam_role == VCAM_ROLE_MIRROR_PROP_LEFT)
485 || (vidcam.vcam_role == VCAM_ROLE_MIRROR_PROP_RIGHT))
486 {
487 // Mirror prop - special processing.
488 float mirror_angle = 0.f;
489 Ogre::Vector3 offset(Ogre::Vector3::ZERO);
490 if (vidcam.vcam_role == VCAM_ROLE_MIRROR_PROP_LEFT)
491 {
492 mirror_angle = m_actor->ar_left_mirror_angle;
493 offset = Ogre::Vector3(0.07f, -0.22f, 0);
494 }
495 else
496 {
497 mirror_angle = m_actor->ar_right_mirror_angle;
498 offset = Ogre::Vector3(0.07f, +0.22f, 0);
499 }
500
501 Ogre::Vector3 normal = vidcam.vcam_prop_scenenode->getOrientation()
502 * Ogre::Vector3(cos(mirror_angle), sin(mirror_angle), 0.0f);
503 Ogre::Vector3 center = vidcam.vcam_prop_scenenode->getPosition()
504 + vidcam.vcam_prop_scenenode->getOrientation() * offset;
505 Ogre::Radian roll = Ogre::Degree(360)
506 - Ogre::Radian(asin(m_actor->getDirection().dotProduct(Ogre::Vector3::UNIT_Y)));
507
508 Ogre::Plane plane = Ogre::Plane(normal, center);
509 Ogre::Vector3 project = plane.projectVector(App::GetCameraManager()->GetCameraNode()->getPosition() - center);
510
511 vidcam.vcam_ogre_camera->setPosition(center);
512 vidcam.vcam_ogre_camera->lookAt(App::GetCameraManager()->GetCameraNode()->getPosition() - 2.0f * project);
513 vidcam.vcam_ogre_camera->roll(roll);
514 vidcam.vcam_ogre_camera->setNearClipDistance(1); // fixes Caelum sky rendered black on mirrors
515
516 continue; // Done processing mirror prop.
517 }
518
519 // update the texture now, otherwise shuttering
520 if (vidcam.vcam_render_target != nullptr)
521 vidcam.vcam_render_target->update();
522
523 if (vidcam.vcam_render_window != nullptr)
524 vidcam.vcam_render_window->update();
525
526 // get the normal of the camera plane now
527 const Ogre::Vector3 abs_pos_center = m_simbuf.simbuf_nodes[vidcam.vcam_node_center].AbsPosition;
528 const Ogre::Vector3 abs_pos_z = m_simbuf.simbuf_nodes[vidcam.vcam_node_dir_z].AbsPosition;
529 const Ogre::Vector3 abs_pos_y = m_simbuf.simbuf_nodes[vidcam.vcam_node_dir_y].AbsPosition;
530 Ogre::Vector3 normal = (-(abs_pos_center - abs_pos_z)).crossProduct(-(abs_pos_center - abs_pos_y));
531 normal.normalise();
532
533 // add user set offset
534 Ogre::Vector3 pos = m_simbuf.simbuf_nodes[vidcam.vcam_node_alt_pos].AbsPosition +
535 (vidcam.vcam_pos_offset.x * normal) +
536 (vidcam.vcam_pos_offset.y * (abs_pos_center - abs_pos_y)) +
537 (vidcam.vcam_pos_offset.z * (abs_pos_center - abs_pos_z));
538
539 //avoid the camera roll
540 // camup orientates to frustrum of world by default -> rotating the cam related to trucks yaw, lets bind cam rotation videocamera base (nref,ny,nz) as frustum
541 // could this be done faster&better with a plane setFrustumExtents ?
542 Ogre::Vector3 frustumUP = abs_pos_center - abs_pos_y;
543 frustumUP.normalise();
544 vidcam.vcam_ogre_camera->setFixedYawAxis(true, frustumUP);
545
546 if (vidcam.vcam_role == VCAM_ROLE_MIRROR || vidcam.vcam_role == VCAM_ROLE_MIRROR_NOFLIP)
547 {
548 //rotate the normal of the mirror by user rotation setting so it reflects correct
549 normal = vidcam.vcam_rotation * normal;
550 // merge camera direction and reflect it on our plane
551 vidcam.vcam_ogre_camera->setDirection((pos - App::GetCameraManager()->GetCameraNode()->getPosition()).reflect(normal));
552 }
553 else if (vidcam.vcam_role == VCAM_ROLE_VIDEOCAM)
554 {
555 // rotate the camera according to the nodes orientation and user rotation
556 Ogre::Vector3 refx = abs_pos_z - abs_pos_center;
557 refx.normalise();
558 Ogre::Vector3 refy = abs_pos_center - abs_pos_y;
559 refy.normalise();
560 Ogre::Quaternion rot = Ogre::Quaternion(-refx, -refy, -normal);
561 vidcam.vcam_ogre_camera->setOrientation(rot * vidcam.vcam_rotation); // rotate the camera orientation towards the calculated cam direction plus user rotation
562 }
563 else if (vidcam.vcam_role == VCAM_ROLE_TRACKING_VIDEOCAM
564 || vidcam.vcam_role == VCAM_ROLE_TRACKING_MIRROR || vidcam.vcam_role == VCAM_ROLE_TRACKING_MIRROR_NOFLIP)
565 {
566 normal = m_simbuf.simbuf_nodes[vidcam.vcam_node_lookat].AbsPosition - pos;
567 normal.normalise();
568 Ogre::Vector3 refx = abs_pos_z - abs_pos_center;
569 refx.normalise();
570 // why does this flip ~2-3� around zero orientation and only with trackercam. back to slower crossproduct calc, a bit slower but better .. sigh
571 // Ogre::Vector3 refy = abs_pos_center - abs_pos_y;
572 Ogre::Vector3 refy = refx.crossProduct(normal);
573 refy.normalise();
574 Ogre::Quaternion rot = Ogre::Quaternion(-refx, -refy, -normal);
575 vidcam.vcam_ogre_camera->setOrientation(rot * vidcam.vcam_rotation); // rotate the camera orientation towards the calculated cam direction plus user rotation
576 }
577
578 if (vidcam.vcam_debug_node != nullptr)
579 {
580 vidcam.vcam_debug_node->setPosition(pos);
581 vidcam.vcam_debug_node->setOrientation(vidcam.vcam_ogre_camera->getOrientation());
582 }
583
584 // set the new position
585 vidcam.vcam_ogre_camera->setPosition(pos);
586 }
587}
588
590{
591 float water_height = 0.f; // Unused if terrain has no water
592 if (App::GetGameContext()->GetTerrain()->getWater() != nullptr)
593 {
595 }
596
597 for (NodeGfx& nfx: m_gfx_nodes)
598 {
599 const node_t& n = m_actor->ar_nodes[nfx.nx_node_idx];
600
601 // 'Wet' effects - water dripping and vapour
602 if (nfx.nx_may_get_wet && !nfx.nx_no_particles)
603 {
604 // Getting out of water?
605 if (!n.nd_under_water && nfx.nx_under_water_prev)
606 {
607 nfx.nx_wet_time_sec = 0.f;
608 }
609
610 // Dripping water?
611 if (nfx.nx_wet_time_sec != -1)
612 {
613 nfx.nx_wet_time_sec += dt;
614 if (nfx.nx_wet_time_sec > 5.f) // Dries off in 5 sec
615 {
616 nfx.nx_wet_time_sec = -1.f;
617 }
618 else if (nfx.nx_may_get_wet)
619 {
620 if (m_particles_drip != nullptr)
621 {
622 m_particles_drip->allocDrip(n.AbsPosition, n.Velocity, nfx.nx_wet_time_sec); // Dripping water particles
623 }
624 if (nfx.nx_is_hot && m_particles_dust != nullptr)
625 {
626 m_particles_dust->allocVapour(n.AbsPosition, n.Velocity, nfx.nx_wet_time_sec); // Water vapour particles
627 }
628 }
629 }
630 }
631
632 // Water splash and ripple
633 if (n.nd_under_water && !nfx.nx_no_particles)
634 {
635 if ((water_height - n.AbsPosition.y < 0.2f) && (n.Velocity.squaredLength() > 4.f))
636 {
637 if (m_particles_splash)
638 {
639 m_particles_splash->allocSplash(n.AbsPosition, n.Velocity);
640 }
641 if (m_particles_ripple)
642 {
643 m_particles_ripple->allocRipple(n.AbsPosition, n.Velocity);
644 }
645 }
646 }
647
648 // Ground collision (dust, sparks, tyre smoke, clumps...)
649 if (!nfx.nx_no_particles && n.nd_has_ground_contact && n.nd_last_collision_gm != nullptr)
650 {
651 switch (n.nd_last_collision_gm->fx_type)
652 {
654 if (m_particles_dust != nullptr)
655 {
656 m_particles_dust->malloc(n.AbsPosition, n.Velocity / 2.0, n.nd_last_collision_gm->fx_colour);
657 }
658 break;
659
661 if (m_particles_clump != nullptr && n.Velocity.squaredLength() > 1.f)
662 {
663 m_particles_clump->allocClump(n.AbsPosition, n.Velocity / 2.0, n.nd_last_collision_gm->fx_colour);
664 }
665 break;
666
668 if (n.nd_tyre_node) // skidmarks and tyre smoke
669 {
670 const float SMOKE_THRESHOLD = 8.f;
671 const float SCREECH_THRESHOLD = 5.f;
672 const float slipv = n.nd_last_collision_slip.length();
673 const float screech = std::min(slipv, n.nd_avg_collision_slip) - SCREECH_THRESHOLD;
674 if (screech > 0.0f)
675 {
676 SOUND_MODULATE(m_actor, SS_MOD_SCREETCH, screech / SCREECH_THRESHOLD);
678 }
679 if (m_particles_dust != nullptr && n.nd_avg_collision_slip > SMOKE_THRESHOLD)
680 {
681 m_particles_dust->allocSmoke(n.AbsPosition, n.Velocity);
682 }
683 }
684 else if (!nfx.nx_no_sparks) // Not a wheel => sparks
685 {
686 if (m_particles_sparks != nullptr && n.nd_avg_collision_slip > 5.f)
687 {
688 if (n.nd_last_collision_slip.squaredLength() > 25.f)
689 {
690 m_particles_sparks->allocSparks(n.AbsPosition, n.Velocity);
691 }
692 }
693 }
694 break;
695
696 default:;
697 }
698 }
699
700 nfx.nx_under_water_prev = n.nd_under_water;
701 }
702 m_particles_drip->AdjustDustPoolSpeedFactor(this);
703 m_particles_dust->AdjustDustPoolSpeedFactor(this);
704 m_particles_splash->AdjustDustPoolSpeedFactor(this);
705 m_particles_ripple->AdjustDustPoolSpeedFactor(this);
706 m_particles_sparks->AdjustDustPoolSpeedFactor(this);
707 m_particles_clump->AdjustDustPoolSpeedFactor(this);
708
709 m_particles_drip->update();
710 m_particles_dust->update();
711 m_particles_splash->update();
712 m_particles_ripple->update();
713 m_particles_sparks->update();
714 m_particles_clump->update();
715}
716
717const ImU32 BEAM_COLOR (0xff556633); // All colors are in ABGR format (alpha, blue, green, red)
718const float BEAM_THICKNESS (1.2f);
719const ImU32 BEAM_BROKEN_COLOR (0xff4466dd);
720const float BEAM_BROKEN_THICKNESS (1.8f);
721const ImU32 BEAM_HYDRO_COLOR (0xff55a3e0);
722const float BEAM_HYDRO_THICKNESS (1.4f);
723const ImU32 BEAM_STRENGTH_TEXT_COLOR (0xffcfd0cc);
724const ImU32 BEAM_STRESS_TEXT_COLOR (0xff58bbfc);
725// TODO: commands cannot be distinguished on runtime
726
727const ImU32 NODE_COLOR (0xff44ddff);
728const float NODE_RADIUS (2.f);
729const ImU32 NODE_TEXT_COLOR (0xffcccccf); // node ID text color
730const ImU32 NODE_MASS_TEXT_COLOR (0xff77bb66);
731const ImU32 NODE_IMMOVABLE_COLOR (0xff0033ff);
732const float NODE_IMMOVABLE_RADIUS (2.8f);
733
735{
736 if (m_actor->m_buoyance)
737 {
738 m_actor->m_buoyance->buoy_debug_view = m_debug_view == DebugViewType::DEBUGVIEW_BUOYANCY;
739 }
740
741 if (m_debug_view == DebugViewType::DEBUGVIEW_NONE && !m_actor->ar_physics_paused)
742 {
743 return; // Nothing to do
744 }
745
746 // Var
747 ImVec2 screen_size = ImGui::GetIO().DisplaySize;
748 World2ScreenConverter world2screen(
749 App::GetCameraManager()->GetCamera()->getViewMatrix(true), App::GetCameraManager()->GetCamera()->getProjectionMatrix(), Ogre::Vector2(screen_size.x, screen_size.y));
750
751 ImDrawList* drawlist = GetImDummyFullscreenWindow();
752
753 if (m_actor->ar_physics_paused && !App::GetGuiManager()->IsGuiHidden())
754 {
755 // Should we replace this circle with a proper bounding box?
756 Ogre::Vector3 pos_xyz = world2screen.Convert(m_actor->getPosition());
757 if (pos_xyz.z < 0.f)
758 {
759 ImVec2 pos(pos_xyz.x, pos_xyz.y);
760
761 float radius = 0.0f;
762 for (int i = 0; i < m_actor->ar_num_nodes; ++i)
763 {
764 radius = std::max(radius, pos_xyz.distance(world2screen.Convert(m_actor->ar_nodes[i].AbsPosition)));
765 }
766
767 drawlist->AddCircleFilled(pos, radius * 1.05f, 0x22222222, 36);
768 }
769 }
770
771 // Skeleton display. NOTE: Order matters, it determines Z-ordering on render
772 if ((m_debug_view == DebugViewType::DEBUGVIEW_SKELETON) ||
773 (m_debug_view == DebugViewType::DEBUGVIEW_NODES) ||
774 (m_debug_view == DebugViewType::DEBUGVIEW_BEAMS))
775 {
776 // Beams
777 const beam_t* beams = m_actor->ar_beams;
778 const size_t num_beams = static_cast<size_t>(m_actor->ar_num_beams);
779 for (size_t i = 0; i < num_beams; ++i)
780 {
781 if (App::diag_hide_wheels->getBool() &&
782 (beams[i].p1->nd_tyre_node || beams[i].p1->nd_rim_node ||
783 beams[i].p2->nd_tyre_node || beams[i].p2->nd_rim_node))
784 continue;
785
786 Ogre::Vector3 pos1 = world2screen.Convert(beams[i].p1->AbsPosition);
787 Ogre::Vector3 pos2 = world2screen.Convert(beams[i].p2->AbsPosition);
788
789 if ((pos1.z < 0.f) && (pos2.z < 0.f))
790 {
791 ImVec2 pos1xy(pos1.x, pos1.y);
792 ImVec2 pos2xy(pos2.x, pos2.y);
793
794 if (beams[i].bm_broken)
795 {
796 if (!App::diag_hide_broken_beams->getBool())
797 {
798 drawlist->AddLine(pos1xy, pos2xy, BEAM_BROKEN_COLOR, BEAM_BROKEN_THICKNESS);
799 }
800 }
801 else if (beams[i].bm_type == BEAM_HYDRO)
802 {
803 if (!beams[i].bm_disabled)
804 {
805 drawlist->AddLine(pos1xy, pos2xy, BEAM_HYDRO_COLOR, BEAM_HYDRO_THICKNESS);
806 }
807 }
808 else
809 {
810 ImU32 color = BEAM_COLOR;
811 if (!App::diag_hide_beam_stress->getBool())
812 {
813 if (beams[i].stress > 0)
814 {
815 float stress_ratio = pow(beams[i].stress / beams[i].maxposstress, 2.0f);
816 float s = std::min(stress_ratio, 1.0f);
817 color = Ogre::ColourValue(0.2f * (1 + 2.0f * s), 0.4f * (1.0f - s), 0.33f, 1.0f).getAsABGR();
818 }
819 else if (beams[i].stress < 0)
820 {
821 float stress_ratio = pow(beams[i].stress / beams[i].maxnegstress, 2.0f);
822 float s = std::min(stress_ratio, 1.0f);
823 color = Ogre::ColourValue(0.2f, 0.4f * (1.0f - s), 0.33f * (1 + 1.0f * s), 1.0f).getAsABGR();
824 }
825 }
826 drawlist->AddLine(pos1xy, pos2xy, color, BEAM_THICKNESS);
827 }
828 }
829 }
830
831 if (!App::diag_hide_nodes->getBool())
832 {
833 // Nodes
834 const node_t* nodes = m_actor->ar_nodes;
835 const size_t num_nodes = static_cast<size_t>(m_actor->ar_num_nodes);
836 for (size_t i = 0; i < num_nodes; ++i)
837 {
838 if (App::diag_hide_wheels->getBool() && (nodes[i].nd_tyre_node || nodes[i].nd_rim_node))
839 continue;
840
841 Ogre::Vector3 pos_xyz = world2screen.Convert(nodes[i].AbsPosition);
842
843 if (pos_xyz.z < 0.f)
844 {
845 ImVec2 pos(pos_xyz.x, pos_xyz.y);
846 if (nodes[i].nd_immovable)
847 {
848 drawlist->AddCircleFilled(pos, NODE_IMMOVABLE_RADIUS, NODE_IMMOVABLE_COLOR);
849 }
850 else
851 {
852 drawlist->AddCircleFilled(pos, NODE_RADIUS, NODE_COLOR);
853 }
854 }
855 }
856
857 // Node info; drawn after nodes to have higher Z-order
858 if ((m_debug_view == DebugViewType::DEBUGVIEW_NODES) || (m_debug_view == DebugViewType::DEBUGVIEW_BEAMS))
859 {
860 for (size_t i = 0; i < num_nodes; ++i)
861 {
862 if ((App::diag_hide_wheels->getBool() || App::diag_hide_wheel_info->getBool()) &&
863 (nodes[i].nd_tyre_node || nodes[i].nd_rim_node))
864 continue;
865
866 Ogre::Vector3 pos = world2screen.Convert(nodes[i].AbsPosition);
867
868 if (pos.z < 0.f)
869 {
870 ImVec2 pos_xy(pos.x, pos.y);
871 Str<25> id_buf;
872 id_buf << nodes[i].pos;
873 drawlist->AddText(pos_xy, NODE_TEXT_COLOR, id_buf.ToCStr());
874
875 if (m_debug_view != DebugViewType::DEBUGVIEW_BEAMS)
876 {
877 char mass_buf[50];
878 snprintf(mass_buf, 50, "|%.1fKg", nodes[i].mass);
879 ImVec2 offset = ImGui::CalcTextSize(id_buf.ToCStr());
880 drawlist->AddText(ImVec2(pos.x + offset.x, pos.y), NODE_MASS_TEXT_COLOR, mass_buf);
881 }
882 }
883 }
884 }
885 }
886
887 // Beam-info: drawn after beams to have higher Z-order
888 if (m_debug_view == DebugViewType::DEBUGVIEW_BEAMS)
889 {
890 for (size_t i = 0; i < num_beams; ++i)
891 {
892 if ((App::diag_hide_wheels->getBool() || App::diag_hide_wheel_info->getBool()) &&
893 (beams[i].p1->nd_tyre_node || beams[i].p1->nd_rim_node ||
894 beams[i].p2->nd_tyre_node || beams[i].p2->nd_rim_node))
895 continue;
896
897 // Position
898 Ogre::Vector3 world_pos = (beams[i].p1->AbsPosition + beams[i].p2->AbsPosition) / 2.f;
899 Ogre::Vector3 pos_xyz = world2screen.Convert(world_pos);
900 if (pos_xyz.z >= 0.f)
901 {
902 continue; // Behind the camera
903 }
904 ImVec2 pos(pos_xyz.x, pos_xyz.y);
905
906 // Strength is usually in thousands or millions - we shorten it.
907 const size_t BUF_LEN = 50;
908 char buf[BUF_LEN];
909 if (beams[i].strength >= 1000000000000.f)
910 {
911 snprintf(buf, BUF_LEN, "%.1fT", (beams[i].strength / 1000000000000.f));
912 }
913 else if (beams[i].strength >= 1000000000.f)
914 {
915 snprintf(buf, BUF_LEN, "%.1fG", (beams[i].strength / 1000000000.f));
916 }
917 else if (beams[i].strength >= 1000000.f)
918 {
919 snprintf(buf, BUF_LEN, "%.1fM", (beams[i].strength / 1000000.f));
920 }
921 else if (beams[i].strength >= 1000.f)
922 {
923 snprintf(buf, BUF_LEN, "%.1fK", (beams[i].strength / 1000.f));
924 }
925 else
926 {
927 snprintf(buf, BUF_LEN, "%.1f", beams[i].strength);
928 }
929 const ImVec2 stren_text_size = ImGui::CalcTextSize(buf);
930 drawlist->AddText(ImVec2(pos.x - stren_text_size.x, pos.y), BEAM_STRENGTH_TEXT_COLOR, buf);
931
932 // Stress
933 snprintf(buf, BUF_LEN, "|%.1f", beams[i].stress);
934 drawlist->AddText(pos, BEAM_STRESS_TEXT_COLOR, buf);
935 }
936 }
937 } else if (m_debug_view == DebugViewType::DEBUGVIEW_WHEELS)
938 {
939 // Wheels
940 const wheel_t* wheels = m_actor->ar_wheels;
941 const size_t num_wheels = static_cast<size_t>(m_actor->ar_num_wheels);
942 for (int i = 0; i < num_wheels; i++)
943 {
944 Ogre::Vector3 axis = wheels[i].wh_axis_node_1->RelPosition - wheels[i].wh_axis_node_0->RelPosition;
945 axis.normalise();
946
947 // Wheel axle
948 {
949 Ogre::Vector3 pos1_xyz = world2screen.Convert(wheels[i].wh_axis_node_1->AbsPosition);
950 if (pos1_xyz.z < 0.f)
951 {
952 ImVec2 pos(pos1_xyz.x, pos1_xyz.y);
953 drawlist->AddCircleFilled(pos, NODE_IMMOVABLE_RADIUS, NODE_COLOR);
954 }
955 Ogre::Vector3 pos2_xyz = world2screen.Convert(wheels[i].wh_axis_node_0->AbsPosition);
956 if (pos2_xyz.z < 0.f)
957 {
958 ImVec2 pos(pos2_xyz.x, pos2_xyz.y);
959 drawlist->AddCircleFilled(pos, NODE_IMMOVABLE_RADIUS, NODE_COLOR);
960 }
961 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f))
962 {
963 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
964 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
965 drawlist->AddLine(pos1xy, pos2xy, BEAM_COLOR, BEAM_BROKEN_THICKNESS);
966 }
967
968 // Id, Speed, Torque
969 Ogre::Vector3 pos_xyz = pos1_xyz.midPoint(pos2_xyz);
970 if (pos_xyz.z < 0.f)
971 {
972 float v = ImGui::GetTextLineHeightWithSpacing();
973 ImVec2 pos(pos_xyz.x, pos_xyz.y);
974 Str<25> wheel_id_buf;
975 wheel_id_buf << "Id: " << (i + 1);
976 float h1 = ImGui::CalcTextSize(wheel_id_buf.ToCStr()).x / 2.0f;
977 drawlist->AddText(ImVec2(pos.x - h1, pos.y), NODE_TEXT_COLOR, wheel_id_buf.ToCStr());
978 Str<25> rpm_buf;
979 rpm_buf << "Speed: " << static_cast<int>(Round(wheels[i].debug_rpm)) << " rpm";
980 float h2 = ImGui::CalcTextSize(rpm_buf.ToCStr()).x / 2.0f;
981 drawlist->AddText(ImVec2(pos.x - h2, pos.y + v), NODE_TEXT_COLOR, rpm_buf.ToCStr());
982 Str<25> torque_buf;
983 torque_buf << "Torque: " << static_cast<int>(Round(wheels[i].debug_torque)) << " Nm";
984 float h3 = ImGui::CalcTextSize(torque_buf.ToCStr()).x / 2.0f;
985 drawlist->AddText(ImVec2(pos.x - h3, pos.y + v + v), NODE_TEXT_COLOR, torque_buf.ToCStr());
986 }
987 }
988
989 Ogre::Vector3 rradius = wheels[i].wh_arm_node->RelPosition - wheels[i].wh_near_attach_node->RelPosition;
990
991 // Reference arm
992 {
993 Ogre::Vector3 pos1_xyz = world2screen.Convert(wheels[i].wh_arm_node->AbsPosition);
994 if (pos1_xyz.z < 0.f)
995 {
996 ImVec2 pos(pos1_xyz.x, pos1_xyz.y);
997 drawlist->AddCircleFilled(pos, NODE_IMMOVABLE_RADIUS, NODE_IMMOVABLE_COLOR);
998 }
999 Ogre::Vector3 pos2_xyz = world2screen.Convert(wheels[i].wh_near_attach_node->AbsPosition);
1000 if (pos2_xyz.z < 0.f)
1001 {
1002 ImVec2 pos(pos2_xyz.x, pos2_xyz.y);
1003 drawlist->AddCircleFilled(pos, NODE_IMMOVABLE_RADIUS, NODE_COLOR);
1004 }
1005 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f))
1006 {
1007 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1008 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1009 drawlist->AddLine(pos1xy, pos2xy, BEAM_BROKEN_COLOR, BEAM_BROKEN_THICKNESS);
1010 }
1011 }
1012
1013 Ogre::Vector3 radius = Ogre::Plane(axis, wheels[i].wh_near_attach_node->RelPosition).projectVector(rradius);
1014
1015 // Projection plane
1016#if 0
1017 {
1018 Ogre::Vector3 up = axis.crossProduct(radius);
1019 Ogre::Vector3 pos1_xyz = world2screen.Convert(wheels[i].wh_near_attach_node->AbsPosition + radius - up);
1020 Ogre::Vector3 pos2_xyz = world2screen.Convert(wheels[i].wh_near_attach_node->AbsPosition + radius + up);
1021 Ogre::Vector3 pos3_xyz = world2screen.Convert(wheels[i].wh_near_attach_node->AbsPosition - radius + up);
1022 Ogre::Vector3 pos4_xyz = world2screen.Convert(wheels[i].wh_near_attach_node->AbsPosition - radius - up);
1023 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f) && (pos3_xyz.z < 0.f) && (pos4_xyz.z < 0.f))
1024 {
1025 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1026 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1027 ImVec2 pos3xy(pos3_xyz.x, pos3_xyz.y);
1028 ImVec2 pos4xy(pos4_xyz.x, pos4_xyz.y);
1029 drawlist->AddQuadFilled(pos1xy, pos2xy, pos3xy, pos4xy, 0x22888888);
1030 }
1031 }
1032#endif
1033 // Projected reference arm & error arm
1034 {
1035 Ogre::Vector3 pos1_xyz = world2screen.Convert(wheels[i].wh_near_attach_node->AbsPosition);
1036 Ogre::Vector3 pos2_xyz = world2screen.Convert(wheels[i].wh_near_attach_node->AbsPosition + radius);
1037 Ogre::Vector3 pos3_xyz = world2screen.Convert(wheels[i].wh_arm_node->AbsPosition);
1038 if (pos2_xyz.z < 0.f)
1039 {
1040 ImVec2 pos(pos2_xyz.x, pos2_xyz.y);
1041 drawlist->AddCircleFilled(pos, NODE_IMMOVABLE_RADIUS, 0x660033ff);
1042 }
1043 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f))
1044 {
1045 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1046 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1047 drawlist->AddLine(pos1xy, pos2xy, 0x664466dd, BEAM_BROKEN_THICKNESS);
1048 }
1049 if ((pos2_xyz.z < 0.f) && (pos3_xyz.z < 0.f))
1050 {
1051 ImVec2 pos1xy(pos2_xyz.x, pos2_xyz.y);
1052 ImVec2 pos2xy(pos3_xyz.x, pos3_xyz.y);
1053 drawlist->AddLine(pos1xy, pos2xy, 0x99555555, BEAM_BROKEN_THICKNESS);
1054 }
1055 }
1056 // Reaction torque
1057 {
1058 Ogre::Vector3 cforce = wheels[i].debug_scaled_cforce;
1059 {
1060 Ogre::Vector3 pos1_xyz = world2screen.Convert(wheels[i].wh_arm_node->AbsPosition);
1061 Ogre::Vector3 pos2_xyz = world2screen.Convert(wheels[i].wh_arm_node->AbsPosition - cforce);
1062 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f))
1063 {
1064 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1065 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1066 drawlist->AddLine(pos1xy, pos2xy, 0xffcc4444, BEAM_BROKEN_THICKNESS);
1067 }
1068 }
1069 {
1070 Ogre::Vector3 pos1_xyz = world2screen.Convert(wheels[i].wh_near_attach_node->AbsPosition);
1071 Ogre::Vector3 pos2_xyz = world2screen.Convert(wheels[i].wh_near_attach_node->AbsPosition + cforce);
1072 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f))
1073 {
1074 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1075 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1076 drawlist->AddLine(pos1xy, pos2xy, 0xffcc4444, BEAM_BROKEN_THICKNESS);
1077 }
1078 }
1079 }
1080
1081 // Wheel slip
1082 {
1083 Ogre::Vector3 m = wheels[i].wh_axis_node_0->AbsPosition.midPoint(wheels[i].wh_axis_node_1->AbsPosition);
1084 Ogre::Real w = wheels[i].wh_axis_node_0->AbsPosition.distance(m);
1085 Ogre::Vector3 u = - axis.crossProduct(m_simbuf.simbuf_direction);
1086 if (!wheels[i].debug_force.isZeroLength())
1087 {
1088 u = - wheels[i].debug_force.normalisedCopy();
1089 }
1090 Ogre::Vector3 f = axis.crossProduct(u);
1091 Ogre::Vector3 a = - axis * w + f * std::max(w, wheels[i].wh_radius * 0.5f);
1092 Ogre::Vector3 b = + axis * w + f * std::max(w, wheels[i].wh_radius * 0.5f);
1093 Ogre::Vector3 c = + axis * w - f * std::max(w, wheels[i].wh_radius * 0.5f);
1094 Ogre::Vector3 d = - axis * w - f * std::max(w, wheels[i].wh_radius * 0.5f);
1095 Ogre::Quaternion r = Ogre::Quaternion::IDENTITY;
1096 if (wheels[i].debug_vel.length() > 1.0f)
1097 {
1098 r = Ogre::Quaternion(f.angleBetween(wheels[i].debug_vel), u);
1099 }
1100 Ogre::Vector3 pos1_xyz = world2screen.Convert(m - u * wheels[i].wh_radius + r * a);
1101 Ogre::Vector3 pos2_xyz = world2screen.Convert(m - u * wheels[i].wh_radius + r * b);
1102 Ogre::Vector3 pos3_xyz = world2screen.Convert(m - u * wheels[i].wh_radius + r * c);
1103 Ogre::Vector3 pos4_xyz = world2screen.Convert(m - u * wheels[i].wh_radius + r * d);
1104 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f) && (pos3_xyz.z < 0.f) && (pos4_xyz.z < 0.f))
1105 {
1106 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1107 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1108 ImVec2 pos3xy(pos3_xyz.x, pos3_xyz.y);
1109 ImVec2 pos4xy(pos4_xyz.x, pos4_xyz.y);
1110 if (!wheels[i].debug_force.isZeroLength())
1111 {
1112 float slipv = wheels[i].debug_slip.length();
1113 float wheelv = wheels[i].debug_vel.length();
1114 float slip_ratio = std::min(slipv, wheelv) / std::max(1.0f, wheelv);
1115 float scale = pow(slip_ratio, 2);
1116 ImU32 col = Ogre::ColourValue(scale, 1.0f - scale, 0.0f, 0.2f).getAsABGR();
1117 drawlist->AddQuadFilled(pos1xy, pos2xy, pos3xy, pos4xy, col);
1118 }
1119 else
1120 {
1121 drawlist->AddQuadFilled(pos1xy, pos2xy, pos3xy, pos4xy, 0x55555555);
1122 }
1123 }
1124 }
1125
1126 // Slip vector
1127 if (!wheels[i].debug_vel.isZeroLength())
1128 {
1129 Ogre::Vector3 m = wheels[i].wh_axis_node_0->AbsPosition.midPoint(wheels[i].wh_axis_node_1->AbsPosition);
1130 Ogre::Real w = wheels[i].wh_axis_node_0->AbsPosition.distance(m);
1131 Ogre::Vector3 d = axis.crossProduct(m_simbuf.simbuf_direction) * wheels[i].wh_radius;
1132 Ogre::Real slipv = wheels[i].debug_slip.length();
1133 Ogre::Real wheelv = wheels[i].debug_vel.length();
1134 Ogre::Vector3 s = wheels[i].debug_slip * (std::min(slipv, wheelv) / std::max(1.0f, wheelv)) / slipv;
1135 Ogre::Vector3 pos1_xyz = world2screen.Convert(m + d);
1136 Ogre::Vector3 pos2_xyz = world2screen.Convert(m + d + s * std::max(w, wheels[i].wh_radius * 0.5f));
1137 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f))
1138 {
1139 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1140 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1141 drawlist->AddLine(pos1xy, pos2xy, 0xbb4466dd, BEAM_BROKEN_THICKNESS);
1142 }
1143 }
1144
1145 // Down force
1146 {
1147 Ogre::Real f = wheels[i].debug_force.length();
1148 Ogre::Real mass = m_actor->getTotalMass(false) * num_wheels;
1149 Ogre::Vector3 normalised_force = wheels[i].debug_force.normalisedCopy() * std::min(f / mass, 1.0f);
1150 Ogre::Vector3 m = wheels[i].wh_axis_node_0->AbsPosition.midPoint(wheels[i].wh_axis_node_1->AbsPosition);
1151 Ogre::Vector3 pos5_xyz = world2screen.Convert(m);
1152 Ogre::Vector3 pos6_xyz = world2screen.Convert(m + normalised_force * wheels[i].wh_radius);
1153 if ((pos5_xyz.z < 0.f) && (pos6_xyz.z < 0.f))
1154 {
1155 ImVec2 pos1xy(pos5_xyz.x, pos5_xyz.y);
1156 ImVec2 pos2xy(pos6_xyz.x, pos6_xyz.y);
1157 drawlist->AddLine(pos1xy, pos2xy, 0x88888888, BEAM_BROKEN_THICKNESS);
1158 }
1159 }
1160 }
1161 } else if (m_debug_view == DebugViewType::DEBUGVIEW_SHOCKS)
1162 {
1163 // Shocks
1164 const beam_t* beams = m_actor->ar_beams;
1165 const size_t num_beams = static_cast<size_t>(m_actor->ar_num_beams);
1166 std::set<int> node_ids;
1167 for (size_t i = 0; i < num_beams; ++i)
1168 {
1169 if (beams[i].bm_type != BEAM_HYDRO)
1170 continue;
1171 if (!(beams[i].bounded == SHOCK1 || beams[i].bounded == SHOCK2 || beams[i].bounded == SHOCK3))
1172 continue;
1173
1174 Ogre::Vector3 pos1_xyz = world2screen.Convert(beams[i].p1->AbsPosition);
1175 Ogre::Vector3 pos2_xyz = world2screen.Convert(beams[i].p2->AbsPosition);
1176
1177 if (pos1_xyz.z < 0.f)
1178 {
1179 node_ids.insert(beams[i].p1->pos);
1180 }
1181 if (pos2_xyz.z < 0.f)
1182 {
1183 node_ids.insert(beams[i].p2->pos);
1184 }
1185
1186 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f))
1187 {
1188 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1189 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1190
1191 ImU32 beam_color = (beams[i].bounded == SHOCK1) ? BEAM_HYDRO_COLOR : BEAM_BROKEN_COLOR;
1192
1193 drawlist->AddLine(pos1xy, pos2xy, beam_color, 1.25f * BEAM_BROKEN_THICKNESS);
1194 }
1195 }
1196 for (auto id : node_ids)
1197 {
1198 Ogre::Vector3 pos_xyz = world2screen.Convert(m_actor->ar_nodes[id].AbsPosition);
1199 if (pos_xyz.z < 0.f)
1200 {
1201 ImVec2 pos_xy(pos_xyz.x, pos_xyz.y);
1202 drawlist->AddCircleFilled(pos_xy, NODE_RADIUS, NODE_COLOR);
1203 // Node info
1204 Str<25> id_buf;
1205 id_buf << id;
1206 drawlist->AddText(pos_xy, NODE_TEXT_COLOR, id_buf.ToCStr());
1207 }
1208 }
1209 for (size_t i = 0; i < num_beams; ++i)
1210 {
1211 if (beams[i].bm_type != BEAM_HYDRO)
1212 continue;
1213 if (!(beams[i].bounded == SHOCK1 || beams[i].bounded == SHOCK2 || beams[i].bounded == SHOCK3))
1214 continue;
1215
1216 Ogre::Vector3 pos1_xyz = world2screen.Convert(beams[i].p1->AbsPosition);
1217 Ogre::Vector3 pos2_xyz = world2screen.Convert(beams[i].p2->AbsPosition);
1218 Ogre::Vector3 pos_xyz = pos1_xyz.midPoint(pos2_xyz);
1219
1220 if (pos_xyz.z < 0.f)
1221 {
1222 // Shock info
1223 float diff = beams[i].p1->AbsPosition.distance(beams[i].p2->AbsPosition) - beams[i].L;
1224 ImU32 text_color = (diff < 0.0f) ? 0xff66ee66 : 0xff8888ff;
1225 float bound = (diff < 0.0f) ? beams[i].shortbound : beams[i].longbound;
1226 float ratio = Ogre::Math::Clamp(diff / (bound * beams[i].L), -2.0f, +2.0f);
1227
1228 float v = ImGui::GetTextLineHeightWithSpacing();
1229 ImVec2 pos(pos_xyz.x, pos_xyz.y - v - v);
1230 Str<25> len_buf;
1231 len_buf << "L: " << static_cast<int>(Round(std::abs(ratio) * 100.0f)) << " %";
1232 float h1 = ImGui::CalcTextSize(len_buf.ToCStr()).x / 2.0f;
1233 drawlist->AddText(ImVec2(pos.x - h1, pos.y), text_color, len_buf.ToCStr());
1234 Str<25> spring_buf;
1235 spring_buf << "S: " << static_cast<int>(Round(beams[i].debug_k)) << " N";
1236 float h2 = ImGui::CalcTextSize(spring_buf.ToCStr()).x / 2.0f;
1237 drawlist->AddText(ImVec2(pos.x - h2, pos.y + v), text_color, spring_buf.ToCStr());
1238 Str<25> damp_buf;
1239 damp_buf << "D: " << static_cast<int>(Round(beams[i].debug_d)) << " N";
1240 float h3 = ImGui::CalcTextSize(damp_buf.ToCStr()).x / 2.0f;
1241 drawlist->AddText(ImVec2(pos.x - h3, pos.y + v + v), text_color, damp_buf.ToCStr());
1242 char vel_buf[25];
1243 snprintf(vel_buf, 25, "V: %.2f m/s", beams[i].debug_v);
1244 float h4 = ImGui::CalcTextSize(vel_buf).x / 2.0f;
1245 drawlist->AddText(ImVec2(pos.x - h4, pos.y + v + v + v), text_color, vel_buf);
1246 }
1247 }
1248 } else if (m_debug_view == DebugViewType::DEBUGVIEW_ROTATORS)
1249 {
1250 // Rotators
1251 const node_t* nodes = m_actor->ar_nodes;
1252 const rotator_t* rotators = m_actor->ar_rotators;
1253 const size_t num_rotators = static_cast<size_t>(m_actor->ar_num_rotators);
1254 for (int i = 0; i < num_rotators; i++)
1255 {
1256 Ogre::Vector3 pos1_xyz = world2screen.Convert(nodes[rotators[i].axis1].AbsPosition);
1257 Ogre::Vector3 pos2_xyz = world2screen.Convert(nodes[rotators[i].axis2].AbsPosition);
1258
1259 // Rotator axle
1260 {
1261 if (pos1_xyz.z < 0.f)
1262 {
1263 ImVec2 pos(pos1_xyz.x, pos1_xyz.y);
1264 drawlist->AddCircleFilled(pos, 1.25f * NODE_IMMOVABLE_RADIUS, NODE_COLOR);
1265 Str<25> id_buf;
1266 id_buf << nodes[rotators[i].axis1].pos;
1267 drawlist->AddText(pos, NODE_TEXT_COLOR, id_buf.ToCStr());
1268 }
1269 if (pos2_xyz.z < 0.f)
1270 {
1271 ImVec2 pos(pos2_xyz.x, pos2_xyz.y);
1272 drawlist->AddCircleFilled(pos, 1.25f * NODE_IMMOVABLE_RADIUS, NODE_COLOR);
1273 Str<25> id_buf;
1274 id_buf << nodes[rotators[i].axis2].pos;
1275 drawlist->AddText(pos, NODE_TEXT_COLOR, id_buf.ToCStr());
1276 }
1277 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f))
1278 {
1279 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1280 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1281 drawlist->AddLine(pos1xy, pos2xy, BEAM_COLOR, 1.25f * BEAM_BROKEN_THICKNESS);
1282 }
1283
1284 // Id, RPM, Error
1285 Ogre::Vector3 pos_xyz = pos1_xyz.midPoint(pos2_xyz);
1286 if (pos_xyz.z < 0.f)
1287 {
1288 float v = ImGui::GetTextLineHeightWithSpacing();
1289 ImVec2 pos(pos_xyz.x, pos_xyz.y);
1290 Str<25> rotator_id_buf;
1291 rotator_id_buf << "Id: " << (i + 1);
1292 float h1 = ImGui::CalcTextSize(rotator_id_buf.ToCStr()).x / 2.0f;
1293 drawlist->AddText(ImVec2(pos.x - h1, pos.y), NODE_TEXT_COLOR, rotator_id_buf.ToCStr());
1294 char angle_buf[25];
1295 snprintf(angle_buf, 25, "Rate: %.1f rpm", 60.0f * rotators[i].debug_rate / Ogre::Math::TWO_PI);
1296 float h2 = ImGui::CalcTextSize(angle_buf).x / 2.0f;
1297 drawlist->AddText(ImVec2(pos.x - h2, pos.y + v), NODE_TEXT_COLOR, angle_buf);
1298 char aerror_buf[25];
1299 snprintf(aerror_buf, 25, "Error: %.1f mrad", 1000.0f * std::abs(rotators[i].debug_aerror));
1300 float h3 = ImGui::CalcTextSize(aerror_buf).x / 2.0f;
1301 drawlist->AddText(ImVec2(pos.x - h3, pos.y + v + v), NODE_TEXT_COLOR, aerror_buf);
1302 }
1303 }
1304
1305 // Reference arms
1306 for (int j = 0; j < 4; j++)
1307 {
1308 // Base plate
1309 {
1310 ImU32 node_color = Ogre::ColourValue(0.33f, 0.33f, 0.33f, j < 2 ? 1.0f : 0.5f).getAsABGR();
1311 ImU32 beam_color = Ogre::ColourValue(0.33f, 0.33f, 0.33f, j < 2 ? 1.0f : 0.5f).getAsABGR();
1312
1313 Ogre::Vector3 pos3_xyz = world2screen.Convert(nodes[rotators[i].nodes1[j]].AbsPosition);
1314 if (pos3_xyz.z < 0.f)
1315 {
1316 ImVec2 pos(pos3_xyz.x, pos3_xyz.y);
1317 drawlist->AddCircleFilled(pos, NODE_RADIUS, node_color);
1318 Str<25> id_buf;
1319 id_buf << nodes[rotators[i].nodes1[j]].pos;
1320 drawlist->AddText(pos, NODE_TEXT_COLOR, id_buf.ToCStr());
1321 }
1322 if ((pos1_xyz.z < 0.f) && (pos3_xyz.z < 0.f))
1323 {
1324 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1325 ImVec2 pos2xy(pos3_xyz.x, pos3_xyz.y);
1326 drawlist->AddLine(pos1xy, pos2xy, beam_color, BEAM_BROKEN_THICKNESS);
1327 }
1328 }
1329 // Rotating plate
1330 {
1331 ImU32 node_color = Ogre::ColourValue(1.00f, 0.87f, 0.27f, j < 2 ? 1.0f : 0.5f).getAsABGR();
1332 ImU32 beam_color = Ogre::ColourValue(0.88f, 0.64f, 0.33f, j < 2 ? 1.0f : 0.5f).getAsABGR();
1333
1334 Ogre::Vector3 pos3_xyz = world2screen.Convert(nodes[rotators[i].nodes2[j]].AbsPosition);
1335 if (pos3_xyz.z < 0.f)
1336 {
1337 ImVec2 pos(pos3_xyz.x, pos3_xyz.y);
1338 drawlist->AddCircleFilled(pos, NODE_RADIUS, node_color);
1339 Str<25> id_buf;
1340 id_buf << nodes[rotators[i].nodes2[j]].pos;
1341 drawlist->AddText(pos, NODE_TEXT_COLOR, id_buf.ToCStr());
1342 }
1343 if ((pos2_xyz.z < 0.f) && (pos3_xyz.z < 0.f))
1344 {
1345 ImVec2 pos1xy(pos2_xyz.x, pos2_xyz.y);
1346 ImVec2 pos2xy(pos3_xyz.x, pos3_xyz.y);
1347 drawlist->AddLine(pos1xy, pos2xy, beam_color, BEAM_BROKEN_THICKNESS);
1348 }
1349 }
1350 }
1351
1352 // Projection plane
1353 {
1354 Ogre::Vector3 mid = nodes[rotators[i].axis1].AbsPosition.midPoint(nodes[rotators[i].axis2].AbsPosition);
1355 Ogre::Vector3 axis = nodes[rotators[i].axis1].RelPosition - nodes[rotators[i].axis2].RelPosition;
1356 Ogre::Vector3 perp = axis.perpendicular();
1357 axis.normalise();
1358
1359 const int steps = 64;
1360 Ogre::Plane plane = Ogre::Plane(axis, mid);
1361
1362 Ogre::Real radius1 = 0.0f;
1363 Ogre::Real offset1 = 0.0f;
1364 for (int k = 0; k < 2; k++)
1365 {
1366 Ogre::Vector3 r1 = nodes[rotators[i].nodes1[k]].RelPosition - nodes[rotators[i].axis1].RelPosition;
1367 Ogre::Real r = plane.projectVector(r1).length();
1368 if (r > radius1)
1369 {
1370 radius1 = r;
1371 offset1 = plane.getDistance(nodes[rotators[i].nodes1[k]].AbsPosition);
1372 }
1373 }
1374 std::vector<ImVec2> pos1_xy;
1375 for (int k = 0; k < steps; k++)
1376 {
1377 Ogre::Quaternion rotation(Ogre::Radian(((float)k / steps) * Ogre::Math::TWO_PI), axis);
1378 Ogre::Vector3 pos_xyz = world2screen.Convert(mid + axis * offset1 + rotation * perp * radius1);
1379 if (pos_xyz.z < 0.f)
1380 {
1381 pos1_xy.push_back(ImVec2(pos_xyz.x, pos_xyz.y));
1382 }
1383 }
1384 if (!pos1_xy.empty())
1385 {
1386 drawlist->AddConvexPolyFilled(pos1_xy.data(), static_cast<int>(pos1_xy.size()), 0x33666666);
1387 }
1388
1389 Ogre::Real radius2 = 0.0f;
1390 Ogre::Real offset2 = 0.0f;
1391 for (int k = 0; k < 2; k++)
1392 {
1393 Ogre::Vector3 r2 = nodes[rotators[i].nodes2[k]].RelPosition - nodes[rotators[i].axis2].RelPosition;
1394 Ogre::Real r = plane.projectVector(r2).length();
1395 if (r > radius2)
1396 {
1397 radius2 = r;
1398 offset2 = plane.getDistance(nodes[rotators[i].nodes2[k]].AbsPosition);
1399 }
1400 }
1401 std::vector<ImVec2> pos2_xy;
1402 for (int k = 0; k < steps; k++)
1403 {
1404 Ogre::Quaternion rotation(Ogre::Radian(((float)k / steps) * Ogre::Math::TWO_PI), axis);
1405 Ogre::Vector3 pos_xyz = world2screen.Convert(mid + axis * offset2 + rotation * perp * radius2);
1406 if (pos_xyz.z < 0.f)
1407 {
1408 pos2_xy.push_back(ImVec2(pos_xyz.x, pos_xyz.y));
1409 }
1410 }
1411 if (!pos2_xy.empty())
1412 {
1413 drawlist->AddConvexPolyFilled(pos2_xy.data(), static_cast<int>(pos2_xy.size()), 0x1155a3e0);
1414 }
1415
1416 for (int k = 0; k < 2; k++)
1417 {
1418 // Projected and rotated base plate arms (theory vectors)
1419 Ogre::Vector3 ref1 = plane.projectVector(nodes[rotators[i].nodes1[k]].AbsPosition - mid);
1420 Ogre::Vector3 th1 = Ogre::Quaternion(Ogre::Radian(rotators[i].angle), axis) * ref1;
1421 {
1422 Ogre::Vector3 pos1_xyz = world2screen.Convert(mid + axis * offset1);
1423 Ogre::Vector3 pos2_xyz = world2screen.Convert(mid + axis * offset1 + th1);
1424 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f))
1425 {
1426 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1427 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1428 drawlist->AddLine(pos1xy, pos2xy, 0x44888888, BEAM_BROKEN_THICKNESS);
1429 }
1430 }
1431 // Projected rotation plate arms
1432 Ogre::Vector3 ref2 = plane.projectVector(nodes[rotators[i].nodes2[k]].AbsPosition - mid);
1433 {
1434 Ogre::Vector3 pos1_xyz = world2screen.Convert(mid + axis * offset2);
1435 Ogre::Vector3 pos2_xyz = world2screen.Convert(mid + axis * offset2 + ref2);
1436 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f))
1437 {
1438 ImVec2 pos1xy(pos1_xyz.x, pos1_xyz.y);
1439 ImVec2 pos2xy(pos2_xyz.x, pos2_xyz.y);
1440 drawlist->AddLine(pos1xy, pos2xy, 0x4455a3e0, BEAM_BROKEN_THICKNESS);
1441 }
1442 }
1443 // Virtual plate connections
1444 th1.normalise();
1445 Ogre::Real radius = std::min(radius1, radius2);
1446 Ogre::Vector3 pos3_xyz = world2screen.Convert(mid + axis * offset1 + th1 * radius);
1447 Ogre::Vector3 pos4_xyz = world2screen.Convert(mid + axis * offset2 + th1 * radius);
1448 if ((pos3_xyz.z < 0.f) && (pos4_xyz.z < 0.f))
1449 {
1450 ImVec2 pos1xy(pos3_xyz.x, pos3_xyz.y);
1451 ImVec2 pos2xy(pos4_xyz.x, pos4_xyz.y);
1452 drawlist->AddLine(pos1xy, pos2xy, BEAM_COLOR, BEAM_BROKEN_THICKNESS);
1453 }
1454 }
1455 }
1456 }
1457 } else if (m_debug_view == DebugViewType::DEBUGVIEW_SLIDENODES)
1458 {
1459 // Slide nodes
1460 const node_t* nodes = m_actor->ar_nodes;
1461 std::set<int> node_ids;
1462 for (auto railgroup : m_actor->m_railgroups)
1463 {
1464 for (auto railsegment : railgroup->rg_segments)
1465 {
1466 Ogre::Vector3 pos1 = world2screen.Convert(railsegment.rs_beam->p1->AbsPosition);
1467 Ogre::Vector3 pos2 = world2screen.Convert(railsegment.rs_beam->p2->AbsPosition);
1468
1469 if (pos1.z < 0.f)
1470 {
1471 node_ids.insert(railsegment.rs_beam->p1->pos);
1472 }
1473 if (pos2.z < 0.f)
1474 {
1475 node_ids.insert(railsegment.rs_beam->p2->pos);
1476 }
1477 if ((pos1.z < 0.f) && (pos2.z < 0.f))
1478 {
1479 ImVec2 pos1xy(pos1.x, pos1.y);
1480 ImVec2 pos2xy(pos2.x, pos2.y);
1481
1482 drawlist->AddLine(pos1xy, pos2xy, BEAM_COLOR, BEAM_BROKEN_THICKNESS);
1483 }
1484 }
1485 }
1486 for (auto id : node_ids)
1487 {
1488 Ogre::Vector3 pos_xyz = world2screen.Convert(nodes[id].AbsPosition);
1489 if (pos_xyz.z < 0.f)
1490 {
1491 ImVec2 pos_xy(pos_xyz.x, pos_xyz.y);
1492 drawlist->AddCircleFilled(pos_xy, NODE_RADIUS, NODE_COLOR);
1493 // Node info
1494 Str<25> id_buf;
1495 id_buf << id;
1496 drawlist->AddText(pos_xy, NODE_TEXT_COLOR, id_buf.ToCStr());
1497 }
1498 }
1499 for (auto slidenode : m_actor->m_slidenodes)
1500 {
1501 auto id = slidenode.GetSlideNodeId();
1502 Ogre::Vector3 pos_xyz = world2screen.Convert(nodes[id].AbsPosition);
1503
1504 if (pos_xyz.z < 0.f)
1505 {
1506 ImVec2 pos(pos_xyz.x, pos_xyz.y);
1507 drawlist->AddCircleFilled(pos, NODE_IMMOVABLE_RADIUS, NODE_IMMOVABLE_COLOR);
1508 // Node info
1509 Str<25> id_buf;
1510 id_buf << id;
1511 drawlist->AddText(pos, NODE_TEXT_COLOR, id_buf.ToCStr());
1512 }
1513 }
1514 } else if (m_debug_view == DebugViewType::DEBUGVIEW_SUBMESH)
1515 {
1516 // Cabs
1517 const node_t* nodes = m_actor->ar_nodes;
1518 const auto cabs = m_actor->ar_cabs;
1519 const auto num_cabs = m_actor->ar_num_cabs;
1520 const auto buoycabs = m_actor->ar_buoycabs;
1521 const auto num_buoycabs = m_actor->ar_num_buoycabs;
1522 const auto collcabs = m_actor->ar_collcabs;
1523 const auto num_collcabs = m_actor->ar_num_collcabs;
1524
1525 std::vector<std::pair<float, int>> render_cabs;
1526 for (int i = 0; i < num_cabs; i++)
1527 {
1528 Ogre::Vector3 pos1_xyz = world2screen.Convert(nodes[cabs[i*3+0]].AbsPosition);
1529 Ogre::Vector3 pos2_xyz = world2screen.Convert(nodes[cabs[i*3+1]].AbsPosition);
1530 Ogre::Vector3 pos3_xyz = world2screen.Convert(nodes[cabs[i*3+2]].AbsPosition);
1531 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f) && (pos3_xyz.z < 0.f))
1532 {
1533 float depth = pos1_xyz.z;
1534 depth = std::max(depth, pos2_xyz.z);
1535 depth = std::max(depth, pos3_xyz.z);
1536 render_cabs.push_back({depth, i});
1537 }
1538 }
1539 std::sort(render_cabs.begin(), render_cabs.end());
1540
1541 // Cabs and contacters (which are part of a cab)
1542 std::vector<int> node_ids;
1543 for (auto render_cab : render_cabs)
1544 {
1545 int i = render_cab.second;
1546 bool coll = std::find(collcabs, collcabs + num_collcabs, i) != (collcabs + num_collcabs);
1547 bool buoy = std::find(buoycabs, buoycabs + num_buoycabs, i) != (buoycabs + num_buoycabs);
1548
1549 ImU32 fill_color = Ogre::ColourValue(0.5f * coll, 0.5f * !buoy, 0.5f * (coll ^ buoy), 0.27f).getAsABGR();
1550 ImU32 beam_color = Ogre::ColourValue(0.5f * coll, 0.5f * !buoy, 0.5f * (coll ^ buoy), 0.53f).getAsABGR();
1551
1552 Ogre::Vector3 pos1_xyz = world2screen.Convert(nodes[cabs[i*3+0]].AbsPosition);
1553 Ogre::Vector3 pos2_xyz = world2screen.Convert(nodes[cabs[i*3+1]].AbsPosition);
1554 Ogre::Vector3 pos3_xyz = world2screen.Convert(nodes[cabs[i*3+2]].AbsPosition);
1555 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f) && (pos3_xyz.z < 0.f))
1556 {
1557 ImVec2 pos1_xy(pos1_xyz.x, pos1_xyz.y);
1558 ImVec2 pos2_xy(pos2_xyz.x, pos2_xyz.y);
1559 ImVec2 pos3_xy(pos3_xyz.x, pos3_xyz.y);
1560 drawlist->AddTriangleFilled(pos1_xy, pos2_xy, pos3_xy, fill_color);
1561 drawlist->AddTriangle(pos1_xy, pos2_xy, pos3_xy, beam_color, BEAM_THICKNESS);
1562 }
1563 for (int k = 0; k < 3; k++)
1564 {
1565 int id = cabs[i*3+k];
1566 if (std::find(node_ids.begin(), node_ids.end(), id) == node_ids.end())
1567 {
1568 Ogre::Vector3 pos_xyz = world2screen.Convert(nodes[id].AbsPosition);
1569 if (pos_xyz.z < 0.f)
1570 {
1571 ImVec2 pos_xy(pos_xyz.x, pos_xyz.y);
1572 drawlist->AddCircleFilled(pos_xy, NODE_RADIUS, nodes[id].nd_contacter ? 0xbb0033ff : 0x88888888);
1573 // Node info
1574 Str<25> id_buf;
1575 id_buf << id;
1576 drawlist->AddText(pos_xy, NODE_TEXT_COLOR, id_buf.ToCStr());
1577 }
1578 node_ids.push_back(id);
1579 }
1580 }
1581 }
1582 } else if (m_debug_view == DebugViewType::DEBUGVIEW_BUOYANCY)
1583 {
1584 if (m_actor->m_buoyance)
1585 {
1586 // Draw submerged triangles (constructed dynamically)
1587 for (BuoyDebugSubCab& subcab: m_actor->m_buoyance->buoy_debug_subcabs)
1588 {
1589 ImU32 fill_color = Ogre::ColourValue(0.4f, 0.4f, 0.9f, 0.27f).getAsABGR();
1590 ImU32 beam_color = Ogre::ColourValue(0.2f, 0.3f, 0.8f, 0.53f).getAsABGR();
1591
1592 Ogre::Vector3 pos1_xyz = world2screen.Convert(subcab.a);
1593 Ogre::Vector3 pos2_xyz = world2screen.Convert(subcab.b);
1594 Ogre::Vector3 pos3_xyz = world2screen.Convert(subcab.c);
1595 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f) && (pos3_xyz.z < 0.f))
1596 {
1597 ImVec2 pos1_xy(pos1_xyz.x, pos1_xyz.y);
1598 ImVec2 pos2_xy(pos2_xyz.x, pos2_xyz.y);
1599 ImVec2 pos3_xy(pos3_xyz.x, pos3_xyz.y);
1600 drawlist->AddTriangleFilled(pos1_xy, pos2_xy, pos3_xy, fill_color);
1601 drawlist->AddTriangle(pos1_xy, pos2_xy, pos3_xy, beam_color, BEAM_THICKNESS);
1602 }
1603 }
1604
1605 // Draw buoyant hull triangles (defined statically)
1606 for (int i = 0; i < m_actor->ar_num_buoycabs; i++)
1607 {
1608 const ImU32 beam_color = Ogre::ColourValue(0.5f, 0.1f, 0.1f, 0.53f).getAsABGR();
1609 int tmpv = m_actor->ar_buoycabs[i] * 3;
1610 Ogre::Vector3 pos1_xyz = world2screen.Convert(m_actor->ar_nodes[m_actor->ar_cabs[tmpv]].AbsPosition);
1611 Ogre::Vector3 pos2_xyz = world2screen.Convert(m_actor->ar_nodes[m_actor->ar_cabs[tmpv + 1]].AbsPosition);
1612 Ogre::Vector3 pos3_xyz = world2screen.Convert(m_actor->ar_nodes[m_actor->ar_cabs[tmpv + 2]].AbsPosition);
1613 if ((pos1_xyz.z < 0.f) && (pos2_xyz.z < 0.f) && (pos3_xyz.z < 0.f))
1614 {
1615 ImVec2 pos1_xy(pos1_xyz.x, pos1_xyz.y);
1616 ImVec2 pos2_xy(pos2_xyz.x, pos2_xyz.y);
1617 ImVec2 pos3_xy(pos3_xyz.x, pos3_xyz.y);
1618 drawlist->AddTriangle(pos1_xy, pos2_xy, pos3_xy, beam_color, BEAM_THICKNESS);
1619 }
1620 }
1621
1622 for (BuoyCachedNode& bcn: m_actor->m_buoyance->buoy_cached_nodes)
1623 {
1624 Ogre::Vector3 pos_xyz = world2screen.Convert(bcn.AbsPosition);
1625 if (pos_xyz.z < 0.f)
1626 {
1627 ImVec2 pos_xy(pos_xyz.x, pos_xyz.y);
1628 ImU32 col = 0x88aaaaaa;
1629 drawlist->AddCircleFilled(pos_xy, NODE_RADIUS, col);
1630 // Node info
1631 Str<25> id_buf;
1632 id_buf << bcn.nodenum;
1633 drawlist->AddText(pos_xy, NODE_TEXT_COLOR, id_buf.ToCStr());
1634 }
1635 }
1636 }
1637 }
1638}
1639
1641{
1642 if (m_debug_view == DebugViewType::DEBUGVIEW_NONE)
1643 m_debug_view = m_last_debug_view;
1644 else
1645 m_debug_view = DebugViewType::DEBUGVIEW_NONE;
1646}
1647
1649{
1650 if (dv < DEBUGVIEWTYPE_FIRST ||
1651 dv > DEBUGVIEWTYPE_LAST ||
1652 dv == DebugViewType::DEBUGVIEW_WHEELS && m_actor->ar_num_wheels == 0 ||
1653 dv == DebugViewType::DEBUGVIEW_SHOCKS && m_actor->ar_num_shocks == 0 ||
1654 dv == DebugViewType::DEBUGVIEW_ROTATORS && m_actor->ar_num_rotators == 0 ||
1655 dv == DebugViewType::DEBUGVIEW_SLIDENODES && m_actor->hasSlidenodes() == 0 ||
1656 dv == DebugViewType::DEBUGVIEW_SUBMESH && m_actor->ar_num_cabs == 0 ||
1657 dv == DebugViewType::DEBUGVIEW_BUOYANCY && m_actor->ar_buoycabs == 0)
1658 {
1659 return false;
1660 }
1661 return true;
1662}
1663
1665{
1666 if (!this->IsDebugViewApplicable(dv))
1667 {
1669 }
1670
1671 m_debug_view = dv;
1673 {
1674 m_last_debug_view = dv;
1675 }
1676}
1677
1679{
1680 do
1681 {
1682 m_debug_view = NextDebugViewType(m_debug_view);
1683 }
1684 while (!this->IsDebugViewApplicable(m_debug_view));
1685}
1686
1688{
1689 for (BeamGfx& rod: m_gfx_beams)
1690 {
1691 rod.rod_scenenode->setVisible(rod.rod_is_visible);
1692 if (!rod.rod_is_visible)
1693 continue;
1694
1695 NodeSB* nodes1 = this->GetSimNodeBuffer();
1696 Ogre::Vector3 pos1 = nodes1[rod.rod_node1].AbsPosition;
1697 NodeSB* nodes2 = rod.rod_target_actor->GetGfxActor()->GetSimNodeBuffer();
1698 Ogre::Vector3 pos2 = nodes2[rod.rod_node2].AbsPosition;
1699
1700 // Classic method
1701 float beam_diameter = rod.rod_diameter;
1702 float beam_length = pos1.distance(pos2);
1703
1704 rod.rod_scenenode->setPosition(pos1.midPoint(pos2));
1705 rod.rod_scenenode->setScale(beam_diameter, beam_length, beam_diameter);
1706 rod.rod_scenenode->setOrientation(GfxScene::SpecialGetRotationTo(Ogre::Vector3::UNIT_Y, (pos1 - pos2)));
1707 }
1708}
1709
1710void RoR::GfxActor::ScaleActor(Ogre::Vector3 relpos, float ratio)
1711{
1712 for (BeamGfx& rod: m_gfx_beams)
1713 {
1714 rod.rod_diameter *= ratio;
1715 }
1716
1717 // props and stuff
1718 // TOFIX: care about prop positions as well!
1719 for (Prop& prop: m_props)
1720 {
1721 if (prop.pp_scene_node)
1722 prop.pp_scene_node->scale(ratio, ratio, ratio);
1723
1724 if (prop.pp_wheel_scene_node)
1725 {
1726 prop.pp_wheel_scene_node->scale(ratio, ratio, ratio);
1727 prop.pp_wheel_pos = relpos + (prop.pp_wheel_pos - relpos) * ratio;
1728 }
1729
1730 if (prop.pp_beacon_scene_node[0])
1731 prop.pp_beacon_scene_node[0]->scale(ratio, ratio, ratio);
1732
1733 if (prop.pp_beacon_scene_node[1])
1734 prop.pp_beacon_scene_node[1]->scale(ratio, ratio, ratio);
1735
1736 if (prop.pp_beacon_scene_node[2])
1737 prop.pp_beacon_scene_node[2]->scale(ratio, ratio, ratio);
1738
1739 if (prop.pp_beacon_scene_node[3])
1740 prop.pp_beacon_scene_node[3]->scale(ratio, ratio, ratio);
1741 }
1742
1743 // Old cab mesh
1744 if (m_cab_mesh)
1745 {
1746 m_cab_mesh->ScaleFlexObj(ratio);
1747 }
1748}
1749
1751{
1752 if (m_gfx_beams_parent_scenenode == nullptr)
1753 {
1754 return; // Vehicle has no visual softbody beams -> nothing to do.
1755 }
1756
1757 // NOTE: We don't use Ogre::SceneNode::setVisible() for performance reasons:
1758 // 1. That function traverses all attached Entities and updates their visibility - too much overhead
1759 // 2. For OGRE up to 1.9 (I don't know about 1.10+) OGRE developers recommended to detach rather than hide.
1760 // ~ only_a_ptr, 12/2017
1761 if (visible && !m_gfx_beams_parent_scenenode->isInSceneGraph())
1762 {
1763 App::GetGfxScene()->GetSceneManager()->getRootSceneNode()->addChild(m_gfx_beams_parent_scenenode);
1764 }
1765 else if (!visible && m_gfx_beams_parent_scenenode->isInSceneGraph())
1766 {
1767 App::GetGfxScene()->GetSceneManager()->getRootSceneNode()->removeChild(m_gfx_beams_parent_scenenode);
1768 }
1769}
1770
1772{
1773 // PLEASE maintain the same order as in `struct ActorSB`
1774
1775 // Gameplay state
1776 m_simbuf.simbuf_actor_state = m_actor->ar_state;
1777 m_simbuf.simbuf_physics_paused = m_actor->ar_physics_paused;
1778 m_simbuf.simbuf_cur_cinecam = m_actor->ar_current_cinecam;
1779 m_simbuf.simbuf_net_username = m_actor->m_net_username;
1780 m_simbuf.simbuf_net_colornum = m_actor->m_net_color_num;
1781 m_simbuf.simbuf_driveable = m_actor->ar_driveable;
1782
1783 // Movement
1784 m_simbuf.simbuf_pos = m_actor->getRotationCenter();
1785 m_simbuf.simbuf_node0_velo = m_actor->ar_nodes[0].Velocity;
1786 m_simbuf.simbuf_rotation = m_actor->getRotation();
1787 m_simbuf.simbuf_direction = m_actor->getDirection();
1788 m_simbuf.simbuf_wheel_speed = m_actor->ar_wheel_speed;
1789 m_simbuf.simbuf_top_speed = m_actor->ar_top_speed;
1790 m_simbuf.simbuf_aabb = m_actor->ar_bounding_box;
1791 if (m_actor->ar_num_cameras > 0)
1792 {
1793 m_simbuf.simbuf_camera0_pos_node = m_actor->ar_camera_node_pos[0];
1794 m_simbuf.simbuf_camera0_roll_node = m_actor->ar_camera_node_roll[0];
1795 }
1796
1797 // Elements: nodes
1798 m_simbuf.simbuf_nodes.resize(m_actor->ar_num_nodes);
1799 for (int i = 0; i < m_actor->ar_num_nodes; ++i)
1800 {
1801 auto node = m_actor->ar_nodes[i];
1802 m_simbuf.simbuf_nodes[i].AbsPosition = node.AbsPosition;
1803 m_simbuf.simbuf_nodes[i].nd_has_contact = node.nd_has_ground_contact || node.nd_has_mesh_contact;
1804 }
1805
1806 for (NodeGfx& nx: m_gfx_nodes)
1807 {
1808 m_simbuf.simbuf_nodes[nx.nx_node_idx].nd_is_wet = (nx.nx_wet_time_sec != -1.f);
1809 }
1810
1811 // Elements: beams
1812 for (BeamGfx& rod: m_gfx_beams)
1813 {
1814 const beam_t& beam = m_actor->ar_beams[rod.rod_beam_index];
1815 rod.rod_node1 = static_cast<uint16_t>(beam.p1->pos);
1816 rod.rod_node2 = static_cast<uint16_t>(beam.p2->pos);
1817 if (beam.bm_inter_actor)
1818 {
1819 rod.rod_target_actor = beam.bm_locked_actor;
1820 }
1821 rod.rod_is_visible = !beam.bm_disabled && !beam.bm_broken;
1822 }
1823
1824 // Elements: airbrakes
1825 m_simbuf.simbuf_airbrakes.resize(m_actor->ar_airbrakes.size());
1826 for (size_t i=0; i< m_actor->ar_airbrakes.size(); ++i)
1827 {
1828 m_simbuf.simbuf_airbrakes[i].simbuf_ab_ratio = m_actor->ar_airbrakes[i]->getRatio();
1829 }
1830
1831 // Elements: Command keys
1832 for (int i = 1; i <= MAX_COMMANDS; ++i) // BEWARE: commandkeys are indexed 1-MAX_COMMANDS!
1833 {
1834 m_simbuf.simbuf_commandkey[i].simbuf_cmd_value = m_actor->ar_command_key[i].commandValue;
1835 }
1836
1837 // Elements: Prop animation keys
1838 m_simbuf.simbuf_prop_anim_keys.resize(m_actor->m_prop_anim_key_states.size());
1839 for (size_t i = 0; i < m_actor->m_prop_anim_key_states.size(); ++i)
1840 {
1841 m_simbuf.simbuf_prop_anim_keys[i].simbuf_anim_active = m_actor->m_prop_anim_key_states[i].anim_active;
1842 }
1843
1844 // Elements: Aeroengines
1845 m_simbuf.simbuf_aeroengines.resize(m_actor->ar_num_aeroengines);
1846 for (int i = 0; i < m_actor->ar_num_aeroengines; ++i)
1847 {
1848 AeroEngine* src = m_actor->ar_aeroengines[i].GetRef();
1849 AeroEngineSB& dst = m_simbuf.simbuf_aeroengines[i];
1850
1851 dst.simbuf_ae_type = src->getType();
1852 dst.simbuf_ae_throttle = src->getThrottle();
1853 dst.simbuf_ae_rpm = src->getRPM();
1854 dst.simbuf_ae_rpmpc = src->getRPMpc();
1855 dst.simbuf_ae_rpm = src->getRPM();
1856 dst.simbuf_ae_ignition = src->getIgnition();
1857 dst.simbuf_ae_failed = src->isFailed();
1858
1860 {
1861 Turboprop* tp = static_cast<Turboprop*>(src);
1862 dst.simbuf_tp_aetorque = (100.0 * tp->indicated_torque / tp->max_torque); // TODO: Code ported as-is from calcAnimators(); what does it do? ~ only_a_ptr, 06/2018
1863 dst.simbuf_tp_aepitch = tp->pitch;
1864 }
1865 else // turbojet
1866 {
1867 Turbojet* tj = static_cast<Turbojet*>(src);
1868 dst.simbuf_tj_afterburn = tj->getAfterburner() != 0.f;
1871 }
1872 }
1873
1874 // Engine (+drivetrain)
1875 m_simbuf.simbuf_hydro_dir_state = m_actor->ar_hydro_dir_state;
1876 m_simbuf.simbuf_brake = m_actor->ar_brake;
1877 if (m_actor->ar_engine != nullptr)
1878 {
1879 m_simbuf.simbuf_has_engine = true;
1880 m_simbuf.simbuf_gear = m_actor->ar_engine->getGear();
1881 m_simbuf.simbuf_autoshift = m_actor->ar_engine->getAutoShift();
1882 m_simbuf.simbuf_engine_ignition = m_actor->ar_engine->hasContact();
1883 m_simbuf.simbuf_engine_rpm = m_actor->ar_engine->getRPM();
1884 m_simbuf.simbuf_engine_turbo_psi= m_actor->ar_engine->getTurboPSI();
1885 m_simbuf.simbuf_engine_accel = m_actor->ar_engine->getAcc();
1886 m_simbuf.simbuf_engine_torque = m_actor->ar_engine->getCurEngineTorque();
1887 m_simbuf.simbuf_inputshaft_rpm = m_actor->ar_engine->getInputShaftRPM();
1888 m_simbuf.simbuf_drive_ratio = m_actor->ar_engine->getDriveRatio();
1889 m_simbuf.simbuf_clutch = m_actor->ar_engine->getClutch();
1890 m_simbuf.simbuf_num_gears = m_actor->ar_engine->getNumGears();
1891 m_simbuf.simbuf_engine_max_rpm = m_actor->ar_engine->getShiftUpRPM();
1892 m_simbuf.simbuf_engine_smoke = m_actor->ar_engine->getSmoke();
1893 }
1894 if (m_actor->m_num_wheel_diffs > 0)
1895 {
1896 m_simbuf.simbuf_diff_type = m_actor->m_wheel_diffs[0]->GetActiveDiffType();
1897 }
1898
1899 // Tyre pressure
1900 m_simbuf.simbuf_tyre_pressure = m_actor->getTyrePressure().GetCurPressure();
1901 m_simbuf.simbuf_tyre_pressurizing = m_actor->getTyrePressure().IsPressurizing();
1902
1903 // Effects
1904 m_simbuf.simbuf_lightmask = m_actor->m_lightmask;
1905 m_simbuf.simbuf_smoke_enabled = m_actor->getSmokeEnabled();
1906 m_simbuf.simbuf_parking_brake = m_actor->ar_parking_brake;
1907 m_simbuf.simbuf_cparticles_active = m_actor->ar_cparticles_active;
1908
1909 // Aerial
1910 m_simbuf.simbuf_hydro_aileron_state = m_actor->ar_hydro_aileron_state;
1911 m_simbuf.simbuf_hydro_elevator_state = m_actor->ar_hydro_elevator_state;
1912 m_simbuf.simbuf_hydro_aero_rudder_state = m_actor->ar_hydro_rudder_state;
1913 m_simbuf.simbuf_aero_flap_state = m_actor->ar_aerial_flap;
1914 m_simbuf.simbuf_airbrake_state = m_actor->ar_airbrake_intensity;
1915 if (m_actor->ar_num_wings > 4)
1916 {
1917 m_simbuf.simbuf_wing4_aoa = m_actor->ar_wings[4].fa->aoa;
1918 }
1919
1920 // Autopilot
1921 if (m_actor->ar_autopilot != nullptr)
1922 {
1923 m_simbuf.simbuf_has_autopilot = true;
1924 m_simbuf.simbuf_ap_heading_mode = m_actor->ar_autopilot->GetHeadingMode();
1925 m_simbuf.simbuf_ap_heading_value = m_actor->ar_autopilot->heading;
1926 m_simbuf.simbuf_ap_alt_mode = m_actor->ar_autopilot->GetAltMode();
1927 m_simbuf.simbuf_ap_alt_value = m_actor->ar_autopilot->GetAltValue();
1928 m_simbuf.simbuf_ap_ias_mode = m_actor->ar_autopilot->GetIasMode();
1929 m_simbuf.simbuf_ap_ias_value = m_actor->ar_autopilot->GetIasValue();
1930 m_simbuf.simbuf_ap_gpws_mode = m_actor->ar_autopilot->GetGpwsMode();
1931 m_simbuf.simbuf_ap_ils_available = m_actor->ar_autopilot->IsIlsAvailable();
1932 m_simbuf.simbuf_ap_ils_vdev = m_actor->ar_autopilot->GetVerticalApproachDeviation();
1933 m_simbuf.simbuf_ap_ils_hdev = m_actor->ar_autopilot->GetHorizontalApproachDeviation();
1934 m_simbuf.simbuf_ap_vs_value = m_actor->ar_autopilot->GetVsValue();
1935 }
1936
1937 m_simbuf.simbuf_speedo_highest_kph = m_actor->ar_guisettings_speedo_max_kph;
1938 m_simbuf.simbuf_speedo_use_engine_max_rpm = m_actor->ar_guisettings_use_engine_max_rpm;
1939 m_simbuf.simbuf_shifter_anim_time = m_actor->ar_guisettings_shifter_anim_time;
1940
1941}
1942
1944{
1945 return (m_actor->ar_state < ActorState::LOCAL_SLEEPING);
1946}
1947
1949{
1950 if ((m_cab_entity != nullptr) && (m_cab_mesh != nullptr))
1951 {
1952 m_cab_scene_node->setPosition(m_cab_mesh->UpdateFlexObj());
1953 }
1954}
1955
1957{
1958 m_flexwheel_tasks.clear();
1959
1960 for (WheelGfx& w: m_wheels)
1961 {
1962 if (w.wx_flex_mesh != nullptr && w.wx_flex_mesh->flexitPrepare())
1963 {
1964 auto func = std::function<void()>([this, w]()
1965 {
1966 w.wx_flex_mesh->flexitCompute();
1967 });
1968 auto task_handle = App::GetThreadPool()->RunTask(func);
1969 m_flexwheel_tasks.push_back(task_handle);
1970 }
1971 }
1972}
1973
1975{
1976 for (auto& task: m_flexwheel_tasks)
1977 {
1978 task->join();
1979 }
1980 for (WheelGfx& w: m_wheels)
1981 {
1982 if (w.wx_scenenode != nullptr && w.wx_flex_mesh != nullptr)
1983 {
1984 w.wx_scenenode->setPosition(w.wx_flex_mesh->flexitFinal());
1985 }
1986 }
1987}
1988
1990{
1991 for (WheelGfx& w: m_wheels)
1992 {
1993 if (w.wx_scenenode != nullptr)
1994 {
1995 w.wx_scenenode->setVisible(value);
1996 }
1997 if (w.wx_flex_mesh != nullptr)
1998 {
1999 w.wx_flex_mesh->setVisible(value);
2000 }
2001 }
2002}
2003
2004
2005int RoR::GfxActor::GetActorId () const { return m_actor->ar_instance_id; }
2006int RoR::GfxActor::GetActorState () const { return static_cast<int>(m_actor->ar_state); }
2007
2009{
2010 const size_t num_airbrakes = m_gfx_airbrakes.size();
2011 for (size_t i=0; i<num_airbrakes; ++i)
2012 {
2013 AirbrakeGfx abx = m_gfx_airbrakes[i];
2014 const float ratio = m_simbuf.simbuf_airbrakes[i].simbuf_ab_ratio;
2015 const float maxangle = m_actor->ar_airbrakes[i]->getMaxAngle();
2016 Ogre::Vector3 ref_node_pos = m_simbuf.simbuf_nodes[m_gfx_airbrakes[i].abx_ref_node].AbsPosition;
2017 Ogre::Vector3 x_node_pos = m_simbuf.simbuf_nodes[m_gfx_airbrakes[i].abx_x_node].AbsPosition;
2018 Ogre::Vector3 y_node_pos = m_simbuf.simbuf_nodes[m_gfx_airbrakes[i].abx_y_node].AbsPosition;
2019
2020 // -- Ported from `AirBrake::updatePosition()` --
2021 Ogre::Vector3 normal = (y_node_pos - ref_node_pos).crossProduct(x_node_pos - ref_node_pos);
2022 normal.normalise();
2023 //position
2024 Ogre::Vector3 mposition = ref_node_pos + abx.abx_offset.x * (x_node_pos - ref_node_pos) + abx.abx_offset.y * (y_node_pos - ref_node_pos);
2025 abx.abx_scenenode->setPosition(mposition + normal * abx.abx_offset.z);
2026 //orientation
2027 Ogre::Vector3 refx = x_node_pos - ref_node_pos;
2028 refx.normalise();
2029 Ogre::Vector3 refy = refx.crossProduct(normal);
2030 Ogre::Quaternion orientation = Ogre::Quaternion(Ogre::Degree(-ratio * maxangle), (x_node_pos - ref_node_pos).normalisedCopy()) * Ogre::Quaternion(refx, normal, refy);
2031 abx.abx_scenenode->setOrientation(orientation);
2032
2033 }
2034}
2035
2037{
2038 for (CParticle& cparticle: m_cparticles)
2039 {
2040 App::GetGfxScene()->AdjustParticleSystemTimeFactor(cparticle.psys, this);
2041 const Ogre::Vector3 pos = m_simbuf.simbuf_nodes[cparticle.emitterNode].AbsPosition;
2042 const Ogre::Vector3 dir = fast_normalise(pos - m_simbuf.simbuf_nodes[cparticle.directionNode].AbsPosition);
2043 cparticle.snode->setPosition(pos);
2044
2045 for (unsigned short j = 0; j < cparticle.psys->getNumEmitters(); j++)
2046 {
2047 cparticle.psys->getEmitter(j)->setEnabled(m_simbuf.simbuf_cparticles_active);
2048 cparticle.psys->getEmitter(j)->setDirection(dir);
2049 }
2050 }
2051}
2052
2054{
2055 if (!m_simbuf.simbuf_has_engine)
2056 return;
2057
2058 for (Exhaust& exhaust: m_exhausts)
2059 {
2060 if (!exhaust.smoker) // This remains `nullptr` if removed via `addonpart_unwanted_exhaust` or Tuning UI.
2061 continue;
2062
2063 App::GetGfxScene()->AdjustParticleSystemTimeFactor(exhaust.smoker, this);
2064 const Ogre::Vector3 pos = m_simbuf.simbuf_nodes[exhaust.emitterNode].AbsPosition;
2065 const Ogre::Vector3 dir = pos - m_simbuf.simbuf_nodes[exhaust.directionNode].AbsPosition;
2066 exhaust.smokeNode->setPosition(pos);
2067
2068 const bool active = m_simbuf.simbuf_smoke_enabled && m_simbuf.simbuf_engine_smoke != -1.f;
2069 exhaust.smoker->getEmitter(0)->setEnabled(active);
2070 if (active) // `setTimeToLive()` assert()s that argument is not negative.
2071 {
2072 exhaust.smoker->getEmitter(0)->setDirection(dir);
2073 exhaust.smoker->getEmitter(0)->setColour(Ogre::ColourValue(0.0, 0.0, 0.0, 0.02 + m_simbuf.simbuf_engine_smoke * 0.06));
2074 exhaust.smoker->getEmitter(0)->setTimeToLive((0.02 + m_simbuf.simbuf_engine_smoke * 0.06) / 0.04);
2075 exhaust.smoker->getEmitter(0)->setParticleVelocity(1.0 + m_simbuf.simbuf_engine_smoke * 2.0, 2.0 + m_simbuf.simbuf_engine_smoke * 3.0);
2076 }
2077 }
2078}
2079
2081{
2082 for (int i = 0; i < m_actor->ar_num_aeroengines; i++)
2083 {
2084 m_actor->ar_aeroengines[i]->updateVisuals(this);
2085 }
2086}
2087
2089{
2090 const bool is_remote =
2091 m_simbuf.simbuf_actor_state == ActorState::NETWORKED_OK ||
2092 m_simbuf.simbuf_actor_state == ActorState::NETWORKED_HIDDEN;
2093
2094 if (App::mp_hide_net_labels->getBool() || (!is_remote && App::mp_hide_own_net_label->getBool()) || App::mp_state->getEnum<MpState>() != MpState::CONNECTED)
2095 {
2096 return;
2097 }
2098
2099 float vlen = m_simbuf.simbuf_pos.distance(App::GetCameraManager()->GetCameraNode()->getPosition());
2100
2101 float y_offset = (m_simbuf.simbuf_aabb.getMaximum().y - m_simbuf.simbuf_pos.y) + (vlen / 100.0);
2102 Ogre::Vector3 scene_pos = m_simbuf.simbuf_pos + Ogre::Vector3::UNIT_Y * y_offset;
2103
2104 App::GetGfxScene()->DrawNetLabel(scene_pos, vlen, m_simbuf.simbuf_net_username, m_simbuf.simbuf_net_colornum);
2105
2106}
2107
2108void RoR::GfxActor::CalculateDriverPos(Ogre::Vector3& out_pos, Ogre::Quaternion& out_rot)
2109{
2110 ROR_ASSERT(m_driverseat_prop_index != -1);
2111 Prop* driverseat_prop = &m_props[m_driverseat_prop_index];
2112
2113 NodeSB* nodes = this->GetSimNodeBuffer();
2114
2115 const Ogre::Vector3 x_pos = nodes[driverseat_prop->pp_node_x].AbsPosition;
2116 const Ogre::Vector3 y_pos = nodes[driverseat_prop->pp_node_y].AbsPosition;
2117 const Ogre::Vector3 center_pos = nodes[driverseat_prop->pp_node_ref].AbsPosition;
2118
2119 const Ogre::Vector3 x_vec = x_pos - center_pos;
2120 const Ogre::Vector3 y_vec = y_pos - center_pos;
2121 const Ogre::Vector3 normal = (y_vec.crossProduct(x_vec)).normalisedCopy();
2122
2123 // Output position
2124 Ogre::Vector3 pos = center_pos;
2125 pos += (driverseat_prop->pp_offset.x * x_vec);
2126 pos += (driverseat_prop->pp_offset.y * y_vec);
2127 pos += (driverseat_prop->pp_offset.z * normal);
2128 out_pos = pos;
2129
2130 // Output orientation
2131 const Ogre::Vector3 x_vec_norm = x_vec.normalisedCopy();
2132 const Ogre::Vector3 y_vec_norm = x_vec_norm.crossProduct(normal);
2133 Ogre::Quaternion rot(x_vec_norm, normal, y_vec_norm);
2134 rot = rot * driverseat_prop->pp_rot;
2135 rot = rot * Ogre::Quaternion(Ogre::Degree(180), Ogre::Vector3::UNIT_Y); // rotate towards the driving direction
2136 out_rot = rot;
2137}
2138
2139void RoR::GfxActor::UpdateBeaconFlare(Prop & prop, float dt, bool is_player_actor)
2140{
2141 // TODO: Quick and dirty port from Beam::updateFlares(), clean it up ~ only_a_ptr, 06/2018
2142 using namespace Ogre;
2143
2144 bool enableAll = !((App::gfx_flares_mode->getEnum<GfxFlaresMode>() == GfxFlaresMode::CURR_VEHICLE_HEAD_ONLY) && !is_player_actor);
2145 NodeSB* nodes = this->GetSimNodeBuffer();
2146
2147 if (prop.pp_beacon_type == 'b')
2148 {
2149 // Get data
2150 Ogre::SceneNode* beacon_scene_node = prop.pp_scene_node;
2151 Ogre::Quaternion beacon_orientation = beacon_scene_node->getOrientation();
2152 Ogre::Light* pp_beacon_light = prop.pp_beacon_light[0];
2153 float beacon_rotation_rate = prop.pp_beacon_rot_rate[0];
2154 float beacon_rotation_angle = prop.pp_beacon_rot_angle[0]; // Updated at end of block
2155
2156 // Transform
2157 pp_beacon_light->setPosition(beacon_scene_node->getPosition() + beacon_orientation * Ogre::Vector3(0, 0, 0.12));
2158 beacon_rotation_angle += dt * beacon_rotation_rate;//rotate baby!
2159 pp_beacon_light->setDirection(beacon_orientation * Ogre::Vector3(cos(beacon_rotation_angle), sin(beacon_rotation_angle), 0));
2160 //billboard
2161 Ogre::Vector3 vdir = pp_beacon_light->getPosition() - App::GetCameraManager()->GetCameraNode()->getPosition(); // TODO: verify the position is already updated here ~ only_a_ptr, 06/2018
2162 float vlen = vdir.length();
2163 if (vlen > 100.0)
2164 {
2165 prop.pp_beacon_scene_node[0]->setVisible(false);
2166 return;
2167 }
2168 //normalize
2169 vdir = vdir / vlen;
2170 prop.pp_beacon_scene_node[0]->setPosition(pp_beacon_light->getPosition() - vdir * 0.1);
2171 float amplitude = pp_beacon_light->getDirection().dotProduct(vdir);
2172 if (amplitude > 0)
2173 {
2174 prop.pp_beacon_scene_node[0]->setVisible(true);
2175 prop.pp_beacon_bbs[0]->setDefaultDimensions(amplitude * amplitude * amplitude, amplitude * amplitude * amplitude);
2176 }
2177 else
2178 {
2179 prop.pp_beacon_scene_node[0]->setVisible(false);
2180 }
2181 pp_beacon_light->setVisible(enableAll);
2182
2183 // Update
2184 prop.pp_beacon_rot_angle[0] = beacon_rotation_angle;
2185 // NOTE: Light position is not updated here!
2186 }
2187 else if (prop.pp_beacon_type == 'p')
2188 {
2189 for (int k = 0; k < 4; k++)
2190 {
2191 //update light
2192 Quaternion orientation = prop.pp_scene_node->getOrientation();
2193 switch (k)
2194 {
2195 case 0: prop.pp_beacon_light[k]->setPosition(prop.pp_scene_node->getPosition() + orientation * Vector3(-0.64, 0, 0.14));
2196 break;
2197 case 1: prop.pp_beacon_light[k]->setPosition(prop.pp_scene_node->getPosition() + orientation * Vector3(-0.32, 0, 0.14));
2198 break;
2199 case 2: prop.pp_beacon_light[k]->setPosition(prop.pp_scene_node->getPosition() + orientation * Vector3(+0.32, 0, 0.14));
2200 break;
2201 case 3: prop.pp_beacon_light[k]->setPosition(prop.pp_scene_node->getPosition() + orientation * Vector3(+0.64, 0, 0.14));
2202 break;
2203 }
2204 prop.pp_beacon_rot_angle[k] += dt * prop.pp_beacon_rot_rate[k];//rotate baby!
2205 prop.pp_beacon_light[k]->setDirection(orientation * Vector3(cos(prop.pp_beacon_rot_angle[k]), sin(prop.pp_beacon_rot_angle[k]), 0));
2206 //billboard
2207 Vector3 vdir = prop.pp_beacon_light[k]->getPosition() - App::GetCameraManager()->GetCameraNode()->getPosition();
2208 float vlen = vdir.length();
2209 if (vlen > 100.0)
2210 {
2211 prop.pp_beacon_scene_node[k]->setVisible(false);
2212 continue;
2213 }
2214 //normalize
2215 vdir = vdir / vlen;
2216 prop.pp_beacon_scene_node[k]->setPosition(prop.pp_beacon_light[k]->getPosition() - vdir * 0.2);
2217 float amplitude = prop.pp_beacon_light[k]->getDirection().dotProduct(vdir);
2218 if (amplitude > 0)
2219 {
2220 prop.pp_beacon_scene_node[k]->setVisible(true);
2221 prop.pp_beacon_bbs[k]->setDefaultDimensions(amplitude * amplitude * amplitude, amplitude * amplitude * amplitude);
2222 }
2223 else
2224 {
2225 prop.pp_beacon_scene_node[k]->setVisible(false);
2226 }
2227 prop.pp_beacon_light[k]->setVisible(enableAll);
2228 }
2229 }
2230 else if (prop.pp_beacon_type == 'r')
2231 {
2232 //update light
2233 Quaternion orientation = prop.pp_scene_node->getOrientation();
2234 prop.pp_beacon_light[0]->setPosition(prop.pp_scene_node->getPosition() + orientation * Vector3(0, 0, 0.06));
2235 prop.pp_beacon_rot_angle[0] += dt * prop.pp_beacon_rot_rate[0];//rotate baby!
2236 //billboard
2237 Vector3 vdir = prop.pp_beacon_light[0]->getPosition() - App::GetCameraManager()->GetCameraNode()->getPosition();
2238 float vlen = vdir.length();
2239 if (vlen > 100.0)
2240 {
2241 prop.pp_beacon_scene_node[0]->setVisible(false);
2242 return;
2243 }
2244 //normalize
2245 vdir = vdir / vlen;
2246 prop.pp_beacon_scene_node[0]->setPosition(prop.pp_beacon_light[0]->getPosition() - vdir * 0.1);
2247 bool visible = false;
2248 if (prop.pp_beacon_rot_angle[0] > 1.0)
2249 {
2250 prop.pp_beacon_rot_angle[0] = 0.0;
2251 visible = true;
2252 }
2253 visible = visible && enableAll;
2254 prop.pp_beacon_light[0]->setVisible(visible);
2255 prop.pp_beacon_scene_node[0]->setVisible(visible);
2256 }
2257 else if (prop.pp_beacon_type == 'R' || prop.pp_beacon_type == 'L') // Avionic navigation lights (red/green)
2258 {
2259 Vector3 mposition = nodes[prop.pp_node_ref].AbsPosition + prop.pp_offset.x * (nodes[prop.pp_node_x].AbsPosition - nodes[prop.pp_node_ref].AbsPosition) + prop.pp_offset.y * (nodes[prop.pp_node_y].AbsPosition - nodes[prop.pp_node_ref].AbsPosition);
2260 //billboard
2261 Vector3 vdir = mposition - App::GetCameraManager()->GetCameraNode()->getPosition();
2262 float vlen = vdir.length();
2263 if (vlen > 100.0)
2264 {
2265 prop.pp_beacon_scene_node[0]->setVisible(false);
2266 return;
2267 }
2268 //normalize
2269 vdir = vdir / vlen;
2270 prop.pp_beacon_scene_node[0]->setPosition(mposition - vdir * 0.1);
2271 }
2272 else if (prop.pp_beacon_type == 'w') // Avionic navigation lights (white rotating beacon)
2273 {
2274 Vector3 mposition = nodes[prop.pp_node_ref].AbsPosition + prop.pp_offset.x * (nodes[prop.pp_node_x].AbsPosition - nodes[prop.pp_node_ref].AbsPosition) + prop.pp_offset.y * (nodes[prop.pp_node_y].AbsPosition - nodes[prop.pp_node_ref].AbsPosition);
2275 prop.pp_beacon_light[0]->setPosition(mposition);
2276 prop.pp_beacon_rot_angle[0] += dt * prop.pp_beacon_rot_rate[0];//rotate baby!
2277 //billboard
2278 Vector3 vdir = mposition - App::GetCameraManager()->GetCameraNode()->getPosition();
2279 float vlen = vdir.length();
2280 if (vlen > 100.0)
2281 {
2282 prop.pp_beacon_scene_node[0]->setVisible(false);
2283 return;
2284 }
2285 //normalize
2286 vdir = vdir / vlen;
2287 prop.pp_beacon_scene_node[0]->setPosition(mposition - vdir * 0.1);
2288 bool visible = false;
2289 if (prop.pp_beacon_rot_angle[0] > 1.0)
2290 {
2291 prop.pp_beacon_rot_angle[0] = 0.0;
2292 visible = true;
2293 }
2294 visible = visible && enableAll;
2295 prop.pp_beacon_light[0]->setVisible(visible);
2296 prop.pp_beacon_scene_node[0]->setVisible(visible);
2297 }
2298}
2299
2300void RoR::GfxActor::UpdateProps(float dt, bool is_player_actor)
2301{
2302 using namespace Ogre;
2303
2304 NodeSB* nodes = this->GetSimNodeBuffer();
2305
2306 // Update prop meshes
2307 for (Prop& prop: m_props)
2308 {
2309 if (prop.pp_scene_node == nullptr) // Wing beacons don't have scenenodes
2310 continue;
2311
2312 // Update visibility
2313 if (prop.pp_aero_propeller_blade || prop.pp_aero_propeller_spin)
2314 {
2315 const float SPINNER_THRESHOLD = 200.f; // TODO: magic! ~ only_a_ptr, 09/2018
2316 const bool show_spinner = m_simbuf.simbuf_aeroengines[prop.pp_aero_engine_idx].simbuf_ae_rpm > SPINNER_THRESHOLD;
2317 if (prop.pp_aero_propeller_blade)
2318 prop.pp_scene_node->setVisible(!show_spinner);
2319 else if (prop.pp_aero_propeller_spin)
2320 prop.pp_scene_node->setVisible(show_spinner);
2321 }
2322 else
2323 {
2324 const bool mo_visible = (prop.pp_camera_mode_active == CAMERA_MODE_ALWAYS_VISIBLE || prop.pp_camera_mode_active == m_simbuf.simbuf_cur_cinecam);
2325 prop.pp_mesh_obj->setVisible(mo_visible);
2326 if (!mo_visible)
2327 {
2328 continue; // No need to update hidden meshes
2329 }
2330 }
2331
2332 // Update position and orientation
2333 // -- quick ugly port from `Actor::updateProps()` --- ~ 06/2018
2334 Vector3 diffX = nodes[prop.pp_node_x].AbsPosition - nodes[prop.pp_node_ref].AbsPosition;
2335 Vector3 diffY = nodes[prop.pp_node_y].AbsPosition - nodes[prop.pp_node_ref].AbsPosition;
2336
2337 Vector3 normal = (diffY.crossProduct(diffX)).normalisedCopy();
2338
2339 Vector3 mposition = nodes[prop.pp_node_ref].AbsPosition + prop.pp_offset.x * diffX + prop.pp_offset.y * diffY;
2340 prop.pp_scene_node->setPosition(mposition + normal * prop.pp_offset.z);
2341
2342 Vector3 refx = diffX.normalisedCopy();
2343 Vector3 refy = refx.crossProduct(normal);
2344 Quaternion orientation = Quaternion(refx, normal, refy) * prop.pp_rot;
2345 prop.pp_scene_node->setOrientation(orientation);
2346
2347 if (prop.pp_wheel_scene_node) // special prop - steering wheel
2348 {
2349 Quaternion brot = Quaternion(Degree(-59.0), Vector3::UNIT_X);
2350 brot = brot * Quaternion(Degree(m_simbuf.simbuf_hydro_dir_state * prop.pp_wheel_rot_degree), Vector3::UNIT_Y);
2351 prop.pp_wheel_scene_node->setPosition(mposition + normal * prop.pp_offset.z + orientation * prop.pp_wheel_pos);
2352 prop.pp_wheel_scene_node->setOrientation(orientation * brot);
2353 }
2354 }
2355
2356 // Update beacon flares
2357 if (BITMASK_IS_1(m_simbuf.simbuf_lightmask, RoRnet::LIGHTMASK_BEACONS) != m_beaconlight_active)
2358 {
2359 m_beaconlight_active = (m_simbuf.simbuf_lightmask & RoRnet::LIGHTMASK_BEACONS);
2360 this->SetBeaconsEnabled(m_beaconlight_active);
2361 }
2362
2363 if ((App::gfx_flares_mode->getEnum<GfxFlaresMode>() != GfxFlaresMode::NONE)
2364 && m_beaconlight_active)
2365 {
2366 for (Prop& prop: m_props)
2367 {
2368 if (prop.pp_beacon_type != 0)
2369 {
2370 this->UpdateBeaconFlare(prop, dt, is_player_actor);
2371 }
2372 }
2373 }
2374}
2375
2377{
2378 for (Prop& prop: m_props)
2379 {
2380 prop.setPropMeshesVisible(visible);
2381 }
2382}
2383
2385{
2386 // For turbojets, this hides meshes (nozzle, abflame) and particles
2387 // For turbo/piston-props, this only hides particles, meshes are in props.
2388
2389 for (int i = 0; i < m_actor->ar_num_aeroengines; i++)
2390 {
2391 m_actor->ar_aeroengines[i]->setVisible(visible);
2392 }
2393}
2394
2395void RoR::GfxActor::SetBeaconsEnabled(bool beacon_light_is_active)
2396{
2398
2399 for (Prop& prop: m_props)
2400 {
2401 char beacon_type = prop.pp_beacon_type;
2402 if (beacon_type == 'b')
2403 {
2404 prop.pp_beacon_light[0]->setVisible(beacon_light_is_active && enableLight);
2405 prop.pp_beacon_scene_node[0]->setVisible(beacon_light_is_active);
2406 if (prop.pp_beacon_bbs[0] && beacon_light_is_active && !prop.pp_beacon_scene_node[0]->numAttachedObjects())
2407 {
2408 prop.pp_beacon_bbs[0]->setVisible(true);
2409 prop.pp_beacon_scene_node[0]->attachObject(prop.pp_beacon_bbs[0]);
2410 }
2411 else if (prop.pp_beacon_bbs[0] && !beacon_light_is_active)
2412 {
2413 prop.pp_beacon_scene_node[0]->detachAllObjects();
2414 prop.pp_beacon_bbs[0]->setVisible(false);
2415 }
2416 }
2417 else if (beacon_type == 'R' || beacon_type == 'L')
2418 {
2419 prop.pp_beacon_scene_node[0]->setVisible(beacon_light_is_active);
2420 if (prop.pp_beacon_bbs[0] && beacon_light_is_active && !prop.pp_beacon_scene_node[0]->numAttachedObjects())
2421 prop.pp_beacon_scene_node[0]->attachObject(prop.pp_beacon_bbs[0]);
2422 else if (prop.pp_beacon_bbs[0] && !beacon_light_is_active)
2423 prop.pp_beacon_scene_node[0]->detachAllObjects();
2424 }
2425 else if (beacon_type == 'p')
2426 {
2427 for (int k = 0; k < 4; k++)
2428 {
2429 prop.pp_beacon_light[k]->setVisible(beacon_light_is_active && enableLight);
2430 prop.pp_beacon_scene_node[k]->setVisible(beacon_light_is_active);
2431 if (prop.pp_beacon_bbs[k] && beacon_light_is_active && !prop.pp_beacon_scene_node[k]->numAttachedObjects())
2432 prop.pp_beacon_scene_node[k]->attachObject(prop.pp_beacon_bbs[k]);
2433 else if (prop.pp_beacon_bbs[k] && !beacon_light_is_active)
2434 prop.pp_beacon_scene_node[k]->detachAllObjects();
2435 }
2436 }
2437 else
2438 {
2439 for (int k = 0; k < 4; k++)
2440 {
2441 if (prop.pp_beacon_light[k])
2442 {
2443 prop.pp_beacon_light[k]->setVisible(beacon_light_is_active && enableLight);
2444 }
2445 if (prop.pp_beacon_scene_node[k])
2446 {
2447 prop.pp_beacon_scene_node[k]->setVisible(beacon_light_is_active);
2448
2449 if (prop.pp_beacon_bbs[k] && beacon_light_is_active && !prop.pp_beacon_scene_node[k]->numAttachedObjects())
2450 {
2451 prop.pp_beacon_scene_node[k]->attachObject(prop.pp_beacon_bbs[k]);
2452 }
2453 else if (prop.pp_beacon_bbs[k] && !beacon_light_is_active)
2454 {
2455 prop.pp_beacon_scene_node[k]->detachAllObjects();
2456 }
2457 }
2458 }
2459 }
2460 }
2461}
2462
2463// Returns a smoothened `cstate`
2464float RoR::GfxActor::UpdateSmoothShift(PropAnim& anim, float dt, float new_target_cstate)
2465{
2466 const float delta_cstate = new_target_cstate - anim.shifterTarget;
2467 if (delta_cstate != 0)
2468 {
2469 anim.shifterStep = delta_cstate;
2470 anim.shifterTarget = new_target_cstate;
2471 }
2472
2473 if (anim.shifterSmooth != anim.shifterTarget)
2474 {
2475 const float cstate_step = (dt / m_simbuf.simbuf_shifter_anim_time) * anim.shifterStep;
2476 anim.shifterSmooth += cstate_step;
2477 // boundary check
2478 if ((anim.shifterStep < 0.f && anim.shifterSmooth < anim.shifterTarget) // undershot
2479 || (anim.shifterStep > 0.f) && anim.shifterSmooth > anim.shifterTarget) // overshot
2480 {
2481 anim.shifterSmooth = anim.shifterTarget;
2482 }
2483 }
2484
2485 return anim.shifterSmooth;
2486}
2487
2488void RoR::GfxActor::CalcPropAnimation(PropAnim& anim, float& cstate, int& div, float dt)
2489{
2490 // Note: This is not the same as 'animators' - those run on physics thread!
2491 // ------------------------------------------------------------------------
2492
2493 //boat rudder
2495 {
2496 size_t spi;
2497 float ctmp = 0.0f;
2498 for (spi = 0; spi < m_simbuf.simbuf_screwprops.size(); spi++)
2499 {
2500 ctmp += m_simbuf.simbuf_screwprops[spi].simbuf_sp_rudder;
2501 }
2502
2503 if (spi > 0)
2504 ctmp = ctmp / spi;
2505 cstate = ctmp;
2506 div++;
2507 }
2508
2509 //boat throttle
2511 {
2512 size_t spi;
2513 float ctmp = 0.0f;
2514 for (spi = 0; spi < m_simbuf.simbuf_screwprops.size(); spi++)
2515 {
2516 ctmp += m_simbuf.simbuf_screwprops[spi].simbuf_sp_throttle;
2517 }
2518
2519 if (spi > 0)
2520 ctmp = ctmp / spi;
2521 cstate = ctmp;
2522 div++;
2523 }
2524
2525 //differential lock status
2527 {
2528 if (m_actor->m_num_wheel_diffs > 0) // read-only attribute - safe to read from here
2529 {
2530 switch (m_simbuf.simbuf_diff_type)
2531 {
2533 cstate = 0.0f;
2534 break;
2536 cstate = 0.5f;
2537 break;
2539 cstate = 1.0f;
2540 break;
2541 default:;
2542 }
2543 }
2544 else // no axles/diffs avail, mode is split by default
2545 cstate = 0.5f;
2546
2547 div++;
2548 }
2549
2550 //heading
2552 {
2553 // rad2deg limitedrange -1 to +1
2554 cstate = (m_simbuf.simbuf_rotation * 57.29578f) / 360.0f;
2555 div++;
2556 }
2557
2558 //torque
2559 const bool has_engine = (m_actor->ar_engine!= nullptr);
2560 if (has_engine && anim.animFlags & PROP_ANIM_FLAG_TORQUE)
2561 {
2562 float torque = m_simbuf.simbuf_engine_crankfactor;
2563 if (torque <= 0.0f)
2564 torque = 0.0f;
2565 if (torque >= m_prop_anim_crankfactor_prev)
2566 cstate -= torque / 10.0f;
2567 else
2568 cstate = 0.0f;
2569
2570 if (cstate <= -1.0f)
2571 cstate = -1.0f;
2572 m_prop_anim_crankfactor_prev = torque;
2573 div++;
2574 }
2575
2576 if (has_engine && anim.animFlags & PROP_ANIM_FLAG_GEAR)
2577 {
2578 bool match = static_cast<int>(anim.animOpt3) == m_actor->ar_engine->getGear();
2579 cstate += static_cast<int>(match);
2580 div++;
2581 }
2582
2583 //shifterseq, to amimate sequentiell shifting
2584 if (has_engine && (anim.animFlags & PROP_ANIM_FLAG_SHIFTER) && anim.animOpt3 == SHIFTERSEQ)
2585 {
2586 float shifterseq_cstate = 0;
2587 // opt1 &opt2 = 0 this is a shifter
2588 if (!anim.lower_limit && !anim.upper_limit)
2589 {
2590 int shifter = m_simbuf.simbuf_gear;
2591 if (shifter > m_prop_anim_prev_gear)
2592 {
2593 shifterseq_cstate = 1.0f;
2594 m_prop_anim_shift_timer = 0.2f;
2595 }
2596 if (shifter < m_prop_anim_prev_gear)
2597 {
2598 shifterseq_cstate = -1.0f;
2599 m_prop_anim_shift_timer = -0.2f;
2600 }
2601 m_prop_anim_prev_gear = shifter;
2602
2603 if (m_prop_anim_shift_timer > 0.0f)
2604 {
2605 shifterseq_cstate = 1.0f;
2606 m_prop_anim_shift_timer -= dt;
2607 if (m_prop_anim_shift_timer < 0.0f)
2608 m_prop_anim_shift_timer = 0.0f;
2609 }
2610 if (m_prop_anim_shift_timer < 0.0f)
2611 {
2612 shifterseq_cstate = -1.0f;
2613 m_prop_anim_shift_timer += dt;
2614 if (m_prop_anim_shift_timer > 0.0f)
2615 m_prop_anim_shift_timer = 0.0f;
2616 }
2617 }
2618 else
2619 {
2620 // check if anim.lower_limit is a valid to get commandvalue, then get commandvalue
2621 if (anim.lower_limit >= 1.0f && anim.lower_limit <= 48.0)
2622 if (m_simbuf.simbuf_commandkey[int(anim.lower_limit)].simbuf_cmd_value > 0)
2623 shifterseq_cstate = 1.0f;
2624 // check if anim.upper_limit is a valid to get commandvalue, then get commandvalue
2625 if (anim.upper_limit >= 1.0f && anim.upper_limit <= 48.0)
2626 if (m_simbuf.simbuf_commandkey[int(anim.upper_limit)].simbuf_cmd_value > 0)
2627 shifterseq_cstate = -1.0f;
2628 }
2629
2630
2631 cstate += UpdateSmoothShift(anim, dt, shifterseq_cstate);
2632 div++;
2633 }
2634
2635 //shifterman1, left/right
2636 if (has_engine && (anim.animFlags & PROP_ANIM_FLAG_SHIFTER) && anim.animOpt3 == SHIFTERMAN1)
2637 {
2638 float shifterman1_cstate = 0.f;
2639 int shifter = m_simbuf.simbuf_gear;
2640 if (!shifter)
2641 {
2642 shifterman1_cstate = -0.5f;
2643 }
2644 else if (shifter < 0)
2645 {
2646 shifterman1_cstate = 1.0f;
2647 }
2648 else
2649 {
2650 shifterman1_cstate = -int((shifter - 1.0) / 2.0);
2651 }
2652
2653 cstate += UpdateSmoothShift(anim, dt, shifterman1_cstate);
2654 div++;
2655 }
2656
2657 //shifterman2, up/down
2658 if (has_engine && (anim.animFlags & PROP_ANIM_FLAG_SHIFTER) && anim.animOpt3 == SHIFTERMAN2)
2659 {
2660 float shifterman2_cstate = 0.f;
2661 int shifter = m_simbuf.simbuf_gear;
2662 shifterman2_cstate = 0.5f;
2663 if (shifter < 0)
2664 {
2665 shifterman2_cstate = 1.0f;
2666 }
2667 if (shifter > 0)
2668 {
2669 shifterman2_cstate = shifter % 2;
2670 }
2671
2672 cstate += UpdateSmoothShift(anim, dt, shifterman2_cstate);
2673 div++;
2674 }
2675
2676 //shifterlinear, to amimate cockpit gearselect gauge and autotransmission stick
2677 if (has_engine && (anim.animFlags & PROP_ANIM_FLAG_SHIFTER) && anim.animOpt3 == SHIFTERLIN)
2678 {
2679 float shifterlin_cstate = 0.f;
2680 int shifter = m_simbuf.simbuf_gear;
2681 int numgears = m_simbuf.simbuf_num_gears;
2682 shifterlin_cstate -= (shifter + 2.0) / (numgears + 2.0);
2683
2684 cstate += UpdateSmoothShift(anim, dt, shifterlin_cstate);
2685 div++;
2686 }
2687
2688 //autoshifterlin, autotransmission stick with only R/N/D positions
2689 if (has_engine && (anim.animFlags & PROP_ANIM_FLAG_SHIFTER) && anim.animOpt3 == AUTOSHIFTERLIN)
2690 {
2691 float shifterlin_cstate = 0.f;
2692 int shifter = std::min(m_simbuf.simbuf_gear, 1); // Clamp forward gears to 1
2693 int numgears = 1; // Number of forward gears
2694 shifterlin_cstate -= (shifter + 2.0) / (numgears + 2.0);
2695
2696 cstate += UpdateSmoothShift(anim, dt, shifterlin_cstate);
2697 div++;
2698 }
2699
2700 //parking brake
2702 {
2703 float pbrake = static_cast<float>(m_simbuf.simbuf_parking_brake); // Bool --> float
2704 cstate -= pbrake;
2705 div++;
2706 }
2707
2708 //speedo ( scales with speedomax )
2710 {
2711 float speedo = m_simbuf.simbuf_wheel_speed / m_simbuf.simbuf_speedo_highest_kph;
2712 cstate -= speedo * 3.0f;
2713 div++;
2714 }
2715
2716 //engine tacho ( scales with maxrpm, default is 3500 )
2717 if (has_engine && anim.animFlags & PROP_ANIM_FLAG_TACHO)
2718 {
2719 float tacho = m_simbuf.simbuf_engine_rpm / m_simbuf.simbuf_engine_max_rpm;
2720 cstate -= tacho;
2721 div++;
2722 }
2723
2724 //turbo
2725 if (has_engine && anim.animFlags & PROP_ANIM_FLAG_TURBO)
2726 {
2727 float turbo = m_simbuf.simbuf_engine_turbo_psi * 3.34;
2728 cstate -= turbo / 67.0f;
2729 div++;
2730 }
2731
2732 //brake
2734 {
2735 float brakes = m_simbuf.simbuf_brake;
2736 cstate -= brakes;
2737 div++;
2738 }
2739
2740 //accelerator
2741 if (has_engine && anim.animFlags & PROP_ANIM_FLAG_ACCEL)
2742 {
2743 float accel = m_simbuf.simbuf_engine_accel;
2744 cstate -= accel + 0.06f;
2745 //( small correction, get acc is nver smaller then 0.06.
2746 div++;
2747 }
2748
2749 //clutch
2750 if (has_engine && anim.animFlags & PROP_ANIM_FLAG_CLUTCH)
2751 {
2752 float clutch = m_simbuf.simbuf_clutch;
2753 cstate -= fabs(1.0f - clutch);
2754 div++;
2755 }
2756
2757 //turn indicator stalk
2759 {
2760 float signal = 0.0f;
2761 if (m_simbuf.simbuf_lightmask & RoRnet::LIGHTMASK_BLINK_LEFT)
2762 signal = -1.0f;
2763 if (m_simbuf.simbuf_lightmask & RoRnet::LIGHTMASK_BLINK_RIGHT)
2764 signal = 1.0f;
2765 cstate -= signal;
2766 div++;
2767 }
2768
2769 //ignition
2770 if (has_engine && anim.animFlags & PROP_ANIM_FLAG_IGNITION)
2771 {
2772 float ignition = m_simbuf.simbuf_engine_ignition;
2773 cstate += ignition;
2774 div++;
2775 }
2776
2777 //aeroengines rpm + throttle + torque ( turboprop ) + pitch ( turboprop ) + status + fire
2778 // `anim.animOpt3` is aeroengine number (starting from 1)
2779 if (anim.animOpt3 > 0.f && anim.animOpt3 <= float(m_simbuf.simbuf_aeroengines.size()))
2780 {
2781 const int aenum = int(anim.animOpt3 - 1.f);
2782 if (anim.animFlags & PROP_ANIM_FLAG_RPM)
2783 {
2784 float angle;
2785 float pcent = m_simbuf.simbuf_aeroengines[aenum].simbuf_ae_rpmpc;
2786 if (pcent < 60.0)
2787 angle = -5.0 + pcent * 1.9167;
2788 else if (pcent < 110.0)
2789 angle = 110.0 + (pcent - 60.0) * 4.075;
2790 else
2791 angle = 314.0;
2792 cstate -= angle / 314.0f;
2793 div++;
2794 }
2796 {
2797 float throttle = m_simbuf.simbuf_aeroengines[aenum].simbuf_ae_throttle;
2798 cstate -= throttle;
2799 div++;
2800 }
2801
2802 if (m_simbuf.simbuf_aeroengines[aenum].simbuf_ae_type == AeroEngineType::AE_XPROP)
2803 {
2805 {
2806 cstate = m_simbuf.simbuf_aeroengines[aenum].simbuf_tp_aetorque / 120.0f;
2807 div++;
2808 }
2809
2811 {
2812 cstate = m_simbuf.simbuf_aeroengines[aenum].simbuf_tp_aepitch / 120.0f;
2813 div++;
2814 }
2815 }
2816
2818 {
2819 if (!m_simbuf.simbuf_aeroengines[aenum].simbuf_ae_ignition)
2820 cstate = 0.0f;
2821 else
2822 cstate = 0.5f;
2823 if (m_simbuf.simbuf_aeroengines[aenum].simbuf_ae_failed)
2824 cstate = 1.0f;
2825 div++;
2826 }
2827 }
2828
2829 const Ogre::Vector3 node0_pos = this->GetSimNodeBuffer()[0].AbsPosition;
2830 const Ogre::Vector3 node0_velo = m_simbuf.simbuf_node0_velo;
2831
2832 //airspeed indicator
2834 {
2835 float ground_speed_kt = node0_velo.length() * 1.9438;
2836 float altitude = node0_pos.y;
2837
2838 float sea_level_pressure = 101325; //in Pa
2839
2840 float airpressure = sea_level_pressure * pow(1.0 - 0.0065 * altitude / 288.15, 5.24947); //in Pa
2841 float airdensity = airpressure * 0.0000120896;//1.225 at sea level
2842 float kt = ground_speed_kt * sqrt(airdensity / 1.225);
2843 cstate -= kt / 100.0f;
2844 div++;
2845 }
2846
2847 //vvi indicator
2848 if (anim.animFlags & PROP_ANIM_FLAG_VVI)
2849 {
2850 float vvi = node0_velo.y * 196.85;
2851 // limit vvi scale to +/- 6m/s
2852 cstate -= vvi / 6000.0f;
2853 if (cstate >= 1.0f)
2854 cstate = 1.0f;
2855 if (cstate <= -1.0f)
2856 cstate = -1.0f;
2857 div++;
2858 }
2859
2860 //altimeter
2862 {
2863 //altimeter indicator 1k oscillating
2864 if (anim.animOpt3 == 3.0f)
2865 {
2866 float altimeter = (node0_pos.y * 1.1811) / 360.0f;
2867 int alti_int = int(altimeter);
2868 float alti_mod = (altimeter - alti_int);
2869 cstate -= alti_mod;
2870 }
2871
2872 //altimeter indicator 10k oscillating
2873 if (anim.animOpt3 == 2.0f)
2874 {
2875 float alti = node0_pos.y * 1.1811 / 3600.0f;
2876 int alti_int = int(alti);
2877 float alti_mod = (alti - alti_int);
2878 cstate -= alti_mod;
2879 if (cstate <= -1.0f)
2880 cstate = -1.0f;
2881 }
2882
2883 //altimeter indicator 100k limited
2884 if (anim.animOpt3 == 1.0f)
2885 {
2886 float alti = node0_pos.y * 1.1811 / 36000.0f;
2887 cstate -= alti;
2888 if (cstate <= -1.0f)
2889 cstate = -1.0f;
2890 }
2891 div++;
2892 }
2893
2894 //AOA
2895 if (anim.animFlags & PROP_ANIM_FLAG_AOA)
2896 {
2897 float aoa = m_simbuf.simbuf_wing4_aoa / 25.f;
2898 if ((node0_velo.length() * 1.9438) < 10.0f)
2899 aoa = 0;
2900 cstate -= aoa;
2901 if (cstate <= -1.0f)
2902 cstate = -1.0f;
2903 if (cstate >= 1.0f)
2904 cstate = 1.0f;
2905 div++;
2906 }
2907
2908 Ogre::Vector3 cam_pos = this->GetSimNodeBuffer()[m_actor->ar_main_camera_node_pos ].AbsPosition;
2909 Ogre::Vector3 cam_roll = this->GetSimNodeBuffer()[m_actor->ar_main_camera_node_roll].AbsPosition;
2910 Ogre::Vector3 cam_dir = this->GetSimNodeBuffer()[m_actor->ar_main_camera_node_dir ].AbsPosition;
2911
2912 // roll
2913 if (anim.animFlags & PROP_ANIM_FLAG_ROLL)
2914 {
2915 Ogre::Vector3 rollv = (cam_pos - cam_roll).normalisedCopy();
2916 Ogre::Vector3 dirv = (cam_pos - cam_dir).normalisedCopy();
2917 Ogre::Vector3 upv = dirv.crossProduct(-rollv);
2918 float rollangle = asin(rollv.dotProduct(Ogre::Vector3::UNIT_Y));
2919 // rad to deg
2920 rollangle = Ogre::Math::RadiansToDegrees(rollangle);
2921 // flip to other side when upside down
2922 if (upv.y < 0)
2923 rollangle = 180.0f - rollangle;
2924 cstate = rollangle / 180.0f;
2925 // data output is -0.5 to 1.5, normalize to -1 to +1 without changing the zero position.
2926 // this is vital for the animator beams and does not effect the animated props
2927 if (cstate >= 1.0f)
2928 cstate = cstate - 2.0f;
2929 div++;
2930 }
2931
2932 // pitch
2934 {
2935 Ogre::Vector3 dirv = (cam_pos - cam_dir).normalisedCopy();
2936 float pitchangle = asin(dirv.dotProduct(Ogre::Vector3::UNIT_Y));
2937 // radian to degrees with a max cstate of +/- 1.0
2938 cstate = (Ogre::Math::RadiansToDegrees(pitchangle) / 90.0f);
2939 div++;
2940 }
2941
2942 // airbrake
2944 {
2945 float airbrake = static_cast<float>(m_simbuf.simbuf_airbrake_state);
2946 // cstate limited to -1.0f
2947 cstate -= airbrake / 5.0f;
2948 div++;
2949 }
2950
2951 //flaps
2952 if (anim.animFlags & PROP_ANIM_FLAG_FLAP)
2953 {
2954 float flaps = FLAP_ANGLES[m_simbuf.simbuf_aero_flap_state];
2955 // cstate limited to -1.0f
2956 cstate = flaps;
2957 div++;
2958 }
2959}
2960
2962{
2963 int prop_anim_key_index = 0;
2964
2965 for (Prop& prop: m_props)
2966 {
2967 int animnum = 0;
2968 float rx = 0.0f;
2969 float ry = 0.0f;
2970 float rz = 0.0f;
2971
2972 for (PropAnim& anim: prop.pp_animations)
2973 {
2974 float cstate = 0.0f;
2975 int div = 0.0f;
2976
2977 this->CalcPropAnimation(anim, cstate, div, dt);
2978
2979 // key triggered animations - state determined in simulation
2980 if (anim.animFlags & PROP_ANIM_FLAG_EVENT)
2981 {
2982 ROR_ASSERT(prop_anim_key_index < (int)m_simbuf.simbuf_prop_anim_keys.size());
2983 const bool anim_active = m_simbuf.simbuf_prop_anim_keys[prop_anim_key_index++].simbuf_anim_active;
2984 cstate += (float)anim_active;
2985 }
2986
2987 // dashboard animations - state determined in simulation
2988 if (anim.animFlags & PROP_ANIM_FLAG_DASHBOARD)
2989 {
2990 int link_id = (int)anim.animOpt3;
2991 ROR_ASSERT(link_id < m_actor->ar_dashboard->getInputCount());
2992 cstate += m_actor->ar_dashboard->getNumeric(link_id);
2993 }
2994
2995 //propanimation placed here to avoid interference with existing hydros(cstate) and permanent prop animation
2996 //land vehicle steering
2997 if (anim.animFlags & PROP_ANIM_FLAG_STEERING)
2998 cstate += m_simbuf.simbuf_hydro_dir_state;
2999 //aileron
3000 if (anim.animFlags & PROP_ANIM_FLAG_AILERONS)
3001 cstate += m_simbuf.simbuf_hydro_aileron_state;
3002 //elevator
3003 if (anim.animFlags & PROP_ANIM_FLAG_ELEVATORS)
3004 cstate += m_simbuf.simbuf_hydro_elevator_state;
3005 //rudder
3006 if (anim.animFlags & PROP_ANIM_FLAG_ARUDDER)
3007 cstate += m_simbuf.simbuf_hydro_aero_rudder_state;
3008 //permanent
3009 if (anim.animFlags & PROP_ANIM_FLAG_PERMANENT)
3010 cstate += 1.0f;
3011
3012 cstate *= anim.animratio;
3013
3014 // autoanimate noflip_bouncer
3015 if (anim.animOpt5)
3016 cstate *= (anim.animOpt5);
3017
3018 //rotate prop
3019 if ((anim.animMode & PROP_ANIM_MODE_ROTA_X) || (anim.animMode & PROP_ANIM_MODE_ROTA_Y) || (anim.animMode & PROP_ANIM_MODE_ROTA_Z))
3020 {
3021 float limiter = 0.0f;
3022 // This code was formerly executed within a fixed timestep of 0.5ms and finetuned accordingly.
3023 // This is now taken into account by factoring in the respective fraction of the variable timestep.
3024 float const dt_frac = dt * 2000.f;
3025 if (anim.animMode & PROP_ANIM_MODE_AUTOANIMATE)
3026 {
3027 if (anim.animMode & PROP_ANIM_MODE_ROTA_X)
3028 {
3029 prop.pp_rota.x += cstate * dt_frac;
3030 limiter = prop.pp_rota.x;
3031 }
3032 if (anim.animMode & PROP_ANIM_MODE_ROTA_Y)
3033 {
3034 prop.pp_rota.y += cstate * dt_frac;
3035 limiter = prop.pp_rota.y;
3036 }
3037 if (anim.animMode & PROP_ANIM_MODE_ROTA_Z)
3038 {
3039 prop.pp_rota.z += cstate * dt_frac;
3040 limiter = prop.pp_rota.z;
3041 }
3042 }
3043 else
3044 {
3045 if (anim.animMode & PROP_ANIM_MODE_ROTA_X)
3046 rx += cstate;
3047 if (anim.animMode & PROP_ANIM_MODE_ROTA_Y)
3048 ry += cstate;
3049 if (anim.animMode & PROP_ANIM_MODE_ROTA_Z)
3050 rz += cstate;
3051 }
3052
3053 bool limiterchanged = false;
3054 // check if a positive custom limit is set to evaluate/calc flip back
3055
3056 if (limiter > anim.upper_limit)
3057 {
3058 if (anim.animMode & PROP_ANIM_MODE_NOFLIP)
3059 {
3060 limiter = anim.upper_limit; // stop at limit
3061 anim.animOpt5 *= -1.0f; // change cstate multiplier if bounce is set
3062 }
3063 else
3064 {
3065 limiter = anim.lower_limit; // flip to other side at limit
3066 }
3067 limiterchanged = true;
3068 }
3069
3070 if (limiter < anim.lower_limit)
3071 {
3072 if (anim.animMode & PROP_ANIM_MODE_NOFLIP)
3073 {
3074 limiter = anim.lower_limit; // stop at limit
3075 anim.animOpt5 *= -1.0f; // change cstate multiplier if active
3076 }
3077 else
3078 {
3079 limiter = anim.upper_limit; // flip to other side at limit
3080 }
3081 limiterchanged = true;
3082 }
3083
3084 if (limiterchanged)
3085 {
3086 if (anim.animMode & PROP_ANIM_MODE_ROTA_X)
3087 prop.pp_rota.x = limiter;
3088 if (anim.animMode & PROP_ANIM_MODE_ROTA_Y)
3089 prop.pp_rota.y = limiter;
3090 if (anim.animMode & PROP_ANIM_MODE_ROTA_Z)
3091 prop.pp_rota.z = limiter;
3092 }
3093 }
3094
3095 //offset prop
3096
3097 if ((anim.animMode & PROP_ANIM_MODE_OFFSET_X) || (anim.animMode & PROP_ANIM_MODE_OFFSET_Y) || (anim.animMode & PROP_ANIM_MODE_OFFSET_Z))
3098 {
3099 float offset = 0.0f;
3100 float autooffset = 0.0f;
3101
3102 if (anim.animMode & PROP_ANIM_MODE_OFFSET_X)
3103 offset = prop.pp_offset_orig.x;
3104 if (anim.animMode & PROP_ANIM_MODE_OFFSET_Y)
3105 offset = prop.pp_offset_orig.y;
3106 if (anim.animMode & PROP_ANIM_MODE_OFFSET_Z)
3107 offset = prop.pp_offset_orig.z;
3108
3109 if (anim.animMode & PROP_ANIM_MODE_AUTOANIMATE)
3110 {
3111 // This code was formerly executed within a fixed timestep of 0.5ms and finetuned accordingly.
3112 // This is now taken into account by factoring in the respective fraction of the variable timestep.
3113 float const dt_frac = dt * 2000.f;
3114 autooffset = offset + cstate * dt_frac;
3115
3116 if (autooffset > anim.upper_limit)
3117 {
3118 if (anim.animMode & PROP_ANIM_MODE_NOFLIP)
3119 {
3120 autooffset = anim.upper_limit; // stop at limit
3121 anim.animOpt5 *= -1.0f; // change cstate multiplier if active
3122 }
3123 else
3124 {
3125 autooffset = anim.lower_limit; // flip to other side at limit
3126 }
3127 }
3128
3129 if (autooffset < anim.lower_limit)
3130 {
3131 if (anim.animMode & PROP_ANIM_MODE_NOFLIP)
3132 {
3133 autooffset = anim.lower_limit; // stop at limit
3134 anim.animOpt5 *= -1.0f; // change cstate multiplier if active
3135 }
3136 else
3137 {
3138 autooffset = anim.upper_limit; // flip to other side at limit
3139 }
3140 }
3141 }
3142 offset += cstate;
3143
3144 if (anim.animMode & PROP_ANIM_MODE_OFFSET_X)
3145 {
3146 prop.pp_offset.x = offset;
3147 if (anim.animMode & PROP_ANIM_MODE_AUTOANIMATE)
3148 prop.pp_offset_orig.x = autooffset;
3149 }
3150 if (anim.animMode & PROP_ANIM_MODE_OFFSET_Y)
3151 {
3152 prop.pp_offset.y = offset;
3153 if (anim.animMode & PROP_ANIM_MODE_AUTOANIMATE)
3154 prop.pp_offset_orig.y = autooffset;
3155 }
3156 if (anim.animMode & PROP_ANIM_MODE_OFFSET_Z)
3157 {
3158 prop.pp_offset.z = offset;
3159 if (anim.animMode & PROP_ANIM_MODE_AUTOANIMATE)
3160 prop.pp_offset_orig.z = autooffset;
3161 }
3162 }
3163 animnum++;
3164 }
3165 //recalc the quaternions with final stacked rotation values ( rx, ry, rz )
3166 rx += prop.pp_rota.x;
3167 ry += prop.pp_rota.y;
3168 rz += prop.pp_rota.z;
3169
3170 prop.pp_rot = Ogre::Quaternion(Ogre::Degree(rz), Ogre::Vector3::UNIT_Z) *
3171 Ogre::Quaternion(Ogre::Degree(ry), Ogre::Vector3::UNIT_Y) *
3172 Ogre::Quaternion(Ogre::Degree(rx), Ogre::Vector3::UNIT_X);
3173 }
3174}
3175
3177{
3178 std::sort(m_flexbodies.begin(), m_flexbodies.end(), [](FlexBody* a, FlexBody* b) { return a->getVertexCount() > b->getVertexCount(); });
3179}
3180
3182{
3183 m_flexbody_tasks.clear();
3184
3185 for (FlexBody* fb: m_flexbodies)
3186 {
3187 // Update visibility (same logic as props)
3188 const bool visible = (fb->fb_camera_mode_active == CAMERA_MODE_ALWAYS_VISIBLE || fb->fb_camera_mode_active == m_simbuf.simbuf_cur_cinecam);
3189 fb->setVisible(visible);
3190
3191 // Update visible on background thread
3192 if (fb->isVisible())
3193 {
3194 auto func = std::function<void()>([fb]()
3195 {
3196 fb->computeFlexbody();
3197 });
3198 auto task_handle = App::GetThreadPool()->RunTask(func);
3199 m_flexbody_tasks.push_back(task_handle);
3200 }
3201 }
3202}
3203
3205{
3206 for (FlexBody* fb: m_flexbodies)
3207 {
3208 fb->reset();
3209 }
3210}
3211
3213{
3214 for (FlexBody* fb: m_flexbodies)
3215 {
3216 fb->setVisible(visible);
3217 }
3218}
3219
3221{
3222 for (auto& task: m_flexbody_tasks)
3223 {
3224 task->join();
3225 }
3226 for (FlexBody* fb: m_flexbodies)
3227 {
3228 if (fb->isVisible())
3229 {
3230 fb->updateFlexbodyVertexBuffers();
3231 }
3232 }
3233}
3234
3235// internal helper
3236bool ShouldEnableLightSource(FlareType type, bool is_player)
3237{
3238 switch (App::gfx_flares_mode->getEnum<GfxFlaresMode>())
3239 {
3241 return is_player && (type == FlareType::HEADLIGHT);
3243 return (type == FlareType::HEADLIGHT);
3245 return true;
3246 default:
3247 return false;
3248 }
3249}
3250
3251void RoR::GfxActor::UpdateFlares(float dt, bool is_player)
3252{
3253 // Flare states are determined in simulation, this function only applies them to OGRE objects
3254 // ------------------------------------------------------------------------------------------
3255
3256 NodeSB* nodes = this->GetSimNodeBuffer();
3257
3258 int num_flares = static_cast<int>(m_actor->ar_flares.size());
3259 for (int i=0; i<num_flares; ++i)
3260 {
3261 flare_t& flare = m_actor->ar_flares[i];
3262
3263 // Skip placeholder flares (removed via Tuning system)
3264 if (!flare.bbs)
3265 {
3266 continue;
3267 }
3268
3269 this->SetMaterialFlareOn(i, flare.intensity > 0.3);
3270
3271 // The billboard flare
3272 flare.snode->setVisible(flare.intensity > 0);
3273 if (flare.uses_inertia
3274 && flare.bbs->getMaterial() // Make sure material is assigned and has a texture unit.
3275 && flare.bbs->getMaterial()->getTechniques()[0]->getPasses()[0]->getTextureUnitStates().size() > 0)
3276 {
3277 // simulate incandescence by adjusting opacity
3278 // Reference: https://forums.ogre3d.org/viewtopic.php?p=226205&sid=c108bfca815d507cbebe1781651e5e67#p226205
3279 flare.bbs->getMaterial()->getTechniques()[0]->getPasses()[0]->getTextureUnitStates()[0]->setAlphaOperation(
3280 Ogre::LBX_BLEND_TEXTURE_ALPHA, Ogre::LBS_MANUAL, Ogre::LBS_TEXTURE, flare.intensity);
3281 }
3282
3283 // The light source
3284 if (flare.light)
3285 {
3286 flare.light->setVisible(flare.intensity > 0 && ShouldEnableLightSource(flare.fl_type, is_player));
3287 }
3288
3289 Ogre::Vector3 normal = (nodes[flare.nodey].AbsPosition - nodes[flare.noderef].AbsPosition).crossProduct(nodes[flare.nodex].AbsPosition - nodes[flare.noderef].AbsPosition);
3290 normal.normalise();
3291 Ogre::Vector3 mposition = nodes[flare.noderef].AbsPosition + flare.offsetx * (nodes[flare.nodex].AbsPosition - nodes[flare.noderef].AbsPosition) + flare.offsety * (nodes[flare.nodey].AbsPosition - nodes[flare.noderef].AbsPosition);
3292 Ogre::Vector3 vdir = mposition - App::GetCameraManager()->GetCameraNode()->getPosition();
3293 float vlen = vdir.length();
3294 // not visible from 500m distance
3295 if (vlen > 500.0)
3296 {
3297 flare.snode->setVisible(false);
3298 continue;
3299 }
3300 //normalize
3301 vdir = vdir / vlen;
3302 float amplitude = normal.dotProduct(vdir);
3303 flare.snode->setPosition(mposition - 0.1 * amplitude * normal * flare.offsetz);
3304 flare.snode->setDirection(normal);
3305 float fsize = flare.size;
3306 if (fsize < 0)
3307 {
3308 amplitude = 1;
3309 fsize *= -1;
3310 }
3311 if (flare.light)
3312 {
3313 flare.light->setPosition(mposition - 0.2 * amplitude * normal);
3314 // point the real light towards the ground a bit
3315 flare.light->setDirection(-normal - Ogre::Vector3(0, 0.2, 0));
3316 }
3317 if (flare.intensity > 0)
3318 {
3319 if (amplitude > 0)
3320 {
3321 flare.bbs->setDefaultDimensions(amplitude * fsize, amplitude * fsize);
3322 flare.snode->setVisible(true);
3323 }
3324 else
3325 {
3326 flare.snode->setVisible(false);
3327 }
3328 }
3329 }
3330}
3331
3333{
3334 // Cab mesh
3335 if (m_cab_scene_node != nullptr)
3336 {
3337 static_cast<Ogre::Entity*>(m_cab_scene_node->getAttachedObject(0))->setCastShadows(value);
3338 }
3339
3340 // Props
3341 for (Prop& prop: m_props)
3342 {
3343 if (prop.pp_mesh_obj != nullptr && prop.pp_mesh_obj->getEntity())
3344 prop.pp_mesh_obj->getEntity()->setCastShadows(value);
3345 if (prop.pp_wheel_mesh_obj != nullptr && prop.pp_wheel_mesh_obj->getEntity())
3346 prop.pp_wheel_mesh_obj->getEntity()->setCastShadows(value);
3347 }
3348
3349 // Wheels
3350 for (WheelGfx& wheel: m_wheels)
3351 {
3352 static_cast<Ogre::Entity*>(wheel.wx_scenenode->getAttachedObject(0))->setCastShadows(value);
3353 }
3354
3355 // Softbody beams
3356 for (BeamGfx& rod: m_gfx_beams)
3357 {
3358 static_cast<Ogre::Entity*>(rod.rod_scenenode->getAttachedObject(0))->setCastShadows(value);
3359 }
3360
3361 // Flexbody meshes
3362 for (FlexBody* fb: m_flexbodies)
3363 {
3364 fb->setFlexbodyCastShadow(value);
3365 }
3366}
3367
3368void RoR::GfxActor::RegisterCabMesh(Ogre::Entity* ent, Ogre::SceneNode* snode, FlexObj* flexobj)
3369{
3370 m_cab_mesh = flexobj;
3371 m_cab_entity = ent;
3372 m_cab_scene_node = snode;
3373}
3374
3376{
3377 if (m_cab_entity != nullptr)
3378 {
3379 m_cab_entity->setVisible(visible);
3380 }
3381 this->SetWheelsVisible(visible);
3382 this->SetPropsVisible(visible);
3383 this->SetFlexbodyVisible(visible);
3384 this->SetWingsVisible(visible);
3385 this->SetRodsVisible(visible);
3386 this->SetAeroEnginesVisible(visible);
3387}
3388
3390{
3391 for (int i = 0; i < m_actor->ar_num_wings; ++i)
3392 {
3393 m_actor->ar_wings[i].cnode->setVisible(visible);
3394 }
3395
3396 for (size_t i=0; i< m_actor->ar_airbrakes.size(); ++i)
3397 {
3398 m_gfx_airbrakes[i].abx_scenenode->setVisible(visible);
3399 }
3400}
3401
3403{
3404 for (int i = 0; i < m_actor->ar_num_wings; ++i)
3405 {
3406 wing_t& wing = m_actor->ar_wings[i];
3407 wing.cnode->setPosition(wing.fa->updateVerticesGfx(this));
3408 wing.fa->uploadVertices();
3409 }
3410}
3411
3413{
3414 int count = 0;
3415 for (const Prop& prop : m_props)
3416 {
3417 if (prop.pp_beacon_type != 0)
3418 {
3419 count++;
3420 }
3421 }
3422 return count;
3423}
3424
3425void RoR::GfxActor::SetNodeHot(NodeNum_t nodenum, bool value)
3426{
3427 for (NodeGfx& nfx : m_gfx_nodes)
3428 {
3429 if (nfx.nx_node_idx == nodenum)
3430 {
3431 nfx.nx_is_hot = value;
3432 }
3433 }
3434}
3435
3436void RoR::GfxActor::RemoveBeam(int beam_index)
3437{
3438 auto itor = m_gfx_beams.begin();
3439 auto endi = m_gfx_beams.end();
3440 while (itor != endi)
3441 {
3442 if (itor->rod_beam_index == beam_index)
3443 {
3444 // Destroy OGRE objects
3445 if (itor->rod_scenenode)
3446 {
3447 if (itor->rod_scenenode->getAttachedObject(0))
3448 {
3449 Ogre::Entity* ent = static_cast<Ogre::Entity*>(itor->rod_scenenode->getAttachedObject(0));
3450 if (ent)
3451 {
3452 ent->detachFromParent();
3453 App::GetGfxScene()->GetSceneManager()->destroyEntity(ent);
3454 }
3455 }
3456
3457 App::GetGfxScene()->GetSceneManager()->destroySceneNode(itor->rod_scenenode);
3458 itor->rod_scenenode = nullptr;
3459 }
3460
3461 // Destroy the beam visuals
3462 m_gfx_beams.erase(itor);
3463 return;
3464 }
3465 itor++;
3466 }
3467}
Vehicle spawning logic.
#define ROR_ASSERT(_EXPR)
Definition Application.h:40
Ogre::Vector3 fast_normalise(Ogre::Vector3 v)
Definition ApproxMath.h:151
#define BITMASK_IS_1(VAR, FLAGS)
Definition BitFlags.h:14
Game state manager and message-queue provider.
const ImU32 BEAM_BROKEN_COLOR(0xff4466dd)
const ImU32 NODE_COLOR(0xff44ddff)
bool ShouldEnableLightSource(FlareType type, bool is_player)
const float NODE_RADIUS(2.f)
const ImU32 NODE_IMMOVABLE_COLOR(0xff0033ff)
const float NODE_IMMOVABLE_RADIUS(2.8f)
const float BEAM_HYDRO_THICKNESS(1.4f)
const ImU32 BEAM_STRESS_TEXT_COLOR(0xff58bbfc)
std::string WhereFrom(GfxActor *gfx_actor, const std::string &doing_what)
Definition GfxActor.cpp:74
const ImU32 BEAM_HYDRO_COLOR(0xff55a3e0)
const float BEAM_BROKEN_THICKNESS(1.8f)
const ImU32 NODE_TEXT_COLOR(0xffcccccf)
const float BEAM_THICKNESS(1.2f)
const ImU32 BEAM_STRENGTH_TEXT_COLOR(0xffcfd0cc)
const ImU32 BEAM_COLOR(0xff556633)
const ImU32 NODE_MASS_TEXT_COLOR(0xff77bb66)
Manager for all visuals belonging to a single actor.
Handles controller inputs from player.
This creates a billboarding object that displays a text.
static const int MAX_COMMANDS
maximum number of commands per actor
#define SOUND_PLAY_ONCE(_ACTOR_, _TRIG_)
#define SOUND_MODULATE(_ACTOR_, _MOD_, _VALUE_)
ActorInstanceID_t ar_instance_id
Static attr; session-unique ID.
Definition Actor.h:464
std::string ar_filename
Attribute; filled at spawn.
Definition Actor.h:518
int ar_net_stream_id
Definition Actor.h:522
Processes a RigDef::Document (parsed from 'truck' file format) into a simulated gameplay object (Acto...
std::string ComposeName(const std::string &object, int number=-1)
Creates name containing actor ID token, i.e. "Object#1 (filename.truck [Instance ID 1])".
virtual bool getIgnition()=0
virtual bool isFailed()=0
virtual float getRPMpc()=0
virtual float getRPM()=0
virtual float getThrottle()=0
virtual AeroEngineType getType()=0
T getEnum() const
Definition CVar.h:99
Ogre::SceneNode * GetCameraNode()
Ogre::Vector3 updateVerticesGfx(RoR::GfxActor *gfx_actor)
Flexbody = A deformable mesh; updated on CPU every frame, then uploaded to video memory.
Definition FlexBody.h:44
A visual mesh, forming a chassis for softbody actor At most one instance is created per actor.
Definition FlexObj.h:60
bool IsGuiHidden() const
Definition GUIManager.h:159
const TerrainPtr & GetTerrain()
void SetNodeHot(NodeNum_t nodenum, bool value)
void UpdateVideoCameras(float dt)
Definition GfxActor.cpp:468
void SetCastShadows(bool value)
void SetRodsVisible(bool visible)
void ResetFlexbodies()
bool IsActorLive() const
Should the visuals be updated for this actor?
DustPool * m_particles_drip
Definition GfxActor.h:193
void SetCabLightsActive(bool state_on)
Definition GfxActor.cpp:419
void UpdateWheelVisuals()
void SetVideoCamState(VideoCamState state)
Definition GfxActor.cpp:440
void UpdateAeroEngines()
DustPool * m_particles_splash
Definition GfxActor.h:195
void SetWingsVisible(bool visible)
DustPool * m_particles_sparks
Definition GfxActor.h:197
void SetWheelsVisible(bool value)
void UpdateRods()
void RegisterCabMesh(Ogre::Entity *ent, Ogre::SceneNode *snode, FlexObj *flexobj)
bool IsDebugViewApplicable(DebugViewType dv)
float UpdateSmoothShift(PropAnim &anim, float dt, float new_target_cstate)
void UpdateCParticles()
void UpdateExhausts()
int GetActorState() const
DustPool * m_particles_clump
Definition GfxActor.h:198
void FinishFlexbodyTasks()
void UpdateSimDataBuffer()
Copies sim. data from Actor to GfxActor for later update.
void UpdatePropAnimations(float dt)
ActorPtr GetActor()
Definition GfxActor.cpp:334
void UpdateDebugView()
Definition GfxActor.cpp:734
void SortFlexbodies()
void UpdateAirbrakes()
void UpdateCabMesh()
void UpdateFlares(float dt, bool is_player)
void UpdateProps(float dt, bool is_player_actor)
void CalcPropAnimation(PropAnim &anim, float &cstate, int &div, float dt)
void RemoveBeam(int beam_index)
void SetAllMeshesVisible(bool value)
void UpdateBeaconFlare(Prop &prop, float dt, bool is_player_actor)
void SetAeroEnginesVisible(bool visible)
void SetFlexbodyVisible(bool visible)
void SetDebugView(DebugViewType dv)
DustPool * m_particles_dust
Definition GfxActor.h:194
void SetPropsVisible(bool visible)
void UpdateParticles(float dt)
Definition GfxActor.cpp:589
int GetActorId() const
void RegisterCabMaterial(Ogre::MaterialPtr mat, Ogre::MaterialPtr mat_trans)
Definition GfxActor.cpp:403
void ScaleActor(Ogre::Vector3 relpos, float ratio)
GfxActor(ActorPtr actor, ActorSpawner *spawner, std::string ogre_resource_group)
Definition GfxActor.cpp:61
void CalculateDriverPos(Ogre::Vector3 &out_pos, Ogre::Quaternion &out_rot)
DustPool * m_particles_ripple
Definition GfxActor.h:196
void FinishWheelUpdates()
void SetMaterialFlareOn(int flare_index, bool state_on)
Definition GfxActor.cpp:339
void CycleDebugViews()
void SetBeaconsEnabled(bool beacon_light_is_active)
void UpdateNetLabels(float dt)
int countBeaconProps() const
void UpdateWingMeshes()
void ToggleDebugView()
void UpdateFlexbodies()
const ActorPtr & getOwningActor()
Definition GfxActor.h:153
static Ogre::Quaternion SpecialGetRotationTo(const Ogre::Vector3 &src, const Ogre::Vector3 &dest)
Definition GfxScene.cpp:586
void DrawNetLabel(Ogre::Vector3 pos, float cam_dist, std::string const &nick, int colornum)
Definition GfxScene.cpp:374
void AdjustParticleSystemTimeFactor(Ogre::ParticleSystem *psys, GfxActor *gfx_actor=nullptr)
Definition GfxScene.cpp:435
Ogre::SceneManager * GetSceneManager()
Definition GfxScene.h:83
Wrapper for classic c-string (local buffer) Refresher: strlen() excludes '\0' terminator; strncat() A...
Definition Str.h:36
const char * ToCStr() const
Definition Str.h:46
SkyManager * getSkyManager()
Definition Terrain.cpp:521
Wavefield * getWater()
Definition Terrain.h:87
std::shared_ptr< Task > RunTask(const std::function< void()> &task_func)
Submit new asynchronous task to thread pool and return Task handle to allow for synchronization.
Definition ThreadPool.h:178
float getAfterburnThrust() const
Definition TurboJet.h:87
float getExhaustVelocity() const
Definition TurboJet.h:90
float getAfterburner()
Definition TurboJet.h:86
float max_torque
Definition TurboProp.h:45
float indicated_torque
Definition TurboProp.h:44
float GetStaticWaterHeight()
Returns static water level configured in 'terrn2'.
Definition Wavefield.cpp:75
< Keeps data close for faster access.
Definition Utils.h:83
Ogre::Vector3 Convert(Ogre::Vector3 world_pos)
Definition Utils.h:90
const PropAnimFlag_t PROP_ANIM_FLAG_ROLL
Definition GfxData.h:50
const PropAnimFlag_t PROP_ANIM_FLAG_AESTATUS
Definition GfxData.h:64
const PropAnimFlag_t PROP_ANIM_FLAG_ARUDDER
Definition GfxData.h:71
const PropAnimFlag_t PROP_ANIM_FLAG_TACHO
Definition GfxData.h:57
const PropAnimFlag_t PROP_ANIM_FLAG_ALTIMETER
Definition GfxData.h:46
const PropAnimFlag_t PROP_ANIM_FLAG_CLUTCH
Definition GfxData.h:56
const PropAnimFlag_t PROP_ANIM_FLAG_PITCH
Definition GfxData.h:51
const PropAnimFlag_t PROP_ANIM_FLAG_TORQUE
Definition GfxData.h:65
const PropAnimFlag_t PROP_ANIM_FLAG_ELEVATORS
Definition GfxData.h:75
const PropAnimMode_t PROP_ANIM_MODE_OFFSET_X
Definition GfxData.h:86
const PropAnimFlag_t PROP_ANIM_FLAG_AIRBRAKE
Definition GfxData.h:49
constexpr DebugViewType DEBUGVIEWTYPE_LAST
Definition GfxData.h:116
const PropAnimMode_t PROP_ANIM_MODE_NOFLIP
Definition GfxData.h:90
const PropAnimFlag_t PROP_ANIM_FLAG_SIGNALSTALK
Turn indicator stalk position (-1=left, 0=off, 1=right)
Definition GfxData.h:77
DebugViewType
Definition GfxData.h:103
const PropAnimFlag_t PROP_ANIM_FLAG_FLAP
Definition GfxData.h:48
const PropAnimFlag_t PROP_ANIM_FLAG_AILERONS
Definition GfxData.h:70
const PropAnimMode_t PROP_ANIM_MODE_ROTA_Z
Definition GfxData.h:85
VideoCamState
Definition GfxData.h:95
const PropAnimFlag_t PROP_ANIM_FLAG_DASHBOARD
Used with dashboard system inputs, see enum DashData in file DashBoardManager.h.
Definition GfxData.h:76
const PropAnimMode_t PROP_ANIM_MODE_OFFSET_Y
Definition GfxData.h:87
const PropAnimFlag_t PROP_ANIM_FLAG_BRUDDER
Definition GfxData.h:72
const PropAnimMode_t PROP_ANIM_MODE_AUTOANIMATE
Definition GfxData.h:89
const PropAnimFlag_t PROP_ANIM_FLAG_AIRSPEED
Definition GfxData.h:44
const PropAnimFlag_t PROP_ANIM_FLAG_TURBO
Definition GfxData.h:60
const PropAnimMode_t PROP_ANIM_MODE_ROTA_Y
Definition GfxData.h:84
DebugViewType NextDebugViewType(DebugViewType dv)
Definition GfxData.h:117
const PropAnimFlag_t PROP_ANIM_FLAG_AOA
Definition GfxData.h:47
const PropAnimFlag_t PROP_ANIM_FLAG_VVI
Definition GfxData.h:45
const PropAnimFlag_t PROP_ANIM_FLAG_BTHROTTLE
Definition GfxData.h:73
const PropAnimFlag_t PROP_ANIM_FLAG_THROTTLE
Definition GfxData.h:52
const PropAnimFlag_t PROP_ANIM_FLAG_PBRAKE
Definition GfxData.h:59
const PropAnimFlag_t PROP_ANIM_FLAG_IGNITION
Definition GfxData.h:79
constexpr DebugViewType DEBUGVIEWTYPE_FIRST
Definition GfxData.h:115
const PropAnimFlag_t PROP_ANIM_FLAG_PERMANENT
Definition GfxData.h:74
const PropAnimFlag_t PROP_ANIM_FLAG_RPM
Definition GfxData.h:53
const PropAnimFlag_t PROP_ANIM_FLAG_HEADING
Definition GfxData.h:66
const PropAnimFlag_t PROP_ANIM_FLAG_AETORQUE
Definition GfxData.h:62
const PropAnimFlag_t PROP_ANIM_FLAG_EVENT
Definition GfxData.h:69
const PropAnimFlag_t PROP_ANIM_FLAG_GEAR
'gearreverse' (animOpt3=-1), 'gearneutral' (animOpt3=0), 'gear#' (animOpt3=#)
Definition GfxData.h:78
const PropAnimFlag_t PROP_ANIM_FLAG_DIFFLOCK
Definition GfxData.h:67
const PropAnimFlag_t PROP_ANIM_FLAG_ACCEL
Definition GfxData.h:54
const PropAnimFlag_t PROP_ANIM_FLAG_STEERING
Definition GfxData.h:68
const PropAnimMode_t PROP_ANIM_MODE_OFFSET_Z
Definition GfxData.h:88
const PropAnimFlag_t PROP_ANIM_FLAG_BRAKE
Definition GfxData.h:55
const PropAnimFlag_t PROP_ANIM_FLAG_SHIFTER
'shifterman1, shifterman2, sequential, shifterlin, autoshifterlin'; animOpt3: see RoR::ShifterPropAni...
Definition GfxData.h:61
const PropAnimFlag_t PROP_ANIM_FLAG_AEPITCH
Definition GfxData.h:63
const PropAnimMode_t PROP_ANIM_MODE_ROTA_X
Definition GfxData.h:83
const PropAnimFlag_t PROP_ANIM_FLAG_SPEEDO
Definition GfxData.h:58
@ SHIFTERMAN1
Definition GfxData.h:125
@ SHIFTERSEQ
Definition GfxData.h:127
@ AUTOSHIFTERLIN
Definition GfxData.h:129
@ SHIFTERMAN2
Definition GfxData.h:126
@ SHIFTERLIN
Definition GfxData.h:128
static const float FLAP_ANGLES[6]
@ BEAM_HYDRO
Definition SimData.h:64
@ SHOCK3
shock3
Definition SimData.h:102
@ SHOCK2
shock2
Definition SimData.h:101
@ SHOCK1
either 'shock1' (with flag BEAM_HYDRO) or a wheel beam
Definition SimData.h:100
@ LOCAL_SLEEPING
sleeping (local) actor
@ NETWORKED_OK
not simulated (remote) actor
@ NETWORKED_HIDDEN
not simulated, not updated (remote)
@ SPLIT_DIFF
@ OPEN_DIFF
@ LOCKED_DIFF
CVar * mp_hide_own_net_label
CVar * mp_hide_net_labels
ThreadPool * GetThreadPool()
CameraManager * GetCameraManager()
CVar * diag_hide_wheels
CVar * diag_hide_nodes
GUIManager * GetGuiManager()
GameContext * GetGameContext()
GfxScene * GetGfxScene()
CVar * app_state
CVar * mp_state
CVar * diag_hide_wheel_info
CVar * gfx_flares_mode
CVar * diag_hide_broken_beams
CVar * diag_hide_beam_stress
GfxFlaresMode
@ CURR_VEHICLE_HEAD_ONLY
Only current vehicle, main lights.
@ ALL_VEHICLES_HEAD_ONLY
All vehicles, main lights.
@ NONE
None (fastest)
@ NO_LIGHTSOURCES
No light sources.
@ ALL_VEHICLES_ALL_LIGHTS
All vehicles, all lights.
void HandleGenericException(const std::string &from, BitMask_t flags)
@ VCAM_ROLE_MIRROR
Flips the video output and when not in driver cam, acts like a natural mirror, not a screen.
@ VCAM_ROLE_TRACKING_MIRROR_NOFLIP
A MIRROR_NOFLIP(2) with tracking node set.
@ VCAM_ROLE_TRACKING_MIRROR
A MIRROR(1) with tracking node set.
@ VCAM_ROLE_MIRROR_PROP_LEFT
The classic 'special prop/rear view mirror'.
@ VCAM_ROLE_TRACKING_VIDEOCAM
@ VCAM_ROLE_MIRROR_PROP_RIGHT
The classic 'special prop/rear view mirror'.
@ VCAM_ROLE_MIRROR_NOFLIP
Same as VCAM_ROLE_MIRROR, but without flipping the texture horizontally (expects texcoords to be alre...
@ VCAM_ROLE_VIDEOCAM
ImDrawList * GetImDummyFullscreenWindow(const std::string &name="RoR_TransparentFullscreenWindow")
Definition GUIUtils.cpp:394
static CameraMode_t CAMERA_MODE_ALWAYS_VISIBLE
uint16_t NodeNum_t
Node position within Actor::ar_nodes; use RoR::NODENUM_INVALID as empty value.
Ogre::Real Round(Ogre::Real value, unsigned short ndigits=0)
Definition Utils.cpp:101
@ HANDLEGENERICEXCEPTION_LOGFILE
@ LIGHTMASK_BEACONS
beacons on
Definition RoRnet.h:122
@ LIGHTMASK_BLINK_RIGHT
right blinker on
Definition RoRnet.h:124
@ LIGHTMASK_BLINK_LEFT
left blinker on
Definition RoRnet.h:123
float simbuf_tj_ab_thrust
Definition SimBuffers.h:98
bool simbuf_ae_ignition
Turbojet.
Definition SimBuffers.h:100
float simbuf_tp_aetorque
Turboprop torque, used by animation "aetorque".
Definition SimBuffers.h:96
float simbuf_ae_throttle
Definition SimBuffers.h:95
float simbuf_tj_exhaust_velo
Turbojet afterburner.
Definition SimBuffers.h:99
float simbuf_tp_aepitch
Turboprop pitch, used by animation "aepitch".
Definition SimBuffers.h:97
AeroEngineType simbuf_ae_type
Definition SimBuffers.h:92
Ogre::Vector3 abx_offset
Definition GfxData.h:320
Ogre::SceneNode * abx_scenenode
Definition GfxData.h:318
Visuals of softbody beam (beam_t struct); Partially updated along with SimBuffer.
Definition GfxData.h:262
< Submerged cab triangle
Definition Buoyance.h:44
Gfx attributes/state of a softbody node.
Definition GfxData.h:238
Ogre::Vector3 AbsPosition
Definition SimBuffers.h:69
float upper_limit
The upper limit for the animation.
Definition GfxData.h:147
float shifterTarget
Definition GfxData.h:152
float shifterSmooth
Definition GfxData.h:150
float shifterStep
Definition GfxData.h:151
float lower_limit
The lower limit for the animation.
Definition GfxData.h:146
PropAnimFlag_t animFlags
Definition GfxData.h:135
float animOpt3
MULTIPURPOSE.
Definition GfxData.h:144
A mesh attached to vehicle frame via 3 nodes.
Definition GfxData.h:157
float pp_beacon_rot_angle[4]
Radians.
Definition GfxData.h:189
Ogre::Quaternion pp_rot
Definition GfxData.h:165
Ogre::SceneNode * pp_beacon_scene_node[4]
Definition GfxData.h:186
NodeNum_t pp_node_ref
Definition GfxData.h:159
NodeNum_t pp_node_y
Definition GfxData.h:161
Ogre::SceneNode * pp_scene_node
The pivot scene node (parented to root-node).
Definition GfxData.h:166
Ogre::Vector3 pp_offset
Definition GfxData.h:162
NodeNum_t pp_node_x
Definition GfxData.h:160
Ogre::BillboardSet * pp_beacon_bbs[4]
Definition GfxData.h:185
char pp_beacon_type
Special prop: beacon {0 = none, 'b' = user-specified, 'r' = red, 'p' = police lightbar,...
Definition GfxData.h:184
Ogre::Light * pp_beacon_light[4]
Definition GfxData.h:187
float pp_beacon_rot_rate[4]
Radians per second.
Definition GfxData.h:188
An Ogre::Camera mounted on the actor and rendering into either in-scene texture or external window.
Definition GfxData.h:216
Ogre::TexturePtr vcam_render_tex
Definition GfxData.h:230
Ogre::RenderTexture * vcam_render_target
Definition GfxData.h:229
Ogre::Camera * vcam_ogre_camera
Definition GfxData.h:228
Simulation: An edge in the softbody structure.
Definition SimData.h:305
node_t * p1
Definition SimData.h:309
bool bm_broken
Definition SimData.h:326
ActorPtr bm_locked_actor
in case p2 is on another actor
Definition SimData.h:324
float longbound
Definition SimData.h:329
bool bm_inter_actor
in case p2 is on another actor
Definition SimData.h:323
SpecialBeam bounded
Definition SimData.h:321
float L
length
Definition SimData.h:313
bool bm_disabled
Definition SimData.h:325
node_t * p2
Definition SimData.h:310
float intensity
Definition SimData.h:615
Ogre::Light * light
Definition SimData.h:607
float offsetx
Definition SimData.h:602
NodeNum_t noderef
Definition SimData.h:599
float offsety
Definition SimData.h:603
Ogre::BillboardSet * bbs
This remains nullptr if removed via addonpart_unwanted_flare or Tuning UI.
Definition SimData.h:606
NodeNum_t nodex
Definition SimData.h:600
float size
Definition SimData.h:614
Ogre::SceneNode * snode
Definition SimData.h:605
float offsetz
Definition SimData.h:604
NodeNum_t nodey
Definition SimData.h:601
bool uses_inertia
Only 'flares3'.
Definition SimData.h:616
FlareType fl_type
Definition SimData.h:608
Ogre::ColourValue fx_colour
Definition SimData.h:726
Physics: A vertex in the softbody structure.
Definition SimData.h:260
Ogre::Vector3 AbsPosition
absolute position in the world (shaky)
Definition SimData.h:267
bool nd_has_ground_contact
Physics state.
Definition SimData.h:287
Ogre::Real nd_avg_collision_slip
Physics state; average slip velocity across the last few physics frames.
Definition SimData.h:297
Ogre::Vector3 Velocity
Definition SimData.h:268
bool nd_rim_node
Attr; This node is part of a rim (only wheel types with separate rim nodes)
Definition SimData.h:283
bool nd_under_water
State; GFX hint.
Definition SimData.h:293
Ogre::Vector3 RelPosition
relative to the local physics origin (one origin per actor) (shaky)
Definition SimData.h:266
Ogre::Vector3 nd_last_collision_slip
Physics state; last collision slip vector.
Definition SimData.h:298
ground_model_t * nd_last_collision_gm
Physics state; last collision 'ground model' (surface definition)
Definition SimData.h:300
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
NodeNum_t pos
This node's index in Actor::ar_nodes array.
Definition SimData.h:277
NodeNum_t axis2
Definition SimData.h:587
NodeNum_t axis1
rot axis
Definition SimData.h:586
NodeNum_t nodes2[4]
Definition SimData.h:585
NodeNum_t nodes1[4]
Definition SimData.h:584
Ogre::Vector3 debug_vel
Definition SimData.h:441
node_t * wh_axis_node_0
Definition SimData.h:408
node_t * wh_arm_node
Definition SimData.h:406
node_t * wh_axis_node_1
Definition SimData.h:409
Ogre::Vector3 debug_force
Definition SimData.h:443
node_t * wh_near_attach_node
Definition SimData.h:407
Ogre::Real wh_radius
Definition SimData.h:411
Ogre::Vector3 debug_scaled_cforce
Definition SimData.h:444
Ogre::Vector3 debug_slip
Definition SimData.h:442
Ogre::SceneNode * cnode
Definition SimData.h:522
FlexAirfoil * fa
Definition SimData.h:521