Aleksmorshen commited on
Commit
798a123
·
verified ·
1 Parent(s): b925469

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +256 -185
index.html CHANGED
@@ -2,247 +2,318 @@
2
  <html lang="ru">
3
  <head>
4
  <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Простой Minecraft-клон на Babylon.js</title>
7
  <style>
 
8
  html, body {
9
- overflow: hidden;
10
- width: 100%;
11
- height: 100%;
12
  margin: 0;
13
  padding: 0;
14
- font-family: sans-serif; /* Добавим шрифт для UI */
 
 
 
15
  }
16
 
17
  #renderCanvas {
18
  width: 100%;
19
  height: 100%;
20
- touch-action: none; /* Для мобильных устройств */
21
- }
22
-
23
- /* Простой интерфейс для прицела */
24
- #crosshair {
25
- position: absolute;
26
- top: 50%;
27
- left: 50%;
28
- width: 10px;
29
- height: 10px;
30
- border: 1px solid white;
31
- background-color: rgba(0, 0, 0, 0.5);
32
- transform: translate(-50%, -50%);
33
- pointer-events: none; /* Чтобы не мешал кликам */
34
- }
35
- /* Инструкция */
36
- #instructions {
37
- position: absolute;
38
- bottom: 10px;
39
- left: 10px;
40
- color: white;
41
- background-color: rgba(0,0,0,0.5);
42
- padding: 5px;
43
- border-radius: 3px;
44
- font-size: 12px;
45
  }
46
  </style>
47
- <!-- Подключение Babylon.js -->
48
  <script src="https://cdn.babylonjs.com/babylon.js"></script>
49
- <!-- Опционально: загрузчики моделей/текстур, если понадобятся -->
 
50
  <!-- <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script> -->
51
  </head>
52
  <body>
53
  <canvas id="renderCanvas"></canvas>
54
- <div id="crosshair"></div>
55
- <div id="instructions">
56
- WASD/Стрелки: Движение | Пробел: Вверх | Shift: Вниз <br>
57
- Левый клик: Удалить блок | Правый клик: Поставить блок
58
- </div>
59
 
60
  <script>
 
61
  const canvas = document.getElementById('renderCanvas');
62
- const engine = new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true });
63
 
64
- const BLOCK_SIZE = 1; // Размер одного блока
 
 
 
 
 
 
 
65
  const WORLD_WIDTH = 16; // Ширина мира в блоках
66
  const WORLD_DEPTH = 16; // Глубина мира в блоках
67
- const WORLD_HEIGHT = 8; // Максимальная высота мира (для генерации)
 
 
 
 
68
 
