Kgshop commited on
Commit
f772a5d
·
verified ·
1 Parent(s): 633445c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +164 -267
app.py CHANGED
@@ -129,7 +129,7 @@ EDITOR_TEMPLATE = '''
129
  button.danger-button:hover { background: #ff3333; }
130
  .slider-container { margin-top: 10px; }
131
  input[type="range"] { width: 100%; }
132
- .radio-group label { display: inline-block; margin-right: 10px; cursor: pointer; font-size: 0.9em; }
133
  .radio-group input { margin-right: 5px; }
134
 
135
  #loading-spinner {
@@ -253,16 +253,16 @@ EDITOR_TEMPLATE = '''
253
  <option value="snow">Снег</option>
254
  <option value="sand">Песок</option>
255
  </select>
256
- <label for="custom-texture-file">Выберите файл текстуры (Albedo):</label>
257
  <input type="file" id="custom-texture-file" accept="image/*">
258
  <button id="update-texture-btn">Обновить текстуру</button>
259
- <p style="font-size:0.8em; color: #aaa;">Для PBR (Normal, Roughness и т.д.) требуется изменение кода.</p>
260
  <label for="texture-slot-name">Новое имя для слота:</label>
261
  <input type="text" id="texture-slot-name" placeholder="например, Лава">
262
  <button id="rename-texture-slot-btn">Переименовать</button>
263
  </div>
264
  <div class="ui-group">
265
  <h3>Размещение объектов</h3>
 
266
  <label for="object-select">Модель для размещения:</label>
267
  <select id="object-select">
268
  <option value="tree">Дерево</option>
@@ -272,6 +272,7 @@ EDITOR_TEMPLATE = '''
272
  </div>
273
  <div class="ui-group">
274
  <h3>Растительность</h3>
 
275
  <button id="clear-foliage" class="danger-button">Очистить растительность</button>
276
  </div>
277
  </div>
@@ -306,6 +307,7 @@ EDITOR_TEMPLATE = '''
306
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
307
  import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
308
  import { Sky } from 'three/addons/objects/Sky.js';
 
309
  import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
310
  import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
311
  import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js';
