34

SQL 3d engine (interactive preview)

 5 years ago
source link: https://www.tuicool.com/articles/hit/JNZz63j
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

SQL 3d engine (interactive preview)

SQLite query:

preHTML(`WITH RECURSIVE numbers AS (
  SELECT 0 AS n UNION ALL SELECT n+1 FROM numbers WHERE n<${Math.max(c.maxCol, c.maxRow)}
),
pixels AS (
  SELECT rows.n as row, cols.n as col
  FROM numbers as rows CROSS JOIN numbers as cols
  WHERE rows.n > ${c.minRow - 1} AND rows.n < ${c.maxRow} AND cols.n > ${c.minCol - 1} AND cols.n < ${c.maxCol}
), rawRays AS (
  SELECT
    row, col,
    ${(c.x0 - c.cols / 2 * c.scaleX * c.ux - c.rows / 2 * c.scaleY * c.vx).toFixed(4)} + col * ${(c.scaleX * c.ux).toFixed(4)} + row * ${(c.scaleY * c.vx).toFixed(4)} as x,
    ${(c.y0 - c.rows / 2 * c.scaleY * c.vy).toFixed(4)} + row * ${(c.scaleY * c.vy).toFixed(4)} as y,
    ${(c.z0 - c.cols / 2 * c.scaleX * c.uz - c.rows / 2 * c.scaleY * c.vz).toFixed(4)} + col * ${(c.scaleX * c.uz).toFixed(4)} + row * ${(c.scaleY * c.vz).toFixed(4)} as z
  FROM pixels
), normsSq AS (
  SELECT row, col, x, y, z, x * x + y * y + z * z AS n2 FROM rawRays
), norms AS (
  SELECT row, col, x, y, z, ${sqrt1SQL('n2', '1')} as n FROM normsSq
), rays AS (
  SELECT row, col, x / n AS x, y / n AS y, z / n AS z FROM norms
), iters AS (
  SELECT row, col, 0 as it, 0 as v FROM rays
  UNION ALL
  SELECT rays.row, rays.col, it + 1 AS it,
    v + ${formula(r.x, r.y, r.z)} AS v
  FROM iters JOIN rays ON rays.row = iters.row AND rays.col = iters.col
  WHERE it < ${c.iters}
), lastIters AS (
  SELECT it0.row, it0.col, it0.v AS v0, it1.v AS v1, it2.v AS v2
  FROM iters as it0
    JOIN iters AS it1 ON it0.row = it1.row AND it0.col = it1.col
    JOIN iters AS it2 ON it0.row = it2.row AND it0.col = it2.col
  WHERE it0.it = ${c.iters} AND it1.it = ${c.iters - 1} AND it2.it = ${c.iters - 2}
), res AS (
  SELECT col, (v0 - v1) / (v1 - v2) as v FROM lastIters
)
SELECT group_concat(
  substr('${ascii}', round(1 + max(0, min(${ascii.length - 1}, v * ${ascii.length}))), 1)
  || CASE WHEN col = ${c.maxCol - 1} THEN X'0A' ELSE '' END
, '')
FROM res;
`)
viewof alpha = slider({
  min: -180, 
  max: 180, 
  step: 1, 
  value: -55, 
  title: "alpha",
  description: "horizontal rotation"
})
viewof beta = slider({
  min: -90, 
  max: 90, 
  step: 1, 
  value: 30, 
  title: "beta",
  description: "vertical rotation"
})
viewof dist = slider({
  min: 0.5, 
  max: 2.5, 
  step: 0.1, 
  value: 1.5, 
  title: "dist",
  description: "distance to camera"
})
viewof fov = slider({
  min: 1, 
  max: 100, 
  step: 1, 
  value: 26, 
  title: "fov",
  description: "camera field of view"
})
{
  const sqIt = (e, i) => (i + e / i) / 2;
  const sqrt = 0 ? x => Math.sqrt(x) : x => sqIt(x, sqIt(x, sqIt(x, 1)));
  const sqrt1a = (x) => (1 + x) / 2;
  const sqrt1b = (x) => 0.14 + 1.78 * x;
  const sqrt1 = (x, init=1) => sqIt(x, init);
  const sqrt2 = (x, init=1) => sqIt(x, sqIt(x, init));
  const fun = 1 ? (x, y, z) => Math.max(
    Math.abs(x) - 0.3, Math.abs(y) - 0.3, Math.abs(z) - 0.3,
    -sqrt1b(x * x + y * y + z * z) + 0.42
  ) : (x, y, z) => Math.max(Math.abs(x), Math.abs(y), Math.abs(z)) - 0.3;
  const iterCount = c.iters;
  let res = '\n';
  for (let row=c.minRow; row<c.maxRow; row++) {
    for (let col=c.minCol; col<c.maxCol; col++) {
      let dist = 0;
      const x = c.x0 + (col - c.cols / 2) * c.ux * c.scaleX + (row - c.rows / 2) * c.vx * c.scaleY;
      const y = c.y0 + (row - c.rows / 2) * c.vy * c.scaleY;
      const z = c.z0 + (col - c.cols / 2) * c.uz * c.scaleX + (row - c.rows / 2) * c.vz * c.scaleY;
      const n = sqrt1a(x * x + y * y + z * z);
      const [nx, ny, nz] = [x / n, y / n, z / n];
      const I = [];
      for (let i=0; i<=iterCount; i++) {
        dist += fun(c.camX + nx * dist, c.camY + ny * dist, c.camZ + nz * dist);
        I[i] = dist;
      }
      const R0 = (I[iterCount] - I[iterCount - 1]) / (I[iterCount - 1] - I[iterCount - 2]);
      const r = Math.min(1, Math.max(0, R0 + colorShift)) ** gamma;
      const R = 1 - (I[iterCount - 1] - I[iterCount - 2] < 0.00000000000001 ? 0.09 : Math.max(0, Math.min(1, R0)));
      const ch = ('' + ascii[Math.round(r * (ascii.length - 1))])[0];
      res += ch;
      // res += R0 > 0.3 ? '#' : ' ';
    }
    res += '\n';
  };
  return preCanvas(res);
}

