diff --git a/src/engine/camera.js b/src/engine/camera.js index c50301d..4895e0e 100644 --- a/src/engine/camera.js +++ b/src/engine/camera.js @@ -9,7 +9,7 @@ const ZFAR = 1000.0 class Camera extends Node { constructor (pos, rotation) { - super(pos, rotation) + super(pos, null, rotation) this.fov = FOV this.speed = SPEED this.sensitivity = SENSITIVTY diff --git a/src/engine/components/index.js b/src/engine/components/index.js index be5eb79..7e154cb 100644 --- a/src/engine/components/index.js +++ b/src/engine/components/index.js @@ -1,9 +1,4 @@ -import Resource from '../resource' import { mat4, quat, vec3 } from 'gl-matrix' -import { Mesh } from '../mesh' -import { Material } from '../mesh/material' - -let meshCache = {} /** * Returns an euler angle representation of a quaternion @@ -48,7 +43,7 @@ class Node { // Scaling this.scale = scale || [1.0, 1.0, 1.0] - // Rotation in Euler angles (yaw, pitch, roll) in radians + // Rotation in Euler angles (pitch, yaw, roll) in degrees this.rotation = rotation || [0.0, 0.0, 0.0] this.transform = mat4.create() @@ -60,31 +55,23 @@ class Node { updateTransform () { let matrix = mat4.create() - - // Add parent node's transform - if (this.parent) { - mat4.mul(matrix, matrix, this.parent.transform) - } - - // Set translation - mat4.translate(matrix, matrix, this.pos) + let rot = quat.create() // Set rotation - if (this.rotation[0] !== 0) { - mat4.rotateX(matrix, matrix, this.rotation[0]) + 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) } - if (this.rotation[1] !== 0) { - mat4.rotateY(matrix, matrix, this.rotation[1]) + // Add parent's transform to this + if (this.parent) { + mat4.mul(matrix, this.parent.transform, matrix) } - if (this.rotation[2] !== 0) { - mat4.rotateZ(matrix, matrix, this.rotation[2]) - } - - // Set scale - mat4.scale(matrix, matrix, this.scale) - // Set the matrix this.transform = matrix @@ -96,12 +83,14 @@ class Node { } } - setTransformation (transform) { + 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) @@ -187,6 +176,7 @@ class Node { } } +// A node defining a single mesh class MeshInstance extends Node { constructor (mesh, pos, scale, rot) { super(pos, scale, rot) @@ -210,6 +200,7 @@ class MeshInstance extends Node { } } +// A node that contains multiple meshes class MultiMeshInstance extends Node { constructor (meshes, pos) { super(pos) diff --git a/src/engine/mesh/loader.js b/src/engine/mesh/loader.js index 66772e8..59bb35a 100644 --- a/src/engine/mesh/loader.js +++ b/src/engine/mesh/loader.js @@ -10,7 +10,7 @@ let meshCache = {} // Parse an assimp2json formatted mesh file // Supports multiple geometries -async function assimp2json (gl, file, dat, pos) { +async function assimp2json (gl, file, dat) { let cleaned = [] let materials = [] for (let mi in dat.meshes) { @@ -85,6 +85,7 @@ async function assimp2json (gl, file, dat, pos) { } let finished = [] + let lastTransform = dat.rootnode.transformation function setChildren (parent, chMeshes, last) { if (!chMeshes.meshes) { if (chMeshes.children) { @@ -100,7 +101,7 @@ async function assimp2json (gl, file, dat, pos) { let meshIndex = chMeshes.meshes[0] let mesh = loadComplete[meshIndex] - let meshInstance = new MeshInstance(mesh, parent == null ? pos : null) + let meshInstance = new MeshInstance(mesh) meshInstance.mesh = mesh @@ -114,15 +115,15 @@ async function assimp2json (gl, file, dat, pos) { meshInstance.name = chMeshes.name if (parent == null) { - // Multiply the last meshless node's transform in order to preserve it - finished.push(meshInstance) } else { parent.children.push(meshInstance) meshInstance.parent = parent } - meshInstance.setTransformation(transform) + mat4.transpose(transform, transform) + meshInstance._rootTransform = transform + meshInstance.updateTransform() } setChildren(null, dat.rootnode) @@ -135,6 +136,9 @@ async function assimp2json (gl, file, dat, pos) { let returnType if (finished.length > 1) { returnType = new MultiMeshInstance(finished) + mat4.transpose(lastTransform, lastTransform) + returnType._rootTransform = lastTransform + returnType.updateTransform() } else { returnType = finished[0] } @@ -143,22 +147,22 @@ async function assimp2json (gl, file, dat, pos) { } // Parse a collada mesh -async function collada (gl, file, dat, pos) { +async function collada (gl, file, dat) { // TODO... } -async function loadMesh (gl, file, pos) { +async function loadMesh (gl, file) { file = '/assets/models/' + file + '.json' // Ensure each mesh file is loaded only once - if (meshCache[file]) return meshCache[file].length > 1 ? new MultiMeshInstance(meshCache[file], pos) : meshCache[file][0] + if (meshCache[file]) return meshCache[file].length > 1 ? new MultiMeshInstance(meshCache[file]) : meshCache[file][0] let dat = await Resource.GET({ type: 'json', url: file }) // Recognize a assimp2json file format if (dat['__metadata__'] && dat['__metadata__'].format === 'assimp2json') { if (!dat.meshes) throw new Error('No geometries found in file ' + file) - return assimp2json(gl, file, dat, pos) + return assimp2json(gl, file, dat) } throw new Error('Unsupported mesh format.') diff --git a/src/index.js b/src/index.js index 09b3782..c5c148a 100644 --- a/src/index.js +++ b/src/index.js @@ -7,19 +7,16 @@ import { Terrain } from './engine/components/terrain' import { SimplexHeightMap } from './engine/components/terrain/heightmap' import { Material } from './engine/mesh/material' -// import { glMatrix } from 'gl-matrix' - let game = new Engine() let env = new Environment() +let t = 0 async function pipeline () { - let entity = await loadMesh(game.gl, 'test', [0.0, 0.0, 0.0]) + let entity = await loadMesh(game.gl, 'test') let shader = await game.shaders.createShaderFromFiles(game.gl, 'basic', false) let terrainShader = await game.shaders.createShaderFromFiles(game.gl, 'terrain', false) - console.log(entity) - - // entity.setRotation([glMatrix.toRadian(-90), 0.0, 0.0]) + entity.setRotation([0.0, 0.0, -90.0]) // Create a height map based on OpenSimplex noise let hmap = new SimplexHeightMap(1, 1, 256, 50) @@ -55,6 +52,10 @@ async function pipeline () { if (game.input.mouseMoved && game.input.mouse.btn0) { cam.processMouseMove(game.input.mouseOffset) } + + // TESTING: Move model forward + t = t + 0.1 + entity.setPosition([t, 0.0, 0.0]) }) // Render function for the triangle @@ -79,6 +80,7 @@ async function pipeline () { game.startGameLoop() } +// Start the game, catch any errors thrown pipeline().catch(function (e) { console.error(e) }) diff --git a/webpack.config.js b/webpack.config.js index e2112c6..7e4fab0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,28 +1,30 @@ const path = require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') -module.exports = { - entry: './src/index.js', - output: { - path: path.resolve(__dirname, 'dist'), - filename: 'app.js' - }, - module: { - rules: [ - { - test: /\.js$/, - exclude: /(node_modules)/, - use: { - loader: 'babel-loader', - options: { - presets: ['@babel/preset-env'] +module.exports = (env) => { + return { + entry: './src/index.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'app.js' + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + presets: ['@babel/preset-env'] + } } } - } + ] + }, + devtool: env.mode === 'development' ? 'inline-source-map' : '', + plugins: [ + new HtmlWebpackPlugin({ template: 'index.html' }) ] - }, - devtool: 'inline-source-map', - plugins: [ - new HtmlWebpackPlugin({ template: 'index.html' }) - ] + } }