酷徒LOGO
0 0 评论
文章标签:SPI  Figure  far  lis  Figures  FIG  SVG  Wheel  

基于开源项目 Spiro2SVG。 本文对文章 Spiro2SVG进行了跟踪- 使用贝塞尔曲线将螺旋转换成 SVG。 我们将开发更通用的方法将 roulettes 转换为 SVG,包括李萨如图形和 Farris。

介绍

在以前,一种方法是用Bezier曲线拟合方法将标准的重旋形状( )的。 这种方法相当专业,不适合其他类型的赌博形状。 本文将讨论两种新型形状的转换,使用更通用的方法。 第一种类型来自程序 SpiroJ.jar。 这个程序完成了一个示例文件库,其中大部分都包含在示例文件夹中。 这个程序也使用了两个转子,但是,x 方向的幅度和频率不同于方向的幅度和频率。 这可以创建各种形状的形状,大多数不具有旋转对称,除了x-或者y 方向。 这种形状的一个子集是 Lissajous图,它模仿了示波器的行为。 第二种新形状是 Farris轮,由 Frank Farris在一篇文章中描述。 这三个转子代替两个转子,转子再次是"圆形",amplitide和频率均为和频率,但每个转子相位移。 这些形状可以由程序 GeometryDemonstration.exe 生成,这是文章 spirograph spirograph Shapes的一部分: 数学公式的WPF贝塞尔贝塞尔形状"by。

主要推动上述形状的处理方法,重点是在SVG文件中保存数据,使用三次贝塞尔曲线。 我们还将描述保存原始图形中的任何旋转对称的方法,即使对称不知道时间。

使用程序

程序用法与以前的文件基本相同: "将螺旋转换为 SVG"。 单击 Spiro2SVG.jar 文件将打开一个文件打开对话框。 浏览到Samples文件夹并打开. xml 文件,而不是. spiro 文件。 .xml 文件有两种类型。 第一种类型是SpiroJ用于两个转子的格式。 下面是图像 meduza.svg的完整文件,如上所示。

<?xmlversion="1.0"encoding="UTF-8"?><spiro><editortype="advanced"/><shapelinewidth="1"linecolor="#000000"fillcolor="#DDDDFF"/><generatorsteps="600"/><operatortype="rotor"><radiusx="50.0"y="50.0"/><frequencyx="3.0"y="3.0"/></operator><operatortype="rotor"><radiusx="30.0"y="30.0"/><frequencyx="11.0"y="8.0"/></operator></spiro>

请注意,在x-和y 方向上频率不同,该文件包含参数"发电机步骤",即使用的直线数。 我们将不使用这个参数,如果我们选择了Bezier渲染方法。

第二类. xml 文件包含Farris轮所需的格式。 不幸的是,我还没有找到任何实际保存这些数据文件供将来使用的程序。 因此,我已经任意创建了一个模仿SpiroJ格式的格式。 下面是图像 Farris_1_7_-17.svg的xml数据文件,上面显示了该文件:

<?xmlversion="1.0"encoding="UTF-8"?><spiro><editortype="Farris"/><shapelinewidth="1.7"linecolor="#00006A"fillcolor="#B8E0CC"/><generatorsteps="600"/><operatortype="rotor"><radiusr="120"/><frequencyw="1"/><phasephi="0"/></operator><operatortype="rotor"><radiusr="60"/><frequencyw="7"/><phasephi="0"/></operator><operatortype="rotor"><radiusr="40"/><frequencyw="-17"/><phasephi="90"/></operator></spiro>

这里数据文件可以用作为它的他类型的Farris轮创建数据文件的模板,它的频率和幅度不同。 当你使用程序显示一个有趣的形状后,就可以从GeometryDemonstration程序中复制相关的输入数据。 注意,这个数据文件有三个转子,而不是两个,并且x 和y 之间没有区别。 在整个周期内,我们也假设频率总是整数,所以物体会在有限的旋转内变成闭合形状,并且我们将以度数单位来指定相位"phi"。

加载输入文件后,你将可以选择呈现方法,以及输出svg文件 NAME的选择。 在本文中,我们只讨论cmmi渲染方法,因为它的他两个方法是直接的,并且在第一篇文章中。

程序计算顺序

