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