给定弧外的一点,如何找到延伸到该点的弧上的点?

Given a point outside of an arc, how can one find the point on the arc which extends to that point?

给定弧外的一点,如何找到延伸到该点的弧上的点?

例如圆(R)的半径为10cm,圆心为[0,0]。
直线 (8) 的原点 (o) 位于 [-3, 10]
我们怎样才能找到点 (p) (p8) 那个点的切线是否继续到直线的原点?

暴力解决方案是不可接受的。

设点坐标为px, py,圆心为(0,0)(如果不是-从所有坐标中减去圆心cx, cy以简化方程,最后将它们加回去) .

你可以为未知数写两个方程x,y。圆方程和垂直度一-切线垂直于半径矢量,它们的点积为零。

(x - px) * x + (y - py) * y = 0
x^2 + y^2 = r^2

x^2 - px * x + y^2 - py * y = 0
r^2 - px * x = py * y
y = (r^2 - px * x) / py
y^2  = r^4 / py ^2 - x * 2 * r^2 * px / py^2 + x^2 * px^2 / py^2    

x^2 * (1 + px^2 / py^2) - x * 2 * r^2 * px / py^2 +  (r^4 / py^2 - r^2) = 0
x^2 * (py^2 + px^2) - x * 2 * r^2 * px  +  (r^4 - r^2 * py^2) = 0

求解x的最后一个二次方程,然后计算y

Delphi函数供参考(注:py=0的情况另行处理)

function GetTangentPts(px, py, r: Double): TArray<Double>;
var
  px2, py2, pxpy, r2, Dis, x, y: Double;
begin
  px2 := px * px;
  py2 := py * py;
  r2 := r * r;
  pxpy := px2 + py2;
  Dis := pxpy - r2;
  if Dis < 0 then    //point is inside
    Exit(nil)
  else if Dis = 0 then begin    //point is at circumference
    SetLength(Result, 2);
    if py = 0 then begin
      x := px;
      y := 0;
    end else begin
      x := px * r2 / pxpy;
      y := (r2 - px * x) / py;
    end;
    Result[0] := x;
    Result[1] := y;
  end else begin       //general case, two tangents
    SetLength(Result, 4);
    if py = 0 then begin
       y := - r * Sqrt(Dis) / px;
       x := px / Abs(px) * r * Sqrt(1 - Dis/px2);
       Result[0] := x;
       Result[1] := y;
       y := r * Sqrt(Dis) / px;
       Result[2] := x;
       Result[3] := y;
    end else begin
      x := (px * r2 - r * Sqrt(py2 * Dis)) / pxpy;
      y := (r2 - px * x) / py;
      Result[0] := x;
      Result[1] := y;
      x := (px * r2 + r * Sqrt(py2 * Dis)) / pxpy;
      y := (r2 - px * x) / py;
      Result[2] := x;
      Result[3] := y;
    end;
  end;
end;

一些结果:

10.00 10.00 10.00 //two perpendicular tangents
 0.00
10.00
10.00
 0.00

-10.00 10.00 10.00
-10.00
 0.00
 0.00
10.00

 1.00  1.00 10.00
 //inside

 0.00 10.00 10.00 //horizontal tangent
 0.00
10.00

10.00  0.00 10.00 //vertical tangent
10.00
 0.00

-14.14  0.00 10.00  //two tangents from OX-axis
-7.07
 7.07
-7.07
-7.07

WLOG 圆以原点为圆心。我们表示圆上的一点,设(u, v),与到圆心和目标点(x, y):

的直线成直角
u (x - u) + v (y - v) = 0

u x + v y = r².

我们改写并平方得到

(r² - u²) y² = (r² - u x)²,

u中的一个二次方程。由此,v = √(r² - u²) 紧随其后,您就有了切点。

编辑: 第一个首选方法是非常基于@MBo方法的js版本修复了一些错误。

