Kgshop commited on
Commit
fa75b3d
·
verified ·
1 Parent(s): 4b49a24

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +114 -67
app.py CHANGED
@@ -123,8 +123,6 @@ EDITOR_TEMPLATE = '''
123
  border: 8px solid #f3f3f3; border-top: 8px solid #3498db; border-radius: 50%;
124
  width: 60px; height: 60px; animation: spin 1s linear infinite; display: none; z-index: 100;
125
  }
126
- #blocker { position: absolute; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); display: none; }
127
- #instructions { width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; font-size: 14px; cursor: pointer; color: white; }
128
  #burger-menu {
129
  position: absolute; top: 15px; left: 15px; z-index: 20; display: none; width: 30px; height: 22px; cursor: pointer; pointer-events: auto;
130
  }
@@ -171,15 +169,14 @@ EDITOR_TEMPLATE = '''
171
  <div id="tool-selector"></div>
172
  <p style="font-size: 0.8em; color: #888; margin-top: 10px;">
173
  ЛКМ: Разместить<br>
174
- ПКМ: Вращать<br>
175
  Shift + ЛКМ: Удалить<br>
176
- Колесо мыши: Вращать
177
  </p>
178
  <button id="clear-level" class="danger-button">Очистить уровень</button>
179
  </div>
180
  </div>
181
  </div>
182
- <div id="blocker"><div id="instructions"><p style="font-size:36px">Нажмите, чтобы играть</p><p>Движение: WASD / Джойстик</p><p>Нажмите ESC для выхода</p></div></div>
183
  <div id="loading-spinner"></div>
184
  <div id="joystick-container"><div id="joystick-handle"></div></div>
185
 
@@ -188,10 +185,8 @@ EDITOR_TEMPLATE = '''
188
  </script>
189
  <script type="module">
190
  import * as THREE from 'three';
191
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
192
 
193
- let scene, camera, renderer, orbitControls;
194
- let raycaster, mouse, placementPlane, gridHelper, previewMesh;
195
 
196
  let isPlayMode = false;
197
  let player, playerVelocity = new THREE.Vector3();
@@ -206,32 +201,56 @@ EDITOR_TEMPLATE = '''
206
  const levelData = { floors: {}, walls: {}, objects: {} };
207
 
208
  const joystick = { active: false, center: new THREE.Vector2(), current: new THREE.Vector2(), vector: new THREE.Vector2() };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
  const ASSETS = {
211
  floors: {
212
- grass: { color: 0x55902f, size: [gridSize, 0.1, gridSize] },
213
- concrete: { color: 0x888888, size: [gridSize, 0.1, gridSize] },
214
- wood: { color: 0x8B4513, size: [gridSize, 0.1, gridSize] },
215
- dirt: { color: 0x9b7653, size: [gridSize, 0.1, gridSize] }
 
 
216
  },
217
  walls: {
218
- brick: { color: 0xb55a44, size: [gridSize, 2.5, 0.2] },
219
- concrete: { color: 0xc2c2c2, size: [gridSize, 2.5, 0.2] }
220
  },
221
  objects: {
222
- crate: { color: 0x966F33, size: [gridSize * 0.8, gridSize * 0.8, gridSize * 0.8] },
223
- barrel: { color: 0x595959, size: [gridSize * 0.4, gridSize * 0.8, 0], geometry: 'cylinder' }
224
  }
225
  };
226
 
227
  const instancedMeshes = {};
 
 
228
 
229
  function init() {
230
  scene = new THREE.Scene();
231
- scene.background = new THREE.Color(0x283747);
 
232
 
233
- camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 2000);
 
 
234
  camera.position.set(25, 30, 25);
 
235
 
236
  renderer = new THREE.WebGLRenderer({ antialias: true });
237
  renderer.setSize(window.innerWidth, window.innerHeight);