@@ -347,227 +349,176 @@ EDITOR_TEMPLATE = '''
347
  return tex;
348
  };
349
 
 
350
  const textures = {
351
  grass: {
352
- map: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/grasslight-big.jpg'),
353
- normal: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/grasslight-big-nm.jpg'),
354
- roughness: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/roughness_map.jpg'),
355
- ao: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/grasslight-big-ao.jpg'),
356
  },
357
  rock: {
358
- map: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/rock.jpg'),
359
- normal: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/rock-nm.jpg'),
360
- roughness: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/rock-roughness.jpg'),
361
- ao: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/rock-ao.jpg'),
362
  },
363
  dirt: {
364
- map: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/sand-512.jpg'),
365
- normal: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/water/Water_1_M_Normal.jpg'),
366
- roughness: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/rock-roughness.jpg'),
367
- ao: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/rock-ao.jpg'),
368
  },
369
  snow: {
370
- map: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/snow.jpg'),
371
- normal: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/water/Water_1_M_Normal.jpg'),
372
- roughness: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/roughness_map.jpg'),
373
- ao: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/rock-ao.jpg'),
374
  },
375
- sand: {
376
- map: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/sand-512.jpg'),
377
- normal: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/water/Water_1_M_Normal.jpg'),
378
- roughness: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/rock-roughness.jpg'),
379
- ao: loadTexture('https://cdn.jsdelivr.net/gh/mrdoob/three.js/examples/textures/terrain/rock-ao.jpg'),
380
  }
381
  };
382
 
383
  const terrainMaterial = new THREE.ShaderMaterial({
384
  uniforms: {
385
- textureScale: { value: 30.0 },
386
- blendSharpness: { value: 1.5 },
387
-
388
  grassMap: { value: textures.grass.map },
389
- grassNormalMap: { value: textures.grass.normal },
390
- grassRoughnessMap: { value: textures.grass.roughness },
391
- grassAOMap: { value: textures.grass.ao },
392
-
393
  rockMap: { value: textures.rock.map },
394
- rockNormalMap: { value: textures.rock.normal },
395
- rockRoughnessMap: { value: textures.rock.roughness },
396
- rockAOMap: { value: textures.rock.ao },
397
 
398
  dirtMap: { value: textures.dirt.map },
399
- dirtNormalMap: { value: textures.dirt.normal },
400
- dirtRoughnessMap: { value: textures.dirt.roughness },
401
- dirtAOMap: { value: textures.dirt.ao },
402
 
403
  snowMap: { value: textures.snow.map },
404
- snowNormalMap: { value: textures.snow.normal },
405
- snowRoughnessMap: { value: textures.snow.roughness },
406
- snowAOMap: { value: textures.snow.ao },
407
 
408
  sandMap: { value: textures.sand.map },
409
- sandNormalMap: { value: textures.sand.normal },
410
- sandRoughnessMap: { value: textures.sand.roughness },
411
- sandAOMap: { value: textures.sand.ao },
412
 
413
- sunDirection: { value: new THREE.Vector3() },
414
- ambientLightColor: { value: new THREE.Color() }
415
  },
416
  vertexShader: `
417
  varying vec2 vUv;
418
  varying vec3 vNormal;
419
  varying vec3 vViewPosition;
420
  attribute vec4 color;
421
- varying vec4 vSplat;
422
  attribute vec4 tangent;
423
- varying mat3 vTBN;
 
424
 
425
  void main() {
426
  vUv = uv;
427
- vSplat = color;
428
  vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
429
  vViewPosition = -mvPosition.xyz;
430
  vNormal = normalize(normalMatrix * normal);
431
-
432
- vec3 T = normalize( normalMatrix * tangent.xyz );
433
- vec3 B = normalize( cross( vNormal, T ) * tangent.w );
434
- vTBN = mat3(T, B, vNormal);
435
-
436
  gl_Position = projectionMatrix * mvPosition;
437
  }
438
  `,
439
  fragmentShader: `
440
- uniform float textureScale;
441
- uniform float blendSharpness;
442
-
443
- uniform sampler2D grassMap, grassNormalMap, grassRoughnessMap, grassAOMap;
444
- uniform sampler2D rockMap, rockNormalMap, rockRoughnessMap, rockAOMap;
445
- uniform sampler2D dirtMap, dirtNormalMap, dirtRoughnessMap, dirtAOMap;
446
- uniform sampler2D snowMap, snowNormalMap, snowRoughnessMap, snowAOMap;
447
- uniform sampler2D sandMap, sandNormalMap, sandRoughnessMap, sandAOMap;
448
 
449
- uniform vec3 sunDirection;
450
- uniform vec3 ambientLightColor;
451
-
452
- varying vec2 vUv;
453
- varying vec4 vSplat;
454
- varying vec3 vNormal;
455
- varying vec3 vViewPosition;
456
- varying mat3 vTBN;
457
-
458
- const float M_PI = 3.141592653589793;
459
 
460
- float blend_overlay(float base, float blend) {
461
- return base < 0.5 ? (2.0 * base * blend) : (1.0 - 2.0 * (1.0 - base) * (1.0 - blend));
462
- }
463
-
464
- vec3 blend_height(vec3 tex1, vec3 tex2, float blend_factor, float h1, float h2) {
465
- float blend = pow(smoothstep(0.0, 1.0, blend_factor), blendSharpness);
466
- float h_blend = clamp((blend + h1 - h2) * blend, 0.0, 1.0);
467
- return mix(tex1, tex2, h_blend);
468
- }
469
 
470
- vec3 getNormalFromMap(sampler2D normalMap, vec2 uv) {
471
- vec3 normal = texture(normalMap, uv).rgb * 2.0 - 1.0;
472
- return normalize(vTBN * normal);
473
- }
474
 
475
- float D_GGX(float dotNH, float roughness) {
476
- float a = roughness * roughness;
477
- float a2 = a * a;
478
- float NdotH = max(dotNH, 0.0);
479
- float NdotH2 = NdotH * NdotH;
480
- float nom = a2;
481
- float den = (NdotH2 * (a2 - 1.0) + 1.0);
482
- den = M_PI * den * den;
483
- return nom / den;
484
- }
485
 
486
- float G_Smith(float dotNV, float dotNL, float roughness) {
487
- float r = (roughness + 1.0);
488
- float k = (r * r) / 8.0;
489
- float ggx2 = dotNV / (dotNV * (1.0 - k) + k);
490
- float ggx1 = dotNL / (dotNL * (1.0 - k) + k);
491
- return ggx1 * ggx2;
492
- }
493
 
494
- vec3 F_Schlick(float cosTheta, vec3 F0) {
495
- return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
496
- }
 
 
 
497
 
498
  void main() {
499
- vec2 uv = vUv * textureScale;
500
-
501
- float grass_h = texture(grassMap, uv).r;
502
- float rock_h = texture(rockMap, uv).r;
503
- float dirt_h = texture(dirtMap, uv).r;
504
- float snow_h = texture(snowMap, uv).r;
505
- float sand_h = texture(sandMap, uv).r;
506
-
507
- vec3 grass_col = texture(grassMap, uv).rgb;
508
- vec3 rock_col = texture(rockMap, uv).rgb;
509
- vec3 dirt_col = texture(dirtMap, uv).rgb;
510
- vec3 snow_col = texture(snowMap, uv).rgb;
511
- vec3 sand_col = texture(sandMap, uv).rgb;
512
-
513
- vec3 albedo = grass_col;
514
- albedo = blend_height(albedo, rock_col, vSplat.r, grass_h, rock_h);
515
- albedo = blend_height(albedo, dirt_col, vSplat.g, grass_h, dirt_h);
516
- albedo = blend_height(albedo, snow_col, vSplat.b, grass_h, snow_h);
517
- albedo = blend_height(albedo, sand_col, vSplat.a, grass_h, sand_h);
518
 
519
- float totalSplat = max(0.001, vSplat.r + vSplat.g + vSplat.b + vSplat.a);
520
- vec4 mixFactors = vSplat / totalSplat;
521
- mixFactors.x = 1.0 - mixFactors.y - mixFactors.z - mixFactors.w;
522
-
523
- vec3 normal = mix(getNormalFromMap(grassNormalMap, uv), getNormalFromMap(rockNormalMap, uv), mixFactors.r);
524
- normal = mix(normal, getNormalFromMap(dirtNormalMap, uv), mixFactors.g);
525
- normal = mix(normal, getNormalFromMap(snowNormalMap, uv), mixFactors.b);
526
- normal = mix(normal, getNormalFromMap(sandNormalMap, uv), mixFactors.a);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
 
528
- float roughness = mix(texture(grassRoughnessMap, uv).g, texture(rockRoughnessMap, uv).g, mixFactors.r);
529
- roughness = mix(roughness, texture(dirtRoughnessMap, uv).g, mixFactors.g);
530
- roughness = mix(roughness, texture(snowRoughnessMap, uv).g, mixFactors.b);
531
- roughness = mix(roughness, texture(sandRoughnessMap, uv).g, mixFactors.a);
532
-
533
- float ao = mix(texture(grassAOMap, uv).r, texture(rockAOMap, uv).r, mixFactors.r);
534
- ao = mix(ao, texture(dirtAOMap, uv).r, mixFactors.g);
535
- ao = mix(ao, texture(snowAOMap, uv).r, mixFactors.b);
536
- ao = mix(ao, texture(sandAOMap, uv).r, mixFactors.a);
537
 
538
- vec3 N = normalize(normal);
539
- vec3 V = normalize(vViewPosition);
540
- vec3 L = normalize(sunDirection);
541
- vec3 H = normalize(V + L);
542
-
543
- float dotNL = max(dot(N, L), 0.0);
544
- float dotNH = max(dot(N, H), 0.0);
545
- float dotVH = max(dot(V, H), 0.0);
546
 
547
- vec3 F0 = vec3(0.04);
548
- float metallic = 0.0;
549
-
550
- float NDF = D_GGX(dotNH, roughness);
551
- float G = G_Smith(dot(N, V), dot(N, L), roughness);
552
- vec3 F = F_Schlick(dotVH, F0);
553
-
554
- vec3 kS = F;
555
- vec3 kD = vec3(1.0) - kS;
556
- kD *= 1.0 - metallic;
557
-
558
- vec3 numerator = NDF * G * F;
559
- float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;
560
- vec3 specular = numerator / denominator;
561
-
562
- vec3 Lo = (kD * albedo / M_PI + specular) * dotNL;
563
-
564
- vec3 ambient = ambientLightColor * albedo * ao;
565
- vec3 finalColor = ambient + Lo;
566
-
567
- finalColor = finalColor / (finalColor + vec3(1.0));
568
- finalColor = pow(finalColor, vec3(1.0/2.2));
569
 
570
- gl_FragColor = vec4(finalColor, 1.0);
 
571
  }
572
  `
573
  });
@@ -586,40 +537,40 @@ EDITOR_TEMPLATE = '''
586
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
587
  renderer.outputColorSpace = THREE.SRGBColorSpace;
