@autumnsgrove/gossamer 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/animation.d.ts +80 -0
- package/dist/animation.d.ts.map +1 -0
- package/dist/characters.d.ts +49 -0
- package/dist/characters.d.ts.map +1 -0
- package/dist/colors.d.ts +312 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/index.d.ts +39 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1826 -2
- package/dist/index.js.map +1 -1
- package/dist/patterns.d.ts +217 -0
- package/dist/patterns.d.ts.map +1 -0
- package/dist/renderer.d.ts +140 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/style.css +124 -0
- package/dist/svelte/GossamerBorder.svelte.d.ts +1 -0
- package/dist/svelte/GossamerClouds.svelte.d.ts +1 -0
- package/dist/svelte/GossamerImage.svelte.d.ts +1 -0
- package/dist/svelte/GossamerOverlay.svelte.d.ts +1 -0
- package/dist/svelte/GossamerText.svelte.d.ts +1 -0
- package/dist/svelte/index.d.ts +20 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +3648 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/svelte/presets.d.ts +38 -0
- package/dist/svelte/presets.d.ts.map +1 -0
- package/dist/utils/canvas.d.ts +73 -0
- package/dist/utils/canvas.d.ts.map +1 -0
- package/dist/utils/image.d.ts +74 -0
- package/dist/utils/image.d.ts.map +1 -0
- package/dist/utils/performance.d.ts +86 -0
- package/dist/utils/performance.d.ts.map +1 -0
- package/package.json +34 -7
- package/src/animation.test.ts +254 -0
- package/src/animation.ts +243 -0
- package/src/characters.test.ts +148 -0
- package/src/characters.ts +219 -0
- package/src/colors.ts +234 -0
- package/src/index.test.ts +115 -0
- package/src/index.ts +164 -11
- package/src/patterns.test.ts +273 -0
- package/src/patterns.ts +760 -0
- package/src/renderer.ts +470 -0
- package/src/svelte/GossamerBorder.svelte +326 -0
- package/src/svelte/GossamerClouds.svelte +269 -0
- package/src/svelte/GossamerImage.svelte +266 -0
- package/src/svelte/GossamerOverlay.svelte +232 -0
- package/src/svelte/GossamerText.svelte +239 -0
- package/src/svelte/index.ts +75 -0
- package/src/svelte/presets.ts +174 -0
- package/src/utils/canvas.ts +210 -0
- package/src/utils/image.ts +275 -0
- package/src/utils/performance.ts +282 -0
package/src/patterns.ts
ADDED
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gossamer Pattern Generators
|
|
3
|
+
*
|
|
4
|
+
* Provides noise and pattern generation functions for ambient ASCII effects.
|
|
5
|
+
* Includes Perlin noise, wave patterns, and static noise.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for pattern generation
|
|
10
|
+
*/
|
|
11
|
+
export interface PatternConfig {
|
|
12
|
+
/** Pattern scale - higher values create finer detail */
|
|
13
|
+
frequency: number;
|
|
14
|
+
/** Pattern intensity multiplier */
|
|
15
|
+
amplitude: number;
|
|
16
|
+
/** Animation speed multiplier */
|
|
17
|
+
speed: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Default pattern configuration
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_PATTERN_CONFIG: PatternConfig = {
|
|
24
|
+
frequency: 0.05,
|
|
25
|
+
amplitude: 1.0,
|
|
26
|
+
speed: 0.5,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Permutation table for Perlin noise
|
|
30
|
+
const PERMUTATION = new Uint8Array(512);
|
|
31
|
+
const P = new Uint8Array([
|
|
32
|
+
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142,
|
|
33
|
+
8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203,
|
|
34
|
+
117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165,
|
|
35
|
+
71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92,
|
|
36
|
+
41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208,
|
|
37
|
+
89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217,
|
|
38
|
+
226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58,
|
|
39
|
+
17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155,
|
|
40
|
+
167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218,
|
|
41
|
+
246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249,
|
|
42
|
+
14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4,
|
|
43
|
+
150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156,
|
|
44
|
+
180,
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
// Initialize permutation table
|
|
48
|
+
for (let i = 0; i < 256; i++) {
|
|
49
|
+
PERMUTATION[i] = P[i];
|
|
50
|
+
PERMUTATION[i + 256] = P[i];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fade function for smooth interpolation (6t^5 - 15t^4 + 10t^3)
|
|
55
|
+
*/
|
|
56
|
+
function fade(t: number): number {
|
|
57
|
+
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Linear interpolation
|
|
62
|
+
*/
|
|
63
|
+
function lerp(a: number, b: number, t: number): number {
|
|
64
|
+
return a + t * (b - a);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Gradient function for Perlin noise
|
|
69
|
+
*/
|
|
70
|
+
function grad(hash: number, x: number, y: number): number {
|
|
71
|
+
const h = hash & 3;
|
|
72
|
+
const u = h < 2 ? x : y;
|
|
73
|
+
const v = h < 2 ? y : x;
|
|
74
|
+
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 2D Perlin noise function
|
|
79
|
+
*
|
|
80
|
+
* @param x - X coordinate
|
|
81
|
+
* @param y - Y coordinate
|
|
82
|
+
* @returns Noise value between -1 and 1
|
|
83
|
+
*/
|
|
84
|
+
export function perlinNoise2D(x: number, y: number): number {
|
|
85
|
+
// Find unit square containing point
|
|
86
|
+
const xi = Math.floor(x) & 255;
|
|
87
|
+
const yi = Math.floor(y) & 255;
|
|
88
|
+
|
|
89
|
+
// Find relative position in square
|
|
90
|
+
const xf = x - Math.floor(x);
|
|
91
|
+
const yf = y - Math.floor(y);
|
|
92
|
+
|
|
93
|
+
// Fade curves
|
|
94
|
+
const u = fade(xf);
|
|
95
|
+
const v = fade(yf);
|
|
96
|
+
|
|
97
|
+
// Hash coordinates of square corners
|
|
98
|
+
const aa = PERMUTATION[PERMUTATION[xi] + yi];
|
|
99
|
+
const ab = PERMUTATION[PERMUTATION[xi] + yi + 1];
|
|
100
|
+
const ba = PERMUTATION[PERMUTATION[xi + 1] + yi];
|
|
101
|
+
const bb = PERMUTATION[PERMUTATION[xi + 1] + yi + 1];
|
|
102
|
+
|
|
103
|
+
// Blend results from 4 corners
|
|
104
|
+
const x1 = lerp(grad(aa, xf, yf), grad(ba, xf - 1, yf), u);
|
|
105
|
+
const x2 = lerp(grad(ab, xf, yf - 1), grad(bb, xf - 1, yf - 1), u);
|
|
106
|
+
|
|
107
|
+
return lerp(x1, x2, v);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Fractal Brownian Motion (fBm) using Perlin noise
|
|
112
|
+
* Creates more organic-looking noise by layering multiple octaves
|
|
113
|
+
*
|
|
114
|
+
* @param x - X coordinate
|
|
115
|
+
* @param y - Y coordinate
|
|
116
|
+
* @param octaves - Number of noise layers (default: 4)
|
|
117
|
+
* @param persistence - Amplitude decay per octave (default: 0.5)
|
|
118
|
+
* @returns Noise value between -1 and 1
|
|
119
|
+
*/
|
|
120
|
+
export function fbmNoise(x: number, y: number, octaves: number = 4, persistence: number = 0.5): number {
|
|
121
|
+
let total = 0;
|
|
122
|
+
let frequency = 1;
|
|
123
|
+
let amplitude = 1;
|
|
124
|
+
let maxValue = 0;
|
|
125
|
+
|
|
126
|
+
for (let i = 0; i < octaves; i++) {
|
|
127
|
+
total += perlinNoise2D(x * frequency, y * frequency) * amplitude;
|
|
128
|
+
maxValue += amplitude;
|
|
129
|
+
amplitude *= persistence;
|
|
130
|
+
frequency *= 2;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return total / maxValue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Wave pattern generator
|
|
138
|
+
*
|
|
139
|
+
* @param x - X coordinate
|
|
140
|
+
* @param y - Y coordinate
|
|
141
|
+
* @param time - Time value for animation
|
|
142
|
+
* @param config - Pattern configuration
|
|
143
|
+
* @returns Value between -1 and 1
|
|
144
|
+
*/
|
|
145
|
+
export function wavePattern(
|
|
146
|
+
x: number,
|
|
147
|
+
y: number,
|
|
148
|
+
time: number,
|
|
149
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
150
|
+
): number {
|
|
151
|
+
const { frequency, amplitude, speed } = config;
|
|
152
|
+
const wave1 = Math.sin(x * frequency + time * speed);
|
|
153
|
+
const wave2 = Math.cos(y * frequency + time * speed * 0.7);
|
|
154
|
+
const wave3 = Math.sin((x + y) * frequency * 0.5 + time * speed * 0.5);
|
|
155
|
+
|
|
156
|
+
return ((wave1 + wave2 + wave3) / 3) * amplitude;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Ripple pattern (concentric waves from center)
|
|
161
|
+
*
|
|
162
|
+
* @param x - X coordinate
|
|
163
|
+
* @param y - Y coordinate
|
|
164
|
+
* @param centerX - Ripple center X
|
|
165
|
+
* @param centerY - Ripple center Y
|
|
166
|
+
* @param time - Time value for animation
|
|
167
|
+
* @param config - Pattern configuration
|
|
168
|
+
* @returns Value between -1 and 1
|
|
169
|
+
*/
|
|
170
|
+
export function ripplePattern(
|
|
171
|
+
x: number,
|
|
172
|
+
y: number,
|
|
173
|
+
centerX: number,
|
|
174
|
+
centerY: number,
|
|
175
|
+
time: number,
|
|
176
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
177
|
+
): number {
|
|
178
|
+
const { frequency, amplitude, speed } = config;
|
|
179
|
+
const dx = x - centerX;
|
|
180
|
+
const dy = y - centerY;
|
|
181
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
182
|
+
|
|
183
|
+
return Math.sin(distance * frequency - time * speed) * amplitude;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Static noise generator (random values)
|
|
188
|
+
*
|
|
189
|
+
* @param seed - Optional seed for reproducible noise
|
|
190
|
+
* @returns Value between 0 and 1
|
|
191
|
+
*/
|
|
192
|
+
export function staticNoise(seed?: number): number {
|
|
193
|
+
if (seed !== undefined) {
|
|
194
|
+
// Simple seeded random using sine
|
|
195
|
+
const x = Math.sin(seed * 12.9898) * 43758.5453;
|
|
196
|
+
return x - Math.floor(x);
|
|
197
|
+
}
|
|
198
|
+
return Math.random();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Seeded 2D noise for reproducible patterns
|
|
203
|
+
*
|
|
204
|
+
* @param x - X coordinate
|
|
205
|
+
* @param y - Y coordinate
|
|
206
|
+
* @param seed - Seed value
|
|
207
|
+
* @returns Value between 0 and 1
|
|
208
|
+
*/
|
|
209
|
+
export function seededNoise2D(x: number, y: number, seed: number = 0): number {
|
|
210
|
+
const n = Math.sin(x * 12.9898 + y * 78.233 + seed) * 43758.5453;
|
|
211
|
+
return n - Math.floor(n);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Clouds pattern - soft, billowy fbm with gentle movement
|
|
216
|
+
* The signature Gossamer effect!
|
|
217
|
+
*
|
|
218
|
+
* @param x - X coordinate
|
|
219
|
+
* @param y - Y coordinate
|
|
220
|
+
* @param time - Time value for animation
|
|
221
|
+
* @param config - Pattern configuration
|
|
222
|
+
* @returns Value between -1 and 1
|
|
223
|
+
*/
|
|
224
|
+
export function cloudsPattern(
|
|
225
|
+
x: number,
|
|
226
|
+
y: number,
|
|
227
|
+
time: number,
|
|
228
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
229
|
+
): number {
|
|
230
|
+
const { frequency, amplitude, speed } = config;
|
|
231
|
+
|
|
232
|
+
// Slow, drifting movement
|
|
233
|
+
const drift = time * speed * 0.02;
|
|
234
|
+
const nx = x * frequency * 0.5 + drift;
|
|
235
|
+
const ny = y * frequency * 0.5 + drift * 0.7;
|
|
236
|
+
|
|
237
|
+
// Layer multiple octaves with high persistence for soft, puffy look
|
|
238
|
+
const base = fbmNoise(nx, ny, 5, 0.6);
|
|
239
|
+
|
|
240
|
+
// Add subtle secondary drift layer
|
|
241
|
+
const detail = fbmNoise(nx * 2 + drift * 0.5, ny * 2 - drift * 0.3, 3, 0.5) * 0.3;
|
|
242
|
+
|
|
243
|
+
// Combine and bias toward lighter values (more sky, less dense cloud)
|
|
244
|
+
const combined = base + detail;
|
|
245
|
+
return Math.tanh(combined * 1.5) * amplitude;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Plasma pattern - classic demoscene effect
|
|
250
|
+
* Combines sine waves at different frequencies and phases
|
|
251
|
+
*
|
|
252
|
+
* @param x - X coordinate
|
|
253
|
+
* @param y - Y coordinate
|
|
254
|
+
* @param time - Time value for animation
|
|
255
|
+
* @param config - Pattern configuration
|
|
256
|
+
* @returns Value between -1 and 1
|
|
257
|
+
*/
|
|
258
|
+
export function plasmaPattern(
|
|
259
|
+
x: number,
|
|
260
|
+
y: number,
|
|
261
|
+
time: number,
|
|
262
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
263
|
+
): number {
|
|
264
|
+
const { frequency, amplitude, speed } = config;
|
|
265
|
+
const t = time * speed;
|
|
266
|
+
|
|
267
|
+
// Classic plasma: sum of sines at different scales and rotations
|
|
268
|
+
const v1 = Math.sin(x * frequency + t);
|
|
269
|
+
const v2 = Math.sin(y * frequency + t * 0.7);
|
|
270
|
+
const v3 = Math.sin((x + y) * frequency * 0.5 + t * 0.5);
|
|
271
|
+
const v4 = Math.sin(Math.sqrt(x * x + y * y) * frequency * 0.3 + t * 0.8);
|
|
272
|
+
|
|
273
|
+
// Add some swirl
|
|
274
|
+
const cx = x - 40;
|
|
275
|
+
const cy = y - 20;
|
|
276
|
+
const v5 = Math.sin(Math.atan2(cy, cx) * 3 + t * 0.4);
|
|
277
|
+
|
|
278
|
+
return ((v1 + v2 + v3 + v4 + v5) / 5) * amplitude;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Vortex pattern - swirling spiral around center
|
|
283
|
+
*
|
|
284
|
+
* @param x - X coordinate
|
|
285
|
+
* @param y - Y coordinate
|
|
286
|
+
* @param centerX - Vortex center X
|
|
287
|
+
* @param centerY - Vortex center Y
|
|
288
|
+
* @param time - Time value for animation
|
|
289
|
+
* @param config - Pattern configuration
|
|
290
|
+
* @returns Value between -1 and 1
|
|
291
|
+
*/
|
|
292
|
+
export function vortexPattern(
|
|
293
|
+
x: number,
|
|
294
|
+
y: number,
|
|
295
|
+
centerX: number,
|
|
296
|
+
centerY: number,
|
|
297
|
+
time: number,
|
|
298
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
299
|
+
): number {
|
|
300
|
+
const { frequency, amplitude, speed } = config;
|
|
301
|
+
|
|
302
|
+
const dx = x - centerX;
|
|
303
|
+
const dy = y - centerY;
|
|
304
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
305
|
+
const angle = Math.atan2(dy, dx);
|
|
306
|
+
|
|
307
|
+
// Spiral: angle + distance creates the swirl
|
|
308
|
+
const spiral = angle + distance * frequency * 0.1 - time * speed;
|
|
309
|
+
|
|
310
|
+
// Add some turbulence based on distance
|
|
311
|
+
const turbulence = perlinNoise2D(distance * 0.1, time * speed * 0.5) * 0.3;
|
|
312
|
+
|
|
313
|
+
return (Math.sin(spiral * 3) + turbulence) * amplitude;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Matrix pattern - falling columns like digital rain
|
|
318
|
+
*
|
|
319
|
+
* @param x - X coordinate (column)
|
|
320
|
+
* @param y - Y coordinate (row)
|
|
321
|
+
* @param time - Time value for animation
|
|
322
|
+
* @param config - Pattern configuration
|
|
323
|
+
* @returns Value between -1 and 1
|
|
324
|
+
*/
|
|
325
|
+
export function matrixPattern(
|
|
326
|
+
x: number,
|
|
327
|
+
y: number,
|
|
328
|
+
time: number,
|
|
329
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
330
|
+
): number {
|
|
331
|
+
const { frequency, amplitude, speed } = config;
|
|
332
|
+
|
|
333
|
+
// Each column has its own speed and phase based on x position
|
|
334
|
+
const columnSeed = seededNoise2D(x, 0, 42);
|
|
335
|
+
const columnSpeed = 0.5 + columnSeed * 1.5;
|
|
336
|
+
const columnPhase = columnSeed * 100;
|
|
337
|
+
|
|
338
|
+
// Calculate falling position
|
|
339
|
+
const fallPosition = (y * frequency + time * speed * columnSpeed + columnPhase) % 20;
|
|
340
|
+
|
|
341
|
+
// Create "head" of the rain drop (brightest) with trailing fade
|
|
342
|
+
const headBrightness = fallPosition < 1 ? 1 : 0;
|
|
343
|
+
const trailLength = 8;
|
|
344
|
+
const trailBrightness =
|
|
345
|
+
fallPosition < trailLength ? Math.pow(1 - fallPosition / trailLength, 2) : 0;
|
|
346
|
+
|
|
347
|
+
// Add some randomness for flickering effect
|
|
348
|
+
const flicker = seededNoise2D(x, Math.floor(time * 10), y) * 0.2;
|
|
349
|
+
|
|
350
|
+
const value = Math.max(headBrightness, trailBrightness) + flicker;
|
|
351
|
+
return (value * 2 - 1) * amplitude;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Gradient pattern - smooth animated gradients
|
|
356
|
+
*
|
|
357
|
+
* @param x - X coordinate
|
|
358
|
+
* @param y - Y coordinate
|
|
359
|
+
* @param cols - Total columns (for normalization)
|
|
360
|
+
* @param rows - Total rows (for normalization)
|
|
361
|
+
* @param time - Time value for animation
|
|
362
|
+
* @param config - Pattern configuration
|
|
363
|
+
* @returns Value between -1 and 1
|
|
364
|
+
*/
|
|
365
|
+
export function gradientPattern(
|
|
366
|
+
x: number,
|
|
367
|
+
y: number,
|
|
368
|
+
cols: number,
|
|
369
|
+
rows: number,
|
|
370
|
+
time: number,
|
|
371
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
372
|
+
): number {
|
|
373
|
+
const { frequency, amplitude, speed } = config;
|
|
374
|
+
|
|
375
|
+
// Normalize coordinates to 0-1
|
|
376
|
+
const nx = x / cols;
|
|
377
|
+
const ny = y / rows;
|
|
378
|
+
|
|
379
|
+
// Animated angle for gradient direction
|
|
380
|
+
const angle = time * speed * 0.2;
|
|
381
|
+
const cos = Math.cos(angle);
|
|
382
|
+
const sin = Math.sin(angle);
|
|
383
|
+
|
|
384
|
+
// Rotate the gradient
|
|
385
|
+
const rotated = nx * cos + ny * sin;
|
|
386
|
+
|
|
387
|
+
// Add some wave distortion
|
|
388
|
+
const distortion = Math.sin(ny * Math.PI * 2 * frequency + time * speed) * 0.1;
|
|
389
|
+
|
|
390
|
+
const value = (rotated + distortion) * 2 - 1;
|
|
391
|
+
return Math.sin(value * Math.PI) * amplitude;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Diamond pattern - interference creating diamond shapes
|
|
396
|
+
*
|
|
397
|
+
* @param x - X coordinate
|
|
398
|
+
* @param y - Y coordinate
|
|
399
|
+
* @param time - Time value for animation
|
|
400
|
+
* @param config - Pattern configuration
|
|
401
|
+
* @returns Value between -1 and 1
|
|
402
|
+
*/
|
|
403
|
+
export function diamondPattern(
|
|
404
|
+
x: number,
|
|
405
|
+
y: number,
|
|
406
|
+
time: number,
|
|
407
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
408
|
+
): number {
|
|
409
|
+
const { frequency, amplitude, speed } = config;
|
|
410
|
+
const t = time * speed;
|
|
411
|
+
|
|
412
|
+
// Diamond pattern using absolute value (Manhattan distance creates diamonds)
|
|
413
|
+
const wave1 = Math.sin((Math.abs(x - 40) + Math.abs(y - 20)) * frequency + t);
|
|
414
|
+
const wave2 = Math.sin((Math.abs(x - 40) - Math.abs(y - 20)) * frequency * 0.7 - t * 0.8);
|
|
415
|
+
|
|
416
|
+
// Add some rotation over time
|
|
417
|
+
const angle = t * 0.1;
|
|
418
|
+
const rx = x * Math.cos(angle) - y * Math.sin(angle);
|
|
419
|
+
const ry = x * Math.sin(angle) + y * Math.cos(angle);
|
|
420
|
+
const wave3 = Math.sin((Math.abs(rx) + Math.abs(ry)) * frequency * 0.5 + t * 0.5);
|
|
421
|
+
|
|
422
|
+
return ((wave1 + wave2 + wave3) / 3) * amplitude;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Fractal pattern - animated Mandelbrot/Julia set
|
|
427
|
+
*
|
|
428
|
+
* @param x - X coordinate
|
|
429
|
+
* @param y - Y coordinate
|
|
430
|
+
* @param cols - Total columns (for centering)
|
|
431
|
+
* @param rows - Total rows (for centering)
|
|
432
|
+
* @param time - Time value for animation
|
|
433
|
+
* @param config - Pattern configuration
|
|
434
|
+
* @returns Value between -1 and 1
|
|
435
|
+
*/
|
|
436
|
+
export function fractalPattern(
|
|
437
|
+
x: number,
|
|
438
|
+
y: number,
|
|
439
|
+
cols: number,
|
|
440
|
+
rows: number,
|
|
441
|
+
time: number,
|
|
442
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
443
|
+
): number {
|
|
444
|
+
const { frequency, amplitude, speed } = config;
|
|
445
|
+
|
|
446
|
+
// Map to complex plane, centered and scaled
|
|
447
|
+
const scale = 3.5 * frequency;
|
|
448
|
+
const zx = ((x / cols) * scale - scale / 2) + Math.sin(time * speed * 0.1) * 0.2;
|
|
449
|
+
const zy = ((y / rows) * scale - scale / 2) + Math.cos(time * speed * 0.1) * 0.2;
|
|
450
|
+
|
|
451
|
+
// Julia set with animated c parameter
|
|
452
|
+
const cx = -0.7 + Math.sin(time * speed * 0.05) * 0.1;
|
|
453
|
+
const cy = 0.27 + Math.cos(time * speed * 0.07) * 0.1;
|
|
454
|
+
|
|
455
|
+
let zrx = zx;
|
|
456
|
+
let zry = zy;
|
|
457
|
+
const maxIter = 20;
|
|
458
|
+
let iter = 0;
|
|
459
|
+
|
|
460
|
+
// Iterate z = z² + c
|
|
461
|
+
while (zrx * zrx + zry * zry < 4 && iter < maxIter) {
|
|
462
|
+
const tmp = zrx * zrx - zry * zry + cx;
|
|
463
|
+
zry = 2 * zrx * zry + cy;
|
|
464
|
+
zrx = tmp;
|
|
465
|
+
iter++;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Smooth coloring
|
|
469
|
+
if (iter === maxIter) {
|
|
470
|
+
return -1 * amplitude; // Inside the set
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Normalize iteration count to [-1, 1]
|
|
474
|
+
const smooth = iter - Math.log2(Math.log2(zrx * zrx + zry * zry));
|
|
475
|
+
return ((smooth / maxIter) * 2 - 1) * amplitude;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Generate a brightness grid for pattern rendering
|
|
480
|
+
*
|
|
481
|
+
* @param cols - Number of columns
|
|
482
|
+
* @param rows - Number of rows
|
|
483
|
+
* @param pattern - Pattern type
|
|
484
|
+
* @param time - Current time in seconds
|
|
485
|
+
* @param config - Pattern configuration
|
|
486
|
+
* @returns 2D array of brightness values (0-255)
|
|
487
|
+
*/
|
|
488
|
+
export function generateBrightnessGrid(
|
|
489
|
+
cols: number,
|
|
490
|
+
rows: number,
|
|
491
|
+
pattern: PatternType,
|
|
492
|
+
time: number = 0,
|
|
493
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
494
|
+
): number[][] {
|
|
495
|
+
const grid: number[][] = [];
|
|
496
|
+
const { frequency, amplitude, speed } = config;
|
|
497
|
+
|
|
498
|
+
for (let row = 0; row < rows; row++) {
|
|
499
|
+
grid[row] = [];
|
|
500
|
+
for (let col = 0; col < cols; col++) {
|
|
501
|
+
let value: number;
|
|
502
|
+
|
|
503
|
+
switch (pattern) {
|
|
504
|
+
case 'perlin':
|
|
505
|
+
value = perlinNoise2D(
|
|
506
|
+
col * frequency + time * speed * 0.1,
|
|
507
|
+
row * frequency + time * speed * 0.05
|
|
508
|
+
);
|
|
509
|
+
break;
|
|
510
|
+
|
|
511
|
+
case 'fbm':
|
|
512
|
+
value = fbmNoise(
|
|
513
|
+
col * frequency + time * speed * 0.1,
|
|
514
|
+
row * frequency + time * speed * 0.05,
|
|
515
|
+
4,
|
|
516
|
+
0.5
|
|
517
|
+
);
|
|
518
|
+
break;
|
|
519
|
+
|
|
520
|
+
case 'waves':
|
|
521
|
+
value = wavePattern(col, row, time, config);
|
|
522
|
+
break;
|
|
523
|
+
|
|
524
|
+
case 'ripple':
|
|
525
|
+
value = ripplePattern(col, row, cols / 2, rows / 2, time, config);
|
|
526
|
+
break;
|
|
527
|
+
|
|
528
|
+
case 'static':
|
|
529
|
+
// For static, use time as seed for animated static
|
|
530
|
+
value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
|
|
531
|
+
break;
|
|
532
|
+
|
|
533
|
+
case 'clouds':
|
|
534
|
+
value = cloudsPattern(col, row, time, config);
|
|
535
|
+
break;
|
|
536
|
+
|
|
537
|
+
case 'plasma':
|
|
538
|
+
value = plasmaPattern(col, row, time, config);
|
|
539
|
+
break;
|
|
540
|
+
|
|
541
|
+
case 'vortex':
|
|
542
|
+
value = vortexPattern(col, row, cols / 2, rows / 2, time, config);
|
|
543
|
+
break;
|
|
544
|
+
|
|
545
|
+
case 'matrix':
|
|
546
|
+
value = matrixPattern(col, row, time, config);
|
|
547
|
+
break;
|
|
548
|
+
|
|
549
|
+
case 'gradient':
|
|
550
|
+
value = gradientPattern(col, row, cols, rows, time, config);
|
|
551
|
+
break;
|
|
552
|
+
|
|
553
|
+
case 'diamond':
|
|
554
|
+
value = diamondPattern(col, row, time, config);
|
|
555
|
+
break;
|
|
556
|
+
|
|
557
|
+
case 'fractal':
|
|
558
|
+
value = fractalPattern(col, row, cols, rows, time, config);
|
|
559
|
+
break;
|
|
560
|
+
|
|
561
|
+
default:
|
|
562
|
+
value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Normalize from [-1, 1] to [0, 255] with amplitude
|
|
567
|
+
const normalized = (value + 1) * 0.5 * amplitude;
|
|
568
|
+
const brightness = Math.max(0, Math.min(255, Math.floor(normalized * 255)));
|
|
569
|
+
grid[row][col] = brightness;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return grid;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Generate ImageData from a brightness grid
|
|
578
|
+
*
|
|
579
|
+
* @param grid - 2D array of brightness values
|
|
580
|
+
* @param cellWidth - Width of each cell
|
|
581
|
+
* @param cellHeight - Height of each cell
|
|
582
|
+
* @returns ImageData object
|
|
583
|
+
*/
|
|
584
|
+
export function gridToImageData(grid: number[][], cellWidth: number, cellHeight: number): ImageData {
|
|
585
|
+
const rows = grid.length;
|
|
586
|
+
const cols = grid[0]?.length || 0;
|
|
587
|
+
const width = cols * cellWidth;
|
|
588
|
+
const height = rows * cellHeight;
|
|
589
|
+
const data = new Uint8ClampedArray(width * height * 4);
|
|
590
|
+
|
|
591
|
+
for (let row = 0; row < rows; row++) {
|
|
592
|
+
for (let col = 0; col < cols; col++) {
|
|
593
|
+
const brightness = grid[row][col];
|
|
594
|
+
|
|
595
|
+
// Fill cell region with brightness value
|
|
596
|
+
for (let cy = 0; cy < cellHeight; cy++) {
|
|
597
|
+
for (let cx = 0; cx < cellWidth; cx++) {
|
|
598
|
+
const px = ((row * cellHeight + cy) * width + (col * cellWidth + cx)) * 4;
|
|
599
|
+
data[px] = brightness; // R
|
|
600
|
+
data[px + 1] = brightness; // G
|
|
601
|
+
data[px + 2] = brightness; // B
|
|
602
|
+
data[px + 3] = 255; // A
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return new ImageData(data, width, height);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export type PatternType =
|
|
612
|
+
| 'perlin'
|
|
613
|
+
| 'waves'
|
|
614
|
+
| 'static'
|
|
615
|
+
| 'ripple'
|
|
616
|
+
| 'fbm'
|
|
617
|
+
| 'clouds'
|
|
618
|
+
| 'plasma'
|
|
619
|
+
| 'vortex'
|
|
620
|
+
| 'matrix'
|
|
621
|
+
| 'gradient'
|
|
622
|
+
| 'diamond'
|
|
623
|
+
| 'fractal';
|
|
624
|
+
|
|
625
|
+
// ============================================================================
|
|
626
|
+
// PERFORMANCE-OPTIMIZED API
|
|
627
|
+
// ============================================================================
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Reusable brightness buffer using flat Uint8Array
|
|
631
|
+
* ~30% faster than number[][] due to contiguous memory and no GC pressure
|
|
632
|
+
*/
|
|
633
|
+
export interface BrightnessBuffer {
|
|
634
|
+
/** Flat array of brightness values (0-255), row-major order */
|
|
635
|
+
data: Uint8Array;
|
|
636
|
+
/** Number of columns */
|
|
637
|
+
cols: number;
|
|
638
|
+
/** Number of rows */
|
|
639
|
+
rows: number;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Create a reusable brightness buffer
|
|
644
|
+
* Call once at init, then reuse with fillBrightnessBuffer
|
|
645
|
+
*
|
|
646
|
+
* @param cols - Number of columns
|
|
647
|
+
* @param rows - Number of rows
|
|
648
|
+
* @returns Reusable buffer object
|
|
649
|
+
*/
|
|
650
|
+
export function createBrightnessBuffer(cols: number, rows: number): BrightnessBuffer {
|
|
651
|
+
return {
|
|
652
|
+
data: new Uint8Array(cols * rows),
|
|
653
|
+
cols,
|
|
654
|
+
rows,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Fill an existing brightness buffer with pattern data (zero allocation)
|
|
660
|
+
* Use this in animation loops for best performance
|
|
661
|
+
*
|
|
662
|
+
* @param buffer - Pre-allocated buffer from createBrightnessBuffer
|
|
663
|
+
* @param pattern - Pattern type to generate
|
|
664
|
+
* @param time - Current time in seconds
|
|
665
|
+
* @param config - Pattern configuration
|
|
666
|
+
*/
|
|
667
|
+
export function fillBrightnessBuffer(
|
|
668
|
+
buffer: BrightnessBuffer,
|
|
669
|
+
pattern: PatternType,
|
|
670
|
+
time: number = 0,
|
|
671
|
+
config: PatternConfig = DEFAULT_PATTERN_CONFIG
|
|
672
|
+
): void {
|
|
673
|
+
const { data, cols, rows } = buffer;
|
|
674
|
+
const { frequency, amplitude, speed } = config;
|
|
675
|
+
|
|
676
|
+
let idx = 0;
|
|
677
|
+
for (let row = 0; row < rows; row++) {
|
|
678
|
+
for (let col = 0; col < cols; col++) {
|
|
679
|
+
let value: number;
|
|
680
|
+
|
|
681
|
+
switch (pattern) {
|
|
682
|
+
case 'perlin':
|
|
683
|
+
value = perlinNoise2D(
|
|
684
|
+
col * frequency + time * speed * 0.1,
|
|
685
|
+
row * frequency + time * speed * 0.05
|
|
686
|
+
);
|
|
687
|
+
break;
|
|
688
|
+
|
|
689
|
+
case 'fbm':
|
|
690
|
+
value = fbmNoise(
|
|
691
|
+
col * frequency + time * speed * 0.1,
|
|
692
|
+
row * frequency + time * speed * 0.05,
|
|
693
|
+
4,
|
|
694
|
+
0.5
|
|
695
|
+
);
|
|
696
|
+
break;
|
|
697
|
+
|
|
698
|
+
case 'waves':
|
|
699
|
+
value = wavePattern(col, row, time, config);
|
|
700
|
+
break;
|
|
701
|
+
|
|
702
|
+
case 'ripple':
|
|
703
|
+
value = ripplePattern(col, row, cols / 2, rows / 2, time, config);
|
|
704
|
+
break;
|
|
705
|
+
|
|
706
|
+
case 'static':
|
|
707
|
+
value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
|
|
708
|
+
break;
|
|
709
|
+
|
|
710
|
+
case 'clouds':
|
|
711
|
+
value = cloudsPattern(col, row, time, config);
|
|
712
|
+
break;
|
|
713
|
+
|
|
714
|
+
case 'plasma':
|
|
715
|
+
value = plasmaPattern(col, row, time, config);
|
|
716
|
+
break;
|
|
717
|
+
|
|
718
|
+
case 'vortex':
|
|
719
|
+
value = vortexPattern(col, row, cols / 2, rows / 2, time, config);
|
|
720
|
+
break;
|
|
721
|
+
|
|
722
|
+
case 'matrix':
|
|
723
|
+
value = matrixPattern(col, row, time, config);
|
|
724
|
+
break;
|
|
725
|
+
|
|
726
|
+
case 'gradient':
|
|
727
|
+
value = gradientPattern(col, row, cols, rows, time, config);
|
|
728
|
+
break;
|
|
729
|
+
|
|
730
|
+
case 'diamond':
|
|
731
|
+
value = diamondPattern(col, row, time, config);
|
|
732
|
+
break;
|
|
733
|
+
|
|
734
|
+
case 'fractal':
|
|
735
|
+
value = fractalPattern(col, row, cols, rows, time, config);
|
|
736
|
+
break;
|
|
737
|
+
|
|
738
|
+
default:
|
|
739
|
+
value = seededNoise2D(col, row, Math.floor(time * speed * 10)) * 2 - 1;
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Normalize from [-1, 1] to [0, 255] with amplitude
|
|
744
|
+
// Using bitwise OR for fast floor: (x | 0) is faster than Math.floor(x)
|
|
745
|
+
const normalized = (value + 1) * 0.5 * amplitude;
|
|
746
|
+
data[idx++] = normalized * 255 | 0;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Get brightness value from buffer at (col, row)
|
|
753
|
+
* @param buffer - Brightness buffer
|
|
754
|
+
* @param col - Column index
|
|
755
|
+
* @param row - Row index
|
|
756
|
+
* @returns Brightness value 0-255
|
|
757
|
+
*/
|
|
758
|
+
export function getBufferValue(buffer: BrightnessBuffer, col: number, row: number): number {
|
|
759
|
+
return buffer.data[row * buffer.cols + col];
|
|
760
|
+
}
|