1use crate::ids::{FaceId, HalfEdgeId, VertexId};
21use crate::property::{MeshProperties, PropertyHandle};
22use crate::storage::MeshStorage;
23
24#[derive(Debug, Clone, Copy, Default, PartialEq)]
30pub struct VertexNormal(pub [f64; 3]);
31
32#[derive(Debug, Clone, Copy, Default, PartialEq)]
34pub struct VertexColor(pub [f64; 3]);
35
36#[derive(Debug, Clone, Copy, Default, PartialEq)]
38pub struct VertexUv(pub [f64; 2]);
39
40#[derive(Debug, Clone, Copy, Default, PartialEq)]
42pub struct FaceNormal(pub [f64; 3]);
43
44#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
46pub struct VertexSelected(pub bool);
47
48#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
51pub struct HalfEdgeSelected(pub bool);
52
53#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
55pub struct FaceSelected(pub bool);
56
57#[derive(Debug, Clone, Copy, Default, PartialEq)]
60pub struct HalfEdgeColor(pub [f64; 3]);
61
62#[derive(Debug, Clone, Copy, Default, PartialEq)]
64pub struct FaceColor(pub [f64; 3]);
65
66#[derive(Debug, Clone, Copy, Default, PartialEq)]
68pub struct VertexSize(pub f64);
69
70#[derive(Debug, Clone, Copy, Default, PartialEq)]
73pub struct HalfEdgeThickness(pub f64);
74
75#[derive(Debug, Clone, Copy, Default, PartialEq)]
77pub struct FaceOpacity(pub f64);
78
79pub fn add_vertex_normals(props: &mut MeshProperties) -> PropertyHandle<VertexNormal> {
85 props.add_vertex_prop::<VertexNormal>()
86}
87
88pub fn add_vertex_colors(props: &mut MeshProperties) -> PropertyHandle<VertexColor> {
90 props.add_vertex_prop::<VertexColor>()
91}
92
93pub fn add_vertex_uvs(props: &mut MeshProperties) -> PropertyHandle<VertexUv> {
95 props.add_vertex_prop::<VertexUv>()
96}
97
98pub fn add_face_normals(props: &mut MeshProperties) -> PropertyHandle<FaceNormal> {
100 props.add_face_prop::<FaceNormal>()
101}
102
103pub fn add_vertex_selection(props: &mut MeshProperties) -> PropertyHandle<VertexSelected> {
105 props.add_vertex_prop::<VertexSelected>()
106}
107
108pub fn add_halfedge_selection(props: &mut MeshProperties) -> PropertyHandle<HalfEdgeSelected> {
110 props.add_halfedge_prop::<HalfEdgeSelected>()
111}
112
113pub fn add_face_selection(props: &mut MeshProperties) -> PropertyHandle<FaceSelected> {
115 props.add_face_prop::<FaceSelected>()
116}
117
118pub fn add_halfedge_colors(props: &mut MeshProperties) -> PropertyHandle<HalfEdgeColor> {
120 props.add_halfedge_prop::<HalfEdgeColor>()
121}
122
123pub fn add_face_colors(props: &mut MeshProperties) -> PropertyHandle<FaceColor> {
125 props.add_face_prop::<FaceColor>()
126}
127
128pub fn add_vertex_sizes(props: &mut MeshProperties) -> PropertyHandle<VertexSize> {
130 props.add_vertex_prop::<VertexSize>()
131}
132
133pub fn add_halfedge_thickness(props: &mut MeshProperties) -> PropertyHandle<HalfEdgeThickness> {
135 props.add_halfedge_prop::<HalfEdgeThickness>()
136}
137
138pub fn add_face_opacity(props: &mut MeshProperties) -> PropertyHandle<FaceOpacity> {
140 props.add_face_prop::<FaceOpacity>()
141}
142
143pub 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
163pub 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
176pub 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
198pub 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
214pub 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
230pub 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
267use crate::io::{ObjError, build_mesh_from_polygons};
272
273pub 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 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 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 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
466pub 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 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 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 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 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
536fn vertex_index(mesh: &crate::storage::MeshStorage, target: VertexId) -> usize {
538 mesh.vertex_ids().position(|v| v == target).unwrap_or(0)
539}
540
541#[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#[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#[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#[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
588pub 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
599pub 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
609pub 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
620pub 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
632pub 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#[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#[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#[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#[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
685pub 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
696pub 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#[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#[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#[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#[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
742pub 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
753pub 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
763pub 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
774pub 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
783pub 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
805pub 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
818pub 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
834pub 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
850pub 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 match mesh.get_halfedge(he).and_then(|h| h.twin) {
862 None => true,
863 Some(twin) => he <= twin,
864 }
865 })
866}
867
868pub 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
884pub 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#[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#[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
924pub 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#[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#[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
965pub 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
976pub 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
988pub 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
1004pub 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#[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#[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
1040pub 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
1051pub fn set_uniform_edge_thickness(
1053 mesh: &MeshStorage,
1054 props: &mut MeshProperties,
1055 h: PropertyHandle<HalfEdgeThickness>,
1056 thickness: f64,
1057) {
1058 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#[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#[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
1096pub 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
1107pub 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#[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 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 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 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 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 fn build_two_tri_mesh() -> (MeshStorage, [VertexId; 4], [HalfEdgeId; 6], [FaceId; 2]) {
1256 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 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 let new_state = toggle_vertex_selection(&mut props, h, v0);
1296 assert!(!new_state);
1297 assert!(!is_vertex_selected(&props, h, v0));
1298
1299 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 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 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 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 let he0 = heids[0];
1400 let twin = mesh
1401 .get_halfedge(he0)
1402 .and_then(|h| h.twin)
1403 .expect("应至少有一条带 twin 的内部边");
1404
1405 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 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 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 for &he in &heids[..4] {
1434 select_edge(&mesh, &mut props, h, he);
1435 }
1436 let edges: Vec<HalfEdgeId> = selected_edge_ids(&mesh, &props, h).collect();
1438 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 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 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 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 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 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 #[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 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 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 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 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 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 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 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 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 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(&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 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 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 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 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 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 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 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}