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