Explanation article (russian): https://habr.com/post/435390/

You may also want to check out the same thing for Excel: https://beta.observablehq.com/@pallada-92/excel-3d-engine-emulator

preHTML(range(80).map(() => '-').join(''))
viewof sqInit = slider({
  min: 0.1, 
  max: 1.0, 
  step: 0.01, 
  value: 0.28, 
  title: "sqInit",
  description: "initial value of square root"
})
viewof gamma = slider({
  min: 0.0, 
  max: 2.0, 
  step: 0.1, 
  value: 1.0, 
  title: "gamma",
  description: "gamma correction"
})
viewof colorShift = slider({
  min: -1.0, 
  max: 1.0, 
  step: 0.1, 
  value: 0, 
  title: "colorShift",
  description: "color shift"
})
c = ({
  iters: 15,
  minRow: 3,
  maxRow: 40,
  minCol: 0,
  maxCol: 80 + 9,
  rows: 36 + 7,
  cols: 80 + 10,
  scaleX: 0.6,
  scaleY: 1.5,
  camX: +(dist * cos(alpha) * cos(beta)).toFixed(camPrecision),
  camY: +(dist * sin(beta)).toFixed(camPrecision),
  camZ: +(dist * sin(alpha) * cos(beta)).toFixed(camPrecision),
  ux: -pixelSize * sin(alpha),
  uz: +pixelSize * cos(alpha),
  vx: +pixelSize * cos(alpha) * sin(beta),
  vy: -pixelSize * cos(beta),
  vz: +pixelSize * sin(alpha) * sin(beta),
  x0: -cos(alpha) * cos(beta),
  y0: -sin(beta),
  z0: -sin(alpha) * cos(beta),
})
formula = (x, y, z) => `MAX(ABS(${x}) - 0.3, ABS(${y}) - 0.3, ABS(${z}) - 0.3, -${sqrt1SQL(`((${x}) * (${x}) + (${y}) * (${y}) + (${z}) * (${z}))`, sqInit.toString())} + 0.42)`
Internals
camPrecision = 2
preCanvas = txt => {
  const ctx = DOM.context2d(width, width);
  const fontSize = Math.round(width / 60);
  ctx.font = `${fontSize}px monospace`;
  ctx.fillStyle = 'black';
  txt.split('\n').map((line, no) => ctx.fillText(line, 5, 5 + no * fontSize * 1.4));
  return ctx.canvas;
}
preHTML = txt => html`<pre>${txt.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')}</pre>`;
cos = x => Math.cos(x * Math.PI / 180)
sin = x => Math.sin(x * Math.PI / 180)
tan = x => Math.tan(x * Math.PI / 180)
rows = 36
pixelSize = tan(fov / 2) / ((rows - 1) / 2)
ascii = `$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,"^. `
rangeSQL = (name, size) => `${name} AS (SELECT 0 AS v UNION ALL SELECT v+1 FROM ${name} WHERE v<${size})`
sqrt1SQL = (expr, init) => init == 1 ? `(${init} + ${expr}) / 2.0` : `(${init} + ${expr} / ${init}) / 2.0`
sqrt2SQL = (expr, init) => sqrt1SQL(expr, '(' + sqrt1SQL(expr, init) + ')')
sqrt3SQL = (expr, init) => sqrt1SQL(expr, '(' + sqrt1SQL(expr, '(' + sqrt1SQL(expr, init) + ')') + ')')
r = ({
  x: `${c.camX}+v*x`,
  y: `${c.camY}+v*y`,
  z: `${c.camZ}+v*z`,
})
range = n => {
  const res = [];
  for (let i=0; i<n; i++) {
    res.push(i);
  }
  return res;
}
function recurIter(n) {
  return `SELECT row, col, nx, ny, nz, v + ${formula(r.x, r.y, r.z)} as v FROM ${n == 0 ? 'iter0' : '(\n' + recurIter(n - 1) + ')\n'}`
}
import {slider, select} from "@jashkenas/inputs"

