原作:Keith Peters https://www.bit-101.com/blog/2022/11/coding-curves/

译者:池中物王二狗(sheldon)

源码:github: https://github.com/willian12345/coding-curves

第十章 螺旋(SPIRALS)

曲线艺术编程系列第 10 章

来聊聊螺旋线。

螺旋非常像圆,它是一组点到定圆中心点的距离关系。与圆不同的是,圆的点与中心点距离固定不变,而螺旋上的点与中心点距离是变化的。给定点与中心点的距离通常是由一个基于两点之间的角度计算得到。所以你需要有函数传入角度返回半径。然后你可以通过半径与角度找到那个角度上对应的点。有很多不同的螺旋公式,会得到不同形态的螺旋。让我们从最基础的一个开始。

注:圆有时候也被称为“退化”的旋转。“退化”这词在这里没有贬义,只是说圆遵循螺旋的“规则”但又不是通常我们想象中的螺旋。就像一个三角形其中某一边的长度为 0。所有三角形数学计算通常都能正常工作,但在我们眼中它却成了一条线。

阿基米德螺旋

如你所见,每个圆的半径都会定量增加。下面是这个螺旋线的公式:

r = a * t

此处,t 是弧度 a 是某个常量,上图中 a 设为 5。 两者相乘的结果即是这个 t 角度上的半径值。如果将 6 增加到 10,我们会得到如下 :

现在如你看到的这样,常量 a 决定了螺旋内每个圆之间的间距。在这个例子中,螺旋都旋到 canvas 边界外了。

当我们绘制螺旋时,我们首先需要决定绘制多少个圈。要绕多少圈?如果 t 从 0 到 2 * PI,我们得到的是一个圈:

你可以看到这个螺旋有一个圈,螺旋曲线从中心开始向外扩展。三个圈表示 t 从 0 到 2 * PI * 3。

有了上面这些,我们可以将它们合在一起创建一个螺旋线的调式器(playground),像这样:

width = 800
height = 800
canvas(width, height)
translate(width / 2, height / 2) cycles = 10
res = 0.01 for (t = res; t < 2 * PI * cycles; t += res) {
r = archimedean(5, t)
x = cos(t) * r
y = sin(t) * r
lineTo(x, y)
}
stroke() function archimedean(a, t) {
r = a * t
return r
}

比如我们希望有 10 个圈, 循环结束点就是 2 * PI * cycles。调用 archimedean 螺旋函数,传入 5 作为它的常量, t 作为弧度。它会返回给我们一个半径值,我们可以用它和弧度计算出对应点的 x, y 坐标并用线连接。

我使用的绘图 api , 角度正向增加是顺时针方向。所以这个螺旋从中心顺时针绕出来。这可能与你正在使用的编程平台内表现有所不同。这取决于你使用的绘图 api 如何处理角度方向。为了翻转旋转方向,有几种方式:

A. 用 scale 翻转 canvas

scale(1, -1)

B. 改变代码创建点的方式,使其中一个轴为负 例如:

x = cos(t) * r
y = -sin(t) * r

C. 将循环的方向变反

