lower res, still slow
[gbajam22] / src / voxscape.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <stdint.h>
5 #include <math.h>
6 #include <assert.h>
7 #include "voxscape.h"
8 #include "debug.h"
9
10 /* hardcoded dimensions for the GBA */
11 #define FBWIDTH         240
12 #define FBHEIGHT        160
13 /* map size */
14 #define XSZ                     256
15 #define YSZ                     256
16 #define XSHIFT          8
17 #define XMASK           0xff
18 #define YMASK           0xff
19 #define HSCALE          20
20
21
22 #define NO_LERP
23
24 #define XLERP(a, b, t, fp) \
25         ((((a) << (fp)) + ((b) - (a)) * (t)) >> fp)
26
27 enum {
28         SLICELEN        = 1
29 };
30
31 struct voxscape {
32         int xsz, ysz;
33         unsigned char *height;
34         unsigned char *color;
35         int xshift, xmask, ymask;
36
37         int hfilt, cfilt;
38
39         /* framebuffer */
40         uint16_t *fb;
41         int fbwidth, fbheight;
42         int *coltop;
43         int horizon;
44
45         /* view */
46         int32_t x, y, angle;
47         int vheight;
48
49         /* projection */
50         int fov, znear, zfar;
51         int nslices;
52         int32_t *slicelen;
53         int proj_dist;
54
55         int zfog;       /* fog start Z (0: no fog) */
56         uint8_t fogcolor;
57
58         unsigned int valid;
59 };
60
61 struct voxscape *vox_create(int xsz, int ysz, uint8_t *himg, uint8_t *cimg)
62 {
63         struct voxscape *vox;
64
65         assert(xsz == XSZ && ysz == YSZ);
66
67         if(!(vox = calloc(1, sizeof *vox))) {
68                 return 0;
69         }
70         vox->height = himg;
71         vox->color = cimg;
72         vox->xsz = xsz;
73         vox->ysz = ysz;
74
75         vox->xmask = vox->xsz - 1;
76         vox->ymask = vox->ysz - 1;
77
78         vox->xshift = -1;
79         while(xsz) {
80                 xsz >>= 1;
81                 vox->xshift++;
82         }
83
84         vox->vheight = 80;
85         vox->proj_dist = 4;     /* TODO */
86
87         return vox;
88 }
89
90 void vox_free(struct voxscape *vox)
91 {
92         if(!vox) return;
93
94         free(vox->color);
95         free(vox->height);
96         free(vox->coltop);
97         free(vox->slicelen);
98         free(vox);
99 }
100
101 uint8_t *vox_texture(struct voxscape *vox, uint8_t *data)
102 {
103         if(data) {
104                 memcpy(vox->color, data, vox->xsz * vox->ysz);
105         }
106         return vox->color;
107 }
108
109 uint8_t *vox_heightmap(struct voxscape *vox, uint8_t *data)
110 {
111         if(data) {
112                 memcpy(vox->height, data, vox->xsz * vox->ysz);
113         }
114         return vox->height;
115 }
116
117 void vox_fog(struct voxscape *vox, int zstart, uint8_t color)
118 {
119         vox->zfog = zstart;
120         vox->fogcolor = color;
121 }
122
123 #define H(x, y) \
124         vox->height[((((y) >> 16) & YMASK) << XSHIFT) + (((x) >> 16) & XMASK)]
125 #define C(x, y) \
126         vox->color[((((y) >> 16) & YMASK) << XSHIFT) + (((x) >> 16) & XMASK)]
127
128 #ifdef NO_LERP
129 #define vox_height(vox, x, y)   H(x, y)
130 #define vox_color(vox, x, y)    C(x, y)
131
132 #else
133
134 int vox_height(struct voxscape *vox, int32_t x, int32_t y)
135 {
136         int32_t u, v;
137         int h00, h01, h10, h11, h0, h1;
138
139         if(!vox->hfilt) {
140                 return H(x, y);
141         }
142
143         h00 = H(x, y);
144         h01 = H(x, y + 0x10000);
145         h10 = H(x + 0x10000, y);
146         h11 = H(x + 0x10000, y + 0x10000);
147
148         u = x & 0xffff;
149         v = y & 0xffff;
150
151         h0 = XLERP(h00, h01, v, 16);
152         h1 = XLERP(h10, h11, v, 16);
153         return XLERP(h0, h1, u, 16);
154 }
155
156 int vox_color(struct voxscape *vox, int32_t x, int32_t y)
157 {
158         int32_t u, v;
159         int c00, c01, c10, c11, c0, c1;
160
161         if(!vox->cfilt) {
162                 return C(x, y);
163         }
164
165         c00 = C(x, y);
166         c01 = C(x, y + 0x10000);
167         c10 = C(x + 0x10000, y);
168         c11 = C(x + 0x10000, y + 0x10000);
169
170         u = x & 0xffff;
171         v = y & 0xffff;
172
173         c0 = XLERP(c00, c01, v, 16);
174         c1 = XLERP(c10, c11, v, 16);
175         return XLERP(c0, c1, u, 16);
176 }
177 #endif  /* !NO_LERP */
178
179
180 void vox_filter(struct voxscape *vox, int hfilt, int cfilt)
181 {
182         vox->hfilt = hfilt;
183         vox->cfilt = cfilt;
184 }
185
186 void vox_framebuf(struct voxscape *vox, int xres, int yres, void *fb, int horizon)
187 {
188         if(xres != vox->fbwidth) {
189                 if(!(vox->coltop = iwram_sbrk(xres * sizeof *vox->coltop))) {
190                         panic(get_pc(), "vox_framebuf: failed to allocate column table (%d)\n", xres);
191                         return;
192                 }
193         }
194         vox->fb = fb;
195         vox->fbwidth = xres;
196         vox->fbheight = yres;
197         vox->horizon = horizon >= 0 ? horizon : (vox->fbheight >> 1);
198 }
199
200 void vox_view(struct voxscape *vox, int32_t x, int32_t y, int h, int32_t angle)
201 {
202         if(h < 0) {
203                 h = vox_height(vox, x, y) - h;
204         }
205
206         vox->x = x;
207         vox->y = y;
208         vox->vheight = h;
209         vox->angle = angle;
210
211         vox->valid &= ~SLICELEN;
212 }
213
214 void vox_proj(struct voxscape *vox, int fov, int znear, int zfar)
215 {
216         vox->fov = fov;
217         vox->znear = znear;
218         vox->zfar = zfar;
219
220         vox->nslices = vox->zfar - vox->znear;
221         free(vox->slicelen);
222         if(!(vox->slicelen = iwram_sbrk(vox->nslices * sizeof *vox->slicelen))) {
223                 panic(get_pc(), "vox_proj: failed to allocate slice length table (%d)\n", vox->nslices);
224                 return;
225         }
226
227         vox->valid &= ~SLICELEN;
228 }
229
230 /* algorithm:
231  * calculate extents of horizontal equidistant line from the viewer based on fov
232  * for each column step along this line and compute height for each pixel
233  * fill the visible (top) part of each column
234  */
235
236 void vox_render(struct voxscape *vox)
237 {
238         int i;
239
240         vox_begin(vox);
241         for(i=0; i<vox->nslices; i++) {
242                 vox_render_slice(vox, i);
243         }
244 }
245
246 void vox_begin(struct voxscape *vox)
247 {
248         int i;
249
250         memset(vox->fb, 0, FBWIDTH * FBHEIGHT);
251         memset(vox->coltop, 0, FBWIDTH * sizeof *vox->coltop);
252
253         if(!(vox->valid & SLICELEN)) {
254                 float theta = (float)vox->fov * M_PI / 360.0f;  /* half angle */
255                 for(i=0; i<vox->nslices; i++) {
256                         vox->slicelen[i] = (int32_t)((vox->znear + i) * tan(theta) * 4.0f * 65536.0f);
257                 }
258                 vox->valid |= SLICELEN;
259         }
260 }
261
262 ARM_IWRAM
263 void vox_render_slice(struct voxscape *vox, int n)
264 {
265         int i, j, hval, last_hval, colstart, colheight, col, z, offs, last_offs = -1;
266         int32_t x, y, len, xstep, ystep;
267         uint8_t color, last_col;
268         uint16_t *fbptr;
269
270         z = vox->znear + n;
271
272         len = vox->slicelen[n] >> 8;
273         xstep = (((COS(vox->angle) >> 4) * len) >> 4) / FBWIDTH;
274         ystep = (((SIN(vox->angle) >> 4) * len) >> 4) / FBWIDTH;
275
276         x = vox->x - SIN(vox->angle) * z - xstep * (FBWIDTH / 2);
277         y = vox->y + COS(vox->angle) * z - ystep * (FBWIDTH / 2);
278
279         for(i=1; i<FBWIDTH / 2; i++) {
280                 col = i * 2;
281                 offs = (((y >> 16) & YMASK) << XSHIFT) + ((x >> 16) & XMASK);
282                 if(offs == last_offs) {
283                         hval = last_hval;
284                         color = last_col;
285                 } else {
286                         hval = vox->height[offs] - vox->vheight;
287                         hval = hval * HSCALE / (vox->znear + n) + vox->horizon;
288                         if(hval > FBHEIGHT) hval = FBHEIGHT;
289                         color = vox->color[offs];
290                         last_offs = offs;
291                         last_hval = hval;
292                         last_col = color;
293                 }
294                 if(hval > vox->coltop[col]) {
295                         colstart = FBHEIGHT - hval;
296                         colheight = hval - vox->coltop[col];
297                         fbptr = vox->fb + colstart * (FBWIDTH / 2) + i;
298
299                         for(j=0; j<colheight; j++) {
300                                 *fbptr |= color;
301                                 fbptr += FBWIDTH / 2;
302                         }
303                         vox->coltop[col] = hval;
304                 }
305                 x += xstep;
306                 y += ystep;
307
308                 col++;
309                 offs = (((y >> 16) & YMASK) << XSHIFT) + ((x >> 16) & XMASK);
310                 if(offs == last_offs) {
311                         hval = last_hval;
312                         color = last_col;
313                 } else {
314                         hval = vox->height[offs] - vox->vheight;
315                         hval = hval * HSCALE / (vox->znear + n) + vox->horizon;
316                         if(hval > FBHEIGHT) hval = FBHEIGHT;
317                         color = vox->color[offs];
318                         last_offs = offs;
319                         last_hval = hval;
320                         last_col = color;
321                 }
322                 if(hval > vox->coltop[col]) {
323                         colstart = FBHEIGHT - hval;
324                         colheight = hval - vox->coltop[col];
325                         fbptr = vox->fb + colstart * (FBWIDTH / 2) + i;
326
327                         for(j=0; j<colheight; j++) {
328                                 *fbptr |= ((uint16_t)color << 8);
329                                 fbptr += FBWIDTH / 2;
330                         }
331                         vox->coltop[col] = hval;
332                 }
333                 x += xstep;
334                 y += ystep;
335         }
336 }
337
338 ARM_IWRAM
339 void vox_sky_solid(struct voxscape *vox, uint8_t color)
340 {
341         int i, j, colh0, colh1, colhboth;
342         uint16_t *fbptr;
343
344         for(i=1; i<FBWIDTH / 2; i++) {
345                 fbptr = vox->fb + i;
346                 colh0 = FBHEIGHT - vox->coltop[i << 1];
347                 colh1 = FBHEIGHT - vox->coltop[(i << 1) + 1];
348                 colhboth = colh0 < colh1 ? colh0 : colh1;
349
350                 for(j=0; j<colhboth; j++) {
351                         *fbptr = color | ((uint16_t)color << 8);
352                         fbptr += FBWIDTH / 2;
353                 }
354
355                 if(colh0 > colh1) {
356                         for(j=colhboth; j<colh0; j++) {
357                                 *fbptr |= color;
358                                 fbptr += FBWIDTH / 2;
359                         }
360                 } else {
361                         for(j=colhboth; j<colh1; j++) {
362                                 *fbptr |= (uint16_t)color << 8;
363                                 fbptr += FBWIDTH / 2;
364                         }
365                 }
366         }
367 }
368
369 void vox_sky_grad(struct voxscape *vox, uint8_t chor, uint8_t ctop)
370 {
371         int i, j, colh0, colh1, colhboth, t;
372         int d = FBHEIGHT - vox->horizon;
373         uint8_t grad[FBHEIGHT];
374         uint16_t *fbptr;
375
376         for(i=0; i<d; i++) {
377                 t = (i << 16) / d;
378                 grad[i] = XLERP(ctop, chor, t, 16);
379         }
380         for(i=d; i<FBHEIGHT; i++) {
381                 grad[i] = chor;
382         }
383
384         for(i=1; i<FBWIDTH / 2; i++) {
385                 fbptr = vox->fb + i;
386                 colh0 = FBHEIGHT - vox->coltop[i << 1];
387                 colh1 = FBHEIGHT - vox->coltop[(i << 1) + 1];
388                 colhboth = colh0 < colh1 ? colh0 : colh1;
389
390                 for(j=0; j<colhboth; j++) {
391                         *fbptr = grad[j] | ((uint16_t)grad[j] << 8);
392                         fbptr += FBWIDTH / 2;
393                 }
394
395                 if(colh0 > colh1) {
396                         for(j=colhboth; j<colh0; j++) {
397                                 *fbptr |= grad[j];
398                                 fbptr += FBWIDTH / 2;
399                         }
400                 } else {
401                         for(j=colhboth; j<colh1; j++) {
402                                 *fbptr |= (uint16_t)grad[j] << 8;
403                                 fbptr += FBWIDTH / 2;
404                         }
405                 }
406         }
407 }