从圆内任何点和角度到圆周的距离

Distance to circumference of circle from any point and angle within the circle

提问人:TheFurryDevil 提问时间:4/3/2023 最后编辑:StefTheFurryDevil 更新时间:4/4/2023 访问量:108

问:

从技术上讲,这是一个数学问题,但我不明白如何将其转换为代码。

Illustration

我有一个圆的中心 C,它的半径 r,以及圆内的点 A 和 B。我需要找到距离 AD,所以 B 仅用于角度。因此,这意味着用 3 个点计算的角度和边来求解三角形 ACD。 换句话说,我想用一个角度找到从圆内任何一点到圆周的距离。

我尝试过的伪代码:

float dis(float a_x, float a_y, float b_x, float b_y, float c_x, float c_y, float r) {
  float ab_x = a_x - b_x;
  float ab_y = a_y - b_y;

  float ac_x = a_x - c_x;
  float ac_y = a_y - c_y;

  float ab_a = atan(ab_y, ab_x);
  float ac_a = atan(ac_y, ac_x);
  float dc_a = pi - ac_a - ab_a;

  return (sin(dc_a) * r) / sin(ab_a);
}

这产生了错误的结果,可能是因为不止一个错误。 如何正确地做到这一点?

我在数学交流中读到它,最终了解到它是一个简单的三角形,但是在编程上下文中,我找不到回答我的问题。

与数学 语言无关 的三角学

评论


答:

1赞 Stef 4/3/2023 #1

使用Al-Kashi关于三角形ACD的定理,我们得到:

CD² = AC² + AD² - 2 AC AD cos(CAB)

我们知道 , , 因为 C 是中心,而 A 和 D 在圆上。CD = rAC = r

我们可以使用点积的欧几里得公式进行计算,也称为“余弦相似度”:cos(CAB)

cos(CAB) = <AB, AC> / (AB AC)    /* where <AB,AC> is dot-product */
                                 /* and (AB AC) is product of norms */ 

调用我们正在寻找的距离,并调用我们计算出的余弦,方程变为:z = ADγ = cos(CAB)

r² = r² + z² - 2rγz

r² 抵消,由于 z 为非零,我们可以将其简化为:

z = 2rγ

这可以成为代码:

def distance_ad(ax,ay, bx,by, cx,cy, r):
    γ = ((bx-ax)*(cx-ax) + (by-ay)*(cy-ay)) / (r * sqrt((bx-ax)² + (by-ay)²))
    return 2 * r * γ

或者,如果您的库中已经有一个函数:cosine_similarity

def distance_ad(ax,ay, bx,by, cz,cy, r):
   γ = cosine_similarity(bx-ax,by-ay, cx-ax,cy-ay)
   return 2 * r * γ
0赞 trincot 4/3/2023 #2

您可以按如下方式进行此操作:

首先,我们可以平移(“移动”)给定的点,使 C 具有坐标 (0, 0)。因此,我们从 A 和 B 中减去 C。这不会影响解决方案。所以现在我们只有 A 和 B 作为输入。

其次,我们可以缩放问题,使圆的半径为 1。这意味着我们将 A 和 B 除以电流半径(除以 |A|)。

通过 A 和 B 的行可以写成 A + n(B−A),其中 n 是可变的。我们调用 W=B−A,并对 W 进行归一化,使其大小为 1(这里我们必须假设 A 和 B 是不同的点)。然后,我们必须满足以下方程才能确定 m 的值:

D = A + mW(D 位于穿过 A 和 B 的线路上),并且
|D|= 1 (D 位于单位圆上)

为了简化计算,我们可以将第二个等式替换为:

|D|² = 1

代入 D,我们得到:

|A + mW|² = 1 (A x + mW x)² + (A y + mW y)² = 1 A x² + 2mA x W x + m² W x² + A y² + 2mA y Wy + m²W y² = 1 A x² + A y² + 2m(AxW x +

A y W y) + m²(W x² + W y²) = 1

