From e27151b796a26cbf055f92e1c037c405358a2fb2 Mon Sep 17 00:00:00 2001 From: Paul Mathieu Date: Tue, 8 Nov 2022 12:04:51 -0800 Subject: [PATCH] skycraft: optimize mesh generation --- skycraft/index.js | 212 +++++++++++++++++++++++++++------------------- 1 file changed, 127 insertions(+), 85 deletions(-) diff --git a/skycraft/index.js b/skycraft/index.js index 17a36d6..2ce8525 100644 --- a/skycraft/index.js +++ b/skycraft/index.js @@ -5,6 +5,7 @@ import {loadTexture, makeProgram} from '../gl'; import {makeFace, makeBufferFromFaces} from '../geometry'; import { loadStlModel } from './stl'; import * as linalg from './linalg'; +import { memoize } from '../memoize'; const VSHADER = ` attribute vec3 aPosition; @@ -243,7 +244,7 @@ const CHUNKSIZE = 32; * * returns: a chunk */ -function getChunk(seed, x, y, z) { +function makeDirtBlock(seed, x, y, z) { // XXX: for now, return a premade chunk: a 24x24x24 cube of dirt // surrounded by 4 blocks of air all around const cs = CHUNKSIZE; @@ -271,15 +272,83 @@ function getChunk(seed, x, y, z) { position: [-half, -half, -half], blocks, underground: false, + seed, }; } -function getBodyChunks(seed) { - if (seed === 0) { - return makeSun(); +function makeSunChunk(seed, i, j, k) { + const cs = CHUNKSIZE; + const radius = 42; + if (Math.abs(cs * i) > radius + || Math.abs(cs * j) > radius + || Math.abs(cs * k) > radius) { + return undefined; + } + const half = cs / 2; + const blocks = new Array(cs**3); + blocks.fill(BlockType.SUN); + + let underground = true; + + for (let x = 0; x < cs; x++) { + for (let y = 0; y < cs; y++) { + for (let z = 0; z < cs; z++) { + const pos = [ + x + i * cs - half, + y + j * cs - half, + z + k * cs - half, + ]; + const idx = ( + z * cs * cs + + y * cs + + x + ); + if (pos[0]**2 + pos[1]**2 + pos[2]**2 > radius**2) { + blocks[idx] = BlockType.AIR; + underground = false; + } + } + } } - return [getChunk(seed, 0, 0, 0)]; + return { + position: [i * cs - half, j * cs - half, k * cs - half], + layout: [i, j, k], + blocks, + seed, + underground, + }; +} + +function _getChunk(seed, chunkX, chunkY, chunkZ) { + if (seed === 0) { + return makeSunChunk(seed, chunkX, chunkY, chunkZ); + } + if (chunkX === 0 && chunkY === 0 && chunkZ === 0) { + return makeDirtBlock(seed); // x, y, z unused right now + } + return undefined; +} +const getChunk = memoize(_getChunk); + +function getBodyChunks(seed) { + const chunks = []; + const toCheck = [[0, 0, 0]]; + while (toCheck.length > 0) { + const [chunkX, chunkY, chunkZ] = toCheck.pop(); + const thisChunk = getChunk(seed, chunkX, chunkY, chunkZ); + if (thisChunk === undefined || chunks.includes(thisChunk)) { + continue; + } + chunks.push(thisChunk); + toCheck.push([chunkX - 1, chunkY, chunkZ]); + toCheck.push([chunkX + 1, chunkY, chunkZ]); + toCheck.push([chunkX, chunkY - 1, chunkZ]); + toCheck.push([chunkX, chunkY + 1, chunkZ]); + toCheck.push([chunkX, chunkY, chunkZ - 1]); + toCheck.push([chunkX, chunkY, chunkZ + 1]); + } + return chunks; } function faceTexture(type, dir) { @@ -320,6 +389,49 @@ function* makeChunkFaces(chunk) { } } + function neighborChunk(dir) { + const [chunkX, chunkY, chunkZ] = chunk.layout; + if (chunk.neighbors === undefined) { + chunk.neighbors = {}; + } + if (!(dir in chunk.neighbors)) { + if (dir === '-x') { + chunk.neighbors[dir] = getChunk(chunk.seed, chunkX - 1, chunkY, chunkZ); + } else if (dir === '+x') { + chunk.neighbors[dir] = getChunk(chunk.seed, chunkX + 1, chunkY, chunkZ); + } else if (dir === '-y') { + chunk.neighbors[dir] = getChunk(chunk.seed, chunkX, chunkY - 1, chunkZ); + } else if (dir === '+y') { + chunk.neighbors[dir] = getChunk(chunk.seed, chunkX, chunkY + 1, chunkZ); + } else if (dir === '-z') { + chunk.neighbors[dir] = getChunk(chunk.seed, chunkX, chunkY, chunkZ - 1); + } else if (dir === '+z') { + chunk.neighbors[dir] = getChunk(chunk.seed, chunkX, chunkY, chunkZ + 1); + } + } + return chunk.neighbors[dir]; + } + + const neighborIndices = { + '-x': (x, y, z) => z * cs * cs + y * cs + (cs - 1), + '+x': (x, y, z) => z * cs * cs + y * cs + 0, + '-y': (x, y, z) => z * cs * cs + (cs - 1) * cs + x, + '+y': (x, y, z) => z * cs * cs + 0 * cs + x, + '-z': (x, y, z) => (cs - 1) * cs * cs + y * cs + x, + '+z': (x, y, z) => 0 * cs * cs + y * cs + x, + }; + + function neighborBlock(dir, x, y, z) { + const neighbor = neighborChunk(dir); + let block; + if (neighbor === undefined) { + block = BlockType.AIR; + } else { + block = neighbor.blocks[neighborIndices[dir](x, y, z)]; + } + return { dir, block }; + } + function* neighbors(x, y, z) { const idx = ( z * cs * cs + @@ -332,21 +444,15 @@ function* makeChunkFaces(chunk) { dir: '-x', }; } else { - yield { - block: BlockType.AIR, - dir: '-x', - }; + yield neighborBlock('-x', x, y, z); } - if (x < 31) { + if (x < cs - 1) { yield { block: chunk.blocks[idx + 1], dir: '+x', }; } else { - yield { - block: BlockType.AIR, - dir: '+x', - }; + yield neighborBlock('+x', x, y, z); } if (y > 0) { yield { @@ -354,21 +460,15 @@ function* makeChunkFaces(chunk) { dir: '-y', }; } else { - yield { - block: BlockType.AIR, - dir: '-y', - }; + yield neighborBlock('-y', x, y, z); } - if (y < 31) { + if (y < cs - 1) { yield { block: chunk.blocks[idx + cs], dir: '+y', }; } else { - yield { - block: BlockType.AIR, - dir: '+y', - }; + yield neighborBlock('+y', x, y, z); } if (z > 0) { yield { @@ -376,21 +476,15 @@ function* makeChunkFaces(chunk) { dir: '-z', }; } else { - yield { - block: BlockType.AIR, - dir: '-z', - }; + yield neighborBlock('-z', x, y, z); } - if (z < 31) { + if (z < cs - 1) { yield { block: chunk.blocks[idx + cs * cs], dir: '+z', }; } else { - yield { - block: BlockType.AIR, - dir: '+z', - }; + yield neighborBlock('+z', x, y, z); } } @@ -430,59 +524,6 @@ function closeToPlanet(context) { return linalg.norm(relativePos) < 20; } -function makeSun(seed) { - const radius = 7; - const radiusChunks = Math.floor(radius / CHUNKSIZE); - - const chunks = []; - - function makeSunChunk(i, j, k) { - const cs = CHUNKSIZE; - const half = cs / 2; - const blocks = new Array(cs**3); - blocks.fill(BlockType.SUN); - - let underground = true; - - for (let x = 0; x < cs; x++) { - for (let y = 0; y < cs; y++) { - for (let z = 0; z < cs; z++) { - const pos = [ - x + i * cs - half, - y + j * cs - half, - z + k * cs - half, - ]; - const idx = ( - z * cs * cs + - y * cs + - x - ); - if (pos[0]**2 + pos[1]**2 + pos[2]**2 > radius**2) { - blocks[idx] = BlockType.AIR; - underground = false; - } - } - } - } - - return { - position: [i * cs - half, j * cs - half, k * cs - half], - blocks, - underground, - }; - } - - for (let i = -radiusChunks; i <= radiusChunks; i++) { - for (let j = -radiusChunks; j <= radiusChunks; j++) { - for (let k = -radiusChunks; k <= radiusChunks; k++) { - chunks.push(makeSunChunk(i, j, k)); - } - } - } - - return chunks; -} - function getBodyGeometry(seed) { const faces = getBodyChunks(seed) .filter(chunk => !chunk.underground) @@ -1208,6 +1249,7 @@ async function main() { // [ ] huge planets // [x] lighting // [ ] better lighting + // [x] optimize geometry generation const modelPromise = loadStlModel('spaceship.stl');