@wolsok/thanos 17.0.37 → 18.0.0
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 +73 -12
- package/esm2022/lib/canvas-particle-renderer.js +98 -0
- package/esm2022/lib/canvas-particle-renderer.js.map +1 -0
- package/esm2022/lib/capture-scale.js +13 -0
- package/esm2022/lib/capture-scale.js.map +1 -0
- package/esm2022/lib/choose-particle-renderer.js +78 -0
- package/esm2022/lib/choose-particle-renderer.js.map +1 -0
- package/esm2022/lib/create-ws-thanos-options.js +4 -1
- package/esm2022/lib/create-ws-thanos-options.js.map +1 -1
- package/esm2022/lib/crumble-shape.js +24 -0
- package/esm2022/lib/crumble-shape.js.map +1 -0
- package/esm2022/lib/particle-renderer.js +1 -0
- package/esm2022/lib/particle-renderer.js.map +1 -0
- package/esm2022/lib/particles.js +84 -0
- package/esm2022/lib/particles.js.map +1 -0
- package/esm2022/lib/snap-sound-envelopes.js +38 -0
- package/esm2022/lib/snap-sound-envelopes.js.map +1 -0
- package/esm2022/lib/snap-sound.service.js +287 -0
- package/esm2022/lib/snap-sound.service.js.map +1 -0
- package/esm2022/lib/vaporizing-front.js +48 -0
- package/esm2022/lib/vaporizing-front.js.map +1 -0
- package/esm2022/lib/webgl/simplex-noise.glsl.js +69 -0
- package/esm2022/lib/webgl/simplex-noise.glsl.js.map +1 -0
- package/esm2022/lib/webgl/webgl-particle-renderer.js +308 -0
- package/esm2022/lib/webgl/webgl-particle-renderer.js.map +1 -0
- package/esm2022/lib/webgl/webgl-shaders.js +323 -0
- package/esm2022/lib/webgl/webgl-shaders.js.map +1 -0
- package/esm2022/lib/ws-thanos.directive.js +8 -6
- package/esm2022/lib/ws-thanos.directive.js.map +1 -1
- package/esm2022/lib/ws-thanos.options.js.map +1 -1
- package/esm2022/lib/ws-thanos.service.js +103 -220
- package/esm2022/lib/ws-thanos.service.js.map +1 -1
- package/lib/canvas-particle-renderer.d.ts +29 -0
- package/lib/capture-scale.d.ts +14 -0
- package/lib/choose-particle-renderer.d.ts +31 -0
- package/lib/crumble-shape.d.ts +11 -0
- package/lib/particle-renderer.d.ts +26 -0
- package/lib/particles.d.ts +35 -0
- package/lib/snap-sound-envelopes.d.ts +15 -0
- package/lib/snap-sound.service.d.ts +27 -0
- package/lib/vaporizing-front.d.ts +34 -0
- package/lib/webgl/simplex-noise.glsl.d.ts +5 -0
- package/lib/webgl/webgl-particle-renderer.d.ts +46 -0
- package/lib/webgl/webgl-shaders.d.ts +27 -0
- package/lib/ws-thanos.directive.d.ts +4 -2
- package/lib/ws-thanos.options.d.ts +14 -0
- package/lib/ws-thanos.service.d.ts +11 -8
- package/package.json +1 -1
|
@@ -1,247 +1,130 @@
|
|
|
1
|
-
import { Injectable, inject } from '@angular/core';
|
|
1
|
+
import { DOCUMENT, Injectable, inject } from '@angular/core';
|
|
2
2
|
import { default as html2canvas } from 'html2canvas';
|
|
3
|
-
import { animationFrameScheduler, finalize, from, interval, map, switchMap, takeWhile, tap, timeInterval, } from 'rxjs';
|
|
4
|
-
import {
|
|
3
|
+
import { animationFrameScheduler, defer, finalize, from, interval, map, switchMap, takeWhile, tap, timeInterval, } from 'rxjs';
|
|
4
|
+
import { EFFECT_HEIGHT_SCALE, EFFECT_WIDTH_SCALE } from './capture-scale';
|
|
5
|
+
import { chooseParticleRenderer, GpuContextBudget, } from './choose-particle-renderer';
|
|
6
|
+
import { createParticles } from './particles';
|
|
7
|
+
import { SnapSoundService } from './snap-sound.service';
|
|
5
8
|
import { WS_THANOS_OPTIONS_TOKEN } from './ws-thanos-options.token';
|
|
6
9
|
import * as i0 from "@angular/core";
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const HEIGHT_SCALE = 5;
|
|
10
|
-
const WIDTH_SCALE = 2;
|
|
11
|
-
const FLOW_FIELD_RES = 0.05;
|
|
10
|
+
/** a short cross fade hides small differences between capture and element */
|
|
11
|
+
const FULL_COVERAGE_FADE_OUT_MS = 200;
|
|
12
12
|
export class WsThanosService {
|
|
13
13
|
constructor() {
|
|
14
14
|
this.thanosOptions = inject(WS_THANOS_OPTIONS_TOKEN);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
y: base + 1,
|
|
20
|
-
dx: base + 2,
|
|
21
|
-
dy: base + 3,
|
|
22
|
-
ax: base + 4,
|
|
23
|
-
ay: base + 5,
|
|
24
|
-
r: base + 6,
|
|
25
|
-
g: base + 7,
|
|
26
|
-
b: base + 8,
|
|
27
|
-
a: base + 9,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
static getColorIndicesForCoord(x, y, width) {
|
|
31
|
-
const red = y * (width * 4) + x * 4;
|
|
32
|
-
return { r: red, g: red + 1, b: red + 2, a: red + 3 };
|
|
33
|
-
}
|
|
34
|
-
static updateParticles({ seed, noise, particlesData, thanosOptions, animationState, }) {
|
|
35
|
-
const { deltaTSec, animationT, maxWidth, maxHeight } = animationState;
|
|
36
|
-
const { particleAcceleration } = thanosOptions;
|
|
37
|
-
const { particles, maxParticleX, minParticleY } = particlesData;
|
|
38
|
-
// the time is used to calculate the vaporization front.
|
|
39
|
-
const time = Math.sin(animationT * (Math.PI / 2)) * 1.1;
|
|
40
|
-
const startAccelerateX = maxParticleX - time * maxParticleX;
|
|
41
|
-
const startAccelerateY = time * (maxHeight - minParticleY) + minParticleY;
|
|
42
|
-
const lengthY = maxHeight - startAccelerateY;
|
|
43
|
-
const accelerateRadiusPow = startAccelerateX * startAccelerateX + lengthY * lengthY;
|
|
44
|
-
for (let i = 0; i < particles.length; i += PARTICLE_BYTE_LENGTH) {
|
|
45
|
-
const { x, y, dx, dy, ax, ay, a } = WsThanosService.getParticleIndicesForBase(i);
|
|
46
|
-
const particleX = particles[x];
|
|
47
|
-
const particleY = particles[y];
|
|
48
|
-
// only update particles that are inside view and visible
|
|
49
|
-
if (particleX > maxWidth ||
|
|
50
|
-
particleX < 0 ||
|
|
51
|
-
particleY > maxHeight ||
|
|
52
|
-
particleY < 0 ||
|
|
53
|
-
particles[a] < MIN_PARTICLE_ALPHA) {
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (!(Math.abs(particles[ax]) > 0 || Math.abs(particles[ay]) > 0)) {
|
|
57
|
-
let pYLength = maxHeight - particleY;
|
|
58
|
-
let pXLength = particleX;
|
|
59
|
-
// we calculate some random looking numbers and functions to have nice looking vaporizing front of particles
|
|
60
|
-
// todo consider to use noise;
|
|
61
|
-
pXLength += Math.tan((pXLength / 20.12) * time + seed) * 0.5;
|
|
62
|
-
pXLength += (particleX % deltaTSec) * 0.5;
|
|
63
|
-
pXLength +=
|
|
64
|
-
Math.sin((pXLength / 30 + 723.394) * time + seed * 12.5) * 11;
|
|
65
|
-
pYLength += Math.tan((pYLength / 0.45) * time + seed * 1.5) * 0.5;
|
|
66
|
-
pYLength +=
|
|
67
|
-
Math.cos((pYLength / 100 + 2323.234) * time + seed * 456.1) * 23;
|
|
68
|
-
const pLength = pXLength * pXLength + pYLength * pYLength;
|
|
69
|
-
if (pLength > accelerateRadiusPow) {
|
|
70
|
-
particles[ax] = Math.random();
|
|
71
|
-
particles[ay] = Math.random() * -1;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
// use noise to flow along velocity field
|
|
76
|
-
const flowFieldAcc = WsThanosService.getXYFromFlowField(noise, seed, particleX, particleY);
|
|
77
|
-
const accelerationX = flowFieldAcc[0];
|
|
78
|
-
const accelerationY = flowFieldAcc[1];
|
|
79
|
-
particles[ax] += accelerationX;
|
|
80
|
-
particles[ay] += accelerationY;
|
|
81
|
-
}
|
|
82
|
-
// * Math.pow(1 + animationT, 4)
|
|
83
|
-
particles[dx] += particles[ax] * particleAcceleration * deltaTSec;
|
|
84
|
-
particles[dy] += particles[ay] * particleAcceleration * deltaTSec;
|
|
85
|
-
particles[x] += particles[dx] * deltaTSec;
|
|
86
|
-
particles[y] += particles[dy] * deltaTSec;
|
|
87
|
-
// fade particle out ( very late);
|
|
88
|
-
particles[a] *= 1 - Math.pow(animationT, 15); // 255 - Math.sqrt(particles[dx] * particles[dx] + particles[dy] * particles[dy]) / 1.2;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
static drawParticles(drawCtx, particles) {
|
|
92
|
-
const { width, height } = drawCtx.canvas;
|
|
93
|
-
drawCtx.clearRect(0, 0, width, height);
|
|
94
|
-
const image = drawCtx.getImageData(0, 0, width, height);
|
|
95
|
-
const imageData = image.data;
|
|
96
|
-
for (let i = 0; i < particles.length; i += PARTICLE_BYTE_LENGTH) {
|
|
97
|
-
const particleX = particles[i];
|
|
98
|
-
const particleY = particles[i + 1];
|
|
99
|
-
const pI = WsThanosService.getParticleIndicesForBase(i);
|
|
100
|
-
if (particleX > width ||
|
|
101
|
-
particleX < 0 ||
|
|
102
|
-
particleY > height ||
|
|
103
|
-
particleY < 0 ||
|
|
104
|
-
particles[pI.a] < MIN_PARTICLE_ALPHA) {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
const { r, g, b, a } = WsThanosService.getColorIndicesForCoord(~~particles[pI.x], ~~particles[pI.y], width);
|
|
108
|
-
imageData[r] = ~~particles[pI.r];
|
|
109
|
-
imageData[g] = ~~particles[pI.g];
|
|
110
|
-
imageData[b] = ~~particles[pI.b];
|
|
111
|
-
imageData[a] = ~~particles[pI.a];
|
|
112
|
-
}
|
|
113
|
-
drawCtx.putImageData(image, 0, 0);
|
|
114
|
-
}
|
|
115
|
-
static prepareCanvasForVaporize(divCanvas, maxParticleCount) {
|
|
116
|
-
const { width, height } = divCanvas;
|
|
117
|
-
// scale result canvas to have more room to draw particles while vaporizing;
|
|
118
|
-
const resultHeight = height * HEIGHT_SCALE;
|
|
119
|
-
const resultWidth = width * WIDTH_SCALE;
|
|
120
|
-
const resultCanvas = document.createElement('canvas');
|
|
121
|
-
resultCanvas.height = resultHeight;
|
|
122
|
-
resultCanvas.width = resultWidth;
|
|
123
|
-
const imageData = divCanvas
|
|
124
|
-
.getContext('2d', { willReadFrequently: true })
|
|
125
|
-
?.getImageData(0, 0, width, height);
|
|
126
|
-
if (imageData == null) {
|
|
127
|
-
throw new Error('Could not get image data from canvas');
|
|
128
|
-
}
|
|
129
|
-
const particlesData = WsThanosService.createParticlesForImageData(imageData, maxParticleCount, resultHeight);
|
|
130
|
-
return { resultCanvas, particlesData };
|
|
131
|
-
}
|
|
132
|
-
static createParticlesForImageData(imageData, maxParticleCount, resultHeight) {
|
|
133
|
-
const { width, height } = imageData;
|
|
134
|
-
let particleCandidates = 0;
|
|
135
|
-
let particleCount = 0;
|
|
136
|
-
const particleCandiatesList = [];
|
|
137
|
-
for (let y = 0; y < height; y++) {
|
|
138
|
-
for (let x = 0; x < width; x++) {
|
|
139
|
-
const { a } = WsThanosService.getColorIndicesForCoord(x, y, width);
|
|
140
|
-
if (imageData.data[a] >= MIN_PARTICLE_ALPHA) {
|
|
141
|
-
particleCandidates++;
|
|
142
|
-
particleCandiatesList.push({ x, y });
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const maxNumParticles = Math.min(particleCandidates, maxParticleCount);
|
|
147
|
-
const particles = new Float32Array(Float32Array.BYTES_PER_ELEMENT * maxNumParticles * PARTICLE_BYTE_LENGTH);
|
|
148
|
-
let maxParticleX = 0;
|
|
149
|
-
let minParticleY = resultHeight;
|
|
150
|
-
while (particleCount < maxNumParticles) {
|
|
151
|
-
// select random index from candiates
|
|
152
|
-
const index = ~~(Math.random() * particleCandiatesList.length);
|
|
153
|
-
const { x, y } = particleCandiatesList[index];
|
|
154
|
-
// overwrite index with last element to prevent double selection of particles
|
|
155
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
156
|
-
particleCandiatesList[index] = particleCandiatesList.pop();
|
|
157
|
-
maxParticleX = Math.max(maxParticleX, x);
|
|
158
|
-
minParticleY = Math.min(minParticleY, y);
|
|
159
|
-
const { dx, dy, ax, ay, r, g, b, a, ...xy } = WsThanosService.getParticleIndicesForBase(particleCount * PARTICLE_BYTE_LENGTH);
|
|
160
|
-
particles[xy.x] = x;
|
|
161
|
-
particles[xy.y] = y + resultHeight - height;
|
|
162
|
-
particles[dx] = 0;
|
|
163
|
-
particles[dy] = 0;
|
|
164
|
-
particles[ax] = 0;
|
|
165
|
-
particles[ay] = 0;
|
|
166
|
-
const indicesImage = WsThanosService.getColorIndicesForCoord(x, y, width);
|
|
167
|
-
particles[r] = imageData.data[indicesImage.r];
|
|
168
|
-
particles[g] = imageData.data[indicesImage.g];
|
|
169
|
-
particles[b] = imageData.data[indicesImage.b];
|
|
170
|
-
particles[a] = imageData.data[indicesImage.a];
|
|
171
|
-
particleCount++;
|
|
172
|
-
}
|
|
173
|
-
return { particles, maxParticleX, minParticleY };
|
|
174
|
-
}
|
|
175
|
-
static getXYFromFlowField(noise, seed, x, y) {
|
|
176
|
-
const sampleX = noise.scaled3D(x, y, seed + 33.23, FLOW_FIELD_RES);
|
|
177
|
-
const sampleY = noise.scaled3D(x, seed / 13.23, y, FLOW_FIELD_RES) * -1;
|
|
178
|
-
return [sampleX, sampleY];
|
|
15
|
+
this.document = inject(DOCUMENT);
|
|
16
|
+
this.snapSound = inject(SnapSoundService);
|
|
17
|
+
/** shared by all effects, browsers only allow a few WebGL contexts */
|
|
18
|
+
this.gpuBudget = new GpuContextBudget();
|
|
179
19
|
}
|
|
180
20
|
/**
|
|
181
21
|
* start the vaporize-effect.
|
|
22
|
+
* @param options override the provided options for this element
|
|
182
23
|
*/
|
|
183
|
-
vaporize(elem) {
|
|
184
|
-
|
|
24
|
+
vaporize(elem, options) {
|
|
25
|
+
// nothing is prepared before someone subscribes
|
|
26
|
+
return defer(() => this.vaporizeIntern(elem, { ...this.thanosOptions, ...options }));
|
|
185
27
|
}
|
|
186
|
-
vaporizeIntern(elem) {
|
|
28
|
+
vaporizeIntern(elem, options) {
|
|
29
|
+
const { animationLength, maxParticleCount, particleAcceleration, crumble } = options;
|
|
30
|
+
const window = this.document.defaultView;
|
|
187
31
|
elem.style.opacity = elem.style.opacity || '1';
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
32
|
+
const rendererChoice = chooseParticleRenderer({
|
|
33
|
+
maxParticleCount,
|
|
34
|
+
devicePixelRatio: window?.devicePixelRatio ?? 1,
|
|
35
|
+
createCanvas: () => this.document.createElement('canvas'),
|
|
36
|
+
gpuBudget: this.gpuBudget,
|
|
37
|
+
});
|
|
38
|
+
const { width, height } = elem.getBoundingClientRect();
|
|
39
|
+
const scale = rendererChoice.captureScale(width, height);
|
|
40
|
+
const seed = Math.random() * 1000;
|
|
41
|
+
let effect;
|
|
191
42
|
const html2CanvasPromise = html2canvas(elem, {
|
|
192
43
|
backgroundColor: null,
|
|
193
|
-
scale
|
|
44
|
+
scale,
|
|
194
45
|
allowTaint: true,
|
|
195
|
-
windowHeight: window
|
|
196
|
-
windowWidth: window
|
|
197
|
-
scrollY: -window
|
|
46
|
+
windowHeight: window?.innerHeight,
|
|
47
|
+
windowWidth: window?.innerWidth,
|
|
48
|
+
scrollY: -(window?.scrollY ?? 0),
|
|
198
49
|
});
|
|
199
|
-
return from(html2CanvasPromise).pipe(map((
|
|
200
|
-
const
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
50
|
+
return from(html2CanvasPromise).pipe(map((capturedElem) => {
|
|
51
|
+
const effectWidth = capturedElem.width * EFFECT_WIDTH_SCALE;
|
|
52
|
+
const effectHeight = capturedElem.height * EFFECT_HEIGHT_SCALE;
|
|
53
|
+
const imageData = capturedElem
|
|
54
|
+
.getContext('2d', { willReadFrequently: true })
|
|
55
|
+
?.getImageData(0, 0, capturedElem.width, capturedElem.height);
|
|
56
|
+
if (imageData == null) {
|
|
57
|
+
throw new Error('Could not get image data from canvas');
|
|
205
58
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
59
|
+
const particles = createParticles(imageData, rendererChoice.maxParticleCount, effectHeight);
|
|
60
|
+
const renderer = rendererChoice.create(particles, {
|
|
61
|
+
width: effectWidth,
|
|
62
|
+
height: effectHeight,
|
|
63
|
+
// particles move in device pixels, keep their speed in css pixels
|
|
64
|
+
particleAcceleration: particleAcceleration * scale,
|
|
65
|
+
pixelScale: scale,
|
|
66
|
+
crumble,
|
|
67
|
+
seed,
|
|
68
|
+
});
|
|
69
|
+
this.placeEffectCanvas(elem, renderer.canvas, width, height);
|
|
70
|
+
renderer.canvas.dataset['wsThanosRenderer'] = renderer.kind;
|
|
71
|
+
renderer.canvas.dataset['wsThanosCrumble'] = renderer.crumble;
|
|
72
|
+
// when every pixel became a particle, the particles replace the element right away
|
|
73
|
+
const fadeOutMs = particles.sampled
|
|
74
|
+
? Math.floor(animationLength * 0.8)
|
|
75
|
+
: FULL_COVERAGE_FADE_OUT_MS;
|
|
76
|
+
elem.style.transition = `opacity ${fadeOutMs}ms ease-out`;
|
|
214
77
|
elem.style.opacity = '0';
|
|
215
|
-
|
|
216
|
-
|
|
78
|
+
const { sound, soundVolume } = options;
|
|
79
|
+
effect = {
|
|
80
|
+
renderer,
|
|
81
|
+
sound: sound
|
|
82
|
+
? this.snapSound.play(animationLength, soundVolume, renderer.crumble)
|
|
83
|
+
: undefined,
|
|
84
|
+
};
|
|
85
|
+
return effect;
|
|
86
|
+
}), switchMap(({ renderer, sound }) => {
|
|
217
87
|
let time = 0;
|
|
218
|
-
const { animationLength, maxParticleCount, particleAcceleration } = this.thanosOptions;
|
|
219
88
|
return interval(1000 / 60, animationFrameScheduler).pipe(timeInterval(), tap((deltaT) => (time += deltaT.interval)), map((deltaT) => ({
|
|
220
89
|
deltaTSec: deltaT.interval / 1000,
|
|
221
90
|
animationT: time / animationLength,
|
|
222
|
-
maxWidth:
|
|
223
|
-
maxHeight:
|
|
224
|
-
})), tap((animationState) =>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
maxParticleCount,
|
|
231
|
-
particleAcceleration,
|
|
232
|
-
},
|
|
233
|
-
noise,
|
|
234
|
-
seed,
|
|
235
|
-
});
|
|
236
|
-
const context = resultCanvas.getContext('2d', {
|
|
237
|
-
willReadFrequently: true,
|
|
238
|
-
});
|
|
239
|
-
if (context) {
|
|
240
|
-
WsThanosService.drawParticles(context, particlesData.particles);
|
|
241
|
-
}
|
|
242
|
-
}), takeWhile((animationState) => animationState.animationT <= 1), finalize(() => resultCanvas.remove()));
|
|
91
|
+
maxWidth: renderer.canvas.width,
|
|
92
|
+
maxHeight: renderer.canvas.height,
|
|
93
|
+
})), tap((animationState) => renderer.render(animationState)), takeWhile((animationState) => animationState.animationT <= 1),
|
|
94
|
+
// a completed effect lets the sound blow away, a cancelled one stops it
|
|
95
|
+
tap({ unsubscribe: () => sound?.stop(), error: () => sound?.stop() }));
|
|
96
|
+
}), finalize(() => {
|
|
97
|
+
effect?.renderer.dispose();
|
|
98
|
+
rendererChoice.release();
|
|
243
99
|
}));
|
|
244
100
|
}
|
|
101
|
+
/** the element sits at the bottom left of the larger effect canvas */
|
|
102
|
+
placeEffectCanvas(elem, canvas, width, height) {
|
|
103
|
+
const { left, top } = this.offsetInParent(elem);
|
|
104
|
+
canvas.style.position = 'absolute';
|
|
105
|
+
canvas.style.left = `${left}px`;
|
|
106
|
+
canvas.style.top = `${top - height * (EFFECT_HEIGHT_SCALE - 1)}px`;
|
|
107
|
+
canvas.style.width = `${width * EFFECT_WIDTH_SCALE}px`;
|
|
108
|
+
canvas.style.height = `${height * EFFECT_HEIGHT_SCALE}px`;
|
|
109
|
+
canvas.style.zIndex = '2000';
|
|
110
|
+
canvas.style.pointerEvents = 'none';
|
|
111
|
+
elem.insertAdjacentElement('beforebegin', canvas);
|
|
112
|
+
}
|
|
113
|
+
/** position of the element inside its parent, where absolute children are placed */
|
|
114
|
+
offsetInParent(elem) {
|
|
115
|
+
const parent = elem.parentElement;
|
|
116
|
+
if (parent == null) {
|
|
117
|
+
return { left: 0, top: 0 };
|
|
118
|
+
}
|
|
119
|
+
parent.style.position = parent.style.position || 'relative';
|
|
120
|
+
// absolute positions are relative to the parent's padding box
|
|
121
|
+
const elemRect = elem.getBoundingClientRect();
|
|
122
|
+
const parentRect = parent.getBoundingClientRect();
|
|
123
|
+
return {
|
|
124
|
+
left: elemRect.left - parentRect.left - parent.clientLeft + parent.scrollLeft,
|
|
125
|
+
top: elemRect.top - parentRect.top - parent.clientTop + parent.scrollTop,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
245
128
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: WsThanosService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
246
129
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: WsThanosService, providedIn: 'root' }); }
|
|
247
130
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-thanos.service.js","sourceRoot":"","sources":["../../../../../../libs/public/ws-thanos/src/lib/ws-thanos.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, inject } from '@angular/core';\n\nimport { default as html2canvas } from 'html2canvas';\nimport {\n animationFrameScheduler,\n finalize,\n from,\n interval,\n map,\n Observable,\n switchMap,\n takeWhile,\n tap,\n timeInterval,\n} from 'rxjs';\nimport { AnimationState } from './animation.state';\nimport { SimplexNoise } from './simplex-noise';\nimport { WS_THANOS_OPTIONS_TOKEN } from './ws-thanos-options.token';\nimport type { WsThanosOptions } from './ws-thanos.options';\n\nconst PARTICLE_BYTE_LENGTH = 10;\nconst MIN_PARTICLE_ALPHA = ~~(255 * 0.01);\nconst HEIGHT_SCALE = 5;\nconst WIDTH_SCALE = 2;\nconst FLOW_FIELD_RES = 0.05;\n\ninterface ParticlesData {\n particles: Float32Array;\n maxParticleX: number;\n minParticleY: number;\n}\n\ninterface UpdateParticleParams {\n particlesData: ParticlesData;\n animationState: AnimationState;\n thanosOptions: WsThanosOptions;\n noise: SimplexNoise;\n seed: number;\n}\n\ninterface ParticleIndices {\n x: number;\n y: number;\n dx: number;\n dy: number;\n ax: number;\n ay: number;\n r: number;\n g: number;\n b: number;\n a: number;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class WsThanosService {\n private readonly thanosOptions: WsThanosOptions = inject(\n WS_THANOS_OPTIONS_TOKEN\n );\n\n private static getParticleIndicesForBase(base: number): ParticleIndices {\n return {\n x: base,\n y: base + 1,\n dx: base + 2,\n dy: base + 3,\n ax: base + 4,\n ay: base + 5,\n r: base + 6,\n g: base + 7,\n b: base + 8,\n a: base + 9,\n };\n }\n\n private static getColorIndicesForCoord(\n x: number,\n y: number,\n width: number\n ): { a: number; r: number; b: number; g: number } {\n const red = y * (width * 4) + x * 4;\n return { r: red, g: red + 1, b: red + 2, a: red + 3 };\n }\n\n private static updateParticles({\n seed,\n noise,\n particlesData,\n thanosOptions,\n animationState,\n }: UpdateParticleParams): void {\n const { deltaTSec, animationT, maxWidth, maxHeight } = animationState;\n const { particleAcceleration } = thanosOptions;\n const { particles, maxParticleX, minParticleY } = particlesData;\n\n // the time is used to calculate the vaporization front.\n const time = Math.sin(animationT * (Math.PI / 2)) * 1.1;\n\n const startAccelerateX = maxParticleX - time * maxParticleX;\n const startAccelerateY = time * (maxHeight - minParticleY) + minParticleY;\n\n const lengthY = maxHeight - startAccelerateY;\n const accelerateRadiusPow =\n startAccelerateX * startAccelerateX + lengthY * lengthY;\n\n for (let i = 0; i < particles.length; i += PARTICLE_BYTE_LENGTH) {\n const { x, y, dx, dy, ax, ay, a } =\n WsThanosService.getParticleIndicesForBase(i);\n const particleX = particles[x];\n const particleY = particles[y];\n\n // only update particles that are inside view and visible\n if (\n particleX > maxWidth ||\n particleX < 0 ||\n particleY > maxHeight ||\n particleY < 0 ||\n particles[a] < MIN_PARTICLE_ALPHA\n ) {\n continue;\n }\n\n if (!(Math.abs(particles[ax]) > 0 || Math.abs(particles[ay]) > 0)) {\n let pYLength = maxHeight - particleY;\n let pXLength = particleX;\n\n // we calculate some random looking numbers and functions to have nice looking vaporizing front of particles\n // todo consider to use noise;\n pXLength += Math.tan((pXLength / 20.12) * time + seed) * 0.5;\n pXLength += (particleX % deltaTSec) * 0.5;\n pXLength +=\n Math.sin((pXLength / 30 + 723.394) * time + seed * 12.5) * 11;\n pYLength += Math.tan((pYLength / 0.45) * time + seed * 1.5) * 0.5;\n pYLength +=\n Math.cos((pYLength / 100 + 2323.234) * time + seed * 456.1) * 23;\n\n const pLength = pXLength * pXLength + pYLength * pYLength;\n if (pLength > accelerateRadiusPow) {\n particles[ax] = Math.random();\n particles[ay] = Math.random() * -1;\n }\n } else {\n // use noise to flow along velocity field\n const flowFieldAcc = WsThanosService.getXYFromFlowField(\n noise,\n seed,\n particleX,\n particleY\n );\n const accelerationX = flowFieldAcc[0];\n const accelerationY = flowFieldAcc[1];\n particles[ax] += accelerationX;\n particles[ay] += accelerationY;\n }\n\n // * Math.pow(1 + animationT, 4)\n particles[dx] += particles[ax] * particleAcceleration * deltaTSec;\n particles[dy] += particles[ay] * particleAcceleration * deltaTSec;\n particles[x] += particles[dx] * deltaTSec;\n particles[y] += particles[dy] * deltaTSec;\n // fade particle out ( very late);\n particles[a] *= 1 - Math.pow(animationT, 15); // 255 - Math.sqrt(particles[dx] * particles[dx] + particles[dy] * particles[dy]) / 1.2;\n }\n }\n\n private static drawParticles(\n drawCtx: CanvasRenderingContext2D,\n particles: Float32Array\n ): void {\n const { width, height } = drawCtx.canvas;\n drawCtx.clearRect(0, 0, width, height);\n const image = drawCtx.getImageData(0, 0, width, height);\n const imageData = image.data;\n for (let i = 0; i < particles.length; i += PARTICLE_BYTE_LENGTH) {\n const particleX = particles[i];\n const particleY = particles[i + 1];\n const pI = WsThanosService.getParticleIndicesForBase(i);\n if (\n particleX > width ||\n particleX < 0 ||\n particleY > height ||\n particleY < 0 ||\n particles[pI.a] < MIN_PARTICLE_ALPHA\n ) {\n continue;\n }\n const { r, g, b, a } = WsThanosService.getColorIndicesForCoord(\n ~~particles[pI.x],\n ~~particles[pI.y],\n width\n );\n imageData[r] = ~~particles[pI.r];\n imageData[g] = ~~particles[pI.g];\n imageData[b] = ~~particles[pI.b];\n imageData[a] = ~~particles[pI.a];\n }\n drawCtx.putImageData(image, 0, 0);\n }\n\n private static prepareCanvasForVaporize(\n divCanvas: HTMLCanvasElement,\n maxParticleCount: number\n ): {\n particlesData: ParticlesData;\n resultCanvas: HTMLCanvasElement;\n } {\n const { width, height } = divCanvas;\n\n // scale result canvas to have more room to draw particles while vaporizing;\n const resultHeight = height * HEIGHT_SCALE;\n const resultWidth = width * WIDTH_SCALE;\n const resultCanvas: HTMLCanvasElement = document.createElement('canvas');\n resultCanvas.height = resultHeight;\n resultCanvas.width = resultWidth;\n\n const imageData = divCanvas\n .getContext('2d', { willReadFrequently: true })\n ?.getImageData(0, 0, width, height);\n if (imageData == null) {\n throw new Error('Could not get image data from canvas');\n }\n const particlesData = WsThanosService.createParticlesForImageData(\n imageData,\n maxParticleCount,\n resultHeight\n );\n return { resultCanvas, particlesData };\n }\n\n private static createParticlesForImageData(\n imageData: ImageData,\n maxParticleCount: number,\n resultHeight: number\n ): { minParticleY: number; maxParticleX: number; particles: Float32Array } {\n const { width, height } = imageData;\n let particleCandidates = 0;\n let particleCount = 0;\n\n const particleCandiatesList: { x: number; y: number }[] = [];\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const { a } = WsThanosService.getColorIndicesForCoord(x, y, width);\n if (imageData.data[a] >= MIN_PARTICLE_ALPHA) {\n particleCandidates++;\n particleCandiatesList.push({ x, y });\n }\n }\n }\n\n const maxNumParticles = Math.min(particleCandidates, maxParticleCount);\n const particles = new Float32Array(\n Float32Array.BYTES_PER_ELEMENT * maxNumParticles * PARTICLE_BYTE_LENGTH\n );\n\n let maxParticleX = 0;\n let minParticleY = resultHeight;\n while (particleCount < maxNumParticles) {\n // select random index from candiates\n const index = ~~(Math.random() * particleCandiatesList.length);\n const { x, y } = particleCandiatesList[index];\n\n // overwrite index with last element to prevent double selection of particles\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n particleCandiatesList[index] = particleCandiatesList.pop()!;\n\n maxParticleX = Math.max(maxParticleX, x);\n minParticleY = Math.min(minParticleY, y);\n const { dx, dy, ax, ay, r, g, b, a, ...xy } =\n WsThanosService.getParticleIndicesForBase(\n particleCount * PARTICLE_BYTE_LENGTH\n );\n particles[xy.x] = x;\n particles[xy.y] = y + resultHeight - height;\n particles[dx] = 0;\n particles[dy] = 0;\n particles[ax] = 0;\n particles[ay] = 0;\n\n const indicesImage = WsThanosService.getColorIndicesForCoord(x, y, width);\n\n particles[r] = imageData.data[indicesImage.r];\n particles[g] = imageData.data[indicesImage.g];\n particles[b] = imageData.data[indicesImage.b];\n particles[a] = imageData.data[indicesImage.a];\n particleCount++;\n }\n return { particles, maxParticleX, minParticleY };\n }\n\n private static getXYFromFlowField(\n noise: SimplexNoise,\n seed: number,\n x: number,\n y: number\n ) {\n const sampleX = noise.scaled3D(x, y, seed + 33.23, FLOW_FIELD_RES);\n const sampleY = noise.scaled3D(x, seed / 13.23, y, FLOW_FIELD_RES) * -1;\n return [sampleX, sampleY];\n }\n\n /**\n * start the vaporize-effect.\n */\n vaporize(elem: HTMLElement): Observable<AnimationState> {\n return this.vaporizeIntern(elem);\n }\n\n private vaporizeIntern(elem: HTMLElement): Observable<AnimationState> {\n elem.style.opacity = elem.style.opacity || '1';\n elem.style.transition = `opacity ${Math.floor(\n this.thanosOptions.animationLength * 0.8\n )}ms ease-out`;\n const noise = new SimplexNoise({ frequency: 0.01, min: 0 });\n const seed = new Date().getTime() * Math.random();\n const html2CanvasPromise: Promise<HTMLCanvasElement> = html2canvas(elem, {\n backgroundColor: null,\n scale: 1,\n allowTaint: true,\n windowHeight: window.innerHeight,\n windowWidth: window.innerWidth,\n scrollY: -window.scrollY,\n });\n\n return from(html2CanvasPromise).pipe(\n map((canvasFromHtmlElem) => {\n const canvasAndParticles = WsThanosService.prepareCanvasForVaporize(\n canvasFromHtmlElem,\n this.thanosOptions.maxParticleCount\n );\n const { resultCanvas } = canvasAndParticles;\n if (elem.parentElement) {\n elem.parentElement.style.position =\n elem.parentElement.style.position || 'relative';\n }\n resultCanvas.style.position = 'absolute';\n resultCanvas.style.left = 0 + 'px';\n resultCanvas.style.top =\n '-' + elem.getBoundingClientRect().height * (HEIGHT_SCALE - 1) + 'px';\n resultCanvas.style.zIndex = '2000';\n resultCanvas.style.pointerEvents = 'none';\n\n elem.insertAdjacentElement('beforebegin', resultCanvas);\n // this should start the transition above defined\n elem.style.opacity = '0';\n return canvasAndParticles;\n }),\n switchMap(({ resultCanvas, particlesData }) => {\n let time = 0;\n const { animationLength, maxParticleCount, particleAcceleration } =\n this.thanosOptions;\n return interval(1000 / 60, animationFrameScheduler).pipe(\n timeInterval(),\n tap((deltaT) => (time += deltaT.interval)),\n map(\n (deltaT) =>\n ({\n deltaTSec: deltaT.interval / 1000,\n animationT: time / animationLength,\n maxWidth: resultCanvas.width,\n maxHeight: resultCanvas.height,\n }) as AnimationState\n ),\n tap((animationState) => {\n WsThanosService.updateParticles({\n particlesData,\n animationState,\n thanosOptions: {\n animationLength,\n maxParticleCount,\n particleAcceleration,\n },\n noise,\n seed,\n });\n const context = resultCanvas.getContext('2d', {\n willReadFrequently: true,\n });\n if (context) {\n WsThanosService.drawParticles(context, particlesData.particles);\n }\n }),\n takeWhile((animationState) => animationState.animationT <= 1),\n finalize(() => resultCanvas.remove())\n );\n })\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ws-thanos.service.js","sourceRoot":"","sources":["../../../../../../libs/public/ws-thanos/src/lib/ws-thanos.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { DOCUMENT, Injectable, inject } from '@angular/core';\n\nimport { default as html2canvas } from 'html2canvas';\nimport {\n animationFrameScheduler,\n defer,\n finalize,\n from,\n interval,\n map,\n Observable,\n switchMap,\n takeWhile,\n tap,\n timeInterval,\n} from 'rxjs';\nimport { AnimationState } from './animation.state';\nimport { EFFECT_HEIGHT_SCALE, EFFECT_WIDTH_SCALE } from './capture-scale';\nimport {\n chooseParticleRenderer,\n GpuContextBudget,\n} from './choose-particle-renderer';\nimport { ParticleRenderer } from './particle-renderer';\nimport { createParticles } from './particles';\nimport { SnapSound, SnapSoundService } from './snap-sound.service';\nimport { WS_THANOS_OPTIONS_TOKEN } from './ws-thanos-options.token';\nimport type { WsThanosOptions } from './ws-thanos.options';\n\n/** a short cross fade hides small differences between capture and element */\nconst FULL_COVERAGE_FADE_OUT_MS = 200;\n\ninterface RunningEffect {\n renderer: ParticleRenderer;\n sound?: SnapSound;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class WsThanosService {\n private readonly thanosOptions: WsThanosOptions = inject(\n WS_THANOS_OPTIONS_TOKEN\n );\n private readonly document = inject(DOCUMENT);\n private readonly snapSound = inject(SnapSoundService);\n /** shared by all effects, browsers only allow a few WebGL contexts */\n private readonly gpuBudget = new GpuContextBudget();\n\n /**\n * start the vaporize-effect.\n * @param options override the provided options for this element\n */\n public vaporize(\n elem: HTMLElement,\n options?: Partial<WsThanosOptions>\n ): Observable<AnimationState> {\n // nothing is prepared before someone subscribes\n return defer(() =>\n this.vaporizeIntern(elem, { ...this.thanosOptions, ...options })\n );\n }\n\n private vaporizeIntern(\n elem: HTMLElement,\n options: WsThanosOptions\n ): Observable<AnimationState> {\n const { animationLength, maxParticleCount, particleAcceleration, crumble } =\n options;\n const window = this.document.defaultView;\n\n elem.style.opacity = elem.style.opacity || '1';\n\n const rendererChoice = chooseParticleRenderer({\n maxParticleCount,\n devicePixelRatio: window?.devicePixelRatio ?? 1,\n createCanvas: () => this.document.createElement('canvas'),\n gpuBudget: this.gpuBudget,\n });\n const { width, height } = elem.getBoundingClientRect();\n const scale = rendererChoice.captureScale(width, height);\n const seed = Math.random() * 1000;\n let effect: RunningEffect | undefined;\n\n const html2CanvasPromise: Promise<HTMLCanvasElement> = html2canvas(elem, {\n backgroundColor: null,\n scale,\n allowTaint: true,\n windowHeight: window?.innerHeight,\n windowWidth: window?.innerWidth,\n scrollY: -(window?.scrollY ?? 0),\n });\n\n return from(html2CanvasPromise).pipe(\n map((capturedElem): RunningEffect => {\n const effectWidth = capturedElem.width * EFFECT_WIDTH_SCALE;\n const effectHeight = capturedElem.height * EFFECT_HEIGHT_SCALE;\n const imageData = capturedElem\n .getContext('2d', { willReadFrequently: true })\n ?.getImageData(0, 0, capturedElem.width, capturedElem.height);\n if (imageData == null) {\n throw new Error('Could not get image data from canvas');\n }\n\n const particles = createParticles(\n imageData,\n rendererChoice.maxParticleCount,\n effectHeight\n );\n const renderer = rendererChoice.create(particles, {\n width: effectWidth,\n height: effectHeight,\n // particles move in device pixels, keep their speed in css pixels\n particleAcceleration: particleAcceleration * scale,\n pixelScale: scale,\n crumble,\n seed,\n });\n\n this.placeEffectCanvas(elem, renderer.canvas, width, height);\n renderer.canvas.dataset['wsThanosRenderer'] = renderer.kind;\n renderer.canvas.dataset['wsThanosCrumble'] = renderer.crumble;\n // when every pixel became a particle, the particles replace the element right away\n const fadeOutMs = particles.sampled\n ? Math.floor(animationLength * 0.8)\n : FULL_COVERAGE_FADE_OUT_MS;\n elem.style.transition = `opacity ${fadeOutMs}ms ease-out`;\n elem.style.opacity = '0';\n\n const { sound, soundVolume } = options;\n effect = {\n renderer,\n sound: sound\n ? this.snapSound.play(\n animationLength,\n soundVolume,\n renderer.crumble\n )\n : undefined,\n };\n return effect;\n }),\n switchMap(({ renderer, sound }) => {\n let time = 0;\n return interval(1000 / 60, animationFrameScheduler).pipe(\n timeInterval(),\n tap((deltaT) => (time += deltaT.interval)),\n map((deltaT): AnimationState => ({\n deltaTSec: deltaT.interval / 1000,\n animationT: time / animationLength,\n maxWidth: renderer.canvas.width,\n maxHeight: renderer.canvas.height,\n })),\n tap((animationState) => renderer.render(animationState)),\n takeWhile((animationState) => animationState.animationT <= 1),\n // a completed effect lets the sound blow away, a cancelled one stops it\n tap({ unsubscribe: () => sound?.stop(), error: () => sound?.stop() })\n );\n }),\n finalize(() => {\n effect?.renderer.dispose();\n rendererChoice.release();\n })\n );\n }\n\n /** the element sits at the bottom left of the larger effect canvas */\n private placeEffectCanvas(\n elem: HTMLElement,\n canvas: HTMLCanvasElement,\n width: number,\n height: number\n ): void {\n const { left, top } = this.offsetInParent(elem);\n canvas.style.position = 'absolute';\n canvas.style.left = `${left}px`;\n canvas.style.top = `${top - height * (EFFECT_HEIGHT_SCALE - 1)}px`;\n canvas.style.width = `${width * EFFECT_WIDTH_SCALE}px`;\n canvas.style.height = `${height * EFFECT_HEIGHT_SCALE}px`;\n canvas.style.zIndex = '2000';\n canvas.style.pointerEvents = 'none';\n elem.insertAdjacentElement('beforebegin', canvas);\n }\n\n /** position of the element inside its parent, where absolute children are placed */\n private offsetInParent(elem: HTMLElement): { left: number; top: number } {\n const parent = elem.parentElement;\n if (parent == null) {\n return { left: 0, top: 0 };\n }\n parent.style.position = parent.style.position || 'relative';\n // absolute positions are relative to the parent's padding box\n const elemRect = elem.getBoundingClientRect();\n const parentRect = parent.getBoundingClientRect();\n return {\n left:\n elemRect.left - parentRect.left - parent.clientLeft + parent.scrollLeft,\n top: elemRect.top - parentRect.top - parent.clientTop + parent.scrollTop,\n };\n }\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AnimationState } from './animation.state';
|
|
2
|
+
import { ParticleRenderer, ParticleRendererParams } from './particle-renderer';
|
|
3
|
+
import { Particles } from './particles';
|
|
4
|
+
import { SimplexNoise } from './simplex-noise';
|
|
5
|
+
/** the CPU can't keep up with more particles than this */
|
|
6
|
+
export declare const CANVAS_MAX_PARTICLE_COUNT = 400000;
|
|
7
|
+
export interface UpdateParticlesParams {
|
|
8
|
+
particles: Particles;
|
|
9
|
+
animationState: AnimationState;
|
|
10
|
+
particleAcceleration: number;
|
|
11
|
+
noise: SimplexNoise;
|
|
12
|
+
seed: number;
|
|
13
|
+
}
|
|
14
|
+
/** Fallback renderer that simulates on the CPU and draws with a 2d context. */
|
|
15
|
+
export declare class CanvasParticleRenderer implements ParticleRenderer {
|
|
16
|
+
readonly canvas: HTMLCanvasElement;
|
|
17
|
+
private readonly particles;
|
|
18
|
+
private readonly params;
|
|
19
|
+
readonly kind = "canvas";
|
|
20
|
+
/** the CPU can't afford to simulate cracks and shards */
|
|
21
|
+
readonly crumble = "dust";
|
|
22
|
+
private readonly noise;
|
|
23
|
+
private readonly context;
|
|
24
|
+
constructor(canvas: HTMLCanvasElement, particles: Particles, params: ParticleRendererParams);
|
|
25
|
+
render(animationState: AnimationState): void;
|
|
26
|
+
dispose(): void;
|
|
27
|
+
}
|
|
28
|
+
export declare function updateParticlesOnCpu({ particles: { state, count, maxParticleX, minParticleY }, animationState: { deltaTSec, animationT, maxWidth, maxHeight }, particleAcceleration, noise, seed, }: UpdateParticlesParams): void;
|
|
29
|
+
export declare function drawParticlesOnCpu(context: CanvasRenderingContext2D, { state, colors, count }: Particles): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** the effect canvas is wider and taller than the element so particles have room to fly */
|
|
2
|
+
export declare const EFFECT_WIDTH_SCALE = 2;
|
|
3
|
+
export declare const EFFECT_HEIGHT_SCALE = 5;
|
|
4
|
+
export interface CaptureScaleParams {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
devicePixelRatio: number;
|
|
8
|
+
maxCanvasSize: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Scale used to capture the element: device pixels for crisp particles,
|
|
12
|
+
* but small enough that the effect canvas fits the device's max canvas size.
|
|
13
|
+
*/
|
|
14
|
+
export declare function computeCaptureScale({ width, height, devicePixelRatio, maxCanvasSize, }: CaptureScaleParams): number;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ParticleRenderer, ParticleRendererParams } from './particle-renderer';
|
|
2
|
+
import { Particles } from './particles';
|
|
3
|
+
/** counts the effects that hold a WebGL context */
|
|
4
|
+
export declare class GpuContextBudget {
|
|
5
|
+
private readonly max;
|
|
6
|
+
private live;
|
|
7
|
+
constructor(max?: number);
|
|
8
|
+
tryAcquire(): boolean;
|
|
9
|
+
release(): void;
|
|
10
|
+
}
|
|
11
|
+
export interface ChooseParticleRendererParams {
|
|
12
|
+
maxParticleCount: number;
|
|
13
|
+
devicePixelRatio: number;
|
|
14
|
+
createCanvas: () => HTMLCanvasElement;
|
|
15
|
+
gpuBudget?: GpuContextBudget;
|
|
16
|
+
}
|
|
17
|
+
export interface ParticleRendererChoice {
|
|
18
|
+
/** how many particles this renderer can handle */
|
|
19
|
+
readonly maxParticleCount: number;
|
|
20
|
+
/** scale to capture an element of the given css size */
|
|
21
|
+
captureScale(width: number, height: number): number;
|
|
22
|
+
create(particles: Particles, params: ParticleRendererParams): ParticleRenderer;
|
|
23
|
+
/** free the reserved GPU context, call it once the effect is over */
|
|
24
|
+
release(): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Picks the GPU renderer when WebGL2 is available and the GPU budget allows it,
|
|
28
|
+
* the CPU canvas renderer otherwise. If the GPU renderer fails to set up,
|
|
29
|
+
* it falls back to the canvas renderer.
|
|
30
|
+
*/
|
|
31
|
+
export declare function chooseParticleRenderer({ maxParticleCount, devicePixelRatio, createCanvas, gpuBudget, }: ChooseParticleRendererParams): ParticleRendererChoice;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { WsThanosCrumble } from './ws-thanos.options';
|
|
2
|
+
export interface CrumbleShape {
|
|
3
|
+
/** half width of the coarse cracks in css pixels, particles closer to a crack break off */
|
|
4
|
+
coarseCrack: number;
|
|
5
|
+
/** half width of the fine cracks in css pixels */
|
|
6
|
+
fineCrack: number;
|
|
7
|
+
/** 0 to 1, how far the shards have shifted and tilted */
|
|
8
|
+
shardGrow: number;
|
|
9
|
+
}
|
|
10
|
+
/** cracks get wider and finer cracks appear while the element decays */
|
|
11
|
+
export declare function crumbleShape(crumble: WsThanosCrumble, animationT: number): CrumbleShape;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AnimationState } from './animation.state';
|
|
2
|
+
import { WsThanosCrumble } from './ws-thanos.options';
|
|
3
|
+
/** moves and draws the particles of one vaporizing element */
|
|
4
|
+
export interface ParticleRenderer {
|
|
5
|
+
/** where the simulation runs */
|
|
6
|
+
readonly kind: 'webgl' | 'canvas';
|
|
7
|
+
/** the effect canvas, positioned by the caller */
|
|
8
|
+
readonly canvas: HTMLCanvasElement;
|
|
9
|
+
/** how this renderer breaks the element apart */
|
|
10
|
+
readonly crumble: WsThanosCrumble;
|
|
11
|
+
/** advance the simulation by one frame and draw it */
|
|
12
|
+
render(animationState: AnimationState): void;
|
|
13
|
+
/** free all resources, the canvas is not usable afterwards */
|
|
14
|
+
dispose(): void;
|
|
15
|
+
}
|
|
16
|
+
export interface ParticleRendererParams {
|
|
17
|
+
/** effect canvas size in device pixels */
|
|
18
|
+
width: number;
|
|
19
|
+
height: number;
|
|
20
|
+
particleAcceleration: number;
|
|
21
|
+
/** device pixels per css pixel */
|
|
22
|
+
pixelScale: number;
|
|
23
|
+
crumble: WsThanosCrumble;
|
|
24
|
+
/** random seed that makes every snap look different */
|
|
25
|
+
seed: number;
|
|
26
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** alpha (0-255) below which a particle counts as invisible */
|
|
2
|
+
export declare const MIN_PARTICLE_ALPHA: number;
|
|
3
|
+
/** float layout of one particle in {@link Particles.state} */
|
|
4
|
+
export declare enum ParticleStateIndex {
|
|
5
|
+
X = 0,
|
|
6
|
+
Y = 1,
|
|
7
|
+
VX = 2,
|
|
8
|
+
VY = 3,
|
|
9
|
+
AX = 4,
|
|
10
|
+
AY = 5,
|
|
11
|
+
ALPHA = 6,
|
|
12
|
+
/** animationT when the particle broke off, 0 while attached, negative while a chunk falls */
|
|
13
|
+
RELEASED_AT = 7
|
|
14
|
+
}
|
|
15
|
+
export declare const PARTICLE_STATE_LENGTH = 8;
|
|
16
|
+
export interface Particles {
|
|
17
|
+
count: number;
|
|
18
|
+
/** position, velocity, acceleration, alpha (0-255) and release time per particle */
|
|
19
|
+
state: Float32Array;
|
|
20
|
+
/** rgba (0-255) per particle */
|
|
21
|
+
colors: Uint8Array;
|
|
22
|
+
/** right most particle x of the captured image, the vaporizing front starts here */
|
|
23
|
+
maxParticleX: number;
|
|
24
|
+
/** top most particle y of the captured image */
|
|
25
|
+
minParticleY: number;
|
|
26
|
+
/** true when only a random sample of the visible pixels became particles */
|
|
27
|
+
sampled: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates one particle per visible pixel, randomly sampled down to maxParticleCount.
|
|
31
|
+
* Particles are placed at the bottom of an effect canvas of the given height.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createParticles(imageData: ImageData, maxParticleCount: number, effectHeight: number): Particles;
|
|
34
|
+
/** a random sample of at most maxCount particles, e.g. when the CPU takes over from the GPU */
|
|
35
|
+
export declare function limitParticles(particles: Particles, maxCount: number): Particles;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loudness of the snap sound layers over the animation progress (0 to 1).
|
|
3
|
+
* The element breaks with a rumble, then the wind slowly grows stronger and
|
|
4
|
+
* carries crackling sand, both get weaker in the last third and fade away.
|
|
5
|
+
*/
|
|
6
|
+
/** part of the animation after which the sound has blown away */
|
|
7
|
+
export declare const SILENT_AT = 0.85;
|
|
8
|
+
/** part of the animation after which the rumble of the breaking element is over */
|
|
9
|
+
export declare const RUMBLE_END = 0.33;
|
|
10
|
+
/** the element breaks: a loud rumble right at the start that dies away within the first third */
|
|
11
|
+
export declare function rumbleEnvelope(progress: number): number;
|
|
12
|
+
/** the wind starts quietly and slowly grows stronger, then fades in the last third */
|
|
13
|
+
export declare function windEnvelope(progress: number): number;
|
|
14
|
+
/** sand crackles as the wind picks it up, gets weaker in the last third and fades */
|
|
15
|
+
export declare function sandEnvelope(progress: number): number;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { WsThanosCrumble } from './ws-thanos.options';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
/** a playing snap sound */
|
|
4
|
+
export interface SnapSound {
|
|
5
|
+
/** fade out quickly and free the sound */
|
|
6
|
+
stop(): void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Generates the snap sound with Web Audio, no audio files needed:
|
|
10
|
+
* the element breaks with a crunch, a rumble and cracks (dust only crackles thinly),
|
|
11
|
+
* then a gusty wind slowly grows stronger and carries hissing, crackling sand,
|
|
12
|
+
* all fading away in the last third.
|
|
13
|
+
* Every snap sounds a bit different.
|
|
14
|
+
*/
|
|
15
|
+
export declare class SnapSoundService {
|
|
16
|
+
private readonly window;
|
|
17
|
+
private audio?;
|
|
18
|
+
/**
|
|
19
|
+
* Play the snap sound. Stays silent when the browser does not allow audio yet,
|
|
20
|
+
* e.g. before the user interacted with the page.
|
|
21
|
+
*/
|
|
22
|
+
play(durationMs: number, volume: number, crumble: WsThanosCrumble): SnapSound;
|
|
23
|
+
/** one audio context with a limiter and noise, shared by all snaps */
|
|
24
|
+
private getAudio;
|
|
25
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SnapSoundService, never>;
|
|
26
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
27
|
+
}
|