[
  {
    "path": ".gitignore",
    "content": "debug_rays\nshow_scene\nscenelist_override.txt\nscenelist.txt\n*.ppm\nanim\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2021 Gary Briggs <chunky@icculus.org>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# A Pure SQL Raytracer\n\nEveryone writes a raytracer sooner or later. This is mine.\n\n## Example Outputs\n\n<img src=\"example_outputs/dielectricparty.png\" width=200> <img src=\"example_outputs/oneglassball.png\" width=200> <img src=\"example_outputs/onemirrorball.png\" width=200> <img src=\"example_outputs/glassmatrix.png\" width=200> <img src=\"example_outputs/airy.png\" width=200> <img src=\"example_outputs/onediamondball.png\" width=200> <img src=\"example_outputs/oneantiglassball.png\" width=200> <img src=\"example_outputs/adjacentballs.png\" width=200> <img src=\"example_outputs/onegreyball.png\" width=200> <img src=\"example_outputs/reflectiontest.png\" width=200> <img src=\"example_outputs/twodiffuseballs.png\" width=200> <img src=\"example_outputs/onegreenball.png\" width=200> <img src=\"example_outputs/threemirrors.png\" width=200> <img src=\"example_outputs/twomirrorballs.png\" width=200> <img src=\"example_outputs/busyday.png\" width=200>\n\n## Usage\n\n```shell\nsh create.sh\n```\n\n```postgres_connection.sh``` contains host/database/user/pass/etc.\nThere are no exotic needs other than \"postgres, like version 10 and up\nor something\"\n\nFor what it's worth, I created mine thus on my ubuntu desktop:\n```shell\nsudo su - postgres\ncreateuser --pwprompt raytracer\ncreatedb -O raytracer raytracer\n```\n\n### Levers for development and rendering\n\nWhile doing development, obviously a few-minute render time is a pretty\npoor cycle time. There are a few levers you can pull to speed things up\nand reduce quality. They're on \"camera\" and \"img\" in setup.sql:\n\n* *samples\\_per\\_px* - This is the number of rays/sub-samples per pixel.\n  - 1 or 2 is fine during debugging\n  - 15-20 gives \"workable\" pictures\n  - Going above 50 doesn't generate much visible improvement\n* *max\\_ray\\_depth* - The maximum number of ray bounces\n  - For simple scenes, it usually makes no more than 5 or so bounces\n* *res\\_x* and *res\\_y* - Final image resolution\n  - Smaller is faster\n\nThe main CTE carries a lot of stuff that's unnecessary to final output.\nThis is so I can examine rays bouncing through the scene with:\n```sql\nSELECT * FROM rays WHERE img_x=100 AND img_y=250\n```\n\nThere's a script to get a quick-and-dirty view of a scene using gnuplot;\nthe script ```show_scene.sh``` should generate a folder of outputs.\n\n## Database\n\nThis is implemented in pure SQL. It doesn't do anything like CREATE\nFUNCTION or other nonportables, except for the trigger to do animation,\nwhich obviously doesn't count.\n\nAt the same time, there are some not-entirely-common features of SQL\nthat it needs:\n\n* JOIN LATERAL\n* PARTITION BY inside of a RECURSIVE CTE\n* Math functions like SIN()\n\nSo although I started developing this in SQLite, I ended up leaning\non PostgreSQL. As I write this, it works in postgres and hasn't been\ntested in anything else.\n\n## Interesting Implementation Pieces\n\nSuch as it is, I did find myself solving some problems in interesting\nways.\n\n### JOIN LATERAL\n\nJOIN LATERAL is a way to do a correlated subquery in a JOIN, instead of\njust in a WHERE clause. I use this as a way to hoist calculations and\ndo many of them only once and, in some cases, avoid excessive duplication.\n\n### Diffuse Scattering\n\nThis requires sampling a uniform sphere. I generate a lot of random\nsamples ahead of time [sample with rejection -> scale points to sphere\nsurface], and number them.\n\nFiguring out a way to join each ray to a single random row from these\nprecalculated scatters was weird; can't just join to RANDOM() because\nevery ray got joined to the same, random, scatter. Can't just select\nwith a typical calculation on a normal because that leads to stripes\nin the picture.  So, instead, I schlep out a later few decimals of one\ndimension of a normal, then join to that. It's \"random\" but also\nunique-enough-per-ray.\n\n### Recursive CTEs\n\nRaytracing very naturally tracks how recursive CTEs work. One of the\nthings I ran into was a clean way to identify which ray is the one to\naccount for. Using a window function ordering by intercept (t) worked\nwell. Every iteration, this query intersects a ray with *everything*\nin front of it and does all of the associated calculations, but then in\nthe WHERE clause will reject everything except the thing the ray\nactually hit.\n\nAlso, there's something really beautiful about the simplicity of the\ncore of the final rollup [edited for clarity]:\n```sql\n SELECT img_x, img_y,\n         SUM(POW(color_mult * ray_col_r/samples_per_px, gamma)) col_r,\n         SUM(POW(color_mult * ray_col_g/samples_per_px, gamma)) col_g,\n         SUM(POW(color_mult * ray_col_b/samples_per_px, gamma)) col_b\n    FROM rays\n    GROUP BY img_y, img_x\n```\n\n### Scenes, materials, etc\n\nBecause this is in SQL, I can store multiple scenes in the database. Which\none is actually rendered is selected in the \"camera\" table.\n\n## Standing on the Necks of Giants\n\nTwo years before I wrote this \"The most advanced MySQL raytracer on the\nmarket right now\" did the rounds on social media:\nhttps://www.pouet.net/prod.php?which=83222\n\nI had a few things in mind that I wanted to do differently [worse?]:\n\n* Demoscene is an artform. I'm not golfing, this isn't minified\n* Not a single query; that can be done with CTEs, but ehhhhhhhh\n* Animation as an endgame [see ```anim.sh```]\n* Mainly, I'm just buggering around with the wrong tool for the job\n\n### I asked Claude for an opinion\n\n*\"Why would someone write something like this in the first place?\"*\n\n> This is a fascinating example of SQL as a general-purpose programming language taken to an extreme. Someone wrote a complete ray tracer in PostgreSQL SQL, which is both impressive and... questionable.\n> \n> [...]\n> \n> This feels like either a brilliant computer science student's thesis project, a senior engineer's weekend experiment, or someone trapped in a very restrictive database-only environment who refused to give up on their ray tracing dreams.\n> \n> It's the programming equivalent of building a race car out of Lego - technically impressive, but you probably shouldn't actually drive it.\n\nI can't tell if I'm flattered or offended.\n\n## References\n\nMost of this is built following the \"Ray Tracing in One Weekend\"\nseries: https://raytracing.github.io/ , then making allowances for\nthe deliberately obtuse way I'm coding it.\n\n\nGary <chunky@icculus.org>\n\n"
  },
  {
    "path": "anim.sh",
    "content": "#!/bin/sh\n\n. ./postgres_connection.sh\n\nscenename=\"busyday\"\noutfolder=anim\ndt=0.05\ngravity=-9.8\n\npsql \\\n\t--host=${PGHOST} \\\n\t--port=${PGPORT} \\\n\t--username=${PGUSER} \\\n\t--dbname=${PGDB} \\\n\t--file=setup.sql \\\n\t--file=raytracer.sql \\\n\t--command=\"UPDATE camera SET sceneid=(SELECT sceneid FROM scene WHERE scenename='${scenename}')\"\n\nmkdir -p ${outfolder}\n\nfor frame in `seq 0 1000`\ndo\n  echo \"Frame ${frame}\"\n  psql \\\n  \t--host=${PGHOST} \\\n  \t--port=${PGPORT} \\\n  \t--username=${PGUSER} \\\n  \t--dbname=${PGDB} \\\n\t--command=\"INSERT INTO updateworld(dt, grav_x, grav_y, grav_z) VALUES (${dt}, 0.0, ${gravity}, 0.0)\" \\\n  \t--command=\"\\\\timing\" \\\n\t--command=\"\\\\copy (select * from ppm) to './${outfolder}/${scenename}_${frame}.ppm' csv\"\n\ndone\n\nffmpeg \\\n\t-r 25 \\\n\t-i ./${outfolder}/${scenename}_%d.ppm \\\n\t-crf 25 \\\n       \t-pix_fmt yuv420p \\\n\t./${outfolder}/${scenename}.mp4\n\n"
  },
  {
    "path": "create.sh",
    "content": "#!/bin/sh\n\n. ./postgres_connection.sh\n\n# Creating this file overrides which scenes get rendered\nscenelist_override=scenelist_override.txt\n\nscenelist=scenelist.txt\n\noutputdir=example_outputs\n\nmkdir -p ${outputdir}\n\npsql \\\n\t--host=${PGHOST} \\\n\t--port=${PGPORT} \\\n\t--username=${PGUSER} \\\n\t--dbname=${PGDB} \\\n\t--file=setup.sql \\\n\t--file=raytracer.sql \\\n\t--command=\"\\\\timing\" \\\n\t--command=\"\\\\copy (select scenename from scene) to './${outputdir}/${scenelist}' csv\"\n\ntest -e ${scenelist_override} && cp ${scenelist_override} ${outputdir}/${scenelist}\n\nwhile read scenename\ndo\n  echo \"\"\n  echo \"Rendering scene ${scenename}\"\n  psql \\\n  \t--host=${PGHOST} \\\n  \t--port=${PGPORT} \\\n  \t--username=${PGUSER} \\\n  \t--dbname=${PGDB} \\\n  \t--command=\"UPDATE camera SET sceneid=(SELECT sceneid FROM scene WHERE scenename='${scenename}')\" \\\n  \t--command=\"\\\\timing\" \\\n\t--command=\"\\\\copy (select * from ppm) to './${outputdir}/${scenename}.ppm' csv\"\n\n  if [ \"$(uname)\" == \"Darwin\" ]; then\n    open ./${outputdir}/${scenename}.ppm\n  else\n    xdg-open ./${outputdir}/${scenename}.ppm\n  fi\n  \n  convert ./${outputdir}/${scenename}.ppm \\\n\t  -gravity SouthEast -pointsize 30 -fill black -annotate +10+10 \"${scenename}\" \\\n\t  ./${outputdir}/${scenename}.png\n\ndone < ./${outputdir}/${scenelist}\n\n"
  },
  {
    "path": "debug_rays.sh",
    "content": "#!/bin/sh\n\n. ./postgres_connection.sh\n\ndebug_dir=debug_rays\nmkdir -p ${debug_dir}\ncd ${debug_dir}\n\npsql \\\n\t--host=${PGHOST} \\\n        --port=${PGPORT} \\\n        --username=${PGUSER} \\\n        --dbname=${PGDB} \\\n        --command=\"\\\\copy (select sphereid, cx, cy, cz, radius from sphere inner join camera c ON c.sceneid=sphere.sceneid) to './spheres.csv' with csv header\" \\\n\t--command=\"\\\\copy (select * from rays WHERE img_x=125 AND img_y between 120 and 195 and 0=img_y%5) to './rays.csv' with csv header\"\n\nimg_size=2048\n\nscale_vector=5\n\ncat <<EOH > gnuplot.gp\nset terminal png size ${img_size},${img_size}\nset xrange [-60:60]\nset yrange [-60:60]\nset datafile separator ','\nset key autotitle columnhead\n\nset output 'debug_rays_xz.png'\nset xlabel \"X\"\nset ylabel \"Z\"\nplot \\\\\n        \"spheres.csv\" u 2:4:5 w circles t 'spheres', \\\\\n\t\"rays.csv\" u 12:14:(${scale_vector}*\\$19/\\$22):(${scale_vector}*\\$21/\\$22) w vectors t 'normals', \\\\\n\t\"rays.csv\" u 12:14:(${scale_vector}*\\$15):(${scale_vector}*\\$17) w vectors filled head t 'rays'\n\nset output 'debug_rays_zx.png'\nset xlabel \"Z\"\nset ylabel \"X\"\nplot \\\\\n        \"spheres.csv\" u 4:2:5 w circles t 'spheres', \\\\\n\t\"rays.csv\" u 14:12:(${scale_vector}*\\$21/\\$22):(${scale_vector}*\\$19/\\$22) w vectors t 'normals', \\\\\n\t\"rays.csv\" u 14:12:(${scale_vector}*\\$17):(${scale_vector}*\\$15) w vectors filled head t 'rays'\n\nset output 'debug_rays_xy.png'\nset xlabel \"X\"\nset ylabel \"Y\"\nplot \\\\\n        \"spheres.csv\" u 2:3:5 w circles t 'spheres', \\\\\n\t\"rays.csv\" u 12:13:(${scale_vector}*\\$19/\\$22):(${scale_vector}*\\$20/\\$22) w vectors t 'normals', \\\\\n\t\"rays.csv\" u 12:13:(${scale_vector}*\\$15):(${scale_vector}*\\$16) w vectors t 'rays'\n\nset output 'debug_rays_yx.png'\nset xlabel \"Y\"\nset ylabel \"X\"\nplot \\\\\n        \"spheres.csv\" u 3:2:5 w circles t 'spheres', \\\\\n\t\"rays.csv\" u 13:12:(${scale_vector}*\\$20/\\$22):(${scale_vector}*\\$19/\\$22) w vectors t 'normals', \\\\\n\t\"rays.csv\" u 13:12:(${scale_vector}*\\$16):(${scale_vector}*\\$15) w vectors t 'rays'\n\nset output 'debug_rays_yz.png'\nset xlabel \"Y\"\nset ylabel \"Z\"\nplot \\\\\n        \"spheres.csv\" u 3:4:5 w circles t 'spheres', \\\n\t\"rays.csv\" u 13:14:(${scale_vector}*\\$20/\\$22):(${scale_vector}*\\$21/\\$22) w vectors t 'normals', \\\\\n\t\"rays.csv\" u 13:14:(${scale_vector}*\\$16):(${scale_vector}*\\$17) w vectors t 'rays'\n\nset output 'debug_rays_zy.png'\nset xlabel \"Z\"\nset ylabel \"Y\"\nplot \\\\\n        \"spheres.csv\" u 4:3:5 w circles t 'spheres', \\\\\n\t\"rays.csv\" u 14:13:(${scale_vector}*\\$21/\\$22):(${scale_vector}*\\$20/\\$22) w vectors t 'normals', \\\\\n\t\"rays.csv\" u 14:13:(${scale_vector}*\\$17):(${scale_vector}*\\$16) w vectors t 'rays'\n\nset output '3view.png'\nset xrange [-120:120]\nset yrange [-120:120]\nset zrange [-120:120]\nset xlabel \"X\"\nset ylabel \"Y\"\nset zlabel \"Z\"\nsplot \\\\\n\t\"spheres.csv\" u 2:3:4:5 w circles t 'spheres', \\\\\n\t\"rays.csv\" u 12:13:14:(${scale_vector}*\\$19/\\$22):(${scale_vector}*\\$20/\\$22):(${scale_vector}*\\$21/\\$22) w vectors filled head t 'normals', \\\\\n\t\"rays.csv\" u 12:13:14:(${scale_vector}*\\$15):(${scale_vector}*\\$16):(${scale_vector}*\\$17) w vectors filled head t 'rays'\n\nset terminal qt\nreplot\n\nEOH\n\ngnuplot gnuplot.gp\n\nmontage \\\n\t-tile 2x2 \\\n\t-geometry ${img_size}x${img_size} \\\n\tdebug_rays_xy.png \\\n       \tdebug_rays_zy.png \\\n       \tdebug_rays_xz.png \\\n       \t3view.png \\\n\tdebug_rays.png\n\nxdg-open debug_rays.png\n\n"
  },
  {
    "path": "postgres_connection.sh",
    "content": "PGHOST=localhost\nPGPORT=5432\nPGUSER=raytracer\nPGDB=raytracer\n\nPGPASSWORD=raytracer\nexport PGPASSWORD\n\n"
  },
  {
    "path": "raytracer.sql",
    "content": "DROP TABLE IF EXISTS sphere_sample CASCADE;\nCREATE TABLE IF NOT EXISTS sphere_sample (x DOUBLE PRECISION NOT NULL, y DOUBLE PRECISION NOT NULL, z DOUBLE PRECISION NOT NULL,\n\ta DOUBLE PRECISION NOT NULL, b DOUBLE PRECISION NOT NULL, c DOUBLE PRECISION NOT NULL, sampleno INTEGER NOT NULL, n_samples INTEGER NOT NULL);\nINSERT INTO sphere_sample\n     WITH square_sample AS (SELECT 2.0*(RANDOM() - 0.5) AS a1, 2.0*(RANDOM() - 0.5) AS b1, 2.0*(RANDOM() - 0.5) AS c1\n          FROM generate_series(1, 5000)),\n     ball_sample AS (SELECT a1 AS a, b1 AS b, c1 AS c, SQRT(a1*a1+b1*b1+c1*c1) AS radius FROM square_sample WHERE 1>=(a1*a1+b1*b1+c1*c1)),\n     sphere_sample AS (SELECT a/radius AS x, b/radius AS y, c/radius AS z, a, b, c, ROW_NUMBER() OVER () AS sampleno, COUNT(*) OVER () AS n_samples FROM ball_sample)\n     SELECT x,y,z,a,b,c,sampleno,n_samples FROM sphere_sample;\nDROP INDEX IF EXISTS idx_ss;\nCREATE INDEX IF NOT EXISTS idx_ss ON sphere_sample(sampleno);\n\nDROP VIEW IF EXISTS rays CASCADE;\nCREATE VIEW rays AS\n    WITH RECURSIVE\n     xs AS (SELECT 0 AS u, 0.0 AS img_frac_x UNION ALL SELECT u+1, (u+1.0)/img.res_x FROM xs, img WHERE xs.u<img.res_x-1),\n     ys AS (SELECT 0 AS v, 0.0 AS img_frac_y UNION ALL SELECT v+1, (v+1.0)/img.res_y FROM ys, img WHERE ys.v<img.res_y-1),\n     px_sample_n(px_sample_n, px_jitter_u, px_jitter_v) AS (SELECT 1, (RANDOM()-0.5) * (fov_rad_x/res_x), (RANDOM()-0.5) * (fov_rad_y/res_y)\n                FROM camera, img\n        UNION ALL SELECT px_sample_n+1, (RANDOM()-0.5) * (fov_rad_x/res_x), (RANDOM()-0.5) * (fov_rad_y/res_y)\n                FROM px_sample_n, camera, img WHERE px_sample_n<camera.samples_per_px),\n     rs(img_x, img_y, sceneid, depth, max_ray_depth, samples_per_px, px_sample_n, color_mult,\n          ray_col_r, ray_col_g, ray_col_b,\n          x1, y1, z1,\n          dir_x, dir_y, dir_z,\n          dir_lensquared,\n          n_x, n_y, n_z, n_len,\n          stop_tracing, ray_len_idx, hit_sphereid, n_sphere_samples, inside_dielectric) AS\n        -- Send out initial set of rays from camera\n         (SELECT xs.u, ys.v, c.sceneid, -1, max_ray_depth, samples_per_px, px_sample_n, CAST(2.0 AS DOUBLE PRECISION),\n                CAST(NULL AS DOUBLE PRECISION), CAST(NULL AS DOUBLE PRECISION), CAST(NULL AS DOUBLE PRECISION),\n                 c.x, c.y, c.z,\n                 (SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u) /\n                    SQRT(((SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u)*(SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u) +\n                      (SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v)*(SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v) + 1.0)),\n                 (SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v) /\n                    SQRT(((SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u)*(SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u) +\n                      (SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v)*(SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v) + 1.0)),\n                 CAST(1.0 AS DOUBLE PRECISION) /\n                    SQRT(((SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u)*(SIN(c.rot_y-(fov_rad_x/2.0)+img_frac_x*fov_rad_x) + px_jitter_u) +\n                      (SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v)*(SIN(c.rot_x-(fov_rad_y/2.0)+img_frac_y*fov_rad_y) + px_jitter_v) + 1.0)),\n                 CAST(1.0 AS DOUBLE PRECISION),\n                 CAST(NULL AS DOUBLE PRECISION), CAST(NULL AS DOUBLE PRECISION), CAST(NULL AS DOUBLE PRECISION), CAST(NULL AS DOUBLE PRECISION),\n                 CAST(0 AS BOOLEAN), CAST(1 AS BIGINT), CAST(NULL AS INTEGER),\n                 (SELECT COUNT(*) FROM sphere_sample), FALSE\n              FROM camera c, img, xs, ys, px_sample_n\n        UNION ALL\n         -- Collide all rays with spheres\n          SELECT img_x, img_y, rs.sceneid, depth+1, max_ray_depth, samples_per_px, px_sample_n,\n                 (CASE WHEN norm_x IS NULL THEN 0.5 ELSE mirror_frac END)*color_mult,\n                 CASE WHEN discrim>0 THEN (CASE\n                                                WHEN shade_normal THEN mat_col_r*(1+norm_x)/2\n                                                ELSE mat_col_r\n                                           END)\n                     ELSE 1.0-(0.5*((dir_y/SQRT(dir_lensquared)+1.0)))+0.2*(0.5*((dir_y/SQRT(dir_lensquared)+1.0))) END,\n                 CASE WHEN discrim>0 THEN (CASE\n                                                WHEN shade_normal THEN mat_col_g*(1+norm_y)/2\n                                                ELSE mat_col_g\n                                           END)\n                     ELSE 1.0-(0.5*((dir_y/SQRT(dir_lensquared)+1.0)))+0.3*(0.5*((dir_y/SQRT(dir_lensquared)+1.0))) END,\n                 CASE WHEN discrim>0 THEN (CASE\n                                                WHEN shade_normal THEN mat_col_b*(1+norm_z)/2\n                                                ELSE mat_col_b\n                                           END)\n                     ELSE 1.0-(0.5*((dir_y/SQRT(dir_lensquared)+1.0)))+1.0*(0.5*((dir_y/SQRT(dir_lensquared)+1.0))) END,\n                 -- x1, y1, z1\n                 hit_x, hit_y, hit_z,\n                 -- dir_x, dir_y, dir_z\nCASE WHEN is_metal \n     THEN (dir_x - 2 * norm_x * dot_ray_norm) / reflection_len\n     WHEN is_dielectric AND must_reflect\n     THEN reflec_dir_x / final_dir_len\n     WHEN is_dielectric \n     THEN refrac_dir_x / final_dir_len\n     ELSE diffuse_dir_x/diffuse_dir_len\nEND,\nCASE WHEN is_metal \n     THEN (dir_y - 2 * norm_y * dot_ray_norm) / reflection_len\n     WHEN is_dielectric AND must_reflect\n     THEN reflec_dir_y / final_dir_len\n     WHEN is_dielectric \n     THEN refrac_dir_y / final_dir_len\n     ELSE diffuse_dir_y/diffuse_dir_len\nEND,\nCASE WHEN is_metal \n     THEN (dir_z - 2 * norm_z * dot_ray_norm) / reflection_len\n     WHEN is_dielectric AND must_reflect\n     THEN reflec_dir_z / final_dir_len\n     WHEN is_dielectric \n     THEN refrac_dir_z / final_dir_len\n     ELSE diffuse_dir_z/diffuse_dir_len\nEND,\n                 1.0,\n                 norm_x, norm_y, norm_z, 1.0,\n                 discrim IS NULL, ROW_NUMBER() OVER (PARTITION BY img_x, img_y, depth+1, px_sample_n\n                                                          ORDER BY t),\n                 sphereid, n_sphere_samples, (inside_dielectric AND NOT must_reflect) OR (NOT inside_dielectric AND NOT is_dielectric)\n           FROM rs\n           LEFT JOIN LATERAL\n               (SELECT s.*, discrim,\n                       CASE WHEN t_near > 0.001 THEN t_near ELSE t_far END AS t\n                         FROM sphere s,\n                              LATERAL (SELECT (x1-s.cx)*dir_x+(y1-s.cy)*dir_y+(z1-s.cz)*dir_z AS hb) hbv,\n                              LATERAL (SELECT hb*hb - ((x1-s.cx)*(x1-s.cx)+(y1-s.cy)*(y1-s.cy)+(z1-s.cz)*(z1-s.cz)-s.radius2)*dir_lensquared AS discrim) dv,\n                              LATERAL (SELECT (-hb - SQRT(discrim))/dir_lensquared AS t_near,\n                                              (-hb + SQRT(discrim))/dir_lensquared AS t_far) tv\n                         WHERE s.sceneid=rs.sceneid AND discrim > 0\n                       ) hit_sphere ON t>0.001\n           LEFT JOIN LATERAL\n               (SELECT x1+dir_x*t AS hit_x, y1+dir_y*t AS hit_y, z1+dir_z*t AS hit_z,\n                       x1+dir_x*t-cx AS norm_x_nonunit, y1+dir_y*t-cy AS norm_y_nonunit, z1+dir_z*t-cz AS norm_z_nonunit,\n                       SQRT((x1+dir_x*t-cx)*(x1+dir_x*t-cx)+(y1+dir_y*t-cy)*(y1+dir_y*t-cy)+(z1+dir_z*t-cz)*(z1+dir_z*t-cz)) AS norm_len,\n                       ROW_NUMBER() OVER (PARTITION BY img_x, img_y, depth, px_sample_n ORDER BY t ASC) AS t_idx\n                       WHERE t>0\n               ) sphere_normal ON t_idx=1\n           LEFT JOIN LATERAL\n               (SELECT CASE WHEN (norm_x_nonunit * dir_x + norm_y_nonunit * dir_y + norm_z_nonunit * dir_z) > 0\n                         THEN -norm_x_nonunit/norm_len \n                         ELSE norm_x_nonunit/norm_len END AS norm_x,\n                    CASE WHEN (norm_x_nonunit * dir_x + norm_y_nonunit * dir_y + norm_z_nonunit * dir_z) > 0\n                         THEN -norm_y_nonunit/norm_len \n                         ELSE norm_y_nonunit/norm_len END AS norm_y,\n                    CASE WHEN (norm_x_nonunit * dir_x + norm_y_nonunit * dir_y + norm_z_nonunit * dir_z) > 0\n                         THEN -norm_z_nonunit/norm_len \n                         ELSE norm_z_nonunit/norm_len END AS norm_z\n               ) sphere_unit_normal ON norm_x IS NOT NULL\n           LEFT JOIN LATERAL\n               (SELECT dir_x*norm_x + dir_y*norm_y + dir_z*norm_z AS dot_ray_norm,\n                       SQRT((dir_x - 2 * norm_x * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z)) * (dir_x - 2 * norm_x * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z)) +\n                       (dir_y - 2 * norm_y * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z)) * (dir_y - 2 * norm_y * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z)) +\n                       (dir_z - 2 * norm_z * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z)) * (dir_z - 2 * norm_z * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z))) AS reflection_len\n               ) dot_ray_norm ON norm_x IS NOT NULL\n           LEFT JOIN material ON material.materialid=hit_sphere.materialid\n           LEFT JOIN LATERAL\n               (SELECT x, y, z,\n                       x+norm_x AS diffuse_dir_x, y+norm_y AS diffuse_dir_y, z+norm_z AS diffuse_dir_z,\n                       SQRT((x+norm_x)*(x+norm_x)+(y+norm_y)*(y+norm_y)+(z+norm_z)*(z+norm_z)) AS diffuse_dir_len\n                FROM sphere_sample ss WHERE ss.sampleno=1+CAST(FLOOR(ABS((100000*dir_x)-FLOOR(100000*dir_x))*n_sphere_samples) AS INTEGER)\n               ) diffuse_scatter ON norm_x IS NOT NULL\n           LEFT JOIN LATERAL\n               (SELECT (CASE WHEN is_dielectric AND (norm_x_nonunit * dir_x + norm_y_nonunit * dir_y + norm_z_nonunit * dir_z) > 0\n                             THEN eta\n                             WHEN is_dielectric\n                             THEN 1.0/eta\n                             ELSE eta END) AS ir) index_of_refraction ON norm_x IS NOT NULL\nLEFT JOIN LATERAL\n    (SELECT ABS(dir_x*norm_x + dir_y*norm_y + dir_z*norm_z) AS cos_theta,\n            ((1.0-ir)/(1.0+ir))*((1.0-ir)/(1.0+ir)) AS r0\n    ) refract_cos_theta ON norm_x IS NOT NULL\nLEFT JOIN LATERAL\n    (SELECT \n        -- Discriminant for total internal reflection check\n        1.0 - ir*ir*(1.0-cos_theta*cos_theta) AS refrac_discriminant,\n        -- Correct refraction direction\n        ir * dir_x + (ir * cos_theta - SQRT(GREATEST(0.0, 1.0 - ir*ir*(1.0-cos_theta*cos_theta)))) * norm_x AS refrac_dir_x,\n        ir * dir_y + (ir * cos_theta - SQRT(GREATEST(0.0, 1.0 - ir*ir*(1.0-cos_theta*cos_theta)))) * norm_y AS refrac_dir_y,\n        ir * dir_z + (ir * cos_theta - SQRT(GREATEST(0.0, 1.0 - ir*ir*(1.0-cos_theta*cos_theta)))) * norm_z AS refrac_dir_z,\n        -- Fresnel reflectance\n        r0 + (1.0 - r0)*pow(1.0-cos_theta, 5) AS reflectance\n    ) refrac_vec ON norm_x IS NOT NULL\nLEFT JOIN LATERAL\n    (SELECT \n        dir_x - 2.0 * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z) * norm_x AS reflec_dir_x,\n        dir_y - 2.0 * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z) * norm_y AS reflec_dir_y,\n        dir_z - 2.0 * (dir_x*norm_x + dir_y*norm_y + dir_z*norm_z) * norm_z AS reflec_dir_z,\n        -- Total internal reflection check\n        (refrac_discriminant < 0) OR (reflectance > RANDOM()) AS must_reflect\n    ) reflec_vec ON norm_x IS NOT NULL\n    LEFT JOIN LATERAL\n    (SELECT \n        CASE WHEN must_reflect \n             THEN SQRT(reflec_dir_x*reflec_dir_x + reflec_dir_y*reflec_dir_y + reflec_dir_z*reflec_dir_z)\n             ELSE SQRT(refrac_dir_x*refrac_dir_x + refrac_dir_y*refrac_dir_y + refrac_dir_z*refrac_dir_z)\n        END AS final_dir_len\n    ) final_dir_len ON norm_x IS NOT NULL\n\n          LEFT JOIN LATERAL\n               (SELECT SQRT((reflec_dir_x+refrac_dir_x)*(reflec_dir_x+refrac_dir_x)+\n                       (reflec_dir_y+refrac_dir_y)*(reflec_dir_y+refrac_dir_y)+\n                       (reflec_dir_z+refrac_dir_z)*(reflec_dir_z+refrac_dir_z)) refrac_len\n               ) refrac_len ON norm_x IS NOT NULL\n              WHERE depth<max_ray_depth AND NOT stop_tracing AND ray_len_idx=1\n             )\n   SELECT * FROM rs WHERE ray_len_idx=1;\n-- select * from rays;\n\nDROP VIEW IF EXISTS do_render;\nCREATE VIEW do_render AS\n SELECT A.img_x, -A.img_y,\n         GREATEST(0.0, LEAST(1.0, SUM(POW(A.color_mult * COALESCE(A.ray_col_r, 0.0)/A.samples_per_px, gamma)))) col_r,\n         GREATEST(0.0, LEAST(1.0, SUM(POW(A.color_mult * COALESCE(A.ray_col_g, 0.0)/A.samples_per_px, gamma)))) col_g,\n         GREATEST(0.0, LEAST(1.0, SUM(POW(A.color_mult * COALESCE(A.ray_col_b, 0.0)/A.samples_per_px, gamma)))) col_b\n    FROM rays A, img\n     WHERE A.depth>=0\n    GROUP BY -A.img_y, A.img_x\n    ORDER BY -A.img_y, A.img_x;\n\nDROP VIEW IF EXISTS ppm;\nCREATE VIEW ppm AS\n WITH maxcol(mc) AS (SELECT 255)\n    SELECT 'P3'\n  UNION ALL\n    SELECT res_x || ' ' || res_y || ' ' || mc FROM img, maxcol\n  UNION ALL\n    SELECT CAST(col_r*mc AS INTEGER) || ' ' || CAST(col_g*mc AS INTEGER) || ' ' || CAST(col_b*mc AS INTEGER)\n      FROM do_render, maxcol;\n  ;\n\n-- SELECT * FROM rays WHERE img_x=2 AND img_y=2;\n"
  },
  {
    "path": "setup.sql",
    "content": "DROP TABLE IF EXISTS material CASCADE;\nCREATE TABLE material (materialid SERIAL PRIMARY KEY, name TEXT,\n  mat_col_r DOUBLE PRECISION, mat_col_g DOUBLE PRECISION, mat_col_b DOUBLE PRECISION,\n  is_metal BOOLEAN NOT NULL, shade_normal BOOLEAN NOT NULL, mirror_frac DOUBLE PRECISION NOT NULL,\n  is_dielectric BOOLEAN NOT NULL, eta DOUBLE PRECISION NOT NULL DEFAULT 1.0);\nINSERT INTO material (name, mat_col_r, mat_col_g, mat_col_b, is_metal, shade_normal, mirror_frac, is_dielectric, eta) VALUES\n    ('dark', 0.1, 0.1, 0.1, FALSE, FALSE, 0.1, FALSE, 1.0),\n    ('red', 0.95, 0.0, 0.0, FALSE, TRUE, 0.5, FALSE, 1.0),\n    ('green', 0.0, 0.95, 0.0, FALSE, TRUE, 0.5, FALSE, 1.0),\n    ('blue', 0.0, 0.0, 0.95, TRUE, TRUE, 0.5, FALSE, 1.0),\n    ('grey', 0.1, 0.1, 0.1, FALSE, FALSE, 0.5, FALSE, 1.0),\n    ('bright', 1.0, 1.0, 1.0, TRUE, TRUE, 0.5, FALSE, 1.0),\n    ('mirror', NULL, NULL, NULL, TRUE, FALSE, 0.99, FALSE, 1.0),\n    ('bluemirror', 0.0, 0.0, 0.3, TRUE, FALSE, 0.9, FALSE, 1.0),\n    ('greenmirror', 0.0, 0.2, 0.0, TRUE, FALSE, 0.9, FALSE, 1.0),\n    ('notquiteair', NULL, NULL, NULL, FALSE, FALSE, 1.0, TRUE, 1.00000001),\n    ('glass', NULL, NULL, NULL, FALSE, FALSE, 0.95, TRUE, 1.5),\n    ('greenglass', 0.0, 0.2, 0.0, FALSE, FALSE, 0.8, TRUE, 1.5),\n    ('diamond', NULL, NULL, NULL, FALSE, FALSE, 0.99, TRUE, 2.4),\n    ('antiglass', NULL, NULL, NULL, FALSE, FALSE, 0.99, TRUE, 0.2)\n;\n\nDROP TABLE IF EXISTS scene CASCADE;\nCREATE TABLE IF NOT EXISTS scene (sceneid SERIAL PRIMARY KEY,\n   scenename TEXT UNIQUE NOT NULL);\nINSERT INTO scene (scenename) VALUES ('dielectricparty'),\n                                     ('oneglassball'),\n                                     ('onediamondball'),\n                                     ('oneantiglassball'),\n                                     ('onegreyball'),\n                                     ('onegreenball'),\n                                     ('twomirrorballs'),\n                                     ('twodiffuseballs'),\n                                     ('onemirrorball'),\n                                     ('reflectiontest'),\n                                     ('threemirrors'),\n                                     ('adjacentballs'),\n                                     ('glassmatrix'),\n                                     ('airy'),\n                                     ('busyday');\n\nDROP TABLE IF EXISTS sphere CASCADE;\nCREATE TABLE sphere (sphereid SERIAL, sceneid INTEGER NOT NULL REFERENCES scene(sceneid),\n  cx DOUBLE PRECISION NOT NULL, cy DOUBLE PRECISION NOT NULL, cz DOUBLE PRECISION NOT NULL,\n  radius DOUBLE PRECISION, radius2 DOUBLE PRECISION, materialid INTEGER NOT NULL REFERENCES material(materialid) DEFERRABLE,\n  vel_x DOUBLE PRECISION NOT NULL DEFAULT 0.0, vel_y DOUBLE PRECISION NOT NULL DEFAULT 0.0, vel_z DOUBLE PRECISION NOT NULL DEFAULT 0.0,\n  coefficient_of_restitution DOUBLE PRECISION NOT NULL DEFAULT 1.0);\n\nINSERT INTO sphere (cx, cy, cz, radius, materialid, sceneid) VALUES\n(0, 24, -10, 5,\n   (SELECT materialid FROM material WHERE name='bright'), (SELECT sceneid FROM scene WHERE scenename='reflectiontest')),\n(0, 5, 0, 5,\n   (SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='reflectiontest')),\n(-17, 15, -30, 15,\n   (SELECT materialid FROM material WHERE name='bluemirror'), (SELECT sceneid FROM scene WHERE scenename='reflectiontest')),\n(24, 23, 10, 23,\n   (SELECT materialid FROM material WHERE name='greenmirror'), (SELECT sceneid FROM scene WHERE scenename='reflectiontest')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='twomirrorballs')),\n(20, 25, -30, 25,\n   (SELECT materialid FROM material WHERE name='greenmirror'), (SELECT sceneid FROM scene WHERE scenename='twomirrorballs')),\n(-20, 25, 0, 25,\n   (SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='twomirrorballs')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='twodiffuseballs')),\n(20, 25, -30, 25,\n   (SELECT materialid FROM material WHERE name='dark'), (SELECT sceneid FROM scene WHERE scenename='twodiffuseballs')),\n(-20, 25, 0, 25,\n   (SELECT materialid FROM material WHERE name='green'), (SELECT sceneid FROM scene WHERE scenename='twodiffuseballs')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='onemirrorball')),\n(-20, 25, 0, 25,\n   (SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='onemirrorball')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='oneglassball')),\n(0, 25, -10, 25,\n   (SELECT materialid FROM material WHERE name='glass'), (SELECT sceneid FROM scene WHERE scenename='oneglassball')),\n(20, 25, 80, 25,\n   (SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='oneglassball')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='onediamondball')),\n(0, 25, -10, 25,\n   (SELECT materialid FROM material WHERE name='diamond'), (SELECT sceneid FROM scene WHERE scenename='onediamondball')),\n(20, 25, 80, 25,\n   (SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='onediamondball')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='oneantiglassball')),\n(0, 25, -10, 25,\n   (SELECT materialid FROM material WHERE name='antiglass'), (SELECT sceneid FROM scene WHERE scenename='oneantiglassball')),\n(20, 25, 80, 25,\n   (SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='oneantiglassball')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),\n(0, 12, 0, NULL,\n   (SELECT materialid FROM material WHERE name='glass'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),\n(25, 12, 0, NULL,\n   (SELECT materialid FROM material WHERE name='antiglass'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),\n(-25, 12, 0, NULL,\n   (SELECT materialid FROM material WHERE name='diamond'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),\n(15, 10, 20, NULL,\n   (SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),\n(-5, 10, 30, NULL,\n   (SELECT materialid FROM material WHERE name='green'), (SELECT sceneid FROM scene WHERE scenename='dielectricparty')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='airy')),\n(0, 12, 0, NULL,\n   (SELECT materialid FROM material WHERE name='notquiteair'), (SELECT sceneid FROM scene WHERE scenename='airy')),\n(25, 12, 0, NULL,\n   (SELECT materialid FROM material WHERE name='notquiteair'), (SELECT sceneid FROM scene WHERE scenename='airy')),\n(-25, 12, 0, NULL,\n   (SELECT materialid FROM material WHERE name='notquiteair'), (SELECT sceneid FROM scene WHERE scenename='airy')),\n(15, 10, 20, NULL,\n   (SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='airy')),\n(-5, 10, 30, NULL,\n   (SELECT materialid FROM material WHERE name='green'), (SELECT sceneid FROM scene WHERE scenename='airy')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='green'), (SELECT sceneid FROM scene WHERE scenename='adjacentballs')),\n(-24, 12, 0, 12,\n   (SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='adjacentballs')),\n(0, 12, 0, 12,\n   (SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='adjacentballs')),\n(24, 12, 0, 12,\n   (SELECT materialid FROM material WHERE name='bright'), (SELECT sceneid FROM scene WHERE scenename='adjacentballs')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='onegreyball')),\n(20, 25, 0, 25,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='onegreyball')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='onegreenball')),\n(20, 25, 0, 25,\n   (SELECT materialid FROM material WHERE name='green'), (SELECT sceneid FROM scene WHERE scenename='onegreenball')),\n\n(-20, 15, -15, 22,\n   (SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='threemirrors')),\n(0, 0, 0, 5,\n   (SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='threemirrors')),\n(30, -15, 0, 25,\n   (SELECT materialid FROM material WHERE name='mirror'), (SELECT sceneid FROM scene WHERE scenename='threemirrors')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='busyday')),\n\n(0, -1250, 0, 1250,\n   (SELECT materialid FROM material WHERE name='grey'), (SELECT sceneid FROM scene WHERE scenename='glassmatrix')),\n(20, 25, 80, 25,\n   (SELECT materialid FROM material WHERE name='red'), (SELECT sceneid FROM scene WHERE scenename='glassmatrix'))\n;\n\nINSERT INTO sphere (cx, cy, cz, radius, materialid, sceneid, coefficient_of_restitution)\nSELECT (RANDOM()-0.5) * 100, 50 + RANDOM() * 50, (RANDOM()-0.5) * 100, RANDOM() * 5.0,\n       1+CAST((RANDOM()*(SELECT MAX(materialid)-1 FROM material)) AS INTEGER), (SELECT sceneid FROM scene WHERE scenename='busyday'),\n       0.7*RANDOM()+0.3\n    FROM generate_series(1, 20)\n    GROUP BY generate_series;\n\nINSERT INTO sphere (cx, cy, cz, radius, materialid, sceneid, coefficient_of_restitution)\nWITH params(r, x1, y1) AS (SELECT 5, -60, -15)\nSELECT x1 + 2 * r * x, y1 + 2 * r * y, -30, r,\n      (SELECT materialid FROM material WHERE name='glass'), (SELECT sceneid FROM scene WHERE scenename='glassmatrix'),\n      1.0\n    FROM generate_series(1, 11) X, generate_series(1, 11) Y, params;\n\nUPDATE sphere SET radius = cy WHERE radius IS NULL;\nUPDATE sphere SET radius2 = radius*radius WHERE radius2 IS NULL;\n\nDROP TABLE IF EXISTS camera CASCADE;\nCREATE TABLE camera (cameraid INTEGER PRIMARY KEY, sceneid INTEGER NOT NULL REFERENCES scene(sceneid),\n  x DOUBLE PRECISION NOT NULL, y DOUBLE PRECISION NOT NULL, z DOUBLE PRECISION NOT NULL,\n  rot_x DOUBLE PRECISION NOT NULL, rot_y DOUBLE PRECISION NOT NULL, rot_z DOUBLE PRECISION NOT NULL,\n  fov_rad_x DOUBLE PRECISION NOT NULL, fov_rad_y DOUBLE PRECISION NOT NULL,\n  max_ray_depth INTEGER NOT NULL, samples_per_px INTEGER NOT NULL);\nINSERT INTO camera (cameraid, x, y, z, rot_x, rot_y, rot_z, fov_rad_x, fov_rad_y, max_ray_depth, samples_per_px, sceneid)\n  VALUES (1.0, 0.0, 65.0, -120.0, -0.34, 0.0, 0.0, PI()/3.0, PI()/3.0,\n          30, 50, (SELECT sceneid FROM scene WHERE scenename='busyday'));\n\nDROP TABLE IF EXISTS img CASCADE;\nCREATE TABLE img (res_x INTEGER NOT NULL, res_y INTEGER NOT NULL, gamma DOUBLE PRECISION);\n    INSERT INTO img (res_x, res_y, gamma)\n        VALUES (650, 650, 1.0);\n\nCREATE OR REPLACE FUNCTION animate_spheres()\nRETURNS TRIGGER\nLANGUAGE PLPGSQL\nAS $$\n    BEGIN\n        UPDATE sphere SET vel_x = vel_x + NEW.grav_x*NEW.dt,\n                          vel_y = vel_y + NEW.grav_y*NEW.dt,\n                          vel_z = vel_z + NEW.grav_z*NEW.dt\n          WHERE sceneid=(SELECT sceneid FROM camera) AND cy>0;\n        UPDATE sphere SET vel_y = -vel_y*coefficient_of_restitution WHERE radius>cy\n          AND sceneid=(SELECT sceneid FROM camera);\n        UPDATE sphere SET cx=cx+vel_x*NEW.dt,\n                          cy=cy+vel_y*NEW.dt,\n                          cz=cz+vel_z*NEW.dt\n          WHERE sceneid=(SELECT sceneid FROM camera);\n        RETURN NEW;\n    END;\n$$ ;\n\nDROP VIEW IF EXISTS updateworld;\nCREATE VIEW updateworld AS (SELECT 0.0 AS dt, 0.0 AS grav_x, 0.0 AS grav_y, 0.0 AS grav_z);\nCREATE TRIGGER trig_update_world INSTEAD OF INSERT ON updateworld FOR EACH ROW\n    EXECUTE PROCEDURE animate_spheres();\n\n-- INSERT INTO updateworld (dt, grav_x, grav_y, grav_z) VALUES (0.1, 0.0, -9.8, 0.0);\n-- select cx, cy, cz, vel_x, vel_y, vel_z from sphere\n--   where sceneid=(SELECT sceneid FROM scene WHERE scenename='busyday');"
  },
  {
    "path": "show_scene.sh",
    "content": "#!/bin/sh\n\n. ./postgres_connection.sh\n\nshow_scene_dir=show_scene\n\nscenelist=scenelist.txt\n\npsql \\\n        --host=${PGHOST} \\\n        --port=${PGPORT} \\\n        --username=${PGUSER} \\\n        --dbname=${PGDB} \\\n        --file=setup.sql \\\n        --file=raytracer.sql \\\n        --command=\"\\\\timing\" \\\n        --command=\"\\\\copy (select scenename from scene) to './${show_scene_dir}/${scenelist}' csv\"\n\nmkdir -p ${show_scene_dir}\ncd ${show_scene_dir}\n\nwhile read scenename\ndo\n  echo \"\"\n  echo \"Rendering scene ${scenename}\"\n\n\npsql \\\n\t--host=${PGHOST} \\\n        --port=${PGPORT} \\\n        --username=${PGUSER} \\\n        --dbname=${PGDB} \\\n        --command=\"\\\\copy (select sphereid, cx, cy, cz, radius from sphere inner join scene s on s.sceneid=sphere.sceneid where s.scenename='${scenename}') to './spheres.csv' with csv header\" \\\n\nimg_size=2048\n\nscale_vector=5\n\ncat <<EOH > gnuplot.gp\nset terminal png size ${img_size},${img_size}\nset xrange [-60:60]\nset yrange [-60:60]\nset datafile separator ','\nset key autotitle columnhead\n\nset output '${scenename}_xz.png'\nset xlabel \"X\"\nset ylabel \"Z\"\nplot \\\\\n        \"spheres.csv\" u 2:4:5 w circles t 'spheres'\n\nset output '${scenename}_zx.png'\nset xlabel \"Z\"\nset ylabel \"X\"\nplot \\\\\n        \"spheres.csv\" u 4:2:5 w circles t 'spheres'\n\nset output '${scenename}_xy.png'\nset xlabel \"X\"\nset ylabel \"Y\"\nplot \\\\\n        \"spheres.csv\" u 2:3:5 w circles t 'spheres'\n\nset output '${scenename}_yx.png'\nset xlabel \"Y\"\nset ylabel \"X\"\nplot \\\\\n        \"spheres.csv\" u 3:2:5 w circles t 'spheres'\n\nset output '${scenename}_yz.png'\nset xlabel \"Y\"\nset ylabel \"Z\"\nplot \\\\\n        \"spheres.csv\" u 3:4:5 w circles t 'spheres'\n\nset output '${scenename}_zy.png'\nset xlabel \"Z\"\nset ylabel \"Y\"\nplot \\\\\n        \"spheres.csv\" u 4:3:5 w circles t 'spheres'\n\nset output '${scenename}_3view.png'\nset xrange [-120:120]\nset yrange [-120:120]\nset zrange [-120:120]\nset xlabel \"X\"\nset ylabel \"Y\"\nset zlabel \"Z\"\nsplot \\\\\n\t\"spheres.csv\" u 2:3:4:5 w circles t 'spheres'\n\nset terminal qt\nreplot\n\nEOH\n\ngnuplot gnuplot.gp\n\nmontage \\\n\t-tile 2x2 \\\n\t-geometry ${img_size}x${img_size} \\\n\t${scenename}_xy.png \\\n       \t${scenename}_zy.png \\\n       \t${scenename}_xz.png \\\n       \t${scenename}_3view.png \\\n\tfullscene_${scenename}.png\n\n\ndone < ./${scenelist}\n\nxdg-open .\n\n"
  }
]