Aleksmorshen commited on
Commit
5f69ca1
·
verified ·
1 Parent(s): 1811dcf

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +369 -288
index.html CHANGED
@@ -2,16 +2,8 @@
2
  <html lang="ru">
3
  <head>
4
  <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
6
- <title>Прото-Метавселенная Демо (Babylon.js)</title>
7
- <meta name="description" content="Демонстрация простого 3D-мира на Babylon.js в одном HTML файле.">
8
-
9
- <!-- Подключение Babylon.js и его зависимостей из CDN -->
10
- <script src="https://cdn.babylonjs.com/babylon.js"></script>
11
- <!-- <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script> -->
12
- <!-- <script src="https://cdn.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script> -->
13
- <!-- Раскомментируйте loaders/materials, если будете загружать модели или использовать спец. материалы -->
14
-
15
  <style>
16
  html, body {
17
  overflow: hidden;
@@ -26,312 +18,401 @@
26
  #renderCanvas {
27
  width: 100%;
28
  height: 100%;
29
- touch-action: none; /* Важно для управления камерой на мобильных */
30
- outline: none; /* Убирает рамку фокуса */
31
  }
32
 
33
- /* Стили для простого индикатора загрузки */
34
- #loadingScreen {
35
  position: absolute;
36
- top: 0;
37
- left: 0;
38
- width: 100%;
39
- height: 100%;
40
- background-color: #222;
41
  color: white;
42
- display: flex;
43
- justify-content: center;
44
- align-items: center;
45
- font-size: 2em;
46
- z-index: 100;
47
- transition: opacity 0.5s ease-out;
 
 
 
48
  }
49
 
50
- /* Стили для простого виртуального джойстика (пример) */
51
- #joystickArea {
52
- position: absolute;
53
- bottom: 20px;
54
- left: 20px;
55
- width: 150px;
56
- height: 150px;
57
- background: rgba(255, 255, 255, 0.2);
58
- border-radius: 50%;
59
- display: none; /* Скрыт по умолчанию, можно показать для мобильных */
60
- z-index: 10;
61
- border: 2px solid rgba(255, 255, 255, 0.4);
62
  }
63
 
64
- #joystickThumb {
65
- position: absolute;
66
- width: 60px;
67
- height: 60px;
68
- background: rgba(255, 255, 255, 0.5);
69
- border-radius: 50%;
70
- top: 50%;
71
- left: 50%;
72
- transform: translate(-50%, -50%);
73
- cursor: grab;
74
  }
75
- /* Стиль для кнопки действия (пример) */
76
- #actionButton {
 
77
  position: absolute;
78
- bottom: 40px;
79
- right: 40px;
80
- width: 80px;
81
- height: 80px;
82
- background: rgba(0, 150, 255, 0.6);
83
- border-radius: 50%;
84
- display: none; /* Скрыт по умолчанию */
85
  z-index: 10;
86
- border: 2px solid rgba(255, 255, 255, 0.4);
 
 
 
 
87
  color: white;
88
- font-size: 1.5em;
89
- text-align: center;
90
- line-height: 80px; /* Центрирование текста по вертикали */
91
  cursor: pointer;
92
- user-select: none; /* Запретить выделение текста */
 
93
  }
94
 
95
- /* Медиа-запрос для отображения мобильных контролов */
96
- @media (max-width: 768px) { /* или другое условие для тач-устройств */
97
- #joystickArea {
98
- /* display: block; */ /* Пока джойстик не реализован */
99
- }
100
- #actionButton {
101
- /* display: block; */ /* Пока кнопка не реализована */
102
- }
103
- }
 
104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </style>
 
 
 
 
 
 
 
106
  </head>
107
  <body>
108
 
109
- <!-- Элемент Canvas для рендеринга 3D -->
110
- <canvas id="renderCanvas"></canvas>
111
 
112
- <!-- Экран загрузки -->
113
- <div id="loadingScreen">Загрузка мира...</div>
114
 
115
- <!-- Область для виртуального джойстика (упрощенно) -->
116
- <div id="joystickArea">
117
- <div id="joystickThumb"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  </div>
119
 
120
- <!-- Кнопка действия (упрощенно) -->
121
- <div id="actionButton">A</div>
122
-
 
 
123
 
124
  <script>
125
- // Получаем элементы DOM
126
  const canvas = document.getElementById('renderCanvas');
 
