@firecms/neat 0.8.0 → 0.9.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 +162 -0
- package/dist/NeatGradient.js +822 -69
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.es.js +1347 -706
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +391 -403
- package/dist/index.umd.js.map +1 -1
- package/dist/math.d.ts +33 -1
- package/dist/math.js +339 -24
- package/dist/math.js.map +1 -1
- package/dist/shaders.d.ts +2 -2
- package/dist/shaders.js +179 -34
- package/dist/shaders.js.map +1 -1
- package/dist/types.d.ts +26 -0
- package/package.json +4 -1
- package/src/NeatGradient.ts +945 -75
- package/src/math.ts +381 -28
- package/src/shaders.ts +179 -34
- package/src/types.ts +30 -0
package/src/NeatGradient.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildColorFunctions, buildNoise, buildVertUniforms, buildFragUniforms, fragmentShaderSource, vertexShaderSource } from "./shaders";
|
|
2
|
-
import { generatePlaneGeometry, OrthographicCamera, updateCamera, Matrix4 } from "./math";
|
|
2
|
+
import { generatePlaneGeometry, generateSphereGeometry, generateTorusGeometry, generateCylinderGeometry, generateRibbonGeometry, OrthographicCamera, updateCamera, Matrix4 } from "./math";
|
|
3
3
|
|
|
4
4
|
console.info(
|
|
5
5
|
"%c🌈 Neat Gradients%c\n\nLicensed under MIT + The Commons Clause.\nFree for personal and commercial use.\nSelling this software or its derivatives is strictly prohibited.\nhttps://neat.firecms.co",
|
|
@@ -61,6 +61,7 @@ export class NeatGradient implements NeatController {
|
|
|
61
61
|
private _grainSpeed: number = -1;
|
|
62
62
|
|
|
63
63
|
private _colorBlending: number = -1;
|
|
64
|
+
private _resolution: number = 1;
|
|
64
65
|
|
|
65
66
|
private _colors: NeatColor[] = [];
|
|
66
67
|
private _wireframe: boolean = false;
|
|
@@ -87,6 +88,7 @@ export class NeatGradient implements NeatController {
|
|
|
87
88
|
private _textureColorBlending: number = 0.01;
|
|
88
89
|
private _textureSeed: number = 333;
|
|
89
90
|
private _textureEase: number = 0.5;
|
|
91
|
+
private _transparentTextureVoid: boolean = false;
|
|
90
92
|
|
|
91
93
|
// New effects
|
|
92
94
|
private _domainWarpEnabled: boolean = false;
|
|
@@ -109,6 +111,35 @@ export class NeatGradient implements NeatController {
|
|
|
109
111
|
private _bloomIntensity: number = 0;
|
|
110
112
|
private _bloomThreshold: number = 0.7;
|
|
111
113
|
private _chromaticAberration: number = 0;
|
|
114
|
+
private _silhouetteFade: number = 0.25;
|
|
115
|
+
private _cylinderFade: number = 0.08;
|
|
116
|
+
private _ribbonFade: number = 0.05;
|
|
117
|
+
private _flatShading: boolean = true;
|
|
118
|
+
|
|
119
|
+
// 3D Shapes config
|
|
120
|
+
private _shapeType: 'plane' | 'sphere' | 'torus' | 'cylinder' | 'ribbon' = 'plane';
|
|
121
|
+
private _shapeRotationX: number = 0;
|
|
122
|
+
private _shapeRotationY: number = 0;
|
|
123
|
+
private _shapeRotationZ: number = 0;
|
|
124
|
+
private _shapeAutoRotateSpeedX: number = 0;
|
|
125
|
+
private _shapeAutoRotateSpeedY: number = 0;
|
|
126
|
+
private _sphereRadius: number = 15;
|
|
127
|
+
private _torusRadius: number = 15;
|
|
128
|
+
private _torusTube: number = 5;
|
|
129
|
+
private _cylinderRadius: number = 10;
|
|
130
|
+
private _cylinderHeight: number = 40;
|
|
131
|
+
private _planeBend: number = 0;
|
|
132
|
+
private _planeTwist: number = 0;
|
|
133
|
+
|
|
134
|
+
// Camera settings
|
|
135
|
+
private _cameraLock: boolean = false;
|
|
136
|
+
private _cameraX: number = 0;
|
|
137
|
+
private _cameraY: number = 0;
|
|
138
|
+
private _cameraZ: number = 0;
|
|
139
|
+
private _cameraRotationX: number = 0;
|
|
140
|
+
private _cameraRotationY: number = 0;
|
|
141
|
+
private _cameraRotationZ: number = 0;
|
|
142
|
+
private _cameraZoom: number = 1.0;
|
|
112
143
|
|
|
113
144
|
private _proceduralTexture: WebGLTexture | null = null;
|
|
114
145
|
private _proceduralBackgroundColor: string = "#000000";
|
|
@@ -130,6 +161,12 @@ export class NeatGradient implements NeatController {
|
|
|
130
161
|
private _yOffsetColorMultiplier: number = 0.004;
|
|
131
162
|
private _yOffsetFlowMultiplier: number = 0.004;
|
|
132
163
|
|
|
164
|
+
// Cached offscreen canvases for procedural texture generation
|
|
165
|
+
private _sourceCanvas: HTMLCanvasElement | null = null;
|
|
166
|
+
private _sourceCtx: CanvasRenderingContext2D | null = null;
|
|
167
|
+
private _maskedCanvas: HTMLCanvasElement | null = null;
|
|
168
|
+
private _maskedCtx: CanvasRenderingContext2D | null = null;
|
|
169
|
+
|
|
133
170
|
// Performance optimizations
|
|
134
171
|
private _resizeTimeoutId: number | null = null;
|
|
135
172
|
private _textureNeedsUpdate: boolean = false;
|
|
@@ -137,6 +174,11 @@ export class NeatGradient implements NeatController {
|
|
|
137
174
|
private _colorsChanged: boolean = true;
|
|
138
175
|
private _uniformsDirty: boolean = true;
|
|
139
176
|
private _textureDirty: boolean = true;
|
|
177
|
+
private _yOffsetDirty: boolean = false;
|
|
178
|
+
private _modelViewMatrix: Matrix4 = new Matrix4();
|
|
179
|
+
private _isVisible: boolean = true;
|
|
180
|
+
private _visibilityObserver: IntersectionObserver | null = null;
|
|
181
|
+
private _visibilityHandler: (() => void) | null = null;
|
|
140
182
|
|
|
141
183
|
constructor(config: NeatConfig & { ref: HTMLCanvasElement, resolution?: number, seed?: number }) {
|
|
142
184
|
|
|
@@ -184,6 +226,7 @@ export class NeatGradient implements NeatController {
|
|
|
184
226
|
textureSeed = 333,
|
|
185
227
|
textureEase = 0.5,
|
|
186
228
|
proceduralBackgroundColor = "#000000",
|
|
229
|
+
transparentTextureVoid = false,
|
|
187
230
|
textureShapeTriangles = 20,
|
|
188
231
|
textureShapeCircles = 15,
|
|
189
232
|
textureShapeBars = 15,
|
|
@@ -192,7 +235,7 @@ export class NeatGradient implements NeatController {
|
|
|
192
235
|
domainWarpEnabled = false,
|
|
193
236
|
domainWarpIntensity = 0.5,
|
|
194
237
|
domainWarpScale = 1.0,
|
|
195
|
-
vignetteIntensity = 0.
|
|
238
|
+
vignetteIntensity = 0.0,
|
|
196
239
|
vignetteRadius = 0.8,
|
|
197
240
|
fresnelEnabled = false,
|
|
198
241
|
fresnelPower = 2.0,
|
|
@@ -204,6 +247,35 @@ export class NeatGradient implements NeatController {
|
|
|
204
247
|
bloomIntensity = 0.0,
|
|
205
248
|
bloomThreshold = 0.7,
|
|
206
249
|
chromaticAberration = 0.0,
|
|
250
|
+
silhouetteFade = 0.25,
|
|
251
|
+
cylinderFade = 0.08,
|
|
252
|
+
ribbonFade = 0.05,
|
|
253
|
+
flatShading = true,
|
|
254
|
+
|
|
255
|
+
// Camera configuration
|
|
256
|
+
cameraLock = false,
|
|
257
|
+
cameraX = 0,
|
|
258
|
+
cameraY = 0,
|
|
259
|
+
cameraZ = 0,
|
|
260
|
+
cameraRotationX = 0,
|
|
261
|
+
cameraRotationY = 0,
|
|
262
|
+
cameraRotationZ = 0,
|
|
263
|
+
cameraZoom = 1.0,
|
|
264
|
+
|
|
265
|
+
// 3D shapes default
|
|
266
|
+
shapeType = 'plane',
|
|
267
|
+
shapeRotationX = 0,
|
|
268
|
+
shapeRotationY = 0,
|
|
269
|
+
shapeRotationZ = 0,
|
|
270
|
+
shapeAutoRotateSpeedX = 0,
|
|
271
|
+
shapeAutoRotateSpeedY = 0,
|
|
272
|
+
sphereRadius = 15,
|
|
273
|
+
torusRadius = 15,
|
|
274
|
+
torusTube = 5,
|
|
275
|
+
cylinderRadius = 10,
|
|
276
|
+
cylinderHeight = 40,
|
|
277
|
+
planeBend = 0,
|
|
278
|
+
planeTwist = 0,
|
|
207
279
|
} = config;
|
|
208
280
|
|
|
209
281
|
|
|
@@ -219,6 +291,7 @@ export class NeatGradient implements NeatController {
|
|
|
219
291
|
this.waveFrequencyY = waveFrequencyY;
|
|
220
292
|
this.waveAmplitude = waveAmplitude;
|
|
221
293
|
this.colorBlending = colorBlending;
|
|
294
|
+
this._resolution = resolution;
|
|
222
295
|
this.grainScale = grainScale;
|
|
223
296
|
this.grainIntensity = grainIntensity;
|
|
224
297
|
this.grainSparsity = grainSparsity;
|
|
@@ -255,6 +328,7 @@ export class NeatGradient implements NeatController {
|
|
|
255
328
|
this.textureSeed = textureSeed;
|
|
256
329
|
this.textureEase = textureEase;
|
|
257
330
|
this._proceduralBackgroundColor = proceduralBackgroundColor;
|
|
331
|
+
this.transparentTextureVoid = transparentTextureVoid;
|
|
258
332
|
|
|
259
333
|
this._textureShapeTriangles = textureShapeTriangles;
|
|
260
334
|
this._textureShapeCircles = textureShapeCircles;
|
|
@@ -276,6 +350,33 @@ export class NeatGradient implements NeatController {
|
|
|
276
350
|
this.bloomIntensity = bloomIntensity;
|
|
277
351
|
this.bloomThreshold = bloomThreshold;
|
|
278
352
|
this.chromaticAberration = chromaticAberration;
|
|
353
|
+
this.silhouetteFade = silhouetteFade;
|
|
354
|
+
this.cylinderFade = cylinderFade;
|
|
355
|
+
this.ribbonFade = ribbonFade;
|
|
356
|
+
this._flatShading = flatShading;
|
|
357
|
+
|
|
358
|
+
this._cameraLock = cameraLock;
|
|
359
|
+
this._cameraX = cameraX;
|
|
360
|
+
this._cameraY = cameraY;
|
|
361
|
+
this._cameraZ = cameraZ;
|
|
362
|
+
this._cameraRotationX = cameraRotationX;
|
|
363
|
+
this._cameraRotationY = cameraRotationY;
|
|
364
|
+
this._cameraRotationZ = cameraRotationZ;
|
|
365
|
+
this._cameraZoom = cameraZoom;
|
|
366
|
+
|
|
367
|
+
this._shapeType = shapeType;
|
|
368
|
+
this._shapeRotationX = shapeRotationX;
|
|
369
|
+
this._shapeRotationY = shapeRotationY;
|
|
370
|
+
this._shapeRotationZ = shapeRotationZ;
|
|
371
|
+
this._shapeAutoRotateSpeedX = shapeAutoRotateSpeedX;
|
|
372
|
+
this._shapeAutoRotateSpeedY = shapeAutoRotateSpeedY;
|
|
373
|
+
this._sphereRadius = sphereRadius;
|
|
374
|
+
this._torusRadius = torusRadius;
|
|
375
|
+
this._torusTube = torusTube;
|
|
376
|
+
this._cylinderRadius = cylinderRadius;
|
|
377
|
+
this._cylinderHeight = cylinderHeight;
|
|
378
|
+
this._planeBend = planeBend;
|
|
379
|
+
this._planeTwist = planeTwist;
|
|
279
380
|
|
|
280
381
|
this.glState = this._initScene(resolution);
|
|
281
382
|
|
|
@@ -306,6 +407,52 @@ export class NeatGradient implements NeatController {
|
|
|
306
407
|
|
|
307
408
|
gl.uniform1f(locations.uniforms['u_time'], tick);
|
|
308
409
|
|
|
410
|
+
// Update modelViewMatrix in every frame to support dynamic rotation and auto-rotation
|
|
411
|
+
const camera = this.glState.camera;
|
|
412
|
+
const modelViewMatrix = this._modelViewMatrix;
|
|
413
|
+
modelViewMatrix.identity();
|
|
414
|
+
|
|
415
|
+
// 1. Camera translation (default camera distance + displacement)
|
|
416
|
+
modelViewMatrix.translate(
|
|
417
|
+
-camera.position[0] - this._cameraX,
|
|
418
|
+
-camera.position[1] - this._cameraY,
|
|
419
|
+
-camera.position[2] - this._cameraZ
|
|
420
|
+
);
|
|
421
|
+
modelViewMatrix.translate(0, 0, -1);
|
|
422
|
+
|
|
423
|
+
// 2. Camera rotation (revolving around target)
|
|
424
|
+
modelViewMatrix.rotateX(-this._cameraRotationX);
|
|
425
|
+
modelViewMatrix.rotateY(-this._cameraRotationY);
|
|
426
|
+
modelViewMatrix.rotateZ(-this._cameraRotationZ);
|
|
427
|
+
|
|
428
|
+
let rx = this._shapeRotationX;
|
|
429
|
+
let ry = this._shapeRotationY;
|
|
430
|
+
let rz = this._shapeRotationZ;
|
|
431
|
+
|
|
432
|
+
if (this._shapeAutoRotateSpeedX !== 0) {
|
|
433
|
+
rx += tick * this._shapeAutoRotateSpeedX * 0.1;
|
|
434
|
+
}
|
|
435
|
+
if (this._shapeAutoRotateSpeedY !== 0) {
|
|
436
|
+
ry += tick * this._shapeAutoRotateSpeedY * 0.1;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (this._shapeType === 'plane' || this._shapeType === 'ribbon') {
|
|
440
|
+
modelViewMatrix.rotateX(rx - Math.PI / 3.5);
|
|
441
|
+
} else {
|
|
442
|
+
modelViewMatrix.rotateX(rx);
|
|
443
|
+
}
|
|
444
|
+
modelViewMatrix.rotateY(ry);
|
|
445
|
+
modelViewMatrix.rotateZ(rz);
|
|
446
|
+
|
|
447
|
+
const mvLoc = locations.uniforms["modelViewMatrix"];
|
|
448
|
+
if (mvLoc) gl.uniformMatrix4fv(mvLoc, false, modelViewMatrix.elements);
|
|
449
|
+
|
|
450
|
+
// Fast path: only upload yOffset when it changed (scroll)
|
|
451
|
+
if (this._yOffsetDirty && !this._uniformsDirty) {
|
|
452
|
+
gl.uniform1f(locations.uniforms['u_y_offset'], this._yOffset);
|
|
453
|
+
this._yOffsetDirty = false;
|
|
454
|
+
}
|
|
455
|
+
|
|
309
456
|
// Only upload static uniforms when they've been modified
|
|
310
457
|
if (this._uniformsDirty) {
|
|
311
458
|
gl.uniform2f(locations.uniforms['u_resolution'], this._ref.clientWidth, this._ref.clientHeight);
|
|
@@ -333,8 +480,16 @@ export class NeatGradient implements NeatController {
|
|
|
333
480
|
gl.uniform1f(locations.uniforms['u_flow_ease'], this._flowEase);
|
|
334
481
|
gl.uniform1f(locations.uniforms['u_flow_enabled'], this._flowEnabled ? 1.0 : 0.0);
|
|
335
482
|
|
|
483
|
+
let shapeTypeVal = 0.0;
|
|
484
|
+
if (this._shapeType === 'sphere') shapeTypeVal = 1.0;
|
|
485
|
+
else if (this._shapeType === 'torus') shapeTypeVal = 2.0;
|
|
486
|
+
else if (this._shapeType === 'cylinder') shapeTypeVal = 3.0;
|
|
487
|
+
else if (this._shapeType === 'ribbon') shapeTypeVal = 4.0;
|
|
488
|
+
gl.uniform1f(locations.uniforms['u_shape_type'], shapeTypeVal);
|
|
489
|
+
|
|
336
490
|
gl.uniform1f(locations.uniforms['u_enable_procedural_texture'], this._enableProceduralTexture ? 1.0 : 0.0);
|
|
337
491
|
gl.uniform1f(locations.uniforms['u_texture_ease'], this._textureEase);
|
|
492
|
+
gl.uniform1f(locations.uniforms['u_transparent_texture_void'], this._transparentTextureVoid ? 1.0 : 0.0);
|
|
338
493
|
|
|
339
494
|
gl.uniform1f(locations.uniforms['u_domain_warp_enabled'], this._domainWarpEnabled ? 1.0 : 0.0);
|
|
340
495
|
gl.uniform1f(locations.uniforms['u_domain_warp_intensity'], this._domainWarpIntensity);
|
|
@@ -355,8 +510,13 @@ export class NeatGradient implements NeatController {
|
|
|
355
510
|
gl.uniform1f(locations.uniforms['u_bloom_intensity'], this._bloomIntensity);
|
|
356
511
|
gl.uniform1f(locations.uniforms['u_bloom_threshold'], this._bloomThreshold);
|
|
357
512
|
gl.uniform1f(locations.uniforms['u_chromatic_aberration'], this._chromaticAberration);
|
|
513
|
+
gl.uniform1f(locations.uniforms['u_silhouette_fade'], this._silhouetteFade);
|
|
514
|
+
gl.uniform1f(locations.uniforms['u_cylinder_fade'], this._cylinderFade);
|
|
515
|
+
gl.uniform1f(locations.uniforms['u_ribbon_fade'], this._ribbonFade);
|
|
516
|
+
gl.uniform1f(locations.uniforms['u_flat_shading'], this._flatShading ? 1.0 : 0.0);
|
|
358
517
|
|
|
359
518
|
this._uniformsDirty = false;
|
|
519
|
+
this._yOffsetDirty = false;
|
|
360
520
|
}
|
|
361
521
|
|
|
362
522
|
// Only regenerate procedural texture when needed
|
|
@@ -415,8 +575,35 @@ export class NeatGradient implements NeatController {
|
|
|
415
575
|
gl.drawElements(gl.TRIANGLES, indexCount, indexType, 0);
|
|
416
576
|
}
|
|
417
577
|
|
|
418
|
-
this.
|
|
578
|
+
if (this._isVisible) {
|
|
579
|
+
this.requestRef = requestAnimationFrame(render);
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
// Visibility optimization: pause rendering when off-screen or tab hidden
|
|
584
|
+
this._visibilityObserver = new IntersectionObserver((entries) => {
|
|
585
|
+
const wasVisible = this._isVisible;
|
|
586
|
+
this._isVisible = entries[0].isIntersecting && document.visibilityState !== 'hidden';
|
|
587
|
+
if (this._isVisible && !wasVisible) {
|
|
588
|
+
lastTime = performance.now(); // Avoid time jump after resume
|
|
589
|
+
this.requestRef = requestAnimationFrame(render);
|
|
590
|
+
}
|
|
591
|
+
}, { threshold: 0 });
|
|
592
|
+
this._visibilityObserver.observe(ref);
|
|
593
|
+
|
|
594
|
+
this._visibilityHandler = () => {
|
|
595
|
+
const wasVisible = this._isVisible;
|
|
596
|
+
if (document.visibilityState === 'hidden') {
|
|
597
|
+
this._isVisible = false;
|
|
598
|
+
} else {
|
|
599
|
+
this._isVisible = true;
|
|
600
|
+
if (!wasVisible) {
|
|
601
|
+
lastTime = performance.now();
|
|
602
|
+
this.requestRef = requestAnimationFrame(render);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
419
605
|
};
|
|
606
|
+
document.addEventListener('visibilitychange', this._visibilityHandler);
|
|
420
607
|
|
|
421
608
|
const setSize = () => {
|
|
422
609
|
|
|
@@ -430,14 +617,14 @@ export class NeatGradient implements NeatController {
|
|
|
430
617
|
|
|
431
618
|
gl.viewport(0, 0, width, height);
|
|
432
619
|
|
|
433
|
-
updateCamera(camera, width, height);
|
|
620
|
+
updateCamera(camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
434
621
|
|
|
435
622
|
|
|
436
623
|
|
|
437
624
|
// Recompute projection matrix on resize
|
|
438
|
-
const projLoc =
|
|
625
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
439
626
|
gl.useProgram(this.glState.program);
|
|
440
|
-
gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
627
|
+
if (projLoc) gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
441
628
|
};
|
|
442
629
|
|
|
443
630
|
// Debounce resize to prevent excessive operations
|
|
@@ -461,6 +648,16 @@ export class NeatGradient implements NeatController {
|
|
|
461
648
|
cancelAnimationFrame(this.requestRef);
|
|
462
649
|
this.sizeObserver.disconnect();
|
|
463
650
|
|
|
651
|
+
// Cleanup visibility observers
|
|
652
|
+
if (this._visibilityObserver) {
|
|
653
|
+
this._visibilityObserver.disconnect();
|
|
654
|
+
this._visibilityObserver = null;
|
|
655
|
+
}
|
|
656
|
+
if (this._visibilityHandler) {
|
|
657
|
+
document.removeEventListener('visibilitychange', this._visibilityHandler);
|
|
658
|
+
this._visibilityHandler = null;
|
|
659
|
+
}
|
|
660
|
+
|
|
464
661
|
// Clear resize timeout
|
|
465
662
|
if (this._resizeTimeoutId !== null) {
|
|
466
663
|
clearTimeout(this._resizeTimeoutId);
|
|
@@ -493,36 +690,226 @@ export class NeatGradient implements NeatController {
|
|
|
493
690
|
downloadURI(dataURL, filename);
|
|
494
691
|
}
|
|
495
692
|
|
|
693
|
+
/**
|
|
694
|
+
* Records the canvas animation as a video with a NEAT watermark overlay.
|
|
695
|
+
* @param options.durationMs Recording duration in milliseconds (default 5000).
|
|
696
|
+
* @param options.filename Output file name (default "neat.firecms.co").
|
|
697
|
+
* @param options.width Output video width in pixels (default: current canvas width).
|
|
698
|
+
* @param options.height Output video height in pixels (default: current canvas height).
|
|
699
|
+
* @param options.format Preferred format: 'mp4' or 'webm' (default: best available).
|
|
700
|
+
* @param options.onProgress Callback with progress 0-1.
|
|
701
|
+
* @param options.onComplete Callback when recording finishes.
|
|
702
|
+
* @returns A stop function to end recording early.
|
|
703
|
+
*/
|
|
704
|
+
recordVideo(options: {
|
|
705
|
+
durationMs?: number;
|
|
706
|
+
filename?: string;
|
|
707
|
+
width?: number;
|
|
708
|
+
height?: number;
|
|
709
|
+
format?: 'mp4' | 'webm';
|
|
710
|
+
onProgress?: (progress: number) => void;
|
|
711
|
+
onComplete?: () => void;
|
|
712
|
+
} = {}): () => void {
|
|
713
|
+
const {
|
|
714
|
+
durationMs = 5000,
|
|
715
|
+
filename = "neat.firecms.co",
|
|
716
|
+
format,
|
|
717
|
+
onProgress,
|
|
718
|
+
onComplete,
|
|
719
|
+
} = options;
|
|
720
|
+
|
|
721
|
+
const source = this._ref;
|
|
722
|
+
const width = options.width || source.width || source.clientWidth;
|
|
723
|
+
const height = options.height || source.height || source.clientHeight;
|
|
724
|
+
|
|
725
|
+
// Offscreen canvas that composites gradient + watermark each frame
|
|
726
|
+
const offscreen = document.createElement("canvas");
|
|
727
|
+
offscreen.width = width;
|
|
728
|
+
offscreen.height = height;
|
|
729
|
+
const ctx = offscreen.getContext("2d")!;
|
|
730
|
+
|
|
731
|
+
// Use captureStream(0) — only captures a frame when we explicitly
|
|
732
|
+
// call requestFrame() on the video track, so every composited frame
|
|
733
|
+
// is guaranteed to be captured.
|
|
734
|
+
const stream = offscreen.captureStream(0);
|
|
735
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
736
|
+
|
|
737
|
+
// Codec candidates ordered by preference
|
|
738
|
+
const mp4Candidates = [
|
|
739
|
+
"video/mp4;codecs=avc1",
|
|
740
|
+
"video/mp4;codecs=avc1,opus",
|
|
741
|
+
"video/mp4",
|
|
742
|
+
];
|
|
743
|
+
const webmCandidates = [
|
|
744
|
+
"video/webm;codecs=vp9,opus",
|
|
745
|
+
"video/webm;codecs=vp9",
|
|
746
|
+
"video/webm;codecs=vp8,opus",
|
|
747
|
+
"video/webm",
|
|
748
|
+
];
|
|
749
|
+
|
|
750
|
+
// Build candidate list based on preferred format
|
|
751
|
+
let candidates: string[];
|
|
752
|
+
if (format === 'mp4') candidates = [...mp4Candidates, ...webmCandidates];
|
|
753
|
+
else if (format === 'webm') candidates = [...webmCandidates, ...mp4Candidates];
|
|
754
|
+
else candidates = [...mp4Candidates, ...webmCandidates];
|
|
755
|
+
|
|
756
|
+
let mimeType = "video/webm";
|
|
757
|
+
for (const candidate of candidates) {
|
|
758
|
+
if (MediaRecorder.isTypeSupported(candidate)) {
|
|
759
|
+
mimeType = candidate;
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Scale bitrate with pixel count: 8 Mbps baseline at 720p
|
|
765
|
+
const pixels = width * height;
|
|
766
|
+
const baseBitrate = 8_000_000;
|
|
767
|
+
const basePixels = 1280 * 720;
|
|
768
|
+
const videoBitsPerSecond = Math.round(baseBitrate * Math.max(1, pixels / basePixels));
|
|
769
|
+
|
|
770
|
+
const recorder = new MediaRecorder(stream, {
|
|
771
|
+
mimeType,
|
|
772
|
+
videoBitsPerSecond,
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
const chunks: Blob[] = [];
|
|
776
|
+
recorder.ondataavailable = (e) => {
|
|
777
|
+
if (e.data.size > 0) chunks.push(e.data);
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
let stopped = false;
|
|
781
|
+
let rafId: number;
|
|
782
|
+
const startTime = performance.now();
|
|
783
|
+
let lastProgressTime = 0;
|
|
784
|
+
|
|
785
|
+
// Composite loop: draw source canvas + watermark overlay on each frame
|
|
786
|
+
const drawFrame = () => {
|
|
787
|
+
if (stopped) return;
|
|
788
|
+
|
|
789
|
+
ctx.clearRect(0, 0, width, height);
|
|
790
|
+
ctx.drawImage(source, 0, 0, width, height);
|
|
791
|
+
|
|
792
|
+
// Watermark: "NEAT" in bottom-right corner
|
|
793
|
+
const fontSize = Math.max(14, Math.round(height * 0.025));
|
|
794
|
+
ctx.font = `bold ${fontSize}px "Sofia Sans", sans-serif`;
|
|
795
|
+
ctx.textAlign = "right";
|
|
796
|
+
ctx.textBaseline = "bottom";
|
|
797
|
+
ctx.shadowColor = "rgba(0,0,0,0.5)";
|
|
798
|
+
ctx.shadowBlur = 4;
|
|
799
|
+
ctx.shadowOffsetX = 1;
|
|
800
|
+
ctx.shadowOffsetY = 1;
|
|
801
|
+
ctx.fillStyle = "rgba(255,255,255,0.7)";
|
|
802
|
+
ctx.fillText("NEAT", width - fontSize * 0.8, height - fontSize * 0.5);
|
|
803
|
+
ctx.shadowColor = "transparent";
|
|
804
|
+
ctx.shadowBlur = 0;
|
|
805
|
+
ctx.shadowOffsetX = 0;
|
|
806
|
+
ctx.shadowOffsetY = 0;
|
|
807
|
+
|
|
808
|
+
// Signal the stream to capture this frame
|
|
809
|
+
// @ts-ignore – requestFrame exists on CanvasCaptureMediaStreamTrack
|
|
810
|
+
if (videoTrack.requestFrame) videoTrack.requestFrame();
|
|
811
|
+
|
|
812
|
+
// Throttle progress to ~4 updates/sec to avoid flooding React state
|
|
813
|
+
if (onProgress) {
|
|
814
|
+
const now = performance.now();
|
|
815
|
+
if (now - lastProgressTime > 250) {
|
|
816
|
+
lastProgressTime = now;
|
|
817
|
+
onProgress(Math.min(0.99, (now - startTime) / durationMs));
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
rafId = requestAnimationFrame(drawFrame);
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
recorder.onstop = () => {
|
|
825
|
+
stopped = true;
|
|
826
|
+
cancelAnimationFrame(rafId);
|
|
827
|
+
|
|
828
|
+
// Use the correct file extension for the actual format
|
|
829
|
+
const isMP4 = mimeType.startsWith("video/mp4");
|
|
830
|
+
const ext = isMP4 ? ".mp4" : ".webm";
|
|
831
|
+
const blobType = isMP4 ? "video/mp4" : "video/webm";
|
|
832
|
+
const finalFilename = filename + ext;
|
|
833
|
+
|
|
834
|
+
const blob = new Blob(chunks, { type: blobType });
|
|
835
|
+
const url = URL.createObjectURL(blob);
|
|
836
|
+
downloadURI(url, finalFilename);
|
|
837
|
+
setTimeout(() => URL.revokeObjectURL(url), 30000);
|
|
838
|
+
|
|
839
|
+
onProgress?.(1);
|
|
840
|
+
onComplete?.();
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
// Start drawing frames, then start recording
|
|
844
|
+
drawFrame();
|
|
845
|
+
recorder.start(100); // collect data every 100ms
|
|
846
|
+
|
|
847
|
+
// Auto-stop after the requested duration
|
|
848
|
+
const timeoutId = window.setTimeout(() => {
|
|
849
|
+
if (recorder.state === "recording") {
|
|
850
|
+
recorder.stop();
|
|
851
|
+
}
|
|
852
|
+
}, durationMs);
|
|
853
|
+
|
|
854
|
+
// Return a stop function for early termination
|
|
855
|
+
return () => {
|
|
856
|
+
clearTimeout(timeoutId);
|
|
857
|
+
if (recorder.state === "recording") {
|
|
858
|
+
recorder.stop();
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
get speed(): number {
|
|
863
|
+
return this._speed * 20;
|
|
864
|
+
}
|
|
496
865
|
set speed(speed: number) {
|
|
497
866
|
this._uniformsDirty = true;
|
|
498
867
|
this._speed = speed / 20;
|
|
499
868
|
}
|
|
500
869
|
|
|
870
|
+
get horizontalPressure(): number {
|
|
871
|
+
return this._horizontalPressure * 4;
|
|
872
|
+
}
|
|
501
873
|
set horizontalPressure(horizontalPressure: number) {
|
|
502
874
|
this._uniformsDirty = true;
|
|
503
875
|
this._horizontalPressure = horizontalPressure / 4;
|
|
504
876
|
}
|
|
505
877
|
|
|
878
|
+
get verticalPressure(): number {
|
|
879
|
+
return this._verticalPressure * 4;
|
|
880
|
+
}
|
|
506
881
|
set verticalPressure(verticalPressure: number) {
|
|
507
882
|
this._uniformsDirty = true;
|
|
508
883
|
this._verticalPressure = verticalPressure / 4;
|
|
509
884
|
}
|
|
510
885
|
|
|
886
|
+
get waveFrequencyX(): number {
|
|
887
|
+
return this._waveFrequencyX / 0.04;
|
|
888
|
+
}
|
|
511
889
|
set waveFrequencyX(waveFrequencyX: number) {
|
|
512
890
|
this._uniformsDirty = true;
|
|
513
891
|
this._waveFrequencyX = waveFrequencyX * 0.04;
|
|
514
892
|
}
|
|
515
893
|
|
|
894
|
+
get waveFrequencyY(): number {
|
|
895
|
+
return this._waveFrequencyY / 0.04;
|
|
896
|
+
}
|
|
516
897
|
set waveFrequencyY(waveFrequencyY: number) {
|
|
517
898
|
this._uniformsDirty = true;
|
|
518
899
|
this._waveFrequencyY = waveFrequencyY * 0.04;
|
|
519
900
|
}
|
|
520
901
|
|
|
902
|
+
get waveAmplitude(): number {
|
|
903
|
+
return this._waveAmplitude / 0.75;
|
|
904
|
+
}
|
|
521
905
|
set waveAmplitude(waveAmplitude: number) {
|
|
522
906
|
this._uniformsDirty = true;
|
|
523
907
|
this._waveAmplitude = waveAmplitude * .75;
|
|
524
908
|
}
|
|
525
909
|
|
|
910
|
+
get colors(): NeatColor[] {
|
|
911
|
+
return this._colors;
|
|
912
|
+
}
|
|
526
913
|
set colors(colors: NeatColor[]) {
|
|
527
914
|
this._uniformsDirty = true;
|
|
528
915
|
this._colors = colors;
|
|
@@ -530,84 +917,120 @@ export class NeatGradient implements NeatController {
|
|
|
530
917
|
this._colorsChanged = true;
|
|
531
918
|
}
|
|
532
919
|
|
|
920
|
+
get highlights(): number {
|
|
921
|
+
return this._highlights * 100;
|
|
922
|
+
}
|
|
533
923
|
set highlights(highlights: number) {
|
|
534
924
|
this._uniformsDirty = true;
|
|
535
925
|
this._highlights = highlights / 100;
|
|
536
926
|
}
|
|
537
927
|
|
|
928
|
+
get shadows(): number {
|
|
929
|
+
return this._shadows * 100;
|
|
930
|
+
}
|
|
538
931
|
set shadows(shadows: number) {
|
|
539
932
|
this._uniformsDirty = true;
|
|
540
933
|
this._shadows = shadows / 100;
|
|
541
934
|
}
|
|
542
935
|
|
|
936
|
+
get colorSaturation(): number {
|
|
937
|
+
return this._saturation * 10;
|
|
938
|
+
}
|
|
543
939
|
set colorSaturation(colorSaturation: number) {
|
|
544
940
|
this._uniformsDirty = true;
|
|
545
941
|
this._saturation = colorSaturation / 10;
|
|
546
942
|
}
|
|
547
943
|
|
|
944
|
+
get colorBrightness(): number {
|
|
945
|
+
return this._brightness;
|
|
946
|
+
}
|
|
548
947
|
set colorBrightness(colorBrightness: number) {
|
|
549
948
|
this._uniformsDirty = true;
|
|
550
949
|
this._brightness = colorBrightness;
|
|
551
950
|
}
|
|
552
951
|
|
|
952
|
+
get colorBlending(): number {
|
|
953
|
+
return this._colorBlending * 10;
|
|
954
|
+
}
|
|
553
955
|
set colorBlending(colorBlending: number) {
|
|
554
956
|
this._uniformsDirty = true;
|
|
555
957
|
this._colorBlending = colorBlending / 10;
|
|
556
958
|
}
|
|
557
959
|
|
|
960
|
+
get grainScale(): number {
|
|
961
|
+
return this._grainScale;
|
|
962
|
+
}
|
|
558
963
|
set grainScale(grainScale: number) {
|
|
559
964
|
this._uniformsDirty = true;
|
|
560
965
|
this._grainScale = grainScale == 0 ? 1 : grainScale;
|
|
561
966
|
}
|
|
562
967
|
|
|
968
|
+
get grainIntensity(): number {
|
|
969
|
+
return this._grainIntensity;
|
|
970
|
+
}
|
|
563
971
|
set grainIntensity(grainIntensity: number) {
|
|
564
972
|
this._uniformsDirty = true;
|
|
565
973
|
this._grainIntensity = grainIntensity;
|
|
566
974
|
}
|
|
567
975
|
|
|
976
|
+
get grainSparsity(): number {
|
|
977
|
+
return this._grainSparsity;
|
|
978
|
+
}
|
|
568
979
|
set grainSparsity(grainSparsity: number) {
|
|
569
980
|
this._uniformsDirty = true;
|
|
570
981
|
this._grainSparsity = grainSparsity;
|
|
571
982
|
}
|
|
572
983
|
|
|
984
|
+
get grainSpeed(): number {
|
|
985
|
+
return this._grainSpeed;
|
|
986
|
+
}
|
|
573
987
|
set grainSpeed(grainSpeed: number) {
|
|
574
988
|
this._uniformsDirty = true;
|
|
575
989
|
this._grainSpeed = grainSpeed;
|
|
576
990
|
}
|
|
577
991
|
|
|
992
|
+
get wireframe(): boolean {
|
|
993
|
+
return this._wireframe;
|
|
994
|
+
}
|
|
578
995
|
set wireframe(wireframe: boolean) {
|
|
579
996
|
this._uniformsDirty = true;
|
|
580
997
|
this._wireframe = wireframe;
|
|
581
998
|
}
|
|
582
999
|
|
|
1000
|
+
get resolution(): number {
|
|
1001
|
+
return this._resolution;
|
|
1002
|
+
}
|
|
583
1003
|
set resolution(resolution: number) {
|
|
584
|
-
this.
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
gl.deleteProgram(this.glState.program);
|
|
588
|
-
gl.deleteBuffer(this.glState.buffers.position);
|
|
589
|
-
gl.deleteBuffer(this.glState.buffers.normal);
|
|
590
|
-
gl.deleteBuffer(this.glState.buffers.uv);
|
|
591
|
-
gl.deleteBuffer(this.glState.buffers.index);
|
|
592
|
-
gl.deleteBuffer(this.glState.buffers.wireframeIndex);
|
|
593
|
-
}
|
|
594
|
-
this.glState = this._initScene(resolution);
|
|
1004
|
+
if (this._resolution === resolution) return;
|
|
1005
|
+
this._resolution = resolution;
|
|
1006
|
+
this._updateGeometry();
|
|
595
1007
|
}
|
|
596
1008
|
|
|
1009
|
+
get backgroundColor(): string {
|
|
1010
|
+
return this._backgroundColor;
|
|
1011
|
+
}
|
|
597
1012
|
set backgroundColor(backgroundColor: string) {
|
|
598
1013
|
this._uniformsDirty = true;
|
|
599
1014
|
this._backgroundColor = backgroundColor;
|
|
600
1015
|
this._backgroundColorRgb = this._hexToRgb(backgroundColor);
|
|
601
1016
|
}
|
|
602
1017
|
|
|
1018
|
+
get backgroundAlpha(): number {
|
|
1019
|
+
return this._backgroundAlpha;
|
|
1020
|
+
}
|
|
603
1021
|
set backgroundAlpha(backgroundAlpha: number) {
|
|
604
1022
|
this._uniformsDirty = true;
|
|
605
1023
|
this._backgroundAlpha = backgroundAlpha;
|
|
606
1024
|
}
|
|
607
1025
|
|
|
1026
|
+
get yOffset(): number {
|
|
1027
|
+
return this._yOffset;
|
|
1028
|
+
}
|
|
608
1029
|
set yOffset(yOffset: number) {
|
|
609
|
-
this.
|
|
610
|
-
|
|
1030
|
+
if (this._yOffset !== yOffset) {
|
|
1031
|
+
this._yOffsetDirty = true;
|
|
1032
|
+
this._yOffset = yOffset;
|
|
1033
|
+
}
|
|
611
1034
|
}
|
|
612
1035
|
|
|
613
1036
|
get yOffsetWaveMultiplier(): number {
|
|
@@ -637,21 +1060,33 @@ export class NeatGradient implements NeatController {
|
|
|
637
1060
|
this._yOffsetFlowMultiplier = value / 1000;
|
|
638
1061
|
}
|
|
639
1062
|
|
|
1063
|
+
get flowDistortionA(): number {
|
|
1064
|
+
return this._flowDistortionA;
|
|
1065
|
+
}
|
|
640
1066
|
set flowDistortionA(value: number) {
|
|
641
1067
|
this._uniformsDirty = true;
|
|
642
1068
|
this._flowDistortionA = value;
|
|
643
1069
|
}
|
|
644
1070
|
|
|
1071
|
+
get flowDistortionB(): number {
|
|
1072
|
+
return this._flowDistortionB;
|
|
1073
|
+
}
|
|
645
1074
|
set flowDistortionB(value: number) {
|
|
646
1075
|
this._uniformsDirty = true;
|
|
647
1076
|
this._flowDistortionB = value;
|
|
648
1077
|
}
|
|
649
1078
|
|
|
1079
|
+
get flowScale(): number {
|
|
1080
|
+
return this._flowScale;
|
|
1081
|
+
}
|
|
650
1082
|
set flowScale(value: number) {
|
|
651
1083
|
this._uniformsDirty = true;
|
|
652
1084
|
this._flowScale = value;
|
|
653
1085
|
}
|
|
654
1086
|
|
|
1087
|
+
get flowEase(): number {
|
|
1088
|
+
return this._flowEase;
|
|
1089
|
+
}
|
|
655
1090
|
set flowEase(value: number) {
|
|
656
1091
|
this._uniformsDirty = true;
|
|
657
1092
|
this._flowEase = value;
|
|
@@ -668,6 +1103,9 @@ export class NeatGradient implements NeatController {
|
|
|
668
1103
|
|
|
669
1104
|
|
|
670
1105
|
|
|
1106
|
+
get enableProceduralTexture(): boolean {
|
|
1107
|
+
return this._enableProceduralTexture;
|
|
1108
|
+
}
|
|
671
1109
|
set enableProceduralTexture(value: boolean) {
|
|
672
1110
|
this._uniformsDirty = true;
|
|
673
1111
|
this._enableProceduralTexture = value;
|
|
@@ -676,6 +1114,9 @@ export class NeatGradient implements NeatController {
|
|
|
676
1114
|
}
|
|
677
1115
|
}
|
|
678
1116
|
|
|
1117
|
+
get textureVoidLikelihood(): number {
|
|
1118
|
+
return this._textureVoidLikelihood;
|
|
1119
|
+
}
|
|
679
1120
|
set textureVoidLikelihood(value: number) {
|
|
680
1121
|
this._uniformsDirty = true;
|
|
681
1122
|
this._textureVoidLikelihood = value;
|
|
@@ -684,6 +1125,9 @@ export class NeatGradient implements NeatController {
|
|
|
684
1125
|
}
|
|
685
1126
|
}
|
|
686
1127
|
|
|
1128
|
+
get textureVoidWidthMin(): number {
|
|
1129
|
+
return this._textureVoidWidthMin;
|
|
1130
|
+
}
|
|
687
1131
|
set textureVoidWidthMin(value: number) {
|
|
688
1132
|
this._uniformsDirty = true;
|
|
689
1133
|
this._textureVoidWidthMin = value;
|
|
@@ -692,6 +1136,9 @@ export class NeatGradient implements NeatController {
|
|
|
692
1136
|
}
|
|
693
1137
|
}
|
|
694
1138
|
|
|
1139
|
+
get textureVoidWidthMax(): number {
|
|
1140
|
+
return this._textureVoidWidthMax;
|
|
1141
|
+
}
|
|
695
1142
|
set textureVoidWidthMax(value: number) {
|
|
696
1143
|
this._uniformsDirty = true;
|
|
697
1144
|
this._textureVoidWidthMax = value;
|
|
@@ -700,6 +1147,9 @@ export class NeatGradient implements NeatController {
|
|
|
700
1147
|
}
|
|
701
1148
|
}
|
|
702
1149
|
|
|
1150
|
+
get textureBandDensity(): number {
|
|
1151
|
+
return this._textureBandDensity;
|
|
1152
|
+
}
|
|
703
1153
|
set textureBandDensity(value: number) {
|
|
704
1154
|
this._uniformsDirty = true;
|
|
705
1155
|
this._textureBandDensity = value;
|
|
@@ -708,6 +1158,9 @@ export class NeatGradient implements NeatController {
|
|
|
708
1158
|
}
|
|
709
1159
|
}
|
|
710
1160
|
|
|
1161
|
+
get textureColorBlending(): number {
|
|
1162
|
+
return this._textureColorBlending;
|
|
1163
|
+
}
|
|
711
1164
|
set textureColorBlending(value: number) {
|
|
712
1165
|
this._uniformsDirty = true;
|
|
713
1166
|
this._textureColorBlending = value;
|
|
@@ -716,6 +1169,9 @@ export class NeatGradient implements NeatController {
|
|
|
716
1169
|
}
|
|
717
1170
|
}
|
|
718
1171
|
|
|
1172
|
+
get textureSeed(): number {
|
|
1173
|
+
return this._textureSeed;
|
|
1174
|
+
}
|
|
719
1175
|
set textureSeed(value: number) {
|
|
720
1176
|
this._uniformsDirty = true;
|
|
721
1177
|
this._textureSeed = value;
|
|
@@ -733,6 +1189,21 @@ export class NeatGradient implements NeatController {
|
|
|
733
1189
|
this._textureEase = value;
|
|
734
1190
|
}
|
|
735
1191
|
|
|
1192
|
+
get transparentTextureVoid(): boolean {
|
|
1193
|
+
return this._transparentTextureVoid;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
set transparentTextureVoid(value: boolean) {
|
|
1197
|
+
this._uniformsDirty = true;
|
|
1198
|
+
this._transparentTextureVoid = value;
|
|
1199
|
+
if (this._enableProceduralTexture) {
|
|
1200
|
+
this._textureNeedsUpdate = true;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
get proceduralBackgroundColor(): string {
|
|
1205
|
+
return this._proceduralBackgroundColor;
|
|
1206
|
+
}
|
|
736
1207
|
set proceduralBackgroundColor(value: string) {
|
|
737
1208
|
this._uniformsDirty = true;
|
|
738
1209
|
this._proceduralBackgroundColor = value;
|
|
@@ -741,27 +1212,93 @@ export class NeatGradient implements NeatController {
|
|
|
741
1212
|
}
|
|
742
1213
|
}
|
|
743
1214
|
|
|
1215
|
+
get textureShapeTriangles(): number {
|
|
1216
|
+
return this._textureShapeTriangles;
|
|
1217
|
+
}
|
|
744
1218
|
set textureShapeTriangles(value: number) {
|
|
745
1219
|
this._uniformsDirty = true;
|
|
746
1220
|
this._textureShapeTriangles = value;
|
|
747
1221
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
748
1222
|
}
|
|
1223
|
+
get textureShapeCircles(): number {
|
|
1224
|
+
return this._textureShapeCircles;
|
|
1225
|
+
}
|
|
749
1226
|
set textureShapeCircles(value: number) {
|
|
750
1227
|
this._uniformsDirty = true;
|
|
751
1228
|
this._textureShapeCircles = value;
|
|
752
1229
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
753
1230
|
}
|
|
1231
|
+
get textureShapeBars(): number {
|
|
1232
|
+
return this._textureShapeBars;
|
|
1233
|
+
}
|
|
754
1234
|
set textureShapeBars(value: number) {
|
|
755
1235
|
this._uniformsDirty = true;
|
|
756
1236
|
this._textureShapeBars = value;
|
|
757
1237
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
758
1238
|
}
|
|
1239
|
+
get textureShapeSquiggles(): number {
|
|
1240
|
+
return this._textureShapeSquiggles;
|
|
1241
|
+
}
|
|
759
1242
|
set textureShapeSquiggles(value: number) {
|
|
760
1243
|
this._uniformsDirty = true;
|
|
761
1244
|
this._textureShapeSquiggles = value;
|
|
762
1245
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
763
1246
|
}
|
|
764
1247
|
|
|
1248
|
+
_updateGeometry() {
|
|
1249
|
+
if (!this.glState) return;
|
|
1250
|
+
const gl = this.glState.gl;
|
|
1251
|
+
const resolution = this._resolution || 1;
|
|
1252
|
+
|
|
1253
|
+
let geometry;
|
|
1254
|
+
if (this._shapeType === 'sphere') {
|
|
1255
|
+
geometry = generateSphereGeometry(this._sphereRadius, 120 * resolution, 120 * resolution);
|
|
1256
|
+
} else if (this._shapeType === 'torus') {
|
|
1257
|
+
geometry = generateTorusGeometry(this._torusRadius, this._torusTube, 120 * resolution, 120 * resolution);
|
|
1258
|
+
} else if (this._shapeType === 'cylinder') {
|
|
1259
|
+
geometry = generateCylinderGeometry(this._cylinderRadius, this._cylinderRadius, this._cylinderHeight, 120 * resolution, 120 * resolution);
|
|
1260
|
+
} else if (this._shapeType === 'ribbon') {
|
|
1261
|
+
geometry = generateRibbonGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution, this._planeBend, this._planeTwist);
|
|
1262
|
+
} else {
|
|
1263
|
+
geometry = generatePlaneGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution);
|
|
1264
|
+
}
|
|
1265
|
+
const { position, normal, uv, index, wireframeIndex } = geometry;
|
|
1266
|
+
|
|
1267
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.position);
|
|
1268
|
+
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
|
|
1269
|
+
|
|
1270
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.normal);
|
|
1271
|
+
gl.bufferData(gl.ARRAY_BUFFER, normal, gl.STATIC_DRAW);
|
|
1272
|
+
|
|
1273
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.uv);
|
|
1274
|
+
gl.bufferData(gl.ARRAY_BUFFER, uv, gl.STATIC_DRAW);
|
|
1275
|
+
|
|
1276
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.index);
|
|
1277
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index, gl.STATIC_DRAW);
|
|
1278
|
+
|
|
1279
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.wireframeIndex);
|
|
1280
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, wireframeIndex, gl.STATIC_DRAW);
|
|
1281
|
+
|
|
1282
|
+
// Restore default bound element buffer
|
|
1283
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.index);
|
|
1284
|
+
|
|
1285
|
+
this.glState.indexCount = index.length;
|
|
1286
|
+
this.glState.wireframeIndexCount = wireframeIndex.length;
|
|
1287
|
+
this.glState.indexType = (index instanceof Uint32Array) ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
1288
|
+
|
|
1289
|
+
// Keep camera updated with the new shapeType and dimensions
|
|
1290
|
+
const width = this._ref.clientWidth;
|
|
1291
|
+
const height = this._ref.clientHeight;
|
|
1292
|
+
updateCamera(this.glState.camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
1293
|
+
|
|
1294
|
+
// Recompute projection matrix
|
|
1295
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
1296
|
+
gl.useProgram(this.glState.program);
|
|
1297
|
+
if (projLoc) gl.uniformMatrix4fv(projLoc, false, this.glState.camera.projectionMatrix.elements);
|
|
1298
|
+
|
|
1299
|
+
this._uniformsDirty = true;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
765
1302
|
_hexToRgb(hex: string): [number, number, number] {
|
|
766
1303
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
767
1304
|
return [
|
|
@@ -788,8 +1325,20 @@ export class NeatGradient implements NeatController {
|
|
|
788
1325
|
|
|
789
1326
|
gl.viewport(0, 0, width, height);
|
|
790
1327
|
|
|
791
|
-
// Generate
|
|
792
|
-
|
|
1328
|
+
// Generate parametric geometry based on shapeType
|
|
1329
|
+
let geometry;
|
|
1330
|
+
if (this._shapeType === 'sphere') {
|
|
1331
|
+
geometry = generateSphereGeometry(this._sphereRadius, 120 * resolution, 120 * resolution);
|
|
1332
|
+
} else if (this._shapeType === 'torus') {
|
|
1333
|
+
geometry = generateTorusGeometry(this._torusRadius, this._torusTube, 120 * resolution, 120 * resolution);
|
|
1334
|
+
} else if (this._shapeType === 'cylinder') {
|
|
1335
|
+
geometry = generateCylinderGeometry(this._cylinderRadius, this._cylinderRadius, this._cylinderHeight, 120 * resolution, 120 * resolution);
|
|
1336
|
+
} else if (this._shapeType === 'ribbon') {
|
|
1337
|
+
geometry = generateRibbonGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution, this._planeBend, this._planeTwist);
|
|
1338
|
+
} else {
|
|
1339
|
+
geometry = generatePlaneGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution);
|
|
1340
|
+
}
|
|
1341
|
+
const { position, normal, uv, index, wireframeIndex } = geometry;
|
|
793
1342
|
|
|
794
1343
|
const positionBuffer = gl.createBuffer()!;
|
|
795
1344
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
@@ -855,7 +1404,7 @@ export class NeatGradient implements NeatController {
|
|
|
855
1404
|
|
|
856
1405
|
const camera = new OrthographicCamera(0, 0, 0, 0, 0, 1000);
|
|
857
1406
|
camera.position = [0, 0, 5];
|
|
858
|
-
updateCamera(camera, width, height);
|
|
1407
|
+
updateCamera(camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
859
1408
|
|
|
860
1409
|
// Define attributes
|
|
861
1410
|
const aPosition = gl.getAttribLocation(program, "position");
|
|
@@ -876,17 +1425,7 @@ export class NeatGradient implements NeatController {
|
|
|
876
1425
|
|
|
877
1426
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
|
878
1427
|
|
|
879
|
-
|
|
880
|
-
// The View Matrix is the inverse of the Camera's position
|
|
881
|
-
// Camera is at [0, 0, 5], so view matrix translates by [0, 0, -5]
|
|
882
|
-
modelViewMatrix.translate(-camera.position[0], -camera.position[1], -camera.position[2]);
|
|
883
|
-
|
|
884
|
-
// The Model Matrix mimicking: plane.rotation.x = -Math.PI / 3.5; plane.position.z = -1;
|
|
885
|
-
modelViewMatrix.translate(0, 0, -1);
|
|
886
|
-
modelViewMatrix.rotateX(-Math.PI / 3.5);
|
|
887
|
-
|
|
888
|
-
const mvLoc = gl.getUniformLocation(program, "modelViewMatrix");
|
|
889
|
-
gl.uniformMatrix4fv(mvLoc, false, modelViewMatrix.elements);
|
|
1428
|
+
// modelViewMatrix is set dynamically in the render loop
|
|
890
1429
|
|
|
891
1430
|
const projLoc = gl.getUniformLocation(program, "projectionMatrix");
|
|
892
1431
|
gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
@@ -901,18 +1440,20 @@ export class NeatGradient implements NeatController {
|
|
|
901
1440
|
gl.uniform1i(colorsCountLoc, COLORS_COUNT);
|
|
902
1441
|
|
|
903
1442
|
const uniformsList = [
|
|
1443
|
+
"projectionMatrix", "modelViewMatrix",
|
|
904
1444
|
"u_time", "u_resolution", "u_color_pressure", "u_wave_frequency_x", "u_wave_frequency_y",
|
|
905
1445
|
"u_wave_amplitude", "u_colors_count", "u_plane_width", "u_plane_height", "u_shadows",
|
|
906
1446
|
"u_highlights", "u_grain_intensity", "u_grain_sparsity", "u_grain_scale", "u_grain_speed",
|
|
907
1447
|
"u_flow_distortion_a", "u_flow_distortion_b", "u_flow_scale", "u_flow_ease", "u_flow_enabled",
|
|
908
1448
|
"u_y_offset", "u_y_offset_wave_multiplier", "u_y_offset_color_multiplier", "u_y_offset_flow_multiplier",
|
|
909
1449
|
|
|
910
|
-
"u_procedural_texture", "u_enable_procedural_texture", "u_texture_ease", "u_saturation", "u_brightness", "u_color_blending",
|
|
1450
|
+
"u_procedural_texture", "u_enable_procedural_texture", "u_texture_ease", "u_transparent_texture_void", "u_saturation", "u_brightness", "u_color_blending",
|
|
911
1451
|
"u_domain_warp_enabled", "u_domain_warp_intensity", "u_domain_warp_scale",
|
|
912
1452
|
"u_vignette_intensity", "u_vignette_radius",
|
|
913
1453
|
"u_fresnel_enabled", "u_fresnel_power", "u_fresnel_intensity", "u_fresnel_color",
|
|
914
1454
|
"u_iridescence_enabled", "u_iridescence_intensity", "u_iridescence_speed",
|
|
915
|
-
"u_bloom_intensity", "u_bloom_threshold", "u_chromatic_aberration"
|
|
1455
|
+
"u_bloom_intensity", "u_bloom_threshold", "u_chromatic_aberration",
|
|
1456
|
+
"u_shape_type", "u_silhouette_fade", "u_cylinder_fade", "u_ribbon_fade", "u_flat_shading"
|
|
916
1457
|
];
|
|
917
1458
|
|
|
918
1459
|
const locations: WebGLState["locations"] = {
|
|
@@ -966,10 +1507,15 @@ export class NeatGradient implements NeatController {
|
|
|
966
1507
|
// Texture size - 1024 provides good balance between quality and performance
|
|
967
1508
|
// Reduced from 2048 for better performance
|
|
968
1509
|
const texSize = 1024;
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1510
|
+
|
|
1511
|
+
if (!this._sourceCanvas) {
|
|
1512
|
+
this._sourceCanvas = document.createElement('canvas');
|
|
1513
|
+
this._sourceCanvas.width = texSize;
|
|
1514
|
+
this._sourceCanvas.height = texSize;
|
|
1515
|
+
this._sourceCtx = this._sourceCanvas.getContext('2d');
|
|
1516
|
+
}
|
|
1517
|
+
const sourceCanvas = this._sourceCanvas;
|
|
1518
|
+
const sCtx = this._sourceCtx;
|
|
973
1519
|
if (!sCtx) return null;
|
|
974
1520
|
|
|
975
1521
|
let seed = this._textureSeed;
|
|
@@ -988,6 +1534,10 @@ export class NeatGradient implements NeatController {
|
|
|
988
1534
|
const colors = this._colors.filter(c => c.enabled).map(c => c.color);
|
|
989
1535
|
if (colors.length === 0) return null;
|
|
990
1536
|
|
|
1537
|
+
const shouldTile = this._shapeType !== 'plane';
|
|
1538
|
+
const dxs = shouldTile ? [-1, 0, 1] : [0];
|
|
1539
|
+
const dys = shouldTile ? [-1, 0, 1] : [0];
|
|
1540
|
+
|
|
991
1541
|
// Helper functions
|
|
992
1542
|
function hexToRgb(hex: string) {
|
|
993
1543
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
@@ -1029,72 +1579,133 @@ export class NeatGradient implements NeatController {
|
|
|
1029
1579
|
|
|
1030
1580
|
// Triangles: use configurable count
|
|
1031
1581
|
for (let i = 0; i < this._textureShapeTriangles; i++) {
|
|
1032
|
-
|
|
1033
|
-
sCtx.beginPath();
|
|
1582
|
+
const fillStyle = getInterColor();
|
|
1034
1583
|
const x = random() * texSize;
|
|
1035
1584
|
const y = random() * texSize;
|
|
1036
1585
|
const s = 100 + random() * 300;
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1586
|
+
const x1 = (random() - 0.5) * s;
|
|
1587
|
+
const y1 = (random() - 0.5) * s;
|
|
1588
|
+
const x2 = (random() - 0.5) * s;
|
|
1589
|
+
const y2 = (random() - 0.5) * s;
|
|
1590
|
+
|
|
1591
|
+
for (const dx of dxs) {
|
|
1592
|
+
for (const dy of dys) {
|
|
1593
|
+
sCtx.fillStyle = fillStyle;
|
|
1594
|
+
sCtx.beginPath();
|
|
1595
|
+
const tx = x + dx * texSize;
|
|
1596
|
+
const ty = y + dy * texSize;
|
|
1597
|
+
sCtx.moveTo(tx, ty);
|
|
1598
|
+
sCtx.lineTo(tx + x1, ty + y1);
|
|
1599
|
+
sCtx.lineTo(tx + x2, ty + y2);
|
|
1600
|
+
sCtx.fill();
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1041
1603
|
}
|
|
1042
1604
|
|
|
1043
1605
|
// Circles / rings: use configurable count
|
|
1044
1606
|
for (let i = 0; i < this._textureShapeCircles; i++) {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
sCtx.beginPath();
|
|
1607
|
+
const strokeStyle = getInterColor();
|
|
1608
|
+
const lineWidth = 10 + random() * 50;
|
|
1048
1609
|
const x = random() * texSize;
|
|
1049
1610
|
const y = random() * texSize;
|
|
1050
1611
|
const r = 50 + random() * 150;
|
|
1051
|
-
|
|
1052
|
-
|
|
1612
|
+
|
|
1613
|
+
for (const dx of dxs) {
|
|
1614
|
+
for (const dy of dys) {
|
|
1615
|
+
sCtx.strokeStyle = strokeStyle;
|
|
1616
|
+
sCtx.lineWidth = lineWidth;
|
|
1617
|
+
sCtx.beginPath();
|
|
1618
|
+
sCtx.arc(x + dx * texSize, y + dy * texSize, r, 0, Math.PI * 2);
|
|
1619
|
+
sCtx.stroke();
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1053
1622
|
}
|
|
1054
1623
|
|
|
1055
1624
|
// Bars: use configurable count
|
|
1056
1625
|
for (let i = 0; i < this._textureShapeBars; i++) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1626
|
+
const fillStyle = getInterColor();
|
|
1627
|
+
const x = random() * texSize;
|
|
1628
|
+
const y = random() * texSize;
|
|
1629
|
+
const rot = random() * Math.PI;
|
|
1630
|
+
|
|
1631
|
+
for (const dx of dxs) {
|
|
1632
|
+
for (const dy of dys) {
|
|
1633
|
+
sCtx.fillStyle = fillStyle;
|
|
1634
|
+
sCtx.save();
|
|
1635
|
+
sCtx.translate(x + dx * texSize, y + dy * texSize);
|
|
1636
|
+
sCtx.rotate(rot);
|
|
1637
|
+
sCtx.fillRect(-150, -25, 300, 50);
|
|
1638
|
+
sCtx.restore();
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1063
1641
|
}
|
|
1064
1642
|
|
|
1065
1643
|
// Squiggles: use configurable count
|
|
1066
1644
|
sCtx.lineWidth = 15;
|
|
1067
1645
|
sCtx.lineCap = 'round';
|
|
1068
1646
|
for (let i = 0; i < this._textureShapeSquiggles; i++) {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1647
|
+
const strokeStyle = getInterColor();
|
|
1648
|
+
const x = random() * texSize;
|
|
1649
|
+
const y = random() * texSize;
|
|
1650
|
+
|
|
1651
|
+
const curves: Array<{ cx1: number, cy1: number, cx2: number, cy2: number, ex: number, ey: number }> = [];
|
|
1652
|
+
let cx = 0;
|
|
1653
|
+
let cy = 0;
|
|
1074
1654
|
for (let j = 0; j < 4; j++) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1655
|
+
const ex = cx + (random() - 0.5) * 300;
|
|
1656
|
+
const ey = cy + (random() - 0.5) * 300;
|
|
1657
|
+
curves.push({
|
|
1658
|
+
cx1: cx + (random() - 0.5) * 300,
|
|
1659
|
+
cy1: cy + (random() - 0.5) * 300,
|
|
1660
|
+
cx2: cx + (random() - 0.5) * 300,
|
|
1661
|
+
cy2: cy + (random() - 0.5) * 300,
|
|
1662
|
+
ex: ex,
|
|
1663
|
+
ey: ey
|
|
1664
|
+
});
|
|
1665
|
+
cx = ex;
|
|
1666
|
+
cy = ey;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
for (const dx of dxs) {
|
|
1670
|
+
for (const dy of dys) {
|
|
1671
|
+
sCtx.strokeStyle = strokeStyle;
|
|
1672
|
+
sCtx.beginPath();
|
|
1673
|
+
const tx = x + dx * texSize;
|
|
1674
|
+
const ty = y + dy * texSize;
|
|
1675
|
+
sCtx.moveTo(tx, ty);
|
|
1676
|
+
|
|
1677
|
+
for (const curve of curves) {
|
|
1678
|
+
sCtx.bezierCurveTo(
|
|
1679
|
+
tx + curve.cx1, ty + curve.cy1,
|
|
1680
|
+
tx + curve.cx2, ty + curve.cy2,
|
|
1681
|
+
tx + curve.ex, ty + curve.ey
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
sCtx.stroke();
|
|
1685
|
+
}
|
|
1082
1686
|
}
|
|
1083
|
-
sCtx.stroke();
|
|
1084
1687
|
}
|
|
1085
1688
|
|
|
1086
1689
|
// === MASKED CANVAS ===
|
|
1087
1690
|
// Masking: Seed isolation
|
|
1088
1691
|
setSeed(50000);
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1692
|
+
if (!this._maskedCanvas) {
|
|
1693
|
+
this._maskedCanvas = document.createElement('canvas');
|
|
1694
|
+
this._maskedCanvas.width = texSize;
|
|
1695
|
+
this._maskedCanvas.height = texSize;
|
|
1696
|
+
this._maskedCtx = this._maskedCanvas.getContext('2d');
|
|
1697
|
+
}
|
|
1698
|
+
const canvas = this._maskedCanvas;
|
|
1699
|
+
const ctx = this._maskedCtx;
|
|
1093
1700
|
if (!ctx) return null;
|
|
1094
1701
|
|
|
1095
1702
|
// Start filled with the chosen void color so gaps show that color
|
|
1096
|
-
|
|
1097
|
-
|
|
1703
|
+
if (this._transparentTextureVoid) {
|
|
1704
|
+
ctx.clearRect(0, 0, texSize, texSize);
|
|
1705
|
+
} else {
|
|
1706
|
+
ctx.fillStyle = baseColor;
|
|
1707
|
+
ctx.fillRect(0, 0, texSize, texSize);
|
|
1708
|
+
}
|
|
1098
1709
|
|
|
1099
1710
|
// Determine layout segments (matter vs void)
|
|
1100
1711
|
let layoutHead = 0;
|
|
@@ -1154,54 +1765,129 @@ export class NeatGradient implements NeatController {
|
|
|
1154
1765
|
return tex;
|
|
1155
1766
|
}
|
|
1156
1767
|
|
|
1768
|
+
get silhouetteFade(): number {
|
|
1769
|
+
return this._silhouetteFade;
|
|
1770
|
+
}
|
|
1771
|
+
set silhouetteFade(value: number) {
|
|
1772
|
+
if (this._silhouetteFade !== value) {
|
|
1773
|
+
this._silhouetteFade = value;
|
|
1774
|
+
this._uniformsDirty = true;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
get cylinderFade(): number {
|
|
1779
|
+
return this._cylinderFade;
|
|
1780
|
+
}
|
|
1781
|
+
set cylinderFade(value: number) {
|
|
1782
|
+
if (this._cylinderFade !== value) {
|
|
1783
|
+
this._cylinderFade = value;
|
|
1784
|
+
this._uniformsDirty = true;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
get ribbonFade(): number {
|
|
1789
|
+
return this._ribbonFade;
|
|
1790
|
+
}
|
|
1791
|
+
set ribbonFade(value: number) {
|
|
1792
|
+
if (this._ribbonFade !== value) {
|
|
1793
|
+
this._ribbonFade = value;
|
|
1794
|
+
this._uniformsDirty = true;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
get flatShading(): boolean {
|
|
1799
|
+
return this._flatShading;
|
|
1800
|
+
}
|
|
1801
|
+
set flatShading(value: boolean) {
|
|
1802
|
+
if (this._flatShading !== value) {
|
|
1803
|
+
this._flatShading = value;
|
|
1804
|
+
this._uniformsDirty = true;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
get domainWarpEnabled(): boolean {
|
|
1809
|
+
return this._domainWarpEnabled;
|
|
1810
|
+
}
|
|
1157
1811
|
set domainWarpEnabled(enabled: boolean) {
|
|
1158
1812
|
if (this._domainWarpEnabled !== enabled) {
|
|
1159
1813
|
this._domainWarpEnabled = enabled;
|
|
1160
1814
|
this._uniformsDirty = true;
|
|
1161
1815
|
}
|
|
1162
1816
|
}
|
|
1817
|
+
|
|
1818
|
+
get domainWarpIntensity(): number {
|
|
1819
|
+
return this._domainWarpIntensity;
|
|
1820
|
+
}
|
|
1163
1821
|
set domainWarpIntensity(intensity: number) {
|
|
1164
1822
|
if (this._domainWarpIntensity !== intensity) {
|
|
1165
1823
|
this._domainWarpIntensity = intensity;
|
|
1166
1824
|
this._uniformsDirty = true;
|
|
1167
1825
|
}
|
|
1168
1826
|
}
|
|
1827
|
+
|
|
1828
|
+
get domainWarpScale(): number {
|
|
1829
|
+
return this._domainWarpScale;
|
|
1830
|
+
}
|
|
1169
1831
|
set domainWarpScale(scale: number) {
|
|
1170
1832
|
if (this._domainWarpScale !== scale) {
|
|
1171
1833
|
this._domainWarpScale = scale;
|
|
1172
1834
|
this._uniformsDirty = true;
|
|
1173
1835
|
}
|
|
1174
1836
|
}
|
|
1837
|
+
|
|
1838
|
+
get vignetteIntensity(): number {
|
|
1839
|
+
return this._vignetteIntensity;
|
|
1840
|
+
}
|
|
1175
1841
|
set vignetteIntensity(intensity: number) {
|
|
1176
1842
|
if (this._vignetteIntensity !== intensity) {
|
|
1177
1843
|
this._vignetteIntensity = intensity;
|
|
1178
1844
|
this._uniformsDirty = true;
|
|
1179
1845
|
}
|
|
1180
1846
|
}
|
|
1847
|
+
|
|
1848
|
+
get vignetteRadius(): number {
|
|
1849
|
+
return this._vignetteRadius;
|
|
1850
|
+
}
|
|
1181
1851
|
set vignetteRadius(radius: number) {
|
|
1182
1852
|
if (this._vignetteRadius !== radius) {
|
|
1183
1853
|
this._vignetteRadius = radius;
|
|
1184
1854
|
this._uniformsDirty = true;
|
|
1185
1855
|
}
|
|
1186
1856
|
}
|
|
1857
|
+
|
|
1858
|
+
get fresnelEnabled(): boolean {
|
|
1859
|
+
return this._fresnelEnabled;
|
|
1860
|
+
}
|
|
1187
1861
|
set fresnelEnabled(enabled: boolean) {
|
|
1188
1862
|
if (this._fresnelEnabled !== enabled) {
|
|
1189
1863
|
this._fresnelEnabled = enabled;
|
|
1190
1864
|
this._uniformsDirty = true;
|
|
1191
1865
|
}
|
|
1192
1866
|
}
|
|
1867
|
+
|
|
1868
|
+
get fresnelPower(): number {
|
|
1869
|
+
return this._fresnelPower;
|
|
1870
|
+
}
|
|
1193
1871
|
set fresnelPower(power: number) {
|
|
1194
1872
|
if (this._fresnelPower !== power) {
|
|
1195
1873
|
this._fresnelPower = power;
|
|
1196
1874
|
this._uniformsDirty = true;
|
|
1197
1875
|
}
|
|
1198
1876
|
}
|
|
1877
|
+
|
|
1878
|
+
get fresnelIntensity(): number {
|
|
1879
|
+
return this._fresnelIntensity;
|
|
1880
|
+
}
|
|
1199
1881
|
set fresnelIntensity(intensity: number) {
|
|
1200
1882
|
if (this._fresnelIntensity !== intensity) {
|
|
1201
1883
|
this._fresnelIntensity = intensity;
|
|
1202
1884
|
this._uniformsDirty = true;
|
|
1203
1885
|
}
|
|
1204
1886
|
}
|
|
1887
|
+
|
|
1888
|
+
get fresnelColor(): string {
|
|
1889
|
+
return this._fresnelColor;
|
|
1890
|
+
}
|
|
1205
1891
|
set fresnelColor(fresnelColor: string) {
|
|
1206
1892
|
if (this._fresnelColor !== fresnelColor) {
|
|
1207
1893
|
this._fresnelColor = fresnelColor;
|
|
@@ -1209,42 +1895,226 @@ export class NeatGradient implements NeatController {
|
|
|
1209
1895
|
this._uniformsDirty = true;
|
|
1210
1896
|
}
|
|
1211
1897
|
}
|
|
1898
|
+
|
|
1899
|
+
get iridescenceEnabled(): boolean {
|
|
1900
|
+
return this._iridescenceEnabled;
|
|
1901
|
+
}
|
|
1212
1902
|
set iridescenceEnabled(enabled: boolean) {
|
|
1213
1903
|
if (this._iridescenceEnabled !== enabled) {
|
|
1214
1904
|
this._iridescenceEnabled = enabled;
|
|
1215
1905
|
this._uniformsDirty = true;
|
|
1216
1906
|
}
|
|
1217
1907
|
}
|
|
1908
|
+
|
|
1909
|
+
get iridescenceIntensity(): number {
|
|
1910
|
+
return this._iridescenceIntensity;
|
|
1911
|
+
}
|
|
1218
1912
|
set iridescenceIntensity(intensity: number) {
|
|
1219
1913
|
if (this._iridescenceIntensity !== intensity) {
|
|
1220
1914
|
this._iridescenceIntensity = intensity;
|
|
1221
1915
|
this._uniformsDirty = true;
|
|
1222
1916
|
}
|
|
1223
1917
|
}
|
|
1918
|
+
|
|
1919
|
+
get iridescenceSpeed(): number {
|
|
1920
|
+
return this._iridescenceSpeed;
|
|
1921
|
+
}
|
|
1224
1922
|
set iridescenceSpeed(speed: number) {
|
|
1225
1923
|
if (this._iridescenceSpeed !== speed) {
|
|
1226
1924
|
this._iridescenceSpeed = speed;
|
|
1227
1925
|
this._uniformsDirty = true;
|
|
1228
1926
|
}
|
|
1229
1927
|
}
|
|
1928
|
+
|
|
1929
|
+
get bloomIntensity(): number {
|
|
1930
|
+
return this._bloomIntensity;
|
|
1931
|
+
}
|
|
1230
1932
|
set bloomIntensity(intensity: number) {
|
|
1231
1933
|
if (this._bloomIntensity !== intensity) {
|
|
1232
1934
|
this._bloomIntensity = intensity;
|
|
1233
1935
|
this._uniformsDirty = true;
|
|
1234
1936
|
}
|
|
1235
1937
|
}
|
|
1938
|
+
|
|
1939
|
+
get bloomThreshold(): number {
|
|
1940
|
+
return this._bloomThreshold;
|
|
1941
|
+
}
|
|
1236
1942
|
set bloomThreshold(threshold: number) {
|
|
1237
1943
|
if (this._bloomThreshold !== threshold) {
|
|
1238
1944
|
this._bloomThreshold = threshold;
|
|
1239
1945
|
this._uniformsDirty = true;
|
|
1240
1946
|
}
|
|
1241
1947
|
}
|
|
1948
|
+
|
|
1949
|
+
get chromaticAberration(): number {
|
|
1950
|
+
return this._chromaticAberration;
|
|
1951
|
+
}
|
|
1242
1952
|
set chromaticAberration(aberration: number) {
|
|
1243
1953
|
if (this._chromaticAberration !== aberration) {
|
|
1244
1954
|
this._chromaticAberration = aberration;
|
|
1245
1955
|
this._uniformsDirty = true;
|
|
1246
1956
|
}
|
|
1247
1957
|
}
|
|
1958
|
+
|
|
1959
|
+
// Getters and Setters for 3D Shapes
|
|
1960
|
+
get shapeType(): 'plane' | 'sphere' | 'torus' | 'cylinder' | 'ribbon' {
|
|
1961
|
+
return this._shapeType;
|
|
1962
|
+
}
|
|
1963
|
+
set shapeType(val: 'plane' | 'sphere' | 'torus' | 'cylinder' | 'ribbon') {
|
|
1964
|
+
if (this._shapeType !== val) {
|
|
1965
|
+
this._shapeType = val;
|
|
1966
|
+
this._updateGeometry();
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
get shapeRotationX(): number { return this._shapeRotationX; }
|
|
1971
|
+
set shapeRotationX(val: number) {
|
|
1972
|
+
this._shapeRotationX = val;
|
|
1973
|
+
this._uniformsDirty = true;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
get shapeRotationY(): number { return this._shapeRotationY; }
|
|
1977
|
+
set shapeRotationY(val: number) {
|
|
1978
|
+
this._shapeRotationY = val;
|
|
1979
|
+
this._uniformsDirty = true;
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
get shapeRotationZ(): number { return this._shapeRotationZ; }
|
|
1983
|
+
set shapeRotationZ(val: number) {
|
|
1984
|
+
this._shapeRotationZ = val;
|
|
1985
|
+
this._uniformsDirty = true;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
get shapeAutoRotateSpeedX(): number { return this._shapeAutoRotateSpeedX; }
|
|
1989
|
+
set shapeAutoRotateSpeedX(val: number) {
|
|
1990
|
+
this._shapeAutoRotateSpeedX = val;
|
|
1991
|
+
this._uniformsDirty = true;
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
get shapeAutoRotateSpeedY(): number { return this._shapeAutoRotateSpeedY; }
|
|
1995
|
+
set shapeAutoRotateSpeedY(val: number) {
|
|
1996
|
+
this._shapeAutoRotateSpeedY = val;
|
|
1997
|
+
this._uniformsDirty = true;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
get sphereRadius(): number { return this._sphereRadius; }
|
|
2001
|
+
set sphereRadius(val: number) {
|
|
2002
|
+
if (this._sphereRadius !== val) {
|
|
2003
|
+
this._sphereRadius = val;
|
|
2004
|
+
this._updateGeometry();
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
get torusRadius(): number { return this._torusRadius; }
|
|
2009
|
+
set torusRadius(val: number) {
|
|
2010
|
+
if (this._torusRadius !== val) {
|
|
2011
|
+
this._torusRadius = val;
|
|
2012
|
+
this._updateGeometry();
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
get torusTube(): number { return this._torusTube; }
|
|
2017
|
+
set torusTube(val: number) {
|
|
2018
|
+
if (this._torusTube !== val) {
|
|
2019
|
+
this._torusTube = val;
|
|
2020
|
+
this._updateGeometry();
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
get cylinderRadius(): number { return this._cylinderRadius; }
|
|
2025
|
+
set cylinderRadius(val: number) {
|
|
2026
|
+
if (this._cylinderRadius !== val) {
|
|
2027
|
+
this._cylinderRadius = val;
|
|
2028
|
+
this._updateGeometry();
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
get cylinderHeight(): number { return this._cylinderHeight; }
|
|
2033
|
+
set cylinderHeight(val: number) {
|
|
2034
|
+
if (this._cylinderHeight !== val) {
|
|
2035
|
+
this._cylinderHeight = val;
|
|
2036
|
+
this._updateGeometry();
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
get planeBend(): number { return this._planeBend; }
|
|
2041
|
+
set planeBend(val: number) {
|
|
2042
|
+
if (this._planeBend !== val) {
|
|
2043
|
+
this._planeBend = val;
|
|
2044
|
+
this._updateGeometry();
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
get planeTwist(): number { return this._planeTwist; }
|
|
2049
|
+
set planeTwist(val: number) {
|
|
2050
|
+
if (this._planeTwist !== val) {
|
|
2051
|
+
this._planeTwist = val;
|
|
2052
|
+
this._updateGeometry();
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
// Camera Getters and Setters
|
|
2057
|
+
get cameraLock(): boolean { return this._cameraLock; }
|
|
2058
|
+
set cameraLock(val: boolean) {
|
|
2059
|
+
this._cameraLock = val;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
get cameraX(): number { return this._cameraX; }
|
|
2063
|
+
set cameraX(val: number) {
|
|
2064
|
+
this._cameraX = val;
|
|
2065
|
+
this._uniformsDirty = true;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
get cameraY(): number { return this._cameraY; }
|
|
2069
|
+
set cameraY(val: number) {
|
|
2070
|
+
this._cameraY = val;
|
|
2071
|
+
this._uniformsDirty = true;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
get cameraZ(): number { return this._cameraZ; }
|
|
2075
|
+
set cameraZ(val: number) {
|
|
2076
|
+
this._cameraZ = val;
|
|
2077
|
+
this._uniformsDirty = true;
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
get cameraRotationX(): number { return this._cameraRotationX; }
|
|
2081
|
+
set cameraRotationX(val: number) {
|
|
2082
|
+
this._cameraRotationX = val;
|
|
2083
|
+
this._uniformsDirty = true;
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
get cameraRotationY(): number { return this._cameraRotationY; }
|
|
2087
|
+
set cameraRotationY(val: number) {
|
|
2088
|
+
this._cameraRotationY = val;
|
|
2089
|
+
this._uniformsDirty = true;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
get cameraRotationZ(): number { return this._cameraRotationZ; }
|
|
2093
|
+
set cameraRotationZ(val: number) {
|
|
2094
|
+
this._cameraRotationZ = val;
|
|
2095
|
+
this._uniformsDirty = true;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
get cameraZoom(): number { return this._cameraZoom; }
|
|
2099
|
+
set cameraZoom(val: number) {
|
|
2100
|
+
if (this._cameraZoom !== val) {
|
|
2101
|
+
this._cameraZoom = val;
|
|
2102
|
+
this._updateCameraFrustum();
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
_updateCameraFrustum() {
|
|
2107
|
+
if (!this.glState) return;
|
|
2108
|
+
const gl = this.glState.gl;
|
|
2109
|
+
const width = this._ref.clientWidth;
|
|
2110
|
+
const height = this._ref.clientHeight;
|
|
2111
|
+
updateCamera(this.glState.camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
2112
|
+
|
|
2113
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
2114
|
+
gl.useProgram(this.glState.program);
|
|
2115
|
+
if (projLoc) gl.uniformMatrix4fv(projLoc, false, this.glState.camera.projectionMatrix.elements);
|
|
2116
|
+
this._uniformsDirty = true;
|
|
2117
|
+
}
|
|
1248
2118
|
}
|
|
1249
2119
|
|
|
1250
2120
|
|