@eva/plugin-renderer-rain-puddle 2.1.0-beta.14
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/README.md +36 -0
- package/dist/EVA.plugin.renderer.rain.puddle.js +529 -0
- package/dist/EVA.plugin.renderer.rain.puddle.min.js +1 -0
- package/dist/plugin-renderer-rain-puddle.cjs.js +524 -0
- package/dist/plugin-renderer-rain-puddle.cjs.prod.js +1 -0
- package/dist/plugin-renderer-rain-puddle.d.ts +116 -0
- package/dist/plugin-renderer-rain-puddle.esm.js +511 -0
- package/index.js +7 -0
- package/package.json +27 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var eva_js = require('@eva/eva.js');
|
|
6
|
+
var pluginRendererShaderEffect = require('@eva/plugin-renderer-shader-effect');
|
|
7
|
+
var pixi_js = require('pixi.js');
|
|
8
|
+
var pluginRendererSceneCapture = require('@eva/plugin-renderer-scene-capture');
|
|
9
|
+
|
|
10
|
+
const RIPPLE_STRIDE = 4;
|
|
11
|
+
const DEFAULT_MAX_RIPPLES = 4;
|
|
12
|
+
const MAX_SHADER_RIPPLES = 8;
|
|
13
|
+
/**
|
|
14
|
+
* Runtime state shared by the rain and puddle effects.
|
|
15
|
+
*
|
|
16
|
+
* Ripples live in a fixed-size ring so pointer and footstep events never
|
|
17
|
+
* allocate per frame. Each vec4 is `(x, y, ageSeconds, strength)`; expired
|
|
18
|
+
* slots use `ageSeconds = -1`.
|
|
19
|
+
*/
|
|
20
|
+
class RainPuddle extends eva_js.Component {
|
|
21
|
+
constructor() {
|
|
22
|
+
super(...arguments);
|
|
23
|
+
this.rainAmount = 0.65;
|
|
24
|
+
this.waterLevel = 0.5;
|
|
25
|
+
this.groundLevel = 0.545;
|
|
26
|
+
this.groundFeather = 0.02;
|
|
27
|
+
this.reflectionStrength = 0.55;
|
|
28
|
+
this.rippleDuration = 1.6;
|
|
29
|
+
this.maxRipples = DEFAULT_MAX_RIPPLES;
|
|
30
|
+
this.quality = 'high';
|
|
31
|
+
this.reflectionTexture = '';
|
|
32
|
+
this.rainSurfaceName = 'rain-surface';
|
|
33
|
+
this.puddleSurfaceName = 'puddle-surface';
|
|
34
|
+
this.rippleUniformData = new Float32Array(DEFAULT_MAX_RIPPLES * RIPPLE_STRIDE);
|
|
35
|
+
this.rippleDirectionUniformData = new Float32Array(DEFAULT_MAX_RIPPLES * 4);
|
|
36
|
+
this.clickRippleUniformData = new Float32Array([-2, -2, 99, 0]);
|
|
37
|
+
this.activeRippleCount = 0;
|
|
38
|
+
this.nextRippleIndex = 0;
|
|
39
|
+
}
|
|
40
|
+
init(params = {}) {
|
|
41
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
42
|
+
this.rainAmount = clamp01((_a = params.rainAmount) !== null && _a !== void 0 ? _a : this.rainAmount);
|
|
43
|
+
this.waterLevel = clamp01((_b = params.waterLevel) !== null && _b !== void 0 ? _b : this.waterLevel);
|
|
44
|
+
this.groundLevel = clamp01((_c = params.groundLevel) !== null && _c !== void 0 ? _c : this.groundLevel);
|
|
45
|
+
this.groundFeather = clamp((_d = params.groundFeather) !== null && _d !== void 0 ? _d : this.groundFeather, 0.001, 1);
|
|
46
|
+
this.reflectionStrength = clamp01((_e = params.reflectionStrength) !== null && _e !== void 0 ? _e : this.reflectionStrength);
|
|
47
|
+
this.rippleDuration = Math.max(0.05, (_f = params.rippleDuration) !== null && _f !== void 0 ? _f : this.rippleDuration);
|
|
48
|
+
this.maxRipples = clampInteger((_g = params.maxRipples) !== null && _g !== void 0 ? _g : this.maxRipples, 1, MAX_SHADER_RIPPLES);
|
|
49
|
+
this.quality = (_h = params.quality) !== null && _h !== void 0 ? _h : this.quality;
|
|
50
|
+
this.reflectionTexture = (_j = params.reflectionTexture) !== null && _j !== void 0 ? _j : this.reflectionTexture;
|
|
51
|
+
this.rainSurfaceName = (_k = params.rainSurfaceName) !== null && _k !== void 0 ? _k : this.rainSurfaceName;
|
|
52
|
+
this.puddleSurfaceName = (_l = params.puddleSurfaceName) !== null && _l !== void 0 ? _l : this.puddleSurfaceName;
|
|
53
|
+
this.rippleUniformData = new Float32Array(this.maxRipples * RIPPLE_STRIDE);
|
|
54
|
+
this.rippleDirectionUniformData = new Float32Array(this.maxRipples * 4);
|
|
55
|
+
this.clickRippleUniformData = new Float32Array([-2, -2, 99, 0]);
|
|
56
|
+
this.resetRipples();
|
|
57
|
+
}
|
|
58
|
+
emitRipple(input) {
|
|
59
|
+
var _a;
|
|
60
|
+
const index = this.nextRippleIndex;
|
|
61
|
+
const offset = index * RIPPLE_STRIDE;
|
|
62
|
+
const wasActive = this.rippleUniformData[offset + 2] >= 0;
|
|
63
|
+
this.rippleUniformData[offset] = clamp01(input.x);
|
|
64
|
+
this.rippleUniformData[offset + 1] = clamp01(input.y);
|
|
65
|
+
this.rippleUniformData[offset + 2] = 0;
|
|
66
|
+
this.rippleUniformData[offset + 3] = clamp01((_a = input.strength) !== null && _a !== void 0 ? _a : 1);
|
|
67
|
+
const direction = normalizedDirection(input.direction);
|
|
68
|
+
const directionOffset = index * 4;
|
|
69
|
+
this.rippleDirectionUniformData[directionOffset] = direction.x;
|
|
70
|
+
this.rippleDirectionUniformData[directionOffset + 1] = direction.y;
|
|
71
|
+
if (!wasActive)
|
|
72
|
+
this.activeRippleCount += 1;
|
|
73
|
+
this.nextRippleIndex = (index + 1) % this.maxRipples;
|
|
74
|
+
}
|
|
75
|
+
/** Click ripples have an independent lifetime and never consume a footstep slot. */
|
|
76
|
+
emitClickRipple(input) {
|
|
77
|
+
var _a;
|
|
78
|
+
this.clickRippleUniformData[0] = clamp01(input.x);
|
|
79
|
+
this.clickRippleUniformData[1] = clamp01(input.y);
|
|
80
|
+
this.clickRippleUniformData[2] = 0;
|
|
81
|
+
this.clickRippleUniformData[3] = clamp01((_a = input.strength) !== null && _a !== void 0 ? _a : 1);
|
|
82
|
+
}
|
|
83
|
+
advanceRipples(deltaSeconds) {
|
|
84
|
+
if (!(deltaSeconds > 0))
|
|
85
|
+
return;
|
|
86
|
+
for (let index = 0; index < this.maxRipples; index += 1) {
|
|
87
|
+
const ageOffset = index * RIPPLE_STRIDE + 2;
|
|
88
|
+
const age = this.rippleUniformData[ageOffset];
|
|
89
|
+
if (age < 0)
|
|
90
|
+
continue;
|
|
91
|
+
const nextAge = age + deltaSeconds;
|
|
92
|
+
if (nextAge >= this.rippleDuration) {
|
|
93
|
+
this.rippleUniformData[ageOffset] = -1;
|
|
94
|
+
this.activeRippleCount -= 1;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
this.rippleUniformData[ageOffset] = nextAge;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (this.clickRippleUniformData[2] >= 0 && this.clickRippleUniformData[2] < this.rippleDuration) {
|
|
101
|
+
this.clickRippleUniformData[2] += deltaSeconds;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
getRippleDirection(index) {
|
|
105
|
+
var _a, _b;
|
|
106
|
+
const offset = index * 4;
|
|
107
|
+
return {
|
|
108
|
+
x: (_a = this.rippleDirectionUniformData[offset]) !== null && _a !== void 0 ? _a : 0,
|
|
109
|
+
y: (_b = this.rippleDirectionUniformData[offset + 1]) !== null && _b !== void 0 ? _b : 0,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
resetRipples() {
|
|
113
|
+
this.rippleUniformData.fill(0);
|
|
114
|
+
for (let index = 0; index < this.maxRipples; index += 1) {
|
|
115
|
+
this.rippleUniformData[index * RIPPLE_STRIDE + 2] = -1;
|
|
116
|
+
}
|
|
117
|
+
this.rippleDirectionUniformData.fill(0);
|
|
118
|
+
this.clickRippleUniformData.set([-2, -2, 99, 0]);
|
|
119
|
+
this.nextRippleIndex = 0;
|
|
120
|
+
this.activeRippleCount = 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
RainPuddle.componentName = 'RainPuddle';
|
|
124
|
+
function normalizedDirection(direction) {
|
|
125
|
+
var _a, _b;
|
|
126
|
+
const x = typeof direction === 'number' ? direction : Number((_a = direction === null || direction === void 0 ? void 0 : direction.x) !== null && _a !== void 0 ? _a : 0);
|
|
127
|
+
const y = typeof direction === 'number' ? 0 : Number((_b = direction === null || direction === void 0 ? void 0 : direction.y) !== null && _b !== void 0 ? _b : 0);
|
|
128
|
+
const length = Math.hypot(x, y);
|
|
129
|
+
if (!(length > 0))
|
|
130
|
+
return { x: 0, y: 1 };
|
|
131
|
+
return { x: x / length, y: y / length };
|
|
132
|
+
}
|
|
133
|
+
function clamp(value, min, max) {
|
|
134
|
+
return Math.min(max, Math.max(min, value));
|
|
135
|
+
}
|
|
136
|
+
function clamp01(value) {
|
|
137
|
+
return clamp(value, 0, 1);
|
|
138
|
+
}
|
|
139
|
+
function clampInteger(value, min, max) {
|
|
140
|
+
return Math.round(clamp(value, min, max));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/******************************************************************************
|
|
144
|
+
Copyright (c) Microsoft Corporation.
|
|
145
|
+
|
|
146
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
147
|
+
purpose with or without fee is hereby granted.
|
|
148
|
+
|
|
149
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
150
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
151
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
152
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
153
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
154
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
155
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
156
|
+
***************************************************************************** */
|
|
157
|
+
|
|
158
|
+
function __decorate(decorators, target, key, desc) {
|
|
159
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
160
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
161
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
162
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
166
|
+
var e = new Error(message);
|
|
167
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/** Advances bounded ripples and keeps both visual passes on one shared state. */
|
|
171
|
+
let RainPuddleSystem = class RainPuddleSystem extends eva_js.System {
|
|
172
|
+
constructor() {
|
|
173
|
+
super(...arguments);
|
|
174
|
+
this.name = 'RainPuddleSystem';
|
|
175
|
+
this.bindings = new Map();
|
|
176
|
+
}
|
|
177
|
+
update() {
|
|
178
|
+
this.consumeObserverChanges();
|
|
179
|
+
}
|
|
180
|
+
consumeObserverChanges() {
|
|
181
|
+
// Plain Eva Systems must drain their observer queue themselves. Renderer
|
|
182
|
+
// subclasses get this behaviour from Renderer.update(), but this logic-only
|
|
183
|
+
// system deliberately extends System and therefore has to dispatch the
|
|
184
|
+
// queued ADD/REMOVE notifications explicitly.
|
|
185
|
+
for (const changed of this.componentObserver.clear()) {
|
|
186
|
+
this.componentChanged(changed);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
componentChanged(changed) {
|
|
190
|
+
if (changed.componentName !== RainPuddle.componentName)
|
|
191
|
+
return;
|
|
192
|
+
if (changed.type === eva_js.OBSERVER_TYPE.REMOVE) {
|
|
193
|
+
this.bindings.delete(changed.gameObject.id);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (changed.type === eva_js.OBSERVER_TYPE.ADD) {
|
|
197
|
+
this.bindings.set(changed.gameObject.id, { component: changed.component });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
frameStart(frame) {
|
|
201
|
+
// A physical frame may contain zero fixed simulation updates. Drain here as
|
|
202
|
+
// well so a newly-added controller can affect the very first presentation.
|
|
203
|
+
this.consumeObserverChanges();
|
|
204
|
+
const deltaSeconds = Math.min(0.1, Math.max(0, frame.rafDeltaTime / 1000));
|
|
205
|
+
for (const binding of this.bindings.values()) {
|
|
206
|
+
const component = binding.component;
|
|
207
|
+
component.advanceRipples(deltaSeconds);
|
|
208
|
+
binding.rainEffect = this.resolveEffect(binding.rainEffect, component.rainSurfaceName);
|
|
209
|
+
binding.puddleEffect = this.resolveEffect(binding.puddleEffect, component.puddleSurfaceName);
|
|
210
|
+
if (binding.rainEffect) {
|
|
211
|
+
binding.rainEffect.uniforms.rainAmount = component.rainAmount;
|
|
212
|
+
binding.rainEffect.uniforms.quality = qualityValue(component.quality);
|
|
213
|
+
}
|
|
214
|
+
if (binding.puddleEffect) {
|
|
215
|
+
binding.puddleEffect.uniforms.waterLevel = component.waterLevel;
|
|
216
|
+
binding.puddleEffect.uniforms.groundLevel = component.groundLevel;
|
|
217
|
+
binding.puddleEffect.uniforms.groundFeather = component.groundFeather;
|
|
218
|
+
binding.puddleEffect.uniforms.reflectionStrength = component.reflectionStrength;
|
|
219
|
+
binding.puddleEffect.uniforms.rippleDuration = component.rippleDuration;
|
|
220
|
+
binding.puddleEffect.uniforms.ripples = component.rippleUniformData;
|
|
221
|
+
binding.puddleEffect.uniforms.rippleDirections = component.rippleDirectionUniformData;
|
|
222
|
+
binding.puddleEffect.uniforms.clickRipple = component.clickRippleUniformData;
|
|
223
|
+
binding.puddleEffect.uniforms.reflectionTexture = component.reflectionTexture;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
onDestroy() {
|
|
228
|
+
this.bindings.clear();
|
|
229
|
+
}
|
|
230
|
+
resolveEffect(current, name) {
|
|
231
|
+
var _a, _b, _c;
|
|
232
|
+
if ((current === null || current === void 0 ? void 0 : current.gameObject) && !current.gameObject.destroyed)
|
|
233
|
+
return current;
|
|
234
|
+
if (!name)
|
|
235
|
+
return undefined;
|
|
236
|
+
const gameObject = (_c = (_b = (_a = this.game) === null || _a === void 0 ? void 0 : _a.findAllByName) === null || _b === void 0 ? void 0 : _b.call(_a, name)) === null || _c === void 0 ? void 0 : _c[0];
|
|
237
|
+
return gameObject === null || gameObject === void 0 ? void 0 : gameObject.getComponent(pluginRendererShaderEffect.ShaderEffect);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
RainPuddleSystem.systemName = 'RainPuddleSystem';
|
|
241
|
+
RainPuddleSystem = __decorate([
|
|
242
|
+
eva_js.decorators.componentObserver({ RainPuddle: [] })
|
|
243
|
+
], RainPuddleSystem);
|
|
244
|
+
var RainPuddleSystem$1 = RainPuddleSystem;
|
|
245
|
+
function qualityValue(quality) {
|
|
246
|
+
if (quality === 'low')
|
|
247
|
+
return 0;
|
|
248
|
+
if (quality === 'medium')
|
|
249
|
+
return 0.55;
|
|
250
|
+
return 1;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const RAIN_EFFECT_ID = 'eva.rain-puddle.rain';
|
|
254
|
+
const PUDDLE_EFFECT_ID = 'eva.rain-puddle.puddle';
|
|
255
|
+
// Pixi's default filter vertex maps vTextureCoord into the pooled input
|
|
256
|
+
// texture, whose physical size may be larger than the filter frame. These
|
|
257
|
+
// effects use Godot-style SCREEN_UV, so keep aPosition as the normalized
|
|
258
|
+
// screen coordinate instead of inheriting the pooled-texture scale.
|
|
259
|
+
const normalizedScreenFilterVert = `
|
|
260
|
+
in vec2 aPosition;
|
|
261
|
+
out vec2 vTextureCoord;
|
|
262
|
+
|
|
263
|
+
uniform vec4 uOutputFrame;
|
|
264
|
+
uniform vec4 uOutputTexture;
|
|
265
|
+
|
|
266
|
+
void main() {
|
|
267
|
+
vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;
|
|
268
|
+
position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;
|
|
269
|
+
position.y = position.y * (2.0 * uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;
|
|
270
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
271
|
+
vTextureCoord = aPosition;
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
const rainFragment = `
|
|
275
|
+
in vec2 vTextureCoord;
|
|
276
|
+
out vec4 finalColor;
|
|
277
|
+
|
|
278
|
+
uniform sampler2D uTexture;
|
|
279
|
+
uniform float uTime;
|
|
280
|
+
uniform float uRainAmount;
|
|
281
|
+
uniform float uQuality;
|
|
282
|
+
|
|
283
|
+
float hash21(vec2 p) {
|
|
284
|
+
return fract(sin(dot(p, vec2(41.31, 289.17))) * 28419.231);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
float rainLayer(vec2 uv, float scale, float speed, float width, float length, float brightness) {
|
|
288
|
+
vec2 p = uv * vec2(150.0 * scale, 78.0 * scale);
|
|
289
|
+
p.x += p.y * 0.24;
|
|
290
|
+
// Texture coordinates grow downwards in Pixi. Subtracting time makes the
|
|
291
|
+
// streak cells travel towards increasing screen Y instead of rising upward.
|
|
292
|
+
p.y -= uTime * speed;
|
|
293
|
+
vec2 id = floor(p);
|
|
294
|
+
vec2 cell = fract(p) - 0.5;
|
|
295
|
+
float seed = hash21(id);
|
|
296
|
+
float thinLine = 1.0 - smoothstep(width, width * 2.0, abs(cell.x + seed * 0.16 - 0.08));
|
|
297
|
+
float longStreak = 1.0 - smoothstep(length, length * 1.35, abs(cell.y));
|
|
298
|
+
return thinLine * longStreak * brightness * step(1.0 - uRainAmount, seed);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
void main() {
|
|
302
|
+
vec2 uv = vTextureCoord;
|
|
303
|
+
float detail = mix(0.78, 1.0, uQuality);
|
|
304
|
+
float rain = rainLayer(uv, 0.72 * detail, 1.15, 0.010, 0.42, 0.28);
|
|
305
|
+
rain += rainLayer(uv + vec2(0.17, 0.0), 1.28 * detail, 1.78, 0.007, 0.46, 0.62);
|
|
306
|
+
float alpha = clamp(rain, 0.0, 0.82);
|
|
307
|
+
finalColor = vec4(vec3(0.66, 0.82, 0.96) * alpha, alpha);
|
|
308
|
+
}
|
|
309
|
+
`;
|
|
310
|
+
const puddleFragment = `
|
|
311
|
+
in vec2 vTextureCoord;
|
|
312
|
+
out vec4 finalColor;
|
|
313
|
+
|
|
314
|
+
uniform sampler2D uTexture;
|
|
315
|
+
uniform sampler2D uReflection;
|
|
316
|
+
uniform float uTime;
|
|
317
|
+
uniform float uWaterLevel;
|
|
318
|
+
uniform float uGroundLevel;
|
|
319
|
+
uniform float uGroundFeather;
|
|
320
|
+
uniform float uReflectionStrength;
|
|
321
|
+
uniform float uRippleDuration;
|
|
322
|
+
uniform vec4 uRipples[${MAX_SHADER_RIPPLES}];
|
|
323
|
+
uniform vec4 uRippleDirections[${MAX_SHADER_RIPPLES}];
|
|
324
|
+
uniform vec4 uClickRipple;
|
|
325
|
+
|
|
326
|
+
float puddleShape(vec2 uv, vec2 center, vec2 radii, float seed) {
|
|
327
|
+
vec2 p = (uv - center) / radii;
|
|
328
|
+
float angle = atan(p.y, p.x);
|
|
329
|
+
float edgeNoise = sin(angle * 5.0 + seed) * 0.075 + sin(angle * 9.0 - seed) * 0.035;
|
|
330
|
+
float distanceToCenter = length(p) + edgeNoise;
|
|
331
|
+
return 1.0 - smoothstep(0.72 + (1.0 - uWaterLevel) * 0.20, 1.02, distanceToCenter);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
float ring(vec2 uv, vec2 center, float radius, float width) {
|
|
335
|
+
float distanceToCenter = length(uv - center);
|
|
336
|
+
return 1.0 - smoothstep(width, width * 2.1, abs(distanceToCenter - radius));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
float projectedFootWave(vec2 uv, vec4 state, vec2 direction) {
|
|
340
|
+
float phase = clamp(state.z / 1.10, 0.0, 1.0);
|
|
341
|
+
float fade = (1.0 - phase) * state.w;
|
|
342
|
+
vec2 wakeCenter = state.xy - direction * phase * 0.014;
|
|
343
|
+
vec2 delta = uv - wakeCenter;
|
|
344
|
+
float projectedDistance = length(vec2(delta.x, delta.y * 3.0));
|
|
345
|
+
float radius = 0.006 + phase * 0.058;
|
|
346
|
+
float leading = 1.0 - smoothstep(0.0018, 0.0040, abs(projectedDistance - radius));
|
|
347
|
+
float secondary = 1.0 - smoothstep(0.0015, 0.0033, abs(projectedDistance - radius * 0.63));
|
|
348
|
+
vec2 trailDelta = uv - (wakeCenter - direction * 0.018 * phase);
|
|
349
|
+
float trailDistance = length(vec2(trailDelta.x, trailDelta.y * 3.0));
|
|
350
|
+
float trail = 1.0 - smoothstep(0.0020, 0.0045, abs(trailDistance - radius * 0.76));
|
|
351
|
+
return (leading + secondary * 0.34 + trail * 0.42) * fade;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
void main() {
|
|
355
|
+
vec2 uv = vTextureCoord;
|
|
356
|
+
float mask = puddleShape(uv, vec2(0.26, 0.68), vec2(0.21, 0.12), 0.3);
|
|
357
|
+
mask = max(mask, puddleShape(uv, vec2(0.56, 0.77), vec2(0.26, 0.10), 2.2));
|
|
358
|
+
mask = max(mask, puddleShape(uv, vec2(0.82, 0.59), vec2(0.14, 0.18), 4.1));
|
|
359
|
+
mask *= smoothstep(uGroundLevel, uGroundLevel + max(uGroundFeather, 0.0001), uv.y);
|
|
360
|
+
mask *= smoothstep(0.04, 0.17, uWaterLevel);
|
|
361
|
+
|
|
362
|
+
float smallWaves = sin(uv.y * 920.0 + uTime * 2.2) + sin(uv.x * 700.0 - uTime * 1.7);
|
|
363
|
+
vec2 distortion = vec2(smallWaves * 0.00075, sin(uv.x * 510.0 + uTime * 2.0) * 0.0011);
|
|
364
|
+
|
|
365
|
+
for (int index = 0; index < 5; index++) {
|
|
366
|
+
float fi = float(index);
|
|
367
|
+
float phase = fract(uTime * (0.13 + fi * 0.023) + fi * 0.618);
|
|
368
|
+
vec2 center = vec2(fract(fi * 0.347 + 0.14), fract(fi * 0.613 + 0.52));
|
|
369
|
+
float radius = phase * 0.075;
|
|
370
|
+
float rainRipple = ring(uv, center, radius, 0.0018) * (1.0 - phase);
|
|
371
|
+
distortion += normalize(uv - center + vec2(0.0001)) * rainRipple * 0.004;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
float clickPhase = clamp(uClickRipple.z / 1.55, 0.0, 1.0);
|
|
375
|
+
float clickRing = ring(uv, uClickRipple.xy, clickPhase * 0.14, 0.0025) * (1.0 - clickPhase) * uClickRipple.w;
|
|
376
|
+
distortion += normalize(uv - uClickRipple.xy + vec2(0.0001)) * clickRing * 0.012;
|
|
377
|
+
|
|
378
|
+
float footRing = 0.0;
|
|
379
|
+
vec2 footPush = vec2(0.0);
|
|
380
|
+
for (int index = 0; index < ${MAX_SHADER_RIPPLES}; index++) {
|
|
381
|
+
vec4 state = uRipples[index];
|
|
382
|
+
if (state.z < 0.0 || state.z >= uRippleDuration) continue;
|
|
383
|
+
float wave = projectedFootWave(uv, state, uRippleDirections[index].xy);
|
|
384
|
+
footRing += wave;
|
|
385
|
+
footPush += (uv - state.xy) * wave;
|
|
386
|
+
}
|
|
387
|
+
footRing = clamp(footRing, 0.0, 1.5);
|
|
388
|
+
distortion += normalize(footPush + vec2(0.0001)) * footRing * 0.0065;
|
|
389
|
+
|
|
390
|
+
vec4 reflection = texture(uReflection, clamp(uv + distortion, vec2(0.002), vec2(0.998)));
|
|
391
|
+
vec3 waterTint = vec3(0.055, 0.15, 0.23);
|
|
392
|
+
float reflectedAmount = reflection.a * uReflectionStrength;
|
|
393
|
+
vec3 color = mix(waterTint, reflection.rgb + vec3(0.03, 0.06, 0.09), reflectedAmount);
|
|
394
|
+
color += vec3(0.30, 0.48, 0.58) * (clickRing + footRing * 1.10 + 0.18 * max(0.0, smallWaves)) * 0.24;
|
|
395
|
+
float alpha = mask * (0.82 + 0.16 * uWaterLevel);
|
|
396
|
+
finalColor = vec4(color * alpha, alpha);
|
|
397
|
+
}
|
|
398
|
+
`;
|
|
399
|
+
function numberUniform(uniforms, key, fallback) {
|
|
400
|
+
const value = uniforms[key];
|
|
401
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
402
|
+
}
|
|
403
|
+
const rainFactory = () => {
|
|
404
|
+
const group = new pixi_js.UniformGroup({
|
|
405
|
+
uTime: { value: 0, type: 'f32' },
|
|
406
|
+
uRainAmount: { value: 0.65, type: 'f32' },
|
|
407
|
+
uQuality: { value: 1, type: 'f32' },
|
|
408
|
+
});
|
|
409
|
+
const filter = new pixi_js.Filter({
|
|
410
|
+
glProgram: pixi_js.GlProgram.from({ name: RAIN_EFFECT_ID, vertex: normalizedScreenFilterVert, fragment: rainFragment }),
|
|
411
|
+
resources: { rainUniforms: group },
|
|
412
|
+
antialias: 'on',
|
|
413
|
+
});
|
|
414
|
+
return {
|
|
415
|
+
filter,
|
|
416
|
+
update(frame, uniforms) {
|
|
417
|
+
const values = group.uniforms;
|
|
418
|
+
values.uTime = frame.rafTime / 1000;
|
|
419
|
+
values.uRainAmount = numberUniform(uniforms, 'rainAmount', 0.65);
|
|
420
|
+
values.uQuality = numberUniform(uniforms, 'quality', 1);
|
|
421
|
+
},
|
|
422
|
+
destroy() {
|
|
423
|
+
filter.destroy();
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
};
|
|
427
|
+
Object.defineProperty(rainFactory, 'backends', { value: ['webgl'] });
|
|
428
|
+
const puddleFactory = () => {
|
|
429
|
+
const rippleBuffer = new Float32Array(MAX_SHADER_RIPPLES * 4);
|
|
430
|
+
const rippleDirectionBuffer = new Float32Array(MAX_SHADER_RIPPLES * 4);
|
|
431
|
+
const clickRippleBuffer = new Float32Array([-2, -2, 99, 0]);
|
|
432
|
+
for (let index = 0; index < MAX_SHADER_RIPPLES; index += 1)
|
|
433
|
+
rippleBuffer[index * 4 + 2] = -1;
|
|
434
|
+
const group = new pixi_js.UniformGroup({
|
|
435
|
+
uTime: { value: 0, type: 'f32' },
|
|
436
|
+
uWaterLevel: { value: 0.5, type: 'f32' },
|
|
437
|
+
uGroundLevel: { value: 0.545, type: 'f32' },
|
|
438
|
+
uGroundFeather: { value: 0.02, type: 'f32' },
|
|
439
|
+
uReflectionStrength: { value: 0.55, type: 'f32' },
|
|
440
|
+
uRippleDuration: { value: 1.6, type: 'f32' },
|
|
441
|
+
uRipples: { value: rippleBuffer, type: 'vec4<f32>', size: MAX_SHADER_RIPPLES },
|
|
442
|
+
uRippleDirections: { value: rippleDirectionBuffer, type: 'vec4<f32>', size: MAX_SHADER_RIPPLES },
|
|
443
|
+
uClickRipple: { value: clickRippleBuffer, type: 'vec4<f32>' },
|
|
444
|
+
});
|
|
445
|
+
const filter = new pixi_js.Filter({
|
|
446
|
+
glProgram: pixi_js.GlProgram.from({
|
|
447
|
+
name: PUDDLE_EFFECT_ID,
|
|
448
|
+
vertex: normalizedScreenFilterVert,
|
|
449
|
+
fragment: puddleFragment,
|
|
450
|
+
}),
|
|
451
|
+
resources: { puddleUniforms: group, uReflection: pixi_js.Texture.EMPTY.source },
|
|
452
|
+
antialias: 'on',
|
|
453
|
+
});
|
|
454
|
+
let boundTextureKey = '';
|
|
455
|
+
return {
|
|
456
|
+
filter,
|
|
457
|
+
update(frame, uniforms) {
|
|
458
|
+
const values = group.uniforms;
|
|
459
|
+
values.uTime = frame.rafTime / 1000;
|
|
460
|
+
values.uWaterLevel = numberUniform(uniforms, 'waterLevel', 0.5);
|
|
461
|
+
values.uGroundLevel = numberUniform(uniforms, 'groundLevel', 0.545);
|
|
462
|
+
values.uGroundFeather = numberUniform(uniforms, 'groundFeather', 0.02);
|
|
463
|
+
values.uReflectionStrength = numberUniform(uniforms, 'reflectionStrength', 0.55);
|
|
464
|
+
values.uRippleDuration = numberUniform(uniforms, 'rippleDuration', 1.6);
|
|
465
|
+
const incomingRipples = uniforms.ripples;
|
|
466
|
+
rippleBuffer.fill(0);
|
|
467
|
+
for (let index = 0; index < MAX_SHADER_RIPPLES; index += 1)
|
|
468
|
+
rippleBuffer[index * 4 + 2] = -1;
|
|
469
|
+
if (incomingRipples instanceof Float32Array) {
|
|
470
|
+
rippleBuffer.set(incomingRipples.subarray(0, rippleBuffer.length));
|
|
471
|
+
}
|
|
472
|
+
rippleDirectionBuffer.fill(0);
|
|
473
|
+
const incomingDirections = uniforms.rippleDirections;
|
|
474
|
+
if (incomingDirections instanceof Float32Array) {
|
|
475
|
+
rippleDirectionBuffer.set(incomingDirections.subarray(0, rippleDirectionBuffer.length));
|
|
476
|
+
}
|
|
477
|
+
clickRippleBuffer.set([-2, -2, 99, 0]);
|
|
478
|
+
const incomingClickRipple = uniforms.clickRipple;
|
|
479
|
+
if (incomingClickRipple instanceof Float32Array) {
|
|
480
|
+
clickRippleBuffer.set(incomingClickRipple.subarray(0, clickRippleBuffer.length));
|
|
481
|
+
}
|
|
482
|
+
const textureKey = typeof uniforms.reflectionTexture === 'string' ? uniforms.reflectionTexture : '';
|
|
483
|
+
if (textureKey && textureKey !== boundTextureKey) {
|
|
484
|
+
const texture = pluginRendererSceneCapture.getSceneCaptureTexture(textureKey);
|
|
485
|
+
if (texture) {
|
|
486
|
+
filter.resources.uReflection = texture.source;
|
|
487
|
+
boundTextureKey = textureKey;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
destroy() {
|
|
492
|
+
filter.destroy();
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
};
|
|
496
|
+
Object.defineProperty(puddleFactory, 'backends', { value: ['webgl'] });
|
|
497
|
+
/** Register the built-in rain and puddle factories once for the current realm. */
|
|
498
|
+
function registerRainPuddleEffects() {
|
|
499
|
+
if (!pluginRendererShaderEffect.getShaderEffect(RAIN_EFFECT_ID))
|
|
500
|
+
pluginRendererShaderEffect.registerShaderEffect(RAIN_EFFECT_ID, rainFactory);
|
|
501
|
+
if (!pluginRendererShaderEffect.getShaderEffect(PUDDLE_EFFECT_ID))
|
|
502
|
+
pluginRendererShaderEffect.registerShaderEffect(PUDDLE_EFFECT_ID, puddleFactory);
|
|
503
|
+
}
|
|
504
|
+
/** Primarily useful for test isolation and hosts that unload plugin bundles. */
|
|
505
|
+
function unregisterRainPuddleEffects() {
|
|
506
|
+
pluginRendererShaderEffect.unregisterShaderEffect(RAIN_EFFECT_ID);
|
|
507
|
+
pluginRendererShaderEffect.unregisterShaderEffect(PUDDLE_EFFECT_ID);
|
|
508
|
+
}
|
|
509
|
+
const rainPuddleShaderSources = {
|
|
510
|
+
vertex: normalizedScreenFilterVert,
|
|
511
|
+
rainFragment,
|
|
512
|
+
puddleFragment,
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
exports.DEFAULT_MAX_RIPPLES = DEFAULT_MAX_RIPPLES;
|
|
516
|
+
exports.MAX_SHADER_RIPPLES = MAX_SHADER_RIPPLES;
|
|
517
|
+
exports.PUDDLE_EFFECT_ID = PUDDLE_EFFECT_ID;
|
|
518
|
+
exports.RAIN_EFFECT_ID = RAIN_EFFECT_ID;
|
|
519
|
+
exports.RIPPLE_STRIDE = RIPPLE_STRIDE;
|
|
520
|
+
exports.RainPuddle = RainPuddle;
|
|
521
|
+
exports.RainPuddleSystem = RainPuddleSystem$1;
|
|
522
|
+
exports.rainPuddleShaderSources = rainPuddleShaderSources;
|
|
523
|
+
exports.registerRainPuddleEffects = registerRainPuddleEffects;
|
|
524
|
+
exports.unregisterRainPuddleEffects = unregisterRainPuddleEffects;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@eva/eva.js"),t=require("@eva/plugin-renderer-shader-effect"),n=require("pixi.js"),i=require("@eva/plugin-renderer-scene-capture");class r extends e.Component{constructor(){super(...arguments),this.rainAmount=.65,this.waterLevel=.5,this.groundLevel=.545,this.groundFeather=.02,this.reflectionStrength=.55,this.rippleDuration=1.6,this.maxRipples=4,this.quality="high",this.reflectionTexture="",this.rainSurfaceName="rain-surface",this.puddleSurfaceName="puddle-surface",this.rippleUniformData=new Float32Array(16),this.rippleDirectionUniformData=new Float32Array(16),this.clickRippleUniformData=new Float32Array([-2,-2,99,0]),this.activeRippleCount=0,this.nextRippleIndex=0}init(e={}){var t,n,i,r,l,u,s,c,p,f,d,m,v,h;this.rainAmount=o(null!==(t=e.rainAmount)&&void 0!==t?t:this.rainAmount),this.waterLevel=o(null!==(n=e.waterLevel)&&void 0!==n?n:this.waterLevel),this.groundLevel=o(null!==(i=e.groundLevel)&&void 0!==i?i:this.groundLevel),this.groundFeather=a(null!==(r=e.groundFeather)&&void 0!==r?r:this.groundFeather,.001,1),this.reflectionStrength=o(null!==(l=e.reflectionStrength)&&void 0!==l?l:this.reflectionStrength),this.rippleDuration=Math.max(.05,null!==(u=e.rippleDuration)&&void 0!==u?u:this.rippleDuration),this.maxRipples=(m=null!==(s=e.maxRipples)&&void 0!==s?s:this.maxRipples,v=1,h=8,Math.round(a(m,v,h))),this.quality=null!==(c=e.quality)&&void 0!==c?c:this.quality,this.reflectionTexture=null!==(p=e.reflectionTexture)&&void 0!==p?p:this.reflectionTexture,this.rainSurfaceName=null!==(f=e.rainSurfaceName)&&void 0!==f?f:this.rainSurfaceName,this.puddleSurfaceName=null!==(d=e.puddleSurfaceName)&&void 0!==d?d:this.puddleSurfaceName,this.rippleUniformData=new Float32Array(4*this.maxRipples),this.rippleDirectionUniformData=new Float32Array(4*this.maxRipples),this.clickRippleUniformData=new Float32Array([-2,-2,99,0]),this.resetRipples()}emitRipple(e){var t;const n=this.nextRippleIndex,i=4*n,r=this.rippleUniformData[i+2]>=0;this.rippleUniformData[i]=o(e.x),this.rippleUniformData[i+1]=o(e.y),this.rippleUniformData[i+2]=0,this.rippleUniformData[i+3]=o(null!==(t=e.strength)&&void 0!==t?t:1);const a=function(e){var t,n;const i="number"==typeof e?e:Number(null!==(t=null==e?void 0:e.x)&&void 0!==t?t:0),r="number"==typeof e?0:Number(null!==(n=null==e?void 0:e.y)&&void 0!==n?n:0),a=Math.hypot(i,r);return a>0?{x:i/a,y:r/a}:{x:0,y:1}}(e.direction),l=4*n;this.rippleDirectionUniformData[l]=a.x,this.rippleDirectionUniformData[l+1]=a.y,r||(this.activeRippleCount+=1),this.nextRippleIndex=(n+1)%this.maxRipples}emitClickRipple(e){var t;this.clickRippleUniformData[0]=o(e.x),this.clickRippleUniformData[1]=o(e.y),this.clickRippleUniformData[2]=0,this.clickRippleUniformData[3]=o(null!==(t=e.strength)&&void 0!==t?t:1)}advanceRipples(e){if(e>0){for(let t=0;t<this.maxRipples;t+=1){const n=4*t+2,i=this.rippleUniformData[n];if(i<0)continue;const r=i+e;r>=this.rippleDuration?(this.rippleUniformData[n]=-1,this.activeRippleCount-=1):this.rippleUniformData[n]=r}this.clickRippleUniformData[2]>=0&&this.clickRippleUniformData[2]<this.rippleDuration&&(this.clickRippleUniformData[2]+=e)}}getRippleDirection(e){var t,n;const i=4*e;return{x:null!==(t=this.rippleDirectionUniformData[i])&&void 0!==t?t:0,y:null!==(n=this.rippleDirectionUniformData[i+1])&&void 0!==n?n:0}}resetRipples(){this.rippleUniformData.fill(0);for(let e=0;e<this.maxRipples;e+=1)this.rippleUniformData[4*e+2]=-1;this.rippleDirectionUniformData.fill(0),this.clickRippleUniformData.set([-2,-2,99,0]),this.nextRippleIndex=0,this.activeRippleCount=0}}function a(e,t,n){return Math.min(n,Math.max(t,e))}function o(e){return a(e,0,1)}r.componentName="RainPuddle","function"==typeof SuppressedError&&SuppressedError;let l=class extends e.System{constructor(){super(...arguments),this.name="RainPuddleSystem",this.bindings=new Map}update(){this.consumeObserverChanges()}consumeObserverChanges(){for(const e of this.componentObserver.clear())this.componentChanged(e)}componentChanged(t){t.componentName===r.componentName&&(t.type!==e.OBSERVER_TYPE.REMOVE?t.type===e.OBSERVER_TYPE.ADD&&this.bindings.set(t.gameObject.id,{component:t.component}):this.bindings.delete(t.gameObject.id))}frameStart(e){this.consumeObserverChanges();const t=Math.min(.1,Math.max(0,e.rafDeltaTime/1e3));for(const e of this.bindings.values()){const n=e.component;n.advanceRipples(t),e.rainEffect=this.resolveEffect(e.rainEffect,n.rainSurfaceName),e.puddleEffect=this.resolveEffect(e.puddleEffect,n.puddleSurfaceName),e.rainEffect&&(e.rainEffect.uniforms.rainAmount=n.rainAmount,e.rainEffect.uniforms.quality=s(n.quality)),e.puddleEffect&&(e.puddleEffect.uniforms.waterLevel=n.waterLevel,e.puddleEffect.uniforms.groundLevel=n.groundLevel,e.puddleEffect.uniforms.groundFeather=n.groundFeather,e.puddleEffect.uniforms.reflectionStrength=n.reflectionStrength,e.puddleEffect.uniforms.rippleDuration=n.rippleDuration,e.puddleEffect.uniforms.ripples=n.rippleUniformData,e.puddleEffect.uniforms.rippleDirections=n.rippleDirectionUniformData,e.puddleEffect.uniforms.clickRipple=n.clickRippleUniformData,e.puddleEffect.uniforms.reflectionTexture=n.reflectionTexture)}}onDestroy(){this.bindings.clear()}resolveEffect(e,n){var i,r,a;if((null==e?void 0:e.gameObject)&&!e.gameObject.destroyed)return e;if(!n)return;const o=null===(a=null===(r=null===(i=this.game)||void 0===i?void 0:i.findAllByName)||void 0===r?void 0:r.call(i,n))||void 0===a?void 0:a[0];return null==o?void 0:o.getComponent(t.ShaderEffect)}};l.systemName="RainPuddleSystem",l=function(e,t,n,i){var r,a=arguments.length,o=a<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,n,i);else for(var l=e.length-1;l>=0;l--)(r=e[l])&&(o=(a<3?r(o):a>3?r(t,n,o):r(t,n))||o);return a>3&&o&&Object.defineProperty(t,n,o),o}([e.decorators.componentObserver({RainPuddle:[]})],l);var u=l;function s(e){return"low"===e?0:"medium"===e?.55:1}const c="eva.rain-puddle.rain",p="eva.rain-puddle.puddle",f="\nin vec2 aPosition;\nout vec2 vTextureCoord;\n\nuniform vec4 uOutputFrame;\nuniform vec4 uOutputTexture;\n\nvoid main() {\n vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;\n position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0 * uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;\n gl_Position = vec4(position, 0.0, 1.0);\n vTextureCoord = aPosition;\n}\n",d="\nin vec2 vTextureCoord;\nout vec4 finalColor;\n\nuniform sampler2D uTexture;\nuniform float uTime;\nuniform float uRainAmount;\nuniform float uQuality;\n\nfloat hash21(vec2 p) {\n return fract(sin(dot(p, vec2(41.31, 289.17))) * 28419.231);\n}\n\nfloat rainLayer(vec2 uv, float scale, float speed, float width, float length, float brightness) {\n vec2 p = uv * vec2(150.0 * scale, 78.0 * scale);\n p.x += p.y * 0.24;\n // Texture coordinates grow downwards in Pixi. Subtracting time makes the\n // streak cells travel towards increasing screen Y instead of rising upward.\n p.y -= uTime * speed;\n vec2 id = floor(p);\n vec2 cell = fract(p) - 0.5;\n float seed = hash21(id);\n float thinLine = 1.0 - smoothstep(width, width * 2.0, abs(cell.x + seed * 0.16 - 0.08));\n float longStreak = 1.0 - smoothstep(length, length * 1.35, abs(cell.y));\n return thinLine * longStreak * brightness * step(1.0 - uRainAmount, seed);\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n float detail = mix(0.78, 1.0, uQuality);\n float rain = rainLayer(uv, 0.72 * detail, 1.15, 0.010, 0.42, 0.28);\n rain += rainLayer(uv + vec2(0.17, 0.0), 1.28 * detail, 1.78, 0.007, 0.46, 0.62);\n float alpha = clamp(rain, 0.0, 0.82);\n finalColor = vec4(vec3(0.66, 0.82, 0.96) * alpha, alpha);\n}\n",m="\nin vec2 vTextureCoord;\nout vec4 finalColor;\n\nuniform sampler2D uTexture;\nuniform sampler2D uReflection;\nuniform float uTime;\nuniform float uWaterLevel;\nuniform float uGroundLevel;\nuniform float uGroundFeather;\nuniform float uReflectionStrength;\nuniform float uRippleDuration;\nuniform vec4 uRipples[8];\nuniform vec4 uRippleDirections[8];\nuniform vec4 uClickRipple;\n\nfloat puddleShape(vec2 uv, vec2 center, vec2 radii, float seed) {\n vec2 p = (uv - center) / radii;\n float angle = atan(p.y, p.x);\n float edgeNoise = sin(angle * 5.0 + seed) * 0.075 + sin(angle * 9.0 - seed) * 0.035;\n float distanceToCenter = length(p) + edgeNoise;\n return 1.0 - smoothstep(0.72 + (1.0 - uWaterLevel) * 0.20, 1.02, distanceToCenter);\n}\n\nfloat ring(vec2 uv, vec2 center, float radius, float width) {\n float distanceToCenter = length(uv - center);\n return 1.0 - smoothstep(width, width * 2.1, abs(distanceToCenter - radius));\n}\n\nfloat projectedFootWave(vec2 uv, vec4 state, vec2 direction) {\n float phase = clamp(state.z / 1.10, 0.0, 1.0);\n float fade = (1.0 - phase) * state.w;\n vec2 wakeCenter = state.xy - direction * phase * 0.014;\n vec2 delta = uv - wakeCenter;\n float projectedDistance = length(vec2(delta.x, delta.y * 3.0));\n float radius = 0.006 + phase * 0.058;\n float leading = 1.0 - smoothstep(0.0018, 0.0040, abs(projectedDistance - radius));\n float secondary = 1.0 - smoothstep(0.0015, 0.0033, abs(projectedDistance - radius * 0.63));\n vec2 trailDelta = uv - (wakeCenter - direction * 0.018 * phase);\n float trailDistance = length(vec2(trailDelta.x, trailDelta.y * 3.0));\n float trail = 1.0 - smoothstep(0.0020, 0.0045, abs(trailDistance - radius * 0.76));\n return (leading + secondary * 0.34 + trail * 0.42) * fade;\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n float mask = puddleShape(uv, vec2(0.26, 0.68), vec2(0.21, 0.12), 0.3);\n mask = max(mask, puddleShape(uv, vec2(0.56, 0.77), vec2(0.26, 0.10), 2.2));\n mask = max(mask, puddleShape(uv, vec2(0.82, 0.59), vec2(0.14, 0.18), 4.1));\n mask *= smoothstep(uGroundLevel, uGroundLevel + max(uGroundFeather, 0.0001), uv.y);\n mask *= smoothstep(0.04, 0.17, uWaterLevel);\n\n float smallWaves = sin(uv.y * 920.0 + uTime * 2.2) + sin(uv.x * 700.0 - uTime * 1.7);\n vec2 distortion = vec2(smallWaves * 0.00075, sin(uv.x * 510.0 + uTime * 2.0) * 0.0011);\n\n for (int index = 0; index < 5; index++) {\n float fi = float(index);\n float phase = fract(uTime * (0.13 + fi * 0.023) + fi * 0.618);\n vec2 center = vec2(fract(fi * 0.347 + 0.14), fract(fi * 0.613 + 0.52));\n float radius = phase * 0.075;\n float rainRipple = ring(uv, center, radius, 0.0018) * (1.0 - phase);\n distortion += normalize(uv - center + vec2(0.0001)) * rainRipple * 0.004;\n }\n\n float clickPhase = clamp(uClickRipple.z / 1.55, 0.0, 1.0);\n float clickRing = ring(uv, uClickRipple.xy, clickPhase * 0.14, 0.0025) * (1.0 - clickPhase) * uClickRipple.w;\n distortion += normalize(uv - uClickRipple.xy + vec2(0.0001)) * clickRing * 0.012;\n\n float footRing = 0.0;\n vec2 footPush = vec2(0.0);\n for (int index = 0; index < 8; index++) {\n vec4 state = uRipples[index];\n if (state.z < 0.0 || state.z >= uRippleDuration) continue;\n float wave = projectedFootWave(uv, state, uRippleDirections[index].xy);\n footRing += wave;\n footPush += (uv - state.xy) * wave;\n }\n footRing = clamp(footRing, 0.0, 1.5);\n distortion += normalize(footPush + vec2(0.0001)) * footRing * 0.0065;\n\n vec4 reflection = texture(uReflection, clamp(uv + distortion, vec2(0.002), vec2(0.998)));\n vec3 waterTint = vec3(0.055, 0.15, 0.23);\n float reflectedAmount = reflection.a * uReflectionStrength;\n vec3 color = mix(waterTint, reflection.rgb + vec3(0.03, 0.06, 0.09), reflectedAmount);\n color += vec3(0.30, 0.48, 0.58) * (clickRing + footRing * 1.10 + 0.18 * max(0.0, smallWaves)) * 0.24;\n float alpha = mask * (0.82 + 0.16 * uWaterLevel);\n finalColor = vec4(color * alpha, alpha);\n}\n";function v(e,t,n){const i=e[t];return"number"==typeof i&&Number.isFinite(i)?i:n}const h=()=>{const e=new n.UniformGroup({uTime:{value:0,type:"f32"},uRainAmount:{value:.65,type:"f32"},uQuality:{value:1,type:"f32"}}),t=new n.Filter({glProgram:n.GlProgram.from({name:c,vertex:f,fragment:d}),resources:{rainUniforms:e},antialias:"on"});return{filter:t,update(t,n){const i=e.uniforms;i.uTime=t.rafTime/1e3,i.uRainAmount=v(n,"rainAmount",.65),i.uQuality=v(n,"quality",1)},destroy(){t.destroy()}}};Object.defineProperty(h,"backends",{value:["webgl"]});const g=()=>{const e=new Float32Array(32),t=new Float32Array(32),r=new Float32Array([-2,-2,99,0]);for(let t=0;t<8;t+=1)e[4*t+2]=-1;const a=new n.UniformGroup({uTime:{value:0,type:"f32"},uWaterLevel:{value:.5,type:"f32"},uGroundLevel:{value:.545,type:"f32"},uGroundFeather:{value:.02,type:"f32"},uReflectionStrength:{value:.55,type:"f32"},uRippleDuration:{value:1.6,type:"f32"},uRipples:{value:e,type:"vec4<f32>",size:8},uRippleDirections:{value:t,type:"vec4<f32>",size:8},uClickRipple:{value:r,type:"vec4<f32>"}}),o=new n.Filter({glProgram:n.GlProgram.from({name:p,vertex:f,fragment:m}),resources:{puddleUniforms:a,uReflection:n.Texture.EMPTY.source},antialias:"on"});let l="";return{filter:o,update(n,u){const s=a.uniforms;s.uTime=n.rafTime/1e3,s.uWaterLevel=v(u,"waterLevel",.5),s.uGroundLevel=v(u,"groundLevel",.545),s.uGroundFeather=v(u,"groundFeather",.02),s.uReflectionStrength=v(u,"reflectionStrength",.55),s.uRippleDuration=v(u,"rippleDuration",1.6);const c=u.ripples;e.fill(0);for(let t=0;t<8;t+=1)e[4*t+2]=-1;c instanceof Float32Array&&e.set(c.subarray(0,e.length)),t.fill(0);const p=u.rippleDirections;p instanceof Float32Array&&t.set(p.subarray(0,t.length)),r.set([-2,-2,99,0]);const f=u.clickRipple;f instanceof Float32Array&&r.set(f.subarray(0,r.length));const d="string"==typeof u.reflectionTexture?u.reflectionTexture:"";if(d&&d!==l){const e=i.getSceneCaptureTexture(d);e&&(o.resources.uReflection=e.source,l=d)}},destroy(){o.destroy()}}};Object.defineProperty(g,"backends",{value:["webgl"]});const x={vertex:f,rainFragment:d,puddleFragment:m};exports.DEFAULT_MAX_RIPPLES=4,exports.MAX_SHADER_RIPPLES=8,exports.PUDDLE_EFFECT_ID=p,exports.RAIN_EFFECT_ID=c,exports.RIPPLE_STRIDE=4,exports.RainPuddle=r,exports.RainPuddleSystem=u,exports.rainPuddleShaderSources=x,exports.registerRainPuddleEffects=function(){t.getShaderEffect(c)||t.registerShaderEffect(c,h),t.getShaderEffect(p)||t.registerShaderEffect(p,g)},exports.unregisterRainPuddleEffects=function(){t.unregisterShaderEffect(c),t.unregisterShaderEffect(p)};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Component } from '@eva/eva.js';
|
|
2
|
+
import { ComponentChanged } from '@eva/eva.js';
|
|
3
|
+
import { FrameParams } from '@eva/eva.js';
|
|
4
|
+
import { System } from '@eva/eva.js';
|
|
5
|
+
|
|
6
|
+
declare interface ClickRippleInput {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
strength?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare const DEFAULT_MAX_RIPPLES = 4;
|
|
13
|
+
|
|
14
|
+
export declare const MAX_SHADER_RIPPLES = 8;
|
|
15
|
+
|
|
16
|
+
export declare const PUDDLE_EFFECT_ID = "eva.rain-puddle.puddle";
|
|
17
|
+
|
|
18
|
+
export declare const RAIN_EFFECT_ID = "eva.rain-puddle.rain";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Runtime state shared by the rain and puddle effects.
|
|
22
|
+
*
|
|
23
|
+
* Ripples live in a fixed-size ring so pointer and footstep events never
|
|
24
|
+
* allocate per frame. Each vec4 is `(x, y, ageSeconds, strength)`; expired
|
|
25
|
+
* slots use `ageSeconds = -1`.
|
|
26
|
+
*/
|
|
27
|
+
export declare class RainPuddle extends Component<RainPuddleParams> {
|
|
28
|
+
static componentName: string;
|
|
29
|
+
rainAmount: number;
|
|
30
|
+
waterLevel: number;
|
|
31
|
+
groundLevel: number;
|
|
32
|
+
groundFeather: number;
|
|
33
|
+
reflectionStrength: number;
|
|
34
|
+
rippleDuration: number;
|
|
35
|
+
maxRipples: number;
|
|
36
|
+
quality: RainPuddleQuality;
|
|
37
|
+
reflectionTexture: string;
|
|
38
|
+
rainSurfaceName: string;
|
|
39
|
+
puddleSurfaceName: string;
|
|
40
|
+
rippleUniformData: Float32Array;
|
|
41
|
+
rippleDirectionUniformData: Float32Array;
|
|
42
|
+
clickRippleUniformData: Float32Array;
|
|
43
|
+
activeRippleCount: number;
|
|
44
|
+
private nextRippleIndex;
|
|
45
|
+
init(params?: RainPuddleParams): void;
|
|
46
|
+
emitRipple(input: RippleInput): void;
|
|
47
|
+
/** Click ripples have an independent lifetime and never consume a footstep slot. */
|
|
48
|
+
emitClickRipple(input: ClickRippleInput): void;
|
|
49
|
+
advanceRipples(deltaSeconds: number): void;
|
|
50
|
+
getRippleDirection(index: number): {
|
|
51
|
+
x: number;
|
|
52
|
+
y: number;
|
|
53
|
+
};
|
|
54
|
+
resetRipples(): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export declare interface RainPuddleParams {
|
|
58
|
+
rainAmount?: number;
|
|
59
|
+
waterLevel?: number;
|
|
60
|
+
/** Normalized screen Y where the ground plane begins. */
|
|
61
|
+
groundLevel?: number;
|
|
62
|
+
/** Normalized vertical distance used to soften the ground-plane edge. */
|
|
63
|
+
groundFeather?: number;
|
|
64
|
+
reflectionStrength?: number;
|
|
65
|
+
rippleDuration?: number;
|
|
66
|
+
maxRipples?: number;
|
|
67
|
+
quality?: RainPuddleQuality;
|
|
68
|
+
reflectionTexture?: string;
|
|
69
|
+
rainSurfaceName?: string;
|
|
70
|
+
puddleSurfaceName?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export declare type RainPuddleQuality = 'low' | 'medium' | 'high';
|
|
74
|
+
|
|
75
|
+
export declare const rainPuddleShaderSources: {
|
|
76
|
+
readonly vertex: "\nin vec2 aPosition;\nout vec2 vTextureCoord;\n\nuniform vec4 uOutputFrame;\nuniform vec4 uOutputTexture;\n\nvoid main() {\n vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;\n position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0 * uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;\n gl_Position = vec4(position, 0.0, 1.0);\n vTextureCoord = aPosition;\n}\n";
|
|
77
|
+
readonly rainFragment: "\nin vec2 vTextureCoord;\nout vec4 finalColor;\n\nuniform sampler2D uTexture;\nuniform float uTime;\nuniform float uRainAmount;\nuniform float uQuality;\n\nfloat hash21(vec2 p) {\n return fract(sin(dot(p, vec2(41.31, 289.17))) * 28419.231);\n}\n\nfloat rainLayer(vec2 uv, float scale, float speed, float width, float length, float brightness) {\n vec2 p = uv * vec2(150.0 * scale, 78.0 * scale);\n p.x += p.y * 0.24;\n // Texture coordinates grow downwards in Pixi. Subtracting time makes the\n // streak cells travel towards increasing screen Y instead of rising upward.\n p.y -= uTime * speed;\n vec2 id = floor(p);\n vec2 cell = fract(p) - 0.5;\n float seed = hash21(id);\n float thinLine = 1.0 - smoothstep(width, width * 2.0, abs(cell.x + seed * 0.16 - 0.08));\n float longStreak = 1.0 - smoothstep(length, length * 1.35, abs(cell.y));\n return thinLine * longStreak * brightness * step(1.0 - uRainAmount, seed);\n}\n\nvoid main() {\n vec2 uv = vTextureCoord;\n float detail = mix(0.78, 1.0, uQuality);\n float rain = rainLayer(uv, 0.72 * detail, 1.15, 0.010, 0.42, 0.28);\n rain += rainLayer(uv + vec2(0.17, 0.0), 1.28 * detail, 1.78, 0.007, 0.46, 0.62);\n float alpha = clamp(rain, 0.0, 0.82);\n finalColor = vec4(vec3(0.66, 0.82, 0.96) * alpha, alpha);\n}\n";
|
|
78
|
+
readonly puddleFragment: string;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Advances bounded ripples and keeps both visual passes on one shared state. */
|
|
82
|
+
export declare class RainPuddleSystem extends System {
|
|
83
|
+
static systemName: string;
|
|
84
|
+
name: string;
|
|
85
|
+
private readonly bindings;
|
|
86
|
+
update(): void;
|
|
87
|
+
private consumeObserverChanges;
|
|
88
|
+
componentChanged(changed: ComponentChanged): void;
|
|
89
|
+
frameStart(frame: FrameParams): void;
|
|
90
|
+
onDestroy(): void;
|
|
91
|
+
private resolveEffect;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Register the built-in rain and puddle factories once for the current realm. */
|
|
95
|
+
export declare function registerRainPuddleEffects(): void;
|
|
96
|
+
|
|
97
|
+
export declare const RIPPLE_STRIDE = 4;
|
|
98
|
+
|
|
99
|
+
export declare interface RippleInput {
|
|
100
|
+
/** Horizontal position in normalized screen coordinates. */
|
|
101
|
+
x: number;
|
|
102
|
+
/** Vertical position in normalized screen coordinates. */
|
|
103
|
+
y: number;
|
|
104
|
+
/** Ripple amplitude, normalized to 0..1. */
|
|
105
|
+
strength?: number;
|
|
106
|
+
/** Optional ground-plane direction used by projected footstep wakes. */
|
|
107
|
+
direction?: number | {
|
|
108
|
+
x: number;
|
|
109
|
+
y: number;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Primarily useful for test isolation and hosts that unload plugin bundles. */
|
|
114
|
+
export declare function unregisterRainPuddleEffects(): void;
|
|
115
|
+
|
|
116
|
+
export { }
|