import { makeFace } from '../geometry'; import * as linalg from './linalg'; import {memoize} from '../memoize'; type direction = ('-x' | '+x' | '-y' | '+y' | '-z' | '+z'); const BlockType = { UNDEFINED: 0, AIR: 1, DIRT: 2, GRASS: 3, STONE: 4, WATER: 5, TREE: 6, LEAVES: 7, SUN: 8, }; const CHUNKSIZE = 32; /** seed: some kind of number uniquely defining the body * x, y, z: space coordinates in the body's frame * * returns: a chunk */ function makeDirtBlock(seed: number) { // XXX: for now, return a premade chunk: a 24x24x24 cube of dirt // surrounded by 4 blocks of air all around const cs = CHUNKSIZE; if (seed !== 1337) { return {}; } // if (Math.abs const blocks = new Array(cs * cs * cs); blocks.fill(BlockType.AIR); const dirt = new Array(24).fill(BlockType.DIRT); for (let i = 0; i < 24; i++) { for (let j = 4; j < 28; j++) { const offset = cs * cs * (i + 4) + cs * j; blocks.splice(offset + 4, 24, ...dirt); } } const half = cs / 2; return { position: [-half, -half, -half], blocks, underground: false, seed, }; } function makeSunChunk(seed: number, i: number, j: number, k: number) { 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 { position: [i * cs - half, j * cs - half, k * cs - half], layout: [i, j, k], blocks, seed, underground, }; } function _getChunk(seed: number, chunkX: number, chunkY: number, chunkZ: number) { 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 faceTexture(type: number, dir: direction) { switch (type) { case BlockType.GRASS: switch (dir) { case '+y': return [0, 15]; case '-y': return [2, 15]; default: return [1, 15]; } case BlockType.DIRT: return [2, 15]; case BlockType.STONE: return [3, 15]; case BlockType.WATER: return [4, 15]; case BlockType.TREE: switch (dir) { case '+y': case '-y': return [5, 15]; default: return [6, 15]; } case BlockType.LEAVES: return [7, 15]; case BlockType.SUN: return [0, 4]; default: return [0, 0]; } } function* makeChunkFaces(chunk) { const cs = CHUNKSIZE; function faceCenter(pos: linalg.vec3, dir: direction) { switch (dir) { case '-x': return [pos[0] - 0.5, pos[1], pos[2]]; case '+x': return [pos[0] + 0.5, pos[1], pos[2]]; case '-y': return [pos[0], pos[1] - 0.5, pos[2]]; case '+y': return [pos[0], pos[1] + 0.5, pos[2]]; case '-z': return [pos[0], pos[1], pos[2] - 0.5]; case '+z': return [pos[0], pos[1], pos[2] + 0.5]; } } function neighborChunk(dir: direction) { const [chunkX, chunkY, chunkZ] = chunk.layout; if (chunk.neighbors === undefined) { chunk.neighbors = {}; } if (!(dir in chunk.neighbors)) { switch (dir) { case '-x': chunk.neighbors[dir] = getChunk(chunk.seed, chunkX - 1, chunkY, chunkZ); break; case '+x': chunk.neighbors[dir] = getChunk(chunk.seed, chunkX + 1, chunkY, chunkZ); break; case '-y': chunk.neighbors[dir] = getChunk(chunk.seed, chunkX, chunkY - 1, chunkZ); break; case '+y': chunk.neighbors[dir] = getChunk(chunk.seed, chunkX, chunkY + 1, chunkZ); break; case '-z': chunk.neighbors[dir] = getChunk(chunk.seed, chunkX, chunkY, chunkZ - 1); break; case '+z': chunk.neighbors[dir] = getChunk(chunk.seed, chunkX, chunkY, chunkZ + 1); break; } } 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: direction, 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 + y * cs + x ); if (x > 0) { yield { block: chunk.blocks[idx - 1], dir: '-x', }; } else { yield neighborBlock('-x', x, y, z); } if (x < cs - 1) { yield { block: chunk.blocks[idx + 1], dir: '+x', }; } else { yield neighborBlock('+x', x, y, z); } if (y > 0) { yield { block: chunk.blocks[idx - cs], dir: '-y', }; } else { yield neighborBlock('-y', x, y, z); } if (y < cs - 1) { yield { block: chunk.blocks[idx + cs], dir: '+y', }; } else { yield neighborBlock('+y', x, y, z); } if (z > 0) { yield { block: chunk.blocks[idx - cs * cs], dir: '-z', }; } else { yield neighborBlock('-z', x, y, z); } if (z < cs - 1) { yield { block: chunk.blocks[idx + cs * cs], dir: '+z', }; } else { yield neighborBlock('+z', x, y, z); } } for (let x = 0; x < cs; x++) { for (let y = 0; y < cs; y++) { for (let z = 0; z < cs; z++) { const idx = ( z * cs * cs + y * cs + x ); const chpos = chunk.position; const bkpos = [ chpos[0] + x, chpos[1] + y, chpos[2] + z, ]; const bt = chunk.blocks[idx]; if (bt === BlockType.AIR) { continue; } for (const { block, dir } of neighbors(x, y, z)) { if (block !== BlockType.AIR) { continue; } yield makeFace(dir, faceTexture(bt, dir), faceCenter(bkpos, dir)); } } } } } function getBodyChunks(seed: number) { 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; } export function getBodyGeometry(seed: number) { const faces = getBodyChunks(seed) .filter(chunk => !chunk.underground) .map(chunk => [...makeChunkFaces(chunk)]); return faces.reduce((a, b) => a.concat(b)); }