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

Last change on this file was 36483, checked in by stoecker, 3 months ago

set eol-style, fix checkstyle issues, add ignores

  • Property svn:eol-style set to native
File size: 24.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package wmsturbochallenge;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Graphics;
8import java.awt.Image;
9import java.awt.Point;
10import java.awt.Toolkit;
11import java.awt.event.ActionEvent;
12import java.awt.event.ActionListener;
13import java.awt.event.KeyAdapter;
14import java.awt.event.KeyEvent;
15import java.awt.image.BufferedImage;
16import java.util.ArrayList;
17import java.util.Arrays;
18import java.util.Collection;
19import java.util.HashMap;
20import java.util.List;
21
22import javax.swing.ImageIcon;
23import javax.swing.JFrame;
24import javax.swing.JPanel;
25import javax.swing.Timer;
26
27import org.openstreetmap.josm.data.ProjectionBounds;
28import org.openstreetmap.josm.data.coor.EastNorth;
29import org.openstreetmap.josm.data.gpx.GpxData;
30import org.openstreetmap.josm.data.gpx.GpxTrack;
31import org.openstreetmap.josm.data.gpx.WayPoint;
32import org.openstreetmap.josm.data.projection.ProjectionRegistry;
33import org.openstreetmap.josm.gui.MainApplication;
34import org.openstreetmap.josm.gui.layer.GpxLayer;
35import org.openstreetmap.josm.gui.layer.Layer;
36
37/**
38 * This implements the game logic.
39 * @author Andrzej Zaborowski
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(MainApplication.getMap().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(ProjectionRegistry.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 GpxTrack(trackSegs, new HashMap<>()));
135
136 ground_view.parent.getLayerManager().addLayer(
137 new GpxLayer(data, "Car GPS trace"));
138 }
139 }
140
141 /* These are EastNorth, not actual LatLon */
142 protected double lat, lon;
143 /* Camera's altitude above surface (same units as lat/lon above) */
144 protected double ele = 0.000003;
145 /* Cut off at ~75px from bottom of the screen */
146 protected double horizon = 0.63;
147 /* Car's distance from the camera lens */
148 protected double cardist = ele * 3;
149
150 /* Pixels per pixel, the bigger the more oldschool :-) */
151 protected int scale = 5;
152
153 protected BufferedImage screen_image;
154 protected Graphics screen;
155 protected int width = 320;
156 protected int height = 200;
157 protected int centre = width / 2;
158
159 double maxdist = ele / (horizon - 0.6);
160 double realwidth = maxdist * width / height;
161 double pixelperlat = 1.0 * width / realwidth;
162 double sratio = 0.85;
163 protected int sw = (int) (2 * Math.PI * maxdist * pixelperlat * sratio);
164
165 /* TODO: figure out how to load these dynamically after splash
166 * screen is shown */
167 protected static final ImageIcon[] car = new ImageIcon[] {
168 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
169 WMSRacer.class.getResource(
170 "/images/car0-l.png"))),
171 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
172 WMSRacer.class.getResource(
173 "/images/car0.png"))),
174 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
175 WMSRacer.class.getResource(
176 "/images/car0-r.png"))),
177 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
178 WMSRacer.class.getResource(
179 "/images/car1-l.png"))),
180 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
181 WMSRacer.class.getResource(
182 "/images/car1.png"))),
183 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
184 WMSRacer.class.getResource(
185 "/images/car1-r.png"))),
186 };
187 protected static final ImageIcon[] bg = new ImageIcon[] {
188 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
189 WMSRacer.class.getResource(
190 "/images/bg0.png"))),
191 };
192 protected static final ImageIcon[] skyline = new ImageIcon[] {
193 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
194 WMSRacer.class.getResource(
195 "/images/horizon.png"))),
196 };
197 protected static final ImageIcon[] cactus = new ImageIcon[] {
198 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
199 WMSRacer.class.getResource(
200 "/images/cactus0.png"))),
201 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
202 WMSRacer.class.getResource(
203 "/images/cactus1.png"))),
204 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
205 WMSRacer.class.getResource(
206 "/images/cactus2.png"))),
207 };
208 protected static final ImageIcon[] cloud = new ImageIcon[] {
209 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
210 WMSRacer.class.getResource(
211 "/images/cloud0.png"))),
212 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
213 WMSRacer.class.getResource(
214 "/images/cloud1.png"))),
215 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
216 WMSRacer.class.getResource(
217 "/images/cloud2.png"))),
218 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
219 WMSRacer.class.getResource(
220 "/images/cloud3.png"))),
221 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
222 WMSRacer.class.getResource(
223 "/images/cloud4.png"))),
224 };
225 protected static final ImageIcon[] aircraft = new ImageIcon[] {
226 new ImageIcon(Toolkit.getDefaultToolkit().createImage(
227 WMSRacer.class.getResource(
228 "/images/aircraft0.png"))),
229 };
230 protected static final ImageIcon loading = new ImageIcon(
231 Toolkit.getDefaultToolkit().createImage(
232 WMSRacer.class.getResource(
233 "/images/loading.png")));
234 protected static Toolkit s = Toolkit.getDefaultToolkit();
235 protected int current_bg = 0;
236 protected int current_car = 0;
237 protected boolean cacti_on = true;
238 protected List<EastNorth> cacti = new ArrayList<>();
239 protected List<EastNorth> todelete = new ArrayList<>();
240 protected int splashframe = -1;
241 protected EastNorth splashcactus;
242
243 protected Layer ground;
244 protected double heading = 0.0;
245 protected double wheelangle = 0.0;
246 protected double speed = 0.0;
247 protected boolean[] key_down = new boolean[] {
248 false, false, false, false, };
249
250 protected void move() {
251 /* Left */
252 /* (At high speeds make more gentle turns) */
253 if (key_down[0])
254 wheelangle -= 0.1 / (1.0 + Math.abs(speed));
255 /* Right */
256 if (key_down[1])
257 wheelangle += 0.1 / (1.0 + Math.abs(speed));
258 if (wheelangle > 0.3)
259 wheelangle = 0.3; /* Radians */
260 if (wheelangle < -0.3)
261 wheelangle = -0.3;
262
263 wheelangle *= 0.7;
264
265 /* Up */
266 if (key_down[2])
267 speed += speed >= 0.0 ? 1.0 / (2.0 + speed) : 0.5;
268 /* Down */
269 if (key_down[3]) {
270 if (speed >= 0.5) /* Brake (TODO: sound) */
271 speed -= 0.5;
272 else if (speed >= 0.01) /* Brake (TODO: sound) */
273 speed = 0.0;
274 else /* Reverse */
275 speed -= 0.5 / (4.0 - speed);
276 }
277
278 speed *= 0.97;
279 car_engine.set_speed(speed);
280
281 if (speed > -0.1 && speed < 0.1)
282 speed = 0;
283
284 heading += wheelangle * speed;
285
286 boolean chop = false;
287 double newlat = lat + Math.cos(heading) * speed * ele * 0.2;
288 double newlon = lon + Math.sin(heading) * speed * ele * 0.2;
289 for (EastNorth pos : cacti) {
290 double alat = Math.abs(pos.north() - newlat);
291 double alon = Math.abs(pos.east() - newlon);
292 if (alat + alon < ele * 1.0) {
293 if (Math.abs(speed) < 2.0) {
294 if (speed > 0.0)
295 speed = -0.5;
296 else
297 speed = 0.3;
298 newlat = lat;
299 newlon = lon;
300 break;
301 }
302
303 chop = true;
304 splashframe = 0;
305 splashcactus = pos;
306 todelete.add(pos);
307 }
308 }
309
310 lat = newlat;
311 lon = newlon;
312
313 /* Seed a new cactus if we're moving.
314 * TODO: hook into data layers and avoid putting the cactus on
315 * the road!
316 */
317 if (cacti_on && Math.random() * 30.0 < speed) {
318 double left_x = maxdist * (width - centre) / height;
319 double right_x = maxdist * (0 - centre) / height;
320 double x = left_x + Math.random() * (right_x - left_x);
321 double clat = lat + (maxdist - cardist) *
322 Math.cos(heading) - x * Math.sin(heading);
323 double clon = lon + (maxdist - cardist) *
324 Math.sin(heading) + x * Math.cos(heading);
325
326 cacti.add(new EastNorth(clon, clat));
327 chop = true;
328 }
329
330 /* Chop down any cactus far enough that it can't
331 * be seen. ``If a cactus falls in a forest and
332 * there is nobody around did it make a sound?''
333 */
334 if (chop) {
335 for (EastNorth pos : cacti) {
336 double alat = Math.abs(pos.north() - lat);
337 double alon = Math.abs(pos.east() - lon);
338 if (alat + alon > 2 * maxdist)
339 todelete.add(pos);
340 }
341 cacti.removeAll(todelete);
342 todelete = new ArrayList<>();
343 }
344 }
345
346 int frame;
347 boolean downloading = false;
348
349 protected void screen_repaint() {
350 /* Draw background first */
351 sky_paint();
352
353 /* On top of it project the floor */
354 ground_paint();
355
356 /* Messages */
357 frame++;
358 if ((frame & 8) == 0 && downloading) {
359 screen.drawImage(loading.getImage(), centre -
360 loading.getIconWidth() / 2, 50, this);
361 }
362
363 /* Sprites */
364 sprites_paint();
365 }
366
367 static double max3(double[] x) {
368 return x[0] > x[1] ? x[2] > x[0] ? x[2] : x[0] :
369 (x[2] > x[1] ? x[2] : x[1]);
370 }
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
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.