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

438 lines
14 KiB
JavaScript
Raw Normal View History

2020-03-29 13:09:20 +00:00
import { Mesh } from '../../mesh'
import { mat4, vec3 } from 'gl-matrix'
import { subv3, mulv3, addv3, divv3, normalv3, crossv3 } from '../../utility'
import Screen from '../../screen'
2020-04-01 14:34:24 +00:00
const lodMax = 11
2020-03-29 13:09:20 +00:00
class PlanetGenerator {
constructor (resolution, radius, noise) {
this.resolution = resolution
this.radius = radius
this.noise = noise
2020-04-01 18:52:45 +00:00
if (resolution <= 1 || resolution % 2 === 0) {
throw new Error('Resolution must be higher than 1 and an odd number.')
}
2020-04-01 20:12:35 +00:00
PlanetIndexBuffers.generate(resolution)
}
}
const PlanetIndexBuffers = {
generate (sideResolution) {
PlanetIndexBuffers.base = PlanetIndexBuffers.generateBuffer(sideResolution)
PlanetIndexBuffers.fixT = PlanetIndexBuffers.generateBuffer(sideResolution, true, false, false, false)
PlanetIndexBuffers.fixTR = PlanetIndexBuffers.generateBuffer(sideResolution, true, true, false, false)
PlanetIndexBuffers.fixTL = PlanetIndexBuffers.generateBuffer(sideResolution, true, false, true, false)
PlanetIndexBuffers.fixB = PlanetIndexBuffers.generateBuffer(sideResolution, false, false, false, true)
PlanetIndexBuffers.fixBR = PlanetIndexBuffers.generateBuffer(sideResolution, false, true, false, true)
PlanetIndexBuffers.fixBL = PlanetIndexBuffers.generateBuffer(sideResolution, false, false, true, true)
PlanetIndexBuffers.fixR = PlanetIndexBuffers.generateBuffer(sideResolution, false, true, false, false)
PlanetIndexBuffers.fixL = PlanetIndexBuffers.generateBuffer(sideResolution, false, false, true, false)
},
generateBuffer (sideResolution, fanTop = false, fanRight = false, fanLeft = false, fanBottom = false) {
const indices = []
for (let y = 0; y < sideResolution - 1; y++) {
let slantLeft = (y % 2) === 0
for (let x = 0; x < sideResolution - 1; x++) {
const topLeft = (y * sideResolution) + x
const topRight = topLeft + 1
const bottomLeft = ((y + 1) * sideResolution) + x
const bottomRight = bottomLeft + 1
let tri1 = slantLeft ? [topLeft, bottomLeft, bottomRight] : [topLeft, bottomLeft, topRight]
let tri2 = slantLeft ? [topLeft, bottomRight, topRight] : [bottomLeft, bottomRight, topRight]
if (fanTop && y === 0) {
if (x % 2 === 0) {
tri2 = [topLeft, bottomRight, topRight + 1]
} else {
tri1 = null
}
}
if (fanRight && x === sideResolution - 2) {
if (y % 2 === 0) {
tri2 = [topRight, bottomLeft, bottomRight + sideResolution]
} else {
tri2 = null
}
}
if (fanBottom && y === sideResolution - 2) {
if (x % 2 === 0) {
tri2 = [bottomLeft, bottomRight + 1, topRight]
} else {
tri1 = null
}
}
if (fanLeft && x === 0) {
if (y % 2 === 0) {
tri1 = [topLeft, bottomLeft + sideResolution, bottomRight]
} else {
tri1 = null
}
}
// faster than concat :p
if (tri1) indices.push(tri1[0], tri1[1], tri1[2])
if (tri2) indices.push(tri2[0], tri2[1], tri2[2])
slantLeft = !slantLeft
}
}
return { buffer: Mesh.loadToBuffer(Screen.gl, Screen.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices)), indices }
},
setBuffer (mesh, buffer) {
mesh.ebo = buffer.buffer
mesh.indices = { length: buffer.indices.length }
2020-03-29 13:09:20 +00:00
}
}
class CubeFace {
2020-04-01 18:52:45 +00:00
constructor (index, parent, level, pos, normal, generator) {
this.index = index
2020-03-29 13:09:20 +00:00
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
2020-04-01 18:52:45 +00:00
const sideResolution = this.generator.resolution
2020-03-31 08:15:29 +00:00
const vertices = []
const normals = []
const textureCoords = []
2020-03-29 13:09:20 +00:00
2020-03-31 08:15:29 +00:00
const radius = this.generator.radius
const divisionLevel = Math.pow(2, this.level)
2020-03-29 13:09:20 +00:00
2020-04-01 18:52:45 +00:00
for (let i = 0, vertexPointer = 0; i < sideResolution; i++) {
for (let j = 0; j < sideResolution; j++, vertexPointer++) {
2020-03-29 13:09:20 +00:00
// Vertex index (0 - 1)
2020-04-01 18:52:45 +00:00
const iindex = i / (sideResolution - 1)
const jindex = j / (sideResolution - 1)
2020-03-29 13:09:20 +00:00
// From the left and forward vectors, we can calculate an oriented vertex
2020-03-31 08:15:29 +00:00
const iv = divv3(mulv3(mulv3(this.left, iindex), radius), divisionLevel)
const jv = divv3(mulv3(mulv3(this.forward, jindex), radius), divisionLevel)
2020-03-29 13:09:20 +00:00
// Add the scaled left and forward to the centered origin
2020-03-31 08:15:29 +00:00
const vertex = addv3(this.position, addv3(iv, jv))
2020-03-29 13:09:20 +00:00
// Normalize and multiply by radius to create a spherical mesh
2020-03-31 08:15:29 +00:00
const normal = normalv3(vertex)
2020-04-01 14:34:24 +00:00
const pointHeight = this.generator.noise.getNoise3D(this.level + 1, normal[0], normal[1], normal[2])
2020-04-01 20:12:35 +00:00
const pos = mulv3(normal, pointHeight * 2 + radius)
2020-03-29 13:09:20 +00:00
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]
2020-04-01 18:52:45 +00:00
textureCoords[vertexPointer * 2] = j * (1 / sideResolution)
textureCoords[vertexPointer * 2 + 1] = i * (1 / sideResolution)
2020-03-29 13:09:20 +00:00
2020-04-01 18:52:45 +00:00
if (i === Math.floor(sideResolution / 2) && j === Math.floor(sideResolution / 2)) {
2020-03-29 13:09:20 +00:00
this.center = pos
}
}
}
2020-04-01 18:52:45 +00:00
// TODO: neighbor detection
2020-04-01 20:12:35 +00:00
const indexBuffer = PlanetIndexBuffers.base
2020-04-01 18:52:45 +00:00
2020-04-01 20:12:35 +00:00
// Generate normals for this mesh
Mesh.generateNormals(normals, PlanetIndexBuffers.base.indices, vertices)
2020-04-01 18:52:45 +00:00
2020-04-01 20:12:35 +00:00
this.mesh = Mesh.construct(Screen.gl, vertices, null, textureCoords, normals)
2020-04-01 18:52:45 +00:00
2020-04-01 20:12:35 +00:00
// Set the index buffer for this mesh to use
PlanetIndexBuffers.setBuffer(this.mesh, indexBuffer)
2020-04-01 18:52:45 +00:00
2020-03-29 13:09:20 +00:00
this.generated = true
}
dispose () {
this.generated = false
if (this.mesh) {
// this.mesh.dispose(Screen.gl)
this.mesh = null
}
}
2020-04-01 18:52:45 +00:00
isLeaf () {
return !this.children.length
}
2020-03-29 13:09:20 +00:00
merge () {
2020-04-01 18:52:45 +00:00
if (this.isLeaf()) return
2020-03-29 13:09:20 +00:00
2020-03-31 08:15:29 +00:00
for (const i in this.children) {
const ch = this.children[i]
2020-03-29 13:09:20 +00:00
ch.merge()
ch.dispose()
}
this.children = []
this.generate()
}
subdivide () {
if (this.level === lodMax) return
2020-03-31 08:15:29 +00:00
const lv = this.level + 1
const stepLeft = mulv3(this.left, this.generator.radius / Math.pow(2, lv))
const stepForward = mulv3(this.forward, this.generator.radius / Math.pow(2, lv))
2020-03-29 13:09:20 +00:00
this.children = [
2020-04-01 18:52:45 +00:00
// Top left corner
new CubeFace(0, this, lv, addv3(this.position, addv3(stepLeft, stepForward)), this.normal, this.generator),
2020-03-31 08:15:29 +00:00
// Top right corner
2020-04-01 18:52:45 +00:00
new CubeFace(1, this, lv, addv3(this.position, stepForward), this.normal, this.generator),
// Bottom right corner
new CubeFace(2, this, lv, this.position, this.normal, this.generator),
2020-03-31 08:15:29 +00:00
// Bottom left corner
2020-04-01 18:52:45 +00:00
new CubeFace(3, this, lv, addv3(this.position, stepLeft), this.normal, this.generator)
2020-03-29 13:09:20 +00:00
]
this.dispose()
}
draw (gl, shader) {
if (!this.mesh) {
2020-03-31 08:15:29 +00:00
for (const i in this.children) {
2020-03-29 13:09:20 +00:00
this.children[i].draw(gl, shader)
}
return
}
2020-04-01 20:12:35 +00:00
CubeFace.determineIndexBuffer(this)
2020-03-29 13:09:20 +00:00
this.mesh.prepare(gl, shader)
this.mesh.draw(gl, shader)
}
update (camera, dt) {
if (!this.center) return
2020-03-31 08:15:29 +00:00
const camToOrigin = vec3.distance(camera.pos, this.center)
const divisionLevel = Math.pow(2, this.level)
const splitDistance = this.generator.radius / divisionLevel
2020-03-29 13:09:20 +00:00
2020-03-29 17:19:45 +00:00
if (camToOrigin < splitDistance * 2 && this.children.length === 0) {
2020-03-29 13:09:20 +00:00
this.subdivide()
return
2020-03-29 17:19:45 +00:00
} else if (camToOrigin > splitDistance * 2.5 && this.children.length > 0) {
2020-03-29 13:09:20 +00:00
this.merge()
return
}
if (this.children.length > 0) {
2020-03-31 08:15:29 +00:00
for (const i in this.children) {
2020-03-29 13:09:20 +00:00
this.children[i].update(camera, dt)
}
}
2020-04-01 20:12:35 +00:00
this.updateNeighbors()
}
updateNeighbors () {
// TODO: planet face traversal
if (this.level === 0) return
switch (this.index) {
// Top left corner
case 0:
if (this.parent.neighborTop != null) {
this.neighborTop = this.parent.neighborTop.children[3]
}
this.neighborRight = this.parent.children[1]
this.neighborBottom = this.parent.children[3]
if (this.parent.neighborLeft != null) {
this.neighborLeft = this.parent.neighborLeft.children[1]
}
break
// Top right corner
case 1:
if (this.parent.neighborTop != null) {
this.neighborTop = this.parent.neighborTop.children[2]
}
if (this.parent.neighborRight != null) {
this.neighborRight = this.parent.neighborRight.children[0]
}
this.neighborBottom = this.parent.children[2]
this.neighborLeft = this.parent.children[0]
break
// Bottom right corner
case 2:
this.neighborTop = this.parent.children[1]
if (this.parent.neighborRight != null) {
this.neighborRight = this.parent.neighborRight.children[3]
}
if (this.parent.neighborBottom != null) {
this.neighborBottom = this.parent.neighborBottom.children[1]
}
this.neighborLeft = this.parent.children[3]
break
// Bottom left corner
case 3:
this.neighborTop = this.parent.children[0]
this.neighborRight = this.parent.children[2]
if (this.parent.neighborBottom != null) {
this.neighborBottom = this.parent.neighborBottom.children[0]
}
if (this.parent.neighborLeft != null) {
this.neighborLeft = this.parent.neighborLeft.children[2]
}
break
}
}
static determineIndexBuffer (face) {
let buffer = PlanetIndexBuffers.base
if (face.level === 0) return PlanetIndexBuffers.setBuffer(face.mesh, buffer)
switch (face.index) {
// Top left corner
case 0:
if (face.neighborTop == null && face.neighborLeft == null) {
buffer = PlanetIndexBuffers.fixTL
} else if (face.neighborTop == null) {
buffer = PlanetIndexBuffers.fixT
} else if (face.neighborLeft == null) {
buffer = PlanetIndexBuffers.fixL
}
break
// Top right corner
case 1:
if (face.neighborTop == null && face.neighborRight == null) {
buffer = PlanetIndexBuffers.fixTR
} else if (face.neighborTop == null) {
buffer = PlanetIndexBuffers.fixT
} else if (face.neighborRight == null) {
buffer = PlanetIndexBuffers.fixR
}
break
// Bottom right corner
case 2:
if (face.neighborBottom == null && face.neighborRight == null) {
buffer = PlanetIndexBuffers.fixBR
} else if (face.neighborBottom == null) {
buffer = PlanetIndexBuffers.fixB
} else if (face.neighborRight == null) {
buffer = PlanetIndexBuffers.fixR
}
break
// Bottom left corner
case 3:
if (face.neighborBottom == null && face.neighborLeft == null) {
buffer = PlanetIndexBuffers.fixBL
} else if (face.neighborBottom == null) {
buffer = PlanetIndexBuffers.fixB
} else if (face.neighborLeft == null) {
buffer = PlanetIndexBuffers.fixL
}
break
}
PlanetIndexBuffers.setBuffer(face.mesh, buffer)
2020-03-29 13:09:20 +00:00
}
}
class CubePlanet {
constructor (origin, generator) {
this.origin = origin
this.generator = generator
this.transform = mat4.create()
mat4.fromTranslation(this.transform, origin)
2020-03-31 08:15:29 +00:00
const hs = generator.radius / 2
2020-03-29 13:09:20 +00:00
this.faces = [
2020-04-01 18:52:45 +00:00
new CubeFace(0, this, 0, [0, 0, -hs], [0, 0, -1], generator), // front
new CubeFace(0, this, 0, [0, 0, hs], [0, 0, 1], generator), // back
2020-03-29 13:09:20 +00:00
2020-04-01 18:52:45 +00:00
new CubeFace(0, this, 0, [-hs, 0, 0], [-1, 0, 0], generator), // left
new CubeFace(0, this, 0, [hs, 0, 0], [1, 0, 0], generator), // right
2020-03-29 13:09:20 +00:00
2020-04-01 18:52:45 +00:00
new CubeFace(0, this, 0, [0, hs, 0], [0, 1, 0], generator), // top
new CubeFace(0, this, 0, [0, -hs, 0], [0, -1, 0], generator) // bottom
2020-03-29 13:09:20 +00:00
]
}
update (camera, dt) {
2020-03-31 08:15:29 +00:00
for (const i in this.faces) {
2020-03-29 13:09:20 +00:00
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)
2020-03-31 08:15:29 +00:00
for (const i in this.faces) {
2020-03-29 13:09:20 +00:00
this.faces[i].draw(gl, shader)
}
}
2020-03-31 13:32:59 +00:00
prepare (gl, shader) {
if (!this.material) return
this.material.apply(gl, shader)
}
static drawPlanetAtmosphere (gl, planet, atmosphere, camera, sun, atmosShader, planetShader, surfaceShader, surfaceEnvironment) {
const height = vec3.length(subv3(camera.pos, atmosphere.pos))
// Render the atmosphere
atmosShader.use(gl)
camera.draw(gl, atmosShader)
atmosphere.draw(gl, atmosShader, camera, sun)
if (height > atmosphere.innerRadius + (atmosphere.outerRadius - atmosphere.innerRadius) / 3) {
// Draw the planet from space
// TODO: maybe separate atmosphere from space and ground shaders?
planetShader.use(gl)
camera.draw(gl, planetShader)
atmosphere.setupPlanetShader(gl, planetShader, camera, sun)
planet.prepare(gl, planetShader)
planet.draw(gl, planetShader)
} else {
// Draw the planet within the atmosphere
surfaceShader.use(gl)
surfaceEnvironment.draw(gl, surfaceShader)
camera.draw(gl, surfaceShader)
planet.prepare(gl, surfaceShader)
planet.draw(gl, surfaceShader)
}
}
2020-03-29 13:09:20 +00:00
}
export { CubePlanet, CubeFace, PlanetGenerator }