Add a few trees

This commit is contained in:
Paul Mathieu 2021-12-24 05:50:13 -08:00
parent 10b5109e21
commit 86a65fc39c
4 changed files with 127 additions and 44 deletions

16
game.js
View File

@ -133,6 +133,17 @@ function getObjects(world, z, x, glContext) {
} }
return true; return true;
})
.sort((a, b) => {
const zz = Math.floor(z / 16);
const xx = Math.floor(x / 16);
const azz = Math.floor(a.position.z / 16) - zz;
const axx = Math.floor(a.position.x / 16) - xx;
const bzz = Math.floor(b.position.z / 16) - zz;
const bxx = Math.floor(b.position.x / 16) - xx;
const da = Math.abs(azz) + Math.abs(axx);
const db = Math.abs(bzz) + Math.abs(bxx);
return db - da;
}); });
const buffers = drawedChunks const buffers = drawedChunks
.map(chunk => chunk.buffer) .map(chunk => chunk.buffer)
@ -203,14 +214,15 @@ function tagABlock(gl, params, objects) {
// [x] double jump!! // [x] double jump!!
// [x] fullscreen // [x] fullscreen
// [ ] a soundrack // [ ] a soundrack
// [ ] trees and stuff // [x] trees and stuff
// [ ] different biomes (with different noise stats) // [ ] different biomes (with different noise stats)
// [ ] water you can swim in (and not walk on)
// [ ] flowy water // [ ] flowy water
// [ ] save the world (yay) to local storage (bah) // [ ] save the world (yay) to local storage (bah)
// [ ] caves // [ ] caves
// [ ] crafting // [ ] crafting
// [ ] fix bugs // [ ] fix bugs
// [ ] better light // [ ] better light (esp. cave lighting)
// [ ] inventory // [ ] inventory
// [ ] monsters // [ ] monsters
// [ ] multi player // [ ] multi player

View File

@ -9,18 +9,23 @@ const sigmoid = (a, b, f = smoothstep) => x => {
return f((x - a) / (b - a)); return f((x - a) / (b - a));
}; };
function ghettoPerlinNoise(seed, x, y, gridSize = 16) {
const dot = (vx, vy) => vx[0] * vy[0] + vx[1] * vy[1];
// super ghetto random // super ghetto random
const xorshift = (x) => { function xorshift(x) {
x ^= x << 13; x ^= x << 13;
x ^= x >> 7; x ^= x >> 7;
x ^= x << 17; x ^= x << 17;
return x; return x;
}; }
export function random(seed, z, x) {
return xorshift(1337 * z + seed + 80085 * x);
}
function ghettoPerlinNoise(seed, x, y, gridSize = 16) {
const dot = (vx, vy) => vx[0] * vy[0] + vx[1] * vy[1];
const randGrad = (x0, y0) => { const randGrad = (x0, y0) => {
const rand = xorshift(1337 * x0 + seed + 80085 * y0); const rand = random(seed, x0, y0);
return [Math.sin(rand), Math.cos(rand)]; return [Math.sin(rand), Math.cos(rand)];
}; };
@ -50,8 +55,7 @@ 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);
} }
export function makeTerrain(x, y) { export function makeTerrain(seed, x, y) {
const seed = 1337;
const lacunarity = 2.1; const lacunarity = 2.1;
const persistence = 0.35; const persistence = 0.35;
const noiseMap = (x, y) => cliffPerlin(seed, x / 2, y / 2); const noiseMap = (x, y) => cliffPerlin(seed, x / 2, y / 2);
@ -119,6 +123,7 @@ function colorInterp(x) {
function main() { function main() {
const canvas = document.querySelector('#canvas'); const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
const seed = 1337;
ctx.fillStyle = 'gray'; ctx.fillStyle = 'gray';
ctx.fill(); ctx.fill();
@ -128,7 +133,7 @@ function main() {
const imgdat = ctx.createImageData(16, 16); const imgdat = ctx.createImageData(16, 16);
const dat = imgdat.data; const dat = imgdat.data;
const chunk = makeTerrain(i - canvas.width / 2, j - canvas.height / 2); const chunk = makeTerrain(seed, i - canvas.width / 2, j - canvas.height / 2);
for (let k = 0; k < 16; k++) { for (let k = 0; k < 16; k++) {
for (let l = 0; l < 16; l++) { for (let l = 0; l < 16; l++) {
const offset = 4 * (16 * (15 - l) + k); const offset = 4 * (16 * (15 - l) + k);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 40 KiB

128
world.js
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 } from "./terrain"; import { makeTerrain, random } from "./terrain";
const VSHADER = ` const VSHADER = `
attribute vec3 aPosition; attribute vec3 aPosition;
@ -56,33 +56,76 @@ export const BlockType = {
GRASS: 3, GRASS: 3,
STONE: 4, STONE: 4,
WATER: 5, WATER: 5,
TREE: 6,
LEAVES: 7,
}; };
function hasATree(seed, z, x) {
const rand = random(seed, z, x);
return (rand % 333 === 123);
}
function makeATree(data, pos, seed, chunkz, chunkx) {
const height = 3 + random(seed, chunkz + pos[0], chunkx + pos[1]) % 6;
const offset = 256 * (16 * pos[0] + pos[1]);
const firstBlock = pos[2];
for (let i = 0; i < height; i++) {
data[offset + firstBlock + i] = BlockType.TREE;
}
function setBlock(i, j, k, type) {
if (i < 0 || j < 0 || k < 0 || i > 15 || j > 15 || k > 255) return;
const offset = 256 * (16 * i + j) + k;
if (data[offset] !== BlockType.AIR) return;
data[offset] = type;
}
for (let i = pos[0] - 2; i < pos[0] + 3; i++) {
for (let j = pos[1] - 2; j < pos[1] + 3; j++) {
for (let k = firstBlock + height - 2; k < firstBlock + height + 2; k++) {
setBlock(i, j, k, BlockType.LEAVES);
}
}
}
return firstBlock + height;
}
function makeChunk(z, x) { function makeChunk(z, x) {
const terrain = makeTerrain(z, x); const seed = 1337;
const terrain = makeTerrain(seed, z, x);
const data = new Uint8Array(16 * 16 * 256); const data = new Uint8Array(16 * 16 * 256);
const trees = [];
for (let i = 0; i < 16; i++) { for (let i = 0; i < 16; i++) {
for (let j = 0; j < 16; j++) { for (let j = 0; j < 16; j++) {
const height = terrain[i * 16 + j]; const height = terrain[i * 16 + j];
// everything above is air
// that block is grass
// everything below is dirt
const offset = i * (16 * 256) + j * 256; const offset = i * (16 * 256) + j * 256;
const stoneHeight = Math.max(48, height); const stoneHeight = Math.max(48, height);
const grassHeight = stoneHeight + 8; const grassHeight = stoneHeight + 8;
const waterHeight = 67; const waterHeight = 67;
let currentHeight = 0;
data.set(Array(stoneHeight).fill(BlockType.STONE), offset); data.set(Array(stoneHeight).fill(BlockType.STONE), offset);
data.set(Array(grassHeight - 1 - stoneHeight).fill(BlockType.DIRT), offset + stoneHeight); currentHeight = stoneHeight;
data.set(Array(grassHeight - currentHeight).fill(BlockType.DIRT), offset + currentHeight);
currentHeight = grassHeight;
if (grassHeight < waterHeight) { if (grassHeight < waterHeight) {
data.set(Array(waterHeight - grassHeight + 1).fill(BlockType.WATER), offset + grassHeight - 1); data.set(Array(waterHeight - currentHeight).fill(BlockType.WATER), offset + currentHeight);
currentHeight = waterHeight;
} else { } else {
data[offset + grassHeight - 1] = BlockType.GRASS; data[offset + grassHeight - 1] = BlockType.GRASS;
if (hasATree(seed, z + i, x + j)) {
trees.push([i, j, currentHeight]);
} }
const surfaceHeight = Math.max(waterHeight, grassHeight);
data.set(Array(256 - surfaceHeight).fill(BlockType.AIR), offset + surfaceHeight);
} }
data.set(Array(256 - currentHeight).fill(BlockType.AIR), offset + currentHeight);
}
}
for (const tree of trees) {
makeATree(data, tree, seed, z, x);
} }
return { return {
@ -140,6 +183,14 @@ function faceTexture(type, dir) {
case BlockType.DIRT: return [2, 15]; case BlockType.DIRT: return [2, 15];
case BlockType.STONE: return [3, 15]; case BlockType.STONE: return [3, 15];
case BlockType.WATER: return [4, 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];
default: return [0, 0]; default: return [0, 0];
} }
} }
@ -156,6 +207,16 @@ function faceCenter(blockCenter, dir) {
} }
} }
function isTransparent(type) {
switch (type) {
case BlockType.WATER:
case BlockType.LEAVES:
case BlockType.AIR:
return true;
default: return false;
}
}
function makeFaceList(data, chunkz, chunkx, blockLookup) { function makeFaceList(data, chunkz, chunkx, blockLookup) {
const lookup = (i, j, k) => { const lookup = (i, j, k) => {
if (i < 0 || j < 0 || i > 15 || j > 15) { if (i < 0 || j < 0 || i > 15 || j > 15) {
@ -176,7 +237,7 @@ function makeFaceList(data, chunkz, chunkx, blockLookup) {
]; ];
const solidFaces = []; const solidFaces = [];
const waterFaces = []; const transparentFaces = [];
for (let i = 0; i < 16; i++) { for (let i = 0; i < 16; i++) {
for (let j = 0; j < 16; j++) { for (let j = 0; j < 16; j++) {
@ -185,21 +246,28 @@ function makeFaceList(data, chunkz, chunkx, blockLookup) {
if (data[bi] === BlockType.AIR) { if (data[bi] === BlockType.AIR) {
continue; continue;
} }
if (isTransparent(data[bi])) {
for (const { dir, faceCenter } of neighbors(i, j, k) for (const { dir, faceCenter } of neighbors(i, j, k)
.filter(({ block }) => block === BlockType.AIR || (block === BlockType.WATER && data[bi] !== BlockType.WATER))) { .filter(({ block }) => block === BlockType.AIR)
if (data[bi] !== BlockType.WATER) { ) {
solidFaces.push({ if (data[bi] === BlockType.WATER) {
blockIndex: bi,
dir,
face: makeFace(dir, faceTexture(data[bi], dir), faceCenter),
});
} else {
faceCenter[1] -= 0.15; faceCenter[1] -= 0.15;
waterFaces.push({ }
blockIndex: bi, transparentFaces.push({
dir, dir,
face: makeFace(dir, faceTexture(data[bi], dir), faceCenter), face: makeFace(dir, faceTexture(data[bi], dir), faceCenter),
}) blockIndex: bi,
});
}
} else {
for (const { dir, faceCenter } of neighbors(i, j, k)
.filter(({ block }) => isTransparent(block))
) {
solidFaces.push({
dir,
face: makeFace(dir, faceTexture(data[bi], dir), faceCenter),
blockIndex: bi,
});
} }
} }
} }
@ -208,7 +276,7 @@ function makeFaceList(data, chunkz, chunkx, blockLookup) {
return { return {
solidFaces, solidFaces,
waterFaces, transparentFaces,
}; };
} }
@ -292,20 +360,19 @@ function invalidateChunkGeometry(world, i, j) {
} }
function addFace(chunk, block, dir) { function addFace(chunk, block, dir) {
if (block.type === BlockType.WATER) {
const face = createChunkFace(block, dir); const face = createChunkFace(block, dir);
if (dir === '+y') { if (isTransparent(block.type)) {
face[1] -= 0.15;
}
chunk.transparentFaces.push(face); chunk.transparentFaces.push(face);
} else { } else {
chunk.faces.push(createChunkFace(block, dir)); chunk.faces.push(face);
} }
} }
export function createChunkFace(block, dir, center = null) { export function createChunkFace(block, dir, center = null) {
center = center ?? faceCenter(block.centerPosition, dir); center = center ?? faceCenter(block.centerPosition, dir);
if (block.type === BlockType.WATER && dir === '+y') {
center[1] -= 0.15;
}
return { return {
dir, dir,
blockIndex: block.blockIndex, blockIndex: block.blockIndex,
@ -337,7 +404,7 @@ export function updateWorldGeometry(gl, world, z, x, timeLimit = 10000) {
const faces = makeFaceList(chunk.data, chunk.position.z, chunk.position.x, lookup); const faces = makeFaceList(chunk.data, chunk.position.z, chunk.position.x, lookup);
chunk.faces = faces.solidFaces; chunk.faces = faces.solidFaces;
chunk.transparentFaces = faces.waterFaces; chunk.transparentFaces = faces.transparentFaces;
} }
chunk.buffer = makeBufferFromFaces(gl, chunk.faces.map(f => f.face)); chunk.buffer = makeBufferFromFaces(gl, chunk.faces.map(f => f.face));
@ -487,6 +554,7 @@ export function markBlock(world, cameraPosition, direction, maxDistance) {
export function destroyBlock(world, block) { export function destroyBlock(world, block) {
const trimFaces = chunk => { const trimFaces = chunk => {
chunk.faces = chunk.faces.filter(({blockIndex}) => chunk.data[blockIndex] !== BlockType.AIR); chunk.faces = chunk.faces.filter(({blockIndex}) => chunk.data[blockIndex] !== BlockType.AIR);
chunk.transparentFaces = chunk.transparentFaces.filter(({blockIndex}) => chunk.data[blockIndex] !== BlockType.AIR);
} }
block.chunk.data[block.blockIndex] = BlockType.AIR; block.chunk.data[block.blockIndex] = BlockType.AIR;
@ -511,8 +579,6 @@ export function destroyBlock(world, block) {
.filter(({ block }) => block.type !== BlockType.AIR && .filter(({ block }) => block.type !== BlockType.AIR &&
block.type !== BlockType.UNDEFINED) block.type !== BlockType.UNDEFINED)
.forEach(({ block, dir }) => { .forEach(({ block, dir }) => {
// TODO: don't add transparent faces twice
// since we "forget" to trim them
addFace(block.chunk, block, dir); addFace(block.chunk, block, dir);
trimFaces(block.chunk); trimFaces(block.chunk);