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