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