计算顺序如第一篇文章所述,除非遇到不同的文件格式,否则会再次合并。 信息流显示在下图中:

如果选择. spiro 文件,则将按照第一篇文章中所述进行处理。 如果选择了. xml 文件,例程 SpiroParse.parse_spiroJ_file() 将决定它是什么类型。 如果是 SpiroJ ( 两个转子) 类型,则在SpiroConfig对话框中将使用以下字段名称:

publicstaticfinalString[][] SpiroJNames = newString[][]
 {{"Radius_x1"}, {"Radius_y1"}, {"Frequency_x1"}, {"Frequency_y1"},
 {"Radius_x2"}, {"Radius_y2"}, {"Frequency_x2"}, {"Frequency_y2"},
 {"generator_steps"}, {"line_width"}, {"line_color"}, {"fill_color"},
 {"Edit Drawing Style"}};

如果是Farris轮( 三个转子),则字段名将为:

publicstaticfinalString[][] FarrisNames = newString[][]
 {{"Radius_1"}, {"Frequency_1"}, {"Phase_1"}, {"Radius_2"},
 {"Frequency_2"}, {"Phase_2"}, {"Radius_3"}, {"Frequency_3"}, {"Phase_3"},
 {"generator_steps"}, {"line_width"}, {"line_color"}, {"fill_color"},
 {"Edit Drawing Style"}};

对于SpiroJ形状,常规 SpiroWrite.convertspiroJParms() 将对形状的类型进行最终确定。 如果x-和y 方向的频率和幅度相同,则SpiroJ形状可以能与标准的are对象相同。 在这种情况下,对象将被定向到标准的spirograph处理例程 getspiroShape,因为它专门处理这个形状。 否则,它将被常规 getspiroJShape 处理,它在本质上更通用。 对于Farris轮,这一点没有决定,因为它们必须分别处理,在例程 getFarrisShape 中。

我们现在假设Bezier渲染方法已经被选择。 在第一篇文章中,将贝塞尔曲线拟合在一起的过程与第一篇文章相同,但是需要描述。

为SpiroJ形状选择t 值

SpiroJ对象遵循以下公式:

x = rx1 cos(wx1t) + rx2 cos ( wx2t )
y = ry1 sin(wy1t) + ry2 sin ( wy2t )

一般来说,这个对象不会有任何旋转对称,而且也没有简单的方法来分类这些形状。 因此,我们将采用一种非常简单的通用方法。 主要要求是,start-和端点之间的arc 角度永远不能被允许为 90度的GREATER。 为了达到这一目的,我们将具有垂直坡度的所有点以及水平坡度和所有弯点均适合。 垂直斜率约束导致这里公式为t:

rx1 wx1 sin(wx1t) + rx2 wx2 sin(wx2t) = 0( 1 )

水平斜率约束导致了以下公式:

ry1 wy1 cos(wy1t) + ry2 wy2 cos(wy2t) = 0

拐点按如下方式确定: 在第一篇文章的Eq.(3) 中,我们有一个曲率表达式。 将这里设置设置为零将产生以下约束: 这将导致以下公式为t:

rx1 ry1 wx1 wy1 ( wy1 sin(wx1t) sin ( wy1t ) + wx1 cos(wx1t) cos ( wy1t ) ) +
rx1 ry2 wx1 wy2 ( wy2 sin(wx1t) sin ( wy2t ) + wx1 cos(wx1t) cos ( wy2t ) ) +
rx2 ry1 wx2 wy1 ( wy1 sin(wx2t) sin ( wy1t ) + wx2 cos(wx2t) cos ( wy1t ) ) +
rx2 ry2 wx2 wy2 ( wy2 sin(wx2t) sin ( wy2t ) + wx2 cos(wx2t) cos ( wy2t ) ) = 0( 2 )

这个方程与以前的方程不同,因为它包含两个三角函数的乘积。 但是,我们可以简化它,使它不包含任何产品,使用以下标识:

sin(A) sin ( B ) = ( cos ( A - B ) - cos ( A + B ) )/2( 3 )
cos(A) cos ( B ) = ( cos ( A - B ) + cos ( A + B ) )/2