588
  document.body.appendChild(renderer.domElement);
 
 
 
 
 
 
589
 
590
  orbitControls = new OrbitControls(camera, renderer.domElement);
591
  orbitControls.enableDamping = true;
592
  orbitControls.maxPolarAngle = Math.PI / 2.1;
593
 
594
- const ambientLight = new THREE.AmbientLight(0xcccccc, 0.2);
595
- scene.add(ambientLight);
596
-
597
- const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.5);
598
- scene.add(hemiLight);
599
- terrainMaterial.uniforms.ambientLightColor.value = new THREE.Color(0xffffff).multiplyScalar(0.2);
600
-
601
- const dirLight = new THREE.DirectionalLight(0xffffff, 1.5);
602
  dirLight.position.set(100, 100, 50);
603
  dirLight.castShadow = true;
604
- dirLight.shadow.mapSize.width = 2048;
605
- dirLight.shadow.mapSize.height = 2048;
606
  dirLight.shadow.camera.top = 100;
607
  dirLight.shadow.camera.bottom = -100;
608
  dirLight.shadow.camera.left = -100;
609
  dirLight.shadow.camera.right = 100;
610
  dirLight.shadow.bias = -0.001;
611
  scene.add(dirLight);
612
-
 
613
  sky = new Sky();
614
  sky.scale.setScalar(450000);
615
  scene.add(sky);
616
  sun = new THREE.Vector3();
617
  const effectController = {
618
- turbidity: 10,
619
- rayleigh: 3,
620
  mieCoefficient: 0.005,
621
- mieDirectionalG: 0.7,
622
- elevation: 4,
623
  azimuth: 180,
624
  };
625
  const uniforms = sky.material.uniforms;
@@ -632,9 +583,6 @@ EDITOR_TEMPLATE = '''
632
  sun.setFromSphericalCoords( 1, phi, theta );
633
  uniforms[ 'sunPosition' ].value.copy( sun );
634
  dirLight.position.copy(sun).multiplyScalar(100);
635
- terrainMaterial.uniforms.sunDirection.value = sun;
636
-
637
- scene.fog = new THREE.FogExp2(0x9db2c9, 0.005);
638
 
639
  const brushGeometry = new THREE.CylinderGeometry(1, 1, 100, 32, 1, true);
640
  const brushMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00, wireframe: true, transparent: true, opacity: 0.5 });
@@ -661,35 +609,26 @@ EDITOR_TEMPLATE = '''
661
  }
