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