I ran into this question while working on perfect-arrows, a TypeScript library for drawing arrows between points, rectangles, and other shapes. While the question didn’t seem to obscure, I found it very hard to find an answer. The math itself is beyond me, but I was able to adapt some earlier work from another language.
我在处理Perfect-arrows (一个用于在点,矩形和其他形状之间绘制箭头)的TypeScript库时遇到了这个问题。 虽然这个问题似乎并没有模糊,但我发现很难找到答案。 数学本身超出了我的范围,但是我能够从另一种语言改编一些早期的作品 。
For the sake of paying it forward (and saving the next sucker—maybe you!—some headaches), here’s the code I came up with:
为了提前付款(并节省下一个傻瓜-也许您!-有点头疼),这是我想出的代码:
/**
* Find the point(s) where a line segment intersects an ellipse.
* @param x0 The x-axis coordinate of the line's start point.
* @param y0 The y-axis coordinate of the line's start point.
* @param x1 The x-axis coordinate of the line's end point.
* @param y1 The y-axis coordinate of the line's end point.
* @param cx The x-axis (horizontal) coordinate of the ellipse's center.
* @param cy The y-axis (vertical) coordinate of the ellipse's center.
* @param rx The ellipse's major-axis radius. Must be non-negative.
* @param ry The ellipse's minor-axis radius. Must be non-negative.
* @param rotation The rotation of the ellipse, expressed in radians.
* @param segment_only When true, will test the segment as a line (of infinite length).
*/
export function getEllipseSegmentIntersections(
x0: number,
y0: number,
x1: number,
y1: number,
cx: number,
cy: number,
rx: number,
ry: number,
rotation = 0,
segment_only = true
) {
// If the ellipse or line segment are empty, return no tValues.
if (rx === 0 || ry === 0 || (x0 === x1 && y0 === y1)) {
return []
}
// Get the semimajor and semiminor axes.
rx = rx < 0 ? rx : -rx
ry = ry < 0 ? ry : -ry
// Rotate points.
if (rotation !== 0) {
;[x0, y0] = rotatePoint(x0, y0, cx, cy, -rotation)
;[x1, y1] = rotatePoint(x1, y1, cx, cy, -rotation)
}
// Translate so the ellipse is centered at the origin.
x0 -= cx
y0 -= cy
x1 -= cx
y1 -= cy
// Calculate the quadratic parameters.
var A = ((x1 - x0) * (x1 - x0)) / rx / rx + ((y1 - y0) * (y1 - y0)) / ry / ry
var B = (2 * x0 * (x1 - x0)) / rx / rx + (2 * y0 * (y1 - y0)) / ry / ry
var C = (x0 * x0) / rx / rx + (y0 * y0) / ry / ry - 1
// Make a list of t values (normalized points on the line where intersections occur).
var tValues: number[] = []
// Calculate the discriminant.
var discriminant = B * B - 4 * A * C
if (discriminant === 0) {
// One real solution.
tValues.push(-B / 2 / A)
} else if (discriminant > 0) {
// Two real solutions.
tValues.push((-B + Math.sqrt(discriminant)) / 2 / A)
tValues.push((-B - Math.sqrt(discriminant)) / 2 / A)
}
return (
tValues
// Filter to only points that are on the segment.
.filter(t => !segment_only || (t >= 0 && t <= 1))
// Solve for points.
.map(t => [x0 + (x1 - x0) * t + cx, y0 + (y1 - y0) * t + cy])
// Counter-rotate points
.map(p =>
rotation === 0 ? p : rotatePoint(p[0], p[1], cx, cy, rotation)
)
)
}
/**
* Rotate a point around a center.
* @param x0 The x-axis coordinate of the point.
* @param y0 The y-axis coordinate of the point.
* @param cx The x-axis coordinate of rotation point.
* @param cy The y-axis coordinate of rotation point.
* @param rotation The rotation, expressed in radians.
*/
export function rotatePoint(
x: number,
y: number,
cx: number,
cy: number,
rotation: number
) {
const s = Math.sin(rotation)
const c = Math.cos(rotation)
const px = x - cx
const py = y - cy
const nx = px * c - py * s
const ny = px * s + py * c
return [nx + cx, ny + cy]
}
The important function there is getEllipseSegmentIntersections
. It’s written in TypeScript, but feel free to pull those types out if you’d like to use it in a JavaScript project.
其中的重要功能是getEllipseSegmentIntersections
。 它是用TypeScript编写的,但是如果您想在JavaScript项目中使用这些类型,可以随时将其提取出来。
Depending on your project, you may need to sort the returned points in clockwise or counterclockwise order, relative to a center. You can use the points’ cross product for this, and I’ve included a test (pointsAreClockwise
) as somewhere to start with that.
根据您的项目,您可能需要按相对于中心的顺时针或逆时针顺序对返回的点进行排序。 您可以为此使用点的叉积,并且我已经在其中开始了一个测试( pointsAreClockwise
)。
Good luck!
祝好运!