import { Mesh } from '../../mesh' import { BoundingBox } from '../../mesh/aabb' import { mat4, vec3 } from 'gl-matrix' import { subv3, mulv3, addv3, divv3, normalv3, crossv3 } from '../../utility' import Screen from '../../screen' const lodMax = 8 class PlanetGenerator { constructor (resolution, radius, noise) { this.resolution = resolution this.radius = radius this.noise = noise } } class CubeFace { constructor (parent, level, pos, normal, generator) { this.parent = parent this.children = [] this.normal = normal this.level = level this.generated = false this.generator = generator // Calculate left (x) and forward (z) vectors from the normal (y) this.left = [normal[1], normal[2], normal[0]] this.forward = crossv3(normal, this.left) this.position = pos // Center the face if (level === 0) { this.position = subv3(this.position, mulv3(this.left, generator.radius / 2)) this.position = subv3(this.position, mulv3(this.forward, generator.radius / 2)) } this.generate() } generate () { if (this.generated) return let VERTICES = this.generator.resolution let count = VERTICES * VERTICES let vertices = new Array(count * 3) let normals = new Array(count * 3) let textureCoords = new Array(count * 2) let indices = new Array(6 * (VERTICES - 1) * (VERTICES - 1)) let radius = this.generator.radius let divisionLevel = Math.pow(2, this.level) for (let i = 0, vertexPointer = 0; i < VERTICES; i++) { for (let j = 0; j < VERTICES; j++, vertexPointer++) { // Vertex index (0 - 1) let iindex = i / (VERTICES - 1) let jindex = j / (VERTICES - 1) // From the left and forward vectors, we can calculate an oriented vertex let iv = divv3(mulv3(mulv3(this.left, iindex), radius), divisionLevel) let jv = divv3(mulv3(mulv3(this.forward, jindex), radius), divisionLevel) // Add the scaled left and forward to the centered origin let vertex = addv3(this.position, addv3(iv, jv)) // Normalize and multiply by radius to create a spherical mesh let normal = normalv3(vertex) let pos = mulv3(normal, (radius)) vertices[vertexPointer * 3] = pos[0] vertices[vertexPointer * 3 + 1] = pos[1] vertices[vertexPointer * 3 + 2] = pos[2] normals[vertexPointer * 3] = normal[0] normals[vertexPointer * 3 + 1] = normal[1] normals[vertexPointer * 3 + 2] = normal[2] textureCoords[vertexPointer * 2] = j * (1 / VERTICES) textureCoords[vertexPointer * 2 + 1] = i * (1 / VERTICES) if (i === VERTICES / 2 && j === VERTICES / 2) { this.center = pos } } } for (let gz = 0, pointer = 0; gz < VERTICES - 1; gz++) { for (let gx = 0; gx < VERTICES - 1; gx++) { let topLeft = (gz * VERTICES) + gx let topRight = topLeft + 1 let bottomLeft = ((gz + 1) * VERTICES) + gx let bottomRight = bottomLeft + 1 indices[pointer++] = topLeft indices[pointer++] = bottomLeft indices[pointer++] = topRight indices[pointer++] = topRight indices[pointer++] = bottomLeft indices[pointer++] = bottomRight } } this.mesh = Mesh.construct(Screen.gl, vertices, indices, textureCoords, normals) this.generated = true } dispose () { this.generated = false if (this.mesh) { // this.mesh.dispose(Screen.gl) this.mesh = null } } merge () { if (!this.children.length) return for (let i in this.children) { let ch = this.children[i] ch.merge() ch.dispose() } this.children = [] this.generate() } subdivide () { if (this.level === lodMax) return let lv = this.level + 1 let stepLeft = mulv3(this.left, this.generator.radius / Math.pow(2, lv)) let stepForward = mulv3(this.forward, this.generator.radius / Math.pow(2, lv)) this.children = [ new CubeFace(this, lv, this.position, this.normal, this.generator), new CubeFace(this, lv, addv3(this.position, stepForward), this.normal, this.generator), new CubeFace(this, lv, addv3(this.position, stepLeft), this.normal, this.generator), new CubeFace(this, lv, addv3(this.position, addv3(stepLeft, stepForward)), this.normal, this.generator) ] this.dispose() } draw (gl, shader) { if (!this.mesh) { for (let i in this.children) { this.children[i].draw(gl, shader) } return } this.mesh.prepare(gl, shader) this.mesh.draw(gl, shader) } update (camera, dt) { if (!this.center) return let camToOrigin = vec3.distance(camera.pos, this.center) let divisionLevel = Math.pow(2, this.level) let splitDistance = this.generator.radius / divisionLevel if (camToOrigin < splitDistance * 1.5 && this.children.length === 0) { this.subdivide() return } else if (camToOrigin > splitDistance * 2 && this.children.length > 0) { this.merge() return } if (this.children.length > 0) { for (let i in this.children) { this.children[i].update(camera, dt) } } } } class CubePlanet { constructor (origin, generator) { this.origin = origin this.generator = generator this.transform = mat4.create() mat4.fromTranslation(this.transform, origin) let hs = generator.radius / 2 this.faces = [ new CubeFace(this, 0, [0, 0, -hs], [0, 0, -1], generator), // front new CubeFace(this, 0, [0, 0, hs], [0, 0, 1], generator), // back new CubeFace(this, 0, [-hs, 0, 0], [-1, 0, 0], generator), // left new CubeFace(this, 0, [hs, 0, 0], [1, 0, 0], generator), // right new CubeFace(this, 0, [0, hs, 0], [0, 1, 0], generator), // top new CubeFace(this, 0, [0, -hs, 0], [0, -1, 0], generator) // bottom ] } update (camera, dt) { for (let i in this.faces) { this.faces[i].update(camera, dt) } } draw (gl, shader) { // Set model transform matrix uniform const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix') gl.uniformMatrix4fv(transformLocation, false, this.transform) for (let i in this.faces) { this.faces[i].draw(gl, shader) } } } export { CubePlanet, CubeFace, PlanetGenerator }