function tangentLines(centerX, centerY, radius, pX, pY) {
  var horizontalAdjustment, verticalAdjustment, pX_Squared, pY_Squared,
    pXY_Squared, radiusSquared, delta, x, y, result, onSameY, temp

  // Center the circle at [0,0] to simplify things
  onSameY = pY === centerY
  horizontalAdjustment = centerX
  verticalAdjustment = centerY
  pX -= horizontalAdjustment
  pY -= verticalAdjustment
  centerX = centerY = 0
  // If pY is on the same vertical as centerY then temporarily swap pX and pY
  // to avoid bug caused by division of 0
  if(onSameY){
    temp = pY
    pY = pX
    pX = temp
  }

  pX_Squared = pX * pX
  pY_Squared = pY * pY
  radiusSquared = radius * radius
  pXY_Squared = pX_Squared + pY_Squared
  delta = pY_Squared * (pXY_Squared - radiusSquared)

  // Point is inside i.e. no tangent
  if (delta < 0 || pXY_Squared < radiusSquared) {
    return false
  }

  // Point is on circumference only one tangent point
  if (delta === 0) {
    x = (pX * radiusSquared)
    if (pXY_Squared) { x /= pXY_Squared }

    y = (radiusSquared - pX * x)
    if (pY) { y /= pY }
    x += horizontalAdjustment
    y += verticalAdjustment
    return onSameY ? [y,x] : [x, y]
  }

  // Regular case point is outside of tangent there are 2 tangent points
  x = (pX * radiusSquared - radius * Math.sqrt(delta))
  if (pXY_Squared) { x /= pXY_Squared }
  y = (radiusSquared - pX * x)
  if (pY) { y /= pY }
  x += horizontalAdjustment
  y += verticalAdjustment
  result = [
    onSameY ? [y, x] : [x, y],
    []
  ]
  x = (pX * radiusSquared + radius * Math.sqrt(delta))
  if (pXY_Squared) { x /= pXY_Squared }
  y = (radiusSquared - pX * x)
  if (pY) { y /= pY }
  x += horizontalAdjustment
  y += verticalAdjustment
  result[1] = onSameY ? [y, x] : [x, y]
  return result
}

new Vue({
  el: '#app',
  template: `
        <div>
          <div>
            centerX: <input v-model="centerX" @input="intersectionPoints">
            centerY:  <input v-model="centerY" @input="intersectionPoints">
            <div>radius: <input v-model="radius" @input="intersectionPoints"></div>
          </div>
          <div>
            pointX: <input v-model="pointX" @input="intersectionPoints">
            pointY: <input v-model="pointY" @input="intersectionPoints">
          </div>
          <div v-if=result>
          <div>Insect points: {{result}}</div>
          </div>
          <div v-if=!result>No intersections :-(</div>
        </div>
            `,
  data: function() {
    return {
      centerX: 200,
      centerY: 200,
      radius: 100,
      pointX: 160,
      pointY: 100,
      result: null
    };
  },
  methods: {
    intersectionPoints() {
      this.result = tangentLines(
        +this.centerX,
        +this.centerY,
        +this.radius,
        +this.pointX,
        +this.pointY
      );
    }
  },
  mounted: function() {
    this.intersectionPoints();
  }
});

