Skip to main content

plot3d/
serialization.rs

1//! JSON serialization for face records and face matches.
2//!
3//! Provides two output formats controlled by the `--diagonal` flag:
4//!
5//! # Default format (lo/hi)
6//!
7//! Face bounds use ascending `lo`/`hi` keys. Every match includes a
8//! `permutation_index` (0-7) indicating which [`PERMUTATION_MATRICES`]
9//! entry transforms face B to match face A.
10//!
11//! ```json
12//! {
13//!   "block1": { "block_index": 0, "lo": [0,0,0], "hi": [0,101,33] },
14//!   "block2": { "block_index": 30, "lo": [0,0,0], "hi": [0,101,33] },
15//!   "permutation_index": 3
16//! }
17//! ```
18//!
19//! # Diagonal format (`--diagonal`)
20//!
21//! - **In-plane** matches (perm 0-3): block2's `lb`/`ub` encodes traversal
22//!   direction. `permutation_index: -1` (direction is fully in the bounds).
23//! - **Cross-plane** matches (perm 4-7): ascending `lb`/`ub` bounds with the
24//!   actual `permutation_index`, since lb/ub can't encode a swap.
25//!
26//! ```json
27//! {
28//!   "block1": { "block_index": 0, "lb": [0,0,0], "ub": [0,101,33] },
29//!   "block2": { "block_index": 30, "lb": [0,101,33], "ub": [0,0,0] },
30//!   "permutation_index": -1
31//! }
32//! ```
33//!
34//! [`PERMUTATION_MATRICES`]: crate::face_record::PERMUTATION_MATRICES
35
36use crate::face_record::{FaceMatch, FaceRecord, OrientationPlane, PERMUTATION_MATRICES};
37use serde_json::{json, Value};
38
39// ── Default format (lo/hi) ──────────────────────────────────────────────
40
41/// Convert a [`FaceRecord`] to JSON with ascending `lo`/`hi` bounds.
42pub fn face_record_to_json(rec: &FaceRecord) -> Value {
43    let (lo, hi) = rec.bounds();
44    let mut obj = json!({
45        "block_index": rec.block_index,
46        "lo": lo,
47        "hi": hi,
48    });
49    if let Some(id) = rec.id {
50        obj["id"] = json!(id);
51    }
52    obj
53}
54
55/// Convert a [`FaceMatch`] to JSON (`lo`/`hi` + `permutation_index` 0-7).
56pub fn face_match_to_json(fm: &FaceMatch) -> Value {
57    let perm_idx: i8 = fm
58        .orientation
59        .as_ref()
60        .map(|o| o.permutation_index as i8)
61        .unwrap_or(0);
62    json!({
63        "block1": face_record_to_json(&fm.block1),
64        "block2": face_record_to_json(&fm.block2),
65        "permutation_index": perm_idx,
66    })
67}
68
69// ── Diagonal format (lb/ub) ─────────────────────────────────────────────
70
71/// Convert a [`FaceRecord`] to JSON with ascending `lb`/`ub` bounds.
72pub fn face_record_to_diagonal_json(rec: &FaceRecord) -> Value {
73    let (lo, hi) = rec.bounds();
74    let mut obj = json!({
75        "block_index": rec.block_index,
76        "lb": lo,
77        "ub": hi,
78    });
79    if let Some(id) = rec.id {
80        obj["id"] = json!(id);
81    }
82    obj
83}
84
85/// Convert a [`FaceRecord`] to JSON with directional `lb`/`ub` based on permutation.
86///
87/// Reconstructs traversal direction from `permutation_index` bits:
88/// - bit 0 (`u_reversed`): reverse the first varying axis
89/// - bit 1 (`v_reversed`): reverse the second varying axis
90fn face_record_to_directed_diagonal_json(rec: &FaceRecord, perm_idx: u8) -> Value {
91    let (lo, hi) = rec.bounds();
92    let const_ax = rec.constant_axis();
93
94    let (lb, ub) = match const_ax {
95        Some(c) => {
96            let vary: Vec<usize> = (0..3).filter(|&d| d != c).collect();
97            let d0 = vary[0]; // u axis
98            let d1 = vary[1]; // v axis
99            let u_rev = perm_idx & 1 != 0;
100            let v_rev = perm_idx & 2 != 0;
101
102            let mut lb = lo;
103            let mut ub = hi;
104            if u_rev {
105                lb[d0] = hi[d0];
106                ub[d0] = lo[d0];
107            }
108            if v_rev {
109                lb[d1] = hi[d1];
110                ub[d1] = lo[d1];
111            }
112            (lb, ub)
113        }
114        None => (lo, hi),
115    };
116
117    let mut obj = json!({
118        "block_index": rec.block_index,
119        "lb": lb,
120        "ub": ub,
121    });
122    if let Some(id) = rec.id {
123        obj["id"] = json!(id);
124    }
125    obj
126}
127
128/// Convert a [`FaceMatch`] to diagonal JSON format.
129///
130/// - **In-plane** (perm 0-3): block2's `lb`/`ub` encodes direction. `permutation_index: -1`.
131/// - **Cross-plane** (perm 4-7): ascending `lb`/`ub`. `permutation_index: N` (actual index).
132pub fn face_match_to_diagonal_json(fm: &FaceMatch) -> Value {
133    let orient = fm.orientation.as_ref();
134    let perm_idx = orient.map(|o| o.permutation_index).unwrap_or(0);
135    let is_cross_plane = orient
136        .map(|o| o.plane == OrientationPlane::CrossPlane)
137        .unwrap_or(false);
138
139    if is_cross_plane {
140        // Cross-plane: lb/ub can't encode swap → ascending bounds + actual permutation_index
141        json!({
142            "block1": face_record_to_diagonal_json(&fm.block1),
143            "block2": face_record_to_diagonal_json(&fm.block2),
144            "permutation_index": perm_idx,
145        })
146    } else {
147        // In-plane: encode direction in block2's lb/ub, permutation_index = -1
148        json!({
149            "block1": face_record_to_diagonal_json(&fm.block1),
150            "block2": face_record_to_directed_diagonal_json(&fm.block2, perm_idx),
151            "permutation_index": -1,
152        })
153    }
154}
155
156/// Serialize the 8 permutation matrices as a JSON array (for inclusion in output headers).
157pub fn permutation_matrices_json() -> Vec<Value> {
158    PERMUTATION_MATRICES
159        .iter()
160        .map(|m| json!([[m[0][0], m[0][1]], [m[1][0], m[1][1]]]))
161        .collect()
162}