Initial git commit -- Transition from CodeGuard repository
[sdk] / samples / guiAndGfx / openrider / form1.ec
1 import "ecere"
2 import "game"
3
4 #define uint _uint
5 #include <stdlib.h>
6 #include <string.h>
7 #undef uint
8
9 #define PREFIX "OpenRider Track\n\n"
10 #define PREFIX_SIZE 17
11
12 define app = ((GuiApplication)__thisModule);
13
14 #define ROTATION_RESOLUTION 128
15
16 struct Matrix2D {
17    double t0,t1;
18    double t2,t3;
19
20    //sets the angle of the matrix's forward transformation to the angle specified
21    void ToAngle(double radians) {
22       t0 = cos(radians);
23       t1 = -sin(radians);
24       t2 = sin(radians);
25       t3 = cos(radians);
26    }
27    void Scale(double multiplier) {
28       t0 *= multiplier;
29       t1 *= multiplier;
30       t2 *= multiplier;
31       t3 *= multiplier;
32    }
33 };
34
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) {
37    int destx,desty;
38    int destw=dest.width, desth=dest.height;
39    int srcx,srcy;
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);
45    long dsrcx, dsrcy;
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;
50    ColorAlpha *p;
51    ColorAlpha *sp = (ColorAlpha*)src.picture;
52    ColorAlpha *s;
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) {
58       p = rp;
59       t0m = t0mi;
60       t2m = t2mi;
61       for (destx=0; destx<destw; destx++, t0m+=tt0, t2m+=tt2,p++) {
62          unsigned long aax, aay;
63          ColorAlpha c[4];
64          unsigned long dma, dmb, dmc, dmd;
65          unsigned short ma,mb,mc,md;
66          dsrcx = t0m + t1m + isx;
67          dsrcy = t2m + t3m + isy;
68          srcx = dsrcx>>16;
69          srcy = dsrcy>>16;
70          aax = dsrcx & 0xFFFFL;
71          aay = dsrcy & 0xFFFFL;
72          dmd = (aax*aay)>>16;
73          dma = (1UL<<16)-aax-aay+dmd;
74          dmb = aax-dmd;
75          dmc = 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;
81
82          if (srcx>=0 && srcx<srcw-1 && srcy>=0 && srcy<srch-1) {
83             c[0] = s[0];
84             c[1] = s[1];
85             c[2] = s[src.stride];
86             c[3] = s[src.stride+1];
87          } else {
88             c[0] = background;
89             c[1] = background;
90             c[2] = background;
91             c[3] = background;
92             if (srcx>=0 && srcx<srcw && srcy>=0 && srcy<srch)
93                c[0] = s[0];
94             if (srcx+1>=0 && srcx+1<srcw && srcy>=0 && srcy<srch)
95                c[1] = s[1];
96             if (srcx>=0 && srcx<srcw && srcy+1>=0 && srcy+1<srch)
97                c[2] = s[src.stride];
98             if (srcx+1>=0 && srcx+1<srcw && srcy+1>=0 && srcy+1<srch)
99                c[3] = s[src.stride+1];
100          }
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);
105       }
106    }
107 }
108
109 int angle2index(double angle) {
110    int index;
111    angle += Pi/ROTATION_RESOLUTION;
112    index = (int)(angle*ROTATION_RESOLUTION/(2*Pi));
113    index %= ROTATION_RESOLUTION;
114    if (index<0)
115       index += ROTATION_RESOLUTION;
116    return index;
117 }
118
119 /*wheel[i] = {alphaBlend=true};
120          wheel[i].Allocate(null, renderwidth, renderwidth, 0, pixelFormat888, false);
121          mat.ToAngle(angle);
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;*/
125
126 Bitmap CreateRotatedBitmap(Bitmap original, double scale, double angle, ColorAlpha background) {
127    uint renderwidth = (uint)((double)original.width*scale);
128    Bitmap ret {alphaBlend = true};
129    Matrix2D mat;
130    ret.Allocate(null, renderwidth, renderwidth, 0, pixelFormat888, false);
131    mat.ToAngle(-angle);
132    mat.Scale(1/scale);
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);
134    return ret;
135 }
136
137 enum DrawingTool {browse, pencil, line, eraser};
138
139 struct Camera2D {
140    double x,y;
141    double zoom; //the higher the zoom, the larger things appear
142 };
143
144 static FileFilter fileFilters[] =
145 {
146    { "OpenRider track files (*.ort)", "ort" },
147    { "All files", null},
148 };
149
150 static FileType fileTypes[] =
151 {
152    { "OpenRider track files", "ort", always }
153 };
154
155 class Form1 : Window
156 {
157    text = "OpenRider";
158    background = activeBorder;
159    borderStyle = sizable;
160    hasMaximize = true;
161    hasMinimize = true;
162    hasClose = true;
163    hasMenuBar = true;
164    size = { 628, 500 };
165    anchor = { horz = -33, vert = -28 };
166
167    bool drawing;
168    double lastx, lasty;
169    double curx, cury;
170    bool navigating;
171    double cstartx, cstarty;
172    double nx, ny;
173    DrawingTool drawingTool; //all tools operated with the left mouse button
174    GameLineType gameLineType;
175    Camera2D camera[2];
176    bool running_b;
177    double lastFrameTime;
178    running_b = false;
179    property bool running {
180       set {
181          if (!running_b && value)
182             lastFrameTime = GetTime();
183          running_b = value;
184          goItem.checked = value;
185          wheelSpinner.started = value;
186          
187          centerItem.disabled = value;
188          nudgeLeftItem.disabled = value;
189          nudgeRightItem.disabled = value;
190          nudgeUpItem.disabled = value;
191          nudgeDownItem.disabled = value;
192          
193          if (!value)
194             Update(null);
195       }
196       get {
197          return running_b;
198       }
199    }
200
201    Timer wheelSpinner {this, 0.015, started=false;
202       bool DelayExpired() {
203          double t = GetTime();
204          
205          this.modifiedDocument = true;
206          
207          //game.FrameMulti(LOGICAL_FPS/DRAWN_FPS*2);
208          game.FrameMulti((uint)((t-lastFrameTime)*LOGICAL_FPS));
209          lastFrameTime = t;
210          
211          //let's center the camera on the ball
212          {
213             Coord2D p = game.vehicles[0].location;
214             Vector2D v = game.vehicles[0].velocity;
215             //camera.zoom = 1.0;
216             camera[1].x = p.x - v.x*5;
217             camera[1].y = p.y - v.y*5;
218          }
219          Update(null);
220          return true;
221       }
222    };
223
224    Bitmap wheel[ROTATION_RESOLUTION];
225    Bitmap wheel_orig {alphaBlend=true};
226
227    ColorAlpha wheel_background;
228    Surface surface;
229    drawing = false;
230    navigating = false;
231    drawingTool = pencil;
232    gameLineType = regular;
233    FileDialog fileSaveDialog
234    {
235       master = this, type = save, text = "Save Track...",
236       types = fileTypes, sizeTypes = sizeof(fileTypes),
237       filters = fileFilters, sizeFilters = sizeof(fileFilters);
238    };
239    FileDialog openDialog
240    {
241       master = this, type = open, text = "Load Track...",
242       types = fileTypes, sizeTypes = sizeof(fileTypes),
243       filters = fileFilters, sizeFilters = sizeof(fileFilters);
244    };
245    saveDialog = fileSaveDialog;
246    Menu fileMenu {menu, "File", f};
247    MenuItem fileNewItem {
248          fileMenu, "New", n, Key {n, ctrl = true};
249
250          bool NotifySelect(MenuItem selection, Modifiers mods)
251          {
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)
255                return true;
256             running = false;
257             camera[0] = camera[1] = {0.0,0.0,1.0};
258             game.Clear();
259             game.ResetBall();
260             this.modifiedDocument = false;
261             this.fileName = null;
262             Update(null);
263             return true;
264          }
265       }
266    MenuItem fileOpenItem {
267          fileMenu, "Open...", o, Key {o, ctrl = true};
268
269          bool NotifySelect(MenuItem selection, Modifiers mods)
270          {
271             if (openDialog.Modal() == ok) {
272                OnLoadFile(openDialog.filePath);
273                /*if (!OnLoadFile(openDialog.filePath))
274                {
275                   MessageBox {master=this, text = "Error opening file.", contents = "Error opening file."}.Modal();
276                   return true;
277                }*/
278             }
279             return true;
280          }
281       }
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 };
286    
287    Menu viewMenu {menu, "View", v};
288    MenuItem centerItem {viewMenu, "Center on ball", c, tab;
289       bool NotifySelect(MenuItem selection, Modifiers mods) {
290          if (!running) {
291             camera[0].x = game.vehicles[0].location.x;
292             camera[0].y = game.vehicles[0].location.y;
293             Update(null);
294          }
295          return true;
296       }
297    }
298    MenuDivider {viewMenu};
299    MenuItem zoomInItem {viewMenu, "Zoom in\t+", i;
300       bool NotifySelect(MenuItem selection, Modifiers mods) {
301          Zoom(1.5, 0);
302          return true;
303       }
304    }
305    MenuItem zoomOutItem {viewMenu, "Zoom out\t-", o;
306       bool NotifySelect(MenuItem selection, Modifiers mods) {
307          Zoom(1/1.5, 0);
308          return true;
309       }
310    }
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}
316    
317    bool NudgeCallback(MenuItem selection, Modifiers mods) {
318       Camera2D *c = camera;
319       double nudgeAmount = 20.0 / c->zoom;
320       
321       if (running)
322          return true;
323       
324       if (selection.id == 0)
325          c->x -= nudgeAmount;
326       else if (selection.id == 1)
327          c->x += nudgeAmount;
328       else if (selection.id == 2)
329          c->y -= nudgeAmount;
330       else
331          c->y += nudgeAmount;
332       
333       Update(null);
334       
335       return true;
336    }
337    
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};
348    
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)
352       {
353          running = selection.checked;
354          return true;
355       }
356    }
357    MenuItem resetItem {controlMenu, "Reset Ball", r, Key {r, ctrl=true};
358       bool NotifySelect(MenuItem selection, Modifiers mods)
359       {
360          this.modifiedDocument = true;
361          running = false;
362          game.ResetBall();
363          Update(null);
364          return true;
365       }
366    }
367
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)
374          drawingTool = line;
375       else if (eraserItem.checked)
376          drawingTool = eraser;
377       
378       if (normalItem.checked)
379          gameLineType = regular;
380       else if (speedItem.checked)
381          gameLineType = speed;
382       else if (slowItem.checked)
383          gameLineType = slow;
384       else if (decorItem.checked)
385          gameLineType = decor;
386       return true;
387    }
388
389    bool OnCreate(void)
390    {
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;
394       game.Init();
395       return true;
396    }
397
398    bool OnSaveFile(char * fileName)
399    {
400       File f = FileOpen(fileName, write);
401       unsigned short mv = (unsigned short)game.version_code;
402       char vn[16];
403       if (!f)
404          return false;
405       memset(vn, 0, 16);
406       strcpy(vn, version_name);
407       f.Write(PREFIX, 1, PREFIX_SIZE);
408       f.Write(vn, 1, 16);
409       f.Put(mv);
410       f.Put(camera[0]);
411       f.Put(game);
412       delete f;
413       this.modifiedDocument = false;
414       return true;
415    }
416
417    void OnLoadFile(char * fileName) {
418       File f = FileOpen(fileName, read);
419       unsigned short mv;
420       char prefix[PREFIX_SIZE];
421       char vn[16];
422
423       if (!f) {
424          MessageBox {master=this, text="OpenRider", contents="Error loading file."}.Modal();
425          return;
426       }
427
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();
431          delete f;
432          return;
433       }
434       f.Read(vn, 1, 16);
435       vn[15] = 0;
436       f.Get(mv);
437       
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)];
442          DialogResult result;
443          sprintf(s, message_format, vnp);
444          result = MessageBox {master=this, text="OpenRider", type=yesNo, contents=s}.Modal();
445          delete s;
446          if (result==no) {
447             delete f;
448             return;
449          }
450       }
451       f.Get(camera[0]);
452       //f.Get(game);
453       game.Unserialize(f);
454       running = false;
455       camera[1] = {0,0,1.0};
456       this.fileName = openDialog.filePath;
457       this.modifiedDocument = false;
458       Update(null);
459       delete f;
460       return;
461    }
462
463    bool OnLoadGraphics() {
464       uint i;
465
466       wheel_orig.Load(":wheel.png", "png", null);
467       if (!wheel_orig) {
468          MessageBox {master=this, contents="Wheel image could not be loaded."}.Modal();
469          exit(0);
470          return true;
471       }
472       #if 1
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();
476          exit(0);
477          return true;
478       }
479
480       //let's assume the topleft pixel of the wheel is background
481       wheel_background = *(ColorAlpha*)wheel_orig.picture;
482
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);
485       
486       for (i=0; i<ROTATION_RESOLUTION; i++)
487          wheel[i].MakeDD(displaySystem);
488       #else
489       wheel_orig.MakeDD(displaySystem);
490       for (i=0; i<ROTATION_RESOLUTION; i++)
491          wheel[i] = wheel_orig;
492       #endif
493       
494       return true;
495    }
496
497    void OnUnloadGraphics() {
498       uint i;
499       for (i=0; i<ROTATION_RESOLUTION; i++)
500          delete wheel[i];
501    }
502
503    void OnRedraw(Surface surface)
504    {
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);
515    }
516
517    bool OnLeftButtonDown(int x, int y, Modifiers mods)
518    {
519       Camera2D *c = &camera[running];
520       if (drawingTool==browse) {
521          if (running)
522             return true;
523          cstartx = c->x;
524          cstarty = c->y;
525          nx = (double)x;
526          ny = (double)y;
527       } else
528          this.modifiedDocument = true;
529       drawing = true;
530       lastx = (double)(x-(clientSize.w>>1))/c->zoom+c->x;
531       lasty = (double)(y-(clientSize.h>>1))/c->zoom+c->y;
532       Capture();
533       dnTimer.Start();
534       return true;
535    }
536
537    bool OnLeftButtonUp(int x, int y, Modifiers mods)
538    {
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);
544          Update(null);
545       }
546       drawing = false;
547       if (!navigating) {
548          ReleaseCapture();
549          dnTimer.Stop();
550       }
551       return true;
552    }
553
554    bool OnMiddleButtonDown(int x, int y, Modifiers mods)
555    {
556       Camera2D *c = &camera[running];
557       if (running)
558          return true;
559       cstartx = c->x;
560       cstarty = c->y;
561       nx = (double)x;
562       ny = (double)y;
563       navigating = true;
564       dnTimer.Start();
565       Capture();
566       return true;
567    }
568
569    bool OnMiddleButtonUp(int x, int y, Modifiers mods)
570    {
571       navigating = false;
572       if (!drawing) {
573          ReleaseCapture();
574          dnTimer.Stop();
575       }
576       return true;
577    }
578
579    bool OnKeyHit(Key key, unichar ch)
580    {
581       if (key.modifiers)
582          return true;
583       if (key.code==space) {
584          running = !running;
585          return true;
586       }
587       /*else if (key.code==tab) {
588          if (running) {
589             //If the game is running, set the pauseCamera to the current camera
590             camera[0] = camera[1];
591          } else {
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;
595             Update(null);
596          }
597          return false;
598       }*/
599       //if (running)
600       //   return true;
601       if (key.code==wheelDown)
602          Zoom(1/1.05, true);
603       else if (key.code==wheelUp)
604          Zoom(1.05, true); 
605       else if (key.code==keyPadMinus)
606          Zoom(1/1.25, false);
607       else if (key.code==keyPadPlus)
608          Zoom(1.25, false);
609       return true;
610    }
611    Timer dnTimer {this, 0.03, false;
612       bool DelayExpired()
613       {
614          Camera2D *c = &camera[running];
615          bool needUpdate = false;
616          int x,y;
617          double fx, fy;
618          
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)
623             return true;
624          curx = fx;
625          cury = fy;
626          if (drawing && drawingTool!=browse) {
627             this.modifiedDocument = true;
628             if (drawingTool==pencil) {
629                AddLineToGame(lastx, lasty, curx, cury);
630                lastx = curx;
631                lasty = cury;
632                needUpdate = true;
633             } else if (drawingTool==line) {
634                needUpdate = true;
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))
640                   needUpdate = true;
641             }
642          }
643          
644          if (navigating || (drawing && drawingTool==browse)) {
645             double ox = nx-(double)x;
646             double oy = ny-(double)y;
647             ox /= c->zoom;
648             oy /= c->zoom;
649             c->x = cstartx + ox;
650             c->y = cstarty + oy;
651             needUpdate = true;
652          }
653          
654          if (needUpdate)
655             Update(null);
656          return true;
657       }
658    };
659    Label cameraLabel { this, size = { 228, 13 }, position = { 384, 24 } };
660    Game game {this;
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)];
667          
668          if (w) {
669             x -= rwidth>>1;
670             y -= rwidth>>1;
671             if (rwidth == w.width)
672                surface.Blit(w, x,y, 0,0, w.width,w.height);
673             else
674                surface.Filter(w, x,y, 0,0, rwidth,rwidth, w.width,w.height);
675          }
676          
677          #if 0
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) {
681                delete pause_wheel;
682                pause_wheel = CreateRotatedBitmap(wheel_orig, newradius*2/(double)wheel_orig.width, ball.angle, wheel_background);
683                if (pause_wheel) {
684                   pause_wheel.MakeDD(displaySystem);
685                   pause_wheel_radius = newradius;
686                }
687             }
688             if (pause_wheel)
689                w = pause_wheel;
690          }
691          if (w) {
692             x -= w.width>>1;
693             y -= w.height>>1;
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);
701             }
702          }
703          #endif
704       }
705
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);
712
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);
719          }*/
720
721          switch (line.type) {
722             case regular:
723                surface.SetForeground(black);
724                break;
725             case decor:
726                surface.SetForeground(green);
727                break;
728             case slow:
729                surface.SetForeground(darkGoldenrod);
730                break;
731             case speed:
732                surface.SetForeground(red);
733                break;
734          }
735          
736          DrawThickLine(x0, y0, x1, y1);
737       }
738    };
739
740    void AddLineToGame(double x0, double y0, double x1, double y1) {
741       game.lines.Add({x0,y0,x1,y1,gameLineType,floor});
742    }
743
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);
749    }
750
751    void Zoom(double scale_factor, bool zoomTowardMouse) {
752       Camera2D *c = &camera[running];
753       if (!running) {
754          if (zoomTowardMouse) { //zoom toward mouse
755             int x,y;
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;
762          }
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;
767          Update(null);
768       } else
769          c->zoom *= scale_factor;
770    }
771 }
772
773 Form1 form1 {};