Motion transfer setup #1

Manually merged
Sybren A. Stüvel merged 18 commits from cgtinker/powership:motion_transfer into main 2023-06-05 12:06:15 +02:00
2 changed files with 77 additions and 84 deletions
Showing only changes of commit e27f71abec - Show all commits

161
nodes.py
View File

@ -724,10 +724,8 @@ class RotateTowards(AbstractPowerShipNode):
# Set the rotation of the control
vec = Vector((destination - origin))
# vec.normalize()
rot = vec.to_track_quat(self.track, self.up)
rot.normalize()
self.outputs["Rotation"].default_value = rot
self.outputs["Rotation"].default_value = rot.normalized()
class OffsetRotation(AbstractPowerShipNode):
@ -776,20 +774,15 @@ class MapRange(AbstractPowerShipNode):
self.outputs["Result"].default_value = slope * val + offset
class RotationFromAngle(AbstractPowerShipNode):
bl_idname = "RotationFromAngle"
bl_label = "Rotation From Vector Angle"
class AngleFromVectors(AbstractPowerShipNode):
bl_idname = "AngleFromVectors"
bl_label = "Angle From Vectors"
bl_icon = "EMPTY_ARROWS"
axis: bpy.props.EnumProperty( # type: ignore
name="Axis",
items=_enum_up_axis_items,
)
angle_type: bpy.props.EnumProperty( # type: ignore
name="Type",
items=[
("DEFAULT", "Default", ""),
("DEFAULT", "Unsigned", ""),
("SIGNED", "Signed", ""),
],
)
@ -798,13 +791,12 @@ class RotationFromAngle(AbstractPowerShipNode):
self, context: bpy.types.Context, layout: bpy.types.UILayout
) -> None:
super().draw_buttons(context, layout)
layout.prop(self, "axis")
layout.prop(self, "angle_type")
def init(self, context):
self.add_optional_input_socket("NodeSocketVector", "U")
self.add_optional_input_socket("NodeSocketVector", "V")
self.outputs.new("NodeSocketQuaternion", "Rotation")
self.outputs.new("NodeSocketFloat", "Angle")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
u = self._get_optional_input_value("U", Vector)
@ -818,10 +810,7 @@ class RotationFromAngle(AbstractPowerShipNode):
else:
angle = u.angle(v)
m = Matrix.Rotation(angle, 3, self.axis)
res = m.to_quaternion()
self.outputs["Rotation"].default_value = res
self.outputs["Angle"].default_value = angle
_enum_vector_math_operations = [
@ -1006,6 +995,7 @@ class SetBoneNode(AbstractPowerShipNode):
bone_mat_world: Matrix = arm_matrix @ bone.matrix
loc, rot, scale = bone_mat_world.decompose()
if control_location is not None:
loc = control_location
if control_rotation is not None:
@ -1013,24 +1003,27 @@ class SetBoneNode(AbstractPowerShipNode):
if control_scale is not None:
scale = control_scale
# TODO: Fix jittering bone scale which happens
# esp. when multiple bones are parented to the rotated bone
# rounding helps but does not entirely fix the issue.
scale = [round(x, 4) for x in scale]
v_nil = Vector((0, 0, 0))
bone_rest_rot_scale = bone.bone.matrix_local.copy()
match self.space:
case "WORLD":
bone_mat_world = Matrix.LocRotScale(loc, rot, scale)
bone_mat_world = Matrix.LocRotScale(
loc, rot.normalized(), scale)
loc, rot, scale = bone_mat_world.decompose()
case "CHANNELS":
# Not sure what causes the scale to flip, however...
# The y/z-scale flips on update if multiple rotations are chained,
# this generates jitter when running const updates - flipping seems to temp fix it.
scale = [scale[i] for i in [0, 2, 1]]
v_nil = Vector((0, 0, 0))
bone_rest_rot_scale = bone.bone.matrix_local.copy()
bone_rest_rot_scale.translation = v_nil
bone_rest_rot = bone_rest_rot_scale.to_quaternion()
mat_rot_scale = Matrix.LocRotScale(
v_nil, rot, scale) @ bone_rest_rot.to_matrix().to_4x4()
v_nil, rot, scale) @ bone_rest_rot_scale
mat_loc = Matrix.Translation(loc)
bone_mat_world = mat_loc @ mat_rot_scale
bone.matrix = arm_matrix.inverted() @ bone_mat_world
loc, rot, scale = bone.matrix.decompose()
class TwoBoneIKNode(AbstractPowerShipNode):
@ -1237,61 +1230,61 @@ class ClampNode(AbstractPowerShipNode):
self.outputs["Result"].default_value = clamped
class AbstractTwoValueMathNode(AbstractPowerShipNode):
def init(self, context: bpy.types.Context) -> None:
self.inputs.new("NodeSocketFloat", "A")
self.inputs.new("NodeSocketFloat", "B")
self.outputs.new("NodeSocketFloat", "Result")
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
a = self._get_input_value("A", float)
b = self._get_input_value("B", float)
self.outputs["Result"].default_value = self.calculate(a, b)
def calculate(self, a: float, b: float) -> float:
return 0
class AddNode(AbstractTwoValueMathNode):
"""Add two values"""
bl_idname = "AddNode"
bl_label = "Add"
def calculate(self, a: float, b: float) -> float:
return a + b
class SubtractNode(AbstractTwoValueMathNode):
"""Subtract two values"""
bl_idname = "SubtractNode"
bl_label = "Subtract"
def calculate(self, a: float, b: float) -> float:
return a - b
class MultiplyNode(AbstractTwoValueMathNode):
"""Multiply two values"""
bl_idname = "MultiplyNode"
bl_label = "Multiply"
def calculate(self, a: float, b: float) -> float:
return a * b
class DivideNode(AbstractTwoValueMathNode):
"""Divide two values; division by zero results in NaN"""
bl_idname = "DivideNode"
bl_label = "Divide"
def calculate(self, a: float, b: float) -> float:
if b == 0:
return float("nan")
return a / b
# class AbstractTwoValueMathNode(AbstractPowerShipNode):
# def init(self, context: bpy.types.Context) -> None:
# self.inputs.new("NodeSocketFloat", "A")
# self.inputs.new("NodeSocketFloat", "B")
# self.outputs.new("NodeSocketFloat", "Result")
#
# def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
# a = self._get_input_value("A", float)
# b = self._get_input_value("B", float)
# self.outputs["Result"].default_value = self.calculate(a, b)
#
# def calculate(self, a: float, b: float) -> float:
# return 0
#
#
# class AddNode(AbstractTwoValueMathNode):
# """Add two values"""
#
# bl_idname = "AddNode"
# bl_label = "Add"
#
# def calculate(self, a: float, b: float) -> float:
# return a + b
#
#
# class SubtractNode(AbstractTwoValueMathNode):
# """Subtract two values"""
#
# bl_idname = "SubtractNode"
# bl_label = "Subtract"
#
# def calculate(self, a: float, b: float) -> float:
# return a - b
#
#
# class MultiplyNode(AbstractTwoValueMathNode):
# """Multiply two values"""
#
# bl_idname = "MultiplyNode"
# bl_label = "Multiply"
#
# def calculate(self, a: float, b: float) -> float:
# return a * b
#
#
# class DivideNode(AbstractTwoValueMathNode):
# """Divide two values; division by zero results in NaN"""
#
# bl_idname = "DivideNode"
# bl_label = "Divide"
#
# def calculate(self, a: float, b: float) -> float:
# if b == 0:
# return float("nan")
# return a / b
def _on_num_sockets_change(self: "SequenceNode", context: bpy.types.Context) -> None:
@ -1394,7 +1387,7 @@ node_categories = [
items=[
nodeitems_utils.NodeItem("RotateTowards"),
nodeitems_utils.NodeItem("OffsetRotation"),
nodeitems_utils.NodeItem("RotationFromAngle"),
nodeitems_utils.NodeItem("AngleFromVectors"),
nodeitems_utils.NodeItem("NormalFromPoints"),
nodeitems_utils.NodeItem("SplitVector"),
nodeitems_utils.NodeItem("ToVector"),
@ -1439,7 +1432,7 @@ classes = (
SequenceNode,
# Math Nodes
RotateTowards,
RotationFromAngle,
AngleFromVectors,
OffsetRotation,
NormalFromPoints,
ToVector,

Binary file not shown.