662
 
663
  function initModels() {
664
- const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22, roughness: 0.8 });
665
- const trunkMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513, roughness: 0.9 });
666
- const rockMaterial = new THREE.MeshStandardMaterial({ color: 0x808080, roughness: 0.6 });
667
-
 
 
 
 
 
668
  const treeModel = new THREE.Group();
669
- const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.4, 3, 8), trunkMaterial);
670
- trunk.position.y = 1.5;
671
- treeModel.add(trunk);
672
-
673
- const crownLevels = 3;
674
- for(let i=0; i<crownLevels; i++) {
675
- const size = 2.0 - i * 0.5;
676
- const crown = new THREE.Mesh(new THREE.ConeGeometry(size, 2.5, 8), treeMaterial);
677
- crown.position.y = 3 + i * 1.2;
678
- treeModel.add(crown);
679
- }
680
- treeModel.traverse(child => { if(child.isMesh) child.castShadow = true; });
681
  models['tree'] = treeModel;
682
 
683
- const rockGeom = new THREE.DodecahedronGeometry(1.5, 1);
684
- const pos = rockGeom.attributes.position;
685
- for(let i=0; i<pos.count; i++){
686
- const v = new THREE.Vector3().fromBufferAttribute(pos, i);
687
- v.multiplyScalar(0.8 + Math.random() * 0.4);
688
- pos.setXYZ(i, v.x, v.y, v.z);
689
- }
690
- rockGeom.computeVertexNormals();
691
  const rockModel = new THREE.Mesh(rockGeom, rockMaterial);
692
  rockModel.castShadow = true;
 
693
  models['rock'] = rockModel;
694
  }
695
 
@@ -704,7 +643,7 @@ EDITOR_TEMPLATE = '''
704
  ssaoPass.maxDistance = 0.1;
705
  composer.addPass(ssaoPass);
706
 
707
- const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 0.3, 0.4, 0.85);
708
  composer.addPass(bloomPass);
709
  }
710
 
@@ -712,7 +651,7 @@ EDITOR_TEMPLATE = '''
712
  if (grassInstances) {
713
  scene.remove(grassInstances);
714
  grassInstances.geometry.dispose();
715
- if(grassMaterial) grassMaterial.dispose();
716
  }
717
  const grassTexture = textureLoader.load('https://threejs.org/examples/textures/sprites/grass.png');
718
  grassMaterial = new THREE.ShaderMaterial({
@@ -747,7 +686,7 @@ EDITOR_TEMPLATE = '''
747
  varying vec2 vUv;
748
 
749
  void main() {
750
- vec4 texColor = texture(map, vUv);
751
  if (texColor.a < 0.5) discard;
752
  gl_FragColor = texColor;
753
  }
@@ -760,7 +699,6 @@ EDITOR_TEMPLATE = '''
760
  grassBlade.translate(0, 0.9, 0);
761
  grassInstances = new THREE.InstancedMesh(grassBlade, grassMaterial, MAX_GRASS_COUNT);
762
  grassInstances.castShadow = true;
763
- grassInstances.receiveShadow = true;
764
  grassInstances.count = 0;
765
  scene.add(grassInstances);
766
  }
@@ -838,35 +776,7 @@ EDITOR_TEMPLATE = '''
838
  }
839
 
840
  function updateCustomTexture() {
841
- const fileInput = document.getElementById('custom-texture-file');
842
- const textureSlot = document.getElementById('texture-slot-select').value;
843
-
844
- if (fileInput.files.length === 0) {
845
- alert('Пожалуйста, выберите файл изображения.');
846
- return;
847
- }
848
-
849
- const file = fileInput.files[0];
850
- const reader = new FileReader();
851
-
852
- reader.onload = (event) => {
853
- const dataUrl = event.target.result;
854
- customTextures[textureSlot] = dataUrl;
855
-
856
- const newTexture = loadTexture(dataUrl);
857
- const uniformName = textureSlot + 'Map';
858
-
859
- if (terrainMaterial.uniforms[uniformName]) {
860
- if (terrainMaterial.uniforms[uniformName].value) {
861
- terrainMaterial.uniforms[uniformName].value.dispose();
862
- }
863
- terrainMaterial.uniforms[uniformName].value = newTexture;
864
- terrainMaterial.needsUpdate = true;
865
- alert(`Текстура для слота "${textureSlot}" успешно обновлена.`);
866
- }
867
- };
868
-
869
- reader.readAsDataURL(file);
870
  }
871
 
872
  function renameTextureSlot() {
@@ -998,20 +908,6 @@ EDITOR_TEMPLATE = '''
998
  }
999
  });
1000
  }
1001
- if (terrainData.customTextures) {
1002
- customTextures = terrainData.customTextures;
1003
- Object.entries(customTextures).forEach(([slot, dataUrl]) => {
1004
- const newTexture = loadTexture(dataUrl);
1005
- const uniformName = slot + 'Map';
1006
- if (terrainMaterial.uniforms[uniformName]) {
1007
- if (terrainMaterial.uniforms[uniformName].value) {
1008
- terrainMaterial.uniforms[uniformName].value.dispose();
1009
- }
1010
- terrainMaterial.uniforms[uniformName].value = newTexture;
1011
- }
1012
- });
1013
- terrainMaterial.needsUpdate = true;
1014
- }
1015
  updateTextureUIAfterLoad(terrainData);
