Forklift example (demo_MBS_forklift.cpp)
Advanced tutorial.
Create a forklift and control it using the keyboard. The forklift collides with a pallet, that can be moved with the fork.
Learn about:
- how to use collisions for complex shapes
- how to use the Irrlicht GUI keyboard events
- how to create some constraints, including a simplified motor
- how to load .OBJ meshes for visualization etc.
// =============================================================================
// PROJECT CHRONO - http://projectchrono.org
//
// Copyright (c) 2014 projectchrono.org
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file at the top level of the distribution and at
// http://projectchrono.org/license-chrono.txt.
//
// =============================================================================
// Authors: Alessandro Tasora, Radu Serban
// =============================================================================
//
// Demo code about
// - modeling a complex mechanism: a forklift
// - loading .obj 3D meshes for 3d viewing
//
// =============================================================================
#include "chrono/assets/ChTexture.h"
#include "chrono/core/ChRealtimeStep.h"
#include "chrono/physics/ChBodyEasy.h"
#include "chrono/physics/ChLinkLockLinActuator.h"
#include "chrono/physics/ChLinkMotorRotationAngle.h"
#include "chrono/physics/ChLinkMotorRotationSpeed.h"
#include "chrono/physics/ChSystemNSC.h"
#include "chrono/core/ChRandom.h"
#include "chrono_irrlicht/ChVisualSystemIrrlicht.h"
// Use the namespaces of Chrono
using namespace chrono;
using namespace chrono::irrlicht;
// Use the main namespaces of Irrlicht
using namespace irr;
using namespace irr::core;
using namespace irr::scene;
using namespace irr::video;
using namespace irr::io;
using namespace irr::gui;
// Definition of the forklift MBS
class MySimpleForklift {
public:
// THE DATA
double throttle; // actual value 0...1 of gas throttle.
double steer; // actual value of steering
double lift; // actual value of fork lifting
// The parts making the forklift, as 3d Irrlicht scene nodes, each containing
// the ChBody object
// .. truss:
std::shared_ptr<ChBody> chassis;
// .. right front wheel:
std::shared_ptr<ChBody> wheelRF;
std::shared_ptr<ChLinkLockRevolute> link_revoluteRF;
// .. left front wheel:
std::shared_ptr<ChBody> wheelLF;
std::shared_ptr<ChLinkLockRevolute> link_revoluteLF;
// .. back wheel:
std::shared_ptr<ChBody> spindleB;
std::shared_ptr<ChBody> wheelB;
std::shared_ptr<ChLinkMotorRotationAngle> link_steer_engineB;
std::shared_ptr<ChLinkMotorRotationSpeed> link_engineB;
// ..the vertical arm
std::shared_ptr<ChBody> arm;
std::shared_ptr<ChLinkMotorRotationAngle> link_engineArm;
// ..the fork
std::shared_ptr<ChBody> fork;
std::shared_ptr<ChLinkLockLinActuator> link_actuatorFork;
std::shared_ptr<ChLinkLockPrismatic> link_prismaticFork;
// Build and initialize the forklift, creating all bodies corresponding to
// the various parts and adding them to the physical system - also creating
// and adding constraints to the system.
throttle = 0; // initially, gas throttle is 0.
steer = 0;
lift = 0;
ChVector3d COG_truss(0, 0.4, 0.5);
ChVector3d COG_wheelRF(-0.566, 0.282, 1.608);
ChVector3d COG_wheelLF(0.566, 0.282, 1.608);
ChVector3d COG_arm(0, 1.300, 1.855);
ChVector3d COG_fork(0, 0.362, 2.100);
ChVector3d COG_wheelB(0, 0.282, 0.003);
ChVector3d POS_pivotarm(0, 0.150, 1.855);
ChVector3d POS_prismatic(0, 0.150, 1.855);
double RAD_back_wheel = 0.28;
double RAD_front_wheel = 0.28;
// --- The car body ---
chassis = chrono_types::make_shared<ChBody>();
sys->Add(chassis);
chassis->SetPos(COG_truss);
chassis->SetMass(200);
chassis->SetInertiaXX(ChVector3d(100, 100, 100));
// collision properties:
auto chassis_mat = chrono_types::make_shared<ChContactMaterialNSC>();
auto cshape1 = chrono_types::make_shared<ChCollisionShapeBox>(chassis_mat, 1.227, 1.621, 1.864);
auto cshape2 = chrono_types::make_shared<ChCollisionShapeBox>(chassis_mat, 0.187, 0.773, 1.201);
auto cshape3 = chrono_types::make_shared<ChCollisionShapeBox>(chassis_mat, 0.187, 0.773, 1.201);
chassis->EnableCollision(true);
// visualization properties:
auto chassis_mesh = chrono_types::make_shared<ChVisualShapeModelFile>();
chassis_mesh->SetFilename(GetChronoDataFile("models/forklift/body.obj"));
// contact material shared among all wheels
auto wheel_mat = chrono_types::make_shared<ChContactMaterialNSC>();
// visualization shape, shared among all wheels
auto wheel_mesh = chrono_types::make_shared<ChVisualShapeModelFile>();
wheel_mesh->SetFilename(GetChronoDataFile("models/forklift/wheel.obj"));
// Front and back wheel collision shapes
auto wshapeF = chrono_types::make_shared<ChCollisionShapeCylinder>(wheel_mat, RAD_front_wheel, 0.2);
auto wshapeB = chrono_types::make_shared<ChCollisionShapeCylinder>(wheel_mat, RAD_back_wheel, 0.2);
// ..the right-front wheel
wheelRF = chrono_types::make_shared<ChBody>();
sys->Add(wheelRF);
wheelRF->SetPos(COG_wheelRF);
wheelRF->SetMass(20);
wheelRF->SetInertiaXX(ChVector3d(2, 2, 2));
// collision properties:
wheelRF->AddCollisionShape(wshapeF, ChFrame<>(VNULL, QuatFromAngleY(CH_PI / 2)));
wheelRF->EnableCollision(true);
// visualization properties:
// .. create the revolute joint between the wheel and the truss
link_revoluteRF = chrono_types::make_shared<ChLinkLockRevolute>(); // right, front, upper, 1
link_revoluteRF->Initialize(wheelRF, chassis, ChFrame<>(COG_wheelRF, chrono::QuatFromAngleY(CH_PI / 2)));
sys->AddLink(link_revoluteRF);
// ..the left-front wheel
wheelLF = chrono_types::make_shared<ChBody>();
sys->Add(wheelLF);
wheelLF->SetPos(COG_wheelLF);
wheelLF->SetRot(chrono::QuatFromAngleY(CH_PI)); // reuse RF wheel shape, flipped
wheelLF->SetMass(20);
wheelLF->SetInertiaXX(ChVector3d(2, 2, 2));
// collision properties:
auto shapeLF = chrono_types::make_shared<ChCollisionShapeCylinder>(wheel_mat, RAD_front_wheel, 0.2);
wheelLF->AddCollisionShape(wshapeF, ChFrame<>(VNULL, QuatFromAngleY(CH_PI / 2)));
wheelLF->EnableCollision(true);
// visualization properties:
// .. create the revolute joint between the wheel and the truss
link_revoluteLF = chrono_types::make_shared<ChLinkLockRevolute>(); // right, front, upper, 1
link_revoluteLF->Initialize(wheelLF, chassis, ChFrame<>(COG_wheelLF, chrono::QuatFromAngleY(CH_PI / 2)));
sys->AddLink(link_revoluteLF);
// ..the back steering spindle (invisible)
spindleB = chrono_types::make_shared<ChBody>();
sys->Add(spindleB);
spindleB->SetPos(COG_wheelB);
spindleB->SetMass(10);
spindleB->SetInertiaXX(ChVector3d(1, 1, 1));
// .. create the vertical steering link between the spindle structure and the truss
link_steer_engineB = chrono_types::make_shared<ChLinkMotorRotationAngle>();
link_steer_engineB->SetAngleFunction(chrono_types::make_shared<ChFunctionConst>(0));
link_steer_engineB->Initialize(spindleB, chassis, ChFrame<>(COG_wheelB, QuatFromAngleX(CH_PI_2)));
sys->AddLink(link_steer_engineB);
// ..the back wheel
wheelB = chrono_types::make_shared<ChBody>();
sys->Add(wheelB);
wheelB->SetPos(COG_wheelB);
wheelB->SetRot(chrono::QuatFromAngleY(CH_PI));
wheelB->SetMass(20);
wheelB->SetInertiaXX(ChVector3d(2, 2, 2));
// collision properties:
wheelB->AddCollisionShape(wshapeB, ChFrame<>(VNULL, QuatFromAngleY(CH_PI_2)));
wheelB->EnableCollision(true);
// visualization properties:
// .. create the motor between the back wheel and the steering spindle structure
link_engineB = chrono_types::make_shared<ChLinkMotorRotationSpeed>();
link_engineB->SetSpeedFunction(chrono_types::make_shared<ChFunctionConst>(0));
link_engineB->Initialize(wheelB, spindleB, ChFrame<>(COG_wheelB, chrono::QuatFromAngleY(CH_PI / 2)));
sys->AddLink(link_engineB);
// ..the arm
arm = chrono_types::make_shared<ChBody>();
sys->Add(arm);
arm->SetPos(COG_arm);
arm->SetMass(100);
arm->SetInertiaXX(ChVector3d(30, 30, 30));
// visualization properties:
auto arm_mesh = chrono_types::make_shared<ChVisualShapeModelFile>();
arm_mesh->SetFilename(GetChronoDataFile("models/forklift/arm.obj"));
// .. create the revolute joint between the arm and the truss
link_engineArm = chrono_types::make_shared<ChLinkMotorRotationAngle>();
link_engineArm->SetAngleFunction(chrono_types::make_shared<ChFunctionConst>(0));
link_engineArm->Initialize(arm, chassis, ChFrame<>(POS_pivotarm, chrono::QuatFromAngleY(CH_PI / 2)));
sys->AddLink(link_engineArm);
// ..the fork
auto fork_mat = chrono_types::make_shared<ChContactMaterialNSC>();
fork = chrono_types::make_shared<ChBody>();
sys->Add(fork);
fork->SetPos(COG_fork);
fork->SetMass(60);
fork->SetInertiaXX(ChVector3d(15, 15, 15));
// collision properties:
auto fshape1 = chrono_types::make_shared<ChCollisionShapeBox>(fork_mat, 0.100, 0.032, 1.033);
auto fshape2 = chrono_types::make_shared<ChCollisionShapeBox>(fork_mat, 0.100, 0.032, 1.033);
auto fshape3 = chrono_types::make_shared<ChCollisionShapeBox>(fork_mat, 0.344, 1.134, 0.101);
fork->EnableCollision(true);
// visualization properties:
auto fork_mesh = chrono_types::make_shared<ChVisualShapeModelFile>();
fork_mesh->SetFilename(GetChronoDataFile("models/forklift/forks.obj"));
// .. create the prismatic joint between the fork and arm
// (set joint as vertical; default would be aligned to z, horizontal)
link_prismaticFork = chrono_types::make_shared<ChLinkLockPrismatic>();
link_prismaticFork->Initialize(fork, arm, ChFrame<>(POS_prismatic, QuatFromAngleX(CH_PI / 2)));
sys->AddLink(link_prismaticFork);
// .. create the linear actuator that pushes upward the fork
link_actuatorFork = chrono_types::make_shared<ChLinkLockLinActuator>();
link_actuatorFork->Initialize(fork, arm, false, ChFrame<>(POS_prismatic + ChVector3d(0, 0.01, 0), QUNIT),
sys->AddLink(link_actuatorFork);
// ..a pallet
auto pallet_mat = chrono_types::make_shared<ChContactMaterialNSC>();
// Create a body with a mesh visualization and collision shape.
// In order to automatically infer mass and inertia properties, the mesh must be closed and watertight!
// Specify an inflation radius which can improve robustness of the collision detection.
auto pallet = chrono_types::make_shared<ChBodyEasyMesh>( //
300, // density
true, // automatic evaluation of inertia propserties
true, // enable visualization
true, // enable collision with mesh
pallet_mat, // contact material
0.001); // radius of mesh sweeping sphere
sys->Add(pallet);
pallet->SetPos(ChVector3d(0, 0.4, 3));
// apply a texture to the pallet:
pallet->GetVisualShape(0)->SetTexture(GetChronoDataFile("textures/cubetexture.png"));
// Move the forklift to initial offset position
chassis->Move(offset);
wheelRF->Move(offset);
wheelLF->Move(offset);
wheelB->Move(offset);
spindleB->Move(offset);
arm->Move(offset);
fork->Move(offset);
}
// Delete the car object, deleting also all bodies corresponding to
// the various parts and removing them from the physical system. Also
// removes constraints from the system.
~MySimpleForklift() {
ChSystem* mysystem = chassis->GetSystem();
mysystem->Remove(link_revoluteRF);
mysystem->Remove(link_revoluteLF);
mysystem->Remove(link_steer_engineB);
mysystem->Remove(link_engineB);
mysystem->Remove(link_engineArm);
mysystem->Remove(link_prismaticFork);
mysystem->Remove(link_actuatorFork);
mysystem->Remove(chassis);
mysystem->Remove(wheelRF);
mysystem->Remove(wheelLF);
mysystem->Remove(wheelB);
mysystem->Remove(spindleB);
mysystem->Remove(arm);
mysystem->Remove(fork);
}
};
// Define a MyEventReceiver class which will be used to manage input from the GUI graphical user interface.
class MyEventReceiver : public IEventReceiver {
public:
MyEventReceiver(MySimpleForklift* mlift) {
// store pointer to physical system & other stuff so we can tweak them by user keyboard
forklift = mlift;
}
bool OnEvent(const SEvent& event) {
// check if user presses keys
if (event.EventType == irr::EET_KEY_INPUT_EVENT && !event.KeyInput.PressedDown) {
switch (event.KeyInput.Key) {
case irr::KEY_KEY_Q:
if (auto mfun = std::dynamic_pointer_cast<ChFunctionConst>(
forklift->link_steer_engineB->GetAngleFunction()))
mfun->SetConstant(+0.3 + mfun->GetConstant());
return true;
case irr::KEY_KEY_W:
if (auto mfun = std::dynamic_pointer_cast<ChFunctionConst>(
forklift->link_steer_engineB->GetAngleFunction()))
mfun->SetConstant(-0.3 + mfun->GetConstant());
return true;
case irr::KEY_KEY_A:
if (auto mfun =
std::dynamic_pointer_cast<ChFunctionConst>(forklift->link_engineB->GetSpeedFunction()))
mfun->SetConstant(0.5 + mfun->GetConstant());
return true;
case irr::KEY_KEY_Z:
if (auto mfun =
std::dynamic_pointer_cast<ChFunctionConst>(forklift->link_engineB->GetSpeedFunction()))
mfun->SetConstant(-0.5 + mfun->GetConstant());
return true;
case irr::KEY_KEY_S:
if (auto mfun = std::dynamic_pointer_cast<ChFunctionConst>(
forklift->link_actuatorFork->GetActuatorFunction()))
mfun->SetConstant(0.05 + mfun->GetConstant());
return true;
case irr::KEY_KEY_X:
if (auto mfun = std::dynamic_pointer_cast<ChFunctionConst>(
forklift->link_actuatorFork->GetActuatorFunction()))
mfun->SetConstant(-0.05 + mfun->GetConstant());
return true;
case irr::KEY_KEY_D:
if (auto mfun =
std::dynamic_pointer_cast<ChFunctionConst>(forklift->link_engineArm->GetAngleFunction()))
mfun->SetConstant(0.005 + mfun->GetConstant());
return true;
case irr::KEY_KEY_C:
if (auto mfun =
std::dynamic_pointer_cast<ChFunctionConst>(forklift->link_engineArm->GetAngleFunction()))
mfun->SetConstant(-0.005 + mfun->GetConstant());
return true;
default:
break;
}
}
return false;
}
private:
MySimpleForklift* forklift;
};
// -----------------------------------------------------------------------------
int main(int argc, char* argv[]) {
std::cout << "Copyright (c) 2017 projectchrono.org\nChrono version: " << CHRONO_VERSION << std::endl;
// Create a Chrono physical system
ChSystemNSC sys;
// Contact material for ground
auto ground_mat = chrono_types::make_shared<ChContactMaterialNSC>();
ground_mat->SetFriction(1.0f);
// ..the world
auto my_ground = chrono_types::make_shared<ChBodyEasyBox>(40, 2, 40, 1000, true, true, ground_mat);
sys.Add(my_ground);
my_ground->SetFixed(true);
my_ground->SetPos(ChVector3d(0, -1, 0));
my_ground->GetVisualShape(0)->SetTexture(GetChronoDataFile("textures/concrete.jpg"));
// ..some obstacles on the ground:
for (int i = 0; i < 6; i++) {
auto my_obstacle = chrono_types::make_shared<ChBodyEasyBox>(1, 0.5, 1, 200, true, true, ground_mat);
sys.Add(my_obstacle);
my_obstacle->SetPos(ChVector3d(20 * ChRandom::Get(), 2, 20 * ChRandom::Get()));
my_obstacle->GetVisualShape(0)->SetTexture(GetChronoDataFile("textures/cubetexture_wood.png"));
}
// ..the forklift (this class - see above - is a 'set' of bodies and links, automatically added at creation)
MySimpleForklift* myforklift = new MySimpleForklift(&sys);
// Create the Irrlicht visualization system
auto vis = chrono_types::make_shared<ChVisualSystemIrrlicht>();
vis->AttachSystem(&sys);
vis->SetWindowSize(800, 600);
vis->SetWindowTitle("Forklift demo");
vis->Initialize();
vis->AddLogo();
vis->AddSkyBox();
vis->AddTypicalLights();
vis->AddCamera(ChVector3d(-6, 3, -6));
// Create some graphical-user-interface (GUI) items to show on the screen.
// This requires an event receiver object -see above.
// This is for GUI tweaking of system parameters..
MyEventReceiver receiver(myforklift);
// note how to add a custom event receiver to the default interface:
vis->AddUserEventReceiver(&receiver);
// add text with info
vis->GetGUIEnvironment()->addStaticText(L"Keys: steer=Q,W; throttle=A,Z; lift=S,X; bank=D,C",
rect<s32>(150, 10, 430, 40), true);
// Solver settings
sys.GetSolver()->AsIterative()->SetMaxIterations(20);
// Simulation loop
double timestep = 0.005;
ChRealtimeStepTimer realtime_timer;
while (vis->Run()) {
vis->BeginScene();
vis->Render();
// Advance the simulation time step
sys.DoStepDynamics(timestep);
vis->EndScene();
realtime_timer.Spin(timestep);
}
if (myforklift)
delete myforklift;
return 0;
}
void AddTypicalLights()
Simple shortcut to set two point lights in the scene.
Definition: ChVisualSystemIrrlicht.cpp:344
std::string GetChronoDataFile(const std::string &filename)
Get the full path to the specified filename, given relative to the Chrono data directory (thread safe...
Definition: ChGlobal.cpp:37
ChQuaterniond QuatFromAngleX(double angle)
Convert from a rotation about X axis to a quaternion.
Definition: ChRotation.cpp:188
void Add(std::shared_ptr< ChPhysicsItem > item)
Attach an arbitrary ChPhysicsItem (e.g.
Definition: ChSystem.cpp:197
virtual std::shared_ptr< ChSolver > GetSolver()
Access the solver currently associated with this system.
Definition: ChSystem.h:124
virtual void Render() override
Draw all 3D shapes and GUI elements at the current frame.
Definition: ChVisualSystemIrrlicht.cpp:570
virtual void Initialize() override
Initialize the visualization system.
Definition: ChVisualSystemIrrlicht.cpp:181
void AddSkyBox(const std::string &texture_dir=GetChronoDataFile("skybox/"))
Add a sky box in a 3D scene.
Definition: ChVisualSystemIrrlicht.cpp:357
const ChQuaterniond QUNIT(1., 0., 0., 0.)
Constant unit quaternion: {1, 0, 0, 0} , corresponds to no rotation (diagonal rotation matrix)
Definition: ChQuaternion.h:431
virtual void AddLink(std::shared_ptr< ChLinkBase > link)
Attach a link to the underlying assembly.
Definition: ChSystem.cpp:145
void Remove(std::shared_ptr< ChPhysicsItem > item)
Remove arbitrary ChPhysicsItem that was added to the underlying assembly.
Definition: ChSystem.cpp:221
ChQuaterniond QuatFromAngleY(double angle)
Convert from a rotation about Y axis to a quaternion.
Definition: ChRotation.cpp:192
Class for a timer which attempts to enforce soft real-time.
Definition: ChRealtimeStep.h:25
Projected SOR (Successive Over-Relaxation)
void Spin(double step)
Call this function INSIDE the simulation loop, just ONCE per loop (preferably as the last call in the...
Definition: ChRealtimeStep.h:34
virtual void EndScene() override
End the scene draw at the end of each animation frame.
Definition: ChVisualSystemIrrlicht.cpp:560
virtual void BeginScene() override
Perform any necessary operations at the beginning of each rendering frame.
Definition: ChVisualSystemIrrlicht.cpp:543
virtual int AddCamera(const ChVector3d &pos, ChVector3d targ=VNULL) override
Add a camera in an Irrlicht 3D scene.
Definition: ChVisualSystemIrrlicht.cpp:290
void AddUserEventReceiver(irr::IEventReceiver *receiver)
Attach a custom event receiver to the application.
Definition: ChVisualSystemIrrlicht.cpp:438
void SetSolverType(ChSolver::Type type)
Choose the solver type, to be used for the simultaneous solution of the constraints in dynamical simu...
Definition: ChSystem.cpp:250
int DoStepDynamics(double step_size)
Advance the dynamics simulation by a single time step of given length.
Definition: ChSystem.cpp:1634
Bullet-based collision detection system.
ChVector3< double > ChVector3d
Alias for double-precision vectors.
Definition: ChVector3.h:283
void SetWindowTitle(const std::string &win_title)
Set the windoiw title (default "").
Definition: ChVisualSystemIrrlicht.cpp:138
virtual void AttachSystem(ChSystem *sys) override
Attach another Chrono system to the run-time visualization system.
Definition: ChVisualSystemIrrlicht.cpp:165
virtual bool Run() override
Run the Irrlicht device.
Definition: ChVisualSystemIrrlicht.cpp:243
void AddLogo(const std::string &logo_filename=GetChronoDataFile("logo_chronoengine_alpha.png"))
Add a logo in a 3D scene.
Definition: ChVisualSystemIrrlicht.cpp:336
virtual void SetCollisionSystemType(ChCollisionSystem::Type type)
Set the collision detection system used by this Chrono system to the specified type.
Definition: ChSystem.cpp:325
Class for a physical system in which contact is modeled using a non-smooth (complementarity-based) me...
Definition: ChSystemNSC.h:29
void SetWindowSize(unsigned int width, unsigned int height)
Set the window size (default 640x480).
Definition: ChVisualSystemIrrlicht.cpp:134