Aleksmorshen commited on
Commit
9365a7e
·
verified ·
1 Parent(s): b6a8119

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +316 -428
index.html CHANGED
@@ -1,467 +1,355 @@
1
  <!DOCTYPE html>
2
- <html lang="ru">
3
  <head>
4
  <meta charset="UTF-8">
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
  <title>Babylon.js Metaverse Demo</title>
7
- <meta name="description" content="Демонстрация базовой метавселенной, созданной с помощью Babylon.js. Включает 3D-мир, управление персонажем и элементы для мобильных устройств.">
8
- <!-- Подключение Babylon.js -->
9
- <script src="https://cdn.babylonjs.com/babylon.js"></script>
10
- <!-- (Опционально) Загрузчики для моделей (например, glTF) -->
11
- <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
12
- <!-- (Опционально) GUI для элементов интерфейса -->
13
- <script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>
14
-
15
  <style>
 
16
  html, body {
17
- overflow: hidden;
18
- width: 100%;
19
- height: 100%;
20
  margin: 0;
21
  padding: 0;
22
- font-family: Arial, sans-serif;
23
- color: #eee; /* Светлый текст для темного фона */
24
- background-color: #111; /* Темный фон по умолчанию */
25
- }
26
-
27
- #renderCanvas {
28
  width: 100%;
29
  height: 100%;
30
- touch-action: none; /* Отключает стандартные жесты браузера (важно для управления) */
31
- outline: none; /* Убирает рамку при фокусе */
32
  }
33
 
34
- /* Стили для экрана загрузки */
35
- #loadingScreen {
36
- position: absolute;
37
- top: 0;
38
- left: 0;
39
  width: 100%;
40
  height: 100%;
41
- background-color: #1a1a1a;
42
- color: #ffffff;
43
- display: flex;
44
- flex-direction: column;
45
- justify-content: center;
46
- align-items: center;
47
- font-size: 1.5em;
48
- z-index: 100;
49
- transition: opacity 0.5s ease-out;
50
- }
51
-
52
- #loadingScreen.hidden {
53
- opacity: 0;
54
- pointer-events: none; /* Чтобы не мешал кликам после скрытия */
55
- }
56
-
57
- #loadingSpinner {
58
- border: 4px solid #f3f3f3;
59
- border-top: 4px solid #3498db;
60
- border-radius: 50%;
61
- width: 40px;
62
- height: 40px;
63
- animation: spin 1s linear infinite;
64
- margin-top: 20px;
65
  }
66
 
67
- @keyframes spin {
68
- 0% { transform: rotate(0deg); }
69
- 100% { transform: rotate(360deg); }
70
- }
71
-
72
- /* Стили для виртуального джойстика (для мобильных) */
73
- #joystickContainer {
74
  position: absolute;
75
- bottom: 20px;
76
- left: 20px;
77
- width: 150px; /* Размер области джойстика */
78
- height: 150px;
79
- /* background-color: rgba(80, 80, 80, 0.3); */ /* Полупрозрачный фон для отладки */
80
- /* border-radius: 50%; */
81
  z-index: 10;
82
- display: none; /* По умолчанию скрыт, показываем через JS при необходимости */
83
- pointer-events: none; /* Контейнер не ловит события, ловит сам джойстик GUI */
84
  }
85
 
86
- /* Можно добавить стили для других элементов GUI здесь */
87
-
88
- /* Простое уведомление */
89
- #infoBox {
90
- position: absolute;
91
- top: 10px;
92
- left: 10px;
93
- background-color: rgba(0,0,0,0.5);
94
- color: white;
95
- padding: 8px;
96
- border-radius: 5px;
97
- font-size: 0.9em;
98
- z-index: 5;
99
- }
100
 
101
  </style>
 
 
 
 
 
 
102
  </head>
103
  <body>
104
- <!-- Экран загрузки -->
105
- <div id="loadingScreen">
106
- <p>Загрузка метавселенной...</p>
107
- <div id="loadingSpinner"></div>
108
- </div>
109
-
110
- <!-- Основной Canvas для рендеринга -->
111
  <canvas id="renderCanvas"></canvas>
112
 
113
- <!-- Контейнер для джойстика (позиционирование) -->
114
- <div id="joystickContainer"></div>
115
-
116
- <!-- Информационный блок -->
117
- <div id="infoBox">
118
- Управление: WASD/Стрелки + Мышь (ПК) | Джойстик + Касание (Мобильные)
119
- </div>
120
 
121
  <script>
