skycraft: optimize mesh generation

This commit is contained in:
Paul Mathieu 2022-11-08 12:04:51 -08:00
parent b0a39bb1ce
commit e27151b796

View File

@ -5,6 +5,7 @@ import {loadTexture, makeProgram} from '../gl';
import {makeFace, makeBufferFromFaces} from '../geometry'; import {makeFace, makeBufferFromFaces} from '../geometry';
import { loadStlModel } from './stl'; import { loadStlModel } from './stl';
import * as linalg from './linalg'; import * as linalg from './linalg';
import { memoize } from '../memoize';
const VSHADER = ` const VSHADER = `
attribute vec3 aPosition; attribute vec3 aPosition;
@ -243,7 +244,7 @@ const CHUNKSIZE = 32;
* *
* returns: a chunk * 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 // XXX: for now, return a premade chunk: a 24x24x24 cube of dirt
// surrounded by 4 blocks of air all around // surrounded by 4 blocks of air all around
const cs = CHUNKSIZE; const cs = CHUNKSIZE;
@ -271,15 +272,83 @@ function getChunk(seed, x, y, z) {
position: [-half, -half, -half], position: [-half, -half, -half],
blocks, blocks,
underground: false, underground: false,
seed,
}; };
} }
function getBodyChunks(seed) { function makeSunChunk(seed, i, j, k) {
if (seed === 0) { const cs = CHUNKSIZE;
return makeSun(); 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) { 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) { function* neighbors(x, y, z) {
const idx = ( const idx = (
z * cs * cs + z * cs * cs +
@ -332,21 +444,15 @@ function* makeChunkFaces(chunk) {
dir: '-x', dir: '-x',
}; };
} else { } else {
yield { yield neighborBlock('-x', x, y, z);
block: BlockType.AIR,
dir: '-x',
};
} }
if (x < 31) { if (x < cs - 1) {
yield { yield {
block: chunk.blocks[idx + 1], block: chunk.blocks[idx + 1],
dir: '+x', dir: '+x',
}; };
} else { } else {
yield { yield neighborBlock('+x', x, y, z);
block: BlockType.AIR,
dir: '+x',
};
} }
if (y > 0) { if (y > 0) {
yield { yield {
@ -354,21 +460,15 @@ function* makeChunkFaces(chunk) {
dir: '-y', dir: '-y',
}; };
} else { } else {
yield { yield neighborBlock('-y', x, y, z);
block: BlockType.AIR,
dir: '-y',
};
} }
if (y < 31) { if (y < cs - 1) {
yield { yield {
block: chunk.blocks[idx + cs], block: chunk.blocks[idx + cs],
dir: '+y', dir: '+y',
}; };
} else { } else {
yield { yield neighborBlock('+y', x, y, z);
block: BlockType.AIR,
dir: '+y',
};
} }
if (z > 0) { if (z > 0) {
yield { yield {
@ -376,21 +476,15 @@ function* makeChunkFaces(chunk) {
dir: '-z', dir: '-z',
}; };
} else { } else {
yield { yield neighborBlock('-z', x, y, z);
block: BlockType.AIR,
dir: '-z',
};
} }
if (z < 31) { if (z < cs - 1) {
yield { yield {
block: chunk.blocks[idx + cs * cs], block: chunk.blocks[idx + cs * cs],
dir: '+z', dir: '+z',
}; };
} else { } else {
yield { yield neighborBlock('+z', x, y, z);
block: BlockType.AIR,
dir: '+z',
};
} }
} }
@ -430,59 +524,6 @@ function closeToPlanet(context) {
return linalg.norm(relativePos) < 20; 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) { function getBodyGeometry(seed) {
const faces = getBodyChunks(seed) const faces = getBodyChunks(seed)
.filter(chunk => !chunk.underground) .filter(chunk => !chunk.underground)
@ -1208,6 +1249,7 @@ async function main() {
// [ ] huge planets // [ ] huge planets
// [x] lighting // [x] lighting
// [ ] better lighting // [ ] better lighting
// [x] optimize geometry generation
const modelPromise = loadStlModel('spaceship.stl'); const modelPromise = loadStlModel('spaceship.stl');