Skip to main content

fission_3d/
lib.rs

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