@firecms/neat 0.4.0 → 0.5.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 +365 -68
- package/dist/NeatGradient.d.ts +98 -0
- package/dist/NeatGradient.js +749 -187
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.es.js +570 -222
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +139 -115
- package/dist/index.umd.js.map +1 -1
- package/package.json +3 -3
- package/src/NeatGradient.ts +902 -194
package/src/NeatGradient.ts
CHANGED
|
@@ -4,7 +4,7 @@ const PLANE_WIDTH = 50;
|
|
|
4
4
|
const PLANE_HEIGHT = 80;
|
|
5
5
|
|
|
6
6
|
const WIREFRAME = true;
|
|
7
|
-
const COLORS_COUNT =
|
|
7
|
+
const COLORS_COUNT = 6;
|
|
8
8
|
|
|
9
9
|
const clock = new THREE.Clock();
|
|
10
10
|
|
|
@@ -18,6 +18,16 @@ type SceneState = {
|
|
|
18
18
|
resolution: number
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// Interface for the Uniforms to avoid @ts-ignore and improve access speed
|
|
22
|
+
interface NeatUniforms {
|
|
23
|
+
[key: string]: THREE.IUniform;
|
|
24
|
+
u_time: { value: number };
|
|
25
|
+
u_resolution: { value: THREE.Vector2 };
|
|
26
|
+
u_color_pressure: { value: THREE.Vector2 };
|
|
27
|
+
u_colors: { value: { is_active: number; color: THREE.Color; influence: number }[] };
|
|
28
|
+
u_mouse_texture: { value: THREE.Texture | null };
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
export type NeatConfig = {
|
|
22
32
|
resolution?: number;
|
|
23
33
|
speed?: number;
|
|
@@ -40,6 +50,37 @@ export type NeatConfig = {
|
|
|
40
50
|
backgroundColor?: string;
|
|
41
51
|
backgroundAlpha?: number;
|
|
42
52
|
yOffset?: number;
|
|
53
|
+
yOffsetWaveMultiplier?: number;
|
|
54
|
+
yOffsetColorMultiplier?: number;
|
|
55
|
+
yOffsetFlowMultiplier?: number;
|
|
56
|
+
// Flow field parameters
|
|
57
|
+
flowDistortionA?: number;
|
|
58
|
+
flowDistortionB?: number;
|
|
59
|
+
flowScale?: number;
|
|
60
|
+
flowEase?: number;
|
|
61
|
+
flowEnabled?: boolean;
|
|
62
|
+
// Mouse interaction
|
|
63
|
+
/** Strength of mouse-driven distortion */
|
|
64
|
+
mouseDistortionStrength?: number;
|
|
65
|
+
/** Radius / area of mouse-driven distortion in UV space (0–1-ish) */
|
|
66
|
+
mouseDistortionRadius?: number;
|
|
67
|
+
/** How quickly mouse trails decay/fade (0.9=slow/wobbly, 0.99=fast/sharp) */
|
|
68
|
+
mouseDecayRate?: number;
|
|
69
|
+
mouseDarken?: number;
|
|
70
|
+
// Texture generation
|
|
71
|
+
enableProceduralTexture?: boolean;
|
|
72
|
+
textureVoidLikelihood?: number;
|
|
73
|
+
textureVoidWidthMin?: number;
|
|
74
|
+
textureVoidWidthMax?: number;
|
|
75
|
+
textureBandDensity?: number;
|
|
76
|
+
textureColorBlending?: number;
|
|
77
|
+
textureSeed?: number;
|
|
78
|
+
textureEase?: number;
|
|
79
|
+
proceduralBackgroundColor?: string;
|
|
80
|
+
textureShapeTriangles?: number;
|
|
81
|
+
textureShapeCircles?: number;
|
|
82
|
+
textureShapeBars?: number;
|
|
83
|
+
textureShapeSquiggles?: number;
|
|
43
84
|
};
|
|
44
85
|
|
|
45
86
|
export type NeatColor = {
|
|
@@ -86,11 +127,58 @@ export class NeatGradient implements NeatController {
|
|
|
86
127
|
private _backgroundColor: string = "#FFFFFF";
|
|
87
128
|
private _backgroundAlpha: number = 1.0;
|
|
88
129
|
|
|
130
|
+
// Flow field properties
|
|
131
|
+
private _flowDistortionA: number = 0;
|
|
132
|
+
private _flowDistortionB: number = 0;
|
|
133
|
+
private _flowScale: number = 1.0;
|
|
134
|
+
private _flowEase: number = 0.0;
|
|
135
|
+
private _flowEnabled: boolean = true;
|
|
136
|
+
|
|
137
|
+
// Mouse interaction properties
|
|
138
|
+
private _mouseDistortionStrength: number = 0.0;
|
|
139
|
+
private _mouseDistortionRadius: number = 0.25;
|
|
140
|
+
private _mouseDecayRate: number = 0.96;
|
|
141
|
+
private _mouseDarken: number = 0.0;
|
|
142
|
+
private _mouse: THREE.Vector2 = new THREE.Vector2(-1000, -1000);
|
|
143
|
+
private _mouseFBO: THREE.WebGLRenderTarget | null = null;
|
|
144
|
+
private _sceneMouse: THREE.Scene | null = null;
|
|
145
|
+
private _cameraMouse: THREE.OrthographicCamera | null = null;
|
|
146
|
+
private _mouseObjects: Array<{ mesh: THREE.Mesh, active: boolean }> = [];
|
|
147
|
+
private _currentBrush: number = 0;
|
|
148
|
+
private _mouseBrushBaseScale: number = 1;
|
|
149
|
+
|
|
150
|
+
// Texture generation properties
|
|
151
|
+
private _enableProceduralTexture: boolean = false;
|
|
152
|
+
private _textureVoidLikelihood: number = 0.45;
|
|
153
|
+
private _textureVoidWidthMin: number = 200;
|
|
154
|
+
private _textureVoidWidthMax: number = 486;
|
|
155
|
+
private _textureBandDensity: number = 2.15;
|
|
156
|
+
private _textureColorBlending: number = 0.01;
|
|
157
|
+
private _textureSeed: number = 333;
|
|
158
|
+
private _textureEase: number = 0.5;
|
|
159
|
+
private _proceduralTexture: THREE.Texture | null = null;
|
|
160
|
+
private _proceduralBackgroundColor: string = "#000000";
|
|
161
|
+
|
|
162
|
+
private _textureShapeTriangles: number = 20;
|
|
163
|
+
private _textureShapeCircles: number = 15;
|
|
164
|
+
private _textureShapeBars: number = 15;
|
|
165
|
+
private _textureShapeSquiggles: number = 10;
|
|
166
|
+
|
|
89
167
|
private requestRef: number = -1;
|
|
90
168
|
private sizeObserver: ResizeObserver;
|
|
91
169
|
private sceneState: SceneState;
|
|
92
170
|
|
|
171
|
+
// Optimization: Cache uniforms to avoid lookups and object creation in render loop
|
|
172
|
+
private _cachedUniforms: NeatUniforms | null = null;
|
|
173
|
+
private _linkElement: HTMLAnchorElement | null = null;
|
|
174
|
+
|
|
93
175
|
private _yOffset: number = 0;
|
|
176
|
+
private _yOffsetWaveMultiplier: number = 0.004;
|
|
177
|
+
private _yOffsetColorMultiplier: number = 0.004;
|
|
178
|
+
private _yOffsetFlowMultiplier: number = 0.004;
|
|
179
|
+
|
|
180
|
+
// For saving/restoring clear color
|
|
181
|
+
private _tempClearColor = new THREE.Color();
|
|
94
182
|
|
|
95
183
|
constructor(config: NeatConfig & { ref: HTMLCanvasElement, resolution?: number, seed?: number }) {
|
|
96
184
|
|
|
@@ -117,7 +205,35 @@ export class NeatGradient implements NeatController {
|
|
|
117
205
|
backgroundAlpha = 1.0,
|
|
118
206
|
resolution = 1,
|
|
119
207
|
seed,
|
|
120
|
-
yOffset = 0
|
|
208
|
+
yOffset = 0,
|
|
209
|
+
yOffsetWaveMultiplier = 4,
|
|
210
|
+
yOffsetColorMultiplier = 4,
|
|
211
|
+
yOffsetFlowMultiplier = 4,
|
|
212
|
+
// Flow field parameters
|
|
213
|
+
flowDistortionA = 0,
|
|
214
|
+
flowDistortionB = 0,
|
|
215
|
+
flowScale = 1.0,
|
|
216
|
+
flowEase = 0.0,
|
|
217
|
+
flowEnabled = true,
|
|
218
|
+
// Mouse interaction
|
|
219
|
+
mouseDistortionStrength = 0.0,
|
|
220
|
+
mouseDistortionRadius = 0.25,
|
|
221
|
+
mouseDecayRate = 0.96,
|
|
222
|
+
mouseDarken = 0.0,
|
|
223
|
+
// Texture generation
|
|
224
|
+
enableProceduralTexture = false,
|
|
225
|
+
textureVoidLikelihood = 0.45,
|
|
226
|
+
textureVoidWidthMin = 200,
|
|
227
|
+
textureVoidWidthMax = 486,
|
|
228
|
+
textureBandDensity = 2.15,
|
|
229
|
+
textureColorBlending = 0.01,
|
|
230
|
+
textureSeed = 333,
|
|
231
|
+
textureEase = 0.5,
|
|
232
|
+
proceduralBackgroundColor = "#000000",
|
|
233
|
+
textureShapeTriangles = 20,
|
|
234
|
+
textureShapeCircles = 15,
|
|
235
|
+
textureShapeBars = 15,
|
|
236
|
+
textureShapeSquiggles = 10,
|
|
121
237
|
} = config;
|
|
122
238
|
|
|
123
239
|
|
|
@@ -147,84 +263,165 @@ export class NeatGradient implements NeatController {
|
|
|
147
263
|
this.backgroundColor = backgroundColor;
|
|
148
264
|
this.backgroundAlpha = backgroundAlpha;
|
|
149
265
|
this.yOffset = yOffset;
|
|
150
|
-
|
|
266
|
+
this.yOffsetWaveMultiplier = yOffsetWaveMultiplier;
|
|
267
|
+
this.yOffsetColorMultiplier = yOffsetColorMultiplier;
|
|
268
|
+
this.yOffsetFlowMultiplier = yOffsetFlowMultiplier;
|
|
269
|
+
|
|
270
|
+
// Flow field
|
|
271
|
+
this.flowDistortionA = flowDistortionA;
|
|
272
|
+
this.flowDistortionB = flowDistortionB;
|
|
273
|
+
this.flowScale = flowScale;
|
|
274
|
+
this.flowEase = flowEase;
|
|
275
|
+
this.flowEnabled = flowEnabled;
|
|
276
|
+
|
|
277
|
+
// Mouse interaction
|
|
278
|
+
this.mouseDistortionStrength = mouseDistortionStrength;
|
|
279
|
+
this.mouseDistortionRadius = mouseDistortionRadius;
|
|
280
|
+
this.mouseDecayRate = mouseDecayRate;
|
|
281
|
+
this.mouseDarken = mouseDarken;
|
|
282
|
+
|
|
283
|
+
// Texture generation
|
|
284
|
+
this.enableProceduralTexture = enableProceduralTexture;
|
|
285
|
+
this.textureVoidLikelihood = textureVoidLikelihood;
|
|
286
|
+
this.textureVoidWidthMin = textureVoidWidthMin;
|
|
287
|
+
this.textureVoidWidthMax = textureVoidWidthMax;
|
|
288
|
+
this.textureBandDensity = textureBandDensity;
|
|
289
|
+
this.textureColorBlending = textureColorBlending;
|
|
290
|
+
this.textureSeed = textureSeed;
|
|
291
|
+
this.textureEase = textureEase;
|
|
292
|
+
this._proceduralBackgroundColor = proceduralBackgroundColor;
|
|
293
|
+
|
|
294
|
+
this._textureShapeTriangles = textureShapeTriangles;
|
|
295
|
+
this._textureShapeCircles = textureShapeCircles;
|
|
296
|
+
this._textureShapeBars = textureShapeBars;
|
|
297
|
+
this._textureShapeSquiggles = textureShapeSquiggles;
|
|
298
|
+
|
|
299
|
+
// FIX 1: Setup mouse resources BEFORE building the material/scene
|
|
300
|
+
// This ensures u_mouse_texture isn't null during material compilation
|
|
301
|
+
this._setupMouseInteraction();
|
|
151
302
|
this.sceneState = this._initScene(resolution);
|
|
152
303
|
|
|
153
304
|
let tick = seed !== undefined ? seed : getElapsedSecondsInLastHour();
|
|
305
|
+
|
|
154
306
|
const render = () => {
|
|
155
307
|
|
|
156
|
-
const { renderer, camera, scene
|
|
308
|
+
const { renderer, camera, scene } = this.sceneState;
|
|
309
|
+
|
|
310
|
+
// Optimization: check if cached link is still valid in DOM, otherwise search
|
|
157
311
|
if (Math.floor(tick * 10) % 5 === 0) {
|
|
158
|
-
|
|
312
|
+
if (!this._linkElement || !document.contains(this._linkElement)) {
|
|
313
|
+
this._linkElement = addNeatLink(ref);
|
|
314
|
+
}
|
|
159
315
|
}
|
|
160
316
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const width = this._ref.width,
|
|
165
|
-
height = this._ref.height;
|
|
166
|
-
|
|
167
|
-
const colors = [
|
|
168
|
-
...this._colors.map(color => {
|
|
169
|
-
let threeColor = new THREE.Color();
|
|
170
|
-
threeColor.setStyle(color.color, "");
|
|
171
|
-
return ({
|
|
172
|
-
is_active: color.enabled,
|
|
173
|
-
color: threeColor,
|
|
174
|
-
influence: color.influence
|
|
175
|
-
});
|
|
176
|
-
}),
|
|
177
|
-
...Array.from({ length: COLORS_COUNT - this._colors.length }).map(() => ({
|
|
178
|
-
is_active: false,
|
|
179
|
-
color: new THREE.Color(0x000000)
|
|
180
|
-
}))
|
|
181
|
-
];
|
|
317
|
+
// Update Uniforms efficiently without creating new objects
|
|
318
|
+
if (this._cachedUniforms) {
|
|
319
|
+
const u = this._cachedUniforms;
|
|
182
320
|
|
|
183
321
|
tick += clock.getDelta() * this._speed;
|
|
184
|
-
// @ts-ignore
|
|
185
|
-
mesh.material.uniforms.u_time.value = tick;
|
|
186
|
-
// @ts-ignore
|
|
187
|
-
mesh.material.uniforms.u_resolution = { value: new THREE.Vector2(width, height) };
|
|
188
|
-
// @ts-ignore
|
|
189
|
-
mesh.material.uniforms.u_color_pressure = { value: new THREE.Vector2(this._horizontalPressure, this._verticalPressure) };
|
|
190
|
-
// @ts-ignore
|
|
191
|
-
mesh.material.uniforms.u_wave_frequency_x = { value: this._waveFrequencyX };
|
|
192
|
-
// @ts-ignore
|
|
193
|
-
mesh.material.uniforms.u_wave_frequency_y = { value: this._waveFrequencyY };
|
|
194
|
-
// @ts-ignore
|
|
195
|
-
mesh.material.uniforms.u_wave_amplitude = { value: this._waveAmplitude };
|
|
196
|
-
// @ts-ignore
|
|
197
|
-
mesh.material.uniforms.u_plane_width = { value: PLANE_WIDTH };
|
|
198
|
-
// @ts-ignore
|
|
199
|
-
mesh.material.uniforms.u_plane_height = { value: PLANE_HEIGHT };
|
|
200
|
-
// @ts-ignore
|
|
201
|
-
mesh.material.uniforms.u_color_blending = { value: this._colorBlending };
|
|
202
|
-
// @ts-ignore
|
|
203
|
-
mesh.material.uniforms.u_colors = { value: colors };
|
|
204
|
-
// @ts-ignore
|
|
205
|
-
mesh.material.uniforms.u_colors_count = { value: COLORS_COUNT };
|
|
206
|
-
// @ts-ignore
|
|
207
|
-
mesh.material.uniforms.u_shadows = { value: this._shadows };
|
|
208
|
-
// @ts-ignore
|
|
209
|
-
mesh.material.uniforms.u_highlights = { value: this._highlights };
|
|
210
|
-
// @ts-ignore
|
|
211
|
-
mesh.material.uniforms.u_saturation = { value: this._saturation };
|
|
212
|
-
// @ts-ignore
|
|
213
|
-
mesh.material.uniforms.u_brightness = { value: this._brightness };
|
|
214
|
-
// @ts-ignore
|
|
215
|
-
mesh.material.uniforms.u_grain_intensity = { value: this._grainIntensity };
|
|
216
|
-
// @ts-ignore
|
|
217
|
-
mesh.material.uniforms.u_grain_sparsity = { value: this._grainSparsity };
|
|
218
|
-
// @ts-ignore
|
|
219
|
-
mesh.material.uniforms.u_grain_speed = { value: this._grainSpeed };
|
|
220
|
-
// @ts-ignore
|
|
221
|
-
mesh.material.uniforms.u_grain_scale = { value: this._grainScale };
|
|
222
|
-
// @ts-ignore
|
|
223
|
-
mesh.material.uniforms.u_y_offset = { value: this._yOffset };
|
|
224
|
-
// @ts-ignore
|
|
225
|
-
mesh.material.wireframe = this._wireframe;
|
|
226
|
-
});
|
|
227
322
|
|
|
323
|
+
u.u_time.value = tick;
|
|
324
|
+
u.u_resolution.value.set(this._ref.width, this._ref.height);
|
|
325
|
+
u.u_color_pressure.value.set(this._horizontalPressure, this._verticalPressure);
|
|
326
|
+
|
|
327
|
+
// Directly assign simple values
|
|
328
|
+
u.u_wave_frequency_x.value = this._waveFrequencyX;
|
|
329
|
+
u.u_wave_frequency_y.value = this._waveFrequencyY;
|
|
330
|
+
u.u_wave_amplitude.value = this._waveAmplitude;
|
|
331
|
+
u.u_color_blending.value = this._colorBlending;
|
|
332
|
+
u.u_shadows.value = this._shadows;
|
|
333
|
+
u.u_highlights.value = this._highlights;
|
|
334
|
+
u.u_saturation.value = this._saturation;
|
|
335
|
+
u.u_brightness.value = this._brightness;
|
|
336
|
+
u.u_grain_intensity.value = this._grainIntensity;
|
|
337
|
+
u.u_grain_sparsity.value = this._grainSparsity;
|
|
338
|
+
u.u_grain_speed.value = this._grainSpeed;
|
|
339
|
+
u.u_grain_scale.value = this._grainScale;
|
|
340
|
+
u.u_y_offset.value = this._yOffset;
|
|
341
|
+
u.u_y_offset_wave_multiplier.value = this._yOffsetWaveMultiplier;
|
|
342
|
+
u.u_y_offset_color_multiplier.value = this._yOffsetColorMultiplier;
|
|
343
|
+
u.u_y_offset_flow_multiplier.value = this._yOffsetFlowMultiplier;
|
|
344
|
+
u.u_flow_distortion_a.value = this._flowDistortionA;
|
|
345
|
+
u.u_flow_distortion_b.value = this._flowDistortionB;
|
|
346
|
+
u.u_flow_scale.value = this._flowScale;
|
|
347
|
+
u.u_flow_ease.value = this._flowEase;
|
|
348
|
+
u.u_flow_enabled.value = this._flowEnabled ? 1.0 : 0.0;
|
|
349
|
+
u.u_mouse_distortion_strength.value = this._mouseDistortionStrength;
|
|
350
|
+
u.u_mouse_distortion_radius.value = this._mouseDistortionRadius;
|
|
351
|
+
u.u_mouse_darken.value = this._mouseDarken;
|
|
352
|
+
u.u_enable_procedural_texture.value = this._enableProceduralTexture ? 1.0 : 0.0;
|
|
353
|
+
u.u_procedural_texture.value = this._proceduralTexture;
|
|
354
|
+
u.u_texture_ease.value = this._textureEase;
|
|
355
|
+
|
|
356
|
+
// Optimized Color Update: Update the existing array objects instead of recreating array
|
|
357
|
+
const shaderColors = u.u_colors.value;
|
|
358
|
+
for(let i = 0; i < COLORS_COUNT; i++) {
|
|
359
|
+
if (i < this._colors.length) {
|
|
360
|
+
const c = this._colors[i];
|
|
361
|
+
shaderColors[i].is_active = c.enabled ? 1.0 : 0.0;
|
|
362
|
+
shaderColors[i].color.setStyle(c.color, "");
|
|
363
|
+
shaderColors[i].influence = c.influence || 0;
|
|
364
|
+
} else {
|
|
365
|
+
shaderColors[i].is_active = 0.0;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
u.u_colors_count.value = COLORS_COUNT;
|
|
370
|
+
// Wireframe is a material property, not a uniform
|
|
371
|
+
// @ts-ignore - access material safely
|
|
372
|
+
this.sceneState.meshes[0].material.wireframe = this._wireframe;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Render mouse interaction to FBO
|
|
376
|
+
if (this._mouseFBO && this._sceneMouse && this._cameraMouse) {
|
|
377
|
+
let hasActiveBrushes = false;
|
|
378
|
+
|
|
379
|
+
// Update mouse objects - decay rate controls how fast trails fade
|
|
380
|
+
for(let i = 0; i < this._mouseObjects.length; i++) {
|
|
381
|
+
const obj = this._mouseObjects[i];
|
|
382
|
+
if (obj.mesh.visible) {
|
|
383
|
+
hasActiveBrushes = true;
|
|
384
|
+
obj.mesh.rotation.z += 0.01;
|
|
385
|
+
if (obj.mesh.material instanceof THREE.MeshBasicMaterial) {
|
|
386
|
+
// Decay only affects opacity
|
|
387
|
+
obj.mesh.material.opacity *= this._mouseDecayRate;
|
|
388
|
+
|
|
389
|
+
if (obj.mesh.material.opacity < 0.01) {
|
|
390
|
+
obj.mesh.visible = false;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// FIX 2: Handle FBO Clearing correctly
|
|
397
|
+
// Store current clear color (likely the main background color)
|
|
398
|
+
renderer.getClearColor(this._tempClearColor);
|
|
399
|
+
const oldClearAlpha = renderer.getClearAlpha();
|
|
400
|
+
|
|
401
|
+
// Set clear color to Black/Transparent for the FBO.
|
|
402
|
+
// Important: If we use the main background color (e.g. White), the FBO
|
|
403
|
+
// will be white, causing 100% distortion everywhere.
|
|
404
|
+
renderer.setClearColor(0x000000, 0.0);
|
|
405
|
+
|
|
406
|
+
renderer.setRenderTarget(this._mouseFBO);
|
|
407
|
+
renderer.clear();
|
|
408
|
+
|
|
409
|
+
if (hasActiveBrushes) {
|
|
410
|
+
renderer.render(this._sceneMouse, this._cameraMouse);
|
|
411
|
+
}
|
|
412
|
+
renderer.setRenderTarget(null);
|
|
413
|
+
|
|
414
|
+
// Restore main background color for the actual scene render
|
|
415
|
+
renderer.setClearColor(this._tempClearColor, oldClearAlpha);
|
|
416
|
+
|
|
417
|
+
// Update mouse texture uniform
|
|
418
|
+
if (this._cachedUniforms) {
|
|
419
|
+
this._cachedUniforms.u_mouse_texture.value = this._mouseFBO.texture;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Ensure we set the clear color for the main scene explicitly before rendering
|
|
424
|
+
renderer.setClearColor(this._backgroundColor, this._backgroundAlpha);
|
|
228
425
|
renderer.render(scene, camera);
|
|
229
426
|
this.requestRef = requestAnimationFrame(render);
|
|
230
427
|
};
|
|
@@ -238,6 +435,19 @@ export class NeatGradient implements NeatController {
|
|
|
238
435
|
|
|
239
436
|
this.sceneState.renderer.setSize(width, height, false);
|
|
240
437
|
updateCamera(this.sceneState.camera, width, height);
|
|
438
|
+
|
|
439
|
+
// FIX 3: Update Mouse FBO and Camera on resize
|
|
440
|
+
// If we don't do this, mouse coordinates map incorrectly after a resize
|
|
441
|
+
if (this._mouseFBO && this._cameraMouse) {
|
|
442
|
+
const fSize = height / 2;
|
|
443
|
+
const aspect = width / height;
|
|
444
|
+
this._mouseFBO.setSize(width / 2, height / 2);
|
|
445
|
+
this._cameraMouse.left = -fSize * aspect;
|
|
446
|
+
this._cameraMouse.right = fSize * aspect;
|
|
447
|
+
this._cameraMouse.top = fSize;
|
|
448
|
+
this._cameraMouse.bottom = -fSize;
|
|
449
|
+
this._cameraMouse.updateProjectionMatrix();
|
|
450
|
+
}
|
|
241
451
|
};
|
|
242
452
|
|
|
243
453
|
this.sizeObserver = new ResizeObserver(entries => {
|
|
@@ -254,6 +464,18 @@ export class NeatGradient implements NeatController {
|
|
|
254
464
|
if (this) {
|
|
255
465
|
cancelAnimationFrame(this.requestRef);
|
|
256
466
|
this.sizeObserver.disconnect();
|
|
467
|
+
|
|
468
|
+
// Cleanup WebGL resources
|
|
469
|
+
if (this.sceneState) {
|
|
470
|
+
this.sceneState.renderer.dispose();
|
|
471
|
+
this.sceneState.meshes.forEach(m => {
|
|
472
|
+
m.geometry.dispose();
|
|
473
|
+
if(Array.isArray(m.material)) m.material.forEach(mat => mat.dispose());
|
|
474
|
+
else m.material.dispose();
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
if (this._mouseFBO) this._mouseFBO.dispose();
|
|
478
|
+
if (this._proceduralTexture) this._proceduralTexture.dispose();
|
|
257
479
|
}
|
|
258
480
|
}
|
|
259
481
|
|
|
@@ -348,11 +570,178 @@ export class NeatGradient implements NeatController {
|
|
|
348
570
|
this._yOffset = yOffset;
|
|
349
571
|
}
|
|
350
572
|
|
|
573
|
+
get yOffsetWaveMultiplier(): number {
|
|
574
|
+
return this._yOffsetWaveMultiplier * 1000;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
set yOffsetWaveMultiplier(value: number) {
|
|
578
|
+
this._yOffsetWaveMultiplier = value / 1000;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
get yOffsetColorMultiplier(): number {
|
|
582
|
+
return this._yOffsetColorMultiplier * 1000;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
set yOffsetColorMultiplier(value: number) {
|
|
586
|
+
this._yOffsetColorMultiplier = value / 1000;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
get yOffsetFlowMultiplier(): number {
|
|
590
|
+
return this._yOffsetFlowMultiplier * 1000;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
set yOffsetFlowMultiplier(value: number) {
|
|
594
|
+
this._yOffsetFlowMultiplier = value / 1000;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
set flowDistortionA(value: number) {
|
|
598
|
+
this._flowDistortionA = value;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
set flowDistortionB(value: number) {
|
|
602
|
+
this._flowDistortionB = value;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
set flowScale(value: number) {
|
|
606
|
+
this._flowScale = value;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
set flowEase(value: number) {
|
|
610
|
+
this._flowEase = value;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
set flowEnabled(value: boolean) {
|
|
614
|
+
this._flowEnabled = value;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
get flowEnabled(): boolean {
|
|
618
|
+
return this._flowEnabled;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
set mouseDistortionStrength(value: number) {
|
|
623
|
+
this._mouseDistortionStrength = Math.max(0, value);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
set mouseDistortionRadius(value: number) {
|
|
627
|
+
// Clamp to a sane range in UV space
|
|
628
|
+
this._mouseDistortionRadius = Math.max(0.01, Math.min(value, 1.0));
|
|
629
|
+
// Update brush scale when radius changes
|
|
630
|
+
this._updateBrushScale();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
_updateBrushScale() {
|
|
634
|
+
if (!this._mouseObjects || this._mouseObjects.length === 0) return;
|
|
635
|
+
// Radius directly controls the brush scale
|
|
636
|
+
// Base geometry is 200px, so radius 0.25 = 50px, radius 1.0 = 200px
|
|
637
|
+
this._mouseBrushBaseScale = this._mouseDistortionRadius;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
set mouseDecayRate(value: number) {
|
|
641
|
+
// Clamp between 0.9 (slow decay, more wobble) and 0.99 (fast decay, less wobble)
|
|
642
|
+
this._mouseDecayRate = Math.max(0.9, Math.min(value, 0.99));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
set mouseDarken(value: number) {
|
|
646
|
+
this._mouseDarken = value;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
set enableProceduralTexture(value: boolean) {
|
|
650
|
+
this._enableProceduralTexture = value;
|
|
651
|
+
if (value && !this._proceduralTexture) {
|
|
652
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
set textureVoidLikelihood(value: number) {
|
|
657
|
+
this._textureVoidLikelihood = value;
|
|
658
|
+
if (this._enableProceduralTexture) {
|
|
659
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
set textureVoidWidthMin(value: number) {
|
|
664
|
+
this._textureVoidWidthMin = value;
|
|
665
|
+
if (this._enableProceduralTexture) {
|
|
666
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
set textureVoidWidthMax(value: number) {
|
|
671
|
+
this._textureVoidWidthMax = value;
|
|
672
|
+
if (this._enableProceduralTexture) {
|
|
673
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
set textureBandDensity(value: number) {
|
|
678
|
+
this._textureBandDensity = value;
|
|
679
|
+
if (this._enableProceduralTexture) {
|
|
680
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
set textureColorBlending(value: number) {
|
|
685
|
+
this._textureColorBlending = value;
|
|
686
|
+
if (this._enableProceduralTexture) {
|
|
687
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
set textureSeed(value: number) {
|
|
692
|
+
this._textureSeed = value;
|
|
693
|
+
if (this._enableProceduralTexture) {
|
|
694
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
get textureEase(): number {
|
|
699
|
+
return this._textureEase;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
set textureEase(value: number) {
|
|
703
|
+
this._textureEase = value;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
set proceduralBackgroundColor(value: string) {
|
|
707
|
+
this._proceduralBackgroundColor = value;
|
|
708
|
+
if (this._enableProceduralTexture) {
|
|
709
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
set textureShapeTriangles(value: number) {
|
|
714
|
+
this._textureShapeTriangles = value;
|
|
715
|
+
if (this._enableProceduralTexture) this._proceduralTexture = this._createProceduralTexture();
|
|
716
|
+
}
|
|
717
|
+
set textureShapeCircles(value: number) {
|
|
718
|
+
this._textureShapeCircles = value;
|
|
719
|
+
if (this._enableProceduralTexture) this._proceduralTexture = this._createProceduralTexture();
|
|
720
|
+
}
|
|
721
|
+
set textureShapeBars(value: number) {
|
|
722
|
+
this._textureShapeBars = value;
|
|
723
|
+
if (this._enableProceduralTexture) this._proceduralTexture = this._createProceduralTexture();
|
|
724
|
+
}
|
|
725
|
+
set textureShapeSquiggles(value: number) {
|
|
726
|
+
this._textureShapeSquiggles = value;
|
|
727
|
+
if (this._enableProceduralTexture) this._proceduralTexture = this._createProceduralTexture();
|
|
728
|
+
}
|
|
729
|
+
|
|
351
730
|
_initScene(resolution: number): SceneState {
|
|
352
731
|
|
|
353
732
|
const width = this._ref.width,
|
|
354
733
|
height = this._ref.height;
|
|
355
734
|
|
|
735
|
+
// Cleanup existing renderer if needed
|
|
736
|
+
if (this.sceneState && this.sceneState.renderer) {
|
|
737
|
+
this.sceneState.renderer.dispose();
|
|
738
|
+
this.sceneState.meshes.forEach(m => {
|
|
739
|
+
m.geometry.dispose();
|
|
740
|
+
if(Array.isArray(m.material)) m.material.forEach(mat => mat.dispose());
|
|
741
|
+
else m.material.dispose();
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
356
745
|
const renderer = new THREE.WebGLRenderer({
|
|
357
746
|
// antialias: true,
|
|
358
747
|
alpha: true,
|
|
@@ -392,17 +781,13 @@ export class NeatGradient implements NeatController {
|
|
|
392
781
|
|
|
393
782
|
_buildMaterial(width: number, height: number) {
|
|
394
783
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
is_active: false,
|
|
403
|
-
color: new THREE.Color(0x000000)
|
|
404
|
-
}))
|
|
405
|
-
];
|
|
784
|
+
// Initialize stable array structure for colors
|
|
785
|
+
// We create 6 objects and just update them in the render loop to avoid GC
|
|
786
|
+
const colors = Array.from({ length: COLORS_COUNT }).map((_, i) => ({
|
|
787
|
+
is_active: i < this._colors.length ? (this._colors[i].enabled ? 1.0 : 0.0) : 0.0,
|
|
788
|
+
color: new THREE.Color(i < this._colors.length ? this._colors[i].color : 0x000000),
|
|
789
|
+
influence: i < this._colors.length ? (this._colors[i].influence || 0) : 0
|
|
790
|
+
}));
|
|
406
791
|
|
|
407
792
|
const uniforms = {
|
|
408
793
|
u_time: { value: 0 },
|
|
@@ -421,6 +806,29 @@ export class NeatGradient implements NeatController {
|
|
|
421
806
|
u_grain_sparsity: { value: this._grainSparsity },
|
|
422
807
|
u_grain_scale: { value: this._grainScale },
|
|
423
808
|
u_grain_speed: { value: this._grainSpeed },
|
|
809
|
+
// Flow field
|
|
810
|
+
u_flow_distortion_a: { value: this._flowDistortionA },
|
|
811
|
+
u_flow_distortion_b: { value: this._flowDistortionB },
|
|
812
|
+
u_flow_scale: { value: this._flowScale },
|
|
813
|
+
u_flow_ease: { value: this._flowEase },
|
|
814
|
+
u_flow_enabled: { value: this._flowEnabled ? 1.0 : 0.0 },
|
|
815
|
+
// Y offset multipliers
|
|
816
|
+
u_y_offset: { value: this._yOffset },
|
|
817
|
+
u_y_offset_wave_multiplier: { value: this._yOffsetWaveMultiplier },
|
|
818
|
+
u_y_offset_color_multiplier: { value: this._yOffsetColorMultiplier },
|
|
819
|
+
u_y_offset_flow_multiplier: { value: this._yOffsetFlowMultiplier },
|
|
820
|
+
// Mouse interaction
|
|
821
|
+
u_mouse_distortion_strength: { value: this._mouseDistortionStrength },
|
|
822
|
+
u_mouse_distortion_radius: { value: this._mouseDistortionRadius },
|
|
823
|
+
u_mouse_darken: { value: this._mouseDarken },
|
|
824
|
+
u_mouse_texture: { value: this._mouseFBO ? this._mouseFBO.texture : null },
|
|
825
|
+
// Procedural texture
|
|
826
|
+
u_procedural_texture: { value: this._proceduralTexture },
|
|
827
|
+
u_enable_procedural_texture: { value: this._enableProceduralTexture ? 1.0 : 0.0 },
|
|
828
|
+
u_texture_ease: { value: this._textureEase },
|
|
829
|
+
u_saturation: { value: this._saturation },
|
|
830
|
+
u_brightness: { value: this._brightness },
|
|
831
|
+
u_color_blending: { value: this._colorBlending }
|
|
424
832
|
};
|
|
425
833
|
|
|
426
834
|
const material = new THREE.ShaderMaterial({
|
|
@@ -429,10 +837,287 @@ export class NeatGradient implements NeatController {
|
|
|
429
837
|
fragmentShader: buildUniforms() + buildColorFunctions() + buildNoise() + buildFragmentShader()
|
|
430
838
|
});
|
|
431
839
|
|
|
840
|
+
// Cache the uniforms object for direct access in render loop
|
|
841
|
+
this._cachedUniforms = uniforms as unknown as NeatUniforms;
|
|
842
|
+
|
|
432
843
|
material.wireframe = WIREFRAME;
|
|
433
844
|
return material;
|
|
434
845
|
}
|
|
435
846
|
|
|
847
|
+
_setupMouseInteraction() {
|
|
848
|
+
if (!this._ref) return;
|
|
849
|
+
|
|
850
|
+
const width = this._ref.width;
|
|
851
|
+
const height = this._ref.height;
|
|
852
|
+
|
|
853
|
+
// Create mouse FBO
|
|
854
|
+
this._mouseFBO = new THREE.WebGLRenderTarget(width / 2, height / 2);
|
|
855
|
+
|
|
856
|
+
// Create mouse scene and camera
|
|
857
|
+
this._sceneMouse = new THREE.Scene();
|
|
858
|
+
const fSize = height / 2;
|
|
859
|
+
const aspect = width / height;
|
|
860
|
+
|
|
861
|
+
// FIX 4: Ensure near plane allows viewing objects at Z=0
|
|
862
|
+
// Near -100 is safer for objects at 0
|
|
863
|
+
this._cameraMouse = new THREE.OrthographicCamera(
|
|
864
|
+
-fSize * aspect, fSize * aspect,
|
|
865
|
+
fSize, -fSize,
|
|
866
|
+
0, 10000
|
|
867
|
+
);
|
|
868
|
+
this._cameraMouse.position.set(0, 0, 100);
|
|
869
|
+
|
|
870
|
+
// Create brush texture - More visible and impactful
|
|
871
|
+
const brushCanvas = document.createElement('canvas');
|
|
872
|
+
brushCanvas.width = 128;
|
|
873
|
+
brushCanvas.height = 128;
|
|
874
|
+
const bCtx = brushCanvas.getContext('2d');
|
|
875
|
+
if (bCtx) {
|
|
876
|
+
const grd = bCtx.createRadialGradient(64, 64, 0, 64, 64, 64);
|
|
877
|
+
// Match reference implementation's stronger gradient
|
|
878
|
+
grd.addColorStop(0, 'rgba(255,255,255,0.8)');
|
|
879
|
+
grd.addColorStop(0.5, 'rgba(255,255,255,0.4)');
|
|
880
|
+
grd.addColorStop(1, 'rgba(255,255,255,0)');
|
|
881
|
+
bCtx.fillStyle = grd;
|
|
882
|
+
bCtx.fillRect(0, 0, 128, 128);
|
|
883
|
+
}
|
|
884
|
+
const brushTex = new THREE.CanvasTexture(brushCanvas);
|
|
885
|
+
const brushMat = new THREE.MeshBasicMaterial({
|
|
886
|
+
map: brushTex,
|
|
887
|
+
transparent: true,
|
|
888
|
+
opacity: 1.0,
|
|
889
|
+
depthTest: false,
|
|
890
|
+
blending: THREE.AdditiveBlending // Additive blending for better accumulation
|
|
891
|
+
});
|
|
892
|
+
// Brush geometry size - will be scaled by radius parameter
|
|
893
|
+
const brushGeo = new THREE.PlaneGeometry(200, 200);
|
|
894
|
+
|
|
895
|
+
// Create brush pool
|
|
896
|
+
const brushPoolSize = 50;
|
|
897
|
+
for (let i = 0; i < brushPoolSize; i++) {
|
|
898
|
+
const m = new THREE.Mesh(brushGeo, brushMat.clone());
|
|
899
|
+
m.visible = false;
|
|
900
|
+
this._sceneMouse!.add(m);
|
|
901
|
+
this._mouseObjects.push({ mesh: m, active: false });
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Initialize brush scale based on current radius
|
|
905
|
+
this._updateBrushScale();
|
|
906
|
+
|
|
907
|
+
// Add mouse move listener
|
|
908
|
+
this._ref.addEventListener('mousemove', this._onMouseMove.bind(this));
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
_onMouseMove(e: MouseEvent) {
|
|
912
|
+
if (!this._ref || !this._sceneMouse) return;
|
|
913
|
+
const rect = this._ref.getBoundingClientRect();
|
|
914
|
+
const width = this._ref.width;
|
|
915
|
+
const height = this._ref.height;
|
|
916
|
+
|
|
917
|
+
this._mouse.x = e.clientX - rect.left - width / 2;
|
|
918
|
+
this._mouse.y = -(e.clientY - rect.top - height / 2);
|
|
919
|
+
|
|
920
|
+
const brush = this._mouseObjects[this._currentBrush];
|
|
921
|
+
brush.mesh.scale.set(this._mouseBrushBaseScale, this._mouseBrushBaseScale, 1.0);
|
|
922
|
+
brush.active = true;
|
|
923
|
+
brush.mesh.visible = true;
|
|
924
|
+
brush.mesh.position.set(this._mouse.x, this._mouse.y, 0);
|
|
925
|
+
brush.mesh.rotation.z = Math.random() * Math.PI * 2;
|
|
926
|
+
if (brush.mesh.material instanceof THREE.MeshBasicMaterial) {
|
|
927
|
+
brush.mesh.material.opacity = 1.0;
|
|
928
|
+
}
|
|
929
|
+
this._currentBrush = (this._currentBrush + 1) % this._mouseObjects.length;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
_createProceduralTexture(): THREE.Texture {
|
|
933
|
+
// Texture size - 1024 provides good balance between quality and performance
|
|
934
|
+
// Can be increased to 2048 for even better quality if needed
|
|
935
|
+
const texSize = 1024;
|
|
936
|
+
const sourceCanvas = document.createElement('canvas');
|
|
937
|
+
sourceCanvas.width = texSize;
|
|
938
|
+
sourceCanvas.height = texSize;
|
|
939
|
+
const sCtx = sourceCanvas.getContext('2d');
|
|
940
|
+
if (!sCtx) return new THREE.Texture();
|
|
941
|
+
|
|
942
|
+
let seed = this._textureSeed;
|
|
943
|
+
const baseSeed = this._textureSeed;
|
|
944
|
+
|
|
945
|
+
function random() {
|
|
946
|
+
const x = Math.sin(seed++) * 10000;
|
|
947
|
+
return x - Math.floor(x);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Helper to reset seed for isolated shape generation
|
|
951
|
+
const setSeed = (offset: number) => {
|
|
952
|
+
seed = baseSeed + offset;
|
|
953
|
+
};
|
|
954
|
+
|
|
955
|
+
const colors = this._colors.filter(c => c.enabled).map(c => c.color);
|
|
956
|
+
if (colors.length === 0) return new THREE.Texture();
|
|
957
|
+
|
|
958
|
+
// Helper functions
|
|
959
|
+
function hexToRgb(hex: string) {
|
|
960
|
+
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
961
|
+
return {
|
|
962
|
+
r: (bigint >> 16) & 255,
|
|
963
|
+
g: (bigint >> 8) & 255,
|
|
964
|
+
b: bigint & 255
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function rgbToHex(r: number, g: number, b: number) {
|
|
969
|
+
return "#" + ((1 << 24) + (Math.round(r) << 16) + (Math.round(g) << 8) + Math.round(b)).toString(16).slice(1);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const getInterColor = () => {
|
|
973
|
+
const c1 = colors[Math.floor(random() * colors.length)];
|
|
974
|
+
const c2 = colors[Math.floor(random() * colors.length)];
|
|
975
|
+
const mix = random() * this._textureColorBlending;
|
|
976
|
+
const rgb1 = hexToRgb(c1);
|
|
977
|
+
const rgb2 = hexToRgb(c2);
|
|
978
|
+
const r = rgb1.r + (rgb2.r - rgb1.r) * mix;
|
|
979
|
+
const g = rgb1.g + (rgb2.g - rgb1.g) * mix;
|
|
980
|
+
const b = rgb1.b + (rgb2.b - rgb1.b) * mix;
|
|
981
|
+
return rgbToHex(r, g, b);
|
|
982
|
+
};
|
|
983
|
+
|
|
984
|
+
// === SOURCE CANVAS ===
|
|
985
|
+
// Base with procedural background color so even sparse areas pick it up
|
|
986
|
+
const baseColor = this._proceduralBackgroundColor || "#000000";
|
|
987
|
+
sCtx.fillStyle = baseColor;
|
|
988
|
+
sCtx.fillRect(0, 0, texSize, texSize);
|
|
989
|
+
|
|
990
|
+
// Then lay a vertical gradient of mixed colors on top for richness
|
|
991
|
+
const bgGrad = sCtx.createLinearGradient(0, 0, 0, texSize);
|
|
992
|
+
bgGrad.addColorStop(0, getInterColor());
|
|
993
|
+
bgGrad.addColorStop(1, getInterColor());
|
|
994
|
+
sCtx.fillStyle = bgGrad;
|
|
995
|
+
sCtx.fillRect(0, 0, texSize, texSize);
|
|
996
|
+
|
|
997
|
+
// Triangles: use configurable count
|
|
998
|
+
for (let i = 0; i < this._textureShapeTriangles; i++) {
|
|
999
|
+
sCtx.fillStyle = getInterColor();
|
|
1000
|
+
sCtx.beginPath();
|
|
1001
|
+
const x = random() * texSize;
|
|
1002
|
+
const y = random() * texSize;
|
|
1003
|
+
const s = 100 + random() * 300;
|
|
1004
|
+
sCtx.moveTo(x, y);
|
|
1005
|
+
sCtx.lineTo(x + (random() - 0.5) * s, y + (random() - 0.5) * s);
|
|
1006
|
+
sCtx.lineTo(x + (random() - 0.5) * s, y + (random() - 0.5) * s);
|
|
1007
|
+
sCtx.fill();
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Circles / rings: use configurable count
|
|
1011
|
+
for (let i = 0; i < this._textureShapeCircles; i++) {
|
|
1012
|
+
sCtx.strokeStyle = getInterColor();
|
|
1013
|
+
sCtx.lineWidth = 10 + random() * 50;
|
|
1014
|
+
sCtx.beginPath();
|
|
1015
|
+
const x = random() * texSize;
|
|
1016
|
+
const y = random() * texSize;
|
|
1017
|
+
const r = 50 + random() * 150;
|
|
1018
|
+
sCtx.arc(x, y, r, 0, Math.PI * 2);
|
|
1019
|
+
sCtx.stroke();
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Bars: use configurable count
|
|
1023
|
+
for (let i = 0; i < this._textureShapeBars; i++) {
|
|
1024
|
+
sCtx.fillStyle = getInterColor();
|
|
1025
|
+
sCtx.save();
|
|
1026
|
+
sCtx.translate(random() * texSize, random() * texSize);
|
|
1027
|
+
sCtx.rotate(random() * Math.PI);
|
|
1028
|
+
sCtx.fillRect(-150, -25, 300, 50);
|
|
1029
|
+
sCtx.restore();
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Squiggles: use configurable count
|
|
1033
|
+
sCtx.lineWidth = 15;
|
|
1034
|
+
sCtx.lineCap = 'round';
|
|
1035
|
+
for (let i = 0; i < this._textureShapeSquiggles; i++) {
|
|
1036
|
+
sCtx.strokeStyle = getInterColor();
|
|
1037
|
+
sCtx.beginPath();
|
|
1038
|
+
let x = random() * texSize;
|
|
1039
|
+
let y = random() * texSize;
|
|
1040
|
+
sCtx.moveTo(x, y);
|
|
1041
|
+
for (let j = 0; j < 4; j++) {
|
|
1042
|
+
sCtx.bezierCurveTo(
|
|
1043
|
+
x + (random() - 0.5) * 300, y + (random() - 0.5) * 300,
|
|
1044
|
+
x + (random() - 0.5) * 300, y + (random() - 0.5) * 300,
|
|
1045
|
+
x + (random() - 0.5) * 300, y + (random() - 0.5) * 300
|
|
1046
|
+
);
|
|
1047
|
+
x += (random() - 0.5) * 300;
|
|
1048
|
+
y += (random() - 0.5) * 300;
|
|
1049
|
+
}
|
|
1050
|
+
sCtx.stroke();
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// === MASKED CANVAS ===
|
|
1054
|
+
// Masking: Seed isolation
|
|
1055
|
+
setSeed(50000);
|
|
1056
|
+
const canvas = document.createElement('canvas');
|
|
1057
|
+
canvas.width = texSize;
|
|
1058
|
+
canvas.height = texSize;
|
|
1059
|
+
const ctx = canvas.getContext('2d');
|
|
1060
|
+
if (!ctx) return new THREE.Texture();
|
|
1061
|
+
|
|
1062
|
+
// Start filled with the chosen void color so gaps show that color
|
|
1063
|
+
ctx.fillStyle = baseColor;
|
|
1064
|
+
ctx.fillRect(0, 0, texSize, texSize);
|
|
1065
|
+
|
|
1066
|
+
// Determine layout segments (matter vs void)
|
|
1067
|
+
let layoutHead = 0;
|
|
1068
|
+
const segments: Array<{ type: 'void' | 'matter', x: number, width: number }> = [];
|
|
1069
|
+
|
|
1070
|
+
while (layoutHead < texSize) {
|
|
1071
|
+
const isVoid = random() < this._textureVoidLikelihood;
|
|
1072
|
+
if (isVoid) {
|
|
1073
|
+
const w = this._textureVoidWidthMin + random() * (this._textureVoidWidthMax - this._textureVoidWidthMin);
|
|
1074
|
+
segments.push({ type: 'void', x: layoutHead, width: w });
|
|
1075
|
+
layoutHead += w;
|
|
1076
|
+
} else {
|
|
1077
|
+
const w = 50 + random() * 200;
|
|
1078
|
+
segments.push({ type: 'matter', x: layoutHead, width: w });
|
|
1079
|
+
layoutHead += w;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Render only matter bands from the source into the masked canvas
|
|
1084
|
+
for (const seg of segments) {
|
|
1085
|
+
if (seg.type === 'matter') {
|
|
1086
|
+
const startX = seg.x;
|
|
1087
|
+
const endX = Math.min(seg.x + seg.width, texSize);
|
|
1088
|
+
let currentX = startX;
|
|
1089
|
+
|
|
1090
|
+
while (currentX < endX) {
|
|
1091
|
+
const stripeWidth = (2 + random() * 20) / this._textureBandDensity;
|
|
1092
|
+
const sourceX = Math.floor(random() * texSize);
|
|
1093
|
+
ctx.drawImage(
|
|
1094
|
+
sourceCanvas,
|
|
1095
|
+
sourceX, 0, stripeWidth, texSize,
|
|
1096
|
+
currentX, 0, stripeWidth, texSize
|
|
1097
|
+
);
|
|
1098
|
+
currentX += stripeWidth;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
// void segments: leave as baseColor
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const tex = new THREE.CanvasTexture(canvas);
|
|
1105
|
+
// Use mipmapping for better quality when texture is scaled
|
|
1106
|
+
tex.minFilter = THREE.LinearMipmapLinearFilter;
|
|
1107
|
+
tex.magFilter = THREE.LinearFilter;
|
|
1108
|
+
tex.wrapS = THREE.RepeatWrapping;
|
|
1109
|
+
tex.wrapT = THREE.RepeatWrapping;
|
|
1110
|
+
|
|
1111
|
+
// Enable anisotropic filtering for much better quality when texture is stretched
|
|
1112
|
+
// 16 is a commonly supported value that dramatically improves quality
|
|
1113
|
+
tex.anisotropy = 16;
|
|
1114
|
+
|
|
1115
|
+
// Ensure mipmaps are generated
|
|
1116
|
+
tex.needsUpdate = true;
|
|
1117
|
+
|
|
1118
|
+
return tex;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
436
1121
|
|
|
437
1122
|
}
|
|
438
1123
|
|
|
@@ -475,41 +1160,62 @@ function updateCamera(camera: THREE.Camera, width: number, height: number) {
|
|
|
475
1160
|
|
|
476
1161
|
function buildVertexShader() {
|
|
477
1162
|
return `
|
|
478
|
-
|
|
479
1163
|
void main() {
|
|
480
|
-
|
|
481
1164
|
vUv = uv;
|
|
482
1165
|
|
|
1166
|
+
// SCROLLING LOGIC
|
|
1167
|
+
// Separate multipliers for wave, color, and flow offsets
|
|
1168
|
+
float waveOffset = -u_y_offset * u_y_offset_wave_multiplier;
|
|
1169
|
+
float colorOffset = -u_y_offset * u_y_offset_color_multiplier;
|
|
1170
|
+
float flowOffset = -u_y_offset * u_y_offset_flow_multiplier;
|
|
1171
|
+
|
|
1172
|
+
// 1. DISPLACEMENT (WAVES)
|
|
1173
|
+
// We add waveOffset to Y to scroll the wave pattern
|
|
483
1174
|
v_displacement_amount = cnoise( vec3(
|
|
484
1175
|
u_wave_frequency_x * position.x + u_time,
|
|
485
|
-
u_wave_frequency_y * position.y + u_time,
|
|
1176
|
+
u_wave_frequency_y * (position.y + waveOffset) + u_time,
|
|
486
1177
|
u_time
|
|
487
1178
|
));
|
|
488
1179
|
|
|
489
|
-
|
|
1180
|
+
// 2. FLOW FIELD
|
|
1181
|
+
// Apply flow offset to scroll the flow field mask
|
|
1182
|
+
vec2 baseUv = vUv;
|
|
1183
|
+
baseUv.y += flowOffset / u_plane_height; // Scale to match wave speed
|
|
1184
|
+
vec2 flowUv = baseUv;
|
|
1185
|
+
|
|
1186
|
+
if (u_flow_enabled > 0.5) {
|
|
1187
|
+
if (u_flow_ease > 0.0 || u_flow_distortion_a > 0.0) {
|
|
1188
|
+
vec2 ppp = -1.0 + 2.0 * baseUv;
|
|
1189
|
+
ppp += 0.1 * cos((1.5 * u_flow_scale) * ppp.yx + 1.1 * u_time + vec2(0.1, 1.1));
|
|
1190
|
+
ppp += 0.1 * cos((2.3 * u_flow_scale) * ppp.yx + 1.3 * u_time + vec2(3.2, 3.4));
|
|
1191
|
+
ppp += 0.1 * cos((2.2 * u_flow_scale) * ppp.yx + 1.7 * u_time + vec2(1.8, 5.2));
|
|
1192
|
+
ppp += u_flow_distortion_a * cos((u_flow_distortion_b * u_flow_scale) * ppp.yx + 1.4 * u_time + vec2(6.3, 3.9));
|
|
1193
|
+
|
|
1194
|
+
float r = length(ppp);
|
|
1195
|
+
flowUv = mix(baseUv, vec2(baseUv.x * (1.0 - u_flow_ease) + r * u_flow_ease, baseUv.y), u_flow_ease);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
490
1198
|
|
|
491
|
-
//
|
|
492
|
-
|
|
1199
|
+
// Pass the standard flow UV to fragment shader (for mouse/texture)
|
|
1200
|
+
vFlowUv = flowUv;
|
|
493
1201
|
|
|
494
|
-
//
|
|
495
|
-
|
|
496
|
-
//
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
1202
|
+
// 3. COLOR MIXING
|
|
1203
|
+
// We take the computed flow UVs and apply the color offset
|
|
1204
|
+
// Scale by plane height to match wave offset speed (world space vs UV space)
|
|
1205
|
+
vec3 color = u_colors[0].color;
|
|
1206
|
+
vec2 adjustedUv = flowUv;
|
|
1207
|
+
adjustedUv.y += colorOffset / u_plane_height; // Scroll the color mixing pattern
|
|
500
1208
|
|
|
1209
|
+
vec2 noise_cord = adjustedUv * u_color_pressure;
|
|
501
1210
|
const float minNoise = .0;
|
|
502
1211
|
const float maxNoise = .9;
|
|
503
1212
|
|
|
504
1213
|
for (int i = 1; i < u_colors_count; i++) {
|
|
505
|
-
|
|
506
|
-
if(u_colors[i].is_active == 1.0){
|
|
1214
|
+
if(u_colors[i].is_active > 0.5){
|
|
507
1215
|
float noiseFlow = (1. + float(i)) / 30.;
|
|
508
1216
|
float noiseSpeed = (1. + float(i)) * 0.11;
|
|
509
1217
|
float noiseSeed = 13. + float(i) * 7.;
|
|
510
1218
|
|
|
511
|
-
int reverseIndex = u_colors_count - i;
|
|
512
|
-
|
|
513
1219
|
float noise = snoise(
|
|
514
1220
|
vec3(
|
|
515
1221
|
noise_cord.x * u_color_pressure.x + u_time * noiseFlow * 2.,
|
|
@@ -519,16 +1225,15 @@ void main() {
|
|
|
519
1225
|
) - (.1 * float(i)) + (.5 * u_color_blending);
|
|
520
1226
|
|
|
521
1227
|
noise = clamp(minNoise, maxNoise + float(i) * 0.02, noise);
|
|
522
|
-
|
|
523
|
-
color = mix(color, nextColor, smoothstep(0.0, u_color_blending, noise));
|
|
1228
|
+
color = mix(color, u_colors[i].color, smoothstep(0.0, u_color_blending, noise));
|
|
524
1229
|
}
|
|
525
1230
|
}
|
|
526
1231
|
|
|
527
1232
|
v_color = color;
|
|
528
1233
|
|
|
1234
|
+
// 4. VERTEX POSITION
|
|
529
1235
|
vec3 newPosition = position + normal * v_displacement_amount * u_wave_amplitude;
|
|
530
1236
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
|
|
531
|
-
|
|
532
1237
|
v_new_position = gl_Position;
|
|
533
1238
|
}
|
|
534
1239
|
`;
|
|
@@ -553,34 +1258,72 @@ float fbm(vec3 x) {
|
|
|
553
1258
|
}
|
|
554
1259
|
|
|
555
1260
|
void main() {
|
|
556
|
-
|
|
1261
|
+
// MOUSE DISTORTION
|
|
1262
|
+
vec2 finalUv = vFlowUv;
|
|
1263
|
+
|
|
1264
|
+
if (u_mouse_distortion_strength > 0.0) {
|
|
1265
|
+
vec4 mouseColor = texture2D(u_mouse_texture, vUv);
|
|
1266
|
+
float mouseValue = mouseColor.r;
|
|
1267
|
+
|
|
1268
|
+
if (mouseValue > 0.001) {
|
|
1269
|
+
float distortionAmount = mouseValue * u_mouse_distortion_strength;
|
|
1270
|
+
vec2 mouseDisp = vec2(distortionAmount, distortionAmount);
|
|
1271
|
+
finalUv -= mouseDisp;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
vec3 baseColor;
|
|
1276
|
+
|
|
1277
|
+
if (u_enable_procedural_texture > 0.5) {
|
|
1278
|
+
// Calculate flow field distance for ease effect
|
|
1279
|
+
vec2 ppp = -1.0 + 2.0 * finalUv;
|
|
1280
|
+
ppp += 0.1 * cos((1.5 * u_flow_scale) * ppp.yx + 1.1 * u_time + vec2(0.1, 1.1));
|
|
1281
|
+
ppp += 0.1 * cos((2.3 * u_flow_scale) * ppp.yx + 1.3 * u_time + vec2(3.2, 3.4));
|
|
1282
|
+
ppp += 0.1 * cos((2.2 * u_flow_scale) * ppp.yx + 1.7 * u_time + vec2(1.8, 5.2));
|
|
1283
|
+
ppp += u_flow_distortion_a * cos((u_flow_distortion_b * u_flow_scale) * ppp.yx + 1.4 * u_time + vec2(6.3, 3.9));
|
|
1284
|
+
float r = length(ppp); // Flow distance
|
|
1285
|
+
|
|
1286
|
+
// Ease blending: 0 = topographic (flow), 1 = image (UV)
|
|
1287
|
+
float vx = (finalUv.x * u_texture_ease) + (r * (1.0 - u_texture_ease));
|
|
1288
|
+
float vy = (finalUv.y * u_texture_ease) + (0.0 * (1.0 - u_texture_ease));
|
|
1289
|
+
vec2 texUv = vec2(vx, vy);
|
|
1290
|
+
|
|
1291
|
+
// PARALLAX SCROLLING
|
|
1292
|
+
// We manually apply a smaller offset here to make the texture lag behind
|
|
1293
|
+
float parallaxFactor = 0.25; // 25% speed of the color mixing
|
|
1294
|
+
texUv.y -= (u_y_offset * u_y_offset_color_multiplier / u_plane_height) * parallaxFactor;
|
|
1295
|
+
|
|
1296
|
+
texUv *= 1.5; // Tiling scale
|
|
1297
|
+
|
|
1298
|
+
vec4 texSample = texture2D(u_procedural_texture, texUv);
|
|
1299
|
+
baseColor = texSample.rgb;
|
|
1300
|
+
} else {
|
|
1301
|
+
baseColor = v_color;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
vec3 color = baseColor;
|
|
1305
|
+
|
|
1306
|
+
// Post-processing
|
|
557
1307
|
color += pow(v_displacement_amount, 1.0) * u_highlights;
|
|
558
1308
|
color -= pow(1.0 - v_displacement_amount, 2.0) * u_shadows;
|
|
559
1309
|
color = saturation(color, 1.0 + u_saturation);
|
|
560
1310
|
color = color * u_brightness;
|
|
561
1311
|
|
|
562
|
-
//
|
|
1312
|
+
// Grain
|
|
563
1313
|
vec2 noiseCoords = gl_FragCoord.xy / u_grain_scale;
|
|
564
1314
|
float grain = (u_grain_speed != 0.0) ? fbm(vec3(noiseCoords, u_time * u_grain_speed)) : fbm(vec3(noiseCoords, 0.0));
|
|
565
1315
|
|
|
566
|
-
// Center the grain around zero
|
|
567
1316
|
grain = grain * 0.5 + 0.5;
|
|
568
1317
|
grain -= 0.5;
|
|
569
|
-
|
|
570
|
-
// Add sparsity control
|
|
571
1318
|
grain = (grain > u_grain_sparsity) ? grain : 0.0;
|
|
572
|
-
|
|
573
|
-
// Apply grain intensity
|
|
574
1319
|
grain *= u_grain_intensity;
|
|
575
1320
|
|
|
576
|
-
// Add grain to color
|
|
577
1321
|
color += vec3(grain);
|
|
578
1322
|
|
|
579
1323
|
gl_FragColor = vec4(color, 1.0);
|
|
580
1324
|
}
|
|
581
1325
|
`;
|
|
582
1326
|
}
|
|
583
|
-
|
|
584
1327
|
const buildUniforms = () => `
|
|
585
1328
|
precision highp float;
|
|
586
1329
|
|
|
@@ -613,12 +1356,34 @@ uniform float u_brightness;
|
|
|
613
1356
|
uniform float u_color_blending;
|
|
614
1357
|
|
|
615
1358
|
uniform int u_colors_count;
|
|
616
|
-
uniform Color u_colors[
|
|
1359
|
+
uniform Color u_colors[6];
|
|
617
1360
|
uniform vec2 u_resolution;
|
|
618
1361
|
|
|
619
1362
|
uniform float u_y_offset;
|
|
1363
|
+
uniform float u_y_offset_wave_multiplier;
|
|
1364
|
+
uniform float u_y_offset_color_multiplier;
|
|
1365
|
+
uniform float u_y_offset_flow_multiplier;
|
|
1366
|
+
|
|
1367
|
+
// Flow field uniforms
|
|
1368
|
+
uniform float u_flow_distortion_a;
|
|
1369
|
+
uniform float u_flow_distortion_b;
|
|
1370
|
+
uniform float u_flow_scale;
|
|
1371
|
+
uniform float u_flow_ease;
|
|
1372
|
+
uniform float u_flow_enabled;
|
|
1373
|
+
|
|
1374
|
+
// Mouse interaction uniforms
|
|
1375
|
+
uniform float u_mouse_distortion_strength;
|
|
1376
|
+
uniform float u_mouse_distortion_radius;
|
|
1377
|
+
uniform float u_mouse_darken;
|
|
1378
|
+
uniform sampler2D u_mouse_texture;
|
|
1379
|
+
|
|
1380
|
+
// Procedural texture uniforms
|
|
1381
|
+
uniform sampler2D u_procedural_texture;
|
|
1382
|
+
uniform float u_enable_procedural_texture;
|
|
1383
|
+
uniform float u_texture_ease;
|
|
620
1384
|
|
|
621
1385
|
varying vec2 vUv;
|
|
1386
|
+
varying vec2 vFlowUv;
|
|
622
1387
|
varying vec4 v_new_position;
|
|
623
1388
|
varying vec3 v_color;
|
|
624
1389
|
varying float v_displacement_amount;
|
|
@@ -627,69 +1392,55 @@ varying float v_displacement_amount;
|
|
|
627
1392
|
|
|
628
1393
|
const buildNoise = () => `
|
|
629
1394
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
vec4 mod289(vec4 x)
|
|
636
|
-
{
|
|
637
|
-
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
|
1395
|
+
// 1. REPLACEMENT PERMUTE:
|
|
1396
|
+
// Uses a hash function (fract/sin) instead of a modular lookup table.
|
|
1397
|
+
vec4 permute(vec4 x) {
|
|
1398
|
+
return floor(fract(sin(x) * 43758.5453123) * 289.0);
|
|
638
1399
|
}
|
|
639
1400
|
|
|
640
|
-
|
|
641
|
-
{
|
|
642
|
-
return mod289(((x*34.0)+1.0)*x);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
vec4 taylorInvSqrt(vec4 r)
|
|
646
|
-
{
|
|
1401
|
+
// Taylor Inverse Sqrt
|
|
1402
|
+
vec4 taylorInvSqrt(vec4 r) {
|
|
647
1403
|
return 1.79284291400159 - 0.85373472095314 * r;
|
|
648
1404
|
}
|
|
649
1405
|
|
|
1406
|
+
// Fade function
|
|
650
1407
|
vec3 fade(vec3 t) {
|
|
651
1408
|
return t*t*t*(t*(t*6.0-15.0)+10.0);
|
|
652
1409
|
}
|
|
653
1410
|
|
|
654
|
-
|
|
655
|
-
{
|
|
1411
|
+
// 3D Simplex Noise
|
|
1412
|
+
float snoise(vec3 v) {
|
|
656
1413
|
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
|
|
657
1414
|
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
|
658
1415
|
|
|
659
|
-
// First corner
|
|
1416
|
+
// First corner
|
|
660
1417
|
vec3 i = floor(v + dot(v, C.yyy) );
|
|
661
1418
|
vec3 x0 = v - i + dot(i, C.xxx) ;
|
|
662
1419
|
|
|
663
|
-
// Other corners
|
|
1420
|
+
// Other corners
|
|
664
1421
|
vec3 g = step(x0.yzx, x0.xyz);
|
|
665
1422
|
vec3 l = 1.0 - g;
|
|
666
1423
|
vec3 i1 = min( g.xyz, l.zxy );
|
|
667
1424
|
vec3 i2 = max( g.xyz, l.zxy );
|
|
668
1425
|
|
|
669
|
-
// x0 = x0 - 0.0 + 0.0 * C.xxx;
|
|
670
|
-
// x1 = x0 - i1 + 1.0 * C.xxx;
|
|
671
|
-
// x2 = x0 - i2 + 2.0 * C.xxx;
|
|
672
|
-
// x3 = x0 - 1.0 + 3.0 * C.xxx;
|
|
673
1426
|
vec3 x1 = x0 - i1 + C.xxx;
|
|
674
|
-
vec3 x2 = x0 - i2 + C.yyy;
|
|
675
|
-
vec3 x3 = x0 - D.yyy;
|
|
1427
|
+
vec3 x2 = x0 - i2 + C.yyy;
|
|
1428
|
+
vec3 x3 = x0 - D.yyy;
|
|
676
1429
|
|
|
677
|
-
// Permutations
|
|
678
|
-
i = mod289(i);
|
|
1430
|
+
// Permutations
|
|
679
1431
|
vec4 p = permute( permute( permute(
|
|
680
1432
|
i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
|
|
681
1433
|
+ i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
|
|
682
1434
|
+ i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
|
|
683
1435
|
|
|
684
|
-
// Gradients
|
|
685
|
-
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
|
|
1436
|
+
// Gradients
|
|
686
1437
|
float n_ = 0.142857142857; // 1.0/7.0
|
|
687
1438
|
vec3 ns = n_ * D.wyz - D.xzx;
|
|
688
1439
|
|
|
689
|
-
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
1440
|
+
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
690
1441
|
|
|
691
1442
|
vec4 x_ = floor(j * ns.z);
|
|
692
|
-
vec4 y_ = floor(j - 7.0 * x_ );
|
|
1443
|
+
vec4 y_ = floor(j - 7.0 * x_ );
|
|
693
1444
|
|
|
694
1445
|
vec4 x = x_ *ns.x + ns.yyyy;
|
|
695
1446
|
vec4 y = y_ *ns.x + ns.yyyy;
|
|
@@ -698,8 +1449,6 @@ float snoise(vec3 v)
|
|
|
698
1449
|
vec4 b0 = vec4( x.xy, y.xy );
|
|
699
1450
|
vec4 b1 = vec4( x.zw, y.zw );
|
|
700
1451
|
|
|
701
|
-
//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
|
|
702
|
-
//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
|
|
703
1452
|
vec4 s0 = floor(b0)*2.0 + 1.0;
|
|
704
1453
|
vec4 s1 = floor(b1)*2.0 + 1.0;
|
|
705
1454
|
vec4 sh = -step(h, vec4(0.0));
|
|
@@ -712,14 +1461,14 @@ float snoise(vec3 v)
|
|
|
712
1461
|
vec3 p2 = vec3(a1.xy,h.z);
|
|
713
1462
|
vec3 p3 = vec3(a1.zw,h.w);
|
|
714
1463
|
|
|
715
|
-
//Normalise gradients
|
|
1464
|
+
// Normalise gradients
|
|
716
1465
|
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
|
|
717
1466
|
p0 *= norm.x;
|
|
718
1467
|
p1 *= norm.y;
|
|
719
1468
|
p2 *= norm.z;
|
|
720
1469
|
p3 *= norm.w;
|
|
721
1470
|
|
|
722
|
-
// Mix final noise value
|
|
1471
|
+
// Mix final noise value
|
|
723
1472
|
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
|
|
724
1473
|
m = m * m;
|
|
725
1474
|
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
|
|
@@ -729,12 +1478,11 @@ float snoise(vec3 v)
|
|
|
729
1478
|
// Classic Perlin noise
|
|
730
1479
|
float cnoise(vec3 P)
|
|
731
1480
|
{
|
|
732
|
-
vec3 Pi0 = floor(P);
|
|
733
|
-
vec3 Pi1 = Pi0 + vec3(1.0);
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
vec3
|
|
737
|
-
vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
|
|
1481
|
+
vec3 Pi0 = floor(P);
|
|
1482
|
+
vec3 Pi1 = Pi0 + vec3(1.0);
|
|
1483
|
+
|
|
1484
|
+
vec3 Pf0 = fract(P);
|
|
1485
|
+
vec3 Pf1 = Pf0 - vec3(1.0);
|
|
738
1486
|
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
|
|
739
1487
|
vec4 iy = vec4(Pi0.yy, Pi1.yy);
|
|
740
1488
|
vec4 iz0 = Pi0.zzzz;
|
|
@@ -795,47 +1543,7 @@ float cnoise(vec3 P)
|
|
|
795
1543
|
float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
|
|
796
1544
|
return 2.2 * n_xyz;
|
|
797
1545
|
}
|
|
798
|
-
|
|
799
|
-
// YUV to RGB matrix
|
|
800
|
-
mat3 yuv2rgb = mat3(1.0, 0.0, 1.13983,
|
|
801
|
-
1.0, -0.39465, -0.58060,
|
|
802
|
-
1.0, 2.03211, 0.0);
|
|
803
|
-
|
|
804
|
-
// RGB to YUV matrix
|
|
805
|
-
mat3 rgb2yuv = mat3(0.2126, 0.7152, 0.0722,
|
|
806
|
-
-0.09991, -0.33609, 0.43600,
|
|
807
|
-
0.615, -0.5586, -0.05639);
|
|
808
|
-
|
|
809
|
-
vec3 oklab2rgb(vec3 linear)
|
|
810
|
-
{
|
|
811
|
-
const mat3 im1 = mat3(0.4121656120, 0.2118591070, 0.0883097947,
|
|
812
|
-
0.5362752080, 0.6807189584, 0.2818474174,
|
|
813
|
-
0.0514575653, 0.1074065790, 0.6302613616);
|
|
814
|
-
|
|
815
|
-
const mat3 im2 = mat3(+0.2104542553, +1.9779984951, +0.0259040371,
|
|
816
|
-
+0.7936177850, -2.4285922050, +0.7827717662,
|
|
817
|
-
-0.0040720468, +0.4505937099, -0.8086757660);
|
|
818
|
-
|
|
819
|
-
vec3 lms = im1 * linear;
|
|
820
|
-
|
|
821
|
-
return im2 * (sign(lms) * pow(abs(lms), vec3(1.0/3.0)));
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
vec3 rgb2oklab(vec3 oklab)
|
|
825
|
-
{
|
|
826
|
-
const mat3 m1 = mat3(+1.000000000, +1.000000000, +1.000000000,
|
|
827
|
-
+0.396337777, -0.105561346, -0.089484178,
|
|
828
|
-
+0.215803757, -0.063854173, -1.291485548);
|
|
829
|
-
|
|
830
|
-
const mat3 m2 = mat3(+4.076724529, -1.268143773, -0.004111989,
|
|
831
|
-
-3.307216883, +2.609332323, -0.703476310,
|
|
832
|
-
+0.230759054, -0.341134429, +1.706862569);
|
|
833
|
-
vec3 lms = m1 * oklab;
|
|
834
|
-
|
|
835
|
-
return m2 * (lms * lms * lms);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
`;
|
|
1546
|
+
`;
|
|
839
1547
|
|
|
840
1548
|
const buildColorFunctions = () => `
|
|
841
1549
|
|
|
@@ -882,7 +1590,6 @@ vec3 hsv2rgb(vec3 c)
|
|
|
882
1590
|
}
|
|
883
1591
|
`;
|
|
884
1592
|
|
|
885
|
-
|
|
886
1593
|
const setLinkStyles = (link: HTMLAnchorElement) => {
|
|
887
1594
|
link.id = LINK_ID;
|
|
888
1595
|
link.href = "https://neat.firecms.co";
|
|
@@ -902,19 +1609,20 @@ const setLinkStyles = (link: HTMLAnchorElement) => {
|
|
|
902
1609
|
link.innerHTML = "NEAT";
|
|
903
1610
|
}
|
|
904
1611
|
|
|
905
|
-
const addNeatLink = (ref: HTMLCanvasElement) => {
|
|
1612
|
+
const addNeatLink = (ref: HTMLCanvasElement): HTMLAnchorElement => {
|
|
906
1613
|
const existingLinks = ref.parentElement?.getElementsByTagName("a");
|
|
907
1614
|
if (existingLinks) {
|
|
908
1615
|
for (let i = 0; i < existingLinks.length; i++) {
|
|
909
1616
|
if (existingLinks[i].id === LINK_ID) {
|
|
910
1617
|
setLinkStyles(existingLinks[i]);
|
|
911
|
-
return;
|
|
1618
|
+
return existingLinks[i];
|
|
912
1619
|
}
|
|
913
1620
|
}
|
|
914
1621
|
}
|
|
915
1622
|
const link = document.createElement("a");
|
|
916
1623
|
setLinkStyles(link);
|
|
917
1624
|
ref.parentElement?.appendChild(link);
|
|
1625
|
+
return link;
|
|
918
1626
|
}
|
|
919
1627
|
|
|
920
1628
|
function getElapsedSecondsInLastHour() {
|