69
- // --- Создание сцены ---
70
  const createScene = () => {
71
  const scene = new BABYLON.Scene(engine);
72
- scene.clearColor = new BABYLON.Color3(0.5, 0.8, 1.0); // Цвет неба
73
- scene.gravity = new BABYLON.Vector3(0, -0.9, 0); // Гравитация
74
- scene.collisionsEnabled = true; // Включаем обработку столкновений
75
-
76
- // --- Камера ---
77
- const camera = new BABYLON.UniversalCamera("playerCamera", new BABYLON.Vector3(WORLD_WIDTH / 2, WORLD_HEIGHT + 2, WORLD_DEPTH / 2), scene);
78
- camera.setTarget(BABYLON.Vector3.Zero()); // Смотрим в центр
79
- camera.attachControl(canvas, true); // Подключаем управление
80
- camera.speed = 0.2; // Скорость движения
81
- camera.angularSensibility = 4000; // Чувствительность мыши
82
-
83
- // Включаем столкновения для камеры
84
- camera.checkCollisions = true;
 
 
 
85
  camera.applyGravity = true;
86
- // Задаем "эллипсоид" - физическую модель игрока для столкновений
87
- camera.ellipsoid = new BABYLON.Vector3(0.5, 0.9, 0.5); // Ширина, Высота, Глубина
 
 
88
 
89
- // Управление с клавиатуры (WASD + пробел/shift)
90
  camera.keysUp.push(87); // W
91
  camera.keysDown.push(83); // S
92
  camera.keysLeft.push(65); // A
93
  camera.keysRight.push(68); // D
94
- // Добавляем управление вверх/вниз (не стандартное для UniversalCamera)
95
- scene.onBeforeRenderObservable.add(() => {
96
- if (camera.inputs.attached.keyboard) {
97
- const keyboard = camera.inputs.attached.keyboard;
98
- if (keyboard.directInput[' ']) { // Пробел
99
- camera.cameraDirection.y += camera.speed / 10; // Двигаемся вверх
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  }
101
- if (keyboard.directInput[16]) { // Shift
102
- camera.cameraDirection.y -= camera.speed / 10; // Двигаемся вниз
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
 
 
 
 
 
 
 
 
 
 
 
104
  }
105
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
 
108
- // --- Освещение ---
109
- const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
110
- light.intensity = 0.8;
111
 
112
- // --- Материалы блоков ---
113
- const grassMaterial = new BABYLON.StandardMaterial("grassMat", scene);
114
- grassMaterial.diffuseColor = new BABYLON.Color3(0.4, 0.8, 0.4); // Зеленый
115
- grassMaterial.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1); // Меньше бликов
116
 
117
- const dirtMaterial = new BABYLON.StandardMaterial("dirtMat", scene);
118
- dirtMaterial.diffuseColor = new BABYLON.Color3(0.6, 0.4, 0.2); // Коричневый
119
- dirtMaterial.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1);
120
 
121
- const stoneMaterial = new BABYLON.StandardMaterial("stoneMat", scene);
122
- stoneMaterial.diffuseColor = new BABYLON.Color3(0.5, 0.5, 0.5); // Серый
123
- stoneMaterial.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1);
 
 
124
 
125
- // --- Мир блоков ---
126
- // Используем Map для хранения блоков { "x,y,z": mesh }
127
- const blocks = new Map();
 
 
 
 
 
 
 
 
 
128
 
129
- // Прототип блока (для клонирования)
130
- const blockPrototype = BABYLON.MeshBuilder.CreateBox("blockProto", { size: BLOCK_SIZE }, scene);
131
- blockPrototype.isVisible = false; // Сам прототип не видим
132
- blockPrototype.checkCollisions = true; // Блоки должны иметь коллизию
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
- // Генерация простого ландшафта
135
  for (let x = 0; x < WORLD_WIDTH; x++) {
136
  for (let z = 0; z < WORLD_DEPTH; z++) {
137
- // Простая высота на основе шума (очень примитивно)
138
- const height = Math.floor(WORLD_HEIGHT / 2 + Math.sin(x * 0.3) * 2 + Math.cos(z * 0.2) * 2);
139
- for (let y = 0; y < height; y++) {
140
- const block = blockPrototype.clone(`block_${x}_${y}_${z}`);
141
- block.position = new BABYLON.Vector3(
142
- x * BLOCK_SIZE + BLOCK_SIZE / 2,
143
- y * BLOCK_SIZE + BLOCK_SIZE / 2,
144
- z * BLOCK_SIZE + BLOCK_SIZE / 2
145
- );
146
- block.isVisible = true;
147
-
148
- // Назначаем материал в зависимости от высоты
149
- if (y === height - 1) {
150
- block.material = grassMaterial; // Верхний слой - трава
151
- } else if (y > height - 4) {
152
- block.material = dirtMaterial; // Под травой - земля
153
- } else {
154
- block.material = stoneMaterial; // Глубже - камень
155
- }
156
-
157
- const blockKey = `${block.position.x - BLOCK_SIZE/2},${block.position.y - BLOCK_SIZE/2},${block.position.z - BLOCK_SIZE/2}`; // Ключ по нижнему углу
158
- blocks.set(blockKey, block);
159
- block.isBlock = true; // Флаг, что это наш блок
160
  }
161
  }
162
  }
 
 
163
 
164
- // --- Взаимодействие с блоками ---
165
- scene.onPointerDown = (evt, pickResult) => {
166
- // evt.button: 0 = левый клик, 1 = средний, 2 = правый клик
167
-
168
- // --- Удаление блока (левый клик) ---
169
- if (evt.button === 0) {
170
- if (pickResult.hit && pickResult.pickedMesh && pickResult.pickedMesh.isBlock) {
171
- const blockToRemove = pickResult.pickedMesh;
172
- const blockKey = `${blockToRemove.position.x - BLOCK_SIZE/2},${blockToRemove.position.y - BLOCK_SIZE/2},${blockToRemove.position.z - BLOCK_SIZE/2}`;
173
- blocks.delete(blockKey);
174
- blockToRemove.dispose();
175
- }
176
- }
177
-
178
- // --- Размещение блока (правый клик) ---
179
- if (evt.button === 2) {
180
- if (pickResult.hit && pickResult.pickedMesh) { // Проверяем, что попали во что-то
181
- // Определяем позицию для нового блока
182
- // Она должна быть смещена от точки попадания по нормали к поверхности
183
- const normal = pickResult.getNormal(true); // Получаем нормаль к грани
184
- const hitPoint = pickResult.pickedPoint;
185
-
186
- // Вычисляем центр потенциального нового блока
187
- const potentialPos = hitPoint.add(normal.scale(BLOCK_SIZE / 2));
188
-
189
- // Округляем (снапим) к сетке блоков
190
- // Важно: Вычитаем/добавляем половину размера блока для центрирования
191
- const newBlockX = Math.floor(potentialPos.x / BLOCK_SIZE) * BLOCK_SIZE + BLOCK_SIZE / 2;
192
- const newBlockY = Math.floor(potentialPos.y / BLOCK_SIZE) * BLOCK_SIZE + BLOCK_SIZE / 2;
193
- const newBlockZ = Math.floor(potentialPos.z / BLOCK_SIZE) * BLOCK_SIZE + BLOCK_SIZE / 2;
194
-
195
- const newBlockKey = `${newBlockX - BLOCK_SIZE/2},${newBlockY - BLOCK_SIZE/2},${newBlockZ - BLOCK_SIZE/2}`;
196
-
197
- // Проверяем, не занято ли это место
198
- if (!blocks.has(newBlockKey)) {
199
- // Проверяем, не ставим ли блок "внутрь" игрока
200
- const playerBox = new BABYLON.BoundingBox(
201
- camera.position.subtract(camera.ellipsoid),
202
- camera.position.add(camera.ellipsoid)
203
- );
204
- const newBlockBox = new BABYLON.BoundingBox(
205
- new BABYLON.Vector3(newBlockX - BLOCK_SIZE/2, newBlockY - BLOCK_SIZE/2, newBlockZ - BLOCK_SIZE/2),
206
- new BABYLON.Vector3(newBlockX + BLOCK_SIZE/2, newBlockY + BLOCK_SIZE/2, newBlockZ + BLOCK_SIZE/2)
207
- );
208
-
209
- if (!playerBox.intersects(newBlockBox)) {
210
- const newBlock = blockPrototype.clone(`block_${newBlockKey.replace(/,/g, '_')}`);
211
- newBlock.position = new BABYLON.Vector3(newBlockX, newBlockY, newBlockZ);
212
- newBlock.material = dirtMaterial; // По умолчанию ставим землю
213
- newBlock.isVisible = true;
214
- newBlock.isBlock = true;
215
- blocks.set(newBlockKey, newBlock);
216
- } else {
217
- console.log("Нельзя ставить блок внутри себя!");
218
- }
219
- } else {
220
- console.log("Место занято!");
221
- }
222
- }
223
- }
224
- };
225
-
226
- // Отключаем контекстное меню по правому клику на канвасе
227
- canvas.addEventListener("contextmenu", (evt) => {
228
- evt.preventDefault();
229
- });
230
-
231
-
232
- // --- Оптимизация (очень базовая) ---
233
- // Можно добавить Octree для ускорения рендеринга и коллизий
234
- // scene.createOrUpdateSelectionOctree();
235
 
236
- return scene;
237
- };
238
 
