UNPKG

infamous

Version:

A CSS3D/WebGL UI library.

845 lines (697 loc) 29.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createWebGLContext = createWebGLContext; exports.removeWebGLContext = removeWebGLContext; exports.setGlResolution = setGlResolution; exports.createShader = createShader; exports.createProgram = createProgram; exports.fragShaderSource = exports.vertShaderSource = exports.m4 = exports.m3 = exports.FourSidedPyramid = exports.ThreeSidedPyramid = exports.Cube = exports.Quad = exports.SymmetricTrapezoid = exports.IsoscelesTriangle = exports.v3 = void 0; // TODO: // - Finish lookAt from the camera tutorial. let targetContextMap = new WeakMap(); function createWebGLContext(target, version) { const canvas = createCanvas('100%', '100%'); const gl = getGl(canvas, version); if (gl) { if (targetContextMap.has(target)) removeWebGLContext(target); target.appendChild(canvas); targetContextMap.set(target, gl); } return gl; } function removeWebGLContext(target) { const gl = targetContextMap.get(target); target.removeChild(gl.canvas); } function createCanvas(width, height) { const canvas = document.createElement('canvas'); setCanvasCSSSize(canvas, width, height); return canvas; } function setCanvasCSSSize(canvas, width, height) { canvas.style.width = width; canvas.style.height = height; } function setGlResolution(gl, width, height) { setCanvasRenderSize(gl.canvas, width, height); gl.viewport(0, 0, width, height); } function setCanvasRenderSize(canvas, width, height) { canvas.width = width; canvas.height = height; } function getGl(canvasOrSelector, version) { let canvas; if (canvasOrSelector instanceof HTMLCanvasElement) canvas = canvasOrSelector; if (!canvas) canvas = document.querySelector(canvasOrSelector); if (!(canvas instanceof HTMLCanvasElement)) return false; if (version == 1 || version == undefined) version = '';else if (version == 2) version = '2';else throw new Error('Invalid WebGL version.'); return canvas.getContext('webgl' + version); } function createShader(gl, type, source) { // Create a vertex shader object const shader = gl.createShader(type); // Attach vertex shader source code gl.shaderSource(shader, source); // Compile the vertex shader gl.compileShader(shader); const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (success) return shader; const error = new Error("*** Error compiling shader '" + shader + "':" + gl.getShaderInfoLog(shader)); gl.deleteShader(shader); throw error; } function createProgram(gl, vertexShader, fragmentShader) { // Create a shader program object to store // the combined shader program const program = gl.createProgram(); // Attach a vertex shader gl.attachShader(program, vertexShader); // Attach a fragment shader gl.attachShader(program, fragmentShader); // Link both programs gl.linkProgram(program); const success = gl.getProgramParameter(program, gl.LINK_STATUS); if (success) { return program; } console.log(' --- Error making program. GL Program Info Log:', gl.getProgramInfoLog(program)); gl.deleteProgram(program); } const v3 = { cross(a, b) { return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]; }, subtract(a, b) { return [a[0] - b[0], a[1] - b[1], a[2] - b[2]]; }, add(a, b) { return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; }, normalize(v) { const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); // make sure we don't divide by 0. if (length > 0.00001) { return [v[0] / length, v[1] / length, v[2] / length]; } else { return [0, 0, 0]; } } }; exports.v3 = v3; class Geometry { constructor(...args) { this._init(...args); } _init() { this.verts = null; // Float32Array this.normals = null; // Float32Array this._colors = null; // Float32Array this._color = null; this._calcVerts(); this.color = [0.5, 0.5, 0.5]; } // TODO handle CSS color strings with tinycolor2 from NPM // @param {Array.number} value - array of four color values r, g, b, and a. // TODO: don't use accept values for color alpha, use node's opacity. set color(value) { if (!value) return; this._color = value; let color = null; if (typeof value == 'string') color = value.trim().split(' ').map(rgbPart => parseFloat(rgbPart));else color = value; // length of _colors array, considering it is four numbers per color, // for each vertex. // TODO: use a uniform instead of attributes that are all the same // value. const l = this.verts.length; const _colorsLength = l + l / 3; const _colors = this._colors = new Float32Array(_colorsLength); for (let i = 0; i < _colorsLength; i += 4) { // 4 color parts per vertex _colors[i + 0] = color[0]; // r _colors[i + 1] = color[1]; // g _colors[i + 2] = color[2]; // b _colors[i + 3] = typeof color[3] == 'undefined' ? 1 : color[3]; // a } } get color() { return this._color; } } class IsoscelesTriangle extends Geometry { _init(width, height) { this.width = width; // number this.height = height; // number super._init(); } _calcVerts() { const { width, height } = this; const verts = this.verts = new Float32Array([-width / 2, 0, 0, width / 2, 0, 0, 0, height, 0]); const normal = [0, 0, 1]; // pointing along Z const normals = this.normals = new Float32Array(verts.length); for (let i = 0, l = verts.length; i < l; i += 3) { // 3 numbers per vertex normals[i + 0] = normal[0]; normals[i + 1] = normal[1]; normals[i + 2] = normal[2]; } } } exports.IsoscelesTriangle = IsoscelesTriangle; class SymmetricTrapezoid extends Geometry { // extends from TwoDeePolygon, which has same normals code. _init(baseWidth, topWidth, height) { this.baseWidth = baseWidth; // number this.topWidth = topWidth; // number this.height = height; // number super._init(); } _calcVerts() { const { baseWidth, topWidth, height } = this; const verts = this.verts = new Float32Array([-baseWidth / 2, 0, 0, baseWidth / 2, 0, 0, topWidth / 2, height, 0, topWidth / 2, height, 0, -topWidth / 2, height, 0, -baseWidth / 2, 0, 0]); const normal = [0, 0, 1]; // pointing along Z const normals = this.normals = new Float32Array(verts.length); for (let i = 0, l = verts.length; i < l; i += 3) { // 3 numbers per vertex normals[i + 0] = normal[0]; normals[i + 1] = normal[1]; normals[i + 2] = normal[2]; } } } exports.SymmetricTrapezoid = SymmetricTrapezoid; class Quad extends Geometry { _init(width, height) { this.width = width; // number this.height = height; // number super._init(); } _calcVerts() { const { width, height } = this; const verts = this.verts = new Float32Array([-width / 2, -height / 2, 0, width / 2, -height / 2, 0, width / 2, height / 2, 0, width / 2, height / 2, 0, -width / 2, height / 2, 0, -width / 2, -height / 2, 0]); const normal = [0, 0, 1]; // pointing along Z const normals = this.normals = new Float32Array(verts.length); for (let i = 0, l = verts.length; i < l; i += 3) { // 3 numbers per vertex normals[i + 0] = normal[0]; normals[i + 1] = normal[1]; normals[i + 2] = normal[2]; } } } exports.Quad = Quad; class Cube extends Geometry { _init(x, y, width) { // the top front left corner this.x = x; // number this.y = y; // number this.width = width; // number super._init(); } _calcVerts() { const { x, y, width } = this; const x2 = x + width; const y2 = y + width; const verts = this.verts = new Float32Array([// front face x, y, 0, x2, y, 0, x2, y2, 0, x2, y2, 0, x, y2, 0, x, y, 0, // left face x, y, 0, x, y, -width, x, y2, -width, x, y2, -width, x, y2, 0, x, y, 0, // right face x2, y, 0, x2, y, -width, x2, y2, -width, x2, y2, -width, x2, y2, 0, x2, y, 0, // back face x, y, -width, x2, y, -width, x2, y2, -width, x2, y2, -width, x, y2, -width, x, y, -width, // top face x, y, 0, x, y, -width, x2, y, -width, x2, y, -width, x2, y, 0, x, y, 0, // bottom face x, y2, 0, x, y2, -width, x2, y2, -width, x2, y2, -width, x2, y2, 0, x, y2, 0]); const faceNormals = [[0, 0, 1], // front face [-1, 0, 0], // left face [1, 0, 0], // right face [0, 0, -1], // back face [0, -1, 0], // top face [0, 1, 0]]; const normals = this.normals = new Float32Array(verts.length); for (let side = 0, i = 0, l = verts.length; i < l; i += 6 * 3, side += 1) { // 6 vertices per side, 3 numbers per vertex normal // first vertex normals[i + 0] = faceNormals[side][0]; normals[i + 1] = faceNormals[side][1]; normals[i + 2] = faceNormals[side][2]; // second vertex normals[i + 3] = faceNormals[side][0]; normals[i + 4] = faceNormals[side][1]; normals[i + 5] = faceNormals[side][2]; // third vertex normals[i + 6] = faceNormals[side][0]; normals[i + 7] = faceNormals[side][1]; normals[i + 8] = faceNormals[side][2]; // fourth vertex normals[i + 9] = faceNormals[side][0]; normals[i + 10] = faceNormals[side][1]; normals[i + 11] = faceNormals[side][2]; // fifth vertex normals[i + 12] = faceNormals[side][0]; normals[i + 13] = faceNormals[side][1]; normals[i + 14] = faceNormals[side][2]; // sixth vertex normals[i + 15] = faceNormals[side][0]; normals[i + 16] = faceNormals[side][1]; normals[i + 17] = faceNormals[side][2]; } } } exports.Cube = Cube; class ThreeSidedPyramid extends Geometry { _init(base, height) { this.base = base; this.height = height; super._init(); } _calcVerts() { const { base, height } = this; // base is hypotenuse in following calculations // TODO: this can be replaced with a loop that can make any-sided // pyramid. const verts = this.verts = new Float32Array([// base 0, 0, 0, // bottom front left base, 0, 0, // bottom front right base / 2, 0, -Math.sin(degToRad(60)) * base, // bottom back // front base, 0, 0, // bottom front right 0, 0, 0, // bottom front left base / 2, height, Math.sin(degToRad(60)) * base / (base / 2) * (base / 2), // tip top // back left 0, 0, 0, // bottom front left base / 2, 0, -Math.sin(degToRad(60)) * base, // bottom back base / 2, height, Math.sin(degToRad(60)) * base / (base / 2) * (base / 2), // tip top // back right base / 2, 0, -Math.sin(degToRad(60)) * base, // bottom back base, 0, 0, // bottom front right base / 2, height, Math.sin(degToRad(60)) * base / (base / 2) * (base / 2)]); // TODO: normals } } exports.ThreeSidedPyramid = ThreeSidedPyramid; class FourSidedPyramid extends Geometry { //_init(base, height) { //this.base = base //this.height = height //super._init() //} //_calcVerts() { //const {base, height} = this //// base is hypotenuse in following calculations //// TODO: this can be replaced with a loop that can make any-sided //// pyramid. //const verts = this.verts = new Float32Array([ //// base //0, 0, 0, // bottom front left //base, 0, 0, // bottom front right //base, 0, -base, // bottom back right //base, 0, -base, // bottom back right //0, 0, -base, // bottom back left //0, 0, 0, // bottom front left //// front //0, 0, 0, // bottom front left //base, 0, 0, // bottom front right //base/2, height, -base/2, // tip top //// right //base, 0, 0, // bottom front right //base, 0, -base, // bottom back right //base/2, height, -base/2, // tip top //// back //base, 0, -base, // bottom back right //0, 0, -base, // bottom back left //base/2, height, -base/2, // tip top //// left //0, 0, -base, // bottom back left //0, 0, 0, // bottom front left //base/2, height, -base/2, // tip top //]) //const faceNormals = [ //// bottom //[0, -1, 0], //// front //v3.cross( //[base, 0, 0], // bottom front right //[base/2, height, -base/2] // tip top //), //// right //v3.cross( //[base, 0, -base], // bottom back right //[-base/2, height, -base/2] // tip top (-x) //), //// left //v3.cross( //[-base, 0, 0], // bottom front right (-x) //[-base/2, height, base/2] // tip top (-x, +z) //), //// right //v3.cross( //[0, 0, base], // bottom back left (+z) //[base/2, height, base/2] // tip top (+z) //), //] //const normals = this.normals = new Float32Array(verts.length) //// bottom (6 verts) //for (let side=0, i=0, l=6*3; i<l; i+=6*3, side+=1) { // 6 vertices per side, 3 numbers per vertex normal //// first vertex //normals[i+0] = faceNormals[side][0] //normals[i+1] = faceNormals[side][1] //normals[i+2] = faceNormals[side][2] //// second vertex //normals[i+3] = faceNormals[side][0] //normals[i+4] = faceNormals[side][1] //normals[i+5] = faceNormals[side][2] //// third vertex //normals[i+6] = faceNormals[side][0] //normals[i+7] = faceNormals[side][1] //normals[i+8] = faceNormals[side][2] //// fourth vertex //normals[i+9] = faceNormals[side][0] //normals[i+10] = faceNormals[side][1] //normals[i+11] = faceNormals[side][2] //// fifth vertex //normals[i+12] = faceNormals[side][0] //normals[i+13] = faceNormals[side][1] //normals[i+14] = faceNormals[side][2] //// sixth vertex //normals[i+15] = faceNormals[side][0] //normals[i+16] = faceNormals[side][1] //normals[i+17] = faceNormals[side][2] //} //// sides (3 verts each) //for (let side=0+1, i=0, l=verts.length - 6*3; i<l; i+=3*3, side+=1) { // 3 vertices per side, 3 numbers per vertex normal //// first vertex //normals[i+0] = faceNormals[side][0] //normals[i+1] = faceNormals[side][1] //normals[i+2] = faceNormals[side][2] //// second vertex //normals[i+3] = faceNormals[side][0] //normals[i+4] = faceNormals[side][1] //normals[i+5] = faceNormals[side][2] //// third vertex //normals[i+6] = faceNormals[side][0] //normals[i+7] = faceNormals[side][1] //normals[i+8] = faceNormals[side][2] //} //} _init() { super._init(); } _calcVerts() { this.verts = new Float32Array([-100, 0.087303, -100, 100, 0.087303, -100, 100, 0.087303, 100, -100, 0.087303, 100, 100, 0.087303, 100, 100, 0.087303, -100, 0, 200.087, 0, 100, 0.087303, -100, -100, 0.087303, -100, 0, 200.087, 0, -100, 0.087303, -100, -100, 0.087303, 100, 0, 200.087, 0, -100, 0.087303, 100, 100, 0.087303, 100, 0, 200.087, 0]); this.normals = new Float32Array([0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0.894427, 0.447214, 0, 0.894427, 0.447214, 0, 0.894427, 0.447214, 0, 0, 0.447214, -0.894427, 0, 0.447214, -0.894427, 0, 0.447214, -0.894427, -0.894427, 0.447214, 0, -0.894427, 0.447214, 0, -0.894427, 0.447214, 0, 0, 0.447214, 0.894427, 0, 0.447214, 0.894427, 0, 0.447214, 0.894427]); } } exports.FourSidedPyramid = FourSidedPyramid; const m3 = { identity: Object.freeze([1, 0, 0, 0, 1, 0, 0, 0, 1]), translation(tx, ty) { return [1, 0, 0, 0, 1, 0, tx, ty, 1]; }, rotation(angleInRadians) { const c = Math.cos(angleInRadians); const s = Math.sin(angleInRadians); return [c, -s, 0, s, c, 0, 0, 0, 1]; }, scaling(sx, sy) { return [sx, 0, 0, 0, sy, 0, 0, 0, 1]; }, // Note: This matrix flips the Y axis so that 0 is at the top. projection(width, height) { // longer version, multiple matrices let matrix = m3.identity; matrix = m3.multiply(m3.scaling(1 / width, 1 / height), matrix); // get the portion of clip space matrix = m3.multiply(m3.scaling(2, 2), matrix); // convert to clip space units matrix = m3.multiply(m3.translation(-1, -1), matrix); // Move from the center to bottom left matrix = m3.multiply(m3.scaling(1, -1), matrix); // move to the top left like DOM return matrix; // shorter version, manual result of the longer version //return [ //2 / width, 0, 0, //0, -2 / height, 0, //-1, 1, 1 //] }, multiply(a, b) { const a00 = a[0]; const a01 = a[1]; const a02 = a[2]; const a10 = a[3]; const a11 = a[4]; const a12 = a[5]; const a20 = a[6]; const a21 = a[7]; const a22 = a[8]; const b00 = b[0]; const b01 = b[1]; const b02 = b[2]; const b10 = b[3]; const b11 = b[4]; const b12 = b[5]; const b20 = b[6]; const b21 = b[7]; const b22 = b[8]; return [b00 * a00 + b01 * a10 + b02 * a20, b00 * a01 + b01 * a11 + b02 * a21, b00 * a02 + b01 * a12 + b02 * a22, b10 * a00 + b11 * a10 + b12 * a20, b10 * a01 + b11 * a11 + b12 * a21, b10 * a02 + b11 * a12 + b12 * a22, b20 * a00 + b21 * a10 + b22 * a20, b20 * a01 + b21 * a11 + b22 * a21, b20 * a02 + b21 * a12 + b22 * a22]; } }; exports.m3 = m3; const m4 = { identity: Object.freeze([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]), translation(tx, ty, tz) { return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, tx, ty, tz, 1]; }, xRotation(degrees) { const radians = degToRad(degrees); const c = Math.cos(radians); const s = Math.sin(radians); return [1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1]; }, yRotation(degrees) { const radians = degToRad(degrees); const c = Math.cos(radians); const s = Math.sin(radians); return [c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 0, 0, 1]; }, zRotation(degrees) { const radians = degToRad(degrees); const c = Math.cos(radians); const s = Math.sin(radians); return [c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; }, scaling(sx, sy, sz) { return [sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1]; }, inverse(m) { const m00 = m[0 * 4 + 0]; const m01 = m[0 * 4 + 1]; const m02 = m[0 * 4 + 2]; const m03 = m[0 * 4 + 3]; const m10 = m[1 * 4 + 0]; const m11 = m[1 * 4 + 1]; const m12 = m[1 * 4 + 2]; const m13 = m[1 * 4 + 3]; const m20 = m[2 * 4 + 0]; const m21 = m[2 * 4 + 1]; const m22 = m[2 * 4 + 2]; const m23 = m[2 * 4 + 3]; const m30 = m[3 * 4 + 0]; const m31 = m[3 * 4 + 1]; const m32 = m[3 * 4 + 2]; const m33 = m[3 * 4 + 3]; const tmp_0 = m22 * m33; const tmp_1 = m32 * m23; const tmp_2 = m12 * m33; const tmp_3 = m32 * m13; const tmp_4 = m12 * m23; const tmp_5 = m22 * m13; const tmp_6 = m02 * m33; const tmp_7 = m32 * m03; const tmp_8 = m02 * m23; const tmp_9 = m22 * m03; const tmp_10 = m02 * m13; const tmp_11 = m12 * m03; const tmp_12 = m20 * m31; const tmp_13 = m30 * m21; const tmp_14 = m10 * m31; const tmp_15 = m30 * m11; const tmp_16 = m10 * m21; const tmp_17 = m20 * m11; const tmp_18 = m00 * m31; const tmp_19 = m30 * m01; const tmp_20 = m00 * m21; const tmp_21 = m20 * m01; const tmp_22 = m00 * m11; const tmp_23 = m10 * m01; const t0 = tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31 - (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31); const t1 = tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31 - (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31); const t2 = tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31 - (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31); const t3 = tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21 - (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21); const d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3); return [d * t0, d * t1, d * t2, d * t3, d * (tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30 - (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)), d * (tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30 - (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)), d * (tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30 - (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)), d * (tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20 - (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20)), d * (tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33 - (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)), d * (tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33 - (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)), d * (tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33 - (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)), d * (tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23 - (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23)), d * (tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12 - (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)), d * (tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22 - (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)), d * (tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02 - (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)), d * (tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12 - (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02))]; }, transpose(m) { return [m[0], m[4], m[8], m[12], m[1], m[5], m[9], m[13], m[2], m[6], m[10], m[14], m[3], m[7], m[11], m[15]]; }, // Note: This matrix flips the Y axis so that 0 is at the top. projection(width, height, depth) { // longer version, multiple matrices //let matrix = m4.identity //matrix = m4.multiply(m4.scaling(1/width, 1/height, 1/depth), matrix) // get the portion of clip space //matrix = m4.multiply(m4.scaling(2, 2, 2), matrix) // convert to clip space units //matrix = m4.multiply(m4.translation(-1, -1, 0), matrix) // Move from the center to bottom left //matrix = m4.multiply(m4.scaling(1, -1, 1), matrix) // move to the top left like DOM //return matrix // shorter version, manual result of the longer version return [2 / width, 0, 0, 0, 0, -2 / height, 0, 0, 0, 0, 2 / depth, 0, -1, 1, 0, 1]; }, // Note: This matrix flips the Y axis so that 0 is at the top. orthographic(left, right, top, bottom, near, far) { return [2 / (right - left), 0, 0, 0, 0, 2 / (top - bottom), 0, 0, 0, 0, 2 / (near - far), 0, (left + right) / (left - right), (bottom + top) / (bottom - top), (near + far) / (near - far), 1]; }, perspective(fieldOfViewInDegrees, aspect, near, far) { const fieldOfViewInRadians = degToRad(fieldOfViewInDegrees); const f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians); const rangeInv = 1.0 / (near - far); return [f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (near + far) * rangeInv, -1, 0, 0, near * far * rangeInv * 2, 0]; }, lookAt(cameraPosition, target, up) { const zAxis = v3.normalize(v3.subtract(cameraPosition, target)); const xAxis = v3.cross(up, zAxis); const yAxis = v3.cross(zAxis, xAxis); return [xAxis[0], xAxis[1], xAxis[2], 0, yAxis[0], yAxis[1], yAxis[2], 0, zAxis[0], zAxis[1], zAxis[2], 0, cameraPosition[0], cameraPosition[1], cameraPosition[2], 1]; }, multiply(a, b) { const a00 = a[0 * 4 + 0]; const a01 = a[0 * 4 + 1]; const a02 = a[0 * 4 + 2]; const a03 = a[0 * 4 + 3]; const a10 = a[1 * 4 + 0]; const a11 = a[1 * 4 + 1]; const a12 = a[1 * 4 + 2]; const a13 = a[1 * 4 + 3]; const a20 = a[2 * 4 + 0]; const a21 = a[2 * 4 + 1]; const a22 = a[2 * 4 + 2]; const a23 = a[2 * 4 + 3]; const a30 = a[3 * 4 + 0]; const a31 = a[3 * 4 + 1]; const a32 = a[3 * 4 + 2]; const a33 = a[3 * 4 + 3]; const b00 = b[0 * 4 + 0]; const b01 = b[0 * 4 + 1]; const b02 = b[0 * 4 + 2]; const b03 = b[0 * 4 + 3]; const b10 = b[1 * 4 + 0]; const b11 = b[1 * 4 + 1]; const b12 = b[1 * 4 + 2]; const b13 = b[1 * 4 + 3]; const b20 = b[2 * 4 + 0]; const b21 = b[2 * 4 + 1]; const b22 = b[2 * 4 + 2]; const b23 = b[2 * 4 + 3]; const b30 = b[3 * 4 + 0]; const b31 = b[3 * 4 + 1]; const b32 = b[3 * 4 + 2]; const b33 = b[3 * 4 + 3]; return [b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33]; } }; exports.m4 = m4; function degToRad(degrees) { return degrees * Math.PI / 180; } const vertShaderSource = ` attribute vec4 a_vertexPosition; uniform mat4 u_worldViewProjectionMatrix; // TODO: awaiting on transpose() method for DOMMatrix //uniform mat4 u_worldInverseTransposeMatrix; // used for correct lighting normals attribute vec4 a_color; varying vec4 v_fragColor; attribute vec3 a_normal; varying vec3 v_vertNormal; uniform mat4 u_worldMatrix; uniform vec3 u_lightWorldPosition; varying vec3 v_surfaceToLightVector; uniform vec3 u_cameraWorldPosition; varying vec3 v_surfaceToCameraVector; attribute vec2 a_textureCoordinate; varying vec2 v_textureCoordinate; void main() { vec3 surfaceWorldPosition = (u_worldMatrix * a_vertexPosition).xyz; // compute the vector of the surface to the pointLight // and pass it to the fragment shader v_surfaceToLightVector = u_lightWorldPosition - surfaceWorldPosition; // compute the vector of the surface to the camera // and pass it to the fragment shader v_surfaceToCameraVector = u_cameraWorldPosition - surfaceWorldPosition; gl_Position = u_worldViewProjectionMatrix * a_vertexPosition; v_fragColor = a_color; //v_fragColor = gl_Position * 0.5 + 0.5; // orient the normals and pass to the fragment shader //v_vertNormal = mat3(u_worldInverseTransposeMatrix) * a_normal; // TODO waiting for transpose() method on DOMMatrix //alternate: v_vertNormal = (u_worldInverseTransposeMatrix * vec4(a_normal, 0)).xyz; v_vertNormal = mat3(u_worldMatrix) * a_normal; v_textureCoordinate = a_textureCoordinate; } `; exports.vertShaderSource = vertShaderSource; const fragShaderSource = ` // TODO: detect highp support, see // https://github.com/greggman/webgl-fundamentals/issues/80#issuecomment-306746556 //precision mediump float; precision highp float; varying vec4 v_fragColor; varying vec3 v_vertNormal; varying vec3 v_surfaceToLightVector; //// TODO: use this for directional lighting (f.e. sunlight or moonlight). //uniform vec3 reverseLightDirection; varying vec3 v_surfaceToCameraVector; uniform float u_shininess; uniform vec3 u_lightColor; uniform vec3 u_specularColor; varying vec2 v_textureCoordinate; uniform sampler2D u_texture; uniform bool u_hasTexture; void main(void) { // because v_vertNormal is a varying it's interpolated // so it will not be a unit vector. Normalizing it // will make it a unit vector again. vec3 normal = normalize(v_vertNormal); vec3 surfaceToCameraDirection = normalize(v_surfaceToCameraVector); vec3 surfaceToLightDirection = normalize(v_surfaceToLightVector); // represents the unit vector oriented at half of the angle between // surfaceToLightDirection and surfaceToCameraDirection. vec3 halfVector = normalize(surfaceToLightDirection + surfaceToCameraDirection); float pointLight = dot(normal, surfaceToLightDirection); float pointLightIntensity = 1.0; // TODO make configurable //float directionalLight = dot(normal, reverseLightDirection); // TODO make configurable //float specular = dot(normal, halfVector); float specular = 0.0; if (pointLight > 0.0) { specular = pow(dot(normal, halfVector), u_shininess); } // TODO make configurable //vec3 ambientLight = vec3(0.361, 0.184, 0.737); // teal vec3 ambientLight = vec3(1.0, 1.0, 1.0); // white float ambientLightIntensity = 0.3; // TODO: user can choose color or texture, default to a color if no texture, etc. // TODO: blend texture on top of color, if texture has alpha. gl_FragColor = v_fragColor; if (u_hasTexture) { gl_FragColor = texture2D(u_texture, v_textureCoordinate); } // Lets multiply just the color portion (not the alpha) of // gl_FragColor by the pointLight + directionalLight //gl_FragColor.rgb *= pointLight * u_lightColor; // point light only. //gl_FragColor.rgb *= directionalLight; // directional light only. //gl_FragColor.rgb *= ambientLight; // ambient light only. gl_FragColor.rgb *= //clamp(directionalLight, 0.0, 1.0) + clamp(pointLight, 0.0, 1.0) * u_lightColor * pointLightIntensity + ambientLight * ambientLightIntensity; // Just add in the specular gl_FragColor.rgb += specular * u_specularColor; //gl_FragColor.a = 0.5; } `; exports.fragShaderSource = fragShaderSource;