Skip to main content

qualia_core_db/render/
anatomy_pack.rs

1//! Shared metadata schema for a `.hmc` **anatomy asset pack** — the per-organ
2//! `meta` carried in each bundle entry.
3//!
4//! A packed anatomy body is a `.hmc` bundle (see [`crate::bundle`]) whose
5//! entries are the per-organ sealed `.10d` meshes. Each entry's opaque `meta`
6//! holds one CBOR-encoded [`AnatomyOrganMeta`]: which body **system** the organ
7//! belongs to, an **approximate** anatomical position for assembling the whole
8//! body, and a **neutral default colour** (the pack ships no personal data — a
9//! person's real burden colouring, from `AnatomyViewReport::paint_organs`,
10//! overrides this at runtime when their records are loaded).
11//!
12//! This lives in `qualia-core-db` so the producer (`qualia-client-core`, which
13//! discovers/compiles organs) and the consumer (the browser `QualiaPortal`
14//! renderer + the native read-through loader) share **one** typed schema.
15
16use serde::{Deserialize, Serialize};
17
18/// Per-organ render metadata carried in a `.hmc` anatomy-pack entry's `meta`.
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct AnatomyOrganMeta {
21    /// The organ's **primary** body-system id (its default colour/placement),
22    /// e.g. `"digestive"`. An organ is a building block shared across systems;
23    /// see [`AnatomyOrganMeta::systems`] for the full set.
24    pub system: String,
25    /// Human-readable name of this part (e.g. `"diaphragm"`, `"liver"`), for a selectable parts list.
26    /// Empty/absent in older packs → the consumer falls back to the entry key (`#[serde(default)]`).
27    #[serde(default)]
28    pub label: String,
29    /// **All** body systems the organ participates in, primary first (the pancreas
30    /// is `["digestive", "endocrine", "exocrine"]`). Lets the renderer colour by the
31    /// primary system *or* blend across memberships, and lets a person's condition on
32    /// any member system light the organ. Empty/absent in older packs → treat as
33    /// `[system]` (back-compatible; `#[serde(default)]`).
34    #[serde(default)]
35    pub systems: Vec<String>,
36    /// Approximate anatomical position offset in normalised body space
37    /// `[x, y, z]` (0..1; x=right, y=up, z=front). Approximate placement — a
38    /// future pass can substitute real CCF transforms.
39    pub position: [f32; 3],
40    /// Neutral default linear RGBA for the organ (overridden by the person's
41    /// σ-derived burden colour at runtime when their data is present).
42    pub rgba: [f32; 4],
43}
44
45impl AnatomyOrganMeta {
46    /// Encode to CBOR for storage in a bundle entry's `meta`.
47    pub fn to_cbor(&self) -> Vec<u8> {
48        let mut out = Vec::new();
49        // Infallible for this small POD struct into a Vec writer.
50        ciborium::into_writer(self, &mut out).expect("cbor encode AnatomyOrganMeta");
51        out
52    }
53
54    /// Decode from a bundle entry's `meta` bytes.
55    pub fn from_cbor(bytes: &[u8]) -> Option<Self> {
56        ciborium::from_reader(bytes).ok()
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn cbor_round_trips() {
66        let m = AnatomyOrganMeta {
67            system: "digestive".to_string(),
68            label: "pancreas".to_string(),
69            systems: vec![
70                "digestive".to_string(),
71                "endocrine".to_string(),
72                "exocrine".to_string(),
73            ],
74            position: [0.5, 0.6, 0.55],
75            rgba: [0.8, 0.3, 0.3, 1.0],
76        };
77        let bytes = m.to_cbor();
78        let back = AnatomyOrganMeta::from_cbor(&bytes).unwrap();
79        assert_eq!(back, m);
80        assert_eq!(back.systems.len(), 3, "all memberships round-trip");
81        assert!(AnatomyOrganMeta::from_cbor(b"not cbor").is_none());
82    }
83
84    /// A pack written before `systems` existed must still decode (the field defaults to empty), so an
85    /// older `.hmc` on disk keeps working. The consumer treats an empty `systems` as `[system]`.
86    #[test]
87    fn old_meta_without_systems_field_still_decodes() {
88        #[derive(serde::Serialize)]
89        struct OldMeta {
90            system: String,
91            position: [f32; 3],
92            rgba: [f32; 4],
93        }
94        let mut bytes = Vec::new();
95        ciborium::into_writer(
96            &OldMeta {
97                system: "respiratory".to_string(),
98                position: [0.4, 0.6, 0.5],
99                rgba: [0.5, 0.7, 0.9, 1.0],
100            },
101            &mut bytes,
102        )
103        .unwrap();
104        let m = AnatomyOrganMeta::from_cbor(&bytes).expect("old meta decodes");
105        assert_eq!(m.system, "respiratory");
106        assert!(m.systems.is_empty(), "absent field defaults to empty");
107    }
108}