Placing blocks. Also better terrain. And still water.
Just a few things.
This commit is contained in:
		
							
								
								
									
										81
									
								
								game.js
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								game.js
									
									
									
									
									
								
							| @@ -1,6 +1,15 @@ | ||||
| import { makeBufferFromFaces } from "./geometry"; | ||||
| import * as se3 from './se3'; | ||||
| import { checkCollision, destroySelectedBlock, generateMissingChunks, markBlock, updateWorldGeometry } from './world'; | ||||
| import { | ||||
|     BlockType, | ||||
|     castRay, | ||||
|     checkCollision, | ||||
|     destroyBlock, | ||||
|     generateMissingChunks, | ||||
|     makeBlock, | ||||
|     markBlock, | ||||
|     updateWorldGeometry, | ||||
| } from './world'; | ||||
|  | ||||
| /** Draw. | ||||
|  * | ||||
| @@ -116,7 +125,7 @@ function handleKeys(params) { | ||||
| } | ||||
|  | ||||
| function getObjects(world, z, x, glContext) { | ||||
|     return world.chunks | ||||
|     const drawedChunks = world.chunks | ||||
|         .filter(chunk => { | ||||
|             if (chunk.position.z < z - 8 * 16) return false; | ||||
|             if (chunk.position.z > z + 7 * 16) return false; | ||||
| @@ -128,13 +137,16 @@ function getObjects(world, z, x, glContext) { | ||||
|             } | ||||
|  | ||||
|             return true; | ||||
|         }) | ||||
|         .map(chunk => ({ | ||||
|             position: [0.0, 0.0, 0.0], | ||||
|             orientation: [0.0, 0.0, 0.0], | ||||
|             geometry: chunk.buffer, | ||||
|             glContext, | ||||
|         })); | ||||
|         }); | ||||
|     const buffers = drawedChunks | ||||
|         .map(chunk => chunk.buffer) | ||||
|         .concat(drawedChunks.map(chunk => chunk.transparentBuffer)); | ||||
|     return buffers.map(buffer => ({ | ||||
|         position: [0.0, 0.0, 0.0], | ||||
|         orientation: [0.0, 0.0, 0.0], | ||||
|         geometry: buffer, | ||||
|         glContext, | ||||
|     })); | ||||
| } | ||||
|  | ||||
| function updatePhysics(params) { | ||||
| @@ -180,32 +192,25 @@ function tagABlock(gl, params, objects) { | ||||
|     objects.push(obj); | ||||
| } | ||||
|  | ||||
| // Mine & place | ||||
| // ------------ | ||||
| // [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 | ||||
| // [x] a movable camera | ||||
| // [x] some kind of gravity | ||||
| // [x] collision detection | ||||
| // [x] more blocks | ||||
| // [ ] ability to mine & place | ||||
| // [x] ability to mine & place | ||||
| // [x] generating & loading of more chunks | ||||
| // [x] distance fog | ||||
| // [x] better controls | ||||
| // [ ] different biomes (with different noise stats) | ||||
| // [ ] non-flowy water | ||||
| // [ ] flowy water | ||||
| // [ ] ALIGN CHUNK WITH WORLD COORDS | ||||
| // [x] better controls | ||||
| // [ ] save the world (yay) to local storage (bah) | ||||
| // [ ] mines | ||||
| // [ ] crafting | ||||
| // [ ] fix bugs | ||||
| // [ ] better light | ||||
| // [ ] inventory | ||||
|  | ||||
| export function setupParamPanel(params) { | ||||
|     document.querySelector('#lightx').oninput = e => { | ||||
| @@ -234,6 +239,33 @@ export function setupParamPanel(params) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| function viewDirection(params) { | ||||
|     const dir = [0, 0, -1, 1.0]; | ||||
|     const camori = params.camera.orientation; | ||||
|     const ori = se3.inverse(se3.rotxyz(-camori[0], -camori[1], -camori[2])); | ||||
|     return se3.apply(ori, dir).slice(0, 3); | ||||
| } | ||||
|  | ||||
| function destroySelectedBlock(params) { | ||||
|     const hit = castRay(params.world, params.camera.position, viewDirection(params), params.blockSelectDistance); | ||||
|     if (hit === undefined || hit.block.type === BlockType.UNDEFINED) { | ||||
|         return; | ||||
|     } | ||||
|     if (hit.block.type === BlockType.WATER) { | ||||
|         return; | ||||
|     } | ||||
|     destroyBlock(params.world, hit.block); | ||||
| } | ||||
|  | ||||
| function makeDirBlock(params) { | ||||
|     const hit = castRay(params.world, params.camera.position, viewDirection(params), params.blockSelectDistance); | ||||
|     if (hit === undefined || hit.block.type === BlockType.UNDEFINED) { | ||||
|         return; | ||||
|     } | ||||
|     const newBlockPosition = hit.block.centerPosition.map((v, i) => v + hit.normal[i]); | ||||
|     makeBlock(params.world, newBlockPosition, BlockType.DIRT); | ||||
| } | ||||
|  | ||||
| export function initUiListeners(params, canvas) { | ||||
|     const canvasClickHandler = () => { | ||||
|         canvas.requestPointerLock(); | ||||
| @@ -244,6 +276,7 @@ export function initUiListeners(params, canvas) { | ||||
|                     destroySelectedBlock(params); | ||||
|                     break; | ||||
|                 case 2:  // right click | ||||
|                     makeDirBlock(params); | ||||
|                     break; | ||||
|             } | ||||
|         }; | ||||
| @@ -298,7 +331,7 @@ export function tick(time, gl, params) { | ||||
|         // frame time is typically 16.7ms, so this may lag a bit | ||||
|         let timeLeft = 30; | ||||
|         const start = performance.now(); | ||||
|         generateMissingChunks(params.world, campos[2], campos[0], timeLeft); | ||||
|         generateMissingChunks(params.world, campos[2], campos[0], 5); | ||||
|  | ||||
|         timeLeft -= performance.now() - start; | ||||
|         updateWorldGeometry(gl, params.world, campos[2], campos[0], timeLeft); | ||||
|   | ||||
							
								
								
									
										150
									
								
								terrain.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								terrain.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| const smoothstep = x => x*x*(3 - 2*x); | ||||
| function interpolate(a, b, x, f = smoothstep) { | ||||
|     const val = a + f(x) * (b - a); | ||||
|     return val; | ||||
| } | ||||
| const sigmoid = (a, b, f = smoothstep) => x => { | ||||
| 	if (x < a) return 0; | ||||
|   if (x > b) return 1; | ||||
|   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 | ||||
|   const xorshift = (x) => { | ||||
|     x ^= x << 13; | ||||
|     x ^= x >> 7; | ||||
|     x ^= x << 17; | ||||
|     return x; | ||||
|   }; | ||||
|  | ||||
|   const randGrad = (x0, y0) => { | ||||
|     const rand = xorshift(1337 * x0 + seed + 80085 * y0); | ||||
|     return [Math.sin(rand), Math.cos(rand)]; | ||||
|   }; | ||||
|  | ||||
|   const x0 = Math.floor(x / gridSize); | ||||
|   const y0 = Math.floor(y / gridSize); | ||||
|   const sx = x / gridSize - x0; | ||||
|   const sy = y / gridSize - y0; | ||||
|  | ||||
|   const n0 = dot(randGrad(x0, y0), [sx, sy]); | ||||
|   const n1 = dot(randGrad(x0 + 1, y0), [sx - 1, sy]); | ||||
|   const n2 = dot(randGrad(x0, y0 + 1), [sx, sy - 1]); | ||||
|   const n3 = dot(randGrad(x0 + 1, y0 + 1), [sx - 1, sy - 1]); | ||||
|  | ||||
|   return 1.5 * interpolate(interpolate(n0, n1, sx), interpolate(n2, n3, sx), sy); | ||||
| } | ||||
|  | ||||
| function cliffPerlin(seed, x, y) { | ||||
| 	const noise1 = ghettoPerlinNoise(seed, x, y); | ||||
|   const noise2 = ghettoPerlinNoise(seed+1, x, y); | ||||
|  | ||||
|   const softerEdge = x => { | ||||
|   	if (x < 0.1) return 0.5 - 5 * x; | ||||
|     if (x > 0.8) return 1.0 - 2.5 * (x - 0.8); | ||||
|     return (x - 0.1) / 0.8; | ||||
|   }; | ||||
|  | ||||
|   return interpolate(-1, 1, 0.5 * (1 + Math.atan2(noise1, noise2) / Math.PI), softerEdge); | ||||
| } | ||||
|  | ||||
| export function makeTerrain(x, y) { | ||||
|   const seed = 1337; | ||||
|   const lacunarity = 2.1; | ||||
|   const persistence = 0.35; | ||||
|   const noiseMap = (x, y) => cliffPerlin(seed, x / 2, y / 2); | ||||
|  | ||||
|   const fractalNoise = (x, y) => { | ||||
|     let value = 0; | ||||
|     let power = 0.6; | ||||
|     let scale = 0.1; | ||||
|     const noises = [ghettoPerlinNoise, ghettoPerlinNoise, cliffPerlin, cliffPerlin]; | ||||
|  | ||||
|     for (const noiseFun of noises) { | ||||
|       const noise = noiseFun(seed, scale * x, scale * y); | ||||
|  | ||||
|       value += noise * power; | ||||
|  | ||||
|       power *= persistence; | ||||
|       scale *= lacunarity; | ||||
|     } | ||||
|  | ||||
|     return interpolate(-0.2, 0.6, value);//, x=>x*x); // between 0 and 1 | ||||
|   } | ||||
|  | ||||
|   const scaledNoise = (x, y) => noiseMap(x / 1, y / 1); | ||||
|   const outputNoise = fractalNoise; | ||||
| //	const outputNoise = scaledNoise; | ||||
|  | ||||
|   const terrain = new Uint8Array(16 * 16); | ||||
|   for (let i = 0; i < 16; i++) { | ||||
|     for (let j = 0; j < 16; j++) { | ||||
|       terrain[i * 16 + j] = Math.floor(64 + 64 * outputNoise(x + i, y + j)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return terrain; | ||||
| } | ||||
|  | ||||
| function colorInterp(x) { | ||||
| 	const ocean = [0.0, 0.0, 0.5]; | ||||
|   const grass = [0.2, 0.7, 0.2]; | ||||
|   const mountain = [0.4, 0.3, 0.1]; | ||||
|   const snow = [0.9, 0.9, 0.9]; | ||||
|  | ||||
|   const grassHeight = 75 / 255; | ||||
|   const mountainHeight = 100 / 255; | ||||
|  | ||||
|   const interp = dim => { | ||||
|   	return x => { | ||||
|     	if (x < grassHeight) { | ||||
|       	return interpolate(ocean[dim], grass[dim], x / grassHeight, sigmoid(0.6, 0.95)); | ||||
|       } else if (x < mountainHeight) { | ||||
|       	return interpolate(grass[dim], mountain[dim], (x - grassHeight) / (mountainHeight - grassHeight)); | ||||
|       } else { | ||||
|      		return interpolate(mountain[dim], snow[dim], (x - mountainHeight) / (1.0 - mountainHeight), sigmoid(0, 0.2)); | ||||
|       } | ||||
|     }; | ||||
|   }; | ||||
|  | ||||
|   return { | ||||
|   	r: 255 * interp(0)(x/255), | ||||
|     g: 255 * interp(1)(x/255), | ||||
|     b: 255 * interp(2)(x/255), | ||||
|   }; | ||||
| } | ||||
|  | ||||
| function main() { | ||||
|   const canvas = document.querySelector('#canvas'); | ||||
|   const ctx = canvas.getContext('2d'); | ||||
|   ctx.fillStyle = 'gray'; | ||||
|   ctx.fill(); | ||||
|  | ||||
|   for (let i = 0; i < canvas.width; i += 16) { | ||||
|     for (let j = 0; j < canvas.height; j += 16) { | ||||
|  | ||||
|       const imgdat = ctx.createImageData(16, 16); | ||||
|       const dat = imgdat.data; | ||||
|  | ||||
|       const chunk = makeTerrain(i - canvas.width / 2, j - canvas.height / 2); | ||||
|       for (let k = 0; k < 16; k++) { | ||||
|         for (let l = 0; l < 16; l++) { | ||||
|           const offset = 4 * (16 * (15 - l) + k); | ||||
|           const val = chunk[16 * k + l]; | ||||
|  | ||||
|           dat[offset + 0] = colorInterp(val).r; | ||||
|           dat[offset + 1] = colorInterp(val).g; | ||||
|           dat[offset + 2] = colorInterp(val).b; | ||||
|           dat[offset + 3] = 255; | ||||
|         } | ||||
|  | ||||
|       } | ||||
|       ctx.putImageData(imgdat, i, canvas.height - 16 - j); | ||||
|  | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| //window.onload = main; | ||||
							
								
								
									
										
											BIN
										
									
								
								texture.png
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								texture.png
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB | 
							
								
								
									
										192
									
								
								world.js
									
									
									
									
									
								
							
							
						
						
									
										192
									
								
								world.js
									
									
									
									
									
								
							| @@ -1,6 +1,7 @@ | ||||
| import { makeBufferFromFaces, makeFace} from "./geometry"; | ||||
| import { loadTexture, makeProgram } from "./gl"; | ||||
| import * as se3 from './se3'; | ||||
| import { makeTerrain } from "./terrain"; | ||||
|  | ||||
| const VSHADER = ` | ||||
| attribute vec3 aPosition; | ||||
| @@ -54,59 +55,9 @@ export const BlockType = { | ||||
|     DIRT: 2, | ||||
|     GRASS: 3, | ||||
|     STONE: 4, | ||||
|     WATER: 5, | ||||
| }; | ||||
|  | ||||
| function ghettoPerlinNoise(seed, x, y, gridSize = 16) { | ||||
|     const dot = (vx, vy) => vx[0] * vy[0] + vx[1] * vy[1]; | ||||
|     // super ghetto random | ||||
|     const xorshift = (x) => { | ||||
|         x ^= x << 13; | ||||
|         x ^= x >> 7; | ||||
|         x ^= x << 17; | ||||
|         return x; | ||||
|     }; | ||||
|  | ||||
|     const randGrad = (x0, y0) => { | ||||
|         const rand = xorshift(1337 * x0 + seed + 80085 * y0); | ||||
|         return [Math.sin(rand), Math.cos(rand)]; | ||||
|     }; | ||||
|  | ||||
|     const interpol = (a, b, x) => a + (x*x*(3 - 2*x)) * (b - a); | ||||
|  | ||||
|     const x0 = Math.floor(x / gridSize); | ||||
|     const y0 = Math.floor(y / gridSize); | ||||
|     const sx = x / gridSize - x0; | ||||
|     const sy = y / gridSize - y0; | ||||
|  | ||||
|     const n0 = dot(randGrad(x0, y0), [sx, sy]); | ||||
|     const n1 = dot(randGrad(x0 + 1, y0), [sx - 1, sy]); | ||||
|     const n2 = dot(randGrad(x0, y0 + 1), [sx, sy - 1]); | ||||
|     const n3 = dot(randGrad(x0 + 1, y0 + 1), [sx - 1, sy - 1]); | ||||
|  | ||||
|     return interpol(interpol(n0, n1, sx), interpol(n2, n3, sx), sy); | ||||
| } | ||||
|  | ||||
| function makeTerrain(x, y) { | ||||
|     const seed = 1337; | ||||
|  | ||||
|     const fractalNoise = (x, y) => ( | ||||
|         ghettoPerlinNoise(seed, x, y, 8) * 0.06 | ||||
|         + ghettoPerlinNoise(seed, x, y, 16) * 0.125 | ||||
|         + ghettoPerlinNoise(seed, x, y, 32) * 0.25 | ||||
|         + ghettoPerlinNoise(seed, x, y, 64) * 0.5 | ||||
|     ); | ||||
|  | ||||
|     const terrain = Array(16 * 16); | ||||
|  | ||||
|     for (let i = 0; i < 16; i++) { | ||||
|         for (let j = 0; j < 16; j++) { | ||||
|             terrain[i * 16 + j] = fractalNoise(x + i, y + j); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return terrain; | ||||
| } | ||||
|  | ||||
| function makeChunk(z, x) { | ||||
|     const terrain = makeTerrain(z, x); | ||||
|  | ||||
| @@ -114,18 +65,23 @@ function makeChunk(z, x) { | ||||
|  | ||||
|     for (let i = 0; i < 16; i++) { | ||||
|         for (let j = 0; j < 16; j++) { | ||||
|             const height = Math.floor(64 + 64 * 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 stoneHeight = Math.min(52, height); | ||||
|             const stoneHeight = Math.max(48, height); | ||||
|             const grassHeight = stoneHeight + 8; | ||||
|             const waterHeight = 67; | ||||
|             data.set(Array(stoneHeight).fill(BlockType.STONE), offset); | ||||
|             if (stoneHeight < height) { | ||||
|                 data.set(Array(height - 1 - stoneHeight).fill(BlockType.DIRT), offset + stoneHeight); | ||||
|                 data[offset + height - 1] = BlockType.GRASS; | ||||
|             data.set(Array(grassHeight - 1 - stoneHeight).fill(BlockType.DIRT), offset + stoneHeight); | ||||
|             if (grassHeight < waterHeight) { | ||||
|                 data.set(Array(waterHeight - grassHeight + 1).fill(BlockType.WATER), offset + grassHeight - 1); | ||||
|             } else { | ||||
|                 data[offset + grassHeight - 1] = BlockType.GRASS; | ||||
|             } | ||||
|             data.set(Array(256 - height).fill(BlockType.AIR), offset + height); | ||||
|             const surfaceHeight = Math.max(waterHeight, grassHeight); | ||||
|             data.set(Array(256 - surfaceHeight).fill(BlockType.AIR), offset + surfaceHeight); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -183,6 +139,7 @@ function faceTexture(type, dir) { | ||||
|             } | ||||
|         case BlockType.DIRT: return [2, 15]; | ||||
|         case BlockType.STONE: return [3, 15]; | ||||
|         case BlockType.WATER: return [4, 15]; | ||||
|         default: return [0, 0]; | ||||
|     } | ||||
| } | ||||
| @@ -218,7 +175,8 @@ function makeFaceList(data, chunkz, chunkx, blockLookup) { | ||||
|         { block: lookup(i, j, k + 1), dir: '+y', faceCenter: [chunkx + j, k + 0.5, chunkz + i] }, | ||||
|     ]; | ||||
|  | ||||
|     const faces = []; | ||||
|     const solidFaces = []; | ||||
|     const waterFaces = []; | ||||
|  | ||||
|     for (let i = 0; i < 16; i++) { | ||||
|         for (let j = 0; j < 16; j++) { | ||||
| @@ -227,17 +185,31 @@ function makeFaceList(data, chunkz, chunkx, blockLookup) { | ||||
|                 if (data[bi] === BlockType.AIR) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 for (const {block, dir, faceCenter} of neighbors(i, j, k).filter(({block}) => block === BlockType.AIR)) { | ||||
|                     faces.push({ | ||||
|                         blockIndex: bi, | ||||
|                         face: makeFace(dir, faceTexture(data[bi], dir), faceCenter), | ||||
|                     }); | ||||
|                 for (const {dir, faceCenter} of neighbors(i, j, k) | ||||
|                     .filter(({ block }) => block === BlockType.AIR || (block === BlockType.WATER && data[bi] !== BlockType.WATER))) { | ||||
|                     if (data[bi] !== BlockType.WATER) { | ||||
|                         solidFaces.push({ | ||||
|                             blockIndex: bi, | ||||
|                             dir, | ||||
|                             face: makeFace(dir, faceTexture(data[bi], dir), faceCenter), | ||||
|                         }); | ||||
|                     } else { | ||||
|                         faceCenter[1] -= 0.15; | ||||
|                         waterFaces.push({ | ||||
|                             blockIndex: bi, | ||||
|                             dir, | ||||
|                             face: makeFace(dir, faceTexture(data[bi], dir), faceCenter), | ||||
|                         }) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return faces; | ||||
|     return { | ||||
|         solidFaces, | ||||
|         waterFaces, | ||||
|     }; | ||||
| } | ||||
|  | ||||
| // - data <-- need to generate the first time, update when m&p | ||||
| @@ -315,6 +287,7 @@ function invalidateChunkGeometry(world, i, j) { | ||||
|  | ||||
| export function createChunkFace(block, dir) { | ||||
|     return { | ||||
|         dir, | ||||
|         blockIndex: block.blockIndex, | ||||
|         face: makeFace(dir, faceTexture(block.type, dir), faceCenter(block.centerPosition, dir)), | ||||
|     }; | ||||
| @@ -342,10 +315,13 @@ export function updateWorldGeometry(gl, world, z, x, timeLimit = 10000) { | ||||
|                     const chunkx = 16 * j; | ||||
|                     const lookup = (i, j, k) => blockLookup(world, j + chunkx, k, i + chunkz).type; | ||||
|  | ||||
|                     chunk.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.transparentFaces = faces.waterFaces; | ||||
|                 } | ||||
|  | ||||
|                 chunk.buffer = makeBufferFromFaces(gl, chunk.faces.map(f => f.face)); | ||||
|                 chunk.transparentBuffer = makeBufferFromFaces(gl, chunk.transparentFaces.map(f => f.face)); | ||||
|  | ||||
|                 // throttle this for fluidity | ||||
|                 if (performance.now() - start > timeLimit) { | ||||
| @@ -435,7 +411,7 @@ function rayThroughGrid(origin, direction, maxDistance) { | ||||
|     return {position, normal, distance: rayLength}; | ||||
| } | ||||
|  | ||||
| function castRay(world, origin, direction, maxDistance) { | ||||
| export function castRay(world, origin, direction, maxDistance) { | ||||
|     let currentPoint = origin; | ||||
|  | ||||
|     while (maxDistance > 0) { | ||||
| @@ -488,49 +464,33 @@ export function markBlock(world, cameraPosition, direction, maxDistance) { | ||||
| } | ||||
|  | ||||
|  | ||||
| function viewDirection(params) { | ||||
|     const dir = [0, 0, -1, 1.0]; | ||||
|     const camori = params.camera.orientation; | ||||
|     const ori = se3.inverse(se3.rotxyz(-camori[0], -camori[1], -camori[2])); | ||||
|     return se3.apply(ori, dir).slice(0, 3); | ||||
| } | ||||
|  | ||||
| export function destroySelectedBlock(params) { | ||||
|     const hit = castRay(params.world, params.camera.position, viewDirection(params), params.blockSelectDistance); | ||||
|     if (hit === undefined || hit.block.type === BlockType.UNDEFINED) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
| export function destroyBlock(world, block) { | ||||
|     const trimFaces = chunk => { | ||||
|         chunk.faces = chunk.faces.filter(({blockIndex}) => chunk.data[blockIndex] !== BlockType.AIR); | ||||
|     } | ||||
|  | ||||
|     hit.block.chunk.data[hit.block.blockIndex] = BlockType.AIR; | ||||
|     if (hit.block.chunk.buffer !== undefined) { | ||||
|         hit.block.chunk.buffer.delete(); | ||||
|         delete hit.block.chunk.buffer; | ||||
|     block.chunk.data[block.blockIndex] = BlockType.AIR; | ||||
|     if (block.chunk.buffer !== undefined) { | ||||
|         block.chunk.buffer.delete(); | ||||
|         delete block.chunk.buffer; | ||||
|     } | ||||
|     trimFaces(hit.block.chunk); | ||||
|     trimFaces(block.chunk); | ||||
|  | ||||
|     const [bx, by, bz] = hit.block.centerPosition; | ||||
|     const [bx, by, bz] = block.centerPosition; | ||||
|  | ||||
|     const neighbors = [ | ||||
|         { block: blockLookup(params.world, bx - 1, by, bz), dir: '+x' }, | ||||
|         { block: blockLookup(params.world, bx + 1, by, bz), dir: '-x' }, | ||||
|         { block: blockLookup(params.world, bx, by - 1, bz), dir: '+y' }, | ||||
|         { block: blockLookup(params.world, bx, by + 1, bz), dir: '-y' }, | ||||
|         { block: blockLookup(params.world, bx, by, bz - 1), dir: '+z' }, | ||||
|         { block: blockLookup(params.world, bx, by, bz + 1), dir: '-z' }, | ||||
|         { block: blockLookup(world, bx - 1, by, bz), dir: '+x' }, | ||||
|         { block: blockLookup(world, bx + 1, by, bz), dir: '-x' }, | ||||
|         { block: blockLookup(world, bx, by - 1, bz), dir: '+y' }, | ||||
|         { block: blockLookup(world, bx, by + 1, bz), dir: '-y' }, | ||||
|         { block: blockLookup(world, bx, by, bz - 1), dir: '+z' }, | ||||
|         { block: blockLookup(world, bx, by, bz + 1), dir: '-z' }, | ||||
|     ]; | ||||
|  | ||||
|     neighbors | ||||
|         .filter(({ block }) => block.type !== BlockType.AIR && | ||||
|             block.type !== BlockType.UNDEFINED) | ||||
|         .forEach(({ block, dir }) => { | ||||
|             const blocki = Math.floor(block.blockIndex / (16 * 256)); | ||||
|             const blockj = Math.floor(block.blockIndex / 256) - 16 * blocki; | ||||
|             const blockk = block.blockIndex % 256; | ||||
|  | ||||
|             block.chunk.faces.push(createChunkFace(block, dir)); | ||||
|             trimFaces(block.chunk); | ||||
|  | ||||
| @@ -541,6 +501,48 @@ export function destroySelectedBlock(params) { | ||||
|         }); | ||||
| } | ||||
|  | ||||
| export function makeBlock(world, position, type) { | ||||
|     const block = blockLookup(world, ...position); | ||||
|     console.assert(block.type === BlockType.AIR); | ||||
|  | ||||
|     block.chunk.data[block.blockIndex] = type; | ||||
|     block.type = type; | ||||
|  | ||||
|     const [bx, by, bz] = block.centerPosition; | ||||
|  | ||||
|     const neighbors = [ | ||||
|         { block: blockLookup(world, bx - 1, by, bz), dir: '-x', ndir: '+x' }, | ||||
|         { block: blockLookup(world, bx + 1, by, bz), dir: '+x', ndir: '-x' }, | ||||
|         { block: blockLookup(world, bx, by - 1, bz), dir: '-y', ndir: '+y' }, | ||||
|         { block: blockLookup(world, bx, by + 1, bz), dir: '+y', ndir: '-y' }, | ||||
|         { block: blockLookup(world, bx, by, bz - 1), dir: '-z', ndir: '+z' }, | ||||
|         { block: blockLookup(world, bx, by, bz + 1), dir: '+z', ndir: '-z' }, | ||||
|     ]; | ||||
|  | ||||
|     const refresh = chunk => { | ||||
|         if (chunk.buffer !== undefined) { | ||||
|             chunk.buffer.delete(); | ||||
|             delete chunk.buffer; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     neighbors | ||||
|         .filter(({ block }) => block.type !== BlockType.UNDEFINED) | ||||
|         .forEach(({ block: nblock, dir, ndir }) => { | ||||
|             if (nblock.type === BlockType.AIR) { | ||||
|                 block.chunk.faces.push(createChunkFace(block, dir)); | ||||
|             } else { | ||||
|                 nblock.chunk.faces = nblock.chunk.faces.filter(f => ( | ||||
|                     f.blockIndex !== nblock.blockIndex || | ||||
|                     f.dir !== ndir | ||||
|                 )); | ||||
|                 refresh(nblock.chunk); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     refresh(block.chunk); | ||||
| } | ||||
|  | ||||
| export async function initWorldGl(gl) { | ||||
|     const program = makeProgram(gl, VSHADER, FSHADER); | ||||
|     const texture = await loadTexture(gl, 'texture.png'); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user