9 #define PREFIX "OpenRider Track\n\n"
10 #define PREFIX_SIZE 17
12 define app = ((GuiApplication)__thisModule);
14 #define ROTATION_RESOLUTION 128
20 //sets the angle of the matrix's forward transformation to the angle specified
21 void ToAngle(double radians) {
27 void Scale(double multiplier) {
35 //This function takes src, transforms it at origin sx, sy using the _inverse_ of trans_inv, and places it in dest at dx, dy
36 void Transform888Bitmap(Bitmap src, Bitmap dest, double sx, double sy, double dx, double dy, Matrix2D trans_inv, ColorAlpha background) {
38 int destw=dest.width, desth=dest.height;
40 int srcw=src.width, srch=src.height;
41 long tt0 = (long)(trans_inv.t0*65536);
42 long tt1 = (long)(trans_inv.t1*65536);
43 long tt2 = (long)(trans_inv.t2*65536);
44 long tt3 = (long)(trans_inv.t3*65536);
46 long isx = (long)(sx*65536);
47 long isy = (long)(sy*65536);
48 long t0m, t1m, t2m, t3m, t0mi, t2mi;
49 ColorAlpha *rp = (ColorAlpha*)dest.picture;
51 ColorAlpha *sp = (ColorAlpha*)src.picture;
53 t0mi = (long)(-dx*trans_inv.t0*65536);
54 t2mi = (long)(-dx*trans_inv.t2*65536);
55 t1m = (long)(-dy*trans_inv.t1*65536);
56 t3m = (long)(-dy*trans_inv.t3*65536);
57 for (desty=0; desty<desth; desty++, t1m+=tt1, t3m+=tt3, rp+=dest.stride) {
61 for (destx=0; destx<destw; destx++, t0m+=tt0, t2m+=tt2,p++) {
62 unsigned long aax, aay;
64 unsigned long dma, dmb, dmc, dmd;
65 unsigned short ma,mb,mc,md;
66 dsrcx = t0m + t1m + isx;
67 dsrcy = t2m + t3m + isy;
70 aax = dsrcx & 0xFFFFL;
71 aay = dsrcy & 0xFFFFL;
73 dma = (1UL<<16)-aax-aay+dmd;
76 ma = (unsigned short)(dma>>8);
77 mb = (unsigned short)(dmb>>8);
78 mc = (unsigned short)(dmc>>8);
79 md = (unsigned short)(dmd>>8);
80 s = sp + srcy*src.stride + srcx;
82 if (srcx>=0 && srcx<srcw-1 && srcy>=0 && srcy<srch-1) {
86 c[3] = s[src.stride+1];
92 if (srcx>=0 && srcx<srcw && srcy>=0 && srcy<srch)
94 if (srcx+1>=0 && srcx+1<srcw && srcy>=0 && srcy<srch)
96 if (srcx>=0 && srcx<srcw && srcy+1>=0 && srcy+1<srch)
98 if (srcx+1>=0 && srcx+1<srcw && srcy+1>=0 && srcy+1<srch)
99 c[3] = s[src.stride+1];
101 p->a = (byte)((c[0].a*ma + c[1].a*mb + c[2].a*mc + c[3].a*md) >> 8);
102 p->color.b = (byte)((c[0].color.b*ma + c[1].color.b*mb + c[2].color.b*mc + c[3].color.b*md) >> 8);
103 p->color.g = (byte)((c[0].color.g*ma + c[1].color.g*mb + c[2].color.g*mc + c[3].color.g*md) >> 8);
104 p->color.r = (byte)((c[0].color.r*ma + c[1].color.r*mb + c[2].color.r*mc + c[3].color.r*md) >> 8);
109 int angle2index(double angle) {
111 angle += Pi/ROTATION_RESOLUTION;
112 index = (int)(angle*ROTATION_RESOLUTION/(2*Pi));
113 index %= ROTATION_RESOLUTION;
115 index += ROTATION_RESOLUTION;
119 /*wheel[i] = {alphaBlend=true};
120 wheel[i].Allocate(null, renderwidth, renderwidth, 0, pixelFormat888, false);
122 mat.Scale(64.0/(double)renderwidth);
123 Transform888Bitmap(orig, wheel[i], 31.5, 31.5, ((double)renderwidth-0.5)/2, ((double)renderwidth-0.5)/2, mat, wheel_background);
124 angle += angle_change;*/
126 Bitmap CreateRotatedBitmap(Bitmap original, double scale, double angle, ColorAlpha background) {
127 uint renderwidth = (uint)((double)original.width*scale);
128 Bitmap ret {alphaBlend = true};
130 ret.Allocate(null, renderwidth, renderwidth, 0, pixelFormat888, false);
133 Transform888Bitmap(original, ret, (double)original.width/2-0.5, (double)original.height/2-0.5, ((double)renderwidth-0.5)/2, ((double)renderwidth-0.5)/2, mat, background);
137 enum DrawingTool {browse, pencil, line, eraser};
141 double zoom; //the higher the zoom, the larger things appear
144 static FileFilter fileFilters[] =
146 { "OpenRider track files (*.ort)", "ort" },
147 { "All files", null},
150 static FileType fileTypes[] =
152 { "OpenRider track files", "ort", always }
158 background = activeBorder;
159 borderStyle = sizable;
165 anchor = { horz = -33, vert = -28 };
171 double cstartx, cstarty;
173 DrawingTool drawingTool; //all tools operated with the left mouse button
174 GameLineType gameLineType;
177 double lastFrameTime;
179 property bool running {
181 if (!running_b && value)
182 lastFrameTime = GetTime();
184 goItem.checked = value;
185 wheelSpinner.started = value;
187 centerItem.disabled = value;
188 nudgeLeftItem.disabled = value;
189 nudgeRightItem.disabled = value;
190 nudgeUpItem.disabled = value;
191 nudgeDownItem.disabled = value;
201 Timer wheelSpinner {this, 0.015, started=false;
202 bool DelayExpired() {
203 double t = GetTime();
205 this.modifiedDocument = true;
207 //game.FrameMulti(LOGICAL_FPS/DRAWN_FPS*2);
208 game.FrameMulti((uint)((t-lastFrameTime)*LOGICAL_FPS));
211 //let's center the camera on the ball
213 Coord2D p = game.vehicles[0].location;
214 Vector2D v = game.vehicles[0].velocity;
216 camera[1].x = p.x - v.x*5;
217 camera[1].y = p.y - v.y*5;
224 Bitmap wheel[ROTATION_RESOLUTION];
225 Bitmap wheel_orig {alphaBlend=true};
227 ColorAlpha wheel_background;
231 drawingTool = pencil;
232 gameLineType = regular;
233 FileDialog fileSaveDialog
235 master = this, type = save, text = "Save Track...",
236 types = fileTypes, sizeTypes = sizeof(fileTypes),
237 filters = fileFilters, sizeFilters = sizeof(fileFilters);
239 FileDialog openDialog
241 master = this, type = open, text = "Load Track...",
242 types = fileTypes, sizeTypes = sizeof(fileTypes),
243 filters = fileFilters, sizeFilters = sizeof(fileFilters);
245 saveDialog = fileSaveDialog;
246 Menu fileMenu {menu, "File", f};
247 MenuItem fileNewItem {
248 fileMenu, "New", n, Key {n, ctrl = true};
250 bool NotifySelect(MenuItem selection, Modifiers mods)
252 if (MessageBox {master=this, text = "OpenRider",
253 contents="Are you sure you want to clear the current track and start a new one?",
254 type=yesNo}.Modal() != yes)
257 camera[0] = camera[1] = {0.0,0.0,1.0};
260 this.modifiedDocument = false;
261 this.fileName = null;
266 MenuItem fileOpenItem {
267 fileMenu, "Open...", o, Key {o, ctrl = true};
269 bool NotifySelect(MenuItem selection, Modifiers mods)
271 if (openDialog.Modal() == ok) {
272 OnLoadFile(openDialog.filePath);
273 /*if (!OnLoadFile(openDialog.filePath))
275 MessageBox {master=this, text = "Error opening file.", contents = "Error opening file."}.Modal();
282 MenuItem fileSaveItem { fileMenu, "Save", s, Key {s, ctrl = true}, NotifySelect=MenuFileSave}
283 MenuItem fileSaveAsItem { fileMenu, "Save As...", a, Key {a, ctrl = true}, NotifySelect=MenuFileSaveAs }
284 MenuDivider {fileMenu};
285 MenuItem exitItem {fileMenu, "Exit", x, altF4, NotifySelect = MenuFileExit };
287 Menu viewMenu {menu, "View", v};
288 MenuItem centerItem {viewMenu, "Center on ball", c, tab;
289 bool NotifySelect(MenuItem selection, Modifiers mods) {
291 camera[0].x = game.vehicles[0].location.x;
292 camera[0].y = game.vehicles[0].location.y;
298 MenuDivider {viewMenu};
299 MenuItem zoomInItem {viewMenu, "Zoom in\t+", i;
300 bool NotifySelect(MenuItem selection, Modifiers mods) {
305 MenuItem zoomOutItem {viewMenu, "Zoom out\t-", o;
306 bool NotifySelect(MenuItem selection, Modifiers mods) {
311 MenuDivider {viewMenu};
312 MenuItem nudgeLeftItem {viewMenu, "Left", l, left, NotifySelect = NudgeCallback, id=0}
313 MenuItem nudgeRightItem {viewMenu, "Right", r, right, NotifySelect = NudgeCallback, id=1}
314 MenuItem nudgeUpItem {viewMenu, "Up", u, up, NotifySelect = NudgeCallback, id=2}
315 MenuItem nudgeDownItem {viewMenu, "Down", d, down, NotifySelect = NudgeCallback, id=3}
317 bool NudgeCallback(MenuItem selection, Modifiers mods) {
318 Camera2D *c = camera;
319 double nudgeAmount = 20.0 / c->zoom;
324 if (selection.id == 0)
326 else if (selection.id == 1)
328 else if (selection.id == 2)
338 Menu toolMenu {menu, "Tool", t};
339 MenuItem browseItem {toolMenu, "Browse", b, b, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
340 MenuItem pencilItem {toolMenu, "Draw", d, d, checked=true, isRadio=true, NotifySelect = MyCustomMenu};
341 MenuItem lineItem {toolMenu, "Straight line", s, s, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
342 MenuItem eraserItem {toolMenu, "Eraser", e, e, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
343 MenuDivider {toolMenu};
344 MenuItem normalItem {toolMenu, "Normal", a, a, checked=true, isRadio=true, NotifySelect = MyCustomMenu};
345 MenuItem speedItem {toolMenu, "Right accelerator", r, r, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
346 MenuItem slowItem {toolMenu, "Left accelerator", f, f, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
347 MenuItem decorItem {toolMenu, "Decoration", c, c, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
349 Menu controlMenu {menu, "Control", c};
350 MenuItem goItem {controlMenu, "Go", g, Key {g, ctrl=true}, checked=false, checkable=true;
351 bool NotifySelect(MenuItem selection, Modifiers mods)
353 running = selection.checked;
357 MenuItem resetItem {controlMenu, "Reset Ball", r, Key {r, ctrl=true};
358 bool NotifySelect(MenuItem selection, Modifiers mods)
360 this.modifiedDocument = true;
368 bool MyCustomMenu(MenuItem selection, Modifiers mods) {
369 if (browseItem.checked)
370 drawingTool = browse;
371 else if (pencilItem.checked)
372 drawingTool = pencil;
373 else if (lineItem.checked)
375 else if (eraserItem.checked)
376 drawingTool = eraser;
378 if (normalItem.checked)
379 gameLineType = regular;
380 else if (speedItem.checked)
381 gameLineType = speed;
382 else if (slowItem.checked)
384 else if (decorItem.checked)
385 gameLineType = decor;
391 camera[0] = {0.0, 0.0, 1.0}; //paused camera
392 camera[1] = {0.0, 0.0, 1.0}; //running camera
393 app.timerResolution = 300;
398 bool OnSaveFile(char * fileName)
400 File f = FileOpen(fileName, write);
401 unsigned short mv = (unsigned short)game.version_code;
406 strcpy(vn, version_name);
407 f.Write(PREFIX, 1, PREFIX_SIZE);
413 this.modifiedDocument = false;
417 void OnLoadFile(char * fileName) {
418 File f = FileOpen(fileName, read);
420 char prefix[PREFIX_SIZE];
424 MessageBox {master=this, text="OpenRider", contents="Error loading file."}.Modal();
428 f.Read(prefix, 1, sizeof(prefix));
429 if (memcmp(prefix, PREFIX, sizeof(prefix))) {
430 MessageBox {master=this, text="OpenRider", contents="This does not appear to be a valid OpenRider track. Try using an older or newer version of OpenRider."}.Modal();
438 if (mv > max_version_code_supported) {
439 const char *vnp = *vn ? vn : "www.thedailywtf.com";
440 const char *message_format = "This track was created by a newer version of OpenRider (version %s). Continue opening it?";
441 char *s = new char[strlen(message_format)+strlen(vnp)];
443 sprintf(s, message_format, vnp);
444 result = MessageBox {master=this, text="OpenRider", type=yesNo, contents=s}.Modal();
455 camera[1] = {0,0,1.0};
456 this.fileName = openDialog.filePath;
457 this.modifiedDocument = false;
463 bool OnLoadGraphics() {
466 wheel_orig.Load(":wheel.png", "png", null);
468 MessageBox {master=this, contents="Wheel image could not be loaded."}.Modal();
473 wheel_orig.Convert(null, pixelFormat888, null);
474 if (wheel_orig.pixelFormat != pixelFormat888) {
475 MessageBox {master=this, contents="Wheel image could not be converted."}.Modal();
480 //let's assume the topleft pixel of the wheel is background
481 wheel_background = *(ColorAlpha*)wheel_orig.picture;
483 for (i=0; i<ROTATION_RESOLUTION; i++)
484 wheel[i] = CreateRotatedBitmap(wheel_orig, 48.0/64.0, (double)i*2*Pi/ROTATION_RESOLUTION, wheel_background);
486 for (i=0; i<ROTATION_RESOLUTION; i++)
487 wheel[i].MakeDD(displaySystem);
489 wheel_orig.MakeDD(displaySystem);
490 for (i=0; i<ROTATION_RESOLUTION; i++)
491 wheel[i] = wheel_orig;
497 void OnUnloadGraphics() {
499 for (i=0; i<ROTATION_RESOLUTION; i++)
503 void OnRedraw(Surface surface)
505 Camera2D *c = &camera[running];
506 double x0 = (double)(-clientSize.w) / c->zoom + c->x;
507 double y0 = (double)(-clientSize.h) / c->zoom + c->y;
508 double x1 = (double)(clientSize.w) / c->zoom + c->x;
509 double y1 = (double)(clientSize.h) / c->zoom + c->y;
510 this.surface = surface;
511 //cameraLabel.SetText("%f, %f, %f", c->x, c->y, c->zoom);
512 if (drawing && drawingTool==line)
513 game.DrawLine(this, {lastx, lasty, curx, cury, gameLineType, floor});
514 game.DrawFrame(x0,y0,x1,y1);
517 bool OnLeftButtonDown(int x, int y, Modifiers mods)
519 Camera2D *c = &camera[running];
520 if (drawingTool==browse) {
528 this.modifiedDocument = true;
530 lastx = (double)(x-(clientSize.w>>1))/c->zoom+c->x;
531 lasty = (double)(y-(clientSize.h>>1))/c->zoom+c->y;
537 bool OnLeftButtonUp(int x, int y, Modifiers mods)
539 Camera2D *c = &camera[running];
540 if (drawing && drawingTool==line) {
541 curx = (double)(x-(clientSize.w>>1)) / c->zoom + c->x;
542 cury = (double)(y-(clientSize.h>>1)) / c->zoom + c->y;
543 AddLineToGame(lastx, lasty, curx, cury);
554 bool OnMiddleButtonDown(int x, int y, Modifiers mods)
556 Camera2D *c = &camera[running];
569 bool OnMiddleButtonUp(int x, int y, Modifiers mods)
579 bool OnKeyHit(Key key, unichar ch)
583 if (key.code==space) {
587 /*else if (key.code==tab) {
589 //If the game is running, set the pauseCamera to the current camera
590 camera[0] = camera[1];
592 //Otherwise, center the camera on the ball
593 camera[0].x = game.vehicles[0].location.x;
594 camera[0].y = game.vehicles[0].location.y;
601 if (key.code==wheelDown)
603 else if (key.code==wheelUp)
605 else if (key.code==keyPadMinus)
607 else if (key.code==keyPadPlus)
611 Timer dnTimer {this, 0.03, false;
614 Camera2D *c = &camera[running];
615 bool needUpdate = false;
619 GetMousePosition(&x, &y);
620 fx = (double)(x-(clientSize.w>>1))/c->zoom+c->x;
621 fy = (double)(y-(clientSize.h>>1))/c->zoom+c->y;
622 if (fx==curx && fy==cury)
626 if (drawing && drawingTool!=browse) {
627 this.modifiedDocument = true;
628 if (drawingTool==pencil) {
629 AddLineToGame(lastx, lasty, curx, cury);
633 } else if (drawingTool==line) {
635 //game.DrawLine(this, {lastx, lasty, curx, cury, regular, floor});
636 } else if (drawingTool==eraser) {
637 double screenEraserSize = 8.0;
638 double eraserSize = screenEraserSize/c->zoom;
639 if (game.EraseAtBox(curx-eraserSize, cury-eraserSize, curx+eraserSize, cury+eraserSize))
644 if (navigating || (drawing && drawingTool==browse)) {
645 double ox = nx-(double)x;
646 double oy = ny-(double)y;
659 Label cameraLabel { this, size = { 228, 13 }, position = { 384, 24 } };
661 void DrawBall(GameBall ball) {
662 Camera2D *c = &camera[running];
663 int x= (int)((ball.location.x-c->x)*c->zoom)+(clientSize.w>>1);
664 int y= (int)((ball.location.y-c->y)*c->zoom)+(clientSize.h>>1);
665 int rwidth = (int)(ball.radius*2*c->zoom);
666 Bitmap w = wheel[angle2index(ball.angle)];
671 if (rwidth == w.width)
672 surface.Blit(w, x,y, 0,0, w.width,w.height);
674 surface.Filter(w, x,y, 0,0, rwidth,rwidth, w.width,w.height);
678 if (!running && fabs(camera.zoom-1.0)>0.01) {
679 double newradius = ball.radius*camera.zoom;
680 if (fabs(newradius-pause_wheel_radius)>0.01) {
682 pause_wheel = CreateRotatedBitmap(wheel_orig, newradius*2/(double)wheel_orig.width, ball.angle, wheel_background);
684 pause_wheel.MakeDD(displaySystem);
685 pause_wheel_radius = newradius;
694 //surface.Stretch(w, x-(rwidth>>1),y-(rwidth>>1), 0,0,rwidth,rwidth,w.width,w.height);
695 //This will crash: surface.Blit(CreateRotatedBitmap(wheel_orig, 21.7331610967957,0,wheel_background), 2147483263, 2147483176,0,0,1390,1390);
696 //We will only draw the bitmap if it is visible on the screen. Otherwise, we might get a crash.
697 if (x>-w.width && x<clientSize.w && y>-w.height && y<clientSize.h) {
698 surface.Blit(w, x,y,0,0,w.width,w.height);
699 surface.Filter(w, x-(rwidth>>1),y-(rwidth>>1), 0,0,rwidth,rwidth,w.width,w.height);
700 //surface.Filter(w, x,y,0,0,w.width*2,w.height*2,w.width,w.height);
706 void DrawLine(GameLine line) {
707 Camera2D *c = &camera[running];
708 int x0=(int)((line.x0-c->x)*c->zoom)+(clientSize.w>>1);
709 int y0=(int)((line.y0-c->y)*c->zoom)+(clientSize.h>>1);
710 int x1=(int)((line.x1-c->x)*c->zoom)+(clientSize.w>>1);
711 int y1=(int)((line.y1-c->y)*c->zoom)+(clientSize.h>>1);
713 /*if (line.floor_or_ceiling == floor) {
714 surface.SetForeground(gray);
715 DrawThickLine(x0, y0+2, x1, y1+2);
716 } else if (line.floor_or_ceiling == ceiling) {
717 surface.SetForeground(gray);
718 DrawThickLine(x0, y0-2, x1, y1-2);
723 surface.SetForeground(black);
726 surface.SetForeground(green);
729 surface.SetForeground(darkGoldenrod);
732 surface.SetForeground(red);
736 DrawThickLine(x0, y0, x1, y1);
740 void AddLineToGame(double x0, double y0, double x1, double y1) {
741 game.lines.Add({x0,y0,x1,y1,gameLineType,floor});
744 void DrawThickLine(int x0, int y0, int x1, int y1) {
745 surface.DrawLine(x0,y0,x1,y1);
746 surface.DrawLine(x0+1,y0,x1+1,y1);
747 surface.DrawLine(x0,y0+1,x1,y1+1);
748 surface.DrawLine(x0+1,y0+1,x1+1,y1+1);
751 void Zoom(double scale_factor, bool zoomTowardMouse) {
752 Camera2D *c = &camera[running];
754 if (zoomTowardMouse) { //zoom toward mouse
756 GetMousePosition(&x, &y);
757 curx = (double)(x-(clientSize.w>>1)) / c->zoom + c->x;
758 cury = (double)(y-(clientSize.h>>1)) / c->zoom + c->y;
759 } else { //zoom toward ball
760 curx = game.vehicles[0].location.x;
761 cury = game.vehicles[0].location.y;
763 //we want (curx-c->x)*zoom*scale factor to equal (curx-old c->x)*zoom
764 c->x = (curx*scale_factor-curx+c->x)/scale_factor;
765 c->y = (cury*scale_factor-cury+c->y)/scale_factor;
766 c->zoom *= scale_factor;
769 c->zoom *= scale_factor;