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

Last change on this file since 26299 was 26299, checked in by stoecker, 13 years ago

i18n update, update to josm core cleanup

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