1 // =============================================================================
2 // PROJECT CHRONO - http://projectchrono.org
3 //
4 // Copyright (c) 2014 projectchrono.org
5 // All rights reserved.
6 //
7 // Use of this source code is governed by a BSD-style license that can be found
8 // in the LICENSE file at the top level of the distribution and at
9 // http://projectchrono.org/license-chrono.txt.
10 //
11 // =============================================================================
12 // Authors: Radu Serban
13 // =============================================================================
14 //
15 // Irrlicht-based visualization wrapper for vehicles.  This class is a wrapper
16 // around a ChIrrApp object and provides the following functionality:
17 //   - rendering of the entire Irrlicht scene
18 //   - implements a custom chase-camera (which can be controlled with keyboard)
19 //   - optional rendering of links, springs, stats, etc.
20 //
21 // =============================================================================
22 
23 #include <algorithm>
24 
25 #include "chrono_vehicle/ChWorldFrame.h"
26 #include "chrono_vehicle/utils/ChVehicleIrrApp.h"
27 
28 using namespace irr;
29 
30 namespace chrono {
31 namespace vehicle {
32 
33 // -----------------------------------------------------------------------------
34 // Implementation of the custom Irrlicht event receiver for camera control
35 // -----------------------------------------------------------------------------
36 class ChCameraEventReceiver : public irr::IEventReceiver {
37   public:
38     // Construct a custom event receiver.
ChCameraEventReceiver(ChVehicleIrrApp * app)39     ChCameraEventReceiver(ChVehicleIrrApp* app) : m_app(app) {}
40 
41     // Implementation of the event processing method.
42     // This function interprets keyboard inputs for controlling the chase camera in
43     // the associated vehicle Irrlicht application.
44     virtual bool OnEvent(const irr::SEvent& event);
45 
46   private:
47     ChVehicleIrrApp* m_app;  // pointer to the associated vehicle Irrlicht app
48 };
49 
OnEvent(const SEvent & event)50 bool ChCameraEventReceiver::OnEvent(const SEvent& event) {
51     // Only interpret keyboard inputs.
52     if (event.EventType != EET_KEY_INPUT_EVENT)
53         return false;
54 
55     if (event.KeyInput.PressedDown) {
56         switch (event.KeyInput.Key) {
57             case KEY_DOWN:
58                 m_app->m_camera.Zoom(1);
59                 return true;
60             case KEY_UP:
61                 m_app->m_camera.Zoom(-1);
62                 return true;
63             case KEY_LEFT:
64                 m_app->m_camera.Turn(1);
65                 return true;
66             case KEY_RIGHT:
67                 m_app->m_camera.Turn(-1);
68                 return true;
69             case KEY_NEXT:
70                 m_app->m_camera.Raise(1);
71                 return true;
72             case KEY_PRIOR:
73                 m_app->m_camera.Raise(-1);
74                 return true;
75             default:
76                 break;
77         }
78     } else {
79         switch (event.KeyInput.Key) {
80             case KEY_KEY_1:
81                 m_app->m_camera.SetState(utils::ChChaseCamera::Chase);
82                 return true;
83             case KEY_KEY_2:
84                 m_app->m_camera.SetState(utils::ChChaseCamera::Follow);
85                 return true;
86             case KEY_KEY_3:
87                 m_app->m_camera.SetState(utils::ChChaseCamera::Track);
88                 return true;
89             case KEY_KEY_4:
90                 m_app->m_camera.SetState(utils::ChChaseCamera::Inside);
91                 return true;
92             case KEY_KEY_5:
93                 m_app->m_camera.SetState(utils::ChChaseCamera::Free);
94                 return true;
95             case KEY_KEY_V:
96                 m_app->m_vehicle->LogConstraintViolations();
97                 return true;
98             default:
99                 break;
100         }
101     }
102 
103     return false;
104 }
105 
106 // -----------------------------------------------------------------------------
107 // Construct a vehicle Irrlicht application.
108 // -----------------------------------------------------------------------------
ChVehicleIrrApp(ChVehicle * vehicle,const std::wstring & title,const irr::core::dimension2d<irr::u32> & dims,irr::ELOG_LEVEL log_level)109 ChVehicleIrrApp::ChVehicleIrrApp(ChVehicle* vehicle,
110                                  const std::wstring& title,
111                                  const irr::core::dimension2d<irr::u32>& dims,
112                                  irr::ELOG_LEVEL log_level)
113     : ChIrrApp(vehicle->GetSystem(), title, dims, irrlicht::VerticalDir::Z, false, false, true, irr::video::EDT_OPENGL, log_level),
114       m_vehicle(vehicle),
115       m_camera(vehicle->GetChassisBody()),
116       m_stepsize(1e-3),
117       m_renderStats(true),
118       m_HUD_x(700),
119       m_HUD_y(20),
120       m_steering(0),
121       m_throttle(0),
122       m_braking(0) {
123     // Initialize the chase camera with default values.
124     m_camera.Initialize(ChVector<>(0, 0, 1), vehicle->GetChassis()->GetLocalDriverCoordsys(), 6.0, 0.5,
125                         ChWorldFrame::Vertical(), ChWorldFrame::Forward());
126     ChVector<> cam_pos = m_camera.GetCameraPos();
127     ChVector<> cam_target = m_camera.GetTargetPos();
128 
129     // Create the event receiver for controlling the chase camera.
130     m_camera_control = new ChCameraEventReceiver(this);
131     SetUserEventReceiver(m_camera_control);
132 
133     // Create and initialize the Irrlicht camera
134     scene::ICameraSceneNode* camera = GetSceneManager()->addCameraSceneNode(
135         GetSceneManager()->getRootSceneNode(), core::vector3df(0, 0, 0), core::vector3df(0, 0, 0));
136 
137     camera->setUpVector(core::vector3dfCH(ChWorldFrame::Vertical()));
138     camera->setPosition(core::vector3dfCH(cam_pos));
139     camera->setTarget(core::vector3dfCH(cam_target));
140 
141 #ifdef CHRONO_IRRKLANG
142     m_sound_engine = 0;
143     m_car_sound = 0;
144 #endif
145 }
146 
~ChVehicleIrrApp()147 ChVehicleIrrApp::~ChVehicleIrrApp() {
148     delete m_camera_control;
149 }
150 
151 // -----------------------------------------------------------------------------
152 // Turn on/off Irrklang sound generation.
153 // Note that this has an effect only if Irrklang support was enabled at
154 // configuration.
155 // -----------------------------------------------------------------------------
EnableSound(bool sound)156 void ChVehicleIrrApp::EnableSound(bool sound) {
157 #ifdef CHRONO_IRRKLANG
158     if (sound) {
159         // Start the sound engine with default parameters
160         m_sound_engine = irrklang::createIrrKlangDevice();
161 
162         // To play a sound, call play2D(). The second parameter tells the engine to
163         // play it looped.
164         if (m_sound_engine) {
165             m_car_sound = m_sound_engine->play2D(GetChronoDataFile("vehicle/sounds/carsound.ogg").c_str(), true, false, true);
166             m_car_sound->setIsPaused(true);
167         } else
168             GetLog() << "Cannot start sound engine Irrklang \n";
169     } else {
170         m_sound_engine = 0;
171         m_car_sound = 0;
172     }
173 #endif
174 }
175 
176 // -----------------------------------------------------------------------------
177 // Create a skybox that has Z pointing up.
178 // Note that the default ChIrrApp::AddTypicalSky() uses Y up.
179 // -----------------------------------------------------------------------------
SetSkyBox()180 void ChVehicleIrrApp::SetSkyBox() {
181     std::string mtexturedir = GetChronoDataFile("skybox/");
182     std::string str_lf = mtexturedir + "sky_lf.jpg";
183     std::string str_up = mtexturedir + "sky_up.jpg";
184     std::string str_dn = mtexturedir + "sky_dn.jpg";
185     irr::video::ITexture* map_skybox_side = GetVideoDriver()->getTexture(str_lf.c_str());
186     irr::scene::ISceneNode* mbox = GetSceneManager()->addSkyBoxSceneNode(
187         GetVideoDriver()->getTexture(str_up.c_str()), GetVideoDriver()->getTexture(str_dn.c_str()), map_skybox_side,
188         map_skybox_side, map_skybox_side, map_skybox_side);
189     ChMatrix33<> A = ChWorldFrame::Rotation() * ChMatrix33<>(Q_from_AngX(-CH_C_PI_2));
190     auto angles = CH_C_RAD_TO_DEG * A.Get_A_Rxyz();
191     mbox->setRotation(irr::core::vector3dfCH(angles));
192 }
193 
194 // -----------------------------------------------------------------------------
195 // Set parameters for the underlying chase camera.
196 // -----------------------------------------------------------------------------
SetChaseCamera(const ChVector<> & ptOnChassis,double chaseDist,double chaseHeight)197 void ChVehicleIrrApp::SetChaseCamera(const ChVector<>& ptOnChassis, double chaseDist, double chaseHeight) {
198     m_camera.Initialize(ptOnChassis, m_vehicle->GetChassis()->GetLocalDriverCoordsys(), chaseDist, chaseHeight,
199                         ChWorldFrame::Vertical(), ChWorldFrame::Forward());
200     ////ChVector<> cam_pos = m_camera.GetCameraPos();
201     ////ChVector<> cam_target = m_camera.GetTargetPos();
202 }
203 
204 // -----------------------------------------------------------------------------
205 // -----------------------------------------------------------------------------
Synchronize(const std::string & msg,const ChDriver::Inputs & driver_inputs)206 void ChVehicleIrrApp::Synchronize(const std::string& msg, const ChDriver::Inputs& driver_inputs) {
207     m_driver_msg = msg;
208     m_steering = driver_inputs.m_steering;
209     m_throttle = driver_inputs.m_throttle;
210     m_braking = driver_inputs.m_braking;
211 }
212 
213 // -----------------------------------------------------------------------------
214 // Advance the dynamics of the chase camera.
215 // The integration of the underlying ODEs is performed using as many steps as
216 // needed to advance by the specified duration.
217 // -----------------------------------------------------------------------------
Advance(double step)218 void ChVehicleIrrApp::Advance(double step) {
219     // Update the ChChaseCamera: take as many integration steps as needed to
220     // exactly reach the value 'step'
221     double t = 0;
222     while (t < step) {
223         double h = std::min<>(m_stepsize, step - t);
224         m_camera.Update(h);
225         t += h;
226     }
227 
228     // Update the Irrlicht camera
229     ChVector<> cam_pos = m_camera.GetCameraPos();
230     ChVector<> cam_target = m_camera.GetTargetPos();
231 
232     GetActiveCamera()->setPosition(core::vector3dfCH(cam_pos));
233     GetActiveCamera()->setTarget(core::vector3dfCH(cam_target));
234 
235 #ifdef CHRONO_IRRKLANG
236     static int stepsbetweensound = 0;
237 
238     // Update sound pitch
239     if (m_car_sound && m_vehicle->GetPowertrain()) {
240         stepsbetweensound++;
241         double engine_rpm = m_vehicle->GetPowertrain()->GetMotorSpeed() * 60 / CH_C_2PI;
242         double soundspeed = engine_rpm / (4000.);  // denominator: to guess
243         if (soundspeed < 0.1)
244             soundspeed = 0.1;
245         if (stepsbetweensound > 20) {
246             stepsbetweensound = 0;
247             if (m_car_sound->getIsPaused())
248                 m_car_sound->setIsPaused(false);
249             m_car_sound->setPlaybackSpeed((irrklang::ik_f32)soundspeed);
250         }
251     }
252 #endif
253 }
254 
255 // -----------------------------------------------------------------------------
256 // Render the Irrlicht scene and additional visual elements.
257 // -----------------------------------------------------------------------------
DrawAll()258 void ChVehicleIrrApp::DrawAll() {
259     ChIrrAppInterface::DrawAll();
260 
261     if (m_renderStats)
262         renderStats();
263 
264     // Allow derived classes to render additional graphical elements
265     renderOtherGraphics();
266 }
267 
268 // Render a horizontal grid
RenderGrid(const ChVector<> & loc,int num_divs,double delta)269 void ChVehicleIrrApp::RenderGrid(const ChVector<>& loc, int num_divs, double delta) {
270     irrlicht::tools::drawGrid(GetVideoDriver(), delta, delta, num_divs, num_divs,
271                                    ChCoordsys<>(loc, ChWorldFrame::Quaternion()),
272                                    irr::video::SColor(255, 255, 200, 0), true);
273 }
274 
275 // Render the specified reference frame
RenderFrame(const ChFrame<> & frame,double axis_length)276 void ChVehicleIrrApp::RenderFrame(const ChFrame<>& frame, double axis_length) {
277     const auto& loc = frame.GetPos();
278     const auto& u = frame.GetA().Get_A_Xaxis();
279     const auto& v = frame.GetA().Get_A_Yaxis();
280     const auto& w = frame.GetA().Get_A_Zaxis();
281     irrlicht::tools::drawSegment(GetVideoDriver(), loc, loc + u * axis_length, irr::video::SColor(255, 255, 0, 0));
282     irrlicht::tools::drawSegment(GetVideoDriver(), loc, loc + v * axis_length, irr::video::SColor(255, 0, 255, 0));
283     irrlicht::tools::drawSegment(GetVideoDriver(), loc, loc + w * axis_length, irr::video::SColor(255, 0, 0, 255));
284 }
285 
286 // Render a linear gauge in the HUD.
renderLinGauge(const std::string & msg,double factor,bool sym,int xpos,int ypos,int length,int height)287 void ChVehicleIrrApp::renderLinGauge(const std::string& msg,
288                                      double factor,
289                                      bool sym,
290                                      int xpos,
291                                      int ypos,
292                                      int length,
293                                      int height) {
294     irr::core::rect<s32> mclip(xpos, ypos, xpos + length, ypos + height);
295     GetVideoDriver()->draw2DRectangle(irr::video::SColor(90, 60, 60, 60),
296                                       irr::core::rect<s32>(xpos, ypos, xpos + length, ypos + height), &mclip);
297 
298     int left = sym ? (int)((length / 2 - 2) * std::min<>(factor, 0.0) + length / 2) : 2;
299     int right = sym ? (int)((length / 2 - 2) * std::max<>(factor, 0.0) + length / 2) : (int)((length - 4) * factor + 2);
300 
301     GetVideoDriver()->draw2DRectangle(irr::video::SColor(255, 250, 200, 0),
302                                       irr::core::rect<s32>(xpos + left, ypos + 2, xpos + right, ypos + height - 2),
303                                       &mclip);
304     if (sym) {
305         GetVideoDriver()->draw2DLine(irr::core::vector2d<irr::s32>(xpos + length / 2, ypos + 2),
306                                      irr::core::vector2d<irr::s32>(xpos + length / 2, ypos + height - 2),
307                                      irr::video::SColor(255, 250, 0, 0));
308     }
309 
310     irr::gui::IGUIFont* font = GetIGUIEnvironment()->getBuiltInFont();
311     font->draw(msg.c_str(), irr::core::rect<s32>(xpos + 3, ypos + 3, xpos + length, ypos + height),
312                irr::video::SColor(255, 20, 20, 20));
313 }
314 
315 // Render text in a box.
renderTextBox(const std::string & msg,int xpos,int ypos,int length,int height,irr::video::SColor color)316 void ChVehicleIrrApp::renderTextBox(const std::string& msg,
317                                     int xpos,
318                                     int ypos,
319                                     int length,
320                                     int height,
321                                     irr::video::SColor color) {
322     irr::core::rect<s32> mclip(xpos, ypos, xpos + length, ypos + height);
323     GetVideoDriver()->draw2DRectangle(irr::video::SColor(90, 60, 60, 60),
324                                       irr::core::rect<s32>(xpos, ypos, xpos + length, ypos + height), &mclip);
325 
326     irr::gui::IGUIFont* font = GetIGUIEnvironment()->getBuiltInFont();
327     font->draw(msg.c_str(), irr::core::rect<s32>(xpos + 3, ypos + 3, xpos + length, ypos + height), color);
328 }
329 
330 // Render stats for the vehicle and powertrain systems (render the HUD).
renderStats()331 void ChVehicleIrrApp::renderStats() {
332     char msg[100];
333 
334     sprintf(msg, "Camera mode: %s", m_camera.GetStateName().c_str());
335     renderTextBox(std::string(msg), m_HUD_x, m_HUD_y, 120, 15);
336 
337     double speed = m_vehicle->GetVehicleSpeed();
338     sprintf(msg, "Speed (m/s): %+.2f", speed);
339     renderLinGauge(std::string(msg), speed / 30, false, m_HUD_x, m_HUD_y + 30, 120, 15);
340 
341     // Display information from powertrain system.
342 
343     auto powertrain = m_vehicle->GetPowertrain();
344     if (powertrain) {
345         double engine_rpm = powertrain->GetMotorSpeed() * 60 / CH_C_2PI;
346         sprintf(msg, "Eng. speed (RPM): %+.2f", engine_rpm);
347         renderLinGauge(std::string(msg), engine_rpm / 7000, false, m_HUD_x, m_HUD_y + 50, 120, 15);
348 
349         double engine_torque = powertrain->GetMotorTorque();
350         sprintf(msg, "Eng. torque (Nm): %+.2f", engine_torque);
351         renderLinGauge(std::string(msg), engine_torque / 600, false, m_HUD_x, m_HUD_y + 70, 120, 15);
352 
353         double tc_slip = powertrain->GetTorqueConverterSlippage();
354         sprintf(msg, "T.conv. slip: %+.2f", tc_slip);
355         renderLinGauge(std::string(msg), tc_slip / 1, false, m_HUD_x, m_HUD_y + 90, 120, 15);
356 
357         double tc_torquein = powertrain->GetTorqueConverterInputTorque();
358         sprintf(msg, "T.conv. in  (Nm): %+.2f", tc_torquein);
359         renderLinGauge(std::string(msg), tc_torquein / 600, false, m_HUD_x, m_HUD_y + 110, 120, 15);
360 
361         double tc_torqueout = powertrain->GetTorqueConverterOutputTorque();
362         sprintf(msg, "T.conv. out (Nm): %+.2f", tc_torqueout);
363         renderLinGauge(std::string(msg), tc_torqueout / 600, false, m_HUD_x, m_HUD_y + 130, 120, 15);
364 
365         double tc_rpmout = powertrain->GetTorqueConverterOutputSpeed() * 60 / CH_C_2PI;
366         sprintf(msg, "T.conv. out (RPM): %+.2f", tc_rpmout);
367         renderLinGauge(std::string(msg), tc_rpmout / 7000, false, m_HUD_x, m_HUD_y + 150, 120, 15);
368 
369         char msgT[5];
370         switch (powertrain->GetTransmissionMode()) {
371             case ChPowertrain::TransmissionMode::AUTOMATIC:
372                 sprintf(msgT, "[A] ");
373                 break;
374             case ChPowertrain::TransmissionMode::MANUAL:
375                 sprintf(msgT, "[M] ");
376                 break;
377             default:
378                 sprintf(msgT, "    ");
379                 break;
380         }
381 
382         int ngear = powertrain->GetCurrentTransmissionGear();
383         ChPowertrain::DriveMode drivemode = powertrain->GetDriveMode();
384         switch (drivemode) {
385             case ChPowertrain::DriveMode::FORWARD:
386                 sprintf(msg, "%s Gear: forward  %d", msgT, ngear);
387                 break;
388             case ChPowertrain::DriveMode::NEUTRAL:
389                 sprintf(msg, "%s Gear: neutral", msgT);
390                 break;
391             case ChPowertrain::DriveMode::REVERSE:
392                 sprintf(msg, "%s Gear: reverse", msgT);
393                 break;
394             default:
395                 sprintf(msg, "Gear:");
396                 break;
397         }
398         renderLinGauge(std::string(msg), (double)ngear / 4.0, false, m_HUD_x, m_HUD_y + 170, 120, 15);
399     }
400 
401     // Display information from driver system.
402 
403     renderTextBox(m_driver_msg, m_HUD_x + 140, m_HUD_y, 120, 15);
404 
405     sprintf(msg, "Steering: %+.2f", m_steering);
406     renderLinGauge(std::string(msg), m_steering, true, m_HUD_x + 140, m_HUD_y + 30, 120, 15);
407 
408     sprintf(msg, "Throttle: %+.2f", m_throttle * 100.);
409     renderLinGauge(std::string(msg), m_throttle, false, m_HUD_x + 140, m_HUD_y + 50, 120, 15);
410 
411     sprintf(msg, "Braking: %+.2f", m_braking * 100.);
412     renderLinGauge(std::string(msg), m_braking, false, m_HUD_x + 140, m_HUD_y + 70, 120, 15);
413 
414     // Display current simulation time.
415 
416     sprintf(msg, "Time %.2f", m_vehicle->GetChTime());
417     renderTextBox(msg, m_HUD_x + 140, m_HUD_y + 100, 120, 15, irr::video::SColor(255, 250, 200, 00));
418 
419     // Allow derived classes to display additional information (e.g. driveline)
420 
421     renderOtherStats(m_HUD_x, m_HUD_y + 200);
422 }
423 
424 // -----------------------------------------------------------------------------
425 // Create a snapshot of the last rendered frame and save it to the provided
426 // file. The file extension determines the image format.
427 // -----------------------------------------------------------------------------
WriteImageToFile(const std::string & filename)428 void ChVehicleIrrApp::WriteImageToFile(const std::string& filename) {
429     video::IImage* image = GetVideoDriver()->createScreenShot();
430     if (image) {
431         GetVideoDriver()->writeImageToFile(image, filename.c_str());
432         image->drop();
433     }
434 }
435 
436 }  // end namespace vehicle
437 }  // end namespace chrono
438