<canvas></canvas>
body {
margin: 0;
overflow: hidden;
}
canvas {
image-rendering: pixelated;
}
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
const entities = [];
const mouse = { x: null, y: null };
const start = { x: null, y: null };
const createShape = points => {
const path = new Path2D();
points.forEach((point, index) => {
if (index === 0) {
path.moveTo(point.x, point.y);
} else {
path.lineTo(point.x, point.y);
}
});
return path;
};
const createLines = points => {
const lines = [];
for (let index = 0; index < points.length; index++) {
const start = points[index];
const end = index === points.length - 1 ? points[0] : points[index + 1];
lines.push({
x1: start.x,
y1: start.y,
x2: end.x,
y2: end.y
});
}
return lines;
};
const createRect = (x, y, width, height) => {
const points = [
{ x: x, y: y },
{ x: x + width, y: y },
{ x: x + width, y: y + height },
{ x: x, y: y + height }
];
entities.push({
lines: createLines(points),
shape: createShape(points)
});
};
const createCircle = (x, y, radius) => {
const points = [];
for (let index = 0; index < 36; index++) {
const angle = (Math.PI * 2 / 36) * index;
points.push({
x: Math.cos(angle) * radius + x,
y: Math.sin(angle) * radius + y
});
}
entities.push({
lines: createLines(points),
shape: createShape(points)
});
};
const createTriangle = (x, y, radius, rotate = 0) => {
const points = [];
for (let index = 0; index < 3; index++) {
const angle = (Math.PI * 2 / 3) * index + rotate;
points.push({
x: Math.cos(angle) * radius + x,
y: Math.sin(angle) * radius + y
});
}
entities.push({
lines: createLines(points),
shape: createShape(points)
});
};
const createStar = (x, y, vertices, innerRadius, outerRadius, rotate = 0) => {
const points = [];
const count = vertices * 2;
for (let index = 0; index < count; index++) {
const angle = (Math.PI * 2 / count) * index + rotate;
const radius = (index % 2) === 0 ? innerRadius : outerRadius;
points.push({
x: Math.cos(angle) * radius + x,
y: Math.sin(angle) * radius + y
});
}
entities.push({
lines: createLines(points),
shape: createShape(points)
});
};
const cast = (line, angle) => {
const { x1, y1, x2, y2 } = line;
const x3 = start.x;
const y3 = start.y;
const x4 = start.x + Math.cos(angle);
const y4 = start.y + Math.sin(angle);
const denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (denominator === 0) {
return null;
}
const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denominator;
const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denominator;
if (t > 0 && t < 1 && u > 0) {
const point = {
x: x1 + t * (x2 - x1),
y: y1 + t * (y2 - y1)
};
return point;
} else {
return null;
}
};
const update = (time) => {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = '#000000';
for (let entity of entities) {
context.fill(entity.shape);
}
context.beginPath();
context.arc(start.x, start.y, 3, 0, Math.PI * 2);
context.closePath();
context.fillStyle = '#ff6600';
context.fill();
if (mouse.x !== null && mouse.y !== null) {
const angle = Math.atan2(mouse.y - start.y, mouse.x - start.x);
context.beginPath();
context.moveTo(start.x, start.y);
context.lineTo(start.x + Math.cos(angle) * 100, start.y + Math.sin(angle) * 100);
context.closePath();
context.strokeStyle = '#ff6600';
context.stroke();
const intersections = [];
for (let entity of entities) {
for (let line of entity.lines) {
const intesection = cast(line, angle);
if (intesection !== null) {
intersections.push(intesection);
}
}
}
const store = intersections.map(intersection => ({
intersection,
distance: Math.hypot(start.x - intersection.x, start.y - intersection.y)
})).sort((a, b) => (a.distance - b.distance));
if (store.length !== 0) {
context.beginPath();
context.arc(store[0].intersection.x, store[0].intersection.y, 5, 0, Math.PI * 2);
context.closePath();
context.fillStyle = '#ff0000';
context.fill();
}
}
requestAnimationFrame(update);
};
const resize = () => {
[canvas.width, canvas.height] = [innerWidth, innerHeight];
};
const init = () => {
resize();
[start.x, start.y] = [canvas.width / 2, canvas.height / 2];
canvas.addEventListener('click', event => {
[start.x, start.y] = [event.layerX, event.layerY];
});
createRect(100, 100, 200, 200);
createRect(100, 400, 50, 200);
createRect(200, 500, 50, 50);
createCircle(500, 300, 50);
createTriangle(400, 500, 40);
createTriangle(450, 450, 40, Math.PI / -2);
createStar(600, 100, 5, 25, 50);
createStar(450, 150, 10, 10, 75);
requestAnimationFrame(update);
};
window.addEventListener('load', init);
window.addEventListener('resize', resize);
window.addEventListener('mousemove', event => {
[mouse.x, mouse.y] = [event.clientX, event.clientY];
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.