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

Faster than save/restore HTML5 canvas

  1. const canvas = document.createElement('canvas')
  2. const c = canvas.getContext('2d')
  3.  
  4. canvas.width = innerWidth
  5. canvas.height = innerHeight
  6.  
  7. document.body.append(canvas)
  8.  
  9. c.fillStyle = 'black'
  10. c.fillRect(0, 0, canvas.width, canvas.height)
  11.  
  12. class Shooter {
  13.   constructor() {
  14.     this.x = innerWidth / 2
  15.     this.y = innerHeight / 2
  16.     this.vx = Math.random() * 10 - 5
  17.     this.vy = Math.random() * 10 - 5
  18.     this.color = 'rgba(255, 0, 0, 0.5)'
  19.     this.size = 10
  20.     this.halfSize = this.size / 2
  21.   }
  22.   draw() {
  23.     this.x += this.vx
  24.     this.y += this.vy
  25.  
  26.     if (this.x < 0) {
  27.       this.x = innerWidth
  28.     } else if (this.x > innerWidth) {
  29.       this.x = 0
  30.     }
  31.  
  32.     if (this.y < 0) {
  33.       this.y = innerHeight
  34.     } else if (this.y > innerHeight) {
  35.       this.y = 0
  36.     }
  37.  
  38.     c.fillStyle = this.color
  39.     c.translate(this.x, this.y)
  40.     c.fillRect(-this.halfSize, -this.halfSize, this.size, this.size)
  41.  
  42.     c.setTransform(1, 0, 0, 1, 0, 0)
  43.   }
  44. }
  45.  
  46. const NUM = 1000
  47. const shooters = []
  48. for (let i = 0; i < NUM; i++) {
  49.   shooters.push(new Shooter())
  50. }
  51.  
  52. function loop() {
  53.   c.fillStyle = 'rgba(0, 0, 0, 0.1)'
  54.   c.fillRect(0, 0, innerWidth, innerHeight)
  55.  
  56.   for (let i = 0; i < NUM; i++) {
  57.     shooters[i].draw()
  58.   }
  59.   requestAnimationFrame(loop)
  60. }
  61. loop()

Using setTransform(1, 0, 0, 1, 0, 0) is faster than using save and restore. If you don’t need to save context info like fills, line styles etc… consider this method.

Decompose Matrix

  1. const deltaTransformPoint = (matrix, point) => {
  2.   return {
  3.     x: point.x * matrix.a + point.y * matrix.c,
  4.     y: point.x * matrix.b + point.y * matrix.d
  5.   }
  6. }
  7.  
  8. const decomposeMatrix = matrix => {
  9.   let px = deltaTransformPoint(matrix, { x: 0, y: 1 })
  10.   let py = deltaTransformPoint(matrix, { x: 1, y: 0 })
  11.   let skewX = FROM_RADS * Math.atan2(px.y, px.x) - 90
  12.   let skewY = FROM_RADS * Math.atan2(py.y, py.x)
  13.  
  14.   return {
  15.     tx: matrix.e,
  16.     ty: matrix.f,
  17.     scaleX: Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b),
  18.     scaleY: Math.sqrt(matrix.c * matrix.c + matrix.d * matrix.d),
  19.     skewX: skewX,
  20.     skewY: skewY,
  21.     rotation: skewX
  22.   }
  23. }

Get the scale, translation, rotationa and skew values from a matrix.

Great stackoverflow answer from user dave

// graphics // math // matrix

Rotate Point Around Point (Golfed)

  1.   rot = (
  2.     cx, cy, X, Y, ang,
  3.     cos = Math.cos(-ang),
  4.     sin = Math.sin(-ang),
  5.     x = cos * (X - cx) + sin * (Y - cy) + cx,
  6.     y = cos * (Y - cy) - sin * (X - cx) + cy) => ({ x, y })

Rotate one point around another… I like to use something like this when speed-coding and golfing…

