wmc/skycraft/chunk.ts

313 lines
9.0 KiB
TypeScript

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));
}