【问题标题】:Calculating coordinate of intersection between lines and circle and degree of line in canvas计算画布中线和圆的交点坐标和线的度数
【发布时间】:2017-08-10 20:55:53
【问题描述】:

好吧,我会称自己为一个非常优秀的程序员,但从未真正遵循学术方式。所以我的数学知识比较有限,这对于我有时需要完成的事情来说是个小负担。我知道你们中的一些人非常聪明,喜欢计算这个(对你们来说很容易)的事情:

我需要为客户实现一些图表。我选择了 OCanvas 作为绘图库。它真的很有帮助,我需要做的一切都很好,但有一个警告。

我有两个圆,并且可以将 OCanvas 为我制作的 Objects 传递给一个函数,该函数在圆 1 的中心到圆 2 的中心之间画一条线。现在我想在该线上添加三角形以使其成为箭头。

我知道我需要计算直线的倾斜度,以正确对齐三角形。我还需要计算圆形边界和线之间的交点位置,以定位三角形。

我会为您提供一些代码,但老实说,我不知道如何计算它。对我来说已知的值是:startXstartYendXendY 以及圆 1 X, Y and radius/circle 2 X, Y and radius(X和Y代表圆心)

提前致谢!

编辑:

需要红色三角形的视觉表示:

【问题讨论】:

  • 我认为我现在的答案真的很接近解决方案,只需要对轮换稍作修正

标签: javascript canvas


【解决方案1】:

不完整,但它可能会给你一个好的开始。

let c = document.getElementById("myCanvas"),
  ctx = c.getContext("2d"),
  startX = 50,
  startY = 50,
  endX = 250,
  endY = 150,
  rS = 30,
  rE = 50,
  alpha = Math.atan((startY - endY) / (startX - endX)),
  dir = 0, // start 0 end 1
  side = 10;

ctx.beginPath();
ctx.arc(startX, startY, rS, 0, 2 * Math.PI);
ctx.stroke();

ctx.beginPath();
ctx.arc(endX, endY, rE, 0, 2 * Math.PI);
ctx.stroke();

if (startX < endX) {
	endcx = endX + (rE + side/2) * Math.cos(alpha + Math.PI);
	endcy = endY + (rE + side/2) * Math.sin(alpha + Math.PI);
	startcx = startX - (rS + side/2) * Math.cos(alpha - Math.PI);
	startcy = startY - (rS + side/2) * Math.sin(alpha - Math.PI);
	dir = 0;
} else if (startX >= endX) {
	endcx = endX - (rE + side/2) * Math.cos(alpha + Math.PI);
	endcy = endY - (rE + side/2) * Math.sin(alpha + Math.PI);
	startcx = startX + (rS + side/2) * Math.cos(alpha - Math.PI);
	startcy = startY + (rS + side/2) * Math.sin(alpha - Math.PI);
	dir = 1;
}

ctx.beginPath();
ctx.moveTo(startcx, startcy);
ctx.lineTo(endcx, endcy);
ctx.lineWidth=3;
ctx.stroke();

drawEqTriangle(ctx, side, startcx, startcy, dir?0:1);
drawEqTriangle(ctx, side, endcx, endcy, dir);

function drawEqTriangle(ctx, side, cx, cy, dir) {
  var h = side * (Math.sqrt(3) / 2);

  ctx.save();
  ctx.strokeStyle = "#000";
  ctx.fillStyle = "#000";
  ctx.translate(cx, cy);
  ctx.rotate(alpha + Math.PI / 2);

  ctx.beginPath();
  ctx.lineWidth=1;
  
if (dir === 0) {
  ctx.moveTo(0, -h / 2);
  ctx.lineTo(-side / 2, h / 2);
  ctx.lineTo(side / 2, h / 2);
  ctx.lineTo(0, -h / 2);
} else if (dir === 1) {
  ctx.moveTo(0, h / 2);
  ctx.lineTo(-side / 2, -h / 2);
  ctx.lineTo(side / 2, -h / 2);
  ctx.lineTo(0, h / 2);
}

  ctx.stroke();
  ctx.fill();

  ctx.closePath();
  ctx.restore();
}
<!DOCTYPE html>
<html>

<body>

  <canvas id="myCanvas" width="400" height="300" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
</body>

</html>

【讨论】:

  • 我已经编辑了您的代码,使其在 startX 大于 endX 时也可以工作。但为什么三角形只是一点点偏离?
  • 谢谢。可能是因为线本身有重量。
  • 我也尝试过切换开始和结束以使其双面。有些东西不太对劲:S 会不会是旋转的轴错了?我添加的代码是否正确,哈哈?
  • 坐标可以预先评估,这样 startx 和 endx 将始终指向左侧的圆:)
  • 我明白了。谢谢,我会接受这个作为答案,并使用与我最初要求的代码相匹配的代码编辑您的答案。
【解决方案2】:

要获得三角形的倾斜角度,您需要以下内容: 假设(x1,y1)是行首的坐标,(x2,y2)是行尾的坐标,你想在行尾画三角形:

alpha = arctan((y2-y2) / (x2-x1))