Shorter, but slower version, I don't know, why:

preHTML(`WITH RECURSIVE numbers AS (
  SELECT 0 AS n UNION ALL SELECT n+1 FROM numbers WHERE n<${Math.max(c.maxCol, c.maxRow)}
),
pixels AS (
  SELECT rows.n as row, cols.n as col
  FROM numbers as rows CROSS JOIN numbers as cols
  WHERE rows.n > ${c.minRow - 1} AND rows.n < ${c.maxRow} AND cols.n > ${c.minCol - 1} AND cols.n < ${c.maxCol}
), rawRays AS (
  SELECT
    row * 100 + col AS no,
    ${(c.x0 - c.cols / 2 * c.scaleX * c.ux - c.rows / 2 * c.scaleY * c.vx).toFixed(4)} + col * ${(c.scaleX * c.ux).toFixed(4)} + row * ${(c.scaleY * c.vx).toFixed(4)} as x,
    ${(c.y0 - c.rows / 2 * c.scaleY * c.vy).toFixed(4)} + row * ${(c.scaleY * c.vy).toFixed(4)} as y,
    ${(c.z0 - c.cols / 2 * c.scaleX * c.uz - c.rows / 2 * c.scaleY * c.vz).toFixed(4)} + col * ${(c.scaleX * c.uz).toFixed(4)} + row * ${(c.scaleY * c.vz).toFixed(4)} as z
  FROM pixels
), normsSq AS (
  SELECT no, x, y, z, x * x + y * y + z * z AS n2 FROM rawRays
), norms AS (
  SELECT no, x, y, z, ${sqrt1SQL('n2', '1')} as n FROM normsSq
), rays AS (
  SELECT no, x / n AS x, y / n AS y, z / n AS z FROM norms
), iters AS (
  SELECT no, 0 as it, 0 as v FROM rays
  UNION ALL
  SELECT rays.no, it + 1 AS it, v + ${formula(r.x, r.y, r.z)} AS v
  FROM iters JOIN rays ON rays.no = iters.no
  WHERE it < ${c.iters}
), lastIters AS (
  SELECT it0.no, it0.v AS v0, it1.v AS v1, it2.v AS v2
  FROM iters as it0
    JOIN iters AS it1 ON it0.no = it1.no
    JOIN iters AS it2 ON it0.no = it2.no
  WHERE it0.it = ${c.iters} AND it1.it = ${c.iters - 1} AND it2.it = ${c.iters - 2}
), res AS (
  SELECT no, (v0 - v1) / (v1 - v2) as v FROM lastIters
)
SELECT group_concat(
  substr('${ascii}', round(1 + max(0, min(${ascii.length - 1}, v * ${ascii.length}))), 1)
  || CASE WHEN no % 1000 = 0 THEN X'0A' ELSE '' END
, '')
FROM res;
`)

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK