-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathmain.js
213 lines (178 loc) · 7.68 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*
To do:
Press z for zen mode (hides all control and other display on top of the canvas)
Ability to add this shader effect on top of an image?
Presets / seed choice??
Allow user to upload a song, and then it becomes audio reactive?
Generate perfect loops in x seconds
*/
// Initialize WebGL context
const canvas = document.getElementById('canvas');
let startingWidth = 1000;
let startingHeight = Math.round(Math.max(1000, Math.min(4000, startingWidth * (window.innerHeight/window.innerWidth))) / 4) * 4;
canvas.width = startingWidth;
canvas.height = startingHeight;
console.log("canvas width/height: "+canvas.width+" / "+canvas.height);
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
let isPlaying = false;
let animationID = null;
let randomSeed;
let time;
let timeOffset = 0;
// FPS tracking variables
let frameCount = 0;
let lastTime = 0;
let fps = 0;
const fpsIndicator = document.getElementById('fpsIndicator');
if (!gl) {
alert('WebGL not supported');
}
// Compile shaders
function compileShader(source, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Shader compilation error:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
// Create program
const vertexShader = compileShader(document.getElementById('vertexShader').textContent, gl.VERTEX_SHADER);
const fragmentShader = compileShader(document.getElementById('fragmentShader').textContent, gl.FRAGMENT_SHADER);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Program linking error:', gl.getProgramInfoLog(program));
}
gl.useProgram(program);
// Create rectangle covering the entire canvas
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
1.0, 1.0
]), gl.STATIC_DRAW);
// Set up attributes and uniforms
const positionLocation = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
const timeLocation = gl.getUniformLocation(program, 'time');
const resolutionLocation = gl.getUniformLocation(program, 'resolution');
const seedLocation = gl.getUniformLocation(program, 'seed');
// GUI-controlled uniform locations
const timeScaleLocation = gl.getUniformLocation(program, 'timeScale');
const bloomStrengthLocation = gl.getUniformLocation(program, 'bloomStrength');
const saturationLocation = gl.getUniformLocation(program, 'saturation');
const grainAmountLocation = gl.getUniformLocation(program, 'grainAmount');
const colorTintLocation = gl.getUniformLocation(program, 'colorTint');
const minCircleSizeLocation = gl.getUniformLocation(program, 'minCircleSize');
const circleStrengthLocation = gl.getUniformLocation(program, 'circleStrength');
const distortXLocation = gl.getUniformLocation(program, 'distortX');
const distortYLocation = gl.getUniformLocation(program, 'distortY');
const patternAmpLocation = gl.getUniformLocation(program, 'patternAmp');
const patternFreqLocation = gl.getUniformLocation(program, 'patternFreq');
// Initialize parameters object for dat.gui
const params = {
canvasWidth: startingWidth,
canvasHeight: startingHeight,
timeScale: 0.4,
patternAmp: 12.0,
patternFreq: 0.5,
bloomStrength: 1.0,
saturation: 0.85,
grainAmount: 0.2,
colorTintR: 1.0,
colorTintG: 1.0,
colorTintB: 1.0,
minCircleSize: 3.0,
circleStrength: 1.0,
distortX: 5.0,
distortY: 20.0,
};
// Also refresh on page load
window.addEventListener('load', refreshPattern);
// Initialize dat.gui
const gui = new dat.GUI({ autoplace: false });
gui.close();
// Add GUI controls with folders for organization
const canvasFolder = gui.addFolder('Canvas Size');
canvasFolder.add(params, 'canvasWidth', 100, 4000).step(10).name('Width').onChange(updateCanvasSize);
canvasFolder.add(params, 'canvasHeight', 100, 4000).step(10).name('Height').onChange(updateCanvasSize);
canvasFolder.open();
const timeFolder = gui.addFolder('Animation');
timeFolder.add(params, 'timeScale', 0.1, 3.0).name('Speed').onChange(updateUniforms);
timeFolder.open();
const patternFolder = gui.addFolder('Pattern');
patternFolder.add(params, 'patternAmp', 1.0, 50.0).step(0.1).name('Pattern Amp').onChange(updateUniforms);
patternFolder.add(params, 'patternFreq', 0.2, 10.0).step(0.1).name('Pattern Freq').onChange(updateUniforms);
patternFolder.open();
const visualFolder = gui.addFolder('Visual Effects');
visualFolder.add(params, 'bloomStrength', 0.0, 5.0).name('Bloom').onChange(updateUniforms);
visualFolder.add(params, 'saturation', 0.0, 2.0).name('Saturation').onChange(updateUniforms);
visualFolder.add(params, 'grainAmount', 0.0, 0.5).name('Grain').onChange(updateUniforms);
visualFolder.add(params, 'minCircleSize', 0.0, 10.0).name('Circle Size').onChange(updateUniforms);
visualFolder.add(params, 'circleStrength', 0.0, 3.0).name('Circle Strength').onChange(updateUniforms);
visualFolder.add(params, 'distortX', 0.0, 50.0).name('Distort-X').onChange(updateUniforms);
visualFolder.add(params, 'distortY', 0.0, 50.0).name('Distort-Y').onChange(updateUniforms);
visualFolder.open();
const colorFolder = gui.addFolder('Color Tint');
colorFolder.add(params, 'colorTintR', 0.0, 1.5).name('Red').onChange(updateUniforms);
colorFolder.add(params, 'colorTintG', 0.0, 1.5).name('Green').onChange(updateUniforms);
colorFolder.add(params, 'colorTintB', 0.0, 1.5).name('Blue').onChange(updateUniforms);
colorFolder.open();
// Function to update shader uniforms from GUI values
function updateUniforms() {
gl.uniform1f(timeScaleLocation, params.timeScale);
gl.uniform1f(patternAmpLocation, params.patternAmp);
gl.uniform1f(patternFreqLocation, params.patternFreq);
gl.uniform1f(bloomStrengthLocation, params.bloomStrength);
gl.uniform1f(saturationLocation, params.saturation);
gl.uniform1f(grainAmountLocation, params.grainAmount);
gl.uniform3f(colorTintLocation, params.colorTintR, params.colorTintG, params.colorTintB);
gl.uniform1f(minCircleSizeLocation, params.minCircleSize);
gl.uniform1f(circleStrengthLocation, params.circleStrength);
gl.uniform1f(distortXLocation, params.distortX);
gl.uniform1f(distortYLocation, params.distortY);
}
function drawScene(){
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
// Animation loop
function render(timestamp) {
if (isPlaying) {
// Calculate adjusted time by subtracting the offset
const adjustedTime = timestamp - timeOffset;
time = timestamp;
const timeInSeconds = adjustedTime * 0.0035;
gl.uniform1f(timeLocation, timeInSeconds);
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
// FPS calculation
frameCount++;
// Update FPS every 500ms
if (time - lastTime >= 500) {
// Calculate FPS: frameCount / timeDiff in seconds
fps = Math.round((frameCount * 1000) / (time - lastTime));
fpsIndicator.textContent = `FPS: ${fps}`;
// Reset for next update
frameCount = 0;
lastTime = time;
}
// If video recording is ongoing, drawScene is called already
if (!recordVideoState || useMobileRecord) {
drawScene();
}
animationID = requestAnimationFrame(render);
}
}
// Start the animation loop
isPlaying = true;
refreshPattern();
updateUniforms();
animationID = requestAnimationFrame(render);