第四章 利萨茹曲线(Lissajous Curves)

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

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

blog: http://cnblogs.com/willian/

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

曲线艺术编程系列第四章

确保您已知晓了最初一章中我们对示例代码的约定。

利萨茹曲线一直以来是我最喜欢的技术之一。除了循环的形状,实际上它可比看起来有用。

在这一篇中我们将覆盖它的基础知识,除此之外像往常一样,我们另外还得看看它在其它方面的应用。

基础

利萨茹曲线又名利萨茹图形或鲍迪奇曲线。两个名字都来自于 19 世纪研究和撰写它们的人名。

这些曲线图形由上下左右来回摆动循环的长曲线形成。在它们在纯粹的形状时(译者注:没有复杂多余线条时),让我们想起常常在老式科幻电影中显示在示波器上发光的图形。

公式

此番,在一开始我搜索利萨茹曲线相关题材时就找到了一个有用的参数方程。

x = A * sin(a * t + d)
y = B * sin(b * t)

有一个正弦波在 x 轴,另一个正弦波在 y 轴。 它们不会在某个方向上趋向无限,它在周期内自循环。

公式中变量挺多的。让我们分别看看。

A 和 B 最后影响的是曲线在 x 轴宽和 y 轴高。确切的讲,只是宽高的一半,因为会在它各自方向上延展。

t 是一个范围 在 0 到 2 * PI 的范围参数变量。尽管在真正执行时它在各自方向上会超出范围,但很快它就会返回回来。在 x 方程式中 t 乘以 a, y 方程式中 t 乘以 b ,然后在各自轴结果传入正弦函数。另外,在 x 方程式中参数 d 或者说 delta 变量用于把它移出相位。

此方程可能看起来像之前章节中我们接触的圆方程。如果我们将 A 和 B 设为相等并且把它当作 r ,并且将 a 和 b 设为 1,再去掉 d ,再用余弦代替 x 方程的 正弦函数,我们就得到了:

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

这就是原点在 0,0 的圆方程了。因为余弦就是正弦 +90 度,所以我们也可以说:

x = r * sin(t + d)
y = r * sin(t)

... 这个 d 就是 90 度 ,或者 PI / 2 弧度。

A 和 B 分开的情况你在椭圆方程里已经见过了, 在那里 A 我们叫作 '半径 x', B 就是 “半径 y”

x = A * sin(t + d)
y = B * sin(t)

所以当参数像上面那样同步变化,我们得到的是圆或椭圆,但当我们改动一些参数后,我们将得到了更有趣的曲线。

好了,说的够多了,开始编码吧。我们直接跳到创建一个利萨如函数。我会简化一下。

function liss(cx, cy, A, B, a, b, d) {
res = 0.01
for (t = 0; t < 2 * PI; t += res) {
x = cx + sin(a * t + d) * A
y = cy + sin(b * t) * B
lineTo(x, y)
}
closePath()
}

此处,分辨率变量 res 值不像之前在圆和椭圆函数内那样。在这里我给了一个很小的值,尽管它意味着画更多简单的小曲线。这样设定后从 0 到 2 * PI (6.28...), 从 0.01 开始增长将会得到 528 条线段,这对大多数例子来说足够用了(译者注:线段越多意味着越平滑)。如果曲线变的不平滑了,你就改这个值让线段变的更多。但我不打算深入测算到一个合适的值。

现在我们能用它画出来像下面这样调用:

width = 600
height = 600
canvas(600, 600) liss(300, 300, 250, 250, 1, 1, PI / 2)
stroke()

这个图就像我上面说的那样,我把 A 和 B 设为相等的值,a 和 b 设为 1 并且 d 设为 PI / 2 。这样实际得到的结果就是一个圆形...

... 看,确实是圆。

让我们先将 d 设为 0 然后 a 和 b 稍后小变化一下子。 这里,将 a 和 b 分别设置为 2 和 1(再次 d 设为 0)

liss(300, 300, 250, 250, 2, 1, 0)

设为 2 和 3 时:

liss(300, 300, 250, 250, 2, 3, 0)

让我们调的更大一点,11 和 8

