@firecms/neat 0.7.1 → 0.9.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/dist/NeatGradient.d.ts +186 -55
- package/dist/NeatGradient.js +900 -65
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +1400 -610
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +411 -339
- package/dist/index.umd.js.map +1 -1
- package/dist/math.d.ts +32 -1
- package/dist/math.js +319 -24
- package/dist/math.js.map +1 -1
- package/dist/shaders.d.ts +2 -2
- package/dist/shaders.js +271 -42
- package/dist/shaders.js.map +1 -1
- package/dist/types.d.ts +40 -0
- package/package.json +1 -1
- package/src/NeatGradient.ts +1044 -131
- package/src/index.ts +1 -0
- package/src/math.ts +373 -28
- package/src/shaders.ts +271 -42
- package/src/types.ts +56 -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",
|
|
@@ -35,67 +35,7 @@ export interface WebGLState {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
export type NeatConfig = {
|
|
40
|
-
resolution?: number;
|
|
41
|
-
speed?: number;
|
|
42
|
-
horizontalPressure?: number;
|
|
43
|
-
verticalPressure?: number;
|
|
44
|
-
waveFrequencyX?: number;
|
|
45
|
-
waveFrequencyY?: number;
|
|
46
|
-
waveAmplitude?: number;
|
|
47
|
-
highlights?: number;
|
|
48
|
-
shadows?: number;
|
|
49
|
-
colorSaturation?: number;
|
|
50
|
-
colorBrightness?: number;
|
|
51
|
-
colors: NeatColor[];
|
|
52
|
-
colorBlending?: number;
|
|
53
|
-
grainScale?: number;
|
|
54
|
-
grainIntensity?: number;
|
|
55
|
-
grainSparsity?: number;
|
|
56
|
-
grainSpeed?: number;
|
|
57
|
-
wireframe?: boolean;
|
|
58
|
-
backgroundColor?: string;
|
|
59
|
-
backgroundAlpha?: number;
|
|
60
|
-
yOffset?: number;
|
|
61
|
-
yOffsetWaveMultiplier?: number;
|
|
62
|
-
yOffsetColorMultiplier?: number;
|
|
63
|
-
yOffsetFlowMultiplier?: number;
|
|
64
|
-
// Flow field parameters
|
|
65
|
-
flowDistortionA?: number;
|
|
66
|
-
flowDistortionB?: number;
|
|
67
|
-
flowScale?: number;
|
|
68
|
-
flowEase?: number;
|
|
69
|
-
flowEnabled?: boolean;
|
|
70
|
-
|
|
71
|
-
// Texture generation
|
|
72
|
-
enableProceduralTexture?: boolean;
|
|
73
|
-
textureVoidLikelihood?: number;
|
|
74
|
-
textureVoidWidthMin?: number;
|
|
75
|
-
textureVoidWidthMax?: number;
|
|
76
|
-
textureBandDensity?: number;
|
|
77
|
-
textureColorBlending?: number;
|
|
78
|
-
textureSeed?: number;
|
|
79
|
-
textureEase?: number;
|
|
80
|
-
proceduralBackgroundColor?: string;
|
|
81
|
-
textureShapeTriangles?: number;
|
|
82
|
-
textureShapeCircles?: number;
|
|
83
|
-
textureShapeBars?: number;
|
|
84
|
-
textureShapeSquiggles?: number;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
export type NeatColor = {
|
|
88
|
-
color: string;
|
|
89
|
-
enabled: boolean;
|
|
90
|
-
/**
|
|
91
|
-
* Value from 0 to 1
|
|
92
|
-
*/
|
|
93
|
-
influence?: number;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export type NeatController = {
|
|
97
|
-
destroy: () => void;
|
|
98
|
-
}
|
|
38
|
+
import { NeatConfig, NeatColor, NeatController } from "./types";
|
|
99
39
|
|
|
100
40
|
export class NeatGradient implements NeatController {
|
|
101
41
|
|
|
@@ -121,6 +61,7 @@ export class NeatGradient implements NeatController {
|
|
|
121
61
|
private _grainSpeed: number = -1;
|
|
122
62
|
|
|
123
63
|
private _colorBlending: number = -1;
|
|
64
|
+
private _resolution: number = 1;
|
|
124
65
|
|
|
125
66
|
private _colors: NeatColor[] = [];
|
|
126
67
|
private _wireframe: boolean = false;
|
|
@@ -147,6 +88,58 @@ export class NeatGradient implements NeatController {
|
|
|
147
88
|
private _textureColorBlending: number = 0.01;
|
|
148
89
|
private _textureSeed: number = 333;
|
|
149
90
|
private _textureEase: number = 0.5;
|
|
91
|
+
private _transparentTextureVoid: boolean = false;
|
|
92
|
+
|
|
93
|
+
// New effects
|
|
94
|
+
private _domainWarpEnabled: boolean = false;
|
|
95
|
+
private _domainWarpIntensity: number = 0.5;
|
|
96
|
+
private _domainWarpScale: number = 1.0;
|
|
97
|
+
|
|
98
|
+
private _vignetteIntensity: number = 0.5;
|
|
99
|
+
private _vignetteRadius: number = 0.8;
|
|
100
|
+
|
|
101
|
+
private _fresnelEnabled: boolean = false;
|
|
102
|
+
private _fresnelPower: number = 2.0;
|
|
103
|
+
private _fresnelIntensity: number = 0.5;
|
|
104
|
+
private _fresnelColor: string = "#FFFFFF";
|
|
105
|
+
private _fresnelColorRgb: [number, number, number] = [1, 1, 1];
|
|
106
|
+
|
|
107
|
+
private _iridescenceEnabled: boolean = false;
|
|
108
|
+
private _iridescenceIntensity: number = 0.5;
|
|
109
|
+
private _iridescenceSpeed: number = 1.0;
|
|
110
|
+
|
|
111
|
+
private _bloomIntensity: number = 0;
|
|
112
|
+
private _bloomThreshold: number = 0.7;
|
|
113
|
+
private _chromaticAberration: number = 0;
|
|
114
|
+
private _silhouetteFade: number = 0.25;
|
|
115
|
+
private _cylinderFade: number = 0.08;
|
|
116
|
+
private _ribbonFade: number = 0.05;
|
|
117
|
+
|
|
118
|
+
// 3D Shapes config
|
|
119
|
+
private _shapeType: 'plane' | 'sphere' | 'torus' | 'cylinder' | 'ribbon' = 'plane';
|
|
120
|
+
private _shapeRotationX: number = 0;
|
|
121
|
+
private _shapeRotationY: number = 0;
|
|
122
|
+
private _shapeRotationZ: number = 0;
|
|
123
|
+
private _shapeAutoRotateSpeedX: number = 0;
|
|
124
|
+
private _shapeAutoRotateSpeedY: number = 0;
|
|
125
|
+
private _sphereRadius: number = 15;
|
|
126
|
+
private _torusRadius: number = 15;
|
|
127
|
+
private _torusTube: number = 5;
|
|
128
|
+
private _cylinderRadius: number = 10;
|
|
129
|
+
private _cylinderHeight: number = 40;
|
|
130
|
+
private _planeBend: number = 0;
|
|
131
|
+
private _planeTwist: number = 0;
|
|
132
|
+
|
|
133
|
+
// Camera settings
|
|
134
|
+
private _cameraLock: boolean = false;
|
|
135
|
+
private _cameraX: number = 0;
|
|
136
|
+
private _cameraY: number = 0;
|
|
137
|
+
private _cameraZ: number = 0;
|
|
138
|
+
private _cameraRotationX: number = 0;
|
|
139
|
+
private _cameraRotationY: number = 0;
|
|
140
|
+
private _cameraRotationZ: number = 0;
|
|
141
|
+
private _cameraZoom: number = 1.0;
|
|
142
|
+
|
|
150
143
|
private _proceduralTexture: WebGLTexture | null = null;
|
|
151
144
|
private _proceduralBackgroundColor: string = "#000000";
|
|
152
145
|
|
|
@@ -167,6 +160,12 @@ export class NeatGradient implements NeatController {
|
|
|
167
160
|
private _yOffsetColorMultiplier: number = 0.004;
|
|
168
161
|
private _yOffsetFlowMultiplier: number = 0.004;
|
|
169
162
|
|
|
163
|
+
// Cached offscreen canvases for procedural texture generation
|
|
164
|
+
private _sourceCanvas: HTMLCanvasElement | null = null;
|
|
165
|
+
private _sourceCtx: CanvasRenderingContext2D | null = null;
|
|
166
|
+
private _maskedCanvas: HTMLCanvasElement | null = null;
|
|
167
|
+
private _maskedCtx: CanvasRenderingContext2D | null = null;
|
|
168
|
+
|
|
170
169
|
// Performance optimizations
|
|
171
170
|
private _resizeTimeoutId: number | null = null;
|
|
172
171
|
private _textureNeedsUpdate: boolean = false;
|
|
@@ -221,10 +220,55 @@ export class NeatGradient implements NeatController {
|
|
|
221
220
|
textureSeed = 333,
|
|
222
221
|
textureEase = 0.5,
|
|
223
222
|
proceduralBackgroundColor = "#000000",
|
|
223
|
+
transparentTextureVoid = false,
|
|
224
224
|
textureShapeTriangles = 20,
|
|
225
225
|
textureShapeCircles = 15,
|
|
226
226
|
textureShapeBars = 15,
|
|
227
227
|
textureShapeSquiggles = 10,
|
|
228
|
+
|
|
229
|
+
domainWarpEnabled = false,
|
|
230
|
+
domainWarpIntensity = 0.5,
|
|
231
|
+
domainWarpScale = 1.0,
|
|
232
|
+
vignetteIntensity = 0.0,
|
|
233
|
+
vignetteRadius = 0.8,
|
|
234
|
+
fresnelEnabled = false,
|
|
235
|
+
fresnelPower = 2.0,
|
|
236
|
+
fresnelIntensity = 0.5,
|
|
237
|
+
fresnelColor = "#FFFFFF",
|
|
238
|
+
iridescenceEnabled = false,
|
|
239
|
+
iridescenceIntensity = 0.5,
|
|
240
|
+
iridescenceSpeed = 1.0,
|
|
241
|
+
bloomIntensity = 0.0,
|
|
242
|
+
bloomThreshold = 0.7,
|
|
243
|
+
chromaticAberration = 0.0,
|
|
244
|
+
silhouetteFade = 0.25,
|
|
245
|
+
cylinderFade = 0.08,
|
|
246
|
+
ribbonFade = 0.05,
|
|
247
|
+
|
|
248
|
+
// Camera configuration
|
|
249
|
+
cameraLock = false,
|
|
250
|
+
cameraX = 0,
|
|
251
|
+
cameraY = 0,
|
|
252
|
+
cameraZ = 0,
|
|
253
|
+
cameraRotationX = 0,
|
|
254
|
+
cameraRotationY = 0,
|
|
255
|
+
cameraRotationZ = 0,
|
|
256
|
+
cameraZoom = 1.0,
|
|
257
|
+
|
|
258
|
+
// 3D shapes default
|
|
259
|
+
shapeType = 'plane',
|
|
260
|
+
shapeRotationX = 0,
|
|
261
|
+
shapeRotationY = 0,
|
|
262
|
+
shapeRotationZ = 0,
|
|
263
|
+
shapeAutoRotateSpeedX = 0,
|
|
264
|
+
shapeAutoRotateSpeedY = 0,
|
|
265
|
+
sphereRadius = 15,
|
|
266
|
+
torusRadius = 15,
|
|
267
|
+
torusTube = 5,
|
|
268
|
+
cylinderRadius = 10,
|
|
269
|
+
cylinderHeight = 40,
|
|
270
|
+
planeBend = 0,
|
|
271
|
+
planeTwist = 0,
|
|
228
272
|
} = config;
|
|
229
273
|
|
|
230
274
|
|
|
@@ -240,6 +284,7 @@ export class NeatGradient implements NeatController {
|
|
|
240
284
|
this.waveFrequencyY = waveFrequencyY;
|
|
241
285
|
this.waveAmplitude = waveAmplitude;
|
|
242
286
|
this.colorBlending = colorBlending;
|
|
287
|
+
this._resolution = resolution;
|
|
243
288
|
this.grainScale = grainScale;
|
|
244
289
|
this.grainIntensity = grainIntensity;
|
|
245
290
|
this.grainSparsity = grainSparsity;
|
|
@@ -276,12 +321,54 @@ export class NeatGradient implements NeatController {
|
|
|
276
321
|
this.textureSeed = textureSeed;
|
|
277
322
|
this.textureEase = textureEase;
|
|
278
323
|
this._proceduralBackgroundColor = proceduralBackgroundColor;
|
|
324
|
+
this.transparentTextureVoid = transparentTextureVoid;
|
|
279
325
|
|
|
280
326
|
this._textureShapeTriangles = textureShapeTriangles;
|
|
281
327
|
this._textureShapeCircles = textureShapeCircles;
|
|
282
328
|
this._textureShapeBars = textureShapeBars;
|
|
283
329
|
this._textureShapeSquiggles = textureShapeSquiggles;
|
|
284
330
|
|
|
331
|
+
this.domainWarpEnabled = domainWarpEnabled;
|
|
332
|
+
this.domainWarpIntensity = domainWarpIntensity;
|
|
333
|
+
this.domainWarpScale = domainWarpScale;
|
|
334
|
+
this.vignetteIntensity = vignetteIntensity;
|
|
335
|
+
this.vignetteRadius = vignetteRadius;
|
|
336
|
+
this.fresnelEnabled = fresnelEnabled;
|
|
337
|
+
this.fresnelPower = fresnelPower;
|
|
338
|
+
this.fresnelIntensity = fresnelIntensity;
|
|
339
|
+
this.fresnelColor = fresnelColor;
|
|
340
|
+
this.iridescenceEnabled = iridescenceEnabled;
|
|
341
|
+
this.iridescenceIntensity = iridescenceIntensity;
|
|
342
|
+
this.iridescenceSpeed = iridescenceSpeed;
|
|
343
|
+
this.bloomIntensity = bloomIntensity;
|
|
344
|
+
this.bloomThreshold = bloomThreshold;
|
|
345
|
+
this.chromaticAberration = chromaticAberration;
|
|
346
|
+
this.silhouetteFade = silhouetteFade;
|
|
347
|
+
this.cylinderFade = cylinderFade;
|
|
348
|
+
this.ribbonFade = ribbonFade;
|
|
349
|
+
|
|
350
|
+
this._cameraLock = cameraLock;
|
|
351
|
+
this._cameraX = cameraX;
|
|
352
|
+
this._cameraY = cameraY;
|
|
353
|
+
this._cameraZ = cameraZ;
|
|
354
|
+
this._cameraRotationX = cameraRotationX;
|
|
355
|
+
this._cameraRotationY = cameraRotationY;
|
|
356
|
+
this._cameraRotationZ = cameraRotationZ;
|
|
357
|
+
this._cameraZoom = cameraZoom;
|
|
358
|
+
|
|
359
|
+
this._shapeType = shapeType;
|
|
360
|
+
this._shapeRotationX = shapeRotationX;
|
|
361
|
+
this._shapeRotationY = shapeRotationY;
|
|
362
|
+
this._shapeRotationZ = shapeRotationZ;
|
|
363
|
+
this._shapeAutoRotateSpeedX = shapeAutoRotateSpeedX;
|
|
364
|
+
this._shapeAutoRotateSpeedY = shapeAutoRotateSpeedY;
|
|
365
|
+
this._sphereRadius = sphereRadius;
|
|
366
|
+
this._torusRadius = torusRadius;
|
|
367
|
+
this._torusTube = torusTube;
|
|
368
|
+
this._cylinderRadius = cylinderRadius;
|
|
369
|
+
this._cylinderHeight = cylinderHeight;
|
|
370
|
+
this._planeBend = planeBend;
|
|
371
|
+
this._planeTwist = planeTwist;
|
|
285
372
|
|
|
286
373
|
this.glState = this._initScene(resolution);
|
|
287
374
|
|
|
@@ -312,6 +399,45 @@ export class NeatGradient implements NeatController {
|
|
|
312
399
|
|
|
313
400
|
gl.uniform1f(locations.uniforms['u_time'], tick);
|
|
314
401
|
|
|
402
|
+
// Update modelViewMatrix in every frame to support dynamic rotation and auto-rotation
|
|
403
|
+
const camera = this.glState.camera;
|
|
404
|
+
const modelViewMatrix = new Matrix4();
|
|
405
|
+
|
|
406
|
+
// 1. Camera translation (default camera distance + displacement)
|
|
407
|
+
modelViewMatrix.translate(
|
|
408
|
+
-camera.position[0] - this._cameraX,
|
|
409
|
+
-camera.position[1] - this._cameraY,
|
|
410
|
+
-camera.position[2] - this._cameraZ
|
|
411
|
+
);
|
|
412
|
+
modelViewMatrix.translate(0, 0, -1);
|
|
413
|
+
|
|
414
|
+
// 2. Camera rotation (revolving around target)
|
|
415
|
+
modelViewMatrix.rotateX(-this._cameraRotationX);
|
|
416
|
+
modelViewMatrix.rotateY(-this._cameraRotationY);
|
|
417
|
+
modelViewMatrix.rotateZ(-this._cameraRotationZ);
|
|
418
|
+
|
|
419
|
+
let rx = this._shapeRotationX;
|
|
420
|
+
let ry = this._shapeRotationY;
|
|
421
|
+
let rz = this._shapeRotationZ;
|
|
422
|
+
|
|
423
|
+
if (this._shapeAutoRotateSpeedX !== 0) {
|
|
424
|
+
rx += tick * this._shapeAutoRotateSpeedX * 0.1;
|
|
425
|
+
}
|
|
426
|
+
if (this._shapeAutoRotateSpeedY !== 0) {
|
|
427
|
+
ry += tick * this._shapeAutoRotateSpeedY * 0.1;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (this._shapeType === 'plane' || this._shapeType === 'ribbon') {
|
|
431
|
+
modelViewMatrix.rotateX(rx - Math.PI / 3.5);
|
|
432
|
+
} else {
|
|
433
|
+
modelViewMatrix.rotateX(rx);
|
|
434
|
+
}
|
|
435
|
+
modelViewMatrix.rotateY(ry);
|
|
436
|
+
modelViewMatrix.rotateZ(rz);
|
|
437
|
+
|
|
438
|
+
const mvLoc = locations.uniforms["modelViewMatrix"];
|
|
439
|
+
if (mvLoc) gl.uniformMatrix4fv(mvLoc, false, modelViewMatrix.elements);
|
|
440
|
+
|
|
315
441
|
// Only upload static uniforms when they've been modified
|
|
316
442
|
if (this._uniformsDirty) {
|
|
317
443
|
gl.uniform2f(locations.uniforms['u_resolution'], this._ref.clientWidth, this._ref.clientHeight);
|
|
@@ -339,8 +465,39 @@ export class NeatGradient implements NeatController {
|
|
|
339
465
|
gl.uniform1f(locations.uniforms['u_flow_ease'], this._flowEase);
|
|
340
466
|
gl.uniform1f(locations.uniforms['u_flow_enabled'], this._flowEnabled ? 1.0 : 0.0);
|
|
341
467
|
|
|
468
|
+
let shapeTypeVal = 0.0;
|
|
469
|
+
if (this._shapeType === 'sphere') shapeTypeVal = 1.0;
|
|
470
|
+
else if (this._shapeType === 'torus') shapeTypeVal = 2.0;
|
|
471
|
+
else if (this._shapeType === 'cylinder') shapeTypeVal = 3.0;
|
|
472
|
+
else if (this._shapeType === 'ribbon') shapeTypeVal = 4.0;
|
|
473
|
+
gl.uniform1f(locations.uniforms['u_shape_type'], shapeTypeVal);
|
|
474
|
+
|
|
342
475
|
gl.uniform1f(locations.uniforms['u_enable_procedural_texture'], this._enableProceduralTexture ? 1.0 : 0.0);
|
|
343
476
|
gl.uniform1f(locations.uniforms['u_texture_ease'], this._textureEase);
|
|
477
|
+
gl.uniform1f(locations.uniforms['u_transparent_texture_void'], this._transparentTextureVoid ? 1.0 : 0.0);
|
|
478
|
+
|
|
479
|
+
gl.uniform1f(locations.uniforms['u_domain_warp_enabled'], this._domainWarpEnabled ? 1.0 : 0.0);
|
|
480
|
+
gl.uniform1f(locations.uniforms['u_domain_warp_intensity'], this._domainWarpIntensity);
|
|
481
|
+
gl.uniform1f(locations.uniforms['u_domain_warp_scale'], this._domainWarpScale);
|
|
482
|
+
|
|
483
|
+
gl.uniform1f(locations.uniforms['u_vignette_intensity'], this._vignetteIntensity);
|
|
484
|
+
gl.uniform1f(locations.uniforms['u_vignette_radius'], this._vignetteRadius);
|
|
485
|
+
|
|
486
|
+
gl.uniform1f(locations.uniforms['u_fresnel_enabled'], this._fresnelEnabled ? 1.0 : 0.0);
|
|
487
|
+
gl.uniform1f(locations.uniforms['u_fresnel_power'], this._fresnelPower);
|
|
488
|
+
gl.uniform1f(locations.uniforms['u_fresnel_intensity'], this._fresnelIntensity);
|
|
489
|
+
gl.uniform3fv(locations.uniforms['u_fresnel_color'], this._fresnelColorRgb);
|
|
490
|
+
|
|
491
|
+
gl.uniform1f(locations.uniforms['u_iridescence_enabled'], this._iridescenceEnabled ? 1.0 : 0.0);
|
|
492
|
+
gl.uniform1f(locations.uniforms['u_iridescence_intensity'], this._iridescenceIntensity);
|
|
493
|
+
gl.uniform1f(locations.uniforms['u_iridescence_speed'], this._iridescenceSpeed);
|
|
494
|
+
|
|
495
|
+
gl.uniform1f(locations.uniforms['u_bloom_intensity'], this._bloomIntensity);
|
|
496
|
+
gl.uniform1f(locations.uniforms['u_bloom_threshold'], this._bloomThreshold);
|
|
497
|
+
gl.uniform1f(locations.uniforms['u_chromatic_aberration'], this._chromaticAberration);
|
|
498
|
+
gl.uniform1f(locations.uniforms['u_silhouette_fade'], this._silhouetteFade);
|
|
499
|
+
gl.uniform1f(locations.uniforms['u_cylinder_fade'], this._cylinderFade);
|
|
500
|
+
gl.uniform1f(locations.uniforms['u_ribbon_fade'], this._ribbonFade);
|
|
344
501
|
|
|
345
502
|
this._uniformsDirty = false;
|
|
346
503
|
}
|
|
@@ -416,14 +573,14 @@ export class NeatGradient implements NeatController {
|
|
|
416
573
|
|
|
417
574
|
gl.viewport(0, 0, width, height);
|
|
418
575
|
|
|
419
|
-
updateCamera(camera, width, height);
|
|
576
|
+
updateCamera(camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
420
577
|
|
|
421
578
|
|
|
422
579
|
|
|
423
580
|
// Recompute projection matrix on resize
|
|
424
|
-
const projLoc =
|
|
581
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
425
582
|
gl.useProgram(this.glState.program);
|
|
426
|
-
gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
583
|
+
if (projLoc) gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
427
584
|
};
|
|
428
585
|
|
|
429
586
|
// Debounce resize to prevent excessive operations
|
|
@@ -479,36 +636,226 @@ export class NeatGradient implements NeatController {
|
|
|
479
636
|
downloadURI(dataURL, filename);
|
|
480
637
|
}
|
|
481
638
|
|
|
639
|
+
/**
|
|
640
|
+
* Records the canvas animation as a video with a NEAT watermark overlay.
|
|
641
|
+
* @param options.durationMs Recording duration in milliseconds (default 5000).
|
|
642
|
+
* @param options.filename Output file name (default "neat.firecms.co").
|
|
643
|
+
* @param options.width Output video width in pixels (default: current canvas width).
|
|
644
|
+
* @param options.height Output video height in pixels (default: current canvas height).
|
|
645
|
+
* @param options.format Preferred format: 'mp4' or 'webm' (default: best available).
|
|
646
|
+
* @param options.onProgress Callback with progress 0-1.
|
|
647
|
+
* @param options.onComplete Callback when recording finishes.
|
|
648
|
+
* @returns A stop function to end recording early.
|
|
649
|
+
*/
|
|
650
|
+
recordVideo(options: {
|
|
651
|
+
durationMs?: number;
|
|
652
|
+
filename?: string;
|
|
653
|
+
width?: number;
|
|
654
|
+
height?: number;
|
|
655
|
+
format?: 'mp4' | 'webm';
|
|
656
|
+
onProgress?: (progress: number) => void;
|
|
657
|
+
onComplete?: () => void;
|
|
658
|
+
} = {}): () => void {
|
|
659
|
+
const {
|
|
660
|
+
durationMs = 5000,
|
|
661
|
+
filename = "neat.firecms.co",
|
|
662
|
+
format,
|
|
663
|
+
onProgress,
|
|
664
|
+
onComplete,
|
|
665
|
+
} = options;
|
|
666
|
+
|
|
667
|
+
const source = this._ref;
|
|
668
|
+
const width = options.width || source.width || source.clientWidth;
|
|
669
|
+
const height = options.height || source.height || source.clientHeight;
|
|
670
|
+
|
|
671
|
+
// Offscreen canvas that composites gradient + watermark each frame
|
|
672
|
+
const offscreen = document.createElement("canvas");
|
|
673
|
+
offscreen.width = width;
|
|
674
|
+
offscreen.height = height;
|
|
675
|
+
const ctx = offscreen.getContext("2d")!;
|
|
676
|
+
|
|
677
|
+
// Use captureStream(0) — only captures a frame when we explicitly
|
|
678
|
+
// call requestFrame() on the video track, so every composited frame
|
|
679
|
+
// is guaranteed to be captured.
|
|
680
|
+
const stream = offscreen.captureStream(0);
|
|
681
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
682
|
+
|
|
683
|
+
// Codec candidates ordered by preference
|
|
684
|
+
const mp4Candidates = [
|
|
685
|
+
"video/mp4;codecs=avc1",
|
|
686
|
+
"video/mp4;codecs=avc1,opus",
|
|
687
|
+
"video/mp4",
|
|
688
|
+
];
|
|
689
|
+
const webmCandidates = [
|
|
690
|
+
"video/webm;codecs=vp9,opus",
|
|
691
|
+
"video/webm;codecs=vp9",
|
|
692
|
+
"video/webm;codecs=vp8,opus",
|
|
693
|
+
"video/webm",
|
|
694
|
+
];
|
|
695
|
+
|
|
696
|
+
// Build candidate list based on preferred format
|
|
697
|
+
let candidates: string[];
|
|
698
|
+
if (format === 'mp4') candidates = [...mp4Candidates, ...webmCandidates];
|
|
699
|
+
else if (format === 'webm') candidates = [...webmCandidates, ...mp4Candidates];
|
|
700
|
+
else candidates = [...mp4Candidates, ...webmCandidates];
|
|
701
|
+
|
|
702
|
+
let mimeType = "video/webm";
|
|
703
|
+
for (const candidate of candidates) {
|
|
704
|
+
if (MediaRecorder.isTypeSupported(candidate)) {
|
|
705
|
+
mimeType = candidate;
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Scale bitrate with pixel count: 8 Mbps baseline at 720p
|
|
711
|
+
const pixels = width * height;
|
|
712
|
+
const baseBitrate = 8_000_000;
|
|
713
|
+
const basePixels = 1280 * 720;
|
|
714
|
+
const videoBitsPerSecond = Math.round(baseBitrate * Math.max(1, pixels / basePixels));
|
|
715
|
+
|
|
716
|
+
const recorder = new MediaRecorder(stream, {
|
|
717
|
+
mimeType,
|
|
718
|
+
videoBitsPerSecond,
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
const chunks: Blob[] = [];
|
|
722
|
+
recorder.ondataavailable = (e) => {
|
|
723
|
+
if (e.data.size > 0) chunks.push(e.data);
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
let stopped = false;
|
|
727
|
+
let rafId: number;
|
|
728
|
+
const startTime = performance.now();
|
|
729
|
+
let lastProgressTime = 0;
|
|
730
|
+
|
|
731
|
+
// Composite loop: draw source canvas + watermark overlay on each frame
|
|
732
|
+
const drawFrame = () => {
|
|
733
|
+
if (stopped) return;
|
|
734
|
+
|
|
735
|
+
ctx.clearRect(0, 0, width, height);
|
|
736
|
+
ctx.drawImage(source, 0, 0, width, height);
|
|
737
|
+
|
|
738
|
+
// Watermark: "NEAT" in bottom-right corner
|
|
739
|
+
const fontSize = Math.max(14, Math.round(height * 0.025));
|
|
740
|
+
ctx.font = `bold ${fontSize}px "Sofia Sans", sans-serif`;
|
|
741
|
+
ctx.textAlign = "right";
|
|
742
|
+
ctx.textBaseline = "bottom";
|
|
743
|
+
ctx.shadowColor = "rgba(0,0,0,0.5)";
|
|
744
|
+
ctx.shadowBlur = 4;
|
|
745
|
+
ctx.shadowOffsetX = 1;
|
|
746
|
+
ctx.shadowOffsetY = 1;
|
|
747
|
+
ctx.fillStyle = "rgba(255,255,255,0.7)";
|
|
748
|
+
ctx.fillText("NEAT", width - fontSize * 0.8, height - fontSize * 0.5);
|
|
749
|
+
ctx.shadowColor = "transparent";
|
|
750
|
+
ctx.shadowBlur = 0;
|
|
751
|
+
ctx.shadowOffsetX = 0;
|
|
752
|
+
ctx.shadowOffsetY = 0;
|
|
753
|
+
|
|
754
|
+
// Signal the stream to capture this frame
|
|
755
|
+
// @ts-ignore – requestFrame exists on CanvasCaptureMediaStreamTrack
|
|
756
|
+
if (videoTrack.requestFrame) videoTrack.requestFrame();
|
|
757
|
+
|
|
758
|
+
// Throttle progress to ~4 updates/sec to avoid flooding React state
|
|
759
|
+
if (onProgress) {
|
|
760
|
+
const now = performance.now();
|
|
761
|
+
if (now - lastProgressTime > 250) {
|
|
762
|
+
lastProgressTime = now;
|
|
763
|
+
onProgress(Math.min(0.99, (now - startTime) / durationMs));
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
rafId = requestAnimationFrame(drawFrame);
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
recorder.onstop = () => {
|
|
771
|
+
stopped = true;
|
|
772
|
+
cancelAnimationFrame(rafId);
|
|
773
|
+
|
|
774
|
+
// Use the correct file extension for the actual format
|
|
775
|
+
const isMP4 = mimeType.startsWith("video/mp4");
|
|
776
|
+
const ext = isMP4 ? ".mp4" : ".webm";
|
|
777
|
+
const blobType = isMP4 ? "video/mp4" : "video/webm";
|
|
778
|
+
const finalFilename = filename + ext;
|
|
779
|
+
|
|
780
|
+
const blob = new Blob(chunks, { type: blobType });
|
|
781
|
+
const url = URL.createObjectURL(blob);
|
|
782
|
+
downloadURI(url, finalFilename);
|
|
783
|
+
setTimeout(() => URL.revokeObjectURL(url), 30000);
|
|
784
|
+
|
|
785
|
+
onProgress?.(1);
|
|
786
|
+
onComplete?.();
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
// Start drawing frames, then start recording
|
|
790
|
+
drawFrame();
|
|
791
|
+
recorder.start(100); // collect data every 100ms
|
|
792
|
+
|
|
793
|
+
// Auto-stop after the requested duration
|
|
794
|
+
const timeoutId = window.setTimeout(() => {
|
|
795
|
+
if (recorder.state === "recording") {
|
|
796
|
+
recorder.stop();
|
|
797
|
+
}
|
|
798
|
+
}, durationMs);
|
|
799
|
+
|
|
800
|
+
// Return a stop function for early termination
|
|
801
|
+
return () => {
|
|
802
|
+
clearTimeout(timeoutId);
|
|
803
|
+
if (recorder.state === "recording") {
|
|
804
|
+
recorder.stop();
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
get speed(): number {
|
|
809
|
+
return this._speed * 20;
|
|
810
|
+
}
|
|
482
811
|
set speed(speed: number) {
|
|
483
812
|
this._uniformsDirty = true;
|
|
484
813
|
this._speed = speed / 20;
|
|
485
814
|
}
|
|
486
815
|
|
|
816
|
+
get horizontalPressure(): number {
|
|
817
|
+
return this._horizontalPressure * 4;
|
|
818
|
+
}
|
|
487
819
|
set horizontalPressure(horizontalPressure: number) {
|
|
488
820
|
this._uniformsDirty = true;
|
|
489
821
|
this._horizontalPressure = horizontalPressure / 4;
|
|
490
822
|
}
|
|
491
823
|
|
|
824
|
+
get verticalPressure(): number {
|
|
825
|
+
return this._verticalPressure * 4;
|
|
826
|
+
}
|
|
492
827
|
set verticalPressure(verticalPressure: number) {
|
|
493
828
|
this._uniformsDirty = true;
|
|
494
829
|
this._verticalPressure = verticalPressure / 4;
|
|
495
830
|
}
|
|
496
831
|
|
|
832
|
+
get waveFrequencyX(): number {
|
|
833
|
+
return this._waveFrequencyX / 0.04;
|
|
834
|
+
}
|
|
497
835
|
set waveFrequencyX(waveFrequencyX: number) {
|
|
498
836
|
this._uniformsDirty = true;
|
|
499
837
|
this._waveFrequencyX = waveFrequencyX * 0.04;
|
|
500
838
|
}
|
|
501
839
|
|
|
840
|
+
get waveFrequencyY(): number {
|
|
841
|
+
return this._waveFrequencyY / 0.04;
|
|
842
|
+
}
|
|
502
843
|
set waveFrequencyY(waveFrequencyY: number) {
|
|
503
844
|
this._uniformsDirty = true;
|
|
504
845
|
this._waveFrequencyY = waveFrequencyY * 0.04;
|
|
505
846
|
}
|
|
506
847
|
|
|
848
|
+
get waveAmplitude(): number {
|
|
849
|
+
return this._waveAmplitude / 0.75;
|
|
850
|
+
}
|
|
507
851
|
set waveAmplitude(waveAmplitude: number) {
|
|
508
852
|
this._uniformsDirty = true;
|
|
509
853
|
this._waveAmplitude = waveAmplitude * .75;
|
|
510
854
|
}
|
|
511
855
|
|
|
856
|
+
get colors(): NeatColor[] {
|
|
857
|
+
return this._colors;
|
|
858
|
+
}
|
|
512
859
|
set colors(colors: NeatColor[]) {
|
|
513
860
|
this._uniformsDirty = true;
|
|
514
861
|
this._colors = colors;
|
|
@@ -516,81 +863,115 @@ export class NeatGradient implements NeatController {
|
|
|
516
863
|
this._colorsChanged = true;
|
|
517
864
|
}
|
|
518
865
|
|
|
866
|
+
get highlights(): number {
|
|
867
|
+
return this._highlights * 100;
|
|
868
|
+
}
|
|
519
869
|
set highlights(highlights: number) {
|
|
520
870
|
this._uniformsDirty = true;
|
|
521
871
|
this._highlights = highlights / 100;
|
|
522
872
|
}
|
|
523
873
|
|
|
874
|
+
get shadows(): number {
|
|
875
|
+
return this._shadows * 100;
|
|
876
|
+
}
|
|
524
877
|
set shadows(shadows: number) {
|
|
525
878
|
this._uniformsDirty = true;
|
|
526
879
|
this._shadows = shadows / 100;
|
|
527
880
|
}
|
|
528
881
|
|
|
882
|
+
get colorSaturation(): number {
|
|
883
|
+
return this._saturation * 10;
|
|
884
|
+
}
|
|
529
885
|
set colorSaturation(colorSaturation: number) {
|
|
530
886
|
this._uniformsDirty = true;
|
|
531
887
|
this._saturation = colorSaturation / 10;
|
|
532
888
|
}
|
|
533
889
|
|
|
890
|
+
get colorBrightness(): number {
|
|
891
|
+
return this._brightness;
|
|
892
|
+
}
|
|
534
893
|
set colorBrightness(colorBrightness: number) {
|
|
535
894
|
this._uniformsDirty = true;
|
|
536
895
|
this._brightness = colorBrightness;
|
|
537
896
|
}
|
|
538
897
|
|
|
898
|
+
get colorBlending(): number {
|
|
899
|
+
return this._colorBlending * 10;
|
|
900
|
+
}
|
|
539
901
|
set colorBlending(colorBlending: number) {
|
|
540
902
|
this._uniformsDirty = true;
|
|
541
903
|
this._colorBlending = colorBlending / 10;
|
|
542
904
|
}
|
|
543
905
|
|
|
906
|
+
get grainScale(): number {
|
|
907
|
+
return this._grainScale;
|
|
908
|
+
}
|
|
544
909
|
set grainScale(grainScale: number) {
|
|
545
910
|
this._uniformsDirty = true;
|
|
546
911
|
this._grainScale = grainScale == 0 ? 1 : grainScale;
|
|
547
912
|
}
|
|
548
913
|
|
|
914
|
+
get grainIntensity(): number {
|
|
915
|
+
return this._grainIntensity;
|
|
916
|
+
}
|
|
549
917
|
set grainIntensity(grainIntensity: number) {
|
|
550
918
|
this._uniformsDirty = true;
|
|
551
919
|
this._grainIntensity = grainIntensity;
|
|
552
920
|
}
|
|
553
921
|
|
|
922
|
+
get grainSparsity(): number {
|
|
923
|
+
return this._grainSparsity;
|
|
924
|
+
}
|
|
554
925
|
set grainSparsity(grainSparsity: number) {
|
|
555
926
|
this._uniformsDirty = true;
|
|
556
927
|
this._grainSparsity = grainSparsity;
|
|
557
928
|
}
|
|
558
929
|
|
|
930
|
+
get grainSpeed(): number {
|
|
931
|
+
return this._grainSpeed;
|
|
932
|
+
}
|
|
559
933
|
set grainSpeed(grainSpeed: number) {
|
|
560
934
|
this._uniformsDirty = true;
|
|
561
935
|
this._grainSpeed = grainSpeed;
|
|
562
936
|
}
|
|
563
937
|
|
|
938
|
+
get wireframe(): boolean {
|
|
939
|
+
return this._wireframe;
|
|
940
|
+
}
|
|
564
941
|
set wireframe(wireframe: boolean) {
|
|
565
942
|
this._uniformsDirty = true;
|
|
566
943
|
this._wireframe = wireframe;
|
|
567
944
|
}
|
|
568
945
|
|
|
946
|
+
get resolution(): number {
|
|
947
|
+
return this._resolution;
|
|
948
|
+
}
|
|
569
949
|
set resolution(resolution: number) {
|
|
570
|
-
this.
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
gl.deleteProgram(this.glState.program);
|
|
574
|
-
gl.deleteBuffer(this.glState.buffers.position);
|
|
575
|
-
gl.deleteBuffer(this.glState.buffers.normal);
|
|
576
|
-
gl.deleteBuffer(this.glState.buffers.uv);
|
|
577
|
-
gl.deleteBuffer(this.glState.buffers.index);
|
|
578
|
-
gl.deleteBuffer(this.glState.buffers.wireframeIndex);
|
|
579
|
-
}
|
|
580
|
-
this.glState = this._initScene(resolution);
|
|
950
|
+
if (this._resolution === resolution) return;
|
|
951
|
+
this._resolution = resolution;
|
|
952
|
+
this._updateGeometry();
|
|
581
953
|
}
|
|
582
954
|
|
|
955
|
+
get backgroundColor(): string {
|
|
956
|
+
return this._backgroundColor;
|
|
957
|
+
}
|
|
583
958
|
set backgroundColor(backgroundColor: string) {
|
|
584
959
|
this._uniformsDirty = true;
|
|
585
960
|
this._backgroundColor = backgroundColor;
|
|
586
961
|
this._backgroundColorRgb = this._hexToRgb(backgroundColor);
|
|
587
962
|
}
|
|
588
963
|
|
|
964
|
+
get backgroundAlpha(): number {
|
|
965
|
+
return this._backgroundAlpha;
|
|
966
|
+
}
|
|
589
967
|
set backgroundAlpha(backgroundAlpha: number) {
|
|
590
968
|
this._uniformsDirty = true;
|
|
591
969
|
this._backgroundAlpha = backgroundAlpha;
|
|
592
970
|
}
|
|
593
971
|
|
|
972
|
+
get yOffset(): number {
|
|
973
|
+
return this._yOffset;
|
|
974
|
+
}
|
|
594
975
|
set yOffset(yOffset: number) {
|
|
595
976
|
this._uniformsDirty = true;
|
|
596
977
|
this._yOffset = yOffset;
|
|
@@ -623,21 +1004,33 @@ export class NeatGradient implements NeatController {
|
|
|
623
1004
|
this._yOffsetFlowMultiplier = value / 1000;
|
|
624
1005
|
}
|
|
625
1006
|
|
|
1007
|
+
get flowDistortionA(): number {
|
|
1008
|
+
return this._flowDistortionA;
|
|
1009
|
+
}
|
|
626
1010
|
set flowDistortionA(value: number) {
|
|
627
1011
|
this._uniformsDirty = true;
|
|
628
1012
|
this._flowDistortionA = value;
|
|
629
1013
|
}
|
|
630
1014
|
|
|
1015
|
+
get flowDistortionB(): number {
|
|
1016
|
+
return this._flowDistortionB;
|
|
1017
|
+
}
|
|
631
1018
|
set flowDistortionB(value: number) {
|
|
632
1019
|
this._uniformsDirty = true;
|
|
633
1020
|
this._flowDistortionB = value;
|
|
634
1021
|
}
|
|
635
1022
|
|
|
1023
|
+
get flowScale(): number {
|
|
1024
|
+
return this._flowScale;
|
|
1025
|
+
}
|
|
636
1026
|
set flowScale(value: number) {
|
|
637
1027
|
this._uniformsDirty = true;
|
|
638
1028
|
this._flowScale = value;
|
|
639
1029
|
}
|
|
640
1030
|
|
|
1031
|
+
get flowEase(): number {
|
|
1032
|
+
return this._flowEase;
|
|
1033
|
+
}
|
|
641
1034
|
set flowEase(value: number) {
|
|
642
1035
|
this._uniformsDirty = true;
|
|
643
1036
|
this._flowEase = value;
|
|
@@ -654,6 +1047,9 @@ export class NeatGradient implements NeatController {
|
|
|
654
1047
|
|
|
655
1048
|
|
|
656
1049
|
|
|
1050
|
+
get enableProceduralTexture(): boolean {
|
|
1051
|
+
return this._enableProceduralTexture;
|
|
1052
|
+
}
|
|
657
1053
|
set enableProceduralTexture(value: boolean) {
|
|
658
1054
|
this._uniformsDirty = true;
|
|
659
1055
|
this._enableProceduralTexture = value;
|
|
@@ -662,6 +1058,9 @@ export class NeatGradient implements NeatController {
|
|
|
662
1058
|
}
|
|
663
1059
|
}
|
|
664
1060
|
|
|
1061
|
+
get textureVoidLikelihood(): number {
|
|
1062
|
+
return this._textureVoidLikelihood;
|
|
1063
|
+
}
|
|
665
1064
|
set textureVoidLikelihood(value: number) {
|
|
666
1065
|
this._uniformsDirty = true;
|
|
667
1066
|
this._textureVoidLikelihood = value;
|
|
@@ -670,6 +1069,9 @@ export class NeatGradient implements NeatController {
|
|
|
670
1069
|
}
|
|
671
1070
|
}
|
|
672
1071
|
|
|
1072
|
+
get textureVoidWidthMin(): number {
|
|
1073
|
+
return this._textureVoidWidthMin;
|
|
1074
|
+
}
|
|
673
1075
|
set textureVoidWidthMin(value: number) {
|
|
674
1076
|
this._uniformsDirty = true;
|
|
675
1077
|
this._textureVoidWidthMin = value;
|
|
@@ -678,6 +1080,9 @@ export class NeatGradient implements NeatController {
|
|
|
678
1080
|
}
|
|
679
1081
|
}
|
|
680
1082
|
|
|
1083
|
+
get textureVoidWidthMax(): number {
|
|
1084
|
+
return this._textureVoidWidthMax;
|
|
1085
|
+
}
|
|
681
1086
|
set textureVoidWidthMax(value: number) {
|
|
682
1087
|
this._uniformsDirty = true;
|
|
683
1088
|
this._textureVoidWidthMax = value;
|
|
@@ -686,6 +1091,9 @@ export class NeatGradient implements NeatController {
|
|
|
686
1091
|
}
|
|
687
1092
|
}
|
|
688
1093
|
|
|
1094
|
+
get textureBandDensity(): number {
|
|
1095
|
+
return this._textureBandDensity;
|
|
1096
|
+
}
|
|
689
1097
|
set textureBandDensity(value: number) {
|
|
690
1098
|
this._uniformsDirty = true;
|
|
691
1099
|
this._textureBandDensity = value;
|
|
@@ -694,6 +1102,9 @@ export class NeatGradient implements NeatController {
|
|
|
694
1102
|
}
|
|
695
1103
|
}
|
|
696
1104
|
|
|
1105
|
+
get textureColorBlending(): number {
|
|
1106
|
+
return this._textureColorBlending;
|
|
1107
|
+
}
|
|
697
1108
|
set textureColorBlending(value: number) {
|
|
698
1109
|
this._uniformsDirty = true;
|
|
699
1110
|
this._textureColorBlending = value;
|
|
@@ -702,6 +1113,9 @@ export class NeatGradient implements NeatController {
|
|
|
702
1113
|
}
|
|
703
1114
|
}
|
|
704
1115
|
|
|
1116
|
+
get textureSeed(): number {
|
|
1117
|
+
return this._textureSeed;
|
|
1118
|
+
}
|
|
705
1119
|
set textureSeed(value: number) {
|
|
706
1120
|
this._uniformsDirty = true;
|
|
707
1121
|
this._textureSeed = value;
|
|
@@ -719,6 +1133,21 @@ export class NeatGradient implements NeatController {
|
|
|
719
1133
|
this._textureEase = value;
|
|
720
1134
|
}
|
|
721
1135
|
|
|
1136
|
+
get transparentTextureVoid(): boolean {
|
|
1137
|
+
return this._transparentTextureVoid;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
set transparentTextureVoid(value: boolean) {
|
|
1141
|
+
this._uniformsDirty = true;
|
|
1142
|
+
this._transparentTextureVoid = value;
|
|
1143
|
+
if (this._enableProceduralTexture) {
|
|
1144
|
+
this._textureNeedsUpdate = true;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
get proceduralBackgroundColor(): string {
|
|
1149
|
+
return this._proceduralBackgroundColor;
|
|
1150
|
+
}
|
|
722
1151
|
set proceduralBackgroundColor(value: string) {
|
|
723
1152
|
this._uniformsDirty = true;
|
|
724
1153
|
this._proceduralBackgroundColor = value;
|
|
@@ -727,27 +1156,93 @@ export class NeatGradient implements NeatController {
|
|
|
727
1156
|
}
|
|
728
1157
|
}
|
|
729
1158
|
|
|
1159
|
+
get textureShapeTriangles(): number {
|
|
1160
|
+
return this._textureShapeTriangles;
|
|
1161
|
+
}
|
|
730
1162
|
set textureShapeTriangles(value: number) {
|
|
731
1163
|
this._uniformsDirty = true;
|
|
732
1164
|
this._textureShapeTriangles = value;
|
|
733
1165
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
734
1166
|
}
|
|
1167
|
+
get textureShapeCircles(): number {
|
|
1168
|
+
return this._textureShapeCircles;
|
|
1169
|
+
}
|
|
735
1170
|
set textureShapeCircles(value: number) {
|
|
736
1171
|
this._uniformsDirty = true;
|
|
737
1172
|
this._textureShapeCircles = value;
|
|
738
1173
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
739
1174
|
}
|
|
1175
|
+
get textureShapeBars(): number {
|
|
1176
|
+
return this._textureShapeBars;
|
|
1177
|
+
}
|
|
740
1178
|
set textureShapeBars(value: number) {
|
|
741
1179
|
this._uniformsDirty = true;
|
|
742
1180
|
this._textureShapeBars = value;
|
|
743
1181
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
744
1182
|
}
|
|
1183
|
+
get textureShapeSquiggles(): number {
|
|
1184
|
+
return this._textureShapeSquiggles;
|
|
1185
|
+
}
|
|
745
1186
|
set textureShapeSquiggles(value: number) {
|
|
746
1187
|
this._uniformsDirty = true;
|
|
747
1188
|
this._textureShapeSquiggles = value;
|
|
748
1189
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
749
1190
|
}
|
|
750
1191
|
|
|
1192
|
+
_updateGeometry() {
|
|
1193
|
+
if (!this.glState) return;
|
|
1194
|
+
const gl = this.glState.gl;
|
|
1195
|
+
const resolution = this._resolution || 1;
|
|
1196
|
+
|
|
1197
|
+
let geometry;
|
|
1198
|
+
if (this._shapeType === 'sphere') {
|
|
1199
|
+
geometry = generateSphereGeometry(this._sphereRadius, 120 * resolution, 120 * resolution);
|
|
1200
|
+
} else if (this._shapeType === 'torus') {
|
|
1201
|
+
geometry = generateTorusGeometry(this._torusRadius, this._torusTube, 120 * resolution, 120 * resolution);
|
|
1202
|
+
} else if (this._shapeType === 'cylinder') {
|
|
1203
|
+
geometry = generateCylinderGeometry(this._cylinderRadius, this._cylinderRadius, this._cylinderHeight, 120 * resolution, 120 * resolution);
|
|
1204
|
+
} else if (this._shapeType === 'ribbon') {
|
|
1205
|
+
geometry = generateRibbonGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution, this._planeBend, this._planeTwist);
|
|
1206
|
+
} else {
|
|
1207
|
+
geometry = generatePlaneGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution);
|
|
1208
|
+
}
|
|
1209
|
+
const { position, normal, uv, index, wireframeIndex } = geometry;
|
|
1210
|
+
|
|
1211
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.position);
|
|
1212
|
+
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
|
|
1213
|
+
|
|
1214
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.normal);
|
|
1215
|
+
gl.bufferData(gl.ARRAY_BUFFER, normal, gl.STATIC_DRAW);
|
|
1216
|
+
|
|
1217
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.uv);
|
|
1218
|
+
gl.bufferData(gl.ARRAY_BUFFER, uv, gl.STATIC_DRAW);
|
|
1219
|
+
|
|
1220
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.index);
|
|
1221
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index, gl.STATIC_DRAW);
|
|
1222
|
+
|
|
1223
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.wireframeIndex);
|
|
1224
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, wireframeIndex, gl.STATIC_DRAW);
|
|
1225
|
+
|
|
1226
|
+
// Restore default bound element buffer
|
|
1227
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.index);
|
|
1228
|
+
|
|
1229
|
+
this.glState.indexCount = index.length;
|
|
1230
|
+
this.glState.wireframeIndexCount = wireframeIndex.length;
|
|
1231
|
+
this.glState.indexType = (index instanceof Uint32Array) ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
1232
|
+
|
|
1233
|
+
// Keep camera updated with the new shapeType and dimensions
|
|
1234
|
+
const width = this._ref.clientWidth;
|
|
1235
|
+
const height = this._ref.clientHeight;
|
|
1236
|
+
updateCamera(this.glState.camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
1237
|
+
|
|
1238
|
+
// Recompute projection matrix
|
|
1239
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
1240
|
+
gl.useProgram(this.glState.program);
|
|
1241
|
+
if (projLoc) gl.uniformMatrix4fv(projLoc, false, this.glState.camera.projectionMatrix.elements);
|
|
1242
|
+
|
|
1243
|
+
this._uniformsDirty = true;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
751
1246
|
_hexToRgb(hex: string): [number, number, number] {
|
|
752
1247
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
753
1248
|
return [
|
|
@@ -774,8 +1269,20 @@ export class NeatGradient implements NeatController {
|
|
|
774
1269
|
|
|
775
1270
|
gl.viewport(0, 0, width, height);
|
|
776
1271
|
|
|
777
|
-
// Generate
|
|
778
|
-
|
|
1272
|
+
// Generate parametric geometry based on shapeType
|
|
1273
|
+
let geometry;
|
|
1274
|
+
if (this._shapeType === 'sphere') {
|
|
1275
|
+
geometry = generateSphereGeometry(this._sphereRadius, 120 * resolution, 120 * resolution);
|
|
1276
|
+
} else if (this._shapeType === 'torus') {
|
|
1277
|
+
geometry = generateTorusGeometry(this._torusRadius, this._torusTube, 120 * resolution, 120 * resolution);
|
|
1278
|
+
} else if (this._shapeType === 'cylinder') {
|
|
1279
|
+
geometry = generateCylinderGeometry(this._cylinderRadius, this._cylinderRadius, this._cylinderHeight, 120 * resolution, 120 * resolution);
|
|
1280
|
+
} else if (this._shapeType === 'ribbon') {
|
|
1281
|
+
geometry = generateRibbonGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution, this._planeBend, this._planeTwist);
|
|
1282
|
+
} else {
|
|
1283
|
+
geometry = generatePlaneGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution);
|
|
1284
|
+
}
|
|
1285
|
+
const { position, normal, uv, index, wireframeIndex } = geometry;
|
|
779
1286
|
|
|
780
1287
|
const positionBuffer = gl.createBuffer()!;
|
|
781
1288
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
@@ -841,7 +1348,7 @@ export class NeatGradient implements NeatController {
|
|
|
841
1348
|
|
|
842
1349
|
const camera = new OrthographicCamera(0, 0, 0, 0, 0, 1000);
|
|
843
1350
|
camera.position = [0, 0, 5];
|
|
844
|
-
updateCamera(camera, width, height);
|
|
1351
|
+
updateCamera(camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
845
1352
|
|
|
846
1353
|
// Define attributes
|
|
847
1354
|
const aPosition = gl.getAttribLocation(program, "position");
|
|
@@ -862,17 +1369,7 @@ export class NeatGradient implements NeatController {
|
|
|
862
1369
|
|
|
863
1370
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
|
864
1371
|
|
|
865
|
-
|
|
866
|
-
// The View Matrix is the inverse of the Camera's position
|
|
867
|
-
// Camera is at [0, 0, 5], so view matrix translates by [0, 0, -5]
|
|
868
|
-
modelViewMatrix.translate(-camera.position[0], -camera.position[1], -camera.position[2]);
|
|
869
|
-
|
|
870
|
-
// The Model Matrix mimicking: plane.rotation.x = -Math.PI / 3.5; plane.position.z = -1;
|
|
871
|
-
modelViewMatrix.translate(0, 0, -1);
|
|
872
|
-
modelViewMatrix.rotateX(-Math.PI / 3.5);
|
|
873
|
-
|
|
874
|
-
const mvLoc = gl.getUniformLocation(program, "modelViewMatrix");
|
|
875
|
-
gl.uniformMatrix4fv(mvLoc, false, modelViewMatrix.elements);
|
|
1372
|
+
// modelViewMatrix is set dynamically in the render loop
|
|
876
1373
|
|
|
877
1374
|
const projLoc = gl.getUniformLocation(program, "projectionMatrix");
|
|
878
1375
|
gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
@@ -887,13 +1384,20 @@ export class NeatGradient implements NeatController {
|
|
|
887
1384
|
gl.uniform1i(colorsCountLoc, COLORS_COUNT);
|
|
888
1385
|
|
|
889
1386
|
const uniformsList = [
|
|
1387
|
+
"projectionMatrix", "modelViewMatrix",
|
|
890
1388
|
"u_time", "u_resolution", "u_color_pressure", "u_wave_frequency_x", "u_wave_frequency_y",
|
|
891
1389
|
"u_wave_amplitude", "u_colors_count", "u_plane_width", "u_plane_height", "u_shadows",
|
|
892
1390
|
"u_highlights", "u_grain_intensity", "u_grain_sparsity", "u_grain_scale", "u_grain_speed",
|
|
893
1391
|
"u_flow_distortion_a", "u_flow_distortion_b", "u_flow_scale", "u_flow_ease", "u_flow_enabled",
|
|
894
1392
|
"u_y_offset", "u_y_offset_wave_multiplier", "u_y_offset_color_multiplier", "u_y_offset_flow_multiplier",
|
|
895
1393
|
|
|
896
|
-
"u_procedural_texture", "u_enable_procedural_texture", "u_texture_ease", "u_saturation", "u_brightness", "u_color_blending"
|
|
1394
|
+
"u_procedural_texture", "u_enable_procedural_texture", "u_texture_ease", "u_transparent_texture_void", "u_saturation", "u_brightness", "u_color_blending",
|
|
1395
|
+
"u_domain_warp_enabled", "u_domain_warp_intensity", "u_domain_warp_scale",
|
|
1396
|
+
"u_vignette_intensity", "u_vignette_radius",
|
|
1397
|
+
"u_fresnel_enabled", "u_fresnel_power", "u_fresnel_intensity", "u_fresnel_color",
|
|
1398
|
+
"u_iridescence_enabled", "u_iridescence_intensity", "u_iridescence_speed",
|
|
1399
|
+
"u_bloom_intensity", "u_bloom_threshold", "u_chromatic_aberration",
|
|
1400
|
+
"u_shape_type", "u_silhouette_fade", "u_cylinder_fade", "u_ribbon_fade"
|
|
897
1401
|
];
|
|
898
1402
|
|
|
899
1403
|
const locations: WebGLState["locations"] = {
|
|
@@ -947,10 +1451,15 @@ export class NeatGradient implements NeatController {
|
|
|
947
1451
|
// Texture size - 1024 provides good balance between quality and performance
|
|
948
1452
|
// Reduced from 2048 for better performance
|
|
949
1453
|
const texSize = 1024;
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1454
|
+
|
|
1455
|
+
if (!this._sourceCanvas) {
|
|
1456
|
+
this._sourceCanvas = document.createElement('canvas');
|
|
1457
|
+
this._sourceCanvas.width = texSize;
|
|
1458
|
+
this._sourceCanvas.height = texSize;
|
|
1459
|
+
this._sourceCtx = this._sourceCanvas.getContext('2d');
|
|
1460
|
+
}
|
|
1461
|
+
const sourceCanvas = this._sourceCanvas;
|
|
1462
|
+
const sCtx = this._sourceCtx;
|
|
954
1463
|
if (!sCtx) return null;
|
|
955
1464
|
|
|
956
1465
|
let seed = this._textureSeed;
|
|
@@ -969,6 +1478,10 @@ export class NeatGradient implements NeatController {
|
|
|
969
1478
|
const colors = this._colors.filter(c => c.enabled).map(c => c.color);
|
|
970
1479
|
if (colors.length === 0) return null;
|
|
971
1480
|
|
|
1481
|
+
const shouldTile = this._shapeType !== 'plane';
|
|
1482
|
+
const dxs = shouldTile ? [-1, 0, 1] : [0];
|
|
1483
|
+
const dys = shouldTile ? [-1, 0, 1] : [0];
|
|
1484
|
+
|
|
972
1485
|
// Helper functions
|
|
973
1486
|
function hexToRgb(hex: string) {
|
|
974
1487
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
@@ -1010,72 +1523,133 @@ export class NeatGradient implements NeatController {
|
|
|
1010
1523
|
|
|
1011
1524
|
// Triangles: use configurable count
|
|
1012
1525
|
for (let i = 0; i < this._textureShapeTriangles; i++) {
|
|
1013
|
-
|
|
1014
|
-
sCtx.beginPath();
|
|
1526
|
+
const fillStyle = getInterColor();
|
|
1015
1527
|
const x = random() * texSize;
|
|
1016
1528
|
const y = random() * texSize;
|
|
1017
1529
|
const s = 100 + random() * 300;
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1530
|
+
const x1 = (random() - 0.5) * s;
|
|
1531
|
+
const y1 = (random() - 0.5) * s;
|
|
1532
|
+
const x2 = (random() - 0.5) * s;
|
|
1533
|
+
const y2 = (random() - 0.5) * s;
|
|
1534
|
+
|
|
1535
|
+
for (const dx of dxs) {
|
|
1536
|
+
for (const dy of dys) {
|
|
1537
|
+
sCtx.fillStyle = fillStyle;
|
|
1538
|
+
sCtx.beginPath();
|
|
1539
|
+
const tx = x + dx * texSize;
|
|
1540
|
+
const ty = y + dy * texSize;
|
|
1541
|
+
sCtx.moveTo(tx, ty);
|
|
1542
|
+
sCtx.lineTo(tx + x1, ty + y1);
|
|
1543
|
+
sCtx.lineTo(tx + x2, ty + y2);
|
|
1544
|
+
sCtx.fill();
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1022
1547
|
}
|
|
1023
1548
|
|
|
1024
1549
|
// Circles / rings: use configurable count
|
|
1025
1550
|
for (let i = 0; i < this._textureShapeCircles; i++) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
sCtx.beginPath();
|
|
1551
|
+
const strokeStyle = getInterColor();
|
|
1552
|
+
const lineWidth = 10 + random() * 50;
|
|
1029
1553
|
const x = random() * texSize;
|
|
1030
1554
|
const y = random() * texSize;
|
|
1031
1555
|
const r = 50 + random() * 150;
|
|
1032
|
-
|
|
1033
|
-
|
|
1556
|
+
|
|
1557
|
+
for (const dx of dxs) {
|
|
1558
|
+
for (const dy of dys) {
|
|
1559
|
+
sCtx.strokeStyle = strokeStyle;
|
|
1560
|
+
sCtx.lineWidth = lineWidth;
|
|
1561
|
+
sCtx.beginPath();
|
|
1562
|
+
sCtx.arc(x + dx * texSize, y + dy * texSize, r, 0, Math.PI * 2);
|
|
1563
|
+
sCtx.stroke();
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1034
1566
|
}
|
|
1035
1567
|
|
|
1036
1568
|
// Bars: use configurable count
|
|
1037
1569
|
for (let i = 0; i < this._textureShapeBars; i++) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1570
|
+
const fillStyle = getInterColor();
|
|
1571
|
+
const x = random() * texSize;
|
|
1572
|
+
const y = random() * texSize;
|
|
1573
|
+
const rot = random() * Math.PI;
|
|
1574
|
+
|
|
1575
|
+
for (const dx of dxs) {
|
|
1576
|
+
for (const dy of dys) {
|
|
1577
|
+
sCtx.fillStyle = fillStyle;
|
|
1578
|
+
sCtx.save();
|
|
1579
|
+
sCtx.translate(x + dx * texSize, y + dy * texSize);
|
|
1580
|
+
sCtx.rotate(rot);
|
|
1581
|
+
sCtx.fillRect(-150, -25, 300, 50);
|
|
1582
|
+
sCtx.restore();
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1044
1585
|
}
|
|
1045
1586
|
|
|
1046
1587
|
// Squiggles: use configurable count
|
|
1047
1588
|
sCtx.lineWidth = 15;
|
|
1048
1589
|
sCtx.lineCap = 'round';
|
|
1049
1590
|
for (let i = 0; i < this._textureShapeSquiggles; i++) {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1591
|
+
const strokeStyle = getInterColor();
|
|
1592
|
+
const x = random() * texSize;
|
|
1593
|
+
const y = random() * texSize;
|
|
1594
|
+
|
|
1595
|
+
const curves: Array<{ cx1: number, cy1: number, cx2: number, cy2: number, ex: number, ey: number }> = [];
|
|
1596
|
+
let cx = 0;
|
|
1597
|
+
let cy = 0;
|
|
1055
1598
|
for (let j = 0; j < 4; j++) {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1599
|
+
const ex = cx + (random() - 0.5) * 300;
|
|
1600
|
+
const ey = cy + (random() - 0.5) * 300;
|
|
1601
|
+
curves.push({
|
|
1602
|
+
cx1: cx + (random() - 0.5) * 300,
|
|
1603
|
+
cy1: cy + (random() - 0.5) * 300,
|
|
1604
|
+
cx2: cx + (random() - 0.5) * 300,
|
|
1605
|
+
cy2: cy + (random() - 0.5) * 300,
|
|
1606
|
+
ex: ex,
|
|
1607
|
+
ey: ey
|
|
1608
|
+
});
|
|
1609
|
+
cx = ex;
|
|
1610
|
+
cy = ey;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
for (const dx of dxs) {
|
|
1614
|
+
for (const dy of dys) {
|
|
1615
|
+
sCtx.strokeStyle = strokeStyle;
|
|
1616
|
+
sCtx.beginPath();
|
|
1617
|
+
const tx = x + dx * texSize;
|
|
1618
|
+
const ty = y + dy * texSize;
|
|
1619
|
+
sCtx.moveTo(tx, ty);
|
|
1620
|
+
|
|
1621
|
+
for (const curve of curves) {
|
|
1622
|
+
sCtx.bezierCurveTo(
|
|
1623
|
+
tx + curve.cx1, ty + curve.cy1,
|
|
1624
|
+
tx + curve.cx2, ty + curve.cy2,
|
|
1625
|
+
tx + curve.ex, ty + curve.ey
|
|
1626
|
+
);
|
|
1627
|
+
}
|
|
1628
|
+
sCtx.stroke();
|
|
1629
|
+
}
|
|
1063
1630
|
}
|
|
1064
|
-
sCtx.stroke();
|
|
1065
1631
|
}
|
|
1066
1632
|
|
|
1067
1633
|
// === MASKED CANVAS ===
|
|
1068
1634
|
// Masking: Seed isolation
|
|
1069
1635
|
setSeed(50000);
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1636
|
+
if (!this._maskedCanvas) {
|
|
1637
|
+
this._maskedCanvas = document.createElement('canvas');
|
|
1638
|
+
this._maskedCanvas.width = texSize;
|
|
1639
|
+
this._maskedCanvas.height = texSize;
|
|
1640
|
+
this._maskedCtx = this._maskedCanvas.getContext('2d');
|
|
1641
|
+
}
|
|
1642
|
+
const canvas = this._maskedCanvas;
|
|
1643
|
+
const ctx = this._maskedCtx;
|
|
1074
1644
|
if (!ctx) return null;
|
|
1075
1645
|
|
|
1076
1646
|
// Start filled with the chosen void color so gaps show that color
|
|
1077
|
-
|
|
1078
|
-
|
|
1647
|
+
if (this._transparentTextureVoid) {
|
|
1648
|
+
ctx.clearRect(0, 0, texSize, texSize);
|
|
1649
|
+
} else {
|
|
1650
|
+
ctx.fillStyle = baseColor;
|
|
1651
|
+
ctx.fillRect(0, 0, texSize, texSize);
|
|
1652
|
+
}
|
|
1079
1653
|
|
|
1080
1654
|
// Determine layout segments (matter vs void)
|
|
1081
1655
|
let layoutHead = 0;
|
|
@@ -1135,7 +1709,346 @@ export class NeatGradient implements NeatController {
|
|
|
1135
1709
|
return tex;
|
|
1136
1710
|
}
|
|
1137
1711
|
|
|
1712
|
+
get silhouetteFade(): number {
|
|
1713
|
+
return this._silhouetteFade;
|
|
1714
|
+
}
|
|
1715
|
+
set silhouetteFade(value: number) {
|
|
1716
|
+
if (this._silhouetteFade !== value) {
|
|
1717
|
+
this._silhouetteFade = value;
|
|
1718
|
+
this._uniformsDirty = true;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
get cylinderFade(): number {
|
|
1723
|
+
return this._cylinderFade;
|
|
1724
|
+
}
|
|
1725
|
+
set cylinderFade(value: number) {
|
|
1726
|
+
if (this._cylinderFade !== value) {
|
|
1727
|
+
this._cylinderFade = value;
|
|
1728
|
+
this._uniformsDirty = true;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
get ribbonFade(): number {
|
|
1733
|
+
return this._ribbonFade;
|
|
1734
|
+
}
|
|
1735
|
+
set ribbonFade(value: number) {
|
|
1736
|
+
if (this._ribbonFade !== value) {
|
|
1737
|
+
this._ribbonFade = value;
|
|
1738
|
+
this._uniformsDirty = true;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
get domainWarpEnabled(): boolean {
|
|
1743
|
+
return this._domainWarpEnabled;
|
|
1744
|
+
}
|
|
1745
|
+
set domainWarpEnabled(enabled: boolean) {
|
|
1746
|
+
if (this._domainWarpEnabled !== enabled) {
|
|
1747
|
+
this._domainWarpEnabled = enabled;
|
|
1748
|
+
this._uniformsDirty = true;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
get domainWarpIntensity(): number {
|
|
1753
|
+
return this._domainWarpIntensity;
|
|
1754
|
+
}
|
|
1755
|
+
set domainWarpIntensity(intensity: number) {
|
|
1756
|
+
if (this._domainWarpIntensity !== intensity) {
|
|
1757
|
+
this._domainWarpIntensity = intensity;
|
|
1758
|
+
this._uniformsDirty = true;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
get domainWarpScale(): number {
|
|
1763
|
+
return this._domainWarpScale;
|
|
1764
|
+
}
|
|
1765
|
+
set domainWarpScale(scale: number) {
|
|
1766
|
+
if (this._domainWarpScale !== scale) {
|
|
1767
|
+
this._domainWarpScale = scale;
|
|
1768
|
+
this._uniformsDirty = true;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
get vignetteIntensity(): number {
|
|
1773
|
+
return this._vignetteIntensity;
|
|
1774
|
+
}
|
|
1775
|
+
set vignetteIntensity(intensity: number) {
|
|
1776
|
+
if (this._vignetteIntensity !== intensity) {
|
|
1777
|
+
this._vignetteIntensity = intensity;
|
|
1778
|
+
this._uniformsDirty = true;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
get vignetteRadius(): number {
|
|
1783
|
+
return this._vignetteRadius;
|
|
1784
|
+
}
|
|
1785
|
+
set vignetteRadius(radius: number) {
|
|
1786
|
+
if (this._vignetteRadius !== radius) {
|
|
1787
|
+
this._vignetteRadius = radius;
|
|
1788
|
+
this._uniformsDirty = true;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
get fresnelEnabled(): boolean {
|
|
1793
|
+
return this._fresnelEnabled;
|
|
1794
|
+
}
|
|
1795
|
+
set fresnelEnabled(enabled: boolean) {
|
|
1796
|
+
if (this._fresnelEnabled !== enabled) {
|
|
1797
|
+
this._fresnelEnabled = enabled;
|
|
1798
|
+
this._uniformsDirty = true;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
get fresnelPower(): number {
|
|
1803
|
+
return this._fresnelPower;
|
|
1804
|
+
}
|
|
1805
|
+
set fresnelPower(power: number) {
|
|
1806
|
+
if (this._fresnelPower !== power) {
|
|
1807
|
+
this._fresnelPower = power;
|
|
1808
|
+
this._uniformsDirty = true;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
get fresnelIntensity(): number {
|
|
1813
|
+
return this._fresnelIntensity;
|
|
1814
|
+
}
|
|
1815
|
+
set fresnelIntensity(intensity: number) {
|
|
1816
|
+
if (this._fresnelIntensity !== intensity) {
|
|
1817
|
+
this._fresnelIntensity = intensity;
|
|
1818
|
+
this._uniformsDirty = true;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
get fresnelColor(): string {
|
|
1823
|
+
return this._fresnelColor;
|
|
1824
|
+
}
|
|
1825
|
+
set fresnelColor(fresnelColor: string) {
|
|
1826
|
+
if (this._fresnelColor !== fresnelColor) {
|
|
1827
|
+
this._fresnelColor = fresnelColor;
|
|
1828
|
+
this._fresnelColorRgb = this._hexToRgb(fresnelColor);
|
|
1829
|
+
this._uniformsDirty = true;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
get iridescenceEnabled(): boolean {
|
|
1834
|
+
return this._iridescenceEnabled;
|
|
1835
|
+
}
|
|
1836
|
+
set iridescenceEnabled(enabled: boolean) {
|
|
1837
|
+
if (this._iridescenceEnabled !== enabled) {
|
|
1838
|
+
this._iridescenceEnabled = enabled;
|
|
1839
|
+
this._uniformsDirty = true;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
get iridescenceIntensity(): number {
|
|
1844
|
+
return this._iridescenceIntensity;
|
|
1845
|
+
}
|
|
1846
|
+
set iridescenceIntensity(intensity: number) {
|
|
1847
|
+
if (this._iridescenceIntensity !== intensity) {
|
|
1848
|
+
this._iridescenceIntensity = intensity;
|
|
1849
|
+
this._uniformsDirty = true;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
get iridescenceSpeed(): number {
|
|
1854
|
+
return this._iridescenceSpeed;
|
|
1855
|
+
}
|
|
1856
|
+
set iridescenceSpeed(speed: number) {
|
|
1857
|
+
if (this._iridescenceSpeed !== speed) {
|
|
1858
|
+
this._iridescenceSpeed = speed;
|
|
1859
|
+
this._uniformsDirty = true;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
get bloomIntensity(): number {
|
|
1864
|
+
return this._bloomIntensity;
|
|
1865
|
+
}
|
|
1866
|
+
set bloomIntensity(intensity: number) {
|
|
1867
|
+
if (this._bloomIntensity !== intensity) {
|
|
1868
|
+
this._bloomIntensity = intensity;
|
|
1869
|
+
this._uniformsDirty = true;
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
get bloomThreshold(): number {
|
|
1874
|
+
return this._bloomThreshold;
|
|
1875
|
+
}
|
|
1876
|
+
set bloomThreshold(threshold: number) {
|
|
1877
|
+
if (this._bloomThreshold !== threshold) {
|
|
1878
|
+
this._bloomThreshold = threshold;
|
|
1879
|
+
this._uniformsDirty = true;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
get chromaticAberration(): number {
|
|
1884
|
+
return this._chromaticAberration;
|
|
1885
|
+
}
|
|
1886
|
+
set chromaticAberration(aberration: number) {
|
|
1887
|
+
if (this._chromaticAberration !== aberration) {
|
|
1888
|
+
this._chromaticAberration = aberration;
|
|
1889
|
+
this._uniformsDirty = true;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
// Getters and Setters for 3D Shapes
|
|
1894
|
+
get shapeType(): 'plane' | 'sphere' | 'torus' | 'cylinder' | 'ribbon' {
|
|
1895
|
+
return this._shapeType;
|
|
1896
|
+
}
|
|
1897
|
+
set shapeType(val: 'plane' | 'sphere' | 'torus' | 'cylinder' | 'ribbon') {
|
|
1898
|
+
if (this._shapeType !== val) {
|
|
1899
|
+
this._shapeType = val;
|
|
1900
|
+
this._updateGeometry();
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
get shapeRotationX(): number { return this._shapeRotationX; }
|
|
1905
|
+
set shapeRotationX(val: number) {
|
|
1906
|
+
this._shapeRotationX = val;
|
|
1907
|
+
this._uniformsDirty = true;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
get shapeRotationY(): number { return this._shapeRotationY; }
|
|
1911
|
+
set shapeRotationY(val: number) {
|
|
1912
|
+
this._shapeRotationY = val;
|
|
1913
|
+
this._uniformsDirty = true;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
get shapeRotationZ(): number { return this._shapeRotationZ; }
|
|
1917
|
+
set shapeRotationZ(val: number) {
|
|
1918
|
+
this._shapeRotationZ = val;
|
|
1919
|
+
this._uniformsDirty = true;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
get shapeAutoRotateSpeedX(): number { return this._shapeAutoRotateSpeedX; }
|
|
1923
|
+
set shapeAutoRotateSpeedX(val: number) {
|
|
1924
|
+
this._shapeAutoRotateSpeedX = val;
|
|
1925
|
+
this._uniformsDirty = true;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
get shapeAutoRotateSpeedY(): number { return this._shapeAutoRotateSpeedY; }
|
|
1929
|
+
set shapeAutoRotateSpeedY(val: number) {
|
|
1930
|
+
this._shapeAutoRotateSpeedY = val;
|
|
1931
|
+
this._uniformsDirty = true;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
get sphereRadius(): number { return this._sphereRadius; }
|
|
1935
|
+
set sphereRadius(val: number) {
|
|
1936
|
+
if (this._sphereRadius !== val) {
|
|
1937
|
+
this._sphereRadius = val;
|
|
1938
|
+
this._updateGeometry();
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
get torusRadius(): number { return this._torusRadius; }
|
|
1943
|
+
set torusRadius(val: number) {
|
|
1944
|
+
if (this._torusRadius !== val) {
|
|
1945
|
+
this._torusRadius = val;
|
|
1946
|
+
this._updateGeometry();
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
get torusTube(): number { return this._torusTube; }
|
|
1951
|
+
set torusTube(val: number) {
|
|
1952
|
+
if (this._torusTube !== val) {
|
|
1953
|
+
this._torusTube = val;
|
|
1954
|
+
this._updateGeometry();
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
get cylinderRadius(): number { return this._cylinderRadius; }
|
|
1959
|
+
set cylinderRadius(val: number) {
|
|
1960
|
+
if (this._cylinderRadius !== val) {
|
|
1961
|
+
this._cylinderRadius = val;
|
|
1962
|
+
this._updateGeometry();
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
get cylinderHeight(): number { return this._cylinderHeight; }
|
|
1967
|
+
set cylinderHeight(val: number) {
|
|
1968
|
+
if (this._cylinderHeight !== val) {
|
|
1969
|
+
this._cylinderHeight = val;
|
|
1970
|
+
this._updateGeometry();
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
get planeBend(): number { return this._planeBend; }
|
|
1975
|
+
set planeBend(val: number) {
|
|
1976
|
+
if (this._planeBend !== val) {
|
|
1977
|
+
this._planeBend = val;
|
|
1978
|
+
this._updateGeometry();
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
get planeTwist(): number { return this._planeTwist; }
|
|
1983
|
+
set planeTwist(val: number) {
|
|
1984
|
+
if (this._planeTwist !== val) {
|
|
1985
|
+
this._planeTwist = val;
|
|
1986
|
+
this._updateGeometry();
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
// Camera Getters and Setters
|
|
1991
|
+
get cameraLock(): boolean { return this._cameraLock; }
|
|
1992
|
+
set cameraLock(val: boolean) {
|
|
1993
|
+
this._cameraLock = val;
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
get cameraX(): number { return this._cameraX; }
|
|
1997
|
+
set cameraX(val: number) {
|
|
1998
|
+
this._cameraX = val;
|
|
1999
|
+
this._uniformsDirty = true;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
get cameraY(): number { return this._cameraY; }
|
|
2003
|
+
set cameraY(val: number) {
|
|
2004
|
+
this._cameraY = val;
|
|
2005
|
+
this._uniformsDirty = true;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
get cameraZ(): number { return this._cameraZ; }
|
|
2009
|
+
set cameraZ(val: number) {
|
|
2010
|
+
this._cameraZ = val;
|
|
2011
|
+
this._uniformsDirty = true;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
get cameraRotationX(): number { return this._cameraRotationX; }
|
|
2015
|
+
set cameraRotationX(val: number) {
|
|
2016
|
+
this._cameraRotationX = val;
|
|
2017
|
+
this._uniformsDirty = true;
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
get cameraRotationY(): number { return this._cameraRotationY; }
|
|
2021
|
+
set cameraRotationY(val: number) {
|
|
2022
|
+
this._cameraRotationY = val;
|
|
2023
|
+
this._uniformsDirty = true;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
get cameraRotationZ(): number { return this._cameraRotationZ; }
|
|
2027
|
+
set cameraRotationZ(val: number) {
|
|
2028
|
+
this._cameraRotationZ = val;
|
|
2029
|
+
this._uniformsDirty = true;
|
|
2030
|
+
}
|
|
1138
2031
|
|
|
2032
|
+
get cameraZoom(): number { return this._cameraZoom; }
|
|
2033
|
+
set cameraZoom(val: number) {
|
|
2034
|
+
if (this._cameraZoom !== val) {
|
|
2035
|
+
this._cameraZoom = val;
|
|
2036
|
+
this._updateCameraFrustum();
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
_updateCameraFrustum() {
|
|
2041
|
+
if (!this.glState) return;
|
|
2042
|
+
const gl = this.glState.gl;
|
|
2043
|
+
const width = this._ref.clientWidth;
|
|
2044
|
+
const height = this._ref.clientHeight;
|
|
2045
|
+
updateCamera(this.glState.camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
2046
|
+
|
|
2047
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
2048
|
+
gl.useProgram(this.glState.program);
|
|
2049
|
+
if (projLoc) gl.uniformMatrix4fv(projLoc, false, this.glState.camera.projectionMatrix.elements);
|
|
2050
|
+
this._uniformsDirty = true;
|
|
2051
|
+
}
|
|
1139
2052
|
}
|
|
1140
2053
|
|
|
1141
2054
|
|