239
  // --- Запуск ---
240
- const scene = createScene();
241
 
 
242
  engine.runRenderLoop(() => {
243
  scene.render();
244
  });
245
 
 
246
  window.addEventListener('resize', () => {
247
  engine.resize();
248
  });
 
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>BabylonCraft Demo</title>
7
  <style>
8
+ /* Базовый сброс стилей и стили для canvas */
9
  html, body {
 
 
 
10
  margin: 0;
11
  padding: 0;
12
+ overflow: hidden; /* Убираем прокрутку */
13
+ width: 100%;
14
+ height: 100%;
15
+ background-color: #000; /* Черный фон на случай проблем */
16
  }
17
 
18
  #renderCanvas {
19
  width: 100%;
20
  height: 100%;
21
+ display: block; /* Убираем лишнее пространство под canvas */
22
+ touch-action: none; /* Отключаем стандартные действия браузера при касании (важно для управления камерой) */
23
+ outline: none; /* Убираем обводку при фокусе */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  }
25
  </style>
26
+ <!-- Подключаем Babylon.js через CDN -->
27
  <script src="https://cdn.babylonjs.com/babylon.js"></script>
28
+ <!-- Можно добавить библиотеки материалов, загрузчиков и т.д. при необходимости -->
29
+ <!-- <script src="https://cdn.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script> -->
30
  <!-- <script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script> -->
