Feature: Sfondo animato con pattern di circuito stampato
parent
97266879e0
commit
79c7cd9642
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
@ -0,0 +1,344 @@
|
||||
|
||||
function pointInInterval(a, b, x) {
|
||||
return a <= x && x <= b;
|
||||
}
|
||||
|
||||
function intervalIntersectsInterval(a, b, c, d) {
|
||||
if (c <= a) {
|
||||
return a <= d;
|
||||
}
|
||||
|
||||
return pointInInterval(a, b, c);
|
||||
}
|
||||
|
||||
function rectIntersectRect(x1, y1, x2, y2, x3, y3, x4, y4) {
|
||||
return intervalIntersectsInterval(x1, x2, x3, x4) && intervalIntersectsInterval(y1, y2, y3, y4);
|
||||
}
|
||||
|
||||
function intersects(x1, y1, x2, y2, x3, y3, x4, y4) {
|
||||
const a_minx = Math.min(x1, x2);
|
||||
const a_maxx = Math.max(x1, x2);
|
||||
const a_miny = Math.min(y1, y2);
|
||||
const a_maxy = Math.max(y1, y2);
|
||||
|
||||
const b_minx = Math.min(x3, x4);
|
||||
const b_maxx = Math.max(x3, x4);
|
||||
const b_miny = Math.min(y3, y4);
|
||||
const b_maxy = Math.max(y3, y4);
|
||||
|
||||
if (!rectIntersectRect(a_minx, a_miny, a_maxx, a_maxy, b_minx, b_miny, b_maxx, b_maxy))
|
||||
return false;
|
||||
|
||||
const a_dx = x2 - x1;
|
||||
const a_dy = y2 - y1;
|
||||
|
||||
const b_dx = x4 - x3;
|
||||
const b_dy = y4 - y3;
|
||||
|
||||
const det = -b_dx * a_dy + a_dx * b_dy;
|
||||
|
||||
if (Math.abs(det) === 0 && -a_dy * (x3 - x1) + a_dx * (y3 - y1) === 0) {
|
||||
return y1 <= y3 <= y2 || y1 <= y4 <= y2;
|
||||
}
|
||||
|
||||
const s = (-a_dy * (x1 - x3) + a_dx * (y1 - y3)) / det;
|
||||
const t = (+b_dx * (y1 - y3) - b_dy * (x1 - x3)) / det;
|
||||
|
||||
return s >= 0 && s <= 1 && t >= 0 && t <= 1;
|
||||
}
|
||||
|
||||
const $canvas = document.querySelector('#circuit-pattern');
|
||||
let g = $canvas.getContext('2d');
|
||||
|
||||
let WIDTH = $canvas.offsetWidth;
|
||||
let HEIGHT = $canvas.offsetHeight;
|
||||
|
||||
const TS = 20;
|
||||
|
||||
let ROWS = HEIGHT / TS;
|
||||
let COLS = WIDTH / TS;
|
||||
|
||||
function updateCanvasDimensions() {
|
||||
g = $canvas.getContext('2d');
|
||||
|
||||
WIDTH = $canvas.offsetWidth;
|
||||
HEIGHT = $canvas.offsetHeight;
|
||||
|
||||
ROWS = HEIGHT / TS;
|
||||
COLS = WIDTH / TS;
|
||||
|
||||
$canvas.width = WIDTH * devicePixelRatio;
|
||||
$canvas.height = HEIGHT * devicePixelRatio;
|
||||
g.scale(2, 2);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', () => updateCanvasDimensions());
|
||||
updateCanvasDimensions();
|
||||
|
||||
const state = {
|
||||
wires: [], // :: [(Int, Int)]
|
||||
nextWire: null,
|
||||
nextWireIndex: 0,
|
||||
nextSegmentLen: 0,
|
||||
t: 0,
|
||||
};
|
||||
|
||||
function randomFloat(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
function randomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||
}
|
||||
|
||||
function randomChoice(list) {
|
||||
return list[randomInt(0, list.length - 1)];
|
||||
}
|
||||
|
||||
const DIRS = {
|
||||
left: [-1, 1],
|
||||
center: [0, 1],
|
||||
right: [+1, 1],
|
||||
};
|
||||
|
||||
const NEXT_DIRS = {
|
||||
left: ['center'],
|
||||
center: ['left', 'right'],
|
||||
right: ['center'],
|
||||
};
|
||||
|
||||
function canPlaceSegment([[p1x, p1y], [p2x, p2y]]) {
|
||||
return state.wires.every(wire => {
|
||||
return everyWireSegment(wire, ([[sx, sy], [lx, ly]]) => {
|
||||
return !intersects(sx, sy, lx, ly, p1x, p1y, p2x, p2y);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function everyWireSegment(wire, segmentFn) {
|
||||
let [start, ...rest] = wire;
|
||||
|
||||
return rest.every((pt) => {
|
||||
const r = segmentFn([start, pt]);
|
||||
start = pt;
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
function generateWire() {
|
||||
const pieceCount = Math.pow(randomFloat(1, 2.5), 2) | 0;
|
||||
const startPoint = [
|
||||
randomInt(0, COLS),
|
||||
Math.pow(randomFloat(0, ROWS / 12), 2) | 0,
|
||||
];
|
||||
const sizes = Array(pieceCount).fill(0).map(() => randomInt(2, 4) | 0);
|
||||
|
||||
const dirs = [randomChoice(Object.keys(DIRS))];
|
||||
for (let i = 0; i < pieceCount - 1; i++) {
|
||||
const last = dirs[dirs.length - 1];
|
||||
dirs.push(randomChoice(NEXT_DIRS[last]));
|
||||
}
|
||||
|
||||
const wire = [startPoint];
|
||||
for (let i = 0; i < pieceCount; i++) {
|
||||
const [lpx, lpy] = wire[wire.length - 1];
|
||||
const [dx, dy] = DIRS[dirs[i]];
|
||||
const s = sizes[i];
|
||||
|
||||
const s_bonus = (dirs[i] === 'center' ? s * 2 : s) | 0;
|
||||
|
||||
wire.push([lpx + dx * s_bonus, lpy + dy * s_bonus]);
|
||||
}
|
||||
|
||||
const ok = everyWireSegment(wire, segment => {
|
||||
return canPlaceSegment(segment);
|
||||
});
|
||||
|
||||
if (!ok)
|
||||
return null;
|
||||
|
||||
return wire;
|
||||
}
|
||||
|
||||
// // LinearintERPolation
|
||||
function lerp(a, b, t) {
|
||||
return t * (b - a) + a;
|
||||
}
|
||||
function lerpPoint([x1, y1], [x2, y2], t) {
|
||||
return [lerp(x1, x2, t), lerp(y1, y2, t)];
|
||||
}
|
||||
|
||||
const CELLS_PER_SECOND = 15;
|
||||
|
||||
let lastTime = new Date().getTime();
|
||||
|
||||
function update() {
|
||||
const now = new Date().getTime();
|
||||
const delta = (now - lastTime) / 1000;
|
||||
lastTime = now;
|
||||
|
||||
if (state.nextWire) {
|
||||
state.t += CELLS_PER_SECOND * delta;
|
||||
|
||||
if (state.t >= 1.0) {
|
||||
const [[x1, y1], [x2, y2]] = state.nextWire.slice(state.nextWireIndex - 2, state.nextWireIndex);
|
||||
const dx = Math.abs(x2 - x1);
|
||||
const dy = Math.abs(y2 - y1);
|
||||
state.t = 0;
|
||||
state.nextSegmentLen = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
state.nextWireIndex++;
|
||||
}
|
||||
|
||||
if (state.nextWireIndex > state.nextWire.length) {
|
||||
state.wires.push(state.nextWire);
|
||||
state.nextWire = null;
|
||||
}
|
||||
} else {
|
||||
const wire = generateWire();
|
||||
if (wire) {
|
||||
state.nextWire = wire;
|
||||
state.nextWireIndex = 2;
|
||||
|
||||
const [[x1, y1], [x2, y2]] = state.nextWire.slice(state.nextWireIndex - 2, state.nextWireIndex);
|
||||
const dx = Math.abs(x2 - x1);
|
||||
const dy = Math.abs(y2 - y1);
|
||||
state.t = 0;
|
||||
state.nextSegmentLen = Math.sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getColors() {
|
||||
if (document.body.classList.contains('dark-mode')) {
|
||||
return {
|
||||
BACKGROUND: '#282828',
|
||||
// CIRCUIT: '#202020',
|
||||
CIRCUIT: '#38302e',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
BACKGROUND: '#eaeaea',
|
||||
CIRCUIT: '#d4d4d4',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
const { BACKGROUND, CIRCUIT } = getColors();
|
||||
|
||||
g.clearRect(0, 0, WIDTH, HEIGHT);
|
||||
|
||||
g.strokeStyle = CIRCUIT;
|
||||
g.lineWidth = 3;
|
||||
|
||||
state.wires.forEach(wire => {
|
||||
const [[sx, sy], ...rest] = wire;
|
||||
|
||||
function renderDot(x, y) {
|
||||
if (y === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
g.beginPath();
|
||||
g.ellipse(x * TS, y * TS, TS / 5, TS / 5, 0, 0, Math.PI * 2);
|
||||
g.stroke();
|
||||
}
|
||||
|
||||
renderDot(sx, sy);
|
||||
|
||||
g.beginPath();
|
||||
g.moveTo(sx * TS, sy * TS);
|
||||
rest.forEach(([x, y]) => {
|
||||
g.lineTo(x * TS, y * TS);
|
||||
});
|
||||
g.stroke();
|
||||
|
||||
const [lx, ly] = wire[wire.length - 1];
|
||||
|
||||
renderDot(lx, ly);
|
||||
|
||||
[wire[0], wire[wire.length - 1]].forEach(([x, y]) => {
|
||||
if (y === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
g.fillStyle = BACKGROUND;
|
||||
g.beginPath();
|
||||
g.ellipse(x * TS, y * TS, TS / 5 - 1, TS / 5 - 1, 0, 0, Math.PI * 2);
|
||||
g.fill();
|
||||
});
|
||||
});
|
||||
|
||||
if (state.nextWire) {
|
||||
const wirePart = state.nextWire.slice(0, state.nextWireIndex);
|
||||
const wire = [
|
||||
...wirePart.slice(0, wirePart.length - 1),
|
||||
lerpPoint(
|
||||
wirePart[wirePart.length - 2],
|
||||
wirePart[wirePart.length - 1],
|
||||
state.t
|
||||
)
|
||||
];
|
||||
const [[sx, sy], ...rest] = wire;
|
||||
|
||||
function renderDot(x, y) {
|
||||
if (y === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
g.beginPath();
|
||||
g.ellipse(x * TS, y * TS, TS / 5, TS / 5, 0, 0, Math.PI * 2);
|
||||
g.stroke();
|
||||
}
|
||||
|
||||
renderDot(sx, sy);
|
||||
|
||||
g.beginPath();
|
||||
g.moveTo(sx * TS, sy * TS);
|
||||
rest.forEach(([x, y]) => {
|
||||
g.lineTo(x * TS, y * TS);
|
||||
});
|
||||
g.stroke();
|
||||
|
||||
const [lx, ly] = wire[wire.length - 1];
|
||||
|
||||
renderDot(lx, ly);
|
||||
|
||||
[wire[0], wire[wire.length - 1]].forEach(([x, y]) => {
|
||||
if (y === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
g.fillStyle = BACKGROUND;
|
||||
g.beginPath();
|
||||
g.ellipse(x * TS, y * TS, TS / 5 - 1, TS / 5 - 1, 0, 0, Math.PI * 2);
|
||||
g.fill();
|
||||
});
|
||||
}
|
||||
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
|
||||
|
||||
// g.translate(0.5, 0.5);
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// $canvas.classList.remove('hide');
|
||||
|
||||
render();
|
||||
|
||||
let i = 20;
|
||||
while (i > 0) {
|
||||
const wire = generateWire();
|
||||
|
||||
if (wire) {
|
||||
state.wires.push(wire);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
update();
|
||||
}, 1000 / 30);
|
||||
})
|
Loading…
Reference in New Issue