127
  const loadingScreen = document.getElementById('loadingScreen');
128
- const joystickArea = document.getElementById('joystickArea');
129
- const actionButton = document.getElementById('actionButton');
130
-
131
- // Проверка поддержки WebGL
132
- if (!BABYLON.Engine.isSupported()) {
133
- window.alert('Ваш браузер не поддерживает WebGL, необходимый для Babylon.js!');
134
- } else {
135
- // Создание движка Babylon.js
136
- const engine = new BABYLON.Engine(canvas, true, {
137
- preserveDrawingBuffer: true, // Для скриншотов, если нужно
138
- stencil: true,
139
- antialias: true // Включаем сглаживание
140
- }, true);
141
-
142
- // Показываем экран загрузки Babylon по умолчанию
143
- engine.displayLoadingUI();
144
-
145
- // --- Создание сцены ---
146
- const createScene = () => {
147
- const scene = new BABYLON.Scene(engine);
148
- scene.clearColor = new BABYLON.Color3(0.1, 0.1, 0.2); // Темно-синий фон
149
-
150
- // --- Камера ---
151
- // ArcRotateCamera - вращается вокруг цели, хорошо для мобильных и ПК
152
- const camera = new BABYLON.ArcRotateCamera("camera",
153
- -Math.PI / 2.5, // Начальный угол (alpha)
154
- Math.PI / 3, // Начальный угол (beta)
155
- 20, // Начальный радиус (удаление)
156
- new BABYLON.Vector3(0, 1, 0), // Точка, вокруг которой вращаемся
157
- scene);
158
-
159
- camera.attachControl(canvas, true); // Привязываем управление к canvas (мышь/тач)
160
- camera.lowerRadiusLimit = 5; // Минимальное приближение
161
- camera.upperRadiusLimit = 50; // Максимальное удаление
162
- camera.wheelPrecision = 50; // Чувствительность колеса мыши/pinch-зума
163
- // camera.useAutoRotationBehavior = true; // Автоматическое медленное вращение (опционально)
164
-
165
- // --- Освещение ---
166
- // Основной свет (имитация солнца)
167
- const light = new BABYLON.DirectionalLight("dirLight", new BABYLON.Vector3(-0.5, -1, 0.3), scene);
168
- light.position = new BABYLON.Vector3(20, 40, 20);
169
- light.intensity = 1.0;
170
-
171
- // Рассеянный свет (подсветка теней)
172
- const hemiLight = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(0, 1, 0), scene);
173
- hemiLight.intensity = 0.4;
174
- hemiLight.diffuse = new BABYLON.Color3(0.8, 0.85, 1); // Светлый холодный оттенок
175
- hemiLight.groundColor = new BABYLON.Color3(0.3, 0.2, 0.1); // Теплый оттенок снизу
176
-
177
- // --- Окружение ---
178
- // Земля (плоскость)
179
- const ground = BABYLON.MeshBuilder.CreateGround("ground", {width: 50, height: 50}, scene);
180
- const groundMaterial = new BABYLON.PBRMetallicRoughnessMaterial("groundMat", scene);
181
- groundMaterial.baseColor = new BABYLON.Color3(0.3, 0.5, 0.3); // Зеленоватый цвет
182
- groundMaterial.metallic = 0.1; // Почти не металл
183
- groundMaterial.roughness = 0.8; // Довольно шероховатая
184
- ground.material = groundMaterial;
185
- ground.receiveShadows = true; // Земля принимает тени
186
-
187
- // Скайбокс (небо)
188
- const skybox = BABYLON.MeshBuilder.CreateBox("skyBox", {size: 1000.0}, scene);
189
- const skyboxMaterial = new BABYLON.StandardMaterial("skyBoxMat", scene);
190
- skyboxMaterial.backFaceCulling = false; // Видим внутренние стороны куба
191
- // Простой процедурный скайбокс, чтобы не зависеть от внешних текстур
192
- skyboxMaterial.reflectionTexture = new BABYLON.HDRCubeTexture("./textures/environment.env", scene, 512, false, true, false, true); // Замените на реальный путь к .env/.hdr если есть
193
- skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE; // Режим скайбокса
194
- // Или можно сделать просто цвет:
195
- // skyboxMaterial.diffuseColor = new BABYLON.Color3(0.5, 0.8, 1.0); // Голубой
196
- // skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0); // Без бликов
197
- skyboxMaterial.disableLighting = true; // Не зависит от освещения сцены
198
- skybox.material = skyboxMaterial;
199
- skybox.infiniteDistance = true; // Скайбокс всегда далеко
200
-
201
-
202
- // --- Объекты в мире ---
203
- // Сфера
204
- const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2, segments: 32}, scene);
205
- sphere.position.y = 1.5;
206
- sphere.position.x = -5;
207
- const sphereMaterial = new BABYLON.PBRMetallicRoughnessMaterial("sphereMat", scene);
208
- sphereMaterial.baseColor = new BABYLON.Color3(0.8, 0.2, 0.2); // Красный
209
- sphereMaterial.metallic = 0.8;
210
- sphereMaterial.roughness = 0.2;
211
- sphere.material = sphereMaterial;
212
-
213
- // Куб
214
- const box = BABYLON.MeshBuilder.CreateBox("box", {size: 2}, scene);
215
- box.position.y = 1;
216
- box.position.x = 0;
217
- box.rotation.y = Math.PI / 5; // Немного повернем
218
- const boxMaterial = new BABYLON.PBRMetallicRoughnessMaterial("boxMat", scene);
219
- boxMaterial.baseColor = new BABYLON.Color3(0.2, 0.3, 0.8); // Синий
220
- boxMaterial.metallic = 0.2;
221
- boxMaterial.roughness = 0.6;
222
- box.material = boxMaterial;
223
-
224
- // Цилиндр
225
- const cylinder = BABYLON.MeshBuilder.CreateCylinder("cylinder", {height: 3, diameter: 1.5}, scene);
226
- cylinder.position.y = 1.5;
227
- cylinder.position.x = 5;
228
- const cylinderMaterial = new BABYLON.PBRMetallicRoughnessMaterial("cylMat", scene);
229
- cylinderMaterial.baseColor = new BABYLON.Color3(0.9, 0.9, 0.9); // Белый
230
- cylinderMaterial.metallic = 1.0; // Полностью металлический
231
- cylinderMaterial.roughness = 0.1; // Очень гладкий
232
- cylinder.material = cylinderMaterial;
233
-
234
- // --- Тени ---
235
- const shadowGenerator = new BABYLON.ShadowGenerator(1024, light); // 1024 - размер карты теней
236
- shadowGenerator.useExponentialShadowMap = true; // Мягкие тени
237
- // Добавляем объекты, отбрасывающие тень
238
- shadowGenerator.addShadowCaster(sphere);
239
- shadowGenerator.addShadowCaster(box);
240
- shadowGenerator.addShadowCaster(cylinder);
241
- // Тень от скайбокса не нужна
242
- // ground.receiveShadows уже установлено
243
-
244
- // --- Простое взаимодействие (клик/тап по объекту) ---
245
- scene.onPointerDown = (evt, pickResult) => {
246
- // Проверяем, кликнули ли мы по какому-то мешу
247
- if (pickResult.hit && pickResult.pickedMesh && pickResult.pickedMesh !== ground && pickResult.pickedMesh !== skybox) {
248
- const pickedMesh = pickResult.pickedMesh;
249
- console.log("Кликнули на:", pickedMesh.name);
250
-
251
- // Простая анимация: пульсация размера
252
- const animationScale = new BABYLON.Animation(
253
- "scaleAnim", // Имя
254
- "scaling", // Анимируемое свойство
255
- 30, // FPS
256
- BABYLON.Animation.ANIMATIONTYPE_VECTOR3, // Тип данных
257
- BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT // Режим проигрывания
258
- );
259
-
260
- const keys = [];
261
- const originalScale = pickedMesh.scaling.clone();
262
- const largeScale = originalScale.multiply(new BABYLON.Vector3(1.3, 1.3, 1.3));
263
-
264
- keys.push({ frame: 0, value: originalScale });
265
- keys.push({ frame: 5, value: largeScale }); // Увеличиваем за 5 кадров
266
- keys.push({ frame: 15, value: originalScale }); // Возвращаем за 10 кадров
267
-
268
- animationScale.setKeys(keys);
269
-
270
- // Добавляем easing для плавности
271
- const easingFunction = new BABYLON.SineEase();
272
- easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
273
- animationScale.setEasingFunction(easingFunction);
274
-
275
-
276
- pickedMesh.animations = []; // Очищаем предыдущие анимации (если есть)
277
- pickedMesh.animations.push(animationScale);
278
-
279
- scene.beginAnimation(pickedMesh, 0, 15, false, 1.0); // Запускаем анимацию
280
- }
281
- };
282
-
283
-
284
- // --- Оптимизации (можно добавить больше) ---
285
- scene.freezeMaterials(); // Оптимизация материалов после их настройки
286
- scene.blockMaterialDirtyMechanism = true; // Блокировка проверки "грязных" материалов
287
-
288
- // Возвращаем созданную сцену
289
- return scene;
290
- }; // --- Конец createScene ---
291
-
292
-
293
- // Создаем сцену
294
- const scene = createScene();
295
-
296
- // Прячем стандартный экран загрузки Babylon и наш кастомный
297
- scene.executeWhenReady(() => {
298
- engine.hideLoadingUI();
299
- if (loadingScreen) {
300
- loadingScreen.style.opacity = '0';
301
- // Можно ��далить элемент после анимации исчезновения
302
- setTimeout(() => {
303
- loadingScreen.style.display = 'none';
304
- }, 500); // Должно совпадать с transition в CSS
305
- }
306
-
307
- // Определяем, мобильное ли устройство (очень упрощенно!)
308
- const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
309
- if (isMobile) {
310
- // Показываем мобильные контролы, если нужно
311
- // joystickArea.style.display = 'block';
312
- // actionButton.style.display = 'block';
313
- console.log("Обнаружено мобильное устройство (упрощенная проверка).");
314
- // Здесь можно добавить логику для виртуального джойстика и кнопки,
315
- // которая будет управлять камерой или персонажем (сейчас не реализовано).
316
- // Babylon.js имеет VirtualJoysticksCamera, но она сложнее в настройке.
317
- // ArcRotateCamera уже хорошо работает с тачем (перетаскивание - вращение, щипок - зум).
318
- } else {
319
- console.log("Обнаружено десктопное устройство.");
320
- }
321
-
322
- // Главный цикл рендеринга
323
- engine.runRenderLoop(() => {
324
- scene.render();
325
- });
326
-
327
- });
328
-
329
-
330
- // Обработка изменения размера окна/экрана
331
- window.addEventListener('resize', () => {
332
- engine.resize();
333
- });
334
- } // Конец проверки WebGL
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
  </script>