31
  </head>
32
  <body>
33
  <canvas id="renderCanvas"></canvas>
 
 
 
 
 
34
 
35
  <script>
36
+ // Получаем элемент canvas
37
  const canvas = document.getElementById('renderCanvas');
 
38
 
39
+ // Создаем движок Babylon.js
40
+ const engine = new BABYLON.Engine(canvas, true, {
41
+ preserveDrawingBuffer: true, // Необходимо для некоторых эффектов, может влиять на производительность
42
+ stencil: true // Необходимо для некоторых эффектов
43
+ });
44
+
45
+ // --- Глобальные переменные и константы ---
46
+ const BLOCK_SIZE = 1; // Размер блока
47
  const WORLD_WIDTH = 16; // Ширина мира в блоках
48
  const WORLD_DEPTH = 16; // Глубина мира в блоках
49
+ const PLAYER_HEIGHT = 1.8; // Рост игрока
50
+ const blockStorage = {}; // Объект для хранения созданных блоков { "x_y_z": blockMesh }
51
+
52
+ // --- Материалы для блоков ---
53
+ let grassMat, dirtMat, stoneMat;
54
 
55
+ // Функция создания основной сцены
56
  const createScene = () => {
57
  const scene = new BABYLON.Scene(engine);
58
+ scene.clearColor = new BABYLON.Color3(0.5, 0.8, 1.0); // Цвет неба (голубой)
59
+ scene.collisionsEnabled = true; // Включаем обработку столкновений на сцене
60
+ scene.gravity = new BABYLON.Vector3(0, -0.9, 0); // Глобальная гравитация (примерно как в Minecraft)
61
+
62
+ // --- Камера от первого лица ---
63
+ // Используем UniversalCamera для поддержки и мыши/клавиатуры, и сенсорного ввода
64
+ const camera = new BABYLON.UniversalCamera("playerCamera", new BABYLON.Vector3(WORLD_WIDTH / 2, PLAYER_HEIGHT * 2, WORLD_DEPTH / 2), scene);
65
+ camera.setTarget(new BABYLON.Vector3(0, PLAYER_HEIGHT, 0)); // Куда смотрит камера при старте
66
+ camera.attachControl(canvas, true); // Привязываем управление к canvas
67
+
68
+ // Настройки камеры
69
+ camera.speed = 0.15; // Скорость перемещения
70
+ camera.angularSensibility = 3000; // Чувствительность мыши/сенсора
71
+ camera.inertia = 0.5; // Инерция при движении и вращении
72
+
73
+ // Включаем гравитацию и столкновения для камеры
74
  camera.applyGravity = true;
75
+ // Определяем размеры "тела" игрока для столкновений (эллипсоид)
76
+ camera.ellipsoid = new BABYLON.Vector3(BLOCK_SIZE * 0.4, PLAYER_HEIGHT * 0.45, BLOCK_SIZE * 0.4);
77
+ camera.ellipsoidOffset = new BABYLON.Vector3(0, PLAYER_HEIGHT * 0.45, 0); // Смещение эллипсоида, чтобы "ноги" были внизу
78
+ camera.checkCollisions = true; // Камера будет проверять столкновения
79
 
80
+ // Назначение клавиш управления (WASD)
81
  camera.keysUp.push(87); // W
82
  camera.keysDown.push(83); // S
83
  camera.keysLeft.push(65); // A
84
  camera.keysRight.push(68); // D
85
+ // camera.keysUpward.push(32); // Пробел для прыжка (требует доп. логики или физического движка)
86
+
87
+ // --- Освещение ---
88
+ // Простое полусферическое освещение, имитирующее небо и землю
89
+ const light = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(0, 1, 0), scene);
90
+ light.intensity = 0.8;
91
+ light.diffuse = new BABYLON.Color3(1, 1, 1); // Цвет света сверху
92
+ light.specular = new BABYLON.Color3(0.5, 0.5, 0.5); // Блики
93
+ light.groundColor = new BABYLON.Color3(0.3, 0.3, 0.3); // Цвет света снизу
94
+
95
+ // --- Создание материалов ---
96
+ grassMat = new BABYLON.StandardMaterial("grassMat", scene);
97
+ grassMat.diffuseColor = new BABYLON.Color3(0.3, 0.6, 0.2); // Зеленый
98
+ grassMat.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1); // Меньше бликов
99
+
100
+ dirtMat = new BABYLON.StandardMaterial("dirtMat", scene);
101
+ dirtMat.diffuseColor = new BABYLON.Color3(0.6, 0.4, 0.2); // Коричневый
102
+ dirtMat.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1);
103
+
104
+ stoneMat = new BABYLON.StandardMaterial("stoneMat", scene);
105
+ stoneMat.diffuseColor = new BABYLON.Color3(0.5, 0.5, 0.5); // Серый
106
+ stoneMat.specularColor = new BABYLON.Color3(0.2, 0.2, 0.2);
107
+
108
+ // --- Генерация простого мира ---
109
+ generateFlatWorld(scene);
110
+
111
+ // --- Логика взаимодействия (размещение/удаление блоков) ---
112
+ scene.onPointerDown = (evt, pickInfo) => {
113
+ // evt.button: 0 = левая, 1 = средняя, 2 = правая
114
+ const isRightClick = evt.button === 2;
115
+ const isLeftClick = evt.button === 0;
116
+
117
+ if (pickInfo.hit) {
118
+ const pickedMesh = pickInfo.pickedMesh;
119
+
120
+ // --- Удаление блока ---
121
+ // Левая кнопка мыши ИЛИ простое касание на мобильном, если попали в блок
122
+ if (pickedMesh && pickedMesh.isBlock && isLeftClick) {
123
+ removeBlock(pickedMesh.position.x, pickedMesh.position.y, pickedMesh.position.z);
124
  }
125
+ // --- Размещение блока ---
126
+ // Правая кнопка мыши ИЛИ простое касание на мобильном, если попали НЕ в блок (или в грань блока)
127
+ else if (pickedMesh && (isRightClick || (!pickedMesh.isBlock && isLeftClick))) {
128
+ // Рассчитываем позицию для нового блока
129
+ // Берем точку попадания и сдвигаемся на половину блока по нормали к поверхности
130
+ const normal = pickInfo.getNormal(true); // Получаем нормаль к поверхности в мировой системе координат
131
+ const placePos = pickInfo.pickedPoint.add(normal.scale(BLOCK_SIZE / 2));
132
+
133
+ // Выравниваем по сетке и центрируем
134
+ const gridX = Math.floor(placePos.x / BLOCK_SIZE) * BLOCK_SIZE + BLOCK_SIZE / 2;
135
+ const gridY = Math.floor(placePos.y / BLOCK_SIZE) * BLOCK_SIZE + BLOCK_SIZE / 2;
136
+ const gridZ = Math.floor(placePos.z / BLOCK_SIZE) * BLOCK_SIZE + BLOCK_SIZE / 2;
137
+
138
+ // Проверка, не пытаемся ли поставить блок внутри игрока
139
+ if (!isPositionOccupiedByPlayer(gridX, gridY, gridZ, camera)) {
140
+ // Сейчас всегда ставим камень
141
+ createBlock(gridX, gridY, gridZ, stoneMat, scene);
142
+ } else {
143
+ console.log("Нельзя разместить блок внутри себя!");
144
+ }
145
  }
146
+ }
147
+ };
148
+
149
+ // Блокировка курсора при клике на canvas для удобного управления мышью
150
+ let isPointerLocked = false;
151
+ scene.onPointerDown = (evt) => {
152
+ if (!isPointerLocked && evt.button === 0) { // Блокируем только по левому клику
153
+ canvas.requestPointerLock = canvas.requestPointerLock || canvas.msRequestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock;
154
+ if (canvas.requestPointerLock) {
155
+ canvas.requestPointerLock();
156
+ }
157
  }
158
+ // Вызываем предыдущую логику клика (размещение/удаление)
159
+ handlePointerDown(evt, scene.pick(scene.pointerX, scene.pointerY));
160
+ };
161
+
162
+ // Освобождение курсора при нажатии Esc
163
+ const pointerLockChange = () => {
164
+ let controlEnabled = document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement || document.msPointerLockElement || null;
165
+ isPointerLocked = !!controlEnabled;
166
+ // Если курсор разблокирован, останавливаем камеру (опционально)
167
+ // if (!controlEnabled) {
168
+ // camera.detachControl(canvas);
169
+ // } else {
170
+ // camera.attachControl(canvas, true);
171
+ // }
172
+ };
173
+
174
+ document.addEventListener("pointerlockchange", pointerLockChange, false);
175
+ document.addEventListener("mspointerlockchange", pointerLockChange, false);
176
+ document.addEventListener("mozpointerlockchange", pointerLockChange, false);
177
+ document.addEventListener("webkitpointerlockchange", pointerLockChange, false);
178
 
