source: osm/applications/editors/josm/plugins/wms-turbo-challenge2/src/wmsturbochallenge/GameWindow.java@ 25107

Last change on this file since 25107 was 24999, checked in by upliner, 14 years ago

make wms-turbo-challenge compatible with recent JOSM.

Still works only for WMS layers. Unfortunately, no way to race around Bing imagery yet.

File size: 23.5 KB
Line 
1/*
2 * GPLv2 or 3, Copyright (c) 2010 Andrzej Zaborowski
3 *
4 * This implements the game logic.
5 */
6package wmsturbochallenge;
7
8import java.awt.Color;
9import java.awt.Graphics;
10import java.awt.Image;
11import java.awt.Point;
12import java.awt.Toolkit;
13import java.awt.event.ActionEvent;
14import java.awt.event.ActionListener;
15import java.awt.event.KeyAdapter;
16import java.awt.event.KeyEvent;
17import java.awt.image.BufferedImage;
18import java.util.ArrayList;
19import java.util.Arrays;
20import java.util.Collection;
21import java.util.HashMap;
22import java.util.List;
23
24import javax.swing.ImageIcon;
25import javax.swing.JFrame;
26import javax.swing.JPanel;
27import javax.swing.Timer;
28
29import org.openstreetmap.josm.Main;
30import org.openstreetmap.josm.data.ProjectionBounds;
31import org.openstreetmap.josm.data.coor.EastNorth;
32import org.openstreetmap.josm.data.gpx.GpxData;
33import org.openstreetmap.josm.data.gpx.ImmutableGpxTrack;
34import org.openstreetmap.josm.data.gpx.WayPoint;
35import org.openstreetmap.josm.gui.layer.GpxLayer;
36import org.openstreetmap.josm.gui.layer.Layer;
37import org.openstreetmap.josm.gui.layer.WMSLayer;
38
39public class GameWindow extends JFrame implements ActionListener {
40 public GameWindow(Layer ground) {
41 setTitle("The Ultimate WMS Super-speed Turbo Challenge II");
42 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
43 setUndecorated(true);
44 setSize(s.getScreenSize().width, s.getScreenSize().height);
45 setLocationRelativeTo(null);
46 setResizable(false);
47
48 while (s.getScreenSize().width < width * scale ||
49 s.getScreenSize().height < height * scale)
50 scale --;
51 add(panel);
52
53 setVisible(true);
54
55 /* TODO: "Intro" screen perhaps with "Hall of Fame" */
56
57 screen_image = new BufferedImage(width, height,
58 BufferedImage.TYPE_INT_RGB);
59 screen = screen_image.getGraphics();
60
61 this.ground = ground;
62 ground_view = new fake_map_view(Main.map.mapView, 0.0000001);
63
64 /* Retrieve start position */
65 EastNorth start = ground_view.parent.getCenter();
66 lat = start.north();
67 lon = start.east();
68
69 addKeyListener(new TAdapter());
70
71 timer = new Timer(80, this);
72 timer.start();
73
74 car_gps = new gps();
75 car_gps.start();
76
77 car_engine = new engine();
78 car_engine.start();
79
80 for (int i = 0; i < maxsprites; i ++)
81 sprites[i] = new sprite_pos();
82
83 generate_sky();
84 }
85
86 protected engine car_engine;
87
88 protected gps car_gps;
89 protected class gps extends Timer implements ActionListener {
90 public gps() {
91 super(1000, null);
92 addActionListener(this);
93
94 trackSegs = new ArrayList<Collection<WayPoint>>();
95 }
96
97 protected Collection<WayPoint> segment;
98 protected Collection<Collection<WayPoint>> trackSegs;
99
100 public void actionPerformed(ActionEvent e) {
101 /* We should count the satellites here, see if we
102 * have a fix and add any distortions. */
103
104 segment.add(new WayPoint(Main.proj.eastNorth2latlon(
105 new EastNorth(lon, lat))));
106 }
107
108 @Override
109 public void start() {
110 super.start();
111
112 /* Start recording */
113 segment = new ArrayList<WayPoint>();
114 trackSegs.add(segment);
115 actionPerformed(null);
116 }
117
118 public void save_trace() {
119 int len = 0;
120 for (Collection<WayPoint> seg : trackSegs)
121 len += seg.size();
122
123 /* Don't save traces shorter than 5s */
124 if (len <= 5)
125 return;
126
127 GpxData data = new GpxData();
128 data.tracks.add(new ImmutableGpxTrack(trackSegs,
129 new HashMap<String, Object>()));
130
131 ground_view.parent.addLayer(
132 new GpxLayer(data, "Car GPS trace"));
133 }
134 }
135
136 /* These are EastNorth, not actual LatLon */
137 protected double lat, lon;
138 /* Camera's altitude above surface (same units as lat/lon above) */
139 protected double ele = 0.000003;
140 /* Cut off at ~75px from bottom of the screen */
141 protected double horizon = 0.63;
142 /* Car's distance from the camera lens */
143 protected double cardist = ele * 3;
144
145 /* Pixels per pixel, the bigger the more oldschool :-) */
146 protected int scale = 5;
147
148 protected BufferedImage screen_image;
149 protected Graphics screen;
150 protected int width = 320;
151 protected int height = 200;
152 protected int centre = width / 2;
153
154 double maxdist = ele / (horizon - 0.6);
155 double realwidth = maxdist * width / height;
156 double pixelperlat = 1.0 * width / realwidth;
157 double sratio = 0.85;
158 protected int sw = (int) (2 * Math.PI * maxdist * pixelperlat * sratio);
159
160 /* TODO: figure out how to load these dynamically after splash
161 * screen is shown */
162 protected static final ImageIcon car[] = new ImageIcon[] {
163 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
164 WMSRacer.class.getResource(
165 "/images/car0-l.png"))),
166 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
167 WMSRacer.class.getResource(
168 "/images/car0.png"))),
169 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
170 WMSRacer.class.getResource(
171 "/images/car0-r.png"))),
172 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
173 WMSRacer.class.getResource(
174 "/images/car1-l.png"))),
175 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
176 WMSRacer.class.getResource(
177 "/images/car1.png"))),
178 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
179 WMSRacer.class.getResource(
180 "/images/car1-r.png"))),
181 };
182 protected static final ImageIcon bg[] = new ImageIcon[] {
183 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
184 WMSRacer.class.getResource(
185 "/images/bg0.png"))),
186 };
187 protected static final ImageIcon skyline[] = new ImageIcon[] {
188 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
189 WMSRacer.class.getResource(
190 "/images/horizon.png"))),
191 };
192 protected static final ImageIcon cactus[] = new ImageIcon[] {
193 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
194 WMSRacer.class.getResource(
195 "/images/cactus0.png"))),
196 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
197 WMSRacer.class.getResource(
198 "/images/cactus1.png"))),
199 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
200 WMSRacer.class.getResource(
201 "/images/cactus2.png"))),
202 };
203 protected static final ImageIcon cloud[] = new ImageIcon[] {
204 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
205 WMSRacer.class.getResource(
206 "/images/cloud0.png"))),
207 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
208 WMSRacer.class.getResource(
209 "/images/cloud1.png"))),
210 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
211 WMSRacer.class.getResource(
212 "/images/cloud2.png"))),
213 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
214 WMSRacer.class.getResource(
215 "/images/cloud3.png"))),
216 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
217 WMSRacer.class.getResource(
218 "/images/cloud4.png"))),
219 };
220 protected static final ImageIcon aircraft[] = new ImageIcon[] {
221 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
222 WMSRacer.class.getResource(
223 "/images/aircraft0.png"))),
224 };
225 protected static final ImageIcon loading = new ImageIcon(
226 Toolkit.getDefaultToolkit().createImage(
227 WMSRacer.class.getResource(
228 "/images/loading.png")));
229 protected static Toolkit s = Toolkit.getDefaultToolkit();
230 protected int current_bg = 0;
231 protected int current_car = 0;
232 protected boolean cacti_on = true;
233 protected List<EastNorth> cacti = new ArrayList<EastNorth>();
234 protected List<EastNorth> todelete = new ArrayList<EastNorth>();
235 protected int splashframe = -1;
236 protected EastNorth splashcactus;
237
238 protected Layer ground;
239 protected double heading = 0.0;
240 protected double wheelangle = 0.0;
241 protected double speed = 0.0;
242 protected boolean key_down[] = new boolean[] {
243 false, false, false, false, };
244
245 protected void move() {
246 /* Left */
247 /* (At high speeds make more gentle turns) */
248 if (key_down[0])
249 wheelangle -= 0.1 / (1.0 + Math.abs(speed));
250 /* Right */
251 if (key_down[1])
252 wheelangle += 0.1 / (1.0 + Math.abs(speed));
253 if (wheelangle > 0.3)
254 wheelangle = 0.3; /* Radians */
255 if (wheelangle < -0.3)
256 wheelangle = -0.3;
257
258 wheelangle *= 0.7;
259
260 /* Up */
261 if (key_down[2])
262 speed += speed >= 0.0 ? 1.0 / (2.0 + speed) : 0.5;
263 /* Down */
264 if (key_down[3]) {
265 if (speed >= 0.5) /* Brake (TODO: sound) */
266 speed -= 0.5;
267 else if (speed >= 0.01) /* Brake (TODO: sound) */
268 speed = 0.0;
269 else /* Reverse */
270 speed -= 0.5 / (4.0 - speed);
271 }
272
273 speed *= 0.97;
274 car_engine.set_speed(speed);
275
276 if (speed > -0.1 && speed < 0.1)
277 speed = 0;
278
279 heading += wheelangle * speed;
280
281 boolean chop = false;
282 double newlat = lat + Math.cos(heading) * speed * ele * 0.2;
283 double newlon = lon + Math.sin(heading) * speed * ele * 0.2;
284 for (EastNorth pos : cacti) {
285 double alat = Math.abs(pos.north() - newlat);
286 double alon = Math.abs(pos.east() - newlon);
287 if (alat + alon < ele * 1.0) {
288 if (Math.abs(speed) < 2.0) {
289 if (speed > 0.0)
290 speed = -0.5;
291 else
292 speed = 0.3;
293 newlat = lat;
294 newlon = lon;
295 break;
296 }
297
298 chop = true;
299 splashframe = 0;
300 splashcactus = pos;
301 todelete.add(pos);
302 }
303 }
304
305 lat = newlat;
306 lon = newlon;
307
308 /* Seed a new cactus if we're moving.
309 * TODO: hook into data layers and avoid putting the cactus on
310 * the road!
311 */
312 if (cacti_on && Math.random() * 30.0 < speed) {
313 double left_x = maxdist * (width - centre) / height;
314 double right_x = maxdist * (0 - centre) / height;
315 double x = left_x + Math.random() * (right_x - left_x);
316 double clat = lat + (maxdist - cardist) *
317 Math.cos(heading) - x * Math.sin(heading);
318 double clon = lon + (maxdist - cardist) *
319 Math.sin(heading) + x * Math.cos(heading);
320
321 cacti.add(new EastNorth(clon, clat));
322 chop = true;
323 }
324
325 /* Chop down any cactus far enough that it can't
326 * be seen. ``If a cactus falls in a forest and
327 * there is nobody around did it make a sound?''
328 */
329 if (chop) {
330 for (EastNorth pos : cacti) {
331 double alat = Math.abs(pos.north() - lat);
332 double alon = Math.abs(pos.east() - lon);
333 if (alat + alon > 2 * maxdist)
334 todelete.add(pos);
335 }
336 cacti.removeAll(todelete);
337 todelete = new ArrayList<EastNorth>();
338 }
339 }
340
341 int frame;
342 boolean downloading = false;
343 protected void screen_repaint() {
344 /* Draw background first */
345 sky_paint();
346
347 /* On top of it project the floor */
348 ground_paint();
349
350 /* Messages */
351 frame ++;
352 if ((frame & 8) == 0 && downloading)
353 screen.drawImage(loading.getImage(), centre -
354 loading.getIconWidth() / 2, 50, this);
355
356 /* Sprites */
357 sprites_paint();
358 }
359
360 static double max3(double x[]) {
361 return x[0] > x[1] ? x[2] > x[0] ? x[2] : x[0] :
362 (x[2] > x[1] ? x[2] : x[1]);
363 }
364 static double min3(double x[]) {
365 return x[0] < x[1] ? x[2] < x[0] ? x[2] : x[0] :
366 (x[2] < x[1] ? x[2] : x[1]);
367 }
368
369 protected void ground_paint() {
370 double sin = Math.sin(heading);
371 double cos = Math.cos(heading);
372
373 /* First calculate the bounding box for the visible area.
374 * The area will be (nearly) a triangle, so calculate the
375 * EastNorth for the three corners and make a bounding box.
376 */
377 double left_x = maxdist * (width - centre) / height;
378 double right_x = maxdist * (0 - centre) / height;
379 double e_lat[] = new double[] {
380 lat + (maxdist - cardist) * cos - left_x * sin,
381 lat + (maxdist - cardist) * cos - right_x * sin,
382 lat - cardist * cos, };
383 double e_lon[] = new double[] {
384 lon + (maxdist - cardist) * sin + left_x * cos,
385 lon + (maxdist - cardist) * sin + right_x * cos,
386 lon - cardist * sin, };
387 ground_view.setProjectionBounds(new ProjectionBounds(
388 new EastNorth(min3(e_lon), min3(e_lat)),
389 new EastNorth(max3(e_lon), max3(e_lat))));
390
391 /* If the layer is a WMS layer, check if any tiles are
392 * missing */
393 if (ground instanceof WMSLayer) {
394 WMSLayer wms = (WMSLayer) ground;
395 downloading = wms.hasAutoDownload() && (
396 null == wms.findImage(new EastNorth(
397 e_lon[0], e_lat[0])) ||
398 null == wms.findImage(new EastNorth(
399 e_lon[0], e_lat[0])) ||
400 null == wms.findImage(new EastNorth(
401 e_lon[0], e_lat[0])));
402 }
403
404 /* Request the image from ground layer */
405 ground.paint(ground_view.graphics, ground_view, null);
406
407 for (int y = (int) (height * horizon + 0.1); y < height; y ++) {
408 /* Assume a 60 deg vertical Field of View when
409 * calculating the distance at given pixel. */
410 double dist = ele / (1.0 * y / height - 0.6);
411 double lat_off = lat + (dist - cardist) * cos;
412 double lon_off = lon + (dist - cardist) * sin;
413
414 for (int x = 0; x < width; x ++) {
415 double p_x = dist * (x - centre) / height;
416
417 EastNorth en = new EastNorth(
418 lon_off + p_x * cos,
419 lat_off - p_x * sin);
420
421 Point pt = ground_view.getPoint(en);
422
423 int rgb = ground_view.ground_image.getRGB(
424 pt.x, pt.y);
425 screen_image.setRGB(x, y, rgb);
426 }
427 }
428 }
429
430 protected BufferedImage sky_image;
431 protected Graphics sky;
432 public void generate_sky() {
433 sky_image = new BufferedImage(sw, 70,
434 BufferedImage.TYPE_INT_ARGB);
435 sky = sky_image.getGraphics();
436
437 int n = (int) (Math.random() * sw * 0.03);
438 for (int i = 0; i < n; i ++) {
439 int t = (int) (Math.random() * 5.0);
440 int x = (int) (Math.random() *
441 (sw - cloud[t].getIconWidth()));
442 int y = (int) ((1 - Math.random() * Math.random()) *
443 (70 - cloud[t].getIconHeight()));
444 sky.drawImage(cloud[t].getImage(), x, y, this);
445 }
446
447 if (Math.random() < 0.5) {
448 int t = 0;
449 int x = (int) (300 + Math.random() * (sw - 500 -
450 aircraft[t].getIconWidth()));
451 sky.drawImage(aircraft[t].getImage(), x, 0, this);
452 }
453 }
454
455 public void sky_paint() {
456 /* for x -> 0, lim sin(x) / x = 1 */
457 int hx = (int) (-heading * maxdist * pixelperlat);
458 int hw = skyline[current_bg].getIconWidth();
459 hx = ((hx % hw) - hw) % hw;
460
461 int sx = (int) (-heading * maxdist * pixelperlat * sratio);
462 sx = ((sx % sw) - sw) % sw;
463
464 screen.drawImage(bg[current_bg].getImage(), 0, 0, this);
465 screen.drawImage(sky_image, sx, 50, this);
466 if (sw + sx < width)
467 screen.drawImage(sky_image, sx + sw, 50, this);
468 screen.drawImage(skyline[current_bg].getImage(), hx, 66, this);
469 if (hw + hx < width)
470 screen.drawImage(skyline[current_bg].getImage(),
471 hx + hw, 66, this);
472 }
473
474 protected class sprite_pos implements Comparable {
475 double dist;
476
477 int x, y, sx, sy;
478 Image sprite;
479
480 public sprite_pos() {
481 }
482
483 public int compareTo(Object x) {
484 sprite_pos other = (sprite_pos) x;
485 return (int) ((other.dist - this.dist) * 1000000.0);
486 }
487 }
488
489 /* sizes decides how many zoom levels the sprites have. We
490 * could do just normal scalling according to distance but
491 * that's not what old games did, they had prescaled sprites
492 * for the different distances and you could see the feature
493 * grow discretely as you approached it. */
494 protected final static int sizes = 8;
495
496 protected final static int maxsprites = 32;
497 protected sprite_pos sprites[] = new sprite_pos[maxsprites];
498
499 protected void sprites_paint() {
500 /* The vehicle */
501 int orientation = (wheelangle > -0.02 ? wheelangle < 0.02 ?
502 1 : 2 : 0) + current_car * 3;
503 sprites[0].sprite = car[orientation].getImage();
504 sprites[0].dist = cardist;
505 sprites[0].sx = car[orientation].getIconWidth();
506 sprites[0].x = centre - sprites[0].sx / 2;
507 sprites[0].sy = car[orientation].getIconHeight();
508 sprites[0].y = height - sprites[0].sy - 10; /* TODO */
509
510 /* The cacti */
511 double sin = Math.sin(-heading);
512 double cos = Math.cos(-heading);
513 int i = 1;
514
515 for (EastNorth ll : cacti) {
516 double clat = ll.north() - lat;
517 double clon = ll.east() - lon;
518 double dist = (clat * cos - clon * sin) + cardist;
519 double p_x = clat * sin + clon * cos;
520
521 if (dist * 8 <= cardist || dist > maxdist)
522 continue;
523
524 int x = (int) (p_x * height / dist + centre);
525 int y = (int) ((ele / dist + 0.6) * height);
526
527 if (i >= maxsprites)
528 break;
529 if (x < -10 || x > width + 10)
530 continue;
531
532 int type = (((int) (ll.north() * 10000000.0) & 31) % 3);
533 int sx = cactus[type].getIconWidth();
534 int sy = cactus[type].getIconHeight();
535
536 sprite_pos pos = sprites[i ++];
537 pos.dist = dist;
538 pos.sprite = cactus[type].getImage();
539 pos.sx = (int) (sx * cardist * 0.7 / dist);
540 pos.sy = (int) (sy * cardist * 0.7 / dist);
541 pos.x = x - pos.sx / 2;
542 pos.y = y - pos.sy;
543 }
544
545 Arrays.sort(sprites, 0, i);
546 for (sprite_pos sprite : sprites)
547 if (i --> 0)
548 screen.drawImage(sprite.sprite,
549 sprite.x, sprite.y,
550 sprite.sx, sprite.sy, this);
551 else
552 break;
553
554 if (splashframe >= 0) {
555 splashframe ++;
556 if (splashframe >= 8)
557 splashframe = -1;
558
559 int type = (((int) (splashcactus.north() *
560 10000000.0) & 31) % 3);
561 int sx = cactus[type].getIconWidth();
562 int sy = cactus[type].getIconHeight();
563 Image image = cactus[type].getImage();
564
565 for (i = 0; i < 50; i ++) {
566 int x = (int) (Math.random() * sx);
567 int y = (int) (Math.random() * sy);
568 int w = (int) (Math.random() * 20);
569 int h = (int) (Math.random() * 20);
570 int nx = centre + splashframe * (x - sx / 2);
571 int ny = height - splashframe * (sy - y);
572 int nw = w + splashframe;
573 int nh = h + splashframe;
574
575 screen.drawImage(image,
576 nx, ny, nx + nw, ny + nh,
577 x, y, x + w, y + h, this);
578 }
579 }
580 }
581
582 public boolean no_super_repaint = false;
583 protected class GamePanel extends JPanel {
584 public GamePanel() {
585 setBackground(Color.BLACK);
586 setDoubleBuffered(true);
587 }
588
589 @Override
590 public void paint(Graphics g) {
591 int w = (int) getSize().getWidth();
592 int h = (int) getSize().getHeight();
593
594 if (no_super_repaint)
595 no_super_repaint = false;
596 else
597 super.paint(g);
598
599 g.drawImage(screen_image, (w - width * scale) / 2,
600 (h - height * scale) / 2,
601 width * scale, height * scale, this);
602
603 Toolkit.getDefaultToolkit().sync();
604 }
605 }
606 JPanel panel = new GamePanel();
607
608 protected void quit() {
609 timer.stop();
610
611 car_engine.stop();
612
613 car_gps.stop();
614 car_gps.save_trace();
615
616 setVisible(false);
617 panel = null;
618 screen_image = null;
619 screen = null;
620 dispose();
621 }
622
623 /*
624 * Supposedly a thread drawing frames and sleeping in a loop is
625 * better than for animating than swing Timers. For the moment
626 * I'll use a timer because I don't want to deal with all the
627 * potential threading issues.
628 */
629 protected Timer timer;
630 public void actionPerformed(ActionEvent e) {
631 move();
632 screen_repaint();
633
634 no_super_repaint = true;
635 panel.repaint();
636 }
637
638 protected class TAdapter extends KeyAdapter {
639 @Override
640 public void keyPressed(KeyEvent e) {
641 int key = e.getKeyCode();
642
643 if (key == KeyEvent.VK_LEFT && !key_down[0]) {
644 wheelangle -= 0.02;
645 key_down[0] = true;
646 }
647
648 if (key == KeyEvent.VK_RIGHT && !key_down[1]) {
649 wheelangle += 0.02;
650 key_down[1] = true;
651 }
652
653 if (key == KeyEvent.VK_UP)
654 key_down[2] = true;
655
656 if (key == KeyEvent.VK_DOWN)
657 key_down[3] = true;
658
659 if (key == KeyEvent.VK_ESCAPE)
660 quit();
661
662 /* Toggle sound */
663 if (key == KeyEvent.VK_S) {
664 if (car_engine.is_on())
665 car_engine.stop();
666 else
667 car_engine.start();
668 }
669
670 /* Toggle cacti */
671 if (key == KeyEvent.VK_C) {
672 cacti_on = !cacti_on;
673 if (!cacti_on)
674 cacti = new ArrayList<EastNorth>();
675 }
676
677 /* Switch vehicle */
678 if (key == KeyEvent.VK_V)
679 if (current_car ++>= 1)
680 current_car = 0;
681 }
682
683 @Override
684 public void keyReleased(KeyEvent e) {
685 int key = e.getKeyCode();
686
687 if (key == KeyEvent.VK_LEFT)
688 key_down[0] = false;
689
690 if (key == KeyEvent.VK_RIGHT)
691 key_down[1] = false;
692
693 if (key == KeyEvent.VK_UP)
694 key_down[2] = false;
695
696 if (key == KeyEvent.VK_DOWN)
697 key_down[3] = false;
698 }
699 }
700 protected fake_map_view ground_view;
701}
Note: See TracBrowser for help on using the repository browser.