288 lines
7.3 KiB
JavaScript
288 lines
7.3 KiB
JavaScript
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 }
|