use std::collections::{HashMap, HashSet};
use crate::brush::eval::BrushNodeEvaluator;
use crate::brush::wire::BrushWireType;
use crate::gpu::params::ParamValue;
use crate::nodegraph::{ExecutionPlan, NodeId, PortDef, PortDir};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ExtentContribution {
Identity,
Multiply(f32),
AddCanvasPixels { passthrough: f32, added_px: f32 },
ClipTo(f32),
}
pub struct ExtentCtx<'a> {
pub node_id: NodeId,
pub params: &'a [ParamValue],
pub port_defs: &'a [PortDef<BrushWireType>],
pub wired_inputs: HashSet<String>,
}
impl ExtentCtx<'_> {
pub fn port_max_value(&self, port_name: &str) -> f32 {
let Some(port) = self
.port_defs
.iter()
.find(|p| p.name == port_name && p.dir == PortDir::Input)
else {
return 0.0;
};
if self.wired_inputs.contains(port_name) {
port.natural_range.map(|(_, max)| max).unwrap_or(port.max)
} else {
port.default
}
}
}
pub(crate) fn compose_brush_extent(
graph: &crate::nodegraph::Graph<BrushWireType>,
plan: &ExecutionPlan,
evaluators: &HashMap<String, Box<dyn BrushNodeEvaluator>>,
) -> (f32, f32) {
let mut factor: f32 = 1.0;
let mut extra_px: f32 = 0.0;
for step in &plan.steps {
let Some(evaluator) = evaluators.get(&step.type_id) else {
continue;
};
let Some(node) = graph.nodes().get(&step.node_id) else {
continue;
};
let wired_inputs: HashSet<String> = step
.input_slots
.iter()
.map(|s| s.port_name.clone())
.collect();
let ectx = ExtentCtx {
node_id: step.node_id,
params: &node.params,
port_defs: &node.ports,
wired_inputs,
};
match evaluator.extent(&ectx) {
ExtentContribution::Identity => {}
ExtentContribution::Multiply(m) => {
factor *= m;
extra_px *= m;
}
ExtentContribution::AddCanvasPixels {
passthrough,
added_px,
} => {
factor *= passthrough;
extra_px = extra_px * passthrough + added_px;
}
ExtentContribution::ClipTo(cap) => {
factor = factor.min(cap);
}
}
}
(factor, extra_px)
}