曲线艺术编程 coding curves 第十章 螺旋曲线(SPIRALS)
原作:Keith Peters https://www.bit-101.com/blog/2022/11/coding-curves/
译者:池中物王二狗(sheldon)
第十章 螺旋(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)的更多相关文章
- 编程艺术第十六~第二十章:全排列/跳台阶/奇偶调序,及一致性Hash算法
目录(?)[+] 第十六~第二十章:全排列,跳台阶,奇偶排序,第一个只出现一次等问题 作者:July.2011.10.16.出处:http://blog.csdn.net/v_JULY_v. 引言 ...
- JavaScript DOM编程艺术-学习笔记(第十章、第十一章)
第十章 1.动画中,因为js的效率高,所以看不见过渡效果 2.题外话:①国外人写书,总是先感谢一遍亲朋好友,最后感谢自己的家人. 3."除非允许用户'冻结'移动的内容,否则应该避免让内容在页 ...
- java并发编程实战:第十章----避免活跃性危险
在安全性和活跃性之间通常存在着某种制衡 一.死锁 定义:在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,线程AB均不会释放自己的锁,那么这两个线程将永远地等待下去 在数据库系统的设中 ...
- 编程之美-1.1 CPU 曲线
解法二: import time def cpu_curve(): busyTime = 50 # 50 ms的效果比10ms的效果要好 idleTime = busyTime startTime = ...
- 艾编程coding老师课堂笔记:java设计模式与并发编程笔记
设计模式概念 1.1 什么是设计模式 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路.它不是语法规定,而是一套用来提高代码可复用性.可维护性.可读性. ...
- 艾编程coding老师课堂笔记:SpringBoot源码深度解析
思想:有道无术,术尚可求,有术无道,止于术! Spring 开源框架,解决企业级开发的复杂性的问题,简化开发 AOP, IOC Spring 配置越来多,配置不方便管理! Javaweb---Serv ...
- 《Linux命令行与shell脚本编程大全》第十章 使用编辑器
主要介绍vim, nano, emacs,KWrite,Kate,GNOME 10.1 vim Unix系统最初的编辑器 10.1.1检查vim软件包 先搞明白你所用的Linux系统是哪种vim软件包 ...
- 《Linux命令行与shell脚本编程大全》第二十章 正则表达式
20.1 什么是正则表达式 20.1.1 定义 正则表达式是你所定义的模式模板.linux工具可以用它来过滤文本. 正则表达式利用通配符来描述数据流中第一个或多个字符. 正则表达式模式含有文本或特殊字 ...
- 《Java并发编程实战》第十章 避免活跃性危急 读书笔记
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/love_world_/article/details/27635333 一.死锁 所谓死锁: 是指两 ...
- 艾编程coding老师:深入JVM底层原理与性能调优
1. Java内存模型JMM,内存泄漏及解决方法:2. JVM内存划分:New.Tenured.Perm:3. 垃圾回收算法:Serial算法.并行算法.并发算法:4. JVM性能调优,CPU负载不足 ...
随机推荐
- android使用---->常用组件1
在TextView中创建空心文字 <TextView android:layout_width="wrap_content" android:layout_height=&q ...
- 机器学习算法(九): 基于线性判别模型的LDA手写数字分类识别
1.机器学习算法(九): 基于线性判别模型的LDA手写数字分类识别 1.1 LDA算法简介和应用 线性判别模型(LDA)在模式识别领域(比如人脸识别等图形图像识别领域)中有非常广泛的应用.LDA是一种 ...
- jason数组实现页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Java BasePooledObjectFactory 对象池化技术
Java GenericObjectPool 对象池化技术--SpringBoot sftp 连接池工具类 一个对象池包含一组已经初始化过且可以使用的对象,而可以在有需求时创建和销毁对象.池的用户可以 ...
- 人人都学会APP开发 提高就业竞争力 简单实用APP应用 安卓浏览器APP 企业内部通用APP制作 制造业通用APP
安卓从2009年开始流程于手机.平板,已经是不争的非常强大生产力工具,更为社会创造非常高的价值, 现在已经是202X年,已经十几年的发展,安卓平台已经无所不在. 因此建议人人都学学APP制作,简易入门 ...
- RDIFramework.NET开发框架编码管理助力业务编码的自动处理
1.概述 几乎每一个企业应用中不可避免的都会涉及到业务编码规则的问题,比如订单管理中的订单编号,商品管理中的商品编码,项目管理中的项目编码等等,针对这一系列的编码如果直接写在程序中,不仅复杂,代码重复 ...
- 基础常用API总结2
String java.lang包下 返回值类型 方法 功能 boolean matches(String regex) 如果匹配当前字符串中regex(正则表达式)所表示的字符,如果有返回ture没 ...
- Linux 内存管理 pt.1
哈喽大家好,我是咸鱼 今天我们来学习一下 Linux 操作系统核心之一:内存 跟 CPU 一样,内存也是操作系统最核心的功能之一,内存主要用来存储系统和程序的指令.数据.缓存等 关于内存的学习,我会尽 ...
- Python OOP之继承封装多态
面向对象的三大特征 继承 封装 多态 继承 子类可以使用父类定义的内容或者行为 继承的实现 父类,基类,超类,被继承的类,Base Class,Super Class 子类:有继承行为的类 所有类都必 ...
- 数据结构(DataStructure)-02
数据结构-02 **数据结构-01回顾** **数据结构-02笔记** **作业讲解** **链表作业题一** **链表作业题二** **链表作业题三** **线性表 - 栈(LIFO)** **线性 ...