Skip to main content

lumen_engine/node/processing/
transform.rs

1use crate::node::{NodeId, NodeParamEvalContext, NodeParams, PortRef};
2
3use crate::gpu::{
4    BoundFrame, CompiledOutput, FrameBindContext, GpuCompileNode, GpuCompiledNode, RasterHandle,
5    compiler,
6};
7
8pub(crate) const SHADER: &str = include_str!("transform.wgsl");
9
10#[derive(
11    Debug, Clone, Copy, PartialEq, Eq, Default, lumen_macros::NodeEnum, lumen_macros::Delegate,
12)]
13#[repr(i64)]
14#[delegate(kind = "enum")]
15pub enum TransformSampling {
16    Nearest = 0,
17    #[default]
18    Linear = 1,
19}
20
21/// Transforms a raster inside its existing static bounds.
22#[derive(Debug, Clone, lumen_macros::Delegate)]
23pub struct TransformParams {
24    /// Horizontal scale multiplier.
25    #[meta(name = "Scale X", step = 0.1)]
26    pub scale_x: f64,
27    /// Vertical scale multiplier.
28    #[meta(name = "Scale Y", step = 0.1)]
29    pub scale_y: f64,
30    /// Horizontal translation in pixels.
31    #[meta(name = "Translate X", step = 1)]
32    pub translate_x: f64,
33    /// Vertical translation in pixels.
34    #[meta(name = "Translate Y", step = 1)]
35    pub translate_y: f64,
36    /// Rotation in degrees.
37    #[meta(name = "Rotate", step = 1)]
38    pub rotate: f64,
39    /// Horizontal pivot point in pixels.
40    #[meta(name = "Pivot X", step = 1)]
41    pub pivot_x: f64,
42    /// Vertical pivot point in pixels.
43    #[meta(name = "Pivot Y", step = 1)]
44    pub pivot_y: f64,
45    /// Sampling filter used when transforming.
46    #[meta(kind = "enum", enum_type = TransformSampling)]
47    pub sampling: TransformSampling,
48}
49
50impl Default for TransformParams {
51    fn default() -> Self {
52        Self {
53            scale_x: 1.0,
54            scale_y: 1.0,
55            translate_x: 0.0,
56            translate_y: 0.0,
57            rotate: 0.0,
58            pivot_x: 0.0,
59            pivot_y: 0.0,
60            sampling: TransformSampling::Linear,
61        }
62    }
63}
64
65/// Transforms a raster inside its existing static bounds.
66#[derive(Debug, Clone, lumen_macros::Node)]
67#[node(kind = "transform", name = "Transform", category = "processing")]
68pub struct Transform {
69    pub id: NodeId,
70    #[params]
71    pub params: TransformParamsDelegate,
72
73    #[input()]
74    pub source: PortRef,
75}
76
77impl Default for Transform {
78    fn default() -> Self {
79        Self {
80            id: NodeId::new(0),
81            params: TransformParamsDelegate::default(),
82            source: PortRef::empty(),
83        }
84    }
85}
86
87impl GpuCompileNode for Transform {
88    fn compile_gpu(
89        &self,
90        ctx: &mut crate::gpu::CompileContext<'_>,
91        port: &PortRef,
92    ) -> crate::Result<CompiledOutput> {
93        let (source, texture, params) = ctx.compile_unary_filter(
94            self.id,
95            &self.source,
96            port,
97            "transform",
98            SHADER,
99            std::mem::size_of::<compiler::TransformParams>() as u64,
100        )?;
101        ctx.register_compiled_node(CompiledTransform {
102            node_id: self.id,
103            params: self.params.clone(),
104            buffer: params,
105        });
106        Ok(CompiledOutput::Raster(RasterHandle {
107            texture,
108            domain: source.domain,
109            metadata: source.metadata,
110        }))
111    }
112}
113
114#[derive(Debug, Clone)]
115struct CompiledTransform {
116    node_id: NodeId,
117    params: TransformParamsDelegate,
118    buffer: lumen_gpu::BufferId,
119}
120
121impl GpuCompiledNode for CompiledTransform {
122    fn node_id(&self) -> NodeId {
123        self.node_id
124    }
125
126    fn bind(&self, ctx: &FrameBindContext<'_>, bound: &mut BoundFrame) -> crate::Result<()> {
127        let evaluated = self.params.eval(&NodeParamEvalContext {
128            node_id: self.node_id,
129            expr: &ctx.expr_context(self.node_id, "params"),
130        })?;
131        let params = compiler::TransformParams {
132            scale: [evaluated.scale_x as f32, evaluated.scale_y as f32],
133            translate: [evaluated.translate_x as f32, evaluated.translate_y as f32],
134            pivot: [evaluated.pivot_x as f32, evaluated.pivot_y as f32],
135            rotate_radians: (evaluated.rotate as f32).to_radians(),
136            sampling: evaluated.sampling as u32,
137            _pad: [0; 4],
138        };
139        bound.write_buffer(self.buffer, 0, bytemuck::bytes_of(&params));
140        Ok(())
141    }
142}