@firecms/neat 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/NeatGradient.d.ts +162 -0
- package/dist/NeatGradient.js +822 -69
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.es.js +1347 -706
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +391 -403
- package/dist/index.umd.js.map +1 -1
- package/dist/math.d.ts +33 -1
- package/dist/math.js +339 -24
- package/dist/math.js.map +1 -1
- package/dist/shaders.d.ts +2 -2
- package/dist/shaders.js +179 -34
- package/dist/shaders.js.map +1 -1
- package/dist/types.d.ts +26 -0
- package/package.json +4 -1
- package/src/NeatGradient.ts +945 -75
- package/src/math.ts +381 -28
- package/src/shaders.ts +179 -34
- package/src/types.ts +30 -0
package/dist/NeatGradient.js
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
|
console.info("%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", "font-weight: bold; font-size: 14px; color: #FF5772;", "color: inherit;");
|
|
4
4
|
const PLANE_WIDTH = 50;
|
|
5
5
|
const PLANE_HEIGHT = 80;
|
|
@@ -22,6 +22,7 @@ export class NeatGradient {
|
|
|
22
22
|
_grainSparsity = -1;
|
|
23
23
|
_grainSpeed = -1;
|
|
24
24
|
_colorBlending = -1;
|
|
25
|
+
_resolution = 1;
|
|
25
26
|
_colors = [];
|
|
26
27
|
_wireframe = false;
|
|
27
28
|
_backgroundColor = "#FFFFFF";
|
|
@@ -43,6 +44,7 @@ export class NeatGradient {
|
|
|
43
44
|
_textureColorBlending = 0.01;
|
|
44
45
|
_textureSeed = 333;
|
|
45
46
|
_textureEase = 0.5;
|
|
47
|
+
_transparentTextureVoid = false;
|
|
46
48
|
// New effects
|
|
47
49
|
_domainWarpEnabled = false;
|
|
48
50
|
_domainWarpIntensity = 0.5;
|
|
@@ -60,6 +62,33 @@ export class NeatGradient {
|
|
|
60
62
|
_bloomIntensity = 0;
|
|
61
63
|
_bloomThreshold = 0.7;
|
|
62
64
|
_chromaticAberration = 0;
|
|
65
|
+
_silhouetteFade = 0.25;
|
|
66
|
+
_cylinderFade = 0.08;
|
|
67
|
+
_ribbonFade = 0.05;
|
|
68
|
+
_flatShading = true;
|
|
69
|
+
// 3D Shapes config
|
|
70
|
+
_shapeType = 'plane';
|
|
71
|
+
_shapeRotationX = 0;
|
|
72
|
+
_shapeRotationY = 0;
|
|
73
|
+
_shapeRotationZ = 0;
|
|
74
|
+
_shapeAutoRotateSpeedX = 0;
|
|
75
|
+
_shapeAutoRotateSpeedY = 0;
|
|
76
|
+
_sphereRadius = 15;
|
|
77
|
+
_torusRadius = 15;
|
|
78
|
+
_torusTube = 5;
|
|
79
|
+
_cylinderRadius = 10;
|
|
80
|
+
_cylinderHeight = 40;
|
|
81
|
+
_planeBend = 0;
|
|
82
|
+
_planeTwist = 0;
|
|
83
|
+
// Camera settings
|
|
84
|
+
_cameraLock = false;
|
|
85
|
+
_cameraX = 0;
|
|
86
|
+
_cameraY = 0;
|
|
87
|
+
_cameraZ = 0;
|
|
88
|
+
_cameraRotationX = 0;
|
|
89
|
+
_cameraRotationY = 0;
|
|
90
|
+
_cameraRotationZ = 0;
|
|
91
|
+
_cameraZoom = 1.0;
|
|
63
92
|
_proceduralTexture = null;
|
|
64
93
|
_proceduralBackgroundColor = "#000000";
|
|
65
94
|
_textureShapeTriangles = 20;
|
|
@@ -75,6 +104,11 @@ export class NeatGradient {
|
|
|
75
104
|
_yOffsetWaveMultiplier = 0.004;
|
|
76
105
|
_yOffsetColorMultiplier = 0.004;
|
|
77
106
|
_yOffsetFlowMultiplier = 0.004;
|
|
107
|
+
// Cached offscreen canvases for procedural texture generation
|
|
108
|
+
_sourceCanvas = null;
|
|
109
|
+
_sourceCtx = null;
|
|
110
|
+
_maskedCanvas = null;
|
|
111
|
+
_maskedCtx = null;
|
|
78
112
|
// Performance optimizations
|
|
79
113
|
_resizeTimeoutId = null;
|
|
80
114
|
_textureNeedsUpdate = false;
|
|
@@ -82,12 +116,21 @@ export class NeatGradient {
|
|
|
82
116
|
_colorsChanged = true;
|
|
83
117
|
_uniformsDirty = true;
|
|
84
118
|
_textureDirty = true;
|
|
119
|
+
_yOffsetDirty = false;
|
|
120
|
+
_modelViewMatrix = new Matrix4();
|
|
121
|
+
_isVisible = true;
|
|
122
|
+
_visibilityObserver = null;
|
|
123
|
+
_visibilityHandler = null;
|
|
85
124
|
constructor(config) {
|
|
86
125
|
const { ref, speed = 4, horizontalPressure = 3, verticalPressure = 3, waveFrequencyX = 5, waveFrequencyY = 5, waveAmplitude = 3, colors, highlights = 4, shadows = 4, colorSaturation = 0, colorBrightness = 1, colorBlending = 5, grainScale = 2, grainIntensity = 0.55, grainSparsity = 0.0, grainSpeed = 0.1, wireframe = false, backgroundColor = "#FFFFFF", backgroundAlpha = 1.0, resolution = 1, seed, yOffset = 0, yOffsetWaveMultiplier = 4, yOffsetColorMultiplier = 4, yOffsetFlowMultiplier = 4,
|
|
87
126
|
// Flow field parameters
|
|
88
127
|
flowDistortionA = 0, flowDistortionB = 0, flowScale = 1.0, flowEase = 0.0, flowEnabled = true,
|
|
89
128
|
// Texture generation
|
|
90
|
-
enableProceduralTexture = false, textureVoidLikelihood = 0.45, textureVoidWidthMin = 200, textureVoidWidthMax = 486, textureBandDensity = 2.15, textureColorBlending = 0.01, textureSeed = 333, textureEase = 0.5, proceduralBackgroundColor = "#000000", textureShapeTriangles = 20, textureShapeCircles = 15, textureShapeBars = 15, textureShapeSquiggles = 10, domainWarpEnabled = false, domainWarpIntensity = 0.5, domainWarpScale = 1.0, vignetteIntensity = 0.
|
|
129
|
+
enableProceduralTexture = false, textureVoidLikelihood = 0.45, textureVoidWidthMin = 200, textureVoidWidthMax = 486, textureBandDensity = 2.15, textureColorBlending = 0.01, textureSeed = 333, textureEase = 0.5, proceduralBackgroundColor = "#000000", transparentTextureVoid = false, textureShapeTriangles = 20, textureShapeCircles = 15, textureShapeBars = 15, textureShapeSquiggles = 10, domainWarpEnabled = false, domainWarpIntensity = 0.5, domainWarpScale = 1.0, vignetteIntensity = 0.0, vignetteRadius = 0.8, fresnelEnabled = false, fresnelPower = 2.0, fresnelIntensity = 0.5, fresnelColor = "#FFFFFF", iridescenceEnabled = false, iridescenceIntensity = 0.5, iridescenceSpeed = 1.0, bloomIntensity = 0.0, bloomThreshold = 0.7, chromaticAberration = 0.0, silhouetteFade = 0.25, cylinderFade = 0.08, ribbonFade = 0.05, flatShading = true,
|
|
130
|
+
// Camera configuration
|
|
131
|
+
cameraLock = false, cameraX = 0, cameraY = 0, cameraZ = 0, cameraRotationX = 0, cameraRotationY = 0, cameraRotationZ = 0, cameraZoom = 1.0,
|
|
132
|
+
// 3D shapes default
|
|
133
|
+
shapeType = 'plane', shapeRotationX = 0, shapeRotationY = 0, shapeRotationZ = 0, shapeAutoRotateSpeedX = 0, shapeAutoRotateSpeedY = 0, sphereRadius = 15, torusRadius = 15, torusTube = 5, cylinderRadius = 10, cylinderHeight = 40, planeBend = 0, planeTwist = 0, } = config;
|
|
91
134
|
this._ref = ref;
|
|
92
135
|
this.destroy = this.destroy.bind(this);
|
|
93
136
|
this._initScene = this._initScene.bind(this);
|
|
@@ -98,6 +141,7 @@ export class NeatGradient {
|
|
|
98
141
|
this.waveFrequencyY = waveFrequencyY;
|
|
99
142
|
this.waveAmplitude = waveAmplitude;
|
|
100
143
|
this.colorBlending = colorBlending;
|
|
144
|
+
this._resolution = resolution;
|
|
101
145
|
this.grainScale = grainScale;
|
|
102
146
|
this.grainIntensity = grainIntensity;
|
|
103
147
|
this.grainSparsity = grainSparsity;
|
|
@@ -130,6 +174,7 @@ export class NeatGradient {
|
|
|
130
174
|
this.textureSeed = textureSeed;
|
|
131
175
|
this.textureEase = textureEase;
|
|
132
176
|
this._proceduralBackgroundColor = proceduralBackgroundColor;
|
|
177
|
+
this.transparentTextureVoid = transparentTextureVoid;
|
|
133
178
|
this._textureShapeTriangles = textureShapeTriangles;
|
|
134
179
|
this._textureShapeCircles = textureShapeCircles;
|
|
135
180
|
this._textureShapeBars = textureShapeBars;
|
|
@@ -149,6 +194,31 @@ export class NeatGradient {
|
|
|
149
194
|
this.bloomIntensity = bloomIntensity;
|
|
150
195
|
this.bloomThreshold = bloomThreshold;
|
|
151
196
|
this.chromaticAberration = chromaticAberration;
|
|
197
|
+
this.silhouetteFade = silhouetteFade;
|
|
198
|
+
this.cylinderFade = cylinderFade;
|
|
199
|
+
this.ribbonFade = ribbonFade;
|
|
200
|
+
this._flatShading = flatShading;
|
|
201
|
+
this._cameraLock = cameraLock;
|
|
202
|
+
this._cameraX = cameraX;
|
|
203
|
+
this._cameraY = cameraY;
|
|
204
|
+
this._cameraZ = cameraZ;
|
|
205
|
+
this._cameraRotationX = cameraRotationX;
|
|
206
|
+
this._cameraRotationY = cameraRotationY;
|
|
207
|
+
this._cameraRotationZ = cameraRotationZ;
|
|
208
|
+
this._cameraZoom = cameraZoom;
|
|
209
|
+
this._shapeType = shapeType;
|
|
210
|
+
this._shapeRotationX = shapeRotationX;
|
|
211
|
+
this._shapeRotationY = shapeRotationY;
|
|
212
|
+
this._shapeRotationZ = shapeRotationZ;
|
|
213
|
+
this._shapeAutoRotateSpeedX = shapeAutoRotateSpeedX;
|
|
214
|
+
this._shapeAutoRotateSpeedY = shapeAutoRotateSpeedY;
|
|
215
|
+
this._sphereRadius = sphereRadius;
|
|
216
|
+
this._torusRadius = torusRadius;
|
|
217
|
+
this._torusTube = torusTube;
|
|
218
|
+
this._cylinderRadius = cylinderRadius;
|
|
219
|
+
this._cylinderHeight = cylinderHeight;
|
|
220
|
+
this._planeBend = planeBend;
|
|
221
|
+
this._planeTwist = planeTwist;
|
|
152
222
|
this.glState = this._initScene(resolution);
|
|
153
223
|
injectSEO();
|
|
154
224
|
let tick = seed !== undefined ? seed : getElapsedSecondsInLastHour();
|
|
@@ -169,6 +239,42 @@ export class NeatGradient {
|
|
|
169
239
|
lastTime = timeNow;
|
|
170
240
|
gl.useProgram(program);
|
|
171
241
|
gl.uniform1f(locations.uniforms['u_time'], tick);
|
|
242
|
+
// Update modelViewMatrix in every frame to support dynamic rotation and auto-rotation
|
|
243
|
+
const camera = this.glState.camera;
|
|
244
|
+
const modelViewMatrix = this._modelViewMatrix;
|
|
245
|
+
modelViewMatrix.identity();
|
|
246
|
+
// 1. Camera translation (default camera distance + displacement)
|
|
247
|
+
modelViewMatrix.translate(-camera.position[0] - this._cameraX, -camera.position[1] - this._cameraY, -camera.position[2] - this._cameraZ);
|
|
248
|
+
modelViewMatrix.translate(0, 0, -1);
|
|
249
|
+
// 2. Camera rotation (revolving around target)
|
|
250
|
+
modelViewMatrix.rotateX(-this._cameraRotationX);
|
|
251
|
+
modelViewMatrix.rotateY(-this._cameraRotationY);
|
|
252
|
+
modelViewMatrix.rotateZ(-this._cameraRotationZ);
|
|
253
|
+
let rx = this._shapeRotationX;
|
|
254
|
+
let ry = this._shapeRotationY;
|
|
255
|
+
let rz = this._shapeRotationZ;
|
|
256
|
+
if (this._shapeAutoRotateSpeedX !== 0) {
|
|
257
|
+
rx += tick * this._shapeAutoRotateSpeedX * 0.1;
|
|
258
|
+
}
|
|
259
|
+
if (this._shapeAutoRotateSpeedY !== 0) {
|
|
260
|
+
ry += tick * this._shapeAutoRotateSpeedY * 0.1;
|
|
261
|
+
}
|
|
262
|
+
if (this._shapeType === 'plane' || this._shapeType === 'ribbon') {
|
|
263
|
+
modelViewMatrix.rotateX(rx - Math.PI / 3.5);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
modelViewMatrix.rotateX(rx);
|
|
267
|
+
}
|
|
268
|
+
modelViewMatrix.rotateY(ry);
|
|
269
|
+
modelViewMatrix.rotateZ(rz);
|
|
270
|
+
const mvLoc = locations.uniforms["modelViewMatrix"];
|
|
271
|
+
if (mvLoc)
|
|
272
|
+
gl.uniformMatrix4fv(mvLoc, false, modelViewMatrix.elements);
|
|
273
|
+
// Fast path: only upload yOffset when it changed (scroll)
|
|
274
|
+
if (this._yOffsetDirty && !this._uniformsDirty) {
|
|
275
|
+
gl.uniform1f(locations.uniforms['u_y_offset'], this._yOffset);
|
|
276
|
+
this._yOffsetDirty = false;
|
|
277
|
+
}
|
|
172
278
|
// Only upload static uniforms when they've been modified
|
|
173
279
|
if (this._uniformsDirty) {
|
|
174
280
|
gl.uniform2f(locations.uniforms['u_resolution'], this._ref.clientWidth, this._ref.clientHeight);
|
|
@@ -194,8 +300,19 @@ export class NeatGradient {
|
|
|
194
300
|
gl.uniform1f(locations.uniforms['u_flow_scale'], this._flowScale);
|
|
195
301
|
gl.uniform1f(locations.uniforms['u_flow_ease'], this._flowEase);
|
|
196
302
|
gl.uniform1f(locations.uniforms['u_flow_enabled'], this._flowEnabled ? 1.0 : 0.0);
|
|
303
|
+
let shapeTypeVal = 0.0;
|
|
304
|
+
if (this._shapeType === 'sphere')
|
|
305
|
+
shapeTypeVal = 1.0;
|
|
306
|
+
else if (this._shapeType === 'torus')
|
|
307
|
+
shapeTypeVal = 2.0;
|
|
308
|
+
else if (this._shapeType === 'cylinder')
|
|
309
|
+
shapeTypeVal = 3.0;
|
|
310
|
+
else if (this._shapeType === 'ribbon')
|
|
311
|
+
shapeTypeVal = 4.0;
|
|
312
|
+
gl.uniform1f(locations.uniforms['u_shape_type'], shapeTypeVal);
|
|
197
313
|
gl.uniform1f(locations.uniforms['u_enable_procedural_texture'], this._enableProceduralTexture ? 1.0 : 0.0);
|
|
198
314
|
gl.uniform1f(locations.uniforms['u_texture_ease'], this._textureEase);
|
|
315
|
+
gl.uniform1f(locations.uniforms['u_transparent_texture_void'], this._transparentTextureVoid ? 1.0 : 0.0);
|
|
199
316
|
gl.uniform1f(locations.uniforms['u_domain_warp_enabled'], this._domainWarpEnabled ? 1.0 : 0.0);
|
|
200
317
|
gl.uniform1f(locations.uniforms['u_domain_warp_intensity'], this._domainWarpIntensity);
|
|
201
318
|
gl.uniform1f(locations.uniforms['u_domain_warp_scale'], this._domainWarpScale);
|
|
@@ -211,7 +328,12 @@ export class NeatGradient {
|
|
|
211
328
|
gl.uniform1f(locations.uniforms['u_bloom_intensity'], this._bloomIntensity);
|
|
212
329
|
gl.uniform1f(locations.uniforms['u_bloom_threshold'], this._bloomThreshold);
|
|
213
330
|
gl.uniform1f(locations.uniforms['u_chromatic_aberration'], this._chromaticAberration);
|
|
331
|
+
gl.uniform1f(locations.uniforms['u_silhouette_fade'], this._silhouetteFade);
|
|
332
|
+
gl.uniform1f(locations.uniforms['u_cylinder_fade'], this._cylinderFade);
|
|
333
|
+
gl.uniform1f(locations.uniforms['u_ribbon_fade'], this._ribbonFade);
|
|
334
|
+
gl.uniform1f(locations.uniforms['u_flat_shading'], this._flatShading ? 1.0 : 0.0);
|
|
214
335
|
this._uniformsDirty = false;
|
|
336
|
+
this._yOffsetDirty = false;
|
|
215
337
|
}
|
|
216
338
|
// Only regenerate procedural texture when needed
|
|
217
339
|
if (this._textureNeedsUpdate && this._enableProceduralTexture) {
|
|
@@ -258,8 +380,34 @@ export class NeatGradient {
|
|
|
258
380
|
else {
|
|
259
381
|
gl.drawElements(gl.TRIANGLES, indexCount, indexType, 0);
|
|
260
382
|
}
|
|
261
|
-
this.
|
|
383
|
+
if (this._isVisible) {
|
|
384
|
+
this.requestRef = requestAnimationFrame(render);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
// Visibility optimization: pause rendering when off-screen or tab hidden
|
|
388
|
+
this._visibilityObserver = new IntersectionObserver((entries) => {
|
|
389
|
+
const wasVisible = this._isVisible;
|
|
390
|
+
this._isVisible = entries[0].isIntersecting && document.visibilityState !== 'hidden';
|
|
391
|
+
if (this._isVisible && !wasVisible) {
|
|
392
|
+
lastTime = performance.now(); // Avoid time jump after resume
|
|
393
|
+
this.requestRef = requestAnimationFrame(render);
|
|
394
|
+
}
|
|
395
|
+
}, { threshold: 0 });
|
|
396
|
+
this._visibilityObserver.observe(ref);
|
|
397
|
+
this._visibilityHandler = () => {
|
|
398
|
+
const wasVisible = this._isVisible;
|
|
399
|
+
if (document.visibilityState === 'hidden') {
|
|
400
|
+
this._isVisible = false;
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
this._isVisible = true;
|
|
404
|
+
if (!wasVisible) {
|
|
405
|
+
lastTime = performance.now();
|
|
406
|
+
this.requestRef = requestAnimationFrame(render);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
262
409
|
};
|
|
410
|
+
document.addEventListener('visibilitychange', this._visibilityHandler);
|
|
263
411
|
const setSize = () => {
|
|
264
412
|
const { gl, camera } = this.glState;
|
|
265
413
|
const width = this._ref.clientWidth;
|
|
@@ -268,11 +416,12 @@ export class NeatGradient {
|
|
|
268
416
|
this._ref.width = width;
|
|
269
417
|
this._ref.height = height;
|
|
270
418
|
gl.viewport(0, 0, width, height);
|
|
271
|
-
updateCamera(camera, width, height);
|
|
419
|
+
updateCamera(camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
272
420
|
// Recompute projection matrix on resize
|
|
273
|
-
const projLoc =
|
|
421
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
274
422
|
gl.useProgram(this.glState.program);
|
|
275
|
-
|
|
423
|
+
if (projLoc)
|
|
424
|
+
gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
276
425
|
};
|
|
277
426
|
// Debounce resize to prevent excessive operations
|
|
278
427
|
this.sizeObserver = new ResizeObserver(() => {
|
|
@@ -290,6 +439,15 @@ export class NeatGradient {
|
|
|
290
439
|
destroy() {
|
|
291
440
|
cancelAnimationFrame(this.requestRef);
|
|
292
441
|
this.sizeObserver.disconnect();
|
|
442
|
+
// Cleanup visibility observers
|
|
443
|
+
if (this._visibilityObserver) {
|
|
444
|
+
this._visibilityObserver.disconnect();
|
|
445
|
+
this._visibilityObserver = null;
|
|
446
|
+
}
|
|
447
|
+
if (this._visibilityHandler) {
|
|
448
|
+
document.removeEventListener('visibilitychange', this._visibilityHandler);
|
|
449
|
+
this._visibilityHandler = null;
|
|
450
|
+
}
|
|
293
451
|
// Clear resize timeout
|
|
294
452
|
if (this._resizeTimeoutId !== null) {
|
|
295
453
|
clearTimeout(this._resizeTimeoutId);
|
|
@@ -318,101 +476,297 @@ export class NeatGradient {
|
|
|
318
476
|
const dataURL = this._ref.toDataURL("image/png");
|
|
319
477
|
downloadURI(dataURL, filename);
|
|
320
478
|
}
|
|
479
|
+
/**
|
|
480
|
+
* Records the canvas animation as a video with a NEAT watermark overlay.
|
|
481
|
+
* @param options.durationMs Recording duration in milliseconds (default 5000).
|
|
482
|
+
* @param options.filename Output file name (default "neat.firecms.co").
|
|
483
|
+
* @param options.width Output video width in pixels (default: current canvas width).
|
|
484
|
+
* @param options.height Output video height in pixels (default: current canvas height).
|
|
485
|
+
* @param options.format Preferred format: 'mp4' or 'webm' (default: best available).
|
|
486
|
+
* @param options.onProgress Callback with progress 0-1.
|
|
487
|
+
* @param options.onComplete Callback when recording finishes.
|
|
488
|
+
* @returns A stop function to end recording early.
|
|
489
|
+
*/
|
|
490
|
+
recordVideo(options = {}) {
|
|
491
|
+
const { durationMs = 5000, filename = "neat.firecms.co", format, onProgress, onComplete, } = options;
|
|
492
|
+
const source = this._ref;
|
|
493
|
+
const width = options.width || source.width || source.clientWidth;
|
|
494
|
+
const height = options.height || source.height || source.clientHeight;
|
|
495
|
+
// Offscreen canvas that composites gradient + watermark each frame
|
|
496
|
+
const offscreen = document.createElement("canvas");
|
|
497
|
+
offscreen.width = width;
|
|
498
|
+
offscreen.height = height;
|
|
499
|
+
const ctx = offscreen.getContext("2d");
|
|
500
|
+
// Use captureStream(0) — only captures a frame when we explicitly
|
|
501
|
+
// call requestFrame() on the video track, so every composited frame
|
|
502
|
+
// is guaranteed to be captured.
|
|
503
|
+
const stream = offscreen.captureStream(0);
|
|
504
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
505
|
+
// Codec candidates ordered by preference
|
|
506
|
+
const mp4Candidates = [
|
|
507
|
+
"video/mp4;codecs=avc1",
|
|
508
|
+
"video/mp4;codecs=avc1,opus",
|
|
509
|
+
"video/mp4",
|
|
510
|
+
];
|
|
511
|
+
const webmCandidates = [
|
|
512
|
+
"video/webm;codecs=vp9,opus",
|
|
513
|
+
"video/webm;codecs=vp9",
|
|
514
|
+
"video/webm;codecs=vp8,opus",
|
|
515
|
+
"video/webm",
|
|
516
|
+
];
|
|
517
|
+
// Build candidate list based on preferred format
|
|
518
|
+
let candidates;
|
|
519
|
+
if (format === 'mp4')
|
|
520
|
+
candidates = [...mp4Candidates, ...webmCandidates];
|
|
521
|
+
else if (format === 'webm')
|
|
522
|
+
candidates = [...webmCandidates, ...mp4Candidates];
|
|
523
|
+
else
|
|
524
|
+
candidates = [...mp4Candidates, ...webmCandidates];
|
|
525
|
+
let mimeType = "video/webm";
|
|
526
|
+
for (const candidate of candidates) {
|
|
527
|
+
if (MediaRecorder.isTypeSupported(candidate)) {
|
|
528
|
+
mimeType = candidate;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// Scale bitrate with pixel count: 8 Mbps baseline at 720p
|
|
533
|
+
const pixels = width * height;
|
|
534
|
+
const baseBitrate = 8_000_000;
|
|
535
|
+
const basePixels = 1280 * 720;
|
|
536
|
+
const videoBitsPerSecond = Math.round(baseBitrate * Math.max(1, pixels / basePixels));
|
|
537
|
+
const recorder = new MediaRecorder(stream, {
|
|
538
|
+
mimeType,
|
|
539
|
+
videoBitsPerSecond,
|
|
540
|
+
});
|
|
541
|
+
const chunks = [];
|
|
542
|
+
recorder.ondataavailable = (e) => {
|
|
543
|
+
if (e.data.size > 0)
|
|
544
|
+
chunks.push(e.data);
|
|
545
|
+
};
|
|
546
|
+
let stopped = false;
|
|
547
|
+
let rafId;
|
|
548
|
+
const startTime = performance.now();
|
|
549
|
+
let lastProgressTime = 0;
|
|
550
|
+
// Composite loop: draw source canvas + watermark overlay on each frame
|
|
551
|
+
const drawFrame = () => {
|
|
552
|
+
if (stopped)
|
|
553
|
+
return;
|
|
554
|
+
ctx.clearRect(0, 0, width, height);
|
|
555
|
+
ctx.drawImage(source, 0, 0, width, height);
|
|
556
|
+
// Watermark: "NEAT" in bottom-right corner
|
|
557
|
+
const fontSize = Math.max(14, Math.round(height * 0.025));
|
|
558
|
+
ctx.font = `bold ${fontSize}px "Sofia Sans", sans-serif`;
|
|
559
|
+
ctx.textAlign = "right";
|
|
560
|
+
ctx.textBaseline = "bottom";
|
|
561
|
+
ctx.shadowColor = "rgba(0,0,0,0.5)";
|
|
562
|
+
ctx.shadowBlur = 4;
|
|
563
|
+
ctx.shadowOffsetX = 1;
|
|
564
|
+
ctx.shadowOffsetY = 1;
|
|
565
|
+
ctx.fillStyle = "rgba(255,255,255,0.7)";
|
|
566
|
+
ctx.fillText("NEAT", width - fontSize * 0.8, height - fontSize * 0.5);
|
|
567
|
+
ctx.shadowColor = "transparent";
|
|
568
|
+
ctx.shadowBlur = 0;
|
|
569
|
+
ctx.shadowOffsetX = 0;
|
|
570
|
+
ctx.shadowOffsetY = 0;
|
|
571
|
+
// Signal the stream to capture this frame
|
|
572
|
+
// @ts-ignore – requestFrame exists on CanvasCaptureMediaStreamTrack
|
|
573
|
+
if (videoTrack.requestFrame)
|
|
574
|
+
videoTrack.requestFrame();
|
|
575
|
+
// Throttle progress to ~4 updates/sec to avoid flooding React state
|
|
576
|
+
if (onProgress) {
|
|
577
|
+
const now = performance.now();
|
|
578
|
+
if (now - lastProgressTime > 250) {
|
|
579
|
+
lastProgressTime = now;
|
|
580
|
+
onProgress(Math.min(0.99, (now - startTime) / durationMs));
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
rafId = requestAnimationFrame(drawFrame);
|
|
584
|
+
};
|
|
585
|
+
recorder.onstop = () => {
|
|
586
|
+
stopped = true;
|
|
587
|
+
cancelAnimationFrame(rafId);
|
|
588
|
+
// Use the correct file extension for the actual format
|
|
589
|
+
const isMP4 = mimeType.startsWith("video/mp4");
|
|
590
|
+
const ext = isMP4 ? ".mp4" : ".webm";
|
|
591
|
+
const blobType = isMP4 ? "video/mp4" : "video/webm";
|
|
592
|
+
const finalFilename = filename + ext;
|
|
593
|
+
const blob = new Blob(chunks, { type: blobType });
|
|
594
|
+
const url = URL.createObjectURL(blob);
|
|
595
|
+
downloadURI(url, finalFilename);
|
|
596
|
+
setTimeout(() => URL.revokeObjectURL(url), 30000);
|
|
597
|
+
onProgress?.(1);
|
|
598
|
+
onComplete?.();
|
|
599
|
+
};
|
|
600
|
+
// Start drawing frames, then start recording
|
|
601
|
+
drawFrame();
|
|
602
|
+
recorder.start(100); // collect data every 100ms
|
|
603
|
+
// Auto-stop after the requested duration
|
|
604
|
+
const timeoutId = window.setTimeout(() => {
|
|
605
|
+
if (recorder.state === "recording") {
|
|
606
|
+
recorder.stop();
|
|
607
|
+
}
|
|
608
|
+
}, durationMs);
|
|
609
|
+
// Return a stop function for early termination
|
|
610
|
+
return () => {
|
|
611
|
+
clearTimeout(timeoutId);
|
|
612
|
+
if (recorder.state === "recording") {
|
|
613
|
+
recorder.stop();
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
get speed() {
|
|
618
|
+
return this._speed * 20;
|
|
619
|
+
}
|
|
321
620
|
set speed(speed) {
|
|
322
621
|
this._uniformsDirty = true;
|
|
323
622
|
this._speed = speed / 20;
|
|
324
623
|
}
|
|
624
|
+
get horizontalPressure() {
|
|
625
|
+
return this._horizontalPressure * 4;
|
|
626
|
+
}
|
|
325
627
|
set horizontalPressure(horizontalPressure) {
|
|
326
628
|
this._uniformsDirty = true;
|
|
327
629
|
this._horizontalPressure = horizontalPressure / 4;
|
|
328
630
|
}
|
|
631
|
+
get verticalPressure() {
|
|
632
|
+
return this._verticalPressure * 4;
|
|
633
|
+
}
|
|
329
634
|
set verticalPressure(verticalPressure) {
|
|
330
635
|
this._uniformsDirty = true;
|
|
331
636
|
this._verticalPressure = verticalPressure / 4;
|
|
332
637
|
}
|
|
638
|
+
get waveFrequencyX() {
|
|
639
|
+
return this._waveFrequencyX / 0.04;
|
|
640
|
+
}
|
|
333
641
|
set waveFrequencyX(waveFrequencyX) {
|
|
334
642
|
this._uniformsDirty = true;
|
|
335
643
|
this._waveFrequencyX = waveFrequencyX * 0.04;
|
|
336
644
|
}
|
|
645
|
+
get waveFrequencyY() {
|
|
646
|
+
return this._waveFrequencyY / 0.04;
|
|
647
|
+
}
|
|
337
648
|
set waveFrequencyY(waveFrequencyY) {
|
|
338
649
|
this._uniformsDirty = true;
|
|
339
650
|
this._waveFrequencyY = waveFrequencyY * 0.04;
|
|
340
651
|
}
|
|
652
|
+
get waveAmplitude() {
|
|
653
|
+
return this._waveAmplitude / 0.75;
|
|
654
|
+
}
|
|
341
655
|
set waveAmplitude(waveAmplitude) {
|
|
342
656
|
this._uniformsDirty = true;
|
|
343
657
|
this._waveAmplitude = waveAmplitude * .75;
|
|
344
658
|
}
|
|
659
|
+
get colors() {
|
|
660
|
+
return this._colors;
|
|
661
|
+
}
|
|
345
662
|
set colors(colors) {
|
|
346
663
|
this._uniformsDirty = true;
|
|
347
664
|
this._colors = colors;
|
|
348
665
|
this._cachedColorRgb = colors.map(c => this._hexToRgb(c.color));
|
|
349
666
|
this._colorsChanged = true;
|
|
350
667
|
}
|
|
668
|
+
get highlights() {
|
|
669
|
+
return this._highlights * 100;
|
|
670
|
+
}
|
|
351
671
|
set highlights(highlights) {
|
|
352
672
|
this._uniformsDirty = true;
|
|
353
673
|
this._highlights = highlights / 100;
|
|
354
674
|
}
|
|
675
|
+
get shadows() {
|
|
676
|
+
return this._shadows * 100;
|
|
677
|
+
}
|
|
355
678
|
set shadows(shadows) {
|
|
356
679
|
this._uniformsDirty = true;
|
|
357
680
|
this._shadows = shadows / 100;
|
|
358
681
|
}
|
|
682
|
+
get colorSaturation() {
|
|
683
|
+
return this._saturation * 10;
|
|
684
|
+
}
|
|
359
685
|
set colorSaturation(colorSaturation) {
|
|
360
686
|
this._uniformsDirty = true;
|
|
361
687
|
this._saturation = colorSaturation / 10;
|
|
362
688
|
}
|
|
689
|
+
get colorBrightness() {
|
|
690
|
+
return this._brightness;
|
|
691
|
+
}
|
|
363
692
|
set colorBrightness(colorBrightness) {
|
|
364
693
|
this._uniformsDirty = true;
|
|
365
694
|
this._brightness = colorBrightness;
|
|
366
695
|
}
|
|
696
|
+
get colorBlending() {
|
|
697
|
+
return this._colorBlending * 10;
|
|
698
|
+
}
|
|
367
699
|
set colorBlending(colorBlending) {
|
|
368
700
|
this._uniformsDirty = true;
|
|
369
701
|
this._colorBlending = colorBlending / 10;
|
|
370
702
|
}
|
|
703
|
+
get grainScale() {
|
|
704
|
+
return this._grainScale;
|
|
705
|
+
}
|
|
371
706
|
set grainScale(grainScale) {
|
|
372
707
|
this._uniformsDirty = true;
|
|
373
708
|
this._grainScale = grainScale == 0 ? 1 : grainScale;
|
|
374
709
|
}
|
|
710
|
+
get grainIntensity() {
|
|
711
|
+
return this._grainIntensity;
|
|
712
|
+
}
|
|
375
713
|
set grainIntensity(grainIntensity) {
|
|
376
714
|
this._uniformsDirty = true;
|
|
377
715
|
this._grainIntensity = grainIntensity;
|
|
378
716
|
}
|
|
717
|
+
get grainSparsity() {
|
|
718
|
+
return this._grainSparsity;
|
|
719
|
+
}
|
|
379
720
|
set grainSparsity(grainSparsity) {
|
|
380
721
|
this._uniformsDirty = true;
|
|
381
722
|
this._grainSparsity = grainSparsity;
|
|
382
723
|
}
|
|
724
|
+
get grainSpeed() {
|
|
725
|
+
return this._grainSpeed;
|
|
726
|
+
}
|
|
383
727
|
set grainSpeed(grainSpeed) {
|
|
384
728
|
this._uniformsDirty = true;
|
|
385
729
|
this._grainSpeed = grainSpeed;
|
|
386
730
|
}
|
|
731
|
+
get wireframe() {
|
|
732
|
+
return this._wireframe;
|
|
733
|
+
}
|
|
387
734
|
set wireframe(wireframe) {
|
|
388
735
|
this._uniformsDirty = true;
|
|
389
736
|
this._wireframe = wireframe;
|
|
390
737
|
}
|
|
738
|
+
get resolution() {
|
|
739
|
+
return this._resolution;
|
|
740
|
+
}
|
|
391
741
|
set resolution(resolution) {
|
|
392
|
-
this.
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
gl.deleteBuffer(this.glState.buffers.index);
|
|
400
|
-
gl.deleteBuffer(this.glState.buffers.wireframeIndex);
|
|
401
|
-
}
|
|
402
|
-
this.glState = this._initScene(resolution);
|
|
742
|
+
if (this._resolution === resolution)
|
|
743
|
+
return;
|
|
744
|
+
this._resolution = resolution;
|
|
745
|
+
this._updateGeometry();
|
|
746
|
+
}
|
|
747
|
+
get backgroundColor() {
|
|
748
|
+
return this._backgroundColor;
|
|
403
749
|
}
|
|
404
750
|
set backgroundColor(backgroundColor) {
|
|
405
751
|
this._uniformsDirty = true;
|
|
406
752
|
this._backgroundColor = backgroundColor;
|
|
407
753
|
this._backgroundColorRgb = this._hexToRgb(backgroundColor);
|
|
408
754
|
}
|
|
755
|
+
get backgroundAlpha() {
|
|
756
|
+
return this._backgroundAlpha;
|
|
757
|
+
}
|
|
409
758
|
set backgroundAlpha(backgroundAlpha) {
|
|
410
759
|
this._uniformsDirty = true;
|
|
411
760
|
this._backgroundAlpha = backgroundAlpha;
|
|
412
761
|
}
|
|
762
|
+
get yOffset() {
|
|
763
|
+
return this._yOffset;
|
|
764
|
+
}
|
|
413
765
|
set yOffset(yOffset) {
|
|
414
|
-
this.
|
|
415
|
-
|
|
766
|
+
if (this._yOffset !== yOffset) {
|
|
767
|
+
this._yOffsetDirty = true;
|
|
768
|
+
this._yOffset = yOffset;
|
|
769
|
+
}
|
|
416
770
|
}
|
|
417
771
|
get yOffsetWaveMultiplier() {
|
|
418
772
|
return this._yOffsetWaveMultiplier * 1000;
|
|
@@ -435,18 +789,30 @@ export class NeatGradient {
|
|
|
435
789
|
this._uniformsDirty = true;
|
|
436
790
|
this._yOffsetFlowMultiplier = value / 1000;
|
|
437
791
|
}
|
|
792
|
+
get flowDistortionA() {
|
|
793
|
+
return this._flowDistortionA;
|
|
794
|
+
}
|
|
438
795
|
set flowDistortionA(value) {
|
|
439
796
|
this._uniformsDirty = true;
|
|
440
797
|
this._flowDistortionA = value;
|
|
441
798
|
}
|
|
799
|
+
get flowDistortionB() {
|
|
800
|
+
return this._flowDistortionB;
|
|
801
|
+
}
|
|
442
802
|
set flowDistortionB(value) {
|
|
443
803
|
this._uniformsDirty = true;
|
|
444
804
|
this._flowDistortionB = value;
|
|
445
805
|
}
|
|
806
|
+
get flowScale() {
|
|
807
|
+
return this._flowScale;
|
|
808
|
+
}
|
|
446
809
|
set flowScale(value) {
|
|
447
810
|
this._uniformsDirty = true;
|
|
448
811
|
this._flowScale = value;
|
|
449
812
|
}
|
|
813
|
+
get flowEase() {
|
|
814
|
+
return this._flowEase;
|
|
815
|
+
}
|
|
450
816
|
set flowEase(value) {
|
|
451
817
|
this._uniformsDirty = true;
|
|
452
818
|
this._flowEase = value;
|
|
@@ -458,6 +824,9 @@ export class NeatGradient {
|
|
|
458
824
|
get flowEnabled() {
|
|
459
825
|
return this._flowEnabled;
|
|
460
826
|
}
|
|
827
|
+
get enableProceduralTexture() {
|
|
828
|
+
return this._enableProceduralTexture;
|
|
829
|
+
}
|
|
461
830
|
set enableProceduralTexture(value) {
|
|
462
831
|
this._uniformsDirty = true;
|
|
463
832
|
this._enableProceduralTexture = value;
|
|
@@ -465,6 +834,9 @@ export class NeatGradient {
|
|
|
465
834
|
this._textureNeedsUpdate = true;
|
|
466
835
|
}
|
|
467
836
|
}
|
|
837
|
+
get textureVoidLikelihood() {
|
|
838
|
+
return this._textureVoidLikelihood;
|
|
839
|
+
}
|
|
468
840
|
set textureVoidLikelihood(value) {
|
|
469
841
|
this._uniformsDirty = true;
|
|
470
842
|
this._textureVoidLikelihood = value;
|
|
@@ -472,6 +844,9 @@ export class NeatGradient {
|
|
|
472
844
|
this._textureNeedsUpdate = true;
|
|
473
845
|
}
|
|
474
846
|
}
|
|
847
|
+
get textureVoidWidthMin() {
|
|
848
|
+
return this._textureVoidWidthMin;
|
|
849
|
+
}
|
|
475
850
|
set textureVoidWidthMin(value) {
|
|
476
851
|
this._uniformsDirty = true;
|
|
477
852
|
this._textureVoidWidthMin = value;
|
|
@@ -479,6 +854,9 @@ export class NeatGradient {
|
|
|
479
854
|
this._textureNeedsUpdate = true;
|
|
480
855
|
}
|
|
481
856
|
}
|
|
857
|
+
get textureVoidWidthMax() {
|
|
858
|
+
return this._textureVoidWidthMax;
|
|
859
|
+
}
|
|
482
860
|
set textureVoidWidthMax(value) {
|
|
483
861
|
this._uniformsDirty = true;
|
|
484
862
|
this._textureVoidWidthMax = value;
|
|
@@ -486,6 +864,9 @@ export class NeatGradient {
|
|
|
486
864
|
this._textureNeedsUpdate = true;
|
|
487
865
|
}
|
|
488
866
|
}
|
|
867
|
+
get textureBandDensity() {
|
|
868
|
+
return this._textureBandDensity;
|
|
869
|
+
}
|
|
489
870
|
set textureBandDensity(value) {
|
|
490
871
|
this._uniformsDirty = true;
|
|
491
872
|
this._textureBandDensity = value;
|
|
@@ -493,6 +874,9 @@ export class NeatGradient {
|
|
|
493
874
|
this._textureNeedsUpdate = true;
|
|
494
875
|
}
|
|
495
876
|
}
|
|
877
|
+
get textureColorBlending() {
|
|
878
|
+
return this._textureColorBlending;
|
|
879
|
+
}
|
|
496
880
|
set textureColorBlending(value) {
|
|
497
881
|
this._uniformsDirty = true;
|
|
498
882
|
this._textureColorBlending = value;
|
|
@@ -500,6 +884,9 @@ export class NeatGradient {
|
|
|
500
884
|
this._textureNeedsUpdate = true;
|
|
501
885
|
}
|
|
502
886
|
}
|
|
887
|
+
get textureSeed() {
|
|
888
|
+
return this._textureSeed;
|
|
889
|
+
}
|
|
503
890
|
set textureSeed(value) {
|
|
504
891
|
this._uniformsDirty = true;
|
|
505
892
|
this._textureSeed = value;
|
|
@@ -514,6 +901,19 @@ export class NeatGradient {
|
|
|
514
901
|
this._uniformsDirty = true;
|
|
515
902
|
this._textureEase = value;
|
|
516
903
|
}
|
|
904
|
+
get transparentTextureVoid() {
|
|
905
|
+
return this._transparentTextureVoid;
|
|
906
|
+
}
|
|
907
|
+
set transparentTextureVoid(value) {
|
|
908
|
+
this._uniformsDirty = true;
|
|
909
|
+
this._transparentTextureVoid = value;
|
|
910
|
+
if (this._enableProceduralTexture) {
|
|
911
|
+
this._textureNeedsUpdate = true;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
get proceduralBackgroundColor() {
|
|
915
|
+
return this._proceduralBackgroundColor;
|
|
916
|
+
}
|
|
517
917
|
set proceduralBackgroundColor(value) {
|
|
518
918
|
this._uniformsDirty = true;
|
|
519
919
|
this._proceduralBackgroundColor = value;
|
|
@@ -521,30 +921,90 @@ export class NeatGradient {
|
|
|
521
921
|
this._textureNeedsUpdate = true;
|
|
522
922
|
}
|
|
523
923
|
}
|
|
924
|
+
get textureShapeTriangles() {
|
|
925
|
+
return this._textureShapeTriangles;
|
|
926
|
+
}
|
|
524
927
|
set textureShapeTriangles(value) {
|
|
525
928
|
this._uniformsDirty = true;
|
|
526
929
|
this._textureShapeTriangles = value;
|
|
527
930
|
if (this._enableProceduralTexture)
|
|
528
931
|
this._textureNeedsUpdate = true;
|
|
529
932
|
}
|
|
933
|
+
get textureShapeCircles() {
|
|
934
|
+
return this._textureShapeCircles;
|
|
935
|
+
}
|
|
530
936
|
set textureShapeCircles(value) {
|
|
531
937
|
this._uniformsDirty = true;
|
|
532
938
|
this._textureShapeCircles = value;
|
|
533
939
|
if (this._enableProceduralTexture)
|
|
534
940
|
this._textureNeedsUpdate = true;
|
|
535
941
|
}
|
|
942
|
+
get textureShapeBars() {
|
|
943
|
+
return this._textureShapeBars;
|
|
944
|
+
}
|
|
536
945
|
set textureShapeBars(value) {
|
|
537
946
|
this._uniformsDirty = true;
|
|
538
947
|
this._textureShapeBars = value;
|
|
539
948
|
if (this._enableProceduralTexture)
|
|
540
949
|
this._textureNeedsUpdate = true;
|
|
541
950
|
}
|
|
951
|
+
get textureShapeSquiggles() {
|
|
952
|
+
return this._textureShapeSquiggles;
|
|
953
|
+
}
|
|
542
954
|
set textureShapeSquiggles(value) {
|
|
543
955
|
this._uniformsDirty = true;
|
|
544
956
|
this._textureShapeSquiggles = value;
|
|
545
957
|
if (this._enableProceduralTexture)
|
|
546
958
|
this._textureNeedsUpdate = true;
|
|
547
959
|
}
|
|
960
|
+
_updateGeometry() {
|
|
961
|
+
if (!this.glState)
|
|
962
|
+
return;
|
|
963
|
+
const gl = this.glState.gl;
|
|
964
|
+
const resolution = this._resolution || 1;
|
|
965
|
+
let geometry;
|
|
966
|
+
if (this._shapeType === 'sphere') {
|
|
967
|
+
geometry = generateSphereGeometry(this._sphereRadius, 120 * resolution, 120 * resolution);
|
|
968
|
+
}
|
|
969
|
+
else if (this._shapeType === 'torus') {
|
|
970
|
+
geometry = generateTorusGeometry(this._torusRadius, this._torusTube, 120 * resolution, 120 * resolution);
|
|
971
|
+
}
|
|
972
|
+
else if (this._shapeType === 'cylinder') {
|
|
973
|
+
geometry = generateCylinderGeometry(this._cylinderRadius, this._cylinderRadius, this._cylinderHeight, 120 * resolution, 120 * resolution);
|
|
974
|
+
}
|
|
975
|
+
else if (this._shapeType === 'ribbon') {
|
|
976
|
+
geometry = generateRibbonGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution, this._planeBend, this._planeTwist);
|
|
977
|
+
}
|
|
978
|
+
else {
|
|
979
|
+
geometry = generatePlaneGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution);
|
|
980
|
+
}
|
|
981
|
+
const { position, normal, uv, index, wireframeIndex } = geometry;
|
|
982
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.position);
|
|
983
|
+
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
|
|
984
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.normal);
|
|
985
|
+
gl.bufferData(gl.ARRAY_BUFFER, normal, gl.STATIC_DRAW);
|
|
986
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, this.glState.buffers.uv);
|
|
987
|
+
gl.bufferData(gl.ARRAY_BUFFER, uv, gl.STATIC_DRAW);
|
|
988
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.index);
|
|
989
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index, gl.STATIC_DRAW);
|
|
990
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.wireframeIndex);
|
|
991
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, wireframeIndex, gl.STATIC_DRAW);
|
|
992
|
+
// Restore default bound element buffer
|
|
993
|
+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.glState.buffers.index);
|
|
994
|
+
this.glState.indexCount = index.length;
|
|
995
|
+
this.glState.wireframeIndexCount = wireframeIndex.length;
|
|
996
|
+
this.glState.indexType = (index instanceof Uint32Array) ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT;
|
|
997
|
+
// Keep camera updated with the new shapeType and dimensions
|
|
998
|
+
const width = this._ref.clientWidth;
|
|
999
|
+
const height = this._ref.clientHeight;
|
|
1000
|
+
updateCamera(this.glState.camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
1001
|
+
// Recompute projection matrix
|
|
1002
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
1003
|
+
gl.useProgram(this.glState.program);
|
|
1004
|
+
if (projLoc)
|
|
1005
|
+
gl.uniformMatrix4fv(projLoc, false, this.glState.camera.projectionMatrix.elements);
|
|
1006
|
+
this._uniformsDirty = true;
|
|
1007
|
+
}
|
|
548
1008
|
_hexToRgb(hex) {
|
|
549
1009
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
550
1010
|
return [
|
|
@@ -564,8 +1024,24 @@ export class NeatGradient {
|
|
|
564
1024
|
const ext = gl.getExtension("OES_standard_derivatives");
|
|
565
1025
|
gl.getExtension("OES_element_index_uint");
|
|
566
1026
|
gl.viewport(0, 0, width, height);
|
|
567
|
-
// Generate
|
|
568
|
-
|
|
1027
|
+
// Generate parametric geometry based on shapeType
|
|
1028
|
+
let geometry;
|
|
1029
|
+
if (this._shapeType === 'sphere') {
|
|
1030
|
+
geometry = generateSphereGeometry(this._sphereRadius, 120 * resolution, 120 * resolution);
|
|
1031
|
+
}
|
|
1032
|
+
else if (this._shapeType === 'torus') {
|
|
1033
|
+
geometry = generateTorusGeometry(this._torusRadius, this._torusTube, 120 * resolution, 120 * resolution);
|
|
1034
|
+
}
|
|
1035
|
+
else if (this._shapeType === 'cylinder') {
|
|
1036
|
+
geometry = generateCylinderGeometry(this._cylinderRadius, this._cylinderRadius, this._cylinderHeight, 120 * resolution, 120 * resolution);
|
|
1037
|
+
}
|
|
1038
|
+
else if (this._shapeType === 'ribbon') {
|
|
1039
|
+
geometry = generateRibbonGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution, this._planeBend, this._planeTwist);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
geometry = generatePlaneGeometry(PLANE_WIDTH, PLANE_HEIGHT, 240 * resolution, 240 * resolution);
|
|
1043
|
+
}
|
|
1044
|
+
const { position, normal, uv, index, wireframeIndex } = geometry;
|
|
569
1045
|
const positionBuffer = gl.createBuffer();
|
|
570
1046
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
571
1047
|
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
|
|
@@ -620,7 +1096,7 @@ export class NeatGradient {
|
|
|
620
1096
|
gl.useProgram(program);
|
|
621
1097
|
const camera = new OrthographicCamera(0, 0, 0, 0, 0, 1000);
|
|
622
1098
|
camera.position = [0, 0, 5];
|
|
623
|
-
updateCamera(camera, width, height);
|
|
1099
|
+
updateCamera(camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
624
1100
|
// Define attributes
|
|
625
1101
|
const aPosition = gl.getAttribLocation(program, "position");
|
|
626
1102
|
const aNormal = gl.getAttribLocation(program, "normal");
|
|
@@ -635,15 +1111,7 @@ export class NeatGradient {
|
|
|
635
1111
|
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
|
|
636
1112
|
gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 0, 0);
|
|
637
1113
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
|
638
|
-
|
|
639
|
-
// The View Matrix is the inverse of the Camera's position
|
|
640
|
-
// Camera is at [0, 0, 5], so view matrix translates by [0, 0, -5]
|
|
641
|
-
modelViewMatrix.translate(-camera.position[0], -camera.position[1], -camera.position[2]);
|
|
642
|
-
// The Model Matrix mimicking: plane.rotation.x = -Math.PI / 3.5; plane.position.z = -1;
|
|
643
|
-
modelViewMatrix.translate(0, 0, -1);
|
|
644
|
-
modelViewMatrix.rotateX(-Math.PI / 3.5);
|
|
645
|
-
const mvLoc = gl.getUniformLocation(program, "modelViewMatrix");
|
|
646
|
-
gl.uniformMatrix4fv(mvLoc, false, modelViewMatrix.elements);
|
|
1114
|
+
// modelViewMatrix is set dynamically in the render loop
|
|
647
1115
|
const projLoc = gl.getUniformLocation(program, "projectionMatrix");
|
|
648
1116
|
gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
649
1117
|
const planeWidthLoc = gl.getUniformLocation(program, "u_plane_width");
|
|
@@ -653,17 +1121,19 @@ export class NeatGradient {
|
|
|
653
1121
|
const colorsCountLoc = gl.getUniformLocation(program, "u_colors_count");
|
|
654
1122
|
gl.uniform1i(colorsCountLoc, COLORS_COUNT);
|
|
655
1123
|
const uniformsList = [
|
|
1124
|
+
"projectionMatrix", "modelViewMatrix",
|
|
656
1125
|
"u_time", "u_resolution", "u_color_pressure", "u_wave_frequency_x", "u_wave_frequency_y",
|
|
657
1126
|
"u_wave_amplitude", "u_colors_count", "u_plane_width", "u_plane_height", "u_shadows",
|
|
658
1127
|
"u_highlights", "u_grain_intensity", "u_grain_sparsity", "u_grain_scale", "u_grain_speed",
|
|
659
1128
|
"u_flow_distortion_a", "u_flow_distortion_b", "u_flow_scale", "u_flow_ease", "u_flow_enabled",
|
|
660
1129
|
"u_y_offset", "u_y_offset_wave_multiplier", "u_y_offset_color_multiplier", "u_y_offset_flow_multiplier",
|
|
661
|
-
"u_procedural_texture", "u_enable_procedural_texture", "u_texture_ease", "u_saturation", "u_brightness", "u_color_blending",
|
|
1130
|
+
"u_procedural_texture", "u_enable_procedural_texture", "u_texture_ease", "u_transparent_texture_void", "u_saturation", "u_brightness", "u_color_blending",
|
|
662
1131
|
"u_domain_warp_enabled", "u_domain_warp_intensity", "u_domain_warp_scale",
|
|
663
1132
|
"u_vignette_intensity", "u_vignette_radius",
|
|
664
1133
|
"u_fresnel_enabled", "u_fresnel_power", "u_fresnel_intensity", "u_fresnel_color",
|
|
665
1134
|
"u_iridescence_enabled", "u_iridescence_intensity", "u_iridescence_speed",
|
|
666
|
-
"u_bloom_intensity", "u_bloom_threshold", "u_chromatic_aberration"
|
|
1135
|
+
"u_bloom_intensity", "u_bloom_threshold", "u_chromatic_aberration",
|
|
1136
|
+
"u_shape_type", "u_silhouette_fade", "u_cylinder_fade", "u_ribbon_fade", "u_flat_shading"
|
|
667
1137
|
];
|
|
668
1138
|
const locations = {
|
|
669
1139
|
attributes: { position: aPosition, normal: aNormal, uv: aUv },
|
|
@@ -708,10 +1178,14 @@ export class NeatGradient {
|
|
|
708
1178
|
// Texture size - 1024 provides good balance between quality and performance
|
|
709
1179
|
// Reduced from 2048 for better performance
|
|
710
1180
|
const texSize = 1024;
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1181
|
+
if (!this._sourceCanvas) {
|
|
1182
|
+
this._sourceCanvas = document.createElement('canvas');
|
|
1183
|
+
this._sourceCanvas.width = texSize;
|
|
1184
|
+
this._sourceCanvas.height = texSize;
|
|
1185
|
+
this._sourceCtx = this._sourceCanvas.getContext('2d');
|
|
1186
|
+
}
|
|
1187
|
+
const sourceCanvas = this._sourceCanvas;
|
|
1188
|
+
const sCtx = this._sourceCtx;
|
|
715
1189
|
if (!sCtx)
|
|
716
1190
|
return null;
|
|
717
1191
|
let seed = this._textureSeed;
|
|
@@ -727,6 +1201,9 @@ export class NeatGradient {
|
|
|
727
1201
|
const colors = this._colors.filter(c => c.enabled).map(c => c.color);
|
|
728
1202
|
if (colors.length === 0)
|
|
729
1203
|
return null;
|
|
1204
|
+
const shouldTile = this._shapeType !== 'plane';
|
|
1205
|
+
const dxs = shouldTile ? [-1, 0, 1] : [0];
|
|
1206
|
+
const dys = shouldTile ? [-1, 0, 1] : [0];
|
|
730
1207
|
// Helper functions
|
|
731
1208
|
function hexToRgb(hex) {
|
|
732
1209
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
@@ -763,64 +1240,120 @@ export class NeatGradient {
|
|
|
763
1240
|
sCtx.fillRect(0, 0, texSize, texSize);
|
|
764
1241
|
// Triangles: use configurable count
|
|
765
1242
|
for (let i = 0; i < this._textureShapeTriangles; i++) {
|
|
766
|
-
|
|
767
|
-
sCtx.beginPath();
|
|
1243
|
+
const fillStyle = getInterColor();
|
|
768
1244
|
const x = random() * texSize;
|
|
769
1245
|
const y = random() * texSize;
|
|
770
1246
|
const s = 100 + random() * 300;
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1247
|
+
const x1 = (random() - 0.5) * s;
|
|
1248
|
+
const y1 = (random() - 0.5) * s;
|
|
1249
|
+
const x2 = (random() - 0.5) * s;
|
|
1250
|
+
const y2 = (random() - 0.5) * s;
|
|
1251
|
+
for (const dx of dxs) {
|
|
1252
|
+
for (const dy of dys) {
|
|
1253
|
+
sCtx.fillStyle = fillStyle;
|
|
1254
|
+
sCtx.beginPath();
|
|
1255
|
+
const tx = x + dx * texSize;
|
|
1256
|
+
const ty = y + dy * texSize;
|
|
1257
|
+
sCtx.moveTo(tx, ty);
|
|
1258
|
+
sCtx.lineTo(tx + x1, ty + y1);
|
|
1259
|
+
sCtx.lineTo(tx + x2, ty + y2);
|
|
1260
|
+
sCtx.fill();
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
775
1263
|
}
|
|
776
1264
|
// Circles / rings: use configurable count
|
|
777
1265
|
for (let i = 0; i < this._textureShapeCircles; i++) {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
sCtx.beginPath();
|
|
1266
|
+
const strokeStyle = getInterColor();
|
|
1267
|
+
const lineWidth = 10 + random() * 50;
|
|
781
1268
|
const x = random() * texSize;
|
|
782
1269
|
const y = random() * texSize;
|
|
783
1270
|
const r = 50 + random() * 150;
|
|
784
|
-
|
|
785
|
-
|
|
1271
|
+
for (const dx of dxs) {
|
|
1272
|
+
for (const dy of dys) {
|
|
1273
|
+
sCtx.strokeStyle = strokeStyle;
|
|
1274
|
+
sCtx.lineWidth = lineWidth;
|
|
1275
|
+
sCtx.beginPath();
|
|
1276
|
+
sCtx.arc(x + dx * texSize, y + dy * texSize, r, 0, Math.PI * 2);
|
|
1277
|
+
sCtx.stroke();
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
786
1280
|
}
|
|
787
1281
|
// Bars: use configurable count
|
|
788
1282
|
for (let i = 0; i < this._textureShapeBars; i++) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
1283
|
+
const fillStyle = getInterColor();
|
|
1284
|
+
const x = random() * texSize;
|
|
1285
|
+
const y = random() * texSize;
|
|
1286
|
+
const rot = random() * Math.PI;
|
|
1287
|
+
for (const dx of dxs) {
|
|
1288
|
+
for (const dy of dys) {
|
|
1289
|
+
sCtx.fillStyle = fillStyle;
|
|
1290
|
+
sCtx.save();
|
|
1291
|
+
sCtx.translate(x + dx * texSize, y + dy * texSize);
|
|
1292
|
+
sCtx.rotate(rot);
|
|
1293
|
+
sCtx.fillRect(-150, -25, 300, 50);
|
|
1294
|
+
sCtx.restore();
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
795
1297
|
}
|
|
796
1298
|
// Squiggles: use configurable count
|
|
797
1299
|
sCtx.lineWidth = 15;
|
|
798
1300
|
sCtx.lineCap = 'round';
|
|
799
1301
|
for (let i = 0; i < this._textureShapeSquiggles; i++) {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1302
|
+
const strokeStyle = getInterColor();
|
|
1303
|
+
const x = random() * texSize;
|
|
1304
|
+
const y = random() * texSize;
|
|
1305
|
+
const curves = [];
|
|
1306
|
+
let cx = 0;
|
|
1307
|
+
let cy = 0;
|
|
805
1308
|
for (let j = 0; j < 4; j++) {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1309
|
+
const ex = cx + (random() - 0.5) * 300;
|
|
1310
|
+
const ey = cy + (random() - 0.5) * 300;
|
|
1311
|
+
curves.push({
|
|
1312
|
+
cx1: cx + (random() - 0.5) * 300,
|
|
1313
|
+
cy1: cy + (random() - 0.5) * 300,
|
|
1314
|
+
cx2: cx + (random() - 0.5) * 300,
|
|
1315
|
+
cy2: cy + (random() - 0.5) * 300,
|
|
1316
|
+
ex: ex,
|
|
1317
|
+
ey: ey
|
|
1318
|
+
});
|
|
1319
|
+
cx = ex;
|
|
1320
|
+
cy = ey;
|
|
1321
|
+
}
|
|
1322
|
+
for (const dx of dxs) {
|
|
1323
|
+
for (const dy of dys) {
|
|
1324
|
+
sCtx.strokeStyle = strokeStyle;
|
|
1325
|
+
sCtx.beginPath();
|
|
1326
|
+
const tx = x + dx * texSize;
|
|
1327
|
+
const ty = y + dy * texSize;
|
|
1328
|
+
sCtx.moveTo(tx, ty);
|
|
1329
|
+
for (const curve of curves) {
|
|
1330
|
+
sCtx.bezierCurveTo(tx + curve.cx1, ty + curve.cy1, tx + curve.cx2, ty + curve.cy2, tx + curve.ex, ty + curve.ey);
|
|
1331
|
+
}
|
|
1332
|
+
sCtx.stroke();
|
|
1333
|
+
}
|
|
809
1334
|
}
|
|
810
|
-
sCtx.stroke();
|
|
811
1335
|
}
|
|
812
1336
|
// === MASKED CANVAS ===
|
|
813
1337
|
// Masking: Seed isolation
|
|
814
1338
|
setSeed(50000);
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1339
|
+
if (!this._maskedCanvas) {
|
|
1340
|
+
this._maskedCanvas = document.createElement('canvas');
|
|
1341
|
+
this._maskedCanvas.width = texSize;
|
|
1342
|
+
this._maskedCanvas.height = texSize;
|
|
1343
|
+
this._maskedCtx = this._maskedCanvas.getContext('2d');
|
|
1344
|
+
}
|
|
1345
|
+
const canvas = this._maskedCanvas;
|
|
1346
|
+
const ctx = this._maskedCtx;
|
|
819
1347
|
if (!ctx)
|
|
820
1348
|
return null;
|
|
821
1349
|
// Start filled with the chosen void color so gaps show that color
|
|
822
|
-
|
|
823
|
-
|
|
1350
|
+
if (this._transparentTextureVoid) {
|
|
1351
|
+
ctx.clearRect(0, 0, texSize, texSize);
|
|
1352
|
+
}
|
|
1353
|
+
else {
|
|
1354
|
+
ctx.fillStyle = baseColor;
|
|
1355
|
+
ctx.fillRect(0, 0, texSize, texSize);
|
|
1356
|
+
}
|
|
824
1357
|
// Determine layout segments (matter vs void)
|
|
825
1358
|
let layoutHead = 0;
|
|
826
1359
|
const segments = [];
|
|
@@ -869,54 +1402,117 @@ export class NeatGradient {
|
|
|
869
1402
|
}
|
|
870
1403
|
return tex;
|
|
871
1404
|
}
|
|
1405
|
+
get silhouetteFade() {
|
|
1406
|
+
return this._silhouetteFade;
|
|
1407
|
+
}
|
|
1408
|
+
set silhouetteFade(value) {
|
|
1409
|
+
if (this._silhouetteFade !== value) {
|
|
1410
|
+
this._silhouetteFade = value;
|
|
1411
|
+
this._uniformsDirty = true;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
get cylinderFade() {
|
|
1415
|
+
return this._cylinderFade;
|
|
1416
|
+
}
|
|
1417
|
+
set cylinderFade(value) {
|
|
1418
|
+
if (this._cylinderFade !== value) {
|
|
1419
|
+
this._cylinderFade = value;
|
|
1420
|
+
this._uniformsDirty = true;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
get ribbonFade() {
|
|
1424
|
+
return this._ribbonFade;
|
|
1425
|
+
}
|
|
1426
|
+
set ribbonFade(value) {
|
|
1427
|
+
if (this._ribbonFade !== value) {
|
|
1428
|
+
this._ribbonFade = value;
|
|
1429
|
+
this._uniformsDirty = true;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
get flatShading() {
|
|
1433
|
+
return this._flatShading;
|
|
1434
|
+
}
|
|
1435
|
+
set flatShading(value) {
|
|
1436
|
+
if (this._flatShading !== value) {
|
|
1437
|
+
this._flatShading = value;
|
|
1438
|
+
this._uniformsDirty = true;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
get domainWarpEnabled() {
|
|
1442
|
+
return this._domainWarpEnabled;
|
|
1443
|
+
}
|
|
872
1444
|
set domainWarpEnabled(enabled) {
|
|
873
1445
|
if (this._domainWarpEnabled !== enabled) {
|
|
874
1446
|
this._domainWarpEnabled = enabled;
|
|
875
1447
|
this._uniformsDirty = true;
|
|
876
1448
|
}
|
|
877
1449
|
}
|
|
1450
|
+
get domainWarpIntensity() {
|
|
1451
|
+
return this._domainWarpIntensity;
|
|
1452
|
+
}
|
|
878
1453
|
set domainWarpIntensity(intensity) {
|
|
879
1454
|
if (this._domainWarpIntensity !== intensity) {
|
|
880
1455
|
this._domainWarpIntensity = intensity;
|
|
881
1456
|
this._uniformsDirty = true;
|
|
882
1457
|
}
|
|
883
1458
|
}
|
|
1459
|
+
get domainWarpScale() {
|
|
1460
|
+
return this._domainWarpScale;
|
|
1461
|
+
}
|
|
884
1462
|
set domainWarpScale(scale) {
|
|
885
1463
|
if (this._domainWarpScale !== scale) {
|
|
886
1464
|
this._domainWarpScale = scale;
|
|
887
1465
|
this._uniformsDirty = true;
|
|
888
1466
|
}
|
|
889
1467
|
}
|
|
1468
|
+
get vignetteIntensity() {
|
|
1469
|
+
return this._vignetteIntensity;
|
|
1470
|
+
}
|
|
890
1471
|
set vignetteIntensity(intensity) {
|
|
891
1472
|
if (this._vignetteIntensity !== intensity) {
|
|
892
1473
|
this._vignetteIntensity = intensity;
|
|
893
1474
|
this._uniformsDirty = true;
|
|
894
1475
|
}
|
|
895
1476
|
}
|
|
1477
|
+
get vignetteRadius() {
|
|
1478
|
+
return this._vignetteRadius;
|
|
1479
|
+
}
|
|
896
1480
|
set vignetteRadius(radius) {
|
|
897
1481
|
if (this._vignetteRadius !== radius) {
|
|
898
1482
|
this._vignetteRadius = radius;
|
|
899
1483
|
this._uniformsDirty = true;
|
|
900
1484
|
}
|
|
901
1485
|
}
|
|
1486
|
+
get fresnelEnabled() {
|
|
1487
|
+
return this._fresnelEnabled;
|
|
1488
|
+
}
|
|
902
1489
|
set fresnelEnabled(enabled) {
|
|
903
1490
|
if (this._fresnelEnabled !== enabled) {
|
|
904
1491
|
this._fresnelEnabled = enabled;
|
|
905
1492
|
this._uniformsDirty = true;
|
|
906
1493
|
}
|
|
907
1494
|
}
|
|
1495
|
+
get fresnelPower() {
|
|
1496
|
+
return this._fresnelPower;
|
|
1497
|
+
}
|
|
908
1498
|
set fresnelPower(power) {
|
|
909
1499
|
if (this._fresnelPower !== power) {
|
|
910
1500
|
this._fresnelPower = power;
|
|
911
1501
|
this._uniformsDirty = true;
|
|
912
1502
|
}
|
|
913
1503
|
}
|
|
1504
|
+
get fresnelIntensity() {
|
|
1505
|
+
return this._fresnelIntensity;
|
|
1506
|
+
}
|
|
914
1507
|
set fresnelIntensity(intensity) {
|
|
915
1508
|
if (this._fresnelIntensity !== intensity) {
|
|
916
1509
|
this._fresnelIntensity = intensity;
|
|
917
1510
|
this._uniformsDirty = true;
|
|
918
1511
|
}
|
|
919
1512
|
}
|
|
1513
|
+
get fresnelColor() {
|
|
1514
|
+
return this._fresnelColor;
|
|
1515
|
+
}
|
|
920
1516
|
set fresnelColor(fresnelColor) {
|
|
921
1517
|
if (this._fresnelColor !== fresnelColor) {
|
|
922
1518
|
this._fresnelColor = fresnelColor;
|
|
@@ -924,42 +1520,199 @@ export class NeatGradient {
|
|
|
924
1520
|
this._uniformsDirty = true;
|
|
925
1521
|
}
|
|
926
1522
|
}
|
|
1523
|
+
get iridescenceEnabled() {
|
|
1524
|
+
return this._iridescenceEnabled;
|
|
1525
|
+
}
|
|
927
1526
|
set iridescenceEnabled(enabled) {
|
|
928
1527
|
if (this._iridescenceEnabled !== enabled) {
|
|
929
1528
|
this._iridescenceEnabled = enabled;
|
|
930
1529
|
this._uniformsDirty = true;
|
|
931
1530
|
}
|
|
932
1531
|
}
|
|
1532
|
+
get iridescenceIntensity() {
|
|
1533
|
+
return this._iridescenceIntensity;
|
|
1534
|
+
}
|
|
933
1535
|
set iridescenceIntensity(intensity) {
|
|
934
1536
|
if (this._iridescenceIntensity !== intensity) {
|
|
935
1537
|
this._iridescenceIntensity = intensity;
|
|
936
1538
|
this._uniformsDirty = true;
|
|
937
1539
|
}
|
|
938
1540
|
}
|
|
1541
|
+
get iridescenceSpeed() {
|
|
1542
|
+
return this._iridescenceSpeed;
|
|
1543
|
+
}
|
|
939
1544
|
set iridescenceSpeed(speed) {
|
|
940
1545
|
if (this._iridescenceSpeed !== speed) {
|
|
941
1546
|
this._iridescenceSpeed = speed;
|
|
942
1547
|
this._uniformsDirty = true;
|
|
943
1548
|
}
|
|
944
1549
|
}
|
|
1550
|
+
get bloomIntensity() {
|
|
1551
|
+
return this._bloomIntensity;
|
|
1552
|
+
}
|
|
945
1553
|
set bloomIntensity(intensity) {
|
|
946
1554
|
if (this._bloomIntensity !== intensity) {
|
|
947
1555
|
this._bloomIntensity = intensity;
|
|
948
1556
|
this._uniformsDirty = true;
|
|
949
1557
|
}
|
|
950
1558
|
}
|
|
1559
|
+
get bloomThreshold() {
|
|
1560
|
+
return this._bloomThreshold;
|
|
1561
|
+
}
|
|
951
1562
|
set bloomThreshold(threshold) {
|
|
952
1563
|
if (this._bloomThreshold !== threshold) {
|
|
953
1564
|
this._bloomThreshold = threshold;
|
|
954
1565
|
this._uniformsDirty = true;
|
|
955
1566
|
}
|
|
956
1567
|
}
|
|
1568
|
+
get chromaticAberration() {
|
|
1569
|
+
return this._chromaticAberration;
|
|
1570
|
+
}
|
|
957
1571
|
set chromaticAberration(aberration) {
|
|
958
1572
|
if (this._chromaticAberration !== aberration) {
|
|
959
1573
|
this._chromaticAberration = aberration;
|
|
960
1574
|
this._uniformsDirty = true;
|
|
961
1575
|
}
|
|
962
1576
|
}
|
|
1577
|
+
// Getters and Setters for 3D Shapes
|
|
1578
|
+
get shapeType() {
|
|
1579
|
+
return this._shapeType;
|
|
1580
|
+
}
|
|
1581
|
+
set shapeType(val) {
|
|
1582
|
+
if (this._shapeType !== val) {
|
|
1583
|
+
this._shapeType = val;
|
|
1584
|
+
this._updateGeometry();
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
get shapeRotationX() { return this._shapeRotationX; }
|
|
1588
|
+
set shapeRotationX(val) {
|
|
1589
|
+
this._shapeRotationX = val;
|
|
1590
|
+
this._uniformsDirty = true;
|
|
1591
|
+
}
|
|
1592
|
+
get shapeRotationY() { return this._shapeRotationY; }
|
|
1593
|
+
set shapeRotationY(val) {
|
|
1594
|
+
this._shapeRotationY = val;
|
|
1595
|
+
this._uniformsDirty = true;
|
|
1596
|
+
}
|
|
1597
|
+
get shapeRotationZ() { return this._shapeRotationZ; }
|
|
1598
|
+
set shapeRotationZ(val) {
|
|
1599
|
+
this._shapeRotationZ = val;
|
|
1600
|
+
this._uniformsDirty = true;
|
|
1601
|
+
}
|
|
1602
|
+
get shapeAutoRotateSpeedX() { return this._shapeAutoRotateSpeedX; }
|
|
1603
|
+
set shapeAutoRotateSpeedX(val) {
|
|
1604
|
+
this._shapeAutoRotateSpeedX = val;
|
|
1605
|
+
this._uniformsDirty = true;
|
|
1606
|
+
}
|
|
1607
|
+
get shapeAutoRotateSpeedY() { return this._shapeAutoRotateSpeedY; }
|
|
1608
|
+
set shapeAutoRotateSpeedY(val) {
|
|
1609
|
+
this._shapeAutoRotateSpeedY = val;
|
|
1610
|
+
this._uniformsDirty = true;
|
|
1611
|
+
}
|
|
1612
|
+
get sphereRadius() { return this._sphereRadius; }
|
|
1613
|
+
set sphereRadius(val) {
|
|
1614
|
+
if (this._sphereRadius !== val) {
|
|
1615
|
+
this._sphereRadius = val;
|
|
1616
|
+
this._updateGeometry();
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
get torusRadius() { return this._torusRadius; }
|
|
1620
|
+
set torusRadius(val) {
|
|
1621
|
+
if (this._torusRadius !== val) {
|
|
1622
|
+
this._torusRadius = val;
|
|
1623
|
+
this._updateGeometry();
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
get torusTube() { return this._torusTube; }
|
|
1627
|
+
set torusTube(val) {
|
|
1628
|
+
if (this._torusTube !== val) {
|
|
1629
|
+
this._torusTube = val;
|
|
1630
|
+
this._updateGeometry();
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
get cylinderRadius() { return this._cylinderRadius; }
|
|
1634
|
+
set cylinderRadius(val) {
|
|
1635
|
+
if (this._cylinderRadius !== val) {
|
|
1636
|
+
this._cylinderRadius = val;
|
|
1637
|
+
this._updateGeometry();
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
get cylinderHeight() { return this._cylinderHeight; }
|
|
1641
|
+
set cylinderHeight(val) {
|
|
1642
|
+
if (this._cylinderHeight !== val) {
|
|
1643
|
+
this._cylinderHeight = val;
|
|
1644
|
+
this._updateGeometry();
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
get planeBend() { return this._planeBend; }
|
|
1648
|
+
set planeBend(val) {
|
|
1649
|
+
if (this._planeBend !== val) {
|
|
1650
|
+
this._planeBend = val;
|
|
1651
|
+
this._updateGeometry();
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
get planeTwist() { return this._planeTwist; }
|
|
1655
|
+
set planeTwist(val) {
|
|
1656
|
+
if (this._planeTwist !== val) {
|
|
1657
|
+
this._planeTwist = val;
|
|
1658
|
+
this._updateGeometry();
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
// Camera Getters and Setters
|
|
1662
|
+
get cameraLock() { return this._cameraLock; }
|
|
1663
|
+
set cameraLock(val) {
|
|
1664
|
+
this._cameraLock = val;
|
|
1665
|
+
}
|
|
1666
|
+
get cameraX() { return this._cameraX; }
|
|
1667
|
+
set cameraX(val) {
|
|
1668
|
+
this._cameraX = val;
|
|
1669
|
+
this._uniformsDirty = true;
|
|
1670
|
+
}
|
|
1671
|
+
get cameraY() { return this._cameraY; }
|
|
1672
|
+
set cameraY(val) {
|
|
1673
|
+
this._cameraY = val;
|
|
1674
|
+
this._uniformsDirty = true;
|
|
1675
|
+
}
|
|
1676
|
+
get cameraZ() { return this._cameraZ; }
|
|
1677
|
+
set cameraZ(val) {
|
|
1678
|
+
this._cameraZ = val;
|
|
1679
|
+
this._uniformsDirty = true;
|
|
1680
|
+
}
|
|
1681
|
+
get cameraRotationX() { return this._cameraRotationX; }
|
|
1682
|
+
set cameraRotationX(val) {
|
|
1683
|
+
this._cameraRotationX = val;
|
|
1684
|
+
this._uniformsDirty = true;
|
|
1685
|
+
}
|
|
1686
|
+
get cameraRotationY() { return this._cameraRotationY; }
|
|
1687
|
+
set cameraRotationY(val) {
|
|
1688
|
+
this._cameraRotationY = val;
|
|
1689
|
+
this._uniformsDirty = true;
|
|
1690
|
+
}
|
|
1691
|
+
get cameraRotationZ() { return this._cameraRotationZ; }
|
|
1692
|
+
set cameraRotationZ(val) {
|
|
1693
|
+
this._cameraRotationZ = val;
|
|
1694
|
+
this._uniformsDirty = true;
|
|
1695
|
+
}
|
|
1696
|
+
get cameraZoom() { return this._cameraZoom; }
|
|
1697
|
+
set cameraZoom(val) {
|
|
1698
|
+
if (this._cameraZoom !== val) {
|
|
1699
|
+
this._cameraZoom = val;
|
|
1700
|
+
this._updateCameraFrustum();
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
_updateCameraFrustum() {
|
|
1704
|
+
if (!this.glState)
|
|
1705
|
+
return;
|
|
1706
|
+
const gl = this.glState.gl;
|
|
1707
|
+
const width = this._ref.clientWidth;
|
|
1708
|
+
const height = this._ref.clientHeight;
|
|
1709
|
+
updateCamera(this.glState.camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
1710
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
1711
|
+
gl.useProgram(this.glState.program);
|
|
1712
|
+
if (projLoc)
|
|
1713
|
+
gl.uniformMatrix4fv(projLoc, false, this.glState.camera.projectionMatrix.elements);
|
|
1714
|
+
this._uniformsDirty = true;
|
|
1715
|
+
}
|
|
963
1716
|
}
|
|
964
1717
|
const setLinkStyles = (link) => {
|
|
965
1718
|
link.id = LINK_ID;
|