broken planet

This commit is contained in:
Evert Prants 2019-02-26 14:02:12 +02:00
parent c785db574c
commit befbcc5480
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
15 changed files with 514 additions and 69 deletions

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8"/>
<style type="text/css">*{margin:0;padding:0;}body{width:100%;height:100%;}body,html{overflow:hidden;}</style>
<title>Trotland</title>
<title>3D Experiments</title>
</head>
<body>
</body>

View File

@ -1,7 +1,7 @@
{
"name": "trotland-game",
"name": "3dexperiments",
"version": "0.0.1",
"description": "Trotland 3D MMORPG",
"description": "3D Experiments",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"serve": "node ./serve.js",
@ -9,7 +9,7 @@
"watch": "webpack -w --mode=development"
},
"keywords": [
"game",
"experiment",
"webgl"
],
"private": true,

View File

@ -1,11 +1,11 @@
import { Node } from './components'
import { glMatrix, mat4, vec2, vec3 } from 'gl-matrix'
const SPEED = 10.0
const SPEED = 100.0
const SENSITIVTY = 100.0
const FOV = 45.0
const ZNEAR = 0.1
const ZFAR = 1000.0
const ZFAR = 10000.0
class Camera extends Node {
constructor (pos, rotation) {

View File

@ -47,6 +47,7 @@ class Node {
this.rotation = rotation || [0.0, 0.0, 0.0]
this.transform = mat4.create()
this.updateTransform()
this.parent = null
@ -63,8 +64,8 @@ class Node {
// 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.worldTransform) {
mat4.mul(matrix, matrix, this.worldTransform)
}
// Add parent's transform to this
@ -158,13 +159,33 @@ class Node {
// Draw base
draw (gl, shader) {
// Set model transform matrix uniform
const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix')
gl.uniformMatrix4fv(transformLocation, false, this.transform)
// Nothing to draw here, so just draw children
for (let i in this.children) {
let child = this.children[i]
if (!(child instanceof Node)) continue
child.draw(gl, shader)
}
}
update (dt) {
// Update children
for (let i in this.children) {
let child = this.children[i]
if (!(child instanceof Node)) continue
child.update(dt)
}
}
addChild (ch) {
// Recursive add in case of table
if (ch && typeof ch === 'object' && ch.length) {
for (let i in ch) {
this.addChild(ch[i])
}
}
if (!(ch instanceof Node)) return
ch.setParent(this)
this.children.push(ch)
this.updateTransform()
}
@ -184,41 +205,18 @@ class MeshInstance extends Node {
}
draw (gl, shader) {
// Set model transform uniform
super.draw(gl, shader)
if (!this.mesh) return super.draw(gl, shader)
// Set model transform matrix uniform
const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix')
gl.uniformMatrix4fv(transformLocation, false, this.transform)
// Draw the mesh
this.mesh.prepare(gl, shader)
this.mesh.draw(gl, shader)
// Invoke children's draw methods
for (let i in this.children) {
let child = this.children[i]
if (!(child instanceof MeshInstance)) continue
child.draw(gl, shader)
}
// Draw children
super.draw(gl, shader)
}
}
// A node that contains multiple meshes
class MultiMeshInstance extends Node {
constructor (meshes, pos) {
super(pos)
for (let i in meshes) {
meshes[i].parent = this
this.children.push(meshes[i])
}
}
draw (gl, shader) {
// Invoke children's draw methods
for (let i in this.children) {
let child = this.children[i]
if (!(child instanceof MeshInstance)) continue
child.draw(gl, shader)
}
}
}
export { Node, MeshInstance, MultiMeshInstance }
export { Node, MeshInstance }

View File

@ -0,0 +1,124 @@
import { Mesh } from '../../mesh'
import { mat4 } from 'gl-matrix'
import { subv3, mulv3, addv3, normalv3, crossv3 } from '../../utility'
class CubeFace {
constructor (parent, level, pos, normal, resolution, radius, generator) {
this.parent = parent
this.children = []
this.position = pos
this.normal = normal
this.resolution = resolution
this.radius = radius
this.level = 0
this.generated = false
// Calculate left and forward vectors from the normal
this.left = [normal[1], normal[2], normal[0]]
this.forward = crossv3(normal, this.left)
this.transform = mat4.create()
mat4.fromTranslation(this.transform, this.position)
this.generate()
}
generate () {
if (this.generated) return
let cpoint = subv3(this.position, mulv3(this.left, this.radius / 2))
cpoint = subv3(cpoint, mulv3(this.forward, this.radius / 2))
let VERTICES = this.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))
for (let i = 0, vertexPointer = 0; i < VERTICES; i++) {
for (let j = 0; j < VERTICES; j++, vertexPointer++) {
let isize = i / VERTICES * this.radius
let jsize = j / VERTICES * this.radius
// From the left and forward vectors, we can calculate an oriented vertex
let iv = mulv3(this.left, isize)
let jv = mulv3(this.forward, jsize)
// Add the scaled left and forward to the centered origin
let vertex = addv3(cpoint.slice(0), addv3(iv, jv))
// Normalize and multiply by radius to create a spherical mesh
let pos = mulv3(normalv3(vertex), this.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 / (VERTICES - 1)
textureCoords[vertexPointer * 2 + 1] = i / (VERTICES - 1)
}
}
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(window.gl, vertices, indices, textureCoords)
this.generated = true
}
draw (gl, shader) {
// Set model transform matrix uniform
const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix')
gl.uniformMatrix4fv(transformLocation, false, this.transform)
this.mesh.prepare(gl, shader)
this.mesh.draw(gl, shader)
}
}
class CubePlanet {
constructor (origin, resolution, radius, generator) {
this.origin = origin
this.resolution = resolution
this.radius = radius
let hs = resolution / 2
this.faces = [
new CubeFace(this, 0, [0, 0, -hs], [0, 0, -1], resolution, radius, generator), // front
// new CubeFace(this, 0, [0, 0, hs], [0, 0, 1], resolution, radius, generator), // back
new CubeFace(this, 0, [-hs, 0, 0], [-1, 0, 0], resolution, radius, generator), // left
// new CubeFace(this, 0, [hs, 0, 0], [1, 0, 0], resolution, radius, generator), // right
// new CubeFace(this, 0, [0, hs, 0], [0, 1, 0], resolution, radius, generator), // top
// new CubeFace(this, 0, [0, -hs, 0], [0, -1, 0], resolution, radius, generator) // bottom
]
}
draw (gl, shader) {
for (let i in this.faces) {
this.faces[i].draw(gl, shader)
}
}
}
export { CubePlanet, CubeFace }

View File

@ -0,0 +1,96 @@
import { Node } from '../'
import { Mesh } from '../../mesh'
import { vec3 } from 'gl-matrix'
import { subv3, mulv3, addv3, normalv3 } from '../../utility'
class Terrain extends Node {
constructor (pos, sWidth, sHeight, face = [0.0, -1.0, 0.0]) {
super(pos)
this.width = sWidth
this.height = sHeight
this.mesh = null
// Calculate left and forward vectors
this.left = [face[1], face[2], face[0]] || [1.0, 0.0, 0.0]
this.forward = [0.0, 0.0, -1.0]
vec3.cross(this.forward, face, this.left)
}
createMesh (gl, heightMap) {
// Center the mesh
let cpoint = [0.0, 0.0, 0.0]
cpoint = subv3(cpoint, mulv3(this.left, heightMap.size / 2))
cpoint = subv3(cpoint, mulv3(this.forward, heightMap.size / 2))
let VERTICES = heightMap.size
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 vertexPointer = 0
for (let i = 0; i < VERTICES; i++) {
for (let j = 0; j < VERTICES; j++) {
let isize = j / (VERTICES - 1) * this.width / 2
let jsize = i / (VERTICES - 1) * this.height / 2
// From the left and forward vectors, we can calculate an oriented vertex
let iv = mulv3(this.left, isize)
let jv = mulv3(this.forward, jsize)
// Add the scaled left and forward to the centered origin
let pos = addv3(cpoint.slice(0), addv3(iv, jv))
vertices[vertexPointer * 3] = pos[0]
vertices[vertexPointer * 3 + 1] = pos[1]
vertices[vertexPointer * 3 + 2] = pos[2]
let normal = [0.0, 1.0, 0.0] // heightMap.getNormal(j, i)
normals[vertexPointer * 3] = normal[0]
normals[vertexPointer * 3 + 1] = normal[1]
normals[vertexPointer * 3 + 2] = normal[2]
textureCoords[vertexPointer * 2] = j / (VERTICES - 1)
textureCoords[vertexPointer * 2 + 1] = i / (VERTICES - 1)
vertexPointer++
}
}
let pointer = 0
for (let gz = 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(gl, vertices, indices, textureCoords, normals)
}
setMaterial (mat) {
this.mesh.material = mat
}
draw (gl, shader) {
if (!this.mesh) return
// Set model transform matrix uniform
const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix')
gl.uniformMatrix4fv(transformLocation, false, this.transform)
this.mesh.prepare(gl, shader)
this.mesh.draw(gl, shader)
super.draw(gl, shader)
}
}
export { Terrain }

View File

@ -60,10 +60,14 @@ class Terrain extends Node {
draw (gl, shader) {
if (!this.mesh) return
super.draw(gl, shader)
// Set model transform matrix uniform
const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix')
gl.uniformMatrix4fv(transformLocation, false, this.transform)
this.mesh.prepare(gl, shader)
this.mesh.draw(gl, shader)
super.draw(gl, shader)
}
}

120
src/engine/gui/index.js Normal file
View File

@ -0,0 +1,120 @@
import { clamp } from '../utility'
class Dim4 {
constructor (scX, ofX, scY, ofY) {
this.scX = scX
this.ofX = ofX
this.scY = scY
this.ofY = ofY
this._update()
}
static fromTable (tbl) {
let sx = 0.0
let ox = 0.0
let sy = 0.0
let oy = 0.0
if (tbl.length === 2) {
let x = tbl[0]
let y = tbl[1]
if (typeof x === 'object' && x.length === 2) {
sx = x[0]
ox = x[1]
} else if (typeof x === 'number') {
sx = x
}
if (typeof y === 'object' && y.length === 2) {
sx = y[0]
ox = y[1]
} else if (typeof y === 'number') {
sx = y
}
} else if (tbl.length === 4) {
sx = tbl[0]
ox = tbl[1]
sy = tbl[2]
oy = tbl[3]
}
return new Dim4(sx, ox, sy, oy)
}
toTable () {
return [this.scX, this.ofX, this.scY, this.ofY]
}
_update () {
this[0] = this.scX
this[1] = this.ofX
this[2] = this.scY
this[3] = this.ofY
}
}
class Node2D {
constructor (pos, size, rotation) {
// Translation
this.gpos = [0.0, 0.0]
this.pos = pos || new Dim4(0.0, 0.0, 0.0, 0.0)
// Scaling
this.gscale = [1.0, 1.0]
this.size = size || new Dim4(0.0, 0.0, 0.0, 0.0)
// Rotation in degrees
this.rotation = rotation || 0.0
this.parent = null
this.children = []
}
updateTransform () {
}
// Getters
get position () {
return this.pos
}
get translation () {
return this.pos
}
// Draw base
draw (gl) {
// Nothing to draw here, so just draw children
for (let i in this.children) {
let child = this.children[i]
if (!(child instanceof Node2D)) continue
child.draw(gl)
}
}
update (dt) {
// Update children
for (let i in this.children) {
let child = this.children[i]
if (!(child instanceof Node2D)) continue
child.update(dt)
}
}
addChild (ch) {
// Recursive add in case of table
if (ch && typeof ch === 'object' && ch.length) {
for (let i in ch) {
this.addChild(ch[i])
}
}
if (!(ch instanceof Node2D)) return
this.children.push(ch)
}
setParent (p) {
if (!(p instanceof Node2D)) return
this.parent = p
}
}

View File

View File

@ -17,6 +17,8 @@ class Engine {
this.frameTime = 0
this.frameCount = 0
this.fps = 0
window.gl = this.screen.gl
}
get gl () {

View File

@ -1,35 +1,36 @@
class Mesh {
static loadToBuffer (gl, type, data, drawtype = gl.STATIC_DRAW) {
let id = gl.createBuffer()
gl.bindBuffer(type, id)
gl.bufferData(type, data, drawtype)
return id
}
static construct (gl, vertices, indices, uvs, normals) {
// VBO for model vertices
let pos = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, pos)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
let pos = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(vertices))
// Indices Buffer
let ebo = gl.createBuffer()
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ebo)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW)
let ebo = Mesh.loadToBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices))
// Create the mesh, as we have the most important data already buffered
let mesh = new Mesh()
mesh.posBuffer = pos
mesh.ebo = ebo
mesh.indices = indices.length
mesh.vertices = vertices
mesh.indices = indices
// VBO for model UVs
if (uvs) {
let uv = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, uv)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(uvs), gl.STATIC_DRAW)
mesh.uvs = uv
mesh.uv = uvs
mesh.uvs = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(uvs))
}
// Normals buffer
if (normals) {
let nms = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, nms)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW)
mesh.nms = nms
mesh.normals = normals
mesh.nms = Mesh.loadToBuffer(gl, gl.ARRAY_BUFFER, new Float32Array(normals))
}
gl.bindBuffer(gl.ARRAY_BUFFER, null)
@ -64,8 +65,31 @@ class Mesh {
}
}
draw (gl, shader) {
gl.drawElements(gl.TRIANGLES, this.indices, gl.UNSIGNED_SHORT, 0)
draw (gl, shader, mode = gl.TRIANGLES) {
if (this.indices) {
gl.drawElements(mode, this.indices.length, gl.UNSIGNED_SHORT, 0)
} else {
gl.drawArrays(mode, 0, this.vertices.length)
}
}
// Make sure no data floats around in memory
dispose (gl) {
gl.deleteBuffer(this.posBuffer)
gl.deleteBuffer(this.ebo)
this.uvs && gl.deleteBuffer(this.uvs)
this.nms && gl.deleteBuffer(this.nms)
this.vertices = null
this.indices = null
this.uv = null
this.normals = null
}
recreate (gl) {
let msh = Mesh.construct(gl, this.vertices, this.indices, this.uv, this.normals)
if (!msh) return null
this.dispose(gl)
return msh
}
}