for (t = -res; t > -2 * PI * cycles; t -= res) {

任意一种方式都可以让你的螺旋方向改变。

还有最后一件事...

代码中值得注意的一点是循环起始点是 res 而不是 0

for (t = res; t < 2 * PI * cycles; t += res) {

想知道为我为啥这么做,我们需要从下一种螺旋线寻找答案。

双曲线螺旋 Hyperbolic Spiral

这个螺旋与第一个螺旋看起来非常不同。上个螺旋内每个圈是等距的。你可能会说每个圈之间的距离都增长了,还是等等先别下结论。

下面这个函数是这个螺旋准备的:

function hyperbolic(a, t) {
r = a / t
return r
}

也没有特别大的不同。我们用除代替了乘。上图我传入了 1000 。如果我将值降到 10, 我们会得到一个很小的螺旋线像这样:

这就很明显了 for 循环以 res 开始而不是 0, 如果是 0,那么就会出现除数为 0 的这种情况,这会导致某些问题。也许会崩溃,或者半径为 NaN (not a number) 值, 这没什么用。所以我们将起始值设为非 0。

另一个值得注意的事是螺旋线的旋转方向。上图我画了 20 个圈。如果降为 5(a 变回 1000),将得到:

你现在可能看到随着 t 值增长螺旋变的很大但,但增长的速度慢小了。当有 100 个圈,螺旋中心位置开始有些堵了。

就如我之前所说,咋一看每个圈半径都在增长,但你现在可以观察到实际上是开始时很大,但是圈与圈之间的半径值逐渐变小了。

费马螺线

这种螺旋线很漂亮。这是公式:

function fermat(a, t)  {
r = a * pow(t, 0.5)
return r
}

这里我们将 a 乘以 t 的 0.5 平方。假定你的编程语言内置了 pow 函数计算第一个参数的第二个参数次幂。根据你使用的编程语言,它可能是这样:

r = a * (t^0.5)

or

或这样:

r = a * (t**0.5)

上一张图 a 设为了20 绘制了 20 圈。 绘制 10 圈 结果如下:

可以看到螺旋线是从内向外绘制的。 一开始圈与圈之间的间距比较大,但越往外面圈的间距越来越小,还超出边界了

圈数改回 20 个圈 a 设为 40:

我们能看到间距有所增加。

100 个圈 a 设为 15。

最终,每圈之间已很难有间距。而我们得到了一些有趣的摩尔纹图案

连锁螺线 Lituus Spiral

它看起来像双曲螺线,但公式却更接近于费马螺线:

function fermat(a, t)  {
r = a * pow(t, -0.5)
return r
}

我们公需要将指数中的 0.5 改为 -0.5。上图螺旋线绘制了 20 个圈 a 的值为 500。 下面是 10 个圈

你可以看到这是另一种向内绘制的螺旋线。 改回 20 圈, a 设为 50,得到:

词 “Lituus” 原意是弯曲的杖,弯曲的魔法棒或角。上图很好的解析了它名字的由来。

将 a 升到 1000, 可以得到:

可以这样讲,a 值越低,螺旋吞噬进中心越快。

还有很多类型的螺旋线,来,继续!

对数螺旋 Logarithmic Spiral

它有点像反费马螺旋。一开始间隔挺小,越往外越大。公式:

function logarithmic(a, k, t)  {
r = a * exp(k * t)
return r
}

公式看起来比之前遇到过的要复杂一点。t 前有两个参数。我们先从 exp 函数开始学习。

我们实际上在谐振波图(Harmonographs) 这一章见过它. 回顾一下,有一个数学常数 e ,也称欧拉数。它的值大概是 2.71828。 当我们在与对数打交道时,常将 e 乘方。所以很多数学程序库会直接提供一个函数, 常被命名为 exp。 举个具体的例子,在 javascript 中,有常用 e, Math.E。为了计算 e 的 2 次幂,就得像下面这样做:

Math.pow(math.E, 2)

但其实已经有一个叫 exp 的函数了,你可以直接用:

Math.exp(2)

代码中的公式也得改成,

r = a * exp(k * t)

我们用 a 乘以 e 的 k * t 次幂。上面的图,我得将 a 设成 0.5 且 将 k 设为 0.05。

如果将 a 变为 1,我们将得到一个相当大的螺旋线,虽然它们看起来很像。

a 设为 0.24 后:

还是挺像的,但更小一点儿了。

将 a 重置为 0.5 并且将 k 升到 0.1

你可以看到扩张的更快了。将 k 设为 0.04 它对结果的影响远超我的预期

黄金螺旋(Golden spiral)

这类螺旋增长率叫“黄金比例”,大概接近 1.618。它的公式是:

function golden(t) {
r = pow(PHI, 2 * t / PI)
return r
}

首先,这个函数除了 t 之外没有其它参数。 PHI 黄金比例值是硬编码在函数内的。很多数学程序库都有黄金比例这个内建常量。如果你没有,将这近似的设置为:

PHI = 1.61803

或者你想更精确一点,你可以像下面这样写得到精确值:

PHI = (1 + sqrt(5) ) / 2

然后放在某个地方按需引用即可。

你很可能已经见过这种螺旋图了:

By Romain – Own work, CC BY-SA 4.0, https://commons.wikimedia.org/w/index.php?curid=114415511

这实际是一个斐波那契螺旋。方块大小是下两个方块之和且每个方块内的曲线是一个角为中心 90 度生成弧线。它不是精确的黄金螺旋,但很接近了。

还有更多

仔细阅读这个螺旋线列表,也许你能找到其它更有趣的

https://en.wikipedia.org/wiki/List_of_spirals

还有:

https://mathworld.wolfram.com/topics/Spirals.html

螺旋角(Spirangles)

在写这一章的时候我偶然发现了这个词 (Spirangles)。本质上它是由直线线段组成的螺旋。通过改变每个线段的角度,你可以将它们组成不同的形状。

在已有的基础上实现它非常容易。上面中有个例子使用的是 archimedean 函数,a 为 3,并且 cycles 为 20。 窍门是减低分辨率 res 上图的效果我是将 res 设为了发下值:

res = PI * 2 / 3

Now on each step of the for loop, t will increase by one-third of a circle. Here are some others, dividing by 4 and 5.

现在,循环中每一步,t 会增加 1/3 个圆。 以下是其它, 除 4 和 除 5 的效果:

窍门你已经知道了。你可能想在其它方法上试试。其中一些通过这样修改后变的让人相当满意。

葵花螺旋(Sunflowers)

若是没有讨论葵花螺旋,那么螺旋这一篇就不算完整。试试用葵花,斐波那契与螺旋的组合搜索,你能得到巨量的阅读材料与漂亮图片。我的意思是,除了绘制葵花螺旋这类有趣的螺旋线外,其它的螺旋我就让你自己去探索了。黄金比例在这个图中是天生的,图是这样:

你可以看到不止一条螺旋,有许多螺旋以不同角度进进出出。我常用下面的代码实现(还是以伪代码展示):

width = 800
height = 800
canvas(width, height)
translate(width / 2, height / 2) count = 1000
for (i = 0; i < count; i++) {
percent = i / count
size = 14.0 * percent
r = 380.0 * percent
t = i * PI * 2 * PHI
x = cos(t) * r
y = sin(t) * r
circle(x, y, size)
fill()
}

我们用变量 count 代表想要绘制多少“葵花籽”。这里 count 设为了 1000.

然后我们循环 用 i / count 得到一个百分比值。

变量 size 是每颗“种子”的半径。当 i 越接近 count 时,percent 的值会越接近 1,因此 size 的值也会越接近最大值 14。

同样 r 用于表示种子分布的半径。它最高值是 380, 仅比 canvas 宽度的一半小一点点。

我们用 t = i * PI * 2 * PHI 计算出 t , 它就是神奇的葵花斐波那契公式。说真的,你想知道更多,那就认真看一下。有了角度和半径,我们就可以得到 x 和 y 坐标,然后根据种子自己的大小绘制种子了。

到目前为止我想说的螺旋线就这么多了。下回见!

本章 Javascript 源码 https://github.com/willian12345/coding-curves/blob/main/examples/ch10


博客园: http://cnblogs.com/willian/

github: https://github.com/willian12345/

曲线艺术编程 coding curves 第十章 螺旋曲线(SPIRALS)的更多相关文章

  1. 编程艺术第十六~第二十章:全排列/跳台阶/奇偶调序,及一致性Hash算法

    目录(?)[+]   第十六~第二十章:全排列,跳台阶,奇偶排序,第一个只出现一次等问题 作者:July.2011.10.16.出处:http://blog.csdn.net/v_JULY_v. 引言 ...

  2. JavaScript DOM编程艺术-学习笔记(第十章、第十一章)

    第十章 1.动画中,因为js的效率高,所以看不见过渡效果 2.题外话:①国外人写书,总是先感谢一遍亲朋好友,最后感谢自己的家人. 3."除非允许用户'冻结'移动的内容,否则应该避免让内容在页 ...

  3. java并发编程实战:第十章----避免活跃性危险

    在安全性和活跃性之间通常存在着某种制衡 一.死锁 定义:在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,线程AB均不会释放自己的锁,那么这两个线程将永远地等待下去 在数据库系统的设中 ...

  4. 编程之美-1.1 CPU 曲线

    解法二: import time def cpu_curve(): busyTime = 50 # 50 ms的效果比10ms的效果要好 idleTime = busyTime startTime = ...

  5. 艾编程coding老师课堂笔记:java设计模式与并发编程笔记

    设计模式概念 1.1 什么是设计模式 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路.它不是语法规定,而是一套用来提高代码可复用性.可维护性.可读性. ...

  6. 艾编程coding老师课堂笔记:SpringBoot源码深度解析

    思想:有道无术,术尚可求,有术无道,止于术! Spring 开源框架,解决企业级开发的复杂性的问题,简化开发 AOP, IOC Spring 配置越来多,配置不方便管理! Javaweb---Serv ...

  7. 《Linux命令行与shell脚本编程大全》第十章 使用编辑器

    主要介绍vim, nano, emacs,KWrite,Kate,GNOME 10.1 vim Unix系统最初的编辑器 10.1.1检查vim软件包 先搞明白你所用的Linux系统是哪种vim软件包 ...

  8. 《Linux命令行与shell脚本编程大全》第二十章 正则表达式

    20.1 什么是正则表达式 20.1.1 定义 正则表达式是你所定义的模式模板.linux工具可以用它来过滤文本. 正则表达式利用通配符来描述数据流中第一个或多个字符. 正则表达式模式含有文本或特殊字 ...

  9. 《Java并发编程实战》第十章 避免活跃性危急 读书笔记

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/love_world_/article/details/27635333 一.死锁 所谓死锁: 是指两 ...

  10. 艾编程coding老师:深入JVM底层原理与性能调优

    1. Java内存模型JMM,内存泄漏及解决方法:2. JVM内存划分:New.Tenured.Perm:3. 垃圾回收算法:Serial算法.并行算法.并发算法:4. JVM性能调优,CPU负载不足 ...

随机推荐

  1. ByteHouse MaterializedMySQL 增强优化

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 前言 社区版 ClickHouse 推出了MaterializedMySQL数据库引擎,用于将 MySQL 中的表 ...

  2. 我的合肥 .NET 俱乐部线下活动之旅

    一:背景 我是一个性格比较内向的人,天然抵触这种线下面对面的大型活动,我害怕上台之后紧张到语无伦次(有过类似经历),越语无伦次又会让我更紧张,刚好谋得程序员这种工作又特别适合我这种性格的人,所以没有刻 ...

  3. DOM属性节点加其他节点的操作

     节点属性 nodeType 返回值为数值                 节点类型(nodeType)    节点名字(nodeName)    节点值(nodeValue)        元素节点 ...

  4. 用BingGPT写一首勉励自己的诗

    觉得写的还挺有意思,所以记录一下,祝自己在今后的生活中努力学习,学有所成 勤学不辍志,博览群书知. 海纳百川理,山高自有路. 勿以时日长,惟以功夫深.

  5. 端口转发、Http Tunnel、内网穿透

    原文链接:https://www.yuque.com/tec-nine/architecture/mgxc71 SSH 命令帮助 命令行选项有: -a 禁止转发认证代理的连接. -A 允许转发认证代理 ...

  6. easy-excel读取远程地址获得文件进行上传

    背景 作为一个快五年的程序员,一直以来还没有自己维护过自己的技术栈,最近也是有时间,所以也是下定决心,从头开始,一步一步的夯基础.最近在系统化的学习easy-excel,今天遇到了一个问题,特意记录一 ...

  7. Helm 安装 Kubernetes 监控套件

    Helm 安装 Grafana Prometheus Altermanager 套件 安装helm # 安装helm工具 curl -fsSL -o get_helm.sh https://raw.g ...

  8. python入门教程之二十二网络编程

    Python 提供了两个级别访问的网络服务.: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法. 高级别的网络 ...

  9. vs的常用配置【以及vs常用的快捷键】

    1.颜色设置 (1) 编译器的主题颜色设置 (2) 字体和颜色设置 (3) 字体大小 更快捷的修改字体大小方式:ctr+鼠标滚轮 2.行号设置 默认就有,不用设置了 3.把解决方案资源管理器移动到左边 ...

  10. v-if与v-show

    v-if的特点 v-if:是真实的条件控制语句,每当我们满足条件的时候就会显示元素,不满足条件的时候不显示元素(不存在元素没有) v-if:有一套自己的条件控制语句v-if; v-else-if; v ...