@@ -239,17 +258,11 @@ EDITOR_TEMPLATE = '''
239
  renderer.shadowMap.enabled = true;
240
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
241
  document.body.appendChild(renderer.domElement);
242
-
243
- orbitControls = new OrbitControls(camera, renderer.domElement);
244
- orbitControls.enableDamping = true;
245
- orbitControls.maxPolarAngle = Math.PI / 2.2;
246
- orbitControls.minDistance = 10;
247
- orbitControls.maxDistance = 100;
248
-
249
- const hemiLight = new THREE.HemisphereLight(0xB1E1FF, 0xB97A20, 0.8);
250
  scene.add(hemiLight);
251
 
252
- const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
253
  dirLight.position.set(-30, 50, -30);
254
  dirLight.castShadow = true;
255
  dirLight.shadow.mapSize.width = 2048;
@@ -279,12 +292,15 @@ EDITOR_TEMPLATE = '''
279
  window.addEventListener('resize', onWindowResize);
280
  renderer.domElement.addEventListener('pointermove', onPointerMove);
281
  renderer.domElement.addEventListener('pointerdown', onPointerDown);
 
282
  window.addEventListener('keydown', e => { keyStates[e.code] = true; handleKeyDown(e); });
283
  window.addEventListener('keyup', e => { keyStates[e.code] = false; });
284
  renderer.domElement.addEventListener('wheel', e => {
285
  if(!isPlayMode) {
286
  e.preventDefault();
287
- rotatePreview(e.deltaY > 0 ? 1 : -1);
 
 
288
  }
289
  }, { passive: false });
290
  renderer.domElement.addEventListener('contextmenu', e => e.preventDefault());
@@ -298,13 +314,16 @@ EDITOR_TEMPLATE = '''
298
  instancedMeshes[category] = {};
299
  for (const type in ASSETS[category]) {
300
  const asset = ASSETS[category][type];
 
 
 
301
  let geometry;
302
  if(asset.geometry === 'cylinder') {
303
  geometry = new THREE.CylinderGeometry(asset.size[0], asset.size[0], asset.size[1], 16);
304
  } else {
305
  geometry = new THREE.BoxGeometry(...asset.size);
306
  }
307
- const material = new THREE.MeshStandardMaterial({ color: asset.color });
308
  const mesh = new THREE.InstancedMesh(geometry, material, MAX_COUNT);
309
  mesh.castShadow = true;
310
  mesh.receiveShadow = true;
@@ -332,7 +351,7 @@ EDITOR_TEMPLATE = '''
332
  for (const type in ASSETS[category]) {
333
  const item = document.createElement('div');
334
  item.className = 'tool-item';
335
- item.textContent = `${category.slice(0,1).toUpperCase()}: ${type}`;
336
  item.dataset.category = category;
337
  item.dataset.type = type;
338
  item.addEventListener('click', () => selectTool(category, type));
@@ -358,8 +377,6 @@ EDITOR_TEMPLATE = '''
358
  const touch = event.touches[0];
359
  joystick.active = true;
360
  joystick.center.set(touch.clientX, touch.clientY);
361
- joystickContainer.style.left = `${touch.clientX - maxRadius}px`;
362
- joystickContainer.style.top = `${touch.clientY - maxRadius}px`;
363
  }
364
  function onTouchMove(event) {
365
  if (!joystick.active) return;
@@ -393,34 +410,60 @@ EDITOR_TEMPLATE = '''
393
  } else {
394
  geometry = new THREE.BoxGeometry(...asset.size);
395
  }
396
- const material = new THREE.MeshBasicMaterial({ color: asset.color, transparent: true, opacity: 0.6 });
 
 
397
  previewMesh = new THREE.Mesh(geometry, material);
398
  scene.add(previewMesh);
399
  }
400
 
401
  function onWindowResize() {
402
- camera.aspect = window.innerWidth / window.innerHeight;
 
 
 
 
 
403
  camera.updateProjectionMatrix();
404
  renderer.setSize(window.innerWidth, window.innerHeight);
405
  }
