awacke1 commited on
Commit
0c62314
·
verified ·
1 Parent(s): 9f9622d

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1487 -238
index.html CHANGED
@@ -1,10 +1,1313 @@
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>
@@ -17,6 +1320,10 @@
17
  </script>
18
  </head>
19
  <body>
 
 
 
 
20
  <script type="importmap">
21
  {
22
  "imports": {
@@ -44,15 +1351,81 @@
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();
@@ -89,6 +1462,7 @@
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.");
@@ -128,7 +1502,7 @@
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;
@@ -157,34 +1531,6 @@
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 => ({
@@ -233,209 +1579,112 @@
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>
 
1
+ const innerFlameGeo = new THREE.ConeGeometry(0.05, 0.15, 8);
2
+ const innerFlameMat = new THREE.MeshStandardMaterial({
3
+ color: 0xFFFF00,
4
+ roughness: 0.2,
5
+ metalness: 0.0,
6
+ emissive: 0xFFFF00,
7
+ emissiveIntensity: 1.0
8
+ });
9
+ const innerFlame = new THREE.Mesh(innerFlameGeo, innerFlameMat);
10
+ innerFlame.position.set(0, 0.04, 0);
11
+ flame.add(innerFlame);
12
+
13
+ group.position.y = 1.5; // Mount height
14
+ return group;
15
+ }
16
+
17
+ function createBonePile() {
18
+ const base = createObjectBase("Bone Pile");
19
+ const group = new THREE.Group();
20
+ Object.assign(group, base);
21
+
22
+ // Create various bones
23
+ for (let i = 0; i < 8; i++) {
24
+ let bone;
25
+ // Choose bone type
26
+ const boneType = Math.floor(Math.random() * 3);
27
+
28
+ if (boneType === 0) { // Long bone
29
+ const boneGeo = new THREE.CylinderGeometry(0.06, 0.08, 0.5 + Math.random() * 0.3, 8);
30
+ bone = new THREE.Mesh(boneGeo, materials.bone);
31
+ } else if (boneType === 1) { // Skull-like
32
+ const boneGeo = new THREE.SphereGeometry(0.15 + Math.random() * 0.1, 8, 8);
33
+ bone = new THREE.Mesh(boneGeo, materials.bone);
34
+ } else { // Smaller bone fragment
35
+ const boneGeo = new THREE.BoxGeometry(
36
+ 0.1 + Math.random() * 0.1,
37
+ 0.05 + Math.random() * 0.05,
38
+ 0.2 + Math.random() * 0.1
39
+ );
40
+ bone = new THREE.Mesh(boneGeo, materials.bone);
41
+ }
42
+
43
+ // Position randomly within a circle
44
+ const angle = Math.random() * Math.PI * 2;
45
+ const distance = Math.random() * 0.6;
46
+ bone.position.set(
47
+ Math.sin(angle) * distance,
48
+ Math.random() * 0.2,
49
+ Math.cos(angle) * distance
50
+ );
51
+
52
+ // Random rotation
53
+ bone.rotation.set(
54
+ Math.random() * Math.PI,
55
+ Math.random() * Math.PI,
56
+ Math.random() * Math.PI
57
+ );
58
+
59
+ bone.castShadow = true;
60
+ bone.receiveShadow = true;
61
+ group.add(bone);
62
+ }
63
+
64
+ // Add a skull as centerpiece
65
+ const skullGeo = new THREE.SphereGeometry(0.2, 8, 8);
66
+ const skull = new THREE.Mesh(skullGeo, materials.bone);
67
+ skull.scale.set(1, 0.8, 1.2);
68
+ skull.position.set(0, 0.15, 0);
69
+ skull.rotation.x = Math.PI/6;
70
+ skull.castShadow = true;
71
+ skull.receiveShadow = true;
72
+ group.add(skull);
73
+
74
+ // Eye sockets
75
+ const socketGeo = new THREE.SphereGeometry(0.06, 8, 8);
76
+ const blackMat = new THREE.MeshBasicMaterial({color: 0x000000});
77
+
78
+ const leftEye = new THREE.Mesh(socketGeo, blackMat);
79
+ leftEye.position.set(-0.07, 0.05, 0.15);
80
+ skull.add(leftEye);
81
+
82
+ const rightEye = new THREE.Mesh(socketGeo, blackMat);
83
+ rightEye.position.set(0.07, 0.05, 0.15);
84
+ skull.add(rightEye);
85
+
86
+ group.position.y = 0.05; // Slightly above ground
87
+ return group;
88
+ }
89
+
90
+ // =================== CHARACTERS CATEGORY ===================
91
+
92
+ function createKingFigure() {
93
+ const base = createObjectBase("King Figure");
94
+ const group = new THREE.Group();
95
+ Object.assign(group, base);
96
+
97
+ // Body
98
+ const bodyGeo = new THREE.CylinderGeometry(0.35, 0.25, 1.2, 8);
99
+ const body = new THREE.Mesh(bodyGeo, materials.cloth);
100
+ body.position.y = 0.6;
101
+ body.castShadow = true;
102
+ body.receiveShadow = true;
103
+ group.add(body);
104
+
105
+ // Head
106
+ const headGeo = new THREE.SphereGeometry(0.25, 8, 8);
107
+ const head = new THREE.Mesh(headGeo, materials.skin);
108
+ head.position.y = 1.35;
109
+ head.castShadow = true;
110
+ head.receiveShadow = true;
111
+ group.add(head);
112
+
113
+ // Crown
114
+ const crownBaseGeo = new THREE.CylinderGeometry(0.28, 0.28, 0.15, 8);
115
+ const crownBase = new THREE.Mesh(crownBaseGeo, materials.goldMetal);
116
+ crownBase.position.y = 1.5;
117
+ crownBase.castShadow = true;
118
+ crownBase.receiveShadow = true;
119
+ group.add(crownBase);
120
+
121
+ // Crown spikes
122
+ for (let i = 0; i < 4; i++) {
123
+ const spikeGeo = new THREE.ConeGeometry(0.06, 0.15, 4);
124
+ const spike = new THREE.Mesh(spikeGeo, materials.goldMetal);
125
+
126
+ const angle = i * Math.PI / 2;
127
+ spike.position.set(
128
+ Math.sin(angle) * 0.2,
129
+ 1.65,
130
+ Math.cos(angle) * 0.2
131
+ );
132
+
133
+ spike.castShadow = true;
134
+ spike.receiveShadow = true;
135
+ group.add(spike);
136
+ }
137
+
138
+ // Arms
139
+ const armGeo = new THREE.CylinderGeometry(0.08, 0.08, 0.6, 8);
140
+
141
+ const leftArm = new THREE.Mesh(armGeo, materials.cloth);
142
+ leftArm.position.set(-0.4, 0.9, 0);
143
+ leftArm.rotation.z = Math.PI / 4;
144
+ leftArm.castShadow = true;
145
+ leftArm.receiveShadow = true;
146
+ group.add(leftArm);
147
+
148
+ const rightArm = new THREE.Mesh(armGeo, materials.cloth);
149
+ rightArm.position.set(0.4, 0.9, 0);
150
+ rightArm.rotation.z = -Math.PI / 4;
151
+ rightArm.castShadow = true;
152
+ rightArm.receiveShadow = true;
153
+ group.add(rightArm);
154
+
155
+ // Cape
156
+ const capeGeo = new THREE.BoxGeometry(0.7, 1, 0.1);
157
+ const cape = new THREE.Mesh(capeGeo, new THREE.MeshStandardMaterial({
158
+ color: 0x880000,
159
+ roughness: 0.8
160
+ }));
161
+ cape.position.set(0, 0.7, -0.2);
162
+ cape.castShadow = true;
163
+ cape.receiveShadow = true;
164
+ group.add(cape);
165
+
166
+ // Scepter
167
+ const scepterGeo = new THREE.CylinderGeometry(0.03, 0.05, 0.8, 8);
168
+ const scepter = new THREE.Mesh(scepterGeo, materials.goldMetal);
169
+ scepter.position.set(0.65, 0.9, 0);
170
+ scepter.rotation.z = -Math.PI / 4;
171
+ scepter.castShadow = true;
172
+ scepter.receiveShadow = true;
173
+ group.add(scepter);
174
+
175
+ // Scepter orb
176
+ const orbGeo = new THREE.SphereGeometry(0.08, 8, 8);
177
+ const orb = new THREE.Mesh(orbGeo, materials.goldMetal);
178
+ orb.position.set(0, 0.45, 0);
179
+ orb.castShadow = true;
180
+ orb.receiveShadow = true;
181
+ scepter.add(orb);
182
+
183
+ return group;
184
+ }
185
+
186
+ function createSoldierFigure() {
187
+ const base = createObjectBase("Soldier Figure");
188
+ const group = new THREE.Group();
189
+ Object.assign(group, base);
190
+
191
+ // Body with armor
192
+ const bodyGeo = new THREE.CylinderGeometry(0.3, 0.25, 1, 8);
193
+ const body = new THREE.Mesh(bodyGeo, materials.metal);
194
+ body.position.y = 0.5;
195
+ body.castShadow = true;
196
+ body.receiveShadow = true;
197
+ group.add(body);
198
+
199
+ // Head
200
+ const headGeo = new THREE.SphereGeometry(0.2, 8, 8);
201
+ const head = new THREE.Mesh(headGeo, materials.skin);
202
+ head.position.y = 1.1;
203
+ head.castShadow = true;
204
+ head.receiveShadow = true;
205
+ group.add(head);
206
+
207
+ // Helmet
208
+ const helmetGeo = new THREE.CylinderGeometry(0.22, 0.22, 0.2, 8);
209
+ const helmet = new THREE.Mesh(helmetGeo, materials.metal);
210
+ helmet.position.y = 1.2;
211
+ helmet.castShadow = true;
212
+ helmet.receiveShadow = true;
213
+ group.add(helmet);
214
+
215
+ const helmetTopGeo = new THREE.SphereGeometry(0.22, 8, 8);
216
+ const helmetTop = new THREE.Mesh(helmetTopGeo, materials.metal);
217
+ helmetTop.position.y = 1.3;
218
+ helmetTop.scale.y = 0.5;
219
+ helmetTop.castShadow = true;
220
+ helmetTop.receiveShadow = true;
221
+ group.add(helmetTop);
222
+
223
+ // Arms
224
+ const armGeo = new THREE.CylinderGeometry(0.07, 0.07, 0.5, 8);
225
+
226
+ const leftArm = new THREE.Mesh(armGeo, materials.metal);
227
+ leftArm.position.set(-0.3, 0.7, 0);
228
+ leftArm.rotation.z = Math.PI / 6;
229
+ leftArm.castShadow = true;
230
+ leftArm.receiveShadow = true;
231
+ group.add(leftArm);
232
+
233
+ const rightArm = new THREE.Mesh(armGeo, materials.metal);
234
+ rightArm.position.set(0.3, 0.7, 0);
235
+ rightArm.rotation.z = -Math.PI / 6;
236
+ rightArm.castShadow = true;
237
+ rightArm.receiveShadow = true;
238
+ group.add(rightArm);
239
+
240
+ // Shield
241
+ const shieldGeo = new THREE.BoxGeometry(0.4, 0.6, 0.1);
242
+ const shield = new THREE.Mesh(shieldGeo, materials.metal);
243
+ shield.position.set(-0.45, 0.6, 0.1);
244
+ shield.rotation.y = Math.PI / 10;
245
+ shield.castShadow = true;
246
+ shield.receiveShadow = true;
247
+ group.add(shield);
248
+
249
+ // Sword
250
+ const swordHandleGeo = new THREE.CylinderGeometry(0.03, 0.03, 0.2, 8);
251
+ const swordHandle = new THREE.Mesh(swordHandleGeo, materials.leather);
252
+ swordHandle.position.set(0.4, 0.6, 0.2);
253
+ swordHandle.rotation.x = Math.PI / 2;
254
+ swordHandle.castShadow = true;
255
+ swordHandle.receiveShadow = true;
256
+ group.add(swordHandle);
257
+
258
+ const swordBladeGeo = new THREE.BoxGeometry(0.05, 0.6, 0.01);
259
+ const swordBlade = new THREE.Mesh(swordBladeGeo, materials.metal);
260
+ swordBlade.position.set(0, -0.3, 0);
261
+ swordBlade.castShadow = true;
262
+ swordBlade.receiveShadow = true;
263
+ swordHandle.add(swordBlade);
264
+
265
+ // Legs
266
+ const legGeo = new THREE.CylinderGeometry(0.09, 0.07, 0.5, 8);
267
+
268
+ const leftLeg = new THREE.Mesh(legGeo, materials.metal);
269
+ leftLeg.position.set(-0.1, 0, 0);
270
+ leftLeg.castShadow = true;
271
+ leftLeg.receiveShadow = true;
272
+ group.add(leftLeg);
273
+
274
+ const rightLeg = new THREE.Mesh(legGeo, materials.metal);
275
+ rightLeg.position.set(0.1, 0, 0);
276
+ rightLeg.castShadow = true;
277
+ rightLeg.receiveShadow = true;
278
+ group.add(rightLeg);
279
+
280
+ group.position.y = 0.5;
281
+ return group;
282
+ }
283
+
284
+ function createMageFigure() {
285
+ const base = createObjectBase("Mage Figure");
286
+ const group = new THREE.Group();
287
+ Object.assign(group, base);
288
+
289
+ // Body (robe)
290
+ const bodyGeo = new THREE.CylinderGeometry(0.4, 0.5, 1.3, 8);
291
+ const bodyMat = new THREE.MeshStandardMaterial({
292
+ color: 0x5522AA,
293
+ roughness: 0.8,
294
+ metalness: 0.1
295
+ });
296
+ const body = new THREE.Mesh(bodyGeo, bodyMat);
297
+ body.position.y = 0.65;
298
+ body.castShadow = true;
299
+ body.receiveShadow = true;
300
+ group.add(body);
301
+
302
+ // Head
303
+ const headGeo = new THREE.SphereGeometry(0.2, 8, 8);
304
+ const head = new THREE.Mesh(headGeo, materials.skin);
305
+ head.position.y = 1.4;
306
+ head.castShadow = true;
307
+ head.receiveShadow = true;
308
+ group.add(head);
309
+
310
+ // Wizard hat
311
+ const hatBaseGeo = new THREE.CylinderGeometry(0.25, 0.25, 0.05, 8);
312
+ const hatBase = new THREE.Mesh(hatBaseGeo, bodyMat);
313
+ hatBase.position.y = 1.55;
314
+ hatBase.castShadow = true;
315
+ hatBase.receiveShadow = true;
316
+ group.add(hatBase);
317
+
318
+ const hatTopGeo = new THREE.ConeGeometry(0.2, 0.5, 8);
319
+ const hatTop = new THREE.Mesh(hatTopGeo, bodyMat);
320
+ hatTop.position.y = 1.8;
321
+ hatTop.castShadow = true;
322
+ hatTop.receiveShadow = true;
323
+ group.add(hatTop);
324
+
325
+ // Arms
326
+ const armGeo = new THREE.CylinderGeometry(0.1, 0.1, 0.6, 8);
327
+
328
+ const leftArm = new THREE.Mesh(armGeo, bodyMat);
329
+ leftArm.position.set(-0.4, 0.9, 0);
330
+ leftArm.rotation.z = Math.PI / 4;
331
+ leftArm.castShadow = true;
332
+ leftArm.receiveShadow = true;
333
+ group.add(leftArm);
334
+
335
+ const rightArm = new THREE.Mesh(armGeo, bodyMat);
336
+ rightArm.position.set(0.4, 0.9, 0);
337
+ rightArm.rotation.z = -Math.PI / 4;
338
+ rightArm.castShadow = true;
339
+ rightArm.receiveShadow = true;
340
+ group.add(rightArm);
341
+
342
+ // Staff
343
+ const staffGeo = new THREE.CylinderGeometry(0.03, 0.05, 1.8, 8);
344
+ const staffMat = new THREE.MeshStandardMaterial({
345
+ color: 0x663300,
346
+ roughness: 0.9
347
+ });
348
+ const staff = new THREE.Mesh(staffGeo, staffMat);
349
+ staff.position.set(0.7, 0.9, 0);
350
+ staff.rotation.z = -0.2;
351
+ staff.castShadow = true;
352
+ staff.receiveShadow = true;
353
+ group.add(staff);
354
+
355
+ // Orb on staff
356
+ const orbGeo = new THREE.SphereGeometry(0.12, 8, 8);
357
+ const orbMat = new THREE.MeshStandardMaterial({
358
+ color: 0x00CCFF,
359
+ roughness: 0.2,
360
+ metalness: 0.5,
361
+ emissive: 0x0088FF,
362
+ emissiveIntensity: 0.8
363
+ });
364
+ const orb = new THREE.Mesh(orbGeo, orbMat);
365
+ orb.position.y = 0.9;
366
+ orb.castShadow = true;
367
+ orb.receiveShadow = true;
368
+ staff.add(orb);
369
+
370
+ // Cloak/cape
371
+ const cloakGeo = new THREE.BoxGeometry(0.8, 1.2, 0.1);
372
+ const cloak = new THREE.Mesh(cloakGeo, bodyMat);
373
+ cloak.position.set(0, 0.7, -0.25);
374
+ cloak.castShadow = true;
375
+ cloak.receiveShadow = true;
376
+ group.add(cloak);
377
+
378
+ group.position.y = 0.5;
379
+ return group;
380
+ }
381
+
382
+ function createZombieFigure() {
383
+ const base = createObjectBase("Zombie Figure");
384
+ const group = new THREE.Group();
385
+ Object.assign(group, base);
386
+
387
+ // Body
388
+ const bodyGeo = new THREE.CylinderGeometry(0.25, 0.25, 1, 8);
389
+ const body = new THREE.Mesh(bodyGeo, materials.zombie);
390
+ body.position.y = 0.5;
391
+ body.rotation.x = 0.2; // Leaning forward
392
+ body.castShadow = true;
393
+ body.receiveShadow = true;
394
+ group.add(body);
395
+
396
+ // Head
397
+ const headGeo = new THREE.SphereGeometry(0.2, 8, 8);
398
+ const head = new THREE.Mesh(headGeo, materials.zombie);
399
+ head.position.set(0, 1.1, 0.1); // Pushed forward due to leaning
400
+ head.castShadow = true;
401
+ head.receiveShadow = true;
402
+ group.add(head);
403
+
404
+ // Arms - asymmetrical for zombie look
405
+ const armGeo1 = new THREE.CylinderGeometry(0.08, 0.06, 0.6, 8);
406
+ const leftArm = new THREE.Mesh(armGeo1, materials.zombie);
407
+ leftArm.position.set(-0.3, 0.7, 0);
408
+ leftArm.rotation.set(0.1, 0, Math.PI / 2.5); // Extended forward
409
+ leftArm.castShadow = true;
410
+ leftArm.receiveShadow = true;
411
+ group.add(leftArm);
412
+
413
+ const armGeo2 = new THREE.CylinderGeometry(0.07, 0.07, 0.55, 8);
414
+ const rightArm = new THREE.Mesh(armGeo2, materials.zombie);
415
+ rightArm.position.set(0.3, 0.7, 0);
416
+ rightArm.rotation.set(-0.3, 0, -Math.PI / 3); // Hanging down
417
+ rightArm.castShadow = true;
418
+ rightArm.receiveShadow = true;
419
+ group.add(rightArm);
420
+
421
+ // Legs - asymmetrical stride
422
+ const legGeo1 = new THREE.CylinderGeometry(0.09, 0.07, 0.5, 8);
423
+ const leftLeg = new THREE.Mesh(legGeo1, materials.zombie);
424
+ leftLeg.position.set(-0.1, 0, 0);
425
+ leftLeg.rotation.x = -0.2; // Stepping forward
426
+ leftLeg.castShadow = true;
427
+ leftLeg.receiveShadow = true;
428
+ group.add(leftLeg);
429
+
430
+ const legGeo2 = new THREE.CylinderGeometry(0.08, 0.07, 0.5, 8);
431
+ const rightLeg = new THREE.Mesh(legGeo2, materials.zombie);
432
+ rightLeg.position.set(0.1, 0, -0.1);
433
+ rightLeg.castShadow = true;
434
+ rightLeg.receiveShadow = true;
435
+ group.add(rightLeg);
436
+
437
+ // Tattered clothes
438
+ const clothesGeo = new THREE.CylinderGeometry(0.26, 0.28, 0.7, 8);
439
+ const clothes = new THREE.Mesh(clothesGeo, new THREE.MeshStandardMaterial({
440
+ color: 0x445566,
441
+ roughness: 0.9,
442
+ metalness: 0.0
443
+ }));
444
+ clothes.position.y = 0.4;
445
+ clothes.castShadow = true;
446
+ clothes.receiveShadow = true;
447
+ body.add(clothes);
448
+
449
+ // Gore detail (blood)
450
+ const goreGeo = new THREE.SphereGeometry(0.1, 8, 8);
451
+ const gore = new THREE.Mesh(goreGeo, new THREE.MeshStandardMaterial({
452
+ color: 0x880000,
453
+ roughness: 0.8,
454
+ metalness: 0.2
455
+ }));
456
+ gore.position.set(0.15, 0.2, 0.1);
457
+ gore.scale.set(1, 0.3, 1);
458
+ body.add(gore);
459
+
460
+ group.position.y = 0.5;
461
+ return group;
462
+ }
463
+
464
+ function createSurvivorFigure() {
465
+ const base = createObjectBase("Survivor Figure");
466
+ const group = new THREE.Group();
467
+ Object.assign(group, base);
468
+
469
+ // Body
470
+ const bodyGeo = new THREE.CylinderGeometry(0.25, 0.25, 1, 8);
471
+ const body = new THREE.Mesh(bodyGeo, materials.leather);
472
+ body.position.y = 0.5;
473
+ body.castShadow = true;
474
+ body.receiveShadow = true;
475
+ group.add(body);
476
+
477
+ // Head
478
+ const headGeo = new THREE.SphereGeometry(0.2, 8, 8);
479
+ const head = new THREE.Mesh(headGeo, materials.skin);
480
+ head.position.y = 1.1;
481
+ head.castShadow = true;
482
+ head.receiveShadow = true;
483
+ group.add(head);
484
+
485
+ // Arms
486
+ const armGeo = new THREE.CylinderGeometry(0.07, 0.07, 0.5, 8);
487
+
488
+ const leftArm = new THREE.Mesh(armGeo, materials.leather);
489
+ leftArm.position.set(-0.3, 0.7, 0);
490
+ leftArm.rotation.z = Math.PI / 6;
491
+ leftArm.castShadow = true;
492
+ leftArm.receiveShadow = true;
493
+ group.add(leftArm);
494
+
495
+ const rightArm = new THREE.Mesh(armGeo, materials.leather);
496
+ rightArm.position.set(0.3, 0.7, 0);
497
+ rightArm.rotation.z = -Math.PI / 6;
498
+ rightArm.castShadow = true;
499
+ rightArm.receiveShadow = true;
500
+ group.add(rightArm);
501
+
502
+ // Legs
503
+ const legGeo = new THREE.CylinderGeometry(0.08, 0.07, 0.5, 8);
504
+
505
+ const leftLeg = new THREE.Mesh(legGeo, new THREE.MeshStandardMaterial({
506
+ color: 0x223355, // Denim
507
+ roughness: 0.8,
508
+ }));
509
+ leftLeg.position.set(-0.1, 0, 0);
510
+ leftLeg.castShadow = true;
511
+ leftLeg.receiveShadow = true;
512
+ group.add(leftLeg);
513
+
514
+ const rightLeg = new THREE.Mesh(legGeo, new THREE.MeshStandardMaterial({
515
+ color: 0x223355, // Denim
516
+ roughness: 0.8
517
+ }));
518
+ rightLeg.position.set(0.1, 0, 0);
519
+ rightLeg.castShadow = true;
520
+ rightLeg.receiveShadow = true;
521
+ group.add(rightLeg);
522
+
523
+ // Backpack
524
+ const backpackGeo = new THREE.BoxGeometry(0.3, 0.4, 0.2);
525
+ const backpack = new THREE.Mesh(backpackGeo, new THREE.MeshStandardMaterial({
526
+ color: 0x556B2F, // Olive green
527
+ roughness: 0.9
528
+ }));
529
+ backpack.position.set(0, 0.6, -0.2);
530
+ backpack.castShadow = true;
531
+ backpack.receiveShadow = true;
532
+ group.add(backpack);
533
+
534
+ // Baseball bat or weapon
535
+ const batGeo = new THREE.CylinderGeometry(0.03, 0.05, 0.8, 8);
536
+ const bat = new THREE.Mesh(batGeo, materials.wood);
537
+ bat.position.set(0.45, 0.4, 0.2);
538
+ bat.rotation.set(Math.PI/6, 0, Math.PI/6);
539
+ bat.castShadow = true;
540
+ bat.receiveShadow = true;
541
+ group.add(bat);
542
+
543
+ group.position.y = 0.5;
544
+ return group;
545
+ }
546
+
547
+ function createDwarfMinerFigure() {
548
+ const base = createObjectBase("Dwarf Miner Figure");
549
+ const group = new THREE.Group();
550
+ Object.assign(group, base);
551
+
552
+ // Body - shorter, stockier
553
+ const bodyGeo = new THREE.CylinderGeometry(0.3, 0.3, 0.7, 8);
554
+ const body = new THREE.Mesh(bodyGeo, new THREE.MeshStandardMaterial({
555
+ color: 0x8B4513, // Brown leather
556
+ roughness: 0.8
557
+ }));
558
+ body.position.y = 0.35;
559
+ body.castShadow = true;
560
+ body.receiveShadow = true;
561
+ group.add(body);
562
+
563
+ // Head - larger in proportion
564
+ const headGeo = new THREE.SphereGeometry(0.22, 8, 8);
565
+ const head = new THREE.Mesh(headGeo, materials.skin);
566
+ head.position.y = 0.8;
567
+ head.castShadow = true;
568
+ head.receiveShadow = true;
569
+ group.add(head);
570
+
571
+ // Beard
572
+ const beardGeo = new THREE.ConeGeometry(0.2, 0.3, 8);
573
+ const beard = new THREE.Mesh(beardGeo, new THREE.MeshStandardMaterial({
574
+ color: 0xDD9933, // Golden beard
575
+ roughness: 0.9
576
+ }));
577
+ beard.position.set(0, 0.7, 0.1);
578
+ beard.rotation.x = -Math.PI/2;
579
+ beard.castShadow = true;
580
+ beard.receiveShadow = true;
581
+ group.add(beard);
582
+
583
+ // Miner's helmet
584
+ const helmetGeo = new THREE.CylinderGeometry(0.23, 0.23, 0.15, 8);
585
+ const helmet = new THREE.Mesh(helmetGeo, materials.metal);
586
+ helmet.position.y = 0.95;
587
+ helmet.castShadow = true;
588
+ helmet.receiveShadow = true;
589
+ group.add(helmet);
590
+
591
+ // Helmet light
592
+ const lightGeo = new THREE.CylinderGeometry(0.05, 0.05, 0.07, 8);
593
+ const light = new THREE.Mesh(lightGeo, new THREE.MeshStandardMaterial({
594
+ color: 0xFFFF00,
595
+ emissive: 0xFFFF00,
596
+ emissiveIntensity: 0.8
597
+ }));
598
+ light.position.set(0, 0, 0.25);
599
+ helmet.add(light);
600
+
601
+ // Arms
602
+ const armGeo = new THREE.CylinderGeometry(0.08, 0.08, 0.4, 8);
603
+
604
+ const leftArm = new THREE.Mesh(armGeo, materials.leather);
605
+ leftArm.position.set(-0.35, 0.5, 0);
606
+ leftArm.rotation.z = Math.PI / 4;
607
+ leftArm.castShadow = true;
608
+ leftArm.receiveShadow = true;
609
+ group.add(leftArm);
610
+
611
+ const rightArm = new THREE.Mesh(armGeo, materials.leather);
612
+ rightArm.position.set(0.35, 0.5, 0);
613
+ rightArm.rotation.z = -Math.PI / 4;
614
+ rightArm.castShadow = true;
615
+ rightArm.receiveShadow = true;
616
+ group.add(rightArm);
617
+
618
+ // Legs - Short, sturdy
619
+ const legGeo = new THREE.CylinderGeometry(0.1, 0.08, 0.3, 8);
620
+
621
+ const leftLeg = new THREE.Mesh(legGeo, materials.leather);
622
+ leftLeg.position.set(-0.15, 0, 0);
623
+ leftLeg.castShadow = true;
624
+ leftLeg.receiveShadow = true;
625
+ group.add(leftLeg);
626
+
627
+ const rightLeg = new THREE.Mesh(legGeo, materials.leather);
628
+ rightLeg.position.set(0.15, 0 const wallGeo = new THREE.BoxGeometry(5, 3, 0.5);
629
+ const wall = new THREE.Mesh(wallGeo, materials.redBrick);
630
+ wall.castShadow = true;
631
+ wall.receiveShadow = true;
632
+ group.add(wall);
633
+
634
+ // Broken window
635
+ const windowGeo = new THREE.BoxGeometry(1.2, 1.2, 0.1);
636
+ const window = new THREE.Mesh(windowGeo, materials.glass);
637
+ window.position.set(-1.2, 0.5, 0.3);
638
+ window.material.opacity = 0.3; // Broken glass is more transparent
639
+ group.add(window);
640
+
641
+ // Door frame
642
+ const doorFrameGeo = new THREE.BoxGeometry(1.1, 2.2, 0.1);
643
+ const doorFrame = new THREE.Mesh(doorFrameGeo, materials.darkWood);
644
+ doorFrame.position.set(1.2, -0.4, 0.3);
645
+ group.add(doorFrame);
646
+
647
+ // Damage details
648
+ const damageGeo = new THREE.BoxGeometry(1.5, 1.2, 0.6);
649
+ const damage = new THREE.Mesh(damageGeo, materials.damagedConcrete);
650
+ damage.position.set(0.5, 1, 0.2);
651
+ group.add(damage);
652
+
653
+ // Add some rubble at the base
654
+ for (let i = 0; i < 5; i++) {
655
+ const rubbleGeo = new THREE.IcosahedronGeometry(0.3 * Math.random() + 0.1, 0);
656
+ const rubble = new THREE.Mesh(rubbleGeo, materials.redBrick);
657
+ rubble.position.set(
658
+ Math.random() * 4 - 2,
659
+ -1.5 + Math.random() * 0.3,
660
+ 0.3 + Math.random() * 0.5
661
+ );
662
+ rubble.rotation.set(
663
+ Math.random() * Math.PI,
664
+ Math.random() * Math.PI,
665
+ Math.random() * Math.PI
666
+ );
667
+ rubble.castShadow = true;
668
+ rubble.receiveShadow = true;
669
+ group.add(rubble);
670
+ }
671
+
672
+ group.position.y = 1.5; // Raise to ground level
673
+ return group;
674
+ }
675
+
676
+ // =================== NATURE CATEGORY ===================
677
+
678
+ function createPineTree() {
679
+ const base = createObjectBase("Pine Tree");
680
+ const group = new THREE.Group();
681
+ Object.assign(group, base);
682
+
683
+ // Trunk
684
+ const trunkGeo = new THREE.CylinderGeometry(0.2, 0.3, 3, 8);
685
+ const trunk = new THREE.Mesh(trunkGeo, materials.wood);
686
+ trunk.position.y = 1.5;
687
+ trunk.castShadow = true;
688
+ trunk.receiveShadow = true;
689
+ group.add(trunk);
690
+
691
+ // Pine layers - cones stacked
692
+ const colors = [0x005500, 0x006600, 0x007700];
693
+ for (let i = 0; i < 3; i++) {
694
+ const coneGeo = new THREE.ConeGeometry(1.2 - i * 0.3, 1.5, 8);
695
+ const leafMat = new THREE.MeshStandardMaterial({
696
+ color: colors[i],
697
+ roughness: 0.8
698
+ });
699
+ const foliage = new THREE.Mesh(coneGeo, leafMat);
700
+ foliage.position.y = 2 + i*1.2;
701
+ foliage.castShadow = true;
702
+ foliage.receiveShadow = true;
703
+ group.add(foliage);
704
+ }
705
+
706
+ return group;
707
+ }
708
+
709
+ function createBoulder() {
710
+ const base = createObjectBase("Boulder");
711
+ const group = new THREE.Group();
712
+ Object.assign(group, base);
713
+
714
+ // Main boulder
715
+ const boulderGeo = new THREE.IcosahedronGeometry(1.2, 1);
716
+ const boulder = new THREE.Mesh(boulderGeo, materials.stone);
717
+ boulder.castShadow = true;
718
+ boulder.receiveShadow = true;
719
+ boulder.scale.y = 0.8;
720
+ boulder.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, Math.random() * Math.PI);
721
+ group.add(boulder);
722
+
723
+ // Add some smaller stones nearby
724
+ for (let i = 0; i < 3; i++) {
725
+ const smallStoneGeo = new THREE.IcosahedronGeometry(0.3 * Math.random() + 0.2, 0);
726
+ const smallStone = new THREE.Mesh(smallStoneGeo, materials.stone);
727
+ smallStone.position.set(
728
+ Math.random() * 2 - 1,
729
+ Math.random() * 0.3,
730
+ Math.random() * 2 - 1
731
+ );
732
+ smallStone.rotation.set(
733
+ Math.random() * Math.PI,
734
+ Math.random() * Math.PI,
735
+ Math.random() * Math.PI
736
+ );
737
+ smallStone.castShadow = true;
738
+ smallStone.receiveShadow = true;
739
+ group.add(smallStone);
740
+ }
741
+
742
+ group.position.y = 0.6; // Raise to ground level
743
+ return group;
744
+ }
745
+
746
+ function createAlienPlant() {
747
+ const base = createObjectBase("Alien Plant");
748
+ const group = new THREE.Group();
749
+ Object.assign(group, base);
750
+
751
+ // Plant base
752
+ const baseGeo = new THREE.CylinderGeometry(0.3, 0.5, 0.5, 6);
753
+ const plantBase = new THREE.Mesh(baseGeo, materials.darkGreen);
754
+ plantBase.position.y = 0.25;
755
+ plantBase.castShadow = true;
756
+ plantBase.receiveShadow = true;
757
+ group.add(plantBase);
758
+
759
+ // Strange tentacle-like growths
760
+ for (let i = 0; i < 5; i++) {
761
+ const tentacleGeo = new THREE.CylinderGeometry(0.1, 0.05, 1.5, 6);
762
+ // Bend the geometry
763
+ const tentacleMat = new THREE.MeshStandardMaterial({
764
+ color: 0x8800AA,
765
+ roughness: 0.6,
766
+ emissive: 0x330033,
767
+ emissiveIntensity: 0.3
768
+ });
769
+ const tentacle = new THREE.Mesh(tentacleGeo, tentacleMat);
770
+
771
+ tentacle.position.set(
772
+ Math.sin(i * Math.PI * 2 / 5) * 0.3,
773
+ 1,
774
+ Math.cos(i * Math.PI * 2 / 5) * 0.3
775
+ );
776
+
777
+ tentacle.rotation.set(
778
+ Math.random() * 0.5 - 0.25 + 0.3,
779
+ 0,
780
+ Math.random() * 0.5 - 0.25 + (i * Math.PI * 2 / 5)
781
+ );
782
+
783
+ tentacle.castShadow = true;
784
+ tentacle.receiveShadow = true;
785
+ group.add(tentacle);
786
+
787
+ // Add glowing bulbs at the end of tentacles
788
+ const bulbGeo = new THREE.SphereGeometry(0.15, 8, 8);
789
+ const bulbMat = new THREE.MeshStandardMaterial({
790
+ color: 0xAA00FF,
791
+ roughness: 0.4,
792
+ emissive: 0xAA00FF,
793
+ emissiveIntensity: 0.8
794
+ });
795
+ const bulb = new THREE.Mesh(bulbGeo, bulbMat);
796
+ bulb.position.y = 0.8;
797
+ tentacle.add(bulb);
798
+ }
799
+
800
+ return group;
801
+ }
802
+
803
+ function createFloatingRockPlatform() {
804
+ const base = createObjectBase("Floating Rock Platform");
805
+ const group = new THREE.Group();
806
+ Object.assign(group, base);
807
+
808
+ // Main floating rock
809
+ const rockGeo = new THREE.CylinderGeometry(1.5, 2, 1, 8);
810
+ const rock = new THREE.Mesh(rockGeo, materials.stone);
811
+ rock.castShadow = true;
812
+ rock.receiveShadow = true;
813
+ rock.position.y = 1.5;
814
+ group.add(rock);
815
+
816
+ // Top surface - grass
817
+ const topGeo = new THREE.CylinderGeometry(1.5, 1.5, 0.1, 8);
818
+ const top = new THREE.Mesh(topGeo, materials.ground);
819
+ top.position.y = 2.05;
820
+ top.receiveShadow = true;
821
+ group.add(top);
822
+
823
+ // Bottom details - crystals
824
+ for (let i = 0; i < 6; i++) {
825
+ const crystalGeo = new THREE.ConeGeometry(0.2, 0.6, 5);
826
+ const crystal = new THREE.Mesh(crystalGeo, materials.crystal);
827
+
828
+ const angle = i * Math.PI * 2 / 6;
829
+ crystal.position.set(
830
+ Math.sin(angle) * 1.7,
831
+ 1,
832
+ Math.cos(angle) * 1.7
833
+ );
834
+
835
+ crystal.rotation.x = Math.PI;
836
+ crystal.rotation.z = Math.random() * 0.3 - 0.15;
837
+
838
+ crystal.castShadow = true;
839
+ crystal.receiveShadow = true;
840
+ group.add(crystal);
841
+ }
842
+
843
+ // Anti-gravity effect (particles) - simulated with small meshes
844
+ for (let i = 0; i < 8; i++) {
845
+ const particleGeo = new THREE.SphereGeometry(0.1, 4, 4);
846
+ const particle = new THREE.Mesh(particleGeo, materials.energyBeam);
847
+
848
+ const angle = i * Math.PI * 2 / 8;
849
+ const distance = 1 + Math.random() * 0.5;
850
+ particle.position.set(
851
+ Math.sin(angle) * distance,
852
+ Math.random() * 1.5,
853
+ Math.cos(angle) * distance
854
+ );
855
+
856
+ group.add(particle);
857
+ }
858
+
859
+ group.position.y = 0.8; // Hover above ground
860
+ return group;
861
+ }
862
+
863
+ function createRubblePile() {
864
+ const base = createObjectBase("Rubble Pile");
865
+ const group = new THREE.Group();
866
+ Object.assign(group, base);
867
+
868
+ // Create multiple rubble pieces
869
+ const materials = [
870
+ new THREE.MeshStandardMaterial({color: 0x888888, roughness: 0.9}), // Concrete
871
+ new THREE.MeshStandardMaterial({color: 0xA03C28, roughness: 0.9}), // Brick
872
+ new THREE.MeshStandardMaterial({color: 0x777777, roughness: 0.8}) // Stone
873
+ ];
874
+
875
+ // Add chunks of various sizes
876
+ for (let i = 0; i < 15; i++) {
877
+ // Choose random geometry
878
+ let geo;
879
+ const geoType = Math.floor(Math.random() * 3);
880
+ if (geoType === 0) {
881
+ geo = new THREE.BoxGeometry(
882
+ 0.3 + Math.random() * 0.7,
883
+ 0.2 + Math.random() * 0.3,
884
+ 0.3 + Math.random() * 0.7
885
+ );
886
+ } else if (geoType === 1) {
887
+ geo = new THREE.IcosahedronGeometry(0.2 + Math.random() * 0.4, 0);
888
+ } else {
889
+ geo = new THREE.TetrahedronGeometry(0.3 + Math.random() * 0.4, 0);
890
+ }
891
+
892
+ // Choose random material
893
+ const material = materials[Math.floor(Math.random() * materials.length)];
894
+
895
+ const chunk = new THREE.Mesh(geo, material);
896
+ chunk.position.set(
897
+ Math.random() * 2 - 1,
898
+ Math.random() * 0.8,
899
+ Math.random() * 2 - 1
900
+ );
901
+ chunk.rotation.set(
902
+ Math.random() * Math.PI,
903
+ Math.random() * Math.PI,
904
+ Math.random() * Math.PI
905
+ );
906
+ chunk.castShadow = true;
907
+ chunk.receiveShadow = true;
908
+ group.add(chunk);
909
+ }
910
+
911
+ // Add some rebar sticking out
912
+ for (let i = 0; i < 5; i++) {
913
+ const rebarGeo = new THREE.CylinderGeometry(0.03, 0.03, 0.8 + Math.random() * 0.8, 6);
914
+ const rebar = new THREE.Mesh(rebarGeo, materials.rustedMetal);
915
+ rebar.position.set(
916
+ Math.random() * 1.8 - 0.9,
917
+ 0.3 + Math.random() * 0.3,
918
+ Math.random() * 1.8 - 0.9
919
+ );
920
+ rebar.rotation.set(
921
+ Math.random() * 0.8 - 0.4,
922
+ Math.random() * Math.PI,
923
+ Math.random() * 0.8 - 0.4
924
+ );
925
+ rebar.castShadow = true;
926
+ rebar.receiveShadow = true;
927
+ group.add(rebar);
928
+ }
929
+
930
+ // Add dusty material on ground
931
+ const dustGeo = new THREE.CircleGeometry(1.5, 16);
932
+ const dustMat = new THREE.MeshStandardMaterial({
933
+ color: 0xCCBBAA,
934
+ roughness: 1.0,
935
+ metalness: 0.0
936
+ });
937
+ const dust = new THREE.Mesh(dustGeo, dustMat);
938
+ dust.rotation.x = -Math.PI/2;
939
+ dust.position.y = 0.01;
940
+ dust.receiveShadow = true;
941
+ group.add(dust);
942
+
943
+ group.position.y = 0.4; // Raise to ground level
944
+ return group;
945
+ }
946
+
947
+ // =================== PROPS CATEGORY ===================
948
+
949
+ function createRooftopACUnit() {
950
+ const base = createObjectBase("Rooftop AC Unit");
951
+ const group = new THREE.Group();
952
+ Object.assign(group, base);
953
+
954
+ // Main AC unit body
955
+ const acGeo = new THREE.BoxGeometry(1.5, 0.8, 1.2);
956
+ const ac = new THREE.Mesh(acGeo, materials.metal);
957
+ ac.castShadow = true;
958
+ ac.receiveShadow = true;
959
+ group.add(ac);
960
+
961
+ // Fan grille on top
962
+ const fanGeo = new THREE.CylinderGeometry(0.4, 0.4, 0.1, 8);
963
+ const fan = new THREE.Mesh(fanGeo, materials.rustedMetal);
964
+ fan.rotation.x = Math.PI/2;
965
+ fan.position.set(0, 0.45, 0);
966
+ group.add(fan);
967
+
968
+ // Vents on sides
969
+ const ventGeo = new THREE.BoxGeometry(1.2, 0.4, 0.1);
970
+
971
+ const vent1 = new THREE.Mesh(ventGeo, materials.rustedMetal);
972
+ vent1.position.set(0, 0, 0.65);
973
+ group.add(vent1);
974
+
975
+ const vent2 = new THREE.Mesh(ventGeo, materials.rustedMetal);
976
+ vent2.position.set(0, 0, -0.65);
977
+ group.add(vent2);
978
+
979
+ // Pipes
980
+ const pipeGeo = new THREE.CylinderGeometry(0.1, 0.1, 0.6, 8);
981
+ const pipe = new THREE.Mesh(pipeGeo, materials.metal);
982
+ pipe.rotation.x = Math.PI/2;
983
+ pipe.position.set(-0.6, -0.25, 0);
984
+ group.add(pipe);
985
+
986
+ group.position.y = 0.4; // Raise to ground level
987
+ return group;
988
+ }
989
+
990
+ function createHolographicWindowDisplay() {
991
+ const base = createObjectBase("Holographic Window Display");
992
+ const group = new THREE.Group();
993
+ Object.assign(group, base);
994
+
995
+ // Frame
996
+ const frameGeo = new THREE.BoxGeometry(2, 1.5, 0.1);
997
+ const frame = new THREE.Mesh(frameGeo, materials.metal);
998
+ frame.castShadow = true;
999
+ frame.receiveShadow = true;
1000
+ group.add(frame);
1001
+
1002
+ // Holographic display
1003
+ const displayGeo = new THREE.PlaneGeometry(1.8, 1.3);
1004
+ const displayMat = new THREE.MeshStandardMaterial({
1005
+ color: 0x00AAFF,
1006
+ roughness: 0.2,
1007
+ metalness: 0.8,
1008
+ emissive: 0x0066FF,
1009
+ emissiveIntensity: 0.8,
1010
+ transparent: true,
1011
+ opacity: 0.7
1012
+ });
1013
+ const display = new THREE.Mesh(displayGeo, displayMat);
1014
+ display.position.z = 0.06;
1015
+ group.add(display);
1016
+
1017
+ // Data lines on the display
1018
+ for (let i = 0; i < 5; i++) {
1019
+ const lineGeo = new THREE.BoxGeometry(1.4, 0.04, 0.01);
1020
+ const line = new THREE.Mesh(lineGeo, materials.energyBeam);
1021
+ line.position.set(0, -0.4 + i * 0.2, 0.07);
1022
+ group.add(line);
1023
+ }
1024
+
1025
+ group.position.y = 1.5; // Raise to wall mount height
1026
+ return group;
1027
+ }
1028
+
1029
+ function createJerseyBarrier() {
1030
+ const base = createObjectBase("Jersey Barrier");
1031
+ const group = new THREE.Group();
1032
+ Object.assign(group, base);
1033
+
1034
+ // Create the barrier shape - custom geometry
1035
+ const shape = new THREE.Shape();
1036
+ shape.moveTo(-0.5, 0);
1037
+ shape.lineTo(-0.4, 0.8);
1038
+ shape.lineTo(0.4, 0.8);
1039
+ shape.lineTo(0.5, 0);
1040
+ shape.lineTo(-0.5, 0);
1041
+
1042
+ const extrudeSettings = {
1043
+ steps: 1,
1044
+ depth: 2,
1045
+ bevelEnabled: false
1046
+ };
1047
+
1048
+ const barrierGeo = new THREE.ExtrudeGeometry(shape, extrudeSettings);
1049
+ const barrier = new THREE.Mesh(barrierGeo, materials.concrete);
1050
+ barrier.castShadow = true;
1051
+ barrier.receiveShadow = true;
1052
+ barrier.rotation.y = Math.PI/2;
1053
+ group.add(barrier);
1054
+
1055
+ // Add some damage/weathering details
1056
+ const damageGeo = new THREE.BoxGeometry(0.2, 0.1, 0.3);
1057
+ const damage1 = new THREE.Mesh(damageGeo, materials.damagedConcrete);
1058
+ damage1.position.set(0.51, 0.4, 0.7);
1059
+ group.add(damage1);
1060
+
1061
+ const damage2 = new THREE.Mesh(damageGeo, materials.damagedConcrete);
1062
+ damage2.position.set(0.51, 0.3, -0.5);
1063
+ group.add(damage2);
1064
+
1065
+ // Optional graffiti - represented as colored patches
1066
+ const graffitiGeo = new THREE.PlaneGeometry(0.8, 0.3);
1067
+ const graffitiMat = new THREE.MeshStandardMaterial({
1068
+ color: 0xFF3300,
1069
+ roughness: 0.9
1070
+ });
1071
+ const graffiti = new THREE.Mesh(graffitiGeo, graffitiMat);
1072
+ graffiti.rotation.y = Math.PI/2;
1073
+ graffiti.position.set(0.51, 0.5, 0);
1074
+ group.add(graffiti);
1075
+
1076
+ group.position.y = 0.4; // Raise to ground level
1077
+ return group;
1078
+ }
1079
+
1080
+ function createOilDrum() {
1081
+ const base = createObjectBase("Oil Drum");
1082
+ const group = new THREE.Group();
1083
+ Object.assign(group, base);
1084
+
1085
+ // Main drum body
1086
+ const drumGeo = new THREE.CylinderGeometry(0.4, 0.4, 1.2, 16);
1087
+ const drum = new THREE.Mesh(drumGeo, materials.rustedMetal);
1088
+ drum.castShadow = true;
1089
+ drum.receiveShadow = true;
1090
+ group.add(drum);
1091
+
1092
+ // Top and bottom rims
1093
+ const rimGeo = new THREE.TorusGeometry(0.4, 0.05, 8, 24);
1094
+
1095
+ const topRim = new THREE.Mesh(rimGeo, materials.metal);
1096
+ topRim.rotation.x = Math.PI/2;
1097
+ topRim.position.y = 0.6;
1098
+ group.add(topRim);
1099
+
1100
+ const bottomRim = new THREE.Mesh(rimGeo, materials.metal);
1101
+ bottomRim.rotation.x = Math.PI/2;
1102
+ bottomRim.position.y = -0.6;
1103
+ group.add(bottomRim);
1104
+
1105
+ // Add some bullet holes
1106
+ for (let i = 0; i < 3; i++) {
1107
+ const holeGeo = new THREE.CircleGeometry(0.05 + Math.random() * 0.05, 8);
1108
+ const hole = new THREE.Mesh(holeGeo, new THREE.MeshBasicMaterial({color: 0x000000}));
1109
+
1110
+ // Position on the curved surface of the drum
1111
+ const angle = Math.random() * Math.PI * 2;
1112
+ hole.position.set(
1113
+ Math.sin(angle) * 0.41,
1114
+ Math.random() * 0.8 - 0.4,
1115
+ Math.cos(angle) * 0.41
1116
+ );
1117
+
1118
+ // Rotate to face outward
1119
+ hole.rotation.y = angle;
1120
+ hole.rotation.x = Math.PI/2;
1121
+
1122
+ group.add(hole);
1123
+ }
1124
+
1125
+ // Add a lid cap
1126
+ const lidGeo = new THREE.CylinderGeometry(0.4, 0.4, 0.05, 16);
1127
+ const lid = new THREE.Mesh(lidGeo, materials.metal);
1128
+ lid.position.y = 0.625;
1129
+ group.add(lid);
1130
+
1131
+ // Add cap in center
1132
+ const capGeo = new THREE.CylinderGeometry(0.1, 0.1, 0.03, 16);
1133
+ const cap = new THREE.Mesh(capGeo, materials.metal);
1134
+ cap.position.y = 0.65;
1135
+ group.add(cap);
1136
+
1137
+ group.position.y = 0.6; // Raise to ground level
1138
+ return group;
1139
+ }
1140
+
1141
+ function createCannedFood() {
1142
+ const base = createObjectBase("Canned Food");
1143
+ const group = new THREE.Group();
1144
+ Object.assign(group, base);
1145
+
1146
+ // Main can body
1147
+ const canGeo = new THREE.CylinderGeometry(0.15, 0.15, 0.25, 16);
1148
+ const can = new THREE.Mesh(canGeo, materials.metal);
1149
+ can.castShadow = true;
1150
+ can.receiveShadow = true;
1151
+ group.add(can);
1152
+
1153
+ // Top and bottom rims
1154
+ const rimGeo = new THREE.TorusGeometry(0.15, 0.02, 8, 24);
1155
+
1156
+ const topRim = new THREE.Mesh(rimGeo, materials.metal);
1157
+ topRim.rotation.x = Math.PI/2;
1158
+ topRim.position.y = 0.125;
1159
+ group.add(topRim);
1160
+
1161
+ const bottomRim = new THREE.Mesh(rimGeo, materials.metal);
1162
+ bottomRim.rotation.x = Math.PI/2;
1163
+ bottomRim.position.y = -0.125;
1164
+ group.add(bottomRim);
1165
+
1166
+ // Label - simulated with a colored band
1167
+ const labelGeo = new THREE.CylinderGeometry(0.151, 0.151, 0.2, 16);
1168
+ const labelMat = new THREE.MeshStandardMaterial({
1169
+ color: 0x2244AA,
1170
+ roughness: 0.9,
1171
+ metalness: 0.0
1172
+ });
1173
+ const label = new THREE.Mesh(labelGeo, labelMat);
1174
+ group.add(label);
1175
+
1176
+ // Add a dent
1177
+ const dentGeo = new THREE.SphereGeometry(0.1, 8, 8);
1178
+ const dent = new THREE.Mesh(dentGeo, materials.metal);
1179
+ dent.position.set(0.08, 0, 0.12);
1180
+ dent.scale.set(0.5, 0.5, 0.2);
1181
+ group.add(dent);
1182
+
1183
+ group.position.y = 0.125; // Raise to ground level
1184
+ group.scale.set(0.7, 0.7, 0.7); // Make the can a reasonable size
1185
+ return group;
1186
+ }
1187
+
1188
+ function createTreasureChest() {
1189
+ const base = createObjectBase("Treasure Chest");
1190
+ const group = new THREE.Group();
1191
+ Object.assign(group, base);
1192
+
1193
+ // Chest base
1194
+ const chestBaseGeo = new THREE.BoxGeometry(1, 0.6, 0.7);
1195
+ const chestBase = new THREE.Mesh(chestBaseGeo, materials.wood);
1196
+ chestBase.position.y = 0.3;
1197
+ chestBase.castShadow = true;
1198
+ chestBase.receiveShadow = true;
1199
+ group.add(chestBase);
1200
+
1201
+ // Chest lid
1202
+ const chestLidGeo = new THREE.BoxGeometry(1, 0.2, 0.7);
1203
+ const chestLid = new THREE.Mesh(chestLidGeo, materials.wood);
1204
+ chestLid.position.set(0, 0.7, -0.2);
1205
+ chestLid.rotation.x = Math.PI/4; // Opened
1206
+ chestLid.castShadow = true;
1207
+ chestLid.receiveShadow = true;
1208
+ group.add(chestLid);
1209
+
1210
+ // Metal bands
1211
+ const bandGeo = new THREE.BoxGeometry(1.02, 0.1, 0.72);
1212
+
1213
+ const band1 = new THREE.Mesh(bandGeo, materials.metal);
1214
+ band1.position.y = 0.15;
1215
+ group.add(band1);
1216
+
1217
+ const band2 = new THREE.Mesh(bandGeo, materials.metal);
1218
+ band2.position.y = 0.45;
1219
+ group.add(band2);
1220
+
1221
+ // Lid band
1222
+ const lidBandGeo = new THREE.BoxGeometry(1.02, 0.1, 0.72);
1223
+ const lidBand = new THREE.Mesh(lidBandGeo, materials.metal);
1224
+ lidBand.position.y = 0.05;
1225
+ chestLid.add(lidBand);
1226
+
1227
+ // Lock
1228
+ const lockGeo = new THREE.BoxGeometry(0.25, 0.2, 0.1);
1229
+ const lock = new THREE.Mesh(lockGeo, materials.goldMetal);
1230
+ lock.position.set(0, 0.6, 0.35);
1231
+ group.add(lock);
1232
+
1233
+ // Glow effect inside (treasure!)
1234
+ const glowGeo = new THREE.IcosahedronGeometry(0.3, 0);
1235
+ const glowMat = new THREE.MeshStandardMaterial({
1236
+ color: 0xFFD700,
1237
+ roughness: 0.2,
1238
+ metalness: 1.0,
1239
+ emissive: 0xFFD700,
1240
+ emissiveIntensity: 0.5
1241
+ });
1242
+ const glow = new THREE.Mesh(glowGeo, glowMat);
1243
+ glow.position.set(0, 0.3, 0);
1244
+ group.add(glow);
1245
+
1246
+ return group;
1247
+ }
1248
+
1249
+ function createWallTorch() {
1250
+ const base = createObjectBase("Wall Torch");
1251
+ const group = new THREE.Group();
1252
+ Object.assign(group, base);
1253
+
1254
+ // Torch handle
1255
+ const handleGeo = new THREE.CylinderGeometry(0.03, 0.03, 0.5, 8);
1256
+ const handle = new THREE.Mesh(handleGeo, materials.wood);
1257
+ handle.rotation.x = Math.PI/4;
1258
+ handle.position.y = 0.2;
1259
+ handle.castShadow = true;
1260
+ handle.receiveShadow = true;
1261
+ group.add(handle);
1262
+
1263
+ // Torch holder
1264
+ const holderGeo = new THREE.CylinderGeometry(0.08, 0.12, 0.15, 8);
1265
+ const holder = new THREE.Mesh(holderGeo, materials.metal);
1266
+ holder.position.set(0, 0.48, 0.17);
1267
+ holder.rotation.x = Math.PI/4;
1268
+ group.add(holder);
1269
+
1270
+ // Wall mount
1271
+ const mountGeo = new THREE.BoxGeometry(0.25, 0.25, 0.1);
1272
+ const mount = new THREE.Mesh(mountGeo, materials.metal);
1273
+ mount.position.z = -0.1;
1274
+ group.add(mount);
1275
+
1276
+ // Flame effect (simplified)
1277
+ const flameGeo = new THREE.ConeGeometry(0.1, 0.25, 8);
1278
+ const flameMat = new THREE.MeshStandardMaterial({
1279
+ color: 0xFF6600,
1280
+ roughness: 0.3,
1281
+ metalness: 0.0,
1282
+ emissive: 0xFF3300,
1283
+ emissiveIntensity: 0.8
1284
+ });
1285
+ const flame = new THREE.Mesh(flameGeo, flameMat);
1286
+ flame.position.set(0, 0.58, 0.26);
1287
+ flame.rotation.x = Math.PI/4;
1288
+ group.add(flame);
1289
+
1290
+ // Inner flame
1291
+ const innerFlameGeo = new THREE.ConeGeometry(0.05, 0.15, 8);
1292
+ const innerFlameM<!DOCTYPE html>
1293
  <html>
1294
  <head>
1295
  <title>Three.js Infinite World</title>
1296
  <style>
1297
  body { margin: 0; overflow: hidden; }
1298
  canvas { display: block; }
1299
+ .info-panel {
1300
+ position: absolute;
1301
+ bottom: 10px;
1302
+ left: 10px;
1303
+ background: rgba(0,0,0,0.6);
1304
+ color: white;
1305
+ padding: 10px;
1306
+ border-radius: 5px;
1307
+ font-family: Arial, sans-serif;
1308
+ font-size: 14px;
1309
+ pointer-events: none;
1310
+ }
1311
  </style>
1312
  <!-- New: Polling function for game state -->
1313
  <script>
 
1320
  </script>
1321
  </head>
1322
  <body>
1323
+ <div class="info-panel">
1324
+ WASD or Arrow Keys to move | Click to place objects
1325
+ </div>
1326
+
1327
  <script type="importmap">
1328
  {
1329
  "imports": {
 
1351
  const allInitialObjects = window.ALL_INITIAL_OBJECTS || [];
1352
  const plotsMetadata = window.PLOTS_METADATA || [];
1353
  const selectedObjectType = window.SELECTED_OBJECT_TYPE || "None";
1354
+ const customScale = window.CUSTOM_SCALE || 1.0;
1355
+ const customRotationY = window.CUSTOM_ROTATION_Y || 0;
1356
  const plotWidth = window.PLOT_WIDTH || 50.0;
1357
  const plotDepth = window.PLOT_DEPTH || 50.0;
1358
 
1359
+ // Materials Library - Centralized for reuse
1360
+ const materials = {
1361
+ // Common materials
1362
+ ground: new THREE.MeshStandardMaterial({
1363
+ color: 0x55aa55, roughness: 0.9, metalness: 0.1, side: THREE.DoubleSide
1364
+ }),
1365
+ placeholderGround: new THREE.MeshStandardMaterial({
1366
+ color: 0x448844, roughness: 0.95, metalness: 0.1, side: THREE.DoubleSide
1367
+ }),
1368
+ wood: new THREE.MeshStandardMaterial({
1369
+ color: 0x8B4513, roughness: 0.9, metalness: 0.1
1370
+ }),
1371
+ darkWood: new THREE.MeshStandardMaterial({
1372
+ color: 0x5D2906, roughness: 0.85, metalness: 0.15
1373
+ }),
1374
+ redBrick: new THREE.MeshStandardMaterial({
1375
+ color: 0xA03C28, roughness: 0.9, metalness: 0.05
1376
+ }),
1377
+ metal: new THREE.MeshStandardMaterial({
1378
+ color: 0x888888, roughness: 0.4, metalness: 0.8
1379
+ }),
1380
+ rustedMetal: new THREE.MeshStandardMaterial({
1381
+ color: 0x964B00, roughness: 0.7, metalness: 0.6
1382
+ }),
1383
+ glass: new THREE.MeshStandardMaterial({
1384
+ color: 0x88CCFF, roughness: 0.1, metalness: 0.9, transparent: true, opacity: 0.4
1385
+ }),
1386
+ concrete: new THREE.MeshStandardMaterial({
1387
+ color: 0x888888, roughness: 0.9, metalness: 0.1
1388
+ }),
1389
+ damagedConcrete: new THREE.MeshStandardMaterial({
1390
+ color: 0x777777, roughness: 0.95, metalness: 0.05
1391
+ }),
1392
+ darkGreen: new THREE.MeshStandardMaterial({
1393
+ color: 0x225522, roughness: 0.8, metalness: 0.1
1394
+ }),
1395
+ skin: new THREE.MeshStandardMaterial({
1396
+ color: 0xE0AC69, roughness: 0.7, metalness: 0.1
1397
+ }),
1398
+ cloth: new THREE.MeshStandardMaterial({
1399
+ color: 0xCCAA88, roughness: 0.9, metalness: 0.0
1400
+ }),
1401
+ neonGlow: new THREE.MeshStandardMaterial({
1402
+ color: 0x00FFFF, roughness: 0.2, metalness: 0.8, emissive: 0x00FFFF, emissiveIntensity: 1.0
1403
+ }),
1404
+ goldMetal: new THREE.MeshStandardMaterial({
1405
+ color: 0xFFD700, roughness: 0.3, metalness: 1.0
1406
+ }),
1407
+ leather: new THREE.MeshStandardMaterial({
1408
+ color: 0x8B4513, roughness: 0.9, metalness: 0.1
1409
+ }),
1410
+ stone: new THREE.MeshStandardMaterial({
1411
+ color: 0x777777, roughness: 0.9, metalness: 0.1
1412
+ }),
1413
+ bone: new THREE.MeshStandardMaterial({
1414
+ color: 0xE3DAC9, roughness: 0.7, metalness: 0.1
1415
+ }),
1416
+ zombie: new THREE.MeshStandardMaterial({
1417
+ color: 0x7D9F85, roughness: 0.8, metalness: 0.1
1418
+ }),
1419
+ lava: new THREE.MeshStandardMaterial({
1420
+ color: 0xFF4500, roughness: 0.7, metalness: 0.3, emissive: 0xFF4500, emissiveIntensity: 0.8
1421
+ }),
1422
+ crystal: new THREE.MeshStandardMaterial({
1423
+ color: 0x88CCFF, roughness: 0.1, metalness: 0.9, transparent: true, opacity: 0.7
1424
+ }),
1425
+ energyBeam: new THREE.MeshStandardMaterial({
1426
+ color: 0x88FFFF, roughness: 0.1, metalness: 0.5, emissive: 0x88FFFF, emissiveIntensity: 1.0, transparent: true, opacity: 0.7
1427
+ })
1428
+ };
1429
 
1430
  function init() {
1431
  scene = new THREE.Scene();
 
1462
  // Define functions callable by Streamlit
1463
  window.teleportPlayer = teleportPlayer;
1464
  window.getSaveDataAndPosition = getSaveDataAndPosition;
1465
+ window.getSaveDataForNamedPlot = getSaveDataForNamedPlot;
1466
  window.resetNewlyPlacedObjects = resetNewlyPlacedObjects;
1467
 
1468
  console.log("Three.js Initialized. World ready.");
 
1502
  if (groundMeshes[gridKey]) return;
1503
  console.log(`Creating ${isPlaceholder ? 'placeholder' : 'initial'} ground at ${gridX}, ${gridZ}`);
1504
  const groundGeometry = new THREE.PlaneGeometry(plotWidth, plotDepth);
1505
+ const material = isPlaceholder ? materials.placeholderGround : materials.ground;
1506
  const groundMesh = new THREE.Mesh(groundGeometry, material);
1507
  groundMesh.rotation.x = -Math.PI / 2;
1508
  groundMesh.position.y = -0.05;
 
1531
  console.log("Finished loading initial objects.");
1532
  }
1533
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1534
  function saveUnsavedState() {
1535
  try {
1536
  const stateToSave = newlyPlacedObjects.map(obj => ({
 
1579
  return { userData: { type: type, obj_id: THREE.MathUtils.generateUUID() } };
1580
  }
1581
 
1582
+ function createAndPlaceObject(objData, isNewObject) {
1583
+ let loadedObject = null;
1584
+ switch (objData.type) {
1585
+ // Original objects
1586
+ case "Simple House": loadedObject = createSimpleHouse(); break;
1587
+ case "Tree": loadedObject = createTree(); break;
1588
+ case "Rock": loadedObject = createRock(); break;
1589
+ case "Fence Post": loadedObject = createFencePost(); break;
1590
+
1591
+ // Building category
1592
+ case "Cyberpunk Wall Panel": loadedObject = createCyberpunkWallPanel(); break;
1593
+ case "Modular Hab Block": loadedObject = createModularHabBlock(); break;
1594
+ case "MegaCorp Skyscraper": loadedObject = createMegaCorpSkyscraper(); break;
1595
+ case "Castle Wall Section": loadedObject = createCastleWallSection(); break;
1596
+ case "Wooden Door": loadedObject = createWoodenDoor(); break;
1597
+ case "House Roof Section": loadedObject = createHouseRoofSection(); break;
1598
+ case "Concrete Bunker Wall": loadedObject = createConcreteBunkerWall(); break;
1599
+ case "Damaged House Facade": loadedObject = createDamagedHouseFacade(); break;
1600
+
1601
+ // Nature category
1602
+ case "Pine Tree": loadedObject = createPineTree(); break;
1603
+ case "Boulder": loadedObject = createBoulder(); break;
1604
+ case "Alien Plant": loadedObject = createAlienPlant(); break;
1605
+ case "Floating Rock Platform": loadedObject = createFloatingRockPlatform(); break;
1606
+ case "Rubble Pile": loadedObject = createRubblePile(); break;
1607
+
1608
+ // Props category
1609
+ case "Rooftop AC Unit": loadedObject = createRooftopACUnit(); break;
1610
+ case "Holographic Window Display": loadedObject = createHolographicWindowDisplay(); break;
1611
+ case "Jersey Barrier": loadedObject = createJerseyBarrier(); break;
1612
+ case "Oil Drum": loadedObject = createOilDrum(); break;
1613
+ case "Canned Food": loadedObject = createCannedFood(); break;
1614
+ case "Treasure Chest": loadedObject = createTreasureChest(); break;
1615
+ case "Wall Torch": loadedObject = createWallTorch(); break;
1616
+ case "Bone Pile": loadedObject = createBonePile(); break;
1617
+
1618
+ // Characters category
1619
+ case "King Figure": loadedObject = createKingFigure(); break;
1620
+ case "Soldier Figure": loadedObject = createSoldierFigure(); break;
1621
+ case "Mage Figure": loadedObject = createMageFigure(); break;
1622
+ case "Zombie Figure": loadedObject = createZombieFigure(); break;
1623
+ case "Survivor Figure": loadedObject = createSurvivorFigure(); break;
1624
+ case "Dwarf Miner Figure": loadedObject = createDwarfMinerFigure(); break;
1625
+ case "Undead Knight Figure": loadedObject = createUndeadKnightFigure(); break;
1626
+ case "Hero Figure": loadedObject = createHeroFigure(); break;
1627
+
1628
+ // Vehicles category
1629
+ case "Wooden Cart": loadedObject = createWoodenCart(); break;
1630
+ case "Ballista": loadedObject = createBallista(); break;
1631
+ case "Siege Tower": loadedObject = createSiegeTower(); break;
1632
+ case "Buggy Frame": loadedObject = createBuggyFrame(); break;
1633
+ case "Motorbike": loadedObject = createMotorbike(); break;
1634
+ case "Hover Bike": loadedObject = createHoverBike(); break;
1635
+ case "APC": loadedObject = createAPC(); break;
1636
+ case "Sand Boat": loadedObject = createSandBoat(); break;
1637
+
1638
+ // Weapons category
1639
+ case "Makeshift Machete": loadedObject = createMakeshiftMachete(); break;
1640
+ case "Pistol Body": loadedObject = createPistolBody(); break;
1641
+ case "Scope Attachment": loadedObject = createScopeAttachment(); break;
1642
+ case "Laser Pistol": loadedObject = createLaserPistol(); break;
1643
+ case "Energy Sword": loadedObject = createEnergySword(); break;
1644
+ case "Dwarven Axe": loadedObject = createDwarvenAxe(); break;
1645
+ case "Magic Staff": loadedObject = createMagicStaff(); break;
1646
+
1647
+ // Effects category
1648
+ case "Candle Flame": loadedObject = createCandleFlame(); break;
1649
+ case "Dust Cloud": loadedObject = createDustCloud(); break;
1650
+ case "Blood Splat Decal": loadedObject = createBloodSplatDecal(); break;
1651
+ case "Burning Barrel Fire": loadedObject = createBurningBarrelFire(); break;
1652
+ case "Warp Tunnel Effect": loadedObject = createWarpTunnelEffect(); break;
1653
+ case "Laser Beam": loadedObject = createLaserBeam(); break;
1654
+ case "Gold Sparkle": loadedObject = createGoldSparkle(); break;
1655
+ case "Steam Vent": loadedObject = createSteamVent(); break;
1656
+
1657
+ default: console.warn("Unknown object type in data:", objData.type); break;
1658
+ }
1659
+ if (loadedObject) {
1660
+ // Apply stored position
1661
+ if (objData.position && objData.position.x !== undefined) {
1662
+ loadedObject.position.set(objData.position.x, objData.position.y, objData.position.z);
1663
+ } else if (objData.pos_x !== undefined) {
1664
+ loadedObject.position.set(objData.pos_x, objData.pos_y, objData.pos_z);
1665
  }
1666
+
1667
+ // Apply stored rotation
1668
+ if (objData.rotation) {
1669
+ loadedObject.rotation.set(objData.rotation._x, objData.rotation._y, objData.rotation._z, objData.rotation._order || 'XYZ');
1670
+ } else if (objData.rot_x !== undefined) {
1671
+ loadedObject.rotation.set(objData.rot_x, objData.rot_y, objData.rot_z, objData.rot_order || 'XYZ');
 
1672
  }
1673
+
1674
+ // Apply custom scale if it's a new object (not loading from save)
1675
+ if (isNewObject && window.CUSTOM_SCALE && window.CUSTOM_SCALE !== 1.0) {
1676
+ loadedObject.scale.set(window.CUSTOM_SCALE, window.CUSTOM_SCALE, window.CUSTOM_SCALE);
1677
+ }
1678
+
1679
+ // Apply custom rotation if it's a new object
1680
+ if (isNewObject && window.CUSTOM_ROTATION_Y) {
1681
+ loadedObject.rotation.y = window.CUSTOM_ROTATION_Y;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1682
  }
1683
+
1684
+ loadedObject.userData.obj_id = objData.obj_id || loadedObject.userData.obj_id;
1685
+ scene.add(loadedObject);
1686
+ if (isNewObject) newlyPlacedObjects.push(loadedObject);
1687
+ return loadedObject;
1688
  }
1689
+ return null;
1690
+ }