- Implemented `cutout_shell.py` to build front normals and shell positions for balloon geometry. - Created `cutout_volume.py` to establish smooth center fields and manage balloon surface attributes. - Introduced `pisa_sr_export.py` for exporting PiSA-SR components with dependencies and asset finalization.
123 lines
5.7 KiB
Python
123 lines
5.7 KiB
Python
"""Continue a front-normal thin shell into the rounded depth volume."""
|
|
from cutout_inflation import build_inflation_position
|
|
from cutout_volume import node, shift_stage
|
|
|
|
|
|
def build_front_normals(group, geometry):
|
|
links = group.links
|
|
normal = node(group, "GeometryNodeInputNormal", (3400, -650))
|
|
control = node(group, "NodeGroupInput", (3400, -950))
|
|
blur = node(group, "GeometryNodeBlurAttribute", (3640, -650))
|
|
blur.data_type = "FLOAT_VECTOR"
|
|
links.new(normal.outputs[0], blur.inputs["Value"])
|
|
links.new(control.outputs["Normal Smooth"], blur.inputs["Iterations"])
|
|
normalize = node(group, "ShaderNodeVectorMath", (3880, -650))
|
|
normalize.operation = "NORMALIZE"
|
|
links.new(blur.outputs[0], normalize.inputs[0])
|
|
store = node(group, "GeometryNodeStoreNamedAttribute", (4110, 0))
|
|
store.data_type, store.domain = "FLOAT_VECTOR", "POINT"
|
|
store.inputs["Name"].default_value = "o_balloon_surface_front_normal"
|
|
links.new(geometry, store.inputs["Geometry"])
|
|
links.new(normalize.outputs[0], store.inputs["Value"])
|
|
return store.outputs[0]
|
|
|
|
|
|
def build_shell_position(group, position, uniform, offset):
|
|
previous = set(group.nodes)
|
|
links = group.links
|
|
|
|
def connect(value, target):
|
|
if hasattr(value, "node"):
|
|
links.new(value, target)
|
|
else:
|
|
target.default_value = value
|
|
|
|
def math(operation, a, b, location):
|
|
result = node(group, "ShaderNodeMath", location)
|
|
result.operation = operation
|
|
connect(a, result.inputs[0])
|
|
connect(b, result.inputs[1])
|
|
return result.outputs[0]
|
|
|
|
def vector(operation, a, b, location):
|
|
result = node(group, "ShaderNodeVectorMath", location)
|
|
result.operation = operation
|
|
connect(a, result.inputs[0])
|
|
connect(b, result.inputs[3 if operation == "SCALE" else 1])
|
|
return result.outputs[0]
|
|
|
|
def read(name, location, kind="FLOAT"):
|
|
result = node(group, "GeometryNodeInputNamedAttribute", location)
|
|
result.data_type = kind
|
|
result.inputs["Name"].default_value = name
|
|
return result.outputs["Attribute"]
|
|
|
|
def z(value, location):
|
|
result = node(group, "ShaderNodeSeparateXYZ", location)
|
|
links.new(value, result.inputs[0])
|
|
return result.outputs["Z"]
|
|
|
|
# Uniform and the thin end of Balloon share the actual front normal.
|
|
front = read("o_depth_surface_position", (0, 0), "FLOAT_VECTOR")
|
|
front_z = z(front, (240, 0))
|
|
raw_z = z(position, (240, 250))
|
|
distance = math("SUBTRACT", raw_z, front_z, (480, 250))
|
|
normal = read("o_balloon_surface_front_normal", (480, 550), "FLOAT_VECTOR")
|
|
thin_offset = vector("SCALE", normal, distance, (720, 550))
|
|
uniform_position = vector("ADD", front, thin_offset, (3120, 550))
|
|
|
|
# R = t * (front_z - center) + (1 - t) * front_delta, with t clamped.
|
|
# This avoids division by Thickness and removes the old zero-thickness drift.
|
|
control = node(group, "NodeGroupInput", (0, -400))
|
|
clamp = node(group, "ShaderNodeClamp", (240, -400))
|
|
links.new(control.outputs["Thickness"], clamp.inputs["Value"])
|
|
t = clamp.outputs[0]
|
|
center = read("o_balloon_surface_center", (0, -750))
|
|
delta = read("o_balloon_surface_front_delta", (480, -1050))
|
|
radius = math("SUBTRACT", front_z, center, (480, -750))
|
|
radius = math("MULTIPLY", radius, t, (720, -750))
|
|
remaining = math("SUBTRACT", 1, t, (480, -400))
|
|
remainder = math("MULTIPLY", remaining, delta, (720, -1050))
|
|
radius = math("ADD", radius, remainder, (960, -750))
|
|
|
|
# Front and rear use the same edge field, evaluated on their own caps.
|
|
edge = read("o_balloon_surface_edge_weight", (0, -1450))
|
|
mask = read("o_normal_scale", (240, -1800))
|
|
remaining = math("SUBTRACT", 1, edge, (240, -1450))
|
|
remaining = math("MULTIPLY", remaining, mask, (480, -1450))
|
|
weight = math("SUBTRACT", 1, remaining, (720, -1450))
|
|
difference = math("SUBTRACT", weight, edge, (960, -1450))
|
|
base = math("MULTIPLY", difference, radius, (1200, -1450))
|
|
layer = math("MULTIPLY", weight, distance, (1200, -1800))
|
|
rear_radius = math("ADD", base, layer, (1440, -1450))
|
|
front_radius = math("MULTIPLY", edge, radius, (1440, -750))
|
|
normal = read("o_balloon_surface_normal", (1200, -300), "FLOAT_VECTOR")
|
|
direction = vector("SUBTRACT", normal, (0, 0, 1), (1440, -300))
|
|
front_shift = vector("SCALE", direction, front_radius, (1680, -750))
|
|
front_position = vector("ADD", front, front_shift, (1920, -750))
|
|
rear_shift = vector("SCALE", direction, rear_radius, (1680, -1450))
|
|
axis_offset = node(group, "ShaderNodeCombineXYZ", (1680, -1800))
|
|
links.new(distance, axis_offset.inputs["Z"])
|
|
full_offset = vector("ADD", axis_offset.outputs[0], rear_shift, (1920, -1450))
|
|
|
|
# Smoothly reach the existing full-volume shape at Thickness >= 1.
|
|
transition = node(group, "ShaderNodeMapRange", (1920, 0))
|
|
transition.interpolation_type = "SMOOTHERSTEP"
|
|
links.new(t, transition.inputs["Value"])
|
|
mix = node(group, "ShaderNodeMix", (2400, -1100))
|
|
mix.data_type = "VECTOR"
|
|
links.new(transition.outputs[0], mix.inputs[0])
|
|
links.new(thin_offset, mix.inputs[4])
|
|
links.new(full_offset, mix.inputs[5])
|
|
balloon_position = vector("ADD", front_position, mix.outputs["Result"], (3120, -750))
|
|
choose = node(group, "GeometryNodeSwitch", (3400, 0))
|
|
choose.input_type = "VECTOR"
|
|
links.new(uniform, choose.inputs["Switch"])
|
|
links.new(balloon_position, choose.inputs["False"])
|
|
links.new(uniform_position, choose.inputs["True"])
|
|
for item in set(group.nodes) - previous:
|
|
item.location.x *= 0.9
|
|
item.location.y *= 0.65
|
|
shift_stage(group, previous, offset)
|
|
return build_inflation_position(group, choose.outputs[0], distance, uniform)
|