1 // =============================================================================
2 // PROJECT CHRONO - http://projectchrono.org
3 //
4 // Copyright (c) 2019 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: Asher Elmquist
13 // =============================================================================
14 //
15 // Chrono demonstration for testing the chrono sensor module
16 //
17 //
18 // =============================================================================
19 
20 #include <cmath>
21 #include <cstdio>
22 #include <iomanip>
23 
24 #include "chrono/assets/ChTriangleMeshShape.h"
25 #include "chrono/assets/ChVisualMaterial.h"
26 #include "chrono/assets/ChVisualization.h"
27 #include "chrono/geometry/ChTriangleMeshConnected.h"
28 #include "chrono/physics/ChBodyEasy.h"
29 #include "chrono/physics/ChSystemNSC.h"
30 #include "chrono/physics/ChInertiaUtils.h"
31 #include "chrono/utils/ChUtilsCreators.h"
32 #include "chrono_thirdparty/filesystem/path.h"
33 
34 #include "chrono_sensor/sensors/ChCameraSensor.h"
35 #include "chrono_sensor/sensors/ChGPSSensor.h"
36 #include "chrono_sensor/sensors/ChIMUSensor.h"
37 #include "chrono_sensor/sensors/ChNoiseModel.h"
38 #include "chrono_sensor/sensors/ChLidarSensor.h"
39 #include "chrono_sensor/ChSensorManager.h"
40 #include "chrono_sensor/filters/ChFilterAccess.h"
41 #include "chrono_sensor/filters/ChFilterImageOps.h"
42 #include "chrono_sensor/filters/ChFilterLidarIntensityClip.h"
43 #include "chrono_sensor/filters/ChFilterCameraNoise.h"
44 #include "chrono_sensor/filters/ChFilterGrayscale.h"
45 #include "chrono_sensor/filters/ChFilterPCfromDepth.h"
46 #include "chrono_sensor/filters/ChFilterSave.h"
47 #include "chrono_sensor/filters/ChFilterSavePtCloud.h"
48 #include "chrono_sensor/filters/ChFilterVisualize.h"
49 #include "chrono_sensor/filters/ChFilterVisualizePointCloud.h"
50 
51 using namespace chrono;
52 using namespace chrono::geometry;
53 using namespace chrono::sensor;
54 
55 int num_cameras = 2 - 1;
56 int num_bodies = 10;
57 int num_groups = 1;
58 
59 bool save_data = false;
60 bool display_data = true;
61 
62 bool run_chrono = true;
63 float time_step = 0.002f;
64 float end_time = 30.0f;
65 
main(int argc,char * argv[])66 int main(int argc, char* argv[]) {
67     GetLog() << "Copyright (c) 2019 projectchrono.org\nChrono version: " << CHRONO_VERSION << "\n\n";
68 
69     // -----------------
70     // Create the system
71     // -----------------
72     auto phys_mat = chrono_types::make_shared<ChMaterialSurfaceNSC>();
73     phys_mat->SetFriction(0.2f);
74 
75     ChSystemNSC mphysicalSystem;
76     mphysicalSystem.Set_G_acc({0, 0, -9.81});
77 
78     auto floor = chrono_types::make_shared<ChBodyEasyBox>(100, 100, 1,      // x,y,z size
79                                                           1000,             // density
80                                                           true,             // collide enable?
81                                                           true, phys_mat);  // visualization?
82     floor->SetPos({0, 0, -1.0});
83     floor->SetRot(Q_from_AngZ(CH_C_PI / 2.0));
84     floor->SetBodyFixed(true);
85     mphysicalSystem.Add(floor);
86 
87     // place objects to visually test rotations
88     auto scalebox = chrono_types::make_shared<ChBodyEasyBox>(1, .2, .4,        // x,y,z size
89                                                              1000,             // density
90                                                              true,             // collide enable?
91                                                              true, phys_mat);  // visualization?
92     scalebox->SetPos({0, -1, 1});
93     scalebox->SetBodyFixed(true);
94     mphysicalSystem.Add(scalebox);
95 
96     // test max reflections
97     auto top_mirror = chrono_types::make_shared<ChBodyEasyBox>(10, 10, .1,       // x,y,z size
98                                                                1000,             // density
99                                                                true,             // collide enable?
100                                                                true, phys_mat);  // visualization?
101     top_mirror->SetPos({0, -1, 1.5});
102     top_mirror->SetBodyFixed(true);
103     mphysicalSystem.Add(top_mirror);
104     auto top_m_asset = top_mirror->GetAssets()[0];
105     if (std::shared_ptr<ChVisualization> visual_asset = std::dynamic_pointer_cast<ChVisualization>(top_m_asset)) {
106         auto vis_mat = chrono_types::make_shared<ChVisualMaterial>();
107         // vis_mat->SetAmbientColor({0.f, 0.f, 0.f});
108         vis_mat->SetDiffuseColor({1, 1, 1});
109         // vis_mat->SetAmbientColor(.2 * vis_mat->GetDiffuseColor());
110         vis_mat->SetSpecularColor({1.f, 1.f, 1.f});
111         vis_mat->SetRoughness(0);
112         vis_mat->SetMetallic(.9);
113         visual_asset->material_list.push_back(vis_mat);
114     }
115 
116     auto bottom_mirror = chrono_types::make_shared<ChBodyEasyBox>(10, 10, .1,       // x,y,z size
117                                                                   1000,             // density
118                                                                   true,             // collide enable?
119                                                                   true, phys_mat);  // visualization?
120     bottom_mirror->SetPos({0, -1, 0.5});
121     bottom_mirror->SetBodyFixed(true);
122     mphysicalSystem.Add(bottom_mirror);
123     auto bottom_m_asset = bottom_mirror->GetAssets()[0];
124     if (std::shared_ptr<ChVisualization> visual_asset = std::dynamic_pointer_cast<ChVisualization>(bottom_m_asset)) {
125         auto vis_mat = chrono_types::make_shared<ChVisualMaterial>();
126         // vis_mat->SetAmbientColor({0.f, 0.f, 0.f});
127         vis_mat->SetDiffuseColor({1, 1, 1});
128         // vis_mat->SetAmbientColor(.2 * vis_mat->GetDiffuseColor());
129         vis_mat->SetSpecularColor({1.f, 1.f, 1.f});
130         vis_mat->SetRoughness(0);
131         vis_mat->SetMetallic(.9);
132         visual_asset->material_list.push_back(vis_mat);
133     }
134     // add a mesh
135     auto mmesh = chrono_types::make_shared<ChTriangleMeshConnected>();
136     mmesh->LoadWavefrontMesh(GetChronoDataFile("models/bulldozer/shoe_collision.obj"), false, true);
137     mmesh->Transform(ChVector<>(0, 0, 0), ChMatrix33<>(1));  // scale to a different size
138     mmesh->RepairDuplicateVertexes(1e-9);
139 
140     double mmass;
141     ChVector<> mcog;
142     ChMatrix33<> minertia;
143     double mdensity = 1000;
144     mmesh->ComputeMassProperties(true, mmass, mcog, minertia);
145     ChMatrix33<> principal_inertia_rot;
146     ChVector<> principal_I;
147     ChInertiaUtils::PrincipalInertia(minertia, principal_I, principal_inertia_rot);
148     auto trimesh_shape = chrono_types::make_shared<ChTriangleMeshShape>();
149     trimesh_shape->SetMesh(mmesh);
150     trimesh_shape->SetName("Mesh");
151     trimesh_shape->SetStatic(true);
152 
153     std::shared_ptr<ChBody> imu_parent;
154     std::shared_ptr<ChBody> gps_parent;
155 
156     // float size = 3;
157     // auto origin = chrono_types::make_shared<ChBodyEasyBox>(size, size, size, 1000, true, false);
158     // origin->SetPos({0, 0, 1});
159     // origin->SetBodyFixed(true);
160     //
161     // mphysicalSystem.Add(origin);
162     // auto origin_asset = origin->GetAssets()[0];
163     // if (std::shared_ptr<ChVisualization> v_asset = std::dynamic_pointer_cast<ChVisualization>(origin_asset)) {
164     //     auto vis_mat = chrono_types::make_shared<ChVisualMaterial>();
165     //     vis_mat->SetDiffuseColor({1, 0, 0});
166     //     vis_mat->SetSpecularColor({.5f, .5f, .5f});
167     //
168     //     v_asset->material_list.push_back(vis_mat);
169     // }
170 
171     // walls to contain falling objects
172     auto wall1 = chrono_types::make_shared<ChBodyEasyBox>(40.0, .1, 10.0, 1000, true, true, phys_mat);
173     wall1->SetPos({0, -20, 4});
174     wall1->SetBodyFixed(true);
175     mphysicalSystem.Add(wall1);
176 
177     auto wall2 = chrono_types::make_shared<ChBodyEasyBox>(40.0, .1, 10.0, 1000, true, true, phys_mat);
178     wall2->SetPos({0, 20, 4});
179     wall2->SetBodyFixed(true);
180     mphysicalSystem.Add(wall2);
181 
182     auto wall3 = chrono_types::make_shared<ChBodyEasyBox>(.1, 40.0, 10.0, 1000, true, true, phys_mat);
183     wall3->SetPos({-20, 0, 4});
184     wall3->SetBodyFixed(true);
185     mphysicalSystem.Add(wall3);
186 
187     auto wall4 = chrono_types::make_shared<ChBodyEasyBox>(.1, 40.0, 10.0, 1000, true, true, phys_mat);
188     wall4->SetPos({20, 0, 4});
189     wall4->SetBodyFixed(true);
190     mphysicalSystem.Add(wall4);
191 
192     // add box, sphere, mesh with textures
193     // auto gator_mesh = chrono_types::make_shared<ChTriangleMeshConnected>();
194     // gator_mesh->LoadWavefrontMesh(GetChronoDataFile("vehicle/gator/gator_chassis.obj"), false, true);
195     // auto gator_meshshape = chrono_types::make_shared<ChTriangleMeshShape>();
196     // gator_meshshape->SetMesh(gator_mesh);
197     // gator_meshshape->SetName("gator");
198     // gator_meshshape->SetStatic(true);
199     // auto gator_mesh_body = chrono_types::make_shared<ChBody>();
200     // gator_mesh_body->SetPos({1, 2, 2});
201     // gator_mesh_body->AddAsset(gator_meshshape);
202     // gator_mesh_body->SetBodyFixed(true);
203     // mphysicalSystem.Add(gator_mesh_body);
204 
205     auto texbox = chrono_types::make_shared<ChBodyEasyBox>(1, 1,
206                                                            1,                 // x,y,z size
207                                                            1000,              // density
208                                                            true,              // vis enable?
209                                                            false, phys_mat);  //
210     texbox->SetPos({1, 0, 3});
211     texbox->SetBodyFixed(true);
212     mphysicalSystem.Add(texbox);
213 
214     auto texbox_asset = texbox->GetAssets()[0];
215     if (std::shared_ptr<ChVisualization> visual_asset = std::dynamic_pointer_cast<ChVisualization>(texbox_asset)) {
216         auto vis_mat = chrono_types::make_shared<ChVisualMaterial>();
217         vis_mat->SetSpecularColor({.2f, .2f, .2f});
218         vis_mat->SetKdTexture(GetChronoDataFile("textures/redwhite.png"));
219         vis_mat->SetNormalMapTexture(GetChronoDataFile("sensor/textures/FaceNormal.jpg"));
220         visual_asset->material_list.push_back(vis_mat);
221     }
222 
223     auto texsphere = chrono_types::make_shared<ChBodyEasySphere>(.6,                // size
224                                                                  1000,              // density
225                                                                  true,              // vis enable?
226                                                                  false, phys_mat);  //
227     texsphere->SetPos({1, -2, 3});
228     texsphere->SetBodyFixed(true);
229     mphysicalSystem.Add(texsphere);
230 
231     auto texsphere_asset = texsphere->GetAssets()[0];
232     if (std::shared_ptr<ChVisualization> visual_asset = std::dynamic_pointer_cast<ChVisualization>(texsphere_asset)) {
233         auto vis_mat = chrono_types::make_shared<ChVisualMaterial>();
234         vis_mat->SetSpecularColor({.2f, .2f, .2f});
235         vis_mat->SetKdTexture(GetChronoDataFile("textures/redwhite.png"));
236         vis_mat->SetNormalMapTexture(GetChronoDataFile("sensor/textures/FaceNormal.jpg"));
237         visual_asset->material_list.push_back(vis_mat);
238     }
239 
240     auto texcyl = chrono_types::make_shared<ChBodyEasyCylinder>(.5, 1,             // size
241                                                                 1000,              // density
242                                                                 true,              // vis enable?
243                                                                 false, phys_mat);  //
244     texcyl->SetPos({1, -4, 3});
245     texcyl->SetBodyFixed(true);
246     mphysicalSystem.Add(texcyl);
247 
248     auto texcyl_asset = texcyl->GetAssets()[0];
249     if (std::shared_ptr<ChVisualization> visual_asset = std::dynamic_pointer_cast<ChVisualization>(texcyl_asset)) {
250         auto vis_mat = chrono_types::make_shared<ChVisualMaterial>();
251         vis_mat->SetSpecularColor({.2f, .2f, .2f});
252         vis_mat->SetKdTexture(GetChronoDataFile("textures/redwhite.png"));
253         vis_mat->SetNormalMapTexture(GetChronoDataFile("sensor/textures/FaceNormal.jpg"));
254         visual_asset->material_list.push_back(vis_mat);
255     }
256 
257     for (int i = 0; i < num_bodies; i++) {
258         // add a box
259         auto box =
260             chrono_types::make_shared<ChBodyEasyBox>((float)ChRandom() / 2.0 + 0.1, (float)ChRandom() / 2.0 + 0.1,
261                                                      (float)ChRandom() / 2.0 + 0.1,  // x,y,z size
262                                                      1000,                           // density
263                                                      true,                           // collide enable?
264                                                      true, phys_mat);                // visualization?
265         box->SetPos({(float)ChRandom(), (float)ChRandom(), 2.0 + i});
266         box->SetRot(Q_from_Euler123({(float)ChRandom(), (float)ChRandom(), (float)ChRandom()}));
267         mphysicalSystem.Add(box);
268 
269         auto box_asset = box->GetAssets()[0];
270         if (std::shared_ptr<ChVisualization> visual_asset = std::dynamic_pointer_cast<ChVisualization>(box_asset)) {
271             auto vis_mat = chrono_types::make_shared<ChVisualMaterial>();
272             // vis_mat->SetAmbientColor({0.f, 0.f, 0.f});
273             vis_mat->SetDiffuseColor({(float)ChRandom(), (float)ChRandom(), (float)ChRandom()});
274             // vis_mat->SetAmbientColor(.2 * vis_mat->GetDiffuseColor());
275             vis_mat->SetSpecularColor({.2f, .2f, .2f});
276 
277             visual_asset->material_list.push_back(vis_mat);
278         }
279 
280         if (!imu_parent) {
281             imu_parent = box;
282         }
283 
284         auto cyl = chrono_types::make_shared<ChBodyEasyCylinder>((float)ChRandom() / 2.0 + 0.1,  // radius
285                                                                  (float)ChRandom() / 2.0 + 0.1,  // height
286                                                                  1000,                           // density
287                                                                  true,                           // collide enable?
288                                                                  true, phys_mat);                // visualization?
289         cyl->SetPos({(float)ChRandom(), (float)ChRandom(), 2.0 + i});
290         cyl->SetRot(Q_from_Euler123({(float)ChRandom(), (float)ChRandom(), (float)ChRandom()}));
291         mphysicalSystem.Add(cyl);
292 
293         auto cyl_asset = cyl->GetAssets()[0];
294         if (std::shared_ptr<ChVisualization> visual_asset = std::dynamic_pointer_cast<ChVisualization>(cyl_asset)) {
295             auto vis_mat = chrono_types::make_shared<ChVisualMaterial>();
296             // vis_mat->SetAmbientColor({0.f, 0.f, 0.f});
297             vis_mat->SetDiffuseColor({(float)ChRandom(), (float)ChRandom(), (float)ChRandom()});
298             // vis_mat->SetAmbientColor(.2 * vis_mat->GetDiffuseColor());
299             vis_mat->SetSpecularColor({.2f, .2f, .2f});
300 
301             visual_asset->material_list.push_back(vis_mat);
302         }
303 
304         auto sphere = chrono_types::make_shared<ChBodyEasySphere>((float)ChRandom() / 2.0 + 0.1,  // radius
305                                                                   1000,                           // density
306                                                                   true,                           // collide enable?
307                                                                   true, phys_mat);                // visualization?
308         sphere->SetPos({(float)ChRandom(), (float)ChRandom(), 2.0 + i});
309         // sphere->SetRot(Q_from_Euler123({(float)ChRandom(), (float)ChRandom(), (float)rand() /
310         // RAND_MAX}));
311         mphysicalSystem.Add(sphere);
312         if (!gps_parent) {
313             gps_parent = sphere;
314         }
315 
316         auto sphere_asset = sphere->GetAssets()[0];
317         if (std::shared_ptr<ChVisualization> visual_asset = std::dynamic_pointer_cast<ChVisualization>(sphere_asset)) {
318             auto vis_mat = chrono_types::make_shared<ChVisualMaterial>();
319             vis_mat->SetAmbientColor({0.f, 0.f, 0.f});
320             vis_mat->SetDiffuseColor({(float)ChRandom(), (float)ChRandom(), (float)ChRandom()});
321             vis_mat->SetSpecularColor({.2f, .2f, .2f});
322 
323             visual_asset->material_list.push_back(vis_mat);
324         }
325 
326         // auto mesh_body = chrono_types::make_shared<ChBody>();
327         // mesh_body->SetPos({0, 0, 0});
328         // mesh_body->AddAsset(trimesh_shape);
329         // mesh_body->SetBodyFixed(true);
330         // mesh_body->GetCollisionModel()->ClearModel();
331         // mesh_body->GetCollisionModel()->AddTriangleMesh(phys_mat, mmesh, true, false);
332         // mesh_body->GetCollisionModel()->BuildModel();
333         // mphysicalSystem.Add(mesh_body);
334 
335         auto mesh_body = chrono_types::make_shared<ChBodyAuxRef>();
336         mesh_body->SetFrame_COG_to_REF(ChFrame<>(mcog, principal_inertia_rot));
337         mesh_body->SetMass(mmass * mdensity);
338         mesh_body->SetInertiaXX(mdensity * principal_I);
339         mesh_body->SetFrame_REF_to_abs(ChFrame<>(ChVector<>((float)ChRandom(), (float)ChRandom(), 2.0 + i)));
340         mphysicalSystem.Add(mesh_body);
341 
342         mesh_body->GetCollisionModel()->ClearModel();
343         mesh_body->GetCollisionModel()->AddTriangleMesh(phys_mat, mmesh, false, false, VNULL, ChMatrix33<>(1), 0.005);
344         mesh_body->GetCollisionModel()->BuildModel();
345         mesh_body->SetCollide(true);
346 
347         mesh_body->AddAsset(trimesh_shape);
348     }
349 
350     std::cout << "sensor manager being made\n";
351     auto manager = chrono_types::make_shared<ChSensorManager>(&mphysicalSystem);
352     manager->SetDeviceList({2, 1, 0});
353     manager->SetMaxEngines(num_groups);  // THIS NEEDS MORE PERFORMANCE TESTING
354     manager->SetVerbose(false);
355 
356     // make some changes/additions to the scene
357     PointLight p0 = {{10, 10, 10}, {1, 1, 1}, 1000};
358     PointLight p1 = {{10, 10, 10}, {0, 0, 1}, 1000};
359     manager->scene->AddPointLight(p0);
360     manager->scene->AddPointLight(p1);
361     manager->scene->AddPointLight({0, 0, 100}, {1, 1, 1}, 1000);
362 
363     // set the background
364     Background b;
365     b.mode = BackgroundMode::GRADIENT;
366     b.color_zenith = {.5f, .6f, .7f};
367     b.color_horizon = {.9f, .8f, .7f};
368     // b.env_tex = GetChronoDataFile("sensor/textures/sky_2_4k.hdr");
369     manager->scene->SetBackground(b);
370 
371     auto cam = chrono_types::make_shared<ChCameraSensor>(
372         floor,                                                               // body camera is attached to
373         60.0f,                                                               // update rate in Hz
374         chrono::ChFrame<double>({-10, 0, 1}, Q_from_AngAxis(0, {0, 0, 1})),  // offset pose
375         1920,                                                                // image width
376         1080,                                                                // image height
377         (float)CH_C_PI / 4,                                                  // field of view
378         2, CameraLensModelType::FOV_LENS, false);
379 
380     std::string color_data_path = "SENSOR_OUTPUT/cam_color/";
381     std::string gray_data_path = "SENSOR_OUTPUT/cam_gray/";
382     std::string lidar_data_path = "SENSOR_OUTPUT/lidar/";
383 
384     cam->SetName("Camera Sensor 0");
385     // cam->SetLag();
386     // cam->SetCollectionWindow(imu_collection_time);
387 
388     // cam->PushFilter(chrono_types::make_shared<ChFilterCameraNoiseConstNormal>(0.f, 0.01));
389     // cam->PushFilter(chrono_types::make_shared<ChFilterCameraNoisePixDep>(0.01f, 0.01f, 0.01f));
390 
391     // we want to save the RGBA buffer to png
392     if (save_data)
393         cam->PushFilter(chrono_types::make_shared<ChFilterSave>(color_data_path));
394 
395     // we want to visualize this sensor right after rendering, so add the visualize filter to the filter list.
396     // if (display_data)
397     cam->PushFilter(chrono_types::make_shared<ChFilterVisualize>(1280, 720, "Whitted, before Grayscale Filter"));
398 
399     // we want to have access to this RGBA8 buffer on the host.
400     cam->PushFilter(chrono_types::make_shared<ChFilterRGBA8Access>());
401 
402     cam->PushFilter(chrono_types::make_shared<ChFilterImageResize>(1280, 720, "resize filter"));
403 
404     // // filter the sensor to grayscale
405     cam->PushFilter(chrono_types::make_shared<ChFilterGrayscale>());
406     //
407     // // we want to visualize this sensor after grayscale, so add the visualize filter to the filter list.
408     if (display_data)
409         cam->PushFilter(chrono_types::make_shared<ChFilterVisualize>(1280, 720, "Whitted, Final Visualization"));
410     //
411     // // we want to save the grayscale buffer to png
412     if (save_data)
413         cam->PushFilter(chrono_types::make_shared<ChFilterSave>(gray_data_path));
414     //
415     // // we also want to have access to this grayscale buffer on the host.
416     cam->PushFilter(chrono_types::make_shared<ChFilterR8Access>());
417 
418     // add sensor to the manager
419     manager->AddSensor(cam);
420 
421     // add a lidar to the floor facing the falling objects
422     auto lidar = chrono_types::make_shared<ChLidarSensor>(
423         floor,                                                              // body to which the IMU is attached
424         10.0f,                                                              // update rate
425         chrono::ChFrame<double>({-8, 0, 1}, Q_from_AngAxis(0, {1, 0, 0})),  // offset pose from body
426         923,                                                                // horizontal samples
427         23,                                                                 // vertical samples/channels
428         2.f * (float)CH_C_PI / 3.0f,                                        // horizontal field of view
429         (float)CH_C_PI / 8.0f, -(float)CH_C_PI / 8.0f, 100.0f               // vertical field of view
430     );
431     lidar->SetName("Lidar Sensor");
432     lidar->SetLag(.1f);
433     lidar->SetCollectionWindow(.1f);
434 
435     // lidar->PushFilter(chrono_types::make_shared<ChFilterLidarIntensityClip>(.02f, 0.f, "Intensity Clip Filter"));
436 
437     if (display_data)
438         lidar->PushFilter(chrono_types::make_shared<ChFilterVisualize>(923, 48, "Raw Lidar Data"));
439     lidar->PushFilter(chrono_types::make_shared<ChFilterDIAccess>());
440     lidar->PushFilter(chrono_types::make_shared<ChFilterPCfromDepth>());
441     if (display_data)
442         lidar->PushFilter(chrono_types::make_shared<ChFilterVisualizePointCloud>(640, 480, 2.0f, "Lidar Point Cloud"));
443     if (save_data)
444         lidar->PushFilter(chrono_types::make_shared<ChFilterSavePtCloud>(lidar_data_path));
445     lidar->PushFilter(chrono_types::make_shared<ChFilterXYZIAccess>());
446     // lidar->PushFilter(chrono_types::make_shared<ChFilterXYZIAccess>());
447     // manager->AddSensor(lidar);
448 
449     // add a lidar to the floor facing the falling objects
450     auto lidar2 = chrono_types::make_shared<ChLidarSensor>(
451         floor,                                                              // body to which the IMU is attached
452         10.0f,                                                              // update rate
453         chrono::ChFrame<double>({-8, 0, 1}, Q_from_AngAxis(0, {1, 0, 0})),  // offset pose from body
454         923,                                                                // horizontal samples
455         23,                                                                 // vertical samples/channels
456         2.f * (float)CH_C_PI / 3.0f,                                        // horizontal field of view
457         (float)CH_C_PI / 8.0f,                                              // max vert angle
458         -(float)CH_C_PI / 8.0f,                                             // min vert angle
459         100.0f,                                                             // max range
460         LidarBeamShape::RECTANGULAR,                                        // beam shape
461         3,                                                                  // beam sample radius
462         .003,                                                               // vert divergence angle
463         .003,                                                               // horizontal divergence angle
464         LidarReturnMode::STRONGEST_RETURN                                   // return type
465     );
466     lidar2->SetName("Lidar Sensor 2");
467     lidar2->SetLag(.1f);
468     lidar2->SetCollectionWindow(.1f);
469 
470     if (display_data)
471         lidar2->PushFilter(chrono_types::make_shared<ChFilterVisualize>(923, 48, "Raw Lidar Data"));
472     lidar2->PushFilter(chrono_types::make_shared<ChFilterDIAccess>());
473     lidar2->PushFilter(chrono_types::make_shared<ChFilterPCfromDepth>());
474     if (display_data)
475         lidar2->PushFilter(
476             chrono_types::make_shared<ChFilterVisualizePointCloud>(640, 480, 2.0f, "Lidar2 Point Cloud"));
477     lidar2->PushFilter(chrono_types::make_shared<ChFilterXYZIAccess>());
478     // manager->AddSensor(lidar2);
479 
480     // add an IMU sensor to one of the boxes
481     auto imu_offset_pose = chrono::ChFrame<double>({0, 0, 0}, Q_from_AngAxis(0, {1, 0, 0}));
482     auto noise_none = chrono_types::make_shared<ChNoiseNone>();
483     auto acc = chrono_types::make_shared<ChAccelerometerSensor>(imu_parent,       // body to which the IMU is
484                                                                                   // attached
485                                                                 100,              // update rate
486                                                                 imu_offset_pose,  // offset pose from body
487                                                                 noise_none);      // IMU noise model
488     acc->SetName("IMU - Accelerometer");
489     acc->PushFilter(chrono_types::make_shared<ChFilterAccelAccess>());  // Add a filter to access the imu data
490     manager->AddSensor(acc);                                            // Add the IMU sensor to the sensor manager
491 
492     auto gyro = chrono_types::make_shared<ChGyroscopeSensor>(imu_parent,       // body to which the IMU is attached
493                                                              100,              // update rate
494                                                              imu_offset_pose,  // offset pose from body
495                                                              noise_none);      // IMU noise model
496     gyro->SetName("IMU - Accelerometer");
497     gyro->PushFilter(chrono_types::make_shared<ChFilterGyroAccess>());  // Add a filter to access the imu data
498     manager->AddSensor(gyro);                                           // Add the IMU sensor to the sensor manager
499 
500     auto mag = chrono_types::make_shared<ChMagnetometerSensor>(imu_parent,       // body to which the IMU is attached
501                                                                100,              // update rate
502                                                                imu_offset_pose,  // offset pose from body
503                                                                noise_none,       // IMU noise model
504                                                                ChVector<double>(43.300, -89.000, 260.0));
505     mag->SetName("IMU - Accelerometer");
506     mag->PushFilter(chrono_types::make_shared<ChFilterMagnetAccess>());  // Add a filter to access the imu data
507     manager->AddSensor(mag);                                             // Add the IMU sensor to the sensor manager
508 
509     // add an IMU sensor to one of the boxes
510     auto noise_model =
511         chrono_types::make_shared<ChNoiseNormal>(ChVector<float>(0.f, 0.f, 0.f), ChVector<float>(1.f, 1.f, 1.f));
512     auto gps = chrono_types::make_shared<ChGPSSensor>(
513         gps_parent,                                                        // body to which the GPS is attached
514         10,                                                                // update rate
515         chrono::ChFrame<double>({0, 0, 0}, Q_from_AngAxis(0, {1, 0, 0})),  // offset pose from body
516         ChVector<double>(43.300, -89.000, 260.0),  // reference GPS location (GPS coordinates of simulation origin)
517         noise_model                                // noise model to use for adding GPS noise (NOT THREAD SAFE)
518     );
519     gps->SetName("GPS");
520     gps->PushFilter(chrono_types::make_shared<ChFilterGPSAccess>());
521     manager->AddSensor(gps);
522 
523     std::vector<std::shared_ptr<ChCameraSensor>> cams;
524 
525     for (int i = 0; i < num_cameras; i++) {
526         auto cam1 = chrono_types::make_shared<ChCameraSensor>(
527             floor,                                                              // body camera is attached to
528             10.0f + 10.0f * (i % 4 + 1),                                        // 30 + i, // update rate in Hz
529             chrono::ChFrame<double>({-3, 0, 2}, Q_from_AngAxis(0, {1, 0, 0})),  // offset pose
530             1280,                                                               // image width
531             720,                                                                // image height
532             (float)CH_C_PI / 3);
533         cams.push_back(cam1);
534 
535         std::stringstream nm;
536         nm << "Camera Sensor " << i + 1;
537         cam1->SetName(nm.str());
538         cam1->SetLag(.1f);
539         cam1->SetCollectionWindow(0);
540 
541         // we want to visualize this sensor, so add the visualize filter to the filter list.
542         if (display_data)
543             cam1->PushFilter(chrono_types::make_shared<ChFilterVisualize>(1280, 720, "Before Grayscale Filter"));
544 
545         // filter the sensor to grayscale
546         cam1->PushFilter(chrono_types::make_shared<ChFilterGrayscale>());
547 
548         // we want to visualize this sensor after grayscale, so add the visualize filter to the filter list.
549         if (display_data)
550             cam1->PushFilter(chrono_types::make_shared<ChFilterVisualize>(1280, 720, "After Grayscale Filter"));
551 
552         if (save_data)
553             cam1->PushFilter(chrono_types::make_shared<ChFilterSave>("SENSOR_OUTPUT/cam" + std::to_string(i) + "/"));
554 
555         // add sensor to the manager
556         manager->AddSensor(cam1);
557     }
558 
559     // manager->ReconstructScenes();
560     // ---------------
561     // Simulate system
562     // ---------------
563     float orbit_radius = 15.f;
564     float orbit_rate = .2f;
565     float ch_time = 0.f;
566 
567     double render_time = 0;
568 
569     std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
570 
571     float light_change = -.002f;
572 
573     std::cout << "Sensor Mangager has: " << manager->GetNumEngines() << " engines running.\n";
574     while (ch_time < end_time) {
575         std::chrono::high_resolution_clock::time_point r0 = std::chrono::high_resolution_clock::now();
576         cam->SetOffsetPose(chrono::ChFrame<double>(
577             {-orbit_radius * cos(ch_time * orbit_rate), -orbit_radius * sin(ch_time * orbit_rate), 3},
578             Q_from_AngAxis(ch_time * orbit_rate, {0, 0, 1})));
579 
580         scalebox->SetRot(Q_from_AngY(ch_time * .3));
581 
582         p0.pos = {-orbit_radius * cos(ch_time * orbit_rate * 2), -orbit_radius * sin(ch_time * orbit_rate * 2), 10};
583         manager->scene->ModifyPointLight(0, p0);
584 
585         p1.color = make_float3(p1.color.x, p1.color.y, p1.color.z + light_change);
586         if (p1.color.z < 0) {
587             p1.color = make_float3(0, 0, 0);
588             light_change = -light_change;
589         }
590         if (p1.color.z > 1) {
591             p1.color = make_float3(0, 0, 1);
592             light_change = -light_change;
593         }
594         manager->scene->ModifyPointLight(1, p1);
595 
596         // origin->SetRot(Q_from_AngAxis(ch_time * orbit_rate, {0, 0, 1}));
597         // origin->SetPos({0, 0, 3 * sin(ch_time * orbit_rate)});
598 
599         manager->Update();
600         std::chrono::high_resolution_clock::time_point r1 = std::chrono::high_resolution_clock::now();
601         std::chrono::duration<double> t_render = std::chrono::duration_cast<std::chrono::duration<double>>(r1 - r0);
602         render_time += t_render.count();
603 
604         mphysicalSystem.DoStepDynamics(time_step);
605 
606         ch_time = (float)mphysicalSystem.GetChTime();
607     }
608     std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
609     std::chrono::duration<double> wall_time = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
610     std::cout << "Simulation time: " << ch_time << "s, wall time: " << wall_time.count() << "s.\n";
611     std::cout << "Time spent in Chrono: " << wall_time.count() - render_time
612               << ", extra time due to rendering: " << render_time << std::endl;
613 
614     return 0;
615 }
616