"""Adapt camera depth along the original outline with batched weight updates.""" from .boundary_smoothing import boundary_influence, reconstruct_camera_depth from .depth_surface import CUT_ATTRIBUTE, _math, ray_distance from .nodes import evaluate_field, group_input, read_float_attribute, sample_field from .smoothing import edge_boundary_field def _outline_influence(group, original, geometry): """Identify original outline points and protect split boundary intersections.""" nodes, links = group.nodes, group.links rim = evaluate_field(group, edge_boundary_field(nodes, links), "FLOAT", "POINT") nearest = nodes.new("GeometryNodeSampleNearest") nearest.domain = "POINT" links.new(original, nearest.inputs["Geometry"]) position = nodes.new("GeometryNodeInputPosition").outputs["Position"] links.new(position, nearest.inputs["Sample Position"]) original_rim = sample_field(group, original, rim, "FLOAT", index=nearest.outputs["Index"]) original_rim = _math(group, "GREATER_THAN", original_rim, 0) cut = _math(group, "GREATER_THAN", read_float_attribute(group, CUT_ATTRIBUTE), 0) protected = _math(group, "MULTIPLY", _math(group, "GREATER_THAN", rim, 0), _math(group, "MAXIMUM", cut, _math(group, "SUBTRACT", 1, original_rim))) # Resolve planar correspondence before replacing positions with camera depth. return sample_field(group, geometry, boundary_influence(group, protected), "FLOAT") def smooth_outline_camera(group, original, geometry, camera, uniform_scale): """Update outline weights and blur ray distance in fixed-weight batches.""" nodes, links = group.nodes, group.links controls = group_input(nodes, {"Outline Depth Fix", "Boundary Smooth"}) influence = _outline_influence(group, original, geometry) depth = ray_distance(group, camera) endpoints = nodes.new("GeometryNodeInputMeshEdgeVertices") length = nodes.new("ShaderNodeVectorMath") length.operation = "DISTANCE" links.new(endpoints.outputs["Position 1"], length.inputs[0]) links.new(endpoints.outputs["Position 2"], length.inputs[1]) statistic = nodes.new("GeometryNodeAttributeStatistic") statistic.data_type, statistic.domain = "FLOAT", "EDGE" links.new(geometry, statistic.inputs["Geometry"]) links.new(length.outputs["Value"], statistic.inputs["Attribute"]) scale = _math(group, "DIVIDE", uniform_scale, _math(group, "MAXIMUM", statistic.outputs["Median"], 1e-8)) seed = nodes.new("ShaderNodeCombineXYZ") links.new(depth, seed.inputs["Z"]) carrier = nodes.new("GeometryNodeSetPosition") links.new(geometry, carrier.inputs["Geometry"]) links.new(seed.outputs[0], carrier.inputs["Position"]) repeat_output = nodes.new("GeometryNodeRepeatOutput") repeat_input = nodes.new("GeometryNodeRepeatInput") repeat_input.pair_with_output(repeat_output) links.new(carrier.outputs[0], repeat_input.inputs["Geometry"]) links.new(controls.outputs["Outline Depth Fix"], repeat_input.inputs["Iterations"]) position = nodes.new("GeometryNodeInputPosition") current = nodes.new("ShaderNodeSeparateXYZ") links.new(position.outputs[0], current.inputs[0]) reference = nodes.new("GeometryNodeBlurAttribute") reference.data_type = "FLOAT" reference.inputs["Iterations"].default_value = 1 reference.inputs["Weight"].default_value = 0.5 links.new(current.outputs["Z"], reference.inputs["Value"]) residual = _math(group, "ABSOLUTE", _math(group, "SUBTRACT", current.outputs["Z"], reference.outputs[0])) ratio = _math(group, "MULTIPLY", residual, scale) weight = nodes.new("ShaderNodeMapRange") weight.interpolation_type, weight.clamp = "SMOOTHSTEP", True weight.inputs["From Min"].default_value = 0.25 weight.inputs["From Max"].default_value = 0.5 links.new(ratio, weight.inputs["Value"]) active = _math(group, "MULTIPLY", influence, _math(group, "GREATER_THAN", depth, 1e-8)) active = sample_field(group, geometry, active, "FLOAT") weighted = _math(group, "MULTIPLY", weight.outputs[0], active) # Sampling the round's input prevents the inner Blur from updating its weight. frozen = sample_field(group, repeat_input.outputs["Geometry"], weighted, "FLOAT") blur = nodes.new("GeometryNodeBlurAttribute") blur.data_type = "FLOAT" links.new(current.outputs["Z"], blur.inputs["Value"]) links.new(frozen, blur.inputs["Weight"]) links.new(controls.outputs["Boundary Smooth"], blur.inputs["Iterations"]) updated = nodes.new("ShaderNodeCombineXYZ") links.new(blur.outputs[0], updated.inputs["Z"]) move = nodes.new("GeometryNodeSetPosition") links.new(repeat_input.outputs["Geometry"], move.inputs["Geometry"]) links.new(updated.outputs[0], move.inputs["Position"]) links.new(move.outputs[0], repeat_output.inputs["Geometry"]) result = sample_field(group, repeat_output.outputs["Geometry"], current.outputs["Z"], "FLOAT") enabled = _math(group, "MULTIPLY", active, _math(group, "MULTIPLY", controls.outputs["Outline Depth Fix"], controls.outputs["Boundary Smooth"])) choose = nodes.new("GeometryNodeSwitch") choose.input_type = "FLOAT" links.new(enabled, choose.inputs["Switch"]) links.new(depth, choose.inputs["False"]) links.new(result, choose.inputs["True"]) return reconstruct_camera_depth(group, camera, depth, choose.outputs[0])