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

Last change on this file since 30737 was 30737, checked in by donvip, 10 years ago

[josm_plugins] fix Java 7 / unused code warnings

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 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 FakeMapView(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<>();
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<>();
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<>();
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 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<sprite_pos> {
477 double dist;
478
479 int x, y, sx, sy;
480 Image sprite;
481
482 public sprite_pos() {
483 }
484
485 public int compareTo(sprite_pos x) {
486 return (int) ((x.dist - this.dist) * 1000000.0);
487 }
488 }
489
490 /* sizes decides how many zoom levels the sprites have. We
491 * could do just normal scalling according to distance but
492 * that's not what old games did, they had prescaled sprites
493 * for the different distances and you could see the feature
494 * grow discretely as you approached it. */
495 protected final static int sizes = 8;
496
497 protected final static int maxsprites = 32;
498 protected sprite_pos sprites[] = new sprite_pos[maxsprites];
499
500 protected void sprites_paint() {
501 /* The vehicle */
502 int orientation = (wheelangle > -0.02 ? wheelangle < 0.02 ?
503 1 : 2 : 0) + current_car * 3;
504 sprites[0].sprite = car[orientation].getImage();
505 sprites[0].dist = cardist;
506 sprites[0].sx = car[orientation].getIconWidth();
507 sprites[0].x = centre - sprites[0].sx / 2;
508 sprites[0].sy = car[orientation].getIconHeight();
509 sprites[0].y = height - sprites[0].sy - 10; /* TODO */
510
511 /* The cacti */
512 double sin = Math.sin(-heading);
513 double cos = Math.cos(-heading);
514 int i = 1;
515
516 for (EastNorth ll : cacti) {
517 double clat = ll.north() - lat;
518 double clon = ll.east() - lon;
519 double dist = (clat * cos - clon * sin) + cardist;
520 double p_x = clat * sin + clon * cos;
521
522 if (dist * 8 <= cardist || dist > maxdist)
523 continue;
524
525 int x = (int) (p_x * height / dist + centre);
526 int y = (int) ((ele / dist + 0.6) * height);
527
528 if (i >= maxsprites)
529 break;
530 if (x < -10 || x > width + 10)
531 continue;
532
533 int type = (((int) (ll.north() * 10000000.0) & 31) % 3);
534 int sx = cactus[type].getIconWidth();
535 int sy = cactus[type].getIconHeight();
536
537 sprite_pos pos = sprites[i ++];
538 pos.dist = dist;
539 pos.sprite = cactus[type].getImage();
540 pos.sx = (int) (sx * cardist * 0.7 / dist);
541 pos.sy = (int) (sy * cardist * 0.7 / dist);
542 pos.x = x - pos.sx / 2;
543 pos.y = y - pos.sy;
544 }
545
546 Arrays.sort(sprites, 0, i);
547 for (sprite_pos sprite : sprites)
548 if (i --> 0)
549 screen.drawImage(sprite.sprite,
550 sprite.x, sprite.y,
551 sprite.sx, sprite.sy, this);
552 else
553 break;
554
555 if (splashframe >= 0) {
556 splashframe ++;
557 if (splashframe >= 8)
558 splashframe = -1;
559
560 int type = (((int) (splashcactus.north() *
561 10000000.0) & 31) % 3);
562 int sx = cactus[type].getIconWidth();
563 int sy = cactus[type].getIconHeight();
564 Image image = cactus[type].getImage();
565
566 for (i = 0; i < 50; i ++) {
567 int x = (int) (Math.random() * sx);
568 int y = (int) (Math.random() * sy);
569 int w = (int) (Math.random() * 20);
570 int h = (int) (Math.random() * 20);
571 int nx = centre + splashframe * (x - sx / 2);
572 int ny = height - splashframe * (sy - y);
573 int nw = w + splashframe;
574 int nh = h + splashframe;
575
576 screen.drawImage(image,
577 nx, ny, nx + nw, ny + nh,
578 x, y, x + w, y + h, this);
579 }
580 }
581 }
582
583 public boolean no_super_repaint = false;
584 protected class GamePanel extends JPanel {
585 public GamePanel() {
586 setBackground(Color.BLACK);
587 setDoubleBuffered(true);
588 }
589
590 @Override
591 public void paint(Graphics g) {
592 int w = (int) getSize().getWidth();
593 int h = (int) getSize().getHeight();
594
595 if (no_super_repaint)
596 no_super_repaint = false;
597 else
598 super.paint(g);
599
600 g.drawImage(screen_image, (w - width * scale) / 2,
601 (h - height * scale) / 2,
602 width * scale, height * scale, this);
603
604 Toolkit.getDefaultToolkit().sync();
605 }
606 }
607 JPanel panel = new GamePanel();
608
609 protected void quit() {
610 timer.stop();
611
612 car_engine.stop();
613
614 car_gps.stop();
615 car_gps.save_trace();
616
617 setVisible(false);
618 panel = null;
619 screen_image = null;
620 screen = null;
621 dispose();
622 }
623
624 /*
625 * Supposedly a thread drawing frames and sleeping in a loop is
626 * better than for animating than swing Timers. For the moment
627 * I'll use a timer because I don't want to deal with all the
628 * potential threading issues.
629 */
630 protected Timer timer;
631 public void actionPerformed(ActionEvent e) {
632 move();
633 screen_repaint();
634
635 no_super_repaint = true;
636 panel.repaint();
637 }
638
639 protected class TAdapter extends KeyAdapter {
640 @Override
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<>();
676 }
677
678 /* Switch vehicle */
679 if (key == KeyEvent.VK_V)
680 if (current_car ++>= 1)
681 current_car = 0;
682 }
683
684 @Override
685 public void keyReleased(KeyEvent e) {
686 int key = e.getKeyCode();
687
688 if (key == KeyEvent.VK_LEFT)
689 key_down[0] = false;
690
691 if (key == KeyEvent.VK_RIGHT)
692 key_down[1] = false;
693
694 if (key == KeyEvent.VK_UP)
695 key_down[2] = false;
696
697 if (key == KeyEvent.VK_DOWN)
698 key_down[3] = false;
699 }
700 }
701 protected FakeMapView ground_view;
702}
Note: See TracBrowser for help on using the repository browser.