- 教程
- 文档
- 示例工程
【免费下载链接】website-archive
Archive of the Coding Train website (first version)
本篇指南以仓库存档文档 _more/archive/nature-of-code/3-Oscillation/3.1-Angles-and-Angular-Motion.md 为核心骨架,系统讲解"角度(angles)"与"角运动(angular motion)"两个核心概念:从角度单位(度与弧度)讲起,再到rotate()旋转形状,最终建立"角度 → 角速度 → 角加速度"的角运动模型,并给出可直接运行的 p5.js 示例。读完本文,你将掌握如何让任意形状以可控制的角速度与角加速度旋转,并能把角运动与重力吸引等向量运动无缝结合,为第 3 章后续的极坐标、简谐运动、摆与弹簧打下基础。
1. 存档文档与它在 Nature of Code 系列中的位置
仓库中的存档文档记录了 2015 年 8 月的早期视频(Processing 版本),属于《The Nature of Code》第 3 章Oscillation(振荡运动)系列的第一集,标题为Angles and Angular Motion:
This video describes how angles work in Processing and how you can model the angular motion of a shape.
该系列存档页见 _more/archive/nature-of-code/3-Oscillation/index.md,它明确指出这些视频课程配套《The Nature of Code》书籍第 3 章(Oscillation)。同目录下的后继存档文档勾勒出整章的知识脉络:
- 3.2-Trigonometry-and-Polar-Coordinates.md:三角学与极坐标;
- 3.3-Simple-Harmonic-Motion.md:利用正弦、余弦实现简谐运动;
- 3.4-Pendulum-Simulation.md:摆的仿真;
- 3.5-Springs.md:基于胡克定律的弹簧力仿真。
这套旧版视频后来被重制为 p5.js 版本,对应的详细课程页为 _learning/nature-of-code/3.1-angles-rotation.md 与 _learning/nature-of-code/3.2-angular-motion.md,完整可运行的源码存放在 learning/nature-of-code/3.1-angles-rotation 与 learning/nature-of-code/3.2-angular-motion。本文的代码示例即取自这些仓库源码,与旧版视频"角度 + 角运动"的主题一脉相承。
2. 角度(Angle):旋转多少的量
角度的本质是"旋转了多少"。在二维坐标系中,一个形状围绕某个支点转动的量就是它的角度。与位置、速度、加速度这类向量量不同,角度是一个标量——它只有一个数值(大小),没有方向分量,方向感由"顺时针还是逆时针"来体现。
在开始任何旋转之前,必须回答一个基本问题:用什么单位度量角度?
2.1 两种单位:度(degrees)与弧度(radians)
- 度(degrees):把一圈均分为 360 份,一份是 1°。90° 是四分之一圈,180° 是半圈,360° 是一整圈。这是日常最直觉的单位。
- 弧度(radians):数学与物理中最自然的单位。定义是"弧长与半径之比",因此一整圈对应
2π弧度:
| 旋转量 | 度 | 弧度 |
|---|---|---|
| 四分之一圈 | 90° | π/2 |
| 半圈 | 180° | π |
| 四分之三圈 | 270° | 3π/2 |
| 一整圈 | 360° | 2π |
2.2 为什么 π(以及 τ)如此重要?
在弧度制下,旋转一整圈就是2π。仓库重制版课程页 _learning/nature-of-code/3.1-angles-rotation.md 中专门有一个话题 "Why is pi so important?"。原因在于:弧度把"旋转"与"三角函数"(正弦、余弦)直接挂钩,而三角函数正是第 3 章后续所有振荡、极坐标、摆、弹簧仿真的数学基础。用弧度计算三角函数、微积分、求导都远比度数自然——例如sin与cos的导数只有在弧度制下才是简洁的cos与-sin。
也正因如此,许多现代编程环境(包括 p5.js)默认使用弧度。如果你更习惯"一圈 360"的直觉,可以改用TAU(即2π)来思考——p5.js 提供TAU常量,一整圈就是TAU弧度,这在心理上等价于"一圈 360°"。
2.3 弧度与度的换算
两个方向的手动换算公式:
- 度 → 弧度:
radians = degrees × π / 180 - 弧度 → 度:
degrees = radians × 180 / π
p5.js 也内置了radians()与degrees()两个转换函数,但在实际编程中更推荐直接切换全局角度模式(见下一节),让引擎替你换算。
3. 设置角度单位:angleMode()
p5.js 用angleMode()统一控制所有角度相关 API(rotate()、arc()、atan2()等)使用的单位。仓库源码 learning/nature-of-code/3.1-angles-rotation/P5/sketch.js 在setup()中这样设置:
function setup() { createCanvas(400, 400); angleMode(RADIANS); // 显式声明使用弧度 }angleMode(RADIANS):弧度制(默认值),旋转一整圈需要2 * PI(即TAU)。angleMode(DEGREES):角度制,此时rotate(90)表示旋转 90°。
虽然弧度是默认值,但显式调用angleMode(RADIANS)是良好习惯——它让代码意图一目了然,也避免读者误以为单位是度。若某个项目你更希望用"90、180、360"这样的数值思考,在setup()中写angleMode(DEGREES)即可,之后所有旋转代码都按度解释。
4. 旋转一个形状:translate + rotate 的协作
仓库中 3.1 的完整示例(sketch.js)演示了如何让一个矩形绕画布中心持续旋转:
let angle = 0; function setup() { createCanvas(400, 400); angleMode(RADIANS); } function draw() { background(146, 83, 161); noStroke(); fill(240, 99, 164); rectMode(CENTER); translate(200, 200); // 把坐标系原点移到画布中心 rotate(angle); // 绕新的原点旋转 rect(0, 0, 128, 64); // 在旋转后的坐标系里绘制矩形 angle += 0.05; // 每帧增加角度 → 持续转动 }4.1 三个关键细节
translate()改变旋转支点:rotate()总是绕当前坐标系原点旋转。translate(200, 200)先把原点移到画布中心,随后rotate(angle)便让矩形绕画布中心转动。若不调用translate(),矩形将绕画布左上角(默认原点)旋转,效果完全不同。rectMode(CENTER):默认模式下矩形以左上角定位,rect(0, 0, 128, 64)会在原点右上区域展开;rectMode(CENTER)让矩形以坐标点为中心,绘制出的图形与旋转支点严格对齐,视觉上更"像绕自身中心转"。- 每帧累加角度:
angle += 0.05让角度每帧增大 0.05 弧度。当角度超过2π(约 6.28 弧度)时并不需要归零——三角函数和旋转对任意大角度都有效,角度自然会"环绕"。0.05 弧度/帧 ≈ 每秒 3 弧度(约每秒半圈),是一个视觉上舒服的转速。
4.2 旋转方向
p5.js 中角度的正方向为顺时针(因为屏幕坐标系 y 轴向下)。若想让物体逆时针旋转,只需把增量改为负数,例如angle -= 0.05。理解这一点对后续交互式角速度控制很重要。
5. 角运动:把向量运动的三大要素迁移到角度
第 1 章我们建立过"位置 → 速度 → 加速度"的向量运动模型:
position += velocity; velocity += acceleration;角运动(angular motion)几乎一模一样,只是把所有量换成标量:
angle += angleV; // 角位置 += 角速度 angleV += angleA; // 角速度 += 角加速度仓库 learning/nature-of-code/3.2-angular-motion/rect-acc/sketch.js 正是这个模型的直接实现:
let angle = 0; // 角位置(弧度) let angleV = 0; // 角速度:每帧转动的弧度 let angleA = 0.001; // 角加速度:每帧角速度的改变量 function setup() { createCanvas(400, 400); angleMode(RADIANS); } function draw() { // 用鼠标水平位置实时映射角加速度 angleA = map(mouseX, 0, width, -0.01, 0.01); // 限制角速度,避免越转越快失控 angleV = constrain(angleV, -0.2, 0.2); background(146, 83, 161); noStroke(); fill(240, 99, 164); rectMode(CENTER); translate(200, 200); rotate(angle); rect(0, 0, 256, 32); angle += angleV; // 位置积分 angleV += angleA; // 速度积分 }5.1 参数解读
angleA(角加速度):由鼠标位置映射而来。map(mouseX, 0, width, -0.01, 0.01)把鼠标在画布上的 x 坐标(0 到画布宽)线性映射到 -0.01 到 0.01 弧度的角加速度范围。鼠标在左半边时矩形逆时针加速,右半边时顺时针加速,中间位置(约 width/2)角加速度接近 0。angleV(角速度):constrain(angleV, -0.2, 0.2)把角速度钳制在 ±0.2 弧度/帧之间,防止加速度无限累积导致旋转失控——这对应第 2 章里对速度做限制的思路。angle(角位置):每帧累加角速度,等价于对速度做数值积分,得到当前旋转角度。
这三个变量的命名也出现在重制版课程页 _learning/nature-of-code/3.2-angular-motion.md 的主题列表中("Code! Angular velocity"、"Code! Angular acceleration"),说明这正是该视频演示的核心代码路径。
6. 交互式角运动:让旋转跟随鼠标
仓库同目录下的 grab 练习(learning/nature-of-code/3.2-angular-motion/grab/sketch.js)把角运动升级为交互式:按住鼠标时矩形"指向"鼠标位置,松开后保持惯性继续旋转。
核心是利用 p5.Vector 的heading()方法——它返回向量方向的弧度角:
function mouseDragged() { let v = createVector(pmouseX - width / 2, pmouseY - height / 2); pAngle = v.heading(); // 记录拖动前的角度 } function mouseReleased() { let v2 = createVector(mouseX - width / 2, mouseY - height / 2); angleV = v2.heading() - pAngle; // 角度差即角速度 console.log(angleV); } function draw() { if (mouseIsPressed) { let v = createVector(mouseX - width / 2, mouseY - height / 2); angle = v.heading(); // 按住时矩形始终指向鼠标 } // ... 绘制与积分代码同前 angle += angleV; angleV += angleA; }这段代码体现了两个实用技巧:
heading()把位置差转成角度:mouseX - width / 2与mouseY - height / 2构成从画布中心指向鼠标的向量,heading()直接给出该向量的方向角,省去手写atan2。- "角速度 = 角度差":松开鼠标瞬间,把"当前角度 − 拖动时角度"作为角速度注入,实现"甩出去继续转"的惯性手感——这本质上是用位置差近似速度的差分思想。
7. 综合案例:把角运动融入引力吸引
角运动最有价值的应用是让它与第 2 章的物理引擎协同工作。仓库 learning/nature-of-code/3.2-angular-motion/gravitational-attraction 下的三件套源码演示了完整的"引力吸引 + 自旋":
- sketch.js:创建 10 个 Mover 与 1 个 Attractor,每帧更新并施加引力;
- attractor.js:基于万有引力公式(引力常量
G = 5)计算引力,距离平方被constrain(force.magSq(), 100, 1000)钳制以避免奇点; - mover.js:每个运动物体在平移之外还携带自身的角状态。
Mover 类的角运动部分(mover.js)展示了如何把加速度"翻译"成角加速度:
this.angle = 0; this.angleV = 0; this.angleA = 0; update() { this.vel.add(this.acc); this.pos.add(this.vel); // 关键:把竖直方向加速度(标量)映射为角加速度 this.angleA = this.acc.y / 50.0; this.angleV += this.angleA; this.angle += this.angleV; this.acc.set(0, 0); // 每帧清零加速度,等待下一帧外力 } show() { push(); translate(this.pos.x, this.pos.y); // 平移到物体位置 rotate(this.angle); // 叠加自身旋转 line(0, 0, this.r, 0); // 一条"指针"线 ellipse(0, 0, this.r * 2); pop(); }这里有一个非常简洁的物理直觉:物体加速下落/上升时,竖直加速度acc.y驱动旋转。this.acc.y / 50.0只是把加速度数值缩放到合适的角加速度量级(50 是经验性缩放因子)。push()/pop()保证translate+rotate只作用于当前物体,不影响其他绘制状态。于是我们同时拥有了"平移物理"(位置、速度、加速度)与"旋转物理"(角度、角速度、角加速度)两套并行的运动系统,这正是存档视频标题 "model the angular motion of a shape" 所指的能力。
8. 运行这些示例
仓库中的示例均为 p5.js 项目,每个目录都包含sketch.js与 index.html。index.html通过 CDN 引入 p5.js 库(如https://cdn.jsdelivr.net/npm/p5@1.4.1/lib/p5.min.js),随后加载本地sketch.js。运行方式:
- 在浏览器中直接打开对应目录下的
index.html(p5.js 的本地文件无需构建步骤); - 或在 p5.js Web Editor 中新建草图,把仓库源码复制进去运行;
- 学习型调试:修改
angle += 0.05的步长、map()的映射范围、constrain()的上下界,观察转速、转向与"惯性"的变化,直观理解每个参数的作用。
9. 小结与下一步
围绕存档文档"角度 + 角运动"的主题,本文建立了完整的技术链路:
| 概念 | 代码要素 | 仓库证据 |
|---|---|---|
| 角度单位 | angleMode(RADIANS/DEGREES)、TAU | 3.1 源码 |
| 旋转绘制 | translate+rotate+rectMode(CENTER) | 同上 |
| 角运动三要素 | angle、angleV、angleA逐帧积分 | rect-acc 源码 |
| 交互控制 | heading()、mouseDragged/mouseReleased | grab 源码 |
| 融合物理 | 角加速度映射自acc.y,push/pop隔离状态 | mover.js |
掌握了角度与角运动之后,第 3 章的下一站是三角学与极坐标——用sin、cos把角度换算成位置;再往后是简谐运动、摆的仿真与弹簧,它们都以本节的角度概念为地基。建议动手练习:把本节任何一个示例中的矩形换成ellipse、triangle或文字,观察旋转支点与绘制顺序对视觉效果的影响——这是建立"旋转坐标系"直觉最快的方式。
- 教程
- 文档
- 示例工程
【免费下载链接】website-archive
Archive of the Coding Train website (first version)
相关推荐
The Nature of Code p5.js 示例项目教程
The Nature of Code p5.js 示例项目教程 1. 项目介绍 The Nature of Code p5.js 是一个开源项目,旨在将《The
示例工程文档/教程前端用 p5.js 的 sin() 函数模拟简谐运动:Nature of Code 3.5 完整实战指南
用 p5.js 的 sin 函数模拟简谐运动:Nature of Code 3.5 完整实战指南 本文基于 Coding Train 网站归档仓库中《Natur
教程文档示例工程Nature of Code 5.18:用 p5.js 与 matter.js 搭建 2D 物理世界(Coding Train 仓库实战)
Nature of Code 5.18:用 p5.js 与 matter.js 搭建 2D 物理世界(Coding Train 仓库实战) 本篇文章以 Codi
教程文档示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考