然后您需要创建以该角度 (alpha) 倾斜的三角形。

另一个三角形是alpha + 180 degreesalpha - 180 degrees(产生相同的结果)。

【讨论】:

  • 谢谢!无论circle1是在circle2的左边还是右边,这都能工作吗?以及如何获得坐标来定位圆?
  • @DavidFariña 对不起,我以为箭头指向圆心,我将编辑我的答案以更正确。是的,无论位置如何,它都应该起作用。
  • @DavidFariña 检查编辑的答案并用结果标记我 :)
  • 我在原始问题中添加了视觉表示。我现在不能严格测试你的公式,但会在晚上报告。非常感谢,但不知何故我仍然缺少(x,y)三角形的位置
  • @DavidFariña 我重新编辑了 asnwer,有一个错误。三角形是如何表示的?坐标代表中心吗?它的边长是多少?他们是平等的吗?
【解决方案3】:

试图帮助您解决问题:

编辑:

通过整合@bluehipy answer,也许这会更接近

//Objects
Circle = function(oX, oY, r){
  this.oX = oX;
  this.oY = oY;
  this.r = r;
  context.beginPath();
  context.arc(oX, oY, r, 0, 2 * Math.PI, false);
  context.fillStyle = 'red';
  context.fill();
  context.lineWidth = 2;
  context.strokeStyle = '#003300';
  context.stroke();
}

//Functions
drawArrowFromCirclesOrigins = function(side,c1, c2){
  context.beginPath();
  //line
  context.moveTo(c1.oX, c1.oY);
  context.lineTo(c2.oX, c2.oY);     
  context.stroke();
  //triangle
  var alpha = Math.tan((c2.oY-c1.oY) / (c2.oX-c1.oX));
  var h = side * (Math.sqrt(3) / 2);
  
  context.translate(c2.oX/alpha+c2.r-(-(side/2)), c2.oY/alpha+c2.r-(-(side/2)));
  context.rotate(alpha + Math.PI / 2);

  context.beginPath();

  context.moveTo(0, -h / 2);
  context.lineTo(-side / 2, h / 2);
  context.lineTo(side / 2, h / 2);
  context.lineTo(0, -h / 2);
  context.fillStyle = 'green';
  context.fill();
  
  context.stroke();
}

//Variables
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');

//Program
var cir1 = new Circle(20, 20, 10);
var cir2 = new Circle(40, 40, 10);
drawArrowFromCirclesOrigins(-11,cir1, cir2);
canvas {
  border: 2px solid black;
}
&lt;canvas id="canvas" width="200" height="100"&gt;&lt;/canvas&gt;

【讨论】:

  • 感谢您的帮助,但这是我已经拥有的。我想在原始问题的编辑中使用红色三角形...
  • 在移到 //triangle 下之后,你必须画线并且你已经完成了,此时我无法考虑一种可扩展的方式来做到这一点
【解决方案4】:

在一行上渲染一个箭头

如果你使用的是纯画布,它会不那么繁琐。

首先是两个圆圈

const circle1 = {
    x : ?,
    y : ?,
    r : ?, // Radius
    lineWidth : ?,
}
const circle2 = {
    x : ?,
    y : ?,
    r : ?,
    lineWidth : ?,
}

然后是箭头说明

const arrow = {
   width : ?,
   depth : ?, // tip to back of arrow head
}

数学。

获取circle1到circle2的向量

var vx = circle2.x - circle1.x;
var vy = circle2.y - circle1.y;

他们之间的距离

var dist = Math.sqrt(vx * vx + vy * vy);

您现在需要通过将向量除以长度来规范化向量。这使得向量长一个单位

vx /= dist;
vy /= dist;

现在您可以渲染箭头了。尖端是圆2的边缘之一。要找到该点,请从距离中减去半径和线宽的一半

const aDist = dist - (circle2.r + circle2.lineWidth / 2);

归一化向量可以乘以距离来找到位置,或者如果使用 canvas 2D API,您可以使用法线设置变换,然后它全部与 x 轴对齐。

ctx.beginPath(); // drawing the arrow head
ctx.lineTo(circle1.x + vx * aDist, circle1.y + vy * aDist); // tip of arrow

我们需要从尖端向后移动并以 90 度从直线向外移动,您可以通过交换 x、y 部分并否定新的 x 来将矢量旋转 90 度。

ctx.lineTo(
     circle1.x + vx * (aDist - arrow.depth) - vy * arrow.width, 
     circle1.y + vy * (aDist - arrow.depth) + vx * arrow.width
     //          ^------- along line -----^ ^--Out from line--^ 
);
// and the other side
ctx.lineTo(
     circle1.x + vx * (aDist - arrow.depth) + vy * arrow.width, 
     circle1.y + vy * (aDist - arrow.depth) - vx * arrow.width
     //          ^------- along line -----^ ^--Out from line--^ 
);

// ctx.closePath();
ctx.fill();

或者如果你使用转换

ctx.setTransform(vx,vy,-vy,vx,circle1.x,circle1.y); // set the circle1 as origin
                                                    // and use the normal to
                                                    // align the x axis
