awacke1 commited on
Commit
9cb2144
·
verified ·
1 Parent(s): 434590c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +23 -380
index.html CHANGED
@@ -6,13 +6,22 @@
6
  body { margin: 0; overflow: hidden; }
7
  canvas { display: block; }
8
  </style>
9
- </head>
 
 
 
 
 
 
 
 
 
10
  <body>
11
  <script type="importmap">
12
  {
13
  "imports": {
14
  "three": "https://unpkg.com/[email protected]/build/three.module.js",
15
- "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
16
  }
17
  }
18
  </script>
@@ -25,403 +34,37 @@
25
  const keysPressed = {};
26
  const playerSpeed = 0.15;
27
  let newlyPlacedObjects = []; // Track objects added THIS session for saving
28
- const placeholderPlots = new Set(); // Track visually created placeholder grounds: 'x_z' string key
29
- const groundMeshes = {}; // Store references to ground meshes: 'x_z' string key -> mesh
30
 
31
  // --- Session Storage Key ---
32
  const SESSION_STORAGE_KEY = 'unsavedInfiniteWorldState';
33
 
34
- // --- Access State from Streamlit ---
35
  const allInitialObjects = window.ALL_INITIAL_OBJECTS || [];
36
- const plotsMetadata = window.PLOTS_METADATA || []; // List of saved plot info
37
  const selectedObjectType = window.SELECTED_OBJECT_TYPE || "None";
38
  const plotWidth = window.PLOT_WIDTH || 50.0;
39
- const plotDepth = window.PLOT_DEPTH || 50.0; // Use plot depth
40
 
41
- const groundMaterial = new THREE.MeshStandardMaterial({ // Reusable material
42
- color: 0x55aa55, roughness: 0.9, metalness: 0.1, side: THREE.DoubleSide
43
  });
44
- const placeholderGroundMaterial = new THREE.MeshStandardMaterial({ // Dimmer for placeholders
45
- color: 0x448844, roughness: 0.95, metalness: 0.1, side: THREE.DoubleSide
46
  });
47
 
