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