diff --git a/game.js b/game.js index 85edc36..5c7a5bd 100644 --- a/game.js +++ b/game.js @@ -123,34 +123,46 @@ function handleKeys(params) { }); } -function getObjects(world, z, x, glContext) { - const drawedChunks = world.chunks - .filter(chunk => { - if (chunk.position.z < z - 8 * 16) return false; - if (chunk.position.z > z + 7 * 16) return false; - if (chunk.position.x < x - 8 * 16) return false; - if (chunk.position.x > x + 7 * 16) return false; +function memoize(f) { + const memo = {}; - if (chunk.buffer === undefined) { - return false; + function g(...args) { + if (!(args in memo)) { + memo[args] = f(...args); + } + return memo[args]; + } + + return g; +} + +function _getChunkOrder(chunki, chunkj) { + return [...function* () { + for (let i = chunki - 8; i < chunki + 8; i++) { + for (let j = chunkj - 8; j < chunkj + 8; j++) { + yield [i, j]; } - - return true; - }) + } + }()] .sort((a, b) => { - const zz = Math.floor(z / 16); - const xx = Math.floor(x / 16); - const azz = Math.floor(a.position.z / 16) - zz; - const axx = Math.floor(a.position.x / 16) - xx; - const bzz = Math.floor(b.position.z / 16) - zz; - const bxx = Math.floor(b.position.x / 16) - xx; - const da = Math.abs(azz) + Math.abs(axx); - const db = Math.abs(bzz) + Math.abs(bxx); - return db - da; + const d = (i, j) => { + const di = i - chunki; + const dj = j - chunkj; + return Math.sqrt(di * di + dj * dj); + }; + return d(...b) - d(...a); }); - const buffers = drawedChunks - .map(chunk => chunk.buffer) - .concat(drawedChunks.map(chunk => chunk.transparentBuffer)); +} +const getChunkOrder = memoize(_getChunkOrder); + +function getObjects(world, z, x, glContext) { + const chunki = Math.floor(z / 16); + const chunkj = Math.floor(x / 16); + const chunks = getChunkOrder(chunki, chunkj) + .map(([i, j]) => world.chunkMap.get(i, j)) + .filter(chunk => chunk.buffer !== undefined); + const buffers = chunks.map(chunk => chunk.buffer) + .concat(chunks.map(chunk => chunk.transparentBuffer)); return buffers.map(buffer => ({ position: [0.0, 0.0, 0.0], orientation: [0.0, 0.0, 0.0], @@ -308,12 +320,13 @@ export function initUiListeners(params, canvas) { }; const keyListener = e => { if (e.type === 'keydown') { + if (e.repeat) return; params.keys.add(e.code); switch (e.code) { case 'KeyF': params.flying = !params.flying; - break; + return false; case 'Space': if (!params.flying) { if (params.jumpAmount > 0) { @@ -322,7 +335,7 @@ export function initUiListeners(params, canvas) { params.jumpAmount -= 1; } } - break; + return false; } } else { params.keys.delete(e.code); @@ -355,13 +368,11 @@ export function initUiListeners(params, canvas) { }; canvas.onclick = canvasClickHandler; document.addEventListener('keydown', e => { + if (e.repeat) return; switch (e.code) { case 'F11': - canvas.requestFullscreen().then(() => { - console.log('full screen!!'); - }); + canvas.requestFullscreen(); break; - } }); } diff --git a/world.js b/world.js index 341b751..2d8d9c5 100644 --- a/world.js +++ b/world.js @@ -218,16 +218,9 @@ function isTransparent(type) { } } -function makeFaceList(data, chunkz, chunkx, blockLookup) { - const lookup = (i, j, k) => { - if (i < 0 || j < 0 || i > 15 || j > 15) { - return blockLookup(i, j, k); - } - if (k < 0 || k > 255) { - return BlockType.UNDEFINED; - } - return data[256*(16*i + j) + k]; - }; +function makeFaceList(chunk, lookup) { + const chunkz = chunk.position.z; + const chunkx = chunk.position.x; const neighbors = (i, j, k) => [ { block: lookup(i - 1, j, k), dir: '-z', faceCenter: [chunkx + j, k, chunkz + i - 0.5] }, { block: lookup(i + 1, j, k), dir: '+z', faceCenter: [chunkx + j, k, chunkz + i + 0.5] }, @@ -237,48 +230,20 @@ function makeFaceList(data, chunkz, chunkx, blockLookup) { { block: lookup(i, j, k + 1), dir: '+y', faceCenter: [chunkx + j, k + 0.5, chunkz + i] }, ]; - const solidFaces = []; - const transparentFaces = []; - - for (let i = 0; i < 16; i++) { - for (let j = 0; j < 16; j++) { - let bi = i * 16 * 256 + j * 256; - for (let k = 0; k < 256; k++, bi++) { - if (data[bi] === BlockType.AIR) { - continue; - } - if (isTransparent(data[bi])) { - for (const { dir, faceCenter } of neighbors(i, j, k) - .filter(({ block }) => block === BlockType.AIR) - ) { - if (data[bi] === BlockType.WATER) { - faceCenter[1] -= 0.15; - } - transparentFaces.push({ - dir, - face: makeFace(dir, faceTexture(data[bi], dir), faceCenter), - blockIndex: bi, - }); - } - } else { - for (const { dir, faceCenter } of neighbors(i, j, k) - .filter(({ block }) => isTransparent(block)) - ) { - solidFaces.push({ - dir, - face: makeFace(dir, faceTexture(data[bi], dir), faceCenter), - blockIndex: bi, - }); - } + function* blocks() { + for (let i = 0; i < 16; i++) { + for (let j = 0; j < 16; j++) { + for (let k = 0; k < 256; k++) { + yield [i, j, k]; } } } } - return { - solidFaces, - transparentFaces, - }; + chunk.faces = []; + chunk.transparentFaces = []; + + return makeFaces(chunk, blocks, neighbors); } // - data <-- need to generate the first time, update when m&p @@ -286,6 +251,10 @@ function makeFaceList(data, chunkz, chunkx, blockLookup) { // - buffers <-- need to generate every time geometry changes? // --> could also render chunks 1 by 1 +const undefinedChunk = {data: new Proxy({}, { + get: () => BlockType.UNDEFINED, +})}; + class ChunkMap { map = {}; @@ -295,7 +264,8 @@ class ChunkMap { } get(i, j) { - return this.map[this.key(i, j)]; + const out = this.map[this.key(i, j)]; + return out ?? undefinedChunk; } set(i, j, x) { @@ -312,6 +282,109 @@ export function makeWorld() { return {chunks: [], chunkMap: new ChunkMap()}; } +function makeFaces(chunk, blocks, neighbors) { + if (chunk === undefined || chunk.faces === undefined) { + return; + } + + let geometryChanged = false; + + for (const [i, j, k] of blocks()) { + const bi = 256 * (16 * i + j) + k; + if (chunk.data[bi] === BlockType.AIR) { + continue; + } + if (isTransparent(chunk.data[bi])) { + for (const { dir, faceCenter } of neighbors(i, j, k) + .filter(({ block }) => block === BlockType.AIR) + ) { + if (chunk.data[bi] === BlockType.WATER) { + faceCenter[1] -= 0.15; + } + chunk.transparentFaces.push({ + dir, + face: makeFace(dir, faceTexture(chunk.data[bi], dir), faceCenter), + blockIndex: bi, + }); + geometryChanged = true; + } + } else { + for (const { dir, faceCenter } of neighbors(i, j, k) + .filter(({ block }) => isTransparent(block)) + ) { + chunk.faces.push({ + dir, + face: makeFace(dir, faceTexture(chunk.data[bi], dir), faceCenter), + blockIndex: bi, + }); + geometryChanged = true; + } + } + } + if (geometryChanged && chunk.buffer !== undefined) { + chunk.buffer.delete(); + delete chunk.buffer; + chunk.transparentBuffer.delete(); + delete chunk.transparentBuffer; + } +} + +function updateMissingFaces(world, chunk) { + const chunkz = chunk.position.z; + const chunkx = chunk.position.x; + const chunki = Math.floor(chunkz / 16); + const chunkj = Math.floor(chunkx / 16); + + makeFaces(world.chunkMap.get(chunki - 1, chunkj), + function* () { + for (let j = 0; j < 16; j++) { + for (let k = 0; k < 256; k++) { + yield [15, j, k]; + } + } + }, (i, j, k) => [{ + block: chunk.data[256 * (16 * 0 + j) + k], + dir: '+z', + faceCenter: [chunkx + j, k, chunkz - 0.5], + }]); + makeFaces(world.chunkMap.get(chunki + 1, chunkj), + function* () { + for (let j = 0; j < 16; j++) { + for (let k = 0; k < 256; k++) { + yield [0, j, k]; + } + } + }, (i, j, k) => [{ + block: chunk.data[256 * (15 * 16 + j) + k], + dir: '-z', + faceCenter: [chunkx + j, k, chunkz + 16 - 0.5], + }]); + makeFaces(world.chunkMap.get(chunki, chunkj - 1), + function* () { + for (let i = 0; i < 16; i++) { + for (let k = 0; k < 256; k++) { + yield [i, 15, k]; + } + } + }, (i, j, k) => [{ + block: chunk.data[256 * (16 * i + 0) + k], + dir: '+x', + faceCenter: [chunkx - 0.5, k, chunkz + i], + }]); + makeFaces(world.chunkMap.get(chunki, chunkj + 1), + function* () { + for (let i = 0; i < 16; i++) { + for (let k = 0; k < 256; k++) { + yield [i, 0, k]; + } + } + }, (i, j, k) => [{ + block: chunk.data[256 * (16 * i + 15) + k], + dir: '-x', + faceCenter: [chunkx + 16 - 0.5, k, chunkz + i], + }]); +} + /** Update the world, generating missing chunks if necessary. */ export function generateMissingChunks(world, z, x, timeLimit = 10000) { const ic = Math.floor(z / 16); @@ -320,46 +393,27 @@ export function generateMissingChunks(world, z, x, timeLimit = 10000) { const start = performance.now(); for (let radius = 1; radius < 8; radius++) { - for (let i = ic - radius; i < ic + radius; i++) { for (let j = jc - radius; j < jc + radius; j++) { if (world.chunkMap.has(i, j)) { continue; } - const chunk = makeChunk(i * 16, j * 16); - world.chunks.push(chunk); - world.chunkMap.set(i, j, chunk); - - invalidateChunkGeometry(world, i - 1, j); - invalidateChunkGeometry(world, i + 1, j); - invalidateChunkGeometry(world, i, j - 1); - invalidateChunkGeometry(world, i, j + 1); if (radius > 2 && performance.now() - start > timeLimit) { return; } + + const chunk = makeChunk(i * 16, j * 16); + world.chunks.push(chunk); + world.chunkMap.set(i, j, chunk); + + updateMissingFaces(world, chunk); } } } return world; } -function invalidateChunkGeometry(world, i, j) { - const chunk = world.chunkMap.get(i, j); - if (chunk === undefined) { - return; - } - if (chunk.faces === undefined) { - return; - } - delete chunk.faces; - if (chunk.buffer === undefined) { - return; - } - chunk.buffer.delete(); - delete chunk.buffer; -} - function addFace(chunk, block, dir) { const face = createChunkFace(block, dir); if (isTransparent(block.type)) { @@ -369,7 +423,7 @@ function addFace(chunk, block, dir) { } } -export function createChunkFace(block, dir, center = null) { +function createChunkFace(block, dir, center = null) { center = center ?? faceCenter(block.centerPosition, dir); if (block.type === BlockType.WATER && dir === '+y') { center[1] -= 0.15; @@ -398,23 +452,31 @@ export function updateWorldGeometry(gl, world, z, x, timeLimit = 10000) { continue; } - if(chunk.faces === undefined) { - const chunkz = 16 * i; - const chunkx = 16 * j; - const lookup = (i, j, k) => blockLookup(world, j + chunkx, k, i + chunkz).type; + if (radius > 2 && performance.now() - start > timeLimit) { + return; + } - const faces = makeFaceList(chunk.data, chunk.position.z, chunk.position.x, lookup); - chunk.faces = faces.solidFaces; - chunk.transparentFaces = faces.transparentFaces; + if(chunk.faces === undefined) { + const neighborChunks = [ + world.chunkMap.get(i - 1, j), + world.chunkMap.get(i + 1, j), + world.chunkMap.get(i, j - 1), + world.chunkMap.get(i, j + 1), + ]; + const lookup = (i, j, k) => { + if (i < 0) { return neighborChunks[0].data[256 * (16 * (i + 16) + j) + k]; } + if (i > 15) { return neighborChunks[1].data[256 * (16 * (i - 16) + j) + k]; } + if (j < 0) { return neighborChunks[2].data[256 * (16 * i + j + 16) + k]; } + if (j > 15) { return neighborChunks[3].data[256 * (16 * i + j - 16) + k]; } + if (k < 0 || k > 255) { return BlockType.UNDEFINED; } + return chunk.data[256*(16*i + j) + k]; + }; + + makeFaceList(chunk, lookup); } chunk.buffer = makeBufferFromFaces(gl, chunk.faces.map(f => f.face)); chunk.transparentBuffer = makeBufferFromFaces(gl, chunk.transparentFaces.map(f => f.face)); - - // throttle this for fluidity - if (radius > 2 && performance.now() - start > timeLimit) { - return; - } } }