Minor performance refactor
- turns out the old makeFaceList was probably a bit faster
This commit is contained in:
parent
a38c7f7a76
commit
c5370bf057
69
game.js
69
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 true;
|
||||
})
|
||||
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];
|
||||
}
|
||||
}
|
||||
}()]
|
||||
.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;
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
230
world.js
230
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 = [];
|
||||
|
||||
function* blocks() {
|
||||
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,
|
||||
});
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user