Spaces:
Sleeping
Sleeping
<html> | |
<head> | |
<title>Three.js Synced World (DB Backend)</title> | |
<style> | |
body { margin: 0; overflow: hidden; } | |
canvas { display: block; } | |
</style> | |
</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'; | |
// ... (Keep all variables: scene, camera, renderer, playerMesh, etc.) ... | |
let scene, camera, renderer, playerMesh; | |
let raycaster, mouse; | |
const keysPressed = {}; | |
const playerSpeed = 0.15; | |
let newlyPlacedObjects = []; // For sessionStorage persistence between non-save reruns | |
const placeholderPlots = new Set(); | |
const groundMeshes = {}; | |
const allRenderedObjects = {}; // Track all scene objects: id -> mesh/group | |
const SESSION_STORAGE_KEY = 'unsavedDbWorldState_v2'; | |
// --- Access State from Streamlit --- | |
const allInitialObjects = window.ALL_INITIAL_OBJECTS || []; // From DB | |
const plotsMetadata = window.PLOTS_METADATA || []; // From DB | |
const selectedObjectType = window.SELECTED_OBJECT_TYPE || "None"; | |
const plotWidth = window.PLOT_WIDTH || 50.0; | |
const plotDepth = window.PLOT_DEPTH || 50.0; | |
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x55aa55, roughness: 0.9, metalness: 0.1, side: THREE.DoubleSide }); | |
const placeholderGroundMaterial = new THREE.MeshStandardMaterial({ color: 0x448844, roughness: 0.95, metalness: 0.1, side: THREE.DoubleSide }); | |
// --- init() function --- | |
function init() { | |
// ... (scene, camera, renderer setup as before) ... | |
scene = new THREE.Scene(); scene.background = new THREE.Color(0xabcdef); | |
const aspect = window.innerWidth / window.innerHeight; camera = new THREE.PerspectiveCamera(60, aspect, 0.1, 4000); camera.position.set(0, 15, 20); camera.lookAt(0, 0, 0); scene.add(camera); | |
setupLighting(); setupInitialGround(); 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; renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.body.appendChild(renderer.domElement); | |
loadInitialObjects(); // Load from DB data | |
restoreUnsavedState(); // Restore from SessionStorage | |
// Event Listeners | |
document.addEventListener('mousemove', onMouseMove, false); document.addEventListener('click', onDocumentClick, false); window.addEventListener('resize', onWindowResize, false); document.addEventListener('keydown', onKeyDown); document.addEventListener('keyup', onKeyUp); | |
// Define global functions needed by Python | |
window.teleportPlayer = teleportPlayer; | |
window.getSaveDataAndPosition = getSaveDataAndPosition; // Ensure this is defined correctly below | |
// window.resetNewlyPlacedObjects = resetNewlyPlacedObjects; // No longer needed | |
console.log("Three.js Initialized (DB Backend v2). World ready."); | |
animate(); | |
} | |
// --- setupLighting(), setupInitialGround(), createGroundPlane(), setupPlayer() --- | |
// ... (Keep these functions exactly as in the previous version) ... | |
function setupLighting() { const a=new THREE.AmbientLight(0xffffff,0.5); scene.add(a); const d=new THREE.DirectionalLight(0xffffff,1.0); d.position.set(50,150,100); d.castShadow=true; d.shadow.mapSize.width=4096; d.shadow.mapSize.height=4096; d.shadow.camera.near=0.5; d.shadow.camera.far=500; d.shadow.camera.left=-150; d.shadow.camera.right=150; d.shadow.camera.top=150; d.shadow.camera.bottom=-150; d.shadow.bias=-0.001; scene.add(d); } | |
function setupInitialGround() { plotsMetadata.forEach(p => {createGroundPlane(p.grid_x,p.grid_z,false);}); if(plotsMetadata.length===0) {createGroundPlane(0,0,false);} } | |
function createGroundPlane(gx,gz,isPlaceholder) { const k=`<span class="math-inline">\{gx\}\_</span>{gz}`; if(groundMeshes[k]) return; const geo=new THREE.PlaneGeometry(plotWidth,plotDepth); const mat=isPlaceholder?placeholderGroundMaterial:groundMaterial; const mesh=new THREE.Mesh(geo,mat); mesh.rotation.x=-Math.PI/2; mesh.position.y=-0.05; mesh.position.x=gx*plotWidth+plotWidth/2; mesh.position.z=gz*plotDepth+plotDepth/2; mesh.receiveShadow=true; mesh.userData.gridKey=k; scene.add(mesh); groundMeshes[k]=mesh; if(isPlaceholder){placeholderPlots.add(k);} } | |
function setupPlayer() { const g=new THREE.CapsuleGeometry(0.4,0.8,4,8); const m=new THREE.MeshStandardMaterial({color:0x0000ff,roughness:0.6}); playerMesh=new THREE.Mesh(g,m); playerMesh.position.set(plotWidth/2,0.8,plotDepth/2); playerMesh.castShadow=true; playerMesh.receiveShadow=true; scene.add(playerMesh); } | |
// --- loadInitialObjects(), clearAllRenderedObjects(), createAndPlaceObject() --- | |
// ... (Keep these functions exactly as in the previous version - ensuring createAndPlaceObject adds to allRenderedObjects) ... | |
function loadInitialObjects() { console.log(`Loading ${allInitialObjects.length} initial objects.`); clearAllRenderedObjects(); allInitialObjects.forEach(d => { createAndPlaceObject(d, false); }); console.log("Finished initial load."); } | |
function clearAllRenderedObjects() { Object.values(allRenderedObjects).forEach(o => scene.remove(o)); for (const k in allRenderedObjects) delete allRenderedObjects[k]; newlyPlacedObjects = []; /* Important: clear this too when reloading all */ } | |
function createAndPlaceObject(objData, isNewObjectForSession) { let obj=null; switch (objData.type) { case "Simple House": obj=createSimpleHouse(); break; case "Tree": obj=createTree(); break; case "Rock": obj=createRock(); break; case "Fence Post": obj=createFencePost(); break; default: return null; } if (obj) { obj.userData.obj_id = objData.obj_id || obj.userData.obj_id; if (allRenderedObjects[obj.userData.obj_id]) return null; if(objData.position&&objData.position.x!==undefined){obj.position.set(objData.position.x,objData.position.y,objData.position.z);}else if(objData.pos_x!==undefined){obj.position.set(objData.pos_x,objData.pos_y,objData.pos_z);} if(objData.rotation){obj.rotation.set(objData.rotation._x,objData.rotation._y,objData.rotation._z,objData.rotation._order||'XYZ');}else if(objData.rot_x!==undefined){obj.rotation.set(objData.rot_x,objData.rot_y,objData.rot_z,objData.rot_order||'XYZ');} scene.add(obj); allRenderedObjects[obj.userData.obj_id] = obj; if(isNewObjectForSession){newlyPlacedObjects.push(obj);} return obj; } return null; } | |
// --- saveUnsavedState(), restoreUnsavedState(), clearUnsavedSessionState() --- | |
// ... (Keep these sessionStorage functions exactly as in the previous version) ... | |
function saveUnsavedState() { try { const d = 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}})); sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(d)); } catch(e) { console.error("Session save error:", e); } } | |
function restoreUnsavedState() { try { const s=sessionStorage.getItem(SESSION_STORAGE_KEY); if(s) { const d=JSON.parse(s); if(Array.isArray(d)) { let c=0; d.forEach(o => { if(createAndPlaceObject(o, true)) c++;}); console.log(`Restored ${c} unsaved objects.`); } } } catch(e) { console.error("Session restore error:", e); sessionStorage.removeItem(SESSION_STORAGE_KEY); } } | |
// function clearUnsavedSessionState() { sessionStorage.removeItem(SESSION_STORAGE_KEY); newlyPlacedObjects = []; console.log("Cleared unsaved session state."); } // Might be useful later | |
// --- Object Creation Functions --- | |
// ... (Keep these exactly as before, ensuring they assign userData.type and userData.obj_id) ... | |
function createObjectBase(type) { return { userData: { type: type, obj_id: THREE.MathUtils.generateUUID() } }; } | |
function createSimpleHouse() { const base = createObjectBase("Simple House"); const group = new THREE.Group(); Object.assign(group, base); const mat1=new THREE.MeshStandardMaterial({color:0xffccaa,roughness:0.8}), mat2=new THREE.MeshStandardMaterial({color:0xaa5533,roughness:0.7}); const m1=new THREE.Mesh(new THREE.BoxGeometry(2,1.5,2.5),mat1); m1.position.y=0.75;m1.castShadow=true;m1.receiveShadow=true;group.add(m1); const m2=new THREE.Mesh(new THREE.ConeGeometry(1.8,1,4),mat2); m2.position.y=1.5+0.5;m2.rotation.y=Math.PI/4;m2.castShadow=true;m2.receiveShadow=true;group.add(m2); return group; } | |
function createTree() { const base=createObjectBase("Tree"); const group=new THREE.Group(); Object.assign(group,base); const mat1=new THREE.MeshStandardMaterial({color:0x8B4513,roughness:0.9}), mat2=new THREE.MeshStandardMaterial({color:0x228B22,roughness:0.8}); const m1=new THREE.Mesh(new THREE.CylinderGeometry(0.3,0.4,2,8),mat1); m1.position.y=1; m1.castShadow=true;m1.receiveShadow=true;group.add(m1); const m2= |