我们现在需要一个通用的规划求解,可以将任意数量的正弦和余弦波和为零。 以前的文章使用 SpiroCalc.calc_cos_t()SpiroCalc.calc_sin_t() 来解决类似的方程,但是它们对这个问题来说太专业了。 为了求解这些方程,我们需要一个表示方程数据的一般方法: 我们使用数组

rotors = newdouble[][] {{amplitude, frequency, phase},.. .};

其中第一个下标指转子的索引号。 例如 Eq.(2) 将产生八个来自八个唯一频率的。 第二个下标引用三个参数: 振幅。频率和相位;相位将以弧度指定,主要用于区分正弦和余弦波。 使用这里表示法,Eq.(1) 表示:

rotors = newdouble[][] {{rx1*wx1, wx1, -Math.PI/2},
 {rx2*wx2, wx2, -Math.PI/2}};

使用 Eq.(3), 简化后的Eq.(2), 表示如下:

rotors = newdouble[][] {{rx1*ry1*wx1*wy1*(wx1 + wy1)/2, wx1 - wy1, 0},
 {rx1*ry1*wx1*wy1*(wx1 - wy1)/2, wx1 + wy1, 0},
 {rx1*ry2*wx1*wy2*(wx1 + wy2)/2, wx1 - wy2, 0},
 {rx1*ry2*wx1*wy2*(wx1 - wy2)/2, wx1 + wy2, 0},
 {rx2*ry1*wx2*wy1*(wx2 + wy1)/2, wx2 - wy1, 0},
 {rx2*ry1*wx2*wy1*(wx2 - wy1)/2, wx2 + wy1, 0},
 {rx2*ry2*wx2*wy2*(wx2 + wy2)/2, wx2 - wy2, 0},
 {rx2*ry2*wx2*wy2*(wx2 - wy2)/2, wx2 + wy2, 0}};

这里数据被发送到 SpiroJCalc.solve_cos_t(double r[][], double t0) 使用牛顿Raphson方法可以找到根。 这里遇到的主要问题是 root的选择,的初始估计。 有很多根要找到,并且总是有可以能收集到错误的root,并意外丢失一个希望的。 我们通过使用一定数量的overkill来避免这种情况: 对于每个转子( 余弦波),对于这个单个转子的每个单独的零个转子,我们执行一个单独的调用。 如果它的他转子没有变得太快,这将使我们合理地接近实际的root。 调用求解器的代码为:

for (i = 0; i <rotors.length; i++)
 if ((Math.abs(rotors[i][0])> TOL) && (Math.abs(rotors[i][1])> TOL))
 for (int j = 0; j <Math.round(Math.abs(2*rotors[i][1])); j++)
 N = main.insert_t_value(N, N, t, 
 solve_cos_t(rotors, Math.PI*(j + 0.5)/Math.abs(rotors[i][1])));

没有理论保证这个方法会找到所有的根,但目前尚未失败。 然而,它所做的是产生大量重复的。冗余的。根的。 这些被检测并被移除 SpiroJCalc.sort_t_values(double[] t, int n)

给定一组t 值作为拟合点,我们可以生成贝塞尔曲线与以前相同,使用 SpiroJCalc.getBezier() 例程。 下面是使用SpiroJ文件 holub.xml的典型SVG结果。 图表还显示了红色的拟合点。 注意,与前一篇文章相比,这里的策略有一个根本差异: 以前,当使用标准的螺旋spirograph形状时,我们知道 priori symmetry对称性,意味着有多少独特的波瓣,所以我们可以根据需要 fit lobe,然后根据需要重复结果。 对于当前形状,我们不知道对称可以能或者可以能是什么,所以我们必须显式计算整个对象。 这不是普通SpiroJ形状的一个问题,但它可以能是下面讨论的Farris轮,它的实是具有旋转对称。 即使我们不知道对称度的程度,我们也需要开发一种自动对称对称的方法。 但是首先,我们需要做一个侧面的旅行来研究Lissajous形状的一个特殊特征。

李萨如定点处的曲率

