曲线艺术编程 coding curves 第十三章 超级椭圆与超级方程(Superellipses and Superformulas)
第十三章 超级椭圆与超级方程(Superellipses and Superformulas)
原作:Keith Peters https://www.bit-101.com/blog/2022/11/coding-curves/
译者:池中物王二狗(sheldon)
曲线艺术编程系列 第十三章
在这一章我们将讨论一些有趣的图形。超级椭圆在设计与UI领域非常有用, 有一种特别的超级椭圆被亲切的称为“方圆形”. 一种介于正方形和圆形之间的几何形状,四个角为圆角,边缘呈现出圆润的曲线。 超级方程是超级椭圆的一个扩展。它更复杂,也许没那么有用,但它自身足够有趣。让我们开始探索吧。
超级椭圆 (Superellipses)
有时候你可能需要绘制矩形。这个需求太常见了。在任何图形库中都会有绘制矩形的方法。
但光是矩形看起来比较无聊。四角都有点儿。。方。 你很自然的想要圆角矩形。有些绘图 api 也有绘制圆角矩形的方法,但我们直接手搓一个也不难。在四个角都绘制一个 90 度的 圆弧,并将它们连起来就是圆角矩形了。
事实上,你可以让四个角拥有相对小的半径,这样它看起来会更圆
但你应该不会喜欢这样做。某种程度上讲是对连续边界的打断。一开始都是漂亮的直线,到边角突然又要使用 arc 这种操作。你也许想在圆角处变化的更自然顺滑。
还是得看超椭圆啊。
你可以想象超级椭圆是介于椭圆和矩形的混合体。就像椭圆和其它曲线一样在本系列中,我们认为这个形状拥有一个中心点和变化的半径 t 从 0 至 2*PI。这有别于大部分矩形方法,矩形方法往往用 x,y 为坐标(通常是矩形的 left 和 top 值),还有 width,height。 但你可以按你自己的想法调整此方法。
在数学上有好几种方法用于表达超级椭圆。我们不停的寻找直到找到下面这个可对 t 进行参数化的公式:
x = pow(abs(cos(t)), 2 / n) * a * sign(cos(t))
y = pow(abs(sin(t)), 2/ n) * b * sign(sin(t))
Hmm... 比你预想的要复杂吧?
这里有个简介的公式像这样:
x = pow(cos(t), 2 / n) * a
y = pow(sin(t), 2 / n) * b
但问题在于,此函数只能处理 1/4: 0 到 PI / 2。相比于为每个角跑4次循环, 并计算出 x 和 y,我们用一些好用的数学方法 abs 和 sign 直接计算。
你使用的语言平台一定也有个 abs 方法,它只是直接返回了绝对值。你可以自己简单动手写一个你自己的:
function abs(val) {
if (val < 0) {
return -val
}
return val
}
这个函数总是会返回 0 或正数。
尽管你可能没有 sign 函数。sign 方法传入负数 返回 -1, 传入 正数 返回 +1。 传入 0 返回 0。可以像下面这样实现 sign 函数
function sign(val) {
if (val < 0) {
return -1
}
if (val > 0) {
return 1
}
return 0
}
你可能跃跃欲试像下面这样写:
function sign(val) {
return val / abs(val)
}
这个函数工作的很完美。直到 val 值为 0 时它崩溃了。为了防止崩溃你得进行一下条件判断。
在上面的公式中,代码有些重复或者变量不太清晰的,并且没有用于设置形状位置方法。下面这个函数才是比较有用的:
function superellipse(xc, yc, rx, ry, n) {
for (t = 0; t < PI * 2; t += 0.01) {
c = cos(t)
s = sin(t)
x = pow(abs(c), 2 / n) * rx * sign(c)
y = pow(abs(s), 2 / n) * ry * sign(s)
lineTo(xc + x, yc + y)
}
closePath()
}
大多数路径绘制 api 有 closePath 这样的方法用于最终闭合路径。
好了。让我们用新的超级椭圆重写最开始的圆角矩形。
width = 600
height = 400
canvas(width, height)
// set color to orange however you do that...
// 把颜色设置为桔色,无论你用怎样的方式
superellipse(300, 200, 250, 150, 10)
fill()
相比于简单的实现圆角矩形,你有可能不喜欢这样复杂的代码, 但让我们继续深入探索一下。
最后一个参数 n 值,控制着圆角的曲度。值越高,它越接近于矩形。这是 n 为 20 时:
这是 n 为 4 时:
它看起来像是老式电视机的屏幕形状。
是时候祭出方圆 squirecles 了。
方圆 (Squircle)
一个超级椭圆的 x 和 y 半径相等则被称为超级圆。也被称为方圆。由方和圆组合而成。 一些方圆的状态定义 n 必须为 4。 它看起来会像下图:
这个形状看起来就舒适多了, 而且最近在 UI 设置界也很流行. 它经常被用于 icon 设计, 特别多用于手机设备 app 的启动图标。有时候 n 值不完全是 4 就像上面那样,但非常接近。
这里有一些方圆公式的变种,由 John Cook 编写 https://www.johndcook.com/blog/2022/10/27/variant-squircle/。 顺便说一句它的网站也是相当好的学习资源。我不能说完全理解他讲的,但它确实启发了我探索各种有趣的方法。
让我们回到超级椭圆。
再看看参数 n。 我们已经知道了参数 n 越大,圆角越小。当 n 为 2 时, 有趣的来了。我们直接得到了一个椭圆(或一个圆,如果宽高相等的话)。
当 n 从 2 降到 1 时,我们可以看到圆边开始转变为角了,并且角变的更直了。 下图是 n 设为 1.5:
当 n = 1 ,我们直接得到了钻石形(菱形)。
n 继续减小,四个角开始向内弯曲,下图是 0.75
当为 0.2 时,超级椭圆几乎消失了。
当 n 值越来越小时本质上图形确实会消失不见。
但当 n 为负数时,事情变的更诡异了。 下面是 n 为 -4 时。我不得不将 整个图弄的小一点,以便让你能看到发生了啥 -- 我们得到了一个填充矩形,超出各个角翻转的超级椭圆。
我不确定最后一部分具体有啥用,但效果就是这样。
这就是超级椭圆。在你的图形工具库里它会很有用。让我们继续往下看。
超级公式(Superformulas)
超级公式广义的或者说是超级椭圆的扩展。再一次,半径从 0 到 2*PI 时会变化。半径的方程如下:
这方程是直接从维基百科上拿的。这比自己用键盘打出来要容易的多了。需要把它们转换成伪代码公式,但你应该可以 看到这个公式与超级椭圆很类似。你可以看到,我们从 cosine 和 sine 得到值后再除以某个值,再对其取绝对值后进行乘方。弯弯曲曲的符号是希腊字母 phi, 我们把它称为 t 。 所以 我们可以这样说角度 t 上的半径值是...
我们还有一些其它的变量, m, n1, n2 和 n3 还有 a 和 b 它们会影响 x 和 y 半径。在我的实现中我将其简化成一个半径。所以我将设置 a 和 b 为 1, 这意为着代码中我们可以将其忽略掉并将结果与我们想要的半径相乘。
当然,这还是挺复杂的,我们将这些变量分步计算实现,最后组合在一起。
首先,传入同样的值给 cosine 与 sine 计算, 这样就可以预计算它们。我将称其为 m 对称。
angle = symmetry * t / 4
然后大的圆括号之间还有两项需要添加上。我们将两项都计算出来:
term1 = pow(abs(cos(angle), n2)
term2 = pow(abs(sin(angle), n3)
再强调一次,我们将 a 和 b 假设为 1, 这样就可以忽略它们了。
接下来,我们把这些项结合到最后的公式里得到最终的半径值。 记住我们需要乘以总的半径:
r = pow(term1 + term2, -1/n1) * radius
最后,用半径与角度得到下一个绘制点的位置:
x = xc + cos(t) * r
y = yc + sin(t) * r
全部合在一起后得到下面的方法:
function superformula(xc, yc, radius, symmetry, n1, n2, n3) {
for (t = 0; t < 2 * PI; t += 0.01) {
angle = symmetry * t / 4
term1 = pow(abs(cos(angle), n2)
term2 = pow(abs(sin(angle), n3)
r = pow(term1 + term2, -1/n1) * radius
x = xc + cos(t) * r
y = yc + sin(t) * r
lineTo(x, y)
}
closePath()
}
好了, 我们能拿这一坨做什么呢?这些参数分别用来控制什么?
当我面对类似这样的代码时,我通常先找到有简单联系且固定的参数,看看这个参数到底改变的是什么。然后再看另一个参数,最后再看可变参数的影响。幸运的是,超级公式的维基百科页面给了我们很好的图示,我们可以从这点入手:
上图中的每张图代表着四个不同参数数值 m(也被称为对称), n1, n2 和 n3。
下图是 对称为 3, 所有 n 参数设为 1 的结果:
这是对称为 5:
这是对称为 8:
现在保持称为 8, 让我们把 n1 设为 10:
n1 降到 3:
再到 1 以下,降到 0.2:
所以由此得知 n1 用来控制形状各个边的凹凸。
好的,现在我们将 m 固定为 8 把 n1 调回到 1。然后将 n2 升至 1.5:
仍然得到了 8 个节点, 间隔的节点有点儿圆,间隔之间的节点有点儿尖。让它更明显一点,设 n2 = 2:
当 n2 = 5, 圆角节点开始翻倍了,尖角节点渐渐降低了
下图是 n2 = 10。我不得不将半径减小,因为形状超出 canvas 边界了:
最后,将 n1 和 n2 都变回 1,我们试试将 n3 设为 3:
它和参数 n2 的影响差不多,只是节点是相反的变化,有点儿像整个形状转了 45 度。
到这里,已经清楚这几个参数的作用了,试试给这几个参数随机值:
m=16, n1=0.5, n2=0.75, n3=2
m=32, n1=0.9, n2=0.2, n3=-0.3
当然你不用像我这样设置随机参数。你自己试试用不同的值。可以用大的数,小的数,小数,负数,质数,能被其它参数整数或不能整的数。看看会发生什么。
你应该记得,我们把 a 和 b 从原公式中去掉了用了单一的半径。你也许想把它们重加入公式用于创建椭圆的超级公式。维基百科中也给了,把 m 分成 y 和 z:
这允许你可以创建更复杂的形状。
我试着用不同的参数创建形状,下图是我创建出的比较喜欢的一个:
我把它作为课后练习留给读者实现。
本章 Javascript 源码 https://github.com/willian12345/coding-curves/blob/main/examples/ch13
博客园: http://cnblogs.com/willian/
github: https://github.com/willian12345/