从一个点到圆上相对的两个切线画一条直线?AS3中的圆锥/楔形。

8

这应该是一些简单的几何学:如何计算下面代码中绘制线条的点,使其形成2D锥形或楔形?

import flash.geom.Point;

//draw circle
var mc=new Sprite()
mc.graphics.lineStyle(0,0)
mc.graphics.drawCircle(0,0,30)
mc.x=mc.y=Math.random()*300+100
addChild(mc)

//draw lines:
graphics.lineStyle(0,0)
var p=new Point(Math.random()*500,Math.random()*400)
graphics.moveTo(p.x, p.y)
graphics.lineTo(mc.x,mc.y) // << should be point on edge of circle
graphics.moveTo(p.x, p.y)
graphics.lineTo(mc.x,mc.y) // << should be point on opposite edge of circle

更新:
谢谢大家,我应该提到我的目的不是要画一个楔形,而是要从一个随机点画一条直线到现有圆的边缘。

如果你对代数比ActionScript更熟悉,也许你可以看一下这张图,并为我发布一个公式? tangents


+1 很棒的问题。我真的很喜欢尝试解决它。 - Chris
我知道有人会喜欢它!这让我感到很头疼 :D 非常感谢你提供的简单解决方案。 - cronoklee
没问题。祝你的项目好运。 - Chris
5个回答

2
您的问题涉及到塔勒斯定理(参见http://en.wikipedia.org/wiki/Thales%27_theorem)。
以下是稍作修改以适用于AS3的定理。
import flash.geom.Point;
// The radius of the circle
var r1:Number = 30;
// The center point of the circle
var cp:Number = Math.random() * 300+100;
var c:Point = new Point(cp, cp);

// draw circle
var mc=new Sprite();
mc.graphics.lineStyle(0,0);
mc.graphics.drawCircle(0,0,r1);
mc.x = mc.y = cp;
addChild(mc);

// The point
var p = new Point(Math.random() * 500, Math.random() * 400);

// Calculate points for intesecting circle
var c2:Point = Point.interpolate(c, p, 0.5);
var r2:Number = Point.distance(c2, c);
var d:Number = Point.distance(c, c2);

// Remove comment below to see intersecting circle
//graphics.beginFill(0xFF0000, 0.25);
//graphics.drawCircle(c2.x, c2.y, r2);

var a:Number = (r1*r1 - r2*r2 + d*d) / (2*d);
var p2x:Number = c.x + a * ( c2.x - c.x ) / d;
var p2y:Number = c.y + a * ( c2.y - c.y ) / d;
var h:Number = Math.sqrt(r1*r1 - a*a);

var d1x:Number = p2x + h * ( c2.y - c.y ) / d;
var d1y:Number = p2y - h * ( c2.x - c.x ) / d;
var d2x:Number = p2x - h * ( c2.y - c.y ) / d;
var d2y:Number = p2y + h * ( c2.x - c.x ) / d;

// Draw lines
graphics.lineStyle(1, 0xFF00FF, 0.5);
graphics.moveTo(p.x, p.y);
graphics.lineTo(d1x, d1y);
graphics.moveTo(p.x, p.y);
graphics.lineTo(d2x, d2y);

最终产品:

enter image description here

用第二个圆画出来(实际上你不需要画出第二个圆,只需要知道它的中心点和半径就可以了)。

enter image description here

请查看以下 SWF 文件以查看它的运行效果(刷新可查看不同的随机圆形):

http://megaswf.com/serve/1097652


r2:Number = Point.distance(c2, c)d:Number = Point.distance(c, c2) 有什么区别? - user151496

1

(xP, yP)为切线的交点,(xC,yY)为圆的中心,你需要找到切点(xT,yT)的坐标。此外,让T成为切线向量,R成为半径向量。由于它们是垂直的,因此有R . T = 0

这给我们带来了

(xT-xC,yT-yC) . (xT-xP, yT-yP) = 0

r为圆的半径,令x:=xT-xC, y:=yT-yC, xp:=xP-xC, yp:=yP-yC(基本上,我们将圆移动到(0,0))。 切点在圆上,因此有x²+y²=r²,因此也有y=sqrt(r²-x²)

对上述方程进行变量替换得:

(x,y) . (x-xp, y-yp) = 0
x²-xp*x + y²-yp*y = 0

利用我们拥有的圆形信息:

r² -xp*x - yp*sqrt(r²-x²) = 0
r² -xp*x = yp*sqrt(r²-x²)
r^4 - 2*r²*xp*x + xp²*x² = yp²*(r²-x²)
(yp²+xp²)*x² - 2*r²*xp*x + r^4-yp²*r² = 0

now let a:=yp²+xp², b:=2*r²*xp, c:= (r²-yp²)*r²
=> ax² + bx + c = 0

这是一个带有0、1或2个解的二次方程。如果P在圆内,则为0;如果P在圆上,则为1;如果P在圆外,则为2。

我不会在这里放置显式解,因为它是一个非常复杂的公式,如果您将此处引入的变量映射到代码中的变量,则更容易编写。

var sq:Function = function (f:Number) { return f*f; }, sqrt:Function = Math.sqrt;
var xp:Number = xP-xC, yp:Number = yP-yC,
    a:Number = sq(xp)+sq(yp), b:Number = 2*sq(r)*xp, c:Number = sq(r)*(sq(r)-sq(yp));
var x1:Number = (-b+sqrt(sq(b)-4*a*c)) / (2 * a),
    x2:Number = (-b+sqrt(sq(b)-4*a*c)) / (2 * a);
if (isNan(x1)) return [];
var p1:Point = new Point(x1+cX, sqrt(sq(r)-sq(x1))+cY),//calculate y and undo shift
    p2:Point = new Point(x2+cX, sqrt(sq(r)-sq(x2))+cY);
return p1.equals(p2) ? [p1] : [p1, p2];

祝你好运,因为我对微积分非常不擅长,而且现在已经凌晨四点了,所以可以肯定,在某个地方会有错误,但这应该能让你朝正确的方向前进 ;)

