onibrowser
Version:
A browser in the browser
206 lines (187 loc) • 5.42 kB
HTML
<meta charset=utf-8>
<meta name=viewport content=width=device-width,initial-scale=1,user-scalable=no>
<title>
BrowserGap — Point field demo
</title>
<style>
:root, body {
height: 200vh;
width: 100%;
margin: 0;
}
canvas {
position: fixed;
top: 0;
width: 100%;
height: 100vh;
user-select: none;
background: linear-gradient(to right top, navy, dodgerblue);
}
aside {
position: relative;
z-index: 2;
padding: 2em;
color: white;
font-size: larger;
font-family: Verdana, Helvetica, Arial, system-ui, sans;
pointer-events: none;
}
</style>
<canvas>
<script>
const frameReady = () => new Promise(res => window.requestAnimationFrame(res))
const sleep = ms => new Promise(res => setTimeout(res, ms));
const ALTERP = 0.618;
const DTHRESHOLD = 0.618;
const FRAMEDURATION = 40;
const F = Math.floor(1000/FRAMEDURATION);
const UPDATEP = 2;
const XAVAL = 7;
const YAVAL = 3;
const SIZE = [3,3];
const N = 71;
const NN = 1;
const MAXNN = Math.ceil(Math.sqrt(N));
const MAXD = Math.min(window.innerWidth**2, window.innerHeight**2)/9;
const C = document.currentScript.parentElement;
const S = C.getContext('2d');
const width = window.innerWidth;
const height = window.innerHeight;
let frameCount = 0;
let nnds = +1;
let nn = Math.round(Math.sqrt(MAXNN * NN));
let allPairsCache;
C.width = width;
C.height = height;
S.fillStyle = 'white';
S.strokeStyle = 'white';
C.addEventListener('click', () => {
nn += nnds;
if ( nn > MAXNN ) {
nnds *= -1;
}
if ( nn < NN ) {
nnds *= -1;
}
});
drawLoop();
async function drawLoop() {
const Points = createPoints(N);
while(true) {
await sleep(FRAMEDURATION);
await frameReady();
S.clearRect(0,0,width,height);
update(Points);
draw(Points);
drawTrianglesOnClosest(Points, frameCount);
frameCount++;
}
}
function update(P) {
P.forEach(updateA);
}
function draw(P) {
P.forEach(({x,y}) => {
//S.fillRect(x,y,...SIZE)
S.beginPath();
S.arc(x,y,SIZE[0],0,Math.PI*2);
S.closePath();
S.fill();
});
}
function drawTrianglesOnClosest(Points, frameNo) {
const found = new Map();
if ( ! allPairsCache ) {
allPairsCache = allPairs(Points)
//.map(distance);
}
allPairsCache
.map(frameNo % UPDATEP == 0 ? ranDistance: X => X)
//.map(ranDistance)
.sort(([{}, {}, d1], [{}, {}, d2]) => d1 - d2)
.filter(([P1,P2,d]) => {
const ok = (!found.has(P1) || found.get(P1) <= nn) && (!found.has(P2) || found.get(P2) <= nn);
if ( ok ) {
found.set(P1, (found.get(P1) || 0)+1);
found.set(P2, (found.get(P2) || 0)+1);
}
return ok && d < MAXD;
})
.forEach(([P1,P2]) => {
S.beginPath();
S.moveTo(P1.x, P1.y);
S.lineTo(P2.x, P2.y);
S.stroke();
S.closePath();
});
/**
.reduce((T, p, i, P) => {
const last = T[T.length-1];
if ( last.length == 3 ) {
T.push([p]);
} else {
last.push(p);
}
}, []);
**/
}
function updateA(A) {
const randomX = (Math.random() * XAVAL) - XAVAL/2;
const randomY = (Math.random() * YAVAL) - YAVAL/2;
A.x += randomX;
A.y += randomY;
return A;
}
function createPoints(n) {
const P = [];
for( let i = 0; i < n; i++ ) {
const Point = {
x: random(width),
y: random(height),
V: {
x: random(width/20),
y: random(height/20)
},
A: updateA({x:XAVAL,y:YAVAL})
};
P.unshift(Point);
}
return P;
}
function random(max) {
return Math.random() * max;
}
function allPairs(A) {
const P = [];
A.forEach((a,i) => {
for( let j = i + 1; j < A.length; j++ ) {
const [{x:x1,y:y1},{x:X1,y:Y1}] = [a,A[j]];
P.push([a,A[j],0]);
}
});
return P;
}
function distance(P) {
const [{x:x1,y:y1},{x:X1,y:Y1}] = P;
const d = ((X1-x1)**2+(Y1-y1)**2);
P[2] = d;
return P;
}
function ranDistance(P) {
const [{x:x1,y:y1},{x:X1,y:Y1}] = P;
const d = ((X1-x1)**2+(Y1-y1)**2);
if ( Math.random() < ALTERP ) {
if ( Math.abs(P[2] - d)/d > DTHRESHOLD ) {
P[2] = d;
}
}
return P;
}
</script>
</canvas>
<aside>
<h1>Point field test</h1>
<p>Could also add colors to each node / point, and sum colors with neighbour to determine line color.</p>
<p>Or even make a linear gradient with direction equal to line direction to move between node colors.</p>
</aside>