Motion transfer setup #1
161
nodes.py
@ -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)
|
||||
cgtinker marked this conversation as resolved
Outdated
|
||||
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
|
||||
|
||||
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):
|
||||
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")
|
||||
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.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
|
||||
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:
|
||||
@ -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,
|
||||
|
default_value
doesn't seem used.