179
 
180
+ return scene;
181
+ };
 
182
 
183
+ // --- Вспомогательные функции ---
 
 
 
184
 
185
+ // Функция создания блока
186
+ function createBlock(x, y, z, material, scene) {
187
+ const blockId = `${Math.floor(x)}_${Math.floor(y)}_${Math.floor(z)}`;
188
 
189
+ // Проверяем, нет ли уже блока в этой позиции
190
+ if (blockStorage[blockId]) {
191
+ // console.log(`Блок ${blockId} уже существует.`);
192
+ return null;
193
+ }
194
 
195
+ // Создаем куб (box)
196
+ const box = BABYLON.MeshBuilder.CreateBox("block_" + blockId, { size: BLOCK_SIZE }, scene);
197
+ box.position = new BABYLON.Vector3(x, y, z);
198
+ box.material = material;
199
+ box.checkCollisions = true; // Включаем столкновения для блока
200
+ box.isBlock = true; // Пользовательское свойство для идентификации
201
+
202
+ // Добавляем блок в хранилище
203
+ blockStorage[blockId] = box;
204
+ // console.log(`Создан блок ${blockId} в ${x}, ${y}, ${z}`);
205
+ return box;
206
+ }
207
 
208
+ // Функция удаления блока
209
+ function removeBlock(x, y, z) {
210
+ const blockId = `${Math.floor(x)}_${Math.floor(y)}_${Math.floor(z)}`;
211
+ const blockToRemove = blockStorage[blockId];
212
+
213
+ if (blockToRemove) {
214
+ blockToRemove.dispose(); // Удаляем меш из сцены
215
+ delete blockStorage[blockId]; // Удаляем из хранилища
216
+ // console.log(`Удален блок ${blockId}`);
217
+ } else {
218
+ // console.log(`Блок ${blockId} не найден для удаления.`);
219
+ }
220
+ }
221
+
222
+ // Функция генерации плоского мира
223
+ function generateFlatWorld(scene) {
224
+ const groundLevel = 0; // Уровень земли
225
+ const dirtDepth = 3; // Глубина слоя грязи под травой
226
 
 
227
  for (let x = 0; x < WORLD_WIDTH; x++) {
228
  for (let z = 0; z < WORLD_DEPTH; z++) {
229
+ // Верхний слой - трава
230
+ createBlock(x + BLOCK_SIZE / 2, groundLevel + BLOCK_SIZE / 2, z + BLOCK_SIZE / 2, grassMat, scene);
231
+ // Слои грязи под травой
232
+ for (let y = 1; y <= dirtDepth; y++) {
233
+ createBlock(x + BLOCK_SIZE / 2, groundLevel + BLOCK_SIZE / 2 - y * BLOCK_SIZE, z + BLOCK_SIZE / 2, dirtMat, scene);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  }
235
  }
236
  }
237
+ console.log(`Сгенерирован плоский мир ${WORLD_WIDTH}x${WORLD_DEPTH}`);
238
+ }
239
 
