/** # Sketching functions for Bwatch Sketching utilities consist of a few functions and definitions. */ #include "bwatch-iterators.h" /** ## Sketch vof facets `sketch_vof()` mimics bview's `draw_vof()` a little */ struct _sketch_vof { scalar f; bool precomp; //precompute normals material mat; }; struct _sketch_vof svlist[N_SKETCH]; // Arguments to sketch vof trace double get_pixel_sketch_vof (struct _sketch_vof inp, ray r, double DG, unsigned char * px) { double distance = HUGE; coord n, a; if (inp.precomp) { foreach_precomp_ray_facet_intersection(r, inp.f, DG) { distance = _distg; n = _n; a = _a[0]; } } else { foreach_ray_facet_intersection(r, inp.f) { distance = _distg; n = _n; a = _a[0]; } } if (distance < HUGE && distance < DG) if (get_color_material (r, a, n, inp.mat, px)) return distance; return HUGE; } // User function: bool sketch_vof (struct _sketch_vof inp) { sketch_list[functot] = &get_pixel_sketch_vof; if (!inp.mat.dull && !inp.mat.ind && !any_col (inp.mat.col) && !inp.mat.s.i && !inp.mat.v.x.i && !(inp.mat.R >= 1.)) inp.mat.col[0] = inp.mat.col[1] = inp.mat.col[2] = 250; if (!inp.mat.map) inp.mat.map = jet; if (!inp.mat.min && !inp.mat.max) { inp.mat.min = -1; inp.mat.max = 1; } if (!inp.mat.SPexp && !inp.mat.SPR) { inp.mat.SPexp = 40; inp.mat.SPR = 0.1; } svlist[functot] = inp; functot++; shading = list_add (shading, inp.f); return true; } /** ## A Slice `quadriangles` may mimic bview's `squares()` a bit. */ struct _quadriangles { scalar s; double alpha; coord n; material mat; }; struct _quadriangles qlist[N_SKETCH]; // Arguments to quadriangles trace double get_pixel_quadriangles (struct _quadriangles inp, ray r, double DG, unsigned char * px) { coord a; double d = ray_plane_intersect (r, inp.n, inp.alpha, &a); if (d <= 0 || d >= DG) return HUGE; Point point = locate (a.x, a.y, a.z); if (point.level < 0) // Only slices in the domain return HUGE; normalize (&inp.n); if (get_color_material (r, a, inp.n, inp.mat, px)) return d; return HUGE; } // User interface void quadriangles (struct _quadriangles inp) { sketch_list[functot] = &get_pixel_quadriangles; if (!any_comp (inp.n)) inp.n.z = 1; if (!inp.mat.dull && !inp.mat.ind && !any_col (inp.mat.col) && !inp.mat.s.i && !inp.mat.v.y.i && !(inp.mat.R >= 1)) { if (inp.s.i) inp.mat.s = inp.s; else inp.mat.col[0] = 250, inp.mat.col[1] = 150, inp.mat.col[2] = 50; } if (!inp.mat.map) inp.mat.map = jet; if (!inp.mat.min && !inp.mat.max) { inp.mat.min = -L0; inp.mat.max = L0; } qlist[functot] = inp; functot++; } /** ## The grid `lattice()` may mimic bview's `cells()` a bit. */ struct _lattice { double alpha; coord n; double width; material mat; bool HQ; // High quality, some background object required }; struct _lattice llist[N_SKETCH]; // Arguments to lattice double get_pixel_lattice (struct _lattice inp, ray r, double DG, unsigned char * px) { coord a; double d = ray_plane_intersect (r, inp.n, inp.alpha, &a); if (d < 0 || d > 1e4*L0 || d > DG) return HUGE; Point point = locate (a.x, a.y, a.z); if (point.level < 0) { return HUGE; } coord cc = {x, y, z}; //grid-alligned slices only foreach_dimension() { if (inp.n.z) { int sx = sign (a.x - cc.x); int sy = sign (a.y - cc.y); double dx = cc.x + sx*Delta/2 - a.x; double dy = cc.y + sy*Delta/2 - a.y; double dist = min (fabs(dx), fabs(dy)); if (dist < (inp.HQ ? 3 : 1)*inp.width && d > Delta*.1) { normalize (&inp.n); if (inp.HQ) { double b = 0, c = 0; foreach_dimension() { b += sq(cam.O.x - a.x); c += sq(cam.O.x - cam.poi.x); } b = sqrt(b); c = sqrt(c); double ps = cam.fov*b/(cam.nx*c); coord np = {0,0,0}, rp = {0, 0, 0}; np.z = 1.; rp.z = r.dir.z; if(dist == fabs(dx)) rp.y = r.dir.y; else rp.x = r.dir.x; normalize (&rp); double cc = fabs(dot(rp, np)); double ss = cc > 0. ? ps/cc : ps; //printf ("%g %g %g\n", ss, ps, cc); double temp = dist + ss/4. < inp.width ? 1 : dist + ss/4. < 2*inp.width ? 1 - (dist + ss/4. - inp.width)/(inp.width) : 0; temp += fabs(dist - ss/4.) < inp.width ? 1 : fabs(dist - ss/4.) < 2*inp.width ? 1 - (fabs(dist - ss/4.) - inp.width)/(inp.width) : 0; temp = (2. - temp)/2.; inp.mat.T = max(min(temp, 1), 0); foreach_dimension() a.x -= 0.04*Delta*r.dir.x; } if (get_color_material (r, a, inp.n, inp.mat, px)) return d - Delta/20.; } } } return HUGE; } // User interface void lattice (struct _lattice inp) { sketch_list[functot] = &get_pixel_lattice; if (!inp.mat.dull && !inp.mat.ind && !any_col (inp.mat.col) && !inp.mat.s.i && !inp.mat.v.x.i && !(inp.mat.R >= 1)) for (int i = 0; i < 3; i++) inp.mat.col[i] = 1; if (!any_comp(inp.n)) inp.n.z = 1.; if (!inp.width) inp.width = cam.fov/(40*N); if (inp.HQ) inp.width /= 1.5; llist[functot] = inp; functot++; } /** ## A disk A round planar disk */ struct _disk { double R; coord P; coord n; material mat; }; struct _disk dlist[N_SKETCH]; // Arguments to disk double get_pixel_disk (struct _disk inp, ray r, double DG, unsigned char * px) { coord a; double alpha = 0; foreach_dimension() alpha += inp.n.x*inp.P.x; double d = ray_plane_intersect (r, inp.n, alpha, &a); if (d >= DG || d < 0.0001) //No further computations return HUGE; double D = 0; foreach_dimension() D += sq(a.x - inp.P.x); if (D > sq(inp.R)) return HUGE; normalize (&inp.n); normalize (&r.dir); if (get_color_material (r, a, inp.n, inp.mat, px)) return d; return HUGE; } bool disk (struct _disk inp) { sketch_list[functot] = &get_pixel_disk; if (!inp.R) inp.R = L0/2.; if (!any_comp (inp.n)) inp.n.z = 1; if (!inp.mat.dull || !inp.mat.ind || !any_col (inp.mat.col) || !inp.mat.s.i || !inp.mat.v.x.i || !(inp.mat.R >= 1.)) inp.mat.R = 1; dlist[functot] = inp; functot++; return true; } /** ## Sketch a sphere */ struct _sphere { double R; //Radius coord C; //centre struct Material mat; }; struct _sphere slist[N_SKETCH]; // Arguments to sphere trace double get_pixel_sphere (struct _sphere inp, ray r, double DG, unsigned char * px) { coord a, n; double d = ray_sphere_intersect (r, inp.C, inp.R, &a, &n); if (d > 0 && d < DG) { // possibly in sight if (get_color_material (r, a, n, inp.mat, px)) return d; } return HUGE; } // User interface void sphere (struct _sphere inp) { sketch_list[functot] = &get_pixel_sphere; if (!inp.R) inp.R = 0.5; if (!inp.mat.dull && !inp.mat.ind && !any_col (inp.mat.col) && !inp.mat.s.i && !inp.mat.v.y.i && !(inp.mat.R >= 1.)) inp.mat.col[0]= 200, inp.mat.col[1] = 100, inp.mat.col[2] = 150; if (inp.mat.dull && !any_col(inp.mat.col)) { inp.mat.col[0] = 50, inp.mat.col[1] = 100, inp.mat.col[2] = 150; inp.mat.col2[0] = 50, inp.mat.col2[1] = 150, inp.mat.col2[2] = 100; inp.mat.n1 = (coord){0.1, 0.9, 0}; } if (!inp.mat.SPexp && !inp.mat.SPR) { inp.mat.SPexp = 40; inp.mat.SPR = 0.1; } if (!inp.mat.map) inp.mat.map = jet; if (!inp.mat.min && !inp.mat.max) { inp.mat.min = -L0; inp.mat.max = L0; } slist[functot] = inp; functot++; } /** ## Equiplane An (Curved) Plane where a scalar field `s` has the value `val`. It can also be used to sketch a smooth vof-interface reconstruction. */ struct _equiplane { scalar s; double val; bool insideout; bool vof; material mat; } _equiplane; struct _equiplane eqlist[N_SKETCH]; trace double get_pixel_equiplane (struct _equiplane inp, ray r, double DG, unsigned char * px) { scalar s = inp.s; // Draw the distance equiplane for vof if (inp.vof) { s = s.vofd; if (inp.insideout) //Invert inp.insideout = false; else inp.insideout = true; } scalar posi = s.possible; vector v = s.normals; double d = HUGE; coord n, a; foreach_possible_ray_equi_intersection(r, s, posi, DG) { if (_dist < d) { double vals[2]; for (int i = 0; i < 2; i++) vals[i] = interpolate(s, _a[i].x, _a[i].y, _a[i].z); if ((vals[0] - inp.val) * (vals[1] - inp.val) < 0.) { double w = fabs(vals[0] - inp.val)/fabs((vals[0] - inp.val) - (vals[1] - inp.val)); if (w < 0 || w > 1) continue; double mrdir = 0; normalize (&r.dir); foreach_dimension() { a.x = w*_a[1].x + (1. - w)*_a[0].x; if (fabs(r.dir.x) > mrdir) { mrdir = fabs(r.dir.x); d = (a.x - r.O.x)/r.dir.x; } } n.x = interpolate (v.x, a.x, a.y, a.z); n.y = interpolate (v.y, a.x, a.y, a.z); n.z = interpolate (v.z, a.x, a.y, a.z); } } } if (any_comp(n) && d < DG && d > 0) { if (inp.insideout) { foreach_dimension() n.x *= -1; } normalize (&n); if (get_color_material (r, a, n, inp.mat, px)) return d; } return HUGE; } void equiplane (struct _equiplane inp) { sketch_list[functot] = &get_pixel_equiplane; if (!inp.mat.dull && !inp.mat.ind && !any_col (inp.mat.col) && !inp.mat.s.i && !inp.mat.v.x.i && !(inp.mat.R >= 1.)) inp.mat.col[0]= 100, inp.mat.col[1] = 200, inp.mat.col[2] = 150; if (!inp.mat.SPexp && !inp.mat.SPR) { inp.mat.SPexp = 40; inp.mat.SPR = 0.1; } if (!inp.mat.map) inp.mat.map = jet; if (!inp.mat.min && !inp.mat.max) { inp.mat.min = -L0; inp.mat.max = L0; } eqlist[functot] = inp; functot++; } /** ## Image in the scene Place an image in the scene */ #define PIXEL_INDEX (((ix + inp.res*jy)*3)) struct _image { char * fname; int res; unsigned char * imgrgb; //[res*res*3]; coord n; double alpha; coord up; unsigned char greenscreen[3]; // Transparent color code (non black green screen) material mat; }; struct _image ilist[N_SKETCH]; double get_pixel_image (struct _image inp, ray r, double DG, unsigned char * px) { coord a; double d = ray_plane_intersect (r, inp.n, inp.alpha, &a); if (d <= 0 || d >= DG) return HUGE; Point point = locate (a.x, a.y, a.z); if (point.level < 0) // Only slices in the domain return HUGE; normalize (&inp.n); {;} //Only grid aligned projections coord Ori = {X0, Y0, Z0}; foreach_dimension() { if (inp.n.z) { int ix = max(min(inp.res*(a.x - Ori.x)/L0, inp.res - 1), 0); int jy = max(min(inp.res*(Ori.y + L0 - a.y)/L0, inp.res - 1), 0); for (int i = 0; i < 3; i++) inp.mat.col[i] = max(inp.imgrgb[PIXEL_INDEX + i], 1); } } if (any_col (inp.greenscreen)) { int count = 0; for (int i = 0; i < 3; i++) if (inp.greenscreen[i] == inp.mat.col[i]) count++; if (count == 3) //its the green-screen val return HUGE; } if (get_color_material (r, a, inp.n, inp.mat, px)) return d; return HUGE; } bool image (struct _image inp) { if (!any_comp(inp.n)) inp.n.z = 1; if (!inp.alpha) inp.alpha = 0; if (!inp.res) inp.res = (1 << grid->maxdepth); if (inp.fname && inp.imgrgb == NULL) { inp.imgrgb = malloc (3*sizeof(unsigned char)*sq(inp.res)); char cmd[999]; sprintf (cmd, "convert -quiet %s -interpolate Nearest -filter point -resize %dx%d^ \ -gravity center -extent %dx%d -depth 8 2not8exist5.ppm", inp.fname, inp.res, inp.res, inp.res, inp.res); assert(!system (cmd)); FILE * fpi = fopen ("2not8exist5.ppm", "r"); char line_one[2]; int height, width, max_color; fscanf(fpi, "%s\n%d %d\n%d\n", line_one, &height, &width, &max_color); //printf ("%s %d %d %d\n", line_one, height, width, max_color); fread (inp.imgrgb, 3*sizeof(unsigned char), sq(inp.res), fpi); fclose (fpi); } else assert (0); ilist[functot] = inp; sketch_list[functot] = &get_pixel_image; functot++; return true; } /** ### Display text It is possible to add text using `convert` and `image` */ struct _sketch_text { char * str; int fs; // Font size (compare against .res) unsigned char tc[3]; // text color char * pos; // Imagemagick Position (center, north, south west etc..) int res; // Text image resolution char * ops; // Options to pass to convert coord n; double alpha; coord up; material mat; }; bool sketch_text (struct _sketch_text inp) { char fname[99] = "3not9exist6.ppm"; char cmd[999]; if (!inp.fs) inp.fs = 100; if (!inp.res) inp.res = 1000; unsigned char tc[3] = {0,0,0}; //black char d_ops[2] = " "; if (!inp.ops) inp.ops = d_ops; if (any_col(inp.tc)) { tc[0] = inp.tc[0]; tc[1] = inp.tc[1]; tc[2] = inp.tc[2]; } unsigned char gs[3]; //Set Green screen color for (int i = 0; i < 3; i++) { int tci = tc[i]; gs[i] = tci - 2 <= 0 ? tc[i] + 2 : tc[i] - 2; } char tmp[99] = "northwest"; if (!inp.pos) inp.pos = tmp; sprintf (cmd, "convert -quiet %s -size %dx%d -background \"rgb(%d, %d, %d)\"\ -fill \"rgb(%d,%d,%d)\" -pointsize %d -gravity %s caption:'%s' -depth 8 %s", inp.ops, inp.res, inp.res, gs[0], gs[1], gs[2], tc[0], tc[1], tc[2], inp.fs, inp.pos, inp.str, fname); //printf ("%s \n", cmd); system (cmd); struct _image inpn; inpn.fname = fname; inpn.greenscreen[0] = gs[0]; inpn.greenscreen[1] = gs[1]; inpn.greenscreen[2] = gs[2]; inpn.res = inp.res; inpn.n = inp.n; inpn.alpha = inp.alpha; inpn.up = inp.up; inpn.mat = inp.mat; inpn.imgrgb = NULL; return image (inpn); } /** ## A volumetric object It is possible to sketch volumetric objects as a smoke concentration field. (`0 ~ mval < s[]`) */ #include "radix.h" //A sorting algorithm bool sketch_a_volume = false; struct _volume { scalar s; double sc; // 1/e scale for s times length double mval; // Minimal value (positive and close to zero) unsigned char col[3]; // prescribed Color bool cols; // Color by scalar value (color map) vector colorv; // Color map 3D vector direction xyz -> rgb colormap map; // color Map (default is cool_warm) double min; // min cbar double max; // max cbar double shading; // shading effect for default lights }; struct _volume vollist; //max 1 double get_attenuation (coord a, struct _volume inp) { ray r = {.O = a, .dir = lights[1].dir}; foreach_dimension() r.dir.x *= -1; normalize (&r.dir); scalar s = inp.s; double sl = 0; foreach_ray_cell_intersection_volume (r, HUGE, s.possible) { coord mean = {0, 0, 0}; double len = 0; foreach_dimension() { mean.x = (_a[0].x + _a[1].x)/2.; len += sq(_a[0].x - _a[1].x); } double val = interpolate (inp.s, mean.x, mean.y, mean.z); if (val > inp.mval) { len = sqrt(len); if (len > 0) sl += fabs(val)*len; } } return sl; } trace void mod_pixel_volume (struct _volume inp, ray r, double DG, unsigned char * px) { scalar s = inp.s; int np = 50, incf = 2; // Array size, increase factor; double * dvl = NULL; int elm = 0; if (inp.cols) { elm = 3 + (inp.shading > 0) + 3*(inp.colorv.x.i > 0); dvl = malloc (elm*sizeof(double)*np); //Distance, values and length; } int celld = 0; // Number if cells double sl = 0; //integral of s times length; foreach_ray_cell_intersection_volume(r, DG, s.possible) { coord mean = {0, 0, 0}; double len = 0; foreach_dimension() { mean.x = (_a[0].x + _a[1].x)/2.; len += sq(_a[0].x - _a[1].x); } double val = interpolate (inp.s, mean.x, mean.y, mean.z); if (val > inp.mval) { len = sqrt(len); if (len > 0) sl += fabs(val)*len; if (inp.cols) { // Record data celld++; if (celld >= np) { // increase array size np *= incf; dvl = realloc (dvl, elm*sizeof(double)*np); } double dirm = 0; foreach_dimension() { //distance if (fabs(r.dir.x) > dirm) dvl[elm*(celld - 1)] = (mean.x - r.O.x)/r.dir.x; } assert (dvl[elm*(celld - 1)] > 0); dvl[elm*(celld - 1) + 1] = val; dvl[elm*(celld - 1) + 2] = len; if (inp.shading) dvl[elm*(celld - 1) + 3] = get_attenuation (mean, inp); if (inp.colorv.x.i) { double xp = mean.x, yp = mean.y, zp = mean.z; int dim = 0; foreach_dimension() dvl[elm*(celld - 1) + 4 + dim++] = interpolate (inp.colorv.x, xp, yp, zp); } } } } if (inp.cols && sl > 0) { // Compute effective absorbed color int ind[celld], dista[celld]; for (int it = 0; it < celld; it++) dista[it] = (int)(1 << (depth() + 1))*(dvl[elm*it]/L0); // distance in integer units radixsort (celld, dista, ind); double col[3] = {0, 0, 0}; double cmap[NCMAP][3]; inp.map (cmap); double sl2 = 0; double TW = 0; for (int it = 0; it < celld; it++) { unsigned char coll[3]; if (inp.colorv.x.i) { int mx = 0; for (int i = 0; i < 3; i++) { coll[i] = 255*min(1, fabs(dvl[elm*ind[it] + 4 + i])/inp.max); if (coll[i] > mx) mx = coll[i]; } for (int i = 0; i < 3; i++) coll[i] = mx > 0 ? min (255, (255*coll[i])/mx) : 1; } else colormap_pigmentation (coll, cmap, dvl[elm*ind[it] + 1], inp.min, inp.max); double sle = dvl[elm*ind[it] + 2]*fabs(dvl[elm*ind[it] + 1]); double Weight = exp(-sl2/inp.sc) - exp(-(sl2 + sle)/inp.sc); double fac = 1; if (inp.shading) { double I = lights[0].I; I += lights[1].I*exp(-dvl[elm*ind[it] + 3]/(smoke.att)); fac = min (max(cam.f(I) - cam.f(cam.min),0)/cam.f(cam.max/cam.min), 1); } TW += Weight; for (int i = 0; i < 3; i++) col[i] += (double)coll[i]*Weight*fac; sl2 += sle; } if (TW > 0) { for (int i = 0; i < 3; i++) inp.col[i] = (unsigned char)(col[i]/TW); } } if (sl > 0) { double W = exp(-sl/inp.sc); px[0] = (1 - W)*inp.col[0] + W*px[0]; px[1] = (1 - W)*inp.col[1] + W*px[1]; px[2] = (1 - W)*inp.col[2] + W*px[2]; } free (dvl); dvl = NULL; } bool volume (struct _volume inp) { if (!inp.sc) inp.sc = 1.; if (!any_col(inp.col)) { inp.col[0] = 255; inp.col[1] = 255; inp.col[2] = 255; } if (!inp.max) inp.max = 1; if (!inp.mval) inp.mval = 0; if (!inp.min) inp.min = inp.mval; if (inp.shading) { scalar s = inp.s; smoke = s; s.att = inp.sc/inp.shading; } if (!inp.map) inp.map = cool_warm; vollist = inp; sketch_a_volume = true; return sketch_a_volume; } /** ## Triangles Draw a list of triangles, stored as a `nodata` terminated list of vertex triplets (e.g. as read by `inport_stl`). */ struct _triangles { coord * c; coord bb[2]; //Bounding box (automatically computed) material mat; }; struct _triangles tlist[N_SKETCH]; trace double get_pixel_triangles (struct _triangles inp, ray r, double DG, unsigned char * px) { coord * p = inp.c; double dist = DG; coord a, n; bool any = false; if (ray_box (r, inp.bb) < dist) { while (p->x != nodata) { double d = ray_triangle (r, p); if (d < dist) { dist = d; any = true; foreach_dimension() a.x = r.O.x + r.dir.x*d; coord a1, a2; foreach_dimension() { a1.x = p[1].x - p[0].x; a2.x = p[2].x - p[0].x; } n = cross (a1, a2); normalize (&n); } p += dimension; //3? } if (any) if (get_color_material (r, a, n, inp.mat, px)) return dist; } return HUGE; } struct _triangles get_bb (struct _triangles in) { struct _triangles inp = in; foreach_dimension() { inp.bb[0].x = HUGE; inp.bb[1].x = -HUGE; } while (in.c->x < nodata) { foreach_dimension() { if (in.c->x < inp.bb[0].x) inp.bb[0].x = in.c->x; if (in.c->x > inp.bb[1].x) inp.bb[1].x = in.c->x; } in.c++; } return inp; } bool triangles (struct _triangles inp) { sketch_list[functot] = &get_pixel_triangles; if (!inp.mat.dull && !inp.mat.ind && !any_col (inp.mat.col) && !inp.mat.s.i && !inp.mat.v.x.i && !(inp.mat.R >= 1.)) inp.mat.col[0]= 200, inp.mat.col[1] = 150, inp.mat.col[2] = 100; if (!inp.mat.SPexp && !inp.mat.SPR) { inp.mat.SPexp = 40; inp.mat.SPR = 0.1; } if (!inp.mat.map) inp.mat.map = jet; if (!inp.mat.min && !inp.mat.max) { inp.mat.min = -L0; inp.mat.max = L0; } inp = get_bb (inp); tlist[functot] = inp; functot++; return true; } /** ## A plain sheet for sketching... `plain()` mimics bview's `clear()` a bit. */ void plain (void) { for (int func = 0; func < functot; func++) if (sketch_list[func] == &get_pixel_image) { free (ilist[func].imgrgb); ilist[func].imgrgb = NULL; } functot = 0; free (shading); shading = NULL; sketch_a_volume = false; } /** ## *The* ray-caster function The implementation of a helper function that gets the `rgb` values of a ray by cycling though all sketch function calls...*/ double get_color_ray (ray r, unsigned char * px) { double Distg = HUGE; for (int func = 0; func < functot; func++) { double dist = HUGE; unsigned char pt[3]; if (sketch_list[func] == &get_pixel_quadriangles) dist = sketch_list[func](qlist[func], r, Distg, pt); else if (sketch_list[func] == &get_pixel_sketch_vof) dist = sketch_list[func](svlist[func], r, Distg, pt); else if (sketch_list[func] == &get_pixel_disk) dist = sketch_list[func](dlist[func], r, Distg, pt); else if (sketch_list[func] == &get_pixel_lattice) dist = sketch_list[func](llist[func], r, Distg, pt); else if (sketch_list[func] == &get_pixel_sphere) dist = sketch_list[func](slist[func], r, Distg, pt); else if (sketch_list[func] == &get_pixel_equiplane) dist = sketch_list[func](eqlist[func], r, Distg, pt); else if (sketch_list[func] == &get_pixel_image) dist = sketch_list[func](ilist[func], r, Distg, pt); else if (sketch_list[func] == &get_pixel_triangles) dist = sketch_list[func](tlist[func], r, Distg, pt); if (dist < Distg && dist > 0) { Distg = dist; for (int i = 0; i < 3; i++) px[i] = pt[i]; } } if (sketch_a_volume) // Modify this pixel color mod_pixel_volume (vollist, r, Distg, px); return Distg; } static inline double point_value (scalar s, Point point) { return s[]; } /** ## Watch the sketch process You may watch the sketching process *live* in a (minimalistic) X11 window. You should have the [`libX11`](https://www.x.org/wiki/) library [installed](https://packages.debian.org/nl/sid/libx11-dev) and compile using something like: ~~~literatec $ qcc -O2 -DWATCH_ALONG bwatch.c -lm -lX11 $ ./a.out ~~~ Note that it may slow the rendering, especially with openmp. */ #if WATCH_ALONG #include GC gc_2; Window win = (Window)NULL; Display *dsp; int screen; Colormap cmap; void XSetup (void) { if (win == (Window)NULL) { #if _OPENMP XInitThreads(); #pragma omp barrier #endif dsp = XOpenDisplay(NULL); screen = DefaultScreen(dsp); win = XCreateWindow(dsp, DefaultRootWindow(dsp), 0, 0, cam.nx, cam.ny, 0, 0, 0, 0, 0, 0); XGCValues gcvalues_2; gcvalues_2.function = GXcopy; gcvalues_2.plane_mask = AllPlanes; gcvalues_2.foreground = 0x00FF00; gcvalues_2.background = 0xFFFFFF; gc_2 = XCreateGC(dsp, win, GCFunction|GCPlaneMask|GCForeground|GCBackground, &gcvalues_2); XStoreName (dsp, win, "Bwatch: Watch Along\0"); XMapWindow(dsp, win); cmap= XDefaultColormap(dsp, screen); } } void XSketchPx (int x, int y, unsigned char px[3]) { XLockDisplay (dsp); XColor pX11; pX11.red = 256*px[0]; pX11.green = 256*px[1];; pX11.blue = 256*px[2];; if (XAllocColor(dsp,cmap,&pX11)==0) assert (0); XSetForeground(dsp,gc_2,pX11.pixel); XDrawPoint(dsp, win, gc_2, x, cam.ny - y ); if (tid() == 0) XFlush (dsp); XUnlockDisplay (dsp); } void XStop (void) { XDestroyWindow(dsp, win); XCloseDisplay(dsp); win = (Window)NULL; } event stop_X11 (t = end) { XStop(); } #endif /** ## Preparation Before rendering, it maybe usefull to compute some helper fields to store data for rendering or help guide optimized grid iterators. */ void preparation_steps (void){ #if WATCH_ALONG XSetup(); #endif for (int func = 0; func < functot; func++) { //Gradients and possibles for isosurfaces, // maybe compute the nearby distances to vof fields. if (sketch_list[func] == &get_pixel_equiplane) { scalar s = eqlist[func].s; if (eqlist[func].vof) { char dname[99]; sprintf (dname, "dfor%s%d", s.name, func); scalar d = new_scalar (dname); s.vofd = d; find_nearby_distances (s); s = d; // we will draw the isosurface of d not s } char vname[99], pname[99]; sprintf (vname, "vfor%s%d", s.name, func); sprintf (pname, "pfor%s%d", s.name, func); vector v = new_vector (vname); scalar p = new_scalar (pname); s.normals = v; s.possible = p; find_possible (s, eqlist[func].val); gradients ({s}, {v}); foreach() { coord n = {0}; foreach_dimension() n.x = v.x[]; if (any_comp(n)) { normalize (&n); foreach_dimension() v.x[] = n.x; } } boundary ((scalar*){v}); } // Precompute normals and plane alphas for vof facets else if (sketch_list[func] == &get_pixel_sketch_vof) { if (svlist[func].precomp) { scalar f = svlist[func].f; char vname[99], aname[99]; sprintf (vname, "vfor%s%d", f.name, func); sprintf (aname, "afor%s%d", f.name, func); vector v = new_vector (vname); scalar alpha = new_scalar (aname); f.normals = v; f.possible = alpha; //alpha foreach() { alpha[] = nodata; foreach_dimension() v.x[] = nodata; if (interfacial2 (f, point)) { coord n = mycs (point, f); //pointing outwards. double l = vec_length(n); alpha[] = plane_alpha (point_value(f, point), n)/l; normalize (&n); foreach_dimension() v.x[] = n.x; } } boundary ({alpha, v}); } } } if (sketch_a_volume) { //Pre compute cells with volume scalar s = vollist.s; char pname [99]; sprintf (pname, "p_for_%s_volume", s.name); scalar p = new_scalar (pname); s.possible = p; find_possible_vol (s, vollist.mval); } } void aftercare (void) { for (int func = 0; func < functot; func++) { if (sketch_list[func] == &get_pixel_equiplane) { scalar s = eqlist[func].s; if (eqlist[func].vof) s = s.vofd; vector v = s.normals; scalar posi = s.possible; delete ({posi, v}); if (eqlist[func].vof) delete ({s}); } else if (sketch_list[func] == &get_pixel_sketch_vof) if (svlist[func].precomp) { scalar s = svlist[func].f; vector v = s.normals; scalar alpha = s.possible; delete ((scalar*){alpha, v}); } } if (sketch_a_volume) { smoke.i = 0; scalar s = vollist.s; s = s.possible; delete ({s}); } } /** ## Store Write a `.ppm` to the `FILE * fp`. */ // Default background #define BGR (0) #define BGG (25.*(ii + jj)/cam.nx + 120) #define BGB (50.*jj/cam.nx + 120) trace bool store (FILE * fp) { if (lights[0].type == 0) // No lights default_lights(); fprintf (fp, "P6\n%d %d\n%d\n", cam.nx, cam.ny, 255); // .ppm header preparation_steps(); foreach_ray() { unsigned char px[3] = {BGR, BGG, BGB}; get_color_ray (_r, px); fwrite (px, 1, 3, fp); #if WATCH_ALONG XSketchPx (ii, jj, px); #endif } fflush (fp); aftercare(); return true; } #if _OPENMP trace bool store_OPENMP (FILE * fp) { if (lights[0].type == 0) // No lights default_lights(); fprintf (fp, "P6\n%d %d\n%d\n", cam.nx, cam.ny, 255); // .ppm header preparation_steps(); #pragma omp parallel shared(fp) { assert (!(cam.nx*cam.ny % npe())); unsigned char px[3]; foreach_ray() { int jjj = cam.ny - jj; if ((jjj*cam.nx + ii) % npe() == tid()) { px[0]= BGR, px[1] = BGG, px[2] = BGB; get_color_ray (_r, px); #if WATCH_ALONG XSketchPx (ii, jj, px); #endif } if ((jjj*cam.nx + ii) % npe() == (npe() - 1)) for (int i = 0; i < npe(); i++) { if (tid() == i) { fwrite (px, 1, 3, fp); } #pragma omp barrier } } } fflush (fp); aftercare(); return true; } // Overload calls to store #define store store_OPENMP #endif //_OPENMP /** ## Store adaptive With [`raycaster`](raycaster.c) (or [this one](raycasterv.c)) installed, we can adaptively sample the scene (lossy compresion). It can be effecient to generate high resolution movies. */ # if 0 #include #define ParentRead read_pipe[0] #define ParentWrite write_pipe[1] #define ChildRead write_pipe[0] #define ChildWrite read_pipe[1] #define BGRa (0) #define BGGa (25.*(b[0] + b[1]) + 120) #define BGBa (50.*b[1] + 120) struct _sa { FILE * fp; int ml; // max level of output image (cam.nx/ny are ignored) double tol; // tolerance }; trace bool store_adaptive (struct _sa inp) { double tol = 25; int ml = 10; if (inp.tol) tol = inp.tol; if (inp.ml) ml = inp.ml; if (lights[0].type == 0) // No lights default_lights(); preparation_steps(); // Organize pipe and fork int read_pipe[2]; int write_pipe[2]; pipe (read_pipe); pipe (write_pipe); pid_t p; p = fork(); if (p == 0) { //child calls `raycaster` close (ParentRead); close (ParentWrite); if (system("which ./raycaster > /dev/null 2>&1")) { fprintf (stderr, "Error: Adaptive raycaster is not installed\n"); assert(0); } else { dup2 (ChildRead, STDIN_FILENO); dup2 (ChildWrite, STDOUT_FILENO); dup2 (fileno(inp.fp), STDERR_FILENO); char m1[9], m2[15]; sprintf (m1, "%d", ml); sprintf (m2, "%g", tol); execl ("raycaster", "raycaster", m1, m2, NULL); } } else { // Parent computes color codes close (ChildRead); close (ChildWrite); int a = sq(1 << (ml - 2)) + 1; int al = a; double * pos = malloc (2*al*sizeof(double)); unsigned char * pxs = malloc (3*al*sizeof(unsigned char)); while (a) { read (ParentRead, &a, sizeof(int)); if (a > al) { al = 2*a; pos = realloc (pos, 2*al*sizeof(double)); pxs = realloc (pxs, 3*al*sizeof(unsigned char)); } for (int i = 0; i < a; i++) read (ParentRead, &pos[2*i], 2*sizeof(double)); coord cr, proj, vert, hori; foreach_dimension() cr.x = cam.poi.x - cam.O.x; normalize (&cr); foreach_dimension() { proj.x = cam.up.x*cr.x; vert.x = cam.up.x - proj.x; } hori = cross (cr, vert); normalize (&vert); normalize (&hori); #if _OPENMP #pragma omp parallel #endif for (int i = tid(); i < a; i += npe()) { coord apoint; double b[2]; b[0] = pos[2*(i)]; b[1] = pos[2*(i) + 1]; foreach_dimension() apoint.x = (cam.poi.x + cam.fov*(b[0] - 0.5)*hori.x + cam.fov*(b[1] - 0.5)*vert.x); // New ray ray _r; _r.depth = 0; _r.O = cam.O; foreach_dimension() _r.dir.x = apoint.x - cam.O.x; normalize (&_r.dir); // Get and write color pxs[3*i] = BGRa; pxs[3*i + 1] = BGGa; pxs[3*i + 2] = BGBa; get_color_ray (_r, &pxs[3*i]); } write (ParentWrite, &pxs[0], 3*a*sizeof(unsigned char)); } wait (NULL); //wait until the child is done writing the image free (pos); pos = NULL; free (pxs); pxs = NULL; } aftercare(); return true; } #endif