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