Skip to main content

fission_3d/
lib.rs

1pub mod render;
2use fission_core::internal::{InternalLowerer, InternalLoweringCx, InternalRenderNode};
3use fission_core::op::Color;
4use fission_core::ui::{Container, Widget};
5
6use fission_ir::op::{EmbedKind, LayoutOp};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Point3D {
11    pub x: f32,
12    pub y: f32,
13    pub z: f32,
14}
15
16impl Point3D {
17    pub fn new(x: f32, y: f32, z: f32) -> Self {
18        Self { x, y, z }
19    }
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub enum Primitive3D {
24    Cube {
25        center: Point3D,
26        size: f32,
27        color: Color,
28    },
29    Sphere {
30        center: Point3D,
31        radius: f32,
32        color: Color,
33    },
34    Mesh {
35        vertices: Vec<Point3D>,
36        indices: Vec<u32>,
37        color: Color,
38    },
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct Scene3D {
43    pub width: Option<f32>,
44    pub height: Option<f32>,
45    pub primitives: Vec<Primitive3D>,
46}
47
48impl Scene3D {
49    pub fn new() -> Self {
50        Self {
51            width: None,
52            height: None,
53            primitives: Vec::new(),
54        }
55    }
56
57    pub fn width(mut self, w: f32) -> Self {
58        self.width = Some(w);
59        self
60    }
61
62    pub fn height(mut self, h: f32) -> Self {
63        self.height = Some(h);
64        self
65    }
66
67    pub fn add_primitive(mut self, primitive: Primitive3D) -> Self {
68        self.primitives.push(primitive);
69        self
70    }
71}
72
73impl From<Scene3D> for Widget {
74    fn from(component: Scene3D) -> Self {
75        let this = &component;
76        let mut container = Container::new(fission_core::internal::custom_render_widget(
77            InternalRenderNode {
78                debug_tag: "fission_3d::Scene3D".into(),
79                lowerer: Some(std::sync::Arc::new(Scene3DInternalLowerer {
80                    scene: this.clone(),
81                })),
82                render_object: None,
83            },
84        ));
85        if let Some(w) = this.width {
86            container = container.width(w);
87        } else {
88            container = container.flex_grow(1.0);
89        }
90        if let Some(h) = this.height {
91            container = container.height(h);
92        } else {
93            if this.width.is_none() {
94                container = container.flex_grow(1.0);
95            }
96        }
97        container.into()
98    }
99}
100
101#[derive(Debug)]
102pub struct Scene3DInternalLowerer {
103    pub scene: Scene3D,
104}
105
106impl InternalLowerer for Scene3DInternalLowerer {
107    fn lower_dyn(&self, cx: &mut InternalLoweringCx) -> fission_ir::WidgetId {
108        let node_id = cx.next_node_id();
109
110        let w = self
111            .scene
112            .width
113            .unwrap_or_else(|| (cx.env.viewport_size.width - 264.0).max(400.0));
114        let h = self
115            .scene
116            .height
117            .unwrap_or_else(|| (cx.env.viewport_size.height - 200.0).max(300.0));
118
119        // In a real implementation, this would emit an EmbedKind::Surface3D
120        // and fission-shell-desktop would intercept it to render a wgpu scene
121        // For this milestone, we emit a 3D placeholder layout op.
122
123        let payload = bincode::serialize(&self.scene.primitives).unwrap_or_default();
124        let op = fission_ir::Op::Layout(LayoutOp::Embed {
125            kind: EmbedKind::Custom(payload),
126            widget_id: fission_ir::WidgetId::explicit("fission_3d_scene"),
127            width: Some(w),
128            height: Some(h),
129        });
130
131        cx.insert_node(node_id, op, vec![])
132    }
133}