From 0568e9d982af2889c0b8c88e3dc6a6a2e3a40ce6 Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Sat, 25 Nov 2023 10:20:41 -0800 Subject: [PATCH] udpate stuff --- .eslintignore | 2 - .eslintrc.cjs | 1 + README.md | 391 +- build/pull-vsa.js | 21 + build/serve.js | 1 + build/tsconfig-serve.json | 2 +- examples/3rdParty/twgl-full.module.js | 10085 ++++++++++++++++ examples/js/index/VSAEffect.js | 343 + examples/js/index/effects.js | 27 + examples/js/index/effects/admo.js | 390 + examples/js/index/effects/bwow.js | 328 + examples/js/index/effects/codez.js | 636 + examples/js/index/effects/cyty.js | 675 ++ examples/js/index/effects/discus.js | 247 + examples/js/index/effects/dotto-chouhoukei.js | 391 + examples/js/index/effects/hexit2.js | 224 + examples/js/index/effects/loop-test.js | 323 + examples/js/index/effects/pookymelon.js | 388 + examples/js/index/effects/rollin.js | 629 + examples/js/index/effects/starfield.js | 317 + examples/js/index/effects/ung.js | 621 + examples/js/index/index.js | 293 + examples/js/{index-umd.js => lots-umd.js} | 408 +- examples/js/{index.js => lots.js} | 42 +- examples/js/{cube.js => model.js} | 11 +- examples/layout/layout.js | 30 +- index-umd.html => examples/lots-umd.html | 4 +- examples/lots.html | 99 + images/muigui-screenshot.png | Bin 80026 -> 29056 bytes index.html | 688 +- package-lock.json | 283 +- package.json | 2 +- src/controllers/Button.js | 3 + src/controllers/Canvas.js | 4 +- src/controllers/ColorChooser.js | 23 +- src/controllers/Folder.js | 1 + src/controllers/PopDownController.js | 5 +- src/controllers/Text.js | 2 +- src/controllers/TextNumber.js | 2 +- src/controllers/create-controller.js | 3 + src/esm.ts | 8 + src/libs/color-utils.js | 44 + src/libs/graph.js | 42 + src/libs/monitor.js | 5 + src/muigui.js | 19 +- src/styles/muigui.css.js | 148 +- src/views/NumberView.js | 13 +- src/views/TextView.js | 8 +- tsconfig.json | 10 +- 49 files changed, 17794 insertions(+), 448 deletions(-) create mode 100644 build/pull-vsa.js create mode 100644 examples/3rdParty/twgl-full.module.js create mode 100644 examples/js/index/VSAEffect.js create mode 100644 examples/js/index/effects.js create mode 100644 examples/js/index/effects/admo.js create mode 100644 examples/js/index/effects/bwow.js create mode 100644 examples/js/index/effects/codez.js create mode 100644 examples/js/index/effects/cyty.js create mode 100644 examples/js/index/effects/discus.js create mode 100644 examples/js/index/effects/dotto-chouhoukei.js create mode 100644 examples/js/index/effects/hexit2.js create mode 100644 examples/js/index/effects/loop-test.js create mode 100644 examples/js/index/effects/pookymelon.js create mode 100644 examples/js/index/effects/rollin.js create mode 100644 examples/js/index/effects/starfield.js create mode 100644 examples/js/index/effects/ung.js create mode 100644 examples/js/index/index.js rename examples/js/{index-umd.js => lots-umd.js} (67%) rename examples/js/{index.js => lots.js} (97%) rename examples/js/{cube.js => model.js} (95%) rename index-umd.html => examples/lots-umd.html (95%) create mode 100644 examples/lots.html create mode 100644 src/libs/graph.js create mode 100644 src/libs/monitor.js diff --git a/.eslintignore b/.eslintignore index e4fc7fe..43821ad 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,5 @@ test/js test/mocha.js -/webgl-lint.js dist examples/3rdParty out -examples/**/*.js diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c3b9a4f..080f24a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,4 +1,5 @@ /* global module */ +/* global __dirname */ module.exports = { parser: '@typescript-eslint/parser', diff --git a/README.md b/README.md index a70362c..4c7410f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,11 @@ -# muigui - -# NOT READY for USE - - +I'm under sure how much time I'll continue to put into this. +I get the feeling other people are far more motivated to make +UIs. Maybe if I'm lucky they'll take some inspiration from +the thoughts above and I'll find they've covered it all. ## License diff --git a/build/pull-vsa.js b/build/pull-vsa.js new file mode 100644 index 0000000..000c98b --- /dev/null +++ b/build/pull-vsa.js @@ -0,0 +1,21 @@ +import fs from 'fs'; +import process from 'process'; + +// node build/pull-vsa.js ...url-to-vsa +const urls = process.argv.slice(2); + +for (const url of urls) { + const req = await fetch(`${url}?format=json`); + const data = await req.json(); + const filename = `examples/js/index/effects/${data.name}.js`; + const shader = data.settings.shader; + data.settings.shader = '--shader--'; + const s = `\ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default ${JSON.stringify(data, null, 2) + .replace(/"(.*?)":/g, '$1:') + .replace('"--shader--"', `\`${shader}\``)};\n`; + console.log('write', filename); + fs.writeFileSync(filename, s); +} \ No newline at end of file diff --git a/build/serve.js b/build/serve.js index a373cb6..db9e638 100644 --- a/build/serve.js +++ b/build/serve.js @@ -5,6 +5,7 @@ import chokidar from 'chokidar'; spawn('./node_modules/.bin/tsc', [ '--watch', + '--project', 'build/tsconfig-serve.json', ], { stdio: 'inherit', }); diff --git a/build/tsconfig-serve.json b/build/tsconfig-serve.json index 76f2768..91bcac4 100644 --- a/build/tsconfig-serve.json +++ b/build/tsconfig-serve.json @@ -1,6 +1,6 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "outDir": "out/src" + "outDir": "../out" } } diff --git a/examples/3rdParty/twgl-full.module.js b/examples/3rdParty/twgl-full.module.js new file mode 100644 index 0000000..dd530b7 --- /dev/null +++ b/examples/3rdParty/twgl-full.module.js @@ -0,0 +1,10085 @@ +/* @license twgl.js 5.5.3 Copyright (c) 2015, Gregg Tavares All Rights Reserved. +Available via the MIT license. +see: http://github.com/greggman/twgl.js for details */ +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/** + * + * Vec3 math math functions. + * + * Almost all functions take an optional `dst` argument. If it is not passed in the + * functions will create a new Vec3. In other words you can do this + * + * var v = v3.cross(v1, v2); // Creates a new Vec3 with the cross product of v1 x v2. + * + * or + * + * var v = v3.create(); + * v3.cross(v1, v2, v); // Puts the cross product of v1 x v2 in v + * + * The first style is often easier but depending on where it's used it generates garbage where + * as there is almost never allocation with the second style. + * + * It is always save to pass any vector as the destination. So for example + * + * v3.cross(v1, v2, v1); // Puts the cross product of v1 x v2 in v1 + * + * @module twgl/v3 + */ + +let VecType = Float32Array; + +/** + * A JavaScript array with 3 values or a Float32Array with 3 values. + * When created by the library will create the default type which is `Float32Array` + * but can be set by calling {@link module:twgl/v3.setDefaultType}. + * @typedef {(number[]|Float32Array)} Vec3 + * @memberOf module:twgl/v3 + */ + +/** + * Sets the type this library creates for a Vec3 + * @param {constructor} ctor the constructor for the type. Either `Float32Array` or `Array` + * @return {constructor} previous constructor for Vec3 + * @memberOf module:twgl/v3 + */ +function setDefaultType$1(ctor) { + const oldType = VecType; + VecType = ctor; + return oldType; +} + +/** + * Creates a vec3; may be called with x, y, z to set initial values. + * @param {number} [x] Initial x value. + * @param {number} [y] Initial y value. + * @param {number} [z] Initial z value. + * @return {module:twgl/v3.Vec3} the created vector + * @memberOf module:twgl/v3 + */ +function create$1(x, y, z) { + const dst = new VecType(3); + if (x) { + dst[0] = x; + } + if (y) { + dst[1] = y; + } + if (z) { + dst[2] = z; + } + return dst; +} + +/** + * Adds two vectors; assumes a and b have the same dimension. + * @param {module:twgl/v3.Vec3} a Operand vector. + * @param {module:twgl/v3.Vec3} b Operand vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} A vector tha tis the sum of a and b. + * @memberOf module:twgl/v3 + */ +function add(a, b, dst) { + dst = dst || new VecType(3); + + dst[0] = a[0] + b[0]; + dst[1] = a[1] + b[1]; + dst[2] = a[2] + b[2]; + + return dst; +} + +/** + * Subtracts two vectors. + * @param {module:twgl/v3.Vec3} a Operand vector. + * @param {module:twgl/v3.Vec3} b Operand vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} A vector that is the difference of a and b. + * @memberOf module:twgl/v3 + */ +function subtract(a, b, dst) { + dst = dst || new VecType(3); + + dst[0] = a[0] - b[0]; + dst[1] = a[1] - b[1]; + dst[2] = a[2] - b[2]; + + return dst; +} + +/** + * Performs linear interpolation on two vectors. + * Given vectors a and b and interpolation coefficient t, returns + * a + t * (b - a). + * @param {module:twgl/v3.Vec3} a Operand vector. + * @param {module:twgl/v3.Vec3} b Operand vector. + * @param {number} t Interpolation coefficient. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} The linear interpolated result. + * @memberOf module:twgl/v3 + */ +function lerp(a, b, t, dst) { + dst = dst || new VecType(3); + + dst[0] = a[0] + t * (b[0] - a[0]); + dst[1] = a[1] + t * (b[1] - a[1]); + dst[2] = a[2] + t * (b[2] - a[2]); + + return dst; +} + +/** + * Performs linear interpolation on two vectors. + * Given vectors a and b and interpolation coefficient vector t, returns + * a + t * (b - a). + * @param {module:twgl/v3.Vec3} a Operand vector. + * @param {module:twgl/v3.Vec3} b Operand vector. + * @param {module:twgl/v3.Vec3} t Interpolation coefficients vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} the linear interpolated result. + * @memberOf module:twgl/v3 + */ +function lerpV(a, b, t, dst) { + dst = dst || new VecType(3); + + dst[0] = a[0] + t[0] * (b[0] - a[0]); + dst[1] = a[1] + t[1] * (b[1] - a[1]); + dst[2] = a[2] + t[2] * (b[2] - a[2]); + + return dst; +} + +/** + * Return max values of two vectors. + * Given vectors a and b returns + * [max(a[0], b[0]), max(a[1], b[1]), max(a[2], b[2])]. + * @param {module:twgl/v3.Vec3} a Operand vector. + * @param {module:twgl/v3.Vec3} b Operand vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} The max components vector. + * @memberOf module:twgl/v3 + */ +function max(a, b, dst) { + dst = dst || new VecType(3); + + dst[0] = Math.max(a[0], b[0]); + dst[1] = Math.max(a[1], b[1]); + dst[2] = Math.max(a[2], b[2]); + + return dst; +} + +/** + * Return min values of two vectors. + * Given vectors a and b returns + * [min(a[0], b[0]), min(a[1], b[1]), min(a[2], b[2])]. + * @param {module:twgl/v3.Vec3} a Operand vector. + * @param {module:twgl/v3.Vec3} b Operand vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} The min components vector. + * @memberOf module:twgl/v3 + */ +function min(a, b, dst) { + dst = dst || new VecType(3); + + dst[0] = Math.min(a[0], b[0]); + dst[1] = Math.min(a[1], b[1]); + dst[2] = Math.min(a[2], b[2]); + + return dst; +} + +/** + * Multiplies a vector by a scalar. + * @param {module:twgl/v3.Vec3} v The vector. + * @param {number} k The scalar. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} The scaled vector. + * @memberOf module:twgl/v3 + */ +function mulScalar(v, k, dst) { + dst = dst || new VecType(3); + + dst[0] = v[0] * k; + dst[1] = v[1] * k; + dst[2] = v[2] * k; + + return dst; +} + +/** + * Divides a vector by a scalar. + * @param {module:twgl/v3.Vec3} v The vector. + * @param {number} k The scalar. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} The scaled vector. + * @memberOf module:twgl/v3 + */ +function divScalar(v, k, dst) { + dst = dst || new VecType(3); + + dst[0] = v[0] / k; + dst[1] = v[1] / k; + dst[2] = v[2] / k; + + return dst; +} + +/** + * Computes the cross product of two vectors; assumes both vectors have + * three entries. + * @param {module:twgl/v3.Vec3} a Operand vector. + * @param {module:twgl/v3.Vec3} b Operand vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} The vector of a cross b. + * @memberOf module:twgl/v3 + */ +function cross(a, b, dst) { + dst = dst || new VecType(3); + + const t1 = a[2] * b[0] - a[0] * b[2]; + const t2 = a[0] * b[1] - a[1] * b[0]; + dst[0] = a[1] * b[2] - a[2] * b[1]; + dst[1] = t1; + dst[2] = t2; + + return dst; +} + +/** + * Computes the dot product of two vectors; assumes both vectors have + * three entries. + * @param {module:twgl/v3.Vec3} a Operand vector. + * @param {module:twgl/v3.Vec3} b Operand vector. + * @return {number} dot product + * @memberOf module:twgl/v3 + */ +function dot(a, b) { + return (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]); +} + +/** + * Computes the length of vector + * @param {module:twgl/v3.Vec3} v vector. + * @return {number} length of vector. + * @memberOf module:twgl/v3 + */ +function length$1(v) { + return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); +} + +/** + * Computes the square of the length of vector + * @param {module:twgl/v3.Vec3} v vector. + * @return {number} square of the length of vector. + * @memberOf module:twgl/v3 + */ +function lengthSq(v) { + return v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; +} + +/** + * Computes the distance between 2 points + * @param {module:twgl/v3.Vec3} a vector. + * @param {module:twgl/v3.Vec3} b vector. + * @return {number} distance between a and b + * @memberOf module:twgl/v3 + */ +function distance(a, b) { + const dx = a[0] - b[0]; + const dy = a[1] - b[1]; + const dz = a[2] - b[2]; + return Math.sqrt(dx * dx + dy * dy + dz * dz); +} + +/** + * Computes the square of the distance between 2 points + * @param {module:twgl/v3.Vec3} a vector. + * @param {module:twgl/v3.Vec3} b vector. + * @return {number} square of the distance between a and b + * @memberOf module:twgl/v3 + */ +function distanceSq(a, b) { + const dx = a[0] - b[0]; + const dy = a[1] - b[1]; + const dz = a[2] - b[2]; + return dx * dx + dy * dy + dz * dz; +} + +/** + * Divides a vector by its Euclidean length and returns the quotient. + * @param {module:twgl/v3.Vec3} a The vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} The normalized vector. + * @memberOf module:twgl/v3 + */ +function normalize(a, dst) { + dst = dst || new VecType(3); + + const lenSq = a[0] * a[0] + a[1] * a[1] + a[2] * a[2]; + const len = Math.sqrt(lenSq); + if (len > 0.00001) { + dst[0] = a[0] / len; + dst[1] = a[1] / len; + dst[2] = a[2] / len; + } else { + dst[0] = 0; + dst[1] = 0; + dst[2] = 0; + } + + return dst; +} + +/** + * Negates a vector. + * @param {module:twgl/v3.Vec3} v The vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} -v. + * @memberOf module:twgl/v3 + */ +function negate$1(v, dst) { + dst = dst || new VecType(3); + + dst[0] = -v[0]; + dst[1] = -v[1]; + dst[2] = -v[2]; + + return dst; +} + +/** + * Copies a vector. + * @param {module:twgl/v3.Vec3} v The vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} A copy of v. + * @memberOf module:twgl/v3 + */ +function copy$1(v, dst) { + dst = dst || new VecType(3); + + dst[0] = v[0]; + dst[1] = v[1]; + dst[2] = v[2]; + + return dst; +} + +/** + * Multiplies a vector by another vector (component-wise); assumes a and + * b have the same length. + * @param {module:twgl/v3.Vec3} a Operand vector. + * @param {module:twgl/v3.Vec3} b Operand vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} The vector of products of entries of a and + * b. + * @memberOf module:twgl/v3 + */ +function multiply$1(a, b, dst) { + dst = dst || new VecType(3); + + dst[0] = a[0] * b[0]; + dst[1] = a[1] * b[1]; + dst[2] = a[2] * b[2]; + + return dst; +} + +/** + * Divides a vector by another vector (component-wise); assumes a and + * b have the same length. + * @param {module:twgl/v3.Vec3} a Operand vector. + * @param {module:twgl/v3.Vec3} b Operand vector. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not new one is created. + * @return {module:twgl/v3.Vec3} The vector of quotients of entries of a and + * b. + * @memberOf module:twgl/v3 + */ +function divide(a, b, dst) { + dst = dst || new VecType(3); + + dst[0] = a[0] / b[0]; + dst[1] = a[1] / b[1]; + dst[2] = a[2] / b[2]; + + return dst; +} + +var v3 = /*#__PURE__*/Object.freeze({ + __proto__: null, + add: add, + copy: copy$1, + create: create$1, + cross: cross, + distance: distance, + distanceSq: distanceSq, + divide: divide, + divScalar: divScalar, + dot: dot, + lerp: lerp, + lerpV: lerpV, + length: length$1, + lengthSq: lengthSq, + max: max, + min: min, + mulScalar: mulScalar, + multiply: multiply$1, + negate: negate$1, + normalize: normalize, + setDefaultType: setDefaultType$1, + subtract: subtract +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/** + * 4x4 Matrix math math functions. + * + * Almost all functions take an optional `dst` argument. If it is not passed in the + * functions will create a new matrix. In other words you can do this + * + * const mat = m4.translation([1, 2, 3]); // Creates a new translation matrix + * + * or + * + * const mat = m4.create(); + * m4.translation([1, 2, 3], mat); // Puts translation matrix in mat. + * + * The first style is often easier but depending on where it's used it generates garbage where + * as there is almost never allocation with the second style. + * + * It is always save to pass any matrix as the destination. So for example + * + * const mat = m4.identity(); + * const trans = m4.translation([1, 2, 3]); + * m4.multiply(mat, trans, mat); // Multiplies mat * trans and puts result in mat. + * + * @module twgl/m4 + */ +let MatType = Float32Array; + +/** + * A JavaScript array with 16 values or a Float32Array with 16 values. + * When created by the library will create the default type which is `Float32Array` + * but can be set by calling {@link module:twgl/m4.setDefaultType}. + * @typedef {(number[]|Float32Array)} Mat4 + * @memberOf module:twgl/m4 + */ + +/** + * Sets the type this library creates for a Mat4 + * @param {constructor} ctor the constructor for the type. Either `Float32Array` or `Array` + * @return {constructor} previous constructor for Mat4 + * @memberOf module:twgl/m4 + */ +function setDefaultType(ctor) { + const oldType = MatType; + MatType = ctor; + return oldType; +} + +/** + * Negates a matrix. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} -m. + * @memberOf module:twgl/m4 + */ +function negate(m, dst) { + dst = dst || new MatType(16); + + dst[ 0] = -m[ 0]; + dst[ 1] = -m[ 1]; + dst[ 2] = -m[ 2]; + dst[ 3] = -m[ 3]; + dst[ 4] = -m[ 4]; + dst[ 5] = -m[ 5]; + dst[ 6] = -m[ 6]; + dst[ 7] = -m[ 7]; + dst[ 8] = -m[ 8]; + dst[ 9] = -m[ 9]; + dst[10] = -m[10]; + dst[11] = -m[11]; + dst[12] = -m[12]; + dst[13] = -m[13]; + dst[14] = -m[14]; + dst[15] = -m[15]; + + return dst; +} + +/** + * Creates a matrix. + * @return {module:twgl/m4.Mat4} A new matrix. + * @memberOf module:twgl/m4 + */ +function create() { + return new MatType(16).fill(0); +} + +/** + * Copies a matrix. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/m4.Mat4} [dst] The matrix. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} A copy of m. + * @memberOf module:twgl/m4 + */ +function copy(m, dst) { + dst = dst || new MatType(16); + + dst[ 0] = m[ 0]; + dst[ 1] = m[ 1]; + dst[ 2] = m[ 2]; + dst[ 3] = m[ 3]; + dst[ 4] = m[ 4]; + dst[ 5] = m[ 5]; + dst[ 6] = m[ 6]; + dst[ 7] = m[ 7]; + dst[ 8] = m[ 8]; + dst[ 9] = m[ 9]; + dst[10] = m[10]; + dst[11] = m[11]; + dst[12] = m[12]; + dst[13] = m[13]; + dst[14] = m[14]; + dst[15] = m[15]; + + return dst; +} + +/** + * Creates an n-by-n identity matrix. + * + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} An n-by-n identity matrix. + * @memberOf module:twgl/m4 + */ +function identity(dst) { + dst = dst || new MatType(16); + + dst[ 0] = 1; + dst[ 1] = 0; + dst[ 2] = 0; + dst[ 3] = 0; + dst[ 4] = 0; + dst[ 5] = 1; + dst[ 6] = 0; + dst[ 7] = 0; + dst[ 8] = 0; + dst[ 9] = 0; + dst[10] = 1; + dst[11] = 0; + dst[12] = 0; + dst[13] = 0; + dst[14] = 0; + dst[15] = 1; + + return dst; +} + +/** + * Takes the transpose of a matrix. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The transpose of m. + * @memberOf module:twgl/m4 + */ + function transpose(m, dst) { + dst = dst || new MatType(16); + if (dst === m) { + let t; + + t = m[1]; + m[1] = m[4]; + m[4] = t; + + t = m[2]; + m[2] = m[8]; + m[8] = t; + + t = m[3]; + m[3] = m[12]; + m[12] = t; + + t = m[6]; + m[6] = m[9]; + m[9] = t; + + t = m[7]; + m[7] = m[13]; + m[13] = t; + + t = m[11]; + m[11] = m[14]; + m[14] = t; + return dst; + } + + const m00 = m[0 * 4 + 0]; + const m01 = m[0 * 4 + 1]; + const m02 = m[0 * 4 + 2]; + const m03 = m[0 * 4 + 3]; + const m10 = m[1 * 4 + 0]; + const m11 = m[1 * 4 + 1]; + const m12 = m[1 * 4 + 2]; + const m13 = m[1 * 4 + 3]; + const m20 = m[2 * 4 + 0]; + const m21 = m[2 * 4 + 1]; + const m22 = m[2 * 4 + 2]; + const m23 = m[2 * 4 + 3]; + const m30 = m[3 * 4 + 0]; + const m31 = m[3 * 4 + 1]; + const m32 = m[3 * 4 + 2]; + const m33 = m[3 * 4 + 3]; + + dst[ 0] = m00; + dst[ 1] = m10; + dst[ 2] = m20; + dst[ 3] = m30; + dst[ 4] = m01; + dst[ 5] = m11; + dst[ 6] = m21; + dst[ 7] = m31; + dst[ 8] = m02; + dst[ 9] = m12; + dst[10] = m22; + dst[11] = m32; + dst[12] = m03; + dst[13] = m13; + dst[14] = m23; + dst[15] = m33; + + return dst; +} + +/** + * Computes the inverse of a 4-by-4 matrix. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The inverse of m. + * @memberOf module:twgl/m4 + */ +function inverse(m, dst) { + dst = dst || new MatType(16); + + const m00 = m[0 * 4 + 0]; + const m01 = m[0 * 4 + 1]; + const m02 = m[0 * 4 + 2]; + const m03 = m[0 * 4 + 3]; + const m10 = m[1 * 4 + 0]; + const m11 = m[1 * 4 + 1]; + const m12 = m[1 * 4 + 2]; + const m13 = m[1 * 4 + 3]; + const m20 = m[2 * 4 + 0]; + const m21 = m[2 * 4 + 1]; + const m22 = m[2 * 4 + 2]; + const m23 = m[2 * 4 + 3]; + const m30 = m[3 * 4 + 0]; + const m31 = m[3 * 4 + 1]; + const m32 = m[3 * 4 + 2]; + const m33 = m[3 * 4 + 3]; + const tmp_0 = m22 * m33; + const tmp_1 = m32 * m23; + const tmp_2 = m12 * m33; + const tmp_3 = m32 * m13; + const tmp_4 = m12 * m23; + const tmp_5 = m22 * m13; + const tmp_6 = m02 * m33; + const tmp_7 = m32 * m03; + const tmp_8 = m02 * m23; + const tmp_9 = m22 * m03; + const tmp_10 = m02 * m13; + const tmp_11 = m12 * m03; + const tmp_12 = m20 * m31; + const tmp_13 = m30 * m21; + const tmp_14 = m10 * m31; + const tmp_15 = m30 * m11; + const tmp_16 = m10 * m21; + const tmp_17 = m20 * m11; + const tmp_18 = m00 * m31; + const tmp_19 = m30 * m01; + const tmp_20 = m00 * m21; + const tmp_21 = m20 * m01; + const tmp_22 = m00 * m11; + const tmp_23 = m10 * m01; + + const t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - + (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31); + const t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - + (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31); + const t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - + (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31); + const t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - + (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21); + + const d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3); + + dst[ 0] = d * t0; + dst[ 1] = d * t1; + dst[ 2] = d * t2; + dst[ 3] = d * t3; + dst[ 4] = d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - + (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)); + dst[ 5] = d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - + (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)); + dst[ 6] = d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - + (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)); + dst[ 7] = d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - + (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)); + dst[ 8] = d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - + (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)); + dst[ 9] = d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - + (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)); + dst[10] = d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - + (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)); + dst[11] = d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - + (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)); + dst[12] = d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - + (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)); + dst[13] = d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - + (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)); + dst[14] = d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - + (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)); + dst[15] = d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - + (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02)); + + return dst; +} + +/** + * Multiplies two 4-by-4 matrices with a on the left and b on the right + * @param {module:twgl/m4.Mat4} a The matrix on the left. + * @param {module:twgl/m4.Mat4} b The matrix on the right. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The matrix product of a and b. + * @memberOf module:twgl/m4 + */ +function multiply(a, b, dst) { + dst = dst || new MatType(16); + + const a00 = a[0]; + const a01 = a[1]; + const a02 = a[2]; + const a03 = a[3]; + const a10 = a[ 4 + 0]; + const a11 = a[ 4 + 1]; + const a12 = a[ 4 + 2]; + const a13 = a[ 4 + 3]; + const a20 = a[ 8 + 0]; + const a21 = a[ 8 + 1]; + const a22 = a[ 8 + 2]; + const a23 = a[ 8 + 3]; + const a30 = a[12 + 0]; + const a31 = a[12 + 1]; + const a32 = a[12 + 2]; + const a33 = a[12 + 3]; + const b00 = b[0]; + const b01 = b[1]; + const b02 = b[2]; + const b03 = b[3]; + const b10 = b[ 4 + 0]; + const b11 = b[ 4 + 1]; + const b12 = b[ 4 + 2]; + const b13 = b[ 4 + 3]; + const b20 = b[ 8 + 0]; + const b21 = b[ 8 + 1]; + const b22 = b[ 8 + 2]; + const b23 = b[ 8 + 3]; + const b30 = b[12 + 0]; + const b31 = b[12 + 1]; + const b32 = b[12 + 2]; + const b33 = b[12 + 3]; + + dst[ 0] = a00 * b00 + a10 * b01 + a20 * b02 + a30 * b03; + dst[ 1] = a01 * b00 + a11 * b01 + a21 * b02 + a31 * b03; + dst[ 2] = a02 * b00 + a12 * b01 + a22 * b02 + a32 * b03; + dst[ 3] = a03 * b00 + a13 * b01 + a23 * b02 + a33 * b03; + dst[ 4] = a00 * b10 + a10 * b11 + a20 * b12 + a30 * b13; + dst[ 5] = a01 * b10 + a11 * b11 + a21 * b12 + a31 * b13; + dst[ 6] = a02 * b10 + a12 * b11 + a22 * b12 + a32 * b13; + dst[ 7] = a03 * b10 + a13 * b11 + a23 * b12 + a33 * b13; + dst[ 8] = a00 * b20 + a10 * b21 + a20 * b22 + a30 * b23; + dst[ 9] = a01 * b20 + a11 * b21 + a21 * b22 + a31 * b23; + dst[10] = a02 * b20 + a12 * b21 + a22 * b22 + a32 * b23; + dst[11] = a03 * b20 + a13 * b21 + a23 * b22 + a33 * b23; + dst[12] = a00 * b30 + a10 * b31 + a20 * b32 + a30 * b33; + dst[13] = a01 * b30 + a11 * b31 + a21 * b32 + a31 * b33; + dst[14] = a02 * b30 + a12 * b31 + a22 * b32 + a32 * b33; + dst[15] = a03 * b30 + a13 * b31 + a23 * b32 + a33 * b33; + + return dst; +} + +/** + * Sets the translation component of a 4-by-4 matrix to the given + * vector. + * @param {module:twgl/m4.Mat4} a The matrix. + * @param {module:twgl/v3.Vec3} v The vector. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The matrix with translation set. + * @memberOf module:twgl/m4 + */ +function setTranslation(a, v, dst) { + dst = dst || identity(); + if (a !== dst) { + dst[ 0] = a[ 0]; + dst[ 1] = a[ 1]; + dst[ 2] = a[ 2]; + dst[ 3] = a[ 3]; + dst[ 4] = a[ 4]; + dst[ 5] = a[ 5]; + dst[ 6] = a[ 6]; + dst[ 7] = a[ 7]; + dst[ 8] = a[ 8]; + dst[ 9] = a[ 9]; + dst[10] = a[10]; + dst[11] = a[11]; + } + dst[12] = v[0]; + dst[13] = v[1]; + dst[14] = v[2]; + dst[15] = 1; + return dst; +} + +/** + * Returns the translation component of a 4-by-4 matrix as a vector with 3 + * entries. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/v3.Vec3} [dst] vector to hold result. If not passed a new one is created. + * @return {module:twgl/v3.Vec3} The translation component of m. + * @memberOf module:twgl/m4 + */ +function getTranslation(m, dst) { + dst = dst || create$1(); + dst[0] = m[12]; + dst[1] = m[13]; + dst[2] = m[14]; + return dst; +} + +/** + * Returns an axis of a 4x4 matrix as a vector with 3 entries + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {number} axis The axis 0 = x, 1 = y, 2 = z; + * @return {module:twgl/v3.Vec3} [dst] vector. + * @return {module:twgl/v3.Vec3} The axis component of m. + * @memberOf module:twgl/m4 + */ +function getAxis(m, axis, dst) { + dst = dst || create$1(); + const off = axis * 4; + dst[0] = m[off + 0]; + dst[1] = m[off + 1]; + dst[2] = m[off + 2]; + return dst; +} + +/** + * Sets an axis of a 4x4 matrix as a vector with 3 entries + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/v3.Vec3} v the axis vector + * @param {number} axis The axis 0 = x, 1 = y, 2 = z; + * @param {module:twgl/m4.Mat4} [dst] The matrix to set. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The matrix with axis set. + * @memberOf module:twgl/m4 + */ +function setAxis(a, v, axis, dst) { + if (dst !== a) { + dst = copy(a, dst); + } + const off = axis * 4; + dst[off + 0] = v[0]; + dst[off + 1] = v[1]; + dst[off + 2] = v[2]; + return dst; +} + +/** + * Computes a 4-by-4 perspective transformation matrix given the angular height + * of the frustum, the aspect ratio, and the near and far clipping planes. The + * arguments define a frustum extending in the negative z direction. The given + * angle is the vertical angle of the frustum, and the horizontal angle is + * determined to produce the given aspect ratio. The arguments near and far are + * the distances to the near and far clipping planes. Note that near and far + * are not z coordinates, but rather they are distances along the negative + * z-axis. The matrix generated sends the viewing frustum to the unit box. + * We assume a unit box extending from -1 to 1 in the x and y dimensions and + * from 0 to 1 in the z dimension. + * @param {number} fieldOfViewYInRadians The camera angle from top to bottom (in radians). + * @param {number} aspect The aspect ratio width / height. + * @param {number} zNear The depth (negative z coordinate) + * of the near clipping plane. + * @param {number} zFar The depth (negative z coordinate) + * of the far clipping plane. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The perspective matrix. + * @memberOf module:twgl/m4 + */ +function perspective(fieldOfViewYInRadians, aspect, zNear, zFar, dst) { + dst = dst || new MatType(16); + + const f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewYInRadians); + const rangeInv = 1.0 / (zNear - zFar); + + dst[0] = f / aspect; + dst[1] = 0; + dst[2] = 0; + dst[3] = 0; + + dst[4] = 0; + dst[5] = f; + dst[6] = 0; + dst[7] = 0; + + dst[8] = 0; + dst[9] = 0; + dst[10] = (zNear + zFar) * rangeInv; + dst[11] = -1; + + dst[12] = 0; + dst[13] = 0; + dst[14] = zNear * zFar * rangeInv * 2; + dst[15] = 0; + + return dst; +} + +/** + * Computes a 4-by-4 orthogonal transformation matrix given the left, right, + * bottom, and top dimensions of the near clipping plane as well as the + * near and far clipping plane distances. + * @param {number} left Left side of the near clipping plane viewport. + * @param {number} right Right side of the near clipping plane viewport. + * @param {number} bottom Bottom of the near clipping plane viewport. + * @param {number} top Top of the near clipping plane viewport. + * @param {number} near The depth (negative z coordinate) + * of the near clipping plane. + * @param {number} far The depth (negative z coordinate) + * of the far clipping plane. + * @param {module:twgl/m4.Mat4} [dst] Output matrix. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The perspective matrix. + * @memberOf module:twgl/m4 + */ +function ortho(left, right, bottom, top, near, far, dst) { + dst = dst || new MatType(16); + + dst[0] = 2 / (right - left); + dst[1] = 0; + dst[2] = 0; + dst[3] = 0; + + dst[4] = 0; + dst[5] = 2 / (top - bottom); + dst[6] = 0; + dst[7] = 0; + + dst[8] = 0; + dst[9] = 0; + dst[10] = 2 / (near - far); + dst[11] = 0; + + dst[12] = (right + left) / (left - right); + dst[13] = (top + bottom) / (bottom - top); + dst[14] = (far + near) / (near - far); + dst[15] = 1; + + return dst; +} + +/** + * Computes a 4-by-4 perspective transformation matrix given the left, right, + * top, bottom, near and far clipping planes. The arguments define a frustum + * extending in the negative z direction. The arguments near and far are the + * distances to the near and far clipping planes. Note that near and far are not + * z coordinates, but rather they are distances along the negative z-axis. The + * matrix generated sends the viewing frustum to the unit box. We assume a unit + * box extending from -1 to 1 in the x and y dimensions and from 0 to 1 in the z + * dimension. + * @param {number} left The x coordinate of the left plane of the box. + * @param {number} right The x coordinate of the right plane of the box. + * @param {number} bottom The y coordinate of the bottom plane of the box. + * @param {number} top The y coordinate of the right plane of the box. + * @param {number} near The negative z coordinate of the near plane of the box. + * @param {number} far The negative z coordinate of the far plane of the box. + * @param {module:twgl/m4.Mat4} [dst] Output matrix. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The perspective projection matrix. + * @memberOf module:twgl/m4 + */ +function frustum(left, right, bottom, top, near, far, dst) { + dst = dst || new MatType(16); + + const dx = (right - left); + const dy = (top - bottom); + const dz = (near - far); + + dst[ 0] = 2 * near / dx; + dst[ 1] = 0; + dst[ 2] = 0; + dst[ 3] = 0; + dst[ 4] = 0; + dst[ 5] = 2 * near / dy; + dst[ 6] = 0; + dst[ 7] = 0; + dst[ 8] = (left + right) / dx; + dst[ 9] = (top + bottom) / dy; + dst[10] = far / dz; + dst[11] = -1; + dst[12] = 0; + dst[13] = 0; + dst[14] = near * far / dz; + dst[15] = 0; + + return dst; +} + +let xAxis; +let yAxis; +let zAxis; + +/** + * Computes a 4-by-4 look-at transformation. + * + * This is a matrix which positions the camera itself. If you want + * a view matrix (a matrix which moves things in front of the camera) + * take the inverse of this. + * + * @param {module:twgl/v3.Vec3} eye The position of the eye. + * @param {module:twgl/v3.Vec3} target The position meant to be viewed. + * @param {module:twgl/v3.Vec3} up A vector pointing up. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The look-at matrix. + * @memberOf module:twgl/m4 + */ +function lookAt(eye, target, up, dst) { + dst = dst || new MatType(16); + + xAxis = xAxis || create$1(); + yAxis = yAxis || create$1(); + zAxis = zAxis || create$1(); + + normalize( + subtract(eye, target, zAxis), zAxis); + normalize(cross(up, zAxis, xAxis), xAxis); + normalize(cross(zAxis, xAxis, yAxis), yAxis); + + dst[ 0] = xAxis[0]; + dst[ 1] = xAxis[1]; + dst[ 2] = xAxis[2]; + dst[ 3] = 0; + dst[ 4] = yAxis[0]; + dst[ 5] = yAxis[1]; + dst[ 6] = yAxis[2]; + dst[ 7] = 0; + dst[ 8] = zAxis[0]; + dst[ 9] = zAxis[1]; + dst[10] = zAxis[2]; + dst[11] = 0; + dst[12] = eye[0]; + dst[13] = eye[1]; + dst[14] = eye[2]; + dst[15] = 1; + + return dst; +} + +/** + * Creates a 4-by-4 matrix which translates by the given vector v. + * @param {module:twgl/v3.Vec3} v The vector by + * which to translate. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The translation matrix. + * @memberOf module:twgl/m4 + */ +function translation(v, dst) { + dst = dst || new MatType(16); + + dst[ 0] = 1; + dst[ 1] = 0; + dst[ 2] = 0; + dst[ 3] = 0; + dst[ 4] = 0; + dst[ 5] = 1; + dst[ 6] = 0; + dst[ 7] = 0; + dst[ 8] = 0; + dst[ 9] = 0; + dst[10] = 1; + dst[11] = 0; + dst[12] = v[0]; + dst[13] = v[1]; + dst[14] = v[2]; + dst[15] = 1; + return dst; +} + +/** + * Translates the given 4-by-4 matrix by the given vector v. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/v3.Vec3} v The vector by + * which to translate. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The translated matrix. + * @memberOf module:twgl/m4 + */ +function translate(m, v, dst) { + dst = dst || new MatType(16); + + const v0 = v[0]; + const v1 = v[1]; + const v2 = v[2]; + const m00 = m[0]; + const m01 = m[1]; + const m02 = m[2]; + const m03 = m[3]; + const m10 = m[1 * 4 + 0]; + const m11 = m[1 * 4 + 1]; + const m12 = m[1 * 4 + 2]; + const m13 = m[1 * 4 + 3]; + const m20 = m[2 * 4 + 0]; + const m21 = m[2 * 4 + 1]; + const m22 = m[2 * 4 + 2]; + const m23 = m[2 * 4 + 3]; + const m30 = m[3 * 4 + 0]; + const m31 = m[3 * 4 + 1]; + const m32 = m[3 * 4 + 2]; + const m33 = m[3 * 4 + 3]; + + if (m !== dst) { + dst[ 0] = m00; + dst[ 1] = m01; + dst[ 2] = m02; + dst[ 3] = m03; + dst[ 4] = m10; + dst[ 5] = m11; + dst[ 6] = m12; + dst[ 7] = m13; + dst[ 8] = m20; + dst[ 9] = m21; + dst[10] = m22; + dst[11] = m23; + } + + dst[12] = m00 * v0 + m10 * v1 + m20 * v2 + m30; + dst[13] = m01 * v0 + m11 * v1 + m21 * v2 + m31; + dst[14] = m02 * v0 + m12 * v1 + m22 * v2 + m32; + dst[15] = m03 * v0 + m13 * v1 + m23 * v2 + m33; + + return dst; +} + +/** + * Creates a 4-by-4 matrix which rotates around the x-axis by the given angle. + * @param {number} angleInRadians The angle by which to rotate (in radians). + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The rotation matrix. + * @memberOf module:twgl/m4 + */ +function rotationX(angleInRadians, dst) { + dst = dst || new MatType(16); + + const c = Math.cos(angleInRadians); + const s = Math.sin(angleInRadians); + + dst[ 0] = 1; + dst[ 1] = 0; + dst[ 2] = 0; + dst[ 3] = 0; + dst[ 4] = 0; + dst[ 5] = c; + dst[ 6] = s; + dst[ 7] = 0; + dst[ 8] = 0; + dst[ 9] = -s; + dst[10] = c; + dst[11] = 0; + dst[12] = 0; + dst[13] = 0; + dst[14] = 0; + dst[15] = 1; + + return dst; +} + +/** + * Rotates the given 4-by-4 matrix around the x-axis by the given + * angle. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {number} angleInRadians The angle by which to rotate (in radians). + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The rotated matrix. + * @memberOf module:twgl/m4 + */ +function rotateX(m, angleInRadians, dst) { + dst = dst || new MatType(16); + + const m10 = m[4]; + const m11 = m[5]; + const m12 = m[6]; + const m13 = m[7]; + const m20 = m[8]; + const m21 = m[9]; + const m22 = m[10]; + const m23 = m[11]; + const c = Math.cos(angleInRadians); + const s = Math.sin(angleInRadians); + + dst[4] = c * m10 + s * m20; + dst[5] = c * m11 + s * m21; + dst[6] = c * m12 + s * m22; + dst[7] = c * m13 + s * m23; + dst[8] = c * m20 - s * m10; + dst[9] = c * m21 - s * m11; + dst[10] = c * m22 - s * m12; + dst[11] = c * m23 - s * m13; + + if (m !== dst) { + dst[ 0] = m[ 0]; + dst[ 1] = m[ 1]; + dst[ 2] = m[ 2]; + dst[ 3] = m[ 3]; + dst[12] = m[12]; + dst[13] = m[13]; + dst[14] = m[14]; + dst[15] = m[15]; + } + + return dst; +} + +/** + * Creates a 4-by-4 matrix which rotates around the y-axis by the given angle. + * @param {number} angleInRadians The angle by which to rotate (in radians). + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The rotation matrix. + * @memberOf module:twgl/m4 + */ +function rotationY(angleInRadians, dst) { + dst = dst || new MatType(16); + + const c = Math.cos(angleInRadians); + const s = Math.sin(angleInRadians); + + dst[ 0] = c; + dst[ 1] = 0; + dst[ 2] = -s; + dst[ 3] = 0; + dst[ 4] = 0; + dst[ 5] = 1; + dst[ 6] = 0; + dst[ 7] = 0; + dst[ 8] = s; + dst[ 9] = 0; + dst[10] = c; + dst[11] = 0; + dst[12] = 0; + dst[13] = 0; + dst[14] = 0; + dst[15] = 1; + + return dst; +} + +/** + * Rotates the given 4-by-4 matrix around the y-axis by the given + * angle. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {number} angleInRadians The angle by which to rotate (in radians). + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The rotated matrix. + * @memberOf module:twgl/m4 + */ +function rotateY(m, angleInRadians, dst) { + dst = dst || new MatType(16); + + const m00 = m[0 * 4 + 0]; + const m01 = m[0 * 4 + 1]; + const m02 = m[0 * 4 + 2]; + const m03 = m[0 * 4 + 3]; + const m20 = m[2 * 4 + 0]; + const m21 = m[2 * 4 + 1]; + const m22 = m[2 * 4 + 2]; + const m23 = m[2 * 4 + 3]; + const c = Math.cos(angleInRadians); + const s = Math.sin(angleInRadians); + + dst[ 0] = c * m00 - s * m20; + dst[ 1] = c * m01 - s * m21; + dst[ 2] = c * m02 - s * m22; + dst[ 3] = c * m03 - s * m23; + dst[ 8] = c * m20 + s * m00; + dst[ 9] = c * m21 + s * m01; + dst[10] = c * m22 + s * m02; + dst[11] = c * m23 + s * m03; + + if (m !== dst) { + dst[ 4] = m[ 4]; + dst[ 5] = m[ 5]; + dst[ 6] = m[ 6]; + dst[ 7] = m[ 7]; + dst[12] = m[12]; + dst[13] = m[13]; + dst[14] = m[14]; + dst[15] = m[15]; + } + + return dst; +} + +/** + * Creates a 4-by-4 matrix which rotates around the z-axis by the given angle. + * @param {number} angleInRadians The angle by which to rotate (in radians). + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The rotation matrix. + * @memberOf module:twgl/m4 + */ +function rotationZ(angleInRadians, dst) { + dst = dst || new MatType(16); + + const c = Math.cos(angleInRadians); + const s = Math.sin(angleInRadians); + + dst[ 0] = c; + dst[ 1] = s; + dst[ 2] = 0; + dst[ 3] = 0; + dst[ 4] = -s; + dst[ 5] = c; + dst[ 6] = 0; + dst[ 7] = 0; + dst[ 8] = 0; + dst[ 9] = 0; + dst[10] = 1; + dst[11] = 0; + dst[12] = 0; + dst[13] = 0; + dst[14] = 0; + dst[15] = 1; + + return dst; +} + +/** + * Rotates the given 4-by-4 matrix around the z-axis by the given + * angle. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {number} angleInRadians The angle by which to rotate (in radians). + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The rotated matrix. + * @memberOf module:twgl/m4 + */ +function rotateZ(m, angleInRadians, dst) { + dst = dst || new MatType(16); + + const m00 = m[0 * 4 + 0]; + const m01 = m[0 * 4 + 1]; + const m02 = m[0 * 4 + 2]; + const m03 = m[0 * 4 + 3]; + const m10 = m[1 * 4 + 0]; + const m11 = m[1 * 4 + 1]; + const m12 = m[1 * 4 + 2]; + const m13 = m[1 * 4 + 3]; + const c = Math.cos(angleInRadians); + const s = Math.sin(angleInRadians); + + dst[ 0] = c * m00 + s * m10; + dst[ 1] = c * m01 + s * m11; + dst[ 2] = c * m02 + s * m12; + dst[ 3] = c * m03 + s * m13; + dst[ 4] = c * m10 - s * m00; + dst[ 5] = c * m11 - s * m01; + dst[ 6] = c * m12 - s * m02; + dst[ 7] = c * m13 - s * m03; + + if (m !== dst) { + dst[ 8] = m[ 8]; + dst[ 9] = m[ 9]; + dst[10] = m[10]; + dst[11] = m[11]; + dst[12] = m[12]; + dst[13] = m[13]; + dst[14] = m[14]; + dst[15] = m[15]; + } + + return dst; +} + +/** + * Creates a 4-by-4 matrix which rotates around the given axis by the given + * angle. + * @param {module:twgl/v3.Vec3} axis The axis + * about which to rotate. + * @param {number} angleInRadians The angle by which to rotate (in radians). + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} A matrix which rotates angle radians + * around the axis. + * @memberOf module:twgl/m4 + */ +function axisRotation(axis, angleInRadians, dst) { + dst = dst || new MatType(16); + + let x = axis[0]; + let y = axis[1]; + let z = axis[2]; + const n = Math.sqrt(x * x + y * y + z * z); + x /= n; + y /= n; + z /= n; + const xx = x * x; + const yy = y * y; + const zz = z * z; + const c = Math.cos(angleInRadians); + const s = Math.sin(angleInRadians); + const oneMinusCosine = 1 - c; + + dst[ 0] = xx + (1 - xx) * c; + dst[ 1] = x * y * oneMinusCosine + z * s; + dst[ 2] = x * z * oneMinusCosine - y * s; + dst[ 3] = 0; + dst[ 4] = x * y * oneMinusCosine - z * s; + dst[ 5] = yy + (1 - yy) * c; + dst[ 6] = y * z * oneMinusCosine + x * s; + dst[ 7] = 0; + dst[ 8] = x * z * oneMinusCosine + y * s; + dst[ 9] = y * z * oneMinusCosine - x * s; + dst[10] = zz + (1 - zz) * c; + dst[11] = 0; + dst[12] = 0; + dst[13] = 0; + dst[14] = 0; + dst[15] = 1; + + return dst; +} + +/** + * Rotates the given 4-by-4 matrix around the given axis by the + * given angle. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/v3.Vec3} axis The axis + * about which to rotate. + * @param {number} angleInRadians The angle by which to rotate (in radians). + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The rotated matrix. + * @memberOf module:twgl/m4 + */ +function axisRotate(m, axis, angleInRadians, dst) { + dst = dst || new MatType(16); + + let x = axis[0]; + let y = axis[1]; + let z = axis[2]; + const n = Math.sqrt(x * x + y * y + z * z); + x /= n; + y /= n; + z /= n; + const xx = x * x; + const yy = y * y; + const zz = z * z; + const c = Math.cos(angleInRadians); + const s = Math.sin(angleInRadians); + const oneMinusCosine = 1 - c; + + const r00 = xx + (1 - xx) * c; + const r01 = x * y * oneMinusCosine + z * s; + const r02 = x * z * oneMinusCosine - y * s; + const r10 = x * y * oneMinusCosine - z * s; + const r11 = yy + (1 - yy) * c; + const r12 = y * z * oneMinusCosine + x * s; + const r20 = x * z * oneMinusCosine + y * s; + const r21 = y * z * oneMinusCosine - x * s; + const r22 = zz + (1 - zz) * c; + + const m00 = m[0]; + const m01 = m[1]; + const m02 = m[2]; + const m03 = m[3]; + const m10 = m[4]; + const m11 = m[5]; + const m12 = m[6]; + const m13 = m[7]; + const m20 = m[8]; + const m21 = m[9]; + const m22 = m[10]; + const m23 = m[11]; + + dst[ 0] = r00 * m00 + r01 * m10 + r02 * m20; + dst[ 1] = r00 * m01 + r01 * m11 + r02 * m21; + dst[ 2] = r00 * m02 + r01 * m12 + r02 * m22; + dst[ 3] = r00 * m03 + r01 * m13 + r02 * m23; + dst[ 4] = r10 * m00 + r11 * m10 + r12 * m20; + dst[ 5] = r10 * m01 + r11 * m11 + r12 * m21; + dst[ 6] = r10 * m02 + r11 * m12 + r12 * m22; + dst[ 7] = r10 * m03 + r11 * m13 + r12 * m23; + dst[ 8] = r20 * m00 + r21 * m10 + r22 * m20; + dst[ 9] = r20 * m01 + r21 * m11 + r22 * m21; + dst[10] = r20 * m02 + r21 * m12 + r22 * m22; + dst[11] = r20 * m03 + r21 * m13 + r22 * m23; + + if (m !== dst) { + dst[12] = m[12]; + dst[13] = m[13]; + dst[14] = m[14]; + dst[15] = m[15]; + } + + return dst; +} + +/** + * Creates a 4-by-4 matrix which scales in each dimension by an amount given by + * the corresponding entry in the given vector; assumes the vector has three + * entries. + * @param {module:twgl/v3.Vec3} v A vector of + * three entries specifying the factor by which to scale in each dimension. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The scaling matrix. + * @memberOf module:twgl/m4 + */ +function scaling(v, dst) { + dst = dst || new MatType(16); + + dst[ 0] = v[0]; + dst[ 1] = 0; + dst[ 2] = 0; + dst[ 3] = 0; + dst[ 4] = 0; + dst[ 5] = v[1]; + dst[ 6] = 0; + dst[ 7] = 0; + dst[ 8] = 0; + dst[ 9] = 0; + dst[10] = v[2]; + dst[11] = 0; + dst[12] = 0; + dst[13] = 0; + dst[14] = 0; + dst[15] = 1; + + return dst; +} + +/** + * Scales the given 4-by-4 matrix in each dimension by an amount + * given by the corresponding entry in the given vector; assumes the vector has + * three entries. + * @param {module:twgl/m4.Mat4} m The matrix to be modified. + * @param {module:twgl/v3.Vec3} v A vector of three entries specifying the + * factor by which to scale in each dimension. + * @param {module:twgl/m4.Mat4} [dst] matrix to hold result. If not passed a new one is created. + * @return {module:twgl/m4.Mat4} The scaled matrix. + * @memberOf module:twgl/m4 + */ +function scale(m, v, dst) { + dst = dst || new MatType(16); + + const v0 = v[0]; + const v1 = v[1]; + const v2 = v[2]; + + dst[ 0] = v0 * m[0 * 4 + 0]; + dst[ 1] = v0 * m[0 * 4 + 1]; + dst[ 2] = v0 * m[0 * 4 + 2]; + dst[ 3] = v0 * m[0 * 4 + 3]; + dst[ 4] = v1 * m[1 * 4 + 0]; + dst[ 5] = v1 * m[1 * 4 + 1]; + dst[ 6] = v1 * m[1 * 4 + 2]; + dst[ 7] = v1 * m[1 * 4 + 3]; + dst[ 8] = v2 * m[2 * 4 + 0]; + dst[ 9] = v2 * m[2 * 4 + 1]; + dst[10] = v2 * m[2 * 4 + 2]; + dst[11] = v2 * m[2 * 4 + 3]; + + if (m !== dst) { + dst[12] = m[12]; + dst[13] = m[13]; + dst[14] = m[14]; + dst[15] = m[15]; + } + + return dst; +} + +/** + * Takes a 4-by-4 matrix and a vector with 3 entries, + * interprets the vector as a point, transforms that point by the matrix, and + * returns the result as a vector with 3 entries. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/v3.Vec3} v The point. + * @param {module:twgl/v3.Vec3} [dst] optional vec3 to store result. If not passed a new one is created. + * @return {module:twgl/v3.Vec3} The transformed point. + * @memberOf module:twgl/m4 + */ +function transformPoint(m, v, dst) { + dst = dst || create$1(); + const v0 = v[0]; + const v1 = v[1]; + const v2 = v[2]; + const d = v0 * m[0 * 4 + 3] + v1 * m[1 * 4 + 3] + v2 * m[2 * 4 + 3] + m[3 * 4 + 3]; + + dst[0] = (v0 * m[0 * 4 + 0] + v1 * m[1 * 4 + 0] + v2 * m[2 * 4 + 0] + m[3 * 4 + 0]) / d; + dst[1] = (v0 * m[0 * 4 + 1] + v1 * m[1 * 4 + 1] + v2 * m[2 * 4 + 1] + m[3 * 4 + 1]) / d; + dst[2] = (v0 * m[0 * 4 + 2] + v1 * m[1 * 4 + 2] + v2 * m[2 * 4 + 2] + m[3 * 4 + 2]) / d; + + return dst; +} + +/** + * Takes a 4-by-4 matrix and a vector with 3 entries, interprets the vector as a + * direction, transforms that direction by the matrix, and returns the result; + * assumes the transformation of 3-dimensional space represented by the matrix + * is parallel-preserving, i.e. any combination of rotation, scaling and + * translation, but not a perspective distortion. Returns a vector with 3 + * entries. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/v3.Vec3} v The direction. + * @param {module:twgl/v3.Vec3} [dst] optional Vec3 to store result. If not passed a new one is created. + * @return {module:twgl/v3.Vec3} The transformed direction. + * @memberOf module:twgl/m4 + */ +function transformDirection(m, v, dst) { + dst = dst || create$1(); + + const v0 = v[0]; + const v1 = v[1]; + const v2 = v[2]; + + dst[0] = v0 * m[0 * 4 + 0] + v1 * m[1 * 4 + 0] + v2 * m[2 * 4 + 0]; + dst[1] = v0 * m[0 * 4 + 1] + v1 * m[1 * 4 + 1] + v2 * m[2 * 4 + 1]; + dst[2] = v0 * m[0 * 4 + 2] + v1 * m[1 * 4 + 2] + v2 * m[2 * 4 + 2]; + + return dst; +} + +/** + * Takes a 4-by-4 matrix m and a vector v with 3 entries, interprets the vector + * as a normal to a surface, and computes a vector which is normal upon + * transforming that surface by the matrix. The effect of this function is the + * same as transforming v (as a direction) by the inverse-transpose of m. This + * function assumes the transformation of 3-dimensional space represented by the + * matrix is parallel-preserving, i.e. any combination of rotation, scaling and + * translation, but not a perspective distortion. Returns a vector with 3 + * entries. + * @param {module:twgl/m4.Mat4} m The matrix. + * @param {module:twgl/v3.Vec3} v The normal. + * @param {module:twgl/v3.Vec3} [dst] The direction. If not passed a new one is created. + * @return {module:twgl/v3.Vec3} The transformed normal. + * @memberOf module:twgl/m4 + */ +function transformNormal$1(m, v, dst) { + dst = dst || create$1(); + const mi = inverse(m); + const v0 = v[0]; + const v1 = v[1]; + const v2 = v[2]; + + dst[0] = v0 * mi[0 * 4 + 0] + v1 * mi[0 * 4 + 1] + v2 * mi[0 * 4 + 2]; + dst[1] = v0 * mi[1 * 4 + 0] + v1 * mi[1 * 4 + 1] + v2 * mi[1 * 4 + 2]; + dst[2] = v0 * mi[2 * 4 + 0] + v1 * mi[2 * 4 + 1] + v2 * mi[2 * 4 + 2]; + + return dst; +} + +var m4 = /*#__PURE__*/Object.freeze({ + __proto__: null, + axisRotate: axisRotate, + axisRotation: axisRotation, + copy: copy, + create: create, + frustum: frustum, + getAxis: getAxis, + getTranslation: getTranslation, + identity: identity, + inverse: inverse, + lookAt: lookAt, + multiply: multiply, + negate: negate, + ortho: ortho, + perspective: perspective, + rotateX: rotateX, + rotateY: rotateY, + rotateZ: rotateZ, + rotationX: rotationX, + rotationY: rotationY, + rotationZ: rotationZ, + scale: scale, + scaling: scaling, + setAxis: setAxis, + setDefaultType: setDefaultType, + setTranslation: setTranslation, + transformDirection: transformDirection, + transformNormal: transformNormal$1, + transformPoint: transformPoint, + translate: translate, + translation: translation, + transpose: transpose +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* DataType */ +const BYTE$2 = 0x1400; +const UNSIGNED_BYTE$3 = 0x1401; +const SHORT$2 = 0x1402; +const UNSIGNED_SHORT$3 = 0x1403; +const INT$3 = 0x1404; +const UNSIGNED_INT$3 = 0x1405; +const FLOAT$3 = 0x1406; +const UNSIGNED_SHORT_4_4_4_4$1 = 0x8033; +const UNSIGNED_SHORT_5_5_5_1$1 = 0x8034; +const UNSIGNED_SHORT_5_6_5$1 = 0x8363; +const HALF_FLOAT$1 = 0x140B; +const UNSIGNED_INT_2_10_10_10_REV$1 = 0x8368; +const UNSIGNED_INT_10F_11F_11F_REV$1 = 0x8C3B; +const UNSIGNED_INT_5_9_9_9_REV$1 = 0x8C3E; +const FLOAT_32_UNSIGNED_INT_24_8_REV$1 = 0x8DAD; +const UNSIGNED_INT_24_8$1 = 0x84FA; + +const glTypeToTypedArray = {}; +{ + const tt = glTypeToTypedArray; + tt[BYTE$2] = Int8Array; + tt[UNSIGNED_BYTE$3] = Uint8Array; + tt[SHORT$2] = Int16Array; + tt[UNSIGNED_SHORT$3] = Uint16Array; + tt[INT$3] = Int32Array; + tt[UNSIGNED_INT$3] = Uint32Array; + tt[FLOAT$3] = Float32Array; + tt[UNSIGNED_SHORT_4_4_4_4$1] = Uint16Array; + tt[UNSIGNED_SHORT_5_5_5_1$1] = Uint16Array; + tt[UNSIGNED_SHORT_5_6_5$1] = Uint16Array; + tt[HALF_FLOAT$1] = Uint16Array; + tt[UNSIGNED_INT_2_10_10_10_REV$1] = Uint32Array; + tt[UNSIGNED_INT_10F_11F_11F_REV$1] = Uint32Array; + tt[UNSIGNED_INT_5_9_9_9_REV$1] = Uint32Array; + tt[FLOAT_32_UNSIGNED_INT_24_8_REV$1] = Uint32Array; + tt[UNSIGNED_INT_24_8$1] = Uint32Array; +} + +/** + * Get the GL type for a typedArray + * @param {ArrayBufferView} typedArray a typedArray + * @return {number} the GL type for array. For example pass in an `Int8Array` and `gl.BYTE` will + * be returned. Pass in a `Uint32Array` and `gl.UNSIGNED_INT` will be returned + * @memberOf module:twgl/typedArray + */ +function getGLTypeForTypedArray(typedArray) { + if (typedArray instanceof Int8Array) { return BYTE$2; } // eslint-disable-line + if (typedArray instanceof Uint8Array) { return UNSIGNED_BYTE$3; } // eslint-disable-line + if (typedArray instanceof Uint8ClampedArray) { return UNSIGNED_BYTE$3; } // eslint-disable-line + if (typedArray instanceof Int16Array) { return SHORT$2; } // eslint-disable-line + if (typedArray instanceof Uint16Array) { return UNSIGNED_SHORT$3; } // eslint-disable-line + if (typedArray instanceof Int32Array) { return INT$3; } // eslint-disable-line + if (typedArray instanceof Uint32Array) { return UNSIGNED_INT$3; } // eslint-disable-line + if (typedArray instanceof Float32Array) { return FLOAT$3; } // eslint-disable-line + throw new Error('unsupported typed array type'); +} + +/** + * Get the GL type for a typedArray type + * @param {ArrayBufferView} typedArrayType a typedArray constructor + * @return {number} the GL type for type. For example pass in `Int8Array` and `gl.BYTE` will + * be returned. Pass in `Uint32Array` and `gl.UNSIGNED_INT` will be returned + * @memberOf module:twgl/typedArray + */ +function getGLTypeForTypedArrayType(typedArrayType) { + if (typedArrayType === Int8Array) { return BYTE$2; } // eslint-disable-line + if (typedArrayType === Uint8Array) { return UNSIGNED_BYTE$3; } // eslint-disable-line + if (typedArrayType === Uint8ClampedArray) { return UNSIGNED_BYTE$3; } // eslint-disable-line + if (typedArrayType === Int16Array) { return SHORT$2; } // eslint-disable-line + if (typedArrayType === Uint16Array) { return UNSIGNED_SHORT$3; } // eslint-disable-line + if (typedArrayType === Int32Array) { return INT$3; } // eslint-disable-line + if (typedArrayType === Uint32Array) { return UNSIGNED_INT$3; } // eslint-disable-line + if (typedArrayType === Float32Array) { return FLOAT$3; } // eslint-disable-line + throw new Error('unsupported typed array type'); +} + +/** + * Get the typed array constructor for a given GL type + * @param {number} type the GL type. (eg: `gl.UNSIGNED_INT`) + * @return {function} the constructor for a the corresponding typed array. (eg. `Uint32Array`). + * @memberOf module:twgl/typedArray + */ +function getTypedArrayTypeForGLType(type) { + const CTOR = glTypeToTypedArray[type]; + if (!CTOR) { + throw new Error('unknown gl type'); + } + return CTOR; +} + +const isArrayBuffer$1 = typeof SharedArrayBuffer !== 'undefined' + ? function isArrayBufferOrSharedArrayBuffer(a) { + return a && a.buffer && (a.buffer instanceof ArrayBuffer || a.buffer instanceof SharedArrayBuffer); + } + : function isArrayBuffer(a) { + return a && a.buffer && a.buffer instanceof ArrayBuffer; + }; + +var typedarrays = /*#__PURE__*/Object.freeze({ + __proto__: null, + getGLTypeForTypedArray: getGLTypeForTypedArray, + getGLTypeForTypedArrayType: getGLTypeForTypedArrayType, + getTypedArrayTypeForGLType: getTypedArrayTypeForGLType, + isArrayBuffer: isArrayBuffer$1 +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* eslint no-console: "off" */ + +/** + * Copy named properties + * + * @param {string[]} names names of properties to copy + * @param {object} src object to copy properties from + * @param {object} dst object to copy properties to + * @private + */ +function copyNamedProperties(names, src, dst) { + names.forEach(function(name) { + const value = src[name]; + if (value !== undefined) { + dst[name] = value; + } + }); +} + +/** + * Copies properties from source to dest only if a matching key is in dest + * + * @param {Object.} src the source + * @param {Object.} dst the dest + * @private + */ +function copyExistingProperties(src, dst) { + Object.keys(dst).forEach(function(key) { + if (dst.hasOwnProperty(key) && src.hasOwnProperty(key)) { /* eslint no-prototype-builtins: 0 */ + dst[key] = src[key]; + } + }); +} + +function error$1(...args) { + console.error(...args); +} + +function warn$1(...args) { + console.warn(...args); +} + +const isTypeWeakMaps = new Map(); + +function isType(object, type) { + if (!object || typeof object !== 'object') { + return false; + } + let weakMap = isTypeWeakMaps.get(type); + if (!weakMap) { + weakMap = new WeakMap(); + isTypeWeakMaps.set(type, weakMap); + } + let isOfType = weakMap.get(object); + if (isOfType === undefined) { + const s = Object.prototype.toString.call(object); + isOfType = s.substring(8, s.length - 1) === type; + weakMap.set(object, isOfType); + } + return isOfType; +} + +function isBuffer(gl, t) { + return typeof WebGLBuffer !== 'undefined' && isType(t, 'WebGLBuffer'); +} + +function isRenderbuffer(gl, t) { + return typeof WebGLRenderbuffer !== 'undefined' && isType(t, 'WebGLRenderbuffer'); +} + +function isTexture(gl, t) { + return typeof WebGLTexture !== 'undefined' && isType(t, 'WebGLTexture'); +} + +function isSampler(gl, t) { + return typeof WebGLSampler !== 'undefined' && isType(t, 'WebGLSampler'); +} + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +const STATIC_DRAW = 0x88e4; +const ARRAY_BUFFER$1 = 0x8892; +const ELEMENT_ARRAY_BUFFER$2 = 0x8893; +const BUFFER_SIZE = 0x8764; + +const BYTE$1 = 0x1400; +const UNSIGNED_BYTE$2 = 0x1401; +const SHORT$1 = 0x1402; +const UNSIGNED_SHORT$2 = 0x1403; +const INT$2 = 0x1404; +const UNSIGNED_INT$2 = 0x1405; +const FLOAT$2 = 0x1406; +const defaults$2 = { + attribPrefix: "", +}; + +/** + * Sets the default attrib prefix + * + * When writing shaders I prefer to name attributes with `a_`, uniforms with `u_` and varyings with `v_` + * as it makes it clear where they came from. But, when building geometry I prefer using un-prefixed names. + * + * In other words I'll create arrays of geometry like this + * + * var arrays = { + * position: ... + * normal: ... + * texcoord: ... + * }; + * + * But need those mapped to attributes and my attributes start with `a_`. + * + * @deprecated see {@link module:twgl.setDefaults} + * @param {string} prefix prefix for attribs + * @memberOf module:twgl/attributes + */ +function setAttributePrefix(prefix) { + defaults$2.attribPrefix = prefix; +} + +function setDefaults$2(newDefaults) { + copyExistingProperties(newDefaults, defaults$2); +} + +function setBufferFromTypedArray(gl, type, buffer, array, drawType) { + gl.bindBuffer(type, buffer); + gl.bufferData(type, array, drawType || STATIC_DRAW); +} + +/** + * Given typed array creates a WebGLBuffer and copies the typed array + * into it. + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @param {ArrayBuffer|SharedArrayBuffer|ArrayBufferView|WebGLBuffer} typedArray the typed array. Note: If a WebGLBuffer is passed in it will just be returned. No action will be taken + * @param {number} [type] the GL bind type for the buffer. Default = `gl.ARRAY_BUFFER`. + * @param {number} [drawType] the GL draw type for the buffer. Default = 'gl.STATIC_DRAW`. + * @return {WebGLBuffer} the created WebGLBuffer + * @memberOf module:twgl/attributes + */ +function createBufferFromTypedArray(gl, typedArray, type, drawType) { + if (isBuffer(gl, typedArray)) { + return typedArray; + } + type = type || ARRAY_BUFFER$1; + const buffer = gl.createBuffer(); + setBufferFromTypedArray(gl, type, buffer, typedArray, drawType); + return buffer; +} + +function isIndices(name) { + return name === "indices"; +} + +// This is really just a guess. Though I can't really imagine using +// anything else? Maybe for some compression? +function getNormalizationForTypedArrayType(typedArrayType) { + if (typedArrayType === Int8Array) { return true; } // eslint-disable-line + if (typedArrayType === Uint8Array) { return true; } // eslint-disable-line + return false; +} + +function getArray$1(array) { + return array.length ? array : array.data; +} + +const texcoordRE = /coord|texture/i; +const colorRE = /color|colour/i; + +function guessNumComponentsFromName(name, length) { + let numComponents; + if (texcoordRE.test(name)) { + numComponents = 2; + } else if (colorRE.test(name)) { + numComponents = 4; + } else { + numComponents = 3; // position, normals, indices ... + } + + if (length % numComponents > 0) { + throw new Error(`Can not guess numComponents for attribute '${name}'. Tried ${numComponents} but ${length} values is not evenly divisible by ${numComponents}. You should specify it.`); + } + + return numComponents; +} + +function getNumComponents$1(array, arrayName, numValues) { + return array.numComponents || array.size || guessNumComponentsFromName(arrayName, numValues || getArray$1(array).length); +} + +function makeTypedArray(array, name) { + if (isArrayBuffer$1(array)) { + return array; + } + + if (isArrayBuffer$1(array.data)) { + return array.data; + } + + if (Array.isArray(array)) { + array = { + data: array, + }; + } + + let Type = array.type ? typedArrayTypeFromGLTypeOrTypedArrayCtor(array.type) : undefined; + if (!Type) { + if (isIndices(name)) { + Type = Uint16Array; + } else { + Type = Float32Array; + } + } + return new Type(array.data); +} + +function glTypeFromGLTypeOrTypedArrayType(glTypeOrTypedArrayCtor) { + return typeof glTypeOrTypedArrayCtor === 'number' + ? glTypeOrTypedArrayCtor + : glTypeOrTypedArrayCtor ? getGLTypeForTypedArrayType(glTypeOrTypedArrayCtor) : FLOAT$2; +} + +function typedArrayTypeFromGLTypeOrTypedArrayCtor(glTypeOrTypedArrayCtor) { + return typeof glTypeOrTypedArrayCtor === 'number' + ? getTypedArrayTypeForGLType(glTypeOrTypedArrayCtor) + : glTypeOrTypedArrayCtor || Float32Array; +} + +function attribBufferFromBuffer(gl, array/*, arrayName */) { + return { + buffer: array.buffer, + numValues: 2 * 3 * 4, // safely divided by 2, 3, 4 + type: glTypeFromGLTypeOrTypedArrayType(array.type), + arrayType: typedArrayTypeFromGLTypeOrTypedArrayCtor(array.type), + }; +} + +function attribBufferFromSize(gl, array/*, arrayName*/) { + const numValues = array.data || array; + const arrayType = typedArrayTypeFromGLTypeOrTypedArrayCtor(array.type); + const numBytes = numValues * arrayType.BYTES_PER_ELEMENT; + const buffer = gl.createBuffer(); + gl.bindBuffer(ARRAY_BUFFER$1, buffer); + gl.bufferData(ARRAY_BUFFER$1, numBytes, array.drawType || STATIC_DRAW); + return { + buffer, + numValues, + type: getGLTypeForTypedArrayType(arrayType), + arrayType, + }; +} + +function attribBufferFromArrayLike(gl, array, arrayName) { + const typedArray = makeTypedArray(array, arrayName); + return { + arrayType: typedArray.constructor, + buffer: createBufferFromTypedArray(gl, typedArray, undefined, array.drawType), + type: getGLTypeForTypedArray(typedArray), + numValues: 0, + }; +} + +/** + * The info for an attribute. This is effectively just the arguments to `gl.vertexAttribPointer` plus the WebGLBuffer + * for the attribute. + * + * @typedef {Object} AttribInfo + * @property {number[]|ArrayBufferView} [value] a constant value for the attribute. Note: if this is set the attribute will be + * disabled and set to this constant value and all other values will be ignored. + * @property {number} [numComponents] the number of components for this attribute. + * @property {number} [size] synonym for `numComponents`. + * @property {number} [type] the type of the attribute (eg. `gl.FLOAT`, `gl.UNSIGNED_BYTE`, etc...) Default = `gl.FLOAT` + * @property {boolean} [normalize] whether or not to normalize the data. Default = false + * @property {number} [offset] offset into buffer in bytes. Default = 0 + * @property {number} [stride] the stride in bytes per element. Default = 0 + * @property {number} [divisor] the divisor in instances. Default = 0. + * Requires WebGL2 or the ANGLE_instanced_arrays extension. + * and, if you're using WebGL1 you must have called {@link module:twgl.addExtensionsToContext} + * @property {WebGLBuffer} buffer the buffer that contains the data for this attribute + * @property {number} [drawType] the draw type passed to gl.bufferData. Default = gl.STATIC_DRAW + * @memberOf module:twgl + */ + +/** + * @typedef {(Int8ArrayConstructor|Uint8ArrayConstructor|Int16ArrayConstructor|Uint16ArrayConstructor|Int32ArrayConstructor|Uint32ArrayConstructor|Float32ArrayConstructor)} TypedArrayConstructor + */ + +/** + * Use this type of array spec when TWGL can't guess the type or number of components of an array + * @typedef {Object} FullArraySpec + * @property {number[]|ArrayBufferView} [value] a constant value for the attribute. Note: if this is set the attribute will be + * disabled and set to this constant value and all other values will be ignored. + * @property {(number|number[]|ArrayBufferView)} [data] The data of the array. A number alone becomes the number of elements of type. + * @property {number} [numComponents] number of components for `vertexAttribPointer`. Default is based on the name of the array. + * If `coord` is in the name assumes `numComponents = 2`. + * If `color` is in the name assumes `numComponents = 4`. + * otherwise assumes `numComponents = 3` + * @property {number|TypedArrayConstructor} [type] type. This is used if `data` is a JavaScript array, or `buffer` is passed in, or `data` is a number. + * It can either be the constructor for a typedarray. (eg. `Uint8Array`) OR a WebGL type, (eg `gl.UNSIGNED_BYTE`). + * For example if you want colors in a `Uint8Array` you might have a `FullArraySpec` like `{ type: gl.UNSIGNED_BYTE, data: [255,0,255,255, ...], }`. + * @property {number} [size] synonym for `numComponents`. + * @property {boolean} [normalize] normalize for `vertexAttribPointer`. Default is true if type is `Int8Array` or `Uint8Array` otherwise false. + * @property {number} [stride] stride for `vertexAttribPointer`. Default = 0 + * @property {number} [offset] offset for `vertexAttribPointer`. Default = 0 + * @property {number} [divisor] divisor for `vertexAttribDivisor`. Default = 0. + * Requires WebGL2 or the ANGLE_instanced_arrays extension. + * and, if you using WebGL1 you must have called {@link module:twgl.addExtensionsToContext} + * @property {string} [attrib] name of attribute this array maps to. Defaults to same name as array prefixed by the default attribPrefix. + * @property {string} [name] synonym for `attrib`. + * @property {string} [attribName] synonym for `attrib`. + * @property {WebGLBuffer} [buffer] Buffer to use for this attribute. This lets you use your own buffer + * but you will need to supply `numComponents` and `type`. You can effectively pass an `AttribInfo` + * to provide this. Example: + * + * const bufferInfo1 = twgl.createBufferInfoFromArrays(gl, { + * position: [1, 2, 3, ... ], + * }); + * const bufferInfo2 = twgl.createBufferInfoFromArrays(gl, { + * position: bufferInfo1.attribs.position, // use the same buffer from bufferInfo1 + * }); + * + * @property {number} [drawType] the draw type passed to gl.bufferData. Default = gl.STATIC_DRAW + * @memberOf module:twgl + */ + +/** + * An individual array in {@link module:twgl.Arrays} + * + * When passed to {@link module:twgl.createBufferInfoFromArrays} if an ArraySpec is `number[]` or `ArrayBufferView` + * the types will be guessed based on the name. `indices` will be `Uint16Array`, everything else will + * be `Float32Array`. If an ArraySpec is a number it's the number of floats for an empty (zeroed) buffer. + * + * @typedef {(number|number[]|ArrayBufferView|module:twgl.FullArraySpec)} ArraySpec + * @memberOf module:twgl + */ + +/** + * This is a JavaScript object of arrays by name. The names should match your shader's attributes. If your + * attributes have a common prefix you can specify it by calling {@link module:twgl.setAttributePrefix}. + * + * Bare JavaScript Arrays + * + * var arrays = { + * position: [-1, 1, 0], + * normal: [0, 1, 0], + * ... + * } + * + * Bare TypedArrays + * + * var arrays = { + * position: new Float32Array([-1, 1, 0]), + * color: new Uint8Array([255, 128, 64, 255]), + * ... + * } + * + * * Will guess at `numComponents` if not specified based on name. + * + * If `coord` is in the name assumes `numComponents = 2` + * + * If `color` is in the name assumes `numComponents = 4` + * + * otherwise assumes `numComponents = 3` + * + * Objects with various fields. See {@link module:twgl.FullArraySpec}. + * + * var arrays = { + * position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], }, + * texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], }, + * normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], }, + * indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], }, + * }; + * + * @typedef {Object.} Arrays + * @memberOf module:twgl + */ + + +/** + * Creates a set of attribute data and WebGLBuffers from set of arrays + * + * Given + * + * var arrays = { + * position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], }, + * texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], }, + * normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], }, + * color: { numComponents: 4, data: [255, 255, 255, 255, 255, 0, 0, 255, 0, 0, 255, 255], type: Uint8Array, }, + * indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], }, + * }; + * + * returns something like + * + * var attribs = { + * position: { numComponents: 3, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, }, + * texcoord: { numComponents: 2, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, }, + * normal: { numComponents: 3, type: gl.FLOAT, normalize: false, buffer: WebGLBuffer, }, + * color: { numComponents: 4, type: gl.UNSIGNED_BYTE, normalize: true, buffer: WebGLBuffer, }, + * }; + * + * notes: + * + * * Arrays can take various forms + * + * Bare JavaScript Arrays + * + * var arrays = { + * position: [-1, 1, 0], + * normal: [0, 1, 0], + * ... + * } + * + * Bare TypedArrays + * + * var arrays = { + * position: new Float32Array([-1, 1, 0]), + * color: new Uint8Array([255, 128, 64, 255]), + * ... + * } + * + * * Will guess at `numComponents` if not specified based on name. + * + * If `coord` is in the name assumes `numComponents = 2` + * + * If `color` is in the name assumes `numComponents = 4` + * + * otherwise assumes `numComponents = 3` + * + * @param {WebGLRenderingContext} gl The webgl rendering context. + * @param {module:twgl.Arrays} arrays The arrays + * @param {module:twgl.BufferInfo} [srcBufferInfo] a BufferInfo to copy from + * This lets you share buffers. Any arrays you supply will override + * the buffers from srcBufferInfo. + * @return {Object.} the attribs + * @memberOf module:twgl/attributes + */ +function createAttribsFromArrays(gl, arrays) { + const attribs = {}; + Object.keys(arrays).forEach(function(arrayName) { + if (!isIndices(arrayName)) { + const array = arrays[arrayName]; + const attribName = array.attrib || array.name || array.attribName || (defaults$2.attribPrefix + arrayName); + if (array.value) { + if (!Array.isArray(array.value) && !isArrayBuffer$1(array.value)) { + throw new Error('array.value is not array or typedarray'); + } + attribs[attribName] = { + value: array.value, + }; + } else { + let fn; + if (array.buffer && array.buffer instanceof WebGLBuffer) { + fn = attribBufferFromBuffer; + } else if (typeof array === "number" || typeof array.data === "number") { + fn = attribBufferFromSize; + } else { + fn = attribBufferFromArrayLike; + } + const {buffer, type, numValues, arrayType} = fn(gl, array, arrayName); + const normalization = array.normalize !== undefined ? array.normalize : getNormalizationForTypedArrayType(arrayType); + const numComponents = getNumComponents$1(array, arrayName, numValues); + attribs[attribName] = { + buffer: buffer, + numComponents: numComponents, + type: type, + normalize: normalization, + stride: array.stride || 0, + offset: array.offset || 0, + divisor: array.divisor === undefined ? undefined : array.divisor, + drawType: array.drawType, + }; + } + } + }); + gl.bindBuffer(ARRAY_BUFFER$1, null); + return attribs; +} + +/** + * Sets the contents of a buffer attached to an attribInfo + * + * This is helper function to dynamically update a buffer. + * + * Let's say you make a bufferInfo + * + * var arrays = { + * position: new Float32Array([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]), + * texcoord: new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]), + * normal: new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]), + * indices: new Uint16Array([0, 1, 2, 1, 2, 3]), + * }; + * var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays); + * + * And you want to dynamically update the positions. You could do this + * + * // assuming arrays.position has already been updated with new data. + * twgl.setAttribInfoBufferFromArray(gl, bufferInfo.attribs.position, arrays.position); + * + * @param {WebGLRenderingContext} gl + * @param {AttribInfo} attribInfo The attribInfo who's buffer contents to set. NOTE: If you have an attribute prefix + * the name of the attribute will include the prefix. + * @param {ArraySpec} array Note: it is arguably inefficient to pass in anything but a typed array because anything + * else will have to be converted to a typed array before it can be used by WebGL. During init time that + * inefficiency is usually not important but if you're updating data dynamically best to be efficient. + * @param {number} [offset] an optional offset into the buffer. This is only an offset into the WebGL buffer + * not the array. To pass in an offset into the array itself use a typed array and create an `ArrayBufferView` + * for the portion of the array you want to use. + * + * var someArray = new Float32Array(1000); // an array with 1000 floats + * var someSubArray = new Float32Array(someArray.buffer, offsetInBytes, sizeInUnits); // a view into someArray + * + * Now you can pass `someSubArray` into setAttribInfoBufferFromArray` + * @memberOf module:twgl/attributes + */ +function setAttribInfoBufferFromArray(gl, attribInfo, array, offset) { + array = makeTypedArray(array); + if (offset !== undefined) { + gl.bindBuffer(ARRAY_BUFFER$1, attribInfo.buffer); + gl.bufferSubData(ARRAY_BUFFER$1, offset, array); + } else { + setBufferFromTypedArray(gl, ARRAY_BUFFER$1, attribInfo.buffer, array, attribInfo.drawType); + } +} + +function getBytesPerValueForGLType(gl, type) { + if (type === BYTE$1) return 1; // eslint-disable-line + if (type === UNSIGNED_BYTE$2) return 1; // eslint-disable-line + if (type === SHORT$1) return 2; // eslint-disable-line + if (type === UNSIGNED_SHORT$2) return 2; // eslint-disable-line + if (type === INT$2) return 4; // eslint-disable-line + if (type === UNSIGNED_INT$2) return 4; // eslint-disable-line + if (type === FLOAT$2) return 4; // eslint-disable-line + return 0; +} + +// Tries to get the number of elements from a set of arrays. +const positionKeys = ['position', 'positions', 'a_position']; +function getNumElementsFromNonIndexedArrays(arrays) { + let key; + let ii; + for (ii = 0; ii < positionKeys.length; ++ii) { + key = positionKeys[ii]; + if (key in arrays) { + break; + } + } + if (ii === positionKeys.length) { + key = Object.keys(arrays)[0]; + } + const array = arrays[key]; + const length = getArray$1(array).length; + if (length === undefined) { + return 1; // There's no arrays + } + const numComponents = getNumComponents$1(array, key); + const numElements = length / numComponents; + if (length % numComponents > 0) { + throw new Error(`numComponents ${numComponents} not correct for length ${length}`); + } + return numElements; +} + +function getNumElementsFromAttributes(gl, attribs) { + let key; + let ii; + for (ii = 0; ii < positionKeys.length; ++ii) { + key = positionKeys[ii]; + if (key in attribs) { + break; + } + key = defaults$2.attribPrefix + key; + if (key in attribs) { + break; + } + } + if (ii === positionKeys.length) { + key = Object.keys(attribs)[0]; + } + const attrib = attribs[key]; + if (!attrib.buffer) { + return 1; // There's no buffer + } + gl.bindBuffer(ARRAY_BUFFER$1, attrib.buffer); + const numBytes = gl.getBufferParameter(ARRAY_BUFFER$1, BUFFER_SIZE); + gl.bindBuffer(ARRAY_BUFFER$1, null); + + const bytesPerValue = getBytesPerValueForGLType(gl, attrib.type); + const totalElements = numBytes / bytesPerValue; + const numComponents = attrib.numComponents || attrib.size; + // TODO: check stride + const numElements = totalElements / numComponents; + if (numElements % 1 !== 0) { + throw new Error(`numComponents ${numComponents} not correct for length ${length}`); + } + return numElements; +} + +/** + * @typedef {Object} BufferInfo + * @property {number} numElements The number of elements to pass to `gl.drawArrays` or `gl.drawElements`. + * @property {number} [elementType] The type of indices `UNSIGNED_BYTE`, `UNSIGNED_SHORT` etc.. + * @property {WebGLBuffer} [indices] The indices `ELEMENT_ARRAY_BUFFER` if any indices exist. + * @property {Object.} [attribs] The attribs appropriate to call `setAttributes` + * @memberOf module:twgl + */ + +/** + * Creates a BufferInfo from an object of arrays. + * + * This can be passed to {@link module:twgl.setBuffersAndAttributes} and to + * {@link module:twgl:drawBufferInfo}. + * + * Given an object like + * + * var arrays = { + * position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], }, + * texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], }, + * normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], }, + * indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], }, + * }; + * + * Creates an BufferInfo like this + * + * bufferInfo = { + * numElements: 4, // or whatever the number of elements is + * indices: WebGLBuffer, // this property will not exist if there are no indices + * attribs: { + * position: { buffer: WebGLBuffer, numComponents: 3, }, + * normal: { buffer: WebGLBuffer, numComponents: 3, }, + * texcoord: { buffer: WebGLBuffer, numComponents: 2, }, + * }, + * }; + * + * The properties of arrays can be JavaScript arrays in which case the number of components + * will be guessed. + * + * var arrays = { + * position: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], + * texcoord: [0, 0, 0, 1, 1, 0, 1, 1], + * normal: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], + * indices: [0, 1, 2, 1, 2, 3], + * }; + * + * They can also be TypedArrays + * + * var arrays = { + * position: new Float32Array([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]), + * texcoord: new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]), + * normal: new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]), + * indices: new Uint16Array([0, 1, 2, 1, 2, 3]), + * }; + * + * Or AugmentedTypedArrays + * + * var positions = createAugmentedTypedArray(3, 4); + * var texcoords = createAugmentedTypedArray(2, 4); + * var normals = createAugmentedTypedArray(3, 4); + * var indices = createAugmentedTypedArray(3, 2, Uint16Array); + * + * positions.push([0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0]); + * texcoords.push([0, 0, 0, 1, 1, 0, 1, 1]); + * normals.push([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]); + * indices.push([0, 1, 2, 1, 2, 3]); + * + * var arrays = { + * position: positions, + * texcoord: texcoords, + * normal: normals, + * indices: indices, + * }; + * + * For the last example it is equivalent to + * + * var bufferInfo = { + * attribs: { + * position: { numComponents: 3, buffer: gl.createBuffer(), }, + * texcoord: { numComponents: 2, buffer: gl.createBuffer(), }, + * normal: { numComponents: 3, buffer: gl.createBuffer(), }, + * }, + * indices: gl.createBuffer(), + * numElements: 6, + * }; + * + * gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.position.buffer); + * gl.bufferData(gl.ARRAY_BUFFER, arrays.position, gl.STATIC_DRAW); + * gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.texcoord.buffer); + * gl.bufferData(gl.ARRAY_BUFFER, arrays.texcoord, gl.STATIC_DRAW); + * gl.bindBuffer(gl.ARRAY_BUFFER, bufferInfo.attribs.normal.buffer); + * gl.bufferData(gl.ARRAY_BUFFER, arrays.normal, gl.STATIC_DRAW); + * gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufferInfo.indices); + * gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, arrays.indices, gl.STATIC_DRAW); + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @param {module:twgl.Arrays} arrays Your data + * @param {module:twgl.BufferInfo} [srcBufferInfo] An existing + * buffer info to start from. WebGLBuffers etc specified + * in the srcBufferInfo will be used in a new BufferInfo + * with any arrays specified overriding the ones in + * srcBufferInfo. + * @return {module:twgl.BufferInfo} A BufferInfo + * @memberOf module:twgl/attributes + */ +function createBufferInfoFromArrays(gl, arrays, srcBufferInfo) { + const newAttribs = createAttribsFromArrays(gl, arrays); + const bufferInfo = Object.assign({}, srcBufferInfo ? srcBufferInfo : {}); + bufferInfo.attribs = Object.assign({}, srcBufferInfo ? srcBufferInfo.attribs : {}, newAttribs); + const indices = arrays.indices; + if (indices) { + const newIndices = makeTypedArray(indices, "indices"); + bufferInfo.indices = createBufferFromTypedArray(gl, newIndices, ELEMENT_ARRAY_BUFFER$2); + bufferInfo.numElements = newIndices.length; + bufferInfo.elementType = getGLTypeForTypedArray(newIndices); + } else if (!bufferInfo.numElements) { + bufferInfo.numElements = getNumElementsFromAttributes(gl, bufferInfo.attribs); + } + + return bufferInfo; +} + +/** + * Creates a buffer from an array, typed array, or array spec + * + * Given something like this + * + * [1, 2, 3], + * + * or + * + * new Uint16Array([1,2,3]); + * + * or + * + * { + * data: [1, 2, 3], + * type: Uint8Array, + * } + * + * returns a WebGLBuffer that contains the given data. + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext. + * @param {module:twgl.ArraySpec} array an array, typed array, or array spec. + * @param {string} arrayName name of array. Used to guess the type if type can not be derived otherwise. + * @return {WebGLBuffer} a WebGLBuffer containing the data in array. + * @memberOf module:twgl/attributes + */ +function createBufferFromArray(gl, array, arrayName) { + const type = arrayName === "indices" ? ELEMENT_ARRAY_BUFFER$2 : ARRAY_BUFFER$1; + const typedArray = makeTypedArray(array, arrayName); + return createBufferFromTypedArray(gl, typedArray, type); +} + +/** + * Creates buffers from arrays or typed arrays + * + * Given something like this + * + * var arrays = { + * positions: [1, 2, 3], + * normals: [0, 0, 1], + * } + * + * returns something like + * + * buffers = { + * positions: WebGLBuffer, + * normals: WebGLBuffer, + * } + * + * If the buffer is named 'indices' it will be made an ELEMENT_ARRAY_BUFFER. + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext. + * @param {module:twgl.Arrays} arrays + * @return {Object} returns an object with one WebGLBuffer per array + * @memberOf module:twgl/attributes + */ +function createBuffersFromArrays(gl, arrays) { + const buffers = { }; + Object.keys(arrays).forEach(function(key) { + buffers[key] = createBufferFromArray(gl, arrays[key], key); + }); + + // Ugh! + if (arrays.indices) { + buffers.numElements = arrays.indices.length; + buffers.elementType = getGLTypeForTypedArray(makeTypedArray(arrays.indices)); + } else { + buffers.numElements = getNumElementsFromNonIndexedArrays(arrays); + } + + return buffers; +} + +var attributes = /*#__PURE__*/Object.freeze({ + __proto__: null, + createAttribsFromArrays: createAttribsFromArrays, + createBuffersFromArrays: createBuffersFromArrays, + createBufferFromArray: createBufferFromArray, + createBufferFromTypedArray: createBufferFromTypedArray, + createBufferInfoFromArrays: createBufferInfoFromArrays, + setAttribInfoBufferFromArray: setAttribInfoBufferFromArray, + setAttributePrefix: setAttributePrefix, + setAttributeDefaults_: setDefaults$2, + getNumComponents_: getNumComponents$1, + getArray_: getArray$1 +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +const getArray = getArray$1; // eslint-disable-line +const getNumComponents = getNumComponents$1; // eslint-disable-line + +/** + * @typedef {(Int8Array|Uint8Array|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array)} TypedArray + */ + +/** + * Add `push` to a typed array. It just keeps a 'cursor' + * and allows use to `push` values into the array so we + * don't have to manually compute offsets + * @param {TypedArray} typedArray TypedArray to augment + * @param {number} numComponents number of components. + * @private + */ +function augmentTypedArray(typedArray, numComponents) { + let cursor = 0; + typedArray.push = function() { + for (let ii = 0; ii < arguments.length; ++ii) { + const value = arguments[ii]; + if (value instanceof Array || isArrayBuffer$1(value)) { + for (let jj = 0; jj < value.length; ++jj) { + typedArray[cursor++] = value[jj]; + } + } else { + typedArray[cursor++] = value; + } + } + }; + typedArray.reset = function(opt_index) { + cursor = opt_index || 0; + }; + typedArray.numComponents = numComponents; + Object.defineProperty(typedArray, 'numElements', { + get: function() { + return this.length / this.numComponents | 0; + }, + }); + return typedArray; +} + +/** + * creates a typed array with a `push` function attached + * so that you can easily *push* values. + * + * `push` can take multiple arguments. If an argument is an array each element + * of the array will be added to the typed array. + * + * Example: + * + * const array = createAugmentedTypedArray(3, 2); // creates a Float32Array with 6 values + * array.push(1, 2, 3); + * array.push([4, 5, 6]); + * // array now contains [1, 2, 3, 4, 5, 6] + * + * Also has `numComponents` and `numElements` properties. + * + * @param {number} numComponents number of components + * @param {number} numElements number of elements. The total size of the array will be `numComponents * numElements`. + * @param {constructor} opt_type A constructor for the type. Default = `Float32Array`. + * @return {ArrayBufferView} A typed array. + * @memberOf module:twgl/primitives + */ +function createAugmentedTypedArray(numComponents, numElements, opt_type) { + const Type = opt_type || Float32Array; + return augmentTypedArray(new Type(numComponents * numElements), numComponents); +} + +function allButIndices(name) { + return name !== "indices"; +} + +/** + * Given indexed vertices creates a new set of vertices un-indexed by expanding the indexed vertices. + * @param {Object.} vertices The indexed vertices to deindex + * @return {Object.} The deindexed vertices + * @memberOf module:twgl/primitives + */ +function deindexVertices(vertices) { + const indices = vertices.indices; + const newVertices = {}; + const numElements = indices.length; + + function expandToUnindexed(channel) { + const srcBuffer = vertices[channel]; + const numComponents = srcBuffer.numComponents; + const dstBuffer = createAugmentedTypedArray(numComponents, numElements, srcBuffer.constructor); + for (let ii = 0; ii < numElements; ++ii) { + const ndx = indices[ii]; + const offset = ndx * numComponents; + for (let jj = 0; jj < numComponents; ++jj) { + dstBuffer.push(srcBuffer[offset + jj]); + } + } + newVertices[channel] = dstBuffer; + } + + Object.keys(vertices).filter(allButIndices).forEach(expandToUnindexed); + + return newVertices; +} + +/** + * flattens the normals of deindexed vertices in place. + * @param {Object.} vertices The deindexed vertices who's normals to flatten + * @return {Object.} The flattened vertices (same as was passed in) + * @memberOf module:twgl/primitives + */ +function flattenNormals(vertices) { + if (vertices.indices) { + throw new Error('can not flatten normals of indexed vertices. deindex them first'); + } + + const normals = vertices.normal; + const numNormals = normals.length; + for (let ii = 0; ii < numNormals; ii += 9) { + // pull out the 3 normals for this triangle + const nax = normals[ii + 0]; + const nay = normals[ii + 1]; + const naz = normals[ii + 2]; + + const nbx = normals[ii + 3]; + const nby = normals[ii + 4]; + const nbz = normals[ii + 5]; + + const ncx = normals[ii + 6]; + const ncy = normals[ii + 7]; + const ncz = normals[ii + 8]; + + // add them + let nx = nax + nbx + ncx; + let ny = nay + nby + ncy; + let nz = naz + nbz + ncz; + + // normalize them + const length = Math.sqrt(nx * nx + ny * ny + nz * nz); + + nx /= length; + ny /= length; + nz /= length; + + // copy them back in + normals[ii + 0] = nx; + normals[ii + 1] = ny; + normals[ii + 2] = nz; + + normals[ii + 3] = nx; + normals[ii + 4] = ny; + normals[ii + 5] = nz; + + normals[ii + 6] = nx; + normals[ii + 7] = ny; + normals[ii + 8] = nz; + } + + return vertices; +} + +function applyFuncToV3Array(array, matrix, fn) { + const len = array.length; + const tmp = new Float32Array(3); + for (let ii = 0; ii < len; ii += 3) { + fn(matrix, [array[ii], array[ii + 1], array[ii + 2]], tmp); + array[ii ] = tmp[0]; + array[ii + 1] = tmp[1]; + array[ii + 2] = tmp[2]; + } +} + +function transformNormal(mi, v, dst) { + dst = dst || create$1(); + const v0 = v[0]; + const v1 = v[1]; + const v2 = v[2]; + + dst[0] = v0 * mi[0 * 4 + 0] + v1 * mi[0 * 4 + 1] + v2 * mi[0 * 4 + 2]; + dst[1] = v0 * mi[1 * 4 + 0] + v1 * mi[1 * 4 + 1] + v2 * mi[1 * 4 + 2]; + dst[2] = v0 * mi[2 * 4 + 0] + v1 * mi[2 * 4 + 1] + v2 * mi[2 * 4 + 2]; + + return dst; +} + +/** + * Reorients directions by the given matrix.. + * @param {(number[]|TypedArray)} array The array. Assumes value floats per element. + * @param {module:twgl/m4.Mat4} matrix A matrix to multiply by. + * @return {(number[]|TypedArray)} the same array that was passed in + * @memberOf module:twgl/primitives + */ +function reorientDirections(array, matrix) { + applyFuncToV3Array(array, matrix, transformDirection); + return array; +} + +/** + * Reorients normals by the inverse-transpose of the given + * matrix.. + * @param {(number[]|TypedArray)} array The array. Assumes value floats per element. + * @param {module:twgl/m4.Mat4} matrix A matrix to multiply by. + * @return {(number[]|TypedArray)} the same array that was passed in + * @memberOf module:twgl/primitives + */ +function reorientNormals(array, matrix) { + applyFuncToV3Array(array, inverse(matrix), transformNormal); + return array; +} + +/** + * Reorients positions by the given matrix. In other words, it + * multiplies each vertex by the given matrix. + * @param {(number[]|TypedArray)} array The array. Assumes value floats per element. + * @param {module:twgl/m4.Mat4} matrix A matrix to multiply by. + * @return {(number[]|TypedArray)} the same array that was passed in + * @memberOf module:twgl/primitives + */ +function reorientPositions(array, matrix) { + applyFuncToV3Array(array, matrix, transformPoint); + return array; +} + +/** + * @typedef {(number[]|TypedArray)} NativeArrayOrTypedArray + */ + +/** + * Reorients arrays by the given matrix. Assumes arrays have + * names that contains 'pos' could be reoriented as positions, + * 'binorm' or 'tan' as directions, and 'norm' as normals. + * + * @param {Object.} arrays The vertices to reorient + * @param {module:twgl/m4.Mat4} matrix matrix to reorient by. + * @return {Object.} same arrays that were passed in. + * @memberOf module:twgl/primitives + */ +function reorientVertices(arrays, matrix) { + Object.keys(arrays).forEach(function(name) { + const array = arrays[name]; + if (name.indexOf("pos") >= 0) { + reorientPositions(array, matrix); + } else if (name.indexOf("tan") >= 0 || name.indexOf("binorm") >= 0) { + reorientDirections(array, matrix); + } else if (name.indexOf("norm") >= 0) { + reorientNormals(array, matrix); + } + }); + return arrays; +} + +/** + * Creates XY quad BufferInfo + * + * The default with no parameters will return a 2x2 quad with values from -1 to +1. + * If you want a unit quad with that goes from 0 to 1 you'd call it with + * + * twgl.primitives.createXYQuadBufferInfo(gl, 1, 0.5, 0.5); + * + * If you want a unit quad centered above 0,0 you'd call it with + * + * twgl.primitives.createXYQuadBufferInfo(gl, 1, 0, 0.5); + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} [size] the size across the quad. Defaults to 2 which means vertices will go from -1 to +1 + * @param {number} [xOffset] the amount to offset the quad in X + * @param {number} [yOffset] the amount to offset the quad in Y + * @return {Object.} the created XY Quad BufferInfo + * @memberOf module:twgl/primitives + * @function createXYQuadBuffers + */ + +/** + * Creates XY quad Buffers + * + * The default with no parameters will return a 2x2 quad with values from -1 to +1. + * If you want a unit quad with that goes from 0 to 1 you'd call it with + * + * twgl.primitives.createXYQuadBufferInfo(gl, 1, 0.5, 0.5); + * + * If you want a unit quad centered above 0,0 you'd call it with + * + * twgl.primitives.createXYQuadBufferInfo(gl, 1, 0, 0.5); + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} [size] the size across the quad. Defaults to 2 which means vertices will go from -1 to +1 + * @param {number} [xOffset] the amount to offset the quad in X + * @param {number} [yOffset] the amount to offset the quad in Y + * @return {module:twgl.BufferInfo} the created XY Quad buffers + * @memberOf module:twgl/primitives + * @function createXYQuadBufferInfo + */ + +/** + * Creates XY quad vertices + * + * The default with no parameters will return a 2x2 quad with values from -1 to +1. + * If you want a unit quad with that goes from 0 to 1 you'd call it with + * + * twgl.primitives.createXYQuadVertices(1, 0.5, 0.5); + * + * If you want a unit quad centered above 0,0 you'd call it with + * + * twgl.primitives.createXYQuadVertices(1, 0, 0.5); + * + * @param {number} [size] the size across the quad. Defaults to 2 which means vertices will go from -1 to +1 + * @param {number} [xOffset] the amount to offset the quad in X + * @param {number} [yOffset] the amount to offset the quad in Y + * @return {Object.} the created XY Quad vertices + * @memberOf module:twgl/primitives + */ +function createXYQuadVertices(size, xOffset, yOffset) { + size = size || 2; + xOffset = xOffset || 0; + yOffset = yOffset || 0; + size *= 0.5; + return { + position: { + numComponents: 2, + data: [ + xOffset + -1 * size, yOffset + -1 * size, + xOffset + 1 * size, yOffset + -1 * size, + xOffset + -1 * size, yOffset + 1 * size, + xOffset + 1 * size, yOffset + 1 * size, + ], + }, + normal: [ + 0, 0, 1, + 0, 0, 1, + 0, 0, 1, + 0, 0, 1, + ], + texcoord: [ + 0, 0, + 1, 0, + 0, 1, + 1, 1, + ], + indices: [ 0, 1, 2, 2, 1, 3 ], + }; +} + +/** + * Creates XZ plane BufferInfo. + * + * The created plane has position, normal, and texcoord data + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} [width] Width of the plane. Default = 1 + * @param {number} [depth] Depth of the plane. Default = 1 + * @param {number} [subdivisionsWidth] Number of steps across the plane. Default = 1 + * @param {number} [subdivisionsDepth] Number of steps down the plane. Default = 1 + * @param {module:twgl/m4.Mat4} [matrix] A matrix by which to multiply all the vertices. + * @return {module:twgl.BufferInfo} The created plane BufferInfo. + * @memberOf module:twgl/primitives + * @function createPlaneBufferInfo + */ + +/** + * Creates XZ plane buffers. + * + * The created plane has position, normal, and texcoord data + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} [width] Width of the plane. Default = 1 + * @param {number} [depth] Depth of the plane. Default = 1 + * @param {number} [subdivisionsWidth] Number of steps across the plane. Default = 1 + * @param {number} [subdivisionsDepth] Number of steps down the plane. Default = 1 + * @param {module:twgl/m4.Mat4} [matrix] A matrix by which to multiply all the vertices. + * @return {Object.} The created plane buffers. + * @memberOf module:twgl/primitives + * @function createPlaneBuffers + */ + +/** + * Creates XZ plane vertices. + * + * The created plane has position, normal, and texcoord data + * + * @param {number} [width] Width of the plane. Default = 1 + * @param {number} [depth] Depth of the plane. Default = 1 + * @param {number} [subdivisionsWidth] Number of steps across the plane. Default = 1 + * @param {number} [subdivisionsDepth] Number of steps down the plane. Default = 1 + * @param {module:twgl/m4.Mat4} [matrix] A matrix by which to multiply all the vertices. + * @return {Object.} The created plane vertices. + * @memberOf module:twgl/primitives + */ +function createPlaneVertices( + width, + depth, + subdivisionsWidth, + subdivisionsDepth, + matrix) { + width = width || 1; + depth = depth || 1; + subdivisionsWidth = subdivisionsWidth || 1; + subdivisionsDepth = subdivisionsDepth || 1; + matrix = matrix || identity(); + + const numVertices = (subdivisionsWidth + 1) * (subdivisionsDepth + 1); + const positions = createAugmentedTypedArray(3, numVertices); + const normals = createAugmentedTypedArray(3, numVertices); + const texcoords = createAugmentedTypedArray(2, numVertices); + + for (let z = 0; z <= subdivisionsDepth; z++) { + for (let x = 0; x <= subdivisionsWidth; x++) { + const u = x / subdivisionsWidth; + const v = z / subdivisionsDepth; + positions.push( + width * u - width * 0.5, + 0, + depth * v - depth * 0.5); + normals.push(0, 1, 0); + texcoords.push(u, v); + } + } + + const numVertsAcross = subdivisionsWidth + 1; + const indices = createAugmentedTypedArray( + 3, subdivisionsWidth * subdivisionsDepth * 2, Uint16Array); + + for (let z = 0; z < subdivisionsDepth; z++) { // eslint-disable-line + for (let x = 0; x < subdivisionsWidth; x++) { // eslint-disable-line + // Make triangle 1 of quad. + indices.push( + (z + 0) * numVertsAcross + x, + (z + 1) * numVertsAcross + x, + (z + 0) * numVertsAcross + x + 1); + + // Make triangle 2 of quad. + indices.push( + (z + 1) * numVertsAcross + x, + (z + 1) * numVertsAcross + x + 1, + (z + 0) * numVertsAcross + x + 1); + } + } + + const arrays = reorientVertices({ + position: positions, + normal: normals, + texcoord: texcoords, + indices: indices, + }, matrix); + return arrays; +} + +/** + * Creates sphere BufferInfo. + * + * The created sphere has position, normal, and texcoord data + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} radius radius of the sphere. + * @param {number} subdivisionsAxis number of steps around the sphere. + * @param {number} subdivisionsHeight number of vertically on the sphere. + * @param {number} [opt_startLatitudeInRadians] where to start the + * top of the sphere. Default = 0. + * @param {number} [opt_endLatitudeInRadians] Where to end the + * bottom of the sphere. Default = Math.PI. + * @param {number} [opt_startLongitudeInRadians] where to start + * wrapping the sphere. Default = 0. + * @param {number} [opt_endLongitudeInRadians] where to end + * wrapping the sphere. Default = 2 * Math.PI. + * @return {module:twgl.BufferInfo} The created sphere BufferInfo. + * @memberOf module:twgl/primitives + * @function createSphereBufferInfo + */ + +/** + * Creates sphere buffers. + * + * The created sphere has position, normal, and texcoord data + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} radius radius of the sphere. + * @param {number} subdivisionsAxis number of steps around the sphere. + * @param {number} subdivisionsHeight number of vertically on the sphere. + * @param {number} [opt_startLatitudeInRadians] where to start the + * top of the sphere. Default = 0. + * @param {number} [opt_endLatitudeInRadians] Where to end the + * bottom of the sphere. Default = Math.PI. + * @param {number} [opt_startLongitudeInRadians] where to start + * wrapping the sphere. Default = 0. + * @param {number} [opt_endLongitudeInRadians] where to end + * wrapping the sphere. Default = 2 * Math.PI. + * @return {Object.} The created sphere buffers. + * @memberOf module:twgl/primitives + * @function createSphereBuffers + */ + +/** + * Creates sphere vertices. + * + * The created sphere has position, normal, and texcoord data + * + * @param {number} radius radius of the sphere. + * @param {number} subdivisionsAxis number of steps around the sphere. + * @param {number} subdivisionsHeight number of vertically on the sphere. + * @param {number} [opt_startLatitudeInRadians] where to start the + * top of the sphere. Default = 0. + * @param {number} [opt_endLatitudeInRadians] Where to end the + * bottom of the sphere. Default = Math.PI. + * @param {number} [opt_startLongitudeInRadians] where to start + * wrapping the sphere. Default = 0. + * @param {number} [opt_endLongitudeInRadians] where to end + * wrapping the sphere. Default = 2 * Math.PI. + * @return {Object.} The created sphere vertices. + * @memberOf module:twgl/primitives + */ +function createSphereVertices( + radius, + subdivisionsAxis, + subdivisionsHeight, + opt_startLatitudeInRadians, + opt_endLatitudeInRadians, + opt_startLongitudeInRadians, + opt_endLongitudeInRadians) { + if (subdivisionsAxis <= 0 || subdivisionsHeight <= 0) { + throw new Error('subdivisionAxis and subdivisionHeight must be > 0'); + } + + opt_startLatitudeInRadians = opt_startLatitudeInRadians || 0; + opt_endLatitudeInRadians = opt_endLatitudeInRadians || Math.PI; + opt_startLongitudeInRadians = opt_startLongitudeInRadians || 0; + opt_endLongitudeInRadians = opt_endLongitudeInRadians || (Math.PI * 2); + + const latRange = opt_endLatitudeInRadians - opt_startLatitudeInRadians; + const longRange = opt_endLongitudeInRadians - opt_startLongitudeInRadians; + + // We are going to generate our sphere by iterating through its + // spherical coordinates and generating 2 triangles for each quad on a + // ring of the sphere. + const numVertices = (subdivisionsAxis + 1) * (subdivisionsHeight + 1); + const positions = createAugmentedTypedArray(3, numVertices); + const normals = createAugmentedTypedArray(3, numVertices); + const texcoords = createAugmentedTypedArray(2, numVertices); + + // Generate the individual vertices in our vertex buffer. + for (let y = 0; y <= subdivisionsHeight; y++) { + for (let x = 0; x <= subdivisionsAxis; x++) { + // Generate a vertex based on its spherical coordinates + const u = x / subdivisionsAxis; + const v = y / subdivisionsHeight; + const theta = longRange * u + opt_startLongitudeInRadians; + const phi = latRange * v + opt_startLatitudeInRadians; + const sinTheta = Math.sin(theta); + const cosTheta = Math.cos(theta); + const sinPhi = Math.sin(phi); + const cosPhi = Math.cos(phi); + const ux = cosTheta * sinPhi; + const uy = cosPhi; + const uz = sinTheta * sinPhi; + positions.push(radius * ux, radius * uy, radius * uz); + normals.push(ux, uy, uz); + texcoords.push(1 - u, v); + } + } + + const numVertsAround = subdivisionsAxis + 1; + const indices = createAugmentedTypedArray(3, subdivisionsAxis * subdivisionsHeight * 2, Uint16Array); + for (let x = 0; x < subdivisionsAxis; x++) { // eslint-disable-line + for (let y = 0; y < subdivisionsHeight; y++) { // eslint-disable-line + // Make triangle 1 of quad. + indices.push( + (y + 0) * numVertsAround + x, + (y + 0) * numVertsAround + x + 1, + (y + 1) * numVertsAround + x); + + // Make triangle 2 of quad. + indices.push( + (y + 1) * numVertsAround + x, + (y + 0) * numVertsAround + x + 1, + (y + 1) * numVertsAround + x + 1); + } + } + + return { + position: positions, + normal: normals, + texcoord: texcoords, + indices: indices, + }; +} + +/** + * Array of the indices of corners of each face of a cube. + * @type {Array.} + * @private + */ +const CUBE_FACE_INDICES = [ + [3, 7, 5, 1], // right + [6, 2, 0, 4], // left + [6, 7, 3, 2], // ?? + [0, 1, 5, 4], // ?? + [7, 6, 4, 5], // front + [2, 3, 1, 0], // back +]; + +/** + * Creates a BufferInfo for a cube. + * + * The cube is created around the origin. (-size / 2, size / 2). + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} [size] width, height and depth of the cube. + * @return {module:twgl.BufferInfo} The created BufferInfo. + * @memberOf module:twgl/primitives + * @function createCubeBufferInfo + */ + +/** + * Creates the buffers and indices for a cube. + * + * The cube is created around the origin. (-size / 2, size / 2). + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} [size] width, height and depth of the cube. + * @return {Object.} The created buffers. + * @memberOf module:twgl/primitives + * @function createCubeBuffers + */ + +/** + * Creates the vertices and indices for a cube. + * + * The cube is created around the origin. (-size / 2, size / 2). + * + * @param {number} [size] width, height and depth of the cube. + * @return {Object.} The created vertices. + * @memberOf module:twgl/primitives + */ +function createCubeVertices(size) { + size = size || 1; + const k = size / 2; + + const cornerVertices = [ + [-k, -k, -k], + [+k, -k, -k], + [-k, +k, -k], + [+k, +k, -k], + [-k, -k, +k], + [+k, -k, +k], + [-k, +k, +k], + [+k, +k, +k], + ]; + + const faceNormals = [ + [+1, +0, +0], + [-1, +0, +0], + [+0, +1, +0], + [+0, -1, +0], + [+0, +0, +1], + [+0, +0, -1], + ]; + + const uvCoords = [ + [1, 0], + [0, 0], + [0, 1], + [1, 1], + ]; + + const numVertices = 6 * 4; + const positions = createAugmentedTypedArray(3, numVertices); + const normals = createAugmentedTypedArray(3, numVertices); + const texcoords = createAugmentedTypedArray(2 , numVertices); + const indices = createAugmentedTypedArray(3, 6 * 2, Uint16Array); + + for (let f = 0; f < 6; ++f) { + const faceIndices = CUBE_FACE_INDICES[f]; + for (let v = 0; v < 4; ++v) { + const position = cornerVertices[faceIndices[v]]; + const normal = faceNormals[f]; + const uv = uvCoords[v]; + + // Each face needs all four vertices because the normals and texture + // coordinates are not all the same. + positions.push(position); + normals.push(normal); + texcoords.push(uv); + + } + // Two triangles make a square face. + const offset = 4 * f; + indices.push(offset + 0, offset + 1, offset + 2); + indices.push(offset + 0, offset + 2, offset + 3); + } + + return { + position: positions, + normal: normals, + texcoord: texcoords, + indices: indices, + }; +} + +/** + * Creates a BufferInfo for a truncated cone, which is like a cylinder + * except that it has different top and bottom radii. A truncated cone + * can also be used to create cylinders and regular cones. The + * truncated cone will be created centered about the origin, with the + * y axis as its vertical axis. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} bottomRadius Bottom radius of truncated cone. + * @param {number} topRadius Top radius of truncated cone. + * @param {number} height Height of truncated cone. + * @param {number} radialSubdivisions The number of subdivisions around the + * truncated cone. + * @param {number} verticalSubdivisions The number of subdivisions down the + * truncated cone. + * @param {boolean} [opt_topCap] Create top cap. Default = true. + * @param {boolean} [opt_bottomCap] Create bottom cap. Default = true. + * @return {module:twgl.BufferInfo} The created cone BufferInfo. + * @memberOf module:twgl/primitives + * @function createTruncatedConeBufferInfo + */ + +/** + * Creates buffers for a truncated cone, which is like a cylinder + * except that it has different top and bottom radii. A truncated cone + * can also be used to create cylinders and regular cones. The + * truncated cone will be created centered about the origin, with the + * y axis as its vertical axis. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} bottomRadius Bottom radius of truncated cone. + * @param {number} topRadius Top radius of truncated cone. + * @param {number} height Height of truncated cone. + * @param {number} radialSubdivisions The number of subdivisions around the + * truncated cone. + * @param {number} verticalSubdivisions The number of subdivisions down the + * truncated cone. + * @param {boolean} [opt_topCap] Create top cap. Default = true. + * @param {boolean} [opt_bottomCap] Create bottom cap. Default = true. + * @return {Object.} The created cone buffers. + * @memberOf module:twgl/primitives + * @function createTruncatedConeBuffers + */ + +/** + * Creates vertices for a truncated cone, which is like a cylinder + * except that it has different top and bottom radii. A truncated cone + * can also be used to create cylinders and regular cones. The + * truncated cone will be created centered about the origin, with the + * y axis as its vertical axis. . + * + * @param {number} bottomRadius Bottom radius of truncated cone. + * @param {number} topRadius Top radius of truncated cone. + * @param {number} height Height of truncated cone. + * @param {number} radialSubdivisions The number of subdivisions around the + * truncated cone. + * @param {number} verticalSubdivisions The number of subdivisions down the + * truncated cone. + * @param {boolean} [opt_topCap] Create top cap. Default = true. + * @param {boolean} [opt_bottomCap] Create bottom cap. Default = true. + * @return {Object.} The created cone vertices. + * @memberOf module:twgl/primitives + */ +function createTruncatedConeVertices( + bottomRadius, + topRadius, + height, + radialSubdivisions, + verticalSubdivisions, + opt_topCap, + opt_bottomCap) { + if (radialSubdivisions < 3) { + throw new Error('radialSubdivisions must be 3 or greater'); + } + + if (verticalSubdivisions < 1) { + throw new Error('verticalSubdivisions must be 1 or greater'); + } + + const topCap = (opt_topCap === undefined) ? true : opt_topCap; + const bottomCap = (opt_bottomCap === undefined) ? true : opt_bottomCap; + + const extra = (topCap ? 2 : 0) + (bottomCap ? 2 : 0); + + const numVertices = (radialSubdivisions + 1) * (verticalSubdivisions + 1 + extra); + const positions = createAugmentedTypedArray(3, numVertices); + const normals = createAugmentedTypedArray(3, numVertices); + const texcoords = createAugmentedTypedArray(2, numVertices); + const indices = createAugmentedTypedArray(3, radialSubdivisions * (verticalSubdivisions + extra / 2) * 2, Uint16Array); + + const vertsAroundEdge = radialSubdivisions + 1; + + // The slant of the cone is constant across its surface + const slant = Math.atan2(bottomRadius - topRadius, height); + const cosSlant = Math.cos(slant); + const sinSlant = Math.sin(slant); + + const start = topCap ? -2 : 0; + const end = verticalSubdivisions + (bottomCap ? 2 : 0); + + for (let yy = start; yy <= end; ++yy) { + let v = yy / verticalSubdivisions; + let y = height * v; + let ringRadius; + if (yy < 0) { + y = 0; + v = 1; + ringRadius = bottomRadius; + } else if (yy > verticalSubdivisions) { + y = height; + v = 1; + ringRadius = topRadius; + } else { + ringRadius = bottomRadius + + (topRadius - bottomRadius) * (yy / verticalSubdivisions); + } + if (yy === -2 || yy === verticalSubdivisions + 2) { + ringRadius = 0; + v = 0; + } + y -= height / 2; + for (let ii = 0; ii < vertsAroundEdge; ++ii) { + const sin = Math.sin(ii * Math.PI * 2 / radialSubdivisions); + const cos = Math.cos(ii * Math.PI * 2 / radialSubdivisions); + positions.push(sin * ringRadius, y, cos * ringRadius); + if (yy < 0) { + normals.push(0, -1, 0); + } else if (yy > verticalSubdivisions) { + normals.push(0, 1, 0); + } else if (ringRadius === 0.0) { + normals.push(0, 0, 0); + } else { + normals.push(sin * cosSlant, sinSlant, cos * cosSlant); + } + texcoords.push((ii / radialSubdivisions), 1 - v); + } + } + + for (let yy = 0; yy < verticalSubdivisions + extra; ++yy) { // eslint-disable-line + if (yy === 1 && topCap || yy === verticalSubdivisions + extra - 2 && bottomCap) { + continue; + } + for (let ii = 0; ii < radialSubdivisions; ++ii) { // eslint-disable-line + indices.push(vertsAroundEdge * (yy + 0) + 0 + ii, + vertsAroundEdge * (yy + 0) + 1 + ii, + vertsAroundEdge * (yy + 1) + 1 + ii); + indices.push(vertsAroundEdge * (yy + 0) + 0 + ii, + vertsAroundEdge * (yy + 1) + 1 + ii, + vertsAroundEdge * (yy + 1) + 0 + ii); + } + } + + return { + position: positions, + normal: normals, + texcoord: texcoords, + indices: indices, + }; +} + +/** + * Expands RLE data + * @param {number[]} rleData data in format of run-length, x, y, z, run-length, x, y, z + * @param {number[]} [padding] value to add each entry with. + * @return {number[]} the expanded rleData + * @private + */ +function expandRLEData(rleData, padding) { + padding = padding || []; + const data = []; + for (let ii = 0; ii < rleData.length; ii += 4) { + const runLength = rleData[ii]; + const element = rleData.slice(ii + 1, ii + 4); + element.push.apply(element, padding); + for (let jj = 0; jj < runLength; ++jj) { + data.push.apply(data, element); + } + } + return data; +} + +/** + * Creates 3D 'F' BufferInfo. + * An 'F' is useful because you can easily tell which way it is oriented. + * The created 'F' has position, normal, texcoord, and color buffers. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @return {module:twgl.BufferInfo} The created BufferInfo. + * @memberOf module:twgl/primitives + * @function create3DFBufferInfo + */ + +/** + * Creates 3D 'F' buffers. + * An 'F' is useful because you can easily tell which way it is oriented. + * The created 'F' has position, normal, texcoord, and color buffers. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @return {Object.} The created buffers. + * @memberOf module:twgl/primitives + * @function create3DFBuffers + */ + +/** + * Creates 3D 'F' vertices. + * An 'F' is useful because you can easily tell which way it is oriented. + * The created 'F' has position, normal, texcoord, and color arrays. + * + * @return {Object.} The created vertices. + * @memberOf module:twgl/primitives + */ +function create3DFVertices() { + + const positions = [ + // left column front + 0, 0, 0, + 0, 150, 0, + 30, 0, 0, + 0, 150, 0, + 30, 150, 0, + 30, 0, 0, + + // top rung front + 30, 0, 0, + 30, 30, 0, + 100, 0, 0, + 30, 30, 0, + 100, 30, 0, + 100, 0, 0, + + // middle rung front + 30, 60, 0, + 30, 90, 0, + 67, 60, 0, + 30, 90, 0, + 67, 90, 0, + 67, 60, 0, + + // left column back + 0, 0, 30, + 30, 0, 30, + 0, 150, 30, + 0, 150, 30, + 30, 0, 30, + 30, 150, 30, + + // top rung back + 30, 0, 30, + 100, 0, 30, + 30, 30, 30, + 30, 30, 30, + 100, 0, 30, + 100, 30, 30, + + // middle rung back + 30, 60, 30, + 67, 60, 30, + 30, 90, 30, + 30, 90, 30, + 67, 60, 30, + 67, 90, 30, + + // top + 0, 0, 0, + 100, 0, 0, + 100, 0, 30, + 0, 0, 0, + 100, 0, 30, + 0, 0, 30, + + // top rung front + 100, 0, 0, + 100, 30, 0, + 100, 30, 30, + 100, 0, 0, + 100, 30, 30, + 100, 0, 30, + + // under top rung + 30, 30, 0, + 30, 30, 30, + 100, 30, 30, + 30, 30, 0, + 100, 30, 30, + 100, 30, 0, + + // between top rung and middle + 30, 30, 0, + 30, 60, 30, + 30, 30, 30, + 30, 30, 0, + 30, 60, 0, + 30, 60, 30, + + // top of middle rung + 30, 60, 0, + 67, 60, 30, + 30, 60, 30, + 30, 60, 0, + 67, 60, 0, + 67, 60, 30, + + // front of middle rung + 67, 60, 0, + 67, 90, 30, + 67, 60, 30, + 67, 60, 0, + 67, 90, 0, + 67, 90, 30, + + // bottom of middle rung. + 30, 90, 0, + 30, 90, 30, + 67, 90, 30, + 30, 90, 0, + 67, 90, 30, + 67, 90, 0, + + // front of bottom + 30, 90, 0, + 30, 150, 30, + 30, 90, 30, + 30, 90, 0, + 30, 150, 0, + 30, 150, 30, + + // bottom + 0, 150, 0, + 0, 150, 30, + 30, 150, 30, + 0, 150, 0, + 30, 150, 30, + 30, 150, 0, + + // left side + 0, 0, 0, + 0, 0, 30, + 0, 150, 30, + 0, 0, 0, + 0, 150, 30, + 0, 150, 0, + ]; + + const texcoords = [ + // left column front + 0.22, 0.19, + 0.22, 0.79, + 0.34, 0.19, + 0.22, 0.79, + 0.34, 0.79, + 0.34, 0.19, + + // top rung front + 0.34, 0.19, + 0.34, 0.31, + 0.62, 0.19, + 0.34, 0.31, + 0.62, 0.31, + 0.62, 0.19, + + // middle rung front + 0.34, 0.43, + 0.34, 0.55, + 0.49, 0.43, + 0.34, 0.55, + 0.49, 0.55, + 0.49, 0.43, + + // left column back + 0, 0, + 1, 0, + 0, 1, + 0, 1, + 1, 0, + 1, 1, + + // top rung back + 0, 0, + 1, 0, + 0, 1, + 0, 1, + 1, 0, + 1, 1, + + // middle rung back + 0, 0, + 1, 0, + 0, 1, + 0, 1, + 1, 0, + 1, 1, + + // top + 0, 0, + 1, 0, + 1, 1, + 0, 0, + 1, 1, + 0, 1, + + // top rung front + 0, 0, + 1, 0, + 1, 1, + 0, 0, + 1, 1, + 0, 1, + + // under top rung + 0, 0, + 0, 1, + 1, 1, + 0, 0, + 1, 1, + 1, 0, + + // between top rung and middle + 0, 0, + 1, 1, + 0, 1, + 0, 0, + 1, 0, + 1, 1, + + // top of middle rung + 0, 0, + 1, 1, + 0, 1, + 0, 0, + 1, 0, + 1, 1, + + // front of middle rung + 0, 0, + 1, 1, + 0, 1, + 0, 0, + 1, 0, + 1, 1, + + // bottom of middle rung. + 0, 0, + 0, 1, + 1, 1, + 0, 0, + 1, 1, + 1, 0, + + // front of bottom + 0, 0, + 1, 1, + 0, 1, + 0, 0, + 1, 0, + 1, 1, + + // bottom + 0, 0, + 0, 1, + 1, 1, + 0, 0, + 1, 1, + 1, 0, + + // left side + 0, 0, + 0, 1, + 1, 1, + 0, 0, + 1, 1, + 1, 0, + ]; + + const normals = expandRLEData([ + // left column front + // top rung front + // middle rung front + 18, 0, 0, 1, + + // left column back + // top rung back + // middle rung back + 18, 0, 0, -1, + + // top + 6, 0, 1, 0, + + // top rung front + 6, 1, 0, 0, + + // under top rung + 6, 0, -1, 0, + + // between top rung and middle + 6, 1, 0, 0, + + // top of middle rung + 6, 0, 1, 0, + + // front of middle rung + 6, 1, 0, 0, + + // bottom of middle rung. + 6, 0, -1, 0, + + // front of bottom + 6, 1, 0, 0, + + // bottom + 6, 0, -1, 0, + + // left side + 6, -1, 0, 0, + ]); + + const colors = expandRLEData([ + // left column front + // top rung front + // middle rung front + 18, 200, 70, 120, + + // left column back + // top rung back + // middle rung back + 18, 80, 70, 200, + + // top + 6, 70, 200, 210, + + // top rung front + 6, 200, 200, 70, + + // under top rung + 6, 210, 100, 70, + + // between top rung and middle + 6, 210, 160, 70, + + // top of middle rung + 6, 70, 180, 210, + + // front of middle rung + 6, 100, 70, 210, + + // bottom of middle rung. + 6, 76, 210, 100, + + // front of bottom + 6, 140, 210, 80, + + // bottom + 6, 90, 130, 110, + + // left side + 6, 160, 160, 220, + ], [255]); + + const numVerts = positions.length / 3; + + const arrays = { + position: createAugmentedTypedArray(3, numVerts), + texcoord: createAugmentedTypedArray(2, numVerts), + normal: createAugmentedTypedArray(3, numVerts), + color: createAugmentedTypedArray(4, numVerts, Uint8Array), + indices: createAugmentedTypedArray(3, numVerts / 3, Uint16Array), + }; + + arrays.position.push(positions); + arrays.texcoord.push(texcoords); + arrays.normal.push(normals); + arrays.color.push(colors); + + for (let ii = 0; ii < numVerts; ++ii) { + arrays.indices.push(ii); + } + + return arrays; +} + +/** + * Creates crescent BufferInfo. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} verticalRadius The vertical radius of the crescent. + * @param {number} outerRadius The outer radius of the crescent. + * @param {number} innerRadius The inner radius of the crescent. + * @param {number} thickness The thickness of the crescent. + * @param {number} subdivisionsDown number of steps around the crescent. + * @param {number} [startOffset] Where to start arc. Default 0. + * @param {number} [endOffset] Where to end arg. Default 1. + * @return {module:twgl.BufferInfo} The created BufferInfo. + * @memberOf module:twgl/primitives + * @function createCresentBufferInfo + */ + +/** + * Creates crescent buffers. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} verticalRadius The vertical radius of the crescent. + * @param {number} outerRadius The outer radius of the crescent. + * @param {number} innerRadius The inner radius of the crescent. + * @param {number} thickness The thickness of the crescent. + * @param {number} subdivisionsDown number of steps around the crescent. + * @param {number} [startOffset] Where to start arc. Default 0. + * @param {number} [endOffset] Where to end arg. Default 1. + * @return {Object.} The created buffers. + * @memberOf module:twgl/primitives + * @function createCresentBuffers + */ + +/** + * Creates crescent vertices. + * + * @param {number} verticalRadius The vertical radius of the crescent. + * @param {number} outerRadius The outer radius of the crescent. + * @param {number} innerRadius The inner radius of the crescent. + * @param {number} thickness The thickness of the crescent. + * @param {number} subdivisionsDown number of steps around the crescent. + * @param {number} [startOffset] Where to start arc. Default 0. + * @param {number} [endOffset] Where to end arg. Default 1. + * @return {Object.} The created vertices. + * @memberOf module:twgl/primitives + * @function createCresentBuffers + */ + +/** + * Creates crescent BufferInfo. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} verticalRadius The vertical radius of the crescent. + * @param {number} outerRadius The outer radius of the crescent. + * @param {number} innerRadius The inner radius of the crescent. + * @param {number} thickness The thickness of the crescent. + * @param {number} subdivisionsDown number of steps around the crescent. + * @param {number} [startOffset] Where to start arc. Default 0. + * @param {number} [endOffset] Where to end arg. Default 1. + * @return {module:twgl.BufferInfo} The created BufferInfo. + * @memberOf module:twgl/primitives + * @function createCrescentBufferInfo + */ + +/** + * Creates crescent buffers. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} verticalRadius The vertical radius of the crescent. + * @param {number} outerRadius The outer radius of the crescent. + * @param {number} innerRadius The inner radius of the crescent. + * @param {number} thickness The thickness of the crescent. + * @param {number} subdivisionsDown number of steps around the crescent. + * @param {number} [startOffset] Where to start arc. Default 0. + * @param {number} [endOffset] Where to end arg. Default 1. + * @return {Object.} The created buffers. + * @memberOf module:twgl/primitives + * @function createCrescentBuffers + */ + +/** + * Creates crescent vertices. + * + * @param {number} verticalRadius The vertical radius of the crescent. + * @param {number} outerRadius The outer radius of the crescent. + * @param {number} innerRadius The inner radius of the crescent. + * @param {number} thickness The thickness of the crescent. + * @param {number} subdivisionsDown number of steps around the crescent. + * @param {number} [startOffset] Where to start arc. Default 0. + * @param {number} [endOffset] Where to end arg. Default 1. + * @return {Object.} The created vertices. + * @memberOf module:twgl/primitives + */ + function createCrescentVertices( + verticalRadius, + outerRadius, + innerRadius, + thickness, + subdivisionsDown, + startOffset, + endOffset) { + if (subdivisionsDown <= 0) { + throw new Error('subdivisionDown must be > 0'); + } + + startOffset = startOffset || 0; + endOffset = endOffset || 1; + + const subdivisionsThick = 2; + + const offsetRange = endOffset - startOffset; + const numVertices = (subdivisionsDown + 1) * 2 * (2 + subdivisionsThick); + const positions = createAugmentedTypedArray(3, numVertices); + const normals = createAugmentedTypedArray(3, numVertices); + const texcoords = createAugmentedTypedArray(2, numVertices); + + function lerp(a, b, s) { + return a + (b - a) * s; + } + + function createArc(arcRadius, x, normalMult, normalAdd, uMult, uAdd) { + for (let z = 0; z <= subdivisionsDown; z++) { + const uBack = x / (subdivisionsThick - 1); + const v = z / subdivisionsDown; + const xBack = (uBack - 0.5) * 2; + const angle = (startOffset + (v * offsetRange)) * Math.PI; + const s = Math.sin(angle); + const c = Math.cos(angle); + const radius = lerp(verticalRadius, arcRadius, s); + const px = xBack * thickness; + const py = c * verticalRadius; + const pz = s * radius; + positions.push(px, py, pz); + const n = add(multiply$1([0, s, c], normalMult), normalAdd); + normals.push(n); + texcoords.push(uBack * uMult + uAdd, v); + } + } + + // Generate the individual vertices in our vertex buffer. + for (let x = 0; x < subdivisionsThick; x++) { + const uBack = (x / (subdivisionsThick - 1) - 0.5) * 2; + createArc(outerRadius, x, [1, 1, 1], [0, 0, 0], 1, 0); + createArc(outerRadius, x, [0, 0, 0], [uBack, 0, 0], 0, 0); + createArc(innerRadius, x, [1, 1, 1], [0, 0, 0], 1, 0); + createArc(innerRadius, x, [0, 0, 0], [uBack, 0, 0], 0, 1); + } + + // Do outer surface. + const indices = createAugmentedTypedArray(3, (subdivisionsDown * 2) * (2 + subdivisionsThick), Uint16Array); + + function createSurface(leftArcOffset, rightArcOffset) { + for (let z = 0; z < subdivisionsDown; ++z) { + // Make triangle 1 of quad. + indices.push( + leftArcOffset + z + 0, + leftArcOffset + z + 1, + rightArcOffset + z + 0); + + // Make triangle 2 of quad. + indices.push( + leftArcOffset + z + 1, + rightArcOffset + z + 1, + rightArcOffset + z + 0); + } + } + + const numVerticesDown = subdivisionsDown + 1; + // front + createSurface(numVerticesDown * 0, numVerticesDown * 4); + // right + createSurface(numVerticesDown * 5, numVerticesDown * 7); + // back + createSurface(numVerticesDown * 6, numVerticesDown * 2); + // left + createSurface(numVerticesDown * 3, numVerticesDown * 1); + + return { + position: positions, + normal: normals, + texcoord: texcoords, + indices: indices, + }; +} + +/** + * Creates cylinder BufferInfo. The cylinder will be created around the origin + * along the y-axis. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} radius Radius of cylinder. + * @param {number} height Height of cylinder. + * @param {number} radialSubdivisions The number of subdivisions around the cylinder. + * @param {number} verticalSubdivisions The number of subdivisions down the cylinder. + * @param {boolean} [topCap] Create top cap. Default = true. + * @param {boolean} [bottomCap] Create bottom cap. Default = true. + * @return {module:twgl.BufferInfo} The created BufferInfo. + * @memberOf module:twgl/primitives + * @function createCylinderBufferInfo + */ + + /** + * Creates cylinder buffers. The cylinder will be created around the origin + * along the y-axis. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} radius Radius of cylinder. + * @param {number} height Height of cylinder. + * @param {number} radialSubdivisions The number of subdivisions around the cylinder. + * @param {number} verticalSubdivisions The number of subdivisions down the cylinder. + * @param {boolean} [topCap] Create top cap. Default = true. + * @param {boolean} [bottomCap] Create bottom cap. Default = true. + * @return {Object.} The created buffers. + * @memberOf module:twgl/primitives + * @function createCylinderBuffers + */ + + /** + * Creates cylinder vertices. The cylinder will be created around the origin + * along the y-axis. + * + * @param {number} radius Radius of cylinder. + * @param {number} height Height of cylinder. + * @param {number} radialSubdivisions The number of subdivisions around the cylinder. + * @param {number} verticalSubdivisions The number of subdivisions down the cylinder. + * @param {boolean} [topCap] Create top cap. Default = true. + * @param {boolean} [bottomCap] Create bottom cap. Default = true. + * @return {Object.} The created vertices. + * @memberOf module:twgl/primitives + */ +function createCylinderVertices( + radius, + height, + radialSubdivisions, + verticalSubdivisions, + topCap, + bottomCap) { + return createTruncatedConeVertices( + radius, + radius, + height, + radialSubdivisions, + verticalSubdivisions, + topCap, + bottomCap); +} + +/** + * Creates BufferInfo for a torus + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} radius radius of center of torus circle. + * @param {number} thickness radius of torus ring. + * @param {number} radialSubdivisions The number of subdivisions around the torus. + * @param {number} bodySubdivisions The number of subdivisions around the body torus. + * @param {boolean} [startAngle] start angle in radians. Default = 0. + * @param {boolean} [endAngle] end angle in radians. Default = Math.PI * 2. + * @return {module:twgl.BufferInfo} The created BufferInfo. + * @memberOf module:twgl/primitives + * @function createTorusBufferInfo + */ + +/** + * Creates buffers for a torus + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} radius radius of center of torus circle. + * @param {number} thickness radius of torus ring. + * @param {number} radialSubdivisions The number of subdivisions around the torus. + * @param {number} bodySubdivisions The number of subdivisions around the body torus. + * @param {boolean} [startAngle] start angle in radians. Default = 0. + * @param {boolean} [endAngle] end angle in radians. Default = Math.PI * 2. + * @return {Object.} The created buffers. + * @memberOf module:twgl/primitives + * @function createTorusBuffers + */ + +/** + * Creates vertices for a torus + * + * @param {number} radius radius of center of torus circle. + * @param {number} thickness radius of torus ring. + * @param {number} radialSubdivisions The number of subdivisions around the torus. + * @param {number} bodySubdivisions The number of subdivisions around the body torus. + * @param {boolean} [startAngle] start angle in radians. Default = 0. + * @param {boolean} [endAngle] end angle in radians. Default = Math.PI * 2. + * @return {Object.} The created vertices. + * @memberOf module:twgl/primitives + */ +function createTorusVertices( + radius, + thickness, + radialSubdivisions, + bodySubdivisions, + startAngle, + endAngle) { + if (radialSubdivisions < 3) { + throw new Error('radialSubdivisions must be 3 or greater'); + } + + if (bodySubdivisions < 3) { + throw new Error('verticalSubdivisions must be 3 or greater'); + } + + startAngle = startAngle || 0; + endAngle = endAngle || Math.PI * 2; + const range = endAngle - startAngle; + + const radialParts = radialSubdivisions + 1; + const bodyParts = bodySubdivisions + 1; + const numVertices = radialParts * bodyParts; + const positions = createAugmentedTypedArray(3, numVertices); + const normals = createAugmentedTypedArray(3, numVertices); + const texcoords = createAugmentedTypedArray(2, numVertices); + const indices = createAugmentedTypedArray(3, (radialSubdivisions) * (bodySubdivisions) * 2, Uint16Array); + + for (let slice = 0; slice < bodyParts; ++slice) { + const v = slice / bodySubdivisions; + const sliceAngle = v * Math.PI * 2; + const sliceSin = Math.sin(sliceAngle); + const ringRadius = radius + sliceSin * thickness; + const ny = Math.cos(sliceAngle); + const y = ny * thickness; + for (let ring = 0; ring < radialParts; ++ring) { + const u = ring / radialSubdivisions; + const ringAngle = startAngle + u * range; + const xSin = Math.sin(ringAngle); + const zCos = Math.cos(ringAngle); + const x = xSin * ringRadius; + const z = zCos * ringRadius; + const nx = xSin * sliceSin; + const nz = zCos * sliceSin; + positions.push(x, y, z); + normals.push(nx, ny, nz); + texcoords.push(u, 1 - v); + } + } + + for (let slice = 0; slice < bodySubdivisions; ++slice) { // eslint-disable-line + for (let ring = 0; ring < radialSubdivisions; ++ring) { // eslint-disable-line + const nextRingIndex = 1 + ring; + const nextSliceIndex = 1 + slice; + indices.push(radialParts * slice + ring, + radialParts * nextSliceIndex + ring, + radialParts * slice + nextRingIndex); + indices.push(radialParts * nextSliceIndex + ring, + radialParts * nextSliceIndex + nextRingIndex, + radialParts * slice + nextRingIndex); + } + } + + return { + position: positions, + normal: normals, + texcoord: texcoords, + indices: indices, + }; +} + + +/** + * Creates a disc BufferInfo. The disc will be in the xz plane, centered at + * the origin. When creating, at least 3 divisions, or pie + * pieces, need to be specified, otherwise the triangles making + * up the disc will be degenerate. You can also specify the + * number of radial pieces `stacks`. A value of 1 for + * stacks will give you a simple disc of pie pieces. If you + * want to create an annulus you can set `innerRadius` to a + * value > 0. Finally, `stackPower` allows you to have the widths + * increase or decrease as you move away from the center. This + * is particularly useful when using the disc as a ground plane + * with a fixed camera such that you don't need the resolution + * of small triangles near the perimeter. For example, a value + * of 2 will produce stacks whose outside radius increases with + * the square of the stack index. A value of 1 will give uniform + * stacks. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} radius Radius of the ground plane. + * @param {number} divisions Number of triangles in the ground plane (at least 3). + * @param {number} [stacks] Number of radial divisions (default=1). + * @param {number} [innerRadius] Default 0. + * @param {number} [stackPower] Power to raise stack size to for decreasing width. + * @return {module:twgl.BufferInfo} The created BufferInfo. + * @memberOf module:twgl/primitives + * @function createDiscBufferInfo + */ + +/** + * Creates disc buffers. The disc will be in the xz plane, centered at + * the origin. When creating, at least 3 divisions, or pie + * pieces, need to be specified, otherwise the triangles making + * up the disc will be degenerate. You can also specify the + * number of radial pieces `stacks`. A value of 1 for + * stacks will give you a simple disc of pie pieces. If you + * want to create an annulus you can set `innerRadius` to a + * value > 0. Finally, `stackPower` allows you to have the widths + * increase or decrease as you move away from the center. This + * is particularly useful when using the disc as a ground plane + * with a fixed camera such that you don't need the resolution + * of small triangles near the perimeter. For example, a value + * of 2 will produce stacks whose outside radius increases with + * the square of the stack index. A value of 1 will give uniform + * stacks. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext. + * @param {number} radius Radius of the ground plane. + * @param {number} divisions Number of triangles in the ground plane (at least 3). + * @param {number} [stacks] Number of radial divisions (default=1). + * @param {number} [innerRadius] Default 0. + * @param {number} [stackPower] Power to raise stack size to for decreasing width. + * @return {Object.} The created buffers. + * @memberOf module:twgl/primitives + * @function createDiscBuffers + */ + +/** + * Creates disc vertices. The disc will be in the xz plane, centered at + * the origin. When creating, at least 3 divisions, or pie + * pieces, need to be specified, otherwise the triangles making + * up the disc will be degenerate. You can also specify the + * number of radial pieces `stacks`. A value of 1 for + * stacks will give you a simple disc of pie pieces. If you + * want to create an annulus you can set `innerRadius` to a + * value > 0. Finally, `stackPower` allows you to have the widths + * increase or decrease as you move away from the center. This + * is particularly useful when using the disc as a ground plane + * with a fixed camera such that you don't need the resolution + * of small triangles near the perimeter. For example, a value + * of 2 will produce stacks whose outside radius increases with + * the square of the stack index. A value of 1 will give uniform + * stacks. + * + * @param {number} radius Radius of the ground plane. + * @param {number} divisions Number of triangles in the ground plane (at least 3). + * @param {number} [stacks] Number of radial divisions (default=1). + * @param {number} [innerRadius] Default 0. + * @param {number} [stackPower] Power to raise stack size to for decreasing width. + * @return {Object.} The created vertices. + * @memberOf module:twgl/primitives + */ +function createDiscVertices( + radius, + divisions, + stacks, + innerRadius, + stackPower) { + if (divisions < 3) { + throw new Error('divisions must be at least 3'); + } + + stacks = stacks ? stacks : 1; + stackPower = stackPower ? stackPower : 1; + innerRadius = innerRadius ? innerRadius : 0; + + // Note: We don't share the center vertex because that would + // mess up texture coordinates. + const numVertices = (divisions + 1) * (stacks + 1); + + const positions = createAugmentedTypedArray(3, numVertices); + const normals = createAugmentedTypedArray(3, numVertices); + const texcoords = createAugmentedTypedArray(2, numVertices); + const indices = createAugmentedTypedArray(3, stacks * divisions * 2, Uint16Array); + + let firstIndex = 0; + const radiusSpan = radius - innerRadius; + const pointsPerStack = divisions + 1; + + // Build the disk one stack at a time. + for (let stack = 0; stack <= stacks; ++stack) { + const stackRadius = innerRadius + radiusSpan * Math.pow(stack / stacks, stackPower); + + for (let i = 0; i <= divisions; ++i) { + const theta = 2.0 * Math.PI * i / divisions; + const x = stackRadius * Math.cos(theta); + const z = stackRadius * Math.sin(theta); + + positions.push(x, 0, z); + normals.push(0, 1, 0); + texcoords.push(1 - (i / divisions), stack / stacks); + if (stack > 0 && i !== divisions) { + // a, b, c and d are the indices of the vertices of a quad. unless + // the current stack is the one closest to the center, in which case + // the vertices a and b connect to the center vertex. + const a = firstIndex + (i + 1); + const b = firstIndex + i; + const c = firstIndex + i - pointsPerStack; + const d = firstIndex + (i + 1) - pointsPerStack; + + // Make a quad of the vertices a, b, c, d. + indices.push(a, b, c); + indices.push(a, c, d); + } + } + + firstIndex += divisions + 1; + } + + return { + position: positions, + normal: normals, + texcoord: texcoords, + indices: indices, + }; +} + +/** + * creates a random integer between 0 and range - 1 inclusive. + * @param {number} range + * @return {number} random value between 0 and range - 1 inclusive. + * @private + */ +function randInt(range) { + return Math.random() * range | 0; +} + +/** + * Used to supply random colors + * @callback RandomColorFunc + * @param {number} ndx index of triangle/quad if unindexed or index of vertex if indexed + * @param {number} channel 0 = red, 1 = green, 2 = blue, 3 = alpha + * @return {number} a number from 0 to 255 + * @memberOf module:twgl/primitives + */ + +/** + * @typedef {Object} RandomVerticesOptions + * @property {number} [vertsPerColor] Defaults to 3 for non-indexed vertices + * @property {module:twgl/primitives.RandomColorFunc} [rand] A function to generate random numbers + * @memberOf module:twgl/primitives + */ + +/** + * Creates an augmentedTypedArray of random vertex colors. + * If the vertices are indexed (have an indices array) then will + * just make random colors. Otherwise assumes they are triangles + * and makes one random color for every 3 vertices. + * @param {Object.} vertices Vertices as returned from one of the createXXXVertices functions. + * @param {module:twgl/primitives.RandomVerticesOptions} [options] options. + * @return {Object.} same vertices as passed in with `color` added. + * @memberOf module:twgl/primitives + */ +function makeRandomVertexColors(vertices, options) { + options = options || {}; + const numElements = vertices.position.numElements; + const vColors = createAugmentedTypedArray(4, numElements, Uint8Array); + const rand = options.rand || function(ndx, channel) { + return channel < 3 ? randInt(256) : 255; + }; + vertices.color = vColors; + if (vertices.indices) { + // just make random colors if index + for (let ii = 0; ii < numElements; ++ii) { + vColors.push(rand(ii, 0), rand(ii, 1), rand(ii, 2), rand(ii, 3)); + } + } else { + // make random colors per triangle + const numVertsPerColor = options.vertsPerColor || 3; + const numSets = numElements / numVertsPerColor; + for (let ii = 0; ii < numSets; ++ii) { // eslint-disable-line + const color = [rand(ii, 0), rand(ii, 1), rand(ii, 2), rand(ii, 3)]; + for (let jj = 0; jj < numVertsPerColor; ++jj) { + vColors.push(color); + } + } + } + return vertices; +} + +/** + * creates a function that calls fn to create vertices and then + * creates a buffers for them + * @private + */ +function createBufferFunc(fn) { + return function(gl) { + const arrays = fn.apply(this, Array.prototype.slice.call(arguments, 1)); + return createBuffersFromArrays(gl, arrays); + }; +} + +/** + * creates a function that calls fn to create vertices and then + * creates a bufferInfo object for them + * @private + */ +function createBufferInfoFunc(fn) { + return function(gl) { + const arrays = fn.apply(null, Array.prototype.slice.call(arguments, 1)); + return createBufferInfoFromArrays(gl, arrays); + }; +} + +const arraySpecPropertyNames = [ + "numComponents", + "size", + "type", + "normalize", + "stride", + "offset", + "attrib", + "name", + "attribName", +]; + +/** + * Copy elements from one array to another + * + * @param {Array|TypedArray} src source array + * @param {Array|TypedArray} dst dest array + * @param {number} dstNdx index in dest to copy src + * @param {number} [offset] offset to add to copied values + * @private + */ +function copyElements(src, dst, dstNdx, offset) { + offset = offset || 0; + const length = src.length; + for (let ii = 0; ii < length; ++ii) { + dst[dstNdx + ii] = src[ii] + offset; + } +} + +/** + * Creates an array of the same time + * + * @param {(number[]|ArrayBufferView|module:twgl.FullArraySpec)} srcArray array who's type to copy + * @param {number} length size of new array + * @return {(number[]|ArrayBufferView|module:twgl.FullArraySpec)} array with same type as srcArray + * @private + */ +function createArrayOfSameType(srcArray, length) { + const arraySrc = getArray(srcArray); + const newArray = new arraySrc.constructor(length); + let newArraySpec = newArray; + // If it appears to have been augmented make new one augmented + if (arraySrc.numComponents && arraySrc.numElements) { + augmentTypedArray(newArray, arraySrc.numComponents); + } + // If it was a full spec make new one a full spec + if (srcArray.data) { + newArraySpec = { + data: newArray, + }; + copyNamedProperties(arraySpecPropertyNames, srcArray, newArraySpec); + } + return newArraySpec; +} + +/** + * Concatenates sets of vertices + * + * Assumes the vertices match in composition. For example + * if one set of vertices has positions, normals, and indices + * all sets of vertices must have positions, normals, and indices + * and of the same type. + * + * Example: + * + * const cubeVertices = twgl.primitives.createCubeVertices(2); + * const sphereVertices = twgl.primitives.createSphereVertices(1, 10, 10); + * // move the sphere 2 units up + * twgl.primitives.reorientVertices( + * sphereVertices, twgl.m4.translation([0, 2, 0])); + * // merge the sphere with the cube + * const cubeSphereVertices = twgl.primitives.concatVertices( + * [cubeVertices, sphereVertices]); + * // turn them into WebGL buffers and attrib data + * const bufferInfo = twgl.createBufferInfoFromArrays(gl, cubeSphereVertices); + * + * @param {module:twgl.Arrays[]} arrays Array of arrays of vertices + * @return {module:twgl.Arrays} The concatenated vertices. + * @memberOf module:twgl/primitives + */ +function concatVertices(arrayOfArrays) { + const names = {}; + let baseName; + // get names of all arrays. + // and numElements for each set of vertices + for (let ii = 0; ii < arrayOfArrays.length; ++ii) { + const arrays = arrayOfArrays[ii]; + Object.keys(arrays).forEach(function(name) { // eslint-disable-line + if (!names[name]) { + names[name] = []; + } + if (!baseName && name !== 'indices') { + baseName = name; + } + const arrayInfo = arrays[name]; + const numComponents = getNumComponents(arrayInfo, name); + const array = getArray(arrayInfo); + const numElements = array.length / numComponents; + names[name].push(numElements); + }); + } + + // compute length of combined array + // and return one for reference + function getLengthOfCombinedArrays(name) { + let length = 0; + let arraySpec; + for (let ii = 0; ii < arrayOfArrays.length; ++ii) { + const arrays = arrayOfArrays[ii]; + const arrayInfo = arrays[name]; + const array = getArray(arrayInfo); + length += array.length; + if (!arraySpec || arrayInfo.data) { + arraySpec = arrayInfo; + } + } + return { + length: length, + spec: arraySpec, + }; + } + + function copyArraysToNewArray(name, base, newArray) { + let baseIndex = 0; + let offset = 0; + for (let ii = 0; ii < arrayOfArrays.length; ++ii) { + const arrays = arrayOfArrays[ii]; + const arrayInfo = arrays[name]; + const array = getArray(arrayInfo); + if (name === 'indices') { + copyElements(array, newArray, offset, baseIndex); + baseIndex += base[ii]; + } else { + copyElements(array, newArray, offset); + } + offset += array.length; + } + } + + const base = names[baseName]; + + const newArrays = {}; + Object.keys(names).forEach(function(name) { + const info = getLengthOfCombinedArrays(name); + const newArraySpec = createArrayOfSameType(info.spec, info.length); + copyArraysToNewArray(name, base, getArray(newArraySpec)); + newArrays[name] = newArraySpec; + }); + return newArrays; +} + +/** + * Creates a duplicate set of vertices + * + * This is useful for calling reorientVertices when you + * also want to keep the original available + * + * @param {module:twgl.Arrays} arrays of vertices + * @return {module:twgl.Arrays} The duplicated vertices. + * @memberOf module:twgl/primitives + */ +function duplicateVertices(arrays) { + const newArrays = {}; + Object.keys(arrays).forEach(function(name) { + const arraySpec = arrays[name]; + const srcArray = getArray(arraySpec); + const newArraySpec = createArrayOfSameType(arraySpec, srcArray.length); + copyElements(srcArray, getArray(newArraySpec), 0); + newArrays[name] = newArraySpec; + }); + return newArrays; +} + +const create3DFBufferInfo = createBufferInfoFunc(create3DFVertices); +const create3DFBuffers = createBufferFunc(create3DFVertices); +const createCubeBufferInfo = createBufferInfoFunc(createCubeVertices); +const createCubeBuffers = createBufferFunc(createCubeVertices); +const createPlaneBufferInfo = createBufferInfoFunc(createPlaneVertices); +const createPlaneBuffers = createBufferFunc(createPlaneVertices); +const createSphereBufferInfo = createBufferInfoFunc(createSphereVertices); +const createSphereBuffers = createBufferFunc(createSphereVertices); +const createTruncatedConeBufferInfo = createBufferInfoFunc(createTruncatedConeVertices); +const createTruncatedConeBuffers = createBufferFunc(createTruncatedConeVertices); +const createXYQuadBufferInfo = createBufferInfoFunc(createXYQuadVertices); +const createXYQuadBuffers = createBufferFunc(createXYQuadVertices); +const createCrescentBufferInfo = createBufferInfoFunc(createCrescentVertices); +const createCrescentBuffers = createBufferFunc(createCrescentVertices); +const createCylinderBufferInfo = createBufferInfoFunc(createCylinderVertices); +const createCylinderBuffers = createBufferFunc(createCylinderVertices); +const createTorusBufferInfo = createBufferInfoFunc(createTorusVertices); +const createTorusBuffers = createBufferFunc(createTorusVertices); +const createDiscBufferInfo = createBufferInfoFunc(createDiscVertices); +const createDiscBuffers = createBufferFunc(createDiscVertices); + +// these were mis-spelled until 4.12 +const createCresentBufferInfo = createCrescentBufferInfo; +const createCresentBuffers = createCrescentBuffers; +const createCresentVertices = createCrescentVertices; + +var primitives = /*#__PURE__*/Object.freeze({ + __proto__: null, + create3DFBufferInfo: create3DFBufferInfo, + create3DFBuffers: create3DFBuffers, + create3DFVertices: create3DFVertices, + createAugmentedTypedArray: createAugmentedTypedArray, + createCubeBufferInfo: createCubeBufferInfo, + createCubeBuffers: createCubeBuffers, + createCubeVertices: createCubeVertices, + createPlaneBufferInfo: createPlaneBufferInfo, + createPlaneBuffers: createPlaneBuffers, + createPlaneVertices: createPlaneVertices, + createSphereBufferInfo: createSphereBufferInfo, + createSphereBuffers: createSphereBuffers, + createSphereVertices: createSphereVertices, + createTruncatedConeBufferInfo: createTruncatedConeBufferInfo, + createTruncatedConeBuffers: createTruncatedConeBuffers, + createTruncatedConeVertices: createTruncatedConeVertices, + createXYQuadBufferInfo: createXYQuadBufferInfo, + createXYQuadBuffers: createXYQuadBuffers, + createXYQuadVertices: createXYQuadVertices, + createCresentBufferInfo: createCresentBufferInfo, + createCresentBuffers: createCresentBuffers, + createCresentVertices: createCresentVertices, + createCrescentBufferInfo: createCrescentBufferInfo, + createCrescentBuffers: createCrescentBuffers, + createCrescentVertices: createCrescentVertices, + createCylinderBufferInfo: createCylinderBufferInfo, + createCylinderBuffers: createCylinderBuffers, + createCylinderVertices: createCylinderVertices, + createTorusBufferInfo: createTorusBufferInfo, + createTorusBuffers: createTorusBuffers, + createTorusVertices: createTorusVertices, + createDiscBufferInfo: createDiscBufferInfo, + createDiscBuffers: createDiscBuffers, + createDiscVertices: createDiscVertices, + deindexVertices: deindexVertices, + flattenNormals: flattenNormals, + makeRandomVertexColors: makeRandomVertexColors, + reorientDirections: reorientDirections, + reorientNormals: reorientNormals, + reorientPositions: reorientPositions, + reorientVertices: reorientVertices, + concatVertices: concatVertices, + duplicateVertices: duplicateVertices +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/** + * Gets the gl version as a number + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @return {number} version of gl + * @private + */ +//function getVersionAsNumber(gl) { +// return parseFloat(gl.getParameter(gl.VERSION).substr(6)); +//} + +/** + * Check if context is WebGL 2.0 + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @return {bool} true if it's WebGL 2.0 + * @memberOf module:twgl + */ +function isWebGL2(gl) { + // This is the correct check but it's slow + // return gl.getParameter(gl.VERSION).indexOf("WebGL 2.0") === 0; + // This might also be the correct check but I'm assuming it's slow-ish + // return gl instanceof WebGL2RenderingContext; + return !!gl.texStorage2D; +} + +/** + * Check if context is WebGL 1.0 + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @return {bool} true if it's WebGL 1.0 + * @memberOf module:twgl + */ +function isWebGL1(gl) { + // This is the correct check but it's slow + // const version = getVersionAsNumber(gl); + // return version <= 1.0 && version > 0.0; // because as of 2016/5 Edge returns 0.96 + // This might also be the correct check but I'm assuming it's slow-ish + // return gl instanceof WebGLRenderingContext; + return !gl.texStorage2D; +} + +/** + * Gets a string for WebGL enum + * + * Note: Several enums are the same. Without more + * context (which function) it's impossible to always + * give the correct enum. As it is, for matching values + * it gives all enums. Checking the WebGL2RenderingContext + * that means + * + * 0 = ZERO | POINT | NONE | NO_ERROR + * 1 = ONE | LINES | SYNC_FLUSH_COMMANDS_BIT + * 32777 = BLEND_EQUATION_RGB | BLEND_EQUATION_RGB + * 36662 = COPY_READ_BUFFER | COPY_READ_BUFFER_BINDING + * 36663 = COPY_WRITE_BUFFER | COPY_WRITE_BUFFER_BINDING + * 36006 = FRAMEBUFFER_BINDING | DRAW_FRAMEBUFFER_BINDING + * + * It's also not useful for bits really unless you pass in individual bits. + * In other words + * + * const bits = gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT; + * twgl.glEnumToString(gl, bits); // not going to work + * + * Note that some enums only exist on extensions. If you + * want them to show up you need to pass the extension at least + * once. For example + * + * const ext = gl.getExtension('WEBGL_compressed_texture_s3tc'); + * if (ext) { + * twgl.glEnumToString(ext, 0); // just prime the function + * + * ..later.. + * + * const internalFormat = ext.COMPRESSED_RGB_S3TC_DXT1_EXT; + * console.log(twgl.glEnumToString(gl, internalFormat)); + * + * Notice I didn't have to pass the extension the second time. This means + * you can have place that generically gets an enum for texture formats for example. + * and as long as you primed the function with the extensions + * + * If you're using `twgl.addExtensionsToContext` to enable your extensions + * then twgl will automatically get the extension's enums. + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext or any extension object + * @param {number} value the value of the enum you want to look up. + * @return {string} enum string or hex value + * @memberOf module:twgl + * @function glEnumToString + */ +const glEnumToString = (function() { + const haveEnumsForType = {}; + const enums = {}; + + function addEnums(gl) { + const type = gl.constructor.name; + if (!haveEnumsForType[type]) { + for (const key in gl) { + if (typeof gl[key] === 'number') { + const existing = enums[gl[key]]; + enums[gl[key]] = existing ? `${existing} | ${key}` : key; + } + } + haveEnumsForType[type] = true; + } + } + + return function glEnumToString(gl, value) { + addEnums(gl); + return enums[value] || (typeof value === 'number' ? `0x${value.toString(16)}` : value); + }; +}()); + +var utils = /*#__PURE__*/Object.freeze({ + __proto__: null, + glEnumToString: glEnumToString, + isWebGL1: isWebGL1, + isWebGL2: isWebGL2 +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +const defaults$1 = { + textureColor: new Uint8Array([128, 192, 255, 255]), + textureOptions: {}, + crossOrigin: undefined, +}; +const isArrayBuffer = isArrayBuffer$1; + +// Should we make this on demand? +const getShared2DContext = function() { + let s_ctx; + return function getShared2DContext() { + s_ctx = s_ctx || + ((typeof document !== 'undefined' && document.createElement) + ? document.createElement("canvas").getContext("2d") + : null); + return s_ctx; + }; +}(); + +// NOTE: Chrome supports 2D canvas in a Worker (behind flag as of v64 but +// not only does Firefox NOT support it but Firefox freezes immediately +// if you try to create one instead of just returning null and continuing. +// : (global.OffscreenCanvas && (new global.OffscreenCanvas(1, 1)).getContext("2d")); // OffscreenCanvas may not support 2d + +// NOTE: We can maybe remove some of the need for the 2d canvas. In WebGL2 +// we can use the various unpack settings. Otherwise we could try using +// the ability of an ImageBitmap to be cut. Unfortunately cutting an ImageBitmap +// is async and the current TWGL code expects a non-Async result though that +// might not be a problem. ImageBitmap though is not available in Edge or Safari +// as of 2018-01-02 + +/* PixelFormat */ +const ALPHA = 0x1906; +const RGB = 0x1907; +const RGBA$1 = 0x1908; +const LUMINANCE = 0x1909; +const LUMINANCE_ALPHA = 0x190A; +const DEPTH_COMPONENT$1 = 0x1902; +const DEPTH_STENCIL$1 = 0x84F9; + +/* TextureWrapMode */ +// const REPEAT = 0x2901; +// const MIRRORED_REPEAT = 0x8370; +const CLAMP_TO_EDGE$1 = 0x812f; + +/* TextureMagFilter */ +const NEAREST = 0x2600; +const LINEAR$1 = 0x2601; + +/* TextureMinFilter */ +// const NEAREST_MIPMAP_NEAREST = 0x2700; +// const LINEAR_MIPMAP_NEAREST = 0x2701; +// const NEAREST_MIPMAP_LINEAR = 0x2702; +// const LINEAR_MIPMAP_LINEAR = 0x2703; + +/* Texture Target */ +const TEXTURE_2D$2 = 0x0de1; +const TEXTURE_CUBE_MAP$1 = 0x8513; +const TEXTURE_3D$1 = 0x806f; +const TEXTURE_2D_ARRAY$1 = 0x8c1a; + +/* Cubemap Targets */ +const TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; +const TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; +const TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; +const TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; +const TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; +const TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851a; + +/* Texture Parameters */ +const TEXTURE_MIN_FILTER = 0x2801; +const TEXTURE_MAG_FILTER = 0x2800; +const TEXTURE_WRAP_S = 0x2802; +const TEXTURE_WRAP_T = 0x2803; +const TEXTURE_WRAP_R = 0x8072; +const TEXTURE_MIN_LOD = 0x813a; +const TEXTURE_MAX_LOD = 0x813b; +const TEXTURE_BASE_LEVEL = 0x813c; +const TEXTURE_MAX_LEVEL = 0x813d; +const TEXTURE_COMPARE_MODE = 0x884C; +const TEXTURE_COMPARE_FUNC = 0x884D; + +/* Pixel store */ +const UNPACK_ALIGNMENT = 0x0cf5; +const UNPACK_ROW_LENGTH = 0x0cf2; +const UNPACK_IMAGE_HEIGHT = 0x806e; +const UNPACK_SKIP_PIXELS = 0x0cf4; +const UNPACK_SKIP_ROWS = 0x0cf3; +const UNPACK_SKIP_IMAGES = 0x806d; +const UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243; +const UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241; +const UNPACK_FLIP_Y_WEBGL = 0x9240; + +const R8 = 0x8229; +const R8_SNORM = 0x8F94; +const R16F = 0x822D; +const R32F = 0x822E; +const R8UI = 0x8232; +const R8I = 0x8231; +const RG16UI = 0x823A; +const RG16I = 0x8239; +const RG32UI = 0x823C; +const RG32I = 0x823B; +const RG8 = 0x822B; +const RG8_SNORM = 0x8F95; +const RG16F = 0x822F; +const RG32F = 0x8230; +const RG8UI = 0x8238; +const RG8I = 0x8237; +const R16UI = 0x8234; +const R16I = 0x8233; +const R32UI = 0x8236; +const R32I = 0x8235; +const RGB8 = 0x8051; +const SRGB8 = 0x8C41; +const RGB565$1 = 0x8D62; +const RGB8_SNORM = 0x8F96; +const R11F_G11F_B10F = 0x8C3A; +const RGB9_E5 = 0x8C3D; +const RGB16F = 0x881B; +const RGB32F = 0x8815; +const RGB8UI = 0x8D7D; +const RGB8I = 0x8D8F; +const RGB16UI = 0x8D77; +const RGB16I = 0x8D89; +const RGB32UI = 0x8D71; +const RGB32I = 0x8D83; +const RGBA8 = 0x8058; +const SRGB8_ALPHA8 = 0x8C43; +const RGBA8_SNORM = 0x8F97; +const RGB5_A1$1 = 0x8057; +const RGBA4$1 = 0x8056; +const RGB10_A2 = 0x8059; +const RGBA16F = 0x881A; +const RGBA32F = 0x8814; +const RGBA8UI = 0x8D7C; +const RGBA8I = 0x8D8E; +const RGB10_A2UI = 0x906F; +const RGBA16UI = 0x8D76; +const RGBA16I = 0x8D88; +const RGBA32I = 0x8D82; +const RGBA32UI = 0x8D70; + +const DEPTH_COMPONENT16$1 = 0x81A5; +const DEPTH_COMPONENT24$1 = 0x81A6; +const DEPTH_COMPONENT32F$1 = 0x8CAC; +const DEPTH32F_STENCIL8$1 = 0x8CAD; +const DEPTH24_STENCIL8$1 = 0x88F0; + +/* DataType */ +const BYTE = 0x1400; +const UNSIGNED_BYTE$1 = 0x1401; +const SHORT = 0x1402; +const UNSIGNED_SHORT$1 = 0x1403; +const INT$1 = 0x1404; +const UNSIGNED_INT$1 = 0x1405; +const FLOAT$1 = 0x1406; +const UNSIGNED_SHORT_4_4_4_4 = 0x8033; +const UNSIGNED_SHORT_5_5_5_1 = 0x8034; +const UNSIGNED_SHORT_5_6_5 = 0x8363; +const HALF_FLOAT = 0x140B; +const HALF_FLOAT_OES = 0x8D61; // Thanks Khronos for making this different >:( +const UNSIGNED_INT_2_10_10_10_REV = 0x8368; +const UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B; +const UNSIGNED_INT_5_9_9_9_REV = 0x8C3E; +const FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD; +const UNSIGNED_INT_24_8 = 0x84FA; + +const RG = 0x8227; +const RG_INTEGER = 0x8228; +const RED = 0x1903; +const RED_INTEGER = 0x8D94; +const RGB_INTEGER = 0x8D98; +const RGBA_INTEGER = 0x8D99; + +const formatInfo = {}; +{ + // NOTE: this is named `numColorComponents` vs `numComponents` so we can let Uglify mangle + // the name. + const f = formatInfo; + f[ALPHA] = { numColorComponents: 1, }; + f[LUMINANCE] = { numColorComponents: 1, }; + f[LUMINANCE_ALPHA] = { numColorComponents: 2, }; + f[RGB] = { numColorComponents: 3, }; + f[RGBA$1] = { numColorComponents: 4, }; + f[RED] = { numColorComponents: 1, }; + f[RED_INTEGER] = { numColorComponents: 1, }; + f[RG] = { numColorComponents: 2, }; + f[RG_INTEGER] = { numColorComponents: 2, }; + f[RGB] = { numColorComponents: 3, }; + f[RGB_INTEGER] = { numColorComponents: 3, }; + f[RGBA$1] = { numColorComponents: 4, }; + f[RGBA_INTEGER] = { numColorComponents: 4, }; + f[DEPTH_COMPONENT$1] = { numColorComponents: 1, }; + f[DEPTH_STENCIL$1] = { numColorComponents: 2, }; +} + +/** + * @typedef {Object} TextureFormatDetails + * @property {number} textureFormat format to pass texImage2D and similar functions. + * @property {boolean} colorRenderable true if you can render to this format of texture. + * @property {boolean} textureFilterable true if you can filter the texture, false if you can ony use `NEAREST`. + * @property {number[]} type Array of possible types you can pass to texImage2D and similar function + * @property {Object.} bytesPerElementMap A map of types to bytes per element + * @private + */ + +let s_textureInternalFormatInfo; +function getTextureInternalFormatInfo(internalFormat) { + if (!s_textureInternalFormatInfo) { + // NOTE: these properties need unique names so we can let Uglify mangle the name. + const t = {}; + // unsized formats + t[ALPHA] = { textureFormat: ALPHA, colorRenderable: true, textureFilterable: true, bytesPerElement: [1, 2, 2, 4], type: [UNSIGNED_BYTE$1, HALF_FLOAT, HALF_FLOAT_OES, FLOAT$1], }; + t[LUMINANCE] = { textureFormat: LUMINANCE, colorRenderable: true, textureFilterable: true, bytesPerElement: [1, 2, 2, 4], type: [UNSIGNED_BYTE$1, HALF_FLOAT, HALF_FLOAT_OES, FLOAT$1], }; + t[LUMINANCE_ALPHA] = { textureFormat: LUMINANCE_ALPHA, colorRenderable: true, textureFilterable: true, bytesPerElement: [2, 4, 4, 8], type: [UNSIGNED_BYTE$1, HALF_FLOAT, HALF_FLOAT_OES, FLOAT$1], }; + t[RGB] = { textureFormat: RGB, colorRenderable: true, textureFilterable: true, bytesPerElement: [3, 6, 6, 12, 2], type: [UNSIGNED_BYTE$1, HALF_FLOAT, HALF_FLOAT_OES, FLOAT$1, UNSIGNED_SHORT_5_6_5], }; + t[RGBA$1] = { textureFormat: RGBA$1, colorRenderable: true, textureFilterable: true, bytesPerElement: [4, 8, 8, 16, 2, 2], type: [UNSIGNED_BYTE$1, HALF_FLOAT, HALF_FLOAT_OES, FLOAT$1, UNSIGNED_SHORT_4_4_4_4, UNSIGNED_SHORT_5_5_5_1], }; + t[DEPTH_COMPONENT$1] = { textureFormat: DEPTH_COMPONENT$1, colorRenderable: true, textureFilterable: false, bytesPerElement: [2, 4], type: [UNSIGNED_INT$1, UNSIGNED_SHORT$1], }; + + // sized formats + t[R8] = { textureFormat: RED, colorRenderable: true, textureFilterable: true, bytesPerElement: [1], type: [UNSIGNED_BYTE$1], }; + t[R8_SNORM] = { textureFormat: RED, colorRenderable: false, textureFilterable: true, bytesPerElement: [1], type: [BYTE], }; + t[R16F] = { textureFormat: RED, colorRenderable: false, textureFilterable: true, bytesPerElement: [4, 2], type: [FLOAT$1, HALF_FLOAT], }; + t[R32F] = { textureFormat: RED, colorRenderable: false, textureFilterable: false, bytesPerElement: [4], type: [FLOAT$1], }; + t[R8UI] = { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [1], type: [UNSIGNED_BYTE$1], }; + t[R8I] = { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [1], type: [BYTE], }; + t[R16UI] = { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [UNSIGNED_SHORT$1], }; + t[R16I] = { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [SHORT], }; + t[R32UI] = { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_INT$1], }; + t[R32I] = { textureFormat: RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [INT$1], }; + t[RG8] = { textureFormat: RG, colorRenderable: true, textureFilterable: true, bytesPerElement: [2], type: [UNSIGNED_BYTE$1], }; + t[RG8_SNORM] = { textureFormat: RG, colorRenderable: false, textureFilterable: true, bytesPerElement: [2], type: [BYTE], }; + t[RG16F] = { textureFormat: RG, colorRenderable: false, textureFilterable: true, bytesPerElement: [8, 4], type: [FLOAT$1, HALF_FLOAT], }; + t[RG32F] = { textureFormat: RG, colorRenderable: false, textureFilterable: false, bytesPerElement: [8], type: [FLOAT$1], }; + t[RG8UI] = { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [UNSIGNED_BYTE$1], }; + t[RG8I] = { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [BYTE], }; + t[RG16UI] = { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_SHORT$1], }; + t[RG16I] = { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [SHORT], }; + t[RG32UI] = { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [UNSIGNED_INT$1], }; + t[RG32I] = { textureFormat: RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [INT$1], }; + t[RGB8] = { textureFormat: RGB, colorRenderable: true, textureFilterable: true, bytesPerElement: [3], type: [UNSIGNED_BYTE$1], }; + t[SRGB8] = { textureFormat: RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [3], type: [UNSIGNED_BYTE$1], }; + t[RGB565$1] = { textureFormat: RGB, colorRenderable: true, textureFilterable: true, bytesPerElement: [3, 2], type: [UNSIGNED_BYTE$1, UNSIGNED_SHORT_5_6_5], }; + t[RGB8_SNORM] = { textureFormat: RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [3], type: [BYTE], }; + t[R11F_G11F_B10F] = { textureFormat: RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [12, 6, 4], type: [FLOAT$1, HALF_FLOAT, UNSIGNED_INT_10F_11F_11F_REV], }; + t[RGB9_E5] = { textureFormat: RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [12, 6, 4], type: [FLOAT$1, HALF_FLOAT, UNSIGNED_INT_5_9_9_9_REV], }; + t[RGB16F] = { textureFormat: RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [12, 6], type: [FLOAT$1, HALF_FLOAT], }; + t[RGB32F] = { textureFormat: RGB, colorRenderable: false, textureFilterable: false, bytesPerElement: [12], type: [FLOAT$1], }; + t[RGB8UI] = { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [3], type: [UNSIGNED_BYTE$1], }; + t[RGB8I] = { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [3], type: [BYTE], }; + t[RGB16UI] = { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [6], type: [UNSIGNED_SHORT$1], }; + t[RGB16I] = { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [6], type: [SHORT], }; + t[RGB32UI] = { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [12], type: [UNSIGNED_INT$1], }; + t[RGB32I] = { textureFormat: RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [12], type: [INT$1], }; + t[RGBA8] = { textureFormat: RGBA$1, colorRenderable: true, textureFilterable: true, bytesPerElement: [4], type: [UNSIGNED_BYTE$1], }; + t[SRGB8_ALPHA8] = { textureFormat: RGBA$1, colorRenderable: true, textureFilterable: true, bytesPerElement: [4], type: [UNSIGNED_BYTE$1], }; + t[RGBA8_SNORM] = { textureFormat: RGBA$1, colorRenderable: false, textureFilterable: true, bytesPerElement: [4], type: [BYTE], }; + t[RGB5_A1$1] = { textureFormat: RGBA$1, colorRenderable: true, textureFilterable: true, bytesPerElement: [4, 2, 4], type: [UNSIGNED_BYTE$1, UNSIGNED_SHORT_5_5_5_1, UNSIGNED_INT_2_10_10_10_REV], }; + t[RGBA4$1] = { textureFormat: RGBA$1, colorRenderable: true, textureFilterable: true, bytesPerElement: [4, 2], type: [UNSIGNED_BYTE$1, UNSIGNED_SHORT_4_4_4_4], }; + t[RGB10_A2] = { textureFormat: RGBA$1, colorRenderable: true, textureFilterable: true, bytesPerElement: [4], type: [UNSIGNED_INT_2_10_10_10_REV], }; + t[RGBA16F] = { textureFormat: RGBA$1, colorRenderable: false, textureFilterable: true, bytesPerElement: [16, 8], type: [FLOAT$1, HALF_FLOAT], }; + t[RGBA32F] = { textureFormat: RGBA$1, colorRenderable: false, textureFilterable: false, bytesPerElement: [16], type: [FLOAT$1], }; + t[RGBA8UI] = { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_BYTE$1], }; + t[RGBA8I] = { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [BYTE], }; + t[RGB10_A2UI] = { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_INT_2_10_10_10_REV], }; + t[RGBA16UI] = { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [UNSIGNED_SHORT$1], }; + t[RGBA16I] = { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [SHORT], }; + t[RGBA32I] = { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [16], type: [INT$1], }; + t[RGBA32UI] = { textureFormat: RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [16], type: [UNSIGNED_INT$1], }; + // Sized Internal + t[DEPTH_COMPONENT16$1] = { textureFormat: DEPTH_COMPONENT$1, colorRenderable: true, textureFilterable: false, bytesPerElement: [2, 4], type: [UNSIGNED_SHORT$1, UNSIGNED_INT$1], }; + t[DEPTH_COMPONENT24$1] = { textureFormat: DEPTH_COMPONENT$1, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_INT$1], }; + t[DEPTH_COMPONENT32F$1] = { textureFormat: DEPTH_COMPONENT$1, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [FLOAT$1], }; + t[DEPTH24_STENCIL8$1] = { textureFormat: DEPTH_STENCIL$1, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [UNSIGNED_INT_24_8], }; + t[DEPTH32F_STENCIL8$1] = { textureFormat: DEPTH_STENCIL$1, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [FLOAT_32_UNSIGNED_INT_24_8_REV], }; + + Object.keys(t).forEach(function(internalFormat) { + const info = t[internalFormat]; + info.bytesPerElementMap = {}; + info.bytesPerElement.forEach(function(bytesPerElement, ndx) { + const type = info.type[ndx]; + info.bytesPerElementMap[type] = bytesPerElement; + }); + }); + s_textureInternalFormatInfo = t; + } + return s_textureInternalFormatInfo[internalFormat]; +} + +/** + * Gets the number of bytes per element for a given internalFormat / type + * @param {number} internalFormat The internalFormat parameter from texImage2D etc.. + * @param {number} type The type parameter for texImage2D etc.. + * @return {number} the number of bytes per element for the given internalFormat, type combo + * @memberOf module:twgl/textures + */ +function getBytesPerElementForInternalFormat(internalFormat, type) { + const info = getTextureInternalFormatInfo(internalFormat); + if (!info) { + throw "unknown internal format"; + } + const bytesPerElement = info.bytesPerElementMap[type]; + if (bytesPerElement === undefined) { + throw "unknown internal format"; + } + return bytesPerElement; +} + +/** + * Info related to a specific texture internalFormat as returned + * from {@link module:twgl/textures.getFormatAndTypeForInternalFormat}. + * + * @typedef {Object} TextureFormatInfo + * @property {number} format Format to pass to texImage2D and related functions + * @property {number} type Type to pass to texImage2D and related functions + * @memberOf module:twgl/textures + */ + +/** + * Gets the format and type for a given internalFormat + * + * @param {number} internalFormat The internal format + * @return {module:twgl/textures.TextureFormatInfo} the corresponding format and type, + * @memberOf module:twgl/textures + */ +function getFormatAndTypeForInternalFormat(internalFormat) { + const info = getTextureInternalFormatInfo(internalFormat); + if (!info) { + throw "unknown internal format"; + } + return { + format: info.textureFormat, + type: info.type[0], + }; +} + +/** + * Returns true if value is power of 2 + * @param {number} value number to check. + * @return true if value is power of 2 + * @private + */ +function isPowerOf2(value) { + return (value & (value - 1)) === 0; +} + +/** + * Gets whether or not we can generate mips for the given + * internal format. + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {number} width The width parameter from texImage2D etc.. + * @param {number} height The height parameter from texImage2D etc.. + * @param {number} internalFormat The internalFormat parameter from texImage2D etc.. + * @return {boolean} true if we can generate mips + * @memberOf module:twgl/textures + */ +function canGenerateMipmap(gl, width, height, internalFormat) { + if (!isWebGL2(gl)) { + return isPowerOf2(width) && isPowerOf2(height); + } + const info = getTextureInternalFormatInfo(internalFormat); + if (!info) { + throw "unknown internal format"; + } + return info.colorRenderable && info.textureFilterable; +} + +/** + * Gets whether or not we can generate mips for the given format + * @param {number} internalFormat The internalFormat parameter from texImage2D etc.. + * @return {boolean} true if we can generate mips + * @memberOf module:twgl/textures + */ +function canFilter(internalFormat) { + const info = getTextureInternalFormatInfo(internalFormat); + if (!info) { + throw "unknown internal format"; + } + return info.textureFilterable; +} + +/** + * Gets the number of components for a given image format. + * @param {number} format the format. + * @return {number} the number of components for the format. + * @memberOf module:twgl/textures + */ +function getNumComponentsForFormat(format) { + const info = formatInfo[format]; + if (!info) { + throw "unknown format: " + format; + } + return info.numColorComponents; +} + +/** + * Gets the texture type for a given array type. + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @return {number} the gl texture type + * @private + */ +function getTextureTypeForArrayType(gl, src, defaultType) { + if (isArrayBuffer(src)) { + return getGLTypeForTypedArray(src); + } + return defaultType || UNSIGNED_BYTE$1; +} + +function guessDimensions(gl, target, width, height, numElements) { + if (numElements % 1 !== 0) { + throw "can't guess dimensions"; + } + if (!width && !height) { + const size = Math.sqrt(numElements / (target === TEXTURE_CUBE_MAP$1 ? 6 : 1)); + if (size % 1 === 0) { + width = size; + height = size; + } else { + width = numElements; + height = 1; + } + } else if (!height) { + height = numElements / width; + if (height % 1) { + throw "can't guess dimensions"; + } + } else if (!width) { + width = numElements / height; + if (width % 1) { + throw "can't guess dimensions"; + } + } + return { + width: width, + height: height, + }; +} + +/** + * Sets the default texture color. + * + * The default texture color is used when loading textures from + * urls. Because the URL will be loaded async we'd like to be + * able to use the texture immediately. By putting a 1x1 pixel + * color in the texture we can start using the texture before + * the URL has loaded. + * + * @param {number[]} color Array of 4 values in the range 0 to 1 + * @deprecated see {@link module:twgl.setDefaults} + * @memberOf module:twgl/textures + */ +function setDefaultTextureColor(color) { + defaults$1.textureColor = new Uint8Array([color[0] * 255, color[1] * 255, color[2] * 255, color[3] * 255]); +} + +function setDefaults$1(newDefaults) { + copyExistingProperties(newDefaults, defaults$1); + if (newDefaults.textureColor) { + setDefaultTextureColor(newDefaults.textureColor); + } +} + +/** + * A function to generate the source for a texture. + * @callback TextureFunc + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @param {module:twgl.TextureOptions} options the texture options + * @return {*} Returns any of the things documented for `src` for {@link module:twgl.TextureOptions}. + * @memberOf module:twgl + */ + +/** + * Texture options passed to most texture functions. Each function will use whatever options + * are appropriate for its needs. This lets you pass the same options to all functions. + * + * Note: A `TexImageSource` is defined in the WebGL spec as a `HTMLImageElement`, `HTMLVideoElement`, + * `HTMLCanvasElement`, `ImageBitmap`, or `ImageData`. + * + * @typedef {Object} TextureOptions + * @property {number} [target] the type of texture `gl.TEXTURE_2D` or `gl.TEXTURE_CUBE_MAP`. Defaults to `gl.TEXTURE_2D`. + * @property {number} [level] the mip level to affect. Defaults to 0. Note, if set auto will be considered false unless explicitly set to true. + * @property {number} [width] the width of the texture. Only used if src is an array or typed array or null. + * @property {number} [height] the height of a texture. Only used if src is an array or typed array or null. + * @property {number} [depth] the depth of a texture. Only used if src is an array or type array or null and target is `TEXTURE_3D` . + * @property {number} [min] the min filter setting (eg. `gl.LINEAR`). Defaults to `gl.NEAREST_MIPMAP_LINEAR` + * or if texture is not a power of 2 on both dimensions then defaults to `gl.LINEAR`. + * @property {number} [mag] the mag filter setting (eg. `gl.LINEAR`). Defaults to `gl.LINEAR` + * @property {number} [minMag] both the min and mag filter settings. + * @property {number} [internalFormat] internal format for texture. Defaults to `gl.RGBA` + * @property {number} [format] format for texture. Defaults to `gl.RGBA`. + * @property {number} [type] type for texture. Defaults to `gl.UNSIGNED_BYTE` unless `src` is ArrayBufferView. If `src` + * is ArrayBufferView defaults to type that matches ArrayBufferView type. + * @property {number} [wrap] Texture wrapping for both S and T (and R if TEXTURE_3D or WebGLSampler). Defaults to `gl.REPEAT` for 2D unless src is WebGL1 and src not npot and `gl.CLAMP_TO_EDGE` for cube + * @property {number} [wrapS] Texture wrapping for S. Defaults to `gl.REPEAT` and `gl.CLAMP_TO_EDGE` for cube. If set takes precedence over `wrap`. + * @property {number} [wrapT] Texture wrapping for T. Defaults to `gl.REPEAT` and `gl.CLAMP_TO_EDGE` for cube. If set takes precedence over `wrap`. + * @property {number} [wrapR] Texture wrapping for R. Defaults to `gl.REPEAT` and `gl.CLAMP_TO_EDGE` for cube. If set takes precedence over `wrap`. + * @property {number} [minLod] TEXTURE_MIN_LOD setting + * @property {number} [maxLod] TEXTURE_MAX_LOD setting + * @property {number} [baseLevel] TEXTURE_BASE_LEVEL setting + * @property {number} [maxLevel] TEXTURE_MAX_LEVEL setting + * @property {number} [compareFunc] TEXTURE_COMPARE_FUNC setting + * @property {number} [compareMode] TEXTURE_COMPARE_MODE setting + * @property {number} [unpackAlignment] The `gl.UNPACK_ALIGNMENT` used when uploading an array. Defaults to 1. + * @property {number[]|ArrayBufferView} [color] Color to initialize this texture with if loading an image asynchronously. + * The default use a blue 1x1 pixel texture. You can set another default by calling `twgl.setDefaults` + * or you can set an individual texture's initial color by setting this property. Example: `[1, .5, .5, 1]` = pink + * @property {number} [premultiplyAlpha] Whether or not to premultiply alpha. Defaults to whatever the current setting is. + * This lets you set it once before calling `twgl.createTexture` or `twgl.createTextures` and only override + * the current setting for specific textures. + * @property {number} [flipY] Whether or not to flip the texture vertically on upload. Defaults to whatever the current setting is. + * This lets you set it once before calling `twgl.createTexture` or `twgl.createTextures` and only override + * the current setting for specific textures. + * @property {number} [colorspaceConversion] Whether or not to let the browser do colorspace conversion of the texture on upload. Defaults to whatever the current setting is. + * This lets you set it once before calling `twgl.createTexture` or `twgl.createTextures` and only override + * the current setting for specific textures. + * @property {boolean} [auto] If `undefined` or `true`, in WebGL1, texture filtering is set automatically for non-power of 2 images and + * mips are generated for power of 2 images. In WebGL2 mips are generated if they can be. Note: if `level` is set above + * then then `auto` is assumed to be `false` unless explicity set to `true`. + * @property {number[]} [cubeFaceOrder] The order that cube faces are pulled out of an img or set of images. The default is + * + * [gl.TEXTURE_CUBE_MAP_POSITIVE_X, + * gl.TEXTURE_CUBE_MAP_NEGATIVE_X, + * gl.TEXTURE_CUBE_MAP_POSITIVE_Y, + * gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, + * gl.TEXTURE_CUBE_MAP_POSITIVE_Z, + * gl.TEXTURE_CUBE_MAP_NEGATIVE_Z] + * + * @property {(number[]|ArrayBufferView|TexImageSource|TexImageSource[]|string|string[]|module:twgl.TextureFunc)} [src] source for texture + * + * If `string` then it's assumed to be a URL to an image. The image will be downloaded async. A usable + * 1x1 pixel texture will be returned immediately. The texture will be updated once the image has downloaded. + * If `target` is `gl.TEXTURE_CUBE_MAP` will attempt to divide image into 6 square pieces. 1x6, 6x1, 3x2, 2x3. + * The pieces will be uploaded in `cubeFaceOrder` + * + * If `string[]` or `TexImageSource[]` and target is `gl.TEXTURE_CUBE_MAP` then it must have 6 entries, one for each face of a cube map. + * + * If `string[]` or `TexImageSource[]` and target is `gl.TEXTURE_2D_ARRAY` then each entry is a slice of the a 2d array texture + * and will be scaled to the specified width and height OR to the size of the first image that loads. + * + * If `TexImageSource` then it wil be used immediately to create the contents of the texture. Examples `HTMLImageElement`, + * `HTMLCanvasElement`, `HTMLVideoElement`. + * + * If `number[]` or `ArrayBufferView` it's assumed to be data for a texture. If `width` or `height` is + * not specified it is guessed as follows. First the number of elements is computed by `src.length / numComponents` + * where `numComponents` is derived from `format`. If `target` is `gl.TEXTURE_CUBE_MAP` then `numElements` is divided + * by 6. Then + * + * * If neither `width` nor `height` are specified and `sqrt(numElements)` is an integer then width and height + * are set to `sqrt(numElements)`. Otherwise `width = numElements` and `height = 1`. + * + * * If only one of `width` or `height` is specified then the other equals `numElements / specifiedDimension`. + * + * If `number[]` will be converted to `type`. + * + * If `src` is a function it will be called with a `WebGLRenderingContext` and these options. + * Whatever it returns is subject to these rules. So it can return a string url, an `HTMLElement` + * an array etc... + * + * If `src` is undefined then an empty texture will be created of size `width` by `height`. + * + * @property {string} [crossOrigin] What to set the crossOrigin property of images when they are downloaded. + * default: undefined. Also see {@link module:twgl.setDefaults}. + * + * @memberOf module:twgl + */ + +/** + * Sets any packing state that will be set based on the options. + * @param {module:twgl.TextureOptions} options A TextureOptions object with whatever parameters you want set. + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @private + */ +function setPackState(gl, options) { + if (options.colorspaceConversion !== undefined) { + gl.pixelStorei(UNPACK_COLORSPACE_CONVERSION_WEBGL, options.colorspaceConversion); + } + if (options.premultiplyAlpha !== undefined) { + gl.pixelStorei(UNPACK_PREMULTIPLY_ALPHA_WEBGL, options.premultiplyAlpha); + } + if (options.flipY !== undefined) { + gl.pixelStorei(UNPACK_FLIP_Y_WEBGL, options.flipY); + } +} + +/** + * Set skip state to defaults + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @private + */ +function setSkipStateToDefault(gl) { + gl.pixelStorei(UNPACK_ALIGNMENT, 4); + if (isWebGL2(gl)) { + gl.pixelStorei(UNPACK_ROW_LENGTH, 0); + gl.pixelStorei(UNPACK_IMAGE_HEIGHT, 0); + gl.pixelStorei(UNPACK_SKIP_PIXELS, 0); + gl.pixelStorei(UNPACK_SKIP_ROWS, 0); + gl.pixelStorei(UNPACK_SKIP_IMAGES, 0); + } +} + +/** + * Sets the parameters of a texture or sampler + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {number|WebGLSampler} target texture target or sampler + * @param {function()} parameteriFn texParameteri or samplerParameteri fn + * @param {WebGLTexture} tex the WebGLTexture to set parameters for + * @param {module:twgl.TextureOptions} options A TextureOptions object with whatever parameters you want set. + * This is often the same options you passed in when you created the texture. + * @private + */ +function setTextureSamplerParameters(gl, target, parameteriFn, options) { + if (options.minMag) { + parameteriFn.call(gl, target, TEXTURE_MIN_FILTER, options.minMag); + parameteriFn.call(gl, target, TEXTURE_MAG_FILTER, options.minMag); + } + if (options.min) { + parameteriFn.call(gl, target, TEXTURE_MIN_FILTER, options.min); + } + if (options.mag) { + parameteriFn.call(gl, target, TEXTURE_MAG_FILTER, options.mag); + } + if (options.wrap) { + parameteriFn.call(gl, target, TEXTURE_WRAP_S, options.wrap); + parameteriFn.call(gl, target, TEXTURE_WRAP_T, options.wrap); + if (target === TEXTURE_3D$1 || isSampler(gl, target)) { + parameteriFn.call(gl, target, TEXTURE_WRAP_R, options.wrap); + } + } + if (options.wrapR) { + parameteriFn.call(gl, target, TEXTURE_WRAP_R, options.wrapR); + } + if (options.wrapS) { + parameteriFn.call(gl, target, TEXTURE_WRAP_S, options.wrapS); + } + if (options.wrapT) { + parameteriFn.call(gl, target, TEXTURE_WRAP_T, options.wrapT); + } + if (options.minLod !== undefined) { + parameteriFn.call(gl, target, TEXTURE_MIN_LOD, options.minLod); + } + if (options.maxLod !== undefined) { + parameteriFn.call(gl, target, TEXTURE_MAX_LOD, options.maxLod); + } + if (options.baseLevel !== undefined) { + parameteriFn.call(gl, target, TEXTURE_BASE_LEVEL, options.baseLevel); + } + if (options.maxLevel !== undefined) { + parameteriFn.call(gl, target, TEXTURE_MAX_LEVEL, options.maxLevel); + } + if (options.compareFunc !== undefined) { + parameteriFn.call(gl, target, TEXTURE_COMPARE_FUNC, options.compareFunc); + } + if (options.compareMode !== undefined) { + parameteriFn.call(gl, target, TEXTURE_COMPARE_MODE, options.compareMode); + } +} + +/** + * Sets the texture parameters of a texture. + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLTexture} tex the WebGLTexture to set parameters for + * @param {module:twgl.TextureOptions} options A TextureOptions object with whatever parameters you want set. + * This is often the same options you passed in when you created the texture. + * @memberOf module:twgl/textures + */ +function setTextureParameters(gl, tex, options) { + const target = options.target || TEXTURE_2D$2; + gl.bindTexture(target, tex); + setTextureSamplerParameters(gl, target, gl.texParameteri, options); +} + +/** + * Sets the sampler parameters of a sampler. + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLSampler} sampler the WebGLSampler to set parameters for + * @param {module:twgl.TextureOptions} options A TextureOptions object with whatever parameters you want set. + * @memberOf module:twgl/textures + */ +function setSamplerParameters(gl, sampler, options) { + setTextureSamplerParameters(gl, sampler, gl.samplerParameteri, options); +} + +/** + * Creates a new sampler object and sets parameters. + * + * Example: + * + * const sampler = twgl.createSampler(gl, { + * minMag: gl.NEAREST, // sets both TEXTURE_MIN_FILTER and TEXTURE_MAG_FILTER + * wrap: gl.CLAMP_TO_NEAREST, // sets both TEXTURE_WRAP_S and TEXTURE_WRAP_T and TEXTURE_WRAP_R + * }); + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {Object.} options A object of TextureOptions one per sampler. + * @return {Object.} the created samplers by name + * @private + */ +function createSampler(gl, options) { + const sampler = gl.createSampler(); + setSamplerParameters(gl, sampler, options); + return sampler; +} + +/** + * Creates a multiple sampler objects and sets parameters on each. + * + * Example: + * + * const samplers = twgl.createSamplers(gl, { + * nearest: { + * minMag: gl.NEAREST, + * }, + * nearestClampS: { + * minMag: gl.NEAREST, + * wrapS: gl.CLAMP_TO_NEAREST, + * }, + * linear: { + * minMag: gl.LINEAR, + * }, + * nearestClamp: { + * minMag: gl.NEAREST, + * wrap: gl.CLAMP_TO_EDGE, + * }, + * linearClamp: { + * minMag: gl.LINEAR, + * wrap: gl.CLAMP_TO_EDGE, + * }, + * linearClampT: { + * minMag: gl.LINEAR, + * wrapT: gl.CLAMP_TO_EDGE, + * }, + * }); + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {module:twgl.TextureOptions} [options] A TextureOptions object with whatever parameters you want set on the sampler + * @private + */ +function createSamplers(gl, samplerOptions) { + const samplers = {}; + Object.keys(samplerOptions).forEach(function(name) { + samplers[name] = createSampler(gl, samplerOptions[name]); + }); + return samplers; +} + +/** + * Makes a 1x1 pixel + * If no color is passed in uses the default color which can be set by calling `setDefaultTextureColor`. + * @param {(number[]|ArrayBufferView)} [color] The color using 0-1 values + * @return {Uint8Array} Unit8Array with color. + * @private + */ +function make1Pixel(color) { + color = color || defaults$1.textureColor; + if (isArrayBuffer(color)) { + return color; + } + return new Uint8Array([color[0] * 255, color[1] * 255, color[2] * 255, color[3] * 255]); +} + +/** + * Sets filtering or generates mips for texture based on width or height + * If width or height is not passed in uses `options.width` and//or `options.height` + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLTexture} tex the WebGLTexture to set parameters for + * @param {module:twgl.TextureOptions} [options] A TextureOptions object with whatever parameters you want set. + * This is often the same options you passed in when you created the texture. + * @param {number} [width] width of texture + * @param {number} [height] height of texture + * @param {number} [internalFormat] The internalFormat parameter from texImage2D etc.. + * @memberOf module:twgl/textures + */ +function setTextureFilteringForSize(gl, tex, options, width, height, internalFormat) { + options = options || defaults$1.textureOptions; + internalFormat = internalFormat || RGBA$1; + const target = options.target || TEXTURE_2D$2; + width = width || options.width; + height = height || options.height; + gl.bindTexture(target, tex); + if (canGenerateMipmap(gl, width, height, internalFormat)) { + gl.generateMipmap(target); + } else { + const filtering = canFilter(internalFormat) ? LINEAR$1 : NEAREST; + gl.texParameteri(target, TEXTURE_MIN_FILTER, filtering); + gl.texParameteri(target, TEXTURE_MAG_FILTER, filtering); + gl.texParameteri(target, TEXTURE_WRAP_S, CLAMP_TO_EDGE$1); + gl.texParameteri(target, TEXTURE_WRAP_T, CLAMP_TO_EDGE$1); + } +} + +function shouldAutomaticallySetTextureFilteringForSize(options) { + return options.auto === true || (options.auto === undefined && options.level === undefined); +} + +/** + * Gets an array of cubemap face enums + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {module:twgl.TextureOptions} options A TextureOptions object with whatever parameters you want set. + * This is often the same options you passed in when you created the texture. + * @return {number[]} cubemap face enums + * @private + */ +function getCubeFaceOrder(gl, options) { + options = options || {}; + return options.cubeFaceOrder || [ + TEXTURE_CUBE_MAP_POSITIVE_X, + TEXTURE_CUBE_MAP_NEGATIVE_X, + TEXTURE_CUBE_MAP_POSITIVE_Y, + TEXTURE_CUBE_MAP_NEGATIVE_Y, + TEXTURE_CUBE_MAP_POSITIVE_Z, + TEXTURE_CUBE_MAP_NEGATIVE_Z, + ]; +} + +/** + * @typedef {Object} FaceInfo + * @property {number} face gl enum for texImage2D + * @property {number} ndx face index (0 - 5) into source data + * @ignore + */ + +/** + * Gets an array of FaceInfos + * There's a bug in some NVidia drivers that will crash the driver if + * `gl.TEXTURE_CUBE_MAP_POSITIVE_X` is not uploaded first. So, we take + * the user's desired order from his faces to WebGL and make sure we + * do the faces in WebGL order + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {module:twgl.TextureOptions} options A TextureOptions object with whatever parameters you want set. + * @return {FaceInfo[]} cubemap face infos. Arguably the `face` property of each element is redundant but + * it's needed internally to sort the array of `ndx` properties by `face`. + * @private + */ +function getCubeFacesWithNdx(gl, options) { + const faces = getCubeFaceOrder(gl, options); + // work around bug in NVidia drivers. We have to upload the first face first else the driver crashes :( + const facesWithNdx = faces.map(function(face, ndx) { + return { face: face, ndx: ndx }; + }); + facesWithNdx.sort(function(a, b) { + return a.face - b.face; + }); + return facesWithNdx; +} + +/** + * Set a texture from the contents of an element. Will also set + * texture filtering or generate mips based on the dimensions of the element + * unless `options.auto === false`. If `target === gl.TEXTURE_CUBE_MAP` will + * attempt to slice image into 1x6, 2x3, 3x2, or 6x1 images, one for each face. + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLTexture} tex the WebGLTexture to set parameters for + * @param {HTMLElement} element a canvas, img, or video element. + * @param {module:twgl.TextureOptions} [options] A TextureOptions object with whatever parameters you want set. + * This is often the same options you passed in when you created the texture. + * @memberOf module:twgl/textures + * @kind function + */ +function setTextureFromElement(gl, tex, element, options) { + options = options || defaults$1.textureOptions; + const target = options.target || TEXTURE_2D$2; + const level = options.level || 0; + let width = element.width; + let height = element.height; + const internalFormat = options.internalFormat || options.format || RGBA$1; + const formatType = getFormatAndTypeForInternalFormat(internalFormat); + const format = options.format || formatType.format; + const type = options.type || formatType.type; + setPackState(gl, options); + gl.bindTexture(target, tex); + if (target === TEXTURE_CUBE_MAP$1) { + // guess the parts + const imgWidth = element.width; + const imgHeight = element.height; + let size; + let slices; + if (imgWidth / 6 === imgHeight) { + // It's 6x1 + size = imgHeight; + slices = [0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0]; + } else if (imgHeight / 6 === imgWidth) { + // It's 1x6 + size = imgWidth; + slices = [0, 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5]; + } else if (imgWidth / 3 === imgHeight / 2) { + // It's 3x2 + size = imgWidth / 3; + slices = [0, 0, 1, 0, 2, 0, 0, 1, 1, 1, 2, 1]; + } else if (imgWidth / 2 === imgHeight / 3) { + // It's 2x3 + size = imgWidth / 2; + slices = [0, 0, 1, 0, 0, 1, 1, 1, 0, 2, 1, 2]; + } else { + throw "can't figure out cube map from element: " + (element.src ? element.src : element.nodeName); + } + const ctx = getShared2DContext(); + if (ctx) { + ctx.canvas.width = size; + ctx.canvas.height = size; + width = size; + height = size; + getCubeFacesWithNdx(gl, options).forEach(function(f) { + const xOffset = slices[f.ndx * 2 + 0] * size; + const yOffset = slices[f.ndx * 2 + 1] * size; + ctx.drawImage(element, xOffset, yOffset, size, size, 0, 0, size, size); + gl.texImage2D(f.face, level, internalFormat, format, type, ctx.canvas); + }); + // Free up the canvas memory + ctx.canvas.width = 1; + ctx.canvas.height = 1; + } else if (typeof createImageBitmap !== 'undefined') { + // NOTE: It seems like we should prefer ImageBitmap because unlike canvas it's + // note lossy? (alpha is not premultiplied? although I'm not sure what + width = size; + height = size; + getCubeFacesWithNdx(gl, options).forEach(function(f) { + const xOffset = slices[f.ndx * 2 + 0] * size; + const yOffset = slices[f.ndx * 2 + 1] * size; + // We can't easily use a default texture color here as it would have to match + // the type across all faces where as with a 2D one there's only one face + // so we're replacing everything all at once. It also has to be the correct size. + // On the other hand we need all faces to be the same size so as one face loads + // the rest match else the texture will be un-renderable. + gl.texImage2D(f.face, level, internalFormat, size, size, 0, format, type, null); + createImageBitmap(element, xOffset, yOffset, size, size, { + premultiplyAlpha: 'none', + colorSpaceConversion: 'none', + }) + .then(function(imageBitmap) { + setPackState(gl, options); + gl.bindTexture(target, tex); + gl.texImage2D(f.face, level, internalFormat, format, type, imageBitmap); + if (shouldAutomaticallySetTextureFilteringForSize(options)) { + setTextureFilteringForSize(gl, tex, options, width, height, internalFormat); + } + }); + }); + } + } else if (target === TEXTURE_3D$1 || target === TEXTURE_2D_ARRAY$1) { + const smallest = Math.min(element.width, element.height); + const largest = Math.max(element.width, element.height); + const depth = largest / smallest; + if (depth % 1 !== 0) { + throw "can not compute 3D dimensions of element"; + } + const xMult = element.width === largest ? 1 : 0; + const yMult = element.height === largest ? 1 : 0; + gl.pixelStorei(UNPACK_ALIGNMENT, 1); + gl.pixelStorei(UNPACK_ROW_LENGTH, element.width); + gl.pixelStorei(UNPACK_IMAGE_HEIGHT, 0); + gl.pixelStorei(UNPACK_SKIP_IMAGES, 0); + gl.texImage3D(target, level, internalFormat, smallest, smallest, smallest, 0, format, type, null); + for (let d = 0; d < depth; ++d) { + const srcX = d * smallest * xMult; + const srcY = d * smallest * yMult; + gl.pixelStorei(UNPACK_SKIP_PIXELS, srcX); + gl.pixelStorei(UNPACK_SKIP_ROWS, srcY); + gl.texSubImage3D(target, level, 0, 0, d, smallest, smallest, 1, format, type, element); + } + setSkipStateToDefault(gl); + } else { + gl.texImage2D(target, level, internalFormat, format, type, element); + } + if (shouldAutomaticallySetTextureFilteringForSize(options)) { + setTextureFilteringForSize(gl, tex, options, width, height, internalFormat); + } + setTextureParameters(gl, tex, options); +} + +function noop() { +} + +/** + * Checks whether the url's origin is the same so that we can set the `crossOrigin` + * @param {string} url url to image + * @returns {boolean} true if the window's origin is the same as image's url + * @private + */ +function urlIsSameOrigin(url) { + if (typeof document !== 'undefined') { + // for IE really + const a = document.createElement('a'); + a.href = url; + return a.hostname === location.hostname && + a.port === location.port && + a.protocol === location.protocol; + } else { + const localOrigin = (new URL(location.href)).origin; + const urlOrigin = (new URL(url, location.href)).origin; + return urlOrigin === localOrigin; + } +} + +function setToAnonymousIfUndefinedAndURLIsNotSameOrigin(url, crossOrigin) { + return crossOrigin === undefined && !urlIsSameOrigin(url) + ? 'anonymous' + : crossOrigin; +} + +/** + * Loads an image + * @param {string} url url to image + * @param {string} crossOrigin + * @param {function(err, img)} [callback] a callback that's passed an error and the image. The error will be non-null + * if there was an error + * @return {HTMLImageElement} the image being loaded. + * @private + */ +function loadImage(url, crossOrigin, callback) { + callback = callback || noop; + let img; + crossOrigin = crossOrigin !== undefined ? crossOrigin : defaults$1.crossOrigin; + crossOrigin = setToAnonymousIfUndefinedAndURLIsNotSameOrigin(url, crossOrigin); + if (typeof Image !== 'undefined') { + img = new Image(); + if (crossOrigin !== undefined) { + img.crossOrigin = crossOrigin; + } + + const clearEventHandlers = function clearEventHandlers() { + img.removeEventListener('error', onError); // eslint-disable-line + img.removeEventListener('load', onLoad); // eslint-disable-line + img = null; + }; + + const onError = function onError() { + const msg = "couldn't load image: " + url; + error$1(msg); + callback(msg, img); + clearEventHandlers(); + }; + + const onLoad = function onLoad() { + callback(null, img); + clearEventHandlers(); + }; + + img.addEventListener('error', onError); + img.addEventListener('load', onLoad); + img.src = url; + return img; + } else if (typeof ImageBitmap !== 'undefined') { + let err; + let bm; + const cb = function cb() { + callback(err, bm); + }; + + const options = {}; + if (crossOrigin) { + options.mode = 'cors'; // TODO: not sure how to translate image.crossOrigin + } + fetch(url, options).then(function(response) { + if (!response.ok) { + throw response; + } + return response.blob(); + }).then(function(blob) { + return createImageBitmap(blob, { + premultiplyAlpha: 'none', + colorSpaceConversion: 'none', + }); + }).then(function(bitmap) { + // not sure if this works. We don't want + // to catch the user's error. So, call + // the callback in a timeout so we're + // not in this scope inside the promise. + bm = bitmap; + setTimeout(cb); + }).catch(function(e) { + err = e; + setTimeout(cb); + }); + img = null; + } + return img; +} + +/** + * check if object is a TexImageSource + * + * @param {Object} obj Object to test + * @return {boolean} true if object is a TexImageSource + * @private + */ +function isTexImageSource(obj) { + return (typeof ImageBitmap !== 'undefined' && obj instanceof ImageBitmap) || + (typeof ImageData !== 'undefined' && obj instanceof ImageData) || + (typeof HTMLElement !== 'undefined' && obj instanceof HTMLElement); +} + +/** + * if obj is an TexImageSource then just + * uses it otherwise if obj is a string + * then load it first. + * + * @param {string|TexImageSource} obj + * @param {string} crossOrigin + * @param {function(err, img)} [callback] a callback that's passed an error and the image. The error will be non-null + * if there was an error + * @private + */ +function loadAndUseImage(obj, crossOrigin, callback) { + if (isTexImageSource(obj)) { + setTimeout(function() { + callback(null, obj); + }); + return obj; + } + + return loadImage(obj, crossOrigin, callback); +} + +/** + * Sets a texture to a 1x1 pixel color. If `options.color === false` is nothing happens. If it's not set + * the default texture color is used which can be set by calling `setDefaultTextureColor`. + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLTexture} tex the WebGLTexture to set parameters for + * @param {module:twgl.TextureOptions} [options] A TextureOptions object with whatever parameters you want set. + * This is often the same options you passed in when you created the texture. + * @memberOf module:twgl/textures + * @private + */ +function setTextureTo1PixelColor(gl, tex, options) { + options = options || defaults$1.textureOptions; + const target = options.target || TEXTURE_2D$2; + gl.bindTexture(target, tex); + if (options.color === false) { + return; + } + // Assume it's a URL + // Put 1x1 pixels in texture. That makes it renderable immediately regardless of filtering. + const color = make1Pixel(options.color); + if (target === TEXTURE_CUBE_MAP$1) { + for (let ii = 0; ii < 6; ++ii) { + gl.texImage2D(TEXTURE_CUBE_MAP_POSITIVE_X + ii, 0, RGBA$1, 1, 1, 0, RGBA$1, UNSIGNED_BYTE$1, color); + } + } else if (target === TEXTURE_3D$1 || target === TEXTURE_2D_ARRAY$1) { + gl.texImage3D(target, 0, RGBA$1, 1, 1, 1, 0, RGBA$1, UNSIGNED_BYTE$1, color); + } else { + gl.texImage2D(target, 0, RGBA$1, 1, 1, 0, RGBA$1, UNSIGNED_BYTE$1, color); + } +} + +/** + * The src image(s) used to create a texture. + * + * When you call {@link module:twgl.createTexture} or {@link module:twgl.createTextures} + * you can pass in urls for images to load into the textures. If it's a single url + * then this will be a single HTMLImageElement. If it's an array of urls used for a cubemap + * this will be a corresponding array of images for the cubemap. + * + * @typedef {HTMLImageElement|HTMLImageElement[]} TextureSrc + * @memberOf module:twgl + */ + +/** + * A callback for when an image finished downloading and been uploaded into a texture + * @callback TextureReadyCallback + * @param {*} err If truthy there was an error. + * @param {WebGLTexture} texture the texture. + * @param {module:twgl.TextureSrc} source image(s) used to as the src for the texture + * @memberOf module:twgl + */ + +/** + * A callback for when all images have finished downloading and been uploaded into their respective textures + * @callback TexturesReadyCallback + * @param {*} err If truthy there was an error. + * @param {Object.} textures the created textures by name. Same as returned by {@link module:twgl.createTextures}. + * @param {Object.} sources the image(s) used for the texture by name. + * @memberOf module:twgl + */ + +/** + * A callback for when an image finished downloading and been uploaded into a texture + * @callback CubemapReadyCallback + * @param {*} err If truthy there was an error. + * @param {WebGLTexture} tex the texture. + * @param {HTMLImageElement[]} imgs the images for each face. + * @memberOf module:twgl + */ + +/** + * A callback for when an image finished downloading and been uploaded into a texture + * @callback ThreeDReadyCallback + * @param {*} err If truthy there was an error. + * @param {WebGLTexture} tex the texture. + * @param {HTMLImageElement[]} imgs the images for each slice. + * @memberOf module:twgl + */ + +/** + * Loads a texture from an image from a Url as specified in `options.src` + * If `options.color !== false` will set the texture to a 1x1 pixel color so that the texture is + * immediately useable. It will be updated with the contents of the image once the image has finished + * downloading. Filtering options will be set as appropriate for image unless `options.auto === false`. + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLTexture} tex the WebGLTexture to set parameters for + * @param {module:twgl.TextureOptions} [options] A TextureOptions object with whatever parameters you want set. + * @param {module:twgl.TextureReadyCallback} [callback] A function to be called when the image has finished loading. err will + * be non null if there was an error. + * @return {HTMLImageElement} the image being downloaded. + * @memberOf module:twgl/textures + */ +function loadTextureFromUrl(gl, tex, options, callback) { + callback = callback || noop; + options = options || defaults$1.textureOptions; + setTextureTo1PixelColor(gl, tex, options); + // Because it's async we need to copy the options. + options = Object.assign({}, options); + const img = loadAndUseImage(options.src, options.crossOrigin, function(err, img) { + if (err) { + callback(err, tex, img); + } else { + setTextureFromElement(gl, tex, img, options); + callback(null, tex, img); + } + }); + return img; +} + +/** + * Loads a cubemap from 6 urls or TexImageSources as specified in `options.src`. Will set the cubemap to a 1x1 pixel color + * so that it is usable immediately unless `option.color === false`. + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLTexture} tex the WebGLTexture to set parameters for + * @param {module:twgl.TextureOptions} options A TextureOptions object with whatever parameters you want set. + * @param {module:twgl.CubemapReadyCallback} [callback] A function to be called when all the images have finished loading. err will + * be non null if there was an error. + * @memberOf module:twgl/textures + * @private + */ +function loadCubemapFromUrls(gl, tex, options, callback) { + callback = callback || noop; + const urls = options.src; + if (urls.length !== 6) { + throw "there must be 6 urls for a cubemap"; + } + const level = options.level || 0; + const internalFormat = options.internalFormat || options.format || RGBA$1; + const formatType = getFormatAndTypeForInternalFormat(internalFormat); + const format = options.format || formatType.format; + const type = options.type || UNSIGNED_BYTE$1; + const target = options.target || TEXTURE_2D$2; + if (target !== TEXTURE_CUBE_MAP$1) { + throw "target must be TEXTURE_CUBE_MAP"; + } + setTextureTo1PixelColor(gl, tex, options); + // Because it's async we need to copy the options. + options = Object.assign({}, options); + let numToLoad = 6; + const errors = []; + const faces = getCubeFaceOrder(gl, options); + let imgs; // eslint-disable-line + + function uploadImg(faceTarget) { + return function(err, img) { + --numToLoad; + if (err) { + errors.push(err); + } else { + if (img.width !== img.height) { + errors.push("cubemap face img is not a square: " + img.src); + } else { + setPackState(gl, options); + gl.bindTexture(target, tex); + + // So assuming this is the first image we now have one face that's img sized + // and 5 faces that are 1x1 pixel so size the other faces + if (numToLoad === 5) { + // use the default order + getCubeFaceOrder().forEach(function(otherTarget) { + // Should we re-use the same face or a color? + gl.texImage2D(otherTarget, level, internalFormat, format, type, img); + }); + } else { + gl.texImage2D(faceTarget, level, internalFormat, format, type, img); + } + + if (shouldAutomaticallySetTextureFilteringForSize(options)) { + gl.generateMipmap(target); + } + } + } + + if (numToLoad === 0) { + callback(errors.length ? errors : undefined, tex, imgs); + } + }; + } + + imgs = urls.map(function(url, ndx) { + return loadAndUseImage(url, options.crossOrigin, uploadImg(faces[ndx])); + }); +} + +/** + * Loads a 2d array or 3d texture from urls OR TexImageSources as specified in `options.src`. + * Will set the texture to a 1x1 pixel color + * so that it is usable immediately unless `option.color === false`. + * + * If the width and height is not specified the width and height of the first + * image loaded will be used. Note that since images are loaded async + * which image downloads first is unknown. + * + * If an image is not the same size as the width and height it will be scaled + * to that width and height. + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLTexture} tex the WebGLTexture to set parameters for + * @param {module:twgl.TextureOptions} options A TextureOptions object with whatever parameters you want set. + * @param {module:twgl.ThreeDReadyCallback} [callback] A function to be called when all the images have finished loading. err will + * be non null if there was an error. + * @memberOf module:twgl/textures + * @private + */ +function loadSlicesFromUrls(gl, tex, options, callback) { + callback = callback || noop; + const urls = options.src; + const internalFormat = options.internalFormat || options.format || RGBA$1; + const formatType = getFormatAndTypeForInternalFormat(internalFormat); + const format = options.format || formatType.format; + const type = options.type || UNSIGNED_BYTE$1; + const target = options.target || TEXTURE_2D_ARRAY$1; + if (target !== TEXTURE_3D$1 && target !== TEXTURE_2D_ARRAY$1) { + throw "target must be TEXTURE_3D or TEXTURE_2D_ARRAY"; + } + setTextureTo1PixelColor(gl, tex, options); + // Because it's async we need to copy the options. + options = Object.assign({}, options); + let numToLoad = urls.length; + const errors = []; + let imgs; // eslint-disable-line + const level = options.level || 0; + let width = options.width; + let height = options.height; + const depth = urls.length; + let firstImage = true; + + function uploadImg(slice) { + return function(err, img) { + --numToLoad; + if (err) { + errors.push(err); + } else { + setPackState(gl, options); + gl.bindTexture(target, tex); + + if (firstImage) { + firstImage = false; + width = options.width || img.width; + height = options.height || img.height; + gl.texImage3D(target, level, internalFormat, width, height, depth, 0, format, type, null); + + // put it in every slice otherwise some slices will be 0,0,0,0 + for (let s = 0; s < depth; ++s) { + gl.texSubImage3D(target, level, 0, 0, s, width, height, 1, format, type, img); + } + } else { + let src = img; + let ctx; + if (img.width !== width || img.height !== height) { + // Size the image to fix + ctx = getShared2DContext(); + src = ctx.canvas; + ctx.canvas.width = width; + ctx.canvas.height = height; + ctx.drawImage(img, 0, 0, width, height); + } + + gl.texSubImage3D(target, level, 0, 0, slice, width, height, 1, format, type, src); + + // free the canvas memory + if (ctx && src === ctx.canvas) { + ctx.canvas.width = 0; + ctx.canvas.height = 0; + } + } + + if (shouldAutomaticallySetTextureFilteringForSize(options)) { + gl.generateMipmap(target); + } + } + + if (numToLoad === 0) { + callback(errors.length ? errors : undefined, tex, imgs); + } + }; + } + + imgs = urls.map(function(url, ndx) { + return loadAndUseImage(url, options.crossOrigin, uploadImg(ndx)); + }); +} + +/** + * Sets a texture from an array or typed array. If the width or height is not provided will attempt to + * guess the size. See {@link module:twgl.TextureOptions}. + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLTexture} tex the WebGLTexture to set parameters for + * @param {(number[]|ArrayBufferView)} src An array or typed arry with texture data. + * @param {module:twgl.TextureOptions} [options] A TextureOptions object with whatever parameters you want set. + * This is often the same options you passed in when you created the texture. + * @memberOf module:twgl/textures + */ +function setTextureFromArray(gl, tex, src, options) { + options = options || defaults$1.textureOptions; + const target = options.target || TEXTURE_2D$2; + gl.bindTexture(target, tex); + let width = options.width; + let height = options.height; + let depth = options.depth; + const level = options.level || 0; + const internalFormat = options.internalFormat || options.format || RGBA$1; + const formatType = getFormatAndTypeForInternalFormat(internalFormat); + const format = options.format || formatType.format; + const type = options.type || getTextureTypeForArrayType(gl, src, formatType.type); + if (!isArrayBuffer(src)) { + const Type = getTypedArrayTypeForGLType(type); + src = new Type(src); + } else if (src instanceof Uint8ClampedArray) { + src = new Uint8Array(src.buffer); + } + + const bytesPerElement = getBytesPerElementForInternalFormat(internalFormat, type); + const numElements = src.byteLength / bytesPerElement; // TODO: check UNPACK_ALIGNMENT? + if (numElements % 1) { + throw "length wrong size for format: " + glEnumToString(gl, format); + } + let dimensions; + if (target === TEXTURE_3D$1 || target === TEXTURE_2D_ARRAY$1) { + if (!width && !height && !depth) { + const size = Math.cbrt(numElements); + if (size % 1 !== 0) { + throw "can't guess cube size of array of numElements: " + numElements; + } + width = size; + height = size; + depth = size; + } else if (width && (!height || !depth)) { + dimensions = guessDimensions(gl, target, height, depth, numElements / width); + height = dimensions.width; + depth = dimensions.height; + } else if (height && (!width || !depth)) { + dimensions = guessDimensions(gl, target, width, depth, numElements / height); + width = dimensions.width; + depth = dimensions.height; + } else { + dimensions = guessDimensions(gl, target, width, height, numElements / depth); + width = dimensions.width; + height = dimensions.height; + } + } else { + dimensions = guessDimensions(gl, target, width, height, numElements); + width = dimensions.width; + height = dimensions.height; + } + setSkipStateToDefault(gl); + gl.pixelStorei(UNPACK_ALIGNMENT, options.unpackAlignment || 1); + setPackState(gl, options); + if (target === TEXTURE_CUBE_MAP$1) { + const elementsPerElement = bytesPerElement / src.BYTES_PER_ELEMENT; + const faceSize = numElements / 6 * elementsPerElement; + + getCubeFacesWithNdx(gl, options).forEach(f => { + const offset = faceSize * f.ndx; + const data = src.subarray(offset, offset + faceSize); + gl.texImage2D(f.face, level, internalFormat, width, height, 0, format, type, data); + }); + } else if (target === TEXTURE_3D$1 || target === TEXTURE_2D_ARRAY$1) { + gl.texImage3D(target, level, internalFormat, width, height, depth, 0, format, type, src); + } else { + gl.texImage2D(target, level, internalFormat, width, height, 0, format, type, src); + } + return { + width: width, + height: height, + depth: depth, + type: type, + }; +} + +/** + * Sets a texture with no contents of a certain size. In other words calls `gl.texImage2D` with `null`. + * You must set `options.width` and `options.height`. + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLTexture} tex the WebGLTexture to set parameters for + * @param {module:twgl.TextureOptions} options A TextureOptions object with whatever parameters you want set. + * @memberOf module:twgl/textures + */ +function setEmptyTexture(gl, tex, options) { + const target = options.target || TEXTURE_2D$2; + gl.bindTexture(target, tex); + const level = options.level || 0; + const internalFormat = options.internalFormat || options.format || RGBA$1; + const formatType = getFormatAndTypeForInternalFormat(internalFormat); + const format = options.format || formatType.format; + const type = options.type || formatType.type; + setPackState(gl, options); + if (target === TEXTURE_CUBE_MAP$1) { + for (let ii = 0; ii < 6; ++ii) { + gl.texImage2D(TEXTURE_CUBE_MAP_POSITIVE_X + ii, level, internalFormat, options.width, options.height, 0, format, type, null); + } + } else if (target === TEXTURE_3D$1 || target === TEXTURE_2D_ARRAY$1) { + gl.texImage3D(target, level, internalFormat, options.width, options.height, options.depth, 0, format, type, null); + } else { + gl.texImage2D(target, level, internalFormat, options.width, options.height, 0, format, type, null); + } +} + +/** + * Creates a texture based on the options passed in. + * + * Note: may reset UNPACK_ALIGNMENT, UNPACK_ROW_LENGTH, UNPACK_IMAGE_HEIGHT, UNPACK_SKIP_IMAGES + * UNPACK_SKIP_PIXELS, and UNPACK_SKIP_ROWS + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {module:twgl.TextureOptions} [options] A TextureOptions object with whatever parameters you want set. + * @param {module:twgl.TextureReadyCallback} [callback] A callback called when an image has been downloaded and uploaded to the texture. + * @return {WebGLTexture} the created texture. + * @memberOf module:twgl/textures + */ +function createTexture(gl, options, callback) { + callback = callback || noop; + options = options || defaults$1.textureOptions; + const tex = gl.createTexture(); + const target = options.target || TEXTURE_2D$2; + let width = options.width || 1; + let height = options.height || 1; + const internalFormat = options.internalFormat || RGBA$1; + gl.bindTexture(target, tex); + if (target === TEXTURE_CUBE_MAP$1) { + // this should have been the default for cubemaps :( + gl.texParameteri(target, TEXTURE_WRAP_S, CLAMP_TO_EDGE$1); + gl.texParameteri(target, TEXTURE_WRAP_T, CLAMP_TO_EDGE$1); + } + let src = options.src; + if (src) { + if (typeof src === "function") { + src = src(gl, options); + } + if (typeof (src) === "string") { + loadTextureFromUrl(gl, tex, options, callback); + } else if (isArrayBuffer(src) || + (Array.isArray(src) && ( + typeof src[0] === 'number' || + Array.isArray(src[0]) || + isArrayBuffer(src[0])) + ) + ) { + const dimensions = setTextureFromArray(gl, tex, src, options); + width = dimensions.width; + height = dimensions.height; + } else if (Array.isArray(src) && (typeof (src[0]) === 'string' || isTexImageSource(src[0]))) { + if (target === TEXTURE_CUBE_MAP$1) { + loadCubemapFromUrls(gl, tex, options, callback); + } else { + loadSlicesFromUrls(gl, tex, options, callback); + } + } else { // if (isTexImageSource(src)) + setTextureFromElement(gl, tex, src, options); + width = src.width; + height = src.height; + } + } else { + setEmptyTexture(gl, tex, options); + } + if (shouldAutomaticallySetTextureFilteringForSize(options)) { + setTextureFilteringForSize(gl, tex, options, width, height, internalFormat); + } + setTextureParameters(gl, tex, options); + return tex; +} + +/** + * Resizes a texture based on the options passed in. + * + * Note: This is not a generic resize anything function. + * It's mostly used by {@link module:twgl.resizeFramebufferInfo} + * It will use `options.src` if it exists to try to determine a `type` + * otherwise it will assume `gl.UNSIGNED_BYTE`. No data is provided + * for the texture. Texture parameters will be set accordingly + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {WebGLTexture} tex the texture to resize + * @param {module:twgl.TextureOptions} options A TextureOptions object with whatever parameters you want set. + * @param {number} [width] the new width. If not passed in will use `options.width` + * @param {number} [height] the new height. If not passed in will use `options.height` + * @param {number} [depth] the new depth. If not passed in will use `options.depth` + * @memberOf module:twgl/textures + */ +function resizeTexture(gl, tex, options, width, height, depth) { + width = width || options.width; + height = height || options.height; + depth = depth || options.depth; + const target = options.target || TEXTURE_2D$2; + gl.bindTexture(target, tex); + const level = options.level || 0; + const internalFormat = options.internalFormat || options.format || RGBA$1; + const formatType = getFormatAndTypeForInternalFormat(internalFormat); + const format = options.format || formatType.format; + let type; + const src = options.src; + if (!src) { + type = options.type || formatType.type; + } else if (isArrayBuffer(src) || (Array.isArray(src) && typeof (src[0]) === 'number')) { + type = options.type || getTextureTypeForArrayType(gl, src, formatType.type); + } else { + type = options.type || formatType.type; + } + if (target === TEXTURE_CUBE_MAP$1) { + for (let ii = 0; ii < 6; ++ii) { + gl.texImage2D(TEXTURE_CUBE_MAP_POSITIVE_X + ii, level, internalFormat, width, height, 0, format, type, null); + } + } else if (target === TEXTURE_3D$1 || target === TEXTURE_2D_ARRAY$1) { + gl.texImage3D(target, level, internalFormat, width, height, depth, 0, format, type, null); + } else { + gl.texImage2D(target, level, internalFormat, width, height, 0, format, type, null); + } +} + +/** + * Check if a src is an async request. + * if src is a string we're going to download an image + * if src is an array of strings we're going to download cubemap images + * @param {*} src The src from a TextureOptions + * @returns {bool} true if src is async. + * @private + */ +function isAsyncSrc(src) { + return typeof src === 'string' || + (Array.isArray(src) && typeof src[0] === 'string'); +} + +/** + * Creates a bunch of textures based on the passed in options. + * + * Example: + * + * const textures = twgl.createTextures(gl, { + * // a power of 2 image + * hftIcon: { src: "images/hft-icon-16.png", mag: gl.NEAREST }, + * // a non-power of 2 image + * clover: { src: "images/clover.jpg" }, + * // From a canvas + * fromCanvas: { src: ctx.canvas }, + * // A cubemap from 6 images + * yokohama: { + * target: gl.TEXTURE_CUBE_MAP, + * src: [ + * 'images/yokohama/posx.jpg', + * 'images/yokohama/negx.jpg', + * 'images/yokohama/posy.jpg', + * 'images/yokohama/negy.jpg', + * 'images/yokohama/posz.jpg', + * 'images/yokohama/negz.jpg', + * ], + * }, + * // A cubemap from 1 image (can be 1x6, 2x3, 3x2, 6x1) + * goldengate: { + * target: gl.TEXTURE_CUBE_MAP, + * src: 'images/goldengate.jpg', + * }, + * // A 2x2 pixel texture from a JavaScript array + * checker: { + * mag: gl.NEAREST, + * min: gl.LINEAR, + * src: [ + * 255,255,255,255, + * 192,192,192,255, + * 192,192,192,255, + * 255,255,255,255, + * ], + * }, + * // a 1x2 pixel texture from a typed array. + * stripe: { + * mag: gl.NEAREST, + * min: gl.LINEAR, + * format: gl.LUMINANCE, + * src: new Uint8Array([ + * 255, + * 128, + * 255, + * 128, + * 255, + * 128, + * 255, + * 128, + * ]), + * width: 1, + * }, + * }); + * + * Now + * + * * `textures.hftIcon` will be a 2d texture + * * `textures.clover` will be a 2d texture + * * `textures.fromCanvas` will be a 2d texture + * * `textures.yohohama` will be a cubemap texture + * * `textures.goldengate` will be a cubemap texture + * * `textures.checker` will be a 2d texture + * * `textures.stripe` will be a 2d texture + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {Object.} options A object of TextureOptions one per texture. + * @param {module:twgl.TexturesReadyCallback} [callback] A callback called when all textures have been downloaded. + * @return {Object.} the created textures by name + * @memberOf module:twgl/textures + */ +function createTextures(gl, textureOptions, callback) { + callback = callback || noop; + let numDownloading = 0; + const errors = []; + const textures = {}; + const images = {}; + + function callCallbackIfReady() { + if (numDownloading === 0) { + setTimeout(function() { + callback(errors.length ? errors : undefined, textures, images); + }, 0); + } + } + + Object.keys(textureOptions).forEach(function(name) { + const options = textureOptions[name]; + let onLoadFn; + if (isAsyncSrc(options.src)) { + onLoadFn = function(err, tex, img) { + images[name] = img; + --numDownloading; + if (err) { + errors.push(err); + } + callCallbackIfReady(); + }; + ++numDownloading; + } + textures[name] = createTexture(gl, options, onLoadFn); + }); + + // queue the callback if there are no images to download. + // We do this because if your code is structured to wait for + // images to download but then you comment out all the async + // images your code would break. + callCallbackIfReady(); + + return textures; +} + +var textures = /*#__PURE__*/Object.freeze({ + __proto__: null, + setTextureDefaults_: setDefaults$1, + createSampler: createSampler, + createSamplers: createSamplers, + setSamplerParameters: setSamplerParameters, + createTexture: createTexture, + setEmptyTexture: setEmptyTexture, + setTextureFromArray: setTextureFromArray, + loadTextureFromUrl: loadTextureFromUrl, + setTextureFromElement: setTextureFromElement, + setTextureFilteringForSize: setTextureFilteringForSize, + setTextureParameters: setTextureParameters, + setDefaultTextureColor: setDefaultTextureColor, + createTextures: createTextures, + resizeTexture: resizeTexture, + canGenerateMipmap: canGenerateMipmap, + canFilter: canFilter, + getNumComponentsForFormat: getNumComponentsForFormat, + getBytesPerElementForInternalFormat: getBytesPerElementForInternalFormat, + getFormatAndTypeForInternalFormat: getFormatAndTypeForInternalFormat +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/** + * Low level shader program related functions + * + * You should generally not need to use these functions. They are provided + * for those cases where you're doing something out of the ordinary + * and you need lower level access. + * + * For backward compatibility they are available at both `twgl.programs` and `twgl` + * itself + * + * See {@link module:twgl} for core functions + * + * @module twgl/programs + */ + +const error = error$1; +const warn = warn$1; +function getElementById(id) { + return (typeof document !== 'undefined' && document.getElementById) + ? document.getElementById(id) + : null; +} + +const TEXTURE0 = 0x84c0; +const DYNAMIC_DRAW = 0x88e8; + +const ARRAY_BUFFER = 0x8892; +const ELEMENT_ARRAY_BUFFER$1 = 0x8893; +const UNIFORM_BUFFER = 0x8a11; +const TRANSFORM_FEEDBACK_BUFFER = 0x8c8e; + +const TRANSFORM_FEEDBACK = 0x8e22; + +const COMPILE_STATUS = 0x8b81; +const LINK_STATUS = 0x8b82; +const FRAGMENT_SHADER = 0x8b30; +const VERTEX_SHADER = 0x8b31; +const SEPARATE_ATTRIBS = 0x8c8d; + +const ACTIVE_UNIFORMS = 0x8b86; +const ACTIVE_ATTRIBUTES = 0x8b89; +const TRANSFORM_FEEDBACK_VARYINGS = 0x8c83; +const ACTIVE_UNIFORM_BLOCKS = 0x8a36; +const UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER = 0x8a44; +const UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER = 0x8a46; +const UNIFORM_BLOCK_DATA_SIZE = 0x8a40; +const UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES = 0x8a43; + +const FLOAT = 0x1406; +const FLOAT_VEC2 = 0x8B50; +const FLOAT_VEC3 = 0x8B51; +const FLOAT_VEC4 = 0x8B52; +const INT = 0x1404; +const INT_VEC2 = 0x8B53; +const INT_VEC3 = 0x8B54; +const INT_VEC4 = 0x8B55; +const BOOL = 0x8B56; +const BOOL_VEC2 = 0x8B57; +const BOOL_VEC3 = 0x8B58; +const BOOL_VEC4 = 0x8B59; +const FLOAT_MAT2 = 0x8B5A; +const FLOAT_MAT3 = 0x8B5B; +const FLOAT_MAT4 = 0x8B5C; +const SAMPLER_2D = 0x8B5E; +const SAMPLER_CUBE = 0x8B60; +const SAMPLER_3D = 0x8B5F; +const SAMPLER_2D_SHADOW = 0x8B62; +const FLOAT_MAT2x3 = 0x8B65; +const FLOAT_MAT2x4 = 0x8B66; +const FLOAT_MAT3x2 = 0x8B67; +const FLOAT_MAT3x4 = 0x8B68; +const FLOAT_MAT4x2 = 0x8B69; +const FLOAT_MAT4x3 = 0x8B6A; +const SAMPLER_2D_ARRAY = 0x8DC1; +const SAMPLER_2D_ARRAY_SHADOW = 0x8DC4; +const SAMPLER_CUBE_SHADOW = 0x8DC5; +const UNSIGNED_INT = 0x1405; +const UNSIGNED_INT_VEC2 = 0x8DC6; +const UNSIGNED_INT_VEC3 = 0x8DC7; +const UNSIGNED_INT_VEC4 = 0x8DC8; +const INT_SAMPLER_2D = 0x8DCA; +const INT_SAMPLER_3D = 0x8DCB; +const INT_SAMPLER_CUBE = 0x8DCC; +const INT_SAMPLER_2D_ARRAY = 0x8DCF; +const UNSIGNED_INT_SAMPLER_2D = 0x8DD2; +const UNSIGNED_INT_SAMPLER_3D = 0x8DD3; +const UNSIGNED_INT_SAMPLER_CUBE = 0x8DD4; +const UNSIGNED_INT_SAMPLER_2D_ARRAY = 0x8DD7; + +const TEXTURE_2D$1 = 0x0DE1; +const TEXTURE_CUBE_MAP = 0x8513; +const TEXTURE_3D = 0x806F; +const TEXTURE_2D_ARRAY = 0x8C1A; + +const typeMap = {}; + +/** + * Returns the corresponding bind point for a given sampler type + * @private + */ +function getBindPointForSamplerType(gl, type) { + return typeMap[type].bindPoint; +} + +// This kind of sucks! If you could compose functions as in `var fn = gl[name];` +// this code could be a lot smaller but that is sadly really slow (T_T) + +function floatSetter(gl, location) { + return function(v) { + gl.uniform1f(location, v); + }; +} + +function floatArraySetter(gl, location) { + return function(v) { + gl.uniform1fv(location, v); + }; +} + +function floatVec2Setter(gl, location) { + return function(v) { + gl.uniform2fv(location, v); + }; +} + +function floatVec3Setter(gl, location) { + return function(v) { + gl.uniform3fv(location, v); + }; +} + +function floatVec4Setter(gl, location) { + return function(v) { + gl.uniform4fv(location, v); + }; +} + +function intSetter(gl, location) { + return function(v) { + gl.uniform1i(location, v); + }; +} + +function intArraySetter(gl, location) { + return function(v) { + gl.uniform1iv(location, v); + }; +} + +function intVec2Setter(gl, location) { + return function(v) { + gl.uniform2iv(location, v); + }; +} + +function intVec3Setter(gl, location) { + return function(v) { + gl.uniform3iv(location, v); + }; +} + +function intVec4Setter(gl, location) { + return function(v) { + gl.uniform4iv(location, v); + }; +} + +function uintSetter(gl, location) { + return function(v) { + gl.uniform1ui(location, v); + }; +} + +function uintArraySetter(gl, location) { + return function(v) { + gl.uniform1uiv(location, v); + }; +} + +function uintVec2Setter(gl, location) { + return function(v) { + gl.uniform2uiv(location, v); + }; +} + +function uintVec3Setter(gl, location) { + return function(v) { + gl.uniform3uiv(location, v); + }; +} + +function uintVec4Setter(gl, location) { + return function(v) { + gl.uniform4uiv(location, v); + }; +} + +function floatMat2Setter(gl, location) { + return function(v) { + gl.uniformMatrix2fv(location, false, v); + }; +} + +function floatMat3Setter(gl, location) { + return function(v) { + gl.uniformMatrix3fv(location, false, v); + }; +} + +function floatMat4Setter(gl, location) { + return function(v) { + gl.uniformMatrix4fv(location, false, v); + }; +} + +function floatMat23Setter(gl, location) { + return function(v) { + gl.uniformMatrix2x3fv(location, false, v); + }; +} + +function floatMat32Setter(gl, location) { + return function(v) { + gl.uniformMatrix3x2fv(location, false, v); + }; +} + +function floatMat24Setter(gl, location) { + return function(v) { + gl.uniformMatrix2x4fv(location, false, v); + }; +} + +function floatMat42Setter(gl, location) { + return function(v) { + gl.uniformMatrix4x2fv(location, false, v); + }; +} + +function floatMat34Setter(gl, location) { + return function(v) { + gl.uniformMatrix3x4fv(location, false, v); + }; +} + +function floatMat43Setter(gl, location) { + return function(v) { + gl.uniformMatrix4x3fv(location, false, v); + }; +} + +function samplerSetter(gl, type, unit, location) { + const bindPoint = getBindPointForSamplerType(gl, type); + return isWebGL2(gl) ? function(textureOrPair) { + let texture; + let sampler; + if (!textureOrPair || isTexture(gl, textureOrPair)) { + texture = textureOrPair; + sampler = null; + } else { + texture = textureOrPair.texture; + sampler = textureOrPair.sampler; + } + gl.uniform1i(location, unit); + gl.activeTexture(TEXTURE0 + unit); + gl.bindTexture(bindPoint, texture); + gl.bindSampler(unit, sampler); + } : function(texture) { + gl.uniform1i(location, unit); + gl.activeTexture(TEXTURE0 + unit); + gl.bindTexture(bindPoint, texture); + }; +} + +function samplerArraySetter(gl, type, unit, location, size) { + const bindPoint = getBindPointForSamplerType(gl, type); + const units = new Int32Array(size); + for (let ii = 0; ii < size; ++ii) { + units[ii] = unit + ii; + } + + return isWebGL2(gl) ? function(textures) { + gl.uniform1iv(location, units); + textures.forEach(function(textureOrPair, index) { + gl.activeTexture(TEXTURE0 + units[index]); + let texture; + let sampler; + if (!textureOrPair || isTexture(gl, textureOrPair)) { + texture = textureOrPair; + sampler = null; + } else { + texture = textureOrPair.texture; + sampler = textureOrPair.sampler; + } + gl.bindSampler(unit, sampler); + gl.bindTexture(bindPoint, texture); + }); + } : function(textures) { + gl.uniform1iv(location, units); + textures.forEach(function(texture, index) { + gl.activeTexture(TEXTURE0 + units[index]); + gl.bindTexture(bindPoint, texture); + }); + }; +} + +typeMap[FLOAT] = { Type: Float32Array, size: 4, setter: floatSetter, arraySetter: floatArraySetter, }; +typeMap[FLOAT_VEC2] = { Type: Float32Array, size: 8, setter: floatVec2Setter, cols: 2, }; +typeMap[FLOAT_VEC3] = { Type: Float32Array, size: 12, setter: floatVec3Setter, cols: 3, }; +typeMap[FLOAT_VEC4] = { Type: Float32Array, size: 16, setter: floatVec4Setter, cols: 4, }; +typeMap[INT] = { Type: Int32Array, size: 4, setter: intSetter, arraySetter: intArraySetter, }; +typeMap[INT_VEC2] = { Type: Int32Array, size: 8, setter: intVec2Setter, cols: 2, }; +typeMap[INT_VEC3] = { Type: Int32Array, size: 12, setter: intVec3Setter, cols: 3, }; +typeMap[INT_VEC4] = { Type: Int32Array, size: 16, setter: intVec4Setter, cols: 4, }; +typeMap[UNSIGNED_INT] = { Type: Uint32Array, size: 4, setter: uintSetter, arraySetter: uintArraySetter, }; +typeMap[UNSIGNED_INT_VEC2] = { Type: Uint32Array, size: 8, setter: uintVec2Setter, cols: 2, }; +typeMap[UNSIGNED_INT_VEC3] = { Type: Uint32Array, size: 12, setter: uintVec3Setter, cols: 3, }; +typeMap[UNSIGNED_INT_VEC4] = { Type: Uint32Array, size: 16, setter: uintVec4Setter, cols: 4, }; +typeMap[BOOL] = { Type: Uint32Array, size: 4, setter: intSetter, arraySetter: intArraySetter, }; +typeMap[BOOL_VEC2] = { Type: Uint32Array, size: 8, setter: intVec2Setter, cols: 2, }; +typeMap[BOOL_VEC3] = { Type: Uint32Array, size: 12, setter: intVec3Setter, cols: 3, }; +typeMap[BOOL_VEC4] = { Type: Uint32Array, size: 16, setter: intVec4Setter, cols: 4, }; +typeMap[FLOAT_MAT2] = { Type: Float32Array, size: 32, setter: floatMat2Setter, rows: 2, cols: 2, }; +typeMap[FLOAT_MAT3] = { Type: Float32Array, size: 48, setter: floatMat3Setter, rows: 3, cols: 3, }; +typeMap[FLOAT_MAT4] = { Type: Float32Array, size: 64, setter: floatMat4Setter, rows: 4, cols: 4, }; +typeMap[FLOAT_MAT2x3] = { Type: Float32Array, size: 32, setter: floatMat23Setter, rows: 2, cols: 3, }; +typeMap[FLOAT_MAT2x4] = { Type: Float32Array, size: 32, setter: floatMat24Setter, rows: 2, cols: 4, }; +typeMap[FLOAT_MAT3x2] = { Type: Float32Array, size: 48, setter: floatMat32Setter, rows: 3, cols: 2, }; +typeMap[FLOAT_MAT3x4] = { Type: Float32Array, size: 48, setter: floatMat34Setter, rows: 3, cols: 4, }; +typeMap[FLOAT_MAT4x2] = { Type: Float32Array, size: 64, setter: floatMat42Setter, rows: 4, cols: 2, }; +typeMap[FLOAT_MAT4x3] = { Type: Float32Array, size: 64, setter: floatMat43Setter, rows: 4, cols: 3, }; +typeMap[SAMPLER_2D] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_2D$1, }; +typeMap[SAMPLER_CUBE] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_CUBE_MAP, }; +typeMap[SAMPLER_3D] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_3D, }; +typeMap[SAMPLER_2D_SHADOW] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_2D$1, }; +typeMap[SAMPLER_2D_ARRAY] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_2D_ARRAY, }; +typeMap[SAMPLER_2D_ARRAY_SHADOW] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_2D_ARRAY, }; +typeMap[SAMPLER_CUBE_SHADOW] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_CUBE_MAP, }; +typeMap[INT_SAMPLER_2D] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_2D$1, }; +typeMap[INT_SAMPLER_3D] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_3D, }; +typeMap[INT_SAMPLER_CUBE] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_CUBE_MAP, }; +typeMap[INT_SAMPLER_2D_ARRAY] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_2D_ARRAY, }; +typeMap[UNSIGNED_INT_SAMPLER_2D] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_2D$1, }; +typeMap[UNSIGNED_INT_SAMPLER_3D] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_3D, }; +typeMap[UNSIGNED_INT_SAMPLER_CUBE] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_CUBE_MAP, }; +typeMap[UNSIGNED_INT_SAMPLER_2D_ARRAY] = { Type: null, size: 0, setter: samplerSetter, arraySetter: samplerArraySetter, bindPoint: TEXTURE_2D_ARRAY, }; + +function floatAttribSetter(gl, index) { + return function(b) { + if (b.value) { + gl.disableVertexAttribArray(index); + switch (b.value.length) { + case 4: + gl.vertexAttrib4fv(index, b.value); + break; + case 3: + gl.vertexAttrib3fv(index, b.value); + break; + case 2: + gl.vertexAttrib2fv(index, b.value); + break; + case 1: + gl.vertexAttrib1fv(index, b.value); + break; + default: + throw new Error('the length of a float constant value must be between 1 and 4!'); + } + } else { + gl.bindBuffer(ARRAY_BUFFER, b.buffer); + gl.enableVertexAttribArray(index); + gl.vertexAttribPointer( + index, b.numComponents || b.size, b.type || FLOAT, b.normalize || false, b.stride || 0, b.offset || 0); + if (gl.vertexAttribDivisor) { + gl.vertexAttribDivisor(index, b.divisor || 0); + } + } + }; +} + +function intAttribSetter(gl, index) { + return function(b) { + if (b.value) { + gl.disableVertexAttribArray(index); + if (b.value.length === 4) { + gl.vertexAttrib4iv(index, b.value); + } else { + throw new Error('The length of an integer constant value must be 4!'); + } + } else { + gl.bindBuffer(ARRAY_BUFFER, b.buffer); + gl.enableVertexAttribArray(index); + gl.vertexAttribIPointer( + index, b.numComponents || b.size, b.type || INT, b.stride || 0, b.offset || 0); + if (gl.vertexAttribDivisor) { + gl.vertexAttribDivisor(index, b.divisor || 0); + } + } + }; +} + +function uintAttribSetter(gl, index) { + return function(b) { + if (b.value) { + gl.disableVertexAttribArray(index); + if (b.value.length === 4) { + gl.vertexAttrib4uiv(index, b.value); + } else { + throw new Error('The length of an unsigned integer constant value must be 4!'); + } + } else { + gl.bindBuffer(ARRAY_BUFFER, b.buffer); + gl.enableVertexAttribArray(index); + gl.vertexAttribIPointer( + index, b.numComponents || b.size, b.type || UNSIGNED_INT, b.stride || 0, b.offset || 0); + if (gl.vertexAttribDivisor) { + gl.vertexAttribDivisor(index, b.divisor || 0); + } + } + }; +} + +function matAttribSetter(gl, index, typeInfo) { + const defaultSize = typeInfo.size; + const count = typeInfo.count; + + return function(b) { + gl.bindBuffer(ARRAY_BUFFER, b.buffer); + const numComponents = b.size || b.numComponents || defaultSize; + const size = numComponents / count; + const type = b.type || FLOAT; + const typeInfo = typeMap[type]; + const stride = typeInfo.size * numComponents; + const normalize = b.normalize || false; + const offset = b.offset || 0; + const rowOffset = stride / count; + for (let i = 0; i < count; ++i) { + gl.enableVertexAttribArray(index + i); + gl.vertexAttribPointer( + index + i, size, type, normalize, stride, offset + rowOffset * i); + if (gl.vertexAttribDivisor) { + gl.vertexAttribDivisor(index + i, b.divisor || 0); + } + } + }; +} + + + +const attrTypeMap = {}; +attrTypeMap[FLOAT] = { size: 4, setter: floatAttribSetter, }; +attrTypeMap[FLOAT_VEC2] = { size: 8, setter: floatAttribSetter, }; +attrTypeMap[FLOAT_VEC3] = { size: 12, setter: floatAttribSetter, }; +attrTypeMap[FLOAT_VEC4] = { size: 16, setter: floatAttribSetter, }; +attrTypeMap[INT] = { size: 4, setter: intAttribSetter, }; +attrTypeMap[INT_VEC2] = { size: 8, setter: intAttribSetter, }; +attrTypeMap[INT_VEC3] = { size: 12, setter: intAttribSetter, }; +attrTypeMap[INT_VEC4] = { size: 16, setter: intAttribSetter, }; +attrTypeMap[UNSIGNED_INT] = { size: 4, setter: uintAttribSetter, }; +attrTypeMap[UNSIGNED_INT_VEC2] = { size: 8, setter: uintAttribSetter, }; +attrTypeMap[UNSIGNED_INT_VEC3] = { size: 12, setter: uintAttribSetter, }; +attrTypeMap[UNSIGNED_INT_VEC4] = { size: 16, setter: uintAttribSetter, }; +attrTypeMap[BOOL] = { size: 4, setter: intAttribSetter, }; +attrTypeMap[BOOL_VEC2] = { size: 8, setter: intAttribSetter, }; +attrTypeMap[BOOL_VEC3] = { size: 12, setter: intAttribSetter, }; +attrTypeMap[BOOL_VEC4] = { size: 16, setter: intAttribSetter, }; +attrTypeMap[FLOAT_MAT2] = { size: 4, setter: matAttribSetter, count: 2, }; +attrTypeMap[FLOAT_MAT3] = { size: 9, setter: matAttribSetter, count: 3, }; +attrTypeMap[FLOAT_MAT4] = { size: 16, setter: matAttribSetter, count: 4, }; + +const errorRE = /ERROR:\s*\d+:(\d+)/gi; +function addLineNumbersWithError(src, log = '', lineOffset = 0) { + // Note: Error message formats are not defined by any spec so this may or may not work. + const matches = [...log.matchAll(errorRE)]; + const lineNoToErrorMap = new Map(matches.map((m, ndx) => { + const lineNo = parseInt(m[1]); + const next = matches[ndx + 1]; + const end = next ? next.index : log.length; + const msg = log.substring(m.index, end); + return [lineNo - 1, msg]; + })); + return src.split('\n').map((line, lineNo) => { + const err = lineNoToErrorMap.get(lineNo); + return `${lineNo + 1 + lineOffset}: ${line}${err ? `\n\n^^^ ${err}` : ''}`; + }).join('\n'); +} + +/** + * Error Callback + * @callback ErrorCallback + * @param {string} msg error message. + * @param {number} [lineOffset] amount to add to line number + * @memberOf module:twgl + */ + +/** + * Program Callback + * @callback ProgramCallback + * @param {string} [err] error message, falsy if no error + * @param {WebGLProgram|module:twgl.ProgramInfo} [result] the program or programInfo + */ + +const spaceRE = /^[ \t]*\n/; + +/** + * Remove the first end of line because WebGL 2.0 requires + * #version 300 es + * as the first line. No whitespace allowed before that line + * so + * + * + * + * Has one line before it which is invalid according to GLSL ES 3.00 + * + * @param {string} shaderSource The source of the shader + * @returns {{shaderSource: string, lineOffset: number}} + * @private + */ +function prepShaderSource(shaderSource) { + let lineOffset = 0; + if (spaceRE.test(shaderSource)) { + lineOffset = 1; + shaderSource = shaderSource.replace(spaceRE, ''); + } + return {lineOffset, shaderSource}; +} + +/** + * @param {module:twgl.ProgramOptions} progOptions + * @param {string} msg + * @return null + * @private + */ +function reportError(progOptions, msg) { + progOptions.errorCallback(msg); + if (progOptions.callback) { + setTimeout(() => { + progOptions.callback(`${msg}\n${progOptions.errors.join('\n')}`); + }); + } + return null; +} + +/** + * Check Shader status + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {number} shaderType The shader type + * @param {WebGLShader} shader The shader + * @param {ErrorCallback} [errFn] function to receive error message. + * @return {string} errors or empty string + * @private + */ +function checkShaderStatus(gl, shaderType, shader, errFn) { + errFn = errFn || error; + // Check the compile status + const compiled = gl.getShaderParameter(shader, COMPILE_STATUS); + if (!compiled) { + // Something went wrong during compilation; get the error + const lastError = gl.getShaderInfoLog(shader); + const {lineOffset, shaderSource} = prepShaderSource(gl.getShaderSource(shader)); + const error = `${addLineNumbersWithError(shaderSource, lastError, lineOffset)}\nError compiling ${glEnumToString(gl, shaderType)}: ${lastError}`; + errFn(error); + return error; + } + return ''; +} + +/** + * @typedef {Object} FullProgramSpec + * @property {string[]} shaders the shader source or element ids. + * @property {function(string)} [errorCallback] callback for errors + * @property {Object.|string[]} [attribLocations] a attribute name to location map, or array of attribute names where index = location. + * @property {(module:twgl.BufferInfo|Object.|string[])} [transformFeedbackVaryings] If passed + * a BufferInfo will use the attribs names inside. If passed an object of AttribInfos will use the names from that object. Otherwise + * you can pass an array of names. + * @property {number} [transformFeedbackMode] the mode to pass `gl.transformFeedbackVaryings`. Defaults to `SEPARATE_ATTRIBS`. + * @property {ProgramCallback} [callback] callback for async program compilation. + * @memberOf module:twgl + */ + +/** + * @typedef {string[]|module:twgl.FullProgramSpec} ProgramSpec + * @memberOf module:twgl + */ + +/** + * @typedef {Object} ProgramOptions + * @property {function(string)} [errorCallback] callback for errors + * @property {Object.|string[]} [attribLocations] a attribute name to location map, or array of attribute names where index = location. + * @property {(module:twgl.BufferInfo|Object.|string[])} [transformFeedbackVaryings] If passed + * a BufferInfo will use the attribs names inside. If passed an object of AttribInfos will use the names from that object. Otherwise + * you can pass an array of names. + * @property {number} [transformFeedbackMode] the mode to pass `gl.transformFeedbackVaryings`. Defaults to `SEPARATE_ATTRIBS`. + * @property {ProgramCallback} [callback] callback for async program compilation. + * @memberOf module:twgl + */ + +/** + * Gets the program options based on all these optional arguments + * @param {module:twgl.ProgramOptions|string[]} [opt_attribs] Options for the program or an array of attribs names. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations] The locations for the. A parallel array to opt_attribs letting you assign locations. + * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @return {module:twgl.ProgramOptions} an instance of ProgramOptions based on the arguments passed in + * @private + */ +function getProgramOptions(opt_attribs, opt_locations, opt_errorCallback) { + let transformFeedbackVaryings; + let transformFeedbackMode; + let callback; + if (typeof opt_locations === 'function') { + opt_errorCallback = opt_locations; + opt_locations = undefined; + } + if (typeof opt_attribs === 'function') { + opt_errorCallback = opt_attribs; + opt_attribs = undefined; + } else if (opt_attribs && !Array.isArray(opt_attribs)) { + const opt = opt_attribs; + opt_errorCallback = opt.errorCallback; + opt_attribs = opt.attribLocations; + transformFeedbackVaryings = opt.transformFeedbackVaryings; + transformFeedbackMode = opt.transformFeedbackMode; + callback = opt.callback; + } + + const errorCallback = opt_errorCallback || error; + const errors = []; + const options = { + errorCallback(msg, ...args) { + errors.push(msg); + errorCallback(msg, ...args); + }, + transformFeedbackVaryings, + transformFeedbackMode, + callback, + errors, + }; + + { + let attribLocations = {}; + if (Array.isArray(opt_attribs)) { + opt_attribs.forEach(function(attrib, ndx) { + attribLocations[attrib] = opt_locations ? opt_locations[ndx] : ndx; + }); + } else { + attribLocations = opt_attribs || {}; + } + options.attribLocations = attribLocations; + } + + return options; +} + +const defaultShaderType = [ + "VERTEX_SHADER", + "FRAGMENT_SHADER", +]; + +function getShaderTypeFromScriptType(gl, scriptType) { + if (scriptType.indexOf("frag") >= 0) { + return FRAGMENT_SHADER; + } else if (scriptType.indexOf("vert") >= 0) { + return VERTEX_SHADER; + } + return undefined; +} + +function deleteProgramAndShaders(gl, program, notThese) { + const shaders = gl.getAttachedShaders(program); + for (const shader of shaders) { + if (notThese.has(shader)) { + gl.deleteShader(shader); + } + } + gl.deleteProgram(program); +} + +const wait = (ms = 0) => new Promise(resolve => setTimeout(resolve, ms)); + +function createProgramNoCheck(gl, shaders, programOptions) { + const program = gl.createProgram(); + const { + attribLocations, + transformFeedbackVaryings, + transformFeedbackMode, + } = getProgramOptions(programOptions); + + for (let ndx = 0; ndx < shaders.length; ++ndx) { + let shader = shaders[ndx]; + if (typeof shader === 'string') { + const elem = getElementById(shader); + const src = elem ? elem.text : shader; + let type = gl[defaultShaderType[ndx]]; + if (elem && elem.type) { + type = getShaderTypeFromScriptType(gl, elem.type) || type; + } + shader = gl.createShader(type); + gl.shaderSource(shader, prepShaderSource(src).shaderSource); + gl.compileShader(shader); + gl.attachShader(program, shader); + } + } + + Object.entries(attribLocations).forEach(([attrib, loc]) => gl.bindAttribLocation(program, loc, attrib)); + + { + let varyings = transformFeedbackVaryings; + if (varyings) { + if (varyings.attribs) { + varyings = varyings.attribs; + } + if (!Array.isArray(varyings)) { + varyings = Object.keys(varyings); + } + gl.transformFeedbackVaryings(program, varyings, transformFeedbackMode || SEPARATE_ATTRIBS); + } + } + + gl.linkProgram(program); + return program; +} + +/** + * Creates a program, attaches (and/or compiles) shaders, binds attrib locations, links the + * program. + * + * NOTE: There are 4 signatures for this function + * + * twgl.createProgram(gl, [vs, fs], options); + * twgl.createProgram(gl, [vs, fs], opt_errFunc); + * twgl.createProgram(gl, [vs, fs], opt_attribs, opt_errFunc); + * twgl.createProgram(gl, [vs, fs], opt_attribs, opt_locations, opt_errFunc); + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {WebGLShader[]|string[]} shaders The shaders to attach, or element ids for their source, or strings that contain their source + * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @return {WebGLProgram?} the created program or null if error of a callback was provided. + * @memberOf module:twgl/programs + */ +function createProgram( + gl, shaders, opt_attribs, opt_locations, opt_errorCallback) { + // This code is really convoluted, because it may or may not be async + // Maybe it would be better to have a separate function + const progOptions = getProgramOptions(opt_attribs, opt_locations, opt_errorCallback); + const shaderSet = new Set(shaders); + const program = createProgramNoCheck(gl, shaders, progOptions); + + function hasErrors(gl, program) { + const errors = getProgramErrors(gl, program, progOptions.errorCallback); + if (errors) { + deleteProgramAndShaders(gl, program, shaderSet); + } + return errors; + } + + if (progOptions.callback) { + waitForProgramLinkCompletionAsync(gl, program).then(() => { + const errors = hasErrors(gl, program); + progOptions.callback(errors, errors ? undefined : program); + }); + return undefined; + } + + return hasErrors(gl, program) ? undefined : program; +} + +/** + * This only works because the functions it wraps the first 2 arguments + * are gl and any, followed by things that become programOptions + * @private + */ +function wrapCallbackFnToAsyncFn(fn) { + return function(gl, arg1, ...args) { + return new Promise((resolve, reject) => { + const programOptions = getProgramOptions(...args); + programOptions.callback = (err, program) => { + if (err) { + reject(err); + } else { + resolve(program); + } + }; + fn(gl, arg1, programOptions); + }); + }; +} + +/** + * Same as createProgram but returns a promise + * + * NOTE: There are 4 signatures for this function + * + * twgl.createProgramAsync(gl, [vs, fs], options); + * twgl.createProgramAsync(gl, [vs, fs], opt_errFunc); + * twgl.createProgramAsync(gl, [vs, fs], opt_attribs, opt_errFunc); + * twgl.createProgramAsync(gl, [vs, fs], opt_attribs, opt_locations, opt_errFunc); + * + * @function + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {WebGLShader[]|string[]} shaders The shaders to attach, or element ids for their source, or strings that contain their source + * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @return {Promise} The created program + * @memberOf module:twgl/programs + */ +const createProgramAsync = wrapCallbackFnToAsyncFn(createProgram); + +/** + * Same as createProgramInfo but returns a promise + * @function + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {string[]} shaderSources Array of sources for the + * shaders or ids. The first is assumed to be the vertex shader, + * the second the fragment shader. + * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @return {Promise} The created ProgramInfo + * @memberOf module:twgl/programs + */ +const createProgramInfoAsync = wrapCallbackFnToAsyncFn(createProgramInfo); + +async function waitForProgramLinkCompletionAsync(gl, program) { + const ext = gl.getExtension('KHR_parallel_shader_compile'); + const checkFn = ext + ? (gl, program) => gl.getProgramParameter(program, ext.COMPLETION_STATUS_KHR) + : () => true; + + let waitTime = 0; + do { + await wait(waitTime); // must wait at least once + waitTime = 1000 / 60; + } while (!checkFn(gl, program)); +} + +async function waitForAllProgramsLinkCompletionAsync(gl, programs) { + for (const program of Object.values(programs)) { + await waitForProgramLinkCompletionAsync(gl, program); + } +} + +/** + * Check a program's link status + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {WebGLProgram} program Program to check + * @param {ErrorCallback} [errFn] func for errors + * @return {string?} errors if program is failed, else undefined + * @private + */ +function getProgramErrors(gl, program, errFn) { + errFn = errFn || error; + // Check the link status + const linked = gl.getProgramParameter(program, LINK_STATUS); + if (!linked) { + // something went wrong with the link + const lastError = gl.getProgramInfoLog(program); + errFn(`Error in program linking: ${lastError}`); + // print any errors from these shaders + const shaders = gl.getAttachedShaders(program); + const errors = shaders.map(shader => checkShaderStatus(gl, gl.getShaderParameter(shader, gl.SHADER_TYPE), shader, errFn)); + return `${lastError}\n${errors.filter(_ => _).join('\n')}`; + } + return undefined; +} + +/** + * Creates a program from 2 script tags. + * + * NOTE: There are 4 signatures for this function + * + * twgl.createProgramFromScripts(gl, [vs, fs], opt_options); + * twgl.createProgramFromScripts(gl, [vs, fs], opt_errFunc); + * twgl.createProgramFromScripts(gl, [vs, fs], opt_attribs, opt_errFunc); + * twgl.createProgramFromScripts(gl, [vs, fs], opt_attribs, opt_locations, opt_errFunc); + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {string[]} shaderScriptIds Array of ids of the script + * tags for the shaders. The first is assumed to be the + * vertex shader, the second the fragment shader. + * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @return {WebGLProgram?} the created program or null if error or a callback was provided. + * @memberOf module:twgl/programs + */ +function createProgramFromScripts( + gl, shaderScriptIds, opt_attribs, opt_locations, opt_errorCallback) { + const progOptions = getProgramOptions(opt_attribs, opt_locations, opt_errorCallback); + const shaders = []; + for (const scriptId of shaderScriptIds) { + const shaderScript = getElementById(scriptId); + if (!shaderScript) { + return reportError(progOptions, `unknown script element: ${scriptId}`); + } + shaders.push(shaderScript.text); + } + return createProgram(gl, shaders, progOptions); +} + +/** + * Creates a program from 2 sources. + * + * NOTE: There are 4 signatures for this function + * + * twgl.createProgramFromSource(gl, [vs, fs], opt_options); + * twgl.createProgramFromSource(gl, [vs, fs], opt_errFunc); + * twgl.createProgramFromSource(gl, [vs, fs], opt_attribs, opt_errFunc); + * twgl.createProgramFromSource(gl, [vs, fs], opt_attribs, opt_locations, opt_errFunc); + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {string[]} shaderSources Array of sources for the + * shaders. The first is assumed to be the vertex shader, + * the second the fragment shader. + * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @return {WebGLProgram?} the created program or null if error or a callback was provided. + * @memberOf module:twgl/programs + */ +function createProgramFromSources( + gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) { + return createProgram(gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback); +} + +/** + * Returns true if attribute/uniform is a reserved/built in + * + * It makes no sense to me why GL returns these because it's + * illegal to call `gl.getUniformLocation` and `gl.getAttribLocation` + * with names that start with `gl_` (and `webgl_` in WebGL) + * + * I can only assume they are there because they might count + * when computing the number of uniforms/attributes used when you want to + * know if you are near the limit. That doesn't really make sense + * to me but the fact that these get returned are in the spec. + * + * @param {WebGLActiveInfo} info As returned from `gl.getActiveUniform` or + * `gl.getActiveAttrib`. + * @return {bool} true if it's reserved + * @private + */ +function isBuiltIn(info) { + const name = info.name; + return name.startsWith("gl_") || name.startsWith("webgl_"); +} + +const tokenRE = /(\.|\[|]|\w+)/g; +const isDigit = s => s >= '0' && s <= '9'; +function addSetterToUniformTree(fullPath, setter, node, uniformSetters) { + const tokens = fullPath.split(tokenRE).filter(s => s !== ''); + let tokenNdx = 0; + let path = ''; + + for (;;) { + const token = tokens[tokenNdx++]; // has to be name or number + path += token; + const isArrayIndex = isDigit(token[0]); + const accessor = isArrayIndex + ? parseInt(token) + : token; + if (isArrayIndex) { + path += tokens[tokenNdx++]; // skip ']' + } + const isLastToken = tokenNdx === tokens.length; + if (isLastToken) { + node[accessor] = setter; + break; + } else { + const token = tokens[tokenNdx++]; // has to be . or [ + const isArray = token === '['; + const child = node[accessor] || (isArray ? [] : {}); + node[accessor] = child; + node = child; + uniformSetters[path] = uniformSetters[path] || function(node) { + return function(value) { + setUniformTree(node, value); + }; + }(child); + path += token; + } + } +} + +/** + * Creates setter functions for all uniforms of a shader + * program. + * + * @see {@link module:twgl.setUniforms} + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {WebGLProgram} program the program to create setters for. + * @returns {Object.} an object with a setter by name for each uniform + * @memberOf module:twgl/programs + */ +function createUniformSetters(gl, program) { + let textureUnit = 0; + + /** + * Creates a setter for a uniform of the given program with it's + * location embedded in the setter. + * @param {WebGLProgram} program + * @param {WebGLUniformInfo} uniformInfo + * @returns {function} the created setter. + */ + function createUniformSetter(program, uniformInfo, location) { + const isArray = uniformInfo.name.endsWith("[0]"); + const type = uniformInfo.type; + const typeInfo = typeMap[type]; + if (!typeInfo) { + throw new Error(`unknown type: 0x${type.toString(16)}`); // we should never get here. + } + let setter; + if (typeInfo.bindPoint) { + // it's a sampler + const unit = textureUnit; + textureUnit += uniformInfo.size; + if (isArray) { + setter = typeInfo.arraySetter(gl, type, unit, location, uniformInfo.size); + } else { + setter = typeInfo.setter(gl, type, unit, location, uniformInfo.size); + } + } else { + if (typeInfo.arraySetter && isArray) { + setter = typeInfo.arraySetter(gl, location); + } else { + setter = typeInfo.setter(gl, location); + } + } + setter.location = location; + return setter; + } + + const uniformSetters = {}; + const uniformTree = {}; + const numUniforms = gl.getProgramParameter(program, ACTIVE_UNIFORMS); + + for (let ii = 0; ii < numUniforms; ++ii) { + const uniformInfo = gl.getActiveUniform(program, ii); + if (isBuiltIn(uniformInfo)) { + continue; + } + let name = uniformInfo.name; + // remove the array suffix. + if (name.endsWith("[0]")) { + name = name.substr(0, name.length - 3); + } + const location = gl.getUniformLocation(program, uniformInfo.name); + // the uniform will have no location if it's in a uniform block + if (location) { + const setter = createUniformSetter(program, uniformInfo, location); + uniformSetters[name] = setter; + addSetterToUniformTree(name, setter, uniformTree, uniformSetters); + } + } + + return uniformSetters; +} + +/** + * @typedef {Object} TransformFeedbackInfo + * @property {number} index index of transform feedback + * @property {number} type GL type + * @property {number} size 1 - 4 + * @memberOf module:twgl + */ + +/** + * Create TransformFeedbackInfo for passing to bindTransformFeedbackInfo. + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {WebGLProgram} program an existing WebGLProgram. + * @return {Object} + * @memberOf module:twgl + */ +function createTransformFeedbackInfo(gl, program) { + const info = {}; + const numVaryings = gl.getProgramParameter(program, TRANSFORM_FEEDBACK_VARYINGS); + for (let ii = 0; ii < numVaryings; ++ii) { + const varying = gl.getTransformFeedbackVarying(program, ii); + info[varying.name] = { + index: ii, + type: varying.type, + size: varying.size, + }; + } + return info; +} + +/** + * Binds buffers for transform feedback. + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {(module:twgl.ProgramInfo|Object)} transformFeedbackInfo A ProgramInfo or TransformFeedbackInfo. + * @param {(module:twgl.BufferInfo|Object)} [bufferInfo] A BufferInfo or set of AttribInfos. + * @memberOf module:twgl + */ +function bindTransformFeedbackInfo(gl, transformFeedbackInfo, bufferInfo) { + if (transformFeedbackInfo.transformFeedbackInfo) { + transformFeedbackInfo = transformFeedbackInfo.transformFeedbackInfo; + } + if (bufferInfo.attribs) { + bufferInfo = bufferInfo.attribs; + } + for (const name in bufferInfo) { + const varying = transformFeedbackInfo[name]; + if (varying) { + const buf = bufferInfo[name]; + if (buf.offset) { + gl.bindBufferRange(TRANSFORM_FEEDBACK_BUFFER, varying.index, buf.buffer, buf.offset, buf.size); + } else { + gl.bindBufferBase(TRANSFORM_FEEDBACK_BUFFER, varying.index, buf.buffer); + } + } + } +} + +/** + * Creates a transform feedback and sets the buffers + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {module:twgl.ProgramInfo} programInfo A ProgramInfo as returned from {@link module:twgl.createProgramInfo} + * @param {(module:twgl.BufferInfo|Object)} [bufferInfo] A BufferInfo or set of AttribInfos. + * @return {WebGLTransformFeedback} the created transform feedback + * @memberOf module:twgl + */ +function createTransformFeedback(gl, programInfo, bufferInfo) { + const tf = gl.createTransformFeedback(); + gl.bindTransformFeedback(TRANSFORM_FEEDBACK, tf); + gl.useProgram(programInfo.program); + bindTransformFeedbackInfo(gl, programInfo, bufferInfo); + gl.bindTransformFeedback(TRANSFORM_FEEDBACK, null); + return tf; +} + +/** + * @typedef {Object} UniformData + * @property {string} name The name of the uniform + * @property {number} type The WebGL type enum for this uniform + * @property {number} size The number of elements for this uniform + * @property {number} blockNdx The block index this uniform appears in + * @property {number} offset The byte offset in the block for this uniform's value + * @memberOf module:twgl + */ + +/** + * The specification for one UniformBlockObject + * + * @typedef {Object} BlockSpec + * @property {number} index The index of the block. + * @property {number} size The size in bytes needed for the block + * @property {number[]} uniformIndices The indices of the uniforms used by the block. These indices + * correspond to entries in a UniformData array in the {@link module:twgl.UniformBlockSpec}. + * @property {bool} usedByVertexShader Self explanatory + * @property {bool} usedByFragmentShader Self explanatory + * @property {bool} used Self explanatory + * @memberOf module:twgl + */ + +/** + * A `UniformBlockSpec` represents the data needed to create and bind + * UniformBlockObjects for a given program + * + * @typedef {Object} UniformBlockSpec + * @property {Object.} blockSpecs The BlockSpec for each block by block name + * @property {UniformData[]} uniformData An array of data for each uniform by uniform index. + * @memberOf module:twgl + */ + +/** + * Creates a UniformBlockSpec for the given program. + * + * A UniformBlockSpec represents the data needed to create and bind + * UniformBlockObjects + * + * @param {WebGL2RenderingContext} gl A WebGL2 Rendering Context + * @param {WebGLProgram} program A WebGLProgram for a successfully linked program + * @return {module:twgl.UniformBlockSpec} The created UniformBlockSpec + * @memberOf module:twgl/programs + */ +function createUniformBlockSpecFromProgram(gl, program) { + const numUniforms = gl.getProgramParameter(program, ACTIVE_UNIFORMS); + const uniformData = []; + const uniformIndices = []; + + for (let ii = 0; ii < numUniforms; ++ii) { + uniformIndices.push(ii); + uniformData.push({}); + const uniformInfo = gl.getActiveUniform(program, ii); + uniformData[ii].name = uniformInfo.name; + } + + [ + [ "UNIFORM_TYPE", "type" ], + [ "UNIFORM_SIZE", "size" ], // num elements + [ "UNIFORM_BLOCK_INDEX", "blockNdx" ], + [ "UNIFORM_OFFSET", "offset", ], + ].forEach(function(pair) { + const pname = pair[0]; + const key = pair[1]; + gl.getActiveUniforms(program, uniformIndices, gl[pname]).forEach(function(value, ndx) { + uniformData[ndx][key] = value; + }); + }); + + const blockSpecs = {}; + + const numUniformBlocks = gl.getProgramParameter(program, ACTIVE_UNIFORM_BLOCKS); + for (let ii = 0; ii < numUniformBlocks; ++ii) { + const name = gl.getActiveUniformBlockName(program, ii); + const blockSpec = { + index: gl.getUniformBlockIndex(program, name), + usedByVertexShader: gl.getActiveUniformBlockParameter(program, ii, UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER), + usedByFragmentShader: gl.getActiveUniformBlockParameter(program, ii, UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER), + size: gl.getActiveUniformBlockParameter(program, ii, UNIFORM_BLOCK_DATA_SIZE), + uniformIndices: gl.getActiveUniformBlockParameter(program, ii, UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES), + }; + blockSpec.used = blockSpec.usedByVertexShader || blockSpec.usedByFragmentShader; + blockSpecs[name] = blockSpec; + } + + return { + blockSpecs: blockSpecs, + uniformData: uniformData, + }; +} + +const arraySuffixRE = /\[\d+\]\.$/; // better way to check? + +const pad = (v, padding) => ((v + (padding - 1)) / padding | 0) * padding; + +function createUniformBlockUniformSetter(view, isArray, rows, cols) { + if (isArray || rows) { + cols = cols || 1; + const numElements = view.length; + const totalRows = numElements / 4; + return function(value) { + let dst = 0; + let src = 0; + for (let row = 0; row < totalRows; ++row) { + for (let col = 0; col < cols; ++col) { + view[dst++] = value[src++]; + } + dst += 4 - cols; + } + }; + } else { + return function(value) { + if (value.length) { + view.set(value); + } else { + view[0] = value; + } + }; + } +} + +/** + * Represents a UniformBlockObject including an ArrayBuffer with all the uniform values + * and a corresponding WebGLBuffer to hold those values on the GPU + * + * @typedef {Object} UniformBlockInfo + * @property {string} name The name of the block + * @property {ArrayBuffer} array The array buffer that contains the uniform values + * @property {Float32Array} asFloat A float view on the array buffer. This is useful + * inspecting the contents of the buffer in the debugger. + * @property {WebGLBuffer} buffer A WebGL buffer that will hold a copy of the uniform values for rendering. + * @property {number} [offset] offset into buffer + * @property {Object} uniforms A uniform name to ArrayBufferView map. + * each Uniform has a correctly typed `ArrayBufferView` into array at the correct offset + * and length of that uniform. So for example a float uniform would have a 1 float `Float32Array` + * view. A single mat4 would have a 16 element `Float32Array` view. An ivec2 would have an + * `Int32Array` view, etc. + * @property {Object} setters A setter for this uniform. + * The reason to use setters is elements of arrays are padded to vec4 sizes which + * means if you want to set an array of 4 floats you'd need to set 16 values + * (or set elements 0, 4, 8, 12). In other words + * `someBlockInfo.uniforms.some4FloatArrayUniform.set([0, , , , 1, , , , 2, , , , 3])` + * where as the setter handles just passing in [0, 1, 2, 3] either directly as in + * `someBlockInfo.setter.some4FloatArrayUniform.set([0, 1, 2, 3])` (not recommended) + * or via {@link module:twgl.setBlockUniforms} + * @memberOf module:twgl + */ + +/** + * Creates a `UniformBlockInfo` for the specified block + * + * Note: **If the blockName matches no existing blocks a warning is printed to the console and a dummy + * `UniformBlockInfo` is returned**. This is because when debugging GLSL + * it is common to comment out large portions of a shader or for example set + * the final output to a constant. When that happens blocks get optimized out. + * If this function did not create dummy blocks your code would crash when debugging. + * + * @param {WebGL2RenderingContext} gl A WebGL2RenderingContext + * @param {WebGLProgram} program A WebGLProgram + * @param {module:twgl.UniformBlockSpec} uniformBlockSpec. A UniformBlockSpec as returned + * from {@link module:twgl.createUniformBlockSpecFromProgram}. + * @param {string} blockName The name of the block. + * @return {module:twgl.UniformBlockInfo} The created UniformBlockInfo + * @memberOf module:twgl/programs + */ +function createUniformBlockInfoFromProgram(gl, program, uniformBlockSpec, blockName) { + const blockSpecs = uniformBlockSpec.blockSpecs; + const uniformData = uniformBlockSpec.uniformData; + const blockSpec = blockSpecs[blockName]; + if (!blockSpec) { + warn("no uniform block object named:", blockName); + return { + name: blockName, + uniforms: {}, + }; + } + const array = new ArrayBuffer(blockSpec.size); + const buffer = gl.createBuffer(); + const uniformBufferIndex = blockSpec.index; + gl.bindBuffer(UNIFORM_BUFFER, buffer); + gl.uniformBlockBinding(program, blockSpec.index, uniformBufferIndex); + + let prefix = blockName + "."; + if (arraySuffixRE.test(prefix)) { + prefix = prefix.replace(arraySuffixRE, "."); + } + const uniforms = {}; + const setters = {}; + const setterTree = {}; + blockSpec.uniformIndices.forEach(function(uniformNdx) { + const data = uniformData[uniformNdx]; + let name = data.name; + if (name.startsWith(prefix)) { + name = name.substr(prefix.length); + } + const isArray = name.endsWith('[0]'); + if (isArray) { + name = name.substr(0, name.length - 3); + } + const typeInfo = typeMap[data.type]; + const Type = typeInfo.Type; + const byteLength = isArray + ? pad(typeInfo.size, 16) * data.size + : typeInfo.size * data.size; + const uniformView = new Type(array, data.offset, byteLength / Type.BYTES_PER_ELEMENT); + uniforms[name] = uniformView; + // Note: I'm not sure what to do here. The original + // idea was to create TypedArray views into each part + // of the block. This is useful, for example if you have + // a block with { mat4: model; mat4 view; mat4 projection; } + // you'll get a Float32Array for each one suitable for + // passing to most JS math libraries including twgl's and glMatrix.js. + // + // But, if you have a an array of structures, especially if that + // array is large, you get a whole bunch of TypedArray views. + // Every one of them has overhead and switching between them all + // is probably a cache miss. In that case it would really be better + // to just have one view (asFloat) and have all the setters + // just reference the correct portion. But, then you can't easily + // treat a matrix, or a vec4, as a standalone thing like you can + // with all the views. + // + // Another problem with the views is they are not shared. With + // uniforms you have one set of setters. With UniformBlockInfo + // you have a set of setters *pre block instance*. That's because + // TypedArray views can't be mapped to different buffers. + // + // My gut right now is if you really want the speed and compactness + // then you should probably roll your own solution. TWGL's goal + // here is ease of use as AFAICT there is no simple generic efficient + // solution. + const setter = createUniformBlockUniformSetter(uniformView, isArray, typeInfo.rows, typeInfo.cols); + setters[name] = setter; + addSetterToUniformTree(name, setter, setterTree, setters); + }); + return { + name: blockName, + array, + asFloat: new Float32Array(array), // for debugging + buffer, + uniforms, + setters, + }; +} + +/** + * Creates a `UniformBlockInfo` for the specified block + * + * Note: **If the blockName matches no existing blocks a warning is printed to the console and a dummy + * `UniformBlockInfo` is returned**. This is because when debugging GLSL + * it is common to comment out large portions of a shader or for example set + * the final output to a constant. When that happens blocks get optimized out. + * If this function did not create dummy blocks your code would crash when debugging. + * + * @param {WebGL2RenderingContext} gl A WebGL2RenderingContext + * @param {module:twgl.ProgramInfo} programInfo a `ProgramInfo` + * as returned from {@link module:twgl.createProgramInfo} + * @param {string} blockName The name of the block. + * @return {module:twgl.UniformBlockInfo} The created UniformBlockInfo + * @memberOf module:twgl/programs + */ +function createUniformBlockInfo(gl, programInfo, blockName) { + return createUniformBlockInfoFromProgram(gl, programInfo.program, programInfo.uniformBlockSpec, blockName); +} + +/** + * Binds a uniform block to the matching uniform block point. + * Matches by blocks by name so blocks must have the same name not just the same + * structure. + * + * If you have changed any values and you upload the values into the corresponding WebGLBuffer + * call {@link module:twgl.setUniformBlock} instead. + * + * @param {WebGL2RenderingContext} gl A WebGL 2 rendering context. + * @param {(module:twgl.ProgramInfo|module:twgl.UniformBlockSpec)} programInfo a `ProgramInfo` + * as returned from {@link module:twgl.createProgramInfo} or or `UniformBlockSpec` as + * returned from {@link module:twgl.createUniformBlockSpecFromProgram}. + * @param {module:twgl.UniformBlockInfo} uniformBlockInfo a `UniformBlockInfo` as returned from + * {@link module:twgl.createUniformBlockInfo}. + * @return {bool} true if buffer was bound. If the programInfo has no block with the same block name + * no buffer is bound. + * @memberOf module:twgl/programs + */ +function bindUniformBlock(gl, programInfo, uniformBlockInfo) { + const uniformBlockSpec = programInfo.uniformBlockSpec || programInfo; + const blockSpec = uniformBlockSpec.blockSpecs[uniformBlockInfo.name]; + if (blockSpec) { + const bufferBindIndex = blockSpec.index; + gl.bindBufferRange(UNIFORM_BUFFER, bufferBindIndex, uniformBlockInfo.buffer, uniformBlockInfo.offset || 0, uniformBlockInfo.array.byteLength); + return true; + } + return false; +} + +/** + * Uploads the current uniform values to the corresponding WebGLBuffer + * and binds that buffer to the program's corresponding bind point for the uniform block object. + * + * If you haven't changed any values and you only need to bind the uniform block object + * call {@link module:twgl.bindUniformBlock} instead. + * + * @param {WebGL2RenderingContext} gl A WebGL 2 rendering context. + * @param {(module:twgl.ProgramInfo|module:twgl.UniformBlockSpec)} programInfo a `ProgramInfo` + * as returned from {@link module:twgl.createProgramInfo} or or `UniformBlockSpec` as + * returned from {@link module:twgl.createUniformBlockSpecFromProgram}. + * @param {module:twgl.UniformBlockInfo} uniformBlockInfo a `UniformBlockInfo` as returned from + * {@link module:twgl.createUniformBlockInfo}. + * @memberOf module:twgl/programs + */ +function setUniformBlock(gl, programInfo, uniformBlockInfo) { + if (bindUniformBlock(gl, programInfo, uniformBlockInfo)) { + gl.bufferData(UNIFORM_BUFFER, uniformBlockInfo.array, DYNAMIC_DRAW); + } +} + +/** + * Sets values of a uniform block object + * + * @param {module:twgl.UniformBlockInfo} uniformBlockInfo A UniformBlockInfo as returned by {@link module:twgl.createUniformBlockInfo}. + * @param {Object.} values A uniform name to value map where the value is correct for the given + * type of uniform. So for example given a block like + * + * uniform SomeBlock { + * float someFloat; + * vec2 someVec2; + * vec3 someVec3Array[2]; + * int someInt; + * } + * + * You can set the values of the uniform block with + * + * twgl.setBlockUniforms(someBlockInfo, { + * someFloat: 12.3, + * someVec2: [1, 2], + * someVec3Array: [1, 2, 3, 4, 5, 6], + * someInt: 5, + * } + * + * Arrays can be JavaScript arrays or typed arrays + * + * You can also fill out structure and array values either via + * shortcut. Example + * + * // -- in shader -- + * struct Light { + * float intensity; + * vec4 color; + * float nearFar[2]; + * }; + * uniform Lights { + * Light lights[2]; + * }; + * + * // in JavaScript + * + * twgl.setBlockUniforms(someBlockInfo, { + * lights: [ + * { intensity: 5.0, color: [1, 0, 0, 1], nearFar[0.1, 10] }, + * { intensity: 2.0, color: [0, 0, 1, 1], nearFar[0.2, 15] }, + * ], + * }); + * + * or the more traditional way + * + * twgl.setBlockUniforms(someBlockInfo, { + * "lights[0].intensity": 5.0, + * "lights[0].color": [1, 0, 0, 1], + * "lights[0].nearFar": [0.1, 10], + * "lights[1].intensity": 2.0, + * "lights[1].color": [0, 0, 1, 1], + * "lights[1].nearFar": [0.2, 15], + * }); + * + * You can also specify partial paths + * + * twgl.setBlockUniforms(someBlockInfo, { + * 'lights[1]': { intensity: 5.0, color: [1, 0, 0, 1], nearFar[0.2, 15] }, + * }); + * + * But you can not specify leaf array indices. + * + * twgl.setBlockUniforms(someBlockInfo, { + * 'lights[1].nearFar[1]': 15, // BAD! nearFar is a leaf + * 'lights[1].nearFar': [0.2, 15], // GOOD + * }); + * + * **IMPORTANT!**, packing in a UniformBlock is unintuitive. + * For example the actual layout of `someVec3Array` above in memory + * is `1, 2, 3, unused, 4, 5, 6, unused`. twgl takes in 6 values + * as shown about and copies them, skipping the padding. This might + * be confusing if you're already familiar with Uniform blocks. + * + * If you want to deal with the padding yourself you can access the array + * buffer views directly. eg: + * + * someBlockInfo.someVec3Array.set([1, 2, 3, 0, 4, 5, 6, 0]); + * + * Any name that doesn't match will be ignored + * @memberOf module:twgl/programs + */ +function setBlockUniforms(uniformBlockInfo, values) { + const setters = uniformBlockInfo.setters; + for (const name in values) { + const setter = setters[name]; + if (setter) { + const value = values[name]; + setter(value); + } + } +} + +function setUniformTree(tree, values) { + for (const name in values) { + const prop = tree[name]; + if (typeof prop === 'function') { + prop(values[name]); + } else { + setUniformTree(tree[name], values[name]); + } + } +} + +/** + * Set uniforms and binds related textures. + * + * example: + * + * const programInfo = createProgramInfo( + * gl, ["some-vs", "some-fs"]); + * + * const tex1 = gl.createTexture(); + * const tex2 = gl.createTexture(); + * + * ... assume we setup the textures with data ... + * + * const uniforms = { + * u_someSampler: tex1, + * u_someOtherSampler: tex2, + * u_someColor: [1,0,0,1], + * u_somePosition: [0,1,1], + * u_someMatrix: [ + * 1,0,0,0, + * 0,1,0,0, + * 0,0,1,0, + * 0,0,0,0, + * ], + * }; + * + * gl.useProgram(programInfo.program); + * + * This will automatically bind the textures AND set the + * uniforms. + * + * twgl.setUniforms(programInfo, uniforms); + * + * For the example above it is equivalent to + * + * let texUnit = 0; + * gl.activeTexture(gl.TEXTURE0 + texUnit); + * gl.bindTexture(gl.TEXTURE_2D, tex1); + * gl.uniform1i(u_someSamplerLocation, texUnit++); + * gl.activeTexture(gl.TEXTURE0 + texUnit); + * gl.bindTexture(gl.TEXTURE_2D, tex2); + * gl.uniform1i(u_someSamplerLocation, texUnit++); + * gl.uniform4fv(u_someColorLocation, [1, 0, 0, 1]); + * gl.uniform3fv(u_somePositionLocation, [0, 1, 1]); + * gl.uniformMatrix4fv(u_someMatrix, false, [ + * 1,0,0,0, + * 0,1,0,0, + * 0,0,1,0, + * 0,0,0,0, + * ]); + * + * Note it is perfectly reasonable to call `setUniforms` multiple times. For example + * + * const uniforms = { + * u_someSampler: tex1, + * u_someOtherSampler: tex2, + * }; + * + * const moreUniforms { + * u_someColor: [1,0,0,1], + * u_somePosition: [0,1,1], + * u_someMatrix: [ + * 1,0,0,0, + * 0,1,0,0, + * 0,0,1,0, + * 0,0,0,0, + * ], + * }; + * + * twgl.setUniforms(programInfo, uniforms); + * twgl.setUniforms(programInfo, moreUniforms); + * + * You can also add WebGLSamplers to uniform samplers as in + * + * const uniforms = { + * u_someSampler: { + * texture: someWebGLTexture, + * sampler: someWebGLSampler, + * }, + * }; + * + * In which case both the sampler and texture will be bound to the + * same unit. + * + * @param {(module:twgl.ProgramInfo|Object.)} setters a `ProgramInfo` as returned from `createProgramInfo` or the setters returned from + * `createUniformSetters`. + * @param {Object.} values an object with values for the + * uniforms. + * You can pass multiple objects by putting them in an array or by calling with more arguments.For example + * + * const sharedUniforms = { + * u_fogNear: 10, + * u_projection: ... + * ... + * }; + * + * const localUniforms = { + * u_world: ... + * u_diffuseColor: ... + * }; + * + * twgl.setUniforms(programInfo, sharedUniforms, localUniforms); + * + * // is the same as + * + * twgl.setUniforms(programInfo, [sharedUniforms, localUniforms]); + * + * // is the same as + * + * twgl.setUniforms(programInfo, sharedUniforms); + * twgl.setUniforms(programInfo, localUniforms}; + * + * You can also fill out structure and array values either via + * shortcut. Example + * + * // -- in shader -- + * struct Light { + * float intensity; + * vec4 color; + * float nearFar[2]; + * }; + * uniform Light lights[2]; + * + * // in JavaScript + * + * twgl.setUniforms(programInfo, { + * lights: [ + * { intensity: 5.0, color: [1, 0, 0, 1], nearFar[0.1, 10] }, + * { intensity: 2.0, color: [0, 0, 1, 1], nearFar[0.2, 15] }, + * ], + * }); + * + * or the more traditional way + * + * twgl.setUniforms(programInfo, { + * "lights[0].intensity": 5.0, + * "lights[0].color": [1, 0, 0, 1], + * "lights[0].nearFar": [0.1, 10], + * "lights[1].intensity": 2.0, + * "lights[1].color": [0, 0, 1, 1], + * "lights[1].nearFar": [0.2, 15], + * }); + * + * You can also specify partial paths + * + * twgl.setUniforms(programInfo, { + * 'lights[1]': { intensity: 5.0, color: [1, 0, 0, 1], nearFar[0.2, 15] }, + * }); + * + * But you can not specify leaf array indices + * + * twgl.setUniforms(programInfo, { + * 'lights[1].nearFar[1]': 15, // BAD! nearFar is a leaf + * 'lights[1].nearFar': [0.2, 15], // GOOD + * }); + * + * @memberOf module:twgl/programs + */ +function setUniforms(setters, ...args) { // eslint-disable-line + const actualSetters = setters.uniformSetters || setters; + const numArgs = args.length; + for (let aNdx = 0; aNdx < numArgs; ++aNdx) { + const values = args[aNdx]; + if (Array.isArray(values)) { + const numValues = values.length; + for (let ii = 0; ii < numValues; ++ii) { + setUniforms(actualSetters, values[ii]); + } + } else { + for (const name in values) { + const setter = actualSetters[name]; + if (setter) { + setter(values[name]); + } + } + } + } +} + +/** + * Alias for `setUniforms` + * @function + * @param {(module:twgl.ProgramInfo|Object.)} setters a `ProgramInfo` as returned from `createProgramInfo` or the setters returned from + * `createUniformSetters`. + * @param {Object.} values an object with values for the + * @memberOf module:twgl/programs + */ +const setUniformsAndBindTextures = setUniforms; + +/** + * Creates setter functions for all attributes of a shader + * program. You can pass this to {@link module:twgl.setBuffersAndAttributes} to set all your buffers and attributes. + * + * @see {@link module:twgl.setAttributes} for example + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {WebGLProgram} program the program to create setters for. + * @return {Object.} an object with a setter for each attribute by name. + * @memberOf module:twgl/programs + */ +function createAttributeSetters(gl, program) { + const attribSetters = { + }; + + const numAttribs = gl.getProgramParameter(program, ACTIVE_ATTRIBUTES); + for (let ii = 0; ii < numAttribs; ++ii) { + const attribInfo = gl.getActiveAttrib(program, ii); + if (isBuiltIn(attribInfo)) { + continue; + } + const index = gl.getAttribLocation(program, attribInfo.name); + const typeInfo = attrTypeMap[attribInfo.type]; + const setter = typeInfo.setter(gl, index, typeInfo); + setter.location = index; + attribSetters[attribInfo.name] = setter; + } + + return attribSetters; +} + +/** + * Sets attributes and binds buffers (deprecated... use {@link module:twgl.setBuffersAndAttributes}) + * + * Example: + * + * const program = createProgramFromScripts( + * gl, ["some-vs", "some-fs"); + * + * const attribSetters = createAttributeSetters(program); + * + * const positionBuffer = gl.createBuffer(); + * const texcoordBuffer = gl.createBuffer(); + * + * const attribs = { + * a_position: {buffer: positionBuffer, numComponents: 3}, + * a_texcoord: {buffer: texcoordBuffer, numComponents: 2}, + * }; + * + * gl.useProgram(program); + * + * This will automatically bind the buffers AND set the + * attributes. + * + * setAttributes(attribSetters, attribs); + * + * Properties of attribs. For each attrib you can add + * properties: + * + * * type: the type of data in the buffer. Default = gl.FLOAT + * * normalize: whether or not to normalize the data. Default = false + * * stride: the stride. Default = 0 + * * offset: offset into the buffer. Default = 0 + * * divisor: the divisor for instances. Default = undefined + * + * For example if you had 3 value float positions, 2 value + * float texcoord and 4 value uint8 colors you'd setup your + * attribs like this + * + * const attribs = { + * a_position: {buffer: positionBuffer, numComponents: 3}, + * a_texcoord: {buffer: texcoordBuffer, numComponents: 2}, + * a_color: { + * buffer: colorBuffer, + * numComponents: 4, + * type: gl.UNSIGNED_BYTE, + * normalize: true, + * }, + * }; + * + * @param {Object.} setters Attribute setters as returned from createAttributeSetters + * @param {Object.} buffers AttribInfos mapped by attribute name. + * @memberOf module:twgl/programs + * @deprecated use {@link module:twgl.setBuffersAndAttributes} + * @private + */ +function setAttributes(setters, buffers) { + for (const name in buffers) { + const setter = setters[name]; + if (setter) { + setter(buffers[name]); + } + } +} + +/** + * Sets attributes and buffers including the `ELEMENT_ARRAY_BUFFER` if appropriate + * + * Example: + * + * const programInfo = createProgramInfo( + * gl, ["some-vs", "some-fs"); + * + * const arrays = { + * position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], }, + * texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], }, + * }; + * + * const bufferInfo = createBufferInfoFromArrays(gl, arrays); + * + * gl.useProgram(programInfo.program); + * + * This will automatically bind the buffers AND set the + * attributes. + * + * setBuffersAndAttributes(gl, programInfo, bufferInfo); + * + * For the example above it is equivalent to + * + * gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + * gl.enableVertexAttribArray(a_positionLocation); + * gl.vertexAttribPointer(a_positionLocation, 3, gl.FLOAT, false, 0, 0); + * gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); + * gl.enableVertexAttribArray(a_texcoordLocation); + * gl.vertexAttribPointer(a_texcoordLocation, 4, gl.FLOAT, false, 0, 0); + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext. + * @param {(module:twgl.ProgramInfo|Object.)} setters A `ProgramInfo` as returned from {@link module:twgl.createProgramInfo} or Attribute setters as returned from {@link module:twgl.createAttributeSetters} + * @param {(module:twgl.BufferInfo|module:twgl.VertexArrayInfo)} buffers a `BufferInfo` as returned from {@link module:twgl.createBufferInfoFromArrays}. + * or a `VertexArrayInfo` as returned from {@link module:twgl.createVertexArrayInfo} + * @memberOf module:twgl/programs + */ +function setBuffersAndAttributes(gl, programInfo, buffers) { + if (buffers.vertexArrayObject) { + gl.bindVertexArray(buffers.vertexArrayObject); + } else { + setAttributes(programInfo.attribSetters || programInfo, buffers.attribs); + if (buffers.indices) { + gl.bindBuffer(ELEMENT_ARRAY_BUFFER$1, buffers.indices); + } + } +} + +/** + * @typedef {Object} ProgramInfo + * @property {WebGLProgram} program A shader program + * @property {Object} uniformSetters object of setters as returned from createUniformSetters, + * @property {Object} attribSetters object of setters as returned from createAttribSetters, + * @property {module:twgl.UniformBlockSpec} [uniformBlockSpec] a uniform block spec for making UniformBlockInfos with createUniformBlockInfo etc.. + * @property {Object} [transformFeedbackInfo] info for transform feedbacks + * @memberOf module:twgl + */ + +/** + * Creates a ProgramInfo from an existing program. + * + * A ProgramInfo contains + * + * programInfo = { + * program: WebGLProgram, + * uniformSetters: object of setters as returned from createUniformSetters, + * attribSetters: object of setters as returned from createAttribSetters, + * } + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {WebGLProgram} program an existing WebGLProgram. + * @return {module:twgl.ProgramInfo} The created ProgramInfo. + * @memberOf module:twgl/programs + */ +function createProgramInfoFromProgram(gl, program) { + const uniformSetters = createUniformSetters(gl, program); + const attribSetters = createAttributeSetters(gl, program); + const programInfo = { + program, + uniformSetters, + attribSetters, + }; + + if (isWebGL2(gl)) { + programInfo.uniformBlockSpec = createUniformBlockSpecFromProgram(gl, program); + programInfo.transformFeedbackInfo = createTransformFeedbackInfo(gl, program); + } + + return programInfo; +} + +const notIdRE = /\s|{|}|;/; + +/** + * Creates a ProgramInfo from 2 sources. + * + * A ProgramInfo contains + * + * programInfo = { + * program: WebGLProgram, + * uniformSetters: object of setters as returned from createUniformSetters, + * attribSetters: object of setters as returned from createAttribSetters, + * } + * + * NOTE: There are 4 signatures for this function + * + * twgl.createProgramInfo(gl, [vs, fs], options); + * twgl.createProgramInfo(gl, [vs, fs], opt_errFunc); + * twgl.createProgramInfo(gl, [vs, fs], opt_attribs, opt_errFunc); + * twgl.createProgramInfo(gl, [vs, fs], opt_attribs, opt_locations, opt_errFunc); + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {string[]} shaderSources Array of sources for the + * shaders or ids. The first is assumed to be the vertex shader, + * the second the fragment shader. + * @param {module:twgl.ProgramOptions|string[]|module:twgl.ErrorCallback} [opt_attribs] Options for the program or an array of attribs names or an error callback. Locations will be assigned by index if not passed in + * @param {number[]} [opt_locations|module:twgl.ErrorCallback] The locations for the. A parallel array to opt_attribs letting you assign locations or an error callback. + * @param {module:twgl.ErrorCallback} [opt_errorCallback] callback for errors. By default it just prints an error to the console + * on error. If you want something else pass an callback. It's passed an error message. + * @return {module:twgl.ProgramInfo?} The created ProgramInfo or null if it failed to link or compile + * @memberOf module:twgl/programs + */ +function createProgramInfo( + gl, shaderSources, opt_attribs, opt_locations, opt_errorCallback) { + const progOptions = getProgramOptions(opt_attribs, opt_locations, opt_errorCallback); + const errors = []; + shaderSources = shaderSources.map(function(source) { + // Lets assume if there is no \n it's an id + if (!notIdRE.test(source)) { + const script = getElementById(source); + if (!script) { + const err = `no element with id: ${source}`; + progOptions.errorCallback(err); + errors.push(err); + } else { + source = script.text; + } + } + return source; + }); + + if (errors.length) { + return reportError(progOptions, ''); + } + + const origCallback = progOptions.callback; + if (origCallback) { + progOptions.callback = (err, program) => { + origCallback(err, err ? undefined : createProgramInfoFromProgram(gl, program)); + }; + } + + const program = createProgramFromSources(gl, shaderSources, progOptions); + if (!program) { + return null; + } + + return createProgramInfoFromProgram(gl, program); +} + +function checkAllPrograms(gl, programs, programSpecs, noDeleteShadersSet, programOptions) { + // check errors for everything. + for (const [name, program] of Object.entries(programs)) { + const options = {...programOptions}; + const spec = programSpecs[name]; + if (!Array.isArray(spec)) { + Object.assign(options, spec); + } + const errors = getProgramErrors(gl, program, options.errorCallback); + if (errors) { + // delete everything we created + for (const program of Object.values(programs)) { + const shaders = gl.getAttachedShaders(program); + gl.deleteProgram(program); + for (const shader of shaders) { + // Don't delete it if we didn't create it. + if (!noDeleteShadersSet.has(shader)) { + gl.deleteShader(shader); + } + } + } + return errors; + } + } + + return undefined; +} + +/** + * Creates multiple programs + * + * Note: the reason this function exists is because the fastest way to create multiple + * programs in WebGL is to create and compile all shaders and link all programs and only + * afterwards check if they succeeded. In that way, giving all your shaders + * + * @see {@link module:twgl.createProgram} + * + * Example: + * + * const programs = twgl.createPrograms(gl, { + * lambert: [lambertVS, lambertFS], + * phong: [phongVS, phoneFS], + * particles: { + * shaders: [particlesVS, particlesFS], + * transformFeedbackVaryings: ['position', 'velocity'], + * }, + * }); + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {Object.} programSpecs An object of ProgramSpecs, one per program. + * @param {module:twgl.ProgramOptions} [programOptions] options to apply to all programs + * @return {Object.?} the created programInfos by name + */ +function createPrograms(gl, programSpecs, programOptions = {}) { + // Remember existing shaders so that if there is an error we don't delete them + const noDeleteShadersSet = new Set(); + + // compile and link everything + const programs = Object.fromEntries(Object.entries(programSpecs).map(([name, spec]) => { + const options = {...programOptions}; + const shaders = Array.isArray(spec) ? spec : spec.shaders; + if (!Array.isArray(spec)) { + Object.assign(options, spec); + } + shaders.forEach(noDeleteShadersSet.add, noDeleteShadersSet); + return [name, createProgramNoCheck(gl, shaders, options)]; + })); + + if (programOptions.callback) { + waitForAllProgramsLinkCompletionAsync(gl, programs).then(() => { + const errors = checkAllPrograms(gl, programs, programSpecs, noDeleteShadersSet, programOptions); + programOptions.callback(errors, errors ? undefined : programs); + }); + return undefined; + } + + const errors = checkAllPrograms(gl, programs, programSpecs, noDeleteShadersSet, programOptions); + return errors ? undefined : programs; +} + +/** + * Creates multiple programInfos + * + * Note: the reason this function exists is because the fastest way to create multiple + * programs in WebGL is to create and compile all shaders and link all programs and only + * afterwards check if they succeeded. In that way, giving all your shaders + * + * @see {@link module:twgl.createProgramInfo} + * + * Examples: + * + * const programInfos = twgl.createProgramInfos(gl, { + * lambert: [lambertVS, lambertFS], + * phong: [phongVS, phoneFS], + * particles: { + * shaders: [particlesVS, particlesFS], + * transformFeedbackVaryings: ['position', 'velocity'], + * }, + * }); + * + * or + * + * const {lambert, phong, particles} = twgl.createProgramInfos(gl, { + * lambert: [lambertVS, lambertFS], + * phong: [phongVS, phoneFS], + * particles: { + * shaders: [particlesVS, particlesFS], + * transformFeedbackVaryings: ['position', 'velocity'], + * }, + * }); + * + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {Object.} programSpecs An object of ProgramSpecs, one per program. + * @param {module:twgl.ProgramOptions} [programOptions] options to apply to all programs + * @return {Object.?} the created programInfos by name + */ +function createProgramInfos(gl, programSpecs, programOptions) { + programOptions = getProgramOptions(programOptions); + + function createProgramInfosForPrograms(gl, programs) { + return Object.fromEntries(Object.entries(programs).map(([name, program]) => + [name, createProgramInfoFromProgram(gl, program)] + )); + } + + const origCallback = programOptions.callback; + if (origCallback) { + programOptions.callback = (err, programs) => { + origCallback(err, err ? undefined : createProgramInfosForPrograms(gl, programs)); + }; + } + + const programs = createPrograms(gl, programSpecs, programOptions); + if (origCallback || !programs) { + return undefined; + } + + return createProgramInfosForPrograms(gl, programs); +} + +/** + * Creates multiple programs asynchronously + * + * @see {@link module:twgl.createProgramAsync} + * + * Example: + * + * const programs = await twgl.createProgramsAsync(gl, { + * lambert: [lambertVS, lambertFS], + * phong: [phongVS, phoneFS], + * particles: { + * shaders: [particlesVS, particlesFS], + * transformFeedbackVaryings: ['position', 'velocity'], + * }, + * }); + * + * @function + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {Object.} programSpecs An object of ProgramSpecs, one per program. + * @param {module:twgl.ProgramOptions} [programOptions] options to apply to all programs + * @return {Object.?} the created programInfos by name + */ +const createProgramsAsync = wrapCallbackFnToAsyncFn(createPrograms); + +/** + * Creates multiple programInfos asynchronously + * + * @see {@link module:twgl.createProgramInfoAsync} + * + * Example: + * + * const programInfos = await twgl.createProgramInfosAsync(gl, { + * lambert: [lambertVS, lambertFS], + * phong: [phongVS, phoneFS], + * particles: { + * shaders: [particlesVS, particlesFS], + * transformFeedbackVaryings: ['position', 'velocity'], + * }, + * }); + * + * @function + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {Object.} programSpecs An object of ProgramSpecs, one per program. + * @param {module:twgl.ProgramOptions} [programOptions] options to apply to all programs + * @return {Promise>} the created programInfos by name + */ +const createProgramInfosAsync = wrapCallbackFnToAsyncFn(createProgramInfos); + +var programs = /*#__PURE__*/Object.freeze({ + __proto__: null, + createAttributeSetters: createAttributeSetters, + createProgram: createProgram, + createProgramAsync: createProgramAsync, + createPrograms: createPrograms, + createProgramsAsync: createProgramsAsync, + createProgramFromScripts: createProgramFromScripts, + createProgramFromSources: createProgramFromSources, + createProgramInfo: createProgramInfo, + createProgramInfoAsync: createProgramInfoAsync, + createProgramInfos: createProgramInfos, + createProgramInfosAsync: createProgramInfosAsync, + createProgramInfoFromProgram: createProgramInfoFromProgram, + createUniformSetters: createUniformSetters, + createUniformBlockSpecFromProgram: createUniformBlockSpecFromProgram, + createUniformBlockInfoFromProgram: createUniformBlockInfoFromProgram, + createUniformBlockInfo: createUniformBlockInfo, + createTransformFeedback: createTransformFeedback, + createTransformFeedbackInfo: createTransformFeedbackInfo, + bindTransformFeedbackInfo: bindTransformFeedbackInfo, + setAttributes: setAttributes, + setBuffersAndAttributes: setBuffersAndAttributes, + setUniforms: setUniforms, + setUniformsAndBindTextures: setUniformsAndBindTextures, + setUniformBlock: setUniformBlock, + setBlockUniforms: setBlockUniforms, + bindUniformBlock: bindUniformBlock +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +const TRIANGLES = 0x0004; +const UNSIGNED_SHORT = 0x1403; + +/** + * Drawing related functions + * + * For backward compatibility they are available at both `twgl.draw` and `twgl` + * itself + * + * See {@link module:twgl} for core functions + * + * @module twgl/draw + */ + +/** + * Calls `gl.drawElements` or `gl.drawArrays`, whichever is appropriate + * + * normally you'd call `gl.drawElements` or `gl.drawArrays` yourself + * but calling this means if you switch from indexed data to non-indexed + * data you don't have to remember to update your draw call. + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @param {(module:twgl.BufferInfo|module:twgl.VertexArrayInfo)} bufferInfo A BufferInfo as returned from {@link module:twgl.createBufferInfoFromArrays} or + * a VertexArrayInfo as returned from {@link module:twgl.createVertexArrayInfo} + * @param {number} [type] eg (gl.TRIANGLES, gl.LINES, gl.POINTS, gl.TRIANGLE_STRIP, ...). Defaults to `gl.TRIANGLES` + * @param {number} [count] An optional count. Defaults to bufferInfo.numElements + * @param {number} [offset] An optional offset. Defaults to 0. + * @param {number} [instanceCount] An optional instanceCount. if set then `drawArraysInstanced` or `drawElementsInstanced` will be called + * @memberOf module:twgl/draw + */ +function drawBufferInfo(gl, bufferInfo, type, count, offset, instanceCount) { + type = type === undefined ? TRIANGLES : type; + const indices = bufferInfo.indices; + const elementType = bufferInfo.elementType; + const numElements = count === undefined ? bufferInfo.numElements : count; + offset = offset === undefined ? 0 : offset; + if (elementType || indices) { + if (instanceCount !== undefined) { + gl.drawElementsInstanced(type, numElements, elementType === undefined ? UNSIGNED_SHORT : bufferInfo.elementType, offset, instanceCount); + } else { + gl.drawElements(type, numElements, elementType === undefined ? UNSIGNED_SHORT : bufferInfo.elementType, offset); + } + } else { + if (instanceCount !== undefined) { + gl.drawArraysInstanced(type, offset, numElements, instanceCount); + } else { + gl.drawArrays(type, offset, numElements); + } + } +} + +/** + * A DrawObject is useful for putting objects in to an array and passing them to {@link module:twgl.drawObjectList}. + * + * You need either a `BufferInfo` or a `VertexArrayInfo`. + * + * @typedef {Object} DrawObject + * @property {boolean} [active] whether or not to draw. Default = `true` (must be `false` to be not true). In other words `undefined` = `true` + * @property {number} [type] type to draw eg. `gl.TRIANGLES`, `gl.LINES`, etc... + * @property {module:twgl.ProgramInfo} programInfo A ProgramInfo as returned from {@link module:twgl.createProgramInfo} + * @property {module:twgl.BufferInfo} [bufferInfo] A BufferInfo as returned from {@link module:twgl.createBufferInfoFromArrays} + * @property {module:twgl.VertexArrayInfo} [vertexArrayInfo] A VertexArrayInfo as returned from {@link module:twgl.createVertexArrayInfo} + * @property {Object} uniforms The values for the uniforms. + * You can pass multiple objects by putting them in an array. For example + * + * var sharedUniforms = { + * u_fogNear: 10, + * u_projection: ... + * ... + * }; + * + * var localUniforms = { + * u_world: ... + * u_diffuseColor: ... + * }; + * + * var drawObj = { + * ... + * uniforms: [sharedUniforms, localUniforms], + * }; + * + * @property {number} [offset] the offset to pass to `gl.drawArrays` or `gl.drawElements`. Defaults to 0. + * @property {number} [count] the count to pass to `gl.drawArrays` or `gl.drawElements`. Defaults to bufferInfo.numElements. + * @property {number} [instanceCount] the number of instances. Defaults to undefined. + * @memberOf module:twgl + */ + +/** + * Draws a list of objects + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @param {DrawObject[]} objectsToDraw an array of objects to draw. + * @memberOf module:twgl/draw + */ +function drawObjectList(gl, objectsToDraw) { + let lastUsedProgramInfo = null; + let lastUsedBufferInfo = null; + + objectsToDraw.forEach(function(object) { + if (object.active === false) { + return; + } + + const programInfo = object.programInfo; + const bufferInfo = object.vertexArrayInfo || object.bufferInfo; + let bindBuffers = false; + const type = object.type === undefined ? TRIANGLES : object.type; + + if (programInfo !== lastUsedProgramInfo) { + lastUsedProgramInfo = programInfo; + gl.useProgram(programInfo.program); + + // We have to rebind buffers when changing programs because we + // only bind buffers the program uses. So if 2 programs use the same + // bufferInfo but the 1st one uses only positions the when the + // we switch to the 2nd one some of the attributes will not be on. + bindBuffers = true; + } + + // Setup all the needed attributes. + if (bindBuffers || bufferInfo !== lastUsedBufferInfo) { + if (lastUsedBufferInfo && lastUsedBufferInfo.vertexArrayObject && !bufferInfo.vertexArrayObject) { + gl.bindVertexArray(null); + } + lastUsedBufferInfo = bufferInfo; + setBuffersAndAttributes(gl, programInfo, bufferInfo); + } + + // Set the uniforms. + setUniforms(programInfo, object.uniforms); + + // Draw + drawBufferInfo(gl, bufferInfo, type, object.count, object.offset, object.instanceCount); + }); + + if (lastUsedBufferInfo && lastUsedBufferInfo.vertexArrayObject) { + gl.bindVertexArray(null); + } +} + +var draw = /*#__PURE__*/Object.freeze({ + __proto__: null, + drawBufferInfo: drawBufferInfo, + drawObjectList: drawObjectList +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +const FRAMEBUFFER = 0x8d40; +const RENDERBUFFER = 0x8d41; +const TEXTURE_2D = 0x0de1; + +const UNSIGNED_BYTE = 0x1401; + +/* PixelFormat */ +const DEPTH_COMPONENT = 0x1902; +const RGBA = 0x1908; +const DEPTH_COMPONENT24 = 0x81a6; +const DEPTH_COMPONENT32F = 0x8cac; +const DEPTH24_STENCIL8 = 0x88f0; +const DEPTH32F_STENCIL8 = 0x8cad; + +/* Framebuffer Object. */ +const RGBA4 = 0x8056; +const RGB5_A1 = 0x8057; +const RGB565 = 0x8D62; +const DEPTH_COMPONENT16 = 0x81A5; +const STENCIL_INDEX = 0x1901; +const STENCIL_INDEX8 = 0x8D48; +const DEPTH_STENCIL = 0x84F9; +const COLOR_ATTACHMENT0 = 0x8CE0; +const DEPTH_ATTACHMENT = 0x8D00; +const STENCIL_ATTACHMENT = 0x8D20; +const DEPTH_STENCIL_ATTACHMENT = 0x821A; + +/* TextureWrapMode */ +const CLAMP_TO_EDGE = 0x812F; + +/* TextureMagFilter */ +const LINEAR = 0x2601; + +/** + * The options for a framebuffer attachment. + * + * Note: For a `format` that is a texture include all the texture + * options from {@link module:twgl.TextureOptions} for example + * `min`, `mag`, `clamp`, etc... Note that unlike {@link module:twgl.TextureOptions} + * `auto` defaults to `false` for attachment textures but `min` and `mag` default + * to `gl.LINEAR` and `wrap` defaults to `CLAMP_TO_EDGE` + * + * @typedef {Object} AttachmentOptions + * @property {number} [attachmentPoint] The attachment point. Defaults + * to `gl.COLOR_ATTACHMENT0 + ndx` unless type is a depth or stencil type + * then it's gl.DEPTH_ATTACHMENT or `gl.DEPTH_STENCIL_ATTACHMENT` depending + * on the format or attachment type. + * @property {number} [format] The format. If one of `gl.RGBA4`, + * `gl.RGB565`, `gl.RGB5_A1`, `gl.DEPTH_COMPONENT16`, + * `gl.STENCIL_INDEX8` or `gl.DEPTH_STENCIL` then will create a + * renderbuffer. Otherwise will create a texture. Default = `gl.RGBA` + * @property {number} [type] The type. Used for texture. Default = `gl.UNSIGNED_BYTE`. + * @property {number} [target] The texture target for `gl.framebufferTexture2D`. + * Defaults to `gl.TEXTURE_2D`. Set to appropriate face for cube maps. + * @property {number} [samples] The number of samples. Default = 1 + * @property {number} [level] level for `gl.framebufferTexture2D`. Defaults to 0. + * @property {number} [layer] layer for `gl.framebufferTextureLayer`. Defaults to undefined. + * If set then `gl.framebufferTextureLayer` is called, if not then `gl.framebufferTexture2D` + * @property {(WebGLRenderbuffer | WebGLTexture)} [attachment] An existing renderbuffer or texture. + * If provided will attach this Object. This allows you to share + * attachments across framebuffers. + * @memberOf module:twgl + * @mixes module:twgl.TextureOptions + */ + +const defaultAttachments = [ + { format: RGBA, type: UNSIGNED_BYTE, min: LINEAR, wrap: CLAMP_TO_EDGE, }, + { format: DEPTH_STENCIL, }, +]; + +const attachmentsByFormat = {}; +attachmentsByFormat[DEPTH_STENCIL] = DEPTH_STENCIL_ATTACHMENT; +attachmentsByFormat[STENCIL_INDEX] = STENCIL_ATTACHMENT; +attachmentsByFormat[STENCIL_INDEX8] = STENCIL_ATTACHMENT; +attachmentsByFormat[DEPTH_COMPONENT] = DEPTH_ATTACHMENT; +attachmentsByFormat[DEPTH_COMPONENT16] = DEPTH_ATTACHMENT; +attachmentsByFormat[DEPTH_COMPONENT24] = DEPTH_ATTACHMENT; +attachmentsByFormat[DEPTH_COMPONENT32F] = DEPTH_ATTACHMENT; +attachmentsByFormat[DEPTH24_STENCIL8] = DEPTH_STENCIL_ATTACHMENT; +attachmentsByFormat[DEPTH32F_STENCIL8] = DEPTH_STENCIL_ATTACHMENT; + +function getAttachmentPointForFormat(format, internalFormat) { + return attachmentsByFormat[format] || attachmentsByFormat[internalFormat]; +} + +const renderbufferFormats = {}; +renderbufferFormats[RGBA4] = true; +renderbufferFormats[RGB5_A1] = true; +renderbufferFormats[RGB565] = true; +renderbufferFormats[DEPTH_STENCIL] = true; +renderbufferFormats[DEPTH_COMPONENT16] = true; +renderbufferFormats[STENCIL_INDEX] = true; +renderbufferFormats[STENCIL_INDEX8] = true; + +function isRenderbufferFormat(format) { + return renderbufferFormats[format]; +} + +const MAX_COLOR_ATTACHMENT_POINTS = 32; // even an 3090 only supports 8 but WebGL/OpenGL ES define constants for 32 + +function isColorAttachmentPoint(attachmentPoint) { + return attachmentPoint >= COLOR_ATTACHMENT0 && attachmentPoint < COLOR_ATTACHMENT0 + MAX_COLOR_ATTACHMENT_POINTS; +} + +/** + * @typedef {Object} FramebufferInfo + * @property {WebGLFramebuffer} framebuffer The WebGLFramebuffer for this framebufferInfo + * @property {Array.<(WebGLRenderbuffer | WebGLTexture)>} attachments The created attachments in the same order as passed in to {@link module:twgl.createFramebufferInfo}. + * @property {number} width The width of the framebuffer and its attachments + * @property {number} height The width of the framebuffer and its attachments + * @memberOf module:twgl + */ + +/** + * Creates a framebuffer and attachments. + * + * This returns a {@link module:twgl.FramebufferInfo} because it needs to return the attachments as well as the framebuffer. + * It also leaves the framebuffer it just created as the currently bound `FRAMEBUFFER`. + * Note: If this is WebGL2 or if you called {@link module:twgl.addExtensionsToContext} then it will set the drawBuffers + * to `[COLOR_ATTACHMENT0, COLOR_ATTACHMENT1, ...]` for how ever many color attachments were created. + * + * The simplest usage + * + * // create an RGBA/UNSIGNED_BYTE texture and DEPTH_STENCIL renderbuffer + * const fbi = twgl.createFramebufferInfo(gl); + * + * More complex usage + * + * // create an RGB565 renderbuffer and a STENCIL_INDEX8 renderbuffer + * const attachments = [ + * { format: RGB565, mag: NEAREST }, + * { format: STENCIL_INDEX8 }, + * ] + * const fbi = twgl.createFramebufferInfo(gl, attachments); + * + * Passing in a specific size + * + * const width = 256; + * const height = 256; + * const fbi = twgl.createFramebufferInfo(gl, attachments, width, height); + * + * **Note!!** It is up to you to check if the framebuffer is renderable by calling `gl.checkFramebufferStatus`. + * [WebGL1 only guarantees 3 combinations of attachments work](https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.6). + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {module:twgl.AttachmentOptions[]} [attachments] which attachments to create. If not provided the default is a framebuffer with an + * `RGBA`, `UNSIGNED_BYTE` texture `COLOR_ATTACHMENT0` and a `DEPTH_STENCIL` renderbuffer `DEPTH_STENCIL_ATTACHMENT`. + * @param {number} [width] the width for the attachments. Default = size of drawingBuffer + * @param {number} [height] the height for the attachments. Default = size of drawingBuffer + * @return {module:twgl.FramebufferInfo} the framebuffer and attachments. + * @memberOf module:twgl/framebuffers + */ +function createFramebufferInfo(gl, attachments, width, height) { + const target = FRAMEBUFFER; + const fb = gl.createFramebuffer(); + gl.bindFramebuffer(target, fb); + width = width || gl.drawingBufferWidth; + height = height || gl.drawingBufferHeight; + attachments = attachments || defaultAttachments; + const usedColorAttachmentsPoints = []; + const framebufferInfo = { + framebuffer: fb, + attachments: [], + width: width, + height: height, + }; + + attachments.forEach(function(attachmentOptions, i) { + let attachment = attachmentOptions.attachment; + const samples = attachmentOptions.samples; + const format = attachmentOptions.format; + let attachmentPoint = attachmentOptions.attachmentPoint || getAttachmentPointForFormat(format, attachmentOptions.internalFormat); + if (!attachmentPoint) { + attachmentPoint = COLOR_ATTACHMENT0 + i; + } + if (isColorAttachmentPoint(attachmentPoint)) { + usedColorAttachmentsPoints.push(attachmentPoint); + } + if (!attachment) { + if (samples !== undefined || isRenderbufferFormat(format)) { + attachment = gl.createRenderbuffer(); + gl.bindRenderbuffer(RENDERBUFFER, attachment); + if (samples > 1) { + gl.renderbufferStorageMultisample(RENDERBUFFER, samples, format, width, height); + } else { + gl.renderbufferStorage(RENDERBUFFER, format, width, height); + } + } else { + const textureOptions = Object.assign({}, attachmentOptions); + textureOptions.width = width; + textureOptions.height = height; + if (textureOptions.auto === undefined) { + textureOptions.auto = false; + textureOptions.min = textureOptions.min || textureOptions.minMag || LINEAR; + textureOptions.mag = textureOptions.mag || textureOptions.minMag || LINEAR; + textureOptions.wrapS = textureOptions.wrapS || textureOptions.wrap || CLAMP_TO_EDGE; + textureOptions.wrapT = textureOptions.wrapT || textureOptions.wrap || CLAMP_TO_EDGE; + } + attachment = createTexture(gl, textureOptions); + } + } + if (isRenderbuffer(gl, attachment)) { + gl.framebufferRenderbuffer(target, attachmentPoint, RENDERBUFFER, attachment); + } else if (isTexture(gl, attachment)) { + if (attachmentOptions.layer !== undefined) { + gl.framebufferTextureLayer( + target, + attachmentPoint, + attachment, + attachmentOptions.level || 0, + attachmentOptions.layer); + } else { + gl.framebufferTexture2D( + target, + attachmentPoint, + attachmentOptions.target || TEXTURE_2D, + attachment, + attachmentOptions.level || 0); + } + } else { + throw new Error('unknown attachment type'); + } + framebufferInfo.attachments.push(attachment); + }); + if (gl.drawBuffers) { + gl.drawBuffers(usedColorAttachmentsPoints); + } + return framebufferInfo; +} + +/** + * Resizes the attachments of a framebuffer. + * + * You need to pass in the same `attachments` as you passed in {@link module:twgl.createFramebufferInfo} + * because TWGL has no idea the format/type of each attachment. + * + * The simplest usage + * + * // create an RGBA/UNSIGNED_BYTE texture and DEPTH_STENCIL renderbuffer + * const fbi = twgl.createFramebufferInfo(gl); + * + * ... + * + * function render() { + * if (twgl.resizeCanvasToDisplaySize(gl.canvas)) { + * // resize the attachments + * twgl.resizeFramebufferInfo(gl, fbi); + * } + * + * More complex usage + * + * // create an RGB565 renderbuffer and a STENCIL_INDEX8 renderbuffer + * const attachments = [ + * { format: RGB565, mag: NEAREST }, + * { format: STENCIL_INDEX8 }, + * ] + * const fbi = twgl.createFramebufferInfo(gl, attachments); + * + * ... + * + * function render() { + * if (twgl.resizeCanvasToDisplaySize(gl.canvas)) { + * // resize the attachments to match + * twgl.resizeFramebufferInfo(gl, fbi, attachments); + * } + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {module:twgl.FramebufferInfo} framebufferInfo a framebufferInfo as returned from {@link module:twgl.createFramebufferInfo}. + * @param {module:twgl.AttachmentOptions[]} [attachments] the same attachments options as passed to {@link module:twgl.createFramebufferInfo}. + * @param {number} [width] the width for the attachments. Default = size of drawingBuffer + * @param {number} [height] the height for the attachments. Default = size of drawingBuffer + * @memberOf module:twgl/framebuffers + */ +function resizeFramebufferInfo(gl, framebufferInfo, attachments, width, height) { + width = width || gl.drawingBufferWidth; + height = height || gl.drawingBufferHeight; + framebufferInfo.width = width; + framebufferInfo.height = height; + attachments = attachments || defaultAttachments; + attachments.forEach(function(attachmentOptions, ndx) { + const attachment = framebufferInfo.attachments[ndx]; + const format = attachmentOptions.format; + const samples = attachmentOptions.samples; + if (samples !== undefined || isRenderbuffer(gl, attachment)) { + gl.bindRenderbuffer(RENDERBUFFER, attachment); + if (samples > 1) { + gl.renderbufferStorageMultisample(RENDERBUFFER, samples, format, width, height); + } else { + gl.renderbufferStorage(RENDERBUFFER, format, width, height); + } + } else if (isTexture(gl, attachment)) { + resizeTexture(gl, attachment, attachmentOptions, width, height); + } else { + throw new Error('unknown attachment type'); + } + }); +} + +/** + * Binds a framebuffer + * + * This function pretty much solely exists because I spent hours + * trying to figure out why something I wrote wasn't working only + * to realize I forget to set the viewport dimensions. + * My hope is this function will fix that. + * + * It is effectively the same as + * + * gl.bindFramebuffer(gl.FRAMEBUFFER, someFramebufferInfo.framebuffer); + * gl.viewport(0, 0, someFramebufferInfo.width, someFramebufferInfo.height); + * + * @param {WebGLRenderingContext} gl the WebGLRenderingContext + * @param {module:twgl.FramebufferInfo|null} [framebufferInfo] a framebufferInfo as returned from {@link module:twgl.createFramebufferInfo}. + * If falsy will bind the canvas. + * @param {number} [target] The target. If not passed `gl.FRAMEBUFFER` will be used. + * @memberOf module:twgl/framebuffers + */ + +function bindFramebufferInfo(gl, framebufferInfo, target) { + target = target || FRAMEBUFFER; + if (framebufferInfo) { + gl.bindFramebuffer(target, framebufferInfo.framebuffer); + gl.viewport(0, 0, framebufferInfo.width, framebufferInfo.height); + } else { + gl.bindFramebuffer(target, null); + gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + } +} + +var framebuffers = /*#__PURE__*/Object.freeze({ + __proto__: null, + bindFramebufferInfo: bindFramebufferInfo, + createFramebufferInfo: createFramebufferInfo, + resizeFramebufferInfo: resizeFramebufferInfo +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/** + * vertex array object related functions + * + * You should generally not need to use these functions. They are provided + * for those cases where you're doing something out of the ordinary + * and you need lower level access. + * + * For backward compatibility they are available at both `twgl.attributes` and `twgl` + * itself + * + * See {@link module:twgl} for core functions + * + * @module twgl/vertexArrays + */ + +const ELEMENT_ARRAY_BUFFER = 0x8893; + +/** + * @typedef {Object} VertexArrayInfo + * @property {number} numElements The number of elements to pass to `gl.drawArrays` or `gl.drawElements`. + * @property {number} [elementType] The type of indices `UNSIGNED_BYTE`, `UNSIGNED_SHORT` etc.. + * @property {WebGLVertexArrayObject} [vertexArrayObject] a vertex array object + * @memberOf module:twgl + */ + +/** + * Creates a VertexArrayInfo from a BufferInfo and one or more ProgramInfos + * + * This can be passed to {@link module:twgl.setBuffersAndAttributes} and to + * {@link module:twgl:drawBufferInfo}. + * + * > **IMPORTANT:** Vertex Array Objects are **not** a direct analog for a BufferInfo. Vertex Array Objects + * assign buffers to specific attributes at creation time. That means they can only be used with programs + * who's attributes use the same attribute locations for the same purposes. + * + * > Bind your attribute locations by passing an array of attribute names to {@link module:twgl.createProgramInfo} + * or use WebGL 2's GLSL ES 3's `layout(location = )` to make sure locations match. + * + * also + * + * > **IMPORTANT:** After calling twgl.setBuffersAndAttribute with a BufferInfo that uses a Vertex Array Object + * that Vertex Array Object will be bound. That means **ANY MANIPULATION OF ELEMENT_ARRAY_BUFFER or ATTRIBUTES** + * will affect the Vertex Array Object state. + * + * > Call `gl.bindVertexArray(null)` to get back manipulating the global attributes and ELEMENT_ARRAY_BUFFER. + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @param {module:twgl.ProgramInfo|module:twgl.ProgramInfo[]} programInfo a programInfo or array of programInfos + * @param {module:twgl.BufferInfo} bufferInfo BufferInfo as returned from createBufferInfoFromArrays etc... + * + * You need to make sure every attribute that will be used is bound. So for example assume shader 1 + * uses attributes A, B, C and shader 2 uses attributes A, B, D. If you only pass in the programInfo + * for shader 1 then only attributes A, B, and C will have their attributes set because TWGL doesn't + * now attribute D's location. + * + * So, you can pass in both shader 1 and shader 2's programInfo + * + * @return {module:twgl.VertexArrayInfo} The created VertexArrayInfo + * + * @memberOf module:twgl/vertexArrays + */ +function createVertexArrayInfo(gl, programInfos, bufferInfo) { + const vao = gl.createVertexArray(); + gl.bindVertexArray(vao); + if (!programInfos.length) { + programInfos = [programInfos]; + } + programInfos.forEach(function(programInfo) { + setBuffersAndAttributes(gl, programInfo, bufferInfo); + }); + gl.bindVertexArray(null); + return { + numElements: bufferInfo.numElements, + elementType: bufferInfo.elementType, + vertexArrayObject: vao, + }; +} + +/** + * Creates a vertex array object and then sets the attributes on it + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext to use. + * @param {Object.} setters Attribute setters as returned from createAttributeSetters + * @param {Object.} attribs AttribInfos mapped by attribute name. + * @param {WebGLBuffer} [indices] an optional ELEMENT_ARRAY_BUFFER of indices + * + * @return {WebGLVertexArrayObject|null} The created WebGLVertexArrayObject + * + * @memberOf module:twgl/vertexArrays + */ +function createVAOAndSetAttributes(gl, setters, attribs, indices) { + const vao = gl.createVertexArray(); + gl.bindVertexArray(vao); + setAttributes(setters, attribs); + if (indices) { + gl.bindBuffer(ELEMENT_ARRAY_BUFFER, indices); + } + // We unbind this because otherwise any change to ELEMENT_ARRAY_BUFFER + // like when creating buffers for other stuff will mess up this VAO's binding + gl.bindVertexArray(null); + return vao; +} + +/** + * Creates a vertex array object and then sets the attributes + * on it + * + * @param {WebGLRenderingContext} gl The WebGLRenderingContext + * to use. + * @param {Object.| module:twgl.ProgramInfo} programInfo as returned from createProgramInfo or Attribute setters as returned from createAttributeSetters + * @param {module:twgl.BufferInfo} bufferInfo BufferInfo as returned from createBufferInfoFromArrays etc... + * @param {WebGLBuffer} [indices] an optional ELEMENT_ARRAY_BUFFER of indices + * + * @return {WebGLVertexArrayObject|null} The created WebGLVertexArrayObject + * + * @memberOf module:twgl/vertexArrays + */ +function createVAOFromBufferInfo(gl, programInfo, bufferInfo) { + return createVAOAndSetAttributes(gl, programInfo.attribSetters || programInfo, bufferInfo.attribs, bufferInfo.indices); +} + +var vertexArrays = /*#__PURE__*/Object.freeze({ + __proto__: null, + createVertexArrayInfo: createVertexArrayInfo, + createVAOAndSetAttributes: createVAOAndSetAttributes, + createVAOFromBufferInfo: createVAOFromBufferInfo +}); + +/* + * Copyright 2019 Gregg Tavares + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +const defaults = { + addExtensionsToContext: true, +}; + +/** + * Various default settings for twgl. + * + * Note: You can call this any number of times. Example: + * + * twgl.setDefaults({ textureColor: [1, 0, 0, 1] }); + * twgl.setDefaults({ attribPrefix: 'a_' }); + * + * is equivalent to + * + * twgl.setDefaults({ + * textureColor: [1, 0, 0, 1], + * attribPrefix: 'a_', + * }); + * + * @typedef {Object} Defaults + * @property {string} [attribPrefix] The prefix to stick on attributes + * + * When writing shaders I prefer to name attributes with `a_`, uniforms with `u_` and varyings with `v_` + * as it makes it clear where they came from. But, when building geometry I prefer using un-prefixed names. + * + * In other words I'll create arrays of geometry like this + * + * const arrays = { + * position: ... + * normal: ... + * texcoord: ... + * }; + * + * But need those mapped to attributes and my attributes start with `a_`. + * + * Default: `""` + * + * @property {number[]} [textureColor] Array of 4 values in the range 0 to 1 + * + * The default texture color is used when loading textures from + * urls. Because the URL will be loaded async we'd like to be + * able to use the texture immediately. By putting a 1x1 pixel + * color in the texture we can start using the texture before + * the URL has loaded. + * + * Default: `[0.5, 0.75, 1, 1]` + * + * @property {string} [crossOrigin] + * + * If not undefined sets the crossOrigin attribute on images + * that twgl creates when downloading images for textures. + * + * Also see {@link module:twgl.TextureOptions}. + * + * @property {bool} [addExtensionsToContext] + * + * If true, then, when twgl will try to add any supported WebGL extensions + * directly to the context under their normal GL names. For example + * if ANGLE_instances_arrays exists then twgl would enable it, + * add the functions `vertexAttribDivisor`, `drawArraysInstanced`, + * `drawElementsInstanced`, and the constant `VERTEX_ATTRIB_ARRAY_DIVISOR` + * to the `WebGLRenderingContext`. + * + * @memberOf module:twgl + */ + +/** + * Sets various defaults for twgl. + * + * In the interest of terseness which is kind of the point + * of twgl I've integrated a few of the older functions here + * + * @param {module:twgl.Defaults} newDefaults The default settings. + * @memberOf module:twgl + */ +function setDefaults(newDefaults) { + copyExistingProperties(newDefaults, defaults); + setDefaults$2(newDefaults); // eslint-disable-line + setDefaults$1(newDefaults); // eslint-disable-line +} + +const prefixRE = /^(.*?)_/; +function addExtensionToContext(gl, extensionName) { + glEnumToString(gl, 0); + const ext = gl.getExtension(extensionName); + if (ext) { + const enums = {}; + const fnSuffix = prefixRE.exec(extensionName)[1]; + const enumSuffix = '_' + fnSuffix; + for (const key in ext) { + const value = ext[key]; + const isFunc = typeof (value) === 'function'; + const suffix = isFunc ? fnSuffix : enumSuffix; + let name = key; + // examples of where this is not true are WEBGL_compressed_texture_s3tc + // and WEBGL_compressed_texture_pvrtc + if (key.endsWith(suffix)) { + name = key.substring(0, key.length - suffix.length); + } + if (gl[name] !== undefined) { + if (!isFunc && gl[name] !== value) { + warn$1(name, gl[name], value, key); + } + } else { + if (isFunc) { + gl[name] = function(origFn) { + return function() { + return origFn.apply(ext, arguments); + }; + }(value); + } else { + gl[name] = value; + enums[name] = value; + } + } + } + // pass the modified enums to glEnumToString + enums.constructor = { + name: ext.constructor.name, + }; + glEnumToString(enums, 0); + } + return ext; +} + +/* + * If you're wondering why the code doesn't just iterate + * over all extensions using `gl.getExtensions` is that it's possible + * some future extension is incompatible with this code. Rather than + * have thing suddenly break it seems better to manually add to this + * list. + * + */ +const supportedExtensions = [ + 'ANGLE_instanced_arrays', + 'EXT_blend_minmax', + 'EXT_color_buffer_float', + 'EXT_color_buffer_half_float', + 'EXT_disjoint_timer_query', + 'EXT_disjoint_timer_query_webgl2', + 'EXT_frag_depth', + 'EXT_sRGB', + 'EXT_shader_texture_lod', + 'EXT_texture_filter_anisotropic', + 'OES_element_index_uint', + 'OES_standard_derivatives', + 'OES_texture_float', + 'OES_texture_float_linear', + 'OES_texture_half_float', + 'OES_texture_half_float_linear', + 'OES_vertex_array_object', + 'WEBGL_color_buffer_float', + 'WEBGL_compressed_texture_atc', + 'WEBGL_compressed_texture_etc1', + 'WEBGL_compressed_texture_pvrtc', + 'WEBGL_compressed_texture_s3tc', + 'WEBGL_compressed_texture_s3tc_srgb', + 'WEBGL_depth_texture', + 'WEBGL_draw_buffers', +]; + +/** + * Attempts to enable all of the following extensions + * and add their functions and constants to the + * `WebGLRenderingContext` using their normal non-extension like names. + * + * ANGLE_instanced_arrays + * EXT_blend_minmax + * EXT_color_buffer_float + * EXT_color_buffer_half_float + * EXT_disjoint_timer_query + * EXT_disjoint_timer_query_webgl2 + * EXT_frag_depth + * EXT_sRGB + * EXT_shader_texture_lod + * EXT_texture_filter_anisotropic + * OES_element_index_uint + * OES_standard_derivatives + * OES_texture_float + * OES_texture_float_linear + * OES_texture_half_float + * OES_texture_half_float_linear + * OES_vertex_array_object + * WEBGL_color_buffer_float + * WEBGL_compressed_texture_atc + * WEBGL_compressed_texture_etc1 + * WEBGL_compressed_texture_pvrtc + * WEBGL_compressed_texture_s3tc + * WEBGL_compressed_texture_s3tc_srgb + * WEBGL_depth_texture + * WEBGL_draw_buffers + * + * For example if `ANGLE_instanced_arrays` exists then the functions + * `drawArraysInstanced`, `drawElementsInstanced`, `vertexAttribDivisor` + * and the constant `VERTEX_ATTRIB_ARRAY_DIVISOR` are added to the + * `WebGLRenderingContext`. + * + * Note that if you want to know if the extension exists you should + * probably call `gl.getExtension` for each extension. Alternatively + * you can check for the existence of the functions or constants that + * are expected to be added. For example + * + * if (gl.drawBuffers) { + * // Either WEBGL_draw_buffers was enabled OR you're running in WebGL2 + * .... + * + * @param {WebGLRenderingContext} gl A WebGLRenderingContext + * @memberOf module:twgl + */ +function addExtensionsToContext(gl) { + for (let ii = 0; ii < supportedExtensions.length; ++ii) { + addExtensionToContext(gl, supportedExtensions[ii]); + } +} + +/** + * Creates a webgl context. + * @param {HTMLCanvasElement} canvas The canvas tag to get + * context from. If one is not passed in one will be + * created. + * @return {WebGLRenderingContext} The created context. + * @private + */ +function create3DContext(canvas, opt_attribs) { + const names = ["webgl", "experimental-webgl"]; + let context = null; + for (let ii = 0; ii < names.length; ++ii) { + context = canvas.getContext(names[ii], opt_attribs); + if (context) { + if (defaults.addExtensionsToContext) { + addExtensionsToContext(context); + } + break; + } + } + return context; +} + +/** + * Gets a WebGL1 context. + * + * Note: Will attempt to enable Vertex Array Objects + * and add WebGL2 entry points. (unless you first set defaults with + * `twgl.setDefaults({enableVertexArrayObjects: false})`; + * + * @param {HTMLCanvasElement} canvas a canvas element. + * @param {WebGLContextAttributes} [opt_attribs] optional webgl context creation attributes + * @return {WebGLRenderingContext} The created context. + * @memberOf module:twgl + * @deprecated + * @private + */ +function getWebGLContext(canvas, opt_attribs) { + const gl = create3DContext(canvas, opt_attribs); + return gl; +} + +/** + * Creates a webgl context. + * + * Will return a WebGL2 context if possible. + * + * You can check if it's WebGL2 with + * + * twgl.isWebGL2(gl); + * + * @param {HTMLCanvasElement} canvas The canvas tag to get + * context from. If one is not passed in one will be + * created. + * @return {WebGLRenderingContext} The created context. + */ +function createContext(canvas, opt_attribs) { + const names = ["webgl2", "webgl", "experimental-webgl"]; + let context = null; + for (let ii = 0; ii < names.length; ++ii) { + context = canvas.getContext(names[ii], opt_attribs); + if (context) { + if (defaults.addExtensionsToContext) { + addExtensionsToContext(context); + } + break; + } + } + return context; +} + +/** + * Gets a WebGL context. Will create a WebGL2 context if possible. + * + * You can check if it's WebGL2 with + * + * function isWebGL2(gl) { + * return gl.getParameter(gl.VERSION).indexOf("WebGL 2.0 ") == 0; + * } + * + * Note: For a WebGL1 context will attempt to enable Vertex Array Objects + * and add WebGL2 entry points. (unless you first set defaults with + * `twgl.setDefaults({enableVertexArrayObjects: false})`; + * + * @param {HTMLCanvasElement} canvas a canvas element. + * @param {WebGLContextAttributes} [opt_attribs] optional webgl context creation attributes + * @return {WebGLRenderingContext} The created context. + * @memberOf module:twgl + */ +function getContext(canvas, opt_attribs) { + const gl = createContext(canvas, opt_attribs); + return gl; +} + +/** + * Resize a canvas to match the size it's displayed. + * @param {HTMLCanvasElement} canvas The canvas to resize. + * @param {number} [multiplier] So you can pass in `window.devicePixelRatio` or other scale value if you want to. + * @return {boolean} true if the canvas was resized. + * @memberOf module:twgl + */ +function resizeCanvasToDisplaySize(canvas, multiplier) { + multiplier = multiplier || 1; + multiplier = Math.max(0, multiplier); + const width = canvas.clientWidth * multiplier | 0; + const height = canvas.clientHeight * multiplier | 0; + if (canvas.width !== width || canvas.height !== height) { + canvas.width = width; + canvas.height = height; + return true; + } + return false; +} + +export { addExtensionsToContext, attributes, bindFramebufferInfo, bindTransformFeedbackInfo, bindUniformBlock, canFilter, canGenerateMipmap, createAttribsFromArrays, createAttributeSetters, createBufferFromArray, createBufferFromTypedArray, createBufferInfoFromArrays, createBuffersFromArrays, createFramebufferInfo, createProgram, createProgramAsync, createProgramFromScripts, createProgramFromSources, createProgramInfo, createProgramInfoAsync, createProgramInfoFromProgram, createProgramInfos, createProgramInfosAsync, createPrograms, createProgramsAsync, createSampler, createSamplers, createTexture, createTextures, createTransformFeedback, createTransformFeedbackInfo, createUniformBlockInfo, createUniformBlockInfoFromProgram, createUniformBlockSpecFromProgram, createUniformSetters, createVAOAndSetAttributes, createVAOFromBufferInfo, createVertexArrayInfo, draw, drawBufferInfo, drawObjectList, framebuffers, getArray$1 as getArray_, getBytesPerElementForInternalFormat, getContext, getFormatAndTypeForInternalFormat, getGLTypeForTypedArray, getGLTypeForTypedArrayType, getNumComponentsForFormat, getNumComponents$1 as getNumComponents_, getTypedArrayTypeForGLType, getWebGLContext, glEnumToString, isArrayBuffer$1 as isArrayBuffer, isWebGL1, isWebGL2, loadTextureFromUrl, m4, primitives, programs, resizeCanvasToDisplaySize, resizeFramebufferInfo, resizeTexture, setAttribInfoBufferFromArray, setDefaults$2 as setAttributeDefaults_, setAttributePrefix, setAttributes, setBlockUniforms, setBuffersAndAttributes, setDefaultTextureColor, setDefaults, setEmptyTexture, setSamplerParameters, setDefaults$1 as setTextureDefaults_, setTextureFilteringForSize, setTextureFromArray, setTextureFromElement, setTextureParameters, setUniformBlock, setUniforms, setUniformsAndBindTextures, textures, typedarrays, utils, v3, vertexArrays }; diff --git a/examples/js/index/VSAEffect.js b/examples/js/index/VSAEffect.js new file mode 100644 index 0000000..2761d0e --- /dev/null +++ b/examples/js/index/VSAEffect.js @@ -0,0 +1,343 @@ +/* eslint-disable no-underscore-dangle */ + +import * as twgl from '../../3rdParty/twgl-full.module.js'; + +const m4 = twgl.m4; + +const kMaxCount = 100000; + +const s_vsHeader = ` +attribute float vertexId; + +uniform vec2 mouse; +uniform vec2 resolution; +uniform vec4 background; +uniform float time; +uniform float vertexCount; +uniform sampler2D sound; +uniform vec2 soundRes; +uniform float _dontUseDirectly_pointSize; + +varying vec4 v_color; +`; + +const s_fs = ` +precision mediump float; + +varying vec4 v_color; + +void main() { + gl_FragColor = v_color; +} +`; + + +const s_historyVS = ` +attribute vec4 position; +attribute vec2 texcoord; +uniform mat4 u_matrix; +varying vec2 v_texcoord; + +void main() { + gl_Position = u_matrix * position; + v_texcoord = texcoord; +} +`; + +const s_historyFS = ` +precision mediump float; + +uniform sampler2D u_texture; +uniform float u_mix; +uniform float u_mult; +varying vec2 v_texcoord; + +void main() { + vec4 color = texture2D(u_texture, v_texcoord); + gl_FragColor = mix(color.aaaa, color.rgba, u_mix) * u_mult; +} +`; + +const s_rectVS = ` +attribute vec4 position; +uniform mat4 u_matrix; + +void main() { + gl_Position = u_matrix * position; +} +`; + +const s_rectFS = ` +precision mediump float; + +uniform vec4 u_color; + +void main() { + gl_FragColor = u_color; +} +`; + +class HistoryTexture { + constructor(gl, options) { + this.gl = gl; + const _width = options.width; + const type = options.type || gl.UNSIGNED_BYTE; + const format = options.format || gl.RGBA; + const Ctor = twgl.getTypedArrayTypeForGLType(type); + const numComponents = twgl.getNumComponentsForFormat(format); + const size = _width * numComponents; + const _buffer = new Ctor(size); + const _texSpec = { + src: _buffer, + height: 1, + min: options.min || gl.LINEAR, + mag: options.mag || gl.LINEAR, + wrap: gl.CLAMP_TO_EDGE, + format: format, + auto: false, // don't set tex params or call genmipmap + }; + const _tex = twgl.createTexture(gl, _texSpec); + + const _length = options.length; + const _historyAttachments = [ + { + format: options.historyFormat || gl.RGBA, + type: type, + mag: options.mag || gl.LINEAR, + min: options.min || gl.LINEAR, + wrap: gl.CLAMP_TO_EDGE, + }, + ]; + + let _srcFBI = twgl.createFramebufferInfo(gl, _historyAttachments, _width, _length); + let _dstFBI = twgl.createFramebufferInfo(gl, _historyAttachments, _width, _length); + + const _historyUniforms = { + u_mix: 0, + u_mult: 1, + u_matrix: m4.identity(), + u_texture: undefined, + }; + + this.buffer = _buffer; + + this.update = (gl, historyProgramInfo, quadBufferInfo) => { + const temp = _srcFBI; + _srcFBI = _dstFBI; + _dstFBI = temp; + + twgl.setTextureFromArray(gl, _tex, _texSpec.src, _texSpec); + + gl.useProgram(historyProgramInfo.program); + twgl.bindFramebufferInfo(gl, _dstFBI); + + // copy from historySrc to historyDst one pixel down + m4.translation([0, 2 / _length, 0], _historyUniforms.u_matrix); + _historyUniforms.u_mix = 1; + _historyUniforms.u_texture = _srcFBI.attachments[0]; + + twgl.setUniforms(historyProgramInfo, _historyUniforms); + twgl.drawBufferInfo(gl, quadBufferInfo); + + // copy audio data into top row of historyDst + _historyUniforms.u_mix = format === gl.ALPHA ? 0 : 1; + _historyUniforms.u_texture = _tex; + m4.translation( + [0, -(_length - 0.5) / _length, 0], + _historyUniforms.u_matrix); + m4.scale( + _historyUniforms.u_matrix, + [1, 1 / _length, 1], + _historyUniforms.u_matrix); + + twgl.setUniforms(historyProgramInfo, _historyUniforms); + twgl.drawBufferInfo(gl, quadBufferInfo); + }; + + this.getTexture = () => { + return _dstFBI.attachments[0]; + }; + } +} + +const mainRE = /(void[ \t\n\r]+main[ \t\n\r]*\([ \t\n\r]*\)[ \t\n\r]\{)/g; +function applyTemplateToShader(src) { + let vSrc = s_vsHeader + src; + vSrc = vSrc.replace(mainRE, function (m) { + return `${m}gl_PointSize=1.0;`; + }); + const lastBraceNdx = vSrc.lastIndexOf('}'); + if (lastBraceNdx >= 0) { + const before = vSrc.substr(0, lastBraceNdx); + const after = vSrc.substr(lastBraceNdx); + vSrc = `${before};gl_PointSize = max(0., gl_PointSize*_dontUseDirectly_pointSize);${after}`; + } + return vSrc; +} + +export default class VSAEffect { + constructor(gl) { + this.gl = gl; + this.time = 0; + this.then = undefined; + this.rectProgramInfo = twgl.createProgramInfo(gl, [s_rectVS, s_rectFS]); + this.historyProgramInfo = twgl.createProgramInfo(gl, [s_historyVS, s_historyFS]); + } + #init(gl, analyser) { + if (this.init) { + return; + } + this.init = true; + const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); + this.numSoundSamples = Math.min(maxTextureSize, analyser.frequencyBinCount); + this.numHistorySamples = 60 * 4; // 4 seconds; + + this.soundHistory = new HistoryTexture(gl, { + width: this.numSoundSamples, + length: this.numHistorySamples, + format: gl.ALPHA, + }); + + const count = new Float32Array(kMaxCount); + for (let ii = 0; ii < count.length; ++ii) { + count[ii] = ii; + } + const arrays = { + vertexId: { data: count, numComponents: 1 }, + }; + this.countBufferInfo = twgl.createBufferInfoFromArrays(gl, arrays); + this.quadBufferInfo = twgl.createBufferInfoFromArrays(gl, { + position: { numComponents: 2, data: [-1, -1, 1, -1, -1, 1, 1, 1] }, + texcoord: [0, 0, 1, 0, 0, 1, 1, 1], + indices: [0, 1, 2, 2, 1, 3], + }); + + this.uniforms = { + time: 0, + vertexCount: 0, + resolution: [1, 1], + background: [0, 0, 0, 1], + mouse: [0, 0], + sound: undefined, + floatSound: undefined, + soundRes: [this.numSoundSamples, this.numHistorySamples], + _dontUseDirectly_pointSize: 1, + }; + + this.historyUniforms = { + u_mix: 0, + u_matrix: m4.identity(), + u_texture: undefined, + }; + } + async setSettings(vsa) { + try { + if (vsa === this.currentVsa) { + // It's the current URL + return; + } + if (vsa === this.pendingVsa) { + // It's the pending Url + return; + } + this.pendingVsa = vsa; + if (this.compiling) { + return; + } + // It doesn't matter if the URL is bad, we don't want to try again + this.currentVsa = this.pendingVsa; + this.pendingVsa = undefined; + this.compiling = true; + const gl = this.gl; + const vs = applyTemplateToShader(vsa.settings.shader); + const programInfo = await twgl.createProgramInfoAsync(gl, [vs, s_fs]); + this.programInfo = programInfo; + this.vsa = vsa; + } catch (e) { + console.error(e); + } + this.compiling = false; + if (this.pendingVsa) { + const nextVsa = this.pendingVsa; + this.pendingVsa = undefined; + this.setSettings(nextVsa); + } + } + reset(/*gl*/) { + } + resize() { + } + + #updateSoundHistory(gl, analysers) { + // Copy audio data to Nx1 texture + analysers[0].getByteFrequencyData(this.soundHistory.buffer); + + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.BLEND); + + twgl.setBuffersAndAttributes(gl, this.historyProgramInfo, this.quadBufferInfo); + + this.soundHistory.update(gl, this.historyProgramInfo, this.quadBufferInfo); + } + + #renderScene(gl, commonUniforms, soundHistoryTex, time) { + twgl.bindFramebufferInfo(gl); + const settings = this.vsa.settings; + + const programInfo = this.programInfo; + if (!programInfo) { + return; + } + + const { width, height, left, bottom } = commonUniforms; + + gl.viewport(left, bottom, width, height); + gl.scissor(left, bottom, width, height); + gl.enable(gl.DEPTH_TEST); + gl.enable(gl.BLEND); + gl.enable(gl.SCISSOR_TEST); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.clearColor(...settings.backgroundColor); + //gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + const num = settings.num; + const mode = gl[settings.mode]; + const uniforms = this.uniforms; + uniforms.time = time; + uniforms.vertexCount = num; + uniforms.resolution[0] = width; + uniforms.resolution[1] = height; + uniforms.background[0] = settings.backgroundColor[0]; + uniforms.background[1] = settings.backgroundColor[1]; + uniforms.background[2] = settings.backgroundColor[2]; + uniforms.background[3] = settings.backgroundColor[3]; + uniforms._dontUseDirectly_pointSize = 1; + uniforms.sound = soundHistoryTex; + + gl.useProgram(programInfo.program); + twgl.setBuffersAndAttributes(gl, programInfo, this.countBufferInfo); + twgl.setUniforms(programInfo, uniforms); + twgl.setUniforms(programInfo, commonUniforms); + twgl.drawBufferInfo(gl, this.countBufferInfo, mode, num); + + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.BLEND); + gl.disable(gl.SCISSOR_TEST); + } + + render(gl, commonUniforms, byteBeat, analyzers) { + if (!this.vsa || !this.programInfo) { + return; + } + this.#init(gl, analyzers[0]); + const now = byteBeat.getTime() / byteBeat.getDesiredSampleRate(); + const deltaTime = (now - (this.then || now)) * (commonUniforms.speed === undefined ? 1 : commonUniforms.speed); + this.then = now; + this.time += deltaTime; + + this.#updateSoundHistory(gl, analyzers, this.time); + + const historyTex = this.soundHistory.getTexture(); + this.#renderScene(gl, commonUniforms, historyTex, this.time); + } +} \ No newline at end of file diff --git a/examples/js/index/effects.js b/examples/js/index/effects.js new file mode 100644 index 0000000..0bd8f39 --- /dev/null +++ b/examples/js/index/effects.js @@ -0,0 +1,27 @@ +import admo from './effects/admo.js'; +import bwow from './effects/bwow.js'; +import codez from './effects/codez.js'; +import cyty from './effects/cyty.js'; +import discus from './effects/discus.js'; +import dottoChoukoukei from './effects/dotto-chouhoukei.js'; +import hexit2 from './effects/hexit2.js'; +import loopTest from './effects/loop-test.js'; +import pookymelon from './effects/pookymelon.js'; +import rollin from './effects/rollin.js'; +import starfield from './effects/starfield.js'; +import ung from './effects/ung.js'; + +export default { + admo, + bwow, + codez, + cyty, + discus, + dottoChoukoukei, + hexit2, + loopTest, + pookymelon, + rollin, + starfield, + ung, +}; diff --git a/examples/js/index/effects/admo.js b/examples/js/index/effects/admo.js new file mode 100644 index 0000000..4af709f --- /dev/null +++ b/examples/js/index/effects/admo.js @@ -0,0 +1,390 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "PkSbv7TufuWHiNRbd", + createdAt: "2017-01-28T04:48:49.529Z", + modifiedAt: "2017-01-28T05:56:37.779Z", + origId: "qjkP6BDvEFyD6CfZC", + name: "admo", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 15360, + mode: "TRIANGLES", + sound: "https://soundcloud.com/beatsfar/grand-mas-mandoline", + lineSize: "NATIVE", + backgroundColor: [ + 1, + 1, + 1, + 1 + ], + shader: `/* 🌈 */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +uniform float opacity; +uniform vec3 color1; +uniform vec3 color2; + + +#define PI radians(180.) + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} + +mat4 rotX(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + 1, 0, 0, 0, + 0, c, s, 0, + 0, -s, c, 0, + 0, 0, 0, 1); +} + +mat4 rotY(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + +mat4 rotZ(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + +mat4 trInv(mat4 m) { + mat3 i = mat3( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); + vec3 t = -i * m[3].xyz; + + return mat4( + i[0], t[0], + i[1], t[1], + i[2], t[2], + 0, 0, 0, 1); +} + +mat4 transpose(mat4 m) { + return mat4( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +mat4 lookAt(vec3 eye, vec3 target, vec3 up) { + vec3 zAxis = normalize(eye - target); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + eye, 1); +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +mat4 cameraLookAt(vec3 eye, vec3 target, vec3 up) { + #if 1 + return inverse(lookAt(eye, target, up)); + #else + vec3 zAxis = normalize(target - eye); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1); + #endif + +} + + + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) { + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +// times 2 minus 1 +float t2m1(float v) { + return v * 2. - 1.; +} + +// times .5 plus .5 +float t5p5(float v) { + return v * 0.5 + 0.5; +} + +float inv(float v) { + return 1. - v; +} + +void getCirclePoint(const float numEdgePointsPerCircle, const float id, const float inner, const float start, const float end, out vec3 pos) { + float outId = id - floor(id / 3.) * 2. - 1.; // 0 1 2 3 4 5 6 7 8 .. 0 1 2, 1 2 3, 2 3 4 + float ux = floor(id / 6.) + mod(id, 2.); + float vy = mod(floor(id / 2.) + floor(id / 3.), 2.); // change that 3. for cool fx + float u = ux / numEdgePointsPerCircle; + float v = mix(inner, 1., vy); + float a = mix(start, end, u) * PI * 2. + PI * 0.0; + float s = sin(a); + float c = cos(a); + float x = c * v; + float y = s * v; + float z = 0.; + pos = vec3(x, y, z); +} + + +#define NUM_EDGE_POINTS_PER_CIRCLE 64.0 +#define NUM_POINTS_PER_CIRCLE (NUM_EDGE_POINTS_PER_CIRCLE * 6.0) +#define NUM_CIRCLES_PER_GROUP 1.0 +void main() { + float circleId = floor(vertexId / NUM_POINTS_PER_CIRCLE); + float groupId = floor(circleId / NUM_CIRCLES_PER_GROUP); + float pointId = mod(vertexId, NUM_POINTS_PER_CIRCLE); + float sliceId = mod(floor(vertexId / 6.), 2.); + float side = mix(-1., 1., step(0.5, mod(circleId, 2.))); + float numCircles = floor(vertexCount / NUM_POINTS_PER_CIRCLE); + float numGroups = floor(numCircles / NUM_CIRCLES_PER_GROUP); + float cu = circleId / numCircles; + float gv = groupId / numGroups; + float cgId = mod(circleId, NUM_CIRCLES_PER_GROUP); + float cgv = cgId / NUM_CIRCLES_PER_GROUP; + float ncgv = 1. - cgv; + + + float tm = time - cgv * 0.2; + + float su = mix(0.01, 0.2, abs(t2m1(cu))); + float sv = 0.; + float s = texture2D(sound, vec2(su, sv)).a; + + + vec3 pos; + float inner = 0.; + float start = 0.; + float end = 1.; + getCirclePoint(NUM_EDGE_POINTS_PER_CIRCLE, pointId, inner, start, end, pos); + pos.z = pos.x * -0.1 + pos.y * -0.1;//cu * 0.001; + pos.x *= cos(pos.y) * mix(.25, .75, abs(sin(time * 0.131))); + + vec3 eye = vec3(sin(time * 0.), sin(time * 0.) * 0.25, 1.); + vec3 target = vec3(0); + vec3 up = vec3(0, 1, 0); + +// mat4 mat = persp(120. * PI / 180., resolution.x / resolution.y, 0.1, 10.); + mat4 mat = ident(); + mat *= scale(0.25 * vec3(1, resolution.x / resolution.y, 1)); + mat *= cameraLookAt(eye, target, up); + mat *= rotZ(time * 0.1 + t2m1(cu) * PI);// * sin(time* 0.1)); + mat *= trans(vec3(t5p5(sin(time * 0.273)), mix(0.2, 1.5, t5p5(sin(time * 0.171))), 0)); + mat *= rotZ(t5p5(sin(time * 0.147))); + + gl_Position = mat * vec4(pos, 1); + + float hue = 1. + cu; + float sat = mix(.3, 1., abs(sin(time * 0.213))); + float val = 1.; + + float colorMix = abs(cu * 2.0 - 1.0); + //vec3 hsv1 = rgb2hsv(color1); + //vec3 hsv2 = rgb2hsv(color2); + //if (abs(hsv1.x - hsv2.x) > 0.5) { + // if (hsv1.x > hsv2.x) { + // hsv2.x += 1.0; + // } else { + // hsv1.x += 1.0; + // } + //} + //vec3 color = hsv2rgb(mix(hsv1, hsv2, colorMix)); + vec3 color = mix(color1, color2, colorMix); + + v_color = //vec4(hsv2rgb(vec3(hue, sat, val)), mix(0.01, 0.2, s)); + vec4(color, 1.0); + v_color.a *= clamp(opacity * 2.0, 0.0, 1.0); + v_color.a += clamp((opacity - 0.5) * 2.0, 0.0, 1.0); + v_color.rgb *= v_color.a; + + if (cu < 0.0 || cu > 1.0) { + v_color = vec4(0, 1, 1, 1); + } +} + +` + }, + revisionId: "vkzqvZawTQrJbA4HY", + revisionUrl: "https://www.vertexshaderart.com/art/PkSbv7TufuWHiNRbd/revision/vkzqvZawTQrJbA4HY", + artUrl: "https://www.vertexshaderart.com/art/undefined", + origUrl: "https://www.vertexshaderart.com/art/qjkP6BDvEFyD6CfZC" +}; diff --git a/examples/js/index/effects/bwow.js b/examples/js/index/effects/bwow.js new file mode 100644 index 0000000..3078a92 --- /dev/null +++ b/examples/js/index/effects/bwow.js @@ -0,0 +1,328 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "6Yx2A7TQ6NnyHhFPQ", + createdAt: "2016-09-02T17:38:52.975Z", + modifiedAt: "2016-09-18T04:57:25.845Z", + origId: "qjkP6BDvEFyD6CfZC", + name: "bwow", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 10404, + mode: "TRIANGLES", + sound: "", + lineSize: "NATIVE", + backgroundColor: [ + 1, + 1, + 1, + 1 + ], + shader: `/* + +Challenge: 01 + +*/ + + + + +#define PI radians(180.) + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +mat4 rotX(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + 1, 0, 0, 0, + 0, c, s, 0, + 0, -s, c, 0, + 0, 0, 0, 1); +} + +mat4 rotY(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + +mat4 rotZ(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + +mat4 trInv(mat4 m) { + mat3 i = mat3( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); + vec3 t = -i * m[3].xyz; + + return mat4( + i[0], t[0], + i[1], t[1], + i[2], t[2], + 0, 0, 0, 1); +} + +mat4 transpose(mat4 m) { + return mat4( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +mat4 lookAt(vec3 eye, vec3 target, vec3 up) { + vec3 zAxis = normalize(eye - target); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + eye, 1); +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +mat4 cameraLookAt(vec3 eye, vec3 target, vec3 up) { + #if 1 + return inverse(lookAt(eye, target, up)); + #else + vec3 zAxis = normalize(target - eye); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1); + #endif + +} + + + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) { + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +// times 2 minus 1 +float t2m1(float v) { + return v * 2. - 1.; +} + +// times .5 plus .5 +float t5p5(float v) { + return v * 0.5 + 0.5; +} + +float inv(float v) { + return 1. - v; +} + +void getCirclePoint(const float numEdgePointsPerCircle, const float id, const float inner, const float start, const float end, out vec3 pos) { + float outId = id - floor(id / 3.) * 2. - 1.; // 0 1 2 3 4 5 6 7 8 .. 0 1 2, 1 2 3, 2 3 4 + float ux = floor(id / 6.) + mod(id, 2.); + float vy = mod(floor(id / 2.) + floor(id / 3.), 2.); // change that 3. for cool fx + float u = ux / numEdgePointsPerCircle; + float v = mix(inner, 1., vy); + float a = mix(start, end, u) * PI * 2. + PI * 0.0; + float s = sin(a); + float c = cos(a); + float x = c * v; + float y = s * v; + float z = 0.; + pos = vec3(x, y, z); +} + + +#define CUBE_POINTS_PER_FACE 6. +#define FACES_PER_CUBE 6. +#define POINTS_PER_CUBE (CUBE_POINTS_PER_FACE * FACES_PER_CUBE) +void getCubePoint(const float id, out vec3 position, out vec3 normal) { + float quadId = floor(mod(id, POINTS_PER_CUBE) / CUBE_POINTS_PER_FACE); + float sideId = mod(quadId, 3.); + float flip = mix(1., -1., step(2.5, quadId)); + // 0 1 2 1 2 3 + float facePointId = mod(id, CUBE_POINTS_PER_FACE); + float pointId = mod(facePointId - floor(facePointId / 3.0), 6.0); + float a = pointId * PI * 2. / 4. + PI * 0.25; + vec3 p = vec3(cos(a), 0.707106781, sin(a)) * flip; + vec3 n = vec3(0, 1, 0) * flip; + float lr = mod(sideId, 2.); + float ud = step(2., sideId); + mat4 mat = rotX(lr * PI * 0.5); + mat *= rotZ(ud * PI * 0.5); + position = (mat * vec4(p, 1)).xyz; + normal = (mat * vec4(n, 0)).xyz; +} + +void main() { + float pointId = vertexId; + + vec3 pos; + vec3 normal; + getCubePoint(pointId, pos, normal); + float cubeId = floor(pointId / 36.); + float faceId = mod(floor(pointId / 6.), 6.); + float numCubes = floor(vertexCount / 36.); + float down = floor(sqrt(numCubes)); + float across = floor(numCubes / down); + + float cx = mod(cubeId, across); + float cy = floor(cubeId / across); + + float cu = cx / (across - 1.); + float cv = cy / (down - 1.); + + float ca = cu * 2. - 1.; + float cd = cv * 2. - 1.; + + float tm = PI * 1.75;time * 0.1; + mat4 mat = persp(radians(60.0), resolution.x / resolution.y, 0.1, 1000.0); + vec3 eye = vec3(cos(tm) * 1., sin(tm * 0.9) * .1 + 0.7, sin(tm) * 1.); + vec3 target = vec3(0); + vec3 up = vec3(0,1,0); + + mat *= cameraLookAt(eye, target, up); + mat *= trans(vec3(ca, 0, cd) * .3); +// mat *= rotX(time + abs(ca) * 5.); +// mat *= rotZ(time + abs(cd) * 6.); + float d = length(vec2(ca, cd)); + mat *= scale(vec3( + 1, + mix(2., + 12., + pow(sin(time * -2. + d * PI * 1.) * 0.5 + 0.5, + 1.5 + (1. - d) * 2.)), + 1)); + mat *= uniformScale(0.03); + + + gl_Position = mat * vec4(pos, 1); + vec3 n = normalize((mat * vec4(normal, 0)).xyz); + + vec3 lightDir = normalize(vec3(3.3, 0.4, -1)); + + float hue = .15 + mod(faceId + 2., 6.) * 0.17; + float sat = 0.3; + float val = 1.; + vec3 color = hsv2rgb(vec3(hue, sat, val)) * 1.125; + v_color = vec4(color * (dot(n, lightDir) * 0.5 + 0.5), 1); +} + +` + }, + revisionId: "vWR26Fhpcy3b2KLme", + revisionUrl: "https://www.vertexshaderart.com/art/6Yx2A7TQ6NnyHhFPQ/revision/vWR26Fhpcy3b2KLme", + artUrl: "https://www.vertexshaderart.com/art/undefined", + origUrl: "https://www.vertexshaderart.com/art/qjkP6BDvEFyD6CfZC" +}; diff --git a/examples/js/index/effects/codez.js b/examples/js/index/effects/codez.js new file mode 100644 index 0000000..28b2b97 --- /dev/null +++ b/examples/js/index/effects/codez.js @@ -0,0 +1,636 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "d7anES7ef6WrrDwsy", + createdAt: "2017-01-28T04:48:49.529Z", + modifiedAt: "2017-01-28T05:56:37.779Z", + origId: "d7anES7ef6WrrDwsy", + name: "codez", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 100000, + mode: "TRIANGLES", + sound: "https://soundcloud.com/beatsfar/grand-mas-mandoline", + lineSize: "NATIVE", + backgroundColor: [ + 1, + 0, + 0, + 1 + ], + shader: ` +/* + + ___ ___ _______ ________ _________ _______ ___ ___ +|\ \ / /|\ ___ \ |\ __ \|\___ ___|\ ___ \ |\ \ / /| +\ \ \ / / \ \ __/|\ \ \|\ \|___ \ \_\ \ __/| \ \ \/ / / + \ \ \/ / / \ \ \_|/_\ \ _ _\ \ \ \ \ \ \_|/__ \ \ / / + \ \ / / \ \ \_|\ \ \ \\ \| \ \ \ \ \ \_|\ \ / \/ + \ \__/ / \ \_______\ \__\\ _\ \ \__\ \ \_______\/ /\ \ + \|__|/ \|_______|\|__|\|__| \|__| \|_______/__/ /\ __\ + |__|/ \|__| + + + ________ ___ ___ ________ ________ _______ ________ +|\ ____\|\ \|\ \|\ __ \|\ ___ \|\ ___ \ |\ __ \ +\ \ \___|\ \ \\\ \ \ \|\ \ \ \_|\ \ \ __/|\ \ \|\ \ + \ \_____ \ \ __ \ \ __ \ \ \ \\ \ \ \_|/_\ \ _ _\ + \|____|\ \ \ \ \ \ \ \ \ \ \ \_\\ \ \ \_|\ \ \ \\ \| + ____\_\ \ \__\ \__\ \__\ \__\ \_______\ \_______\ \__\\ _\ + |\_________\|__|\|__|\|__|\|__|\|_______|\|_______|\|__|\|__| + \|_________| + + + ________ ________ _________ +|\ __ \|\ __ \|\___ ___\ +\ \ \|\ \ \ \|\ \|___ \ \_| + \ \ __ \ \ _ _\ \ \ \ + \ \ \ \ \ \ \\ \| \ \ \ + \ \__\ \__\ \__\\ _\ \ \__\ + \|__|\|__|\|__|\|__| \|__| + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + +vec3 gSunColor = vec3(1.0, 1.2, 1.4) * 10.1; + +vec3 gSkyTop = vec3( 0.1, 0.2, 0.8 ) * 0.5; +vec3 gSkyBottom = vec3( 0.5, 0.8, 1.0 ) * 1.5; + +vec3 gCubeColor = vec3(1.0, 1.0, 1.0); +float gExposure = 0.3; + +float gCubeColorRandom = 0.0; + +#define MOVE_OUTWARDS + +float fAOAmount = 0.8; +float gFloorHeight = -1.0; +float g_cameraFar = 1000.0; + +#define PI radians( 180.0 ) + + +vec3 GetSunDir() +{ + return normalize( vec3( 20.0, 40.3, -10.4 ) ); +} + +struct SceneVertex +{ + vec3 vWorldPos; + vec3 vColor; + float fAlpha; +}; + + +float GetCosSunRadius() +{ + return 0.01; +} + + +float GetSunIntensity() +{ + return 0.001; +} + + +vec3 GetSkyColor( vec3 vViewDir ) +{ + return mix( gSkyBottom, gSkyTop, max( 0.0, vViewDir.y ) ); +} +const float g_cubeFaces = 6.0; +const float g_cubeVerticesPerFace = ( 2.0 * 3.0 ); +const float g_cubeVertexCount = ( g_cubeVerticesPerFace * g_cubeFaces ); + +// 6 7 +// +----------+ +// /| /| +// 2 / | 3/ | +// +----------+ | +// | | | | +// Y Z | 4| | 5| +// | +-------|--+ +// ^ / | / | / +// |/ 0|/ 1|/ +// +--> X +----------+ + +vec3 GetCubeVertex( float fVertexIndex ) +{ + float f = fVertexIndex / 8.0; + return vec3( + mix(-1., 1., step(0.5, fract(f * 4.))), + mix(-1., 1., step(0.5, fract(f * 2.))), + mix(-1., 1., step(0.5, fract(f)))); +} + + +void GetCubeVertex( const float vertexIndex, const mat4 mat, out vec3 vWorldPos, out vec3 vWorldNormal ) +{ + float fFaceIndex = floor( vertexIndex / g_cubeFaces ); + + vec3 v0, v1, v2, v3; + + if ( fFaceIndex < 0.5 ) + { + v0 = GetCubeVertex( 0.0 ); + v1 = GetCubeVertex( 2.0 ); + v2 = GetCubeVertex( 3.0 ); + v3 = GetCubeVertex( 1.0 ); + } + else if ( fFaceIndex < 1.5 ) + { + v0 = GetCubeVertex( 5.0 ); + v1 = GetCubeVertex( 7.0 ); + v2 = GetCubeVertex( 6.0 ); + v3 = GetCubeVertex( 4.0 ); + } + else if ( fFaceIndex < 2.5 ) + { + v0 = GetCubeVertex( 1.0 ); + v1 = GetCubeVertex( 3.0 ); + v2 = GetCubeVertex( 7.0 ); + v3 = GetCubeVertex( 5.0 ); + } + else if ( fFaceIndex < 3.5 ) + { + v0 = GetCubeVertex( 4.0 ); + v1 = GetCubeVertex( 6.0 ); + v2 = GetCubeVertex( 2.0 ); + v3 = GetCubeVertex( 0.0 ); + } + else if ( fFaceIndex < 4.5 ) + { + v0 = GetCubeVertex( 2.0 ); + v1 = GetCubeVertex( 6.0 ); + v2 = GetCubeVertex( 7.0 ); + v3 = GetCubeVertex( 3.0 ); + } + else + { + v0 = GetCubeVertex( 1.0 ); + v1 = GetCubeVertex( 5.0 ); + v2 = GetCubeVertex( 4.0 ); + v3 = GetCubeVertex( 0.0 ); + } + v0 = (mat * vec4(v0, 1)).xyz; + v1 = (mat * vec4(v1, 1)).xyz; + v2 = (mat * vec4(v2, 1)).xyz; + v3 = (mat * vec4(v3, 1)).xyz; + + float fFaceVertexIndex = mod( vertexIndex, 6.0 ); + + if ( fFaceVertexIndex < 0.5 ) + { + vWorldPos = v0; + } + else if ( fFaceVertexIndex < 1.5 ) + { + vWorldPos = v1; + } + else if ( fFaceVertexIndex < 2.5 ) + { + vWorldPos = v2; + } + else if ( fFaceVertexIndex < 3.5 ) + { + vWorldPos = v0; + } + else if ( fFaceVertexIndex < 4.5 ) + { + vWorldPos = v2; + } + else + { + vWorldPos = v3; + } + + vWorldNormal = normalize( cross( v1 - v0, v2 - v0 ) ); +} + + +vec3 GetSunLighting( const vec3 vNormal ) +{ + vec3 vLight = -GetSunDir(); + + float NdotL = max( 0.0, dot( vNormal, -vLight ) ); + + return gSunColor * NdotL; +} + + +vec3 GetSunSpec( const vec3 vPos, const vec3 vNormal, const vec3 vCameraPos ) +{ + vec3 vLight = -GetSunDir(); + + vec3 vView = normalize( vCameraPos - vPos ); + + vec3 vH = normalize( vView - vLight ); + + float NdotH = max( 0.0, dot( vNormal, vH ) ); + float NdotL = max( 0.0, dot( vNormal, -vLight ) ); + + float f = mix( 0.01, 1.0, pow( 1.0 - NdotL, 5.0 ) ); + + return gSunColor * pow( NdotH, 20.0 ) * NdotL * f * 4.0; +} + + +vec3 GetSkyLighting( const vec3 vNormal ) +{ + vec3 vSkyLight = normalize( vec3( -1.0, -2.0, -0.5 ) ); + + float fSkyBlend = vNormal.y * 0.5 + 0.5; + + return mix( gSkyBottom, gSkyTop, fSkyBlend ); +} + + +void GenerateCubeVertex( const float vertexIndex, const mat4 mat, const vec3 vCubeCol, const vec3 vCameraPos, out SceneVertex outSceneVertex ) +{ + vec3 vNormal; + + GetCubeVertex( vertexIndex, mat, outSceneVertex.vWorldPos, vNormal ); + + outSceneVertex.vColor = vec3( 0.0 ); + + outSceneVertex.fAlpha = 1.0; + + float h = outSceneVertex.vWorldPos.y - gFloorHeight; + outSceneVertex.vColor += GetSkyLighting( vNormal ); + outSceneVertex.vColor *= mix( 1.0, fAOAmount, clamp( h, 0.0, 1.0 ) ); + + outSceneVertex.vColor += GetSunLighting( vNormal ); + + outSceneVertex.vColor *= vCubeCol; + + outSceneVertex.vColor += GetSunSpec( outSceneVertex.vWorldPos, vNormal, vCameraPos ); +} + + + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) +{ + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + +mat4 rotY( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + + +mat4 rotZ( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + #if 0 + return mat4( + 1, 0, 0, trans[0], + 0, 1, 0, trans[1], + 0, 0, 1, trans[2], + 0, 0, 0, 1); + #else + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); + #endif +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + +mat4 trInv(mat4 m) { + mat3 i = mat3( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); + vec3 t = -i * m[3].xyz; + + return mat4( + i[0], t[0], + i[1], t[1], + i[2], t[2], + 0, 0, 0, 1); +} + +mat4 transpose(mat4 m) { + return mat4( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +mat4 lookAt(vec3 eye, vec3 target, vec3 up) { + vec3 zAxis = normalize(eye - target); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + eye, 1); +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +mat4 cameraLookAt(vec3 eye, vec3 target, vec3 up) { + #if 1 + return inverse(lookAt(eye, target, up)); + #else + vec3 zAxis = normalize(target - eye); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1); + #endif + +} + + +float m1p1(float v) { + return v * 2. - 1.; +} + +float p1m1(float v) { + return v * .5 + .5; +} + +float inRange(float v, float minV, float maxV) { + return step(minV, v) * step(v, maxV); +} + +float at(float v, float target) { + return inRange(v, target - 0.1, target + 0.1); +} + +float easeInOutCubic(float pos) { + if (pos < 0.5) { + return 0.5 * pow(pos / 0.5, 3.); + } + pos -= 0.5; + pos /= 0.5; + pos = 1. - pos; + return (1. - pow(pos, 3.)) * 0.5 + 0.5; +} + +float inOut(float v) { + float t = fract(v); + if (t < 0.5) { + return easeInOutCubic(t / 0.5); + } + return easeInOutCubic(2. - t * 2.); +} + +const float perBlock = 4.; + +uniform vec3 color1; +uniform vec3 color2; + +void GetCubePosition( float fCubeId, float numCubes, out mat4 mat, out vec4 vCubeCol ) +{ + float fSeed = fCubeId; + float fPositionBase = fCubeId; + float fSize = 1.0; + + vec3 vCubeOrigin = vec3( 0.0, 0.0, 0.0 ); + + float across = 48.; + float down = floor(numCubes / across); + float uId = mod(fCubeId, across); + float vId = floor(fCubeId / across); + float u = uId / (across); + float v = vId / down; + float bxId = floor(uId / perBlock); + float bzId = floor(vId / perBlock); + float numRows = floor(numCubes / across); + float numBlocks = floor(numRows / perBlock); + + float uP = m1p1(u); + float vP = m1p1(v); + + float ll = length(vec2(uP, vP * 1.5)); + float snd = texture2D(sound, vec2(mix(0.001, 0.0199, uP), ll * 0.3)).a; + //float s2 = 0.;texture2D(sound, vec2(mix(0.02, 0.04, hash(u + v + 2.34)), hash(v) * 0.05)).a; + + vCubeOrigin.x += uP * across * 1.2 + bxId * 0. ; + float vSpace = numRows * 1.4 + numBlocks * 0.; + float z = vP * down * 1.4 + bzId * 0.; + vCubeOrigin.z += z; + float height = 1.; + //vCubeOrigin.y += pow(sin(time + v * 9.), 1.) * pow(cos(time + u * 8.17), 1.) * 4. * inOut(time * 0.1); + + mat = ident(); + mat *= trans(vCubeOrigin); + mat *= rotZ(p1m1(snd) * 10.); + mat *= rotY(p1m1(snd) * 10.); + mat *= uniformScale(mix(0., 3.0, pow(clamp(mix(-0.5, 1., snd), 0., 1.), 7.))); + + vec3 vRandCol; + + float st = step(0.9, snd); + float h = 0. + floor(time * 0.0) * 0.1 + easeInOutCubic(fract(time * 0.1)) * 0.1; + //vCubeCol.rgb = hsv2rgb(vec3(mix(h, h + 0.5, st), + // st,//pow(snd, 0.), + // 1)); + vCubeCol.rgb = color1; + //vCubeCol.rgb = mix(vCubeCol.rgb, vec3(1,0,0), step(0.9, snd)); + vCubeCol.rgb = mix(vCubeCol.rgb, color2, step(0.98, snd)); + vCubeCol.a = 0.;vCubeOrigin.z / vSpace; +} + +float goop(float t) { + return sin(t) * sin(t * 0.27) * sin(t * 0.13) * sin(t * 0.73); +} + + +void main() +{ + SceneVertex sceneVertex; + + float fov = 1.8; + +// vec3 vCameraTarget = vec3( 300, -400.6, 500.0 ); +// vec3 vCameraPos = vec3(-45.1, 20., -0.); + + vec3 vCameraTarget = vec3( 0, 0., 0.0 ); + vec3 vCameraPos = vCameraTarget + vec3(0, 100., 0); + float ca = 0.; + + // get sick! + ca = time * 0.1; + vec3 vCameraUp = vec3( 1, 0, 0 ); + // vec3 vCameraUp = vec3( 0, 0, 1 ); + + vec3 vCameraForwards = normalize(vCameraTarget - vCameraPos); + + float vertexIndex = vertexId; + + + float fCubeId = floor( vertexIndex / g_cubeVertexCount ); + float fCubeVertex = mod( vertexIndex, g_cubeVertexCount ); + float fNumCubes = floor( vertexCount / g_cubeVertexCount ); + + mat4 mCube; + vec4 vCubeCol; + + GetCubePosition( fCubeId, fNumCubes, mCube, vCubeCol ); + + GenerateCubeVertex( fCubeVertex, mCube, vCubeCol.xyz, vCameraPos, sceneVertex ); + + mat4 m = persp(radians(45.), resolution.x / resolution.y, 0.1, 1000.); + m *= cameraLookAt(vCameraPos, vCameraTarget, vCameraUp); + gl_Position = m * vec4(sceneVertex.vWorldPos, 1); + + // Final output color + float fExposure = gExposure;// min( gExposure, time * 0.1 ); + vec3 vFinalColor = sqrt( vec3(1.0) - exp2( sceneVertex.vColor * -fExposure ) ); + + v_color = mix(vec4(vFinalColor, 1), background, vCubeCol.a); + +} + +` + }, + revisionId: "d7anES7ef6WrrDwsy", + revisionUrl: "https://www.vertexshaderart.com/art/d7anES7ef6WrrDwsy/revision/d7anES7ef6WrrDwsy", + artUrl: "https://www.vertexshaderart.com/art/d7anES7ef6WrrDwsy", + origUrl: "https://www.vertexshaderart.com/art/d7anES7ef6WrrDwsy" +}; diff --git a/examples/js/index/effects/cyty.js b/examples/js/index/effects/cyty.js new file mode 100644 index 0000000..b8e7620 --- /dev/null +++ b/examples/js/index/effects/cyty.js @@ -0,0 +1,675 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "bbsF39W6bJo3j4R3j", + createdAt: "2016-01-09T08:24:22.209Z", + modifiedAt: "2016-01-25T18:24:26.127Z", + origId: "xvg4vyvfWjCvKZQfW", + name: "cyty", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 100000, + mode: "TRIANGLES", + sound: "https://soundcloud.com/stuart-king/kraftwerk-the-model-stuart", + lineSize: "NATIVE", + backgroundColor: [ + 1, + 1, + 1, + 1 + ], + shader: `/* + + ___ ___ _______ ________ _________ _______ ___ ___ +|\ \ / /|\ ___ \ |\ __ \|\___ ___|\ ___ \ |\ \ / /| +\ \ \ / / \ \ __/|\ \ \|\ \|___ \ \_\ \ __/| \ \ \/ / / + \ \ \/ / / \ \ \_|/_\ \ _ _\ \ \ \ \ \ \_|/__ \ \ / / + \ \ / / \ \ \_|\ \ \ \\ \| \ \ \ \ \ \_|\ \ / \/ + \ \__/ / \ \_______\ \__\\ _\ \ \__\ \ \_______\/ /\ \ + \|__|/ \|_______|\|__|\|__| \|__| \|_______/__/ /\ __\ + |__|/ \|__| + + + ________ ___ ___ ________ ________ _______ ________ +|\ ____\|\ \|\ \|\ __ \|\ ___ \|\ ___ \ |\ __ \ +\ \ \___|\ \ \\\ \ \ \|\ \ \ \_|\ \ \ __/|\ \ \|\ \ + \ \_____ \ \ __ \ \ __ \ \ \ \\ \ \ \_|/_\ \ _ _\ + \|____|\ \ \ \ \ \ \ \ \ \ \ \_\\ \ \ \_|\ \ \ \\ \| + ____\_\ \ \__\ \__\ \__\ \__\ \_______\ \_______\ \__\\ _\ + |\_________\|__|\|__|\|__|\|__|\|_______|\|_______|\|__|\|__| + \|_________| + + + ________ ________ _________ +|\ __ \|\ __ \|\___ ___\ +\ \ \|\ \ \ \|\ \|___ \ \_| + \ \ __ \ \ _ _\ \ \ \ + \ \ \ \ \ \ \\ \| \ \ \ + \ \__\ \__\ \__\\ _\ \ \__\ + \|__|\|__|\|__|\|__| \|__| + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + +vec3 gSunColor = vec3(1.0, 1.2, 1.4) * 10.; + +vec3 gSkyTop = vec3( 0.1, 0.2, 0.8 ) * 0.5; +vec3 gSkyBottom = vec3( 0.5, 0.8, 1.0 ) * 1.5; + +vec3 gCubeColor = vec3(1.0, 1.0, 1.0); +float gExposure = 0.3; + +float gCubeColorRandom = 0.0; + +#define MOVE_OUTWARDS + +float fAOAmount = 0.8; +float gFloorHeight = -1.0; +float g_cameraFar = 1000.0; + +#define PI radians( 180.0 ) + + +vec3 GetSunDir() +{ + return normalize( vec3( 20.0, 40.3, -10.4 ) ); +} + + +void GetQuadInfo( const float vertexIndex, out vec2 quadVertId, out float quadId ) +{ + float twoTriVertexIndex = mod( vertexIndex, 6.0 ); + float triVertexIndex = mod( vertexIndex, 3.0 ); + + if ( twoTriVertexIndex < 0.5 ) quadVertId = vec2( 0.0, 0.0 ); + else if ( twoTriVertexIndex < 1.5 ) quadVertId = vec2( 1.0, 0.0 ); + else if ( twoTriVertexIndex < 2.5 ) quadVertId = vec2( 0.0, 1.0 ); + else if ( twoTriVertexIndex < 3.5 ) quadVertId = vec2( 1.0, 0.0 ); + else if ( twoTriVertexIndex < 4.5 ) quadVertId = vec2( 1.0, 1.0 ); + else quadVertId = vec2( 0.0, 1.0 ); + + quadId = floor( vertexIndex / 6.0 ); +} + + +void GetQuadTileInfo( const vec2 quadVertId, const float quadId, const vec2 vDim, out vec2 vQuadTileIndex, out vec2 vQuadUV ) +{ + vQuadTileIndex.x = floor( mod( quadId, vDim.x ) ); + vQuadTileIndex.y = floor( quadId / vDim.x ); + + vQuadUV.x = floor(quadVertId.x + vQuadTileIndex.x); + vQuadUV.y = floor(quadVertId.y + vQuadTileIndex.y); + + vQuadUV = vQuadUV * (1.0 / vDim); +} + + +void GetQuadTileInfo( const float vertexIndex, const vec2 vDim, out vec2 vQuadTileIndex, out vec2 vQuadUV ) +{ + vec2 quadVertId; + float quadId; + GetQuadInfo( vertexIndex, quadVertId, quadId ); + GetQuadTileInfo( quadVertId, quadId, vDim, vQuadTileIndex, vQuadUV ); +} + + +void GetMatrixFromZY( const vec3 vZ, const vec3 vY, out mat3 m ) +{ + vec3 vX = normalize( cross( vY, vZ ) ); + vec3 vOrthoY = normalize( cross( vZ, vX ) ); + m[0] = vX; + m[1] = vOrthoY; + m[2] = vZ; +} + + +void GetMatrixFromZ( vec3 vZAxis, out mat3 m ) +{ + vec3 vZ = normalize(vZAxis); + vec3 vY = vec3( 0.0, 1.0, 0.0 ); + if ( abs(vZ.y) > 0.99 ) + { + vY = vec3( 1.0, 0.0, 0.0 ); + } + GetMatrixFromZY( vZ, vY, m ); +} + + +struct SceneVertex +{ + vec3 vWorldPos; + vec3 vColor; + float fAlpha; +}; + + +float GetCosSunRadius() +{ + return 0.01; +} + + +float GetSunIntensity() +{ + return 0.001; +} + + +vec3 GetSkyColor( vec3 vViewDir ) +{ + return mix( gSkyBottom, gSkyTop, max( 0.0, vViewDir.y ) ); +} +#define g_cubeFaces 6.0 +#define g_cubeVerticesPerFace ( 2.0 * 3.0 ) +#define g_cubeVertexCount ( g_cubeVerticesPerFace * g_cubeFaces ) + +// 6 7 +// +----------+ +// /| /| +// 2 / | 3/ | +// +----------+ | +// | | | | +// Y Z | 4| | 5| +// | +-------|--+ +// ^ / | / | / +// |/ 0|/ 1|/ +// +--> X +----------+ + +vec3 GetCubeVertex( float fVertexIndex ) +{ + vec3 fResult = vec3( 1.0 ); + + float f = fVertexIndex / 8.0; + if ( fract( f * 4.0 ) < 0.5 ) + { + fResult.x = -fResult.x; + } + + if ( fract( f * 2.0 ) < 0.5 ) + { + fResult.y = -fResult.y; + } + + if ( fract( f ) < 0.5 ) + { + fResult.z = -fResult.z; + } + + return fResult; +} + + +void GetCubeVertex( const float vertexIndex, const mat4 mat, out vec3 vWorldPos, out vec3 vWorldNormal ) +{ + float fFaceIndex = floor( vertexIndex / g_cubeFaces ); + + vec3 v0, v1, v2, v3; + + if ( fFaceIndex < 0.5 ) + { + v0 = GetCubeVertex( 0.0 ); + v1 = GetCubeVertex( 2.0 ); + v2 = GetCubeVertex( 3.0 ); + v3 = GetCubeVertex( 1.0 ); + } + else if ( fFaceIndex < 1.5 ) + { + v0 = GetCubeVertex( 5.0 ); + v1 = GetCubeVertex( 7.0 ); + v2 = GetCubeVertex( 6.0 ); + v3 = GetCubeVertex( 4.0 ); + } + else if ( fFaceIndex < 2.5 ) + { + v0 = GetCubeVertex( 1.0 ); + v1 = GetCubeVertex( 3.0 ); + v2 = GetCubeVertex( 7.0 ); + v3 = GetCubeVertex( 5.0 ); + } + else if ( fFaceIndex < 3.5 ) + { + v0 = GetCubeVertex( 4.0 ); + v1 = GetCubeVertex( 6.0 ); + v2 = GetCubeVertex( 2.0 ); + v3 = GetCubeVertex( 0.0 ); + } + else if ( fFaceIndex < 4.5 ) + { + v0 = GetCubeVertex( 2.0 ); + v1 = GetCubeVertex( 6.0 ); + v2 = GetCubeVertex( 7.0 ); + v3 = GetCubeVertex( 3.0 ); + } + else + { + v0 = GetCubeVertex( 1.0 ); + v1 = GetCubeVertex( 5.0 ); + v2 = GetCubeVertex( 4.0 ); + v3 = GetCubeVertex( 0.0 ); + } + #if 0 + v0 = (vec4(v0, 1) * mat).xyz; + v1 = (vec4(v1, 1) * mat).xyz; + v2 = (vec4(v2, 1) * mat).xyz; + v3 = (vec4(v3, 1) * mat).xyz; + #else + v0 = (mat * vec4(v0, 1)).xyz; + v1 = (mat * vec4(v1, 1)).xyz; + v2 = (mat * vec4(v2, 1)).xyz; + v3 = (mat * vec4(v3, 1)).xyz; + #endif + float fFaceVertexIndex = mod( vertexIndex, 6.0 ); + + if ( fFaceVertexIndex < 0.5 ) + { + vWorldPos = v0; + } + else if ( fFaceVertexIndex < 1.5 ) + { + vWorldPos = v1; + } + else if ( fFaceVertexIndex < 2.5 ) + { + vWorldPos = v2; + } + else if ( fFaceVertexIndex < 3.5 ) + { + vWorldPos = v0; + } + else if ( fFaceVertexIndex < 4.5 ) + { + vWorldPos = v2; + } + else + { + vWorldPos = v3; + } + + vWorldNormal = normalize( cross( v1 - v0, v2 - v0 ) ); +} + + +vec3 GetSunLighting( const vec3 vNormal ) +{ + vec3 vLight = -GetSunDir(); + + float NdotL = max( 0.0, dot( vNormal, -vLight ) ); + + return gSunColor * NdotL; +} + + +vec3 GetSunSpec( const vec3 vPos, const vec3 vNormal, const vec3 vCameraPos ) +{ + vec3 vLight = -GetSunDir(); + + vec3 vView = normalize( vCameraPos - vPos ); + + vec3 vH = normalize( vView - vLight ); + + float NdotH = max( 0.0, dot( vNormal, vH ) ); + float NdotL = max( 0.0, dot( vNormal, -vLight ) ); + + float f = mix( 0.01, 1.0, pow( 1.0 - NdotL, 5.0 ) ); + + return gSunColor * pow( NdotH, 20.0 ) * NdotL * f * 4.0; +} + + +vec3 GetSkyLighting( const vec3 vNormal ) +{ + vec3 vSkyLight = normalize( vec3( -1.0, -2.0, -0.5 ) ); + + float fSkyBlend = vNormal.y * 0.5 + 0.5; + + return mix( gSkyBottom, gSkyTop, fSkyBlend ); +} + + +void GenerateCubeVertex( const float vertexIndex, const mat4 mat, const vec3 vCubeCol, const vec3 vCameraPos, out SceneVertex outSceneVertex ) +{ + vec3 vNormal; + + GetCubeVertex( vertexIndex, mat, outSceneVertex.vWorldPos, vNormal ); + + outSceneVertex.vColor = vec3( 0.0 ); + + outSceneVertex.fAlpha = 1.0; + + float h = outSceneVertex.vWorldPos.y - gFloorHeight; + outSceneVertex.vColor += GetSkyLighting( vNormal ); + outSceneVertex.vColor *= mix( 1.0, fAOAmount, clamp( h, 0.0, 1.0 ) ); + + outSceneVertex.vColor += GetSunLighting( vNormal ); + + outSceneVertex.vColor *= vCubeCol; + + outSceneVertex.vColor += GetSunSpec( outSceneVertex.vWorldPos, vNormal, vCameraPos ); +} + + + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) +{ + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + + +mat4 rotY( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + + +mat4 rotZ( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + #if 0 + return mat4( + 1, 0, 0, trans[0], + 0, 1, 0, trans[1], + 0, 0, 1, trans[2], + 0, 0, 0, 1); + #else + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); + #endif +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + +mat4 trInv(mat4 m) { + mat3 i = mat3( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); + vec3 t = -i * m[3].xyz; + + return mat4( + i[0], t[0], + i[1], t[1], + i[2], t[2], + 0, 0, 0, 1); +} + +mat4 transpose(mat4 m) { + return mat4( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +mat4 lookAt(vec3 eye, vec3 target, vec3 up) { + vec3 zAxis = normalize(eye - target); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + eye, 1); +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +mat4 cameraLookAt(vec3 eye, vec3 target, vec3 up) { + #if 1 + return inverse(lookAt(eye, target, up)); + #else + vec3 zAxis = normalize(target - eye); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1); + #endif + +} + + +float m1p1(float v) { + return v * 2. - 1.; +} + +float p1m1(float v) { + return v * .5 + .5; +} + +float inRange(float v, float minV, float maxV) { + return step(minV, v) * step(v, maxV); +} + +float at(float v, float target) { + return inRange(v, target - 0.1, target + 0.1); +} + + +const float perBlock = 4.; + +void GetCubePosition( float fCubeId, float numCubes, out mat4 mat, out vec4 vCubeCol ) +{ + float fSeed = fCubeId; + float fPositionBase = fCubeId; + float fSize = 1.0; + + vec3 vCubeOrigin = vec3( 0.0, 0.0, 0.0 ); + + float across = 48.; + float down = 32.; + float uId = mod(fCubeId, across); + float vId = floor(fCubeId / across); + float u = uId / (across - .1); + float v = vId / down; + float bxId = floor(uId / perBlock); + float bzId = floor(vId / perBlock); + float numRows = floor(numCubes / across); + float numBlocks = floor(numRows / perBlock); + + float snd = texture2D(sound, vec2(mix(0.015, 0.015, u), v * 0.1)).a; + float s2 = texture2D(sound, vec2(mix(0.02, 0.04, hash(u + v + 2.34)), hash(v) * 0.05)).a; + + vCubeOrigin.x += m1p1(u) * across * 1.05 + bxId; + float vSpace = numRows * 2.05 + numBlocks * 2.; + float z = v * down * 2.05 + bzId * 2.; + vCubeOrigin.z += fract(-time * 0.2 + z / vSpace) * vSpace; + float height = mix(0.1, 0.5, hash(fCubeId)) + smoothstep(0.75, 1., s2) * 1.; + vCubeOrigin.y += height;; + + mat = ident(); + mat *= trans(vCubeOrigin); + mat *= scale(vec3(1, height, 1)); + + vec3 vRandCol; + + + vCubeCol.rgb = vec3(1);//mix(vec3(0.5), vec3(1,1,1), pow(s2, 40.0)); + vCubeCol.rgb = mix(vCubeCol.rgb, vec3(0,0.5,1), step(0.75,s2)); + //vCubeCol.rgb = vec3(1. - pow(s2, 3.)); + vCubeCol.a = vCubeOrigin.z / vSpace; +} + +float goop(float t) { + return sin(t) * sin(t * 0.27) * sin(t * 0.13) * sin(t * 0.73); +} + + +void main() +{ + SceneVertex sceneVertex; + + float fov = 1.8; + + vec3 vCameraTarget = vec3( 10, 5.6, 1.0 ); + vec3 vCameraPos = vec3(10.1, 6., -0.); + float ca = 0.; + + // get sick! + // ca = time + sin(time) * 2.; + vec3 vCameraUp = vec3( sin(ca), cos(ca), 0.0 ); + + vec3 vCameraForwards = normalize(vCameraTarget - vCameraPos); + + mat3 mCamera; + GetMatrixFromZY( vCameraForwards, normalize(vCameraUp), mCamera ); + + float vertexIndex = vertexId; + + + float fCubeId = floor( vertexIndex / g_cubeVertexCount ); + float fCubeVertex = mod( vertexIndex, g_cubeVertexCount ); + float fNumCubes = floor( vertexCount / g_cubeVertexCount ); + + mat4 mCube; + vec4 vCubeCol; + + GetCubePosition( fCubeId, fNumCubes, mCube, vCubeCol ); + + GenerateCubeVertex( fCubeVertex, mCube, vCubeCol.xyz, vCameraPos, sceneVertex ); + + mat4 m = persp(radians(45.), resolution.x / resolution.y, 0.1, 1000.); + m *= cameraLookAt(vCameraPos, vCameraTarget, vCameraUp); + gl_Position = m * vec4(sceneVertex.vWorldPos, 1); + + // Final output color + float fExposure = gExposure;// min( gExposure, time * 0.1 ); + vec3 vFinalColor = sqrt( vec3(1.0) - exp2( sceneVertex.vColor * -fExposure ) ); + + v_color = mix(vec4(vFinalColor, 1), background, vCubeCol.a); + +}` + }, + revisionId: "8z2whXqEmXfeJTPD8", + revisionUrl: "https://www.vertexshaderart.com/art/bbsF39W6bJo3j4R3j/revision/8z2whXqEmXfeJTPD8", + artUrl: "https://www.vertexshaderart.com/art/undefined", + origUrl: "https://www.vertexshaderart.com/art/xvg4vyvfWjCvKZQfW" +}; diff --git a/examples/js/index/effects/discus.js b/examples/js/index/effects/discus.js new file mode 100644 index 0000000..618f654 --- /dev/null +++ b/examples/js/index/effects/discus.js @@ -0,0 +1,247 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "yX9SGHv6RPPqcsXvh", + createdAt: "2017-01-28T04:48:49.529Z", + modifiedAt: "2017-01-28T05:56:37.779Z", + origId: "yX9SGHv6RPPqcsXvh", + name: "discus", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 100000, + mode: "TRIANGLES", + sound: "https://soundcloud.com/beatsfar/grand-mas-mandoline", + lineSize: "NATIVE", + backgroundColor: [ + 1, + 0, + 0, + 1 + ], + shader: ` + + +#define PI radians(180.) + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +mat4 rotY( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + + +mat4 rotZ( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) { + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +float m1p1(float v) { + return v * 2. - 1.; +} + +float p1m1(float v) { + return v * 0.5 + 0.5; +} + +float inv(float v) { + return 1. - v; +} + +uniform float numSides; +#define NUM_EDGE_POINTS_PER_CIRCLE numSides +#define NUM_POINTS_PER_CIRCLE (NUM_EDGE_POINTS_PER_CIRCLE * 6.) +#define NUM_CIRCLES_PER_GROUP 2. +void getCirclePoint(const float id, const float inner, const float start, const float end, out vec3 pos) { + float outId = id - floor(id / 3.) * 2. - 1.; // 0 1 2 3 4 5 6 7 8 .. 0 1 2, 1 2 3, 2 3 4 + float ux = floor(id / 6.) + mod(id, 2.); + float vy = mod(floor(id / 2.) + floor(id / 3.), 2.); // change that 3. for cool fx + float u = ux / NUM_EDGE_POINTS_PER_CIRCLE; + float v = mix(inner, 1., vy); + float a = mix(start, end, u) * PI * 2. + PI * 0.0; + float s = sin(a); + float c = cos(a); + float x = c * v; + float y = s * v; + float z = 0.; + pos = vec3(x, y, z); +} + +float goop(float t) { + return sin(t) + sin(t * 0.27) + sin(t * 0.13) + sin(t * 0.73); +} + +float easeInOutSine(float t) { + return (-0.5 * (cos(PI * t) - 1.)); +} + +float mixer(float t, float timeOff, float duration) { + t = mod(t, duration * 2.0); + t = t - timeOff; + if (t > duration) { + t = duration + 1. - t; + } + return easeInOutSine(clamp(t, 0., 1.)); +} + +uniform float speed; +uniform float brightness; +uniform vec3 color1; +uniform vec3 color2; +uniform float rotation; +uniform float split; + +void main() { + float circleId = floor(vertexId / NUM_POINTS_PER_CIRCLE); + float groupId = floor(circleId / NUM_CIRCLES_PER_GROUP); + float pointId = mod(vertexId, NUM_POINTS_PER_CIRCLE); + float sliceId = mod(floor(vertexId / 6.), 2.); + float side = mix(-1., 1., step(0.5, mod(circleId, 2.))); + float numCircles = floor(vertexCount / NUM_POINTS_PER_CIRCLE); + float numGroups = floor(numCircles / NUM_CIRCLES_PER_GROUP); + float cu = circleId / numCircles; + float gv = groupId / numGroups; + float cgId = mod(circleId, NUM_CIRCLES_PER_GROUP); + float cgv = cgId / NUM_CIRCLES_PER_GROUP; + float ncgv = 1. - cgv; + + + float tm = time - cgv * 0.2; + float su = hash(groupId); + float snd = texture2D(sound, vec2(mix(0.01, 0.14, su), gv * 0.05)).a; + + //snd = pow(snd, mix(2., 0.5, su)); + + + vec3 pos; + float inner = mix(0.0, 1. - pow(snd, 4.), cgId); + float start = 0.;//fract(hash(sideId * 0.33) + sin(time * 0.1 + sideId) * 1.1); + float end = 1.; //start + hash(sideId + 1.); + getCirclePoint(pointId, inner, start, end, pos); + pos.z = cgv; + +// float historyX = mix(0.01, 0.14, u); +// snd = pow(snd, mix(2., 0.5, u)); + + + + // ---- + float gDown = floor(sqrt(numGroups)); + float gAcross = floor(numGroups / gDown); + vec3 offset0 = vec3( + mod(groupId, gAcross) - (gAcross - 1.) / 2., + floor(groupId / gAcross) - (gDown - 1.) / 2., + 0) * 0.2; + + // ---- + float ang = gv * 10.0; + vec3 offset1 = vec3(cos(ang), sin(ang), 0) * gv * 0.5; + + // ---- + vec3 offset2 = (vec3(hash(groupId), hash(groupId * 0.37), 0) * 2. - 1.) * 0.8; + + // ---- + ang = gv * 20.0; + float rad = floor(groupId / pow(2., gv + 3.)); + vec3 offset3 = vec3(cos(ang), sin(ang), 0) * mix(0.3, 0.7, rad); + + // 0-6 + float m = 0.;tm; //mod(tm, 4. * 3.); + float mix01 = mixer(m, 0., 3.); + float mix23 = mixer(m, 0., 3.); + float mix0123 = mixer(m, 0., 6.); + vec3 offset = + mix( + mix(offset0, offset1, mix01), + mix(offset2, offset3, mix23), + mix0123); + +// vec3 offset = vec3(hash(groupId) * 0.8, m1p1(hash(groupId * 0.37)), cgv); +// offset.x += m1p1(pow(snd, 5.0) + goop(groupId + time * 0.) * 0.1); +// offset.y += goop(groupId + time * 0.) * 0.1; + vec3 aspect = vec3(1, resolution.x / resolution.y, 1); + + mat4 mat = ident(); + mat *= scale(aspect / gAcross * 12.); + mat *= trans(vec3(0.25,0,0)); + mat *= rotZ(rotation); + mat *= trans(offset); + mat *= rotZ(offset.x * offset.y); + float sp = pow(snd, 5.0); + + mat *= uniformScale(mix(sp, 1. - sp, cgId) * 0.1 + sliceId * 0.0); + gl_Position = vec4((mat * vec4(pos, 1)).xyz, 1); + gl_PointSize = 4.; + + float hue = tm * 0.0 + mix(0., 20.2, hash(groupId * 0.23)); + float sat = 1. - pow(snd, 5.); + float pump = step(snd, split); //pow(snd, 2.); + float val = pump * brightness;//ncgv;//1.;//mix(0.0, 0.0, fract(circleId * 0.79)) + sliceId * .65; +// v_color = vec4(mix(color1, hsv2rgb(vec3(hue, sat, val)), pump), 1); + v_color = vec4(mix(color1, color2, pump), 1); + v_color.rgb *= v_color.a; +} +` + }, + revisionId: "yX9SGHv6RPPqcsXvh", + revisionUrl: "https://www.vertexshaderart.com/art/yX9SGHv6RPPqcsXvh/revision/yX9SGHv6RPPqcsXvh", + artUrl: "https://www.vertexshaderart.com/art/yX9SGHv6RPPqcsXvh", + origUrl: "https://www.vertexshaderart.com/art/yX9SGHv6RPPqcsXvh" +}; diff --git a/examples/js/index/effects/dotto-chouhoukei.js b/examples/js/index/effects/dotto-chouhoukei.js new file mode 100644 index 0000000..04beb67 --- /dev/null +++ b/examples/js/index/effects/dotto-chouhoukei.js @@ -0,0 +1,391 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "JANswiNfyJnruo62E", + createdAt: "2017-07-07T14:54:01.007Z", + modifiedAt: "2017-07-11T03:19:45.238Z", + origId: "JRyewraYLfP9taa98", + name: "dotto-chouhoukei", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 100000, + mode: "POINTS", + sound: "https://soundcloud.com/ambient_space/4-ali-khan-sol", + lineSize: "NATIVE", + backgroundColor: [ + 0, + 0, + 0, + 1 + ], + shader: `/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + + + + +#define PI radians(180.) + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +mat4 rotX(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + 1, 0, 0, 0, + 0, c, s, 0, + 0, -s, c, 0, + 0, 0, 0, 1); +} + +mat4 rotY(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + +mat4 rotZ(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + +mat4 trInv(mat4 m) { + mat3 i = mat3( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); + vec3 t = -i * m[3].xyz; + + return mat4( + i[0], t[0], + i[1], t[1], + i[2], t[2], + 0, 0, 0, 1); +} + +mat4 transpose(mat4 m) { + return mat4( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +mat4 lookAt(vec3 eye, vec3 target, vec3 up) { + vec3 zAxis = normalize(eye - target); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + eye, 1); +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +mat4 cameraLookAt(vec3 eye, vec3 target, vec3 up) { + #if 1 + return inverse(lookAt(eye, target, up)); + #else + vec3 zAxis = normalize(target - eye); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1); + #endif + +} + + + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) { + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +// times 2 minus 1 +float t2m1(float v) { + return v * 2. - 1.; +} + +// times .5 plus .5 +float t5p5(float v) { + return v * 0.5 + 0.5; +} + +float inv(float v) { + return 1. - v; +} + +void getCirclePoint(const float numEdgePointsPerCircle, const float id, const float inner, const float start, const float end, out vec3 pos) { + float outId = id - floor(id / 3.) * 2. - 1.; // 0 1 2 3 4 5 6 7 8 .. 0 1 2, 1 2 3, 2 3 4 + float ux = floor(id / 6.) + mod(id, 2.); + float vy = mod(floor(id / 2.) + floor(id / 3.), 2.); // change that 3. for cool fx + float u = ux / numEdgePointsPerCircle; + float v = mix(inner, 1., vy); + float a = mix(start, end, u) * PI * 2. + PI * 0.0; + float s = sin(a); + float c = cos(a); + float x = c * v; + float y = s * v; + float z = 0.; + pos = vec3(x, y, z); +} + + +#define CUBE_POINTS_PER_FACE 6. +#define FACES_PER_CUBE 6. +#define POINTS_PER_CUBE (CUBE_POINTS_PER_FACE * FACES_PER_CUBE) +void getCubePoint(const float id, out vec3 position, out vec3 normal) { + float quadId = floor(mod(id, POINTS_PER_CUBE) / CUBE_POINTS_PER_FACE); + float sideId = mod(quadId, 3.); + float flip = mix(1., -1., step(2.5, quadId)); + // 0 1 2 1 2 3 + float facePointId = mod(id, CUBE_POINTS_PER_FACE); + float pointId = mod(facePointId - floor(facePointId / 3.0), 6.0); + float a = pointId * PI * 2. / 4. + PI * 0.25; + vec3 p = vec3(cos(a), 0.707106781, sin(a)) * flip; + vec3 n = vec3(0, 1, 0) * flip; + float lr = mod(sideId, 2.); + float ud = step(2., sideId); + mat4 mat = rotX(lr * PI * 0.5); + mat *= rotZ(ud * PI * 0.5); + position = (mat * vec4(p, 1)).xyz; + normal = (mat * vec4(n, 0)).xyz; +} + +void main() { + float deep = floor(pow(vertexCount, 0.33333)); + float down = floor(deep); + float across = floor(vertexCount / down / deep); + + float cx = mod(vertexId, across); + float cy = mod(floor(vertexId / across), down); + float cz = floor(vertexId / across / down); + float cu = cx / (across - 1.); + float cv = cy / (down - 1.); + float cw = cz / (deep - 1.); + + float ca = cu * 2. - 1.; + float cd = cv * 2. - 1.; + float ce = cw * 2. - 1.; + + float tm = time * .1; + mat4 mat = persp(radians(60.0), resolution.x / resolution.y, .1, 1000.0); + float rad = 2.; + vec3 eye = vec3(cos(tm) * rad, sin(tm * 0.9) * rad * 1.1, sin(tm) * rad); + vec3 target = vec3(0); + vec3 up = vec3(0,1,0); + + mat *= cameraLookAt(eye, target, up); + mat *= trans(vec3(ca, ce, cd) * 2.); + + vec3 pos = vec3(0); + + gl_Position = mat * vec4(pos, 1); + + float sz = 1. - abs((gl_Position.z / gl_Position.w * .5 + .5)); + float st = time; //-0.5 * PI * 10.; + float minRez = min(resolution.x, resolution.y); + gl_PointSize = mix(minRez / 8000., pow(sz, 1.) * 120. * minRez / 1000., sin(st * .2) * .5 + .5); + gl_PointSize = max(gl_PointSize, 1.0); + + float z = gl_Position.w / gl_Position.z * .5 + .5; + float hue = 1.1;// + mix(.4, .9, sin(st * .1) * .5 + .5); + float sat = 1.; + float val = 1. ; + + v_color = vec4(hsv2rgb(vec3(hue, sat, val)), 1); +} + +` + }, + revisionId: "qRsQkY9R9zMTgAdsN", + revisionUrl: "https://www.vertexshaderart.com/art/JANswiNfyJnruo62E/revision/qRsQkY9R9zMTgAdsN", + artUrl: "https://www.vertexshaderart.com/art/undefined", + origUrl: "https://www.vertexshaderart.com/art/JRyewraYLfP9taa98" +}; diff --git a/examples/js/index/effects/hexit2.js b/examples/js/index/effects/hexit2.js new file mode 100644 index 0000000..abe9fef --- /dev/null +++ b/examples/js/index/effects/hexit2.js @@ -0,0 +1,224 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "d7anES7ef6WrrDwsy", + createdAt: "2017-01-28T04:48:49.529Z", + modifiedAt: "2017-01-28T05:56:37.779Z", + origId: "yey7qrMtmhZZhq2K6", + name: "hexit2", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 100000, + mode: "TRIANGLES", + sound: "https://soundcloud.com/beatsfar/grand-mas-mandoline", + lineSize: "NATIVE", + backgroundColor: [ + 1, + 0, + 0, + 1 + ], + shader: ` +#define PI radians(180.0) + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +mat4 rotY( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + + +mat4 rotZ( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) { + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +float m1p1(float v) { + return v * 2. - 1.; +} + +float p1m1(float v) { + return v * 0.5 + 0.5; +} + +float inv(float v) { + return 1. - v; +} + +uniform float numSides; + +#define NUM_EDGE_POINTS_PER_CIRCLE numSides +#define NUM_POINTS_PER_CIRCLE (NUM_EDGE_POINTS_PER_CIRCLE * 6.) +#define NUM_CIRCLES_PER_GROUP 1. +void getCirclePoint(const float id, const float inner, const float start, const float end, out vec3 pos) { + float outId = id - floor(id / 3.) * 2. - 1.; // 0 1 2 3 4 5 6 7 8 .. 0 1 2, 1 2 3, 2 3 4 + float ux = floor(id / 6.) + mod(id, 2.); + float vy = mod(floor(id / 2.) + floor(id / 3.), 2.); // change that 3. for cool fx + float u = ux / NUM_EDGE_POINTS_PER_CIRCLE; + float v = mix(inner, 1., vy); + float a = mix(start, end, u) * PI * 2. + PI * 0.0; + float s = sin(a); + float c = cos(a); + float x = c * v; + float y = s * v; + float z = 0.; + pos = vec3(x, y, z); +} + +float goop(float t) { + return sin(t) + sin(t * 0.27) + sin(t * 0.13) + sin(t * 0.73); +} + +float easeInOutSine(float t) { + return (-0.5 * (cos(PI * t) - 1.)); +} + +float mixer(float t, float timeOff, float duration) { + t = mod(t, duration * 2.0); + t = t - timeOff; + if (t > duration) { + t = duration + 1. - t; + } + return easeInOutSine(clamp(t, 0., 1.)); +} + +uniform float s1; +uniform float s2; +uniform float s3; +uniform float s4; + +void main() { + float circleId = floor(vertexId / NUM_POINTS_PER_CIRCLE); + float groupId = floor(circleId / NUM_CIRCLES_PER_GROUP); + float pointId = mod(vertexId, NUM_POINTS_PER_CIRCLE); + float sliceId = mod(floor(vertexId / 6.), 2.); + float side = mix(-1., 1., step(0.5, mod(circleId, 2.))); + float numCircles = floor(vertexCount / NUM_POINTS_PER_CIRCLE); + float numGroups = floor(numCircles / NUM_CIRCLES_PER_GROUP); + float cu = circleId / numCircles; + float gv = groupId / numGroups; + float cgId = mod(circleId, NUM_CIRCLES_PER_GROUP); + float cgv = cgId / NUM_CIRCLES_PER_GROUP; + float ncgv = 1. - cgv; + + + //snd = pow(snd, mix(2., 0.5, su)); + + + vec3 pos; + float inner = 0.;//mix(0.0, 1. - pow(snd, 4.), cgId); + float start = 0.;//fract(hash(sideId * 0.33) + sin(time * 0.1 + sideId) * 1.1); + float end = 1.; //start + hash(sideId + 1.); + getCirclePoint(pointId, inner, start, end, pos); + pos.z = cgv; + +// float historyX = mix(0.01, 0.14, u); +// snd = pow(snd, mix(2., 0.5, u)); + + + + // ---- + float gDown = floor(sqrt(numGroups)); + float gAcross = floor(numGroups / gDown); + float gx = mod(groupId, gAcross); + float gy = floor(groupId / gAcross); + vec3 offset = vec3( + gx - (gAcross - 1.) / 2. + mod(gy, 2.) * 0.5, + gy - (gDown - 1.) / 2., + 0) * 0.17; + + float tm = time - cgv * 0.2; + float su = hash(groupId); + float snd = texture2D(sound, vec2(mix(0.001, 0.015, su), length(offset) * 0.125)).a; + + + +// vec3 offset = vec3(hash(groupId) * 0.8, m1p1(hash(groupId * 0.37)), cgv); +// offset.x += m1p1(pow(snd, 5.0) + goop(groupId + time * 0.) * 0.1); +// offset.y += goop(groupId + time * 0.) * 0.1; + vec3 aspect = vec3(1, resolution.x / resolution.y, 1); + + mat4 mat = ident(); + mat *= scale(aspect * (0.2 / 3.0) * numSides); + mat *= trans(vec3(0.25,0,0)); + mat *= rotZ(-time * 0. + snd * .0); + mat *= trans(offset); + float sp = pow(snd, 5.0); + + mat *= rotZ(time * 0.01 + sin(gx * s1 * 0.1) + cos(gy * s2 * 0.1)); + mat *= uniformScale(0.1 * pow(snd, 0.));// + -sin((time + 0.5) * 6.) * 0.01); + //mat *= uniformScale(mix(sp, 1. - sp, cgId) * 0.1 + sliceId * 0.0); + gl_Position = vec4((mat * vec4(pos, 1)).xyz, 1); + gl_PointSize = 4.; + + float hue = tm * 0.05 + fract(snd * 2.5) * 0.2 + 0.3 + mix(0., .02, length(offset)); + float sat = pow(snd, 5.); + float val = mix(hash(groupId), 1.0, step(0.98, snd));//ncgv;//1.;//mix(0.0, 0.0, fract(circleId * 0.79)) + sliceId * .65; + v_color = vec4(hsv2rgb(vec3(hue, sat, val)), 1); + v_color.rgb *= v_color.a; +} +` + }, + revisionId: "yey7qrMtmhZZhq2K6", + revisionUrl: "https://www.vertexshaderart.com/art/yey7qrMtmhZZhq2K6/revision/yey7qrMtmhZZhq2K6", + artUrl: "https://www.vertexshaderart.com/art/yey7qrMtmhZZhq2K6", + origUrl: "https://www.vertexshaderart.com/art/yey7qrMtmhZZhq2K6" +}; diff --git a/examples/js/index/effects/loop-test.js b/examples/js/index/effects/loop-test.js new file mode 100644 index 0000000..14f5b7d --- /dev/null +++ b/examples/js/index/effects/loop-test.js @@ -0,0 +1,323 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "ZFSiQpx33DLDg9hmd", + createdAt: "2018-06-06T08:31:10.941Z", + modifiedAt: "2018-06-06T08:31:10.941Z", + origId: null, + name: "loop-test", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 100000, + mode: "TRIANGLES", + sound: "https://soundcloud.com/greggman/testing-1-2-3", + lineSize: "NATIVE", + backgroundColor: [ + 1, + 1, + 1, + 1 + ], + shader: `/* + +VertexShaderArt Boilerplate Library + +*/ + + + + +#define PI radians(180.) + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +mat4 rotX(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + 1, 0, 0, 0, + 0, c, s, 0, + 0, -s, c, 0, + 0, 0, 0, 1); +} + +mat4 rotY(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + +mat4 rotZ(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + +mat4 trInv(mat4 m) { + mat3 i = mat3( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); + vec3 t = -i * m[3].xyz; + + return mat4( + i[0], t[0], + i[1], t[1], + i[2], t[2], + 0, 0, 0, 1); +} + +mat4 transpose(mat4 m) { + return mat4( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +mat4 lookAt(vec3 eye, vec3 target, vec3 up) { + vec3 zAxis = normalize(eye - target); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + eye, 1); +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +mat4 cameraLookAt(vec3 eye, vec3 target, vec3 up) { + #if 1 + return inverse(lookAt(eye, target, up)); + #else + vec3 zAxis = normalize(target - eye); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1); + #endif + +} + + + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) { + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +// times 2 minus 1 +float t2m1(float v) { + return v * 2. - 1.; +} + +// times .5 plus .5 +float t5p5(float v) { + return v * 0.5 + 0.5; +} + +float inv(float v) { + return 1. - v; +} + +void getCirclePoint(const float numEdgePointsPerCircle, const float id, const float inner, const float start, const float end, out vec3 pos) { + float outId = id - floor(id / 3.) * 2. - 1.; // 0 1 2 3 4 5 6 7 8 .. 0 1 2, 1 2 3, 2 3 4 + float ux = floor(id / 6.) + mod(id, 2.); + float vy = mod(floor(id / 2.) + floor(id / 3.), 2.); // change that 3. for cool fx + float u = ux / numEdgePointsPerCircle; + float v = mix(inner, 1., vy); + float a = mix(start, end, u) * PI * 2. + PI * 0.0; + float s = sin(a); + float c = cos(a); + float x = c * v; + float y = s * v; + float z = 0.; + pos = vec3(x, y, z); +} + + +#define CUBE_POINTS_PER_FACE 6. +#define FACES_PER_CUBE 6. +#define POINTS_PER_CUBE (CUBE_POINTS_PER_FACE * FACES_PER_CUBE) +void getCubePoint(const float id, out vec3 position, out vec3 normal) { + float quadId = floor(mod(id, POINTS_PER_CUBE) / CUBE_POINTS_PER_FACE); + float sideId = mod(quadId, 3.); + float flip = mix(1., -1., step(2.5, quadId)); + // 0 1 2 1 2 3 + float facePointId = mod(id, CUBE_POINTS_PER_FACE); + float pointId = mod(facePointId - floor(facePointId / 3.0), 6.0); + float a = pointId * PI * 2. / 4. + PI * 0.25; + vec3 p = vec3(cos(a), 0.707106781, sin(a)) * flip; + vec3 n = vec3(0, 1, 0) * flip; + float lr = mod(sideId, 2.); + float ud = step(2., sideId); + mat4 mat = rotX(lr * PI * 0.5); + mat *= rotZ(ud * PI * 0.5); + position = (mat * vec4(p, 1)).xyz; + normal = (mat * vec4(n, 0)).xyz; +} + +#if 1 +void main() { + float pointId = vertexId; + + vec3 pos; + vec3 normal; + getCubePoint(pointId, pos, normal); + float cubeId = floor(pointId / 36.); + float numCubes = floor(vertexCount / 36.); + float down = floor(sqrt(numCubes)); + float across = floor(numCubes / down); + + float cx = mod(cubeId, across); + float cy = floor(cubeId / across); + + float cu = cx / (across - 1.); + float cv = cy / (down - 1.); + + float ca = cu * 2. - 1.; + float cd = cv * 2. - 1.; + + float tm = time * 0.1; + mat4 mat = persp(radians(60.0), resolution.x / resolution.y, 0.1, 1000.0); + vec3 eye = vec3(cos(tm) * 1., sin(tm * 0.9) * .1 + 0.5, sin(tm) * 1.); + vec3 target = vec3(-eye.x, -1, -eye.z) * 5.5; + vec3 up = vec3(0,1,0); + + mat *= cameraLookAt(eye, target, up); + mat *= trans(vec3(ca, 0, cd) * 2.); + mat *= rotX(time + abs(ca) * 5.); + mat *= rotZ(time + abs(cd) * 6.); + mat *= uniformScale(0.03); + + + gl_Position = mat * vec4(pos, 1); + vec3 n = normalize((mat * vec4(normal, 0)).xyz); + + vec3 lightDir = normalize(vec3(0.3, 0.4, -1)); + + float snd = texture2D(sound, vec2(mix(0.1, 0.5, abs(atan(ca, cd)) / PI), length(vec2(ca,cd))) * .5).a; + + float hue = abs(ca * cd) * 2.; + float sat = pow(snd, 5.); + float val = mix(1., 0.5, abs(cd)); + vec3 color = hsv2rgb(vec3(hue, sat, val)); + v_color = vec4(color * (dot(n, lightDir) * 0.5 + 0.5), pow(snd, 2.)); + v_color.rgb *= v_color.a; +} +#endif + +` + }, + revisionId: "2a6NsC49zwe5Z6NZ6", + revisionUrl: "https://www.vertexshaderart.com/art/ZFSiQpx33DLDg9hmd/revision/2a6NsC49zwe5Z6NZ6", + artUrl: "https://www.vertexshaderart.com/art/undefined" +}; diff --git a/examples/js/index/effects/pookymelon.js b/examples/js/index/effects/pookymelon.js new file mode 100644 index 0000000..597e610 --- /dev/null +++ b/examples/js/index/effects/pookymelon.js @@ -0,0 +1,388 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "A8Zc7NFQdTdeKQimv", + createdAt: "2017-09-30T17:14:27.900Z", + modifiedAt: "2017-09-30T17:14:27.900Z", + origId: "7TrYkuK4aHzLqvZ7r", + name: "pookymelon", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 97200, + mode: "TRIANGLES", + sound: "https://soundcloud.com/mixmag-1/premiere-michael-klein-pan-pot-haze-effect", + lineSize: "NATIVE", + backgroundColor: [ + 0, + 0, + 0, + 1 + ], + shader: `/* + +┬ ┬┌─┐┬─┐┌┬┐┌─┐─┐ ┬┌─┐┬ ┬┌─┐┌┬┐┌─┐┬─┐┌─┐┬─┐┌┬┐ +└┐┌┘├┤ ├┬┘ │ ├┤ ┌┴┬┘└─┐├─┤├─┤ ││├┤ ├┬┘├─┤├┬┘ │ + └┘ └─┘┴└─ ┴ └─┘┴ └─└─┘┴ ┴┴ ┴─┴┘└─┘┴└─┴ ┴┴└─ ┴ + +*/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#define PI radians(180.0) + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +mat4 rotX(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + 1, 0, 0, 0, + 0, c, s, 0, + 0, -s, c, 0, + 0, 0, 0, 1); +} + +mat4 rotY(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + +mat4 rotZ(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + +mat4 trInv(mat4 m) { + mat3 i = mat3( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); + vec3 t = -i * m[3].xyz; + + return mat4( + i[0], t[0], + i[1], t[1], + i[2], t[2], + 0, 0, 0, 1); +} + +mat4 transpose(mat4 m) { + return mat4( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +mat4 lookAt(vec3 eye, vec3 target, vec3 up) { + vec3 zAxis = normalize(eye - target); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + eye, 1); +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +mat4 cameraLookAt(vec3 eye, vec3 target, vec3 up) { + #if 1 + return inverse(lookAt(eye, target, up)); + #else + vec3 zAxis = normalize(target - eye); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1); + #endif + +} + + + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) { + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +// times 2 minus 1 +float t2m1(float v) { + return v * 2. - 1.; +} + +// times .5 plus .5 +float t5p5(float v) { + return v * 0.5 + 0.5; +} + +float inv(float v) { + return 1. - v; +} + + +#define NUM_EDGE_POINTS_PER_CIRCLE 100. +#define NUM_POINTS_PER_DIVISION (NUM_EDGE_POINTS_PER_CIRCLE * 6.) +#define NUM_POINTS_PER_CIRCLE (NUM_SUBDIVISIONS_PER_CIRCLE * NUM_POINTS_PER_DIVISION) +void getCirclePoint(const float id, const float inner, const float start, const float end, out vec3 pos, out vec4 uvf, out float snd) { + float NUM_SUBDIVISIONS_PER_CIRCLE = floor(vertexCount / NUM_POINTS_PER_DIVISION); + float edgeId = mod(id, NUM_POINTS_PER_DIVISION); + float ux = floor(edgeId / 6.) + mod(edgeId, 2.); + float vy = mod(floor(id / 2.) + floor(id / 3.), 2.); // change that 3. for cool fx + float sub = floor(id / NUM_POINTS_PER_DIVISION); + float subV = sub / (NUM_SUBDIVISIONS_PER_CIRCLE - 1.); + float level = subV + vy / (NUM_SUBDIVISIONS_PER_CIRCLE - 1.); + float u = ux / NUM_EDGE_POINTS_PER_CIRCLE; + float v = 1.;//mix(inner, 1., level); + float ringId = sub + vy; + float ringV = ringId / NUM_SUBDIVISIONS_PER_CIRCLE; + float numRings = vertexCount / NUM_SUBDIVISIONS_PER_CIRCLE; + float a = mix(start, end, u) * PI * 2. + PI * 0.0; + float skew = 1. - step(0.5, mod(ringId - 2., 3.)); + float su = fract(abs(u * 2. - 1.) + time * 0.1); + + a += 1. / NUM_EDGE_POINTS_PER_CIRCLE * PI * 2.;// * 20. * sin(time * 1.) + snd * 1.5; + float s = sin(a); + float c = cos(a); + float z = mix(inner, 2., level) - vy / NUM_SUBDIVISIONS_PER_CIRCLE * 0.; + float x = c * v * z; + float y = s * v * z; + pos = vec3(x, y, 0.); + uvf = vec4(floor(edgeId / 6.) / NUM_EDGE_POINTS_PER_CIRCLE, subV, floor(id / 6.), sub); +} + +float goop(float t) { + return sin(t) + sin(t * 0.27) + sin(t * 0.13) + sin(t * 0.73); +} + +float modStep(float count, float steps) { + return mod(count, steps) / steps; +} + + +void main() { + float numQuads = floor(vertexCount / 6.); + float around = 180.; + float down = numQuads / around; + float quadId = floor(vertexId / 6.); + + float qx = mod(quadId, around); + float qy = floor(quadId / around); + + // 0--1 3 + // | / /| + // |/ / | + // 2 4--5 + // + // 0 1 0 1 0 1 + // 0 0 1 0 1 1 + + float edgeId = mod(vertexId, 6.); + float ux = mod(edgeId, 2.); + float vy = mod(floor(edgeId / 2.) + floor(edgeId / 3.), 2.); + + float qu = (qx + ux) / around; + float qv = (qy + vy) / down; + + float r = sin(qv * PI); + float x = cos(qu * PI * 2.) * r; + float z = sin(qu * PI * 2.) * r; + + vec3 pos = vec3(x, cos(qv * PI), z); + vec3 nrm = vec3( + cos((qx + .5) / around * PI * 2.), + cos((qy + .5) / down * PI), + sin((qx + .5) / around * PI * 2.) + ); + + float tm = time * 1.1; + float rd = mix(2., 3.5, t5p5(sin(time * 0.11))); + mat4 mat = persp(PI * 0.25, resolution.x / resolution.y, 0.1, 100.); + vec3 eye = vec3(cos(tm) * rd, sin(tm * 0.9) * .0 + 0., sin(tm) * rd); + vec3 target = vec3(0); + vec3 up = vec3(0,sin(tm),cos(tm)); + + float s = texture2D(sound, vec2(mix(0.1, .25, abs(qu * 2. - 1.)), mix(0., .12, qv))).a; + + mat *= cameraLookAt(eye, target, up); + mat *= uniformScale(mix(0.5, 2.5, pow(s + .15, 5.))); + + gl_Position = mat * vec4(pos, 1); + gl_PointSize = 4.; + + float odd = mod(floor(quadId / 2.), 2.); + float hue = time * .1 +s * .15; + float sat = mix(0., 3., pow(s, 5.)); + float val = mix(0.1, 1., pow(s + .4, 15.)); + v_color = vec4(hsv2rgb(vec3(hue, sat, val)), 1.); + + + v_color.rgb *= v_color.a; + + + +}` + }, + revisionId: "8TbPNzy7gAKpNs7Hj", + revisionUrl: "https://www.vertexshaderart.com/art/A8Zc7NFQdTdeKQimv/revision/8TbPNzy7gAKpNs7Hj", + artUrl: "https://www.vertexshaderart.com/art/undefined", + origUrl: "https://www.vertexshaderart.com/art/7TrYkuK4aHzLqvZ7r" +}; diff --git a/examples/js/index/effects/rollin.js b/examples/js/index/effects/rollin.js new file mode 100644 index 0000000..576cbed --- /dev/null +++ b/examples/js/index/effects/rollin.js @@ -0,0 +1,629 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "FPFBuCexgLQpriEoS", + createdAt: "2016-01-17T01:42:37.484Z", + modifiedAt: "2016-01-23T14:53:24.281Z", + origId: "bbsF39W6bJo3j4R3j", + name: "rollin", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 100000, + mode: "TRIANGLES", + sound: "https://soundcloud.com/chill/ed-sheeran-i-see-fire-kalev-remix", + lineSize: "NATIVE", + backgroundColor: [ + 1, + 1, + 1, + 1 + ], + shader: `/* + + ___ ___ _______ ________ _________ _______ ___ ___ +|\ \ / /|\ ___ \ |\ __ \|\___ ___|\ ___ \ |\ \ / /| +\ \ \ / / \ \ __/|\ \ \|\ \|___ \ \_\ \ __/| \ \ \/ / / + \ \ \/ / / \ \ \_|/_\ \ _ _\ \ \ \ \ \ \_|/__ \ \ / / + \ \ / / \ \ \_|\ \ \ \\ \| \ \ \ \ \ \_|\ \ / \/ + \ \__/ / \ \_______\ \__\\ _\ \ \__\ \ \_______\/ /\ \ + \|__|/ \|_______|\|__|\|__| \|__| \|_______/__/ /\ __\ + |__|/ \|__| + + + ________ ___ ___ ________ ________ _______ ________ +|\ ____\|\ \|\ \|\ __ \|\ ___ \|\ ___ \ |\ __ \ +\ \ \___|\ \ \\\ \ \ \|\ \ \ \_|\ \ \ __/|\ \ \|\ \ + \ \_____ \ \ __ \ \ __ \ \ \ \\ \ \ \_|/_\ \ _ _\ + \|____|\ \ \ \ \ \ \ \ \ \ \ \_\\ \ \ \_|\ \ \ \\ \| + ____\_\ \ \__\ \__\ \__\ \__\ \_______\ \_______\ \__\\ _\ + |\_________\|__|\|__|\|__|\|__|\|_______|\|_______|\|__|\|__| + \|_________| + + + ________ ________ _________ +|\ __ \|\ __ \|\___ ___\ +\ \ \|\ \ \ \|\ \|___ \ \_| + \ \ __ \ \ _ _\ \ \ \ + \ \ \ \ \ \ \\ \| \ \ \ + \ \__\ \__\ \__\\ _\ \ \__\ + \|__|\|__|\|__|\|__| \|__| + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + +vec3 gSunColor = vec3(1.0, 1.2, 1.4) * 10.1; + +vec3 gSkyTop = vec3( 0.1, 0.2, 0.8 ) * 0.5; +vec3 gSkyBottom = vec3( 0.5, 0.8, 1.0 ) * 1.5; + +vec3 gCubeColor = vec3(1.0, 1.0, 1.0); +float gExposure = 0.3; + +float gCubeColorRandom = 0.0; + +#define MOVE_OUTWARDS + +float fAOAmount = 0.8; +float gFloorHeight = -1.0; +float g_cameraFar = 1000.0; + +#define PI radians( 180.0 ) + + +vec3 GetSunDir() +{ + return normalize( vec3( 20.0, 40.3, -10.4 ) ); +} + +struct SceneVertex +{ + vec3 vWorldPos; + vec3 vColor; + float fAlpha; +}; + + +float GetCosSunRadius() +{ + return 0.01; +} + + +float GetSunIntensity() +{ + return 0.001; +} + + +vec3 GetSkyColor( vec3 vViewDir ) +{ + return mix( gSkyBottom, gSkyTop, max( 0.0, vViewDir.y ) ); +} +const float g_cubeFaces = 6.0; +const float g_cubeVerticesPerFace = ( 2.0 * 3.0 ); +const float g_cubeVertexCount = ( g_cubeVerticesPerFace * g_cubeFaces ); + +// 6 7 +// +----------+ +// /| /| +// 2 / | 3/ | +// +----------+ | +// | | | | +// Y Z | 4| | 5| +// | +-------|--+ +// ^ / | / | / +// |/ 0|/ 1|/ +// +--> X +----------+ + +vec3 GetCubeVertex( float fVertexIndex ) +{ + float f = fVertexIndex / 8.0; + return vec3( + mix(-1., 1., step(0.5, fract(f * 4.))), + mix(-1., 1., step(0.5, fract(f * 2.))), + mix(-1., 1., step(0.5, fract(f)))); +} + + +void GetCubeVertex( const float vertexIndex, const mat4 mat, out vec3 vWorldPos, out vec3 vWorldNormal ) +{ + float fFaceIndex = floor( vertexIndex / g_cubeFaces ); + + vec3 v0, v1, v2, v3; + + if ( fFaceIndex < 0.5 ) + { + v0 = GetCubeVertex( 0.0 ); + v1 = GetCubeVertex( 2.0 ); + v2 = GetCubeVertex( 3.0 ); + v3 = GetCubeVertex( 1.0 ); + } + else if ( fFaceIndex < 1.5 ) + { + v0 = GetCubeVertex( 5.0 ); + v1 = GetCubeVertex( 7.0 ); + v2 = GetCubeVertex( 6.0 ); + v3 = GetCubeVertex( 4.0 ); + } + else if ( fFaceIndex < 2.5 ) + { + v0 = GetCubeVertex( 1.0 ); + v1 = GetCubeVertex( 3.0 ); + v2 = GetCubeVertex( 7.0 ); + v3 = GetCubeVertex( 5.0 ); + } + else if ( fFaceIndex < 3.5 ) + { + v0 = GetCubeVertex( 4.0 ); + v1 = GetCubeVertex( 6.0 ); + v2 = GetCubeVertex( 2.0 ); + v3 = GetCubeVertex( 0.0 ); + } + else if ( fFaceIndex < 4.5 ) + { + v0 = GetCubeVertex( 2.0 ); + v1 = GetCubeVertex( 6.0 ); + v2 = GetCubeVertex( 7.0 ); + v3 = GetCubeVertex( 3.0 ); + } + else + { + v0 = GetCubeVertex( 1.0 ); + v1 = GetCubeVertex( 5.0 ); + v2 = GetCubeVertex( 4.0 ); + v3 = GetCubeVertex( 0.0 ); + } + v0 = (mat * vec4(v0, 1)).xyz; + v1 = (mat * vec4(v1, 1)).xyz; + v2 = (mat * vec4(v2, 1)).xyz; + v3 = (mat * vec4(v3, 1)).xyz; + + float fFaceVertexIndex = mod( vertexIndex, 6.0 ); + + if ( fFaceVertexIndex < 0.5 ) + { + vWorldPos = v0; + } + else if ( fFaceVertexIndex < 1.5 ) + { + vWorldPos = v1; + } + else if ( fFaceVertexIndex < 2.5 ) + { + vWorldPos = v2; + } + else if ( fFaceVertexIndex < 3.5 ) + { + vWorldPos = v0; + } + else if ( fFaceVertexIndex < 4.5 ) + { + vWorldPos = v2; + } + else + { + vWorldPos = v3; + } + + vWorldNormal = normalize( cross( v1 - v0, v2 - v0 ) ); +} + + +vec3 GetSunLighting( const vec3 vNormal ) +{ + vec3 vLight = -GetSunDir(); + + float NdotL = max( 0.0, dot( vNormal, -vLight ) ); + + return gSunColor * NdotL; +} + + +vec3 GetSunSpec( const vec3 vPos, const vec3 vNormal, const vec3 vCameraPos ) +{ + vec3 vLight = -GetSunDir(); + + vec3 vView = normalize( vCameraPos - vPos ); + + vec3 vH = normalize( vView - vLight ); + + float NdotH = max( 0.0, dot( vNormal, vH ) ); + float NdotL = max( 0.0, dot( vNormal, -vLight ) ); + + float f = mix( 0.01, 1.0, pow( 1.0 - NdotL, 5.0 ) ); + + return gSunColor * pow( NdotH, 20.0 ) * NdotL * f * 4.0; +} + + +vec3 GetSkyLighting( const vec3 vNormal ) +{ + vec3 vSkyLight = normalize( vec3( -1.0, -2.0, -0.5 ) ); + + float fSkyBlend = vNormal.y * 0.5 + 0.5; + + return mix( gSkyBottom, gSkyTop, fSkyBlend ); +} + + +void GenerateCubeVertex( const float vertexIndex, const mat4 mat, const vec3 vCubeCol, const vec3 vCameraPos, out SceneVertex outSceneVertex ) +{ + vec3 vNormal; + + GetCubeVertex( vertexIndex, mat, outSceneVertex.vWorldPos, vNormal ); + + outSceneVertex.vColor = vec3( 0.0 ); + + outSceneVertex.fAlpha = 1.0; + + float h = outSceneVertex.vWorldPos.y - gFloorHeight; + outSceneVertex.vColor += GetSkyLighting( vNormal ); + outSceneVertex.vColor *= mix( 1.0, fAOAmount, clamp( h, 0.0, 1.0 ) ); + + outSceneVertex.vColor += GetSunLighting( vNormal ); + + outSceneVertex.vColor *= vCubeCol; + + outSceneVertex.vColor += GetSunSpec( outSceneVertex.vWorldPos, vNormal, vCameraPos ); +} + + + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) +{ + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + +mat4 rotY( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + + +mat4 rotZ( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + #if 0 + return mat4( + 1, 0, 0, trans[0], + 0, 1, 0, trans[1], + 0, 0, 1, trans[2], + 0, 0, 0, 1); + #else + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); + #endif +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + +mat4 trInv(mat4 m) { + mat3 i = mat3( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); + vec3 t = -i * m[3].xyz; + + return mat4( + i[0], t[0], + i[1], t[1], + i[2], t[2], + 0, 0, 0, 1); +} + +mat4 transpose(mat4 m) { + return mat4( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +mat4 lookAt(vec3 eye, vec3 target, vec3 up) { + vec3 zAxis = normalize(eye - target); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + eye, 1); +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +mat4 cameraLookAt(vec3 eye, vec3 target, vec3 up) { + #if 1 + return inverse(lookAt(eye, target, up)); + #else + vec3 zAxis = normalize(target - eye); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1); + #endif + +} + + +float m1p1(float v) { + return v * 2. - 1.; +} + +float p1m1(float v) { + return v * .5 + .5; +} + +float inRange(float v, float minV, float maxV) { + return step(minV, v) * step(v, maxV); +} + +float at(float v, float target) { + return inRange(v, target - 0.1, target + 0.1); +} + +float easeInOutCubic(float pos) { + if (pos < 0.5) { + return 0.5 * pow(pos / 0.5, 3.); + } + pos -= 0.5; + pos /= 0.5; + pos = 1. - pos; + return (1. - pow(pos, 3.)) * 0.5 + 0.5; +} + +float inOut(float v) { + float t = fract(v); + if (t < 0.5) { + return easeInOutCubic(t / 0.5); + } + return easeInOutCubic(2. - t * 2.); +} + +const float perBlock = 4.; + +uniform vec3 baseColor; +uniform float heightMult; +uniform float period1; +uniform float period2; +uniform float p1; +uniform float p2; + +float spow(float v, float p) { + return pow(abs(v), p) * sign(v); +} + +void GetCubePosition( float fCubeId, float numCubes, out mat4 mat, out vec4 vCubeCol ) +{ + float fSeed = fCubeId; + float fPositionBase = fCubeId; + float fSize = 1.0; + + vec3 vCubeOrigin = vec3( 0.0, 0.0, 0.0 ); + + float across = 48.; + float down = 32.; + float uId = mod(fCubeId, across); + float vId = floor(fCubeId / across); + float u = uId / (across); + float v = vId / down; + float bxId = floor(uId / perBlock); + float bzId = floor(vId / perBlock); + float numRows = floor(numCubes / across); + float numBlocks = floor(numRows / perBlock); + + float snd = 1.;texture2D(sound, vec2(mix(0.015, 0.015, u), v * 0.1)).a; + float s2 = 0.;texture2D(sound, vec2(mix(0.02, 0.04, hash(u + v + 2.34)), hash(v) * 0.05)).a; + + vCubeOrigin.x += m1p1(u) * across * 1.0 + bxId * 0. ; + float vSpace = numRows * 2.0 + numBlocks * 0.; + float z = v * down * 2.0 + bzId * 0.; + vCubeOrigin.z += z; + float height = 1.; + vCubeOrigin.y += spow(sin(time + v * period1), p1) * spow(cos(time + u * period2), p2) * heightMult * inOut(time * 0.1); + + mat = ident(); + mat *= trans(vCubeOrigin); + mat *= scale(vec3(1, height, 1) * snd); + + vec3 vRandCol; + + + vCubeCol.rgb = hsv2rgb(vec3( + 0.2 + floor(time * 0.1) * 0.1 + easeInOutCubic(fract(time * 0.1)) * 0.1, + 0.95, + 1)); + vCubeCol.rgb = baseColor; + vCubeCol.a = vCubeOrigin.z / vSpace; +} + +float goop(float t) { + return sin(t) * sin(t * 0.27) * sin(t * 0.13) * sin(t * 0.73); +} + + +void main() +{ + SceneVertex sceneVertex; + + float fov = 1.8; + + vec3 vCameraTarget = vec3( 600, -400.6, 1000.0 ); + vec3 vCameraPos = vec3(-45.1, 6., -0.); + float ca = 0.; + + // get sick! + // ca = time + sin(time) * 2.; + vec3 vCameraUp = vec3( sin(ca), cos(ca), 0.0 ); + + vec3 vCameraForwards = normalize(vCameraTarget - vCameraPos); + + float vertexIndex = vertexId; + + + float fCubeId = floor( vertexIndex / g_cubeVertexCount ); + float fCubeVertex = mod( vertexIndex, g_cubeVertexCount ); + float fNumCubes = floor( vertexCount / g_cubeVertexCount ); + + mat4 mCube; + vec4 vCubeCol; + + GetCubePosition( fCubeId, fNumCubes, mCube, vCubeCol ); + + GenerateCubeVertex( fCubeVertex, mCube, vCubeCol.xyz, vCameraPos, sceneVertex ); + + mat4 m = persp(radians(45.), resolution.x / resolution.y, 0.1, 1000.); + m *= cameraLookAt(vCameraPos, vCameraTarget, vCameraUp); + gl_Position = m * vec4(sceneVertex.vWorldPos, 1); + + // Final output color + float fExposure = gExposure;// min( gExposure, time * 0.1 ); + vec3 vFinalColor = sqrt( vec3(1.0) - exp2( sceneVertex.vColor * -fExposure ) ); + + v_color = mix(vec4(vFinalColor, 1), background, vCubeCol.a); + +}` + }, + revisionId: "rThcGALqhF88J2BET", + revisionUrl: "https://www.vertexshaderart.com/art/FPFBuCexgLQpriEoS/revision/rThcGALqhF88J2BET", + artUrl: "https://www.vertexshaderart.com/art/undefined", + origUrl: "https://www.vertexshaderart.com/art/bbsF39W6bJo3j4R3j" +}; diff --git a/examples/js/index/effects/starfield.js b/examples/js/index/effects/starfield.js new file mode 100644 index 0000000..20cdb36 --- /dev/null +++ b/examples/js/index/effects/starfield.js @@ -0,0 +1,317 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "pfa9757K3NJx6euhN", + createdAt: "2019-02-21T11:38:22.742Z", + modifiedAt: "2019-02-26T06:41:10.489Z", + origId: null, + name: "starfield", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 100000, + mode: "POINTS", + sound: "", + lineSize: "NATIVE", + backgroundColor: [ + 0, + 0, + 0, + 1 + ], + shader: `#define PI radians(180.0) + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + +mat4 rotX(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + 1, 0, 0, 0, + 0, c, s, 0, + 0, -s, c, 0, + 0, 0, 0, 1); +} + +mat4 rotY(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + +mat4 rotZ(float angleInRadians) { + float s = sin(angleInRadians); + float c = cos(angleInRadians); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + +mat4 trInv(mat4 m) { + mat3 i = mat3( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); + vec3 t = -i * m[3].xyz; + + return mat4( + i[0], t[0], + i[1], t[1], + i[2], t[2], + 0, 0, 0, 1); +} + +mat4 transpose(mat4 m) { + return mat4( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +mat4 lookAt(vec3 eye, vec3 target, vec3 up) { + vec3 zAxis = normalize(eye - target); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + eye, 1); +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +mat4 cameraLookAt(vec3 eye, vec3 target, vec3 up) { + #if 1 + return inverse(lookAt(eye, target, up)); + #else + vec3 zAxis = normalize(target - eye); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1); + #endif + +} + + + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) { + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +// times 2 minus 1 +float t2m1(float v) { + return v * 2. - 1.; +} + +// times .5 plus .5 +float t5p5(float v) { + return v * 0.5 + 0.5; +} + +float inv(float v) { + return 1. - v; +} + + +void main() { + + float near = 0.01; + float far = 25.0; + mat4 pmat = persp(radians(60.0), resolution.x / resolution.y, near, far); + mat4 cmat = ident(); + cmat *= rotX(mouse.y * PI); + cmat *= rotY(mouse.x * -PI); + mat4 vmat = inverse(cmat); + + vec3 pos = vec3( + hash(vertexId * 0.123), + hash(vertexId * 0.357), + // fract(hash(vertexId * 0.531) - time * .01)) * vec3(2, 2, -40) - vec3(1, 1, -20); + fract(hash(vertexId * 0.531) + time * .05)) * 2. - 1.; + float d = length(pos); + pos *= vec3(2); + + vec3 boxMin = (cmat * vec4(-1, -1, 0, 1)).xyz; + vec3 boxMax = (cmat * vec4(1, 1, -20, 1)).xyz; + + + + + /* + + +--------+--------+--------+--------+ + | | | | | + | |\ | | | + | | \ | | | + | | \ | | | + +--------+--------+--------+--------+ + | | \ | | | + | | \ | | | + | | \ | | | + | | \| | | + +--------+--------c--------+--------+ + | | /| | | + | | / | | | + | | / | | | + | | / | | | + +--------+--------+--------+--------+ + | | / | | | + | | / | | | + | |/ | | | + | | | | | + +--------+--------+--------+--------+ + + */ + + + //mat[2][2] /= far; + //mat[2][3] /= far; + + gl_Position = pmat * vmat * vec4(pos, 1); + // gl_Position.z = gl_Position.z * gl_Position.w / far; + + + /* +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + + rangeInv = 1.0 / (near - far) + rangnInv = 1.0 / (0.01 - 25.0) + rangeInv = 1.0 / -24.99 + rangeInv = -0.040016006402561027 + + -0.1 * (0.1 + 25.0) * -0.04001 + 1 * (0.1 * 25.0 * -0.04001) * 2 = -0.000996 + -20 * (0.1 + 25.0) * -0.04001 + 1 * (0.1 * 25.0 * -0.04001) * 2 = 0.1988 + + + */ + + vec4 f = gl_Position; + float depth = f.z * .5 + .5; + + gl_PointSize = mix(10.0, 1.0, depth); + v_color = vec4(hsv2rgb(vec3(hash(vertexId * 0.237), 0.25, 1)), 1. - d); + v_color.rgb *= v_color.a; +// v_color = vec4(hsv2rgb(vec3(depth, 1, 1)), 1); +}` + }, + revisionId: "oTpAmrwEQsSFL3L3y", + revisionUrl: "https://www.vertexshaderart.com/art/pfa9757K3NJx6euhN/revision/oTpAmrwEQsSFL3L3y", + artUrl: "https://www.vertexshaderart.com/art/undefined" +}; diff --git a/examples/js/index/effects/ung.js b/examples/js/index/effects/ung.js new file mode 100644 index 0000000..f68da90 --- /dev/null +++ b/examples/js/index/effects/ung.js @@ -0,0 +1,621 @@ +/* eslint-disable require-trailing-comma/require-trailing-comma */ +/* eslint-disable no-useless-escape */ +export default { + _id: "9mqwFjEipb8pPtcPw", + createdAt: "2016-01-25T18:48:08.828Z", + modifiedAt: "2018-03-30T01:58:37.742Z", + origId: "FPFBuCexgLQpriEoS", + name: "ung", + username: "gman", + avatarUrl: "https://secure.gravatar.com/avatar/dcc0309895c3d6db087631813efaa9d1?default=retro&size=200", + settings: { + num: 100000, + mode: "TRIANGLES", + sound: "https://soundcloud.com/substruk-records/sub032-sixis-orbital-substruk", + lineSize: "NATIVE", + backgroundColor: [ + 1, + 1, + 1, + 1 + ], + shader: `/* + + ___ ___ _______ ________ _________ _______ ___ ___ +|\ \ / /|\ ___ \ |\ __ \|\___ ___|\ ___ \ |\ \ / /| +\ \ \ / / \ \ __/|\ \ \|\ \|___ \ \_\ \ __/| \ \ \/ / / + \ \ \/ / / \ \ \_|/_\ \ _ _\ \ \ \ \ \ \_|/__ \ \ / / + \ \ / / \ \ \_|\ \ \ \\ \| \ \ \ \ \ \_|\ \ / \/ + \ \__/ / \ \_______\ \__\\ _\ \ \__\ \ \_______\/ /\ \ + \|__|/ \|_______|\|__|\|__| \|__| \|_______/__/ /\ __\ + |__|/ \|__| + + + ________ ___ ___ ________ ________ _______ ________ +|\ ____\|\ \|\ \|\ __ \|\ ___ \|\ ___ \ |\ __ \ +\ \ \___|\ \ \\\ \ \ \|\ \ \ \_|\ \ \ __/|\ \ \|\ \ + \ \_____ \ \ __ \ \ __ \ \ \ \\ \ \ \_|/_\ \ _ _\ + \|____|\ \ \ \ \ \ \ \ \ \ \ \_\\ \ \ \_|\ \ \ \\ \| + ____\_\ \ \__\ \__\ \__\ \__\ \_______\ \_______\ \__\\ _\ + |\_________\|__|\|__|\|__|\|__|\|_______|\|_______|\|__|\|__| + \|_________| + + + ________ ________ _________ +|\ __ \|\ __ \|\___ ___\ +\ \ \|\ \ \ \|\ \|___ \ \_| + \ \ __ \ \ _ _\ \ \ \ + \ \ \ \ \ \ \\ \| \ \ \ + \ \__\ \__\ \__\\ _\ \ \__\ + \|__|\|__|\|__|\|__| \|__| + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +*/ + +vec3 gSunColor = vec3(1.0, 1.2, 1.4) * 10.; + +vec3 gSkyTop = vec3( 0.1, 0.2, 0.8 ) * 0.5; +vec3 gSkyBottom = vec3( 0.5, 0.8, 1.0 ) * 1.5; + +vec3 gCubeColor = vec3(1.0, 1.0, 1.0); +float gExposure = 0.3; + +float gCubeColorRandom = 0.0; + +#define MOVE_OUTWARDS + +float fAOAmount = 0.8; +float gFloorHeight = -1.0; +float g_cameraFar = 1000.0; + +#define PI radians( 180.0 ) + + +vec3 GetSunDir() +{ + return normalize( vec3( 20.0, 40.3, -10.4 ) ); +} + +struct SceneVertex +{ + vec3 vWorldPos; + vec3 vColor; + float fAlpha; +}; + + +float GetCosSunRadius() +{ + return 0.01; +} + + +float GetSunIntensity() +{ + return 0.001; +} + + +vec3 GetSkyColor( vec3 vViewDir ) +{ + return mix( gSkyBottom, gSkyTop, max( 0.0, vViewDir.y ) ); +} +const float g_cubeFaces = 6.0; +const float g_cubeVerticesPerFace = ( 2.0 * 3.0 ); +const float g_cubeVertexCount = ( g_cubeVerticesPerFace * g_cubeFaces ); + +// 6 7 +// +----------+ +// /| /| +// 2 / | 3/ | +// +----------+ | +// | | | | +// Y Z | 4| | 5| +// | +-------|--+ +// ^ / | / | / +// |/ 0|/ 1|/ +// +--> X +----------+ + +vec3 GetCubeVertex( float fVertexIndex ) +{ + float f = fVertexIndex / 8.0; + return vec3( + mix(-1., 1., step(0.5, fract(f * 4.))), + mix(-1., 1., step(0.5, fract(f * 2.))), + mix(-1., 1., step(0.5, fract(f)))); +} + + +void GetCubeVertex( const float vertexIndex, const mat4 mat, out vec3 vWorldPos, out vec3 vWorldNormal ) +{ + float fFaceIndex = floor( vertexIndex / g_cubeFaces ); + + vec3 v0, v1, v2, v3; + + if ( fFaceIndex < 0.5 ) + { + v0 = GetCubeVertex( 0.0 ); + v1 = GetCubeVertex( 2.0 ); + v2 = GetCubeVertex( 3.0 ); + v3 = GetCubeVertex( 1.0 ); + } + else if ( fFaceIndex < 1.5 ) + { + v0 = GetCubeVertex( 5.0 ); + v1 = GetCubeVertex( 7.0 ); + v2 = GetCubeVertex( 6.0 ); + v3 = GetCubeVertex( 4.0 ); + } + else if ( fFaceIndex < 2.5 ) + { + v0 = GetCubeVertex( 1.0 ); + v1 = GetCubeVertex( 3.0 ); + v2 = GetCubeVertex( 7.0 ); + v3 = GetCubeVertex( 5.0 ); + } + else if ( fFaceIndex < 3.5 ) + { + v0 = GetCubeVertex( 4.0 ); + v1 = GetCubeVertex( 6.0 ); + v2 = GetCubeVertex( 2.0 ); + v3 = GetCubeVertex( 0.0 ); + } + else if ( fFaceIndex < 4.5 ) + { + v0 = GetCubeVertex( 2.0 ); + v1 = GetCubeVertex( 6.0 ); + v2 = GetCubeVertex( 7.0 ); + v3 = GetCubeVertex( 3.0 ); + } + else + { + v0 = GetCubeVertex( 1.0 ); + v1 = GetCubeVertex( 5.0 ); + v2 = GetCubeVertex( 4.0 ); + v3 = GetCubeVertex( 0.0 ); + } + v0 = (mat * vec4(v0, 1)).xyz; + v1 = (mat * vec4(v1, 1)).xyz; + v2 = (mat * vec4(v2, 1)).xyz; + v3 = (mat * vec4(v3, 1)).xyz; + + float fFaceVertexIndex = mod( vertexIndex, 6.0 ); + + if ( fFaceVertexIndex < 0.5 ) + { + vWorldPos = v0; + } + else if ( fFaceVertexIndex < 1.5 ) + { + vWorldPos = v1; + } + else if ( fFaceVertexIndex < 2.5 ) + { + vWorldPos = v2; + } + else if ( fFaceVertexIndex < 3.5 ) + { + vWorldPos = v0; + } + else if ( fFaceVertexIndex < 4.5 ) + { + vWorldPos = v2; + } + else + { + vWorldPos = v3; + } + + vWorldNormal = normalize( cross( v1 - v0, v2 - v0 ) ); +} + + +vec3 GetSunLighting( const vec3 vNormal ) +{ + vec3 vLight = -GetSunDir(); + + float NdotL = max( 0.0, dot( vNormal, -vLight ) ); + + return gSunColor * NdotL; +} + + +vec3 GetSunSpec( const vec3 vPos, const vec3 vNormal, const vec3 vCameraPos ) +{ + vec3 vLight = -GetSunDir(); + + vec3 vView = normalize( vCameraPos - vPos ); + + vec3 vH = normalize( vView - vLight ); + + float NdotH = max( 0.0, dot( vNormal, vH ) ); + float NdotL = max( 0.0, dot( vNormal, -vLight ) ); + + float f = mix( 0.01, 1.0, pow( 1.0 - NdotL, 5.0 ) ); + + return gSunColor * pow( NdotH, 20.0 ) * NdotL * f * 4.0; +} + + +vec3 GetSkyLighting( const vec3 vNormal ) +{ + vec3 vSkyLight = normalize( vec3( -1.0, -2.0, -0.5 ) ); + + float fSkyBlend = vNormal.y * 0.5 + 0.5; + + return mix( gSkyBottom, gSkyTop, fSkyBlend ); +} + + +void GenerateCubeVertex( const float vertexIndex, const mat4 mat, const vec3 vCubeCol, const vec3 vCameraPos, out SceneVertex outSceneVertex ) +{ + vec3 vNormal; + + GetCubeVertex( vertexIndex, mat, outSceneVertex.vWorldPos, vNormal ); + + outSceneVertex.vColor = vec3( 0.0 ); + + outSceneVertex.fAlpha = 1.0; + + float h = outSceneVertex.vWorldPos.y - gFloorHeight; + outSceneVertex.vColor += GetSkyLighting( vNormal ); + outSceneVertex.vColor *= mix( 1.0, fAOAmount, clamp( h, 0.0, 1.0 ) ); + + outSceneVertex.vColor += GetSunLighting( vNormal ); + + outSceneVertex.vColor *= vCubeCol; + + outSceneVertex.vColor += GetSunSpec( outSceneVertex.vWorldPos, vNormal, vCameraPos ); +} + + + +// hash function from https://www.shadertoy.com/view/4djSRW +float hash(float p) +{ + vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427)); + p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137)); + return fract(p2.x * p2.y * 95.4337); +} + +vec3 hsv2rgb(vec3 c) { + c = vec3(c.x, clamp(c.yz, 0.0, 1.0)); + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + + +mat4 rotY( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c, 0,-s, 0, + 0, 1, 0, 0, + s, 0, c, 0, + 0, 0, 0, 1); +} + + +mat4 rotZ( float angle ) { + float s = sin( angle ); + float c = cos( angle ); + + return mat4( + c,-s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 trans(vec3 trans) { + #if 0 + return mat4( + 1, 0, 0, trans[0], + 0, 1, 0, trans[1], + 0, 0, 1, trans[2], + 0, 0, 0, 1); + #else + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + trans, 1); + #endif +} + +mat4 ident() { + return mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); +} + +mat4 uniformScale(float s) { + return mat4( + s, 0, 0, 0, + 0, s, 0, 0, + 0, 0, s, 0, + 0, 0, 0, 1); +} + +mat4 scale(vec3 s) { + return mat4( + s[0], 0, 0, 0, + 0, s[1], 0, 0, + 0, 0, s[2], 0, + 0, 0, 0, 1); +} + +mat4 persp(float fov, float aspect, float zNear, float zFar) { + float f = tan(PI * 0.5 - 0.5 * fov); + float rangeInv = 1.0 / (zNear - zFar); + + return mat4( + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (zNear + zFar) * rangeInv, -1, + 0, 0, zNear * zFar * rangeInv * 2., 0); +} + +mat4 trInv(mat4 m) { + mat3 i = mat3( + m[0][0], m[1][0], m[2][0], + m[0][1], m[1][1], m[2][1], + m[0][2], m[1][2], m[2][2]); + vec3 t = -i * m[3].xyz; + + return mat4( + i[0], t[0], + i[1], t[1], + i[2], t[2], + 0, 0, 0, 1); +} + +mat4 transpose(mat4 m) { + return mat4( + m[0][0], m[1][0], m[2][0], m[3][0], + m[0][1], m[1][1], m[2][1], m[3][1], + m[0][2], m[1][2], m[2][2], m[3][2], + m[0][3], m[1][3], m[2][3], m[3][3]); +} + +mat4 lookAt(vec3 eye, vec3 target, vec3 up) { + vec3 zAxis = normalize(eye - target); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + eye, 1); +} + +mat4 inverse(mat4 m) { + float + a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3], + a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3], + a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3], + a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3], + + b00 = a00 * a11 - a01 * a10, + b01 = a00 * a12 - a02 * a10, + b02 = a00 * a13 - a03 * a10, + b03 = a01 * a12 - a02 * a11, + b04 = a01 * a13 - a03 * a11, + b05 = a02 * a13 - a03 * a12, + b06 = a20 * a31 - a21 * a30, + b07 = a20 * a32 - a22 * a30, + b08 = a20 * a33 - a23 * a30, + b09 = a21 * a32 - a22 * a31, + b10 = a21 * a33 - a23 * a31, + b11 = a22 * a33 - a23 * a32, + + det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + return mat4( + a11 * b11 - a12 * b10 + a13 * b09, + a02 * b10 - a01 * b11 - a03 * b09, + a31 * b05 - a32 * b04 + a33 * b03, + a22 * b04 - a21 * b05 - a23 * b03, + a12 * b08 - a10 * b11 - a13 * b07, + a00 * b11 - a02 * b08 + a03 * b07, + a32 * b02 - a30 * b05 - a33 * b01, + a20 * b05 - a22 * b02 + a23 * b01, + a10 * b10 - a11 * b08 + a13 * b06, + a01 * b08 - a00 * b10 - a03 * b06, + a30 * b04 - a31 * b02 + a33 * b00, + a21 * b02 - a20 * b04 - a23 * b00, + a11 * b07 - a10 * b09 - a12 * b06, + a00 * b09 - a01 * b07 + a02 * b06, + a31 * b01 - a30 * b03 - a32 * b00, + a20 * b03 - a21 * b01 + a22 * b00) / det; +} + +mat4 cameraLookAt(vec3 eye, vec3 target, vec3 up) { + #if 1 + return inverse(lookAt(eye, target, up)); + #else + vec3 zAxis = normalize(target - eye); + vec3 xAxis = normalize(cross(up, zAxis)); + vec3 yAxis = cross(zAxis, xAxis); + + return mat4( + xAxis, 0, + yAxis, 0, + zAxis, 0, + -dot(xAxis, eye), -dot(yAxis, eye), -dot(zAxis, eye), 1); + #endif + +} + + +float m1p1(float v) { + return v * 2. - 1.; +} + +float p1m1(float v) { + return v * .5 + .5; +} + +float inRange(float v, float minV, float maxV) { + return step(minV, v) * step(v, maxV); +} + +float at(float v, float target) { + return inRange(v, target - 0.1, target + 0.1); +} + +float easeInOutCubic(float pos) { + if (pos < 0.5) { + return 0.5 * pow(pos / 0.5, 3.); + } + pos -= 0.5; + pos /= 0.5; + pos = 1. - pos; + return (1. - pow(pos, 3.)) * 0.5 + 0.5; +} + +float inOut(float v) { + float t = fract(v); + if (t < 0.5) { + return easeInOutCubic(t / 0.5); + } + return easeInOutCubic(2. - t * 2.); +} + +const float perBlock = 4.; + +void GetCubePosition( float fCubeId, float numCubes, out mat4 mat, out vec4 vCubeCol ) +{ + float fSeed = fCubeId; + float fPositionBase = fCubeId; + float fSize = 1.0; + + vec3 vCubeOrigin = vec3( 0.0, 0.0, 0.0 ); + + float across = 48.; + float down = 32.; + float uId = mod(fCubeId, across); + float vId = floor(fCubeId / across); + float u = uId / (across); + float v = vId / down; + float bxId = floor(uId / perBlock); + float bzId = floor(vId / perBlock); + float numRows = floor(numCubes / across); + float numBlocks = floor(numRows / perBlock); + + float a = atan(u - 0.5, v - .5); + float r = length(vec2(u - 0.5, v - .5)); + float snd = texture2D(sound, vec2(mix(0.0, 0.0135, abs(a / PI)), r * 0.3)).a; + float s2 = 0.;texture2D(sound, vec2(mix(0.02, 0.04, hash(u + v + 2.34)), hash(v) * 0.05)).a; + + vCubeOrigin.x += m1p1(u) * across * 1.0 + bxId * 0. ; + float vSpace = numRows * 2.0 + numBlocks * 0.; + float z = v * down * 2.0 + bzId * 0.; + vCubeOrigin.z += z; + float height = 0.1 + pow(snd, 5.) * 7.0; + vCubeOrigin.y += height;pow(sin(time + v * 9.), 1.) * pow(cos(time + u * 8.17), 1.) * 4. * inOut(time * 0.1); + + mat = ident(); + mat *= trans(vCubeOrigin); + mat *= scale(vec3(1, height, 1)); + + vec3 vRandCol; + + + vCubeCol.rgb = mix( + hsv2rgb(vec3(time * 0.1,0.7,0.8)), hsv2rgb(vec3(time * 0.1 + 0.5,1,1)), smoothstep(0.50,1., snd)); + // hsv2rgb(vec3( + // mix(1., 1.5, pow(snd, 5.)) + fract(time * 0.1), // + 0.2 + floor(time * 0.1) * 0.1 + easeInOutCubic(fract(time * 0.1)) * 0.1, + // 0.5 + snd * 0.5, + // 1)); + vCubeCol.a = vCubeOrigin.z / vSpace; +} + +float goop(float t) { + return sin(t) * sin(t * 0.27) * sin(t * 0.13) * sin(t * 0.73); +} + + +void main() +{ + SceneVertex sceneVertex; + + float fov = 1.8; + + vec3 vCameraTarget = vec3( sin(time) * 700., -600.6 + cos(time) * 100., 1000.0 ); + vec3 vCameraPos = vec3(sin(time) * -45.1, 40., -10.); + float ca = 0.; + + // get sick! + // ca = time + sin(time) * 2.; + vec3 vCameraUp = vec3( sin(ca), cos(ca), 0.0 ); + + vec3 vCameraForwards = normalize(vCameraTarget - vCameraPos); + + float vertexIndex = vertexId; + + + float fCubeId = floor( vertexIndex / g_cubeVertexCount ); + float fCubeVertex = mod( vertexIndex, g_cubeVertexCount ); + float fNumCubes = floor( vertexCount / g_cubeVertexCount ); + + mat4 mCube; + vec4 vCubeCol; + + GetCubePosition( fCubeId, fNumCubes, mCube, vCubeCol ); + + GenerateCubeVertex( fCubeVertex, mCube, vCubeCol.xyz, vCameraPos, sceneVertex ); + + mat4 m = persp(radians(45.), resolution.x / resolution.y, 0.1, 1000.); + m *= cameraLookAt(vCameraPos, vCameraTarget, vCameraUp); + gl_Position = m * vec4(sceneVertex.vWorldPos, 1); + + // Final output color + float fExposure = gExposure;// min( gExposure, time * 0.1 ); + vec3 vFinalColor = sqrt( vec3(1.0) - exp2( sceneVertex.vColor * -fExposure ) ); + + v_color = mix(vec4(vFinalColor, 1), background, vCubeCol.a); + +}` + }, + revisionId: "bF76cKMgYSz9qXQGS", + revisionUrl: "https://www.vertexshaderart.com/art/9mqwFjEipb8pPtcPw/revision/bF76cKMgYSz9qXQGS", + artUrl: "https://www.vertexshaderart.com/art/undefined", + origUrl: "https://www.vertexshaderart.com/art/FPFBuCexgLQpriEoS" +}; diff --git a/examples/js/index/index.js b/examples/js/index/index.js new file mode 100644 index 0000000..2620286 --- /dev/null +++ b/examples/js/index/index.js @@ -0,0 +1,293 @@ +import * as twgl from '../../3rdParty/twgl-full.module.js'; +import VSAEffect from './VSAEffect.js'; +import effects from './effects.js'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import GUI, { helpers, Direction, TextNumber } from '../../../src/esm.js'; + +const canvas = document.querySelector('#bg'); +const gl = canvas.getContext('webgl'); + +const elements = []; + +function render(time) { + time *= 0.001; + + twgl.resizeCanvasToDisplaySize(canvas); + + canvas.style.transform = `translateX(${window.scrollX}px) translateY(${window.scrollY}px)`; + + for (const {render, elem} of elements) { + const rect = elem.getBoundingClientRect(); + if (rect.bottom < 0 || rect.top > gl.canvas.clientHeight || + rect.right < 0 || rect.left > gl.canvas.clientWidth) { + continue; // it's off screen + } + + const width = rect.right - rect.left; + const height = rect.bottom - rect.top; + const left = rect.left; + const bottom = gl.canvas.clientHeight - rect.bottom; + + render(gl, { + time, + width, + height, + left, + bottom, + }); + } + requestAnimationFrame(render); +} +requestAnimationFrame(render); + +//const eff = Object.values(effects); +// eslint-disable-next-line no-underscore-dangle +//console.log(eff.map(e => `https://www.vertexshaderart.com/art/${e._id}`).join(' ')); + +const sections = { + basic({uiElem}) { + const gui = new GUI(uiElem); + const vsaEffect = new VSAEffect(gl); + const vsa = effects.discus; + + const settings = { + numSides: 4, + speed: 0, + brightness: 0.1, + opacity: 1, + run: true, + rotation: 0, + split: 0.96, + s1: 1, + s2: 1, + s3: 10, + s4: 10, + color1: [0.439, 0.463, 0.78], + color2: [1, 1, 0], + }; + gui.add(settings, 'run'); + gui.add(settings, 'numSides', { + keyValues: { + '△ triangle': 3, + '□ square': 4, + '⭔ pentagon': 5, + '⬡ hexagon': 6, + '○ circle': 48, + }, + }).name('shape'); + //gui.add(settings, 'brightness', 0, 1); + //gui.add(settings, 's1', 0, 1); + //gui.add(settings, 's2', 0, 1); + //gui.add(settings, 's3', 0, 50); + //gui.add(settings, 's4', 0, 50); + const degToRad = d => d * Math.PI / 180; + const radToDeg = r => r * 180 / Math.PI; + //gui.add(new Direction(settings, 'rotation', { + // converters: { + // to: v => { return radToDeg(v); }, + // from: v => { const outV = degToRad(v); console.log(outV); return [true, outV]; }, + // }, + //})); + gui.add(settings, 'rotation', { + min: -180, + max: 180, + converters: { + to: radToDeg, + from: v => [true, degToRad(v)], + }, + }); + gui.add(settings, 'split', 0, 1); + //gui.add(settings, 'count', 1, 100, 1); + gui.addColor(settings, 'color1'); + gui.addColor(settings, 'color2'); + + vsaEffect.setSettings(vsa); + + let time = 0; + let then = 0; + return function (gl, inputs) { + const deltaTime = settings.run ? (inputs.time - then) : 0; + then = inputs.time; + time += deltaTime; + vsaEffect.render(gl, { + ...inputs, + ...settings, + time, + }, { + getTime() { + return time * 44100 | 0; + }, + getDesiredSampleRate() { + return 44100; + }, + }, [ + { + frequencyBinCount: 4096, + getByteFrequencyData(b) { + const {s1, s2, s3, s4} = settings; + for (let i = 0; i < b.length; ++i) { + b[i] = (Math.sin(time * s1 + i * s3) + Math.sin(time * s2 + i * s4)) * 0.5 * 127 + 127; + } + }, + }, + ]); + }; + }, + float({uiElem}) { + const gui = new GUI(uiElem); + gui.setTheme('float'); + const vsaEffect = new VSAEffect(gl); + const vsa = effects.rollin; + + const settings = { + period1: 9, + period2: 8.17, + p1: 1, + p2: 1, + heightMult: 4, + baseColor: [0.02, 0.396, 1], + }; + gui.add(settings, 'period1', 0.1, 20); + gui.add(settings, 'period2', 0.1, 20); + gui.add(settings, 'p1', 0.1, 20); + gui.add(settings, 'p2', 0.1, 20); + gui.add(settings, 'heightMult', 1, 10); + gui.addColor(settings, 'baseColor'); + + vsaEffect.setSettings(vsa); + + return function (gl, inputs) { + vsaEffect.render(gl, { + ...inputs, + ...settings, + }, { + getTime() { + return inputs.time * 44100 | 0; + }, + getDesiredSampleRate() { + return 44100; + }, + }, [ + { + frequencyBinCount: 4096, + getByteFrequencyData(b) { + for (let i = 0; i < b.length; ++i) { + b[i] = Math.sin(inputs.time * 10 + i * 0.1) * 0.2 * 127 + 127; + } + }, + }, + ]); + }; + }, + form({uiElem}) { + const s = { + name: "Jane Cheng", + address1: "B 1, No. 5, Xuzhou R", + address2: "Taipei 100218", + email: "jane_c@notreally.notcom", + receipt: true, + currency: '$', + }; + + const gui = new GUI(uiElem).name(''); + gui.setTheme('form'); + gui.add(s, 'name'); + gui.add(s, 'address1'); + gui.add(s, 'address2'); + gui.add(s, 'email'); + gui.add(s, 'receipt'); + gui.add(s, 'currency', ['$', '¥', '€', '£', '₣']); + gui.addButton('submit', () => {}); + }, +}; + +document.querySelectorAll('[data-section]').forEach(elem => { + const uiElem = elem.querySelector('.ui'); + const effectElem = elem.querySelector('.effect'); + const fn = sections[elem.dataset.section]; + if (!fn) { + console.error(`no effect: '${elem.dataset.section}'`); + return; + } + const render = fn({elem, uiElem, effectElem}); + if (render) { + elements.push({ + elem: effectElem, + render, + }); + } +}); + +const getNextId = (() => { + let nextId = 0; + return function getNextId() { + return `gui-${nextId++}`; + }; +})(); + +window.GUI = GUI; +window.TextNumber = TextNumber; +window.randElem = (arr) => arr[Math.random() * arr.length | 0]; +window.helpers = helpers; + +function getSupportCode({logId, code}) { + return ` +${code} + +function log(...args) { + const logElem = document.querySelector('#${logId}'); + logElem.className = 'log'; + const lineNo = parseInt(logElem.dataset.lineNo || 1); + const lines = logElem.textContent.split('\\n'); + lines.push(\`\${lineNo}: \${args.join(' ')}\`); + if (lines.length > 3) { + lines.shift(); + } + logElem.textContent = lines.join('\\n'); + logElem.dataset.lineNo = lineNo + 1; +} +`; +} + +document.querySelectorAll('[data-example]').forEach(elem => { + const pre = elem.querySelector('pre'); + const div = document.createElement('div'); + const id = getNextId(); + div.id = id; + div.className = 'ui'; + elem.appendChild(div); + + const logElem = document.createElement('pre'); + const logId = getNextId(); + logElem.id = logId; + elem.appendChild(logElem); + + const extra = elem.dataset.extraCode || ''; + const code = elem.querySelector('code').textContent + .replace('GUI()', `GUI(document.querySelector('#${id}'))`) + .replace(/import(.*?)'(\/.*?)'/g, `import$1'${window.location.origin}$2'`); + const script = document.createElement('script'); + script.type = 'module'; + script.text = getSupportCode({logId, code: `${code}\n${extra}`}); + pre.appendChild(script); +}); + +/* global hljs */ +document.querySelectorAll('pre>code').forEach(elem => { + elem.textContent = elem.textContent.trim(); +}); +hljs.highlightAll(); + +// show min/max + +// show slider +// show direction +// show vec2 +// show folder +// show onChange + // show onChange of folder +// make button function so no need for prop + +// save/restore? +// show float +// show form diff --git a/examples/js/index-umd.js b/examples/js/lots-umd.js similarity index 67% rename from examples/js/index-umd.js rename to examples/js/lots-umd.js index 6033f92..69d0d6c 100644 --- a/examples/js/index-umd.js +++ b/examples/js/lots-umd.js @@ -1,5 +1,6 @@ - -import {cube} from './cube.js'; +/* eslint-disable no-constant-condition */ +/* global GUI */ +import {model} from './model.js'; import Logger from './logger.js'; import { getCSSRulesBySelector, @@ -13,9 +14,11 @@ const ColorChooser = GUI.ColorChooser; // from '../../src/controllers/ColorChoos const RadioGrid = GUI.RadioGrid; // from '../../src/controllers/RadioGrid.js'; const Slider = GUI.Slider; // from '../../src/controllers/Slider.js'; const Select = GUI.Select; // from '../../src/controllers/Select.js'; -const Range = GUI.Range; // from '../../src/controllers/Range.js'; +//const Range = GUI.Range; // from '../../src/controllers/Range.js'; const TextNumber = GUI.TextNumber; // from '../../src/controllers/TextNumber.js'; +/* cut-here */ + const uiElem = document.querySelector('#ui'); const logElem = document.querySelector('#log'); @@ -33,17 +36,18 @@ const log = (...args) => logImpl('inherit', ...args); // Using an invisible GUI to get the colors // In a real app we'd just use the GUI we created // but I don't want to clutter the other examples. -let uiColors; -const updateUIColors = (() => { +let uiCSSColorVariableNames; +const getListOfUIColorCSSVariableNames = (() => { const div = document.createElement('div'); uiElem.appendChild(div); const gui = new GUI(div).hide(); return function updateUIColors() { - uiColors = gui.getColors(); + uiCSSColorVariableNames = gui.getColors(); }; })(); -updateUIColors(); +getListOfUIColorCSSVariableNames(); +// eslint-disable-next-line no-constant-condition if (false) { const s = { speed: 0.5, @@ -56,8 +60,9 @@ if (false) { gui.addColor(s, 'color'); } -if (true) { -{ +// eslint-disable-next-line no-constant-condition +const showUI = true; +if (showUI) { const s = { speed: 0.5, direction: 45, @@ -131,7 +136,6 @@ if (true) { thicksColor: 'transparent', })); - gui.addDivider(); gui.add(s, 'run'); gui.addLabel('Pet'); @@ -143,6 +147,7 @@ if (true) { document.body.style.backgroundColor = v; }).listen(); gui.add(s, 'show', {name: 'Show Current Values'}); + gui.add({updateDisplay: () => gui.updateDisplay()}, 'updateDisplay'); if (i === 2) { gui.name('Disabled'); @@ -157,34 +162,37 @@ if (true) { f.add(s, 'hobby').onFinishChange(e => log(new Date(), e.value)); f.add(s, 'propertyWithLongName', ['longNamedEnumThatWillPushSizeTooFar']); f.addController(new Direction(s, 'direction')).listen(); - f.addController(new Direction(s, 'hour', {step: 360 / 12, conversion: { - to: v => { - const newV = (v - 3) * 360 / 12; - console.log('to:', v, newV); - return newV; - }, - from: v => { - const newV = v * 12 / 360 + 3; - console.log('from:', v, newV); - return [true, newV]; + f.addController(new Direction(s, 'hour', { + step: 360 / 12, conversion: { + to: v => { + const newV = (v - 3) * 360 / 12; + console.log('to:', v, newV); + return newV; + }, + from: v => { + const newV = v * 12 / 360 + 3; + console.log('from:', v, newV); + return [true, newV]; + }, }, - }})).listen(); + })).listen(); f.addController(new Vec2(s, 'vec', {range: 100})).listen(); - f.addController(new ColorChooser(s, 'c2')).listen(); + f.addController(new ColorChooser(s, 'c2'));//.listen(); const ctx = c.canvas.getContext('2d'); let lastY = 0; let lTime1 = 0; let lTime2 = 0; let then = 0; - function draw(now) { + // eslint-disable-next-line no-loop-func + const draw = (now) => { const elapsedTime = now - then; then = now; lTime1 += elapsedTime * s.period1; lTime2 += elapsedTime * s.period2; const res = 2; resizeCanvasToDisplaySize(ctx.canvas, res); - + const width = ctx.canvas.width; const height = ctx.canvas.height; if (width && height) { @@ -196,7 +204,7 @@ if (true) { 0, 0, width - res, height); ctx.clearRect(width - res, 0, res, height); ctx.globalCompositeOperation = 'source-over'; - ctx.strokeStyle = uiColors.color; + ctx.strokeStyle = uiCSSColorVariableNames.color; const s1 = Math.sin(lTime1 * 0.01); const s2 = Math.sin(lTime2 * 0.01); const newY = height / 2 + (s1 + s2) * (height - 1) / 4; @@ -207,13 +215,13 @@ if (true) { lastY = newY; } requestAnimationFrame(draw); - } + }; requestAnimationFrame(draw); } } // Using Sliders -{ +if (showUI) { const div = document.createElement('div'); uiElem.appendChild(div); const gui = new GUI({parent: div, title: 'Sliders'}); @@ -254,7 +262,7 @@ if (true) { } // Using TextNumber -{ +if (showUI) { const div = document.createElement('div'); uiElem.appendChild(div); const gui = new GUI({parent: div, title: 'Numbers'}); @@ -296,7 +304,7 @@ if (true) { logger.setController(gui.addLabel('')); } -{ +if (showUI) { const div = document.createElement('div'); uiElem.appendChild(div); const gui = new GUI({parent: div, title: 'Callbacks'}); @@ -319,7 +327,7 @@ if (true) { gui.add({func() {}}, 'func').onChange(change).onFinishChange(finishChange); } -{ +if (showUI) { const div = document.createElement('div'); uiElem.appendChild(div); const gui = new GUI(div).name('Colors'); @@ -341,7 +349,7 @@ if (true) { addColor('"RGB"', '8F8'); addColor('"rgb(r, g, b)"', 'rgb(170,68,240)'); addColor('"hsl(h, s, l)"', 'hsl(170,100%,50%)'); - addColor('0xRRGGBB', 0xFEA956, undefined, v => `0x${v.toString(16).padStart(6, '0')}`); + addColor('0xRRGGBB', 0xFEA956, 'uint32-rgb', v => `0x${v.toString(16).padStart(6, '0')}`); addColor('[r(u8), b(u8), c(u8)]', [255, 192, 255], 'uint8-rgb'); addColor('Uint8Array(3)', new Uint8Array([75, 150, 225]), undefined, v => `[${v.join(', ')}]`); // note: Because it's Float32Array, if we just use map it won't work because @@ -350,15 +358,20 @@ if (true) { addColor('Float32Array(3)', new Float32Array([0.9, 0.7, 0.5]), undefined, v => `[${Array.from(v).map(v => f3(v)).join(', ')}]`); addColor('[r(f), g(f), b(f)]', [0.2, 0.9, 0.5], undefined, v => `[${v.map(v => f3(v))}]`); addColor('{r, g, b}', {r: 0, g: 0, b: 1}, undefined, v => `{r: ${f3(v.r)}, g: ${f3(v.g)}, b: ${f3(v.b)}}`); + gui.addLabel('rgba'); + addColor('#RRGGBBAA', '#5438a180'); + addColor('0xRRGGBBAA', 0xEF569A80, 'uint32-rgba'); + addColor('rgba(r, g, b, a)', 'rgba(64, 128, 255, 0.25)'); + addColor('hsl(h, s, l / a)', 'hsl(180 100% 50% / 0.75)'); logger.setController(gui.addLabel('')); } -{ +if (showUI) { const div = document.createElement('div'); uiElem.appendChild(div); const gui = new GUI(div).name('Material'); - const s = cube(gui.addCanvas('canvas').canvas); + const s = model(gui.addCanvas('canvas').canvas); gui.addColor(s.material, 'color').name('material color'); gui.add(s.material, 'shininess', {min: 0, max: 300}); gui.addColor(s.light, 'color').name('light color'); @@ -366,7 +379,7 @@ if (true) { gui.add(s.camera, 'fov', {min: 1, max: 179}).name('field of view'); } -{ +if (showUI) { const div = document.createElement('div'); uiElem.appendChild(div); const gui = new GUI({parent: div, title: 'Add/Remove'}); @@ -374,19 +387,19 @@ if (true) { const controllers = []; const periods = []; - function addRow() { + const addRow = () => { const id = controllers.length; periods.push(Math.random() * 2.5 + 0.5); controllers.push(gui.add(periods, id, 0, 3).name(`input ${id + 1}`)); - } + }; - function delRow() { + const delRow = () => { const row = controllers.pop(); if (row) { periods.pop(); gui.remove(row); } - } + }; const s = {separate: true}; gui.add(s, 'separate'); @@ -402,7 +415,7 @@ if (true) { const lastYs = []; const times = []; - function draw(now) { + const draw = (now) => { ++ticks; const elapsedTime = now - then; then = now; @@ -426,9 +439,9 @@ if (true) { 0, 0, width - res, height); ctx.globalCompositeOperation = 'source-over'; const x = width - res; - ctx.fillStyle = ticks % 16 === 0 ? uiColors.menuSepColor : uiColors.menuBgColor; + ctx.fillStyle = ticks % 16 === 0 ? uiCSSColorVariableNames.menuSepColor : uiCSSColorVariableNames.menuBgColor; ctx.fillRect(x, 0, res, height); - ctx.fillStyle = uiColors.menuSepColor; + ctx.fillStyle = uiCSSColorVariableNames.menuSepColor; for (let y = 8; y < height; y += 16) { ctx.fillRect(x, y, 1, 1); } @@ -457,18 +470,18 @@ if (true) { } } requestAnimationFrame(draw); - } + }; requestAnimationFrame(draw); } -{ - function makeGUI(title, num) { +if (showUI) { + const makeGUI = (title, num) => { const gui = new GUI({title}).hide(); for (let i = 0; i < num; ++i) { gui.add([Math.random()], '0', {min: 0, max: 1}).name(`value ${i}`); } return gui; - } + }; const guis = { short: makeGUI('Short', 5), @@ -486,22 +499,42 @@ if (true) { }); } -const updateAppearance = function() { - - const themeElem = document.createElement('style'); - document.head.appendChild(themeElem); - const styleElem = document.createElement('style'); - document.head.appendChild(styleElem); - +{ + const pageCSS = ` + #ui-helper { + display: grid; + grid-template-columns: repeat(auto-fit, 250px); /* as as column width */ + gap: 10px; /* same as column gap */ + } + #ui { + columns: 250px; + column-gap: 10px; + orphans: 1; + widows: 1; + grid-column: 1 / -1; /* take all the columns of the grid*/ + } + #ui>div { + break-inside: avoid-column; + margin-bottom: 1em; + display: inline-block; + } + `; const themes = { - default: '', - 'default-mono': ` + default: { + muigui: '/* empty */', + page: '', + }, + 'default-mono': { + muigui: ` .muigui { --font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", monospace; } - `, - light: ` - .muigui { + `, + page: pageCSS, + }, + light: { + muigui: ` + .muigui-colors { --bg-color: #f6f6f6; --color: #3d3d3d; --value-color: #2b95a1; @@ -513,9 +546,12 @@ const updateAppearance = function() { --invalid-color: #FF0000; --selected-color: rgba(0, 0, 0, 0.1); } - `, - 'solarized-light': ` - .muigui { + `, + page: '', + }, + 'solarized-light': { + muigui: ` + .muigui-colors { --bg-color: #fdf6e3; --color: #657b83; --value-color: #2aa0f3; @@ -527,9 +563,12 @@ const updateAppearance = function() { --invalid-color: #FF0000; --selected-color: rgba(0, 0, 0, 0.1); } - `, - 'solarized-dark': ` - .muigui { + `, + page: pageCSS, + }, + 'solarized-dark': { + muigui: ` + .muigui-colors { --bg-color: #002b36; --color: #b2c2c2; --value-color: #5abaff; @@ -540,13 +579,93 @@ const updateAppearance = function() { --hover-bg-color: #0a6277; --invalid-color: #FF6666; } - `, - 'bubble-dark': ` + `, + page: '', + }, + 'bubble-dark': { + muigui: ` .muigui { --border-radius: 1em; } - `, - 'form': ` + `, + page: pageCSS, + }, + 'float': { + muigui: ` + :root { + color-scheme: light dark, + } + + .muigui { + --width: 400px; + --bg-color: inherit; + --color: inherit; + --label-width: 25%; + } + + input { + text-shadow: + -1px -1px 0 #FFF, + 1px -1px 0 #FFF, + -1px 1px 0 #FFF, + 1px 1px 0 #FFF; + } + .muigui-label-controller>label { + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000; + } + + .muigui-root>*:nth-child(1) { + display: none; + } + + .muigui-range input[type=range]::-webkit-slider-thumb { + border-radius: 1em; + } + + .muigui-range input[type=range]::-webkit-slider-runnable-track { + -webkit-appearance: initial; + appearance: none; + border: 1px solid rgba(0, 0, 0, 0.25); + height: 2px; + } + + .muigui-colors { + --value-color: #000000; + --value-bg-color: rgba(0, 0, 0, 0.1); + --disabled-color: #cccccc; + --menu-bg-color: rgba(0, 0, 0, 0.1); + --menu-sep-color: #bbbbbb; + --hover-bg-color: rgba(0, 0, 0, 0); + --invalid-color: #FF0000; + --selected-color: rgba(0, 0, 0, 0.3); + --range-color: rgba(0, 0, 0, 0.125); + } + `, + page: ` + #ui-helper { + display: block; + } + #ui { + columns: none; + } + #ui>div { + break-inside: avoid-column; + margin-bottom: 1em; + display: block; + } + + body { + background: rgb(34,193,195); + background: linear-gradient(209deg, rgba(34,193,195,1) 0%, rgba(253,187,45,1) 100%); + } + `, + }, + 'form': { + muigui: ` :root { color-scheme: light dark, } @@ -558,7 +677,9 @@ const updateAppearance = function() { --font-size-mono: medium; --bg-color: inherit; --color: inherit; + } + .muigui-colors { --value-color: #2b95a1; --value-bg-color: #e8e8e8; --disabled-color: #cccccc; @@ -567,81 +688,120 @@ const updateAppearance = function() { --hover-bg-color: #f0f0f0; --invalid-color: #FF0000; --selected-color: rgba(0, 0, 0, 0.1); - } - `, + `, + page: pageCSS, + }, }; + const cssColorRE = /^\s*(#|rgb|hsl|lab|hwb|lab|lch|oklch|oklab)/; + const looksLikeCSSColor = s => cssColorRE.test(s); + + const cssColorToRGBA8 = (() => { + const canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + const ctx = canvas.getContext('2d', {willReadFrequently: true}); + return cssColor => { + ctx.clearRect(0, 0, 1, 1); + ctx.fillStyle = cssColor; + ctx.fillRect(0, 0, 1, 1); + return Array.from(ctx.getImageData(0, 0, 1, 1).data); + }; + })(); + + const cssStringToHexColor = s => `#${cssColorToRGBA8(s).slice(0, 3).map(v => v.toString(16).padStart(2, '0')).join('')}`; + + + // --------------- [ get list of CSS variables that affect GUI ] ----------------- + const selectors = ['.muigui', '.muigui-colors']; + const varNamesBySelector = selectors.map(selector => ({ + selector, + vars: getCSSRulesBySelector(selector, GUI.getBaseStyleSheet()) + .map(rule => Object.values(rule.style) + .filter(s => s.startsWith('--') && !rule.style.getPropertyValue(s).trim().startsWith('var')) + .map(s => ({key: s, rule}))).flat(), + })); + + const showStyles = false; + const div = document.createElement('div'); uiElem.appendChild(div); const gui = new GUI(div).name('Appearance'); gui.addController(new Select({theme: 'default'}, 'theme', {keyValues: [...Object.keys(themes)]})).onChange(v => { - themeElem.textContent = themes[v]; - styleElem.textContent = ''; - updateAppearance(); + GUI.setUserStyles(themes[v].muigui); + document.querySelector('#theme').innerText = themes[v].page; + if (showStyles) { + updateGUIValuesWithCurrentCSSValues(); + } }); - const cssStringToHexColor = s => s.length === 7 - ? s - : `#${s[1]}${s[1]}${s[2]}${s[2]}${s[3]}${s[3]}`; - - const fns = { - '--width': (v) => { - uiElem.style.columnWidth = v; - uiElem.parentElement.style.gridTemplateColumns = `repeat(auto-fit, ${v})`; - }, - }; - - const folder = gui.addFolder('Style'); - const rule = getCSSRulesBySelector('.muigui')[0]; // assuming the first one - const varNames = Object.values(rule.style).filter(s => s.startsWith('--')); - const obj = {}; const controllersByKey = {}; + if (showStyles) { + const fns = { + '--width': (v) => { + uiElem.style.columnWidth = v; + uiElem.parentElement.style.gridTemplateColumns = `repeat(auto-fit, ${v})`; + }, + }; - const updateStyles = () => { - styleElem.textContent = `.muigui {\n${ - [...Object.entries(obj)].map(([key, value]) => { - return `${key}: ${value};`; - }).join('\n')}\n}`; - updateUIColors(); - }; + // --------------- [ make a GUI for each CSS variable that affects GUI ] ----------------- + const obj = {}; - for (const key of varNames) { - const value = rule.style.getPropertyValue(key).trim(); - if (value.startsWith('#')) { - obj[key] = cssStringToHexColor(value); - controllersByKey[key] = folder.addColor(obj, key).onChange(updateStyles); - } else if (!value.startsWith('var')){ - obj[key] = value; - controllersByKey[key] = folder.add(obj, key).onChange(v => { - const fn = fns[key]; - if (fn) { - fn(v); + const folder = gui.addFolder('Style'); + for (const {vars} of varNamesBySelector) { + for (const {key, rule} of vars) { + const value = rule.style.getPropertyValue(key).trim(); + if (looksLikeCSSColor(value)) { + obj[key] = cssStringToHexColor(value); + controllersByKey[key] = folder.addColor(obj, key).onChange(updateMuiguiCSSStyles); + } else if (!value.startsWith('var')){ + obj[key] = value; + controllersByKey[key] = folder.add(obj, key).onChange(v => { + const fn = fns[key]; + if (fn) { + fn(v); + } + updateMuiguiCSSStyles(); + }); } - updateStyles(); - }); + } } + + // -------------- + + const updateMuiguiCSSStyles = () => { + const css = varNamesBySelector.map(({selector, vars}) => ` + ${selector} { + ${ + vars.map(({key}) => `${key}: ${obj[key]};`).join('\n') + } + } + `).join('\n'); + GUI.setUserStyles(css); + }; } - return function() { + // -------------- + const updateGUIValuesWithCurrentCSSValues = () => { const map = new Map(); - for (const rule of getCSSRulesBySelector('.muigui')) { - const varNames = Object.values(rule.style).filter(s => s.startsWith('--')); - for (const key of varNames) { - const value = rule.style.getPropertyValue(key).trim(); - map.set(key, value); + for (const selector of selectors) { + for (const rule of getCSSRulesBySelector(selector, GUI.getBaseStyleSheet())) { + const varNames = Object.values(rule.style).filter(s => s.startsWith('--')); + for (const key of varNames) { + const value = rule.style.getPropertyValue(key).trim(); + map.set(key, value); + } } + map.forEach((value, key) => { + const controller = controllersByKey[key]; + if (controller) { + controller.setValue(looksLikeCSSColor(value) ? cssStringToHexColor(value) : value); + } else { + console.warn(`no setting in this theme for: ${key}`); + } + }); } - map.forEach((value, key) => { - const controller = controllersByKey[key]; - if (controller) { - controller.setValue(value); - } else { - console.warn(`no setting in this theme for: ${key}`); - } - }); - updateUIColors(); + getListOfUIColorCSSVariableNames(); }; -}(); - } diff --git a/examples/js/index.js b/examples/js/lots.js similarity index 97% rename from examples/js/index.js rename to examples/js/lots.js index 263e0b3..4592881 100644 --- a/examples/js/index.js +++ b/examples/js/lots.js @@ -1,6 +1,6 @@ import {GUI} from '../../src/muigui.js'; -import {cube} from './cube.js'; +import {model} from './model.js'; import Logger from './logger.js'; import { getCSSRulesBySelector, @@ -17,6 +17,8 @@ import Select from '../../src/controllers/Select.js'; // import Range from '../../src/controllers/Range.js'; import TextNumber from '../../src/controllers/TextNumber.js'; +/* cut-here */ + const uiElem = document.querySelector('#ui'); const logElem = document.querySelector('#log'); @@ -182,7 +184,8 @@ if (showUI) { let lTime1 = 0; let lTime2 = 0; let then = 0; - function draw(now) { + // eslint-disable-next-line no-loop-func + const draw = (now) => { const elapsedTime = now - then; then = now; lTime1 += elapsedTime * s.period1; @@ -212,7 +215,7 @@ if (showUI) { lastY = newY; } requestAnimationFrame(draw); - } + }; requestAnimationFrame(draw); } } @@ -228,6 +231,7 @@ if (showUI) { angleDeg: 180, tempC: 20, tempF: 72, + ticks: 7, }; const logger = new Logger(3); const log = logger.log; @@ -254,7 +258,6 @@ if (showUI) { gui.add(s, 'tempF', {min: 0, max: 100, step: 0.1, converters: {to: fToC, from: v => [true, cToF(v)]}}) .name('F° ↔ C°') .onChange(v => log(`${v}F°`)); - logger.setController(gui.addLabel('')); } @@ -308,7 +311,10 @@ if (showUI) { const changes = { onChange: 0, onFinishChange: 0 }; const change = () => changes.onChange++; - const finishChange = () => changes.onFinishChange++; + const finishChange = () => { + console.log('fiinsh change'); + changes.onFinishChange++; + }; const folder = gui.addFolder('Counts'); folder.add(changes, 'onChange').disable().listen(); @@ -368,7 +374,7 @@ if (showUI) { uiElem.appendChild(div); const gui = new GUI(div).name('Material'); - const s = cube(gui.addCanvas('canvas').canvas); + const s = model(gui.addCanvas('canvas').canvas); gui.addColor(s.material, 'color').name('material color'); gui.add(s.material, 'shininess', {min: 0, max: 300}); gui.addColor(s.light, 'color').name('light color'); @@ -384,19 +390,19 @@ if (showUI) { const controllers = []; const periods = []; - function addRow() { + const addRow = () => { const id = controllers.length; periods.push(Math.random() * 2.5 + 0.5); controllers.push(gui.add(periods, id, 0, 3).name(`input ${id + 1}`)); - } + }; - function delRow() { + const delRow = () => { const row = controllers.pop(); if (row) { periods.pop(); gui.remove(row); } - } + }; const s = {separate: true}; gui.add(s, 'separate'); @@ -412,7 +418,7 @@ if (showUI) { const lastYs = []; const times = []; - function draw(now) { + const draw = (now) => { ++ticks; const elapsedTime = now - then; then = now; @@ -467,18 +473,18 @@ if (showUI) { } } requestAnimationFrame(draw); - } + }; requestAnimationFrame(draw); } if (showUI) { - function makeGUI(title, num) { + const makeGUI = (title, num) => { const gui = new GUI({title}).hide(); for (let i = 0; i < num; ++i) { gui.add([Math.random()], '0', {min: 0, max: 1}).name(`value ${i}`); } return gui; - } + }; const guis = { short: makeGUI('Short', 5), @@ -767,7 +773,7 @@ if (showUI) { // -------------- - function updateMuiguiCSSStyles() { + const updateMuiguiCSSStyles = () => { const css = varNamesBySelector.map(({selector, vars}) => ` ${selector} { ${ @@ -776,11 +782,11 @@ if (showUI) { } `).join('\n'); GUI.setUserStyles(css); - } + }; } // -------------- - function updateGUIValuesWithCurrentCSSValues() { + const updateGUIValuesWithCurrentCSSValues = () => { const map = new Map(); for (const selector of selectors) { for (const rule of getCSSRulesBySelector(selector, GUI.getBaseStyleSheet())) { @@ -800,5 +806,5 @@ if (showUI) { }); } getListOfUIColorCSSVariableNames(); - } + }; } diff --git a/examples/js/cube.js b/examples/js/model.js similarity index 95% rename from examples/js/cube.js rename to examples/js/model.js index c1bb760..c1f49e3 100644 --- a/examples/js/cube.js +++ b/examples/js/model.js @@ -1,7 +1,8 @@ +/* eslint-disable no-constant-condition */ import * as THREE from '../3rdParty/threejs/build/three.module.js'; import {TrackballControls} from '../3rdParty/threejs/examples/jsm/controls/TrackballControls.js'; -export function cube(canvas) { +export function model(canvas) { const renderer = new THREE.WebGLRenderer({canvas, alpha: true}); const scene = new THREE.Scene(); @@ -30,7 +31,7 @@ export function cube(canvas) { const meshes = []; const material = new THREE.MeshPhongMaterial({ - color: 'red', + color: 'red', shininess: 150, flatShading: true, }); @@ -43,7 +44,7 @@ export function cube(canvas) { meshes.push(mesh); } - if(0){ + if (0) { const geometry = new THREE.SphereGeometry(0.8, 12, 6); const mesh = new THREE.Mesh(geometry, material); mesh.position.x = -1.5; @@ -51,7 +52,7 @@ export function cube(canvas) { meshes.push(mesh); } - if(0){ + if (0) { const geometry = new THREE.TorusGeometry(0.4, 0.3, 6, 12); const mesh = new THREE.Mesh(geometry, material); mesh.position.x = 1.5; @@ -68,7 +69,7 @@ export function cube(canvas) { controls.handleResize(); controls.update(); renderer.render(scene, camera); - }; + } function resizeRendererToDisplaySize(renderer, mult = 1) { const canvas = renderer.domElement; diff --git a/examples/layout/layout.js b/examples/layout/layout.js index 4c7ac32..2976792 100644 --- a/examples/layout/layout.js +++ b/examples/layout/layout.js @@ -1,17 +1,17 @@ -import { - Column, - Frame, - Grid, - Row, -} from '../../src/muigui.js'; - -const elem = document.querySelector('#ui'); - - -const root = new Column(); -const topRow = root.add(new Row()); -const midRow = root.add(new Row()); -const botRow = root.add(new Row()); +//import { +// Column, +// Frame, +// Grid, +// Row, +//} from '../../src/muigui.js'; +// +//const elem = document.querySelector('#ui'); +// +// +//const root = new Column(); +//const topRow = root.add(new Row()); +//const midRow = root.add(new Row()); +//const botRow = root.add(new Row()); /* @@ -19,7 +19,7 @@ const botRow = root.add(new Row()); |+-[Row]------------------------ ||+-[Frame(fixed)]-++-[Frame(stretch-h)]- ||| || -||+-- +||+-- | +--- diff --git a/index-umd.html b/examples/lots-umd.html similarity index 95% rename from index-umd.html rename to examples/lots-umd.html index 9778b2a..efd8e7e 100644 --- a/index-umd.html +++ b/examples/lots-umd.html @@ -89,10 +89,10 @@

muigui

- + \ No newline at end of file diff --git a/examples/lots.html b/examples/lots.html new file mode 100644 index 0000000..c8fe0d4 --- /dev/null +++ b/examples/lots.html @@ -0,0 +1,99 @@ + + + + + + muigui + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

muigui

+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/images/muigui-screenshot.png b/images/muigui-screenshot.png index ddc2b0904700de521f703289b38dcdffd7107b6f..61b88918a823cde50eb222927a00f2722045a17b 100644 GIT binary patch literal 29056 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 P4Uv;pdR{DP9PqyYxxhE= literal 80026 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

lsPrkZ!(x|4JcB}_kWZEQ;?Q4+Z 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 c+rMaRRM^R}D`HI*3-i;_&{wZlb%^ - + + + + - + +
+

muigui

-

muigui

-
-
-
-
- - - - - + +const pets = ['cat', 'dog', 'bird']; +const gui = new GUI(); +const a = gui.add(s, 'amount', 0, 50); +const n = gui.add(s, 'name'); +const p = gui.add(s, 'pet', pets); +gui.addButton('randomize', randomize); +gui.addButton('update amount', _ => a.updateDisplay()); +gui.addButton('update name', _ => n.updateDisplay()); +gui.addButton('update pet', _ => p.updateDisplay()); +gui.addButton('update all', _ => gui.updateDisplay()); + +
+ + +

Folder:

+

A way to make collapsable sections.

+ +
+

+const position = {
+  x: 5,
+  y: 20,
+  z: -15,
+};
+const rotation = {
+  x: 45,
+  y: 90,
+  z: -15,
+};
+
+const gui = new GUI();
+const p = gui.addFolder('position');
+p.add(position, 'x', -100, 100);
+p.add(position, 'y', -100, 100);
+p.add(position, 'z', -100, 100);
+const r = gui.addFolder('rotation');
+r.add(rotation, 'x', -100, 100);
+r.add(rotation, 'y', -100, 100);
+r.add(rotation, 'z', -100, 100);
+    
+
+ + + + +

Float Theme:

+

Useful when you want the UI over something

+ +
+
+
+

+const gui = new GUI();
+gui.setTheme('float');
+gui.add(settings, 'period1', 0.1, 20);
+gui.add(settings, 'period2', 0.1, 20);
+gui.add(settings, 'p1', 0.1, 20);
+gui.add(settings, 'p2', 0.1, 20);
+gui.add(settings, 'heightMult', 1, 10);
+gui.addColor(settings, 'baseColor');
+    
+  
+ + +

Form Theme:

+ +
+
+

+const s = {
+  name: "Jane Cheng",
+  address1: "B 1, No. 5, Xuzhou R",
+  address2: "Taipei 100218",
+  email: "jane_c@notreally.notcom",
+  receipt: true,
+  currency: '$',
+};
+
+const gui = new GUI();
+gui.setTheme('form');
+gui.add(s, 'name');
+gui.add(s, 'address1');
+gui.add(s, 'address2');
+gui.add(s, 'email');
+gui.add(s, 'receipt');
+gui.add(
+  s, 'currency',
+  ['$', '¥', '€', '£', '₣']);
+gui.addButton('submit', submit);
+
+
+
+
+
+
+
+
+//
+    
+  
+ + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d9deaef..03556fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", "chokidar": "^3.5.3", - "eslint": "^8.20.0", + "eslint": "^8.54.0", "eslint-plugin-html": "^7.1.0", "eslint-plugin-one-variable-per-var": "^0.0.3", "eslint-plugin-optional-comma-spacing": "0.0.4", @@ -28,6 +28,15 @@ "typescript": "5.2" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", @@ -189,14 +198,14 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -212,21 +221,21 @@ } }, "node_modules/@eslint/js": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz", - "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -248,9 +257,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@jridgewell/gen-mapping": { @@ -721,6 +730,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -735,9 +750,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1607,27 +1622,28 @@ } }, "node_modules/eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.37.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1635,22 +1651,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -1700,9 +1713,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -1710,6 +1723,9 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { @@ -1725,14 +1741,14 @@ } }, "node_modules/espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1951,7 +1967,7 @@ "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "node_modules/fastq": { @@ -2212,9 +2228,9 @@ } }, "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2264,12 +2280,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2608,16 +2618,6 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2923,17 +2923,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -3174,9 +3174,9 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -4110,15 +4110,6 @@ "node": ">= 8" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -4229,6 +4220,12 @@ } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, "@babel/code-frame": { "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", @@ -4358,14 +4355,14 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", - "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.1", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -4375,18 +4372,18 @@ } }, "@eslint/js": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.37.0.tgz", - "integrity": "sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true }, "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" } @@ -4398,9 +4395,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "@jridgewell/gen-mapping": { @@ -4713,6 +4710,12 @@ "eslint-visitor-keys": "^3.4.1" } }, + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -4724,9 +4727,9 @@ } }, "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true }, "acorn-jsx": { @@ -5347,27 +5350,28 @@ } }, "eslint": { - "version": "8.37.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.37.0.tgz", - "integrity": "sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.37.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.54.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5375,22 +5379,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" } }, @@ -5425,9 +5426,9 @@ "requires": {} }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -5441,14 +5442,14 @@ "dev": true }, "espree": { - "version": "9.5.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", - "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" + "eslint-visitor-keys": "^3.4.1" } }, "esprima": { @@ -5612,7 +5613,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fastq": { @@ -5814,9 +5815,9 @@ } }, "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -5851,12 +5852,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, "graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -6096,12 +6091,6 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "dev": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6333,17 +6322,17 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" } }, "p-limit": { @@ -6521,9 +6510,9 @@ } }, "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "puppeteer": { @@ -7244,12 +7233,6 @@ "isexe": "^2.0.0" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 64a99ac..7cd9391 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", "chokidar": "^3.5.3", - "eslint": "^8.20.0", + "eslint": "^8.54.0", "eslint-plugin-html": "^7.1.0", "eslint-plugin-one-variable-per-var": "^0.0.3", "eslint-plugin-optional-comma-spacing": "0.0.4", diff --git a/src/controllers/Button.js b/src/controllers/Button.js index 9c45bdd..efa096c 100644 --- a/src/controllers/Button.js +++ b/src/controllers/Button.js @@ -26,6 +26,9 @@ export default class Button extends Controller { })); this.setOptions({name: property, ...options}); } + name(name) { + this.#buttonElem.textContent = name; + } setOptions(options) { copyExistingProperties(this.#options, options); const {name} = this.#options; diff --git a/src/controllers/Canvas.js b/src/controllers/Canvas.js index b19b15d..c4c1114 100644 --- a/src/controllers/Canvas.js +++ b/src/controllers/Canvas.js @@ -5,8 +5,8 @@ import LabelController from './LabelController.js'; export default class Canvas extends LabelController { #canvasElem; - constructor() { - super('muigui-canvas'); + constructor(name) { + super('muigui-canvas', name); this.#canvasElem = this.add( new ElementView('canvas', 'muigui-canvas'), ).domElement; diff --git a/src/controllers/ColorChooser.js b/src/controllers/ColorChooser.js index e31f1f0..1bfb67e 100644 --- a/src/controllers/ColorChooser.js +++ b/src/controllers/ColorChooser.js @@ -16,7 +16,6 @@ export default class ColorChooser extends PopDownController { #colorView; #textView; #to; - #setKnobHelper; constructor(object, property, options = {}) { super(object, property, 'muigui-color-chooser'); @@ -28,20 +27,22 @@ export default class ColorChooser extends PopDownController { this.addTop(this.#textView); this.addBottom(this.#colorView); // WTF! FIX! - this.#setKnobHelper = () => { - if (this.#to) { - const hex6Or8 = this.#to(this.getValue()); - const hsl = rgbUint8ToHsl(hexToUint8RGB(hex6Or8)); - hsl[2] = (hsl[2] + 50) % 100; - const hex = uint8RGBToHex(hslToRgbUint8(hsl)); - this.setKnobColor(`${hex6Or8.substring(0, 7)}FF`, hex); - } - }; + this.___setKnobHelper = true; this.updateDisplay(); } + #setKnobHelper() { + if (this.#to) { + const hex6Or8 = this.#to(this.getValue()); + const alpha = hex6Or8.length === 9 ? hex6Or8.substring(7, 9) : 'FF'; + const hsl = rgbUint8ToHsl(hexToUint8RGB(hex6Or8)); + hsl[2] = (hsl[2] + 50) % 100; + const hex = uint8RGBToHex(hslToRgbUint8(hsl)); + this.setKnobColor(`${hex6Or8.substring(0, 7)}${alpha}`, hex); + } + } updateDisplay() { super.updateDisplay(); - if (this.#setKnobHelper) { + if (this.___setKnobHelper) { this.#setKnobHelper(); } } diff --git a/src/controllers/Folder.js b/src/controllers/Folder.js index ac5ee29..527c16e 100644 --- a/src/controllers/Folder.js +++ b/src/controllers/Folder.js @@ -11,6 +11,7 @@ export default class Folder extends Container { type: 'button', onClick: () => this.toggleOpen(), }, [this.#labelElem])); + this.pushContainer(new Container('muigui-open-container')); this.pushContainer(new Container()); this.name(name); this.open(); diff --git a/src/controllers/PopDownController.js b/src/controllers/PopDownController.js index 322a9c9..ec1c0e2 100644 --- a/src/controllers/PopDownController.js +++ b/src/controllers/PopDownController.js @@ -54,7 +54,10 @@ export default class PopDownController extends ValueController { })); this.#checkboxElem = checkboxElem; this.#valuesView = this.#top.add(new ElementView('div', 'muigui-pop-down-values')); - this.#bottom = this.add(new ElementView('div', 'muigui-pop-down-bottom')); + const container = new ElementView('div', 'muigui-pop-down-bottom muigui-open-container'); + this.#bottom = new ElementView('div'); + container.add(this.#bottom); + this.add(container); this.setOptions(options); } setKnobColor(bgCssColor/*, fgCssColor*/) { diff --git a/src/controllers/Text.js b/src/controllers/Text.js index 8b163c0..deb0f1c 100644 --- a/src/controllers/Text.js +++ b/src/controllers/Text.js @@ -3,7 +3,7 @@ import ValueController from './ValueController.js'; export default class Text extends ValueController { constructor(object, property) { - super(object, property, 'muigui-checkbox'); + super(object, property, 'muigui-text'); this.add(new TextView(this)); this.updateDisplay(); } diff --git a/src/controllers/TextNumber.js b/src/controllers/TextNumber.js index e494dfa..764b476 100644 --- a/src/controllers/TextNumber.js +++ b/src/controllers/TextNumber.js @@ -11,7 +11,7 @@ export default class TextNumber extends ValueController { #step; constructor(object, property, options = {}) { - super(object, property, 'muigui-checkbox'); + super(object, property, 'muigui-text-number'); this.#textView = this.add(new NumberView(this, options)); this.updateDisplay(); } diff --git a/src/controllers/create-controller.js b/src/controllers/create-controller.js index bf21ba1..20cd7e5 100644 --- a/src/controllers/create-controller.js +++ b/src/controllers/create-controller.js @@ -24,6 +24,9 @@ export function createController(object, property, ...args) { if (Array.isArray(arg1)) { return new Select(object, property, {keyValues: arg1}); } + if (arg1 && arg1.keyValues) { + return new Select(object, property, {keyValues: arg1.keyValues}); + } const t = typeof object[property]; switch (t) { diff --git a/src/esm.ts b/src/esm.ts index 8dba403..4525558 100644 --- a/src/esm.ts +++ b/src/esm.ts @@ -9,4 +9,12 @@ export { default as Slider } from './controllers/Slider.js'; export { default as TextNumber } from './controllers/TextNumber.js'; export { default as Vec2 } from './controllers/Vec2.js'; +import {graph} from './libs/graph.js'; +import {monitor} from './libs/monitor.js'; + +export const helpers = { + graph, + monitor, +}; + export default GUI; \ No newline at end of file diff --git a/src/libs/color-utils.js b/src/libs/color-utils.js index abd7430..a51e084 100644 --- a/src/libs/color-utils.js +++ b/src/libs/color-utils.js @@ -583,6 +583,50 @@ export const colorFormatConverters = { to: v => Array.from(v).map(v => f3(v)).join(', '), }, }, + 'float-hsv': { + color: { + from: v => [true, rgbFloatToHSV01(hexToFloatRGB(v))], + to: v => hsv01ToRGBFloat(floatRGBToHex(v)), + }, + text: { + from: strTo3Floats, + // need Array.from because map of Float32Array makes a Float32Array + to: v => Array.from(v).map(v => f3(v)).join(', '), + }, + }, + 'float-hsva': { + color: { + from: v => [true, rgbaFloatToHSVA01(hexToFloatRGB(v))], + to: v => hsva01ToRGBAFloat(floatRGBToHex(v)), + }, + text: { + from: strTo4Floats, + // need Array.from because map of Float32Array makes a Float32Array + to: v => Array.from(v).map(v => f3(v)).join(', '), + }, + }, + //'float-hsl': { + // color: { + // from: v => [true, rgbFloatToHsl01(hexToFloatRGB(v))], + // to: v => hsl01ToRGBFloat(floatRGBToHex(v)), + // }, + // text: { + // from: strTo3Floats, + // // need Array.from because map of Float32Array makes a Float32Array + // to: v => Array.from(v).map(v => f3(v)).join(', '), + // }, + //}, + //'float-hsla': { + // color: { + // from: v => [true, hexToFloatRGBA(v)], + // to: floatRGBAToHex, + // }, + // text: { + // from: strTo4Floats, + // // need Array.from because map of Float32Array makes a Float32Array + // to: v => Array.from(v).map(v => f3(v)).join(', '), + // }, + //}, 'object-rgb': { color: { from: v => [true, hexToObjectRGB(v)], diff --git a/src/libs/graph.js b/src/libs/graph.js new file mode 100644 index 0000000..e8feea9 --- /dev/null +++ b/src/libs/graph.js @@ -0,0 +1,42 @@ +const darkColors = { + main: '#ddd', +}; +const lightColors = { + main: '#333', +}; + +const darkMatcher = window.matchMedia('(prefers-color-scheme: dark)'); + +let colors; +let isDarkMode; + +function update() { + isDarkMode = darkMatcher.matches; + colors = isDarkMode ? darkColors : lightColors; +} +darkMatcher.addEventListener('change', update); +update(); + +export function graph(canvas, data, { + min = -1, + max = 1, + interval = 16, + color, + }) { + const ctx = canvas.getContext('2d'); + + function render() { + const {width, height} = canvas; + ctx.clearRect(0, 0, width, height); + ctx.beginPath(); + const range = max - min; + for (let i = 0; i < data.length; ++i) { + const x = i * width / data.length; + const y = (data[i] - min) * height / range; + ctx.lineTo(x, y); + } + ctx.strokeStyle = color || colors.main; + ctx.stroke(); + } + setInterval(render, interval); +} \ No newline at end of file diff --git a/src/libs/monitor.js b/src/libs/monitor.js new file mode 100644 index 0000000..d79633a --- /dev/null +++ b/src/libs/monitor.js @@ -0,0 +1,5 @@ +export function monitor(label, object, property, {interval = 200} = {}) { + setInterval(() => { + label.text(JSON.stringify(object[property], null, 2)); + }, interval); +} diff --git a/src/muigui.js b/src/muigui.js index 8a59469..066272b 100644 --- a/src/muigui.js +++ b/src/muigui.js @@ -61,6 +61,10 @@ export class GUIFolder extends Folder { addLabel(text) { return this.addController(new Label(text)); } + addButton(name, fn) { + const o = {fn}; + return this.add(o, 'fn').name(name); + } } class MuiguiElement extends HTMLElement { @@ -73,7 +77,7 @@ class MuiguiElement extends HTMLElement { customElements.define('muigui-element', MuiguiElement); const baseStyleSheet = new CSSStyleSheet(); -baseStyleSheet.replaceSync(css.default); +//baseStyleSheet.replaceSync(css.default); const userStyleSheet = new CSSStyleSheet(); function makeStyleSheetUpdater(styleSheet) { @@ -100,6 +104,11 @@ function makeStyleSheetUpdater(styleSheet) { const updateBaseStyle = makeStyleSheetUpdater(baseStyleSheet); const updateUserStyle = makeStyleSheetUpdater(userStyleSheet); +function getTheme(name) { + const { include, css: cssStr } = css.themes[name]; + return `${include.map(m => css[m]).join('\n')} : css.default}\n${cssStr || ''}`; +} + export class GUI extends GUIFolder { static converters = converters; static mapRange = mapRange; @@ -131,13 +140,14 @@ export class GUI extends GUIFolder { } if (parent) { const muiguiElement = createElem('muigui-element'); - muiguiElement.shadowRoot.adoptedStyleSheets = [baseStyleSheet, userStyleSheet, this.#localStyleSheet]; + muiguiElement.shadowRoot.adoptedStyleSheets = [this.#localStyleSheet, baseStyleSheet, userStyleSheet]; muiguiElement.shadow.appendChild(this.domElement); parent.appendChild(muiguiElement); } if (title) { this.title(title); } + this.#localStyleSheet.replaceSync(css.default); this.domElement.classList.add('muigui', 'muigui-colors'); } setStyle(css) { @@ -155,8 +165,11 @@ export class GUI extends GUIFolder { static getUserStyleSheet() { return userStyleSheet; } + setTheme(name) { + this.setStyle(getTheme(name)); + } static setTheme(name) { - GUI.setBaseStyles(`${css.default}\n${css.themes[name] || ''}`); + GUI.setBaseStyles(getTheme(name)); } } diff --git a/src/styles/muigui.css.js b/src/styles/muigui.css.js index f961386..3f1ce69 100644 --- a/src/styles/muigui.css.js +++ b/src/styles/muigui.css.js @@ -10,16 +10,24 @@ export default { --menu-bg-color: #f8f8f8; --menu-sep-color: #bbb; --hover-bg-color: #999; - --focus-color: #68C; - --range-color: #888888; + --focus-color: #8BF; + --range-color: #AAA; --invalid-color: #FF0000; --selected-color: rgb(255, 255, 255, 0.9); --button-bg-color: var(--value-bg-color); + --image-open: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHN0eWxlPSJmaWxsOiAjNDQ0OyIgeD0iMjAlIiB5PSI0NSUiIHdpZHRoPSI2MCUiIGhlaWdodD0iMTAlIj48L3JlY3Q+Cjwvc3ZnPg==); + --image-closed: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHN0eWxlPSJmaWxsOiAjNDQ0OyIgeD0iNDUlIiB5PSIyMCUiIHdpZHRoPSIxMCUiIGhlaWdodD0iNjAlIj48L3JlY3Q+CiAgPHJlY3Qgc3R5bGU9ImZpbGw6ICM0NDQ7IiB4PSIyMCUiIHk9IjQ1JSIgd2lkdGg9IjYwJSIgaGVpZ2h0PSIxMCUiPjwvcmVjdD4KPC9zdmc+); + --image-checkerboard: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHN0eWxlPSJmaWxsOiAjNDA0MDQwOyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSI+PC9yZWN0PgogIDxyZWN0IHN0eWxlPSJmaWxsOiAjODA4MDgwOyIgeD0iMCIgeT0iMCIgd2lkdGg9IjUwJSIgaGVpZ2h0PSI1MCUiPjwvcmVjdD4KICA8cmVjdCBzdHlsZT0iZmlsbDogIzgwODA4MDsiIHg9IjUwJSIgeT0iNTAlIiB3aWR0aD0iNTAlIiBoZWlnaHQ9IjUwJSI+PC9yZWN0Pgo8L3N2Zz4=); + --range-left-color: var(--value-color); --range-right-color: var(--value-bg-color); --range-right-hover-color: var(--hover-bg-color); + --button-image: + linear-gradient( + rgba(255, 255, 255, 1), rgba(0, 0, 0, 0.2) + ); color: var(--color); background-color: var(--bg-color); @@ -36,7 +44,7 @@ export default { --menu-bg-color: #080808; --menu-sep-color: #444444; --hover-bg-color: #666666; - --focus-color: #88AAFF; + --focus-color: #458; /*#88AAFF*/; --range-color: #888888; --invalid-color: #FF6666; --selected-color: rgba(255, 255, 255, 0.3); @@ -46,9 +54,15 @@ export default { --range-left-color: var(--value-color); --range-right-color: var(--value-bg-color); --range-right-hover-color: var(--hover-bg-color); + --button-image: linear-gradient( + rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.4) + ); color: var(--color); background-color: var(--bg-color); + + --image-closed: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHN0eWxlPSJmaWxsOiAjREREOyIgeD0iMjAlIiB5PSI0NSUiIHdpZHRoPSI2MCUiIGhlaWdodD0iMTAlIj48L3JlY3Q+Cjwvc3ZnPg==); + --image-open: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxyZWN0IHN0eWxlPSJmaWxsOiAjREREOyIgeD0iNDUlIiB5PSIyMCUiIHdpZHRoPSIxMCUiIGhlaWdodD0iNjAlIj48L3JlY3Q+CiAgPHJlY3Qgc3R5bGU9ImZpbGw6ICNEREQ7IiB4PSIyMCUiIHk9IjQ1JSIgd2lkdGg9IjYwJSIgaGVpZ2h0PSIxMCUiPjwvcmVjdD4KPC9zdmc+); } } @@ -57,7 +71,6 @@ export default { --label-width: 45%; --number-width: 40%; - --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; --font-size: 11px; --font-family-mono: Menlo, Monaco, Consolas, "Droid Sans Mono", monospace; @@ -150,6 +163,9 @@ export default { min-width: 0; min-height: var(--line-height); } +.muigui-root { + z-index: 1; +} .muigui-root, .muigui-menu { display: flex; @@ -174,8 +190,7 @@ export default { color: var(--color); background-color: var(--menu-bg-color); min-height: var(--line-height); - padding-top: 0.2em; - padding-bottom: 0.2em; + padding: 0.2em; cursor: pointer; border-radius: var(--border-radius); } @@ -196,7 +211,7 @@ export default { .muigui-controller>*:nth-child(1) { flex: 1 0 var(--label-width); min-width: 0; - white-space: pre; + /* white-space: pre; why?? */ } .muigui-controller>label:nth-child(1) { place-content: center start; @@ -238,32 +253,41 @@ export default { /* fix! */ .muigui-open>button>label::before, .muigui-closed>button>label::before { + content: "X"; + color: rgba(0, 0, 0, 0); + background-color: var(--range-color); + border-radius: 0.2em; width: 1.25em; - height: var(--line-height); + margin-right: 0.25em; + height: 1.25em; /*var(--line-height);*/ display: inline-grid; place-content: center start; pointer-events: none; } .muigui-open>button>label::before { - content: "ⓧ"; /*"▼";*/ + background-image: var(--image-open); } .muigui-closed>button>label::before { - content: "⨁"; /*"▶";*/ + background-image: var(--image-closed); } -.muigui-open>*:nth-child(2) { - transition: max-height 0.2s ease-out, - opacity 0.5s ease-out; - max-height: 100vh; + +.muigui-open>.muigui-open-container { + transition: all 0.1s ease-out; overflow: auto; - opacity: 1; + height: 100%; } - -.muigui-closed>*:nth-child(2) { - transition: max-height 0.2s ease-out, - opacity 1s; - max-height: 0; - opacity: 0; +.muigui-closed>.muigui-open-container { + transition: all 0.1s ease-out; overflow: hidden; + min-height: 0; +} +.muigui-open>.muigui-open-container>* { + transition: all 0.1s ease-out; + margin-top: 0px; +} +.muigui-closed>.muigui-open-container>* { + transition: all 0.1s ease-out; + margin-top: -100%; } /* ---- popdown ---- */ @@ -275,8 +299,12 @@ export default { .muigui-value>*:nth-child(1).muigui-pop-down-top { flex: 0; } -.muigui-pop-down-bottom { +.muigui-closed .muigui-pop-down-bottom { + max-height: 0; +} +.muigui-value .muigui-pop-down-bottom { + margin: 0; } .muigui-pop-down-values { @@ -298,6 +326,10 @@ export default { width: auto; color: var(--value-color); background-color: var(--value-bg-color); + background-image: var(--image-checkerboard); + background-size: 10px 10px; + background-position: 0 0, 0 5px, 5px -5px, -5px 0px; + cursor: pointer; display: grid; @@ -389,14 +421,16 @@ export default { .muigui-button { display: grid; - + padding: 2px 0 2px 0; } .muigui-button button { border: none; color: var(--value-color); background-color: var(--button-bg-color); + background-image: var(--button-image); cursor: pointer; place-content: center center; + height: var(--line-height); } /* ------ [ color ] ------ */ @@ -608,9 +642,9 @@ export default { border-bottom: 1px solid rgba(0,0,0,0.2); border-right: 1px solid rgba(0,0,0,0.2); background-color: var(--range-color); - margin-top: calc((var(--line-height) - 2px) / -2); - width: calc(var(--line-height) - 2px); - height: calc(var(--line-height) - 2px); + margin-top: calc((var(--line-height) - 6px) / -2); + width: calc(var(--line-height) - 6px); + height: calc(var(--line-height) - 6px); } .muigui-range input[type=range]::-webkit-slider-runnable-track { @@ -694,8 +728,14 @@ export default { `, themes: { - default: '', - float: ` + default: { + include: ['default'], + css: ` + `, + }, + float: { + include: ['default'], + css: ` :root { color-scheme: light dark, } @@ -752,5 +792,57 @@ themes: { --range-color: rgba(0, 0, 0, 0.125); } `, + }, + form: { + include: [], + css: ` + .muigui { + --width: 100%; + --label-width: 45%; + --number-width: 40%; + } + .muigui-root>button { + display: none; + } + .muigui-controller { + margin-top: 1em; + } + .muigui-label-controller { + display: flex; + flex-direction: column; + align-items: stretch; + margin-top: 1em; + } + .muigui-label-controller:has(.muigui-checkbox) { + flex-direction: row; + } + .muigui-value { + display: flex; + align-items: stretch; + } + .muigui-value>* { + flex: 1 1 auto; + min-width: 0; + } + .muigui-controller>*:nth-child(1) { + flex: 1 0 var(--label-width); + min-width: 0; + white-space: pre; + } + .muigui-controller>label:nth-child(1) { + place-content: center start; + display: inline-grid; + overflow: hidden; + } + .muigui-controller>*:nth-child(2) { + flex: 1 1 75%; + min-width: 0; + } + `, + }, + none: { + include: [], + css: '', + }, }, }; diff --git a/src/views/NumberView.js b/src/views/NumberView.js index 3b506dd..c6c63d5 100644 --- a/src/views/NumberView.js +++ b/src/views/NumberView.js @@ -22,15 +22,22 @@ export default class NumberView extends EditView { const wheelHelper = createWheelHelper(); super(createElem('input', { type: 'number', - onInput: () => this.#handleInput(setValue, true), - onChange: () => this.#handleInput(setFinalValue, false), + onInput: () => { + this.#handleInput(setValue, true); + }, + onChange: () => { + this.#handleInput(setFinalValue, false); + }, onWheel: e => { e.preventDefault(); const {min, max, step} = this.#options; const delta = wheelHelper(e, step); const v = parseFloat(this.domElement.value); const newV = clamp(stepify(v + delta, v => v, step), min, max); - setter.setValue(newV); + const [valid, outV] = this.#from(newV); + if (valid) { + setter.setValue(outV); + } }, })); this.setOptions(options); diff --git a/src/views/TextView.js b/src/views/TextView.js index a3cd8f6..61bb574 100644 --- a/src/views/TextView.js +++ b/src/views/TextView.js @@ -16,8 +16,12 @@ export default class TextView extends EditView { const setFinalValue = setter.setFinalValue.bind(setter); super(createElem('input', { type: 'text', - onInput: () => this.#handleInput(setValue, true), - onChange: () => this.#handleInput(setFinalValue, false), + onInput: () => { + this.#handleInput(setValue, true); + }, + onChange: () => { + this.#handleInput(setFinalValue, false); + }, })); this.setOptions(options); } diff --git a/tsconfig.json b/tsconfig.json index 42c165d..c433efe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,15 +12,15 @@ "src/**/*.ts", "src/**/*.js", "build/**/*.js", - //"examples/**/*.js", + "examples/**/*.js", "test/**/*.js", - //"build/**/*.js", - //".eslintrc.cjs", + ".eslintrc.cjs", "*.js", - //"test/tests/**/*.html", ], "exclude": [ - "examples/**/*.js", + "examples/3rdParty/**/*.js", "test/mocha.js", + "out/**/*.js", + "build/out/**/", ], } \ No newline at end of file