Kgshop commited on
Commit
5ae2454
·
verified ·
1 Parent(s): bcf83c2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +129 -70
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>Проекты</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
- <label for="project-name">Имя проекта:</label>
 
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">Длина:</label>
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
- <label for="custom-texture-file">Выберите файл текстуры:</label>
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 blendedNormal = normalize(mix(grassNormal, rockNormal, vColor.r));
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 = 2048;
438
- dirLight.shadow.mapSize.height = 2048;
439
- dirLight.shadow.camera.top = 100;
440
- dirLight.shadow.camera.bottom = -100;
441
- dirLight.shadow.camera.left = -100;
442
- dirLight.shadow.camera.right = 100;
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 fileInput = document.getElementById('custom-texture-file');
 
621
  const textureSlot = document.getElementById('texture-slot-select').value;
622
-
623
- if (fileInput.files.length === 0) {
624
- alert('Пожалуйста, выберите файл изображения.');
 
 
 
625
  return;
626
  }
627
-
628
- const file = fileInput.files[0];
629
- const reader = new FileReader();
630
-
631
- reader.onload = (event) => {
632
- const dataUrl = event.target.result;
633
- customTextures[textureSlot] = dataUrl;
634
-
635
- const newTexture = loadTexture(dataUrl);
636
- const uniformName = textureSlot + 'Texture';
637
-
638
- if (terrainMaterial.uniforms[uniformName]) {
639
- if (terrainMaterial.uniforms[uniformName].value) {
640
- terrainMaterial.uniforms[uniformName].value.dispose();
641
  }
642
- terrainMaterial.uniforms[uniformName].value = newTexture;
643
- terrainMaterial.needsUpdate = true;
644
- alert(`Текстура для слота "${textureSlot}" успешно обновлена.`);
645
- }
 
 
 
 
 
 
 
 
 
 
646
  };
647
-
648
- reader.readAsDataURL(file);
 
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
- if (terrainData.customTextures) {
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
- if (terrainMaterial.uniforms[uniformName].value) {
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