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