- 教程
- 文档
- 示例工程
【免费下载链接】website-archive
Archive of the Coding Train website (first version)
本文基于仓库归档文档 _more/archive/nature-of-code/1-Vectors/1.4-Vector-Math-II.md(Coding Train《The Nature of Code》第 1 章第 4 个视频配套笔记)展开。该视频延续向量数学的讨论,重点讲解两个在物理模拟中最基础也最常用的 PVector 操作:计算向量的大小(magnitude,
mag())与归一化向量(normalize,normalize())。读完本文,你将理解"向量长度"与"方向"这对核心概念的区别,掌握mag()、normalize()、setMag()、limit()的用法差异,并看到它们如何在仓库的加速度、摩擦力、阻力、转向行为等真实示例中驱动物体运动。
为什么需要"大小"与"方向"两个维度
在前几节中我们已经知道,向量是一个同时携带**大小(magnitude)和方向(direction)**的量。在 Processing / p5.js 中,PVector(或p5.Vector)就是用来承载这两个属性的数据结构,例如v = (3, 4)表示向右 3 个单位、向下 4 个单位(在屏幕坐标系中 y 轴向下),其大小为 5,方向指向右下。
- 视频系列起点可参考归档笔记 1.1-Vectors.md 与 1.2-PVector-class.md;
- 向量加法与减法的操作可参考 1.3-Vector-Math.md。
但在做运动模拟时,我们经常面临两个相反的需求:
- 我想知道这个向量有多长(例如物体当前速度的大小);
- 我想把这个向量只当作方向用,长度无所谓(例如"朝鼠标方向移动",但速度要统一为固定值)。
第 1 个需求由mag()解决,第 2 个需求由normalize()解决。这正是本视频的核心。
用 mag() 计算向量的大小
一个二维向量v = (x, y)的大小(模长)定义为:
mag = sqrt(x * x + y * y)也就是勾股定理:x与y是直角三角形的两条直角边,向量本身是斜边。
在 p5.js 中,调用实例方法v.mag()即可获得该向量的大小(返回浮点数):
let v = createVector(3, 4); console.log(v.mag()); // 输出 5PVector类在 Processing 中同样提供mag()方法。仓库中多个示例都依赖这一操作来读取"当前运动状态的强度":
- 在 5.5-wander/main/vehicle.js 的
arrive()行为中,先通过p5.Vector.sub(target, this.pos)得到指向目标的向量,再用force.mag()取得"距目标还有多远"(距离即向量大小),当距离小于减速半径slowRadius时,用map()把距离映射为期望速度,实现"靠近时减速、到达时停止"的缓停效果; - 在 2.4_drag/P5/mover.js 的阻力模拟中,使用
this.vel.magSq()获取速度大小的平方,用来计算流体阻力的强度(drag.setMag(c * speedSq))。magSq()是mag()的平方版本,省去一次开平方运算,在性能敏感场景中更高效,且在比较大小(如magSq < 半径²)时结果等价。
用 normalize() 得到单位向量
归一化(normalize)就是把一个向量的大小变成 1,但保持方向不变。归一化后的向量被称为单位向量(unit vector)。
数学上,归一化就是让每个分量除以向量的大小:
unit.x = v.x / mag(v) unit.y = v.y / mag(v)归一化后的向量长度恒为 1,此时它纯粹表达方向,不再携带"多快/多远"的信息。这正是运动模拟中最常见的需求:把"方向"和"速度大小"解耦,随后再单独乘以想要的速度值。
p5.js 中直接调用v.normalize()即可就地归一化。仓库里 learning/nature-of-code/1.5_unit_vector/P5/sketch.js 给出了一个完整的可运行示例,绘制一条"始终从画布中心指向鼠标、长度固定为 50"的线段:
function setup() { createCanvas(400, 400); } function draw() { background(0); let pos = createVector(200, 200); let mouse = createVector(mouseX, mouseY); let v = p5.Vector.sub(mouse, pos); // 指向鼠标的向量 v.normalize().mult(50); // 归一化后放大到长度 50 translate(width / 2, height / 2); strokeWeight(4); stroke(255); line(0, 0, v.x, v.y); }这段代码的妙处在于:无论鼠标在屏幕哪个位置,v.normalize()都先把向量压成长度为 1 的纯方向,随后.mult(50)把它拉长到固定长度 50。因此线段永远"指向鼠标且长度恒定",不会因为鼠标距离画布中心忽远忽近而忽长忽短。
手动归一化:mag() 与 div() 的组合
normalize()只是封装了"除以自身大小"这一操作。想理解它的本质,可以在不使用normalize()的情况下手写等价代码。示例源码中保留了被注释的等价写法(见 1.5_unit_vector/P5/sketch.js):
// 方式一:手动归一化 let m = v.mag(); v.div(m); // 方式二:链式归一化 + 缩放 v.normalize().mult(50); // 方式三:一步到位 v.setMag(50);三种写法完全等价:v.div(m)把x、y各除以大小,效果与normalize()相同。理解这层关系后,遇到"向量为零向量(0, 0)"的情况就要特别小心——此时mag()为 0,除以 0 会产生NaN(Infinity/NaN分量),导致后续所有基于该向量的计算失效。实际工程中应避免对零向量做归一化,或在调用前先判断if (v.mag() > 0)。
setMag():方向与大小的"一步合成"
归一化最典型的用途,是"先定方向、再定速度"。由于这个组合太常见,p5.js 提供了setMag(len)方法:在保持方向不变的前提下,直接把向量长度设置为len,等价于normalize().mult(len)的组合调用。
仓库源码中有大量setMag()的实战用法:
- 在 1.6_acceleration_vector/P5/mover.js 中,Mover 先通过
p5.Vector.sub(mouse, this.pos)获得指向鼠标的向量,再this.acc.setMag(1)把加速度大小固定为 1,让物体以恒定加速度冲向鼠标; - 在 5.2-seek/seek/vehicle.js 的转向(seek)行为中,
force.setMag(this.maxSpeed)先把期望速度向量设置为最大速度,再减去当前速度、用force.limit(this.maxForce)限制每帧可施加的转向力,这是 Craig Reynolds 经典 Steering Behavior 的核心一行; - 在 5.5-wander/main/vehicle.js 的漫游行为中,
wanderPoint.setMag(100)沿当前速度方向前推 100 像素,构造出"前方虚拟圆"的圆心,从而产生随机而平滑的转向。
归一化在力模拟中的典型应用:摩擦与阻力
归一化的价值在物理力模拟中体现得最充分。摩擦力与阻力的方向都与运动方向相反,但它们的"方向"和"大小"需要分开计算。
摩擦力(见 2.3_friction/P5/mover.js):
// 摩擦力的方向:与速度方向相反 let friction = this.vel.copy(); friction.normalize(); // 只保留方向 friction.mult(-1); // 反转为反向 // 摩擦力的大小:mu * normal let normal = this.mass; friction.setMag(mu * normal); // 重新设置大小 this.applyForce(friction);这里先normalize()提取速度方向并取反,再用setMag(mu * normal)重新赋予符合物理公式的大小——方向与大小的计算完全解耦。注释中还提供了链式简写this.vel.copy().normalize().mult(-1)。
阻力(见 2.4_drag/P5/mover.js):
let drag = this.vel.copy(); drag.normalize(); // 方向:与速度相反 drag.mult(-1); let speedSq = this.vel.magSq(); // 大小:与速度平方成正比 drag.setMag(c * speedSq); this.applyForce(drag);注意这里用magSq()而非mag()读取速度强度——因为阻力公式F = c * v²需要的正是速度大小的平方,直接用平方值省去开方再平方的往返损耗。
零向量与"除以零"的边界处理
综合mag()与normalize()的使用,一个值得铭记的边界情况是零向量。当v = (0, 0)时:
v.mag()返回 0,这在数学上是合法的;- 但
v.normalize()内部执行v.x / 0、v.y / 0,会产生NaN; - 后续
v.mult(50)、v.add(...)都会把NaN传播到位置、速度上,导致物体"瞬移消失"或渲染异常。
因此凡是可能对零向量(例如两物体完全重合、速度恰好为 0)调用归一化或setMag()的地方,建议先做保护:
if (v.mag() > 0) { v.normalize(); }在 p5.js 实现中,setMag()、limit()等同样基于mag(),对零向量的处理同样需要留意。
小结与延伸阅读
本节的核心可以浓缩为三条:
mag()返回向量的长度(勾股定理),用来读取"运动有多强/距离有多远";追求性能时可使用平方版本magSq();normalize()把向量长度压缩为 1、保留方向,得到纯方向向量(单位向量),是"方向与速度解耦"的关键操作;setMag(len)是normalize().mult(len)的便捷等价,在"给定方向、设定大小"的物理模拟中高频出现,仓库的加速度、摩擦、阻力、seek、arrive、wander 示例均直接使用它。
本视频的归档笔记位于 _more/archive/nature-of-code/1-Vectors/1.4-Vector-Math-II.md,其配套章节目录见 _more/archive/nature-of-code/1-Vectors/index.md。紧接着的下一节 1.5-Acceleration.md 将把本节的方向向量(归一化、setMag)应用于加速度,实现"物体持续加速"的经典 Mover 示例——完整可运行代码就在仓库的 learning/nature-of-code/1.6_acceleration_vector/P5/mover.js 中。动手把上面的三种归一化写法逐一替换运行,观察线段长度是否保持不变,你会对"大小与方向分离"获得最直观的体感。
- 教程
- 文档
- 示例工程
【免费下载链接】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
示例工程文档/教程前端Arco Design React Form 数据归一化实战:深入理解 normalize 与 formatter
Arco Design React Form 数据归一化实战:深入理解 normalize 与 formatter 本指南以 Arco Design React
UI组件前端设计系统探索自然法则的代码之旅:《The Nature of Code》示例项目解析与推荐
探索自然法则的代码之旅:《The Nature of Code》示例项目解析与推荐 项目介绍 欢迎来到一个独特的编程资源宝库——《The Nature of C
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考