Click here to Skip to main content
15,892,839 members
Articles / Desktop Programming / MFC

2D LUA Based Robot Simulator

Rate me:
Please Sign up or sign in to vote.
4.89/5 (26 votes)
14 Apr 2014Public Domain9 min read 131K   7.9K   119  
An article on designing your own robot simulator
		// Author : Auralius Manurung
// Contact : auralius@lavabit.com

#include "StdAfx.h"
#include "math.h"
#include "Robot.h"

///////////////////////////////////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS AND DESTRUCTORS ///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////

CRobot::CRobot(void)
{
	// Default setting when CRobot is created

	mCanDrawTrajectory = false;
	mZoom = 1;

	SetSensorNum(6); // 6 sensors
	SetRadiationCone();
	SetSensorRange();
	SetNoiseProperties(0.0, 0.0);
	SetSimulationTimeStep();

	AddRobot(CPoint(200, 200));
	SetActiveRobot(0);
}

CRobot::~CRobot(void)
{
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE MEMBERS/////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////

inline float CRobot::UniformNoise()
{
	return ((float)rand()/(float)(RAND_MAX+1));
}

float CRobot::GaussianNoise(const float &mean, const float &deviation)
{
	// Press W.H., Teukolsky S.A., Vetterling W.T., Flannery B.P. Numerical recipes in C, pp 289-290
	if (mean == 0.0 && deviation == 0.0)
		return 0.0;

	float x,v1,v2,r;
	if (mNoiseTime == 0.0){
		do {
			v1 = 2.0 * UniformNoise() - 1.0;
			v2 = 2.0 * UniformNoise() - 1.0;
			r = v1 * v1 + v2 * v2;
		} while (r >= 1.0);

		r = sqrt((-2.0 * log(r)) / r);
		mNoiseTime = v2 * r;
		return (mean + v1 * r * deviation);
	}
		else{
			x = mNoiseTime;
			mNoiseTime = 0.0;
			return (mean + x * deviation);
		}
}

inline CPoint CRobot::Rotate(CPoint center_pt, float dist_from_center_pt, float theta)
{
	return CPoint(dist_from_center_pt * cos(theta) + center_pt.x, dist_from_center_pt * sin(theta) + center_pt.y);
}

inline float CRobot::CalcDistance(CPoint pt1, CPoint pt2)
{
	return sqrt(pow((float)(pt2.x - pt1.x), 2) + pow((float)(pt2.y - pt1.y), 2));
}

void CRobot::UpdateSensorValue(CDC* pDC, ROBOT *robot, float accuracy)
{
	int pos_x = robot->XPos;
	int pos_y = robot->YPos;

	float sensor_dist = robot->Size / 2 - 3;
	float step = 2 * M_PI / mSensorNum;

	for (int sensor_index = 0; sensor_index < mSensorNum; sensor_index++){
		pDC->MoveTo(pos_x, pos_y);
		float min_dist = mSensorRange[MAX_VAL];
		float pivot_angle = robot->Theta + sensor_index * step;

		// Calculate sensor position 
		CPoint sensor_position = Rotate(CPoint(pos_x, pos_y), sensor_dist, pivot_angle);
			
		// measure
		for (int i = 0; i <= 2; i++){
			float dist = 0.0;
			while (dist <= mSensorRange[MAX_VAL]){
				dist = dist + accuracy;
				CPoint pt = Rotate(sensor_position, dist, pivot_angle - mRadiationCone + i * mRadiationCone);
				dist = CalcDistance(sensor_position, pt);
				if (IsObstacleDetected(pDC, pt)){
					if (dist > 5) // minimum sensor reading
						break;
				}
			}
			if (dist < min_dist)
				min_dist = dist;
		}

		// Should not smaller than desired minimum value
		if (min_dist >=  mSensorRange[MIN_VAL])
			robot->SensorValue[sensor_index] = min_dist + GaussianNoise(mMeanNoise,mDevNoise);
		else
			robot->SensorValue[sensor_index] = mSensorRange[MIN_VAL];

		CPen sensor_pen(PS_DOT, 1, RGB(255, 0, 0));
		CPen *old_pen = pDC->SelectObject(&sensor_pen);

		// draw sensor beam
		if (min_dist < mSensorRange[MAX_VAL]){
			pDC->MoveTo(sensor_position);
			pDC->LineTo(Rotate(sensor_position, min_dist, pivot_angle - mRadiationCone));
			pDC->LineTo(Rotate(sensor_position, min_dist, pivot_angle + mRadiationCone));
			pDC->LineTo(sensor_position);
		}

		pDC->SelectObject(old_pen);
	}
}

bool CRobot::IsObstacleDetected(CDC* pDC, CPoint pt)
{
	//if (rgb > (OBSTACLE - 10000) && rgb < (OBSTACLE + 10000)) // Make it safe
	if (pDC->GetPixel(pt.x, pt.y) == OBSTACLE)
		return true;

	return false;
}

void CRobot::UpdateTrajectoryData(ROBOT *robot)
{
	CPoint pos = CPoint(robot->XPos, robot->YPos);
	UINT size = robot->Trail.size();
	
	if (size > 0){
		if ( CalcDistance(pos, robot->Trail.at(size - 1)) > 5.0)
			robot->Trail.push_back(pos);
	}
	else
		robot->Trail.push_back(pos);
}

void CRobot::DrawTrajectory(CDC *pDC, ROBOT *robot)
{
	UINT size = robot->Trail.size();

	// Draw if not empty
	if (size > 1)
	{
		CPen trail_pen(PS_DOT, 2, robot->RobotColor);
		CPen *old_pen = pDC->SelectObject(&trail_pen);
		for (UINT count = 1; count < size; count++)
		{
			CPoint start = robot->Trail.at(count-1);
			CPoint end = robot->Trail.at(count);

			pDC->MoveTo(start);
			if (CalcDistance(start, end) < 10)
				pDC->LineTo(end);
		}
		pDC->SelectObject(old_pen);
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC MEMBERS//////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////

void CRobot::AddRobot(CPoint initial_pos)
{
	ROBOT new_robot;
	new_robot.XPos = initial_pos.x;
	new_robot.YPos = initial_pos.y;
	new_robot.Theta = 0;
	new_robot.Size = 40 * mZoom;
	new_robot.LWheelSpeed = 0;
	new_robot.RWheelSpeed = 0;
	new_robot.HittingWall = false;

	// Random initial color
	COLORREF random_color;
	srand(mVectRobot.size()+time(0));
	do{
		random_color = RGB(UniformNoise()*255, UniformNoise()*255, UniformNoise()*255);
		
	} while (random_color < RGB(255,255,255)/2); // Get rid off dark colors

	new_robot.RobotColor = random_color;
	mVectRobot.push_back(new_robot);
}

void CRobot::RemoveRobot()
{
	if (mVectRobot.size() > 1)
		mVectRobot.erase(mVectRobot.end()-1);
}

void CRobot::SetActiveRobot(UINT index)
{
	if (index < mVectRobot.size()){
		mActiveRobot = index;
		mRobot = &mVectRobot.at(index);
	}
}

void CRobot::DrawRobot(CDC* pDC)
{
	const float deg45 = M_PI / 4;
	const float deg135 = M_PI / 4 * 3;
	const float deg15 = M_PI / 180 * 15;

	for (UINT count = 0; count < mVectRobot.size(); count++){
		ROBOT *robot = &mVectRobot.at(count);
		float robot_radius = robot->Size / 2;
		CPoint pos = CPoint(robot->XPos, robot->YPos);

		pDC->MoveTo(pos);

		// Draw robot
		CPen robot_pen(PS_SOLID, 2, OBSTACLE);
		CPen *old_pen = pDC->SelectObject(&robot_pen);

		CBrush brush;
		brush.CreateSolidBrush(robot->RobotColor);
		CBrush *old_brush = pDC->SelectObject(&brush);

		pDC->Ellipse(robot->XPos - robot_radius, robot->YPos - robot_radius, robot->XPos + robot_radius, robot->YPos + robot_radius);	

		pDC->SelectObject(old_brush);
		
		int l = robot_radius - 4;

		// For wheels
		CPoint pt1 = Rotate(pos, l, robot->Theta + deg45);
		CPoint pt2 = Rotate(pos, l, robot->Theta + deg135);
		CPoint pt3 = Rotate(pos, l, robot->Theta - deg45 );
		CPoint pt4 = Rotate(pos, l, robot->Theta - deg135);

		// For Head
		CPoint pt0 = Rotate(pos, robot_radius, robot->Theta);
		CPoint pt5 = Rotate(pos, l, robot->Theta + deg15 );
		CPoint pt6 = Rotate(pos, l, robot->Theta - deg15);
		
		// Draw head
		pDC->MoveTo(pt5);
		pDC->LineTo(pt0);
		pDC->LineTo(pt6);

		// Draw wheels
		pDC->MoveTo(pt1);
		pDC->LineTo(pt2);
		pDC->MoveTo(pt3);
		pDC->LineTo(pt4);

		pDC->SelectObject(old_pen);
	}

	for (UINT count = 0; count < mVectRobot.size(); count++){
		ROBOT *robot = &mVectRobot.at(count);
		DrawTrajectory(pDC, robot);
		robot->HittingWall = SimpleCollisionDetection(robot, pDC);
		UpdateSensorValue(pDC, robot);
	}
}

void CRobot::SetDrawTrajectoryStatus(bool status)
{
	mCanDrawTrajectory = status;

	if (!status)
	for (UINT count = 0; count < mVectRobot.size(); count++){
		ROBOT *robot = &mVectRobot.at(count);
		robot->Trail.clear();
	}
}

void CRobot::SetSimulationTimeStep(float time_step)
{
	mSimulationTimeStep = time_step;
}

void CRobot::GoRobotGo()
{
	for (UINT count = 0; count < mVectRobot.size(); count++){
		ROBOT *robot = &mVectRobot.at(count);
		if (!robot->HittingWall){
			float plus_factor = robot->RWheelSpeed + robot->LWheelSpeed;
			float minus_factor = robot->LWheelSpeed - robot->RWheelSpeed;
			float half_plus_factor = plus_factor / 2;
			float a = robot->Size / 2 * plus_factor / minus_factor;
			float b = robot->Theta + minus_factor * mSimulationTimeStep / robot->Size;
			float cos_theta = cos(robot->Theta);
			float sin_theta = sin(robot->Theta);

			if (minus_factor != 0.0){
				robot->Theta =  b;
				robot->XPos = robot->XPos + a * (sin(b) - sin_theta);
				robot->YPos = robot->YPos - a * (cos(b) - cos_theta);
			}
			else{
				robot->XPos = half_plus_factor * cos_theta * mSimulationTimeStep + robot->XPos;
				robot->YPos = half_plus_factor * sin_theta * mSimulationTimeStep + robot->YPos;
			}

			if (mCanDrawTrajectory)
				UpdateTrajectoryData(robot);
		}
	}
}

void CRobot::SetSpeed(float l_speed, float r_speed)
{
		mRobot->LWheelSpeed = l_speed * mZoom;
		mRobot->RWheelSpeed = r_speed * mZoom;
}

void CRobot::PrepareToRun()
{
	// Random seed
	srand(time(0));
	mNoiseTime = 0.0;
}

void CRobot::SetPosition(CPoint pt)
{
	mRobot->XPos = pt.x;
	mRobot->YPos = pt.y;
}

void CRobot::ResetPosition()
{
	 // Line up the robot horizontally
	for (UINT count = 0; count < mVectRobot.size(); count++){
		ROBOT *robot = &mVectRobot.at(count);
		robot->XPos = 100 + (robot->Size + 10) * count;
		robot->YPos = 100;
	}
}

void CRobot::SetAngle(float theta)
{
	mRobot->Theta = theta;
}

void CRobot::SetRadiationCone(float cone_angle) 
{
	mRadiationCone = cone_angle / 2;
}

void CRobot::SetNoiseProperties(float mean, float dev)
{
	mMeanNoise = mean;
	mDevNoise = dev;
}

void CRobot::SetSensorRange(float min_dist, float max_dist)
{
	if (min_dist >= 0 && max_dist <= 1000){
		mSensorRange[MIN_VAL] = min_dist;
		mSensorRange[MAX_VAL] = max_dist;
	}
}

bool CRobot::IsRobotDetected(CPoint pt)
{
	for (UINT count = 0; count < mVectRobot.size(); count++){
		mRobot = &mVectRobot.at(count);
		float robot_radius = mRobot->Size / 2;

		if (pt.x > mRobot->XPos - robot_radius && pt.x < mRobot->XPos + robot_radius
			&& pt.y > mRobot->YPos - robot_radius && pt.y < mRobot->YPos + robot_radius){
				SetActiveRobot(count);
				return true;
		}
	}

	return false;
}

void CRobot::Zoom(float zoom_factor)
{
	mZoom = mZoom * zoom_factor;
	for (UINT count = 0; count < mVectRobot.size(); count++){
		ROBOT *robot = &mVectRobot.at(count);
		SetSize(robot->Size * zoom_factor);
		SetPosition(CPoint((int)robot->XPos * zoom_factor + 0.5, (int)robot->YPos * zoom_factor + 0.5));
		SetSensorRange(mSensorRange[MIN_VAL] * zoom_factor, mSensorRange[MAX_VAL] * zoom_factor);
	}
}

bool CRobot::SimpleCollisionDetection(ROBOT *robot, CDC *pDC)
{
	float step = M_PI / 10;
	float half_pi = M_PI / 2; 
	float dist = robot->Size / 2 + 5;
	CPoint pos = CPoint(robot->XPos, robot->YPos);

	// Head as normal
	if(robot->LWheelSpeed >= 0 && robot->RWheelSpeed >= 0){
		float count = -half_pi;
		while(count <= half_pi){
			CPoint pt = Rotate(pos, dist, robot->Theta + count);
			if (IsObstacleDetected(pDC, pt))
				return true;
			count = count + step;
		}
	}
	
	// Tail as normal
	else if (robot->LWheelSpeed <= 0 && robot->RWheelSpeed <= 0){
		float count = half_pi;
		while(count <= 3 * half_pi){
			CPoint pt = Rotate(pos, dist, robot->Theta + count);
			if (IsObstacleDetected(pDC, pt))
				return true;
			count = count + step;
		}
	}

	return false;
}

float CRobot::GetRadiationCone()
{
	return 2 * mRadiationCone;
}

float CRobot::GetSensorRange(int index)
{
	return mSensorRange[index] / mZoom;
}

void CRobot::GetNoiseProperties(float &mean, float &dev)
{
	mean = mMeanNoise;
	dev = mMeanNoise;
}

float CRobot::GetSpeed(int index)
{
	if (index = RIGHT)
		return mRobot->RWheelSpeed / mZoom;
	else if (index = LEFT)
		return mRobot->LWheelSpeed / mZoom;
	else
		return 0;
}

COLORREF CRobot::GetRobotColor(UINT index)
{
	return mVectRobot.at(index).RobotColor;
}

float CRobot::GetSensorValue(int index)
{
	return mRobot->SensorValue[index] / mZoom;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Student
Indonesia Indonesia
http://kataauralius.com/

Comments and Discussions