news 2026/10/14 18:36:22

极坐标与三角函数:用 p5.js 绘制圆形、螺旋与自定义形状(Nature of Code 3.4 实战)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
极坐标与三角函数:用 p5.js 绘制圆形、螺旋与自定义形状(Nature of Code 3.4 实战)
  • 教程
  • 文档
  • 示例工程

【免费下载链接】website-archive

Archive of the Coding Train website (first version)

项目地址:https://gitcode.com/gh_mirrors/we/website-archive
点击查看免费下载

本篇技术指南以 Coding Train《The Nature of Code》第 3.4 节「Polar Coordinates」为核心,系统讲解三角函数(正弦、余弦、正切)与极坐标思维在 p5.js 中的落地用法。你将学会用cos()/sin()把"角度 + 半径"翻译成画布上的x / y坐标,并基于仓库中 basic-polar-coordinates、spiral、random-trail、circular-shape 四个可运行示例,亲手实现圆周运动、螺旋线、随机轨迹与不规则环形图案——这些正是后续振荡运动、粒子系统与自主代理(wander)编程的数学基础。

为什么要"用极坐标思考"?

在 p5.js 里,绘制任何点都默认使用直角坐标系(笛卡尔坐标):一个点的位置由(x, y)描述。但自然界里大量运动天然围绕"中心 + 角度"展开——钟摆的摆动、行星的公转、雪花的分支、鱼的转向,本质上都是极坐标(Polar Coordinates)问题:用半径 r(到原点的距离)和角度 θ来定位一个点。

极坐标到直角坐标的换算,正是本节的灵魂公式:

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

当角度 θ 从 0 递增到 2π(即TWO_PI)时,点(x, y)就会在半径为 r 的圆上完整地绕一圈。理解了这一点,你就能用"半径 + 角度"两个标量轻松描述任何旋转、盘旋与环形图案,而不是手动推算一长串直角坐标。

三角函数三兄弟:sin、cos、tan

在进入代码之前,先厘清三个核心三角函数的几何含义(本视频 0:11–5:49 部分重点讲解):

函数含义值域(弧度制)在 p5.js 中的典型用途
sin(θ)角度 θ 对应单位圆上的y 分量[-1, 1]由角度求纵向偏移、垂直方向的速度分量
cos(θ)角度 θ 对应单位圆上的x 分量[-1, 1]由角度求横向偏移、水平方向的速度分量
tan(θ)对边 / 邻边 =sin(θ) / cos(θ)无界由向量求指向角(见 3.3 的heading())

三个函数接受的都是弧度(radians)而非角度(degrees)。p5.js 中一个整圆为TWO_PI(约 6.283),半个圆为PI,四分之一圆为PI / 2。如果想用度数,可用angleMode(DEGREES)切换(详见 3.1 角度与旋转 一节)。

从源码结构看,本系列的教学编排也是层层递进的:3.2 节把"矢量运动"的思想迁移到角度上(角速度、角加速度),3.3 节再用p5.Vector.fromAngle()与vel.heading()建立"向量 ↔ 角度"的双向转换,而 3.4 节正是把三角函数与极坐标作为核心工具正式展开。

示例一:基础极坐标——让一个点沿圆周运动

仓库中的第一个示例 basic-polar-coordinates/sketch.js 是最直白的演示:用一个黄色圆点沿半径为 150 的圆周匀速移动。