337
 
 
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>Arctic Mineral Water - 3D Презентация</title>
 
 
 
 
 
 
 
 
7
  <style>
8
  html, body {
9
  overflow: hidden;
 
18
  #renderCanvas {
19
  width: 100%;
20
  height: 100%;
21
+ touch-action: none; /* Важно для Babylon.js на мобильных */
 
22
  }
23
 
24
+ /* Стили для информационных блоков */
25
+ .info-overlay {
26
  position: absolute;
27
+ top: 15%;
28
+ left: 50%;
29
+ transform: translateX(-50%);
 
 
30
  color: white;
31
+ background-color: rgba(0, 20, 50, 0.7);
32
+ padding: 15px 25px;
33
+ border-radius: 10px;
34
+ text-align: center;
35
+ max-width: 80%;
36
+ box-shadow: 0 0 15px rgba(100, 150, 255, 0.5);
37
+ opacity: 0;
38
+ transition: opacity 0.5s ease-in-out;
39
+ pointer-events: none; /* Чтобы не мешать взаимодействию с canvas */
40
  }
41
 
42
+ .info-overlay.visible {
43
+ opacity: 1;
 
 
 
 
 
 
 
 
 
 
44
  }
45
 
46
+ .info-overlay h2 {
47
+ margin-top: 0;
48
+ margin-bottom: 10px;
49
+ font-size: 1.5em;
50
+ color: #cceeff;
51
+ }
52
+
53
+ .info-overlay p {
54
+ margin-bottom: 0;
55
+ font-size: 1em;
56
  }