406
 
407
  function onPointerMove(event) {
408
- if (isPlayMode) return;
409
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
410
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
 
 
 
 
 
 
 
 
 
 
 
 
411
  raycaster.setFromCamera(mouse, camera);
412
  const intersects = raycaster.intersectObject(placementPlane);
413
  if (intersects.length > 0) {
414
  const point = intersects[0].point;
 
 
 
 
415
  const gridX = Math.round(point.x / gridSize);
416
  const gridZ = Math.round(point.z / gridSize);
417
- previewMesh.position.set(gridX * gridSize, 0, gridZ * gridSize);
418
 
419
- const asset = ASSETS[currentTool.category][currentTool.type];
 
 
 
 
 
 
 
420
  if (currentTool.category === 'floors') {
421
  previewMesh.position.y = asset.size[1] / 2;
422
- } else if (currentTool.category === 'walls') {
423
- previewMesh.position.y = asset.size[1] / 2;
424
  } else {
425
  previewMesh.position.y = asset.size[1] / 2;
426
  }
@@ -438,18 +481,20 @@ EDITOR_TEMPLATE = '''
438
  }
439
 
440
  function onPointerDown(event) {
441
- if (isPlayMode || !previewMesh.visible) return;
442
- const isRemoving = event.shiftKey;
443
-
444
- if (event.button === 2) { // Right click
445
- rotatePreview(1);
 
446
  return;
447
  }
448
-
449
- if (event.button !== 0) return; // Not left click
 
450
 
451
  const pos = previewMesh.position;
452
- const key = `${pos.x},${pos.z}`;
453
 
454
  if (isRemoving) {
455
  removeItemAt(pos);
@@ -458,6 +503,12 @@ EDITOR_TEMPLATE = '''
458
  }
459
  updateLevelGeometry();
460
  }
 
 
 
 
 
 
461
 
462
  function rotatePreview(direction) {
463
  currentRotation += (Math.PI / 2) * direction;
@@ -470,6 +521,7 @@ EDITOR_TEMPLATE = '''
470
 
471
  removeItemAt(pos);
472
 
 
473
  if (!levelData[category][type]) levelData[category][type] = [];
474
  levelData[category][type].push([pos.x, pos.z, rotation]);
475
  }
@@ -503,17 +555,15 @@ EDITOR_TEMPLATE = '''
503
  dummy.position.set(x, 0, z);
504
  dummy.rotation.set(0, rot, 0);
505
 
506
- if (category === 'floors') {
507
- dummy.position.y = asset.size[1] / 2;
508
- } else {
509
- dummy.position.y = asset.size[1] / 2;
510
- }
511
 
512
  dummy.updateMatrix();
513
  mesh.setMatrixAt(i, dummy.matrix);
514
-
515
- if (category === 'walls') {
516
- const box = new THREE.Box3().setFromObject(dummy);
 
 
517
  wallBBoxes.push(box);
518
  }
519
  }
@@ -533,28 +583,25 @@ EDITOR_TEMPLATE = '''
533
  function togglePlayMode() {
534
  isPlayMode = !isPlayMode;
535
  const uiContainer = document.getElementById('ui-container');
536
- const blocker = document.getElementById('blocker');
537
  const joystickContainer = document.getElementById('joystick-container');
538
 
539
  if (isPlayMode) {
540
  uiContainer.style.display = 'none';
541
  gridHelper.visible = false;
542
  previewMesh.visible = false;
543
- orbitControls.enabled = false;
544
  player.visible = true;
545
  player.position.set(0, 0, 0);
546
- blocker.style.display = 'block';
547
- joystickContainer.style.display = 'block';
548
  } else {
549
  uiContainer.style.display = 'flex';
550
  gridHelper.visible = true;
551
  previewMesh.visible = true;
552
- orbitControls.enabled = true;
553
  player.visible = false;
554
- blocker.style.display = 'none';
555
  joystickContainer.style.display = 'none';
556
  camera.position.set(25, 30, 25);
557
- orbitControls.target.set(0, 0, 0);
 
 
558
  }
559
  }
560
 
@@ -605,6 +652,8 @@ EDITOR_TEMPLATE = '''
605
  }
