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: Justin Madsen, Radu Serban
13 // =============================================================================
14 //
15 //  Demo code about
16 //    - Creating a soil bin test rig mechanism
17 //    - A particle generating class to control some particle creation properties
18 //	  - Torque drives a rigid tire over the material, in-plane
19 //    - Irrlicht event receiver, to drive/modify particle properties with the GUI
20 //
21 // =============================================================================
22 
23 #include <algorithm>
24 
25 #include "chrono/assets/ChPointPointDrawing.h"
26 #include "chrono/core/ChRealtimeStep.h"
27 #include "chrono/geometry/ChTriangleMeshConnected.h"
28 #include "chrono/physics/ChBodyEasy.h"
29 #include "chrono/physics/ChLinkMotorRotationTorque.h"
30 #include "chrono/physics/ChSystemNSC.h"
31 
32 #include "chrono_irrlicht/ChIrrApp.h"
33 
34 using namespace chrono;
35 using namespace chrono::geometry;
36 using namespace chrono::collision;
37 using namespace chrono::irrlicht;
38 
39 using namespace irr;
40 using namespace irr::core;
41 using namespace irr::scene;
42 using namespace irr::video;
43 using namespace irr::io;
44 using namespace irr::gui;
45 
46 class ParticleGenerator {
47   public:
ParticleGenerator(ChIrrApp * application,ChSystemNSC * mphysicalSystem,double width,double len,double sphDensity=100.0,double boxDensity=100.0,double mu=0.33)48     ParticleGenerator(ChIrrApp* application,
49                       ChSystemNSC* mphysicalSystem,
50                       double width,
51                       double len,
52                       double sphDensity = 100.0,
53                       double boxDensity = 100.0,
54                       double mu = 0.33) {
55         this->app = application;
56         this->msys = mphysicalSystem;
57         this->bedLength = len;
58         this->bedWidth = width;
59         this->sphDens = sphDensity;
60         this->boxDens = boxDensity;
61         this->simTime_lastPcreated = 0.0;
62         this->totalParticles = 0;
63         this->totalParticleMass = 0.0;
64         this->mu = (float)mu;
65 
66         // keep track of some statistics
67         this->pRadMean = 0.0;
68         this->pRadStdDev = 0.0;
69         this->pRad_s1 = 0.0;
70         this->pRad_s2 = 0.0;
71         this->pMass_s2 = 0.0;
72         this->pMassMean = 0.0;
73         this->pMassStdDev = 0.0;
74     }
75 
~ParticleGenerator()76     ~ParticleGenerator() {}
77 
78     // return the total # of particles
nparticles() const79     int nparticles() const { return this->totalParticles; }
80 
81     // return the total particle mass
particleMass() const82     double particleMass() const { return (this->totalParticleMass); }
83 
getMu() const84     double getMu() const { return this->mu; }
85 
setMu(double newMu)86     void setMu(double newMu) {
87         if (newMu < 0.0) {
88             GetLog() << "can't set mu less than 0  \n";
89             return;
90         }
91         if (newMu > 1.0)
92             GetLog() << "probably shouldn't have mu > 1.0   \n";
93 
94         // set mu anyway if >1.0
95         this->mu = (float)newMu;
96     }
97 
getSphDensity() const98     double getSphDensity() const { return this->sphDens; }
setSphDensity(double newDens)99     void setSphDensity(double newDens) {
100         if (newDens < 0.0)
101             GetLog() << "can't set density less than 0  \n";
102         else
103             this->sphDens = newDens;
104     }
105 
setBoxDensity(double newDens)106     void setBoxDensity(double newDens) {
107         if (newDens < 0.0)
108             GetLog() << "can't set density less than 0  \n";
109         else
110             this->boxDens = newDens;
111     }
112 
113     // create some spheres with size = pSize + ChRank()*pDev
114     // also, you can create boxes too, with the sides being sized in the same sort of manner as the spheres
create_some_falling_items(double pSize,double pDev,int nParticles,int nBoxes=0)115     bool create_some_falling_items(double pSize, double pDev, int nParticles, int nBoxes = 0) {
116         double minTime_betweenCreate = 0.05;  // this much simulation time MUST elapse before being allowed to
117                                               // create more particles
118         if ((msys->GetChTime() - this->simTime_lastPcreated) >= minTime_betweenCreate) {
119             // reset the timer if we get in here
120             this->simTime_lastPcreated = msys->GetChTime();
121 
122             // generate some dirt in the bin
123             auto cubeMap = chrono_types::make_shared<ChTexture>();
124             cubeMap->SetTextureFilename(GetChronoDataFile("textures/concrete.jpg"));
125             auto rockMap = chrono_types::make_shared<ChTexture>();
126             rockMap->SetTextureFilename(GetChronoDataFile("textures/rock.jpg"));
127 
128             // I should really check these
129             ChCollisionModel::SetDefaultSuggestedEnvelope(0.003);
130             ChCollisionModel::SetDefaultSuggestedMargin(0.002);
131 
132             // increment the counters for total # of particles
133             this->totalParticles += nParticles;
134             this->totalParticles += nBoxes;
135             // kind of guess the height of the particle stack
136             double stackHeight = (this->totalParticles / 2000.0) * pSize - 0.2;
137 
138             // create the spheres
139             auto sphere_mat = chrono_types::make_shared<ChMaterialSurfaceNSC>();
140             sphere_mat->SetFriction(this->mu);
141 
142             for (int bi = 0; bi < nParticles; bi++) {
143                 double sphrad = pSize + pDev * ChRandom();
144                 double sphmass = (4 / 3) * CH_C_PI * pow(sphrad, 3) * this->sphDens;
145                 ChQuaternion<> randrot(ChRandom(), ChRandom(), ChRandom(), ChRandom());
146                 randrot.Normalize();
147                 // randomize spawning position, take stack height into consideration
148                 ChVector<> currPos = ChVector<>(-0.5 * bedWidth + ChRandom() * bedWidth,
149                                                 stackHeight + 2 * pSize * ((double)bi / (20.0 * ChRandom() + 50.0)),
150                                                 -0.5 * bedLength + ChRandom() * bedLength);
151                 auto currRigidBody =
152                     chrono_types::make_shared<ChBodyEasySphere>(sphrad, this->sphDens, true, true, sphere_mat);
153                 currRigidBody->SetPos(currPos);
154                 currRigidBody->SetRot(randrot);
155                 currRigidBody->AddAsset(rockMap);
156 
157                 msys->AddBody(currRigidBody);
158                 app->AssetBind(currRigidBody);
159                 app->AssetUpdate(currRigidBody);
160 
161                 // every time we add a body, increment the counter and mass
162                 this->totalParticleMass += sphmass;
163                 this->pMass_s2 += sphmass * sphmass;
164                 this->pRad_s1 += sphrad;
165                 this->pRad_s2 += sphrad * sphrad;
166             }
167 
168             // create the boxes
169             auto box_mat = chrono_types::make_shared<ChMaterialSurfaceNSC>();
170             box_mat->SetFriction(0.5f);
171 
172             for (int bi = 0; bi < nBoxes; bi++) {
173                 double xscale = 1.5 * ChRandom();  // scale 2 of the 3 dimensions
174                 double yscale = 2.0;
175                 double zscale = 1.5 * ChRandom();
176                 double boxmass = (pSize * xscale) * (pSize * yscale) * (pSize * zscale) * this->boxDens;
177                 // position found the same way as the spheres
178                 ChVector<> currPos = ChVector<>(-0.5 * bedWidth + ChRandom() * bedWidth,
179                                                 stackHeight + 2 * pSize * ((double)bi / (20.0 * ChRandom() + 20.0)),
180                                                 -0.5 * bedLength + ChRandom() * bedLength);
181 
182                 // randomize the initial orientation
183                 ChQuaternion<> randrot(ChRandom(), ChRandom(), ChRandom(), ChRandom());
184                 randrot.Normalize();
185                 // create the body object
186                 auto currRigidBody = chrono_types::make_shared<ChBodyEasyBox>(
187                     pSize * xscale, pSize * yscale, pSize * zscale, this->boxDens, true, true, box_mat);
188                 currRigidBody->SetPos(currPos);
189                 currRigidBody->SetRot(randrot);
190                 currRigidBody->AddAsset(cubeMap);
191 
192                 msys->AddBody(currRigidBody);
193                 app->AssetBind(currRigidBody);
194                 app->AssetUpdate(currRigidBody);
195 
196                 this->totalParticles++;
197                 this->totalParticleMass += boxmass;
198                 this->pMass_s2 += boxmass * boxmass;
199             }
200 
201             // update the statistics
202             this->pRadMean = pRad_s1 / (double)totalParticles;
203             this->pRadStdDev = sqrt((double)totalParticles * pRad_s2 - pRad_s1 * pRad_s1) / (double)totalParticles;
204             this->pMassMean = this->totalParticleMass / (double)totalParticles;
205             this->pMassStdDev = sqrt((double)totalParticles * pMass_s2 - totalParticleMass * totalParticleMass) /
206                                 (double)totalParticles;
207 
208             // created particles this step
209             return true;
210         }
211 
212         // did not create particles this time step
213         return false;
214     }
215 
216     // output in the same order as in class list
getStatistics()217     std::vector<double> getStatistics() {
218         std::vector<double> out;
219         out.resize(9);
220         out[0] = pRadMean;
221         out[1] = pRadStdDev;
222         out[2] = pRad_s1;
223         out[3] = pRad_s2;
224         out[4] = totalParticleMass;
225         out[5] = pMass_s2;
226         out[6] = pMassMean;
227         out[7] = pMassStdDev;
228 
229         return out;
230     }
231 
232   private:
233     ChIrrApp* app;
234     ChSystemNSC* msys;
235     int totalParticles;
236     double totalParticleMass;
237     double bedLength;
238     double bedWidth;
239     double simTime_lastPcreated;  // keep track of the sim time when trying to creatye particles
240 
241     // density of shape primitives
242     double sphDens;  // material density for spheres
243     double boxDens;  // material density for boxes
244     float mu;        // friction coef
245 
246     // for statistics
247     double pRadMean;     // running mean of particle rad
248     double pRadStdDev;   // running std. dev. of particle rad
249     double pRad_s1;      // running sum of radius
250     double pRad_s2;      // running square of radius
251     double pMass_s2;     // running square of mass
252     double pMassMean;    // running mean of mass
253     double pMassStdDev;  // running std. dev. of mass
254 };
255 
256 class SoilbinWheel {
257   public:
258     std::shared_ptr<ChBody> wheel;
259 
260     // Use convex decomposition for collision detection with the Trelleborg tire
SoilbinWheel(ChSystemNSC * system,ChVector<> mposition,double mass,ChVector<> & inertia)261     SoilbinWheel(ChSystemNSC* system, ChVector<> mposition, double mass, ChVector<>& inertia) {
262         ChCollisionModel::SetDefaultSuggestedEnvelope(0.005);
263         ChCollisionModel::SetDefaultSuggestedMargin(0.004);
264 
265         // Create the wheel body
266         wheel = chrono_types::make_shared<ChBody>();
267         wheel->SetPos(mposition);
268         wheel->SetMass(mass);
269         wheel->SetInertiaXX(inertia);
270         wheel->SetCollide(true);
271 
272         // Visualization mesh
273         auto tireMesh = chrono_types::make_shared<ChTriangleMeshConnected>();
274         tireMesh->LoadWavefrontMesh(GetChronoDataFile("models/tractor_wheel/tractor_wheel.obj"), true, true);
275         auto tireMesh_asset = chrono_types::make_shared<ChTriangleMeshShape>();
276         tireMesh_asset->SetMesh(tireMesh);
277         wheel->AddAsset(tireMesh_asset);
278 
279         // Contact material
280         auto wheel_mat = chrono_types::make_shared<ChMaterialSurfaceNSC>();
281         wheel_mat->SetFriction(0.4f);
282 
283         // Contact mesh
284         wheel->GetCollisionModel()->ClearModel();
285         // Describe the (invisible) colliding shape by adding the 'carcass' decomposed shape and the
286         // 'knobs'. Since these decompositions are only for 1/15th of the wheel, use for() to pattern them.
287         for (double mangle = 0; mangle < 360.; mangle += (360. / 15.)) {
288             ChQuaternion<> myrot;
289             ChStreamInAsciiFile myknobs(GetChronoDataFile("models/tractor_wheel/tractor_wheel_knobs.chulls").c_str());
290             ChStreamInAsciiFile myslice(GetChronoDataFile("models/tractor_wheel/tractor_wheel_slice.chulls").c_str());
291             myrot.Q_from_AngAxis(mangle * (CH_C_PI / 180.), VECT_X);
292             ChMatrix33<> mm(myrot);
293             wheel->GetCollisionModel()->AddConvexHullsFromFile(wheel_mat, myknobs, ChVector<>(0, 0, 0), mm);
294             wheel->GetCollisionModel()->AddConvexHullsFromFile(wheel_mat, myslice, ChVector<>(0, 0, 0), mm);
295         }
296         wheel->GetCollisionModel()->BuildModel();
297 
298         // Add wheel body to system
299         system->AddBody(wheel);
300     }
301 
~SoilbinWheel()302     ~SoilbinWheel() {}
303 };
304 
305 // create a test mechanism made up of 2 bodies
306 // a hub to connect to the wheel spindle and apply a torque through it
307 // a weight that moves vertically and horizontally w.r.t. the wheel spindle CM location
308 // spring/damper to apply a vertical load to the tire
309 // Purpose: only allow the tire to operate In-Plane, to simulate how a soil bin test mechanism works
310 class TestMech {
311   public:
312     // data
313     std::shared_ptr<ChBodyEasyBox> truss;       // spindle truss
314     std::shared_ptr<ChBodyEasyBox> suspweight;  // suspended weight
315     std::shared_ptr<ChBodyEasyBox> floor;
316     std::shared_ptr<ChBodyEasyBox> wall1;
317     std::shared_ptr<ChBodyEasyBox> wall2;
318     std::shared_ptr<ChBodyEasyBox> wall3;
319     std::shared_ptr<ChBodyEasyBox> wall4;
320     std::shared_ptr<ChLinkTSDA> spring;
321     std::shared_ptr<ChLinkMotorRotationTorque> torqueDriver;
322     std::shared_ptr<ChLinkLockRevolute> spindle;
323 
324     // GUI-tweaked data
325     bool isTorqueApplied;
326     double currTorque;
327 
328     // functions
TestMech(ChSystemNSC * system,std::shared_ptr<ChBody> wheelBody,double binWidth=1.0,double binLength=2.0,double weightMass=100.0,double springK=25000,double springD=100)329     TestMech(ChSystemNSC* system,
330              std::shared_ptr<ChBody> wheelBody,
331              double binWidth = 1.0,
332              double binLength = 2.0,
333              double weightMass = 100.0,
334              double springK = 25000,
335              double springD = 100) {
336         ChCollisionModel::SetDefaultSuggestedEnvelope(0.003);
337         ChCollisionModel::SetDefaultSuggestedMargin(0.002);
338 
339         ChQuaternion<> rot;
340         rot.Q_from_AngAxis(ChRandom() * CH_C_2PI, VECT_Y);
341 
342         // *******
343         // Create a soil bin with planes. bin width = x-dir, bin length = z-dir
344         // Note: soil bin depth will always be ~ 1m
345         // *******
346         double binHeight = 1.0;
347         double wallWidth = std::min<double>(binWidth, binLength) / 10.0;  // wall width = 1/10 of min of bin dims
348 
349         // create the floor
350         auto cubeMap = chrono_types::make_shared<ChTexture>();
351         cubeMap->SetTextureFilename(GetChronoDataFile("textures/concrete.jpg"));
352 
353         auto floor_mat = chrono_types::make_shared<ChMaterialSurfaceNSC>();
354         floor_mat->SetFriction(0.5f);
355 
356         floor = chrono_types::make_shared<ChBodyEasyBox>(binWidth + wallWidth / 2.0, wallWidth,
357                                                          binLength + wallWidth / 2.0, 1.0, true, true, floor_mat);
358         floor->SetPos(ChVector<>(0, -0.5 - wallWidth / 2.0, 0));
359         floor->SetBodyFixed(true);
360         floor->AddAsset(cubeMap);
361         system->AddBody(floor);
362 
363         // add some transparent walls to the soilBin, w.r.t. width, length of bin
364         wall1 = chrono_types::make_shared<ChBodyEasyBox>(wallWidth, binHeight, binLength, 1.0, true, true, floor_mat);
365         wall1->SetPos(ChVector<>(-binWidth / 2.0 - wallWidth / 2.0, 0, 0));
366         wall1->SetBodyFixed(true);
367         system->AddBody(wall1);
368 
369         wall2 = chrono_types::make_shared<ChBodyEasyBox>(wallWidth, binHeight, binLength, 1.0, false, true, floor_mat);
370         wall2->SetPos(ChVector<>(binWidth / 2.0 + wallWidth / 2.0, 0, 0));
371         wall2->SetBodyFixed(true);
372         system->AddBody(wall2);
373 
374         wall3 = chrono_types::make_shared<ChBodyEasyBox>(binWidth + wallWidth / 2.0, binHeight, wallWidth, 1.0, false,
375                                                          true, floor_mat);
376         wall3->SetPos(ChVector<>(0, 0, -binLength / 2.0 - wallWidth / 2.0));
377         wall3->SetBodyFixed(true);
378         system->AddBody(wall3);
379 
380         // wall 4
381         wall4 =
382             chrono_types::make_shared<ChBodyEasyBox>(binWidth + wallWidth / 2.0, binHeight, wallWidth, 1.0, true, true, floor_mat);
383         wall4->SetPos(ChVector<>(0, 0, binLength / 2.0 + wallWidth / 2.0));
384         wall4->SetBodyFixed(true);
385         system->AddBody(wall4);
386 
387         // ******
388         // make a truss, connect it to the wheel via revolute joint
389         // single rotational DOF will be driven with a user-input for torque
390         // *****
391         auto bluMap = chrono_types::make_shared<ChTexture>();
392         bluMap->SetTextureFilename(GetChronoDataFile("textures/blue.png"));
393         ChVector<> trussCM = wheelBody->GetPos();
394 
395         truss = chrono_types::make_shared<ChBodyEasyBox>(0.2, 0.2, 0.4, 300.0, true, false);
396         truss->SetPos(trussCM);
397         truss->SetMass(5.0);
398         truss->AddAsset(bluMap);
399         system->AddBody(truss);
400 
401         // create the revolute joint between the wheel and spindle
402         spindle = chrono_types::make_shared<ChLinkLockRevolute>();
403         spindle->Initialize(truss, wheelBody, ChCoordsys<>(trussCM, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_Y)));
404         system->AddLink(spindle);
405 
406         // create a torque between the truss and wheel
407         torqueDriver = chrono_types::make_shared<ChLinkMotorRotationTorque>();
408         torqueDriver->Initialize(truss, wheelBody, ChFrame<>(trussCM, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_Y)));
409         system->AddLink(torqueDriver);
410 
411         // ******
412         // create a body that will be used as a vehicle weight
413         ChVector<> weightCM = ChVector<>(trussCM);
414         weightCM.y() += 1.0;  // note: this will determine the spring free length
415 
416         suspweight = chrono_types::make_shared<ChBodyEasyBox>(0.2, 0.4, 0.2, 5000.0, true, false);
417         suspweight->SetPos(weightCM);
418         suspweight->SetMass(weightMass);
419         suspweight->AddAsset(bluMap);
420         system->AddBody(suspweight);
421 
422         // create the translational joint between the truss and weight load
423         auto translational = chrono_types::make_shared<ChLinkLockPrismatic>();
424         translational->Initialize(truss, suspweight,
425                                   ChCoordsys<>(trussCM, chrono::Q_from_AngAxis(CH_C_PI / 2, VECT_X)));
426         system->AddLink(translational);
427 
428         // create a spring between spindle truss and weight
429         spring = chrono_types::make_shared<ChLinkTSDA>();
430         spring->Initialize(truss, suspweight, false, trussCM, suspweight->GetPos());
431         spring->SetSpringCoefficient(springK);
432         spring->SetDampingCoefficient(springD);
433         system->AddLink(spring);
434 
435         spring->AddAsset(chrono_types::make_shared<ChColorAsset>(0.6f, 0.1f, 0.1f));
436         spring->AddAsset(chrono_types::make_shared<ChPointPointSpring>(0.05, 80, 15));
437 
438         // create a prismatic constraint between the weight and the ground
439         auto weightLink = chrono_types::make_shared<ChLinkLockOldham>();
440         weightLink->Initialize(suspweight, floor,
441                                ChCoordsys<>(weightCM, chrono::Q_from_AngAxis(CH_C_PI / 2.0, VECT_Y)));
442         system->AddLink(weightLink);
443     }
444 
445     // set the spring and damper constants
setSpringKD(double k,double d)446     void setSpringKD(double k, double d) {
447         this->spring->SetSpringCoefficient(k);
448         this->spring->SetDampingCoefficient(d);
449     }
450 
451     // for now, just use the slider value as directly as the torque
applyTorque()452     void applyTorque() {
453         // note: negative sign is to get Trelleborg tire to spin in the correct direction
454         auto mfun = std::static_pointer_cast<ChFunction_Const>(torqueDriver->GetTorqueFunction());
455         mfun->Set_yconst(-this->currTorque);
456     }
457 
~TestMech()458     ~TestMech() {}
459 };
460 
461 class MyEventReceiver : public IEventReceiver {
462   public:
463     // keep the tabs public
464     gui::IGUITabControl* gad_tabbed;
465     gui::IGUITab* gad_tab_controls;
466     gui::IGUITab* gad_tab_wheel;
467     gui::IGUITab* gad_tab_soil;
468 
469     // @param pSize particle radius
470     // @param pDev multiplier added to ChRandom()
471     // @param maxTorque max slider torque applied to wheel
472     // @param maxParticles max number of particles to generate each spawning event
MyEventReceiver(ChIrrApp * app,SoilbinWheel * wheel,TestMech * tester,ParticleGenerator * particleGenerator,double pSize=0.02,double pDev=0.02,double maxTorque=100.0,int maxParticles=50)473     MyEventReceiver(ChIrrApp* app,
474                     SoilbinWheel* wheel,
475                     TestMech* tester,
476                     ParticleGenerator* particleGenerator,
477                     double pSize = 0.02,
478                     double pDev = 0.02,
479                     double maxTorque = 100.0,
480                     int maxParticles = 50) {
481         // store pointer to physical system & other stuff so we can tweak them by user keyboard
482         this->mapp = app;
483         // any rigid bodies that have their states modified by the GUI need to go here
484         this->mwheel = wheel;
485         this->mtester = tester;
486         this->mgenerator = particleGenerator;
487         // for getting output from the TM_Module module
488         // initial checkbox values
489         this->wheelLocked = true;
490         this->makeParticles = false;
491         this->wheelCollision = false;
492         this->pVisible = true;
493         this->wheelVisible = true;
494 
495         // initial values for the sliders
496         this->particleSize0 = pSize;
497         this->particleDev0 = pDev;
498         this->maxTorque = maxTorque;
499         this->nParticlesGenMax = maxParticles;
500 
501         // **** ***
502         // create the GUI items here
503         irr::s32 x0 = 740;
504         irr::s32 y0 = 20;  // box0 top left corner
505         // create the tabs for the rig output: NOTE: GUI widget locations are all relative to the TabControl!
506         gad_tabbed =
507             mapp->GetIGUIEnvironment()->addTabControl(core::rect<s32>(x0, y0, x0 + 255, y0 + 440), 0, true, true);
508         gad_tab_controls = gad_tabbed->addTab(L"Controls");  // static text will be printed w/ each checkbox or slider
509         gad_text_wheelControls = mapp->GetIGUIEnvironment()->addStaticText(
510             L"Wheel Control", core::rect<s32>(10, 10, 245, 150), true, true, gad_tab_controls);
511         irr::s32 y1 = 165;  // box1 top left corner
512         gad_text_pControls = mapp->GetIGUIEnvironment()->addStaticText(
513             L"Particle Control", core::rect<s32>(10, y1, 245, y1 + 230), true, true, gad_tab_controls);
514         gad_tab_wheel = gad_tabbed->addTab(L"Wheel State");
515         gad_text_wheelState = mapp->GetIGUIEnvironment()->addStaticText(L"WS", core::rect<s32>(10, 10, 290, 250), true,
516                                                                         true, gad_tab_wheel);
517         gad_tab_soil = gad_tabbed->addTab(L"Soil State");
518         gad_text_soilState = mapp->GetIGUIEnvironment()->addStaticText(L"SS", core::rect<s32>(10, 10, 290, 250), true,
519                                                                        true, gad_tab_soil);
520 
521         // **** GUI CONTROLS ***
522         // -------- Wheel controls
523         // ..add a GUI for wheel position lock ( id = 2110 )
524         checkbox_wheelLocked = app->GetIGUIEnvironment()->addCheckBox(wheelLocked, core::rect<s32>(20, 30, 35, 45),
525                                                                       gad_tab_controls, 2110);
526         text_wheelLocked = app->GetIGUIEnvironment()->addStaticText(L"Wheel Locked", core::rect<s32>(45, 30, 125, 45),
527                                                                     false, false, gad_tab_controls);
528         checkbox_wheelLocked->setVisible(true);
529         text_wheelLocked->setVisible(true);
530         this->mwheel->wheel->SetBodyFixed(wheelLocked);  // set IC of checkbox
531         this->mtester->truss->SetBodyFixed(wheelLocked);
532         this->mtester->suspweight->SetBodyFixed(wheelLocked);
533 
534         // turn wheel visibility on/off, ie = 2115
535         ////checkbox_wheelVisible = app->GetIGUIEnvironment()->addCheckBox(wheelVisible, core::rect<s32>(180, 30, 195,
536         /// 45),
537         ////                                                               gad_tab_controls, 2115);
538         ////text_wheelVisible = app->GetIGUIEnvironment()->addStaticText(L"visible?", core::rect<s32>(205, 30, 290, 45),
539         ////                                                             false, false, gad_tab_controls);
540 
541         // add a GUI for setting the wheel collision ( id = 2112 )
542         checkbox_wheelCollision = app->GetIGUIEnvironment()->addCheckBox(
543             wheelCollision, core::rect<s32>(20, 60, 35, 75), gad_tab_controls, 2112);
544         text_wheelCollision = app->GetIGUIEnvironment()->addStaticText(
545             L"Wheel collide? ", core::rect<s32>(45, 60, 125, 75), false, false, gad_tab_controls);
546         checkbox_wheelCollision->setVisible(true);
547         text_wheelCollision->setVisible(true);
548         this->mwheel->wheel->SetCollide(wheelCollision);
549 
550         // torque slider	(id = 1103)
551         scrollbar_torque =
552             mapp->GetIGUIEnvironment()->addScrollBar(true, rect<s32>(20, 115, 150, 130), gad_tab_controls, 1103);
553         scrollbar_torque->setMax(100);
554         scrollbar_torque->setPos(50);
555         text_torque = mapp->GetIGUIEnvironment()->addStaticText(L"Torque[N/m]: 0 ", rect<s32>(160, 115, 300, 130),
556                                                                 false, false, gad_tab_controls);
557         this->mtester->currTorque = 0;  // set the IC of this slider
558 
559         // -------- Particle Controls
560         // add a GUI for turning particle creation on/off ( id = 2111 )
561         checkbox_createParticles = app->GetIGUIEnvironment()->addCheckBox(
562             makeParticles, core::rect<s32>(20, y1 + 20, 35, y1 + 35), gad_tab_controls, 2111);
563         text_createParticles = app->GetIGUIEnvironment()->addStaticText(
564             L"create Particles? ", core::rect<s32>(45, y1 + 20, 165, y1 + 35), false, false, gad_tab_controls);
565         checkbox_createParticles->setVisible(true);
566         text_createParticles->setVisible(true);
567 
568         // add a checkbox to make particle visibility turn on/off, id = 2114
569         ////checkbox_particlesVisible = app->GetIGUIEnvironment()->addCheckBox(
570         ////    pVisible, core::rect<s32>(180, y1 + 20, 195, y1 + 35), gad_tab_controls, 2114);
571         ////text_particlesVisible = app->GetIGUIEnvironment()->addStaticText(
572         ////    L"visible?", core::rect<s32>(205, y1 + 20, 290, y1 + 35), false, false, gad_tab_controls);
573 
574         // create sliders to modify particle size/dev ( id = 1101)
575         scrollbar_pSize = mapp->GetIGUIEnvironment()->addScrollBar(true, rect<s32>(20, y1 + 50, 150, y1 + 65),
576                                                                    gad_tab_controls, 1101);
577         scrollbar_pSize->setMax(100);
578         scrollbar_pSize->setPos(50);
579         char message[50];
580         sprintf(message, "p rad [m]: %g", particleSize0);
581         text_pSize = mapp->GetIGUIEnvironment()->addStaticText(
582             core::stringw(message).c_str(), rect<s32>(160, y1 + 50, 300, y1 + 65), false, false, gad_tab_controls);
583         this->currParticleSize = particleSize0;  // set the IC
584 
585         // particle rad Deviation slider	(id = 1102)
586         scrollbar_pDev = mapp->GetIGUIEnvironment()->addScrollBar(true, rect<s32>(20, y1 + 80, 150, y1 + 95),
587                                                                   gad_tab_controls, 1102);
588         scrollbar_pDev->setMax(100);
589         scrollbar_pDev->setPos(50);
590         char message1[50];
591         sprintf(message1, "p dev.[m]: %g", particleDev0);
592         text_pDev = mapp->GetIGUIEnvironment()->addStaticText(
593             core::stringw(message1).c_str(), rect<s32>(160, y1 + 80, 300, y1 + 95), false, false, gad_tab_controls);
594         this->currParticleDev = particleDev0;  // set the IC for the slider
595 
596         // nParticlesGen slider ( id = 1104)
597         scrollbar_nParticlesGen = mapp->GetIGUIEnvironment()->addScrollBar(true, rect<s32>(20, y1 + 110, 150, y1 + 125),
598                                                                            gad_tab_controls, 1104);
599         scrollbar_nParticlesGen->setMax(100);
600         scrollbar_nParticlesGen->setPos(50);
601         this->currNparticlesGen = nParticlesGenMax / 2;  // IC of this slider
602         char message2[50];
603         sprintf(message2, "# p Gen: %d", this->currNparticlesGen);
604         text_nParticlesGen = mapp->GetIGUIEnvironment()->addStaticText(
605             core::stringw(message2).c_str(), rect<s32>(160, y1 + 110, 300, y1 + 125), false, false, gad_tab_controls);
606 
607         // friction coefficient of particles, id = 1105
608         scrollbar_particleFriction = mapp->GetIGUIEnvironment()->addScrollBar(
609             true, rect<s32>(20, y1 + 140, 150, y1 + 155), gad_tab_controls, 1105);
610         scrollbar_particleFriction->setMax(100);
611         scrollbar_particleFriction->setPos(33);
612         this->currParticleFriction = 0.33;
613         char message3[50];
614         sprintf(message3, "mu: %g", this->currParticleFriction);
615         text_particleFriction = mapp->GetIGUIEnvironment()->addStaticText(
616             core::stringw(message3).c_str(), rect<s32>(160, y1 + 140, 300, y1 + 155), false, false, gad_tab_controls);
617 
618         // particle density, id = 1106
619         scrollbar_particleDensity = mapp->GetIGUIEnvironment()->addScrollBar(
620             true, rect<s32>(20, y1 + 170, 150, y1 + 185), gad_tab_controls, 1106);
621         scrollbar_particleDensity->setMax(100);
622         scrollbar_particleDensity->setPos(50);
623         this->avgDensity = this->mgenerator->getSphDensity();
624         char message4[50];
625         sprintf(message4, "rho [kg/m3]: %g", this->avgDensity);
626         text_particleDensity = mapp->GetIGUIEnvironment()->addStaticText(
627             core::stringw(message4).c_str(), rect<s32>(160, y1 + 170, 300, y1 + 185), false, false, gad_tab_controls);
628 
629         // ******* GUI WHEEL STATE
630         // wheel CM pos
631         ChVector<> cm = mwheel->wheel->GetPos();
632         char message5[100];
633         sprintf(message5, "CM pos, x: %4.4g, y: %4.4g, z: %4.4g", cm.x(), cm.y(), cm.z());
634         text_cmPos = mapp->GetIGUIEnvironment()->addStaticText(core::stringw(message5).c_str(),
635                                                                rect<s32>(10, 30, 280, 45), false, false, gad_tab_wheel);
636         // wheel CM vel
637         ChVector<> cmVel = mwheel->wheel->GetPos_dt();
638         char messageV[100];
639         sprintf(messageV, "CM vel, x: %4.4g, y: %4.4g, z: %4.4g", cmVel.x(), cmVel.y(), cmVel.z());
640         text_cmVel = mapp->GetIGUIEnvironment()->addStaticText(core::stringw(message5).c_str(),
641                                                                rect<s32>(10, 60, 280, 75), false, false, gad_tab_wheel);
642         // rxn. forces on spindle, in the local coordinate system
643         ChVector<> rxnF = mtester->spindle->Get_react_force();
644         char messageF[100];
645         sprintf(messageF, "spindle Rxn. F, x: %4.3g, y: %4.3g, z: %4.3g", rxnF.x(), rxnF.y(), rxnF.z());
646         text_spindleForces = mapp->GetIGUIEnvironment()->addStaticText(
647             core::stringw(message5).c_str(), rect<s32>(10, 90, 280, 105), false, false, gad_tab_wheel);
648         // rxn. torques on spindle, in local coordinate system
649         ChVector<> rxnT = mtester->spindle->Get_react_torque();
650         char messageT[100];
651         sprintf(messageT, "spindle Rxn. T, x: %4.3g, y: %4.3g, z: %4.3g", rxnT.x(), rxnT.y(), rxnT.z());
652         text_spindleTorque = mapp->GetIGUIEnvironment()->addStaticText(
653             core::stringw(messageT).c_str(), rect<s32>(10, 120, 280, 135), false, false, gad_tab_wheel);
654 
655         // ******* GUI PARTICLE STATE
656         // average particle size: pRadMean
657         // running/continuous std. dev: pRadStdDev
658         // total particle mass:	totalParticleMass
659         // average particle mass: pMassMean
660         // running/continuous std. dev of mass: pMassStdDev
661         std::vector<double> particleStats = this->mgenerator->getStatistics();
662 
663         char messageRad[100];
664         sprintf(messageRad, "p Rad mean, std. dev: %4.4g, %4.4g", particleStats[0], particleStats[1]);
665         text_pRad = mapp->GetIGUIEnvironment()->addStaticText(core::stringw(messageRad).c_str(),
666                                                               rect<s32>(10, 30, 280, 45), false, false, gad_tab_soil);
667         char messageMass[100];
668         sprintf(messageMass, "p mass mean, std. dev: %4.4g, %4.4g", particleStats[6], particleStats[7]);
669         text_pMass = mapp->GetIGUIEnvironment()->addStaticText(core::stringw(messageMass).c_str(),
670                                                                rect<s32>(10, 60, 280, 75), false, false, gad_tab_soil);
671     }
672 
OnEvent(const SEvent & event)673     bool OnEvent(const SEvent& event) {
674         // check if user moved the sliders with mouse..
675         if (event.EventType == EET_GUI_EVENT) {
676             s32 id = event.GUIEvent.Caller->getID();
677 
678             switch (event.GUIEvent.EventType) {
679                 case EGET_SCROLL_BAR_CHANGED:
680                     if (id == 1101)  // id of particle size slider
681                     {
682                         s32 currPos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
683                         this->currParticleSize = particleSize0 + ((currPos - 50) / 50.0) * particleSize0 + 0.001;
684                         char message[50];
685                         sprintf(message, "p rad [m]: %g", currParticleSize);
686                         text_pSize->setText(core::stringw(message).c_str());
687                     }
688                     if (id == 1102)  // id of particle Dev slider
689                     {
690                         s32 currPos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
691                         this->currParticleDev = particleDev0 + ((currPos - 50) / 50.0) * particleDev0 + 0.001;
692                         char message[50];
693                         sprintf(message, "p dev.[m]: %g", currParticleDev);
694                         text_pDev->setText(core::stringw(message).c_str());
695                     }
696                     if (id == 1103)  // torque slider
697                     {
698                         s32 currPos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
699                         double torquenew = ((currPos - 50.0) / 50.0) * maxTorque;
700                         char message[50];
701                         sprintf(message, "Torque[N/m]: %g", torquenew);
702                         text_torque->setText(core::stringw(message).c_str());
703                         // set the new torque to the tester
704                         this->mtester->currTorque = torquenew;  // set the new torque
705                     }
706                     if (id == 1104)  // # particles to generate
707                     {
708                         s32 currPos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
709                         this->currNparticlesGen =
710                             nParticlesGenMax + int(double(currPos - 50) / 50.0) * nParticlesGenMax;
711                         char message[50];
712                         sprintf(message, "# p Gen: %d", this->currNparticlesGen);
713                         text_nParticlesGen->setText(core::stringw(message).c_str());
714                     }
715                     if (id == 1105)  // mu of particlers
716                     {
717                         s32 sliderPos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
718                         this->currParticleFriction = sliderPos / 100.0;
719                         char message[50];
720                         sprintf(message, "mu: %g", this->currParticleFriction);
721                         text_particleFriction->setText(core::stringw(message).c_str());
722                         // set the friction of the particles generated
723                         this->mgenerator->setMu(sliderPos / 100.0);
724                     }
725                     if (id == 1106)  // density of spheres
726                     {
727                         s32 sliderPos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
728                         double density = this->avgDensity + ((sliderPos - 50.0) / 100.0) * this->avgDensity;
729                         char message[50];
730                         sprintf(message, "rho [kg/m3]: %g", density);
731                         text_particleDensity->setText(core::stringw(message).c_str());
732                         // now, set the Sph density in the particle generator
733                         this->mgenerator->setSphDensity(density);
734                     }
735                     break;
736                 case gui::EGET_CHECKBOX_CHANGED:
737                     if (id == 2110) {
738                         wheelLocked = checkbox_wheelLocked->isChecked();
739                         GetLog() << checkbox_wheelLocked->isChecked() << "\n";
740                         // activate/deactivate motion for the wheel, truss and suspweight
741                         this->mwheel->wheel->SetBodyFixed(wheelLocked);
742                         this->mtester->suspweight->SetBodyFixed(wheelLocked);
743                         this->mtester->truss->SetBodyFixed(wheelLocked);
744                         return true;
745                     }
746                     if (id == 2111) {
747                         makeParticles = checkbox_createParticles->isChecked();
748                         GetLog() << checkbox_createParticles->isChecked() << "\n";
749                         // if checked, report the total # of particles
750                         char message[50];
751                         sprintf(message, "create Particles?: %d", this->mgenerator->nparticles());
752                         text_createParticles->setText(core::stringw(message).c_str());
753                         GetLog() << "total particle mass = " << this->mgenerator->particleMass() << "\n";
754                         return true;
755                     }
756                     if (id == 2112) {
757                         wheelCollision = checkbox_wheelCollision->isChecked();
758                         GetLog() << checkbox_wheelCollision->isChecked() << "\n";
759                         // activate/deactivate the wheel collision detection
760                         this->mwheel->wheel->SetCollide(wheelCollision);
761                         return true;
762                     }
763                     /*
764                     if (id == 2114) {
765                         pVisible = checkbox_particlesVisible->isChecked();
766                         GetLog() << checkbox_particlesVisible->isChecked() << "\n";
767                         // turn off the particle visibility
768                         this->mgenerator->toggleVisibility(pVisible);
769                         return true;
770                     }
771                     */
772                     /*
773                     if (id == 2115) {
774                         wheelVisible = checkbox_wheelVisible->isChecked();
775                         GetLog() << checkbox_wheelVisible->isChecked() << "\n";
776                         // turn wheel visibility on/off
777                         this->mwheel->toggleVisibility(wheelVisible);
778                         return true;
779                     }
780                     */
781                     break;
782                 default:
783                     break;
784             }
785         }
786 
787         return false;
788     }
789 
drawGrid()790     void drawGrid() {
791         // wall 1
792         ChCoordsys<> wall1Csys = this->mtester->wall1->GetCoord();
793         wall1Csys.rot = chrono::Q_from_AngAxis(CH_C_PI / 2.0, VECT_Y);
794         wall1Csys.pos.x() += .05;
795         tools::drawGrid(this->mapp->GetVideoDriver(), 0.1, 0.05, 24, 20, wall1Csys,
796                              video::SColor(255, 80, 130, 130), true);
797 
798         // wall 3
799         ChCoordsys<> wall3Csys = this->mtester->wall3->GetCoord();
800         wall3Csys.pos.z() += .05;
801         tools::drawGrid(this->mapp->GetVideoDriver(), 0.1, 0.05, 10, 20, wall3Csys,
802                              video::SColor(255, 80, 130, 130), true);
803 
804         // wall 4
805         ChCoordsys<> wall4Csys = this->mtester->wall4->GetCoord();
806         wall4Csys.pos.z() -= .05;
807         tools::drawGrid(this->mapp->GetVideoDriver(), 0.1, 0.05, 10, 20, wall4Csys,
808                              video::SColor(255, 80, 130, 130), true);
809     }
810 
811     // output any relevant test rig data here
drawWheelOutput()812     void drawWheelOutput() {
813         ChVector<> cm = mwheel->wheel->GetPos();
814         char messageCM[100];
815         sprintf(messageCM, "CM pos, x: %4.4g, y: %4.4g, z: %4.4g", cm.x(), cm.y(), cm.z());
816         text_cmPos->setText(core::stringw(messageCM).c_str());
817         // wheel CM vel
818         ChVector<> cmVel = mwheel->wheel->GetPos_dt();
819         char messageV[100];
820         sprintf(messageV, "CM vel, x: %4.4g, y: %4.4g, z: %4.4g", cmVel.x(), cmVel.y(), cmVel.z());
821         text_cmVel->setText(core::stringw(messageV).c_str());
822         // rxn. forces on spindle
823         ChVector<> rxnF = mtester->spindle->Get_react_force();
824         char messageF[100];
825         sprintf(messageF, "spindle Rxn. F, x: %4.3g, y: %4.3g, z: %4.3g", rxnF.x(), rxnF.y(), rxnF.z());
826         text_spindleForces->setText(core::stringw(messageF).c_str());
827         // rxn. torques on spindle
828         ChVector<> rxnT = mtester->spindle->Get_react_torque();
829         char messageT[100];
830         sprintf(messageT, "spindle Rxn. T, x: %4.3g, y: %4.3g, z: %4.3g", rxnT.x(), rxnT.y(), rxnT.z());
831         text_spindleTorque->setText(core::stringw(messageT).c_str());
832     }
833 
drawSoilOutput()834     void drawSoilOutput() {
835         std::vector<double> particleStats = this->mgenerator->getStatistics();
836         char messageRad[100];
837         sprintf(messageRad, "p Rad mean, std. dev: %4.4g, %4.4g", particleStats[0], particleStats[1]);
838         text_pRad->setText(core::stringw(messageRad).c_str());
839 
840         char messageMass[100];
841         sprintf(messageMass, "p mass mean, std. dev: %4.4g, %4.4g", particleStats[6], particleStats[7]);
842         text_pMass->setText(core::stringw(messageMass).c_str());
843     }
844 
845     // helper functions, these are called in the time step loop
getCurrentPsize() const846     double getCurrentPsize() const { return currParticleSize; }
getCurrentPdev() const847     double getCurrentPdev() const { return currParticleDev; }
createParticles() const848     bool createParticles() const { return makeParticles; }
849 
850     // try to generate some particles. Returne T/F if anything was created
genParticles()851     bool genParticles() {
852         return mgenerator->create_some_falling_items(currParticleSize, currParticleDev, currNparticlesGen, 0);
853     }
854 
855   private:
856     ChIrrApp* mapp;
857 
858     // bodies/joints
859     SoilbinWheel* mwheel;
860     TestMech* mtester;
861     ParticleGenerator* mgenerator;
862 
863     // for check boxes
864     bool wheelLocked;     // id = 2110
865     bool makeParticles;   // 2111
866     bool wheelCollision;  // 2112
867     bool pVisible;        // 2114
868     bool wheelVisible;    // 2115
869 
870     // particle size, deviation
871     double particleSize0;         // initial
872     double currParticleSize;      // current value
873     double particleDev0;          // initial
874     double currParticleDev;       // current val
875     double maxTorque;             // max torque applied to wheel
876     int nParticlesGenMax;         // max number of particles to generate
877     int currNparticlesGen;        // # of particles to generate this step
878     double currParticleFriction;  // coulomb friction coef, between 0-1
879     double avgDensity;            // input/average density for soil particles
880 
881     // menu items, checkboxes ids are: 2xxx
882     //	gui::IGUIContextMenu* menu;
883     gui::IGUICheckBox* checkbox_wheelLocked;  // ic = 2110
884     gui::IGUIStaticText* text_wheelLocked;
885     gui::IGUICheckBox* checkbox_createParticles;  // id = 2111
886     gui::IGUIStaticText* text_createParticles;
887     gui::IGUICheckBox* checkbox_wheelCollision;  // id = 2112
888     gui::IGUIStaticText* text_wheelCollision;
889     /*
890     gui::IGUICheckBox* checkbox_particlesVisible;  // id = 2114
891     gui::IGUIStaticText* text_particlesVisible;
892     gui::IGUICheckBox* checkbox_wheelVisible;  // id = 2115
893     gui::IGUIStaticText* text_wheelVisible;
894     */
895 
896     // scroll bars, ids are: 1xxx
897     IGUIScrollBar* scrollbar_pSize;  // particle size, id = 1101
898     IGUIStaticText* text_pSize;
899     IGUIScrollBar* scrollbar_pDev;  // deviation of mean particle size, id = 1102
900     IGUIStaticText* text_pDev;
901     IGUIScrollBar* scrollbar_torque;  // torque applied to wheel, id = 1103
902     IGUIStaticText* text_torque;
903     IGUIScrollBar* scrollbar_nParticlesGen;  // particles to spawn, id = 1104
904     IGUIStaticText* text_nParticlesGen;
905     IGUIScrollBar* scrollbar_particleFriction;  // friction coefficient of particles, id = 1105
906     IGUIStaticText* text_particleFriction;
907     IGUIScrollBar* scrollbar_particleDensity;  // particle density, id = 1106
908     IGUIStaticText* text_particleDensity;
909 
910     // output tabs, and their text boxes
911 
912     gui::IGUIStaticText* gad_text_wheelControls;
913     gui::IGUIStaticText* gad_text_pControls;
914 
915     gui::IGUIStaticText* gad_text_wheelState;  // panel for all wheel state output data
916     gui::IGUIStaticText* text_cmPos;
917     gui::IGUIStaticText* text_cmVel;
918     gui::IGUIStaticText* text_spindleForces;  // spindle reaction forces, torques
919     gui::IGUIStaticText* text_spindleTorque;
920 
921     gui::IGUIStaticText* gad_text_soilState;  // panel for all soil state output data
922     gui::IGUIStaticText* text_pRad;
923     gui::IGUIStaticText* text_pMass;
924 };
925 
main(int argc,char * argv[])926 int main(int argc, char* argv[]) {
927     GetLog() << "Copyright (c) 2017 projectchrono.org\nChrono version: " << CHRONO_VERSION << "\n\n";
928 
929     // Create a ChronoENGINE physical system
930     ChSystemNSC mphysicalSystem;
931 
932     // ** user input
933     double wheelMass = 5.0;  // mass of wheel
934     double suspMass = 10.0;  // mass of suspended weight
935 
936     // Create the Irrlicht visualization (open the Irrlicht device,
937     // bind a simple user interface, etc. etc.)
938     ChIrrApp application(&mphysicalSystem, L"Soil bin demo", core::dimension2d<u32>(1024, 768));
939     application.AddTypicalLogo();
940     application.AddTypicalSky();
941     application.AddTypicalLights(core::vector3df(20., 30., 25.), core::vector3df(25., 25., -25.), 65.0, 75.);
942     application.AddTypicalCamera(core::vector3df(3.5f, 2.5f, -2.4f));
943 
944     // ******* SOIL BIN WHEEL
945     // Create the wheel
946     ChVector<> wheelCMpos = ChVector<>(0, 0.5, 0);
947     ChVector<> wheelInertia = ChVector<>(1.0, 1.0, 1.0);
948     SoilbinWheel* mwheel = new SoilbinWheel(&mphysicalSystem, wheelCMpos, wheelMass, wheelInertia);
949 
950     // ***** TESTING MECHANISM
951     // now, create the testing mechanism and attach the wheel to it
952     double binWidth = 1.0;
953     double binLen = 2.4;
954     TestMech* mTestMechanism = new TestMech(&mphysicalSystem, mwheel->wheel, binWidth, binLen, suspMass, 2500., 10.);
955 
956     // ***** PARTICLE GENERATOR
957     // make a particle generator, that the sceneManager can use to easily dump a bunch of dirt in the bin
958     ParticleGenerator* mParticleGen = new ParticleGenerator(&application, &mphysicalSystem, binWidth, binLen);
959 
960     // Bind visualization assets.
961     application.AssetBindAll();
962     application.AssetUpdateAll();
963 
964     // ***** Create the User - GUI
965     double torqueMax = 50.;
966     MyEventReceiver receiver(&application, mwheel, mTestMechanism, mParticleGen, 0.02, 0.02, torqueMax);
967     // add a custom event receiver to the default interface:
968     application.SetUserEventReceiver(&receiver);
969 
970     // Set some integrator settings
971     // mphysicalSystem.SetSolverType(ChSolver::Type::APGD);
972     mphysicalSystem.SetSolverType(ChSolver::Type::PSOR);
973     mphysicalSystem.SetSolverMaxIterations(70);
974 
975     // Use real-time step of the simulation, OR...
976     application.SetTimestep(0.01);
977     application.SetTryRealtime(true);
978 
979     while (application.GetDevice()->run()) {
980         application.BeginScene(true, true, SColor(255, 140, 161, 192));
981         application.DrawAll();
982 
983         // draw the custom links
984         receiver.drawGrid();
985 
986         // output relevant soil, wheel data if the tab is selected
987         if (receiver.gad_tab_soil->isVisible())
988             receiver.drawSoilOutput();
989         if (receiver.gad_tab_wheel->isVisible())
990             receiver.drawWheelOutput();
991         receiver.drawWheelOutput();
992 
993         // apply torque to the wheel
994         mTestMechanism->applyTorque();
995 
996         application.DoStep();
997 
998         if (!application.GetPaused()) {
999             // add bodies to the system?
1000             if (receiver.createParticles()) {
1001                 receiver.genParticles();
1002             }
1003         }
1004 
1005         application.EndScene();
1006     }
1007 
1008     return 0;
1009 }
1010