File size: 4,283 Bytes
1390db3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import * as THREE from 'three';
import { G } from './globals.js';
import { MOUNTAINS } from './config.js';
const COLOR_N = new THREE.Color(MOUNTAINS.colorNight);
const COLOR_D = new THREE.Color(MOUNTAINS.colorDay);
const TMP_COLOR = new THREE.Color();

function dayFactor(t) {
  return 0.5 - 0.5 * Math.cos(2 * Math.PI * t);
}

function buildMountainRing({ radius, segments, baseHeight, heightVar, yOffset }) {
  const vertCount = segments * 2;
  const positions = new Float32Array(vertCount * 3);
  const colors = new Float32Array(vertCount * 3);
  const aH = new Float32Array(vertCount); // 0 at bottom ring, 1 at peaks
  const indices = new Uint16Array(segments * 6);

  const colBottom = new THREE.Color(MOUNTAINS.colorBase);
  const colTop = new THREE.Color(MOUNTAINS.colorPeak);

  function ridge(a) {
    const s1 = Math.sin(a * 3.0) * 0.7 + Math.sin(a * 7.0) * 0.3;
    const s2 = Math.sin(a * 1.7 + 1.3) * 0.5 + Math.sin(a * 4.3 + 0.7) * 0.5;
    const v = 0.5 * s1 + 0.5 * s2;
    return baseHeight + heightVar * (0.5 + 0.5 * v);
  }

  for (let i = 0; i < segments; i++) {
    const a0 = (i / segments) * Math.PI * 2;
    const h = ridge(a0);
    const x = Math.cos(a0) * radius;
    const z = Math.sin(a0) * radius;

    const iBot = i * 3;
    positions[iBot + 0] = x;
    positions[iBot + 1] = yOffset;
    positions[iBot + 2] = z;

    const iTop = (segments + i) * 3;
    positions[iTop + 0] = x;
    positions[iTop + 1] = yOffset + h;
    positions[iTop + 2] = z;

    colors[iBot + 0] = colBottom.r;
    colors[iBot + 1] = colBottom.g;
    colors[iBot + 2] = colBottom.b;
    colors[iTop + 0] = colTop.r;
    colors[iTop + 1] = colTop.g;
    colors[iTop + 2] = colTop.b;

    aH[i] = 0.0;                   // bottom vertex ratio
    aH[segments + i] = 1.0;        // top vertex ratio
  }

  let idx = 0;
  for (let i = 0; i < segments; i++) {
    const n = (i + 1) % segments;
    const b0 = i;
    const b1 = n;
    const t0 = segments + i;
    const t1 = segments + n;
    indices[idx++] = b0;
    indices[idx++] = t0;
    indices[idx++] = b1;
    indices[idx++] = b1;
    indices[idx++] = t0;
    indices[idx++] = t1;
  }

  const geo = new THREE.BufferGeometry();
  geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
  geo.setIndex(new THREE.BufferAttribute(indices, 1));
  geo.setAttribute('aH', new THREE.BufferAttribute(aH, 1));
  geo.computeBoundingSphere();
  return geo;
}

export function setupMountains() {
  if (!MOUNTAINS.enabled) return;
  const geo = buildMountainRing({
    radius: MOUNTAINS.radius,
    segments: MOUNTAINS.segments,
    baseHeight: MOUNTAINS.baseHeight,
    heightVar: MOUNTAINS.heightVar,
    yOffset: MOUNTAINS.yOffset
  });
  const mat = new THREE.MeshBasicMaterial({
    color: new THREE.Color(MOUNTAINS.colorDay),
    vertexColors: true,
    fog: false,           // keep visible even in heavy fog
    depthTest: true,
    depthWrite: false,
    side: THREE.DoubleSide, // ensure visible regardless of winding
    transparent: true
  });
  mat.onBeforeCompile = (shader) => {
    shader.uniforms.uFadeEdge = { value: MOUNTAINS.fadeEdge ?? 0.35 };
    shader.uniforms.uFadePow = { value: MOUNTAINS.fadePow ?? 1.5 };
    shader.vertexShader = (
      'attribute float aH;\n' +
      'varying float vH;\n' +
      shader.vertexShader
    ).replace(
      '#include <begin_vertex>',
      `#include <begin_vertex>\n vH = aH;`
    );
    shader.fragmentShader = (
      'varying float vH;\n uniform float uFadeEdge; uniform float uFadePow;\n' +
      shader.fragmentShader
    ).replace(
      '#include <dithering_fragment>',
      `diffuseColor.a *= pow(smoothstep(uFadeEdge, 1.0, vH), uFadePow);\n#include <dithering_fragment>`
    );
  };
  const mesh = new THREE.Mesh(geo, mat);
  mesh.castShadow = false;
  mesh.receiveShadow = false;
  mesh.renderOrder = -10;
  G.scene.add(mesh);
  G.mountains = mesh;
}

export function updateMountains(delta) {
  if (!MOUNTAINS.enabled || !G.mountains) return;
  const p = G.player ? G.player.pos : G.camera.position;
  G.mountains.position.set(p.x, 0, p.z);

  const dayF = dayFactor(G.timeOfDay || 0);
  TMP_COLOR.copy(COLOR_N).lerp(COLOR_D, dayF);
  G.mountains.material.color.copy(TMP_COLOR);
}