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