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