This commit is contained in:
Paul Mathieu 2021-12-28 08:45:31 -08:00
parent d0f1749ef2
commit 19fb285d18
5 changed files with 159 additions and 27 deletions

20
game.js
View File

@ -1,6 +1,8 @@
import { makeBufferFromFaces } from "./geometry"; import { makeBufferFromFaces } from "./geometry";
import { memoize } from "./memoize";
import * as se3 from './se3'; import * as se3 from './se3';
import { import {
blockLookup,
BlockType, BlockType,
castRay, castRay,
checkCollision, checkCollision,
@ -123,19 +125,6 @@ function handleKeys(params) {
}); });
} }
function memoize(f) {
const memo = {};
function g(...args) {
if (!(args in memo)) {
memo[args] = f(...args);
}
return memo[args];
}
return g;
}
/** From far to near. */ /** From far to near. */
function _getChunkOrder(chunki, chunkj) { function _getChunkOrder(chunki, chunkj) {
return [...function* () { return [...function* () {
@ -193,6 +182,10 @@ function tagABlock(gl, params, objects) {
const ori = se3.inverse(se3.rotxyz(-camori[0], -camori[1], -camori[2])); const ori = se3.inverse(se3.rotxyz(-camori[0], -camori[1], -camori[2]));
const viewDirection = se3.apply(ori, dir).slice(0, 3); const viewDirection = se3.apply(ori, dir).slice(0, 3);
if (blockLookup(params.world, ...params.camera.position).type !== BlockType.AIR) {
return;
}
const face = markBlock(params.world, params.camera.position, viewDirection, params.blockSelectDistance); const face = markBlock(params.world, params.camera.position, viewDirection, params.blockSelectDistance);
if (face === undefined) { if (face === undefined) {
return; return;
@ -242,6 +235,7 @@ function tagABlock(gl, params, objects) {
// [ ] inventory // [ ] inventory
// [ ] monsters // [ ] monsters
// [ ] multi player // [ ] multi player
// [ ] chat
export function setupParamPanel(params) { export function setupParamPanel(params) {
document.querySelector('#lightx').oninput = e => { document.querySelector('#lightx').oninput = e => {

View File

@ -1,15 +1,4 @@
function memoize(f) { import { memoize } from "./memoize";
const memo = {};
function g(...args) {
if (!(args in memo)) {
memo[args] = f(...args);
}
return memo[args];
}
return g;
}
function _makeTextureFace(texture) { function _makeTextureFace(texture) {
const textMul = 0.0625; const textMul = 0.0625;

12
memoize.js Normal file
View File

@ -0,0 +1,12 @@
export function memoize(f) {
const memo = {};
function g(...args) {
if (!(args in memo)) {
memo[args] = f(...args);
}
return memo[args];
}
return g;
}

View File

@ -1,3 +1,5 @@
import { memoize } from "./memoize";
const smoothstep = x => x*x*(3 - 2*x); const smoothstep = x => x*x*(3 - 2*x);
function interpolate(a, b, x, f = smoothstep) { function interpolate(a, b, x, f = smoothstep) {
const val = a + f(x) * (b - a); const val = a + f(x) * (b - a);
@ -21,6 +23,10 @@ export function random(seed, z, x) {
return xorshift(1337 * z + seed + 80085 * x); return xorshift(1337 * z + seed + 80085 * x);
} }
export function random3d(seed, z, x, y) {
return xorshift(1337 * z + seed + 80085 * x + 13 * y);
}
function ghettoPerlinNoise(seed, x, y, gridSize = 16) { function ghettoPerlinNoise(seed, x, y, gridSize = 16) {
const dot = (vx, vy) => vx[0] * vy[0] + vx[1] * vy[1]; const dot = (vx, vy) => vx[0] * vy[0] + vx[1] * vy[1];
@ -42,6 +48,39 @@ function ghettoPerlinNoise(seed, x, y, gridSize = 16) {
return 1.5 * interpolate(interpolate(n0, n1, sx), interpolate(n2, n3, sx), sy); return 1.5 * interpolate(interpolate(n0, n1, sx), interpolate(n2, n3, sx), sy);
} }
function gpn3d(seed, x, y, z, gridSize = 16) {
const dot = (u, v) => u[0] * v[0] + u[1] * v[1] + u[2] * v[2];
const randGrad = (x0, y0, z0) => {
const rand0 = random3d(seed, x0, y0, z0);
const rand1 = random3d(seed+1, x0, y0, z0);
return [Math.cos(rand0), Math.sin(rand1) * Math.sin(rand0), Math.cos(rand1) * Math.sin(rand0)];
};
const x0 = Math.floor(x / gridSize);
const y0 = Math.floor(y / gridSize);
const z0 = Math.floor(z / gridSize);
const sx = x / gridSize - x0;
const sy = y / gridSize - y0;
const sz = z / gridSize - z0;
const n0 = dot(randGrad(x0, y0, z0), [sx, sy, sz]);
const n1 = dot(randGrad(x0 + 1, y0, z0), [sx - 1, sy, sz]);
const n2 = dot(randGrad(x0, y0 + 1, z0), [sx, sy - 1, sz]);
const n3 = dot(randGrad(x0 + 1, y0 + 1, z0), [sx - 1, sy - 1, sz]);
const n4 = dot(randGrad(x0, y0, z0 + 1), [sx, sy, sz - 1]);
const n5 = dot(randGrad(x0 + 1, y0, z0 + 1), [sx - 1, sy, sz - 1]);
const n6 = dot(randGrad(x0, y0 + 1, z0 + 1), [sx, sy - 1, sz - 1]);
const n7 = dot(randGrad(x0 + 1, y0 + 1, z0 + 1), [sx - 1, sy - 1, sz - 1]);
return 1.5 * interpolate(
interpolate(interpolate(n0, n1, sx), interpolate(n2, n3, sx), sy),
interpolate(interpolate(n4, n5, sx), interpolate(n6, n7, sx), sy),
sz,
);
}
function cliffPerlin(seed, x, y) { function cliffPerlin(seed, x, y) {
const noise1 = ghettoPerlinNoise(seed, x, y); const noise1 = ghettoPerlinNoise(seed, x, y);
const noise2 = ghettoPerlinNoise(seed+1, x, y); const noise2 = ghettoPerlinNoise(seed+1, x, y);
@ -55,6 +94,56 @@ function cliffPerlin(seed, x, y) {
return interpolate(-1, 1, 0.5 * (1 + Math.atan2(noise1, noise2) / Math.PI), softerEdge); return interpolate(-1, 1, 0.5 * (1 + Math.atan2(noise1, noise2) / Math.PI), softerEdge);
} }
function _fractalGpn3d(seed, x, y, z) {
const lacunarity = 3.4;
const persistence = 0.28;
let value = 0;
let power = 0.6;
let scale = 0.7;
const noises = [gpn3d, gpn3d, gpn3d];
for (const noiseFun of noises) {
const noise = noiseFun(seed, scale * x, scale * y, scale * z);
value += noise * power;
power *= persistence;
scale *= lacunarity;
}
return value;
}
const fractalGpn3d = memoize(_fractalGpn3d);
export function checkCave(seed, x, y, z) {
const nx = Math.floor(x / 8) * 8;
const ny = Math.floor(y / 4) * 4;
const nz = Math.floor(z / 8) * 8;
const sx = (x - nx) / 8;
const sy = (y - ny) / 4;
const sz = (z - nz) / 8;
const itp = interpolate;
const n = (x, y, z) => fractalGpn3d(seed, x / 3, y, z / 3);
const noise = itp(
itp(itp(n(nx, ny, nz), n(nx + 8, ny, nz), sx),
itp(n(nx, ny + 4, nz), n(nx + 8, ny + 4, nz), sx),
sy
),
itp(itp(n(nx, ny, nz + 8), n(nx + 8, ny, nz + 8), sx),
itp(n(nx, ny + 4, nz + 8), n(nx + 8, ny + 4, nz + 8), sx),
sy
),
sz);
//if (n(nx, ny, nz) > 0.3) throw Error('break!');
return noise > 0.28 && noise < 0.55;
}
export function makeTerrain(seed, x, y) { export function makeTerrain(seed, x, y) {
const lacunarity = 2.1; const lacunarity = 2.1;
const persistence = 0.35; const persistence = 0.35;

View File

@ -1,7 +1,7 @@
import { makeBufferFromFaces, makeFace } from "./geometry"; import { makeBufferFromFaces, makeFace } from "./geometry";
import { loadTexture, makeProgram } from "./gl"; import { loadTexture, makeProgram } from "./gl";
import * as se3 from './se3'; import * as se3 from './se3';
import { makeTerrain, random } from "./terrain"; import { checkCave, makeTerrain, random } from "./terrain";
const VSHADER = ` const VSHADER = `
attribute vec3 aPosition; attribute vec3 aPosition;
@ -132,6 +132,54 @@ function makeChunk(z, x) {
makeATree(data, tree, seed, z, x); makeATree(data, tree, seed, z, x);
} }
// caves
// [ ] 3d perlin noise up to 64
// [ ] sample 4x4x4 to check for caves
// [ ] fill with air where appropriate
function propagateCave(i, j, k) {
const neighbors = (i, j, k) => [
[i - 1, j, k],
[i + 1, j, k],
[i, j - 1, k],
[i, j + 1, k],
[i, j, k - 1],
[i, j, k + 1],
];
const queue = neighbors(i, j, k);
while (queue.length > 0) {
const [ni, nj, nk] = queue.pop();
if (ni < 0 || ni > 15 || nj < 0 || nj > 15 || nk < 0 || nk > 255) {
continue;
}
const bi = 256 * (16 * ni + nj) + nk;
if (data[bi] === BlockType.AIR || data[bi] === BlockType.WATER) {
continue;
}
if (checkCave(seed, x + nj, nk, z + ni)) {
data[bi] = BlockType.AIR;
queue.push(...neighbors(ni, nj, nk));
}
}
}
for (let i = 0; i < 16; i += 4) {
for (let j = 0; j < 16; j += 4) {
let bi = 256 * (16 * i + j);
for (let k = 0; k < 58; k += 4, bi += 4) {
if (data[bi] === BlockType.AIR) {
continue;
}
if (checkCave(seed, x + j, k, z + i)) {
data[bi] = BlockType.AIR;
propagateCave(i, j, k);
}
}
}
}
return { return {
position: {z, x}, position: {z, x},
data, data,
@ -302,7 +350,7 @@ function makeFaces(chunk, blocks, neighbors) {
.filter(({ block }) => isTransparent(block) && block !== BlockType.WATER) .filter(({ block }) => isTransparent(block) && block !== BlockType.WATER)
) { ) {
if (chunk.data[bi] === BlockType.WATER) { if (chunk.data[bi] === BlockType.WATER) {
faceCenter[1] -= 0.15; faceCenter[1] -= 0.15; // TODO: lower face should be normal
} }
chunk.transparentFaces.push({ chunk.transparentFaces.push({
dir, dir,