diff --git a/assets/models/test.json b/assets/models/test.json index 1441821..0fb9015 100644 --- a/assets/models/test.json +++ b/assets/models/test.json @@ -27,17 +27,17 @@ { "name": "Circle" ,"transformation": [ - 1 + 1.29986 ,0 ,0 ,0 ,0 - ,1 + ,1.29986 ,0 ,0 ,0 ,0 - ,1 + ,1.29986 ,0 ,0 ,0 diff --git a/src/engine/components/terrain/heightmap.js b/src/engine/components/terrain/heightmap.js index 147b2fa..345fdd3 100644 --- a/src/engine/components/terrain/heightmap.js +++ b/src/engine/components/terrain/heightmap.js @@ -43,29 +43,48 @@ class HeightMap { } class SimplexHeightMap extends HeightMap { - constructor (offsetX, offsetY, size, seed) { + // amplitude - Controls the amount the height changes. The higher, the taller the hills. + // persistence - Controls details, value in [0,1]. Higher increases grain, lower increases smoothness. + // octaves - Number of noise layers + // period - Distance above which we start to see similarities. The higher, the longer "hills" will be on a terrain. + // lacunarity - Controls period change across octaves. 2 is usually a good value to address all detail levels. + constructor (offsetX, offsetY, size, seed, amplitude = 15, persistence = 0.4, octaves = 5, period = 80, lacunarity = 2) { super(size) this.ix = offsetX this.iy = offsetY this.seed = seed this.osn = new OpenSimplexNoise(seed) + + this.amplitude = amplitude + this.period = period + this.lacunarity = lacunarity + this.octaves = octaves + this.persistence = persistence } - getNoise (relX, relY) { - let x = ((this.ix * this.size) + relX) / this.size - 0.5 - let y = ((this.iy * this.size) + relY) / this.size - 0.5 + getNoise (zx, zy) { + let x = ((this.size * this.ix) + zx) / this.period + let y = ((this.size * this.iy) + zy) / this.period - let total = this.osn.noise2D(2 * x, 2 * y) + - 0.5 * this.osn.noise2D(4 * x, 4 * y) + - 0.25 * this.osn.noise2D(2 * x, 2 * y) + let amp = 1.0 + let max = 1.0 + let sum = this.osn.noise2D(x, y) - total *= 10 - return total + let i = 0 + while (++i < this.octaves) { + x *= this.lacunarity + y *= this.lacunarity + amp *= this.persistence + max += amp + sum += this.osn.noise2D(x, y) * amp + } + + return sum / max } getHeight (x, y) { - return this.getNoise(x, y) + return this.getNoise(x, y) * this.amplitude } } diff --git a/src/engine/mesh/index.js b/src/engine/mesh/index.js index f229698..26e446f 100644 --- a/src/engine/mesh/index.js +++ b/src/engine/mesh/index.js @@ -1,13 +1,53 @@ import Resource from '../resource' import { Texture, Material } from './material' -import { mat4 } from 'gl-matrix' +import { mat4, quat, vec3 } from 'gl-matrix' let meshCache = {} +/** + * 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 (yaw, pitch, roll) in radians this.rotation = rotation || [0.0, 0.0, 0.0] this.transform = mat4.create() @@ -55,12 +95,35 @@ class Node { } } + setTransformation (transform) { + let quaternion = quat.create() + let translation = vec3.create() + let rotation = vec3.create() + let scale = vec3.create() + + mat4.getTranslation(translation, transform) + mat4.getScaling(scale, transform) + mat4.getRotation(quaternion, 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() @@ -99,6 +162,10 @@ class Node { return this.pos } + get translation () { + return this.pos + } + // Draw base draw (gl, shader) { // Set model transform matrix uniform @@ -132,7 +199,7 @@ class Mesh extends Node { gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW) let mesh = new Mesh() - mesh.pos = pos + mesh.posBuffer = pos mesh.ebo = ebo mesh.indices = indices.length @@ -219,7 +286,8 @@ class Mesh extends Node { }) } - let finished = [] + // Load everything + let loadComplete = [] for (let i in cleaned) { let meshdata = cleaned[i] let mesh = Mesh.construct(gl, meshdata.vertices, meshdata.indices, @@ -235,7 +303,37 @@ class Mesh extends Node { } } - finished.push(mesh) + loadComplete.push(mesh) + } + + // Now, we need to give the meshes the appropriate parents and transforms. + let finished = [] + function setChildren (parent, chMeshes) { + let meshIndex = chMeshes.meshes[0] + let mesh = loadComplete[meshIndex] + + if (chMeshes.children) { + for (let i in chMeshes.children) { + if (!chMeshes.children[i].meshes) continue + setChildren(mesh, chMeshes.children[i]) + } + } + + mesh.name = chMeshes.name + + if (parent == null) { + finished.push(mesh) + } else { + parent.children.push(mesh) + mesh.parent = parent + mesh.setTransformation(chMeshes.transformation) + } + } + + let rootchildren = dat.rootnode.children + for (let j in rootchildren) { + if (!rootchildren[j].meshes) continue + setChildren(null, rootchildren[j]) } // Cache the mesh @@ -245,7 +343,7 @@ class Mesh extends Node { } bindBuffers (gl, shader) { - gl.bindBuffer(gl.ARRAY_BUFFER, this.pos) + gl.bindBuffer(gl.ARRAY_BUFFER, this.posBuffer) shader.setAttribute(gl, 'aVertexPosition', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0) if (this.nms && shader.hasAttribute(gl, 'aNormal')) { diff --git a/src/index.js b/src/index.js index bc20cc3..3697c91 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import Engine from './engine' import Camera from './engine/camera' -// import Entity from './engine/mesh/entity' +import Entity from './engine/mesh/entity' import { Environment } from './engine/environment' import { Terrain } from './engine/components/terrain' @@ -11,10 +11,12 @@ let game = new Engine() let env = new Environment() async function pipeline () { - // let entity = await Entity.createEntity(game.gl, 'test', [0.0, 0.0, -6.0]) - // let shader = await game.shaders.createShaderFromFiles(game.gl, 'basic', false) + let entity = await Entity.createEntity(game.gl, 'test', [0.0, 0.0, -6.0]) + let shader = await game.shaders.createShaderFromFiles(game.gl, 'basic', false) let terrainShader = await game.shaders.createShaderFromFiles(game.gl, 'terrain', false) + console.log(entity.mesh) + // Create a height map based on OpenSimplex noise let hmap = new SimplexHeightMap(1, 1, 256, 50) @@ -53,6 +55,10 @@ async function pipeline () { // Render function for the triangle game.addRenderFunction(function (gl) { + shader.use(gl) + cam.draw(gl, shader) + entity.draw(gl, shader) + // Use terrain shader terrainShader.use(gl)