第十一章 玫瑰花形 ROSES

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

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

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

曲线艺术编程系列第 11 章

这一篇我们将看到另一种我钟意的曲线类型 -- 玫瑰花形或玫形曲线。 在我看来这类曲线就像是圆形的利萨茹曲线 Lissajous curves,或者非常规则的谐波图。事实上它们是一类特别的长短幅内摆线,特别到足够自成一类。先给你看些例子,下面是玫形曲线:

(译都注:这是玫瑰花 roses 我是不信的...)

就像其它我们关注过的其它曲线一样,我们有参数方程, 用 t 从 0 到 2 * PI 递增,计算得到返回值并用它绘制出曲线。我们先往回看看圆的方程:

function circle(x, y, r) {
for (t = 0; t < 2 * PI; t += 0.01) {
x1 = x + cos(t) * r
y1 = y + sin(t) * r
lineTo(x1, y1)
}
}

你当然可以对 for 循环内的代码简化成一行,但为了解释的更清晰我还是将它展开了。

一个玫瑰花形曲线使用同样的策略,但使用以 t 和其它参数为基础的动态半径取代固定的半径。下面是半径方程:

r = a * cos(n * t)

现在我们有了 2 个新变量。a 是花的覆盖半径, n 控制着花瓣数(当然花瓣数量的计算有点儿复杂,捎后回来解释)。现在我们可以创建一个玫瑰花形函数:

function rose(x, y, a, n) {
for (t = 0; t < 2 * PI; t += 0.01) {
r = a * cos(n * t)
x1 = x + cos(t) * r
y1 = y + sin(t) * r
lineTo(x1, y1)
}
}

当然如果你愿意,可以整理一下代码:

function rose(x, y, a, n) {
for (t = 0; t < 2 * PI; t += 0.01) {
r = a * cos(n * t)
lineTo(x + cos(t) * r, y + sin(t) * r)
}
}

现阶段先假设函数的 n 参数为正整数。当然后面我们也会探索更大的范围。

现在我们就可以绘制我们第一个玫瑰花形:

width = 800
height = 800
canvas(800, 800) rose(width / 2, height / 2, width * 0.45, 5)
stroke()

在这里我会经常用 width * 0.45。它刚好比 canvas 宽度的一半小一点儿,这让曲线几乎刚好覆盖整个 canvas 但又没能碰到边界。

上面代码会生成一个 5 个花瓣的玫瑰花形:

第一个例子中 n 用的是 7。 而下面这个花 n 用的是 11:

到目前为止我们可以看到 n 与 花瓣数量之间有很好的相关性。前面 n 用的都是奇数。如果我们用的是偶数 n 为 4 会如何呢?

有意思,产生了 8 个花瓣。n 的值遵循 奇数 n 创建 n 个花瓣。 偶数 n 创建 2 * n 个花瓣。把 n 再设大一点看看,设 n = 40,它将产生 80 个花瓣。我不得不提升辨率 -- 将循环上的 t 改为 0.001 以保证花瓣没有锯齿。

相反,如果 n = 1,仅会得到一个单圆

有点怪,但在数学上说得通。你会发现 n 为负值时,玫瑰花看起来与 n 为正值时得到的结果图形一样。 下面左边的是 5 右边的是 -5

毫无意外, n = 0 时会得不到任何图形。并且我们已经覆盖了全部的整数玫瑰花形。如果这是全部了的话就太好了。可惜还有更多的类型。