liss(300, 300, 250, 250, 11, 8, 0)

以上的这几个例子都中 d 都是 0, 波形都在自己轴的相位。现在我们保持 11 和 8 不变,将 d 变成 0.5, 移出它们的相位。

liss(300, 300, 250, 250, 11, 8, 0.5)

这里的动画是 a 为 6,b 为 7, d 持续变化时产生的图形变化

如果你希望你创建的曲线首尾丝滑的连接,那么没有什么 比 a 和 b 不是整数更糟的了(译者注:想丝滑需要 a 和 b 为整数)。 如果你将 a 设为 6.4,b 设为 7.3

liss(300, 300, 250, 250, 6.4, 7.3, 0)

如果你想看清楚点发生了啥,那么将 closePath 从函数内注释掉看看结果:

现在你可以看到曲线开始与结束点在随机位置, 而不是像 a 与 b 为整数时丝滑的连接起来。

当然,当你在这里使用小数时,你可以将变量 t 的范围从 0 到 2 * PI 继续放大到可以填满整个空间。 这里是我将 t 范围扩展到 20 * PI 的效果:

如果 a 和 b 你使用了有理数路径将最终又连接起来。 但可能会花点时间(译者注:如果是在动画中)。这个图中看起来就基本很接近了。

我们当然也能通过改变 A 和 B 这两个变量来获取宽的图形:

A = 250, B = 100

or tall figures:

或高的

A = 100, B = 250

这就是绘制利萨茹图形最直接的方法。 可以查查维基百科与此相关的资料,学习一下这类曲线的多种属性。 我考虑在这里创建一个可交互的 demo, 但最后发现网上已经有很多了。文章最后我放了一些链接

取而代之的是,我们可以看看另外的应用方向。

动画

此处不会像上面做的曲线动画一样,不讨论曲线自身的动画,但会利用利萨茹曲线的结果应用在某个对象上。

但,首先我们先聊一聊最基础的动画

摆动(振荡)

通常你想在屏幕上对一个对象进行动画但又不想它超出屏幕, 让它上下左右的动,或者对它进行扭曲,变大,缩小。 你可能已经学习过一些 2D 图形变形属性,平移,旋转,缩放。 你可以用正弦或余弦函数计算结果值应用于这些变形属性,随着时间的推移改变那些函数方法的输入值就可以。

我们需要扩展一下我们的伪代码,它包含一个 loop 循环函数用于时刻进行不同绘制,它运行起来就是动画了。Processing (译者注:一个著名的图形库) 已经有内建的功能,其它图形系统也有类似的功能。但是你可能需要自己编写一个函数来实现这些功能。如果你用的是 HTML 和 javascript 的,那么我们可以使用 requestAnimationFrame 函数来实现。

我们假定它也拥有一个 clearCanvas 方法。我们可以把上一篇中编写的 circle 函数用到此处,或者使用你编码平台内置的绘图 api 。 我们像下面这样开始:

width = 400
height = 400
t = 0
canvas(width, height) function loop() {
clearScreen()
circle(width / 2 + sin(t) * 100, height / 2, 20)
fill()
t += 0.1
}

这里将正弦中的 t 乘以 100 以获得圆在 x 轴上的偏移。当 t 持续增长时,这个圆会来回运动。你也可以简单的对 y 轴应用同样的效果。或者应用在半径上:

width = 400
height = 400
t = 0
canvas(width, height) function loop() {
clearScreen()
circle(width / 2, height / 2, 50 + sin(t) * 20)
fill()
t += 0.1
}

此处,我们用 50 作为基础半径,加上使用 t 的正弦值乘以 20。由于 正弦值从 -1 到 +1 ,半径会加上 -20 到 +20 , 结果就是 30 至 70 之间摆动。

我们同样也可以将此应用在旋转属性上,但需要换一下图形,以便于我们可以观察到它的旋转。我把这个留给你自己实现吧。

回到绕圆运动。我们希望它绕整个 canvas 做圆周运行。我们一样可以使用正弦配合余弦完成圆周运动:

