diff --git a/geometry.js b/geometry.js index c1aa0ab..bbd57d8 100644 --- a/geometry.js +++ b/geometry.js @@ -174,17 +174,4 @@ export function makeBufferFromFaces(gl, faces) { numVertices, delete: () => gl.deleteBuffer(glBuffer), }; -} - -export function makeGrassCube(gl) { - faces = [ - makeFace('+y', [0, 15], [0.0, 0.5, 0.0]), - makeFace('-y', [2, 15], [0.0, -0.5, 0.0]), - makeFace('-x', [1, 15], [-0.5, 0.0, 0.0]), - makeFace('+x', [1, 15], [0.5, 0.0, 0.0]), - makeFace('-z', [1, 15], [0.0, 0.0, -0.5]), - makeFace('+z', [1, 15], [0.0, 0.0, 0.5]), - ]; - - return makeBuffersFromFraces(gl, faces); } \ No newline at end of file diff --git a/index.js b/index.js index 65bd087..a68707b 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import { makeBufferFromFaces, makeFace, makeGrassCube } from "./geometry"; +import { makeBufferFromFaces, makeFace} from "./geometry"; import { loadTexture, makeProgram } from "./gl"; import * as se3 from './se3'; @@ -112,6 +112,8 @@ function draw(gl, params, objects) { gl.uniform1i(samplerLoc, 0); gl.drawArrays(gl.TRIANGLES, 0, geometry.numVertices); +// gl.bindTexture(gl.TEXTURE_2D, null); +// gl.drawArrays(gl.LINE_STRIP, 0, geometry.numVertices); } } @@ -126,8 +128,24 @@ function handleKeys(params) { const ori = se3.rotxyz(...camori); const tf = se3.apply(ori, dir); - params.camera.position[0] += tf[0]; - params.camera.position[2] += tf[2]; + if (params.flying) { + params.camera.position[0] += tf[0]; + params.camera.position[2] += tf[2]; + } + if (params.isOnGround) { + params.camera.velocity[0] = tf[0]; + params.camera.velocity[2] = tf[2]; + } else { + params.camera.velocity[0] += tf[0] / 60; + params.camera.velocity[2] += tf[2] / 60; + + if (Math.abs(params.camera.velocity[0]) > Math.abs(tf[0])) { + params.camera.velocity[0] = tf[0]; + } + if (Math.abs(params.camera.velocity[2]) > Math.abs(tf[2])) { + params.camera.velocity[2] = tf[2]; + } + } }; params.keys.forEach(key => { @@ -146,7 +164,14 @@ function handleKeys(params) { return; case 'Space': - params.camera.position[1] += 0.1; + if(params.flying) { + params.camera.position[1] += 0.1; + } else { + if (params.jumpAmount > 0) { + params.camera.velocity[1] = 5.0 / 60; + params.jumpAmount -= 5.0; + } + } return; case 'ControlLeft': params.camera.position[1] -= 0.1; @@ -178,8 +203,27 @@ function getObjects(world, z, x, program, texture) { })); } +function updatePhysics(params) { + params.camera.velocity[1] -= 9.8 / 60 / 60; + + const oldPos = params.camera.position; + const targetPos = params.flying ? oldPos : params.camera.position.map((v, i) => v + params.camera.velocity[i]); + const {isOnGround, newPos} = checkCollision(oldPos, targetPos, params.world); + params.camera.position = newPos; + params.camera.velocity = newPos.map((v, i) => v - oldPos[i]); + if (isOnGround) { + params.jumpAmount = 20; + params.camera.velocity = params.camera.velocity.map(v => v * 0.7); + } + params.isOnGround = isOnGround; +} + function tick(time, gl, params) { + handleKeys(params); + updatePhysics(params); + const campos = params.camera.position; + // expensive stuff, can take several cycles try { let timeLeft = 10; @@ -198,8 +242,6 @@ function tick(time, gl, params) { const objects = getObjects(params.world, campos[2], campos[0], params.program, params.texture); draw(gl, params, objects); - handleKeys(params); - const dt = (time - stuff.lastFrameTime) * 0.001; stuff.lastFrameTime = time; @@ -296,19 +338,31 @@ function makeChunk(z, x) { } function blockLookup(world, x, y, z) { - const chunki = Math.floor(z / 16); - const chunkj = Math.floor(x / 16); + const midx = x + 0.5; + const midy = y + 0.5; + const midz = z + 0.5; + + const chunki = Math.floor(midz / 16); + const chunkj = Math.floor(midx / 16); const chunk = world.chunkMap.get(chunki, chunkj); if (chunk === undefined) { - return BlockType.STONE; + return { + type: BlockType.STONE, + x, y, z, + }; } - const i = Math.floor(z - chunki * 16); - const j = Math.floor(x - chunkj * 16); - const k = Math.floor(y); + const i = Math.floor(midz - chunki * 16); + const j = Math.floor(midx - chunkj * 16); + const k = Math.floor(midy); - return chunk.data[256 * (16*i + j) + k]; + return { + type: chunk.data[256 * (16*i + j) + k], + x: j, + y: k, + z: i, + }; } function makeChunkBuffer(gl, data, z0, x0, lookup) { @@ -419,7 +473,7 @@ function generateMissingChunks(world, z, x, timeLimit = 10000) { invalidateChunkGeometry(world, i + 1, j); invalidateChunkGeometry(world, i, j - 1); invalidateChunkGeometry(world, i, j + 1); - + if (performance.now() - start > timeLimit) { throw 'timesup'; } @@ -462,7 +516,7 @@ function updateWorldGeometry(gl, world, z, x, timeLimit = 10000) { const chunkz = 16 * i; const chunkx = 16 * j; - const lookup = (i, j, k) => blockLookup(world, j + chunkx, k, i + chunkz); + const lookup = (i, j, k) => blockLookup(world, j + chunkx, k, i + chunkz).type; chunk.buffer = makeChunkBuffer(gl, chunk.data, chunk.position.z, chunk.position.x, lookup); @@ -477,23 +531,63 @@ function updateWorldGeometry(gl, world, z, x, timeLimit = 10000) { } 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 + // I guess Steve is about 1.7 m tall? + // he also has a 60x60 cm axis-aligned square section '^_^ + // box is centered around the camera + const steveBB = [ + [-0.3, 0.2, -0.3], + [-0.3, 0.2, 0.3], + [0.3, 0.2, -0.3], + [0.3, 0.2, 0.3], - return safeNewPos; + [-0.3, -1.5, -0.3], + [-0.3, -1.5, 0.3], + [0.3, -1.5, -0.3], + [0.3, -1.5, 0.3], + ]; + + const translate = (v, pos) => v.map((el, i) => el + pos[i]); + + let dp = newPos.map((x, i) => x - curPos[i]); + let isOnGround = false; + + for (let i = 0; i < 3; i++) { + const newSteve = v => v.map((x, j) => i === j ? x + newPos[j] : x + curPos[j]); + for (const point of steveBB.map(newSteve)) { + const block = blockLookup(world, ...point); + if (block.type !== BlockType.AIR) { + if (i === 1 && dp[i] < 0) { + isOnGround = true; + console.log(`on ground`); + } + dp[i] = 0; + } + } + } + for (let i = 0; i < 3; i++) { + const newSteve = v => v.map((x, j) => curPos[j] + x + dp[j]); + for (const point of steveBB.map(newSteve)) { + const block = blockLookup(world, ...point); + if (block.type !== BlockType.AIR) { + dp[i] = 0; + } + } + } + + return { + newPos: translate(curPos, dp), + isOnGround, + }; } // Stuff I need to do: // [x] a skybox // [x] a movable camera -// [ ] some kind of gravity -// [ ] collision detection +// [x] some kind of gravity +// [x] collision detection // [x] more blocks // [ ] ability to mine & place -// [ ] generating & loading of more chunks +// [x] generating & loading of more chunks // [x] distance fog // [ ] different biomes (with different noise stats) // [ ] non-flowy water @@ -519,6 +613,7 @@ async function main() { camera: { position: [0.0, 70.5, 0.0], orientation: [0.0, Math.PI, 0.0], + velocity: [0, 0, 0], }, keys: new Set(), world: makeWorld(), @@ -526,6 +621,8 @@ async function main() { program, lightDirection: [-0.2, -0.5, 0.4], ambiantLight: 0.7, + flying: false, + isOnGround: false, } document.querySelector('#lightx').oninput = e => { @@ -558,6 +655,12 @@ async function main() { const keyListener = e => { if (e.type === 'keydown') { params.keys.add(e.code); + + switch (e.code) { + case 'KeyF': + params.flying = !params.flying; + console.log(`flying: ${params.flying}`) + } } else { params.keys.delete(e.code); } @@ -583,4 +686,4 @@ async function main() { requestAnimationFrame(time => tick(time, gl, params)); } -window.onload = main; \ No newline at end of file +window.onload = main;