RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 707811
Accepted
user239133
user239133
Asked:2020-08-19 04:18:14 +0000 UTC2020-08-19 04:18:14 +0000 UTC 2020-08-19 04:18:14 +0000 UTC

用两条弧近似替换三次贝塞尔曲线

  • 772

你好。我的旧图形框架 (GD) 无法绘制三次贝塞尔曲线(它根本无法绘制)。为了从 TTF 字体中绘制字符(任务不允许直接使用它),我想以某种方式部分地用弧线替换贝塞尔曲线。但是怎么做?我不需要现成的科学作品(我知道它们存在并具有工业意义),它允许使用一组弧线使结果尽可能准确地接近贝塞尔曲线。两条弧线对我来说就足够了。

这是我所拥有的: 在此处输入图像描述

点 P1 和 P2 的坐标是已知的。控制点 C₁ 和 C₂ 也是如此。从点 P1 和 P2 我画出垂直于红色线段 P1C1 和 P2C2 的直线。所需弧的中心将位于这些线上,否则点 P 1 和 P 2 处的切线将不重合,并且违反了问题的条件。

然后我决定这样做。通过点 P₁ 和 P₂ 之间的中点 P₀,我画了一条垂直于它们之间的线段的直线(灰色),然后是“灰色垂直线”。然后我添加第一个圆圈(绿色):

在此处输入图像描述

可以看出,根据它的半径R1,它的中心简单地沿着红色直线“行进”,而与灰色垂线的交点和交角发生变化。

一切都与半径为 R₂(蓝色)的第二个圆相似:在此处输入图像描述

所以,我需要找到半径R₁和R₂的这样的值,使两个圆与灰色垂线相交于一点,此外,它们在这一点具有相同的切角。然后我会以某种方式自己将它们切成弧形。

也就是说,初始数据是P1,P2,C1,C2,其坐标(x,y)是已知的。你需要找到 R₁ 和 R₂(圆半径,标量数)。

我不能再用一堆三角函数分别求解带有 X 和 Y 的方程。也许在向量代数的帮助下,这会更容易、更优雅地解决?

对于这个问题,我自己已经做了什么。当预先知道第一个弧的半径时,我解决了一个稍微不同的问题。

<?php

$width = 600;
$height = 600;

function demo($im) {

    $x1 = 124;
    $y1 = 253;
    imagefilledarc($im, $x1, $y1, 10, 10, 0, 0, 0x000000, 0);
    imagestring($im, 2, $x1-30, $y1+10, "x1, y1", 0x000000);

    $x2 = 445;
    $y2 = 428;
    imagefilledarc($im, $x2, $y2, 10, 10, 0, 0, 0x000000, 0);
    imagestring($im, 2, $x2+0, $y2+10, "x2, y2", 0x000000);

    imageline($im, $x1, $y1, $x2, $y2, 0x000000);

    $xc1 = $x1 + 50;
    $yc1 = $y1 - 200;
    imageline($im, $x1, $y1, $xc1, $yc1, 0x000000);
    imagefilledarc($im, $xc1, $yc1, 10, 10, 0, 0, 0x000000, 0);

    $xc2 = $x2 -5;
    $yc2 = $y2 -200;
    imageline($im, $x2, $y2, $xc2, $yc2, 0x000000);
    imagefilledarc($im, $xc2, $yc2, 10, 10, 0, 0, 0x000000, 0);

    $d1 = 10 * sqrt( pow($x1-$xc1, 2) + pow($y1-$yc1, 2) );
    $ac1 = atan2($yc1-$y1, $xc1-$x1);
    $ap1 = $ac1 + pi() / 2.;
    $xp1 = $x1 + $d1*cos($ap1);
    $yp1 = $y1 + $d1*sin($ap1);
    imageline($im, $x1, $y1, $xp1, $yp1, 0x808080);

    $d2 = 10 * sqrt( pow($x2-$xc2, 2) + pow($y2-$yc2, 2) );
    $ac2 = atan2($yc2-$y2, $xc2-$x2);
    $ap2 = $ac2 - pi() / 2.;
    $xp2 = $x2 + $d2*cos($ap2);
    $yp2 = $y2 + $d2*sin($ap2);
    imageline($im, $x2, $y2, $xp2, $yp2, 0x808080);


    /** Принудительно задается радиус первой (красной) дуги, что неправильно!!! */
    $r1 = 135;
    /** Из него вычисляется положение центра красной дуги, обозначен красной точкой. */
    $r1x = $x1 + $r1*cos($ap1);
    $r1y = $y1 + $r1*sin($ap1);
    imagefilledarc($im, $r1x, $r1y, 10, 10, 0, 0, 0xFF0000, 0);
    imagestring($im, 2, $r1x-40, $r1y-30, "r1x, r1y", 0x000000);

    /** Это результат решения уравнения для заданного радиуса красной дуги */
    $L = (
        2.*($r1x*$x2 + $r1y*$y2)
        - pow($x2, 2)
        - pow($y2, 2)
        - pow($r1x, 2)
        - pow($r1y, 2)
        + pow($r1, 2)
    ) / 2. / (
        cos($ap2)*($x2-$r1x)
        + sin($ap2)*($y2-$r1y)
        + $r1
    );

    $xl = $x2 + $L*cos($ap2);
    $yl = $y2 + $L*sin($ap2);

    imagefilledarc($im, $xl, $yl, 10, 10, 0, 0, 0x00FF00, 0);

    $aspl = atan2($r1y-$yl, $r1x-$xl);

    imagearc($im, $r1x, $r1y, 2*$r1, 2*$r1, rad2deg($ap1)+180, rad2deg($aspl), 0xFF0000);
    imagearc($im, $xl, $yl, 2*$L, 2*$L, rad2deg($aspl), rad2deg($ap2)-180, 0x0000FF);

}

