mcpField
M
math

math

github.com/drawcall-ai开发187 次安装
代码仓库

关于 math

math 是面向 Three.js 常见 3D 数学任务的指导 Skill,说明如何选用内置数学类,并处理坐标系、旋转、广告牌朝向及动画模型边界测量。

主要功能

  1. 01球坐标辅助方法
  2. 02Euler 与 Quaternion 使用指导
  3. 03世界空间广告牌朝向
  4. 04边界框测量注意事项
  5. 05蒙皮网格尺寸测量方法

适用场景

  1. 01计算相机轨道位置
  2. 02选择旋转表示方式
  3. 03让标签始终朝向相机
  4. 04测量动画角色模型

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/drawcall-ai/skills --skill math

安装后在 Claude Code 中输入/math确认已加载。

详细介绍

优先使用 Three.js 数学类(Vector3、Euler、Quaternion、Matrix4),而不是自定义实现:它们经过充分测试,能处理边界情况,并且可以与 Three.js 对象无缝集成。测量原语(如 Box3)和坐标辅助类(如 Spherical)也是如此。贯穿始终的关键在于参考坐标系——世界空间还是局部空间——如果忽略这一点,即使是内置功能也会造成误导。

本文涵盖四个方面。球面坐标与三角函数介绍如何替代手动计算 sin/cos。旋转介绍如何在 Euler 和 Quaternion 之间进行选择。公告板介绍面向摄像机的四边形;它们必须在世界空间中定向。**测量对象大小(Box3)**介绍为什么使用 setFromObject 测量蒙皮/动画角色会导致结果失真,以及应如何处理。

球面坐标与三角函数

对于球面坐标或任何涉及 sin/cos 的情况,请使用 Euler、Vector3 和 Quaternion,而不要手写三角函数。要计算轨道位置,请构造一个 Spherical 并将其读入向量。要根据累积角度确定方向,请构造一个 Euler 并将其应用于前向向量。

// Instead of manual sin/cos for orbit position:
const spherical = new Spherical(radius, polarAngle, azimuthalAngle)
position.setFromSpherical(spherical)

// For direction from angles:
const euler = new Euler(pitch, yaw, 0, 'YXZ')
const direction = new Vector3(0, 0, -1).applyEuler(euler)

旋转

旋转需要谨慎处理,具体表示方式应根据使用场景选择。对于轨道摄像机和 FPS 摄像机,使用 'YXZ' 顺序的 Euler,以便分别累积俯仰角和偏航角。对于一般旋转、插值(slerp)以及避免万向锁,请使用 Quaternion。在不同表示方式之间转换时,请考虑 Euler 顺序。始终使用单一表示方式可以减少错误。Three.js 对象同时提供 .rotation(一个 Euler)和 .quaternion(一个 Quaternion),并会自动保持两者同步。

公告板(面向摄像机的标签、生命值条和图标)

公告板四边形必须在世界空间中定向。如果它是旋转对象(敌人、炮塔)的子对象,将摄像机的朝向复制到它的局部旋转中是错误的——父对象的世界旋转仍会叠加生效,因此四边形会随父对象倾斜和旋转。这种局部旋转错误通常会导致生命值条方向不正确。

修复方法 A:在世界空间中调整朝向,并补偿父对象的世界旋转。修复方法 B(更简单):不要将标签设为移动对象的子对象,而是将它保留在世界空间中并直接设置位置。

// A — orient in world space, compensating for the parent's world rotation:
mesh.quaternion.copy(camera.quaternion) // desired WORLD rotation
const parentWorld = mesh.parent.getWorldQuaternion(new Quaternion())
mesh.quaternion.premultiply(parentWorld.invert()) // local = parentWorld⁻¹ · desiredWorld

// B (simpler) — don't parent it to the moving object; keep it in world space:
scene.add(label)
label.position.copy(targetWorldPosition).add(offset)
label.quaternion.copy(camera.quaternion)

THREE.Sprite 始终会自动面向摄像机,完全避免了这个问题——对于简单的生命值条和图标,优先使用它。

测量对象的尺寸(Box3)——注意蒙皮/绑定网格

new Box3().setFromObject(object) 对静态网格是正确的。对角色等蒙皮、绑定或动画对象则是错误且不稳定的,原因有二。首先,它会合并所有后代对象——包括远离可见身体的骨架骨骼,以及角色系统保留的任何引用或克隆子对象。其次,它会扩展到绑定姿势几何体,因此随着动画对骨骼进行姿势调整,包围盒会逐帧变化。

错误结果通常是可见身体高度的 2–3 倍(甚至更多),并会悄无声息地破坏所有基于该数值进行尺寸调整的功能。典型故障:缩放一个角色,使其匹配另一个角色测得的高度,结果却变成了巨人,因为前一个角色是在实时动画中测量的(尺寸膨胀),而另一个是在刚加载时测量的(结果正常)。

使用以下三种正确方法之一。

  • 测量 SkinnedMesh 的 geometry.boundingBox——这是稳定的绑定姿势网格范围(执行 mesh.geometry.computeBoundingBox(),然后读取 mesh.geometry.boundingBox)。注意:该包围盒位于局部几何体空间中;若要得到世界单位下的高度,需要使用网格的世界矩阵进行变换——mesh.geometry.boundingBox.clone().applyMatrix4(mesh.matrixWorld)——这会合并父节点的缩放,而 glTF 的导入缩放通常就在这里。直接读取原始值,或只乘以 mesh.scale,都会导致测量缩放后的角色时出错。
  • 在绑定、添加动画或封装之前,在加载时测量一次原始模型。
  • 完全避免测量:使用已知常量作为尺寸基准——即你设定的目标角色高度。

不要每帧对实时角色执行 setFromObject,并相信测得的高度。

评论

支持 Markdown