606
 
607
  const moveDirection = new THREE.Vector3();
 
 
608
  function updatePlayer(deltaTime) {
609
  const speed = playerSpeed * deltaTime;
610
  moveDirection.set(0,0,0);
@@ -646,10 +695,9 @@ EDITOR_TEMPLATE = '''
646
  }
647
  if(!collisionZ) player.position.z = intendedZPos.z;
648
 
649
- camera.position.x = player.position.x + 15;
650
- camera.position.z = player.position.z + 15;
651
- camera.position.y = 20;
652
  camera.lookAt(player.position);
 
653
  }
654
 
655
  function showSpinner(show) {
@@ -662,9 +710,8 @@ EDITOR_TEMPLATE = '''
662
 
663
  if (isPlayMode) {
664
  updatePlayer(deltaTime);
665
- } else {
666
- orbitControls.update();
667
  }
 
668
  renderer.render(scene, camera);
669
  }
670
 
 
123
  border: 8px solid #f3f3f3; border-top: 8px solid #3498db; border-radius: 50%;
124
  width: 60px; height: 60px; animation: spin 1s linear infinite; display: none; z-index: 100;
125
  }
 
 
126
  #burger-menu {
127
  position: absolute; top: 15px; left: 15px; z-index: 20; display: none; width: 30px; height: 22px; cursor: pointer; pointer-events: auto;
128
  }
 
169
  <div id="tool-selector"></div>
170
  <p style="font-size: 0.8em; color: #888; margin-top: 10px;">
171
  ЛКМ: Разместить<br>
172
+ ПКМ: Вращать / Двигать камеру<br>
173
  Shift + ЛКМ: Удалить<br>
174
+ Колесо мыши: Приближение
175
  </p>
176
  <button id="clear-level" class="danger-button">Очистить уровень</button>
177
  </div>
178
  </div>
179
  </div>
 
180
  <div id="loading-spinner"></div>
181
  <div id="joystick-container"><div id="joystick-handle"></div></div>
182
 
 
185
  </script>
186
  <script type="module">
187
  import * as THREE from 'three';
 
188
 
189
+ let scene, camera, renderer, raycaster, mouse, placementPlane, gridHelper, previewMesh;
 
190
 
191
  let isPlayMode = false;
192
  let player, playerVelocity = new THREE.Vector3();
 
201
  const levelData = { floors: {}, walls: {}, objects: {} };
202
 
203
  const joystick = { active: false, center: new THREE.Vector2(), current: new THREE.Vector2(), vector: new THREE.Vector2() };
204
+
205
+ const isMobile = 'ontouchstart' in window;
206
+
207
+ const textureLoader = new THREE.TextureLoader();
208
+ const loadedTextures = {};
209
+
210
+ function loadTexture(url) {
211
+ if (loadedTextures[url]) return loadedTextures[url];
212
+ const texture = textureLoader.load(url);
213
+ texture.wrapS = THREE.RepeatWrapping;
214
+ texture.wrapT = THREE.RepeatWrapping;
215
+ texture.magFilter = THREE.NearestFilter;
216
+ texture.minFilter = THREE.NearestFilter;
217
+ loadedTextures[url] = texture;
218
+ return texture;
219
+ }
220
 