1016
  }
1017
 
@@ -1389,7 +1285,7 @@ EDITOR_TEMPLATE = '''
1389
  newObject.rotation.y = Math.random() * Math.PI * 2;
1390
  const scale = Math.random() * 0.5 + 0.75;
1391
  newObject.scale.set(scale, scale, scale);
1392
-
1393
  placedObjectsGroup.add(newObject);
1394
  }
1395
 
@@ -1411,11 +1307,12 @@ EDITOR_TEMPLATE = '''
1411
 
1412
  let rock = 0, dirt = 0, snow = 0, sand = 0;
1413
 
1414
- if (yPos > snowHeight + (Math.random() - 0.5) * 5) {
1415
  snow = 1.0;
1416
  } else if (yNorm < rockSlopeThreshold) {
1417
  rock = 1.0;
1418
- } else { }
 
1419
  colors.setXYZW(i, rock, dirt, snow, sand);
1420
  }
1421
  colors.needsUpdate = true;
@@ -1446,10 +1343,11 @@ EDITOR_TEMPLATE = '''
1446
  });
1447
 
1448
  const serializedObjects = placedObjectsGroup.children.map(obj => {
 
 
 
1449
  return {
1450
- type: Object.keys(models).find(key => {
1451
- return obj.children.length > 0 && models[key].children.length > 0 && obj.children[0].geometry === models[key].children[0].geometry
1452
- }) || Object.keys(models).find(key => obj.geometry === models[key].geometry),
1453
  position: obj.position,
1454
  quaternion: obj.quaternion,
1455
  scale: obj.scale
@@ -1466,7 +1364,6 @@ EDITOR_TEMPLATE = '''
1466
  grass: grassMatrices
1467
  },
1468
  placedObjects: serializedObjects,
1469
- customTextures: customTextures,
1470
  textureNames: textureNames
1471
  };
1472
 
 
129
  button.danger-button:hover { background: #ff3333; }
130
  .slider-container { margin-top: 10px; }
131
  input[type="range"] { width: 100%; }
132
+ .radio-group label { display: inline-block; margin-right: 10px; cursor: pointer; }
133
  .radio-group input { margin-right: 5px; }
134
 
135
  #loading-spinner {
 
253
  <option value="snow">Снег</option>
254
  <option value="sand">Песок</option>
255
  </select>
256
+ <label for="custom-texture-file">Выберите файл текстуры:</label>
257
  <input type="file" id="custom-texture-file" accept="image/*">
258
  <button id="update-texture-btn">Обновить текстуру</button>
 
259
  <label for="texture-slot-name">Новое имя для слота:</label>
260
  <input type="text" id="texture-slot-name" placeholder="например, Лава">
261
  <button id="rename-texture-slot-btn">Переименовать</button>
262
  </div>
263
  <div class="ui-group">
264
  <h3>Размещение объектов</h3>
265
+ <p style="font-size:0.8em; color: #aaa;">Режим "Объект" в кистях. Выберите модель и рисуйте ей на карте.</p>
266
  <label for="object-select">Модель для размещения:</label>
267
  <select id="object-select">
268
  <option value="tree">Дерево</option>
 
272
  </div>
273
  <div class="ui-group">
274
  <h3>Растительность</h3>
275
+ <p style="font-size:0.8em; color: #aaa;">Режим "Растительность" в кистях.</p>
276
  <button id="clear-foliage" class="danger-button">Очистить растительность</button>
277
  </div>
278
  </div>
 
307
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
308
  import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
309
  import { Sky } from 'three/addons/objects/Sky.js';
310
+ import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
311
  import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
312
  import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
313
  import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js';
 
349
  return tex;
350
  };
351
 
352
+ const textureBaseUrl = 'https://cdn.jsdelivr.net/gh/pmndrs/drei-assets@master/textures/pbr/';
353
  const textures = {
354
  grass: {
355
+ map: loadTexture(textureBaseUrl + 'grass/color.jpg'),
356
+ normalMap: loadTexture(textureBaseUrl + 'grass/normal.jpg'),
357
+ roughnessMap: loadTexture(textureBaseUrl + 'grass/roughness.jpg'),
358
+ aoMap: loadTexture(textureBaseUrl + 'grass/ao.jpg'),
359
  },
360
  rock: {
361
+ map: loadTexture(textureBaseUrl + 'rock_02/color.jpg'),
362
+ normalMap: loadTexture(textureBaseUrl + 'rock_02/normal.jpg'),
363
+ roughnessMap: loadTexture(textureBaseUrl + 'rock_02/roughness.jpg'),
364
+ aoMap: loadTexture(textureBaseUrl + 'rock_02/ao.jpg'),
365
  },
366
  dirt: {
367
+ map: loadTexture(textureBaseUrl + 'dirt_path/color.jpg'),
368
+ normalMap: loadTexture(textureBaseUrl + 'dirt_path/normal.jpg'),
369
+ roughnessMap: loadTexture(textureBaseUrl + 'dirt_path/roughness.jpg'),
370
+ aoMap: loadTexture(textureBaseUrl + 'dirt_path/ao.jpg'),
371
  },
372
  snow: {
373
+ map: loadTexture(textureBaseUrl + 'snow_02/color.jpg'),
374
+ normalMap: loadTexture(textureBaseUrl + 'snow_02/normal.jpg'),
375
+ roughnessMap: loadTexture(textureBaseUrl + 'snow_02/roughness.jpg'),
376
+ aoMap: loadTexture(textureBaseUrl + 'snow_02/ao.jpg'),
377
  },
378
+ sand: {
379
+ map: loadTexture(textureBaseUrl + 'sand_01/color.jpg'),
380
+ normalMap: loadTexture(textureBaseUrl + 'sand_01/normal.jpg'),
381
+ roughnessMap: loadTexture(textureBaseUrl + 'sand_01/roughness.jpg'),
382
+ aoMap: loadTexture(textureBaseUrl + 'sand_01/ao.jpg'),
383
  }
384
  };
