Meta Description" name="description" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CraftJS - Minecraft Clone</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #000; overflow: hidden; font-family: monospace; }
canvas#c { display: block; }
#overlay { position: fixed; inset: 0; pointer-events: none; z-index: 5; }
#crosshair {
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
color: white; font-size: 30px; font-weight: 100;
text-shadow: 1px 1px 2px #000, -1px -1px 2px #000;
width: 30px; height: 30px;
display: flex; align-items: center; justify-content: center;
line-height: 1;
}
#hud {
position: absolute; top: 8px; left: 8px;
background: rgba(0,0,0,0.6); color: #fff;
padding: 8px 12px; border-radius: 4px;
font-size: 12px; line-height: 2;
}
#hud b { color: #7dd; }
#xyz {
position: absolute; top: 8px; right: 8px;
background: rgba(0,0,0,0.6); color: #fff;
padding: 8px 12px; border-radius: 4px;
font-size: 12px; line-height: 2;
}
#hotbar {
position: absolute; bottom: 16px; left: 50%;
transform: translateX(-50%);
display: flex; gap: 3px;
}
.hslot {
width: 50px; height: 50px;
border: 2px solid #666;
background: rgba(0,0,0,0.65);
display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 1px;
}
.hslot.sel { border: 2px solid #fff; box-shadow: 0 0 6px rgba(255,255,255,0.5); }
.hslot canvas { image-rendering: pixelated; }
.hslot span { font-size: 7px; color: #bbb; text-transform: uppercase; }
#breakbar {
position: absolute; bottom: 80px; left: 50%;
transform: translateX(-50%);
width: 140px; height: 7px;
background: rgba(0,0,0,0.5);
border-radius: 4px; overflow: hidden; display: none;
}
#breakfill { height: 100%; background: #e06020; width: 0; }
#startscreen {
position: fixed; inset: 0;
background: linear-gradient(160deg, #1a3a5c 0%, #0d1f33 100%);
display: flex; flex-direction: column;
align-items: center; justify-content: center;
z-index: 100; color: #fff;
}
#startscreen h1 {
font-size: 52px; font-weight: 900;
color: #5cf; letter-spacing: 6px;
text-shadow: 0 0 40px rgba(85,204,255,0.4);
margin-bottom: 4px;
}
#startscreen p { color: #89a; margin-bottom: 36px; font-size: 14px; letter-spacing: 2px; }
#playBtn {
background: #2a7a2a; color: #fff; border: none;
padding: 14px 44px; font: 16px monospace;
letter-spacing: 3px; cursor: pointer; border-radius: 3px;
}
#playBtn:hover { background: #3a9a3a; }
#loading { display: none; color: #7dd; font-size: 14px; margin-top: 16px; letter-spacing: 2px; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<div id="overlay">
<div id="crosshair">+</div>
<div id="hud">
<b>Move:</b> WASD <b>Jump:</b> Space<br>
<b>Sprint:</b> Shift <b>Look:</b> Mouse<br>
<b>Break:</b> Hold LMB <b>Place:</b> RMB<br>
<b>Block:</b> Scroll / 1\u20138
</div>
<div id="xyz"></div>
<div id="hotbar"></div>
<div id="breakbar"><div id="breakfill"></div></div>
</div>
<div id="startscreen">
<h1>CRAFTJS</h1>
<p>Three.js Minecraft Clone</p>
<button id="playBtn">PLAY</button>
<div id="loading">Generating world...</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// \u2500\u2500 BLOCK IDS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
var B = { AIR:0, GRASS:1, DIRT:2, STONE:3, SAND:4, WOOD:5, LEAVES:6, PLANKS:7, COBBLE:8 };
var BREAK_TIME = {1:0.6, 2:0.5, 3:1.4, 4:0.5, 5:1.0, 6:0.3, 7:0.8, 8:1.3};
// \u2500\u2500 TEXTURES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
function pxr(x, y, seed) {
var s = Math.sin(x*73.1 + y*179.3 + seed*251.7) * 43758.545;
return s - Math.floor(s);
}
function mkTex(fn) {
var cv = document.createElement('canvas');
cv.width = cv.height = 16;
var ctx = cv.getContext('2d');
fn(ctx);
var t = new THREE.CanvasTexture(cv);
t.magFilter = THREE.NearestFilter;
t.minFilter = THREE.NearestFilter;
return { tex: t, cv: cv };
}
var TX = {};
TX.grass_top = mkTex(function(ctx) {
for (var y=0;y<16;y++) for (var x=0;x<16;x++) {
var n=pxr(x,y,1), g=(80+n*50)|0;
ctx.fillStyle='rgb('+(20+n*10|0)+','+g+','+(15+n*8|0)+')';
ctx.fillRect(x,y,1,1);
}
});
TX.dirt = mkTex(function(ctx) {
for (var y=0;y<16;y++) for (var x=0;x<16;x++) {
var n=pxr(x,y,2), v=(95+n*45)|0;
ctx.fillStyle='rgb('+(v*.58|0)+','+(v*.38|0)+','+(v*.22|0)+')';
ctx.fillRect(x,y,1,1);
}
});
TX.grass_side = mkTex(function(ctx) {
for (var y=0;y<16;y++) for (var x=0;x<16;x++) {
var n=pxr(x,y,2), v=(95+n*45)|0;
ctx.fillStyle='rgb('+(v*.58|0)+','+(v*.38|0)+','+(v*.22|0)+')';
ctx.fillRect(x,y,1,1);
}
for (var y=0;y<3;y++) for (var x=0;x<16;x++) {
var n=pxr(x,y,7), g=(75+n*40)|0;
ctx.fillStyle='rgb(20,'+g+',15)';
ctx.fillRect(x,y,1,1);
}
});
TX.stone = mkTex(function(ctx) {
for (var y=0;y<16;y++) for (var x=0;x<16;x++) {
var n=pxr(x,y,3), v=(110+n*55)|0;
ctx.fillStyle='rgb('+v+','+v+','+v+')';
ctx.fillRect(x,y,1,1);
}
ctx.fillStyle='rgba(70,70,70,0.45)';
[[1,3,7,1],[9,6,5,1],[3,10,6,1],[11,1,4,1]].forEach(function(a){ctx.fillRect(a[0],a[1],a[2],a[3]);});
[[1,3,1,7],[9,6,1,5],[3,10,1,4]].forEach(function(a){ctx.fillRect(a[0],a[1],a[2],a[3]);});
});
TX.sand = mkTex(function(ctx) {
for (var y=0;y<16;y++) for (var x=0;x<16;x++) {
var n=pxr(x,y,4), v=(200+n*35)|0;
ctx.fillStyle='rgb('+v+','+(v*.9|0)+','+(v*.5|0)+')';
ctx.fillRect(x,y,1,1);
}
});
TX.wood = mkTex(function(ctx) {
for (var y=0;y<16;y++) for (var x=0;x<16;x++) {
var n=pxr(x,y,5);
var ring=Math.sin((x-8)*.5+(y-8)*.15)*.5+.5;
var v=(80+ring*35+n*15)|0;
ctx.fillStyle='rgb('+(v*.72|0)+','+(v*.52|0)+','+(v*.28|0)+')';
ctx.fillRect(x,y,1,1);
}
ctx.fillStyle='rgba(30,15,0,0.3)';
for (var y=0;y<16;y+=3) ctx.fillRect(0,y,16,1);
});
TX.leaves = mkTex(function(ctx) {
ctx.clearRect(0,0,16,16);
for (var y=0;y<16;y++) for (var x=0;x<16;x++) {
var n=pxr(x,y,6);
if (n>0.18) {
var g=(65+n*65)|0;
ctx.fillStyle='rgba(20,'+g+',15,0.95)';
ctx.fillRect(x,y,1,1);
}
}
});
TX.planks = mkTex(function(ctx) {
for (var y=0;y<16;y++) for (var x=0;x<16;x++) {
var n=pxr(x,y,7), row=Math.floor(y/4)|0;
var v=(145+n*25+row%2*12)|0;
ctx.fillStyle='rgb('+(v*.7|0)+','+(v*.55|0)+','+(v*.3|0)+')';
ctx.fillRect(x,y,1,1);
}
ctx.fillStyle='rgba(0,0,0,0.3)';
for (var y=0;y<16;y+=4) ctx.fillRect(0,y,16,1);
ctx.fillRect(8,0,1,4); ctx.fillRect(0,4,1,4);
ctx.fillRect(8,8,1,4); ctx.fillRect(0,12,1,4);
});
TX.cobble = mkTex(function(ctx) {
for (var y=0;y<16;y++) for (var x=0;x<16;x++) {
var n=pxr(x,y,8), v=(95+n*45)|0;
ctx.fillStyle='rgb('+v+','+v+','+v+')';
ctx.fillRect(x,y,1,1);
}
ctx.fillStyle='rgba(50,50,50,0.55)';
[[0,0,8,1],[8,4,8,1],[0,8,5,1],[5,12,11,1],
[0,0,1,8],[8,0,1,4],[5,8,1,8]].forEach(function(a){ctx.fillRect(a[0],a[1],a[2],a[3]);});
});
// \u2500\u2500 MATERIALS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
function mml(txo, opts) {
var cfg = Object.assign({ map: txo.tex }, opts||{});
return new THREE.MeshLambertMaterial(cfg);
}
var MATS = {};
MATS[B.GRASS] = [mml(TX.grass_side),mml(TX.grass_side),mml(TX.grass_top),mml(TX.dirt),mml(TX.grass_side),mml(TX.grass_side)];
MATS[B.DIRT] = mml(TX.dirt);
MATS[B.STONE] = mml(TX.stone);
MATS[B.SAND] = mml(TX.sand);
MATS[B.WOOD] = mml(TX.wood);
MATS[B.LEAVES] = mml(TX.leaves, {transparent:true, alphaTest:0.15, side:THREE.DoubleSide});
MATS[B.PLANKS] = mml(TX.planks);
MATS[B.COBBLE] = mml(TX.cobble);
// \u2500\u2500 THREE.JS SETUP \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
var canvas = document.getElementById('c');
var renderer = new THREE.WebGLRenderer({canvas:canvas, antialias:false});
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.setClearColor(0x87ceeb);
var scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x87ceeb, 45, 95);
var camera = new THREE.PerspectiveCamera(75, 1, 0.05, 200);
var sun = new THREE.DirectionalLight(0xfffbe8, 1.1);
sun.position.set(50, 80, 30);
sun.castShadow = true;
sun.shadow.mapSize.set(1024,1024);
sun.shadow.camera.left = sun.shadow.camera.bottom = -60;
sun.shadow.camera.right = sun.shadow.camera.top = 60;
sun.shadow.camera.far = 200;
scene.add(sun);
scene.add(new THREE.AmbientLight(0x99bbdd, 0.55));
scene.add(new THREE.HemisphereLight(0x87ceeb, 0x447722, 0.35));
function doResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
}
window.addEventListener('resize', doResize);
doResize();
// \u2500\u2500 WORLD \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
var world = {};
var meshMap = {};
function wk(x,y,z) { return x+','+y+','+z; }
function getB(x,y,z) { return world[wk(x,y,z)] || 0; }
function setB(x,y,z,t) {
var k=wk(x,y,z);
if (t===B.AIR) delete world[k]; else world[k]=t;
}
// Smooth noise
function sn(x, z, seed) {
var ix=Math.floor(x), iz=Math.floor(z);
var fx=x-ix, fz=z-iz;
function h(a,b) {
var v=Math.sin(a*127.1+b*311.7+seed*73.9)*43758.545;
return v-Math.floor(v);
}
var ux=fx*fx*(3-2*fx), uz=fz*fz*(3-2*fz);
return h(ix,iz)+(h(ix+1,iz)-h(ix,iz))*ux
+(h(ix,iz+1)-h(ix,iz))*uz
+(h(ix+1,iz+1)-h(ix+1,iz)-h(ix,iz+1)+h(ix,iz))*ux*uz;
}
function fbm(x, z, seed) {
return sn(x*.04,z*.04,seed)*.60
+ sn(x*.09,z*.09,seed+1)*.25
+ sn(x*.20,z*.20,seed+2)*.15;
}
function placeTree(ox, sy, oz) {
var h = 4 + (pxr(ox,oz,99)*3)|0;
for (var i=0;i<h;i++) setB(ox, sy+i, oz, B.WOOD);
var top = sy+h;
for (var lx=-2;lx<=2;lx++)
for (var lz=-2;lz<=2;lz++)
for (var ly=-1;ly<=2;ly++) {
if (Math.abs(lx)+Math.abs(lz) > 3) continue;
if (lx===0&&lz===0&&ly<0) continue;
if (getB(ox+lx,top+ly,oz+lz)===B.AIR)
setB(ox+lx,top+ly,oz+lz, B.LEAVES);
}
}
function generateWorld() {
var R = 26;
for (var x=-R;x<R;x++)
for (var z=-R;z<R;z++) {
var h = fbm(x, z, 0);
var biome = fbm(x+200, z+200, 5);
var isSand = biome < 0.35;
var isRocky = h > 0.72;
var sy = Math.max(2, (h*22+5)|0);
for (var y=0;y<=sy;y++) {
var t;
if (y===sy) t = isSand ? B.SAND : isRocky ? B.STONE : B.GRASS;
else if (y>=sy-3) t = isSand ? B.SAND : B.DIRT;
else t = B.STONE;
setB(x,y,z,t);
}
if (!isSand && !isRocky && sy>5 && pxr(x,z,55)>0.80)
placeTree(x, sy+1, z);
}
}
// \u2500\u2500 MESH BUILDER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
var boxGeo = new THREE.BoxGeometry(1,1,1);
var DIRS6 = [[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]];
function needsMesh(x,y,z) {
if (getB(x,y,z)===B.AIR) return false;
for (var i=0;i<6;i++) {
var d=DIRS6[i];
if (getB(x+d[0],y+d[1],z+d[2])===B.AIR) return true;
}
return false;
}
function addMesh(x,y,z) {
var k=wk(x,y,z);
if (meshMap[k]) { scene.remove(meshMap[k]); delete meshMap[k]; }
var type=getB(x,y,z);
if (type===B.AIR) return;
if (!needsMesh(x,y,z)) return;
var m = new THREE.Mesh(boxGeo, MATS[type]);
m.position.set(x,y,z);
m.castShadow = true;
m.receiveShadow = true;
m.userData = {x:x,y:y,z:z,type:type};
scene.add(m);
meshMap[k] = m;
}
function removeMesh(x,y,z) {
var k=wk(x,y,z);
if (meshMap[k]) { scene.remove(meshMap[k]); delete meshMap[k]; }
}
function rebuildAround(x,y,z) {
addMesh(x,y,z);
for (var i=0;i<6;i++) {
var d=DIRS6[i]; addMesh(x+d[0],y+d[1],z+d[2]);
}
}
function buildAll() {
for (var k in world) {
var p=k.split(','); addMesh(+p[0],+p[1],+p[2]);
}
}
// \u2500\u2500 PLAYER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
var pl = { x:0.5, y:20, z:0.5, vx:0, vy:0, vz:0, yaw:0, pitch:0, onGround:false };
var EYE=1.62, PW=0.28, PH=1.75;
var GRAVITY=-26, JUMP_VY=9;
function spawnY(x, z) {
for (var y=40;y>=0;y--)
if (getB(Math.floor(x),y,Math.floor(z))!==B.AIR && getB(Math.floor(x),y+1,Math.floor(z))===B.AIR)
return y+1.5;
return 20;
}
function collides(px,py,pz) {
var offsets=[[-PW,0.05,-PW],[-PW,0.05,PW],[PW,0.05,-PW],[PW,0.05,PW],
[-PW,PH*.5,-PW],[-PW,PH*.5,PW],[PW,PH*.5,-PW],[PW,PH*.5,PW],
[-PW,PH-0.05,-PW],[-PW,PH-0.05,PW],[PW,PH-0.05,-PW],[PW,PH-0.05,PW]];
for (var i=0;i<offsets.length;i++) {
var o=offsets[i];
if (getB(Math.floor(px+o[0]),Math.floor(py+o[1]),Math.floor(pz+o[2]))!==B.AIR) return true;
}
return false;
}
function movePlayer(dt) {
var sprint=(keys['ShiftLeft']||keys['ShiftRight']);
var spd=sprint?9:5;
var sy=Math.sin(pl.yaw), cy=Math.cos(pl.yaw);
var mx=0, mz=0;
if (keys['KeyW']||keys['ArrowUp']) { mx-=sy; mz-=cy; }
if (keys['KeyS']||keys['ArrowDown']) { mx+=sy; mz+=cy; }
if (keys['KeyA']||keys['ArrowLeft']) { mx-=cy; mz+=sy; }
if (keys['KeyD']||keys['ArrowRight']) { mx+=cy; mz-=sy; }
var ml=Math.sqrt(mx*mx+mz*mz);
if (ml>0) { mx=mx/ml*spd; mz=mz/ml*spd; }
pl.vx=mx; pl.vz=mz;
pl.vy+=GRAVITY*dt;
if (keys['Space']&&pl.onGround) { pl.vy=JUMP_VY; pl.onGround=false; }
pl.x+=pl.vx*dt;
if (collides(pl.x,pl.y,pl.z)) { pl.x-=pl.vx*dt; pl.vx=0; }
pl.z+=pl.vz*dt;
if (collides(pl.x,pl.y,pl.z)) { pl.z-=pl.vz*dt; pl.vz=0; }
pl.y+=pl.vy*dt;
pl.onGround=false;
if (collides(pl.x,pl.y,pl.z)) {
if (pl.vy<0) pl.onGround=true;
pl.y-=pl.vy*dt; pl.vy=0;
}
if (pl.y<-10) { pl.y=spawnY(0,0); pl.vy=0; }
}
// \u2500\u2500 INPUT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
var keys={};
var locked=false;
document.addEventListener('keydown', function(e){ keys[e.code]=true; });
document.addEventListener('keyup', function(e){ keys[e.code]=false; });
document.addEventListener('pointerlockchange', function(){ locked=!!document.pointerLockElement; });
document.addEventListener('mousemove', function(e){
if (!locked) return;
pl.yaw -= e.movementX*0.0022;
pl.pitch -= e.movementY*0.0022;
pl.pitch = Math.max(-1.55, Math.min(1.55, pl.pitch));
});
// \u2500\u2500 HOTBAR \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
var HOTBAR=[
{type:B.GRASS, label:'Grass', tx:TX.grass_top},
{type:B.DIRT, label:'Dirt', tx:TX.dirt},
{type:B.STONE, label:'Stone', tx:TX.stone},
{type:B.SAND, label:'Sand', tx:TX.sand},
{type:B.WOOD, label:'Wood', tx:TX.wood},
{type:B.LEAVES,label:'Leaves', tx:TX.leaves},
{type:B.PLANKS,label:'Planks', tx:TX.planks},
{type:B.COBBLE,label:'Cobble', tx:TX.cobble},
];
var slot=0;
function buildHotbar() {
var hb=document.getElementById('hotbar');
hb.innerHTML='';
for (var i=0;i<HOTBAR.length;i++) {
var item=HOTBAR[i];
var div=document.createElement('div');
div.className='hslot'+(i===slot?' sel':'');
var cv=document.createElement('canvas');
cv.width=cv.height=32; cv.style.width='32px'; cv.style.height='32px';
var ctx=cv.getContext('2d');
ctx.imageSmoothingEnabled=false;
ctx.drawImage(item.tx.cv, 0,0,32,32);
var sp=document.createElement('span');
sp.textContent=item.label;
div.appendChild(cv); div.appendChild(sp);
hb.appendChild(div);
}
}
document.addEventListener('wheel', function(e){
slot=((slot+(e.deltaY>0?1:-1))+HOTBAR.length)%HOTBAR.length;
buildHotbar();
}, {passive:true});
for (var _i=1;_i<=8;_i++) {
(function(n){
document.addEventListener('keydown', function(e){
if (e.code==='Digit'+n) { slot=n-1; buildHotbar(); }
});
})(_i);
}
// \u2500\u2500 RAYCAST \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
var rc = new THREE.Raycaster();
rc.near=0.1; rc.far=8;
function doRaycast() {
var dir=new THREE.Vector3(0,0,-1);
var eu=new THREE.Euler(pl.pitch, pl.yaw, 0, 'YXZ');
dir.applyEuler(eu);
var ori=new THREE.Vector3(pl.x, pl.y+EYE, pl.z);
rc.set(ori, dir);
var objs=Object.values(meshMap);
var hits=rc.intersectObjects(objs, false);
return hits.length>0 ? hits[0] : null;
}
// \u2500\u2500 HIGHLIGHT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
var hlMesh=new THREE.Mesh(
new THREE.BoxGeometry(1.005,1.005,1.005),
new THREE.MeshBasicMaterial({color:0x000000,wireframe:true,transparent:true,opacity:0.45})
);
scene.add(hlMesh);
hlMesh.visible=false;
// \u2500\u2500 BREAK/PLACE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2
2
139KB
610KB
278.0ms
192.0ms
278.0ms