fourbar Tutorial: demo_fourbar.cpp

This tutorial is similar to the demo_crank example, but here a small user-interface is created, so that the user can play with a slider to modify the speed of the motor. The user interface is created with simple GUI features of Irrlicht, but other kind of interfaces/visualization systems could be used.
Here you can also learn
- how to make a four-bar linkage
- an example of how to plot/fetch data during the simulation.

Let's start
First of all, include some headers needed for this example, use required namespaces, initialize the library, etc:
 
#include "physics/CHapidll.h" 
#include "physics/CHsystem.h"
#include "irrlicht_interface/CHbodySceneNode.h"
#include "irrlicht_interface/CHbodySceneNodeTools.h" 
#include "irrlicht_interface/CHdisplayTools.h" 
#include "irrlicht_interface/CHirrWizard.h" 
 
#include <irrlicht.h>

 
// Use the namespace of Chrono

using namespace chrono;

// Use the main namespaces of Irrlicht
using namespace irr;

using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

Some static data: the engine speed. (Using static data is not reccomended in big applications, but this is just a small demo, and makes things easier).

IGUIStaticText* text_enginespeed = 0;

The MyEventReceiver class will be used to manage input from the GUI graphical user interface (the interface will be created with the basic -yet flexible- platform independent toolset of Irrlicht). See the Irrlicht documentation for additional informations on how to create and manage user interfaces. In this case, only the slider event is monitored: each time the user plays with the slider, the value is used to modify the speed of the engine, with mengine->Get_spe_funct()->Set_yconst(newspeed); assuming that the engine is already in 'imposed speed mode'.


class MyEventReceiver : public IEventReceiver
{
public:

	MyEventReceiver(ChSystem* asystem,  
					IrrlichtDevice *adevice,
					ChSharedPtr<ChLinkEngine> aengine)
	{
		// store pointer to physical system & other stuff so we can tweak them by user keyboard
		msystem = asystem;
		mdevice = adevice;
		mengine = aengine;	
	}

