Math — 数学库

RoninNetease 数学库提供了三维向量、四维向量、4×4 矩阵、插值工具、时间常量以及客户端/服务端的碰撞检测工具。所有 API 以模块级函数形式导出,底层使用网易引擎 SDK 的 Vector3Matrix 类型。


1. 模块结构

architect.math
├── vec3.py      ← Vector3 三维向量运算(30+ 函数)
├── vec4.py      ← Vector4 四维齐次坐标
├── mat4.py      ← 4×4 变换矩阵(右手系,行向量左乘)
├── double.py    ← 标量插值与截断、常量(inf, epsilon)
├── unit.py      ← time 时间常量类
├── common.py    ← 汇总导出(vec3 + mat4 + double + unit)
├── utils.py     ← 客户端工具(屏幕转换、射线、碰撞检测)
└── utilsServer.py ← 服务端工具(碰撞检测、实体查询)

common.py 聚合导出了 vec3.*mat4double.*unit.*,一行导入即可使用所有基础数学函数:

from architect.math.common import *

2. 向量运算 — vec3.py

底层使用引擎 SDK 的 Vector3 类型。所有函数操作 Vector3 对象。

2.1 构造与转换

from architect.math.vec3 import vec, tup

v = vec()                    # Vector3(0, 0, 0)
v = vec((1.0, 2.0, 3.0))    # 从 tuple 创建
v = vec(1.0, 2.0, 3.0)      # 从三个 float 创建
v = vec(someObject)          # 从有 .x/.y/.z 属性的对象创建(如 Vector4)
t = tup(v)                   # Vector3 → (x, y, z) tuple

vec() 支持 4 种输入类型:无参(零向量)、float, float, floattuple、以及任何有 x/y/z 属性的对象。

2.2 四则运算

from architect.math.vec3 import add, sub, mul, div

add(v1, v2)    # v1 + v2(返回 Vector3)
sub(v1, v2)    # v1 - v2
mul(v, 2.0)    # v * 2.0(标量乘)
div(v, 2.0)    # v / 2.0(标量除)

Vector3 原生支持 + - * / 运算符,这些包装函数直接使用引擎底层实现。

2.3 向量代数

from architect.math.vec3 import dot, cross, modulo, moduloSqrt, normalize

dot(v1, v2)          # 点积(返回 float)
cross(v1, v2)        # 叉积 Vector3.Cross(a, b)
modulo(v)            # 向量长度 Vector3.Length(a)
moduloSqrt(v)        # 长度平方 Vector3.LengthSquared(a)
normalize(v)         # 单位化 Vector3.Normalized(a)

2.4 插值与钳制

from architect.math.vec3 import lerp, nlerp, clamp, compare, vabs

lerp(a, b, t)        # 线性插值 a*(1-t) + b*t(t ∈ [0, 1])
nlerp(a, b, t)       # 归一化线性插值(a、b 需为单位向量)

clamp(v, min, max)   # 将向量长度限制在 [min_len, max_len]
                     # len < min → 拉伸到 min
                     # len > max → 截断到 max

compare(a, b)        # 比较长度平方:>0 表示 |a| > |b|,<0 表示 |a| < |b|,=0 表示相等
vabs(v)              # 各分量取绝对值 Vector3(abs(x), abs(y), abs(z))

clamp 实现细节:

def clamp(v, min, max):
    lenSqrt = Vector3.LengthSquared(v)
    if lenSqrt > max * max:
        return v * (max / math.sqrt(lenSqrt))   # 截断到 max 长度
    elif lenSqrt < min * min:
        return v * (min / math.sqrt(lenSqrt))   # 拉伸到 min 长度
    return v

3. 四维向量 — vec4.py

Vector4 是框架自定义的齐次坐标类,用于矩阵变换后的 4D 结果(如 transformPoint 返回 Vector4)。

3.1 构造

from architect.math.vec4 import Vector4, vec4, tup4

v = vec4()                  # Vector4(0, 0, 0, 1.0)
v = vec4(1.0, 2.0, 3.0)    # Vector4(1, 2, 3, 1.0)
v = vec4(1.0, 2.0, 3.0, 0.5)  # Vector4(1, 2, 3, 0.5)
v = vec4((1.0, 2.0, 3.0))  # 从 tuple
v = vec4(someVector3)       # 从 Vector3 提升(w=1.0)
v = vec4(existingVector4)   # 直通原样返回

t = tup4(v)                 # (x, y, z, w)

3.2 运算

from architect.math.vec4 import Vector4

a = Vector4(1, 2, 3, 1)
b = Vector4(0, 0, 0, 1)

c = a + b        # 分量加法
d = a - b        # 分量减法
e = a * 2.0      # 标量乘(各分量乘)
f = a * b        # 分量乘(Hadamard 积)
g = a / 2.0      # 标量除