57
+
58
+ /* Стили для кнопок навигации */
59
+ #navigation {
60
  position: absolute;
61
+ bottom: 20px;
62
+ left: 50%;
63
+ transform: translateX(-50%);
64
+ display: flex;
65
+ gap: 15px;
 
 
66
  z-index: 10;
67
+ }
68
+
69
+ #navigation button {
70
+ padding: 12px 20px;
71
+ font-size: 1em;
72
  color: white;
73
+ background-color: rgba(0, 50, 100, 0.8);
74
+ border: 1px solid #cceeff;
75
+ border-radius: 25px;
76
  cursor: pointer;
77
+ transition: background-color 0.3s ease, transform 0.1s ease;
78
+ box-shadow: 0 0 10px rgba(100, 150, 255, 0.4);
79
  }
80
 
81
+ #navigation button:disabled {
82
+ background-color: rgba(50, 50, 50, 0.6);
83
+ color: #aaa;
84
+ cursor: not-allowed;
85
+ border-color: #777;
86
+ }
87
+
88
+ #navigation button:not(:disabled):active {
89
+ transform: scale(0.95);
90
+ }
91
 
92
+ #loadingScreen {
93
+ position: absolute;
94
+ top: 0;
95
+ left: 0;
96
+ width: 100%;
97
+ height: 100%;
98
+ background-color: #05101f;
99
+ color: white;
100
+ display: flex;
101
+ justify-content: center;
102
+ align-items: center;
103
+ font-size: 1.5em;
104
+ z-index: 100;
105
+ transition: opacity 1s ease-out;
106
+ }
107
  </style>