// Without Vue just use something like
// tangentLines(200, 200, 100, 160, 100);
div {
  margin: .5em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

这是我想出的原始代码。

基于@Aretino 的回答https://math.stackexchange.com/questions/3541795/given-a-point-outside-of-an-arc-how-can-one-find-the-point-on-the-arc-which-ext/3541928#3541928

  1. op 之间距离的一半为半径画一个圆,以直线 op.
  2. 的中点为原点
  3. 计算原圆与新圆的交点

// Function to find tangent intersection points to a point outside of the circle
// Credits
// https://math.stackexchange.com/questions/3541795/given-a-point-outside-of-an-arc-how-can-one-find-the-point-on-the-arc-which-ext/3541928#3541928
// 
/**
 *
 *
 * @param {number} centerX1 Center of circle 1 X
 * @param {number} centerY1 Center of circle 1 Y
 * @param {number} radius1 Radius of circle 1
 * @param {number} centerX2 Center of circle 2 X
 * @param {number} centerY2 Center of circle 2 Y
 * @param {number} radius2 Radius of circle 2
 * @returns {object | boolean} The to intersect points { point1: [x1, y1], point2: [x2, y2] } or false
 * @credit Math based on https://www.analyzemath.com/CircleEq/circle_intersection_calcu.html
 */
var circleIntersections = function(
  centerX1,
  centerY1,
  radius1,
  centerX2,
  centerY2,
  radius2
) {
  var a, b, c, A, B, C, delta, x1, x2, y1, y2;

  a = -(centerY1 - centerY2) / (centerX1 - centerX2);
  b = 2 * (centerX1 - centerX2);
  c =
    (-radius1 * radius1 +
      radius2 * radius2 +
      centerX1 * centerX1 -
      centerX2 * centerX2 +
      centerY1 * centerY1 -
      centerY2 * centerY2) /
    b;
  A = a * a + 1;
  B = 2 * a * c - 2 * centerX1 * a - 2 * centerY1;
  C =
    c * c +
    centerX1 * centerX1 -
    2 * centerX1 * c +
    centerY1 * centerY1 -
    radius1 * radius1;
  delta = B * B - 4 * A * C;
  if (delta < 0) {
    return false;
  }
  y1 = (-B + Math.sqrt(delta)) / (2 * A);
  x1 = a * y1 + c;
  y2 = (-B - Math.sqrt(delta)) / (2 * A);
  x2 = a * y2 + c;

  return {
    point1: [x1, y1],
    point2: [x2, y2]
  };
};

/**
 *
 *
 * @param {number} centerX Center of circle X
 * @param {number} centerY Center of circle Y
 * @param {number} radius Radius of circle
 * @param {number} pointX Point to tangent to X
 * @param {number} pointY Point to tangent to Y
 * @returns {object | boolean} The to intersect points { point1: [x1, y1], point2: [x2, y2] } or false
 */
var tangentLines = function(centerX, centerY, radius, pointX, pointY) {
  var centerX2, centerY2, radius2, dX, dY;
  centerX2 = centerX - (centerX - pointX) / 2;
  centerY2 = centerY - (centerY - pointY) / 2;
  dX = centerX2 - centerX;
  dY = centerY2 - centerY;
  radius2 = Math.sqrt(dX * dX + dY * dY);

  return circleIntersections(
    centerX,
    centerY,
    radius,
    centerX2,
    centerY2,
    radius2
  );
};

new Vue({
  el: '#app',
  template: `
        <div>
          <div>
            centerX: <input v-model="centerX" @input="intersectionPoints">
            centerY:  <input v-model="centerY" @input="intersectionPoints">
            <div>radius: <input v-model="radius" @input="intersectionPoints"></div>
          </div>
          <div>
            pointX: <input v-model="pointX" @input="intersectionPoints">
            pointY: <input v-model="pointY" @input="intersectionPoints">
          </div>
          <div v-if=result>
          <div>point1: {{result.point1}}</div>
          <div>point2: {{result.point2}}</div>
          </div>
          <div v-if=!result>No intersections :-(</div>
        </div>
            `,
  data: function() {
    return {
      centerX: 200,
      centerY: 200,
      radius: 100,
      pointX: 160,
      pointY: 100,
      result: null
    };
  },
  methods: {
    intersectionPoints() {
      this.result = tangentLines(
        this.centerX,
        this.centerY,
        this.radius,
        this.pointX,
        this.pointY
      );
    }
  },
  mounted: function() {
    this.intersectionPoints();
  }
});

// Without Vue just use something like
// tangentLines(200, 200, 100, 160, 100);
div {
margin:5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id=app></div>