ctx.beginPath(); // drawing the arrow head
ctx.lineTo(aDist,0); // tip of arrow
ctx.lineTo(aDist - arrow.depth, arrow.width);
ctx.lineTo(aDist - arrow.depth, -arrow.width);
// ctx.closePath();
ctx.fill();
ctx.setTransform(1,0,0,1,0,0); // restore default transform.

示例

const ctx = canvas.getContext("2d");   


const circle1 = {
    x : 50,
    y : 100,
    r : 45, // Radius
    lineWidth : 3,
}
const circle2 = {
    x : 250,
    y : 50,
    r : 45,
    lineWidth : 3,
}
const arrow = {
   width : 10,
   depth : 20, // tip to back of arrow head
}
var vx = circle2.x - circle1.x;
var vy = circle2.y - circle1.y;
var dist = Math.sqrt(vx * vx + vy * vy);
vx /= dist;
vy /= dist;
const aDist = dist - (circle2.r + circle2.lineWidth / 2);
ctx.lineWidth = circle1.lineWidth;
ctx.beginPath();
ctx.arc(circle1.x, circle1.y, circle1.r, 0, 2 * Math.PI);
ctx.moveTo(circle2.x + circle2.r, circle2.y);
ctx.arc(circle2.x, circle2.y, circle2.r, 0, 2 * Math.PI);
ctx.moveTo(circle1.x, circle1.y);
ctx.lineTo(circle2.x, circle2.y);
ctx.stroke();    

ctx.beginPath(); // drawing the arrow head
ctx.lineTo(circle1.x + vx * aDist, circle1.y + vy * aDist); // tip of arrow
ctx.lineTo(
     circle1.x + vx * (aDist - arrow.depth) - vy * arrow.width, 
     circle1.y + vy * (aDist - arrow.depth) + vx * arrow.width
     //          ^------- along line -----^ ^--Out from line--^ 
);
// and the other side
ctx.lineTo(
     circle1.x + vx * (aDist - arrow.depth) + vy * arrow.width, 
     circle1.y + vy * (aDist - arrow.depth) - vx * arrow.width
     //          ^------- along line -----^ ^--Out from line--^ 
);

// ctx.closePath();
ctx.fill();
canvas { border : 2px solid black; }


const ctx = canvas.getContext("2d");
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

Example2 使用setTransform

这使用 setTransform 来减少数学量。此示例也使用线宽,但箭头指向圆圈

const ctx = canvas.getContext("2d");   


const circle1 = {
    x : 50,
    y : 100,
    r : 45, // Radius
    lineWidth : 3,
}
const circle2 = {
    x : 250,
    y : 50,
    r : 45,
    lineWidth : 3,
}
const arrow = {
   width : 10,
   depth : 20, // tip to back of arrow head
}
var vx = circle2.x - circle1.x;
var vy = circle2.y - circle1.y;
var dist = Math.sqrt(vx * vx + vy * vy);
vx /= dist;
vy /= dist;
const aDist = dist - (circle2.r + circle2.lineWidth / 2);
ctx.lineWidth = circle1.lineWidth;
ctx.beginPath();
ctx.arc(circle1.x, circle1.y, circle1.r, 0, 2 * Math.PI);
ctx.moveTo(circle2.x + circle2.r, circle2.y);
ctx.arc(circle2.x, circle2.y, circle2.r, 0, 2 * Math.PI);
ctx.moveTo(circle1.x, circle1.y);
ctx.lineTo(circle2.x, circle2.y);
ctx.stroke();    

ctx.beginPath(); // drawing the arrow head
ctx.setTransform(vx, vy, -vy, vx, circle1.x, circle1.y); // set the circle1 as origin
                                                    // and use the normal to
                                                    // align the x axis
ctx.beginPath(); // drawing the arrow head
ctx.lineTo(aDist,-circle1.lineWidth/2); // tip of arrow circle1.linewidth is same for line between circles
ctx.lineTo(aDist,circle1.lineWidth/2); // tip of arrow circle1.linewidth is same for line between circles
ctx.lineTo(aDist - arrow.depth, arrow.width + circle1.lineWidth/2);
ctx.lineTo(aDist - arrow.depth, -arrow.width - circle1.lineWidth/2);
// ctx.closePath();
ctx.fill();
ctx.setTransform(1,0,0,1,0,0); // restore default transform.
ctx.fill();
canvas { border : 2px solid black; }


const ctx = canvas.getContext("2d");
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

【讨论】:

    【解决方案5】:

    另一个你可以用来简化事情的想法(可能是上面实现的@Blindman67,但我不理解它)是这样想的:

    无论圆圈位于何处,您都可以旋转上下文,使它们的中心在水平或垂直(如果您愿意)上,这样所有兴趣点都在一个线性坐标上。绘制您需要的内容,然后将上下文旋转回来。

    【讨论】:

    • 其实我已经接受了你的其他回答。如果您能接受我的编辑,那就太好了,因为这正是我所要求的。再次感谢您的帮助!
    猜你喜欢
    • 2014-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-12
    相关资源
    最近更新 更多