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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +285 -301
index.html CHANGED
@@ -1,355 +1,339 @@
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>
 
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, 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;
18
+ width: 100%;
19
+ height: 100%;
20
  margin: 0;
21
  padding: 0;
22
+ font-family: Arial, sans-serif;
23
+ background-color: #000; /* Фон на случай долгой загрузки */
24
+ }
25
+
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
+
338
  </body>
339
  </html>