import Resource from '../resource' import { Texture, Material } from './material' import { mat4 } from 'gl-matrix' let meshCache = {} class Node { constructor (pos, scale, rotation) { this.pos = pos || [0.0, 0.0, 0.0] this.scale = scale || [1.0, 1.0, 1.0] this.rotation = rotation || [0.0, 0.0, 0.0] this.transform = mat4.create() this.updateTransform() this.parent = null this.children = [] } updateTransform () { let matrix = mat4.create() // Set translation mat4.translate(matrix, matrix, this.pos) // Set scale mat4.scale(matrix, matrix, this.scale) // Set rotation if (this.rotation[0] !== 0) { mat4.rotateX(matrix, matrix, this.rotation[0]) } if (this.rotation[1] !== 0) { mat4.rotateY(matrix, matrix, this.rotation[1]) } if (this.rotation[2] !== 0) { mat4.rotateZ(matrix, matrix, this.rotation[2]) } // Add parent node's transform if (this.parent) { mat4.add(matrix, this.parent.transform, matrix) } // Set the matrix this.transform = matrix // Update children's transforms for (let i in this.children) { let child = this.children[i] if (!(child instanceof Node)) continue child.updateTransform() } } // Setters setPosition (newPos) { this.pos = newPos this.updateTransform() } setScale (newScale) { this.scale = newScale this.updateTransform() } setRotation (newRotation) { this.rotation = newRotation this.updateTransform() } // Transforms translate (pos) { mat4.translate(this.transform, this.transform, pos) } scale (scale) { mat4.scale(this.transform, this.transform, scale) } rotate (rot) { if (rot[0] !== 0) { mat4.rotateX(this.transform, this.transform, rot[0]) } if (rot[1] !== 0) { mat4.rotateY(this.transform, this.transform, rot[1]) } if (rot[2] !== 0) { mat4.rotateZ(this.transform, this.transform, rot[2]) } } // Getters get position () { return this.pos } // Draw base draw (gl, shader) { // Set model transform matrix uniform const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix') gl.uniformMatrix4fv(transformLocation, false, this.transform) } addChild (ch) { if (!(ch instanceof Node)) return this.children.push(ch) this.updateTransform() } setParent (p) { if (!(p instanceof Node)) return this.parent = p this.updateTransform() } } class Mesh extends Node { static construct (gl, vertices, indices, uvs, normals) { // VBO for model vertices let pos = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, pos) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW) // Indices Buffer let ebo = gl.createBuffer() gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo) gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW) let mesh = new Mesh() mesh.pos = pos mesh.ebo = ebo mesh.indices = indices.length // VBO for model UVs if (uvs) { let uv = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, uv) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(uvs), gl.STATIC_DRAW) mesh.uvs = uv } // Normals buffer if (normals) { let nms = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, nms) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW) mesh.nms = nms } gl.bindBuffer(gl.ARRAY_BUFFER, null) return mesh } static async loadFile (gl, file) { file = '/assets/models/' + file + '.json' // Ensure each mesh file is loaded only once if (meshCache[file]) return meshCache[file].length > 1 ? meshCache[file] : meshCache[file][0] let dat = await Resource.GET({ type: 'json', url: file }) if (!dat.meshes) throw new Error('No meshes defined in file.') let cleaned = [] let materials = [] for (let mi in dat.meshes) { let mesh = dat.meshes[mi] let material if (mesh.materialindex != null && dat.materials && dat.materials.length) { // Ensure we don't re-create materials with the same index if (materials[mesh.materialindex]) { material = materials[mesh.materialindex] } else { // Load a new material material = new Material() let matdata = dat.materials[mesh.materialindex].properties // Parse material information for (let pi in matdata) { let property = matdata[pi] if (!property || !property.key) continue if (property.key === '?mat.name') material.name = property.value else if (property.key.indexOf('$clr.') === 0) { let dproperty = property.key.substr(5) switch (dproperty) { case 'specular': case 'diffuse': case 'shininess': case 'ambient': case 'reflective': material[dproperty] = property.value break } } else if (property.key.indexOf('$tex.file') === 0) { if (!material.textures) { material.textures = [] } material.textures.push(property.value) } } materials[mesh.materialindex] = material } } cleaned.push({ vertices: mesh.vertices, indices: [].concat.apply([], mesh.faces), uv: mesh.texturecoords ? mesh.texturecoords[0] : null, normals: mesh.normals ? mesh.normals : null, material }) } let finished = [] for (let i in cleaned) { let meshdata = cleaned[i] let mesh = Mesh.construct(gl, meshdata.vertices, meshdata.indices, meshdata.uv, meshdata.normals) // Initialize the material's texture if present if (meshdata.material) { mesh.material = meshdata.material // Ensure all textures get loaded before finishing if (meshdata.material.textures) { await meshdata.material.loadTextures(gl) } } finished.push(mesh) } // Cache the mesh meshCache[file] = finished return finished.length > 1 ? finished : finished[0] } bindBuffers (gl, shader) { gl.bindBuffer(gl.ARRAY_BUFFER, this.pos) shader.setAttribute(gl, 'aVertexPosition', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0) if (this.nms && shader.hasAttribute(gl, 'aNormal')) { gl.bindBuffer(gl.ARRAY_BUFFER, this.nms) shader.setAttribute(gl, 'aNormal', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0) } if (this.uvs && shader.hasAttribute(gl, 'aTexCoords')) { gl.bindBuffer(gl.ARRAY_BUFFER, this.uvs) shader.setAttribute(gl, 'aTexCoords', 2, false, 2 * Float32Array.BYTES_PER_ELEMENT, 0) } gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ebo) } draw (gl, shader) { // Set model transform uniform super.draw(gl, shader) // Bind attrib arrays this.bindBuffers(gl, shader) // Give materials to shader if (this.material) { this.material.apply(gl, shader) } gl.drawElements(gl.TRIANGLES, this.indices, gl.UNSIGNED_SHORT, 0) // Invoke children's draw methods for (let i in this.children) { let child = this.children[i] if (!(child instanceof Mesh)) continue child.draw(gl, shader) } } } export { Node, Mesh, Texture, Material }