bnb = async opts => {
let {
globals = {},
preload: _preload = (sketch, globals) => {},
setup: _setup = (sketch, globals) => {},
draw: _draw = (sketch, time, globals) => sketch.background(sketch.sin(time * sketch.TAU) * 255),
w = 540,
h = 540,
webgl = false,
numFrames = 60,
fps = 30,
play = true,
record = false,
video = false,
preview = undefined,
loops = 1,
recordFromFrame = 1,
shutterAngle = 0,
samplesPerFrame = 1,
chromaticAberration = 0,
antiAliasing = 1,
shaderPass = undefined,
} = opts
globals = {
w, h,
numFrames,
fps,
loops,
shutterAngle,
samplesPerFrame,
chromaticAberration,
antiAliasing,
isReady: true,
...globals,
frame: 0
}
let recorder = undefined
let blob = undefined
if (record) {
if(video) {
if(video === 'webm') {
recorder = new WebMWriter({ quality: 0.99, frameRate: fps })
}
else {
recorder = await HME.createH264MP4Encoder()
recorder.width = w
recorder.height = h
recorder.frameRate = fps
recorder.quantizationParameter = 10
recorder.initialize()
}
}
else {
recorder = new GIF({
workers: 2,
workerScript: gifWorkerScript,
quality: 10,
})
}
}
function* p5(sketch) {
const container = DOM.element('div')
yield container;
const instance = new P5(sketch, container)
try {
while (!blob) yield container
if(video) {
if(video === 'webm') {
yield html`<video width="${w}" autoplay loop>
<source src="${URL.createObjectURL(blob)}" type="video/webm">
Sorry, your browser doesn't support embedded videos.
</video><br>
${DOM.download(blob, `${new Date().toJSON()}.webm`)}`
}
else {
yield html`<video width="${w}" autoplay loop>
<source src="${URL.createObjectURL(blob)}" type="video/mp4">
Sorry, your browser doesn't support embedded videos.
</video><br>
${DOM.download(blob, `${new Date().toJSON()}.mp4`)}`
}
}
else {
yield html`<img src="${URL.createObjectURL(blob, { type: 'image/gif' })}" /><br>
${DOM.download(blob, `${new Date().toJSON()}.gif`)}`
}
}
finally {
instance.remove()
}
}
return p5(sketch => {
const vs = `
precision highp float;
attribute vec3 aPosition;
void main() {
gl_Position = vec4(aPosition, 1.0);
}`
const fs = `
#define AA ${antiAliasing}
precision highp float;
uniform vec2 resolution;
uniform vec2 mouse;
uniform float time;
uniform sampler2D sum;
uniform sampler2D tex;
void main() {
vec3 color = vec3(0.);
#if AA>1
for(int m = 0; m < AA; m++)
for(int n = 0; n < AA; n++) {
// pixel coordinates
vec2 delta = vec2(float(m), float(n)) / float(AA) - 0.5;
vec2 pos = (gl_FragCoord.xy + delta) / resolution.xy;
#else
vec2 pos = gl_FragCoord.xy / resolution.xy;
#endif
pos.y = 1. - pos.y;
vec3 col = texture2D(sum, pos).rgb;
for(int i = 0; i < 3; i ++) {
vec2 p = (pos - 0.5) * (1. + 0.008 * float(i) * float(${chromaticAberration})) + 0.5;
col[i] += texture2D(tex, p)[i] / ${samplesPerFrame}.;
}
color += col;
#if AA>1
}
color /= float(AA*AA);
#endif
gl_FragColor = vec4(color, 1.0);
}`
let beesAndBombsShader
let sum, tex, shaderPassGraphics
const time = []
sketch.preload = () => _preload(sketch, globals)
sketch.setup = () => {
sketch.createCanvas(w, h, (record || shaderPass) ? sketch.P2D : webgl ? sketch.WEBGL : sketch.P2D)
sketch.pixelDensity(1)
if(!record) _setup(sketch, globals)
sketch.frameRate(fps)
if(!play) sketch.noLoop()
sketch.canvas.addEventListener('click', () => {
play = !play
play ? sketch.noLoop() : sketch.loop()
})
if(shaderPass) {
shaderPassGraphics = sketch.createGraphics(w, h, sketch.WEBGL)
shaderPassGraphics.pixelDensity(1)
shaderPass = [shaderPass].flat()
shaderPassGraphics.sh = shaderPass.map(sh => shaderPassGraphics.createShader(vs, sh))
globals.shaderPass = shaderPassGraphics.sh
shaderPassGraphics.shader(shaderPassGraphics.sh[0])
shaderPassGraphics.noStroke()
shaderPassGraphics.u_texture = sketch.createGraphics(w, h, webgl ? sketch.WEBGL : sketch.P2D)
_setup(shaderPassGraphics.u_texture, globals)
}
if(record) {
for(let frame = 0; frame < numFrames; frame++) {
for(let sample = 0; sample < samplesPerFrame; sample++) {
time.push((frame + sample * shutterAngle / samplesPerFrame) / numFrames)
}
}
sum = sketch.createGraphics(w, h, sketch.WEBGL)
beesAndBombsShader = sum.createShader(vs, fs)
sum.shader(beesAndBombsShader)
beesAndBombsShader.setUniform('resolution', [w, h])
tex = sketch.createGraphics(w, h, webgl ? sketch.WEBGL : sketch.P2D)
_setup(tex, globals)
tex.pixelDensity(1)
beesAndBombsShader.setUniform(`tex`, tex)
tex.createGraphics = sketch.createGraphics
}
}
sketch.draw = () => {
if(!globals.isReady) {
sketch.frameCount = 0
return
}
globals.frame = (sketch.frameCount - 1) % numFrames
if(!record) {
const time = ((sketch.frameCount - 1) / numFrames) % 1
if(shaderPass) {
shaderPassGraphics.u_texture.clear()
shaderPassGraphics.u_texture.push()
_draw(shaderPassGraphics.u_texture, time, globals)
shaderPassGraphics.u_texture.pop()
shaderPassGraphics.sh.forEach((sh, i) => {
shaderPassGraphics.shader(sh)
sh.setUniform('u_resolution', [w, h])
sh.setUniform('u_time', time)
sh.setUniform('u_mouse', [sketch.mouseX, sketch.mouseY])
sh.setUniform('u_frame', globals.frame)
sh.setUniform('u_framebuffer', shaderPassGraphics)
sh.setUniform('u_texture', i === 0 ? shaderPassGraphics.u_texture : shaderPassGraphics)
shaderPassGraphics.quad(-1, -1, 1, -1, 1, 1, -1, 1)
})
sketch.image(shaderPassGraphics, 0, 0)
}
else {
_draw(sketch, time, globals)
}
}
else {
sum.clear()
beesAndBombsShader.setUniform('sum', sum)
for(let i = 0; i < samplesPerFrame; i++) {
tex.clear()
tex.push()
_draw(tex, time[i + ((sketch.frameCount - 1) % numFrames) * samplesPerFrame], globals)
tex.pop()
if(shaderPass) {
shaderPassGraphics.sh.forEach((sh, i) => {
shaderPassGraphics.shader(sh)
sh.setUniform('u_resolution', [w, h])
sh.setUniform('u_time', time[i + ((sketch.frameCount - 1) % numFrames) * samplesPerFrame])
sh.setUniform('u_mouse', [sketch.mouseX, sketch.mouseY])
sh.setUniform('u_frame', globals.frame)
sh.setUniform('u_framebuffer', shaderPassGraphics)
sh.setUniform('u_texture', i === 0 ? tex : shaderPassGraphics)
shaderPassGraphics.quad(-1, -1, 1, -1, 1, 1, -1, 1)
})
beesAndBombsShader.setUniform('tex', shaderPassGraphics)
}
else {
beesAndBombsShader.setUniform('tex', tex)
}
sum.shader(beesAndBombsShader)
sum.quad(-1, -1, 1, -1, 1, 1, -1, 1)
}
sketch.image(sum, 0, 0)
if(sketch.frameCount >= recordFromFrame) {
if(video) {
if(video === 'webm') {
recorder.addFrame(sketch.canvas)
}
else {
recorder.addFrameRgba(sketch.drawingContext.getImageData(0, 0, w, h).data)
}
}
else recorder.addFrame(sketch.canvas, { copy: true, delay: 1000 / fps, quality: 1 })
}
if(sketch.frameCount === numFrames * loops || sketch.frameCount === preview) {
sketch.noLoop()
if(video) {
if (video === 'webm') {
recorder.complete().then(_blob => blob = _blob)
}
else {
recorder.finalize()
const uint8Array = recorder.FS.readFile(recorder.outputFilename)
blob = new Blob([uint8Array], { type: "video/mp4" })
}
}
else {
recorder.on('finished', _blob => blob = _blob)
recorder.render()
}
}
}
}
})
}