	bool OnEvent(SEvent event)
	{
		// check if user moved the sliders with mouse..
		if (event.EventType == EET_GUI_EVENT)
		{
			s32 id = event.GUIEvent.Caller->getID();
			IGUIEnvironment* env = mdevice->getGUIEnvironment();

			switch(event.GUIEvent.EventType)
			{
			case EGET_SCROLL_BAR_CHANGED:
					if (id == 101) // id of 'engine speed' slider..
					{
						s32 pos = ((IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
						double newspeed = 10*(double)pos/100.0;
						// set the speed into engine object
						mengine->Get_spe_funct()->Set_yconst(newspeed);
						// show speed as formatted text in interface screen
						char message[50]; sprintf(message,"Engine speed: %g [rad/s]",newspeed);
						text_enginespeed->setText(core::stringw(message).c_str());
					}
			break;
			}
			
		} 


		return false;
	}

private:
	ChSystem*       msystem;
	IrrlichtDevice* mdevice;
	ChSharedPtr<ChLinkEngine> mengine;
};


The following is the main function. It begins by creating the Irrlicht device, for the visualization, with proper lights and camera, as usual.



	// In CHRONO engine, The DLL_CreateGlobals() - DLL_DeleteGlobals(); pair is needed if
	// global functions are needed.
	ChGlobals* GLOBAL_Vars = DLL_CreateGlobals();

	// Create the IRRLICHT context (device, etc.)
	IrrlichtDevice* device = createDevice(video::EDT_DIRECT3D9, core::dimension2d<s32>(640, 480));
	if (device == 0)
	{
		GetLog() << "Cannot use DirectX - switch to OpenGL \n"; 
		device = createDevice(video::EDT_OPENGL, core::dimension2d<s32>(640, 480));
		if (!device) return 1;
	}

	device->setWindowCaption(L"Example of integration of Chrono::Engine and Irrlicht, with GUI");

	// Easy shortcuts to add logo, camera, lights and sky in Irrlicht scene:
	ChIrrWizard::add_typical_Logo(device);
	ChIrrWizard::add_typical_Sky(device);
	ChIrrWizard::add_typical_Lights(device);
	ChIrrWizard::add_typical_Camera(device);
 
Now create the mechanical system with Chrono::Engine. It consists of a four-bar mechanism, moved by a motorized crank. There will be four rigid bodies (one is used as the truss) and four constraints (one is the engine).


	// 1- Create a ChronoENGINE physical system: all bodies and constraints
	//    will be handled by this ChSystem object.
	ChSystem my_system;

Now create the mechanical system with Chrono::Engine. It consists of a four-bar mechanism, moved by a motorized crank. There will be four rigid bodies (one is used as the truss) and four constraints (one is the engine).
First of all, create the physical system. If you want you can set the parameters for the physical engine, if you don't like the default values.

Maybe you want to use the direct solver: it is a 'direct' pivoting solver which is more precise than the default 'iterative' solver, but slower (so it is good for simple systems as in this example, but NOT for collisions!). If you need it, simply write
    my_system.SetLcpSolverType(ChSystem::LCP_SIMPLEX);
instead of
    my_system.SetLcpSolverType(ChSystem::LCP_ITERATIVE_SOR);
(note that the iterative solver is the default one).

Also, you can chose the integration method (the stepper), using functions like
    my_system.SetIntegration(INT_ANITESCU);
(to chose the Anitescu method, which performs a single LCP per step, with automatic constraint stabilization) or:
    my_system.SetIntegration(INT_TASORA);
(to chose the Tasora method, which performs a LCP for the speed-impulse problem, followed by a LCP for the position problem, hence satisfying constraints without the need of a stabilization term, giving less 'boucy' behaviour when constraints are not well stabilized).
	my_system.SetIntegration(INT_TASORA); // <-- separate constraint post-stabilization as pos.LCP
	
 
	// 2- Create the rigid bodies of the four-bar mechanical system
	//   (a flywheel, a rod, a rocker, a truss), maybe setting 
	//   position/mass/inertias of their center of mass (COG) etc.
	
	// ..the truss
	ChSharedBodyPtr  my_body_A(new ChBody);   
	my_system.AddBody(my_body_A);
	my_body_A->SetBodyFixed(true);			// truss does not move!

	// ..the flywheel
	ChSharedBodyPtr  my_body_B(new ChBody);	  
	my_system.AddBody(my_body_B);
	my_body_B->SetPos(ChVector<>(0,0,0));	// position of COG of flywheel

	// ..the rod
	ChSharedBodyPtr  my_body_C(new ChBody);	 
	my_system.AddBody(my_body_C);
	my_body_C->SetPos(ChVector<>(4,0,0));	// position of COG of rod

	// ..the rocker
	ChSharedBodyPtr  my_body_D(new ChBody);	 
	my_system.AddBody(my_body_D);
	my_body_D->SetPos(ChVector<>(8,-4,0));	// position of COG of rod
 

	// 3- Create constraints: the mechanical joints between the 
	//    rigid bodies. Doesn't matter if some constraints are redundant.
    
	// .. an engine between flywheel and truss	
	ChSharedPtr<ChLinkEngine> my_link_AB(new ChLinkEngine);
	my_link_AB->Initialize(my_body_A, my_body_B, ChCoordsys<>(ChVector<>(0,0,0)));
	my_link_AB->Set_eng_mode(ChLinkEngine::ENG_MODE_SPEED);
	my_link_AB->Get_spe_funct()->Set_yconst(CH_C_PI); // speed w=3.145 rad/sec
	my_system.AddLink(my_link_AB);

	// .. a revolute joint between flywheel and rod
	ChSharedPtr<ChLinkLockRevolute>  my_link_BC(new ChLinkLockRevolute);
	my_link_BC->Initialize(my_body_B, my_body_C, ChCoordsys<>(ChVector<>(2,0,0)));
	my_system.AddLink(my_link_BC);

	// .. a revolute joint between rod and rocker
	ChSharedPtr<ChLinkLockRevolute> my_link_CD(new ChLinkLockRevolute);
	my_link_CD->Initialize(my_body_C, my_body_D, ChCoordsys<>(ChVector<>(8,0,0)));
	my_system.AddLink(my_link_CD);

	// .. a revolute joint between rocker and truss
	ChSharedPtr<ChLinkLockRevolute> my_link_DA(new ChLinkLockRevolute);
	my_link_DA->Initialize(my_body_D, my_body_A, ChCoordsys<>(ChVector<>(8,-8,0)));
	my_system.AddLink(my_link_DA);


  
Prepare some graphical-user-interface (GUI) items to show on the screen (mostly, a slider and a text showing the rotation speed of the engine).


	// ..add a GUI text and GUI slider to control motor of mechanism via mouse
	text_enginespeed         = device->getGUIEnvironment()->addStaticText(
					L"Engine speed:", rect<s32>(10,85,150,100), false);
	IGUIScrollBar* scrollbar = device->getGUIEnvironment()->addScrollBar(
					true, rect<s32>(10, 105, 150, 120), 0, 101);
	scrollbar->setMax(100);

	// ..Finally create the event receiver, for handling all the GUI (user will use
	//   buttons/sliders to modify parameters)
	MyEventReceiver receiver(&my_system, device, my_link_AB);
	device->setEventReceiver(&receiver);


This is the endless loop for showing the motion of the four-bar mechanism. Note that the mechanism is simply drawn with a wireframe, that is a polygonal line passing through the four corners of the mechanism. Also, a trajectory is drawn on the screen, showing the motion of a point belonging to the rod.


	// use this array of points to store trajectory of a rod-point
	std::vector<chrono::ChVector<>> mtrajectory; 

	while(device->run())
	{ 
		// Irrlicht must prepare frame to draw
		device->getVideoDriver()->beginScene(true, true, SColor(255,140,161,192));
	
  
		// Irrlicht now draws simple lines in 3D world representing a 
		// skeleton of the mechanism, in this instant:
		//
		// .. draw items belonging to Irrlicht scene, if any
		device->getSceneManager()->drawAll();
		// .. draw GUI items belonging to Irrlicht screen, if any
		device->getGUIEnvironment()->drawAll();

		// .. draw a grid
		ChIrrTools::drawGrid(device->getVideoDriver(), 0.5, 0.5);
		// .. draw a circle representing flywheel
		ChIrrTools::drawCircle(device->getVideoDriver(), 2.1, ChCoordsys<>(ChVector<>(0,0,0), QUNIT));
		// .. draw a small circle representing joint BC
		ChIrrTools::drawCircle(device->getVideoDriver(), 0.06, ChCoordsys<>(my_link_BC->GetMarker1()->GetAbsCoord().pos, QUNIT));
		// .. draw a small circle representing joint CD
		ChIrrTools::drawCircle(device->getVideoDriver(), 0.06, ChCoordsys<>(my_link_CD->GetMarker1()->GetAbsCoord().pos, QUNIT));
		// .. draw a small circle representing joint DA
		ChIrrTools::drawCircle(device->getVideoDriver(), 0.06, ChCoordsys<>(my_link_DA->GetMarker1()->GetAbsCoord().pos, QUNIT));
		// .. draw the rod (from joint BC to joint CD)
		ChIrrTools::drawSegment(device->getVideoDriver(), 
			my_link_BC->GetMarker1()->GetAbsCoord().pos, 
			my_link_CD->GetMarker1()->GetAbsCoord().pos,
			video::SColor(255,   0,255,0));
		// .. draw the rocker (from joint CD to joint DA)
		ChIrrTools::drawSegment(device->getVideoDriver(), 
			my_link_CD->GetMarker1()->GetAbsCoord().pos, 
			my_link_DA->GetMarker1()->GetAbsCoord().pos,
			video::SColor(255, 255,0,0));
		// .. draw the trajectory of the rod-point
		ChIrrTools::drawPolyline(device->getVideoDriver(), 
			mtrajectory, video::SColor(255, 0,150,0));

 
		// HERE CHRONO INTEGRATION IS PERFORMED: THE 
		// TIME OF THE SIMULATION ADVANCES FOR A SINGLE
		// STEP:
		my_system.DoStepDynamics(0.01);

		// We need to add another point to the array of 3d
		// points describing the trajectory to be drawn..
		mtrajectory.push_back(my_body_C->Point_Body2World(&ChVector<>(1,1,0)));
		// keep only last 150 points..
		if (mtrajectory.size()>150) mtrajectory.erase(mtrajectory.begin());

		// Irrlicht must finish drawing the frame
		device->getVideoDriver()->endScene(); 

	}
	
When the while() loop is ended (maybe because the user pressed the 'close window' button), you must delete all Irrlicht items and Chrono::Engine items, before exiting. You do NOT need to delete rigid bodies and links (all things created with smart shared pointers do not need the delete()!). Simply do the following.
	
	

	// This safely delete every Irrlicht item..
	device->getVideoDriver()->drop();


	// Remember this at the end of the program, if you started
	// with DLL_CreateGlobals();
	DLL_DeleteGlobals();

	return 0;
}
that's all.