View File

@ -1,5 +1,5 @@
import { Mesh } from './'
import { MeshInstance, MultiMeshInstance } from '../components'
import { Node, MeshInstance } from '../components'
import { Material } from './material'
import { mat4 } from 'gl-matrix'
@ -122,7 +122,7 @@ async function assimp2json (gl, file, dat) {
}
mat4.transpose(transform, transform)
meshInstance._rootTransform = transform
meshInstance.worldTransform = transform
meshInstance.updateTransform()
}
@ -135,9 +135,10 @@ async function assimp2json (gl, file, dat) {
let returnType
if (finished.length > 1) {
returnType = new MultiMeshInstance(finished)
returnType = new Node()
returnType.addChild(finished)
mat4.transpose(lastTransform, lastTransform)
returnType._rootTransform = lastTransform
returnType.worldTransform = lastTransform
returnType.updateTransform()
} else {
returnType = finished[0]
@ -155,7 +156,12 @@ 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]) : meshCache[file][0]
if (meshCache[file]) {
if (meshCache[file].length <= 1) return meshCache[file][0]
let cached = new Node()
cached.addChild(meshCache[file])
return cached
}
let dat = await Resource.GET({ type: 'json', url: file })

View File

@ -40,8 +40,8 @@ class Material {
for (let i in this.textures) {
let tex = this.textures[i]
if (tex && tex instanceof Texture) {
gl.bindTexture(tex.type, tex.id)
gl.activeTexture(gl.TEXTURE0 + parseInt(i))
gl.bindTexture(tex.type, tex.id)
}
}
}

