@absulit/points
Version:
A Generative Art library made in WebGPU
621 lines (515 loc) • 74.8 kB
JavaScript
/* @ts-self-types="./points.d.ts" */
class ShaderType{static VERTEX=1;static COMPUTE=2;static FRAGMENT=3}class RenderPass{#vertexShader;#computeShader;#fragmentShader;#compiledShaders;#computePipeline=null;#renderPipeline=null;#computeBindGroup=null;#uniformBindGroup=null;#bindGroupLayout=null;#bindGroupLayoutCompute=null;#entries=null;#internal=false;#hasComputeShader;#hasVertexShader;#hasFragmentShader;#hasVertexAndFragmentShader;#workgroupCountX;#workgroupCountY;#workgroupCountZ;constructor(vertexShader,fragmentShader,computeShader,workgroupCountX,workgroupCountY,workgroupCountZ){this.#vertexShader=vertexShader;this.#computeShader=computeShader;this.#fragmentShader=fragmentShader;this.#compiledShaders={vertex:"",compute:"",fragment:""};this.#hasComputeShader=!!this.#computeShader;this.#hasVertexShader=!!this.#vertexShader;this.#hasFragmentShader=!!this.#fragmentShader;this.#hasVertexAndFragmentShader=this.#hasVertexShader&&this.#hasFragmentShader;this.#workgroupCountX=workgroupCountX||8;this.#workgroupCountY=workgroupCountY||8;this.#workgroupCountZ=workgroupCountZ||1;Object.seal(this)}get internal(){return this.#internal}set internal(value){this.#internal=value}get vertexShader(){return this.#vertexShader}get computeShader(){return this.#computeShader}get fragmentShader(){return this.#fragmentShader}set computePipeline(value){this.#computePipeline=value}get computePipeline(){return this.#computePipeline}set renderPipeline(value){this.#renderPipeline=value}get renderPipeline(){return this.#renderPipeline}set computeBindGroup(value){this.#computeBindGroup=value}get computeBindGroup(){return this.#computeBindGroup}set uniformBindGroup(value){this.#uniformBindGroup=value}get uniformBindGroup(){return this.#uniformBindGroup}set bindGroupLayout(value){this.#bindGroupLayout=value}get bindGroupLayout(){return this.#bindGroupLayout}set bindGroupLayoutCompute(value){this.#bindGroupLayoutCompute=value}get bindGroupLayoutCompute(){return this.#bindGroupLayoutCompute}set entries(value){this.#entries=value}get entries(){return this.#entries}get compiledShaders(){return this.#compiledShaders}get hasComputeShader(){return this.#hasComputeShader}get hasVertexShader(){return this.#hasVertexShader}get hasFragmentShader(){return this.#hasFragmentShader}get hasVertexAndFragmentShader(){return this.#hasVertexAndFragmentShader}get workgroupCountX(){return this.#workgroupCountX}get workgroupCountY(){return this.#workgroupCountY}get workgroupCountZ(){return this.#workgroupCountZ}}const vert$8=`
@vertex
fn main(
@location(0) position: vec4f,
@location(1) color: vec4f,
@location(2) uv: vec2f,
@builtin(vertex_index) vertexIndex: u32
) -> Fragment {
return defaultVertexBody(position, color, uv);
}
`;const texture=`
fn texture(texture:texture_2d<f32>, aSampler:sampler, uv:vec2f, crop:bool) -> vec4f {
let flipTexture = vec2(1.,-1.);
let flipTextureCoordinates = vec2(-1.,1.);
let dims:vec2u = textureDimensions(texture, 0);
let dimsF32 = vec2f(dims);
let minScreenSize = params.screen.y;
let imageRatio = dimsF32 / minScreenSize;
let displaceImagePosition = vec2(0., 1.);
let imageUV = uv / imageRatio * flipTexture + displaceImagePosition;
var rgbaImage = textureSample(texture, aSampler, imageUV);
// e.g. if uv.x < 0. OR uv.y < 0. || uv.x > imageRatio.x OR uv.y > imageRatio.y
if (crop && (any(uv < vec2(0.0)) || any(uv > imageRatio))) {
rgbaImage = vec4(0.);
}
return rgbaImage;
}
`;const texturePosition=`
fn texturePosition(texture:texture_2d<f32>, aSampler:sampler, position:vec2f, uv:vec2f, crop:bool) -> vec4f {
let flipTexture = vec2(1.,-1.);
let flipTextureCoordinates = vec2(-1.,1.);
let dims: vec2<u32> = textureDimensions(texture, 0);
let dimsF32 = vec2f(dims);
let minScreenSize = params.screen.y;
let imageRatio = dimsF32 / minScreenSize;
let displaceImagePosition = position * flipTextureCoordinates / imageRatio + vec2(0., 1.);
let top = position + vec2(0, imageRatio.y);
let imageUV = uv / imageRatio * flipTexture + displaceImagePosition;
var rgbaImage = textureSample(texture, aSampler, imageUV);
// e.g. if uv.x < 0. OR uv.y < 0. || uv.x > imageRatio.x OR uv.y > imageRatio.y
if (crop && (any(uv < vec2(0.0)) || any(uv > imageRatio))) {
rgbaImage = vec4(0.);
}
return rgbaImage;
}
`;const pixelateTexturePosition=`
fn pixelateTexturePosition(texture:texture_2d<f32>, textureSampler:sampler, position:vec2f, pixelsWidth:f32, pixelsHeight:f32, uv:vec2f) -> vec4f {
let dx = pixelsWidth * (1. / params.screen.x);
let dy = pixelsHeight * (1. / params.screen.y);
let coord = vec2(dx*floor( uv.x / dx), dy * floor( uv.y / dy));
//texturePosition(texture:texture_2d<f32>, aSampler:sampler, position:vec2f, uv:vec2f, crop:bool) -> vec4f {
return texturePosition(texture, textureSampler, position, coord, true);
}
`;const frag$8=`
${texturePosition}
@fragment
fn main(
@location(0) color: vec4f,
@location(1) uv: vec2f,
@location(2) ratio: vec2f, // relation between params.screen.x and params.screen.y
@location(3) uvr: vec2f, // uv with aspect ratio corrected
@location(4) mouse: vec2f,
@builtin(position) position: vec4f
) -> @location(0) vec4f {
let imageColor = texturePosition(renderpass_feedbackTexture, renderpass_feedbackSampler, vec2(0., 0), uvr, true);
let colorParam = vec4(params.color_r, params.color_g, params.color_b, params.color_a);
let finalColor:vec4f = (imageColor + colorParam) * params.color_blendAmount;
return finalColor;
}
`;const color={vertexShader:vert$8,fragmentShader:frag$8,init:async(points,params)=>{points._setInternal(true);points.setSampler("renderpass_feedbackSampler",null);points.setTexture2d("renderpass_feedbackTexture",true);points.setUniform("color_blendAmount",params?.blendAmount||.5);points.setUniform("color_r",params?.color[0]||1);points.setUniform("color_g",params?.color[1]||1);points.setUniform("color_b",params?.color[2]||0);points.setUniform("color_a",params?.color[3]||1);points._setInternal(false)},update:points=>{}};const vert$7=`
@vertex
fn main(
@location(0) position: vec4f,
@location(1) color: vec4f,
@location(2) uv: vec2f,
@builtin(vertex_index) vertexIndex: u32
) -> Fragment {
return defaultVertexBody(position, color, uv);
}
`;const fnusin=`
fn fnusin(speed: f32) -> f32{
return (sin(params.time * speed) + 1.) * .5;
}
`;const WHITE=`
const WHITE = vec4(1.,1.,1.,1.);
`;const bloom$1=`
fn bloom(input:f32, iterations:i32, intensity:f32) -> f32 {
var output = 0.;
let iterationsF32 = f32(iterations);
for (var k = 0; k < iterations; k++) {
let kf32 = f32(k);
for (var n = 0; n < iterations; n++) {
let coef = cos(2. * PI * kf32 * f32(n) / iterationsF32 );
output += input * coef * intensity;
}
}
return output;
}
`;const brightness=`
fn brightness(color:vec4f) -> f32 {
// // Standard
// LuminanceA = (0.2126*R) + (0.7152*G) + (0.0722*B)
// // Percieved A
// LuminanceB = (0.299*R + 0.587*G + 0.114*B)
// // Perceived B, slower to calculate
// LuminanceC = sqrt(0.299*(R**2) + 0.587*(G**2) + 0.114*(B**2))
return (0.2126 * color.r) + (0.7152 * color.g) + (0.0722 * color.b);
}
`;const frag$7=`
${fnusin}
${texturePosition}
${brightness}
${WHITE}
@fragment
fn main(
@location(0) color: vec4f,
@location(1) uv: vec2f,
@location(2) ratio: vec2f, // relation between params.screen.x and params.screen.y
@location(3) uvr: vec2f, // uv with aspect ratio corrected
@location(4) mouse: vec2f,
@builtin(position) position: vec4f
) -> @location(0) vec4f {
let imageColor = texturePosition(renderpass_feedbackTexture, renderpass_feedbackSampler, vec2(0., 0), uvr, true);
let finalColor:vec4f = brightness(imageColor) * WHITE;
return finalColor;
}
`;const grayscale={vertexShader:vert$7,fragmentShader:frag$7,init:async(points,params)=>{points._setInternal(true);points.setSampler("renderpass_feedbackSampler",null);points.setTexture2d("renderpass_feedbackTexture",true);points._setInternal(false)},update:points=>{}};const vert$6=`
@vertex
fn main(
@location(0) position: vec4f,
@location(1) color: vec4f,
@location(2) uv: vec2f,
@builtin(vertex_index) vertexIndex: u32
) -> Fragment {
return defaultVertexBody(position, color, uv);
}
`;const frag$6=`
${texturePosition}
@fragment
fn main(
@location(0) color: vec4f,
@location(1) uv: vec2f,
@location(2) ratio: vec2f, // relation between params.screen.x and params.screen.y
@location(3) uvr: vec2f, // uv with aspect ratio corrected
@location(4) mouse: vec2f,
@builtin(position) position: vec4f
) -> @location(0) vec4f {
let imageColor = texturePosition(renderpass_feedbackTexture, renderpass_feedbackSampler, vec2(0., 0), uvr, true);
// --------- chromatic displacement vector
let cdv = vec2(params.chromaticAberration_distance, 0.);
let d = distance(vec2(.5,.5), uvr);
let imageColorR = texturePosition(renderpass_feedbackTexture, renderpass_feedbackSampler, vec2(0.) * ratio, uvr + cdv * d, true).r;
let imageColorG = texturePosition(renderpass_feedbackTexture, renderpass_feedbackSampler, vec2(0.) * ratio, uvr, true).g;
let imageColorB = texturePosition(renderpass_feedbackTexture, renderpass_feedbackSampler, vec2(0.) * ratio, uvr - cdv * d, true).b;
let finalColor:vec4f = vec4(imageColorR, imageColorG, imageColorB, 1);
return finalColor;
}
`;const chromaticAberration={vertexShader:vert$6,fragmentShader:frag$6,init:async(points,params)=>{points._setInternal(true);points.setSampler("renderpass_feedbackSampler",null);points.setTexture2d("renderpass_feedbackTexture",true);points.setUniform("chromaticAberration_distance",params.distance);points._setInternal(false)},update:points=>{}};const vert$5=`
@vertex
fn main(
@location(0) position: vec4f,
@location(1) color: vec4f,
@location(2) uv: vec2f,
@builtin(vertex_index) vertexIndex: u32
) -> Fragment {
return defaultVertexBody(position, color, uv);
}
`;const frag$5=`
${texturePosition}
${pixelateTexturePosition}
@fragment
fn main(
@location(0) color: vec4f,
@location(1) uv: vec2f,
@location(2) ratio: vec2f, // relation between params.screen.x and params.screen.y
@location(3) uvr: vec2f, // uv with aspect ratio corrected
@location(4) mouse: vec2f,
@builtin(position) position: vec4f
) -> @location(0) vec4f {
let pixelatedColor = pixelateTexturePosition(
renderpass_feedbackTexture,
renderpass_feedbackSampler,
vec2(0.),
params.pixelate_pixelsWidth,
params.pixelate_pixelsHeight,
uvr
);
let finalColor:vec4f = pixelatedColor;
return finalColor;
}
`;const pixelate={vertexShader:vert$5,fragmentShader:frag$5,init:async(points,params)=>{points._setInternal(true);points.setSampler("renderpass_feedbackSampler",null);points.setTexture2d("renderpass_feedbackTexture",true);points.setUniform("pixelate_pixelsWidth",params.pixelsWidth);points.setUniform("pixelate_pixelsHeight",params.pixelsHeight);points._setInternal(false)},update:points=>{}};const vert$4=`
@vertex
fn main(
@location(0) position: vec4f,
@location(1) color: vec4f,
@location(2) uv: vec2f,
@builtin(vertex_index) vertexIndex: u32
) -> Fragment {
return defaultVertexBody(position, color, uv);
}
`;const PI=`const PI = 3.14159265;`;const polar=`
fn polar(distance: f32, radians: f32) -> vec2f {
return vec2f(distance * cos(radians), distance * sin(radians));
}
`;const rotateVector=`
fn rotateVector(p:vec2f, rads:f32 ) -> vec2f {
let s = sin(rads);
let c = cos(rads);
let xnew = p.x * c - p.y * s;
let ynew = p.x * s + p.y * c;
return vec2(xnew, ynew);
}
`;const snoise=`
fn mod289_v3(x: vec3f) -> vec3f {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
fn mod289_v2(x: vec2f) -> vec2f {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
fn permute(x: vec3f) -> vec3f {
return mod289_v3(((x*34.0)+10.0)*x);
}
fn snoise(v:vec2f) -> f32 {
let C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
-0.577350269189626, // -1.0 + 2.0 * C.x
0.024390243902439); // 1.0 / 41.0
// First corner
var i = floor(v + dot(v, C.yy) );
var x0 = v - i + dot(i, C.xx);
// Other corners
var i1 = vec2(0.);
//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
//i1.y = 1.0 - i1.x;
//i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
if(x0.x > x0.y){ i1 = vec2(1.0, 0.0); }else{ i1 = vec2(0.0, 1.0); }
//x0 = x0 - 0.0 + 0.0 * C.xx ;
// x1 = x0 - i1 + 1.0 * C.xx ;
// x2 = x0 - 1.0 + 2.0 * C.xx ;
var x12 = x0.xyxy + C.xxzz;
//x12.xy -= i1;
x12 = vec4(x12.xy - i1, x12.zw); // ?? fix
// Permutations
i = mod289_v2(i); // Avoid truncation effects in permutation
let p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
+ i.x + vec3(0.0, i1.x, 1.0 ));
var m = max(vec3(0.5) - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), vec3(0.0));
m = m*m ;
m = m*m ;
// Gradients: 41 points uniformly over a line, mapped onto a diamond.
// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
let x = 2.0 * fract(p * C.www) - 1.0;
let h = abs(x) - 0.5;
let ox = floor(x + 0.5);
let a0 = x - ox;
// Normalise gradients implicitly by scaling m
// Approximation of: m *= inversesqrt( a0*a0 + h*h );
m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
// Compute final noise value at P
var g = vec3(0.);
g.x = a0.x * x0.x + h.x * x0.y;
//g.yz = a0.yz * x12.xz + h.yz * x12.yw;
g = vec3(g.x,a0.yz * x12.xz + h.yz * x12.yw);
return 130.0 * dot(m, g);
}
`;const frag$4=`
${texture}
${rotateVector}
${snoise}
${PI}
${WHITE}
${polar}
fn angle(p1:vec2f, p2:vec2f) -> f32 {
let d = p1 - p2;
return abs(atan2(d.y, d.x)) / PI;
}
@fragment
fn main(
@location(0) color: vec4f,
@location(1) uv: vec2f,
@location(2) ratio: vec2f, // relation between params.screen.x and params.screen.y
@location(3) uvr: vec2f, // uv with aspect ratio corrected
@location(4) mouse: vec2f,
@builtin(position) position: vec4f
) -> @location(0) vec4f {
let imagePosition = vec2(0.0,0.0) * ratio;
let center = vec2(.5,.5) * ratio;
let d = distance(center, uvr); // sqrt(dot(d, d));
//vector from center to current fragment
let vectorToCenter = uvr - center;
let sqrtDotCenter = sqrt(dot(center, center));
//amount of effect
let power = 2.0 * PI / (2.0 * sqrtDotCenter ) * (params.lensDistortion_amount - 0.5);
//radius of 1:1 effect
var bind = .0;
if (power > 0.0){
//stick to corners
bind = sqrtDotCenter;
} else {
//stick to borders
if (ratio.x < 1.0) {
bind = center.x;
} else {
bind = center.y;
};
}
//Weird formulas
var nuv = uvr;
if (power > 0.0){//fisheye
nuv = center + normalize(vectorToCenter) * tan(d * power) * bind / tan( bind * power);
} else if (power < 0.0){//antifisheye
nuv = center + normalize(vectorToCenter) * atan(d * -power * 10.0) * bind / atan(-power * bind * 10.0);
} else {
nuv = uvr;
}
// let imageColor = texturePosition(renderpass_feedbackTexture, renderpass_feedbackSampler, imagePosition, nuv, false);
// Chromatic Aberration --
// --------- chromatic displacement vector
let cdv = vec2(params.lensDistortion_distance, 0.);
// let dis = distance(vec2(.5,.5), uvr);
let imageColorR = texture(renderpass_feedbackTexture, renderpass_feedbackSampler, nuv + cdv * params.lensDistortion_amount , true).r;
let imageColorG = texture(renderpass_feedbackTexture, renderpass_feedbackSampler, nuv, true).g;
let imageColorB = texture(renderpass_feedbackTexture, renderpass_feedbackSampler, nuv - cdv * params.lensDistortion_amount , true).b;
let chromaticAberration:vec4f = vec4(imageColorR, imageColorG, imageColorB, 1);
// -- Chromatic Aberration
let finalColor = chromaticAberration;
// let finalColor = vec4(nuv,0,1) * WHITE;
return finalColor;
}
`;const lensDistortion={vertexShader:vert$4,fragmentShader:frag$4,init:async(points,params)=>{points._setInternal(true);points.setSampler("renderpass_feedbackSampler",null);points.setTexture2d("renderpass_feedbackTexture",true);points.setUniform("lensDistortion_amount",params?.amount||.4);points.setUniform("lensDistortion_distance",params?.distance||.01);points._setInternal(false)},update:async points=>{}};const vert$3=`
@vertex
fn main(
@location(0) position: vec4f,
@location(1) color: vec4f,
@location(2) uv: vec2f,
@builtin(vertex_index) vertexIndex: u32
) -> Fragment {
return defaultVertexBody(position, color, uv);
}
`;const rand=`
var<private> rand_seed : vec2f;
fn rand() -> f32 {
rand_seed.x = fract(cos(dot(rand_seed, vec2f(23.14077926, 232.61690225))) * 136.8168);
rand_seed.y = fract(cos(dot(rand_seed, vec2f(54.47856553, 345.84153136))) * 534.7645);
return rand_seed.y;
}
`;const frag$3=`
${texturePosition}
${rand}
${snoise}
@fragment
fn main(
@location(0) color: vec4f,
@location(1) uv: vec2f,
@location(2) ratio: vec2f, // relation between params.screen.x and params.screen.y
@location(3) uvr: vec2f, // uv with aspect ratio corrected
@location(4) mouse: vec2f,
@builtin(position) position: vec4f
) -> @location(0) vec4f {
let imageColor = texturePosition(renderpass_feedbackTexture, renderpass_feedbackSampler, vec2(0., 0), uvr, true);
rand_seed = uvr + params.time;
var noise = rand();
noise = noise * .5 + .5;
let finalColor = (imageColor + imageColor * noise) * .5;
return finalColor;
}
`;const filmgrain={vertexShader:vert$3,fragmentShader:frag$3,init:async(points,params)=>{points._setInternal(true);points.setSampler("renderpass_feedbackSampler",null);points.setTexture2d("renderpass_feedbackTexture",true);points._setInternal(false)},update:points=>{}};const vert$2=`
@vertex
fn main(
@location(0) position: vec4f,
@location(1) color: vec4f,
@location(2) uv: vec2f,
@builtin(vertex_index) vertexIndex: u32
) -> Fragment {
return defaultVertexBody(position, color, uv);
}
`;const frag$2=`
${texturePosition}
${bloom$1}
${brightness}
${PI}
@fragment
fn main(
@location(0) color: vec4f,
@location(1) uv: vec2f,
@location(2) ratio: vec2f, // relation between params.screen.x and params.screen.y
@location(3) uvr: vec2f, // uv with aspect ratio corrected
@location(4) mouse: vec2f,
@builtin(position) position: vec4f
) -> @location(0) vec4f {
let startPosition = vec2(0.,0.);
let rgbaImage = texturePosition(renderpass_feedbackTexture, renderpass_feedbackSampler, startPosition, uvr, false); //* .998046;
let input = brightness(rgbaImage);
let bloomVal = bloom(input, i32(params.bloom_iterations), params.bloom_amount);
let rgbaBloom = vec4(bloomVal);
let finalColor:vec4f = rgbaImage + rgbaBloom;
return finalColor;
}
`;const bloom={vertexShader:vert$2,fragmentShader:frag$2,init:async(points,params)=>{points._setInternal(true);points.setSampler("renderpass_feedbackSampler",null);points.setTexture2d("renderpass_feedbackTexture",true);points.setUniform("bloom_amount",params?.amount||.5);points.setUniform("bloom_iterations",params?.iterations||2);points._setInternal(false)},update:points=>{}};const vert$1=`
@vertex
fn main(
@location(0) position: vec4f,
@location(1) color: vec4f,
@location(2) uv: vec2f,
@builtin(vertex_index) vertexIndex: u32
) -> Fragment {
return defaultVertexBody(position, color, uv);
}
`;const blur9=`
fn blur9(image: texture_2d<f32>, imageSampler:sampler, position:vec2f, uv:vec2f, resolution: vec2f, direction: vec2f) -> vec4f {
var color = vec4(0.0);
let off1 = vec2(1.3846153846) * direction;
let off2 = vec2(3.2307692308) * direction;
color += texturePosition(image, imageSampler, position, uv, true) * 0.2270270270;
color += texturePosition(image, imageSampler, position, uv + (off1 / resolution), true) * 0.3162162162;
color += texturePosition(image, imageSampler, position, uv - (off1 / resolution), true) * 0.3162162162;
color += texturePosition(image, imageSampler, position, uv + (off2 / resolution), true) * 0.0702702703;
color += texturePosition(image, imageSampler, position, uv - (off2 / resolution), true) * 0.0702702703;
return color;
}
`;const frag$1=`
${texturePosition}
${PI}
${rotateVector}
${blur9}
@fragment
fn main(
@location(0) color: vec4f,
@location(1) uv: vec2f,
@location(2) ratio: vec2f, // relation between params.screen.x and params.screen.y
@location(3) uvr: vec2f, // uv with aspect ratio corrected
@location(4) mouse: vec2f,
@builtin(position) position: vec4f
) -> @location(0) vec4f {
let feedbackColor = blur9(
renderpass_feedbackTexture,
renderpass_feedbackSampler,
vec2(0.,0),
uvr,
vec2(params.blur_resolution_x, params.blur_resolution_y), // resolution
rotateVector(vec2(params.blur_direction_x, params.blur_direction_y), params.blur_radians) // direction
);
let finalColor = feedbackColor;
return finalColor;
}
`;const blur={vertexShader:vert$1,fragmentShader:frag$1,init:async(points,params)=>{points._setInternal(true);points.setSampler("renderpass_feedbackSampler",null);points.setTexture2d("renderpass_feedbackTexture",true);points.setUniform("blur_resolution_x",params?.resolution[0]||50);points.setUniform("blur_resolution_y",params?.resolution[1]||50);points.setUniform("blur_direction_x",params?.direction[0]||.4);points.setUniform("blur_direction_y",params?.direction[1]||.4);points.setUniform("blur_radians",params?.radians||0);points._setInternal(false)},update:points=>{}};const vert=`
@vertex
fn main(
@location(0) position: vec4f,
@location(1) color: vec4f,
@location(2) uv: vec2f,
@builtin(vertex_index) vertexIndex: u32
) -> Fragment {
return defaultVertexBody(position, color, uv);
}
`;const frag=`
${texturePosition}
${snoise}
@fragment
fn main(
@location(0) color: vec4f,
@location(1) uv: vec2f,
@location(2) ratio: vec2f, // relation between params.screen.x and params.screen.y
@location(3) uvr: vec2f, // uv with aspect ratio corrected
@location(4) mouse: vec2f,
@builtin(position) position: vec4f
) -> @location(0) vec4f {
let scale = params.waves_scale;
let intensity = params.waves_intensity;
let n1 = (snoise(uv / scale + vec2(.03, .4) * params.time) * .5 + .5) * intensity;
let n2 = (snoise(uv / scale + vec2(.3, .02) * params.time) * .5 + .5) * intensity;
let n = n1 + n2;
let imageColor = texturePosition(renderpass_feedbackTexture, renderpass_feedbackSampler, vec2(0., 0), uvr + n2, true);
let finalColor:vec4f = imageColor;
return finalColor;
}
`;const waves={vertexShader:vert,fragmentShader:frag,init:async(points,params)=>{points._setInternal(true);points.setSampler("renderpass_feedbackSampler",null);points.setTexture2d("renderpass_feedbackTexture",true);points.setUniform("waves_scale",params?.scale||.45);points.setUniform("waves_intensity",params?.intensity||.03);points._setInternal(false)},update:points=>{}};class RenderPasses{static COLOR=1;static GRAYSCALE=2;static CHROMATIC_ABERRATION=3;static PIXELATE=4;static LENS_DISTORTION=5;static FILM_GRAIN=6;static BLOOM=7;static BLUR=8;static WAVES=9;static #LIST={1:color,2:grayscale,3:chromaticAberration,4:pixelate,5:lensDistortion,6:filmgrain,7:bloom,8:blur,9:waves};static async add(points,renderPassId,params){if(points.renderPasses?.length){throw"`addPostRenderPass` should be called prior `Points.init()`"}let shaders=this.#LIST[renderPassId];let renderPass=new RenderPass(shaders.vertexShader,shaders.fragmentShader,shaders.computeShader);renderPass.internal=true;points.addRenderPass(renderPass);await shaders.init(points,params)}static async color(points,r,g,b,a,blendAmount){return await RenderPasses.add(points,RenderPasses.COLOR,{color:[r,g,b,a],blendAmount})}static async grayscale(points){return await RenderPasses.add(points,RenderPasses.GRAYSCALE)}static async chromaticAberration(points,distance){return await RenderPasses.add(points,RenderPasses.CHROMATIC_ABERRATION,{distance})}static async pixelate(points,width,height){return await RenderPasses.add(points,RenderPasses.PIXELATE,{pixelsWidth:width,pixelsHeight:height})}static async lensDistortion(points,amount,distance){return await RenderPasses.add(points,RenderPasses.LENS_DISTORTION,{amount,distance})}static async filmgrain(points){return await RenderPasses.add(points,RenderPasses.FILM_GRAIN)}static async bloom(points,amount){return await RenderPasses.add(points,RenderPasses.BLOOM,{amount})}static async blur(points,resolutionX,resolutionY,directionX,directionY,radians){return await RenderPasses.add(points,RenderPasses.BLUR,{resolution:[resolutionX,resolutionY],direction:[directionX,directionY],radians})}static async waves(points,scale,intensity){return await RenderPasses.add(points,RenderPasses.WAVES,{scale,intensity})}}class UniformKeys{static TIME="time";static DELTA="delta";static EPOCH="epoch";static SCREEN="screen";static MOUSE="mouse";static MOUSE_CLICK="mouseClick";static MOUSE_DOWN="mouseDown";static MOUSE_WHEEL="mouseWheel";static MOUSE_DELTA="mouseDelta"}class VertexBufferInfo{#vertexSize;#vertexOffset;#colorOffset;#uvOffset;#vertexCount;constructor(vertexArray,triangleDataLength=10,vertexOffset=0,colorOffset=4,uvOffset=8){this.#vertexSize=vertexArray.BYTES_PER_ELEMENT*triangleDataLength;this.#vertexOffset=vertexArray.BYTES_PER_ELEMENT*vertexOffset;this.#colorOffset=vertexArray.BYTES_PER_ELEMENT*colorOffset;this.#uvOffset=vertexArray.BYTES_PER_ELEMENT*uvOffset;this.#vertexCount=vertexArray.byteLength/this.#vertexSize}get vertexSize(){return this.#vertexSize}get vertexOffset(){return this.#vertexOffset}get colorOffset(){return this.#colorOffset}get uvOffset(){return this.#uvOffset}get vertexCount(){return this.#vertexCount}}class Coordinate{#x;#y;#z;#value;constructor(x=0,y=0,z=0){this.#x=x;this.#y=y;this.#z=z;this.#value=[x,y,z]}set x(value){this.#x=value;this.#value[0]=value}set y(value){this.#y=value;this.#value[1]=value}set z(value){this.#z=value;this.#value[2]=value}get x(){return this.#x}get y(){return this.#y}get z(){return this.#z}get value(){return this.#value}set(x,y,z){this.#x=x;this.#y=y;this.#z=z;this.#value[0]=x;this.#value[1]=y;this.#value[2]=z}}class RGBAColor{#value;constructor(r=0,g=0,b=0,a=1){if(r>1&&g>1&&b>1){r/=255;g/=255;b/=255;if(a>1){a/=255}}this.#value=[r,g,b,a]}set r(value){this.#value[0]=value}set g(value){this.#value[1]=value}set b(value){this.#value[2]=value}set a(value){this.#value[3]=value}get r(){return this.#value[0]}get g(){return this.#value[1]}get b(){return this.#value[2]}get a(){return this.#value[3]}get value(){return this.#value}get brightness(){let[r,g,b,a]=this.#value;return .2126*r+.7152*g+.0722*b}set brightness(value){this.#value=[value,value,value,1]}set(r,g,b,a){this.#value=[r,g,b,a]}setColor(color){this.#value=[color.r,color.g,color.b,color.a]}add(color){let[r,g,b,a]=this.#value;this.#value=[r+color.r,g+color.g,b+color.b,a+color.a]}blend(color){let[r0,g0,b0,a0]=this.#value;let[r1,b1,g1,a1]=color.value;let a01=(1-a0)*a1+a0;let r01=((1-a0)*a1*r1+a0*r0)/a01;let g01=((1-a0)*a1*g1+a0*g0)/a01;let b01=((1-a0)*a1*b1+a0*b0)/a01;this.#value=[r01,g01,b01,a01]}additive(color){let base=this.#value;let added=color.value;let mix=[];mix[3]=1-(1-added[3])*(1-base[3]);mix[0]=Math.round(added[0]*added[3]/mix[3]+base[0]*base[3]*(1-added[3])/mix[3]);mix[1]=Math.round(added[1]*added[3]/mix[3]+base[1]*base[3]*(1-added[3])/mix[3]);mix[2]=Math.round(added[2]*added[3]/mix[3]+base[2]*base[3]*(1-added[3])/mix[3]);this.#value=mix}equal(color){return this.#value[0]==color.r&&this.#value[1]==color.g&&this.#value[2]==color.b&&this.#value[3]==color.a}static average(colors){let r=0,g=0,b=0;for(let index=0;index<colors.length;index++){const color=colors[index];r+=color.r*color.r;g+=color.g*color.g;b+=color.b*color.b}return new RGBAColor(Math.sqrt(r/colors.length),Math.sqrt(g/colors.length),Math.sqrt(b/colors.length))}static difference(c1,c2){let r=0;let g=0;let b=0;if(c1&&!c1.isNull()&&c2&&!c2.isNull()){const{r:r1,g:g1,b:b1}=c1;const{r:r2,g:g2,b:b2}=c2;r=r1-r2;g=g1-g2;b=b1-b2}return new RGBAColor(r,g,b)}isNull(){const[r,g,b,a]=this.#value;return!(isNaN(r)&&isNaN(g)&&isNaN(b)&&isNaN(a))}static colorRGBEuclideanDistance(c1,c2){return Math.sqrt(Math.pow(c1.r-c2.r,2)+Math.pow(c1.g-c2.g,2)+Math.pow(c1.b-c2.b,2))}euclideanDistance(color){const[r,g,b]=this.#value;return Math.sqrt(Math.pow(r-color.r,2)+Math.pow(g-color.g,2)+Math.pow(b-color.b,2))}static getClosestColorInPalette(color,palette){if(!palette){throw"Palette should be an array of `RGBA`s"}let distance=100;let selectedColor=null;palette.forEach(paletteColor=>{let currentDistance=color.euclideanDistance(paletteColor);if(currentDistance<distance){selectedColor=paletteColor;distance=currentDistance}});return selectedColor}}class Clock{#time=0;#oldTime=0;#delta=0;constructor(){}get time(){return this.#time}get delta(){return this.#delta}#now(){return(typeof performance==="undefined"?Date:performance).now()}getDelta(){this.#delta=0;const newTime=this.#now();this.#delta=(newTime-this.#oldTime)/1e3;this.#oldTime=newTime;this.#time+=this.#delta;return this.#delta}}const defaultStructs=`
struct Fragment {
@builtin(position) position: vec4f,
@location(0) color: vec4f,
@location(1) uv: vec2f,
@location(2) ratio: vec2f,
@location(3) uvr: vec2f,
@location(4) mouse: vec2f
}
struct Sound {
data: array<f32, 2048>,
//play
//dataLength
//duration
//currentPosition
}
struct Event {
updated: u32,
data: array<f32>
}
`;const defaultVertexBody=`
fn defaultVertexBody(position: vec4f, color: vec4f, uv: vec2f) -> Fragment {
var result: Fragment;
let ratioX = params.screen.x / params.screen.y;
let ratioY = 1. / ratioX / (params.screen.y / params.screen.x);
result.ratio = vec2(ratioX, ratioY);
result.position = vec4f(position);
result.color = vec4f(color);
result.uv = uv;
result.uvr = vec2(uv.x * result.ratio.x, uv.y);
result.mouse = vec2(params.mouse.x / params.screen.x, params.mouse.y / params.screen.y);
result.mouse = result.mouse * vec2(1.,-1.) - vec2(0., -1.); // flip and move up
return result;
}
`;const size_4_align_4={size:4,align:4};const size_8_align_8={size:8,align:8};const size_12_align_16={size:12,align:16};const size_16_align_16={size:16,align:16};const size_16_align_8={size:16,align:8};const size_32_align_8={size:32,align:8};const size_24_align_16={size:24,align:16};const size_48_align_16={size:48,align:16};const size_32_align_16={size:32,align:16};const size_64_align_16={size:64,align:16};const typeSizes={"bool":size_4_align_4,"f32":size_4_align_4,"i32":size_4_align_4,"u32":size_4_align_4,"vec2<bool>":size_8_align_8,"vec2<f32>":size_8_align_8,"vec2<i32>":size_8_align_8,"vec2<u32>":size_8_align_8,"vec2f":size_8_align_8,"vec2i":size_8_align_8,"vec2u":size_8_align_8,"vec3<bool>":size_12_align_16,"vec3<f32>":size_12_align_16,"vec3<i32>":size_12_align_16,"vec3<u32>":size_12_align_16,"vec3f":size_12_align_16,"vec3i":size_12_align_16,"vec3u":size_12_align_16,"vec4<bool>":size_16_align_16,"vec4<f32>":size_16_align_16,"vec4<i32>":size_16_align_16,"vec4<u32>":size_16_align_16,"mat2x2<f32>":size_16_align_8,"mat2x3<f32>":size_32_align_8,"mat2x4<f32>":size_32_align_8,"mat3x2<f32>":size_24_align_16,"mat3x3<f32>":size_48_align_16,"mat3x4<f32>":size_48_align_16,"mat4x2<f32>":size_32_align_16,"mat4x3<f32>":size_64_align_16,"mat4x4<f32>":size_64_align_16,"vec4f":size_16_align_16,"vec4i":size_16_align_16,"vec4u":size_16_align_16,"mat2x2f":size_16_align_8,"mat2x3f":size_32_align_8,"mat2x4f":size_32_align_8,"mat3x2f":size_24_align_16,"mat3x3f":size_48_align_16,"mat3x4f":size_48_align_16,"mat4x2f":size_32_align_16,"mat4x3f":size_64_align_16,"mat4x4f":size_64_align_16};const removeCommentsRE=/^(?:(?!\/\/|\/*.*\/).|\n)+/gim;const getStructNameRE=/struct\s+?(\w+)\s*{[^}]+}\n?/g;const insideStructRE=/struct\s+?\w+\s*{([^}]+)}\n?/g;const arrayTypeAndAmountRE=/\s*<\s*([^,]+)\s*,?\s*(\d+)?\s*>/g;const arrayIntegrityRE=/\s*(array\s*<\s*\w+\s*(?:,\s*\d+)?\s*>)\s*,?/g;function removeComments(value){const matches=value.matchAll(removeCommentsRE);let result="";for(const match of matches){const captured=match[0];result+=captured}return result}function getInsideStruct(value){const matches=value.matchAll(insideStructRE);let lines=null;for(const match of matches){lines=match[1].split("\n");lines=lines.map(element=>element.trim()).filter(e=>e!=="")}return lines}function getStructDataByName(value){const matches=value.matchAll(getStructNameRE);let result=new Map;for(const match of matches){const captured=match[0];const name=match[1];const lines=getInsideStruct(captured);const types=lines.map(l=>{const right=l.split(":")[1];let type="";if(isArray(right)){const arrayMatch=right.matchAll(arrayIntegrityRE);type=arrayMatch.next().value[1]}else{type=right.split(",")[0].trim()}return type});const names=lines.map(l=>{const left=l.split(":")[0];let name="";name=left.split(",")[0].trim();return name});result.set(name,{captured,lines,types,unique_types:[...new Set(types)],names})}return result}function getArrayTypeAndAmount(value){const matches=value.matchAll(arrayTypeAndAmountRE);let result=[];for(const match of matches){const type=match[1];const amount=match[2];result.push({type,amount:Number(amount)})}return result}function getPadding(bytes,aligment,nextTypeDataSize){const nextMultiple=bytes+aligment-1&~(aligment-1);const needsPadding=bytes+nextTypeDataSize>nextMultiple;let padAmount=0;if(needsPadding){padAmount=nextMultiple-bytes}return padAmount}function isArray(value){return value.indexOf("array")!=-1}function getArrayAlign(structName,structData){const[d]=getArrayTypeAndAmount(structName);const t=typeSizes[d.type]||structData.get(d.type);if(!t){throw new Error(`${d.type} type has not been declared previously`)}return t.align||t.maxAlign}function getArrayTypeData(currentType,structData){const[d]=getArrayTypeAndAmount(currentType);if(!d){throw`${currentType} seems to have an error, maybe a wrong amount?`}if(d.amount==0){throw new Error(`${currentType} has an amount of 0`)}let currentTypeData={size:16,align:16};if(!!d.amount){const t=typeSizes[d.type];if(t){currentTypeData={size:t.align*d.amount,align:t.align}}else{const sd=structData.get(d.type);if(sd){currentTypeData={size:sd.bytes*d.amount,align:sd.maxAlign}}}}else{const t=typeSizes[d.type]||structData.get(d.type);currentTypeData={size:t.size||t.bytes,align:t.maxAlign}}return currentTypeData}const dataSize=value=>{const noCommentsValue=removeComments(value);const structData=getStructDataByName(noCommentsValue);for(const[structDatumKey,structDatum]of structData){structDatum.unique_types.forEach(ut=>{let maxAlign=structDatum.maxAlign||0;let align=0;if(!typeSizes[ut]){if(isArray(ut)){align=getArrayAlign(ut,structData)}else{const sd=structData.get(ut);align=sd.maxAlign}}else{align=typeSizes[ut].align}maxAlign=align>maxAlign?align:maxAlign;structDatum.maxAlign=maxAlign});let byteCounter=0;structDatum.types.forEach((t,i)=>{const name=structDatum.names[i];const currentType=t;const nextType=structDatum.types[i+1];let currentTypeData=typeSizes[currentType];let nextTypeData=typeSizes[nextType];structDatum.paddings=structDatum.paddings||{};if(!currentTypeData){if(currentType){if(isArray(currentType)){currentTypeData=getArrayTypeData(currentType,structData)}else{const sd=structData.get(currentType);if(sd){currentTypeData={size:sd.bytes,align:sd.maxAlign}}}}}if(!nextTypeData){if(nextType){if(isArray(nextType)){nextTypeData=getArrayTypeData(nextType,structData)}else{const sd=structData.get(nextType);if(sd){nextTypeData={size:sd.bytes,align:sd.maxAlign}}}}}if(!!currentTypeData){byteCounter+=currentTypeData.size;if(currentTypeData.size===structDatum.maxAlign||!nextType){return}}if(!!nextTypeData){const padAmount=getPadding(byteCounter,structDatum.maxAlign,nextTypeData.size);if(padAmount){structDatum.paddings[name]=padAmount/4;byteCounter+=padAmount}}});const padAmount=getPadding(byteCounter,structDatum.maxAlign,16);if(padAmount){structDatum.paddings[""]=padAmount/4;byteCounter+=padAmount}structDatum.bytes=byteCounter}return structData};async function loadImage(src){return new Promise((resolve,reject)=>{const img=new Image;img.src=src;img.onload=()=>resolve(img);img.onerror=err=>reject(err)})}function strToCodes(s){return Array.from(s).map(c=>c.charCodeAt(0))}function sprite(atlas,ctx,index,size,finalIndex){const{width}=atlas;const numColumns=width/size.x;const x=index%numColumns;const y=Math.floor(index/numColumns);ctx.drawImage(atlas,x*size.x,y*size.y,size.x,size.y,size.x*finalIndex,0,size.x,size.y)}function strToImage(str,atlasImg,size,offset=0){const chars=strToCodes(str);const canvas=document.createElement("canvas");canvas.width=chars.length*size.x;canvas.height=size.y;const ctx=canvas.getContext("2d");chars.forEach((c,i)=>sprite(atlasImg,ctx,c+offset,size,i));return canvas.toDataURL("image/png")}class LayersArray extends Array{#buffer=null;#shaderType=null;constructor(...elements){super(...elements)}get buffer(){return this.#buffer}set buffer(v){this.#buffer=v}get shaderType(){return this.#shaderType}set shaderType(v){this.#shaderType=v}}class UniformsArray extends Array{#buffer=null;constructor(...elements){super(...elements)}get buffer(){return this.#buffer}set buffer(v){this.#buffer=v}}class Points{#canvasId=null;#canvas=null;#device=null;#context=null;#presentationFormat=null;#renderPasses=null;#postRenderPasses=[];#vertexBufferInfo=null;#buffer=null;#internal=false;#presentationSize=null;#depthTexture=null;#vertexArray=[];#numColumns=1;#numRows=1;#commandsFinished=[];#renderPassDescriptor=null;#uniforms=new UniformsArray;#storage=[];#readStorage=[];#samplers=[];#textures2d=[];#texturesToCopy=[];#textures2dArray=[];#texturesExternal=[];#texturesStorage2d=[];#bindingTextures=[];#layers=new LayersArray;#originalCanvasWidth=null;#originalCanvasHeigth=null;#clock=new Clock;#time=0;#delta=0;#epoch=0;#mouseX=0;#mouseY=0;#mouseDown=false;#mouseClick=false;#mouseWheel=false;#mouseDelta=[0,0];#fullscreen=false;#fitWindow=false;#lastFitWindow=false;#sounds=[];#events=new Map;#events_ids=0;#dataSize=null;constructor(canvasId){this.#canvasId=canvasId;this.#canvas=document.getElementById(this.#canvasId);if(this.#canvasId){this.#canvas.addEventListener("click",e=>{this.#mouseClick=true});this.#canvas.addEventListener("mousemove",this.#onMouseMove,{passive:true});this.#canvas.addEventListener("mousedown",e=>{this.#mouseDown=true});this.#canvas.addEventListener("mouseup",e=>{this.#mouseDown=false});this.#canvas.addEventListener("wheel",e=>{this.#mouseWheel=true;this.#mouseDelta=[e.deltaX,e.deltaY]},{passive:true});this.#originalCanvasWidth=this.#canvas.clientWidth;this.#originalCanvasHeigth=this.#canvas.clientHeight;window.addEventListener("resize",this.#resizeCanvasToFitWindow,false);document.addEventListener("fullscreenchange",e=>{this.#fullscreen=!!document.fullscreenElement;if(!this.#fullscreen&&!this.#fitWindow){this.#resizeCanvasToDefault()}if(!this.#fullscreen){this.fitWindow=this.#lastFitWindow}})}}#resizeCanvasToFitWindow=()=>{if(this.#fitWindow){const{offsetWidth,offsetHeight}=this.#canvas.parentNode;this.#canvas.width=offsetWidth;this.#canvas.height=offsetHeight;this.#setScreenSize()}};#resizeCanvasToDefault=()=>{this.#canvas.width=this.#originalCanvasWidth;this.#canvas.height=this.#originalCanvasHeigth;this.#setScreenSize()};#setScreenSize=()=>{this.#presentationSize=[this.#canvas.clientWidth,this.#canvas.clientHeight];this.#context.configure({device:this.#device,format:this.#presentationFormat,width:this.#canvas.clientWidth,height:this.#canvas.clientHeight,alphaMode:"premultiplied",usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.COPY_SRC});this.#depthTexture=this.#device.createTexture({size:this.#presentationSize,format:"depth24plus",usage:GPUTextureUsage.RENDER_ATTACHMENT});this.#textures2d.forEach(texture2d=>{if(!texture2d.imageTexture&&texture2d.texture){this.#createTextureBindingToCopy(texture2d)}})};#onMouseMove=e=>{const rect=this.#canvas.getBoundingClientRect();this.#mouseX=e.clientX-rect.left;this.#mouseY=e.clientY-rect.top};setUniform(name,value,structName=null){const uniformToUpdate=this.#nameExists(this.#uniforms,name);if(uniformToUpdate&&structName){throw"`setUniform()` can't set the structName of an already defined uniform."}if(uniformToUpdate){uniformToUpdate.value=value;return}if(structName&&isArray(structName)){throw`${structName} is an array, which is currently not supported for Uniforms.`}const uniform={name:name,value:value,type:structName,size:null,internal:this.#internal};this.#uniforms.push(uniform);return uniform}updateUniforms(arr){arr.forEach(uniform=>{const variable=this.#uniforms.find(v=>v.name===uniform.name);if(!variable){throw"`updateUniform()` can't be called without first `setUniform()`."}variable.value=uniform.value})}setStorage(name,structName,read,shaderType,arrayData){if(this.#nameExists(this.#storage,name)){throw`\`setStorage()\` You have already defined \`${name}\``}const storage={mapped:!!arrayData,name:name,structName:structName,shaderType:shaderType,read:read,buffer:null,internal:this.#internal};this.#storage.push(storage);return storage}setStorageMap(name,arrayData,structName,read=false,shaderType=null){const storageToUpdate=this.#nameExists(this.#storage,name);if(storageToUpdate){storageToUpdate.array=arrayData;return storageToUpdate}const storage={mapped:true,name:name,structName:structName,shaderType:shaderType,array:arrayData,buffer:null,read:read,internal:this.#internal};this.#storage.push(storage);return storage}async readStorage(name){let storageItem=this.#readStorage.find(storageItem=>storageItem.name===name);let arrayBuffer=null;let arrayBufferCopy=null;if(storageItem){await storageItem.buffer.mapAsync(GPUMapMode.READ);arrayBuffer=storageItem.buffer.getMappedRange();arrayBufferCopy=new Float32Array(arrayBuffer.slice(0));storageItem.buffer.unmap()}return arrayBufferCopy}setLayers(numLayers,shaderType){for(let layerIndex=0;layerIndex<numLayers;layerIndex++){this.#layers.shaderType=shaderType;this.#layers.push({name:`layer${layerIndex}`,size:this.#canvas.width*this.#canvas.height,structName:"vec4f",structSize:16,array:null,buffer:null,internal:this.#internal})}}#nameExists(arrayOfObjects,name){return arrayOfObjects.find(obj=>obj.name==name)}setSampler(name,descriptor,shaderType){if("sampler"==name){throw"setSampler: `name` can not be sampler since is a WebGPU keyword."}const exists=this.#nameExists(this.#samplers,name);if(exists){console.warn(`setSampler: \`${name}\` already exists.`);return exists}descriptor=descriptor||{addressModeU:"clamp-to-edge",addressModeV:"clamp-to-edge",magFilter:"linear",minFilter:"linear",mipmapFilter:"linear"};const sampler={name:name,descriptor:descriptor,shaderType:shaderType,resource:null,internal:this.#internal};this.#samplers.push(sampler);return sampler}setTexture2d(name,copyCurrentTexture,shaderType,renderPassIndex){const exists=this.#nameExists(this.#textures2d,name);if(exists){console.warn(`setTexture2d: \`${name}\` already exists.`);return exists}const texture2d={name:name,copyCurrentTexture:copyCurrentTexture,shaderType:shaderType,texture:null,renderPassIndex:renderPassIndex,internal:this.#internal};this.#textures2d.push(texture2d);return texture2d}copyTexture(nameTextureA,nameTextureB){const texture2d_A=this.#nameExists(this.#textures2d,nameTextureA);const texture2d_B=this.#nameExists(this.#textures2d,nameTextureB);if(!(texture2d_A&&texture2d_B)){console.error("One of the textures does not exist.")}const a=texture2d_A.texture;const cubeTexture=this.#device.createTexture({size:[a.width,a.height,1],format:"rgba8unorm",usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT});texture2d_B.texture=cubeTexture;this.#texturesToCopy.push({a,b:texture2d_B.texture})}async setTextureImage(name,path,shaderType=null){const texture2dToUpdate=this.#nameExists(this.#textures2d,name);const response=await fetch(path);const blob=await response.blob();const imageBitmap=await createImageBitmap(blob);if(texture2dToUpdate){if(shaderType){throw"`setTextureImage()` the param `shaderType` should not be updated after its creation."}texture2dToUpdate.imageTexture.bitmap=imageBitmap;const cubeTexture=this.#device.createTexture({size:[imageBitmap.width,imageBitmap.height,1],format:"rgba8unorm",usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_SRC|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT});this.#device.queue.copyExternalImageToTexture({source:imageBitmap},{texture:cubeTexture},[imageBitmap.width,imageBitmap.height]);texture2dToUpdate.texture=cubeTexture;return texture2dToUpdate}const texture2d={name:name,copyCurrentTexture:false,shaderType:shaderType,texture:null,imageTexture:{bitmap:imageBitmap},internal:this.#internal};this.#textures2d.push(texture2d);return texture2d}async setTextureString(name,text,path,size,offset=0,shaderType=null){const atlas=await loadImage(path);const textImg=strToImage(text,atlas,size,offset);return this.setTextureImage(name,textImg,shaderType)}async setTextureImageArray(name,paths,shaderType){if(this.#nameExists(this.#textures2dArray,name)){return}const imageBitmaps=[];for await(const path of paths){const response=await fetch(path);const blob=await response.blob();imageBitmaps.push(await createImageBitmap(blob))}this.#textures2dArray.push({name:name,copyCurrentTexture:false,shaderType:shaderType,texture:null,imageTextures:{bitmaps:imageBitmaps},internal:this.#internal})}async setTextureVideo(name,path,shaderType){if(this.#nameExists(this.#texturesExternal,name)){throw`setTextureVideo: ${name} already exists.`}const video=document.createElement("video");video.loop=true;video.autoplay=true;video.muted=true;video.src=new URL(path,import.meta.url).toString();await video.play();const textureExternal={name:name,shaderType:shaderType,video:video,internal:this.#internal};this.#texturesExternal.push(textureExternal);return textureExternal}async setTextureWebcam(name,shaderType){if(this.#nameExists(this.#texturesExternal,name)){throw`setTextureWebcam: ${name} already exists.`}const video=document.createElement("video");video.muted=true;if(navigator.mediaDevices.getUserMedia){await navigator.mediaDevices.getUserMedia({video:true}).then(async function(stream){video.srcObject=stream;await video.play()}).catch(function(err){console.log(err)})}const textureExternal={name:name,shaderType:shaderType,video:video,internal:this.#internal};this.#texturesExternal.push(textureExternal);return textureExternal}setAudio(name,path,volume,loop,autoplay){const audio=new Audio(path);audio.volume=volume;audio.autoplay=autoplay;audio.loop=loop;const sound={name:name,path:path,audio:audio,analyser:null,data:null};const audioContext=new AudioContext;const resume=_=>{audioContext.resume()};if(audioContext.state==="suspended"){document.body.addEventListener("touchend",resume,false);document.body.addEventListener("click",resume,false)}const source=audioContext.createMediaElementSource(audio);const analyser=audioContext.createAnalyser();analyser.fftSize=2048;source.connect(analyser);analyser.connect(audioContext.destination);const bufferLength=analyser.fftSize;const data=new Uint8Array(bufferLength);analyser.getByteFrequencyData(data);this.setStorageMap(name,data,"Sound");this.setUniform(`${name}Length`,analyser.frequencyBinCount);sound.analyser=analyser;sound.data=data;this.#sounds.push(sound);return audio}setTextureStorage2d(name,shaderType){if(this.#nameExists(this.#texturesStorage2d,name)){throw`setTextureStorage2d: ${name} already exists.`}const texturesStorage2d={name:name,shaderType:shaderType,texture:null,internal:this.#internal};this.#texturesStorage2d.push(texturesStorage2d);return texturesStorage2d}setBindingTexture(computeName,fragmentName,size){const bindingTexture={compute:{name:computeName,shaderType:ShaderType.COMPUTE},fragment:{name:fragmentName,shaderType:ShaderType.FRAGMENT},texture:null,size:size,internal:this.#internal};this.#bindingTextures.push(bindingTexture);return bindingTexture}addEventListener(name,callback,structSize){const data=new Uint8Array(Array(structSize+4).fill(0));this.setStorageMap(name,data,"Event",true);this.#events.set(this.#events_ids,{id:this.#events_ids,name:name,callback:callback});++this.#events_ids}_setInternal(value){this.#internal=value}#createDynamicGroupBindings(shaderType,internal){internal=internal||false;if(!shaderType){throw"`ShaderType` is required"}const groupId=0;let dynamicGroupBindings="";let bindingIndex=0;if(this.#uniforms.length){dynamicGroupBindings+=`@group(${groupId}) @binding(${bindingIndex}) var <uniform> params: Params;
`;bindingIndex+=1}this.#storage.forEach(storageItem=>{const internalCheck=internal==storageItem.internal;if(!storageItem.shaderType&&internalCheck||storageItem.shaderType==shaderType&&internalCheck){const T=storageItem.structName;dynamicGroupBindings+=`@group(${groupId}) @binding(${bindingIndex}) var <storage, read_write> ${storageItem.name}: ${T};
`;bindingIndex+=1}});if(this.#layers.length){if(!this.#layers.shaderType||this.#layers.shaderType==shaderType){let totalSize=0;this.#layers.forEach(layerItem=>totalSize+=layerItem.size);dynamicGroupBindings+=`@group(${groupId}) @binding(${bindingIndex}) var <storage, read_write> layers: array<array<vec4f, ${totalSize}>>;
`;bindingIndex+=1}}this.#samplers.forEach((sampler,index)=>{const inte