385
 
386
  const terrainMaterial = new THREE.ShaderMaterial({
387
  uniforms: {
 
 
 
388
  grassMap: { value: textures.grass.map },
389
+ grassNormalMap: { value: textures.grass.normalMap },
390
+ grassRoughnessMap: { value: textures.grass.roughnessMap },
391
+ grassAoMap: { value: textures.grass.aoMap },
392
+
393
  rockMap: { value: textures.rock.map },
394
+ rockNormalMap: { value: textures.rock.normalMap },
395
+ rockRoughnessMap: { value: textures.rock.roughnessMap },
396
+ rockAoMap: { value: textures.rock.aoMap },
397
 
398
  dirtMap: { value: textures.dirt.map },
399
+ dirtNormalMap: { value: textures.dirt.normalMap },
400
+ dirtRoughnessMap: { value: textures.dirt.roughnessMap },
401
+ dirtAoMap: { value: textures.dirt.aoMap },
402
 
403
  snowMap: { value: textures.snow.map },
404
+ snowNormalMap: { value: textures.snow.normalMap },
405
+ snowRoughnessMap: { value: textures.snow.roughnessMap },
406
+ snowAoMap: { value: textures.snow.aoMap },
407
 
408
  sandMap: { value: textures.sand.map },
409
+ sandNormalMap: { value: textures.sand.normalMap },
410
+ sandRoughnessMap: { value: textures.sand.roughnessMap },
411
+ sandAoMap: { value: textures.sand.aoMap },
412
 
413
+ lightDirection: { value: new THREE.Vector3(0.5, 0.5, 0.5).normalize() }
 
414
  },
415
  vertexShader: `
416
  varying vec2 vUv;
417
  varying vec3 vNormal;
418
  varying vec3 vViewPosition;
419
  attribute vec4 color;
420
+ varying vec4 vColor;
421
  attribute vec4 tangent;
422
+ varying vec3 vTangent;
423
+ varying vec3 vBitangent;
424
 
425
  void main() {
426
  vUv = uv;
427
+ vColor = color;
428
  vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
429
  vViewPosition = -mvPosition.xyz;
430
  vNormal = normalize(normalMatrix * normal);
431
+ vTangent = normalize(normalMatrix * tangent.xyz);
432
+ vBitangent = normalize(cross(vNormal, vTangent) * tangent.w);
 
 
 
433
  gl_Position = projectionMatrix * mvPosition;
434
  }
435
  `,
436
  fragmentShader: `
437
+ uniform sampler2D grassMap;
438
+ uniform sampler2D grassNormalMap;
439
+ uniform sampler2D grassRoughnessMap;
440
+ uniform sampler2D grassAoMap;
 
 
 
 
441
 
442
+ uniform sampler2D rockMap;
443
+ uniform sampler2D rockNormalMap;
444
+ uniform sampler2D rockRoughnessMap;
445
+ uniform sampler2D rockAoMap;
 
 
 
 
 
 
446
 
447
+ uniform sampler2D dirtMap;
448
+ uniform sampler2D dirtNormalMap;
449
+ uniform sampler2D dirtRoughnessMap;
450
+ uniform sampler2D dirtAoMap;
 
 
 
 
 
451
 
452
+ uniform sampler2D snowMap;
453
+ uniform sampler2D snowNormalMap;
454
+ uniform sampler2D snowRoughnessMap;
455
+ uniform sampler2D snowAoMap;
456
 
457
+ uniform sampler2D sandMap;
458
+ uniform sampler2D sandNormalMap;
459
+ uniform sampler2D sandRoughnessMap;
460
+ uniform sampler2D sandAoMap;
 
 
 
 
 
 
461
 
462
+ uniform vec3 lightDirection;
 
 
 
 
 
 
463
 
464
+ varying vec2 vUv;
465
+ varying vec4 vColor;
466
+ varying vec3 vNormal;
467
+ varying vec3 vViewPosition;
468
+ varying vec3 vTangent;
469
+ varying vec3 vBitangent;
470
 
471
  void main() {
472
+ float TILING = 30.0;
473
+ vec2 uv_scaled = vUv * TILING;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
 
475
+ vec3 grassAlbedo = texture2D(grassMap, uv_scaled).rgb;
476
+ vec3 rockAlbedo = texture2D(rockMap, uv_scaled).rgb;
477
+ vec3 dirtAlbedo = texture2D(dirtMap, uv_scaled).rgb;
478
+ vec3 snowAlbedo = texture2D(snowMap, uv_scaled).rgb;
479
+ vec3 sandAlbedo = texture2D(sandMap, uv_scaled).rgb;
480
+
481
+ float grassRoughness = texture2D(grassRoughnessMap, uv_scaled).r;
482
+ float rockRoughness = texture2D(rockRoughnessMap, uv_scaled).r;
483
+ float dirtRoughness = texture2D(dirtRoughnessMap, uv_scaled).r;
484
+ float snowRoughness = texture2D(snowRoughnessMap, uv_scaled).r;
485
+ float sandRoughness = texture2D(sandRoughnessMap, uv_scaled).r;
486
+
487
+ float grassAO = texture2D(grassAoMap, uv_scaled).r;
488
+ float rockAO = texture2D(rockAoMap, uv_scaled).r;
489
+ float dirtAO = texture2D(dirtAoMap, uv_scaled).r;
490
+ float snowAO = texture2D(snowAoMap, uv_scaled).r;
491
+ float sandAO = texture2D(sandAoMap, uv_scaled).r;
492
+
493
+ vec3 grassNormal = texture2D(grassNormalMap, uv_scaled).xyz * 2.0 - 1.0;
494
+ vec3 rockNormal = texture2D(rockNormalMap, uv_scaled).xyz * 2.0 - 1.0;
495
+ vec3 dirtNormal = texture2D(dirtNormalMap, uv_scaled).xyz * 2.0 - 1.0;
496
+ vec3 snowNormal = texture2D(snowNormalMap, uv_scaled).xyz * 2.0 - 1.0;
497
+ vec3 sandNormal = texture2D(sandNormalMap, uv_scaled).xyz * 2.0 - 1.0;
498
+
499
+ vec3 blendedAlbedo = grassAlbedo;
500
+ blendedAlbedo = mix(blendedAlbedo, rockAlbedo, vColor.r);
501
+ blendedAlbedo = mix(blendedAlbedo, dirtAlbedo, vColor.g);
502
+ blendedAlbedo = mix(blendedAlbedo, snowAlbedo, vColor.b);
503
+ blendedAlbedo = mix(blendedAlbedo, sandAlbedo, vColor.a);
504
 
505
+ float blendedAO = grassAO;
506
+ blendedAO = mix(blendedAO, rockAO, vColor.r);
507
+ blendedAO = mix(blendedAO, dirtAO, vColor.g);
508
+ blendedAO = mix(blendedAO, snowAO, vColor.b);
509
+ blendedAO = mix(blendedAO, sandAO, vColor.a);
 
 
 
 
510
 
511
+ vec3 blendedNormal = grassNormal;
512
+ blendedNormal = mix(blendedNormal, rockNormal, vColor.r);
513
+ blendedNormal = mix(blendedNormal, dirtNormal, vColor.g);
514
+ blendedNormal = mix(blendedNormal, snowNormal, vColor.b);
515
+ blendedNormal = mix(blendedNormal, sandNormal, vColor.a);
 
 
 
516
 
517
+ mat3 tbn = mat3(vTangent, vBitangent, vNormal);
518
+ vec3 normal = normalize(tbn * blendedNormal);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
519
 
520
+ float lighting = dot(normal, lightDirection) * 0.6 + 0.4;
521
+ gl_FragColor = vec4(blendedAlbedo * lighting * blendedAO, 1.0);
522
  }
523
  `
524
  });
 
