use super::super::hlsl::{HlslShader, HlslTranspileMode};
use crate::types::SdfNode;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlinkScriptTranspileMode {
Hardcoded,
Dynamic,
}
impl From<BlinkScriptTranspileMode> for HlslTranspileMode {
fn from(m: BlinkScriptTranspileMode) -> Self {
match m {
BlinkScriptTranspileMode::Hardcoded => Self::Hardcoded,
BlinkScriptTranspileMode::Dynamic => Self::Dynamic,
}
}
}
#[derive(Debug, Clone)]
pub struct BlinkScriptShader {
pub source: String,
pub helper_count: usize,
pub param_layout: Vec<f32>,
pub mode: BlinkScriptTranspileMode,
}
impl BlinkScriptShader {
pub fn transpile(node: &SdfNode, mode: BlinkScriptTranspileMode) -> Self {
let hlsl = HlslShader::transpile(node, mode.into());
Self {
source: hlsl.source,
helper_count: hlsl.helper_count,
param_layout: hlsl.param_layout,
mode,
}
}
pub fn extract_params(node: &SdfNode) -> Vec<f32> {
HlslShader::extract_params(node)
}
pub fn to_kernel(&self, z_uniform: f32, bounds: (f32, f32)) -> String {
let (bmin, bmax) = bounds;
let dynamic_note = if self.mode == BlinkScriptTranspileMode::Dynamic {
"// NOTE: Dynamic mode — populate the `params[]` float[] buffer\n// on the BlinkScript node's Parameters panel before recompiling.\n"
} else {
""
};
format!(
r#"// ALICE-SDF Generated BlinkScript Kernel
// Function body reused verbatim from the HLSL transpiler.
// Drop into the "Kernel" text area of a BlinkScript node in Nuke 12+.
{dynamic_note}
kernel AliceSdfSliceKernel : ImageComputationKernel<eComponentWise>
{{
Image<eWrite, eAccessPoint> dst;
param:
float z_slice;
float bounds_min;
float bounds_max;
void define() {{
defineParam(z_slice, "z_slice", {z_uniform});
defineParam(bounds_min, "bounds_min", {bmin});
defineParam(bounds_max, "bounds_max", {bmax});
}}
// ---- SDF eval function (transpiled from ALICE-SDF tree) ----
{source}
void process(int2 pos) {{
float span = bounds_max - bounds_min;
float w = float(dst.bounds.width());
float h = float(dst.bounds.height());
float u = (float(pos.x) + 0.5f) / w;
float v = (float(pos.y) + 0.5f) / h;
float3 p = float3(
bounds_min + u * span,
bounds_min + v * span,
z_slice
);
dst() = sdf_eval(p);
}}
}}
"#,
dynamic_note = dynamic_note,
source = self.source,
z_uniform = z_uniform,
bmin = bmin,
bmax = bmax,
)
}
pub fn get_eval_function(&self) -> &str {
&self.source
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::SdfNode;
#[test]
fn transpile_sphere_produces_sdf_eval_function() {
let node = SdfNode::sphere(1.0);
let shader = BlinkScriptShader::transpile(&node, BlinkScriptTranspileMode::Hardcoded);
assert!(
shader.source.contains("float sdf_eval(float3 p)"),
"missing sdf_eval signature: {}",
shader.source
);
assert_eq!(shader.mode, BlinkScriptTranspileMode::Hardcoded);
assert!(shader.param_layout.is_empty());
}
#[test]
fn to_kernel_embeds_source_and_bounds() {
let node = SdfNode::sphere(1.0);
let shader = BlinkScriptShader::transpile(&node, BlinkScriptTranspileMode::Hardcoded);
let kernel = shader.to_kernel(0.0, (-2.0, 2.0));
assert!(kernel.contains("kernel AliceSdfSliceKernel"));
assert!(kernel.contains("ImageComputationKernel<eComponentWise>"));
assert!(kernel.contains("defineParam(z_slice"));
assert!(kernel.contains(r#"defineParam(bounds_min, "bounds_min", -2)"#));
assert!(kernel.contains(r#"defineParam(bounds_max, "bounds_max", 2)"#));
assert!(kernel.contains(r#"defineParam(z_slice, "z_slice", 0)"#));
assert!(kernel.contains("sdf_eval(p)"));
}
#[test]
fn dynamic_mode_matches_hlsl_param_extract() {
let node = SdfNode::sphere(1.5).smooth_union(SdfNode::box3d(0.5, 0.5, 0.5), 0.2);
let dyn_shader = BlinkScriptShader::transpile(&node, BlinkScriptTranspileMode::Dynamic);
let params = BlinkScriptShader::extract_params(&node);
assert_eq!(dyn_shader.param_layout, params);
}
}