Skip to main content

halfedge/
builtin_attrs.rs

1//! 内置属性模块:normal / uv / color / selection / size 作为一等公民属性。
2//!
3//! 与 [`crate::property`] 的泛型属性系统互补,本模块定义强类型 Newtype
4//! 包装,让顶点法向 / 顶点 UV / 顶点颜色 / 面法向 / 选择态 / 边面色 /
5//! 可视化尺寸成为内置属性:
6//!
7//! - [`VertexNormal`]:`[f64; 3]`,与 `geometry::vertex_normal` 一致;
8//! - [`VertexColor`]:`[f64; 3]`,RGB 通道 ∈ [0, 1];
9//! - [`VertexUv`]:`[f64; 2]`,纹理坐标;
10//! - [`FaceNormal`]:`[f64; 3]`,与 `geometry::face_normal` 一致;
11//! - [`VertexSelected`] / [`HalfEdgeSelected`] / [`FaceSelected`]:`bool` 选择态;
12//! - [`HalfEdgeColor`] / [`FaceColor`]:`[f64; 3]`,边/面颜色;
13//! - [`VertexSize`]:`f64`,顶点显示半径;
14//! - [`HalfEdgeThickness`]:`f64`,边粗细(twin 同步);
15//! - [`FaceOpacity`]:`f64`,面透明度 ∈ [0, 1]。
16//!
17//! 通过 [`MeshProperties`] 注册后,可通过句柄 `PropertyHandle<T>` 读写。
18//! [`io`] 模块的 OBJ/PLY 路径会感知这些属性并双向同步。
19
20use crate::ids::{FaceId, HalfEdgeId, VertexId};
21use crate::property::{MeshProperties, PropertyHandle};
22use crate::storage::MeshStorage;
23
24// ============================================================
25// Newtype 包装
26// ============================================================
27
28/// 顶点法向(单位向量)。
29#[derive(Debug, Clone, Copy, Default, PartialEq)]
30pub struct VertexNormal(pub [f64; 3]);
31
32/// 顶点颜色(RGB,每个通道 ∈ [0, 1])。
33#[derive(Debug, Clone, Copy, Default, PartialEq)]
34pub struct VertexColor(pub [f64; 3]);
35
36/// 顶点 UV 纹理坐标。
37#[derive(Debug, Clone, Copy, Default, PartialEq)]
38pub struct VertexUv(pub [f64; 2]);
39
40/// 面法向(单位向量)。
41#[derive(Debug, Clone, Copy, Default, PartialEq)]
42pub struct FaceNormal(pub [f64; 3]);
43
44/// 顶点选择态(`true` = 选中)。
45#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
46pub struct VertexSelected(pub bool);
47
48/// 半边选择态(`true` = 选中)。无向边的选择应同时设置 twin 对,
49/// 详见 [`select_edge`] / [`is_edge_selected`]。
50#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
51pub struct HalfEdgeSelected(pub bool);
52
53/// 面选择态(`true` = 选中)。
54#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
55pub struct FaceSelected(pub bool);
56
57/// 半边颜色(RGB,每通道 ∈ [0, 1])。无向边颜色应同时设置 twin 对,
58/// 详见 [`set_edge_color`] / [`edge_color`]。
59#[derive(Debug, Clone, Copy, Default, PartialEq)]
60pub struct HalfEdgeColor(pub [f64; 3]);
61
62/// 面颜色(RGB,每通道 ∈ [0, 1])。
63#[derive(Debug, Clone, Copy, Default, PartialEq)]
64pub struct FaceColor(pub [f64; 3]);
65
66/// 顶点显示大小(半径,单位与 position 一致)。默认 0 表示不渲染标记。
67#[derive(Debug, Clone, Copy, Default, PartialEq)]
68pub struct VertexSize(pub f64);
69
70/// 半边粗细(线宽,单位与 position 一致)。无向边粗细应同时设置 twin 对,
71/// 详见 [`set_edge_thickness`] / [`edge_thickness`]。
72#[derive(Debug, Clone, Copy, Default, PartialEq)]
73pub struct HalfEdgeThickness(pub f64);
74
75/// 面透明度 ∈ [0, 1]:0 = 完全透明,1 = 完全不透明。
76#[derive(Debug, Clone, Copy, Default, PartialEq)]
77pub struct FaceOpacity(pub f64);
78
79// ============================================================
80// 类型化句柄便捷函数
81// ============================================================
82
83/// 注册顶点法向属性,返回句柄。
84pub fn add_vertex_normals(props: &mut MeshProperties) -> PropertyHandle<VertexNormal> {
85    props.add_vertex_prop::<VertexNormal>()
86}
87
88/// 注册顶点颜色属性,返回句柄。
89pub fn add_vertex_colors(props: &mut MeshProperties) -> PropertyHandle<VertexColor> {
90    props.add_vertex_prop::<VertexColor>()
91}
92
93/// 注册顶点 UV 属性,返回句柄。
94pub fn add_vertex_uvs(props: &mut MeshProperties) -> PropertyHandle<VertexUv> {
95    props.add_vertex_prop::<VertexUv>()
96}
97
98/// 注册面法向属性,返回句柄。
99pub fn add_face_normals(props: &mut MeshProperties) -> PropertyHandle<FaceNormal> {
100    props.add_face_prop::<FaceNormal>()
101}
102
103/// 注册顶点选择态属性,返回句柄。
104pub fn add_vertex_selection(props: &mut MeshProperties) -> PropertyHandle<VertexSelected> {
105    props.add_vertex_prop::<VertexSelected>()
106}
107
108/// 注册半边选择态属性,返回句柄。
109pub fn add_halfedge_selection(props: &mut MeshProperties) -> PropertyHandle<HalfEdgeSelected> {
110    props.add_halfedge_prop::<HalfEdgeSelected>()
111}
112
113/// 注册面选择态属性,返回句柄。
114pub fn add_face_selection(props: &mut MeshProperties) -> PropertyHandle<FaceSelected> {
115    props.add_face_prop::<FaceSelected>()
116}
117
118/// 注册半边颜色属性,返回句柄。
119pub fn add_halfedge_colors(props: &mut MeshProperties) -> PropertyHandle<HalfEdgeColor> {
120    props.add_halfedge_prop::<HalfEdgeColor>()
121}
122
123/// 注册面颜色属性,返回句柄。
124pub fn add_face_colors(props: &mut MeshProperties) -> PropertyHandle<FaceColor> {
125    props.add_face_prop::<FaceColor>()
126}
127
128/// 注册顶点显示大小属性,返回句柄。
129pub fn add_vertex_sizes(props: &mut MeshProperties) -> PropertyHandle<VertexSize> {
130    props.add_vertex_prop::<VertexSize>()
131}
132
133/// 注册半边粗细属性,返回句柄。
134pub fn add_halfedge_thickness(props: &mut MeshProperties) -> PropertyHandle<HalfEdgeThickness> {
135    props.add_halfedge_prop::<HalfEdgeThickness>()
136}
137
138/// 注册面透明度属性,返回句柄。
139pub fn add_face_opacity(props: &mut MeshProperties) -> PropertyHandle<FaceOpacity> {
140    props.add_face_prop::<FaceOpacity>()
141}
142
143// ============================================================
144// 批量计算与填充
145// ============================================================
146
147/// 为所有顶点计算法向并写入属性。
148///
149/// 法向通过 [`crate::geometry::vertex_normal`] 计算(面法向面积加权)。
150/// 若某顶点法向不可计算(孤立 / 边界退化),跳过该顶点。
151pub fn populate_vertex_normals(
152    mesh: &crate::storage::MeshStorage,
153    props: &mut MeshProperties,
154    handle: PropertyHandle<VertexNormal>,
155) {
156    for v in mesh.vertex_ids() {
157        if let Some(n) = crate::geometry::vertex_normal(mesh, v) {
158            props.set_vertex_prop(handle, v, VertexNormal(n));
159        }
160    }
161}
162
163/// 为所有面计算法向并写入属性。
164pub fn populate_face_normals(
165    mesh: &crate::storage::MeshStorage,
166    props: &mut MeshProperties,
167    handle: PropertyHandle<FaceNormal>,
168) {
169    for f in mesh.face_ids() {
170        if let Some(n) = crate::geometry::face_normal(mesh, f) {
171            props.set_face_prop(handle, f, FaceNormal(n));
172        }
173    }
174}
175
176// ============================================================
177// IO 辅助:导出/导入属性
178// ============================================================
179
180/// 收集所有顶点的法向(按 `vertex_ids()` 顺序)。
181///
182/// 若属性未注册或某顶点未设置法向,对应位置填 `[0,0,0]`。
183pub fn collect_vertex_normals(
184    mesh: &crate::storage::MeshStorage,
185    props: &MeshProperties,
186    handle: PropertyHandle<VertexNormal>,
187) -> Vec<[f64; 3]> {
188    mesh.vertex_ids()
189        .map(|v| {
190            props
191                .get_vertex_prop(handle, v)
192                .map(|n| n.0)
193                .unwrap_or([0.0, 0.0, 0.0])
194        })
195        .collect()
196}
197
198/// 收集所有顶点的 UV(按 `vertex_ids()` 顺序)。
199pub fn collect_vertex_uvs(
200    mesh: &crate::storage::MeshStorage,
201    props: &MeshProperties,
202    handle: PropertyHandle<VertexUv>,
203) -> Vec<[f64; 2]> {
204    mesh.vertex_ids()
205        .map(|v| {
206            props
207                .get_vertex_prop(handle, v)
208                .map(|uv| uv.0)
209                .unwrap_or([0.0, 0.0])
210        })
211        .collect()
212}
213
214/// 收集所有顶点的颜色(按 `vertex_ids()` 顺序)。
215pub fn collect_vertex_colors(
216    mesh: &crate::storage::MeshStorage,
217    props: &MeshProperties,
218    handle: PropertyHandle<VertexColor>,
219) -> Vec<[f64; 3]> {
220    mesh.vertex_ids()
221        .map(|v| {
222            props
223                .get_vertex_prop(handle, v)
224                .map(|c| c.0)
225                .unwrap_or([1.0, 1.0, 1.0])
226        })
227        .collect()
228}
229
230/// 从顶点位置数组与可选属性数组重建属性系统。
231///
232/// 适用于 OBJ/PLY 解析后的属性同步:传入按顶点顺序排列的法向/UV/颜色
233/// 切片,自动注册属性并填充。任一切片为 `None` 则跳过对应属性。
234pub fn install_vertex_attrs(
235    props: &mut MeshProperties,
236    normals: Option<&[[f64; 3]]>,
237    uvs: Option<&[[f64; 2]]>,
238    colors: Option<&[[f64; 3]]>,
239    vertex_ids: &[VertexId],
240) {
241    if let Some(ns) = normals {
242        let h = add_vertex_normals(props);
243        for (i, &v) in vertex_ids.iter().enumerate() {
244            if let Some(n) = ns.get(i) {
245                props.set_vertex_prop(h, v, VertexNormal(*n));
246            }
247        }
248    }
249    if let Some(uvs_arr) = uvs {
250        let h = add_vertex_uvs(props);
251        for (i, &v) in vertex_ids.iter().enumerate() {
252            if let Some(uv) = uvs_arr.get(i) {
253                props.set_vertex_prop(h, v, VertexUv(*uv));
254            }
255        }
256    }
257    if let Some(cs) = colors {
258        let h = add_vertex_colors(props);
259        for (i, &v) in vertex_ids.iter().enumerate() {
260            if let Some(c) = cs.get(i) {
261                props.set_vertex_prop(h, v, VertexColor(*c));
262            }
263        }
264    }
265}
266
267// ============================================================
268// OBJ 属性感知 IO
269// ============================================================
270
271use crate::io::{ObjError, build_mesh_from_polygons};
272
273/// 解析 OBJ 文本,同时提取 `vn`(顶点法向)与 `vt`(顶点 UV)属性。
274///
275/// 返回 `(mesh, props)`,其中 `props` 已注册 `VertexNormal` 与 `VertexUv`。
276/// 颜色信息不在标准 OBJ 中,需通过 PLY 通道。
277///
278/// 面 `f` 行支持 `v/vt/vn` 形式:本函数按面顶点顺序收集 `vt`/`vn` 索引,
279/// 并按顶点位置去重后写入属性。若某顶点未被任何面引用,不写入属性。
280pub fn parse_obj_with_attrs(
281    text: &str,
282) -> Result<(crate::storage::MeshStorage, MeshProperties), ObjError> {
283    let mut vertices: Vec<[f64; 3]> = Vec::new();
284    let mut texcoords: Vec<[f64; 2]> = Vec::new();
285    let mut normals: Vec<[f64; 3]> = Vec::new();
286    let mut faces: Vec<Vec<u32>> = Vec::new();
287    // 每个面顶点 (vt_idx, vn_idx)(1-based,0 表示未指定)
288    let mut face_attrs: Vec<Vec<(i64, i64)>> = Vec::new();
289
290    for (line_no, raw) in text.lines().enumerate() {
291        let line = raw.trim();
292        if line.is_empty() || line.starts_with('#') {
293            continue;
294        }
295        let mut tokens = line.split_whitespace();
296        let kind = match tokens.next() {
297            Some(k) => k,
298            None => continue,
299        };
300        match kind {
301            "v" => {
302                let coords: Vec<f64> = tokens
303                    .take(3)
304                    .map(|t| {
305                        t.parse::<f64>().map_err(|_| ObjError::Parse {
306                            line: line_no + 1,
307                            msg: format!("无法解析顶点坐标: {}", t),
308                        })
309                    })
310                    .collect::<Result<Vec<_>, _>>()?;
311                if coords.len() != 3 {
312                    return Err(ObjError::Parse {
313                        line: line_no + 1,
314                        msg: "顶点行缺少坐标分量".into(),
315                    });
316                }
317                vertices.push([coords[0], coords[1], coords[2]]);
318            }
319            "vt" => {
320                let coords: Vec<f64> = tokens
321                    .take(2)
322                    .map(|t| {
323                        t.parse::<f64>().map_err(|_| ObjError::Parse {
324                            line: line_no + 1,
325                            msg: format!("无法解析纹理坐标: {}", t),
326                        })
327                    })
328                    .collect::<Result<Vec<_>, _>>()?;
329                if coords.len() == 2 {
330                    texcoords.push([coords[0], coords[1]]);
331                }
332            }
333            "vn" => {
334                let coords: Vec<f64> = tokens
335                    .take(3)
336                    .map(|t| {
337                        t.parse::<f64>().map_err(|_| ObjError::Parse {
338                            line: line_no + 1,
339                            msg: format!("无法解析法向: {}", t),
340                        })
341                    })
342                    .collect::<Result<Vec<_>, _>>()?;
343                if coords.len() == 3 {
344                    normals.push([coords[0], coords[1], coords[2]]);
345                }
346            }
347            "f" => {
348                let mut verts: Vec<i64> = Vec::new();
349                let mut attrs: Vec<(i64, i64)> = Vec::new();
350                for t in tokens {
351                    // 三种形式:v / v/vt / v/vt/vn / v//vn
352                    let parts: Vec<&str> = t.split('/').collect();
353                    let v = parts[0].parse::<i64>().map_err(|_| ObjError::Parse {
354                        line: line_no + 1,
355                        msg: format!("无法解析面索引: {}", t),
356                    })?;
357                    let vt = parts
358                        .get(1)
359                        .and_then(|s| s.parse::<i64>().ok())
360                        .unwrap_or(0);
361                    let vn = parts
362                        .get(2)
363                        .and_then(|s| s.parse::<i64>().ok())
364                        .unwrap_or(0);
365                    verts.push(v);
366                    attrs.push((vt, vn));
367                }
368                if verts.len() < 3 {
369                    return Err(ObjError::NotTriangular {
370                        line: line_no + 1,
371                        face_verts: verts.len(),
372                    });
373                }
374                let to_zero = |i: i64| -> Result<u32, ObjError> {
375                    let zero_based = if i > 0 {
376                        (i - 1) as usize
377                    } else if i < 0 {
378                        let n = vertices.len() as i64;
379                        if n + i < 0 {
380                            return Err(ObjError::IndexOutOfRange {
381                                line: line_no + 1,
382                                idx: i,
383                                vertex_count: vertices.len(),
384                            });
385                        }
386                        (n + i) as usize
387                    } else {
388                        return Err(ObjError::Parse {
389                            line: line_no + 1,
390                            msg: "面索引不能为 0".into(),
391                        });
392                    };
393                    if zero_based >= vertices.len() {
394                        return Err(ObjError::IndexOutOfRange {
395                            line: line_no + 1,
396                            idx: i,
397                            vertex_count: vertices.len(),
398                        });
399                    }
400                    Ok(zero_based as u32)
401                };
402                let indices: Vec<u32> = verts
403                    .iter()
404                    .map(|&i| to_zero(i))
405                    .collect::<Result<_, _>>()?;
406                faces.push(indices);
407                face_attrs.push(attrs);
408            }
409            _ => {}
410        }
411    }
412
413    let mesh = build_mesh_from_polygons(&vertices, &faces)
414        .expect("OBJ indices already validated during parsing");
415    let mut props = MeshProperties::new();
416
417    // 把 vn / vt 属性按面顶点关联到对应顶点
418    // 注意:同一顶点可能被多个面引用,后面的覆盖前面
419    let mut vert_normal: Vec<Option<[f64; 3]>> = vec![None; vertices.len()];
420    let mut vert_uv: Vec<Option<[f64; 2]>> = vec![None; vertices.len()];
421    for (face_idx, attrs) in face_attrs.iter().enumerate() {
422        let face = &faces[face_idx];
423        for (k, &(vt, vn)) in attrs.iter().enumerate() {
424            let v = face[k] as usize;
425            if vn > 0 {
426                let ni = (vn - 1) as usize;
427                if ni < normals.len() {
428                    vert_normal[v] = Some(normals[ni]);
429                }
430            }
431            if vt > 0 {
432                let ti = (vt - 1) as usize;
433                if ti < texcoords.len() {
434                    vert_uv[v] = Some(texcoords[ti]);
435                }
436            }
437        }
438    }
439
440    let ids: Vec<VertexId> = mesh.vertex_ids().collect();
441    let ns: Vec<[f64; 3]> = vert_normal.iter().filter_map(|x| *x).collect();
442    let uvs_v: Vec<[f64; 2]> = vert_uv.iter().filter_map(|x| *x).collect();
443    let has_normals = !ns.is_empty();
444    let has_uvs = !uvs_v.is_empty();
445
446    if has_normals {
447        let h = add_vertex_normals(&mut props);
448        for (i, &v) in ids.iter().enumerate() {
449            if let Some(n) = vert_normal.get(i).and_then(|x| *x) {
450                props.set_vertex_prop(h, v, VertexNormal(n));
451            }
452        }
453    }
454    if has_uvs {
455        let h = add_vertex_uvs(&mut props);
456        for (i, &v) in ids.iter().enumerate() {
457            if let Some(uv) = vert_uv.get(i).and_then(|x| *x) {
458                props.set_vertex_prop(h, v, VertexUv(uv));
459            }
460        }
461    }
462
463    Ok((mesh, props))
464}
465
466/// 把网格与属性序列化为带 `vn`/`vt` 的 OBJ 文本。
467///
468/// 仅写出已设置属性的顶点;未设置属性的顶点写 0 占位。
469pub fn format_obj_with_attrs(
470    mesh: &crate::storage::MeshStorage,
471    props: &MeshProperties,
472    normal_handle: Option<PropertyHandle<VertexNormal>>,
473    uv_handle: Option<PropertyHandle<VertexUv>>,
474) -> String {
475    let mut out = String::new();
476    // 顶点行
477    for v in mesh.vertex_ids() {
478        let p = mesh.get_vertex(v).map(|v| v.position).unwrap_or([0.0; 3]);
479        out.push_str(&format!("v {:.6} {:.6} {:.6}\n", p[0], p[1], p[2]));
480    }
481    // vt 行
482    let mut vt_lines: Vec<String> = Vec::new();
483    if let Some(h) = uv_handle {
484        for v in mesh.vertex_ids() {
485            if let Some(uv) = props.get_vertex_prop(h, v) {
486                vt_lines.push(format!("vt {:.6} {:.6}\n", uv.0[0], uv.0[1]));
487            } else {
488                vt_lines.push("vt 0.000000 0.000000\n".into());
489            }
490        }
491    }
492    // vn 行
493    let mut vn_lines: Vec<String> = Vec::new();
494    if let Some(h) = normal_handle {
495        for v in mesh.vertex_ids() {
496            if let Some(n) = props.get_vertex_prop(h, v) {
497                vn_lines.push(format!("vn {:.6} {:.6} {:.6}\n", n.0[0], n.0[1], n.0[2]));
498            } else {
499                vn_lines.push("vn 0.000000 0.000000 0.000000\n".into());
500            }
501        }
502    }
503    for l in &vt_lines {
504        out.push_str(l);
505    }
506    for l in &vn_lines {
507        out.push_str(l);
508    }
509    // 面行:v/vt/vn 形式
510    let has_vt = !vt_lines.is_empty();
511    let has_vn = !vn_lines.is_empty();
512    for f in mesh.face_ids() {
513        let verts: Vec<VertexId> = crate::traversal::FaceVertices::new(mesh, f).collect();
514        if verts.is_empty() {
515            continue;
516        }
517        out.push('f');
518        for (i, v) in verts.iter().enumerate() {
519            let v_idx = vertex_index(mesh, *v) + 1;
520            if has_vt && has_vn {
521                out.push_str(&format!(" {}/{}/{}", v_idx, v_idx, v_idx));
522            } else if has_vt {
523                out.push_str(&format!(" {}/{}", v_idx, v_idx));
524            } else if has_vn {
525                out.push_str(&format!(" {}//{}", v_idx, v_idx));
526            } else {
527                out.push_str(&format!(" {}", v_idx));
528            }
529            let _ = i;
530        }
531        out.push('\n');
532    }
533    out
534}
535
536/// 内部辅助:按 `vertex_ids()` 顺序返回指定顶点的 1-based 索引。
537fn vertex_index(mesh: &crate::storage::MeshStorage, target: VertexId) -> usize {
538    mesh.vertex_ids().position(|v| v == target).unwrap_or(0)
539}
540
541// ============================================================
542// 选择态便捷 API
543// ============================================================
544//
545// 对 vertex / halfedge / face 三类元素提供统一的「选择 / 取消 / 切换 / 查询 /
546// 清空 / 遍历选中」接口。语义统一:未设置属性视为 `false`,避免强制初始化。
547
548// ---------- Vertex 选择 ----------
549
550/// 选中顶点 `id`。
551#[inline]
552pub fn select_vertex(props: &mut MeshProperties, h: PropertyHandle<VertexSelected>, id: VertexId) {
553    props.set_vertex_prop(h, id, VertexSelected(true));
554}
555
556/// 取消选中顶点 `id`。
557#[inline]
558pub fn deselect_vertex(
559    props: &mut MeshProperties,
560    h: PropertyHandle<VertexSelected>,
561    id: VertexId,
562) {
563    props.set_vertex_prop(h, id, VertexSelected(false));
564}
565
566/// 切换顶点 `id` 的选择态,返回切换后的新状态。
567#[inline]
568pub fn toggle_vertex_selection(
569    props: &mut MeshProperties,
570    h: PropertyHandle<VertexSelected>,
571    id: VertexId,
572) -> bool {
573    let cur = is_vertex_selected(props, h, id);
574    props.set_vertex_prop(h, id, VertexSelected(!cur));
575    !cur
576}
577
578/// 查询顶点 `id` 是否被选中。未注册或未设置返回 `false`。
579#[inline]
580pub fn is_vertex_selected(
581    props: &MeshProperties,
582    h: PropertyHandle<VertexSelected>,
583    id: VertexId,
584) -> bool {
585    props.get_vertex_prop(h, id).map(|s| s.0).unwrap_or(false)
586}
587
588/// 清空所有顶点选择(保留其他属性与其他类型的注册)。
589pub fn clear_vertex_selection(
590    mesh: &MeshStorage,
591    props: &mut MeshProperties,
592    h: PropertyHandle<VertexSelected>,
593) {
594    for v in mesh.vertex_ids() {
595        props.remove_vertex_prop(h, v);
596    }
597}
598
599/// 遍历所有选中的顶点 ID(懒迭代器)。
600pub fn selected_vertex_ids<'a>(
601    mesh: &'a MeshStorage,
602    props: &'a MeshProperties,
603    h: PropertyHandle<VertexSelected>,
604) -> impl Iterator<Item = VertexId> + 'a {
605    mesh.vertex_ids()
606        .filter(move |&v| is_vertex_selected(props, h, v))
607}
608
609/// 选中所有顶点。
610pub fn select_all_vertices(
611    mesh: &MeshStorage,
612    props: &mut MeshProperties,
613    h: PropertyHandle<VertexSelected>,
614) {
615    for v in mesh.vertex_ids() {
616        props.set_vertex_prop(h, v, VertexSelected(true));
617    }
618}
619
620/// 反转顶点选择(选中→取消,未选中→选中)。
621pub fn invert_vertex_selection(
622    mesh: &MeshStorage,
623    props: &mut MeshProperties,
624    h: PropertyHandle<VertexSelected>,
625) {
626    for v in mesh.vertex_ids() {
627        let next = !is_vertex_selected(props, h, v);
628        props.set_vertex_prop(h, v, VertexSelected(next));
629    }
630}
631
632/// 统计被选中的顶点数。
633pub fn count_selected_vertices(
634    mesh: &MeshStorage,
635    props: &MeshProperties,
636    h: PropertyHandle<VertexSelected>,
637) -> usize {
638    selected_vertex_ids(mesh, props, h).count()
639}
640
641// ---------- HalfEdge 选择(按半边) ----------
642
643/// 选中半边 `id`。
644#[inline]
645pub fn select_halfedge(
646    props: &mut MeshProperties,
647    h: PropertyHandle<HalfEdgeSelected>,
648    id: HalfEdgeId,
649) {
650    props.set_halfedge_prop(h, id, HalfEdgeSelected(true));
651}
652
653/// 取消选中半边 `id`。
654#[inline]
655pub fn deselect_halfedge(
656    props: &mut MeshProperties,
657    h: PropertyHandle<HalfEdgeSelected>,
658    id: HalfEdgeId,
659) {
660    props.set_halfedge_prop(h, id, HalfEdgeSelected(false));
661}
662
663/// 切换半边 `id` 的选择态,返回切换后的新状态。
664#[inline]
665pub fn toggle_halfedge_selection(
666    props: &mut MeshProperties,
667    h: PropertyHandle<HalfEdgeSelected>,
668    id: HalfEdgeId,
669) -> bool {
670    let cur = is_halfedge_selected(props, h, id);
671    props.set_halfedge_prop(h, id, HalfEdgeSelected(!cur));
672    !cur
673}
674
675/// 查询半边 `id` 是否被选中。未注册或未设置返回 `false`。
676#[inline]
677pub fn is_halfedge_selected(
678    props: &MeshProperties,
679    h: PropertyHandle<HalfEdgeSelected>,
680    id: HalfEdgeId,
681) -> bool {
682    props.get_halfedge_prop(h, id).map(|s| s.0).unwrap_or(false)
683}
684
685/// 清空所有半边选择(保留其他属性)。
686pub fn clear_halfedge_selection(
687    mesh: &MeshStorage,
688    props: &mut MeshProperties,
689    h: PropertyHandle<HalfEdgeSelected>,
690) {
691    for he in mesh.halfedge_ids() {
692        props.remove_halfedge_prop(h, he);
693    }
694}
695
696/// 遍历所有选中的半边 ID(懒迭代器)。
697pub fn selected_halfedge_ids<'a>(
698    mesh: &'a MeshStorage,
699    props: &'a MeshProperties,
700    h: PropertyHandle<HalfEdgeSelected>,
701) -> impl Iterator<Item = HalfEdgeId> + 'a {
702    mesh.halfedge_ids()
703        .filter(move |&he| is_halfedge_selected(props, h, he))
704}
705
706// ---------- Face 选择 ----------
707
708/// 选中面 `id`。
709#[inline]
710pub fn select_face(props: &mut MeshProperties, h: PropertyHandle<FaceSelected>, id: FaceId) {
711    props.set_face_prop(h, id, FaceSelected(true));
712}
713
714/// 取消选中面 `id`。
715#[inline]
716pub fn deselect_face(props: &mut MeshProperties, h: PropertyHandle<FaceSelected>, id: FaceId) {
717    props.set_face_prop(h, id, FaceSelected(false));
718}
719
720/// 切换面 `id` 的选择态,返回切换后的新状态。
721#[inline]
722pub fn toggle_face_selection(
723    props: &mut MeshProperties,
724    h: PropertyHandle<FaceSelected>,
725    id: FaceId,
726) -> bool {
727    let cur = is_face_selected(props, h, id);
728    props.set_face_prop(h, id, FaceSelected(!cur));
729    !cur
730}
731
732/// 查询面 `id` 是否被选中。未注册或未设置返回 `false`。
733#[inline]
734pub fn is_face_selected(
735    props: &MeshProperties,
736    h: PropertyHandle<FaceSelected>,
737    id: FaceId,
738) -> bool {
739    props.get_face_prop(h, id).map(|s| s.0).unwrap_or(false)
740}
741
742/// 清空所有面选择(保留其他属性)。
743pub fn clear_face_selection(
744    mesh: &MeshStorage,
745    props: &mut MeshProperties,
746    h: PropertyHandle<FaceSelected>,
747) {
748    for f in mesh.face_ids() {
749        props.remove_face_prop(h, f);
750    }
751}
752
753/// 遍历所有选中的面 ID(懒迭代器)。
754pub fn selected_face_ids<'a>(
755    mesh: &'a MeshStorage,
756    props: &'a MeshProperties,
757    h: PropertyHandle<FaceSelected>,
758) -> impl Iterator<Item = FaceId> + 'a {
759    mesh.face_ids()
760        .filter(move |&f| is_face_selected(props, h, f))
761}
762
763/// 选中所有面。
764pub fn select_all_faces(
765    mesh: &MeshStorage,
766    props: &mut MeshProperties,
767    h: PropertyHandle<FaceSelected>,
768) {
769    for f in mesh.face_ids() {
770        props.set_face_prop(h, f, FaceSelected(true));
771    }
772}
773
774/// 统计被选中的面数。
775pub fn count_selected_faces(
776    mesh: &MeshStorage,
777    props: &MeshProperties,
778    h: PropertyHandle<FaceSelected>,
779) -> usize {
780    selected_face_ids(mesh, props, h).count()
781}
782
783// ============================================================
784// 边级(twin 对)选择 / 颜色 API
785// ============================================================
786//
787// 无向边由两条互为 twin 的半边组成。从语义上,边的选择/颜色应当一致。
788// 下面这一组助手自动同步 twin 对,调用方只需提供任一半边 ID。
789//
790// 约定:若 `twin = None`(边界),仅作用于本半边。
791
792/// 选中由半边 `he` 表示的无向边(同时设置 `he` 与其 twin)。
793pub fn select_edge(
794    mesh: &MeshStorage,
795    props: &mut MeshProperties,
796    h: PropertyHandle<HalfEdgeSelected>,
797    he: HalfEdgeId,
798) {
799    props.set_halfedge_prop(h, he, HalfEdgeSelected(true));
800    if let Some(twin) = mesh.get_halfedge(he).and_then(|h| h.twin) {
801        props.set_halfedge_prop(h, twin, HalfEdgeSelected(true));
802    }
803}
804
805/// 取消选中由半边 `he` 表示的无向边。
806pub fn deselect_edge(
807    mesh: &MeshStorage,
808    props: &mut MeshProperties,
809    h: PropertyHandle<HalfEdgeSelected>,
810    he: HalfEdgeId,
811) {
812    props.set_halfedge_prop(h, he, HalfEdgeSelected(false));
813    if let Some(twin) = mesh.get_halfedge(he).and_then(|h| h.twin) {
814        props.set_halfedge_prop(h, twin, HalfEdgeSelected(false));
815    }
816}
817
818/// 切换无向边的选择态,返回切换后的新状态(基于 `he` 本身的状态)。
819pub fn toggle_edge_selection(
820    mesh: &MeshStorage,
821    props: &mut MeshProperties,
822    h: PropertyHandle<HalfEdgeSelected>,
823    he: HalfEdgeId,
824) -> bool {
825    let next = !is_edge_selected(mesh, props, h, he);
826    if next {
827        select_edge(mesh, props, h, he);
828    } else {
829        deselect_edge(mesh, props, h, he);
830    }
831    next
832}
833
834/// 查询由半边 `he` 表示的无向边是否被选中(任一 half 被选中即视为选中)。
835pub fn is_edge_selected(
836    mesh: &MeshStorage,
837    props: &MeshProperties,
838    h: PropertyHandle<HalfEdgeSelected>,
839    he: HalfEdgeId,
840) -> bool {
841    if is_halfedge_selected(props, h, he) {
842        return true;
843    }
844    mesh.get_halfedge(he)
845        .and_then(|h| h.twin)
846        .map(|twin| is_halfedge_selected(props, h, twin))
847        .unwrap_or(false)
848}
849
850/// 遍历所有被选中的无向边,每个边返回一次(canonical half:`he <= twin`)。
851pub fn selected_edge_ids<'a>(
852    mesh: &'a MeshStorage,
853    props: &'a MeshProperties,
854    h: PropertyHandle<HalfEdgeSelected>,
855) -> impl Iterator<Item = HalfEdgeId> + 'a {
856    mesh.halfedge_ids().filter(move |&he| {
857        if !is_halfedge_selected(props, h, he) {
858            return false;
859        }
860        // canonical:仅当 twin 不存在或 twin >= he 时输出
861        match mesh.get_halfedge(he).and_then(|h| h.twin) {
862            None => true,
863            Some(twin) => he <= twin,
864        }
865    })
866}
867
868// ---------- 边颜色(twin 同步) ----------
869
870/// 设置由半边 `he` 表示的无向边颜色(同时设置 `he` 与其 twin)。
871pub fn set_edge_color(
872    mesh: &MeshStorage,
873    props: &mut MeshProperties,
874    h: PropertyHandle<HalfEdgeColor>,
875    he: HalfEdgeId,
876    color: [f64; 3],
877) {
878    props.set_halfedge_prop(h, he, HalfEdgeColor(color));
879    if let Some(twin) = mesh.get_halfedge(he).and_then(|h| h.twin) {
880        props.set_halfedge_prop(h, twin, HalfEdgeColor(color));
881    }
882}
883
884/// 查询由半边 `he` 表示的无向边颜色(优先 `he`,未设置则尝试 twin)。
885pub fn edge_color(
886    mesh: &MeshStorage,
887    props: &MeshProperties,
888    h: PropertyHandle<HalfEdgeColor>,
889    he: HalfEdgeId,
890) -> Option<[f64; 3]> {
891    if let Some(c) = props.get_halfedge_prop(h, he).map(|c| c.0) {
892        return Some(c);
893    }
894    mesh.get_halfedge(he)
895        .and_then(|h| h.twin)
896        .and_then(|twin| props.get_halfedge_prop(h, twin).map(|c| c.0))
897}
898
899// ============================================================
900// 面颜色便捷 API
901// ============================================================
902
903/// 设置面颜色。
904#[inline]
905pub fn set_face_color(
906    props: &mut MeshProperties,
907    h: PropertyHandle<FaceColor>,
908    id: FaceId,
909    color: [f64; 3],
910) {
911    props.set_face_prop(h, id, FaceColor(color));
912}
913
914/// 查询面颜色。
915#[inline]
916pub fn face_color(
917    props: &MeshProperties,
918    h: PropertyHandle<FaceColor>,
919    id: FaceId,
920) -> Option<[f64; 3]> {
921    props.get_face_prop(h, id).map(|c| c.0)
922}
923
924/// 清空所有面颜色(保留其他属性)。
925pub fn clear_face_colors(
926    mesh: &MeshStorage,
927    props: &mut MeshProperties,
928    h: PropertyHandle<FaceColor>,
929) {
930    for f in mesh.face_ids() {
931        props.remove_face_prop(h, f);
932    }
933}
934
935// ============================================================
936// 可视化尺寸 / 粗细 / 透明度 API
937// ============================================================
938//
939// 顶点大小(半径)、半边粗细(twin 同步)、面透明度。
940// 与颜色 API 形态一致:未注册视为 0(不渲染标记)或 None。
941
942// ---------- 顶点大小 ----------
943
944/// 设置顶点 `id` 的显示大小(半径)。
945#[inline]
946pub fn set_vertex_size(
947    props: &mut MeshProperties,
948    h: PropertyHandle<VertexSize>,
949    id: VertexId,
950    size: f64,
951) {
952    props.set_vertex_prop(h, id, VertexSize(size));
953}
954
955/// 查询顶点 `id` 的显示大小。未注册或未设置返回 `None`。
956#[inline]
957pub fn vertex_size(
958    props: &MeshProperties,
959    h: PropertyHandle<VertexSize>,
960    id: VertexId,
961) -> Option<f64> {
962    props.get_vertex_prop(h, id).map(|s| s.0)
963}
964
965/// 清空所有顶点大小(保留其他属性)。
966pub fn clear_vertex_sizes(
967    mesh: &MeshStorage,
968    props: &mut MeshProperties,
969    h: PropertyHandle<VertexSize>,
970) {
971    for v in mesh.vertex_ids() {
972        props.remove_vertex_prop(h, v);
973    }
974}
975
976/// 批量设置所有顶点大小为同一值。
977pub fn set_uniform_vertex_size(
978    mesh: &MeshStorage,
979    props: &mut MeshProperties,
980    h: PropertyHandle<VertexSize>,
981    size: f64,
982) {
983    for v in mesh.vertex_ids() {
984        props.set_vertex_prop(h, v, VertexSize(size));
985    }
986}
987
988// ---------- 边粗细(twin 同步) ----------
989
990/// 设置由半边 `he` 表示的无向边粗细(同时设置 `he` 与其 twin)。
991pub fn set_edge_thickness(
992    mesh: &MeshStorage,
993    props: &mut MeshProperties,
994    h: PropertyHandle<HalfEdgeThickness>,
995    he: HalfEdgeId,
996    thickness: f64,
997) {
998    props.set_halfedge_prop(h, he, HalfEdgeThickness(thickness));
999    if let Some(twin) = mesh.get_halfedge(he).and_then(|h| h.twin) {
1000        props.set_halfedge_prop(h, twin, HalfEdgeThickness(thickness));
1001    }
1002}
1003
1004/// 查询由半边 `he` 表示的无向边粗细(优先 `he`,未设置则尝试 twin)。
1005pub fn edge_thickness(
1006    mesh: &MeshStorage,
1007    props: &MeshProperties,
1008    h: PropertyHandle<HalfEdgeThickness>,
1009    he: HalfEdgeId,
1010) -> Option<f64> {
1011    if let Some(t) = props.get_halfedge_prop(h, he).map(|t| t.0) {
1012        return Some(t);
1013    }
1014    mesh.get_halfedge(he)
1015        .and_then(|h| h.twin)
1016        .and_then(|twin| props.get_halfedge_prop(h, twin).map(|t| t.0))
1017}
1018
1019/// 设置单条半边的粗细(不同步 twin,用于非对称渲染)。
1020#[inline]
1021pub fn set_halfedge_thickness(
1022    props: &mut MeshProperties,
1023    h: PropertyHandle<HalfEdgeThickness>,
1024    he: HalfEdgeId,
1025    thickness: f64,
1026) {
1027    props.set_halfedge_prop(h, he, HalfEdgeThickness(thickness));
1028}
1029
1030/// 查询单条半边的粗细。
1031#[inline]
1032pub fn halfedge_thickness(
1033    props: &MeshProperties,
1034    h: PropertyHandle<HalfEdgeThickness>,
1035    he: HalfEdgeId,
1036) -> Option<f64> {
1037    props.get_halfedge_prop(h, he).map(|t| t.0)
1038}
1039
1040/// 清空所有半边粗细(保留其他属性)。
1041pub fn clear_halfedge_thickness(
1042    mesh: &MeshStorage,
1043    props: &mut MeshProperties,
1044    h: PropertyHandle<HalfEdgeThickness>,
1045) {
1046    for he in mesh.halfedge_ids() {
1047        props.remove_halfedge_prop(h, he);
1048    }
1049}
1050
1051/// 批量设置所有边的粗细为同一值(每条边自动同步 twin)。
1052pub fn set_uniform_edge_thickness(
1053    mesh: &MeshStorage,
1054    props: &mut MeshProperties,
1055    h: PropertyHandle<HalfEdgeThickness>,
1056    thickness: f64,
1057) {
1058    // 仅处理 canonical half,避免重复设置
1059    let canonical: Vec<HalfEdgeId> = mesh
1060        .halfedge_ids()
1061        .filter(|&he| match mesh.get_halfedge(he).and_then(|h| h.twin) {
1062            None => true,
1063            Some(twin) => he <= twin,
1064        })
1065        .collect();
1066    for he in canonical {
1067        set_edge_thickness(mesh, props, h, he, thickness);
1068    }
1069}
1070
1071// ---------- 面透明度 ----------
1072
1073/// 设置面 `id` 的透明度(0 = 完全透明,1 = 完全不透明)。
1074/// 输入会自动 clamp 到 [0, 1]。
1075#[inline]
1076pub fn set_face_opacity(
1077    props: &mut MeshProperties,
1078    h: PropertyHandle<FaceOpacity>,
1079    id: FaceId,
1080    opacity: f64,
1081) {
1082    let clamped = opacity.clamp(0.0, 1.0);
1083    props.set_face_prop(h, id, FaceOpacity(clamped));
1084}
1085
1086/// 查询面 `id` 的透明度。未注册或未设置返回 `None`。
1087#[inline]
1088pub fn face_opacity(
1089    props: &MeshProperties,
1090    h: PropertyHandle<FaceOpacity>,
1091    id: FaceId,
1092) -> Option<f64> {
1093    props.get_face_prop(h, id).map(|o| o.0)
1094}
1095
1096/// 清空所有面透明度(保留其他属性)。
1097pub fn clear_face_opacity(
1098    mesh: &MeshStorage,
1099    props: &mut MeshProperties,
1100    h: PropertyHandle<FaceOpacity>,
1101) {
1102    for f in mesh.face_ids() {
1103        props.remove_face_prop(h, f);
1104    }
1105}
1106
1107/// 批量设置所有面透明度为同一值。
1108pub fn set_uniform_face_opacity(
1109    mesh: &MeshStorage,
1110    props: &mut MeshProperties,
1111    h: PropertyHandle<FaceOpacity>,
1112    opacity: f64,
1113) {
1114    let clamped = opacity.clamp(0.0, 1.0);
1115    for f in mesh.face_ids() {
1116        props.set_face_prop(h, f, FaceOpacity(clamped));
1117    }
1118}
1119
1120// ============================================================
1121// 单元测试
1122// ============================================================
1123
1124#[cfg(test)]
1125mod tests {
1126    use super::*;
1127    use crate::storage::MeshStorage;
1128
1129    #[test]
1130    fn vertex_normal_roundtrip() {
1131        let mesh = crate::test_util::build_icosphere(1);
1132        let mut props = MeshProperties::new();
1133        let h = add_vertex_normals(&mut props);
1134        populate_vertex_normals(&mesh, &mut props, h);
1135
1136        let normals = collect_vertex_normals(&mesh, &props, h);
1137        assert_eq!(normals.len(), mesh.vertex_count());
1138        // 每个法向应是单位向量
1139        for n in &normals {
1140            let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
1141            assert!((len - 1.0).abs() < 1e-10, "法向应单位化, len={len}");
1142        }
1143    }
1144
1145    #[test]
1146    fn face_normal_roundtrip() {
1147        let mesh = crate::test_util::build_icosphere(1);
1148        let mut props = MeshProperties::new();
1149        let h = add_face_normals(&mut props);
1150        populate_face_normals(&mesh, &mut props, h);
1151
1152        let count = mesh.face_count();
1153        let mut found = 0;
1154        for f in mesh.face_ids() {
1155            if props.get_face_prop(h, f).is_some() {
1156                found += 1;
1157            }
1158        }
1159        assert_eq!(found, count);
1160    }
1161
1162    #[test]
1163    fn vertex_color_default_is_white() {
1164        let mesh = crate::test_util::build_icosphere(0);
1165        let mut props = MeshProperties::new();
1166        let h = add_vertex_colors(&mut props);
1167        // 没有设置任何颜色,应返回默认白色
1168        let colors = collect_vertex_colors(&mesh, &props, h);
1169        assert_eq!(colors.len(), mesh.vertex_count());
1170        for c in &colors {
1171            assert_eq!(*c, [1.0, 1.0, 1.0]);
1172        }
1173    }
1174
1175    #[test]
1176    fn vertex_uv_set_and_get() {
1177        let mesh = crate::test_util::build_icosphere(0);
1178        let mut props = MeshProperties::new();
1179        let h = add_vertex_uvs(&mut props);
1180        let v0 = mesh.vertex_ids().next().unwrap();
1181        props.set_vertex_prop(h, v0, VertexUv([0.5, 0.7]));
1182        let uv = props.get_vertex_prop(h, v0).map(|u| u.0);
1183        assert_eq!(uv, Some([0.5, 0.7]));
1184    }
1185
1186    #[test]
1187    fn install_vertex_attrs_handles_partial_input() {
1188        let mesh = crate::test_util::build_icosphere(0);
1189        let ids: Vec<VertexId> = mesh.vertex_ids().collect();
1190        let mut props = MeshProperties::new();
1191
1192        // 只传法向,不传 UV 与颜色
1193        let normals: Vec<[f64; 3]> = ids.iter().map(|_| [0.0, 1.0, 0.0]).collect();
1194        install_vertex_attrs(&mut props, Some(&normals), None, None, &ids);
1195
1196        assert!(props.has_vertex_prop::<VertexNormal>());
1197        assert!(!props.has_vertex_prop::<VertexUv>());
1198        assert!(!props.has_vertex_prop::<VertexColor>());
1199    }
1200
1201    #[test]
1202    fn install_vertex_attrs_all_three() {
1203        let mesh = crate::test_util::build_icosphere(0);
1204        let ids: Vec<VertexId> = mesh.vertex_ids().collect();
1205        let mut props = MeshProperties::new();
1206
1207        let normals: Vec<[f64; 3]> = ids.iter().map(|_| [0.0, 1.0, 0.0]).collect();
1208        let uvs: Vec<[f64; 2]> = ids
1209            .iter()
1210            .enumerate()
1211            .map(|(i, _)| [i as f64 * 0.1, 1.0 - i as f64 * 0.1])
1212            .collect();
1213        let colors: Vec<[f64; 3]> = ids.iter().map(|_| [0.5, 0.5, 0.5]).collect();
1214
1215        install_vertex_attrs(&mut props, Some(&normals), Some(&uvs), Some(&colors), &ids);
1216
1217        assert!(props.has_vertex_prop::<VertexNormal>());
1218        assert!(props.has_vertex_prop::<VertexUv>());
1219        assert!(props.has_vertex_prop::<VertexColor>());
1220    }
1221
1222    #[test]
1223    fn newtype_default_is_zero() {
1224        let n = VertexNormal::default();
1225        assert_eq!(n.0, [0.0, 0.0, 0.0]);
1226        let uv = VertexUv::default();
1227        assert_eq!(uv.0, [0.0, 0.0]);
1228        let c = VertexColor::default();
1229        assert_eq!(c.0, [0.0, 0.0, 0.0]);
1230        let f = FaceNormal::default();
1231        assert_eq!(f.0, [0.0, 0.0, 0.0]);
1232    }
1233
1234    #[test]
1235    fn missing_handle_returns_none() {
1236        let mesh = crate::test_util::build_icosphere(0);
1237        let props = MeshProperties::new();
1238        let h = PropertyHandle::<VertexNormal>::default();
1239        let v = mesh.vertex_ids().next().unwrap();
1240        assert!(props.get_vertex_prop(h, v).is_none());
1241    }
1242
1243    #[test]
1244    fn populate_face_normals_skips_invalid() {
1245        // 空网格不应 panic
1246        let mesh = MeshStorage::new();
1247        let mut props = MeshProperties::new();
1248        let h = add_face_normals(&mut props);
1249        populate_face_normals(&mesh, &mut props, h);
1250        assert_eq!(mesh.face_count(), 0);
1251    }
1252
1253    // ---------- 选择态测试 ----------
1254
1255    fn build_two_tri_mesh() -> (MeshStorage, [VertexId; 4], [HalfEdgeId; 6], [FaceId; 2]) {
1256        // 两三角形共享一条边:v0-v1-v2 与 v1-v3-v2
1257        let verts = vec![
1258            [0.0, 0.0, 0.0],
1259            [1.0, 0.0, 0.0],
1260            [0.0, 1.0, 0.0],
1261            [1.0, 1.0, 0.0],
1262        ];
1263        let faces = vec![[0, 1, 2], [1, 3, 2]];
1264        let mesh = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
1265        let vids: Vec<VertexId> = mesh.vertex_ids().collect();
1266        let heids: Vec<HalfEdgeId> = mesh.halfedge_ids().collect();
1267        let fids: Vec<FaceId> = mesh.face_ids().collect();
1268        assert!(vids.len() >= 4, "需要 4 个顶点,实际 {}", vids.len());
1269        assert!(heids.len() >= 6, "需要至少 6 条半边");
1270        assert_eq!(fids.len(), 2, "需要 2 个面");
1271        (
1272            mesh,
1273            [vids[0], vids[1], vids[2], vids[3]],
1274            [heids[0], heids[1], heids[2], heids[3], heids[4], heids[5]],
1275            [fids[0], fids[1]],
1276        )
1277    }
1278
1279    #[test]
1280    fn vertex_selection_basic() {
1281        let mesh = crate::test_util::build_icosphere(0);
1282        let mut props = MeshProperties::new();
1283        let h = add_vertex_selection(&mut props);
1284        let v0 = mesh.vertex_ids().next().unwrap();
1285
1286        // 初始未选中
1287        assert!(!is_vertex_selected(&props, h, v0));
1288        assert_eq!(count_selected_vertices(&mesh, &props, h), 0);
1289
1290        select_vertex(&mut props, h, v0);
1291        assert!(is_vertex_selected(&props, h, v0));
1292        assert_eq!(count_selected_vertices(&mesh, &props, h), 1);
1293
1294        // toggle 关闭
1295        let new_state = toggle_vertex_selection(&mut props, h, v0);
1296        assert!(!new_state);
1297        assert!(!is_vertex_selected(&props, h, v0));
1298
1299        // toggle 重新打开
1300        let new_state = toggle_vertex_selection(&mut props, h, v0);
1301        assert!(new_state);
1302        assert!(is_vertex_selected(&props, h, v0));
1303    }
1304
1305    #[test]
1306    fn vertex_select_all_and_invert() {
1307        let mesh = crate::test_util::build_icosphere(0);
1308        let n = mesh.vertex_count();
1309        let mut props = MeshProperties::new();
1310        let h = add_vertex_selection(&mut props);
1311
1312        select_all_vertices(&mesh, &mut props, h);
1313        assert_eq!(count_selected_vertices(&mesh, &props, h), n);
1314
1315        invert_vertex_selection(&mesh, &mut props, h);
1316        assert_eq!(count_selected_vertices(&mesh, &props, h), 0);
1317
1318        // 再反转回来
1319        invert_vertex_selection(&mesh, &mut props, h);
1320        assert_eq!(count_selected_vertices(&mesh, &props, h), n);
1321    }
1322
1323    #[test]
1324    fn vertex_clear_selection_keeps_registration() {
1325        let mesh = crate::test_util::build_icosphere(0);
1326        let mut props = MeshProperties::new();
1327        let h = add_vertex_selection(&mut props);
1328        select_all_vertices(&mesh, &mut props, h);
1329
1330        clear_vertex_selection(&mesh, &mut props, h);
1331        assert_eq!(count_selected_vertices(&mesh, &props, h), 0);
1332        // 类型注册仍保留
1333        assert!(props.has_vertex_prop::<VertexSelected>());
1334    }
1335
1336    #[test]
1337    fn selected_vertex_ids_iterator_correct() {
1338        let mesh = crate::test_util::build_icosphere(0);
1339        let mut props = MeshProperties::new();
1340        let h = add_vertex_selection(&mut props);
1341        let vids: Vec<VertexId> = mesh.vertex_ids().collect();
1342        // 选中第 0、2 个
1343        select_vertex(&mut props, h, vids[0]);
1344        select_vertex(&mut props, h, vids[2]);
1345
1346        let collected: Vec<VertexId> = selected_vertex_ids(&mesh, &props, h).collect();
1347        assert_eq!(collected.len(), 2);
1348        assert!(collected.contains(&vids[0]));
1349        assert!(collected.contains(&vids[2]));
1350    }
1351
1352    #[test]
1353    fn face_selection_basic() {
1354        let (mesh, _, _, fids) = build_two_tri_mesh();
1355        let mut props = MeshProperties::new();
1356        let h = add_face_selection(&mut props);
1357
1358        assert!(!is_face_selected(&props, h, fids[0]));
1359        select_face(&mut props, h, fids[0]);
1360        assert!(is_face_selected(&props, h, fids[0]));
1361        assert!(!is_face_selected(&props, h, fids[1]));
1362
1363        assert_eq!(count_selected_faces(&mesh, &props, h), 1);
1364
1365        select_all_faces(&mesh, &mut props, h);
1366        assert_eq!(count_selected_faces(&mesh, &props, h), 2);
1367
1368        clear_face_selection(&mesh, &mut props, h);
1369        assert_eq!(count_selected_faces(&mesh, &props, h), 0);
1370    }
1371
1372    #[test]
1373    fn halfedge_selection_basic() {
1374        let (mesh, _, heids, _) = build_two_tri_mesh();
1375        let mut props = MeshProperties::new();
1376        let h = add_halfedge_selection(&mut props);
1377
1378        select_halfedge(&mut props, h, heids[0]);
1379        assert!(is_halfedge_selected(&props, h, heids[0]));
1380        assert!(!is_halfedge_selected(&props, h, heids[1]));
1381
1382        let count = selected_halfedge_ids(&mesh, &props, h).count();
1383        assert_eq!(count, 1);
1384
1385        toggle_halfedge_selection(&mut props, h, heids[0]);
1386        assert!(!is_halfedge_selected(&props, h, heids[0]));
1387
1388        clear_halfedge_selection(&mesh, &mut props, h);
1389        assert_eq!(selected_halfedge_ids(&mesh, &props, h).count(), 0);
1390    }
1391
1392    #[test]
1393    fn edge_selection_syncs_twin_pair() {
1394        let (mesh, _, heids, _) = build_two_tri_mesh();
1395        let mut props = MeshProperties::new();
1396        let h = add_halfedge_selection(&mut props);
1397
1398        // 找一对 twin 半边
1399        let he0 = heids[0];
1400        let twin = mesh
1401            .get_halfedge(he0)
1402            .and_then(|h| h.twin)
1403            .expect("应至少有一条带 twin 的内部边");
1404
1405        // 选中 he0,twin 应也被同步选中
1406        select_edge(&mesh, &mut props, h, he0);
1407        assert!(is_halfedge_selected(&props, h, he0));
1408        assert!(is_halfedge_selected(&props, h, twin));
1409        assert!(is_edge_selected(&mesh, &props, h, he0));
1410        assert!(is_edge_selected(&mesh, &props, h, twin));
1411
1412        // 取消选中
1413        deselect_edge(&mesh, &mut props, h, he0);
1414        assert!(!is_halfedge_selected(&props, h, he0));
1415        assert!(!is_halfedge_selected(&props, h, twin));
1416        assert!(!is_edge_selected(&mesh, &props, h, he0));
1417
1418        // 仅选中 twin 时,is_edge_selected 也应返回 true
1419        select_halfedge(&mut props, h, twin);
1420        assert!(!is_halfedge_selected(&props, h, he0));
1421        assert!(is_halfedge_selected(&props, h, twin));
1422        assert!(is_edge_selected(&mesh, &props, h, he0));
1423        assert!(is_edge_selected(&mesh, &props, h, twin));
1424    }
1425
1426    #[test]
1427    fn selected_edge_ids_returns_canonical_only() {
1428        let (mesh, _, heids, _) = build_two_tri_mesh();
1429        let mut props = MeshProperties::new();
1430        let h = add_halfedge_selection(&mut props);
1431
1432        // 选中两条带 twin 的边(4 条半边)
1433        for &he in &heids[..4] {
1434            select_edge(&mesh, &mut props, h, he);
1435        }
1436        // selected_edge_ids 应去重,每个无向边只返回一次
1437        let edges: Vec<HalfEdgeId> = selected_edge_ids(&mesh, &props, h).collect();
1438        // 2 个三角形的内部边数 = 1(共享边);外部边数 = 4;共 5 条无向边
1439        // 但我们只选了前 4 条半边所在的边,可能覆盖 2~4 条无向边
1440        // 验证:每个返回的 he,其 twin 不在结果集中
1441        for &he in &edges {
1442            if let Some(twin) = mesh.get_halfedge(he).and_then(|h| h.twin) {
1443                assert!(
1444                    !edges.contains(&twin),
1445                    "twin {twin:?} 与 {he:?} 不应同时出现"
1446                );
1447            }
1448        }
1449        assert!(!edges.is_empty());
1450    }
1451
1452    #[test]
1453    fn edge_color_syncs_twin_pair() {
1454        let (mesh, _, heids, _) = build_two_tri_mesh();
1455        let mut props = MeshProperties::new();
1456        let h = add_halfedge_colors(&mut props);
1457
1458        let he0 = heids[0];
1459        let twin = mesh
1460            .get_halfedge(he0)
1461            .and_then(|h| h.twin)
1462            .expect("应至少有一条带 twin 的内部边");
1463
1464        // 设置 he0 的颜色,twin 也应同步
1465        set_edge_color(&mesh, &mut props, h, he0, [0.2, 0.4, 0.6]);
1466        assert_eq!(
1467            props.get_halfedge_prop(h, he0).map(|c| c.0),
1468            Some([0.2, 0.4, 0.6])
1469        );
1470        assert_eq!(
1471            props.get_halfedge_prop(h, twin).map(|c| c.0),
1472            Some([0.2, 0.4, 0.6])
1473        );
1474
1475        // edge_color 任一半边都能读到
1476        assert_eq!(edge_color(&mesh, &props, h, he0), Some([0.2, 0.4, 0.6]));
1477        assert_eq!(edge_color(&mesh, &props, h, twin), Some([0.2, 0.4, 0.6]));
1478
1479        // 未设置颜色的边返回 None
1480        let uncolored = heids.iter().find(|&&he| {
1481            !is_halfedge_colored(&props, h, he)
1482                && mesh
1483                    .get_halfedge(he)
1484                    .and_then(|h| h.twin)
1485                    .is_some_and(|t| !is_halfedge_colored(&props, h, t))
1486        });
1487        if let Some(&uncolored_he) = uncolored {
1488            assert_eq!(edge_color(&mesh, &props, h, uncolored_he), None);
1489        }
1490    }
1491
1492    fn is_halfedge_colored(
1493        props: &MeshProperties,
1494        h: PropertyHandle<HalfEdgeColor>,
1495        he: HalfEdgeId,
1496    ) -> bool {
1497        props.get_halfedge_prop(h, he).is_some()
1498    }
1499
1500    #[test]
1501    fn face_color_basic() {
1502        let (mesh, _, _, fids) = build_two_tri_mesh();
1503        let mut props = MeshProperties::new();
1504        let h = add_face_colors(&mut props);
1505
1506        assert_eq!(face_color(&props, h, fids[0]), None);
1507
1508        set_face_color(&mut props, h, fids[0], [0.8, 0.1, 0.1]);
1509        assert_eq!(face_color(&props, h, fids[0]), Some([0.8, 0.1, 0.1]));
1510        assert_eq!(face_color(&props, h, fids[1]), None);
1511
1512        clear_face_colors(&mesh, &mut props, h);
1513        assert_eq!(face_color(&props, h, fids[0]), None);
1514        assert_eq!(face_color(&props, h, fids[1]), None);
1515    }
1516
1517    #[test]
1518    fn selection_newtype_default_is_false() {
1519        assert!(!VertexSelected::default().0);
1520        assert!(!HalfEdgeSelected::default().0);
1521        assert!(!FaceSelected::default().0);
1522    }
1523
1524    #[test]
1525    fn color_newtype_default_is_zero() {
1526        assert_eq!(HalfEdgeColor::default().0, [0.0, 0.0, 0.0]);
1527        assert_eq!(FaceColor::default().0, [0.0, 0.0, 0.0]);
1528    }
1529
1530    #[test]
1531    fn unregistered_selection_safe_no_panic() {
1532        // 未注册属性时调用查询接口不应 panic
1533        let mesh = crate::test_util::build_icosphere(0);
1534        let props = MeshProperties::new();
1535        let h: PropertyHandle<VertexSelected> = PropertyHandle::new();
1536        let v = mesh.vertex_ids().next().unwrap();
1537        assert!(!is_vertex_selected(&props, h, v));
1538        assert_eq!(count_selected_vertices(&mesh, &props, h), 0);
1539        assert_eq!(selected_vertex_ids(&mesh, &props, h).count(), 0);
1540    }
1541
1542    #[test]
1543    fn unregistered_edge_safe_no_panic() {
1544        let (mesh, _, heids, _) = build_two_tri_mesh();
1545        let props = MeshProperties::new();
1546        let h: PropertyHandle<HalfEdgeSelected> = PropertyHandle::new();
1547        let he = heids[0];
1548        assert!(!is_edge_selected(&mesh, &props, h, he));
1549        assert_eq!(selected_edge_ids(&mesh, &props, h).count(), 0);
1550    }
1551
1552    #[test]
1553    fn empty_mesh_selection_no_panic() {
1554        // 空网格上调用选择接口不应 panic
1555        let mesh = MeshStorage::new();
1556        let mut props = MeshProperties::new();
1557        let h = add_vertex_selection(&mut props);
1558
1559        select_all_vertices(&mesh, &mut props, h);
1560        invert_vertex_selection(&mesh, &mut props, h);
1561        assert_eq!(count_selected_vertices(&mesh, &props, h), 0);
1562        assert_eq!(selected_vertex_ids(&mesh, &props, h).count(), 0);
1563    }
1564
1565    // ---------- 可视化尺寸 / 粗细 / 透明度测试 ----------
1566
1567    #[test]
1568    fn vertex_size_basic() {
1569        let mesh = crate::test_util::build_icosphere(0);
1570        let mut props = MeshProperties::new();
1571        let h = add_vertex_sizes(&mut props);
1572        let v0 = mesh.vertex_ids().next().unwrap();
1573
1574        // 未设置返回 None
1575        assert_eq!(vertex_size(&props, h, v0), None);
1576
1577        set_vertex_size(&mut props, h, v0, 0.05);
1578        assert_eq!(vertex_size(&props, h, v0), Some(0.05));
1579
1580        // 批量统一设置
1581        set_uniform_vertex_size(&mesh, &mut props, h, 0.1);
1582        for v in mesh.vertex_ids() {
1583            assert_eq!(vertex_size(&props, h, v), Some(0.1));
1584        }
1585
1586        // 清空
1587        clear_vertex_sizes(&mesh, &mut props, h);
1588        for v in mesh.vertex_ids() {
1589            assert_eq!(vertex_size(&props, h, v), None);
1590        }
1591        // 类型注册保留
1592        assert!(props.has_vertex_prop::<VertexSize>());
1593    }
1594
1595    #[test]
1596    fn vertex_size_default_is_zero() {
1597        assert_eq!(VertexSize::default().0, 0.0);
1598    }
1599
1600    #[test]
1601    fn edge_thickness_syncs_twin_pair() {
1602        let (mesh, _, heids, _) = build_two_tri_mesh();
1603        let mut props = MeshProperties::new();
1604        let h = add_halfedge_thickness(&mut props);
1605
1606        let he0 = heids[0];
1607        let twin = mesh
1608            .get_halfedge(he0)
1609            .and_then(|h| h.twin)
1610            .expect("应至少有一条带 twin 的内部边");
1611
1612        // 设置 he0 粗细,twin 也应同步
1613        set_edge_thickness(&mesh, &mut props, h, he0, 0.3);
1614        assert_eq!(props.get_halfedge_prop(h, he0).map(|t| t.0), Some(0.3));
1615        assert_eq!(props.get_halfedge_prop(h, twin).map(|t| t.0), Some(0.3));
1616
1617        // edge_thickness 任一半边都能读到
1618        assert_eq!(edge_thickness(&mesh, &props, h, he0), Some(0.3));
1619        assert_eq!(edge_thickness(&mesh, &props, h, twin), Some(0.3));
1620
1621        // 仅设置 twin,he0 也能读到(fallback)
1622        clear_halfedge_thickness(&mesh, &mut props, h);
1623        set_halfedge_thickness(&mut props, h, twin, 0.7);
1624        assert_eq!(edge_thickness(&mesh, &props, h, he0), Some(0.7));
1625
1626        // 找一个 he 与 twin 都未设置的边,edge_thickness 应返回 None
1627        let uncolored_he = heids
1628            .iter()
1629            .copied()
1630            .find(|&he| {
1631                halfedge_thickness(&props, h, he).is_none()
1632                    && mesh
1633                        .get_halfedge(he)
1634                        .and_then(|h| h.twin)
1635                        .is_none_or(|twin| halfedge_thickness(&props, h, twin).is_none())
1636            })
1637            .expect("应存在 he 与 twin 都未设置的半边");
1638        assert_eq!(edge_thickness(&mesh, &props, h, uncolored_he), None);
1639    }
1640
1641    #[test]
1642    fn edge_thickness_uniform_sets_all() {
1643        let (mesh, _, _, _) = build_two_tri_mesh();
1644        let mut props = MeshProperties::new();
1645        let h = add_halfedge_thickness(&mut props);
1646
1647        set_uniform_edge_thickness(&mesh, &mut props, h, 0.25);
1648
1649        // 每条边(twin 对)都应被设置,且值一致
1650        let mut edges_checked = 0;
1651        for he in mesh.halfedge_ids() {
1652            if let Some(t) = edge_thickness(&mesh, &props, h, he) {
1653                assert!((t - 0.25).abs() < 1e-12);
1654                if let Some(twin) = mesh.get_halfedge(he).and_then(|h| h.twin) {
1655                    if he <= twin {
1656                        edges_checked += 1;
1657                    }
1658                } else {
1659                    edges_checked += 1;
1660                }
1661            }
1662        }
1663        assert!(edges_checked > 0, "应至少设置一条边");
1664    }
1665
1666    #[test]
1667    fn halfedge_thickness_independent_set() {
1668        let (mesh, _, heids, _) = build_two_tri_mesh();
1669        let mut props = MeshProperties::new();
1670        let h = add_halfedge_thickness(&mut props);
1671
1672        // set_halfedge_thickness 不同步 twin
1673        set_halfedge_thickness(&mut props, h, heids[0], 0.5);
1674        assert_eq!(halfedge_thickness(&props, h, heids[0]), Some(0.5));
1675
1676        let twin = mesh
1677            .get_halfedge(heids[0])
1678            .and_then(|h| h.twin)
1679            .expect("应存在 twin");
1680        // twin 不应被设置(无对称写入)
1681        assert_eq!(halfedge_thickness(&props, h, twin), None);
1682    }
1683
1684    #[test]
1685    fn face_opacity_basic() {
1686        let (mesh, _, _, fids) = build_two_tri_mesh();
1687        let mut props = MeshProperties::new();
1688        let h = add_face_opacity(&mut props);
1689
1690        assert_eq!(face_opacity(&props, h, fids[0]), None);
1691
1692        set_face_opacity(&mut props, h, fids[0], 0.5);
1693        assert_eq!(face_opacity(&props, h, fids[0]), Some(0.5));
1694        assert_eq!(face_opacity(&props, h, fids[1]), None);
1695
1696        // clamp 测试:超出 [0, 1] 的输入被截断
1697        set_face_opacity(&mut props, h, fids[0], -0.5);
1698        assert_eq!(face_opacity(&props, h, fids[0]), Some(0.0));
1699        set_face_opacity(&mut props, h, fids[0], 1.5);
1700        assert_eq!(face_opacity(&props, h, fids[0]), Some(1.0));
1701
1702        // 批量统一
1703        set_uniform_face_opacity(&mesh, &mut props, h, 0.7);
1704        for f in mesh.face_ids() {
1705            assert_eq!(face_opacity(&props, h, f), Some(0.7));
1706        }
1707        // 批量统一也 clamp
1708        set_uniform_face_opacity(&mesh, &mut props, h, 2.0);
1709        for f in mesh.face_ids() {
1710            assert_eq!(face_opacity(&props, h, f), Some(1.0));
1711        }
1712
1713        // 清空
1714        clear_face_opacity(&mesh, &mut props, h);
1715        for f in mesh.face_ids() {
1716            assert_eq!(face_opacity(&props, h, f), None);
1717        }
1718        assert!(props.has_face_prop::<FaceOpacity>());
1719    }
1720
1721    #[test]
1722    fn face_opacity_default_is_zero() {
1723        assert_eq!(FaceOpacity::default().0, 0.0);
1724    }
1725
1726    #[test]
1727    fn unregistered_size_thickness_opacity_safe_no_panic() {
1728        // 未注册属性时调用查询接口不应 panic
1729        let mesh = crate::test_util::build_icosphere(0);
1730        let props = MeshProperties::new();
1731
1732        let hv: PropertyHandle<VertexSize> = PropertyHandle::new();
1733        let v = mesh.vertex_ids().next().unwrap();
1734        assert_eq!(vertex_size(&props, hv, v), None);
1735
1736        let he: PropertyHandle<HalfEdgeThickness> = PropertyHandle::new();
1737        let he_id = mesh.halfedge_ids().next().unwrap();
1738        assert_eq!(edge_thickness(&mesh, &props, he, he_id), None);
1739
1740        let hf: PropertyHandle<FaceOpacity> = PropertyHandle::new();
1741        let f = mesh.face_ids().next().unwrap();
1742        assert_eq!(face_opacity(&props, hf, f), None);
1743    }
1744
1745    #[test]
1746    fn empty_mesh_size_thickness_opacity_no_panic() {
1747        // 空网格上调用尺寸/粗细/透明度接口不应 panic
1748        let mesh = MeshStorage::new();
1749        let mut props = MeshProperties::new();
1750
1751        let hv = add_vertex_sizes(&mut props);
1752        set_uniform_vertex_size(&mesh, &mut props, hv, 0.1);
1753        clear_vertex_sizes(&mesh, &mut props, hv);
1754
1755        let he = add_halfedge_thickness(&mut props);
1756        set_uniform_edge_thickness(&mesh, &mut props, he, 0.2);
1757        clear_halfedge_thickness(&mesh, &mut props, he);
1758
1759        let hf = add_face_opacity(&mut props);
1760        set_uniform_face_opacity(&mesh, &mut props, hf, 0.5);
1761        clear_face_opacity(&mesh, &mut props, hf);
1762    }
1763}