Files
matrix/js/utils.js
Rezmason 99ef8bbf0a Separated color passes into separate modules.
Moved main JS into its own module.
Main module now builds passes into a pipeline, based on the value of config.effect.
The passes no longer make stubs when they're not meant to be active.
Asset loading has been moved into the passes, which resolve their ready promise when they've finished loading.
2020-01-25 23:05:54 -08:00

182 lines
3.6 KiB
JavaScript

const makePassTexture = regl =>
regl.texture({
width: 1,
height: 1,
type: "half float",
wrap: "clamp",
min: "linear",
mag: "linear"
});
const makePassFBO = regl => regl.framebuffer({ color: makePassTexture(regl) });
// A pyramid is just an array of FBOs, where each FBO is half the width
// and half the height of the FBO below it.
const makePyramid = (regl, height) =>
Array(height)
.fill()
.map(_ => makePassFBO(regl));
const makeDoubleBuffer = (regl, props) => {
const state = Array(2)
.fill()
.map(() =>
regl.framebuffer({
color: regl.texture(props),
depthStencil: false
})
);
return {
front: ({ tick }) => state[tick % 2],
back: ({ tick }) => state[(tick + 1) % 2]
};
};
const resizePyramid = (pyramid, vw, vh, scale) =>
pyramid.forEach((fbo, index) =>
fbo.resize(
Math.floor((vw * scale) / 2 ** index),
Math.floor((vh * scale) / 2 ** index)
)
);
const loadImages = async (regl, manifest) => {
const keys = Object.keys(manifest);
const urls = Object.values(manifest);
const images = await Promise.all(urls.map(url => loadImageOld(regl, url)));
return Object.fromEntries(images.map((image, index) => [keys[index], image]));
};
const loadImageOld = async (regl, url) => {
if (url == null) {
return null;
}
const image = new Image();
image.crossOrigin = "anonymous";
image.src = url;
await image.decode();
return regl.texture({
data: image,
mag: "linear",
min: "linear",
flipY: true
});
};
const loadImage = (regl, url) => {
let texture = regl.texture([[0]]);
let loaded = false;
return {
texture: () => {
if (!loaded) {
console.warn(`texture still loading: ${url}`);
}
return texture;
},
ready: (async () => {
if (url != null) {
const data = new Image();
data.crossOrigin = "anonymous";
data.src = url;
await data.decode();
loaded = true;
texture = regl.texture({
data,
mag: "linear",
min: "linear",
flipY: true
});
}
})()
};
};
const makeFullScreenQuad = (regl, uniforms = {}, context = {}) =>
regl({
vert: `
precision mediump float;
attribute vec2 aPosition;
varying vec2 vUV;
void main() {
vUV = 0.5 * (aPosition + 1.0);
gl_Position = vec4(aPosition, 0, 1);
}
`,
frag: `
precision mediump float;
varying vec2 vUV;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, vUV);
}
`,
attributes: {
aPosition: [-4, -4, 4, -4, 0, 4]
},
uniforms: {
...uniforms,
time: regl.context("time")
},
context,
depth: { enable: false },
count: 3
});
const make1DTexture = (regl, data) =>
regl.texture({
data,
width: data.length / 3,
height: 1,
format: "rgb",
mag: "linear",
min: "linear"
});
const makePass = (output, render, resize, ready) => {
if (render == null) {
render = () => {};
}
if (resize == null) {
resize = (w, h) => output.resize(w, h);
}
if (ready == null) {
ready = Promise.resolve();
}
return {
output,
render,
resize,
ready
};
};
const makePipeline = (steps, getInput, ...params) =>
steps
.filter(f => f != null)
.reduce(
(pipeline, f, i) => [
...pipeline,
f(...params, i == 0 ? null : getInput(pipeline[i - 1]))
],
[]
);
export {
makePassTexture,
makePassFBO,
makeDoubleBuffer,
makePyramid,
resizePyramid,
loadImage,
loadImages,
makeFullScreenQuad,
make1DTexture,
makePass,
makePipeline
};