537
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
538
  renderer.outputColorSpace = THREE.SRGBColorSpace;
539
  document.body.appendChild(renderer.domElement);
540
+
541
+ new RGBELoader().load('https://unpkg.com/three@0.160.0/examples/textures/equirectangular/venice_sunset_1k.hdr', function (texture) {
542
+ texture.mapping = THREE.EquirectangularReflectionMapping;
543
+ scene.background = texture;
544
+ scene.environment = texture;
545
+ });
546
 
547
  orbitControls = new OrbitControls(camera, renderer.domElement);
548
  orbitControls.enableDamping = true;
549
  orbitControls.maxPolarAngle = Math.PI / 2.1;
550
 
551
+ const dirLight = new THREE.DirectionalLight(0xffffff, 2.0);
 
 
 
 
 
 
 
552
  dirLight.position.set(100, 100, 50);
553
  dirLight.castShadow = true;
554
+ dirLight.shadow.mapSize.width = 4096;
555
+ dirLight.shadow.mapSize.height = 4096;
556
  dirLight.shadow.camera.top = 100;
557
  dirLight.shadow.camera.bottom = -100;
558
  dirLight.shadow.camera.left = -100;
559
  dirLight.shadow.camera.right = 100;
560
  dirLight.shadow.bias = -0.001;
561
  scene.add(dirLight);
562
+ terrainMaterial.uniforms.lightDirection.value = dirLight.position.clone().normalize();
563
+
564
  sky = new Sky();
565
  sky.scale.setScalar(450000);
566
  scene.add(sky);
567
  sun = new THREE.Vector3();
568
  const effectController = {
569
+ turbidity: 2,
570
+ rayleigh: 1,
571
  mieCoefficient: 0.005,
572
+ mieDirectionalG: 0.8,
573
+ elevation: 6,
574
  azimuth: 180,
575
  };
