awacke1 commited on
Commit
926eeea
·
verified ·
1 Parent(s): 5b479a3

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +441 -0
index.html ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Three.js Infinite World</title>
5
+ <style>
6
+ body { margin: 0; overflow: hidden; }
7
+ canvas { display: block; }
8
+ </style>
9
+ <!-- New: Polling function for game state -->
10
+ <script>
11
+ // Poll the shared game state every 5 seconds (for demonstration)
12
+ function pollGameState() {
13
+ console.log("Polling updated game state:", window.GAME_STATE);
14
+ // Here you could update the scene based on the new state.
15
+ }
16
+ setInterval(pollGameState, 5000);
17
+ </script>
18
+ </head>
19
+ <body>
20
+ <script type="importmap">
21
+ {
22
+ "imports": {
23
+ "three": "https://unpkg.com/[email protected]/build/three.module.js",
24
+ "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
25
+ }
26
+ }
27
+ </script>
28
+
29
+ <script type="module">
30
+ import * as THREE from 'three';
31
+
32
+ let scene, camera, renderer, playerMesh;
33
+ let raycaster, mouse;
34
+ const keysPressed = {};
35
+ const playerSpeed = 0.15;
36
+ let newlyPlacedObjects = []; // Track objects added THIS session for saving
37
+ const placeholderPlots = new Set();
38
+ const groundMeshes = {}; // Store ground mesh references
39
+
40
+ // --- Session Storage Key ---
41
+ const SESSION_STORAGE_KEY = 'unsavedInfiniteWorldState';
42
+
43
+ // --- Injected State from Streamlit ---
44
+ const allInitialObjects = window.ALL_INITIAL_OBJECTS || [];
45
+ const plotsMetadata = window.PLOTS_METADATA || [];
46
+ const selectedObjectType = window.SELECTED_OBJECT_TYPE || "None";
47
+ const plotWidth = window.PLOT_WIDTH || 50.0;
48
+ const plotDepth = window.PLOT_DEPTH || 50.0;
49
+
50
+ const groundMaterial = new THREE.MeshStandardMaterial({
51
+ color: 0x55aa55, roughness: 0.9, metalness: 0.1, side: THREE.DoubleSide
52
+ });
53
+ const placeholderGroundMaterial = new THREE.MeshStandardMaterial({
54
+ color: 0x448844, roughness: 0.95, metalness: 0.1, side: THREE.DoubleSide
55
+ });
56
+
57
+ function init() {
58
+ scene = new THREE.Scene();
59
+ scene.background = new THREE.Color(0xabcdef);
60
+ const aspect = window.innerWidth / window.innerHeight;
61
+ camera = new THREE.PerspectiveCamera(60, aspect, 0.1, 4000);
62
+ camera.position.set(0, 15, 20);
63
+ camera.lookAt(0, 0, 0);
64
+ scene.add(camera);
65
+
66
+ setupLighting();
67
+ setupInitialGround();
68
+ setupPlayer();
69
+
70
+ raycaster = new THREE.Raycaster();
71
+ mouse = new THREE.Vector2();
72
+
73
+ renderer = new THREE.WebGLRenderer({ antialias: true });
74
+ renderer.setSize(window.innerWidth, window.innerHeight);
75
+ renderer.shadowMap.enabled = true;
76
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
77
+ document.body.appendChild(renderer.domElement);
78
+
79
+ loadInitialObjects();
80
+ restoreUnsavedState();
81
+
82
+ // Event Listeners
83
+ document.addEventListener('mousemove', onMouseMove, false);
84
+ document.addEventListener('click', onDocumentClick, false);
85
+ window.addEventListener('resize', onWindowResize, false);
86
+ document.addEventListener('keydown', onKeyDown);
87
+ document.addEventListener('keyup', onKeyUp);
88
+
89
+ // Define functions callable by Streamlit
90
+ window.teleportPlayer = teleportPlayer;
91
+ window.getSaveDataAndPosition = getSaveDataAndPosition;
92
+ window.resetNewlyPlacedObjects = resetNewlyPlacedObjects;
93
+
94
+ console.log("Three.js Initialized. World ready.");
95
+ animate();
96
+ }
97
+
98
+ function setupLighting() {
99
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
100
+ scene.add(ambientLight);
101
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
102
+ directionalLight.position.set(50, 150, 100);
103
+ directionalLight.castShadow = true;
104
+ directionalLight.shadow.mapSize.width = 4096;
105
+ directionalLight.shadow.mapSize.height = 4096;
106
+ directionalLight.shadow.camera.near = 0.5;
107
+ directionalLight.shadow.camera.far = 500;
108
+ directionalLight.shadow.camera.left = -150;
109
+ directionalLight.shadow.camera.right = 150;
110
+ directionalLight.shadow.camera.top = 150;
111
+ directionalLight.shadow.camera.bottom = -150;
112
+ directionalLight.shadow.bias = -0.001;
113
+ scene.add(directionalLight);
114
+ }
115
+
116
+ function setupInitialGround() {
117
+ console.log(`Setting up initial ground for ${plotsMetadata.length} plots.`);
118
+ plotsMetadata.forEach(plot => {
119
+ createGroundPlane(plot.grid_x, plot.grid_z, false);
120
+ });
121
+ if (plotsMetadata.length === 0) {
122
+ createGroundPlane(0, 0, false);
123
+ }
124
+ }
125
+
126
+ function createGroundPlane(gridX, gridZ, isPlaceholder) {
127
+ const gridKey = `${gridX}_${gridZ}`;
128
+ if (groundMeshes[gridKey]) return;
129
+ console.log(`Creating ${isPlaceholder ? 'placeholder' : 'initial'} ground at ${gridX}, ${gridZ}`);
130
+ const groundGeometry = new THREE.PlaneGeometry(plotWidth, plotDepth);
131
+ const material = isPlaceholder ? placeholderGroundMaterial : groundMaterial;
132
+ const groundMesh = new THREE.Mesh(groundGeometry, material);
133
+ groundMesh.rotation.x = -Math.PI / 2;
134
+ groundMesh.position.y = -0.05;
135
+ groundMesh.position.x = gridX * plotWidth + plotWidth / 2.0;
136
+ groundMesh.position.z = gridZ * plotDepth + plotDepth / 2.0;
137
+ groundMesh.receiveShadow = true;
138
+ groundMesh.userData.gridKey = gridKey;
139
+ scene.add(groundMesh);
140
+ groundMeshes[gridKey] = groundMesh;
141
+ if (isPlaceholder) placeholderPlots.add(gridKey);
142
+ }
143
+
144
+ function setupPlayer() {
145
+ const playerGeo = new THREE.CapsuleGeometry(0.4, 0.8, 4, 8);
146
+ const playerMat = new THREE.MeshStandardMaterial({ color: 0x0000ff, roughness: 0.6 });
147
+ playerMesh = new THREE.Mesh(playerGeo, playerMat);
148
+ playerMesh.position.set(plotWidth / 2, 0.4 + 0.8/2, plotDepth/2);
149
+ playerMesh.castShadow = true;
150
+ playerMesh.receiveShadow = true;
151
+ scene.add(playerMesh);
152
+ }
153
+
154
+ function loadInitialObjects() {
155
+ console.log(`Loading ${allInitialObjects.length} initial objects from Python.`);
156
+ allInitialObjects.forEach(objData => { createAndPlaceObject(objData, false); });
157
+ console.log("Finished loading initial objects.");
158
+ }
159
+
160
+ function createAndPlaceObject(objData, isNewObject) {
161
+ let loadedObject = null;
162
+ switch (objData.type) {
163
+ case "Simple House": loadedObject = createSimpleHouse(); break;
164
+ case "Tree": loadedObject = createTree(); break;
165
+ case "Rock": loadedObject = createRock(); break;
166
+ case "Fence Post": loadedObject = createFencePost(); break;
167
+ default: console.warn("Unknown object type in data:", objData.type); break;
168
+ }
169
+ if (loadedObject) {
170
+ if (objData.position && objData.position.x !== undefined) {
171
+ loadedObject.position.set(objData.position.x, objData.position.y, objData.position.z);
172
+ } else if (objData.pos_x !== undefined) {
173
+ loadedObject.position.set(objData.pos_x, objData.pos_y, objData.pos_z);
174
+ }
175
+ if (objData.rotation) {
176
+ loadedObject.rotation.set(objData.rotation._x, objData.rotation._y, objData.rotation._z, objData.rotation._order || 'XYZ');
177
+ } else if (objData.rot_x !== undefined) {
178
+ loadedObject.rotation.set(objData.rot_x, objData.rot_y, objData.rot_z, objData.rot_order || 'XYZ');
179
+ }
180
+ loadedObject.userData.obj_id = objData.obj_id || loadedObject.userData.obj_id;
181
+ scene.add(loadedObject);
182
+ if (isNewObject) newlyPlacedObjects.push(loadedObject);
183
+ return loadedObject;
184
+ }
185
+ return null;
186
+ }
187
+
188
+ function saveUnsavedState() {
189
+ try {
190
+ const stateToSave = newlyPlacedObjects.map(obj => ({
191
+ obj_id: obj.userData.obj_id,
192
+ type: obj.userData.type,
193
+ position: { x: obj.position.x, y: obj.position.y, z: obj.position.z },
194
+ rotation: { _x: obj.rotation.x, _y: obj.rotation.y, _z: obj.rotation.z, _order: obj.rotation.order }
195
+ }));
196
+ sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(stateToSave));
197
+ console.log(`Saved ${stateToSave.length} unsaved objects to sessionStorage.`);
198
+ } catch (e) {
199
+ console.error("Error saving state to sessionStorage:", e);
200
+ }
201
+ }
202
+
203
+ function restoreUnsavedState() {
204
+ try {
205
+ const savedState = sessionStorage.getItem(SESSION_STORAGE_KEY);
206
+ if (savedState) {
207
+ console.log("Found unsaved state in sessionStorage. Restoring...");
208
+ const objectsToRestore = JSON.parse(savedState);
209
+ if (Array.isArray(objectsToRestore)) {
210
+ newlyPlacedObjects = [];
211
+ let count = 0;
212
+ objectsToRestore.forEach(objData => {
213
+ if(createAndPlaceObject(objData, true)) { count++; }
214
+ });
215
+ console.log(`Restored ${count} objects.`);
216
+ }
217
+ } else {
218
+ console.log("No unsaved state found in sessionStorage.");
219
+ }
220
+ } catch (e) {
221
+ console.error("Error restoring state from sessionStorage:", e);
222
+ sessionStorage.removeItem(SESSION_STORAGE_KEY);
223
+ }
224
+ }
225
+
226
+ function clearUnsavedState() {
227
+ sessionStorage.removeItem(SESSION_STORAGE_KEY);
228
+ newlyPlacedObjects = [];
229
+ console.log("Cleared unsaved state from memory and sessionStorage.");
230
+ }
231
+
232
+ function createObjectBase(type) {
233
+ return { userData: { type: type, obj_id: THREE.MathUtils.generateUUID() } };
234
+ }
235
+
236
+ function createSimpleHouse() {
237
+ const base = createObjectBase("Simple House");
238
+ const group = new THREE.Group();
239
+ Object.assign(group, base);
240
+ const mat1 = new THREE.MeshStandardMaterial({color:0xffccaa, roughness:0.8});
241
+ const mat2 = new THREE.MeshStandardMaterial({color:0xaa5533, roughness:0.7});
242
+ const m1 = new THREE.Mesh(new THREE.BoxGeometry(2,1.5,2.5), mat1);
243
+ m1.position.y = 1.5/2;
244
+ m1.castShadow = true;
245
+ m1.receiveShadow = true;
246
+ group.add(m1);
247
+ const m2 = new THREE.Mesh(new THREE.ConeGeometry(1.8,1,4), mat2);
248
+ m2.position.y = 1.5+1/2;
249
+ m2.rotation.y = Math.PI/4;
250
+ m2.castShadow = true;
251
+ m2.receiveShadow = true;
252
+ group.add(m2);
253
+ return group;
254
+ }
255
+
256
+ function createTree() {
257
+ const base = createObjectBase("Tree");
258
+ const group = new THREE.Group();
259
+ Object.assign(group, base);
260
+ const mat1 = new THREE.MeshStandardMaterial({color:0x8B4513, roughness:0.9});
261
+ const mat2 = new THREE.MeshStandardMaterial({color:0x228B22, roughness:0.8});
262
+ const m1 = new THREE.Mesh(new THREE.CylinderGeometry(0.3,0.4,2,8), mat1);
263
+ m1.position.y = 1;
264
+ m1.castShadow = true;
265
+ m1.receiveShadow = true;
266
+ group.add(m1);
267
+ const m2 = new THREE.Mesh(new THREE.IcosahedronGeometry(1.2,0), mat2);
268
+ m2.position.y = 2.8;
269
+ m2.castShadow = true;
270
+ m2.receiveShadow = true;
271
+ group.add(m2);
272
+ return group;
273
+ }
274
+
275
+ function createRock() {
276
+ const base = createObjectBase("Rock");
277
+ const mat = new THREE.MeshStandardMaterial({color:0xaaaaaa, roughness:0.8, metalness:0.1});
278
+ const rock = new THREE.Mesh(new THREE.IcosahedronGeometry(0.7,0), mat);
279
+ Object.assign(rock, base);
280
+ rock.position.y = 0.35;
281
+ rock.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0);
282
+ rock.castShadow = true;
283
+ rock.receiveShadow = true;
284
+ return rock;
285
+ }
286
+
287
+ function createFencePost() {
288
+ const base = createObjectBase("Fence Post");
289
+ const mat = new THREE.MeshStandardMaterial({color:0xdeb887, roughness:0.9});
290
+ const post = new THREE.Mesh(new THREE.BoxGeometry(0.2,1.5,0.2), mat);
291
+ Object.assign(post, base);
292
+ post.position.y = 0.75;
293
+ post.castShadow = true;
294
+ post.receiveShadow = true;
295
+ return post;
296
+ }
297
+
298
+ function onMouseMove(event) {
299
+ mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
300
+ mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
301
+ }
302
+
303
+ function onDocumentClick(event) {
304
+ if (selectedObjectType === "None") return;
305
+ const groundCandidates = Object.values(groundMeshes);
306
+ if (groundCandidates.length === 0) return;
307
+ raycaster.setFromCamera(mouse, camera);
308
+ const intersects = raycaster.intersectObjects(groundCandidates);
309
+ if (intersects.length > 0) {
310
+ const intersectPoint = intersects[0].point;
311
+ let newObjectToPlace = null;
312
+ switch (selectedObjectType) {
313
+ case "Simple House": newObjectToPlace = createSimpleHouse(); break;
314
+ case "Tree": newObjectToPlace = createTree(); break;
315
+ case "Rock": newObjectToPlace = createRock(); break;
316
+ case "Fence Post": newObjectToPlace = createFencePost(); break;
317
+ default: return;
318
+ }
319
+ if (newObjectToPlace) {
320
+ newObjectToPlace.position.copy(intersectPoint);
321
+ newObjectToPlace.position.y += 0.01;
322
+ scene.add(newObjectToPlace);
323
+ newlyPlacedObjects.push(newObjectToPlace);
324
+ saveUnsavedState();
325
+ console.log(`Placed new ${selectedObjectType}. Total unsaved: ${newlyPlacedObjects.length}`);
326
+ }
327
+ }
328
+ }
329
+
330
+ function onKeyDown(event) { keysPressed[event.code] = true; }
331
+ function onKeyUp(event) { keysPressed[event.code] = false; }
332
+
333
+ function teleportPlayer(targetX, targetZ) {
334
+ console.log(`JS teleportPlayer called with targetX: ${targetX}, targetZ: ${targetZ}`);
335
+ if (playerMesh) {
336
+ playerMesh.position.x = targetX;
337
+ playerMesh.position.z = targetZ;
338
+ const offset = new THREE.Vector3(0, 15, 20);
339
+ const targetPosition = playerMesh.position.clone().add(offset);
340
+ camera.position.copy(targetPosition);
341
+ camera.lookAt(playerMesh.position);
342
+ console.log("Player teleported to:", playerMesh.position);
343
+ } else {
344
+ console.error("Player mesh not found for teleport.");
345
+ }
346
+ }
347
+
348
+ function getSaveDataAndPosition() {
349
+ console.log(`JS getSaveDataAndPosition called. Found ${newlyPlacedObjects.length} new objects.`);
350
+ const objectsToSave = newlyPlacedObjects.map(obj => {
351
+ if (!obj.userData || !obj.userData.type) { return null; }
352
+ const rotation = { _x: obj.rotation.x, _y: obj.rotation.y, _z: obj.rotation.z, _order: obj.rotation.order };
353
+ return {
354
+ obj_id: obj.userData.obj_id, type: obj.userData.type,
355
+ position: { x: obj.position.x, y: obj.position.y, z: obj.position.z },
356
+ rotation: rotation
357
+ };
358
+ }).filter(obj => obj !== null);
359
+ const playerPos = playerMesh ? { x: playerMesh.position.x, y: playerMesh.position.y, z: playerMesh.position.z } : {x:0, y:0, z:0};
360
+ const payload = {
361
+ playerPosition: playerPos,
362
+ objectsToSave: objectsToSave
363
+ };
364
+ console.log("Prepared payload for saving:", payload);
365
+ return JSON.stringify(payload);
366
+ }
367
+
368
+ function resetNewlyPlacedObjects() {
369
+ console.log(`JS resetNewlyPlacedObjects called.`);
370
+ clearUnsavedState();
371
+ }
372
+
373
+ function updatePlayerMovement() {
374
+ if (!playerMesh) return;
375
+ const moveDirection = new THREE.Vector3(0, 0, 0);
376
+ if (keysPressed['KeyW'] || keysPressed['ArrowUp']) moveDirection.z -= 1;
377
+ if (keysPressed['KeyS'] || keysPressed['ArrowDown']) moveDirection.z += 1;
378
+ if (keysPressed['KeyA'] || keysPressed['ArrowLeft']) moveDirection.x -= 1;
379
+ if (keysPressed['KeyD'] || keysPressed['ArrowRight']) moveDirection.x += 1;
380
+ if (moveDirection.lengthSq() > 0) {
381
+ moveDirection.normalize().multiplyScalar(playerSpeed);
382
+ const forward = new THREE.Vector3();
383
+ camera.getWorldDirection(forward);
384
+ forward.y = 0;
385
+ forward.normalize();
386
+ const right = new THREE.Vector3().crossVectors(camera.up, forward).normalize();
387
+ const worldMove = new THREE.Vector3();
388
+ worldMove.add(forward.multiplyScalar(-moveDirection.z));
389
+ worldMove.add(right.multiplyScalar(-moveDirection.x));
390
+ worldMove.normalize().multiplyScalar(playerSpeed);
391
+ playerMesh.position.add(worldMove);
392
+ playerMesh.position.y = Math.max(playerMesh.position.y, 0.4 + 0.8/2);
393
+ checkAndExpandGround();
394
+ }
395
+ }
396
+
397
+ function checkAndExpandGround() {
398
+ if (!playerMesh) return;
399
+ const currentGridX = Math.floor(playerMesh.position.x / plotWidth);
400
+ const currentGridZ = Math.floor(playerMesh.position.z / plotDepth);
401
+ for (let dx = -1; dx <= 1; dx++) {
402
+ for (let dz = -1; dz <= 1; dz++) {
403
+ if (dx === 0 && dz === 0) continue;
404
+ const checkX = currentGridX + dx;
405
+ const checkZ = currentGridZ + dz;
406
+ const gridKey = `${checkX}_${checkZ}`;
407
+ if (!groundMeshes[gridKey]) {
408
+ const isSavedPlot = plotsMetadata.some(plot => plot.grid_x === checkX && plot.grid_z === checkZ);
409
+ if (!isSavedPlot) {
410
+ createGroundPlane(checkX, checkZ, true);
411
+ }
412
+ }
413
+ }
414
+ }
415
+ }
416
+
417
+ function updateCamera() {
418
+ if (!playerMesh) return;
419
+ const offset = new THREE.Vector3(0, 15, 20);
420
+ const targetPosition = playerMesh.position.clone().add(offset);
421
+ camera.position.lerp(targetPosition, 0.08);
422
+ camera.lookAt(playerMesh.position);
423
+ }
424
+
425
+ function onWindowResize() {
426
+ camera.aspect = window.innerWidth / window.innerHeight;
427
+ camera.updateProjectionMatrix();
428
+ renderer.setSize(window.innerWidth, window.innerHeight);
429
+ }
430
+
431
+ function animate() {
432
+ requestAnimationFrame(animate);
433
+ updatePlayerMovement();
434
+ updateCamera();
435
+ renderer.render(scene, camera);
436
+ }
437
+
438
+ init();
439
+ </script>
440
+ </body>
441
+ </html>