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