/** @type {HTMLCanvasElement} */
const canvas = document.getElementById("canvas");
const gl = canvas.getContext("webgl");
const iterations = 1000;
const startingX = -.566;
const startingY = .525;
let smallRes;
const vShaderSource = `
attribute vec4 a_position;
uniform vec2 u_unitSize;
varying vec2 v_position;
void main() {
gl_Position = a_position;
v_position = u_unitSize * a_position.xy;
}
`;
const fShaderSource = `
precision highp float;
uniform vec2 u_constant;
varying vec2 v_position;
float calcJulia(vec2 pos) {
float newRE = pos.x;
float newIM = pos.y;
for (int i = 0; i < ${iterations}; i++) {
float oldRE = newRE;
float oldIM = newIM;
newRE = oldRE * oldRE - oldIM * oldIM + u_constant.x;
newIM = 2.0 * oldRE * oldIM + u_constant.y;
float dist = newRE * newRE + newIM * newIM;
if (dist >= 4.0) {
return float(i + 1) - log(log(dist))/log(2.0);
}
}
return -1.0;
}
void main() {
float calced = calcJulia(v_position);
float t = mod(calced / 100.0, 2.0);
if (t > 1.0) {
t = 2.0 - t;
}
if (calced == -1.0) {
gl_FragColor = vec4(.0, .0, .0, 1.0);
}
else if (t <= 1.0 / 6.0) {
gl_FragColor = vec4(1.0, t * 6.0, .0, 1.0);
} else if (t <= 1.0 / 3.0) {
gl_FragColor = vec4(1.0 - t * 6.0 + 1.0, 1.0, .0, 1.0);
} else if (t <= 1.0 / 2.0) {
gl_FragColor = vec4(.0, 1.0, t * 6.0 - 2.0, 1.0);
} else if (t <= 2.0 / 3.0) {
gl_FragColor = vec4(.0, 1.0 - t * 6.0 + 3.0, 1.0, 1.0);
} else if (t <= 5.0 / 6.0) {
gl_FragColor = vec4(t * 6.0 - 4.0, .0, 1.0, 1.0);
} else {
gl_FragColor = vec4(1.0, .0, 1.0 - t * 6.0 + 5.0, 1.0);
}
// else { // VIRIDIS COLOR SCHEME (maybe)
// gl_FragColor = vec4(
// .280268003 + .230519207 * t,
// .165368639 + .836977008 * t - .271554750 * t*t,
// .476688822 - .290407016 * t + .009943850 * t*t,
// 1.0
// );
// }
}
`;
function createShader(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
const vertexShader = createShader(gl.VERTEX_SHADER, vShaderSource);
const fragmentShader = createShader(gl.FRAGMENT_SHADER, fShaderSource);
function createProgram(vShader, fShader) {
const program = gl.createProgram();
gl.attachShader(program, vShader);
gl.attachShader(program, fShader);
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
const program = createProgram(vertexShader, fragmentShader);
gl.useProgram(program);
const posBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
const constLocation = gl.getUniformLocation(program, "u_constant");
const uSizeLocation = gl.getUniformLocation(program, "u_unitSize");
let toRedraw = false;
function resizeCanvas() {
canvas.width = window.innerWidth * window.devicePixelRatio;
canvas.height = window.innerHeight * window.devicePixelRatio;
smallRes = Math.min(canvas.width, canvas.height);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.uniform2f(uSizeLocation, canvas.width / smallRes, canvas.height / smallRes);
toRedraw = true;
}
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
const posAttribLocation = gl.getAttribLocation(program, "a_position");
const positions = [
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, 1,
1, -1
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(posAttribLocation);
gl.vertexAttribPointer(posAttribLocation, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
function drawJulia(constPosX, constPosY) {
gl.uniform2f(constLocation, constPosX, constPosY);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
let posX = 0;
let posY = 0;
document.addEventListener("mousemove", e => {
posX = e.clientX * window.devicePixelRatio;
posY = e.clientY * window.devicePixelRatio;
});
document.addEventListener("touchmove", e => {
e.preventDefault();
posX = e.touches[0].clientX * window.devicePixelRatio;
posY = e.touches[0].clientY * window.devicePixelRatio;
});
let currentPosX = 0;
let currentPosY = 0;
const normalRate = .2;
const lowEaseRate = .01 * normalRate;
let easeRate = normalRate;
document.addEventListener("keydown", e => {
if (e.key == "Shift") {
easeRate = lowEaseRate;
}
});
document.addEventListener("keyup", e => {
if (e.key == "Shift") {
easeRate = normalRate;
}
});
function main() {
const deltaPosX = posX - currentPosX;
const deltaPosY = posY - currentPosY;
currentPosX += easeRate * deltaPosX;
currentPosY += easeRate * deltaPosY;
const actualPosX = 2 * (currentPosX - canvas.width / 2) / smallRes;
const actualPosY = 2 * (currentPosY - canvas.height / 2) / smallRes;
if (toRedraw || deltaPosX**2 + deltaPosY**2 > .005**2) {
drawJulia(actualPosX, actualPosY);
toRedraw = false;
}
requestAnimationFrame(main)
}
// Initiating...
drawJulia(startingX, startingY);
toRedraw = false;
main();
// setInterval(main)