Add a few trees
This commit is contained in:
		
							
								
								
									
										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 | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								terrain.js
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								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)); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | // super ghetto random | ||||||
|  | function xorshift(x) { | ||||||
|  |   x ^= x << 13; | ||||||
|  |   x ^= x >> 7; | ||||||
|  |   x ^= x << 17; | ||||||
|  |   return x; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function random(seed, z, x) { | ||||||
|  |   return xorshift(1337 * z + seed + 80085 * x); | ||||||
|  | } | ||||||
|  |  | ||||||
| function ghettoPerlinNoise(seed, x, y, gridSize = 16) { | function ghettoPerlinNoise(seed, x, y, gridSize = 16) { | ||||||
|   const dot = (vx, vy) => vx[0] * vy[0] + vx[1] * vy[1]; |   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 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 | 
							
								
								
									
										128
									
								
								world.js
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								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,35 +56,78 @@ 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 - currentHeight).fill(BlockType.AIR), offset + currentHeight); | ||||||
|             data.set(Array(256 - surfaceHeight).fill(BlockType.AIR), offset + surfaceHeight); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     for (const tree of trees) { | ||||||
|  |         makeATree(data, tree, seed, z, x); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         position: {z, x}, |         position: {z, x}, | ||||||
|         data, |         data, | ||||||
| @@ -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) { | ||||||
|  |                             faceCenter[1] -= 0.15; | ||||||
|  |                         } | ||||||
|  |                         transparentFaces.push({ | ||||||
|                             dir, |                             dir, | ||||||
|                             face: makeFace(dir, faceTexture(data[bi], dir), faceCenter), |                             face: makeFace(dir, faceTexture(data[bi], dir), faceCenter), | ||||||
|  |                             blockIndex: bi, | ||||||
|                         }); |                         }); | ||||||
|                     } else { |                     } | ||||||
|                         faceCenter[1] -= 0.15; |                 } else { | ||||||
|                         waterFaces.push({ |                     for (const { dir, faceCenter } of neighbors(i, j, k) | ||||||
|                             blockIndex: bi, |                         .filter(({ block }) => isTransparent(block)) | ||||||
|  |                     ) { | ||||||
|  |                         solidFaces.push({ | ||||||
|                             dir, |                             dir, | ||||||
|                             face: makeFace(dir, faceTexture(data[bi], dir), faceCenter), |                             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 (isTransparent(block.type)) { | ||||||
|         if (dir === '+y') { |  | ||||||
|             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); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user