如何使用JavaScript利用两个坐标绘制椭圆?

5
如下图所示,我有两个坐标点,希望绘制一个椭圆,使其主轴边缘与这两个点匹配。

enter image description here

我试图获取这两个坐标之间的中点,并基于这个坐标绘制一个椭圆。以下函数返回数组形式的椭圆点,正是我想要的:
 function add_oval(centre, x, y) {
    var assemble = new Array();
    var angle;
    var dot;
    var tangent = x / y;
    for (i = 0; i < 36; i++) {
        angle = (2 * Math.PI / 36) * i;
        dot = [centre.lng + Math.sin(angle) * y * tangent, centre.lat + Math.cos(angle) * y];
        assemble.push(dot);
    }
    return assemble;
}

但问题是,这些只能画水平椭圆,我不知道如何改变角度。

有人知道如何解决我的问题吗?


5
为了画一个椭圆,你需要定义短轴。 - Teemu
@Teemu,我可以为次轴制定一个特定的值,这并不重要。 - Jack Zhang
2个回答

4

提示:

如果javascript中没有旋转椭圆的功能,可以使用Bezier曲线逼近来解决。请参考这篇文章How to create circle with Bézier curves?,了解如何用四个Bezier弧线逼近一个圆。然后一个椭圆就是一个拉伸的圆,只需要以相同的方式拉伸控制点即可。

从一个单位圆开始,按轴长度缩放控制点,应用所需的旋转并将其平移到所需中心。缩放和旋转参数可以从给定的主轴(加上次要轴的长度)中绘制出来。

另一种方法是使用椭圆的参数方程,并将其绘制为折线。


4
我会使用基向量来实现这个。
  1. ellipse definition

    ellipse

    So we know A,B 2D points. You need to define also scalar minor semi-axis size |b| to define your ellipse. so knowns are:

    A=!
    B=!
    |b|=!
    
  2. Mid point C

    That is easy it is half between A,B

    C.x=A.x + (B.x-A.x)/2
    C.y=A.y + (B.y-A.y)/2
    

    or average of A,B

    C.x = (A.x+B.x)/2
    C.y = (A.y+B.y)/2
    

    choose which you like more (it does not matter which). This point C will serve as basis vectors origin.

  3. Basis vectors a,b

    major semi-axis is easy as it is defined by CB or CA (also does not matter which for full ellipse)

    a.x = B.x-C.x
    a.y = B.y-C.y
    

    You can also use half of AB

    a.x = (B.x-A.x)/2
    a.y = (B.y-A.y)/2
    

    minor semi-axis b is worse and we know that is perpendicular to a so either exploit cross product of a with vector (0,0,1) in 3D ad rescale or use the fact that if you swap (x,y) to (y,-x) or (-y,x) you obtain 90 degree rotation in 2D. and rescale from size |a| to size |b| so:

    |a| = sqrt( a.x*a.x + a.y*a.y )
    b.x = a.y * |b|/|a|
    b.y =-a.x * |b|/|a|
    
  4. Ellipse

    Now we finally got everything we need for rendering. Any point on our ellipse parametrized by angle ang=<0,2.0*M_PI> where M_PI=3.1415926535897932384626433832795 is easy:

    x = C.x + a.x*cos(ang) + b.x*sin(ang)
    y = C.y + a.y*cos(ang) + b.y*sin(ang)
    

    So either add this into your for loop and render your ellipse with points as you have for now. Or render the ellipse as polyline with lines ... for example something like this in VCL/GDI (sorry I do not use javascript):

    bool e,e0;
    double ang,dang=2.0*M_PI/100.0; // 100 lines per 360 degree
    for (e=true,e0=true,ang=0.0;e;ang+=dang,e0=false)
     {
     if (ang>=2.0*M_PI) { ang=2.0*M_PI; e=false; } // reached end? 360 degree
     x = C.x + a.x*cos(ang) + b.x*sin(ang);
     y = C.y + a.y*cos(ang) + b.y*sin(ang);
     if (e0) Canvas->MoveTo(x,y); // first time is cursor moved to (x,y)
      else   Canvas->LineTo(x,y); // all the other iterations just draws a line from last cursor to (x,y) and also moves the cursor there
     }
    

注意

  • 2D中的向量被定义为一组2个坐标(x,y)
  • 标量是单个值(标准数字)
  • |a|代表向量a的大小
  • 以上计算不需要|a|>|b|
  • 如果未知|b|,您可以使用缩放|a|。例如,让|b|/|a|=0.3

如果我想获取椭圆的所有坐标,但不想“绘制”它,该怎么办? 实际上,绘制方法不由我控制。我只能为该方法提供点列表。 - Jack Zhang
@JackZhang 然后您可以忽略moveto/line to,而是将(x,y)添加到您的点列表中。此外,您需要更改dang步骤以获得足够的点数。计算精确的相邻像素位置是一个非常困难的问题,因为旋转会导致超越问题...更简单和更快的方法是使用较小的dang步骤并冒险出现重复像素。为了更好地估计,您可以使用圆形逼近...所以如果|a|>=|b|,那么如果我没有弄错的话,dang<=1/|a|应该非常接近像素步长。这取决于离心率...我会使用dang=0.75/|a|或更小的步长。 - Spektre

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