Update app.py
Browse files
app.py
CHANGED
|
@@ -173,59 +173,50 @@ EDITOR_TEMPLATE = '''
|
|
| 173 |
<div id="ui-panel">
|
| 174 |
<div class="ui-group">
|
| 175 |
<h3>Режим</h3>
|
| 176 |
-
<button id="play-mode-toggle" class="play-button">Играть</button>
|
| 177 |
</div>
|
| 178 |
<div class="ui-group">
|
| 179 |
-
<h3
|
| 180 |
-
<label for="project-list">Загрузить проект:</label>
|
| 181 |
<select id="project-list">
|
| 182 |
<option value="">Выберите проект...</option>
|
| 183 |
{% for project in projects %}
|
| 184 |
<option value="{{ project }}">{{ project }}</option>
|
| 185 |
{% endfor %}
|
| 186 |
</select>
|
| 187 |
-
<button id="load-project">Загрузить</button>
|
| 188 |
-
<
|
|
|
|
| 189 |
<input type="text" id="project-name" placeholder="new-level-01">
|
| 190 |
-
<button id="save-project">Сохранить</button>
|
| 191 |
</div>
|
| 192 |
<div class="ui-group">
|
| 193 |
<h3>Ландшафт</h3>
|
| 194 |
-
<label for="terrain-width">Ширина:</label>
|
| 195 |
<input type="number" id="terrain-width" value="100">
|
| 196 |
-
<label for="terrain-height"
|
| 197 |
<input type="number" id="terrain-height" value="100">
|
| 198 |
-
<button id="create-terrain">Создать новый ландшафт</button>
|
| 199 |
</div>
|
| 200 |
<div class="ui-group">
|
| 201 |
<h3>Кисть</h3>
|
| 202 |
<div class="radio-group">
|
| 203 |
-
<label><input type="radio" name="brush-mode" value="raise" checked> Поднять</label>
|
| 204 |
-
<label><input type="radio" name="brush-mode" value="lower"> Опустить</label>
|
| 205 |
-
<label><input type="radio" name="brush-mode" value="roughen"> Шум</label>
|
| 206 |
-
<label><input type="radio" name="brush-mode" value="smooth"> Сгладить</label>
|
| 207 |
-
<label><input type="radio" name="brush-mode" value="flatten"> Выровнять</label>
|
| 208 |
-
<label><input type="radio" name="brush-mode" value="paint"> Текстура</label>
|
| 209 |
-
<label><input type="radio" name="brush-mode" value="place"> Объект</label>
|
| 210 |
</div>
|
| 211 |
<div class="slider-container">
|
| 212 |
-
<label for="brush-size">Размер: <span id="brush-size-value">10</span></label>
|
| 213 |
<input type="range" id="brush-size" min="1" max="50" value="10">
|
| 214 |
</div>
|
| 215 |
<div class="slider-container">
|
| 216 |
-
<label for="brush-strength">Сила: <span id="brush-strength-value">0.5</span></label>
|
| 217 |
<input type="range" id="brush-strength" min="0.1" max="2" step="0.1" value="0.5">
|
| 218 |
</div>
|
| 219 |
-
</div>
|
| 220 |
-
<div class="ui-group">
|
| 221 |
-
<h3>Текстуры ландшафта</h3>
|
| 222 |
-
<div class="radio-group" id="texture-selector">
|
| 223 |
-
<label><input type="radio" name="texture-type" value="grass" checked> Трава</label>
|
| 224 |
-
<label><input type="radio" name="texture-type" value="rock"> Скалы</label>
|
| 225 |
-
<label><input type="radio" name="texture-type" value="dirt"> Грунт</label>
|
| 226 |
-
<label><input type="radio" name="texture-type" value="snow"> Снег</label>
|
| 227 |
-
<label><input type="radio" name="texture-type" value="sand"> Песок</label>
|
| 228 |
-
</div>
|
| 229 |
</div>
|
| 230 |
<div class="ui-group" id="texture-management-group">
|
| 231 |
<h3>Управление текстурами</h3>
|
|
@@ -237,19 +228,32 @@ EDITOR_TEMPLATE = '''
|
|
| 237 |
<option value="snow">Снег</option>
|
| 238 |
<option value="sand">Песок</option>
|
| 239 |
</select>
|
| 240 |
-
|
| 241 |
-
<input type="file" id="custom-texture-file" accept="image/*">
|
| 242 |
-
<button id="update-texture-btn">Обновить текстуру</button>
|
| 243 |
-
<label for="texture-slot-name">Новое имя для слота:</label>
|
| 244 |
<input type="text" id="texture-slot-name" placeholder="например, Лава">
|
| 245 |
<button id="rename-texture-slot-btn">Переименовать</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
</div>
|
| 247 |
<div class="ui-group">
|
| 248 |
<h3>Объекты</h3>
|
| 249 |
<div class="radio-group" id="object-selector">
|
| 250 |
<label><input type="radio" name="object-type" value="grass" checked> Трава</label>
|
| 251 |
</div>
|
| 252 |
-
<button id="clear-objects">Очистить объекты</button>
|
| 253 |
</div>
|
| 254 |
</div>
|
| 255 |
</div>
|
|
@@ -311,6 +315,7 @@ EDITOR_TEMPLATE = '''
|
|
| 311 |
|
| 312 |
const textureLoader = new THREE.TextureLoader();
|
| 313 |
let customTextures = {};
|
|
|
|
| 314 |
let flattenHeight = null;
|
| 315 |
|
| 316 |
const loadTexture = (url) => {
|
|
@@ -328,9 +333,20 @@ EDITOR_TEMPLATE = '''
|
|
| 328 |
snow: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/snow.jpg'),
|
| 329 |
sand: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/sand-512.jpg'),
|
| 330 |
grassNormal: loadTexture('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/terrain/grasslight-big-nm.jpg'),
|
| 331 |
-
rockNormal: loadTexture('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/terrain/rock-nm.jpg')
|
|
|
|
|
|
|
|
|
|
| 332 |
};
|
| 333 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
const terrainMaterial = new THREE.ShaderMaterial({
|
| 335 |
uniforms: {
|
| 336 |
grassTexture: { value: textures.grass },
|
|
@@ -340,6 +356,9 @@ EDITOR_TEMPLATE = '''
|
|
| 340 |
sandTexture: { value: textures.sand },
|
| 341 |
grassNormalMap: { value: textures.grassNormal },
|
| 342 |
rockNormalMap: { value: textures.rockNormal },
|
|
|
|
|
|
|
|
|
|
| 343 |
lightDirection: { value: new THREE.Vector3(0.5, 0.5, 0.5).normalize() }
|
| 344 |
},
|
| 345 |
vertexShader: `
|
|
@@ -371,6 +390,10 @@ EDITOR_TEMPLATE = '''
|
|
| 371 |
uniform sampler2D sandTexture;
|
| 372 |
uniform sampler2D grassNormalMap;
|
| 373 |
uniform sampler2D rockNormalMap;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
uniform vec3 lightDirection;
|
| 375 |
|
| 376 |
varying vec2 vUv;
|
|
@@ -399,9 +422,17 @@ EDITOR_TEMPLATE = '''
|
|
| 399 |
|
| 400 |
vec3 grassNormal = texture2D(grassNormalMap, uv_scaled).xyz * 2.0 - 1.0;
|
| 401 |
vec3 rockNormal = texture2D(rockNormalMap, uv_scaled).xyz * 2.0 - 1.0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 402 |
|
| 403 |
-
vec3
|
| 404 |
-
vec3 normal = normalize(tbn * blendedNormal);
|
| 405 |
|
| 406 |
float lighting = dot(normal, lightDirection) * 0.5 + 0.5;
|
| 407 |
gl_FragColor = vec4(finalColor * lighting, 1.0);
|
|
@@ -419,7 +450,7 @@ EDITOR_TEMPLATE = '''
|
|
| 419 |
renderer.setSize(window.innerWidth, window.innerHeight);
|
| 420 |
renderer.setPixelRatio(window.devicePixelRatio);
|
| 421 |
renderer.shadowMap.enabled = true;
|
| 422 |
-
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
| 423 |
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
| 424 |
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
| 425 |
document.body.appendChild(renderer.domElement);
|
|
@@ -434,13 +465,14 @@ EDITOR_TEMPLATE = '''
|
|
| 434 |
const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
| 435 |
dirLight.position.set(100, 100, 50);
|
| 436 |
dirLight.castShadow = true;
|
| 437 |
-
dirLight.shadow.mapSize.width =
|
| 438 |
-
dirLight.shadow.mapSize.height =
|
| 439 |
-
dirLight.shadow.camera.top =
|
| 440 |
-
dirLight.shadow.camera.bottom = -
|
| 441 |
-
dirLight.shadow.camera.left = -
|
| 442 |
-
dirLight.shadow.camera.right =
|
| 443 |
dirLight.shadow.bias = -0.001;
|
|
|
|
| 444 |
scene.add(dirLight);
|
| 445 |
terrainMaterial.uniforms.lightDirection.value = dirLight.position.clone().normalize();
|
| 446 |
|
|
@@ -617,37 +649,50 @@ EDITOR_TEMPLATE = '''
|
|
| 617 |
}
|
| 618 |
|
| 619 |
function updateCustomTexture() {
|
| 620 |
-
const
|
|
|
|
| 621 |
const textureSlot = document.getElementById('texture-slot-select').value;
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
|
|
|
|
|
|
|
|
|
| 625 |
return;
|
| 626 |
}
|
| 627 |
-
|
| 628 |
-
const
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
if (terrainMaterial.uniforms[uniformName]) {
|
| 639 |
-
if (terrainMaterial.uniforms[uniformName].value) {
|
| 640 |
-
terrainMaterial.uniforms[uniformName].value.dispose();
|
| 641 |
}
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 646 |
};
|
| 647 |
-
|
| 648 |
-
|
|
|
|
| 649 |
}
|
| 650 |
|
|
|
|
| 651 |
function renameTextureSlot() {
|
| 652 |
const slotValue = document.getElementById('texture-slot-select').value;
|
| 653 |
const newName = document.getElementById('texture-slot-name').value.trim();
|
|
@@ -763,20 +808,33 @@ EDITOR_TEMPLATE = '''
|
|
| 763 |
}
|
| 764 |
grassInstances.instanceMatrix.needsUpdate = true;
|
| 765 |
}
|
| 766 |
-
|
| 767 |
customTextures = terrainData.customTextures;
|
| 768 |
Object.entries(customTextures).forEach(([slot, dataUrl]) => {
|
| 769 |
const newTexture = loadTexture(dataUrl);
|
| 770 |
const uniformName = slot + 'Texture';
|
| 771 |
if (terrainMaterial.uniforms[uniformName]) {
|
| 772 |
-
|
| 773 |
terrainMaterial.uniforms[uniformName].value.dispose();
|
| 774 |
}
|
| 775 |
terrainMaterial.uniforms[uniformName].value = newTexture;
|
| 776 |
}
|
| 777 |
});
|
| 778 |
-
terrainMaterial.needsUpdate = true;
|
| 779 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
updateTextureUIAfterLoad(terrainData);
|
| 781 |
}
|
| 782 |
|
|
@@ -1083,6 +1141,7 @@ EDITOR_TEMPLATE = '''
|
|
| 1083 |
grass: grassMatrices
|
| 1084 |
},
|
| 1085 |
customTextures: customTextures,
|
|
|
|
| 1086 |
textureNames: textureNames
|
| 1087 |
};
|
| 1088 |
|
|
|
|
| 173 |
<div id="ui-panel">
|
| 174 |
<div class="ui-group">
|
| 175 |
<h3>Режим</h3>
|
| 176 |
+
<button id="play-mode-toggle" class="play-button" title="Переключиться в режим игры">Играть</button>
|
| 177 |
</div>
|
| 178 |
<div class="ui-group">
|
| 179 |
+
<h3>Проект</h3>
|
| 180 |
+
<label for="project-list" title="Выбрать существующий проект для загрузки">Загрузить проект:</label>
|
| 181 |
<select id="project-list">
|
| 182 |
<option value="">Выберите проект...</option>
|
| 183 |
{% for project in projects %}
|
| 184 |
<option value="{{ project }}">{{ project }}</option>
|
| 185 |
{% endfor %}
|
| 186 |
</select>
|
| 187 |
+
<button id="load-project" title="Загрузить выбранный проект">Загрузить</button>
|
| 188 |
+
<hr style="border-color: #333; margin: 15px 0;">
|
| 189 |
+
<label for="project-name" title="Введите имя для нового или существующего проекта">Имя проекта:</label>
|
| 190 |
<input type="text" id="project-name" placeholder="new-level-01">
|
| 191 |
+
<button id="save-project" title="Сохранить текущий проект на сервере">Сохранить</button>
|
| 192 |
</div>
|
| 193 |
<div class="ui-group">
|
| 194 |
<h3>Ландшафт</h3>
|
| 195 |
+
<label for="terrain-width" title="Ширина ландшафта по оси X">Ширина:</label>
|
| 196 |
<input type="number" id="terrain-width" value="100">
|
| 197 |
+
<label for="terrain-height" title="Глубина ландшафта по оси Z">Глубина:</label>
|
| 198 |
<input type="number" id="terrain-height" value="100">
|
| 199 |
+
<button id="create-terrain" title="Создать новый ландшафт с указанными размерами">Создать новый ландшафт</button>
|
| 200 |
</div>
|
| 201 |
<div class="ui-group">
|
| 202 |
<h3>Кисть</h3>
|
| 203 |
<div class="radio-group">
|
| 204 |
+
<label title="Поднять ландшафт"><input type="radio" name="brush-mode" value="raise" checked> Поднять</label>
|
| 205 |
+
<label title="Опустить ландшафт"><input type="radio" name="brush-mode" value="lower"> Опустить</label>
|
| 206 |
+
<label title="Добавить шум"><input type="radio" name="brush-mode" value="roughen"> Шум</label>
|
| 207 |
+
<label title="Сгладить ландшафт"><input type="radio" name="brush-mode" value="smooth"> Сгладить</label>
|
| 208 |
+
<label title="Выровнять до определенной высоты"><input type="radio" name="brush-mode" value="flatten"> Выровнять</label>
|
| 209 |
+
<label title="Рисовать текстуру"><input type="radio" name="brush-mode" value="paint"> Текстура</label>
|
| 210 |
+
<label title="Разместить объект"><input type="radio" name="brush-mode" value="place"> Объект</label>
|
| 211 |
</div>
|
| 212 |
<div class="slider-container">
|
| 213 |
+
<label for="brush-size" title="Радиус кисти">Размер: <span id="brush-size-value">10</span></label>
|
| 214 |
<input type="range" id="brush-size" min="1" max="50" value="10">
|
| 215 |
</div>
|
| 216 |
<div class="slider-container">
|
| 217 |
+
<label for="brush-strength" title="Интенсивность кисти">Сила: <span id="brush-strength-value">0.5</span></label>
|
| 218 |
<input type="range" id="brush-strength" min="0.1" max="2" step="0.1" value="0.5">
|
| 219 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
</div>
|
| 221 |
<div class="ui-group" id="texture-management-group">
|
| 222 |
<h3>Управление текстурами</h3>
|
|
|
|
| 228 |
<option value="snow">Снег</option>
|
| 229 |
<option value="sand">Песок</option>
|
| 230 |
</select>
|
| 231 |
+
<label for="texture-slot-name">Новое имя для слота:</label>
|
|
|
|
|
|
|
|
|
|
| 232 |
<input type="text" id="texture-slot-name" placeholder="например, Лава">
|
| 233 |
<button id="rename-texture-slot-btn">Переименовать</button>
|
| 234 |
+
<hr style="border-color: #333; margin: 15px 0;">
|
| 235 |
+
<label for="custom-texture-file">Текстура (Diffuse):</label>
|
| 236 |
+
<input type="file" id="custom-texture-file" accept="image/*" title="Выберите изображение для основной текстуры">
|
| 237 |
+
<label for="custom-normal-file">Карта нормалей (Normal):</label>
|
| 238 |
+
<input type="file" id="custom-normal-file" accept="image/*" title="Выберите изображение для карты нормалей">
|
| 239 |
+
<button id="update-texture-btn">Обновить текстуру</button>
|
| 240 |
+
</div>
|
| 241 |
+
<div class="ui-group">
|
| 242 |
+
<h3>Текстуры ландшафта</h3>
|
| 243 |
+
<div class="radio-group" id="texture-selector">
|
| 244 |
+
<label><input type="radio" name="texture-type" value="grass" checked> Трава</label>
|
| 245 |
+
<label><input type="radio" name="texture-type" value="rock"> Скалы</label>
|
| 246 |
+
<label><input type="radio" name="texture-type" value="dirt"> Грунт</label>
|
| 247 |
+
<label><input type="radio" name="texture-type" value="snow"> Снег</label>
|
| 248 |
+
<label><input type="radio" name="texture-type" value="sand"> Песок</label>
|
| 249 |
+
</div>
|
| 250 |
</div>
|
| 251 |
<div class="ui-group">
|
| 252 |
<h3>Объекты</h3>
|
| 253 |
<div class="radio-group" id="object-selector">
|
| 254 |
<label><input type="radio" name="object-type" value="grass" checked> Трава</label>
|
| 255 |
</div>
|
| 256 |
+
<button id="clear-objects" title="Удалить всю растительность с ландшафта">Очистить объекты</button>
|
| 257 |
</div>
|
| 258 |
</div>
|
| 259 |
</div>
|
|
|
|
| 315 |
|
| 316 |
const textureLoader = new THREE.TextureLoader();
|
| 317 |
let customTextures = {};
|
| 318 |
+
let customNormalMaps = {};
|
| 319 |
let flattenHeight = null;
|
| 320 |
|
| 321 |
const loadTexture = (url) => {
|
|
|
|
| 333 |
snow: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/snow.jpg'),
|
| 334 |
sand: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/sand-512.jpg'),
|
| 335 |
grassNormal: loadTexture('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/terrain/grasslight-big-nm.jpg'),
|
| 336 |
+
rockNormal: loadTexture('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/terrain/rock-nm.jpg'),
|
| 337 |
+
dirtNormal: new THREE.DataTexture(new Uint8Array([128, 128, 255, 255]), 1, 1),
|
| 338 |
+
snowNormal: new THREE.DataTexture(new Uint8Array([128, 128, 255, 255]), 1, 1),
|
| 339 |
+
sandNormal: new THREE.DataTexture(new Uint8Array([128, 128, 255, 255]), 1, 1)
|
| 340 |
};
|
| 341 |
|
| 342 |
+
Object.values(textures).forEach(tex => {
|
| 343 |
+
if (tex.isDataTexture) {
|
| 344 |
+
tex.needsUpdate = true;
|
| 345 |
+
} else {
|
| 346 |
+
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
|
| 347 |
+
}
|
| 348 |
+
});
|
| 349 |
+
|
| 350 |
const terrainMaterial = new THREE.ShaderMaterial({
|
| 351 |
uniforms: {
|
| 352 |
grassTexture: { value: textures.grass },
|
|
|
|
| 356 |
sandTexture: { value: textures.sand },
|
| 357 |
grassNormalMap: { value: textures.grassNormal },
|
| 358 |
rockNormalMap: { value: textures.rockNormal },
|
| 359 |
+
dirtNormalMap: { value: textures.dirtNormal },
|
| 360 |
+
snowNormalMap: { value: textures.snowNormal },
|
| 361 |
+
sandNormalMap: { value: textures.sandNormal },
|
| 362 |
lightDirection: { value: new THREE.Vector3(0.5, 0.5, 0.5).normalize() }
|
| 363 |
},
|
| 364 |
vertexShader: `
|
|
|
|
| 390 |
uniform sampler2D sandTexture;
|
| 391 |
uniform sampler2D grassNormalMap;
|
| 392 |
uniform sampler2D rockNormalMap;
|
| 393 |
+
uniform sampler2D dirtNormalMap;
|
| 394 |
+
uniform sampler2D snowNormalMap;
|
| 395 |
+
uniform sampler2D sandNormalMap;
|
| 396 |
+
|
| 397 |
uniform vec3 lightDirection;
|
| 398 |
|
| 399 |
varying vec2 vUv;
|
|
|
|
| 422 |
|
| 423 |
vec3 grassNormal = texture2D(grassNormalMap, uv_scaled).xyz * 2.0 - 1.0;
|
| 424 |
vec3 rockNormal = texture2D(rockNormalMap, uv_scaled).xyz * 2.0 - 1.0;
|
| 425 |
+
vec3 dirtNormal = texture2D(dirtNormalMap, uv_scaled).xyz * 2.0 - 1.0;
|
| 426 |
+
vec3 snowNormal = texture2D(snowNormalMap, uv_scaled).xyz * 2.0 - 1.0;
|
| 427 |
+
vec3 sandNormal = texture2D(sandNormalMap, uv_scaled).xyz * 2.0 - 1.0;
|
| 428 |
+
|
| 429 |
+
vec3 finalNormal = grassNormal;
|
| 430 |
+
finalNormal = mix(finalNormal, rockNormal, vColor.r);
|
| 431 |
+
finalNormal = mix(finalNormal, dirtNormal, vColor.g);
|
| 432 |
+
finalNormal = mix(finalNormal, snowNormal, vColor.b);
|
| 433 |
+
finalNormal = mix(finalNormal, sandNormal, vColor.a);
|
| 434 |
|
| 435 |
+
vec3 normal = normalize(tbn * finalNormal);
|
|
|
|
| 436 |
|
| 437 |
float lighting = dot(normal, lightDirection) * 0.5 + 0.5;
|
| 438 |
gl_FragColor = vec4(finalColor * lighting, 1.0);
|
|
|
|
| 450 |
renderer.setSize(window.innerWidth, window.innerHeight);
|
| 451 |
renderer.setPixelRatio(window.devicePixelRatio);
|
| 452 |
renderer.shadowMap.enabled = true;
|
| 453 |
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
| 454 |
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
| 455 |
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
| 456 |
document.body.appendChild(renderer.domElement);
|
|
|
|
| 465 |
const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
|
| 466 |
dirLight.position.set(100, 100, 50);
|
| 467 |
dirLight.castShadow = true;
|
| 468 |
+
dirLight.shadow.mapSize.width = 4096;
|
| 469 |
+
dirLight.shadow.mapSize.height = 4096;
|
| 470 |
+
dirLight.shadow.camera.top = 150;
|
| 471 |
+
dirLight.shadow.camera.bottom = -150;
|
| 472 |
+
dirLight.shadow.camera.left = -150;
|
| 473 |
+
dirLight.shadow.camera.right = 150;
|
| 474 |
dirLight.shadow.bias = -0.001;
|
| 475 |
+
dirLight.shadow.normalBias = 0.05;
|
| 476 |
scene.add(dirLight);
|
| 477 |
terrainMaterial.uniforms.lightDirection.value = dirLight.position.clone().normalize();
|
| 478 |
|
|
|
|
| 649 |
}
|
| 650 |
|
| 651 |
function updateCustomTexture() {
|
| 652 |
+
const textureFileInput = document.getElementById('custom-texture-file');
|
| 653 |
+
const normalFileInput = document.getElementById('custom-normal-file');
|
| 654 |
const textureSlot = document.getElementById('texture-slot-select').value;
|
| 655 |
+
|
| 656 |
+
const textureFile = textureFileInput.files[0];
|
| 657 |
+
const normalFile = normalFileInput.files[0];
|
| 658 |
+
|
| 659 |
+
if (!textureFile && !normalFile) {
|
| 660 |
+
alert('Пожалуйста, выберите файл текстуры или карты нормалей.');
|
| 661 |
return;
|
| 662 |
}
|
| 663 |
+
|
| 664 |
+
const updateTexture = (file, isNormalMap) => {
|
| 665 |
+
if (!file) return;
|
| 666 |
+
const reader = new FileReader();
|
| 667 |
+
reader.onload = (event) => {
|
| 668 |
+
const dataUrl = event.target.result;
|
| 669 |
+
|
| 670 |
+
if (isNormalMap) {
|
| 671 |
+
customNormalMaps[textureSlot] = dataUrl;
|
| 672 |
+
} else {
|
| 673 |
+
customTextures[textureSlot] = dataUrl;
|
|
|
|
|
|
|
|
|
|
| 674 |
}
|
| 675 |
+
|
| 676 |
+
const newTexture = loadTexture(dataUrl);
|
| 677 |
+
const uniformName = textureSlot + (isNormalMap ? 'NormalMap' : 'Texture');
|
| 678 |
+
|
| 679 |
+
if (terrainMaterial.uniforms[uniformName]) {
|
| 680 |
+
if (terrainMaterial.uniforms[uniformName].value) {
|
| 681 |
+
terrainMaterial.uniforms[uniformName].value.dispose();
|
| 682 |
+
}
|
| 683 |
+
terrainMaterial.uniforms[uniformName].value = newTexture;
|
| 684 |
+
terrainMaterial.needsUpdate = true;
|
| 685 |
+
alert(`Карта ${isNormalMap ? 'нормалей' : 'текстуры'} для слота "${textureSlot}" успешно обновлена.`);
|
| 686 |
+
}
|
| 687 |
+
};
|
| 688 |
+
reader.readAsDataURL(file);
|
| 689 |
};
|
| 690 |
+
|
| 691 |
+
updateTexture(textureFile, false);
|
| 692 |
+
updateTexture(normalFile, true);
|
| 693 |
}
|
| 694 |
|
| 695 |
+
|
| 696 |
function renameTextureSlot() {
|
| 697 |
const slotValue = document.getElementById('texture-slot-select').value;
|
| 698 |
const newName = document.getElementById('texture-slot-name').value.trim();
|
|
|
|
| 808 |
}
|
| 809 |
grassInstances.instanceMatrix.needsUpdate = true;
|
| 810 |
}
|
| 811 |
+
if (terrainData.customTextures) {
|
| 812 |
customTextures = terrainData.customTextures;
|
| 813 |
Object.entries(customTextures).forEach(([slot, dataUrl]) => {
|
| 814 |
const newTexture = loadTexture(dataUrl);
|
| 815 |
const uniformName = slot + 'Texture';
|
| 816 |
if (terrainMaterial.uniforms[uniformName]) {
|
| 817 |
+
if (terrainMaterial.uniforms[uniformName].value) {
|
| 818 |
terrainMaterial.uniforms[uniformName].value.dispose();
|
| 819 |
}
|
| 820 |
terrainMaterial.uniforms[uniformName].value = newTexture;
|
| 821 |
}
|
| 822 |
});
|
|
|
|
| 823 |
}
|
| 824 |
+
if (terrainData.customNormalMaps) {
|
| 825 |
+
customNormalMaps = terrainData.customNormalMaps;
|
| 826 |
+
Object.entries(customNormalMaps).forEach(([slot, dataUrl]) => {
|
| 827 |
+
const newNormalMap = loadTexture(dataUrl);
|
| 828 |
+
const uniformName = slot + 'NormalMap';
|
| 829 |
+
if (terrainMaterial.uniforms[uniformName]) {
|
| 830 |
+
if (terrainMaterial.uniforms[uniformName].value) {
|
| 831 |
+
terrainMaterial.uniforms[uniformName].value.dispose();
|
| 832 |
+
}
|
| 833 |
+
terrainMaterial.uniforms[uniformName].value = newNormalMap;
|
| 834 |
+
}
|
| 835 |
+
});
|
| 836 |
+
}
|
| 837 |
+
terrainMaterial.needsUpdate = true;
|
| 838 |
updateTextureUIAfterLoad(terrainData);
|
| 839 |
}
|
| 840 |
|
|
|
|
| 1141 |
grass: grassMatrices
|
| 1142 |
},
|
| 1143 |
customTextures: customTextures,
|
| 1144 |
+
customNormalMaps: customNormalMaps,
|
| 1145 |
textureNames: textureNames
|
| 1146 |
};
|
| 1147 |
|