Skip to main content

lumen_engine/node/processing/
transform.rs

1use crate::node::{NodeId, NodeProperty, PortRef};
2
3use crate::gpu::{
4    BoundFrame, CompiledOutput, FrameBindContext, FrameBinding, GpuCompileNode, GpuFrameBindNode,
5    RasterHandle, compiler,
6};
7
8pub(crate) const SHADER: &str = include_str!("transform.wgsl");
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, lumen_macros::NodeEnum)]
11#[repr(i64)]
12pub enum TransformSampling {
13    Nearest = 0,
14    Linear = 1,
15}
16
17impl TransformSampling {
18    pub fn from_int(value: i64) -> Self {
19        if value == Self::Nearest as i64 {
20            Self::Nearest
21        } else {
22            Self::Linear
23        }
24    }
25}
26
27/// Transforms a raster inside its existing static bounds.
28#[derive(Debug, Clone, lumen_macros::Node)]
29#[node(kind = "transform", name = "Transform", category = "processing")]
30pub struct Transform {
31    pub id: NodeId,
32    /// Horizontal scale multiplier.
33    #[property(kind = "float", name = "Scale X", step = 0.1)]
34    pub scale_x: NodeProperty,
35    /// Vertical scale multiplier.
36    #[property(kind = "float", name = "Scale Y", step = 0.1)]
37    pub scale_y: NodeProperty,
38    /// Horizontal translation in pixels.
39    #[property(kind = "float", name = "Translate X", step = 1)]
40    pub translate_x: NodeProperty,
41    /// Vertical translation in pixels.
42    #[property(kind = "float", name = "Translate Y", step = 1)]
43    pub translate_y: NodeProperty,
44    /// Rotation in degrees.
45    #[property(kind = "float", name = "Rotate", step = 1)]
46    pub rotate: NodeProperty,
47    /// Horizontal pivot point in pixels.
48    #[property(kind = "float", name = "Pivot X", step = 1)]
49    pub pivot_x: NodeProperty,
50    /// Vertical pivot point in pixels.
51    #[property(kind = "float", name = "Pivot Y", step = 1)]
52    pub pivot_y: NodeProperty,
53    /// Sampling filter used when transforming.
54    #[property(kind = "enum", enum_type = TransformSampling)]
55    pub sampling: NodeProperty,
56    #[input()]
57    pub source: PortRef,
58}
59
60impl Default for Transform {
61    fn default() -> Self {
62        Self {
63            id: NodeId::new(0),
64            scale_x: NodeProperty::Float(1.0),
65            scale_y: NodeProperty::Float(1.0),
66            translate_x: NodeProperty::Float(0.0),
67            translate_y: NodeProperty::Float(0.0),
68            rotate: NodeProperty::Float(0.0),
69            pivot_x: NodeProperty::Float(0.0),
70            pivot_y: NodeProperty::Float(0.0),
71            sampling: NodeProperty::Int(TransformSampling::Linear as i64),
72            source: PortRef::empty(),
73        }
74    }
75}
76
77impl GpuCompileNode for Transform {
78    fn compile_gpu(
79        &self,
80        ctx: &mut crate::gpu::CompileContext<'_>,
81        port: &PortRef,
82    ) -> crate::Result<CompiledOutput> {
83        let (source, texture, params) = ctx.compile_unary_filter(
84            self.id,
85            &self.source,
86            port,
87            "transform",
88            SHADER,
89            std::mem::size_of::<compiler::TransformParams>() as u64,
90        )?;
91        ctx.push_frame_binding(FrameBinding::Transform {
92            node_id: self.id,
93            scale_x: self.scale_x.clone(),
94            scale_y: self.scale_y.clone(),
95            translate_x: self.translate_x.clone(),
96            translate_y: self.translate_y.clone(),
97            rotate: self.rotate.clone(),
98            pivot_x: self.pivot_x.clone(),
99            pivot_y: self.pivot_y.clone(),
100            sampling: self.sampling.clone(),
101            buffer: params,
102        });
103        Ok(CompiledOutput::Raster(RasterHandle {
104            texture,
105            domain: source.domain,
106            metadata: source.metadata,
107        }))
108    }
109}
110
111impl GpuFrameBindNode for Transform {
112    fn bind_gpu_frame(
113        &self,
114        ctx: &FrameBindContext<'_>,
115        binding: &FrameBinding,
116        bound: &mut BoundFrame,
117    ) -> crate::Result<()> {
118        let FrameBinding::Transform {
119            node_id,
120            scale_x,
121            scale_y,
122            translate_x,
123            translate_y,
124            rotate,
125            pivot_x,
126            pivot_y,
127            sampling,
128            buffer,
129        } = binding
130        else {
131            return Ok(());
132        };
133        let params = compiler::TransformParams {
134            scale: [
135                scale_x.resolve_float(
136                    *node_id,
137                    "scale_x",
138                    &ctx.expr_context(*node_id, "scale_x"),
139                )? as f32,
140                scale_y.resolve_float(
141                    *node_id,
142                    "scale_y",
143                    &ctx.expr_context(*node_id, "scale_y"),
144                )? as f32,
145            ],
146            translate: [
147                translate_x.resolve_float(
148                    *node_id,
149                    "translate_x",
150                    &ctx.expr_context(*node_id, "translate_x"),
151                )? as f32,
152                translate_y.resolve_float(
153                    *node_id,
154                    "translate_y",
155                    &ctx.expr_context(*node_id, "translate_y"),
156                )? as f32,
157            ],
158            pivot: [
159                pivot_x.resolve_float(
160                    *node_id,
161                    "pivot_x",
162                    &ctx.expr_context(*node_id, "pivot_x"),
163                )? as f32,
164                pivot_y.resolve_float(
165                    *node_id,
166                    "pivot_y",
167                    &ctx.expr_context(*node_id, "pivot_y"),
168                )? as f32,
169            ],
170            rotate_radians: (rotate.resolve_float(
171                *node_id,
172                "rotate",
173                &ctx.expr_context(*node_id, "rotate"),
174            )? as f32)
175                .to_radians(),
176            sampling: TransformSampling::from_int(sampling.resolve_int(
177                *node_id,
178                "sampling",
179                &ctx.expr_context(*node_id, "sampling"),
180            )?) as u32,
181            _pad: [0; 4],
182        };
183        bound.write_buffer(*buffer, 0, bytemuck::bytes_of(&params));
184        Ok(())
185    }
186}