108
+ <!-- Подключение Babylon.js -->
109
+ <script src="https://cdn.babylonjs.com/babylon.js"></script>
110
+ <!-- Опционально: для загрузки моделей GLTF/GLB -->
111
+ <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
112
+ <!-- Опционально: для удобных материалов -->
113
+ <script src="https://cdn.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
114
+
115
  </head>
116
  <body>
117
 
118
+ <div id="loadingScreen">Загрузка арктической среды...</div>
 
119
 
120
+ <canvas id="renderCanvas"></canvas>
 
121
 
122
+ <!-- Информационные блоки (скрыты по умолчанию) -->
123
+ <div id="info-welcome" class="info-overlay">
124
+ <h2>Arctic</h2>
125
+ <p>Чистота северных ледников в каждой капле.</p>
126
+ </div>
127
+ <div id="info-source" class="info-overlay">
128
+ <h2>Источник</h2>
129
+ <p>Добывается из реликтового подземного озера под вечной мерзлотой.</p>
130
+ </div>
131
+ <div id="info-purity" class="info-overlay">
132
+ <h2>Кристальная Чистота</h2>
133
+ <p>Многоступенчатая природная фильтрация сквозь горные породы.</p>
134
+ </div>
135
+ <div id="info-bottle" class="info-overlay">
136
+ <h2>Уникальный Дизайн</h2>
137
+ <p>Бутылка, вдохновленная формой айсберга.</p>
138
  </div>
139
 
140
+ <!-- Кнопки навигации -->
141
+ <div id="navigation">
142
+ <button id="prevBtn" disabled>Назад</button>
143
+ <button id="nextBtn">Далее</button>
144
+ </div>
145
 
146
  <script>
 
147
  const canvas = document.getElementById('renderCanvas');
148
+ const engine = new BABYLON.Engine(canvas, true, { stencil: true, preserveDrawingBuffer: true }, true);
149
  const loadingScreen = document.getElementById('loadingScreen');
