@firecms/neat 0.8.0 → 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 +154 -0
- package/dist/NeatGradient.js +758 -66
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.es.js +1316 -703
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +389 -403
- 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 +176 -33
- package/dist/shaders.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/package.json +1 -1
- package/src/NeatGradient.ts +876 -72
- package/src/math.ts +373 -28
- package/src/shaders.ts +176 -33
- package/src/types.ts +29 -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,34 @@ 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
|
+
|
|
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;
|
|
112
142
|
|
|
113
143
|
private _proceduralTexture: WebGLTexture | null = null;
|
|
114
144
|
private _proceduralBackgroundColor: string = "#000000";
|
|
@@ -130,6 +160,12 @@ export class NeatGradient implements NeatController {
|
|
|
130
160
|
private _yOffsetColorMultiplier: number = 0.004;
|
|
131
161
|
private _yOffsetFlowMultiplier: number = 0.004;
|
|
132
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
|
+
|
|
133
169
|
// Performance optimizations
|
|
134
170
|
private _resizeTimeoutId: number | null = null;
|
|
135
171
|
private _textureNeedsUpdate: boolean = false;
|
|
@@ -184,6 +220,7 @@ export class NeatGradient implements NeatController {
|
|
|
184
220
|
textureSeed = 333,
|
|
185
221
|
textureEase = 0.5,
|
|
186
222
|
proceduralBackgroundColor = "#000000",
|
|
223
|
+
transparentTextureVoid = false,
|
|
187
224
|
textureShapeTriangles = 20,
|
|
188
225
|
textureShapeCircles = 15,
|
|
189
226
|
textureShapeBars = 15,
|
|
@@ -192,7 +229,7 @@ export class NeatGradient implements NeatController {
|
|
|
192
229
|
domainWarpEnabled = false,
|
|
193
230
|
domainWarpIntensity = 0.5,
|
|
194
231
|
domainWarpScale = 1.0,
|
|
195
|
-
vignetteIntensity = 0.
|
|
232
|
+
vignetteIntensity = 0.0,
|
|
196
233
|
vignetteRadius = 0.8,
|
|
197
234
|
fresnelEnabled = false,
|
|
198
235
|
fresnelPower = 2.0,
|
|
@@ -204,6 +241,34 @@ export class NeatGradient implements NeatController {
|
|
|
204
241
|
bloomIntensity = 0.0,
|
|
205
242
|
bloomThreshold = 0.7,
|
|
206
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,
|
|
207
272
|
} = config;
|
|
208
273
|
|
|
209
274
|
|
|
@@ -219,6 +284,7 @@ export class NeatGradient implements NeatController {
|
|
|
219
284
|
this.waveFrequencyY = waveFrequencyY;
|
|
220
285
|
this.waveAmplitude = waveAmplitude;
|
|
221
286
|
this.colorBlending = colorBlending;
|
|
287
|
+
this._resolution = resolution;
|
|
222
288
|
this.grainScale = grainScale;
|
|
223
289
|
this.grainIntensity = grainIntensity;
|
|
224
290
|
this.grainSparsity = grainSparsity;
|
|
@@ -255,6 +321,7 @@ export class NeatGradient implements NeatController {
|
|
|
255
321
|
this.textureSeed = textureSeed;
|
|
256
322
|
this.textureEase = textureEase;
|
|
257
323
|
this._proceduralBackgroundColor = proceduralBackgroundColor;
|
|
324
|
+
this.transparentTextureVoid = transparentTextureVoid;
|
|
258
325
|
|
|
259
326
|
this._textureShapeTriangles = textureShapeTriangles;
|
|
260
327
|
this._textureShapeCircles = textureShapeCircles;
|
|
@@ -276,6 +343,32 @@ export class NeatGradient implements NeatController {
|
|
|
276
343
|
this.bloomIntensity = bloomIntensity;
|
|
277
344
|
this.bloomThreshold = bloomThreshold;
|
|
278
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;
|
|
279
372
|
|
|
280
373
|
this.glState = this._initScene(resolution);
|
|
281
374
|
|
|
@@ -306,6 +399,45 @@ export class NeatGradient implements NeatController {
|
|
|
306
399
|
|
|
307
400
|
gl.uniform1f(locations.uniforms['u_time'], tick);
|
|
308
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
|
+
|
|
309
441
|
// Only upload static uniforms when they've been modified
|
|
310
442
|
if (this._uniformsDirty) {
|
|
311
443
|
gl.uniform2f(locations.uniforms['u_resolution'], this._ref.clientWidth, this._ref.clientHeight);
|
|
@@ -333,8 +465,16 @@ export class NeatGradient implements NeatController {
|
|
|
333
465
|
gl.uniform1f(locations.uniforms['u_flow_ease'], this._flowEase);
|
|
334
466
|
gl.uniform1f(locations.uniforms['u_flow_enabled'], this._flowEnabled ? 1.0 : 0.0);
|
|
335
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
|
+
|
|
336
475
|
gl.uniform1f(locations.uniforms['u_enable_procedural_texture'], this._enableProceduralTexture ? 1.0 : 0.0);
|
|
337
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);
|
|
338
478
|
|
|
339
479
|
gl.uniform1f(locations.uniforms['u_domain_warp_enabled'], this._domainWarpEnabled ? 1.0 : 0.0);
|
|
340
480
|
gl.uniform1f(locations.uniforms['u_domain_warp_intensity'], this._domainWarpIntensity);
|
|
@@ -355,6 +495,9 @@ export class NeatGradient implements NeatController {
|
|
|
355
495
|
gl.uniform1f(locations.uniforms['u_bloom_intensity'], this._bloomIntensity);
|
|
356
496
|
gl.uniform1f(locations.uniforms['u_bloom_threshold'], this._bloomThreshold);
|
|
357
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);
|
|
358
501
|
|
|
359
502
|
this._uniformsDirty = false;
|
|
360
503
|
}
|
|
@@ -430,14 +573,14 @@ export class NeatGradient implements NeatController {
|
|
|
430
573
|
|
|
431
574
|
gl.viewport(0, 0, width, height);
|
|
432
575
|
|
|
433
|
-
updateCamera(camera, width, height);
|
|
576
|
+
updateCamera(camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
434
577
|
|
|
435
578
|
|
|
436
579
|
|
|
437
580
|
// Recompute projection matrix on resize
|
|
438
|
-
const projLoc =
|
|
581
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
439
582
|
gl.useProgram(this.glState.program);
|
|
440
|
-
gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
583
|
+
if (projLoc) gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
441
584
|
};
|
|
442
585
|
|
|
443
586
|
// Debounce resize to prevent excessive operations
|
|
@@ -493,36 +636,226 @@ export class NeatGradient implements NeatController {
|
|
|
493
636
|
downloadURI(dataURL, filename);
|
|
494
637
|
}
|
|
495
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
|
+
}
|
|
496
811
|
set speed(speed: number) {
|
|
497
812
|
this._uniformsDirty = true;
|
|
498
813
|
this._speed = speed / 20;
|
|
499
814
|
}
|
|
500
815
|
|
|
816
|
+
get horizontalPressure(): number {
|
|
817
|
+
return this._horizontalPressure * 4;
|
|
818
|
+
}
|
|
501
819
|
set horizontalPressure(horizontalPressure: number) {
|
|
502
820
|
this._uniformsDirty = true;
|
|
503
821
|
this._horizontalPressure = horizontalPressure / 4;
|
|
504
822
|
}
|
|
505
823
|
|
|
824
|
+
get verticalPressure(): number {
|
|
825
|
+
return this._verticalPressure * 4;
|
|
826
|
+
}
|
|
506
827
|
set verticalPressure(verticalPressure: number) {
|
|
507
828
|
this._uniformsDirty = true;
|
|
508
829
|
this._verticalPressure = verticalPressure / 4;
|
|
509
830
|
}
|
|
510
831
|
|
|
832
|
+
get waveFrequencyX(): number {
|
|
833
|
+
return this._waveFrequencyX / 0.04;
|
|
834
|
+
}
|
|
511
835
|
set waveFrequencyX(waveFrequencyX: number) {
|
|
512
836
|
this._uniformsDirty = true;
|
|
513
837
|
this._waveFrequencyX = waveFrequencyX * 0.04;
|
|
514
838
|
}
|
|
515
839
|
|
|
840
|
+
get waveFrequencyY(): number {
|
|
841
|
+
return this._waveFrequencyY / 0.04;
|
|
842
|
+
}
|
|
516
843
|
set waveFrequencyY(waveFrequencyY: number) {
|
|
517
844
|
this._uniformsDirty = true;
|
|
518
845
|
this._waveFrequencyY = waveFrequencyY * 0.04;
|
|
519
846
|
}
|
|
520
847
|
|
|
848
|
+
get waveAmplitude(): number {
|
|
849
|
+
return this._waveAmplitude / 0.75;
|
|
850
|
+
}
|
|
521
851
|
set waveAmplitude(waveAmplitude: number) {
|
|
522
852
|
this._uniformsDirty = true;
|
|
523
853
|
this._waveAmplitude = waveAmplitude * .75;
|
|
524
854
|
}
|
|
525
855
|
|
|
856
|
+
get colors(): NeatColor[] {
|
|
857
|
+
return this._colors;
|
|
858
|
+
}
|
|
526
859
|
set colors(colors: NeatColor[]) {
|
|
527
860
|
this._uniformsDirty = true;
|
|
528
861
|
this._colors = colors;
|
|
@@ -530,81 +863,115 @@ export class NeatGradient implements NeatController {
|
|
|
530
863
|
this._colorsChanged = true;
|
|
531
864
|
}
|
|
532
865
|
|
|
866
|
+
get highlights(): number {
|
|
867
|
+
return this._highlights * 100;
|
|
868
|
+
}
|
|
533
869
|
set highlights(highlights: number) {
|
|
534
870
|
this._uniformsDirty = true;
|
|
535
871
|
this._highlights = highlights / 100;
|
|
536
872
|
}
|
|
537
873
|
|
|
874
|
+
get shadows(): number {
|
|
875
|
+
return this._shadows * 100;
|
|
876
|
+
}
|
|
538
877
|
set shadows(shadows: number) {
|
|
539
878
|
this._uniformsDirty = true;
|
|
540
879
|
this._shadows = shadows / 100;
|
|
541
880
|
}
|
|
542
881
|
|
|
882
|
+
get colorSaturation(): number {
|
|
883
|
+
return this._saturation * 10;
|
|
884
|
+
}
|
|
543
885
|
set colorSaturation(colorSaturation: number) {
|
|
544
886
|
this._uniformsDirty = true;
|
|
545
887
|
this._saturation = colorSaturation / 10;
|
|
546
888
|
}
|
|
547
889
|
|
|
890
|
+
get colorBrightness(): number {
|
|
891
|
+
return this._brightness;
|
|
892
|
+
}
|
|
548
893
|
set colorBrightness(colorBrightness: number) {
|
|
549
894
|
this._uniformsDirty = true;
|
|
550
895
|
this._brightness = colorBrightness;
|
|
551
896
|
}
|
|
552
897
|
|
|
898
|
+
get colorBlending(): number {
|
|
899
|
+
return this._colorBlending * 10;
|
|
900
|
+
}
|
|
553
901
|
set colorBlending(colorBlending: number) {
|
|
554
902
|
this._uniformsDirty = true;
|
|
555
903
|
this._colorBlending = colorBlending / 10;
|
|
556
904
|
}
|
|
557
905
|
|
|
906
|
+
get grainScale(): number {
|
|
907
|
+
return this._grainScale;
|
|
908
|
+
}
|
|
558
909
|
set grainScale(grainScale: number) {
|
|
559
910
|
this._uniformsDirty = true;
|
|
560
911
|
this._grainScale = grainScale == 0 ? 1 : grainScale;
|
|
561
912
|
}
|
|
562
913
|
|
|
914
|
+
get grainIntensity(): number {
|
|
915
|
+
return this._grainIntensity;
|
|
916
|
+
}
|
|
563
917
|
set grainIntensity(grainIntensity: number) {
|
|
564
918
|
this._uniformsDirty = true;
|
|
565
919
|
this._grainIntensity = grainIntensity;
|
|
566
920
|
}
|
|
567
921
|
|
|
922
|
+
get grainSparsity(): number {
|
|
923
|
+
return this._grainSparsity;
|
|
924
|
+
}
|
|
568
925
|
set grainSparsity(grainSparsity: number) {
|
|
569
926
|
this._uniformsDirty = true;
|
|
570
927
|
this._grainSparsity = grainSparsity;
|
|
571
928
|
}
|
|
572
929
|
|
|
930
|
+
get grainSpeed(): number {
|
|
931
|
+
return this._grainSpeed;
|
|
932
|
+
}
|
|
573
933
|
set grainSpeed(grainSpeed: number) {
|
|
574
934
|
this._uniformsDirty = true;
|
|
575
935
|
this._grainSpeed = grainSpeed;
|
|
576
936
|
}
|
|
577
937
|
|
|
938
|
+
get wireframe(): boolean {
|
|
939
|
+
return this._wireframe;
|
|
940
|
+
}
|
|
578
941
|
set wireframe(wireframe: boolean) {
|
|
579
942
|
this._uniformsDirty = true;
|
|
580
943
|
this._wireframe = wireframe;
|
|
581
944
|
}
|
|
582
945
|
|
|
946
|
+
get resolution(): number {
|
|
947
|
+
return this._resolution;
|
|
948
|
+
}
|
|
583
949
|
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);
|
|
950
|
+
if (this._resolution === resolution) return;
|
|
951
|
+
this._resolution = resolution;
|
|
952
|
+
this._updateGeometry();
|
|
595
953
|
}
|
|
596
954
|
|
|
955
|
+
get backgroundColor(): string {
|
|
956
|
+
return this._backgroundColor;
|
|
957
|
+
}
|
|
597
958
|
set backgroundColor(backgroundColor: string) {
|
|
598
959
|
this._uniformsDirty = true;
|
|
599
960
|
this._backgroundColor = backgroundColor;
|
|
600
961
|
this._backgroundColorRgb = this._hexToRgb(backgroundColor);
|
|
601
962
|
}
|
|
602
963
|
|
|
964
|
+
get backgroundAlpha(): number {
|
|
965
|
+
return this._backgroundAlpha;
|
|
966
|
+
}
|
|
603
967
|
set backgroundAlpha(backgroundAlpha: number) {
|
|
604
968
|
this._uniformsDirty = true;
|
|
605
969
|
this._backgroundAlpha = backgroundAlpha;
|
|
606
970
|
}
|
|
607
971
|
|
|
972
|
+
get yOffset(): number {
|
|
973
|
+
return this._yOffset;
|
|
974
|
+
}
|
|
608
975
|
set yOffset(yOffset: number) {
|
|
609
976
|
this._uniformsDirty = true;
|
|
610
977
|
this._yOffset = yOffset;
|
|
@@ -637,21 +1004,33 @@ export class NeatGradient implements NeatController {
|
|
|
637
1004
|
this._yOffsetFlowMultiplier = value / 1000;
|
|
638
1005
|
}
|
|
639
1006
|
|
|
1007
|
+
get flowDistortionA(): number {
|
|
1008
|
+
return this._flowDistortionA;
|
|
1009
|
+
}
|
|
640
1010
|
set flowDistortionA(value: number) {
|
|
641
1011
|
this._uniformsDirty = true;
|
|
642
1012
|
this._flowDistortionA = value;
|
|
643
1013
|
}
|
|
644
1014
|
|
|
1015
|
+
get flowDistortionB(): number {
|
|
1016
|
+
return this._flowDistortionB;
|
|
1017
|
+
}
|
|
645
1018
|
set flowDistortionB(value: number) {
|
|
646
1019
|
this._uniformsDirty = true;
|
|
647
1020
|
this._flowDistortionB = value;
|
|
648
1021
|
}
|
|
649
1022
|
|
|
1023
|
+
get flowScale(): number {
|
|
1024
|
+
return this._flowScale;
|
|
1025
|
+
}
|
|
650
1026
|
set flowScale(value: number) {
|
|
651
1027
|
this._uniformsDirty = true;
|
|
652
1028
|
this._flowScale = value;
|
|
653
1029
|
}
|
|
654
1030
|
|
|
1031
|
+
get flowEase(): number {
|
|
1032
|
+
return this._flowEase;
|
|
1033
|
+
}
|
|
655
1034
|
set flowEase(value: number) {
|
|
656
1035
|
this._uniformsDirty = true;
|
|
657
1036
|
this._flowEase = value;
|
|
@@ -668,6 +1047,9 @@ export class NeatGradient implements NeatController {
|
|
|
668
1047
|
|
|
669
1048
|
|
|
670
1049
|
|
|
1050
|
+
get enableProceduralTexture(): boolean {
|
|
1051
|
+
return this._enableProceduralTexture;
|
|
1052
|
+
}
|
|
671
1053
|
set enableProceduralTexture(value: boolean) {
|
|
672
1054
|
this._uniformsDirty = true;
|
|
673
1055
|
this._enableProceduralTexture = value;
|
|
@@ -676,6 +1058,9 @@ export class NeatGradient implements NeatController {
|
|
|
676
1058
|
}
|
|
677
1059
|
}
|
|
678
1060
|
|
|
1061
|
+
get textureVoidLikelihood(): number {
|
|
1062
|
+
return this._textureVoidLikelihood;
|
|
1063
|
+
}
|
|
679
1064
|
set textureVoidLikelihood(value: number) {
|
|
680
1065
|
this._uniformsDirty = true;
|
|
681
1066
|
this._textureVoidLikelihood = value;
|
|
@@ -684,6 +1069,9 @@ export class NeatGradient implements NeatController {
|
|
|
684
1069
|
}
|
|
685
1070
|
}
|
|
686
1071
|
|
|
1072
|
+
get textureVoidWidthMin(): number {
|
|
1073
|
+
return this._textureVoidWidthMin;
|
|
1074
|
+
}
|
|
687
1075
|
set textureVoidWidthMin(value: number) {
|
|
688
1076
|
this._uniformsDirty = true;
|
|
689
1077
|
this._textureVoidWidthMin = value;
|
|
@@ -692,6 +1080,9 @@ export class NeatGradient implements NeatController {
|
|
|
692
1080
|
}
|
|
693
1081
|
}
|
|
694
1082
|
|
|
1083
|
+
get textureVoidWidthMax(): number {
|
|
1084
|
+
return this._textureVoidWidthMax;
|
|
1085
|
+
}
|
|
695
1086
|
set textureVoidWidthMax(value: number) {
|
|
696
1087
|
this._uniformsDirty = true;
|
|
697
1088
|
this._textureVoidWidthMax = value;
|
|
@@ -700,6 +1091,9 @@ export class NeatGradient implements NeatController {
|
|
|
700
1091
|
}
|
|
701
1092
|
}
|
|
702
1093
|
|
|
1094
|
+
get textureBandDensity(): number {
|
|
1095
|
+
return this._textureBandDensity;
|
|
1096
|
+
}
|
|
703
1097
|
set textureBandDensity(value: number) {
|
|
704
1098
|
this._uniformsDirty = true;
|
|
705
1099
|
this._textureBandDensity = value;
|
|
@@ -708,6 +1102,9 @@ export class NeatGradient implements NeatController {
|
|
|
708
1102
|
}
|
|
709
1103
|
}
|
|
710
1104
|
|
|
1105
|
+
get textureColorBlending(): number {
|
|
1106
|
+
return this._textureColorBlending;
|
|
1107
|
+
}
|
|
711
1108
|
set textureColorBlending(value: number) {
|
|
712
1109
|
this._uniformsDirty = true;
|
|
713
1110
|
this._textureColorBlending = value;
|
|
@@ -716,6 +1113,9 @@ export class NeatGradient implements NeatController {
|
|
|
716
1113
|
}
|
|
717
1114
|
}
|
|
718
1115
|
|
|
1116
|
+
get textureSeed(): number {
|
|
1117
|
+
return this._textureSeed;
|
|
1118
|
+
}
|
|
719
1119
|
set textureSeed(value: number) {
|
|
720
1120
|
this._uniformsDirty = true;
|
|
721
1121
|
this._textureSeed = value;
|
|
@@ -733,6 +1133,21 @@ export class NeatGradient implements NeatController {
|
|
|
733
1133
|
this._textureEase = value;
|
|
734
1134
|
}
|
|
735
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
|
+
}
|
|
736
1151
|
set proceduralBackgroundColor(value: string) {
|
|
737
1152
|
this._uniformsDirty = true;
|
|
738
1153
|
this._proceduralBackgroundColor = value;
|
|
@@ -741,27 +1156,93 @@ export class NeatGradient implements NeatController {
|
|
|
741
1156
|
}
|
|
742
1157
|
}
|
|
743
1158
|
|
|
1159
|
+
get textureShapeTriangles(): number {
|
|
1160
|
+
return this._textureShapeTriangles;
|
|
1161
|
+
}
|
|
744
1162
|
set textureShapeTriangles(value: number) {
|
|
745
1163
|
this._uniformsDirty = true;
|
|
746
1164
|
this._textureShapeTriangles = value;
|
|
747
1165
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
748
1166
|
}
|
|
1167
|
+
get textureShapeCircles(): number {
|
|
1168
|
+
return this._textureShapeCircles;
|
|
1169
|
+
}
|
|
749
1170
|
set textureShapeCircles(value: number) {
|
|
750
1171
|
this._uniformsDirty = true;
|
|
751
1172
|
this._textureShapeCircles = value;
|
|
752
1173
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
753
1174
|
}
|
|
1175
|
+
get textureShapeBars(): number {
|
|
1176
|
+
return this._textureShapeBars;
|
|
1177
|
+
}
|
|
754
1178
|
set textureShapeBars(value: number) {
|
|
755
1179
|
this._uniformsDirty = true;
|
|
756
1180
|
this._textureShapeBars = value;
|
|
757
1181
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
758
1182
|
}
|
|
1183
|
+
get textureShapeSquiggles(): number {
|
|
1184
|
+
return this._textureShapeSquiggles;
|
|
1185
|
+
}
|
|
759
1186
|
set textureShapeSquiggles(value: number) {
|
|
760
1187
|
this._uniformsDirty = true;
|
|
761
1188
|
this._textureShapeSquiggles = value;
|
|
762
1189
|
if (this._enableProceduralTexture) this._textureNeedsUpdate = true;
|
|
763
1190
|
}
|
|
764
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
|
+
|
|
765
1246
|
_hexToRgb(hex: string): [number, number, number] {
|
|
766
1247
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
767
1248
|
return [
|
|
@@ -788,8 +1269,20 @@ export class NeatGradient implements NeatController {
|
|
|
788
1269
|
|
|
789
1270
|
gl.viewport(0, 0, width, height);
|
|
790
1271
|
|
|
791
|
-
// Generate
|
|
792
|
-
|
|
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;
|
|
793
1286
|
|
|
794
1287
|
const positionBuffer = gl.createBuffer()!;
|
|
795
1288
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
@@ -855,7 +1348,7 @@ export class NeatGradient implements NeatController {
|
|
|
855
1348
|
|
|
856
1349
|
const camera = new OrthographicCamera(0, 0, 0, 0, 0, 1000);
|
|
857
1350
|
camera.position = [0, 0, 5];
|
|
858
|
-
updateCamera(camera, width, height);
|
|
1351
|
+
updateCamera(camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
859
1352
|
|
|
860
1353
|
// Define attributes
|
|
861
1354
|
const aPosition = gl.getAttribLocation(program, "position");
|
|
@@ -876,17 +1369,7 @@ export class NeatGradient implements NeatController {
|
|
|
876
1369
|
|
|
877
1370
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
|
878
1371
|
|
|
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);
|
|
1372
|
+
// modelViewMatrix is set dynamically in the render loop
|
|
890
1373
|
|
|
891
1374
|
const projLoc = gl.getUniformLocation(program, "projectionMatrix");
|
|
892
1375
|
gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
@@ -901,18 +1384,20 @@ export class NeatGradient implements NeatController {
|
|
|
901
1384
|
gl.uniform1i(colorsCountLoc, COLORS_COUNT);
|
|
902
1385
|
|
|
903
1386
|
const uniformsList = [
|
|
1387
|
+
"projectionMatrix", "modelViewMatrix",
|
|
904
1388
|
"u_time", "u_resolution", "u_color_pressure", "u_wave_frequency_x", "u_wave_frequency_y",
|
|
905
1389
|
"u_wave_amplitude", "u_colors_count", "u_plane_width", "u_plane_height", "u_shadows",
|
|
906
1390
|
"u_highlights", "u_grain_intensity", "u_grain_sparsity", "u_grain_scale", "u_grain_speed",
|
|
907
1391
|
"u_flow_distortion_a", "u_flow_distortion_b", "u_flow_scale", "u_flow_ease", "u_flow_enabled",
|
|
908
1392
|
"u_y_offset", "u_y_offset_wave_multiplier", "u_y_offset_color_multiplier", "u_y_offset_flow_multiplier",
|
|
909
1393
|
|
|
910
|
-
"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",
|
|
911
1395
|
"u_domain_warp_enabled", "u_domain_warp_intensity", "u_domain_warp_scale",
|
|
912
1396
|
"u_vignette_intensity", "u_vignette_radius",
|
|
913
1397
|
"u_fresnel_enabled", "u_fresnel_power", "u_fresnel_intensity", "u_fresnel_color",
|
|
914
1398
|
"u_iridescence_enabled", "u_iridescence_intensity", "u_iridescence_speed",
|
|
915
|
-
"u_bloom_intensity", "u_bloom_threshold", "u_chromatic_aberration"
|
|
1399
|
+
"u_bloom_intensity", "u_bloom_threshold", "u_chromatic_aberration",
|
|
1400
|
+
"u_shape_type", "u_silhouette_fade", "u_cylinder_fade", "u_ribbon_fade"
|
|
916
1401
|
];
|
|
917
1402
|
|
|
918
1403
|
const locations: WebGLState["locations"] = {
|
|
@@ -966,10 +1451,15 @@ export class NeatGradient implements NeatController {
|
|
|
966
1451
|
// Texture size - 1024 provides good balance between quality and performance
|
|
967
1452
|
// Reduced from 2048 for better performance
|
|
968
1453
|
const texSize = 1024;
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
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;
|
|
973
1463
|
if (!sCtx) return null;
|
|
974
1464
|
|
|
975
1465
|
let seed = this._textureSeed;
|
|
@@ -988,6 +1478,10 @@ export class NeatGradient implements NeatController {
|
|
|
988
1478
|
const colors = this._colors.filter(c => c.enabled).map(c => c.color);
|
|
989
1479
|
if (colors.length === 0) return null;
|
|
990
1480
|
|
|
1481
|
+
const shouldTile = this._shapeType !== 'plane';
|
|
1482
|
+
const dxs = shouldTile ? [-1, 0, 1] : [0];
|
|
1483
|
+
const dys = shouldTile ? [-1, 0, 1] : [0];
|
|
1484
|
+
|
|
991
1485
|
// Helper functions
|
|
992
1486
|
function hexToRgb(hex: string) {
|
|
993
1487
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
@@ -1029,72 +1523,133 @@ export class NeatGradient implements NeatController {
|
|
|
1029
1523
|
|
|
1030
1524
|
// Triangles: use configurable count
|
|
1031
1525
|
for (let i = 0; i < this._textureShapeTriangles; i++) {
|
|
1032
|
-
|
|
1033
|
-
sCtx.beginPath();
|
|
1526
|
+
const fillStyle = getInterColor();
|
|
1034
1527
|
const x = random() * texSize;
|
|
1035
1528
|
const y = random() * texSize;
|
|
1036
1529
|
const s = 100 + random() * 300;
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
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
|
+
}
|
|
1041
1547
|
}
|
|
1042
1548
|
|
|
1043
1549
|
// Circles / rings: use configurable count
|
|
1044
1550
|
for (let i = 0; i < this._textureShapeCircles; i++) {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
sCtx.beginPath();
|
|
1551
|
+
const strokeStyle = getInterColor();
|
|
1552
|
+
const lineWidth = 10 + random() * 50;
|
|
1048
1553
|
const x = random() * texSize;
|
|
1049
1554
|
const y = random() * texSize;
|
|
1050
1555
|
const r = 50 + random() * 150;
|
|
1051
|
-
|
|
1052
|
-
|
|
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
|
+
}
|
|
1053
1566
|
}
|
|
1054
1567
|
|
|
1055
1568
|
// Bars: use configurable count
|
|
1056
1569
|
for (let i = 0; i < this._textureShapeBars; i++) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
+
}
|
|
1063
1585
|
}
|
|
1064
1586
|
|
|
1065
1587
|
// Squiggles: use configurable count
|
|
1066
1588
|
sCtx.lineWidth = 15;
|
|
1067
1589
|
sCtx.lineCap = 'round';
|
|
1068
1590
|
for (let i = 0; i < this._textureShapeSquiggles; i++) {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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;
|
|
1074
1598
|
for (let j = 0; j < 4; j++) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
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
|
+
}
|
|
1082
1630
|
}
|
|
1083
|
-
sCtx.stroke();
|
|
1084
1631
|
}
|
|
1085
1632
|
|
|
1086
1633
|
// === MASKED CANVAS ===
|
|
1087
1634
|
// Masking: Seed isolation
|
|
1088
1635
|
setSeed(50000);
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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;
|
|
1093
1644
|
if (!ctx) return null;
|
|
1094
1645
|
|
|
1095
1646
|
// Start filled with the chosen void color so gaps show that color
|
|
1096
|
-
|
|
1097
|
-
|
|
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
|
+
}
|
|
1098
1653
|
|
|
1099
1654
|
// Determine layout segments (matter vs void)
|
|
1100
1655
|
let layoutHead = 0;
|
|
@@ -1154,54 +1709,119 @@ export class NeatGradient implements NeatController {
|
|
|
1154
1709
|
return tex;
|
|
1155
1710
|
}
|
|
1156
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
|
+
}
|
|
1157
1745
|
set domainWarpEnabled(enabled: boolean) {
|
|
1158
1746
|
if (this._domainWarpEnabled !== enabled) {
|
|
1159
1747
|
this._domainWarpEnabled = enabled;
|
|
1160
1748
|
this._uniformsDirty = true;
|
|
1161
1749
|
}
|
|
1162
1750
|
}
|
|
1751
|
+
|
|
1752
|
+
get domainWarpIntensity(): number {
|
|
1753
|
+
return this._domainWarpIntensity;
|
|
1754
|
+
}
|
|
1163
1755
|
set domainWarpIntensity(intensity: number) {
|
|
1164
1756
|
if (this._domainWarpIntensity !== intensity) {
|
|
1165
1757
|
this._domainWarpIntensity = intensity;
|
|
1166
1758
|
this._uniformsDirty = true;
|
|
1167
1759
|
}
|
|
1168
1760
|
}
|
|
1761
|
+
|
|
1762
|
+
get domainWarpScale(): number {
|
|
1763
|
+
return this._domainWarpScale;
|
|
1764
|
+
}
|
|
1169
1765
|
set domainWarpScale(scale: number) {
|
|
1170
1766
|
if (this._domainWarpScale !== scale) {
|
|
1171
1767
|
this._domainWarpScale = scale;
|
|
1172
1768
|
this._uniformsDirty = true;
|
|
1173
1769
|
}
|
|
1174
1770
|
}
|
|
1771
|
+
|
|
1772
|
+
get vignetteIntensity(): number {
|
|
1773
|
+
return this._vignetteIntensity;
|
|
1774
|
+
}
|
|
1175
1775
|
set vignetteIntensity(intensity: number) {
|
|
1176
1776
|
if (this._vignetteIntensity !== intensity) {
|
|
1177
1777
|
this._vignetteIntensity = intensity;
|
|
1178
1778
|
this._uniformsDirty = true;
|
|
1179
1779
|
}
|
|
1180
1780
|
}
|
|
1781
|
+
|
|
1782
|
+
get vignetteRadius(): number {
|
|
1783
|
+
return this._vignetteRadius;
|
|
1784
|
+
}
|
|
1181
1785
|
set vignetteRadius(radius: number) {
|
|
1182
1786
|
if (this._vignetteRadius !== radius) {
|
|
1183
1787
|
this._vignetteRadius = radius;
|
|
1184
1788
|
this._uniformsDirty = true;
|
|
1185
1789
|
}
|
|
1186
1790
|
}
|
|
1791
|
+
|
|
1792
|
+
get fresnelEnabled(): boolean {
|
|
1793
|
+
return this._fresnelEnabled;
|
|
1794
|
+
}
|
|
1187
1795
|
set fresnelEnabled(enabled: boolean) {
|
|
1188
1796
|
if (this._fresnelEnabled !== enabled) {
|
|
1189
1797
|
this._fresnelEnabled = enabled;
|
|
1190
1798
|
this._uniformsDirty = true;
|
|
1191
1799
|
}
|
|
1192
1800
|
}
|
|
1801
|
+
|
|
1802
|
+
get fresnelPower(): number {
|
|
1803
|
+
return this._fresnelPower;
|
|
1804
|
+
}
|
|
1193
1805
|
set fresnelPower(power: number) {
|
|
1194
1806
|
if (this._fresnelPower !== power) {
|
|
1195
1807
|
this._fresnelPower = power;
|
|
1196
1808
|
this._uniformsDirty = true;
|
|
1197
1809
|
}
|
|
1198
1810
|
}
|
|
1811
|
+
|
|
1812
|
+
get fresnelIntensity(): number {
|
|
1813
|
+
return this._fresnelIntensity;
|
|
1814
|
+
}
|
|
1199
1815
|
set fresnelIntensity(intensity: number) {
|
|
1200
1816
|
if (this._fresnelIntensity !== intensity) {
|
|
1201
1817
|
this._fresnelIntensity = intensity;
|
|
1202
1818
|
this._uniformsDirty = true;
|
|
1203
1819
|
}
|
|
1204
1820
|
}
|
|
1821
|
+
|
|
1822
|
+
get fresnelColor(): string {
|
|
1823
|
+
return this._fresnelColor;
|
|
1824
|
+
}
|
|
1205
1825
|
set fresnelColor(fresnelColor: string) {
|
|
1206
1826
|
if (this._fresnelColor !== fresnelColor) {
|
|
1207
1827
|
this._fresnelColor = fresnelColor;
|
|
@@ -1209,42 +1829,226 @@ export class NeatGradient implements NeatController {
|
|
|
1209
1829
|
this._uniformsDirty = true;
|
|
1210
1830
|
}
|
|
1211
1831
|
}
|
|
1832
|
+
|
|
1833
|
+
get iridescenceEnabled(): boolean {
|
|
1834
|
+
return this._iridescenceEnabled;
|
|
1835
|
+
}
|
|
1212
1836
|
set iridescenceEnabled(enabled: boolean) {
|
|
1213
1837
|
if (this._iridescenceEnabled !== enabled) {
|
|
1214
1838
|
this._iridescenceEnabled = enabled;
|
|
1215
1839
|
this._uniformsDirty = true;
|
|
1216
1840
|
}
|
|
1217
1841
|
}
|
|
1842
|
+
|
|
1843
|
+
get iridescenceIntensity(): number {
|
|
1844
|
+
return this._iridescenceIntensity;
|
|
1845
|
+
}
|
|
1218
1846
|
set iridescenceIntensity(intensity: number) {
|
|
1219
1847
|
if (this._iridescenceIntensity !== intensity) {
|
|
1220
1848
|
this._iridescenceIntensity = intensity;
|
|
1221
1849
|
this._uniformsDirty = true;
|
|
1222
1850
|
}
|
|
1223
1851
|
}
|
|
1852
|
+
|
|
1853
|
+
get iridescenceSpeed(): number {
|
|
1854
|
+
return this._iridescenceSpeed;
|
|
1855
|
+
}
|
|
1224
1856
|
set iridescenceSpeed(speed: number) {
|
|
1225
1857
|
if (this._iridescenceSpeed !== speed) {
|
|
1226
1858
|
this._iridescenceSpeed = speed;
|
|
1227
1859
|
this._uniformsDirty = true;
|
|
1228
1860
|
}
|
|
1229
1861
|
}
|
|
1862
|
+
|
|
1863
|
+
get bloomIntensity(): number {
|
|
1864
|
+
return this._bloomIntensity;
|
|
1865
|
+
}
|
|
1230
1866
|
set bloomIntensity(intensity: number) {
|
|
1231
1867
|
if (this._bloomIntensity !== intensity) {
|
|
1232
1868
|
this._bloomIntensity = intensity;
|
|
1233
1869
|
this._uniformsDirty = true;
|
|
1234
1870
|
}
|
|
1235
1871
|
}
|
|
1872
|
+
|
|
1873
|
+
get bloomThreshold(): number {
|
|
1874
|
+
return this._bloomThreshold;
|
|
1875
|
+
}
|
|
1236
1876
|
set bloomThreshold(threshold: number) {
|
|
1237
1877
|
if (this._bloomThreshold !== threshold) {
|
|
1238
1878
|
this._bloomThreshold = threshold;
|
|
1239
1879
|
this._uniformsDirty = true;
|
|
1240
1880
|
}
|
|
1241
1881
|
}
|
|
1882
|
+
|
|
1883
|
+
get chromaticAberration(): number {
|
|
1884
|
+
return this._chromaticAberration;
|
|
1885
|
+
}
|
|
1242
1886
|
set chromaticAberration(aberration: number) {
|
|
1243
1887
|
if (this._chromaticAberration !== aberration) {
|
|
1244
1888
|
this._chromaticAberration = aberration;
|
|
1245
1889
|
this._uniformsDirty = true;
|
|
1246
1890
|
}
|
|
1247
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
|
+
}
|
|
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
|
+
}
|
|
1248
2052
|
}
|
|
1249
2053
|
|
|
1250
2054
|
|