Add a few trees
This commit is contained in:
parent
10b5109e21
commit
86a65fc39c
16
game.js
16
game.js
@ -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
|
||||||
|
23
terrain.js
23
terrain.js
@ -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) {
|
// super ghetto random
|
||||||
const dot = (vx, vy) => vx[0] * vy[0] + vx[1] * vy[1];
|
function xorshift(x) {
|
||||||
// super ghetto random
|
|
||||||
const 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);
|
||||||
|
BIN
texture.png
BIN
texture.png
Binary file not shown.
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
130
world.js
130
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 } 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;
|
||||||
}
|
}
|
||||||
for (const {dir, faceCenter} of neighbors(i, j, k)
|
if (isTransparent(data[bi])) {
|
||||||
.filter(({ block }) => block === BlockType.AIR || (block === BlockType.WATER && data[bi] !== BlockType.WATER))) {
|
for (const { dir, faceCenter } of neighbors(i, j, k)
|
||||||
if (data[bi] !== BlockType.WATER) {
|
.filter(({ block }) => block === BlockType.AIR)
|
||||||
solidFaces.push({
|
) {
|
||||||
blockIndex: bi,
|
if (data[bi] === BlockType.WATER) {
|
||||||
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);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user