@adatechnology/game-weather-three 0.4.3 → 0.5.1
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/chunk-SOEOSGSU.js +621 -0
- package/dist/chunk-SOEOSGSU.js.map +1 -0
- package/dist/index.d.ts +6 -354
- package/dist/index.global.js +206 -24
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +38 -537
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +379 -0
- package/dist/testing.js +9 -0
- package/dist/testing.js.map +1 -0
- package/package.json +10 -5
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
// src/bench/visual.ts
|
|
2
|
+
import { isLightBudget } from "@adatechnology/game-render-three";
|
|
3
|
+
import { footprintBlocker } from "@adatechnology/game-contracts";
|
|
4
|
+
|
|
5
|
+
// src/headlights.ts
|
|
6
|
+
import { LIGHT_PRIORITY } from "@adatechnology/game-render-three";
|
|
7
|
+
var HEADLIGHTS = {
|
|
8
|
+
spot: { color: 16773584, intensity: 2.4, distance: 26, angle: 0.33, penumbra: 0.5, decay: 1.6 },
|
|
9
|
+
/** Lamp position on the car (x is mirrored), where the spot points, and the cone drawn in the air. */
|
|
10
|
+
lamp: [0.65, 0.74, 2.15],
|
|
11
|
+
aim: [0.65, 0.2, 12],
|
|
12
|
+
cone: { radius: 2.4, length: 9, segments: 18, color: 16773584, opacity: 0.06 },
|
|
13
|
+
lens: { on: 16773584, off: 3815988 },
|
|
14
|
+
/** Lit drops a second, thrown into the beam 1 to 13 m ahead (W1176). */
|
|
15
|
+
lit: { rate: 600, depth: [1, 13], spread: 0.33, color: 13622512, alpha: 0.9, life: 0.06, len: 0.035, velocity: [0.8, -14, 0] },
|
|
16
|
+
/** Beyond this many metres a car keeps only the cone: no real light (W-b text of `farol`). */
|
|
17
|
+
lightRange: 40
|
|
18
|
+
};
|
|
19
|
+
var defaultHeadlightRule = (sample) => sample.nightFactor > 0.3 || sample.rain > 0 || sample.fogDensity > 0.02 || sample.visibility < 150;
|
|
20
|
+
function createHeadlights(THREE, rules = {}) {
|
|
21
|
+
const rigs = /* @__PURE__ */ new Set();
|
|
22
|
+
const cone = HEADLIGHTS.cone;
|
|
23
|
+
const lampWorld = new THREE.Vector3();
|
|
24
|
+
const aimWorld = new THREE.Vector3();
|
|
25
|
+
const forward = new THREE.Vector3();
|
|
26
|
+
const rule = rules.lit ?? defaultHeadlightRule;
|
|
27
|
+
const range = rules.lightRange ?? HEADLIGHTS.lightRange;
|
|
28
|
+
const lights = rules.lights;
|
|
29
|
+
const forwardAxis = new THREE.Vector3(0, 0, -1);
|
|
30
|
+
function lampSource(side, car, targets) {
|
|
31
|
+
const lamp = new THREE.Object3D();
|
|
32
|
+
lamp.position.set(side * HEADLIGHTS.lamp[0], HEADLIGHTS.lamp[1], HEADLIGHTS.lamp[2]);
|
|
33
|
+
const aim = new THREE.Vector3(side * HEADLIGHTS.aim[0], HEADLIGHTS.aim[1], HEADLIGHTS.aim[2]).sub(lamp.position).normalize();
|
|
34
|
+
lamp.quaternion.setFromUnitVectors(forwardAxis, aim);
|
|
35
|
+
car.add(lamp);
|
|
36
|
+
targets.push(lamp);
|
|
37
|
+
const spot = HEADLIGHTS.spot;
|
|
38
|
+
return lights.addSource({ owner: "weather.headlight", kind: "spot", priority: LIGHT_PRIORITY.headlight, color: spot.color, intensity: 0, distance: spot.distance, decay: spot.decay, angle: spot.angle, penumbra: spot.penumbra, position: [0, 0, 0], follow: lamp });
|
|
39
|
+
}
|
|
40
|
+
function legacySpot(side, car, targets) {
|
|
41
|
+
const spot = new THREE.SpotLight(HEADLIGHTS.spot.color, 0, HEADLIGHTS.spot.distance, HEADLIGHTS.spot.angle, HEADLIGHTS.spot.penumbra, HEADLIGHTS.spot.decay);
|
|
42
|
+
spot.position.set(side * HEADLIGHTS.lamp[0], HEADLIGHTS.lamp[1], HEADLIGHTS.lamp[2]);
|
|
43
|
+
const target = new THREE.Object3D();
|
|
44
|
+
target.position.set(side * HEADLIGHTS.aim[0], HEADLIGHTS.aim[1], HEADLIGHTS.aim[2]);
|
|
45
|
+
car.add(spot, target);
|
|
46
|
+
spot.target = target;
|
|
47
|
+
targets.push(target);
|
|
48
|
+
return spot;
|
|
49
|
+
}
|
|
50
|
+
function attach(car, lens) {
|
|
51
|
+
const spots = [];
|
|
52
|
+
const cones = [];
|
|
53
|
+
const targets = [];
|
|
54
|
+
const sources = [];
|
|
55
|
+
for (const side of [-1, 1]) {
|
|
56
|
+
if (lights) sources.push(lampSource(side, car, targets));
|
|
57
|
+
else spots.push(legacySpot(side, car, targets));
|
|
58
|
+
const geometry = new THREE.ConeGeometry(cone.radius, cone.length, cone.segments, 1, true);
|
|
59
|
+
geometry.translate(0, -cone.length / 2, 0);
|
|
60
|
+
geometry.rotateX(-Math.PI / 2);
|
|
61
|
+
const material = new THREE.MeshBasicMaterial({ color: cone.color, transparent: true, opacity: cone.opacity, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide, fog: false });
|
|
62
|
+
const beam = new THREE.Mesh(geometry, material);
|
|
63
|
+
beam.position.set(side * HEADLIGHTS.lamp[0], HEADLIGHTS.lamp[1], HEADLIGHTS.lamp[2]);
|
|
64
|
+
beam.visible = false;
|
|
65
|
+
car.add(beam);
|
|
66
|
+
cones.push(beam);
|
|
67
|
+
}
|
|
68
|
+
const rig = { car, spots, sources, cones, lens, targets };
|
|
69
|
+
rigs.add(rig);
|
|
70
|
+
return () => {
|
|
71
|
+
rigs.delete(rig);
|
|
72
|
+
for (const object of [...spots, ...cones, ...targets]) car.remove(object);
|
|
73
|
+
for (const beam of cones) {
|
|
74
|
+
beam.geometry.dispose();
|
|
75
|
+
beam.material.dispose();
|
|
76
|
+
}
|
|
77
|
+
for (const spot of spots) spot.dispose();
|
|
78
|
+
for (const source of sources) source.remove();
|
|
79
|
+
lens?.color.setHex(HEADLIGHTS.lens.off);
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
attach,
|
|
84
|
+
update(sample, dt, camera, engine, enabled = true) {
|
|
85
|
+
const on = enabled && rule(sample);
|
|
86
|
+
let index = 0;
|
|
87
|
+
for (const rig of rigs) {
|
|
88
|
+
rig.car.updateWorldMatrix(true, false);
|
|
89
|
+
lampWorld.set(0, HEADLIGHTS.lamp[1], HEADLIGHTS.lamp[2]).applyMatrix4(rig.car.matrixWorld);
|
|
90
|
+
const near = Math.hypot(lampWorld.x - camera[0], lampWorld.z - camera[2]) <= range;
|
|
91
|
+
for (const spot of rig.spots) spot.intensity = on && near ? HEADLIGHTS.spot.intensity : 0;
|
|
92
|
+
for (const source of rig.sources) source.update({ intensity: on && near ? HEADLIGHTS.spot.intensity : 0 });
|
|
93
|
+
for (const beam of rig.cones) beam.visible = on;
|
|
94
|
+
rig.lens?.color.setHex(on ? HEADLIGHTS.lens.on : HEADLIGHTS.lens.off);
|
|
95
|
+
if (!on || !engine || sample.rain <= 0 || !near) {
|
|
96
|
+
index++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
aimWorld.set(0, HEADLIGHTS.lamp[1], HEADLIGHTS.lamp[2] + 1).applyMatrix4(rig.car.matrixWorld);
|
|
100
|
+
forward.copy(aimWorld).sub(lampWorld).normalize();
|
|
101
|
+
const side = [forward.z, -forward.x];
|
|
102
|
+
const { random } = engine;
|
|
103
|
+
const lit = HEADLIGHTS.lit;
|
|
104
|
+
engine.emit(`weather-lit-${index}`, "streak", lit.rate, dt, () => {
|
|
105
|
+
const depth = random.between(lit.depth[0], lit.depth[1]);
|
|
106
|
+
const spread = depth * lit.spread * Math.sqrt(random.next());
|
|
107
|
+
const turn = random.between(0, 6.283);
|
|
108
|
+
return {
|
|
109
|
+
p: [lampWorld.x + forward.x * depth + side[0] * Math.cos(turn) * spread, lampWorld.y + forward.y * depth + Math.sin(turn) * spread * 0.6, lampWorld.z + forward.z * depth + side[1] * Math.cos(turn) * spread],
|
|
110
|
+
v: lit.velocity,
|
|
111
|
+
sp: 0,
|
|
112
|
+
life: lit.life,
|
|
113
|
+
len: lit.len,
|
|
114
|
+
c0: lit.color,
|
|
115
|
+
a: lit.alpha
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
index++;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
count: () => rigs.size,
|
|
122
|
+
dispose() {
|
|
123
|
+
for (const rig of [...rigs]) {
|
|
124
|
+
rigs.delete(rig);
|
|
125
|
+
for (const object of [...rig.spots, ...rig.cones, ...rig.targets]) rig.car.remove(object);
|
|
126
|
+
for (const source of rig.sources) source.remove();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/fog.ts
|
|
133
|
+
import { createFog as createStableFog } from "@adatechnology/game-render-three";
|
|
134
|
+
|
|
135
|
+
// src/sky.ts
|
|
136
|
+
var SKY_NIGHT = 724760;
|
|
137
|
+
var SKY_DAY = 8885926;
|
|
138
|
+
var ZONE_LIGHT = { base: 0.35, sun: 0.65 };
|
|
139
|
+
var ZONE_FOG_DEFAULTS = { color: 3820090, far: 60 };
|
|
140
|
+
function createSky(THREE, scene) {
|
|
141
|
+
const night = new THREE.Color(SKY_NIGHT);
|
|
142
|
+
const day = new THREE.Color(SKY_DAY);
|
|
143
|
+
const zoneColor = new THREE.Color(ZONE_FOG_DEFAULTS.color);
|
|
144
|
+
const tint = new THREE.Color();
|
|
145
|
+
const color = new THREE.Color();
|
|
146
|
+
return {
|
|
147
|
+
color,
|
|
148
|
+
update(sample, zone) {
|
|
149
|
+
color.lerpColors(night, day, sample.sun);
|
|
150
|
+
color.multiplyScalar(sample.skyScale);
|
|
151
|
+
if (zone && zone.weight > 0) {
|
|
152
|
+
zoneColor.setHex(zone.color ?? ZONE_FOG_DEFAULTS.color);
|
|
153
|
+
color.lerp(tint.copy(zoneColor).multiplyScalar(ZONE_LIGHT.base + sample.sun * ZONE_LIGHT.sun), zone.weight);
|
|
154
|
+
}
|
|
155
|
+
const background = scene.background;
|
|
156
|
+
if (background && background.isColor) background.copy(color);
|
|
157
|
+
else scene.background = color.clone();
|
|
158
|
+
return color;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/fog.ts
|
|
164
|
+
var FOG_NEAR = 25;
|
|
165
|
+
var FOG_ZONE_NEAR = 4;
|
|
166
|
+
var FOG_EXP_FROM = 5e-4;
|
|
167
|
+
var densityForFar = (far) => Math.sqrt(Math.log(50)) / far;
|
|
168
|
+
function fogOf(sample, zone, expFrom = FOG_EXP_FROM) {
|
|
169
|
+
const weight = zone?.weight ?? 0;
|
|
170
|
+
const zoneFar = zone?.far ?? ZONE_FOG_DEFAULTS.far;
|
|
171
|
+
if (sample.fogDensity >= expFrom) return { mode: "exp2", density: sample.fogDensity + (densityForFar(zoneFar) - sample.fogDensity) * weight };
|
|
172
|
+
return { mode: "linear", near: FOG_NEAR + (FOG_ZONE_NEAR - FOG_NEAR) * weight, far: sample.fogFar + (zoneFar - sample.fogFar) * weight };
|
|
173
|
+
}
|
|
174
|
+
function createFog(THREE, scene, options = {}) {
|
|
175
|
+
if (options.stable) return stableFog(THREE, scene, options);
|
|
176
|
+
const original = scene.fog;
|
|
177
|
+
const kept = original && original.isFog ? original.clone() : original;
|
|
178
|
+
return {
|
|
179
|
+
update(sample, sky, zone) {
|
|
180
|
+
const values = fogOf(sample, zone, options.expFrom);
|
|
181
|
+
const current = scene.fog;
|
|
182
|
+
if (values.mode === "linear") {
|
|
183
|
+
if (current?.isFog) {
|
|
184
|
+
current.color.copy(sky);
|
|
185
|
+
current.near = values.near;
|
|
186
|
+
current.far = values.far;
|
|
187
|
+
} else scene.fog = new THREE.Fog(sky.getHex(), values.near, values.far);
|
|
188
|
+
} else if (current?.isFogExp2) {
|
|
189
|
+
current.color.copy(sky);
|
|
190
|
+
current.density = values.density;
|
|
191
|
+
} else scene.fog = new THREE.FogExp2(sky.getHex(), values.density);
|
|
192
|
+
const fog = scene.fog;
|
|
193
|
+
fog.color.copy(sky);
|
|
194
|
+
},
|
|
195
|
+
clear() {
|
|
196
|
+
scene.fog = kept && kept.isFog ? kept.clone() : kept ?? null;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function stableFog(THREE, scene, options) {
|
|
201
|
+
const controller = createStableFog(THREE, scene, { stable: true });
|
|
202
|
+
return {
|
|
203
|
+
update(sample, sky, zone) {
|
|
204
|
+
const values = fogOf(sample, zone, options.expFrom);
|
|
205
|
+
if (values.mode === "linear") controller.useLinear(sky.getHex(), values.near, values.far);
|
|
206
|
+
else controller.useExp2(sky.getHex(), values.density);
|
|
207
|
+
scene.fog.color.copy(sky);
|
|
208
|
+
},
|
|
209
|
+
clear: () => controller.clear()
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
var MIST = { box: { x: [-14, 10], y: [0.2, 1.1], z: [-3, 8] }, drift: [0.15, 0.35], life: [9, 13], size: [3, 5], grow: 1.4, color: 8227488, alpha: 0.13, fadeIn: 0.35 };
|
|
213
|
+
var ASH = { box: { x: [-10, 8], y: [4, 6], z: [-3, 7] }, fall: -0.35, life: [8, 12], size: [0.025, 0.04], color: 10525328, alpha: 0.75, fadeIn: 0.1 };
|
|
214
|
+
function emitAirborne(engine, sample, dt, camera) {
|
|
215
|
+
const { random } = engine;
|
|
216
|
+
const around = (box) => [
|
|
217
|
+
camera[0] + random.between(box.x[0], box.x[1]),
|
|
218
|
+
random.between(box.y[0], box.y[1]),
|
|
219
|
+
camera[2] + random.between(box.z[0], box.z[1])
|
|
220
|
+
];
|
|
221
|
+
if (sample.mistPerSecond > 0) {
|
|
222
|
+
engine.emit("weather-mist", "normal", sample.mistPerSecond, dt, () => ({
|
|
223
|
+
p: around(MIST.box),
|
|
224
|
+
v: [random.between(MIST.drift[0], MIST.drift[1]), 0, random.between(-0.05, 0.05)],
|
|
225
|
+
sp: 0,
|
|
226
|
+
life: MIST.life,
|
|
227
|
+
size: MIST.size,
|
|
228
|
+
grow: MIST.grow,
|
|
229
|
+
c0: MIST.color,
|
|
230
|
+
a: MIST.alpha,
|
|
231
|
+
fi: MIST.fadeIn
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
if (sample.ashPerSecond > 0) {
|
|
235
|
+
engine.emit("weather-ash", "normal", sample.ashPerSecond, dt, () => ({
|
|
236
|
+
p: around(ASH.box),
|
|
237
|
+
v: [random.between(0.1, 0.4), ASH.fall, random.between(-0.1, 0.1)],
|
|
238
|
+
sp: [0.05, 0.15],
|
|
239
|
+
life: ASH.life,
|
|
240
|
+
size: ASH.size,
|
|
241
|
+
c0: ASH.color,
|
|
242
|
+
a: ASH.alpha,
|
|
243
|
+
fi: ASH.fadeIn,
|
|
244
|
+
gnd: 1
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/heat-haze.ts
|
|
250
|
+
var HEAT_HAZE = { opacity: 0.09, bands: 38, rise: 1.4, color: [1, 0.93, 0.82] };
|
|
251
|
+
var HEAT_HAZE_VERTEX = "varying vec2 vUv; void main(){ vUv = uv; gl_Position = vec4(position.xy, 0., 1.); }";
|
|
252
|
+
var HEAT_HAZE_FRAGMENT = "uniform float uTime; uniform float uHaze; uniform vec3 uColor; varying vec2 vUv; void main(){ float low = 1. - smoothstep(0.15, 0.7, vUv.y); float wave = sin(vUv.y*38. - uTime*1.4 + sin(vUv.x*11. + uTime*.7)*2.2) * .5 + .5; gl_FragColor = vec4(uColor, uHaze*low*wave*.09); }";
|
|
253
|
+
function createHeatHaze(THREE) {
|
|
254
|
+
const uniforms = { uTime: { value: 0 }, uHaze: { value: 0 }, uColor: { value: new THREE.Vector3(...HEAT_HAZE.color) } };
|
|
255
|
+
const material = new THREE.ShaderMaterial({ uniforms, vertexShader: HEAT_HAZE_VERTEX, fragmentShader: HEAT_HAZE_FRAGMENT, transparent: true, depthTest: false, depthWrite: false });
|
|
256
|
+
const object = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
|
|
257
|
+
object.frustumCulled = false;
|
|
258
|
+
object.renderOrder = 999;
|
|
259
|
+
object.name = "weather-heat-haze";
|
|
260
|
+
object.visible = false;
|
|
261
|
+
return {
|
|
262
|
+
object,
|
|
263
|
+
update(sample, time) {
|
|
264
|
+
uniforms.uHaze.value = sample.haze;
|
|
265
|
+
uniforms.uTime.value = time;
|
|
266
|
+
object.visible = sample.haze > 0;
|
|
267
|
+
},
|
|
268
|
+
dispose() {
|
|
269
|
+
object.geometry.dispose();
|
|
270
|
+
material.dispose();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/lightning.ts
|
|
276
|
+
var LIGHTNING = { light: 3.2, background: [0.25, 0.27, 0.32], white: 0.12, skyColor: 3820650, groundColor: 723724 };
|
|
277
|
+
function createLightning(THREE, scene, vfx, options = {}) {
|
|
278
|
+
const leased = options.lights?.leaseHemisphere("weather.lightning") ?? null;
|
|
279
|
+
const light = leased ?? new THREE.HemisphereLight(LIGHTNING.skyColor, LIGHTNING.groundColor, 0);
|
|
280
|
+
light.name = "weather-lightning";
|
|
281
|
+
light.color.setHex(LIGHTNING.skyColor);
|
|
282
|
+
light.groundColor.setHex(LIGHTNING.groundColor);
|
|
283
|
+
if (!options.lights) scene.add(light);
|
|
284
|
+
return {
|
|
285
|
+
light,
|
|
286
|
+
update(sample, background) {
|
|
287
|
+
const flash = sample.flash;
|
|
288
|
+
light.intensity = flash * LIGHTNING.light;
|
|
289
|
+
if (flash <= 0) return;
|
|
290
|
+
if (background) {
|
|
291
|
+
background.r += flash * LIGHTNING.background[0];
|
|
292
|
+
background.g += flash * LIGHTNING.background[1];
|
|
293
|
+
background.b += flash * LIGHTNING.background[2];
|
|
294
|
+
}
|
|
295
|
+
const screen = vfx?.workshop().screen;
|
|
296
|
+
if (screen) screen.white = Math.max(screen.white, flash * LIGHTNING.white);
|
|
297
|
+
},
|
|
298
|
+
dispose() {
|
|
299
|
+
light.intensity = 0;
|
|
300
|
+
if (options.lights) return;
|
|
301
|
+
scene.remove(light);
|
|
302
|
+
light.dispose();
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/rain-near.ts
|
|
308
|
+
import { inPuddle } from "@adatechnology/game-vfx";
|
|
309
|
+
var SPLASH_FROM_MM = 5;
|
|
310
|
+
function createNearRain(vfx, options = {}) {
|
|
311
|
+
const puddles = options.puddles ?? [];
|
|
312
|
+
return {
|
|
313
|
+
update(sample, dt, camera) {
|
|
314
|
+
const raining = sample.rain > 0;
|
|
315
|
+
const engine = raining ? vfx?.workshop() : vfx?.peekWorkshop();
|
|
316
|
+
if (!engine) return;
|
|
317
|
+
const field = engine.rain;
|
|
318
|
+
if (!raining) {
|
|
319
|
+
if (field.on) field.clear();
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
field.puddleAt = options.puddleAt ?? ((x, z) => inPuddle(puddles, x, z));
|
|
323
|
+
field.drive(sample.drops, sample.windX, camera[0], camera[2]);
|
|
324
|
+
field.update(dt, sample.rain >= (options.splashFrom ?? SPLASH_FROM_MM), camera);
|
|
325
|
+
},
|
|
326
|
+
count: () => vfx?.peekWorkshop()?.rain.count ?? 0,
|
|
327
|
+
clear() {
|
|
328
|
+
vfx?.peekWorkshop()?.rain.clear();
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/rain-curtain.ts
|
|
334
|
+
var RAIN_CURTAIN = { count: 14e3, width: 70, height: 25, speed: 13, pointSize: 1.6 };
|
|
335
|
+
var RAIN_CURTAIN_VERTEX = "uniform float uTime; uniform vec3 uCam; void main(){ vec3 p = position; p.y = mod(p.y - uTime*13., 25.); p.xz = uCam.xz + mod(p.xz - uCam.xz + 35., 70.) - 35.; gl_Position = projectionMatrix*viewMatrix*vec4(p,1.); gl_PointSize = 1.6; }";
|
|
336
|
+
var RAIN_CURTAIN_FRAGMENT = "uniform float uDay; void main(){ gl_FragColor = vec4(vec3(.68,.72,.78)*(0.25+0.75*uDay), .22+.1*uDay); }";
|
|
337
|
+
var RAIN_CURTAIN_PROFILES = { low: 0.35, medium: 0.7, high: 1 };
|
|
338
|
+
var RAIN_CURTAIN_FULL_AT = 4e3;
|
|
339
|
+
function rainCurtainPositions(random = Math.random, count = RAIN_CURTAIN.count) {
|
|
340
|
+
const positions = new Float32Array(count * 3);
|
|
341
|
+
for (let index = 0; index < count; index++) {
|
|
342
|
+
positions[index * 3] = (random() - 0.5) * RAIN_CURTAIN.width;
|
|
343
|
+
positions[index * 3 + 1] = random() * RAIN_CURTAIN.height;
|
|
344
|
+
positions[index * 3 + 2] = (random() - 0.5) * RAIN_CURTAIN.width;
|
|
345
|
+
}
|
|
346
|
+
return positions;
|
|
347
|
+
}
|
|
348
|
+
function createRainCurtain(THREE, options = {}) {
|
|
349
|
+
const count = options.count ?? RAIN_CURTAIN.count;
|
|
350
|
+
const geometry = new THREE.BufferGeometry();
|
|
351
|
+
geometry.setAttribute("position", new THREE.BufferAttribute(rainCurtainPositions(options.random, count), 3));
|
|
352
|
+
const uniforms = { uTime: { value: 0 }, uCam: { value: new THREE.Vector3() }, uDay: { value: 1 } };
|
|
353
|
+
const material = new THREE.ShaderMaterial({ uniforms, transparent: true, depthWrite: false, vertexShader: RAIN_CURTAIN_VERTEX, fragmentShader: RAIN_CURTAIN_FRAGMENT });
|
|
354
|
+
const object = new THREE.Points(geometry, material);
|
|
355
|
+
object.frustumCulled = false;
|
|
356
|
+
object.name = "weather-rain-curtain";
|
|
357
|
+
object.visible = false;
|
|
358
|
+
let share = RAIN_CURTAIN_PROFILES[options.profile ?? "high"];
|
|
359
|
+
let drawn = 0;
|
|
360
|
+
return {
|
|
361
|
+
object,
|
|
362
|
+
update(sample, camera, time, daylight) {
|
|
363
|
+
const fullAt = options.fullAt ?? RAIN_CURTAIN_FULL_AT;
|
|
364
|
+
drawn = sample.rain > 0 ? Math.round(count * share * Math.min(1, sample.drops / fullAt)) : 0;
|
|
365
|
+
object.visible = drawn > 0;
|
|
366
|
+
geometry.setDrawRange(0, drawn);
|
|
367
|
+
uniforms.uTime.value = time;
|
|
368
|
+
uniforms.uDay.value = daylight;
|
|
369
|
+
uniforms.uCam.value.set(camera.x, camera.y, camera.z);
|
|
370
|
+
},
|
|
371
|
+
setProfile(profile) {
|
|
372
|
+
share = RAIN_CURTAIN_PROFILES[profile];
|
|
373
|
+
},
|
|
374
|
+
drawn: () => drawn,
|
|
375
|
+
dispose() {
|
|
376
|
+
geometry.dispose();
|
|
377
|
+
material.dispose();
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// src/renderer.ts
|
|
383
|
+
var WEATHER_LAYERS = ["far_rain", "near_rain", "mist", "fog", "sky", "lightning", "headlights", "haze"];
|
|
384
|
+
function createWeatherRenderer(THREE, options) {
|
|
385
|
+
const { scene, camera, weather, vfx } = options;
|
|
386
|
+
const group = new THREE.Group();
|
|
387
|
+
group.name = "weather";
|
|
388
|
+
scene.add(group);
|
|
389
|
+
const curtain = createRainCurtain(THREE, { ...options.random ? { random: options.random } : {}, ...options.profile ? { profile: options.profile } : {} });
|
|
390
|
+
const haze = createHeatHaze(THREE);
|
|
391
|
+
group.add(curtain.object, haze.object);
|
|
392
|
+
const sky = createSky(THREE, scene);
|
|
393
|
+
const fog = createFog(THREE, scene, { stable: options.lights !== void 0 });
|
|
394
|
+
const lightning = createLightning(THREE, scene, vfx, options.lights ? { lights: options.lights } : {});
|
|
395
|
+
const headlights = createHeadlights(THREE, options.lights ? { ...options.headlights, lights: options.lights } : options.headlights);
|
|
396
|
+
const nearRain = createNearRain(vfx, { ...options.puddles ? { puddles: options.puddles } : {}, ...options.puddleAt ? { puddleAt: options.puddleAt } : {} });
|
|
397
|
+
const on = new Map(WEATHER_LAYERS.map((id) => [id, options.layers?.[id] ?? true]));
|
|
398
|
+
let zone = options.zone;
|
|
399
|
+
let time = 0;
|
|
400
|
+
const eye = { x: 0, y: 0, z: 0 };
|
|
401
|
+
const switchedOff = (id) => {
|
|
402
|
+
if (id === "far_rain") curtain.object.visible = false;
|
|
403
|
+
else if (id === "near_rain") nearRain.clear();
|
|
404
|
+
else if (id === "fog") fog.clear();
|
|
405
|
+
else if (id === "lightning") lightning.light.intensity = 0;
|
|
406
|
+
else if (id === "haze") haze.object.visible = false;
|
|
407
|
+
};
|
|
408
|
+
return {
|
|
409
|
+
curtain,
|
|
410
|
+
nearRain,
|
|
411
|
+
fog,
|
|
412
|
+
sky,
|
|
413
|
+
lightning,
|
|
414
|
+
headlights,
|
|
415
|
+
haze,
|
|
416
|
+
update(dt) {
|
|
417
|
+
time += dt;
|
|
418
|
+
const sample = weather.sample();
|
|
419
|
+
const position = camera.position;
|
|
420
|
+
eye.x = position.x;
|
|
421
|
+
eye.y = position.y;
|
|
422
|
+
eye.z = position.z;
|
|
423
|
+
const where = [eye.x, eye.y, eye.z];
|
|
424
|
+
const background = scene.background ?? void 0;
|
|
425
|
+
const ownSky = on.get("sky") === true;
|
|
426
|
+
const color = ownSky ? sky.update(sample, zone) : background && background.isColor ? background : sky.color;
|
|
427
|
+
if (on.get("fog")) fog.update(sample, color, zone);
|
|
428
|
+
if (on.get("lightning")) lightning.update(sample, ownSky ? scene.background : void 0);
|
|
429
|
+
if (on.get("far_rain")) curtain.update(sample, eye, time, sample.sun);
|
|
430
|
+
if (on.get("haze")) haze.update(sample, time);
|
|
431
|
+
if (vfx) {
|
|
432
|
+
if (on.get("near_rain")) nearRain.update(sample, dt, where);
|
|
433
|
+
if (on.get("mist")) emitAirborne(vfx.workshop(), sample, dt, where);
|
|
434
|
+
}
|
|
435
|
+
headlights.update(sample, dt, where, vfx && sample.rain > 0 ? vfx.workshop() : void 0, on.get("headlights"));
|
|
436
|
+
},
|
|
437
|
+
setLayer(id, value) {
|
|
438
|
+
on.set(id, value);
|
|
439
|
+
if (!value) switchedOff(id);
|
|
440
|
+
},
|
|
441
|
+
layer: (id) => on.get(id) === true,
|
|
442
|
+
setZoneFog(next) {
|
|
443
|
+
zone = next;
|
|
444
|
+
},
|
|
445
|
+
setProfile(profile) {
|
|
446
|
+
curtain.setProfile(profile);
|
|
447
|
+
},
|
|
448
|
+
stats: () => ({ rainCurtain: curtain.drawn(), rainNear: nearRain.count(), cars: headlights.count() }),
|
|
449
|
+
dispose() {
|
|
450
|
+
scene.remove(group);
|
|
451
|
+
curtain.dispose();
|
|
452
|
+
haze.dispose();
|
|
453
|
+
lightning.dispose();
|
|
454
|
+
headlights.dispose();
|
|
455
|
+
nearRain.clear();
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/bench/text.ts
|
|
461
|
+
var TEXTS = {
|
|
462
|
+
en: { layers: { far_rain: "Rain curtain", near_rain: "Near rain and splashes", mist: "Mist and ash", sky: "Sky", lightning: "Lightning", headlights: "Headlights", haze: "Heat haze" } },
|
|
463
|
+
"pt-BR": { layers: { far_rain: "Cortina de chuva", near_rain: "Chuva perto e respingos", mist: "N\xE9voa e cinza", sky: "C\xE9u", lightning: "Rel\xE2mpago", headlights: "Far\xF3is", haze: "N\xE9voa de calor" } }
|
|
464
|
+
};
|
|
465
|
+
var weatherVisualTextOf = (locale) => TEXTS[locale] ?? TEXTS[locale.split("-")[0] ?? "en"] ?? TEXTS.en;
|
|
466
|
+
|
|
467
|
+
// src/bench/visual.ts
|
|
468
|
+
var RIG_VEHICLE = "midsize_sedan";
|
|
469
|
+
var LAYER_ORDER = ["far_rain", "near_rain", "mist", "sky", "lightning", "headlights", "haze"];
|
|
470
|
+
var RIG_DEFAULT = [1.6, -2.5, 0];
|
|
471
|
+
function buildRig(THREE, vehicles, key) {
|
|
472
|
+
const group = new THREE.Group();
|
|
473
|
+
group.name = "weather-lamp-rig";
|
|
474
|
+
const car = vehicles?.build(key, { quality: "lite" });
|
|
475
|
+
if (car) {
|
|
476
|
+
car.group.position.z = HEADLIGHTS.lamp[2] + 0.1 - car.length / 2;
|
|
477
|
+
group.add(car.group);
|
|
478
|
+
}
|
|
479
|
+
const footprint = car ? footprintOf(car) : void 0;
|
|
480
|
+
return {
|
|
481
|
+
group,
|
|
482
|
+
...footprint ? { footprint } : {},
|
|
483
|
+
dispose() {
|
|
484
|
+
car?.dispose();
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function createWeatherVisual(options = {}) {
|
|
489
|
+
return {
|
|
490
|
+
layers(locale) {
|
|
491
|
+
const text = weatherVisualTextOf(locale);
|
|
492
|
+
return LAYER_ORDER.map((id) => ({ id, name: text.layers[id], default: options.layers?.[id] ?? true }));
|
|
493
|
+
},
|
|
494
|
+
create({ host, weather }) {
|
|
495
|
+
const THREE = host.THREE;
|
|
496
|
+
const renderer = createWeatherRenderer(THREE, {
|
|
497
|
+
scene: host.scene,
|
|
498
|
+
camera: host.camera,
|
|
499
|
+
weather,
|
|
500
|
+
...options.vfx ? { vfx: options.vfx } : {},
|
|
501
|
+
...options.profile ? { profile: options.profile } : {},
|
|
502
|
+
...options.random ? { random: options.random } : {},
|
|
503
|
+
...options.zone ? { zone: options.zone } : {},
|
|
504
|
+
...options.puddles ? { puddles: options.puddles } : {},
|
|
505
|
+
...options.puddleAt ? { puddleAt: options.puddleAt } : {},
|
|
506
|
+
...options.headlights ? { headlights: options.headlights } : {},
|
|
507
|
+
...options.layers ? { layers: options.layers } : {},
|
|
508
|
+
...isLightBudget(host.lights) ? { lights: host.lights } : {}
|
|
509
|
+
});
|
|
510
|
+
const item = options.rigItem && host.addObject ? options.rigItem : void 0;
|
|
511
|
+
return attachRig(THREE, host, renderer, options.rig === void 0 ? RIG_DEFAULT : options.rig, item ? void 0 : options.vehicles?.(THREE), options.rigVehicle ?? RIG_VEHICLE, item);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function footprintOf(car) {
|
|
516
|
+
if (!car.width) return void 0;
|
|
517
|
+
const z = car.group.position.z;
|
|
518
|
+
return { minX: -car.width / 2, maxX: car.width / 2, minZ: z - car.length / 2, maxZ: z + car.length / 2 };
|
|
519
|
+
}
|
|
520
|
+
function lengthOf(THREE, root) {
|
|
521
|
+
root.updateMatrixWorld(true);
|
|
522
|
+
const inverse = new THREE.Matrix4().copy(root.matrixWorld).invert();
|
|
523
|
+
const box = new THREE.Box3();
|
|
524
|
+
const piece = new THREE.Box3();
|
|
525
|
+
const matrix = new THREE.Matrix4();
|
|
526
|
+
root.traverse((mesh) => {
|
|
527
|
+
const m = mesh;
|
|
528
|
+
if (!m.isMesh || !m.geometry) return;
|
|
529
|
+
if (!m.geometry.boundingBox) m.geometry.computeBoundingBox();
|
|
530
|
+
piece.copy(m.geometry.boundingBox).applyMatrix4(matrix.multiplyMatrices(inverse, m.matrixWorld));
|
|
531
|
+
box.union(piece);
|
|
532
|
+
});
|
|
533
|
+
return box.isEmpty() ? 0 : box.max.z - box.min.z;
|
|
534
|
+
}
|
|
535
|
+
function attachRig(THREE, host, renderer, place, vehicles, key, item) {
|
|
536
|
+
const scene = host.scene;
|
|
537
|
+
const rig = place === false ? void 0 : buildRig(THREE, vehicles, key);
|
|
538
|
+
let detach;
|
|
539
|
+
let unblock;
|
|
540
|
+
if (rig && place) {
|
|
541
|
+
rig.group.position.set(place[0], 0, place[1]);
|
|
542
|
+
rig.group.rotation.y = place[2];
|
|
543
|
+
scene.add(rig.group);
|
|
544
|
+
detach = renderer.headlights.attach(rig.group);
|
|
545
|
+
if (rig.footprint) unblock = host.addBlocker?.(footprintBlocker(rig.group, rig.footprint));
|
|
546
|
+
}
|
|
547
|
+
let car = null;
|
|
548
|
+
let stopWaiting;
|
|
549
|
+
if (item && place) {
|
|
550
|
+
const tryPlace = () => {
|
|
551
|
+
if (car || !host.addObject) return;
|
|
552
|
+
const placed = host.addObject(item, { position: [place[0], 0, place[1]], rotationY: place[2] });
|
|
553
|
+
if (!placed) return;
|
|
554
|
+
car = placed;
|
|
555
|
+
stopWaiting?.();
|
|
556
|
+
stopWaiting = void 0;
|
|
557
|
+
const ahead = lengthOf(THREE, placed.root) / 2 - HEADLIGHTS.lamp[2] - 0.1;
|
|
558
|
+
rig.group.position.set(place[0] + Math.sin(place[2]) * ahead, 0, place[1] + Math.cos(place[2]) * ahead);
|
|
559
|
+
};
|
|
560
|
+
tryPlace();
|
|
561
|
+
if (!car) stopWaiting = host.onFrame(tryPlace);
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
update: (dt) => renderer.update(dt),
|
|
565
|
+
setLayer(id, on) {
|
|
566
|
+
if (WEATHER_IDS.includes(id)) renderer.setLayer(id, on);
|
|
567
|
+
},
|
|
568
|
+
dispose() {
|
|
569
|
+
detach?.();
|
|
570
|
+
unblock?.();
|
|
571
|
+
stopWaiting?.();
|
|
572
|
+
car?.remove();
|
|
573
|
+
if (rig) {
|
|
574
|
+
scene.remove(rig.group);
|
|
575
|
+
rig.dispose();
|
|
576
|
+
}
|
|
577
|
+
renderer.dispose();
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
var WEATHER_IDS = ["far_rain", "near_rain", "mist", "fog", "sky", "lightning", "headlights", "haze"];
|
|
582
|
+
|
|
583
|
+
export {
|
|
584
|
+
SKY_NIGHT,
|
|
585
|
+
SKY_DAY,
|
|
586
|
+
ZONE_LIGHT,
|
|
587
|
+
ZONE_FOG_DEFAULTS,
|
|
588
|
+
createSky,
|
|
589
|
+
FOG_NEAR,
|
|
590
|
+
FOG_ZONE_NEAR,
|
|
591
|
+
FOG_EXP_FROM,
|
|
592
|
+
densityForFar,
|
|
593
|
+
fogOf,
|
|
594
|
+
createFog,
|
|
595
|
+
MIST,
|
|
596
|
+
ASH,
|
|
597
|
+
emitAirborne,
|
|
598
|
+
HEADLIGHTS,
|
|
599
|
+
defaultHeadlightRule,
|
|
600
|
+
createHeadlights,
|
|
601
|
+
HEAT_HAZE,
|
|
602
|
+
HEAT_HAZE_VERTEX,
|
|
603
|
+
HEAT_HAZE_FRAGMENT,
|
|
604
|
+
createHeatHaze,
|
|
605
|
+
LIGHTNING,
|
|
606
|
+
createLightning,
|
|
607
|
+
SPLASH_FROM_MM,
|
|
608
|
+
createNearRain,
|
|
609
|
+
RAIN_CURTAIN,
|
|
610
|
+
RAIN_CURTAIN_VERTEX,
|
|
611
|
+
RAIN_CURTAIN_FRAGMENT,
|
|
612
|
+
RAIN_CURTAIN_PROFILES,
|
|
613
|
+
RAIN_CURTAIN_FULL_AT,
|
|
614
|
+
rainCurtainPositions,
|
|
615
|
+
createRainCurtain,
|
|
616
|
+
WEATHER_LAYERS,
|
|
617
|
+
createWeatherRenderer,
|
|
618
|
+
weatherVisualTextOf,
|
|
619
|
+
createWeatherVisual
|
|
620
|
+
};
|
|
621
|
+
//# sourceMappingURL=chunk-SOEOSGSU.js.map
|