Inifinite world!! (almost)
This commit is contained in:
parent
5a334fec13
commit
6e47e9cd0a
173
geometry.js
173
geometry.js
@ -37,79 +37,142 @@ function makeSquare() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeZFace(normal, texture, transform) {
|
function memoize(f) {
|
||||||
const textMul = 0.0625;
|
const memo = {};
|
||||||
const textOff = texture.map(x => x * textMul);
|
|
||||||
|
|
||||||
const vertices = [];
|
function g(...args) {
|
||||||
const textures = [];
|
if (!(args in memo)) {
|
||||||
|
memo[args] = f(...args);
|
||||||
const sq = makeSquare();
|
}
|
||||||
|
return memo[args];
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
vertices.push(
|
|
||||||
se3.product(
|
|
||||||
transform,
|
|
||||||
sq.vertices.slice(3 * i, 3 * (i + 1)).concat([1.0])
|
|
||||||
).slice(0, 3));
|
|
||||||
textures.push(sq.textures.slice(2 * i, 2 * (i + 1)).map((v, j) => textMul * v + textOff[j]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const normals = [].concat(...Array(6).fill(normal));
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _makeTextureFace(texture) {
|
||||||
|
const textMul = 0.0625;
|
||||||
|
const textOff = texture.map(x => x * textMul);
|
||||||
|
const textuv = [
|
||||||
|
[0.99, 0.99],
|
||||||
|
[0.01, 0.99],
|
||||||
|
[0.01, 0.01],
|
||||||
|
[0.99, 0.99],
|
||||||
|
[0.01, 0.01],
|
||||||
|
[0.99, 0.01],
|
||||||
|
];
|
||||||
|
|
||||||
|
const textures = textuv.map(uv => uv.map((x, j) => textMul * x + textOff[j]));
|
||||||
|
|
||||||
|
return textures;
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeTextureFace = memoize(_makeTextureFace);
|
||||||
|
|
||||||
|
function makePZFace(texture) {
|
||||||
return {
|
return {
|
||||||
vertices: [].concat(...vertices),
|
vertices: [[0.5, 0.5, 0], [-0.5, 0.5, 0], [-0.5, -0.5, 0], [0.5, 0.5, 0], [-0.5, -0.5, 0], [0.5, -0.5, 0]],
|
||||||
normals,
|
normals: Array(6).fill([0.0, 0.0, 1.0]),
|
||||||
textures: [].concat(...textures),
|
textures: makeTextureFace(texture),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
function makeNZFace(texture) {
|
||||||
|
return {
|
||||||
|
vertices: [[-0.5, 0.5, 0.0], [0.5, 0.5, 0.0], [0.5, -0.5, 0.0], [-0.5, 0.5, 0.0], [0.5, -0.5, 0.0], [-0.5, -0.5, 0.0]],
|
||||||
|
normals: Array(6).fill([0.0, 0.0, -1.0]),
|
||||||
|
textures: makeTextureFace(texture),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function makePXFace(texture) {
|
||||||
|
return {
|
||||||
|
vertices: [[0, 0.5, -0.5], [0, 0.5, 0.5], [0, -0.5, 0.5], [0, 0.5, -0.5], [0, -0.5, 0.5], [0, -0.5, -0.5]],
|
||||||
|
normals: Array(6).fill([1.0, 0.0, 0.0]),
|
||||||
|
textures: makeTextureFace(texture),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function makeNXFace(texture) {
|
||||||
|
return {
|
||||||
|
vertices: [[0, 0.5, 0.5], [0, 0.5, -0.5], [0, -0.5, -0.5], [0, 0.5, 0.5], [0, -0.5, -0.5], [0, -0.5, 0.5]],
|
||||||
|
normals: Array(6).fill([-1.0, 0.0, 0.0]),
|
||||||
|
textures: makeTextureFace(texture),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function makePYFace(texture) {
|
||||||
|
return {
|
||||||
|
vertices: [[0.5, 0, -0.5], [-0.5, 0, -0.5], [-0.5, 0, 0.5], [0.5, 0, -0.5], [-0.5, 0, 0.5], [0.5, 0, 0.5]],
|
||||||
|
normals: Array(6).fill([0.0, 1.0, 0.0]),
|
||||||
|
textures: makeTextureFace(texture),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function makeNYFace(texture) {
|
||||||
|
return {
|
||||||
|
vertices: [[0.5, 0, 0.5], [-0.5, 0, 0.5], [-0.5, 0, -0.5], [0.5, 0, 0.5], [-0.5, 0, -0.5], [0.5, 0, -0.5]],
|
||||||
|
normals: Array(6).fill([0.0, -1.0, 0.0]),
|
||||||
|
textures: makeTextureFace(texture),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function translateFace(face, x, y, z) {
|
||||||
|
return {
|
||||||
|
normals: face.normals,
|
||||||
|
textures: face.textures,
|
||||||
|
vertices: face.vertices.map(([vx, vy, vz]) => [vx + x, vy + y, vz + z]),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeFace(which, texture, centerPos) {
|
export function makeFace(which, texture, centerPos) {
|
||||||
const rot = {
|
switch(which) {
|
||||||
'-x': se3.roty(-Math.PI / 2),
|
case '-x': return translateFace(makeNXFace(texture), ...centerPos);
|
||||||
'+x': se3.roty(Math.PI / 2),
|
case '+x': return translateFace(makePXFace(texture), ...centerPos);
|
||||||
'-y': se3.rotx(Math.PI / 2),
|
case '-y': return translateFace(makeNYFace(texture), ...centerPos);
|
||||||
'+y': se3.rotx(-Math.PI / 2),
|
case '+y': return translateFace(makePYFace(texture), ...centerPos);
|
||||||
'-z': se3.roty(Math.PI),
|
case '-z': return translateFace(makeNZFace(texture), ...centerPos);
|
||||||
'+z': se3.identity(),
|
case '+z': return translateFace(makePZFace(texture), ...centerPos);
|
||||||
}[which];
|
}
|
||||||
|
|
||||||
const normal = {
|
throw Error('unknown face');
|
||||||
'+x': [1.0, 0.0, 0.0],
|
|
||||||
'-x': [-1.0, 0.0, 0.0],
|
|
||||||
'+y': [0.0, 1.0, 0.0],
|
|
||||||
'-y': [0.0, -1.0, 0.0],
|
|
||||||
'+z': [0.0, 0.0, 1.0],
|
|
||||||
'-z': [0.0, 0.0, -1.0],
|
|
||||||
}[which];
|
|
||||||
|
|
||||||
const tf = se3.product(se3.translation(...centerPos), rot);
|
|
||||||
return makeZFace(normal, texture, tf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeBuffersFromFraces(gl, faces) {
|
/** Packs all those faces into one big buffer. */
|
||||||
vertices = [].concat(...faces.map(f => f.vertices));
|
export function makeBufferFromFaces(gl, faces) {
|
||||||
normals = [].concat(...faces.map(f => f.normals));
|
const numVertices = faces.map(f => f.vertices).reduce((count, vertices) => count + vertices.length, 0);
|
||||||
textures = [].concat(...faces.map(f => f.textures));
|
|
||||||
|
|
||||||
const vertexBuffer = gl.createBuffer();
|
// 3 * float32 + 3 * byte (padded to 4) + 2 * short
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
// see https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer#examples
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
|
const vertexSize = 3 * 4 + 4 + 4;
|
||||||
|
|
||||||
const normalBuffer = gl.createBuffer();
|
const glBuffer = gl.createBuffer();
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
|
|
||||||
|
|
||||||
const textureBuffer = gl.createBuffer();
|
const buffer = new Uint8Array(numVertices * vertexSize);
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
|
const dv = new DataView(buffer.buffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textures), gl.STATIC_DRAW);
|
let offset = 0;
|
||||||
|
|
||||||
|
faces.forEach(face => {
|
||||||
|
for (let i = 0; i < face.vertices.length; i++) {
|
||||||
|
const position = face.vertices[i];
|
||||||
|
dv.setFloat32(offset + 0, position[0], true);
|
||||||
|
dv.setFloat32(offset + 4, position[1], true);
|
||||||
|
dv.setFloat32(offset + 8, position[2], true);
|
||||||
|
offset += 12;
|
||||||
|
const normal = face.normals[i];
|
||||||
|
dv.setInt8(offset + 0, normal[0] * 0x7f);
|
||||||
|
dv.setInt8(offset + 1, normal[1] * 0x7f);
|
||||||
|
dv.setInt8(offset + 2, normal[2] * 0x7f);
|
||||||
|
offset += 4;
|
||||||
|
const texture = face.textures[i];
|
||||||
|
dv.setUint16(offset + 0, texture[0] * 0xffff, true);
|
||||||
|
dv.setUint16(offset + 2, texture[1] * 0xffff, true);
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STATIC_DRAW);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
vertexBuffer,
|
glBuffer,
|
||||||
normalBuffer,
|
numVertices,
|
||||||
textureBuffer,
|
delete: () => gl.deleteBuffer(glBuffer),
|
||||||
numVertices: vertices.length / 3,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
291
index.js
291
index.js
@ -1,4 +1,4 @@
|
|||||||
import { makeBuffersFromFraces, makeFace, makeGrassCube } from "./geometry";
|
import { makeBufferFromFaces, makeFace, makeGrassCube } from "./geometry";
|
||||||
import { loadTexture, makeProgram } from "./gl";
|
import { loadTexture, makeProgram } from "./gl";
|
||||||
import * as se3 from './se3';
|
import * as se3 from './se3';
|
||||||
|
|
||||||
@ -70,6 +70,7 @@ function draw(gl, params, objects) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const {program, texture, position, orientation, geometry} of objects) {
|
for (const {program, texture, position, orientation, geometry} of objects) {
|
||||||
|
// load those ahead of time
|
||||||
const viewLoc = gl.getUniformLocation(program, 'uView');
|
const viewLoc = gl.getUniformLocation(program, 'uView');
|
||||||
const modelLoc = gl.getUniformLocation(program, 'uModel');
|
const modelLoc = gl.getUniformLocation(program, 'uModel');
|
||||||
const projLoc = gl.getUniformLocation(program, 'uProjection');
|
const projLoc = gl.getUniformLocation(program, 'uProjection');
|
||||||
@ -80,7 +81,8 @@ function draw(gl, params, objects) {
|
|||||||
const normalLoc = gl.getAttribLocation(program, 'aNormal');
|
const normalLoc = gl.getAttribLocation(program, 'aNormal');
|
||||||
const textureLoc = gl.getAttribLocation(program, 'aTextureCoord');
|
const textureLoc = gl.getAttribLocation(program, 'aTextureCoord');
|
||||||
|
|
||||||
const mvMatrix = se3.product(se3.translation(...position), se3.rotxyz(...orientation));
|
//const mvMatrix = se3.product(se3.translation(...position), se3.rotxyz(...orientation));
|
||||||
|
const mvMatrix = se3.identity();
|
||||||
|
|
||||||
gl.useProgram(program);
|
gl.useProgram(program);
|
||||||
|
|
||||||
@ -89,16 +91,15 @@ function draw(gl, params, objects) {
|
|||||||
gl.uniformMatrix4fv(projLoc, false, new Float32Array(params.projMatrix));
|
gl.uniformMatrix4fv(projLoc, false, new Float32Array(params.projMatrix));
|
||||||
gl.uniform3f(fogColorLoc, 0.6, 0.8, 1.0);
|
gl.uniform3f(fogColorLoc, 0.6, 0.8, 1.0);
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, geometry.vertexBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, geometry.glBuffer);
|
||||||
gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 0, 0);
|
|
||||||
|
gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 20, 0);
|
||||||
gl.enableVertexAttribArray(positionLoc);
|
gl.enableVertexAttribArray(positionLoc);
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, geometry.normalBuffer);
|
gl.vertexAttribPointer(normalLoc, 3, gl.BYTE, true, 20, 12);
|
||||||
gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 0, 0);
|
|
||||||
gl.enableVertexAttribArray(normalLoc);
|
gl.enableVertexAttribArray(normalLoc);
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, geometry.textureBuffer);
|
gl.vertexAttribPointer(textureLoc, 2, gl.UNSIGNED_SHORT, true, 20, 16);
|
||||||
gl.vertexAttribPointer(textureLoc, 2, gl.FLOAT, false, 0, 0);
|
|
||||||
gl.enableVertexAttribArray(textureLoc);
|
gl.enableVertexAttribArray(textureLoc);
|
||||||
|
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
@ -149,7 +150,47 @@ function handleKeys(params) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function tick(time, gl, params, objects) {
|
function getObjects(world, z, x, program, texture) {
|
||||||
|
return world.chunks
|
||||||
|
.filter(chunk => {
|
||||||
|
if (chunk.position.z < z - 8 * 16) return false;
|
||||||
|
if (chunk.position.z > z + 7 * 16) return false;
|
||||||
|
if (chunk.position.x < x - 8 * 16) return false;
|
||||||
|
if (chunk.position.x > x + 7 * 16) return false;
|
||||||
|
|
||||||
|
if (chunk.buffer === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map(chunk => ({
|
||||||
|
position: [0.0, 0.0, 0.0],
|
||||||
|
orientation: [0.0, 0.0, 0.0],
|
||||||
|
program,
|
||||||
|
geometry: chunk.buffer,
|
||||||
|
texture,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function tick(time, gl, params) {
|
||||||
|
const campos = params.camera.position;
|
||||||
|
// expensive stuff, can take several cycles
|
||||||
|
try {
|
||||||
|
let timeLeft = 10;
|
||||||
|
const start = performance.now();
|
||||||
|
generateMissingChunks(params.world, campos[2], campos[0], timeLeft);
|
||||||
|
|
||||||
|
timeLeft -= performance.now() - start;
|
||||||
|
updateWorldGeometry(gl, params.world, campos[2], campos[0], timeLeft);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
if (ex !== 'timesup') {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const objects = getObjects(params.world, campos[2], campos[0], params.program, params.texture);
|
||||||
draw(gl, params, objects);
|
draw(gl, params, objects);
|
||||||
|
|
||||||
handleKeys(params);
|
handleKeys(params);
|
||||||
@ -159,7 +200,7 @@ function tick(time, gl, params, objects) {
|
|||||||
|
|
||||||
document.querySelector('#fps').textContent = `${1.0 / dt} fps`;
|
document.querySelector('#fps').textContent = `${1.0 / dt} fps`;
|
||||||
|
|
||||||
requestAnimationFrame(time => tick(time, gl, params, objects));
|
requestAnimationFrame(time => tick(time, gl, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
const BlockType = {
|
const BlockType = {
|
||||||
@ -209,7 +250,7 @@ function makeTerrain(x, y) {
|
|||||||
+ ghettoPerlinNoise(seed, x, y, 64) * 0.5
|
+ ghettoPerlinNoise(seed, x, y, 64) * 0.5
|
||||||
);
|
);
|
||||||
|
|
||||||
const terrain = Array(16);
|
const terrain = Array(16 * 16);
|
||||||
|
|
||||||
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++) {
|
||||||
@ -220,8 +261,8 @@ function makeTerrain(x, y) {
|
|||||||
return terrain;
|
return terrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeChunk(x, y) {
|
function makeChunk(z, x) {
|
||||||
const terrain = makeTerrain(x, y);
|
const terrain = makeTerrain(z, x);
|
||||||
|
|
||||||
const data = new Uint8Array(16 * 16 * 256);
|
const data = new Uint8Array(16 * 16 * 256);
|
||||||
|
|
||||||
@ -232,7 +273,7 @@ function makeChunk(x, y) {
|
|||||||
// that block is grass
|
// that block is grass
|
||||||
// everything below is dirt
|
// everything below is dirt
|
||||||
const offset = i * (16 * 256) + j * 256;
|
const offset = i * (16 * 256) + j * 256;
|
||||||
const stoneHeight = Math.min(62, height);
|
const stoneHeight = Math.min(52, height);
|
||||||
data.set(Array(stoneHeight).fill(BlockType.STONE), offset);
|
data.set(Array(stoneHeight).fill(BlockType.STONE), offset);
|
||||||
if (stoneHeight < height) {
|
if (stoneHeight < height) {
|
||||||
data.set(Array(height - 1 - stoneHeight).fill(BlockType.DIRT), offset + stoneHeight);
|
data.set(Array(height - 1 - stoneHeight).fill(BlockType.DIRT), offset + stoneHeight);
|
||||||
@ -243,50 +284,51 @@ function makeChunk(x, y) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
position: {x, y},
|
position: {z, x},
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeChunkMesh(chunk) {
|
function blockLookup(world, x, y, z) {
|
||||||
const {x: z0, y: x0} = chunk.position;
|
const chunki = Math.floor(z / 16);
|
||||||
const {data} = chunk;
|
const chunkj = Math.floor(x / 16);
|
||||||
|
|
||||||
const blockPos = i => {
|
const chunk = world.chunkMap.get(chunki, chunkj);
|
||||||
const x = Math.floor(i / (16 * 256));
|
if (chunk === undefined) {
|
||||||
const y = Math.floor((i - 16 * 258 * x) / 256);
|
return BlockType.STONE;
|
||||||
const z = i - 256 * (16*x + y);
|
}
|
||||||
|
|
||||||
return [x, y, z];
|
const i = Math.floor(z - chunki * 16);
|
||||||
};
|
const j = Math.floor(x - chunkj * 16);
|
||||||
|
const k = Math.floor(y);
|
||||||
|
|
||||||
const blockIndex = (i, j, k) => 256 * (i*16 +j) + k;
|
return chunk.data[256 * (16*i + j) + k];
|
||||||
|
}
|
||||||
|
|
||||||
const sideNeighbors = (i, j) => {
|
function makeChunkBuffer(gl, data, z0, x0, lookup) {
|
||||||
|
const sideNeighbors = (bi, i, j, k) => {
|
||||||
|
if (i === 0 || j === 0 || i === 15 || j === 15) {
|
||||||
|
return [
|
||||||
|
{ block: lookup(i - 1, j, k), dir: '-z', faceCenter: [x0 + j, k, z0 + i - 0.5] },
|
||||||
|
{ block: lookup(i + 1, j, k), dir: '+z', faceCenter: [x0 + j, k, z0 + i + 0.5] },
|
||||||
|
{ block: lookup(i, j - 1, k), dir: '-x', faceCenter: [x0 + j - 0.5, k, z0 + i] },
|
||||||
|
{ block: lookup(i, j + 1, k), dir: '+x', faceCenter: [x0 + j + 0.5, k, z0 + i] },
|
||||||
|
];
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
{pos: [i - 1, j], dir: '-z'},
|
{ block: data[bi - 256 * 16], dir: '-z', faceCenter: [x0 + j, k, z0 + i - 0.5] },
|
||||||
{pos: [i + 1, j], dir: '+z'},
|
{ block: data[bi + 256 * 16], dir: '+z', faceCenter: [x0 + j, k, z0 + i + 0.5] },
|
||||||
{pos: [i, j - 1], dir: '-x'},
|
{ block: data[bi - 256], dir: '-x', faceCenter: [x0 + j - 0.5, k, z0 + i] },
|
||||||
{pos: [i, j + 1], dir: '+x'},
|
{ block: data[bi + 256], dir: '+x', faceCenter: [x0 + j + 0.5, k, z0 + i] },
|
||||||
].filter(v => v.pos.every(i => i >= 0 && i < 16));
|
];
|
||||||
};
|
|
||||||
|
|
||||||
const faceDir = (dir, x, y, z) => {
|
|
||||||
return {
|
|
||||||
'-x': [x - 0.5, y, z],
|
|
||||||
'+x': [x + 0.5, y, z],
|
|
||||||
'-z': [x, y, z - 0.5],
|
|
||||||
'+z': [x, y, z + 0.5],
|
|
||||||
}[dir];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const faces = [];
|
const faces = [];
|
||||||
|
|
||||||
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++) {
|
||||||
for (let k = 0; k < 256; k++) {
|
let bi = i * 16 * 256 + j * 256;
|
||||||
const bi = blockIndex(i, j, k);
|
for (let k = 0; k < 256; k++, bi++) {
|
||||||
|
|
||||||
const upTexture = (() => {
|
const upTexture = (() => {
|
||||||
switch (data[bi - 1]) {
|
switch (data[bi - 1]) {
|
||||||
case BlockType.GRASS: return [0, 15];
|
case BlockType.GRASS: return [0, 15];
|
||||||
@ -308,41 +350,131 @@ function makeChunkMesh(chunk) {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
for ({ pos, dir } of sideNeighbors(i, j)) {
|
for (let {block, dir, faceCenter} of sideNeighbors(bi, i, j, k)) {
|
||||||
if (data[blockIndex(...pos, k)] == BlockType.AIR) {
|
if (block === BlockType.AIR) {
|
||||||
faces.push(makeFace(dir, sideTexture, faceDir(dir, x0 + j, k, z0 + i)));
|
faces.push(makeFace(dir, sideTexture, faceCenter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i == 0) {
|
|
||||||
faces.push(makeFace('-z', sideTexture, faceDir('-z', x0 + j, k, z0 + i)));
|
|
||||||
}
|
|
||||||
if (i == 15) {
|
|
||||||
faces.push(makeFace('+z', sideTexture, faceDir('+z', x0 + j, k, z0 + i)));
|
|
||||||
}
|
|
||||||
if (j == 0) {
|
|
||||||
faces.push(makeFace('-x', sideTexture, faceDir('-x', x0 + j, k, z0 + i)));
|
|
||||||
}
|
|
||||||
if (j == 15) {
|
|
||||||
faces.push(makeFace('+x', sideTexture, faceDir('+x', x0 + j, k, z0 + i)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return faces;
|
return makeBufferFromFaces(gl, faces);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeWorldBuffers(gl) {
|
// - data <-- need to generate the first time, update when m&p
|
||||||
const chunkFaces = [].concat(
|
// - faces <-- need to generate once when chunk enters view
|
||||||
makeChunkMesh(makeChunk(0, -16)),
|
// - buffers <-- need to generate every time geometry changes?
|
||||||
makeChunkMesh(makeChunk(16, -16)),
|
// --> could also render chunks 1 by 1
|
||||||
makeChunkMesh(makeChunk(0, 0)),
|
|
||||||
makeChunkMesh(makeChunk(16, 0)),
|
|
||||||
makeChunkMesh(makeChunk(16, 16)),
|
|
||||||
makeChunkMesh(makeChunk(0, 16)),
|
|
||||||
);
|
|
||||||
|
|
||||||
return makeBuffersFromFraces(gl, chunkFaces);
|
class ChunkMap {
|
||||||
|
map = {};
|
||||||
|
|
||||||
|
key(i, j) {
|
||||||
|
// meh, this limits us to 65536 chunks in the x direction :/
|
||||||
|
return (i << 16) + j;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(i, j) {
|
||||||
|
return this.map[this.key(i, j)];
|
||||||
|
}
|
||||||
|
|
||||||
|
set(i, j, x) {
|
||||||
|
this.map[this.key(i, j)] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
has(i, j) {
|
||||||
|
return this.key(i, j) in this.map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Makes a brave new world, centered at (0, 0). */
|
||||||
|
function makeWorld() {
|
||||||
|
return generateMissingChunks({chunks: [], chunkMap: new ChunkMap()}, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the world, generating missing chunks if necessary. */
|
||||||
|
function generateMissingChunks(world, z, x, timeLimit = 10000) {
|
||||||
|
const ic = Math.floor(z / 16);
|
||||||
|
const jc = Math.floor(x / 16);
|
||||||
|
|
||||||
|
const start = performance.now();
|
||||||
|
|
||||||
|
for (let i = ic - 8; i < ic + 8; i++) {
|
||||||
|
for (let j = jc - 8; j < jc + 8; j++) {
|
||||||
|
if (world.chunkMap.has(i, j)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const chunk = makeChunk(i * 16, j * 16);
|
||||||
|
world.chunks.push(chunk);
|
||||||
|
world.chunkMap.set(i, j, chunk);
|
||||||
|
|
||||||
|
invalidateChunkGeometry(world, i - 1, j);
|
||||||
|
invalidateChunkGeometry(world, i + 1, j);
|
||||||
|
invalidateChunkGeometry(world, i, j - 1);
|
||||||
|
invalidateChunkGeometry(world, i, j + 1);
|
||||||
|
|
||||||
|
if (performance.now() - start > timeLimit) {
|
||||||
|
throw 'timesup';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidateChunkGeometry(world, i, j) {
|
||||||
|
const chunk = world.chunkMap.get(i, j);
|
||||||
|
if (chunk === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chunk.buffer === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chunk.buffer.delete();
|
||||||
|
delete chunk.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generates geometry for all visible chunks. */
|
||||||
|
function updateWorldGeometry(gl, world, z, x, timeLimit = 10000) {
|
||||||
|
const ic = Math.floor(z / 16);
|
||||||
|
const jc = Math.floor(x / 16);
|
||||||
|
|
||||||
|
const blockIndex = (i, j, k) => 256 * (i*16 +j) + k;
|
||||||
|
|
||||||
|
const start = performance.now();
|
||||||
|
|
||||||
|
// k. Now, generate buffers for all chunks
|
||||||
|
for (let i = ic - 8; i < ic + 8; i++) {
|
||||||
|
for (let j = jc - 8; j < jc + 8; j++) {
|
||||||
|
const chunk = world.chunkMap.get(i, j);
|
||||||
|
|
||||||
|
if (chunk.buffer !== undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkz = 16 * i;
|
||||||
|
const chunkx = 16 * j;
|
||||||
|
const lookup = (i, j, k) => blockLookup(world, j + chunkx, k, i + chunkz);
|
||||||
|
|
||||||
|
chunk.buffer = makeChunkBuffer(gl, chunk.data, chunk.position.z, chunk.position.x, lookup);
|
||||||
|
|
||||||
|
// throttle this for fluidity
|
||||||
|
if (performance.now() - start > timeLimit) {
|
||||||
|
throw 'timesup';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkCollision(curPos, newPos, world) {
|
||||||
|
// [ ] get a BB for the player at newPos
|
||||||
|
// [ ] check it against world
|
||||||
|
// [ ] need a struct to access world @ x, y, z
|
||||||
|
// [ ] need to check all 8 corners of the BB
|
||||||
|
// [ ] if it collides, figure out by how much and return a safe newPos
|
||||||
|
|
||||||
|
return safeNewPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stuff I need to do:
|
// Stuff I need to do:
|
||||||
@ -359,6 +491,7 @@ function makeWorldBuffers(gl) {
|
|||||||
// [ ] flowy water
|
// [ ] flowy water
|
||||||
// [ ] ALIGN CHUNK WITH WORLD COORDS
|
// [ ] ALIGN CHUNK WITH WORLD COORDS
|
||||||
// [x] better controls
|
// [x] better controls
|
||||||
|
// [ ] save the world (yay) to local storage (bah)
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const canvas = document.querySelector('#game');
|
const canvas = document.querySelector('#game');
|
||||||
@ -370,6 +503,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const program = makeProgram(gl, TEST_VSHADER, TEST_FSHADER);
|
const program = makeProgram(gl, TEST_VSHADER, TEST_FSHADER);
|
||||||
|
const texture = await loadTexture(gl, 'texture.png');
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
projMatrix: se3.perspective(Math.PI / 3, canvas.clientWidth / canvas.clientHeight, 0.1, 100.0),
|
projMatrix: se3.perspective(Math.PI / 3, canvas.clientWidth / canvas.clientHeight, 0.1, 100.0),
|
||||||
@ -377,21 +511,12 @@ async function main() {
|
|||||||
position: [0.0, 70.5, 0.0],
|
position: [0.0, 70.5, 0.0],
|
||||||
orientation: [0.0, Math.PI, 0.0],
|
orientation: [0.0, Math.PI, 0.0],
|
||||||
},
|
},
|
||||||
keys: new Set()
|
keys: new Set(),
|
||||||
|
world: makeWorld(),
|
||||||
|
texture,
|
||||||
|
program,
|
||||||
}
|
}
|
||||||
|
|
||||||
const texture = await loadTexture(gl, 'texture.png');
|
|
||||||
|
|
||||||
const objects = [
|
|
||||||
{
|
|
||||||
program,
|
|
||||||
position: [0.0, 0.0, 0.0],
|
|
||||||
orientation: [0.0, 0.0, 0.0],
|
|
||||||
geometry: makeWorldBuffers(gl),
|
|
||||||
texture,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
canvas.onclick = e => {
|
canvas.onclick = e => {
|
||||||
canvas.requestPointerLock();
|
canvas.requestPointerLock();
|
||||||
const keyListener = e => {
|
const keyListener = e => {
|
||||||
@ -419,7 +544,7 @@ async function main() {
|
|||||||
document.addEventListener('keydown', keyListener);
|
document.addEventListener('keydown', keyListener);
|
||||||
document.addEventListener('keyup', keyListener);
|
document.addEventListener('keyup', keyListener);
|
||||||
};
|
};
|
||||||
requestAnimationFrame(time => tick(time, gl, params, objects));
|
requestAnimationFrame(time => tick(time, gl, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = main;
|
window.onload = main;
|
Loading…
Reference in New Issue
Block a user