122
- // Получаем элементы DOM
123
- const canvas = document.getElementById("renderCanvas");
124
- const loadingScreen = document.getElementById("loadingScreen");
125
- const joystickContainer = document.getElementById("joystickContainer"); // Хотя GUI не использует его напрямую, он полезен для понимания области
126
-
127
- // Инициализация движка Babylon.js
128
- const engine = new BABYLON.Engine(canvas, true, {
129
- preserveDrawingBuffer: true,
130
- stencil: true,
131
- antialias: true // Включаем сглаживание для лучшей графики
132
- });
133
-
134
- // Глобальные переменные для сцены, камеры и управления
135
- let scene;
136
- let camera;
137
- let inputMap = {}; // Для отслеживания нажатых клавиш
138
- let moveSpeed = 0.15; // Скорость движения
139
- let cameraSensitivity = 0.003; // Чувствительность мыши/касания
140
- let virtualJoystick = null; // Для мобильного управления
141
-
142
- // --- Функция создания сцены ---
143
- const createScene = async function () {
144
- scene = new BABYLON.Scene(engine);
145
- scene.clearColor = new BABYLON.Color3.FromHexString("#2c3e50"); // Цвет фона (небо)
146
-
147
- // --- Камера ---
148
- // Универсальная камера подходит для ПК (WASD/Мышь) и имеет настройки для Touch
149
- camera = new BABYLON.UniversalCamera("playerCamera", new BABYLON.Vector3(0, 2, -10), scene);
150
- camera.attachControl(canvas, true); // Прикрепляем управление к canvas
151
- camera.speed = 0; // Отключаем встроенное движение WASD, будем управлять вручную
152
- camera.angularSensibility = 8000 * cameraSensitivity; // Настройка чувствительности вращения
153
-
154
- // Настройки для камеры от первого лица
155
- camera.ellipsoid = new BABYLON.Vector3(0.5, 0.9, 0.5); // Размер "тела" игрока для коллизий
156
- camera.checkCollisions = true; // Включаем проверку коллизий для камеры
157
- camera.applyGravity = true; // Включаем гравитацию
158
- camera.minZ = 0.1; // Ближняя плоскость отсечения
159
-
160
- // --- Освещение ---
161
- // Мягкий окружающий свет
162
- const light = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(0, 1, 0), scene);
163
- light.intensity = 0.7;
164
- light.diffuse = new BABYLON.Color3(1, 1, 1);
165
- light.specular = new BABYLON.Color3(0.5, 0.5, 0.5);
166
- light.groundColor = new BABYLON.Color3(0.3, 0.3, 0.3);
167
-
168
- // Направленный свет (солнце) для теней
169
- const dirLight = new BABYLON.DirectionalLight("dirLight", new BABYLON.Vector3(-0.5, -1, -0.5), scene);
170
- dirLight.position = new BABYLON.Vector3(20, 40, 20);
171
- dirLight.intensity = 0.8;
172
-
173
- // --- Тени ---
174
- // Генератор теней высокого качества (можно настроить для производительности)
175
- const shadowGenerator = new BABYLON.ShadowGenerator(2048, dirLight); // 2048 - размер карты теней
176
- shadowGenerator.useExponentialShadowMap = true; // Мягкие тени
177
- // shadowGenerator.useBlurExponentialShadowMap = true; // Еще мягче, но дороже
178
- // shadowGenerator.blurKernel = 32;
179
- shadowGenerator.darkness = 0.5;
180
-
181
- // --- Мир (Окружение) ---
182
- // Земля
183
- const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 100, height: 100 }, scene);
184
- ground.checkCollisions = true; // Земля участвует в коллизиях
185
- const groundMaterial = new BABYLON.StandardMaterial("groundMat", scene);
186
- // Простая текстура для земли (можно заменить на PBR материал и текстуры для "флагманской" графики)
187
- const groundTexture = new BABYLON.Texture("https://www.babylonjs-playground.com/textures/grass.png", scene); // Пример текстуры
188
- groundTexture.uScale = 20;
189
- groundTexture.vScale = 20;
190
- groundMaterial.diffuseTexture = groundTexture;
191
- groundMaterial.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1); // Меньше бликов на траве
192
- ground.material = groundMaterial;
193
- ground.receiveShadows = true; // Земля принимает тени
194
-
195
- // Небо (Skybox) для атмосферы
196
- const skybox = BABYLON.MeshBuilder.CreateBox("skyBox", { size: 1000.0 }, scene);
197
- const skyboxMaterial = new BABYLON.StandardMaterial("skyBoxMat", scene);
198
- skyboxMaterial.backFaceCulling = false; // Рендерим внутренние грани куба
199
- // Текстура неба (ищите "cube texture" или "skybox texture")
200
- skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("https://www.babylonjs-playground.com/textures/skybox", scene);
201
- skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
202
- skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0);
203
- skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
204
- skybox.material = skyboxMaterial;
205
-
206
- // --- Добавляем объекты в мир ---
207
- // Коробка
208
- const box = BABYLON.MeshBuilder.CreateBox("box", { size: 2 }, scene);
209
- box.position = new BABYLON.Vector3(5, 1, 5);
210
- box.checkCollisions = true;
211
- const boxMat = new BABYLON.StandardMaterial("boxMat", scene);
212
- boxMat.diffuseColor = new BABYLON.Color3.FromHexString("#e74c3c"); // Красный
213
- box.material = boxMat;
214
- shadowGenerator.addShadowCaster(box); // Коробка отбрасывает тень
215
-
216
- // Сфера
217
- const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 3 }, scene);
218
- sphere.position = new BABYLON.Vector3(-5, 1.5, -5);
219
- sphere.checkCollisions = true;
220
- const sphereMat = new BABYLON.StandardMaterial("sphereMat", scene);
221
- sphereMat.diffuseColor = new BABYLON.Color3.FromHexString("#3498db"); // Синий
222
- sphereMat.specularPower = 64; // Блик
223
- sphere.material = sphereMat;
224
- shadowGenerator.addShadowCaster(sphere); // Сфера отбрасывает тень
225
-
226
- // Колонна (цилиндр)
227
- const cylinder = BABYLON.MeshBuilder.CreateCylinder("cylinder", {height: 6, diameter: 1}, scene);
228
- cylinder.position = new BABYLON.Vector3(0, 3, 8);
229
- cylinder.checkCollisions = true;
230
- const cylMat = new BABYLON.StandardMaterial("cylMat", scene);
231
- cylMat.diffuseTexture = new BABYLON.Texture("https://www.babylonjs-playground.com/textures/rock.jpg", scene); // Текстура камня
232
- cylinder.material = cylMat;
233
- shadowGenerator.addShadowCaster(cylinder);
234
-
235
- // Пример использования PBR материала для более реалистичного вида
236
- const pbrSphere = BABYLON.MeshBuilder.CreateSphere("pbrSphere", {diameter: 2.5}, scene);
237
- pbrSphere.position = new BABYLON.Vector3(-8, 1.25, 4);
238
- const pbrMat = new BABYLON.PBRMaterial("pbrMat", scene);
239
- pbrMat.metallic = 1.0; // Металлический
240
- pbrMat.roughness = 0.2; // Относительно гладкий (отражающий)
241
- pbrMat.albedoColor = new BABYLON.Color3(0.8, 0.8, 0.85);
242
- pbrMat.reflectionTexture = skyboxMaterial.reflectionTexture; // Используем скайбокс для отражений
243
- pbrMat.enableSpecularAntiAliasing = true; // Улучшение отражений
244
- pbrSphere.material = pbrMat;
245
- pbrSphere.checkCollisions = true;
246
- shadowGenerator.addShadowCaster(pbrSphere);
247
-
248
-
249
- // --- Физика ---
250
- scene.gravity = new BABYLON.Vector3(0, -9.81 / 60, 0); // Применяем гравитацию (делим на 60 для согласования с частотой кадров)
251
- scene.collisionsEnabled = true; // Включаем систему коллизий в сцене
252
-
253
- // --- Управление ---
254
- setupInputHandling();
255
-
256
- // --- Мобильное управление (Виртуальный джойстик) ---
257
- if (isMobileDevice()) {
258
- setupVirtualJoystick();
259
- joystickContainer.style.display = 'block'; // Показываем область джойстика
260
- }
261
-
262
- // --- Пост-обработка (для "флагманской" графики) ---
263
- // Свечение (Bloom)
264
- const pipeline = new BABYLON.DefaultRenderingPipeline(
265
- "defaultPipeline", // Имя
266
- true, // Использовать HDR текстуры (лучше для bloom)
267
- scene, // Сцена
268
- [camera] // Камеры, к которым применяется
269
- );
270
- pipeline.samples = 4; // Сглаживание для пост-эффектов
271
- pipeline.bloomEnabled = true;
272
- pipeline.bloomThreshold = 0.8; // Порог яркости для свечения
273
- pipeline.bloomWeight = 0.5; // Интенсивность свечения
274
- pipeline.bloomKernel = 64; // Размер размытия для свечения
275
- pipeline.bloomScale = 0.5; // Масштаб текстуры свечения
276
-
277
- // (Опционально) Ambient Occlusion для лучшего затенения в углах
278
- // pipeline.ssaoEnabled = true;
279
- // pipeline.ssaoRatio = 0.5; // Настройки SSAO (могут влиять на производительность)
280
- // pipeline.ssaoBlurRatio = 0.5;
281
-
282
-
283
- // --- Оптимизация ---
284
- // scene.freezeActiveMeshes(); // Полезно для статичных сцен, но не для динамичных
285
-
286
- // Действия после полной загрузки сцены
287
- await scene.whenReadyAsync();
288
- loadingScreen.classList.add('hidden'); // Скрываем экран загрузки
289
- console.log("Сцена готова!");
290
-
291
- return scene;
292
- };
293
-
294
- // --- Функция настройки обработки ввода (Клавиатура) ---
295
- function setupInputHandling() {
296
- scene.actionManager = new BABYLON.ActionManager(scene);
297
-
298
- // Регистрация нажатия клавиши
299
- scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnKeyDownTrigger, function (evt) {
300
- inputMap[evt.sourceEvent.key.toLowerCase()] = true; // Записываем нажатую клавишу
301
- }));
302
-
303
- // Регистрация отпускания клавиши
304
- scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnKeyUpTrigger, function (evt) {
305
- inputMap[evt.sourceEvent.key.toLowerCase()] = false; // Удаляем клавишу
306
- }));
307
-
308
- // Управление вращением камеры мышью/касанием (обрабатывается UniversalCamera)
309
- // Дополнительно можно настроить чувствительность и т.д.
310
- }
311
-
312
- // --- Функция настройки виртуального джойстика ---
313
- function setupVirtualJoystick() {
314
- const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
315
-
316
- virtualJoystick = new BABYLON.GUI.VirtualJoystick(true); // true = left handed joystick
317
- virtualJoystick.setJoystickSensibility(2.5); // Чувствительность джойстика
318
-
319
- // Настройка внешнего вида джойстика
320
- virtualJoystick.joystickArea.width = "150px";
321
- virtualJoystick.joystickArea.height = "150px";
322
- virtualJoystick.joystickArea.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
323
- virtualJoystick.joystickArea.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM;
324
- virtualJoystick.joystickArea.left = "20px"; // Отступ слева
325
- virtualJoystick.joystickArea.top = "-20px"; // Отступ снизу (отрицательный top при bottom-выравнивании)
326
- virtualJoystick.joystickArea.background = "rgba(100, 100, 100, 0.4)";
327
- virtualJoystick.joystickArea.alpha = 0.7;
328
- virtualJoystick.joystickArea.cornerRadius = 75; // Круглый
329
-
330
- virtualJoystick.joystickThumb.width = "60px";
331
- virtualJoystick.joystickThumb.height = "60px";
332
- virtualJoystick.joystickThumb.background = "rgba(200, 200, 200, 0.6)";
333
- virtualJoystick.joystickThumb.color = "white"; // Цвет иконки (если есть)
334
- virtualJoystick.joystickThumb.alpha = 0.8;
335
- virtualJoystick.joystickThumb.cornerRadius = 30; // Круглый
336
-
337
- // Скрытие стандартного контейнера джойстика (мы используем свой div для позиционирования)
338
- virtualJoystick.joystickContainer.isVisible = false;
339
-
340
- advancedTexture.addControl(virtualJoystick);
341
-
342
- // Обработка касаний для вращения камеры на мобильных
343
- let isPointerDown = false;
344
- let startX = 0;
345
- let startY = 0;
346
-
347
- canvas.addEventListener("pointerdown", (evt) => {
348
- // Не обрабатываем клики по джойстику как вращение
349
- if (virtualJoystick.joystickArea.contains(evt.clientX, evt.clientY)) {
350
- return;
351
  }
352
- isPointerDown = true;
353
- startX = evt.clientX;
354
- startY = evt.clientY;
355
- // Предотвращаем выделение текста/элементов при движении пальца
356
- evt.preventDefault();
357
- });
358
-
359
- canvas.addEventListener("pointermove", (evt) => {
360
- if (!isPointerDown) return;
361
-
362
- const deltaX = (evt.clientX - startX) * cameraSensitivity;
363
- const deltaY = (evt.clientY - startY) * cameraSensitivity;
364
-
365
- // Вращаем камеру
366
- // В UniversalCamera нужно использовать setTarget или вращать вручную
367
- // Простой способ - имитировать движение мыши
368
- camera.cameraRotation.y += deltaX;
369
- camera.cameraRotation.x += deltaY;
370
-
371
- // Ограничиваем вертикальное вращение
372
- camera.cameraRotation.x = Math.max(-Math.PI / 2.1, Math.min(Math.PI / 2.1, camera.cameraRotation.x));
373
-
374
-
375
- startX = evt.clientX;
376
- startY = evt.clientY;
377
- evt.preventDefault();
378
- });
379
-
380
- canvas.addEventListener("pointerup", (evt) => {
381
- isPointerDown = false;
382
- evt.preventDefault();
383
- });
384
- canvas.addEventListener("pointerout", (evt) => { // На случай, если палец ушел за пределы canvas
385
- isPointerDown = false;
386
- evt.preventDefault();
387
- });
388
 
389
- }
390
-
391
- // --- Функция проверки, мобильное ли устройство ---
392
- function isMobileDevice() {
393
- return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
394
- }
395
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
 
