)
}
}
)
(
}
{
)
)
(
)
(
(
{
}
)
(
)
}
)
)
{
(
(
)
)
}
)
(
}

3d Hypocycloid Shape

  1. (() => {
  2.   const m = new Float32Array([
  3.     0, 0, 0, 0, 
  4.     0, 0, 0, 0, 
  5.     0, 0, 0, 0, 
  6.     0, 0, 0, 0
  7.   ])
  8.  
  9.   const pointSize = Math.max(Math.min(10, ~~(innerWidth / 100)), 3);
  10.  
  11.   const vert = `
  12.     attribute vec3 vec;
  13.     uniform mat4 mat;
  14.     void main(void) {
  15.       gl_Position = mat * vec4(vec, 1.);
  16.       gl_PointSize = ${pointSize}.;
  17.     }
  18.   `
  19.  
  20.   const frag = `
  21.     void main(void) {
  22.       gl_FragColor = vec4(1., 1., 1., .25);
  23.     }
  24.   `
  25.  
  26.   document.body.style.background = '#232323'
  27.   const gl = document.body
  28.     .appendChild(document.createElement('canvas'))
  29.     .getContext('webgl')
  30.  
  31.   Object.assign(gl.canvas.style, {
  32.     position: 'absolute',
  33.     left: '50%',
  34.     top: '50%',
  35.     transform: 'translate(-50%, -50%)',
  36.     outline: '1px solid gray'
  37.   })
  38.  
  39.   with (gl) {
  40.     const NUM = 600
  41.     const TWO_PI = Math.PI * 2;
  42.     const verts = []
  43.  
  44.     let step =.05
  45.     let n = 8
  46.     let xp = 0
  47.     let yp = 0
  48.     let a = 12
  49.     let t = 0
  50.     let scl = .005;
  51.  
  52.     for (var i = 0; i < TWO_PI; i += step) {
  53.       for (var j = 0; j < TWO_PI; j += step) {
  54.         // unoptimized for readability
  55.         let cosi = a/n * ((n - 1) * Math.cos(i) + Math.cos(Math.abs((n - 1) * i)))
  56.         let sini = a/n * ((n - 1) * Math.sin(i) - Math.sin(Math.abs((n - 1) * i)))
  57.         let cosj = a/n * ((n - 1) * Math.cos(j) + Math.cos(Math.abs((n - 1) * j)))
  58.         let sinj = a/n * ((n - 1) * Math.sin(j) - Math.sin(Math.abs((n - 1) * j)))
  59.         verts.push(cosi * cosj * scl)
  60.         verts.push(sini * cosj * scl)
  61.         verts.push(a * sinj * scl)
  62.       }
  63.     }
  64.  
  65.     const leng = verts.length / 3
  66.     bindBuffer(ARRAY_BUFFER, createBuffer())
  67.     bufferData(ARRAY_BUFFER, new Float32Array(verts), STATIC_DRAW)
  68.  
  69.     const vs = createShader(VERTEX_SHADER)
  70.     shaderSource(vs, vert)
  71.     compileShader(vs)
  72.  
  73.     const fs = createShader(FRAGMENT_SHADER)
  74.     const sp = createProgram()
  75.  
  76.     shaderSource(fs, frag)
  77.     compileShader(fs)
  78.     attachShader(sp, vs)
  79.     attachShader(sp, fs)
  80.     linkProgram(sp)
  81.     useProgram(sp)
  82.  
  83.     const vec = getAttribLocation(sp, 'vec')
  84.     vertexAttribPointer(vec, 3, FLOAT, false, 0, 0)
  85.     enableVertexAttribArray(vec)
  86.  
  87.     const matLoc = getUniformLocation(sp, 'mat')
  88.  
  89.     function rot(x, y, z) {
  90.       // https://wikimedia.org/api/rest_v1/media/math/render/svg/a8e16f4967571b7a572d1a19f3f6468512f9843e
  91.  
  92.       const sinA = Math.sin(x)
  93.       const cosA = Math.cos(x)
  94.       const sinB = Math.sin(y)
  95.       const cosB = Math.cos(y)
  96.       const sinY = Math.sin(z)
  97.       const cosY = Math.cos(z)
  98.  
  99.       m[0] = cosA * cosB
  100.       m[1] = cosA * sinB * sinY - sinA * cosY
  101.       m[2] = cosA * sinB * cosY + sinA * sinY
  102.       m[3] = 0
  103.  
  104.       m[4] = sinA * cosB
  105.       m[5] = sinA * sinB * sinY + cosA * cosY
  106.       m[6] = sinA * sinB * cosY - cosA * sinY
  107.       m[7] = 0
  108.  
  109.       m[8] = -sinB
  110.       m[9] = cosB * sinY
  111.       m[10] = cosB * cosY
  112.       m[11] = m[12] = m[13] = 0
  113.       m[15] = 1
  114.  
  115.       uniformMatrix4fv(matLoc, false, m)
  116.     }
  117.  
  118.     onresize = () => {
  119.       const { canvas } = gl
  120.       const size = Math.min(innerWidth, innerHeight) - 20
  121.       canvas.width = canvas.height = size
  122.       viewport(0, 0, size, size)
  123.     }
  124.  
  125.     onresize()
  126.  
  127.     let rx = 0, ry = 0, rz = 0
  128.  
  129.     disable(DEPTH_TEST)
  130.     enable(BLEND)
  131.     blendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
  132.  
  133.     function loop() {
  134.       rot(rx += .01, ry -= 0.03, rz += 0.01)
  135.  
  136.       clearColor(0, 0, 0, 1)
  137.       clear(COLOR_BUFFER_BIT)
  138.       drawArrays(POINTS, 0, leng)
  139.       window.requestAnimationFrame(loop)
  140.     }
  141.     loop()
  142.   }
  143. })()

