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