Spaces:
Running
Running
<!-- index.html --> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Three.js Infinite World</title> | |
<style>body{margin:0;overflow:hidden;}canvas{display:block;}</style> | |
<!-- State injected here --> | |
</head> | |
<body> | |
<script type="importmap"> | |
{"imports":{"three":"https://unpkg.com/[email protected]/build/three.module.js","three/addons/":"https://unpkg.com/[email protected]/examples/jsm/"}} | |
</script> | |
<script type="module"> | |
import * as THREE from 'three'; | |
let scene, camera, renderer, playerMesh, raycaster, mouse; | |
let newlyPlacedObjects = []; | |
const groundMeshes = {}, placeholderPlots=new Set(); | |
const SESSION_KEY='unsavedInfiniteWorldState'; | |
function init(){ | |
scene=new THREE.Scene(); scene.background=new THREE.Color(0xabcdef); | |
camera=new THREE.PerspectiveCamera(60,window.innerWidth/window.innerHeight,0.1,4000); | |
camera.position.set(0,15,20);camera.lookAt(0,0,0); | |
setupLighting(); setupGround(); setupPlayer(); | |
raycaster=new THREE.Raycaster(); mouse=new THREE.Vector2(); | |
renderer=new THREE.WebGLRenderer({antialias:true}); | |
renderer.setSize(window.innerWidth,window.innerHeight); | |
renderer.shadowMap.enabled=true; document.body.appendChild(renderer.domElement); | |
loadObjects(); restoreUnsavedState(); | |
window.addEventListener('mousemove',onMouseMove,false); | |
window.addEventListener('click',onDocClick,false); | |
window.addEventListener('keydown',onKeyDown); | |
window.addEventListener('keyup',onKeyUp); | |
window.teleportPlayer=teleportPlayer; | |
window.getSaveDataAndPosition=getSaveDataAndPosition; | |
window.resetNewlyPlacedObjects=resetNewlyPlacedObjects; | |
setInterval(pollGameState,5000); | |
animate(); | |
} | |
function setupLighting(){ | |
scene.add(new THREE.AmbientLight(0xffffff,0.5)); | |
const dir=new THREE.DirectionalLight(0xffffff,1);dir.position.set(50,150,100); | |
dir.castShadow=true;scene.add(dir); | |
} | |
function setupGround(){ | |
const plots=window.PLOTS_METADATA||[]; | |
(plots.length?plots:[{grid_x:0,grid_z:0}]).forEach(p=>createGround(p.grid_x,p.grid_z,false)); | |
} | |
function createGround(x,z,ph=false){ | |
const key=`${x}_${z}`; if(groundMeshes[key])return; | |
const geo=new THREE.PlaneGeometry(window.PLOT_WIDTH,window.PLOT_DEPTH); | |
const mat=new THREE.MeshStandardMaterial({color:ph?0x448844:0x55aa55,side:THREE.DoubleSide}); | |
const m=new THREE.Mesh(geo,mat); m.rotation.x=-Math.PI/2; | |
m.position.set(x*window.PLOT_WIDTH+window.PLOT_WIDTH/2,-0.05,z*window.PLOT_DEPTH+window.PLOT_DEPTH/2); | |
scene.add(m); groundMeshes[key]=m; if(ph)placeholderPlots.add(key); | |
} | |
function setupPlayer(){ | |
const geo=new THREE.CapsuleGeometry(0.4,0.8,4,8); | |
playerMesh=new THREE.Mesh(geo,new THREE.MeshStandardMaterial({color:0x0000ff,roughness:0.6})); | |
playerMesh.position.set(window.PLOT_WIDTH/2,1.2,window.PLOT_DEPTH/2); | |
scene.add(playerMesh); | |
} | |
function loadObjects(){ | |
(window.ALL_INITIAL_OBJECTS||[]).forEach(d=>createAndPlace(d,false)); | |
} | |
function createAndPlace(d,newObj){ | |
const obj=window.KITS[d.type]?createPrefabKit(d.type):null; | |
if(!obj) return; | |
obj.position.set(d.pos_x||d.position.x,d.pos_y||d.position.y,d.pos_z||d.position.z); | |
scene.add(obj); if(newObj)newlyPlacedObjects.push(obj); | |
} | |
function createPrefabKit(name){ | |
const kit=window.KITS[name]||{}; const grp=new THREE.Group(); | |
kit.modules?.forEach(m=>{const md=createModule(m); if(md)grp.add(md)}); | |
return grp; | |
} | |
function createModule(n){ | |
switch(n){ | |
case 'Cube': return new THREE.Mesh(new THREE.BoxGeometry(1,1,1),new THREE.MeshStandardMaterial()); | |
case 'Sphere': return new THREE.Mesh(new THREE.SphereGeometry(0.5,16,16),new THREE.MeshStandardMaterial()); | |
case 'Cylinder': return new THREE.Mesh(new THREE.CylinderGeometry(0.5,0.5,1,12),new THREE.MeshStandardMaterial()); | |
case 'Cone': return new THREE.Mesh(new THREE.ConeGeometry(0.5,1,12),new THREE.MeshStandardMaterial()); | |
default: console.warn('Unknown module',n); return null; | |
} | |
} | |
function onDocClick(ev){ | |
if(window.SELECTED_OBJECT_TYPE==='None')return; const candidates=Object.values(groundMeshes); | |
raycaster.setFromCamera(mouse,camera); const hit=raycaster.intersectObjects(candidates); | |
if(!hit.length)return; const pt=hit[0].point; | |
const mesh=createPrefabKit(window.SELECTED_OBJECT_TYPE); | |
if(mesh){mesh.position.copy(pt);scene.add(mesh);newlyPlacedObjects.push(mesh);saveUnsaved();} | |
} | |
function saveUnsaved(){sessionStorage.setItem(SESSION_KEY,JSON.stringify(newlyPlacedObjects.map(o=>({obj_id:o.userData?.obj_id,type:o.userData?.type,position:{x:o.position.x,y:o.position.y,z:o.position.z},rotation:{_x:o.rotation.x,_y:o.rotation.y,_z:o.rotation.z,_order:o.rotation.order}}))));} | |
function restoreUnsavedState(){const s=sessionStorage.getItem(SESSION_KEY); if(s){JSON.parse(s).forEach(d=>createAndPlace(d,true));}} | |
function resetNewlyPlacedObjects(){sessionStorage.removeItem(SESSION_KEY);newlyPlacedObjects=[];} | |
function getSaveDataAndPosition(){const objs=newlyPlacedObjects.map(o=>({obj_id:o.userData?.obj_id,type:o.userData?.type,position:{x:o.position.x,y:o.position.y,z:o.position.z},rotation:{_x:o.rotation.x,_y:o.rotation.y,_z:o.rotation.z,_order:o.rotation.order}})); | |
const pos={x:playerMesh.position.x,y:playerMesh.position.y,z:playerMesh.position.z};return JSON.stringify({playerPosition:pos,objectsToSave:objs}); | |
} | |
function teleportPlayer(x,z){playerMesh.position.set(x,playerMesh.position.y,z);camera.position.set(x,playerMesh.position.y+15,z+20);camera.lookAt(playerMesh.position);} | |
function pollGameState(){console.log('Polling',window.GAME_STATE);} | |
function onMouseMove(e){mouse.x=(e.clientX/window.innerWidth)*2-1;mouse.y=-(e.clientY/window.innerHeight)*2+1;} | |
function onKeyDown(e){/* movement logic */} | |
function onKeyUp(e){} | |
function animate(){requestAnimationFrame(animate);renderer.render(scene,camera);} | |
init(); | |
</script> | |
</body> | |
</html> |