File size: 9,413 Bytes
06b17d1
 
 
4788bb9
06b17d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1f9eb7e
84a18e0
06b17d1
 
 
1f9eb7e
4788bb9
 
1f9eb7e
06b17d1
4788bb9
046998c
1f9eb7e
 
 
06b17d1
 
4788bb9
84a18e0
4788bb9
 
06b17d1
 
1f9eb7e
 
 
 
 
 
 
 
d1944fb
1f9eb7e
 
046998c
06b17d1
1f9eb7e
06b17d1
1f9eb7e
06b17d1
1f9eb7e
 
06b17d1
4788bb9
06b17d1
 
 
1f9eb7e
 
 
 
 
 
4788bb9
 
1f9eb7e
 
 
 
 
d1944fb
1f9eb7e
 
 
 
 
d1944fb
1f9eb7e
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<!DOCTYPE html>
<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=