diff --git a/index.js b/index.js index 1067a69..1ff44de 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ import { loadTexture, makeProgram } from "./gl"; import { blockLookup, BlockType, generateMissingChunks, makeWorld, updateWorldGeometry } from './world'; import * as se3 from './se3'; +import { makeBufferFromFaces, makeFace } from "./geometry"; const TEST_VSHADER = ` attribute vec3 aPosition; @@ -62,6 +63,8 @@ function draw(gl, params, objects) { gl.enable(gl.CULL_FACE); gl.cullFace(gl.BACK); + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); @@ -196,6 +199,34 @@ function updatePhysics(params) { params.isOnGround = isOnGround; } +function tagABlock(gl, params, objects) { + const dir = [0, 0, -1, 1.0]; + const camori = params.camera.orientation; + const ori = se3.inverse(se3.rotxyz(-camori[0], -camori[1], -camori[2])); + const viewDirection = se3.apply(ori, dir).slice(0, 3); + + const face = markBlock(params.world, params.camera.position, viewDirection); + if (face === undefined) { + return; + } + + if (params.tagBuffer !== undefined) { + gl.deleteBuffer(params.tagBuffer.glBuffer); + delete params.tagBuffer; + } + const buffer = makeBufferFromFaces(gl, [face]); + params.tagBuffer = buffer; + + const obj = { + position: [0.0, 0.0, 0.0], + orientation: [0.0, 0.0, 0.0], + geometry: buffer, + glContext: params.worldGl, + }; + + objects.push(obj); +} + function tick(time, gl, params) { handleKeys(params); updatePhysics(params); @@ -218,6 +249,9 @@ function tick(time, gl, params) { } const objects = getObjects(params.world, campos[2], campos[0], params.worldGl); + + tagABlock(gl, params, objects); + draw(gl, params, objects); const dt = (time - stuff.lastFrameTime) * 0.001; @@ -278,13 +312,99 @@ function checkCollision(curPos, newPos, world) { }; } +function minIndex(arr) { + return arr.reduce((min, val, i) => val >= arr[min] ? min : i, -1); +} + +function movePoint(p, s, u) { + return [p[0] + s * u[0], p[1] + s * u[1], p[2] + s * u[2]]; +} + +function rayThroughGrid(origin, direction, maxDistance) { + const range = i => [...Array(i).keys()]; + const length = p => Math.sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * [2]); + + const nextGrid = range(3).map(i => direction[i] > 0 ? + Math.floor(origin[i] + 0.5) + 0.5 : + Math.floor(origin[i] + 0.5) - 0.5); + const distanceToGrid = range(3).map(i => (nextGrid[i] - origin[i]) / direction[i]) + .map(v => v === 0.0 ? Number.POSITIVE_INFINITY : v); + const axis = minIndex(distanceToGrid); + const rayLength = distanceToGrid[axis]; + + if (rayLength > maxDistance) { + return {}; + } + + const position = movePoint(origin, distanceToGrid[axis], direction); + const normal = range(3).map(i => i === axis ? -Math.sign(direction[i]) : 0); + + return {position, normal, distance: rayLength}; +} + +function castRay(world, origin, direction, maxDistance) { + let currentPoint = origin; + + while (maxDistance > 0) { + const {position, normal, distance} = rayThroughGrid(currentPoint, direction, maxDistance); + + if (position === undefined) { + return; + } + + maxDistance -= distance; + currentPoint = position; + const blockCenter = movePoint(position, -0.5, normal); + const block = blockLookup(world, ...blockCenter); + + if (block.type === BlockType.AIR) { + continue; + } + + return { + block, + normal, + }; + } +} + +function markBlock(world, cameraPosition, direction) { + const maxDistance = 10; + const hit = castRay(world, cameraPosition, direction, maxDistance); + + if (hit === undefined) { + return; + } + + const texture = [0, 14]; + const {normal, block} = hit; + const faceCenter = movePoint(block.centerPosition, 0.51, normal); + + if (normal[0] > 0) { + return makeFace('+x', texture, faceCenter); + } else if (normal[0] < 0) { + return makeFace('-x', texture, faceCenter); + } else if (normal[1] > 0) { + return makeFace('+y', texture, faceCenter); + } else if (normal[1] < 0) { + return makeFace('-y', texture, faceCenter); + } else if (normal[2] > 0) { + return makeFace('+z', texture, faceCenter); + } else if (normal[2] < 0) { + return makeFace('-z', texture, faceCenter); + } +} + // Mine & place // ------------ -// [ ] ray casting -// [ ] block outline +// [x] ray casting +// [x] block outline // [ ] crosshair // [ ] dynamic terrain re-rendering // [ ] should use a linked list of air contact blocks +// --> might not be needed. Only need to re-render a single chunk, +// should be fast enough. We render 16 of them every time we +// walk 16 blocks in any direction. // Stuff I need to do: // [x] a skybox diff --git a/se3.js b/se3.js index 3b88fae..5717e9d 100644 --- a/se3.js +++ b/se3.js @@ -68,6 +68,16 @@ export function translation(x, y, z) { ]; } +export function inverse(m) { + // TODO: translation + return [ + m[0], m[4], m[8], 0.0, + m[1], m[5], m[9], 0.0, + m[2], m[6], m[10], 0.0, + 0, 0, 0, 1, + ]; +} + export function product(a, b) { const c = (i, j) => ( a[4 * 0 + i] * b[4 * j + 0] + diff --git a/texture.png b/texture.png index aab2d7b..72efbd4 100644 Binary files a/texture.png and b/texture.png differ