WebGL Tangled Supershape Points

  1. (() => {
  2.   const TWO_PI = Math.PI * 2;
  3.   const m = new Float32Array([
  4.     0, 0, 0, 0,
  5.     0, 0, 0, 0,
  6.     0, 0, 0, 0,
  7.     0, 0, 0, 0
  8.   ])
  9.  
  10.  
  11.   const vert = `
  12.     attribute vec3 vec;
  13.     uniform mat4 mat;
  14.     void main(void) {
  15.       gl_Position = mat * vec4(vec, 1.0);
  16.       gl_PointSize = 2.0;
  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.       preserveDrawingBuffer: true,
  31.       powerPreference: 'high-performance'
  32.     })
  33.  
  34.   Object.assign(gl.canvas.style, {
  35.     position: 'absolute',
  36.     left: '50%',
  37.     top: '50%',
  38.     transform: 'translate(-50%, -50%)',
  39.     outline: '1px solid gray'
  40.   })
  41.  
  42.  
  43.   with(gl) {
  44.     const NUM = 120
  45.     const radius = 0.7
  46.     let verts = []
  47.  
  48.     // Superformula (equations from):
  49.     // https://bsapubs.onlinelibrary.wiley.com/doi/10.3732/ajb.90.3.333
  50.     // http://en.wikipedia.org/wiki/Superformula
  51.     function superShape(a, b, m, n1, n2, n3, scale, x = 0, y = 0, z = 0) {
  52.       const { random, pow, abs, cos, sin } = Math
  53.       // with(Math) { // destrucuring vs the dreaded `with`
  54.         let r = 0
  55.         let p = 0
  56.         let xp = 0
  57.         let yp = 0
  58.         let zp = 0
  59.  
  60.         let rotX = random() * TWO_PI
  61.         let rotY = random() * TWO_PI
  62.         let rotZ = random() * TWO_PI
  63.  
  64.         let cosX = cos(rotX)
  65.         let cosY = cos(rotY)
  66.         let sinX = sin(rotX)
  67.         let sinY = sin(rotY)
  68.  
  69.         while (p <= TWO_PI) {
  70.           let ang = (m * p) / 4
  71.  
  72.           r = pow(pow(abs(cos(ang) / a), n2) + pow(abs(sin(ang) / b), n3), -1 / n1)
  73.           xp = r * cos(p)
  74.           yp = r * sin(p)
  75.  
  76.           p += 0.05
  77.  
  78.           zp = zp * cosX - xp * sinX
  79.           xp = zp * sinX + xp * cosX
  80.           yp = yp * cosY - zp * sinY
  81.           zp = yp * sinY + zp * cosY
  82.  
  83.           verts[inc] = xp * scale + x
  84.           verts[inc + 1] = yp * scale + y
  85.           verts[inc + 2] = zp * scale + z
  86.           inc += 3;
  87.         }
  88.       // }
  89.     }
  90.  
  91.     let inc = 0; 
  92.     for (let i = 0; i < NUM; i++) {
  93.       superShape(1, 1, 1 + ~~(Math.random() * 20),
  94.         ~~(Math.random() * 30),
  95.         ~~(Math.random() * 30),
  96.         ~~(Math.random() * 30), Math.random() * .2,
  97.         Math.random() - .5,
  98.         Math.random() - .5,
  99.         Math.random() - .5 )
  100.     }
  101.  
  102.  
  103.     console.log(verts.length)
  104.     const overts = verts.concat()
  105.     const leng = verts.length / 3
  106.  
  107.     bindBuffer(ARRAY_BUFFER, createBuffer())
  108.     bufferData(ARRAY_BUFFER, new Float32Array(verts), STATIC_DRAW)
  109.  
  110.     const vs = createShader(VERTEX_SHADER)
  111.     shaderSource(vs, vert)
  112.     compileShader(vs)
  113.  
  114.     const fs = createShader(FRAGMENT_SHADER)
  115.     const sp = createProgram()
  116.  
  117.     shaderSource(fs, frag)
  118.     compileShader(fs)
  119.     attachShader(sp, vs)
  120.     attachShader(sp, fs)
  121.     linkProgram(sp)
  122.     useProgram(sp)
  123.  
  124.     const vec = getAttribLocation(sp, 'vec')
  125.     vertexAttribPointer(vec, 3, FLOAT, false, 0, 0)
  126.     enableVertexAttribArray(vec)
  127.  
  128.     const matLoc = getUniformLocation(sp, 'mat')
  129.  
  130.     function rot(x, y, z) {
  131.       // https://wikimedia.org/api/rest_v1/media/math/render/svg/a8e16f4967571b7a572d1a19f3f6468512f9843e
  132.  
  133.       const sinA = Math.sin(x)
  134.       const cosA = Math.cos(x)
  135.       const sinB = Math.sin(y)
  136.       const cosB = Math.cos(y)
  137.       const sinY = Math.sin(z)
  138.       const cosY = Math.cos(z)
  139.  
  140.       m[0] = cosA * cosB
  141.       m[1] = cosA * sinB * sinY - sinA * cosY
  142.       m[2] = cosA * sinB * cosY + sinA * sinY
  143.       m[3] = 0
  144.  
  145.       m[4] = sinA * cosB
  146.       m[5] = sinA * sinB * sinY + cosA * cosY
  147.       m[6] = sinA * sinB * cosY - cosA * sinY
  148.       m[7] = 0
  149.  
  150.       m[8] = -sinB
  151.       m[9] = cosB * sinY
  152.       m[10] = cosB * cosY
  153.       m[11] = m[12] = m[13] = 0
  154.       m[15] = 1
  155.  
  156.       uniformMatrix4fv(matLoc, false, m)
  157.     }
  158.  
  159.     onresize = () => {
  160.       const {
  161.         canvas
  162.       } = gl
  163.       const size = Math.min(innerWidth, innerHeight) - 20
  164.       canvas.width = canvas.height = size
  165.       viewport(0, 0, size, size)
  166.     }
  167.  
  168.     onresize()
  169.  
  170.     let rx = 0, ry = 0, rz = 0
  171.  
  172.     function loop() {
  173.       rx += 0.01
  174.       ry += 0.01
  175.       rz += 0.01
  176.       rot(rx, ry, rz)
  177.  
  178.       disable(DEPTH_TEST)
  179.       enable(BLEND)
  180.       blendFunc(SRC_ALPHA, ONE_MINUS_SRC_ALPHA)
  181.       clearColor(0, 0, 0, 1)
  182.       clear(COLOR_BUFFER_BIT)
  183.       drawArrays(POINTS, 0, leng)
  184.       window.requestAnimationFrame(loop)
  185.     }
  186.     loop()
  187.   }
  188. })()

An interesting accident while playing around with Supershapes and WebgGL points…

WebGL Particles in Sphere

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

Playing around with animating points…

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