397
- // --- Основной цикл рендеринга ---
398
- engine.runRenderLoop(function () {
399
- if (!scene || !scene.isReady()) {
400
- return; // Если сцена не готова, ничего не делаем
401
- }
402
-
403
- // --- Обновление ��вижения игрока ---
404
- const forward = new BABYLON.Vector3(
405
- Math.sin(camera.rotation.y) * moveSpeed,
406
- 0, // Мы не двигаемся вверх/вниз клавишами напрямую (гравитация делает свое дело)
407
- Math.cos(camera.rotation.y) * moveSpeed
408
- );
409
- const right = new BABYLON.Vector3(
410
- Math.sin(camera.rotation.y + Math.PI / 2) * moveSpeed,
411
- 0,
412
- Math.cos(camera.rotation.y + Math.PI / 2) * moveSpeed
413
- );
414
- const moveDirection = BABYLON.Vector3.Zero();
415
-
416
- // Клавиатура
417
- if (inputMap["w"] || inputMap["arrowup"]) {
418
- moveDirection.addInPlace(forward);
419
- }
420
- if (inputMap["s"] || inputMap["arrowdown"]) {
421
- moveDirection.addInPlace(forward.negate()); // Движение назад
422
- }
423
- if (inputMap["a"] || inputMap["arrowleft"]) {
424
- moveDirection.addInPlace(right.negate()); // Движение влево
425
- }
426
- if (inputMap["d"] || inputMap["arrowright"]) {
427
- moveDirection.addInPlace(right); // Движение вправо
428
- }
429
-
430
- // Джойстик (если есть)
431
- if (virtualJoystick && virtualJoystick.pressed) {
432
- // Добавляем вектор от джойстика к общему вектору движения
433
- const joystickForward = forward.scale(virtualJoystick.deltaPosition.y);
434
- const joystickRight = right.scale(virtualJoystick.deltaPosition.x);
435
- moveDirection.addInPlace(joystickForward);
436
- moveDirection.addInPlace(joystickRight);
437
- }
438
 
439
- // Применяем движение к камере
440
- if (moveDirection.lengthSquared() > 0) {
441
- // Нормализуем и масштабируем, чтобы диагональное движение не было быстрее
442
- // moveDirection.normalize().scaleInPlace(moveSpeed); - Не нужно, уже учтено в forward/right
443
- camera.moveWithCollisions(moveDirection);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  }
445
 
446
-
447
- // Рендерим сцену
448
- scene.render();
449
- });
450
-
451
- // --- Обработчик изменения размера окна ---
452
- window.addEventListener("resize", function () {
453
- engine.resize();
454
- });
455
-
456
- // --- Запуск создания сцены ---
457
- createScene().then(() => {
458
- // Сцена успешно создана
459
- }).catch(error => {
460
- console.error("Ошибка при создании сцены:", error);
461
- loadingScreen.innerHTML = "<p>Ошибка загрузки. Пожалуйста, обновите страницу.</p>";
462
- });
463
-
464
  </script>
