impact-ui 5.0.1-beta.1 → 5.0.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/halloween_devil_morning_final.html.js +1304 -0
- package/dist/assets/halloween_devil_night_Final.html.js +1299 -0
- package/dist/components/HalloweenStyleIntro/HalloweenStyleIntroProvider.d.ts.map +1 -1
- package/dist/components/HalloweenStyleIntro/HalloweenStyleIntroProvider.js +23 -4
- package/dist/components/HalloweenStyleIntro/halloweenIntro.constants.d.ts +5 -0
- package/dist/components/HalloweenStyleIntro/halloweenIntro.constants.d.ts.map +1 -1
- package/dist/components/HalloweenStyleIntro/halloweenIntro.constants.js +5 -0
- package/dist/components/HalloweenStyleIntro/halloweenIntroHtml.d.ts +10 -3
- package/dist/components/HalloweenStyleIntro/halloweenIntroHtml.d.ts.map +1 -1
- package/dist/components/HalloweenStyleIntro/halloweenIntroHtml.js +129 -9
- package/dist/components/HalloweenStyleIntro/halloweenIntroHtml.test.d.ts +2 -0
- package/dist/components/HalloweenStyleIntro/halloweenIntroHtml.test.d.ts.map +1 -0
- package/dist/components/Header/Header.types.d.ts +5 -0
- package/dist/components/Header/Header.types.d.ts.map +1 -1
- package/dist/components/Header/index.d.ts +1 -1
- package/dist/components/Header/index.d.ts.map +1 -1
- package/dist/components/Header/index.js +4 -0
- package/dist/components/Input/index.js +1 -1
- package/dist/components/Notification/NotificationBody/NotificationList/NotificationChipsList.d.ts.map +1 -1
- package/dist/components/Notification/NotificationBody/NotificationList/NotificationChipsList.js +3 -1
- package/dist/components/Notification/NotificationBody/NotificationList/NotificationList.js +1 -1
- package/dist/components/Notification/NotificationBody/NotificationTaskList.js +4 -2
- package/dist/components/Notification/index.js +1 -1
- package/dist/components/ProfileMenu/ProfileMenu.types.d.ts +5 -0
- package/dist/components/ProfileMenu/ProfileMenu.types.d.ts.map +1 -1
- package/dist/components/ProfileMenu/index.d.ts +1 -1
- package/dist/components/ProfileMenu/index.d.ts.map +1 -1
- package/dist/components/ProfileMenu/index.js +5 -1
- package/dist/components/TableV36/index.d.ts.map +1 -1
- package/dist/components/TableV36/index.js +8 -1
- package/dist/components/ThemingPanel/StylesSection.d.ts.map +1 -1
- package/dist/components/ThemingPanel/StylesSection.js +5 -4
- package/dist/components/ThemingPanel/ThemingPanel.d.ts +1 -1
- package/dist/components/ThemingPanel/ThemingPanel.d.ts.map +1 -1
- package/dist/components/ThemingPanel/ThemingPanel.js +24 -11
- package/dist/components/ThemingPanel/ThemingPanel.types.d.ts +5 -3
- package/dist/components/ThemingPanel/ThemingPanel.types.d.ts.map +1 -1
- package/dist/components/ThemingPanel/themingPanel.constants.d.ts +4 -0
- package/dist/components/ThemingPanel/themingPanel.constants.d.ts.map +1 -1
- package/dist/components/ThemingPanel/themingPanel.constants.js +11 -3
- package/dist/locales/de-DE.json.d.ts +4 -1
- package/dist/locales/de-DE.json.js +4 -1
- package/dist/locales/en-US.json.d.ts +4 -1
- package/dist/locales/en-US.json.js +4 -1
- package/dist/locales/es-ES.json.d.ts +4 -1
- package/dist/locales/es-ES.json.js +4 -1
- package/dist/mcp-component-registry.json +14 -53
- package/dist/stories/designTokensStoryHelpers.d.ts.map +1 -1
- package/dist/tokens/ImpactUiThemeProvider.js +2 -2
- package/dist/tokens/impactUiPreferences.d.ts +23 -0
- package/dist/tokens/impactUiPreferences.d.ts.map +1 -0
- package/dist/tokens/impactUiPreferences.js +92 -0
- package/dist/tokens/impactUiPreferences.test.d.ts +2 -0
- package/dist/tokens/impactUiPreferences.test.d.ts.map +1 -0
- package/dist/tokens/index.d.ts +1 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/interfaceStyleDom.d.ts +7 -2
- package/dist/tokens/interfaceStyleDom.d.ts.map +1 -1
- package/dist/tokens/interfaceStyleDom.js +4 -10
- package/dist/tokens/themeDom.d.ts +4 -1
- package/dist/tokens/themeDom.d.ts.map +1 -1
- package/dist/tokens/themeDom.js +4 -10
- package/package.json +1 -1
- package/dist/assets/halloween_candle_light.html.js +0 -2320
- package/dist/assets/halloween_night.html.js +0 -2307
|
@@ -1,2320 +0,0 @@
|
|
|
1
|
-
const halloweenCandleLightHtml = `<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
|
-
<title>The Last Candle</title>
|
|
7
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
|
-
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Creepster&display=swap" />
|
|
10
|
-
<style>
|
|
11
|
-
html,
|
|
12
|
-
body {
|
|
13
|
-
margin: 0;
|
|
14
|
-
height: 100%;
|
|
15
|
-
background: #000;
|
|
16
|
-
overflow: hidden;
|
|
17
|
-
}
|
|
18
|
-
canvas {
|
|
19
|
-
display: block;
|
|
20
|
-
}
|
|
21
|
-
#hint {
|
|
22
|
-
position: fixed;
|
|
23
|
-
inset: 0;
|
|
24
|
-
display: grid;
|
|
25
|
-
place-items: center;
|
|
26
|
-
margin: 0;
|
|
27
|
-
pointer-events: none;
|
|
28
|
-
user-select: none;
|
|
29
|
-
font:
|
|
30
|
-
400 15px/1.5 'Iowan Old Style',
|
|
31
|
-
'Palatino Linotype',
|
|
32
|
-
Palatino,
|
|
33
|
-
Georgia,
|
|
34
|
-
serif;
|
|
35
|
-
letter-spacing: 0.05em;
|
|
36
|
-
color: #7f766a;
|
|
37
|
-
transition: opacity 1.4s ease;
|
|
38
|
-
}
|
|
39
|
-
#hint.gone {
|
|
40
|
-
opacity: 0;
|
|
41
|
-
}
|
|
42
|
-
</style>
|
|
43
|
-
<script type="importmap">
|
|
44
|
-
{
|
|
45
|
-
"imports": {
|
|
46
|
-
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
|
|
47
|
-
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
<\/script>
|
|
51
|
-
</head>
|
|
52
|
-
<body>
|
|
53
|
-
<p id="hint">Best experienced with sound.</p>
|
|
54
|
-
|
|
55
|
-
<script type="module">
|
|
56
|
-
import * as THREE from 'three';
|
|
57
|
-
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
|
|
58
|
-
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
|
59
|
-
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
|
60
|
-
import { BokehPass } from 'three/addons/postprocessing/BokehPass.js';
|
|
61
|
-
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
|
|
62
|
-
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
|
|
63
|
-
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
|
|
64
|
-
import { mergeGeometries, mergeVertices } from 'three/addons/utils/BufferGeometryUtils.js';
|
|
65
|
-
|
|
66
|
-
/* ==========================================================================
|
|
67
|
-
THE LAST CANDLE
|
|
68
|
-
An empty graveyard at night. The camera makes one slow push down the aisle
|
|
69
|
-
while the low morning sun, haze and carved pumpkins fade up from black; at the end
|
|
70
|
-
every light goes out at once.
|
|
71
|
-
|
|
72
|
-
Timeline (seconds)
|
|
73
|
-
0.0 - 0.8 pitch black, every light at 0
|
|
74
|
-
0.8 - 6.5 morning sun, sky, haze and environment fade up; pumpkins kindle
|
|
75
|
-
10 - 15.5 the pumpkins gutter
|
|
76
|
-
2.5 - 7.5 the stone monument at the end of the aisle wakes: its edges and
|
|
77
|
-
veins glow blue to violet; a witch's hat hovers above it
|
|
78
|
-
9.8 - 16.9 "Happy Halloween" burns in at the top-left corner: fire-lit letters,
|
|
79
|
-
a hot igniting edge, heat shimmer and rising embers
|
|
80
|
-
1.0 - 16.9 a small "Applying Style...." status line at the bottom centre, dots loading
|
|
81
|
-
16.9 every light hits exactly 0
|
|
82
|
-
16.9 - 20.0 pitch black
|
|
83
|
-
========================================================================== */
|
|
84
|
-
|
|
85
|
-
const RENDER = new URLSearchParams(location.search).has('render');
|
|
86
|
-
const EMBED = new URLSearchParams(location.search).has('embed');
|
|
87
|
-
const T = {
|
|
88
|
-
revealIn: 0.8,
|
|
89
|
-
revealFull: 6.5,
|
|
90
|
-
devilGo: 2.2,
|
|
91
|
-
devilArrive: 14.4,
|
|
92
|
-
lift0: 15.0,
|
|
93
|
-
lift1: 16.1,
|
|
94
|
-
blow0: 16.25,
|
|
95
|
-
out: 16.9,
|
|
96
|
-
end: 20.0,
|
|
97
|
-
loop: 22.0,
|
|
98
|
-
};
|
|
99
|
-
const Z_START = -31.0,
|
|
100
|
-
Z_END = 0.4; // the point the camera looks at and focuses on
|
|
101
|
-
const CAM_Z0 = 9.0,
|
|
102
|
-
CAM_Z1 = 3.4; // dolly path
|
|
103
|
-
const CAM_Y0 = 1.74,
|
|
104
|
-
CAM_Y1 = 1.45;
|
|
105
|
-
const FOG_DENSITY = 0.026; // light morning haze
|
|
106
|
-
// light levels at full reveal (physical units: lux for the sun, candela for point lights)
|
|
107
|
-
const K = {
|
|
108
|
-
sun: 3.8,
|
|
109
|
-
hemi: 1.05,
|
|
110
|
-
env: 0.4,
|
|
111
|
-
pump: 0.42,
|
|
112
|
-
glow: 1.6,
|
|
113
|
-
ap0: 0.00025,
|
|
114
|
-
ap1: 0.0013,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/* ---------- deterministic randomness ---------- */
|
|
118
|
-
function mulberry32(a) {
|
|
119
|
-
return function () {
|
|
120
|
-
a |= 0;
|
|
121
|
-
a = (a + 0x6d2b79f5) | 0;
|
|
122
|
-
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
123
|
-
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
124
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
const rng = mulberry32(31102026);
|
|
128
|
-
const rand = (a = 0, b = 1) => a + (b - a) * rng();
|
|
129
|
-
function hash1(n) {
|
|
130
|
-
const x = Math.sin(n * 127.1 + 311.7) * 43758.5453;
|
|
131
|
-
return x - Math.floor(x);
|
|
132
|
-
}
|
|
133
|
-
function noise1(x) {
|
|
134
|
-
const i = Math.floor(x),
|
|
135
|
-
f = x - i,
|
|
136
|
-
u = f * f * (3 - 2 * f);
|
|
137
|
-
return hash1(i) * (1 - u) + hash1(i + 1) * u;
|
|
138
|
-
}
|
|
139
|
-
function fbm1(x) {
|
|
140
|
-
return 0.5 * noise1(x) + 0.3 * noise1(x * 2.03 + 7.1) + 0.2 * noise1(x * 4.11 + 3.3);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/* ---------- easing ---------- */
|
|
144
|
-
const clamp = (x, a = 0, b = 1) => Math.min(b, Math.max(a, x));
|
|
145
|
-
const lerp = (a, b, t) => a + (b - a) * t;
|
|
146
|
-
const smooth = (e0, e1, x) => {
|
|
147
|
-
const t = clamp((x - e0) / (e1 - e0));
|
|
148
|
-
return t * t * (3 - 2 * t);
|
|
149
|
-
};
|
|
150
|
-
const smoother = (e0, e1, x) => {
|
|
151
|
-
const t = clamp((x - e0) / (e1 - e0));
|
|
152
|
-
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
153
|
-
};
|
|
154
|
-
// long-tailed ease: very gentle start and an extremely soft landing
|
|
155
|
-
function easeLong(u) {
|
|
156
|
-
u = clamp(u);
|
|
157
|
-
const p = 2.3,
|
|
158
|
-
a = Math.pow(u, p),
|
|
159
|
-
b = Math.pow(1 - u, p);
|
|
160
|
-
return a / (a + b);
|
|
161
|
-
}
|
|
162
|
-
// glide profile: speed ~ u^1.6 (1-u)^2.6, integrated once (a drifting body that bleeds speed slowly)
|
|
163
|
-
const TRAVEL = (() => {
|
|
164
|
-
const N = 2048,
|
|
165
|
-
arr = new Float32Array(N + 1);
|
|
166
|
-
let s = 0;
|
|
167
|
-
for (let i = 1; i <= N; i++) {
|
|
168
|
-
const u = (i - 0.5) / N;
|
|
169
|
-
s += Math.pow(u, 1.6) * Math.pow(1 - u, 2.6);
|
|
170
|
-
arr[i] = s;
|
|
171
|
-
}
|
|
172
|
-
for (let i = 0; i <= N; i++) arr[i] /= s;
|
|
173
|
-
return arr;
|
|
174
|
-
})();
|
|
175
|
-
function travel(u) {
|
|
176
|
-
u = clamp(u);
|
|
177
|
-
const f = u * 2048,
|
|
178
|
-
i = Math.min(2047, Math.floor(f));
|
|
179
|
-
return lerp(TRAVEL[i], TRAVEL[i + 1], f - i);
|
|
180
|
-
}
|
|
181
|
-
function devilZ(t) {
|
|
182
|
-
return lerp(Z_START, Z_END, travel((t - T.devilGo) / (T.devilArrive - T.devilGo)));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/* ---------- sound: UI-style, procedural, scheduled on the same timeline ----------
|
|
186
|
-
0.8 - 16.9 a very quiet cinematic bed: low drone, dark pad, soft wind
|
|
187
|
-
0.8 - 16.9 slight wind, gusting and drifting across the speakers
|
|
188
|
-
1.5 a ghostly, theremin-like wail drifts across
|
|
189
|
-
8.0 the wail again, drifting back the other way
|
|
190
|
-
5.2, 10.6, 15.0 bats flit past, squeaking softly
|
|
191
|
-
11.8 a wolf howls far off to the right, very quietly
|
|
192
|
-
16.9 power-off blip, then silence
|
|
193
|
-
------------------------------------------------------------------------- */
|
|
194
|
-
const sndCache = new WeakMap();
|
|
195
|
-
function noiseBuf(ctx, seconds, kind, seed) {
|
|
196
|
-
const sr = ctx.sampleRate,
|
|
197
|
-
fade = Math.floor(0.05 * sr),
|
|
198
|
-
n = Math.floor(seconds * sr) + fade,
|
|
199
|
-
b = ctx.createBuffer(2, n, sr);
|
|
200
|
-
for (let ch = 0; ch < 2; ch++) {
|
|
201
|
-
const r = mulberry32(seed + ch * 7919),
|
|
202
|
-
d = b.getChannelData(ch);
|
|
203
|
-
let b0 = 0,
|
|
204
|
-
b1 = 0,
|
|
205
|
-
b2 = 0,
|
|
206
|
-
last = 0;
|
|
207
|
-
for (let i = 0; i < n; i++) {
|
|
208
|
-
const w = r() * 2 - 1;
|
|
209
|
-
if (kind === 'white') d[i] = w * 0.7;
|
|
210
|
-
else if (kind === 'pink') {
|
|
211
|
-
b0 = 0.99765 * b0 + w * 0.099046;
|
|
212
|
-
b1 = 0.963 * b1 + w * 0.2965164;
|
|
213
|
-
b2 = 0.57 * b2 + w * 1.0526913;
|
|
214
|
-
d[i] = (b0 + b1 + b2 + w * 0.1848) * 0.2;
|
|
215
|
-
} else {
|
|
216
|
-
last = (last + 0.02 * w) / 1.02;
|
|
217
|
-
d[i] = last * 3.2;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
// seamless loop: the head fades in from the tail that will precede it
|
|
221
|
-
for (let i = 0; i < fade; i++) {
|
|
222
|
-
const k = i / fade;
|
|
223
|
-
d[i] = d[i] * k + d[n - fade + i] * (1 - k);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
b.__loopEnd = (n - fade) / sr;
|
|
227
|
-
return b;
|
|
228
|
-
}
|
|
229
|
-
function impulseBuf(ctx, seconds, seed) {
|
|
230
|
-
const sr = ctx.sampleRate,
|
|
231
|
-
n = Math.floor(seconds * sr),
|
|
232
|
-
b = ctx.createBuffer(2, n, sr),
|
|
233
|
-
pre = Math.floor(0.018 * sr);
|
|
234
|
-
for (let ch = 0; ch < 2; ch++) {
|
|
235
|
-
const r = mulberry32(seed + ch * 31),
|
|
236
|
-
d = b.getChannelData(ch);
|
|
237
|
-
let lp = 0;
|
|
238
|
-
for (let i = 0; i < n; i++) {
|
|
239
|
-
const x = i / n,
|
|
240
|
-
w = r() * 2 - 1;
|
|
241
|
-
lp += (w - lp) * (0.6 - 0.5 * x);
|
|
242
|
-
d[i] = i < pre ? 0 : lp * Math.pow(1 - x, 1.6) * Math.exp(-3.0 * x);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return b;
|
|
246
|
-
}
|
|
247
|
-
function buildSound(ctx, t0, dest) {
|
|
248
|
-
const S =
|
|
249
|
-
sndCache.get(ctx) ||
|
|
250
|
-
(() => {
|
|
251
|
-
const c = {
|
|
252
|
-
white: noiseBuf(ctx, 3, 'white', 101),
|
|
253
|
-
brown: noiseBuf(ctx, 8, 'brown', 303),
|
|
254
|
-
ir: impulseBuf(ctx, 1.1, 404),
|
|
255
|
-
};
|
|
256
|
-
sndCache.set(ctx, c);
|
|
257
|
-
return c;
|
|
258
|
-
})();
|
|
259
|
-
const master = ctx.createGain();
|
|
260
|
-
master.gain.value = 0;
|
|
261
|
-
master.gain.setValueAtTime(2.2, t0);
|
|
262
|
-
master.gain.setValueAtTime(2.2, t0 + T.out + 0.22);
|
|
263
|
-
master.gain.linearRampToValueAtTime(0, t0 + T.out + 0.25);
|
|
264
|
-
const comp = ctx.createDynamicsCompressor();
|
|
265
|
-
comp.threshold.value = -14;
|
|
266
|
-
comp.ratio.value = 3;
|
|
267
|
-
comp.attack.value = 0.004;
|
|
268
|
-
comp.release.value = 0.2;
|
|
269
|
-
master.connect(comp);
|
|
270
|
-
comp.connect(dest);
|
|
271
|
-
const verb = ctx.createConvolver();
|
|
272
|
-
verb.buffer = S.ir;
|
|
273
|
-
const verbOut = ctx.createGain();
|
|
274
|
-
verbOut.gain.value = 0.18;
|
|
275
|
-
verb.connect(verbOut).connect(master);
|
|
276
|
-
const bus = ctx.createGain();
|
|
277
|
-
bus.connect(master);
|
|
278
|
-
bus.connect(verb);
|
|
279
|
-
|
|
280
|
-
const tone = (t, f0, f1, dur, type = 'sine', amp = 0.2, pan = 0, attack = 0.004) => {
|
|
281
|
-
const o = ctx.createOscillator();
|
|
282
|
-
o.type = type;
|
|
283
|
-
o.frequency.setValueAtTime(f0, t0 + t);
|
|
284
|
-
if (f1 !== f0) o.frequency.exponentialRampToValueAtTime(f1, t0 + t + dur);
|
|
285
|
-
const g = ctx.createGain();
|
|
286
|
-
g.gain.value = 0;
|
|
287
|
-
g.gain.setValueAtTime(0, t0 + t);
|
|
288
|
-
g.gain.linearRampToValueAtTime(amp, t0 + t + attack);
|
|
289
|
-
g.gain.exponentialRampToValueAtTime(0.0001, t0 + t + dur);
|
|
290
|
-
const p = ctx.createStereoPanner();
|
|
291
|
-
p.pan.value = pan;
|
|
292
|
-
o.connect(g).connect(p).connect(bus);
|
|
293
|
-
o.start(t0 + t);
|
|
294
|
-
o.stop(t0 + t + dur + 0.02);
|
|
295
|
-
};
|
|
296
|
-
const whoosh = (t, dur, fA, fB, amp, pan0 = 0, pan1 = 0) => {
|
|
297
|
-
const s = ctx.createBufferSource();
|
|
298
|
-
s.buffer = S.white;
|
|
299
|
-
s.loop = true;
|
|
300
|
-
s.loopEnd = S.white.__loopEnd;
|
|
301
|
-
const bp = ctx.createBiquadFilter();
|
|
302
|
-
bp.type = 'bandpass';
|
|
303
|
-
bp.Q.value = 1.2;
|
|
304
|
-
bp.frequency.setValueAtTime(fA, t0 + t);
|
|
305
|
-
bp.frequency.exponentialRampToValueAtTime(fB, t0 + t + dur);
|
|
306
|
-
const g = ctx.createGain();
|
|
307
|
-
g.gain.value = 0;
|
|
308
|
-
g.gain.setValueAtTime(0, t0 + t);
|
|
309
|
-
g.gain.linearRampToValueAtTime(amp, t0 + t + dur * 0.45);
|
|
310
|
-
g.gain.exponentialRampToValueAtTime(0.0001, t0 + t + dur);
|
|
311
|
-
const p = ctx.createStereoPanner();
|
|
312
|
-
p.pan.setValueAtTime(pan0, t0 + t);
|
|
313
|
-
p.pan.linearRampToValueAtTime(pan1, t0 + t + dur);
|
|
314
|
-
s.connect(bp).connect(g).connect(p).connect(bus);
|
|
315
|
-
s.start(t0 + t, 0);
|
|
316
|
-
s.stop(t0 + t + dur + 0.02);
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
// cinematic bed, very low: fades up with the picture, swells slowly, cuts with the lights
|
|
320
|
-
{
|
|
321
|
-
const bed = ctx.createGain();
|
|
322
|
-
bed.connect(bus);
|
|
323
|
-
const lvl = (t) => smoother(0.8, 6.0, t) * (0.75 + 0.25 * smoother(8, 15.5, t));
|
|
324
|
-
const n = Math.ceil(T.out * 30),
|
|
325
|
-
env = new Float32Array(n);
|
|
326
|
-
for (let i = 0; i < n; i++) env[i] = 0.1 * lvl((T.out * i) / (n - 1)); // 0.1 keeps it far under everything else
|
|
327
|
-
bed.gain.value = 0;
|
|
328
|
-
bed.gain.setValueCurveAtTime(env, t0, T.out - 0.02);
|
|
329
|
-
bed.gain.linearRampToValueAtTime(0, t0 + T.out);
|
|
330
|
-
const stopAt = t0 + T.out + 0.05;
|
|
331
|
-
const osc = (type, f, det, amp, dest) => {
|
|
332
|
-
const o = ctx.createOscillator();
|
|
333
|
-
o.type = type;
|
|
334
|
-
o.frequency.value = f;
|
|
335
|
-
o.detune.value = det;
|
|
336
|
-
const g = ctx.createGain();
|
|
337
|
-
g.gain.value = amp;
|
|
338
|
-
o.connect(g).connect(dest);
|
|
339
|
-
o.start(t0);
|
|
340
|
-
o.stop(stopAt);
|
|
341
|
-
};
|
|
342
|
-
// low drone (A and E)
|
|
343
|
-
const droneLP = ctx.createBiquadFilter();
|
|
344
|
-
droneLP.type = 'lowpass';
|
|
345
|
-
droneLP.frequency.value = 320;
|
|
346
|
-
droneLP.connect(bed);
|
|
347
|
-
osc('sine', 55, 0, 0.05, droneLP);
|
|
348
|
-
osc('sine', 55.3, 0, 0.04, droneLP);
|
|
349
|
-
osc('triangle', 82.41, 0, 0.02, droneLP);
|
|
350
|
-
osc('triangle', 110, 3, 0.012, droneLP);
|
|
351
|
-
// dark A-minor pad that slowly opens
|
|
352
|
-
const padLP = ctx.createBiquadFilter();
|
|
353
|
-
padLP.type = 'lowpass';
|
|
354
|
-
padLP.Q.value = 0.8;
|
|
355
|
-
padLP.connect(bed);
|
|
356
|
-
const m = Math.ceil(T.out * 20),
|
|
357
|
-
cut = new Float32Array(m);
|
|
358
|
-
for (let i = 0; i < m; i++) {
|
|
359
|
-
const t = (T.out * i) / (m - 1);
|
|
360
|
-
cut[i] = 380 + 520 * smoother(4, 15.5, t) + 60 * Math.sin(t * 0.7);
|
|
361
|
-
}
|
|
362
|
-
padLP.frequency.value = cut[0];
|
|
363
|
-
padLP.frequency.setValueCurveAtTime(cut, t0, T.out);
|
|
364
|
-
[
|
|
365
|
-
[110, -6],
|
|
366
|
-
[110, 6],
|
|
367
|
-
[130.81, -5],
|
|
368
|
-
[130.81, 5],
|
|
369
|
-
[164.81, -4],
|
|
370
|
-
[164.81, 4],
|
|
371
|
-
].forEach(([f, d]) => osc('sawtooth', f, d, 0.006, padLP));
|
|
372
|
-
// soft wind
|
|
373
|
-
const wind = ctx.createBufferSource();
|
|
374
|
-
wind.buffer = S.brown;
|
|
375
|
-
wind.loop = true;
|
|
376
|
-
wind.loopEnd = S.brown.__loopEnd;
|
|
377
|
-
const windLP = ctx.createBiquadFilter();
|
|
378
|
-
windLP.type = 'lowpass';
|
|
379
|
-
windLP.Q.value = 0.5;
|
|
380
|
-
const wc = new Float32Array(m),
|
|
381
|
-
wg = new Float32Array(m);
|
|
382
|
-
for (let i = 0; i < m; i++) {
|
|
383
|
-
const t = (T.out * i) / (m - 1);
|
|
384
|
-
wc[i] = 260 + 380 * fbm1(t * 0.4 + 3.1);
|
|
385
|
-
wg[i] = 0.05 * (0.6 + 0.8 * (fbm1(t * 0.45 + 7.3) - 0.3));
|
|
386
|
-
}
|
|
387
|
-
windLP.frequency.value = wc[0];
|
|
388
|
-
windLP.frequency.setValueCurveAtTime(wc, t0, T.out);
|
|
389
|
-
const windG = ctx.createGain();
|
|
390
|
-
windG.gain.value = wg[0];
|
|
391
|
-
windG.gain.setValueCurveAtTime(wg, t0, T.out);
|
|
392
|
-
wind.connect(windLP).connect(windG).connect(bed);
|
|
393
|
-
wind.start(t0, 1.3);
|
|
394
|
-
wind.stop(stopAt);
|
|
395
|
-
}
|
|
396
|
-
// slight wind in the background: a low rush plus an airy gusting top, drifting left and right
|
|
397
|
-
{
|
|
398
|
-
const WIND = 0.07; // overall level: slight
|
|
399
|
-
const m = Math.ceil(T.out * 30),
|
|
400
|
-
at = (i) => (T.out * i) / (m - 1);
|
|
401
|
-
const curve = (fn) => {
|
|
402
|
-
const c = new Float32Array(m);
|
|
403
|
-
for (let i = 0; i < m; i++) c[i] = fn(at(i));
|
|
404
|
-
return c;
|
|
405
|
-
};
|
|
406
|
-
const gust = (t) => 0.55 + 0.75 * Math.max(0, fbm1(t * 0.5 + 11.7) - 0.25);
|
|
407
|
-
const level = (t) => smoother(0.8, 5.0, t) * gust(t);
|
|
408
|
-
const out = ctx.createGain();
|
|
409
|
-
out.gain.value = 0;
|
|
410
|
-
out.gain.setValueCurveAtTime(
|
|
411
|
-
curve((t) => WIND * level(t)),
|
|
412
|
-
t0,
|
|
413
|
-
T.out - 0.02
|
|
414
|
-
);
|
|
415
|
-
out.gain.linearRampToValueAtTime(0, t0 + T.out);
|
|
416
|
-
const pan = ctx.createStereoPanner();
|
|
417
|
-
pan.pan.value = 0;
|
|
418
|
-
pan.pan.setValueCurveAtTime(
|
|
419
|
-
curve((t) => 0.55 * Math.sin(t * 0.33 + 0.8)),
|
|
420
|
-
t0,
|
|
421
|
-
T.out
|
|
422
|
-
);
|
|
423
|
-
out.connect(pan).connect(bus);
|
|
424
|
-
const src = (buf, offset) => {
|
|
425
|
-
const s = ctx.createBufferSource();
|
|
426
|
-
s.buffer = buf;
|
|
427
|
-
s.loop = true;
|
|
428
|
-
s.loopEnd = buf.__loopEnd;
|
|
429
|
-
s.start(t0, offset);
|
|
430
|
-
s.stop(t0 + T.out + 0.05);
|
|
431
|
-
return s;
|
|
432
|
-
};
|
|
433
|
-
// low rush
|
|
434
|
-
const lp = ctx.createBiquadFilter();
|
|
435
|
-
lp.type = 'lowpass';
|
|
436
|
-
lp.Q.value = 0.6;
|
|
437
|
-
lp.frequency.value = 500;
|
|
438
|
-
lp.frequency.setValueCurveAtTime(
|
|
439
|
-
curve((t) => 380 + 520 * fbm1(t * 0.5 + 11.7)),
|
|
440
|
-
t0,
|
|
441
|
-
T.out
|
|
442
|
-
);
|
|
443
|
-
src(S.brown, 4.2).connect(lp).connect(out);
|
|
444
|
-
// airy, whistling top that rises with each gust
|
|
445
|
-
const bp = ctx.createBiquadFilter();
|
|
446
|
-
bp.type = 'bandpass';
|
|
447
|
-
bp.Q.value = 1.6;
|
|
448
|
-
bp.frequency.value = 900;
|
|
449
|
-
bp.frequency.setValueCurveAtTime(
|
|
450
|
-
curve((t) => 700 + 900 * fbm1(t * 0.5 + 11.7) + 150 * Math.sin(t * 0.9)),
|
|
451
|
-
t0,
|
|
452
|
-
T.out
|
|
453
|
-
);
|
|
454
|
-
const airG = ctx.createGain();
|
|
455
|
-
airG.gain.value = 0.35;
|
|
456
|
-
src(S.white, 1.1).connect(bp).connect(airG).connect(out);
|
|
457
|
-
}
|
|
458
|
-
// bats: a few quiet passes of high squeaks and soft wing flutter, flying across the stereo field
|
|
459
|
-
const batPass = (tStart, dur, panFrom, panTo, seed, amp) => {
|
|
460
|
-
const R = mulberry32(seed);
|
|
461
|
-
for (let t = tStart + 0.05; t < tStart + dur - 0.05;) {
|
|
462
|
-
const u = (t - tStart) / dur,
|
|
463
|
-
near = Math.sin(Math.PI * u); // louder as it passes closest
|
|
464
|
-
const pan = lerp(panFrom, panTo, u),
|
|
465
|
-
f0 = 7000 + R() * 2500;
|
|
466
|
-
tone(
|
|
467
|
-
t,
|
|
468
|
-
f0,
|
|
469
|
-
f0 * (0.45 + R() * 0.1),
|
|
470
|
-
0.018 + R() * 0.02,
|
|
471
|
-
'sine',
|
|
472
|
-
amp * (0.4 + 0.6 * near) * (0.6 + 0.4 * R()),
|
|
473
|
-
pan,
|
|
474
|
-
0.002
|
|
475
|
-
);
|
|
476
|
-
t += R() < 0.3 ? 0.035 + R() * 0.03 : 0.09 + R() * 0.16; // short clusters, then gaps
|
|
477
|
-
}
|
|
478
|
-
const n = ctx.createBufferSource();
|
|
479
|
-
n.buffer = S.white;
|
|
480
|
-
n.loop = true;
|
|
481
|
-
n.loopEnd = S.white.__loopEnd;
|
|
482
|
-
const bp = ctx.createBiquadFilter();
|
|
483
|
-
bp.type = 'bandpass';
|
|
484
|
-
bp.frequency.value = 900;
|
|
485
|
-
bp.Q.value = 0.9;
|
|
486
|
-
const g = ctx.createGain(),
|
|
487
|
-
k = Math.ceil(dur * 200),
|
|
488
|
-
env = new Float32Array(k);
|
|
489
|
-
for (let i = 0; i < k; i++) {
|
|
490
|
-
const u = i / (k - 1);
|
|
491
|
-
env[i] =
|
|
492
|
-
amp *
|
|
493
|
-
0.9 *
|
|
494
|
-
Math.sin(Math.PI * u) *
|
|
495
|
-
Math.pow(0.5 + 0.5 * Math.sin(u * dur * 2 * Math.PI * 11), 3);
|
|
496
|
-
}
|
|
497
|
-
g.gain.value = 0;
|
|
498
|
-
g.gain.setValueCurveAtTime(env, t0 + tStart, dur);
|
|
499
|
-
const pn = ctx.createStereoPanner();
|
|
500
|
-
pn.pan.setValueAtTime(panFrom, t0 + tStart);
|
|
501
|
-
pn.pan.linearRampToValueAtTime(panTo, t0 + tStart + dur);
|
|
502
|
-
n.connect(bp).connect(g).connect(pn).connect(bus);
|
|
503
|
-
n.start(t0 + tStart, R() * 2);
|
|
504
|
-
n.stop(t0 + tStart + dur + 0.02);
|
|
505
|
-
};
|
|
506
|
-
batPass(5.2, 1.6, -0.8, 0.8, 4101, 0.035);
|
|
507
|
-
batPass(10.6, 1.1, 0.8, -0.5, 4202, 0.03);
|
|
508
|
-
batPass(15.0, 1.3, -0.6, 0.9, 4303, 0.028);
|
|
509
|
-
// a voice whose pitch and level follow curves over its life
|
|
510
|
-
const voiceCurve = (t, dur, freqFn, ampFn, type = 'sine', pan = 0, lpf = 4000) => {
|
|
511
|
-
const n = Math.max(8, Math.ceil(dur * 120)),
|
|
512
|
-
fq = new Float32Array(n),
|
|
513
|
-
am = new Float32Array(n);
|
|
514
|
-
for (let i = 0; i < n; i++) {
|
|
515
|
-
const u = i / (n - 1);
|
|
516
|
-
fq[i] = freqFn(u);
|
|
517
|
-
am[i] = ampFn(u);
|
|
518
|
-
}
|
|
519
|
-
am[n - 1] = 0;
|
|
520
|
-
const o = ctx.createOscillator();
|
|
521
|
-
o.type = type;
|
|
522
|
-
o.frequency.value = fq[0];
|
|
523
|
-
o.frequency.setValueCurveAtTime(fq, t0 + t, dur);
|
|
524
|
-
const g = ctx.createGain();
|
|
525
|
-
g.gain.value = 0;
|
|
526
|
-
g.gain.setValueCurveAtTime(am, t0 + t, dur);
|
|
527
|
-
const lp = ctx.createBiquadFilter();
|
|
528
|
-
lp.type = 'lowpass';
|
|
529
|
-
lp.frequency.value = lpf;
|
|
530
|
-
const p = ctx.createStereoPanner();
|
|
531
|
-
if (Array.isArray(pan)) {
|
|
532
|
-
p.pan.setValueAtTime(pan[0], t0 + t);
|
|
533
|
-
p.pan.linearRampToValueAtTime(pan[1], t0 + t + dur);
|
|
534
|
-
} else p.pan.value = pan;
|
|
535
|
-
o.connect(lp).connect(g).connect(p).connect(bus);
|
|
536
|
-
o.start(t0 + t);
|
|
537
|
-
o.stop(t0 + t + dur + 0.02);
|
|
538
|
-
};
|
|
539
|
-
// ghostly wail: theremin-like rise and fall with vibrato, drifting across, low
|
|
540
|
-
const wail = (t, pan) => {
|
|
541
|
-
const vib = (u) => 1 + 0.03 * Math.sin(u * 2.3 * 2 * Math.PI * 5.5),
|
|
542
|
-
shape = (u) => Math.sin(Math.PI * Math.min(1, u * 1.3));
|
|
543
|
-
voiceCurve(
|
|
544
|
-
t,
|
|
545
|
-
2.3,
|
|
546
|
-
(u) => (520 + 380 * shape(u)) * vib(u),
|
|
547
|
-
(u) => 0.045 * Math.sin(Math.PI * u),
|
|
548
|
-
'sine',
|
|
549
|
-
pan,
|
|
550
|
-
3000
|
|
551
|
-
);
|
|
552
|
-
voiceCurve(
|
|
553
|
-
t,
|
|
554
|
-
2.3,
|
|
555
|
-
(u) => (260 + 190 * shape(u)) * vib(u),
|
|
556
|
-
(u) => 0.018 * Math.sin(Math.PI * u),
|
|
557
|
-
'triangle',
|
|
558
|
-
pan,
|
|
559
|
-
1500
|
|
560
|
-
);
|
|
561
|
-
};
|
|
562
|
-
wail(1.5, [0.7, -0.7]); // as the graveyard fades up, right to left
|
|
563
|
-
wail(8.0, [-0.7, 0.7]); // again in the middle, left to right
|
|
564
|
-
// wolf howl, far off to the right, very low
|
|
565
|
-
voiceCurve(
|
|
566
|
-
11.8,
|
|
567
|
-
2.4,
|
|
568
|
-
(u) =>
|
|
569
|
-
(u < 0.25 ? 300 + 320 * (u / 0.25) : u < 0.7 ? 620 : 620 - 270 * ((u - 0.7) / 0.3)) *
|
|
570
|
-
(1 + 0.012 * Math.sin(u * 2.4 * 2 * Math.PI * 6)),
|
|
571
|
-
(u) => 0.025 * Math.min(1, u * 5) * Math.min(1, (1 - u) * 4),
|
|
572
|
-
'sine',
|
|
573
|
-
0.75,
|
|
574
|
-
1600
|
|
575
|
-
);
|
|
576
|
-
// lights out: power off
|
|
577
|
-
tone(T.out, 880, 90, 0.22, 'sine', 0.2);
|
|
578
|
-
tone(T.out, 1760, 180, 0.12, 'triangle', 0.06);
|
|
579
|
-
tone(T.out, 4000, 4000, 0.008, 'square', 0.03);
|
|
580
|
-
}
|
|
581
|
-
function wavBase64(buf) {
|
|
582
|
-
const ch = buf.numberOfChannels,
|
|
583
|
-
n = buf.length,
|
|
584
|
-
sr = buf.sampleRate,
|
|
585
|
-
bytes = new Uint8Array(44 + n * ch * 2),
|
|
586
|
-
v = new DataView(bytes.buffer);
|
|
587
|
-
const str = (o, s) => {
|
|
588
|
-
for (let i = 0; i < s.length; i++) v.setUint8(o + i, s.charCodeAt(i));
|
|
589
|
-
};
|
|
590
|
-
str(0, 'RIFF');
|
|
591
|
-
v.setUint32(4, 36 + n * ch * 2, true);
|
|
592
|
-
str(8, 'WAVE');
|
|
593
|
-
str(12, 'fmt ');
|
|
594
|
-
v.setUint32(16, 16, true);
|
|
595
|
-
v.setUint16(20, 1, true);
|
|
596
|
-
v.setUint16(22, ch, true);
|
|
597
|
-
v.setUint32(24, sr, true);
|
|
598
|
-
v.setUint32(28, sr * ch * 2, true);
|
|
599
|
-
v.setUint16(32, ch * 2, true);
|
|
600
|
-
v.setUint16(34, 16, true);
|
|
601
|
-
str(36, 'data');
|
|
602
|
-
v.setUint32(40, n * ch * 2, true);
|
|
603
|
-
const data = [];
|
|
604
|
-
for (let c = 0; c < ch; c++) data.push(buf.getChannelData(c));
|
|
605
|
-
let o = 44;
|
|
606
|
-
for (let i = 0; i < n; i++)
|
|
607
|
-
for (let c = 0; c < ch; c++) {
|
|
608
|
-
const s = Math.max(-1, Math.min(1, data[c][i]));
|
|
609
|
-
v.setInt16(o, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
|
610
|
-
o += 2;
|
|
611
|
-
}
|
|
612
|
-
let bin = '';
|
|
613
|
-
for (let i = 0; i < bytes.length; i += 0x8000)
|
|
614
|
-
bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
|
|
615
|
-
return btoa(bin);
|
|
616
|
-
}
|
|
617
|
-
async function renderSoundtrack() {
|
|
618
|
-
const ctx = new OfflineAudioContext(2, Math.ceil(T.end * 48000), 48000);
|
|
619
|
-
buildSound(ctx, 0, ctx.destination);
|
|
620
|
-
return wavBase64(await ctx.startRendering());
|
|
621
|
-
}
|
|
622
|
-
/* ---------- end sound ---------- */
|
|
623
|
-
|
|
624
|
-
/* ---------- renderer ---------- */
|
|
625
|
-
let W = RENDER ? 1920 : innerWidth,
|
|
626
|
-
H = RENDER ? 1080 : innerHeight;
|
|
627
|
-
const renderer = new THREE.WebGLRenderer({
|
|
628
|
-
antialias: true,
|
|
629
|
-
powerPreference: 'high-performance',
|
|
630
|
-
preserveDrawingBuffer: RENDER,
|
|
631
|
-
});
|
|
632
|
-
let pixelRatio = RENDER ? 1 : Math.min(devicePixelRatio, 1.5);
|
|
633
|
-
renderer.setPixelRatio(pixelRatio);
|
|
634
|
-
renderer.setSize(W, H);
|
|
635
|
-
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
636
|
-
renderer.toneMappingExposure = 1.0;
|
|
637
|
-
renderer.shadowMap.enabled = true;
|
|
638
|
-
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
639
|
-
renderer.setClearColor(0x000000, 1);
|
|
640
|
-
document.body.appendChild(renderer.domElement);
|
|
641
|
-
const maxAniso = renderer.capabilities.getMaxAnisotropy();
|
|
642
|
-
|
|
643
|
-
const scene = new THREE.Scene();
|
|
644
|
-
const FOG_BASE = new THREE.Color(0xb3c2d2); // pale blue morning haze
|
|
645
|
-
scene.fog = new THREE.FogExp2(0x000000, FOG_DENSITY);
|
|
646
|
-
scene.background = new THREE.Color(0x000000);
|
|
647
|
-
const pmrem = new THREE.PMREMGenerator(renderer);
|
|
648
|
-
scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;
|
|
649
|
-
scene.environmentIntensity = 0;
|
|
650
|
-
|
|
651
|
-
const camera = new THREE.PerspectiveCamera(32, W / H, 0.1, 100);
|
|
652
|
-
camera.position.set(0, CAM_Y0, CAM_Z0);
|
|
653
|
-
|
|
654
|
-
/* ---------- procedural textures (seeded, tileable) ---------- */
|
|
655
|
-
function tileNoise(size, cells, seed) {
|
|
656
|
-
const r = mulberry32(seed),
|
|
657
|
-
g = new Float32Array(cells * cells);
|
|
658
|
-
for (let i = 0; i < g.length; i++) g[i] = r();
|
|
659
|
-
const out = new Float32Array(size * size);
|
|
660
|
-
for (let y = 0; y < size; y++) {
|
|
661
|
-
const fy = (y / size) * cells,
|
|
662
|
-
iy = Math.floor(fy),
|
|
663
|
-
ty = fy - iy,
|
|
664
|
-
sy = ty * ty * (3 - 2 * ty),
|
|
665
|
-
y0 = iy % cells,
|
|
666
|
-
y1 = (iy + 1) % cells;
|
|
667
|
-
for (let x = 0; x < size; x++) {
|
|
668
|
-
const fx = (x / size) * cells,
|
|
669
|
-
ix = Math.floor(fx),
|
|
670
|
-
tx = fx - ix,
|
|
671
|
-
sx = tx * tx * (3 - 2 * tx),
|
|
672
|
-
x0 = ix % cells,
|
|
673
|
-
x1 = (ix + 1) % cells;
|
|
674
|
-
const a = g[y0 * cells + x0],
|
|
675
|
-
b = g[y0 * cells + x1],
|
|
676
|
-
c = g[y1 * cells + x0],
|
|
677
|
-
d = g[y1 * cells + x1];
|
|
678
|
-
out[y * size + x] = (a * (1 - sx) + b * sx) * (1 - sy) + (c * (1 - sx) + d * sx) * sy;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return out;
|
|
682
|
-
}
|
|
683
|
-
function fbmField(size, base, oct, seed) {
|
|
684
|
-
const acc = new Float32Array(size * size);
|
|
685
|
-
let amp = 0.5,
|
|
686
|
-
tot = 0;
|
|
687
|
-
for (let o = 0; o < oct; o++) {
|
|
688
|
-
const n = tileNoise(size, base << o, seed + o * 101);
|
|
689
|
-
for (let i = 0; i < acc.length; i++) acc[i] += n[i] * amp;
|
|
690
|
-
tot += amp;
|
|
691
|
-
amp *= 0.5;
|
|
692
|
-
}
|
|
693
|
-
for (let i = 0; i < acc.length; i++) acc[i] = clamp((acc[i] / tot - 0.5) * 2.3 + 0.5);
|
|
694
|
-
return acc;
|
|
695
|
-
}
|
|
696
|
-
function canvasTex(size, fill, srgb, repeat = 1) {
|
|
697
|
-
const c = document.createElement('canvas');
|
|
698
|
-
c.width = c.height = size;
|
|
699
|
-
const ctx = c.getContext('2d'),
|
|
700
|
-
img = ctx.createImageData(size, size);
|
|
701
|
-
for (let i = 0; i < size * size; i++) {
|
|
702
|
-
const p = fill(i);
|
|
703
|
-
img.data[i * 4] = p[0] * 255;
|
|
704
|
-
img.data[i * 4 + 1] = p[1] * 255;
|
|
705
|
-
img.data[i * 4 + 2] = p[2] * 255;
|
|
706
|
-
img.data[i * 4 + 3] = 255;
|
|
707
|
-
}
|
|
708
|
-
ctx.putImageData(img, 0, 0);
|
|
709
|
-
const t = new THREE.CanvasTexture(c);
|
|
710
|
-
t.wrapS = t.wrapT = THREE.RepeatWrapping;
|
|
711
|
-
t.repeat.set(repeat, repeat);
|
|
712
|
-
t.colorSpace = srgb ? THREE.SRGBColorSpace : THREE.NoColorSpace;
|
|
713
|
-
t.anisotropy = maxAniso;
|
|
714
|
-
return t;
|
|
715
|
-
}
|
|
716
|
-
const TS = 512;
|
|
717
|
-
const nA = fbmField(TS, 4, 6, 11),
|
|
718
|
-
nB = fbmField(TS, 8, 4, 23),
|
|
719
|
-
nC = fbmField(TS, 32, 3, 37),
|
|
720
|
-
nD = fbmField(TS, 4, 5, 53);
|
|
721
|
-
|
|
722
|
-
const stoneMap = canvasTex(
|
|
723
|
-
TS,
|
|
724
|
-
(i) => {
|
|
725
|
-
const base = 0.34 + 0.2 * nA[i] + 0.07 * (nC[i] - 0.5);
|
|
726
|
-
let r = base,
|
|
727
|
-
g = base * 0.985,
|
|
728
|
-
b = base * 0.95;
|
|
729
|
-
const lichen = smooth(0.62, 0.85, nB[i]) * 0.38;
|
|
730
|
-
r = lerp(r, 0.24, lichen);
|
|
731
|
-
g = lerp(g, 0.27, lichen);
|
|
732
|
-
b = lerp(b, 0.15, lichen);
|
|
733
|
-
const pit = smooth(0.68, 0.88, nC[i]) * 0.22;
|
|
734
|
-
return [r * (1 - pit), g * (1 - pit), b * (1 - pit)];
|
|
735
|
-
},
|
|
736
|
-
true,
|
|
737
|
-
1.7
|
|
738
|
-
);
|
|
739
|
-
const stoneRough = canvasTex(
|
|
740
|
-
TS,
|
|
741
|
-
(i) => {
|
|
742
|
-
const v = 0.7 + 0.28 * nB[i];
|
|
743
|
-
return [v, v, v];
|
|
744
|
-
},
|
|
745
|
-
false,
|
|
746
|
-
1.7
|
|
747
|
-
);
|
|
748
|
-
const stoneBump = canvasTex(
|
|
749
|
-
TS,
|
|
750
|
-
(i) => {
|
|
751
|
-
const v = 0.6 * nC[i] + 0.4 * nA[i];
|
|
752
|
-
return [v, v, v];
|
|
753
|
-
},
|
|
754
|
-
false,
|
|
755
|
-
1.7
|
|
756
|
-
);
|
|
757
|
-
const soilMap = canvasTex(
|
|
758
|
-
TS,
|
|
759
|
-
(i) => {
|
|
760
|
-
const a = nD[i],
|
|
761
|
-
c = nC[i];
|
|
762
|
-
let r = 0.13 + 0.07 * a,
|
|
763
|
-
g = 0.105 + 0.055 * a,
|
|
764
|
-
b = 0.085 + 0.04 * a;
|
|
765
|
-
const leaf = smooth(0.62, 0.8, nB[i]) * 0.6;
|
|
766
|
-
r = lerp(r, 0.24, leaf);
|
|
767
|
-
g = lerp(g, 0.17, leaf);
|
|
768
|
-
b = lerp(b, 0.09, leaf);
|
|
769
|
-
const k = 0.85 + 0.3 * c;
|
|
770
|
-
return [r * k, g * k, b * k];
|
|
771
|
-
},
|
|
772
|
-
true,
|
|
773
|
-
1
|
|
774
|
-
);
|
|
775
|
-
soilMap.repeat.set(22, 34);
|
|
776
|
-
const soilBump = canvasTex(
|
|
777
|
-
TS,
|
|
778
|
-
(i) => {
|
|
779
|
-
const v = 0.55 * nC[i] + 0.45 * nB[i];
|
|
780
|
-
return [v, v, v];
|
|
781
|
-
},
|
|
782
|
-
false,
|
|
783
|
-
1
|
|
784
|
-
);
|
|
785
|
-
soilBump.repeat.set(22, 34);
|
|
786
|
-
const fabricBump = canvasTex(
|
|
787
|
-
TS,
|
|
788
|
-
(i) => {
|
|
789
|
-
const x = (i % TS) / TS;
|
|
790
|
-
const v =
|
|
791
|
-
0.5 + 0.38 * Math.sin(Math.PI * 2 * (x * 11) + 3.2 * nA[i]) + 0.12 * (nC[i] - 0.5);
|
|
792
|
-
return [v, v, v];
|
|
793
|
-
},
|
|
794
|
-
false,
|
|
795
|
-
1
|
|
796
|
-
);
|
|
797
|
-
/* ---------- materials ---------- */
|
|
798
|
-
const P = (o) => new THREE.MeshPhysicalMaterial(o);
|
|
799
|
-
const stoneMats = [0xbdb8ae, 0xa8a49c, 0xc9c2b4, 0x9a9890].map((c, k) =>
|
|
800
|
-
P({
|
|
801
|
-
color: c,
|
|
802
|
-
map: stoneMap,
|
|
803
|
-
roughnessMap: stoneRough,
|
|
804
|
-
roughness: 1.0,
|
|
805
|
-
bumpMap: stoneBump,
|
|
806
|
-
bumpScale: 0.5 + k * 0.1,
|
|
807
|
-
metalness: 0,
|
|
808
|
-
specularIntensity: 0.35,
|
|
809
|
-
envMapIntensity: 0.6,
|
|
810
|
-
})
|
|
811
|
-
);
|
|
812
|
-
const groundMat = P({
|
|
813
|
-
color: 0xffffff,
|
|
814
|
-
vertexColors: true,
|
|
815
|
-
map: soilMap,
|
|
816
|
-
roughness: 0.97,
|
|
817
|
-
bumpMap: soilBump,
|
|
818
|
-
bumpScale: 3.0,
|
|
819
|
-
specularIntensity: 0.25,
|
|
820
|
-
});
|
|
821
|
-
const barkMat = P({
|
|
822
|
-
color: 0x3a322b,
|
|
823
|
-
roughness: 0.93,
|
|
824
|
-
bumpMap: stoneBump,
|
|
825
|
-
bumpScale: 4.0,
|
|
826
|
-
specularIntensity: 0.2,
|
|
827
|
-
});
|
|
828
|
-
const ironMat = P({
|
|
829
|
-
color: 0x1d1c1b,
|
|
830
|
-
metalness: 0.85,
|
|
831
|
-
roughness: 0.58,
|
|
832
|
-
envMapIntensity: 0.8,
|
|
833
|
-
});
|
|
834
|
-
const grassMat = P({
|
|
835
|
-
color: 0xffffff,
|
|
836
|
-
roughness: 0.85,
|
|
837
|
-
specularIntensity: 0.3,
|
|
838
|
-
sheen: 0.4,
|
|
839
|
-
sheenColor: 0x8a8460,
|
|
840
|
-
sheenRoughness: 0.6,
|
|
841
|
-
});
|
|
842
|
-
|
|
843
|
-
/* ---------- terrain ---------- */
|
|
844
|
-
function groundH(x, z) {
|
|
845
|
-
const m = smooth(0.9, 2.4, Math.abs(x));
|
|
846
|
-
const h =
|
|
847
|
-
0.1 * Math.sin(0.33 * x + 0.21 * z) * Math.sin(0.27 * z - 0.13 * x + 1.0) +
|
|
848
|
-
0.05 * Math.sin(1.13 * x + 0.6) * Math.cos(0.87 * z + 0.3) +
|
|
849
|
-
0.025 * Math.sin(2.7 * x + 1.9 * z);
|
|
850
|
-
const hill = 1.4 * smooth(-26, -70, z) * (0.6 + 0.4 * Math.sin(0.13 * x + 0.8));
|
|
851
|
-
return h * m - 0.015 * (1 - m) + hill;
|
|
852
|
-
}
|
|
853
|
-
{
|
|
854
|
-
const g = new THREE.PlaneGeometry(76, 110, 152, 220);
|
|
855
|
-
g.rotateX(-Math.PI / 2);
|
|
856
|
-
g.translate(0, 0, -35);
|
|
857
|
-
const pos = g.attributes.position,
|
|
858
|
-
col = new Float32Array(pos.count * 3);
|
|
859
|
-
for (let i = 0; i < pos.count; i++) {
|
|
860
|
-
const x = pos.getX(i),
|
|
861
|
-
z = pos.getZ(i);
|
|
862
|
-
pos.setY(i, groundH(x, z));
|
|
863
|
-
const path = 1 - smooth(0.5, 1.6, Math.abs(x));
|
|
864
|
-
const damp = 0.5 + 0.5 * Math.sin(x * 0.7 + z * 0.4) * Math.sin(z * 0.23);
|
|
865
|
-
col[i * 3] = lerp(0.82 + 0.1 * damp, 1.18, path);
|
|
866
|
-
col[i * 3 + 1] = lerp(0.86 + 0.08 * damp, 1.1, path);
|
|
867
|
-
col[i * 3 + 2] = lerp(0.8, 1.02, path);
|
|
868
|
-
}
|
|
869
|
-
g.setAttribute('color', new THREE.BufferAttribute(col, 3));
|
|
870
|
-
g.computeVertexNormals();
|
|
871
|
-
const ground = new THREE.Mesh(g, groundMat);
|
|
872
|
-
ground.receiveShadow = true;
|
|
873
|
-
scene.add(ground);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
/* ---------- sky dome: morning blue overhead, hazy at the horizon, warm around a low sun ---------- */
|
|
877
|
-
const SUN_POS = new THREE.Vector3(14, 9, -40); // low, upper right of frame, behind the monument
|
|
878
|
-
const sunDir = SUN_POS.clone().normalize();
|
|
879
|
-
const ZENITH = new THREE.Color(0x6f9ccf),
|
|
880
|
-
SUN_TINT = new THREE.Color(0xffc283);
|
|
881
|
-
const skyMat = new THREE.ShaderMaterial({
|
|
882
|
-
side: THREE.BackSide,
|
|
883
|
-
depthWrite: false,
|
|
884
|
-
fog: false,
|
|
885
|
-
uniforms: {
|
|
886
|
-
uFog: { value: new THREE.Color(0) },
|
|
887
|
-
uZenith: { value: new THREE.Color(0) },
|
|
888
|
-
uSunCol: { value: new THREE.Color(0) },
|
|
889
|
-
uSun: { value: sunDir },
|
|
890
|
-
},
|
|
891
|
-
vertexShader: \`varying vec3 vDir; void main(){ vec4 w = modelMatrix*vec4(position,1.0); vDir = w.xyz - cameraPosition; gl_Position = projectionMatrix*viewMatrix*w; }\`,
|
|
892
|
-
fragmentShader: \`uniform vec3 uFog, uZenith, uSunCol, uSun; varying vec3 vDir;
|
|
893
|
-
void main(){ vec3 d = normalize(vDir); float h = d.y;
|
|
894
|
-
vec3 col = mix(uFog, uZenith, smoothstep(0.0, 0.55, h));
|
|
895
|
-
float m = max(dot(d, normalize(uSun)), 0.0);
|
|
896
|
-
col += uSunCol * (0.35*pow(m, 6.0) + 0.6*pow(m, 60.0)) * smoothstep(-0.1, 0.15, h); // warm glow around the sun
|
|
897
|
-
col += uSunCol * 4.0 * smoothstep(0.99955, 0.9998, m); // the sun's disc
|
|
898
|
-
gl_FragColor = vec4(col, 1.0); }\`,
|
|
899
|
-
});
|
|
900
|
-
const sky = new THREE.Mesh(new THREE.SphereGeometry(85, 32, 16), skyMat);
|
|
901
|
-
sky.renderOrder = -1;
|
|
902
|
-
scene.add(sky);
|
|
903
|
-
|
|
904
|
-
/* ---------- tombstones ---------- */
|
|
905
|
-
function stoneShape(w, h, type) {
|
|
906
|
-
const s = new THREE.Shape(),
|
|
907
|
-
hw = w / 2;
|
|
908
|
-
if (type === 0) {
|
|
909
|
-
s.moveTo(-hw, 0);
|
|
910
|
-
s.lineTo(hw, 0);
|
|
911
|
-
s.lineTo(hw, h - hw);
|
|
912
|
-
s.absarc(0, h - hw, hw, 0, Math.PI, false);
|
|
913
|
-
s.lineTo(-hw, 0);
|
|
914
|
-
} else if (type === 1) {
|
|
915
|
-
s.moveTo(-hw, 0);
|
|
916
|
-
s.lineTo(hw, 0);
|
|
917
|
-
s.lineTo(hw, h * 0.62);
|
|
918
|
-
s.quadraticCurveTo(hw, h * 0.9, 0, h);
|
|
919
|
-
s.quadraticCurveTo(-hw, h * 0.9, -hw, h * 0.62);
|
|
920
|
-
s.lineTo(-hw, 0);
|
|
921
|
-
} else if (type === 2) {
|
|
922
|
-
s.moveTo(-hw, 0);
|
|
923
|
-
s.lineTo(hw, 0);
|
|
924
|
-
s.lineTo(hw, h * 0.8);
|
|
925
|
-
s.quadraticCurveTo(hw * 0.95, h * 0.88, hw * 0.58, h * 0.88);
|
|
926
|
-
s.quadraticCurveTo(hw * 0.25, h * 0.88, 0, h);
|
|
927
|
-
s.quadraticCurveTo(-hw * 0.25, h * 0.88, -hw * 0.58, h * 0.88);
|
|
928
|
-
s.quadraticCurveTo(-hw * 0.95, h * 0.88, -hw, h * 0.8);
|
|
929
|
-
s.lineTo(-hw, 0);
|
|
930
|
-
} else {
|
|
931
|
-
const a = w * 0.15,
|
|
932
|
-
y0 = h * 0.6,
|
|
933
|
-
y1 = y0 + 2 * a;
|
|
934
|
-
s.moveTo(-a, 0);
|
|
935
|
-
s.lineTo(a, 0);
|
|
936
|
-
s.lineTo(a, y0);
|
|
937
|
-
s.lineTo(hw, y0);
|
|
938
|
-
s.lineTo(hw, y1);
|
|
939
|
-
s.lineTo(a, y1);
|
|
940
|
-
s.lineTo(a, h);
|
|
941
|
-
s.lineTo(-a, h);
|
|
942
|
-
s.lineTo(-a, y1);
|
|
943
|
-
s.lineTo(-hw, y1);
|
|
944
|
-
s.lineTo(-hw, y0);
|
|
945
|
-
s.lineTo(-a, y0);
|
|
946
|
-
s.lineTo(-a, 0);
|
|
947
|
-
}
|
|
948
|
-
return s;
|
|
949
|
-
}
|
|
950
|
-
const stoneGroup = new THREE.Group();
|
|
951
|
-
scene.add(stoneGroup);
|
|
952
|
-
function addTomb(x, z, scale) {
|
|
953
|
-
const type = rng() < 0.12 ? 3 : Math.floor(rand(0, 3)),
|
|
954
|
-
r = rng();
|
|
955
|
-
const w = (type === 3 ? rand(0.55, 0.75) : rand(0.5, 0.78)) * scale,
|
|
956
|
-
h = (type === 3 ? rand(1.0, 1.35) : rand(0.7, 1.15)) * scale,
|
|
957
|
-
d = rand(0.1, 0.17) * scale;
|
|
958
|
-
const geo = new THREE.ExtrudeGeometry(stoneShape(w, h, type), {
|
|
959
|
-
depth: d,
|
|
960
|
-
bevelEnabled: true,
|
|
961
|
-
bevelThickness: 0.014,
|
|
962
|
-
bevelSize: 0.014,
|
|
963
|
-
bevelSegments: 2,
|
|
964
|
-
curveSegments: 18,
|
|
965
|
-
});
|
|
966
|
-
geo.translate(0, 0, -d / 2);
|
|
967
|
-
const m = new THREE.Mesh(geo, stoneMats[Math.floor(r * 4)]);
|
|
968
|
-
const grp = new THREE.Group();
|
|
969
|
-
grp.add(m);
|
|
970
|
-
if (type !== 3 && rng() < 0.45) {
|
|
971
|
-
const pl = new THREE.Mesh(
|
|
972
|
-
new THREE.BoxGeometry(w * 1.18, 0.12 * scale, d * 2.2),
|
|
973
|
-
m.material
|
|
974
|
-
);
|
|
975
|
-
pl.position.y = 0.0;
|
|
976
|
-
m.position.y = 0.05 * scale;
|
|
977
|
-
grp.add(pl);
|
|
978
|
-
pl.castShadow = pl.receiveShadow = true;
|
|
979
|
-
}
|
|
980
|
-
grp.position.set(x, groundH(x, z) - rand(0.03, 0.1), z);
|
|
981
|
-
grp.rotation.set(
|
|
982
|
-
rand(-0.11, 0.07),
|
|
983
|
-
rand(-0.3, 0.3) + (x > 0 ? -0.08 : 0.08),
|
|
984
|
-
rand(-0.09, 0.09)
|
|
985
|
-
);
|
|
986
|
-
m.castShadow = m.receiveShadow = true;
|
|
987
|
-
stoneGroup.add(grp);
|
|
988
|
-
if (rng() < 0.5) {
|
|
989
|
-
const mound = new THREE.Mesh(
|
|
990
|
-
new THREE.SphereGeometry(1, 20, 12, 0, Math.PI * 2, 0, Math.PI / 2),
|
|
991
|
-
groundMat
|
|
992
|
-
);
|
|
993
|
-
mound.scale.set(w * 0.62, 0.11, 0.85);
|
|
994
|
-
mound.position.set(x + rand(-0.05, 0.05), groundH(x, z + 0.95) - 0.05, z + 0.95);
|
|
995
|
-
mound.receiveShadow = true;
|
|
996
|
-
scene.add(mound);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
function addObelisk(x, z) {
|
|
1000
|
-
const h = rand(1.5, 2.1),
|
|
1001
|
-
w = rand(0.32, 0.4),
|
|
1002
|
-
grp = new THREE.Group(),
|
|
1003
|
-
mat = stoneMats[Math.floor(rand(0, 4))];
|
|
1004
|
-
const base = new THREE.Mesh(new THREE.BoxGeometry(w * 1.7, 0.28, w * 1.7), mat);
|
|
1005
|
-
base.position.y = 0.1;
|
|
1006
|
-
const shaft = new THREE.Mesh(new THREE.CylinderGeometry(w * 0.48, w * 0.68, h, 4, 1), mat);
|
|
1007
|
-
shaft.rotation.y = Math.PI / 4;
|
|
1008
|
-
shaft.position.y = 0.24 + h / 2;
|
|
1009
|
-
const tip = new THREE.Mesh(new THREE.ConeGeometry(w * 0.48, w * 0.7, 4), mat);
|
|
1010
|
-
tip.rotation.y = Math.PI / 4;
|
|
1011
|
-
tip.position.y = 0.24 + h + w * 0.35;
|
|
1012
|
-
[base, shaft, tip].forEach((o) => {
|
|
1013
|
-
o.castShadow = o.receiveShadow = true;
|
|
1014
|
-
grp.add(o);
|
|
1015
|
-
});
|
|
1016
|
-
grp.position.set(x, groundH(x, z) - 0.05, z);
|
|
1017
|
-
grp.rotation.set(rand(-0.04, 0.04), rand(0, 1), rand(-0.05, 0.05));
|
|
1018
|
-
stoneGroup.add(grp);
|
|
1019
|
-
}
|
|
1020
|
-
for (const side of [-1, 1]) {
|
|
1021
|
-
for (let z = 6.2; z > -40; z -= rand(2.2, 3.0))
|
|
1022
|
-
addTomb(side * rand(1.55, 2.05), z + rand(-0.4, 0.4), rand(0.9, 1.1));
|
|
1023
|
-
for (let z = 5.0; z > -42; z -= rand(2.4, 3.4))
|
|
1024
|
-
addTomb(side * rand(3.0, 4.2), z + rand(-0.5, 0.5), rand(0.85, 1.15));
|
|
1025
|
-
for (let z = 3.0; z > -44; z -= rand(2.6, 4.0)) {
|
|
1026
|
-
const x = side * rand(5.2, 8.5);
|
|
1027
|
-
if (rng() < 0.18) addObelisk(x, z);
|
|
1028
|
-
else addTomb(x, z, rand(0.9, 1.25));
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
/* ---------- dead trees ---------- */
|
|
1033
|
-
function makeTree(seed, scale) {
|
|
1034
|
-
const r = mulberry32(seed),
|
|
1035
|
-
geos = [],
|
|
1036
|
-
Y = new THREE.Vector3(0, 1, 0);
|
|
1037
|
-
function seg(a, b, r0, r1, joint) {
|
|
1038
|
-
const d = new THREE.Vector3().subVectors(b, a),
|
|
1039
|
-
L = d.length();
|
|
1040
|
-
const g = new THREE.CylinderGeometry(r1, r0, L, 7, 1);
|
|
1041
|
-
g.translate(0, L / 2, 0);
|
|
1042
|
-
g.applyQuaternion(new THREE.Quaternion().setFromUnitVectors(Y, d.normalize()));
|
|
1043
|
-
g.translate(a.x, a.y, a.z);
|
|
1044
|
-
geos.push(g);
|
|
1045
|
-
if (joint) {
|
|
1046
|
-
const s = new THREE.SphereGeometry(r1 * 1.02, 8, 6);
|
|
1047
|
-
s.translate(b.x, b.y, b.z);
|
|
1048
|
-
geos.push(s);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
function branch(p, dir, len, rad, depth) {
|
|
1052
|
-
let cur = p.clone(),
|
|
1053
|
-
d = dir.clone(),
|
|
1054
|
-
r0 = rad * (depth === 0 ? 1.7 : 1);
|
|
1055
|
-
for (let i = 0; i < 3; i++) {
|
|
1056
|
-
d.add(
|
|
1057
|
-
new THREE.Vector3((r() - 0.5) * 0.7, (r() - 0.45) * 0.35, (r() - 0.5) * 0.7)
|
|
1058
|
-
).normalize();
|
|
1059
|
-
const nxt = cur.clone().addScaledVector(d, len / 3),
|
|
1060
|
-
r1 = rad * (1 - (0.5 * (i + 1)) / 3);
|
|
1061
|
-
seg(cur, nxt, r0, r1, depth < 3);
|
|
1062
|
-
cur = nxt;
|
|
1063
|
-
r0 = r1;
|
|
1064
|
-
}
|
|
1065
|
-
if (depth >= 5 || rad < 0.012) return;
|
|
1066
|
-
const kids = depth < 1 ? 3 : 2 + (r() < 0.4 ? 1 : 0);
|
|
1067
|
-
for (let k = 0; k < kids; k++) {
|
|
1068
|
-
const nd = d
|
|
1069
|
-
.clone()
|
|
1070
|
-
.add(new THREE.Vector3((r() - 0.5) * 1.7, r() * 0.7 - 0.15, (r() - 0.5) * 1.7))
|
|
1071
|
-
.normalize();
|
|
1072
|
-
branch(cur, nd, len * (0.6 + r() * 0.16), r0 * 0.74, depth + 1);
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
branch(
|
|
1076
|
-
new THREE.Vector3(0, -0.3, 0),
|
|
1077
|
-
new THREE.Vector3(0, 1, 0),
|
|
1078
|
-
3.4 * scale,
|
|
1079
|
-
0.2 * scale,
|
|
1080
|
-
0
|
|
1081
|
-
);
|
|
1082
|
-
return mergeGeometries(geos);
|
|
1083
|
-
}
|
|
1084
|
-
[
|
|
1085
|
-
[-5.6, -13, 1.25, 7],
|
|
1086
|
-
[6.8, -19, 1.1, 19],
|
|
1087
|
-
[-8.5, -27, 1.3, 33],
|
|
1088
|
-
[4.6, -35, 1.0, 41],
|
|
1089
|
-
[9.5, -8, 0.9, 57],
|
|
1090
|
-
[-11, -6, 1.0, 71],
|
|
1091
|
-
[12, -30, 1.2, 83],
|
|
1092
|
-
].forEach(([x, z, s, seed]) => {
|
|
1093
|
-
const t = new THREE.Mesh(makeTree(seed, s), barkMat);
|
|
1094
|
-
t.position.set(x, groundH(x, z), z);
|
|
1095
|
-
t.rotation.y = seed;
|
|
1096
|
-
t.castShadow = true;
|
|
1097
|
-
t.receiveShadow = true;
|
|
1098
|
-
scene.add(t);
|
|
1099
|
-
});
|
|
1100
|
-
|
|
1101
|
-
/* ---------- gate and iron fence (at the far end of the aisle) ---------- */
|
|
1102
|
-
const GATE_Z = -27;
|
|
1103
|
-
{
|
|
1104
|
-
const pillarMat = stoneMats[1];
|
|
1105
|
-
for (const side of [-1, 1]) {
|
|
1106
|
-
const pg = new THREE.Group();
|
|
1107
|
-
const p = new THREE.Mesh(new THREE.BoxGeometry(0.5, 2.5, 0.5), pillarMat);
|
|
1108
|
-
p.position.y = 1.25;
|
|
1109
|
-
const cap = new THREE.Mesh(new THREE.BoxGeometry(0.64, 0.14, 0.64), pillarMat);
|
|
1110
|
-
cap.position.y = 2.57;
|
|
1111
|
-
const fin = new THREE.Mesh(new THREE.SphereGeometry(0.17, 20, 14), pillarMat);
|
|
1112
|
-
fin.position.y = 2.82;
|
|
1113
|
-
[p, cap, fin].forEach((o) => {
|
|
1114
|
-
o.castShadow = o.receiveShadow = true;
|
|
1115
|
-
pg.add(o);
|
|
1116
|
-
});
|
|
1117
|
-
pg.position.set(side * 1.65, groundH(side * 1.65, GATE_Z), GATE_Z);
|
|
1118
|
-
scene.add(pg);
|
|
1119
|
-
// open gate leaf, hinged at the pillar, swung toward the camera
|
|
1120
|
-
const bars = [];
|
|
1121
|
-
for (let i = 0; i < 8; i++) {
|
|
1122
|
-
const b = new THREE.CylinderGeometry(0.013, 0.013, 1.9, 6);
|
|
1123
|
-
b.translate(0.08 + i * 0.16, 0.98, 0);
|
|
1124
|
-
bars.push(b);
|
|
1125
|
-
const tip = new THREE.ConeGeometry(0.03, 0.12, 6);
|
|
1126
|
-
tip.translate(0.08 + i * 0.16, 1.98, 0);
|
|
1127
|
-
bars.push(tip);
|
|
1128
|
-
}
|
|
1129
|
-
for (const y of [0.18, 1.55, 1.8]) {
|
|
1130
|
-
const r = new THREE.BoxGeometry(1.24, 0.035, 0.035);
|
|
1131
|
-
r.translate(0.64, y, 0);
|
|
1132
|
-
bars.push(r);
|
|
1133
|
-
}
|
|
1134
|
-
const leaf = new THREE.Mesh(mergeGeometries(bars.map((g) => g.toNonIndexed())), ironMat);
|
|
1135
|
-
leaf.castShadow = true;
|
|
1136
|
-
const hinge = new THREE.Group();
|
|
1137
|
-
hinge.position.set(side * 1.38, groundH(side * 1.38, GATE_Z), GATE_Z + 0.02);
|
|
1138
|
-
hinge.rotation.y = side < 0 ? -1.15 : Math.PI + 1.15;
|
|
1139
|
-
hinge.add(leaf);
|
|
1140
|
-
scene.add(hinge);
|
|
1141
|
-
}
|
|
1142
|
-
// arch
|
|
1143
|
-
const arch = new THREE.Mesh(new THREE.TorusGeometry(1.4, 0.022, 8, 48, Math.PI), ironMat);
|
|
1144
|
-
arch.position.set(0, 2.45, GATE_Z);
|
|
1145
|
-
arch.castShadow = true;
|
|
1146
|
-
scene.add(arch);
|
|
1147
|
-
// fence
|
|
1148
|
-
const barGeo = mergeGeometries([
|
|
1149
|
-
new THREE.CylinderGeometry(0.011, 0.011, 1.5, 5).translate(0, 0.75, 0).toNonIndexed(),
|
|
1150
|
-
new THREE.ConeGeometry(0.026, 0.1, 5).translate(0, 1.55, 0).toNonIndexed(),
|
|
1151
|
-
]);
|
|
1152
|
-
const xs = [];
|
|
1153
|
-
for (const side of [-1, 1]) for (let x = 2.0; x < 16; x += 0.15) xs.push(side * x);
|
|
1154
|
-
const fence = new THREE.InstancedMesh(barGeo, ironMat, xs.length);
|
|
1155
|
-
const m4 = new THREE.Matrix4();
|
|
1156
|
-
xs.forEach((x, i) => {
|
|
1157
|
-
m4.makeRotationZ((hash1(i * 3.1) - 0.5) * 0.06);
|
|
1158
|
-
m4.setPosition(x, groundH(x, GATE_Z) - 0.05, GATE_Z);
|
|
1159
|
-
fence.setMatrixAt(i, m4);
|
|
1160
|
-
});
|
|
1161
|
-
fence.castShadow = true;
|
|
1162
|
-
scene.add(fence);
|
|
1163
|
-
for (const side of [-1, 1])
|
|
1164
|
-
for (const y of [0.25, 1.32]) {
|
|
1165
|
-
const r = new THREE.Mesh(new THREE.BoxGeometry(14, 0.03, 0.03), ironMat);
|
|
1166
|
-
r.position.set(side * 9, y + 0.1, GATE_Z);
|
|
1167
|
-
r.castShadow = true;
|
|
1168
|
-
scene.add(r);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
/* ---------- dead grass ---------- */
|
|
1173
|
-
{
|
|
1174
|
-
const N = 5200,
|
|
1175
|
-
blade = new THREE.ConeGeometry(0.008, 0.18, 3, 1);
|
|
1176
|
-
blade.translate(0, 0.09, 0);
|
|
1177
|
-
const grass = new THREE.InstancedMesh(blade, grassMat, N),
|
|
1178
|
-
m4 = new THREE.Matrix4(),
|
|
1179
|
-
q = new THREE.Quaternion(),
|
|
1180
|
-
e = new THREE.Euler(),
|
|
1181
|
-
s = new THREE.Vector3(),
|
|
1182
|
-
p = new THREE.Vector3(),
|
|
1183
|
-
c = new THREE.Color();
|
|
1184
|
-
let n = 0;
|
|
1185
|
-
while (n < N) {
|
|
1186
|
-
const x = rand(-13, 13),
|
|
1187
|
-
z = rand(-40, 8.5);
|
|
1188
|
-
if (Math.abs(x) < 0.75 + rng() * 0.6) continue;
|
|
1189
|
-
e.set(rand(-0.45, 0.45), rand(0, 6.28), rand(-0.45, 0.45));
|
|
1190
|
-
q.setFromEuler(e);
|
|
1191
|
-
s.set(rand(0.7, 1.3), rand(0.45, 1.5), rand(0.7, 1.3));
|
|
1192
|
-
p.set(x, groundH(x, z) - 0.02, z);
|
|
1193
|
-
m4.compose(p, q, s);
|
|
1194
|
-
grass.setMatrixAt(n, m4);
|
|
1195
|
-
c.setRGB(lerp(0.14, 0.27, rng()), lerp(0.13, 0.23, rng()), lerp(0.08, 0.12, rng()));
|
|
1196
|
-
grass.setColorAt(n, c);
|
|
1197
|
-
n++;
|
|
1198
|
-
}
|
|
1199
|
-
grass.receiveShadow = true;
|
|
1200
|
-
scene.add(grass);
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
/* ---------- pumpkins ---------- */
|
|
1204
|
-
const pumpkinColorDark = new THREE.Color(0x5a1e06),
|
|
1205
|
-
pumpkinColorLight = new THREE.Color(0xd9661c);
|
|
1206
|
-
function pumpkinDeform(v) {
|
|
1207
|
-
const th = Math.atan2(v.z, v.x),
|
|
1208
|
-
lobe = Math.pow(Math.abs(Math.cos(th * 4)), 0.5);
|
|
1209
|
-
const rf = (1 - 0.075 * (1 - lobe)) * (1 - 0.06 * v.y * v.y);
|
|
1210
|
-
let y = v.y * 0.72 - 0.2 * smooth(0.72, 1.0, v.y) + 0.08 * smooth(-0.8, -1.0, v.y);
|
|
1211
|
-
return { p: new THREE.Vector3(v.x * rf, y, v.z * rf), lobe };
|
|
1212
|
-
}
|
|
1213
|
-
const pumpkinGeo = (() => {
|
|
1214
|
-
let g = new THREE.SphereGeometry(1, 64, 40);
|
|
1215
|
-
g.deleteAttribute('uv');
|
|
1216
|
-
g.deleteAttribute('normal');
|
|
1217
|
-
g = mergeVertices(g);
|
|
1218
|
-
const pos = g.attributes.position,
|
|
1219
|
-
col = new Float32Array(pos.count * 3),
|
|
1220
|
-
v = new THREE.Vector3(),
|
|
1221
|
-
c = new THREE.Color();
|
|
1222
|
-
for (let i = 0; i < pos.count; i++) {
|
|
1223
|
-
v.fromBufferAttribute(pos, i).normalize();
|
|
1224
|
-
const d = pumpkinDeform(v);
|
|
1225
|
-
pos.setXYZ(i, d.p.x, d.p.y, d.p.z);
|
|
1226
|
-
c.copy(pumpkinColorDark).lerp(
|
|
1227
|
-
pumpkinColorLight,
|
|
1228
|
-
Math.pow(d.lobe, 1.6) * (0.75 + 0.25 * (1 - Math.abs(v.y)))
|
|
1229
|
-
);
|
|
1230
|
-
col[i * 3] = c.r;
|
|
1231
|
-
col[i * 3 + 1] = c.g;
|
|
1232
|
-
col[i * 3 + 2] = c.b;
|
|
1233
|
-
}
|
|
1234
|
-
g.setAttribute('color', new THREE.BufferAttribute(col, 3));
|
|
1235
|
-
g.computeVertexNormals();
|
|
1236
|
-
return g;
|
|
1237
|
-
})();
|
|
1238
|
-
const pumpkinMat = P({
|
|
1239
|
-
color: 0xffffff,
|
|
1240
|
-
vertexColors: true,
|
|
1241
|
-
roughness: 0.52,
|
|
1242
|
-
clearcoat: 0.35,
|
|
1243
|
-
clearcoatRoughness: 0.45,
|
|
1244
|
-
sheen: 0.3,
|
|
1245
|
-
sheenColor: 0xffa060,
|
|
1246
|
-
specularIntensity: 0.6,
|
|
1247
|
-
});
|
|
1248
|
-
const stemMat = P({ color: 0x4a4426, roughness: 0.82, bumpMap: stoneBump, bumpScale: 3 });
|
|
1249
|
-
function subdivide(geo, times) {
|
|
1250
|
-
let g = geo.index ? geo.toNonIndexed() : geo;
|
|
1251
|
-
for (let k = 0; k < times; k++) {
|
|
1252
|
-
const a = g.attributes.position.array,
|
|
1253
|
-
out = [];
|
|
1254
|
-
for (let i = 0; i < a.length; i += 9) {
|
|
1255
|
-
const A = [a[i], a[i + 1], a[i + 2]],
|
|
1256
|
-
B = [a[i + 3], a[i + 4], a[i + 5]],
|
|
1257
|
-
C = [a[i + 6], a[i + 7], a[i + 8]];
|
|
1258
|
-
const mid = (p, q) => [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2, (p[2] + q[2]) / 2];
|
|
1259
|
-
const ab = mid(A, B),
|
|
1260
|
-
bc = mid(B, C),
|
|
1261
|
-
ca = mid(C, A);
|
|
1262
|
-
out.push(
|
|
1263
|
-
...A,
|
|
1264
|
-
...ab,
|
|
1265
|
-
...ca,
|
|
1266
|
-
...ab,
|
|
1267
|
-
...B,
|
|
1268
|
-
...bc,
|
|
1269
|
-
...ca,
|
|
1270
|
-
...bc,
|
|
1271
|
-
...C,
|
|
1272
|
-
...ab,
|
|
1273
|
-
...bc,
|
|
1274
|
-
...ca
|
|
1275
|
-
);
|
|
1276
|
-
}
|
|
1277
|
-
g = new THREE.BufferGeometry();
|
|
1278
|
-
g.setAttribute('position', new THREE.Float32BufferAttribute(out, 3));
|
|
1279
|
-
}
|
|
1280
|
-
return g;
|
|
1281
|
-
}
|
|
1282
|
-
const faceGeo = (() => {
|
|
1283
|
-
const shapes = [];
|
|
1284
|
-
const tri = (pts) => {
|
|
1285
|
-
const s = new THREE.Shape();
|
|
1286
|
-
s.moveTo(...pts[0]);
|
|
1287
|
-
pts.slice(1).forEach((p) => s.lineTo(...p));
|
|
1288
|
-
s.closePath();
|
|
1289
|
-
return s;
|
|
1290
|
-
};
|
|
1291
|
-
shapes.push(
|
|
1292
|
-
tri([
|
|
1293
|
-
[0.12, 0.08],
|
|
1294
|
-
[0.42, 0.17],
|
|
1295
|
-
[0.25, 0.38],
|
|
1296
|
-
]),
|
|
1297
|
-
tri([
|
|
1298
|
-
[-0.12, 0.08],
|
|
1299
|
-
[-0.25, 0.38],
|
|
1300
|
-
[-0.42, 0.17],
|
|
1301
|
-
]),
|
|
1302
|
-
tri([
|
|
1303
|
-
[-0.06, -0.03],
|
|
1304
|
-
[0.06, -0.03],
|
|
1305
|
-
[0, 0.07],
|
|
1306
|
-
])
|
|
1307
|
-
);
|
|
1308
|
-
const yu = (x) => -0.1 - 0.1 * (1 - (x / 0.5) ** 2),
|
|
1309
|
-
yl = (x) => -0.14 - 0.26 * (1 - (x / 0.5) ** 2);
|
|
1310
|
-
const m = new THREE.Shape();
|
|
1311
|
-
m.moveTo(-0.5, yu(-0.5));
|
|
1312
|
-
for (let x = -0.45; x <= 0.451; x += 0.05) {
|
|
1313
|
-
const xr = Math.round(x * 100) / 100;
|
|
1314
|
-
if (xr === -0.05) {
|
|
1315
|
-
m.lineTo(-0.05, yl(-0.05));
|
|
1316
|
-
m.lineTo(-0.05, yl(-0.05) + 0.08);
|
|
1317
|
-
m.lineTo(0.05, yl(0.05) + 0.08);
|
|
1318
|
-
m.lineTo(0.05, yl(0.05));
|
|
1319
|
-
continue;
|
|
1320
|
-
}
|
|
1321
|
-
if (xr === 0 || xr === 0.05) continue;
|
|
1322
|
-
m.lineTo(xr, yl(xr));
|
|
1323
|
-
}
|
|
1324
|
-
m.lineTo(0.5, yu(0.5));
|
|
1325
|
-
for (let x = 0.45; x >= -0.451; x -= 0.05) {
|
|
1326
|
-
const xr = Math.round(x * 100) / 100;
|
|
1327
|
-
if (xr === 0.22) {
|
|
1328
|
-
m.lineTo(0.22, yu(0.22));
|
|
1329
|
-
m.lineTo(0.22, yu(0.22) - 0.08);
|
|
1330
|
-
m.lineTo(0.12, yu(0.12) - 0.08);
|
|
1331
|
-
m.lineTo(0.12, yu(0.12));
|
|
1332
|
-
continue;
|
|
1333
|
-
}
|
|
1334
|
-
if (xr === -0.12) {
|
|
1335
|
-
m.lineTo(-0.12, yu(-0.12));
|
|
1336
|
-
m.lineTo(-0.12, yu(-0.12) - 0.08);
|
|
1337
|
-
m.lineTo(-0.22, yu(-0.22) - 0.08);
|
|
1338
|
-
m.lineTo(-0.22, yu(-0.22));
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1341
|
-
if ((xr > 0.12 && xr < 0.22) || (xr > -0.22 && xr < -0.12)) continue;
|
|
1342
|
-
m.lineTo(xr, yu(xr));
|
|
1343
|
-
}
|
|
1344
|
-
m.closePath();
|
|
1345
|
-
shapes.push(m);
|
|
1346
|
-
const flat = subdivide(new THREE.ShapeGeometry(shapes, 1), 3),
|
|
1347
|
-
pos = flat.attributes.position;
|
|
1348
|
-
const col = new Float32Array(pos.count * 3),
|
|
1349
|
-
v = new THREE.Vector3();
|
|
1350
|
-
for (let i = 0; i < pos.count; i++) {
|
|
1351
|
-
const x = pos.getX(i),
|
|
1352
|
-
y = pos.getY(i);
|
|
1353
|
-
v.set(x, y, Math.sqrt(Math.max(0, 1 - x * x - y * y))).normalize();
|
|
1354
|
-
const d = pumpkinDeform(v);
|
|
1355
|
-
d.p.addScaledVector(v, 0.014);
|
|
1356
|
-
pos.setXYZ(i, d.p.x, d.p.y, d.p.z);
|
|
1357
|
-
const k = 0.55 + 0.45 * smooth(0.55, 0.0, Math.hypot(x * 0.8, y + 0.05));
|
|
1358
|
-
col[i * 3] = k;
|
|
1359
|
-
col[i * 3 + 1] = k * k;
|
|
1360
|
-
col[i * 3 + 2] = k * k * k;
|
|
1361
|
-
}
|
|
1362
|
-
flat.setAttribute('color', new THREE.BufferAttribute(col, 3));
|
|
1363
|
-
return flat;
|
|
1364
|
-
})();
|
|
1365
|
-
const pumpkins = [];
|
|
1366
|
-
function addPumpkin(x, z, r, carved, withLight) {
|
|
1367
|
-
const g = new THREE.Group(),
|
|
1368
|
-
body = new THREE.Mesh(pumpkinGeo, pumpkinMat);
|
|
1369
|
-
body.castShadow = body.receiveShadow = true;
|
|
1370
|
-
g.add(body);
|
|
1371
|
-
const st = new THREE.CylinderGeometry(0.045, 0.08, 0.26, 7, 5);
|
|
1372
|
-
const sp = st.attributes.position;
|
|
1373
|
-
for (let i = 0; i < sp.count; i++) {
|
|
1374
|
-
const yy = sp.getY(i) + 0.13;
|
|
1375
|
-
sp.setX(i, sp.getX(i) + 0.1 * yy * yy);
|
|
1376
|
-
}
|
|
1377
|
-
st.computeVertexNormals();
|
|
1378
|
-
const stem = new THREE.Mesh(st, stemMat);
|
|
1379
|
-
stem.position.y = 0.58;
|
|
1380
|
-
stem.rotation.y = rand(0, 6);
|
|
1381
|
-
stem.castShadow = true;
|
|
1382
|
-
g.add(stem);
|
|
1383
|
-
let face = null,
|
|
1384
|
-
faceMat = null,
|
|
1385
|
-
light = null;
|
|
1386
|
-
if (carved) {
|
|
1387
|
-
faceMat = new THREE.MeshBasicMaterial({
|
|
1388
|
-
color: 0x000000,
|
|
1389
|
-
vertexColors: true,
|
|
1390
|
-
polygonOffset: true,
|
|
1391
|
-
polygonOffsetFactor: -2,
|
|
1392
|
-
polygonOffsetUnits: -2,
|
|
1393
|
-
});
|
|
1394
|
-
face = new THREE.Mesh(faceGeo, faceMat);
|
|
1395
|
-
g.add(face);
|
|
1396
|
-
if (withLight) {
|
|
1397
|
-
light = new THREE.PointLight(0xff7a2a, 0, 5, 2);
|
|
1398
|
-
light.position.set(0, 0.25, 0.55);
|
|
1399
|
-
g.add(light);
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
g.scale.setScalar(r);
|
|
1403
|
-
g.position.set(x, groundH(x, z) + r * 0.62, z);
|
|
1404
|
-
g.rotation.y = Math.atan2(0 - x, 3.0 - z) + rand(-0.18, 0.18);
|
|
1405
|
-
g.rotation.z = rand(-0.08, 0.08);
|
|
1406
|
-
scene.add(g);
|
|
1407
|
-
pumpkins.push({ g, faceMat, light, seed: pumpkins.length * 13.7 + 2.1 });
|
|
1408
|
-
}
|
|
1409
|
-
addPumpkin(-0.98, -1.35, 0.3, true, true);
|
|
1410
|
-
addPumpkin(1.18, -2.75, 0.34, true, true);
|
|
1411
|
-
addPumpkin(1.42, -2.25, 0.2, false);
|
|
1412
|
-
addPumpkin(-1.38, 4.6, 0.26, true, true);
|
|
1413
|
-
addPumpkin(-2.4, 2.1, 0.22, false);
|
|
1414
|
-
addPumpkin(1.55, -6.6, 0.27, true, false);
|
|
1415
|
-
addPumpkin(-1.75, -11.2, 0.3, true, false);
|
|
1416
|
-
addPumpkin(2.6, -8.4, 0.24, false);
|
|
1417
|
-
addPumpkin(1.6, -17.5, 0.28, true, false);
|
|
1418
|
-
addPumpkin(-2.9, -3.8, 0.2, false);
|
|
1419
|
-
|
|
1420
|
-
/* ---------- ground mist (soft sheets, never in the dolly path) ---------- */
|
|
1421
|
-
const mistUniforms = {
|
|
1422
|
-
uTime: { value: 0 },
|
|
1423
|
-
uColor: { value: new THREE.Color(0) },
|
|
1424
|
-
uCandlePos: { value: new THREE.Vector3() },
|
|
1425
|
-
uCandleCol: { value: new THREE.Color(1.0, 0.5, 0.18) },
|
|
1426
|
-
uCandleI: { value: 0 },
|
|
1427
|
-
uCam: { value: new THREE.Vector3() },
|
|
1428
|
-
uFogDensity: { value: FOG_DENSITY },
|
|
1429
|
-
};
|
|
1430
|
-
function mistMaterial(vertical, opacity) {
|
|
1431
|
-
return new THREE.ShaderMaterial({
|
|
1432
|
-
transparent: true,
|
|
1433
|
-
depthWrite: false,
|
|
1434
|
-
fog: false,
|
|
1435
|
-
side: THREE.DoubleSide,
|
|
1436
|
-
uniforms: {
|
|
1437
|
-
...mistUniforms,
|
|
1438
|
-
uVertical: { value: vertical ? 1 : 0 },
|
|
1439
|
-
uOpacity: { value: opacity },
|
|
1440
|
-
},
|
|
1441
|
-
vertexShader: \`varying vec3 vW; void main(){ vec4 w = modelMatrix*vec4(position,1.0); vW = w.xyz; gl_Position = projectionMatrix*viewMatrix*w; }\`,
|
|
1442
|
-
fragmentShader: \`
|
|
1443
|
-
uniform float uTime, uOpacity, uFogDensity, uVertical, uCandleI; uniform vec3 uColor, uCandlePos, uCandleCol, uCam; varying vec3 vW;
|
|
1444
|
-
float hash(vec2 p){ p = fract(p*vec2(123.34,456.21)); p += dot(p, p+45.32); return fract(p.x*p.y); }
|
|
1445
|
-
float vn(vec2 p){ vec2 i = floor(p), f = fract(p), u = f*f*(3.0-2.0*f);
|
|
1446
|
-
return mix(mix(hash(i), hash(i+vec2(1,0)), u.x), mix(hash(i+vec2(0,1)), hash(i+vec2(1,1)), u.x), u.y); }
|
|
1447
|
-
float fbm(vec2 p){ float a = 0.5, s = 0.0; for(int i=0;i<4;i++){ s += a*vn(p); p = p*2.03 + vec2(1.7,9.2); a *= 0.5; } return s; }
|
|
1448
|
-
void main(){
|
|
1449
|
-
vec2 q = uVertical > 0.5 ? vec2(vW.x*0.32 + uTime*0.05, vW.y*0.75 - uTime*0.012) : vec2(vW.x*0.22 + uTime*0.04, vW.z*0.2 + uTime*0.025);
|
|
1450
|
-
float n = fbm(q + 1.3*fbm(q*1.6 - uTime*0.03));
|
|
1451
|
-
float a = smoothstep(0.38, 0.86, n) * uOpacity;
|
|
1452
|
-
if (uVertical > 0.5) { a *= smoothstep(0.0, 0.6, vW.y) * smoothstep(3.0, 1.1, vW.y) * smoothstep(13.0, 5.0, abs(vW.x)); }
|
|
1453
|
-
else { a *= smoothstep(15.0, 4.0, abs(vW.x)); }
|
|
1454
|
-
float dc = distance(uCam, vW); a *= smoothstep(0.9, 3.5, dc);
|
|
1455
|
-
float fogF = 1.0 - exp(-pow(uFogDensity*dc, 2.0));
|
|
1456
|
-
float dl = distance(vW, uCandlePos); float w = uCandleI / (0.3 + dl*dl);
|
|
1457
|
-
vec3 col = uColor + uCandleCol * w * 0.22;
|
|
1458
|
-
a = a * (1.0 - 0.75*fogF) * (1.0 + min(w, 3.0)*0.35);
|
|
1459
|
-
gl_FragColor = vec4(col, clamp(a, 0.0, 1.0));
|
|
1460
|
-
}\`,
|
|
1461
|
-
});
|
|
1462
|
-
}
|
|
1463
|
-
const mistGroup = new THREE.Group();
|
|
1464
|
-
scene.add(mistGroup);
|
|
1465
|
-
{
|
|
1466
|
-
const h1 = new THREE.Mesh(new THREE.PlaneGeometry(36, 66), mistMaterial(false, 0.55));
|
|
1467
|
-
h1.rotation.x = -Math.PI / 2;
|
|
1468
|
-
h1.position.set(0, 0.2, -24);
|
|
1469
|
-
mistGroup.add(h1);
|
|
1470
|
-
const h2 = new THREE.Mesh(new THREE.PlaneGeometry(36, 60), mistMaterial(false, 0.3));
|
|
1471
|
-
h2.rotation.x = -Math.PI / 2;
|
|
1472
|
-
h2.position.set(0, 0.55, -27);
|
|
1473
|
-
mistGroup.add(h2);
|
|
1474
|
-
for (const z of [-3.5, -9, -15.5, -22.5]) {
|
|
1475
|
-
const v = new THREE.Mesh(new THREE.PlaneGeometry(26, 3.2), mistMaterial(true, 0.5));
|
|
1476
|
-
v.position.set(0, 1.5, z);
|
|
1477
|
-
mistGroup.add(v);
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
/* ==========================================================================
|
|
1482
|
-
THE MONUMENT: three carved stone shards on a buried boulder, glowing blue
|
|
1483
|
-
to violet along their edges and veins, with a witch's hat hovering above.
|
|
1484
|
-
========================================================================== */
|
|
1485
|
-
const MON_Z = -4.5,
|
|
1486
|
-
MON_LIFT = 0.12,
|
|
1487
|
-
PX = 0.00685; // reference drawing: 1 px = 6.85 mm
|
|
1488
|
-
function vnoise3(x, y, z) {
|
|
1489
|
-
const xi = Math.floor(x),
|
|
1490
|
-
yi = Math.floor(y),
|
|
1491
|
-
zi = Math.floor(z),
|
|
1492
|
-
s = (t) => t * t * (3 - 2 * t),
|
|
1493
|
-
u = s(x - xi),
|
|
1494
|
-
v = s(y - yi),
|
|
1495
|
-
w = s(z - zi);
|
|
1496
|
-
const hh = (a, b, c) => hash1(a * 127.1 + b * 311.7 + c * 74.7),
|
|
1497
|
-
l = (a, b, t) => a + (b - a) * t;
|
|
1498
|
-
return l(
|
|
1499
|
-
l(
|
|
1500
|
-
l(hh(xi, yi, zi), hh(xi + 1, yi, zi), u),
|
|
1501
|
-
l(hh(xi, yi + 1, zi), hh(xi + 1, yi + 1, zi), u),
|
|
1502
|
-
v
|
|
1503
|
-
),
|
|
1504
|
-
l(
|
|
1505
|
-
l(hh(xi, yi, zi + 1), hh(xi + 1, yi, zi + 1), u),
|
|
1506
|
-
l(hh(xi, yi + 1, zi + 1), hh(xi + 1, yi + 1, zi + 1), u),
|
|
1507
|
-
v
|
|
1508
|
-
),
|
|
1509
|
-
w
|
|
1510
|
-
);
|
|
1511
|
-
}
|
|
1512
|
-
const GN = \`
|
|
1513
|
-
float gh(vec3 p){ p = fract(p*0.3183099 + 0.1); p *= 17.0; return fract(p.x*p.y*p.z*(p.x+p.y+p.z)); }
|
|
1514
|
-
float gn(vec3 x){ vec3 i = floor(x), f = fract(x); f = f*f*(3.0-2.0*f);
|
|
1515
|
-
return mix(mix(mix(gh(i), gh(i+vec3(1,0,0)), f.x), mix(gh(i+vec3(0,1,0)), gh(i+vec3(1,1,0)), f.x), f.y),
|
|
1516
|
-
mix(mix(gh(i+vec3(0,0,1)), gh(i+vec3(1,0,1)), f.x), mix(gh(i+vec3(0,1,1)), gh(i+vec3(1,1,1)), f.x), f.y), f.z); }
|
|
1517
|
-
\`;
|
|
1518
|
-
const monUni = {
|
|
1519
|
-
uGlow: { value: 0 },
|
|
1520
|
-
uTime: { value: 0 },
|
|
1521
|
-
uTop: { value: new THREE.Color(0x6aaee6) },
|
|
1522
|
-
uBot: { value: new THREE.Color(0x6a6ee6) },
|
|
1523
|
-
uOrigin: { value: new THREE.Vector3(0, 0, MON_Z) },
|
|
1524
|
-
};
|
|
1525
|
-
const glowStoneMat = P({
|
|
1526
|
-
color: 0x4c5262,
|
|
1527
|
-
map: stoneMap,
|
|
1528
|
-
roughnessMap: stoneRough,
|
|
1529
|
-
roughness: 1,
|
|
1530
|
-
bumpMap: stoneBump,
|
|
1531
|
-
bumpScale: 1.4,
|
|
1532
|
-
specularIntensity: 0.45,
|
|
1533
|
-
envMapIntensity: 0.7,
|
|
1534
|
-
});
|
|
1535
|
-
glowStoneMat.onBeforeCompile = (sh) => {
|
|
1536
|
-
Object.assign(sh.uniforms, monUni);
|
|
1537
|
-
sh.vertexShader =
|
|
1538
|
-
'uniform vec3 uOrigin; varying vec3 vMon;\\n' +
|
|
1539
|
-
sh.vertexShader.replace(
|
|
1540
|
-
'#include <begin_vertex>',
|
|
1541
|
-
'#include <begin_vertex>\\n vMon = (modelMatrix * vec4(transformed, 1.0)).xyz - uOrigin;'
|
|
1542
|
-
);
|
|
1543
|
-
sh.fragmentShader =
|
|
1544
|
-
'uniform float uGlow, uTime; uniform vec3 uTop, uBot; varying vec3 vMon;\\n' +
|
|
1545
|
-
GN +
|
|
1546
|
-
sh.fragmentShader.replace(
|
|
1547
|
-
'#include <emissivemap_fragment>',
|
|
1548
|
-
\`#include <emissivemap_fragment>
|
|
1549
|
-
float k = clamp(0.5 + 0.38*(vMon.y - 1.1) - 0.3*vMon.x, 0.0, 1.0); // light blue top-left, violet bottom-right
|
|
1550
|
-
vec3 gcol = mix(uBot, uTop, k);
|
|
1551
|
-
float rim = pow(1.0 - abs(dot(normalize(normal), normalize(vViewPosition))), 3.0);
|
|
1552
|
-
float r1 = 1.0 - abs(gn(vMon*2.4 + vec3(0.0, uTime*0.04, 0.0))*2.0 - 1.0);
|
|
1553
|
-
float r2 = 1.0 - abs(gn(vMon*5.5 + 11.0)*2.0 - 1.0);
|
|
1554
|
-
float vein = smoothstep(0.982, 0.999, r1) + 0.5*smoothstep(0.99, 0.9995, r2); // sparse, thin cracks
|
|
1555
|
-
float pulse = 0.8 + 0.2*sin(uTime*2.2 - vMon.y*3.0);
|
|
1556
|
-
totalEmissiveRadiance += gcol * uGlow * (0.08 + 1.9*rim + 3.0*vein*pulse);\`
|
|
1557
|
-
);
|
|
1558
|
-
};
|
|
1559
|
-
glowStoneMat.customProgramCacheKey = () => 'glow-stone';
|
|
1560
|
-
const monument = new THREE.Group();
|
|
1561
|
-
monument.position.set(0, MON_LIFT, MON_Z);
|
|
1562
|
-
scene.add(monument);
|
|
1563
|
-
function shard(pts, depth, zOff, seed) {
|
|
1564
|
-
const s = new THREE.Shape(),
|
|
1565
|
-
m2 = pts.map(([px, py]) => [(px - 227) * PX, (575 - py) * PX]);
|
|
1566
|
-
const cx = m2.reduce((a, q) => a + q[0], 0) / m2.length,
|
|
1567
|
-
cy = m2.reduce((a, q) => a + q[1], 0) / m2.length;
|
|
1568
|
-
m2.forEach(([x, y], i) => {
|
|
1569
|
-
x = cx + (x - cx) * 0.95;
|
|
1570
|
-
y = cy + (y - cy) * 0.95;
|
|
1571
|
-
if (i) s.lineTo(x, y);
|
|
1572
|
-
else s.moveTo(x, y);
|
|
1573
|
-
});
|
|
1574
|
-
s.closePath(); // shrink a little so the gaps stay open
|
|
1575
|
-
const g = new THREE.ExtrudeGeometry(s, {
|
|
1576
|
-
depth,
|
|
1577
|
-
bevelEnabled: true,
|
|
1578
|
-
bevelThickness: 0.06,
|
|
1579
|
-
bevelSize: 0.03,
|
|
1580
|
-
bevelSegments: 3,
|
|
1581
|
-
curveSegments: 1,
|
|
1582
|
-
});
|
|
1583
|
-
g.translate(0, 0, -depth / 2 + zOff);
|
|
1584
|
-
const pos = g.attributes.position,
|
|
1585
|
-
v = new THREE.Vector3();
|
|
1586
|
-
for (let i = 0; i < pos.count; i++) {
|
|
1587
|
-
// rough it up; shared corners move together, so no cracks
|
|
1588
|
-
v.fromBufferAttribute(pos, i);
|
|
1589
|
-
const n = (a, b) => vnoise3(v.x * a + seed, v.y * a + b, v.z * a) - 0.5;
|
|
1590
|
-
v.x += 0.05 * n(5, 1) + 0.015 * n(17, 4);
|
|
1591
|
-
v.y += 0.05 * n(5, 7) + 0.015 * n(17, 9);
|
|
1592
|
-
v.z += 0.06 * n(5, 13);
|
|
1593
|
-
pos.setXYZ(i, v.x, v.y, v.z);
|
|
1594
|
-
}
|
|
1595
|
-
g.computeVertexNormals();
|
|
1596
|
-
const m = new THREE.Mesh(g, glowStoneMat);
|
|
1597
|
-
m.castShadow = m.receiveShadow = true;
|
|
1598
|
-
monument.add(m);
|
|
1599
|
-
}
|
|
1600
|
-
shard(
|
|
1601
|
-
[
|
|
1602
|
-
[166, 350],
|
|
1603
|
-
[226, 397],
|
|
1604
|
-
[140, 575],
|
|
1605
|
-
[82, 528],
|
|
1606
|
-
],
|
|
1607
|
-
0.42,
|
|
1608
|
-
0.0,
|
|
1609
|
-
3.1
|
|
1610
|
-
); // leaning slab
|
|
1611
|
-
shard(
|
|
1612
|
-
[
|
|
1613
|
-
[198, 283],
|
|
1614
|
-
[258, 330],
|
|
1615
|
-
[233, 383],
|
|
1616
|
-
[172, 338],
|
|
1617
|
-
],
|
|
1618
|
-
0.36,
|
|
1619
|
-
0.04,
|
|
1620
|
-
7.7
|
|
1621
|
-
); // top block
|
|
1622
|
-
shard(
|
|
1623
|
-
[
|
|
1624
|
-
[268, 356],
|
|
1625
|
-
[372, 575],
|
|
1626
|
-
[207, 488],
|
|
1627
|
-
],
|
|
1628
|
-
0.4,
|
|
1629
|
-
-0.03,
|
|
1630
|
-
12.9
|
|
1631
|
-
); // triangle
|
|
1632
|
-
{
|
|
1633
|
-
// the boulder it stands on, half buried
|
|
1634
|
-
const g = new THREE.IcosahedronGeometry(1, 4),
|
|
1635
|
-
pos = g.attributes.position,
|
|
1636
|
-
v = new THREE.Vector3();
|
|
1637
|
-
for (let i = 0; i < pos.count; i++) {
|
|
1638
|
-
v.fromBufferAttribute(pos, i);
|
|
1639
|
-
const k =
|
|
1640
|
-
1 +
|
|
1641
|
-
0.18 * (vnoise3(v.x * 2.2, v.y * 2.2, v.z * 2.2) - 0.5) +
|
|
1642
|
-
0.06 * (vnoise3(v.x * 6 + 3, v.y * 6, v.z * 6) - 0.5);
|
|
1643
|
-
pos.setXYZ(i, v.x * k * 1.45, v.y * k * 0.38, v.z * k * 0.85);
|
|
1644
|
-
}
|
|
1645
|
-
g.computeVertexNormals();
|
|
1646
|
-
const base = new THREE.Mesh(
|
|
1647
|
-
g,
|
|
1648
|
-
P({
|
|
1649
|
-
color: 0x5d5b58,
|
|
1650
|
-
map: stoneMap,
|
|
1651
|
-
roughnessMap: stoneRough,
|
|
1652
|
-
roughness: 1,
|
|
1653
|
-
bumpMap: stoneBump,
|
|
1654
|
-
bumpScale: 1.6,
|
|
1655
|
-
specularIntensity: 0.3,
|
|
1656
|
-
})
|
|
1657
|
-
);
|
|
1658
|
-
base.position.set(0.02, groundH(0, MON_Z) - 0.02, MON_Z);
|
|
1659
|
-
base.castShadow = base.receiveShadow = true;
|
|
1660
|
-
scene.add(base);
|
|
1661
|
-
}
|
|
1662
|
-
// its glow on the ground, the fog and the underside of the hat
|
|
1663
|
-
const monLights = [
|
|
1664
|
-
[new THREE.PointLight(0x6aaee6, 0, 0, 2), [-0.5, 1.2, 1.6], 1.0],
|
|
1665
|
-
[new THREE.PointLight(0x6a6ee6, 0, 0, 2), [0.7, 0.45, 1.5], 1.0],
|
|
1666
|
-
[new THREE.PointLight(0x7a9ef0, 0, 0, 2), [0, 2.25, 0.35], 0.45],
|
|
1667
|
-
];
|
|
1668
|
-
monLights.forEach(([l, p]) => {
|
|
1669
|
-
l.position.set(...p);
|
|
1670
|
-
monument.add(l);
|
|
1671
|
-
});
|
|
1672
|
-
|
|
1673
|
-
// the hat: crooked cone, curled brim, band and gold buckle
|
|
1674
|
-
const HAT_Y = MON_LIFT + 2.0 + 0.32;
|
|
1675
|
-
const hat = new THREE.Group();
|
|
1676
|
-
hat.position.set(-0.08, HAT_Y, MON_Z);
|
|
1677
|
-
scene.add(hat);
|
|
1678
|
-
const hatRed = new THREE.Color(0x8f3434),
|
|
1679
|
-
hatPink = new THREE.Color(0xe9b7ac);
|
|
1680
|
-
const hatMat = P({
|
|
1681
|
-
color: 0xffffff,
|
|
1682
|
-
vertexColors: true,
|
|
1683
|
-
roughness: 0.86,
|
|
1684
|
-
sheen: 0.6,
|
|
1685
|
-
sheenColor: 0xf0b8a8,
|
|
1686
|
-
sheenRoughness: 0.5,
|
|
1687
|
-
specularIntensity: 0.3,
|
|
1688
|
-
side: THREE.DoubleSide,
|
|
1689
|
-
bumpMap: fabricBump,
|
|
1690
|
-
bumpScale: 0.6,
|
|
1691
|
-
});
|
|
1692
|
-
{
|
|
1693
|
-
const H = 1.35,
|
|
1694
|
-
pts = [];
|
|
1695
|
-
for (let i = 0; i <= 48; i++) {
|
|
1696
|
-
const t = i / 48;
|
|
1697
|
-
pts.push(new THREE.Vector2(0.42 * Math.pow(1 - t, 1.1) + 0.012, t * H));
|
|
1698
|
-
}
|
|
1699
|
-
const g = new THREE.LatheGeometry(pts, 72, Math.PI, Math.PI * 2),
|
|
1700
|
-
pos = g.attributes.position,
|
|
1701
|
-
col = new Float32Array(pos.count * 3),
|
|
1702
|
-
c = new THREE.Color();
|
|
1703
|
-
for (let i = 0; i < pos.count; i++) {
|
|
1704
|
-
let x = pos.getX(i),
|
|
1705
|
-
y = pos.getY(i),
|
|
1706
|
-
z = pos.getZ(i);
|
|
1707
|
-
const hn = y / H,
|
|
1708
|
-
phi = Math.atan2(x, z);
|
|
1709
|
-
const wr =
|
|
1710
|
-
1 +
|
|
1711
|
-
0.06 * Math.sin(hn * 15 + phi * 2) * smooth(0.1, 0.6, hn) +
|
|
1712
|
-
0.03 * Math.sin(phi * 5 + hn * 9);
|
|
1713
|
-
x *= wr;
|
|
1714
|
-
z *= wr;
|
|
1715
|
-
x += -0.45 * Math.pow(hn, 2.2) - 0.12 * smooth(0.75, 1, hn); // tip bends to the left
|
|
1716
|
-
y -= 0.14 * smooth(0.72, 1, hn) * hn; // and droops
|
|
1717
|
-
pos.setXYZ(i, x, y, z);
|
|
1718
|
-
const hl =
|
|
1719
|
-
smooth(0.1, 0.85, Math.sin(phi)) *
|
|
1720
|
-
smooth(0.12, 0.45, hn) *
|
|
1721
|
-
(1 - smooth(0.85, 1, hn)) *
|
|
1722
|
-
0.75;
|
|
1723
|
-
c.copy(hatRed).lerp(hatPink, hl);
|
|
1724
|
-
col[i * 3] = c.r;
|
|
1725
|
-
col[i * 3 + 1] = c.g;
|
|
1726
|
-
col[i * 3 + 2] = c.b;
|
|
1727
|
-
}
|
|
1728
|
-
g.setAttribute('color', new THREE.BufferAttribute(col, 3));
|
|
1729
|
-
g.computeVertexNormals();
|
|
1730
|
-
const cone = new THREE.Mesh(g, hatMat);
|
|
1731
|
-
cone.castShadow = cone.receiveShadow = true;
|
|
1732
|
-
hat.add(cone);
|
|
1733
|
-
}
|
|
1734
|
-
{
|
|
1735
|
-
const prof = [
|
|
1736
|
-
[0.3, 0.02],
|
|
1737
|
-
[0.6, 0.0],
|
|
1738
|
-
[0.85, 0.03],
|
|
1739
|
-
[0.98, 0.1],
|
|
1740
|
-
[1.0, 0.12],
|
|
1741
|
-
[0.99, 0.1],
|
|
1742
|
-
[0.85, 0.0],
|
|
1743
|
-
[0.6, -0.025],
|
|
1744
|
-
[0.3, -0.01],
|
|
1745
|
-
];
|
|
1746
|
-
const g = new THREE.LatheGeometry(
|
|
1747
|
-
prof.map((p) => new THREE.Vector2(...p)),
|
|
1748
|
-
96,
|
|
1749
|
-
Math.PI,
|
|
1750
|
-
Math.PI * 2
|
|
1751
|
-
);
|
|
1752
|
-
const pos = g.attributes.position,
|
|
1753
|
-
col = new Float32Array(pos.count * 3),
|
|
1754
|
-
c = new THREE.Color();
|
|
1755
|
-
for (let i = 0; i < pos.count; i++) {
|
|
1756
|
-
let x = pos.getX(i),
|
|
1757
|
-
y = pos.getY(i),
|
|
1758
|
-
z = pos.getZ(i);
|
|
1759
|
-
const r = Math.hypot(x, z),
|
|
1760
|
-
phi = Math.atan2(x, z),
|
|
1761
|
-
top = i % prof.length < 5;
|
|
1762
|
-
y += 0.035 * Math.sin(phi * 3 + 1) * smooth(0.4, 1, r);
|
|
1763
|
-
y += 0.16 * smooth(0.55, 1, r) * Math.pow(Math.max(0, Math.sin(phi)), 3); // right edge curls up
|
|
1764
|
-
y -= 0.05 * smooth(0.6, 1, r) * Math.max(0, -Math.sin(phi));
|
|
1765
|
-
pos.setXYZ(i, x, y, z * 0.82);
|
|
1766
|
-
c.copy(hatRed).lerp(hatPink, smooth(0.7, 1, r) * Math.max(0, Math.sin(phi)) * 0.6);
|
|
1767
|
-
if (!top) c.multiplyScalar(0.6);
|
|
1768
|
-
col[i * 3] = c.r;
|
|
1769
|
-
col[i * 3 + 1] = c.g;
|
|
1770
|
-
col[i * 3 + 2] = c.b;
|
|
1771
|
-
}
|
|
1772
|
-
g.setAttribute('color', new THREE.BufferAttribute(col, 3));
|
|
1773
|
-
g.computeVertexNormals();
|
|
1774
|
-
const brim = new THREE.Mesh(g, hatMat);
|
|
1775
|
-
brim.castShadow = brim.receiveShadow = true;
|
|
1776
|
-
hat.add(brim);
|
|
1777
|
-
}
|
|
1778
|
-
{
|
|
1779
|
-
const band = new THREE.Mesh(
|
|
1780
|
-
new THREE.CylinderGeometry(0.432, 0.448, 0.13, 72, 1, true),
|
|
1781
|
-
P({
|
|
1782
|
-
color: 0x5a1f1f,
|
|
1783
|
-
roughness: 0.8,
|
|
1784
|
-
sheen: 0.4,
|
|
1785
|
-
sheenColor: 0xc08070,
|
|
1786
|
-
side: THREE.DoubleSide,
|
|
1787
|
-
})
|
|
1788
|
-
);
|
|
1789
|
-
band.position.y = 0.075;
|
|
1790
|
-
band.castShadow = true;
|
|
1791
|
-
hat.add(band);
|
|
1792
|
-
const a = 0.55,
|
|
1793
|
-
piv = new THREE.Group();
|
|
1794
|
-
piv.position.set(Math.sin(a) * 0.455, 0.075, Math.cos(a) * 0.455);
|
|
1795
|
-
piv.rotation.y = a;
|
|
1796
|
-
hat.add(piv);
|
|
1797
|
-
const buckle = new THREE.Mesh(
|
|
1798
|
-
new THREE.TorusGeometry(0.075, 0.014, 8, 4),
|
|
1799
|
-
P({ color: 0xf2c230, metalness: 1, roughness: 0.32, clearcoat: 0.3 })
|
|
1800
|
-
);
|
|
1801
|
-
buckle.rotation.z = Math.PI / 4;
|
|
1802
|
-
piv.add(buckle);
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
/* ---------- lights ---------- */
|
|
1806
|
-
const sun = new THREE.DirectionalLight(0xffcf9a, 0); // low warm morning sun, backlighting the scene
|
|
1807
|
-
sun.position.copy(SUN_POS);
|
|
1808
|
-
sun.target.position.set(0, 0, -12);
|
|
1809
|
-
sun.castShadow = true;
|
|
1810
|
-
sun.shadow.mapSize.set(2048, 2048);
|
|
1811
|
-
Object.assign(sun.shadow.camera, {
|
|
1812
|
-
left: -27,
|
|
1813
|
-
right: 27,
|
|
1814
|
-
top: 27,
|
|
1815
|
-
bottom: -27,
|
|
1816
|
-
near: 1,
|
|
1817
|
-
far: 90,
|
|
1818
|
-
});
|
|
1819
|
-
sun.shadow.bias = -0.0005;
|
|
1820
|
-
sun.shadow.normalBias = 0.03;
|
|
1821
|
-
scene.add(sun, sun.target);
|
|
1822
|
-
const hemi = new THREE.HemisphereLight(0xbcd0e8, 0x5e5040, 0);
|
|
1823
|
-
scene.add(hemi);
|
|
1824
|
-
|
|
1825
|
-
/* ---------- "Happy Halloween": a burning title in the top-left corner ----------
|
|
1826
|
-
Screen-space pass after tone mapping, placed in the lower part of the frame so it frames
|
|
1827
|
-
the monument instead of covering it. The letters ignite left to right behind a hot edge,
|
|
1828
|
-
then burn: fire flowing upward inside, a charred rim, an orange halo, heat shimmer and
|
|
1829
|
-
embers rising off the words. */
|
|
1830
|
-
// <fire-title>
|
|
1831
|
-
const TITLE_T0 = 9.8,
|
|
1832
|
-
TITLE_W = 0.42,
|
|
1833
|
-
TITLE_CY = 0.867,
|
|
1834
|
-
TITLE_LEFT = 0.05; // start, rect width, rect centre (from bottom), left margin
|
|
1835
|
-
const TITLE_FRAG = \`
|
|
1836
|
-
uniform sampler2D tDiffuse, tMask; uniform float uT, uAspect, uOn; uniform vec4 uRect; varying vec2 vUv;
|
|
1837
|
-
float hh(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
|
|
1838
|
-
float vn(vec2 p){ vec2 i = floor(p), f = fract(p); f = f*f*(3.0-2.0*f);
|
|
1839
|
-
return mix(mix(hh(i), hh(i+vec2(1.0,0.0)), f.x), mix(hh(i+vec2(0.0,1.0)), hh(i+vec2(1.0,1.0)), f.x), f.y); }
|
|
1840
|
-
float fbm(vec2 p){ float s = 0.0, a = 0.5; for (int i = 0; i < 4; i++){ s += a*vn(p); p = p*2.03 + vec2(1.7, 9.2); a *= 0.5; } return s; }
|
|
1841
|
-
void main(){
|
|
1842
|
-
vec3 col = texture2D(tDiffuse, vUv).rgb;
|
|
1843
|
-
vec2 q = (vUv - uRect.xy)/uRect.zw + 0.5;
|
|
1844
|
-
if (uT <= 0.0 || uOn <= 0.0 || q.x < -0.12 || q.x > 1.12 || q.y < -0.5 || q.y > 2.2) { gl_FragColor = vec4(col, 1.0); return; }
|
|
1845
|
-
float R = uRect.z*uAspect/uRect.w;
|
|
1846
|
-
vec2 hq = q + vec2((vn(vec2(q.x*14.0, q.y*6.0 - uT*3.0)) - 0.5)*0.003, (vn(vec2(q.x*10.0 + 3.0, q.y*5.0 - uT*2.5)) - 0.5)*0.012); // heat shimmer
|
|
1847
|
-
vec4 M = texture2D(tMask, hq); // r sharp, g soft, b wide halo
|
|
1848
|
-
float n = fbm(q*vec2(R*2.5, 2.5) + 3.1);
|
|
1849
|
-
float front = uT*0.75 - q.x*0.9 + (n - 0.5)*0.5; // ignition sweeps left to right
|
|
1850
|
-
float lit = smoothstep(0.0, 0.08, front);
|
|
1851
|
-
float edge = smoothstep(0.0, 0.05, front)*(1.0 - smoothstep(0.05, 0.28, front));
|
|
1852
|
-
col *= 1.0 - 0.5*M.b*smoothstep(-0.2, 0.3, front); // the night darkens behind the words
|
|
1853
|
-
float flick = 0.86 + 0.14*vn(vec2(uT*7.0, 1.0));
|
|
1854
|
-
float fire = fbm(vec2(q.x*R*3.0, q.y*3.0 - uT*1.6));
|
|
1855
|
-
vec3 deep = vec3(0.78, 0.17, 0.02), hot = vec3(1.0, 0.6, 0.1), core = vec3(1.0, 0.92, 0.66);
|
|
1856
|
-
vec3 fillc = mix(deep, hot, smoothstep(0.3, 0.75, fire + 0.3*(1.0 - clamp(q.y, 0.0, 1.0))));
|
|
1857
|
-
fillc = mix(fillc, core, smoothstep(0.72, 0.95, fire)*0.6);
|
|
1858
|
-
float inside = smoothstep(0.38, 0.62, M.g);
|
|
1859
|
-
float rim = smoothstep(0.38, 0.5, M.g)*(1.0 - smoothstep(0.55, 0.85, M.g));
|
|
1860
|
-
vec3 letters = fillc*flick*(1.0 - 0.45*rim); // charred rim, burning heart
|
|
1861
|
-
col += vec3(1.0, 0.4, 0.07)*(M.g*0.55 + M.b*0.55)*flick*lit*0.6; // halo
|
|
1862
|
-
col = mix(col, letters, inside*lit);
|
|
1863
|
-
col += vec3(1.0, 0.82, 0.5)*edge*smoothstep(0.15, 0.6, M.g)*1.3; // the igniting edge flares
|
|
1864
|
-
vec2 sp = vec2(q.x*R*6.0, q.y*6.0 - uT*1.3); // embers drifting up off the words
|
|
1865
|
-
vec2 cid = floor(sp), cf = fract(sp) - 0.5; float hs = hh(cid);
|
|
1866
|
-
vec2 off = vec2(hh(cid + 7.1) - 0.5, hh(cid + 3.3) - 0.5)*0.6 + vec2(sin(uT*3.0 + hs*20.0)*0.15, 0.0);
|
|
1867
|
-
float spark = step(0.86, hs)*smoothstep(0.09, 0.0, length(cf - off))*smoothstep(2.0, 0.7, q.y)*smoothstep(0.1, 0.6, q.y)
|
|
1868
|
-
* smoothstep(0.05, 0.3, max(texture2D(tMask, vec2(q.x, 0.34)).b, texture2D(tMask, vec2(q.x, 0.66)).b))*lit;
|
|
1869
|
-
col += vec3(1.0, 0.55, 0.15)*spark*(0.55 + 0.45*sin(uT*9.0 + hs*40.0));
|
|
1870
|
-
gl_FragColor = vec4(min(col, vec3(1.0)), 1.0);
|
|
1871
|
-
}\`;
|
|
1872
|
-
function blurInto(src, CW, CH, RB, passes) {
|
|
1873
|
-
const b = src.slice(),
|
|
1874
|
-
tmp = new Float32Array(src.length),
|
|
1875
|
-
cl = (v, n) => Math.min(n - 1, Math.max(0, v)),
|
|
1876
|
-
k = 1 / (2 * RB + 1);
|
|
1877
|
-
for (let pass = 0; pass < passes; pass++) {
|
|
1878
|
-
for (let y = 0; y < CH; y++) {
|
|
1879
|
-
const row = y * CW;
|
|
1880
|
-
let s = 0;
|
|
1881
|
-
for (let x = -RB; x <= RB; x++) s += b[row + cl(x, CW)];
|
|
1882
|
-
for (let x = 0; x < CW; x++) {
|
|
1883
|
-
tmp[row + x] = s * k;
|
|
1884
|
-
s += b[row + cl(x + RB + 1, CW)] - b[row + cl(x - RB, CW)];
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
for (let x = 0; x < CW; x++) {
|
|
1888
|
-
let s = 0;
|
|
1889
|
-
for (let y = -RB; y <= RB; y++) s += tmp[cl(y, CH) * CW + x];
|
|
1890
|
-
for (let y = 0; y < CH; y++) {
|
|
1891
|
-
b[y * CW + x] = s * k;
|
|
1892
|
-
s += tmp[cl(y + RB + 1, CH) * CW + x] - tmp[cl(y - RB, CH) * CW + x];
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
1896
|
-
return b;
|
|
1897
|
-
}
|
|
1898
|
-
function buildTitleMask(text) {
|
|
1899
|
-
const CW = 2048,
|
|
1900
|
-
CH = 512,
|
|
1901
|
-
c = document.createElement('canvas');
|
|
1902
|
-
c.width = CW;
|
|
1903
|
-
c.height = CH;
|
|
1904
|
-
const g = c.getContext('2d'),
|
|
1905
|
-
font = (s) => \`400 \${s}px "Creepster", "Chiller", "Papyrus", Georgia, serif\`;
|
|
1906
|
-
const lines = text.split('\\n');
|
|
1907
|
-
g.font = font(260);
|
|
1908
|
-
const widest = Math.max(...lines.map((l) => g.measureText(l).width));
|
|
1909
|
-
const fs = Math.floor(Math.min((260 * 1820) / widest, 400 / (lines.length * 1.12)));
|
|
1910
|
-
g.font = font(fs);
|
|
1911
|
-
g.textAlign = 'left';
|
|
1912
|
-
g.textBaseline = 'middle';
|
|
1913
|
-
g.fillStyle = g.strokeStyle = '#fff';
|
|
1914
|
-
g.lineJoin = 'round';
|
|
1915
|
-
g.lineWidth = fs * 0.02;
|
|
1916
|
-
lines.forEach((l, i) => {
|
|
1917
|
-
const y = CH / 2 + (i - (lines.length - 1) / 2) * fs * 1.12;
|
|
1918
|
-
g.fillText(l, 40, y);
|
|
1919
|
-
g.strokeText(l, 40, y);
|
|
1920
|
-
}); // left-aligned
|
|
1921
|
-
const px = g.getImageData(0, 0, CW, CH).data,
|
|
1922
|
-
a = new Float32Array(CW * CH);
|
|
1923
|
-
for (let i = 0; i < a.length; i++) a[i] = px[i * 4 + 3] / 255;
|
|
1924
|
-
const soft = blurInto(a, CW, CH, 3, 2),
|
|
1925
|
-
wide = blurInto(a, CW, CH, 22, 3);
|
|
1926
|
-
const data = new Uint8Array(CW * CH * 4);
|
|
1927
|
-
for (let y = 0; y < CH; y++)
|
|
1928
|
-
for (let x = 0; x < CW; x++) {
|
|
1929
|
-
const i = y * CW + x,
|
|
1930
|
-
o = ((CH - 1 - y) * CW + x) * 4; // flip: texture rows run bottom-up
|
|
1931
|
-
data[o] = a[i] * 255;
|
|
1932
|
-
data[o + 1] = soft[i] * 255;
|
|
1933
|
-
data[o + 2] = Math.min(1, wide[i] * 1.8) * 255;
|
|
1934
|
-
data[o + 3] = 255;
|
|
1935
|
-
}
|
|
1936
|
-
return { data, CW, CH };
|
|
1937
|
-
}
|
|
1938
|
-
// </fire-title>
|
|
1939
|
-
await Promise.race([
|
|
1940
|
-
document.fonts ? document.fonts.load('400 200px "Creepster"') : Promise.resolve(),
|
|
1941
|
-
new Promise((res) => setTimeout(res, 3000)),
|
|
1942
|
-
]);
|
|
1943
|
-
const titleMask = buildTitleMask('Happy\\nHalloween');
|
|
1944
|
-
const titleTex = new THREE.DataTexture(
|
|
1945
|
-
titleMask.data,
|
|
1946
|
-
titleMask.CW,
|
|
1947
|
-
titleMask.CH,
|
|
1948
|
-
THREE.RGBAFormat
|
|
1949
|
-
);
|
|
1950
|
-
titleTex.minFilter = titleTex.magFilter = THREE.LinearFilter;
|
|
1951
|
-
titleTex.generateMipmaps = false;
|
|
1952
|
-
titleTex.needsUpdate = true;
|
|
1953
|
-
const titlePass = new ShaderPass({
|
|
1954
|
-
uniforms: {
|
|
1955
|
-
tDiffuse: { value: null },
|
|
1956
|
-
tMask: { value: titleTex },
|
|
1957
|
-
uT: { value: 0 },
|
|
1958
|
-
uOn: { value: 0 },
|
|
1959
|
-
uAspect: { value: W / H },
|
|
1960
|
-
uRect: {
|
|
1961
|
-
value: new THREE.Vector4(
|
|
1962
|
-
TITLE_LEFT + TITLE_W / 2 - (40 / 2048) * TITLE_W,
|
|
1963
|
-
TITLE_CY,
|
|
1964
|
-
TITLE_W,
|
|
1965
|
-
(TITLE_W * (W / H)) / 4
|
|
1966
|
-
),
|
|
1967
|
-
},
|
|
1968
|
-
},
|
|
1969
|
-
vertexShader: \`varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0); }\`,
|
|
1970
|
-
fragmentShader: TITLE_FRAG,
|
|
1971
|
-
});
|
|
1972
|
-
|
|
1973
|
-
/* ---------- "Applying Style....": a small loading line at the bottom centre ----------
|
|
1974
|
-
Text and dots are drawn once into a mipmapped mask (red = words, green = dots) so they stay
|
|
1975
|
-
crisp at a small size; the dots fill in one by one and reset, and a soft shimmer sweeps the words. */
|
|
1976
|
-
// <status-line>
|
|
1977
|
-
const STATUS_CY = 0.045,
|
|
1978
|
-
STATUS_H = 0.031; // centre (from bottom) and rect height, as fractions of the screen
|
|
1979
|
-
const STATUS_FRAG = \`
|
|
1980
|
-
uniform sampler2D tDiffuse, tStatus; uniform float uT, uOn, uDot0, uDotStep; uniform vec4 uRect; varying vec2 vUv;
|
|
1981
|
-
void main(){
|
|
1982
|
-
vec3 col = texture2D(tDiffuse, vUv).rgb;
|
|
1983
|
-
vec2 q = (vUv - uRect.xy)/uRect.zw + 0.5;
|
|
1984
|
-
if (uOn <= 0.0 || q.x < 0.0 || q.x > 1.0 || q.y < -0.2 || q.y > 1.0) { gl_FragColor = vec4(col, 1.0); return; }
|
|
1985
|
-
float appear = smoothstep(1.0, 1.8, uT);
|
|
1986
|
-
vec4 m = texture2D(tStatus, q);
|
|
1987
|
-
float phase = fract(uT/1.6)*5.0, i = floor((q.x - uDot0)/uDotStep);
|
|
1988
|
-
float dotA = smoothstep(i + 0.6, i + 1.0, phase)*(1.0 - smoothstep(4.55, 4.95, phase)); // dots fill in, then reset
|
|
1989
|
-
float sweep = exp(-pow((q.x - (fract(uT/2.4)*1.6 - 0.3))*7.0, 2.0)); // soft shimmer
|
|
1990
|
-
float a = (m.r*(0.6 + 0.4*sweep) + m.g*dotA*0.85)*appear;
|
|
1991
|
-
gl_FragColor = vec4(mix(col, vec3(0.93, 0.94, 0.97), a), 1.0);
|
|
1992
|
-
}\`;
|
|
1993
|
-
function buildStatusMask(words, dots) {
|
|
1994
|
-
const CW = 1024,
|
|
1995
|
-
CH = 128,
|
|
1996
|
-
c = document.createElement('canvas');
|
|
1997
|
-
c.width = CW;
|
|
1998
|
-
c.height = CH;
|
|
1999
|
-
const g = c.getContext('2d');
|
|
2000
|
-
g.fillStyle = '#000';
|
|
2001
|
-
g.fillRect(0, 0, CW, CH);
|
|
2002
|
-
g.font = '300 64px "Helvetica Neue", Helvetica, Arial, sans-serif';
|
|
2003
|
-
if ('letterSpacing' in g) g.letterSpacing = '2px';
|
|
2004
|
-
g.textBaseline = 'middle';
|
|
2005
|
-
const ww = g.measureText(words).width,
|
|
2006
|
-
dw = g.measureText('.').width + 4,
|
|
2007
|
-
x0 = (CW - (ww + dw * dots)) / 2;
|
|
2008
|
-
g.fillStyle = '#f00';
|
|
2009
|
-
g.fillText(words, x0, CH / 2);
|
|
2010
|
-
g.fillStyle = '#0f0';
|
|
2011
|
-
for (let i = 0; i < dots; i++) g.fillText('.', x0 + ww + i * dw, CH / 2);
|
|
2012
|
-
const tex = new THREE.CanvasTexture(c);
|
|
2013
|
-
tex.colorSpace = THREE.NoColorSpace;
|
|
2014
|
-
tex.generateMipmaps = true;
|
|
2015
|
-
tex.minFilter = THREE.LinearMipmapLinearFilter;
|
|
2016
|
-
tex.magFilter = THREE.LinearFilter;
|
|
2017
|
-
tex.anisotropy = 4;
|
|
2018
|
-
return { tex, dot0: (x0 + ww) / CW, dotStep: dw / CW, aspect: CW / CH };
|
|
2019
|
-
}
|
|
2020
|
-
// </status-line>
|
|
2021
|
-
const status = buildStatusMask('Applying Style', 4);
|
|
2022
|
-
const statusPass = new ShaderPass({
|
|
2023
|
-
uniforms: {
|
|
2024
|
-
tDiffuse: { value: null },
|
|
2025
|
-
tStatus: { value: status.tex },
|
|
2026
|
-
uT: { value: 0 },
|
|
2027
|
-
uOn: { value: 0 },
|
|
2028
|
-
uDot0: { value: status.dot0 },
|
|
2029
|
-
uDotStep: { value: status.dotStep },
|
|
2030
|
-
uRect: {
|
|
2031
|
-
value: new THREE.Vector4(
|
|
2032
|
-
0.5,
|
|
2033
|
-
STATUS_CY,
|
|
2034
|
-
(STATUS_H * status.aspect) / (W / H),
|
|
2035
|
-
STATUS_H
|
|
2036
|
-
),
|
|
2037
|
-
},
|
|
2038
|
-
},
|
|
2039
|
-
vertexShader: \`varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0); }\`,
|
|
2040
|
-
fragmentShader: STATUS_FRAG,
|
|
2041
|
-
});
|
|
2042
|
-
|
|
2043
|
-
/* ---------- post: MSAA HDR -> depth of field -> ACES -> title -> status -> film ---------- */
|
|
2044
|
-
const rt = new THREE.WebGLRenderTarget(W * pixelRatio, H * pixelRatio, {
|
|
2045
|
-
type: THREE.HalfFloatType,
|
|
2046
|
-
samples: 4,
|
|
2047
|
-
});
|
|
2048
|
-
const composer = new EffectComposer(renderer, rt);
|
|
2049
|
-
composer.addPass(new RenderPass(scene, camera));
|
|
2050
|
-
const bokeh = new BokehPass(scene, camera, { focus: 10, aperture: 0.0004, maxblur: 0.009 });
|
|
2051
|
-
const bokehRender = bokeh.render.bind(bokeh);
|
|
2052
|
-
bokeh.render = (...a) => {
|
|
2053
|
-
mistGroup.visible = false;
|
|
2054
|
-
sky.visible = false;
|
|
2055
|
-
bokehRender(...a);
|
|
2056
|
-
mistGroup.visible = true;
|
|
2057
|
-
sky.visible = true;
|
|
2058
|
-
};
|
|
2059
|
-
composer.addPass(bokeh);
|
|
2060
|
-
const bloom = new UnrealBloomPass(
|
|
2061
|
-
new THREE.Vector2(W * pixelRatio, H * pixelRatio),
|
|
2062
|
-
0.5,
|
|
2063
|
-
0.6,
|
|
2064
|
-
1.3
|
|
2065
|
-
);
|
|
2066
|
-
composer.addPass(bloom);
|
|
2067
|
-
composer.addPass(new OutputPass());
|
|
2068
|
-
const filmPass = new ShaderPass({
|
|
2069
|
-
uniforms: {
|
|
2070
|
-
tDiffuse: { value: null },
|
|
2071
|
-
uTime: { value: 0 },
|
|
2072
|
-
uFade: { value: 0 },
|
|
2073
|
-
uRes: { value: new THREE.Vector2(W, H) },
|
|
2074
|
-
},
|
|
2075
|
-
vertexShader: \`varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0); }\`,
|
|
2076
|
-
fragmentShader: \`uniform sampler2D tDiffuse; uniform float uTime, uFade; uniform vec2 uRes; varying vec2 vUv;
|
|
2077
|
-
float h(vec2 p){ return fract(sin(dot(p, vec2(12.9898, 78.233)))*43758.5453); }
|
|
2078
|
-
void main(){ vec2 c = vUv - 0.5; vec2 ca = c*vec2(uRes.x/uRes.y, 1.0); float r2 = dot(ca, ca);
|
|
2079
|
-
vec2 off = c*r2*0.006;
|
|
2080
|
-
vec3 col = vec3(texture2D(tDiffuse, vUv - off).r, texture2D(tDiffuse, vUv).g, texture2D(tDiffuse, vUv + off).b);
|
|
2081
|
-
col *= 1.0 - 0.58*smoothstep(0.12, 0.95, sqrt(r2));
|
|
2082
|
-
float g = h(floor(vUv*uRes) + fract(uTime*7.31)*vec2(91.7, 37.3)) - 0.5;
|
|
2083
|
-
col *= 1.0 + g*0.11;
|
|
2084
|
-
gl_FragColor = vec4(max(col, 0.0)*uFade, 1.0); }\`,
|
|
2085
|
-
});
|
|
2086
|
-
composer.addPass(titlePass);
|
|
2087
|
-
composer.addPass(statusPass);
|
|
2088
|
-
composer.addPass(filmPass);
|
|
2089
|
-
|
|
2090
|
-
/* ==========================================================================
|
|
2091
|
-
SEQUENCE — every value is a pure function of t
|
|
2092
|
-
========================================================================== */
|
|
2093
|
-
const tmpV = new THREE.Vector3(),
|
|
2094
|
-
faceW = new THREE.Vector3(),
|
|
2095
|
-
lookFar = new THREE.Vector3(0, 1.15, -30),
|
|
2096
|
-
lookDev = new THREE.Vector3(),
|
|
2097
|
-
Yup = new THREE.Vector3(0, 1, 0);
|
|
2098
|
-
const fogCol = new THREE.Color();
|
|
2099
|
-
|
|
2100
|
-
function update(t) {
|
|
2101
|
-
const out = t >= T.out;
|
|
2102
|
-
const on = out ? 0 : 1;
|
|
2103
|
-
|
|
2104
|
-
// world light: the morning fades up from absolute zero; thin drifting cloud barely varies the sun
|
|
2105
|
-
const reveal = smoother(T.revealIn, T.revealFull, t) * on;
|
|
2106
|
-
const cloud = 1 + 0.05 * (fbm1(t * 0.35) - 0.5);
|
|
2107
|
-
sun.intensity = K.sun * reveal * cloud;
|
|
2108
|
-
hemi.intensity = K.hemi * reveal;
|
|
2109
|
-
scene.environmentIntensity = K.env * reveal;
|
|
2110
|
-
fogCol.copy(FOG_BASE).multiplyScalar(reveal);
|
|
2111
|
-
scene.fog.color.copy(fogCol);
|
|
2112
|
-
scene.background.copy(fogCol);
|
|
2113
|
-
skyMat.uniforms.uFog.value.copy(fogCol);
|
|
2114
|
-
skyMat.uniforms.uZenith.value.copy(ZENITH).multiplyScalar(reveal);
|
|
2115
|
-
skyMat.uniforms.uSunCol.value.copy(SUN_TINT).multiplyScalar(reveal * cloud);
|
|
2116
|
-
|
|
2117
|
-
// the monument wakes: edges and veins glow, the hat hovers and sways above it
|
|
2118
|
-
const glowLevel = smoother(2.5, 7.5, t) * on * (0.88 + 0.12 * Math.sin(t * 1.6));
|
|
2119
|
-
monUni.uGlow.value = glowLevel;
|
|
2120
|
-
monUni.uTime.value = t;
|
|
2121
|
-
monLights.forEach(([l, , k]) => {
|
|
2122
|
-
l.intensity = K.glow * k * glowLevel;
|
|
2123
|
-
});
|
|
2124
|
-
hat.position.y = HAT_Y + 0.06 * Math.sin(t * 1.25) + 0.02 * Math.sin(t * 2.7 + 1.0);
|
|
2125
|
-
hat.rotation.set(
|
|
2126
|
-
0.04 * Math.sin(t * 0.8 + 0.5),
|
|
2127
|
-
0.25 * Math.sin(t * 0.35),
|
|
2128
|
-
-0.08 + 0.05 * Math.sin(t * 0.95)
|
|
2129
|
-
);
|
|
2130
|
-
|
|
2131
|
-
// pumpkins kindle with the reveal and gutter toward the end
|
|
2132
|
-
const fear = smoother(10, 15.5, t);
|
|
2133
|
-
for (const p of pumpkins) {
|
|
2134
|
-
if (!p.faceMat) continue;
|
|
2135
|
-
const f =
|
|
2136
|
-
(0.78 + 0.22 * fbm1(t * 3.1 + p.seed) + 0.07 * noise1(t * 11 + p.seed)) *
|
|
2137
|
-
(1 - 0.45 * fear);
|
|
2138
|
-
const k = smoother(1.6, 6.0, t) * on * f;
|
|
2139
|
-
p.faceMat.color.setRGB(2.6 * k, 0.95 * k, 0.22 * k);
|
|
2140
|
-
if (p.light) p.light.intensity = K.pump * k;
|
|
2141
|
-
}
|
|
2142
|
-
|
|
2143
|
-
// camera: one continuous dolly, eased to an almost imperceptible landing
|
|
2144
|
-
const cu = easeLong(t / T.out);
|
|
2145
|
-
camera.position.set(0, lerp(CAM_Y0, CAM_Y1, cu), lerp(CAM_Z0, CAM_Z1, cu));
|
|
2146
|
-
const wLook = smoother(3.5, 13.8, t);
|
|
2147
|
-
lookDev.set(0, 1.7, MON_Z);
|
|
2148
|
-
camera.lookAt(tmpV.copy(lookFar).lerp(lookDev, wLook));
|
|
2149
|
-
camera.updateMatrixWorld();
|
|
2150
|
-
|
|
2151
|
-
// derived: focus on the point the camera is looking at
|
|
2152
|
-
faceW.set(0, 1.4, MON_Z + 0.3);
|
|
2153
|
-
mistUniforms.uTime.value = t;
|
|
2154
|
-
mistUniforms.uColor.value.copy(fogCol).multiplyScalar(1.1);
|
|
2155
|
-
mistUniforms.uCam.value.copy(camera.position);
|
|
2156
|
-
mistUniforms.uCandlePos.value.set(0, 1.2, MON_Z + 0.6);
|
|
2157
|
-
mistUniforms.uCandleCol.value.setRGB(0.42, 0.62, 1.0);
|
|
2158
|
-
mistUniforms.uCandleI.value = 1.2 * glowLevel;
|
|
2159
|
-
sky.position.copy(camera.position);
|
|
2160
|
-
const focus = -faceW.clone().applyMatrix4(camera.matrixWorldInverse).z;
|
|
2161
|
-
bokeh.uniforms.focus.value = focus;
|
|
2162
|
-
bokeh.uniforms.aperture.value = lerp(K.ap0, K.ap1, smoother(5, 15.5, t));
|
|
2163
|
-
|
|
2164
|
-
filmPass.uniforms.uTime.value = t;
|
|
2165
|
-
filmPass.uniforms.uFade.value = on;
|
|
2166
|
-
titlePass.uniforms.uT.value = t - TITLE_T0;
|
|
2167
|
-
titlePass.uniforms.uOn.value = on;
|
|
2168
|
-
statusPass.uniforms.uT.value = t;
|
|
2169
|
-
statusPass.uniforms.uOn.value = on;
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
/* ---------- drive ---------- */
|
|
2173
|
-
function resize() {
|
|
2174
|
-
if (RENDER) return;
|
|
2175
|
-
W = innerWidth;
|
|
2176
|
-
H = innerHeight;
|
|
2177
|
-
camera.aspect = W / H;
|
|
2178
|
-
camera.updateProjectionMatrix();
|
|
2179
|
-
renderer.setPixelRatio(pixelRatio);
|
|
2180
|
-
renderer.setSize(W, H);
|
|
2181
|
-
composer.setPixelRatio(pixelRatio);
|
|
2182
|
-
composer.setSize(W, H);
|
|
2183
|
-
bokeh.uniforms.aspect.value = camera.aspect;
|
|
2184
|
-
filmPass.uniforms.uRes.value.set(W * pixelRatio, H * pixelRatio);
|
|
2185
|
-
titlePass.uniforms.uAspect.value = W / H;
|
|
2186
|
-
titlePass.uniforms.uRect.value.w = (TITLE_W * (W / H)) / 4;
|
|
2187
|
-
statusPass.uniforms.uRect.value.z = (STATUS_H * status.aspect) / (W / H);
|
|
2188
|
-
}
|
|
2189
|
-
filmPass.uniforms.uRes.value.set(W * pixelRatio, H * pixelRatio);
|
|
2190
|
-
addEventListener('resize', resize);
|
|
2191
|
-
|
|
2192
|
-
update(0);
|
|
2193
|
-
renderer.compile(scene, camera);
|
|
2194
|
-
|
|
2195
|
-
if (RENDER) {
|
|
2196
|
-
window.__renderAt = (t) => {
|
|
2197
|
-
update(t);
|
|
2198
|
-
composer.render();
|
|
2199
|
-
return true;
|
|
2200
|
-
};
|
|
2201
|
-
window.__duration = T.end;
|
|
2202
|
-
window.__renderAudio = renderSoundtrack; // base64 WAV, 48 kHz stereo, same timeline
|
|
2203
|
-
document.getElementById('hint').remove();
|
|
2204
|
-
window.__ready = true;
|
|
2205
|
-
} else {
|
|
2206
|
-
// Live: the timeline runs on the audio clock so picture and sound stay locked.
|
|
2207
|
-
// Browsers only allow sound after a gesture, so it waits on black for a click or key.
|
|
2208
|
-
const hint = document.getElementById('hint');
|
|
2209
|
-
let actx = null,
|
|
2210
|
-
outGain = null,
|
|
2211
|
-
cycleOut = null,
|
|
2212
|
-
cycleStart = 0,
|
|
2213
|
-
nextQueued = false,
|
|
2214
|
-
started = false,
|
|
2215
|
-
clockStart = 0,
|
|
2216
|
-
muted = false;
|
|
2217
|
-
let frames = 0,
|
|
2218
|
-
acc = 0,
|
|
2219
|
-
tuned = false,
|
|
2220
|
-
last = 0,
|
|
2221
|
-
introCompleteSent = false;
|
|
2222
|
-
const schedule = (at) => {
|
|
2223
|
-
const g = actx.createGain();
|
|
2224
|
-
g.connect(outGain);
|
|
2225
|
-
buildSound(actx, at, g);
|
|
2226
|
-
return g;
|
|
2227
|
-
};
|
|
2228
|
-
function begin() {
|
|
2229
|
-
hint.classList.add('gone');
|
|
2230
|
-
try {
|
|
2231
|
-
if (!actx) {
|
|
2232
|
-
actx = new (window.AudioContext || window.webkitAudioContext)();
|
|
2233
|
-
outGain = actx.createGain();
|
|
2234
|
-
outGain.connect(actx.destination);
|
|
2235
|
-
}
|
|
2236
|
-
actx.resume();
|
|
2237
|
-
} catch (e) {
|
|
2238
|
-
actx = null;
|
|
2239
|
-
}
|
|
2240
|
-
if (actx) {
|
|
2241
|
-
if (cycleOut) {
|
|
2242
|
-
const old = cycleOut;
|
|
2243
|
-
old.gain.setTargetAtTime(0, actx.currentTime, 0.04);
|
|
2244
|
-
setTimeout(() => old.disconnect(), 800);
|
|
2245
|
-
}
|
|
2246
|
-
cycleStart = actx.currentTime + 0.15;
|
|
2247
|
-
cycleOut = schedule(cycleStart);
|
|
2248
|
-
nextQueued = false;
|
|
2249
|
-
} else clockStart = performance.now();
|
|
2250
|
-
started = true;
|
|
2251
|
-
}
|
|
2252
|
-
function clock() {
|
|
2253
|
-
if (!actx) return (performance.now() - clockStart) / 1000;
|
|
2254
|
-
return actx.currentTime - cycleStart - (actx.outputLatency || actx.baseLatency || 0);
|
|
2255
|
-
}
|
|
2256
|
-
function frame(now) {
|
|
2257
|
-
let t = 0;
|
|
2258
|
-
if (started) {
|
|
2259
|
-
t = clock();
|
|
2260
|
-
if (actx && !nextQueued && t > T.loop - 0.6) {
|
|
2261
|
-
cycleOut = schedule(cycleStart + T.loop);
|
|
2262
|
-
nextQueued = true;
|
|
2263
|
-
}
|
|
2264
|
-
if (t >= T.loop) {
|
|
2265
|
-
if (actx) {
|
|
2266
|
-
cycleStart += T.loop;
|
|
2267
|
-
nextQueued = false;
|
|
2268
|
-
} else clockStart += T.loop * 1000;
|
|
2269
|
-
t -= T.loop;
|
|
2270
|
-
}
|
|
2271
|
-
t = Math.max(0, t);
|
|
2272
|
-
}
|
|
2273
|
-
update(t);
|
|
2274
|
-
composer.render();
|
|
2275
|
-
if (EMBED && started && !introCompleteSent && t >= T.end) {
|
|
2276
|
-
introCompleteSent = true;
|
|
2277
|
-
try {
|
|
2278
|
-
window.parent.postMessage(
|
|
2279
|
-
{ source: 'impact-ui-halloween-intro', type: 'complete' },
|
|
2280
|
-
'*'
|
|
2281
|
-
);
|
|
2282
|
-
} catch (_) {}
|
|
2283
|
-
}
|
|
2284
|
-
if (started && !tuned && t > 2) {
|
|
2285
|
-
acc += now - last;
|
|
2286
|
-
frames++;
|
|
2287
|
-
if (frames === 90) {
|
|
2288
|
-
tuned = true;
|
|
2289
|
-
if (acc / frames > 19 && pixelRatio > 1) {
|
|
2290
|
-
pixelRatio = 1;
|
|
2291
|
-
resize();
|
|
2292
|
-
}
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
last = now;
|
|
2296
|
-
requestAnimationFrame(frame);
|
|
2297
|
-
}
|
|
2298
|
-
requestAnimationFrame(frame);
|
|
2299
|
-
if (EMBED) {
|
|
2300
|
-
setTimeout(begin, 150);
|
|
2301
|
-
} else {
|
|
2302
|
-
addEventListener('pointerdown', begin);
|
|
2303
|
-
addEventListener('keydown', (e) => {
|
|
2304
|
-
if (e.key === 'm' || e.key === 'M') {
|
|
2305
|
-
muted = !muted;
|
|
2306
|
-
if (outGain) outGain.gain.setTargetAtTime(muted ? 0 : 1, actx.currentTime, 0.05);
|
|
2307
|
-
} else if (e.key === ' ' || e.key === 'Enter') {
|
|
2308
|
-
e.preventDefault();
|
|
2309
|
-
begin();
|
|
2310
|
-
}
|
|
2311
|
-
});
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
<\/script>
|
|
2315
|
-
</body>
|
|
2316
|
-
</html>
|
|
2317
|
-
`;
|
|
2318
|
-
export {
|
|
2319
|
-
halloweenCandleLightHtml as default
|
|
2320
|
-
};
|