Skip to main content

oxihuman_viewer/
mesh_patch_view.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! Patch-colored mesh view (one color per UV chart/patch).
5
6#![allow(dead_code)]
7
8/// Config for patch-colored mesh rendering.
9#[allow(dead_code)]
10#[derive(Debug, Clone)]
11pub struct MeshPatchViewConfig {
12    /// Show patch boundaries.
13    pub show_boundaries: bool,
14    /// Boundary line width.
15    pub boundary_width: f32,
16    /// Random-seed for color assignment.
17    pub color_seed: u32,
18    /// Opacity of patch colors.
19    pub opacity: f32,
20}
21
22#[allow(dead_code)]
23impl Default for MeshPatchViewConfig {
24    fn default() -> Self {
25        Self {
26            show_boundaries: true,
27            boundary_width: 1.0,
28            color_seed: 42,
29            opacity: 1.0,
30        }
31    }
32}
33
34/// One patch record.
35#[allow(dead_code)]
36#[derive(Debug, Clone)]
37pub struct PatchRecord {
38    pub id: u32,
39    pub color: [f32; 3],
40    pub face_count: u32,
41}
42
43/// Create default config.
44#[allow(dead_code)]
45pub fn new_mesh_patch_view_config() -> MeshPatchViewConfig {
46    MeshPatchViewConfig::default()
47}
48
49/// Deterministic color for a patch ID (LCG-based).
50#[allow(dead_code)]
51pub fn patch_color(id: u32, seed: u32) -> [f32; 3] {
52    let h = id.wrapping_mul(2_654_435_761_u32).wrapping_add(seed);
53    let r = ((h & 0xFF) as f32) / 255.0;
54    let g = (((h >> 8) & 0xFF) as f32) / 255.0;
55    let b = (((h >> 16) & 0xFF) as f32) / 255.0;
56    [r * 0.7 + 0.3, g * 0.7 + 0.3, b * 0.7 + 0.3]
57}
58
59/// Generate patch records for a given number of patches.
60#[allow(dead_code)]
61pub fn generate_patch_records(num_patches: u32, cfg: &MeshPatchViewConfig) -> Vec<PatchRecord> {
62    (0..num_patches)
63        .map(|id| PatchRecord {
64            id,
65            color: patch_color(id, cfg.color_seed),
66            face_count: 0,
67        })
68        .collect()
69}
70
71/// Set opacity.
72#[allow(dead_code)]
73pub fn mpv_set_opacity(cfg: &mut MeshPatchViewConfig, value: f32) {
74    cfg.opacity = value.clamp(0.0, 1.0);
75}
76
77/// Toggle boundary display.
78#[allow(dead_code)]
79pub fn mpv_toggle_boundaries(cfg: &mut MeshPatchViewConfig) {
80    cfg.show_boundaries = !cfg.show_boundaries;
81}
82
83/// Set boundary line width.
84#[allow(dead_code)]
85pub fn mpv_set_boundary_width(cfg: &mut MeshPatchViewConfig, w: f32) {
86    cfg.boundary_width = w.max(0.0);
87}
88
89/// Count patches with at least one face.
90#[allow(dead_code)]
91pub fn mpv_active_patch_count(records: &[PatchRecord]) -> usize {
92    records.iter().filter(|r| r.face_count > 0).count()
93}
94
95/// Serialize to JSON.
96#[allow(dead_code)]
97pub fn mesh_patch_view_to_json(cfg: &MeshPatchViewConfig) -> String {
98    format!(
99        r#"{{"show_boundaries":{},"boundary_width":{:.4},"opacity":{:.4}}}"#,
100        cfg.show_boundaries, cfg.boundary_width, cfg.opacity
101    )
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_default() {
110        let c = MeshPatchViewConfig::default();
111        assert!(c.show_boundaries);
112        assert!((c.opacity - 1.0).abs() < 1e-6);
113    }
114
115    #[test]
116    fn test_patch_color_deterministic() {
117        let c1 = patch_color(0, 42);
118        let c2 = patch_color(0, 42);
119        assert_eq!(c1, c2);
120    }
121
122    #[test]
123    fn test_patch_color_different_ids() {
124        let c1 = patch_color(0, 42);
125        let c2 = patch_color(1, 42);
126        assert!(c1 != c2 || c1 == c2);
127    }
128
129    #[test]
130    fn test_generate_patch_records() {
131        let cfg = MeshPatchViewConfig::default();
132        let records = generate_patch_records(5, &cfg);
133        assert_eq!(records.len(), 5);
134        assert_eq!(records[0].id, 0);
135    }
136
137    #[test]
138    fn test_set_opacity_clamped() {
139        let mut c = MeshPatchViewConfig::default();
140        mpv_set_opacity(&mut c, 5.0);
141        assert!((c.opacity - 1.0).abs() < 1e-6);
142    }
143
144    #[test]
145    fn test_toggle_boundaries() {
146        let mut c = MeshPatchViewConfig::default();
147        mpv_toggle_boundaries(&mut c);
148        assert!(!c.show_boundaries);
149    }
150
151    #[test]
152    fn test_active_patch_count_zero() {
153        let cfg = MeshPatchViewConfig::default();
154        let records = generate_patch_records(3, &cfg);
155        assert_eq!(mpv_active_patch_count(&records), 0);
156    }
157
158    #[test]
159    fn test_active_patch_count_some() {
160        let records = vec![
161            PatchRecord {
162                id: 0,
163                color: [1.0, 0.0, 0.0],
164                face_count: 4,
165            },
166            PatchRecord {
167                id: 1,
168                color: [0.0, 1.0, 0.0],
169                face_count: 0,
170            },
171        ];
172        assert_eq!(mpv_active_patch_count(&records), 1);
173    }
174
175    #[test]
176    fn test_to_json() {
177        let j = mesh_patch_view_to_json(&MeshPatchViewConfig::default());
178        assert!(j.contains("show_boundaries"));
179    }
180}