@firecms/neat 0.3.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 +105 -0
- package/dist/NeatGradient.js +793 -202
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.es.js +604 -236
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +156 -133
- package/dist/index.umd.js.map +1 -1
- package/package.json +3 -3
- package/src/NeatGradient.ts +955 -209
package/dist/NeatGradient.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as THREE from "three";
|
|
|
2
2
|
const PLANE_WIDTH = 50;
|
|
3
3
|
const PLANE_HEIGHT = 80;
|
|
4
4
|
const WIREFRAME = true;
|
|
5
|
-
const COLORS_COUNT =
|
|
5
|
+
const COLORS_COUNT = 6;
|
|
6
6
|
const clock = new THREE.Clock();
|
|
7
7
|
const LINK_ID = generateRandomString();
|
|
8
8
|
export class NeatGradient {
|
|
@@ -19,17 +19,66 @@ export class NeatGradient {
|
|
|
19
19
|
_brightness = -1;
|
|
20
20
|
_grainScale = -1;
|
|
21
21
|
_grainIntensity = -1;
|
|
22
|
+
_grainSparsity = -1;
|
|
22
23
|
_grainSpeed = -1;
|
|
23
24
|
_colorBlending = -1;
|
|
24
25
|
_colors = [];
|
|
25
26
|
_wireframe = false;
|
|
26
27
|
_backgroundColor = "#FFFFFF";
|
|
27
28
|
_backgroundAlpha = 1.0;
|
|
29
|
+
// Flow field properties
|
|
30
|
+
_flowDistortionA = 0;
|
|
31
|
+
_flowDistortionB = 0;
|
|
32
|
+
_flowScale = 1.0;
|
|
33
|
+
_flowEase = 0.0;
|
|
34
|
+
_flowEnabled = true;
|
|
35
|
+
// Mouse interaction properties
|
|
36
|
+
_mouseDistortionStrength = 0.0;
|
|
37
|
+
_mouseDistortionRadius = 0.25;
|
|
38
|
+
_mouseDecayRate = 0.96;
|
|
39
|
+
_mouseDarken = 0.0;
|
|
40
|
+
_mouse = new THREE.Vector2(-1000, -1000);
|
|
41
|
+
_mouseFBO = null;
|
|
42
|
+
_sceneMouse = null;
|
|
43
|
+
_cameraMouse = null;
|
|
44
|
+
_mouseObjects = [];
|
|
45
|
+
_currentBrush = 0;
|
|
46
|
+
_mouseBrushBaseScale = 1;
|
|
47
|
+
// Texture generation properties
|
|
48
|
+
_enableProceduralTexture = false;
|
|
49
|
+
_textureVoidLikelihood = 0.45;
|
|
50
|
+
_textureVoidWidthMin = 200;
|
|
51
|
+
_textureVoidWidthMax = 486;
|
|
52
|
+
_textureBandDensity = 2.15;
|
|
53
|
+
_textureColorBlending = 0.01;
|
|
54
|
+
_textureSeed = 333;
|
|
55
|
+
_textureEase = 0.5;
|
|
56
|
+
_proceduralTexture = null;
|
|
57
|
+
_proceduralBackgroundColor = "#000000";
|
|
58
|
+
_textureShapeTriangles = 20;
|
|
59
|
+
_textureShapeCircles = 15;
|
|
60
|
+
_textureShapeBars = 15;
|
|
61
|
+
_textureShapeSquiggles = 10;
|
|
28
62
|
requestRef = -1;
|
|
29
63
|
sizeObserver;
|
|
30
64
|
sceneState;
|
|
65
|
+
// Optimization: Cache uniforms to avoid lookups and object creation in render loop
|
|
66
|
+
_cachedUniforms = null;
|
|
67
|
+
_linkElement = null;
|
|
68
|
+
_yOffset = 0;
|
|
69
|
+
_yOffsetWaveMultiplier = 0.004;
|
|
70
|
+
_yOffsetColorMultiplier = 0.004;
|
|
71
|
+
_yOffsetFlowMultiplier = 0.004;
|
|
72
|
+
// For saving/restoring clear color
|
|
73
|
+
_tempClearColor = new THREE.Color();
|
|
31
74
|
constructor(config) {
|
|
32
|
-
const { ref, speed = 4, horizontalPressure = 3, verticalPressure = 3, waveFrequencyX = 5, waveFrequencyY = 5, waveAmplitude = 3, colors, highlights = 4, shadows = 4, colorSaturation = 0, colorBrightness = 1, colorBlending = 5, grainScale = 2, grainIntensity = 0.55, grainSpeed = 0.1, wireframe = false, backgroundColor = "#FFFFFF", backgroundAlpha = 1.0, resolution = 1, seed
|
|
75
|
+
const { ref, speed = 4, horizontalPressure = 3, verticalPressure = 3, waveFrequencyX = 5, waveFrequencyY = 5, waveAmplitude = 3, colors, highlights = 4, shadows = 4, colorSaturation = 0, colorBrightness = 1, colorBlending = 5, grainScale = 2, grainIntensity = 0.55, grainSparsity = 0.0, grainSpeed = 0.1, wireframe = false, backgroundColor = "#FFFFFF", backgroundAlpha = 1.0, resolution = 1, seed, yOffset = 0, yOffsetWaveMultiplier = 4, yOffsetColorMultiplier = 4, yOffsetFlowMultiplier = 4,
|
|
76
|
+
// Flow field parameters
|
|
77
|
+
flowDistortionA = 0, flowDistortionB = 0, flowScale = 1.0, flowEase = 0.0, flowEnabled = true,
|
|
78
|
+
// Mouse interaction
|
|
79
|
+
mouseDistortionStrength = 0.0, mouseDistortionRadius = 0.25, mouseDecayRate = 0.96, mouseDarken = 0.0,
|
|
80
|
+
// Texture generation
|
|
81
|
+
enableProceduralTexture = false, textureVoidLikelihood = 0.45, textureVoidWidthMin = 200, textureVoidWidthMax = 486, textureBandDensity = 2.15, textureColorBlending = 0.01, textureSeed = 333, textureEase = 0.5, proceduralBackgroundColor = "#000000", textureShapeTriangles = 20, textureShapeCircles = 15, textureShapeBars = 15, textureShapeSquiggles = 10, } = config;
|
|
33
82
|
this._ref = ref;
|
|
34
83
|
this.destroy = this.destroy.bind(this);
|
|
35
84
|
this._initScene = this._initScene.bind(this);
|
|
@@ -43,6 +92,7 @@ export class NeatGradient {
|
|
|
43
92
|
this.colorBlending = colorBlending;
|
|
44
93
|
this.grainScale = grainScale;
|
|
45
94
|
this.grainIntensity = grainIntensity;
|
|
95
|
+
this.grainSparsity = grainSparsity;
|
|
46
96
|
this.grainSpeed = grainSpeed;
|
|
47
97
|
this.colors = colors;
|
|
48
98
|
this.shadows = shadows;
|
|
@@ -52,71 +102,142 @@ export class NeatGradient {
|
|
|
52
102
|
this.wireframe = wireframe;
|
|
53
103
|
this.backgroundColor = backgroundColor;
|
|
54
104
|
this.backgroundAlpha = backgroundAlpha;
|
|
105
|
+
this.yOffset = yOffset;
|
|
106
|
+
this.yOffsetWaveMultiplier = yOffsetWaveMultiplier;
|
|
107
|
+
this.yOffsetColorMultiplier = yOffsetColorMultiplier;
|
|
108
|
+
this.yOffsetFlowMultiplier = yOffsetFlowMultiplier;
|
|
109
|
+
// Flow field
|
|
110
|
+
this.flowDistortionA = flowDistortionA;
|
|
111
|
+
this.flowDistortionB = flowDistortionB;
|
|
112
|
+
this.flowScale = flowScale;
|
|
113
|
+
this.flowEase = flowEase;
|
|
114
|
+
this.flowEnabled = flowEnabled;
|
|
115
|
+
// Mouse interaction
|
|
116
|
+
this.mouseDistortionStrength = mouseDistortionStrength;
|
|
117
|
+
this.mouseDistortionRadius = mouseDistortionRadius;
|
|
118
|
+
this.mouseDecayRate = mouseDecayRate;
|
|
119
|
+
this.mouseDarken = mouseDarken;
|
|
120
|
+
// Texture generation
|
|
121
|
+
this.enableProceduralTexture = enableProceduralTexture;
|
|
122
|
+
this.textureVoidLikelihood = textureVoidLikelihood;
|
|
123
|
+
this.textureVoidWidthMin = textureVoidWidthMin;
|
|
124
|
+
this.textureVoidWidthMax = textureVoidWidthMax;
|
|
125
|
+
this.textureBandDensity = textureBandDensity;
|
|
126
|
+
this.textureColorBlending = textureColorBlending;
|
|
127
|
+
this.textureSeed = textureSeed;
|
|
128
|
+
this.textureEase = textureEase;
|
|
129
|
+
this._proceduralBackgroundColor = proceduralBackgroundColor;
|
|
130
|
+
this._textureShapeTriangles = textureShapeTriangles;
|
|
131
|
+
this._textureShapeCircles = textureShapeCircles;
|
|
132
|
+
this._textureShapeBars = textureShapeBars;
|
|
133
|
+
this._textureShapeSquiggles = textureShapeSquiggles;
|
|
134
|
+
// FIX 1: Setup mouse resources BEFORE building the material/scene
|
|
135
|
+
// This ensures u_mouse_texture isn't null during material compilation
|
|
136
|
+
this._setupMouseInteraction();
|
|
55
137
|
this.sceneState = this._initScene(resolution);
|
|
56
138
|
let tick = seed !== undefined ? seed : getElapsedSecondsInLastHour();
|
|
57
139
|
const render = () => {
|
|
58
|
-
const { renderer, camera, scene
|
|
140
|
+
const { renderer, camera, scene } = this.sceneState;
|
|
141
|
+
// Optimization: check if cached link is still valid in DOM, otherwise search
|
|
59
142
|
if (Math.floor(tick * 10) % 5 === 0) {
|
|
60
|
-
|
|
143
|
+
if (!this._linkElement || !document.contains(this._linkElement)) {
|
|
144
|
+
this._linkElement = addNeatLink(ref);
|
|
145
|
+
}
|
|
61
146
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
const colors = [
|
|
66
|
-
...this._colors.map(color => {
|
|
67
|
-
let threeColor = new THREE.Color();
|
|
68
|
-
threeColor.setStyle(color.color, "");
|
|
69
|
-
return ({
|
|
70
|
-
is_active: color.enabled,
|
|
71
|
-
color: threeColor,
|
|
72
|
-
influence: color.influence
|
|
73
|
-
});
|
|
74
|
-
}),
|
|
75
|
-
...Array.from({ length: COLORS_COUNT - this._colors.length }).map(() => ({
|
|
76
|
-
is_active: false,
|
|
77
|
-
color: new THREE.Color(0x000000)
|
|
78
|
-
}))
|
|
79
|
-
];
|
|
147
|
+
// Update Uniforms efficiently without creating new objects
|
|
148
|
+
if (this._cachedUniforms) {
|
|
149
|
+
const u = this._cachedUniforms;
|
|
80
150
|
tick += clock.getDelta() * this._speed;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
151
|
+
u.u_time.value = tick;
|
|
152
|
+
u.u_resolution.value.set(this._ref.width, this._ref.height);
|
|
153
|
+
u.u_color_pressure.value.set(this._horizontalPressure, this._verticalPressure);
|
|
154
|
+
// Directly assign simple values
|
|
155
|
+
u.u_wave_frequency_x.value = this._waveFrequencyX;
|
|
156
|
+
u.u_wave_frequency_y.value = this._waveFrequencyY;
|
|
157
|
+
u.u_wave_amplitude.value = this._waveAmplitude;
|
|
158
|
+
u.u_color_blending.value = this._colorBlending;
|
|
159
|
+
u.u_shadows.value = this._shadows;
|
|
160
|
+
u.u_highlights.value = this._highlights;
|
|
161
|
+
u.u_saturation.value = this._saturation;
|
|
162
|
+
u.u_brightness.value = this._brightness;
|
|
163
|
+
u.u_grain_intensity.value = this._grainIntensity;
|
|
164
|
+
u.u_grain_sparsity.value = this._grainSparsity;
|
|
165
|
+
u.u_grain_speed.value = this._grainSpeed;
|
|
166
|
+
u.u_grain_scale.value = this._grainScale;
|
|
167
|
+
u.u_y_offset.value = this._yOffset;
|
|
168
|
+
u.u_y_offset_wave_multiplier.value = this._yOffsetWaveMultiplier;
|
|
169
|
+
u.u_y_offset_color_multiplier.value = this._yOffsetColorMultiplier;
|
|
170
|
+
u.u_y_offset_flow_multiplier.value = this._yOffsetFlowMultiplier;
|
|
171
|
+
u.u_flow_distortion_a.value = this._flowDistortionA;
|
|
172
|
+
u.u_flow_distortion_b.value = this._flowDistortionB;
|
|
173
|
+
u.u_flow_scale.value = this._flowScale;
|
|
174
|
+
u.u_flow_ease.value = this._flowEase;
|
|
175
|
+
u.u_flow_enabled.value = this._flowEnabled ? 1.0 : 0.0;
|
|
176
|
+
u.u_mouse_distortion_strength.value = this._mouseDistortionStrength;
|
|
177
|
+
u.u_mouse_distortion_radius.value = this._mouseDistortionRadius;
|
|
178
|
+
u.u_mouse_darken.value = this._mouseDarken;
|
|
179
|
+
u.u_enable_procedural_texture.value = this._enableProceduralTexture ? 1.0 : 0.0;
|
|
180
|
+
u.u_procedural_texture.value = this._proceduralTexture;
|
|
181
|
+
u.u_texture_ease.value = this._textureEase;
|
|
182
|
+
// Optimized Color Update: Update the existing array objects instead of recreating array
|
|
183
|
+
const shaderColors = u.u_colors.value;
|
|
184
|
+
for (let i = 0; i < COLORS_COUNT; i++) {
|
|
185
|
+
if (i < this._colors.length) {
|
|
186
|
+
const c = this._colors[i];
|
|
187
|
+
shaderColors[i].is_active = c.enabled ? 1.0 : 0.0;
|
|
188
|
+
shaderColors[i].color.setStyle(c.color, "");
|
|
189
|
+
shaderColors[i].influence = c.influence || 0;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
shaderColors[i].is_active = 0.0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
u.u_colors_count.value = COLORS_COUNT;
|
|
196
|
+
// Wireframe is a material property, not a uniform
|
|
197
|
+
// @ts-ignore - access material safely
|
|
198
|
+
this.sceneState.meshes[0].material.wireframe = this._wireframe;
|
|
199
|
+
}
|
|
200
|
+
// Render mouse interaction to FBO
|
|
201
|
+
if (this._mouseFBO && this._sceneMouse && this._cameraMouse) {
|
|
202
|
+
let hasActiveBrushes = false;
|
|
203
|
+
// Update mouse objects - decay rate controls how fast trails fade
|
|
204
|
+
for (let i = 0; i < this._mouseObjects.length; i++) {
|
|
205
|
+
const obj = this._mouseObjects[i];
|
|
206
|
+
if (obj.mesh.visible) {
|
|
207
|
+
hasActiveBrushes = true;
|
|
208
|
+
obj.mesh.rotation.z += 0.01;
|
|
209
|
+
if (obj.mesh.material instanceof THREE.MeshBasicMaterial) {
|
|
210
|
+
// Decay only affects opacity
|
|
211
|
+
obj.mesh.material.opacity *= this._mouseDecayRate;
|
|
212
|
+
if (obj.mesh.material.opacity < 0.01) {
|
|
213
|
+
obj.mesh.visible = false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// FIX 2: Handle FBO Clearing correctly
|
|
219
|
+
// Store current clear color (likely the main background color)
|
|
220
|
+
renderer.getClearColor(this._tempClearColor);
|
|
221
|
+
const oldClearAlpha = renderer.getClearAlpha();
|
|
222
|
+
// Set clear color to Black/Transparent for the FBO.
|
|
223
|
+
// Important: If we use the main background color (e.g. White), the FBO
|
|
224
|
+
// will be white, causing 100% distortion everywhere.
|
|
225
|
+
renderer.setClearColor(0x000000, 0.0);
|
|
226
|
+
renderer.setRenderTarget(this._mouseFBO);
|
|
227
|
+
renderer.clear();
|
|
228
|
+
if (hasActiveBrushes) {
|
|
229
|
+
renderer.render(this._sceneMouse, this._cameraMouse);
|
|
230
|
+
}
|
|
231
|
+
renderer.setRenderTarget(null);
|
|
232
|
+
// Restore main background color for the actual scene render
|
|
233
|
+
renderer.setClearColor(this._tempClearColor, oldClearAlpha);
|
|
234
|
+
// Update mouse texture uniform
|
|
235
|
+
if (this._cachedUniforms) {
|
|
236
|
+
this._cachedUniforms.u_mouse_texture.value = this._mouseFBO.texture;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Ensure we set the clear color for the main scene explicitly before rendering
|
|
240
|
+
renderer.setClearColor(this._backgroundColor, this._backgroundAlpha);
|
|
120
241
|
renderer.render(scene, camera);
|
|
121
242
|
this.requestRef = requestAnimationFrame(render);
|
|
122
243
|
};
|
|
@@ -127,6 +248,18 @@ export class NeatGradient {
|
|
|
127
248
|
const height = canvas.clientHeight;
|
|
128
249
|
this.sceneState.renderer.setSize(width, height, false);
|
|
129
250
|
updateCamera(this.sceneState.camera, width, height);
|
|
251
|
+
// FIX 3: Update Mouse FBO and Camera on resize
|
|
252
|
+
// If we don't do this, mouse coordinates map incorrectly after a resize
|
|
253
|
+
if (this._mouseFBO && this._cameraMouse) {
|
|
254
|
+
const fSize = height / 2;
|
|
255
|
+
const aspect = width / height;
|
|
256
|
+
this._mouseFBO.setSize(width / 2, height / 2);
|
|
257
|
+
this._cameraMouse.left = -fSize * aspect;
|
|
258
|
+
this._cameraMouse.right = fSize * aspect;
|
|
259
|
+
this._cameraMouse.top = fSize;
|
|
260
|
+
this._cameraMouse.bottom = -fSize;
|
|
261
|
+
this._cameraMouse.updateProjectionMatrix();
|
|
262
|
+
}
|
|
130
263
|
};
|
|
131
264
|
this.sizeObserver = new ResizeObserver(entries => {
|
|
132
265
|
setSize();
|
|
@@ -138,8 +271,29 @@ export class NeatGradient {
|
|
|
138
271
|
if (this) {
|
|
139
272
|
cancelAnimationFrame(this.requestRef);
|
|
140
273
|
this.sizeObserver.disconnect();
|
|
274
|
+
// Cleanup WebGL resources
|
|
275
|
+
if (this.sceneState) {
|
|
276
|
+
this.sceneState.renderer.dispose();
|
|
277
|
+
this.sceneState.meshes.forEach(m => {
|
|
278
|
+
m.geometry.dispose();
|
|
279
|
+
if (Array.isArray(m.material))
|
|
280
|
+
m.material.forEach(mat => mat.dispose());
|
|
281
|
+
else
|
|
282
|
+
m.material.dispose();
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (this._mouseFBO)
|
|
286
|
+
this._mouseFBO.dispose();
|
|
287
|
+
if (this._proceduralTexture)
|
|
288
|
+
this._proceduralTexture.dispose();
|
|
141
289
|
}
|
|
142
290
|
}
|
|
291
|
+
downloadAsPNG(filename = "neat.png") {
|
|
292
|
+
console.log("Downloading as PNG", this._ref);
|
|
293
|
+
const dataURL = this._ref.toDataURL("image/png");
|
|
294
|
+
console.log("data", dataURL);
|
|
295
|
+
downloadURI(dataURL, filename);
|
|
296
|
+
}
|
|
143
297
|
set speed(speed) {
|
|
144
298
|
this._speed = speed / 20;
|
|
145
299
|
}
|
|
@@ -182,6 +336,9 @@ export class NeatGradient {
|
|
|
182
336
|
set grainIntensity(grainIntensity) {
|
|
183
337
|
this._grainIntensity = grainIntensity;
|
|
184
338
|
}
|
|
339
|
+
set grainSparsity(grainSparsity) {
|
|
340
|
+
this._grainSparsity = grainSparsity;
|
|
341
|
+
}
|
|
185
342
|
set grainSpeed(grainSpeed) {
|
|
186
343
|
this._grainSpeed = grainSpeed;
|
|
187
344
|
}
|
|
@@ -197,11 +354,159 @@ export class NeatGradient {
|
|
|
197
354
|
set backgroundAlpha(backgroundAlpha) {
|
|
198
355
|
this._backgroundAlpha = backgroundAlpha;
|
|
199
356
|
}
|
|
357
|
+
set yOffset(yOffset) {
|
|
358
|
+
this._yOffset = yOffset;
|
|
359
|
+
}
|
|
360
|
+
get yOffsetWaveMultiplier() {
|
|
361
|
+
return this._yOffsetWaveMultiplier * 1000;
|
|
362
|
+
}
|
|
363
|
+
set yOffsetWaveMultiplier(value) {
|
|
364
|
+
this._yOffsetWaveMultiplier = value / 1000;
|
|
365
|
+
}
|
|
366
|
+
get yOffsetColorMultiplier() {
|
|
367
|
+
return this._yOffsetColorMultiplier * 1000;
|
|
368
|
+
}
|
|
369
|
+
set yOffsetColorMultiplier(value) {
|
|
370
|
+
this._yOffsetColorMultiplier = value / 1000;
|
|
371
|
+
}
|
|
372
|
+
get yOffsetFlowMultiplier() {
|
|
373
|
+
return this._yOffsetFlowMultiplier * 1000;
|
|
374
|
+
}
|
|
375
|
+
set yOffsetFlowMultiplier(value) {
|
|
376
|
+
this._yOffsetFlowMultiplier = value / 1000;
|
|
377
|
+
}
|
|
378
|
+
set flowDistortionA(value) {
|
|
379
|
+
this._flowDistortionA = value;
|
|
380
|
+
}
|
|
381
|
+
set flowDistortionB(value) {
|
|
382
|
+
this._flowDistortionB = value;
|
|
383
|
+
}
|
|
384
|
+
set flowScale(value) {
|
|
385
|
+
this._flowScale = value;
|
|
386
|
+
}
|
|
387
|
+
set flowEase(value) {
|
|
388
|
+
this._flowEase = value;
|
|
389
|
+
}
|
|
390
|
+
set flowEnabled(value) {
|
|
391
|
+
this._flowEnabled = value;
|
|
392
|
+
}
|
|
393
|
+
get flowEnabled() {
|
|
394
|
+
return this._flowEnabled;
|
|
395
|
+
}
|
|
396
|
+
set mouseDistortionStrength(value) {
|
|
397
|
+
this._mouseDistortionStrength = Math.max(0, value);
|
|
398
|
+
}
|
|
399
|
+
set mouseDistortionRadius(value) {
|
|
400
|
+
// Clamp to a sane range in UV space
|
|
401
|
+
this._mouseDistortionRadius = Math.max(0.01, Math.min(value, 1.0));
|
|
402
|
+
// Update brush scale when radius changes
|
|
403
|
+
this._updateBrushScale();
|
|
404
|
+
}
|
|
405
|
+
_updateBrushScale() {
|
|
406
|
+
if (!this._mouseObjects || this._mouseObjects.length === 0)
|
|
407
|
+
return;
|
|
408
|
+
// Radius directly controls the brush scale
|
|
409
|
+
// Base geometry is 200px, so radius 0.25 = 50px, radius 1.0 = 200px
|
|
410
|
+
this._mouseBrushBaseScale = this._mouseDistortionRadius;
|
|
411
|
+
}
|
|
412
|
+
set mouseDecayRate(value) {
|
|
413
|
+
// Clamp between 0.9 (slow decay, more wobble) and 0.99 (fast decay, less wobble)
|
|
414
|
+
this._mouseDecayRate = Math.max(0.9, Math.min(value, 0.99));
|
|
415
|
+
}
|
|
416
|
+
set mouseDarken(value) {
|
|
417
|
+
this._mouseDarken = value;
|
|
418
|
+
}
|
|
419
|
+
set enableProceduralTexture(value) {
|
|
420
|
+
this._enableProceduralTexture = value;
|
|
421
|
+
if (value && !this._proceduralTexture) {
|
|
422
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
set textureVoidLikelihood(value) {
|
|
426
|
+
this._textureVoidLikelihood = value;
|
|
427
|
+
if (this._enableProceduralTexture) {
|
|
428
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
set textureVoidWidthMin(value) {
|
|
432
|
+
this._textureVoidWidthMin = value;
|
|
433
|
+
if (this._enableProceduralTexture) {
|
|
434
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
set textureVoidWidthMax(value) {
|
|
438
|
+
this._textureVoidWidthMax = value;
|
|
439
|
+
if (this._enableProceduralTexture) {
|
|
440
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
set textureBandDensity(value) {
|
|
444
|
+
this._textureBandDensity = value;
|
|
445
|
+
if (this._enableProceduralTexture) {
|
|
446
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
set textureColorBlending(value) {
|
|
450
|
+
this._textureColorBlending = value;
|
|
451
|
+
if (this._enableProceduralTexture) {
|
|
452
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
set textureSeed(value) {
|
|
456
|
+
this._textureSeed = value;
|
|
457
|
+
if (this._enableProceduralTexture) {
|
|
458
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
get textureEase() {
|
|
462
|
+
return this._textureEase;
|
|
463
|
+
}
|
|
464
|
+
set textureEase(value) {
|
|
465
|
+
this._textureEase = value;
|
|
466
|
+
}
|
|
467
|
+
set proceduralBackgroundColor(value) {
|
|
468
|
+
this._proceduralBackgroundColor = value;
|
|
469
|
+
if (this._enableProceduralTexture) {
|
|
470
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
set textureShapeTriangles(value) {
|
|
474
|
+
this._textureShapeTriangles = value;
|
|
475
|
+
if (this._enableProceduralTexture)
|
|
476
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
477
|
+
}
|
|
478
|
+
set textureShapeCircles(value) {
|
|
479
|
+
this._textureShapeCircles = value;
|
|
480
|
+
if (this._enableProceduralTexture)
|
|
481
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
482
|
+
}
|
|
483
|
+
set textureShapeBars(value) {
|
|
484
|
+
this._textureShapeBars = value;
|
|
485
|
+
if (this._enableProceduralTexture)
|
|
486
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
487
|
+
}
|
|
488
|
+
set textureShapeSquiggles(value) {
|
|
489
|
+
this._textureShapeSquiggles = value;
|
|
490
|
+
if (this._enableProceduralTexture)
|
|
491
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
492
|
+
}
|
|
200
493
|
_initScene(resolution) {
|
|
201
494
|
const width = this._ref.width, height = this._ref.height;
|
|
495
|
+
// Cleanup existing renderer if needed
|
|
496
|
+
if (this.sceneState && this.sceneState.renderer) {
|
|
497
|
+
this.sceneState.renderer.dispose();
|
|
498
|
+
this.sceneState.meshes.forEach(m => {
|
|
499
|
+
m.geometry.dispose();
|
|
500
|
+
if (Array.isArray(m.material))
|
|
501
|
+
m.material.forEach(mat => mat.dispose());
|
|
502
|
+
else
|
|
503
|
+
m.material.dispose();
|
|
504
|
+
});
|
|
505
|
+
}
|
|
202
506
|
const renderer = new THREE.WebGLRenderer({
|
|
203
507
|
// antialias: true,
|
|
204
508
|
alpha: true,
|
|
509
|
+
preserveDrawingBuffer: true,
|
|
205
510
|
canvas: this._ref
|
|
206
511
|
});
|
|
207
512
|
renderer.setClearColor(0xFF0000, .5);
|
|
@@ -228,17 +533,13 @@ export class NeatGradient {
|
|
|
228
533
|
};
|
|
229
534
|
}
|
|
230
535
|
_buildMaterial(width, height) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
is_active: false,
|
|
239
|
-
color: new THREE.Color(0x000000)
|
|
240
|
-
}))
|
|
241
|
-
];
|
|
536
|
+
// Initialize stable array structure for colors
|
|
537
|
+
// We create 6 objects and just update them in the render loop to avoid GC
|
|
538
|
+
const colors = Array.from({ length: COLORS_COUNT }).map((_, i) => ({
|
|
539
|
+
is_active: i < this._colors.length ? (this._colors[i].enabled ? 1.0 : 0.0) : 0.0,
|
|
540
|
+
color: new THREE.Color(i < this._colors.length ? this._colors[i].color : 0x000000),
|
|
541
|
+
influence: i < this._colors.length ? (this._colors[i].influence || 0) : 0
|
|
542
|
+
}));
|
|
242
543
|
const uniforms = {
|
|
243
544
|
u_time: { value: 0 },
|
|
244
545
|
u_color_pressure: { value: new THREE.Vector2(this._horizontalPressure, this._verticalPressure) },
|
|
@@ -253,17 +554,275 @@ export class NeatGradient {
|
|
|
253
554
|
u_shadows: { value: this._shadows },
|
|
254
555
|
u_highlights: { value: this._highlights },
|
|
255
556
|
u_grain_intensity: { value: this._grainIntensity },
|
|
557
|
+
u_grain_sparsity: { value: this._grainSparsity },
|
|
256
558
|
u_grain_scale: { value: this._grainScale },
|
|
257
559
|
u_grain_speed: { value: this._grainSpeed },
|
|
560
|
+
// Flow field
|
|
561
|
+
u_flow_distortion_a: { value: this._flowDistortionA },
|
|
562
|
+
u_flow_distortion_b: { value: this._flowDistortionB },
|
|
563
|
+
u_flow_scale: { value: this._flowScale },
|
|
564
|
+
u_flow_ease: { value: this._flowEase },
|
|
565
|
+
u_flow_enabled: { value: this._flowEnabled ? 1.0 : 0.0 },
|
|
566
|
+
// Y offset multipliers
|
|
567
|
+
u_y_offset: { value: this._yOffset },
|
|
568
|
+
u_y_offset_wave_multiplier: { value: this._yOffsetWaveMultiplier },
|
|
569
|
+
u_y_offset_color_multiplier: { value: this._yOffsetColorMultiplier },
|
|
570
|
+
u_y_offset_flow_multiplier: { value: this._yOffsetFlowMultiplier },
|
|
571
|
+
// Mouse interaction
|
|
572
|
+
u_mouse_distortion_strength: { value: this._mouseDistortionStrength },
|
|
573
|
+
u_mouse_distortion_radius: { value: this._mouseDistortionRadius },
|
|
574
|
+
u_mouse_darken: { value: this._mouseDarken },
|
|
575
|
+
u_mouse_texture: { value: this._mouseFBO ? this._mouseFBO.texture : null },
|
|
576
|
+
// Procedural texture
|
|
577
|
+
u_procedural_texture: { value: this._proceduralTexture },
|
|
578
|
+
u_enable_procedural_texture: { value: this._enableProceduralTexture ? 1.0 : 0.0 },
|
|
579
|
+
u_texture_ease: { value: this._textureEase },
|
|
580
|
+
u_saturation: { value: this._saturation },
|
|
581
|
+
u_brightness: { value: this._brightness },
|
|
582
|
+
u_color_blending: { value: this._colorBlending }
|
|
258
583
|
};
|
|
259
584
|
const material = new THREE.ShaderMaterial({
|
|
260
585
|
uniforms: uniforms,
|
|
261
586
|
vertexShader: buildUniforms() + buildNoise() + buildColorFunctions() + buildVertexShader(),
|
|
262
587
|
fragmentShader: buildUniforms() + buildColorFunctions() + buildNoise() + buildFragmentShader()
|
|
263
588
|
});
|
|
589
|
+
// Cache the uniforms object for direct access in render loop
|
|
590
|
+
this._cachedUniforms = uniforms;
|
|
264
591
|
material.wireframe = WIREFRAME;
|
|
265
592
|
return material;
|
|
266
593
|
}
|
|
594
|
+
_setupMouseInteraction() {
|
|
595
|
+
if (!this._ref)
|
|
596
|
+
return;
|
|
597
|
+
const width = this._ref.width;
|
|
598
|
+
const height = this._ref.height;
|
|
599
|
+
// Create mouse FBO
|
|
600
|
+
this._mouseFBO = new THREE.WebGLRenderTarget(width / 2, height / 2);
|
|
601
|
+
// Create mouse scene and camera
|
|
602
|
+
this._sceneMouse = new THREE.Scene();
|
|
603
|
+
const fSize = height / 2;
|
|
604
|
+
const aspect = width / height;
|
|
605
|
+
// FIX 4: Ensure near plane allows viewing objects at Z=0
|
|
606
|
+
// Near -100 is safer for objects at 0
|
|
607
|
+
this._cameraMouse = new THREE.OrthographicCamera(-fSize * aspect, fSize * aspect, fSize, -fSize, 0, 10000);
|
|
608
|
+
this._cameraMouse.position.set(0, 0, 100);
|
|
609
|
+
// Create brush texture - More visible and impactful
|
|
610
|
+
const brushCanvas = document.createElement('canvas');
|
|
611
|
+
brushCanvas.width = 128;
|
|
612
|
+
brushCanvas.height = 128;
|
|
613
|
+
const bCtx = brushCanvas.getContext('2d');
|
|
614
|
+
if (bCtx) {
|
|
615
|
+
const grd = bCtx.createRadialGradient(64, 64, 0, 64, 64, 64);
|
|
616
|
+
// Match reference implementation's stronger gradient
|
|
617
|
+
grd.addColorStop(0, 'rgba(255,255,255,0.8)');
|
|
618
|
+
grd.addColorStop(0.5, 'rgba(255,255,255,0.4)');
|
|
619
|
+
grd.addColorStop(1, 'rgba(255,255,255,0)');
|
|
620
|
+
bCtx.fillStyle = grd;
|
|
621
|
+
bCtx.fillRect(0, 0, 128, 128);
|
|
622
|
+
}
|
|
623
|
+
const brushTex = new THREE.CanvasTexture(brushCanvas);
|
|
624
|
+
const brushMat = new THREE.MeshBasicMaterial({
|
|
625
|
+
map: brushTex,
|
|
626
|
+
transparent: true,
|
|
627
|
+
opacity: 1.0,
|
|
628
|
+
depthTest: false,
|
|
629
|
+
blending: THREE.AdditiveBlending // Additive blending for better accumulation
|
|
630
|
+
});
|
|
631
|
+
// Brush geometry size - will be scaled by radius parameter
|
|
632
|
+
const brushGeo = new THREE.PlaneGeometry(200, 200);
|
|
633
|
+
// Create brush pool
|
|
634
|
+
const brushPoolSize = 50;
|
|
635
|
+
for (let i = 0; i < brushPoolSize; i++) {
|
|
636
|
+
const m = new THREE.Mesh(brushGeo, brushMat.clone());
|
|
637
|
+
m.visible = false;
|
|
638
|
+
this._sceneMouse.add(m);
|
|
639
|
+
this._mouseObjects.push({ mesh: m, active: false });
|
|
640
|
+
}
|
|
641
|
+
// Initialize brush scale based on current radius
|
|
642
|
+
this._updateBrushScale();
|
|
643
|
+
// Add mouse move listener
|
|
644
|
+
this._ref.addEventListener('mousemove', this._onMouseMove.bind(this));
|
|
645
|
+
}
|
|
646
|
+
_onMouseMove(e) {
|
|
647
|
+
if (!this._ref || !this._sceneMouse)
|
|
648
|
+
return;
|
|
649
|
+
const rect = this._ref.getBoundingClientRect();
|
|
650
|
+
const width = this._ref.width;
|
|
651
|
+
const height = this._ref.height;
|
|
652
|
+
this._mouse.x = e.clientX - rect.left - width / 2;
|
|
653
|
+
this._mouse.y = -(e.clientY - rect.top - height / 2);
|
|
654
|
+
const brush = this._mouseObjects[this._currentBrush];
|
|
655
|
+
brush.mesh.scale.set(this._mouseBrushBaseScale, this._mouseBrushBaseScale, 1.0);
|
|
656
|
+
brush.active = true;
|
|
657
|
+
brush.mesh.visible = true;
|
|
658
|
+
brush.mesh.position.set(this._mouse.x, this._mouse.y, 0);
|
|
659
|
+
brush.mesh.rotation.z = Math.random() * Math.PI * 2;
|
|
660
|
+
if (brush.mesh.material instanceof THREE.MeshBasicMaterial) {
|
|
661
|
+
brush.mesh.material.opacity = 1.0;
|
|
662
|
+
}
|
|
663
|
+
this._currentBrush = (this._currentBrush + 1) % this._mouseObjects.length;
|
|
664
|
+
}
|
|
665
|
+
_createProceduralTexture() {
|
|
666
|
+
// Texture size - 1024 provides good balance between quality and performance
|
|
667
|
+
// Can be increased to 2048 for even better quality if needed
|
|
668
|
+
const texSize = 1024;
|
|
669
|
+
const sourceCanvas = document.createElement('canvas');
|
|
670
|
+
sourceCanvas.width = texSize;
|
|
671
|
+
sourceCanvas.height = texSize;
|
|
672
|
+
const sCtx = sourceCanvas.getContext('2d');
|
|
673
|
+
if (!sCtx)
|
|
674
|
+
return new THREE.Texture();
|
|
675
|
+
let seed = this._textureSeed;
|
|
676
|
+
const baseSeed = this._textureSeed;
|
|
677
|
+
function random() {
|
|
678
|
+
const x = Math.sin(seed++) * 10000;
|
|
679
|
+
return x - Math.floor(x);
|
|
680
|
+
}
|
|
681
|
+
// Helper to reset seed for isolated shape generation
|
|
682
|
+
const setSeed = (offset) => {
|
|
683
|
+
seed = baseSeed + offset;
|
|
684
|
+
};
|
|
685
|
+
const colors = this._colors.filter(c => c.enabled).map(c => c.color);
|
|
686
|
+
if (colors.length === 0)
|
|
687
|
+
return new THREE.Texture();
|
|
688
|
+
// Helper functions
|
|
689
|
+
function hexToRgb(hex) {
|
|
690
|
+
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
691
|
+
return {
|
|
692
|
+
r: (bigint >> 16) & 255,
|
|
693
|
+
g: (bigint >> 8) & 255,
|
|
694
|
+
b: bigint & 255
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function rgbToHex(r, g, b) {
|
|
698
|
+
return "#" + ((1 << 24) + (Math.round(r) << 16) + (Math.round(g) << 8) + Math.round(b)).toString(16).slice(1);
|
|
699
|
+
}
|
|
700
|
+
const getInterColor = () => {
|
|
701
|
+
const c1 = colors[Math.floor(random() * colors.length)];
|
|
702
|
+
const c2 = colors[Math.floor(random() * colors.length)];
|
|
703
|
+
const mix = random() * this._textureColorBlending;
|
|
704
|
+
const rgb1 = hexToRgb(c1);
|
|
705
|
+
const rgb2 = hexToRgb(c2);
|
|
706
|
+
const r = rgb1.r + (rgb2.r - rgb1.r) * mix;
|
|
707
|
+
const g = rgb1.g + (rgb2.g - rgb1.g) * mix;
|
|
708
|
+
const b = rgb1.b + (rgb2.b - rgb1.b) * mix;
|
|
709
|
+
return rgbToHex(r, g, b);
|
|
710
|
+
};
|
|
711
|
+
// === SOURCE CANVAS ===
|
|
712
|
+
// Base with procedural background color so even sparse areas pick it up
|
|
713
|
+
const baseColor = this._proceduralBackgroundColor || "#000000";
|
|
714
|
+
sCtx.fillStyle = baseColor;
|
|
715
|
+
sCtx.fillRect(0, 0, texSize, texSize);
|
|
716
|
+
// Then lay a vertical gradient of mixed colors on top for richness
|
|
717
|
+
const bgGrad = sCtx.createLinearGradient(0, 0, 0, texSize);
|
|
718
|
+
bgGrad.addColorStop(0, getInterColor());
|
|
719
|
+
bgGrad.addColorStop(1, getInterColor());
|
|
720
|
+
sCtx.fillStyle = bgGrad;
|
|
721
|
+
sCtx.fillRect(0, 0, texSize, texSize);
|
|
722
|
+
// Triangles: use configurable count
|
|
723
|
+
for (let i = 0; i < this._textureShapeTriangles; i++) {
|
|
724
|
+
sCtx.fillStyle = getInterColor();
|
|
725
|
+
sCtx.beginPath();
|
|
726
|
+
const x = random() * texSize;
|
|
727
|
+
const y = random() * texSize;
|
|
728
|
+
const s = 100 + random() * 300;
|
|
729
|
+
sCtx.moveTo(x, y);
|
|
730
|
+
sCtx.lineTo(x + (random() - 0.5) * s, y + (random() - 0.5) * s);
|
|
731
|
+
sCtx.lineTo(x + (random() - 0.5) * s, y + (random() - 0.5) * s);
|
|
732
|
+
sCtx.fill();
|
|
733
|
+
}
|
|
734
|
+
// Circles / rings: use configurable count
|
|
735
|
+
for (let i = 0; i < this._textureShapeCircles; i++) {
|
|
736
|
+
sCtx.strokeStyle = getInterColor();
|
|
737
|
+
sCtx.lineWidth = 10 + random() * 50;
|
|
738
|
+
sCtx.beginPath();
|
|
739
|
+
const x = random() * texSize;
|
|
740
|
+
const y = random() * texSize;
|
|
741
|
+
const r = 50 + random() * 150;
|
|
742
|
+
sCtx.arc(x, y, r, 0, Math.PI * 2);
|
|
743
|
+
sCtx.stroke();
|
|
744
|
+
}
|
|
745
|
+
// Bars: use configurable count
|
|
746
|
+
for (let i = 0; i < this._textureShapeBars; i++) {
|
|
747
|
+
sCtx.fillStyle = getInterColor();
|
|
748
|
+
sCtx.save();
|
|
749
|
+
sCtx.translate(random() * texSize, random() * texSize);
|
|
750
|
+
sCtx.rotate(random() * Math.PI);
|
|
751
|
+
sCtx.fillRect(-150, -25, 300, 50);
|
|
752
|
+
sCtx.restore();
|
|
753
|
+
}
|
|
754
|
+
// Squiggles: use configurable count
|
|
755
|
+
sCtx.lineWidth = 15;
|
|
756
|
+
sCtx.lineCap = 'round';
|
|
757
|
+
for (let i = 0; i < this._textureShapeSquiggles; i++) {
|
|
758
|
+
sCtx.strokeStyle = getInterColor();
|
|
759
|
+
sCtx.beginPath();
|
|
760
|
+
let x = random() * texSize;
|
|
761
|
+
let y = random() * texSize;
|
|
762
|
+
sCtx.moveTo(x, y);
|
|
763
|
+
for (let j = 0; j < 4; j++) {
|
|
764
|
+
sCtx.bezierCurveTo(x + (random() - 0.5) * 300, y + (random() - 0.5) * 300, x + (random() - 0.5) * 300, y + (random() - 0.5) * 300, x + (random() - 0.5) * 300, y + (random() - 0.5) * 300);
|
|
765
|
+
x += (random() - 0.5) * 300;
|
|
766
|
+
y += (random() - 0.5) * 300;
|
|
767
|
+
}
|
|
768
|
+
sCtx.stroke();
|
|
769
|
+
}
|
|
770
|
+
// === MASKED CANVAS ===
|
|
771
|
+
// Masking: Seed isolation
|
|
772
|
+
setSeed(50000);
|
|
773
|
+
const canvas = document.createElement('canvas');
|
|
774
|
+
canvas.width = texSize;
|
|
775
|
+
canvas.height = texSize;
|
|
776
|
+
const ctx = canvas.getContext('2d');
|
|
777
|
+
if (!ctx)
|
|
778
|
+
return new THREE.Texture();
|
|
779
|
+
// Start filled with the chosen void color so gaps show that color
|
|
780
|
+
ctx.fillStyle = baseColor;
|
|
781
|
+
ctx.fillRect(0, 0, texSize, texSize);
|
|
782
|
+
// Determine layout segments (matter vs void)
|
|
783
|
+
let layoutHead = 0;
|
|
784
|
+
const segments = [];
|
|
785
|
+
while (layoutHead < texSize) {
|
|
786
|
+
const isVoid = random() < this._textureVoidLikelihood;
|
|
787
|
+
if (isVoid) {
|
|
788
|
+
const w = this._textureVoidWidthMin + random() * (this._textureVoidWidthMax - this._textureVoidWidthMin);
|
|
789
|
+
segments.push({ type: 'void', x: layoutHead, width: w });
|
|
790
|
+
layoutHead += w;
|
|
791
|
+
}
|
|
792
|
+
else {
|
|
793
|
+
const w = 50 + random() * 200;
|
|
794
|
+
segments.push({ type: 'matter', x: layoutHead, width: w });
|
|
795
|
+
layoutHead += w;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
// Render only matter bands from the source into the masked canvas
|
|
799
|
+
for (const seg of segments) {
|
|
800
|
+
if (seg.type === 'matter') {
|
|
801
|
+
const startX = seg.x;
|
|
802
|
+
const endX = Math.min(seg.x + seg.width, texSize);
|
|
803
|
+
let currentX = startX;
|
|
804
|
+
while (currentX < endX) {
|
|
805
|
+
const stripeWidth = (2 + random() * 20) / this._textureBandDensity;
|
|
806
|
+
const sourceX = Math.floor(random() * texSize);
|
|
807
|
+
ctx.drawImage(sourceCanvas, sourceX, 0, stripeWidth, texSize, currentX, 0, stripeWidth, texSize);
|
|
808
|
+
currentX += stripeWidth;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
// void segments: leave as baseColor
|
|
812
|
+
}
|
|
813
|
+
const tex = new THREE.CanvasTexture(canvas);
|
|
814
|
+
// Use mipmapping for better quality when texture is scaled
|
|
815
|
+
tex.minFilter = THREE.LinearMipmapLinearFilter;
|
|
816
|
+
tex.magFilter = THREE.LinearFilter;
|
|
817
|
+
tex.wrapS = THREE.RepeatWrapping;
|
|
818
|
+
tex.wrapT = THREE.RepeatWrapping;
|
|
819
|
+
// Enable anisotropic filtering for much better quality when texture is stretched
|
|
820
|
+
// 16 is a commonly supported value that dramatically improves quality
|
|
821
|
+
tex.anisotropy = 16;
|
|
822
|
+
// Ensure mipmaps are generated
|
|
823
|
+
tex.needsUpdate = true;
|
|
824
|
+
return tex;
|
|
825
|
+
}
|
|
267
826
|
}
|
|
268
827
|
function updateCamera(camera, width, height) {
|
|
269
828
|
const viewPortAreaRatio = 1000000;
|
|
@@ -295,36 +854,62 @@ function updateCamera(camera, width, height) {
|
|
|
295
854
|
}
|
|
296
855
|
function buildVertexShader() {
|
|
297
856
|
return `
|
|
298
|
-
|
|
299
857
|
void main() {
|
|
300
|
-
|
|
301
858
|
vUv = uv;
|
|
302
859
|
|
|
860
|
+
// SCROLLING LOGIC
|
|
861
|
+
// Separate multipliers for wave, color, and flow offsets
|
|
862
|
+
float waveOffset = -u_y_offset * u_y_offset_wave_multiplier;
|
|
863
|
+
float colorOffset = -u_y_offset * u_y_offset_color_multiplier;
|
|
864
|
+
float flowOffset = -u_y_offset * u_y_offset_flow_multiplier;
|
|
865
|
+
|
|
866
|
+
// 1. DISPLACEMENT (WAVES)
|
|
867
|
+
// We add waveOffset to Y to scroll the wave pattern
|
|
303
868
|
v_displacement_amount = cnoise( vec3(
|
|
304
869
|
u_wave_frequency_x * position.x + u_time,
|
|
305
|
-
u_wave_frequency_y * position.y + u_time,
|
|
870
|
+
u_wave_frequency_y * (position.y + waveOffset) + u_time,
|
|
306
871
|
u_time
|
|
307
872
|
));
|
|
308
|
-
|
|
309
|
-
vec3 color;
|
|
310
873
|
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
874
|
+
// 2. FLOW FIELD
|
|
875
|
+
// Apply flow offset to scroll the flow field mask
|
|
876
|
+
vec2 baseUv = vUv;
|
|
877
|
+
baseUv.y += flowOffset / u_plane_height; // Scale to match wave speed
|
|
878
|
+
vec2 flowUv = baseUv;
|
|
879
|
+
|
|
880
|
+
if (u_flow_enabled > 0.5) {
|
|
881
|
+
if (u_flow_ease > 0.0 || u_flow_distortion_a > 0.0) {
|
|
882
|
+
vec2 ppp = -1.0 + 2.0 * baseUv;
|
|
883
|
+
ppp += 0.1 * cos((1.5 * u_flow_scale) * ppp.yx + 1.1 * u_time + vec2(0.1, 1.1));
|
|
884
|
+
ppp += 0.1 * cos((2.3 * u_flow_scale) * ppp.yx + 1.3 * u_time + vec2(3.2, 3.4));
|
|
885
|
+
ppp += 0.1 * cos((2.2 * u_flow_scale) * ppp.yx + 1.7 * u_time + vec2(1.8, 5.2));
|
|
886
|
+
ppp += u_flow_distortion_a * cos((u_flow_distortion_b * u_flow_scale) * ppp.yx + 1.4 * u_time + vec2(6.3, 3.9));
|
|
887
|
+
|
|
888
|
+
float r = length(ppp);
|
|
889
|
+
flowUv = mix(baseUv, vec2(baseUv.x * (1.0 - u_flow_ease) + r * u_flow_ease, baseUv.y), u_flow_ease);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Pass the standard flow UV to fragment shader (for mouse/texture)
|
|
894
|
+
vFlowUv = flowUv;
|
|
895
|
+
|
|
896
|
+
// 3. COLOR MIXING
|
|
897
|
+
// We take the computed flow UVs and apply the color offset
|
|
898
|
+
// Scale by plane height to match wave offset speed (world space vs UV space)
|
|
899
|
+
vec3 color = u_colors[0].color;
|
|
900
|
+
vec2 adjustedUv = flowUv;
|
|
901
|
+
adjustedUv.y += colorOffset / u_plane_height; // Scroll the color mixing pattern
|
|
902
|
+
|
|
903
|
+
vec2 noise_cord = adjustedUv * u_color_pressure;
|
|
316
904
|
const float minNoise = .0;
|
|
317
905
|
const float maxNoise = .9;
|
|
318
|
-
|
|
906
|
+
|
|
319
907
|
for (int i = 1; i < u_colors_count; i++) {
|
|
320
|
-
|
|
321
|
-
if(u_colors[i].is_active == 1.0){
|
|
908
|
+
if(u_colors[i].is_active > 0.5){
|
|
322
909
|
float noiseFlow = (1. + float(i)) / 30.;
|
|
323
910
|
float noiseSpeed = (1. + float(i)) * 0.11;
|
|
324
911
|
float noiseSeed = 13. + float(i) * 7.;
|
|
325
|
-
|
|
326
|
-
int reverseIndex = u_colors_count - i;
|
|
327
|
-
|
|
912
|
+
|
|
328
913
|
float noise = snoise(
|
|
329
914
|
vec3(
|
|
330
915
|
noise_cord.x * u_color_pressure.x + u_time * noiseFlow * 2.,
|
|
@@ -332,25 +917,17 @@ void main() {
|
|
|
332
917
|
u_time * noiseSpeed
|
|
333
918
|
) + noiseSeed
|
|
334
919
|
) - (.1 * float(i)) + (.5 * u_color_blending);
|
|
335
|
-
|
|
920
|
+
|
|
336
921
|
noise = clamp(minNoise, maxNoise + float(i) * 0.02, noise);
|
|
337
|
-
|
|
338
|
-
color = mix(color, nextColor, smoothstep(0.0, u_color_blending, noise));
|
|
339
|
-
|
|
340
|
-
// vec3 colorOklab = oklab2rgb(color);
|
|
341
|
-
// vec3 nextColorOklab = oklab2rgb(nextColor);
|
|
342
|
-
// vec3 mixColor = mix(colorOklab, nextColorOklab, smoothstep(0.0, u_color_blending, noise));
|
|
343
|
-
// color = rgb2oklab(mixColor);
|
|
344
|
-
|
|
922
|
+
color = mix(color, u_colors[i].color, smoothstep(0.0, u_color_blending, noise));
|
|
345
923
|
}
|
|
346
|
-
|
|
347
924
|
}
|
|
348
|
-
|
|
925
|
+
|
|
349
926
|
v_color = color;
|
|
350
|
-
|
|
927
|
+
|
|
928
|
+
// 4. VERTEX POSITION
|
|
351
929
|
vec3 newPosition = position + normal * v_displacement_amount * u_wave_amplitude;
|
|
352
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(
|
|
353
|
-
|
|
930
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
|
|
354
931
|
v_new_position = gl_Position;
|
|
355
932
|
}
|
|
356
933
|
`;
|
|
@@ -365,7 +942,6 @@ float fbm(vec3 x) {
|
|
|
365
942
|
float value = 0.0;
|
|
366
943
|
float amplitude = 0.5;
|
|
367
944
|
float frequency = 1.0;
|
|
368
|
-
|
|
369
945
|
for (int i = 0; i < 4; i++) {
|
|
370
946
|
value += amplitude * snoise(x * frequency);
|
|
371
947
|
frequency *= 2.0;
|
|
@@ -373,35 +949,73 @@ float fbm(vec3 x) {
|
|
|
373
949
|
}
|
|
374
950
|
return value;
|
|
375
951
|
}
|
|
952
|
+
|
|
953
|
+
void main() {
|
|
954
|
+
// MOUSE DISTORTION
|
|
955
|
+
vec2 finalUv = vFlowUv;
|
|
376
956
|
|
|
377
|
-
|
|
378
|
-
|
|
957
|
+
if (u_mouse_distortion_strength > 0.0) {
|
|
958
|
+
vec4 mouseColor = texture2D(u_mouse_texture, vUv);
|
|
959
|
+
float mouseValue = mouseColor.r;
|
|
960
|
+
|
|
961
|
+
if (mouseValue > 0.001) {
|
|
962
|
+
float distortionAmount = mouseValue * u_mouse_distortion_strength;
|
|
963
|
+
vec2 mouseDisp = vec2(distortionAmount, distortionAmount);
|
|
964
|
+
finalUv -= mouseDisp;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
379
967
|
|
|
968
|
+
vec3 baseColor;
|
|
969
|
+
|
|
970
|
+
if (u_enable_procedural_texture > 0.5) {
|
|
971
|
+
// Calculate flow field distance for ease effect
|
|
972
|
+
vec2 ppp = -1.0 + 2.0 * finalUv;
|
|
973
|
+
ppp += 0.1 * cos((1.5 * u_flow_scale) * ppp.yx + 1.1 * u_time + vec2(0.1, 1.1));
|
|
974
|
+
ppp += 0.1 * cos((2.3 * u_flow_scale) * ppp.yx + 1.3 * u_time + vec2(3.2, 3.4));
|
|
975
|
+
ppp += 0.1 * cos((2.2 * u_flow_scale) * ppp.yx + 1.7 * u_time + vec2(1.8, 5.2));
|
|
976
|
+
ppp += u_flow_distortion_a * cos((u_flow_distortion_b * u_flow_scale) * ppp.yx + 1.4 * u_time + vec2(6.3, 3.9));
|
|
977
|
+
float r = length(ppp); // Flow distance
|
|
978
|
+
|
|
979
|
+
// Ease blending: 0 = topographic (flow), 1 = image (UV)
|
|
980
|
+
float vx = (finalUv.x * u_texture_ease) + (r * (1.0 - u_texture_ease));
|
|
981
|
+
float vy = (finalUv.y * u_texture_ease) + (0.0 * (1.0 - u_texture_ease));
|
|
982
|
+
vec2 texUv = vec2(vx, vy);
|
|
983
|
+
|
|
984
|
+
// PARALLAX SCROLLING
|
|
985
|
+
// We manually apply a smaller offset here to make the texture lag behind
|
|
986
|
+
float parallaxFactor = 0.25; // 25% speed of the color mixing
|
|
987
|
+
texUv.y -= (u_y_offset * u_y_offset_color_multiplier / u_plane_height) * parallaxFactor;
|
|
988
|
+
|
|
989
|
+
texUv *= 1.5; // Tiling scale
|
|
990
|
+
|
|
991
|
+
vec4 texSample = texture2D(u_procedural_texture, texUv);
|
|
992
|
+
baseColor = texSample.rgb;
|
|
993
|
+
} else {
|
|
994
|
+
baseColor = v_color;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
vec3 color = baseColor;
|
|
998
|
+
|
|
999
|
+
// Post-processing
|
|
380
1000
|
color += pow(v_displacement_amount, 1.0) * u_highlights;
|
|
381
1001
|
color -= pow(1.0 - v_displacement_amount, 2.0) * u_shadows;
|
|
382
1002
|
color = saturation(color, 1.0 + u_saturation);
|
|
383
1003
|
color = color * u_brightness;
|
|
384
|
-
|
|
385
|
-
// Generate grain using fbm
|
|
386
|
-
vec2 noiseCoords = gl_FragCoord.xy / u_grain_scale;
|
|
387
1004
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
1005
|
+
// Grain
|
|
1006
|
+
vec2 noiseCoords = gl_FragCoord.xy / u_grain_scale;
|
|
1007
|
+
float grain = (u_grain_speed != 0.0) ? fbm(vec3(noiseCoords, u_time * u_grain_speed)) : fbm(vec3(noiseCoords, 0.0));
|
|
391
1008
|
|
|
392
|
-
// Center the grain around zero
|
|
393
1009
|
grain = grain * 0.5 + 0.5;
|
|
394
1010
|
grain -= 0.5;
|
|
395
|
-
|
|
396
|
-
// Apply grain intensity
|
|
1011
|
+
grain = (grain > u_grain_sparsity) ? grain : 0.0;
|
|
397
1012
|
grain *= u_grain_intensity;
|
|
398
1013
|
|
|
399
|
-
// Add grain to color
|
|
400
1014
|
color += vec3(grain);
|
|
401
|
-
|
|
402
|
-
gl_FragColor = vec4(color,1.0);
|
|
1015
|
+
|
|
1016
|
+
gl_FragColor = vec4(color, 1.0);
|
|
403
1017
|
}
|
|
404
|
-
`;
|
|
1018
|
+
`;
|
|
405
1019
|
}
|
|
406
1020
|
const buildUniforms = () => `
|
|
407
1021
|
precision highp float;
|
|
@@ -413,6 +1027,7 @@ struct Color {
|
|
|
413
1027
|
};
|
|
414
1028
|
|
|
415
1029
|
uniform float u_grain_intensity;
|
|
1030
|
+
uniform float u_grain_sparsity;
|
|
416
1031
|
uniform float u_grain_scale;
|
|
417
1032
|
uniform float u_grain_speed;
|
|
418
1033
|
uniform float u_time;
|
|
@@ -434,10 +1049,34 @@ uniform float u_brightness;
|
|
|
434
1049
|
uniform float u_color_blending;
|
|
435
1050
|
|
|
436
1051
|
uniform int u_colors_count;
|
|
437
|
-
uniform Color u_colors[
|
|
1052
|
+
uniform Color u_colors[6];
|
|
438
1053
|
uniform vec2 u_resolution;
|
|
439
1054
|
|
|
1055
|
+
uniform float u_y_offset;
|
|
1056
|
+
uniform float u_y_offset_wave_multiplier;
|
|
1057
|
+
uniform float u_y_offset_color_multiplier;
|
|
1058
|
+
uniform float u_y_offset_flow_multiplier;
|
|
1059
|
+
|
|
1060
|
+
// Flow field uniforms
|
|
1061
|
+
uniform float u_flow_distortion_a;
|
|
1062
|
+
uniform float u_flow_distortion_b;
|
|
1063
|
+
uniform float u_flow_scale;
|
|
1064
|
+
uniform float u_flow_ease;
|
|
1065
|
+
uniform float u_flow_enabled;
|
|
1066
|
+
|
|
1067
|
+
// Mouse interaction uniforms
|
|
1068
|
+
uniform float u_mouse_distortion_strength;
|
|
1069
|
+
uniform float u_mouse_distortion_radius;
|
|
1070
|
+
uniform float u_mouse_darken;
|
|
1071
|
+
uniform sampler2D u_mouse_texture;
|
|
1072
|
+
|
|
1073
|
+
// Procedural texture uniforms
|
|
1074
|
+
uniform sampler2D u_procedural_texture;
|
|
1075
|
+
uniform float u_enable_procedural_texture;
|
|
1076
|
+
uniform float u_texture_ease;
|
|
1077
|
+
|
|
440
1078
|
varying vec2 vUv;
|
|
1079
|
+
varying vec2 vFlowUv;
|
|
441
1080
|
varying vec4 v_new_position;
|
|
442
1081
|
varying vec3 v_color;
|
|
443
1082
|
varying float v_displacement_amount;
|
|
@@ -445,69 +1084,55 @@ varying float v_displacement_amount;
|
|
|
445
1084
|
`;
|
|
446
1085
|
const buildNoise = () => `
|
|
447
1086
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
vec4 mod289(vec4 x)
|
|
454
|
-
{
|
|
455
|
-
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
|
1087
|
+
// 1. REPLACEMENT PERMUTE:
|
|
1088
|
+
// Uses a hash function (fract/sin) instead of a modular lookup table.
|
|
1089
|
+
vec4 permute(vec4 x) {
|
|
1090
|
+
return floor(fract(sin(x) * 43758.5453123) * 289.0);
|
|
456
1091
|
}
|
|
457
1092
|
|
|
458
|
-
|
|
459
|
-
{
|
|
460
|
-
return mod289(((x*34.0)+1.0)*x);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
vec4 taylorInvSqrt(vec4 r)
|
|
464
|
-
{
|
|
1093
|
+
// Taylor Inverse Sqrt
|
|
1094
|
+
vec4 taylorInvSqrt(vec4 r) {
|
|
465
1095
|
return 1.79284291400159 - 0.85373472095314 * r;
|
|
466
1096
|
}
|
|
467
1097
|
|
|
1098
|
+
// Fade function
|
|
468
1099
|
vec3 fade(vec3 t) {
|
|
469
1100
|
return t*t*t*(t*(t*6.0-15.0)+10.0);
|
|
470
1101
|
}
|
|
471
1102
|
|
|
472
|
-
|
|
473
|
-
{
|
|
1103
|
+
// 3D Simplex Noise
|
|
1104
|
+
float snoise(vec3 v) {
|
|
474
1105
|
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
|
|
475
1106
|
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
|
476
1107
|
|
|
477
|
-
// First corner
|
|
1108
|
+
// First corner
|
|
478
1109
|
vec3 i = floor(v + dot(v, C.yyy) );
|
|
479
1110
|
vec3 x0 = v - i + dot(i, C.xxx) ;
|
|
480
1111
|
|
|
481
|
-
// Other corners
|
|
1112
|
+
// Other corners
|
|
482
1113
|
vec3 g = step(x0.yzx, x0.xyz);
|
|
483
1114
|
vec3 l = 1.0 - g;
|
|
484
1115
|
vec3 i1 = min( g.xyz, l.zxy );
|
|
485
1116
|
vec3 i2 = max( g.xyz, l.zxy );
|
|
486
1117
|
|
|
487
|
-
// x0 = x0 - 0.0 + 0.0 * C.xxx;
|
|
488
|
-
// x1 = x0 - i1 + 1.0 * C.xxx;
|
|
489
|
-
// x2 = x0 - i2 + 2.0 * C.xxx;
|
|
490
|
-
// x3 = x0 - 1.0 + 3.0 * C.xxx;
|
|
491
1118
|
vec3 x1 = x0 - i1 + C.xxx;
|
|
492
|
-
vec3 x2 = x0 - i2 + C.yyy;
|
|
493
|
-
vec3 x3 = x0 - D.yyy;
|
|
1119
|
+
vec3 x2 = x0 - i2 + C.yyy;
|
|
1120
|
+
vec3 x3 = x0 - D.yyy;
|
|
494
1121
|
|
|
495
|
-
// Permutations
|
|
496
|
-
i = mod289(i);
|
|
1122
|
+
// Permutations
|
|
497
1123
|
vec4 p = permute( permute( permute(
|
|
498
1124
|
i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
|
|
499
1125
|
+ i.y + vec4(0.0, i1.y, i2.y, 1.0 ))
|
|
500
1126
|
+ i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
|
|
501
1127
|
|
|
502
|
-
// Gradients
|
|
503
|
-
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
|
|
1128
|
+
// Gradients
|
|
504
1129
|
float n_ = 0.142857142857; // 1.0/7.0
|
|
505
1130
|
vec3 ns = n_ * D.wyz - D.xzx;
|
|
506
1131
|
|
|
507
|
-
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
1132
|
+
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
508
1133
|
|
|
509
1134
|
vec4 x_ = floor(j * ns.z);
|
|
510
|
-
vec4 y_ = floor(j - 7.0 * x_ );
|
|
1135
|
+
vec4 y_ = floor(j - 7.0 * x_ );
|
|
511
1136
|
|
|
512
1137
|
vec4 x = x_ *ns.x + ns.yyyy;
|
|
513
1138
|
vec4 y = y_ *ns.x + ns.yyyy;
|
|
@@ -516,8 +1141,6 @@ float snoise(vec3 v)
|
|
|
516
1141
|
vec4 b0 = vec4( x.xy, y.xy );
|
|
517
1142
|
vec4 b1 = vec4( x.zw, y.zw );
|
|
518
1143
|
|
|
519
|
-
//vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
|
|
520
|
-
//vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
|
|
521
1144
|
vec4 s0 = floor(b0)*2.0 + 1.0;
|
|
522
1145
|
vec4 s1 = floor(b1)*2.0 + 1.0;
|
|
523
1146
|
vec4 sh = -step(h, vec4(0.0));
|
|
@@ -530,14 +1153,14 @@ float snoise(vec3 v)
|
|
|
530
1153
|
vec3 p2 = vec3(a1.xy,h.z);
|
|
531
1154
|
vec3 p3 = vec3(a1.zw,h.w);
|
|
532
1155
|
|
|
533
|
-
//Normalise gradients
|
|
1156
|
+
// Normalise gradients
|
|
534
1157
|
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
|
|
535
1158
|
p0 *= norm.x;
|
|
536
1159
|
p1 *= norm.y;
|
|
537
1160
|
p2 *= norm.z;
|
|
538
1161
|
p3 *= norm.w;
|
|
539
1162
|
|
|
540
|
-
// Mix final noise value
|
|
1163
|
+
// Mix final noise value
|
|
541
1164
|
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
|
|
542
1165
|
m = m * m;
|
|
543
1166
|
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
|
|
@@ -547,12 +1170,11 @@ float snoise(vec3 v)
|
|
|
547
1170
|
// Classic Perlin noise
|
|
548
1171
|
float cnoise(vec3 P)
|
|
549
1172
|
{
|
|
550
|
-
vec3 Pi0 = floor(P);
|
|
551
|
-
vec3 Pi1 = Pi0 + vec3(1.0);
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
vec3
|
|
555
|
-
vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
|
|
1173
|
+
vec3 Pi0 = floor(P);
|
|
1174
|
+
vec3 Pi1 = Pi0 + vec3(1.0);
|
|
1175
|
+
|
|
1176
|
+
vec3 Pf0 = fract(P);
|
|
1177
|
+
vec3 Pf1 = Pf0 - vec3(1.0);
|
|
556
1178
|
vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
|
|
557
1179
|
vec4 iy = vec4(Pi0.yy, Pi1.yy);
|
|
558
1180
|
vec4 iz0 = Pi0.zzzz;
|
|
@@ -613,47 +1235,7 @@ float cnoise(vec3 P)
|
|
|
613
1235
|
float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
|
|
614
1236
|
return 2.2 * n_xyz;
|
|
615
1237
|
}
|
|
616
|
-
|
|
617
|
-
// YUV to RGB matrix
|
|
618
|
-
mat3 yuv2rgb = mat3(1.0, 0.0, 1.13983,
|
|
619
|
-
1.0, -0.39465, -0.58060,
|
|
620
|
-
1.0, 2.03211, 0.0);
|
|
621
|
-
|
|
622
|
-
// RGB to YUV matrix
|
|
623
|
-
mat3 rgb2yuv = mat3(0.2126, 0.7152, 0.0722,
|
|
624
|
-
-0.09991, -0.33609, 0.43600,
|
|
625
|
-
0.615, -0.5586, -0.05639);
|
|
626
|
-
|
|
627
|
-
vec3 oklab2rgb(vec3 linear)
|
|
628
|
-
{
|
|
629
|
-
const mat3 im1 = mat3(0.4121656120, 0.2118591070, 0.0883097947,
|
|
630
|
-
0.5362752080, 0.6807189584, 0.2818474174,
|
|
631
|
-
0.0514575653, 0.1074065790, 0.6302613616);
|
|
632
|
-
|
|
633
|
-
const mat3 im2 = mat3(+0.2104542553, +1.9779984951, +0.0259040371,
|
|
634
|
-
+0.7936177850, -2.4285922050, +0.7827717662,
|
|
635
|
-
-0.0040720468, +0.4505937099, -0.8086757660);
|
|
636
|
-
|
|
637
|
-
vec3 lms = im1 * linear;
|
|
638
|
-
|
|
639
|
-
return im2 * (sign(lms) * pow(abs(lms), vec3(1.0/3.0)));
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
vec3 rgb2oklab(vec3 oklab)
|
|
643
|
-
{
|
|
644
|
-
const mat3 m1 = mat3(+1.000000000, +1.000000000, +1.000000000,
|
|
645
|
-
+0.396337777, -0.105561346, -0.089484178,
|
|
646
|
-
+0.215803757, -0.063854173, -1.291485548);
|
|
647
|
-
|
|
648
|
-
const mat3 m2 = mat3(+4.076724529, -1.268143773, -0.004111989,
|
|
649
|
-
-3.307216883, +2.609332323, -0.703476310,
|
|
650
|
-
+0.230759054, -0.341134429, +1.706862569);
|
|
651
|
-
vec3 lms = m1 * oklab;
|
|
652
|
-
|
|
653
|
-
return m2 * (lms * lms * lms);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
`;
|
|
1238
|
+
`;
|
|
657
1239
|
const buildColorFunctions = () => `
|
|
658
1240
|
|
|
659
1241
|
vec3 saturation(vec3 rgb, float adjustment) {
|
|
@@ -722,13 +1304,14 @@ const addNeatLink = (ref) => {
|
|
|
722
1304
|
for (let i = 0; i < existingLinks.length; i++) {
|
|
723
1305
|
if (existingLinks[i].id === LINK_ID) {
|
|
724
1306
|
setLinkStyles(existingLinks[i]);
|
|
725
|
-
return;
|
|
1307
|
+
return existingLinks[i];
|
|
726
1308
|
}
|
|
727
1309
|
}
|
|
728
1310
|
}
|
|
729
1311
|
const link = document.createElement("a");
|
|
730
1312
|
setLinkStyles(link);
|
|
731
1313
|
ref.parentElement?.appendChild(link);
|
|
1314
|
+
return link;
|
|
732
1315
|
};
|
|
733
1316
|
function getElapsedSecondsInLastHour() {
|
|
734
1317
|
const now = new Date();
|
|
@@ -745,4 +1328,12 @@ function generateRandomString(length = 6) {
|
|
|
745
1328
|
}
|
|
746
1329
|
return result;
|
|
747
1330
|
}
|
|
1331
|
+
function downloadURI(uri, name) {
|
|
1332
|
+
const link = document.createElement("a");
|
|
1333
|
+
link.download = name;
|
|
1334
|
+
link.href = uri;
|
|
1335
|
+
document.body.appendChild(link);
|
|
1336
|
+
link.click();
|
|
1337
|
+
document.body.removeChild(link);
|
|
1338
|
+
}
|
|
748
1339
|
//# sourceMappingURL=NeatGradient.js.map
|