diff --git a/client/public/brick-renderer/basic.fs b/client/public/brick-renderer/basic.fs index 0f83d05..83e1e77 100644 --- a/client/public/brick-renderer/basic.fs +++ b/client/public/brick-renderer/basic.fs @@ -5,24 +5,29 @@ uniform SceneUniforms { mat4 viewProj; vec4 eyePosition; vec4 lightPosition; -} uScene; - -uniform sampler2D tex; +} uView; in vec3 vPosition; in vec3 vNormal; out vec4 fragColor; +// TODO: PBR +// https://github.com/Moguri/panda3d-simplepbr void main() { - vec3 color = vNormal; - + vec3 color = vec3(0.89019607843, 0.0, 0.00392156862); vec3 normal = normalize(vNormal); - vec3 eyeVec = normalize(uScene.eyePosition.xyz - vPosition); - vec3 incidentVec = normalize(vPosition - uScene.lightPosition.xyz); - vec3 lightVec = -incidentVec; - float diffuse = max(dot(lightVec, normal), 0.0); - float highlight = pow(max(dot(eyeVec, reflect(incidentVec, normal)), 0.0), 100.0); - float ambient = 0.1; - fragColor = vec4(color * (diffuse + highlight + ambient), 1.0); + + vec3 lightDir = normalize(uView.lightPosition.xyz - vPosition); + vec3 viewDir = normalize(uView.eyePosition.xyz - vPosition); + vec3 halfDir = normalize(lightDir + viewDir); + float spec = pow(max(dot(normal, halfDir), 0.0), 20.0); + vec3 specular = vec3(0.3) * spec; + + float diff = max(dot(lightDir, normal), 0.0); + vec3 diffuse = color * diff; + + vec3 ambient = color * 0.1; + + fragColor = vec4(ambient + diffuse + specular, 1.0); } diff --git a/client/public/brick-renderer/basic.vs b/client/public/brick-renderer/basic.vs index 08659d0..f85faf0 100644 --- a/client/public/brick-renderer/basic.vs +++ b/client/public/brick-renderer/basic.vs @@ -1,7 +1,5 @@ #version 300 es -layout(std140, column_major) uniform; - layout(location=0) in vec4 position; layout(location=1) in vec4 normal; @@ -9,7 +7,7 @@ uniform SceneUniforms { mat4 viewProj; vec4 eyePosition; vec4 lightPosition; -} uScene; +} uView; uniform mat4 uModel; @@ -20,5 +18,5 @@ void main() { vec4 worldPosition = uModel * position; vPosition = worldPosition.xyz; vNormal = (uModel * normal).xyz; - gl_Position = uScene.viewProj * worldPosition; + gl_Position = uView.viewProj * worldPosition; } diff --git a/client/public/brick-renderer/box.mjs b/client/public/brick-renderer/box.mjs index 4f1c21d..1d0177a 100644 --- a/client/public/brick-renderer/box.mjs +++ b/client/public/brick-renderer/box.mjs @@ -14,24 +14,26 @@ export default class Box { bind() { this.gl.bindVertexArray(this.vao); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.normalBuffer); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); } create(options) { const { positions, normals } = this.boxVerticies(options); this.verticies = positions.length / 3; + this.positionBuffer = this.gl.createBuffer(); this.gl.bindVertexArray(this.vao); - - const positionBuffer = this.gl.createBuffer(); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW); this.gl.vertexAttribPointer(0, 3, this.gl.FLOAT, false, 0, 0); this.gl.enableVertexAttribArray(0); - const normalBuffer = this.gl.createBuffer(); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, normalBuffer); + this.normalBuffer = this.gl.createBuffer(); + this.gl.bindVertexArray(this.vao); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.normalBuffer); this.gl.bufferData(this.gl.ARRAY_BUFFER, normals, this.gl.STATIC_DRAW); - this.gl.vertexAttribPointer(2, 3, this.gl.FLOAT, false, 0, 0); + this.gl.vertexAttribPointer(1, 3, this.gl.FLOAT, false, 0, 0); this.gl.enableVertexAttribArray(1); } diff --git a/client/public/brick-renderer/glm/glm.mjs b/client/public/brick-renderer/glm/glm.mjs index e89dffe..4222445 100644 --- a/client/public/brick-renderer/glm/glm.mjs +++ b/client/public/brick-renderer/glm/glm.mjs @@ -1,11 +1,11 @@ -import * as glMatrix from "./common.js"; -import * as mat2 from "./mat2.js"; -import * as mat2d from "./mat2d.js"; -import * as mat3 from "./mat3.js"; -import * as mat4 from "./mat4.js"; -import * as quat from "./quat.js"; -import * as quat2 from "./quat2.js"; -import * as vec2 from "./vec2.js"; -import * as vec3 from "./vec3.js"; -import * as vec4 from "./vec4.js"; +import * as glMatrix from './common.js'; +import * as mat2 from './mat2.js'; +import * as mat2d from './mat2d.js'; +import * as mat3 from './mat3.js'; +import * as mat4 from './mat4.js'; +import * as quat from './quat.js'; +import * as quat2 from './quat2.js'; +import * as vec2 from './vec2.js'; +import * as vec3 from './vec3.js'; +import * as vec4 from './vec4.js'; export { glMatrix, mat2, mat2d, mat3, mat4, quat, quat2, vec2, vec3, vec4 }; \ No newline at end of file diff --git a/client/public/brick-renderer/index.mjs b/client/public/brick-renderer/index.mjs index 0416454..85d2e27 100644 --- a/client/public/brick-renderer/index.mjs +++ b/client/public/brick-renderer/index.mjs @@ -1,12 +1,18 @@ import { mat4, vec3 } from './glm/glm.mjs'; import Shader from './shader.mjs'; import Box from './box.mjs'; +import LoadObj from './wavefront-obj.mjs'; let BasicVsource, BasicFSource; +let LegoStudObjSource, LegoStudObjParseResult; + export async function RendererPreInit() { BasicFSource = await fetch('./brick-renderer/basic.fs').then(r => r.text()); BasicVsource = await fetch('./brick-renderer/basic.vs').then(r => r.text()); + LegoStudObjSource = await fetch('./res/lego_stud.obj').then(r => r.text()); + LegoStudObjParseResult = LoadObj(LegoStudObjSource); + console.log(LegoStudObjParseResult); } class BaseRenderer { @@ -15,11 +21,12 @@ class BaseRenderer { this.gl = canvas.getContext('webgl2'); this.gl.viewport(0, 0, canvas.width, canvas.height); this.gl.clearColor(0.84313, 0.76078, 1.0, 1.0); - this.gl.clear(this.gl.COLOR_BUFFER_BIT); this.gl.enable(this.gl.DEPTH_TEST); this.shader = new Shader(this.gl, BasicVsource, BasicFSource); this.shader.link(); + + WebGLDebugUtils.init(this.gl); } } @@ -27,11 +34,46 @@ export class BrickRenderer extends BaseRenderer { constructor(canvas, options) { super(canvas); - const sceneUniformLocation = this.shader.getUniformBlock('SceneUniforms'); - const modelMatrixLocation = this.shader.getUniform('uModel'); + this.angleX = 0; + this.angleY = 0; + + // random number between 0 and 0.1 + this.dx = Math.random() * 0.09; + this.dy = Math.random() * 0.09; + + + ///////////////////////// + // TESTING LEGO STUDS // + + this.VAO = this.gl.createVertexArray(); + this.gl.bindVertexArray(this.VAO); + + this.VBO = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.VBO); + this.gl.bufferData(this.gl.ARRAY_BUFFER, LegoStudObjParseResult.vertices, this.gl.STATIC_DRAW); + this.gl.vertexAttribPointer(0, 3, this.gl.FLOAT, false, 0, 0); + this.gl.enableVertexAttribArray(0); + + const nVBO = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, nVBO); + this.gl.bufferData(this.gl.ARRAY_BUFFER, LegoStudObjParseResult.normals, this.gl.STATIC_DRAW); + this.gl.vertexAttribPointer(1, 3, this.gl.FLOAT, false, 0, 0); + this.gl.enableVertexAttribArray(1); + + this.EBO = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.EBO); + this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, LegoStudObjParseResult.indices, this.gl.STATIC_DRAW); + + // TESTING LEGO STUDS // + ///////////////////////// + + + this.sceneUniformLocation = this.shader.getUniformBlock('SceneUniforms'); + this.modelMatrixLocation = this.shader.getUniform('uView'); this.shader.attatch(); - const boxObj = new Box(this.gl, { dimensions: [0.5, 0.6, 0.5] }); + this.boxObj = new Box(this.gl, { dimensions: [0.5, 0.6, 0.5] }); + this.boxObj.create(); const projMatrix = mat4.create(); mat4.perspective(projMatrix, Math.PI / 2, this.gl.drawingBufferWidth / this.gl.drawingBufferHeight, 0.1, 10.0); @@ -45,9 +87,9 @@ export class BrickRenderer extends BaseRenderer { const lightPosition = vec3.fromValues(1, 1, 0.5); - const modelMatrix = mat4.create(); - const rotateXMatrix = mat4.create(); - const rotateYMatrix = mat4.create(); + this.modelMatrix = mat4.create(); + this.rotateXMatrix = mat4.create(); + this.rotateYMatrix = mat4.create(); const sceneUniformData = new Float32Array(24); sceneUniformData.set(viewProjMatrix); sceneUniformData.set(eyePosition, 16); @@ -57,25 +99,32 @@ export class BrickRenderer extends BaseRenderer { this.gl.bindBufferBase(this.gl.UNIFORM_BUFFER, 0, sceneUniformBuffer); this.gl.bufferData(this.gl.UNIFORM_BUFFER, sceneUniformData, this.gl.STATIC_DRAW); - let angleX = 0; - let angleY = 0; + requestAnimationFrame(this.draw.bind(this)); + } - function draw() { - angleX += 0.01; - angleY += 0.015; + draw() { + this.gl.clear(this.gl.COLOR_BUFFER_BIT); - mat4.fromXRotation(rotateXMatrix, angleX); - mat4.fromYRotation(rotateYMatrix, angleY); - mat4.multiply(modelMatrix, rotateXMatrix, rotateYMatrix); + this.angleX += this.dx; + this.angleY += this.dy; - this.gl.uniformMatrix4fv(modelMatrixLocation, false, modelMatrix); + mat4.fromXRotation(this.rotateXMatrix, this.angleX); + mat4.fromYRotation(this.rotateYMatrix, this.angleY); + mat4.multiply(this.modelMatrix, this.rotateXMatrix, this.rotateYMatrix); - this.gl.clear(this.gl.COLOR_BUFFER_BIT); - this.gl.drawArrays(this.gl.TRIANGLES, 0, boxObj.vertexCount); + this.gl.uniformMatrix4fv(this.modelMatrixLocation, false, this.modelMatrix); - requestAnimationFrame(draw); + this.gl.bindVertexArray(this.VAO); + this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.EBO); + this.gl.drawElements(this.gl.TRIANGLES, LegoStudObjParseResult.indices.length * 3, this.gl.UNSIGNED_SHORT, 0); + + // this.boxObj.bind(); + // this.gl.drawArrays(this.gl.TRIANGLES, 0, this.boxObj.vertexCount); + + if (this.gl.getError() !== this.gl.NO_ERROR) { + console.error(WebGLDebugUtils.glEnumToString(this.gl.getError())); } - requestAnimationFrame(draw); + requestAnimationFrame(this.draw.bind(this)); } } diff --git a/client/public/brick-renderer/renderable.mjs b/client/public/brick-renderer/renderable.mjs new file mode 100644 index 0000000..db7ae29 --- /dev/null +++ b/client/public/brick-renderer/renderable.mjs @@ -0,0 +1,57 @@ +import { mat4, vec3, vec4 } from './glm/glm.mjs'; + +export class Material { + constructor(gl, colour = [0.89019607843, 0.0, 0.00392156862], shininess = 20.0) { + this.gl = gl; + this.colour = colour; + this.shininess = shininess; + } +} + +export class Renderable { + constructor(gl, shader, material = new Material()) { + this.gl = gl; + // TODO: Get these from the shader + this.attributeLocations = { + position: 0, + normal: 1, + }; + this.buffers = { + vertexBuffer: null, + normalBuffer: null, + faceBuffer: null, + }; + this.data = { + verticies: [], + normals: [], + faces: [], + }; + this.shader = shader; + this.material = material; + this.uniforms = { + modelMatrix: mat4.create(), + u_modelMatrix: null, + viewMatrix: mat4.create(), + u_viewMatrix: null, + projectionMatrix: mat4.create(), + u_projectionMatrix: null, + lightPosition: vec3.create(), + u_lightPosition: null, + + ambientLightColor: vec3.fromValues(0.2, 0.2, 0.2), + u_ambientLightColor: null, + diffuseLightColor: vec3.fromValues(0.8, 0.8, 0.8), + u_diffuseLightColor: null, + specularLightColor: vec3.fromValues(1.0, 1.0, 1.0), + u_specularLightColor: null, + lightIntensity: 1.0, + u_lightIntensity: null, + ambientIntensity: 1.0, + u_ambientIntensity: null, + }; + } +} + +export class LegoBrickRenderable { + +} diff --git a/client/public/brick-renderer/wavefront-obj.mjs b/client/public/brick-renderer/wavefront-obj.mjs new file mode 100644 index 0000000..02c2861 --- /dev/null +++ b/client/public/brick-renderer/wavefront-obj.mjs @@ -0,0 +1,51 @@ +// Looseley based on https://webglfundamentals.org/webgl/lessons/webgl-load-obj.html + +// returns verticies, normals and indicies +// (texture coordinates are for nerds) +export default function LoadObj(objText) { + const lines = objText.split('\n'); + + const v = []; + const vn = []; + const f = []; + const fn = []; + + for (const line of lines) { + const words = line.split(' '); + if (words[0] === 'v') { + // verticies + const x = parseFloat(words[1]); + const y = parseFloat(words[2]); + const z = parseFloat(words[3]); + const vert = [x, y, z]; + v.push(vert); + } else if (words[0] === 'vn') { + // normals + const nx = parseFloat(words[1]); + const ny = parseFloat(words[2]); + const nz = parseFloat(words[3]); + const n = [nx, ny, nz]; + vn.push(n); + } else if (words[0] === 'f') { + // indicies + const pos = []; + const nor = []; + for (let i = 1; i < words.length; i++) { + const face = words[i].split('//'); + const v = parseInt(face[0]); + const n = parseInt(face[1]); + pos.push(v); + nor.push(n); + } + f.push(pos); + fn.push(nor); + } + } + + return { + vertices: v, + normals: vn, + indices: f, + normalIndicies: fn, + }; +} diff --git a/client/public/brick-renderer/webgl-debug.js b/client/public/brick-renderer/webgl-debug.js new file mode 100644 index 0000000..7304d92 --- /dev/null +++ b/client/public/brick-renderer/webgl-debug.js @@ -0,0 +1,1191 @@ +/* +** Copyright (c) 2012 The Khronos Group Inc. +** +** Permission is hereby granted, free of charge, to any person obtaining a +** copy of this software and/or associated documentation files (the +** "Materials"), to deal in the Materials without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Materials, and to +** permit persons to whom the Materials are furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be included +** in all copies or substantial portions of the Materials. +** +** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. +*/ + +// Various functions for helping debug WebGL apps. + +WebGLDebugUtils = function() { + + /** + * Wrapped logging function. + * @param {string} msg Message to log. + */ + var log = function(msg) { + if (window.console && window.console.log) { + window.console.log(msg); + } + }; + + /** + * Wrapped error logging function. + * @param {string} msg Message to log. + */ + var error = function(msg) { + if (window.console && window.console.error) { + window.console.error(msg); + } else { + log(msg); + } + }; + + + /** + * Which arguments are enums based on the number of arguments to the function. + * So + * 'texImage2D': { + * 9: { 0:true, 2:true, 6:true, 7:true }, + * 6: { 0:true, 2:true, 3:true, 4:true }, + * }, + * + * means if there are 9 arguments then 6 and 7 are enums, if there are 6 + * arguments 3 and 4 are enums + * + * @type {!Object.} + */ + var glValidEnumContexts = { + // Generic setters and getters + + 'enable': {1: { 0:true }}, + 'disable': {1: { 0:true }}, + 'getParameter': {1: { 0:true }}, + + // Rendering + + 'drawArrays': {3:{ 0:true }}, + 'drawElements': {4:{ 0:true, 2:true }}, + + // Shaders + + 'createShader': {1: { 0:true }}, + 'getShaderParameter': {2: { 1:true }}, + 'getProgramParameter': {2: { 1:true }}, + 'getShaderPrecisionFormat': {2: { 0: true, 1:true }}, + + // Vertex attributes + + 'getVertexAttrib': {2: { 1:true }}, + 'vertexAttribPointer': {6: { 2:true }}, + + // Textures + + 'bindTexture': {2: { 0:true }}, + 'activeTexture': {1: { 0:true }}, + 'getTexParameter': {2: { 0:true, 1:true }}, + 'texParameterf': {3: { 0:true, 1:true }}, + 'texParameteri': {3: { 0:true, 1:true, 2:true }}, + // texImage2D and texSubImage2D are defined below with WebGL 2 entrypoints + 'copyTexImage2D': {8: { 0:true, 2:true }}, + 'copyTexSubImage2D': {8: { 0:true }}, + 'generateMipmap': {1: { 0:true }}, + // compressedTexImage2D and compressedTexSubImage2D are defined below with WebGL 2 entrypoints + + // Buffer objects + + 'bindBuffer': {2: { 0:true }}, + // bufferData and bufferSubData are defined below with WebGL 2 entrypoints + 'getBufferParameter': {2: { 0:true, 1:true }}, + + // Renderbuffers and framebuffers + + 'pixelStorei': {2: { 0:true, 1:true }}, + // readPixels is defined below with WebGL 2 entrypoints + 'bindRenderbuffer': {2: { 0:true }}, + 'bindFramebuffer': {2: { 0:true }}, + 'checkFramebufferStatus': {1: { 0:true }}, + 'framebufferRenderbuffer': {4: { 0:true, 1:true, 2:true }}, + 'framebufferTexture2D': {5: { 0:true, 1:true, 2:true }}, + 'getFramebufferAttachmentParameter': {3: { 0:true, 1:true, 2:true }}, + 'getRenderbufferParameter': {2: { 0:true, 1:true }}, + 'renderbufferStorage': {4: { 0:true, 1:true }}, + + // Frame buffer operations (clear, blend, depth test, stencil) + + 'clear': {1: { 0: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }}}, + 'depthFunc': {1: { 0:true }}, + 'blendFunc': {2: { 0:true, 1:true }}, + 'blendFuncSeparate': {4: { 0:true, 1:true, 2:true, 3:true }}, + 'blendEquation': {1: { 0:true }}, + 'blendEquationSeparate': {2: { 0:true, 1:true }}, + 'stencilFunc': {3: { 0:true }}, + 'stencilFuncSeparate': {4: { 0:true, 1:true }}, + 'stencilMaskSeparate': {2: { 0:true }}, + 'stencilOp': {3: { 0:true, 1:true, 2:true }}, + 'stencilOpSeparate': {4: { 0:true, 1:true, 2:true, 3:true }}, + + // Culling + + 'cullFace': {1: { 0:true }}, + 'frontFace': {1: { 0:true }}, + + // ANGLE_instanced_arrays extension + + 'drawArraysInstancedANGLE': {4: { 0:true }}, + 'drawElementsInstancedANGLE': {5: { 0:true, 2:true }}, + + // EXT_blend_minmax extension + + 'blendEquationEXT': {1: { 0:true }}, + + // WebGL 2 Buffer objects + + 'bufferData': { + 3: { 0:true, 2:true }, // WebGL 1 + 4: { 0:true, 2:true }, // WebGL 2 + 5: { 0:true, 2:true } // WebGL 2 + }, + 'bufferSubData': { + 3: { 0:true }, // WebGL 1 + 4: { 0:true }, // WebGL 2 + 5: { 0:true } // WebGL 2 + }, + 'copyBufferSubData': {5: { 0:true, 1:true }}, + 'getBufferSubData': {3: { 0:true }, 4: { 0:true }, 5: { 0:true }}, + + // WebGL 2 Framebuffer objects + + 'blitFramebuffer': {10: { 8: { 'enumBitwiseOr': ['COLOR_BUFFER_BIT', 'DEPTH_BUFFER_BIT', 'STENCIL_BUFFER_BIT'] }, 9:true }}, + 'framebufferTextureLayer': {5: { 0:true, 1:true }}, + 'invalidateFramebuffer': {2: { 0:true }}, + 'invalidateSubFramebuffer': {6: { 0:true }}, + 'readBuffer': {1: { 0:true }}, + + // WebGL 2 Renderbuffer objects + + 'getInternalformatParameter': {3: { 0:true, 1:true, 2:true }}, + 'renderbufferStorageMultisample': {5: { 0:true, 2:true }}, + + // WebGL 2 Texture objects + + 'texStorage2D': {5: { 0:true, 2:true }}, + 'texStorage3D': {6: { 0:true, 2:true }}, + 'texImage2D': { + 9: { 0:true, 2:true, 6:true, 7:true }, // WebGL 1 & 2 + 6: { 0:true, 2:true, 3:true, 4:true }, // WebGL 1 + 10: { 0:true, 2:true, 6:true, 7:true } // WebGL 2 + }, + 'texImage3D': { + 10: { 0:true, 2:true, 7:true, 8:true }, + 11: { 0:true, 2:true, 7:true, 8:true } + }, + 'texSubImage2D': { + 9: { 0:true, 6:true, 7:true }, // WebGL 1 & 2 + 7: { 0:true, 4:true, 5:true }, // WebGL 1 + 10: { 0:true, 6:true, 7:true } // WebGL 2 + }, + 'texSubImage3D': { + 11: { 0:true, 8:true, 9:true }, + 12: { 0:true, 8:true, 9:true } + }, + 'copyTexSubImage3D': {9: { 0:true }}, + 'compressedTexImage2D': { + 7: { 0: true, 2:true }, // WebGL 1 & 2 + 8: { 0: true, 2:true }, // WebGL 2 + 9: { 0: true, 2:true } // WebGL 2 + }, + 'compressedTexImage3D': { + 8: { 0: true, 2:true }, + 9: { 0: true, 2:true }, + 10: { 0: true, 2:true } + }, + 'compressedTexSubImage2D': { + 8: { 0: true, 6:true }, // WebGL 1 & 2 + 9: { 0: true, 6:true }, // WebGL 2 + 10: { 0: true, 6:true } // WebGL 2 + }, + 'compressedTexSubImage3D': { + 10: { 0: true, 8:true }, + 11: { 0: true, 8:true }, + 12: { 0: true, 8:true } + }, + + // WebGL 2 Vertex attribs + + 'vertexAttribIPointer': {5: { 2:true }}, + + // WebGL 2 Writing to the drawing buffer + + 'drawArraysInstanced': {4: { 0:true }}, + 'drawElementsInstanced': {5: { 0:true, 2:true }}, + 'drawRangeElements': {6: { 0:true, 4:true }}, + + // WebGL 2 Reading back pixels + + 'readPixels': { + 7: { 4:true, 5:true }, // WebGL 1 & 2 + 8: { 4:true, 5:true } // WebGL 2 + }, + + // WebGL 2 Multiple Render Targets + + 'clearBufferfv': {3: { 0:true }, 4: { 0:true }}, + 'clearBufferiv': {3: { 0:true }, 4: { 0:true }}, + 'clearBufferuiv': {3: { 0:true }, 4: { 0:true }}, + 'clearBufferfi': {4: { 0:true }}, + + // WebGL 2 Query objects + + 'beginQuery': {2: { 0:true }}, + 'endQuery': {1: { 0:true }}, + 'getQuery': {2: { 0:true, 1:true }}, + 'getQueryParameter': {2: { 1:true }}, + + // WebGL 2 Sampler objects + + 'samplerParameteri': {3: { 1:true, 2:true }}, + 'samplerParameterf': {3: { 1:true }}, + 'getSamplerParameter': {2: { 1:true }}, + + // WebGL 2 Sync objects + + 'fenceSync': {2: { 0:true, 1: { 'enumBitwiseOr': [] } }}, + 'clientWaitSync': {3: { 1: { 'enumBitwiseOr': ['SYNC_FLUSH_COMMANDS_BIT'] } }}, + 'waitSync': {3: { 1: { 'enumBitwiseOr': [] } }}, + 'getSyncParameter': {2: { 1:true }}, + + // WebGL 2 Transform Feedback + + 'bindTransformFeedback': {2: { 0:true }}, + 'beginTransformFeedback': {1: { 0:true }}, + 'transformFeedbackVaryings': {3: { 2:true }}, + + // WebGL2 Uniform Buffer Objects and Transform Feedback Buffers + + 'bindBufferBase': {3: { 0:true }}, + 'bindBufferRange': {5: { 0:true }}, + 'getIndexedParameter': {2: { 0:true }}, + 'getActiveUniforms': {3: { 2:true }}, + 'getActiveUniformBlockParameter': {3: { 2:true }} + }; + + /** + * Map of numbers to names. + * @type {Object} + */ + var glEnums = null; + + /** + * Map of names to numbers. + * @type {Object} + */ + var enumStringToValue = null; + + /** + * Initializes this module. Safe to call more than once. + * @param {!WebGLRenderingContext} ctx A WebGL context. If + * you have more than one context it doesn't matter which one + * you pass in, it is only used to pull out constants. + */ + function init(ctx) { + if (glEnums == null) { + glEnums = { }; + enumStringToValue = { }; + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'number') { + glEnums[ctx[propertyName]] = propertyName; + enumStringToValue[propertyName] = ctx[propertyName]; + } + } + } + } + + /** + * Checks the utils have been initialized. + */ + function checkInit() { + if (glEnums == null) { + throw 'WebGLDebugUtils.init(ctx) not called'; + } + } + + /** + * Returns true or false if value matches any WebGL enum + * @param {*} value Value to check if it might be an enum. + * @return {boolean} True if value matches one of the WebGL defined enums + */ + function mightBeEnum(value) { + checkInit(); + return (glEnums[value] !== undefined); + } + + /** + * Gets an string version of an WebGL enum. + * + * Example: + * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); + * + * @param {number} value Value to return an enum for + * @return {string} The string version of the enum. + */ + function glEnumToString(value) { + checkInit(); + var name = glEnums[value]; + return (name !== undefined) ? ("gl." + name) : + ("/*UNKNOWN WebGL ENUM*/ 0x" + value.toString(16) + ""); + } + + /** + * Returns the string version of a WebGL argument. + * Attempts to convert enum arguments to strings. + * @param {string} functionName the name of the WebGL function. + * @param {number} numArgs the number of arguments passed to the function. + * @param {number} argumentIndx the index of the argument. + * @param {*} value The value of the argument. + * @return {string} The value as a string. + */ + function glFunctionArgToString(functionName, numArgs, argumentIndex, value) { + var funcInfo = glValidEnumContexts[functionName]; + if (funcInfo !== undefined) { + var funcInfo = funcInfo[numArgs]; + if (funcInfo !== undefined) { + if (funcInfo[argumentIndex]) { + if (typeof funcInfo[argumentIndex] === 'object' && + funcInfo[argumentIndex]['enumBitwiseOr'] !== undefined) { + var enums = funcInfo[argumentIndex]['enumBitwiseOr']; + var orResult = 0; + var orEnums = []; + for (var i = 0; i < enums.length; ++i) { + var enumValue = enumStringToValue[enums[i]]; + if ((value & enumValue) !== 0) { + orResult |= enumValue; + orEnums.push(glEnumToString(enumValue)); + } + } + if (orResult === value) { + return orEnums.join(' | '); + } else { + return glEnumToString(value); + } + } else { + return glEnumToString(value); + } + } + } + } + if (value === null) { + return "null"; + } else if (value === undefined) { + return "undefined"; + } else { + return value.toString(); + } + } + + /** + * Converts the arguments of a WebGL function to a string. + * Attempts to convert enum arguments to strings. + * + * @param {string} functionName the name of the WebGL function. + * @param {number} args The arguments. + * @return {string} The arguments as a string. + */ + function glFunctionArgsToString(functionName, args) { + // apparently we can't do args.join(","); + var argStr = ""; + var numArgs = args.length; + for (var ii = 0; ii < numArgs; ++ii) { + argStr += ((ii == 0) ? '' : ', ') + + glFunctionArgToString(functionName, numArgs, ii, args[ii]); + } + return argStr; + }; + + + function makePropertyWrapper(wrapper, original, propertyName) { + //log("wrap prop: " + propertyName); + wrapper.__defineGetter__(propertyName, function() { + return original[propertyName]; + }); + // TODO(gmane): this needs to handle properties that take more than + // one value? + wrapper.__defineSetter__(propertyName, function(value) { + //log("set: " + propertyName); + original[propertyName] = value; + }); + } + + // Makes a function that calls a function on another object. + function makeFunctionWrapper(original, functionName) { + //log("wrap fn: " + functionName); + var f = original[functionName]; + return function() { + //log("call: " + functionName); + var result = f.apply(original, arguments); + return result; + }; + } + + /** + * Given a WebGL context returns a wrapped context that calls + * gl.getError after every command and calls a function if the + * result is not gl.NO_ERROR. + * + * @param {!WebGLRenderingContext} ctx The webgl context to + * wrap. + * @param {!function(err, funcName, args): void} opt_onErrorFunc + * The function to call when gl.getError returns an + * error. If not specified the default function calls + * console.log with a message. + * @param {!function(funcName, args): void} opt_onFunc The + * function to call when each webgl function is called. + * You can use this to log all calls for example. + * @param {!WebGLRenderingContext} opt_err_ctx The webgl context + * to call getError on if different than ctx. + */ + function makeDebugContext(ctx, opt_onErrorFunc, opt_onFunc, opt_err_ctx) { + opt_err_ctx = opt_err_ctx || ctx; + init(ctx); + opt_onErrorFunc = opt_onErrorFunc || function(err, functionName, args) { + // apparently we can't do args.join(","); + var argStr = ""; + var numArgs = args.length; + for (var ii = 0; ii < numArgs; ++ii) { + argStr += ((ii == 0) ? '' : ', ') + + glFunctionArgToString(functionName, numArgs, ii, args[ii]); + } + error("WebGL error "+ glEnumToString(err) + " in "+ functionName + + "(" + argStr + ")"); + }; + + // Holds booleans for each GL error so after we get the error ourselves + // we can still return it to the client app. + var glErrorShadow = { }; + + // Makes a function that calls a WebGL function and then calls getError. + function makeErrorWrapper(ctx, functionName) { + return function() { + if (opt_onFunc) { + opt_onFunc(functionName, arguments); + } + var result = ctx[functionName].apply(ctx, arguments); + var err = opt_err_ctx.getError(); + if (err != 0) { + glErrorShadow[err] = true; + opt_onErrorFunc(err, functionName, arguments); + } + return result; + }; + } + + // Make a an object that has a copy of every property of the WebGL context + // but wraps all functions. + var wrapper = {}; + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'function') { + if (propertyName != 'getExtension') { + wrapper[propertyName] = makeErrorWrapper(ctx, propertyName); + } else { + var wrapped = makeErrorWrapper(ctx, propertyName); + wrapper[propertyName] = function () { + var result = wrapped.apply(ctx, arguments); + if (!result) { + return null; + } + return makeDebugContext(result, opt_onErrorFunc, opt_onFunc, opt_err_ctx); + }; + } + } else { + makePropertyWrapper(wrapper, ctx, propertyName); + } + } + + // Override the getError function with one that returns our saved results. + wrapper.getError = function() { + for (var err in glErrorShadow) { + if (glErrorShadow.hasOwnProperty(err)) { + if (glErrorShadow[err]) { + glErrorShadow[err] = false; + return err; + } + } + } + return ctx.NO_ERROR; + }; + + return wrapper; + } + + function resetToInitialState(ctx) { + var isWebGL2RenderingContext = !!ctx.createTransformFeedback; + + if (isWebGL2RenderingContext) { + ctx.bindVertexArray(null); + } + + var numAttribs = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS); + var tmp = ctx.createBuffer(); + ctx.bindBuffer(ctx.ARRAY_BUFFER, tmp); + for (var ii = 0; ii < numAttribs; ++ii) { + ctx.disableVertexAttribArray(ii); + ctx.vertexAttribPointer(ii, 4, ctx.FLOAT, false, 0, 0); + ctx.vertexAttrib1f(ii, 0); + if (isWebGL2RenderingContext) { + ctx.vertexAttribDivisor(ii, 0); + } + } + ctx.deleteBuffer(tmp); + + var numTextureUnits = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS); + for (var ii = 0; ii < numTextureUnits; ++ii) { + ctx.activeTexture(ctx.TEXTURE0 + ii); + ctx.bindTexture(ctx.TEXTURE_CUBE_MAP, null); + ctx.bindTexture(ctx.TEXTURE_2D, null); + if (isWebGL2RenderingContext) { + ctx.bindTexture(ctx.TEXTURE_2D_ARRAY, null); + ctx.bindTexture(ctx.TEXTURE_3D, null); + ctx.bindSampler(ii, null); + } + } + + ctx.activeTexture(ctx.TEXTURE0); + ctx.useProgram(null); + ctx.bindBuffer(ctx.ARRAY_BUFFER, null); + ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, null); + ctx.bindFramebuffer(ctx.FRAMEBUFFER, null); + ctx.bindRenderbuffer(ctx.RENDERBUFFER, null); + ctx.disable(ctx.BLEND); + ctx.disable(ctx.CULL_FACE); + ctx.disable(ctx.DEPTH_TEST); + ctx.disable(ctx.DITHER); + ctx.disable(ctx.SCISSOR_TEST); + ctx.blendColor(0, 0, 0, 0); + ctx.blendEquation(ctx.FUNC_ADD); + ctx.blendFunc(ctx.ONE, ctx.ZERO); + ctx.clearColor(0, 0, 0, 0); + ctx.clearDepth(1); + ctx.clearStencil(-1); + ctx.colorMask(true, true, true, true); + ctx.cullFace(ctx.BACK); + ctx.depthFunc(ctx.LESS); + ctx.depthMask(true); + ctx.depthRange(0, 1); + ctx.frontFace(ctx.CCW); + ctx.hint(ctx.GENERATE_MIPMAP_HINT, ctx.DONT_CARE); + ctx.lineWidth(1); + ctx.pixelStorei(ctx.PACK_ALIGNMENT, 4); + ctx.pixelStorei(ctx.UNPACK_ALIGNMENT, 4); + ctx.pixelStorei(ctx.UNPACK_FLIP_Y_WEBGL, false); + ctx.pixelStorei(ctx.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + // TODO: Delete this IF. + if (ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL) { + ctx.pixelStorei(ctx.UNPACK_COLORSPACE_CONVERSION_WEBGL, ctx.BROWSER_DEFAULT_WEBGL); + } + ctx.polygonOffset(0, 0); + ctx.sampleCoverage(1, false); + ctx.scissor(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.stencilFunc(ctx.ALWAYS, 0, 0xFFFFFFFF); + ctx.stencilMask(0xFFFFFFFF); + ctx.stencilOp(ctx.KEEP, ctx.KEEP, ctx.KEEP); + ctx.viewport(0, 0, ctx.canvas.width, ctx.canvas.height); + ctx.clear(ctx.COLOR_BUFFER_BIT | ctx.DEPTH_BUFFER_BIT | ctx.STENCIL_BUFFER_BIT); + + if (isWebGL2RenderingContext) { + ctx.drawBuffers([ctx.BACK]); + ctx.readBuffer(ctx.BACK); + ctx.bindBuffer(ctx.COPY_READ_BUFFER, null); + ctx.bindBuffer(ctx.COPY_WRITE_BUFFER, null); + ctx.bindBuffer(ctx.PIXEL_PACK_BUFFER, null); + ctx.bindBuffer(ctx.PIXEL_UNPACK_BUFFER, null); + var numTransformFeedbacks = ctx.getParameter(ctx.MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS); + for (var ii = 0; ii < numTransformFeedbacks; ++ii) { + ctx.bindBufferBase(ctx.TRANSFORM_FEEDBACK_BUFFER, ii, null); + } + var numUBOs = ctx.getParameter(ctx.MAX_UNIFORM_BUFFER_BINDINGS); + for (var ii = 0; ii < numUBOs; ++ii) { + ctx.bindBufferBase(ctx.UNIFORM_BUFFER, ii, null); + } + ctx.disable(ctx.RASTERIZER_DISCARD); + ctx.pixelStorei(ctx.UNPACK_IMAGE_HEIGHT, 0); + ctx.pixelStorei(ctx.UNPACK_SKIP_IMAGES, 0); + ctx.pixelStorei(ctx.UNPACK_ROW_LENGTH, 0); + ctx.pixelStorei(ctx.UNPACK_SKIP_ROWS, 0); + ctx.pixelStorei(ctx.UNPACK_SKIP_PIXELS, 0); + ctx.pixelStorei(ctx.PACK_ROW_LENGTH, 0); + ctx.pixelStorei(ctx.PACK_SKIP_ROWS, 0); + ctx.pixelStorei(ctx.PACK_SKIP_PIXELS, 0); + ctx.hint(ctx.FRAGMENT_SHADER_DERIVATIVE_HINT, ctx.DONT_CARE); + } + + // TODO: This should NOT be needed but Firefox fails with 'hint' + while(ctx.getError()); + } + + function makeLostContextSimulatingCanvas(canvas) { + var unwrappedContext_; + var wrappedContext_; + var onLost_ = []; + var onRestored_ = []; + var wrappedContext_ = {}; + var contextId_ = 1; + var contextLost_ = false; + var resourceId_ = 0; + var resourceDb_ = []; + var numCallsToLoseContext_ = 0; + var numCalls_ = 0; + var canRestore_ = false; + var restoreTimeout_ = 0; + var isWebGL2RenderingContext; + + // Holds booleans for each GL error so can simulate errors. + var glErrorShadow_ = { }; + + canvas.getContext = function(f) { + return function() { + var ctx = f.apply(canvas, arguments); + // Did we get a context and is it a WebGL context? + if ((ctx instanceof WebGLRenderingContext) || (window.WebGL2RenderingContext && (ctx instanceof WebGL2RenderingContext))) { + if (ctx != unwrappedContext_) { + if (unwrappedContext_) { + throw "got different context" + } + isWebGL2RenderingContext = window.WebGL2RenderingContext && (ctx instanceof WebGL2RenderingContext); + unwrappedContext_ = ctx; + wrappedContext_ = makeLostContextSimulatingContext(unwrappedContext_); + } + return wrappedContext_; + } + return ctx; + } + }(canvas.getContext); + + function wrapEvent(listener) { + if (typeof(listener) == "function") { + return listener; + } else { + return function(info) { + listener.handleEvent(info); + } + } + } + + var addOnContextLostListener = function(listener) { + onLost_.push(wrapEvent(listener)); + }; + + var addOnContextRestoredListener = function(listener) { + onRestored_.push(wrapEvent(listener)); + }; + + + function wrapAddEventListener(canvas) { + var f = canvas.addEventListener; + canvas.addEventListener = function(type, listener, bubble) { + switch (type) { + case 'webglcontextlost': + addOnContextLostListener(listener); + break; + case 'webglcontextrestored': + addOnContextRestoredListener(listener); + break; + default: + f.apply(canvas, arguments); + } + }; + } + + wrapAddEventListener(canvas); + + canvas.loseContext = function() { + if (!contextLost_) { + contextLost_ = true; + numCallsToLoseContext_ = 0; + ++contextId_; + while (unwrappedContext_.getError()); + clearErrors(); + glErrorShadow_[unwrappedContext_.CONTEXT_LOST_WEBGL] = true; + var event = makeWebGLContextEvent("context lost"); + var callbacks = onLost_.slice(); + setTimeout(function() { + //log("numCallbacks:" + callbacks.length); + for (var ii = 0; ii < callbacks.length; ++ii) { + //log("calling callback:" + ii); + callbacks[ii](event); + } + if (restoreTimeout_ >= 0) { + setTimeout(function() { + canvas.restoreContext(); + }, restoreTimeout_); + } + }, 0); + } + }; + + canvas.restoreContext = function() { + if (contextLost_) { + if (onRestored_.length) { + setTimeout(function() { + if (!canRestore_) { + throw "can not restore. webglcontestlost listener did not call event.preventDefault"; + } + freeResources(); + resetToInitialState(unwrappedContext_); + contextLost_ = false; + numCalls_ = 0; + canRestore_ = false; + var callbacks = onRestored_.slice(); + var event = makeWebGLContextEvent("context restored"); + for (var ii = 0; ii < callbacks.length; ++ii) { + callbacks[ii](event); + } + }, 0); + } + } + }; + + canvas.loseContextInNCalls = function(numCalls) { + if (contextLost_) { + throw "You can not ask a lost contet to be lost"; + } + numCallsToLoseContext_ = numCalls_ + numCalls; + }; + + canvas.getNumCalls = function() { + return numCalls_; + }; + + canvas.setRestoreTimeout = function(timeout) { + restoreTimeout_ = timeout; + }; + + function isWebGLObject(obj) { + //return false; + return (obj instanceof WebGLBuffer || + obj instanceof WebGLFramebuffer || + obj instanceof WebGLProgram || + obj instanceof WebGLRenderbuffer || + obj instanceof WebGLShader || + obj instanceof WebGLTexture); + } + + function checkResources(args) { + for (var ii = 0; ii < args.length; ++ii) { + var arg = args[ii]; + if (isWebGLObject(arg)) { + return arg.__webglDebugContextLostId__ == contextId_; + } + } + return true; + } + + function clearErrors() { + var k = Object.keys(glErrorShadow_); + for (var ii = 0; ii < k.length; ++ii) { + delete glErrorShadow_[k[ii]]; + } + } + + function loseContextIfTime() { + ++numCalls_; + if (!contextLost_) { + if (numCallsToLoseContext_ == numCalls_) { + canvas.loseContext(); + } + } + } + + // Makes a function that simulates WebGL when out of context. + function makeLostContextFunctionWrapper(ctx, functionName) { + var f = ctx[functionName]; + return function() { + // log("calling:" + functionName); + // Only call the functions if the context is not lost. + loseContextIfTime(); + if (!contextLost_) { + //if (!checkResources(arguments)) { + // glErrorShadow_[wrappedContext_.INVALID_OPERATION] = true; + // return; + //} + var result = f.apply(ctx, arguments); + return result; + } + }; + } + + function freeResources() { + for (var ii = 0; ii < resourceDb_.length; ++ii) { + var resource = resourceDb_[ii]; + if (resource instanceof WebGLBuffer) { + unwrappedContext_.deleteBuffer(resource); + } else if (resource instanceof WebGLFramebuffer) { + unwrappedContext_.deleteFramebuffer(resource); + } else if (resource instanceof WebGLProgram) { + unwrappedContext_.deleteProgram(resource); + } else if (resource instanceof WebGLRenderbuffer) { + unwrappedContext_.deleteRenderbuffer(resource); + } else if (resource instanceof WebGLShader) { + unwrappedContext_.deleteShader(resource); + } else if (resource instanceof WebGLTexture) { + unwrappedContext_.deleteTexture(resource); + } + else if (isWebGL2RenderingContext) { + if (resource instanceof WebGLQuery) { + unwrappedContext_.deleteQuery(resource); + } else if (resource instanceof WebGLSampler) { + unwrappedContext_.deleteSampler(resource); + } else if (resource instanceof WebGLSync) { + unwrappedContext_.deleteSync(resource); + } else if (resource instanceof WebGLTransformFeedback) { + unwrappedContext_.deleteTransformFeedback(resource); + } else if (resource instanceof WebGLVertexArrayObject) { + unwrappedContext_.deleteVertexArray(resource); + } + } + } + } + + function makeWebGLContextEvent(statusMessage) { + return { + statusMessage: statusMessage, + preventDefault: function() { + canRestore_ = true; + } + }; + } + + return canvas; + + function makeLostContextSimulatingContext(ctx) { + // copy all functions and properties to wrapper + for (var propertyName in ctx) { + if (typeof ctx[propertyName] == 'function') { + wrappedContext_[propertyName] = makeLostContextFunctionWrapper( + ctx, propertyName); + } else { + makePropertyWrapper(wrappedContext_, ctx, propertyName); + } + } + + // Wrap a few functions specially. + wrappedContext_.getError = function() { + loseContextIfTime(); + if (!contextLost_) { + var err; + while (err = unwrappedContext_.getError()) { + glErrorShadow_[err] = true; + } + } + for (var err in glErrorShadow_) { + if (glErrorShadow_[err]) { + delete glErrorShadow_[err]; + return err; + } + } + return wrappedContext_.NO_ERROR; + }; + + var creationFunctions = [ + "createBuffer", + "createFramebuffer", + "createProgram", + "createRenderbuffer", + "createShader", + "createTexture" + ]; + if (isWebGL2RenderingContext) { + creationFunctions.push( + "createQuery", + "createSampler", + "fenceSync", + "createTransformFeedback", + "createVertexArray" + ); + } + for (var ii = 0; ii < creationFunctions.length; ++ii) { + var functionName = creationFunctions[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return null; + } + var obj = f.apply(ctx, arguments); + obj.__webglDebugContextLostId__ = contextId_; + resourceDb_.push(obj); + return obj; + }; + }(ctx[functionName]); + } + + var functionsThatShouldReturnNull = [ + "getActiveAttrib", + "getActiveUniform", + "getBufferParameter", + "getContextAttributes", + "getAttachedShaders", + "getFramebufferAttachmentParameter", + "getParameter", + "getProgramParameter", + "getProgramInfoLog", + "getRenderbufferParameter", + "getShaderParameter", + "getShaderInfoLog", + "getShaderSource", + "getTexParameter", + "getUniform", + "getUniformLocation", + "getVertexAttrib" + ]; + if (isWebGL2RenderingContext) { + functionsThatShouldReturnNull.push( + "getInternalformatParameter", + "getQuery", + "getQueryParameter", + "getSamplerParameter", + "getSyncParameter", + "getTransformFeedbackVarying", + "getIndexedParameter", + "getUniformIndices", + "getActiveUniforms", + "getActiveUniformBlockParameter", + "getActiveUniformBlockName" + ); + } + for (var ii = 0; ii < functionsThatShouldReturnNull.length; ++ii) { + var functionName = functionsThatShouldReturnNull[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return null; + } + return f.apply(ctx, arguments); + } + }(wrappedContext_[functionName]); + } + + var isFunctions = [ + "isBuffer", + "isEnabled", + "isFramebuffer", + "isProgram", + "isRenderbuffer", + "isShader", + "isTexture" + ]; + if (isWebGL2RenderingContext) { + isFunctions.push( + "isQuery", + "isSampler", + "isSync", + "isTransformFeedback", + "isVertexArray" + ); + } + for (var ii = 0; ii < isFunctions.length; ++ii) { + var functionName = isFunctions[ii]; + wrappedContext_[functionName] = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return false; + } + return f.apply(ctx, arguments); + } + }(wrappedContext_[functionName]); + } + + wrappedContext_.checkFramebufferStatus = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return wrappedContext_.FRAMEBUFFER_UNSUPPORTED; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.checkFramebufferStatus); + + wrappedContext_.getAttribLocation = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return -1; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getAttribLocation); + + wrappedContext_.getVertexAttribOffset = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return 0; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getVertexAttribOffset); + + wrappedContext_.isContextLost = function() { + return contextLost_; + }; + + if (isWebGL2RenderingContext) { + wrappedContext_.getFragDataLocation = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return -1; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getFragDataLocation); + + wrappedContext_.clientWaitSync = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return wrappedContext_.WAIT_FAILED; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.clientWaitSync); + + wrappedContext_.getUniformBlockIndex = function(f) { + return function() { + loseContextIfTime(); + if (contextLost_) { + return wrappedContext_.INVALID_INDEX; + } + return f.apply(ctx, arguments); + }; + }(wrappedContext_.getUniformBlockIndex); + } + + return wrappedContext_; + } + } + + return { + /** + * Initializes this module. Safe to call more than once. + * @param {!WebGLRenderingContext} ctx A WebGL context. If + * you have more than one context it doesn't matter which one + * you pass in, it is only used to pull out constants. + */ + 'init': init, + + /** + * Returns true or false if value matches any WebGL enum + * @param {*} value Value to check if it might be an enum. + * @return {boolean} True if value matches one of the WebGL defined enums + */ + 'mightBeEnum': mightBeEnum, + + /** + * Gets an string version of an WebGL enum. + * + * Example: + * WebGLDebugUtil.init(ctx); + * var str = WebGLDebugUtil.glEnumToString(ctx.getError()); + * + * @param {number} value Value to return an enum for + * @return {string} The string version of the enum. + */ + 'glEnumToString': glEnumToString, + + /** + * Converts the argument of a WebGL function to a string. + * Attempts to convert enum arguments to strings. + * + * Example: + * WebGLDebugUtil.init(ctx); + * var str = WebGLDebugUtil.glFunctionArgToString('bindTexture', 2, 0, gl.TEXTURE_2D); + * + * would return 'TEXTURE_2D' + * + * @param {string} functionName the name of the WebGL function. + * @param {number} numArgs The number of arguments + * @param {number} argumentIndx the index of the argument. + * @param {*} value The value of the argument. + * @return {string} The value as a string. + */ + 'glFunctionArgToString': glFunctionArgToString, + + /** + * Converts the arguments of a WebGL function to a string. + * Attempts to convert enum arguments to strings. + * + * @param {string} functionName the name of the WebGL function. + * @param {number} args The arguments. + * @return {string} The arguments as a string. + */ + 'glFunctionArgsToString': glFunctionArgsToString, + + /** + * Given a WebGL context returns a wrapped context that calls + * gl.getError after every command and calls a function if the + * result is not NO_ERROR. + * + * You can supply your own function if you want. For example, if you'd like + * an exception thrown on any GL error you could do this + * + * function throwOnGLError(err, funcName, args) { + * throw WebGLDebugUtils.glEnumToString(err) + + * " was caused by call to " + funcName; + * }; + * + * ctx = WebGLDebugUtils.makeDebugContext( + * canvas.getContext("webgl"), throwOnGLError); + * + * @param {!WebGLRenderingContext} ctx The webgl context to wrap. + * @param {!function(err, funcName, args): void} opt_onErrorFunc The function + * to call when gl.getError returns an error. If not specified the default + * function calls console.log with a message. + * @param {!function(funcName, args): void} opt_onFunc The + * function to call when each webgl function is called. You + * can use this to log all calls for example. + */ + 'makeDebugContext': makeDebugContext, + + /** + * Given a canvas element returns a wrapped canvas element that will + * simulate lost context. The canvas returned adds the following functions. + * + * loseContext: + * simulates a lost context event. + * + * restoreContext: + * simulates the context being restored. + * + * lostContextInNCalls: + * loses the context after N gl calls. + * + * getNumCalls: + * tells you how many gl calls there have been so far. + * + * setRestoreTimeout: + * sets the number of milliseconds until the context is restored + * after it has been lost. Defaults to 0. Pass -1 to prevent + * automatic restoring. + * + * @param {!Canvas} canvas The canvas element to wrap. + */ + 'makeLostContextSimulatingCanvas': makeLostContextSimulatingCanvas, + + /** + * Resets a context to the initial state. + * @param {!WebGLRenderingContext} ctx The webgl context to + * reset. + */ + 'resetToInitialState': resetToInitialState + }; + + }(); + \ No newline at end of file diff --git a/client/public/index.html b/client/public/index.html index 080bf8e..6620ebc 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -9,8 +9,9 @@ - - + + + @@ -33,7 +34,19 @@ - + + + diff --git a/client/public/index.mjs b/client/public/index.mjs index b7d7e8a..60e963b 100644 --- a/client/public/index.mjs +++ b/client/public/index.mjs @@ -3,8 +3,10 @@ import { RendererPreInit, BrickRenderer } from './brick-renderer/index.mjs'; async function main() { await RendererPreInit(); - const canvas = document.querySelector('#webglviewer'); - const Renderer = new BrickRenderer(canvas); + const canvas = document.querySelectorAll('#webglviewer'); + for (let i = 0; i < canvas.length; i++) { + const Renderer = new BrickRenderer(canvas[i]); + } } window.onload = main; diff --git a/client/public/res/lego_stud.obj b/client/public/res/lego_stud.obj index d03eda8..91b8d81 100644 --- a/client/public/res/lego_stud.obj +++ b/client/public/res/lego_stud.obj @@ -1,6 +1,5 @@ # Blender v3.0.1 OBJ File: '' # www.blender.org -mtllib lego_stud.mtl o Cylinder.001 v 0.138972 0.159959 -0.074116 v 0.151918 0.159959 -0.074119 @@ -18,7 +17,7 @@ v 0.102694 0.159959 -0.056218 v 0.136048 0.159959 -0.058725 v 0.131358 0.159959 -0.058326 v 0.140280 0.159959 -0.058326 -v 0.143815 0.159959 -0.057156 +v 0.143815 0.159959 -0.057156 v 0.126426 0.159959 -0.057156 v 0.146648 0.159959 -0.055259 v 0.121309 0.159959 -0.055259 diff --git a/docs/API.md b/docs/API.md index 96c7d1d..52acd66 100644 --- a/docs/API.md +++ b/docs/API.md @@ -23,6 +23,7 @@ automatically every request | PUT | /api/auth/basket/:id | quantity | yes | | | POST | /api/auth/basket/:id | | yes | manipulate basket content | | DEL | /api/auth/basket/:id | quantity | yes | if no id, delete whole | +| DEL | /api/auth/basket/ | | yes | if no id, delete whole | ## Query structure diff --git a/src/database/database.js b/src/database/database.js index 20b058e..19039d7 100644 --- a/src/database/database.js +++ b/src/database/database.js @@ -59,5 +59,5 @@ class Database { module.exports = { IDatabase: Database, DataTypes: require('./psql-entity-framework/types.js'), - DataConstraints: require('./psql-entity-framework/relationships_constraints.js'), + DataConstraints: require('./psql-entity-framework/relationships-constraints.js'), }; diff --git a/src/database/psql-entity-framework/entity-relationships.js b/src/database/psql-entity-framework/entity-relationships.js index 8258756..6423b96 100644 --- a/src/database/psql-entity-framework/entity-relationships.js +++ b/src/database/psql-entity-framework/entity-relationships.js @@ -1,6 +1,6 @@ const Logger = require('../../logger.js'); const DataTypes = require('./types.js'); -const DataConstraints = require('./relationships_constraints.js'); +const DataConstraints = require('./relationships-constraints.js'); const Model = require('./model.js'); /** diff --git a/src/database/psql-entity-framework/relationships_constraints.js b/src/database/psql-entity-framework/relationships-constraints.js similarity index 100% rename from src/database/psql-entity-framework/relationships_constraints.js rename to src/database/psql-entity-framework/relationships-constraints.js diff --git a/src/index.js b/src/index.js index 5d60d43..0f8eb1b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ const Logger = require('./logger.js'); const Config = require('./config.js'); const Server = require('./routes/server.js'); +const API = require('./routes/api.js'); const Databse = require('./database/database.js'); const ModelManager = require('./models/model-manager.js'); @@ -16,13 +17,14 @@ async function main() { }); Logger.Info('Pre-Init Complete'); - const Database = new Databse.IDatabase(); - await Database.connect(); + // const Database = new Databse.IDatabase(); + // await Database.connect(); - ModelManager.Init(Database); - await Database.ORMReady(); + // ModelManager.Init(Database); + // await Database.ORMReady(); Server.Listen(process.env.PORT); + API.Init(); } main(); diff --git a/src/logger.js b/src/logger.js index c58c8e6..eb629df 100644 --- a/src/logger.js +++ b/src/logger.js @@ -53,7 +53,7 @@ const Warn = (...messages) => internalLog('WARN', messages.join(' '), clc.yellow const Error = (...messages) => internalLog('ERROR', messages.join(' '), clc.redBright, LEVEL_STICK); const Panic = (...messages) => internalLog('PANIC', messages.join(' '), clc.bgRedBright, LEVEL_STICK); const Debug = (...messages) => internalLog('DEBUG', messages.join(' '), clc.cyanBright, LEVEL_DEBUG); -const Module = (module, ...messages) => internalLog('MODULE', `[${module}] ${messages.join(' ')}`, clc.blue, LEVEL_INFO); +const Module = (module, ...messages) => internalLog(`MODULE [${module}]`, ` ${messages.join(' ')}`, clc.blue, LEVEL_INFO); const Database = (...messages) => internalLog('PSQL', `[DB] ${messages.join(' ')}`, clc.blue, LEVEL_INFO); const ExpressLogger = (req, res, next) => { internalLog('HTTP', `[${req.method}] ${req.originalUrl} FROM ${req.headers['x-forwarded-for'] || req.socket.remoteAddress}`, clc.magenta, LEVEL_VERBOSE); diff --git a/src/models/catagory.js b/src/models/catagory.js index 5426c56..89e04d8 100644 --- a/src/models/catagory.js +++ b/src/models/catagory.js @@ -2,7 +2,7 @@ const Models = require('./model-manager.js'); const { DataTypes, DataConstraints } = require('../database/database.js'); const { ORM } = Models.Database; -function init() { +function Init() { ORM.addModel('catagory', { id: { type: DataTypes.INTEGER, @@ -13,5 +13,5 @@ function init() { } module.exports = { - Init: init, + Init, }; diff --git a/src/models/lego-brick.js b/src/models/lego-brick.js index 57717d2..b6bef3e 100644 --- a/src/models/lego-brick.js +++ b/src/models/lego-brick.js @@ -2,7 +2,7 @@ const Models = require('./model-manager.js'); const { DataTypes, DataConstraints } = require('../database/database.js'); const { ORM } = Models.Database; -function init() { +function Init() { ORM.addModel('lego_brick', { id: { type: DataTypes.VARCHAR(50), @@ -20,5 +20,5 @@ function init() { } module.exports = { - Init: init, + Init, }; diff --git a/src/models/model-manager.js b/src/models/model-manager.js index cbda260..19df1d4 100644 --- a/src/models/model-manager.js +++ b/src/models/model-manager.js @@ -1,6 +1,6 @@ const fs = require('fs'); -function init(databaseInstance) { +function Init(databaseInstance) { module.exports.Database = databaseInstance; module.exports.Models = {}; @@ -15,5 +15,5 @@ function init(databaseInstance) { } module.exports = { - Init: init, + Init, }; diff --git a/src/routes/api.js b/src/routes/api.js index 52c08a2..ca4da57 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -1,5 +1,34 @@ -const Server = require('/routes/server.js'); +const Logger = require('../logger.js'); +const Server = require('./server.js'); + +const Bricks = require('./bricks-router.js'); +const Sets = require('./sets-router.js'); +const Query = require('./query-router.js'); +const Auth0 = require('./auth0-router.js'); + +function Init() { + Server.App.get('/api/search/', []); + Server.App.get('/api/bricks/'); + Server.App.get('/api/sets/'); + Server.App.get('/api/brick/:id/'); + Server.App.get('/api/set/:id/'); + + Server.App.get('/api/cdn/:id/'); + + Server.App.put('/api/auth/login/'); + Server.App.post('/api/auth/signup/'); + Server.App.get('/api/auth/orders/'); + Server.App.get('/api/auth/order/:id/'); + + Server.App.get('/api/auth/basket/'); + Server.App.put('/api/auth/basket/:id/'); + Server.App.post('/api/auth/basket/:id/'); + Server.App.delete('/api/auth/basket/:id/'); + Server.App.delete('/api/auth/basket/'); + + Logger.Module('API', 'API Routes Initialized'); +} module.exports = { - + Init, }; diff --git a/src/routes/auth0-router.js b/src/routes/auth0-router.js new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/bricks-router.js b/src/routes/bricks-router.js new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/query-router.js b/src/routes/query-router.js new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/sets-router.js b/src/routes/sets-router.js new file mode 100644 index 0000000..e69de29