awacke1 commited on
Commit
27e5fda
·
verified ·
1 Parent(s): 2c71111

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +678 -18
index.html CHANGED
@@ -1,19 +1,679 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Three.js Infinite World Builder</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ overflow: hidden;
11
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
12
+ background-color: #333;
13
+ }
14
+ canvas {
15
+ display: block;
16
+ }
17
+ #ui-container {
18
+ position: absolute;
19
+ top: 10px;
20
+ left: 10px;
21
+ z-index: 10;
22
+ background: rgba(0,0,0,0.6);
23
+ padding: 10px;
24
+ border-radius: 8px;
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: 10px;
28
+ color: white;
29
+ max-width: 250px;
30
+ }
31
+ .ui-row {
32
+ display: flex;
33
+ justify-content: space-between;
34
+ align-items: center;
35
+ gap: 10px;
36
+ }
37
+ .ui-row label {
38
+ white-space: nowrap;
39
+ }
40
+ button, select, input[type="number"] {
41
+ background-color: #555;
42
+ color: white;
43
+ border: 1px solid #777;
44
+ padding: 8px;
45
+ border-radius: 5px;
46
+ cursor: pointer;
47
+ width: 100%;
48
+ box-sizing: border-box;
49
+ }
50
+ button:hover {
51
+ background-color: #666;
52
+ }
53
+ select, input[type="number"] {
54
+ -webkit-appearance: none;
55
+ -moz-appearance: none;
56
+ appearance: none;
57
+ background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23ffffff%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.4-5.4-13z%22%2F%3E%3C%2Fsvg%3E');
58
+ background-repeat: no-repeat;
59
+ background-position: right 10px top 50%;
60
+ background-size: .65em auto;
61
+ padding-right: 2em;
62
+ }
63
+ </style>
64
+ </head>
65
+ <body>
66
+ <!-- UI Elements for User Interaction -->
67
+ <div id="ui-container">
68
+ <div class="ui-row">
69
+ <button id="newWorldButton">New World</button>
70
+ </div>
71
+ <div class="ui-row">
72
+ <button id="loadWorldButton">Load World</button>
73
+ <input type="file" id="loadWorldInput" accept=".json" style="display: none;">
74
+ </div>
75
+ <div class="ui-row">
76
+ <button id="saveWorldButton">Save World</button>
77
+ </div>
78
+ <hr style="border-color: #555; width:100%;">
79
+ <div class="ui-row">
80
+ <label for="objectSelect">Object:</label>
81
+ <select id="objectSelect"></select>
82
+ </div>
83
+ <div class="ui-row">
84
+ <label for="scaleInput">Scale:</label>
85
+ <input type="number" id="scaleInput" value="1.0" step="0.1" min="0.1">
86
+ </div>
87
+ <div class="ui-row">
88
+ <label for="rotationInput">Rotation (Y):</label>
89
+ <input type="number" id="rotationInput" value="0" step="15" min="0" max="345">
90
+ </div>
91
+ </div>
92
+
93
+ <!-- Import map for Three.js -->
94
+ <script type="importmap">
95
+ {
96
+ "imports": {
97
+ "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
98
+ "three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/"
99
+ }
100
+ }
101
+ </script>
102
+
103
+ <!-- Main Application Logic -->
104
+ <script type="module">
105
+ import * as THREE from 'three';
106
+
107
+ // --- Core Scene Variables ---
108
+ let scene, camera, renderer, playerMesh;
109
+ let raycaster, mouse;
110
+ const keysPressed = {};
111
+ const playerSpeed = 0.15;
112
+
113
+ // --- World State Management ---
114
+ let worldObjects = []; // This is the single source of truth for all placed objects
115
+ const groundMeshes = {}; // Store ground mesh references by grid key
116
+
117
+ // --- World Configuration ---
118
+ const PLOT_WIDTH = 50.0;
119
+ const PLOT_DEPTH = 50.0;
120
+
121
+ // --- Materials ---
122
+ const groundMaterial = new THREE.MeshStandardMaterial({
123
+ color: 0x55aa55, roughness: 0.9, metalness: 0.1, side: THREE.DoubleSide
124
+ });
125
+
126
+ // --- Object Creation Map ---
127
+ const objectFactory = {};
128
+
129
+ // --- UI Elements ---
130
+ const newWorldButton = document.getElementById('newWorldButton');
131
+ const saveWorldButton = document.getElementById('saveWorldButton');
132
+ const loadWorldButton = document.getElementById('loadWorldButton');
133
+ const loadWorldInput = document.getElementById('loadWorldInput');
134
+ const objectSelect = document.getElementById('objectSelect');
135
+ const scaleInput = document.getElementById('scaleInput');
136
+ const rotationInput = document.getElementById('rotationInput');
137
+
138
+ /**
139
+ * Initializes the entire application.
140
+ */
141
+ function init() {
142
+ scene = new THREE.Scene();
143
+ scene.background = new THREE.Color(0xabcdef);
144
+
145
+ const aspect = window.innerWidth / window.innerHeight;
146
+ camera = new THREE.PerspectiveCamera(60, aspect, 0.1, 4000);
147
+ camera.position.set(0, 15, 20);
148
+ camera.lookAt(0, 0, 0);
149
+ scene.add(camera);
150
+
151
+ setupLighting();
152
+ setupPlayer();
153
+ populateObjectFactory();
154
+ populateObjectSelector();
155
+
156
+ raycaster = new THREE.Raycaster();
157
+ mouse = new THREE.Vector2();
158
+
159
+ renderer = new THREE.WebGLRenderer({ antialias: true });
160
+ renderer.setSize(window.innerWidth, window.innerHeight);
161
+ renderer.shadowMap.enabled = true;
162
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
163
+ document.body.appendChild(renderer.domElement);
164
+
165
+ addEventListeners();
166
+
167
+ resetWorld(); // Start with a fresh world
168
+
169
+ console.log("Three.js Initialized. World ready.");
170
+ animate();
171
+ }
172
+
173
+ /**
174
+ * Sets up lights for the scene.
175
+ */
176
+ function setupLighting() {
177
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
178
+ scene.add(ambientLight);
179
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
180
+ directionalLight.position.set(50, 150, 100);
181
+ directionalLight.castShadow = true;
182
+ directionalLight.shadow.mapSize.width = 4096;
183
+ directionalLight.shadow.mapSize.height = 4096;
184
+ directionalLight.shadow.camera.near = 0.5;
185
+ directionalLight.shadow.camera.far = 500;
186
+ directionalLight.shadow.camera.left = -150;
187
+ directionalLight.shadow.camera.right = 150;
188
+ directionalLight.shadow.camera.top = 150;
189
+ directionalLight.shadow.camera.bottom = -150;
190
+ directionalLight.shadow.bias = -0.001;
191
+ scene.add(directionalLight);
192
+ }
193
+
194
+ /**
195
+ * Creates the player representation in the scene.
196
+ */
197
+ function setupPlayer() {
198
+ const playerGeo = new THREE.CapsuleGeometry(0.4, 0.8, 4, 8);
199
+ const playerMat = new THREE.MeshStandardMaterial({ color: 0x0000ff, roughness: 0.6 });
200
+ playerMesh = new THREE.Mesh(playerGeo, playerMat);
201
+ playerMesh.castShadow = true;
202
+ playerMesh.receiveShadow = true;
203
+ scene.add(playerMesh);
204
+ }
205
+
206
+ /**
207
+ * Clears the world and resets the state to a default.
208
+ */
209
+ function resetWorld() {
210
+ console.log("Creating a new world.");
211
+ // Clear existing objects
212
+ worldObjects.forEach(objData => {
213
+ const object = scene.getObjectByProperty('uuid', objData.uuid);
214
+ if (object) {
215
+ scene.remove(object);
216
+ }
217
+ });
218
+ worldObjects = [];
219
+
220
+ // Clear ground planes
221
+ for (const key in groundMeshes) {
222
+ scene.remove(groundMeshes[key]);
223
+ delete groundMeshes[key];
224
+ }
225
+
226
+ // Create the initial ground plane at (0,0)
227
+ createGroundPlane(0, 0);
228
+
229
+ // Reset player position
230
+ playerMesh.position.set(PLOT_WIDTH / 2, 0.4 + 0.8 / 2, PLOT_DEPTH / 2);
231
+ updateCamera(true); // Force camera snap
232
+ }
233
+
234
+ /**
235
+ * Populates the world based on data from a loaded file.
236
+ * @param {object} worldData - The parsed JSON data from a world file.
237
+ */
238
+ function populateWorld(worldData) {
239
+ console.log("Loading world from file...");
240
+ resetWorld(); // Ensure the scene is clean before loading
241
+
242
+ if (worldData.objects && Array.isArray(worldData.objects)) {
243
+ worldData.objects.forEach(objData => {
244
+ // Create and place the object in the scene
245
+ const newObject = createObject(objData.type);
246
+ if (newObject) {
247
+ newObject.position.copy(objData.position);
248
+ newObject.rotation.set(objData.rotation._x, objData.rotation._y, objData.rotation._z, objData.rotation._order);
249
+ newObject.scale.copy(objData.scale);
250
+ newObject.userData.obj_id = objData.obj_id; // Preserve original ID
251
+ scene.add(newObject);
252
+
253
+ // Add its data to our source of truth, including its new scene UUID
254
+ const newObjData = { ...objData, uuid: newObject.uuid };
255
+ worldObjects.push(newObjData);
256
+ }
257
+ });
258
+ }
259
+
260
+ // Generate all necessary ground planes for the loaded objects
261
+ generateGroundForLoadedWorld();
262
+
263
+ // Set player position if it exists in the save file
264
+ if (worldData.playerPosition) {
265
+ playerMesh.position.copy(worldData.playerPosition);
266
+ }
267
+
268
+ updateCamera(true); // Snap camera to new position
269
+ console.log(`Loaded ${worldObjects.length} objects.`);
270
+ }
271
+
272
+ /**
273
+ * Creates a ground plane at a specific grid coordinate.
274
+ * @param {number} gridX - The x-coordinate in the grid.
275
+ * @param {number} gridZ - The z-coordinate in the grid.
276
+ */
277
+ function createGroundPlane(gridX, gridZ) {
278
+ const gridKey = `${gridX}_${gridZ}`;
279
+ if (groundMeshes[gridKey]) return; // Don't create if it already exists
280
+
281
+ console.log(`Creating ground at ${gridX}, ${gridZ}`);
282
+ const groundGeometry = new THREE.PlaneGeometry(PLOT_WIDTH, PLOT_DEPTH);
283
+ const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
284
+ groundMesh.rotation.x = -Math.PI / 2;
285
+ groundMesh.position.y = -0.05;
286
+ groundMesh.position.x = gridX * PLOT_WIDTH + PLOT_WIDTH / 2.0;
287
+ groundMesh.position.z = gridZ * PLOT_DEPTH + PLOT_DEPTH / 2.0;
288
+ groundMesh.receiveShadow = true;
289
+ groundMesh.userData.gridKey = gridKey;
290
+ scene.add(groundMesh);
291
+ groundMeshes[gridKey] = groundMesh;
292
+ }
293
+
294
+ /**
295
+ * After loading a world, this ensures all necessary ground tiles are created.
296
+ */
297
+ function generateGroundForLoadedWorld() {
298
+ const requiredGrids = new Set();
299
+ worldObjects.forEach(objData => {
300
+ const gridX = Math.floor(objData.position.x / PLOT_WIDTH);
301
+ const gridZ = Math.floor(objData.position.z / PLOT_DEPTH);
302
+ requiredGrids.add(`${gridX}_${gridZ}`);
303
+ });
304
+
305
+ // Also add grid for player
306
+ const playerGridX = Math.floor(playerMesh.position.x / PLOT_WIDTH);
307
+ const playerGridZ = Math.floor(playerMesh.position.z / PLOT_DEPTH);
308
+ requiredGrids.add(`${playerGridX}_${playerGridZ}`);
309
+
310
+ requiredGrids.forEach(key => {
311
+ const [gridX, gridZ] = key.split('_').map(Number);
312
+ createGroundPlane(gridX, gridZ);
313
+ });
314
+ }
315
+
316
+ /**
317
+ * Creates a generic base for any object, assigning a type and unique ID.
318
+ * @param {string} type - The type name of the object.
319
+ * @returns {object} - An object with userData containing type and a new UUID.
320
+ */
321
+ function createObjectBase(type) {
322
+ return { userData: { type: type, obj_id: THREE.MathUtils.generateUUID() } };
323
+ }
324
+
325
+ /**
326
+ * Central function to create a 3D object based on its type string.
327
+ * @param {string} type - The type of object to create.
328
+ * @returns {THREE.Object3D|null} The created Three.js object or null if type is unknown.
329
+ */
330
+ function createObject(type) {
331
+ if (objectFactory[type]) {
332
+ return objectFactory[type]();
333
+ }
334
+ console.warn("Unknown object type:", type);
335
+ return null;
336
+ }
337
+
338
+ // --- All Object Creation Functions ---
339
+ // (Abridged for brevity, the full list is included below this script block)
340
+ function createSimpleHouse() {
341
+ const base = createObjectBase("Simple House");
342
+ const group = new THREE.Group();
343
+ Object.assign(group, base);
344
+ const mat1 = new THREE.MeshStandardMaterial({color:0xffccaa, roughness:0.8});
345
+ const mat2 = new THREE.MeshStandardMaterial({color:0xaa5533, roughness:0.7});
346
+ const m1 = new THREE.Mesh(new THREE.BoxGeometry(2,1.5,2.5), mat1);
347
+ m1.position.y = 1.5/2;
348
+ m1.castShadow = true; m1.receiveShadow = true;
349
+ group.add(m1);
350
+ const m2 = new THREE.Mesh(new THREE.ConeGeometry(1.8,1,4), mat2);
351
+ m2.position.y = 1.5+1/2;
352
+ m2.rotation.y = Math.PI/4;
353
+ m2.castShadow = true; m2.receiveShadow = true;
354
+ group.add(m2);
355
+ return group;
356
+ }
357
+
358
+ function createTree() {
359
+ const base = createObjectBase("Tree");
360
+ const group = new THREE.Group();
361
+ Object.assign(group, base);
362
+ const mat1 = new THREE.MeshStandardMaterial({color:0x8B4513, roughness:0.9});
363
+ const mat2 = new THREE.MeshStandardMaterial({color:0x228B22, roughness:0.8});
364
+ const m1 = new THREE.Mesh(new THREE.CylinderGeometry(0.3,0.4,2,8), mat1);
365
+ m1.position.y = 1;
366
+ m1.castShadow = true; m1.receiveShadow = true;
367
+ group.add(m1);
368
+ const m2 = new THREE.Mesh(new THREE.IcosahedronGeometry(1.2,0), mat2);
369
+ m2.position.y = 2.8;
370
+ m2.castShadow = true; m2.receiveShadow = true;
371
+ group.add(m2);
372
+ return group;
373
+ }
374
+
375
+ function createRock() {
376
+ const base = createObjectBase("Rock");
377
+ const mat = new THREE.MeshStandardMaterial({color:0xaaaaaa, roughness:0.8, metalness:0.1});
378
+ const rock = new THREE.Mesh(new THREE.IcosahedronGeometry(0.7,0), mat);
379
+ Object.assign(rock, base);
380
+ rock.position.y = 0.35;
381
+ rock.rotation.set(Math.random()*Math.PI, Math.random()*Math.PI, 0);
382
+ rock.castShadow = true; rock.receiveShadow = true;
383
+ return rock;
384
+ }
385
+
386
+ // --- File and State Management ---
387
+
388
+ /**
389
+ * Gathers all world data and triggers a download of the world file.
390
+ */
391
+ function saveWorldToFile() {
392
+ console.log("Saving world to file...");
393
+ const dataToSave = {
394
+ meta: {
395
+ version: "1.0",
396
+ plotWidth: PLOT_WIDTH,
397
+ plotDepth: PLOT_DEPTH,
398
+ savedAt: new Date().toISOString()
399
+ },
400
+ playerPosition: playerMesh.position.clone(),
401
+ objects: worldObjects.map(objData => {
402
+ // We only need to store the data, not the live scene object reference
403
+ const { uuid, ...rest } = objData;
404
+ return rest;
405
+ })
406
+ };
407
+
408
+ const jsonString = JSON.stringify(dataToSave, null, 2);
409
+ const blob = new Blob([jsonString], { type: 'application/json' });
410
+ const url = URL.createObjectURL(blob);
411
+
412
+ const a = document.createElement('a');
413
+ a.href = url;
414
+ a.download = `world_${Date.now()}.json`;
415
+ document.body.appendChild(a);
416
+ a.click();
417
+ document.body.removeChild(a);
418
+ URL.revokeObjectURL(url);
419
+ console.log(`World saved with ${dataToSave.objects.length} objects.`);
420
+ }
421
+
422
+ /**
423
+ * Handles the file selection for loading a world.
424
+ * @param {Event} event - The file input change event.
425
+ */
426
+ function handleWorldFileLoad(event) {
427
+ const file = event.target.files[0];
428
+ if (!file) return;
429
+
430
+ const reader = new FileReader();
431
+ reader.onload = (e) => {
432
+ try {
433
+ const worldData = JSON.parse(e.target.result);
434
+ populateWorld(worldData);
435
+ } catch (error) {
436
+ console.error("Error parsing world file:", error);
437
+ alert("Failed to load world file. It might be corrupted.");
438
+ }
439
+ };
440
+ reader.readAsText(file);
441
+ }
442
+
443
+ // --- Event Handlers ---
444
+
445
+ function addEventListeners() {
446
+ document.addEventListener('mousemove', onMouseMove, false);
447
+ document.addEventListener('click', onDocumentClick, false);
448
+ window.addEventListener('resize', onWindowResize, false);
449
+ document.addEventListener('keydown', onKeyDown);
450
+ document.addEventListener('keyup', onKeyUp);
451
+
452
+ newWorldButton.addEventListener('click', () => {
453
+ if (confirm('Are you sure you want to start a new world? Any unsaved changes will be lost.')) {
454
+ resetWorld();
455
+ }
456
+ });
457
+ saveWorldButton.addEventListener('click', saveWorldToFile);
458
+ loadWorldButton.addEventListener('click', () => loadWorldInput.click());
459
+ loadWorldInput.addEventListener('change', handleWorldFileLoad);
460
+ }
461
+
462
+ function onMouseMove(event) {
463
+ // Adjust for UI offset if mouse is over it
464
+ const uiRect = document.getElementById('ui-container').getBoundingClientRect();
465
+ if (event.clientX < uiRect.right && event.clientY < uiRect.bottom) {
466
+ mouse.x = -99; // Prevent placement when mouse is over UI
467
+ mouse.y = -99;
468
+ return;
469
+ }
470
+ mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
471
+ mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
472
+ }
473
+
474
+ function onDocumentClick(event) {
475
+ if (mouse.x === -99) return; // Don't place if mouse was over UI
476
+
477
+ const selectedObjectType = objectSelect.value;
478
+ if (selectedObjectType === "None") return;
479
+
480
+ const groundCandidates = Object.values(groundMeshes);
481
+ if (groundCandidates.length === 0) return;
482
+
483
+ raycaster.setFromCamera(mouse, camera);
484
+ const intersects = raycaster.intersectObjects(groundCandidates);
485
+
486
+ if (intersects.length > 0) {
487
+ const intersectPoint = intersects[0].point;
488
+ const newObject = createObject(selectedObjectType);
489
+
490
+ if (newObject) {
491
+ newObject.position.copy(intersectPoint);
492
+ newObject.position.y += 0.01; // Prevent z-fighting
493
+
494
+ const scale = parseFloat(scaleInput.value) || 1.0;
495
+ const rotationY = THREE.MathUtils.degToRad(parseFloat(rotationInput.value) || 0);
496
+
497
+ newObject.scale.setScalar(scale);
498
+ newObject.rotation.y = rotationY;
499
+
500
+ scene.add(newObject);
501
+
502
+ // Add the new object's data to our main array
503
+ const newObjectData = {
504
+ obj_id: newObject.userData.obj_id,
505
+ uuid: newObject.uuid, // Keep track of the live object in the scene
506
+ type: newObject.userData.type,
507
+ position: newObject.position.clone(),
508
+ rotation: { _x: newObject.rotation.x, _y: newObject.rotation.y, _z: newObject.rotation.z, _order: newObject.rotation.order },
509
+ scale: newObject.scale.clone()
510
+ };
511
+ worldObjects.push(newObjectData);
512
+
513
+ console.log(`Placed new ${selectedObjectType}. Total objects: ${worldObjects.length}`);
514
+ }
515
+ }
516
+ }
517
+
518
+ function onKeyDown(event) { keysPressed[event.code] = true; }
519
+ function onKeyUp(event) { keysPressed[event.code] = false; }
520
+ function onWindowResize() {
521
+ camera.aspect = window.innerWidth / window.innerHeight;
522
+ camera.updateProjectionMatrix();
523
+ renderer.setSize(window.innerWidth, window.innerHeight);
524
+ }
525
+
526
+ // --- Game Loop and Updates ---
527
+
528
+ function updatePlayerMovement() {
529
+ if (!playerMesh) return;
530
+ const moveDirection = new THREE.Vector3(0, 0, 0);
531
+ if (keysPressed['KeyW'] || keysPressed['ArrowUp']) moveDirection.z -= 1;
532
+ if (keysPressed['KeyS'] || keysPressed['ArrowDown']) moveDirection.z += 1;
533
+ if (keysPressed['KeyA'] || keysPressed['ArrowLeft']) moveDirection.x -= 1;
534
+ if (keysPressed['KeyD'] || keysPressed['ArrowRight']) moveDirection.x += 1;
535
+
536
+ if (moveDirection.lengthSq() > 0) {
537
+ moveDirection.normalize().multiplyScalar(playerSpeed);
538
+
539
+ const forward = new THREE.Vector3();
540
+ camera.getWorldDirection(forward);
541
+ forward.y = 0;
542
+ forward.normalize();
543
+
544
+ const right = new THREE.Vector3().crossVectors(camera.up, forward).normalize();
545
+
546
+ const worldMove = new THREE.Vector3();
547
+ worldMove.add(forward.multiplyScalar(-moveDirection.z));
548
+ worldMove.add(right.multiplyScalar(-moveDirection.x));
549
+ worldMove.normalize().multiplyScalar(playerSpeed);
550
+
551
+ playerMesh.position.add(worldMove);
552
+ playerMesh.position.y = Math.max(playerMesh.position.y, 0.4 + 0.8/2);
553
+
554
+ checkAndExpandGround();
555
+ }
556
+ }
557
+
558
+ function checkAndExpandGround() {
559
+ if (!playerMesh) return;
560
+ const currentGridX = Math.floor(playerMesh.position.x / PLOT_WIDTH);
561
+ const currentGridZ = Math.floor(playerMesh.position.z / PLOT_DEPTH);
562
+
563
+ // Check a 3x3 area around the player
564
+ for (let dx = -1; dx <= 1; dx++) {
565
+ for (let dz = -1; dz <= 1; dz++) {
566
+ createGroundPlane(currentGridX + dx, currentGridZ + dz);
567
+ }
568
+ }
569
+ }
570
+
571
+ function updateCamera(forceSnap = false) {
572
+ if (!playerMesh) return;
573
+ const offset = new THREE.Vector3(0, 15, 20);
574
+ const targetPosition = playerMesh.position.clone().add(offset);
575
+
576
+ if (forceSnap) {
577
+ camera.position.copy(targetPosition);
578
+ } else {
579
+ camera.position.lerp(targetPosition, 0.08);
580
+ }
581
+
582
+ camera.lookAt(playerMesh.position);
583
+ }
584
+
585
+ function animate() {
586
+ requestAnimationFrame(animate);
587
+ updatePlayerMovement();
588
+ updateCamera();
589
+ renderer.render(scene, camera);
590
+ }
591
+
592
+ // --- Full Object Factory ---
593
+ function populateObjectFactory() {
594
+ Object.assign(objectFactory, {
595
+ "Simple House": createSimpleHouse, "Cyberpunk Wall Panel": createCyberpunkWallPanel, "Modular Hab Block": createModularHabBlock, "MegaCorp Skyscraper": createMegaCorpSkyscraper,
596
+ "Castle Wall Section": createCastleWallSection, "Wooden Door": createWoodenDoor, "House Roof Section": createHouseRoofSection, "Concrete Bunker Wall": createConcreteBunkerWall,
597
+ "Damaged House Facade": createDamagedHouseFacade, "Tree": createTree, "Rock": createRock, "Pine Tree": createPineTree, "Boulder": createBoulder, "Alien Plant": createAlienPlant,
598
+ "Floating Rock Platform": createFloatingRockPlatform, "Rubble Pile": createRubblePile, "Fence Post": createFencePost, "Rooftop AC Unit": createRooftopACUnit,
599
+ "Holographic Window Display": createHolographicWindowDisplay, "Jersey Barrier": createJerseyBarrier, "Oil Drum": createOilDrum, "Canned Food": createCannedFood,
600
+ "Treasure Chest": createTreasureChest, "Wall Torch": createWallTorch, "Bone Pile": createBonePile, "King Figure": createKingFigure, "Soldier Figure": createSoldierFigure,
601
+ "Mage Figure": createMageFigure, "Zombie Figure": createZombieFigure, "Survivor Figure": createSurvivorFigure, "Dwarf Miner Figure": createDwarfMinerFigure,
602
+ "Undead Knight Figure": createUndeadKnightFigure, "Hero Figure": createHeroFigure, "Wooden Cart": createWoodenCart, "Ballista": createBallista, "Siege Tower": createSiegeTower,
603
+ "Buggy Frame": createBuggyFrame, "Motorbike": createMotorbike, "Hover Bike": createHoverBike, "APC": createAPC, "Sand Boat": createSandBoat, "Makeshift Machete": createMakeshiftMachete,
604
+ "Pistol Body": createPistolBody, "Scope Attachment": createScopeAttachment, "Laser Pistol": createLaserPistol, "Energy Sword": createEnergySword, "Dwarven Axe": createDwarvenAxe,
605
+ "Magic Staff": createMagicStaff, "Candle Flame": createCandleFlame, "Dust Cloud": createDustCloud, "Blood Splat Decal": createBloodSplatDecal,
606
+ "Burning Barrel Fire": createBurningBarrelFire, "Warp Tunnel Effect": createWarpTunnelEffect, "Laser Beam": createLaserBeam, "Gold Sparkle": createGoldSparkle, "Steam Vent": createSteamVent
607
+ });
608
+ }
609
+
610
+ function populateObjectSelector() {
611
+ const options = ["None", ...Object.keys(objectFactory)];
612
+ options.forEach(name => {
613
+ const option = document.createElement('option');
614
+ option.value = name;
615
+ option.textContent = name;
616
+ objectSelect.appendChild(option);
617
+ });
618
+ }
619
+
620
+ // --- All the create... functions from the original file go here ---
621
+ function createFencePost(){const b=createObjectBase("Fence Post"),a=new THREE.MeshStandardMaterial({color:14596231,roughness:.9}),c=new THREE.Mesh(new THREE.BoxGeometry(.2,1.5,.2),a);return Object.assign(c,b),c.position.y=.75,c.castShadow=!0,c.receiveShadow=!0,c}
622
+ function createCyberpunkWallPanel(){const b=createObjectBase("Cyberpunk Wall Panel"),a=new THREE.MeshStandardMaterial({color:47083,roughness:.5,metalness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(2,3,.2),a);return Object.assign(c,b),c.position.y=1.5,c.castShadow=!0,c.receiveShadow=!0,c}
623
+ function createModularHabBlock(){const b=createObjectBase("Modular Hab Block"),a=new THREE.Group;Object.assign(a,b);const c=new THREE.MeshStandardMaterial({color:6710886,roughness:.7}),d=new THREE.Mesh(new THREE.BoxGeometry(3,2,3),c);return d.position.y=1,d.castShadow=!0,d.receiveShadow=!0,a.add(d),a}
624
+ function createMegaCorpSkyscraper(){const b=createObjectBase("MegaCorp Skyscraper"),a=new THREE.Group;Object.assign(a,b);const c=new THREE.MeshStandardMaterial({color:3355545,roughness:.4,metalness:.9}),d=new THREE.Mesh(new THREE.BoxGeometry(4,10,4),c);return d.position.y=5,d.castShadow=!0,d.receiveShadow=!0,a.add(d),a}
625
+ function createCastleWallSection(){const b=createObjectBase("Castle Wall Section"),a=new THREE.MeshStandardMaterial({color:8421504,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(5,3,1),a);return Object.assign(c,b),c.position.y=1.5,c.castShadow=!0,c.receiveShadow=!0,c}
626
+ function createWoodenDoor(){const b=createObjectBase("Wooden Door"),a=new THREE.MeshStandardMaterial({color:9127187,roughness:.9}),c=new THREE.Mesh(new THREE.BoxGeometry(1,2,.2),a);return Object.assign(c,b),c.position.y=1,c.castShadow=!0,c.receiveShadow=!0,c}
627
+ function createHouseRoofSection(){const b=createObjectBase("House Roof Section"),a=new THREE.MeshStandardMaterial({color:8388608,roughness:.7}),c=new THREE.Mesh(new THREE.ConeGeometry(2,1,4),a);return Object.assign(c,b),c.position.y=1,c.rotation.y=Math.PI/4,c.castShadow=!0,c.receiveShadow=!0,c}
628
+ function createConcreteBunkerWall(){const b=createObjectBase("Concrete Bunker Wall"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.9}),c=new THREE.Mesh(new THREE.BoxGeometry(4,2,1),a);return Object.assign(c,b),c.position.y=1,c.castShadow=!0,c.receiveShadow=!0,c}
629
+ function createDamagedHouseFacade(){const b=createObjectBase("Damaged House Facade"),a=new THREE.MeshStandardMaterial({color:11184810,roughness:.9}),c=new THREE.Mesh(new THREE.BoxGeometry(3,2,.3),a);return Object.assign(c,b),c.position.y=1,c.castShadow=!0,c.receiveShadow=!0,c}
630
+ function createPineTree(){const c=createObjectBase("Pine Tree"),a=new THREE.Group;Object.assign(a,c);const d=new THREE.MeshStandardMaterial({color:9127187,roughness:.9}),b=new THREE.MeshStandardMaterial({color:25600,roughness:.8}),e=new THREE.Mesh(new THREE.CylinderGeometry(.2,.3,1.5,8),d);e.position.y=.75,e.castShadow=!0,e.receiveShadow=!0,a.add(e);const f=new THREE.Mesh(new THREE.ConeGeometry(1,2,8),b);return f.position.y=2,f.castShadow=!0,f.receiveShadow=!0,a.add(f),a}
631
+ function createBoulder(){const b=createObjectBase("Boulder"),a=new THREE.MeshStandardMaterial({color:6710886,roughness:.8}),c=new THREE.Mesh(new THREE.IcosahedronGeometry(1,0),a);return Object.assign(c,b),c.position.y=.5,c.rotation.set(Math.random()*Math.PI,Math.random()*Math.PI,0),c.castShadow=!0,c.receiveShadow=!0,c}
632
+ function createAlienPlant(){const c=createObjectBase("Alien Plant"),a=new THREE.Group;Object.assign(a,c);const b=new THREE.MeshStandardMaterial({color:65280,roughness:.7}),d=new THREE.Mesh(new THREE.CylinderGeometry(.1,.1,1,8),b);d.position.y=.5,d.castShadow=!0,d.receiveShadow=!0,a.add(d);const e=new THREE.Mesh(new THREE.SphereGeometry(.3,8,8),b);return e.position.y=1,e.castShadow=!0,e.receiveShadow=!0,a.add(e),a}
633
+ function createFloatingRockPlatform(){const b=createObjectBase("Floating Rock Platform"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(3,.5,3),a);return Object.assign(c,b),c.position.y=2,c.castShadow=!0,c.receiveShadow=!0,c}
634
+ function createRubblePile(){const b=createObjectBase("Rubble Pile"),a=new THREE.Group;Object.assign(a,b);const c=new THREE.MeshStandardMaterial({color:7829367,roughness:.9});for(let d=0;d<5;d++){const e=new THREE.Mesh(new THREE.IcosahedronGeometry(.3,0),c);e.position.set(Math.random()*.5-.25,Math.random()*.3,Math.random()*.5-.25),e.castShadow=!0,e.receiveShadow=!0,a.add(e)}return a}
635
+ function createRooftopACUnit(){const b=createObjectBase("Rooftop AC Unit"),a=new THREE.MeshStandardMaterial({color:6710886,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(1,.8,1),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
636
+ function createHolographicWindowDisplay(){const b=createObjectBase("Holographic Window Display"),a=new THREE.MeshStandardMaterial({color:47083,roughness:.5,transparent:!0,opacity:.7}),c=new THREE.Mesh(new THREE.PlaneGeometry(1,1),a);return Object.assign(c,b),c.position.y=1,c.castShadow=!1,c.receiveShadow=!1,c}
637
+ function createJerseyBarrier(){const b=createObjectBase("Jersey Barrier"),a=new THREE.MeshStandardMaterial({color:8947848,roughness:.9}),c=new THREE.Mesh(new THREE.BoxGeometry(2,.8,.5),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
638
+ function createOilDrum(){const b=createObjectBase("Oil Drum"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.7,metalness:.3}),c=new THREE.Mesh(new THREE.CylinderGeometry(.4,.4,1,12),a);return Object.assign(c,b),c.position.y=.5,c.castShadow=!0,c.receiveShadow=!0,c}
639
+ function createCannedFood(){const b=createObjectBase("Canned Food"),a=new THREE.MeshStandardMaterial({color:11184810,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.CylinderGeometry(.1,.1,.2,12),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!0,c}
640
+ function createTreasureChest(){const b=createObjectBase("Treasure Chest"),a=new THREE.MeshStandardMaterial({color:9127187,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(1,.6,.8),a);return Object.assign(c,b),c.position.y=.3,c.castShadow=!0,c.receiveShadow=!0,c}
641
+ function createWallTorch(){const c=createObjectBase("Wall Torch"),a=new THREE.Group;Object.assign(a,c);const d=new THREE.MeshStandardMaterial({color:14596231,roughness:.9}),b=new THREE.MeshStandardMaterial({color:16729344,roughness:.5}),e=new THREE.Mesh(new THREE.BoxGeometry(.1,.5,.1),d);e.position.y=.25,e.castShadow=!0,e.receiveShadow=!0,a.add(e);const f=new THREE.Mesh(new THREE.SphereGeometry(.1,8,8),b);return f.position.y=.5,f.castShadow=!0,f.receiveShadow=!1,a.add(f),a}
642
+ function createBonePile(){const b=createObjectBase("Bone Pile"),a=new THREE.Group;Object.assign(a,b);const c=new THREE.MeshStandardMaterial({color:14540253,roughness:.9});for(let d=0;d<3;d++){const e=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,.3,8),c);e.position.set(Math.random()*.2-.1,Math.random()*.1,Math.random()*.2-.1),e.rotation.set(Math.random()*Math.PI,Math.random()*Math.PI,Math.random()*Math.PI),e.castShadow=!0,e.receiveShadow=!0,a.add(e)}return a}
643
+ function createKingFigure(){const b=createObjectBase("King Figure"),a=new THREE.MeshStandardMaterial({color:16766720,roughness:.6}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
644
+ function createSoldierFigure(){const b=createObjectBase("Soldier Figure"),a=new THREE.MeshStandardMaterial({color:2263842,roughness:.7}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
645
+ function createMageFigure(){const b=createObjectBase("Mage Figure"),a=new THREE.MeshStandardMaterial({color:8388736,roughness:.7}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
646
+ function createZombieFigure(){const b=createObjectBase("Zombie Figure"),a=new THREE.MeshStandardMaterial({color:9498256,roughness:.8}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
647
+ function createSurvivorFigure(){const b=createObjectBase("Survivor Figure"),a=new THREE.MeshStandardMaterial({color:4620980,roughness:.7}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
648
+ function createDwarfMinerFigure(){const b=createObjectBase("Dwarf Miner Figure"),a=new THREE.MeshStandardMaterial({color:9127187,roughness:.7}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.5,4,8),a);return Object.assign(c,b),c.position.y=.35,c.castShadow=!0,c.receiveShadow=!0,c}
649
+ function createUndeadKnightFigure(){const b=createObjectBase("Undead Knight Figure"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.8}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
650
+ function createHeroFigure(){const b=createObjectBase("Hero Figure"),a=new THREE.MeshStandardMaterial({color:16711680,roughness:.6}),c=new THREE.Mesh(new THREE.CapsuleGeometry(.2,.6,4,8),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
651
+ function createWoodenCart(){const b=createObjectBase("Wooden Cart"),a=new THREE.MeshStandardMaterial({color:9127187,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(2,.8,1),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
652
+ function createBallista(){const b=createObjectBase("Ballista"),a=new THREE.MeshStandardMaterial({color:14596231,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(1.5,.8,.5),a);return Object.assign(c,b),c.position.y=.4,c.castShadow=!0,c.receiveShadow=!0,c}
653
+ function createSiegeTower(){const b=createObjectBase("Siege Tower"),a=new THREE.MeshStandardMaterial({color:9127187,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(2,4,2),a);return Object.assign(c,b),c.position.y=2,c.castShadow=!0,c.receiveShadow=!0,c}
654
+ function createBuggyFrame(){const b=createObjectBase("Buggy Frame"),a=new THREE.MeshStandardMaterial({color:6710886,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(2,.5,1),a);return Object.assign(c,b),c.position.y=.25,c.castShadow=!0,c.receiveShadow=!0,c}
655
+ function createMotorbike(){const b=createObjectBase("Motorbike"),a=new THREE.MeshStandardMaterial({color:3355443,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(1.5,.6,.4),a);return Object.assign(c,b),c.position.y=.3,c.castShadow=!0,c.receiveShadow=!0,c}
656
+ function createHoverBike(){const b=createObjectBase("Hover Bike"),a=new THREE.MeshStandardMaterial({color:47083,roughness:.5,metalness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(1.5,.4,.4),a);return Object.assign(c,b),c.position.y=.5,c.castShadow=!0,c.receiveShadow=!0,c}
657
+ function createAPC(){const b=createObjectBase("APC"),a=new THREE.MeshStandardMaterial({color:3099951,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(3,1.5,1.5),a);return Object.assign(c,b),c.position.y=.75,c.castShadow=!0,c.receiveShadow=!0,c}
658
+ function createSandBoat(){const b=createObjectBase("Sand Boat"),a=new THREE.MeshStandardMaterial({color:14596231,roughness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(2,.5,1),a);return Object.assign(c,b),c.position.y=.25,c.castShadow=!0,c.receiveShadow=!0,c}
659
+ function createMakeshiftMachete(){const b=createObjectBase("Makeshift Machete"),a=new THREE.MeshStandardMaterial({color:7829367,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(.8,.05,.2),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!0,c}
660
+ function createPistolBody(){const b=createObjectBase("Pistol Body"),a=new THREE.MeshStandardMaterial({color:3355443,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(.5,.2,.3),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!0,c}
661
+ function createScopeAttachment(){const b=createObjectBase("Scope Attachment"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,.3,8),a);return Object.assign(c,b),c.position.y=.15,c.rotation.z=Math.PI/2,c.castShadow=!0,c.receiveShadow=!0,c}
662
+ function createLaserPistol(){const b=createObjectBase("Laser Pistol"),a=new THREE.MeshStandardMaterial({color:47083,roughness:.5,metalness:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(.5,.2,.3),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!0,c}
663
+ function createEnergySword(){const b=createObjectBase("Energy Sword"),a=new THREE.MeshStandardMaterial({color:65280,roughness:.5,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.BoxGeometry(.8,.05,.2),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!0,c}
664
+ function createDwarvenAxe(){const b=createObjectBase("Dwarven Axe"),a=new THREE.MeshStandardMaterial({color:5592405,roughness:.7,metalness:.5}),c=new THREE.Mesh(new THREE.BoxGeometry(.5,.3,.1),a);return Object.assign(c,b),c.position.y=.15,c.castShadow=!0,c.receiveShadow=!0,c}
665
+ function createMagicStaff(){const c=createObjectBase("Magic Staff"),a=new THREE.Group;Object.assign(a,c);const d=new THREE.MeshStandardMaterial({color:9127187,roughness:.8}),b=new THREE.MeshStandardMaterial({color:16711935,roughness:.5}),e=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,1.5,8),d);e.position.y=.75,e.castShadow=!0,e.receiveShadow=!0,a.add(e);const f=new THREE.Mesh(new THREE.SphereGeometry(.1,8,8),b);return f.position.y=1.5,f.castShadow=!0,f.receiveShadow=!0,a.add(f),a}
666
+ function createCandleFlame(){const b=createObjectBase("Candle Flame"),a=new THREE.MeshStandardMaterial({color:16729344,roughness:.5,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.SphereGeometry(.1,8,8),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!0,c.receiveShadow=!1,c}
667
+ function createDustCloud(){const b=createObjectBase("Dust Cloud"),a=new THREE.MeshStandardMaterial({color:11184810,roughness:.9,transparent:!0,opacity:.5}),c=new THREE.Mesh(new THREE.SphereGeometry(.5,8,8),a);return Object.assign(c,b),c.position.y=.25,c.castShadow=!1,c.receiveShadow=!1,c}
668
+ function createBloodSplatDecal(){const b=createObjectBase("Blood Splat Decal"),a=new THREE.MeshStandardMaterial({color:9109504,roughness:.9,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.PlaneGeometry(.5,.5),a);return Object.assign(c,b),c.position.y=.01,c.rotation.x=-Math.PI/2,c.castShadow=!1,c.receiveShadow=!0,c}
669
+ function createBurningBarrelFire(){const c=createObjectBase("Burning Barrel Fire"),a=new THREE.Group;Object.assign(a,c);const d=new THREE.MeshStandardMaterial({color:5592405,roughness:.7,metalness:.5}),b=new THREE.MeshStandardMaterial({color:16729344,roughness:.5,transparent:!0,opacity:.8}),e=new THREE.Mesh(new THREE.CylinderGeometry(.4,.4,1,12),d);e.position.y=.5,e.castShadow=!0,e.receiveShadow=!0,a.add(e);const f=new THREE.Mesh(new THREE.SphereGeometry(.3,8,8),b);return f.position.y=1,f.castShadow=!0,f.receiveShadow=!1,a.add(f),a}
670
+ function createWarpTunnelEffect(){const b=createObjectBase("Warp Tunnel Effect"),a=new THREE.MeshStandardMaterial({color:47083,roughness:.5,transparent:!0,opacity:.7}),c=new THREE.Mesh(new THREE.CylinderGeometry(.5,.5,2,12),a);return Object.assign(c,b),c.position.y=1,c.castShadow=!1,c.receiveShadow=!1,c}
671
+ function createLaserBeam(){const b=createObjectBase("Laser Beam"),a=new THREE.MeshStandardMaterial({color:16711680,roughness:.5,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.CylinderGeometry(.05,.05,1,8),a);return Object.assign(c,b),c.position.y=.5,c.rotation.z=Math.PI/2,c.castShadow=!1,c.receiveShadow=!1,c}
672
+ function createGoldSparkle(){const b=createObjectBase("Gold Sparkle"),a=new THREE.MeshStandardMaterial({color:16766720,roughness:.5,transparent:!0,opacity:.8}),c=new THREE.Mesh(new THREE.SphereGeometry(.1,8,8),a);return Object.assign(c,b),c.position.y=.1,c.castShadow=!1,c.receiveShadow=!1,c}
673
+ function createSteamVent(){const b=createObjectBase("Steam Vent"),a=new THREE.MeshStandardMaterial({color:11184810,roughness:.9,transparent:!0,opacity:.5}),c=new THREE.Mesh(new THREE.CylinderGeometry(.1,.1,.5,8),a);return Object.assign(c,b),c.position.y=.25,c.castShadow=!1,c.receiveShadow=!1,c}
674
+
675
+ // --- Start the application ---
676
+ init();
677
+ </script>
678
+ </body>
679
  </html>