width = 400
height = 400
t = 0
canvas(width, height) function loop() {
clearScreen()
circle(width / 2 + cos(t) * 100, height / 2 + sin(t) * 100, 20)
fill()
t += 0.1
}

假设我们希望它运动的更加自然该如何实现?我们可以为它添加一些随机运动,然后你会发现一个问题,如何保证它只在屏幕内运动。不难,我们可以使用利萨茹的公式来实现这一点,像下面这样:

width = 400
height = 400
t = 0
a = 13
b = 11
canvas(width, height) function loop() {
clearScreen()
circle(width / 2 + sin(a * t) * 100, height / 2 + sin(b * t) * 100, 20)
fill()
t += 0.02
}

虽然这是一个不太完美的循环运动 gif 动画,但精神你肯定已经领会到了。这个圆看起来就像是随机绕着屏幕运动,但它实际是按利萨茹曲线路径运动。在我看来它就像是一只苍蝇在来回飞。事实上如果你多添加一些应用不同的参数的圆,那么它们会看起来更像一群苍蝇在飞。没有公分母的 a 和 b 数值设的越高,它在路径的运动看起来会更加随机。

利萨茹网络和随机利萨茹网络

下面这些图并非“标准官方”的图形,你可以在任何地方找到相关文档(可能我以前在某个地方提到过),但我始终觉得相互交流不同的创意是非常好的。你常常可以创造出一些独一无二的东西。所以我就放一些我自创的图在这里吧。

一些年前我曾经使用过利萨如曲线并尝试在不同的方式渲染它们。不仅仅是渲染它自己的路径,我决定用曲线上的每个相近点连接成线条。结果非常酷炫, 我将它们称为“利萨茹网络”。这里是一些例子:

你可以在这里找到更多 http://www.artfromcode.com/?p=657

我觉得它们看起来超酷。这些技术包含在了 Generative Design 这本书中 (信我)

随着我创意持续的进步, 我希望让它们更加的自然。 而不是仅仅给 a 和 b 添加乘数,我开始让这些参数变的随机一点。这会生成真正惊艳的图,我把它们称作“随机利萨茹网络”

它成为了我最爱欢迎作品这一。几年后我甚至被委托用此项技术设计一个系列印在红酒瓶上。

(译者注:红酒网站打不开...)

我就不再亮代码了。希望更多是对你的启发 -- 如何利用利萨茹曲线这一概念让颠覆事物让它变的更有趣。

下一章我们将聚焦在一些相关的系统上,它们同样使用非常有趣的方式创建曲线。

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


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

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

