├── src
├── assets
│ ├── lut.png
│ ├── ogl.png
│ ├── black.jpg
│ ├── dark.png
│ ├── white.jpg
│ ├── favicon.png
│ ├── ui
│ │ ├── download.jpg
│ │ ├── loader.jpg
│ │ ├── support.jpg
│ │ └── instructions.jpg
│ ├── interior-diffuse-RGBM.png
│ ├── raleway-bold-webfont.woff
│ ├── interior-specular-RGBM.png
│ ├── raleway-bold-webfont.woff2
│ ├── raleway-regular-webfont.woff
│ ├── raleway-regular-webfont.woff2
│ ├── materials
│ │ ├── hammered-metal-mro.jpg
│ │ └── hammered-metal-normal.jpg
│ └── main.css
├── SaveAs.js
├── shaders
│ ├── AtlasShader.js
│ ├── BackgroundShader.js
│ ├── DiffuseShader.js
│ ├── SpecularShader.js
│ └── PBRShader.js
├── DragDrop.js
├── Diffuse.js
├── 16bit
│ ├── use.js
│ └── encode16BitPNG.js
├── GLSLVersion.js
├── Specular.js
├── PNG.js
├── PBR.js
├── inflate.module.min.js
├── PNGWalker.js
└── HDRLoader.js
├── ogl
├── math
│ ├── Vec4.js
│ ├── Color.js
│ ├── Euler.js
│ ├── Mat3.js
│ ├── functions
│ │ ├── EulerFunc.js
│ │ ├── Vec2Func.js
│ │ ├── Vec4Func.js
│ │ └── QuatFunc.js
│ ├── Vec2.js
│ ├── Quat.js
│ ├── Vec3.js
│ └── Mat4.js
├── OGL.js
├── core
│ ├── Camera.js
│ ├── Mesh.js
│ ├── Transform.js
│ ├── RenderTarget.js
│ ├── Geometry.js
│ ├── Texture.js
│ ├── Program.js
│ └── Renderer.js
└── extras
│ ├── Cube.js
│ ├── Plane.js
│ ├── Sphere.js
│ └── Orbit.js
├── LICENSE
├── readme.md
└── index.html
/src/assets/lut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/lut.png
--------------------------------------------------------------------------------
/src/assets/ogl.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/ogl.png
--------------------------------------------------------------------------------
/src/assets/black.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/black.jpg
--------------------------------------------------------------------------------
/src/assets/dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/dark.png
--------------------------------------------------------------------------------
/src/assets/white.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/white.jpg
--------------------------------------------------------------------------------
/src/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/favicon.png
--------------------------------------------------------------------------------
/src/assets/ui/download.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/ui/download.jpg
--------------------------------------------------------------------------------
/src/assets/ui/loader.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/ui/loader.jpg
--------------------------------------------------------------------------------
/src/assets/ui/support.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/ui/support.jpg
--------------------------------------------------------------------------------
/src/assets/ui/instructions.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/ui/instructions.jpg
--------------------------------------------------------------------------------
/src/assets/interior-diffuse-RGBM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/interior-diffuse-RGBM.png
--------------------------------------------------------------------------------
/src/assets/raleway-bold-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/raleway-bold-webfont.woff
--------------------------------------------------------------------------------
/src/assets/interior-specular-RGBM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/interior-specular-RGBM.png
--------------------------------------------------------------------------------
/src/assets/raleway-bold-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/raleway-bold-webfont.woff2
--------------------------------------------------------------------------------
/src/assets/raleway-regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/raleway-regular-webfont.woff
--------------------------------------------------------------------------------
/src/assets/raleway-regular-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/raleway-regular-webfont.woff2
--------------------------------------------------------------------------------
/src/assets/materials/hammered-metal-mro.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/materials/hammered-metal-mro.jpg
--------------------------------------------------------------------------------
/src/assets/materials/hammered-metal-normal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oframe/ibl-converter/HEAD/src/assets/materials/hammered-metal-normal.jpg
--------------------------------------------------------------------------------
/src/SaveAs.js:
--------------------------------------------------------------------------------
1 | var a = document.createElement('a');
2 | document.body.appendChild(a);
3 | a.style.display = 'none';
4 |
5 | export function saveAs(arr, fileName) {
6 | const blob = new Blob([arr], {type: 'octet/stream'});
7 | const url = URL.createObjectURL(blob);
8 | a.href = url;
9 | a.download = fileName;
10 | a.click();
11 | window.URL.revokeObjectURL(url);
12 | }
--------------------------------------------------------------------------------
/src/shaders/AtlasShader.js:
--------------------------------------------------------------------------------
1 | const vertex = `
2 | precision highp float;
3 | precision highp int;
4 |
5 | attribute vec3 position;
6 | attribute vec2 uv;
7 |
8 | varying vec2 vUv;
9 |
10 | void main() {
11 | vUv = uv;
12 |
13 | gl_Position = vec4(position, 1.0);
14 | }
15 | `;
16 |
17 | const fragment = `
18 | precision highp float;
19 | precision highp int;
20 |
21 | uniform sampler2D tMap[7];
22 |
23 | varying vec2 vUv;
24 |
25 | void main() {
26 | vec2 uv = vUv;
27 | uv.y *= 2.0;
28 | vec4 tex;
29 |
30 | for(int i = 0; i < 7; i++) {
31 | if (uv.y >= 0.0 && uv.y <= 1.0) {
32 | tex = texture2D(tMap[i], uv);
33 | }
34 | uv.y -= 1.0;
35 | uv *= 2.0;
36 | }
37 |
38 | gl_FragColor = tex;
39 | }
40 | `;
41 |
42 | export default {vertex, fragment};
--------------------------------------------------------------------------------
/ogl/math/Vec4.js:
--------------------------------------------------------------------------------
1 | import * as Vec4Func from './functions/Vec4Func.js';
2 |
3 | export class Vec4 extends Float32Array {
4 | constructor(array = [0, 0, 0, 0]) {
5 | if (!array.length) array = [array, array, array];
6 | super(array);
7 | return this;
8 | }
9 |
10 | get x() {
11 | return this[0];
12 | }
13 |
14 | set x(v) {
15 | this[0] = v;
16 | }
17 |
18 | get y() {
19 | return this[1];
20 | }
21 |
22 | set y(v) {
23 | this[1] = v;
24 | }
25 |
26 | get z() {
27 | return this[2];
28 | }
29 |
30 | set z(v) {
31 | this[2] = v;
32 | }
33 |
34 | get w() {
35 | return this[3];
36 | }
37 |
38 | set w(v) {
39 | this[3] = v;
40 | }
41 |
42 | set(x, y, z, w) {
43 | Vec4Func.set(this, x, y, z, w);
44 | return this;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 oframe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ogl/math/Color.js:
--------------------------------------------------------------------------------
1 | // TODO : support more color formats - e.g 0xffffff, '#fff'
2 |
3 | export class Color extends Float32Array {
4 | constructor(array = [0, 0, 0]) {
5 | super(3);
6 | if (typeof array === 'string') array = Color.hexToRGB(array);
7 | this.set(...array);
8 | return this;
9 | }
10 |
11 | get r() {
12 | return this[0];
13 | }
14 |
15 | set r(v) {
16 | this[0] = v;
17 | }
18 |
19 | get g() {
20 | return this[1];
21 | }
22 |
23 | set g(v) {
24 | this[1] = v;
25 | }
26 |
27 | get b() {
28 | return this[2];
29 | }
30 |
31 | set b(v) {
32 | this[2] = v;
33 | }
34 |
35 | set(r, g, b) {
36 | this[0] = r;
37 | this[1] = g;
38 | this[2] = b;
39 | return this;
40 | }
41 |
42 | static hexToRGB(hex) {
43 | const r = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
44 | return r ? [
45 | parseInt(r[1], 16) / 255,
46 | parseInt(r[2], 16) / 255,
47 | parseInt(r[3], 16) / 255
48 | ] : null;
49 | }
50 | }
--------------------------------------------------------------------------------
/src/DragDrop.js:
--------------------------------------------------------------------------------
1 | export const DragDrop = {};
2 |
3 | addHandlers();
4 | function addHandlers() {
5 | document.addEventListener('dragenter', enter);
6 | document.addEventListener('dragover', over);
7 | document.addEventListener('dragleave', leave);
8 | document.addEventListener('drop', drop);
9 | }
10 |
11 | function enter(e) {
12 | e.preventDefault();
13 | }
14 |
15 | function over(e) {
16 | e.preventDefault();
17 | }
18 |
19 | function leave(e) {
20 | e.preventDefault();
21 | }
22 |
23 | function drop(e) {
24 | e.preventDefault();
25 |
26 | const file = e.dataTransfer.files[0];
27 | let split = file.name.split('.');
28 | const name = split[0];
29 | const ext = split[1].toLowerCase();
30 |
31 | const reader = new FileReader();
32 |
33 | reader.onload = function (e) {
34 | const buffer = e.target.result;
35 |
36 | DragDrop.onDrop && DragDrop.onDrop(buffer, name, ext);
37 | };
38 |
39 | switch (ext) {
40 | case 'hdr':
41 | case 'exr':
42 | reader.readAsArrayBuffer(file);
43 | break;
44 | default:
45 | reader.readAsDataURL(file);
46 | break;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/ogl/OGL.js:
--------------------------------------------------------------------------------
1 | // Core
2 | export {Geometry} from './core/Geometry.js';
3 | export {Program} from './core/Program.js';
4 | export {Renderer} from './core/Renderer.js';
5 | export {Camera} from './core/Camera.js';
6 | export {Transform} from './core/Transform.js';
7 | export {Mesh} from './core/Mesh.js';
8 | export {Texture} from './core/Texture.js';
9 | export {RenderTarget} from './core/RenderTarget.js';
10 |
11 | // Maths
12 | export {Color} from './math/Color.js';
13 | export {Euler} from './math/Euler.js';
14 | export {Mat3} from './math/Mat3.js';
15 | export {Mat4} from './math/Mat4.js';
16 | export {Quat} from './math/Quat.js';
17 | export {Vec2} from './math/Vec2.js';
18 | export {Vec3} from './math/Vec3.js';
19 | export {Vec4} from './math/Vec4.js';
20 |
21 | // Extras
22 | export {Plane} from './extras/Plane.js';
23 | export {Cube} from './extras/Cube.js';
24 | export {Sphere} from './extras/Sphere.js';
25 | export {Orbit} from './extras/Orbit.js';
26 | // export {Curve} from './extras/Curve.js';
27 | // export {Raycasting} from './extras/Raycasting.js';
28 | // export {Projection} from './extras/Projection.js';
29 | // export {Post} from './extras/Post.js';
30 | // export {Rig} from './extras/Rig.js';
31 | // export {RigAnimation} from './extras/RigAnimation.js';
32 |
--------------------------------------------------------------------------------
/ogl/math/Euler.js:
--------------------------------------------------------------------------------
1 | import * as EulerFunc from './functions/EulerFunc.js';
2 | import {Mat4} from './Mat4.js';
3 |
4 | const tmpMat4 = new Mat4();
5 |
6 | export class Euler extends Float32Array {
7 | constructor(array = [0, 0, 0], order = 'YXZ') {
8 | super(3);
9 | if (typeof array === 'string') array = this.hexToRGB(array);
10 | this.onChange = () => {};
11 | this.set(...array);
12 | this.reorder(order);
13 | return this;
14 | }
15 |
16 | get x() {
17 | return this[0];
18 | }
19 |
20 | set x(v) {
21 | this[0] = v;
22 | this.onChange();
23 | }
24 |
25 | get y() {
26 | return this[1];
27 | }
28 |
29 | set y(v) {
30 | this[1] = v;
31 | this.onChange();
32 | }
33 |
34 | get z() {
35 | return this[2];
36 | }
37 |
38 | set z(v) {
39 | this[2] = v;
40 | this.onChange();
41 | }
42 |
43 | set(x, y = x, z = x) {
44 | this[0] = x;
45 | this[1] = y;
46 | this[2] = z;
47 | this.onChange();
48 | return this;
49 | }
50 |
51 | reorder(order) {
52 | this.order = order;
53 | this.onChange();
54 | return this;
55 | }
56 |
57 | fromRotationMatrix(m, order = this.order) {
58 | EulerFunc.fromRotationMatrix(this, m, order);
59 | return this;
60 | }
61 |
62 | fromQuaternion(q, order = this.order) {
63 | tmpMat4.fromQuaternion(q);
64 | return this.fromRotationMatrix(tmpMat4, order);
65 | }
66 | }
--------------------------------------------------------------------------------
/ogl/core/Camera.js:
--------------------------------------------------------------------------------
1 | import {Transform} from './Transform.js';
2 | import {Mat4} from '../math/Mat4.js';
3 |
4 | export class Camera extends Transform {
5 | constructor(gl, {
6 | near = 0.1,
7 | far = 100,
8 | fov = 45,
9 | aspect = 1,
10 | left,
11 | right,
12 | bottom,
13 | top,
14 |
15 | } = {}) {
16 | super(gl);
17 |
18 | this.near = near;
19 | this.far = far;
20 | this.fov = fov;
21 | this.aspect = aspect;
22 |
23 | this.projectionMatrix = new Mat4();
24 | this.viewMatrix = new Mat4();
25 |
26 | // Use orthographic if values set, else default to perspective camera
27 | if (left || right) this.orthographic({left, right, bottom, top});
28 | else this.perspective();
29 | }
30 |
31 | perspective({
32 | near = this.near,
33 | far = this.far,
34 | fov = this.fov,
35 | aspect = this.aspect,
36 | } = {}) {
37 | this.projectionMatrix.fromPerspective({fov: fov * (Math.PI / 180), aspect, near, far});
38 | this.type = 'perspective';
39 | }
40 |
41 | orthographic({
42 | near = this.near,
43 | far = this.far,
44 | left = -1,
45 | right = 1,
46 | bottom = 1,
47 | top = 1,
48 | } = {}) {
49 | this.projectionMatrix.fromOrthogonal({left, right, bottom, top, near, far});
50 | this.type = 'orthographic';
51 | }
52 |
53 | updateMatrixWorld() {
54 | super.updateMatrixWorld();
55 | this.viewMatrix.copy(this.worldMatrix).invert();
56 | }
57 |
58 | lookAt(target) {
59 | super.lookAt(target, true);
60 | };
61 | }
--------------------------------------------------------------------------------
/src/Diffuse.js:
--------------------------------------------------------------------------------
1 | import {Renderer, Plane, Texture, RenderTarget, Program, Mesh} from '../ogl/OGL.js';
2 | import DiffuseShader from './shaders/DiffuseShader.js';
3 |
4 | // TODO: try and minimize harsh point at poles
5 |
6 | let OUTPUT_SIZE = 128;
7 |
8 | // sRGB == 0
9 | // RGBE == 1
10 | // RGBM == 2
11 | // RGBD == 3
12 | let INPUT_TYPE = 1;
13 | let OUTPUT_TYPE = 2;
14 |
15 | const renderer = new Renderer({alpha: true, premultipliedAlpha: true});
16 | renderer.setSize(OUTPUT_SIZE, OUTPUT_SIZE / 2);
17 | export const gl = renderer.gl;
18 | gl.clearColor(0, 0, 0, 0);
19 | // document.body.appendChild(gl.canvas);
20 |
21 | const geometry = new Plane(gl, 2, 2);
22 |
23 | const program = new Program(gl, {
24 | vertexShader: DiffuseShader.vertex,
25 | fragmentShader: DiffuseShader.fragment,
26 | uniforms: {
27 | tMap: {value: null},
28 | uInputType: {value: 0},
29 | uOutputType: {value: 0},
30 | },
31 | transparent: true,
32 | });
33 |
34 | const mesh = new Mesh(gl, {geometry, program});
35 |
36 | export function renderDiffuse(data) {
37 | INPUT_TYPE = data.inputType;
38 | OUTPUT_TYPE = data.outputType;
39 |
40 | // Create input texture
41 | const texture = new Texture(gl, {
42 | image: data.data,
43 | width: data.width,
44 | height: data.height,
45 | generateMipmaps: false,
46 | });
47 |
48 | // Update program with new texture and values
49 | program.uniforms.tMap.value = texture;
50 | program.uniforms.uInputType.value = INPUT_TYPE;
51 | program.uniforms.uOutputType.value = OUTPUT_TYPE;
52 |
53 | const size = OUTPUT_SIZE;
54 |
55 | renderer.setSize(size, size / 2);
56 |
57 | // Need to render twice when on external server - I have no idea why
58 | renderer.render({scene: mesh});
59 | renderer.render({scene: mesh});
60 | }
--------------------------------------------------------------------------------
/ogl/math/Mat3.js:
--------------------------------------------------------------------------------
1 | import * as Mat3Func from './functions/Mat3Func.js';
2 |
3 | export class Mat3 extends Float32Array {
4 | constructor(array = [1, 0, 0, 0, 1, 0, 0, 0, 1]) {
5 | super(array);
6 | return this;
7 | }
8 |
9 | set(m00, m01, m02, m10, m11, m12, m20, m21, m22) {
10 | Mat3Func.set(this, m00, m01, m02, m10, m11, m12, m20, m21, m22);
11 | return this;
12 | }
13 |
14 | translate(v, m = this) {
15 | Mat3Func.translate(this, m, v);
16 | return this;
17 | }
18 |
19 | rotate(v, m = this) {
20 | Mat3Func.rotate(this, m, v);
21 | return this;
22 | }
23 |
24 | scale(v, m = this) {
25 | Mat3Func.scale(this, m, v);
26 | return this;
27 | }
28 |
29 | multiply(ma, mb) {
30 | if (mb) {
31 | Mat3Func.multiply(this, ma, mb);
32 | } else {
33 | Mat3Func.multiply(this, this, ma);
34 | }
35 | return this;
36 | }
37 |
38 | identity() {
39 | Mat3Func.identity(this);
40 | return this;
41 | }
42 |
43 | copy(m) {
44 | Mat3Func.copy(this, m);
45 | return this;
46 | }
47 |
48 | fromMatrix4(m) {
49 | Mat3Func.fromMat4(this, m);
50 | return this;
51 | }
52 |
53 | fromQuaternion(q) {
54 | Mat3Func.fromQuat(this, q);
55 | return this;
56 | }
57 |
58 | fromBasis(vec3a, vec3b, vec3c) {
59 | this.set(
60 | vec3a[0],
61 | vec3a[1],
62 | vec3a[2],
63 | vec3b[0],
64 | vec3b[1],
65 | vec3b[2],
66 | vec3c[0],
67 | vec3c[1],
68 | vec3c[2]
69 | );
70 | return this;
71 | }
72 |
73 | invert(m = this) {
74 | Mat3Func.invert(this, m);
75 | return this;
76 | }
77 |
78 | getNormalMatrix(m) {
79 | Mat3Func.normalFromMat4(this, m);
80 | return this;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/16bit/use.js:
--------------------------------------------------------------------------------
1 | import './assets/pako_deflate.js';
2 | import { encodePNG } from './assets/PNG.js';
3 | import { saveAs } from './assets/saveAs.js';
4 |
5 | {
6 | const width = 256;
7 | const height = 256;
8 | const colorChannels = 3;
9 | const alphaChannels = 0;
10 | const depth = 16;
11 | const array = new Uint16Array(width * height * (colorChannels + alphaChannels));
12 | const max = 65535;
13 | // const depth = 8;
14 | // const array = new Uint8Array(width * height * (colorChannels + alphaChannels));
15 |
16 | for (let i = 0; i < height; i++) {
17 | for (let j = 0; j < width; j++) {
18 | array[(i * width + j) * 3 + 0] = (j / width) * max;
19 | array[(i * width + j) * 3 + 1] = (j / width) * max;
20 | array[(i * width + j) * 3 + 2] = (j / width) * max;
21 | }
22 | }
23 |
24 | // for (let i = 0, j = 0; i < array.length; i += colorChannels + alphaChannels, j++) {
25 | // // 65535 = max
26 | // array[i + 0] = j;
27 | // array[i + 1] = j;
28 | // array[i + 2] = j;
29 | // // array[i + 0] = (j / 65535) * 255;
30 | // // array[i + 1] = (j / 65535) * 255;
31 | // // array[i + 2] = (j / 65535) * 255;
32 | // // array[i + 0] = Math.random() * 65535;
33 | // // array[i + 1] = Math.random() * 65535;
34 | // // array[i + 2] = Math.random() * 65535;
35 | // }
36 |
37 | const dataView = new DataView(array.buffer);
38 |
39 | // Swap little to big endian
40 | if (depth === 16) {
41 | for (let i = 0; i < dataView.byteLength / 2; i++) {
42 | dataView.setUint16(i * 2, swap16(dataView.getUint16(i * 2)));
43 | }
44 | }
45 |
46 | function swap16(val) {
47 | return ((val & 0xff) << 8) | ((val >> 8) & 0xff);
48 | }
49 |
50 | const png = encodePNG([dataView.buffer], width, height, colorChannels, alphaChannels, depth);
51 |
52 | // saveAs(png, 'test.png');
53 | }
54 |
--------------------------------------------------------------------------------
/ogl/extras/Cube.js:
--------------------------------------------------------------------------------
1 | import {Geometry} from '../core/Geometry.js';
2 | import {Plane} from './Plane.js';
3 |
4 | export class Cube extends Geometry {
5 | constructor(gl, width = 1, height = width, depth = width, wSegs = 1, hSegs = wSegs, dSegs = wSegs) {
6 | const num = (wSegs + 1) * (hSegs + 1) * 2 + (wSegs + 1) * (dSegs + 1) * 2 + (hSegs + 1) * (dSegs + 1) * 2;
7 | const numIndices = wSegs * hSegs * 2 + wSegs * dSegs * 2 + hSegs * dSegs * 2;
8 |
9 | const position = new Float32Array(num * 3);
10 | const normal = new Float32Array(num * 3);
11 | const uv = new Float32Array(num * 2);
12 | const index = new Uint16Array(numIndices * 6);
13 |
14 | let i = 0;
15 | let ii = 0;
16 |
17 | // left, right
18 | Plane.buildPlane(position, normal, uv, index, depth, height, width, dSegs, hSegs, 2, 1, 0, -1, -1, i, ii);
19 | Plane.buildPlane(position, normal, uv, index, depth, height, -width, dSegs, hSegs, 2, 1, 0, 1, -1, i += (dSegs + 1) * (hSegs + 1), ii += dSegs * hSegs);
20 |
21 | // top, bottom
22 | Plane.buildPlane(position, normal, uv, index, width, depth, height, dSegs, hSegs, 0, 2, 1, 1, 1, i += (dSegs + 1) * (hSegs + 1), ii += dSegs * hSegs);
23 | Plane.buildPlane(position, normal, uv, index, width, depth, -height, dSegs, hSegs, 0, 2, 1, 1, -1, i += (wSegs + 1) * (dSegs + 1), ii += wSegs * dSegs);
24 |
25 | // front, back
26 | Plane.buildPlane(position, normal, uv, index, width, height, -depth, wSegs, hSegs, 0, 1, 2, -1, -1, i += (wSegs + 1) * (dSegs + 1), ii += wSegs * dSegs);
27 | Plane.buildPlane(position, normal, uv, index, width, height, depth, wSegs, hSegs, 0, 1, 2, 1, -1, i += (wSegs + 1) * (hSegs + 1), ii += wSegs * hSegs);
28 |
29 | super(gl, {
30 | position: {size: 3, data: position},
31 | normal: {size: 3, data: normal},
32 | uv: {size: 2, data: uv},
33 | index: {data: index},
34 | });
35 | }
36 | }
--------------------------------------------------------------------------------
/src/GLSLVersion.js:
--------------------------------------------------------------------------------
1 | export function toGLSL100(input) {
2 | return input;
3 |
4 | // TODO: convert down
5 | // if dfdy or dfdx add '#extension GL_OES_standard_derivatives : enable\n'
6 | }
7 |
8 | export function toGLSL300(input) {
9 | const lines = input.split('\n');
10 |
11 | // Determine if vertex or fragment by checking if gl_Position present
12 | const isVertex = !!~input.indexOf('gl_Position');
13 |
14 | // Must start with version
15 | let output = '#version 300 es\n';
16 |
17 | lines.forEach(line => {
18 |
19 | // Skip any old version lines
20 | if (!!~line.indexOf('#version')) return;
21 |
22 | // Skip any extension lines
23 | if (!!~line.indexOf('#extension')) return;
24 |
25 | // Replace 'attribute' with 'in'
26 | if (isVertex) line = line.split('attribute').join('in');
27 |
28 | // Replace 'varying' with 'in' or 'out'
29 | line = line.split('varying').join(isVertex ? 'out' : 'in');
30 |
31 | // Replace 'texture2D' and 'textureCube' with 'texture'
32 | line = line.split('texture2D').join('texture');
33 | line = line.split('textureCube').join('texture');
34 |
35 | // Add replacement output for gl_FragColor just before main()
36 | if (!isVertex) line = line.split('void main()').join('out vec4 fragColor;\nvoid main()');
37 |
38 | // Replace 'gl_FragColor' with 'fragColor'
39 | if (!isVertex) line = line.split('gl_FragColor').join('fragColor');
40 |
41 | output += `${line}\n`;
42 | });
43 |
44 | return output;
45 | }
46 |
47 | // Automatically determine which GLSL version for the active WebGL version
48 | export function convertGLSL(gl, input) {
49 | const isGLSL300 = !!~input.indexOf('#version 300 es');
50 | if (gl.renderer.isWebgl2) {
51 | if (isGLSL300) return input;
52 | return toGLSL300(input);
53 | } else {
54 | if (!isGLSL300) return input;
55 | return toGLSL100(input);
56 | }
57 | }
--------------------------------------------------------------------------------
/ogl/math/functions/EulerFunc.js:
--------------------------------------------------------------------------------
1 |
2 | // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
3 | export function fromRotationMatrix(out, m, order = 'YXZ') {
4 |
5 | if (order === 'XYZ') {
6 | out[1] = Math.asin(Math.min(Math.max(m[8], -1), 1));
7 | if (Math.abs(m[8]) < 0.99999) {
8 | out[0] = Math.atan2(-m[9], m[10]);
9 | out[2] = Math.atan2(-m[4], m[0]);
10 | } else {
11 | out[0] = Math.atan2(m[6], m[5]);
12 | out[2] = 0;
13 | }
14 | } else if (order === 'YXZ') {
15 | out[0] = Math.asin(-Math.min(Math.max(m[9], -1), 1));
16 | if (Math.abs(m[9]) < 0.99999) {
17 | out[1] = Math.atan2(m[8], m[10]);
18 | out[2] = Math.atan2(m[1], m[5]);
19 | } else {
20 | out[1] = Math.atan2(-m[2], m[0]);
21 | out[2] = 0;
22 | }
23 | } else if (order === 'ZXY') {
24 | out[0] = Math.asin(Math.min(Math.max(m[6], -1), 1));
25 | if (Math.abs(m[6]) < 0.99999) {
26 | out[1] = Math.atan2(-m[2], m[10]);
27 | out[2] = Math.atan2(-m[4], m[5]);
28 | } else {
29 | out[1] = 0;
30 | out[2] = Math.atan2(m[1], m[0]);
31 | }
32 | } else if (order === 'ZYX') {
33 | out[1] = Math.asin(-Math.min(Math.max(m[2], -1), 1));
34 | if (Math.abs(m[2]) < 0.99999) {
35 | out[0] = Math.atan2(m[6], m[10]);
36 | out[2] = Math.atan2(m[1], m[0]);
37 | } else {
38 | out[0] = 0;
39 | out[2] = Math.atan2(-m[4], m[5]);
40 | }
41 | } else if (order === 'YZX') {
42 | out[2] = Math.asin(Math.min(Math.max(m[1], -1), 1));
43 | if (Math.abs(m[1]) < 0.99999) {
44 | out[0] = Math.atan2(-m[9], m[5]);
45 | out[1] = Math.atan2(-m[2], m[0]);
46 | } else {
47 | out[0] = 0;
48 | out[1] = Math.atan2(m[8], m[10]);
49 | }
50 | } else if (order === 'XZY') {
51 | out[2] = Math.asin(-Math.min(Math.max(m[4], -1), 1));
52 | if (Math.abs(m[4]) < 0.99999) {
53 | out[0] = Math.atan2(m[6], m[5]);
54 | out[1] = Math.atan2(m[8], m[0]);
55 | } else {
56 | out[0] = Math.atan2(-m[9], m[10]);
57 | out[1] = 0;
58 | }
59 | }
60 |
61 | return out;
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | IBL Converter For PBR
7 |
8 |
9 | Generate IBL maps for use with PBR shaders.
10 |
11 |
12 |
13 |
14 | Use this to convert different environment maps into the required formats for use in a PBR render.
15 |
16 | [Link here](https://oframe.github.io/ibl-converter/)
17 |
18 | ## Overview
19 |
20 | Drag and drop an equirectangular environment map to generate the two necessary textures for adding Image Based Lighting to PBR shaders.
21 |
22 | Made for use with the [OGL PBR example](https://oframe.github.io/ogl/examples/?src=pbr.html) shader, however can be plugged into any framework.
23 |
24 | ### Supported input types:
25 | - *.hdr (Radiance/RGBE)*, which outputs a *.png* in *RGBM* format. This conversion takes the HDR (high dynamic range) values and converts them into the RGBA channels of an 8bit PNG (8bit per channel = 32bits); the output PNG will likely look mostly transparent when previewed directly.
26 |
27 | - *.exr (OpenEXR)*, which outputs a *.png* in *RGBM* format. Same output as above.
28 |
29 | - *.jpg/.png (sRGB)* bitmap, which outputs a *.png* in *sRGB* format. This is an SDR (standard dynamic range) format.
30 |
31 | ### Output files:
32 | - Irradiance Diffuse Map. Currently outputting at 128x64, however can likely go smaller with no quality drop. This map is pre-filtered and hence looks very blurry. It gives the average diffuse lighting in a given direction.
33 |
34 | - Radiance Specular Map Atlas. Currently set at 512x512, which is ok for most cases but not close-up reflections. If wished, this setting can be increased in Specular.js. This map is an atlas made up of 7 sequentially prefiltered renders, each half the size of the previous - used to simulate varying roughness levels in the PBR render. The renders are stacked vertically, with the bottom half of the texture being the first, non-filtered level.
35 |
36 | When you hit the download prompt, the two maps will be downloaded to your local file system. The downloaded files use the following naming structure:
37 | ```
38 | [input filename]-[map type]-[output format].png
39 | ```
40 | eg. _**sky-diffuse-RGBM.png**_ and _**sky-specular-RGBM.png**_
41 |
42 | ## TODO
43 | - feature small library of pre-generated maps
44 | - Reduce artifacts at the poles
45 | - Allow user to select output type
--------------------------------------------------------------------------------
/ogl/extras/Plane.js:
--------------------------------------------------------------------------------
1 | import {Geometry} from '../core/Geometry.js';
2 |
3 | export class Plane extends Geometry {
4 | constructor(gl, width = 1, height = width, wSegs = 1, hSegs = wSegs) {
5 |
6 | // Determine length of arrays
7 | const num = (wSegs + 1) * (hSegs + 1);
8 | const numIndices = wSegs * hSegs;
9 |
10 | // Generate empty arrays once
11 | const position = new Float32Array(num * 3);
12 | const normal = new Float32Array(num * 3);
13 | const uv = new Float32Array(num * 2);
14 | const index = new Uint16Array(numIndices * 6);
15 |
16 | Plane.buildPlane(position, normal, uv, index, width, height, 0, wSegs, hSegs);
17 |
18 | super(gl, {
19 | position: {size: 3, data: position},
20 | normal: {size: 3, data: normal},
21 | uv: {size: 2, data: uv},
22 | index: {data: index},
23 | });
24 | }
25 |
26 | static buildPlane(position, normal, uv, index, width, height, depth, wSegs, hSegs,
27 | u = 0, v = 1, w = 2,
28 | uDir = 1, vDir = -1,
29 | i = 0, ii = 0
30 | ) {
31 | const io = i;
32 | const segW = width / wSegs;
33 | const segH = height / hSegs;
34 |
35 | for (let iy = 0; iy <= hSegs; iy++) {
36 | let y = iy * segH - height / 2;
37 | for (let ix = 0; ix <= wSegs; ix++, i++) {
38 | let x = ix * segW - width / 2;
39 |
40 | position[i * 3 + u] = x * uDir;
41 | position[i * 3 + v] = y * vDir;
42 | position[i * 3 + w] = depth / 2;
43 |
44 | normal[i * 3 + u] = 0;
45 | normal[i * 3 + v] = 0;
46 | normal[i * 3 + w] = depth >= 0 ? 1 : -1;
47 |
48 | uv[i * 2] = ix / wSegs;
49 | uv[i * 2 + 1] = 1 - iy / hSegs;
50 |
51 | if (iy === hSegs || ix === wSegs) continue;
52 | let a = io + ix + iy * (wSegs + 1);
53 | let b = io + ix + (iy + 1) * (wSegs + 1);
54 | let c = io + ix + (iy + 1) * (wSegs + 1) + 1;
55 | let d = io + ix + iy * (wSegs + 1) + 1;
56 |
57 | index[ii * 6] = a;
58 | index[ii * 6 + 1] = b;
59 | index[ii * 6 + 2] = d;
60 | index[ii * 6 + 3] = b;
61 | index[ii * 6 + 4] = c;
62 | index[ii * 6 + 5] = d;
63 | ii++;
64 | }
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/src/assets/main.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | user-select: none;
4 | -moz-user-select: none;
5 | -webkit-user-select: none;
6 | -o-user-select: none;
7 | -ms-user-select: none;
8 | -webkit-user-drag: none;
9 | }
10 |
11 | body, html {
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | overflow-x: hidden;
15 | height: 100%;
16 | }
17 |
18 | body, button, p, ul {
19 | margin: 0;
20 | padding: 0;
21 | }
22 |
23 | button, select {
24 | border: none;
25 | outline: none;
26 | background: none;
27 | font-family: inherit;
28 | }
29 |
30 | a, button, input, select, textarea {
31 | -webkit-tap-highlight-color: transparent;
32 | }
33 |
34 | a {
35 | color: inherit; /* blue colors for links too */
36 | text-decoration: inherit; /* no underline */
37 | }
38 |
39 | :root {
40 | overflow-x: hidden;
41 | height: 100%;
42 | }
43 |
44 | @font-face {
45 | font-family: 'Raleway';
46 | src: url('raleway-regular-webfont.woff2') format('woff2'),
47 | url('raleway-regular-webfont.woff') format('woff');
48 | font-weight: 400;
49 | font-style: normal;
50 | }
51 |
52 | @font-face {
53 | font-family: 'Raleway';
54 | src: url('raleway-bold-webfont.woff2') format('woff2'),
55 | url('raleway-bold-webfont.woff') format('woff');
56 | font-weight: 700;
57 | font-style: normal;
58 | }
59 |
60 | body {
61 | font-family: 'Raleway', sans-serif;
62 | font-weight: 700;
63 | font-size: 14px;
64 | }
65 |
66 | .Side { padding-left: 20px; width: 280px; height: 100%; overflow: auto; padding-bottom: 100px; }
67 | .Iframe { position: absolute; left: 300px; right: 0; top: 0; bottom: 0; height: 100%; width: calc(100% - 300px); border: none; margin: 0; }
68 |
69 | .Title { font-size: 18px; margin: 20px 0; display: block; }
70 | .SubTitle { font-weight: 400; font-size: 17px; margin: 20px 0 50px; }
71 | .Section { font-weight: 400; margin: 40px 0 20px; padding-left: 15px; }
72 | .Example { margin: 10px 0; padding-left: 30px; display: block; }
73 | div.Example { opacity: 0.2; }
74 |
75 | .CodeIcon { position: absolute; display: block; z-index: 1; bottom: 0; right: 0; margin: 30px; width: 50px; height: 50px; line-height: 50px; text-align: center; background: #fff; border-radius: 100%; font-size: 19px; letter-spacing: -0.1em; box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.1); }
76 |
77 | .Info { padding: 20px; position: relative; z-index: 1; }
78 | canvas { position: absolute; width: 100%; height: 100%; top: 0; left: 0; }
--------------------------------------------------------------------------------
/ogl/math/Vec2.js:
--------------------------------------------------------------------------------
1 | import * as Vec2Func from './functions/Vec2Func.js';
2 |
3 | export class Vec2 extends Float32Array {
4 | constructor(array = [0, 0]) {
5 | if (!array.length) array = [array, array];
6 | super(array);
7 | return this;
8 | }
9 |
10 | get x() {
11 | return this[0];
12 | }
13 |
14 | set x(v) {
15 | this[0] = v;
16 | }
17 |
18 | get y() {
19 | return this[1];
20 | }
21 |
22 | set y(v) {
23 | this[1] = v;
24 | }
25 |
26 | set(x, y = x) {
27 | Vec2Func.set(this, x, y);
28 | return this;
29 | }
30 |
31 | copy(v) {
32 | Vec2Func.copy(this, v);
33 | return this;
34 | }
35 |
36 | add(v) {
37 | Vec2Func.add(this, this, v);
38 | return this;
39 | }
40 |
41 | multiply(m) {
42 | if (m.length) Vec2Func.multiply(this, this, m);
43 | else Vec2Func.scale(this, this, m);
44 | return this;
45 | }
46 |
47 | distance(v) {
48 | if (v) return Vec2Func.distance(this, v);
49 | else return Vec2Func.length(this);
50 | }
51 |
52 | squaredDistance(v) {
53 | if (v) return Vec2Func.squaredDistance(this, v);
54 | else return Vec2Func.squaredLength(this);
55 | }
56 |
57 | squaredLength() {
58 | return this.squaredDistance();
59 | }
60 |
61 | subtract(va, vb) {
62 | if (vb) Vec2Func.subtract(this, va, vb);
63 | else Vec2Func.subtract(this, this, va);
64 | return this;
65 | }
66 |
67 | negate(v = this) {
68 | Vec2Func.negate(this, v);
69 | return this;
70 | }
71 |
72 | cross(va, vb) {
73 | Vec2Func.cross(this, va, vb);
74 | return this;
75 | }
76 |
77 | scale(v) {
78 | Vec2Func.scale(this, this, v);
79 | return this;
80 | }
81 |
82 | normalize() {
83 | Vec2Func.normalize(this, this);
84 | }
85 |
86 | dot(v) {
87 | return Vec2Func.dot(this, v);
88 | }
89 |
90 | equals(v) {
91 | return Vec2Func.exactEquals(this, v);
92 | }
93 |
94 | applyMatrix3(mat3) {
95 | Vec2Func.transformMat3(this, this, mat3);
96 | return this;
97 | }
98 |
99 | applyMatrix4(mat4) {
100 | Vec2Func.transformMat4(this, this, mat4);
101 | return this;
102 | }
103 |
104 | lerp(v, a) {
105 | Vec2Func.lerp(this, this, v, a);
106 | }
107 |
108 | clone() {
109 | return new Vec2(this);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/shaders/BackgroundShader.js:
--------------------------------------------------------------------------------
1 | const vertex = `
2 | precision highp float;
3 | precision highp int;
4 |
5 | attribute vec3 position;
6 | attribute vec2 uv;
7 |
8 | uniform mat4 modelViewMatrix;
9 | uniform mat4 projectionMatrix;
10 |
11 | varying vec2 vUv;
12 |
13 | void main() {
14 | vUv = uv;
15 |
16 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
17 | }
18 | `;
19 |
20 | const fragment = `
21 | precision highp float;
22 | precision highp int;
23 |
24 | uniform sampler2D tMap;
25 | uniform float uInputType;
26 |
27 | varying vec2 vUv;
28 |
29 | vec4 SRGBtoLinear(vec4 srgb) {
30 | vec3 linOut = pow(srgb.xyz, vec3(2.2));
31 | return vec4(linOut, srgb.w);
32 | }
33 |
34 | vec4 RGBMToLinear(vec4 value) {
35 | float maxRange = 6.0;
36 | return vec4(value.xyz * value.w * maxRange, 1.0);
37 | }
38 |
39 | vec3 linearToSRGB(vec3 color) {
40 | return pow(color, vec3(1.0 / 2.2));
41 | }
42 |
43 | vec4 RGBDtoLinear(vec4 value) {
44 | float maxRange = 6.0;
45 | return vec4(value.rgb * ((maxRange / 255.0) / value.a), 1.0);
46 | }
47 |
48 | vec4 RGBEtoLinear(vec4 value) {
49 | return vec4(value.rgb * exp2(value.a * 255.0 - 128.0), 1.0);
50 | }
51 |
52 | void main() {
53 | vec4 color;
54 |
55 | // 'If else' statements caused the strangest gpu bug
56 | // if (uInputType < 0.5) {
57 |
58 | // // sRGB == 0
59 | // color = SRGBtoLinear(texture2D(tMap, vUv));
60 | // } else if (uInputType < 1.5) {
61 |
62 | // // RGBE == 1
63 | // color = RGBEtoLinear(texture2D(tMap, vUv));
64 | // } else if (uInputType < 2.5) {
65 |
66 | // // RGBM == 2
67 | // color = RGBMToLinear(texture2D(tMap, vUv));
68 | // } else if (uInputType < 3.5) {
69 |
70 | // // RGBD == 3
71 | // color = RGBDtoLinear(texture2D(tMap, vUv));
72 | // }
73 |
74 | // sRGB == 0
75 | color = SRGBtoLinear(texture2D(tMap, vUv));
76 |
77 | // RGBE == 1
78 | float mixRGBE = clamp(1.0 - abs(uInputType - 1.0), 0.0, 1.0);
79 | color = mix(color, RGBEtoLinear(texture2D(tMap, vUv)), mixRGBE);
80 |
81 | // RGBM == 2
82 | float mixRGBM = clamp(1.0 - abs(uInputType - 2.0), 0.0, 1.0);
83 | color = mix(color, RGBMToLinear(texture2D(tMap, vUv)), mixRGBM);
84 |
85 | // RGBD == 3
86 | float mixRGBD = clamp(1.0 - abs(uInputType - 3.0), 0.0, 1.0);
87 | color = mix(color, RGBDtoLinear(texture2D(tMap, vUv)), mixRGBD);
88 |
89 | gl_FragColor = color;
90 | gl_FragColor.rgb = linearToSRGB(gl_FragColor.rgb);
91 | }
92 | `;
93 |
94 | export default {vertex, fragment};
--------------------------------------------------------------------------------
/ogl/core/Mesh.js:
--------------------------------------------------------------------------------
1 | import {Transform} from './Transform.js';
2 | import {Mat3} from '../math/Mat3.js';
3 | import {Mat4} from '../math/Mat4.js';
4 |
5 | export class Mesh extends Transform {
6 | constructor(gl, {
7 | geometry,
8 | program,
9 | mode = gl.TRIANGLES,
10 | } = {}) {
11 | super(gl);
12 | this.gl = gl;
13 |
14 | this.geometry = geometry;
15 | this.program = program;
16 | this.mode = mode;
17 |
18 | this.modelViewMatrix = new Mat4();
19 | this.normalMatrix = new Mat3();
20 |
21 | // Add empty matrix uniforms to program if unset
22 | if (!this.program.uniforms.modelMatrix) {
23 | Object.assign(this.program.uniforms, {
24 | modelMatrix: {value: null},
25 | viewMatrix: {value: null},
26 | modelViewMatrix: {value: null},
27 | normalMatrix: {value: null},
28 | projectionMatrix: {value: null},
29 | cameraPosition: {value: null},
30 | });
31 | }
32 | }
33 |
34 | draw({
35 | camera,
36 | } = {}) {
37 | this.onBeforeRender && this.onBeforeRender({mesh: this, camera});
38 |
39 | // Set the matrix uniforms
40 | if (camera) {
41 | this.program.uniforms.projectionMatrix.value = camera.projectionMatrix;
42 | this.program.uniforms.cameraPosition.value = camera.position;
43 | this.program.uniforms.viewMatrix.value = camera.viewMatrix;
44 |
45 | this.modelViewMatrix.multiply(camera.viewMatrix, this.worldMatrix);
46 | this.normalMatrix.getNormalMatrix(this.modelViewMatrix);
47 |
48 | this.program.uniforms.modelMatrix.value = this.matrix;
49 | this.program.uniforms.modelViewMatrix.value = this.modelViewMatrix;
50 | this.program.uniforms.normalMatrix.value = this.normalMatrix;
51 | }
52 |
53 | // determine if faces need to be flipped - when mesh scaled negatively
54 | let flipFaces = this.program.cullFace && this.worldMatrix.determinant() < 0;
55 |
56 | // Check here if any bindings can be skipped. Geometry also needs to be rebound if different program
57 | const programActive = this.gl.renderer.currentProgram === this.program.id;
58 | const geometryBound = programActive && this.gl.renderer.currentGeometry === this.geometry.id;
59 |
60 | this.program.use({programActive, flipFaces});
61 | this.geometry.draw({mode: this.mode, program: this.program, geometryBound});
62 |
63 | this.onAfterRender && this.onAfterRender({mesh: this, camera});
64 | }
65 | }
--------------------------------------------------------------------------------
/ogl/math/Quat.js:
--------------------------------------------------------------------------------
1 | import * as QuatFunc from './functions/QuatFunc.js';
2 |
3 | export class Quat extends Float32Array {
4 | constructor(array = [0, 0, 0, 1]) {
5 | super(array);
6 | this.onChange = () => {};
7 | return this;
8 | }
9 |
10 | get x() {
11 | return this[0];
12 | }
13 |
14 | set x(v) {
15 | this[0] = v;
16 | this.onChange();
17 | }
18 |
19 | get y() {
20 | return this[1];
21 | }
22 |
23 | set y(v) {
24 | this[1] = v;
25 | this.onChange();
26 | }
27 |
28 | get z() {
29 | return this[2];
30 | }
31 |
32 | set z(v) {
33 | this[2] = v;
34 | this.onChange();
35 | }
36 |
37 | get w() {
38 | return this[3];
39 | }
40 |
41 | set w(v) {
42 | this[3] = v;
43 | this.onChange();
44 | }
45 |
46 | identity() {
47 | QuatFunc.identity(this);
48 | this.onChange();
49 | return this;
50 | }
51 |
52 | set(x, y, z, w) {
53 | QuatFunc.set(this, x, y, z, w);
54 | this.onChange();
55 | return this;
56 | }
57 |
58 | rotateX(a) {
59 | QuatFunc.rotateX(this, this, a);
60 | this.onChange();
61 | return this;
62 | }
63 |
64 | rotateY(a) {
65 | QuatFunc.rotateY(this, this, a);
66 | this.onChange();
67 | return this;
68 | }
69 |
70 | rotateZ(a) {
71 | QuatFunc.rotateZ(this, this, a);
72 | this.onChange();
73 | return this;
74 | }
75 |
76 | invert(q = this) {
77 | QuatFunc.invert(this, q);
78 | this.onChange();
79 | return this;
80 | }
81 |
82 | copy(q) {
83 | QuatFunc.copy(this, q);
84 | this.onChange();
85 | return this;
86 | }
87 |
88 | normalize(q = this) {
89 | QuatFunc.normalize(this, q);
90 | this.onChange();
91 | return this;
92 | }
93 |
94 | multiply(qA, qB) {
95 | if (qB) {
96 | QuatFunc.multiply(this, qA, qB);
97 | } else {
98 | QuatFunc.multiply(this, this, qA);
99 | }
100 | this.onChange();
101 | return this;
102 | }
103 |
104 | dot(v) {
105 | return QuatFunc.dot(this, v);
106 | }
107 |
108 | fromMatrix3(matrix3) {
109 | QuatFunc.fromMat3(this, matrix3);
110 | this.onChange();
111 | return this;
112 | }
113 |
114 | fromEuler(euler) {
115 | QuatFunc.fromEuler(this, euler, euler.order);
116 | return this;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/ogl/math/Vec3.js:
--------------------------------------------------------------------------------
1 | import * as Vec3Func from './functions/Vec3Func.js';
2 |
3 | export class Vec3 extends Float32Array {
4 | constructor(array = [0, 0, 0]) {
5 | if (!array.length) array = [array, array, array];
6 | super(array);
7 | return this;
8 | }
9 |
10 | get x() {
11 | return this[0];
12 | }
13 |
14 | set x(v) {
15 | this[0] = v;
16 | }
17 |
18 | get y() {
19 | return this[1];
20 | }
21 |
22 | set y(v) {
23 | this[1] = v;
24 | }
25 |
26 | get z() {
27 | return this[2];
28 | }
29 |
30 | set z(v) {
31 | this[2] = v;
32 | }
33 |
34 | set(x, y = x, z = x) {
35 | Vec3Func.set(this, x, y, z);
36 | return this;
37 | }
38 |
39 | copy(v) {
40 | Vec3Func.copy(this, v);
41 | return this;
42 | }
43 |
44 | add(v) {
45 | Vec3Func.add(this, this, v);
46 | return this;
47 | }
48 |
49 | multiply(m) {
50 | if (m.length) Vec3Func.multiply(this, this, m);
51 | else Vec3Func.scale(this, this, m);
52 | return this;
53 | }
54 |
55 | length() {
56 | return Vec3Func.length(this);
57 | }
58 |
59 | distance(v) {
60 | return Vec3Func.distance(this, v);
61 | }
62 |
63 | squaredDistance(v) {
64 | if (v) return Vec3Func.squaredDistance(this, v);
65 | else return Vec3Func.squaredLength(this);
66 | }
67 |
68 | squaredLength() {
69 | return this.squaredDistance();
70 | }
71 |
72 | subtract(va, vb) {
73 | if (vb) Vec3Func.subtract(this, va, vb);
74 | else Vec3Func.subtract(this, this, va);
75 | return this;
76 | }
77 |
78 | negate(v = this) {
79 | Vec3Func.negate(this, v);
80 | return this;
81 | }
82 |
83 | cross(va, vb) {
84 | Vec3Func.cross(this, va, vb);
85 | return this;
86 | }
87 |
88 | scale(v) {
89 | Vec3Func.scale(this, this, v);
90 | return this;
91 | }
92 |
93 | normalize() {
94 | Vec3Func.normalize(this, this);
95 | return this;
96 | }
97 |
98 | dot(v) {
99 | return Vec3Func.dot(this, v);
100 | }
101 |
102 | equals(v) {
103 | return Vec3Func.exactEquals(this, v);
104 | }
105 |
106 | applyMatrix4(mat4) {
107 | Vec3Func.transformMat4(this, this, mat4);
108 | return this;
109 | }
110 |
111 | angle(v) {
112 | return Vec3Func.angle(this, v);
113 | }
114 |
115 | lerp(v, t) {
116 | Vec3Func.lerp(this, this, v, t);
117 | return this;
118 | }
119 |
120 | clone() {
121 | return new Vec3(this);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/ogl/extras/Sphere.js:
--------------------------------------------------------------------------------
1 | import {Geometry} from '../core/Geometry.js';
2 | import {Vec3} from '../math/Vec3.js';
3 |
4 | export class Sphere extends Geometry {
5 | constructor(gl, radius = 0.5, wSegs = 16, hSegs = Math.ceil(wSegs * 0.5), pStart = 0, pLength = Math.PI * 2, tStart = 0, tLength = Math.PI) {
6 | const num = (wSegs + 1) * (hSegs + 1);
7 | const numIndices = wSegs * hSegs * 6;
8 |
9 | const position = new Float32Array(num * 3);
10 | const normal = new Float32Array(num * 3);
11 | const uv = new Float32Array(num * 2);
12 | const index = new Uint16Array(numIndices);
13 |
14 | let i = 0;
15 | let iv = 0;
16 | let ii = 0;
17 | let te = tStart + tLength;
18 | const grid = [];
19 |
20 | let n = new Vec3();
21 |
22 | for (let iy = 0; iy <= hSegs; iy++) {
23 | let vRow = [];
24 | let v = iy / hSegs;
25 | for (let ix = 0; ix <= wSegs; ix++, i++) {
26 | let u = ix / wSegs;
27 | let x = -radius * Math.cos(pStart + u * pLength) * Math.sin(tStart + v * tLength);
28 | let y = radius * Math.cos(tStart + v * tLength);
29 | let z = radius * Math.sin(pStart + u * pLength) * Math.sin(tStart + v * tLength);
30 |
31 | position[i * 3] = x;
32 | position[i * 3 + 1] = y;
33 | position[i * 3 + 2] = z;
34 |
35 | n.set(x, y, z).normalize();
36 | normal[i * 3] = n.x;
37 | normal[i * 3 + 1] = n.y;
38 | normal[i * 3 + 2] = n.z;
39 |
40 | uv[i * 2] = u;
41 | uv[i * 2 + 1] = 1 - v;
42 |
43 | vRow.push(iv++);
44 | }
45 |
46 | grid.push(vRow);
47 | }
48 |
49 | for (let iy = 0; iy < hSegs; iy++) {
50 | for (let ix = 0; ix < wSegs; ix++) {
51 | let a = grid[iy][ix + 1];
52 | let b = grid[iy][ix];
53 | let c = grid[iy + 1][ix];
54 | let d = grid[iy + 1][ix + 1];
55 |
56 | if (iy !== 0 || tStart > 0) {
57 | index[ii * 3] = a;
58 | index[ii * 3 + 1] = b;
59 | index[ii * 3 + 2] = d;
60 | ii++;
61 | }
62 | if (iy !== hSegs - 1 || te < Math.PI) {
63 | index[ii * 3] = b;
64 | index[ii * 3 + 1] = c;
65 | index[ii * 3 + 2] = d;
66 | ii++;
67 | }
68 | }
69 | }
70 |
71 | super(gl, {
72 | position: {size: 3, data: position},
73 | normal: {size: 3, data: normal},
74 | uv: {size: 2, data: uv},
75 | index: {data: index},
76 | });
77 | }
78 | }
--------------------------------------------------------------------------------
/ogl/core/Transform.js:
--------------------------------------------------------------------------------
1 | import {Vec3} from '../math/Vec3.js';
2 | import {Quat} from '../math/Quat.js';
3 | import {Mat4} from '../math/Mat4.js';
4 | import {Euler} from '../math/Euler.js';
5 |
6 | export class Transform {
7 | constructor() {
8 | this.parent = null;
9 | this.children = [];
10 |
11 | this.matrix = new Mat4();
12 | this.worldMatrix = new Mat4();
13 | this.matrixAutoUpdate = true;
14 |
15 | this.position = new Vec3();
16 | this.quaternion = new Quat();
17 | this.scale = new Vec3([1, 1, 1]);
18 | this.rotation = new Euler();
19 | this.up = new Vec3([0, 1, 0]);
20 |
21 | this.rotation.onChange = () => this.quaternion.fromEuler(this.rotation);
22 | this.quaternion.onChange = () => this.rotation.fromQuaternion(this.quaternion);
23 | }
24 |
25 | setParent(parent, notifyChild = true) {
26 | if (!parent && notifyChild) this.parent.removeChild(this, false);
27 | this.parent = parent;
28 | if (parent && notifyChild) parent.addChild(this, false);
29 | }
30 |
31 | addChild(child, notifyParent = true) {
32 | if (!~this.children.indexOf(child)) this.children.push(child);
33 | if (notifyParent) child.setParent(this, false);
34 | }
35 |
36 | removeChild(child, notifyParent = true) {
37 | if (!!~this.children.indexOf(child)) this.children.splice(this.children.indexOf(child), 1);
38 | if (notifyParent) child.setParent(null, false);
39 | }
40 |
41 | updateMatrixWorld(force) {
42 | if (this.matrixAutoUpdate) this.updateMatrix();
43 | if (this.worldMatrixNeedsUpdate || force) {
44 | if (this.parent === null) this.worldMatrix.copy(this.matrix);
45 | else this.worldMatrix.multiply(this.parent.worldMatrix, this.matrix);
46 | this.worldMatrixNeedsUpdate = false;
47 | force = true;
48 | }
49 |
50 | let children = this.children;
51 | for (let i = 0, l = children.length; i < l; i ++) {
52 | children[i].updateMatrixWorld(force);
53 | }
54 | }
55 |
56 | updateMatrix() {
57 | this.matrix.compose(this.quaternion, this.position, this.scale);
58 | this.worldMatrixNeedsUpdate = true;
59 | }
60 |
61 | traverse(callback) {
62 | callback(this);
63 | for (let i = 0, l = this.children.length; i < l; i ++) {
64 | this.children[i].traverse(callback);
65 | }
66 | }
67 |
68 | decompose() {
69 | this.matrix.getTranslation(this.position);
70 | this.matrix.getRotation(this.quaternion);
71 | this.matrix.getScaling(this.scale);
72 | this.rotation.fromQuaternion(this.quaternion);
73 | }
74 |
75 | lookAt(target, invert = false) {
76 | if (invert) this.matrix.lookAt(this.position, target, this.up);
77 | else this.matrix.lookAt(target, this.position, this.up);
78 | this.matrix.getRotation(this.quaternion);
79 | this.rotation.fromQuaternion(this.quaternion);
80 | };
81 | }
--------------------------------------------------------------------------------
/src/Specular.js:
--------------------------------------------------------------------------------
1 | import { Renderer, Plane, Texture, RenderTarget, Program, Mesh } from '../ogl/OGL.js';
2 | import SpecularShader from './shaders/SpecularShader.js';
3 | import AtlasShader from './shaders/AtlasShader.js';
4 |
5 | let OUTPUT_SIZE = 512;
6 |
7 | // sRGB == 0
8 | // RGBE == 1
9 | // RGBM == 2
10 | // RGBD == 3
11 | let INPUT_TYPE = 1;
12 | let OUTPUT_TYPE = 2;
13 |
14 | const renderer = new Renderer({ alpha: true, premultipliedAlpha: true });
15 | renderer.setSize(OUTPUT_SIZE, OUTPUT_SIZE);
16 | export const gl = renderer.gl;
17 | gl.clearColor(0, 0, 0, 0);
18 | // document.body.appendChild(gl.canvas);
19 |
20 | const geometry = new Plane(gl, 2, 2);
21 |
22 | const specularProgram = new Program(gl, {
23 | vertexShader: SpecularShader.vertex,
24 | fragmentShader: SpecularShader.fragment,
25 | uniforms: {
26 | tMap: { value: null },
27 | uRoughness: { value: 0 },
28 | uInputType: { value: 0 },
29 | uOutputType: { value: 0 },
30 | },
31 | transparent: true,
32 | });
33 |
34 | const specularMesh = new Mesh(gl, { geometry, program: specularProgram });
35 |
36 | const atlasProgram = new Program(gl, {
37 | vertexShader: AtlasShader.vertex,
38 | fragmentShader: AtlasShader.fragment,
39 | uniforms: {
40 | tMap: { value: null },
41 | },
42 | transparent: true,
43 | });
44 |
45 | const atlasMesh = new Mesh(gl, { geometry, program: atlasProgram });
46 |
47 | export function renderSpecular(data) {
48 | INPUT_TYPE = data.inputType;
49 | OUTPUT_TYPE = data.outputType;
50 |
51 | // Create input texture
52 | const texture = new Texture(gl, {
53 | image: data.data,
54 | width: data.width,
55 | height: data.height,
56 | generateMipmaps: false,
57 | });
58 |
59 | let size = OUTPUT_SIZE;
60 | let num = 6; // Don't see why you'd want more than that
61 | // let num = Math.log(size) / Math.log(2) - 2;
62 |
63 | // Create RenderTargets for each roughness level
64 | const targets = [];
65 |
66 | for (var i = 0; i < num; i++) {
67 | const target = new RenderTarget(gl, {
68 | width: size,
69 | height: size / 2,
70 | wrapS: gl.REPEAT,
71 | // FLOAT, RGBFormat
72 | });
73 | targets.push(target);
74 |
75 | size = Math.max(16, size / 2);
76 | }
77 |
78 | // Update program with new texture and values
79 | specularProgram.uniforms.tMap.value = texture;
80 | specularProgram.uniforms.uInputType.value = INPUT_TYPE;
81 | specularProgram.uniforms.uOutputType.value = OUTPUT_TYPE;
82 |
83 | // randiance maps for specular
84 | targets.forEach((target, i) => {
85 | renderer.setSize(target.width, target.height);
86 |
87 | var r = i / (targets.length - 1) || 0;
88 | specularProgram.uniforms.uRoughness.value = r * 0.9;
89 |
90 | renderer.render({ scene: specularMesh, target });
91 | specularProgram.uniforms.tMap.value = target.texture;
92 |
93 | if (i === 0) specularProgram.uniforms.uInputType.value = specularProgram.uniforms.uOutputType.value;
94 | });
95 |
96 | // Update atlas textures
97 | atlasProgram.uniforms.tMap.value = targets.map((target) => target.texture);
98 |
99 | // Render all targets to atlas output
100 | renderer.setSize(OUTPUT_SIZE, OUTPUT_SIZE);
101 |
102 | // Need to render twice when on external server - I have no idea why
103 | renderer.render({ scene: atlasMesh });
104 | renderer.render({ scene: atlasMesh });
105 | }
106 |
--------------------------------------------------------------------------------
/ogl/math/Mat4.js:
--------------------------------------------------------------------------------
1 | import * as Mat4Func from './functions/Mat4Func.js';
2 |
3 | export class Mat4 extends Float32Array {
4 | constructor(array = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) {
5 | super(array);
6 | return this;
7 | }
8 |
9 | set x(v) {
10 | this[12] = v;
11 | }
12 |
13 | get x() {
14 | return this[12];
15 | }
16 |
17 | set y(v) {
18 | this[13] = v;
19 | }
20 |
21 | get y() {
22 | return this[13];
23 | }
24 |
25 | set z(v) {
26 | this[14] = v;
27 | }
28 |
29 | get z() {
30 | return this[14];
31 | }
32 |
33 | set w(v) {
34 | this[15] = v;
35 | }
36 |
37 | get w() {
38 | return this[15];
39 | }
40 |
41 | set(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) {
42 | if (m00.length) {
43 | return this.copy(m00);
44 | }
45 | Mat4Func.set(this, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33);
46 | return this;
47 | }
48 |
49 | translate(v, m = this) {
50 | Mat4Func.translate(this, m, v);
51 | return this;
52 | }
53 |
54 | rotateX(v, m = this) {
55 | Mat4Func.rotateX(this, m, v);
56 | return this;
57 | }
58 |
59 | rotateY(v, m = this) {
60 | Mat4Func.rotateY(this, m, v);
61 | return this;
62 | }
63 |
64 | rotateZ(v, m = this) {
65 | Mat4Func.rotateZ(this, m, v);
66 | return this;
67 | }
68 |
69 | scale(v, m = this) {
70 | Mat4Func.scale(this, m, typeof v === "number" ? [v, v, v] : v);
71 | return this;
72 | }
73 |
74 | multiply(ma, mb) {
75 | if (mb) {
76 | Mat4Func.multiply(this, ma, mb);
77 | } else {
78 | Mat4Func.multiply(this, this, ma);
79 | }
80 | return this;
81 | }
82 |
83 | identity() {
84 | Mat4Func.identity(this);
85 | return this;
86 | }
87 |
88 | copy(m) {
89 | Mat4Func.copy(this, m);
90 | return this;
91 | }
92 |
93 | fromPerspective({fov, aspect, near, far} = {}) {
94 | Mat4Func.perspective(this, fov, aspect, near, far);
95 | return this;
96 | }
97 |
98 | fromOrthogonal({left, right, bottom, top, near, far}) {
99 | Mat4Func.ortho(this, left, right, bottom, top, near, far);
100 | return this;
101 | }
102 |
103 | fromQuaternion(q) {
104 | Mat4Func.fromQuat(this, q);
105 | return this;
106 | }
107 |
108 | setPosition(v) {
109 | this.x = v[0];
110 | this.y = v[1];
111 | this.z = v[2];
112 | return this;
113 | }
114 |
115 | invert(m = this) {
116 | Mat4Func.invert(this, m);
117 | return this;
118 | }
119 |
120 | compose(q, pos, scale) {
121 | Mat4Func.fromRotationTranslationScale(this, q, pos, scale);
122 | return this;
123 | }
124 |
125 | getRotation(q) {
126 | Mat4Func.getRotation(q, this);
127 | return this;
128 | }
129 |
130 | getTranslation(pos) {
131 | Mat4Func.getTranslation(pos, this);
132 | return this;
133 | }
134 |
135 | getScaling(scale) {
136 | Mat4Func.getScaling(scale, this);
137 | return this;
138 | }
139 |
140 | lookAt(eye, target, up) {
141 | Mat4Func.targetTo(this, eye, target, up);
142 | return this;
143 | }
144 |
145 | determinant() {
146 | return Mat4Func.determinant(this);
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/ogl/core/RenderTarget.js:
--------------------------------------------------------------------------------
1 | // TODO: multi target rendering
2 | // TODO: test stencil and depth
3 | // TODO: different type and format
4 | import {Texture} from './Texture.js';
5 |
6 | export class RenderTarget {
7 | constructor(gl, {
8 | width = gl.canvas.width,
9 | height = gl.canvas.height,
10 |
11 | target = gl.FRAMEBUFFER,
12 | color = 1, // number of color attachments
13 | depth = true,
14 | stencil = false,
15 | depthTexture = false, // note - stencil breaks
16 |
17 | wrapS = gl.CLAMP_TO_EDGE,
18 | wrapT = gl.CLAMP_TO_EDGE,
19 | minFilter = gl.LINEAR,
20 | magFilter = gl.LINEAR,
21 |
22 | } = {}) {
23 | this.gl = gl;
24 | this.width = width;
25 | this.height = height;
26 | this.buffer = this.gl.createFramebuffer();
27 | this.target = target;
28 | this.gl.bindFramebuffer(this.target, this.buffer);
29 |
30 | this.textures = [];
31 |
32 | // TODO: multi target rendering
33 | // create and attach required num of color textures
34 | for (let i = 0; i < color; i++) {
35 | this.textures.push(new Texture(gl, {
36 | width, height, wrapS, wrapT, minFilter, magFilter,
37 | flipY: false,
38 | generateMipmaps: false,
39 | }));
40 | this.textures[i].update();
41 | this.gl.framebufferTexture2D(this.target, this.gl.COLOR_ATTACHMENT0 + i, this.gl.TEXTURE_2D, this.textures[i].texture, 0 /* level */);
42 | }
43 |
44 | // alias for majority of use cases
45 | this.texture = this.textures[0];
46 |
47 | // note depth textures break stencil - so can't use together
48 | if (depthTexture && (this.gl.renderer.isWebgl2 || this.gl.renderer.getExtension('WEBGL_depth_texture'))) {
49 | this.depthTexture = new Texture(gl, {
50 | width, height, wrapS, wrapT,
51 | minFilter: this.gl.NEAREST,
52 | magFilter: this.gl.NEAREST,
53 | flipY: false,
54 | format: this.gl.DEPTH_COMPONENT,
55 | internalFormat: gl.renderer.isWebgl2 ? this.gl.DEPTH_COMPONENT24 : this.gl.DEPTH_COMPONENT,
56 | type: this.gl.UNSIGNED_INT,
57 | generateMipmaps: false,
58 | });
59 | this.depthTexture.update();
60 | this.gl.framebufferTexture2D(this.target, this.gl.DEPTH_ATTACHMENT, this.gl.TEXTURE_2D, this.depthTexture.texture, 0 /* level */);
61 | } else {
62 |
63 | // Render buffers
64 | if (depth && !stencil) {
65 | this.depthBuffer = this.gl.createRenderbuffer();
66 | this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.depthBuffer);
67 | this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, width, height);
68 | this.gl.framebufferRenderbuffer(this.target, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, this.depthBuffer);
69 | }
70 |
71 | if (stencil && !depth) {
72 | this.stencilBuffer = this.gl.createRenderbuffer();
73 | this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.stencilBuffer);
74 | this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.STENCIL_INDEX8, width, height);
75 | this.gl.framebufferRenderbuffer(this.target, this.gl.STENCIL_ATTACHMENT, this.gl.RENDERBUFFER, this.stencilBuffer);
76 | }
77 |
78 | if (depth && stencil) {
79 | this.depthStencilBuffer = this.gl.createRenderbuffer();
80 | this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.depthStencilBuffer);
81 | this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_STENCIL, width, height);
82 | this.gl.framebufferRenderbuffer(this.target, this.gl.DEPTH_STENCIL_ATTACHMENT, this.gl.RENDERBUFFER, this.depthStencilBuffer);
83 | }
84 | }
85 |
86 | this.gl.bindFramebuffer(this.target, null);
87 | }
88 |
89 | }
--------------------------------------------------------------------------------
/src/PNG.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Converts a Typed Array into an RGBA PNG.
4 | Lossless, non-compressed, non-filtered.
5 | Just the raw data wrapped in the necessary zlib headers.
6 |
7 | */
8 |
9 | import { ArrayBufferWalker } from './PNGWalker.js';
10 |
11 | export function writePNG(width, height, data) {
12 | let w = width;
13 | let h = height;
14 |
15 | const PRE_HEADER = '\x89PNG\r\n\x1A\n';
16 | const BLOCK_SIZE = 65535;
17 | const dataLength = w * h * 4 + h; // add extra height for filter byte per scanline
18 | const numberOfBlocks = Math.ceil(dataLength / BLOCK_SIZE);
19 | const zlibDataLength = (() => {
20 | return 1 + // Compression method/flags code
21 | 1 + // Additional flags/check bits
22 | (5 * numberOfBlocks) + // Number of Zlib block headers we'll need
23 | 4 + // ADLER checksum
24 | dataLength; // actual data
25 | })();
26 |
27 | const arrayBufferLength =
28 |
29 | // Header
30 | 8 +
31 |
32 | // IHDR
33 | 4 + // Chunk length identifier
34 | 4 + // chunk header
35 | 13 + // actual IHDR length
36 | 4 + // CRC32 check;
37 |
38 | // IDAT
39 | 4 + // chunk length
40 | 4 + // "IDAT"
41 | zlibDataLength + // raw data wrapped in the zlib shit
42 | 4 + // CRC
43 |
44 | // IEND
45 | 4 + // "IEND"
46 | 4 + // CRC
47 | 4; // length
48 |
49 | const buffer = new ArrayBuffer(arrayBufferLength);
50 | const walker = new ArrayBufferWalker(buffer);
51 |
52 | // Header
53 | walker.writeString(PRE_HEADER);
54 |
55 | // IHDR
56 | walker.writeUint32(13); // IDHR is always 13 bytes
57 | walker.startCRC();
58 | walker.writeString("IHDR");
59 | walker.writeUint32(w);
60 | walker.writeUint32(h);
61 | walker.writeUint8(8); // bitDepth
62 | walker.writeUint8(6); // color type. 6 = RGBA
63 | walker.writeUint8(0); // compressionMethod. 0 = none
64 | walker.writeUint8(0); // filter. 0 = none
65 | walker.writeUint8(0); // interface. 0 = none
66 | walker.writeCRC();
67 |
68 | // IDAT (data)
69 | walker.writeUint32(zlibDataLength);
70 | walker.startCRC();
71 | walker.writeString("IDAT");
72 |
73 | // zlib header
74 | walker.writeUint8(120);
75 | walker.writeUint8(1);
76 |
77 | let bytesLeft = dataLength;
78 | let bytesLeftInWindow = 0;
79 |
80 | function startBlock() {
81 |
82 | // Whether this is the final block. If we've got less than 32KB to write, then yes.
83 | let bfinal = bytesLeft < BLOCK_SIZE ? 1 : 0;
84 |
85 | // Compression type. Will always be zero = uncompressed
86 | let btype = 0;
87 | walker.writeUint8((bfinal) | (btype << 1));
88 |
89 | // Again, this logic comes from: https://github.com/imaya/zlib.js/blob/master/src/deflate.js#L110
90 | let blockLength = Math.min(bytesLeft, BLOCK_SIZE);
91 | let nlen = (~blockLength + 0x10000) & 0xffff;
92 | // IMPORTANT: these values must be little-endian.
93 | walker.writeUint16(blockLength, true);
94 | walker.writeUint16(nlen, true);
95 |
96 | bytesLeftInWindow = Math.min(bytesLeft, BLOCK_SIZE);
97 | }
98 |
99 | function writeBlockData(val) {
100 | if (bytesLeft <= 0) {
101 | throw new Error('Ran out of space');
102 | }
103 | if (bytesLeftInWindow === 0) {
104 | walker.pauseAdler();
105 | startBlock();
106 | walker.startAdler();
107 | }
108 | walker.writeUint8(val);
109 | bytesLeftInWindow--;
110 | bytesLeft--;
111 | }
112 |
113 | startBlock();
114 | walker.startAdler();
115 |
116 | // Read rows back-to-front as gl.readPixels method flips canvas
117 | for (let i = h - 1; i >= 0; i--) {
118 | writeBlockData(0); // filter type per scanline
119 | for (let j = 0; j < w; j++) {
120 | let pixel = i * w + j;
121 | writeBlockData(data[pixel * 4 + 0]); // red
122 | writeBlockData(data[pixel * 4 + 1]); // green
123 | writeBlockData(data[pixel * 4 + 2]); // blue
124 | writeBlockData(data[pixel * 4 + 3]); // alpha
125 | }
126 | }
127 |
128 | walker.writeAdler();
129 | walker.writeCRC();
130 |
131 | // IEND
132 | walker.writeUint32(0);
133 | walker.startCRC();
134 | walker.writeString('IEND');
135 | walker.writeCRC();
136 |
137 | return buffer;
138 | }
--------------------------------------------------------------------------------
/src/PBR.js:
--------------------------------------------------------------------------------
1 | import {
2 | Renderer,
3 | Transform,
4 | Camera,
5 | Orbit,
6 | Geometry,
7 | Sphere,
8 | Cube,
9 | Texture,
10 | Program,
11 | Mesh,
12 | Color,
13 | Vec3,
14 | } from '../ogl/OGL.js';
15 | import { convertGLSL } from './GLSLVersion.js';
16 | import PBRShader from './shaders/PBRShader.js';
17 | import BackgroundShader from './shaders/BackgroundShader.js';
18 |
19 | const OUTPUT_SIZE = 512;
20 |
21 | // sRGB == 0
22 | // RGBE == 1
23 | // RGBM == 2
24 | // RGBD == 3
25 | let INPUT_TYPE = 0;
26 |
27 | const renderer = new Renderer({ dpr: 2 });
28 | renderer.setSize(OUTPUT_SIZE, OUTPUT_SIZE);
29 | const gl = renderer.gl;
30 | document.body.appendChild(gl.canvas);
31 |
32 | const camera = new Camera(gl, { fov: 35 });
33 | camera.position.set(2, 0.5, 3);
34 |
35 | // Create controls and pass parameters
36 | const controls = new Orbit(camera, {
37 | enableZoom: false,
38 | });
39 |
40 | function resize() {
41 | renderer.setSize(window.innerWidth, window.innerHeight);
42 | camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });
43 | }
44 | window.addEventListener('resize', resize, false);
45 | resize();
46 |
47 | const scene = new Transform();
48 |
49 | const program = new Program(gl, {
50 | vertexShader: PBRShader.vertex,
51 | fragmentShader: PBRShader.fragment,
52 | uniforms: {
53 | uBaseColor: { value: new Color([1, 1, 1]) },
54 |
55 | tRMO: { value: getTexture('src/assets/materials/hammered-metal-mro.jpg') },
56 | uRoughness: { value: 1 },
57 | uMetallic: { value: 1 },
58 | uOcclusion: { value: 1 },
59 |
60 | tNormal: { value: getTexture('src/assets/materials/hammered-metal-normal.jpg') },
61 | uNormalScale: { value: 2 },
62 | uNormalUVScale: { value: 3 },
63 |
64 | tLUT: { value: getTexture('src/assets/lut.png', false) },
65 |
66 | tEnvDiffuse: { value: getTexture('src/assets/interior-diffuse-RGBM.png', false) },
67 | tEnvSpecular: { value: getTexture('src/assets/interior-specular-RGBM.png', false) },
68 | uEnvSpecular: { value: 1.0 },
69 |
70 | uInputType: { value: 2 },
71 |
72 | uLightDirection: { value: new Color([1, 1, 1]) },
73 | uLightColor: { value: new Vec3(1) },
74 | },
75 | });
76 |
77 | loadShaderBall();
78 | async function loadShaderBall() {
79 | const data = await (await fetch(`src/assets/shaderball.json`)).json();
80 |
81 | const geometry = new Geometry(gl, {
82 | position: { size: 3, data: new Float32Array(data.position) },
83 | uv: { size: 2, data: new Float32Array(data.uv) },
84 | normal: { size: 3, data: new Float32Array(data.normal) },
85 | });
86 |
87 | const mesh = new Mesh(gl, { geometry, program });
88 | mesh.position.y = -0.5;
89 | mesh.setParent(scene);
90 | }
91 |
92 | const bgGeometry = new Sphere(gl, 1, 32);
93 |
94 | const bgProgram = new Program(gl, {
95 | vertexShader: convertGLSL(gl, BackgroundShader.vertex),
96 | fragmentShader: convertGLSL(gl, BackgroundShader.fragment),
97 | uniforms: {
98 | tMap: { value: getTexture('src/assets/interior-diffuse-RGBM.png', false) },
99 |
100 | uInputType: { value: 2 },
101 | },
102 | cullFace: gl.FRONT,
103 | });
104 |
105 | const background = new Mesh(gl, { geometry: bgGeometry, program: bgProgram });
106 | background.scale.set(5);
107 | background.setParent(scene);
108 |
109 | function getTexture(src, generateMipmaps = true) {
110 | const texture = new Texture(gl, { generateMipmaps });
111 | texture.wrapS = texture.wrapT = gl.REPEAT;
112 | const image = new Image();
113 | image.onload = () => {
114 | texture.image = image;
115 | };
116 | image.src = src;
117 |
118 | return texture;
119 | }
120 |
121 | export function initPBR() {
122 | animate();
123 | function animate() {
124 | requestAnimationFrame(animate);
125 |
126 | controls.update();
127 | renderer.render({ scene, camera });
128 | }
129 | }
130 |
131 | export function updateIBL(specularData, diffuseData, inputType) {
132 | const diffuse = getTexture(URL.createObjectURL(new Blob([diffuseData], { type: 'image/png' })), false);
133 | const specular = getTexture(URL.createObjectURL(new Blob([specularData], { type: 'image/png' })), false);
134 |
135 | bgProgram.uniforms.tMap.value = diffuse;
136 | bgProgram.uniforms.uInputType.value = inputType;
137 |
138 | program.uniforms.tEnvDiffuse.value = diffuse;
139 | program.uniforms.tEnvSpecular.value = specular;
140 | program.uniforms.uInputType.value = inputType;
141 | }
142 |
--------------------------------------------------------------------------------
/src/shaders/DiffuseShader.js:
--------------------------------------------------------------------------------
1 | const vertex = `
2 | precision highp float;
3 | precision highp int;
4 |
5 | attribute vec3 position;
6 | attribute vec2 uv;
7 |
8 | varying vec2 vUv;
9 |
10 | void main() {
11 | vUv = uv;
12 |
13 | gl_Position = vec4(position, 1.0);
14 | }
15 | `;
16 |
17 | const fragment = `
18 | precision highp float;
19 | precision highp int;
20 |
21 | uniform sampler2D tMap;
22 | uniform float uInputType;
23 | uniform float uOutputType;
24 |
25 | varying vec2 vUv;
26 |
27 | const float PI = 3.14159265359;
28 | const float PI2 = 6.28318530718;
29 | const float RECIPROCAL_PI = 0.31830988618;
30 | const float RECIPROCAL_PI2 = 0.15915494;
31 |
32 | vec4 SRGBtoLinear(vec4 srgb) {
33 | vec3 linOut = pow(srgb.xyz, vec3(2.2));
34 | return vec4(linOut, srgb.w);;
35 | }
36 |
37 | vec4 linearToSRGB(vec4 color) {
38 | return vec4(pow(color.rgb, vec3(1.0 / 2.2)), color.a);
39 | }
40 |
41 | vec4 RGBEToLinear(in vec4 value) {
42 | return vec4(value.rgb * exp2(value.a * 255.0 - 128.0), 1.0);
43 | }
44 |
45 | vec4 LinearToRGBE(in vec3 value) {
46 | float maxComponent = max(max(value.r, value.g), value.b);
47 | float fExp = clamp(ceil(log2(maxComponent)), -128.0, 127.0);
48 | return vec4(value.rgb / exp2(fExp), (fExp + 128.0) / 255.0);
49 | }
50 |
51 | vec4 RGBMToLinear(in vec4 value) {
52 | float maxRange = 6.0;
53 | return vec4(value.xyz * value.w * maxRange, 1.0);
54 | }
55 |
56 | vec4 LinearToRGBM(in vec3 value) {
57 | float maxRange = 6.0;
58 | float maxRGB = max(value.x, max(value.g, value.b));
59 | float M = clamp(maxRGB / maxRange, 0.0, 1.0);
60 | M = ceil(M * 255.0) / 255.0;
61 | return vec4(value.rgb / (M * maxRange), M);
62 | }
63 |
64 | vec4 RGBDToLinear(in vec4 value, in float maxRange) {
65 | return vec4(value.rgb * ((maxRange / 255.0) / value.a), 1.0);
66 | }
67 |
68 | vec4 LinearToRGBD(in vec3 value, in float maxRange) {
69 | float maxRGB = max(value.x, max(value.g, value.b));
70 | float D = max(maxRange / maxRGB, 1.0);
71 | D = min(floor(D) / 255.0, 1.0);
72 | return vec4(value.rgb * (D * (255.0 / maxRange)), D);
73 | }
74 |
75 | vec2 cartesianToPolar(vec3 n) {
76 | vec2 uv;
77 | uv.x = atan(n.z, n.x) * RECIPROCAL_PI2 + 0.5;
78 | uv.y = asin(n.y) * RECIPROCAL_PI + 0.5;
79 | return uv;
80 | }
81 |
82 | vec3 polarToCartesian(vec2 uv) {
83 | float theta = (uv.x - 0.5) * PI2;
84 | float phi = (uv.y) * PI;
85 |
86 | vec3 n;
87 | n.x = sin(phi) * cos(theta);
88 | n.z = sin(phi) * sin(theta);
89 | n.y = -cos(phi);
90 | return normalize(n);
91 | }
92 |
93 | void main() {
94 | vec2 uv = vUv;
95 | vec3 normal = polarToCartesian(uv);
96 | vec3 irradiance;
97 |
98 | vec3 up = vec3(0.0, 1.0, 0.0);
99 | vec3 right = cross(up, normal);
100 | up = cross(normal, right);
101 |
102 | const float delta = 0.025;
103 | float samples = 0.0;
104 | for(float phi = 0.0; phi < PI2; phi += delta) {
105 | for(float theta = 0.0; theta < 0.5 * PI; theta += delta) {
106 | vec3 tangent = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta));
107 | vec3 dir = tangent.x * right + tangent.y * up + tangent.z * normal;
108 |
109 | vec3 sample;
110 | if (uInputType < 0.5) {
111 |
112 | // sRGB == 0
113 | sample = SRGBtoLinear(texture2D(tMap, cartesianToPolar(dir))).rgb;
114 | } else if (uInputType < 1.5) {
115 |
116 | // RGBE == 1
117 | sample = RGBEToLinear(texture2D(tMap, cartesianToPolar(dir))).rgb;
118 | } else if (uInputType < 2.5) {
119 |
120 | // RGBM == 2
121 | sample = RGBMToLinear(texture2D(tMap, cartesianToPolar(dir))).rgb;
122 | } else if (uInputType < 3.5) {
123 |
124 | // RGBD == 3
125 | sample = RGBDToLinear(texture2D(tMap, cartesianToPolar(dir)), 6.0).rgb;
126 | }
127 | irradiance += sample * cos(theta) * sin(theta);
128 |
129 | samples++;
130 | }
131 | }
132 | irradiance = PI * irradiance / samples;
133 |
134 | if (uOutputType < 0.5) {
135 |
136 | // sRGB == 0
137 | gl_FragColor = linearToSRGB(vec4(irradiance, 1.0));
138 | } else if (uOutputType < 1.5) {
139 |
140 | // RGBE == 1
141 | gl_FragColor = LinearToRGBE(irradiance);
142 | } else if (uOutputType < 2.5) {
143 |
144 | // RGBM == 2
145 | gl_FragColor = LinearToRGBM(irradiance);
146 | } else if (uOutputType < 3.5) {
147 |
148 | // RGBD == 3
149 | gl_FragColor = LinearToRGBD(irradiance, 6.0);
150 | }
151 | }
152 | `;
153 |
154 | export default {vertex, fragment};
--------------------------------------------------------------------------------
/src/shaders/SpecularShader.js:
--------------------------------------------------------------------------------
1 | const vertex = `
2 | precision highp float;
3 | precision highp int;
4 |
5 | attribute vec3 position;
6 | attribute vec2 uv;
7 |
8 | varying vec2 vUv;
9 |
10 | void main() {
11 | vUv = uv;
12 | gl_Position = vec4(position, 1.0);
13 | }
14 | `;
15 |
16 | const fragment = `
17 | precision highp float;
18 | precision highp int;
19 |
20 | uniform sampler2D tMap;
21 | uniform float uRoughness;
22 | uniform float uInputType;
23 | uniform float uOutputType;
24 |
25 | varying vec2 vUv;
26 |
27 | const int SAMPLES = 1000;
28 |
29 | const float PI = 3.14159265359;
30 | const float PI2 = 6.28318530718;
31 | const float RECIPROCAL_PI = 0.31830988618;
32 | const float RECIPROCAL_PI2 = 0.15915494;
33 |
34 | float VanDerCorpus(int n, int base) {
35 | float invBase = 1.0 / float(base);
36 | float denom = 1.0;
37 | float result = 0.0;
38 |
39 | for(int i = 0; i < 32; ++i) {
40 | if(n > 0) {
41 | denom = mod(float(n), 2.0);
42 | result += denom * invBase;
43 | invBase = invBase / 2.0;
44 | n = int(float(n) / 2.0);
45 | }
46 | }
47 | return result;
48 | }
49 |
50 | vec2 Hammersley(int i, int N) {
51 | return vec2(float(i) / float(N), VanDerCorpus(i, 2));
52 | }
53 |
54 | vec4 SRGBtoLinear(vec4 srgb) {
55 | vec3 linOut = pow(srgb.xyz, vec3(2.2));
56 | return vec4(linOut, srgb.w);;
57 | }
58 |
59 | vec4 linearToSRGB(vec4 color) {
60 | return vec4(pow(color.rgb, vec3(1.0 / 2.2)), color.a);
61 | }
62 |
63 | vec4 RGBEToLinear(in vec4 value) {
64 | return vec4(value.rgb * exp2(value.a * 255.0 - 128.0), 1.0);
65 | }
66 |
67 | vec4 LinearToRGBE(in vec3 value) {
68 | float maxComponent = max(max(value.r, value.g), value.b);
69 | float fExp = clamp(ceil(log2(maxComponent)), -128.0, 127.0);
70 | return vec4(value.rgb / exp2(fExp), (fExp + 128.0) / 255.0);
71 | }
72 |
73 | vec4 RGBMToLinear(in vec4 value) {
74 | float maxRange = 6.0;
75 | return vec4(value.xyz * value.w * maxRange, 1.0);
76 | }
77 |
78 | vec4 LinearToRGBM(in vec3 value) {
79 | float maxRange = 6.0;
80 | float maxRGB = max(value.r, max(value.g, value.b));
81 | float M = clamp(maxRGB / maxRange, 0.0, 1.0);
82 | M = ceil(M * 255.0) / 255.0;
83 | return vec4(value.rgb / (M * maxRange), M);
84 | }
85 |
86 | vec4 RGBDToLinear(in vec4 value, in float maxRange) {
87 | return vec4(value.rgb * ((maxRange / 255.0) / value.a), 1.0);
88 | }
89 |
90 | vec4 LinearToRGBD(in vec3 value, in float maxRange) {
91 | float maxRGB = max(value.x, max(value.g, value.b));
92 | float D = max(maxRange / maxRGB, 1.0);
93 | D = min(floor(D) / 255.0, 1.0);
94 | return vec4(value.rgb * (D * (255.0 / maxRange)), D);
95 | }
96 |
97 | vec3 importanceSampleGGX(vec2 uv, mat3 mNormal, float roughness) {
98 | float a = roughness * roughness;
99 | float phi = 2.0 * PI * uv.x;
100 | float cosTheta = sqrt((1.0 - uv.y) / (1.0 + (a * a - 1.0) * uv.y));
101 | float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
102 | return mNormal * vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
103 | }
104 |
105 | mat3 matrixFromVector(vec3 n) {
106 | float a = 1.0 / (1.0 + n.z);
107 | float b = -n.x * n.y * a;
108 | vec3 b1 = vec3(1.0 - n.x * n.x * a, b, -n.x);
109 | vec3 b2 = vec3(b, 1.0 - n.y * n.y * a, -n.y);
110 | return mat3(b1, b2, n);
111 | }
112 |
113 | vec2 cartesianToPolar(vec3 n) {
114 | vec2 uv;
115 | uv.x = atan(n.z, n.x) * RECIPROCAL_PI2 + 0.5;
116 | uv.y = asin(n.y) * RECIPROCAL_PI + 0.5;
117 | return uv;
118 | }
119 |
120 | vec3 polarToCartesian(vec2 uv) {
121 | float theta = (uv.x - 0.5) * PI2;
122 | float phi = (uv.y) * PI;
123 |
124 | vec3 n;
125 | n.x = sin(phi) * cos(theta);
126 | n.z = sin(phi) * sin(theta);
127 | n.y = -cos(phi);
128 | return normalize(n);
129 | }
130 |
131 | void main() {
132 | vec2 uv = vUv;
133 | vec3 normal = polarToCartesian(uv);
134 | mat3 mNormal = matrixFromVector(normal);
135 | vec3 color;
136 | vec3 dir;
137 | for(int i = 0; i < SAMPLES; i++) {
138 | vec2 r = Hammersley(i, SAMPLES);
139 | dir = importanceSampleGGX(vec2(float(i) / float(SAMPLES), r), mNormal, uRoughness);
140 |
141 | if (uInputType < 0.5) {
142 |
143 | // sRGB == 0
144 | color.rgb += SRGBtoLinear(texture2D(tMap, cartesianToPolar(dir))).rgb;
145 | } else if (uInputType < 1.5) {
146 |
147 | // RGBE == 1
148 | color.rgb += RGBEToLinear(texture2D(tMap, cartesianToPolar(dir))).rgb;
149 | } else if (uInputType < 2.5) {
150 |
151 | // RGBM == 2
152 | color.rgb += RGBMToLinear(texture2D(tMap, cartesianToPolar(dir))).rgb;
153 | } else if (uInputType < 3.5) {
154 |
155 | // RGBD == 3
156 | color.rgb += RGBDToLinear(texture2D(tMap, cartesianToPolar(dir)), 6.0).rgb;
157 | }
158 | }
159 | color /= float(SAMPLES);
160 |
161 | if (uOutputType < 0.5) {
162 |
163 | // sRGB == 0
164 | gl_FragColor = linearToSRGB(vec4(color, 1.0));
165 | } else if (uOutputType < 1.5) {
166 |
167 | // RGBE == 1
168 | gl_FragColor = LinearToRGBE(color);
169 | } else if (uOutputType < 2.5) {
170 |
171 | // RGBM == 2
172 | gl_FragColor = LinearToRGBM(color);
173 | } else if (uOutputType < 3.5) {
174 |
175 | // RGBD == 3
176 | gl_FragColor = LinearToRGBD(color, 6.0);
177 | }
178 | }
179 | `;
180 |
181 | export default {vertex, fragment};
--------------------------------------------------------------------------------
/ogl/core/Geometry.js:
--------------------------------------------------------------------------------
1 | // attribute params
2 | // {
3 | // data - typed array eg UInt16Array for indices, Float32Array
4 | // size - int default 1
5 | // instanced - boolean default false. can pass true or divisor amount
6 | // type - gl enum default gl.UNSIGNED_SHORT for 'index', gl.FLOAT for others
7 | // normalize - boolean default false
8 | // }
9 |
10 | // TODO: fit in transform feedback
11 | // TODO: bounding box for auto culling
12 | // this.boundingBox.center
13 |
14 | // TODO: when would I disableVertexAttribArray ?
15 | // TODO: do I need to unbind after drawing ?
16 | // TODO: test updating attributes on the fly
17 |
18 | let ID = 0;
19 |
20 | export class Geometry {
21 | constructor(gl, attributes = {}) {
22 | this.gl = gl;
23 | this.attributes = attributes;
24 | this.id = ID++;
25 |
26 | this.drawRange = {start: 0, count: 0};
27 | this.instancedCount = 0;
28 |
29 | // Unbind current VAO so that new buffers don't get added to active mesh
30 | this.gl.renderer.bindVertexArray(null);
31 | this.gl.renderer.currentGeometry = null;
32 |
33 | // create the buffers
34 | for (let key in attributes) {
35 | this.addAttribute(key, attributes[key]);
36 | }
37 | }
38 |
39 | addAttribute(key, attr) {
40 | this.attributes[key] = attr;
41 |
42 | // Set options
43 | attr.size = attr.size || 1;
44 | attr.type = attr.type || key === 'index' ? this.gl.UNSIGNED_SHORT : this.gl.FLOAT;
45 | attr.target = key === 'index' ? this.gl.ELEMENT_ARRAY_BUFFER : this.gl.ARRAY_BUFFER;
46 | attr.normalize = attr.normalize || false;
47 | attr.buffer = this.gl.createBuffer();
48 | attr.count = attr.data.length / attr.size;
49 | attr.divisor = !attr.instanced ? 0 : typeof attr.instanced === 'number' ? attr.instanced : 1;
50 |
51 | // Push data to buffer
52 | this.updateAttribute(attr);
53 |
54 | // Update geometry counts. If indexed, ignore regular attributes
55 | if (attr.divisor) {
56 | this.isInstanced = true;
57 | if (this.instancedCount && this.instancedCount !== attr.count * attr.divisor) {
58 | console.warn('geometry has multiple instanced buffers of different length');
59 | return this.instancedCount = Math.min(this.instancedCount, attr.count * attr.divisor);
60 | }
61 | this.instancedCount = attr.count * attr.divisor;
62 | } else if (key === 'index') {
63 | this.drawRange.count = attr.count;
64 | } else if (!this.attributes.index) {
65 | this.drawRange.count = Math.max(this.drawRange.count, attr.count);
66 | }
67 | }
68 |
69 | updateAttribute(attr) {
70 | this.gl.bindBuffer(attr.target, attr.buffer);
71 | this.gl.bufferData(attr.target, attr.data, this.gl.STATIC_DRAW);
72 | this.gl.bindBuffer(attr.target, null);
73 | }
74 |
75 | setIndex(value) {
76 | this.addAttribute('index', value);
77 | }
78 |
79 | setDrawRange(start, count) {
80 | this.drawRange.start = start;
81 | this.drawRange.count = count;
82 | }
83 |
84 | setInstancedCount(value) {
85 | this.instancedCount = value;
86 | }
87 |
88 | createVAO(program) {
89 | this.vao = this.gl.renderer.createVertexArray();
90 | this.gl.renderer.bindVertexArray(this.vao);
91 | this.bindAttributes(program);
92 | }
93 |
94 | bindAttributes(program) {
95 |
96 | // Link all attributes to program using gl.vertexAttribPointer
97 | program.attributeLocations.forEach((location, name) => {
98 |
99 | // If geometry missing a required shader attribute
100 | if (!this.attributes[name]) {
101 | console.warn(`active attribute ${name} not being supplied`);
102 | return;
103 | }
104 |
105 | const attr = this.attributes[name];
106 |
107 | this.gl.bindBuffer(this.gl.ARRAY_BUFFER, attr.buffer);
108 | this.gl.vertexAttribPointer(
109 | location,
110 | attr.size,
111 | attr.type,
112 | attr.normalize,
113 | 0, // stride
114 | 0 // offset
115 | );
116 | this.gl.enableVertexAttribArray(location);
117 |
118 | // For instanced attributes, divisor needs to be set.
119 | // For firefox, need to set back to 0 if non-instanced drawn after instanced. Else won't render
120 | this.gl.renderer.vertexAttribDivisor(location, attr.divisor);
121 | });
122 |
123 | // Bind indices if geometry indexed
124 | if (this.attributes.index) this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.attributes.index.buffer);
125 | }
126 |
127 | draw({
128 | program,
129 | mode = this.gl.TRIANGLES,
130 | geometryBound = false,
131 | }) {
132 | if (!geometryBound) {
133 |
134 | // Create VAO on first draw. Needs to wait for program to get attribute locations
135 | if (!this.vao) this.createVAO(program);
136 |
137 | // TODO: add fallback for non vao support (ie)
138 |
139 | // Bind if not already bound to program
140 | this.gl.renderer.bindVertexArray(this.vao);
141 |
142 | // Store so doesn't bind redundantly
143 | this.gl.renderer.currentGeometry = this.id;
144 | }
145 |
146 | if (this.isInstanced) {
147 | if (this.attributes.index) {
148 | this.gl.renderer.drawElementsInstanced(mode, this.drawRange.count, this.attributes.index.type, this.drawRange.start, this.instancedCount);
149 | } else {
150 | this.gl.renderer.drawArraysInstanced(mode, this.drawRange.start, this.drawRange.count, this.instancedCount);
151 | }
152 | } else {
153 | if (this.attributes.index) {
154 | this.gl.drawElements(mode, this.drawRange.count, this.attributes.index.type, this.drawRange.start);
155 | } else {
156 | this.gl.drawArrays(mode, this.drawRange.start, this.drawRange.count);
157 | }
158 | }
159 | }
160 |
161 | remove() {
162 | if (this.vao) this.gl.renderer.deleteVertexArray(this.vao);
163 | for (let key in this.attributes) {
164 | this.gl.deleteBuffer(this.attributes[key].buffer);
165 | delete this.attributes[key];
166 |
167 | }
168 | }
169 | }
--------------------------------------------------------------------------------
/src/inflate.module.min.js:
--------------------------------------------------------------------------------
1 | /** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */var mod={}, l=void 0,aa=mod;function r(c,d){var a=c.split("."),b=aa;!(a[0]in b)&&b.execScript&&b.execScript("var "+a[0]);for(var e;a.length&&(e=a.shift());)!a.length&&d!==l?b[e]=d:b=b[e]?b[e]:b[e]={}};var t="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;function v(c){var d=c.length,a=0,b=Number.POSITIVE_INFINITY,e,f,g,h,k,m,n,p,s,x;for(p=0;pa&&(a=c[p]),c[p]>=1;x=g<<16|p;for(s=m;s>>=1;switch(c){case 0:var d=this.input,a=this.a,b=this.c,e=this.b,f=d.length,g=l,h=l,k=b.length,m=l;this.d=this.f=0;if(a+1>=f)throw Error("invalid uncompressed block header: LEN");g=d[a++]|d[a++]<<8;if(a+1>=f)throw Error("invalid uncompressed block header: NLEN");h=d[a++]|d[a++]<<8;if(g===~h)throw Error("invalid uncompressed block header: length verify");if(a+g>d.length)throw Error("input buffer is broken");switch(this.i){case A:for(;e+
4 | g>b.length;){m=k-e;g-=m;if(t)b.set(d.subarray(a,a+m),e),e+=m,a+=m;else for(;m--;)b[e++]=d[a++];this.b=e;b=this.e();e=this.b}break;case y:for(;e+g>b.length;)b=this.e({p:2});break;default:throw Error("invalid inflate mode");}if(t)b.set(d.subarray(a,a+g),e),e+=g,a+=g;else for(;g--;)b[e++]=d[a++];this.a=a;this.b=e;this.c=b;break;case 1:this.j(ba,ca);break;case 2:for(var n=C(this,5)+257,p=C(this,5)+1,s=C(this,4)+4,x=new (t?Uint8Array:Array)(D.length),S=l,T=l,U=l,u=l,M=l,F=l,z=l,q=l,V=l,q=0;q=P?8:255>=P?9:279>=P?7:8;var ba=v(O),Q=new (t?Uint8Array:Array)(30),R,ga;R=0;for(ga=Q.length;R=g)throw Error("input buffer is broken");a|=e[f++]<>>d;c.d=b-d;c.a=f;return h}
8 | function E(c,d){for(var a=c.f,b=c.d,e=c.input,f=c.a,g=e.length,h=d[0],k=d[1],m,n;b=g);)a|=e[f++]<>>16;if(n>b)throw Error("invalid code length: "+n);c.f=a>>n;c.d=b-n;c.a=f;return m&65535}
9 | w.prototype.j=function(c,d){var a=this.c,b=this.b;this.o=c;for(var e=a.length-258,f,g,h,k;256!==(f=E(this,c));)if(256>f)b>=e&&(this.b=b,a=this.e(),b=this.b),a[b++]=f;else{g=f-257;k=I[g];0=e&&(this.b=b,a=this.e(),b=this.b);for(;k--;)a[b]=a[b++-h]}for(;8<=this.d;)this.d-=8,this.a--;this.b=b};
10 | w.prototype.w=function(c,d){var a=this.c,b=this.b;this.o=c;for(var e=a.length,f,g,h,k;256!==(f=E(this,c));)if(256>f)b>=e&&(a=this.e(),e=a.length),a[b++]=f;else{g=f-257;k=I[g];0e&&(a=this.e(),e=a.length);for(;k--;)a[b]=a[b++-h]}for(;8<=this.d;)this.d-=8,this.a--;this.b=b};
11 | w.prototype.e=function(){var c=new (t?Uint8Array:Array)(this.b-32768),d=this.b-32768,a,b,e=this.c;if(t)c.set(e.subarray(32768,c.length));else{a=0;for(b=c.length;aa;++a)e[a]=e[d+a];this.b=32768;return e};
12 | w.prototype.z=function(c){var d,a=this.input.length/this.a+1|0,b,e,f,g=this.input,h=this.c;c&&("number"===typeof c.p&&(a=c.p),"number"===typeof c.u&&(a+=c.u));2>a?(b=(g.length-this.a)/this.o[2],f=258*(b/2)|0,e=fd&&(this.c.length=d),c=this.c);return this.buffer=c};function W(c,d){var a,b;this.input=c;this.a=0;if(d||!(d={}))d.index&&(this.a=d.index),d.verify&&(this.A=d.verify);a=c[this.a++];b=c[this.a++];switch(a&15){case ha:this.method=ha;break;default:throw Error("unsupported compression method");}if(0!==((a<<8)+b)%31)throw Error("invalid fcheck flag:"+((a<<8)+b)%31);if(b&32)throw Error("fdict flag is not supported");this.q=new w(c,{index:this.a,bufferSize:d.bufferSize,bufferType:d.bufferType,resize:d.resize})}
15 | W.prototype.k=function(){var c=this.input,d,a;d=this.q.k();this.a=this.q.a;if(this.A){a=(c[this.a++]<<24|c[this.a++]<<16|c[this.a++]<<8|c[this.a++])>>>0;var b=d;if("string"===typeof b){var e=b.split(""),f,g;f=0;for(g=e.length;f>>0;b=e}for(var h=1,k=0,m=b.length,n,p=0;0>>0)throw Error("invalid adler-32 checksum");}return d};var ha=8;r("Zlib.Inflate",W);r("Zlib.Inflate.prototype.decompress",W.prototype.k);var X={ADAPTIVE:B.s,BLOCK:B.t},Y,Z,$,ia;if(Object.keys)Y=Object.keys(X);else for(Z in Y=[],$=0,X)Y[$++]=Z;$=0;for(ia=Y.length;$> 24) & 255;
6 | buff[p + 1] = (n >> 16) & 255;
7 | buff[p + 2] = (n >> 8) & 255;
8 | buff[p + 3] = n & 255;
9 | }
10 | function writeASCII(data, p, s) {
11 | for (var i = 0; i < s.length; i++) data[p + i] = s.charCodeAt(i);
12 | }
13 |
14 | const crcTable = (function () {
15 | var tab = new Uint32Array(256);
16 | for (var n = 0; n < 256; n++) {
17 | var c = n;
18 | for (var k = 0; k < 8; k++) {
19 | if (c & 1) c = 0xedb88320 ^ (c >>> 1);
20 | else c = c >>> 1;
21 | }
22 | tab[n] = c;
23 | }
24 | return tab;
25 | })();
26 | function crcUpdate(c, buf, off, len) {
27 | for (var i = 0; i < len; i++) c = crcTable[(c ^ buf[off + i]) & 0xff] ^ (c >>> 8);
28 | return c;
29 | }
30 | function crc(b, o, l) {
31 | return crcUpdate(0xffffffff, b, o, l) ^ 0xffffffff;
32 | }
33 |
34 | export function encodePNG(bufs, w, h, cc, ac, depth, dels, tabs) {
35 | var nimg = { ctype: 0 + (cc == 1 ? 0 : 2) + (ac == 0 ? 0 : 4), depth: depth, frames: [] };
36 |
37 | var bipp = (cc + ac) * depth,
38 | bipl = bipp * w;
39 | for (var i = 0; i < bufs.length; i++)
40 | nimg.frames.push({
41 | rect: { x: 0, y: 0, width: w, height: h },
42 | img: new Uint8Array(bufs[i]),
43 | blend: 0,
44 | dispose: 1,
45 | bpp: Math.ceil(bipp / 8),
46 | bpl: Math.ceil(bipl / 8),
47 | });
48 |
49 | compressPNG(nimg);
50 |
51 | var out = encodeMain(nimg, w, h, dels, tabs);
52 | return out;
53 | }
54 |
55 | function compressPNG(out) {
56 | for (var i = 0; i < out.frames.length; i++) {
57 | var frm = out.frames[i],
58 | nh = frm.rect.height;
59 | var fdata = new Uint8Array(nh * frm.bpl + nh);
60 | frm.cimg = filterZero(frm.img, nh, frm.bpp, frm.bpl, fdata);
61 | }
62 | }
63 |
64 | function filterZero(img, h, bpp, bpl, data) {
65 | var fls = [];
66 | var opts = { level: 0 };
67 | var CMPR = pako;
68 |
69 | for (var y = 0; y < h; y++) filterLine(data, img, y, bpl, bpp, 0);
70 | fls.push(CMPR['deflate'](data, opts));
71 |
72 | var ti,
73 | tsize = 1e9;
74 | for (var i = 0; i < fls.length; i++)
75 | if (fls[i].length < tsize) {
76 | ti = i;
77 | tsize = fls[i].length;
78 | }
79 | return fls[ti];
80 | }
81 | function filterLine(data, img, y, bpl, bpp, type) {
82 | var i = y * bpl,
83 | di = i + y;
84 | data[di] = type;
85 | di++;
86 |
87 | if (type == 0) {
88 | if (bpl < 500) for (var x = 0; x < bpl; x++) data[di + x] = img[i + x];
89 | else data.set(new Uint8Array(img.buffer, i, bpl), di);
90 | }
91 | }
92 |
93 | function encodeMain(nimg, w, h, dels, tabs) {
94 | if (tabs == null) tabs = {};
95 | var wUi = writeUint,
96 | wAs = writeASCII;
97 | var offset = 8,
98 | pltAlpha = false;
99 |
100 | var leng = 8 + (16 + 5 + 4); /*+ (9+4)*/
101 | if (tabs['sRGB'] != null) leng += 8 + 1 + 4;
102 | if (tabs['pHYs'] != null) leng += 8 + 9 + 4;
103 | if (nimg.ctype == 3) {
104 | var dl = nimg.plte.length;
105 | for (var i = 0; i < dl; i++) if (nimg.plte[i] >>> 24 != 255) pltAlpha = true;
106 | leng += 8 + dl * 3 + 4 + (pltAlpha ? 8 + dl * 1 + 4 : 0);
107 | }
108 | for (var j = 0; j < nimg.frames.length; j++) {
109 | var fr = nimg.frames[j];
110 | leng += fr.cimg.length + 12;
111 | if (j != 0) leng += 4;
112 | }
113 | leng += 12;
114 |
115 | var data = new Uint8Array(leng);
116 | var wr = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
117 | for (var i = 0; i < 8; i++) data[i] = wr[i];
118 |
119 | wUi(data, offset, 13);
120 | offset += 4;
121 | wAs(data, offset, 'IHDR');
122 | offset += 4;
123 | wUi(data, offset, w);
124 | offset += 4;
125 | wUi(data, offset, h);
126 | offset += 4;
127 | data[offset] = nimg.depth;
128 | offset++; // depth
129 | data[offset] = nimg.ctype;
130 | offset++; // ctype
131 | data[offset] = 0;
132 | offset++; // compress
133 | data[offset] = 0;
134 | offset++; // filter
135 | data[offset] = 0;
136 | offset++; // interlace
137 | wUi(data, offset, crc(data, offset - 17, 17));
138 | offset += 4; // crc
139 |
140 | // 13 bytes to say, that it is sRGB
141 | if (tabs['sRGB'] != null) {
142 | wUi(data, offset, 1);
143 | offset += 4;
144 | wAs(data, offset, 'sRGB');
145 | offset += 4;
146 | data[offset] = tabs['sRGB'];
147 | offset++;
148 | wUi(data, offset, crc(data, offset - 5, 5));
149 | offset += 4; // crc
150 | }
151 | if (tabs['pHYs'] != null) {
152 | wUi(data, offset, 9);
153 | offset += 4;
154 | wAs(data, offset, 'pHYs');
155 | offset += 4;
156 | wUi(data, offset, tabs['pHYs'][0]);
157 | offset += 4;
158 | wUi(data, offset, tabs['pHYs'][1]);
159 | offset += 4;
160 | data[offset] = tabs['pHYs'][2];
161 | offset++;
162 | wUi(data, offset, crc(data, offset - 13, 13));
163 | offset += 4; // crc
164 | }
165 |
166 | if (nimg.ctype == 3) {
167 | var dl = nimg.plte.length;
168 | wUi(data, offset, dl * 3);
169 | offset += 4;
170 | wAs(data, offset, 'PLTE');
171 | offset += 4;
172 | for (var i = 0; i < dl; i++) {
173 | var ti = i * 3,
174 | c = nimg.plte[i],
175 | r = c & 255,
176 | g = (c >>> 8) & 255,
177 | b = (c >>> 16) & 255;
178 | data[offset + ti + 0] = r;
179 | data[offset + ti + 1] = g;
180 | data[offset + ti + 2] = b;
181 | }
182 | offset += dl * 3;
183 | wUi(data, offset, crc(data, offset - dl * 3 - 4, dl * 3 + 4));
184 | offset += 4; // crc
185 |
186 | if (pltAlpha) {
187 | wUi(data, offset, dl);
188 | offset += 4;
189 | wAs(data, offset, 'tRNS');
190 | offset += 4;
191 | for (var i = 0; i < dl; i++) data[offset + i] = (nimg.plte[i] >>> 24) & 255;
192 | offset += dl;
193 | wUi(data, offset, crc(data, offset - dl - 4, dl + 4));
194 | offset += 4; // crc
195 | }
196 | }
197 |
198 | var fi = 0;
199 | for (var j = 0; j < nimg.frames.length; j++) {
200 | var fr = nimg.frames[j];
201 |
202 | var imgd = fr.cimg,
203 | dl = imgd.length;
204 | wUi(data, offset, dl + (j == 0 ? 0 : 4));
205 | offset += 4;
206 | var ioff = offset;
207 | wAs(data, offset, j == 0 ? 'IDAT' : 'fdAT');
208 | offset += 4;
209 | if (j != 0) {
210 | wUi(data, offset, fi++);
211 | offset += 4;
212 | }
213 | data.set(imgd, offset);
214 | offset += dl;
215 | wUi(data, offset, crc(data, ioff, offset - ioff));
216 | offset += 4; // crc
217 | }
218 |
219 | wUi(data, offset, 0);
220 | offset += 4;
221 | wAs(data, offset, 'IEND');
222 | offset += 4;
223 | wUi(data, offset, crc(data, offset - 4, 4));
224 | offset += 4; // crc
225 |
226 | return data.buffer;
227 | }
228 |
--------------------------------------------------------------------------------
/ogl/core/Texture.js:
--------------------------------------------------------------------------------
1 | // TODO: Compressed Texture
2 | // TODO: data texture
3 | // TODO: cube map
4 |
5 | const emptyPixel = new Uint8Array(4);
6 |
7 | function isPowerOf2(value) {
8 | return (value & (value - 1)) === 0;
9 | }
10 |
11 | export class Texture {
12 | constructor(gl, {
13 | image,
14 | target = gl.TEXTURE_2D,
15 | type = gl.UNSIGNED_BYTE,
16 | format = gl.RGBA,
17 | internalFormat = format,
18 | wrapS = gl.CLAMP_TO_EDGE,
19 | wrapT = gl.CLAMP_TO_EDGE,
20 | generateMipmaps = true,
21 | minFilter = generateMipmaps ? gl.NEAREST_MIPMAP_LINEAR : gl.LINEAR,
22 | magFilter = gl.LINEAR,
23 | premultiplyAlpha = false,
24 | flipY = true,
25 | level = 0,
26 | width, // used for RenderTargets or Data Textures
27 | height = width,
28 |
29 | // TODO: need? encoding = linearEncoding
30 | } = {}) {
31 | this.gl = gl;
32 |
33 | this.image = image;
34 | this.target = target;
35 | this.type = type;
36 | this.format = format;
37 | this.internalFormat = internalFormat;
38 | this.minFilter = minFilter;
39 | this.magFilter = magFilter;
40 | this.wrapS = wrapS;
41 | this.wrapT = wrapT;
42 | this.generateMipmaps = generateMipmaps;
43 | this.premultiplyAlpha = premultiplyAlpha;
44 | this.flipY = flipY;
45 | this.level = level;
46 | this.width = width;
47 | this.height = height;
48 | this.texture = this.gl.createTexture();
49 |
50 | // Store which values have been set on the gpu. Fill with webgl defaults
51 | this.store = {
52 | image: null,
53 | minFilter: this.gl.NEAREST_MIPMAP_LINEAR,
54 | magFilter: this.gl.LINEAR,
55 | wrapS: this.gl.REPEAT,
56 | wrapT: this.gl.REPEAT,
57 | premultiplyAlpha: false,
58 | flipY: false,
59 | };
60 | }
61 |
62 | update() {
63 |
64 | // Bind so we can set its params
65 | this.gl.bindTexture(this.target, this.texture);
66 |
67 | if (this.flipY !== this.store.flipY) {
68 | this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, this.flipY);
69 | this.store.flipY = this.flipY;
70 | }
71 |
72 | if (this.premultiplyAlpha !== this.store.premultiplyAlpha) {
73 | this.gl.pixelStorei(this.gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this.premultiplyAlpha);
74 | this.store.premultiplyAlpha = this.premultiplyAlpha;
75 | }
76 |
77 | if (this.minFilter !== this.store.minFilter) {
78 | this.gl.texParameteri(this.target, this.gl.TEXTURE_MIN_FILTER, this.minFilter);
79 | this.store.minFilter = this.minFilter;
80 | }
81 |
82 | if (this.magFilter !== this.store.magFilter) {
83 | this.gl.texParameteri(this.target, this.gl.TEXTURE_MAG_FILTER, this.magFilter);
84 | this.store.magFilter = this.magFilter;
85 | }
86 |
87 | if (this.wrapS !== this.store.wrapS) {
88 | this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
89 | this.store.wrapS = this.wrapS;
90 | }
91 |
92 | if (this.wrapT !== this.store.wrapT) {
93 | this.gl.texParameteri(this.target, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
94 | this.store.wrapT = this.wrapT;
95 | }
96 |
97 | if (this.image !== this.store.image || this.needsUpdate) {
98 | this.needsUpdate = false;
99 | if (this.image) {
100 |
101 | if (this.image.width) {
102 | this.width = this.image.width;
103 | this.height = this.image.height;
104 | }
105 |
106 | // TODO: all sides if cubemap
107 | // gl.TEXTURE_CUBE_MAP_POSITIVE_X
108 |
109 | // TODO: check is ArrayBuffer.isView is best way to check for Typed Arrays?
110 | if (this.gl.renderer.isWebgl2 || ArrayBuffer.isView(this.image)) {
111 | this.gl.texImage2D(this.target, this.level, this.internalFormat, this.width, this.height, 0 /* border */, this.format, this.type, this.image);
112 | } else {
113 | this.gl.texImage2D(this.target, this.level, this.internalFormat, this.format, this.type, this.image);
114 | }
115 |
116 | // TODO: support everything
117 | // WebGL1:
118 | // gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView? pixels);
119 | // gl.texImage2D(target, level, internalformat, format, type, ImageData? pixels);
120 | // gl.texImage2D(target, level, internalformat, format, type, HTMLImageElement? pixels);
121 | // gl.texImage2D(target, level, internalformat, format, type, HTMLCanvasElement? pixels);
122 | // gl.texImage2D(target, level, internalformat, format, type, HTMLVideoElement? pixels);
123 | // gl.texImage2D(target, level, internalformat, format, type, ImageBitmap? pixels);
124 |
125 | // WebGL2:
126 | // gl.texImage2D(target, level, internalformat, width, height, border, format, type, GLintptr offset);
127 | // gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLCanvasElement source);
128 | // gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLImageElement source);
129 | // gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLVideoElement source);
130 | // gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageBitmap source);
131 | // gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageData source);
132 | // gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView srcData, srcOffset);
133 |
134 | // TODO: mipmap data textures?
135 | if (this.generateMipmaps) {
136 |
137 | // For WebGL1, if not a power of 2, turn off mips, set wrapping to clamp to edge and minFilter to linear
138 | if (!this.gl.renderer.isWebgl2 && (!isPowerOf2(this.image.width) || !isPowerOf2(this.image.height))) {
139 | this.generateMipmaps = false;
140 | this.wrapS = this.wrapT = this.gl.CLAMP_TO_EDGE;
141 | this.minFilter = this.gl.LINEAR;
142 | } else {
143 | this.gl.generateMipmap(this.target);
144 | }
145 | }
146 | } else {
147 |
148 | if (this.width) {
149 |
150 | // image intentionally left null for RenderTarget
151 | this.gl.texImage2D(this.target, this.level, this.internalFormat, this.width, this.height, 0, this.format, this.type, null);
152 | } else {
153 |
154 | // Upload empty pixel if no image to avoid errors while image or video loading
155 | this.gl.texImage2D(this.target, 0, this.gl.RGBA, 1, 1, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, emptyPixel);
156 | }
157 |
158 | }
159 | this.store.image = this.image;
160 | }
161 | }
162 | }
--------------------------------------------------------------------------------
/src/PNGWalker.js:
--------------------------------------------------------------------------------
1 | // Functions below sourced from the png-pong lib to help with
2 | // the adlers and CNCs and whatnot
3 | // https://github.com/gdnmobilelab/png-pong
4 |
5 | let TABLE = new Int32Array([
6 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
7 | 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
8 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
9 | 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
10 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
11 | 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
12 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
13 | 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
14 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
15 | 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
16 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
17 | 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
18 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
19 | 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
20 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
21 | 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
22 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
23 | 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
24 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
25 | 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
26 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
27 | 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
28 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
29 | 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
30 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
31 | 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
32 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
33 | 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
34 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
35 | 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
36 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
37 | 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
38 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
39 | 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
40 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
41 | 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
42 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
43 | 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
44 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
45 | 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
46 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
47 | 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
48 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
49 | 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
50 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
51 | 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
52 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
53 | 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
54 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
55 | 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
56 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
57 | 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
58 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
59 | 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
60 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
61 | 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
62 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
63 | 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
64 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
65 | 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
66 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
67 | 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
68 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
69 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
70 | ]);
71 |
72 | function crc32(buf, offset, length, previous) {
73 | let crc = previous === 0 ? 0 : ~~previous ^ -1;
74 | for (let index = offset; index < offset + length; index++) {
75 | const byte = buf[index];
76 | crc = TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8);
77 | }
78 | return crc ^ -1;
79 | }
80 |
81 | function adler32_buf(buf, offset, length, seed) {
82 | var a = 1, b = 0, L = offset + length, M = 0;
83 | if (typeof seed === 'number') {
84 | a = seed & 0xFFFF;
85 | b = (seed >>> 16) & 0xFFFF;
86 | }
87 | for (var i = offset; i < L;) {
88 | M = Math.min(L - i, 3850) + i;
89 | for (; i < M; i++) {
90 | a += buf[i] & 0xFF;
91 | b += a;
92 | }
93 | a = (15 * (a >>> 16) + (a & 65535));
94 | b = (15 * (b >>> 16) + (b & 65535));
95 | }
96 | return ((b % 65521) << 16) | (a % 65521);
97 | }
98 |
99 | function swap16(val) {
100 | return ((val & 0xFF) << 8)
101 | | ((val >> 8) & 0xFF);
102 | }
103 |
104 | function swap32(val) {
105 | return ((val & 0xFF) << 24)
106 | | ((val & 0xFF00) << 8)
107 | | ((val >> 8) & 0xFF00)
108 | | ((val >> 24) & 0xFF);
109 | }
110 |
111 | export class ArrayBufferWalker {
112 | constructor(bufferOrLength) {
113 | this.bufferOrLength = bufferOrLength;
114 | this.offset = 0;
115 | if (bufferOrLength instanceof ArrayBuffer) {
116 | this.array = new Uint8Array(bufferOrLength);
117 | }
118 | else {
119 | this.array = new Uint8Array(bufferOrLength);
120 | }
121 | }
122 |
123 | writeUint32(value, littleEndian = false) {
124 | if (littleEndian) {
125 | value = swap32(value);
126 | }
127 | this.array[this.offset++] = (value >> 24) & 255;
128 | this.array[this.offset++] = (value >> 16) & 255;
129 | this.array[this.offset++] = (value >> 8) & 255;
130 | this.array[this.offset++] = value & 255;
131 | }
132 |
133 | writeUint16(value, littleEndian = false) {
134 | if (littleEndian) {
135 | value = swap16(value);
136 | }
137 | this.array[this.offset++] = (value >> 8) & 255;
138 | this.array[this.offset++] = value & 255;
139 | }
140 |
141 | writeUint8(value) {
142 | this.array[this.offset++] = value & 255;
143 | }
144 |
145 | writeString(value) {
146 | for (let i = 0, n = value.length; i < n; i++) {
147 | this.array[this.offset++] = value.charCodeAt(i);
148 | }
149 | }
150 |
151 | startCRC() {
152 | if (this.crcStartOffset) {
153 | throw new Error("CRC already started");
154 | }
155 | this.crcStartOffset = this.offset;
156 | }
157 |
158 | writeCRC() {
159 | if (this.crcStartOffset === undefined) {
160 | throw new Error("CRC has not been started, cannot write");
161 | }
162 | let crc = crc32(this.array, this.crcStartOffset, this.offset - this.crcStartOffset);
163 | this.crcStartOffset = undefined;
164 | this.writeUint32(crc);
165 | }
166 |
167 | startAdler() {
168 | if (this.adlerStartOffset) {
169 | throw new Error("Adler already started");
170 | }
171 | this.adlerStartOffset = this.offset;
172 | }
173 |
174 | pauseAdler() {
175 | if (this.adlerStartOffset === undefined) {
176 | throw new Error("Adler has not been started, cannot pause");
177 | }
178 | this.savedAdlerValue = adler32_buf(this.array, this.adlerStartOffset, this.offset - this.adlerStartOffset, this.savedAdlerValue);
179 | this.adlerStartOffset = undefined;
180 | }
181 |
182 | writeAdler() {
183 | if (this.adlerStartOffset === undefined && this.savedAdlerValue === undefined) {
184 | throw new Error("CRC has not been started, cannot write");
185 | }
186 | if (this.adlerStartOffset === undefined) {
187 | this.writeUint32(this.savedAdlerValue);
188 | this.savedAdlerValue = undefined;
189 | return;
190 | }
191 | let adler = adler32_buf(this.array, this.adlerStartOffset, this.offset - this.adlerStartOffset, this.savedAdlerValue);
192 | this.adlerStartOffset = undefined;
193 | this.writeUint32(adler);
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 | IBL Map Converter For PBR
13 |
14 |
15 |
16 |
193 |
194 |
195 |
--------------------------------------------------------------------------------
/src/HDRLoader.js:
--------------------------------------------------------------------------------
1 | // Load 32bit .hdr (RGBE) format
2 |
3 | const RGBE_RETURN_FAILURE = -1;
4 |
5 | // default error routine. Change this to change error handling
6 | const READ_ERROR = 1;
7 | const WRITE_ERROR = 2;
8 | const FORMAT_ERROR = 3;
9 | const MEMORY_ERROR = 4;
10 |
11 | // flags indicating which fields in an rgbe_header_info are valid
12 | const RGBE_VALID_PROGRAMTYPE = 1;
13 | const RGBE_VALID_FORMAT = 2;
14 | const RGBE_VALID_DIMENSIONS = 4;
15 | const NEWLINE = '\n';
16 |
17 | export function parseHDR(buffer) {
18 | // If src passed in instead
19 | // const buffer = await (await fetch(src)).arrayBuffer();
20 |
21 | const byteArray = new Uint8Array(buffer);
22 | byteArray.pos = 0;
23 |
24 | const rgbe_header_info = readHeader(byteArray);
25 | if (rgbe_header_info === RGBE_RETURN_FAILURE) return;
26 |
27 | let width = rgbe_header_info.width;
28 | let height = rgbe_header_info.height;
29 | let image_rgba_data = readPixels_RLE(byteArray.subarray(byteArray.pos), width, height);
30 | if (image_rgba_data === RGBE_RETURN_FAILURE) return;
31 |
32 | return {
33 | width,
34 | height,
35 | data: image_rgba_data,
36 | header: rgbe_header_info,
37 | gamma: rgbe_header_info.gamma,
38 | exposure: rgbe_header_info.exposure,
39 | format: 'RGBE',
40 | type: 'UnsignedByteType',
41 | };
42 |
43 | function logError(logError_code, msg) {
44 | switch (logError_code) {
45 | case READ_ERROR:
46 | console.error('Read Error: ' + (msg || ''));
47 | break;
48 | case WRITE_ERROR:
49 | console.error('Write Error: ' + (msg || ''));
50 | break;
51 | case FORMAT_ERROR:
52 | console.error('Bad File Format: ' + (msg || ''));
53 | break;
54 | default:
55 | case MEMORY_ERROR:
56 | console.error('Error: ' + (msg || ''));
57 | }
58 | return RGBE_RETURN_FAILURE;
59 | }
60 |
61 | function fgets(buffer, lineLimit = 1024, consume) {
62 | let p = buffer.pos;
63 | let i = -1;
64 | let len = 0;
65 | let s = '';
66 | let chunkSize = 128;
67 | let chunk = String.fromCharCode.apply(null, new Uint16Array(buffer.subarray(p, p + chunkSize)));
68 |
69 | while ((i = chunk.indexOf(NEWLINE)) < 0 && len < lineLimit && p < buffer.byteLength) {
70 | s += chunk;
71 | len += chunk.length;
72 | p += chunkSize;
73 | chunk += String.fromCharCode.apply(null, new Uint16Array(buffer.subarray(p, p + chunkSize)));
74 | }
75 | if (i > -1) {
76 | buffer.pos += len + i + 1;
77 | return s + chunk.slice(0, i);
78 | }
79 | return false;
80 | }
81 |
82 | function readHeader(buffer) {
83 | // regexes to parse header info fields
84 | const magic_token_re = /^#\?(\S+)$/;
85 | const gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/;
86 | const exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/;
87 | const format_re = /^\s*FORMAT=(\S+)\s*$/;
88 | const dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/;
89 |
90 | // RGBE format header struct
91 | const header = {
92 | valid: 0 /* indicate which fields are valid */,
93 | string: '' /* the actual header string */,
94 | comments: '' /* comments found in header */,
95 | programtype:
96 | 'RGBE' /* listed at beginning of file to identify it after "#?". defaults to "RGBE" */,
97 | format: '' /* RGBE format, default 32-bit_rle_rgbe */,
98 | gamma: 1.0 /* image has already been gamma corrected with given gamma. defaults to 1.0 (no correction) */,
99 | exposure: 1.0 /* a value of 1.0 in an image corresponds to watts/steradian/m^2. defaults to 1.0 */,
100 | width: 0,
101 | height: 0 /* image dimensions, width/height */,
102 | };
103 |
104 | let line = fgets(buffer);
105 | if (buffer.pos >= buffer.byteLength || !line) return logError(READ_ERROR, 'no header found');
106 |
107 | let match = line.match(magic_token_re);
108 | if (!match) return logError(FORMAT_ERROR, 'bad initial token');
109 |
110 | header.valid |= RGBE_VALID_PROGRAMTYPE;
111 | header.programtype = match[1];
112 | header.string += line + '\n';
113 |
114 | while (true) {
115 | line = fgets(buffer);
116 | if (line === false) break;
117 | header.string += line + '\n';
118 | if (line.charAt(0) === '#') {
119 | header.comments += line + '\n';
120 | continue; // comment line
121 | }
122 | if ((match = line.match(gamma_re))) {
123 | header.gamma = parseFloat(match[1], 10);
124 | }
125 | if ((match = line.match(exposure_re))) {
126 | header.exposure = parseFloat(match[1], 10);
127 | }
128 | if ((match = line.match(format_re))) {
129 | header.valid |= RGBE_VALID_FORMAT;
130 | header.format = match[1]; //'32-bit_rle_rgbe';
131 | }
132 | if ((match = line.match(dimensions_re))) {
133 | header.valid |= RGBE_VALID_DIMENSIONS;
134 | header.height = parseInt(match[1], 10);
135 | header.width = parseInt(match[2], 10);
136 | }
137 | if (header.valid & RGBE_VALID_FORMAT && header.valid & RGBE_VALID_DIMENSIONS) break;
138 | }
139 | if (!(header.valid & RGBE_VALID_FORMAT)) {
140 | return logError(FORMAT_ERROR, 'missing format specifier');
141 | }
142 | if (!(header.valid & RGBE_VALID_DIMENSIONS)) {
143 | return logError(FORMAT_ERROR, 'missing image size specifier');
144 | }
145 | return header;
146 | }
147 |
148 | function readPixels_RLE(buffer, w, h) {
149 | const scanline_width = w;
150 | let num_scanlines = h;
151 |
152 | // run length encoding is not allowed so read flat
153 | // this file is not run length encoded
154 | if (
155 | scanline_width < 8 ||
156 | scanline_width > 0x7fff ||
157 | 2 !== buffer[0] ||
158 | 2 !== buffer[1] ||
159 | buffer[2] & 0x80
160 | ) {
161 | // return the flat buffer
162 | return new Uint8Array(buffer);
163 | }
164 | if (scanline_width !== ((buffer[2] << 8) | buffer[3])) {
165 | return logError(FORMAT_ERROR, 'wrong scanline width');
166 | }
167 |
168 | // Create output buffer
169 | const data_rgba = new Uint8Array(4 * w * h);
170 | if (!data_rgba || !data_rgba.length) return logError(MEMORY_ERROR, 'unable to allocate buffer space');
171 |
172 | let offset = 0;
173 | let pos = 0;
174 | const ptr_end = 4 * scanline_width;
175 | const rgbeStart = new Uint8Array(4);
176 | const scanline_buffer = new Uint8Array(ptr_end);
177 |
178 | // read in each successive scanline
179 | while (num_scanlines > 0 && pos < buffer.byteLength) {
180 | if (pos + 4 > buffer.byteLength) {
181 | return logError(READ_ERROR);
182 | }
183 | rgbeStart[0] = buffer[pos++];
184 | rgbeStart[1] = buffer[pos++];
185 | rgbeStart[2] = buffer[pos++];
186 | rgbeStart[3] = buffer[pos++];
187 | if (
188 | 2 != rgbeStart[0] ||
189 | 2 != rgbeStart[1] ||
190 | ((rgbeStart[2] << 8) | rgbeStart[3]) != scanline_width
191 | ) {
192 | return logError(FORMAT_ERROR, 'bad rgbe scanline format');
193 | }
194 |
195 | // read each of the four channels for the scanline into the buffer
196 | // first red, then green, then blue, then exponent
197 | let ptr = 0;
198 | while (ptr < ptr_end && pos < buffer.byteLength) {
199 | let count = buffer[pos++];
200 | const isEncodedRun = count > 128;
201 | if (isEncodedRun) count -= 128;
202 | if (0 === count || ptr + count > ptr_end) {
203 | return logError(FORMAT_ERROR, 'bad scanline data');
204 | }
205 | if (isEncodedRun) {
206 | // a (encoded) run of the same value
207 | let byteValue = buffer[pos++];
208 | for (let i = 0; i < count; i++) {
209 | scanline_buffer[ptr++] = byteValue;
210 | }
211 | } else {
212 | // a literal-run
213 | scanline_buffer.set(buffer.subarray(pos, pos + count), ptr);
214 | ptr += count;
215 | pos += count;
216 | }
217 | }
218 |
219 | // now convert data from buffer into rgba
220 | // first red, then green, then blue, then exponent (alpha)
221 | for (let i = 0; i < scanline_width; i++) {
222 | let off = 0;
223 | data_rgba[offset] = scanline_buffer[i + off];
224 | off += scanline_width; //1;
225 | data_rgba[offset + 1] = scanline_buffer[i + off];
226 | off += scanline_width; //1;
227 | data_rgba[offset + 2] = scanline_buffer[i + off];
228 | off += scanline_width; //1;
229 | data_rgba[offset + 3] = scanline_buffer[i + off];
230 | offset += 4;
231 | }
232 | num_scanlines--;
233 | }
234 | return data_rgba;
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/ogl/core/Program.js:
--------------------------------------------------------------------------------
1 | // TODO: upload empty texture if null
2 | // TODO: upload identity matrix if null
3 | // TODO: texture units/locations
4 | // TODO: sampler Cube
5 |
6 | let ID = 0;
7 |
8 | // cache of typed arrays used to flatten uniform arrays
9 | const arrayCacheF32 = {};
10 |
11 | function setUniform(gl, type, location, value) {
12 | switch (type) {
13 | case 5126 : return value.length ? gl.uniform1fv(location, value) : gl.uniform1f(location, value); // FLOAT
14 | case 35664 : return gl.uniform2fv(location, value[0].length ? flatten(value) : value); // FLOAT_VEC2
15 | case 35665 : return gl.uniform3fv(location, value[0].length ? flatten(value) : value); // FLOAT_VEC3
16 | case 35666 : return gl.uniform4fv(location, value[0].length ? flatten(value) : value); // FLOAT_VEC4
17 | case 35670 : // BOOL
18 | case 5124 : // INT
19 | case 35678 : // SAMPLER_2D
20 | case 35680 : return value.length ? gl.uniform1iv(location, value) : gl.uniform1i(location, value); // SAMPLER_CUBE
21 | case 35671 : // BOOL_VEC2
22 | case 35667 : return gl.uniform2iv(location, value); // INT_VEC2
23 | case 35672 : // BOOL_VEC3
24 | case 35668 : return gl.uniform3iv(location, value); // INT_VEC3
25 | case 35673 : // BOOL_VEC4
26 | case 35669 : return gl.uniform4iv(location, value); // INT_VEC4
27 | case 35674 : return gl.uniformMatrix2fv(location, false, value[0].length ? flatten(value) : value); // FLOAT_MAT2
28 | case 35675 : return gl.uniformMatrix3fv(location, false, value[0].length ? flatten(value) : value); // FLOAT_MAT3
29 | case 35676 : return gl.uniformMatrix4fv(location, false, value[0].length ? flatten(value) : value); // FLOAT_MAT4
30 | }
31 | }
32 |
33 | function addLineNumbers(string) {
34 | let lines = string.split('\n');
35 | for (let i = 0; i < lines.length; i ++) {
36 | lines[i] = (i + 1) + ': ' + lines[i];
37 | }
38 | return lines.join('\n');
39 | }
40 |
41 | function flatten(array) {
42 | const arrayLen = array.length;
43 | const valueLen = array[0].length;
44 | const length = arrayLen * valueLen;
45 | let value = arrayCacheF32[length];
46 | if (!value) arrayCacheF32[length] = value = new Float32Array(length);
47 | for (let i = 0; i < arrayLen; i++) value.set(array[i], i * valueLen);
48 | return value;
49 | }
50 |
51 | export class Program {
52 | constructor(gl, {
53 | vertexShader,
54 | fragmentShader,
55 | uniforms = {},
56 |
57 | transparent = false,
58 | cullFace = gl.BACK,
59 | frontFace = gl.CCW,
60 | depthTest = true,
61 | depthWrite = true,
62 | depthFunc = gl.LESS,
63 | } = {}) {
64 | this.gl = gl;
65 | this.uniforms = uniforms;
66 | this.id = ID++;
67 |
68 | if (!vertexShader) console.warn('vertex shader not supplied');
69 | if (!fragmentShader) console.warn('fragment shader not supplied');
70 |
71 | // Store program state
72 | this.transparent = transparent;
73 | this.cullFace = cullFace;
74 | this.frontFace = frontFace;
75 | this.depthTest = depthTest;
76 | this.depthWrite = depthWrite;
77 | this.depthFunc = depthFunc;
78 | this.blendFunc = {};
79 | this.blendEquation = {};
80 |
81 | // set default blendFunc if transparent flagged
82 | if (this.transparent && !this.blendFunc.src) {
83 | if (this.gl.renderer.premultipliedAlpha) this.setBlendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);
84 | else this.setBlendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
85 | }
86 |
87 | // compile vertex shader and log errors
88 | this.vertexShader = gl.createShader(gl.VERTEX_SHADER);
89 | gl.shaderSource(this.vertexShader, vertexShader);
90 | gl.compileShader(this.vertexShader);
91 | if (gl.getShaderInfoLog(this.vertexShader) !== '') {
92 | console.warn(`${gl.getShaderInfoLog(this.vertexShader)}\nVertex Shader\n${addLineNumbers(vertexShader)}`);
93 | }
94 |
95 | // compile fragment shader and log errors
96 | this.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
97 | gl.shaderSource(this.fragmentShader, fragmentShader);
98 | gl.compileShader(this.fragmentShader);
99 | if (gl.getShaderInfoLog(this.fragmentShader) !== '') {
100 | console.warn(`${gl.getShaderInfoLog(this.fragmentShader)}\nFragment Shader\n${addLineNumbers(fragmentShader)}`);
101 | }
102 |
103 | // compile program and log errors
104 | this.program = gl.createProgram();
105 | gl.attachShader(this.program, this.vertexShader);
106 | gl.attachShader(this.program, this.fragmentShader);
107 | gl.linkProgram(this.program);
108 | if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
109 | return console.warn(gl.getProgramInfoLog(this.program));
110 | }
111 |
112 | // Get active uniform locations
113 | this.uniformLocations = new Map();
114 | let numUniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
115 | for (let uIndex = 0; uIndex < numUniforms; uIndex++) {
116 | let uniform = gl.getActiveUniform(this.program, uIndex);
117 | this.uniformLocations.set(uniform, gl.getUniformLocation(this.program, uniform.name));
118 |
119 | // trim uniforms' names to omit array declarations
120 | uniform.uniformName = uniform.name.split('[')[0];
121 | }
122 |
123 | // Get active attribute locations
124 | this.attributeLocations = new Map();
125 | let numAttribs = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
126 | for (let aIndex = 0; aIndex < numAttribs; aIndex++) {
127 | let attribute = gl.getActiveAttrib(this.program, aIndex);
128 | this.attributeLocations.set(attribute.name, gl.getAttribLocation(this.program, attribute.name));
129 | }
130 | }
131 |
132 | setBlendFunc(src, dst, srcAlpha, dstAlpha) {
133 | this.blendFunc.src = src;
134 | this.blendFunc.dst = dst;
135 | this.blendFunc.srcAlpha = srcAlpha;
136 | this.blendFunc.dstAlpha = dstAlpha;
137 | if (src) this.transparent = true;
138 | }
139 |
140 | setBlendEquation(modeRGB, modeAlpha) {
141 | this.blendEquation.modeRGB = modeRGB;
142 | this.blendEquation.modeAlpha = modeAlpha;
143 | }
144 |
145 | applyState() {
146 | if (this.depthTest) this.gl.renderer.enable(this.gl.DEPTH_TEST);
147 | else this.gl.renderer.disable(this.gl.DEPTH_TEST);
148 |
149 | if (this.cullFace) this.gl.renderer.enable(this.gl.CULL_FACE);
150 | else this.gl.renderer.disable(this.gl.CULL_FACE);
151 |
152 | if (this.blendFunc.src) this.gl.renderer.enable(this.gl.BLEND);
153 | else this.gl.renderer.disable(this.gl.BLEND);
154 |
155 | if (this.cullFace) this.gl.renderer.setCullFace(this.cullFace);
156 | this.gl.renderer.setFrontFace(this.frontFace);
157 | this.gl.renderer.setDepthMask(this.depthWrite);
158 | this.gl.renderer.setDepthFunc(this.depthFunc);
159 | if (this.blendFunc.src) this.gl.renderer.setBlendFunc(this.blendFunc.src, this.blendFunc.dst, this.blendFunc.srcAlpha, this.blendFunc.dstAlpha);
160 | if (this.blendEquation.modeRGB) this.gl.renderer.setBlendEquation(this.blendEquation.modeRGB, this.blendEquation.modeAlpha);
161 | }
162 |
163 | use({
164 | programActive = false,
165 | flipFaces = false,
166 | } = {}) {
167 | let textureUnit = -1;
168 |
169 | // Avoid gl call if program already in use
170 | if (!programActive) {
171 | this.gl.useProgram(this.program);
172 | this.gl.renderer.currentProgram = this.id;
173 | }
174 |
175 | // Set only the active uniforms found in the shader
176 | this.uniformLocations.forEach((location, activeUniform) => {
177 | const name = activeUniform.uniformName;
178 |
179 | // get supplied uniform
180 | const uniform = this.uniforms[name];
181 | if (!uniform) {
182 | return console.warn(`Active uniform ${name} has not been supplied`);
183 | }
184 | if (uniform && uniform.value === undefined) {
185 | return console.warn(`${name} uniform is missing a value parameter`);
186 | }
187 |
188 | if (uniform.value.texture) {
189 | textureUnit++;
190 | this.gl.activeTexture(this.gl.TEXTURE0 + textureUnit);
191 |
192 | // bind texture and update any unset props
193 | uniform.value.update();
194 |
195 | // set uniform using texture unit instead of value
196 | return setUniform(this.gl, activeUniform.type, location, textureUnit);
197 | }
198 |
199 | if (uniform.value.length && uniform.value[0].texture) {
200 | const textureUnits = [];
201 | uniform.value.forEach(value => {
202 | textureUnits.push(++textureUnit);
203 | this.gl.activeTexture(this.gl.TEXTURE0 + textureUnit);
204 | value.update();
205 | });
206 |
207 | // For texture arrays, pass an array of texture units
208 | return setUniform(this.gl, activeUniform.type, location, textureUnits);
209 | }
210 |
211 | setUniform(this.gl, activeUniform.type, location, uniform.value);
212 | });
213 |
214 | this.applyState();
215 | if (flipFaces) this.gl.renderer.setFrontFace(this.frontFace === this.gl.CCW ? this.gl.CW : this.gl.CCW);
216 | }
217 |
218 | remove() {
219 | this.gl.deleteProgram(this.program);
220 | this.gl.deleteShader(this.vertexShader);
221 | this.gl.deleteShader(this.fragmentShader);
222 | }
223 | }
--------------------------------------------------------------------------------
/src/shaders/PBRShader.js:
--------------------------------------------------------------------------------
1 | const vertex = `#version 300 es
2 | precision highp float;
3 | precision highp int;
4 |
5 | in vec3 position;
6 | in vec2 uv;
7 | in vec3 normal;
8 |
9 | uniform mat3 normalMatrix;
10 | uniform mat4 modelMatrix;
11 | uniform mat4 modelViewMatrix;
12 | uniform mat4 projectionMatrix;
13 |
14 | out vec2 vUv;
15 | out vec3 vNormal;
16 | out vec3 vMPos;
17 |
18 | void main() {
19 | vUv = uv;
20 | vNormal = normalize(normalMatrix * normal);
21 | vec4 mPos = modelMatrix * vec4(position, 1.0);
22 | vMPos = mPos.xyz / mPos.w;
23 |
24 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
25 | }
26 | `;
27 |
28 | const fragment = `#version 300 es
29 | precision highp float;
30 | precision highp int;
31 |
32 | uniform vec3 cameraPosition;
33 | uniform mat4 viewMatrix;
34 |
35 | uniform vec3 uBaseColor;
36 |
37 | uniform sampler2D tRMO;
38 | uniform float uMetallic;
39 | uniform float uRoughness;
40 | uniform float uOcclusion;
41 |
42 | uniform sampler2D tNormal;
43 | uniform float uNormalScale;
44 | uniform float uNormalUVScale;
45 |
46 | uniform sampler2D tLUT;
47 | uniform sampler2D tEnvDiffuse;
48 | uniform sampler2D tEnvSpecular;
49 | uniform float uEnvSpecular;
50 |
51 | uniform float uInputType;
52 |
53 | uniform vec3 uLightDirection;
54 | uniform vec3 uLightColor;
55 |
56 | in vec2 vUv;
57 | in vec3 vNormal;
58 | in vec3 vMPos;
59 |
60 | out vec4 FragColor;
61 |
62 | const float PI = 3.14159265359;
63 | const float RECIPROCAL_PI = 0.31830988618;
64 | const float RECIPROCAL_PI2 = 0.15915494;
65 | const float LN2 = 0.6931472;
66 |
67 | const float ENV_LODS = 6.0;
68 |
69 | vec4 SRGBtoLinear(vec4 srgb) {
70 | vec3 linOut = pow(srgb.xyz, vec3(2.2));
71 | return vec4(linOut, srgb.w);
72 | }
73 |
74 | vec3 linearToSRGB(vec3 color) {
75 | return pow(color, vec3(1.0 / 2.2));
76 | }
77 |
78 | vec4 RGBEToLinear(vec4 value) {
79 | return vec4(value.rgb * exp2(value.a * 255.0 - 128.0), 1.0);
80 | }
81 |
82 | vec4 RGBMToLinear(vec4 value) {
83 | float maxRange = 6.0;
84 | return vec4(value.xyz * value.w * maxRange, 1.0);
85 | }
86 |
87 | vec4 RGBDToLinear(vec4 value) {
88 | float maxRange = 6.0;
89 | return vec4(value.rgb * ((maxRange / 255.0) / value.a), 1.0);
90 | }
91 |
92 | vec3 getNormal() {
93 | vec3 pos_dx = dFdx(vMPos.xyz);
94 | vec3 pos_dy = dFdy(vMPos.xyz);
95 | vec2 tex_dx = dFdx(vUv);
96 | vec2 tex_dy = dFdy(vUv);
97 |
98 | vec3 t = normalize(pos_dx * tex_dy.t - pos_dy * tex_dx.t);
99 | vec3 b = normalize(-pos_dx * tex_dy.s + pos_dy * tex_dx.s);
100 | mat3 tbn = mat3(t, b, normalize(vNormal));
101 |
102 | vec3 n = texture(tNormal, vUv * uNormalUVScale).rgb * 2.0 - 1.0;
103 | n.xy *= uNormalScale;
104 | vec3 normal = normalize(tbn * n);
105 |
106 | // Get world normal from view normal (normalMatrix * normal)
107 | return normalize((vec4(normal, 0.0) * viewMatrix).xyz);
108 | }
109 |
110 | vec3 specularReflection(vec3 specularEnvR0, vec3 specularEnvR90, float VdH) {
111 | return specularEnvR0 + (specularEnvR90 - specularEnvR0) * pow(clamp(1.0 - VdH, 0.0, 1.0), 5.0);
112 | }
113 |
114 | float geometricOcclusion(float NdL, float NdV, float roughness) {
115 | float r = roughness;
116 |
117 | float attenuationL = 2.0 * NdL / (NdL + sqrt(r * r + (1.0 - r * r) * (NdL * NdL)));
118 | float attenuationV = 2.0 * NdV / (NdV + sqrt(r * r + (1.0 - r * r) * (NdV * NdV)));
119 | return attenuationL * attenuationV;
120 | }
121 |
122 | float microfacetDistribution(float roughness, float NdH) {
123 | float roughnessSq = roughness * roughness;
124 | float f = (NdH * roughnessSq - NdH) * NdH + 1.0;
125 | return roughnessSq / (PI * f * f);
126 | }
127 |
128 | vec2 cartesianToPolar(vec3 n) {
129 | vec2 uv;
130 | uv.x = atan(n.z, n.x) * RECIPROCAL_PI2 + 0.5;
131 | uv.y = asin(n.y) * RECIPROCAL_PI + 0.5;
132 | return uv;
133 | }
134 |
135 | void getIBLContribution(inout vec3 diffuse, inout vec3 specular, float NdV, float roughness, vec3 n, vec3 reflection, vec3 diffuseColor, vec3 specularColor) {
136 | vec3 brdf = SRGBtoLinear(texture(tLUT, vec2(NdV, roughness))).rgb;
137 |
138 | // Sample 2 levels and mix between to get smoother degradation
139 | float blend = roughness * ENV_LODS;
140 | float level0 = floor(blend);
141 | float level1 = min(ENV_LODS, level0 + 1.0);
142 | blend -= level0;
143 |
144 | // Sample the specular env map atlas depending on the roughness value
145 | vec2 uvSpec = cartesianToPolar(reflection);
146 | uvSpec.y /= 2.0;
147 |
148 | vec2 uv0 = uvSpec;
149 | vec2 uv1 = uvSpec;
150 |
151 | uv0 /= pow(2.0, level0);
152 | uv0.y += 1.0 - exp(-LN2 * level0);
153 |
154 | uv1 /= pow(2.0, level1);
155 | uv1.y += 1.0 - exp(-LN2 * level1);
156 |
157 | vec3 diffuseLight;
158 | vec3 specular0;
159 | vec3 specular1;
160 |
161 | // 'If else' statements caused the strangest gpu bug
162 | // if (uInputType < 0.5) {
163 | //
164 | // // sRGB == 0
165 | // diffuseLight = SRGBToLinear(texture(tEnvDiffuse, cartesianToPolar(n))).rgb;
166 | // specular0 = SRGBToLinear(texture(tEnvSpecular, uv0)).rgb;
167 | // specular1 = SRGBToLinear(texture(tEnvSpecular, uv1)).rgb;
168 | // } else if (uInputType < 1.5) {
169 | //
170 | // // RGBE == 1
171 | // diffuseLight = RGBEToLinear(texture(tEnvDiffuse, cartesianToPolar(n))).rgb;
172 | // specular0 = RGBEToLinear(texture(tEnvSpecular, uv0)).rgb;
173 | // specular1 = RGBEToLinear(texture(tEnvSpecular, uv1)).rgb;
174 | // } else if (uInputType < 2.5) {
175 | //
176 | // // RGBM == 2
177 | // diffuseLight = RGBMToLinear(texture(tEnvDiffuse, cartesianToPolar(n))).rgb;
178 | // specular0 = RGBMToLinear(texture(tEnvSpecular, uv0)).rgb;
179 | // specular1 = RGBMToLinear(texture(tEnvSpecular, uv1)).rgb;
180 | // } else if (uInputType < 3.5) {
181 | //
182 | // // RGBD == 3
183 | // diffuseLight = RGBDToLinear(texture(tEnvDiffuse, cartesianToPolar(n))).rgb;
184 | // specular0 = RGBDToLinear(texture(tEnvSpecular, uv0)).rgb;
185 | // specular1 = RGBDToLinear(texture(tEnvSpecular, uv1)).rgb;
186 | // }
187 |
188 |
189 | // sRGB == 0
190 | diffuseLight = SRGBtoLinear(texture(tEnvDiffuse, cartesianToPolar(n))).rgb;
191 | specular0 = SRGBtoLinear(texture(tEnvSpecular, uv0)).rgb;
192 | specular1 = SRGBtoLinear(texture(tEnvSpecular, uv1)).rgb;
193 |
194 | // RGBE == 1
195 | float mixRGBE = clamp(1.0 - abs(uInputType - 1.0), 0.0, 1.0);
196 | diffuseLight = mix(diffuseLight, RGBEToLinear(texture(tEnvDiffuse, cartesianToPolar(n))).rgb, mixRGBE);
197 | specular0 = mix(specular0, RGBEToLinear(texture(tEnvSpecular, uv0)).rgb, mixRGBE);
198 | specular1 = mix(specular1, RGBEToLinear(texture(tEnvSpecular, uv1)).rgb, mixRGBE);
199 |
200 | // RGBM == 2
201 | float mixRGBM = clamp(1.0 - abs(uInputType - 2.0), 0.0, 1.0);
202 | diffuseLight = mix(diffuseLight, RGBMToLinear(texture(tEnvDiffuse, cartesianToPolar(n))).rgb, mixRGBM);
203 | specular0 = mix(specular0, RGBMToLinear(texture(tEnvSpecular, uv0)).rgb, mixRGBM);
204 | specular1 = mix(specular1, RGBMToLinear(texture(tEnvSpecular, uv1)).rgb, mixRGBM);
205 |
206 | // RGBD == 3
207 | float mixRGBD = clamp(1.0 - abs(uInputType - 3.0), 0.0, 1.0);
208 | diffuseLight = mix(diffuseLight, RGBDToLinear(texture(tEnvDiffuse, cartesianToPolar(n))).rgb, mixRGBD);
209 | specular0 = mix(specular0, RGBDToLinear(texture(tEnvSpecular, uv0)).rgb, mixRGBD);
210 | specular1 = mix(specular1, RGBDToLinear(texture(tEnvSpecular, uv1)).rgb, mixRGBD);
211 |
212 |
213 |
214 | vec3 specularLight = mix(specular0, specular1, blend);
215 |
216 | diffuse = diffuseLight * diffuseColor;
217 |
218 | // Bit of extra reflection for smooth materials
219 | float reflectivity = pow((1.0 - roughness), 2.0) * 0.05;
220 | specular = specularLight * (specularColor * brdf.x + brdf.y + reflectivity);
221 | specular *= uEnvSpecular;
222 | }
223 |
224 | void main() {
225 | vec3 baseColor = uBaseColor;
226 |
227 | // RMO map packed as rgb = [roughness, metallic, occlusion]
228 | vec4 rmaSample = texture(tRMO, vUv);
229 | float roughness = clamp(rmaSample.r * uRoughness, 0.04, 1.0);
230 | float metallic = clamp(rmaSample.g * uMetallic, 0.04, 1.0);
231 |
232 | vec3 f0 = vec3(0.04);
233 | vec3 diffuseColor = baseColor * (vec3(1.0) - f0) * (1.0 - metallic);
234 | vec3 specularColor = mix(f0, baseColor, metallic);
235 |
236 | vec3 specularEnvR0 = specularColor;
237 | vec3 specularEnvR90 = vec3(clamp(max(max(specularColor.r, specularColor.g), specularColor.b) * 25.0, 0.0, 1.0));
238 |
239 | vec3 N = getNormal();
240 | vec3 V = normalize(cameraPosition - vMPos);
241 | vec3 L = normalize(uLightDirection);
242 | vec3 H = normalize(L + V);
243 | vec3 reflection = normalize(reflect(-V, N));
244 |
245 | float NdL = clamp(dot(N, L), 0.001, 1.0);
246 | float NdV = clamp(abs(dot(N, V)), 0.001, 1.0);
247 | float NdH = clamp(dot(N, H), 0.0, 1.0);
248 | float LdH = clamp(dot(L, H), 0.0, 1.0);
249 | float VdH = clamp(dot(V, H), 0.0, 1.0);
250 |
251 | vec3 F = specularReflection(specularEnvR0, specularEnvR90, VdH);
252 | float G = geometricOcclusion(NdL, NdV, roughness);
253 | float D = microfacetDistribution(roughness, NdH);
254 |
255 | vec3 diffuseContrib = (1.0 - F) * (diffuseColor / PI);
256 | vec3 specContrib = F * G * D / (4.0 * NdL * NdV);
257 |
258 | // Shading based off lights
259 | vec3 color = NdL * uLightColor * (diffuseContrib + specContrib);
260 |
261 | // Calculate IBL lighting
262 | vec3 diffuseIBL;
263 | vec3 specularIBL;
264 | getIBLContribution(diffuseIBL, specularIBL, NdV, roughness, N, reflection, diffuseColor, specularColor);
265 |
266 | // Add IBL on top of color
267 | color += diffuseIBL + specularIBL;
268 |
269 | // Multiply occlusion
270 | color = mix(color, color * rmaSample.b, uOcclusion);
271 |
272 | // Convert to sRGB to display
273 | FragColor.rgb = linearToSRGB(color);
274 | FragColor.a = 1.0;
275 | }
276 | `;
277 |
278 | export default {vertex, fragment};
--------------------------------------------------------------------------------
/ogl/core/Renderer.js:
--------------------------------------------------------------------------------
1 | import {Mat4} from '../math/Mat4.js';
2 | import {Vec3} from '../math/Vec3.js';
3 |
4 | // TODO: frustum culling
5 | // TODO: gl.canvas.addEventListener('webglcontextlost', contextLost, false);
6 | // TODO: gl.canvas.addEventListener('webglcontextrestored', contextRestore, false);
7 |
8 | // Not automatic - devs to use these methods manually
9 | // gl.colorMask( colorMask, colorMask, colorMask, colorMask );
10 | // gl.clearColor( r, g, b, a );
11 | // gl.stencilMask( stencilMask );
12 | // gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
13 | // gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
14 | // gl.clearStencil( stencil );
15 |
16 | const projMat4 = new Mat4();
17 | const tempVec3 = new Vec3();
18 |
19 | export class Renderer {
20 | constructor({
21 | canvas = document.createElement('canvas'),
22 | width = 300,
23 | height = 150,
24 | dpr = 1,
25 | alpha = false,
26 | depth = true,
27 | stencil = false,
28 | antialias = false,
29 | premultipliedAlpha = false,
30 | preserveDrawingBuffer = false,
31 | powerPreference = 'default',
32 | autoClear = true,
33 | sort = true,
34 | } = {}) {
35 | const attributes = {alpha, depth, stencil, antialias, premultipliedAlpha, preserveDrawingBuffer, powerPreference};
36 | this.dpr = dpr;
37 | this.alpha = alpha;
38 | this.color = true;
39 | this.depth = depth;
40 | this.stencil = stencil;
41 | this.premultipliedAlpha = premultipliedAlpha;
42 | this.autoClear = autoClear;
43 | this.sort = sort;
44 |
45 | // Attempt WebGL2, otherwise fallback to WebGL1
46 | this.gl = canvas.getContext('webgl2', attributes);
47 | this.isWebgl2 = !!this.gl;
48 | if (!this.gl) {
49 | this.gl = canvas.getContext('webgl', attributes) || canvas.getContext('experimental-webgl', attributes);
50 | }
51 |
52 | // Attach renderer to gl so that all classes have access to internal state functions
53 | this.gl.renderer = this;
54 |
55 | // initialise size values
56 | this.setSize(width, height);
57 |
58 | // gl state stores to avoid redundant calls on methods used internally
59 | this.state = {};
60 | this.state.blendFunc = {src: this.gl.ONE, dst: this.gl.ZERO};
61 | this.state.blendEquation = {modeRGB: this.gl.FUNC_ADD};
62 | this.state.cullFace = null;
63 | this.state.frontFace = this.gl.CCW;
64 | this.state.depthMask = true;
65 | this.state.depthFunc = this.gl.LESS;
66 | this.state.framebuffer = null;
67 | this.state.viewport = {width: null, height: null};
68 |
69 | // store requested extensions
70 | this.extensions = {};
71 |
72 | if (!this.isWebgl2) {
73 |
74 | // Initialise extra format types
75 | this.getExtension('OES_texture_float');
76 | this.getExtension('OES_texture_float_linear');
77 | this.getExtension('OES_texture_half_float');
78 | this.getExtension('OES_element_index_uint');
79 | this.getExtension('OES_standard_derivatives');
80 | this.getExtension('EXT_sRGB');
81 | this.getExtension('WEBGL_depth_texture');
82 | }
83 |
84 | // Create method aliases using extension or native if available
85 | this.vertexAttribDivisor = this.gl.renderer.getExtension('ANGLE_instanced_arrays', 'vertexAttribDivisor', 'vertexAttribDivisorANGLE');
86 | this.drawArraysInstanced = this.gl.renderer.getExtension('ANGLE_instanced_arrays', 'drawArraysInstanced', 'drawArraysInstancedANGLE');
87 | this.drawElementsInstanced = this.gl.renderer.getExtension('ANGLE_instanced_arrays', 'drawElementsInstanced', 'drawElementsInstancedANGLE');
88 | this.createVertexArray = this.gl.renderer.getExtension('OES_vertex_array_object', 'createVertexArray', 'createVertexArrayOES');
89 | this.bindVertexArray = this.gl.renderer.getExtension('OES_vertex_array_object', 'bindVertexArray', 'bindVertexArrayOES');
90 | this.deleteVertexArray = this.gl.renderer.getExtension('OES_vertex_array_object', 'deleteVertexArray', 'deleteVertexArrayOES');
91 | }
92 |
93 | setSize(width, height) {
94 | this.width = width;
95 | this.height = height;
96 |
97 | this.gl.canvas.width = width * this.dpr;
98 | this.gl.canvas.height = height * this.dpr;
99 |
100 | Object.assign(this.gl.canvas.style, {
101 | width: width + 'px',
102 | height: height + 'px',
103 | });
104 | }
105 |
106 | setViewport(width, height) {
107 | if (this.state.viewport.width === width && this.state.viewport.height === height) return;
108 | this.state.viewport.width = width;
109 | this.state.viewport.height = height;
110 | this.gl.viewport(0, 0, width, height);
111 | }
112 |
113 | enable(id) {
114 | if (this.state[id] === true) return;
115 | this.gl.enable(id);
116 | this.state[id] = true;
117 | }
118 |
119 | disable(id) {
120 | if (this.state[id] === false) return;
121 | this.gl.disable(id);
122 | this.state[id] = false;
123 | }
124 |
125 | setBlendFunc(src, dst, srcAlpha, dstAlpha) {
126 | if (this.state.blendFunc.src === src &&
127 | this.state.blendFunc.dst === dst &&
128 | this.state.blendFunc.srcAlpha === srcAlpha &&
129 | this.state.blendFunc.dstAlpha === dstAlpha) return;
130 | this.state.blendFunc.src = src;
131 | this.state.blendFunc.dst = dst;
132 | this.state.blendFunc.srcAlpha = srcAlpha;
133 | this.state.blendFunc.dstAlpha = dstAlpha;
134 | if (srcAlpha !== undefined) this.gl.blendFuncSeparate(src, dst, srcAlpha, dstAlpha);
135 | else this.gl.blendFunc(src, dst);
136 | }
137 |
138 | setBlendEquation(modeRGB, modeAlpha) {
139 | if (this.state.blendEquation.modeRGB === modeRGB &&
140 | this.state.blendEquation.modeAlpha === modeAlpha) return;
141 | this.state.blendEquation.modeRGB = modeRGB;
142 | this.state.blendEquation.modeAlpha = modeAlpha;
143 | if (modeAlpha !== undefined) this.gl.blendEquationSeparate(modeRGB, modeAlpha);
144 | else this.gl.blendEquation(modeRGB);
145 | }
146 |
147 | setCullFace(value) {
148 | if (this.state.cullFace === value) return;
149 | this.state.cullFace = value;
150 | this.gl.cullFace(value);
151 | }
152 |
153 | setFrontFace(value) {
154 | if (this.state.frontFace === value) return;
155 | this.state.frontFace = value;
156 | this.gl.frontFace(value);
157 | }
158 |
159 | setDepthMask(value) {
160 | if (this.state.depthMask === value) return;
161 | this.state.depthMask = value;
162 | this.gl.depthMask(value);
163 | }
164 |
165 | setDepthFunc(value) {
166 | if (this.state.depthFunc === value) return;
167 | this.state.depthFunc = value;
168 | this.gl.depthFunc(value);
169 | }
170 |
171 | bindFramebuffer({target = this.gl.FRAMEBUFFER, buffer = null} = {}) {
172 | if (this.state.framebuffer === buffer) return;
173 | this.state.framebuffer = buffer;
174 | this.gl.bindFramebuffer(target, buffer);
175 | }
176 |
177 | getExtension(extension, webgl2Func, extFunc) {
178 |
179 | // if webgl2 function supported, return func bound to gl context
180 | if (webgl2Func && this.gl[webgl2Func]) return this.gl[webgl2Func].bind(this.gl);
181 |
182 | // fetch extension once only
183 | if (!this.extensions[extension]) {
184 | this.extensions[extension] = this.gl.getExtension(extension);
185 | }
186 |
187 | // return extension if no function requested
188 | if (!webgl2Func) return this.extensions[extension];
189 |
190 | // return extension function, bound to extension
191 | return this.extensions[extension][extFunc].bind(this.extensions[extension]);
192 | }
193 |
194 | drawOpaque(scene, camera) {
195 | const opaque = [];
196 | scene.traverse(node => {
197 | if (!node.program || node.program.transparent) return;
198 | opaque.splice(getRenderOrder(node), 0, node);
199 | });
200 |
201 | function getRenderOrder(node) {
202 | node.worldMatrix.getTranslation(tempVec3);
203 | tempVec3.applyMatrix4(projMat4);
204 |
205 | node.zDepth = tempVec3.z;
206 | for (let i = 0; i < opaque.length; i++) {
207 | if (node.zDepth <= opaque[i].zDepth) return i;
208 | }
209 | return opaque.length;
210 | }
211 |
212 | for (let i = 0; i < opaque.length; i++) {
213 | opaque[i].draw({camera});
214 | }
215 | }
216 |
217 | drawTransparent(scene, camera) {
218 | const transparent = [];
219 | const custom = [];
220 | scene.traverse(node => {
221 | if (!node.program || !node.program.transparent) return;
222 |
223 | // If manual order set, add to queue last
224 | if (node.renderOrder !== undefined) return custom.push(node);
225 | transparent.splice(getRenderOrder(node), 0, node);
226 | });
227 |
228 | function getRenderOrder(node) {
229 | node.worldMatrix.getTranslation(tempVec3);
230 | tempVec3.applyMatrix4(projMat4);
231 |
232 | node.zDepth = tempVec3.z;
233 | for (let i = 0; i < transparent.length; i++) {
234 | if (node.zDepth >= transparent[i].zDepth) return i;
235 | }
236 | return transparent.length;
237 | }
238 |
239 | // Add manually ordered nodes
240 | for (let i = 0; i < custom.length; i++) {
241 | transparent.splice(custom[i].renderOrder, 0, custom[i]);
242 | }
243 |
244 | for (let i = 0; i < transparent.length; i++) {
245 | transparent[i].draw({camera});
246 | }
247 | }
248 |
249 | render({
250 | scene,
251 | camera,
252 | target = null,
253 | update = true,
254 | }) {
255 |
256 | if (target === null) {
257 |
258 | // make sure no render target bound so draws to canvas
259 | this.bindFramebuffer();
260 | this.setViewport(this.width * this.dpr, this.height * this.dpr);
261 | } else {
262 |
263 | // bind supplied render target and update viewport
264 | this.bindFramebuffer(target);
265 | this.setViewport(target.width, target.height);
266 | }
267 |
268 | if (this.autoClear) {
269 |
270 | // Ensure depth buffer writing is enabled so it can be cleared
271 | if (this.depth) {
272 | this.enable(this.gl.DEPTH_TEST);
273 | this.setDepthMask(true);
274 | }
275 | this.gl.clear((this.color ? this.gl.COLOR_BUFFER_BIT : 0) | (this.depth ? this.gl.DEPTH_BUFFER_BIT : 0) | (this.stencil ? this.gl.STENCIL_BUFFER_BIT : 0));
276 | }
277 |
278 | // updates all scene graph matrices
279 | if (update) scene.updateMatrixWorld();
280 |
281 | // Update camera separately if not in scene graph
282 | if (camera && camera.parent === null) camera.updateMatrixWorld();
283 |
284 | // can only sort if camera available
285 | if (this.sort && camera) {
286 | projMat4.multiply(camera.projectionMatrix, camera.viewMatrix);
287 | this.drawOpaque(scene, camera);
288 | this.drawTransparent(scene, camera);
289 | } else {
290 | scene.traverse(node => {
291 | if (!node.draw) return;
292 | node.draw({camera});
293 | });
294 | }
295 |
296 | // culling
297 | // _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
298 | // _frustum.setFromMatrix( _projScreenMatrix );
299 | }
300 | }
--------------------------------------------------------------------------------
/ogl/math/functions/Vec2Func.js:
--------------------------------------------------------------------------------
1 | const EPSILON = 0.000001;
2 |
3 | /**
4 | * Copy the values from one vec2 to another
5 | *
6 | * @param {vec2} out the receiving vector
7 | * @param {vec2} a the source vector
8 | * @returns {vec2} out
9 | */
10 | export function copy(out, a) {
11 | out[0] = a[0];
12 | out[1] = a[1];
13 | return out;
14 | }
15 |
16 | /**
17 | * Set the components of a vec2 to the given values
18 | *
19 | * @param {vec2} out the receiving vector
20 | * @param {Number} x X component
21 | * @param {Number} y Y component
22 | * @returns {vec2} out
23 | */
24 | export function set(out, x, y) {
25 | out[0] = x;
26 | out[1] = y;
27 | return out;
28 | }
29 |
30 | /**
31 | * Adds two vec2's
32 | *
33 | * @param {vec2} out the receiving vector
34 | * @param {vec2} a the first operand
35 | * @param {vec2} b the second operand
36 | * @returns {vec2} out
37 | */
38 | export function add(out, a, b) {
39 | out[0] = a[0] + b[0];
40 | out[1] = a[1] + b[1];
41 | return out;
42 | }
43 |
44 | /**
45 | * Subtracts vector b from vector a
46 | *
47 | * @param {vec2} out the receiving vector
48 | * @param {vec2} a the first operand
49 | * @param {vec2} b the second operand
50 | * @returns {vec2} out
51 | */
52 | export function subtract(out, a, b) {
53 | out[0] = a[0] - b[0];
54 | out[1] = a[1] - b[1];
55 | return out;
56 | }
57 |
58 | /**
59 | * Multiplies two vec2's
60 | *
61 | * @param {vec2} out the receiving vector
62 | * @param {vec2} a the first operand
63 | * @param {vec2} b the second operand
64 | * @returns {vec2} out
65 | */
66 | export function multiply(out, a, b) {
67 | out[0] = a[0] * b[0];
68 | out[1] = a[1] * b[1];
69 | return out;
70 | };
71 |
72 | /**
73 | * Divides two vec2's
74 | *
75 | * @param {vec2} out the receiving vector
76 | * @param {vec2} a the first operand
77 | * @param {vec2} b the second operand
78 | * @returns {vec2} out
79 | */
80 | export function divide(out, a, b) {
81 | out[0] = a[0] / b[0];
82 | out[1] = a[1] / b[1];
83 | return out;
84 | };
85 |
86 | /**
87 | * Math.ceil the components of a vec2
88 | *
89 | * @param {vec2} out the receiving vector
90 | * @param {vec2} a vector to ceil
91 | * @returns {vec2} out
92 | */
93 | export function ceil(out, a) {
94 | out[0] = Math.ceil(a[0]);
95 | out[1] = Math.ceil(a[1]);
96 | return out;
97 | };
98 |
99 | /**
100 | * Math.floor the components of a vec2
101 | *
102 | * @param {vec2} out the receiving vector
103 | * @param {vec2} a vector to floor
104 | * @returns {vec2} out
105 | */
106 | export function floor(out, a) {
107 | out[0] = Math.floor(a[0]);
108 | out[1] = Math.floor(a[1]);
109 | return out;
110 | };
111 |
112 | /**
113 | * Returns the minimum of two vec2's
114 | *
115 | * @param {vec2} out the receiving vector
116 | * @param {vec2} a the first operand
117 | * @param {vec2} b the second operand
118 | * @returns {vec2} out
119 | */
120 | export function min(out, a, b) {
121 | out[0] = Math.min(a[0], b[0]);
122 | out[1] = Math.min(a[1], b[1]);
123 | return out;
124 | };
125 |
126 | /**
127 | * Returns the maximum of two vec2's
128 | *
129 | * @param {vec2} out the receiving vector
130 | * @param {vec2} a the first operand
131 | * @param {vec2} b the second operand
132 | * @returns {vec2} out
133 | */
134 | export function max(out, a, b) {
135 | out[0] = Math.max(a[0], b[0]);
136 | out[1] = Math.max(a[1], b[1]);
137 | return out;
138 | };
139 |
140 | /**
141 | * Math.round the components of a vec2
142 | *
143 | * @param {vec2} out the receiving vector
144 | * @param {vec2} a vector to round
145 | * @returns {vec2} out
146 | */
147 | export function round(out, a) {
148 | out[0] = Math.round(a[0]);
149 | out[1] = Math.round(a[1]);
150 | return out;
151 | };
152 |
153 | /**
154 | * Scales a vec2 by a scalar number
155 | *
156 | * @param {vec2} out the receiving vector
157 | * @param {vec2} a the vector to scale
158 | * @param {Number} b amount to scale the vector by
159 | * @returns {vec2} out
160 | */
161 | export function scale(out, a, b) {
162 | out[0] = a[0] * b;
163 | out[1] = a[1] * b;
164 | return out;
165 | };
166 |
167 | /**
168 | * Adds two vec2's after scaling the second operand by a scalar value
169 | *
170 | * @param {vec2} out the receiving vector
171 | * @param {vec2} a the first operand
172 | * @param {vec2} b the second operand
173 | * @param {Number} scale the amount to scale b by before adding
174 | * @returns {vec2} out
175 | */
176 | export function scaleAndAdd(out, a, b, scale) {
177 | out[0] = a[0] + (b[0] * scale);
178 | out[1] = a[1] + (b[1] * scale);
179 | return out;
180 | };
181 |
182 | /**
183 | * Calculates the euclidian distance between two vec2's
184 | *
185 | * @param {vec2} a the first operand
186 | * @param {vec2} b the second operand
187 | * @returns {Number} distance between a and b
188 | */
189 | export function distance(a, b) {
190 | var x = b[0] - a[0],
191 | y = b[1] - a[1];
192 | return Math.sqrt(x * x + y * y);
193 | };
194 |
195 | /**
196 | * Calculates the squared euclidian distance between two vec2's
197 | *
198 | * @param {vec2} a the first operand
199 | * @param {vec2} b the second operand
200 | * @returns {Number} squared distance between a and b
201 | */
202 | export function squaredDistance(a, b) {
203 | var x = b[0] - a[0],
204 | y = b[1] - a[1];
205 | return x * x + y * y;
206 | };
207 |
208 | /**
209 | * Calculates the length of a vec2
210 | *
211 | * @param {vec2} a vector to calculate length of
212 | * @returns {Number} length of a
213 | */
214 | export function length(a) {
215 | var x = a[0],
216 | y = a[1];
217 | return Math.sqrt(x * x + y * y);
218 | };
219 |
220 | /**
221 | * Calculates the squared length of a vec2
222 | *
223 | * @param {vec2} a vector to calculate squared length of
224 | * @returns {Number} squared length of a
225 | */
226 | export function squaredLength(a) {
227 | var x = a[0],
228 | y = a[1];
229 | return x * x + y * y;
230 | };
231 |
232 | /**
233 | * Negates the components of a vec2
234 | *
235 | * @param {vec2} out the receiving vector
236 | * @param {vec2} a vector to negate
237 | * @returns {vec2} out
238 | */
239 | export function negate(out, a) {
240 | out[0] = -a[0];
241 | out[1] = -a[1];
242 | return out;
243 | };
244 |
245 | /**
246 | * Returns the inverse of the components of a vec2
247 | *
248 | * @param {vec2} out the receiving vector
249 | * @param {vec2} a vector to invert
250 | * @returns {vec2} out
251 | */
252 | export function inverse(out, a) {
253 | out[0] = 1.0 / a[0];
254 | out[1] = 1.0 / a[1];
255 | return out;
256 | };
257 |
258 | /**
259 | * Normalize a vec2
260 | *
261 | * @param {vec2} out the receiving vector
262 | * @param {vec2} a vector to normalize
263 | * @returns {vec2} out
264 | */
265 | export function normalize(out, a) {
266 | var x = a[0],
267 | y = a[1];
268 | var len = x * x + y * y;
269 | if (len > 0) {
270 | //TODO: evaluate use of glm_invsqrt here?
271 | len = 1 / Math.sqrt(len);
272 | out[0] = a[0] * len;
273 | out[1] = a[1] * len;
274 | }
275 | return out;
276 | };
277 |
278 | /**
279 | * Calculates the dot product of two vec2's
280 | *
281 | * @param {vec2} a the first operand
282 | * @param {vec2} b the second operand
283 | * @returns {Number} dot product of a and b
284 | */
285 | export function dot(a, b) {
286 | return a[0] * b[0] + a[1] * b[1];
287 | };
288 |
289 | /**
290 | * Computes the cross product of two vec2's
291 | * Note that the cross product must by definition produce a 3D vector
292 | *
293 | * @param {vec3} out the receiving vector
294 | * @param {vec2} a the first operand
295 | * @param {vec2} b the second operand
296 | * @returns {vec3} out
297 | */
298 | export function cross(out, a, b) {
299 | var z = a[0] * b[1] - a[1] * b[0];
300 | out[0] = out[1] = 0;
301 | out[2] = z;
302 | return out;
303 | };
304 |
305 | /**
306 | * Performs a linear interpolation between two vec2's
307 | *
308 | * @param {vec2} out the receiving vector
309 | * @param {vec2} a the first operand
310 | * @param {vec2} b the second operand
311 | * @param {Number} t interpolation amount between the two inputs
312 | * @returns {vec2} out
313 | */
314 | export function lerp(out, a, b, t) {
315 | var ax = a[0],
316 | ay = a[1];
317 | out[0] = ax + t * (b[0] - ax);
318 | out[1] = ay + t * (b[1] - ay);
319 | return out;
320 | };
321 |
322 | /**
323 | * Generates a random vector with the given scale
324 | *
325 | * @param {vec2} out the receiving vector
326 | * @param {Number} [scale] Length of the resulting vector. If ommitted, a unit vector will be returned
327 | * @returns {vec2} out
328 | */
329 | export function random(out, scale) {
330 | scale = scale || 1.0;
331 | var r = Math.random() * 2.0 * Math.PI;
332 | out[0] = Math.cos(r) * scale;
333 | out[1] = Math.sin(r) * scale;
334 | return out;
335 | };
336 |
337 | /**
338 | * Transforms the vec2 with a mat2
339 | *
340 | * @param {vec2} out the receiving vector
341 | * @param {vec2} a the vector to transform
342 | * @param {mat2} m matrix to transform with
343 | * @returns {vec2} out
344 | */
345 | export function transformMat2(out, a, m) {
346 | var x = a[0],
347 | y = a[1];
348 | out[0] = m[0] * x + m[2] * y;
349 | out[1] = m[1] * x + m[3] * y;
350 | return out;
351 | };
352 |
353 | /**
354 | * Transforms the vec2 with a mat2d
355 | *
356 | * @param {vec2} out the receiving vector
357 | * @param {vec2} a the vector to transform
358 | * @param {mat2d} m matrix to transform with
359 | * @returns {vec2} out
360 | */
361 | export function transformMat2d(out, a, m) {
362 | var x = a[0],
363 | y = a[1];
364 | out[0] = m[0] * x + m[2] * y + m[4];
365 | out[1] = m[1] * x + m[3] * y + m[5];
366 | return out;
367 | };
368 |
369 | /**
370 | * Transforms the vec2 with a mat3
371 | * 3rd vector component is implicitly '1'
372 | *
373 | * @param {vec2} out the receiving vector
374 | * @param {vec2} a the vector to transform
375 | * @param {mat3} m matrix to transform with
376 | * @returns {vec2} out
377 | */
378 | export function transformMat3(out, a, m) {
379 | var x = a[0],
380 | y = a[1];
381 | out[0] = m[0] * x + m[3] * y + m[6];
382 | out[1] = m[1] * x + m[4] * y + m[7];
383 | return out;
384 | };
385 |
386 | /**
387 | * Transforms the vec2 with a mat4
388 | * 3rd vector component is implicitly '0'
389 | * 4th vector component is implicitly '1'
390 | *
391 | * @param {vec2} out the receiving vector
392 | * @param {vec2} a the vector to transform
393 | * @param {mat4} m matrix to transform with
394 | * @returns {vec2} out
395 | */
396 | export function transformMat4(out, a, m) {
397 | let x = a[0];
398 | let y = a[1];
399 | out[0] = m[0] * x + m[4] * y + m[12];
400 | out[1] = m[1] * x + m[5] * y + m[13];
401 | return out;
402 | }
403 |
404 | /**
405 | * Returns a string representation of a vector
406 | *
407 | * @param {vec2} a vector to represent as a string
408 | * @returns {String} string representation of the vector
409 | */
410 | export function str(a) {
411 | return 'vec2(' + a[0] + ', ' + a[1] + ')';
412 | }
413 |
414 | /**
415 | * Returns whether or not the vectors exactly have the same elements in the same position (when compared with ===)
416 | *
417 | * @param {vec2} a The first vector.
418 | * @param {vec2} b The second vector.
419 | * @returns {Boolean} True if the vectors are equal, false otherwise.
420 | */
421 | export function exactEquals(a, b) {
422 | return a[0] === b[0] && a[1] === b[1];
423 | }
424 |
425 | /**
426 | * Returns whether or not the vectors have approximately the same elements in the same position.
427 | *
428 | * @param {vec2} a The first vector.
429 | * @param {vec2} b The second vector.
430 | * @returns {Boolean} True if the vectors are equal, false otherwise.
431 | */
432 | export function equals(a, b) {
433 | let a0 = a[0], a1 = a[1];
434 | let b0 = b[0], b1 = b[1];
435 | return (Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
436 | Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)));
437 | }
--------------------------------------------------------------------------------
/ogl/math/functions/Vec4Func.js:
--------------------------------------------------------------------------------
1 | const EPSILON = 0.000001;
2 |
3 | /**
4 | * Copy the values from one vec4 to another
5 | *
6 | * @param {vec4} out the receiving vector
7 | * @param {vec4} a the source vector
8 | * @returns {vec4} out
9 | */
10 | export function copy(out, a) {
11 | out[0] = a[0];
12 | out[1] = a[1];
13 | out[2] = a[2];
14 | out[3] = a[3];
15 | return out;
16 | }
17 |
18 | /**
19 | * Set the components of a vec4 to the given values
20 | *
21 | * @param {vec4} out the receiving vector
22 | * @param {Number} x X component
23 | * @param {Number} y Y component
24 | * @param {Number} z Z component
25 | * @param {Number} w W component
26 | * @returns {vec4} out
27 | */
28 | export function set(out, x, y, z, w) {
29 | out[0] = x;
30 | out[1] = y;
31 | out[2] = z;
32 | out[3] = w;
33 | return out;
34 | }
35 |
36 | /**
37 | * Adds two vec4's
38 | *
39 | * @param {vec4} out the receiving vector
40 | * @param {vec4} a the first operand
41 | * @param {vec4} b the second operand
42 | * @returns {vec4} out
43 | */
44 | export function add(out, a, b) {
45 | out[0] = a[0] + b[0];
46 | out[1] = a[1] + b[1];
47 | out[2] = a[2] + b[2];
48 | out[3] = a[3] + b[3];
49 | return out;
50 | }
51 |
52 | /**
53 | * Subtracts vector b from vector a
54 | *
55 | * @param {vec4} out the receiving vector
56 | * @param {vec4} a the first operand
57 | * @param {vec4} b the second operand
58 | * @returns {vec4} out
59 | */
60 | export function subtract(out, a, b) {
61 | out[0] = a[0] - b[0];
62 | out[1] = a[1] - b[1];
63 | out[2] = a[2] - b[2];
64 | out[3] = a[3] - b[3];
65 | return out;
66 | }
67 |
68 | /**
69 | * Multiplies two vec4's
70 | *
71 | * @param {vec4} out the receiving vector
72 | * @param {vec4} a the first operand
73 | * @param {vec4} b the second operand
74 | * @returns {vec4} out
75 | */
76 | export function multiply(out, a, b) {
77 | out[0] = a[0] * b[0];
78 | out[1] = a[1] * b[1];
79 | out[2] = a[2] * b[2];
80 | out[3] = a[3] * b[3];
81 | return out;
82 | }
83 |
84 | /**
85 | * Divides two vec4's
86 | *
87 | * @param {vec4} out the receiving vector
88 | * @param {vec4} a the first operand
89 | * @param {vec4} b the second operand
90 | * @returns {vec4} out
91 | */
92 | export function divide(out, a, b) {
93 | out[0] = a[0] / b[0];
94 | out[1] = a[1] / b[1];
95 | out[2] = a[2] / b[2];
96 | out[3] = a[3] / b[3];
97 | return out;
98 | }
99 |
100 | /**
101 | * Math.ceil the components of a vec4
102 | *
103 | * @param {vec4} out the receiving vector
104 | * @param {vec4} a vector to ceil
105 | * @returns {vec4} out
106 | */
107 | export function ceil(out, a) {
108 | out[0] = Math.ceil(a[0]);
109 | out[1] = Math.ceil(a[1]);
110 | out[2] = Math.ceil(a[2]);
111 | out[3] = Math.ceil(a[3]);
112 | return out;
113 | }
114 |
115 | /**
116 | * Math.floor the components of a vec4
117 | *
118 | * @param {vec4} out the receiving vector
119 | * @param {vec4} a vector to floor
120 | * @returns {vec4} out
121 | */
122 | export function floor(out, a) {
123 | out[0] = Math.floor(a[0]);
124 | out[1] = Math.floor(a[1]);
125 | out[2] = Math.floor(a[2]);
126 | out[3] = Math.floor(a[3]);
127 | return out;
128 | }
129 |
130 | /**
131 | * Returns the minimum of two vec4's
132 | *
133 | * @param {vec4} out the receiving vector
134 | * @param {vec4} a the first operand
135 | * @param {vec4} b the second operand
136 | * @returns {vec4} out
137 | */
138 | export function min(out, a, b) {
139 | out[0] = Math.min(a[0], b[0]);
140 | out[1] = Math.min(a[1], b[1]);
141 | out[2] = Math.min(a[2], b[2]);
142 | out[3] = Math.min(a[3], b[3]);
143 | return out;
144 | }
145 |
146 | /**
147 | * Returns the maximum of two vec4's
148 | *
149 | * @param {vec4} out the receiving vector
150 | * @param {vec4} a the first operand
151 | * @param {vec4} b the second operand
152 | * @returns {vec4} out
153 | */
154 | export function max(out, a, b) {
155 | out[0] = Math.max(a[0], b[0]);
156 | out[1] = Math.max(a[1], b[1]);
157 | out[2] = Math.max(a[2], b[2]);
158 | out[3] = Math.max(a[3], b[3]);
159 | return out;
160 | }
161 |
162 | /**
163 | * Math.round the components of a vec4
164 | *
165 | * @param {vec4} out the receiving vector
166 | * @param {vec4} a vector to round
167 | * @returns {vec4} out
168 | */
169 | export function round(out, a) {
170 | out[0] = Math.round(a[0]);
171 | out[1] = Math.round(a[1]);
172 | out[2] = Math.round(a[2]);
173 | out[3] = Math.round(a[3]);
174 | return out;
175 | }
176 |
177 | /**
178 | * Scales a vec4 by a scalar number
179 | *
180 | * @param {vec4} out the receiving vector
181 | * @param {vec4} a the vector to scale
182 | * @param {Number} b amount to scale the vector by
183 | * @returns {vec4} out
184 | */
185 | export function scale(out, a, b) {
186 | out[0] = a[0] * b;
187 | out[1] = a[1] * b;
188 | out[2] = a[2] * b;
189 | out[3] = a[3] * b;
190 | return out;
191 | }
192 |
193 | /**
194 | * Adds two vec4's after scaling the second operand by a scalar value
195 | *
196 | * @param {vec4} out the receiving vector
197 | * @param {vec4} a the first operand
198 | * @param {vec4} b the second operand
199 | * @param {Number} scale the amount to scale b by before adding
200 | * @returns {vec4} out
201 | */
202 | export function scaleAndAdd(out, a, b, scale) {
203 | out[0] = a[0] + (b[0] * scale);
204 | out[1] = a[1] + (b[1] * scale);
205 | out[2] = a[2] + (b[2] * scale);
206 | out[3] = a[3] + (b[3] * scale);
207 | return out;
208 | }
209 |
210 | /**
211 | * Calculates the euclidian distance between two vec4's
212 | *
213 | * @param {vec4} a the first operand
214 | * @param {vec4} b the second operand
215 | * @returns {Number} distance between a and b
216 | */
217 | export function distance(a, b) {
218 | let x = b[0] - a[0];
219 | let y = b[1] - a[1];
220 | let z = b[2] - a[2];
221 | let w = b[3] - a[3];
222 | return Math.sqrt(x * x + y * y + z * z + w * w);
223 | }
224 |
225 | /**
226 | * Calculates the squared euclidian distance between two vec4's
227 | *
228 | * @param {vec4} a the first operand
229 | * @param {vec4} b the second operand
230 | * @returns {Number} squared distance between a and b
231 | */
232 | export function squaredDistance(a, b) {
233 | let x = b[0] - a[0];
234 | let y = b[1] - a[1];
235 | let z = b[2] - a[2];
236 | let w = b[3] - a[3];
237 | return x * x + y * y + z * z + w * w;
238 | }
239 |
240 | /**
241 | * Calculates the length of a vec4
242 | *
243 | * @param {vec4} a vector to calculate length of
244 | * @returns {Number} length of a
245 | */
246 | export function length(a) {
247 | let x = a[0];
248 | let y = a[1];
249 | let z = a[2];
250 | let w = a[3];
251 | return Math.sqrt(x * x + y * y + z * z + w * w);
252 | }
253 |
254 | /**
255 | * Calculates the squared length of a vec4
256 | *
257 | * @param {vec4} a vector to calculate squared length of
258 | * @returns {Number} squared length of a
259 | */
260 | export function squaredLength(a) {
261 | let x = a[0];
262 | let y = a[1];
263 | let z = a[2];
264 | let w = a[3];
265 | return x * x + y * y + z * z + w * w;
266 | }
267 |
268 | /**
269 | * Negates the components of a vec4
270 | *
271 | * @param {vec4} out the receiving vector
272 | * @param {vec4} a vector to negate
273 | * @returns {vec4} out
274 | */
275 | export function negate(out, a) {
276 | out[0] = -a[0];
277 | out[1] = -a[1];
278 | out[2] = -a[2];
279 | out[3] = -a[3];
280 | return out;
281 | }
282 |
283 | /**
284 | * Returns the inverse of the components of a vec4
285 | *
286 | * @param {vec4} out the receiving vector
287 | * @param {vec4} a vector to invert
288 | * @returns {vec4} out
289 | */
290 | export function inverse(out, a) {
291 | out[0] = 1.0 / a[0];
292 | out[1] = 1.0 / a[1];
293 | out[2] = 1.0 / a[2];
294 | out[3] = 1.0 / a[3];
295 | return out;
296 | }
297 |
298 | /**
299 | * Normalize a vec4
300 | *
301 | * @param {vec4} out the receiving vector
302 | * @param {vec4} a vector to normalize
303 | * @returns {vec4} out
304 | */
305 | export function normalize(out, a) {
306 | let x = a[0];
307 | let y = a[1];
308 | let z = a[2];
309 | let w = a[3];
310 | let len = x * x + y * y + z * z + w * w;
311 | if (len > 0) {
312 | len = 1 / Math.sqrt(len);
313 | out[0] = x * len;
314 | out[1] = y * len;
315 | out[2] = z * len;
316 | out[3] = w * len;
317 | }
318 | return out;
319 | }
320 |
321 | /**
322 | * Calculates the dot product of two vec4's
323 | *
324 | * @param {vec4} a the first operand
325 | * @param {vec4} b the second operand
326 | * @returns {Number} dot product of a and b
327 | */
328 | export function dot(a, b) {
329 | return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
330 | }
331 |
332 | /**
333 | * Performs a linear interpolation between two vec4's
334 | *
335 | * @param {vec4} out the receiving vector
336 | * @param {vec4} a the first operand
337 | * @param {vec4} b the second operand
338 | * @param {Number} t interpolation amount between the two inputs
339 | * @returns {vec4} out
340 | */
341 | export function lerp(out, a, b, t) {
342 | let ax = a[0];
343 | let ay = a[1];
344 | let az = a[2];
345 | let aw = a[3];
346 | out[0] = ax + t * (b[0] - ax);
347 | out[1] = ay + t * (b[1] - ay);
348 | out[2] = az + t * (b[2] - az);
349 | out[3] = aw + t * (b[3] - aw);
350 | return out;
351 | }
352 |
353 | /**
354 | * Transforms the vec4 with a mat4.
355 | *
356 | * @param {vec4} out the receiving vector
357 | * @param {vec4} a the vector to transform
358 | * @param {mat4} m matrix to transform with
359 | * @returns {vec4} out
360 | */
361 | export function transformMat4(out, a, m) {
362 | let x = a[0], y = a[1], z = a[2], w = a[3];
363 | out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w;
364 | out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w;
365 | out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w;
366 | out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w;
367 | return out;
368 | }
369 |
370 | /**
371 | * Transforms the vec4 with a quat
372 | *
373 | * @param {vec4} out the receiving vector
374 | * @param {vec4} a the vector to transform
375 | * @param {quat} q quaternion to transform with
376 | * @returns {vec4} out
377 | */
378 | export function transformQuat(out, a, q) {
379 | let x = a[0], y = a[1], z = a[2];
380 | let qx = q[0], qy = q[1], qz = q[2], qw = q[3];
381 |
382 | // calculate quat * vec
383 | let ix = qw * x + qy * z - qz * y;
384 | let iy = qw * y + qz * x - qx * z;
385 | let iz = qw * z + qx * y - qy * x;
386 | let iw = -qx * x - qy * y - qz * z;
387 |
388 | // calculate result * inverse quat
389 | out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy;
390 | out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz;
391 | out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx;
392 | out[3] = a[3];
393 | return out;
394 | }
395 |
396 | /**
397 | * Returns a string representation of a vector
398 | *
399 | * @param {vec4} a vector to represent as a string
400 | * @returns {String} string representation of the vector
401 | */
402 | export function str(a) {
403 | return 'vec4(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')';
404 | }
405 |
406 | /**
407 | * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===)
408 | *
409 | * @param {vec4} a The first vector.
410 | * @param {vec4} b The second vector.
411 | * @returns {Boolean} True if the vectors are equal, false otherwise.
412 | */
413 | export function exactEquals(a, b) {
414 | return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3];
415 | }
416 |
417 | /**
418 | * Returns whether or not the vectors have approximately the same elements in the same position.
419 | *
420 | * @param {vec4} a The first vector.
421 | * @param {vec4} b The second vector.
422 | * @returns {Boolean} True if the vectors are equal, false otherwise.
423 | */
424 | export function equals(a, b) {
425 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3];
426 | let b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
427 | return (Math.abs(a0 - b0) <= EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) &&
428 | Math.abs(a1 - b1) <= EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) &&
429 | Math.abs(a2 - b2) <= EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) &&
430 | Math.abs(a3 - b3) <= EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)));
431 | }
432 |
--------------------------------------------------------------------------------
/ogl/extras/Orbit.js:
--------------------------------------------------------------------------------
1 | // Based from ThreeJS' OrbitControls class, rewritten using es6 with some additions and subtractions.
2 | // TODO: abstract event handlers so can be fed from other sources
3 | // TODO: make scroll zoom more accurate than just >/< zero
4 |
5 | import {Vec3} from '../math/Vec3.js';
6 | import {Vec2} from '../math/Vec2.js';
7 |
8 | const STATE = {NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, DOLLY_PAN: 3};
9 | const tempVec3 = new Vec3();
10 | const tempVec2a = new Vec2();
11 | const tempVec2b = new Vec2();
12 |
13 | export function Orbit(object, {
14 | element = document,
15 | enabled = true,
16 | target = new Vec3(),
17 | ease = 0.25,
18 | inertia = 0.85,
19 | enableRotate = true,
20 | rotateSpeed = 0.1,
21 | enableZoom = true,
22 | zoomSpeed = 1,
23 | enablePan = true,
24 | panSpeed = 0.1,
25 | minPolarAngle = 0,
26 | maxPolarAngle = Math.PI,
27 | minAzimuthAngle = -Infinity,
28 | maxAzimuthAngle = Infinity,
29 | minDistance = 0,
30 | maxDistance = Infinity,
31 | } = {}) {
32 | this.enabled = enabled;
33 | this.target = target;
34 |
35 | // Catch attempts to disable - set to 1 so has no effect
36 | ease = ease || 1;
37 | inertia = inertia || 1;
38 |
39 | this.minDistance = minDistance;
40 | this.maxDistance = maxDistance;
41 |
42 | // current position in sphericalTarget coordinates
43 | const sphericalDelta = {radius: 1, phi: 0, theta: 0};
44 | const sphericalTarget = {radius: 1, phi: 0, theta: 0};
45 | const spherical = {radius: 1, phi: 0, theta: 0};
46 | const panDelta = new Vec3();
47 |
48 | // Grab initial position values
49 | const offset = new Vec3();
50 | offset.copy(object.position).subtract(this.target);
51 | spherical.radius = sphericalTarget.radius = offset.length();
52 | spherical.theta = sphericalTarget.theta = Math.atan2(offset.x, offset.z);
53 | spherical.phi = sphericalTarget.phi = Math.acos(Math.min(Math.max(offset.y / sphericalTarget.radius, -1), 1));
54 |
55 | this.update = () => {
56 |
57 | // apply delta
58 | sphericalTarget.radius *= sphericalDelta.radius;
59 | sphericalTarget.theta += sphericalDelta.theta;
60 | sphericalTarget.phi += sphericalDelta.phi;
61 |
62 | // apply boundaries
63 | sphericalTarget.theta = Math.max(minAzimuthAngle, Math.min(maxAzimuthAngle, sphericalTarget.theta));
64 | sphericalTarget.phi = Math.max(minPolarAngle, Math.min(maxPolarAngle, sphericalTarget.phi));
65 | sphericalTarget.radius = Math.max(this.minDistance, Math.min(this.maxDistance, sphericalTarget.radius));
66 |
67 | // ease values
68 | spherical.phi += (sphericalTarget.phi - spherical.phi) * ease;
69 | spherical.theta += (sphericalTarget.theta - spherical.theta) * ease;
70 | spherical.radius += (sphericalTarget.radius - spherical.radius) * ease;
71 |
72 | // apply pan to target. As offset is relative to target, it also shifts
73 | this.target.add(panDelta);
74 |
75 | // apply rotation to offset
76 | let sinPhiRadius = spherical.radius * Math.sin(Math.max(0.000001, spherical.phi));
77 | offset.x = sinPhiRadius * Math.sin(spherical.theta);
78 | offset.y = spherical.radius * Math.cos(spherical.phi);
79 | offset.z = sinPhiRadius * Math.cos(spherical.theta);
80 |
81 | // Apply updated values to object
82 | object.position.copy(this.target).add(offset);
83 | object.lookAt(this.target);
84 |
85 | // Apply inertia to values
86 | sphericalDelta.theta *= inertia;
87 | sphericalDelta.phi *= inertia;
88 | panDelta.multiply(inertia);
89 |
90 | // Reset scale every frame to avoid applying scale multiple times
91 | sphericalDelta.radius = 1;
92 | };
93 |
94 |
95 |
96 | // Everything below here just updates panDelta and sphericalDelta
97 | // Using those two objects' values, the orbit is calculated
98 |
99 |
100 |
101 | const rotateStart = new Vec2();
102 | const panStart = new Vec2();
103 | const dollyStart = new Vec2();
104 |
105 | let state = STATE.NONE;
106 | this.mouseButtons = {ORBIT: 0, ZOOM: 1, PAN: 2};
107 |
108 | function getZoomScale() {
109 | return Math.pow(0.95, zoomSpeed);
110 | }
111 |
112 | function panLeft(distance, m) {
113 | tempVec3.set(m[0], m[1], m[2]);
114 | tempVec3.multiply(-distance);
115 | panDelta.add(tempVec3);
116 | }
117 |
118 | function panUp(distance, m) {
119 | tempVec3.set(m[4], m[5], m[6]);
120 | tempVec3.multiply(distance);
121 | panDelta.add(tempVec3);
122 | }
123 |
124 | const pan = (deltaX, deltaY) => {
125 | let el = element === document ? document.body : element;
126 | tempVec3.copy(object.position).subtract(this.target);
127 | let targetDistance = tempVec3.length();
128 | targetDistance *= Math.tan(((object.fov || 45) / 2) * Math.PI / 180.0);
129 | panLeft(2 * deltaX * targetDistance / el.clientHeight, object.matrix);
130 | panUp(2 * deltaY * targetDistance / el.clientHeight, object.matrix);
131 | };
132 |
133 | function dolly(dollyScale) {
134 | sphericalDelta.radius /= dollyScale;
135 | }
136 |
137 | function handleMoveRotate(x, y) {
138 | tempVec2a.set(x, y);
139 | tempVec2b.subtract(tempVec2a, rotateStart).multiply(rotateSpeed);
140 | let el = element === document ? document.body : element;
141 | sphericalDelta.theta -= 2 * Math.PI * tempVec2b.x / el.clientHeight;
142 | sphericalDelta.phi -= 2 * Math.PI * tempVec2b.y / el.clientHeight;
143 | rotateStart.copy(tempVec2a);
144 | }
145 |
146 | function handleMouseMoveDolly(e) {
147 | tempVec2a.set(e.clientX, e.clientY);
148 | tempVec2b.subtract(tempVec2a, dollyStart);
149 | if (tempVec2b.y > 0) {
150 | dolly(getZoomScale());
151 | } else if (tempVec2b.y < 0) {
152 | dolly(1 / getZoomScale());
153 | }
154 | dollyStart.copy(tempVec2a);
155 | }
156 |
157 | function handleMovePan(x, y) {
158 | tempVec2a.set(x, y);
159 | tempVec2b.subtract(tempVec2a, panStart).multiply(panSpeed);
160 | pan(tempVec2b.x, tempVec2b.y);
161 | panStart.copy(tempVec2a);
162 | }
163 |
164 | function handleTouchStartDollyPan(e) {
165 | if (enableZoom) {
166 | let dx = e.touches[0].pageX - e.touches[1].pageX;
167 | let dy = e.touches[0].pageY - e.touches[1].pageY;
168 | let distance = Math.sqrt(dx * dx + dy * dy);
169 | dollyStart.set(0, distance);
170 | }
171 |
172 | if (enablePan) {
173 | let x = 0.5 * (e.touches[0].pageX + e.touches[1].pageX);
174 | let y = 0.5 * (e.touches[0].pageY + e.touches[1].pageY);
175 | panStart.set(x, y);
176 | }
177 | }
178 |
179 | function handleTouchMoveDollyPan(e) {
180 | if (enableZoom) {
181 | let dx = e.touches[0].pageX - e.touches[1].pageX;
182 | let dy = e.touches[0].pageY - e.touches[1].pageY;
183 | let distance = Math.sqrt(dx * dx + dy * dy);
184 | tempVec2a.set(0, distance);
185 | tempVec2b.set(0, Math.pow(tempVec2a.y / dollyStart.y, zoomSpeed));
186 | dolly(tempVec2b.y);
187 | dollyStart.copy(tempVec2a);
188 | }
189 |
190 | if (enablePan) {
191 | let x = 0.5 * (e.touches[0].pageX + e.touches[1].pageX);
192 | let y = 0.5 * (e.touches[0].pageY + e.touches[1].pageY);
193 | handleMovePan(x, y);
194 | }
195 | }
196 |
197 | const onMouseDown = (e) => {
198 | if (!this.enabled) return;
199 |
200 | switch (e.button) {
201 | case this.mouseButtons.ORBIT:
202 | if (enableRotate === false) return;
203 | rotateStart.set(e.clientX, e.clientY);
204 | state = STATE.ROTATE;
205 | break;
206 | case this.mouseButtons.ZOOM:
207 | if (enableZoom === false) return;
208 | dollyStart.set(e.clientX, e.clientY);
209 | state = STATE.DOLLY;
210 | break;
211 | case this.mouseButtons.PAN:
212 | if (enablePan === false) return;
213 | panStart.set(e.clientX, e.clientY);
214 | state = STATE.PAN;
215 | break;
216 | }
217 |
218 | if (state !== STATE.NONE) {
219 | window.addEventListener('mousemove', onMouseMove, false);
220 | window.addEventListener('mouseup', onMouseUp, false);
221 | }
222 | };
223 |
224 | const onMouseMove = (e) => {
225 | if (!this.enabled) return;
226 |
227 | switch (state) {
228 | case STATE.ROTATE:
229 | if (enableRotate === false) return;
230 | handleMoveRotate(e.clientX, e.clientY);
231 | break;
232 | case STATE.DOLLY:
233 | if (enableZoom === false) return;
234 | handleMouseMoveDolly(e);
235 | break;
236 | case STATE.PAN:
237 | if (enablePan === false) return;
238 | handleMovePan(e.clientX, e.clientY);
239 | break;
240 | }
241 | };
242 |
243 | const onMouseUp = () => {
244 | if (!this.enabled) return;
245 | document.removeEventListener('mousemove', onMouseMove, false);
246 | document.removeEventListener('mouseup', onMouseUp, false);
247 | state = STATE.NONE;
248 | };
249 |
250 | const onMouseWheel = (e) => {
251 | if (!this.enabled || !enableZoom || (state !== STATE.NONE && state !== STATE.ROTATE)) return;
252 | e.preventDefault();
253 | e.stopPropagation();
254 |
255 | if (e.deltaY < 0) {
256 | dolly(1 / getZoomScale());
257 | } else if (e.deltaY > 0) {
258 | dolly(getZoomScale());
259 | }
260 | };
261 |
262 | const onTouchStart = (e) => {
263 | if (!this.enabled) return;
264 | e.preventDefault();
265 |
266 | switch (e.touches.length) {
267 | case 1:
268 | if (enableRotate === false) return;
269 | rotateStart.set(e.touches[0].pageX, e.touches[0].pageY);
270 | state = STATE.ROTATE;
271 | break;
272 | case 2:
273 | if (enableZoom === false && enablePan === false) return;
274 | handleTouchStartDollyPan(e);
275 | state = STATE.DOLLY_PAN;
276 | break;
277 | default:
278 | state = STATE.NONE;
279 | }
280 | };
281 |
282 | const onTouchMove = (e) => {
283 | if (!this.enabled) return;
284 | e.preventDefault();
285 | e.stopPropagation();
286 |
287 | switch (e.touches.length) {
288 | case 1:
289 | if (enableRotate === false) return;
290 | handleMoveRotate(e.touches[0].pageX, e.touches[0].pageY);
291 | break;
292 | case 2:
293 | if (enableZoom === false && enablePan === false) return;
294 | handleTouchMoveDollyPan(e);
295 | break;
296 | default:
297 | state = STATE.NONE;
298 | }
299 | };
300 |
301 | const onTouchEnd = () => {
302 | if (!this.enabled) return;
303 | state = STATE.NONE;
304 | };
305 |
306 | const onContextMenu = (e) => {
307 | if (!this.enabled) return;
308 | e.preventDefault();
309 | };
310 |
311 | function addHandlers() {
312 | element.addEventListener('contextmenu', onContextMenu, false);
313 | element.addEventListener('mousedown', onMouseDown, false);
314 | window.addEventListener('wheel', onMouseWheel, false);
315 | element.addEventListener('touchstart', onTouchStart, {passive: false});
316 | element.addEventListener('touchend', onTouchEnd, false);
317 | element.addEventListener('touchmove', onTouchMove, {passive: false});
318 | }
319 |
320 | this.remove = function() {
321 | element.removeEventListener('contextmenu', onContextMenu, false);
322 | element.removeEventListener('mousedown', onMouseDown, false);
323 | window.removeEventListener('wheel', onMouseWheel, false);
324 | element.removeEventListener('touchstart', onTouchStart, false);
325 | element.removeEventListener('touchend', onTouchEnd, false);
326 | element.removeEventListener('touchmove', onTouchMove, false);``
327 | window.removeEventListener('mousemove', onMouseMove, false);
328 | window.removeEventListener('mouseup', onMouseUp, false);
329 | };
330 |
331 | addHandlers();
332 | }
--------------------------------------------------------------------------------
/ogl/math/functions/QuatFunc.js:
--------------------------------------------------------------------------------
1 | import * as mat3 from './Mat3Func.js';
2 | import * as vec3 from './Vec3Func.js';
3 | import * as vec4 from './Vec4Func.js';
4 |
5 | /**
6 | * Set a quat to the identity quaternion
7 | *
8 | * @param {quat} out the receiving quaternion
9 | * @returns {quat} out
10 | */
11 | export function identity(out) {
12 | out[0] = 0;
13 | out[1] = 0;
14 | out[2] = 0;
15 | out[3] = 1;
16 | return out;
17 | }
18 |
19 | /**
20 | * Sets a quat from the given angle and rotation axis,
21 | * then returns it.
22 | *
23 | * @param {quat} out the receiving quaternion
24 | * @param {vec3} axis the axis around which to rotate
25 | * @param {Number} rad the angle in radians
26 | * @returns {quat} out
27 | **/
28 | export function setAxisAngle(out, axis, rad) {
29 | rad = rad * 0.5;
30 | let s = Math.sin(rad);
31 | out[0] = s * axis[0];
32 | out[1] = s * axis[1];
33 | out[2] = s * axis[2];
34 | out[3] = Math.cos(rad);
35 | return out;
36 | }
37 |
38 | /**
39 | * Gets the rotation axis and angle for a given
40 | * quaternion. If a quaternion is created with
41 | * setAxisAngle, this method will return the same
42 | * values as providied in the original parameter list
43 | * OR functionally equivalent values.
44 | * Example: The quaternion formed by axis [0, 0, 1] and
45 | * angle -90 is the same as the quaternion formed by
46 | * [0, 0, 1] and 270. This method favors the latter.
47 | * @param {vec3} out_axis Vector receiving the axis of rotation
48 | * @param {quat} q Quaternion to be decomposed
49 | * @return {Number} Angle, in radians, of the rotation
50 | */
51 | export function getAxisAngle(out_axis, q) {
52 | let rad = Math.acos(q[3]) * 2.0;
53 | let s = Math.sin(rad / 2.0);
54 | if (s != 0.0) {
55 | out_axis[0] = q[0] / s;
56 | out_axis[1] = q[1] / s;
57 | out_axis[2] = q[2] / s;
58 | } else {
59 | // If s is zero, return any axis (no rotation - axis does not matter)
60 | out_axis[0] = 1;
61 | out_axis[1] = 0;
62 | out_axis[2] = 0;
63 | }
64 | return rad;
65 | }
66 |
67 | /**
68 | * Multiplies two quats
69 | *
70 | * @param {quat} out the receiving quaternion
71 | * @param {quat} a the first operand
72 | * @param {quat} b the second operand
73 | * @returns {quat} out
74 | */
75 | export function multiply(out, a, b) {
76 | let ax = a[0], ay = a[1], az = a[2], aw = a[3];
77 | let bx = b[0], by = b[1], bz = b[2], bw = b[3];
78 |
79 | out[0] = ax * bw + aw * bx + ay * bz - az * by;
80 | out[1] = ay * bw + aw * by + az * bx - ax * bz;
81 | out[2] = az * bw + aw * bz + ax * by - ay * bx;
82 | out[3] = aw * bw - ax * bx - ay * by - az * bz;
83 | return out;
84 | }
85 |
86 | /**
87 | * Rotates a quaternion by the given angle about the X axis
88 | *
89 | * @param {quat} out quat receiving operation result
90 | * @param {quat} a quat to rotate
91 | * @param {number} rad angle (in radians) to rotate
92 | * @returns {quat} out
93 | */
94 | export function rotateX(out, a, rad) {
95 | rad *= 0.5;
96 |
97 | let ax = a[0], ay = a[1], az = a[2], aw = a[3];
98 | let bx = Math.sin(rad), bw = Math.cos(rad);
99 |
100 | out[0] = ax * bw + aw * bx;
101 | out[1] = ay * bw + az * bx;
102 | out[2] = az * bw - ay * bx;
103 | out[3] = aw * bw - ax * bx;
104 | return out;
105 | }
106 |
107 | /**
108 | * Rotates a quaternion by the given angle about the Y axis
109 | *
110 | * @param {quat} out quat receiving operation result
111 | * @param {quat} a quat to rotate
112 | * @param {number} rad angle (in radians) to rotate
113 | * @returns {quat} out
114 | */
115 | export function rotateY(out, a, rad) {
116 | rad *= 0.5;
117 |
118 | let ax = a[0], ay = a[1], az = a[2], aw = a[3];
119 | let by = Math.sin(rad), bw = Math.cos(rad);
120 |
121 | out[0] = ax * bw - az * by;
122 | out[1] = ay * bw + aw * by;
123 | out[2] = az * bw + ax * by;
124 | out[3] = aw * bw - ay * by;
125 | return out;
126 | }
127 |
128 | /**
129 | * Rotates a quaternion by the given angle about the Z axis
130 | *
131 | * @param {quat} out quat receiving operation result
132 | * @param {quat} a quat to rotate
133 | * @param {number} rad angle (in radians) to rotate
134 | * @returns {quat} out
135 | */
136 | export function rotateZ(out, a, rad) {
137 | rad *= 0.5;
138 |
139 | let ax = a[0], ay = a[1], az = a[2], aw = a[3];
140 | let bz = Math.sin(rad), bw = Math.cos(rad);
141 |
142 | out[0] = ax * bw + ay * bz;
143 | out[1] = ay * bw - ax * bz;
144 | out[2] = az * bw + aw * bz;
145 | out[3] = aw * bw - az * bz;
146 | return out;
147 | }
148 |
149 | /**
150 | * Calculates the W component of a quat from the X, Y, and Z components.
151 | * Assumes that quaternion is 1 unit in length.
152 | * Any existing W component will be ignored.
153 | *
154 | * @param {quat} out the receiving quaternion
155 | * @param {quat} a quat to calculate W component of
156 | * @returns {quat} out
157 | */
158 | export function calculateW(out, a) {
159 | let x = a[0], y = a[1], z = a[2];
160 |
161 | out[0] = x;
162 | out[1] = y;
163 | out[2] = z;
164 | out[3] = Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z));
165 | return out;
166 | }
167 |
168 | /**
169 | * Performs a spherical linear interpolation between two quat
170 | *
171 | * @param {quat} out the receiving quaternion
172 | * @param {quat} a the first operand
173 | * @param {quat} b the second operand
174 | * @param {Number} t interpolation amount between the two inputs
175 | * @returns {quat} out
176 | */
177 | export function slerp(out, a, b, t) {
178 | // benchmarks:
179 | // http://jsperf.com/quaternion-slerp-implementations
180 | let ax = a[0], ay = a[1], az = a[2], aw = a[3];
181 | let bx = b[0], by = b[1], bz = b[2], bw = b[3];
182 |
183 | let omega, cosom, sinom, scale0, scale1;
184 |
185 | // calc cosine
186 | cosom = ax * bx + ay * by + az * bz + aw * bw;
187 | // adjust signs (if necessary)
188 | if (cosom < 0.0) {
189 | cosom = -cosom;
190 | bx = -bx;
191 | by = -by;
192 | bz = -bz;
193 | bw = -bw;
194 | }
195 | // calculate coefficients
196 | if ((1.0 - cosom) > 0.000001) {
197 | // standard case (slerp)
198 | omega = Math.acos(cosom);
199 | sinom = Math.sin(omega);
200 | scale0 = Math.sin((1.0 - t) * omega) / sinom;
201 | scale1 = Math.sin(t * omega) / sinom;
202 | } else {
203 | // "from" and "to" quaternions are very close
204 | // ... so we can do a linear interpolation
205 | scale0 = 1.0 - t;
206 | scale1 = t;
207 | }
208 | // calculate final values
209 | out[0] = scale0 * ax + scale1 * bx;
210 | out[1] = scale0 * ay + scale1 * by;
211 | out[2] = scale0 * az + scale1 * bz;
212 | out[3] = scale0 * aw + scale1 * bw;
213 |
214 | return out;
215 | }
216 |
217 | /**
218 | * Calculates the inverse of a quat
219 | *
220 | * @param {quat} out the receiving quaternion
221 | * @param {quat} a quat to calculate inverse of
222 | * @returns {quat} out
223 | */
224 | export function invert(out, a) {
225 | let a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3];
226 | let dot = a0 * a0 + a1 * a1 + a2 * a2 + a3 * a3;
227 | let invDot = dot ? 1.0 / dot : 0;
228 |
229 | // TODO: Would be faster to return [0,0,0,0] immediately if dot == 0
230 |
231 | out[0] = -a0 * invDot;
232 | out[1] = -a1 * invDot;
233 | out[2] = -a2 * invDot;
234 | out[3] = a3 * invDot;
235 | return out;
236 | }
237 |
238 | /**
239 | * Calculates the conjugate of a quat
240 | * If the quaternion is normalized, this function is faster than quat.inverse and produces the same result.
241 | *
242 | * @param {quat} out the receiving quaternion
243 | * @param {quat} a quat to calculate conjugate of
244 | * @returns {quat} out
245 | */
246 | export function conjugate(out, a) {
247 | out[0] = -a[0];
248 | out[1] = -a[1];
249 | out[2] = -a[2];
250 | out[3] = a[3];
251 | return out;
252 | }
253 |
254 | /**
255 | * Creates a quaternion from the given 3x3 rotation matrix.
256 | *
257 | * NOTE: The resultant quaternion is not normalized, so you should be sure
258 | * to renormalize the quaternion yourself where necessary.
259 | *
260 | * @param {quat} out the receiving quaternion
261 | * @param {mat3} m rotation matrix
262 | * @returns {quat} out
263 | * @function
264 | */
265 | export function fromMat3(out, m) {
266 | // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes
267 | // article "Quaternion Calculus and Fast Animation".
268 | let fTrace = m[0] + m[4] + m[8];
269 | let fRoot;
270 |
271 | if (fTrace > 0.0) {
272 | // |w| > 1/2, may as well choose w > 1/2
273 | fRoot = Math.sqrt(fTrace + 1.0); // 2w
274 | out[3] = 0.5 * fRoot;
275 | fRoot = 0.5 / fRoot; // 1/(4w)
276 | out[0] = (m[5] - m[7]) * fRoot;
277 | out[1] = (m[6] - m[2]) * fRoot;
278 | out[2] = (m[1] - m[3]) * fRoot;
279 | } else {
280 | // |w| <= 1/2
281 | let i = 0;
282 | if (m[4] > m[0])
283 | i = 1;
284 | if (m[8] > m[i * 3 + i])
285 | i = 2;
286 | let j = (i + 1) % 3;
287 | let k = (i + 2) % 3;
288 |
289 | fRoot = Math.sqrt(m[i * 3 + i] - m[j * 3 + j] - m[k * 3 + k] + 1.0);
290 | out[i] = 0.5 * fRoot;
291 | fRoot = 0.5 / fRoot;
292 | out[3] = (m[j * 3 + k] - m[k * 3 + j]) * fRoot;
293 | out[j] = (m[j * 3 + i] + m[i * 3 + j]) * fRoot;
294 | out[k] = (m[k * 3 + i] + m[i * 3 + k]) * fRoot;
295 | }
296 |
297 | return out;
298 | }
299 |
300 | /**
301 | * Creates a quaternion from the given euler angle x, y, z.
302 | *
303 | * @param {quat} out the receiving quaternion
304 | * @param {vec3} euler Angles to rotate around each axis in degrees.
305 | * @param {String} order detailing order of operations. Default 'XYZ'.
306 | * @returns {quat} out
307 | * @function
308 | */
309 | export function fromEuler(out, euler, order = 'YXZ') {
310 | let sx = Math.sin(euler[0] * 0.5);
311 | let cx = Math.cos(euler[0] * 0.5);
312 | let sy = Math.sin(euler[1] * 0.5);
313 | let cy = Math.cos(euler[1] * 0.5);
314 | let sz = Math.sin(euler[2] * 0.5);
315 | let cz = Math.cos(euler[2] * 0.5);
316 |
317 | if (order === 'XYZ') {
318 | out[0] = sx * cy * cz + cx * sy * sz;
319 | out[1] = cx * sy * cz - sx * cy * sz;
320 | out[2] = cx * cy * sz + sx * sy * cz;
321 | out[3] = cx * cy * cz - sx * sy * sz;
322 | } else if (order === 'YXZ') {
323 | out[0] = sx * cy * cz + cx * sy * sz;
324 | out[1] = cx * sy * cz - sx * cy * sz;
325 | out[2] = cx * cy * sz - sx * sy * cz;
326 | out[3] = cx * cy * cz + sx * sy * sz;
327 | } else if (order === 'ZXY') {
328 | out[0] = sx * cy * cz - cx * sy * sz;
329 | out[1] = cx * sy * cz + sx * cy * sz;
330 | out[2] = cx * cy * sz + sx * sy * cz;
331 | out[3] = cx * cy * cz - sx * sy * sz;
332 | } else if (order === 'ZYX') {
333 | out[0] = sx * cy * cz - cx * sy * sz;
334 | out[1] = cx * sy * cz + sx * cy * sz;
335 | out[2] = cx * cy * sz - sx * sy * cz;
336 | out[3] = cx * cy * cz + sx * sy * sz;
337 | } else if (order === 'YZX') {
338 | out[0] = sx * cy * cz + cx * sy * sz;
339 | out[1] = cx * sy * cz + sx * cy * sz;
340 | out[2] = cx * cy * sz - sx * sy * cz;
341 | out[3] = cx * cy * cz - sx * sy * sz;
342 | } else if (order === 'XZY') {
343 | out[0] = sx * cy * cz - cx * sy * sz;
344 | out[1] = cx * sy * cz - sx * cy * sz;
345 | out[2] = cx * cy * sz + sx * sy * cz;
346 | out[3] = cx * cy * cz + sx * sy * sz;
347 | }
348 |
349 | return out;
350 | }
351 |
352 | /**
353 | * Returns a string representation of a quatenion
354 | *
355 | * @param {quat} a vector to represent as a string
356 | * @returns {String} string representation of the vector
357 | */
358 | export function str(a) {
359 | return 'quat(' + a[0] + ', ' + a[1] + ', ' + a[2] + ', ' + a[3] + ')';
360 | }
361 |
362 |
363 | /**
364 | * Copy the values from one quat to another
365 | *
366 | * @param {quat} out the receiving quaternion
367 | * @param {quat} a the source quaternion
368 | * @returns {quat} out
369 | * @function
370 | */
371 | export const copy = vec4.copy;
372 |
373 | /**
374 | * Set the components of a quat to the given values
375 | *
376 | * @param {quat} out the receiving quaternion
377 | * @param {Number} x X component
378 | * @param {Number} y Y component
379 | * @param {Number} z Z component
380 | * @param {Number} w W component
381 | * @returns {quat} out
382 | * @function
383 | */
384 | export const set = vec4.set;
385 |
386 | /**
387 | * Adds two quat's
388 | *
389 | * @param {quat} out the receiving quaternion
390 | * @param {quat} a the first operand
391 | * @param {quat} b the second operand
392 | * @returns {quat} out
393 | * @function
394 | */
395 | export const add = vec4.add;
396 |
397 | /**
398 | * Scales a quat by a scalar number
399 | *
400 | * @param {quat} out the receiving vector
401 | * @param {quat} a the vector to scale
402 | * @param {Number} b amount to scale the vector by
403 | * @returns {quat} out
404 | * @function
405 | */
406 | export const scale = vec4.scale;
407 |
408 | /**
409 | * Calculates the dot product of two quat's
410 | *
411 | * @param {quat} a the first operand
412 | * @param {quat} b the second operand
413 | * @returns {Number} dot product of a and b
414 | * @function
415 | */
416 | export const dot = vec4.dot;
417 |
418 | /**
419 | * Performs a linear interpolation between two quat's
420 | *
421 | * @param {quat} out the receiving quaternion
422 | * @param {quat} a the first operand
423 | * @param {quat} b the second operand
424 | * @param {Number} t interpolation amount between the two inputs
425 | * @returns {quat} out
426 | * @function
427 | */
428 | export const lerp = vec4.lerp;
429 |
430 | /**
431 | * Calculates the length of a quat
432 | *
433 | * @param {quat} a vector to calculate length of
434 | * @returns {Number} length of a
435 | */
436 | export const length = vec4.length;
437 |
438 | /**
439 | * Calculates the squared length of a quat
440 | *
441 | * @param {quat} a vector to calculate squared length of
442 | * @returns {Number} squared length of a
443 | * @function
444 | */
445 | export const squaredLength = vec4.squaredLength;
446 |
447 | /**
448 | * Normalize a quat
449 | *
450 | * @param {quat} out the receiving quaternion
451 | * @param {quat} a quaternion to normalize
452 | * @returns {quat} out
453 | * @function
454 | */
455 | export const normalize = vec4.normalize;
456 |
457 | /**
458 | * Returns whether or not the quaternions have exactly the same elements in the same position (when compared with ===)
459 | *
460 | * @param {quat} a The first quaternion.
461 | * @param {quat} b The second quaternion.
462 | * @returns {Boolean} True if the vectors are equal, false otherwise.
463 | */
464 | export const exactEquals = vec4.exactEquals;
465 |
466 | /**
467 | * Returns whether or not the quaternions have approximately the same elements in the same position.
468 | *
469 | * @param {quat} a The first vector.
470 | * @param {quat} b The second vector.
471 | * @returns {Boolean} True if the vectors are equal, false otherwise.
472 | */
473 | export const equals = vec4.equals;
474 |
475 | /**
476 | * Sets a quaternion to represent the shortest rotation from one
477 | * vector to another.
478 | *
479 | * Both vectors are assumed to be unit length.
480 | *
481 | * @param {quat} out the receiving quaternion.
482 | * @param {vec3} a the initial vector
483 | * @param {vec3} b the destination vector
484 | * @returns {quat} out
485 | */
486 | export const rotationTo = (function () {
487 | let tmpvec3 = new Float32Array([0, 0, 0]);
488 | let xUnitVec3 = new Float32Array([1, 0, 0]);
489 | let yUnitVec3 = new Float32Array([0, 1, 0]);
490 |
491 | return function (out, a, b) {
492 | let dot = vec3.dot(a, b);
493 | if (dot < -0.999999) {
494 | vec3.cross(tmpvec3, xUnitVec3, a);
495 | if (vec3.length(tmpvec3) < 0.000001)
496 | vec3.cross(tmpvec3, yUnitVec3, a);
497 | vec3.normalize(tmpvec3, tmpvec3);
498 | setAxisAngle(out, tmpvec3, Math.PI);
499 | return out;
500 | } else if (dot > 0.999999) {
501 | out[0] = 0;
502 | out[1] = 0;
503 | out[2] = 0;
504 | out[3] = 1;
505 | return out;
506 | } else {
507 | vec3.cross(tmpvec3, a, b);
508 | out[0] = tmpvec3[0];
509 | out[1] = tmpvec3[1];
510 | out[2] = tmpvec3[2];
511 | out[3] = 1 + dot;
512 | return normalize(out, out);
513 | }
514 | };
515 | })();
516 |
517 | /**
518 | * Performs a spherical linear interpolation with two control points
519 | *
520 | * @param {quat} out the receiving quaternion
521 | * @param {quat} a the first operand
522 | * @param {quat} b the second operand
523 | * @param {quat} c the third operand
524 | * @param {quat} d the fourth operand
525 | * @param {Number} t interpolation amount
526 | * @returns {quat} out
527 | */
528 | export const sqlerp = (function () {
529 | let temp1 = identity(new Float32Array(4));
530 | let temp2 = identity(new Float32Array(4));
531 |
532 | return function (out, a, b, c, d, t) {
533 | slerp(temp1, a, d, t);
534 | slerp(temp2, b, c, t);
535 | slerp(out, temp1, temp2, 2 * t * (1 - t));
536 |
537 | return out;
538 | };
539 | }());
540 |
541 | /**
542 | * Sets the specified quaternion with values corresponding to the given
543 | * axes. Each axis is a vec3 and is expected to be unit length and
544 | * perpendicular to all other specified axes.
545 | *
546 | * @param {vec3} view the vector representing the viewing direction
547 | * @param {vec3} right the vector representing the local "right" direction
548 | * @param {vec3} up the vector representing the local "up" direction
549 | * @returns {quat} out
550 | */
551 | export const setAxes = (function () {
552 | let matr = mat3.identity(new Float32Array(9));
553 |
554 | return function (out, view, right, up) {
555 | matr[0] = right[0];
556 | matr[3] = right[1];
557 | matr[6] = right[2];
558 |
559 | matr[1] = up[0];
560 | matr[4] = up[1];
561 | matr[7] = up[2];
562 |
563 | matr[2] = -view[0];
564 | matr[5] = -view[1];
565 | matr[8] = -view[2];
566 |
567 | return normalize(out, fromMat3(out, matr));
568 | };
569 | })();
570 |
--------------------------------------------------------------------------------