65
src/engine/utility.js Normal file
View File

@ -0,0 +1,65 @@
import { vec3 } from 'gl-matrix'
export function clamp (num, min, max) {
return Math.min(Math.max(num, min), max)
}
export function addv3 (one, two) {
if (one.length !== 3) return null
if (typeof two !== 'object') {
return [
one[0] + two, one[1] + two, one[2] + two
]
}
return [
one[0] + two[0], one[1] + two[1], one[2] + two[2]
]
}
export function subv3 (one, two) {
if (one.length !== 3) return null
if (typeof two !== 'object') {
return [
one[0] - two, one[1] - two, one[2] - two
]
}
return [
one[0] - two[0], one[1] - two[1], one[2] - two[2]
]
}
export function mulv3 (one, two) {
if (one.length !== 3) return null
if (typeof two !== 'object') {
return [
one[0] * two, one[1] * two, one[2] * two
]
}
return [
one[0] * two[0], one[1] * two[1], one[2] * two[2]
]
}
export function divv3 (one, two) {
if (one.length !== 3) return null
if (typeof two !== 'object') {
return [
one[0] / two, one[1] / two, one[2] / two
]
}
return [
one[0] / two[0], one[1] / two[1], one[2] / two[2]
]
}
export function normalv3 (vec) {
let res = []
vec3.normalize(res, vec)
return res
}
export function crossv3 (vec1, vec2) {
let res = []
vec3.cross(res, vec1, vec2)
return res
}

