1 /***************************************************************************
2                           routetracker.cpp  -  (Re-)tracking the routes in a track
3                              -------------------
4     begin                : zo mei 18 2008
5     copyright            : (C) 2008 by CJP
6     email                : cornware-cjp@users.sourceforge.net
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 #include <cstdio>
18 
19 #include "routetracker.h"
20 
CRouteTracker(CEditTrack * track)21 CRouteTracker::CRouteTracker(CEditTrack *track)
22 {
23 	m_Track = track;
24 	m_DataManager = m_Track->getManager();
25 }
26 
27 
~CRouteTracker()28 CRouteTracker::~CRouteTracker()
29 {
30 }
31 
trackRoutes()32 void CRouteTracker::trackRoutes()
33 {
34 	m_Track->m_Markers.clear();
35 
36 	m_Track->m_Routes.clear();
37 
38 	const unsigned int L = m_Track->m_L;
39 	const unsigned int W = m_Track->m_W;
40 	const unsigned int H = m_Track->m_H;
41 
42 	//Search for start locations:
43 	CTrack::CCheckpoint start;
44 	start.x = start.y = start.z = 0;
45 
46 	bool found = false;
47 	for(unsigned int x=0; x < L; x++)
48 	for(unsigned int z=0; z < W; z++)
49 	for(unsigned int y=0; y < H; y++)
50 	{
51 		unsigned int n = y + H * (z+W*x);
52 		STile &t = m_Track->m_Track[n];
53 		CDataObject *tmodel = m_DataManager->getObject(CDataObject::eTileModel, t.m_Model);
54 
55 		bool isStart = tmodel->getParamList().getValue("flags", "").inStr('s') >= 0;
56 		if(isStart)
57 		{
58 			if(found) //already found another one
59 			{
60 				printf("Error: found multiple start tiles\n");
61 
62 				CEditTrack::SMarker marker;
63 
64 				marker.pos = start;
65 				m_Track->m_Markers.push_back(marker);
66 
67 				marker.pos.x = x;
68 				marker.pos.y = t.m_Z;
69 				marker.pos.z = z;
70 				m_Track->m_Markers.push_back(marker);
71 
72 				return;
73 			}
74 
75 			start.x = x;
76 			start.y = t.m_Z;
77 			start.z = z;
78 
79 			found = true;
80 		}
81 	}
82 
83 	if(!found)
84 	{
85 		printf("Error: found no start tiles\n");
86 		return;
87 	}
88 
89 	//printf("Start tile: %d, %d, %d\n", start.x, start.y, start.z);
90 
91 	//Add the start of the first route
92 	CTrack::CRoute mainRoute;
93 	mainRoute.finishRoute = mainRoute.finishTile = mainRoute.startRoute = mainRoute.startTile = 0;
94 	mainRoute.push_back(start);
95 	m_Track->m_Routes.push_back(mainRoute);
96 
97 	//Then track all routes (including the other ones that will be added during the process)
98 	for(unsigned int i=0; i < m_Track->m_Routes.size(); i++)
99 		trackSingleRoute(i);
100 
101 	//Then erase all empty routes:
102 	for(unsigned int i=0; i < m_Track->m_Routes.size(); i++)
103 		if(m_Track->m_Routes[i].empty())
104 		{
105 			m_Track->m_Routes.erase(m_Track->m_Routes.begin() + i);
106 			i--;
107 		}
108 }
109 
trackSingleRoute(unsigned int routenr)110 void CRouteTracker::trackSingleRoute(unsigned int routenr)
111 {
112 	//printf("\nStart of route %d\n", routenr);
113 
114 	if(routenr >= m_Track->m_Routes.size())
115 	{
116 		printf("Error: routenr >= number of routes\n");
117 		return;
118 	}
119 
120 	CTrack::CRoute *theRoute = &(m_Track->m_Routes[routenr]);
121 
122 	if(theRoute->empty())
123 	{
124 		printf("Error: route has no start tile\n");
125 		return;
126 	}
127 
128 	CTrack::CCheckpoint currentPos = (*theRoute)[0];
129 	bool currentIsForward = true; //direction of current route
130 
131 	unsigned int currentTileRoute = 0;
132 	if(routenr > 0)
133 	{
134 		//Try to guess the current tile route for alternative routes
135 		currentTileRoute = 1; //TODO: more advanced test
136 	}
137 
138 	STile currentTile = getTile(currentPos);
139 	const CTETile *currentModel = getModel(currentTile);
140 	if(currentTileRoute >= currentModel->m_Routes.size())
141 	{
142 		printf("Error: currentTileRoute >= number of tile routes\n");
143 		printf("    Tile: %s\n", currentModel->getFilename().c_str());
144 		printf("    %d >= %d\n", currentTileRoute, currentModel->m_Routes.size());
145 		return;
146 	}
147 
148 	while(true)
149 	{
150 		CTrack::CCheckpoint newPos = currentPos;
151 		unsigned int newTileRoute = currentTileRoute;
152 		bool newIsForward = currentIsForward;
153 
154 		bool hasReturnedToSplit = false;
155 
156 		if(!findNextTile(newPos, newTileRoute, newIsForward))
157 		{
158 			//printf("End of route\n");
159 
160 			CEditTrack::SMarker marker;
161 			marker.pos = currentPos;
162 			m_Track->m_Markers.push_back(marker);
163 
164 			if(!goBackToLastSplit(routenr, newPos, newTileRoute, newIsForward))
165 			{
166 				//printf("Route is invalid\n");
167 
168 				//Route is invalid.
169 				//Remove it if it's not the primary route:
170 				if(routenr != 0) //Only remove non-primary routes:
171 					theRoute->clear(); //empty routes will be deleted
172 
173 				return;
174 			}
175 
176 			//printf("Returned to last split\n");
177 
178 			//Update the route pointer, because goBackToLastSplit
179 			//makes changes to the route array:
180 			theRoute = &(m_Track->m_Routes[routenr]);
181 
182 			hasReturnedToSplit = true;
183 		}
184 
185 		//printf("New pos: %d, %d, %d\n", newPos.x, newPos.y, newPos.z);
186 		theRoute->push_back(newPos);
187 
188 		//Check for finish:
189 		if(isFinish(newPos))
190 		{
191 			//printf("Route stops at finish\n");
192 			theRoute->finishRoute = 0;
193 			theRoute->finishTile = 0;
194 			return;
195 		}
196 
197 		STile newTile = getTile(newPos);
198 		const CTETile *newModel = getModel(newTile);
199 
200 		//Check for splits
201 		if(newModel->m_RouteType == CTETile::eSplit && newIsForward)
202 		{
203 			if(newIsForward && !hasReturnedToSplit)
204 			{
205 				//Add new route for alternative direction
206 				CTrack::CRoute newRoute;
207 				newRoute.finishRoute = newRoute.finishTile = 0;
208 				newRoute.startRoute = routenr;
209 				newRoute.startTile = theRoute->size()-1;
210 				newRoute.push_back(newPos);
211 
212 				//printf("  Created new route %d: startTile = %d; "
213 				//	"startPos = %d,%d,%d\n",
214 				//	m_Track->m_Routes.size(), newRoute.startTile,
215 				//	newPos.x, newPos.y, newPos.z);
216 
217 				m_Track->m_Routes.push_back(newRoute);
218 
219 				//Update the route pointer, because we
220 				//made a change to the route array:
221 				theRoute = &(m_Track->m_Routes[routenr]);
222 			}
223 
224 			//don't check for joins:
225 			//move to new position
226 			currentPos = newPos;
227 			currentTileRoute = newTileRoute;
228 			currentIsForward = newIsForward;
229 			continue;
230 		}
231 
232 		//Check for joins:
233 		for(unsigned int j=0; j <= routenr; j++)
234 		{
235 			CTrack::CRoute &prevRoute = m_Track->m_Routes[j];
236 			for(unsigned int k=1; k+1 < prevRoute.size(); k++)
237 			if(prevRoute[k] == newPos)
238 			{
239 				//printf("Found existing route on the new pos\n");
240 
241 				//Now check if the join is valid:
242 				CTrack::CCheckpoint newPos2 = newPos;
243 				unsigned int newTileRoute2 = newTileRoute;
244 				bool newIsForward2 = newIsForward;
245 
246 				if(!findNextTile(newPos2, newTileRoute2, newIsForward2))
247 				{
248 					//End of route
249 
250 					//Go back to the last split,
251 					//or, if that fails, remove the entire route and return
252 
253 					if(!goBackToLastSplit(routenr, newPos, newTileRoute, newIsForward))
254 					{
255 						//Entire route is invalid.
256 						//Remove it if it's not the primary route:
257 						if(routenr != 0) //Only remove non-primary routes:
258 							{theRoute->clear();} //empty routes will be deleted
259 						else
260 						{
261 							CEditTrack::SMarker marker;
262 							marker.pos = currentPos;
263 							m_Track->m_Markers.push_back(marker);
264 						}
265 
266 						return;
267 					}
268 
269 					//Update the route pointer, because we
270 					//made a change to the route array:
271 					theRoute = &(m_Track->m_Routes[routenr]);
272 
273 					//Add the last split position again:
274 					//printf("New pos: %d, %d, %d\n", newPos.x, newPos.y, newPos.z);
275 					theRoute->push_back(newPos);
276 
277 					//move to new position
278 					currentPos = newPos;
279 					currentTileRoute = newTileRoute;
280 					currentIsForward = newIsForward;
281 					continue;
282 				}
283 
284 				CTrack::CCheckpoint backwardPos = prevRoute[k-1];
285 				CTrack::CCheckpoint forwardPos = prevRoute[k+1];
286 
287 				if(newPos2 == forwardPos) //join in the same direction
288 				{
289 					if(j == routenr) //joining itself: this is invalid
290 					{
291 						//Go back to the last split,
292 						//or, if that fails, remove the entire route and return
293 
294 						if(!goBackToLastSplit(routenr, newPos, newTileRoute, newIsForward))
295 						{
296 							//Entire route is invalid.
297 							//Remove it if it's not the primary route:
298 							if(routenr != 0) //Only remove non-primary routes:
299 								{theRoute->clear();} //empty routes will be deleted
300 							else
301 							{
302 								CEditTrack::SMarker marker;
303 								marker.pos = currentPos;
304 								m_Track->m_Markers.push_back(marker);
305 							}
306 
307 							return;
308 						}
309 
310 						//Update the route pointer, because we
311 						//made a change to the route array:
312 						theRoute = &(m_Track->m_Routes[routenr]);
313 
314 						//Add the last split position again:
315 						//printf("New pos: %d, %d, %d\n", newPos.x, newPos.y, newPos.z);
316 						theRoute->push_back(newPos);
317 					}
318 					else
319 					{
320 						//Stop route at the join
321 						theRoute->finishRoute = j;
322 						theRoute->finishTile = k;
323 						return;
324 					}
325 				}
326 				else if(newPos2 == backwardPos) //join in opposite direction
327 				{
328 					//this is inavlid in the current game version
329 
330 					//Go back to the last split,
331 					//or, if that fails, remove the entire route and return
332 
333 					if(!goBackToLastSplit(routenr, newPos, newTileRoute, newIsForward))
334 					{
335 						//Entire route is invalid.
336 						//Remove it if it's not the primary route:
337 						if(routenr != 0) //Only remove non-primary routes:
338 							{theRoute->clear();} //empty routes will be deleted
339 						else
340 						{
341 							CEditTrack::SMarker marker;
342 							marker.pos = currentPos;
343 							m_Track->m_Markers.push_back(marker);
344 						}
345 
346 						return;
347 					}
348 
349 					//Update the route pointer, because we
350 					//made a change to the route array:
351 					theRoute = &(m_Track->m_Routes[routenr]);
352 
353 					//Add the last split position again:
354 					//printf("New pos: %d, %d, %d\n", newPos.x, newPos.y, newPos.z);
355 					theRoute->push_back(newPos);
356 				}
357 			}
358 		}
359 
360 		//move to new position
361 		currentPos = newPos;
362 		currentTileRoute = newTileRoute;
363 		currentIsForward = newIsForward;
364 	}
365 }
366 
goBackToLastSplit(unsigned int routenr,CTrack::CCheckpoint & pos,unsigned int & route,bool & forward)367 bool CRouteTracker::goBackToLastSplit(
368 	unsigned int routenr,
369 	CTrack::CCheckpoint &pos, unsigned int &route, bool &forward
370 	)
371 {
372 	//printf("goBackToLastSplit on route %d\n", routenr);
373 
374 	//First find the split-up route number.
375 	//All candidates are after this route in the route array.
376 	//The last split is the last item that starts at our route.
377 	unsigned int splitRoute = 0;
378 	if(routenr+1 >= m_Track->m_Routes.size()) return false; //There are no split-up routes
379 	for(unsigned int i = m_Track->m_Routes.size()-1; i != routenr; i--)
380 		if(m_Track->m_Routes[i].startRoute == routenr)
381 		{
382 			splitRoute = i;
383 			break;
384 		}
385 	if(splitRoute == 0) return false; //We didn't find a route splitting from this route
386 
387 	unsigned int splitTile = m_Track->m_Routes[splitRoute].startTile;
388 
389 	//printf("Found split route %d, starting on tile %d\n", splitRoute, splitTile);
390 
391 	CTrack::CRoute &theRoute = m_Track->m_Routes[routenr];
392 
393 	if(m_Track->m_Routes[splitRoute].size() != 1)
394 	{
395 		printf("Bug in route tracking: split-up route does not contain 1 position\n");
396 		return false;
397 	}
398 
399 	if(theRoute[splitTile] != (m_Track->m_Routes[splitRoute])[0])
400 	{
401 		printf("Bug in route tracking: split-up route positions don't match\n");
402 		return false;
403 	}
404 
405 	//The return state:
406 	pos = theRoute[splitTile];
407 	route = 1; //TODO: more advanced check to find the alternative route
408 	forward = true;
409 
410 	//Remove later tiles in this route (including the split, which will be added again later):
411 	theRoute.resize(splitTile);
412 
413 	//Remove the split-up route:
414 	m_Track->m_Routes.erase(m_Track->m_Routes.begin() + splitRoute);
415 
416 	//printf("goBackToLastSplit finished succesfully\n");
417 
418 	return true;
419 }
420 
removeSplitRoutes(unsigned int routenr)421 void CRouteTracker::removeSplitRoutes(unsigned int routenr)
422 {
423 	while(true)
424 	{
425 		if(routenr+1 >= m_Track->m_Routes.size()) return; //no more split routes
426 
427 		bool found = false;
428 		for(unsigned int i=routenr+1; i < m_Track->m_Routes.size(); i++)
429 			if(m_Track->m_Routes[i].startRoute == routenr)
430 			{
431 				m_Track->m_Routes.erase(m_Track->m_Routes.begin() + i);
432 
433 				found = true;
434 				break;
435 			}
436 
437 		if(!found) return; //no more split routes found
438 	}
439 }
440 
findNextTile(CTrack::CCheckpoint & pos,unsigned int & route,bool & forward) const441 bool CRouteTracker::findNextTile(CTrack::CCheckpoint &pos, unsigned int &route, bool &forward) const
442 {
443 	//Current situation:
444 	CTrack::CCheckpoint currentPos = pos;
445 	unsigned int currentRoute = route;
446 	bool currentForward = forward;
447 
448 	STile currentTile = getTile(currentPos);
449 	const CTETile *currentModel = getModel(currentTile);
450 
451 	//The route on the current tile:
452 	CTETile::SRoute theTileRoute = currentModel->m_Routes[currentRoute];
453 
454 	//Exit points of current tile route:
455 	vector<CTETile::SIntVector> exitPoints =
456 		currentForward? theTileRoute.outPos : theTileRoute.inPos;
457 
458 	//Extended positions are useful for jumps, ramps etc.
459 	//Currently we go from 0 to 2, but more could be added in the future.
460 	//See also getRoutePosition for their exact effect.
461 	for(unsigned int extendedPos=0; extendedPos < 3; extendedPos++)
462 	for(unsigned int i=0; i < exitPoints.size(); i++)
463 	{
464 		pos = getRoutePosition(
465 			currentPos, exitPoints[i], currentTile.m_R, extendedPos);
466 
467 		//printf("Candidate pos: %d, %d, %d\n", pos.x, pos.y, pos.z);
468 
469 		if(!isInTrack(pos)) continue;
470 
471 		if(pos == currentPos) continue; //invalid
472 
473 		if(!tileExists(pos)) continue;
474 
475 		STile newTile = getTile(pos);
476 		const CTETile *newModel = getModel(newTile);
477 
478 		//printf("Candidate tile: %s\n", newModel->getFilename().c_str());
479 
480 		if(newModel->m_Routes.empty()) continue; //don't go if there is no route
481 
482 		unsigned int bestScore = 0;
483 
484 		//Check for a double match in the next tile's routes
485 		for(unsigned int j=0; j < newModel->m_Routes.size(); j++)
486 		{
487 			CTETile::SRoute newRoute = newModel->m_Routes[j];
488 
489 			//Check entry points
490 			for(unsigned int k=0; k < newRoute.inPos.size(); k++)
491 			{
492 				CTrack::CCheckpoint backPos =
493 					getRoutePosition(pos,
494 					newRoute.inPos[k], newTile.m_R);
495 
496 				//printf("Entry backpos: %d, %d, %d\n", backPos.x, backPos.y, backPos.z);
497 
498 				unsigned int score = backtrackScore(currentPos, pos, backPos);
499 
500 				if(score > bestScore)
501 				{
502 					//printf("Found forward: %s\n", newModel->getFilename().c_str());
503 					forward = true;
504 					route = j;
505 					bestScore = score;
506 				}
507 			}
508 
509 			//Check exit points
510 			for(unsigned int k=0; k < newRoute.outPos.size(); k++)
511 			{
512 				CTrack::CCheckpoint backPos =
513 					getRoutePosition(pos,
514 					newRoute.outPos[k], newTile.m_R);
515 
516 				//printf("Exit backpos: %d, %d, %d\n", backPos.x, backPos.y, backPos.z);
517 
518 				unsigned int score = backtrackScore(currentPos, pos, backPos);
519 
520 				if(score > bestScore)
521 				{
522 					//printf("Found backward: %s\n", newModel->getFilename().c_str());
523 					forward = false;
524 					route = j;
525 					bestScore = score;
526 				}
527 			}
528 		}
529 
530 		if(bestScore != 0) return true; //we found a match
531 	}
532 
533 	return false; //didn't find a tile
534 }
535 
backtrackScore(const CTrack::CCheckpoint currentPos,const CTrack::CCheckpoint newPos,const CTrack::CCheckpoint backtrackPos) const536 unsigned int CRouteTracker::backtrackScore(
537 	const CTrack::CCheckpoint currentPos,
538 	const CTrack::CCheckpoint newPos,
539 	const CTrack::CCheckpoint backtrackPos
540 	) const
541 {
542 	//Exact match:
543 	if(backtrackPos == currentPos) return 1000;
544 
545 	//Only y difference:
546 	if(backtrackPos.x == currentPos.x && backtrackPos.z == currentPos.z) return 10;
547 
548 	//In constant-x line:
549 	if(backtrackPos.x == currentPos.x && backtrackPos.x == newPos.x)
550 	{
551 		//Between current and new:
552 		if(backtrackPos.z > currentPos.z && backtrackPos.z < newPos.z) return 1;
553 		if(backtrackPos.z < currentPos.z && backtrackPos.z > newPos.z) return 1;
554 	}
555 
556 	//In constant-z line:
557 	if(backtrackPos.z == currentPos.z && backtrackPos.z == newPos.z)
558 	{
559 		//Between current and new:
560 		if(backtrackPos.x > currentPos.x && backtrackPos.x < newPos.x) return 1;
561 		if(backtrackPos.x < currentPos.x && backtrackPos.x > newPos.x) return 1;
562 	}
563 
564 	return 0;
565 }
566 
getRoutePosition(CTrack::CCheckpoint tilepos,CTETile::SIntVector relpos,int rotation,unsigned int extendedPos) const567 CTrack::CCheckpoint CRouteTracker::getRoutePosition(
568 	CTrack::CCheckpoint tilepos,
569 	CTETile::SIntVector relpos,
570 	int rotation,
571 	unsigned int extendedPos
572 	) const
573 {
574 
575 	//Extended positions:
576 	if(relpos.y == 0)
577 	{
578 		switch(extendedPos)
579 		{
580 		case 1:
581 			relpos.y = -1;
582 			break;
583 		case 2:
584 			relpos.y = -2;
585 			break;
586 		}
587 	}
588 
589 
590 	//Position translation (with rotated vector):
591 
592 	tilepos.y += relpos.y;
593 
594 	switch(rotation & 0x3) //rotation modulo 4
595 	{
596 	case 0:
597 		tilepos.x += relpos.x;
598 		tilepos.z += relpos.z;
599 		break;
600 	case 1:
601 		tilepos.x += relpos.z;
602 		tilepos.z -= relpos.x;
603 		break;
604 	case 2:
605 		tilepos.x -= relpos.x;
606 		tilepos.z -= relpos.z;
607 		break;
608 	case 3:
609 		tilepos.x -= relpos.z;
610 		tilepos.z += relpos.x;
611 		break;
612 	}
613 
614 	return tilepos;
615 }
616 
isInTrack(const CTrack::CCheckpoint & pos) const617 bool CRouteTracker::isInTrack(const CTrack::CCheckpoint &pos) const
618 {
619 	return
620 		pos.x >= 0 && pos.x < m_Track->m_L &&
621 		pos.z >= 0 && pos.z < m_Track->m_W;
622 }
623 
isFinish(const CTrack::CCheckpoint & pos) const624 bool CRouteTracker::isFinish(const CTrack::CCheckpoint &pos) const
625 {
626 	const CTETile *model = getModel(getTile(pos));
627 	return model->getParamList().getValue("flags", "").inStr('f') >= 0;
628 }
629 
tileExists(const CTrack::CCheckpoint & pos) const630 bool CRouteTracker::tileExists(const CTrack::CCheckpoint &pos) const
631 {
632 	unsigned int n = m_Track->m_H * (pos.z+m_Track->m_W*pos.x);
633 
634 	for(int i=0; i < m_Track->m_H; i++)
635 		if(m_Track->m_Track[n+i].m_Z == pos.y) return true;
636 
637 	return false;
638 }
639 
getTile(const CTrack::CCheckpoint & pos) const640 STile CRouteTracker::getTile(const CTrack::CCheckpoint &pos) const
641 {
642 	unsigned int n = m_Track->m_H * (pos.z+m_Track->m_W*pos.x);
643 
644 	vector<STile> candidates;
645 	for(int i=0; i < m_Track->m_H; i++)
646 	{
647 		STile &t = m_Track->m_Track[n+i];
648 		if(t.m_Model != 0 && getModel(t)->m_Routes.size() != 0)
649 			candidates.push_back(t);
650 	}
651 
652 	//No candidates: return lowest tile
653 	if(candidates.size() == 0)
654 		return m_Track->m_Track[n];
655 
656 	/*
657 	for(unsigned int i=0; i < candidates.size(); i++)
658 		printf("%d: %d\n", candidates[i].m_Z, candidates[i].m_Model);
659 	*/
660 
661 	//Below lowest: return lowest
662 	if(pos.y < candidates[0].m_Z)
663 	{
664 		//printf("Below: %d < %d\n", pos.y, candidates[0].m_Z);
665 		return candidates[0];
666 	}
667 
668 	for(unsigned int i=0; i < candidates.size(); i++)
669 	{
670 		//Return exact match:
671 		if(candidates[i].m_Z == pos.y)
672 		{
673 			//printf("Exact match\n");
674 			return candidates[i];
675 		}
676 
677 		//TODO: between
678 	}
679 
680 	//Above highest: return highest
681 	//printf("Above\n");
682 	return candidates[candidates.size()-1];
683 }
684 
getModel(const STile & tile) const685 const CTETile *CRouteTracker::getModel(const STile &tile) const
686 {
687 	return (CTETile *)(m_DataManager->getObject(CDataObject::eTileModel, tile.m_Model));
688 }
689 
690