Caves!!
This commit is contained in:
parent
d0f1749ef2
commit
19fb285d18
20
game.js
20
game.js
@ -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 => {
|
||||||
|
13
geometry.js
13
geometry.js
@ -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
12
memoize.js
Normal 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;
|
||||||
|
}
|
89
terrain.js
89
terrain.js
@ -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;
|
||||||
|
52
world.js
52
world.js
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user