曲线艺术编程 coding curves 第四章 利萨茹曲线(Lissajous Curves)的更多相关文章

  1. 数学图形(2.19) 利萨茹3D曲线

    在前面的章节数学图形(1.13) 利萨茹曲线中,写的是二维的利萨茹曲线,这一节,将其变为3D图形. #http://www.mathcurve.com/courbes3d/lissajous3d/li ...

  2. C语言编程入门之--第四章C语言基本数据类型

      导读:C语言程序中经常涉及一些数学计算,所以要熟悉其基本的数据类型.数据类型学习起来比较枯燥,不过结合之前的内存概念,以及本节的字节概念,相信数据类型也就不难理解了.本章从二进制的基本概念开始,然 ...

  3. 《Linux命令行与shell脚本编程大全》 第四章

    4.1 监测程序 1. ps  默认只显示运行在当前控制台下的属于当前用户的进程.  可以接很多选项,比如 -A表示所有进程  -e等. 2. ps -l  查看进程更多信息 UID:启动这些进程的用 ...

  4. java并发编程实战:第四章----对象的组合

    一.设计线程安全的类 找出构造对象状态的所有变量(若变量为引用类型,还包括引用对象中的域) 约束状态变量的不变性条件 建立对象状态的并发访问管理策略(规定了如何维护线程安全性) 1.收集同步需求(找出 ...

  5. 《C++编程思想》第四章 初始化与清除(原书代码+习题+解答)

    相关代码: 1. #include <stdio.h> class tree { int height; public: tree(int initialHeight); ~tree(); ...

  6. Java编程基础篇第四章

    循环结构 循环结构的分类 for循环,while循环,do...while()循环 for循环 注意事项: a:判断条件语句无论简单还是复杂结果是boolean类型 b:循环体语句如果是一条语句,大括 ...

  7. “AS3.0高级动画编程”学习:第四章 寻路(AStar/A星/A*)算法 (下)

    在前一部分的最后,我们给出了一个寻路的示例,在大多数情况下,运行还算良好,但是有一个小问题,如下图: 很明显,障碍物已经把路堵死了,但是小球仍然穿过对角线跑了出来! 问题在哪里:我们先回顾一下ASta ...

  8. 《UNIX环境网络编程》第十四章第14.9小结(bug)

    1.源代码中的<sys/devpoll.h>头文件在我的CentOS7系统下的urs/include/sys/目录下没有找到. 而且我的CentOS7也不存在这个/dev/poll文件. ...

  9. 《Java并发编程实战》第四章 对象的组合 读书笔记

    一.设计线程安全的类 在设计线程安全类的过程中,须要包括下面三个基本要素:  . 找出构成对象状态的全部变量.  . 找出约束状态变量的不变性条件.  . 建立对象状态的并发訪问管理策略. 分析对象的 ...

  10. 《Python网络编程基础》第四章 域名系统

    域名系统(DNS) 是一个分布式的数据库,它主要用来把主机名转换成IP地址.DNS以及相关系统之所以存在,主要有以下两个原因:   它们可以使人们比较容易地记住名字,如www.baidu.com. 它 ...

随机推荐

  1. Vditor在原生JS中如何结合后端使用

    目录 1.Vditor介绍 2.如何在原生JS中结合后端使用 2.1 背景 2.2 正确使用方式 2.2.1 编辑页面 2.2.2 回显页面(修改页面) 2.2.3 预览页面 3.小结一下 1.Vdi ...

  2. Trie树结构

    PrefixTree 208. 实现 Trie (前缀树) Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键.这一数据结构 ...

  3. 【深入浅出 Yarn 架构与实现】5-3 Yarn 调度器资源抢占模型

    本篇将对 Yarn 调度器中的资源抢占方式进行探究.分析当集群资源不足时,占用量资源少的队列,是如何从其他队列中抢夺资源的.我们将深入源码,一步步分析抢夺资源的具体逻辑. 一.简介 在资源调度器中,以 ...

  4. Node.js中理解asyncmap函数 ,爬取王者荣耀荣耀官网壁纸400多张

    async/mapLimit函数理解 const phantom = require('phantom') const express = require('express'); const app ...

  5. 多台服务器之间配置ssh免密登录

    需求:假设有N台服务器,N台服务器之间都需要配置相互间免密登录 步骤1:在一台服务器上安装ansible yum -y install epel-release &&  yum -y ...

  6. 每日复习——static , 饿汉式方法,懒汉式方法,以及单例设计模式

    1.1.static 的使用 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过 new 关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部 ...

  7. 人工智能NVIDIA显卡计算(CUDA+CUDNN)平台搭建

    NVIDIA是GPU(图形处理器)的发明者,也是人工智能计算的引领者.我们创建了世界上最大的游戏平台和世界上最快的超级计算机. 第一步,首先安装N卡驱动. cby@cby-Inspiron-7577: ...

  8. 四月九号java知识

    1.do{}while();和while(){}结构最主要区别就是前者后面要一个分号 2.System.out.print();与System.out.println();的区别后者输出换行, 前者不 ...

  9. 关于取消DevTools listening on ws://127.0.0.1…提示的方法

    Python代码写好之后,通过任务计划程序定期执行.py文件,但总会有命令窗口,虽然不影响程序执行,但每次需要手动叉掉比较烦.于是我网上搜索了一些方法. 网上的方法并没有直接解决我的问题,但我借助搜索 ...

  10. 用Abp实现两步验证(Two-Factor Authentication,2FA)登录(二):Vue网页端开发

    @ 目录 发送验证码 登录 退出登录 界面控件 获取用户信息功能 项目地址 前端代码的框架采用vue.js + elementUI 这套较为简单的方式实现,以及typescript语法更方便阅读. 首 ...