lsnes rr2-β4
[lsnes.git] / src / emulation / sky / draw.cpp
blobd8c90f84de61c87163d945281473a1e594a2fc4e
1 #include "draw.hpp"
2 #include <cmath>
3 #include <cstdio>
4 #include "romimage.hpp"
5 #include "framebuffer.hpp"
6 #include "messages.hpp"
7 #include "library/minmax.hpp"
9 /*
10 Point of escape is approximately at normalized y=0.16.
11 Baseline is approximately at normalized y=0.52
12 1st tile after starts approximately at y=0.38
19 namespace sky
21 //Projection parameters.
22 //Inverse of normalized y0 at baseline.
23 const double z0 = 1.9;
24 //Depth projection scale.
25 const double zscale = 0.6;
26 //Point of escape on screen.
27 const double yescape = 0.16;
28 //Horizontal draw scale.
29 const double hscale = 0.15;
30 //Vertical draw scale.
31 const double vscale = 0.1;
32 //Normalized floor thickness.
33 const double fthickness = 0.33;
35 const unsigned pipe_slices = 256;
36 //Type of point representation.
37 struct point_t
39 double x;
40 double y;
43 struct pipe_cache
45 double min_h;
46 double min_v;
47 double max_h;
48 double max_v;
49 //double dmin;
50 //double dmax;
51 //double raise[pipe_slices];
52 uint32_t colors[pipe_slices];
54 struct pipe_cache pipecache[7];
56 unsigned top_palette[] = {61, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
57 unsigned front_palette[] = {62, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30};
58 unsigned right_palette[] = {63, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45};
59 unsigned left_palette[] = {64, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60};
60 double slopepoints[] = {-3.45, -2.45, -1.45, 0, 1.45, 2.45, 3.45};
61 double angles[] = {1.35, 1.07, 0.88, 0.745};
62 const char thruster_pn[80] ="BCAAABACBBABCABBCAACBBBACBCCBBCABACACABCBABCBBACBABCCABACCAAABBCBABCAACBAABBACC";
64 double calc_slope(double x)
66 return 1.6 * (hscale * x) / (1 / z0 - yescape);
69 std::pair<signed, signed> point_project(double x, double y, double z)
71 double c = (z0 + zscale * z);
72 if(c < 0.001)
73 c = 0.001;
74 double y0 = 1 / c;
75 double s = (y0 - yescape) / (1 / z0 - yescape);
76 if(s >= 0)
77 return std::make_pair(FB_WIDTH * (hscale * x * s + 0.5), FB_HEIGHT * (y0 - vscale * y * s));
78 else
79 return std::make_pair(FB_WIDTH / 2, FB_HEIGHT * yescape);
82 point_t point_project_b(double x, double y, double z)
84 double c = (z0 + zscale * z);
85 if(c < 0.001)
86 c = 0.001;
87 double y0 = 1 / c;
88 double s = (y0 - yescape) / (1 / z0 - yescape);
89 point_t p;
90 if(s >= 0) {
91 p.x = FB_WIDTH * (hscale * x * s + 0.5);
92 p.y = FB_HEIGHT * (y0 - vscale * y * s);
93 } else {
94 p.x = FB_WIDTH / 2;
95 p.y = FB_HEIGHT * yescape;
97 return p;
100 int32_t ship_sprite_normal(uint16_t hpos, int vdir, uint8_t thruster)
102 int hdir;
103 if(hpos < 18048) hdir = 0;
104 else if(hpos < 23936) hdir = 1;
105 else if(hpos < 29824) hdir = 2;
106 else if(hpos < 35712) hdir = 3;
107 else if(hpos < 41600) hdir = 4;
108 else if(hpos < 47488) hdir = 5;
109 else hdir = 6;
110 if(vdir == -1) vdir = 2;
111 return 9 * hdir + 3 * vdir + thruster + 14;
114 int32_t ship_sprite_explode(int32_t frame)
116 return (frame < 42) ? (frame / 3) : -1;
119 int32_t ship_sprite(physics& p)
121 if(p.expframe)
122 return ship_sprite_explode(p.expframe);
123 if(p.death == physics::death_finished)
124 return -1;
125 int vdir;
126 uint16_t thruster;
127 if(p.vspeed > 300) vdir = 1;
128 else if(p.vspeed < -300) vdir = -1;
129 else vdir = 0;
130 if(p.death == physics::death_fuel)
131 thruster = 0;
132 else {
133 //Some kind of PN sequence.
134 thruster = thruster_pn[p.framecounter % (sizeof(thruster_pn) - 1)] - 'A';
136 return ship_sprite_normal(p.hpos, vdir, thruster % 3);
139 void draw_sprite(double h, double v, int32_t num)
141 if(num < 0)
142 return;
143 signed x = FB_WIDTH * hscale * h + FB_WIDTH / 2 - FB_SCALE * 15;
144 signed y = FB_HEIGHT / z0 - FB_HEIGHT * vscale * v - FB_SCALE * ship.width + FB_SCALE;
145 uint32_t offset = ship.width * 30 * num;
146 //For some darn reason, CARS.LZS has the image rotated 90 degrees counterclockwise.
147 for(signed j = 0; j < FB_SCALE * (int)ship.width; j++) {
148 if(y + j < 0 || y + j > overlap_end)
149 continue;
150 uint32_t offset2 = offset + j / FB_SCALE;
151 size_t base = FB_WIDTH * (y + j) + x;
152 for(signed i = 0; i < 30 * FB_SCALE; i++) {
153 if(x + i < 0)
154 continue;
155 if(x + i >= FB_WIDTH)
156 break;
157 uint8_t c = ship.decode[offset2 + ship.width * (i / FB_SCALE)];
158 if(!c)
159 continue;
160 uint32_t pix = ship.palette[c] & 0xFFFFFF;
161 if((framebuffer[base + i] >> 24) == 0)
162 framebuffer[base + i] = pix;
167 void draw_grav_g_meter(gstate& s)
169 uint16_t realgrav = 100 * (s.p.gravity - 3);
170 uint16_t x = 116;
171 uint16_t y = 156;
172 static size_t sep = strlen(_numbers_g) / 10;
173 while(realgrav) {
174 uint8_t digit = realgrav % 10;
175 draw_block2(_numbers_g + sep * digit, y * 320 + (x - 5), dashpalette[6], dashpalette[5],
176 true);
177 x -= 5;
178 realgrav /= 10;
182 void draw_indicator(uint8_t& curval, uint8_t newval, gauge& g, uint8_t on1, uint8_t on2, uint8_t off1,
183 uint8_t off2)
185 unsigned tmp = newval;
186 if(tmp > g.maxlimit()) tmp = g.maxlimit();
187 if(curval < tmp)
188 for(unsigned i = curval; i < tmp; i++) {
189 draw_block(g.get_data(i), g.get_position(i), dashpalette[on1], dashpalette[on2]);
191 else
192 for(unsigned i = tmp; i < curval; i++) {
193 draw_block(g.get_data(i), g.get_position(i), dashpalette[off1], dashpalette[off2]);
195 curval = tmp;
198 void draw_gauges(gstate& s)
200 //draw_grav_g_meter(s);
201 uint8_t timermod = ((s.p.framecounter % 9) > 4) ? 1 : 0;
202 draw_indicator(s.speedind, (s.p.lspeed - s.p.speedbias) / 0x141, speed_dat, 2, 3, 0, 1);
203 draw_indicator(s.o2ind, (s.p.o2_left + 0xbb7) / 0xbb8, oxydisp_dat, 2, 3, 0, 1);
204 draw_indicator(s.fuelind, (s.p.fuel_left + 0xbb7) / 0xbb8, fueldisp_dat, 2, 3, 0, 1);
205 //Lock indicator.
206 bool lck = s.p.is_set(physics::flag_locked);
207 if(lck != s.lockind)
208 draw_block2(_lockind_g + (lck ? strlen(_lockind_g) / 2 : 0), 0x9c * 320 + 0xcb,
209 dashpalette[6], dashpalette[5], true);
210 s.lockind = lck;
211 //Out of oxygen blink&beep.
212 if(s.p.death == physics::death_o2 && s.beep_phase != timermod) {
213 blink_between(0xa0, 0xa1, 7, 7, dashpalette[7], dashpalette[8]);
214 if(timermod)
215 gsfx(sound_beep);
217 //Out of fuel blink&beep.
218 if(s.p.death == physics::death_fuel && s.beep_phase != timermod) {
219 blink_between(0x9b, 0xa9, 16, 5, dashpalette[7], dashpalette[8]);
220 if(timermod)
221 gsfx(sound_beep);
223 //Distance gauge.
224 uint32_t res = s.curlevel.apparent_length() / 88;
225 size_t tmp = (s.p.lpos - 3 * 65536) / res;
226 for(unsigned i = s.distind; i < tmp && i < 88; i++)
227 draw_distance_column(i, dashpalette[4]);
228 s.distind = tmp;
229 s.beep_phase = timermod;
232 inline double horizon_distance() { return (1 / yescape - z0) / zscale; }
233 inline double near_distance() { return (600 / overlap_end - z0) / zscale; }
236 void draw_quad_x(point_t p1, point_t p2, point_t p3, point_t p4, uint32_t color)
238 if(fabs(p1.x - p2.x) < 0.1)
239 return;
240 double qp1 = min(p1.x, p2.x);
241 double qp2 = max(p1.x, p2.x);
242 int x1 = floor(qp1);
243 int x2 = ceil(qp2);
244 double utmax = max(p1.y, p2.y);
245 double utmin = min(p1.y, p2.y);
246 double ltmax = max(p3.y, p4.y);
247 double ltmin = min(p3.y, p4.y);
248 signed y1 = max((int)floor(utmin), 0);
249 signed y2 = min((int)ceil(ltmax), (int)overlap_end);
250 for(signed j = y1; j < y2; j++) {
251 signed xstart = x1, xend = x2;
252 if(j < utmax) {
253 if(p1.y > p2.y)
254 xstart = floor((qp1 - qp2) * (j - p2.y) / (p1.y - p2.y) + qp2);
255 else
256 xend = ceil((qp2 - qp1) * (j - p1.y) / (p2.y - p1.y) + qp1);
258 if(j >= ltmin) {
259 if(p3.y > p4.y)
260 xend = ceil((qp1 - qp2) * (j - p4.y) / (p3.y - p4.y) + qp2);
261 else
262 xstart = floor((qp2 - qp1) * (j - p3.y) / (p4.y - p3.y) + qp1);
264 xstart = max(xstart, 0);
265 xend = min(xend, FB_WIDTH);
266 size_t base = FB_WIDTH * j;
267 if(j < overlap_start)
268 for(signed i = xstart; i < xend; i++)
269 framebuffer[base + i] = color;
270 else
271 for(signed i = xstart; i < xend; i++)
272 framebuffer_blend2(framebuffer[base + i], color);
276 void draw_quad_y(point_t p1, point_t p2, point_t p3, point_t p4, uint32_t color)
278 if(fabs(p1.y - p3.y) < 0.1)
279 return;
280 signed y1 = max((int)floor(p1.y), 0);
281 signed y2 = min((int)ceil(p3.y), (int)overlap_end);
282 for(signed j = y1; j < y2; j++) {
283 signed xstart, xend;
284 xstart = floor((p3.x - p1.x) * (j - p1.y) / (p3.y - p1.y) + p1.x);
285 xend = ceil((p4.x - p2.x) * (j - p1.y) / (p3.y - p1.y) + p2.x);
286 xstart = max(xstart, 0);
287 xend = min(xend, FB_WIDTH);
288 size_t base = FB_WIDTH * j;
289 if(j < overlap_start)
290 for(signed i = xstart; i < xend; i++)
291 framebuffer[base + i] = color;
292 else
293 for(signed i = xstart; i < xend; i++)
294 framebuffer_blend2(framebuffer[base + i], color);
298 void draw_quad_z(point_t p1, point_t p2, point_t p3, point_t p4, uint32_t color)
300 if(fabs(p1.x - p2.x) < 0.1 || fabs(p1.y - p3.y) < 0.1)
301 return;
302 signed x1 = max((int)floor(p1.x), 0);
303 signed x2 = min((int)ceil(p2.x), FB_WIDTH);
304 signed y1 = max((int)floor(p1.y), 0);
305 signed y2 = min((int)ceil(p3.y), (int)overlap_end);
306 for(signed j = y1; j < y2; j++) {
307 size_t base = FB_WIDTH * j;
308 if(j < overlap_start)
309 for(signed i = x1; i < x2; i++)
310 framebuffer[base + i] = color;
311 else
312 for(signed i = x1; i < x2; i++)
313 framebuffer_blend2(framebuffer[base + i], color);
317 void draw_quad_z_tunshadow(point_t p1, point_t p2, point_t p3, point_t p4, uint32_t color, int x)
319 if(fabs(p1.x - p2.x) < 0.1 || fabs(p1.y - p3.y) < 0.1)
320 return;
321 if(!x)
322 return; //Center pipe has no shadow.
323 signed x1 = max((int)floor(p1.x), 0);
324 signed x2 = min((int)ceil(p2.x), FB_WIDTH);
325 signed y1 = max((int)floor(p1.y), 0);
326 signed y2 = min((int)ceil(p3.y), (int)overlap_end);
327 double slope = calc_slope(slopepoints[x + 3]);
328 const double width = p2.x - p1.x;
329 const double hwidth = 0.45 * width;
330 const double center = (p1.x + p2.x) / 2;
331 for(signed j = y1; j < y2; j++) {
332 signed c1 = x2, c2 = x1;
333 size_t base = FB_WIDTH * j;
334 double ry = (p3.y - j) / (0.625 * vscale / hscale * 2 * hwidth);
335 if(ry < 1) {
336 c1 = center - hwidth * sqrt(1 - ry * ry);
337 c2 = center + hwidth * sqrt(1 - ry * ry);
339 if(x < 0) {
340 //The shadow is on left.
341 c2 = center - hwidth - slope * (p3.y - j);
342 } else {
343 //The shadow is on right.
344 c1 = center + hwidth - slope * (p3.y - j);
346 c1 = max(c1, 0);
347 c2 = min(c2, FB_WIDTH);
348 if(j < overlap_start)
349 for(signed i = c1; i < c2; i++)
350 framebuffer[base + i] = color;
351 else
352 for(signed i = c1; i < c2; i++)
353 framebuffer_blend2(framebuffer[base + i], color);
357 void draw_quad_z_tunnel(point_t p1, point_t p2, point_t p3, point_t p4, uint32_t color)
359 if(fabs(p1.x - p2.x) < 0.1 || fabs(p1.y - p3.y) < 0.1)
360 return;
361 signed x1 = max((int)floor(p1.x), 0);
362 signed x2 = min((int)ceil(p2.x), FB_WIDTH);
363 signed y1 = max((int)floor(p1.y), 0);
364 signed y2 = min((int)ceil(p3.y), (int)overlap_end);
365 const double width = p2.x - p1.x;
366 const double hwidth = 0.45 * width;
367 const double center = (p1.x + p2.x) / 2;
368 for(signed j = y1; j < y2; j++) {
369 signed c1 = x2, c2 = x2;
370 size_t base = FB_WIDTH * j;
371 double ry = (p3.y - j) / (0.625 * vscale / hscale * 2 * hwidth);
372 if(ry < 1) {
373 c1 = center - hwidth * sqrt(1 - ry * ry);
374 c2 = center + hwidth * sqrt(1 - ry * ry);
376 x1 = max(x1, 0);
377 c2 = max(c2, 0);
378 x2 = min(x2, FB_WIDTH);
379 c1 = min(c1, FB_WIDTH);
380 if(j < overlap_start) {
381 for(signed i = x1; i < c1; i++)
382 framebuffer[base + i] = color;
383 for(signed i = c2; i < x2; i++)
384 framebuffer[base + i] = color;
385 } else {
386 for(signed i = x1; i < c1; i++)
387 framebuffer_blend2(framebuffer[base + i], color);
388 for(signed i = c2; i < x2; i++)
389 framebuffer_blend2(framebuffer[base + i], color);
394 void draw_quad_z_pipefront(double z, int x, uint32_t color)
396 struct pipe_cache& p = pipecache[x + 3];
397 auto p5 = point_project_b(x - 0.5, 0, z);
398 auto p6 = point_project_b(x, 1, z);
399 auto p7 = point_project_b(x + 0.5, 0, z);
400 double ymind = p6.y;
401 double ymaxd = p5.y;
402 int16_t ymin = floor(ymind);
403 int16_t ymax = ceil(ymaxd);
404 if(ymaxd - ymind < 0.1)
405 return;
406 int16_t cheight = floor(p5.y - p6.y);
407 int16_t ciheight = floor(0.9 * (p5.y - p6.y));
408 int16_t cwidth = floor((p7.x - p5.x) / 2);
409 int16_t ciwidth = floor(0.9 * (p7.x - p5.x) / 2);
410 int16_t ccenter = floor((p7.x + p5.x) / 2);
411 int16_t nxs = ccenter - cwidth;
412 int16_t nxe = ccenter + cwidth;
413 for(signed j = ymax - cheight; j < ymax; j++) {
414 if(j < 0 || j > overlap_end)
415 continue;
416 int16_t dstart = nxs;
417 int16_t dend = nxe;
418 int16_t cstart = ccenter;
419 int16_t cend = ccenter;
420 int16_t cistart = ccenter;
421 int16_t ciend = ccenter;
422 if(true) {
423 int16_t o = ymax - j;
424 double o2 = 1.0 * o / cheight;
425 int16_t w = cwidth * sqrt(1 - o2 * o2);
426 cstart -= w;
427 cend += w;
429 if(j >= ymax - ciheight) {
430 int16_t o = ymax - j;
431 double o2 = 1.0 * o / ciheight;
432 int16_t w = ciwidth * sqrt(1 - o2 * o2);
433 cistart -= w;
434 ciend += w;
436 dstart = max(cstart, (int16_t)0);
437 dend = min(cend, (int16_t)FB_WIDTH);
438 size_t base = FB_WIDTH * j;
439 if(j < overlap_start)
440 for(signed i = dstart; i < dend; i++) {
441 if(i < cistart || i >= ciend)
442 framebuffer[base + i] = color;
444 else
445 for(signed i = dstart; i < dend; i++) {
446 if(i < cistart || i >= ciend)
447 framebuffer_blend2(framebuffer[base + i], color);
452 // x = (k*y0+b)*c*(0.5*sin(alpha)+X0)
453 // y = -(k*y0+b)*d*cos(alpha)+y0
455 // dx/dy0 = k*c*0.5*sin(alpha)+k*c*X0
456 // dy/dy0 = k*d*cos(alpha)+1
457 // dx/dy = (k*c*0.5*sin(alpha)+k*c*X0)/(k*d*cos(alpha)+1)
458 // d/dalpha[(k*c*0.5*sin(alpha)+k*c*X0)/(k*d*cos(alpha)+1)]
459 //=(k*c*0.5*cos(alpha))/(k*d*cos(alpha)+1) +(k*c*0.5*sin(alpha)+k*c*X0)*(k*d*sin(alpha))/(k*d*cos(alpha)+1)^2
461 // 0.5*k*d+0.5*cos(alpha)+X0*(k*d*sin(alpha)) = 0
463 uint32_t mix_color(uint32_t* c, uint32_t c2)
465 uint32_t low = c[c2 / 256];
466 uint32_t high = c[c2 / 256 + 1];
467 uint16_t mod = c2 % 256;
468 uint16_t imod = 256 - mod;
469 const uint32_t Lm = 0x00FF00FF;
470 const uint32_t Hm = 0xFF00FF00;
471 uint32_t L = (((low & Lm) * imod + (high & Lm) * mod) >> 8) & Lm;
472 uint32_t H = (((low & Hm) >> 8) * imod + ((high & Hm) >> 8) * mod) & Hm;
473 return L | H;
476 void rebuild_pipe_quad_cache(pipe_cache& p, int x, uint32_t* c)
478 double k = 1 / (1 / z0 - yescape);
479 double b = -yescape * k;
480 double gmin = 999999;
481 double gmax = -999999;
482 double amin = 999999;
483 double amax = -999999;
484 double y00 = (1-b)/k;
485 for(unsigned i = 0; i < 256; i++)
486 p.colors[i] = 0xFFFFFFFF;
487 //Compute span range.
488 for(double a = -M_PI / 2; a < M_PI / 2; a += 0.01) {
489 double A = vscale * cos(a);
490 double y0 = (y00 + b * A) / (1 - k * A);
491 double xp = (k * y0 + b) * (x + 0.5 * sin(a));
492 if(xp < gmin) {
493 p.min_h = x + 0.5 * sin(a);
494 p.min_v = cos(a);
495 gmin = xp;
496 amin = a;
498 if(xp > gmax) {
499 p.max_h = x + 0.5 * sin(a);
500 p.max_v = cos(a);
501 gmax = xp;
502 amax = a;
505 for(double a = amin; a < amax; a += 0.01) {
506 double A = vscale * cos(a);
507 double y0 = (y00 + b * A) / (1 - k * A);
508 double xp = (k * y0 + b) * (x + 0.5 * sin(a));
509 signed c2 = (a + M_PI / 2) * 1280 / M_PI;
510 signed x = 255 * (xp - gmin) / (gmax - gmin);
511 c2 = max(min(c2, 1280), 0);
512 x = max(min(x, 255), 0);
513 p.colors[x] = mix_color(c, c2);
515 for(unsigned i = 0; i < 256; i++) {
516 if(p.colors[i] == 0xFFFFFFFF)
517 if(i == 0) {
518 for(unsigned j = 0; j < 255; j++)
519 if(p.colors[j] != 0xFFFFFFFF) {
520 p.colors[i] = p.colors[j];
521 break;
523 } else
524 p.colors[i] = p.colors[i - 1];
526 //for(unsigned i = 0; i < 256; i++)
527 // p.colors[i] = 0xFF8000 + i;
530 void rebuild_pipe_quad_caches(uint32_t color1, uint32_t color2, uint32_t color3, uint32_t color4)
532 uint32_t c[6];
533 c[0] = color4;
534 c[1] = color3;
535 c[2] = color2;
536 c[3] = color1;
537 c[4] = color2;
538 c[5] = color3;
539 for(int x = 0; x < 7; x++)
540 rebuild_pipe_quad_cache(pipecache[x], x - 3, c);
543 void draw_quad_y_pipe_last(double z, int x, bool top)
545 struct pipe_cache& p = pipecache[x + 3];
546 uint32_t fcolor = (!top && z < 0.2) ? 0x1000000 : 0;
547 //We need these for slope projection.
548 auto p1 = point_project_b(p.min_h, p.min_v, z);
549 auto p2 = point_project_b(p.max_h, p.max_v, z);
550 auto p3 = point_project_b(p.min_h, p.min_v, z - 1);
551 auto p4 = point_project_b(p.max_h, p.max_v, z - 1);
552 auto p5 = point_project_b(x - 0.5, 0, z);
553 auto p6 = point_project_b(x, 1, z);
554 auto p7 = point_project_b(x + 0.5, 0, z);
555 if(p3.y - p1.y < 0.1)
556 return;
557 if(p4.y - p2.y < 0.1)
558 return;
559 double ymind = p6.y;
560 double ymaxd = p5.y;
561 int16_t ymin = floor(ymind);
562 int16_t ymax = ceil(ymaxd);
563 double sl1 = (p3.x - p1.x) / (p3.y - p1.y);
564 double sl2 = (p4.x - p2.x) / (p4.y - p2.y);
565 double c1 = p1.x - sl1 * p1.y;
566 double c2 = p2.x - sl2 * p2.y;
567 int16_t cheight = floor(p5.y - p6.y);
568 int16_t cwidth = floor((p7.x - p5.x) / 2);
569 int16_t ccenter = floor((p7.x + p5.x) / 2);
570 for(signed j = ymax - cheight; j < ymax; j++) {
571 if(j < 0 || j > overlap_end)
572 continue;
573 int16_t nxs = floor(sl1 * j + c1);
574 int16_t nxe = ceil(sl2 * j + c2);
575 int16_t dstart = nxs;
576 int16_t dend = nxe;
577 uint32_t cstep = 255 * 65536 / (nxe - nxs + 1);
578 uint32_t color = 0;
579 if(true) {
580 int16_t o = ymax - j;
581 double o2 = 1.0 * o / cheight;
582 int16_t w = cwidth * sqrt(1 - o2 * o2);
583 dstart = ccenter - w;
584 dend = ccenter + w;
586 dstart = max(max(dstart, nxs), (int16_t)0);
587 dend = min(min(dend, nxe), (int16_t)FB_WIDTH);
588 if(dstart > nxs)
589 color += (dstart - nxs) * cstep;
590 size_t base = FB_WIDTH * j;
591 if(j < overlap_start)
592 for(signed i = dstart; i < dend; i++) {
593 framebuffer[base + i] = fcolor | p.colors[color >> 16];
594 color += cstep;
596 else
597 for(signed i = dstart; i < dend; i++) {
598 framebuffer_blend2(framebuffer[base + i], fcolor | p.colors[color >> 16]);
599 color += cstep;
604 void draw_quad_y_pipe_first(double z1, double z2, int x, bool top)
606 struct pipe_cache& p = pipecache[x + 3];
607 uint32_t fcolor = (!top && z1 < 0) ? 0x1000000 : 0;
608 auto p1 = point_project_b(p.min_h, p.min_v, z2);
609 auto p2 = point_project_b(p.max_h, p.max_v, z2);
610 auto p3 = point_project_b(p.min_h, p.min_v, z1);
611 auto p4 = point_project_b(p.max_h, p.max_v, z1);
612 auto p5 = point_project_b(x - 0.5, 0, z1);
613 auto p6 = point_project_b(x, 1, z1);
614 auto p7 = point_project_b(x + 0.5, 0, z1);
615 double ymind = min(p1.y, p2.y);
616 double ymaxd = p5.y;
617 int16_t ymin = floor(ymind);
618 int16_t ymax = ceil(ymaxd);
619 int16_t ymin2 = ceil(max(p1.y, p2.y));
620 if(p3.y - p1.y < 0.1)
621 return;
622 if(p4.y - p2.y < 0.1)
623 return;
624 double sl1 = (p3.x - p1.x) / (p3.y - p1.y);
625 double sl2 = (p4.x - p2.x) / (p4.y - p2.y);
626 double c1 = p1.x - sl1 * p1.y;
627 double c2 = p2.x - sl2 * p2.y;
628 int16_t cheight = floor(p5.y - p6.y);
629 int16_t cwidth = floor((p7.x - p5.x) / 2);
630 int16_t ccenter = floor((p7.x + p5.x) / 2);
631 bool hclip = false;
632 uint16_t mindist = 65535;
633 for(signed j = ymin; j < ymax; j++) {
634 if(j < 0 || j > overlap_end)
635 continue;
636 int16_t nxs = floor(sl1 * j + c1);
637 int16_t nxe = ceil(sl2 * j + c2);
638 int16_t dstart = nxs;
639 int16_t dend = nxe;
640 uint32_t cstep = 255 * 65536 / (nxe - nxs + 1);
641 uint32_t color = 0;
642 int16_t cstart = ccenter;
643 int16_t cend = ccenter;
644 if(j < ymin2 && p1.y != p2.y) {
645 //The upper triangular region.
646 if(p1.y < p2.y)
647 dend = ceil((p2.x - p1.x) * (j - p1.y) / (p2.y - p1.y) + p1.x);
648 else
649 dstart = floor((p2.x - p1.x) * (j - p1.y) / (p2.y - p1.y) + p1.x);
651 if(j >= ymax - cheight) {
652 int16_t o = ymax - j;
653 double o2 = 1.0 * o / cheight;
654 int16_t w = cwidth * sqrt(1 - o2 * o2);
655 cstart -= w;
656 cend += w;
657 if(hclip) {
658 if(x < 0)
659 dstart = max((int16_t)dstart, cstart);
660 if(x > 0)
661 dend = min((int16_t)dend, cend);
662 } else {
663 uint16_t dist;
664 if(x < 0)
665 dist = cstart - dstart;
666 if(x > 0)
667 dist = dend - cend;
669 if(dist > mindist)
670 hclip = true;
671 else
672 mindist = dist;
675 dstart = max(max(dstart, nxs), (int16_t)0);
676 dend = min(min(dend, nxe), (int16_t)FB_WIDTH);
677 if(dstart > nxs)
678 color += (dstart - nxs) * cstep;
679 size_t base = FB_WIDTH * j;
680 if(j < overlap_start)
681 for(signed i = dstart; i < dend; i++) {
682 if(i < cstart || i >= cend)
683 framebuffer[base + i] = fcolor | p.colors[color >> 16];
684 color += cstep;
686 else
687 for(signed i = dstart; i < dend; i++) {
688 if(i < cstart || i >= cend)
689 framebuffer_blend2(framebuffer[base + i], fcolor |
690 p.colors[color >> 16]);
691 color += cstep;
696 void draw_quad_y_pipe(double z1, double z2, int x, bool top)
698 struct pipe_cache& p = pipecache[x + 3];
699 uint32_t fcolor = (!top && z1 < 0.2) ? 0x1000000 : 0;
700 auto p1 = point_project_b(p.min_h, p.min_v, z2);
701 auto p2 = point_project_b(p.max_h, p.max_v, z2);
702 auto p3 = point_project_b(p.min_h, p.min_v, z1);
703 auto p4 = point_project_b(p.max_h, p.max_v, z1);
704 double ymind = min(p1.y, p2.y);
705 double ymaxd = max(p3.y, p4.y);
706 int16_t ymin = floor(ymind);
707 int16_t ymax = ceil(ymaxd) + 1;
708 int16_t ymin2 = ceil(max(p1.y, p2.y));
709 int16_t ymax2 = floor(min(p3.y, p4.y));
710 if(p3.y - p1.y < 0.1)
711 return;
712 if(p4.y - p2.y < 0.1)
713 return;
714 double sl1 = (p3.x - p1.x) / (p3.y - p1.y);
715 double sl2 = (p4.x - p2.x) / (p4.y - p2.y);
716 double c1 = p1.x - sl1 * p1.y;
717 double c2 = p2.x - sl2 * p2.y;
718 //FIXME: This leaves some graphical glitching in seams.
719 for(signed j = ymin; j < ymax; j++) {
720 if(j < 0 || j > overlap_end)
721 continue;
722 int16_t nxs = floor(sl1 * j + c1);
723 int16_t nxe = ceil(sl2 * j + c2);
724 int16_t dstart = nxs;
725 int16_t dend = nxe;
726 uint32_t cstep = 255 * 65536 / (nxe - nxs + 1);
727 uint32_t color = 0;
728 if(j < ymin2 && p1.y != p2.y) {
729 //The upper triangular region.
730 if(p1.y < p2.y)
731 dend = ceil((p2.x - p1.x) * (j - p1.y) / (p2.y - p1.y) + p1.x);
732 else
733 dstart = floor((p2.x - p1.x) * (j - p1.y) / (p2.y - p1.y) + p1.x);
735 if(j >= ymax2 && p3.y != p4.y) {
736 //The lower triangular region.
737 if(p3.y < p4.y)
738 dstart = floor((p4.x - p3.x) * (j - p3.y - 1) / (p4.y - p3.y) + p3.x);
739 else
740 dend = ceil((p4.x - p3.x) * (j - p3.y - 1) / (p4.y - p3.y) + p3.x);
742 dstart = max(max(dstart, nxs), (int16_t)0);
743 dend = min(min(dend, nxe), (int16_t)FB_WIDTH);
744 if(dstart > nxs)
745 color += (dstart - nxs) * cstep;
746 size_t base = FB_WIDTH * j;
747 if(j < overlap_start)
748 for(signed i = dstart; i < dend; i++) {
749 framebuffer[base + i] = fcolor | p.colors[color >> 16];
750 color += cstep;
752 else
753 for(signed i = dstart; i < dend; i++) {
754 framebuffer_blend2(framebuffer[base + i], fcolor | p.colors[color >> 16]);
755 color += cstep;
760 void draw_level(gstate& s)
762 for(unsigned i = 0; i < overlap_start; i++)
763 for(unsigned j = 0; j < FB_WIDTH; j++)
764 framebuffer[i * FB_WIDTH + j] = origbuffer[(i / FB_SCALE) * 320 + (j / FB_SCALE)];
765 for(unsigned i = overlap_start; i < overlap_end; i++)
766 for(unsigned j = 0; j < FB_WIDTH; j++)
767 framebuffer_blend2(framebuffer[i * FB_WIDTH + j], origbuffer[(i / FB_SCALE) * 320 +
768 (j / FB_SCALE)]);
769 static signed dorder[] = {-3, 3, -2, 2, -1, 1, 0};
770 level& l = s.curlevel;
771 double zship = s.p.lpos / 65536.0;
772 double horizon = horizon_distance();
773 double near = near_distance();
774 signed maxtile = ceil(zship + horizon);
775 signed mintile = floor(zship + near - 1);
776 for(signed zt = maxtile; zt >= mintile; zt--) {
777 for(signed dxt = 0; dxt < 7; dxt++) {
778 signed xt = dorder[dxt];
779 tile t = l.at_tile(zt, xt);
780 tile tleft = l.at_tile(zt, xt - 1);
781 tile tright = l.at_tile(zt, xt + 1);
782 tile tfront = l.at_tile(zt - 1, xt);
783 tile tback = l.at_tile(zt + 1, xt);
784 //First, draw the floor level.
785 if(t.lower_floor() && (!t.is_rblock() || t.is_tunnel())) {
786 bool ifront = (s.p.vpos < 10240);
787 ifront &= (xt >= 0 || s.p.hpos < 5888 * xt + 32768 - 2944);
788 ifront &= (xt <= 0 || s.p.hpos > 5888 * xt + 32768 + 2944);
789 draw_quad_y(point_project_b(xt - 0.5, 0, zt + 1 - zship),
790 point_project_b(xt + 0.5, 0, zt + 1 - zship),
791 point_project_b(xt - 0.5, 0, zt - zship),
792 point_project_b(xt + 0.5, 0, zt - zship),
793 l.get_palette_color(top_palette[t.lower_floor()], ifront));
795 //Draw pipe shadow.
796 if(t.is_tunnel() && !tfront.is_block()) {
797 //Pipe shadow never obscures the ship.
798 draw_quad_z_tunshadow(point_project_b(xt - 0.5, 1, zt - zship),
799 point_project_b(xt + 0.5, 1, zt - zship),
800 point_project_b(xt - 0.5, 0, zt - zship),
801 point_project_b(xt + 0.5, 0, zt - zship),
802 l.get_palette_color(t.is_rblock() ? 65 : 67), xt);
804 //Draw the top surface.
805 if(t.is_rblock()) {
806 signed q = t.apparent_height();
807 bool ifront = (s.p.vpos < 10240 + q * 2560);
808 ifront &= (xt >= 0 || s.p.hpos < 5888 * xt + 32768 - 2944);
809 ifront &= (xt <= 0 || s.p.hpos > 5888 * xt + 32768 + 2944);
810 ifront |= (l.in_pipe(zt * 65536, s.p.hpos, s.p.vpos) && zt < zship);
811 draw_quad_y(point_project_b(xt - 0.5, q, zt + 1 - zship),
812 point_project_b(xt + 0.5, q, zt + 1 - zship),
813 point_project_b(xt - 0.5, q, zt - zship),
814 point_project_b(xt + 0.5, q, zt - zship),
815 l.get_palette_color(top_palette[t.upper_floor()], ifront));
817 //Left/Right block surface.
818 if(t.is_rblock() && xt < 0 && tright.apparent_height() < t.apparent_height()) {
819 signed q1 = tright.apparent_height();
820 signed q2 = t.apparent_height();
821 bool ifront = (s.p.vpos < 10240 + q2 * 2560);
822 ifront &= (s.p.hpos < 5888 * xt + 32768 + 2944);
823 draw_quad_x(point_project_b(xt + 0.5, q2, zt - zship),
824 point_project_b(xt + 0.5, q2, zt + 1 - zship),
825 point_project_b(xt + 0.5, q1, zt - zship),
826 point_project_b(xt + 0.5, q1, zt + 1 - zship),
827 l.get_palette_color(63, ifront));
829 if(t.is_rblock() && xt > 0 && tleft.apparent_height() < t.apparent_height()) {
830 signed q1 = tleft.apparent_height();
831 signed q2 = t.apparent_height();
832 bool ifront = (s.p.vpos < 10240 + q2 * 2560);
833 ifront &= (s.p.hpos > 5888 * xt + 32768 - 2944);
834 draw_quad_x(point_project_b(xt - 0.5, q2, zt + 1 - zship),
835 point_project_b(xt - 0.5, q2, zt - zship),
836 point_project_b(xt - 0.5, q1, zt + 1- zship),
837 point_project_b(xt - 0.5, q1, zt - zship),
838 l.get_palette_color(64, ifront));
840 //Front block surface.
841 if(t.is_rblock() && tfront.apparent_height() < t.apparent_height()) {
842 signed q1 = tfront.apparent_height();
843 signed q2 = t.apparent_height();
844 bool ifront = (zt < zship);
845 if(t.is_tunnel() && !tfront.is_block())
846 draw_quad_z_tunnel(point_project_b(xt - 0.5, q2, zt - zship),
847 point_project_b(xt + 0.5, q2, zt - zship),
848 point_project_b(xt - 0.5, q1, zt - zship),
849 point_project_b(xt + 0.5, q1, zt - zship),
850 l.get_palette_color(62, ifront));
851 else {
852 draw_quad_z(point_project_b(xt - 0.5, q2, zt - zship),
853 point_project_b(xt + 0.5, q2, zt - zship),
854 point_project_b(xt - 0.5, q1, zt - zship),
855 point_project_b(xt + 0.5, q1, zt - zship),
856 l.get_palette_color(62, ifront));
859 //Last tunnel block.
860 if(t.is_tunnel() && !t.is_rblock()) {
861 bool top = (s.p.vpos > 10240);
862 top &= !l.in_pipe(zt * 65536, s.p.hpos, s.p.vpos);
863 if(tback.is_rblock() || !tback.is_block())
864 draw_quad_y_pipe_last(zt + 1 - zship, xt, top);
865 //Standalone tunnel top.
866 if(tfront.is_block())
867 draw_quad_y_pipe(zt - zship, zt + 1 - zship, xt, top);
868 else
869 draw_quad_y_pipe_first(zt - zship, zt + 1 - zship, xt, top);
870 //Standalone tunnel front.
871 if(!tfront.is_block()) {
872 bool ifront = (zt < zship);
873 draw_quad_z_pipefront(zt - zship, xt, l.get_palette_color(66, ifront));
876 //Left/Right floor surface.
877 if(xt < 0 && t.lower_floor() && !tright.lower_floor()) {
878 bool ifront = (s.p.vpos < 10240);
879 ifront &= (s.p.hpos > 5888 * xt + 32768);
880 draw_quad_x(point_project_b(xt + 0.5, 0, zt - zship),
881 point_project_b(xt + 0.5, 0, zt + 1 - zship),
882 point_project_b(xt + 0.5, -fthickness, zt - zship),
883 point_project_b(xt + 0.5, -fthickness, zt + 1 - zship),
884 l.get_palette_color(right_palette[t.lower_floor()]));
886 if(xt > 0 && t.lower_floor() && !tleft.lower_floor()) {
887 bool ifront = (s.p.vpos < 10240);
888 ifront &= (s.p.hpos > 5888 * xt + 32768);
889 draw_quad_x(point_project_b(xt - 0.5, 0, zt + 1 - zship),
890 point_project_b(xt - 0.5, 0, zt - zship),
891 point_project_b(xt - 0.5, -fthickness, zt + 1- zship),
892 point_project_b(xt - 0.5, -fthickness, zt - zship),
893 l.get_palette_color(left_palette[t.lower_floor()], ifront));
895 //Front floor surface.
896 if(t.lower_floor() && !tfront.lower_floor()) {
897 bool ifront = (s.p.vpos < 10240);
898 ifront &= (s.p.hpos < 5888 * xt + 32768);
899 draw_quad_z(point_project_b(xt - 0.5, 0, zt - zship),
900 point_project_b(xt + 0.5, 0, zt - zship),
901 point_project_b(xt - 0.5, -fthickness, zt - zship),
902 point_project_b(xt + 0.5, -fthickness, zt - zship),
903 l.get_palette_color(front_palette[t.lower_floor()], ifront));
907 draw_sprite((s.p.hpos - 32768.0) / 5888.0, (s.p.vpos - 10240.0) / 2560.0,
908 ship_sprite(s.p));
911 const char* period = "%&(ccK";
912 const char* dash = "%'(cScS";
913 const char* vline = "%$(b";
914 const char* tback = "%F*ccccccccccccccccccccccccccccccccccccccccc";
916 void draw_timeattack_time(const char* msg)
918 uint16_t w = 321;
919 uint16_t nst = strlen(_numbers_g) / 10;
920 draw_block2(tback, 0, 0xFFFFFF, 0xFFFFFF, false);
921 while(*msg) {
922 if(*msg >= '0' && *msg <= '9') {
923 draw_block2(_numbers_g + (*msg - '0') * nst, w, 0xFFFFFF, 0xFFFFFF, true);
924 draw_block2(vline, w + 4, 0xFFFFFF, 0xFFFFFF, true);
925 w += 5;
926 } else if(*msg == ':') {
927 draw_block2(period, w, 0xFFFFFF, 0xFFFFFF, true);
928 w += 3;
929 } else if(*msg == '-') {
930 draw_block2(dash, w, 0xFFFFFF, 0xFFFFFF, true);
931 draw_block2(vline, w + 4, 0xFFFFFF, 0xFFFFFF, true);
932 w += 5;
934 msg++;
936 for(unsigned i = 0; i < 7; i++)
937 for(unsigned j = 0; j < 35; j++)
938 origbuffer[320 * i + j] ^= 0xFFFFFF;
939 render_framebuffer_update(0, 0, 35, 7);
942 void draw_timeattack_time(uint16_t frames)
944 char msg[8];
945 if(frames > 64807) {
946 strcpy(msg, "----:--");
947 } else {
948 unsigned seconds = 18227 * frames / 656250;
949 unsigned subseconds = 36454U * frames / 13125 - 100 * seconds;
950 sprintf(msg, "%u:%02u", seconds, subseconds);
952 draw_timeattack_time(msg);