start using git cuz this thing is getting pretty big
This commit is contained in:
commit
e4082406c9
15
.babelrc
Normal file
15
.babelrc
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"presets": ["@babel/preset-env"],
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"@babel/plugin-transform-runtime",
|
||||||
|
{
|
||||||
|
"corejs": false,
|
||||||
|
"helpers": true,
|
||||||
|
"regenerator": true,
|
||||||
|
"useESModules": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
# Matches multiple files with brace expansion notation
|
||||||
|
# Set default charset
|
||||||
|
[*.js]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/node_modules/
|
||||||
|
/dist/
|
||||||
|
/package-lock.json
|
9090
assets/dna.txt
Normal file
9090
assets/dna.txt
Normal file
File diff suppressed because it is too large
Load Diff
36461
assets/models/test.json
Normal file
36461
assets/models/test.json
Normal file
File diff suppressed because it is too large
Load Diff
9
assets/shaders/basic.fs
Normal file
9
assets/shaders/basic.fs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
varying vec2 uv;
|
||||||
|
|
||||||
|
uniform sampler2D texture0;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = texture2D(texture0, uv);
|
||||||
|
}
|
16
assets/shaders/basic.vs
Normal file
16
assets/shaders/basic.vs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
attribute vec3 aVertexPosition;
|
||||||
|
attribute vec2 aTexCoords;
|
||||||
|
|
||||||
|
uniform mat4 uModelMatrix;
|
||||||
|
uniform mat4 uViewMatrix;
|
||||||
|
uniform mat4 uProjectionMatrix;
|
||||||
|
|
||||||
|
varying vec2 uv;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 worldPosition = uModelMatrix * vec4(aVertexPosition, 1.0);
|
||||||
|
gl_Position = uProjectionMatrix * uViewMatrix * worldPosition;
|
||||||
|
uv = aTexCoords;
|
||||||
|
}
|
44
assets/shaders/terrain.fs
Normal file
44
assets/shaders/terrain.fs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
precision highp float;
|
||||||
|
|
||||||
|
varying vec2 uv;
|
||||||
|
varying vec3 toLightVector[8];
|
||||||
|
varying vec3 surfaceNormal;
|
||||||
|
varying vec3 toCameraVector;
|
||||||
|
|
||||||
|
uniform sampler2D texture0;
|
||||||
|
uniform vec3 lightColor[8];
|
||||||
|
uniform vec3 attenuation[8];
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float reflectivity = 0.0;
|
||||||
|
float shineDamper = 0.0;
|
||||||
|
|
||||||
|
vec3 unitNormal = normalize(surfaceNormal);
|
||||||
|
vec3 unitVectorToCamera = normalize(toCameraVector);
|
||||||
|
|
||||||
|
vec3 totalDiffuse = vec3(0.0);
|
||||||
|
vec3 totalSpecular = vec3(0.0);
|
||||||
|
|
||||||
|
for(int i=0;i<8;i++){
|
||||||
|
float distance = length(toLightVector[i]);
|
||||||
|
float attFactor = attenuation[i].x + (attenuation[i].y * distance) + (attenuation[i].z * distance * distance);
|
||||||
|
vec3 unitLightVector = normalize(toLightVector[i]);
|
||||||
|
float nDotl = dot(unitNormal,unitLightVector);
|
||||||
|
float brightness = max(nDotl,0.0);
|
||||||
|
vec3 lightDirection = -unitLightVector;
|
||||||
|
vec3 reflectedLightDirection = reflect(lightDirection, unitNormal);
|
||||||
|
float specularFactor = dot(reflectedLightDirection, unitVectorToCamera);
|
||||||
|
specularFactor = max(specularFactor,0.0);
|
||||||
|
float dampedFactor = pow(specularFactor,shineDamper);
|
||||||
|
totalDiffuse = totalDiffuse + (brightness * lightColor[i]) / attFactor;
|
||||||
|
totalSpecular = totalSpecular + (dampedFactor * reflectivity * lightColor[i]) / attFactor;
|
||||||
|
}
|
||||||
|
totalDiffuse = max(totalDiffuse,0.2);
|
||||||
|
|
||||||
|
vec4 textureColor = texture2D(texture0, uv * 40.0);
|
||||||
|
if(textureColor.a<0.5){
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
gl_FragColor = vec4(totalDiffuse,1.0) * textureColor + vec4(totalSpecular,1.0);
|
||||||
|
}
|
67
assets/shaders/terrain.vs
Normal file
67
assets/shaders/terrain.vs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
attribute vec3 aVertexPosition;
|
||||||
|
attribute vec3 aNormal;
|
||||||
|
attribute vec2 aTexCoords;
|
||||||
|
|
||||||
|
uniform mat4 uModelMatrix;
|
||||||
|
uniform mat4 uViewMatrix;
|
||||||
|
uniform mat4 uProjectionMatrix;
|
||||||
|
uniform vec3 lightPosition[8];
|
||||||
|
|
||||||
|
varying vec2 uv;
|
||||||
|
varying vec3 toLightVector[8];
|
||||||
|
varying vec3 surfaceNormal;
|
||||||
|
varying vec3 toCameraVector;
|
||||||
|
|
||||||
|
mat4 inverse(mat4 m) {
|
||||||
|
float
|
||||||
|
a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3],
|
||||||
|
a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3],
|
||||||
|
a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3],
|
||||||
|
a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3],
|
||||||
|
|
||||||
|
b00 = a00 * a11 - a01 * a10,
|
||||||
|
b01 = a00 * a12 - a02 * a10,
|
||||||
|
b02 = a00 * a13 - a03 * a10,
|
||||||
|
b03 = a01 * a12 - a02 * a11,
|
||||||
|
b04 = a01 * a13 - a03 * a11,
|
||||||
|
b05 = a02 * a13 - a03 * a12,
|
||||||
|
b06 = a20 * a31 - a21 * a30,
|
||||||
|
b07 = a20 * a32 - a22 * a30,
|
||||||
|
b08 = a20 * a33 - a23 * a30,
|
||||||
|
b09 = a21 * a32 - a22 * a31,
|
||||||
|
b10 = a21 * a33 - a23 * a31,
|
||||||
|
b11 = a22 * a33 - a23 * a32,
|
||||||
|
|
||||||
|
det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
|
||||||
|
|
||||||
|
return mat4(
|
||||||
|
a11 * b11 - a12 * b10 + a13 * b09,
|
||||||
|
a02 * b10 - a01 * b11 - a03 * b09,
|
||||||
|
a31 * b05 - a32 * b04 + a33 * b03,
|
||||||
|
a22 * b04 - a21 * b05 - a23 * b03,
|
||||||
|
a12 * b08 - a10 * b11 - a13 * b07,
|
||||||
|
a00 * b11 - a02 * b08 + a03 * b07,
|
||||||
|
a32 * b02 - a30 * b05 - a33 * b01,
|
||||||
|
a20 * b05 - a22 * b02 + a23 * b01,
|
||||||
|
a10 * b10 - a11 * b08 + a13 * b06,
|
||||||
|
a01 * b08 - a00 * b10 - a03 * b06,
|
||||||
|
a30 * b04 - a31 * b02 + a33 * b00,
|
||||||
|
a21 * b02 - a20 * b04 - a23 * b00,
|
||||||
|
a11 * b07 - a10 * b09 - a12 * b06,
|
||||||
|
a00 * b09 - a01 * b07 + a02 * b06,
|
||||||
|
a31 * b01 - a30 * b03 - a32 * b00,
|
||||||
|
a20 * b03 - a21 * b01 + a22 * b00) / det;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 worldPosition = uModelMatrix * vec4(aVertexPosition, 1.0);
|
||||||
|
gl_Position = uProjectionMatrix * uViewMatrix * worldPosition;
|
||||||
|
uv = aTexCoords;
|
||||||
|
surfaceNormal = (uModelMatrix * vec4(aNormal,0.0)).xyz;
|
||||||
|
for(int i=0;i<8;i++){
|
||||||
|
toLightVector[i] = lightPosition[i] - worldPosition.xyz;
|
||||||
|
}
|
||||||
|
toCameraVector = (inverse(uViewMatrix) * vec4(0.0,0.0,0.0,1.0)).xyz - worldPosition.xyz;
|
||||||
|
}
|
BIN
assets/textures/grass-1024.jpg
Normal file
BIN
assets/textures/grass-1024.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 525 KiB |
BIN
assets/textures/heightmap.jpg
Normal file
BIN
assets/textures/heightmap.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
assets/textures/noisy.png
Normal file
BIN
assets/textures/noisy.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 159 KiB |
10
index.html
Normal file
10
index.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<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>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
34
package.json
Normal file
34
package.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "trotland-game",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Trotland 3D MMORPG",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"serve": "node ./serve.js",
|
||||||
|
"build": "webpack -p",
|
||||||
|
"watch": "webpack -w --mode=development"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"game",
|
||||||
|
"webgl"
|
||||||
|
],
|
||||||
|
"private": true,
|
||||||
|
"author": "Evert \"Diamond\" Prants <evert@lunasqu.ee>",
|
||||||
|
"license": "LGPL-3.0-or-later",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.1.6",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.1.0",
|
||||||
|
"@babel/preset-env": "^7.1.6",
|
||||||
|
"babel-loader": "^8.0.4",
|
||||||
|
"express": "^4.16.4",
|
||||||
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"standard": "^12.0.1",
|
||||||
|
"webpack": "^4.26.0",
|
||||||
|
"webpack-command": "^0.4.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.1.5",
|
||||||
|
"gl-matrix": "^2.8.1",
|
||||||
|
"open-simplex-noise": "^1.5.0"
|
||||||
|
}
|
||||||
|
}
|
11
serve.js
Executable file
11
serve.js
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const express = require('express')
|
||||||
|
const path = require('path')
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
app.use('/assets', express.static(path.join(__dirname, 'assets')))
|
||||||
|
app.use('/', express.static(path.join(__dirname, 'dist')))
|
||||||
|
|
||||||
|
app.listen(3000, function () {
|
||||||
|
console.log('server listening on 0.0.0.0:3000')
|
||||||
|
})
|
126
src/engine/camera.js
Normal file
126
src/engine/camera.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { Node } from './mesh'
|
||||||
|
import { glMatrix, mat4, vec2, vec3 } from 'gl-matrix'
|
||||||
|
|
||||||
|
const SPEED = 100.0
|
||||||
|
const SENSITIVTY = 100.0
|
||||||
|
const FOV = 45.0
|
||||||
|
const ZNEAR = 0.1
|
||||||
|
const ZFAR = 1000.0
|
||||||
|
|
||||||
|
class Camera extends Node {
|
||||||
|
constructor (pos, rotation) {
|
||||||
|
super(pos, rotation)
|
||||||
|
this.fov = FOV
|
||||||
|
this.speed = SPEED
|
||||||
|
this.sensitivity = SENSITIVTY
|
||||||
|
|
||||||
|
// Create an empty projection matrix
|
||||||
|
this.projection = mat4.create()
|
||||||
|
|
||||||
|
// Helping vectors for calculating the view matrix
|
||||||
|
this.up = vec3.create()
|
||||||
|
this.front = vec3.fromValues(0.0, 0.0, -1.0)
|
||||||
|
this.right = vec3.create()
|
||||||
|
this.worldUp = vec3.fromValues(0.0, 1.0, 0.0)
|
||||||
|
|
||||||
|
this.updateTransform()
|
||||||
|
}
|
||||||
|
|
||||||
|
processKeyboard (direction, delta) {
|
||||||
|
let newSpeed = this.speed * delta
|
||||||
|
let velocity = vec3.fromValues(newSpeed, newSpeed, newSpeed)
|
||||||
|
let vec = vec3.create()
|
||||||
|
|
||||||
|
if (direction === 0) {
|
||||||
|
vec3.multiply(vec, this.front, velocity)
|
||||||
|
vec3.add(this.pos, this.pos, vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === 1) {
|
||||||
|
vec3.multiply(vec, this.front, velocity)
|
||||||
|
vec3.sub(this.pos, this.pos, vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === 2) {
|
||||||
|
vec3.multiply(vec, this.right, velocity)
|
||||||
|
vec3.sub(this.pos, this.pos, vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === 3) {
|
||||||
|
vec3.multiply(vec, this.right, velocity)
|
||||||
|
vec3.add(this.pos, this.pos, vec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processMouseMove (offset, constrain = true) {
|
||||||
|
let fst = vec2.fromValues(offset.x * this.sensitivity, offset.y * this.sensitivity)
|
||||||
|
this.rotation[0] += glMatrix.toRadian(fst[0])
|
||||||
|
this.rotation[1] += glMatrix.toRadian(fst[1])
|
||||||
|
|
||||||
|
// Make sure that when pitch is out of bounds, screen doesn't get flipped
|
||||||
|
if (constrain) {
|
||||||
|
if (this.rotation[1] > glMatrix.toRadian(89.0)) {
|
||||||
|
this.rotation[1] = glMatrix.toRadian(89.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rotation[1] < -glMatrix.toRadian(89.0)) {
|
||||||
|
this.rotation[1] = -glMatrix.toRadian(89.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateTransform()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the vertices required for the view matrix
|
||||||
|
updateTransform () {
|
||||||
|
// Prevent premature call (from super class)
|
||||||
|
if (!this.front || !this.worldUp) return
|
||||||
|
|
||||||
|
// Calculate the new Front vector
|
||||||
|
let front = vec3.create()
|
||||||
|
|
||||||
|
front[0] = Math.cos(this.rotation[0]) * Math.cos(this.rotation[1])
|
||||||
|
front[1] = Math.sin(this.rotation[1])
|
||||||
|
front[2] = Math.sin(this.rotation[0]) * Math.cos(this.rotation[1])
|
||||||
|
|
||||||
|
vec3.normalize(this.front, front)
|
||||||
|
|
||||||
|
// Also re-calculate the Right and Up vector
|
||||||
|
// Normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
|
||||||
|
let rightCross = vec3.create()
|
||||||
|
let upCross = vec3.create()
|
||||||
|
|
||||||
|
vec3.cross(rightCross, this.front, this.worldUp)
|
||||||
|
vec3.normalize(this.right, rightCross)
|
||||||
|
|
||||||
|
vec3.cross(upCross, this.right, this.front)
|
||||||
|
vec3.normalize(this.up, upCross)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProjection (gl) {
|
||||||
|
let aspect = gl.canvas.width / gl.canvas.height
|
||||||
|
mat4.perspective(this.projection, this.fov, aspect, ZNEAR, ZFAR)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the view matrix on-the-go
|
||||||
|
// Really no advantage in storing this
|
||||||
|
get view () {
|
||||||
|
let mat = mat4.create()
|
||||||
|
let center = vec3.create()
|
||||||
|
vec3.add(center, this.pos, this.front)
|
||||||
|
mat4.lookAt(mat, this.pos, center, this.up)
|
||||||
|
return mat
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override the default draw method because we don't need to draw the camera,
|
||||||
|
// instead set the projection and view matrices
|
||||||
|
draw (gl, shader) {
|
||||||
|
const projloc = shader.getUniformLocation(gl, 'uProjectionMatrix')
|
||||||
|
const viewloc = shader.getUniformLocation(gl, 'uViewMatrix')
|
||||||
|
|
||||||
|
gl.uniformMatrix4fv(projloc, false, this.projection)
|
||||||
|
gl.uniformMatrix4fv(viewloc, false, this.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Camera
|
72
src/engine/components/terrain/heightmap.js
Normal file
72
src/engine/components/terrain/heightmap.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import OpenSimplexNoise from 'open-simplex-noise'
|
||||||
|
import { vec3 } from 'gl-matrix'
|
||||||
|
import Resource from '../../resource'
|
||||||
|
|
||||||
|
class HeightMap {
|
||||||
|
constructor (size) {
|
||||||
|
this.size = size
|
||||||
|
}
|
||||||
|
|
||||||
|
static async fromFile (file, amplitude) {
|
||||||
|
let img = await Resource.loadImage(file)
|
||||||
|
if (img.width / img.height !== 1) throw new Error('Height Map needs to be of 1:1 aspect ratio.')
|
||||||
|
|
||||||
|
let hmap = new HeightMap(img.width)
|
||||||
|
let sampler = Resource.imageToSampler(img)
|
||||||
|
|
||||||
|
for (let x = 0; x < img.width; x++) {
|
||||||
|
for (let y = 0; y < img.width; y++) {
|
||||||
|
hmap['h' + x + ';' + y] = (sampler(x, y)[0] / 255 * 2 - 1) * amplitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sampler = null
|
||||||
|
|
||||||
|
return hmap
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeight (x, y) {
|
||||||
|
if (x > this.size || y > this.size || x < 0 || y < 0) return 0
|
||||||
|
if (!this['h' + x + ';' + y]) return 0
|
||||||
|
return this['h' + x + ';' + y]
|
||||||
|
}
|
||||||
|
|
||||||
|
getNormal (x, y) {
|
||||||
|
let hL = this.getHeight(x - 1, y)
|
||||||
|
let hR = this.getHeight(x + 1, y)
|
||||||
|
let hD = this.getHeight(x, y - 1)
|
||||||
|
let hU = this.getHeight(x, y + 1)
|
||||||
|
let normal = vec3.fromValues(hL - hR, 2.0, hD - hU)
|
||||||
|
vec3.normalize(normal, normal)
|
||||||
|
return normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimplexHeightMap extends HeightMap {
|
||||||
|
constructor (offsetX, offsetY, size, seed) {
|
||||||
|
super(size)
|
||||||
|
this.ix = offsetX
|
||||||
|
this.iy = offsetY
|
||||||
|
this.seed = seed
|
||||||
|
|
||||||
|
this.osn = new OpenSimplexNoise(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
total *= 10
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeight (x, y) {
|
||||||
|
return this.getNoise(x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { HeightMap, SimplexHeightMap }
|
67
src/engine/components/terrain/index.js
Normal file
67
src/engine/components/terrain/index.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Mesh, Node } from '../../mesh'
|
||||||
|
|
||||||
|
class Terrain extends Node {
|
||||||
|
constructor (pos, sWidth, sHeight) {
|
||||||
|
super(pos)
|
||||||
|
|
||||||
|
this.width = sWidth
|
||||||
|
this.height = sHeight
|
||||||
|
|
||||||
|
this.mesh = null
|
||||||
|
}
|
||||||
|
|
||||||
|
createMesh (gl, heightMap) {
|
||||||
|
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++) {
|
||||||
|
vertices[vertexPointer * 3] = j / (VERTICES - 1) * this.width
|
||||||
|
vertices[vertexPointer * 3 + 1] = heightMap.getHeight(j, i)
|
||||||
|
vertices[vertexPointer * 3 + 2] = i / (VERTICES - 1) * this.height
|
||||||
|
let normal = 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
|
||||||
|
super.draw(gl, shader)
|
||||||
|
this.mesh.draw(gl, shader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Terrain }
|
102
src/engine/environment.js
Normal file
102
src/engine/environment.js
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
|
||||||
|
const ENV_MAX_LIGHTS = 8
|
||||||
|
|
||||||
|
class Light {
|
||||||
|
constructor (pos, color, attenuation = [1.0, 0.0, 0.0]) {
|
||||||
|
this.pos = pos
|
||||||
|
this.color = color
|
||||||
|
this.attenuation = attenuation
|
||||||
|
}
|
||||||
|
|
||||||
|
setPosition (pos) {
|
||||||
|
this.pos = pos
|
||||||
|
}
|
||||||
|
|
||||||
|
setColor (col) {
|
||||||
|
this.color = col
|
||||||
|
}
|
||||||
|
|
||||||
|
setAttenuation (attn) {
|
||||||
|
this.attenuation = attn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SpotLight extends Light {
|
||||||
|
constructor (pos, dir, color, attenuation) {
|
||||||
|
super(pos, color, attenuation)
|
||||||
|
this.dir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirectionalLight extends SpotLight {
|
||||||
|
constructor (pos, dir, color) {
|
||||||
|
super(pos, dir, color, [1.0, 0.0, 0.0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Environment {
|
||||||
|
constructor (ambient) {
|
||||||
|
this.ambient = ambient
|
||||||
|
|
||||||
|
this.fogStart = 0
|
||||||
|
this.fogEnd = 0
|
||||||
|
this.fogColor = [0.8, 0.8, 0.8]
|
||||||
|
|
||||||
|
this.sun = new DirectionalLight([0.0, 1000.0, 2000.0], [-1.0, -1.0, 0.0], [1.0, 1.0, 1.0])
|
||||||
|
this.lights = [ this.sun ]
|
||||||
|
|
||||||
|
this.maxEnvironmentLights = ENV_MAX_LIGHTS
|
||||||
|
}
|
||||||
|
|
||||||
|
addLight (l) {
|
||||||
|
if (!(l instanceof Light)) return
|
||||||
|
this.lights.push(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
addSpotLight (pos, dir, color, attenuation) {
|
||||||
|
this.lights.push(new SpotLight(pos, dir, color, attenuation))
|
||||||
|
}
|
||||||
|
|
||||||
|
addPointLight (pos, color, attenuation) {
|
||||||
|
this.lights.push(new Light(pos, color, attenuation))
|
||||||
|
}
|
||||||
|
|
||||||
|
setSunlight (pos, dir, color, attenuation) {
|
||||||
|
this.sun = new DirectionalLight(pos, dir, color, attenuation)
|
||||||
|
this.lights[0] = this.sun
|
||||||
|
}
|
||||||
|
|
||||||
|
setAmbient (color) {
|
||||||
|
this.ambient = color
|
||||||
|
}
|
||||||
|
|
||||||
|
setFog (color, start, end) {
|
||||||
|
this.fogColor = color
|
||||||
|
this.fogStart = start
|
||||||
|
this.fogEnd = end
|
||||||
|
}
|
||||||
|
|
||||||
|
setMaxLights (num) {
|
||||||
|
this.maxEnvironmentLights = num
|
||||||
|
}
|
||||||
|
|
||||||
|
draw (gl, shader) {
|
||||||
|
for (let i = 0; i < this.maxEnvironmentLights; i++) {
|
||||||
|
let lightColor = shader.getUniformLocation(gl, 'lightColor[' + i + ']')
|
||||||
|
let lightPosition = shader.getUniformLocation(gl, 'lightPosition[' + i + ']')
|
||||||
|
let lightAttn = shader.getUniformLocation(gl, 'attenuation[' + i + ']')
|
||||||
|
|
||||||
|
if (this.lights[i]) {
|
||||||
|
gl.uniform3fv(lightColor, this.lights[i].color)
|
||||||
|
gl.uniform3fv(lightPosition, this.lights[i].pos)
|
||||||
|
gl.uniform3fv(lightAttn, this.lights[i].attenuation)
|
||||||
|
} else {
|
||||||
|
gl.uniform3fv(lightColor, [0.0, 0.0, 0.0])
|
||||||
|
gl.uniform3fv(lightPosition, [0.0, 0.0, 0.0])
|
||||||
|
gl.uniform3fv(lightAttn, [1.0, 0.0, 0.0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Environment, Light, SpotLight, DirectionalLight }
|
89
src/engine/index.js
Normal file
89
src/engine/index.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/* global performance */
|
||||||
|
import Screen from './screen'
|
||||||
|
import Input from './input'
|
||||||
|
import { ShaderManager } from './shader'
|
||||||
|
|
||||||
|
class Engine {
|
||||||
|
constructor () {
|
||||||
|
this.screen = new Screen()
|
||||||
|
this.input = new Input(this.screen.gl.canvas)
|
||||||
|
this.shaders = new ShaderManager()
|
||||||
|
this.running = false
|
||||||
|
|
||||||
|
// Queues
|
||||||
|
this.rst = []
|
||||||
|
this.ust = []
|
||||||
|
|
||||||
|
this.frameTime = 0
|
||||||
|
this.frameCount = 0
|
||||||
|
this.fps = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
get gl () {
|
||||||
|
return this.screen.gl
|
||||||
|
}
|
||||||
|
|
||||||
|
render (gl) {
|
||||||
|
// Set clear color to black, fully opaque
|
||||||
|
gl.clearColor(0.0, 0.7, 1.0, 1.0)
|
||||||
|
|
||||||
|
// Clear the color buffer with specified clear color
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||||
|
|
||||||
|
// Enable depth testing
|
||||||
|
gl.enable(gl.DEPTH_TEST)
|
||||||
|
|
||||||
|
// Enable back-face culling
|
||||||
|
gl.enable(gl.CULL_FACE)
|
||||||
|
gl.cullFace(gl.BACK)
|
||||||
|
|
||||||
|
// Render functions
|
||||||
|
for (let i in this.rst) {
|
||||||
|
this.rst[i](gl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update (dt) {
|
||||||
|
this.input.update()
|
||||||
|
|
||||||
|
// Updates
|
||||||
|
for (let i in this.ust) {
|
||||||
|
this.ust[i](dt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step () {
|
||||||
|
this.running && window.requestAnimationFrame(() => this.step())
|
||||||
|
|
||||||
|
this.render(this.gl)
|
||||||
|
|
||||||
|
let ts = performance.now()
|
||||||
|
let timeDiff = ts - this.frameTime // time difference in milliseconds
|
||||||
|
this.frameCount++
|
||||||
|
|
||||||
|
if (timeDiff > 0) {
|
||||||
|
this.fps = Math.floor(this.frameCount / timeDiff * 1000)
|
||||||
|
this.frameCount = 0
|
||||||
|
this.frameTime = ts
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update(timeDiff / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
addRenderFunction (fn) {
|
||||||
|
this.rst.push(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
addUpdateFunction (fn) {
|
||||||
|
this.ust.push(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
startGameLoop () {
|
||||||
|
if (this.running) throw new Error('Game Loop is already running!')
|
||||||
|
|
||||||
|
this.running = true
|
||||||
|
this.step()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Engine
|
222
src/engine/input.js
Normal file
222
src/engine/input.js
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
|
||||||
|
const specialKeyMap = {
|
||||||
|
'backspace': 8,
|
||||||
|
'tab': 9,
|
||||||
|
'enter': 13,
|
||||||
|
'shift': 16,
|
||||||
|
'ctrl': 17,
|
||||||
|
'alt': 18,
|
||||||
|
'pausebreak': 19,
|
||||||
|
'capslock': 20,
|
||||||
|
'escape': 27,
|
||||||
|
'pgup': 33,
|
||||||
|
'pgdown': 34,
|
||||||
|
'end': 35,
|
||||||
|
'home': 36,
|
||||||
|
'left': 37,
|
||||||
|
'up': 38,
|
||||||
|
'right': 39,
|
||||||
|
'down': 40,
|
||||||
|
'insert': 45,
|
||||||
|
'delete': 46,
|
||||||
|
'left-window': 91,
|
||||||
|
'right-window': 92,
|
||||||
|
'select': 93,
|
||||||
|
'numpad0': 96,
|
||||||
|
'numpad1': 97,
|
||||||
|
'numpad2': 98,
|
||||||
|
'numpad3': 99,
|
||||||
|
'numpad4': 100,
|
||||||
|
'numpad5': 101,
|
||||||
|
'numpad6': 102,
|
||||||
|
'numpad7': 103,
|
||||||
|
'numpad8': 104,
|
||||||
|
'numpad9': 105,
|
||||||
|
'multiply': 106,
|
||||||
|
'add': 107,
|
||||||
|
'subtract': 109,
|
||||||
|
'decimal': 110,
|
||||||
|
'divide': 111,
|
||||||
|
'f1': 112,
|
||||||
|
'f2': 113,
|
||||||
|
'f3': 114,
|
||||||
|
'f4': 115,
|
||||||
|
'f5': 116,
|
||||||
|
'f6': 117,
|
||||||
|
'f7': 118,
|
||||||
|
'f8': 119,
|
||||||
|
'f9': 120,
|
||||||
|
'f10': 121,
|
||||||
|
'f11': 122,
|
||||||
|
'f12': 123,
|
||||||
|
'numlock': 144,
|
||||||
|
'scrolllock': 145,
|
||||||
|
'semi-colon': 186,
|
||||||
|
'equals': 187,
|
||||||
|
'comma': 188,
|
||||||
|
'dash': 189,
|
||||||
|
'period': 190,
|
||||||
|
'fwdslash': 191,
|
||||||
|
'grave': 192,
|
||||||
|
'open-bracket': 219,
|
||||||
|
'bkslash': 220,
|
||||||
|
'close-braket': 221,
|
||||||
|
'single-quote': 222
|
||||||
|
}
|
||||||
|
|
||||||
|
class Input {
|
||||||
|
constructor (canvas) {
|
||||||
|
this.keyList = {}
|
||||||
|
this.previousKeyList = {}
|
||||||
|
this.canvas = canvas
|
||||||
|
|
||||||
|
this.mouse = {
|
||||||
|
pos: { x: 0, y: 0 },
|
||||||
|
frame: { x: 0, y: 0 },
|
||||||
|
previous: { x: 0, y: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', (e) => this.keyDown(e), false)
|
||||||
|
window.addEventListener('keyup', (e) => this.keyUp(e), false)
|
||||||
|
|
||||||
|
canvas.addEventListener('mousemove', (e) => {
|
||||||
|
let x
|
||||||
|
let y
|
||||||
|
|
||||||
|
if (e.changedTouches) {
|
||||||
|
let touch = e.changedTouches[0]
|
||||||
|
if (touch) {
|
||||||
|
e.pageX = touch.pageX
|
||||||
|
e.pageY = touch.pageY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.pageX || e.pageY) {
|
||||||
|
x = e.pageX
|
||||||
|
y = e.pageY
|
||||||
|
} else {
|
||||||
|
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft
|
||||||
|
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop
|
||||||
|
}
|
||||||
|
|
||||||
|
x -= canvas.offsetLeft
|
||||||
|
y -= canvas.offsetTop
|
||||||
|
|
||||||
|
this.mouse.frame.x = x
|
||||||
|
this.mouse.frame.y = y
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
canvas.addEventListener('mousedown', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.mouse['btn' + e.button] = true
|
||||||
|
})
|
||||||
|
|
||||||
|
canvas.addEventListener('mouseup', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
this.mouse['btn' + e.button] = false
|
||||||
|
})
|
||||||
|
|
||||||
|
canvas.addEventListener('contextmenu', (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get mousePos () {
|
||||||
|
return this.mouse.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
get mouseMoved () {
|
||||||
|
return (this.mouse.pos.x !== this.mouse.previous.x ||
|
||||||
|
this.mouse.pos.y !== this.mouse.previous.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
get mouseOffset () {
|
||||||
|
return {
|
||||||
|
x: this.mouse.previous.x - this.mouse.pos.x,
|
||||||
|
y: this.mouse.previous.y - this.mouse.pos.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleKey (keyCode, on) {
|
||||||
|
// Find key in special key list
|
||||||
|
let key = null
|
||||||
|
for (let k in specialKeyMap) {
|
||||||
|
let val = specialKeyMap[k]
|
||||||
|
if (keyCode === val) {
|
||||||
|
key = k
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use fromCharCode
|
||||||
|
if (!key) {
|
||||||
|
key = String.fromCharCode(keyCode).toLowerCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.keyList[key] = (on === true)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyDown (e) {
|
||||||
|
let keycode
|
||||||
|
|
||||||
|
if (window.event) {
|
||||||
|
keycode = window.event.keyCode
|
||||||
|
} else if (e) {
|
||||||
|
keycode = e.which
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleKey(keycode, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyUp (e) {
|
||||||
|
let keycode
|
||||||
|
|
||||||
|
if (window.event) {
|
||||||
|
keycode = window.event.keyCode
|
||||||
|
} else if (e) {
|
||||||
|
keycode = e.which
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggleKey(keycode, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
down (key) {
|
||||||
|
return this.keyList[key] != null ? this.keyList[key] : false
|
||||||
|
}
|
||||||
|
|
||||||
|
downLast (key) {
|
||||||
|
return this.previousKeyList[key] != null ? this.previousKeyList[key] : false
|
||||||
|
}
|
||||||
|
|
||||||
|
isDown (key) {
|
||||||
|
return this.down(key) && this.downLast(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
isUp (key) {
|
||||||
|
return !this.isDown(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
isPressed (key) {
|
||||||
|
return this.down(key) === true && this.downLast(key) === false
|
||||||
|
}
|
||||||
|
|
||||||
|
update () {
|
||||||
|
this.previousKeyList = {}
|
||||||
|
for (let k in this.keyList) {
|
||||||
|
if (this.keyList[k] === true) {
|
||||||
|
this.previousKeyList[k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse positions in the previous frame
|
||||||
|
this.mouse.previous.x = this.mouse.pos.x
|
||||||
|
this.mouse.previous.y = this.mouse.pos.y
|
||||||
|
|
||||||
|
// Mouse positions in the current frame
|
||||||
|
// Convert to OpenGL coordinate system
|
||||||
|
this.mouse.pos.x = this.mouse.frame.x / this.canvas.width * 2 - 1
|
||||||
|
this.mouse.pos.y = this.mouse.frame.y / this.canvas.height * 2 - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Input
|
33
src/engine/mesh/animation/index.js
Normal file
33
src/engine/mesh/animation/index.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import Entity from '../entity'
|
||||||
|
|
||||||
|
class AnimatedEntity extends Entity {
|
||||||
|
constructor (mesh, pos, scale, rotation) {
|
||||||
|
super(mesh, pos, scale, rotation)
|
||||||
|
|
||||||
|
this.animPlayer = null
|
||||||
|
this.animation = null
|
||||||
|
this.keyframe = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
setAnimation (name) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
playAnimation () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
stopAnimation () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
update (dt) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
draw (gl, shader) {
|
||||||
|
super.draw(gl, shader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnimatedEntity
|
32
src/engine/mesh/entity.js
Normal file
32
src/engine/mesh/entity.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Mesh, Node } from './index'
|
||||||
|
|
||||||
|
// Entity is just a Mesh with extra functionality.
|
||||||
|
class Entity extends Node {
|
||||||
|
constructor (mesh, pos, scale, rotation) {
|
||||||
|
super(pos, scale, rotation)
|
||||||
|
this.mesh = mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drawing related
|
||||||
|
update (dt) {
|
||||||
|
// Override this!
|
||||||
|
}
|
||||||
|
|
||||||
|
draw (gl, shader) {
|
||||||
|
// Set model transform matrix uniform
|
||||||
|
const modelloc = shader.getUniformLocation(gl, 'uModelMatrix')
|
||||||
|
gl.uniformMatrix4fv(modelloc, false, this.transform)
|
||||||
|
|
||||||
|
// Draw the mesh
|
||||||
|
this.mesh.draw(gl, shader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generators
|
||||||
|
static async createEntity (gl, meshName, pos) {
|
||||||
|
let mesh = await Mesh.loadFile(gl, meshName)
|
||||||
|
let entity = new Entity(mesh, pos)
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Entity
|
287
src/engine/mesh/index.js
Normal file
287
src/engine/mesh/index.js
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
import Resource from '../resource'
|
||||||
|
import { Texture, Material } from './material'
|
||||||
|
import { mat4 } from 'gl-matrix'
|
||||||
|
|
||||||
|
let meshCache = {}
|
||||||
|
|
||||||
|
class Node {
|
||||||
|
constructor (pos, scale, rotation) {
|
||||||
|
this.pos = pos || [0.0, 0.0, 0.0]
|
||||||
|
this.scale = scale || [1.0, 1.0, 1.0]
|
||||||
|
this.rotation = rotation || [0.0, 0.0, 0.0]
|
||||||
|
|
||||||
|
this.transform = mat4.create()
|
||||||
|
this.updateTransform()
|
||||||
|
|
||||||
|
this.parent = null
|
||||||
|
this.children = []
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTransform () {
|
||||||
|
let matrix = mat4.create()
|
||||||
|
|
||||||
|
// Set translation
|
||||||
|
mat4.translate(matrix, matrix, this.pos)
|
||||||
|
|
||||||
|
// Set scale
|
||||||
|
mat4.scale(matrix, matrix, this.scale)
|
||||||
|
|
||||||
|
// Set rotation
|
||||||
|
if (this.rotation[0] !== 0) {
|
||||||
|
mat4.rotateX(matrix, matrix, this.rotation[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rotation[1] !== 0) {
|
||||||
|
mat4.rotateY(matrix, matrix, this.rotation[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.rotation[2] !== 0) {
|
||||||
|
mat4.rotateZ(matrix, matrix, this.rotation[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add parent node's transform
|
||||||
|
if (this.parent) {
|
||||||
|
mat4.add(matrix, this.parent.transform, matrix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the matrix
|
||||||
|
this.transform = matrix
|
||||||
|
|
||||||
|
// Update children's transforms
|
||||||
|
for (let i in this.children) {
|
||||||
|
let child = this.children[i]
|
||||||
|
if (!(child instanceof Node)) continue
|
||||||
|
child.updateTransform()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setters
|
||||||
|
setPosition (newPos) {
|
||||||
|
this.pos = newPos
|
||||||
|
this.updateTransform()
|
||||||
|
}
|
||||||
|
|
||||||
|
setScale (newScale) {
|
||||||
|
this.scale = newScale
|
||||||
|
this.updateTransform()
|
||||||
|
}
|
||||||
|
|
||||||
|
setRotation (newRotation) {
|
||||||
|
this.rotation = newRotation
|
||||||
|
this.updateTransform()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transforms
|
||||||
|
translate (pos) {
|
||||||
|
mat4.translate(this.transform, this.transform, pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
scale (scale) {
|
||||||
|
mat4.scale(this.transform, this.transform, scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
rotate (rot) {
|
||||||
|
if (rot[0] !== 0) {
|
||||||
|
mat4.rotateX(this.transform, this.transform, rot[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rot[1] !== 0) {
|
||||||
|
mat4.rotateY(this.transform, this.transform, rot[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rot[2] !== 0) {
|
||||||
|
mat4.rotateZ(this.transform, this.transform, rot[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
get position () {
|
||||||
|
return this.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw base
|
||||||
|
draw (gl, shader) {
|
||||||
|
// Set model transform matrix uniform
|
||||||
|
const transformLocation = shader.getUniformLocation(gl, 'uModelMatrix')
|
||||||
|
gl.uniformMatrix4fv(transformLocation, false, this.transform)
|
||||||
|
}
|
||||||
|
|
||||||
|
addChild (ch) {
|
||||||
|
if (!(ch instanceof Node)) return
|
||||||
|
this.children.push(ch)
|
||||||
|
this.updateTransform()
|
||||||
|
}
|
||||||
|
|
||||||
|
setParent (p) {
|
||||||
|
if (!(p instanceof Node)) return
|
||||||
|
this.parent = p
|
||||||
|
this.updateTransform()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Mesh extends Node {
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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 mesh = new Mesh()
|
||||||
|
mesh.pos = pos
|
||||||
|
mesh.ebo = ebo
|
||||||
|
mesh.indices = indices.length
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, null)
|
||||||
|
|
||||||
|
return mesh
|
||||||
|
}
|
||||||
|
|
||||||
|
static async loadFile (gl, file) {
|
||||||
|
file = '/assets/models/' + file + '.json'
|
||||||
|
|
||||||
|
// Ensure each mesh file is loaded only once
|
||||||
|
if (meshCache[file]) return meshCache[file].length > 1 ? meshCache[file] : meshCache[file][0]
|
||||||
|
|
||||||
|
let dat = await Resource.GET({ type: 'json', url: file })
|
||||||
|
if (!dat.meshes) throw new Error('No meshes defined in file.')
|
||||||
|
|
||||||
|
let cleaned = []
|
||||||
|
let materials = []
|
||||||
|
for (let mi in dat.meshes) {
|
||||||
|
let mesh = dat.meshes[mi]
|
||||||
|
let material
|
||||||
|
|
||||||
|
if (mesh.materialindex != null && dat.materials && dat.materials.length) {
|
||||||
|
// Ensure we don't re-create materials with the same index
|
||||||
|
if (materials[mesh.materialindex]) {
|
||||||
|
material = materials[mesh.materialindex]
|
||||||
|
} else {
|
||||||
|
// Load a new material
|
||||||
|
material = new Material()
|
||||||
|
let matdata = dat.materials[mesh.materialindex].properties
|
||||||
|
|
||||||
|
// Parse material information
|
||||||
|
for (let pi in matdata) {
|
||||||
|
let property = matdata[pi]
|
||||||
|
if (!property || !property.key) continue
|
||||||
|
if (property.key === '?mat.name') material.name = property.value
|
||||||
|
else if (property.key.indexOf('$clr.') === 0) {
|
||||||
|
let dproperty = property.key.substr(5)
|
||||||
|
switch (dproperty) {
|
||||||
|
case 'specular':
|
||||||
|
case 'diffuse':
|
||||||
|
case 'shininess':
|
||||||
|
case 'ambient':
|
||||||
|
case 'reflective':
|
||||||
|
material[dproperty] = property.value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if (property.key.indexOf('$tex.file') === 0) {
|
||||||
|
if (!material.textures) {
|
||||||
|
material.textures = []
|
||||||
|
}
|
||||||
|
|
||||||
|
material.textures.push(property.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
materials[mesh.materialindex] = material
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleaned.push({
|
||||||
|
vertices: mesh.vertices,
|
||||||
|
indices: [].concat.apply([], mesh.faces),
|
||||||
|
uv: mesh.texturecoords ? mesh.texturecoords[0] : null,
|
||||||
|
normals: mesh.normals ? mesh.normals : null,
|
||||||
|
material
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let finished = []
|
||||||
|
for (let i in cleaned) {
|
||||||
|
let meshdata = cleaned[i]
|
||||||
|
let mesh = Mesh.construct(gl, meshdata.vertices, meshdata.indices,
|
||||||
|
meshdata.uv, meshdata.normals)
|
||||||
|
|
||||||
|
// Initialize the material's texture if present
|
||||||
|
if (meshdata.material) {
|
||||||
|
mesh.material = meshdata.material
|
||||||
|
|
||||||
|
// Ensure all textures get loaded before finishing
|
||||||
|
if (meshdata.material.textures) {
|
||||||
|
await meshdata.material.loadTextures(gl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finished.push(mesh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the mesh
|
||||||
|
meshCache[file] = finished
|
||||||
|
|
||||||
|
return finished.length > 1 ? finished : finished[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
bindBuffers (gl, shader) {
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.pos)
|
||||||
|
shader.setAttribute(gl, 'aVertexPosition', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0)
|
||||||
|
|
||||||
|
if (this.nms && shader.hasAttribute(gl, 'aNormal')) {
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.nms)
|
||||||
|
shader.setAttribute(gl, 'aNormal', 3, false, 3 * Float32Array.BYTES_PER_ELEMENT, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.uvs && shader.hasAttribute(gl, 'aTexCoords')) {
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.uvs)
|
||||||
|
shader.setAttribute(gl, 'aTexCoords', 2, false, 2 * Float32Array.BYTES_PER_ELEMENT, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.ebo)
|
||||||
|
}
|
||||||
|
|
||||||
|
draw (gl, shader) {
|
||||||
|
// Set model transform uniform
|
||||||
|
super.draw(gl, shader)
|
||||||
|
|
||||||
|
// Bind attrib arrays
|
||||||
|
this.bindBuffers(gl, shader)
|
||||||
|
|
||||||
|
// Give materials to shader
|
||||||
|
if (this.material) {
|
||||||
|
this.material.apply(gl, shader)
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.drawElements(gl.TRIANGLES, this.indices, gl.UNSIGNED_SHORT, 0)
|
||||||
|
|
||||||
|
// Invoke children's draw methods
|
||||||
|
for (let i in this.children) {
|
||||||
|
let child = this.children[i]
|
||||||
|
if (!(child instanceof Mesh)) continue
|
||||||
|
child.draw(gl, shader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Node, Mesh, Texture, Material }
|
50
src/engine/mesh/material.js
Normal file
50
src/engine/mesh/material.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import Resource from '../resource'
|
||||||
|
|
||||||
|
class Texture {
|
||||||
|
static createTexture2D (gl, img) {
|
||||||
|
let tex = gl.createTexture()
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, tex)
|
||||||
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true)
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img)
|
||||||
|
|
||||||
|
gl.generateMipmap(gl.TEXTURE_2D)
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, null)
|
||||||
|
|
||||||
|
let oTex = new Texture()
|
||||||
|
oTex.type = gl.TEXTURE_2D
|
||||||
|
oTex.id = tex
|
||||||
|
|
||||||
|
return oTex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Material {
|
||||||
|
async loadTextures (gl) {
|
||||||
|
if (this.textures) {
|
||||||
|
for (let ti in this.textures) {
|
||||||
|
let tex = this.textures[ti]
|
||||||
|
if (!(tex instanceof Texture)) {
|
||||||
|
let teximg = await Resource.loadImage(tex)
|
||||||
|
let result = Texture.createTexture2D(gl, teximg)
|
||||||
|
this.textures[ti] = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apply (gl, shader) {
|
||||||
|
// TODO: lighting related things
|
||||||
|
|
||||||
|
// Load textures
|
||||||
|
if (!this.textures || !this.textures.length) return
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Texture, Material }
|
88
src/engine/resource.js
Normal file
88
src/engine/resource.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/* global XMLHttpRequest, Image */
|
||||||
|
|
||||||
|
let imgCache = {}
|
||||||
|
|
||||||
|
function GET (url, istext) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var xmlHttp = new XMLHttpRequest()
|
||||||
|
|
||||||
|
xmlHttp.onreadystatechange = function () {
|
||||||
|
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
|
||||||
|
resolve(xmlHttp.responseText)
|
||||||
|
} else if (xmlHttp.readyState === 4 && xmlHttp.status >= 400) {
|
||||||
|
let err = new Error(xmlHttp.status)
|
||||||
|
err.request = xmlHttp
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlHttp.open('GET', url, true)
|
||||||
|
istext && (xmlHttp.responseType = 'text')
|
||||||
|
xmlHttp.send(null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function smartGET (data) {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
data = {
|
||||||
|
url: data,
|
||||||
|
type: 'text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.type) data.type = 'text'
|
||||||
|
let istext = (data.type !== 'image' && data.type !== 'file')
|
||||||
|
|
||||||
|
let url = data.url
|
||||||
|
if (!url) throw new Error('URL is required!')
|
||||||
|
|
||||||
|
if (data.type === 'json') {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
GET(url).then((dtext) => {
|
||||||
|
try {
|
||||||
|
let jsonp = JSON.parse(dtext)
|
||||||
|
return resolve(jsonp)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
}, reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return GET(data.url, istext)
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadImage (url) {
|
||||||
|
url = '/assets/textures/' + url
|
||||||
|
|
||||||
|
// Ensure we don't load a texture multiple times
|
||||||
|
if (imgCache[url]) return imgCache[url]
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let img = new Image()
|
||||||
|
|
||||||
|
img.onload = function () {
|
||||||
|
imgCache[url] = img
|
||||||
|
resolve(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
img.onerror = function (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
img.src = url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageToSampler (img) {
|
||||||
|
let canvas = document.createElement('canvas')
|
||||||
|
let ctx = canvas.getContext('2d')
|
||||||
|
canvas.width = img.width
|
||||||
|
canvas.height = img.height
|
||||||
|
ctx.drawImage(img, 0, 0, img.width, img.height)
|
||||||
|
return function (x, y) {
|
||||||
|
return ctx.getImageData(x, y, 1, 1).data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { GET: smartGET, imageToSampler, loadImage }
|
35
src/engine/screen.js
Normal file
35
src/engine/screen.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/* global alert */
|
||||||
|
|
||||||
|
class Screen {
|
||||||
|
constructor () {
|
||||||
|
this._el = document.createElement('canvas')
|
||||||
|
this.resize()
|
||||||
|
this._gl = this._el.getContext('webgl')
|
||||||
|
|
||||||
|
if (!this._gl) {
|
||||||
|
alert('Your machine or browser does not support WebGL!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(this._el)
|
||||||
|
|
||||||
|
window.addEventListener('resize', (e) => {
|
||||||
|
this.resize()
|
||||||
|
}, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
get gl () {
|
||||||
|
return this._gl
|
||||||
|
}
|
||||||
|
|
||||||
|
get ctx () {
|
||||||
|
return this._gl
|
||||||
|
}
|
||||||
|
|
||||||
|
resize () {
|
||||||
|
this._el.width = window.innerWidth
|
||||||
|
this._el.height = window.innerHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Screen
|
181
src/engine/shader.js
Normal file
181
src/engine/shader.js
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import Resource from './resource'
|
||||||
|
|
||||||
|
class Shader {
|
||||||
|
constructor (type, source) {
|
||||||
|
this.type = type
|
||||||
|
this.source = source
|
||||||
|
this.id = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
compile (gl) {
|
||||||
|
const shader = gl.createShader(this.type)
|
||||||
|
|
||||||
|
// Send the source to the shader object
|
||||||
|
gl.shaderSource(shader, this.source)
|
||||||
|
|
||||||
|
// Compile the shader program
|
||||||
|
gl.compileShader(shader)
|
||||||
|
|
||||||
|
// See if it compiled successfully
|
||||||
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||||
|
let inf = gl.getShaderInfoLog(shader)
|
||||||
|
gl.deleteShader(shader)
|
||||||
|
throw new Error('An error occurred compiling the shaders: ' + inf)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.id = shader
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShaderProgram {
|
||||||
|
constructor (name) {
|
||||||
|
this.name = name
|
||||||
|
this.id = 0
|
||||||
|
|
||||||
|
this.vertexShader = null
|
||||||
|
this.geometryShader = null
|
||||||
|
this.fragmentShader = null
|
||||||
|
|
||||||
|
this.uniforms = {}
|
||||||
|
this.attribs = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
link (gl, vs, fs, gs) {
|
||||||
|
let vsh = [ vs, fs, gs ]
|
||||||
|
|
||||||
|
for (let i in vsh) {
|
||||||
|
vsh[i] && vsh[i].compile(gl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const shaderProgram = gl.createProgram()
|
||||||
|
|
||||||
|
// Attach the shaders
|
||||||
|
gl.attachShader(shaderProgram, vs.id)
|
||||||
|
gs && gl.attachShader(shaderProgram, gs.id)
|
||||||
|
gl.attachShader(shaderProgram, fs.id)
|
||||||
|
|
||||||
|
// Link the program
|
||||||
|
gl.linkProgram(shaderProgram)
|
||||||
|
|
||||||
|
// If creating the shader program failed, error
|
||||||
|
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
||||||
|
throw new Error('Unable to initialize the shader program ' + this.name + ': ' + gl.getProgramInfoLog(shaderProgram))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.id = shaderProgram
|
||||||
|
this.vertexShader = vs
|
||||||
|
this.fragmentShader = fs
|
||||||
|
this.geometryShader = gs
|
||||||
|
|
||||||
|
// Detach shaders after use
|
||||||
|
gl.detachShader(shaderProgram, vs.id)
|
||||||
|
gs && gl.detachShader(shaderProgram, gs.id)
|
||||||
|
gl.detachShader(shaderProgram, fs.id)
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
getUniformLocation (gl, name) {
|
||||||
|
if (this.uniforms[name]) return this.uniforms[name]
|
||||||
|
let uni = gl.getUniformLocation(this.id, name)
|
||||||
|
if (uni < 0) return null
|
||||||
|
|
||||||
|
this.uniforms[name] = uni
|
||||||
|
return uni
|
||||||
|
}
|
||||||
|
|
||||||
|
getAttributeLocation (gl, name) {
|
||||||
|
if (this.attribs[name]) return this.attribs[name]
|
||||||
|
let pos = gl.getAttribLocation(this.id, name)
|
||||||
|
if (pos < 0) throw new Error(`No such attribute location ${name} in shader ${this.name}!`)
|
||||||
|
this.attribs[name] = pos
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
hasAttribute (gl, name) {
|
||||||
|
try {
|
||||||
|
this.getAttributeLocation(gl, name)
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setAttribute (gl, name, size, normalized, stride, offset, type) {
|
||||||
|
let loc = this.getAttributeLocation(gl, name) // throws an error in case the name doesn't exist in shader
|
||||||
|
|
||||||
|
gl.enableVertexAttribArray(loc)
|
||||||
|
gl.vertexAttribPointer(
|
||||||
|
loc,
|
||||||
|
size,
|
||||||
|
type || gl.FLOAT,
|
||||||
|
normalized,
|
||||||
|
stride,
|
||||||
|
offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
use (gl) {
|
||||||
|
if (this.id === 0) return
|
||||||
|
gl.useProgram(this.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShaderManager {
|
||||||
|
constructor () {
|
||||||
|
this.shaders = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
createShader (gl, name, vs, fs, gs) {
|
||||||
|
if (this.shaders[name]) return this.shaders[name]
|
||||||
|
let shader = new ShaderProgram(name)
|
||||||
|
|
||||||
|
let vert = new Shader(gl.VERTEX_SHADER, vs)
|
||||||
|
let frag = new Shader(gl.FRAGMENT_SHADER, fs)
|
||||||
|
let geom = gs ? new Shader(gl.GEOMETRY_SHADER, gs) : null
|
||||||
|
|
||||||
|
shader.link(gl, vert, frag, geom)
|
||||||
|
|
||||||
|
this.shaders[name] = shader
|
||||||
|
return shader
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard shader nomenclature: /assets/shaders/shader-name.vs|fs[|gs]
|
||||||
|
// shader-name.vs and shader-name.fs are mandatory!
|
||||||
|
createShaderFromFiles (gl, name, gs) {
|
||||||
|
let stdloc = '/assets/shaders/' + name
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
function finishLink (vs, fs, gss) {
|
||||||
|
try {
|
||||||
|
let shader = this.createShader(gl, name, vs, fs, gss)
|
||||||
|
resolve(shader)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Resource.GET(stdloc + '.vs').then((vs) => {
|
||||||
|
Resource.GET(stdloc + '.fs').then((fs) => {
|
||||||
|
if (gs !== false) {
|
||||||
|
// Try to find the geometry shader if it wasn't explicitly stated to not exist
|
||||||
|
return Resource.GET(stdloc + '.gs').then((gss) => {
|
||||||
|
finishLink.apply(this, [vs, fs, gss])
|
||||||
|
}, () => {
|
||||||
|
finishLink.apply(this, [vs, fs])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
finishLink.apply(this, [vs, fs, null])
|
||||||
|
}, reject)
|
||||||
|
}, reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
use (gl, name) {
|
||||||
|
if (this.shaders[name]) this.shaders[name].use(gl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Shader, ShaderProgram, ShaderManager }
|
74
src/index.js
Normal file
74
src/index.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import Engine from './engine'
|
||||||
|
import Camera from './engine/camera'
|
||||||
|
// import Entity from './engine/mesh/entity'
|
||||||
|
|
||||||
|
import { Environment } from './engine/environment'
|
||||||
|
import { Terrain } from './engine/components/terrain'
|
||||||
|
import { SimplexHeightMap } from './engine/components/terrain/heightmap'
|
||||||
|
import { Material } from './engine/mesh/material'
|
||||||
|
|
||||||
|
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 terrainShader = await game.shaders.createShaderFromFiles(game.gl, 'terrain', false)
|
||||||
|
|
||||||
|
// Create a height map based on OpenSimplex noise
|
||||||
|
let hmap = new SimplexHeightMap(1, 1, 256, 50)
|
||||||
|
|
||||||
|
// Create a terrain
|
||||||
|
let terrain = new Terrain([0.0, 0.0, 0.0], 256, 256)
|
||||||
|
terrain.createMesh(game.gl, hmap)
|
||||||
|
|
||||||
|
// Terrain material
|
||||||
|
let material = new Material()
|
||||||
|
material.textures = ['grass-1024.jpg']
|
||||||
|
await material.loadTextures(game.gl)
|
||||||
|
terrain.setMaterial(material)
|
||||||
|
|
||||||
|
// Create and initialize the camera
|
||||||
|
let cam = new Camera([0.0, 1.0, 2.0])
|
||||||
|
cam.updateProjection(game.gl)
|
||||||
|
|
||||||
|
// Update function for camera
|
||||||
|
game.addUpdateFunction(function (dt) {
|
||||||
|
if (game.input.isDown('w')) {
|
||||||
|
cam.processKeyboard(0, dt)
|
||||||
|
} else if (game.input.isDown('s')) {
|
||||||
|
cam.processKeyboard(1, dt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.input.isDown('a')) {
|
||||||
|
cam.processKeyboard(2, dt)
|
||||||
|
} else if (game.input.isDown('d')) {
|
||||||
|
cam.processKeyboard(3, dt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game.input.mouseMoved && game.input.mouse.btn0) {
|
||||||
|
cam.processMouseMove(game.input.mouseOffset)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Render function for the triangle
|
||||||
|
game.addRenderFunction(function (gl) {
|
||||||
|
// Use terrain shader
|
||||||
|
terrainShader.use(gl)
|
||||||
|
|
||||||
|
// Set environment variables in shader
|
||||||
|
env.draw(gl, terrainShader)
|
||||||
|
|
||||||
|
// Set the viewport uniforms
|
||||||
|
cam.draw(gl, terrainShader)
|
||||||
|
|
||||||
|
// Draw terrain
|
||||||
|
terrain.draw(gl, terrainShader)
|
||||||
|
})
|
||||||
|
|
||||||
|
game.startGameLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline().catch(function (e) {
|
||||||
|
console.error(e)
|
||||||
|
})
|
28
webpack.config.js
Normal file
28
webpack.config.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './src/index.js',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'app.js'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
exclude: /(node_modules)/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
presets: ['@babel/preset-env']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
plugins: [
|
||||||
|
new HtmlWebpackPlugin({ template: 'index.html' })
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user