568 lines
20 KiB
TypeScript
568 lines
20 KiB
TypeScript
;(() => {
|
|
const elemParent = document.querySelector('div.layout-background')
|
|
const elemSprite = document.querySelector<HTMLLinkElement>('link[rel="texture"]')
|
|
if (!elemParent || !elemSprite) throw 'Invalid Document'
|
|
|
|
const FRAME_INTERVAL = 1000
|
|
const FRAME_TIME_IDLE = 12
|
|
const FRAME_TIME_ACTIVE = 60
|
|
const CAM_ACCEL = 2
|
|
const CAM_FRICTION = 0.85
|
|
const PARTICLE_COUNT = 160
|
|
const COLOR_PARTICLE = 0x484848
|
|
const COLOR_FOREGROUND = 0x363636
|
|
const COLOR_BACKGROUND = 0x000000
|
|
|
|
let keysHeld = new Set()
|
|
let frameTime = FRAME_INTERVAL / FRAME_TIME_IDLE
|
|
let lastTime = 0
|
|
let lastFrame = 0
|
|
let time = 0
|
|
let cameraMVP = mat4()
|
|
let cameraRotX = 0
|
|
let cameraRotY = 0
|
|
let camVelRotX = 0
|
|
let camVelRotY = 0
|
|
let spriteTexture: WebGLTexture
|
|
let spriteModel: Float32Array
|
|
let particlePos: Float32Array
|
|
let particleColor: Float32Array
|
|
let particleData: {
|
|
x: number
|
|
y: number
|
|
z: number
|
|
vx: number
|
|
vy: number
|
|
vz: number
|
|
life: number
|
|
maxLife: number
|
|
}[] = []
|
|
let planeW: number
|
|
let planeH: number
|
|
let planeSegX: number
|
|
let planeSegY: number
|
|
let planeVerts: Float32Array
|
|
let planeOrig: Float32Array
|
|
let planeIdx
|
|
|
|
const canvas = document.createElement('canvas')
|
|
const gl = canvas.getContext('webgl')
|
|
if (!gl) throw 'Failed to allocate WebGL Context'
|
|
|
|
// --- Camera Controls ---
|
|
document.addEventListener('keydown', (e) => {
|
|
if (!e.shiftKey && !e.ctrlKey) return
|
|
if (e.target instanceof HTMLTextAreaElement) return
|
|
if (e.target instanceof HTMLInputElement) return
|
|
if ((e as any).isContentEditable) return
|
|
keysHeld.add(e.key)
|
|
})
|
|
document.addEventListener('keyup', (e) => {
|
|
keysHeld.delete(e.key)
|
|
})
|
|
|
|
// --- Math Functions ---
|
|
function mat4(): Float32Array {
|
|
return new Float32Array(16)
|
|
}
|
|
function mat4Identity(m: Float32Array): Float32Array {
|
|
m[0] = 1
|
|
m[1] = 0
|
|
m[2] = 0
|
|
m[3] = 0
|
|
m[4] = 0
|
|
m[5] = 1
|
|
m[6] = 0
|
|
m[7] = 0
|
|
m[8] = 0
|
|
m[9] = 0
|
|
m[10] = 1
|
|
m[11] = 0
|
|
m[12] = 0
|
|
m[13] = 0
|
|
m[14] = 0
|
|
m[15] = 1
|
|
return m
|
|
}
|
|
function mat4Multiply(out: Float32Array, a: Float32Array, b: Float32Array): Float32Array {
|
|
for (let i = 0; i < 4; i++)
|
|
for (let j = 0; j < 4; j++) {
|
|
out[j * 4 + i] = 0
|
|
for (let k = 0; k < 4; k++) out[j * 4 + i] += a[k * 4 + i] * b[j * 4 + k]
|
|
}
|
|
return out
|
|
}
|
|
function mat4Perspective(m: Float32Array, fovY: number, aspect: number, near: number, far: number): Float32Array {
|
|
const f = 1.0 / Math.tan(fovY / 2)
|
|
m[0] = f / aspect
|
|
m[1] = 0
|
|
m[2] = 0
|
|
m[3] = 0
|
|
m[4] = 0
|
|
m[5] = f
|
|
m[6] = 0
|
|
m[7] = 0
|
|
m[8] = 0
|
|
m[9] = 0
|
|
m[10] = (far + near) / (near - far)
|
|
m[11] = -1
|
|
m[12] = 0
|
|
m[13] = 0
|
|
m[14] = (2 * far * near) / (near - far)
|
|
m[15] = 0
|
|
return m
|
|
}
|
|
function mat4RotateX(m: Float32Array, angle: number): Float32Array {
|
|
const c = Math.cos(angle)
|
|
const s = Math.sin(angle)
|
|
const t = mat4Identity(mat4())
|
|
t[5] = c
|
|
t[6] = s
|
|
t[9] = -s
|
|
t[10] = c
|
|
return mat4Multiply(mat4(), t, m)
|
|
}
|
|
function mat4RotateY(m: Float32Array, angle: number): Float32Array {
|
|
const c = Math.cos(angle)
|
|
const s = Math.sin(angle)
|
|
const t = mat4Identity(mat4())
|
|
t[0] = c
|
|
t[2] = -s
|
|
t[8] = s
|
|
t[10] = c
|
|
return mat4Multiply(mat4(), t, m)
|
|
}
|
|
function mat4Translate(m: Float32Array, x: number, y: number, z: number): Float32Array {
|
|
const t = mat4Identity(mat4())
|
|
t[12] = x
|
|
t[13] = y
|
|
t[14] = z
|
|
return mat4Multiply(mat4(), t, m)
|
|
}
|
|
function randFloat(lo: number, hi: number): number {
|
|
return lo + Math.random() * (hi - lo)
|
|
}
|
|
function randFloatSpread(range: number): number {
|
|
return randFloat(-range / 2, range / 2)
|
|
}
|
|
function degToRad(d: number): number {
|
|
return (d * Math.PI) / 180
|
|
}
|
|
function intToRGB(i: number): [number, number, number] {
|
|
return [((i >> 16) & 0xff) / 255, ((i >> 8) & 0xff) / 255, ((i >> 0) & 0xff) / 255]
|
|
}
|
|
|
|
// --- Prepare Shaders ---
|
|
function createShader(type: number, src: string): WebGLShader {
|
|
if (!gl) throw 'Missing Global GL Context'
|
|
const s = gl.createShader(type)
|
|
if (!s) throw 'Shader Compilation Failed'
|
|
|
|
gl.shaderSource(s, src)
|
|
gl.compileShader(s)
|
|
return s
|
|
}
|
|
|
|
function createProgram(vert: string, frag: string): WebGLProgram {
|
|
if (!gl) throw 'Missing Global GL Context'
|
|
const p = gl.createProgram()
|
|
gl.attachShader(p, createShader(gl.VERTEX_SHADER, vert))
|
|
gl.attachShader(p, createShader(gl.FRAGMENT_SHADER, frag))
|
|
gl.linkProgram(p)
|
|
return p
|
|
}
|
|
|
|
const planeProg = createProgram(
|
|
`attribute vec3 aPosition;
|
|
uniform mat4 uMVP;
|
|
uniform float uTime;
|
|
varying float vDist;
|
|
void main() {
|
|
float dist = sqrt(aPosition.x * aPosition.x + aPosition.z * aPosition.z);
|
|
float wave = sin(dist * 0.5 - uTime);
|
|
float cave = -exp(-dist * 0.1) * 3.5;
|
|
vec3 pos = vec3(aPosition.x, aPosition.y + wave + cave, aPosition.z);
|
|
vDist = length((uMVP * vec4(pos, 1.0)).xyz);
|
|
gl_Position = uMVP * vec4(pos, 1.0);
|
|
}`,
|
|
`precision mediump float;
|
|
uniform vec3 uFogColor;
|
|
uniform float uFogNear;
|
|
uniform float uFogFar;
|
|
varying float vDist;
|
|
void main() {
|
|
float fog = clamp((vDist - uFogNear) / (uFogFar - uFogNear), 0.0, 1.0);
|
|
vec3 color = mix(vec3(${intToRGB(COLOR_FOREGROUND).join(',')}), uFogColor, fog);
|
|
gl_FragColor = vec4(color, 1.0);
|
|
}`,
|
|
)
|
|
|
|
const particleProg = createProgram(
|
|
`attribute vec3 aPosition;
|
|
attribute vec4 aColor;
|
|
uniform mat4 uMVP;
|
|
varying vec4 vColor;
|
|
varying float vDist;
|
|
void main() {
|
|
vec4 pos = uMVP * vec4(aPosition, 1.0);
|
|
vDist = length(pos.xyz);
|
|
vColor = aColor;
|
|
gl_PointSize = 3.0;
|
|
gl_Position = pos;
|
|
}`,
|
|
`precision mediump float;
|
|
uniform vec3 uFogColor;
|
|
uniform float uFogNear;
|
|
uniform float uFogFar;
|
|
varying vec4 vColor;
|
|
varying float vDist;
|
|
void main() {
|
|
float fog = clamp((vDist - uFogNear) / (uFogFar - uFogNear), 0.0, 1.0);
|
|
float alpha = vColor.a * (1.0 - fog);
|
|
gl_FragColor = vec4(mix(vColor.rgb, uFogColor, fog), alpha);
|
|
}`,
|
|
)
|
|
|
|
const spriteProg = createProgram(
|
|
`attribute vec2 aPosition;
|
|
attribute vec2 aUV;
|
|
uniform mat4 uMVP;
|
|
varying vec2 vUV;
|
|
void main() {
|
|
vUV = aUV;
|
|
gl_Position = uMVP * vec4(aPosition.x , 0, aPosition.y, 1);
|
|
}`,
|
|
`precision mediump float;
|
|
uniform sampler2D uTex;
|
|
varying vec2 vUV;
|
|
void main() {
|
|
gl_FragColor = texture2D(uTex, vUV);
|
|
}`,
|
|
)
|
|
|
|
const planeBuf = gl.createBuffer()
|
|
const planeOrigBuf = gl.createBuffer()
|
|
const planeIdxBuf = gl.createBuffer()
|
|
const uTimePlane = gl.getUniformLocation(planeProg, 'uTime')
|
|
const uMVPPlane = gl.getUniformLocation(planeProg, 'uMVP')
|
|
const uFogColorPlane = gl.getUniformLocation(planeProg, 'uFogColor')
|
|
const uFogNearPlane = gl.getUniformLocation(planeProg, 'uFogNear')
|
|
const uFogFarPlane = gl.getUniformLocation(planeProg, 'uFogFar')
|
|
|
|
const particlePosBuf = gl.createBuffer()
|
|
const particleColorBuf = gl.createBuffer()
|
|
const uMVPParticle = gl.getUniformLocation(particleProg, 'uMVP')
|
|
const uFogColorParticle = gl.getUniformLocation(particleProg, 'uFogColor')
|
|
const uFogNearParticle = gl.getUniformLocation(particleProg, 'uFogNear')
|
|
const uFogFarParticle = gl.getUniformLocation(particleProg, 'uFogFar')
|
|
|
|
const spriteImage = new Image()
|
|
spriteImage.src = elemSprite.href
|
|
|
|
const spriteBuf = gl.createBuffer()
|
|
const spriteIdxBuf = gl.createBuffer()
|
|
const uMVPSprite = gl.getUniformLocation(spriteProg, 'uMVP')
|
|
const uTexSprite = gl.getUniformLocation(spriteProg, 'uTex')
|
|
|
|
function spawnParticle(i: number) {
|
|
// Put them in the center because its out of frame anyways save some resources
|
|
const x = randFloat(-planeW / 2, planeW / 2)
|
|
const z = randFloat(-planeH / 2, planeH / 2)
|
|
const l = randFloat(3.0, 6.0)
|
|
|
|
particleData[i] = {
|
|
x,
|
|
y: 0,
|
|
z,
|
|
vx: randFloatSpread(0.05),
|
|
vy: randFloat(0.02, 0.05),
|
|
vz: randFloatSpread(0.05),
|
|
life: l,
|
|
maxLife: l,
|
|
}
|
|
const p = i * 3
|
|
particlePos[p + 0] = x
|
|
particlePos[p + 1] = 0
|
|
particlePos[p + 2] = z
|
|
|
|
const c = i * 4
|
|
const [r, g, b] = intToRGB(COLOR_PARTICLE)
|
|
particleColor[c + 0] = r
|
|
particleColor[c + 1] = g
|
|
particleColor[c + 2] = b
|
|
particleColor[c + 3] = 0
|
|
}
|
|
|
|
function startup() {
|
|
if (!gl) throw 'Missing Global GL Context'
|
|
|
|
// Render Resolution
|
|
canvas.width = Math.floor(window.innerWidth * 0.3)
|
|
canvas.height = Math.floor(window.innerHeight * 0.3)
|
|
canvas.style.width = window.innerWidth + 'px'
|
|
canvas.style.height = window.innerHeight + 'px'
|
|
canvas.style.imageRendering = 'pixelated'
|
|
|
|
// Build Sprite
|
|
spriteImage.onload = () => {
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, spriteBuf)
|
|
gl.bufferData(
|
|
gl.ARRAY_BUFFER,
|
|
new Float32Array([-1, -1, 1, 1, 1, -1, 0, 1, 1, 1, 0, 0, -1, 1, 1, 0]),
|
|
gl.STATIC_DRAW,
|
|
)
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, spriteIdxBuf)
|
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW)
|
|
|
|
spriteModel = mat4Identity(mat4())
|
|
spriteModel = mat4RotateY(spriteModel, degToRad(180))
|
|
spriteModel = mat4Translate(spriteModel, 0, 4, 16)
|
|
|
|
// Upload Texture
|
|
spriteTexture = gl.createTexture()
|
|
gl.bindTexture(gl.TEXTURE_2D, spriteTexture)
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, spriteImage)
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
|
|
|
gl.useProgram(spriteProg)
|
|
gl.uniform1i(uTexSprite, 0)
|
|
gl.activeTexture(gl.TEXTURE0)
|
|
gl.bindTexture(gl.TEXTURE_2D, spriteTexture)
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, spriteBuf)
|
|
}
|
|
|
|
// Build Plane
|
|
gl.viewport(0, 0, canvas.width, canvas.height)
|
|
gl.clearColor(...intToRGB(COLOR_BACKGROUND), 1)
|
|
gl.enable(gl.BLEND)
|
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
|
|
|
const ratio = canvas.width / canvas.height
|
|
const scale = 24
|
|
planeSegX = Math.round(12 * ratio)
|
|
planeSegY = 12
|
|
planeW = scale * ratio
|
|
planeH = scale
|
|
|
|
const nx = planeSegX + 1
|
|
const ny = planeSegY + 1
|
|
planeVerts = new Float32Array(nx * ny * 3)
|
|
planeOrig = new Float32Array(nx * ny * 3)
|
|
|
|
let vi = 0
|
|
for (let iy = 0; iy < ny; iy++) {
|
|
for (let ix = 0; ix < nx; ix++) {
|
|
const x = (ix / planeSegX - 0.5) * planeW
|
|
const z = (iy / planeSegY - 0.5) * planeH
|
|
planeVerts[vi] = x
|
|
planeVerts[vi + 1] = 0
|
|
planeVerts[vi + 2] = z
|
|
planeOrig[vi] = x
|
|
planeOrig[vi + 1] = 0
|
|
planeOrig[vi + 2] = z
|
|
vi += 3
|
|
}
|
|
}
|
|
const lines = []
|
|
for (let iy = 0; iy < ny; iy++) {
|
|
for (let ix = 0; ix < nx; ix++) {
|
|
const idx = iy * nx + ix
|
|
// wireframe indices two triangles per quad
|
|
if (ix < planeSegX) {
|
|
lines.push(idx, idx + 1)
|
|
}
|
|
if (iy < planeSegY) {
|
|
lines.push(idx, idx + nx)
|
|
}
|
|
if (ix < planeSegX && iy < planeSegY) {
|
|
lines.push(idx, idx + nx + 1)
|
|
}
|
|
}
|
|
}
|
|
planeIdx = new Uint16Array(lines)
|
|
|
|
// Upload plane buffers
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, planeBuf)
|
|
gl.bufferData(gl.ARRAY_BUFFER, planeVerts, gl.DYNAMIC_DRAW)
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, planeOrigBuf)
|
|
gl.bufferData(gl.ARRAY_BUFFER, planeOrig, gl.STATIC_DRAW)
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, planeIdxBuf)
|
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, planeIdx, gl.STATIC_DRAW)
|
|
|
|
// Initialize particles if first time
|
|
if (particleData.length === 0) {
|
|
particlePos = new Float32Array(PARTICLE_COUNT * 3)
|
|
particleColor = new Float32Array(PARTICLE_COUNT * 4)
|
|
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
|
spawnParticle(i)
|
|
}
|
|
}
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, particlePosBuf)
|
|
gl.bufferData(gl.ARRAY_BUFFER, particlePos, gl.DYNAMIC_DRAW)
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, particleColorBuf)
|
|
gl.bufferData(gl.ARRAY_BUFFER, particleColor, gl.DYNAMIC_DRAW)
|
|
|
|
// Initialize Fog
|
|
gl.useProgram(planeProg)
|
|
gl.uniform3f(uFogColorPlane, ...intToRGB(COLOR_BACKGROUND))
|
|
gl.uniform1f(uFogNearPlane, 2)
|
|
gl.uniform1f(uFogFarPlane, 22)
|
|
|
|
gl.useProgram(particleProg)
|
|
gl.uniformMatrix4fv(uMVPParticle, false, cameraMVP)
|
|
gl.uniform3f(uFogColorParticle, ...intToRGB(COLOR_BACKGROUND))
|
|
gl.uniform1f(uFogNearParticle, 6)
|
|
gl.uniform1f(uFogFarParticle, 24)
|
|
|
|
// Force dirty camera reset
|
|
camVelRotY = 0.005
|
|
}
|
|
|
|
function animate(now: number) {
|
|
requestAnimationFrame(animate)
|
|
|
|
// Sleep (warning this sucks)
|
|
if (now - lastFrame < frameTime) return
|
|
const delta = (now - lastTime) * 0.00008 || 0
|
|
lastFrame = now
|
|
lastTime = now
|
|
time += delta
|
|
|
|
if (!gl) throw 'Missing Global GL Context'
|
|
gl.clear(gl.COLOR_BUFFER_BIT)
|
|
|
|
// Camera Movement
|
|
if (keysHeld.has('ArrowLeft')) camVelRotY -= CAM_ACCEL
|
|
if (keysHeld.has('ArrowRight')) camVelRotY += CAM_ACCEL
|
|
if (keysHeld.has('ArrowUp')) camVelRotX -= CAM_ACCEL
|
|
if (keysHeld.has('ArrowDown')) camVelRotX += CAM_ACCEL + 8
|
|
|
|
camVelRotX *= CAM_FRICTION
|
|
camVelRotY *= CAM_FRICTION
|
|
const dirty = Math.abs(camVelRotX) + Math.abs(camVelRotY) > 0.001
|
|
|
|
if (dirty) {
|
|
cameraRotX += camVelRotX * delta * 1000
|
|
cameraRotY += camVelRotY * delta * 1000
|
|
|
|
// Update Camera
|
|
const ratio = canvas.width / canvas.height
|
|
const proj = mat4Perspective(mat4(), degToRad(70), ratio, 0.1, 100)
|
|
|
|
let view = mat4Identity(mat4())
|
|
view = mat4Translate(view, 0, -7.5, -15)
|
|
view = mat4RotateX(view, degToRad(camVelRotX + 33.75))
|
|
view = mat4RotateY(view, degToRad(camVelRotY))
|
|
|
|
cameraMVP = mat4Multiply(mat4(), proj, view)
|
|
gl.useProgram(planeProg)
|
|
gl.uniformMatrix4fv(uMVPPlane, false, cameraMVP)
|
|
gl.useProgram(particleProg)
|
|
gl.uniformMatrix4fv(uMVPParticle, false, cameraMVP)
|
|
|
|
frameTime = FRAME_INTERVAL / FRAME_TIME_ACTIVE
|
|
} else {
|
|
frameTime = FRAME_INTERVAL / FRAME_TIME_IDLE
|
|
}
|
|
|
|
// Update Sprite
|
|
if (spriteTexture) {
|
|
const spriteMVP = mat4Multiply(mat4(), cameraMVP, spriteModel)
|
|
gl.useProgram(spriteProg)
|
|
gl.uniformMatrix4fv(uMVPSprite, false, spriteMVP)
|
|
gl.uniform1i(uTexSprite, 0)
|
|
gl.activeTexture(gl.TEXTURE0)
|
|
gl.bindTexture(gl.TEXTURE_2D, spriteTexture)
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, spriteBuf)
|
|
const aPos = gl.getAttribLocation(spriteProg, 'aPosition')
|
|
const aUV = gl.getAttribLocation(spriteProg, 'aUV')
|
|
gl.enableVertexAttribArray(aPos)
|
|
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 16, 0)
|
|
gl.enableVertexAttribArray(aUV)
|
|
gl.vertexAttribPointer(aUV, 2, gl.FLOAT, false, 16, 8)
|
|
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, spriteIdxBuf)
|
|
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0)
|
|
}
|
|
|
|
{
|
|
// Update Particles
|
|
for (let i = 0; i < PARTICLE_COUNT; i++) {
|
|
const p = particleData[i]
|
|
|
|
// fade out
|
|
p.life -= delta
|
|
if (p.life <= 0) {
|
|
spawnParticle(i)
|
|
continue
|
|
}
|
|
particleColor[i * 4 + 3] = Math.min((p.life / p.maxLife) * 1.2, 1)
|
|
|
|
// drift away
|
|
p.x += p.vx * delta * 40
|
|
p.y += p.vy * delta * 20
|
|
p.z += p.vz * delta * 40
|
|
const pi = i * 3
|
|
particlePos[pi + 0] = p.x
|
|
particlePos[pi + 1] = p.y
|
|
particlePos[pi + 2] = p.z
|
|
}
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, particlePosBuf)
|
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, particlePos)
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, particleColorBuf)
|
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, particleColor)
|
|
|
|
// Draw Particles
|
|
gl.useProgram(particleProg)
|
|
const aPos = gl.getAttribLocation(particleProg, 'aPosition')
|
|
const aCol = gl.getAttribLocation(particleProg, 'aColor')
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, particlePosBuf)
|
|
gl.enableVertexAttribArray(aPos)
|
|
gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 0, 0)
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, particleColorBuf)
|
|
gl.enableVertexAttribArray(aCol)
|
|
gl.vertexAttribPointer(aCol, 4, gl.FLOAT, false, 0, 0)
|
|
|
|
gl.drawArrays(gl.POINTS, 0, PARTICLE_COUNT)
|
|
}
|
|
|
|
{
|
|
// Update Plane
|
|
for (let i = 0; i < planeVerts.length; i += 3) {
|
|
const x = planeOrig[i]
|
|
const z = planeOrig[i + 2]
|
|
const dist = Math.sqrt(x * x + z * z)
|
|
planeVerts[i] = x
|
|
planeVerts[i + 1] = Math.sin(dist * 0.5 - time) * 0.5 + -Math.exp(-dist * 0.1) * 3.5
|
|
planeVerts[i + 2] = z
|
|
}
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, planeBuf)
|
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, planeVerts)
|
|
|
|
// Draw Plane
|
|
gl.useProgram(planeProg)
|
|
gl.uniform1f(uTimePlane, time)
|
|
|
|
const aPos = gl.getAttribLocation(planeProg, 'aPosition')
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, planeBuf)
|
|
gl.enableVertexAttribArray(aPos)
|
|
gl.vertexAttribPointer(aPos, 3, gl.FLOAT, false, 0, 0)
|
|
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, planeIdxBuf)
|
|
gl.drawElements(gl.LINES, planeIdx.length, gl.UNSIGNED_SHORT, 0)
|
|
}
|
|
}
|
|
|
|
// --- Prepare Canvas ---
|
|
window.addEventListener('resize', startup)
|
|
startup()
|
|
animate(0)
|
|
elemParent.append(canvas)
|
|
})()
|