View File

@ -7,10 +7,12 @@ import { Terrain } from './engine/components/terrain'
import { SimplexHeightMap } from './engine/components/terrain/heightmap'
import { Material } from './engine/mesh/material'
import { CubePlanet } from './engine/components/planet'
let game = new Engine()
let env = new Environment()
let t = 0
// let t = 0
async function pipeline () {
let entity = await loadMesh(game.gl, 'test')
let shader = await game.shaders.createShaderFromFiles(game.gl, 'basic', false)
@ -32,9 +34,12 @@ async function pipeline () {
terrain.setMaterial(material)
// Create and initialize the camera
let cam = new Camera([0.0, 1.0, 2.0])
let cam = new Camera([-100.0, 1.0, 0.0])
cam.updateProjection(game.gl)
// Planet test
let planet = new CubePlanet([0.0, 0.0, 0.0], 16, 32)
// Update function for camera
game.addUpdateFunction(function (dt) {
if (game.input.isDown('w')) {
@ -54,8 +59,8 @@ async function pipeline () {
}
// TESTING: Move model forward
t = t + 0.1
entity.setPosition([t, 0.0, 0.0])
// t = t + 0.1
// entity.setPosition([t, 0.0, 0.0])
})
// Render function for the triangle
@ -63,6 +68,7 @@ async function pipeline () {
shader.use(gl)
cam.draw(gl, shader)
entity.draw(gl, shader)
planet.draw(gl, shader)
// Use terrain shader
terrainShader.use(gl)