7fc2b46049ce33331d5274148abdd056a5b75eca
[csgray] / src / csgray.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <math.h>
5 #include <float.h>
6 #include <treestore.h>
7 #include "csgimpl.h"
8 #include "matrix.h"
9 #include "geom.h"
10
11 static void calc_primary_ray(struct ray *ray, int x, int y, int w, int h, float aspect);
12 static int ray_trace(struct ray *ray, float *col);
13 static void shade(float *col, struct ray *ray, struct hit *hit);
14 static void background(float *col, struct ray *ray);
15 static int find_intersection(struct ray *ray, struct hit *best);
16 static csg_object *load_object(struct ts_node *node);
17
18 static float ambient[3];
19 static struct camera cam;
20 static csg_object *oblist;
21 static csg_object *plights;
22
23 int csg_init(void)
24 {
25         oblist = 0;
26         plights = 0;
27
28         csg_ambient(0, 0, 0);
29         csg_view(0, 0, 5, 0, 0, 0);
30         csg_fov(50);
31
32         return 0;
33 }
34
35 void csg_destroy(void)
36 {
37         while(oblist) {
38                 csg_object *o = oblist;
39                 oblist = oblist->ob.next;
40                 csg_free_object(o);
41         }
42         oblist = 0;
43 }
44
45 void csg_view(float x, float y, float z, float tx, float ty, float tz)
46 {
47         cam.x = x;
48         cam.y = y;
49         cam.z = z;
50         cam.tx = tx;
51         cam.ty = ty;
52         cam.tz = tz;
53 }
54
55 void csg_fov(float fov)
56 {
57         cam.fov = M_PI * fov / 180.0f;
58 }
59
60
61 int csg_load(const char *fname)
62 {
63         struct ts_node *root = 0, *c;
64
65         if(!(root = ts_load(fname))) {
66                 fprintf(stderr, "failed to open %s\n", fname);
67                 return -1;
68         }
69         if(strcmp(root->name, "scene") != 0) {
70                 fprintf(stderr, "invalid scene file: %s\n", fname);
71                 goto err;
72         }
73
74         c = root->child_list;
75         while(c) {
76                 csg_object *o = load_object(c);
77                 if(!o) goto err;
78                 csg_add_object(o);
79                 c = c->next;
80         }
81
82         ts_free_tree(root);
83         return 0;
84
85 err:
86         if(root) {
87                 ts_free_tree(root);
88         }
89         return -1;
90 }
91
92 int csg_save(const char *fname)
93 {
94         return -1;      /* TODO */
95 }
96
97 void csg_add_object(csg_object *o)
98 {
99         o->ob.next = oblist;
100         oblist = o;
101
102         if(o->ob.type == OB_NULL && (o->ob.emr > 0.0f || o->ob.emg > 0.0f || o->ob.emb > 0.0f)) {
103                 o->ob.plt_next = plights;
104                 plights = o;
105         }
106 }
107
108 int csg_remove_object(csg_object *o)
109 {
110         csg_object dummy, *n;
111
112         dummy.ob.next = oblist;
113         n = &dummy;
114
115         while(n->ob.next) {
116                 if(n->ob.next == o) {
117                         n->ob.next = o->ob.next;
118                         return 1;
119                 }
120                 n = n->ob.next;
121         }
122         return 0;
123 }
124
125 void csg_free_object(csg_object *o)
126 {
127         if(o->ob.destroy) {
128                 o->ob.destroy(o);
129         }
130         free(o);
131 }
132
133 static union csg_object *alloc_object(int type)
134 {
135         csg_object *o;
136
137         if(!(o = calloc(sizeof *o, 1))) {
138                 return 0;
139         }
140         o->ob.type = type;
141         mat4_identity(o->ob.xform);
142         mat4_identity(o->ob.inv_xform);
143
144         csg_emission(o, 0, 0, 0);
145         csg_color(o, 1, 1, 1);
146         csg_roughness(o, 1);
147         csg_opacity(o, 1);
148
149         return o;
150 }
151
152 csg_object *csg_null(float x, float y, float z)
153 {
154         csg_object *o;
155
156         if(!(o = alloc_object(OB_NULL))) {
157                 return 0;
158         }
159
160         mat4_translation(o->ob.xform, x, y, z);
161         return o;
162 }
163
164 csg_object *csg_sphere(float x, float y, float z, float r)
165 {
166         csg_object *o;
167
168         if(!(o = alloc_object(OB_SPHERE))) {
169                 return 0;
170         }
171
172         o->sph.rad = r;
173         mat4_translation(o->ob.xform, x, y, z);
174         mat4_copy(o->ob.inv_xform, o->ob.xform);
175         mat4_inverse(o->ob.inv_xform);
176         return o;
177 }
178
179 csg_object *csg_cylinder(float x0, float y0, float z0, float x1, float y1, float z1, float r)
180 {
181         csg_object *o;
182         float dx, dy, dz;
183         int major;
184
185         if(!(o = alloc_object(OB_CYLINDER))) {
186                 return 0;
187         }
188
189         dx = x1 - x0;
190         dy = y1 - y0;
191         dz = z1 - z0;
192
193         if(fabs(dx) > fabs(dy) && fabs(dx) > fabs(dz)) {
194                 major = 0;
195         } else if(fabs(dy) > fabs(dz)) {
196                 major = 1;
197         } else {
198                 major = 2;
199         }
200
201         o->cyl.rad = r;
202         mat4_lookat(o->ob.xform, x0, y0, z0, x1, y1, z1, 0, major == 2 ? 1 : 0, major == 2 ? 0 : 1);
203         mat4_copy(o->ob.inv_xform, o->ob.xform);
204         mat4_inverse(o->ob.inv_xform);
205         return o;
206 }
207
208 csg_object *csg_plane(float x, float y, float z, float nx, float ny, float nz)
209 {
210         csg_object *o;
211         float len;
212
213         if(!(o = alloc_object(OB_PLANE))) {
214                 return 0;
215         }
216
217         len = sqrt(nx * nx + ny * ny + nz * nz);
218         if(len != 0.0f) {
219                 float s = 1.0f / len;
220                 nx *= s;
221                 ny *= s;
222                 nz *= s;
223         }
224
225         o->plane.nx = nx;
226         o->plane.ny = ny;
227         o->plane.nz = nz;
228         o->plane.d = x * nx + y * ny + z * nz;
229         return o;
230 }
231
232 csg_object *csg_box(float x, float y, float z, float xsz, float ysz, float zsz)
233 {
234         return 0;
235 }
236
237 csg_object *csg_union(csg_object *a, csg_object *b)
238 {
239         csg_object *o;
240
241         if(!(o = alloc_object(OB_UNION))) {
242                 return 0;
243         }
244         o->un.a = a;
245         o->un.b = b;
246         return o;
247 }
248
249 csg_object *csg_intersection(csg_object *a, csg_object *b)
250 {
251         csg_object *o;
252
253         if(!(o = alloc_object(OB_INTERSECTION))) {
254                 return 0;
255         }
256         o->isect.a = a;
257         o->isect.b = b;
258         return o;
259 }
260
261 csg_object *csg_subtraction(csg_object *a, csg_object *b)
262 {
263         csg_object *o;
264
265         if(!(o = alloc_object(OB_SUBTRACTION))) {
266                 return 0;
267         }
268         o->sub.a = a;
269         o->sub.b = b;
270         return o;
271 }
272
273 void csg_ambient(float r, float g, float b)
274 {
275         ambient[0] = r;
276         ambient[1] = g;
277         ambient[2] = b;
278 }
279
280 void csg_emission(csg_object *o, float r, float g, float b)
281 {
282         o->ob.emr = r;
283         o->ob.emg = g;
284         o->ob.emb = b;
285 }
286
287 void csg_color(csg_object *o, float r, float g, float b)
288 {
289         o->ob.r = r;
290         o->ob.g = g;
291         o->ob.b = b;
292 }
293
294 void csg_roughness(csg_object *o, float r)
295 {
296         o->ob.roughness = r;
297 }
298
299 void csg_opacity(csg_object *o, float p)
300 {
301         o->ob.opacity = p;
302 }
303
304 void csg_metallic(csg_object *o, int m)
305 {
306         o->ob.metallic = m;
307 }
308
309
310 void csg_render_pixel(int x, int y, int width, int height, float aspect, float *color)
311 {
312         struct ray ray;
313
314         calc_primary_ray(&ray, x, y, width, height, aspect);
315         ray_trace(&ray, color);
316 }
317
318 void csg_render_image(float *pixels, int width, int height)
319 {
320         int i, j;
321         float aspect = (float)width / (float)height;
322
323         for(i=0; i<height; i++) {
324                 for(j=0; j<width; j++) {
325                         csg_render_pixel(j, i, width, height, aspect, pixels);
326                         pixels += 3;
327                 }
328         }
329 }
330
331 static void calc_primary_ray(struct ray *ray, int x, int y, int w, int h, float aspect)
332 {
333         /* TODO */
334         ray->dx = aspect * ((float)x / (float)w * 2.0f - 1.0f);
335         ray->dy = 1.0f - (float)y / (float)h * 2.0f;
336         ray->dz = -1.0f / tan(cam.fov * 0.5f);
337
338         ray->x = cam.x;
339         ray->y = cam.y;
340         ray->z = cam.z;
341 }
342
343 static int ray_trace(struct ray *ray, float *col)
344 {
345         struct hit hit;
346
347         if(!find_intersection(ray, &hit)) {
348                 background(col, ray);
349                 return 0;
350         }
351
352         shade(col, ray, &hit);
353         return 1;
354 }
355
356 #define NULLXPOS(o)     ((o)->ob.xform[12])
357 #define NULLYPOS(o)     ((o)->ob.xform[13])
358 #define NULLZPOS(o)     ((o)->ob.xform[14])
359
360 static void shade(float *col, struct ray *ray, struct hit *hit)
361 {
362         float ndotl, ndoth, len, falloff, spec;
363         csg_object *o, *lt = plights;
364         float dcol[3], scol[3] = {0};
365         float ldir[3], lcol[3], hdir[3];
366         struct ray sray;
367         struct hit tmphit;
368
369         o = hit->o;
370         dcol[0] = ambient[0];
371         dcol[1] = ambient[1];
372         dcol[2] = ambient[2];
373
374         while(lt) {
375                 ldir[0] = NULLXPOS(lt) - hit->x;
376                 ldir[1] = NULLYPOS(lt) - hit->y;
377                 ldir[2] = NULLZPOS(lt) - hit->z;
378
379                 sray.x = hit->x;
380                 sray.y = hit->y;
381                 sray.z = hit->z;
382                 sray.dx = ldir[0];
383                 sray.dy = ldir[1];
384                 sray.dz = ldir[2];
385
386                 if(!find_intersection(&sray, &tmphit) || tmphit.t > 1.0f) {
387                         if((len = sqrt(ldir[0] * ldir[0] + ldir[1] * ldir[1] + ldir[2] * ldir[2])) != 0.0f) {
388                                 float s = 1.0f / len;
389                                 ldir[0] *= s;
390                                 ldir[1] *= s;
391                                 ldir[2] *= s;
392                         }
393                         falloff = 1.0f / (len * len);
394
395                         lcol[0] = lt->ob.emr * falloff;
396                         lcol[1] = lt->ob.emg * falloff;
397                         lcol[2] = lt->ob.emb * falloff;
398
399                         if((ndotl = hit->nx * ldir[0] + hit->ny * ldir[1] + hit->nz * ldir[2]) < 0.0f) {
400                                 ndotl = 0.0f;
401                         }
402
403                         dcol[0] += o->ob.r * lcol[0] * ndotl;
404                         dcol[1] += o->ob.g * lcol[1] * ndotl;
405                         dcol[2] += o->ob.b * lcol[2] * ndotl;
406
407                         if(o->ob.roughness < 1.0f) {
408                                 float gloss = 1.0f - o->ob.roughness;
409
410                                 hdir[0] = ldir[0] - ray->dx;
411                                 hdir[1] = ldir[1] - ray->dy;
412                                 hdir[2] = ldir[2] - ray->dz;
413                                 if((len = sqrt(hdir[0] * hdir[0] + hdir[1] * hdir[1] + hdir[2] * hdir[2])) != 0.0f) {
414                                         float s = 1.0f / len;
415                                         hdir[0] *= s;
416                                         hdir[1] *= s;
417                                         hdir[2] *= s;
418                                 }
419
420                                 if((ndoth = hit->nx * hdir[0] + hit->ny * hdir[1] + hit->nz * hdir[2]) < 0.0f) {
421                                         ndoth = 0.0f;
422                                 }
423                                 spec = gloss * pow(ndoth, 100.0f * gloss);
424
425                                 if(o->ob.metallic) {
426                                         lcol[0] *= o->ob.r;
427                                         lcol[1] *= o->ob.g;
428                                         lcol[2] *= o->ob.b;
429                                 }
430                                 scol[0] += lcol[0] * spec;
431                                 scol[1] += lcol[1] * spec;
432                                 scol[2] += lcol[2] * spec;
433                         }
434                 }
435
436                 lt = lt->ob.plt_next;
437         }
438
439         col[0] = dcol[0] + scol[0];
440         col[1] = dcol[1] + scol[1];
441         col[2] = dcol[2] + scol[2];
442 }
443
444 static void background(float *col, struct ray *ray)
445 {
446         col[0] = col[1] = col[2] = 0.0f;
447 }
448
449 static int find_intersection(struct ray *ray, struct hit *best)
450 {
451         int idx = 0;
452         csg_object *o;
453         struct hinterv *hit, *it;
454
455         best->t = FLT_MAX;
456         best->o = 0;
457
458         o = oblist;
459         while(o) {
460                 if((hit = ray_intersect(ray, o))) {
461                         it = hit;
462                         while(it) {
463                                 if(it->end[0].t > 1e-6) {
464                                         idx = 0;
465                                         break;
466                                 }
467                                 if(it->end[1].t > 1e-6) {
468                                         idx = 1;
469                                         break;
470                                 }
471                                 it = it->next;
472                         }
473
474                         if(it && it->end[idx].t < best->t) {
475                                 *best = it->end[idx];
476                         }
477                 }
478                 free_hit_list(hit);
479                 o = o->ob.next;
480         }
481
482         return best->o != 0;
483 }
484
485 static csg_object *load_object(struct ts_node *node)
486 {
487         float *apos, *arot, *ascale, *acolor;
488         float aroughness;
489         struct ts_node *c;
490         csg_object *o, *olist = 0, *otail = 0;
491         int num_subobj = 0;
492
493         c = node->child_list;
494         while(c) {
495                 if((o = load_object(c))) {
496                         if(olist) {
497                                 otail->ob.next = o;
498                                 otail = o;
499                         } else {
500                                 olist = otail = o;
501                         }
502                         ++num_subobj;
503                 }
504                 c = c->next;
505         }
506
507         apos = ts_get_attr_vec(node, "position", 0);
508         arot = ts_get_attr_vec(node, "rotation", 0);
509         ascale = ts_get_attr_vec(node, "scaling", 0);
510         acolor = ts_get_attr_vec(node, "color", 0);
511         aroughness = ts_get_attr_num(node, "roughness", 0);
512
513         if(strcmp(node->name, "null") == 0) {
514                 if(num_subobj) {
515                         fprintf(stderr, "null can't have sub-objects\n");
516                         goto err;
517                 }
518                 if(!(o = alloc_object(OB_NULL))) {
519                         goto err;
520                 }
521                 return o;
522
523         } else if(strcmp(node->name, "sphere") == 0) {
524                 if(num_subobj) {
525                         fprintf(stderr, "sphere can't have sub-objects\n");
526                         goto err;
527                 }
528                 if(!(o = alloc_object(OB_SPHERE))) {
529                         goto err;
530                 }
531         }
532
533 err:
534         while(olist) {
535                 o = olist;
536                 olist = olist->ob.next;
537                 csg_free_object(o);
538         }
539         return 0;
540 }