170 lines
6.4 KiB
JavaScript
170 lines
6.4 KiB
JavaScript
"use strict";
|
|
//
|
|
// This program graphs the Mandelbrot set and the burning ship fractal.
|
|
//
|
|
|
|
let width, height = 0;
|
|
// Set default viewing point
|
|
let view = [-0.75, 0];
|
|
let canvas, context, imagedata, viewWidth;
|
|
|
|
|
|
|
|
// Event called every time the page loads or reloads
|
|
window.onload = function() {
|
|
canvas = document.getElementById("viewport");
|
|
width = canvas.width;
|
|
height = canvas.height;
|
|
// Obtain the 2d graphics context from the canvas
|
|
context = canvas.getContext("2d");
|
|
// create an image data object that will be set with pixels later
|
|
imagedata = context.createImageData(width, height);
|
|
|
|
render();
|
|
|
|
// Register event to trigger on click of the canvas
|
|
canvas.addEventListener("mousedown", function(e)
|
|
{
|
|
clickCanvas(e);
|
|
});
|
|
};
|
|
|
|
function clickCanvas(event) {
|
|
// Get mouse position on canvas
|
|
const rect = canvas.getBoundingClientRect();
|
|
const x = event.clientX - rect.left;
|
|
const y = event.clientY - rect.top;
|
|
// Convert canvas coordinates to usable coordinates within the coordinate system
|
|
const yu = (viewWidth / height * (2 * (height - y) - height)) + view[1];
|
|
const xu = (viewWidth / width * (2 * x - width)) + view[0];
|
|
// Update the viewing area
|
|
view = [xu, yu];
|
|
// Re-render the canvas
|
|
render();
|
|
}
|
|
|
|
function render() {
|
|
// Store the starting time of render so that it can be subtracted from the end time to find the render time.
|
|
const startTime = performance.now();
|
|
|
|
// Read the value of the zoom slider, then scale it down.
|
|
const zoomv = document.getElementById("zoom").value / 100;
|
|
// Read the value of the iterations slider
|
|
const maxIterations = document.getElementById("iterations").value;
|
|
// Get value of radio button to see which mode will be used for coloring
|
|
const colorMode = document.getElementById("mode").options[document.getElementById("mode").selectedIndex].value;
|
|
// Determine whether or not mandelbrot or burning ship
|
|
const fractalType = document.getElementById("type").options[document.getElementById("type").selectedIndex].value;
|
|
|
|
// Adjust the viewWidth the be : viewWidth = 1 / 10^zoom
|
|
viewWidth = Math.pow(10, -zoomv);
|
|
|
|
// Loop through every pixel on the canvas representing points on the complex plane.
|
|
// X values, (Real)
|
|
for (let x = 0; x <= width; x++) {
|
|
// Y values, (Imaginary)
|
|
for (let y = 0; y <= height; y++) {
|
|
// Reset the colors for each individual pixel after a loop
|
|
let red, green, blue = 0;
|
|
|
|
// xu and yu represent the coordinate system adjusted values of x and y (canvas values)
|
|
// This is needed as the top left of the canvas is considered (0,0). Without this, only the 4th quadrant would be rendered, and there would be no zoom.
|
|
const yu = (viewWidth / height * (2 * (height - y) - height)) + view[1];
|
|
const xu = (viewWidth / width * (2 * x - width)) + view[0];
|
|
|
|
|
|
let x2, y2 = 0;
|
|
|
|
// Set up the first iteration of the fractal before looping through the rest.
|
|
// this may not be necesary, but I found that I needed to do this to get it to work right.
|
|
// There may be another way to do this though.
|
|
let x1 = (xu * xu) + xu - (yu * yu);
|
|
let y1 = (2 * xu * yu) + yu;
|
|
|
|
// Itterate
|
|
for (let i = 0; i < maxIterations; i++) {
|
|
|
|
// Update x and y following z = z^2 + c
|
|
x2 = (x1 * x1 + xu - y1 * y1);
|
|
y2 = (2 * x1 * y1 + yu);
|
|
|
|
// Set Inital values to be the ones calculated. If the mode is set to 1,
|
|
// render the burning ship fractal. (A close cousin to the Mandelbrot set)
|
|
if (fractalType == 1) {
|
|
x1 = x2;
|
|
y1 = y2;
|
|
} else {
|
|
x1 = Math.abs(x2);
|
|
y1 = -Math.abs(y2);
|
|
}
|
|
|
|
// Find the distance between the point on last iteration and origin
|
|
const distance = Math.sqrt(x2 * x2 + y2 * y2);
|
|
|
|
// If distance from origin is greater than 2, will always expand to
|
|
// infinity. In that case, end iteration.
|
|
if (distance > 2) {
|
|
|
|
// This section colors in the point based entirely on the i value.
|
|
// There is not a science to this, I just found some combinations
|
|
// of trig functions and other nonsense that looks acceptable.
|
|
|
|
// Coloring Mode 1
|
|
if (colorMode == 2) {
|
|
i = (i / maxIterations) * 255;
|
|
if (i < (2 / 3 * maxIterations)) {
|
|
i = i + (Math.pow((i - (2 / 3 * maxIterations)), 2) / (maxIterations * 0.8));
|
|
}
|
|
red = i * (Math.sin(i));
|
|
blue = i * (0 - Math.sin(i));
|
|
green = i * (Math.sin(i * 2));
|
|
}
|
|
|
|
// Coloring Mode 2
|
|
if (colorMode == 1) {
|
|
|
|
i = i / maxIterations;
|
|
red = i * 20 + Math.abs(Math.cos(2 * i) * 230);
|
|
blue = Math.sqrt(i * 10) + Math.abs(Math.sin(2 * i + 1 / 4) * 230);
|
|
green = Math.sqrt(i * 10) + Math.abs(Math.sin(10 * i) * 105);
|
|
}
|
|
// End looping after coloring.
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
// Store pixel data into imagedata
|
|
const pixelindex = (y * width + x) * 4;
|
|
imagedata.data[pixelindex + 2] = blue;
|
|
imagedata.data[pixelindex + 1] = green;
|
|
imagedata.data[pixelindex] = red;
|
|
imagedata.data[pixelindex + 3] = 255;
|
|
}
|
|
}
|
|
|
|
// Render the image to the canvas
|
|
|
|
// For some reason chrome did not like placing the imagedata directly onto the canvas.
|
|
// Offsetting it by one pixel fixed it somehow
|
|
context.putImageData(imagedata, 0, 1);
|
|
|
|
// Calculate time taken to render image
|
|
const renderTime = performance.now() - startTime;
|
|
|
|
// Add additional info as text at the top left
|
|
context.font = "14px serif";
|
|
context.strokeStyle = "white";
|
|
context.fillStyle = "white";
|
|
context.fillText("Re = " + view[0], 5, 19);
|
|
context.fillText("Im = " + view[1], 5, 2 * 19);
|
|
context.fillText("width = " + viewWidth, 5, 3 * 19);
|
|
context.fillText("Iterations:" + maxIterations, 5, 4 * 19);
|
|
|
|
if (renderTime > 1000) {
|
|
context.fillText("time = " + Math.round(renderTime / 100) / 10 + "s", 5, 5 * 19);
|
|
} else {
|
|
context.fillText("time = " + Math.round(renderTime) + "ms", 5, 5 * 19);
|
|
}
|
|
}
|