qualia_core_db/entity_view/
projection.rs1use super::entity_id::{EntityId, EntityKind};
4use super::observer::{AffordanceBits, RepresentationWing};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
9#[repr(u8)]
10pub enum PresentationLevel {
11 #[default]
12 Document = 0,
13 AppHabitat = 1,
14 SpatialDesk = 2,
15 StageWorld = 3,
16 EmbodiedWorld = 4,
17 Infosphere = 5,
18 MultiSensory = 6,
19}
20
21impl PresentationLevel {
22 pub fn from_u8(v: u8) -> Self {
23 match v {
24 1 => Self::AppHabitat,
25 2 => Self::SpatialDesk,
26 3 => Self::StageWorld,
27 4 => Self::EmbodiedWorld,
28 5 => Self::Infosphere,
29 6 => Self::MultiSensory,
30 _ => Self::Document,
31 }
32 }
33
34 pub fn as_u8(self) -> u8 {
35 self as u8
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct FlatCard {
42 pub entity_id: u64,
43 pub kind: EntityKind,
44 pub title: String,
45 pub excerpt: String,
46 pub wing: RepresentationWing,
47 pub affordance_bits: u8,
48 pub honesty: String,
49 pub uri: String,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct SceneNodeProj {
55 pub entity_id: u64,
56 pub id: String,
57 pub x: f64,
58 pub y: f64,
59 pub z: f64,
60 pub color: String,
61 pub radius: f64,
62 pub alpha: f64,
63 pub affordance_bits: u8,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, Default)]
68pub struct ProjectionResult {
69 pub observer: String,
70 pub presentation_level: u8,
71 pub flat: Vec<FlatCard>,
72 pub scene_nodes: Vec<SceneNodeProj>,
73 pub hidden_count: u32,
74}
75
76#[derive(Debug, Clone, Copy)]
78pub struct LayoutInput {
79 pub entity_id: EntityId,
80 pub lat: Option<f32>,
81 pub lon: Option<f32>,
82 pub affordances: AffordanceBits,
83 pub wing: RepresentationWing,
84}
85
86pub fn wing_color(wing: RepresentationWing) -> &'static str {
88 match wing {
89 RepresentationWing::Private => "#a78bfa", RepresentationWing::Offered => "#34d399", RepresentationWing::Commons => "#38bdf8", }
93}
94
95pub fn layout_scene_nodes(inputs: &[LayoutInput], out: &mut [SceneNodeProj]) -> usize {
98 let mut n = 0;
99 let mut field_i = 0usize;
100 let field_total = inputs
101 .iter()
102 .filter(|i| i.lat.is_none() || i.lon.is_none())
103 .count()
104 .max(1);
105 for inp in inputs {
106 if n >= out.len() {
107 break;
108 }
109 let (x, y, z) = if let (Some(lat), Some(lon)) = (inp.lat, inp.lon) {
110 let xn = ((lon as f64) + 180.0) / 360.0;
112 let yn = 1.0 - ((lat as f64) + 90.0) / 180.0;
113 (xn.clamp(0.05, 0.95), yn.clamp(0.05, 0.95), 0.15)
114 } else {
115 let i = field_i as f64;
117 let tot = field_total as f64;
118 field_i += 1;
119 let golden = std::f64::consts::PI * (3.0 - 5.0_f64.sqrt());
120 let r = ((i + 0.5) / tot).sqrt() * 0.38;
121 let theta = i * golden;
122 let xn = 0.5 + r * theta.cos();
123 let yn = 0.5 + r * theta.sin() * 0.72;
124 let zn = 0.2 + (i / tot) * 0.55;
125 (
126 xn.clamp(0.08, 0.92),
127 yn.clamp(0.08, 0.92),
128 zn.clamp(0.05, 0.95),
129 )
130 };
131 let bits = inp.affordances.pack();
132 let radius = 5.5
133 + if inp.affordances.can_edit { 2.0 } else { 0.0 }
134 + if inp.affordances.can_share { 1.0 } else { 0.0 };
135 out[n] = SceneNodeProj {
136 entity_id: inp.entity_id.raw(),
137 id: format!("{:016x}", inp.entity_id.raw()),
138 x,
139 y,
140 z,
141 color: wing_color(inp.wing).into(),
142 radius,
143 alpha: (0.5 + z * 0.5).clamp(0.45, 1.0),
144 affordance_bits: bits,
145 };
146 n += 1;
147 }
148 n
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::entity_view::entity_id::EntityId;
155 use crate::entity_view::observer::AffordanceBits;
156
157 #[test]
158 fn layout_uses_geo_when_present() {
159 let inputs = [LayoutInput {
160 entity_id: EntityId::from_uri("urn:pin"),
161 lat: Some(0.0),
162 lon: Some(0.0),
163 affordances: AffordanceBits::FULL,
164 wing: RepresentationWing::Commons,
165 }];
166 let mut out: Vec<SceneNodeProj> = (0..2)
167 .map(|_| SceneNodeProj {
168 entity_id: 0,
169 id: String::new(),
170 x: 0.0,
171 y: 0.0,
172 z: 0.0,
173 color: String::new(),
174 radius: 0.0,
175 alpha: 0.0,
176 affordance_bits: 0,
177 })
178 .collect();
179 let n = layout_scene_nodes(&inputs, &mut out);
180 assert_eq!(n, 1);
181 assert!((out[0].x - 0.5).abs() < 0.01);
182 }
183}