576
  const uniforms = sky.material.uniforms;
 
583
  sun.setFromSphericalCoords( 1, phi, theta );
584
  uniforms[ 'sunPosition' ].value.copy( sun );
585
  dirLight.position.copy(sun).multiplyScalar(100);
 
 
 
586
 
587
  const brushGeometry = new THREE.CylinderGeometry(1, 1, 100, 32, 1, true);
588
  const brushMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00, wireframe: true, transparent: true, opacity: 0.5 });
 
609
  }
610
 
611
  function initModels() {
612
+ const treeMaterial = new THREE.MeshStandardMaterial({ color: 0x2C5234, roughness: 0.8 });
613
+ const trunkMaterial = new THREE.MeshStandardMaterial({ color: 0x5C3D2E, roughness: 0.9 });
614
+ const rockMaterial = new THREE.MeshStandardMaterial({ color: 0x5E6061, roughness: 0.7 });
615
+
616
+ const treeCrown = new THREE.ConeGeometry(2, 4, 8);
617
+ treeCrown.translate(0, 3, 0);
618
+ const treeTrunk = new THREE.CylinderGeometry(0.5, 0.5, 2, 8);
619
+ const treeCrownMesh = new THREE.Mesh(treeCrown, treeMaterial);
620
+ const treeTrunkMesh = new THREE.Mesh(treeTrunk, trunkMaterial);
621
  const treeModel = new THREE.Group();
622
+ treeModel.add(treeCrownMesh);
623
+ treeModel.add(treeTrunkMesh);
624
+ treeModel.castShadow = true;
625
+ treeModel.traverse(node => { if(node.isMesh) node.receiveShadow = true; });
 
 
 
 
 
 
 
 
626
  models['tree'] = treeModel;
627
 
628
+ const rockGeom = new THREE.DodecahedronGeometry(1.5, 0);
 
 
 
 
 
 
 
629
  const rockModel = new THREE.Mesh(rockGeom, rockMaterial);
630
  rockModel.castShadow = true;
631
+ rockModel.receiveShadow = true;
632
  models['rock'] = rockModel;
633
  }
634
 
 
643
  ssaoPass.maxDistance = 0.1;
644
  composer.addPass(ssaoPass);
645
 
646
+ const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 0.3, 0.4, 0.9);
647
  composer.addPass(bloomPass);
648
  }
649
 
 
651
  if (grassInstances) {
652
  scene.remove(grassInstances);
653
  grassInstances.geometry.dispose();
654
+ grassMaterial.dispose();
655
  }
656
  const grassTexture = textureLoader.load('https://threejs.org/examples/textures/sprites/grass.png');
657
  grassMaterial = new THREE.ShaderMaterial({
 
686
  varying vec2 vUv;
687
 
688
  void main() {
689
+ vec4 texColor = texture2D(map, vUv);
690
  if (texColor.a < 0.5) discard;
691
  gl_FragColor = texColor;
692
  }
 
699
  grassBlade.translate(0, 0.9, 0);
700
  grassInstances = new THREE.InstancedMesh(grassBlade, grassMaterial, MAX_GRASS_COUNT);
701
  grassInstances.castShadow = true;
 
702
  grassInstances.count = 0;
703
  scene.add(grassInstances);
704
  }
 
776
  }
777
 
778
  function updateCustomTexture() {
779
+ alert("Замена PBR текстур через UI не поддерживается в этой версии.");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
780
  }
781
 
782
  function renameTextureSlot() {
 
908
  }
909
  });
910
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
911
  updateTextureUIAfterLoad(terrainData);
912
  }
913
 
 
1285
  newObject.rotation.y = Math.random() * Math.PI * 2;
1286
  const scale = Math.random() * 0.5 + 0.75;
1287
  newObject.scale.set(scale, scale, scale);
1288
+ newObject.traverse(node => { if(node.isMesh) { node.castShadow = true; node.receiveShadow = true; }});
1289
  placedObjectsGroup.add(newObject);
1290
  }
1291
 
 
1307
 
1308
  let rock = 0, dirt = 0, snow = 0, sand = 0;
1309
 
1310
+ if (yPos > snowHeight) {
1311
  snow = 1.0;
1312
  } else if (yNorm < rockSlopeThreshold) {
1313
  rock = 1.0;
1314
+ } else {
1315
+ }
1316
  colors.setXYZW(i, rock, dirt, snow, sand);
1317
  }
1318
  colors.needsUpdate = true;
 
1343
  });
1344
 
1345
  const serializedObjects = placedObjectsGroup.children.map(obj => {
1346
+ const type = Object.keys(models).find(key => {
1347
+ return obj.geometry === models[key].geometry || (obj.children.length > 0 && models[key].children.length > 0 && obj.children[0].geometry === models[key].children[0].geometry)
1348
+ });
1349
  return {
1350
+ type: type,
 
 
1351
  position: obj.position,
1352
  quaternion: obj.quaternion,
1353
  scale: obj.scale
 
1364
  grass: grassMatrices
1365
  },
1366
  placedObjects: serializedObjects,
 
1367
  textureNames: textureNames
1368
  };
1369