(译者注:如果你用 javascript 代码实现测试,你会发现 n = 0 时你会画出一个大圆,n = 1 时是右侧一个小圆,

可用源码 https://github.com/willian12345/coding-curves/tree/main/examples/ch11/rose-3.html 测试)

交替玫瑰花形

在我讲完所有 n 值前, 我想先提一下交替玫瑰方程。在半径方程中用正弦 sine 代替余弦 cosine :

r = a * sin(n * t)

它会生成与原来一样的花形,但是旋转了。左侧是用余弦 5 个花瓣的原图, 右侧是用正弦:

8 个花瓣的也是一样的效果 (n = 4)

真实的旋转值是 PI / (2 * n) 弧度, 或 90 / n 度。n 为奇数时,视觉上看起来总是旋转了 90 度 (实际旋转可能不同,但由于旋转对称性,它看起来就像是旋转了90度)。当 n 为偶数时花瓣就会在原版本花瓣之间。

当 n 为分数

当我们给 n 的值是分数时事情就变的更有趣了。我们可以试着用分数生成玫瑰花 :

rose(width/2, height/2, width * 0.45, 5.0 / 4.0)
stroke()

但是结果越令人相当失望

问题在于它需要超过 2 * PI 以获取整圆。那么要超过多少呢? 好吧,我们用编程的方式来解决,我们首先需要确保 n 为有理数。如果它是无理数, 这朵玫瑰花就会无法闭合回起点(译者注:绕多少圈圆也无法与起始点相接)。我们也需要知道分数的分子与分母。我们可以通过调整玫瑰花形生成函数,为其额外添加一个参数,我们将 n 作为分子,d 作为分母。

rose(x, y, a, n, d) {
for (t = 0; t < 2 * PI; t += 0.01) {
r = a * cos(n / d * t)
lineTo(x + cos(t) * r, y + sin(t) * r)
}
}

调整后的函数暂时还没有解决问题,但这是解决问题的第一步。你可以强制 n 和 d 为整数,以确保得到有理数分数(译者注:数学上,有理数是一个整数 a 和一个 正整数 b 的比), 但确保相除前转换它们以保证它返回值为浮点数。

现在需要调整 for 循环 2*PI 的结束条件为我们需要的值。 这个范围值是:

limit = PI * d * m

但是这个新的 m 变量是什么?如果 d * n 结果为奇数则 m 应该为 1。如果 d * n 结果为偶数则 m 应该为 2。 Woo! 有点儿复杂了.. 别担心我们可以简化它。

我们通常将数对2取余来判断数的奇偶性。如果结果为 0 ,则这个数是偶数,如果结果为 1 则原值为奇数。 我们想:

m = 1 when d * n % 2 == 1

并且

m = 2 when d * n % 2 == 0

所以我们可以这样设置:

(译者注: d * n % 2 == 1 奇数 m = 1; d * n % 2 == 0 偶数 m = 2; )

m = 2 - d * n % 2

下面是修改后的函数:

rose(x, y, a, n, d) {
m = 2 - d * n % 2
limit = PI * d * m
for (t = 0; t < limit; t += 0.01) {
r = a * cos(n / d * t)
lineTo(x + cos(t) * r, y + sin(t) * r)
}
}

记住,如果 n 和 d 强制为整数, 为了保证运行正常你可能需要对它们强制转换。我将这部分工作留给你们自己完成 。现在我们可以重新调用:

rose(width/2, height/2, width * 0.45, 5, 4)
stroke()

现在得到的结果就好多了:

(译者注:可用源码 https://github.com/willian12345/coding-curves/tree/main/examples/ch11/rose-6.html 测试)

这回这玫瑰花算是完整了。

现在你可以用不同的分数来测试下效果了。我发现如果分子分母值大但相互很接近会生成非常有趣的数。举个例子, n = 22, d = 21:

甚至是 81 和 80:

当分数小于 1.0 时图形变得完全不同但依然很有趣。举个例子,下图左侧图形 n 和 d 分别为 1,2,中间图形是 1,3, 右侧图形 1,4。

有个找到有趣图案的小技巧是可以对的这对值可以化简,比如 17 / 51 可以化简成 1 / 3, 结果图形就是上图的中间那一幅。当改动一点点其中的某个值时,比如变成 17 / 52:

变动了 1 但造成的结果差异非常大。

有些特别的玫瑰曲线拥有自己的名称。我分享几个给你们。

蚶线三分角 (Limaçon Trisectrix)

n / d 是 1 / 3, 我们在前面已经见过了。

杜勒叶形线 (Dürer Folium)

n / d 是 1 / 2, 之前也见过了

四叶玫瑰线(Quadrifolium)

比例是 2 / 1

三叶玫瑰线(Quadrifolium)

比例是 3 / 1

毛雷尔玫瑰(Maurer Roses)

你以为我们这就结束了?错!还有其它类型的玫瑰形曲线等我们探索呢 -- 毛雷尔玫瑰(Maurer Roses)!

毛雷尔玫瑰 建立在 rose 函数基础上, 与一直绘制曲线不同的是,它绘制的是一系列线段将延玫瑰曲线上的点连接起来。通常使用 360 个特殊角度的线段绘制,虽然这不是必须的。 我们先用比例为 4 / 1 建个玫瑰图, 然后挑一个角度值用于循环内。此例中我使用的是 49。然后我们使用 t 循环 0 到 360度,将 t 乘以这个角度值。 所以 t 会从 0 递增, 到 49, 98, 147, 196 继续往后。我们在我们的玫瑰曲线的下一个点上应用(当然角度要转变成弧度)。下面这个 gif 图 表现的是前 30 次循环时的画面。

换种说法,在普通的玫瑰曲线中,递增间隔非常非常小,所以我们可以得到一个非常平滑的曲线。这里我们递增间隔巨大,这会让图看起来乱糟糟的。但是,我们等它完成 360 次迭代路径绘制完成,将会得到:

Aha! 全部完成后线条竟然不乱了!事实上,看起来还挺不错。上面我在毛雷尔玫瑰上还绘制了标准玫瑰图。但下面这个才是毛雷尔玫瑰的全貌:

我觉得两个图组合起来还挺好看。

所以具体怎么做呢?

我们还是得从基础的 rose 函数开始。 但这次,我们使用单个整数值。所以只有参数 n 没有参数 d 了。我们还要指定每次迭代角度的递增值。为了避免与之前 d 参数混淆,将参数名改成 deg 。这个函数签名是:

function maurer(x, y, a, n, deg)

还有,我们希望将 t 从 0 循环至 360。然后将 t 乘以 deg 。这就是角度了就像之前动画时那样。我们将它设为变量 k ,但在此之前还需要乘以 PI 再除以 180 转换成弧度

function maurer(x, y, a, n, deg) {
for (t = 0; t < 360; t++) {
k = t * deg * PI / 180
r = a * cos(n * k)
lineTo(x + cos(k) * r, y + sin(k) * r)
}
}

我们用 k 代替之前的 t 后执行 rose 算法。

现在我们可以像下面这样调用函数了:

width = 800
height = 800
canvas(800, 800) maurer(width / 2, height / 2, width * 0.45, 5, 37)
stroke() // drawing the regular rose is optional
// 标准玫瑰图可画可不画
rose(width / 2, height / 2, width * 0.45, 5, 1)
stroke()

得到:

对 n 和 deg 用不同的值试试。你会发现 n 的作用与前面标准玫瑰图形一样。但是变量 deg 即便是微小的变化都能让图形完全不同。 例如,在这个图形中,n 为 7 且 deg 为 23:

但将 deg 变为 24 时会得到下面这样的结果:

显然没那么好看了。

一般来说,变量 deg 使用奇数会比偶数时要好(当然也有例外)。

任何能被 360 整除的整数都不太行。举个例子, 4, 120:

我还是绘制了完整的图,但毛雷尔图形却仅仅绘制成了右边一个三角形。 于是将值增大到 121, 这回可就好多了:

而且,较小的质数通常都都能得到较漂亮的图形。 我注意到如果 n 值够小那么 deg 质数大一点儿最后的效果也还行。 我还没有完整验证过。我要多尝试测试下。

还有一项你可能想尝试的东西,就是给毛雷尔玫瑰用分数值。你完全不用改动函数。 你就直接传一个分数值给 maurer 函数。 因为我们总是从0 循环至 360, 我们不需要调整循环范围。下面是个传分数值的例子,注意在给 rose 函数传递时两个参数还是分开传的。

maurer(0, 0, width * 0.45, 5.0 / 4.0, 229)
stroke()
rose(0, 0, width * 0.45, 5, 4)
stroke()

看看你能在所有可能的图形变化中找到什么。

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


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

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

曲线艺术编程 coding curves 第十一章 玫瑰花形( ROSES)的更多相关文章

  1. 《Linux命令行与shell脚本编程大全》第二十一章 sed进阶

    本章介绍一些sed编辑器提供的高级特性. 21.1 多行命令 按照之前的知识,所有的sed编辑器命令都是针对单行数据执行操作的. 在sed编辑器读取数据流时,它会基于换行符的位置将数据分成行,一次处理 ...

  2. Python 编程快速上手 第十一章 Web scrapping

    前言 这一章讲了如何在 Web 上抓取相关的信息,工具是三个模块: webbrowser 模块:用于打开浏览器指定页面 requests 模块:用于下载文件 Beautiful Soup 模块:用于解 ...

  3. 《Java并发编程实战》第十一章 性能与可伸缩性 读书笔记

    造成开销的操作包含: 1. 线程之间的协调(比如:锁.触发信号以及内存同步等) 2. 添加�的上下文切换 3. 线程的创建和销毁 4. 线程的调度 一.对性能的思考 1 性能与可伸缩性 执行速度涉及下 ...

  4. 《Linux命令行与shell脚本编程大全》第十一章 构建基本脚本

    11.1使用多个命令 $date;who   //  命令列表,加入分号就可以,这样会依次执行.参见5.2.1节 注意区分$(date;who),这个是进程列表,会生成一个子shell来执行 Shel ...

  5. java并发编程实战:第十一章----性能和可伸缩性

    线程的最主要目的是提高程序的运行性能,但性能的提升会导致复杂性的提升,又会导致安全性和活跃性的风险 一.对性能的思考 提升性能意味着用更少的资源做更多地事情.要想通过并发来获得更好的性能,就要更有效地 ...

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

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

  7. [CSAPP笔记][第十一章网络编程]

    第十一章 网络编程 我们需要理解基本的客户端-服务端编程模型,以及如何编写使用因特网提供的服务的客户端-服务端程序. 最后,我们将把所有这些概念结合起来,开发一个小的但功能齐全的Web服务器,能够为真 ...

  8. CSAPP:第十一章 网络编程

    CSAPP:第十一章 网络编程 11.1 客户端服务器模型11.2 全球IP因特网11.3 套接字接口 11.1 客户端服务器模型   每个网络应用都是基于客户端-服务器模型.采用这个模型,一个应用是 ...

  9. 第十一章:Python高级编程-协程和异步IO

    第十一章:Python高级编程-协程和异步IO Python3高级核心技术97讲 笔记 目录 第十一章:Python高级编程-协程和异步IO 11.1 并发.并行.同步.异步.阻塞.非阻塞 11.2 ...

  10. 《汇编语言 基于x86处理器》第十一章 MS-DOS 编程部分的代码 part 2

    ▶ 书中第十一章的程序,主要讲了 Windows 接口,在小黑框中进行程序交互 ● 在屏幕指定位置输出带自定义属性的文字 INCLUDE Irvine32.inc .data outHandle HA ...

随机推荐

  1. 企业信息化-3.6 IT资源管理2-系统及应用

    笔者从业的主要是App Dev&Ops,对操作系统有些了解,对应用软件了解的更多.以下是总结了以前跟Host&Server Service.Cloud Service.IT Solut ...

  2. go微服务框架kratos学习笔记二(kratos demo 结构)

    目录 api cmd configs dao di model server service 上篇文章go微服务框架kratos学习笔记一(kratos demo)跑了kratos demo 本章来看 ...

  3. VMware Workstation Pro许可证

    永久许可证:ZC10K-8EF57-084QZ-VXYXE-ZF2XF 备用许可证: UF71K-2TW5J-M88QZ-8WMNT-WKUY4 AZ7MK-44Y1J-H819Z-WMYNC-N7A ...

  4. PyTorch实践模型训练(Torchvision)

    模型训练的开发过程可以看作是一套完整的生产流程,这些环节包括: 数据读取.网络设计.优化方法与损失函数的选择以及一些辅助的工具等,TorchVision是一个和PyTorch配合使用的Python包, ...

  5. Flask 上下文是什么 ?

    哈喽大家好,我是咸鱼.今天我们来聊聊什么是 Flask 上下文   咸鱼在刚接触到这个概念的时候脑子里蹦出的第一个词是 CPU 上下文 今天咸鱼希望通过这篇文章,让大家能够对 Flask 上下文设计的 ...

  6. ROS2的安装与使用(超详细图文教程)

    ROS2的安装与使用(超详细图文教程) 如果前面的虚拟机以及Ubuntu22.04镜像都安装好了,根据目录直接跳到ROS2的安装. 资料参考于:古月居 VMware虚拟机的安装 安装地址: 对于不了解 ...

  7. linux syslog.d日记操作记录-小节

    以下记录在学习LDD3时调试处理打印的一些操作 syslog 不同的发行版,不同的脚本文件,如fedora18中为rsyslog的名称 1:配置文件 /etc/syslog.conf(fedora r ...

  8. 一文掌握ArrayList和LinkedList源码解读

    大家好,我是Leo! 今天来看一下ArrayList和LinkedList的源码,主要是看一下常用的方法,包括像add.get.remove方法,大部分都是从源码直接解读的,相信大家读完都会有一定收获 ...

  9. 沁恒 CH32V208(三): CH32V208 Ubuntu22.04 Makefile VSCode环境配置

    目录 沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置 沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟 沁恒 CH32V208 ...

  10. DFS(深度优先搜索) 总是需要重置 visited 的状态吗?

    问题来自 P1902 刺杀大使,在最初的实现中 DFS 中一段代码如下: visited[x2][y2] = true; flag = dfs(v, x2, y2); visited[x2][y2] ...