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

Last change on this file since 20048 was 19990, checked in by balrog-kun, 15 years ago

Initial commit of the wms-turbo-challenge2 plugin.

This may not be an extremely useful plugin but it will still be great to
have it in the source tree so that it updated when upstream interfaces
change, and get nice versioning.

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