221
  const ASSETS = {
222
  floors: {
223
+ grass: { size: [1, 0.1, 1], mapUrl: 'https://i.ibb.co/FqsRw2T/grass.png' },
224
+ grass_4x4: { size: [4, 0.1, 4], mapUrl: 'https://i.ibb.co/FqsRw2T/grass.png', repeat: [4,4] },
225
+ concrete: { size: [1, 0.1, 1], mapUrl: 'https://i.ibb.co/yQdGbpS/concrete.png' },
226
+ concrete_4x4: { size: [4, 0.1, 4], mapUrl: 'https://i.ibb.co/yQdGbpS/concrete.png', repeat: [4,4] },
227
+ wood: { size: [1, 0.1, 1], mapUrl: 'https://i.ibb.co/GvxPb7k/wood.png' },
228
+ dirt: { size: [1, 0.1, 1], mapUrl: 'https://i.ibb.co/P9tLdZn/dirt.png' }
229
  },
230
  walls: {
231
+ brick: { size: [1, 2.5, 0.2], mapUrl: 'https://i.ibb.co/PMNtV6z/brick.png', repeat: [1,2] },
232
+ concrete: { size: [1, 2.5, 0.2], mapUrl: 'https://i.ibb.co/VMyXgV8/wall-concrete.png', repeat: [1,2] }
233
  },
234
  objects: {
235
+ crate: { size: [0.8, 0.8, 0.8], mapUrl: 'https://i.ibb.co/PZ9rV3g/crate.png' },
236
+ barrel: { size: [0.4, 0.8, 0], geometry: 'cylinder', mapUrl: 'https://i.ibb.co/PcnwQqp/barrel.png' }
237
  }
238
  };
239
 
240
  const instancedMeshes = {};
241
+ let isPanning = false;
242
+ const panStart = new THREE.Vector2();
243
 