240
+ // Проверка, попадает ли позиция в эллипсоид игрока
241
+ function isPositionOccupiedByPlayer(gridX, gridY, gridZ, camera) {
242
+ const playerPos = camera.position;
243
+ const playerEllipsoid = camera.ellipsoid;
244
+ const playerOffset = camera.ellipsoidOffset;
245
+
246
+ // Центр эллипсоида игрока
247
+ const playerCenterY = playerPos.y + playerOffset.y;
248
+
249
+ // Границы блока
250
+ const blockMinX = gridX - BLOCK_SIZE / 2;
251
+ const blockMaxX = gridX + BLOCK_SIZE / 2;
252
+ const blockMinY = gridY - BLOCK_SIZE / 2;
253
+ const blockMaxY = gridY + BLOCK_SIZE / 2;
254
+ const blockMinZ = gridZ - BLOCK_SIZE / 2;
255
+ const blockMaxZ = gridZ + BLOCK_SIZE / 2;
256
+
257
+ // Границы эллипсоида игрока (приблизительно)
258
+ const playerMinX = playerPos.x - playerEllipsoid.x;
259
+ const playerMaxX = playerPos.x + playerEllipsoid.x;
260
+ const playerMinY = playerCenterY - playerEllipsoid.y; // Используем центр эллипсоида
261
+ const playerMaxY = playerCenterY + playerEllipsoid.y; // Используем центр эллипсоида
262
+ const playerMinZ = playerPos.z - playerEllipsoid.z;
263
+ const playerMaxZ = playerPos.z + playerEllipsoid.z;
264
+
265
+ // Проверка на пересечение AABB (Axis-Aligned Bounding Box)
266
+ const overlapX = playerMinX < blockMaxX && playerMaxX > blockMinX;
267
+ const overlapY = playerMinY < blockMaxY && playerMaxY > blockMinY;
268
+ const overlapZ = playerMinZ < blockMaxZ && playerMaxZ > blockMinZ;
269
+
270
+ return overlapX && overlapY && overlapZ;
271
+ }
272
+
273
+ // Обработчик клика/касания (вынесен для работы с блокировкой курсора)
274
+ function handlePointerDown(evt, pickInfo) {
275
+ // evt.button: 0 = левая, 1 = средняя, 2 = правая
276
+ const isRightClick = evt.button === 2;
277
+ const isLeftClick = evt.button === 0;
278
+
279
+ if (pickInfo && pickInfo.hit) {
280
+ const pickedMesh = pickInfo.pickedMesh;
281
+ const scene = pickedMesh.getScene(); // Получаем сцену из объекта
282
+
283
+ // --- Удаление блока ---
284
+ // Левая кнопка мыши ИЛИ простое касание на мобильном, если попали в блок
285
+ if (pickedMesh && pickedMesh.isBlock && isLeftClick) {
286
+ removeBlock(pickedMesh.position.x, pickedMesh.position.y, pickedMesh.position.z);
287
+ }
288
+ // --- Размещение блока ---
289
+ // Правая кнопка мыши ИЛИ простое касание на мобильном, если попали НЕ в блок (или в грань блока)
290
+ else if (pickedMesh && (isRightClick || (!pickedMesh.isBlock && isLeftClick))) {
291
+ const normal = pickInfo.getNormal(true);
292
+ const placePos = pickInfo.pickedPoint.add(normal.scale(BLOCK_SIZE / 2));
293
+
294
+ const gridX = Math.floor(placePos.x / BLOCK_SIZE) * BLOCK_SIZE + BLOCK_SIZE / 2;
295
+ const gridY = Math.floor(placePos.y / BLOCK_SIZE) * BLOCK_SIZE + BLOCK_SIZE / 2;
296
+ const gridZ = Math.floor(placePos.z / BLOCK_SIZE) * BLOCK_SIZE + BLOCK_SIZE / 2;
297
+
298
+ if (!isPositionOccupiedByPlayer(gridX, gridY, gridZ, scene.activeCamera)) {
299
+ createBlock(gridX, gridY, gridZ, stoneMat, scene);
300
+ } else {
301
+ console.log("Нельзя разместить блок внутри себя!");
302
+ }
303
+ }
304
+ }
305
+ }
 
 
 
 
 
306
 
 
 
307
 
308
  // --- Запуск ---
309
+ const scene = createScene(); // Создаем сцену
310
 
311
+ // Главный цикл рендеринга
312
  engine.runRenderLoop(() => {
313
  scene.render();
314
  });
315
 
316
+ // Обработчик изменения размера окна/экрана
317
  window.addEventListener('resize', () => {
318
  engine.resize();
319
  });