4 #define PREFIX "OpenRider Track\n\n"
7 define app = ((GuiApplication)__thisModule);
9 #define ROTATION_RESOLUTION 128
15 //sets the angle of the matrix's forward transformation to the angle specified
16 void ToAngle(double radians) {
22 void Scale(double multiplier) {
30 //This function takes src, transforms it at origin sx, sy using the _inverse_ of trans_inv, and places it in dest at dx, dy
31 void Transform888Bitmap(Bitmap src, Bitmap dest, double sx, double sy, double dx, double dy, Matrix2D trans_inv, ColorAlpha background) {
33 int destw=dest.width, desth=dest.height;
35 int srcw=src.width, srch=src.height;
36 uint tt0 = (uint)(trans_inv.t0*65536);
37 uint tt1 = (uint)(trans_inv.t1*65536);
38 uint tt2 = (uint)(trans_inv.t2*65536);
39 uint tt3 = (uint)(trans_inv.t3*65536);
41 uint isx = (uint)(sx*65536);
42 uint isy = (uint)(sy*65536);
43 uint t0m, t1m, t2m, t3m, t0mi, t2mi;
44 ColorAlpha *rp = (ColorAlpha*)dest.picture;
46 ColorAlpha *sp = (ColorAlpha*)src.picture;
48 t0mi = (uint)(-dx*trans_inv.t0*65536);
49 t2mi = (uint)(-dx*trans_inv.t2*65536);
50 t1m = (uint)(-dy*trans_inv.t1*65536);
51 t3m = (uint)(-dy*trans_inv.t3*65536);
52 for (desty=0; desty<desth; desty++, t1m+=tt1, t3m+=tt3, rp+=dest.stride) {
56 for (destx=0; destx<destw; destx++, t0m+=tt0, t2m+=tt2,p++) {
59 uint dma, dmb, dmc, dmd;
60 unsigned short ma,mb,mc,md;
61 dsrcx = t0m + t1m + isx;
62 dsrcy = t2m + t3m + isy;
65 aax = dsrcx & 0xFFFFL;
66 aay = dsrcy & 0xFFFFL;
68 dma = (1UL<<16)-aax-aay+dmd;
71 ma = (unsigned short)(dma>>8);
72 mb = (unsigned short)(dmb>>8);
73 mc = (unsigned short)(dmc>>8);
74 md = (unsigned short)(dmd>>8);
75 s = sp + srcy*src.stride + srcx;
77 if (srcx>=0 && srcx<srcw-1 && srcy>=0 && srcy<srch-1) {
81 c[3] = s[src.stride+1];
87 if (srcx>=0 && srcx<srcw && srcy>=0 && srcy<srch)
89 if (srcx+1>=0 && srcx+1<srcw && srcy>=0 && srcy<srch)
91 if (srcx>=0 && srcx<srcw && srcy+1>=0 && srcy+1<srch)
93 if (srcx+1>=0 && srcx+1<srcw && srcy+1>=0 && srcy+1<srch)
94 c[3] = s[src.stride+1];
96 p->a = (byte)((c[0].a*ma + c[1].a*mb + c[2].a*mc + c[3].a*md) >> 8);
97 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);
98 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);
99 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);
104 int angle2index(double angle) {
106 angle += Pi/ROTATION_RESOLUTION;
107 index = (int)(angle*ROTATION_RESOLUTION/(2*Pi));
108 index %= ROTATION_RESOLUTION;
110 index += ROTATION_RESOLUTION;
114 /*wheel[i] = {alphaBlend=true};
115 wheel[i].Allocate(null, renderwidth, renderwidth, 0, pixelFormat888, false);
117 mat.Scale(64.0/(double)renderwidth);
118 Transform888Bitmap(orig, wheel[i], 31.5, 31.5, ((double)renderwidth-0.5)/2, ((double)renderwidth-0.5)/2, mat, wheel_background);
119 angle += angle_change;*/
121 Bitmap CreateRotatedBitmap(Bitmap original, double scale, double angle, ColorAlpha background) {
122 uint renderwidth = (uint)((double)original.width*scale);
123 Bitmap ret {alphaBlend = true};
125 ret.Allocate(null, renderwidth, renderwidth, 0, pixelFormat888, false);
128 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);
132 enum DrawingTool {browse, pencil, line, eraser};
136 double zoom; //the higher the zoom, the larger things appear
139 static FileFilter fileFilters[] =
141 { "OpenRider track files (*.ort)", "ort" },
142 { "All files", null},
145 static FileType fileTypes[] =
147 { "OpenRider track files", "ort", always }
153 background = activeBorder;
154 borderStyle = sizable;
160 anchor = { horz = -33, vert = -28 };
166 double cstartx, cstarty;
168 DrawingTool drawingTool; //all tools operated with the left mouse button
169 GameLineType gameLineType;
172 double lastFrameTime;
174 property bool running {
176 if (!running_b && value)
177 lastFrameTime = GetTime();
179 goItem.checked = value;
180 wheelSpinner.started = value;
182 centerItem.disabled = value;
183 nudgeLeftItem.disabled = value;
184 nudgeRightItem.disabled = value;
185 nudgeUpItem.disabled = value;
186 nudgeDownItem.disabled = value;
196 Timer wheelSpinner {this, 0.015, started=false;
197 bool DelayExpired() {
198 double t = GetTime();
200 this.modifiedDocument = true;
202 //game.FrameMulti(LOGICAL_FPS/DRAWN_FPS*2);
203 game.FrameMulti((uint)((t-lastFrameTime)*LOGICAL_FPS));
206 //let's center the camera on the ball
208 Coord2D p = game.vehicles[0].location;
209 Vector2D v = game.vehicles[0].velocity;
211 camera[1].x = p.x - v.x*5;
212 camera[1].y = p.y - v.y*5;
219 Bitmap wheel[ROTATION_RESOLUTION];
220 Bitmap wheel_orig {alphaBlend=true};
222 ColorAlpha wheel_background;
226 drawingTool = pencil;
227 gameLineType = regular;
228 FileDialog fileSaveDialog
230 master = this, type = save, text = "Save Track...",
231 types = fileTypes, sizeTypes = sizeof(fileTypes),
232 filters = fileFilters, sizeFilters = sizeof(fileFilters);
234 FileDialog openDialog
236 master = this, type = open, text = "Load Track...",
237 types = fileTypes, sizeTypes = sizeof(fileTypes),
238 filters = fileFilters, sizeFilters = sizeof(fileFilters);
240 saveDialog = fileSaveDialog;
241 Menu fileMenu {menu, "File", f};
242 MenuItem fileNewItem {
243 fileMenu, "New", n, Key {n, ctrl = true};
245 bool NotifySelect(MenuItem selection, Modifiers mods)
247 if (MessageBox {master=this, text = "OpenRider",
248 contents="Are you sure you want to clear the current track and start a new one?",
249 type=yesNo}.Modal() != yes)
252 camera[0] = camera[1] = {0.0,0.0,1.0};
255 this.modifiedDocument = false;
256 this.fileName = null;
261 MenuItem fileOpenItem {
262 fileMenu, "Open...", o, Key {o, ctrl = true};
264 bool NotifySelect(MenuItem selection, Modifiers mods)
266 if (openDialog.Modal() == ok) {
267 OnLoadFile(openDialog.filePath);
268 /*if (!OnLoadFile(openDialog.filePath))
270 MessageBox {master=this, text = "Error opening file.", contents = "Error opening file."}.Modal();
277 MenuItem fileSaveItem { fileMenu, "Save", s, Key {s, ctrl = true}, NotifySelect=MenuFileSave}
278 MenuItem fileSaveAsItem { fileMenu, "Save As...", a, Key {a, ctrl = true}, NotifySelect=MenuFileSaveAs }
279 MenuDivider {fileMenu};
280 MenuItem exitItem {fileMenu, "Exit", x, altF4, NotifySelect = MenuFileExit };
282 Menu viewMenu {menu, "View", v};
283 MenuItem centerItem {viewMenu, "Center on ball", c, tab;
284 bool NotifySelect(MenuItem selection, Modifiers mods) {
286 camera[0].x = game.vehicles[0].location.x;
287 camera[0].y = game.vehicles[0].location.y;
293 MenuDivider {viewMenu};
294 MenuItem zoomInItem {viewMenu, "Zoom in\t+", i;
295 bool NotifySelect(MenuItem selection, Modifiers mods) {
300 MenuItem zoomOutItem {viewMenu, "Zoom out\t-", o;
301 bool NotifySelect(MenuItem selection, Modifiers mods) {
306 MenuDivider {viewMenu};
307 MenuItem nudgeLeftItem {viewMenu, "Left", l, left, NotifySelect = NudgeCallback, id=0}
308 MenuItem nudgeRightItem {viewMenu, "Right", r, right, NotifySelect = NudgeCallback, id=1}
309 MenuItem nudgeUpItem {viewMenu, "Up", u, up, NotifySelect = NudgeCallback, id=2}
310 MenuItem nudgeDownItem {viewMenu, "Down", d, down, NotifySelect = NudgeCallback, id=3}
312 bool NudgeCallback(MenuItem selection, Modifiers mods) {
313 Camera2D *c = camera;
314 double nudgeAmount = 20.0 / c->zoom;
319 if (selection.id == 0)
321 else if (selection.id == 1)
323 else if (selection.id == 2)
333 Menu toolMenu {menu, "Tool", t};
334 MenuItem browseItem {toolMenu, "Browse", b, b, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
335 MenuItem pencilItem {toolMenu, "Draw", d, d, checked=true, isRadio=true, NotifySelect = MyCustomMenu};
336 MenuItem lineItem {toolMenu, "Straight line", s, s, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
337 MenuItem eraserItem {toolMenu, "Eraser", e, e, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
338 MenuDivider {toolMenu};
339 MenuItem normalItem {toolMenu, "Normal", a, a, checked=true, isRadio=true, NotifySelect = MyCustomMenu};
340 MenuItem speedItem {toolMenu, "Right accelerator", r, r, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
341 MenuItem slowItem {toolMenu, "Left accelerator", f, f, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
342 MenuItem decorItem {toolMenu, "Decoration", c, c, checked=false, isRadio=true, NotifySelect = MyCustomMenu};
344 Menu controlMenu {menu, "Control", c};
345 MenuItem goItem {controlMenu, "Go", g, Key {g, ctrl=true}, checked=false, checkable=true;
346 bool NotifySelect(MenuItem selection, Modifiers mods)
348 running = selection.checked;
352 MenuItem resetItem {controlMenu, "Reset Ball", r, Key {r, ctrl=true};
353 bool NotifySelect(MenuItem selection, Modifiers mods)
355 this.modifiedDocument = true;
363 bool MyCustomMenu(MenuItem selection, Modifiers mods) {
364 if (browseItem.checked)
365 drawingTool = browse;
366 else if (pencilItem.checked)
367 drawingTool = pencil;
368 else if (lineItem.checked)
370 else if (eraserItem.checked)
371 drawingTool = eraser;
373 if (normalItem.checked)
374 gameLineType = regular;
375 else if (speedItem.checked)
376 gameLineType = speed;
377 else if (slowItem.checked)
379 else if (decorItem.checked)
380 gameLineType = decor;
386 camera[0] = {0.0, 0.0, 1.0}; //paused camera
387 camera[1] = {0.0, 0.0, 1.0}; //running camera
388 app.timerResolution = 300;
393 bool OnSaveFile(char * fileName)
395 File f = FileOpen(fileName, write);
396 unsigned short mv = (unsigned short)game.version_code;
401 strcpy(vn, version_name);
402 f.Write(PREFIX, 1, PREFIX_SIZE);
408 this.modifiedDocument = false;
412 void OnLoadFile(char * fileName) {
413 File f = FileOpen(fileName, read);
415 char prefix[PREFIX_SIZE];
419 MessageBox {master=this, text="OpenRider", contents="Error loading file."}.Modal();
423 f.Read(prefix, 1, sizeof(prefix));
424 if (strncmp(prefix, PREFIX, sizeof(prefix))) {
425 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();
433 if (mv > max_version_code_supported) {
434 const char *vnp = *vn ? vn : "www.thedailywtf.com";
435 const char *message_format = "This track was created by a newer version of OpenRider (version %s). Continue opening it?";
436 char *s = new char[strlen(message_format)+strlen(vnp)];
438 sprintf(s, message_format, vnp);
439 result = MessageBox {master=this, text="OpenRider", type=yesNo, contents=s}.Modal();
449 camera[1] = {0,0,1.0};
450 this.fileName = openDialog.filePath;
451 this.modifiedDocument = false;
457 bool OnLoadGraphics() {
460 wheel_orig.Load(":wheel.png", "png", null);
462 MessageBox {master=this, contents="Wheel image could not be loaded."}.Modal();
466 wheel_orig.Convert(null, pixelFormat888, null);
467 if (wheel_orig.pixelFormat != pixelFormat888) {
468 MessageBox {master=this, contents="Wheel image could not be converted."}.Modal();
472 //let's assume the topleft pixel of the wheel is background
473 wheel_background = *(ColorAlpha*)wheel_orig.picture;
475 for (i=0; i<ROTATION_RESOLUTION; i++)
476 wheel[i] = CreateRotatedBitmap(wheel_orig, 48.0/64.0, (double)i*2*Pi/ROTATION_RESOLUTION, wheel_background);
478 for (i=0; i<ROTATION_RESOLUTION; i++)
479 wheel[i].MakeDD(displaySystem);
481 wheel_orig.MakeDD(displaySystem);
482 for (i=0; i<ROTATION_RESOLUTION; i++)
483 wheel[i] = wheel_orig;
489 void OnUnloadGraphics() {
491 for (i=0; i<ROTATION_RESOLUTION; i++)
495 void OnRedraw(Surface surface)
497 Camera2D *c = &camera[running];
498 double x0 = (double)(-clientSize.w) / c->zoom + c->x;
499 double y0 = (double)(-clientSize.h) / c->zoom + c->y;
500 double x1 = (double)(clientSize.w) / c->zoom + c->x;
501 double y1 = (double)(clientSize.h) / c->zoom + c->y;
502 this.surface = surface;
503 //cameraLabel.SetText("%f, %f, %f", c->x, c->y, c->zoom);
504 if (drawing && drawingTool==line)
505 game.DrawLine(this, {lastx, lasty, curx, cury, gameLineType, floor});
506 game.DrawFrame(x0,y0,x1,y1);
509 bool OnLeftButtonDown(int x, int y, Modifiers mods)
511 Camera2D *c = &camera[running];
512 if (drawingTool==browse) {
520 this.modifiedDocument = true;
522 lastx = (double)(x-(clientSize.w>>1))/c->zoom+c->x;
523 lasty = (double)(y-(clientSize.h>>1))/c->zoom+c->y;
529 bool OnLeftButtonUp(int x, int y, Modifiers mods)
531 Camera2D *c = &camera[running];
532 if (drawing && drawingTool==line) {
533 curx = (double)(x-(clientSize.w>>1)) / c->zoom + c->x;
534 cury = (double)(y-(clientSize.h>>1)) / c->zoom + c->y;
535 AddLineToGame(lastx, lasty, curx, cury);
546 bool OnMiddleButtonDown(int x, int y, Modifiers mods)
548 Camera2D *c = &camera[running];
561 bool OnMiddleButtonUp(int x, int y, Modifiers mods)
571 bool OnKeyHit(Key key, unichar ch)
575 switch((SmartKey)key)
582 //If the game is running, set the pauseCamera to the current camera
583 camera[0] = camera[1];
585 //Otherwise, center the camera on the ball
586 camera[0].x = game.vehicles[0].location.x;
587 camera[0].y = game.vehicles[0].location.y;
606 Timer dnTimer {this, 0.03, false;
609 Camera2D *c = &camera[running];
610 bool needUpdate = false;
614 GetMousePosition(&x, &y);
615 fx = (double)(x-(clientSize.w>>1))/c->zoom+c->x;
616 fy = (double)(y-(clientSize.h>>1))/c->zoom+c->y;
617 if (fx==curx && fy==cury)
621 if (drawing && drawingTool!=browse) {
622 this.modifiedDocument = true;
623 if (drawingTool==pencil) {
624 AddLineToGame(lastx, lasty, curx, cury);
628 } else if (drawingTool==line) {
630 //game.DrawLine(this, {lastx, lasty, curx, cury, regular, floor});
631 } else if (drawingTool==eraser) {
632 double screenEraserSize = 8.0;
633 double eraserSize = screenEraserSize/c->zoom;
634 if (game.EraseAtBox(curx-eraserSize, cury-eraserSize, curx+eraserSize, cury+eraserSize))
639 if (navigating || (drawing && drawingTool==browse)) {
640 double ox = nx-(double)x;
641 double oy = ny-(double)y;
654 Label cameraLabel { this, size = { 228, 13 }, position = { 384, 24 } };
656 void DrawBall(GameBall ball) {
657 Camera2D *c = &camera[running];
658 int x= (int)((ball.location.x-c->x)*c->zoom)+(clientSize.w>>1);
659 int y= (int)((ball.location.y-c->y)*c->zoom)+(clientSize.h>>1);
660 int rwidth = (int)(ball.radius*2*c->zoom);
661 Bitmap w = wheel[angle2index(ball.angle)];
666 if (rwidth == w.width)
667 surface.Blit(w, x,y, 0,0, w.width,w.height);
669 surface.Filter(w, x,y, 0,0, rwidth,rwidth, w.width,w.height);
673 if (!running && fabs(camera.zoom-1.0)>0.01) {
674 double newradius = ball.radius*camera.zoom;
675 if (fabs(newradius-pause_wheel_radius)>0.01) {
677 pause_wheel = CreateRotatedBitmap(wheel_orig, newradius*2/(double)wheel_orig.width, ball.angle, wheel_background);
679 pause_wheel.MakeDD(displaySystem);
680 pause_wheel_radius = newradius;
689 //surface.Stretch(w, x-(rwidth>>1),y-(rwidth>>1), 0,0,rwidth,rwidth,w.width,w.height);
690 //This will crash: surface.Blit(CreateRotatedBitmap(wheel_orig, 21.7331610967957,0,wheel_background), 2147483263, 2147483176,0,0,1390,1390);
691 //We will only draw the bitmap if it is visible on the screen. Otherwise, we might get a crash.
692 if (x>-w.width && x<clientSize.w && y>-w.height && y<clientSize.h) {
693 surface.Blit(w, x,y,0,0,w.width,w.height);
694 surface.Filter(w, x-(rwidth>>1),y-(rwidth>>1), 0,0,rwidth,rwidth,w.width,w.height);
695 //surface.Filter(w, x,y,0,0,w.width*2,w.height*2,w.width,w.height);
701 void DrawLine(GameLine line) {
702 Camera2D *c = &camera[running];
703 int x0=(int)((line.x0-c->x)*c->zoom)+(clientSize.w>>1);
704 int y0=(int)((line.y0-c->y)*c->zoom)+(clientSize.h>>1);
705 int x1=(int)((line.x1-c->x)*c->zoom)+(clientSize.w>>1);
706 int y1=(int)((line.y1-c->y)*c->zoom)+(clientSize.h>>1);
708 /*if (line.floor_or_ceiling == floor) {
709 surface.SetForeground(gray);
710 DrawThickLine(x0, y0+2, x1, y1+2);
711 } else if (line.floor_or_ceiling == ceiling) {
712 surface.SetForeground(gray);
713 DrawThickLine(x0, y0-2, x1, y1-2);
718 surface.SetForeground(black);
721 surface.SetForeground(green);
724 surface.SetForeground(darkGoldenrod);
727 surface.SetForeground(red);
731 DrawThickLine(x0, y0, x1, y1);
735 void AddLineToGame(double x0, double y0, double x1, double y1) {
736 game.lines.Add({x0,y0,x1,y1,gameLineType,floor});
739 void DrawThickLine(int x0, int y0, int x1, int y1) {
740 surface.DrawLine(x0,y0,x1,y1);
741 surface.DrawLine(x0+1,y0,x1+1,y1);
742 surface.DrawLine(x0,y0+1,x1,y1+1);
743 surface.DrawLine(x0+1,y0+1,x1+1,y1+1);
746 void Zoom(double scale_factor, bool zoomTowardMouse) {
747 Camera2D *c = &camera[running];
749 if (zoomTowardMouse) { //zoom toward mouse
751 GetMousePosition(&x, &y);
752 curx = (double)(x-(clientSize.w>>1)) / c->zoom + c->x;
753 cury = (double)(y-(clientSize.h>>1)) / c->zoom + c->y;
754 } else { //zoom toward ball
755 curx = game.vehicles[0].location.x;
756 cury = game.vehicles[0].location.y;
758 //we want (curx-c->x)*zoom*scale factor to equal (curx-old c->x)*zoom
759 c->x = (curx*scale_factor-curx+c->x)/scale_factor;
760 c->y = (cury*scale_factor-cury+c->y)/scale_factor;
761 c->zoom *= scale_factor;
764 c->zoom *= scale_factor;