let angle = 0; function setup() { createCanvas(400, 400); } function draw() { background(0); stroke(255); strokeWeight(4); noFill(); translate(200, 200); // 把坐标系原点移到画布中心 let r = 150; // 固定半径 circle(0, 0, r * 2); // 画出参考圆 strokeWeight(32); stroke(252, 238, 33); // 编码列车标志性的黄色 let x = r * cos(angle); let y = r * sin(angle); point(x, y); // 在极坐标换算出的位置画点 angle += 0.01; // 每帧旋转一点点 }

关键点逐行拆解:

  • translate(200, 200):把原点从画布左上角移到中心。此后所有坐标都相对新原点计算,极坐标的"半径"才有了直观意义。
  • let r = 150:固定半径,点的运动轨迹被约束在圆上。
  • let x = r * cos(angle); let y = r * sin(angle);:核心换算公式,把极坐标(r, angle)转成画布坐标(x, y)。
  • angle += 0.01:每帧增加 0.01 弧度。约TWO_PI / 0.01 ≈ 628帧走完一整圈(约 10.5 秒)。数值越大,转得越快——这就是"角速度"的雏形,与 3.2 角运动 的思路一脉相承。

运行效果:画布中央出现一个白色参考圆,黄色大圆点沿圆周匀速绕行。

示例二:螺旋——让半径随时间收缩

把"固定半径"改为"每帧递减",圆就变成了向内收拢的螺旋线。spiral/sketch.js 中注释掉了参考圆与背景重绘,让每一帧的点迹层层叠叠地留在画布上:

let angle = 0; let r = 150; function setup() { createCanvas(400, 400); background(0); // 只清屏一次,后续不重绘背景 } function draw() { translate(200, 200); strokeWeight(16); stroke(252, 238, 33); let x = r * cos(angle); let y = r * sin(angle); point(x, y); angle += 0.04; // 每帧旋转 0.04 弧度 r -= 0.2; // 半径每帧缩小 0.2 }

两个增量参数直接决定螺旋形态:

参数作用本例取值调整效果
angle += 0.04每帧转过的弧度0.04越大圈越密、转速越快
r -= 0.2每帧半径收缩量0.2越大螺旋收拢得越快;半径减到 0 以下后点会越过中心继续"穿出"

因为background(0)只执行了一次,黄点走过的每一帧位置都被保留,形成从圆周向中心收拢的明亮螺旋线。

示例三:随机轨迹——给运动加入随机性

random-trail/sketch.js 在前一个示例的基础上引入随机量,并改用半透明背景制造"轨迹渐隐"效果:

let angle = 0; let r = 150; function setup() { createCanvas(400, 400); } function draw() { background(0, 10); // 半透明黑色,旧点逐渐淡出 translate(200, 200); strokeWeight(16); stroke(252, 238, 33); let x = r * cos(angle); let y = r * sin(angle); point(x, y); angle += 0.05; // 固定角增量 // angle += random(-0.1, 0.1); // 可取消注释:角度也随机抖动 r -= random(-2, 2); // 半径在每帧 -2 ~ +2 之间随机变化 }

与螺旋示例的核心差异:

  • background(0, 10):第二参数是 alpha 透明度。每帧用 10/255 透明度的黑色覆盖画布,旧点不会立即消失而是逐步淡出,形成流动的拖尾轨迹。
  • r -= random(-2, 2):半径不再单调收缩,而是每帧在 -2 到 +2 之间随机增减,轨迹在"扩张—收缩"之间游走。
  • 源码中还保留了一行被注释的angle += random(-0.1, 0.1),取消注释即可让角度也带上随机抖动——这是探索"确定性运动 + 随机扰动"两种风格的最佳实验入口。

示例四:自定义环形形状——用 beginShape 画出不规则圆环

最后一个示例 circular-shape/sketch.js 展示了极坐标的进阶用法:不再逐帧画点,而是一次性沿圆周采样大量顶点,并用随机半径勾勒出有机的环形轮廓。

let angle = 0; let r = 150; function setup() { createCanvas(400, 400); } function draw() { background(0); translate(200, 200); stroke(255); strokeWeight(4); noFill(); // let increment = map(mouseX, 0, width, PI, 0.01); // 交互版:鼠标控制顶点密度 let increment = 0.1; // 每 0.1 弧度采样一个顶点 noLoop(); // 只绘制一帧,得到静态图形 beginShape(); for (let a = 0; a < TWO_PI; a += increment) { let r1 = r + random(-50, 10); // 每个顶点的半径随机偏移 let x = r1 * cos(a); let y = r1 * sin(a); vertex(x, y); } endShape(CLOSE); }

实现要点:

  • for (let a = 0; a < TWO_PI; a += increment):角度从 0 遍历到 2π,步长increment = 0.1,共约 63 个采样点。步长越小顶点越多、轮廓越圆润;步长越大越呈多边棱角。
  • let r1 = r + random(-50, 10):每个顶点独立获得半径扰动(-50 到 +10),这是图形呈现"不规则感"的关键,也解释了为何形状偏向一侧隆起——扰动区间不对称(偏负更多)。
  • beginShape() ... vertex(x, y) ... endShape(CLOSE):p5.js 的形状构建 API,把所有极坐标采样点依次连线并闭合,生成完整的封闭轮廓。
  • noLoop():停用持续刷新,只渲染一帧静态图形。每次刷新页面都会得到一幅不同的随机环形图案。
  • 源码注释提供了交互升级方案:用map(mouseX, 0, width, PI, 0.01)把鼠标横坐标映射为采样步长,即可实时改变轮廓的细腻程度。

运行效果:画布中央出现一个白色不规则环形轮廓,像一块随机起伏的"石头"或"斑块",每一次刷新形状都独一无二。

纵深补充:向量与角度的双向转换(3.3 节的工具)

极坐标不只服务于"画圆"。在 3.3 角度与向量 中,p5.js 提供了两个与本节公式互为逆运算的工具,配合极坐标能解决"物体朝向运动方向"这类高频问题:

  • p5.Vector.fromAngle(angle):由角度直接生成单位向量(cos(angle), sin(angle)),等价于本节的x = r * cos(angle)一行写法。见 3.3 的 vehicle 示例:this.vel = p5.Vector.fromAngle(this.angle)。
  • vec.heading():返回向量指向的角度(内部基于atan2),等价于从直角坐标反解出角度。见 direction-pointing 示例:this.angle = this.vel.heading()后再rotate(this.angle)让三角形对准运动方向。

这套"角度 ↔ 向量"工具与本节公式共同构成了振荡与转向类模拟的完整工具箱,后续 5.5 漫游行为 中会再次引用极坐标来生成随机转向角度。

实战延伸:极坐标 × 柏林噪声——不规则闭合曲线

极坐标最优雅的应用之一,是用它"驱动"柏林噪声采样,生成永远闭合、永不平滑的有机曲线。仓库中的 Polar Perlin Noise Loops(Coding Challenge #136) 正是这一思路的完整实现,其 P5/sketch.js 核心逻辑如下:

let xoff = map(cos(a + phase), -1, 1, 0, noiseMax); let yoff = map(sin(a + phase), -1, 1, 0, noiseMax); let r = map(noise(xoff, yoff, zoff), 0, 1, 100, height / 2); let x = r * cos(a); let y = r * sin(a);

其中a同样从 0 遍历到TWO_PI,但半径r不再固定或随机,而是由二维柏林噪声noise(xoff, yoff)决定——而xoff、yoff恰好由cos(a)、sin(a)映射而来,形成"圆环路径上采样噪声"的巧妙结构。这正是 3.4 节"以角度为驱动、以半径为输出"思想的直接延伸:只要半径是平滑函数,闭合曲线就能呈现自然的形变。

延伸学习路径

本节处于《The Nature of Code》第三章"振荡运动"的中段,与相邻章节构成完整知识链:

  • 前置:3.1 角度与旋转(弧度制与angleMode)、3.2 角运动(角速度/角加速度)、3.3 角度与向量(fromAngle与heading)。
  • 后置:3.5 简谐运动(用sin()生成波动)、3.6 绘制正弦曲线 与 3.7 叠加波,最终过渡到第 5 章自主代理。
  • 应用:Asteroids 小游戏(Coding Challenge #46) 用角度控制飞船朝向;Polar Perlin Noise Loops(#136) 用极坐标绘制噪声闭合曲线;第 5.5 节 wander 漫游 直接复用本节思想生成转向角度。

动手练习建议

  1. 修改示例一的angle += 0.01为angle += 0.05,观察转速变化,并把半径换成r = 50 * sin(angle) + 100,看看轨迹如何从圆变成花瓣状。
  2. 在示例三中取消angle += random(-0.1, 0.1)的注释,对比"平滑螺旋"与"抖动轨迹"两种运动质感。
  3. 为示例四加入颜色:用stroke随a变化的hue值,做出彩虹环形图案(仓库社区贡献中即有 "Rainbow Spiral using Polar Coordinates" 的同类作品)。

四个示例的完整代码均可在仓库目录 learning/nature-of-code/3.4-polar-coordinates/ 下查看与运行,每个子目录都包含标准index.html(通过 CDN 引入 p5.js 1.4.1)与独立的sketch.js,下载后直接在浏览器打开即可体验。

  • 教程
  • 文档
  • 示例工程

【免费下载链接】website-archive

Archive of the Coding Train website (first version)

项目地址:https://gitcode.com/gh_mirrors/we/website-archive
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 18:35:19

Map 的 putIfAbsent(k, v) 详解

承接前两篇早会分享&#xff1a;getOrDefault&#xff08;借你用一下不存&#xff09;→ computeIfAbsent&#xff08;没有就现场造一个&#xff0c;并真的存进去&#xff09;。 这篇讲第三兄弟 putIfAbsent&#xff1a;没有才放进去——而且放的是你提前准备好的值&#xff0c…

作者头像 李华
网站建设 2026/10/14 18:33:50

死锁的四个必要条件:从多线程事故到系统化排查

1. 死锁不是玄学&#xff1a;从一次真实的多线程事故说起三年前我在某团队维护一个订单处理服务&#xff0c;有一阵子线上频繁报警&#xff0c;线程池被占满&#xff0c;请求大量超时。翻日志发现一段很诡异的循环&#xff1a;A任务在等B任务释放锁&#xff0c;B任务又在等A任务…

作者头像 李华
网站建设 2026/10/14 18:33:29

2026年全球AI大模型API聚合平台深度评测:企业级与开发者各自的首选

选大模型 API 聚合平台&#xff0c;2026 年公认的四个硬维度是&#xff1a;延迟、价格、模型覆盖、合规支付。本篇基于这四个维度对全球主流聚合服务做一轮深度评测&#xff0c;覆盖 OpenRouter、硅基流动、词元之河(TokenRiver.ai)等代表选手&#xff0c;并给出企业与开发者各…

作者头像 李华