上述SpiroJ形状的方法适用于没有对称的一般形状。 然而,在Lissajous图形的特殊情况下,它会反转它的运动方向。 这就产生了一个必须特别处理的固定点。 下面给出了一个例子。 这显示了两个叠加的数字: 第一个有 wx1 = 1,wy1 = 3,导致运动从不停止的闭合图形。 第二个是 wx1 = 2,wy1 = 3,这导致运动停止并反转,使它的看起来像一条曲线,使它看起来像一条曲线,在每个端点上都有固定点。 这些固定点与正常spirograph中遇到的点不同。 在正常旋转中,平稳点实际上是在|c|等于|b|的情况下曲率的一个奇异点。 根据c 与b的相对大小,此时的spirograph曲率可能是 ±∞。 另一方面,平稳点的曲率很好,但必须考虑分析,因为分子和分母都接近零。 我们可以使用简化的表示法来表示Lissajous图形,而不损失通用性:

x = rx cos ( wxt )
y = sin ( wyt )

这要求:


M 和N 是任意整数。 我们在这些方程中,对频率有一个新的约束:

2Nwy = ( 2m + 1 ) wx( 4 )

对于任意的wx和 wy,都不能解这些方程。 例如如果wx为奇数,则 Eq.(4) 永远不会有解决方案。 这会产生


κstationary = ( -1 ) N+M rx wx2 wy2 (。wx2 - wy2)/3/(rx2 wx4 + ry2 wy4 ) 3/2

我们只保存了前导术语,它们的分子和分母都是 Δt3,因这里取消了 Δt aproaches零。 我们在 SpiroJCalc.getBezier() 中使用这个结果来计算固定点处的曲率。 这会产生如下所示的蓝色曲线,例如 wx1 = 2,wy1 = 3。

求Farris轮曲率的极值

因为它通常会拥有一些旋转对称,所以我们现在的拟合方法并不显示对称的对称性。 我们的方法目前适用于所有垂直和水平斜坡。 然而,如果Farris轮子有六个折叠对称,如果我们用垂直和水平斜率拟合一个波瓣,那么rotate旋转 fitted fitted rotate rotate rotate rotate original original degrees degrees degrees degrees degrees degrees degrees degrees。 这是我们希望对对称对称的意义,但问题是,对称性的确切性是不明确的。 同样,对于标准spirograph形状,对每个子类别进行分类并开发特殊的技术也很容易;对于Farris轮,这可能不太可行。 因这里,我们需要使用形状测量,它会自动保存对称性,如果它存在,不会显式地。 一个度量值是特性曲率,它与方向无关。 因此任何从曲率中得到的度量也将是不变的。 这使得我们可以考虑使用最大曲率或者最小曲率点作为拟合点。

我们首先搜索四种类型的拟合点:

  • 曲率最大绝对值的点
  • 曲率最小绝对值
  • 拐点( 零曲率)
  • 正切为与最大曲率点垂直的90度的点

注意,这一套标准基本上与hypotrochoid相同,因为对于标准 spirograph,最大曲率的点也是最大曲率的一点,也就是最小半径的点。 因此,目前的标准集是以前用于标准方法的方法的一个自然概括。

为了计算转弯点,我们使用相同的方法,同样使用 SpiroJ,并用 Eq.(3), 获得转子集:

double Sum23 = r1*r1*w1*w1*w1 + r2*r2*w2*w2*w2 + r3*r3*w3*w3*w3;double A12 = r1*r2*w1*w2;double A23 = r2*r3*w2*w3;double A31 = r3*r1*w3*w1;
rotors = newdouble[][] {{Sum23, 0, 0},
 {A12*(w1 + w2), w1 - w2, phi1 - phi2},
 {A23*(w2 + w3), w2 - w3, phi2 - phi3},
 {A31*(w3 + w1), w3 - w1, phi3 - phi1}};

然后,通过设置 dκ/dt = 0获得最大/最小曲率的点,从而生成以下公式:

( 5 )

这里表达式中的单个术语由以下各项给出:


+ r1 r2 w2 ( w1 + ) cos ( θ12 )
+ r2 r3 ( + w3 ) cos ( θ23 )
+ r3 r1 w3 ( w3 + w1 ) cos ( θ31 )


+ 2 r1 r2 w2 cos ( θ12 )
+ 2 r2引脚电流( θ23 )
+ 2 r3 r1 w3 cos ( θ31 )

