Motion transfer setup #1
161
nodes.py
@ -724,10 +724,8 @@ class RotateTowards(AbstractPowerShipNode):
|
|||||||
|
|
||||||
# Set the rotation of the control
|
# Set the rotation of the control
|
||||||
vec = Vector((destination - origin))
|
vec = Vector((destination - origin))
|
||||||
# vec.normalize()
|
|
||||||
rot = vec.to_track_quat(self.track, self.up)
|
rot = vec.to_track_quat(self.track, self.up)
|
||||||
cgtinker marked this conversation as resolved
Outdated
|
|||||||
rot.normalize()
|
self.outputs["Rotation"].default_value = rot.normalized()
|
||||||
self.outputs["Rotation"].default_value = rot
|
|
||||||
|
|
||||||
|
|
||||||
class OffsetRotation(AbstractPowerShipNode):
|
class OffsetRotation(AbstractPowerShipNode):
|
||||||
@ -776,20 +774,15 @@ class MapRange(AbstractPowerShipNode):
|
|||||||
self.outputs["Result"].default_value = slope * val + offset
|
self.outputs["Result"].default_value = slope * val + offset
|
||||||
|
|
||||||
Sybren A. Stüvel
commented
Why name it Why name it `DEFAULT` instead of `UNSIGNED`? And why is `UNSIGNED` the default value? Not saying that it should be the other one, just wondering about your thought process.
Denys Hsu
commented
Usually an angle is not signed, you got to define something to sign it. That's why I thought unsigned angles should be the default. I rechecked and to be honest, I neither knew that the "signed" vector method is only for 2D vectors nor how it actually signed an angle. So I guess I removed it and kept only the "unsigned" angle for now - my bad sorry. Kinda liked the idea of an easy to use signed angle. So far, when I needed a signed angle, I ended up using a plane. Basically I've used the signed distance (based on the normal) from the plane to the destination of the vector and used the sign for the angle. This is possible with the current system without changes so I guess it's fine. Usually an angle is not signed, you got to define something to sign it. That's why I thought unsigned angles should be the default.
I rechecked and to be honest, I neither knew that the "signed" vector method is only for 2D vectors nor how it actually signed an angle. So I guess I removed it and kept only the "unsigned" angle for now - my bad sorry. Kinda liked the idea of an easy to use signed angle.
So far, when I needed a signed angle, I ended up using a plane. Basically I've used the signed distance (based on the normal) from the plane to the destination of the vector and used the sign for the angle. This is possible with the current system without changes so I guess it's fine.
|
|||||||
|
|
||||||
class RotationFromAngle(AbstractPowerShipNode):
|
class AngleFromVectors(AbstractPowerShipNode):
|
||||||
bl_idname = "RotationFromAngle"
|
bl_idname = "AngleFromVectors"
|
||||||
bl_label = "Rotation From Vector Angle"
|
bl_label = "Angle From Vectors"
|
||||||
bl_icon = "EMPTY_ARROWS"
|
bl_icon = "EMPTY_ARROWS"
|
||||||
|
|
||||||
axis: bpy.props.EnumProperty( # type: ignore
|
|
||||||
name="Axis",
|
|
||||||
items=_enum_up_axis_items,
|
|
||||||
)
|
|
||||||
|
|
||||||
angle_type: bpy.props.EnumProperty( # type: ignore
|
angle_type: bpy.props.EnumProperty( # type: ignore
|
||||||
name="Type",
|
name="Type",
|
||||||
items=[
|
items=[
|
||||||
("DEFAULT", "Default", ""),
|
("DEFAULT", "Unsigned", ""),
|
||||||
("SIGNED", "Signed", ""),
|
("SIGNED", "Signed", ""),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -798,13 +791,12 @@ class RotationFromAngle(AbstractPowerShipNode):
|
|||||||
self, context: bpy.types.Context, layout: bpy.types.UILayout
|
self, context: bpy.types.Context, layout: bpy.types.UILayout
|
||||||
) -> None:
|
) -> None:
|
||||||
super().draw_buttons(context, layout)
|
super().draw_buttons(context, layout)
|
||||||
layout.prop(self, "axis")
|
|
||||||
layout.prop(self, "angle_type")
|
layout.prop(self, "angle_type")
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
self.add_optional_input_socket("NodeSocketVector", "U")
|
self.add_optional_input_socket("NodeSocketVector", "U")
|
||||||
cgtinker marked this conversation as resolved
Outdated
Sybren A. Stüvel
commented
Don't compute Don't compute `length` when you can use `length_squared` as well.
|
|||||||
self.add_optional_input_socket("NodeSocketVector", "V")
|
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:
|
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
|
||||||
u = self._get_optional_input_value("U", Vector)
|
u = self._get_optional_input_value("U", Vector)
|
||||||
@ -818,10 +810,7 @@ class RotationFromAngle(AbstractPowerShipNode):
|
|||||||
else:
|
else:
|
||||||
angle = u.angle(v)
|
angle = u.angle(v)
|
||||||
|
|
||||||
m = Matrix.Rotation(angle, 3, self.axis)
|
self.outputs["Angle"].default_value = angle
|
||||||
res = m.to_quaternion()
|
|
||||||
|
|
||||||
self.outputs["Rotation"].default_value = res
|
|
||||||
|
|
||||||
|
|
||||||
_enum_vector_math_operations = [
|
_enum_vector_math_operations = [
|
||||||
@ -1006,6 +995,7 @@ class SetBoneNode(AbstractPowerShipNode):
|
|||||||
bone_mat_world: Matrix = arm_matrix @ bone.matrix
|
bone_mat_world: Matrix = arm_matrix @ bone.matrix
|
||||||
|
|
||||||
loc, rot, scale = bone_mat_world.decompose()
|
loc, rot, scale = bone_mat_world.decompose()
|
||||||
|
|
||||||
if control_location is not None:
|
if control_location is not None:
|
||||||
loc = control_location
|
loc = control_location
|
||||||
Sybren A. Stüvel
commented
in which case would in which case would `rot` not be normalized? I'd expect `bone_mat_world.decompose()` to return a unit quaternion. If you want to normalize the socket value, do that above with `rot = control_rotation.normalized()`.
Denys Hsu
commented
I think that can be removed. I was confused because the quaternion affected the scale which indicates that it's not a unit quaternion. This happened on longer bone chains - rounding the scale seemed to fix the issue so I guess it has been some floating point error. I think that can be removed. I was confused because the quaternion affected the scale which indicates that it's not a unit quaternion. This happened on longer bone chains - rounding the scale seemed to fix the issue so I guess it has been some floating point error.
|
|||||||
if control_rotation is not None:
|
if control_rotation is not None:
|
||||||
@ -1013,24 +1003,27 @@ class SetBoneNode(AbstractPowerShipNode):
|
|||||||
if control_scale is not None:
|
if control_scale is not None:
|
||||||
scale = control_scale
|
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:
|
match self.space:
|
||||||
case "WORLD":
|
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":
|
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_scale.translation = v_nil
|
||||||
bone_rest_rot = bone_rest_rot_scale.to_quaternion()
|
|
||||||
mat_rot_scale = Matrix.LocRotScale(
|
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)
|
mat_loc = Matrix.Translation(loc)
|
||||||
bone_mat_world = mat_loc @ mat_rot_scale
|
bone_mat_world = mat_loc @ mat_rot_scale
|
||||||
|
|
||||||
bone.matrix = arm_matrix.inverted() @ bone_mat_world
|
bone.matrix = arm_matrix.inverted() @ bone_mat_world
|
||||||
|
loc, rot, scale = bone.matrix.decompose()
|
||||||
|
|
||||||
|
|
||||||
class TwoBoneIKNode(AbstractPowerShipNode):
|
class TwoBoneIKNode(AbstractPowerShipNode):
|
||||||
@ -1237,61 +1230,61 @@ class ClampNode(AbstractPowerShipNode):
|
|||||||
self.outputs["Result"].default_value = clamped
|
self.outputs["Result"].default_value = clamped
|
||||||
|
|
||||||
|
|
||||||
class AbstractTwoValueMathNode(AbstractPowerShipNode):
|
# class AbstractTwoValueMathNode(AbstractPowerShipNode):
|
||||||
def init(self, context: bpy.types.Context) -> None:
|
# def init(self, context: bpy.types.Context) -> None:
|
||||||
self.inputs.new("NodeSocketFloat", "A")
|
# self.inputs.new("NodeSocketFloat", "A")
|
||||||
self.inputs.new("NodeSocketFloat", "B")
|
# self.inputs.new("NodeSocketFloat", "B")
|
||||||
self.outputs.new("NodeSocketFloat", "Result")
|
# self.outputs.new("NodeSocketFloat", "Result")
|
||||||
|
#
|
||||||
def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
|
# def execute(self, depsgraph: bpy.types.Depsgraph) -> None:
|
||||||
a = self._get_input_value("A", float)
|
# a = self._get_input_value("A", float)
|
||||||
b = self._get_input_value("B", float)
|
# b = self._get_input_value("B", float)
|
||||||
self.outputs["Result"].default_value = self.calculate(a, b)
|
# self.outputs["Result"].default_value = self.calculate(a, b)
|
||||||
|
#
|
||||||
def calculate(self, a: float, b: float) -> float:
|
# def calculate(self, a: float, b: float) -> float:
|
||||||
return 0
|
# return 0
|
||||||
|
#
|
||||||
|
#
|
||||||
class AddNode(AbstractTwoValueMathNode):
|
# class AddNode(AbstractTwoValueMathNode):
|
||||||
"""Add two values"""
|
# """Add two values"""
|
||||||
|
#
|
||||||
bl_idname = "AddNode"
|
# bl_idname = "AddNode"
|
||||||
bl_label = "Add"
|
# bl_label = "Add"
|
||||||
|
#
|
||||||
def calculate(self, a: float, b: float) -> float:
|
# def calculate(self, a: float, b: float) -> float:
|
||||||
return a + b
|
# return a + b
|
||||||
|
#
|
||||||
|
#
|
||||||
class SubtractNode(AbstractTwoValueMathNode):
|
# class SubtractNode(AbstractTwoValueMathNode):
|
||||||
"""Subtract two values"""
|
# """Subtract two values"""
|
||||||
|
#
|
||||||
bl_idname = "SubtractNode"
|
# bl_idname = "SubtractNode"
|
||||||
bl_label = "Subtract"
|
# bl_label = "Subtract"
|
||||||
|
#
|
||||||
def calculate(self, a: float, b: float) -> float:
|
# def calculate(self, a: float, b: float) -> float:
|
||||||
return a - b
|
# return a - b
|
||||||
|
#
|
||||||
|
#
|
||||||
class MultiplyNode(AbstractTwoValueMathNode):
|
# class MultiplyNode(AbstractTwoValueMathNode):
|
||||||
"""Multiply two values"""
|
# """Multiply two values"""
|
||||||
|
#
|
||||||
bl_idname = "MultiplyNode"
|
# bl_idname = "MultiplyNode"
|
||||||
bl_label = "Multiply"
|
# bl_label = "Multiply"
|
||||||
|
#
|
||||||
def calculate(self, a: float, b: float) -> float:
|
# def calculate(self, a: float, b: float) -> float:
|
||||||
return a * b
|
# return a * b
|
||||||
|
#
|
||||||
|
#
|
||||||
class DivideNode(AbstractTwoValueMathNode):
|
# class DivideNode(AbstractTwoValueMathNode):
|
||||||
"""Divide two values; division by zero results in NaN"""
|
# """Divide two values; division by zero results in NaN"""
|
||||||
|
#
|
||||||
bl_idname = "DivideNode"
|
# bl_idname = "DivideNode"
|
||||||
bl_label = "Divide"
|
# bl_label = "Divide"
|
||||||
|
#
|
||||||
def calculate(self, a: float, b: float) -> float:
|
# def calculate(self, a: float, b: float) -> float:
|
||||||
if b == 0:
|
# if b == 0:
|
||||||
return float("nan")
|
# return float("nan")
|
||||||
return a / b
|
# return a / b
|
||||||
|
|
||||||
|
|
||||||
def _on_num_sockets_change(self: "SequenceNode", context: bpy.types.Context) -> None:
|
def _on_num_sockets_change(self: "SequenceNode", context: bpy.types.Context) -> None:
|
||||||
@ -1394,7 +1387,7 @@ node_categories = [
|
|||||||
items=[
|
items=[
|
||||||
nodeitems_utils.NodeItem("RotateTowards"),
|
nodeitems_utils.NodeItem("RotateTowards"),
|
||||||
nodeitems_utils.NodeItem("OffsetRotation"),
|
nodeitems_utils.NodeItem("OffsetRotation"),
|
||||||
nodeitems_utils.NodeItem("RotationFromAngle"),
|
nodeitems_utils.NodeItem("AngleFromVectors"),
|
||||||
nodeitems_utils.NodeItem("NormalFromPoints"),
|
nodeitems_utils.NodeItem("NormalFromPoints"),
|
||||||
nodeitems_utils.NodeItem("SplitVector"),
|
nodeitems_utils.NodeItem("SplitVector"),
|
||||||
nodeitems_utils.NodeItem("ToVector"),
|
nodeitems_utils.NodeItem("ToVector"),
|
||||||
@ -1439,7 +1432,7 @@ classes = (
|
|||||||
SequenceNode,
|
SequenceNode,
|
||||||
# Math Nodes
|
# Math Nodes
|
||||||
RotateTowards,
|
RotateTowards,
|
||||||
RotationFromAngle,
|
AngleFromVectors,
|
||||||
OffsetRotation,
|
OffsetRotation,
|
||||||
NormalFromPoints,
|
NormalFromPoints,
|
||||||
ToVector,
|
ToVector,
|
||||||
|
default_value
doesn't seem used.