added cylinder primitive
[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         float dir[3];
48         float len;
49
50         cam.x = x;
51         cam.y = y;
52         cam.z = z;
53         cam.tx = tx;
54         cam.ty = ty;
55         cam.tz = tz;
56
57         dir[0] = tx - x;
58         dir[1] = ty - y;
59         dir[2] = tz - z;
60         len = sqrt(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]);
61
62         if(1.0f - fabs(ty - y) / len < 1e-6f) {
63                 cam.ux = cam.uy = 0.0f;
64                 cam.uz = -1.0f;
65         } else {
66                 cam.ux = cam.uz = 0.0f;
67                 cam.uy = 1.0f;
68         }
69
70         mat4_lookat(cam.xform, x, y, z, tx, ty, tz, cam.ux, cam.uy, cam.uz);
71 }
72
73 void csg_fov(float fov)
74 {
75         cam.fov = M_PI * fov / 180.0f;
76 }
77
78
79 int csg_load(const char *fname)
80 {
81         struct ts_node *root = 0, *c;
82         csg_object *o;
83
84         if(!(root = ts_load(fname))) {
85                 fprintf(stderr, "failed to open %s\n", fname);
86                 return -1;
87         }
88         if(strcmp(root->name, "csgray_scene") != 0) {
89                 fprintf(stderr, "invalid scene file: %s\n", fname);
90                 goto err;
91         }
92
93         c = root->child_list;
94         while(c) {
95                 if(strcmp(c->name, "viewer") == 0) {
96                         static float def_pos[] = {0, 0, 5};
97                         static float def_targ[] = {0, 0, 0};
98
99                         float *p = ts_get_attr_vec(c, "position", def_pos);
100                         float *t = ts_get_attr_vec(c, "target", def_targ);
101
102                         csg_view(p[0], p[1], p[2], t[0], t[1], t[2]);
103                         csg_fov(ts_get_attr_num(c, "fov", 50.0f));
104
105                 } else if((o = load_object(c))) {
106                         csg_add_object(o);
107                 }
108                 c = c->next;
109         }
110
111         ts_free_tree(root);
112         return 0;
113
114 err:
115         if(root) {
116                 ts_free_tree(root);
117         }
118         return -1;
119 }
120
121 int csg_save(const char *fname)
122 {
123         return -1;      /* TODO */
124 }
125
126 void csg_add_object(csg_object *o)
127 {
128         o->ob.next = oblist;
129         oblist = o;
130
131         if(o->ob.type == OB_NULL && (o->ob.emr > 0.0f || o->ob.emg > 0.0f || o->ob.emb > 0.0f)) {
132                 o->ob.plt_next = plights;
133                 plights = o;
134         }
135 }
136
137 int csg_remove_object(csg_object *o)
138 {
139         csg_object dummy, *n;
140
141         dummy.ob.next = oblist;
142         n = &dummy;
143
144         while(n->ob.next) {
145                 if(n->ob.next == o) {
146                         n->ob.next = o->ob.next;
147                         return 1;
148                 }
149                 n = n->ob.next;
150         }
151         return 0;
152 }
153
154 void csg_free_object(csg_object *o)
155 {
156         if(o) {
157                 if(o->ob.destroy) {
158                         o->ob.destroy(o);
159                 }
160                 free(o);
161         }
162 }
163
164 static union csg_object *alloc_object(int type)
165 {
166         csg_object *o;
167
168         if(!(o = calloc(sizeof *o, 1))) {
169                 return 0;
170         }
171         o->ob.type = type;
172         mat4_identity(o->ob.xform);
173         mat4_identity(o->ob.inv_xform);
174
175         csg_emission(o, 0, 0, 0);
176         csg_color(o, 1, 1, 1);
177         csg_roughness(o, 1);
178         csg_opacity(o, 1);
179
180         return o;
181 }
182
183 csg_object *csg_null(float x, float y, float z)
184 {
185         csg_object *o;
186
187         if(!(o = alloc_object(OB_NULL))) {
188                 return 0;
189         }
190
191         mat4_translation(o->ob.xform, x, y, z);
192         mat4_translation(o->ob.inv_xform, -x, -y, -z);
193         return o;
194 }
195
196 csg_object *csg_sphere(float x, float y, float z, float r)
197 {
198         csg_object *o;
199
200         if(!(o = alloc_object(OB_SPHERE))) {
201                 return 0;
202         }
203
204         o->sph.rad = r;
205         mat4_translation(o->ob.xform, x, y, z);
206         mat4_translation(o->ob.inv_xform, -x, -y, -z);
207         return o;
208 }
209
210 csg_object *csg_cylinder(float x0, float y0, float z0, float x1, float y1, float z1, float r)
211 {
212         csg_object *o;
213         float x, y, z, dx, dy, dz;
214         int major;
215
216         if(!(o = alloc_object(OB_CYLINDER))) {
217                 return 0;
218         }
219         o->cyl.rad = r;
220
221         dx = x1 - x0;
222         dy = y1 - y0;
223         dz = z1 - z0;
224
225         o->cyl.height = sqrt(dx * dx + dy * dy + dz * dz);
226
227         if(fabs(dx) > fabs(dy) && fabs(dx) > fabs(dz)) {
228                 major = 0;
229         } else if(fabs(dy) > fabs(dz)) {
230                 major = 1;
231         } else {
232                 major = 2;
233         }
234
235         x = (x0 + x1) / 2.0f;
236         y = (y0 + y1) / 2.0f;
237         z = (z0 + z1) / 2.0f;
238
239         mat4_lookat(o->ob.xform, x, y, z, dx, dz, -dy, 0, major == 2 ? 0 : 1, major == 2 ? 1 : 0);
240         mat4_copy(o->ob.inv_xform, o->ob.xform);
241         mat4_inverse(o->ob.inv_xform);
242         return o;
243 }
244
245 csg_object *csg_plane(float x, float y, float z, float nx, float ny, float nz)
246 {
247         csg_object *o;
248         float len;
249
250         if(!(o = alloc_object(OB_PLANE))) {
251                 return 0;
252         }
253
254         len = sqrt(nx * nx + ny * ny + nz * nz);
255         if(len != 0.0f) {
256                 float s = 1.0f / len;
257                 nx *= s;
258                 ny *= s;
259                 nz *= s;
260         }
261
262         o->plane.nx = nx;
263         o->plane.ny = ny;
264         o->plane.nz = nz;
265         o->plane.d = 0.0f;
266
267         mat4_translation(o->ob.xform, x, y, z);
268         mat4_translation(o->ob.inv_xform, -x, -y, -z);
269         return o;
270 }
271
272 csg_object *csg_box(float x, float y, float z, float xsz, float ysz, float zsz)
273 {
274         csg_object *o;
275
276         if(!(o = alloc_object(OB_BOX))) {
277                 return 0;
278         }
279
280         o->box.xsz = xsz;
281         o->box.ysz = ysz;
282         o->box.zsz = zsz;
283
284         mat4_translation(o->ob.xform, x, y, z);
285         mat4_translation(o->ob.inv_xform, -x, -y, -z);
286         return o;
287 }
288
289 csg_object *csg_union(csg_object *a, csg_object *b)
290 {
291         csg_object *o;
292
293         if(!(o = alloc_object(OB_UNION))) {
294                 return 0;
295         }
296         o->un.a = a;
297         o->un.b = b;
298         return o;
299 }
300
301 csg_object *csg_intersection(csg_object *a, csg_object *b)
302 {
303         csg_object *o;
304
305         if(!(o = alloc_object(OB_INTERSECTION))) {
306                 return 0;
307         }
308         o->isect.a = a;
309         o->isect.b = b;
310         return o;
311 }
312
313 csg_object *csg_subtraction(csg_object *a, csg_object *b)
314 {
315         csg_object *o;
316
317         if(!(o = alloc_object(OB_SUBTRACTION))) {
318                 return 0;
319         }
320         o->sub.a = a;
321         o->sub.b = b;
322         return o;
323 }
324
325 void csg_ambient(float r, float g, float b)
326 {
327         ambient[0] = r;
328         ambient[1] = g;
329         ambient[2] = b;
330 }
331
332 void csg_emission(csg_object *o, float r, float g, float b)
333 {
334         o->ob.emr = r;
335         o->ob.emg = g;
336         o->ob.emb = b;
337 }
338
339 void csg_color(csg_object *o, float r, float g, float b)
340 {
341         o->ob.r = r;
342         o->ob.g = g;
343         o->ob.b = b;
344 }
345
346 void csg_roughness(csg_object *o, float r)
347 {
348         o->ob.roughness = r;
349 }
350
351 void csg_opacity(csg_object *o, float p)
352 {
353         o->ob.opacity = p;
354 }
355
356 void csg_metallic(csg_object *o, int m)
357 {
358         o->ob.metallic = m;
359 }
360
361 void csg_reset_xform(csg_object *o)
362 {
363         mat4_identity(o->ob.xform);
364         mat4_identity(o->ob.inv_xform);
365 }
366
367 void csg_translate(csg_object *o, float x, float y, float z)
368 {
369         mat4_translate(o->ob.xform, x, y, z);
370         mat4_pre_translate(o->ob.inv_xform, -x, -y, -z);
371 }
372
373 void csg_rotate(csg_object *o, float angle, float x, float y, float z)
374 {
375         mat4_rotate(o->ob.xform, angle, x, y, z);
376         mat4_pre_rotate(o->ob.inv_xform, -angle, x, y, z);
377 }
378
379 void csg_scale(csg_object *o, float x, float y, float z)
380 {
381         mat4_scale(o->ob.xform, x, y, z);
382         mat4_pre_scale(o->ob.inv_xform, 1.0f / x, 1.0f / y, 1.0f / z);
383 }
384
385 void csg_lookat(csg_object *o, float x, float y, float z, float tx, float ty, float tz, float ux, float uy, float uz)
386 {
387         mat4_lookat(o->ob.xform, x, y, z, tx, ty, tz, ux, uy, uz);
388         mat4_inv_lookat(o->ob.inv_xform, x, y, z, tx, ty, tz, ux, uy, uz);
389 }
390
391 void csg_render_pixel(int x, int y, int width, int height, float aspect, float *color)
392 {
393         struct ray ray;
394
395         csg_dbg_pixel = (x == 400 && y == 186);
396
397         calc_primary_ray(&ray, x, y, width, height, aspect);
398         ray_trace(&ray, color);
399 }
400
401 void csg_render_image(float *pixels, int width, int height)
402 {
403         int i, j;
404         float aspect = (float)width / (float)height;
405
406         for(i=0; i<height; i++) {
407                 for(j=0; j<width; j++) {
408                         csg_render_pixel(j, i, width, height, aspect, pixels);
409                         pixels += 3;
410                 }
411         }
412 }
413
414 static void calc_primary_ray(struct ray *ray, int x, int y, int w, int h, float aspect)
415 {
416         ray->dx = aspect * ((float)x / (float)w * 2.0f - 1.0f);
417         ray->dy = 1.0f - (float)y / (float)h * 2.0f;
418         ray->dz = -1.0f / tan(cam.fov * 0.5f);
419
420         ray->x = 0;
421         ray->y = 0;
422         ray->z = 0;
423
424         xform_ray(ray, cam.xform);
425 }
426
427 static int ray_trace(struct ray *ray, float *col)
428 {
429         struct hit hit;
430
431         if(!find_intersection(ray, &hit)) {
432                 background(col, ray);
433                 return 0;
434         }
435
436         shade(col, ray, &hit);
437         return 1;
438 }
439
440 #define NULLXPOS(o)     ((o)->ob.xform[12])
441 #define NULLYPOS(o)     ((o)->ob.xform[13])
442 #define NULLZPOS(o)     ((o)->ob.xform[14])
443
444 static void shade(float *col, struct ray *ray, struct hit *hit)
445 {
446         float ndotl, ndoth, len, falloff, spec;
447         csg_object *o, *lt = plights;
448         float dcol[3], scol[3] = {0};
449         float ldir[3], lcol[3], hdir[3];
450         struct ray sray;
451         struct hit tmphit;
452
453         o = hit->o;
454         dcol[0] = ambient[0];
455         dcol[1] = ambient[1];
456         dcol[2] = ambient[2];
457
458         while(lt) {
459                 ldir[0] = NULLXPOS(lt) - hit->x;
460                 ldir[1] = NULLYPOS(lt) - hit->y;
461                 ldir[2] = NULLZPOS(lt) - hit->z;
462
463                 sray.x = hit->x;
464                 sray.y = hit->y;
465                 sray.z = hit->z;
466                 sray.dx = ldir[0];
467                 sray.dy = ldir[1];
468                 sray.dz = ldir[2];
469
470                 if(!find_intersection(&sray, &tmphit) || tmphit.t < 0.000001 || tmphit.t > 1.0f) {
471                         if((len = sqrt(ldir[0] * ldir[0] + ldir[1] * ldir[1] + ldir[2] * ldir[2])) != 0.0f) {
472                                 float s = 1.0f / len;
473                                 ldir[0] *= s;
474                                 ldir[1] *= s;
475                                 ldir[2] *= s;
476                         }
477                         falloff = 1.0f / (len * len);
478
479                         lcol[0] = lt->ob.emr * falloff;
480                         lcol[1] = lt->ob.emg * falloff;
481                         lcol[2] = lt->ob.emb * falloff;
482
483                         if((ndotl = hit->nx * ldir[0] + hit->ny * ldir[1] + hit->nz * ldir[2]) < 0.0f) {
484                                 ndotl = 0.0f;
485                         }
486
487                         dcol[0] += o->ob.r * lcol[0] * ndotl;
488                         dcol[1] += o->ob.g * lcol[1] * ndotl;
489                         dcol[2] += o->ob.b * lcol[2] * ndotl;
490
491                         if(o->ob.roughness < 1.0f) {
492                                 float gloss = 1.0f - o->ob.roughness;
493
494                                 hdir[0] = ldir[0] - ray->dx;
495                                 hdir[1] = ldir[1] - ray->dy;
496                                 hdir[2] = ldir[2] - ray->dz;
497                                 if((len = sqrt(hdir[0] * hdir[0] + hdir[1] * hdir[1] + hdir[2] * hdir[2])) != 0.0f) {
498                                         float s = 1.0f / len;
499                                         hdir[0] *= s;
500                                         hdir[1] *= s;
501                                         hdir[2] *= s;
502                                 }
503
504                                 if((ndoth = hit->nx * hdir[0] + hit->ny * hdir[1] + hit->nz * hdir[2]) < 0.0f) {
505                                         ndoth = 0.0f;
506                                 }
507                                 spec = gloss * pow(ndoth, 100.0f * gloss);
508
509                                 if(o->ob.metallic) {
510                                         lcol[0] *= o->ob.r;
511                                         lcol[1] *= o->ob.g;
512                                         lcol[2] *= o->ob.b;
513                                 }
514                                 scol[0] += lcol[0] * spec;
515                                 scol[1] += lcol[1] * spec;
516                                 scol[2] += lcol[2] * spec;
517                         }
518                 }
519
520                 lt = lt->ob.plt_next;
521         }
522
523         col[0] = dcol[0] + scol[0];
524         col[1] = dcol[1] + scol[1];
525         col[2] = dcol[2] + scol[2];
526 }
527
528 static void background(float *col, struct ray *ray)
529 {
530         col[0] = col[1] = col[2] = 0.0f;
531 }
532
533 static int find_intersection(struct ray *ray, struct hit *best)
534 {
535         int idx = 0;
536         csg_object *o;
537         struct hinterv *hit, *it;
538
539         best->t = FLT_MAX;
540         best->o = 0;
541
542         o = oblist;
543         while(o) {
544                 if((hit = ray_intersect(ray, o))) {
545                         it = hit;
546                         while(it) {
547                                 if(it->end[0].t > 1e-6) {
548                                         idx = 0;
549                                         break;
550                                 }
551                                 if(it->end[1].t > 1e-6) {
552                                         idx = 1;
553                                         break;
554                                 }
555                                 it = it->next;
556                         }
557
558                         if(it && it->end[idx].t < best->t) {
559                                 *best = it->end[idx];
560                         }
561                 }
562                 free_hit_list(hit);
563                 o = o->ob.next;
564         }
565
566         return best->o != 0;
567 }
568
569 static csg_object *load_object(struct ts_node *node)
570 {
571         float *avec;
572         struct ts_node *c;
573         csg_object *sub, *o = 0, *olist = 0, *otail = 0;
574         int num_subobj = 0, is_csgop = 0;
575
576         if(strcmp(node->name, "null") == 0) {
577                 if(!(o = csg_null(0, 0, 0))) {
578                         goto err;
579                 }
580
581         } else if(strcmp(node->name, "sphere") == 0) {
582                 float rad = ts_get_attr_num(node, "radius", 1.0f);
583                 if(!(o = csg_sphere(0, 0, 0, rad))) {
584                         goto err;
585                 }
586
587         } else if(strcmp(node->name, "cylinder") == 0) {
588                 float rad = ts_get_attr_num(node, "radius", 1.0f);
589                 float height = ts_get_attr_num(node, "height", 1.0f);
590                 if(!(o = csg_cylinder(0, -height/2.0f, 0, 0, height/2.0f, 0, rad))) {
591                         goto err;
592                 }
593
594         } else if(strcmp(node->name, "plane") == 0) {
595                 static float def_norm[] = {0, 1, 0};
596                 float *norm = ts_get_attr_vec(node, "normal", def_norm);
597                 if(!(o = csg_plane(0, 0, 0, norm[0], norm[1], norm[2]))) {
598                         goto err;
599                 }
600
601         } else if(strcmp(node->name, "box") == 0) {
602                 static float def_sz[] = {1, 1, 1};
603                 float *sz = ts_get_attr_vec(node, "size", def_sz);
604                 if(!(o = csg_box(0, 0, 0, sz[0], sz[1], sz[2]))) {
605                         goto err;
606                 }
607
608         } else if(strcmp(node->name, "union") == 0) {
609                 if(!(o = csg_union(0, 0))) {
610                         goto err;
611                 }
612                 is_csgop = 1;
613
614         } else if(strcmp(node->name, "intersect") == 0) {
615                 if(!(o = csg_intersection(0, 0))) {
616                         goto err;
617                 }
618                 is_csgop = 1;
619
620         } else if(strcmp(node->name, "subtract") == 0) {
621                 if(!(o = csg_subtraction(0, 0))) {
622                         goto err;
623                 }
624                 is_csgop = 1;
625
626         } else {
627                 goto err;
628         }
629
630         if(is_csgop) {
631                 c = node->child_list;
632                 while(c) {
633                         if((sub = load_object(c))) {
634                                 if(olist) {
635                                         otail->ob.next = sub;
636                                         otail = sub;
637                                 } else {
638                                         olist = otail = sub;
639                                 }
640                                 ++num_subobj;
641                         }
642                         c = c->next;
643                 }
644
645                 if(num_subobj != 2) {
646                         goto err;
647                 }
648                 o->un.a = olist;
649                 o->un.b = olist->ob.next;
650                 olist->ob.next = 0;
651         }
652
653         if((avec = ts_get_attr_vec(node, "position", 0))) {
654                 csg_translate(o, avec[0], avec[1], avec[2]);
655         }
656         if((avec = ts_get_attr_vec(node, "rotaxis", 0))) {
657                 csg_rotate(o, ts_get_attr_num(node, "rotangle", 0.0f), avec[0], avec[1], avec[2]);
658         }
659         if((avec = ts_get_attr_vec(node, "scaling", 0))) {
660                 csg_scale(o, avec[0], avec[1], avec[2]);
661         }
662         if((avec = ts_get_attr_vec(node, "target", 0))) {
663                 /* don't move this before position */
664                 float def_up[] = {0, 1, 0};
665                 float *up = ts_get_attr_vec(node, "up", def_up);
666                 float x = o->ob.xform[12];
667                 float y = o->ob.xform[13];
668                 float z = o->ob.xform[14];
669                 csg_lookat(o, x, y, z, avec[0], avec[1], avec[2], up[0], up[1], up[2]);
670         }
671
672         if((avec = ts_get_attr_vec(node, "color", 0))) {
673                 csg_color(o, avec[0], avec[1], avec[2]);
674         }
675         if((avec = ts_get_attr_vec(node, "emission", 0))) {
676                 csg_emission(o, avec[0], avec[1], avec[2]);
677         }
678
679         csg_roughness(o, ts_get_attr_num(node, "roughness", o->ob.roughness));
680         csg_opacity(o, ts_get_attr_num(node, "opacity", o->ob.opacity));
681         csg_metallic(o, ts_get_attr_int(node, "metallic", o->ob.metallic));
682
683         return o;
684
685 err:
686         csg_free_object(o);
687         while(olist) {
688                 o = olist;
689                 olist = olist->ob.next;
690                 csg_free_object(o);
691         }
692         return 0;
693 }