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

221 lines
6.2 KiB
JavaScript

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 }