int SpecificWorker::getMinTag(int flag){
	
	int index = -1;
	QVec targetRobot;
	float min_distance = 10000;

	if (flag == 0){ //If looking for a box
		for(unsigned int i = 0; i < listaMarcas.size(); i++){
			targetRobot = innermodel->transform("world",QVec::vec3(listaMarcas[i].tx, 0, listaMarcas[i].tz) , "rgbd");

			if (targetRobot.norm2() < min_distance && listaMarcas[i].id > 9 && std::find(cajasRecogidas.begin(), cajasRecogidas.end(), listaMarcas[i].id) == cajasRecogidas.end()){
				min_distance = targetRobot.norm2();
				index = i;
				currentBox = listaMarcas[i].id;
			}
		}
	}
	else{ //If looking for a wall tag		
		for(unsigned int i = 0; i < listaMarcas.size(); i++){
			targetRobot = innermodel->transform("world",QVec::vec3(listaMarcas[i].tx, 0, listaMarcas[i].tz) , "rgbd");

			if (targetRobot.norm2() < min_distance && listaMarcas[i].id < 9){
				min_distance = targetRobot.norm2();
				index = i;
			}
		}
		
	}
	
	return index;
}
bool SpecificWorker::hayCamino()
{

  
  QVec t = inner->transform("robot", ctarget.target, "world");
  float alpha =atan2(t.x(), t.z() );
  float d = t.norm2();
  float x, z;
 //int i = 50;

    for ( uint i = 0; i<ldata.size(); i++ ) {
        if ( ldata[i].angle <= alpha ) {
            if ( ldata[i].dist < d ) {
	        qDebug() <<"NO hay camino";
                return false;
            } else {
                ctarget.activeSub=false;
                qDebug() <<"hay camino";
                return true;
            }
        }
    }
    
    qDebug() <<"NO ve la marca";
    state = State::TURN;
    return false;
}
void SpecificWorker::irASubTarget()
{
 qDebug() <<  __FUNCTION__<<"ir a subTarget";
    QVec t = inner->transform ( "robot", ctarget.subTarget, "world" );
    float alpha =atan2 ( t.x(), t.z() );
    float r= 0.4*alpha;
    float d = t.norm2();

    if ( d<100 ) {
        ctarget.activeSub=false;
        differentialrobot_proxy->setSpeedBase ( 0,0 );
        sleep ( 1 );

    } else {
        if ( fabs ( r ) > 0.2 ) {
            d = 0;
        }
        if ( d>300 ) {
            d=300;
        }
        try {
            differentialrobot_proxy->setSpeedBase ( d,r );
        } catch ( const Ice::Exception &ex ) {
            std::cout << ex << std::endl;
        }
    }
}
Example #4
0
inline void LMap::fromImageToVirtualLaser(float xi, float zi, int laserBins, float &dist, int32_t &bin)
{
	QVec mapCoordInLaser = fromImageToReference(xi, zi, virtualLaserID);
	float angle = atan2(mapCoordInLaser(0), mapCoordInLaser(2));
	dist = mapCoordInLaser.norm2();
	bin = angle2bin(angle, laserBins);
}
bool SpecificWorker::heLlegado()
{
  QVec t = inner->transform("robot", ctarget.target, "world");
 // qDebug()<< ctarget.target;
  float d = t.norm2();
  qDebug()<< "distancia: "<<d;
  if ( d < 100 )
  {
     return true;
  }else
  {
     return false;
  }

}
void SpecificWorker::irATarget()
{
   
QVec t = inner->transform ( "robot", ctarget.target, "world" );
    qDebug() << __FUNCTION__<< ctarget.target;

    qDebug() <<  __FUNCTION__<< t;
    float alpha =atan2 ( t.x(), t.z() );
    float r= 0.3*alpha;
    float d = 0.3*t.norm2();
    qDebug() << "velocidad " << d;
    if ( fabs ( r ) > 0.2 ) {
        d = 0;
    }
    if ( d>300 ) {
        d=300;
    }
    try {
        differentialrobot_proxy->setSpeedBase ( d,r );
    } catch ( const Ice::Exception &ex ) {
        std::cout << ex << std::endl;
    }
  
}
Example #7
0
bool SpecificWorker::checkFreeWay( const QVec &targetInRobot)
{
	qDebug() << __FUNCTION__ ;
	//First turn the robot to point towards the new target
	if (alignToTarget( targetInRobot ) != true )
	{
		stopAction();
		qFatal("Could not align the robot");
	}
	
	QList<QVec> lPoints;
	//points on the corners of thw square
	lPoints.append( QVec::vec3(ROBOT_RADIUS,0,ROBOT_RADIUS)); 
	lPoints.append( QVec::vec3(ROBOT_RADIUS,0,-ROBOT_RADIUS)); 
	lPoints.append( QVec::vec3(-ROBOT_RADIUS,0,-ROBOT_RADIUS));
	lPoints.append( QVec::vec3(-ROBOT_RADIUS,0,ROBOT_RADIUS));
	
	//points on the sides
	lPoints.append( QVec::vec3(ROBOT_RADIUS,0,0));
	lPoints.append( QVec::vec3(0,0,-ROBOT_RADIUS));
	lPoints.append( QVec::vec3(-ROBOT_RADIUS,0,0));
	lPoints.append( QVec::vec3(0,0, ROBOT_RADIUS));
	
	float dist = targetInRobot.norm2();	
	float alpha =atan2(targetInRobot.x(), targetInRobot.z() );
	float step = ceil(dist/ (ROBOT_SIZE/3));
	QVec tNorm = targetInRobot.normalize();
	QVec r;
	inner->updateTransformValues ("vbox",0, 0, 0, 0, 0, 0, "robot");	
	
	for(size_t  i = 0; i<ldata.size(); i++)
  {
		if(ldata[i].angle <= alpha)
    {
			for(float landa=400; landa<=dist; landa+=step)
			{	
				r = tNorm * landa;
				inner->updateTransformValues ("vbox",r.x(), r.y(), r.z(), 0, alpha, 0, "robot");	
				foreach(QVec p, lPoints)
				{
						QVec pointInRobot = inner->transform("robot", p, "vbox");
						float dPR = pointInRobot.norm2();
						float alphaPR = atan2(pointInRobot.x(), pointInRobot.z());
						for(auto ld : ldata)
							if(ld.angle <= alphaPR)
							{
								if( ld.dist>0 and ld.dist < LASER_MAX and ld.dist >= dPR)
								{
									//qDebug()<<__FUNCTION__<< "Hay camino libre al target" << ldata[i].dist << d;
									break;
								}
								else
								{
									qDebug() << "collision of point(R) " << inner->transform("robot", p, "vbox");
									return false;
								}
							}		
				}
			}
			qDebug()<<__FUNCTION__<< "Hay camino libre al target. Laser distance:" << ldata[i].dist << ". Target distance:" << dist;
			break;  //We found the laser ray aligned with the target.
     }
  }
void Worker::compute()
{
	/// Clear laser measurement
	for (int32_t i=0; i<LASER_SIZE; ++i)
	{
		(*laserDataW)[i].dist = maxLength;
	}


	/// Update InnerModel with joint information
	if (updateJoint)
	{
		RoboCompJointMotor::MotorStateMap motorMap;
		try
		{
			jointmotor->getAllMotorState(motorMap);
			for (RoboCompJointMotor::MotorStateMap::iterator it=motorMap.begin(); it!=motorMap.end(); ++it)
			{
				innerModel->updateJointValue(it->first.c_str(), it->second.pos);
			}
		}
		catch (const Ice::Exception &ex)
		{
			cout << "Can't connect to jointMotor: " << ex << endl;
		}
	}
	else
	{
		printf("not using joint\n");
	}

	/// FOR EACH OF THE CONFIGURED PROXIES
// 	#pragma omp parallel for
	for (uint r=0; r<rgbds.size(); ++r)
	{
#ifdef STORE_POINTCLOUDS_AND_EXIT
		pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>); // PCL
#endif

		if (rgbds[r].bus == true) /// If the proxy is a bus
		{
			/// FOR EACH OF THE CAMERAS OF THE BUS
			RoboCompRGBDBus::CameraList clist;
			clist.resize(1);
			RoboCompRGBDBus::CameraParamsMap::iterator iter;
			for (iter=rgbds[r].cameras.begin(); iter!=rgbds[r].cameras.end(); iter++)
			{
				if (iter->first == rgbds[r].id)
				{
					clist[0] = iter->first;
					RoboCompRGBDBus::ImageMap images;

					try
					{
						if (DECIMATION_LEVEL == 0)
							rgbds[r].proxyRGBDBus->getImages(clist,images);
						else
							rgbds[r].proxyRGBDBus->getDecimatedImages(clist, DECIMATION_LEVEL, images);
					}
					catch (const Ice::Exception &ex)
					{
						cout << "Can't connect to rgbd: " << ex << endl;
						continue;
					}

					/// Get the corresponding (stored) protocloud
					RoboCompRGBDBus::PointCloud pointCloud = rgbds[r].protoPointClouds[clist[0]];
					/// Multiply the protocloud by the depth
					for (uint32_t pi=0; pi<pointCloud.size(); pi++)
					{
						pointCloud[pi].x *= images[iter->first].depthImage[pi];
						pointCloud[pi].y *= images[iter->first].depthImage[pi];
						pointCloud[pi].z  = images[iter->first].depthImage[pi];
					}

					/// Inserts the resulting points in the virtual laser
					RTMat TR = innerModel->getTransformationMatrix(base, QString::fromStdString(iter->first));
#ifdef STORE_POINTCLOUDS_AND_EXIT
					cloud->points.resize(pointCloud.size());
#endif
					for (uint32_t ioi=0; ioi<pointCloud.size(); ioi+=3)
					{
						if ((not isnan(images[iter->first].depthImage[ioi])) and images[iter->first].depthImage[ioi] > 10)
						{
							QVec p = (TR * QVec::vec4(pointCloud[ioi].x, pointCloud[ioi].y, pointCloud[ioi].z, 1)).fromHomogeneousCoordinates();
#ifdef STORE_POINTCLOUDS_AND_EXIT
							cloud->points[ioi].x =  p(0)/1000;
							cloud->points[ioi].y =  p(1)/1000;
							cloud->points[ioi].z = -p(2)/1000;
#endif
							if ( (p(1)>=minHeight and p(1)<=maxHeight) /* or (p(1)<minHeightNeg) */)
							{
								p(1) = 0;
								float d = sqrt(p(0)*p(0) + p(2)*p(2));
								if (d>maxLength) d = maxLength;
								const float a = atan2(p(0), p(2));
								const int32_t bin = angle2bin(a);
								if (bin>=0 and bin<LASER_SIZE and (*laserDataW)[bin].dist > d)
								{
									if ( (*laserDataW)[bin].dist > d)
										(*laserDataW)[bin].dist = d;
								}
							}
						}
					}
				}
			}
		}
		else /// If the proxy is a good old RGBD interface
		{
			RoboCompRGBD::PointSeq points;
			try
			{
				rgbds[r].proxyRGBD->getXYZ(points, hState, bState);
			}
			catch (const Ice::Exception &ex)
			{
				cout << "Can't connect to rgbd: " << ex << endl;
				continue;
			}
			RTMat TR = innerModel->getTransformationMatrix(base, QString::fromStdString(rgbds[r].id));
#ifdef STORE_POINTCLOUDS_AND_EXIT
			cloud->points.resize(points.size());
#endif
// printf("%d\n", __LINE__);

			uint32_t pw = 640;
			uint32_t ph = 480;
			uint32_t step = 13;
			if (points.size() == 320*240) { pw=320; ph=240; step=11; }
			if (points.size() == 160*120) { pw=160; ph=120; step=5; }
			if (points.size() == 80*60) { pw=80; ph=60; step=3; }
			for (uint32_t rr=0; rr<ph; rr+=step)
			{
				for (uint32_t cc=rr%5; cc<pw; cc+=2)
				{
					uint32_t ioi = rr*pw+cc;
					if (ioi<points.size())
					{
						const QVec p = (TR * QVec::vec4(points[ioi].x, points[ioi].y, points[ioi].z, 1)).fromHomogeneousCoordinates();
#ifdef STORE_POINTCLOUDS_AND_EXIT
						cloud->points[ioi].x =  p(0)/1000;
						cloud->points[ioi].y =  p(1)/1000;
						cloud->points[ioi].z = -p(2)/1000;
#endif
						if ( (p(1)>=minHeight and p(1)<=maxHeight) or (p(1)<minHeightNeg) )
						{
// 							p(1) = 0;
							float d = sqrt(p(0)*p(0) + p(2)*p(2));
							if (d>maxLength) d = maxLength;
							const float a = atan2(p(0), p(2));
							const int32_t bin = angle2bin(a);
							if (bin>=0 and bin<LASER_SIZE and (*laserDataW)[bin].dist > d)
							{
								(*laserDataW)[bin].dist = d;
							}
						}
					}
				}
			}
// printf("%d\n", __LINE__);

		}
#ifdef STORE_POINTCLOUDS_AND_EXIT
		writePCD(rgbds[r].id+".pcd", cloud);
#endif
	}
#ifdef STORE_POINTCLOUDS_AND_EXIT
	qFatal("done");
#endif

	try
	{
		RoboCompLaser::TLaserData alData = laser->getLaserData();
		for (uint i=0; i<alData.size(); i++)
		{
			if (i==alData.size()/2) printf("PC %d  (%f _ %f)\n", i, alData[i].dist, alData[i].angle);
			const QVec p = innerModel->laserTo(actualLaserID, actualLaserID, alData[i].dist, alData[i].angle);
			if (i==alData.size()/2) p.print("en base");
			if (i==alData.size()/2) printf("(%s)", base.toStdString().c_str());
			const float angle = atan2(p(0), p(2));
			const float dist = p.norm2();
			if (i==alData.size()/2) printf("enlaser %f %f\n", dist, angle);
			const int j = LASER_SIZE*angle/FOV + (LASER_SIZE/2);
			if (i==alData.size()/2) printf("index %d\n", j);
// 			printf("FOV:%f, angle:%f, LASER_SIZE=%f, j:%d\n", (float)FOV, angle, (float)LASER_SIZE, j);
			
			if (j>=0 and j<(int)laserDataW->size())
			{
				if ((*laserDataW)[j].dist > dist)
				{
					(*laserDataW)[j].dist = dist;
				}
			}
		}
	}
	catch (const Ice::Exception &ex)
	{
		cout << "Can't connect to laser: " << ex << endl;
	}

	
	
	
	
	
	
	RoboCompOmniRobot::TBaseState oState;
	try { omnirobot->getBaseState(oState); }
	catch (Ice::Exception e) { qDebug()<<"error talking to base"<<e.what(); }
	bStateOut.x     = oState.x;
	bStateOut.z     = oState.z;
	bStateOut.alpha = oState.alpha;
	// Double buffer swap
	RoboCompLaser::TLaserData *t = laserDataR;
	mutex->lock();
	laserDataR = laserDataW;
	mutex->unlock();
	innerModel->updateTransformValues("robot", bStateOut.x, 0, bStateOut.z, 0, bStateOut.alpha,0);
// 	printf("%f %f ___ %f\n", bStateOut.x, bStateOut.z, bStateOut.alpha);
#ifdef USE_EXTENSION
	extended->update(*laserDataR);
	static QTime te = QTime::currentTime();
	float re = 11.*te.elapsed()/1000.;
	te = QTime::currentTime();
// 	printf("S ------------   %d       %f\n", extended->size(), re);
	extended->relax(re, innerModel, "laser", "root");
#endif

// 	medianFilter();
	laserDataW = t;
}
float ElasticBand::computeForces(InnerModel *innermodel, WayPoints &road, const RoboCompLaser::TLaserData &laserData)
{
	if (road.size() < 3)
		return 0;

	// To avoid moving the rotation element attached to the last
	int lastP;
	if (road.last().hasRotation)
		lastP = road.size() - 2;
	else
		lastP = road.size() - 1;

	// Go through all points in the road
	float totalChange = 0.f;
	for (int i = 1; i < lastP; i++)
	{
		if (road[i].isVisible == false)
			break;

		WayPoint &w0 = road[i - 1];
		WayPoint &w1 = road[i];
		WayPoint &w2 = road[i + 1];

		// Atraction force caused by the trajectory stiffnes, trying to straighten itself. It is computed as a measure of local curvature
		QVec atractionForce(3);
		float n = (w0.pos - w1.pos).norm2() / ((w0.pos - w1.pos).norm2() + w1.initialDistanceToNext);
		atractionForce = (w2.pos - w0.pos) * n - (w1.pos - w0.pos);

		//Compute derivative of force field and store values in w1.bMinuxX .... and w1.minDist. Also variations wrt former epochs
		computeDistanceField(innermodel, w1, laserData, FORCE_DISTANCE_LIMIT);

		QVec repulsionForce = QVec::zeros(3);
		QVec jacobian(3);

		// space interval to compute the derivative. Related to to robot's size
		float h = DELTA_H;
		if ((w1.minDistHasChanged == true) /*and (w1.minDist < 250)*/ )
		{
			jacobian = QVec::vec3(w1.bMinusX - w1.bPlusX,
			                      0,
			                      w1.bMinusY - w1.bPlusY) * (T) (1.f / (2.f * h));

			// repulsion force is computed in the direction of maximun laser-point distance variation and scaled so it is 0 is beyond FORCE_DISTANCE_LIMIT and FORCE_DISTANCE_LIMIT if w1.minDist.
			repulsionForce = jacobian * (FORCE_DISTANCE_LIMIT - w1.minDist);

		}

		float alpha = -0.5; //Negative values between -0.1 and -1. The bigger in magnitude, the stiffer the road becomes
		float beta = 0.55;  //Posibite values between  0.1 and 1	 The bigger in magnitude, more separation from obstacles

		QVec change = (atractionForce * alpha) + (repulsionForce * beta);
		if (std::isnan(change.x()) or std::isnan(change.y()) or std::isnan(change.z()))
		{
			road.print();
			qDebug() << atractionForce << repulsionForce;
			qFatal("change");
		}
		//Now we remove the tangencial component of the force to avoid recirculation of band points
		//QVec pp = road.getTangentToCurrentPoint().getPerpendicularVector();
		//QVec nChange = pp * (pp * change);

		w1.pos = w1.pos - change;
		totalChange = totalChange + change.norm2();
	}
	return totalChange;
}
Example #10
0
void SpecificWorker::crearSubTarget()
{
    qDebug() <<  __FUNCTION__ << "creando subTarget";

    float dt;
    QVec t = inner->transform ( "rgbd", ctarget.target, "world" );
    float d = t.norm2();
    float alpha =atan2 ( t.x(), t.z() );
    uint i,j;
    //const int R =400;

    for ( i = 5; i<ldata.size()-5; i++ ) {
        if ( ldata[i].angle < alpha ) {
            if ( d>ldata[i].dist ) {
                dt=ldata[i].dist;
                break;
            }
        }
    }
    qDebug() <<  __FUNCTION__<<i;
    qDebug() <<  __FUNCTION__<<ldata[i].dist<<ldata[i].angle;

    for ( j = i; j<ldata.size()-5; j++ ) {
        qDebug() <<  __FUNCTION__<<dt<< dt+ ( dt*0.2 ) <<ldata[j].dist << ldata[j].angle;

        if ( ldata[j].dist> ( dt+ ( dt*0.2 ) ) and ldata[j].angle < 0 ) {
            ctarget.subTarget=inner->transform ( "world", QVec::vec3 ( ldata[j].dist *sin ( ldata[j].angle )-2000,0, ldata[j].dist *cos ( ldata[j].angle ) ), "laser" );
            ctarget.activeSub = true;
            break;
        }
    }
    qDebug() <<  __FUNCTION__<< "Subtargeet" << QVec::vec3 ( ldata[j].dist *sin ( ldata[j].angle ),0, ldata[j].dist *cos ( ldata[j].angle ) );
/*
  
  //Search the first increasing step from the center to the right
	uint i,j;
	const int R =600;
	for(i=ldata.size()/2; i>5; i--)
	{
		if( (ldata[i].dist - ldata[i-1].dist) < -R )
		{
			if(i<=7) 
			{ 
				i=0; 
				break;
			}
			uint k=i-2;
			while( (k >= 0) and (fabs( ldata[k].dist*sin(ldata[k].angle - ldata[i-1].angle)) < R ))
			{ k--; }
			i=k;
			break;
		}
	}
	for(j=ldata.size()/2-5; j<ldata.size()-1; j++)
	{
		if( (ldata[j].dist - ldata[j+1].dist) < -R )
		{
			if(j>ldata.size()-3)
			{
				j=ldata.size()-1;
				break;
			}
			uint k=j+2;
			while( (k < ldata.size()) and (fabs( ldata[k].dist*sin(ldata[k].angle - ldata[j+1].angle)) < R ))
			{ k++; }
			j=k;
			break;
		}
	}
	
	QVec sI = inner->transform("world", QVec::vec3(ldata[j].dist *sin(ldata[j].angle),0, ldata[j].dist *cos(ldata[j].angle)), "laser");
	QVec sD = inner->transform("world", QVec::vec3(ldata[i].dist *sin(ldata[i].angle),0, ldata[i].dist *cos(ldata[i].angle)), "laser");
	
	if( (sI-ctarget.target).norm2() > (sD-ctarget.target).norm2() ) 
		ctarget.subtarget=sD;
	else
		ctarget.subtarget=sI;
		
	ctarget.activeSub=true;

  
  
//   for(i=5; i<ldata.size()-5;i++)
//   {
//     if(ldata[i-1].dist - ldata[i].dist > 400) 
//     {
//       ctarget.subtarget=inner->transform("world", QVec::vec3(ldata[i].dist *sin(ldata[i].angle),0, ldata[i].dist *cos(ldata[i].angle)), "laser");
//       ctarget.activeSub=true;
//       break;
//     }
//     
//     if(ldata[i+1].dist - ldata[i].dist > 400)
//     {
//      
//       ctarget.subtarget=inner->transform("world", QVec::vec3(ldata[i].dist *sin(ldata[i].angle),0, ldata[i].dist *cos(ldata[i].angle)), "laser");
//       ctarget.activeSub=true;
//       break;   
//     }
//     
//   }
  
  qDebug()<<ctarget.subtarget;
   // qDebug()<<"distancia subtarget"<<ldata[i].dist;
  
  
 // differentialrobot_proxy->setSpeedBase(0,0.5);
  //exit(-1);*/
}
void SpecificWorker::ballTouched(bool first)
{
	QTime fff;
	if (first) fff = QTime::currentTime();

// 	if (tocaButton->isChecked())
	{
		tocaButton->setText("tocando");
		const int32_t ball = atoi(params["b"].value.c_str());
		const int32_t robot = atoi(params["r"].value.c_str());

		printf("\n\n----------\n");
		try
		{
			// Get 
			const float tx = str2float(worldModel->getSymbol(ball)->getAttribute("tx"));
			const float ty = str2float(worldModel->getSymbol(ball)->getAttribute("ty"));
			const float tz = str2float(worldModel->getSymbol(ball)->getAttribute("tz"));
			const QVec offset = QVec::vec3(0., 0., -100.);
			const QVec targetRobot = QVec::vec3(tx, ty, tz) + offset;
			
			const QVec actualWristPosition = QVec::vec3(
			str2float(worldModel->getSymbol(robot)->attributes["rightwrist_tx"]),
			str2float(worldModel->getSymbol(robot)->attributes["rightwrist_ty"]),
			str2float(worldModel->getSymbol(robot)->attributes["rightwrist_tz"]));
			
			actualWristPosition.print("actualWristPosition");
			

			const QVec error = targetRobot-actualWristPosition;

			// If the hand is far from the target, move the hand
			int32_t extra = 0.015 * (fff.elapsed() - 2000);
			if (extra<0) extra = 0;
			if (extra>200) extra = 200;
			int32_t umbral = 100. + extra;
			if (error.norm2() > umbral)
			{
				targetRobot.print("targetRobot");
				actualWristPosition.print("actualWristPosition");
				error.print("error");
				printf("ERROR: %f\n", error.norm2());
				printf("UMBRAL: %d\n", umbral);
				const QVec poseTr = innerModel->transform("world", targetRobot, "robot");
				printf("move hand to T=(%.2f, %.2f, %.2f)  \n", poseTr(0), poseTr(1), poseTr(2));
				sendRightHandPose(poseTr, QVec::vec3(0,0,0), QVec::vec3(1,1,1), QVec::vec3(0,0,0));		
			}
			// If the hand is close to the target, acknowledge the new state
			else
			{
				printf("lo conseguimos!!!\n");
				printf("lo conseguimos!!!\n");
				printf("lo conseguimos!!!\n");
				printf("lo conseguimos!!!\n");
				printf("lo conseguimos!!!\n");
				printf("lo conseguimos!!!\n");
				resetGame();
			}
		}
		catch(AGMModelException &e)
		{
			printf("I don't know ball %d\n", ball);
		}
	}
}