12 · Vector3:长度、距离与约束
长度衡量单个向量的大小,距离衡量两个位置的差。对位置 p,p.length() 是到坐标原点的距离,不是到角色或相机的距离。以下查询方法返回数值且不修改对象;其余约束和取整方法修改接收者、返回 this。
lengthSq()
无参数,返回 x²+y²+z²。(3,4,0) 返回 25。用于比较长短,避免不必要的平方根;阈值也必须平方,例如距离 5 米对应阈值 25 平方米。非常大的分量平方可能溢出,平方距离也不是任意量级都稳定。
length()
返回欧氏长度 √(x²+y²+z²)。(3,4,0) 返回 5。用于显示速度、测量位移,结果与分量使用同一单位。对非有限输入不会自动报业务错误,应在数据入口验证。
manhattanLength()
返回 |x|+|y|+|z|,(3,-4,0) 返回 7。可以表示只能沿坐标轴走动时的总路程,例如三维格点移动;允许斜走或存在障碍时,它不再等于实际最短路径,寻路启发式还应结合移动规则选择。
normalize()
无参数,除以长度,返回 this。(3,4,0) 变为 (0.6,0.8,0)。归一化去掉大小,只保留朝向;输入为速度时会丢失速度数值。
零向量在 r185 中除以 1,保持零,不会获得某个默认方向。近零但非零的噪声向量会被放大成单位向量;判断朝向前通常先用 lengthSq() 与业务容差比较。
setLength(length)
先归一化再乘指定长度,修改接收者。(3,4,0).setLength(10) 得 (6,8,0),适合将方向转换成指定速度。零向量仍为零;负参数会反向,几何长度实际为参数绝对值,通常应由业务限制为非负。
distanceTo(v)
返回 |this-v|。位置 (1,2,0) 到 (4,6,0) 的距离为 5,不修改两点。两点必须在同一空间且单位一致;世界距离应先用 getWorldPosition() 读取,而不是默认 object.position 已是世界位置。
distanceToSquared(v)
返回距离平方,同例返回 25。用于交互范围判断:actor.distanceToSquared(item) <= radius * radius。它不生成临时位移向量;平方比较可让代码更直接,但整体性能仍应测量,不能据此宣称渲染帧率一定提高。
manhattanDistanceTo(v)
返回 |x-v.x|+|y-v.y|+|z-v.z|,同例返回 7。适合轴对齐网格的距离成本,而三维测量尺通常应使用 distanceTo()。
min(v)
逐分量保留较小值:(2,8,-1).min((3,4,0))=(2,4,-1),返回 this。不是选出“长度较小的整个向量”。可累积包围盒最小角,初值常设为 (Infinity,Infinity,Infinity);若无任何输入点,必须另外处理空集合。
max(v)
逐分量保留较大值:(2,8,-1).max((3,4,0))=(3,8,0),返回 this。与 min 一起累计轴对齐范围;输入局部点得到局部包围盒,输入世界点得到世界包围盒。
clamp(min, max)
把每个分量限制在相应上下限之间,返回 this。(5,-2,3) 限制到最小 (0,0,0)、最大 (4,4,4) 后为 (4,0,3)。适合限制点在轴对齐房间内;默认各轴 min ≤ max,不会修正颠倒的边界。仅夹住物体中心不能保证整个物体都在房间内,应从允许范围扣除其半尺寸。
clampScalar(minVal, maxVal)
三轴共用同一个标量区间。(2,-3,0.5).clampScalar(-1,1) 得 (1,-1,0.5)。适合限制各轴输入,但这样可能改变方向,也不能保证最终长度不超过 1,例如 (1,1,1) 的长度为 √3。
clampLength(min, max)
按当前方向把长度限制在区间内。(3,4,0).clampLength(0,2) 得 (1.2,1.6,0)。适合最大速度限制,建议 0 ≤ min ≤ max。零向量即便 min 为正仍为零,因为没有可用方向;它也不把向量限制在盒子内。
floor()
各分量向负无穷取整。(1.8,-1.8,0.2) 得 (1,-2,0)。用于查找网格单元;负坐标的 -1.8 属于 [-2,-1) 单元,不能用向零截断代替。
ceil()
各分量向正无穷取整,同例得 (2,-1,1)。常用于计算覆盖某范围需要多少单元。它与 floor 是上下边界操作,不是四舍五入。
round()
逐分量使用 Math.round 取最近整数,同例得 (2,-2,0)。恰好半整数时向正无穷方向取,例如 -1.5 变为 -1,不要假定是对称“远离零”的舍入。返回 this,适合最近网格点吸附。
roundToZero()
逐分量截去小数,即 Math.trunc,同例得 (1,-1,0)。正值向下、负值向上,适合向零截断规则。它不是负坐标场景的通用网格索引算法。
实践:网格吸附与限速
import * as THREE from 'three';
const snapped = new THREE.Vector3();
function snapToGrid(point, gridOrigin, spacing, out) {
if (!(spacing > 0)) throw new Error('网格间距必须大于零');
// 原点偏移和点必须在同一个空间。
return out.copy(point).sub(gridOrigin).divideScalar(spacing).round()
.multiplyScalar(spacing).add(gridOrigin);
}
snapToGrid(new THREE.Vector3(1.2, 0, -1.7), new THREE.Vector3(), 0.5, snapped);
console.log(snapped.toArray()); // [1, 0, -1.5]
const input = new THREE.Vector3(1, 0, 1);
const velocity = input.clone().clampLength(0, 1).multiplyScalar(4);
console.log(velocity.length()); // 约 4
用 clampLength(0,1) 可以保留摇杆小幅输入;无条件 normalize 会把任何微小输入放大为满速。将 spacing 从 0.5 改成 1,吸附点变稀;它是位置量化,会产生跳动,不适合直接替代平滑运动。
练习(暂不附答案)
- 分别用 clampScalar 和 clampLength 限制
(1,0,1),比较方向和长度。 - 写一个拾取半径检查,使用平方距离,支持对象存在不同父节点的情况。
- 将网格吸附函数扩展为三个轴具有不同间距,并处理间距为零的输入策略。
- 对零向量和
(1e-12,0,0)调用 normalize,观察结果,设计角色静止时保留上次朝向的规则。