docs: explain Tessera modules and execution flows in Chinese
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user