awacke1 commited on
Commit
b23db76
Β·
verified Β·
1 Parent(s): f265ff5

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +435 -181
index.html CHANGED
@@ -1,187 +1,441 @@
1
  <!DOCTYPE html>
2
- <html lang="en">
3
  <head>
4
- <meta charset="UTF-8" />
5
- <title>Infinite World</title>
6
- <style>
7
- body { margin:0; overflow:hidden; }
8
- canvas { display:block; }
9
- </style>
10
-
11
- <script type="module">
12
- import * as THREE from 'three';
13
- // … your existing three.js init, camera, lights, renderer, controls …
14
-
15
- // ─── Primitive helpers ──────────────────────────────────────────────────
16
- function createBox({width, height, depth, materialProps, position}) {
17
- const geom = new THREE.BoxGeometry(width, height, depth);
18
- const mat = new THREE.MeshStandardMaterial(materialProps);
19
- const mesh = new THREE.Mesh(geom, mat);
20
- mesh.position.copy(position);
21
- return mesh;
22
- }
23
- // … any other helpers like createPlane(), createSplineTube(), createParticles() …
24
-
25
- // ─── Existing simple creators ───────────────────────────────────────────
26
- // function createSimpleHouse(...) { … }
27
- // function createTree(...) { … }
28
- // function createRock(...) { … }
29
- // function createFencePost(...) { … }
30
-
31
- // ─── 1. Cyberpunk City Builder Kit ────────────────────────────────────────
32
- function createCyberpunkWallPanel({
33
- width=2, height=3, depth=0.2,
34
- baseMaterial={ color:0x555555, metalness:0.8, roughness:0.4 },
35
- trimMaterial={ emissive:0x00ffff, emissiveIntensity:1.2 },
36
- windowCutouts=true, doorCutouts=false,
37
- position=new THREE.Vector3()
38
- }) {
39
- const panel = createBox({ width, height, depth, materialProps: baseMaterial, position });
40
- // trim
41
- const trim = createBox({
42
- width: width * 0.1, height: height, depth: depth + 0.01,
43
- materialProps: trimMaterial,
44
- position: position.clone().add(new THREE.Vector3(width/2 + 0.05, 0, 0))
45
- });
46
- panel.add(trim);
47
- // TODO: boolean cutouts for windows/doors
48
- return panel;
49
- }
50
-
51
- function createRooftopACUnit({
52
- width=1, height=0.5, depth=1,
53
- materialProps={ color:0x777777, roughness:0.7, metalness:0.3 },
54
- position=new THREE.Vector3()
55
- }) {
56
- const unit = createBox({ width, height, depth, materialProps, position });
57
- // grill detail, pipes, etc.
58
- return unit;
59
- }
60
-
61
- function createHolographicWindowDisplay({
62
- width=1.5, height=1, position=new THREE.Vector3()
63
- }) {
64
- const planeMat = new THREE.MeshBasicMaterial({
65
- color: 0xffffff, transparent:true, opacity:0.6
66
- });
67
- const geom = new THREE.PlaneGeometry(width, height);
68
- const display = new THREE.Mesh(geom, planeMat);
69
- display.position.copy(position);
70
- // animate emissive shader parameter here…
71
- return display;
72
- }
73
-
74
- function createGreebleSet({
75
- count=10, scale=0.2, position=new THREE.Vector3()
76
- }) {
77
- const group = new THREE.Group();
78
- for(let i=0; i<count; i++){
79
- const x = (Math.random()-0.5)*2;
80
- const y = (Math.random()-0.5)*2;
81
- const z = (Math.random()-0.5)*0.2;
82
- const pebble = createBox({
83
- width: scale, height: scale, depth: scale,
84
- materialProps:{ color:0x444444, roughness:0.6, metalness:0.4 },
85
- position: position.clone().add(new THREE.Vector3(x,y,z))
86
- });
87
- group.add(pebble);
88
- }
89
- return group;
90
- }
91
-
92
- function createCyberpunkKit(position=new THREE.Vector3()) {
93
- const kit = new THREE.Group();
94
- kit.add(createCyberpunkWallPanel({ position: position.clone() }));
95
- kit.add(createRooftopACUnit({ position: position.clone().add(new THREE.Vector3(3,0,0)) }));
96
- kit.add(createHolographicWindowDisplay({ position: position.clone().add(new THREE.Vector3(6,1,0)) }));
97
- kit.add(createGreebleSet({ position: position.clone().add(new THREE.Vector3(9,0,0)) }));
98
- scene.add(kit);
99
- }
100
-
101
- // ─── 2. POLYGON – Fantasy Kingdom Pack ───────────────────────────────────
102
- function createKingFigure({
103
- scale=1, materialProps={ color:0xffd700, metalness:0.9, roughness:0.2 },
104
- position=new THREE.Vector3()
105
- }) {
106
- // placeholder low‐poly silhouette
107
- const geom = new THREE.CylinderGeometry(0.3*scale, 0.5*scale, 1.8*scale, 6);
108
- const mesh = new THREE.Mesh(geom, new THREE.MeshStandardMaterial(materialProps));
109
- mesh.position.copy(position);
110
- return mesh;
111
- }
112
- function createSoldierFigure({ position=new THREE.Vector3() }) { /*…*/ }
113
- function createMageFigure({ position=new THREE.Vector3() }) { /*…*/ }
114
-
115
- function createFantasyKingdomPack(position=new THREE.Vector3()) {
116
- const pack = new THREE.Group();
117
- pack.add(createKingFigure({ position }));
118
- pack.add(createSoldierFigure({ position: position.clone().add(new THREE.Vector3(2,0,0)) }));
119
- pack.add(createMageFigure({ position: position.clone().add(new THREE.Vector3(4,0,0)) }));
120
- scene.add(pack);
121
- }
122
-
123
- // ─── 3. HEROIC FANTASY CREATURES FULL PACK VOLΒ 1 ─────────────────────────
124
- function createDemonicCreatureBase({ position=new THREE.Vector3() }) { /*…*/ }
125
- function createFantasyAnimalBase({ position=new THREE.Vector3() }) { /*…*/ }
126
- function createFantasyLizardBase({ position=new THREE.Vector3() }) { /*…*/ }
127
- function createLivingDeadBase({ position=new THREE.Vector3() }) { /*…*/ }
128
- function createFantasyVillainBase({ position=new THREE.Vector3() }) { /*…*/ }
129
- function createMythologicalCreatureBase({ position=new THREE.Vector3() }) { /*…*/ }
130
-
131
- function createHeroicFantasyCreaturesPack(position=new THREE.Vector3()) {
132
- const grp = new THREE.Group();
133
- grp.add(createDemonicCreatureBase({ position }));
134
- grp.add(createFantasyAnimalBase({ position: position.clone().add(new THREE.Vector3(3,0,0)) }));
135
- grp.add(createFantasyLizardBase({ position: position.clone().add(new THREE.Vector3(6,0,0)) }));
136
- grp.add(createLivingDeadBase({ position: position.clone().add(new THREE.Vector3(9,0,0)) }));
137
- grp.add(createFantasyVillainBase({ position: position.clone().add(new THREE.Vector3(12,0,0)) }));
138
- grp.add(createMythologicalCreatureBase({ position: position.clone().add(new THREE.Vector3(15,0,0)) }));
139
- scene.add(grp);
140
- }
141
-
142
- // ─── 4. POLYGON – Apocalypse Pack ─────────────────────────────────────────
143
- function createZombieFigure({ position=new THREE.Vector3() }) { /*…*/ }
144
- function createSurvivorFigure({ position=new THREE.Vector3() }) { /*…*/ }
145
- function createBackpack({ position=new THREE.Vector3() }) { /*…*/ }
146
- function createMakeshiftArmor({ position=new THREE.Vector3() }) { /*…*/ }
147
- function createBuggyFrame({ position=new THREE.Vector3() }) { /*…*/ }
148
-
149
- function createApocalypsePack(position=new THREE.Vector3()) {
150
- const pack = new THREE.Group();
151
- pack.add(createZombieFigure({ position }));
152
- pack.add(createSurvivorFigure({ position: position.clone().add(new THREE.Vector3(3,0,0)) }));
153
- pack.add(createBackpack({ position: position.clone().add(new THREE.Vector3(6,0,0)) }));
154
- pack.add(createMakeshiftArmor({ position: position.clone().add(new THREE.Vector3(9,0,0)) }));
155
- pack.add(createBuggyFrame({ position: position.clone().add(new THREE.Vector3(12,0,0)) }));
156
- scene.add(pack);
157
- }
158
-
159
- // ─── Hook into your existing object‐placement switch ─────────────────────
160
- window.placeObject = function(type, position) {
161
- switch(type) {
162
- case 'Cyberpunk City Builder Kit':
163
- createCyberpunkKit(position);
164
- break;
165
- case 'POLYGON - Fantasy Kingdom Pack':
166
- createFantasyKingdomPack(position);
167
- break;
168
- case 'HEROIC FANTASY CREATURES FULL PACKΒ VOLΒ 1':
169
- createHeroicFantasyCreaturesPack(position);
170
- break;
171
- case 'POLYGON - Apocalypse Pack':
172
- createApocalypsePack(position);
173
- break;
174
-
175
- // … your existing cases for Simple House, Tree, etc. …
176
- }
177
- };
178
-
179
- // ─── Initialize scene ───────────────────────────────────────────────────
180
- init();
181
- </script>
182
  </head>
183
  <body>
184
- <canvas id="canvas"></canvas>
185
- <!-- your existing teleportPlayer, getSaveDataAndPosition hooks, etc. -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  </body>
187
  </html>
 
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>