48
-
49
  function init() {
50
  scene = new THREE.Scene();
51
  scene.background = new THREE.Color(0xabcdef);
52
-
53
  const aspect = window.innerWidth / window.innerHeight;
54
- camera = new THREE.PerspectiveCamera(60, aspect, 0.1, 4000); // Increase far plane more
55
  camera.position.set(0, 15, 20);
56
  camera.lookAt(0, 0, 0);
57
  scene.add(camera);
58
 
59
  setupLighting();
60
- setupInitialGround(); // Setup ground for existing plots ONLY initially
61
  setupPlayer();
62
 
63
- raycaster = new THREE.Raycaster();
64
- mouse = new THREE.Vector2();
65
-
66
- renderer = new THREE.WebGLRenderer({ antialias: true });
67
- renderer.setSize(window.innerWidth, window.innerHeight);
68
- renderer.shadowMap.enabled = true;
69
- renderer.shadowMap.type = THREE.PCFSoftShadowMap;
70
- document.body.appendChild(renderer.domElement);
71
-
72
- loadInitialObjects();
73
- restoreUnsavedState(); // Restore unsaved objects after initial load
74
-
75
- // Event Listeners
76
- document.addEventListener('mousemove', onMouseMove, false);
77
- document.addEventListener('click', onDocumentClick, false);
78
- window.addEventListener('resize', onWindowResize, false);
79
- document.addEventListener('keydown', onKeyDown);
80
- document.addEventListener('keyup', onKeyUp);
81
-
82
- // Define global functions needed by Python
83
- window.teleportPlayer = teleportPlayer;
84
- window.getSaveDataAndPosition = getSaveDataAndPosition; // Renamed JS function
85
- window.resetNewlyPlacedObjects = resetNewlyPlacedObjects;
86
-
87
- console.log("Three.js Initialized. World ready.");
88
- animate();
89
- }
90
-
91
- function setupLighting() { /* ... unchanged ... */
92
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
93
- scene.add(ambientLight);
94
- const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
95
- directionalLight.position.set(50, 150, 100); // Higher and angled light
96
- directionalLight.castShadow = true;
97
- directionalLight.shadow.mapSize.width = 4096; // Increase shadow map size
98
- directionalLight.shadow.mapSize.height = 4096;
99
- directionalLight.shadow.camera.near = 0.5;
100
- directionalLight.shadow.camera.far = 500;
101
- // Dynamic frustum needed for large worlds, but keep wide for now
102
- directionalLight.shadow.camera.left = -150;
103
- directionalLight.shadow.camera.right = 150;
104
- directionalLight.shadow.camera.top = 150;
105
- directionalLight.shadow.camera.bottom = -150;
106
- directionalLight.shadow.bias = -0.001;
107
- scene.add(directionalLight);
108
- }
109
-
110
- function setupInitialGround() {
111
- // Create ground ONLY for plots defined in plotsMetadata
112
- console.log(`Setting up initial ground for ${plotsMetadata.length} plots.`);
113
- plotsMetadata.forEach(plot => {
114
- createGroundPlane(plot.grid_x, plot.grid_z, false); // false = not a placeholder
115
- });
116
- // Create a small initial ground at 0,0 if no plots exist yet
117
- if (plotsMetadata.length === 0) {
118
- createGroundPlane(0, 0, false);
119
- }
120
- }
121
-
122
- // *** NEW: Function to create a ground plane for a specific grid cell ***
123
- function createGroundPlane(gridX, gridZ, isPlaceholder) {
124
- const gridKey = `${gridX}_${gridZ}`;
125
- if (groundMeshes[gridKey]) return; // Don't recreate if it exists
126
-
127
- console.log(`Creating ${isPlaceholder ? 'placeholder' : 'initial'} ground at ${gridX}, ${gridZ}`);
128
- const groundGeometry = new THREE.PlaneGeometry(plotWidth, plotDepth);
129
- const material = isPlaceholder ? placeholderGroundMaterial : groundMaterial;
130
- const groundMesh = new THREE.Mesh(groundGeometry, material);
131
-
132
- groundMesh.rotation.x = -Math.PI / 2;
133
- groundMesh.position.y = -0.05;
134
- // Position the center of the plane correctly
135
- groundMesh.position.x = gridX * plotWidth + plotWidth / 2.0;
136
- groundMesh.position.z = gridZ * plotDepth + plotDepth / 2.0;
137
-
138
- groundMesh.receiveShadow = true;
139
- groundMesh.userData.gridKey = gridKey; // Store key for potential removal/update
140
- scene.add(groundMesh);
141
- groundMeshes[gridKey] = groundMesh; // Store reference
142
- if (isPlaceholder) {
143
- placeholderPlots.add(gridKey); // Track placeholders
144
- }
145
- }
146
-
147
- function setupPlayer() { /* ... unchanged ... */
148
- const playerGeo = new THREE.CapsuleGeometry(0.4, 0.8, 4, 8);
149
- const playerMat = new THREE.MeshStandardMaterial({ color: 0x0000ff, roughness: 0.6 });
150
- playerMesh = new THREE.Mesh(playerGeo, playerMat);
151
- playerMesh.position.set(plotWidth / 2, 0.4 + 0.8/2, plotDepth/2); // Start in center of 0,0 plot
152
- playerMesh.castShadow = true; playerMesh.receiveShadow = true;
153
- scene.add(playerMesh);
154
- }
155
-
156
- function loadInitialObjects() { /* ... unchanged, uses createAndPlaceObject ... */
157
- console.log(`Loading ${allInitialObjects.length} initial objects from Python.`);
158
- allInitialObjects.forEach(objData => { createAndPlaceObject(objData, false); });
159
- console.log("Finished loading initial objects.");
160
- }
161
- function createAndPlaceObject(objData, isNewObject) { /* ... unchanged ... */
162
- let loadedObject = null;
163
- switch (objData.type) {
164
- case "Simple House": loadedObject = createSimpleHouse(); break;
165
- case "Tree": loadedObject = createTree(); break;
166
- case "Rock": loadedObject = createRock(); break;
167
- case "Fence Post": loadedObject = createFencePost(); break;
168
- default: console.warn("Unknown object type in data:", objData.type); break;
169
- }
170
- if (loadedObject) {
171
- if (objData.position && objData.position.x !== undefined) {
172
- loadedObject.position.set(objData.position.x, objData.position.y, objData.position.z);
173
- } else if (objData.pos_x !== undefined) {
174
- loadedObject.position.set(objData.pos_x, objData.pos_y, objData.pos_z);
175
- }
176
- if (objData.rotation) {
177
- loadedObject.rotation.set(objData.rotation._x, objData.rotation._y, objData.rotation._z, objData.rotation._order || 'XYZ');
178
- } else if (objData.rot_x !== undefined) {
179
- loadedObject.rotation.set(objData.rot_x, objData.rot_y, objData.rot_z, objData.rot_order || 'XYZ');
180
- }
181
- loadedObject.userData.obj_id = objData.obj_id || loadedObject.userData.obj_id;
182
- scene.add(loadedObject);
183
- if (isNewObject) { newlyPlacedObjects.push(loadedObject); }
184
- return loadedObject;
185
- }
186
- return null;
187
- }
188
- function saveUnsavedState() { /* ... unchanged ... */
189
- try {
190
- const stateToSave = newlyPlacedObjects.map(obj => ({ obj_id: obj.userData.obj_id, type: obj.userData.type, position: { x: obj.position.x, y: obj.position.y, z: obj.position.z }, rotation: { _x: obj.rotation.x, _y: obj.rotation.y, _z: obj.rotation.z, _order: obj.rotation.order } }));
191
- sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(stateToSave));
192
- console.log(`Saved ${stateToSave.length} unsaved objects to sessionStorage.`);
193
- } catch (e) { console.error("Error saving state to sessionStorage:", e); }
194
- }
195
- function restoreUnsavedState() { /* ... unchanged ... */
196
- try {
197
- const savedState = sessionStorage.getItem(SESSION_STORAGE_KEY);
198
- if (savedState) {
199
- console.log("Found unsaved state in sessionStorage. Restoring...");
200
- const objectsToRestore = JSON.parse(savedState);
201
- if (Array.isArray(objectsToRestore)) {
202
- newlyPlacedObjects = []; let count = 0;
203
- objectsToRestore.forEach(objData => { if(createAndPlaceObject(objData, true)) { count++; } });
204
- console.log(`Restored ${count} objects.`);
205
- }
206
- } else { console.log("No unsaved state found in sessionStorage."); }
207
- } catch (e) { console.error("Error restoring state from sessionStorage:", e); sessionStorage.removeItem(SESSION_STORAGE_KEY); }
208
- }
209
- function clearUnsavedState() { /* ... unchanged ... */
210
- sessionStorage.removeItem(SESSION_STORAGE_KEY);
211
- newlyPlacedObjects = [];
212
- console.log("Cleared unsaved state from memory and sessionStorage.");
213
- }
214
- function createObjectBase(type) { /* ... unchanged ... */
215
- return { userData: { type: type, obj_id: THREE.MathUtils.generateUUID() } };
216
- }
217
- function createSimpleHouse() { /* ... unchanged ... */
218
- const base = createObjectBase("Simple House"); const group = new THREE.Group(); Object.assign(group, base);
219
- const mat1=new THREE.MeshStandardMaterial({color:0xffccaa,roughness:0.8}), mat2=new THREE.MeshStandardMaterial({color:0xaa5533,roughness:0.7});
220
- const m1=new THREE.Mesh(new THREE.BoxGeometry(2,1.5,2.5),mat1); m1.position.y=1.5/2;m1.castShadow=true;m1.receiveShadow=true;group.add(m1);
221
- const m2=new THREE.Mesh(new THREE.ConeGeometry(1.8,1,4),mat2); m2.position.y=1.5+1/2;m2.rotation.y=Math.PI/4;m2.castShadow=true;m2.receiveShadow=true;group.add(m2); return group;
222
- }
223
- function createTree() { /* ... unchanged ... */
224
- const base=createObjectBase("Tree"); const group=new THREE.Group(); Object.assign(group,base);
225
- const mat1=new THREE.MeshStandardMaterial({color:0x8B4513,roughness:0.9}), mat2=new THREE.MeshStandardMaterial({color:0x228B22,roughness:0.8});
226
- 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);
227
- const m2=new THREE.Mesh(new THREE.IcosahedronGeometry(1.2,0),mat2); m2.position.y=2.8; m2.castShadow=true;m2.receiveShadow=true;group.add(m2); return group;
228
- }
229
- function createRock() { /* ... unchanged ... */
230
- const base=createObjectBase("Rock"); const mat=new THREE.MeshStandardMaterial({color:0xaaaaaa,roughness:0.8,metalness:0.1});
231
- const rock=new THREE.Mesh(new THREE.IcosahedronGeometry(0.7,0),mat); Object.assign(rock,base);
232
- rock.position.y=0.35; rock.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0); rock.castShadow=true;rock.receiveShadow=true; return rock;
233
- }
234
- function createFencePost() { /* ... unchanged ... */
235
- const base=createObjectBase("Fence Post"); const mat=new THREE.MeshStandardMaterial({color:0xdeb887,roughness:0.9});
236
- const post=new THREE.Mesh(new THREE.BoxGeometry(0.2,1.5,0.2),mat); Object.assign(post,base);
237
- post.position.y=0.75; post.castShadow=true;post.receiveShadow=true; return post;
238
- }
239
-
240
- // --- Event Handlers ---
241
- function onMouseMove(event) { /* ... unchanged ... */
242
- mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
243
- mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
244
- }
245
-
246
- function onDocumentClick(event) { // Place object and save state
247
- if (selectedObjectType === "None") return;
248
- // Determine ground mesh to intersect (could be multiple now)
249
- const groundCandidates = Object.values(groundMeshes);
250
- if (groundCandidates.length === 0) return; // No ground to place on
251
-
252
- raycaster.setFromCamera(mouse, camera);
253
- // Intersect ALL ground meshes
254
- const intersects = raycaster.intersectObjects(groundCandidates);
255
-
256
- if (intersects.length > 0) {
257
- // Intersect point is on the specific ground mesh clicked
258
- const intersectPoint = intersects[0].point;
259
- let newObjectToPlace = null;
260
-
261
- switch (selectedObjectType) { /* ... create object ... */
262
- case "Simple House": newObjectToPlace = createSimpleHouse(); break;
263
- case "Tree": newObjectToPlace = createTree(); break;
264
- case "Rock": newObjectToPlace = createRock(); break;
265
- case "Fence Post": newObjectToPlace = createFencePost(); break;
266
- default: return;
267
- }
268
-
269
- if (newObjectToPlace) {
270
- newObjectToPlace.position.copy(intersectPoint);
271
- // Adjust Y position if needed based on object geometry origin
272
- // Base objects on y=0 in create functions for simplicity
273
- if(newObjectToPlace.geometry?.type.includes("Geometry")){ // Adjust Y so base is on ground
274
- newObjectToPlace.position.y += 0.01; // Slight offset above ground
275
- } else if (newObjectToPlace.type === "Group") {
276
- newObjectToPlace.position.y += 0.01; // Assume group base is near 0
277
- }
278
-
279
- scene.add(newObjectToPlace);
280
- newlyPlacedObjects.push(newObjectToPlace);
281
- saveUnsavedState(); // Save to sessionStorage immediately
282
- console.log(`Placed new ${selectedObjectType}. Total unsaved: ${newlyPlacedObjects.length}`);
283
- }
284
- }
285
- }
286
-
287
- function onKeyDown(event) { keysPressed[event.code] = true; }
288
- function onKeyUp(event) { keysPressed[event.code] = false; }
289
-
290
- // --- Functions called by Python via streamlit-js-eval ---
291
- function teleportPlayer(targetX, targetZ) { // Now accepts Z coordinate
292
- console.log(`JS teleportPlayer called with targetX: ${targetX}, targetZ: ${targetZ}`);
293
- if (playerMesh) {
294
- // Teleport near center of the target plot
295
- playerMesh.position.x = targetX;
296
- playerMesh.position.z = targetZ;
297
- const offset = new THREE.Vector3(0, 15, 20);
298
- const targetPosition = playerMesh.position.clone().add(offset);
299
- camera.position.copy(targetPosition);
300
- camera.lookAt(playerMesh.position);
301
- console.log("Player teleported to:", playerMesh.position);
302
- } else { console.error("Player mesh not found for teleport."); }
303
- }
304
-
305
- // *** UPDATED: Send player position along with save data ***
306
- function getSaveDataAndPosition() {
307
- console.log(`JS getSaveDataAndPosition called. Found ${newlyPlacedObjects.length} new objects.`);
308
- const objectsToSave = newlyPlacedObjects.map(obj => {
309
- if (!obj.userData || !obj.userData.type) { return null; }
310
- const rotation = { _x: obj.rotation.x, _y: obj.rotation.y, _z: obj.rotation.z, _order: obj.rotation.order };
311
- return { // Send WORLD positions
312
- obj_id: obj.userData.obj_id, type: obj.userData.type,
313
- position: { x: obj.position.x, y: obj.position.y, z: obj.position.z },
314
- rotation: rotation
315
- };
316
- }).filter(obj => obj !== null);
317
-
318
- const playerPos = playerMesh ? { x: playerMesh.position.x, y: playerMesh.position.y, z: playerMesh.position.z } : {x:0, y:0, z:0};
319
-
320
- const payload = {
321
- playerPosition: playerPos,
322
- objectsToSave: objectsToSave
323
- };
324
- console.log("Prepared payload for saving:", payload);
325
- return JSON.stringify(payload); // Return as JSON string
326
- }
327
-
328
- function resetNewlyPlacedObjects() { // Called by Python AFTER successful save
329
- console.log(`JS resetNewlyPlacedObjects called.`);
330
- clearUnsavedState(); // Clear memory array AND sessionStorage
331
- }
332
-
333
-
334
- // --- Animation Loop ---
335
- function updatePlayerMovement() {
336
- if (!playerMesh) return;
337
- const moveDirection = new THREE.Vector3(0, 0, 0);
338
- // Basic WASD movement
339
- if (keysPressed['KeyW'] || keysPressed['ArrowUp']) moveDirection.z -= 1;
340
- if (keysPressed['KeyS'] || keysPressed['ArrowDown']) moveDirection.z += 1;
341
- if (keysPressed['KeyA'] || keysPressed['ArrowLeft']) moveDirection.x -= 1;
342
- if (keysPressed['KeyD'] || keysPressed['ArrowRight']) moveDirection.x += 1;
343
-
344
- if (moveDirection.lengthSq() > 0) {
345
- moveDirection.normalize().multiplyScalar(playerSpeed);
346
- // Apply movement relative to camera direction for better control
347
- const forward = new THREE.Vector3();
348
- camera.getWorldDirection(forward);
349
- forward.y = 0; // Project onto XZ plane
350
- forward.normalize();
351
- const right = new THREE.Vector3().crossVectors(camera.up, forward).normalize(); // Get right vector
352
-
353
- const worldMove = new THREE.Vector3();
354
- worldMove.add(forward.multiplyScalar(-moveDirection.z)); // W/S -> Forward/Backward
355
- worldMove.add(right.multiplyScalar(-moveDirection.x)); // A/D -> Left/Right
356
- worldMove.normalize().multiplyScalar(playerSpeed);
357
-
358
- playerMesh.position.add(worldMove);
359
-
360
- // Basic ground clamping
361
- playerMesh.position.y = Math.max(playerMesh.position.y, 0.4 + 0.8/2); // Adjust based on capsule base
362
-
363
- // *** ADDED: Check for ground expansion ***
364
- checkAndExpandGround();
365
- }
366
- }
367
-
368
- // *** NEW: Check if player is near edge and create placeholder ground ***
369
- function checkAndExpandGround() {
370
- if (!playerMesh) return;
371
-
372
- const currentGridX = Math.floor(playerMesh.position.x / plotWidth);
373
- const currentGridZ = Math.floor(playerMesh.position.z / plotDepth);
374
-
375
- // Check surrounding cells (Manhattan distance 1)
376
- for (let dx = -1; dx <= 1; dx++) {
377
- for (let dz = -1; dz <= 1; dz++) {
378
- if (dx === 0 && dz === 0) continue; // Skip current cell
379
-
380
- const checkX = currentGridX + dx;
381
- const checkZ = currentGridZ + dz;
382
- const gridKey = `${checkX}_${checkZ}`;
383
-
384
- // Check if this grid cell already has ground (initial or placeholder)
385
- if (!groundMeshes[gridKey]) {
386
- // Check if this grid cell corresponds to a SAVED plot (from metadata)
387
- const isSavedPlot = plotsMetadata.some(plot => plot.grid_x === checkX && plot.grid_z === checkZ);
388
-
389
- // If it's NOT a saved plot, create a placeholder
390
- if (!isSavedPlot) {
391
- createGroundPlane(checkX, checkZ, true); // true = is placeholder
392
- }
393
- // If it IS a saved plot but somehow missing ground (error?), recreate it?
394
- // else { createGroundPlane(checkX, checkZ, false); } // Optional robustness
395
- }
396
- }
397
- }
398
- }
399
-
400
-
401
- function updateCamera() { /* ... unchanged ... */
402
- if (!playerMesh) return;
403
- const offset = new THREE.Vector3(0, 15, 20); // Fixed offset for now
404
- const targetPosition = playerMesh.position.clone().add(offset);
405
- camera.position.lerp(targetPosition, 0.08);
406
- camera.lookAt(playerMesh.position);
407
- }
408
-
409
- function onWindowResize() { /* ... unchanged ... */
410
- camera.aspect = window.innerWidth / window.innerHeight;
411
- camera.updateProjectionMatrix();
412
- renderer.setSize(window.innerWidth, window.innerHeight);
413
- }
414
-
415
- function animate() {
416
- requestAnimationFrame(animate);
417
- updatePlayerMovement(); // Includes ground check now
418
- updateCamera();
419
- renderer.render(scene, camera);
420
- }
421
-
422
- // --- Start ---
423
- init();
424
-
425
- </script>
426
- </body>
427
- </html>
 
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>
 
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