@firecms/neat 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/NeatGradient.d.ts +154 -0
- package/dist/NeatGradient.js +758 -66
- package/dist/NeatGradient.js.map +1 -1
- package/dist/index.es.js +1316 -703
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +389 -403
- package/dist/index.umd.js.map +1 -1
- package/dist/math.d.ts +32 -1
- package/dist/math.js +319 -24
- package/dist/math.js.map +1 -1
- package/dist/shaders.d.ts +2 -2
- package/dist/shaders.js +176 -33
- package/dist/shaders.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/package.json +1 -1
- package/src/NeatGradient.ts +876 -72
- package/src/math.ts +373 -28
- package/src/shaders.ts +176 -33
- package/src/types.ts +29 -0
package/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,32 @@ 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
|
+
// 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;
|
|
63
91
|
_proceduralTexture = null;
|
|
64
92
|
_proceduralBackgroundColor = "#000000";
|
|
65
93
|
_textureShapeTriangles = 20;
|
|
@@ -75,6 +103,11 @@ export class NeatGradient {
|
|
|
75
103
|
_yOffsetWaveMultiplier = 0.004;
|
|
76
104
|
_yOffsetColorMultiplier = 0.004;
|
|
77
105
|
_yOffsetFlowMultiplier = 0.004;
|
|
106
|
+
// Cached offscreen canvases for procedural texture generation
|
|
107
|
+
_sourceCanvas = null;
|
|
108
|
+
_sourceCtx = null;
|
|
109
|
+
_maskedCanvas = null;
|
|
110
|
+
_maskedCtx = null;
|
|
78
111
|
// Performance optimizations
|
|
79
112
|
_resizeTimeoutId = null;
|
|
80
113
|
_textureNeedsUpdate = false;
|
|
@@ -87,7 +120,11 @@ export class NeatGradient {
|
|
|
87
120
|
// Flow field parameters
|
|
88
121
|
flowDistortionA = 0, flowDistortionB = 0, flowScale = 1.0, flowEase = 0.0, flowEnabled = true,
|
|
89
122
|
// 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.
|
|
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;
|
|
91
128
|
this._ref = ref;
|
|
92
129
|
this.destroy = this.destroy.bind(this);
|
|
93
130
|
this._initScene = this._initScene.bind(this);
|
|
@@ -98,6 +135,7 @@ export class NeatGradient {
|
|
|
98
135
|
this.waveFrequencyY = waveFrequencyY;
|
|
99
136
|
this.waveAmplitude = waveAmplitude;
|
|
100
137
|
this.colorBlending = colorBlending;
|
|
138
|
+
this._resolution = resolution;
|
|
101
139
|
this.grainScale = grainScale;
|
|
102
140
|
this.grainIntensity = grainIntensity;
|
|
103
141
|
this.grainSparsity = grainSparsity;
|
|
@@ -130,6 +168,7 @@ export class NeatGradient {
|
|
|
130
168
|
this.textureSeed = textureSeed;
|
|
131
169
|
this.textureEase = textureEase;
|
|
132
170
|
this._proceduralBackgroundColor = proceduralBackgroundColor;
|
|
171
|
+
this.transparentTextureVoid = transparentTextureVoid;
|
|
133
172
|
this._textureShapeTriangles = textureShapeTriangles;
|
|
134
173
|
this._textureShapeCircles = textureShapeCircles;
|
|
135
174
|
this._textureShapeBars = textureShapeBars;
|
|
@@ -149,6 +188,30 @@ export class NeatGradient {
|
|
|
149
188
|
this.bloomIntensity = bloomIntensity;
|
|
150
189
|
this.bloomThreshold = bloomThreshold;
|
|
151
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;
|
|
152
215
|
this.glState = this._initScene(resolution);
|
|
153
216
|
injectSEO();
|
|
154
217
|
let tick = seed !== undefined ? seed : getElapsedSecondsInLastHour();
|
|
@@ -169,6 +232,36 @@ export class NeatGradient {
|
|
|
169
232
|
lastTime = timeNow;
|
|
170
233
|
gl.useProgram(program);
|
|
171
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);
|
|
172
265
|
// Only upload static uniforms when they've been modified
|
|
173
266
|
if (this._uniformsDirty) {
|
|
174
267
|
gl.uniform2f(locations.uniforms['u_resolution'], this._ref.clientWidth, this._ref.clientHeight);
|
|
@@ -194,8 +287,19 @@ export class NeatGradient {
|
|
|
194
287
|
gl.uniform1f(locations.uniforms['u_flow_scale'], this._flowScale);
|
|
195
288
|
gl.uniform1f(locations.uniforms['u_flow_ease'], this._flowEase);
|
|
196
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);
|
|
197
300
|
gl.uniform1f(locations.uniforms['u_enable_procedural_texture'], this._enableProceduralTexture ? 1.0 : 0.0);
|
|
198
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);
|
|
199
303
|
gl.uniform1f(locations.uniforms['u_domain_warp_enabled'], this._domainWarpEnabled ? 1.0 : 0.0);
|
|
200
304
|
gl.uniform1f(locations.uniforms['u_domain_warp_intensity'], this._domainWarpIntensity);
|
|
201
305
|
gl.uniform1f(locations.uniforms['u_domain_warp_scale'], this._domainWarpScale);
|
|
@@ -211,6 +315,9 @@ export class NeatGradient {
|
|
|
211
315
|
gl.uniform1f(locations.uniforms['u_bloom_intensity'], this._bloomIntensity);
|
|
212
316
|
gl.uniform1f(locations.uniforms['u_bloom_threshold'], this._bloomThreshold);
|
|
213
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);
|
|
214
321
|
this._uniformsDirty = false;
|
|
215
322
|
}
|
|
216
323
|
// Only regenerate procedural texture when needed
|
|
@@ -268,11 +375,12 @@ export class NeatGradient {
|
|
|
268
375
|
this._ref.width = width;
|
|
269
376
|
this._ref.height = height;
|
|
270
377
|
gl.viewport(0, 0, width, height);
|
|
271
|
-
updateCamera(camera, width, height);
|
|
378
|
+
updateCamera(camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
272
379
|
// Recompute projection matrix on resize
|
|
273
|
-
const projLoc =
|
|
380
|
+
const projLoc = this.glState.locations.uniforms["projectionMatrix"];
|
|
274
381
|
gl.useProgram(this.glState.program);
|
|
275
|
-
|
|
382
|
+
if (projLoc)
|
|
383
|
+
gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
276
384
|
};
|
|
277
385
|
// Debounce resize to prevent excessive operations
|
|
278
386
|
this.sizeObserver = new ResizeObserver(() => {
|
|
@@ -318,98 +426,292 @@ export class NeatGradient {
|
|
|
318
426
|
const dataURL = this._ref.toDataURL("image/png");
|
|
319
427
|
downloadURI(dataURL, filename);
|
|
320
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
|
+
}
|
|
321
570
|
set speed(speed) {
|
|
322
571
|
this._uniformsDirty = true;
|
|
323
572
|
this._speed = speed / 20;
|
|
324
573
|
}
|
|
574
|
+
get horizontalPressure() {
|
|
575
|
+
return this._horizontalPressure * 4;
|
|
576
|
+
}
|
|
325
577
|
set horizontalPressure(horizontalPressure) {
|
|
326
578
|
this._uniformsDirty = true;
|
|
327
579
|
this._horizontalPressure = horizontalPressure / 4;
|
|
328
580
|
}
|
|
581
|
+
get verticalPressure() {
|
|
582
|
+
return this._verticalPressure * 4;
|
|
583
|
+
}
|
|
329
584
|
set verticalPressure(verticalPressure) {
|
|
330
585
|
this._uniformsDirty = true;
|
|
331
586
|
this._verticalPressure = verticalPressure / 4;
|
|
332
587
|
}
|
|
588
|
+
get waveFrequencyX() {
|
|
589
|
+
return this._waveFrequencyX / 0.04;
|
|
590
|
+
}
|
|
333
591
|
set waveFrequencyX(waveFrequencyX) {
|
|
334
592
|
this._uniformsDirty = true;
|
|
335
593
|
this._waveFrequencyX = waveFrequencyX * 0.04;
|
|
336
594
|
}
|
|
595
|
+
get waveFrequencyY() {
|
|
596
|
+
return this._waveFrequencyY / 0.04;
|
|
597
|
+
}
|
|
337
598
|
set waveFrequencyY(waveFrequencyY) {
|
|
338
599
|
this._uniformsDirty = true;
|
|
339
600
|
this._waveFrequencyY = waveFrequencyY * 0.04;
|
|
340
601
|
}
|
|
602
|
+
get waveAmplitude() {
|
|
603
|
+
return this._waveAmplitude / 0.75;
|
|
604
|
+
}
|
|
341
605
|
set waveAmplitude(waveAmplitude) {
|
|
342
606
|
this._uniformsDirty = true;
|
|
343
607
|
this._waveAmplitude = waveAmplitude * .75;
|
|
344
608
|
}
|
|
609
|
+
get colors() {
|
|
610
|
+
return this._colors;
|
|
611
|
+
}
|
|
345
612
|
set colors(colors) {
|
|
346
613
|
this._uniformsDirty = true;
|
|
347
614
|
this._colors = colors;
|
|
348
615
|
this._cachedColorRgb = colors.map(c => this._hexToRgb(c.color));
|
|
349
616
|
this._colorsChanged = true;
|
|
350
617
|
}
|
|
618
|
+
get highlights() {
|
|
619
|
+
return this._highlights * 100;
|
|
620
|
+
}
|
|
351
621
|
set highlights(highlights) {
|
|
352
622
|
this._uniformsDirty = true;
|
|
353
623
|
this._highlights = highlights / 100;
|
|
354
624
|
}
|
|
625
|
+
get shadows() {
|
|
626
|
+
return this._shadows * 100;
|
|
627
|
+
}
|
|
355
628
|
set shadows(shadows) {
|
|
356
629
|
this._uniformsDirty = true;
|
|
357
630
|
this._shadows = shadows / 100;
|
|
358
631
|
}
|
|
632
|
+
get colorSaturation() {
|
|
633
|
+
return this._saturation * 10;
|
|
634
|
+
}
|
|
359
635
|
set colorSaturation(colorSaturation) {
|
|
360
636
|
this._uniformsDirty = true;
|
|
361
637
|
this._saturation = colorSaturation / 10;
|
|
362
638
|
}
|
|
639
|
+
get colorBrightness() {
|
|
640
|
+
return this._brightness;
|
|
641
|
+
}
|
|
363
642
|
set colorBrightness(colorBrightness) {
|
|
364
643
|
this._uniformsDirty = true;
|
|
365
644
|
this._brightness = colorBrightness;
|
|
366
645
|
}
|
|
646
|
+
get colorBlending() {
|
|
647
|
+
return this._colorBlending * 10;
|
|
648
|
+
}
|
|
367
649
|
set colorBlending(colorBlending) {
|
|
368
650
|
this._uniformsDirty = true;
|
|
369
651
|
this._colorBlending = colorBlending / 10;
|
|
370
652
|
}
|
|
653
|
+
get grainScale() {
|
|
654
|
+
return this._grainScale;
|
|
655
|
+
}
|
|
371
656
|
set grainScale(grainScale) {
|
|
372
657
|
this._uniformsDirty = true;
|
|
373
658
|
this._grainScale = grainScale == 0 ? 1 : grainScale;
|
|
374
659
|
}
|
|
660
|
+
get grainIntensity() {
|
|
661
|
+
return this._grainIntensity;
|
|
662
|
+
}
|
|
375
663
|
set grainIntensity(grainIntensity) {
|
|
376
664
|
this._uniformsDirty = true;
|
|
377
665
|
this._grainIntensity = grainIntensity;
|
|
378
666
|
}
|
|
667
|
+
get grainSparsity() {
|
|
668
|
+
return this._grainSparsity;
|
|
669
|
+
}
|
|
379
670
|
set grainSparsity(grainSparsity) {
|
|
380
671
|
this._uniformsDirty = true;
|
|
381
672
|
this._grainSparsity = grainSparsity;
|
|
382
673
|
}
|
|
674
|
+
get grainSpeed() {
|
|
675
|
+
return this._grainSpeed;
|
|
676
|
+
}
|
|
383
677
|
set grainSpeed(grainSpeed) {
|
|
384
678
|
this._uniformsDirty = true;
|
|
385
679
|
this._grainSpeed = grainSpeed;
|
|
386
680
|
}
|
|
681
|
+
get wireframe() {
|
|
682
|
+
return this._wireframe;
|
|
683
|
+
}
|
|
387
684
|
set wireframe(wireframe) {
|
|
388
685
|
this._uniformsDirty = true;
|
|
389
686
|
this._wireframe = wireframe;
|
|
390
687
|
}
|
|
688
|
+
get resolution() {
|
|
689
|
+
return this._resolution;
|
|
690
|
+
}
|
|
391
691
|
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);
|
|
692
|
+
if (this._resolution === resolution)
|
|
693
|
+
return;
|
|
694
|
+
this._resolution = resolution;
|
|
695
|
+
this._updateGeometry();
|
|
696
|
+
}
|
|
697
|
+
get backgroundColor() {
|
|
698
|
+
return this._backgroundColor;
|
|
403
699
|
}
|
|
404
700
|
set backgroundColor(backgroundColor) {
|
|
405
701
|
this._uniformsDirty = true;
|
|
406
702
|
this._backgroundColor = backgroundColor;
|
|
407
703
|
this._backgroundColorRgb = this._hexToRgb(backgroundColor);
|
|
408
704
|
}
|
|
705
|
+
get backgroundAlpha() {
|
|
706
|
+
return this._backgroundAlpha;
|
|
707
|
+
}
|
|
409
708
|
set backgroundAlpha(backgroundAlpha) {
|
|
410
709
|
this._uniformsDirty = true;
|
|
411
710
|
this._backgroundAlpha = backgroundAlpha;
|
|
412
711
|
}
|
|
712
|
+
get yOffset() {
|
|
713
|
+
return this._yOffset;
|
|
714
|
+
}
|
|
413
715
|
set yOffset(yOffset) {
|
|
414
716
|
this._uniformsDirty = true;
|
|
415
717
|
this._yOffset = yOffset;
|
|
@@ -435,18 +737,30 @@ export class NeatGradient {
|
|
|
435
737
|
this._uniformsDirty = true;
|
|
436
738
|
this._yOffsetFlowMultiplier = value / 1000;
|
|
437
739
|
}
|
|
740
|
+
get flowDistortionA() {
|
|
741
|
+
return this._flowDistortionA;
|
|
742
|
+
}
|
|
438
743
|
set flowDistortionA(value) {
|
|
439
744
|
this._uniformsDirty = true;
|
|
440
745
|
this._flowDistortionA = value;
|
|
441
746
|
}
|
|
747
|
+
get flowDistortionB() {
|
|
748
|
+
return this._flowDistortionB;
|
|
749
|
+
}
|
|
442
750
|
set flowDistortionB(value) {
|
|
443
751
|
this._uniformsDirty = true;
|
|
444
752
|
this._flowDistortionB = value;
|
|
445
753
|
}
|
|
754
|
+
get flowScale() {
|
|
755
|
+
return this._flowScale;
|
|
756
|
+
}
|
|
446
757
|
set flowScale(value) {
|
|
447
758
|
this._uniformsDirty = true;
|
|
448
759
|
this._flowScale = value;
|
|
449
760
|
}
|
|
761
|
+
get flowEase() {
|
|
762
|
+
return this._flowEase;
|
|
763
|
+
}
|
|
450
764
|
set flowEase(value) {
|
|
451
765
|
this._uniformsDirty = true;
|
|
452
766
|
this._flowEase = value;
|
|
@@ -458,6 +772,9 @@ export class NeatGradient {
|
|
|
458
772
|
get flowEnabled() {
|
|
459
773
|
return this._flowEnabled;
|
|
460
774
|
}
|
|
775
|
+
get enableProceduralTexture() {
|
|
776
|
+
return this._enableProceduralTexture;
|
|
777
|
+
}
|
|
461
778
|
set enableProceduralTexture(value) {
|
|
462
779
|
this._uniformsDirty = true;
|
|
463
780
|
this._enableProceduralTexture = value;
|
|
@@ -465,6 +782,9 @@ export class NeatGradient {
|
|
|
465
782
|
this._textureNeedsUpdate = true;
|
|
466
783
|
}
|
|
467
784
|
}
|
|
785
|
+
get textureVoidLikelihood() {
|
|
786
|
+
return this._textureVoidLikelihood;
|
|
787
|
+
}
|
|
468
788
|
set textureVoidLikelihood(value) {
|
|
469
789
|
this._uniformsDirty = true;
|
|
470
790
|
this._textureVoidLikelihood = value;
|
|
@@ -472,6 +792,9 @@ export class NeatGradient {
|
|
|
472
792
|
this._textureNeedsUpdate = true;
|
|
473
793
|
}
|
|
474
794
|
}
|
|
795
|
+
get textureVoidWidthMin() {
|
|
796
|
+
return this._textureVoidWidthMin;
|
|
797
|
+
}
|
|
475
798
|
set textureVoidWidthMin(value) {
|
|
476
799
|
this._uniformsDirty = true;
|
|
477
800
|
this._textureVoidWidthMin = value;
|
|
@@ -479,6 +802,9 @@ export class NeatGradient {
|
|
|
479
802
|
this._textureNeedsUpdate = true;
|
|
480
803
|
}
|
|
481
804
|
}
|
|
805
|
+
get textureVoidWidthMax() {
|
|
806
|
+
return this._textureVoidWidthMax;
|
|
807
|
+
}
|
|
482
808
|
set textureVoidWidthMax(value) {
|
|
483
809
|
this._uniformsDirty = true;
|
|
484
810
|
this._textureVoidWidthMax = value;
|
|
@@ -486,6 +812,9 @@ export class NeatGradient {
|
|
|
486
812
|
this._textureNeedsUpdate = true;
|
|
487
813
|
}
|
|
488
814
|
}
|
|
815
|
+
get textureBandDensity() {
|
|
816
|
+
return this._textureBandDensity;
|
|
817
|
+
}
|
|
489
818
|
set textureBandDensity(value) {
|
|
490
819
|
this._uniformsDirty = true;
|
|
491
820
|
this._textureBandDensity = value;
|
|
@@ -493,6 +822,9 @@ export class NeatGradient {
|
|
|
493
822
|
this._textureNeedsUpdate = true;
|
|
494
823
|
}
|
|
495
824
|
}
|
|
825
|
+
get textureColorBlending() {
|
|
826
|
+
return this._textureColorBlending;
|
|
827
|
+
}
|
|
496
828
|
set textureColorBlending(value) {
|
|
497
829
|
this._uniformsDirty = true;
|
|
498
830
|
this._textureColorBlending = value;
|
|
@@ -500,6 +832,9 @@ export class NeatGradient {
|
|
|
500
832
|
this._textureNeedsUpdate = true;
|
|
501
833
|
}
|
|
502
834
|
}
|
|
835
|
+
get textureSeed() {
|
|
836
|
+
return this._textureSeed;
|
|
837
|
+
}
|
|
503
838
|
set textureSeed(value) {
|
|
504
839
|
this._uniformsDirty = true;
|
|
505
840
|
this._textureSeed = value;
|
|
@@ -514,6 +849,19 @@ export class NeatGradient {
|
|
|
514
849
|
this._uniformsDirty = true;
|
|
515
850
|
this._textureEase = value;
|
|
516
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
|
+
}
|
|
517
865
|
set proceduralBackgroundColor(value) {
|
|
518
866
|
this._uniformsDirty = true;
|
|
519
867
|
this._proceduralBackgroundColor = value;
|
|
@@ -521,30 +869,90 @@ export class NeatGradient {
|
|
|
521
869
|
this._textureNeedsUpdate = true;
|
|
522
870
|
}
|
|
523
871
|
}
|
|
872
|
+
get textureShapeTriangles() {
|
|
873
|
+
return this._textureShapeTriangles;
|
|
874
|
+
}
|
|
524
875
|
set textureShapeTriangles(value) {
|
|
525
876
|
this._uniformsDirty = true;
|
|
526
877
|
this._textureShapeTriangles = value;
|
|
527
878
|
if (this._enableProceduralTexture)
|
|
528
879
|
this._textureNeedsUpdate = true;
|
|
529
880
|
}
|
|
881
|
+
get textureShapeCircles() {
|
|
882
|
+
return this._textureShapeCircles;
|
|
883
|
+
}
|
|
530
884
|
set textureShapeCircles(value) {
|
|
531
885
|
this._uniformsDirty = true;
|
|
532
886
|
this._textureShapeCircles = value;
|
|
533
887
|
if (this._enableProceduralTexture)
|
|
534
888
|
this._textureNeedsUpdate = true;
|
|
535
889
|
}
|
|
890
|
+
get textureShapeBars() {
|
|
891
|
+
return this._textureShapeBars;
|
|
892
|
+
}
|
|
536
893
|
set textureShapeBars(value) {
|
|
537
894
|
this._uniformsDirty = true;
|
|
538
895
|
this._textureShapeBars = value;
|
|
539
896
|
if (this._enableProceduralTexture)
|
|
540
897
|
this._textureNeedsUpdate = true;
|
|
541
898
|
}
|
|
899
|
+
get textureShapeSquiggles() {
|
|
900
|
+
return this._textureShapeSquiggles;
|
|
901
|
+
}
|
|
542
902
|
set textureShapeSquiggles(value) {
|
|
543
903
|
this._uniformsDirty = true;
|
|
544
904
|
this._textureShapeSquiggles = value;
|
|
545
905
|
if (this._enableProceduralTexture)
|
|
546
906
|
this._textureNeedsUpdate = true;
|
|
547
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
|
+
}
|
|
548
956
|
_hexToRgb(hex) {
|
|
549
957
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
550
958
|
return [
|
|
@@ -564,8 +972,24 @@ export class NeatGradient {
|
|
|
564
972
|
const ext = gl.getExtension("OES_standard_derivatives");
|
|
565
973
|
gl.getExtension("OES_element_index_uint");
|
|
566
974
|
gl.viewport(0, 0, width, height);
|
|
567
|
-
// Generate
|
|
568
|
-
|
|
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;
|
|
569
993
|
const positionBuffer = gl.createBuffer();
|
|
570
994
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
571
995
|
gl.bufferData(gl.ARRAY_BUFFER, position, gl.STATIC_DRAW);
|
|
@@ -620,7 +1044,7 @@ export class NeatGradient {
|
|
|
620
1044
|
gl.useProgram(program);
|
|
621
1045
|
const camera = new OrthographicCamera(0, 0, 0, 0, 0, 1000);
|
|
622
1046
|
camera.position = [0, 0, 5];
|
|
623
|
-
updateCamera(camera, width, height);
|
|
1047
|
+
updateCamera(camera, width, height, PLANE_WIDTH, PLANE_HEIGHT, this._shapeType, this._cameraZoom);
|
|
624
1048
|
// Define attributes
|
|
625
1049
|
const aPosition = gl.getAttribLocation(program, "position");
|
|
626
1050
|
const aNormal = gl.getAttribLocation(program, "normal");
|
|
@@ -635,15 +1059,7 @@ export class NeatGradient {
|
|
|
635
1059
|
gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer);
|
|
636
1060
|
gl.vertexAttribPointer(aUv, 2, gl.FLOAT, false, 0, 0);
|
|
637
1061
|
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);
|
|
1062
|
+
// modelViewMatrix is set dynamically in the render loop
|
|
647
1063
|
const projLoc = gl.getUniformLocation(program, "projectionMatrix");
|
|
648
1064
|
gl.uniformMatrix4fv(projLoc, false, camera.projectionMatrix.elements);
|
|
649
1065
|
const planeWidthLoc = gl.getUniformLocation(program, "u_plane_width");
|
|
@@ -653,17 +1069,19 @@ export class NeatGradient {
|
|
|
653
1069
|
const colorsCountLoc = gl.getUniformLocation(program, "u_colors_count");
|
|
654
1070
|
gl.uniform1i(colorsCountLoc, COLORS_COUNT);
|
|
655
1071
|
const uniformsList = [
|
|
1072
|
+
"projectionMatrix", "modelViewMatrix",
|
|
656
1073
|
"u_time", "u_resolution", "u_color_pressure", "u_wave_frequency_x", "u_wave_frequency_y",
|
|
657
1074
|
"u_wave_amplitude", "u_colors_count", "u_plane_width", "u_plane_height", "u_shadows",
|
|
658
1075
|
"u_highlights", "u_grain_intensity", "u_grain_sparsity", "u_grain_scale", "u_grain_speed",
|
|
659
1076
|
"u_flow_distortion_a", "u_flow_distortion_b", "u_flow_scale", "u_flow_ease", "u_flow_enabled",
|
|
660
1077
|
"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",
|
|
1078
|
+
"u_procedural_texture", "u_enable_procedural_texture", "u_texture_ease", "u_transparent_texture_void", "u_saturation", "u_brightness", "u_color_blending",
|
|
662
1079
|
"u_domain_warp_enabled", "u_domain_warp_intensity", "u_domain_warp_scale",
|
|
663
1080
|
"u_vignette_intensity", "u_vignette_radius",
|
|
664
1081
|
"u_fresnel_enabled", "u_fresnel_power", "u_fresnel_intensity", "u_fresnel_color",
|
|
665
1082
|
"u_iridescence_enabled", "u_iridescence_intensity", "u_iridescence_speed",
|
|
666
|
-
"u_bloom_intensity", "u_bloom_threshold", "u_chromatic_aberration"
|
|
1083
|
+
"u_bloom_intensity", "u_bloom_threshold", "u_chromatic_aberration",
|
|
1084
|
+
"u_shape_type", "u_silhouette_fade", "u_cylinder_fade", "u_ribbon_fade"
|
|
667
1085
|
];
|
|
668
1086
|
const locations = {
|
|
669
1087
|
attributes: { position: aPosition, normal: aNormal, uv: aUv },
|
|
@@ -708,10 +1126,14 @@ export class NeatGradient {
|
|
|
708
1126
|
// Texture size - 1024 provides good balance between quality and performance
|
|
709
1127
|
// Reduced from 2048 for better performance
|
|
710
1128
|
const texSize = 1024;
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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;
|
|
715
1137
|
if (!sCtx)
|
|
716
1138
|
return null;
|
|
717
1139
|
let seed = this._textureSeed;
|
|
@@ -727,6 +1149,9 @@ export class NeatGradient {
|
|
|
727
1149
|
const colors = this._colors.filter(c => c.enabled).map(c => c.color);
|
|
728
1150
|
if (colors.length === 0)
|
|
729
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];
|
|
730
1155
|
// Helper functions
|
|
731
1156
|
function hexToRgb(hex) {
|
|
732
1157
|
const bigint = parseInt(hex.replace('#', ''), 16);
|
|
@@ -763,64 +1188,120 @@ export class NeatGradient {
|
|
|
763
1188
|
sCtx.fillRect(0, 0, texSize, texSize);
|
|
764
1189
|
// Triangles: use configurable count
|
|
765
1190
|
for (let i = 0; i < this._textureShapeTriangles; i++) {
|
|
766
|
-
|
|
767
|
-
sCtx.beginPath();
|
|
1191
|
+
const fillStyle = getInterColor();
|
|
768
1192
|
const x = random() * texSize;
|
|
769
1193
|
const y = random() * texSize;
|
|
770
1194
|
const s = 100 + random() * 300;
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
+
}
|
|
775
1211
|
}
|
|
776
1212
|
// Circles / rings: use configurable count
|
|
777
1213
|
for (let i = 0; i < this._textureShapeCircles; i++) {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
sCtx.beginPath();
|
|
1214
|
+
const strokeStyle = getInterColor();
|
|
1215
|
+
const lineWidth = 10 + random() * 50;
|
|
781
1216
|
const x = random() * texSize;
|
|
782
1217
|
const y = random() * texSize;
|
|
783
1218
|
const r = 50 + random() * 150;
|
|
784
|
-
|
|
785
|
-
|
|
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
|
+
}
|
|
786
1228
|
}
|
|
787
1229
|
// Bars: use configurable count
|
|
788
1230
|
for (let i = 0; i < this._textureShapeBars; i++) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
+
}
|
|
795
1245
|
}
|
|
796
1246
|
// Squiggles: use configurable count
|
|
797
1247
|
sCtx.lineWidth = 15;
|
|
798
1248
|
sCtx.lineCap = 'round';
|
|
799
1249
|
for (let i = 0; i < this._textureShapeSquiggles; i++) {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
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;
|
|
805
1256
|
for (let j = 0; j < 4; j++) {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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
|
+
}
|
|
809
1282
|
}
|
|
810
|
-
sCtx.stroke();
|
|
811
1283
|
}
|
|
812
1284
|
// === MASKED CANVAS ===
|
|
813
1285
|
// Masking: Seed isolation
|
|
814
1286
|
setSeed(50000);
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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;
|
|
819
1295
|
if (!ctx)
|
|
820
1296
|
return null;
|
|
821
1297
|
// Start filled with the chosen void color so gaps show that color
|
|
822
|
-
|
|
823
|
-
|
|
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
|
+
}
|
|
824
1305
|
// Determine layout segments (matter vs void)
|
|
825
1306
|
let layoutHead = 0;
|
|
826
1307
|
const segments = [];
|
|
@@ -869,54 +1350,108 @@ export class NeatGradient {
|
|
|
869
1350
|
}
|
|
870
1351
|
return tex;
|
|
871
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
|
+
}
|
|
872
1383
|
set domainWarpEnabled(enabled) {
|
|
873
1384
|
if (this._domainWarpEnabled !== enabled) {
|
|
874
1385
|
this._domainWarpEnabled = enabled;
|
|
875
1386
|
this._uniformsDirty = true;
|
|
876
1387
|
}
|
|
877
1388
|
}
|
|
1389
|
+
get domainWarpIntensity() {
|
|
1390
|
+
return this._domainWarpIntensity;
|
|
1391
|
+
}
|
|
878
1392
|
set domainWarpIntensity(intensity) {
|
|
879
1393
|
if (this._domainWarpIntensity !== intensity) {
|
|
880
1394
|
this._domainWarpIntensity = intensity;
|
|
881
1395
|
this._uniformsDirty = true;
|
|
882
1396
|
}
|
|
883
1397
|
}
|
|
1398
|
+
get domainWarpScale() {
|
|
1399
|
+
return this._domainWarpScale;
|
|
1400
|
+
}
|
|
884
1401
|
set domainWarpScale(scale) {
|
|
885
1402
|
if (this._domainWarpScale !== scale) {
|
|
886
1403
|
this._domainWarpScale = scale;
|
|
887
1404
|
this._uniformsDirty = true;
|
|
888
1405
|
}
|
|
889
1406
|
}
|
|
1407
|
+
get vignetteIntensity() {
|
|
1408
|
+
return this._vignetteIntensity;
|
|
1409
|
+
}
|
|
890
1410
|
set vignetteIntensity(intensity) {
|
|
891
1411
|
if (this._vignetteIntensity !== intensity) {
|
|
892
1412
|
this._vignetteIntensity = intensity;
|
|
893
1413
|
this._uniformsDirty = true;
|
|
894
1414
|
}
|
|
895
1415
|
}
|
|
1416
|
+
get vignetteRadius() {
|
|
1417
|
+
return this._vignetteRadius;
|
|
1418
|
+
}
|
|
896
1419
|
set vignetteRadius(radius) {
|
|
897
1420
|
if (this._vignetteRadius !== radius) {
|
|
898
1421
|
this._vignetteRadius = radius;
|
|
899
1422
|
this._uniformsDirty = true;
|
|
900
1423
|
}
|
|
901
1424
|
}
|
|
1425
|
+
get fresnelEnabled() {
|
|
1426
|
+
return this._fresnelEnabled;
|
|
1427
|
+
}
|
|
902
1428
|
set fresnelEnabled(enabled) {
|
|
903
1429
|
if (this._fresnelEnabled !== enabled) {
|
|
904
1430
|
this._fresnelEnabled = enabled;
|
|
905
1431
|
this._uniformsDirty = true;
|
|
906
1432
|
}
|
|
907
1433
|
}
|
|
1434
|
+
get fresnelPower() {
|
|
1435
|
+
return this._fresnelPower;
|
|
1436
|
+
}
|
|
908
1437
|
set fresnelPower(power) {
|
|
909
1438
|
if (this._fresnelPower !== power) {
|
|
910
1439
|
this._fresnelPower = power;
|
|
911
1440
|
this._uniformsDirty = true;
|
|
912
1441
|
}
|
|
913
1442
|
}
|
|
1443
|
+
get fresnelIntensity() {
|
|
1444
|
+
return this._fresnelIntensity;
|
|
1445
|
+
}
|
|
914
1446
|
set fresnelIntensity(intensity) {
|
|
915
1447
|
if (this._fresnelIntensity !== intensity) {
|
|
916
1448
|
this._fresnelIntensity = intensity;
|
|
917
1449
|
this._uniformsDirty = true;
|
|
918
1450
|
}
|
|
919
1451
|
}
|
|
1452
|
+
get fresnelColor() {
|
|
1453
|
+
return this._fresnelColor;
|
|
1454
|
+
}
|
|
920
1455
|
set fresnelColor(fresnelColor) {
|
|
921
1456
|
if (this._fresnelColor !== fresnelColor) {
|
|
922
1457
|
this._fresnelColor = fresnelColor;
|
|
@@ -924,42 +1459,199 @@ export class NeatGradient {
|
|
|
924
1459
|
this._uniformsDirty = true;
|
|
925
1460
|
}
|
|
926
1461
|
}
|
|
1462
|
+
get iridescenceEnabled() {
|
|
1463
|
+
return this._iridescenceEnabled;
|
|
1464
|
+
}
|
|
927
1465
|
set iridescenceEnabled(enabled) {
|
|
928
1466
|
if (this._iridescenceEnabled !== enabled) {
|
|
929
1467
|
this._iridescenceEnabled = enabled;
|
|
930
1468
|
this._uniformsDirty = true;
|
|
931
1469
|
}
|
|
932
1470
|
}
|
|
1471
|
+
get iridescenceIntensity() {
|
|
1472
|
+
return this._iridescenceIntensity;
|
|
1473
|
+
}
|
|
933
1474
|
set iridescenceIntensity(intensity) {
|
|
934
1475
|
if (this._iridescenceIntensity !== intensity) {
|
|
935
1476
|
this._iridescenceIntensity = intensity;
|
|
936
1477
|
this._uniformsDirty = true;
|
|
937
1478
|
}
|
|
938
1479
|
}
|
|
1480
|
+
get iridescenceSpeed() {
|
|
1481
|
+
return this._iridescenceSpeed;
|
|
1482
|
+
}
|
|
939
1483
|
set iridescenceSpeed(speed) {
|
|
940
1484
|
if (this._iridescenceSpeed !== speed) {
|
|
941
1485
|
this._iridescenceSpeed = speed;
|
|
942
1486
|
this._uniformsDirty = true;
|
|
943
1487
|
}
|
|
944
1488
|
}
|
|
1489
|
+
get bloomIntensity() {
|
|
1490
|
+
return this._bloomIntensity;
|
|
1491
|
+
}
|
|
945
1492
|
set bloomIntensity(intensity) {
|
|
946
1493
|
if (this._bloomIntensity !== intensity) {
|
|
947
1494
|
this._bloomIntensity = intensity;
|
|
948
1495
|
this._uniformsDirty = true;
|
|
949
1496
|
}
|
|
950
1497
|
}
|
|
1498
|
+
get bloomThreshold() {
|
|
1499
|
+
return this._bloomThreshold;
|
|
1500
|
+
}
|
|
951
1501
|
set bloomThreshold(threshold) {
|
|
952
1502
|
if (this._bloomThreshold !== threshold) {
|
|
953
1503
|
this._bloomThreshold = threshold;
|
|
954
1504
|
this._uniformsDirty = true;
|
|
955
1505
|
}
|
|
956
1506
|
}
|
|
1507
|
+
get chromaticAberration() {
|
|
1508
|
+
return this._chromaticAberration;
|
|
1509
|
+
}
|
|
957
1510
|
set chromaticAberration(aberration) {
|
|
958
1511
|
if (this._chromaticAberration !== aberration) {
|
|
959
1512
|
this._chromaticAberration = aberration;
|
|
960
1513
|
this._uniformsDirty = true;
|
|
961
1514
|
}
|
|
962
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
|
+
}
|
|
963
1655
|
}
|
|
964
1656
|
const setLinkStyles = (link) => {
|
|
965
1657
|
link.id = LINK_ID;
|