3dexperiments/src/engine/components/index.js

225 lines
4.9 KiB
JavaScript

import { mat4, quat, vec3 } from 'gl-matrix'
/**
* Returns an euler angle representation of a quaternion
* @param {vec3} out Euler angles, pitch-yaw-roll
* @param {quat} mat Quaternion
* @return {vec3} out
*/
quat.getEuler = function (out, quat) {
let x = quat[0]
let y = quat[1]
let z = quat[2]
let w = quat[3]
let x2 = x * x
let y2 = y * y
let z2 = z * z
let w2 = w * w
let unit = x2 + y2 + z2 + w2
let test = x * w - y * z
if (test > 0.499995 * unit) {
// singularity at the north pole
out[0] = Math.PI / 2
out[1] = 2 * Math.atan2(y, x)
out[2] = 0
} else if (test < -0.499995 * unit) {
// singularity at the south pole
out[0] = -Math.PI / 2
out[1] = 2 * Math.atan2(y, x)
out[2] = 0
} else {
out[0] = Math.asin(2 * (x * z - w * y))
out[1] = Math.atan2(2 * (x * w + y * z), 1 - 2 * (z2 + w2))
out[2] = Math.atan2(2 * (x * y + z * w), 1 - 2 * (y2 + z2))
}
return out
}
class Node {
constructor (pos, scale, rotation) {
// Translation
this.pos = pos || [0.0, 0.0, 0.0]
// Scaling
this.scale = scale || [1.0, 1.0, 1.0]
// Rotation in Euler angles (pitch, yaw, roll) in degrees
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()
let rot = quat.create()
// Set rotation
quat.fromEuler(rot, this.rotation[0], this.rotation[1], this.rotation[2])
mat4.fromRotationTranslationScale(matrix, rot, this.pos, this.scale)
// Add local transform to the global transform, if present
// Will be present in loaded models
if (this._rootTransform) {
mat4.mul(matrix, matrix, this._rootTransform)
}
// Add parent's transform to this
if (this.parent) {
mat4.mul(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()
}
}
fromLocalTransform (transform) {
let quaternion = quat.create()
let translation = vec3.create()
let rotation = vec3.create()
let scale = vec3.create()
mat4.transpose(transform, transform)
mat4.getScaling(scale, transform)
mat4.getRotation(quaternion, transform)
mat4.getTranslation(translation, transform)
quat.getEuler(rotation, quaternion)
this.rotation = rotation
this.pos = translation
this.scale = scale
this.updateTransform()
}
// Setters
setPosition (newPos) {
this.pos = newPos
this.updateTransform()
}
setTranslation (newTrans) {
this.setPosition(newTrans)
}
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
}
get translation () {
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()
}
}
// A node defining a single mesh
class MeshInstance extends Node {
constructor (mesh, pos, scale, rot) {
super(pos, scale, rot)
this.mesh = mesh
}
draw (gl, shader) {
// Set model transform uniform
super.draw(gl, shader)
// Draw the mesh
this.mesh.prepare(gl, shader)
this.mesh.draw(gl, shader)
// Invoke children's draw methods
for (let i in this.children) {
let child = this.children[i]
if (!(child instanceof MeshInstance)) continue
child.draw(gl, shader)
}
}
}
// A node that contains multiple meshes
class MultiMeshInstance extends Node {
constructor (meshes, pos) {
super(pos)
for (let i in meshes) {
meshes[i].parent = this
this.children.push(meshes[i])
}
}
draw (gl, shader) {
// Invoke children's draw methods
for (let i in this.children) {
let child = this.children[i]
if (!(child instanceof MeshInstance)) continue
child.draw(gl, shader)
}
}
}
export { Node, MeshInstance, MultiMeshInstance }