Вычислить положение точки вдоль края правильного многоугольника?

Допустим, у меня есть пятиугольник (и мы нумеруем стороны, как движемся вокруг часов):

Как вычислить положение точки в этих точках, начиная с центра многоугольника (вдоль линии края многоугольника):

  1. В вершине между сторонами 2 и 3 (это максимальное расстояние от центра).
  2. В средней точке 4 (это минимальное расстояние от центра).
  3. В точке 2/3 стороны 3, двигаясь по часовой стрелке (случайно выбранное расстояние от центра).

Знание того, как вычислить координаты x/y относительно центра, будет означать, что я могу строить точки вдоль отрезков прямых линий произвольного многоугольника (скажем, от 3 до 20 сторон). Я очень долго обдумываю, как это сделать, не говоря уже о том, чтобы заставить это работать в коде. Не имеет значения, на каком языке он написан, но желательно на JavaScript/TypeScript или Python или C (или псевдокод, говорящий сам за себя).

Вот комбинация того, что я придумал. Компоновка многоугольников работает правильно, но позиционирование точек не работает. Как вы расположите эти 3 пункта?

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.info('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.info('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.info('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)

Я хотел бы иметь возможность решить эту проблему для любого многоугольника от 3 до 20 сторон, а не только для пятиугольника.

Простите, но я так и не понял, какие именно точки вы пытаетесь вычислить. У вас уже есть координаты вершин при создании пятиугольника. Таким образом, вы можете линейно интерполировать точки между всеми краями на основе начальной и конечной точки (вершины) при заданном значении t. См. код: codepen.io/herrstrietzel/pen/ZENLmGv?editors=0010

herrstrietzel 30.05.2024 18:59

Итак 1. В вершине между сторонами 2 и 3 (это максимальное расстояние от центра). на самом деле должно быть pentagonPoints[2] (3-я точка из полученного массива точек многоугольника). Может быть, вы пытаетесь найти точки на многоугольнике под определенными углами?

herrstrietzel 31.05.2024 01:36
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
2
101
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Проблема с вашими тригонометрическими вычислениями заключается в том, что описанная окружность слишком велика. Вы можете увидеть это, если добавите

<circle cx = "«centerX»" cy = "«centerY»" r = "«R»"/>

к вашей схеме:

Но вам не обязательно выполнять вычисления самостоятельно, вы можете позволить SVG сделать это за вас с помощью функции path.getPointAtLength(length). В ваших случаях

  1. вершина между сторонами 2 и 3 находится в точке length=2
  2. середина числа 4 находится в точке length=3.5
  3. точка 2/3 на стороне 3, двигаясь по часовой стрелке, находится в точке length=2.67.

var path = document.querySelector("polygon");
var circle = document.querySelector("circle");
var control = document.querySelector("input[type=range]");

function init(n) {
  control.setAttribute("max", n);
  var d = [];
  for (var i = 0; i < n; i++) {
    var alpha = 2 * i * Math.PI / n;
    d.push(`${Math.sin(alpha)},${-Math.cos(alpha)}`);
  }
  path.setAttribute("points", d.join(" "));
  control.value = 0;
  move();
}

function move() {
  var p = path.getPointAtLength(path.getTotalLength() * control.value / control.getAttribute("max"));
  circle.setAttribute("cx", p.x);
  circle.setAttribute("cy", p.y);
  control.nextElementSibling.textContent = control.value;
}

init(5);
polygon {
  fill: yellow;
  stroke: black;
  stroke-width: 0.01;
}

circle {
  fill: red;
}
<input type = "number" value = "5" onchange = "init(this.value)" />
<svg viewBox = "-1 -1 2 2">
    <polygon/>
    <circle r = "0.1"/>
</svg>
<input type = "range" step = "0.01" onchange = "move()" /><span></span>

Я надеялся получить более абстрактные вычисления, так как я не хочу располагать их точно на краю многоугольника, а смещать их от края на определенный зазор. Думаю, я мог бы просто нарисовать еще один скрытый многоугольник... Но было бы неплохо знать, как это сделать, не полагаясь на помощники SVG. Мой код также всегда помещает плоский край внизу.

Lance 01.06.2024 03:32
Ответ принят как подходящий

Вот ответ, я наконец понял это.

export function calculatePolygonDotPosition({
  polygonRadius,
  polygonSideCount,
  polygonEdgeNumber,
  polygonEdgePositionRatio, // from 0 to 1
  gap = 0,
  dotRadius,
  rotation = 0,
  offset = 0,
}: {
  polygonRadius: number
  polygonSideCount: number
  polygonEdgeNumber: number
  polygonEdgePositionRatio: number
  gap?: number
  dotRadius: number
  rotation?: number
  offset?: number
}) {
  const n = polygonSideCount
  const R = polygonRadius
  const e = polygonEdgeNumber
  const t = polygonEdgePositionRatio
  const o = gap

  const rotationAngle = (rotation * Math.PI) / 180

  const V1 = rotatePoint(getPolygonVertex(e - 1, n, R), rotationAngle)
  const V2 = rotatePoint(getPolygonVertex(e % n, n, R), rotationAngle) // Wrap around using modulo

  // Interpolate position along the edge
  const P = {
    x: (1 - t) * V1.x + t * V2.x,
    y: (1 - t) * V1.y + t * V2.y,
  }

  // Calculate the edge vector and the normal vector
  const dx = V2.x - V1.x
  const dy = V2.y - V1.y
  const edgeLength = Math.sqrt(dx * dx + dy * dy)

  // Unit normal vector (rotate by 90 degrees counter-clockwise)
  const normal = {
    x: -dy / edgeLength,
    y: dx / edgeLength,
  }

  // Offset the point by the gap distance
  const P_offset = {
    x: P.x + (-o - dotRadius + offset) * normal.x,
    y: P.y + (-o - dotRadius + offset) * normal.y,
  }

  return {
    x: P_offset.x + R,
    y: -P_offset.y + R,
  }
}

// Calculate vertex positions
export function getPolygonVertex(i: number, n: number, R: number) {
  const angle = (2 * Math.PI * i) / n + Math.PI / 2
  return { x: R * Math.cos(angle), y: R * Math.sin(angle) }
}

function rotatePoint(
  { x, y }: { x: number; y: number },
  angle: number,
) {
  const cos = Math.cos(angle)
  const sin = Math.sin(angle)
  return {
    x: x * cos - y * sin,
    y: x * sin + y * cos,
  }
}

Использование:

calculatePolygonDotPosition({
  polygonRadius: 300,
  polygonSideCount: 5,
  polygonEdgeNumber: 2,
  polygonEdgePositionRatio: 0.5,
  gap: 4,
  dotRadius: 3,
  offset: 4, // strokeWidth
})

Вот 4 точки, расположенные сразу за внешним краем.

Другие вопросы по теме