docs: explain Tessera modules and execution flows in Chinese

This commit is contained in:
2026-11-11 23:36:03 +08:00
parent 4c3fc8b5dd
commit 1fedd21d3c
77 changed files with 1418 additions and 0 deletions
+29
View File
@@ -1,19 +1,26 @@
//! 3D 数学基础:向量、仿射矩阵、摄像机基向量、投影射线和三角形相交。
//! 矩阵组合顺序决定局部变换如何进入父坐标系;接近退化的逆矩阵、法线和射线使用 Option 拒绝无效计算。
//!
use serde::{Deserialize, Serialize};
use std::ops::{Add, Mul, Sub};
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
/// 三个有限浮点分量的数学向量,不是托管 Vec3 对象。
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
}
impl Vec3 {
/// 构造本类型并初始化其状态;资源建立和默认字段以函数体为准。
pub const fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
/// 计算向量点积,用于方向投影、光照和相交判断。
pub fn dot(self, b: Self) -> f32 {
self.x * b.x + self.y * b.y + self.z * b.z
}
/// 计算右手叉积,用于摄像机基向量和三角形法线。
pub fn cross(self, b: Self) -> Self {
Self::new(
self.y * b.z - self.z * b.y,
@@ -21,13 +28,16 @@ impl Vec3 {
self.x * b.y - self.y * b.x,
)
}
/// 计算向量长度;归一化前还需检查是否接近零。
pub fn length(self) -> f32 {
self.dot(self).sqrt()
}
/// 仅在长度有效且不退化时返回单位向量,避免除零产生非有限结果。
pub fn normalized(self) -> Option<Self> {
let length = self.length();
(length.is_finite() && length > 1e-6).then(|| self * (1.0 / length))
}
/// 检查数值、几何和资源条件是否满足本类型约束,供上层拒绝非法输入。
pub fn valid(self) -> bool {
[self.x, self.y, self.z]
.iter()
@@ -36,26 +46,31 @@ impl Vec3 {
}
impl Add for Vec3 {
type Output = Self;
/// 按分量执行向量加减或标量乘法,保持数学运算不依赖 VM 状态。
fn add(self, b: Self) -> Self {
Self::new(self.x + b.x, self.y + b.y, self.z + b.z)
}
}
impl Sub for Vec3 {
type Output = Self;
/// 按分量执行向量加减或标量乘法,保持数学运算不依赖 VM 状态。
fn sub(self, b: Self) -> Self {
Self::new(self.x - b.x, self.y - b.y, self.z - b.z)
}
}
impl Mul<f32> for Vec3 {
type Output = Self;
/// 按分量执行向量加减或标量乘法,保持数学运算不依赖 VM 状态。
fn mul(self, b: f32) -> Self {
Self::new(self.x * b, self.y * b, self.z * b)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
/// 仿射 4×4 变换矩阵,支持局部到世界的组合与求逆。
pub struct Matrix(pub [f32; 16]);
impl Default for Matrix {
/// 建立类型约定的初始值,供尚未发布或尚未交互的状态使用。
fn default() -> Self {
Self([
1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
@@ -63,6 +78,7 @@ impl Default for Matrix {
}
}
impl Matrix {
/// 求仿射世界矩阵的逆;退化或不可逆时返回空值。
pub fn inverse(self) -> Option<Self> {
let m = self.0;
let determinant = m[0] * (m[5] * m[10] - m[6] * m[9]) - m[1] * (m[4] * m[10] - m[6] * m[8])
@@ -95,6 +111,7 @@ impl Matrix {
out.0[11] = translation.z;
Some(out)
}
/// 组合父子仿射变换,调用顺序决定局部坐标最终落在哪个世界位置。
pub fn compose(self, local: Self) -> Self {
let mut out = [0.; 16];
for row in 0..4 {
@@ -106,6 +123,7 @@ impl Matrix {
}
Self(out)
}
/// 对一个位置应用矩阵,包括平移项;不要用它代替纯方向变换。
pub fn point(self, p: Vec3) -> Vec3 {
Vec3::new(
self.0[0] * p.x + self.0[1] * p.y + self.0[2] * p.z + self.0[3],
@@ -113,6 +131,7 @@ impl Matrix {
self.0[8] * p.x + self.0[9] * p.y + self.0[10] * p.z + self.0[11],
)
}
/// 由平移、角度旋转和缩放构造局部仿射矩阵。
pub fn trs(position: Vec3, rotation: Vec3, scale: Vec3) -> Self {
let (sx, cx) = rotation.x.to_radians().sin_cos();
let (sy, cy) = rotation.y.to_radians().sin_cos();
@@ -140,12 +159,14 @@ impl Matrix {
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
/// 投影策略:透视视场角或正交可见高度。
pub enum Projection {
Perspective { fov: f32 },
Orthographic { height: f32 },
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
/// 原生摄像机描述,包括观察基、近远面和投影参数;宽高比由视口提供。
pub struct Camera {
pub position: Vec3,
pub target: Vec3,
@@ -155,6 +176,7 @@ pub struct Camera {
pub projection: Projection,
}
impl Default for Camera {
/// 建立类型约定的初始值,供尚未发布或尚未交互的状态使用。
fn default() -> Self {
Self {
position: Vec3::new(0., 0., 5.),
@@ -167,18 +189,21 @@ impl Default for Camera {
}
}
#[derive(Debug, Clone, Copy)]
/// 摄像机的右、上、前单位基向量。
pub struct Basis {
pub right: Vec3,
pub up: Vec3,
pub forward: Vec3,
}
impl Camera {
/// 从观察位置、目标和上方向建立正交相机基;方向退化时拒绝。
pub fn basis(self) -> Option<Basis> {
let forward = (self.target - self.position).normalized()?;
let right = forward.cross(self.up).normalized()?;
let up = right.cross(forward).normalized()?;
Some(Basis { right, up, forward })
}
/// 检查数值、几何和资源条件是否满足本类型约束,供上层拒绝非法输入。
pub fn valid(self) -> bool {
self.position.valid()
&& self.target.valid()
@@ -196,10 +221,12 @@ impl Camera {
}
}
}
/// 把世界点投影到相机的右/上/前基向量坐标。
pub fn view(self, basis: Basis, p: Vec3) -> Vec3 {
let p = p - self.position;
Vec3::new(p.dot(basis.right), p.dot(basis.up), p.dot(basis.forward))
}
/// 从视口归一化坐标生成透视或正交射线;宽高比由具体视口提供。
pub fn ray(self, u: f32, v: f32, aspect: f32) -> Option<Ray> {
let basis = self.basis()?;
let (x, y) = (u * 2. - 1., 1. - v * 2.);
@@ -224,11 +251,13 @@ impl Camera {
}
}
#[derive(Debug, Clone, Copy)]
/// 世界坐标中的射线原点与方向,供网格和面板求交。
pub struct Ray {
pub origin: Vec3,
pub direction: Vec3,
}
impl Ray {
/// 求射线与三角形相交距离和重心坐标,拒绝退化三角形及射线起点后方的交点。
pub fn triangle(self, a: Vec3, b: Vec3, c: Vec3) -> Option<(f32, [f32; 3])> {
let ab = b - a;
let ac = c - a;
+15
View File
@@ -1,3 +1,7 @@
//! 连接 3D 平面与普通 UI 的坐标和绘制桥梁。
//! Mapping 沿嵌套面板逐层把页面指针变成纹理坐标;可见命中还须检查最近射线交点,防止点穿遮挡物。
//! compose 把各 Mount 的绘制命令按所属面板分组并递归装配;texture 再调用普通软件渲染器绘制面板内容。
//!
//! Perspective UI mapping. The same world plane supplies texture coordinates
//! during rendering and inverse pointer coordinates during event dispatch.
use super::{
@@ -11,6 +15,7 @@ use crate::{
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
/// 一次面板出现的正逆投影信息,可以链接父面板形成嵌套映射。
pub struct Mapping {
pub owner: u64,
pub key: u64,
@@ -21,6 +26,7 @@ pub struct Mapping {
pub parent: Option<Arc<Mapping>>,
}
impl Mapping {
/// 沿父面板逆投影到当前平面的 UI 像素;要求可见时使用最近射线命中拒绝被遮挡的目标。
pub fn texture_point(&self, point: Point, clipped: bool) -> Option<Point> {
let point = if let Some(parent) = &self.parent {
parent.texture_point(point, clipped)?
@@ -47,6 +53,7 @@ impl Mapping {
})?;
let panel = mesh.panel.as_ref()?;
if clipped {
// 指针落在平面投影内还不够:最近三角形必须属于本面板,不能点穿前方网格。
let hit = self.view.pick(u, v, width / height)?;
if self.view.scene.meshes[hit.instance]
.panel
@@ -56,6 +63,7 @@ impl Mapping {
return None;
}
}
// 捕获拖动可以不要求指针仍位于面板内,但仍使用原面板平面的连续逆映射。
let ray = self.view.camera.ray(u, v, width / height)?;
let center = mesh.world.point(Vec3::default());
let normal = (mesh.world.point(Vec3::new(1., 0., 0.)) - center)
@@ -81,6 +89,7 @@ impl Mapping {
(point.x.is_finite() && point.y.is_finite()).then_some(point)
}
/// 把当前面板的 UI 像素正向投影到外层页面空间,供坐标查询和交互测试使用。
pub fn project_point(&self, point: Point) -> Option<Point> {
let mesh = self.view.scene.meshes.iter().find(|mesh| {
mesh.panel
@@ -118,6 +127,7 @@ impl Mapping {
}
#[derive(Clone)]
/// 归属明确的一条内部 UI 绘制命令,装配时还保留裁剪、变换和 owner。
struct Entry {
command: crate::document::RenderCommand,
clip: Option<[f32; 4]>,
@@ -127,12 +137,14 @@ struct Entry {
/// Collect a panel's ordinary paint subtree into its texture. Nested 3D
/// commands are assembled recursively before their containing texture.
/// 按面板所属视口和稳定 key 分组绘制命令,递归将内部 UI 装配进 3D 面板表面。
pub fn compose(frame: &mut crate::document::Frame) -> anyhow::Result<()> {
use crate::document::RenderCommand;
use std::collections::{HashMap, HashSet};
let mounts: HashMap<_, _> = frame.mounts.iter().map(|mount| (mount.id, mount)).collect();
let mut groups = HashMap::<(u64, u64), Vec<Entry>>::new();
let mut roots = Vec::new();
// 每条命令都有出现归属;据此分流为窗口根命令或某个世界面板的内部命令。
for (index, command) in frame.commands.iter().enumerate() {
let owner = frame.command_mounts[index];
let mount = mounts[&owner];
@@ -172,6 +184,7 @@ pub fn compose(frame: &mut crate::document::Frame) -> anyhow::Result<()> {
owner: mount.id,
});
}
/// 递归装配一个绘制条目包含的世界 UI,检查嵌套环与深度,再生成独立不可变视图。
fn assemble(
entry: Entry,
groups: &HashMap<(u64, u64), Vec<Entry>>,
@@ -186,6 +199,7 @@ pub fn compose(frame: &mut crate::document::Frame) -> anyhow::Result<()> {
depth < 8 && path.insert(entry.owner),
"3D UI paint nesting cycle or depth budget exceeded"
);
// 只构造新的原生快照,不写回源 Scene3D;同一业务场景仍可被其他视口共享。
let mut scene = (*view.scene).clone();
for mesh in &mut scene.meshes {
if let Some(panel) = &mut mesh.panel {
@@ -226,6 +240,7 @@ pub fn compose(frame: &mut crate::document::Frame) -> anyhow::Result<()> {
Ok(())
}
/// 为一个面板分配有限像素表面,并调用普通 UI 软件渲染;当前实现不会按面板内容独立缓存纹理。
pub fn texture(panel: &super::Panel) -> Result<Vec<u32>, &'static str> {
crate::software_renderer::reserve_pixels(panel.width as usize * panel.height as usize)?;
let mut pixels =
+17
View File
@@ -1,3 +1,6 @@
//! 不可变世界空间 BVH。用网格变换后的包围盒快速筛选视锥与射线候选。
//! 空间索引只做宽阶段筛选;最终拾取仍检查真实三角形。相机变化可复用索引,几何或变换变化需要新索引。
//!
//! Immutable world-space BVH. Camera changes reuse it; it contains no VM data.
use super::{
Mesh,
@@ -5,11 +8,13 @@ use super::{
};
#[derive(Debug, Clone, Copy)]
/// 变换后网格的世界空间 AABB,做保守筛选而不是精确表面描述。
pub struct Bounds {
pub min: Vec3,
pub max: Vec3,
}
impl Bounds {
/// 变换网格顶点并求世界 AABB,作为空间索引的保守外包围。
fn mesh(mesh: &Mesh) -> Option<Self> {
let mut points = mesh
.geometry
@@ -36,6 +41,7 @@ impl Bounds {
},
))
}
/// 求两个包围盒的最小共同外包围,供 BVH 父节点使用。
fn union(self, other: Self) -> Self {
Self {
min: Vec3::new(
@@ -50,11 +56,13 @@ impl Bounds {
),
}
}
/// 取指定轴中心位置,供最长轴排序和分割使用。
fn center(self, axis: usize) -> f32 {
let min = [self.min.x, self.min.y, self.min.z];
let max = [self.max.x, self.max.y, self.max.z];
(min[axis] + max[axis]) / 2.
}
/// 用包围盒筛出射线候选;索引查询按进入距离排序,精确三角形求交在调用方完成。
fn ray(self, ray: Ray) -> Option<f32> {
let mut near = 0f32;
let mut far = f32::INFINITY;
@@ -79,6 +87,7 @@ impl Bounds {
}
Some(near)
}
/// 用相机六平面保守判断包围盒可见性,返回保持原场景顺序的候选。
fn visible(self, camera: Camera, basis: Basis, planes: &[[f32; 4]; 6]) -> bool {
let corners: [Vec3; 8] = std::array::from_fn(|index| {
camera.view(
@@ -110,6 +119,7 @@ impl Bounds {
}
}
#[derive(Debug)]
/// BVH 叶或分支;叶保存实例索引,分支保存两个子节点索引。
enum Node {
Leaf {
bounds: Bounds,
@@ -122,6 +132,7 @@ enum Node {
},
}
impl Node {
/// 读取叶/分支统一的包围盒,隐藏节点种类差异。
fn bounds(&self) -> Bounds {
match self {
Self::Leaf { bounds, .. } | Self::Branch { bounds, .. } => *bounds,
@@ -130,12 +141,14 @@ impl Node {
}
#[derive(Debug, Default)]
/// 由不可变场景建立的包围盒树,供相机视锥和射线查询复用。
pub struct Index {
bounds: Vec<Option<Bounds>>,
nodes: Vec<Node>,
root: Option<usize>,
}
impl Index {
/// 根据当前输入构造本模块的派生结果,具体的验证与资源边界见函数内步骤。
pub fn build(meshes: &[Mesh]) -> Self {
let bounds: Vec<_> = meshes.iter().map(Bounds::mesh).collect();
let mut items: Vec<_> = bounds
@@ -153,6 +166,7 @@ impl Index {
}
index
}
/// 按包围盒最长轴的中心排序并递归二分,叶节点保留少量网格候选。
fn split(&mut self, items: &mut [usize]) -> usize {
let bounds = items
.iter()
@@ -196,9 +210,11 @@ impl Index {
}
index
}
/// 检查索引覆盖的网格数量是否与场景相符;缺失缓存时允许重建。
pub fn matches(&self, meshes: usize) -> bool {
self.bounds.len() == meshes
}
/// 用包围盒筛出射线候选;索引查询按进入距离排序,精确三角形求交在调用方完成。
pub fn ray(&self, ray: Ray) -> Vec<(f32, usize)> {
let mut stack: Vec<_> = self.root.into_iter().collect();
let mut output = Vec::new();
@@ -223,6 +239,7 @@ impl Index {
output.sort_unstable_by(|a, b| a.0.total_cmp(&b.0).then(a.1.cmp(&b.1)));
output
}
/// 用相机六平面保守判断包围盒可见性,返回保持原场景顺序的候选。
pub fn visible(&self, camera: Camera, aspect: f32) -> Vec<usize> {
let Some(basis) = camera.basis() else {
return Vec::new();