465
-
466
  </body>
467
  </html>
 
1
  <!DOCTYPE html>
2
+ <html>
3
  <head>
4
  <meta charset="UTF-8">
5
+ <!-- Ensures proper rendering and touch zooming on mobile devices -->
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
7
  <title>Babylon.js Metaverse Demo</title>
 
 
 
 
 
 
 
 
8
  <style>
9
+ /* Basic CSS Reset & Canvas Styling */
10
  html, body {
 
 
 
11
  margin: 0;
12
  padding: 0;
 
 
 
 
 
 
13
  width: 100%;
14
  height: 100%;
15
+ overflow: hidden; /* Prevent scrollbars */
16
+ background-color: #000; /* Black background */
17
  }
18
 
19
+ #renderCanvas {
 
 
 
 
20
  width: 100%;
21
  height: 100%;
22
+ display: block; /* Remove potential extra space below canvas */
23
+ touch-action: none; /* Prevent default browser touch actions like pinch-zoom */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
 
26
+ /* Simple loading indicator (optional) */
27
+ #loadingIndicator {
 
 
 
 
 
28
  position: absolute;
29
+ top: 50%;
30
+ left: 50%;
31
+ transform: translate(-50%, -50%);
32
+ color: white;
33
+ font-size: 24px;
34
+ font-family: sans-serif;
35
  z-index: 10;
 
 
