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

Last change on this file since 34661 was 34570, checked in by donvip, 6 years ago

update to JOSM 14153

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