(Wx² + W y²)m² + 2(AxWx + A y Wym + Ax² + Ay² − 1 = 0

现在 Ax² + Ay² 是 |A|²,我们在缩放问题时已经确定它是 1(如果我们选择 m=0,那么 D = A + mW 变为 D = A,|D|² = |A|² = 1)。所以以上简化为:

(宽x² + 周数)m² + 2(AxWx + AyWy)m = 0

直线 AB 穿过圆两次(通常),当 m 为零时,我们有 A。我们想要非零 m(如果有第二个交叉点),所以我们将方程除以 m:

(宽x² + Wy²)m + 2(AxWx + AyWy) = 0
m = −2(AxWx + AyWy)/(宽x² + Wy²)

最后,由于我们将问题缩放到一个单位圆,因此需要将 m 的找到的值缩小到原始问题。

下面是 JavaScript 中的实现:

// Utility functions to work with 2D vectors:
function sub(u, v) {
    return [u[0] - v[0], u[1] - v[1]];
}

function mul(u, m) { // Scalar multiplication
    return [m * u[0], m * u[1]];
}

function size(u) {
    return Math.sqrt(u[0] * u[0] + u[1] * u[1]);
}

function norm(u) { // Resize vector to size 1
    return mul(u, 1 / size(u));
}

function solve(a, b, c) {
    // Translate problem to C at (0, 0): this does not influence the result
    a = sub(a, c);
    b = sub(b, c);
    // Scale problem to Unit circle
    const r = size(a);
    a = mul(a, 1/r);
    b = mul(b, 1/r);
    // Define W as the direction vector of the line A-B, with size 1.
    const w = norm(sub(b, a));
    // Solve equation to find m, such that D = A+mW and |D|² is 1, i.e.
    // |A+mW|² = 1, or 
    // (Ax+mWx)²+(Ay+mWy)² = 1, or 
    // (Ax)² + 2mAxWx + m²(Wx)² + (Ay)² + 2mAyWy + m²(Wy)² = 1, or
    // (Ax)²+(Ay)² - 1 + 2m(AxWx+AyWy) + m²((Wx)²+(Wy)²) = 0
    // As (Ax)²+(Ay)² = 1, we can eliminate (Ax)²+(Ay)² - 1.
    // m=0 is a solution. But we want to get the other root. 
    // So divide the equation by m, and solve by m:
    const m = -2 * (a[0] * w[0] + a[1] * w[1]) / (w[0] * w[0] + w[1] * w[1]);
    // Scale the solution back to the original size
    return m * r; // This is a signed number. For distance, take absolute value
}

// Demo 
const dist = solve([3, 5], [6, 6], [5, 10]);
console.log(dist);

这是一个交互式版本,其中 A 点和 C 点是固定的,但 B 对应于鼠标指针所在的位置。然后,您可以在移动鼠标指针时看到距离,以及相应的线段。

// Utility functions to work with 2D vectors:
function sub(a, b) {
    return [a[0] - b[0], a[1] - b[1]];
}

function add(a, b) {
    return [a[0] + b[0], a[1] + b[1]];
}

function mul(a, m) { // Scalar multiplication
    return [m * a[0], m * a[1]];
}

function size(a) {
    return Math.sqrt(a[0] * a[0] + a[1] * a[1]);
}

function norm(a) { // Resize vector to size 1
    return mul(a, 1 / size(a));
}

function solve(a, b, c) {
    // Translate problem to C at (0, 0): this does not influence the result
    a = sub(a, c);
    b = sub(b, c);
    // Scale problem to Unit circle
    const r = size(a);
    a = mul(a, 1/r);
    b = mul(b, 1/r);
    // Define G as the direction vector of the line A-B, with size 1.
    const w = norm(sub(b, a));
    // Solve equation to find m, such that D = A+mW and |D|² is 1, i.e.
    // |A+mW|² = 1, or 
    // (Ax+mWx)²+(Ay+mWy)² = 1, or 
    // (Ax)² + 2mAxWx + m²(Wx)² + (Ay)² + 2mAyWy + m²(Wy)² = 1, or
    // (Ax)²+(Ay)² - 1 + 2m(AxWx+AyWy) + m²((Wx)²+(Wy)²) = 0
    // As (Ax)²+(Ay)² = 1, we can eliminate (Ax)²+(Ay)² - 1.
    // m=0 is a solution. But we want to get the other root. 
    // So divide the equation by m, and solve by m:
    const m = -2 * (a[0] * w[0] + a[1] * w[1]) / (w[0] * w[0] + w[1] * w[1]);
    // Scale the solution back to the original size
    return m * r; // This is a signed number. For distance, take absolute value
}

// I/O handling and actual call of the solve function. 

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const output = document.querySelector("span");

function drawCircle(c, a) {
    ctx.beginPath();
    ctx.arc(...c, size(sub(c, a)), 0, 2 * Math.PI);
    ctx.stroke();
}

function drawLine(a, d) {
    ctx.beginPath();
    ctx.moveTo(...a);
    ctx.lineTo(...d);
    ctx.stroke();
}

function clear() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
}

document.addEventListener("mousemove", refresh);

function refresh(e) {
    // For demo we fix points a and c. b is determined by pointer position
    const c = [200,  90];
    const a = [150,  30];
    clear();
    drawCircle(c, a);
    if (!e) return; // No coordinates for b provided.
    // Convert mouse pointer to coordinates for b (relative to the canvas)
    const b = [e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop];
    const dist = solve(a, b, c); // <<<< EXECUTE THE ALGORITHM >>>>
    output.textContent = Math.abs(dist); // Output the result
    // For drawing the line segment, calculate d using a, b and dist
    const d = add(a, mul(norm(sub(b, a)), dist));
    drawLine(a, d);
}
refresh();
<canvas height="170" width="500"></canvas>
<div>Size of line segment: <span></span></div>

评论

1赞 TheFurryDevil 4/3/2023
两个答案都有效,但这个答案让我更好地理解了这个过程,非常感谢!
0赞 Stef 4/4/2023
如果你知道 0 总是二次方程的解,你不能因式分解变量并得到线性方程吗?
0赞 trincot 4/4/2023
@Stef,是的,这是可能的。我更新了帖子。
0赞 Stef 4/4/2023
我默默地希望线性方程在缩减后将等价于 :-)distance = 2 * r * cosine_similarity(AB,AC)
0赞 trincot 4/5/2023
是的。询问者也证实了结果是一样的。