36
  }
37
 
38
+ /* Styles for Virtual Joystick (handled by Babylon.GUI, but could be styled if needed) */
39
+ /* Example: Make joystick area slightly more visible if desired */
40
+ /* .babylonGuiEllipse { border: 1px solid rgba(255, 255, 255, 0.2); } */
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  </style>
43
+ <!-- Babylon.js Core Library -->
44
+ <script src="https://cdn.babylonjs.com/babylon.js"></script>
45
+ <!-- Babylon.js Loaders (optional, if you were loading external models) -->
46
+ <!-- <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script> -->
47
+ <!-- Babylon.js GUI Library (for virtual joysticks and UI elements) -->
48
+ <script src="https://cdn.babylonjs.com/gui/babylon.gui.min.js"></script>
49
  </head>
50
  <body>
51
+ <!-- The canvas element where the 3D scene will be rendered -->
 
 
 
 
 
 
52
  <canvas id="renderCanvas"></canvas>
53
 
54
+ <!-- Simple loading text -->
55
+ <div id="loadingIndicator">Loading Metaverse Demo...</div>
 
 
 
 
 
56
 
57
  <script>
58
+ // Wait until the DOM is fully loaded before running the Babylon.js code
59
+ window.addEventListener('DOMContentLoaded', () => {
60
+ const canvas = document.getElementById('renderCanvas');
61
+ const loadingIndicator = document.getElementById('loadingIndicator');
62
+
63
+ // --- Basic Setup ---
64
+ // Antialiasing enabled for smoother edges
65
+ const engine = new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true, disableWebGL2Support: false });
66
+ engine.loadingUIText = "Initializing Scene..."; // Text shown during engine init
67
+
68
+ const createScene = () => {
69
+ const scene = new BABYLON.Scene(engine);
70
+ // Slightly dark ambient color
71
+ scene.clearColor = new BABYLON.Color4(0.1, 0.1, 0.2, 1.0);
72
+
73
+ // Enable physics/collisions using the Oimo.js plugin (loaded implicitly by Babylon)
74
+ // Note: For more complex physics, you might link Cannon.js or Ammo.js explicitly
75
+ scene.enablePhysics(new BABYLON.Vector3(0, -9.81, 0), new BABYLON.OimoJSPlugin());
76
+
77
+ scene.collisionsEnabled = true; // Enable collisions globally
78
+
79
+ // --- Camera ---
80
+ // UniversalCamera is good for FPS/TPS style controls on both desktop and mobile (when paired with VJ)
81
+ const camera = new BABYLON.UniversalCamera("playerCamera", new BABYLON.Vector3(0, 2.5, -10), scene);
82
+ camera.setTarget(new BABYLON.Vector3(0, 1.5, 0)); // Look slightly forward
83
+ camera.attachControl(canvas, true); // Attach default controls (keyboard/mouse)
84
+
85
+ // Camera properties
86
+ camera.speed = 0.15; // Movement speed
87
+ camera.angularSensibility = 4000; // Mouse look sensitivity
88
+ camera.inertia = 0.2; // Smooth movement/rotation stop
89
+
90
+ // Apply gravity to the camera
91
+ camera.applyGravity = true;
92
+ // Define the collision ellipsoid around the camera (width, height, depth)
93
+ // This represents the player's collision volume
94
+ camera.ellipsoid = new BABYLON.Vector3(0.6, 1.5, 0.6); // Adjust size as needed
95
+ camera.checkCollisions = true; // Enable collisions for the camera
96
+
97
+ // Prevent camera from going below a certain height (e.g., ground level + half height)
98
+ camera.minZ = 0.45; // Prevent clipping through objects very close
99
+
100
+ // --- Lighting ---
101
+ // Hemispheric light provides overall ambient illumination
102
+ const hemiLight = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(0.1, 1, 0.1), scene);
103
+ hemiLight.intensity = 0.6;
104
+ hemiLight.groundColor = new BABYLON.Color3(0.3, 0.3, 0.4);
105
+
106
+ // Directional light simulates sunlight and casts shadows
107
+ const dirLight = new BABYLON.DirectionalLight("dirLight", new BABYLON.Vector3(-0.5, -1, -0.5), scene);
108
+ dirLight.position = new BABYLON.Vector3(20, 40, 20);
109
+ dirLight.intensity = 0.7;
110
+
111
+ // --- Shadows ---
112
+ // Shadow generator needs a light source
113
+ const shadowGenerator = new BABYLON.ShadowGenerator(1024, dirLight); // 1024 is shadow map size
114
+ shadowGenerator.useExponentialShadowMap = true;
115
+ shadowGenerator.useBlurExponentialShadowMap = true; // Smoother shadows
116
+ shadowGenerator.blurKernel = 32;
117
+ shadowGenerator.darkness = 0.5;
118
+
119
+ // --- Environment ---
120
+ // Ground
121
+ const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 100, height: 100 }, scene);
122
+ ground.checkCollisions = true; // Ground should impede camera movement
123
+ const groundMat = new BABYLON.StandardMaterial("groundMat", scene);
124
+ // Simple procedural texture for the ground
125
+ const groundTexture = new BABYLON.Texture("https://www.babylonjs-playground.com/textures/grass.jpg", scene); // Example texture
126
+ groundTexture.uScale = 10;
127
+ groundTexture.vScale = 10;
128
+ groundMat.diffuseTexture = groundTexture;
129
+ groundMat.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1); // Less shiny
130
+ ground.material = groundMat;
131
+ ground.receiveShadows = true; // Ground should receive shadows
132
+
133
+ // Physics for the ground (static object)
134
+ ground.physicsImpostor = new BABYLON.PhysicsImpostor(ground, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 0, restitution: 0.9 }, scene);
135
+
136
+
137
+ // Skybox (creates a sense of space)
138
+ const skybox = BABYLON.MeshBuilder.CreateBox("skyBox", { size: 500.0 }, scene);
139
+ const skyboxMaterial = new BABYLON.StandardMaterial("skyBoxMat", scene);
140
+ skyboxMaterial.backFaceCulling = false; // Render the inside of the box
141
+ // Use a cube texture for the skybox. Replace URLs with your desired skybox faces.
142
+ // Example from Babylon.js playground:
143
+ skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("https://www.babylonjs-playground.com/textures/skybox", scene);
144
+ // For single file, ideally use a procedural sky or embed small textures via Base64 (becomes very large)
145
+ // skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture.CreateFromImages([
146
+ // "data:image/png;base64,...", // px
147
+ // "data:image/png;base64,...", // py
148
+ // "data:image/png;base64,...", // pz
149
+ // "data:image/png;base64,...", // nx
150
+ // "data:image/png;base64,...", // ny
151
+ // "data:image/png;base64,...", // nz
152
+ // ], scene);
153
+
154
+ skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
155
+ skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0); // Don't add color
156
+ skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0); // Don't make it shiny
157
+ skybox.material = skyboxMaterial;
158
+ skybox.infiniteDistance = true; // Keep skybox centered on camera
159
+
160
+
161
+ // --- Simple World Objects ---
162
+ // Add some basic shapes with physics and shadows
163
+
164
+ // Big Red Sphere
165
+ const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 4, segments: 32 }, scene);
166
+ sphere.position = new BABYLON.Vector3(-10, 2, 10);
167
+ sphere.checkCollisions = true;
168
+ const sphereMat = new BABYLON.StandardMaterial("sphereMat", scene);
169
+ sphereMat.diffuseColor = new BABYLON.Color3(0.8, 0.2, 0.2);
170
+ sphereMat.specularColor = new BABYLON.Color3(0.5, 0.5, 0.5);
171
+ sphere.material = sphereMat;
172
+ shadowGenerator.addShadowCaster(sphere); // This object casts shadows
173
+ sphere.physicsImpostor = new BABYLON.PhysicsImpostor(sphere, BABYLON.PhysicsImpostor.SphereImpostor, { mass: 1, restitution: 0.5 }, scene);
174
+
175
+ // Tall Blue Box
176
+ const box = BABYLON.MeshBuilder.CreateBox("box", { width: 2, height: 6, depth: 2 }, scene);
177
+ box.position = new BABYLON.Vector3(10, 3, 8);
178
+ box.checkCollisions = true;
179
+ const boxMat = new BABYLON.StandardMaterial("boxMat", scene);
180
+ boxMat.diffuseColor = new BABYLON.Color3(0.2, 0.3, 0.9);
181
+ box.material = boxMat;
182
+ shadowGenerator.addShadowCaster(box);
183
+ box.physicsImpostor = new BABYLON.PhysicsImpostor(box, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1, restitution: 0.1 }, scene);
184
+
185
+ // Small Green Cylinder
186
+ const cylinder = BABYLON.MeshBuilder.CreateCylinder("cylinder", { height: 3, diameter: 1.5, tessellation: 24 }, scene);
187
+ cylinder.position = new BABYLON.Vector3(0, 1.5, 15);
188
+ cylinder.checkCollisions = true;
189
+ const cylMat = new BABYLON.StandardMaterial("cylMat", scene);
190
+ cylMat.diffuseColor = new BABYLON.Color3(0.2, 0.8, 0.3);
191
+ cylinder.material = cylMat;
192
+ shadowGenerator.addShadowCaster(cylinder);
193
+ cylinder.physicsImpostor = new BABYLON.PhysicsImpostor(cylinder, BABYLON.PhysicsImpostor.CylinderImpostor, { mass: 0.5, restitution: 0.7 }, scene);
194
+
195
+ // Add a few smaller dynamic boxes
196
+ for (let i = 0; i < 5; i++) {
197
+ const smallBox = BABYLON.MeshBuilder.CreateBox(`smallBox${i}`, { size: 1 }, scene);
198
+ smallBox.position = new BABYLON.Vector3(Math.random() * 10 - 5, 5 + i * 1.5, Math.random() * 10 - 5);
199
+ smallBox.checkCollisions = true;
200
+ const smallBoxMat = new BABYLON.StandardMaterial(`smallBoxMat${i}`, scene);
201
+ smallBoxMat.diffuseColor = BABYLON.Color3.Random(); // Random color
202
+ smallBox.material = smallBoxMat;
203
+ shadowGenerator.addShadowCaster(smallBox);
204
+ smallBox.physicsImpostor = new BABYLON.PhysicsImpostor(smallBox, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 0.2, restitution: 0.4 }, scene);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
 
 
 
 
 
 
207
 
