@firecms/neat 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/NeatGradient.d.ts +7 -0
- package/dist/NeatGradient.js +166 -73
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.es.js +253 -217
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +6 -6
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/NeatGradient.ts +180 -75
package/src/NeatGradient.ts
CHANGED
|
@@ -180,6 +180,15 @@ export class NeatGradient implements NeatController {
|
|
|
180
180
|
// For saving/restoring clear color
|
|
181
181
|
private _tempClearColor = new THREE.Color();
|
|
182
182
|
|
|
183
|
+
// Performance optimizations
|
|
184
|
+
private _resizeTimeoutId: number | null = null;
|
|
185
|
+
private _textureNeedsUpdate: boolean = false;
|
|
186
|
+
private _lastColorUpdate: number = 0;
|
|
187
|
+
private _linkCheckCounter: number = 0;
|
|
188
|
+
private _mouseUpdateScheduled: boolean = false;
|
|
189
|
+
private _pendingMousePosition: { x: number; y: number } | null = null;
|
|
190
|
+
private _colorsChanged: boolean = true; // Track if colors need update
|
|
191
|
+
|
|
183
192
|
constructor(config: NeatConfig & { ref: HTMLCanvasElement, resolution?: number, seed?: number }) {
|
|
184
193
|
|
|
185
194
|
const {
|
|
@@ -307,8 +316,10 @@ export class NeatGradient implements NeatController {
|
|
|
307
316
|
|
|
308
317
|
const { renderer, camera, scene } = this.sceneState;
|
|
309
318
|
|
|
310
|
-
// Optimization: check if cached link is still valid in DOM
|
|
311
|
-
|
|
319
|
+
// Optimization: check if cached link is still valid in DOM less frequently
|
|
320
|
+
this._linkCheckCounter++;
|
|
321
|
+
if (this._linkCheckCounter >= 300) { // Check every ~5 seconds at 60fps
|
|
322
|
+
this._linkCheckCounter = 0;
|
|
312
323
|
if (!this._linkElement || !document.contains(this._linkElement)) {
|
|
313
324
|
this._linkElement = addNeatLink(ref);
|
|
314
325
|
}
|
|
@@ -350,30 +361,49 @@ export class NeatGradient implements NeatController {
|
|
|
350
361
|
u.u_mouse_distortion_radius.value = this._mouseDistortionRadius;
|
|
351
362
|
u.u_mouse_darken.value = this._mouseDarken;
|
|
352
363
|
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
364
|
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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;
|
|
365
|
+
// Only regenerate procedural texture when needed
|
|
366
|
+
if (this._textureNeedsUpdate && this._enableProceduralTexture) {
|
|
367
|
+
if (this._proceduralTexture) {
|
|
368
|
+
this._proceduralTexture.dispose();
|
|
366
369
|
}
|
|
370
|
+
this._proceduralTexture = this._createProceduralTexture();
|
|
371
|
+
this._textureNeedsUpdate = false;
|
|
367
372
|
}
|
|
368
373
|
|
|
369
|
-
u.
|
|
370
|
-
|
|
374
|
+
u.u_procedural_texture.value = this._proceduralTexture;
|
|
375
|
+
u.u_texture_ease.value = this._textureEase;
|
|
376
|
+
|
|
377
|
+
// Wireframe is a material property and must update every frame to avoid artifacts
|
|
371
378
|
// @ts-ignore - access material safely
|
|
372
379
|
this.sceneState.meshes[0].material.wireframe = this._wireframe;
|
|
380
|
+
|
|
381
|
+
// Optimized Color Update: Update immediately on change, or throttle to 10 times per second
|
|
382
|
+
const now = Date.now();
|
|
383
|
+
const shouldUpdate = this._colorsChanged || (now - this._lastColorUpdate > 100);
|
|
384
|
+
|
|
385
|
+
if (shouldUpdate) {
|
|
386
|
+
this._lastColorUpdate = now;
|
|
387
|
+
this._colorsChanged = false;
|
|
388
|
+
|
|
389
|
+
const shaderColors = u.u_colors.value;
|
|
390
|
+
for(let i = 0; i < COLORS_COUNT; i++) {
|
|
391
|
+
if (i < this._colors.length) {
|
|
392
|
+
const c = this._colors[i];
|
|
393
|
+
shaderColors[i].is_active = c.enabled ? 1.0 : 0.0;
|
|
394
|
+
shaderColors[i].color.setStyle(c.color, "");
|
|
395
|
+
shaderColors[i].influence = c.influence || 0;
|
|
396
|
+
} else {
|
|
397
|
+
shaderColors[i].is_active = 0.0;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
u.u_colors_count.value = COLORS_COUNT;
|
|
402
|
+
}
|
|
373
403
|
}
|
|
374
404
|
|
|
375
|
-
// Render mouse interaction to FBO
|
|
376
|
-
if (this._mouseFBO && this._sceneMouse && this._cameraMouse) {
|
|
405
|
+
// Render mouse interaction to FBO - optimize by only rendering when needed
|
|
406
|
+
if (this._mouseFBO && this._sceneMouse && this._cameraMouse && this._mouseDistortionStrength > 0) {
|
|
377
407
|
let hasActiveBrushes = false;
|
|
378
408
|
|
|
379
409
|
// Update mouse objects - decay rate controls how fast trails fade
|
|
@@ -393,30 +423,27 @@ export class NeatGradient implements NeatController {
|
|
|
393
423
|
}
|
|
394
424
|
}
|
|
395
425
|
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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);
|
|
426
|
+
// Only render FBO if there are active brushes
|
|
427
|
+
if (hasActiveBrushes) {
|
|
428
|
+
// Store current clear color (likely the main background color)
|
|
429
|
+
renderer.getClearColor(this._tempClearColor);
|
|
430
|
+
const oldClearAlpha = renderer.getClearAlpha();
|
|
405
431
|
|
|
406
|
-
|
|
407
|
-
|
|
432
|
+
// Set clear color to Black/Transparent for the FBO.
|
|
433
|
+
renderer.setClearColor(0x000000, 0.0);
|
|
408
434
|
|
|
409
|
-
|
|
435
|
+
renderer.setRenderTarget(this._mouseFBO);
|
|
436
|
+
renderer.clear();
|
|
410
437
|
renderer.render(this._sceneMouse, this._cameraMouse);
|
|
411
|
-
|
|
412
|
-
renderer.setRenderTarget(null);
|
|
438
|
+
renderer.setRenderTarget(null);
|
|
413
439
|
|
|
414
|
-
|
|
415
|
-
|
|
440
|
+
// Restore main background color for the actual scene render
|
|
441
|
+
renderer.setClearColor(this._tempClearColor, oldClearAlpha);
|
|
416
442
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
443
|
+
// Update mouse texture uniform
|
|
444
|
+
if (this._cachedUniforms) {
|
|
445
|
+
this._cachedUniforms.u_mouse_texture.value = this._mouseFBO.texture;
|
|
446
|
+
}
|
|
420
447
|
}
|
|
421
448
|
}
|
|
422
449
|
|
|
@@ -450,8 +477,15 @@ export class NeatGradient implements NeatController {
|
|
|
450
477
|
}
|
|
451
478
|
};
|
|
452
479
|
|
|
453
|
-
|
|
454
|
-
|
|
480
|
+
// Debounce resize to prevent excessive operations
|
|
481
|
+
this.sizeObserver = new ResizeObserver(() => {
|
|
482
|
+
if (this._resizeTimeoutId !== null) {
|
|
483
|
+
clearTimeout(this._resizeTimeoutId);
|
|
484
|
+
}
|
|
485
|
+
this._resizeTimeoutId = window.setTimeout(() => {
|
|
486
|
+
setSize();
|
|
487
|
+
this._resizeTimeoutId = null;
|
|
488
|
+
}, 100); // Wait 100ms after last resize event
|
|
455
489
|
});
|
|
456
490
|
|
|
457
491
|
this.sizeObserver.observe(ref);
|
|
@@ -465,6 +499,12 @@ export class NeatGradient implements NeatController {
|
|
|
465
499
|
cancelAnimationFrame(this.requestRef);
|
|
466
500
|
this.sizeObserver.disconnect();
|
|
467
501
|
|
|
502
|
+
// Clear resize timeout
|
|
503
|
+
if (this._resizeTimeoutId !== null) {
|
|
504
|
+
clearTimeout(this._resizeTimeoutId);
|
|
505
|
+
this._resizeTimeoutId = null;
|
|
506
|
+
}
|
|
507
|
+
|
|
468
508
|
// Cleanup WebGL resources
|
|
469
509
|
if (this.sceneState) {
|
|
470
510
|
this.sceneState.renderer.dispose();
|
|
@@ -512,6 +552,7 @@ export class NeatGradient implements NeatController {
|
|
|
512
552
|
|
|
513
553
|
set colors(colors: NeatColor[]) {
|
|
514
554
|
this._colors = colors;
|
|
555
|
+
this._colorsChanged = true; // Flag for immediate update
|
|
515
556
|
}
|
|
516
557
|
|
|
517
558
|
set highlights(highlights: number) {
|
|
@@ -649,49 +690,49 @@ export class NeatGradient implements NeatController {
|
|
|
649
690
|
set enableProceduralTexture(value: boolean) {
|
|
650
691
|
this._enableProceduralTexture = value;
|
|
651
692
|
if (value && !this._proceduralTexture) {
|
|
652
|
-
this.
|
|
693
|
+
this._textureNeedsUpdate = true;
|
|
653
694
|
}
|
|
654
695
|
}
|
|
655
696
|
|
|
656
697
|
set textureVoidLikelihood(value: number) {
|
|
657
698
|
this._textureVoidLikelihood = value;
|
|
658
699
|
if (this._enableProceduralTexture) {
|
|
659
|
-
this.
|
|
700
|
+
this._textureNeedsUpdate = true;
|
|
660
701
|
}
|
|
661
702
|
}
|
|
662
703
|
|
|
663
704
|
set textureVoidWidthMin(value: number) {
|
|
664
705
|
this._textureVoidWidthMin = value;
|
|
665
706
|
if (this._enableProceduralTexture) {
|
|
666
|
-
this.
|
|
707
|
+
this._textureNeedsUpdate = true;
|
|
667
708
|
}
|
|
668
709
|
}
|
|
669
710
|
|
|
670
711
|
set textureVoidWidthMax(value: number) {
|
|
671
712
|
this._textureVoidWidthMax = value;
|
|
672
713
|
if (this._enableProceduralTexture) {
|
|
673
|
-
this.
|
|
714
|
+
this._textureNeedsUpdate = true;
|
|
674
715
|
}
|
|
675
716
|
}
|
|
676
717
|
|
|
677
718
|
set textureBandDensity(value: number) {
|
|
678
719
|
this._textureBandDensity = value;
|
|
679
720
|
if (this._enableProceduralTexture) {
|
|
680
|
-
this.
|
|
721
|
+
this._textureNeedsUpdate = true;
|
|
681
722
|
}
|
|
682
723
|
}
|
|
683
724
|
|
|
684
725
|
set textureColorBlending(value: number) {
|
|
685
726
|
this._textureColorBlending = value;
|
|
686
727
|
if (this._enableProceduralTexture) {
|
|
687
|
-
this.
|
|
728
|
+
this._textureNeedsUpdate = true;
|
|
688
729
|
}
|
|
689
730
|
}
|
|
690
731
|
|
|
691
732
|
set textureSeed(value: number) {
|
|
692
733
|
this._textureSeed = value;
|
|
693
734
|
if (this._enableProceduralTexture) {
|
|
694
|
-
this.
|
|
735
|
+
this._textureNeedsUpdate = true;
|
|
695
736
|
}
|
|
696
737
|
}
|
|
697
738
|
|
|
@@ -706,25 +747,25 @@ export class NeatGradient implements NeatController {
|
|
|
706
747
|
set proceduralBackgroundColor(value: string) {
|
|
707
748
|
this._proceduralBackgroundColor = value;
|
|
708
749
|
if (this._enableProceduralTexture) {
|
|
709
|
-
this.
|
|
750
|
+
this._textureNeedsUpdate = true;
|
|
710
751
|
}
|
|
711
752
|
}
|
|
712
753
|
|
|
713
754
|
set textureShapeTriangles(value: number) {
|
|
714
755
|
this._textureShapeTriangles = value;
|
|
715
|
-
if (this._enableProceduralTexture) this.
|
|
756
|
+
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
716
757
|
}
|
|
717
758
|
set textureShapeCircles(value: number) {
|
|
718
759
|
this._textureShapeCircles = value;
|
|
719
|
-
if (this._enableProceduralTexture) this.
|
|
760
|
+
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
720
761
|
}
|
|
721
762
|
set textureShapeBars(value: number) {
|
|
722
763
|
this._textureShapeBars = value;
|
|
723
|
-
if (this._enableProceduralTexture) this.
|
|
764
|
+
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
724
765
|
}
|
|
725
766
|
set textureShapeSquiggles(value: number) {
|
|
726
767
|
this._textureShapeSquiggles = value;
|
|
727
|
-
if (this._enableProceduralTexture) this.
|
|
768
|
+
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
728
769
|
}
|
|
729
770
|
|
|
730
771
|
_initScene(resolution: number): SceneState {
|
|
@@ -910,33 +951,52 @@ export class NeatGradient implements NeatController {
|
|
|
910
951
|
|
|
911
952
|
_onMouseMove(e: MouseEvent) {
|
|
912
953
|
if (!this._ref || !this._sceneMouse) return;
|
|
954
|
+
|
|
913
955
|
const rect = this._ref.getBoundingClientRect();
|
|
914
956
|
const width = this._ref.width;
|
|
915
957
|
const height = this._ref.height;
|
|
916
958
|
|
|
917
|
-
|
|
918
|
-
this.
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
959
|
+
// Store pending mouse position
|
|
960
|
+
this._pendingMousePosition = {
|
|
961
|
+
x: e.clientX - rect.left - width / 2,
|
|
962
|
+
y: -(e.clientY - rect.top - height / 2)
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// Batch mouse updates using requestAnimationFrame
|
|
966
|
+
if (!this._mouseUpdateScheduled) {
|
|
967
|
+
this._mouseUpdateScheduled = true;
|
|
968
|
+
requestAnimationFrame(() => {
|
|
969
|
+
this._mouseUpdateScheduled = false;
|
|
970
|
+
|
|
971
|
+
if (!this._pendingMousePosition) return;
|
|
972
|
+
|
|
973
|
+
this._mouse.x = this._pendingMousePosition.x;
|
|
974
|
+
this._mouse.y = this._pendingMousePosition.y;
|
|
975
|
+
|
|
976
|
+
const brush = this._mouseObjects[this._currentBrush];
|
|
977
|
+
brush.mesh.scale.set(this._mouseBrushBaseScale, this._mouseBrushBaseScale, 1.0);
|
|
978
|
+
brush.active = true;
|
|
979
|
+
brush.mesh.visible = true;
|
|
980
|
+
brush.mesh.position.set(this._mouse.x, this._mouse.y, 0);
|
|
981
|
+
brush.mesh.rotation.z = Math.random() * Math.PI * 2;
|
|
982
|
+
if (brush.mesh.material instanceof THREE.MeshBasicMaterial) {
|
|
983
|
+
brush.mesh.material.opacity = 1.0;
|
|
984
|
+
}
|
|
985
|
+
this._currentBrush = (this._currentBrush + 1) % this._mouseObjects.length;
|
|
986
|
+
|
|
987
|
+
this._pendingMousePosition = null;
|
|
988
|
+
});
|
|
928
989
|
}
|
|
929
|
-
this._currentBrush = (this._currentBrush + 1) % this._mouseObjects.length;
|
|
930
990
|
}
|
|
931
991
|
|
|
932
992
|
_createProceduralTexture(): THREE.Texture {
|
|
933
993
|
// Texture size - 1024 provides good balance between quality and performance
|
|
934
|
-
//
|
|
994
|
+
// Reduced from 2048 for better performance
|
|
935
995
|
const texSize = 1024;
|
|
936
996
|
const sourceCanvas = document.createElement('canvas');
|
|
937
997
|
sourceCanvas.width = texSize;
|
|
938
998
|
sourceCanvas.height = texSize;
|
|
939
|
-
const sCtx = sourceCanvas.getContext('2d');
|
|
999
|
+
const sCtx = sourceCanvas.getContext('2d', { willReadFrequently: true });
|
|
940
1000
|
if (!sCtx) return new THREE.Texture();
|
|
941
1001
|
|
|
942
1002
|
let seed = this._textureSeed;
|
|
@@ -1056,7 +1116,7 @@ export class NeatGradient implements NeatController {
|
|
|
1056
1116
|
const canvas = document.createElement('canvas');
|
|
1057
1117
|
canvas.width = texSize;
|
|
1058
1118
|
canvas.height = texSize;
|
|
1059
|
-
const ctx = canvas.getContext('2d');
|
|
1119
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
1060
1120
|
if (!ctx) return new THREE.Texture();
|
|
1061
1121
|
|
|
1062
1122
|
// Start filled with the chosen void color so gaps show that color
|
|
@@ -1134,11 +1194,26 @@ function updateCamera(camera: THREE.Camera, width: number, height: number) {
|
|
|
1134
1194
|
const targetWidth = Math.sqrt(targetPlaneArea * ratio);
|
|
1135
1195
|
const targetHeight = targetPlaneArea / targetWidth;
|
|
1136
1196
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1197
|
+
let left = -PLANE_WIDTH / 2;
|
|
1198
|
+
let right = Math.min((left + targetWidth) / 1.5, PLANE_WIDTH / 2);
|
|
1199
|
+
|
|
1200
|
+
let top = PLANE_HEIGHT / 4;
|
|
1201
|
+
let bottom = Math.max((top - targetHeight) / 2, -PLANE_HEIGHT / 4);
|
|
1202
|
+
|
|
1203
|
+
// Fix for mobile portrait: adjust bounds for proper aspect ratio AND zoom out slightly
|
|
1204
|
+
if (ratio < 1) {
|
|
1205
|
+
// Portrait mode - scale horizontal bounds by aspect ratio to prevent stretching
|
|
1206
|
+
const horizontalScale = ratio;
|
|
1207
|
+
left = left * horizontalScale;
|
|
1208
|
+
right = right * horizontalScale;
|
|
1139
1209
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1210
|
+
// Zoom out slightly on mobile (1.1 = 10% zoom out)
|
|
1211
|
+
const mobileZoomFactor = 1.05;
|
|
1212
|
+
left = left * mobileZoomFactor;
|
|
1213
|
+
right = right * mobileZoomFactor;
|
|
1214
|
+
top = top * mobileZoomFactor;
|
|
1215
|
+
bottom = bottom * mobileZoomFactor;
|
|
1216
|
+
}
|
|
1142
1217
|
|
|
1143
1218
|
const near = -100;
|
|
1144
1219
|
const far = 1000;
|
|
@@ -1158,8 +1233,13 @@ function updateCamera(camera: THREE.Camera, width: number, height: number) {
|
|
|
1158
1233
|
}
|
|
1159
1234
|
|
|
1160
1235
|
|
|
1236
|
+
// Cache shader strings to avoid repeated concatenation
|
|
1237
|
+
let cachedVertexShader: string | null = null;
|
|
1238
|
+
let cachedFragmentShader: string | null = null;
|
|
1239
|
+
|
|
1161
1240
|
function buildVertexShader() {
|
|
1162
|
-
return
|
|
1241
|
+
if (cachedVertexShader) return cachedVertexShader;
|
|
1242
|
+
cachedVertexShader = `
|
|
1163
1243
|
void main() {
|
|
1164
1244
|
vUv = uv;
|
|
1165
1245
|
|
|
@@ -1237,10 +1317,12 @@ void main() {
|
|
|
1237
1317
|
v_new_position = gl_Position;
|
|
1238
1318
|
}
|
|
1239
1319
|
`;
|
|
1320
|
+
return cachedVertexShader;
|
|
1240
1321
|
}
|
|
1241
1322
|
|
|
1242
1323
|
function buildFragmentShader() {
|
|
1243
|
-
return
|
|
1324
|
+
if (cachedFragmentShader) return cachedFragmentShader;
|
|
1325
|
+
cachedFragmentShader = `
|
|
1244
1326
|
float random(vec2 p) {
|
|
1245
1327
|
return fract(sin(dot(p, vec2(12.9898,78.233))) * 43758.5453);
|
|
1246
1328
|
}
|
|
@@ -1323,8 +1405,15 @@ void main() {
|
|
|
1323
1405
|
gl_FragColor = vec4(color, 1.0);
|
|
1324
1406
|
}
|
|
1325
1407
|
`;
|
|
1408
|
+
return cachedFragmentShader;
|
|
1326
1409
|
}
|
|
1327
|
-
|
|
1410
|
+
|
|
1411
|
+
// Cache uniforms string as well
|
|
1412
|
+
let cachedUniformsShader: string | null = null;
|
|
1413
|
+
|
|
1414
|
+
const buildUniforms = () => {
|
|
1415
|
+
if (cachedUniformsShader) return cachedUniformsShader;
|
|
1416
|
+
cachedUniformsShader = `
|
|
1328
1417
|
precision highp float;
|
|
1329
1418
|
|
|
1330
1419
|
struct Color {
|
|
@@ -1389,8 +1478,15 @@ varying vec3 v_color;
|
|
|
1389
1478
|
varying float v_displacement_amount;
|
|
1390
1479
|
|
|
1391
1480
|
`;
|
|
1481
|
+
return cachedUniformsShader;
|
|
1482
|
+
};
|
|
1392
1483
|
|
|
1393
|
-
|
|
1484
|
+
// Cache noise functions as well
|
|
1485
|
+
let cachedNoiseShader: string | null = null;
|
|
1486
|
+
|
|
1487
|
+
const buildNoise = () => {
|
|
1488
|
+
if (cachedNoiseShader) return cachedNoiseShader;
|
|
1489
|
+
cachedNoiseShader = `
|
|
1394
1490
|
|
|
1395
1491
|
// 1. REPLACEMENT PERMUTE:
|
|
1396
1492
|
// Uses a hash function (fract/sin) instead of a modular lookup table.
|
|
@@ -1544,8 +1640,15 @@ float cnoise(vec3 P)
|
|
|
1544
1640
|
return 2.2 * n_xyz;
|
|
1545
1641
|
}
|
|
1546
1642
|
`;
|
|
1643
|
+
return cachedNoiseShader;
|
|
1644
|
+
};
|
|
1547
1645
|
|
|
1548
|
-
|
|
1646
|
+
// Cache color functions as well
|
|
1647
|
+
let cachedColorFunctionsShader: string | null = null;
|
|
1648
|
+
|
|
1649
|
+
const buildColorFunctions = () => {
|
|
1650
|
+
if (cachedColorFunctionsShader) return cachedColorFunctionsShader;
|
|
1651
|
+
cachedColorFunctionsShader = `
|
|
1549
1652
|
|
|
1550
1653
|
vec3 saturation(vec3 rgb, float adjustment) {
|
|
1551
1654
|
const vec3 W = vec3(0.2125, 0.7154, 0.0721);
|
|
@@ -1589,6 +1692,8 @@ vec3 hsv2rgb(vec3 c)
|
|
|
1589
1692
|
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
|
1590
1693
|
}
|
|
1591
1694
|
`;
|
|
1695
|
+
return cachedColorFunctionsShader;
|
|
1696
|
+
};
|
|
1592
1697
|
|
|
1593
1698
|
const setLinkStyles = (link: HTMLAnchorElement) => {
|
|
1594
1699
|
link.id = LINK_ID;
|