Javascript 将双曲线转换为Bé;绘制轨道轨迹的zier曲线

Javascript 将双曲线转换为Bé;绘制轨道轨迹的zier曲线,javascript,html5-canvas,bezier,Javascript,Html5 Canvas,Bezier,我正在使用HTML画布编写一个2D模拟器和游戏,其中涉及轨道力学。该程序的一个特点是获取卫星在某一点的位置和速度矢量,并返回围绕一颗行星的二维轨道的半长轴、偏心率、近点参数等。当偏心率小于1时,我可以使用ctx.eliple()轻松地将轨道绘制为椭圆。然而,对于大于1的偏心率,轨道的正确形状是双曲线。目前,如果偏心率大于1,我的程序什么也不画,但我希望它能画出正确的双曲线轨道。由于没有内置的“双曲线”函数,我需要将我的轨道转换为贝塞尔曲线。我不知道该怎么做。输入将是一个焦点的位置、半长轴、偏心率

我正在使用HTML画布编写一个2D模拟器和游戏,其中涉及轨道力学。该程序的一个特点是获取卫星在某一点的位置和速度矢量,并返回围绕一颗行星的二维轨道的半长轴、偏心率、近点参数等。当偏心率小于1时,我可以使用ctx.eliple()轻松地将轨道绘制为椭圆。然而,对于大于1的偏心率,轨道的正确形状是双曲线。目前,如果偏心率大于1,我的程序什么也不画,但我希望它能画出正确的双曲线轨道。由于没有内置的“双曲线”函数,我需要将我的轨道转换为贝塞尔曲线。我不知道该怎么做。输入将是一个焦点的位置、半长轴、偏心率和近拱点的参数(基本上是轨道旋转的距离),它应该返回正确的控制点,以绘制双曲线的贝塞尔曲线近似值。它不一定是完美的,只要它足够贴身。如何解决这个问题?