$im = imagecreatetruecolor($width, $height);
imagefilledrectangle($im, 0, 0, $width-1, $height-1, 0xc0c0c0);

demo($im);

header('Content-Type: image/png');
imagepng($im, null, 9);

在此处输入图像描述

在这种情况下,第一条弧线是红色的。但这不是所需要的。我需要弧之间的某种“奇偶性”,而不是第一个弧的半径是强制的。

我要开始比赛了。因此,为了不误导参与者,我现在将陈述我自己过去一段时间得出的结论。

在此处输入图像描述

我从它应该通过的任何点(Xz,Yz)的坐标中得到了圆半径的公式:

            (Xz-X₁)² + (Yz-Y₁)²
R₁ = 2 ---------------------------
         (Xz-X₁)Cosβ + (Yz-Y₁)Sinβ

            (Xz-X₂)² + (Yz-Y₂)²
R₂ = 2 ---------------------------
         (Xz-X₂)Cosγ + (Yz-Y₂)Sinγ


然后我指定横坐标轴和1) 点 P₁ - β 的红色垂直线之间的角度(不在图片中)
2) 点 P₂ - γ 的红色垂直线
3) 点 P₀ - δ 的灰色垂直线
(我保留了角度 α 以防直线 P₁P₂ 的斜率,但不需要)。

此外,在点(Xz,Yz)处增加了切圆相等的条件。它只是意味着(Xz,Yz),(Xr1,Yr1)和(Xr2,Yr2)位于同一条线上。简单比例:

Xz - Xr₁   Xz - Xr₂
-------- = --------
Yz - Yr₁   Yz - Yr₂

然后我用与其他点的角度和距离来表示点的坐标:

Xz = X₀ + ZCosδ
Yz = Y₀ + ZSinδ
Xr₁ = X₁ + R₁Cosβ 
Yr₁ = Y₁ + R₁Sinβ
Xr₂ = X₂ + R₂Cosγ
Yr₂ = Y₂ + R₂Sinγ

结果是:

具有三个未知(R1、R2、Z)的方程组:

            (Xz-X₁)² + (Yz-Y₁)²
R₁ = 2 ---------------------------
         (Xz-X₁)Cosβ + (Yz-Y₁)Sinβ

            (Xz-X₂)² + (Yz-Y₂)²
R₂ = 2 ---------------------------
         (Xz-X₂)Cosγ + (Yz-Y₂)Sinγ

Xz - Xr₁   Xz - Xr₂
-------- = --------
Yz - Yr₁   Yz - Yr₂

