313 lines
9.0 KiB
TypeScript
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));
|
|
} |