WebGL plot of a hypocycloid shape. I enjoy using with here…

WebGL Points and Rotation

  1. (() => {
  2.   const m = new Float32Array([
  3.     0, 0, 0, 0, 
  4.     0, 0, 0, 0, 
  5.     0, 0, 0, 0, 
  6.     0, 0, 0, 0
  7.   ])
  8.  
  9.   const pointSize = Math.max(Math.min(10, ~~(innerWidth / 70)), 3);
  10.  
  11.   const vert = `
  12.     attribute vec3 vec;
  13.     uniform mat4 mat;
  14.     void main(void) {
  15.       gl_Position = mat * vec4(vec, 1.);
  16.       gl_PointSize = ${pointSize}.;
  17.     }
  18.   `
  19.  
  20.   const frag = `
  21.     void main(void) {
  22.       gl_FragColor = vec4(1., 1., 1., .4);
  23.     }
  24.   `
  25.  
  26.   document.body.style.background = '#333'
  27.   const gl = document.body
  28.     .appendChild(document.createElement('canvas'))
  29.     .getContext('webgl')
  30.  
  31.   Object.assign(gl.canvas.style, {
  32.     position: 'absolute',
  33.     left: '50%',
  34.     top: '50%',
  35.     transform: 'translate(-50%, -50%)',
  36.     outline: '1px solid gray'
  37.   })
  38.  
  39.   with (gl) {
  40.     const NUM = 600
  41.     const verts = []
  42.  
  43.     for (let i = 0; i < NUM; i++) {
  44.       verts.push(
  45.         Math.random() * 1 - 0.5,
  46.         Math.random() * 1 - 0.5,
  47.         Math.random() * 1 - 0.5
  48.       )
  49.     }
  50.  
  51.     const leng = verts.length / 3
  52.     bindBuffer(ARRAY_BUFFER, createBuffer())
  53.     bufferData(ARRAY_BUFFER, new Float32Array(verts), STATIC_DRAW)
  54.  
  55.     const vs = createShader(VERTEX_SHADER)
  56.     shaderSource(vs, vert)
  57.     compileShader(vs)
  58.  
  59.     const fs = createShader(FRAGMENT_SHADER)
  60.     const sp = createProgram()
  61.  
  62.     shaderSource(fs, frag)
  63.     compileShader(fs)
  64.     attachShader(sp, vs)
  65.     attachShader(sp, fs)
  66.     linkProgram(sp)
  67.     useProgram(sp)
  68.  
  69.     const vec = getAttribLocation(sp, 'vec')
  70.     vertexAttribPointer(vec, 3, FLOAT, false, 0, 0)
  71.     enableVertexAttribArray(vec)
  72.  
  73.     const matLoc = getUniformLocation(sp, 'mat')
  74.  
  75.     function rot(x, y, z) {
  76.       // https://wikimedia.org/api/rest_v1/media/math/render/svg/a8e16f4967571b7a572d1a19f3f6468512f9843e
  77.  
  78.       const sinA = Math.sin(x)
  79.       const cosA = Math.cos(x)
  80.       const sinB = Math.sin(y)
  81.       const cosB = Math.cos(y)
  82.       const sinY = Math.sin(z)
  83.       const cosY = Math.cos(z)
  84.  
  85.       m[0] = cosA * cosB
  86.       m[1] = cosA * sinB * sinY - sinA * cosY
  87.       m[2] = cosA * sinB * cosY + sinA * sinY
  88.       m[3] = 0
  89.  
  90.       m[4] = sinA * cosB
  91.       m[5] = sinA * sinB * sinY + cosA * cosY
  92.       m[6] = sinA * sinB * cosY - cosA * sinY
  93.       m[7] = 0
  94.  
  95.       m[8] = -sinB
  96.       m[9] = cosB * sinY
  97.       m[10] = cosB * cosY
  98.       m[11] = m[12] = m[13] = 0
  99.       m[15] = 1
  100.  
  101.       uniformMatrix4fv(matLoc, false, m)
  102.     }
  103.  
  104.     onresize = () => {
  105.       const { canvas } = gl
  106.       const size = Math.min(innerWidth, innerHeight) - 20
  107.       canvas.width = canvas.height = size
  108.       viewport(0, 0, size, size)
  109.     }
  110.  
  111.     onresize()
  112.  
  113.     let ry = 0, rz = 0
  114.  
  115.     disable(DEPTH_TEST)
  116.     enable(BLEND)
  117.     blendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
  118.  
  119.     function loop() {
  120.       rot(0, ry -= 0.03, rz += 0.001)
  121.  
  122.       clearColor(0, 0, 0, 1)
  123.       clear(COLOR_BUFFER_BIT)
  124.       drawArrays(POINTS, 0, leng)
  125.       window.requestAnimationFrame(loop)
  126.     }
  127.     loop()
  128.   }
  129. })()