Xz = X₀ + ZCosδ
Yz = Y₀ + ZSinδ
Xr₁ = X₁ + R₁Cosβ 
Yr₁ = Y₁ + R₁Sinβ
Xr₂ = X₂ + R₂Cosγ
Yr₂ = Y₂ + R₂Sinγ

此外,所有这些正弦和余弦通常都是常数,因为角度(它们的参数)是作为常数给出的。

一旦我扭曲了这个系统。打开括号,使用计算机代数(Maxima 程序),包括部分结果。事实证明,没有解析解。而且数值方法不适合,因为我打算用弧线从字体中绘制符号,你能想象它会以什么速度出现。

因此,宣布比赛。只有问题的标题很重要。然后我们根据需要做出决定,只是分析性的。注意我在文本中所说的所有废话是没有必要的,而且可能是有害的。

PS,除其他外,我对基于弧(半径)之间的某种关系的“奇偶校验”感兴趣。用给定的半径制作一个小圆圈并将第二个“附加”到它 - 这已经在问题的文本中完成了。

顺便说一下,这里是 R₂ 的负值。基于前两个公式在 PHP GD 中渲染,没有任何附加条件:

在此处输入图像描述

也就是说,与灰色垂线相交的圆与点 P₀ 相交的距离没有限制。

在这里,以防万一,谁需要它,点 P1、P2 以及需要它的控制点 C1 和 C2 上的三次贝塞尔曲线,用红色虚线表示:

在此处输入图像描述

它不需要近似,而是简单地替换为基于两条弧线的平滑值。

我会在问题中添加动画,否则很少关注它。

在此处输入图像描述

$r2 = .5 * (pow($r1, 2.) - pow($x2-$r1x, 2.) - pow($y2-$r1y, 2.)) /
    (cos($ap2)*($x2-$r1x) + sin($ap2)*($y2-$r1y) + $r1);

$ap2 就是 P2 处切线的垂线。

любой-язык
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. Zergatul
    2020-08-24T08:28:49Z2020-08-24T08:28:49Z

    您已将问题表述为在一般情况下没有解决方案。

    所以,我需要找到半径R₁和R₂的这样的值,使两个圆与灰色垂线相交于一点,此外,它们在这一点具有相同的切角。

    Если 2 окружности пересекаются, то в точке пересечения они имеют одинаковый угол касательной тогда и только тогда, когда точка пересечения одна:

    在此处输入图像描述

    В примере выше, в первых 2 случаях угол касательной одинаковый, в 3 случае, никак нельзя совместить 2 дуги.

    Я нарисовал в редакторе решение для примера, как на ваших рисунках. Решение не учитывает ваше условие про пересечение на перпендикуляре:

    在此处输入图像描述

    Если я продолжу увеличивать радиус большей окружности, и подбирать радиус меньшей, что бы они пересекались в одной точке, эта точка никогда не будет на перпендикуляре. Даже если мы увеличим радиус второй окружности до бесконечности (она станет прямой), пересечение все еще не достигнет перпендикуляра:

    在此处输入图像描述

    Решение без вложенных окружностей не подходит, потому что дуги не с правильной стороны выходят:

    在此处输入图像描述

    Тоже самое и с решением, когда вторая окружность внутри первой:

    在此处输入图像描述

    • 6
  2. Best Answer
    VladD
    2020-08-25T06:42:48Z2020-08-25T06:42:48Z

    作为一个简单的选择,我建议光栅化。

    中间点很容易计算:

    Point GetMiddlePoint(double alpha)
    {
        Point Q0 = Middle(P1, C1, alpha);
        Point Q1 = Middle(C1, C2, alpha);
        Point Q2 = Middle(C2, P2, alpha);
        Point R0 = Middle(Q0, Q1, alpha);
        Point R1 = Middle(Q1, Q2, alpha);
        return Middle(R0, R1, alpha);
    }
    
    Point Middle(Point X, Point Y, double alpha) => X + (Y - X) * alpha;
    

    通过用线连接中间点,我们已经在 8 个点处得到了一个不错的结果:

    光栅化不是狗屎代码!

    • 5

相关问题

  • 是否可以使用指示语气来构建程序?[关闭]

  • 代码中的注释应该怎么说?

  • “生产”和“研究”编程语言

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5