150
+ const infoOverlays = {
151
+ welcome: document.getElementById('info-welcome'),
152
+ source: document.getElementById('info-source'),
153
+ purity: document.getElementById('info-purity'),
154
+ bottle: document.getElementById('info-bottle')
155
+ };
156
+ const prevBtn = document.getElementById('prevBtn');
157
+ const nextBtn = document.getElementById('nextBtn');
158
+
159
+ let currentStage = 0;
160
+ let scene = null;
161
+ let camera = null;
162
+ const animationDuration = 1.5; // Длительность анимации перехода (в секундах)
163
+
164
+ // Скрыть лоадер, когда сцена готова
165
+ engine.loadingScreen = {
166
+ displayLoadingUI: function() {
167
+ loadingScreen.style.opacity = '1';
168
+ },
169
+ hideLoadingUI: function() {
170
+ loadingScreen.style.opacity = '0';
171
+ // Скрыть после завершения анимации исчезновения
172
+ setTimeout(() => { loadingScreen.style.display = 'none'; }, 1000);
173
+ },
174
+ loadingUIBackgroundColor: "#05101f",
175
+ loadingUIText: "Загрузка арктической среды..."
176
+ };
177
+
178
+ engine.displayLoadingUI(); // Показать лоадер сразу
179
+
180
+ function createScene() {
181
+ scene = new BABYLON.Scene(engine);
182
+ scene.clearColor = new BABYLON.Color3(0.1, 0.2, 0.35); // Темно-синий фон
183
+
184
+ // --- Камера ---
185
+ // Используем UniversalCamera для лучшего контроля над анимацией
186
+ camera = new BABYLON.UniversalCamera("camera", new BABYLON.Vector3(0, 5, -20), scene);
187
+ camera.setTarget(new BABYLON.Vector3(0, 2, 0));
188
+ // camera.attachControl(canvas, true); // Отключаем управление камерой по умолчанию
189
+
190
+ // --- Освещение ---
191
+ const light = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(0, 1, 0), scene);
192
+ light.intensity = 0.8;
193
+ light.groundColor = new BABYLON.Color3(0.5, 0.7, 1); // Холодный цвет снизу
194
+ light.specular = BABYLON.Color3.Black(); // Убираем блики от этого света
195
+
196
+ const dirLight = new BABYLON.DirectionalLight("dirLight", new BABYLON.Vector3(-0.5, -1, -0.8), scene);
197
+ dirLight.position = new BABYLON.Vector3(20, 40, 30);
198
+ dirLight.intensity = 0.7;
199
+ dirLight.shadowMinZ = 1;
200
+ dirLight.shadowMaxZ = 150;
201
+
202
+ // --- Тень ---
203
+ const shadowGenerator = new BABYLON.ShadowGenerator(1024, dirLight);
204
+ shadowGenerator.useBlurExponentialShadowMap = true;
205
+ shadowGenerator.blurKernel = 32;
206
+ shadowGenerator.darkness = 0.5;
207
+
208
+ // --- Skybox (Арктическое небо) ---
209
+ const skybox = BABYLON.MeshBuilder.CreateBox("skyBox", { size: 1000.0 }, scene);
210
+ const skyboxMaterial = new BABYLON.StandardMaterial("skyBoxMat", scene);
211
+ skyboxMaterial.backFaceCulling = false;
212
+ // Используем кубическую текстуру. Замените пути на реальные URL текстур неба!
213
+ // Эти текстуры взяты из примеров BabylonJS Playground
214
+ skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("https://playground.babylonjs.com/textures/skybox_ice", scene);
215
+ // Старые версии использовали:
216
+ // skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("textures/TropicalSunnyDay", scene); // Заменить на арктическую!
217
+ skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
218
+ skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0);
219
+ skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
220
+ skybox.material = skyboxMaterial;
221
+ skybox.infiniteDistance = true; // Фиксируем скайбокс
222
+
223
+ // --- Ландшафт (Земля/Снег) ---
224
+ const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 200, height: 200, subdivisions: 50 }, scene);
225
+ const groundMaterial = new BABYLON.StandardMaterial("groundMat", scene);
226
+ // Простая текстура снега из Playground для примера
227
+ groundMaterial.diffuseTexture = new BABYLON.Texture("https://playground.babylonjs.com/textures/snow.jpg", scene);
228
+ groundMaterial.diffuseTexture.uScale = 10;
229
+ groundMaterial.diffuseTexture.vScale = 10;
230
+ groundMaterial.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1); // Небольшой блик
231
+ groundMaterial.freeze(); // Оптимизация
232
+ ground.material = groundMaterial;
233
+ ground.receiveShadows = true;
234
+
235
+ // --- Плейсхолдер бутылки ---
236
+ // !!! ЗАМЕНИТЬ НА РЕАЛЬНУЮ МОДЕЛЬ !!!
237
+ const bottlePlaceholder = BABYLON.MeshBuilder.CreateCylinder("bottle", { height: 4, diameterTop: 0.8, diameterBottom: 1, tessellation: 24 }, scene);
238
+ bottlePlaceholder.position = new BABYLON.Vector3(0, 2, 0); // Позиция в центре
239
+ const bottleMaterial = new BABYLON.StandardMaterial("bottleMat", scene);
240
+ bottleMaterial.diffuseColor = new BABYLON.Color3(0.6, 0.8, 1.0); // Голубоватый цвет
241
+ bottleMaterial.alpha = 0.7; // Полупрозрачность
242
+ bottleMaterial.specularColor = new BABYLON.Color3(0.8, 0.9, 1);
243
+ bottleMaterial.specularPower = 64;
244
+ // Добавляем отражение окружения для эффекта стекла/льда
245
+ bottleMaterial.reflectionTexture = skyboxMaterial.reflectionTexture;
246
+ bottleMaterial.reflectionFresnelParameters = new BABYLON.FresnelParameters();
247
+ bottleMaterial.reflectionFresnelParameters.bias = 0.1;
248
+ bottleMaterial.reflectionFresnelParameters.power = 2;
249
+ bottlePlaceholder.material = bottleMaterial;
250
+ shadowGenerator.addShadowCaster(bottlePlaceholder); // Бутылка отбрасывает тень
251
+
252
+ // --- Простые элементы окружения (Ледяные глыбы/Горы) ---
253
+ const iceMaterial = new BABYLON.StandardMaterial("iceMat", scene);
254
+ iceMaterial.diffuseColor = new BABYLON.Color3(0.8, 0.9, 1.0);
255
+ iceMaterial.alpha = 0.85;
256
+ iceMaterial.specularColor = new BABYLON.Color3(1, 1, 1);
257
+ iceMaterial.reflectionTexture = skyboxMaterial.reflectionTexture; // Отражения
258
+ iceMaterial.reflectionFresnelParameters = new BABYLON.FresnelParameters();
259
+ iceMaterial.reflectionFresnelParameters.bias = 0.3;
260
+ iceMaterial.reflectionFresnelParameters.power = 1.5;
261
+
262
+ const mountainPositions = [
263
+ new BABYLON.Vector3(-40, 0, 50),
264
+ new BABYLON.Vector3(50, 0, 30),
265
+ new BABYLON.Vector3(0, 0, 70),
266
+ new BABYLON.Vector3(-60, 0, -20),
267
+ new BABYLON.Vector3(30, 0, -50),
268
+ ];
269
+
270
+ mountainPositions.forEach((pos, i) => {
271
+ const size = 20 + Math.random() * 30;
272
+ const mountain = BABYLON.MeshBuilder.CreateSphere(`ice_${i}`, {diameter: size}, scene);
273
+ mountain.position = pos;
274
+ mountain.scaling.y = 0.8 + Math.random() * 1.2; // Приплюснуть или вытянуть
275
+ mountain.scaling.x = 1 + Math.random() * 0.5;
276
+ mountain.scaling.z = 1 + Math.random() * 0.5;
277
+ mountain.material = iceMaterial;
278
+ shadowGenerator.addShadowCaster(mountain);
279
+ mountain.receiveShadows = true;
280
+ });
281
+
282
+ // --- Добавить эффект легкого тумана/дымки ---
283
+ scene.fogMode = BABYLON.Scene.FOGMODE_EXP;
284
+ scene.fogDensity = 0.006;
285
+ scene.fogColor = new BABYLON.Color3(0.5, 0.7, 0.9);
286
+
287
+ return scene;
288
+ }
289
+
290
+ // --- Логика навигации и анимации ---
291
+
292
+ // Определяем точки (стадии) обзора
293
+ const stages = [
294
+ { // 0: Начальный вид издалека
295
+ cameraPos: new BABYLON.Vector3(0, 8, -25),
296
+ cameraTarget: new BABYLON.Vector3(0, 2, 0),
297
+ infoId: 'welcome'
298
+ },
299
+ { // 1: Ближе к источнику (условному)
300
+ cameraPos: new BABYLON.Vector3(-15, 6, 15),
301
+ cameraTarget: new BABYLON.Vector3(-5, 1, 5), // Смотрим чуть в сторону
302
+ infoId: 'source'
303
+ },
304
+ { // 2: Вид на "чистоту" ландшафта
305
+ cameraPos: new BABYLON.Vector3(20, 10, 20),
306
+ cameraTarget: new BABYLON.Vector3(0, 0, 0), // Смотрим на центр
307
+ infoId: 'purity'
308
+ },
309
+ { // 3: Крупный план бутылки
310
+ cameraPos: new BABYLON.Vector3(0, 3.5, -6),
311
+ cameraTarget: new BABYLON.Vector3(0, 2, 0), // Смотрим на бутылку
312
+ infoId: 'bottle'
313
+ }
314
+ ];
315
+
316
+ function updateUI(stageIndex) {
317
+ // Сначала скрыть все инфо-блоки
318
+ Object.values(infoOverlays).forEach(el => el.classList.remove('visible'));
319
+
320
+ // Показать нужный блок
321
+ const currentInfoId = stages[stageIndex]?.infoId;
322
+ if (currentInfoId && infoOverlays[currentInfoId]) {
323
+ infoOverlays[currentInfoId].classList.add('visible');
324
+ }
325
+
326
+ // Обновить состояние кнопок
327
+ prevBtn.disabled = stageIndex === 0;
328
+ nextBtn.disabled = stageIndex === stages.length - 1;
329
+ }
330
+
331
+ function animateCameraToStage(stageIndex) {
332
+ const targetStage = stages[stageIndex];
333
+ if (!targetStage || !camera) return;
334
+
335
+ const frameRate = 60;
336
+ const totalFrames = animationDuration * frameRate;
337
+
338
+ // Анимация позиции камеры
339
+ const camPosAnimation = new BABYLON.Animation(
340
+ "camPosAnim",
341
+ "position",
342
+ frameRate,
343
+ BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
344
+ BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
345
+ );
346
+ const keysPos = [];
347
+ keysPos.push({ frame: 0, value: camera.position });
348
+ keysPos.push({ frame: totalFrames, value: targetStage.cameraPos });
349
+ camPosAnimation.setKeys(keysPos);
350
+
351
+ // Добавляем Easing Function для плавности
352
+ const easingFunction = new BABYLON.QuadraticEase();
353
+ easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
354
+ camPosAnimation.setEasingFunction(easingFunction);
355
+
356
+
357
+ // Анимация цели (точки, куда смотрит камера)
358
+ const camTargetAnimation = new BABYLON.Animation(
359
+ "camTargetAnim",
360
+ "target", // Используем setTarget для UniversalCamera
361
+ frameRate,
362
+ BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
363
+ BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
364
+ );
365
+ const keysTarget = [];
366
+ keysTarget.push({ frame: 0, value: camera.getTarget() }); // Текущая цель
367
+ keysTarget.push({ frame: totalFrames, value: targetStage.cameraTarget });
368
+ camTargetAnimation.setKeys(keysTarget);
369
+ camTargetAnimation.setEasingFunction(easingFunction); // Та же плавность
370
+
371
+ // Запускаем обе анимации
372
+ scene.beginDirectAnimation(camera, [camPosAnimation, camTargetAnimation], 0, totalFrames, false, 1, () => {
373
+ // Коллбэк после завершения анимации
374
+ camera.setTarget(targetStage.cameraTarget); // Убедимся, что цель установлена точно
375
+ updateUI(stageIndex); // Показать UI после завершения перехода
376
+ });
377
+ }
378
+
379
+ // --- Инициализация и обработчики событий ---
380
+ scene = createScene();
381
+
382
+ // Начать рендеринг
383
+ engine.runRenderLoop(function () {
384
+ if (scene && scene.activeCamera) {
385
+ scene.render();
386
+ }
387
+ });
388
+
389
+ // Обработка изменения размера окна
390
+ window.addEventListener('resize', function () {
391
+ engine.resize();
392
+ });
393
+
394
+ // Обработчики кнопок
395
+ nextBtn.addEventListener('click', () => {
396
+ if (currentStage < stages.length - 1) {
397
+ currentStage++;
398
+ animateCameraToStage(currentStage);
399
+ updateUI(currentStage); // Можно обновить кнопки сразу, не дожидаясь конца анимации
400
+ }
401
+ });
402
+
403
+ prevBtn.addEventListener('click', () => {
404
+ if (currentStage > 0) {
405
+ currentStage--;
406
+ animateCameraToStage(currentStage);
407
+ updateUI(currentStage);
408
+ }
409
+ });
410
+
411
+ // Показать начальный UI после небольшой задержки (когда сцена уже видна)
412
+ scene.executeWhenReady(() => {
413
+ engine.hideLoadingUI(); // Скрыть экран загрузки
414
+ updateUI(currentStage); // Показать первый блок информации
415
+ });
416
 
417
  </script>
418