1 
2 #include "TrackView.h"
3 #include <math.h>
4 #include <cstring>
5 
6 #include "ScAnimation.h"
7 #include "tinyxml.h"
8 #include "ScException.h"
9 #include <FL/Fl_JPEG_Image.H>
10 #include <FL/Fl_PNG_Image.H>
11 #include <FL/Fl_BMP_Image.H>
12 #include <FL/Fl_PNM_Image.H>
13 #include <FL/Fl_GIF_Image.H>
14 #include <FL/Fl_XPM_Image.H>
15 #include <FL/Fl_File_Chooser.H>   // fl_alert
16 #include "GameConstants.h"
17 #include "TrackEditorUI.h"
18 
19 #ifdef XCODE
20 #include <Carbon/Carbon.h>
21 #endif
22 
23 #ifndef strtok_r
24    #define strtok_r(s,d,p) strtok(s,d)
25 #endif
26 
27 const unsigned int kMinimapUpdateLimit = 10;
28 const int kMainWindowWidth = 1024;
29 const int kMainWindowHeight = 768;
30 const int kScrollbarMaxValue = 10000;
31 const double kVertexSelectMinDist = 10 / kScale;
32 const double kPathSelectMinDist = 10 / kScale;
33 const double kIntervalHandleRadius = 8 / kScale;
34 
drawCircleAt(double x,double y,double r,bool fill=true,int n=10)35 void drawCircleAt(double x, double y, double r, bool fill = true, int n = 10) {
36    const double da = 1.0 / n;
37    if (fill)
38       glBegin(GL_POLYGON);
39    else
40       glBegin(GL_LINE_LOOP);
41    for (double a = 0; a < 1; a += da) {
42       glVertex2d((x + r*cos(2*M_PI*a)) * kScale,
43                  (y + r*sin(2*M_PI*a)) * kScale);
44    }
45    glEnd();
46 }
47 
freeSearchDirectories(SearchDirectories * dirs)48 void freeSearchDirectories(SearchDirectories *dirs) {
49    SearchDirectories *temp;
50    while (dirs != NULL) {
51       temp = dirs->next;
52       free(dirs);
53       dirs = temp;
54    }
55 }
56 
makeSearchDirectories(const char * path,SearchDirectories * next)57 SearchDirectories *makeSearchDirectories(const char *path, SearchDirectories *next) {
58    SearchDirectories *result = new SearchDirectories;
59    strncpy(result->path, path, 255);
60    result->next = next;
61    return result;
62 }
63 
findXMLFile(const char * filename,SearchDirectories * dirs,char * outPath=NULL)64 TiXmlDocument *findXMLFile(const char *filename, SearchDirectories *dirs, char *outPath = NULL)
65 {
66    const int filenameLen = strlen(filename);
67    char str[256];
68    TiXmlDocument *xmlDoc;
69    while (dirs != NULL) {
70       if (strlen(dirs->path) + filenameLen + 1 < 255) {
71          strcpy(str, dirs->path);
72          strcat(str, "/");
73          strcat(str, filename);
74          xmlDoc = new TiXmlDocument(str);
75          if (xmlDoc->LoadFile()) {
76             if (outPath)
77                strcpy(outPath, dirs->path);
78             return xmlDoc;
79          }
80          else {
81             delete xmlDoc;
82          }
83          dirs = dirs->next;
84       }
85    }
86    return NULL;
87 }
88 
89 //{jpg,png,bmp,pnm,pbm,pgm,ppm}
loadImage(const char * filename)90 Fl_Image* loadImage(const char *filename)
91 {
92    const char *suffix = strrchr(filename, '.');
93    if (suffix == NULL)
94       return NULL;
95    if (strcmp(suffix,".jpg") == 0)
96       return new Fl_JPEG_Image(filename);
97    else if (strcmp(suffix,".png") == 0)
98       return new Fl_PNG_Image(filename);
99    else if (strcmp(suffix,".bmp") == 0)
100       return new Fl_BMP_Image(filename);
101    else if (strcmp(suffix,".pnm") == 0)
102       return new Fl_PNM_Image(filename);
103    else if (strcmp(suffix,".pbm") == 0)
104       return new Fl_PNM_Image(filename);
105    else if (strcmp(suffix,".pgm") == 0)
106       return new Fl_PNM_Image(filename);
107    else if (strcmp(suffix,".ppm") == 0)
108       return new Fl_PNM_Image(filename);/*
109    else if (strcmp(suffix,".gif") == 0)
110       return new Fl_GIF_Image(filename);
111    else if (strcmp(suffix,".xpm") == 0)
112       return new Fl_XPM_Image(filename);*/
113    else
114       return NULL;
115 }
116 
PixelDataForImage(const Fl_Image * src,ScPixelData & dst)117 void PixelDataForImage(const Fl_Image* src, ScPixelData& dst)
118 {
119 	dst.bytes_pp = src->d();
120 	dst.width = src->w();
121 	dst.height = src->h();
122 	dst.pitch = dst.width * dst.bytes_pp;
123 
124 	switch (dst.bytes_pp)
125 	{
126 		case 4:
127 			dst.format = GL_RGBA;
128 			dst.type = GL_UNSIGNED_BYTE;
129 			dst.internalformat = GL_RGBA;
130 			break;
131 
132 		case 3:
133 			dst.format = GL_RGB;
134 			dst.type = GL_UNSIGNED_BYTE;
135 			dst.internalformat = GL_RGB;
136 			break;
137 
138 		default:
139 			ScThrowErr("Only 32 bit or 24 bit is supported at present");
140 			break;
141 	}
142 
143    if (src->count() == 1)
144       dst.pixels = src->data()[0];
145    else
146       ScThrowErr("Unsupported image type");
147 }
148 
TrackView(int x,int y,int w,int h,const char * l)149 TrackView::TrackView(int x,int y,int w,int h,const char *l)
150    : Fl_Gl_Window(x,y,w,h,l), startLine(0), signs(0), view(0), searchPath(0), tiles(0), clockwise(false),
151      hoverPathVertexExists(false), selectedPathVertexExists(false), hoverPathIntermediateVertexExists(false), drawingSignInterval(false),
152      signIntervalStartMouse(NULL), signIntervalEndMouse(NULL)
153 {
154 #ifdef WIN32
155    strncpy(dataDir, DATA_DIR, 1023);
156    strncpy(userDataDir, DATA_DIR, 1023);
157    searchPath = makeSearchDirectories(dataDir, NULL);
158 #else
159  #ifdef XCODE
160    // in the Xcode bundle DATA_DIR is only a relative directory so we need to make it absolute here.
161    Boolean succ = CFURLGetFileSystemRepresentation(CFBundleCopyBundleURL(CFBundleGetMainBundle()), TRUE, (UInt8*)dataDir, 1023);
162    //getcwd(dataDir, 1023 - strlen(DATA_DIR) - 1);
163    if (!succ) {
164       fprintf(stderr, "Error: Failed to get application bundle path\n");
165       exit(1);
166    }
167    char *s = strrchr(dataDir, '/');
168    s[1] = '\0';
169    strcat(dataDir, DATA_DIR);
170  #else
171    strncpy(dataDir, DATA_DIR, 1023);
172  #endif // XCODE
173    strcat(dataDir,"/data");
174    sprintf(userDataDir, "%s/.toycars", getenv("HOME"));
175    searchPath = makeSearchDirectories(dataDir, makeSearchDirectories(userDataDir, NULL));
176 #endif // WIN32
177    printf("dataDir = %s\nuserDataDir = %s\n", dataDir, userDataDir);
178 
179 	TiXmlDocument *xmlDoc = findXMLFile("tilesets/default/default.xml", searchPath);
180    if (!xmlDoc) {
181       fl_alert("Could not find default tileset. Make sure we are in the same directory as the toycars app bundle.\n");
182       exit(1);
183    }
184    delete xmlDoc;
185 }
186 
~TrackView()187 TrackView::~TrackView()
188 {
189    if (view)
190       delete view;
191    if (searchPath)
192       freeSearchDirectories(searchPath);
193    if (signs)
194       delete signs;
195 }
196 
setTool(ToolType t)197 void TrackView::setTool(ToolType t)
198 {
199    tool = t;
200    if (tool != kPathTool) {
201       hoverPathVertexExists = false;
202       selectedPathVertexExists = false;
203       hoverPathIntermediateVertexExists = false;
204    }
205    redraw();
206 }
207 
loadTileset(const char * tilesetFilename,short border,short seperation,bool blend)208 ScTilemap* TrackView::loadTileset(const char* tilesetFilename, short border, short seperation, bool blend)
209 {
210 	char path[1024] = "tilesets/";
211 	char filename[1024];
212    char foundPath[1024];
213 
214 	strcat(path, tilesetFilename);
215 	strcat(path, "/");
216 	strcpy(filename, path);
217 	strcat(filename, tilesetFilename);
218 	strcat(filename, ".xml");
219 
220 	// Load the xml file
221 	TiXmlDocument *xmlDoc = findXMLFile(filename, searchPath, foundPath);
222 	if (!xmlDoc)
223 	{
224 		ScThrowErr("xml file not found");
225 	}
226    strcat(foundPath, "/");
227 
228 	// Do the document parsing here
229 	TiXmlElement *xTileset = xmlDoc->FirstChildElement("tileset");
230 	if (!xTileset)
231 	{
232 		xmlDoc->Clear();
233 		delete xmlDoc;
234 		ScThrowErr("xml tileset element not found");
235 	}
236 
237 	TiXmlHandle tilesetHandle(xTileset);
238 
239 	TiXmlText *xImage = tilesetHandle.FirstChild("image").FirstChild().ToText();
240 	if (!xImage)
241 	{
242 		xmlDoc->Clear();
243 		delete xmlDoc;
244 		ScThrowErr("xml image element not found");
245 	}
246 
247 	TiXmlText *xTilewidth = tilesetHandle.FirstChild("tilewidth").FirstChild().ToText();
248 	if (!xTilewidth)
249 	{
250 		xmlDoc->Clear();
251 		delete xmlDoc;
252 		ScThrowErr("xml tilewidth element not found");
253 	}
254 
255 	TiXmlText *xTileheight = tilesetHandle.FirstChild("tileheight").FirstChild().ToText();
256 	if (!xTileheight)
257 	{
258 		xmlDoc->Clear();
259 		delete xmlDoc;
260 		ScThrowErr("xml tileheight element not found");
261 	}
262 
263 	ScPixelData pixelData;
264 	Fl_Image* img;
265 	ScTilemap* tilemap;
266 	int tileWidth = strtol(xTilewidth->Value(), (char **)NULL, 10);
267 	int tileHeight = strtol(xTileheight->Value(), (char **)NULL, 10);
268 
269 	if (tileWidth <= 0)
270 		ScThrowErr("tilewidth is invalid string");
271 	if (tileHeight <= 0)
272 		ScThrowErr("tileheight is invalid string");
273 
274 	// create the tile map
275 	strcpy(filename, foundPath);
276 	strcat(filename, path);
277 	strcat(filename, xImage->Value());
278 	img = loadImage(filename);
279 	if (img == NULL)
280 		ScThrowErr("Could not load tiles image file");
281 	PixelDataForImage(img, pixelData);
282 	tilemap = new ScTilemap(pixelData, tileWidth, tileWidth, border, seperation, blend);
283 	delete img;
284 
285 	printf("successfully created tileset from %s\n", filename);
286 
287 	xmlDoc->Clear();
288    delete xmlDoc;
289 	return tilemap;
290 }
291 
resetScroll()292 void TrackView::resetScroll()
293 {
294    view->scrollTo(0,0);
295    boundScroll();
296    updateScrollBars();
297    redraw();
298 }
299 
newMap(int w,int h)300 void TrackView::newMap(int w, int h)
301 {
302 	tiles->newMap(w, h, kWallTile);
303    pathPolygon.clear();
304    redraw();
305    ui->setChanged(false);
306    resetScroll();
307 }
308 
readPointsFromString(const char * str,Tuple * points,short & count)309 void readPointsFromString(const char* str, Tuple* points, short& count)
310 {
311 //    char *word, *last;
312     char *word;
313     char *temp = strdup(str);
314 
315     for (   word = strtok_r(temp, ";", &last), count = 0;
316             word;
317             word = strtok_r(NULL, ";", &last), count++)
318     {
319         points[count].x = atof(word);
320         if ((word = strchr(word, ',')) == NULL)
321             ScThrowErr("Badly formatted points list");
322         points[count].y = atof(word + 1);
323     }
324 
325     free(temp);
326 }
327 
readIntervalsFromString(const char * str,RoadSignInterval * intervals,short & count)328 void readIntervalsFromString(const char* str, RoadSignInterval* intervals, short& count)
329 {
330    //    char *word, *last;
331    char *word;
332    char *temp = strdup(str);
333 
334    for ( word = strtok_r(temp, ";", &last), count = 0;
335          word;
336          word = strtok_r(NULL, ";", &last), count++)
337    {
338       intervals[count].type = (SignType) atoi(word);
339       if ((word = strchr(word, ',')) == NULL)
340          ScThrowErr("Badly formatted intervals list");
341       word++;
342       intervals[count].start = atof(word);
343       if ((word = strchr(word, ',')) == NULL)
344          ScThrowErr("Badly formatted intervals list");
345       word++;
346       intervals[count].end = atof(word);
347    }
348 
349    free(temp);
350 }
351 
loadMap(const char * fullFilename)352 void TrackView::loadMap(const char *fullFilename)
353 {
354 	char filename[1024];
355    char *temp;
356 
357 	// Load the xml file
358 	TiXmlDocument *xmlDoc = new TiXmlDocument(fullFilename);
359 	if (!xmlDoc->LoadFile())
360 	{
361 		delete xmlDoc;
362 		ScThrowErr("xml file not found");
363 	}
364 
365 	TiXmlHandle docHandle(xmlDoc);
366 
367 	TiXmlElement *xTrack = docHandle.FirstChildElement("track").Element();
368 	TiXmlText *xTileset = docHandle.FirstChildElement("track").FirstChild("tileset").FirstChild().ToText();
369 	TiXmlText *xTilemap = docHandle.FirstChildElement("track").FirstChild("tilemap").FirstChild().ToText();
370 	TiXmlElement *xStartline = docHandle.FirstChildElement("track").FirstChildElement("startline").Element();
371 	TiXmlText *xPath = docHandle.FirstChildElement("track").FirstChild("path").FirstChild().ToText();
372 	TiXmlText *xRoadSigns = docHandle.FirstChildElement("track").FirstChild("roadsigns").FirstChild().ToText();
373 
374 	// do error checking
375 	if (!(xTrack && xTileset && xTilemap && xStartline && xPath))
376 	{
377 		xmlDoc->Clear();
378 		delete xmlDoc;
379 		if (!xTrack)
380 			ScThrowErr("xml track element not found");
381 		if (!xTileset)
382 			ScThrowErr("xml tileset element not found");
383 		if (!xTilemap)
384 			ScThrowErr("xml tilemap element not found");
385 		if (!xStartline)
386 			ScThrowErr("xml startline element not found");
387 		if (!xPath)
388 			ScThrowErr("xml path element not found");
389 		if (!xRoadSigns)
390 			ScThrowErr("xml roadsigns element not found");
391 	}
392 
393    if (xRoadSigns == NULL)
394       printf("warning: xml roadsigns element not found. Continuing without it.\n");
395 
396 	// load the tilemap from the specified tilemap file
397 	strncpy(filename, fullFilename, 1023);
398 	temp = (char *)strrchr(filename, '/');
399 	if (!temp)
400 		ScThrowErr("xml filename is not absolute");
401    temp[1] = '\0';
402 	strcat(filename, xTilemap->Value());
403 	try { tiles->loadMap(filename); }
404 	catch (ScException e) {
405       e.printMsg();
406       newMap(80,50);
407       return;
408    }
409 
410 	// the path
411 	pathPolygon.clear();
412 	if (xPath != NULL) {
413 		Tuple points[16384];
414 		short pointsCount;
415 		readPointsFromString(xPath->Value(), points, pointsCount);
416 		for (short k = 0; k < pointsCount; k++) {
417 			pathPolygon.push_back(points[k] * tiles->getTileWidth() / kScale);
418 		}
419 	}
420 
421 	// the roadsigns
422 	signIntervals.clear();
423 	if (xRoadSigns != NULL) {
424 		RoadSignInterval intervals[16384];
425 		short count;
426 		readIntervalsFromString(xRoadSigns->Value(), intervals, count);
427 		for (short k = 0; k < count; k++) {
428          intervals[k].startPoint = computePathLocation(intervals[k].start);
429          intervals[k].endPoint = computePathLocation(intervals[k].end);
430 			signIntervals.push_back(intervals[k]);
431 		}
432 	}
433 
434 	// get the start line attributes
435 	Tuple p1, p2;
436    const char *directionStr;
437 	if (!(xStartline->Attribute("x1", &p1.x) && xStartline->Attribute("y1", &p1.y) &&
438 			xStartline->Attribute("x2", &p2.x) && xStartline->Attribute("y2", &p2.y) && (directionStr = xStartline->Attribute("direction"))))
439 	{
440 		xmlDoc->Clear();
441 		delete xmlDoc;
442 		ScThrowErr("xml startline attributes not set");
443 	}
444 	p1.x *= tiles->getTileWidth();
445 	p2.x *= tiles->getTileWidth();
446 	p1.y *= tiles->getTileHeight();
447 	p2.y *= tiles->getTileHeight();
448 	if (strcmp(directionStr, "clockwise") == 0) {
449       clockwise = true;
450    }
451    else if (strcmp(directionStr, "anti-clockwise") == 0) {
452       clockwise = false;
453    }
454    else {
455       ScThrowErr("xml startline attribute direction is invalid");
456    }
457 
458 	startLine->setLine(p1, p2);
459 	if (clockwise)
460       ui->clockwiseMenuItem->set();
461    else
462       ui->clockwiseMenuItem->clear();
463 
464 	// Clear and delete our allocated document and return success ;)
465 	xmlDoc->Clear();
466 	delete xmlDoc;
467 
468 	printf("loading done\n");
469    redraw();
470    ui->setChanged(false);
471    resetScroll();
472 }
473 
init()474 void TrackView::init()
475 {
476    try {
477 	ScTilemap* tilemap = loadTileset("default");	// will get deleted when animation destructor is called
478 
479 	animation.push_back(tilemap);
480 	tiles = tilemap;
481    printf("Current window dims: %d %d\n", this->w(), this->h());
482 	ScPortRect portRect = {0, 0, this->w(), this->h()};
483 	ScRect viewRect = {0, 0, portRect.width, portRect.height};
484 	view = new ScView(portRect, viewRect, animation);
485 	tilemap->newMap(80, 50, kWallTile);
486 
487 	//list<Player*> emptyPlayersList;
488 	//minimap = new Minimap(*tilemap, emptyPlayersList);
489 
490 	animation.push_back(new PathLine(tilemap->getBounds(), &pathPolygon));
491 
492 	startLine = new Startline(Tuple(viewRect.right*0.5,viewRect.top*0.75+viewRect.bottom*0.25),
493 										 Tuple(viewRect.right*0.5,viewRect.top*0.25+viewRect.bottom*0.75),
494 										 kDefaultStartLineWidth, true);
495 	animation.push_back(startLine);
496 
497    char signsFile[1024];
498    strcpy(signsFile, dataDir);
499    strcat(signsFile, "/images/signs.png");
500    //printf("f = %s\n", signsFile);
501    Fl_Image * signsImg = loadImage(signsFile);
502    if (signsImg == NULL) {
503       ScThrowErr("Could not find signs image file");
504    }
505 
506    ScPixelData signsData;
507    PixelDataForImage(signsImg, signsData);
508    signs = new ScSprite(64, 64, 0, signsData);
509    delete signsImg;
510 
511    updateScrollBars();
512    }
513    catch (ScException e) {
514       printf("Failed to initialise. Make sure we are in the same directory as the toycars app bundle.\n");
515       e.printMsg();
516       exit(1);
517    }
518 }
519 
computeTotalPathLength()520 double TrackView::computeTotalPathLength() {
521    double dist = 0;
522    list<Tuple>::const_iterator tmp;
523    for (list<Tuple>::const_iterator it = pathPolygon.begin(); it != pathPolygon.end(); it++) {
524       tmp = it;
525       tmp++;
526       if (tmp == pathPolygon.end())
527          tmp = pathPolygon.begin();
528       dist += (*tmp - *it).Length();
529    }
530    return dist;
531 }
532 
533 /*
534 double TrackView::computeCursorPathPosition() {
535    if (pathPolygon.size() > 1) {
536       if (hoverPathVertexExists) {
537          double dist = 0;
538          list<Tuple>::const_iterator tmp;
539          for (list<Tuple>::const_iterator it = pathPolygon.begin(); it != pathPolygon.end(); it++) {
540             if (it == hoverPathVertex)
541                return dist;
542             tmp = it;
543             tmp++;
544             if (tmp == pathPolygon.end())
545                tmp = pathPolygon.begin();
546             dist += (*tmp - *it).Length();
547          }
548       }
549       else if (hoverPathIntermediateVertexExists) {
550          double dist = 0;
551          list<Tuple>::const_iterator tmp, it;
552          for (it = pathPolygon.begin(); it != pathPolygon.end(); it++) {
553             tmp = it;
554             tmp++;
555             if (tmp == pathPolygon.end())
556                tmp = pathPolygon.begin();
557             if (tmp == hoverPathIntermediateNeighbour)
558                break;
559             dist += (*tmp - *it).Length();
560          }
561          dist += (*it - hoverPathIntermediateVertex).Length();
562          return dist;
563       }
564    }
565 
566    return -1;
567 }
568 */
569 
computeNearestPathPosition(Tuple pos)570 double TrackView::computeNearestPathPosition(Tuple pos) {
571    double bestPathDist = 0;
572    double curPathDist = 0;
573    double bestNormDist = INFINITY;
574    Tuple p, norm;
575    double len, x, dist;
576    for (list<Tuple>::iterator it = pathPolygon.begin(); it != pathPolygon.end(); it++) {
577       list<Tuple>::iterator next = it;
578       next++;
579       if (next == pathPolygon.end())
580          next = pathPolygon.begin();
581       // project pos onto the line it..next
582       p = *next - *it;
583       len = p.Normalise();
584       x = p * (pos - *it);
585       // check if this edge is the closest to pos so far
586       if (0 <= x && x <= len) {
587          norm = (x * p) + *it;
588          dist = (norm - pos).Norm();
589          if (dist < bestNormDist) {
590             bestNormDist = dist;
591             bestPathDist = curPathDist + x;
592          }
593       }
594       // check if this vertrex is the closest to pos so far
595       dist = (*it - pos).Norm();
596       if (dist < bestNormDist) {
597          bestNormDist = dist;
598          bestPathDist = curPathDist;
599       }
600       curPathDist += len;
601    }
602    return bestPathDist;
603 }
604 
handle(int event)605 int TrackView::handle(int event) {
606    int x, y;
607    Tuple mpos;
608    if (view) {
609       x = Fl::event_x();
610       y = h() - Fl::event_y();
611       x += view->getViewRect().left;
612       y += view->getViewRect().bottom;
613       mpos.x = x / kScale;
614       mpos.y = y / kScale;
615    }
616    switch(event) {
617       case FL_FOCUS:
618       case FL_UNFOCUS:
619          return 1;
620       case FL_KEYDOWN:
621          if (Fl::event_key() == FL_Delete) {
622             if (tool == kRoadSignTool) {
623                if (signIntervalStartMouse) {
624                   for (list<RoadSignInterval>::iterator it = signIntervals.begin(); it != signIntervals.end(); it++) {
625                      if (&(*it) == signIntervalStartMouse) {
626                         signIntervals.erase(it);
627                         signIntervalStartMouse = NULL;
628                         signIntervalEndMouse = NULL;
629                         redraw();
630                         break;
631                      }
632                   }
633                }
634                else if (signIntervalEndMouse) {
635                   for (list<RoadSignInterval>::iterator it = signIntervals.begin(); it != signIntervals.end(); it++) {
636                      if (&(*it) == signIntervalEndMouse) {
637                         signIntervals.erase(it);
638                         signIntervalStartMouse = NULL;
639                         signIntervalEndMouse = NULL;
640                         redraw();
641                         break;
642                      }
643                   }
644                }
645             }
646             else if (selectedPathVertexExists) {
647                pathPolygon.erase(selectedPathVertex);
648                if (pathPolygon.empty()) {
649                   selectedPathVertexExists = false;
650                }
651                else {
652                   selectedPathVertexExists = true;
653                   selectedPathVertex = pathPolygon.end();
654                   selectedPathVertex--;
655                }
656                hoverPathVertexExists = false;
657                ui->setChanged(true);
658                redraw();
659                return 1;
660             }
661          }
662          return Fl_Gl_Window::handle(event);
663       case FL_ENTER:
664       case FL_LEAVE:
665          return 1;
666       case FL_MOVE:
667       {
668             bool oldHoverPathIntermediateVertexExists = hoverPathIntermediateVertexExists;
669             bool oldHoverPathVertexExists = hoverPathVertexExists;
670             hoverPathIntermediateVertexExists = false;
671             hoverPathVertexExists = false;
672             if (tool == kRoadSignTool) {
673                signIntervalEndMouse = NULL;
674                signIntervalStartMouse = NULL;
675                signEnd = computeNearestPathPosition(mpos);
676                if (signEnd < signStart)
677                   signEnd += computeTotalPathLength();
678                if (drawingSignInterval)
679                   redraw();
680                for (list<RoadSignInterval>::iterator it = signIntervals.begin(); it != signIntervals.end(); it++) {
681                   if (sqr(mpos.x - it->startPoint.x) + sqr(mpos.y - it->startPoint.y) < sqr(kIntervalHandleRadius)) {
682                      signIntervalStartMouse = &(*it);
683                   }
684                   if (sqr(mpos.x - it->endPoint.x) + sqr(mpos.y - it->endPoint.y) < sqr(kIntervalHandleRadius)) {
685                      signIntervalEndMouse = &(*it);
686                   }
687                }
688             }
689             if (tool == kPathTool || tool == kRoadSignTool) {
690 
691                // look to see if hovering cursor over a vertex in the path polygon
692                for (list<Tuple>::iterator it = pathPolygon.begin(); it != pathPolygon.end(); it++) {
693                   if (((*it) - mpos).Norm() < sqr(kVertexSelectMinDist)) {
694                      hoverPathVertex = it;
695                      hoverPathVertexExists = true;
696                   }
697                }
698                if (oldHoverPathVertexExists != hoverPathVertexExists)
699                   redraw();
700                if (hoverPathVertexExists) {
701                   return 1;
702                }
703 
704                // look to see if hovering cursor over a point on an edge of the path polygon
705                if (pathPolygon.size() <= 1)
706                   return 1;
707                for (list<Tuple>::iterator it = pathPolygon.begin(); it != pathPolygon.end(); it++) {
708                   list<Tuple>::iterator next = it;
709                   next++;
710                   if (next == pathPolygon.end())
711                      next = pathPolygon.begin();
712                   // project mpos onto the line it..next
713                   Tuple p = *next - *it;
714                   double len = p.Normalise();
715                   double x = p * (mpos - *it);
716                   if (0 <= x && x <= len) {
717                      hoverPathIntermediateVertex = (x * p) + *it;
718                      if ((hoverPathIntermediateVertex - mpos).Norm() < sqr(kPathSelectMinDist)) {
719                         hoverPathIntermediateVertexExists = true;
720                         hoverPathIntermediateNeighbour = next;
721                         redraw();
722                         break;
723                      }
724                   }
725                }
726                if (oldHoverPathIntermediateVertexExists != hoverPathIntermediateVertexExists)
727                   redraw();
728             }
729          }
730          return 1;
731       case FL_DRAG:
732       case FL_PUSH: {
733          int b = Fl::event_button();
734          if (event == FL_PUSH) {
735             click_x = x;
736             click_y = y;
737             drag_x = Fl::event_x();
738             drag_y = Fl::event_y();
739          }
740          const ToolType saveTool = tool;
741          if (b == FL_RIGHT_MOUSE)
742             tool = kScrollTool;
743          switch (tool) {
744             case kScrollTool:
745                if (event == FL_DRAG) {
746                   view->scrollBy(drag_x - Fl::event_x(), Fl::event_y() - drag_y);
747                   boundScroll();
748                   updateScrollBars();
749                }
750                break;
751             case kDrawTool:
752                if (b == FL_LEFT_MOUSE || b == FL_RIGHT_MOUSE) {
753                   int col = x / tiles->getTileWidth();
754                   int row = y / tiles->getTileHeight();
755                   int t=0;
756                   switch (tile) {case kRoad: t=(col%5)+5*(row%5)+kStartRoadTiles; break; case kOffroad: t=(col%5)+5*(row%5)+kStartOffroadTiles; break; case kWall: t=kWallTile; break;}
757                   tiles->getTileAtPoint(x,y) = b == FL_LEFT_MOUSE ? t : kWallTile;
758                   ui->setChanged(true);
759                }
760                break;
761             case kPathTool:
762                if (hoverPathVertexExists && b == FL_LEFT_MOUSE) {
763                   ui->setChanged(true);
764                   selectedPathVertex = hoverPathVertex;
765                   if (event == FL_DRAG)
766                      *selectedPathVertex = mpos;
767                   selectedPathVertexExists = true;
768                   ui->setChanged(true);
769                   break;
770                }
771                if (hoverPathIntermediateVertexExists && b == FL_LEFT_MOUSE) {
772                   ui->setChanged(true);
773                   pathPolygon.insert(hoverPathIntermediateNeighbour, hoverPathIntermediateVertex);
774                   list<Tuple>::iterator temp = hoverPathIntermediateNeighbour;
775                   temp--;
776                   selectedPathVertex = temp;
777                   hoverPathVertex = selectedPathVertex;
778                   hoverPathIntermediateVertexExists = false;
779                   hoverPathVertexExists = true;
780                   selectedPathVertexExists = true;
781                   ui->setChanged(true);
782                   break;
783                }
784                if (b == FL_LEFT_MOUSE) {
785                   ui->setChanged(true);
786                   if (selectedPathVertexExists && Fl::event_shift()) {
787                      selectedPathVertex->x = x/kScale;
788                      selectedPathVertex->y = y/kScale;
789                   }
790                   else {
791                      pathPolygon.push_back(Tuple(x,y)/kScale);
792                      hoverPathVertex = pathPolygon.end();
793                      hoverPathVertex--;
794                      selectedPathVertex = hoverPathVertex;
795                      hoverPathVertexExists = true;
796                      selectedPathVertexExists = true;
797                   }
798                }
799                break;
800             case kStartlineTool:
801                if (b == FL_LEFT_MOUSE) {
802                   ui->setChanged(true);
803                   startLine->setLine(Tuple(click_x,click_y),Tuple(x,y));
804                }
805                if (b == FL_RIGHT_MOUSE) {
806                   ui->setChanged(true);
807                   startLine->setLine(startLine->getStartPoint(),Tuple(x,y));
808                }
809                break;
810             case kRoadSignTool:
811                if (b == FL_LEFT_MOUSE) {
812                   ui->setChanged(true);
813                   if (signIntervalStartMouse) {
814                      signIntervalStartMouse->start = computeNearestPathPosition(mpos);
815                      signIntervalStartMouse->startPoint = computePathLocation(signIntervalStartMouse->start);
816                      const double pathLen = computeTotalPathLength();
817                      if (signIntervalStartMouse->start > signIntervalStartMouse->end)
818                         signIntervalStartMouse->end += pathLen;
819                      else if (signIntervalStartMouse->end - signIntervalStartMouse->start > pathLen)
820                         signIntervalStartMouse->end -= pathLen;
821                   }
822                   else if (signIntervalEndMouse) {
823                      signIntervalEndMouse->end = computeNearestPathPosition(mpos);
824                      signIntervalEndMouse->endPoint = computePathLocation(signIntervalEndMouse->end);
825                      const double pathLen = computeTotalPathLength();
826                      if (signIntervalEndMouse->start > signIntervalEndMouse->end)
827                         signIntervalEndMouse->end += pathLen;
828                      else if (signIntervalEndMouse->end - signIntervalEndMouse->start > pathLen)
829                         signIntervalEndMouse->end -= pathLen;
830                   }
831                   else if (event == FL_PUSH) {
832                      //double curPos = computeCursorPathPosition();
833                      double curPos = computeNearestPathPosition(mpos);
834                      if (drawingSignInterval) {
835                         drawingSignInterval = false;
836                         if (curPos < signStart)
837                            curPos += computeTotalPathLength();
838                         signIntervals.push_back(RoadSignInterval(sign, signStart, curPos,
839                                                 computePathLocation(signStart), computePathLocation(curPos)));
840                      }
841                      else {
842                         drawingSignInterval = true;
843                         signStart = curPos;
844                         signEnd = signStart;
845                      }
846                   }
847                }
848                break;
849          }
850          tool = saveTool;
851          if (event == FL_DRAG) {
852             drag_x = Fl::event_x();
853             drag_y = Fl::event_y();
854          }
855          redraw();
856          return 1;
857       }/*
858       case FL_MOUSEWHEEL:
859          view->scrollBy(Fl::event_dx(), -Fl::event_dy());
860          boundScroll();
861          updateScrollBars();
862          redraw();
863          return 1;*/
864       /*
865       case FL_RELEASE:
866          printf("b: %d x: %d y: %d\n", Fl::event_button(), Fl::event_x(), Fl::event_y());
867          redraw();
868          return 1;
869       case FL_SHORTCUT:
870          if (Fl::event_key() == 'x') {
871             printf("you pressed x\n");
872          }
873          redraw();
874          return 0;
875       */
876       default:
877          return Fl_Gl_Window::handle(event);
878    }
879 }
880 
resetImportImageDialog(const char * filename)881 void TrackView::resetImportImageDialog(const char *filename)
882 {
883    Fl_Image *img = loadImage(filename);
884    if (img == NULL)
885       return;
886    ui->importImageBox->image(img);
887    ui->importImageBox->setFilename(filename);
888    ui->roadColourButton->color(FL_BACKGROUND_COLOR);
889    ui->offroadColourButton->color(FL_BACKGROUND_COLOR);
890    ui->wallColourButton->color(FL_BACKGROUND_COLOR);
891    ui->roadColourButton->clear();
892    ui->offroadColourButton->clear();
893    ui->wallColourButton->clear();
894    ui->roadColourButton->set();
895 }
896 
compareColours(const unsigned char * c1,const unsigned char * c2)897 bool compareColours(const unsigned char *c1, const unsigned char *c2)
898 {
899    return c1[0] == c2[0] && c1[1] == c2[1] && c1[2] == c2[2];
900 }
901 
importImage(const char * fullFilename)902 void TrackView::importImage(const char *fullFilename)
903 {
904 	Fl_Image	*img;
905 	unsigned short width, height, bpp, pad_bytes;
906 	unsigned char value8;
907 	int i, j;
908 	unsigned char *cur;
909 
910 	/* Try to load the image file */
911 	img = loadImage(fullFilename);
912 	if (img == NULL) {
913 		ScThrowErr("Error opening image file");
914 	}
915 
916 	/* calculate some numbers */
917 	width = img->w();
918 	height = img->h();
919 	bpp = img->d();
920 	/*pad_bytes = (4 - (width%4)) % 4;*/
921 	//pad_bytes = surface->pitch/bpp - width;
922 	pad_bytes = 0;
923 
924 	//printf("testing: %d should be equal to %d\n", pad_bytes, (4 - (width%4)) % 4);
925 
926    printf("bpp: %d count: %d\n", bpp, img->count());
927 
928 	newMap(width, height);
929 
930 	/* Write map tile data to tilemap */
931 	cur = (unsigned char *)img->data()[0];
932 	for (i = 0; i < height; i++)
933 	{
934 		for (j = 0; j < width; j++)
935 		{
936          if (compareColours(cur, ui->importImageBox->getRoadColour()))
937             value8 = 5*(i%5) + (j%5) + kStartRoadTiles;
938          else if (compareColours(cur, ui->importImageBox->getOffroadColour()))
939             value8 = 5*(i%5) + (j%5) + kStartOffroadTiles;
940          else// if (compareColours(cur, importImageBox->getWallColour())
941             value8 = kWallTile;
942 
943 			(*tiles)(j,height-i-1) = value8;
944 			cur += bpp;
945 		}
946 		cur += pad_bytes;
947 	}
948 
949 	delete img;
950 
951 	printf("loading from image done\n");
952    ui->setChanged(false);
953    resetScroll();
954 }
955 
956 // installs map into user data directory
installMap(const char * name)957 void TrackView::installMap(const char *name)
958 {
959 #if 1
960    fl_alert("Unfortunately this is unsupported at this time.\nYou may still install maps manually by saving them to a folder and placing it inside the data\\tracks directory and editing the tracklist.xml file.");
961    return;
962 #else
963    char fullname[1024];
964    char trackname[256];
965    const char kTracklistName[64] = "/tracks/tracklist.xml";
966    char *temp;
967 
968    // first we uninstall the named track
969    uninstallMap(name);
970 
971    // chop off any .xml suffix
972    temp = strrchr(name, '.');
973    if (temp) {
974       if (strcmp(temp, ".xml") == 0)
975          temp[0] = '\0';
976    }
977    // remove path
978    temp = strrchr(name, '/');
979    if (temp) {
980       strncpy(trackname, temp+1, 255);
981    }
982 
983    strncpy(fullname, userDataDir, 1023 - strlen(kTracklistName));
984    strcat(fullname, kTracklistName);
985 
986 	// Load the xml file
987 	TiXmlDocument *xmlDoc = new TiXmlDocument(fullname);
988 	if (!xmlDoc->LoadFile())
989 	{
990 		delete xmlDoc;
991 		ScThrowErr("xml file not found");
992 	}
993 
994 	// Add name to track list
995 	TiXmlHandle *docHandle = new TiXmlHandle(xmlDoc);
996 	TiXmlElement *xTracklist = docHandle->FirstChildElement("tracklist").Element();
997 	if (xTracklist == NULL)
998 		ScThrowErr("xml file could not find tracklist element");
999    TiXmlElement track("track");
1000    track.SetAttribute("name", trackname);
1001    xTracklist->InsertEndChild(track);
1002 
1003 	// Save the xml file
1004 	if (!xmlDoc->SaveFile())
1005 	{
1006 		delete xmlDoc;
1007 		ScThrowErr("error saving xml file");
1008 	}
1009 
1010 	//delete xTracklist;
1011 	delete docHandle;
1012 	delete xmlDoc;
1013 
1014    strncpy(fullname, userDataDir, 1023 - 8);
1015    strcat(fullname, "/tracks/");
1016    strcat(fullname, trackname);
1017 
1018    // make sure the tracks directory exists. nb: what whould be done on win32 here???
1019    char str[1024];
1020    sprintf(str, "mkdir -p %s", fullname);
1021    system(str);
1022 
1023    strcat(fullname, "/");
1024    strcat(fullname, trackname);
1025    strcat(fullname, ".xml");
1026    saveMap(fullname);
1027 
1028 	printf("install done\n");
1029 #endif
1030 }
1031 
1032 // uninstall track from user data directory.
1033 // nb: doesn't remove track data itself, only remove entry from tracklist xml file.
uninstallMap(const char * name)1034 void TrackView::uninstallMap(const char *name)
1035 {
1036 #if 1
1037    fl_alert("Unfortunately this is unsupported at this time.\nYou may still uninstall maps manually by removing them from the data\\tracks directory and editing the tracklist.xml file.");
1038    return;
1039 #else
1040    char fullname[1024];
1041    char trackname[256];
1042    const char kTracklistName[64] = "/tracks/tracklist.xml";
1043 	const char *attribute;
1044    char *temp;
1045 
1046    // chop off any .xml suffix
1047    temp = strrchr(name, '.');
1048    if (temp) {
1049       if (strcmp(temp, ".xml") == 0)
1050          temp[0] = '\0';
1051    }
1052    // remove path
1053    temp = strrchr(name, '/');
1054    if (temp) {
1055       strncpy(trackname, temp+1, 255);
1056    }
1057 
1058    strncpy(fullname, userDataDir, 1023 - strlen(kTracklistName));
1059    strcat(fullname, kTracklistName);
1060 
1061 	// Load the xml file
1062 	TiXmlDocument *xmlDoc = new TiXmlDocument(fullname);
1063 	if (!xmlDoc->LoadFile())
1064 	{
1065 		delete xmlDoc;
1066 		ScThrowErr("xml file not found");
1067 	}
1068 
1069 	// Remove all occurrences of the track the from track list
1070 	TiXmlHandle *docHandle = new TiXmlHandle(xmlDoc);
1071 	TiXmlElement *xTracklist = docHandle->FirstChildElement("tracklist").Element();
1072 	if (xTracklist == NULL)
1073 		ScThrowErr("xml file could not find tracklist element");
1074    TiXmlElement *track = xTracklist->FirstChildElement("track");
1075    TiXmlElement *prevTrack;
1076 	while (track != NULL)
1077 	{
1078 		attribute = track->Attribute("name");
1079 		if (attribute == NULL)
1080 			ScThrowErr("xml track element does not have attribute called \"name\"");
1081 		prevTrack = track;
1082 		track = track->NextSiblingElement("track");
1083       if (strcmp(attribute, trackname) == 0) {
1084          xTracklist->RemoveChild(prevTrack);
1085       }
1086 	}
1087 
1088    // Save the xml file
1089    if (!xmlDoc->SaveFile()) {
1090       delete xmlDoc;
1091       ScThrowErr("error saving xml file");
1092    }
1093 
1094 	//delete xTracklist;
1095 	delete docHandle;
1096 	delete xmlDoc;
1097 
1098 	printf("uninstall done\n");
1099 #endif
1100 }
1101 
saveMap(const char * fullFilename)1102 void TrackView::saveMap(const char *fullFilename)
1103 {
1104 	char filename[1024] = "";
1105    char trackname[256] = "";
1106    char tempStr[256];
1107    char longStr[16384] = "";
1108    char *suffix;
1109    Tuple startLineA = startLine->getStartPoint();
1110    Tuple startLineB = startLine->getEndPoint();
1111 
1112 	// Setup the xml file
1113 	//printf("xml filename is %s\n", filename);
1114 	TiXmlDocument *xmlDoc = new TiXmlDocument(fullFilename);
1115 
1116    TiXmlDeclaration decl("1.0", "UTF-8", "yes");
1117 	TiXmlElement xTrack("track");
1118 	TiXmlElement xTileset("tileset");
1119 	TiXmlElement xTilemap("tilemap");
1120 	TiXmlElement xStartline("startline");
1121 	TiXmlElement xPath("path");
1122 	TiXmlElement xRoadSigns("roadsigns");
1123 
1124 	xTileset.InsertEndChild(TiXmlText("default"));
1125 
1126 	startLineA.x /= tiles->getTileWidth();
1127 	startLineA.y /= tiles->getTileHeight();
1128 	startLineB.x /= tiles->getTileWidth();
1129 	startLineB.y /= tiles->getTileHeight();
1130 	xStartline.SetDoubleAttribute("x1", startLineA.x);
1131 	xStartline.SetDoubleAttribute("y1", startLineA.y);
1132 	xStartline.SetDoubleAttribute("x2", startLineB.x);
1133 	xStartline.SetDoubleAttribute("y2", startLineB.y);
1134 	xStartline.SetAttribute("direction", clockwise ? "clockwise" : "anti-clockwise");
1135 
1136    strncpy(filename, fullFilename, 1023);
1137    suffix = strrchr(filename, '.');
1138    if (!suffix)
1139       ScThrowErr("File has no suffix");
1140    if (strcmp(suffix,".xml") != 0)
1141       ScThrowErr("File suffix is not .xml");
1142    suffix[0] = '\0';
1143 
1144    suffix = strrchr(filename, '/');
1145    suffix[0] = '\0';
1146    if (!suffix)
1147       ScThrowErr("File path is not absolute");
1148    strncpy(trackname, &suffix[1], 255);
1149 
1150 	strcat(trackname, ".map");
1151 	xTilemap.InsertEndChild(TiXmlText(trackname));
1152 
1153    // create the path element
1154 	for (list<Tuple>::const_iterator i = pathPolygon.begin(); i != pathPolygon.end(); i++) {
1155 		sprintf(tempStr, "%.2lf,%.2lf; ", i->x / tiles->getTileWidth() * kScale, i->y / tiles->getTileWidth() * kScale);
1156 		strcat(longStr, tempStr);
1157 	}
1158 	if (!pathPolygon.empty()) {
1159 		longStr[strlen(longStr) - 2] = '\0';
1160 		xPath.InsertEndChild(TiXmlText(longStr));
1161 	}
1162 	else {
1163 		xPath.InsertEndChild(TiXmlText(""));
1164 	}
1165 
1166    // create the roadsigns element
1167    longStr[0] = '\0';
1168 	for (list<RoadSignInterval>::const_iterator i = signIntervals.begin(); i != signIntervals.end(); i++) {
1169 		sprintf(tempStr, "%d,%.2lf,%.2lf; ", i->type, i->start, i->end);
1170 		strcat(longStr, tempStr);
1171 	}
1172 	if (!signIntervals.empty()) {
1173 		longStr[strlen(longStr) - 2] = '\0';
1174 		xRoadSigns.InsertEndChild(TiXmlText(longStr));
1175 	}
1176 	else {
1177 		xRoadSigns.InsertEndChild(TiXmlText(""));
1178 	}
1179 
1180 	xTrack.InsertEndChild(xTileset);
1181 	xTrack.InsertEndChild(xTilemap);
1182 	xTrack.InsertEndChild(xStartline);
1183 	xTrack.InsertEndChild(xPath);
1184 	xTrack.InsertEndChild(xRoadSigns);
1185 
1186 	strcat(filename, "/");
1187 	strcat(filename, trackname);
1188    xmlDoc->InsertEndChild(decl);
1189    xmlDoc->InsertEndChild(xTrack);
1190 	xmlDoc->SaveFile();
1191 	printf("Saving to file %s...\n", filename);
1192 	tiles->saveMap(filename);
1193 
1194 	// Clear and delete our allocated document and return success ;)
1195 	xmlDoc->Clear();
1196 	delete xmlDoc;
1197 
1198 	printf("saving done\n");
1199    ui->setChanged(false);
1200 }
1201 
updateScrollBars()1202 void TrackView::updateScrollBars() {
1203    const float viewHeight = view->getViewRect().top-view->getViewRect().bottom;
1204    const float mapHeight = tiles->getBounds().top-tiles->getBounds().bottom;
1205    const float scrollCentreV = 0.5*(view->getViewRect().bottom+view->getViewRect().top);
1206    const float scrollBarHeight = viewHeight/mapHeight;
1207 
1208    const float viewWidth = view->getViewRect().right-view->getViewRect().left;
1209    const float mapWidth = tiles->getBounds().right-tiles->getBounds().left;
1210    const float scrollCentreH = 0.5*(view->getViewRect().right+view->getViewRect().left);
1211    const float scrollBarWidth = viewWidth/mapWidth;
1212 
1213    ui->vertScroll->value((1 - (scrollCentreV - 0.5*viewHeight)/(mapHeight - viewHeight))*(1-scrollBarHeight)*kScrollbarMaxValue, scrollBarHeight*kScrollbarMaxValue, 0, kScrollbarMaxValue);
1214    ui->horizScroll->value(((scrollCentreH - 0.5*viewWidth)/(mapWidth - viewWidth))*(1-scrollBarWidth)*kScrollbarMaxValue, scrollBarWidth*kScrollbarMaxValue, 0, kScrollbarMaxValue);
1215 }
1216 
drawTrack()1217 void TrackView::drawTrack() {
1218    view->draw();
1219 }
1220 
resize(int x,int y,int w,int h)1221 void TrackView::resize(int x, int y, int w, int h)
1222 {
1223    Fl_Gl_Window::resize(x,y,w,h);
1224    view->resize(this->w(), this->h());
1225    boundScroll();
1226    updateScrollBars();
1227    redraw();
1228 }
1229 
setHorizScrollFrac(double x)1230 void TrackView::setHorizScrollFrac(double x)
1231 {
1232    const float width = view->getViewRect().right - view->getViewRect().left;
1233    view->scrollTo(floor(x*tiles->getBounds().right + 0.5*width), floor(0.5*(view->getViewRect().bottom+view->getViewRect().top)));
1234    boundScroll();
1235    redraw();
1236 }
1237 
setVertScrollFrac(double x)1238 void TrackView::setVertScrollFrac(double x)
1239 {
1240    const float height = view->getViewRect().top - view->getViewRect().bottom;
1241    view->scrollTo(floor(0.5*(view->getViewRect().left+view->getViewRect().right)), floor(tiles->getBounds().top - (x*tiles->getBounds().top + 0.5*height)));
1242    boundScroll();
1243    redraw();
1244 }
1245 
boundScroll()1246 void TrackView::boundScroll()
1247 {
1248    if (view->getViewRect().left < tiles->getBounds().left)
1249       view->scrollBy(-view->getViewRect().left, 0);
1250    if (view->getViewRect().bottom < tiles->getBounds().bottom)
1251       view->scrollBy(0, -view->getViewRect().bottom);
1252    if (view->getViewRect().right > tiles->getBounds().right)
1253       view->scrollBy(tiles->getWidth() - view->getViewRect().right, 0);
1254    if (view->getViewRect().top > tiles->getBounds().top)
1255       view->scrollBy(0, tiles->getHeight() - view->getViewRect().top);
1256 }
1257 
computePathLocation(const double loc)1258 Tuple TrackView::computePathLocation(const double loc) {
1259    if (pathPolygon.size() == 1)
1260       return pathPolygon.front();
1261    if (pathPolygon.empty())
1262       ScThrowErr("Attempt to compute path location but no path exists.");
1263    /*
1264    if (loc < 0)
1265       printf("warning: negative path loc\n");//ScThrowErr("Attempt to compute path location with negative value.");
1266    */
1267    double dist = 0;
1268    list<Tuple>::const_iterator tmp;
1269    while (1) {
1270       for (list<Tuple>::const_iterator it = pathPolygon.begin(); it != pathPolygon.end(); it++) {
1271          tmp = it;
1272          tmp++;
1273          if (tmp == pathPolygon.end())
1274             tmp = pathPolygon.begin();
1275          double segmentDist = (*tmp - *it).Length();
1276          if (dist + segmentDist >= loc) {
1277             double offset = loc - dist;
1278             Tuple N = (*tmp - *it).Direction() * offset;
1279             return *it + N;
1280          }
1281          dist += segmentDist;
1282       }
1283    }
1284 
1285    return Tuple(0,0);
1286 }
1287 
drawSignInterval(RoadSignInterval & interval,bool drawSign)1288 void TrackView::drawSignInterval(RoadSignInterval &interval, bool drawSign) {
1289    const Tuple A = computePathLocation(interval.start);
1290    const Tuple B = computePathLocation(interval.end);
1291    interval.startPoint = A;
1292    interval.endPoint = B;
1293    double dist = 0;
1294    list<Tuple>::const_iterator tmp;
1295    bool pastStart = false;
1296    bool drawingPath = true;
1297 
1298    // draw the path interval
1299    glLineWidth(12);
1300    glColor4f(1,1,1,0.5);
1301    glDisable(GL_TEXTURE_2D);
1302    glEnable(GL_TEXTURE_1D);
1303    glBegin(GL_LINES);
1304 
1305    while (drawingPath) {
1306       for (list<Tuple>::const_iterator it = pathPolygon.begin(); it != pathPolygon.end(); it++) {
1307          tmp = it;
1308          tmp++;
1309          if (tmp == pathPolygon.end())
1310             tmp = pathPolygon.begin();
1311          dist += (*tmp - *it).Length();
1312          if (dist > interval.start) {
1313             if (!pastStart) {
1314                if (dist > interval.end) {
1315                   glVertex2d(A.x * kScale, A.y * kScale);
1316                   glVertex2d(B.x * kScale, B.y * kScale);
1317                   drawingPath = false;
1318                   break;
1319                }
1320                pastStart = true;
1321                // draw from A to *it
1322                glVertex2d(A.x * kScale, A.y * kScale);
1323                glVertex2d(tmp->x * kScale, tmp->y * kScale);
1324                continue;
1325             }
1326          }
1327          if (dist > interval.end && pastStart) {
1328             // draw from *it to B
1329             glVertex2d(it->x * kScale, it->y * kScale);
1330             glVertex2d(B.x * kScale, B.y * kScale);
1331             drawingPath = false;
1332             break;
1333          }
1334          // draw from *it to *tmp
1335          if (pastStart) {
1336             glVertex2d(it->x * kScale, it->y * kScale);
1337             glVertex2d(tmp->x * kScale, tmp->y * kScale);
1338          }
1339       }
1340    }
1341 
1342    glEnd();
1343 
1344    // draw the actual sign
1345    if (drawSign) {
1346       glEnable(GL_TEXTURE_2D);
1347       glDisable(GL_TEXTURE_1D);
1348       signs->setFrame(interval.type);
1349       signs->moveTo(A.x * kScale, A.y * kScale);
1350       signs->doDraw();
1351    }
1352 
1353    // draw handles
1354    glDisable(GL_TEXTURE_2D);
1355    glEnable(GL_TEXTURE_1D);
1356    glColor4f(0.2, 0, 1, 1);
1357    drawCircleAt(A.x, A.y, kIntervalHandleRadius);
1358    glColor4f(1, 0, 0.2, 1);
1359    drawCircleAt(B.x, B.y, kIntervalHandleRadius);
1360 }
1361 
draw()1362 void TrackView::draw() {
1363    if (!view)
1364       init();
1365    if (!valid()) {
1366       //glLoadIdentity();
1367       //glViewport(0,0,w(),h());
1368       //glOrtho(0, w(), 0, h(), -1, 1);
1369       //ortho();
1370       glEnable(GL_BLEND);
1371       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1372    }
1373 
1374    glClear(GL_COLOR_BUFFER_BIT);
1375 
1376    glPushMatrix();
1377 
1378    drawTrack();
1379 
1380    // draw path and road signs
1381    if (hoverPathVertexExists) {
1382       if (tool == kRoadSignTool) {
1383          if (!(signIntervalStartMouse || signIntervalEndMouse || drawingSignInterval)) {
1384             signs->setFrame(sign);
1385             signs->moveTo(hoverPathVertex->x * kScale, hoverPathVertex->y * kScale);
1386             signs->doDraw();
1387          }
1388       }
1389       else {
1390          glColor4f(1.0, 1.0, 1.0, 0.5);
1391          glDisable(GL_TEXTURE_2D);
1392          glEnable(GL_TEXTURE_1D);
1393          drawCircleAt(hoverPathVertex->x, hoverPathVertex->y, kVertexSelectMinDist);
1394       }
1395    }
1396    if (hoverPathIntermediateVertexExists) {
1397       if (tool == kRoadSignTool) {
1398          if (!(signIntervalStartMouse || signIntervalEndMouse || drawingSignInterval)) {
1399             signs->setFrame(sign);
1400             signs->moveTo(hoverPathIntermediateVertex.x * kScale, hoverPathIntermediateVertex.y * kScale);
1401             signs->doDraw();
1402          }
1403       }
1404       else {
1405          glColor4f(0.0, 1.0, 1.0, 0.5);
1406          glDisable(GL_TEXTURE_2D);
1407          glEnable(GL_TEXTURE_1D);
1408          drawCircleAt(hoverPathIntermediateVertex.x, hoverPathIntermediateVertex.y, kPathSelectMinDist);
1409       }
1410    }
1411    if (selectedPathVertexExists) {
1412       glLineWidth(1);
1413       glColor4f(1.0, 0, 0, 0.7);
1414       glDisable(GL_TEXTURE_2D);
1415       glEnable(GL_TEXTURE_1D);
1416       drawCircleAt(selectedPathVertex->x, selectedPathVertex->y, kVertexSelectMinDist);
1417       glColor4f(1.0, 1.0, 1.0, 1.0);
1418       drawCircleAt(selectedPathVertex->x, selectedPathVertex->y, kVertexSelectMinDist, false);
1419    }
1420 
1421    // draw road sign intervals
1422    if (tool == kRoadSignTool || tool == kPathTool) {
1423       for (list<RoadSignInterval>::iterator it = signIntervals.begin(); it != signIntervals.end(); it++) {
1424          drawSignInterval(*it, tool == kRoadSignTool);
1425       }
1426    }
1427 
1428    // drawing interval
1429    if (drawingSignInterval) {
1430       RoadSignInterval i(sign, signStart, signEnd, computePathLocation(signStart), computePathLocation(signEnd));
1431       drawSignInterval(i, false);
1432    }
1433 
1434    // draw selection of interval handle
1435    glLineWidth(1);
1436    glColor4f(1, 1, 1, 1);
1437    glDisable(GL_TEXTURE_2D);
1438    glEnable(GL_TEXTURE_1D);
1439    if (signIntervalStartMouse) {
1440       drawCircleAt(signIntervalStartMouse->startPoint.x, signIntervalStartMouse->startPoint.y, kIntervalHandleRadius, false);
1441    }
1442    if (signIntervalEndMouse) {
1443       drawCircleAt(signIntervalEndMouse->endPoint.x, signIntervalEndMouse->endPoint.y, kIntervalHandleRadius, false);
1444    }
1445 
1446    glPopMatrix();
1447 }
1448 
swap(unsigned char & x,unsigned char & y)1449 void swap(unsigned char &x, unsigned char &y) {
1450    unsigned char z = x;
1451    x = y;
1452    y = z;
1453 }
1454 
flipHorizontal()1455 void TrackView::flipHorizontal() {
1456    const short h = tiles->getMapHeight();
1457    const short w = tiles->getMapWidth();
1458    const short W = tiles->getMapWidth() * tiles->getTileWidth();
1459    Tuple p1, p2;
1460    double lineWidth;
1461    int i, j;
1462 
1463    for (i = 0; i < w/2; i++) {
1464       for (j = 0; j < h; j++) {
1465          swap((*tiles)(i,j), (*tiles)(w-i-1,j));
1466       }
1467    }
1468 
1469    for (list<Tuple>::iterator it = pathPolygon.begin(); it != pathPolygon.end(); it++) {
1470       it->x = W/kScale - it->x;
1471    }
1472    pathPolygon.reverse();
1473 
1474    p1 = startLine->getStartPoint();
1475    p2 = startLine->getEndPoint();
1476    lineWidth = startLine->getWidth();
1477 
1478    p1.x = W - p1.x;
1479    p2.x = W - p2.x;
1480 
1481    startLine->setLine(p2,p1,lineWidth);
1482 }
1483 
flipVertical()1484 void TrackView::flipVertical() {
1485    const short h = tiles->getMapHeight();
1486    const short w = tiles->getMapWidth();
1487    const short H = tiles->getMapHeight() * tiles->getTileHeight();
1488    Tuple p1, p2;
1489    double lineWidth;
1490    int i, j;
1491 
1492    for (i = 0; i < w; i++) {
1493       for (j = 0; j < h/2; j++) {
1494          swap((*tiles)(i,j), (*tiles)(i,h-j-1));
1495       }
1496    }
1497 
1498    for (list<Tuple>::iterator it = pathPolygon.begin(); it != pathPolygon.end(); it++) {
1499       it->y = H/kScale - it->y;
1500    }
1501    pathPolygon.reverse();
1502 
1503    p1 = startLine->getStartPoint();
1504    p2 = startLine->getEndPoint();
1505    lineWidth = startLine->getWidth();
1506 
1507    p1.y = H - p1.y;
1508    p2.y = H - p2.y;
1509 
1510    startLine->setLine(p2,p1,lineWidth);
1511 }
1512