1

将您的点标记为P,圆心标记为M,圆上切点标记为X。三角形 PMT 是一直角三角形。您可能需要在操作过程中在纸上勾画出来,以便更容易跟随。

X 的位置就是 M 的位置加上到 X 的径向量,即边缘 MX。然后计算的是计算矢量 MX。

矢量 MX 可以分解成两个垂直的分量。一个是平行于 MP 的,另一个是垂直于 MP 的。首先要做的是获得这两个方向上的单位向量。第一个很容易,因为它只是 MP 的归一化版本。通过交换分量并否定一个分量可以很容易地获得此向量的垂直向量。您否定哪个分量在这里不重要,因为最终您想要两条切线。

现在我们有了两个单位向量,我们需要确定每个向量需要多少才能创建径向量 MX。假设 PMX 的角度为 θ,则从简单的直角三角形可知 cos(θ) = r/|MP|,其中 r 是您的圆的半径,|MP| 是 MP 的长度(您已经计算出这个长度以获取您上面的单位向量)。

从X到MP垂直的线段形成了另一个直角三角形,涉及到θ,其对边和邻边分别代表我们想要的两个分量。这些边的长度只是在MP方向上的r * cos(theta),在垂直方向上的r * sin(theta)。
因此,你的最终结果本质上是
对于其中一个切点,X = M + r * cos(theta) * unit_MP + r * sin(theta) * unit_MP_perp_1
对于另一个切点,X = M + r * cos(theta) * unit_MP + r * sin(theta) * unit_MP_perp_2 unit_MP和unit_MP_perp_1/2是我们之前计算出来的单位向量。perp向量的1/2版本对应于交换后否定第一个或第二个分量。
编辑
根据您添加的图表,方程变为
x1 = cx + R * cos(theta) * Ux + R * sin(theta) * U1x y1 = cy + R * cos(theta) * Uy + R * sin(theta) * U1y
类似的方程(x2,y2)。在这些方程中,

cos(theta) = r / D
sin(theta) = A / D

其中 D = sqrt( (px - cx )^2 + (py - cy)^2 )

而且

Ux = (px -cx) / D
Uy = (py -cy) / D

因此

U1x = -Uy
U1y = Ux

另一个切点的垂直单位向量为

U2x = Uy
U2y =-Ux


非常感谢@Troubadour,但是我对你的变量仍然有点困惑。你能否看一下上面发布的图形,并使用我的术语重新编写公式?它看起来正是我所需要的! - cronoklee
你的解释中的"M"是什么意思?我听不懂了。 - user677526
非常抱歉,我全心全意地道歉。我在纸上写下了步骤,以M作为圆心,因为您的变量被称为mc,但后来意识到它应该是C!当我写我的答案时,我开始使用C,但很快就回到了使用M。对于混淆大家感到抱歉!由于大部分答案已经使用了M,我已将其更改为M。 - Troubadour
@cronoklee:我在最后添加了一个部分,以图表中的内容作为答案。 - Troubadour

0

这里是绘制2D楔形图形的代码片段。您可以调整startAngleangle变量来控制楔形的角度。半径将确定形状的宽度。这应该在具有图形对象的Shape、Sprite、MovieClip或某些子类中使用。

var i:int;
var p:Point = new Point();
var g:Graphics = graphics;
var radius:Number = 100;
var startAngle:Number = 130;
var angle:Number = 280;
var segments:Number = 40;
var degrees:Number = ( startAngle + ( angle - startAngle ) ) / segments;

g.beginFill( 0xFF0000, 1 );
for( i = 0; i <= segments; i++ ) 
{
    p.x = Math.cos( ( ( degrees * i ) + startAngle ) * Math.PI / 180 ) * radius;
    p.y = Math.sin( ( ( degrees * i ) + startAngle ) * Math.PI / 180 ) * radius;
    g.lineTo( p.x, p.y );
}
g.endFill();

0

graphics.curveTo(controlX,ControlY,endX,endY);

在当前点和终点 (x,y) 之间绘制贝塞尔曲线,其中控制点 (x,y) 是曲线弯曲的方向(就像 Photoshop 中的钢笔工具)。将其设置为半个 ΔX,并使用 Y 来调整其强度。


谢谢,我应该提到我的目的不是要画一个楔形,而是要画一条线到现有圆的边缘。 - cronoklee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接