Spaces:
Running
Running
Update index.html
Browse files- index.html +285 -301
index.html
CHANGED
@@ -1,355 +1,339 @@
|
|
1 |
<!DOCTYPE html>
|
2 |
-
<html>
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
-
|
6 |
-
<
|
7 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
<style>
|
9 |
-
/* Basic CSS Reset & Canvas Styling */
|
10 |
html, body {
|
|
|
|
|
|
|
11 |
margin: 0;
|
12 |
padding: 0;
|
|
|
|
|
|
|
|
|
|
|
13 |
width: 100%;
|
14 |
height: 100%;
|
15 |
-
|
16 |
-
|
17 |
}
|
18 |
|
19 |
-
|
|
|
|
|
|
|
|
|
20 |
width: 100%;
|
21 |
height: 100%;
|
22 |
-
|
23 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
}
|
25 |
|
26 |
-
|
27 |
-
#
|
28 |
position: absolute;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
top: 50%;
|
30 |
left: 50%;
|
31 |
transform: translate(-50%, -50%);
|
32 |
-
|
33 |
-
|
34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
z-index: 10;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
}
|
37 |
|
38 |
-
|
39 |
-
|
40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
52 |
<canvas id="renderCanvas"></canvas>
|
53 |
|
54 |
-
<!--
|
55 |
-
<div id="
|
56 |
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
const loadingIndicator = document.getElementById('loadingIndicator');
|
62 |
|
63 |
-
|
64 |
-
|
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 |
-
|
71 |
-
|
72 |
-
|
73 |
-
//
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
camera.
|
83 |
-
camera.
|
84 |
-
|
85 |
-
//
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
//
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
//
|
101 |
-
|
102 |
-
const
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
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; //
|
141 |
-
//
|
142 |
-
|
143 |
-
skyboxMaterial.reflectionTexture =
|
144 |
-
//
|
145 |
-
// skyboxMaterial.
|
146 |
-
//
|
147 |
-
|
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; //
|
159 |
-
|
160 |
-
|
161 |
-
// ---
|
162 |
-
//
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
box.material =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
|
|
|
|
|
|
|
|
212 |
|
213 |
-
|
214 |
-
|
215 |
-
|
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 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
|
|
|
|
|
|
|
|
|
|
321 |
|
322 |
|
323 |
-
// ---
|
324 |
-
|
|
|
325 |
|
|
|
326 |
return scene;
|
327 |
-
}; //
|
328 |
|
329 |
-
// --- Create and Render ---
|
330 |
-
try {
|
331 |
-
const scene = createScene();
|
332 |
|
333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
334 |
engine.runRenderLoop(() => {
|
335 |
-
|
336 |
-
scene.render();
|
337 |
-
}
|
338 |
});
|
339 |
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
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>
|