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

Last change on this file since 33342 was 33342, checked in by stoecker, 7 years ago

checkstyle fixes

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