You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
7.3 KiB
JavaScript
344 lines
7.3 KiB
JavaScript
3 years ago
|
|
||
|
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);
|
||
|
})
|