Some WebGL points with rotation… I couldn’t resist using with here…

SVG getScreenCTM

  1. const el = document.body.appendChild(
  2.   document.createElement`div`
  3. );
  4. el.innerHTML = `
  5. <svg width="200" height="200" viewBox="0 0 200 200">
  6.   <rect 
  7.     class="rect"
  8.     transform="translate(50, 50) scale(1.2) rotate(25)"
  9.     fill="purple"
  10.     x="0" y="0" width="50" height="50" />
  11. </svg>
  12. `;
  13.  
  14. const box = document.body.appendChild(
  15.   document.createElement`div`
  16. );
  17.  
  18. Object.assign(box.style, {
  19.   position: 'absolute',
  20.   left: 0, top: 0,
  21.   width: '50px',
  22.   height: '50px',
  23.   transformOrigin: '0 0',
  24.   outline: '5px solid red'
  25. });
  26.  
  27. const rect = document.querySelector('.rect');
  28. const {a, b, c, d, e, f} = rect.getScreenCTM()
  29.  
  30. box.style.transform = `
  31.   matrix(${[a, b, c, d, e, f]})
  32. `;

The transformation matrix of an SVG element can be obtained using getScreenCTM or getCTM. The latter of which will be relative to the SVG coordinate space, vs the coordinate space of the page.

Here we take the matrix data from getScreenCTM and use it on a div to place a border over an SVG rect node. This is great for layering HTML on top of SVG.

// dom // javascript // math // matrix // svg // tricks // ui

