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