Imagine you have a pentagon, with numbered sides like moving around a clock:
https://i.sstatic.net/pWqdFtfg.png
Starting from the center of the polygon, how can you calculate the position of a point in these locations (along the edge of the polygon):
- At the vertex between sides 2 and 3 (this is the maximum distance from the center).
- At the midpoint of side 4 (this is the minimum distance from the center).
- At a point 2/3 across side 3, moving clockwise (randomly chosen distance from the center).
Understanding how to calculate the x/y coordinates relative to the center will enable me to plot points along the straight line segments of any polygon (ranging from 3 to 20 sides). I am struggling to conceptualize this, let alone make it work in code. It doesn't matter what programming language is used, but preferably JavaScript/TypeScript, or else Python or C (or self-explanatory pseudocode).
Here is a combination of my attempts so far. The polygon layout is correct, but the point positioning is not working. How would you position these 3 points?
const ANGLE = -Math.PI / 2 // Start the first vertex at the top center
function computePolygonPoints({
width,
height,
sides,
strokeWidth = 0,
rotation = 0,
}) {
const centerX = width / 2 + strokeWidth / 2
const centerY = height / 2 + strokeWidth / 2
const radiusX = width / 2 - strokeWidth / 2
const radiusY = height / 2 - strokeWidth / 2
const offsetX = strokeWidth / 2
const offsetY = strokeWidth / 2
const rotationRad = (rotation * Math.PI) / 180
const points = Array.from({ length: sides }, (_, i) => {
const angle = (i * 2 * Math.PI) / sides + ANGLE
const x = centerX + radiusX * Math.cos(angle)
const y = centerY + radiusY * Math.sin(angle)
// Apply rotation around the center
const rotatedX =
centerX +
(x - centerX) * Math.cos(rotationRad) -
(y - centerY) * Math.sin(rotationRad)
const rotatedY =
centerY +
(x - centerX) * Math.sin(rotationRad) +
(y - centerY) * Math.cos(rotationRad)
return { x: rotatedX, y: rotatedY }
})
const minX = Math.min(...points.map(p => p.x))
const minY = Math.min(...points.map(p => p.y))
const adjustedPoints = points.map(p => ({
x: offsetX + p.x - minX,
y: offsetY + p.y - minY,
}))
return adjustedPoints
}
function vertexCoordinates(n, R, vertexIndex) {
const angle = 2 * Math.PI * vertexIndex / n - Math.PI / 2; // Adjusting to start from the top
return {
x: R * Math.cos(angle),
y: R * Math.sin(angle),
}
}
function midpointCoordinates(x1, y1, x2) {
return {
x: (x1 + x2.x) / 2,
y: (y1 + x2.y) / 2,
}
}
function fractionalPoint(x1, y1, x2, fraction) {
return {
x: x1 + fraction * (x2.x - x1),
y: y1 + fraction * (x2.y - y1),
}
}
const pentagonPoints = computePolygonPoints({ width: 300, height: 300, sides: 5 })
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.setAttribute("width", 300)
svg.setAttribute("height", 300);
const pentagon = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
pentagon.setAttribute('fill', 'cyan')
pentagon.setAttribute('points', pentagonPoints
.map((p) => `${p.x},${p.y}`)
.join(" "))
svg.appendChild(pentagon)
document.body.appendChild(svg);
const n = 5 // Number of sides for a pentagon
const width = 300; // Width of the pentagon
const R = width / (2 * Math.cos(Math.PI / n)); // Radius of the circumscribed circle
const centerX = 150; // Center of the canvas
const centerY = 150;
// Vertex between sides 2 and 3
const vertex23 = vertexCoordinates(n, R, 2)
const vertex23Adjusted = {
x: centerX + vertex23.x, // subtract radius too?
y: centerY + vertex23.y
};
console.log('Vertex between sides 2 and 3:', vertex23Adjusted)
const circle23 = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle23.setAttribute('fill', 'magenta')
circle23.setAttribute('r', 16)
circle23.setAttribute('cx', vertex23Adjusted.x)
circle23.setAttribute('cy', vertex23Adjusted.y)
svg.appendChild(circle23)
// Midpoint of side 4
const vertex4_1 = vertexCoordinates(n, R, 3)
const vertex4_2 = vertexCoordinates(n, R, 4)
const mid4 = midpointCoordinates(vertex4_1.x, vertex4_1.y, vertex4_2)
console.log('Midpoint of side 4:', mid4)
const mid4Circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
mid4Circle.setAttribute('fill', 'magenta')
mid4Circle.setAttribute('r', 16)
mid4Circle.setAttribute('cx', mid4.x)
mid4Circle.setAttribute('cy', mid4.y)
svg.appendChild(mid4Circle)
// Point 2/3 across side 3, moving clockwise
const vertex3_1 = vertexCoordinates(n, R, 2)
const vertex3_2 = vertexCoordinates(n, R, 3)
const frac3 = fractionalPoint(
vertex3_1.x,
vertex3_1.y,
vertex3_2,
2 / 3,
)
console.log('Point 2/3 across side 3:', frac3)
const frac3Circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
frac3Circle.setAttribute('fill', 'magenta')
frac3Circle.setAttribute('r', 16)
frac3Circle.setAttribute('cx', frac3.x)
frac3Circle.setAttribute('cy', frac3.y)
svg.appendChild(frac3Circle)
I would like to be able to solve this for any polygon from 3 to 20 sides, not just for the pentagon.