其中 θij = ( wi wj ) + phii - phij。 采用这些术语的第一导数并取代 Eq.(5),得到t的一个方程,可以用 Eq.(3) 构成二十二个转子。 转子有些太复杂,不能在这里显示,但是它们在 FarrisCalc.get_t_values() 中明确定义,并在 SpiroJCalc.solve_cos_t() 中解决。

最后,通过扫描之前获得的拟合点,搜索角度变化大于10度,得到相对于最大曲率点旋转 90度的点;。 如果发生,则计算最高曲率点的切角,并且搜索开始于与它的任意边垂直的拟合点。 作为初始估计,我们使用t 值,即最高曲率点处t 值的平均值及其在两侧的最近邻值。 到目前为止,这种初始估计总是导致所期望结果的收敛。 下面是典型的Farris轮子,利用( -2,5,19 ) 频率产生一个 7-fold 旋转对称。 拟合点以红色显示。 注意,它显示了所需的对称性,一个叶的拟合点可以由( 1/7 ) 旋转,直接在下叶的拟合点上。

Farris轮子中的固定点

由于曲率不能在这些点上定义,Farris轮也可以显示需要特别处理的尖端。 当运动暂时静止时这些点就会发生。 它们类似于 |c| = |b| ( 例如 ) 时正常spirograph中发生的点。 当hypotrochoid变成马蹄形时)。 然而对于Farris车轮,这些固定点可能在不同半径范围内,而不仅仅是单个半径,因为我们在Farris车轮上有一个更自由度。 描述这些点的最佳方法是计算Farris轮子上某一点的速度的行为。 Farris轮点的速度矢量本身也是一个Farris轮,具有新的参数。 如果原始Farris轮子点的位置是由类型的一组转子给出的:

rotors = newdouble[][] {{r1, w1, phi1},.. .};
( 6 )

然后,通过该类型的转子矢量和给出同一点的速度矢量:

rotors = newdouble[][] {{r1*w1, w1, phi1 + Math.PI/2},.. .};
( 7 )

利用这种新的速度表示,我们可以看到,在原来的Farris轮子中,当三个速度矢量,加到零时,就会出现一个静止的点。 这在理论上并不总是可能的。 例如,如果我们以 |r1 w1|> |r2 w2|> |r3 w3|的方式重命名三个转子,则理论上只能为零。

|r1 w1| <|r2 w2| + |r3 w3|。

这定义了轮子半径可以具有的最大值和最小值,假定我们保持了另外两个半径固定。 如果不满足这个不等式,那么就可以计算出它的实际情况下的特定条件。 特定条件将显示为对相移phii的约束,例如:

r12 w12 = r22 w22 + r32 w32 + 2 r2 r3 ( phi2 - phi3 )

类似方程式也适用于( phi1 - phi2 ) 和( phi3 - phi1 )。 这三个约束都可以看作是余弦定律的表达式,它适用于三个向量和的向量和,这些矢量和等于零的向量和。 利用这些方程,我们现在可以为Farris车轮建立一个尖点检测器来确定 ,当一个点存在时,它就。 这是在常规 FarrisCalc.calc_t_cusp() 中完成的。 我们需要预测这些尖端,因为这个时候我们可能会得到无限或者未定义的曲率。 如果检测到尖点,那么我们将在这里时将贝塞尔臂长度设置为零,与标准的,完全相同。

在正常情况下,这种类型的存在是非常少见的,但是很有趣地看到它们的外观。 下图显示了一个基于一系列 32图像的动画,每个图像都包含有点。 它们是从顶部的Farris轮上生产出来的,带有频率( 1,7,-17 ),最小的轮子半径,从最小半径到最大半径。 为了确认贝塞尔曲线计算在这些情况下仍然表现良好,这只是做了一个测试。

6. 今后工作

用于Farris轮的方法比SpiroJ形状或者标准旋转形状使用的方法更适用。 通过将曲率最大化/最小化代码应用到这里所述的所有形状,可以能很有可以能。 但是,这将涉及大量已经正常工作的代码重写,因这里我认为我将作为一个感兴趣的读者。



文章标签:lis  SVG  SPI  FIG  far  Wheel  Figure  Figures  

Copyright © 2011 HelpLib All rights reserved.    知识分享协议 京ICP备17041772号-2  |  如果智培  |  酷兔英语  |  帮酷