Creative Coding Auto-Painting

  1. Object.getOwnPropertyNames(Math).map(i => (this[i] = Math[i]))
  2. ;((
  3.   width = innerWidth * 2,
  4.   height = innerHeight * 2,
  5.   cnv = document.body.appendChild(
  6.     Object.assign(document.createElement('canvas'), {
  7.       width,
  8.       height
  9.     })
  10.   ),
  11.   c = cnv.getContext('2d'),
  12.   r = (n = 1) => Math.random() * n,
  13.   NUM = 50,
  14.   f = () => ({
  15.     ax: r(width),
  16.     ay: r(height),
  17.     x: 0,
  18.     y: 0,
  19.     T: r(9),
  20.     R: r(innerWidth * 0.8) + 40,
  21.     t: r(6),
  22.     C: round(r(255)),
  23.     m: r(5) + 1
  24.   }),
  25.   cs,
  26.   sn,
  27.   dx,
  28.   dy,
  29.   ns = [...Array(NUM)].map(f)
  30. ) => {
  31.   Object.assign(cnv.style, {
  32.     transformOrigin: '0 0',
  33.     transform: 'scale(.5)'
  34.   })
  35.   Object.assign(document.body.style, {
  36.     margin: 0,
  37.     padding: 0
  38.   })
  39.  
  40.   const clear = () => {
  41.     c.fillStyle = '#666668'
  42.     c.fillRect(0, 0, width, height)
  43.     c.globalAlpha = 0.5
  44.   }
  45.  
  46.   onresize = () => {
  47.     width = cnv.width = innerWidth * 2
  48.     height = cnv.height = innerHeight * 2
  49.     clear()
  50.   }
  51.  
  52.   clear()
  53.  
  54.   setInterval(() => {
  55.     for (i = 0; i < 30; i++) {
  56.       ns.map((n, i) => {
  57.         with (n) {
  58.           x = ax + R * cos(t)
  59.           y = ay + R * sin(t) * pow(sin(t * 0.5), m)
  60.           c.fillStyle = `rgba(${C},${C},${C},.02)`
  61.           ;(cs = cos(T)), (sn = sin(T)), (dx = x - ax), (dy = y - ay)
  62.           c.fillRect(cs * dx - sn * dy + ax, sn * dx + cs * dy + ay, 50, 50)
  63.           t += 0.1
  64.           R -= 0.01
  65.           if (R < 5) ns[i] = f()
  66.         }
  67.       })
  68.     }
  69.   }, 16)
  70. })()

Speed coded semi-golfed canvas texture. Best if viewed in fullscreen.

Line to Line Intersection

  1. // line to line intersection https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
  2. function lineIntersection(p1, p2, p3, p4) {
  3.   let ip = { x: 0, y: 0 };
  4.   // rare case where I use `_` in a non-constant variable name
  5.   // to make this easy to read to with the wikipedia info
  6.   const x4_x3 = p4.x - p3.x;
  7.   const y4_y3 = p4.y - p3.y;
  8.   const x2_x1 = p2.x - p1.x;
  9.   const y2_y1 = p2.y - p1.y;
  10.   const x1_x3 = p1.x - p3.x;
  11.   const y1_y3 = p1.y - p3.y;
  12.   let nx, ny, dn;
  13.  
  14.   nx = x4_x3 * y1_y3 - y4_y3 * x1_x3;
  15.   ny = x2_x1 * y1_y3 - y2_y1 * x1_x3;
  16.   dn = y4_y3 * x2_x1 - x4_x3 * y2_y1;
  17.  
  18.   nx /= dn;
  19.   ny /= dn;
  20.  
  21.   // has intersection
  22.   if (nx >= 0 && nx <= 1 && ny >= 0 && ny <= 1) {
  23.     ny = p1.y + nx * y2_y1;
  24.     nx = p1.x + nx * x2_x1;
  25.     ip.x = nx;
  26.     ip.y = ny;
  27.   } else {
  28.     // no intersection
  29.     ip = null;
  30.   }
  31.   return ip;
  32. }
  33.  
  34. const el = document.body.appendChild(
  35.   document.createElement('div'));
  36.  
  37. // hard coding line values for simplicity and ease of understanding
  38. el.innerHTML = `
  39.   <svg width="100%" height="100%" viewBox="0 0 550 496">
  40.     <path id='path' d="M 10 10 L 300 300 M 100 10 L 160 320" stroke="black" fill='none' vector-effect="non-scaling-stroke"/>
  41.     <rect id="intersecton" x="0" y="0" width="10" height="10" fill="red" />
  42.   </svg>
  43.   <style>
  44.     svg, div, body, html {
  45.       overflow: visible; 
  46.       height: 100%; 
  47.       width: 100%;
  48.       margin: 0; padding: 0;
  49.     }
  50.   </style>
  51. `;
  52.  
  53. const loc = lineIntersection(
  54.   {x: 10, y: 10}, {x: 300, y:300},
  55.   {x: 100, y: 10}, {x: 160, y:320}
  56. );
  57.  
  58. // subtract half the size of the rect from both axis to center it
  59. intersecton.x.baseVal.value = loc.x - 5;
  60.  
  61. // @NOTE: using the `id` global `window.intersection` 
  62. // is just good for demos - little risky for real stuff 
  63. // since it lends itself to easy collision
  64. window.intersecton.y.baseVal.value = loc.y - 5;

Line to line intersection rendered with SVG.

snippet.zone ~ 2021-24 /// {s/z}