244
  function init() {
245
  scene = new THREE.Scene();
246
+ scene.background = new THREE.Color(0x3d4a55);
247
+ scene.fog = new THREE.Fog(0x3d4a55, 50, 150);
248
 
249
+ const aspect = window.innerWidth / window.innerHeight;
250
+ const frustumSize = 30;
251
+ camera = new THREE.OrthographicCamera(frustumSize * aspect / -2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / -2, 1, 1000);
252
  camera.position.set(25, 30, 25);
253
+ camera.lookAt(0,0,0);
254
 
255
  renderer = new THREE.WebGLRenderer({ antialias: true });
256
  renderer.setSize(window.innerWidth, window.innerHeight);
 
258
  renderer.shadowMap.enabled = true;
259
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
260
  document.body.appendChild(renderer.domElement);
261
+
262
+ const hemiLight = new THREE.HemisphereLight(0xB1E1FF, 0x495057, 0.6);
 
 
 
 
 
 
263
  scene.add(hemiLight);
264
 
265
+ const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
266
  dirLight.position.set(-30, 50, -30);
267
  dirLight.castShadow = true;
268
  dirLight.shadow.mapSize.width = 2048;
 
292
  window.addEventListener('resize', onWindowResize);
293
  renderer.domElement.addEventListener('pointermove', onPointerMove);
294
  renderer.domElement.addEventListener('pointerdown', onPointerDown);
295
+ renderer.domElement.addEventListener('pointerup', onPointerUp);
296
  window.addEventListener('keydown', e => { keyStates[e.code] = true; handleKeyDown(e); });
297
  window.addEventListener('keyup', e => { keyStates[e.code] = false; });
298
  renderer.domElement.addEventListener('wheel', e => {
299
  if(!isPlayMode) {
300
  e.preventDefault();
301
+ const zoomAmount = e.deltaY > 0 ? 1.1 : 0.9;
302
+ camera.zoom *= zoomAmount;
303
+ camera.updateProjectionMatrix();
304
  }
305
  }, { passive: false });
306
  renderer.domElement.addEventListener('contextmenu', e => e.preventDefault());
 
314
  instancedMeshes[category] = {};
315
  for (const type in ASSETS[category]) {
316
  const asset = ASSETS[category][type];
317
+ const texture = loadTexture(asset.mapUrl);
318
+ if(asset.repeat) texture.repeat.set(...asset.repeat);
319
+
320
  let geometry;
321
  if(asset.geometry === 'cylinder') {
322
  geometry = new THREE.CylinderGeometry(asset.size[0], asset.size[0], asset.size[1], 16);
323
  } else {
324
  geometry = new THREE.BoxGeometry(...asset.size);
325
  }
326
+ const material = new THREE.MeshStandardMaterial({ map: texture });
327
  const mesh = new THREE.InstancedMesh(geometry, material, MAX_COUNT);
328
  mesh.castShadow = true;
329
  mesh.receiveShadow = true;
 
351
  for (const type in ASSETS[category]) {
352
  const item = document.createElement('div');
353
  item.className = 'tool-item';
354
+ item.textContent = `${category.slice(0,1).toUpperCase()}: ${type.replace('_',' ')}`;
355
  item.dataset.category = category;
356
  item.dataset.type = type;
357
  item.addEventListener('click', () => selectTool(category, type));
 
377
  const touch = event.touches[0];
378
  joystick.active = true;
379
  joystick.center.set(touch.clientX, touch.clientY);
 
 
380
  }
381
  function onTouchMove(event) {
382
  if (!joystick.active) return;
 
410
  } else {
411
  geometry = new THREE.BoxGeometry(...asset.size);
412
  }
413
+ const material = new THREE.MeshBasicMaterial({ map: loadTexture(asset.mapUrl), transparent: true, opacity: 0.6 });
414
+ if(asset.repeat) material.map.repeat.set(...asset.repeat);
415
+
416
  previewMesh = new THREE.Mesh(geometry, material);
417
  scene.add(previewMesh);
418
  }
419
 
420
  function onWindowResize() {
421
+ const aspect = window.innerWidth / window.innerHeight;
422
+ const frustumSize = 30 / camera.zoom;
423
+ camera.left = frustumSize * aspect / -2;
424
+ camera.right = frustumSize * aspect / 2;
425
+ camera.top = frustumSize / 2;
426
+ camera.bottom = frustumSize / -2;
427
  camera.updateProjectionMatrix();
428
  renderer.setSize(window.innerWidth, window.innerHeight);
429
  }
430
 
431
  function onPointerMove(event) {
 
432
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
433
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
434
+
435
+ if(isPanning) {
436
+ const dx = event.clientX - panStart.x;
437
+ const dy = event.clientY - panStart.y;
438
+ panStart.set(event.clientX, event.clientY);
439
+ const panSpeed = (camera.right - camera.left) / (camera.zoom * renderer.domElement.clientWidth);
440
+ camera.position.x -= dx * panSpeed;
441
+ camera.position.z -= dy * panSpeed * Math.cos(camera.rotation.x);
442
+ return;
443
+ }
444
+
445
+ if (isPlayMode) return;
446
  raycaster.setFromCamera(mouse, camera);
447
  const intersects = raycaster.intersectObject(placementPlane);
448
  if (intersects.length > 0) {
449
  const point = intersects[0].point;
450
+ const asset = ASSETS[currentTool.category][currentTool.type];
451
+ const w = (asset.size[0] / gridSize);
452
+ const h = (asset.size[2] / gridSize);
453
+
454
  const gridX = Math.round(point.x / gridSize);
455
  const gridZ = Math.round(point.z / gridSize);
 
456
 
457
+ let finalX = gridX * gridSize;
458
+ let finalZ = gridZ * gridSize;
459
+
460
+ if (w > 1) finalX += (w/2-0.5) * Math.sign(finalX) * gridSize;
461
+ if (h > 1) finalZ += (h/2-0.5) * Math.sign(finalZ) * gridSize;
462
+
463
+ previewMesh.position.set(finalX, 0, finalZ);
464
+
465
  if (currentTool.category === 'floors') {
466
  previewMesh.position.y = asset.size[1] / 2;
 
 
467
  } else {
468
  previewMesh.position.y = asset.size[1] / 2;
469
  }
 
481
  }
482
 
483
  function onPointerDown(event) {
484
+ if (isPlayMode && event.target.tagName === 'CANVAS') {
485
+ if(isMobile) return;
486
+ }
487
+ if (!isPlayMode && event.button === 2) {
488
+ isPanning = true;
489
+ panStart.set(event.clientX, event.clientY);
490
  return;
491
  }
492
+
493
+ if (isPlayMode || !previewMesh.visible || event.button !== 0) return;
494
+ const isRemoving = event.shiftKey;
495
 
496
  const pos = previewMesh.position;
497
+ const key = `${pos.x},${pos.y},${pos.z}`;
498
 
499
  if (isRemoving) {
500
  removeItemAt(pos);
 
503
  }
504
  updateLevelGeometry();
505
  }
506
+
507
+ function onPointerUp(event) {
508
+ if (event.button === 2) {
509
+ isPanning = false;
510
+ }
511
+ }
512
 
513
  function rotatePreview(direction) {
514
  currentRotation += (Math.PI / 2) * direction;
 
521
 
522
  removeItemAt(pos);
523
 
524
+ if (!levelData[category]) levelData[category] = {};
525
  if (!levelData[category][type]) levelData[category][type] = [];
526
  levelData[category][type].push([pos.x, pos.z, rotation]);
527
  }
 
555
  dummy.position.set(x, 0, z);
556
  dummy.rotation.set(0, rot, 0);
557
 
558
+ dummy.position.y = (category === 'floors' ? asset.size[1] / 2 : asset.size[1] / 2);
 
 
 
 
559
 
560
  dummy.updateMatrix();
561
  mesh.setMatrixAt(i, dummy.matrix);
562
+
563
+ if (category === 'walls' || category === 'objects') {
564
+ const boxHelper = new THREE.BoxHelper(dummy);
565
+ boxHelper.geometry.computeBoundingBox();
566
+ const box = boxHelper.geometry.boundingBox;
567
  wallBBoxes.push(box);
568
  }
569
  }
 
583
  function togglePlayMode() {
584
  isPlayMode = !isPlayMode;
585
  const uiContainer = document.getElementById('ui-container');
 
586
  const joystickContainer = document.getElementById('joystick-container');
587
 
588
  if (isPlayMode) {
589
  uiContainer.style.display = 'none';
590
  gridHelper.visible = false;
591
  previewMesh.visible = false;
 
592
  player.visible = true;
593
  player.position.set(0, 0, 0);
594
+ if(isMobile) joystickContainer.style.display = 'block';
 
595
  } else {
596
  uiContainer.style.display = 'flex';
597
  gridHelper.visible = true;
598
  previewMesh.visible = true;
 
599
  player.visible = false;
 
600
  joystickContainer.style.display = 'none';
601
  camera.position.set(25, 30, 25);
602
+ camera.lookAt(0,0,0);
603
+ camera.zoom = 1;
604
+ camera.updateProjectionMatrix();
605
  }
606
  }
607
 
 
652
  }
653
 
654
  const moveDirection = new THREE.Vector3();
655
+ const cameraOffset = new THREE.Vector3(20, 25, 20);
656
+
657
  function updatePlayer(deltaTime) {
658
  const speed = playerSpeed * deltaTime;
659
  moveDirection.set(0,0,0);
 
695
  }
696
  if(!collisionZ) player.position.z = intendedZPos.z;
697
 
698
+ camera.position.copy(player.position).add(cameraOffset);
 
 
699
  camera.lookAt(player.position);
700
+ camera.updateProjectionMatrix();
701
  }
702
 
703
  function showSpinner(show) {
 
710
 
711
  if (isPlayMode) {
712
  updatePlayer(deltaTime);
 
 
713
  }
714
+
715
  renderer.render(scene, camera);
716
  }
717