core API
包 Luna-Flow/geometry3d/core 承载管线中的 3D 数据和仿射数学:向量、四边形网格、网格生成器、面法向量、背面可见性、朗伯亮度以及 4×4 齐次变换。它对相机、屏幕、终端和浏览器一无所知。向量和矩阵是 Luna-Flow/linear-algebra 中的可变稠密类型,这里以 @la 导入。
按如下方式导入:
import {
"Luna-Flow/geometry3d/core",
"Luna-Flow/linear-algebra/mutable" @la,
}
core 设计推导了下文用到的公式,core 教程通过示例逐一讲解。
常量
DEPTH_EPSILON
DEPTH_EPSILON 是整个管线共用的容差。
pub const DEPTH_EPSILON : Double = 1.0e-9
它用作:normalize_vec 返回零向量的长度阈值,Transform3::apply_point 跳过齐次除法的 w 阈值,光栅化的最小三角形面积,以及 view、frontend 和各后端包中每次深度测试的余量。
向量
所有向量辅助函数都接受并返回 @la.Vector[Double]。它们只读取所需的分量(3D 辅助函数读取下标 0..2),因此传给 3D 辅助函数的四分量向量会被视为其前三个分量。
vec3
vec3 构造三分量向量 。
pub fn vec3(Double, Double, Double) -> @mutable.Vector[Double]
vec4
vec4 构造四分量向量 ,即 Transform3 内部使用的齐次形式。
pub fn vec4(Double, Double, Double, Double) -> @mutable.Vector[Double]
sub_vec
sub_vec(a, b) 返回三维差 。
pub fn sub_vec(@mutable.Vector[Double], @mutable.Vector[Double]) -> @mutable.Vector[Double]
cross_vec
cross_vec(a, b) 返回叉积 。
pub fn cross_vec(@mutable.Vector[Double], @mutable.Vector[Double]) -> @mutable.Vector[Double]
vec_length
vec_length(v) 返回 v 所有分量上的欧几里得范数 。
pub fn vec_length(@mutable.Vector[Double]) -> Double
不要把它与返回分量个数的 @la.Vector::length 混淆。
normalize_vec
normalize_vec(v) 返回 ;当 DEPTH_EPSILON 时返回零向量 。
pub fn normalize_vec(@mutable.Vector[Double]) -> @mutable.Vector[Double]
零结果使退化面无害:它们的法向量为零,因此永远不可见、也永远不会被照亮。
test "vector helpers" {
let x = @core.vec3(1.0, 0.0, 0.0)
let y = @core.vec3(0.0, 1.0, 0.0)
let z = @core.cross_vec(x, y)
inspect(z[2], content="1")
inspect(@core.vec_length(@core.vec3(3.0, 4.0, 0.0)), content="5")
let n = @core.normalize_vec(@core.vec3(0.0, 0.0, 1.0e-12))
inspect(@core.vec_length(n), content="0")
inspect(@core.sub_vec(z, x)[0], content="-1")
}
min3, max3
min3 和 max3 返回三个数中的最小值和最大值。光栅化器用它们计算三角形的包围盒。
pub fn min3(Double, Double, Double) -> Double
pub fn max3(Double, Double, Double) -> Double
test "min3 and max3" {
inspect(@core.min3(2.0, -1.0, 0.5), content="-1")
inspect(@core.max3(2.0, -1.0, 0.5), content="2")
}
网格
QuadFace
QuadFace 是网格的一个面,由指向顶点数组的四个下标给出。
pub struct QuadFace {
a : Int
b : Int
c : Int
d : Int
}
顶点的排列使 指向封闭网格的外部。三角形存储为退化四边形,其最后一个下标重复第一个()。
TriangleFace
TriangleFace 是由三个顶点下标给出的三角形;triangulate_quad 为光栅化器生成它。
pub struct TriangleFace {
a : Int
b : Int
c : Int
}
Mesh
Mesh 是一个索引面集:顶点位置加上引用它们的四边形面。
pub struct Mesh {
vertices : Array[@mutable.Vector[Double]]
faces : Array[QuadFace]
}
Mesh、QuadFace 和 TriangleFace 的字段在包外是只读的,因此你需要从下面的生成器获得网格,并用 Transform3::apply_mesh 改变它们。
cube_mesh, sphere_mesh, cylinder_mesh, cone_mesh, triangular_pyramid_mesh, torus_mesh
这些生成器构造封闭的基本网格。
每个生成器都返回一个以原点为中心、向外环绕的网格,并把分辨率参数提升到至少 3。
| 生成器 | 形状 | 顶点数 | 面数 |
|---|---|---|---|
cube_mesh(s) | 立方体 | 8 | 6 |
sphere_mesh(r, rings, segments) | 半径为 的 UV 球,两极位于 轴上 | ||
cylinder_mesh(r, h, n) | 沿 轴、带端盖的圆柱, | ||
cone_mesh(r, h, n) | 带底面的圆锥,顶点在 ,底面在 | ||
triangular_pyramid_mesh(r, h) | 四面体,其底面三角形内接于半径为 的圆 | 4 | 4 |
torus_mesh(R, r, M, m) | 绕 轴的圆环 |
pub fn cube_mesh(Double) -> Mesh
pub fn sphere_mesh(Double, Int, Int) -> Mesh
pub fn cylinder_mesh(Double, Double, Int) -> Mesh
pub fn cone_mesh(Double, Double, Int) -> Mesh
pub fn triangular_pyramid_mesh(Double, Double) -> Mesh
pub fn torus_mesh(Double, Double, Int, Int) -> Mesh
端盖、圆锥侧面和棱锥的面都是退化四边形()。torus_mesh 还会把非正的主半径替换为 1.0、把非正的次半径替换为 0.25;其他生成器按原样使用尺寸参数。
test "mesh generators" {
let cube = @core.cube_mesh(1.0)
inspect(cube.vertices.length(), content="8")
inspect(cube.faces.length(), content="6")
let sphere = @core.sphere_mesh(2.4, 18, 36)
inspect(sphere.vertices.length(), content="614")
inspect(sphere.faces.length(), content="648")
let torus = @core.torus_mesh(2.1, 0.72, 28, 16)
inspect(torus.faces.length(), content="448")
inspect(@core.cone_mesh(1.0, 2.0, 2).faces.length(), content="6")
}
face_vertices
face_vertices(mesh, face) 按 a、b、c、d 的顺序返回 face 的四个顶点。
pub fn face_vertices(Mesh, QuadFace) -> Array[@mutable.Vector[Double]]
这些向量与 mesh.vertices 共享,而不是副本。
triangulate_quad
triangulate_quad(face) 把四边形拆成扇形 、。
pub fn triangulate_quad(QuadFace) -> Array[TriangleFace]
两个三角形都保持四边形的环绕方向。对于退化四边形,第二个三角形 面积为零,光栅化器会跳过它。
test "face vertices and triangulation" {
let cube = @core.cube_mesh(1.0)
let face = cube.faces[0]
inspect(@core.face_vertices(cube, face).length(), content="4")
let triangles = @core.triangulate_quad(face)
inspect(triangles.length(), content="2")
inspect(triangles[1].a == face.a && triangles[1].c == face.d, content="true")
}
面数
面辅助函数把顶点数组与面分开传入,因此你可以传入已变换到另一空间(例如相机空间)的顶点,同时复用原网格的面列表。
face_center
face_center(vertices, face) 返回面的四个顶点的平均值 。
pub fn face_center(Array[@mutable.Vector[Double]], QuadFace) -> @mutable.Vector[Double]
对于退化四边形,重复的顶点会被计算两次。
face_normal
face_normal(vertices, face) 返回由前三个顶点计算的单位法向量 。
pub fn face_normal(Array[@mutable.Vector[Double]], QuadFace) -> @mutable.Vector[Double]
当三个顶点共线时,它返回零向量。
face_is_visible
face_is_visible(vertices, face, eye) 是背面测试:当 时返回 true,其中 是面的中心。
pub fn face_is_visible(Array[@mutable.Vector[Double]], QuadFace, @mutable.Vector[Double]) -> Bool
face_intensity
face_intensity(vertices, face, light) 返回朗伯项 。
pub fn face_intensity(Array[@mutable.Vector[Double]], QuadFace, @mutable.Vector[Double]) -> Double
light 应为从表面指向光源的单位向量;此时结果就是入射角的余弦,位于 中。
test "face helpers" {
let cube = @core.cube_mesh(1.0)
let front = cube.faces[0] // the face at z = -1
let n = @core.face_normal(cube.vertices, front)
inspect(n[2], content="-1")
let c = @core.face_center(cube.vertices, front)
inspect(c[2], content="-1")
let eye = @core.vec3(0.0, 0.0, -4.5)
inspect(@core.face_is_visible(cube.vertices, front, eye), content="true")
inspect(@core.face_is_visible(cube.vertices, cube.faces[1], eye), content="false")
let light = @core.vec3(0.0, 0.0, -1.0)
inspect(@core.face_intensity(cube.vertices, front, light), content="1")
}
矩阵
矩阵构造函数返回 4×4 的 @la.Matrix[Double],作用于列向量 。角度以弧度为单位。
identity_matrix4
identity_matrix4() 返回 4×4 单位矩阵 。
pub fn identity_matrix4() -> @mutable.Matrix[Double]
translation_matrix
translation_matrix(x, y, z) 返回 ,其中 。
pub fn translation_matrix(Double, Double, Double) -> @mutable.Matrix[Double]
scale_matrix
scale_matrix(x, y, z) 返回 。
pub fn scale_matrix(Double, Double, Double) -> @mutable.Matrix[Double]
rotation_x, rotation_y, rotation_z
rotation_x(θ)、rotation_y(θ) 和 rotation_z(θ) 返回绕各坐标轴旋转 的旋转矩阵。
pub fn rotation_x(Double) -> @mutable.Matrix[Double]
pub fn rotation_y(Double) -> @mutable.Matrix[Double]
pub fn rotation_z(Double) -> @mutable.Matrix[Double]
它们的 3×3 块为
正角度分别把 转向 、把 转向 、把 转向 。
rotation_matrix
rotation_matrix(α, β, γ) 返回欧拉角旋转 。
pub fn rotation_matrix(Double, Double, Double) -> @mutable.Matrix[Double]
作用于列向量时,先绕 旋转,再绕 ,最后绕 ,都是绕固定的世界坐标轴。core 设计给出了完整矩阵及其万向节锁行为。
test "matrix builders" {
let r = @core.rotation_z(@math.PI / 2.0)
let p = r.unchecked_mul_vec(@core.vec4(1.0, 0.0, 0.0, 1.0))
inspect(p[1], content="1")
let t = @core.translation_matrix(1.0, 2.0, 3.0)
let q = t.unchecked_mul_vec(@core.vec4(0.0, 0.0, 0.0, 1.0))
inspect(q[2], content="3")
let m = @core.rotation_matrix(0.0, 0.0, 0.0)
inspect(m.get(0, 0) == 1.0 && m.get(0, 1) == 0.0, content="true")
}
变换
Transform3
Transform3 包装一个 4×4 齐次矩阵,并把它作用于点、方向和网格。
pub struct Transform3 {
matrix : @mutable.Matrix[Double]
}
Transform3::identity, Transform3::translation, Transform3::scale, Transform3::rotation
这些构造函数分别包装 identity_matrix4、translation_matrix、scale_matrix 和 rotation_matrix。
pub fn Transform3::identity() -> Self
pub fn Transform3::translation(Double, Double, Double) -> Self
pub fn Transform3::scale(Double, Double, Double) -> Self
pub fn Transform3::rotation(Double, Double, Double) -> Self
Transform3::rotation_euler
Transform3::rotation_euler(α, β, γ) 与 Transform3::rotation(α, β, γ) 相同;这个名字点明了角度约定。
pub fn Transform3::rotation_euler(Double, Double, Double) -> Self
Transform3::from_matrix
Transform3::from_matrix(m) 包装任意 4×4 矩阵,包括最后一行不是 的射影矩阵。
pub fn Transform3::from_matrix(@mutable.Matrix[Double]) -> Self
矩阵按引用保存而不复制,并且必须是 4×4:对其他形状,apply 系列方法会中止。
Transform3::compose
t.compose(next) 返回先应用 t、再应用 next 的变换;其矩阵为 next.matrix * t.matrix。
pub fn Transform3::compose(Self, Self) -> Self
组合满足结合律但不满足交换律。请从左到右阅读一条链:scale.compose(rotation).compose(translation) 先缩放,再旋转,最后平移。
Transform3::apply_point
t.apply_point(p) 通过 映射点 ,并返回 。
pub fn Transform3::apply_point(Self, @mutable.Vector[Double]) -> @mutable.Vector[Double]
对于仿射变换有 。当 DEPTH_EPSILON 时跳过除法,直接返回 。
Transform3::apply_direction
t.apply_direction(d) 以 映射方向 ,因此平移不影响它,并在不做除法的情况下返回前三个分量。
pub fn Transform3::apply_direction(Self, @mutable.Vector[Double]) -> @mutable.Vector[Double]
结果不会被重新归一化。管线中的法向量不是这样变换的:它们由变换后的顶点重新计算。
Transform3::apply_vertex
t.apply_vertex(v) 与 t.apply_point(v) 相同。
pub fn Transform3::apply_vertex(Self, @mutable.Vector[Double]) -> @mutable.Vector[Double]
Transform3::apply_mesh
t.apply_mesh(mesh) 返回一个新网格,其每个顶点都经 apply_point 映射,面数组保持不变。
pub fn Transform3::apply_mesh(Self, Mesh) -> Mesh
面数组与输入网格共享;由于面是不可变的,这样做是安全的。
test "transforms" {
let shift = @core.Transform3::translation(1.0, 0.0, 0.0)
let turn = @core.Transform3::rotation(0.0, 0.0, @math.PI / 2.0)
let p = @core.vec3(1.0, 0.0, 0.0)
// rotate first, then translate: (1,0,0) -> (0,1,0) -> (1,1,0)
let a = turn.compose(shift).apply_point(p)
inspect((a[0] - 1.0).abs() < 1.0e-12 && (a[1] - 1.0).abs() < 1.0e-12, content="true")
// translate first, then rotate: (1,0,0) -> (2,0,0) -> (0,2,0)
let b = shift.compose(turn).apply_point(p)
inspect((b[1] - 2.0).abs() < 1.0e-12, content="true")
// directions ignore the translation
let d = shift.apply_direction(p)
inspect(d[0], content="1")
let cube = @core.Transform3::scale(2.0, 2.0, 2.0).apply_mesh(@core.cube_mesh(1.0))
inspect(cube.vertices[6][0], content="2")
}