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

Last change on this file since 32963 was 32914, checked in by donvip, 8 years ago

fix error-prone warnings

File size: 24.0 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;
39
40public class GameWindow extends JFrame implements ActionListener {
41 public GameWindow(Layer ground) {
42 setTitle(tr("The Ultimate WMS Super-speed Turbo Challenge II"));
43 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
44 setUndecorated(true);
45 setSize(s.getScreenSize().width, s.getScreenSize().height);
46 setLocationRelativeTo(null);
47 setResizable(false);
48
49 while (s.getScreenSize().width < width * scale ||
50 s.getScreenSize().height < height * scale)
51 scale --;
52 add(panel);
53
54 setVisible(true);
55
56 /* TODO: "Intro" screen perhaps with "Hall of Fame" */
57
58 screen_image = new BufferedImage(width, height,
59 BufferedImage.TYPE_INT_RGB);
60 screen = screen_image.getGraphics();
61
62 this.ground = ground;
63 ground_view = new FakeMapView(Main.map.mapView, 0.0000001);
64
65 /* Retrieve start position */
66 EastNorth start = ground_view.parent.getCenter();
67 lat = start.north();
68 lon = start.east();
69
70 addKeyListener(new TAdapter());
71
72 timer = new Timer(80, this);
73 timer.start();
74
75 car_gps = new gps();
76 car_gps.start();
77
78 car_engine = new EngineSound();
79 car_engine.start();
80
81 for (int i = 0; i < maxsprites; i ++)
82 sprites[i] = new sprite_pos();
83
84 generate_sky();
85 }
86
87 protected EngineSound car_engine;
88
89 protected gps car_gps;
90 protected class gps extends Timer implements ActionListener {
91 public gps() {
92 super(1000, null);
93 addActionListener(this);
94
95 trackSegs = new ArrayList<>();
96 }
97
98 protected Collection<WayPoint> segment;
99 protected Collection<Collection<WayPoint>> trackSegs;
100
101 @Override
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<>();
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.getLayerManager().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<>();
236 protected List<EastNorth> todelete = new ArrayList<>();
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<>();
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 /* FIXME: the code below is commented to fix compilation problems. Not sure if the code below is needed
396 if (ground instanceof WMSLayer) {
397 WMSLayer wms = (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 static class sprite_pos implements Comparable<sprite_pos> {
478 double dist;
479
480 int x, y, sx, sy;
481 Image sprite;
482
483 public sprite_pos() {
484 }
485
486 @Override
487 public int compareTo(sprite_pos x) {
488 return (int) ((x.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 @Override
593 public void paint(Graphics g) {
594 int w = (int) getSize().getWidth();
595 int h = (int) getSize().getHeight();
596
597 if (no_super_repaint)
598 no_super_repaint = false;
599 else
600 super.paint(g);
601
602 g.drawImage(screen_image, (w - width * scale) / 2,
603 (h - height * scale) / 2,
604 width * scale, height * scale, this);
605
606 Toolkit.getDefaultToolkit().sync();
607 }
608 }
609 JPanel panel = new GamePanel();
610
611 protected void quit() {
612 timer.stop();
613
614 car_engine.stop();
615
616 car_gps.stop();
617 car_gps.save_trace();
618
619 setVisible(false);
620 panel = null;
621 screen_image = null;
622 screen = null;
623 dispose();
624 }
625
626 /*
627 * Supposedly a thread drawing frames and sleeping in a loop is
628 * better than for animating than swing Timers. For the moment
629 * I'll use a timer because I don't want to deal with all the
630 * potential threading issues.
631 */
632 protected Timer timer;
633 @Override
634 public void actionPerformed(ActionEvent e) {
635 move();
636 screen_repaint();
637
638 no_super_repaint = true;
639 panel.repaint();
640 }
641
642 protected class TAdapter extends KeyAdapter {
643 @Override
644 public void keyPressed(KeyEvent e) {
645 int key = e.getKeyCode();
646
647 if (key == KeyEvent.VK_LEFT && !key_down[0]) {
648 wheelangle -= 0.02;
649 key_down[0] = true;
650 }
651
652 if (key == KeyEvent.VK_RIGHT && !key_down[1]) {
653 wheelangle += 0.02;
654 key_down[1] = true;
655 }
656
657 if (key == KeyEvent.VK_UP)
658 key_down[2] = true;
659
660 if (key == KeyEvent.VK_DOWN)
661 key_down[3] = true;
662
663 if (key == KeyEvent.VK_ESCAPE)
664 quit();
665
666 /* Toggle sound */
667 if (key == KeyEvent.VK_S) {
668 if (car_engine.is_on())
669 car_engine.stop();
670 else
671 car_engine.start();
672 }
673
674 /* Toggle cacti */
675 if (key == KeyEvent.VK_C) {
676 cacti_on = !cacti_on;
677 if (!cacti_on)
678 cacti = new ArrayList<>();
679 }
680
681 /* Switch vehicle */
682 if (key == KeyEvent.VK_V)
683 if (current_car ++>= 1)
684 current_car = 0;
685 }
686
687 @Override
688 public void keyReleased(KeyEvent e) {
689 int key = e.getKeyCode();
690
691 if (key == KeyEvent.VK_LEFT)
692 key_down[0] = false;
693
694 if (key == KeyEvent.VK_RIGHT)
695 key_down[1] = false;
696
697 if (key == KeyEvent.VK_UP)
698 key_down[2] = false;
699
700 if (key == KeyEvent.VK_DOWN)
701 key_down[3] = false;
702 }
703 }
704 protected FakeMapView ground_view;
705}
Note: See TracBrowser for help on using the repository browser.