3.3 与 Vector3 互转

from architect.math.vec3 import vec

# Vector4 → Vector3(丢弃 w 分量)
v3 = vec(someVector4)

# Vector3 → Vector4(w 自动设为 1.0)
v4 = vec4(someVector3)

4. 矩阵函数 — mat4.py

操作引擎 SDK 的 Matrix 对象(4×4 矩阵,行向量左乘v' = v * M),右手坐标系(摄像机看向 -Z 方向)。

4.1 基础矩阵

from architect.math.mat4 import identity, translate, scale, rotateX, rotateY, rotateZ, rotateAxis, rotateXYZ

identity()                        # 4×4 单位矩阵

translate(vec((x, y, z)))         # 平移矩阵
scale(vec((sx, sy, sz)))          # 缩放矩阵

rotateX(angle)                    # 绕 X 轴旋转(弧度)
rotateY(angle)                    # 绕 Y 轴旋转(弧度)
rotateZ(angle)                    # 绕 Z 轴旋转(弧度)
rotateAxis(axis_vec, angle)       # 绕任意轴旋转(Rodrigues 公式)
rotateXYZ(rz, ry, rx)             # 组合旋转:rotateZ * rotateY * rotateX

rotateXYZ 旋转顺序: 先绕 Z、再绕 Y、最后绕 X(矩阵乘法从右到左应用)。参数名 (roll, yaw, pitch) 对应 (z 旋转, y 旋转, x 旋转)

rotateX / rotateY 的符号约定:

# rotateX: 绕 X 轴逆时针(从 +X 看向 -X 方向)
# 注意 Y 分量使用 -s(sin 取负),这是右手坐标系的标准
| 1   0   0   0 |
| 0   c  -s   0 |
| 0   s   c   0 |
| 0   0   0   1 |

# rotateY: 绕 Y 轴逆时针(从 +Y 看向 -Y 方向)  
# 注意 Z 分量使用 -s
| c   0   s   0 |
| 0   1   0   0 |
| -s  0   c   0 |
| 0   0   0   1 |

4.2 观察与投影矩阵

from architect.math.mat4 import lookAt, perspective

lookAt(eye, target, up)
# 右手系观察矩阵,摄像机看向 -Z 方向
# eye: 摄像机位置 (Vector3)
# target: 目标点 (Vector3)
# up: 上方向 (Vector3),通常为 vec((0, 1, 0))

perspective(fov_degrees, aspect, near, far)
# 右手系透视投影矩阵
# fov_degrees: 垂直视场角(角度制)
# aspect: 宽高比 (width / height)
# near/far: 近/远裁剪面
# NDC z 范围映射到 [-1, 1]

4.3 矩阵运算

from architect.math.mat4 import multiply, transpose, inverse

multiply(a, b)     # a * b(使用 Matrix.matrix4_multiply)
transpose(m)       # 转置(m.Transpose())
inverse(m)         # 逆矩阵(m.Inverse())

4.4 变换

from architect.math.mat4 import transform, transformPoint, transformVector, localToWorld, worldToLocal

# 标准模型变换:M_final = M_parent * T * R * S
transform(m, translate_vec, rotate_vec, scale_vec)
# m: 父矩阵(通常为 identity())
# translate_vec: 平移 Vector3
# rotate_vec: 旋转 Vector3(分量 = z/y/x 弧度)
# scale_vec: 缩放 Vector3

# 用矩阵变换点(w=1,含平移)
transformPoint(mat, point)        # 返回 Vector4

# 用矩阵变换向量(w=0,忽略平移)
transformVector(mat, vector)      # 返回 Vector3

# 坐标空间转换
localToWorld(modelMatrix, localPoint)   # 模型空间 → 世界空间
worldToLocal(modelMatrix, worldPoint)   # 世界空间 → 模型空间

transformPointtransformVector 源码对比:

# transformPoint 手动做 m * [x, y, z, 1]^T
rx = m[0,0]*x + m[0,1]*y + m[0,2]*z + m[0,3]*1  # ← w=1,平移生效

# transformVector 手动做 m * [x, y, z, 0]^T
rx = m[0,0]*x + m[0,1]*y + m[0,2]*z             # ← w=0,平移被忽略

4.5 屏幕坐标转换

from architect.math.mat4 import worldToScreen

screenPoint = worldToScreen(modelMatrix, viewMatrix, projMatrix, viewport, worldPoint)
# modelMatrix: 模型 → 世界矩阵
# viewMatrix: 世界 → 视图(lookAt 结果)
# projMatrix: 视图 → 投影(perspective 结果)
# viewport: (width, height) 窗口尺寸
# 返回 Vector3,x/y 为屏幕坐标,z 为深度

内部使用 mvpMatrix = projection * view * model,经过透视除法后映射到屏幕坐标系。


5. 插值与常量 — double.py

from architect.math.double import lerp, clamp, smoothstep, alerp, inf, epsilon

lerp(a, b, t)           # 线性插值 a*(1-t) + b*t
clamp(x, min, max)      # 截断到 [min, max]
smoothstep(e0, e1, x)   # 平滑阶梯 t^2(3-2t),t = clamp((x-e0)/(e1-e0), 0, 1)
alerp(start, end, t)    # 角度线性插值(自动处理 0°/360° 环绕)

inf   = 1e+10           # 大数常量
epsilon = 1e-8          # 浮点精度常量

5.1 alerp — 角度插值

对角度值进行线性插值,自动选择最短路径处理 0°/360° 的环绕问题:

from architect.math.double import alerp

alerp(350.0, 10.0, 0.5)   # 0.0  (即 360°/0°,走 20° 短路径,而非 340° 长路径)
alerp(10.0, 350.0, 0.5)   # 0.0
alerp(10.0, 100.0, 0.5)   # 55.0

# 内部实现
diff = (end - start) % 360.0
if diff > 180.0:   diff -= 360.0
elif diff < -180.0: diff += 360.0
return start + diff * t

5.2 smoothstep — 平滑曲线

smoothstep(0.0, 1.0, 0.5)  # 0.5(中间值不变)
smoothstep(0.0, 1.0, 0.25) # 约 0.156(靠近边缘时减速)

# 典型用途:动画缓入缓出
t = smoothstep(0.0, duration, elapsed)
alpha = lerp(fromVal, toVal, t)

6. 时间常量 — unit.py

from architect.math.unit import time

time.entityTick   # 0.05 秒(实体 Tick 间隔)
time.tick         # ~0.033 秒(30fps Tick 间隔)
time.ms           # 0.001 秒
time.s            # 1 秒
time.m            # 60 秒
time.h            # 3600 秒
time.d            # 86400 秒
time.w            # 604800 秒
time.y            # 31536000 秒

用法:time.s * 3 表示 3 秒,time.ms * 500 表示 500 毫秒。


7. 客户端工具 — math/utils.py

7.1 屏幕坐标工具

from architect.math.utils import screenSize, localViewMatrix, localProjectionMatrix, worldPosToScreenPos, screenToWorld

w, h = screenSize()          # 获取窗口尺寸

view = localViewMatrix()     # 从摄像机位置和朝向构建 lookAt 矩阵
proj = localProjectionMatrix()  # 从 fov 和宽高比构建 perspective 矩阵

screenPoint = worldPosToScreenPos(worldPoint)
# 世界坐标 → 屏幕坐标(内部调用 worldToScreen)

worldPoint = screenToWorld(modelMatrix, screenPoint, filterType, debug)
# 屏幕坐标 → 世界坐标(射线拾取)
# modelMatrix: 模型矩阵(通常 identity())
# screenPoint: 屏幕点 Vector3(x, y 为屏幕坐标,可忽略 z)
# filterType: RayFilterType.OnlyBlocks / .OnlyEntities 等
# debug=False: 设为 True 会绘制调试射线
# 返回 Vector3 世界坐标,或 None(未命中)

screenToWorld 实现原理: 将屏幕坐标通过逆 MVP 矩阵转换到世界空间,构建射线后用引擎 getEntitiesOrBlockFromRay 进行碰撞检测。

7.2 实体朝向与查询

from architect.math.utils import forward, facing, around, entityAabbDef

# forward: 实体前方单位向量(忽略 Y 分量,水平方向)
dirVec = forward(entityId)      # 返回 Vector3
dirVec = forward(entityId, 5.0)  # 返回长度为 5 的单位方向向量

# facing: 实体完整朝向向量(含 Y 分量)
facingVec = facing(entityId)

# around: 查询半径内的实体 ID 列表(方形区域)
nearby = around(entityId, 10.0)  # 边长 20 的正方形区域

# entityAabbDef: 获取实体头部 AABB 包围盒(基于 Molang 骨骼查询)
(min_x, min_y, min_z), (max_x, max_y, max_z) = entityAabbDef(entityId)
# 内部通过 EvalMolangExpression("q.bone_aabb('head')") 获取

7.3 碰撞检测

from architect.math.utils import boxOverlap3dClient, boxOverlap3dForward, boxOverlap3dFacing, boxOverlap3dBouding

# 任意位置和朝向的盒体碰撞检测
entities = boxOverlap3dClient(pos, rot, size, debug=False)
# pos: (x, y, z) 世界坐标
# rot: (yaw, pitch, roll) 弧度
# size: (width, height, depth) 盒体全尺寸

# 实体正前方的盒体碰撞(自动获取位置 + 朝向)
entities = boxOverlap3dForward(entityId, size, debug=False)
# size: (width, height, depth),盒体中心在实体前方 zDist 处

# 实体朝向的盒体碰撞(使用 GetRot 获取旋转 + 位置)
entities = boxOverlap3dFacing(entityId, size, debug=False)

# 自定义起终点包围盒检测
entities = boxOverlap3dBouding(start, end, forward, debug=False)
# start/end: (x, y, z) 世界坐标(包围盒的两个对角点)
# forward: (x, y, z) 朝向单位向量

7.4 包围盒点测试

from architect.math.utils import pointInBox, pointInAabb

inside = pointInBox(point, size)
# point: (x, y, z) tuple
# size: (width, height, depth) 全尺寸
# 盒体以原点为中心,范围 [-size/2, size/2]

inside = pointInAabb(point, min_vec, max_vec)
# 轴对齐包围盒,范围 [min, max]

7.5 默认过滤器

from architect.math.utils import defaultFilters

# 射线投射默认过滤器:只命中 player 和 mob
defaultFilters = {
    "any_of": [
        {"subject": "other", "test": "is_family", "value": "player"},
        {"subject": "other", "test": "is_family", "value": "mob"}
    ]
}

8. 服务端工具 — math/utilsServer.py

8.1 碰撞检测

from architect.math.utilsServer import boxOverlap3dServer, boxOverlap3dForward

# 服务端盒体碰撞(使用 Location 而非 tuple)
entities = boxOverlap3dServer(location, rot, size)
# location: Location(pos=(x,y,z), dim=dimId)
# rot: (yaw, pitch, roll) 弧度
# size: (width, height, depth)

# 实体正前方碰撞(自动获取位置 + 朝向 + 维度)
entities = boxOverlap3dForward(entityId, size)

8.2 实体查询

from architect.math.utilsServer import forward, facing, around

dirVec = forward(entityId)      # 实体前方单位向量(水平方向)
dirVec = forward(entityId, 5.0)  # 长度 5

facingVec = facing(entityId)     # 完整朝向(含 Y 分量)

nearby = around(location, radius)  # 以 Location 为中心查询方形区域实体

8.3 包围盒测试

from architect.math.utilsServer import pointInBox

inside = pointInBox(point, size)
# 与客户端版本的 pointInBox 签名相同

9. 完整示例

9.1 近战攻击检测

from architect.math.vec3 import vec, tup, normalize
from architect.math.utils import boxOverlap3dForward, forward

class CombatSystem(ClientSubsystem):
    ATTACK_SIZE = (2, 3, 2)  # (宽, 高, 深)

    def tryAttack(self):
        hits = boxOverlap3dForward(self.entityId, self.ATTACK_SIZE)
        for targetId in hits:
            self.doDamage(targetId)

9.2 摄像机追踪(利用矩阵坐标转换)

from architect.math.vec3 import vec, normalize, add, sub, modulo
from architect.math.mat4 import lookAt, perspective, WorldToScreen
from architect.math.double import clamp, smoothstep
from architect.math.unit import time

class CameraTrack(ClientSubsystem):
    def trackTarget(self, targetPos, elapsed):
        camPos = vec(self.camera.GetPosition())
        forward = normalize(sub(targetPos, camPos))
        dist = modulo(sub(targetPos, camPos))

        # 平滑变焦
        t = clamp(elapsed / (2 * time.s), 0.0, 1.0)
        desiredFov = 30.0 + (dist * 2)
        currentFov = 60.0 + (desiredFov - 60.0) * smoothstep(0.0, 1.0, t)
        return currentFov

9.3 世界坐标 → 屏幕坐标(HUD 绘制)

from architect.math.mat4 import identity, worldToScreen
from architect.math.utils import localViewMatrix, localProjectionMatrix, screenSize
from architect.math.vec3 import vec

w, h = screenSize()
screenPt = worldToScreen(
    identity(),
    localViewMatrix(),
    localProjectionMatrix(),
    (w, h),
    vec(entityPos)
)

# screenPt.x / screenPt.y 即为屏幕像素坐标
# screenPt.z 为深度值(可用于深度排序)

9.4 分帧平滑旋转(alerp 防止 360° 跳变)

from architect.math.double import alerp

class SmoothRotation(ClientSubsystem):
    def updateRotation(self, targetAngle):
        self.currentAngle = alerp(self.currentAngle, targetAngle, 0.1)
        # 0.1 的 t 值 = 指数衰减,每帧靠近 10%
        # alerp 自动选择最短路径,避免 350° → 10° 时旋转 340°

下一步