208
+ // --- Mobile Controls (Virtual Joysticks using Babylon.GUI) ---
209
+ const isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
210
+ let leftJoystick = null;
211
+ let rightJoystick = null;
212
+
213
+ if (isMobile) {
214
+ // Disable default mouse input for camera on mobile
215
+ camera.inputs.removeByType("FreeCameraMouseInput"); // UniversalCamera uses FreeCamera inputs internally
216
+
217
+ const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
218
+
219
+ // --- Left Joystick (Movement) ---
220
+ leftJoystick = new BABYLON.GUI.VirtualJoystick({
221
+ limitToContainer: true, // Keep nub inside the container
222
+ alwaysVisible: true, // Show even when not touched
223
+ });
224
+ leftJoystick.setJoystickColor("rgba(180, 180, 180, 0.6)"); // Semi-transparent gray
225
+ leftJoystick.container.width = "150px";
226
+ leftJoystick.container.height = "150px";
227
+ leftJoystick.container.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
228
+ leftJoystick.container.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM;
229
+ leftJoystick.container.left = "50px"; // Offset from edge
230
+ leftJoystick.container.top = "-30px"; // Offset from bottom
231
+ advancedTexture.addControl(leftJoystick.container); // Add joystick container to UI
232
+
233
+ // --- Right Joystick (Look/Rotation - Optional, sometimes just touch-drag on screen is preferred) ---
234
+ // Let's implement touch-drag look instead of a right joystick for simplicity
235
+ let isPointerDown = false;
236
+ let pointerStartX = 0;
237
+ let pointerStartY = 0;
238
+ const lookSensitivity = 0.005; // Adjust sensitivity for touch look
239
+
240
+ scene.onPointerObservable.add((pointerInfo) => {
241
+ // Check if the pointer event is on the canvas and not over a GUI element (like the joystick)
242
+ const target = pointerInfo.pickInfo?.pickedMesh || pointerInfo.event.target;
243
+ const isOverGUI = pointerInfo.event.target && pointerInfo.event.target.closest('.babylonGUI'); // Rough check
244
+
245
+ if (target === canvas && !isOverGUI && !leftJoystick.isPointerDown(pointerInfo.event.pointerId) ) {
246
+ switch (pointerInfo.type) {
247
+ case BABYLON.PointerEventTypes.POINTERDOWN:
248
+ isPointerDown = true;
249
+ pointerStartX = pointerInfo.event.clientX;
250
+ pointerStartY = pointerInfo.event.clientY;
251
+ // Prevent default actions if needed
252
+ // pointerInfo.event.preventDefault();
253
+ break;
254
+ case BABYLON.PointerEventTypes.POINTERUP:
255
+ isPointerDown = false;
256
+ break;
257
+ case BABYLON.PointerEventTypes.POINTERMOVE:
258
+ if (isPointerDown) {
259
+ const deltaX = pointerInfo.event.clientX - pointerStartX;
260
+ const deltaY = pointerInfo.event.clientY - pointerStartY;
261
+
262
+ // Update camera rotation based on drag
263
+ // Invert Y-axis movement if desired (common for touch look)
264
+ camera.cameraRotation.y += deltaX * lookSensitivity;
265
+ camera.cameraRotation.x += deltaY * lookSensitivity * -1; // Adjust multiplier/sign as needed
266
+
267
+ // Clamp vertical rotation to prevent flipping upside down
268
+ camera.cameraRotation.x = Math.max(-Math.PI / 2.1, Math.min(Math.PI / 2.1, camera.cameraRotation.x));
269
+
270
+ // Update start position for next move calculation
271
+ pointerStartX = pointerInfo.event.clientX;
272
+ pointerStartY = pointerInfo.event.clientY;
273
+ }
274
+ break;
275
+ }
276
+ } else {
277
+ // Reset if pointer goes up outside canvas or over GUI
278
+ if (pointerInfo.type === BABYLON.PointerEventTypes.POINTERUP) {
279
+ isPointerDown = false;
280
+ }
281
+ }
282
+ });
283
+
284
+
285
+ // --- Link Left Joystick to Camera Movement ---
286
+ scene.onBeforeRenderObservable.add(() => {
287
+ if (leftJoystick && leftJoystick.pressed) {
288
+ // Get joystick delta vector
289
+ const joyX = leftJoystick.deltaPosition.x;
290
+ const joyY = leftJoystick.deltaPosition.y; // Y is forward/backward
291
+
292
+ // Calculate movement direction based on camera's orientation
293
+ const forward = camera.getDirection(BABYLON.Axis.Z);
294
+ const right = camera.getDirection(BABYLON.Axis.X);
295
+
296
+ // Note: Joystick Y is typically inverted (up is positive, forward is negative Z)
297
+ let moveDirection = forward.scale(joyY * -1).add(right.scale(joyX));
298
+
299
+ // Normalize and scale by camera speed and joystick magnitude
300
+ if (moveDirection.length() > 0.01) { // Avoid tiny movements
301
+ moveDirection.normalize();
302
+ // Apply movement - camera.cameraDirection is the vector to add to position
303
+ camera.cameraDirection.addInPlace(moveDirection.scale(camera.speed));
304
+ }
305
+ }
306
+ });
307
+
308
+ } else {
309
+ // Optional: Display controls hint for desktop users
310
+ const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
311
+ const instructions = new BABYLON.GUI.TextBlock();
312
+ instructions.text = "Use WASD/Arrows to Move | Mouse to Look";
313
+ instructions.color = "white";
314
+ instructions.fontSize = 16;
315
+ instructions.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
316
+ instructions.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
317
+ instructions.paddingTop = "15px";
318
+ instructions.paddingRight = "15px";
319
+ advancedTexture.addControl(instructions);
320
+ }
321
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
 
323
+ // --- Hide Loading Indicator ---
324
+ if(loadingIndicator) loadingIndicator.style.display = 'none';
325
+
326
+ return scene;
327
+ }; // End of createScene function
328
+
329
+ // --- Create and Render ---
330
+ try {
331
+ const scene = createScene();
332
+
333
+ // Main render loop
334
+ engine.runRenderLoop(() => {
335
+ if (scene) {
336
+ scene.render();
337
+ }
338
+ });
339
+
340
+ // Handle window resize
341
+ window.addEventListener('resize', () => {
342
+ engine.resize();
343
+ });
344
+ } catch (e) {
345
+ console.error("Error creating Babylon scene:", e);
346
+ if(loadingIndicator) {
347
+ loadingIndicator.textContent = "Error loading demo. Check console.";
348
+ loadingIndicator.style.color = "red";
349
+ }
350
  }
351
 
352
+ }); // End of DOMContentLoaded listener
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  </script>
 
354
  </body>
355
  </html>