x:0,y:-30,z:0,vx:0,vy:0,vz:0,rx:-1,ry:0,rz:0
const CAM_SENSITIVITY = 10;
const MAX_CAVE_SIZE = 0.6;
[[70, 163, 79],[70, 163, 79],[70, 163, 79],[100,60,20],[70, 163, 79],[70, 163, 79]],
[[100,60,20],[100,60,20],[100,60,20],[100,60,20],[100,60,20],[100,60,20]],
[[140,140,140],[140,140,140],[140,140,140],[140,140,140],[140,140,140],[140,140,140]],
[[56,55,50],[56,55,50],[56,55,50],[56,55,50],[56,55,50],[56,55,50]],
[[207,88,52],[207,88,52],[207,88,52],[207,88,52],[207,88,52],[207,88,52]],
[[213, 219, 218],[213, 219, 218],[213, 219, 218],[213, 219, 218],[213, 219, 218],[213, 219, 218]],
[[120, 58, 4],[120, 58, 4],[232, 204, 104],[232, 204, 104],[120, 58, 4],[120, 58, 4]],
[[80,180,100],[80,180,100],[80,180,100],[80,180,100],[80,180,100],[80,180,100],],
[[180, 245, 240],[180, 245, 240],[180, 245, 240],[180, 245, 240],[180, 245, 240],[180, 245, 240],]
function preload(){grass = loadImage('grass.jpg');
imag = loadImage('cursor.png');
alert('WASD to move, move mouse to turn, click to destroy/build based on your input');
cam.chunkPos = [floor(cam.x/CHUNK_SIZE),floor(cam.y/CHUNK_SIZE),floor(cam.z/CHUNK_SIZE)];
generateNewChunks(cam.chunkPos,3);
createCanvas(windowWidth, windowHeight);
let choicetheanswer = prompt('build mode or destroy mode (1 for build mode 0 for destroy mode)');
if(mouseIsPressed){Chunk.setBlock(choicetheanswer,cam.x,cam.y,cam.z);}
min(map(cam.y,40,30,0,100),100),
min(map(cam.y,40,30,0,230),230),
min(map(cam.y,40,30,0,240)),240);
cam.chunkPos = [floor(cam.x/CHUNK_SIZE),floor(cam.y/CHUNK_SIZE),floor(cam.z/CHUNK_SIZE)];
worldTime = (frameCount*5)%1440;
generateNewChunks(cam.chunkPos);
chunks.sort((a,b)=>camDist([
(b.pos[0]+0.5)*CHUNK_SIZE,(b.pos[1]+0.5)*CHUNK_SIZE,(b.pos[2]+0.5)*CHUNK_SIZE]) -
(a.pos[0]+0.5)*CHUNK_SIZE,(a.pos[1]+0.5)*CHUNK_SIZE,(a.pos[2]+0.5)*CHUNK_SIZE])
let effectiveRenderDist = cam.y>0?RENDER_DIST - 1:RENDER_DIST;
for (const i in chunks) {
let distFromCamX = Math.abs(cam.chunkPos[0]-chunk.pos[0]);
let distFromCamY = Math.abs(cam.chunkPos[1]-chunk.pos[1]);
let distFromCamZ = Math.abs(cam.chunkPos[2]-chunk.pos[2]);
if (distFromCamX <= effectiveRenderDist && distFromCamY <= effectiveRenderDist && distFromCamZ <= effectiveRenderDist) {
for (const i of spliceInds) {
text(round(frameRate()/5)*5 + " FPS",width-100,height-30);
text("x: "+floor(cam.x)+", y: "+-floor(cam.y)+", z: "+floor(cam.z),width-180,height-60);
image(imag, mouseX, mouseY)
if (keyIsDown(87)||keyIsDown(38)) {
cam.vx+=ACCEL*Math.sin(cam.ry)*Math.cos(cam.rx);
cam.vy-=ACCEL*Math.sin(cam.rx);
cam.vz+=ACCEL*Math.cos(cam.ry)*Math.cos(cam.rx);
if (keyIsDown(83)||keyIsDown(40)) {
cam.vx-=ACCEL*Math.sin(cam.ry)*Math.cos(cam.rx);
cam.vy+=ACCEL*Math.sin(cam.rx);
cam.vz-=ACCEL*Math.cos(cam.ry)*Math.cos(cam.rx);
if (keyIsDown(65)||keyIsDown(37)) {
cam.vx-=ACCEL*Math.cos(cam.ry);
cam.vz+=ACCEL*Math.sin(cam.ry);
if (keyIsDown(68)||keyIsDown(39)) {
cam.vx+=ACCEL*Math.cos(cam.ry);
cam.vz-=ACCEL*Math.sin(cam.ry);
cam.ry += CAM_SENSITIVITY*2*movedX/width;
cam.rx -= CAM_SENSITIVITY*2*movedY/height;
cam.rx = constrain(cam.rx, -Math.PI/2, Math.PI/2);
if (x>=0) return Math.floor(x+0.0000001)%CHUNK_SIZE;
else return (CHUNK_SIZE + Math.floor(x+0.00000001)%CHUNK_SIZE)%CHUNK_SIZE;
function rot(thetax,thetay,thetaz) {
Math.cos(thetay)*Math.cos(thetaz),
-Math.cos(thetay)*Math.sin(thetaz),Math.sin(thetay),
Math.sin(thetax)*Math.sin(thetay)*Math.cos(thetaz)+Math.cos(thetax)*Math.sin(thetaz),
Math.cos(thetax)*Math.cos(thetaz)-Math.sin(thetax)*Math.sin(thetay)*Math.sin(thetaz),
-Math.sin(thetax)*Math.cos(thetay),
Math.sin(thetax)*Math.sin(thetaz)-Math.cos(thetax)*Math.sin(thetay)*Math.cos(thetaz),
Math.cos(thetax)*Math.sin(thetay)*Math.sin(thetaz)+Math.sin(thetax)*Math.cos(thetaz),
Math.cos(thetax)*Math.cos(thetay)
A[0]*v[0]+A[1]*v[1]+A[2]*v[2],
A[3]*v[0]+A[4]*v[1]+A[5]*v[2],
A[6]*v[0]+A[7]*v[1]+A[8]*v[2],
constructor(verts,squares,rotation) {
this.rot = {x:rotation[0],y:rotation[1],z:rotation[2]};
for (const v of this.verts) {
const tv = multPt(rot(-cam.rx,-cam.ry,-cam.rz),[
rv[0]-cam.x,rv[1]-cam.y,rv[2]-cam.z,
SCALE*tv[0]/Math.max(tv[2],0.0001)+width/2,
SCALE*tv[1]/Math.max(tv[2],0.0001)+height/2,
Math.sqrt(tv[0]*tv[0]+tv[1]*tv[1]+tv[2]*tv[2])
for (const t of this.squares) {
if (proj[t[0]][2]<0 && proj[t[1]][2]<0 && proj[t[2]][2]<0 && proj[t[3]][2]<0) continue;
const tnormal = triangleNormal(
rvs[t[0]], rvs[t[1]], rvs[t[2]]
if (dotV(tnormal,toCam)<0) {
(this.verts[t[0]][0]+this.verts[t[1]][0]+this.verts[t[2]][0]+this.verts[t[3]][0])*0.25,
(this.verts[t[0]][1]+this.verts[t[1]][1]+this.verts[t[2]][1]+this.verts[t[3]][1])*0.25,
(this.verts[t[0]][2]+this.verts[t[1]][2]+this.verts[t[2]][2]+this.verts[t[3]][2])*0.25,
projSquares.sort((a,b)=>(b[11]-a[11]));
for (const t of projSquares) {
let c = textures[t[12]][t[14]];
quad(t[0],t[1],t[2],t[3],t[4],t[5],t[6],t[7]);
return Math.sqrt((p[0]-cam.x)*(p[0]-cam.x)+(p[1]-cam.y)*(p[1]-cam.y)+(p[2]-cam.z)*(p[2]-cam.z));
return v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2];
function triangleNormal(p1,p2,p3) {
(p2[1]-p1[1])*(p3[2]-p1[2])-(p2[2]-p1[2])*(p3[1]-p1[1]),
(p2[2]-p1[2])*(p3[0]-p1[0])-(p2[0]-p1[0])*(p3[2]-p1[2]),
(p2[0]-p1[0])*(p3[1]-p1[1])-(p2[1]-p1[1])*(p3[0]-p1[0]),
const invMag = 1/Math.sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
return [v[0]*invMag, v[1]*invMag, v[2]*invMag];
this.calcNeighbourInds();
this.mesh = new WorldObject([],[],[0,0,0]);
for (let z=0;z<CHUNK_SIZE;z++) {
for (let y=0;y<CHUNK_SIZE;y++) {
for (let x=0;x<CHUNK_SIZE;x++) {
if (!isTransparent(this.data[z][y][x])) {
if ((inChunk(x-1) && isTransparent(this.data[z][y][x-1])) || (!inChunk(x-1) && isTransparentChunk(this.neighbourInds,0,CHUNK_SIZE-1,y,z))) {
this.mesh.verts = this.mesh.verts.concat([[x,y+1,z+1],[x,y+1,z],[x,y,z+1],[x,y,z],]);
if (inChunk(x+1) && isTransparent(this.data[z][y][x+1]) || (!inChunk(x+1) && isTransparentChunk(this.neighbourInds,1,0,y,z))) {
this.mesh.verts = this.mesh.verts.concat([[x+1,y,z],[x+1,y+1,z],[x+1,y,z+1],[x+1,y+1,z+1]]);
if (inChunk(y-1) && isTransparent(this.data[z][y-1][x]) || (!inChunk(y-1) && isTransparentChunk(this.neighbourInds,2,x,CHUNK_SIZE-1,z))) {
this.mesh.verts = this.mesh.verts.concat([[x+1,y,z+1],[x,y,z+1],[x+1,y,z],[x,y,z],]);
if (inChunk(y+1) && isTransparent(this.data[z][y+1][x]) || (!inChunk(y+1) && isTransparentChunk(this.neighbourInds,3,x,0,z))) {
this.mesh.verts = this.mesh.verts.concat([[x,y+1,z],[x,y+1,z+1],[x+1,y+1,z],[x+1,y+1,z+1]]);
if (inChunk(z-1) && isTransparent(this.data[z-1][y][x]) || (!inChunk(z-1) && isTransparentChunk(this.neighbourInds,4,x,y,CHUNK_SIZE-1))) {
this.mesh.verts = this.mesh.verts.concat([[x+1,y+1,z],[x+1,y,z],[x,y+1,z],[x,y,z],]);
if (inChunk(z+1) && isTransparent(this.data[z+1][y][x]) || (!inChunk(z+1) && isTransparentChunk(this.neighbourInds,5,x,y,0))) {
this.mesh.verts = this.mesh.verts.concat([[x,y,z+1],[x+1,y,z+1],[x,y+1,z+1],[x+1,y+1,z+1]]);
for (let j=0;j<c.length;j++) {
this.mesh.verts[i][0] += this.pos[0]*CHUNK_SIZE;
this.mesh.verts[i][1] += this.pos[1]*CHUNK_SIZE;
this.mesh.verts[i][2] += this.pos[2]*CHUNK_SIZE;
this.mesh.verts[i+1][0] += this.pos[0]*CHUNK_SIZE;
this.mesh.verts[i+1][1] += this.pos[1]*CHUNK_SIZE;
this.mesh.verts[i+1][2] += this.pos[2]*CHUNK_SIZE;
this.mesh.verts[i+2][0] += this.pos[0]*CHUNK_SIZE;
this.mesh.verts[i+2][1] += this.pos[1]*CHUNK_SIZE;
this.mesh.verts[i+2][2] += this.pos[2]*CHUNK_SIZE;
this.mesh.verts[i+3][0] += this.pos[0]*CHUNK_SIZE;
this.mesh.verts[i+3][1] += this.pos[1]*CHUNK_SIZE;
this.mesh.verts[i+3][2] += this.pos[2]*CHUNK_SIZE;
this.mesh.squares.push([i,i+1,i+3,i+2,this.data[z][y][x],c[j]-1]);
this.neighbourInds.push(chunks.findIndex((a)=>(
a.pos[0] == this.pos[0]-1 &&
a.pos[1] == this.pos[1] &&
this.neighbourInds.push(chunks.findIndex((a)=>(
a.pos[0] == this.pos[0]+1 &&
a.pos[1] == this.pos[1] &&
this.neighbourInds.push(chunks.findIndex((a)=>(
a.pos[0] == this.pos[0] &&
a.pos[1] == this.pos[1]-1 &&
this.neighbourInds.push(chunks.findIndex((a)=>(
a.pos[0] == this.pos[0] &&
a.pos[1] == this.pos[1]+1 &&
this.neighbourInds.push(chunks.findIndex((a)=>(
a.pos[0] == this.pos[0] &&
a.pos[1] == this.pos[1] &&
a.pos[2] == this.pos[2]-1
this.neighbourInds.push(chunks.findIndex((a)=>(
a.pos[0] == this.pos[0] &&
a.pos[1] == this.pos[1] &&
a.pos[2] == this.pos[2]+1
static setBlock(val,x,y,z) {
let chunkX = floor(x/CHUNK_SIZE);
let chunkY = floor(y/CHUNK_SIZE);
let chunkZ = floor(z/CHUNK_SIZE);
const ind = chunks.findIndex((a)=>(
a.pos[0] === chunkX && a.pos[1] === chunkY && a.pos[2] === chunkZ
if (ind<0) throw new Error("Chunk not loaded");
chunks[ind].setBlock(val,chunkBlock(x),chunkBlock(y),chunkBlock(z));
this.data[z][y][x] = val;
for (const ne of this.neighbourInds) {
let chunkX = floor(x/CHUNK_SIZE);
let chunkY = floor(y/CHUNK_SIZE);
let chunkZ = floor(z/CHUNK_SIZE);
const ind = chunks.findIndex((a)=>(
a.pos[0] === chunkX && a.pos[1] === chunkY && a.pos[2] === chunkZ
if (ind<0) return generateTerrain(chunkX,chunkY,chunkZ);
return chunks[ind].getBlock(x%CHUNK_SIZE,y%CHUNK_SIZE,z%CHUNK_SIZE);
return this.data[z][y][x];
static generateNewChunk(x,y,z) {
for (let i=0;i<CHUNK_SIZE;i++) {
for (let j=0;j<CHUNK_SIZE;j++) {
for (let k=0;k<CHUNK_SIZE;k++) {
const newX = k+x*CHUNK_SIZE;
const newY = j+y*CHUNK_SIZE;
const newZ = i+z*CHUNK_SIZE;
generateTerrain(newX,newY,newZ));
const newChunk = new Chunk([x,y,z],data);
for (let i of newChunk.neighbourInds) {
chunks[i].calcNeighbourInds();
function isTransparent(val) {
function generateTerrain(x,y,z) {
let noiseVal = calcNoiseVal(x,z);
let caveVal = Math.abs(2*noise(x*0.02+59834,y*0.02+43652,z*0.02+42398)-1)+Math.abs(2*noise(x*0.02+534263,y*0.02+7474742,z*0.02+3463456)-1);
let caveSize = MAX_CAVE_SIZE*noise(x*0.002+235643,y*0.002+643566,z*0.002+682573)**4;
if (caveSize > 0.02 && caveVal < caveSize && y > noiseVal) return 0;
if (y > noiseVal+1) return 2;
if (y > noiseVal) return 1;
if (y > noiseVal - 5 && tv) return 7;
if (y > noiseVal - 7 && tv) return 8;
if (leafVal(x,y,z,noiseVal)) return 8;
if (noise(x*0.1+584234,y*0.1+63943,z*0.1+239045)<0.33) stoneVal = 6;
if (noise(x*0.2+145235,y*0.2+252652,z*0.2+236542)<0.25) stoneVal = 4;
if (noise(x*0.15+436654,y*0.15+983475,z*0.15+825634)<0.18) stoneVal = 5;
if (y > 80 && noise(x*100+342234,y*100+45325,z*100+812357)<0.05) stoneVal = 9;
if (y > 160 && noise(x*100+342234,y*100+45325,z*100+812357)<0.07) stoneVal = 9;
function calcNoiseVal(x,y) {
let terrainMask = 10+70*noise(x*0.01+598234,y*0.01+528333)**3;
return -terrainMask*noise(x*0.05+431023,y*0.05+521354);
function leafVal(x,y,z,noiseVal) {
for (let a = x-1;a<x+2;a++) {
for (let b = z-1;b<z+2;b++) {
let noiseVal = calcNoiseVal(a,b);
if (treeVal(a,b) && y<noiseVal-3 && y>noiseVal-6) {
let nv = noise(x*243+234834,y*324932+243832);
let treeMask = 0.2*noise(x*0.01+592495,y*0.01+239492);
function isTransparentChunk(neighbourInds, index, blockX,blockY,blockZ) {
if (neighbourInds[index]<0) return false;
let chunk = chunks[neighbourInds[index]];
return isTransparent(chunk.data[blockZ][blockY][blockX]);
function generateNewChunks(camChunkPos,dist = LOAD_DIST) {
for (let i=-dist;i<=dist;i++) {
for (let j=-dist;j<=dist;j++) {
for (let k=-dist;k<=dist;k++) {
if (chunks.findIndex((a)=>(a.pos[0] === camChunkPos[0]+k && a.pos[1] === camChunkPos[1]+j && a.pos[2] === camChunkPos[2]+i))<0) {
Chunk.generateNewChunk(camChunkPos[0]+k,camChunkPos[1]+j,camChunkPos[2]+i);
return 0<=val && val<CHUNK_SIZE;