Add julia viewer
This commit is contained in:
parent
d553553d1a
commit
079cf65682
@ -13,7 +13,7 @@ body {
|
|||||||
.profile-picture {
|
.profile-picture {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
max-width: 600px;
|
max-width: 40vh;
|
||||||
box-shadow:0 10px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
|
box-shadow:0 10px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
index.html
11
index.html
@ -58,6 +58,17 @@
|
|||||||
<p>In this game, shoot all the centipede bodys and score points and go up levels.</p>
|
<p>In this game, shoot all the centipede bodys and score points and go up levels.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="project" onclick="window.location='julia/index.html'">
|
||||||
|
<div class="project-logo-container">
|
||||||
|
<i class="fa-solid fa-square-root-variable"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project-body">
|
||||||
|
<h1>Julia Set Explorer</h1>
|
||||||
|
<p>Zoom, pan, and "c" complex changes (terrible pun on see) in this fun GPU-accelerated playground!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
19677
julia/gpu-browser.js
Normal file
19677
julia/gpu-browser.js
Normal file
File diff suppressed because it is too large
Load Diff
51
julia/index.html
Normal file
51
julia/index.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.bottomRight {
|
||||||
|
padding: 12px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: rgba(255,255,255,0.5);
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-right: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.verticalSlider
|
||||||
|
{
|
||||||
|
writing-mode: bt-lr; /* IE */
|
||||||
|
-webkit-appearance: slider-vertical; /* Chromium */
|
||||||
|
width: 8px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="canvasHolder">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="bottomRight">
|
||||||
|
<input orient="vertical" type="range" min="-1000" max="1000" value="0" id="imaginarySlider" class="verticalSlider">
|
||||||
|
<input type="range" min="-1000" max="1000" value="0" id="realSlider">
|
||||||
|
<br>
|
||||||
|
imaginary: <span id="imag-val"></span>
|
||||||
|
<button id="animate-imag" onclick="startImagAnim()">Animate</button>
|
||||||
|
<br>
|
||||||
|
real: <span id="real-val"></span>
|
||||||
|
<button id="animate-real" onclick="startRealAnim()">Animate</button>
|
||||||
|
<br>
|
||||||
|
powered by <a href="https://github.com/gpujs/gpu.js">gpu.js</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="gpu-browser.js"></script>
|
||||||
|
<script src="julia.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
153
julia/julia.js
Normal file
153
julia/julia.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
const MIN_ZOOM = 0.0001;
|
||||||
|
const MAX_ZOOM = 4;
|
||||||
|
const SLIDER_DIV = 1000;
|
||||||
|
|
||||||
|
const gpu = new GPU();
|
||||||
|
const buildRender = (width, height) => gpu.createKernel(function (maxIterations, cr, ci, centerX, centerY, zoom, colorMultipliers) {
|
||||||
|
let zx = (this.output.x / this.output.y) * (centerX + (4 * this.thread.x / this.output.x - 2) * (zoom / 4));
|
||||||
|
let zy = centerY + (4 * this.thread.y / this.output.y - 2) * (zoom / 4);
|
||||||
|
let iterations = 0;
|
||||||
|
for (let i = 0; i < maxIterations; i++) {
|
||||||
|
const xtemp = zx * zx - zy * zy + cr;
|
||||||
|
zy = 2 * zx * zy + ci;
|
||||||
|
zx = xtemp;
|
||||||
|
|
||||||
|
// filled Julia set for Q_c exists only when z has radius less than 2 and less than |c|
|
||||||
|
// and we are only allowing |c| < 2
|
||||||
|
if (zx * zx + zy * zy > 4) {
|
||||||
|
iterations = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (iterations == 0 || iterations == maxIterations) {
|
||||||
|
this.color(0, 0, 0);
|
||||||
|
} else {
|
||||||
|
this.color(colorMultipliers[0] * iterations, colorMultipliers[1] * iterations, colorMultipliers[2] * iterations);
|
||||||
|
}
|
||||||
|
}, { output: [width, height], graphical: true });
|
||||||
|
|
||||||
|
const canvasHolder = document.getElementById('canvasHolder');
|
||||||
|
let render; // The GPU kernel built from buildRender
|
||||||
|
let state = {
|
||||||
|
colorMultipliers: [0.01 * Math.random(), 0.03 * Math.random(), 0.02 * Math.random()],
|
||||||
|
changes: {
|
||||||
|
centerX: 0,
|
||||||
|
centerY: 0,
|
||||||
|
zoom: 3,
|
||||||
|
cr: parseFloat(document.getElementById("realSlider").value) / SLIDER_DIV,
|
||||||
|
ci: parseFloat(document.getElementById("imaginarySlider").value) / SLIDER_DIV,
|
||||||
|
maxIterations: 1000,
|
||||||
|
width: document.body.clientWidth,
|
||||||
|
height: document.body.clientHeight,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const doRender = (renderF, state) => {
|
||||||
|
renderF(state.maxIterations, state.cr, state.ci, state.centerX, state.centerY, state.zoom, state.colorMultipliers);
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachCanvasWithDimension = () => {
|
||||||
|
render = buildRender(state.width, state.height);
|
||||||
|
doRender(render, state);
|
||||||
|
canvasHolder.appendChild(render.canvas);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loop = () => {
|
||||||
|
const stateChanges = Object.keys(state.changes);
|
||||||
|
if (stateChanges.length > 0) {
|
||||||
|
state = {...state, ...state.changes};
|
||||||
|
if (state.changes.width || state.changes.height) {
|
||||||
|
attachCanvasWithDimension();
|
||||||
|
}
|
||||||
|
if (state.changes.ci !== undefined) {
|
||||||
|
document.getElementById("imag-val").innerHTML = state.ci.toFixed(4);
|
||||||
|
}
|
||||||
|
if (state.changes.cr !== undefined) {
|
||||||
|
document.getElementById("real-val").innerHTML = state.cr.toFixed(4);
|
||||||
|
}
|
||||||
|
state.changes = {};
|
||||||
|
doRender(render, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(loop);
|
||||||
|
};
|
||||||
|
|
||||||
|
loop();
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: UI code is trash lol
|
||||||
|
const startImagAnim = () => {
|
||||||
|
const start = setInterval(() => {
|
||||||
|
if (state.ci >= 1) {
|
||||||
|
clearInterval(start);
|
||||||
|
document.getElementById("animate-imag").innerHTML = "Animate";
|
||||||
|
document.getElementById("animate-imag").onclick = startImagAnim;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.changes.ci = state.ci + 0.001;
|
||||||
|
document.getElementById("imaginarySlider").value = state.changes.ci * 1000;
|
||||||
|
}, 1000/60);
|
||||||
|
document.getElementById("animate-imag").innerHTML = "Stop";
|
||||||
|
document.getElementById("animate-imag").onclick = () => {
|
||||||
|
clearInterval(start);
|
||||||
|
document.getElementById("animate-imag").innerHTML = "Animate";
|
||||||
|
document.getElementById("animate-imag").onclick = startImagAnim;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const startRealAnim = () => {
|
||||||
|
const start = setInterval(() => {
|
||||||
|
if (state.cr >= 1) {
|
||||||
|
clearInterval(start);
|
||||||
|
document.getElementById("animate-real").innerHTML = "Animate";
|
||||||
|
document.getElementById("animate-real").onclick = startRealAnim;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.changes.cr = state.cr + 0.001;
|
||||||
|
document.getElementById("realSlider").value = state.changes.cr * 1000;
|
||||||
|
}, 1000/60);
|
||||||
|
document.getElementById("animate-real").innerHTML = "Stop";
|
||||||
|
document.getElementById("animate-real").onclick = () => {
|
||||||
|
clearInterval(start);
|
||||||
|
document.getElementById("animate-real").innerHTML = "Animate";
|
||||||
|
document.getElementById("animate-real").onclick = startRealAnim;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("realSlider").oninput = function() {
|
||||||
|
state.changes.cr = parseFloat(this.value) / SLIDER_DIV;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById("imaginarySlider").oninput = function() {
|
||||||
|
state.changes.ci = parseFloat(this.value) / SLIDER_DIV;
|
||||||
|
};
|
||||||
|
|
||||||
|
canvasHolder.onwheel = (event) => {
|
||||||
|
state.changes.zoom = Math.min(Math.max(state.zoom + event.deltaY * 0.001 * state.zoom, MIN_ZOOM), MAX_ZOOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
let isDown = false;
|
||||||
|
canvasHolder.addEventListener('mousedown', function(e) {
|
||||||
|
isDown = true;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
canvasHolder.addEventListener('mouseup', function() {
|
||||||
|
isDown = false;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
canvasHolder.addEventListener('mousemove', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (isDown) {
|
||||||
|
let deltaX = -event.movementX * state.zoom / 700;
|
||||||
|
let deltaY = event.movementY * state.zoom / 700;
|
||||||
|
state.changes.centerX = state.centerX + deltaX;
|
||||||
|
state.changes.centerY = state.centerY + deltaY;
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
state.changes.width = document.body.clientWidth;
|
||||||
|
state.changes.height = document.body.clientHeight;
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user