对于圆锥曲线,双曲线是画布无法自然渲染的一类曲线,因此您必须近似所需的曲线。这里有一些选项:

  • 通过在距离中的一个或两个点以及靠近极值的许多点处采样双曲线,可以展平曲线,以便可以绘制一个看起来像曲线的简单多边形
  • 用一条“最佳近似”二次曲线或三次曲线对双曲线建模
  • 正如@fang提到的:在几个点上采样曲线,并通过这些点将Catmull Rom样条线转换为Bezier形式
  • 结合方法1和2。使用一条贝塞尔曲线来近似双曲线中实际看起来弯曲的部分,并使用直线来表示不弯曲的部分
  • 结合方法1和方法3,使用Catmull Rom样条曲线作为曲线钻头,直线作为直线钻头
  • 1:曲线展平 曲线展平基本上是微不足道的。旋转曲线直到轴对齐,然后使用标准双曲线函数计算给定的
    x
    ,其中
    a
    是极值之间距离的一半,
    b
    是半短轴:

    x²/a² - y²/b² = 1
    x²/a² = 1 + y²/b² 
    x²/a² - 1 = y²/b² 
    b²(x²/a² - 1) = y²
    b²(x²/a² - 1) = y²
    ± sqrt(b²(x²/a² - 1)) = y
    
    插入您的值,在
    x
    上迭代以获得一系列
    (x,y
    )坐标(记住在极值附近生成更多坐标),然后将这些坐标转换为第一个坐标的
    moveTo()
    ,然后再调用剩余的
    lineTo()
    调用。只要你的点密度足够高,适合你呈现的比例,这看起来应该很好:

    function flattenHyperbola(a, b, inf=1000) {
      const points = [],
            a2 = a**2,
            b2 = b**2;
    
      let x, y, x2;
    
      for (x=inf; x>0.1; x/=2) {
        x2 = (a+x)**2;
        y = -Math.sqrt(b2*x2/a2 - b2);
        points.push({x: a+x, y});
      }
    
      points.push({x:a, y:0});
    
      for (x=0.1; x<inf; x*=2) {
        x2 = (a+x)*(a+x);
        y = Math.sqrt(b2*x2/a2 - b2);
        points.push({x:  a+x, y});
      }
    
      return points;
    }
    
    因此,对于每个维度,我们可能只需要前两个项,在这种情况下,我们可以使用立方贝塞尔(因为最高阶数为t³):

    事实证明,这是不行的:它太不准确了,所以我们必须更好地近似:我们创建一条Bezier曲线,起点和终点“远离距离”,并设置控制点。如果我们尝试这样做,我们可能会被愚弄,认为这样做会奏效:

    但如果我们选择的
    x
    距离足够远,我们会发现这种近似很快停止工作:

    function touchingParabolicHyperbola(a, b, inf=1000) {
      const beziers = [],
            a2 = a**2,
            b2 = b**2;
    
      let x, x2, y, A, CA;
    
      for(x=50; x<inf; x+=50) {
        x2 = x**2;
        y = sqrt(b2*x2/a2 - b2);
    
        // Hit up https://pomax.github.io/bezierinfo/#abc
        // and model the hyperbola in the cubic graphic to
        // understand why the next, very simple-looking,
        // line actually works:
        A = a - (x-a)/3;
    
        // We want the control points for this A to lie on
        // the asymptote, but for small x we want it to be 0,
        // otherwise the curve won't run parallel to the
        // hyperbola at the start and end points.
        CA = lerp(0, A*b/a, x/inf);
    
        beziers.push([
          {x,    y: -y}, 
          {x: A, y:-CA}, 
          {x: A, y: CA}, 
          {x,    y}, 
        ]);
      }
    
      return beziers;
    }
    
    这给了我们以下结果(蓝色的Bezier,黑色的线段):

    这不太好,但也不可怕。如果观众不仔细检查渲染效果,这当然足够好了,而且绝对便宜,但我们只需多做一点工作,就可以做得更好,因此:让我们看看我们在这里可能得出的最佳近似值:

    5:合并(1)和(3) 如果一条贝塞尔曲线不起作用,我们已经看到用一条Catmull Rom样条曲线代替一条曲线效果更好,那么我们当然也可以将方法1和3结合起来。通过构造两条Bezier曲线而不是一条曲线,生成以极值为中心的五个点,并通过这些点将生成的Catmull Rom样条曲线转换为Bezier形式,我们可以在极值周围形成更好的拟合:

    function probablyTheBestHyperbola(a, b, inf=1000) {
      let curve = [],
          a2 = a**2,
          b2 = b**2,
          x, y, x2,
          cover = 100;
    
      // generate two points approaching the midpoint
      for (x=a+cover; x>a; x-=cover/2) {
        x2 = x**2;
        y = -Math.sqrt(b2*x2/a2 - b2);
        curve.add(new Vec2(x, y));
      }
    
      // generate three points departing at the midpoint
      for (x=a; x<=a+cover; x+=cover/2) {
        x2 = x*x;
        y = sqrt(b2*x2/a2 - b2);
        curve.add(new Vec2(x, y));
      }
    
      const beziers = crToBezier(curve),
            start = {
              x1: points.get(1).x, y1: points.get(1).y,
              x2: inf, y2: -inf * b/a
            },
            end = {
              x1: points.get(3).x, y1: points.get(3).y,
              x2: inf, y2: inf * b/a
            };
    
      return { start, beziers, end };
    }
    
    函数概率双曲线(a,b,inf=1000){
    设曲线=[],
    a2=a**2,
    b2=b**2,
    x、 y,x2,
    覆盖率=100;
    //生成接近中点的两个点
    对于(x=a+保护层;x>a;x-=保护层/2){
    x2=x**2;
    y=-Math.sqrt(b2*x2/a2-b2);
    添加(新的Vec2(x,y));
    }
    //生成三个从中点出发的点
    
    对于(x=a;x)在椭圆或双曲线上取几个点,然后创建一条catmull rom样条曲线。catmull rom脊椎的每一段都是一条三次贝塞尔曲线。显示您的代码,我们可能会帮助您修复它。
    function tangentialParabolicHyperbola(a, b, inf=1000) {
      const beziers = [],
            a2 = a**2,
            b2 = b**2;
    
      let x, x2, y;
    
      for(x=50; x<inf; x+=50) {
        x2 = x**2;
        y = sqrt(b2*x2/a2 - b2);  
        beziers.push([
          {x, y:-y}, 
          {x: 0, y:0}, 
          {x: 0, y:0}, 
          {x, y}, 
        ]);
      }
    
      return beziers;
    }
    
    function hyperbolaToPolyBezier(a, b, inf=1000) {
      const points = [],
            a2 = a**2,
            b2 = b**2,
            step = inf/10;
    
      let x, y, x2,
       
      for (x=a+inf; x>a; x-=step) {
        x2 = x**2;
        y = -Math.sqrt(b2*x2/a2 - b2);
        points.push({x, y});
      }
    
      for (x=a; x<a+inf; x+=step) {
        x2 = x**2;
        y = Math.sqrt(b2*x2/a2 - b2);
        points.push({x, y});
      }
    
      return crToBezier(points);
    }
    
    function crToBezier(points) {
      const beziers = [];
    
      for(let i=0; i<points.length-3; i++) {
        //  NOTE THE i++ HERE! We're performing a sliding window conversion.
        let [p1, p2, p3, p4] = points.slice(i);
        beziers.push({
          start: p2,
          end: p3,
          c1: { x: p2.x + (p3.x-p1.x)/6, y: p2.y + (p3.y-p1.y)/6 },
          c2: { x: p3.x - (p4.x-p2.x)/6, y: p3.y - (p4.y-p2.y)/6 }
        })
      }
    
      return beziers;
    }
    
    function hyperbolicallyFitParabolica(a, b, inf=1000) {
      const a2 = a**2,
            b2 = b**2,
            x = 4*a,
            x2 = x**2,
            y = sqrt(b2*x2/a2 - b2)
            bezier = [
              {x: x, y:-y}, 
              {x: 0, y: 0}, 
              {x: 0, y: 0}, 
              {x: x, y: y}, 
            ],
            start = { x1:x, y1:-y, x2:inf, y2: -inf * b/a},
            end   = { x1:x, y1: y, x2:inf, y2:  inf * b/a};
    
      return [start, bezier, end];
    }
    
    function probablyTheBestHyperbola(a, b, inf=1000) {
      let curve = [],
          a2 = a**2,
          b2 = b**2,
          x, y, x2,
          cover = 100;
    
      // generate two points approaching the midpoint
      for (x=a+cover; x>a; x-=cover/2) {
        x2 = x**2;
        y = -Math.sqrt(b2*x2/a2 - b2);
        curve.add(new Vec2(x, y));
      }
    
      // generate three points departing at the midpoint
      for (x=a; x<=a+cover; x+=cover/2) {
        x2 = x*x;
        y = sqrt(b2*x2/a2 - b2);
        curve.add(new Vec2(x, y));
      }
    
      const beziers = crToBezier(curve),
            start = {
              x1: points.get(1).x, y1: points.get(1).y,
              x2: inf, y2: -inf * b/a
            },
            end = {
              x1: points.get(3).x, y1: points.get(3).y,
              x2: inf, y2: inf * b/a
            };
    
      return { start, beziers, end };
    }