Improved quaternion and angle handling in logic nodes (+2bugfixes)
- Made so all nodes outputting a quaternion object also output it as a XYZ (vector) + W (float) combination - Modernized the interface of the "Action/Rotate Object" node, to align on the newer "Action/Set Rotation" node interface ("Action/Rotate Object Along Axis" is now depreciated, but still usable) - Fixed a blender-side-only bug with the "Logic/Switch" node (...which technically could have lead to a compile-time problem if exploited the right way) - Fixed a bug on the "Action/Set Rotation" node: now, quaternion input is automatically normalized in order to avoid accidental scaling - Added a "Value/Separate Quaternion" node - Made so the names of some sockets change in the "Set Rotation" and "Rotate Object" nodes, so they adapt to those nodes' input types. (Same thing with "Value/Vector From Transform"'s output type)
This commit is contained in:
parent
a4f7030ef8
commit
17daabeb29
|
@ -1,6 +1,7 @@
|
|||
package armory.logicnode;
|
||||
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class QuaternionNode extends LogicNode {
|
||||
|
||||
|
@ -22,7 +23,22 @@ class QuaternionNode extends LogicNode {
|
|||
value.y = inputs[1].get();
|
||||
value.z = inputs[2].get();
|
||||
value.w = inputs[3].get();
|
||||
return value;
|
||||
value.normalize();
|
||||
switch (from){
|
||||
case 0:
|
||||
return value;
|
||||
case 1:
|
||||
var value1 = new Vec4();
|
||||
value1.x = value.x;
|
||||
value1.y = value.y;
|
||||
value1.z = value.z;
|
||||
value1.w = 0; // use 0 to avoid this vector being translated.
|
||||
return value1;
|
||||
case 2:
|
||||
return value.w;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
override function set(value: Dynamic) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import armory.trait.physics.RigidBody;
|
|||
|
||||
class RotateObjectNode extends LogicNode {
|
||||
|
||||
public var property0 = "uninitialized";
|
||||
var q = new Quat();
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
|
@ -16,10 +17,34 @@ class RotateObjectNode extends LogicNode {
|
|||
override function run(from: Int) {
|
||||
var object: Object = inputs[1].get();
|
||||
var vec: Vec4 = inputs[2].get();
|
||||
|
||||
var w: Float = 0;
|
||||
|
||||
// the next if/else block exist to ensure backwards compatibility with nodes that were created before armory 2020.09.
|
||||
// delete it when the "old version" of this node will be considered removed from armory.
|
||||
if (property0=="uninitialized") {
|
||||
property0="Euler Angles";
|
||||
}
|
||||
else{
|
||||
w = inputs[3].get();
|
||||
}
|
||||
if (object == null || vec == null) return;
|
||||
|
||||
q.fromEuler(vec.x, vec.y, vec.z);
|
||||
switch (property0) {
|
||||
case "Euler Angles":
|
||||
q.fromEuler(vec.x, vec.y, vec.z);
|
||||
case "Angle Axies (Degrees)" | "Angle Axies (Radians)":
|
||||
var angle: Float = w;
|
||||
if (property0 == "Angle Axies (Degrees)") {
|
||||
angle = angle * (Math.PI / 180);
|
||||
}
|
||||
var angleSin = Math.sin(angle / 2);
|
||||
vec = vec.normalize();
|
||||
var angleCos = Math.cos(angle / 2);
|
||||
q = new Quat(vec.x * angleSin, vec.y * angleSin, vec.z * angleSin, angleCos);
|
||||
case "Quaternion":
|
||||
q = new Quat(vec.x, vec.y, vec.z, w);
|
||||
q.normalize();
|
||||
}
|
||||
|
||||
object.transform.rot.mult(q);
|
||||
object.transform.buildMatrix();
|
||||
|
|
23
Sources/armory/logicnode/SeparateQuaternionNode.hx
Normal file
23
Sources/armory/logicnode/SeparateQuaternionNode.hx
Normal file
|
@ -0,0 +1,23 @@
|
|||
package armory.logicnode;
|
||||
|
||||
import iron.math.Quat;
|
||||
import kha.FastFloat;
|
||||
|
||||
class SeparateQuaternionNode extends LogicNode {
|
||||
var q:Quat = null;
|
||||
|
||||
public function new(tree:LogicTree) { super(tree); }
|
||||
|
||||
override function get(from:Int):Dynamic{
|
||||
q = inputs[0].get();
|
||||
if (from==0)
|
||||
return q.x;
|
||||
else if (from==1)
|
||||
return q.y;
|
||||
else if (from==2)
|
||||
return q.z;
|
||||
else
|
||||
return q.w;
|
||||
|
||||
}
|
||||
}
|
|
@ -34,6 +34,7 @@ class SetRotationNode extends LogicNode {
|
|||
object.transform.rot = new Quat(vec.x * angleSin, vec.y * angleSin, vec.z * angleSin, angleCos);
|
||||
case "Quaternion":
|
||||
object.transform.rot = new Quat(vec.x, vec.y, vec.z, w);
|
||||
object.transform.rot.normalize();
|
||||
}
|
||||
object.transform.buildMatrix();
|
||||
#if arm_physics
|
||||
|
|
|
@ -2,6 +2,7 @@ package armory.logicnode;
|
|||
|
||||
import iron.math.Quat;
|
||||
import iron.math.Mat4;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class VectorFromTransformNode extends LogicNode {
|
||||
|
||||
|
@ -16,16 +17,37 @@ class VectorFromTransformNode extends LogicNode {
|
|||
|
||||
if (m == null) return null;
|
||||
|
||||
switch (property0) {
|
||||
case "Up":
|
||||
return m.up();
|
||||
case "Right":
|
||||
return m.right();
|
||||
case "Look":
|
||||
return m.look();
|
||||
case "Quaternion":
|
||||
var q = new Quat();
|
||||
return q.fromMat(m);
|
||||
switch(from) {
|
||||
case 0:
|
||||
switch (property0) {
|
||||
case "Up":
|
||||
return m.up();
|
||||
case "Right":
|
||||
return m.right();
|
||||
case "Look":
|
||||
return m.look();
|
||||
case "Quaternion":
|
||||
var q = new Quat();
|
||||
q.fromMat(m);
|
||||
return q.normalize();
|
||||
}
|
||||
case 1:
|
||||
if (property0 == "Quaternion") {
|
||||
var q = new Quat();
|
||||
q.fromMat(m);
|
||||
q.normalize();
|
||||
var v = new Vec4();
|
||||
v.x = q.x; v.y = q.y; v.z = q.z;
|
||||
v.w = 0; //prevent vector translation
|
||||
return v;
|
||||
}
|
||||
case 2:
|
||||
if (property0 == "Quaternion") {
|
||||
var q = new Quat();
|
||||
q.fromMat(m);
|
||||
q.normalize();
|
||||
return q.w;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -12,7 +12,40 @@ class RotateObjectNode(Node, ArmLogicTreeNode):
|
|||
def init(self, context):
|
||||
self.inputs.new('ArmNodeSocketAction', 'In')
|
||||
self.inputs.new('ArmNodeSocketObject', 'Object')
|
||||
self.inputs.new('NodeSocketVector', 'Vector')
|
||||
self.inputs.new('NodeSocketVector', 'Euler Angles')
|
||||
self.inputs.new('NodeSocketFloat', 'Angle / W')
|
||||
self.outputs.new('ArmNodeSocketAction', 'Out')
|
||||
|
||||
def on_property_update(self, context):
|
||||
""" called by the EnumProperty, used to update the node socket labels"""
|
||||
if self.property0 == "Quaternion":
|
||||
self.inputs[2].name = "Quaternion XYZ"
|
||||
self.inputs[3].name = "Quaternion W"
|
||||
elif self.property0 == "Euler Angles":
|
||||
self.inputs[2].name = "Euler Angles"
|
||||
self.inputs[3].name = "[unused for Euler input]"
|
||||
elif self.property0.startswith("Angle Axies"):
|
||||
self.inputs[2].name = "Axis"
|
||||
self.inputs[3].name = "Angle"
|
||||
else:
|
||||
raise ValueError('No nodesocket labels for current input mode: check self-consistancy of action_set_rotation.py')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
# this block is here to ensure backwards compatibility and warn the user.
|
||||
# delete it (only keep the "else" part) when the 'old version' of the node will be considered removed.
|
||||
# (note: please also update the corresponding haxe file when doing so)
|
||||
if len(self.inputs) < 4:
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Node has been updated with armory 2020.09. Please consider deleting and recreating it.")
|
||||
else:
|
||||
layout.prop(self, 'property0')
|
||||
|
||||
property0: EnumProperty(
|
||||
items = [('Euler Angles', 'Euler Angles', 'Euler Angles'),
|
||||
('Angle Axies (Radians)', 'Angle Axies (Radians)', 'Angle Axies (Radians)'),
|
||||
('Angle Axies (Degrees)', 'Angle Axies (Degrees)', 'Angle Axies (Degrees)'),
|
||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
||||
name='', default='Euler Angles',
|
||||
update = on_property_update)
|
||||
|
||||
add_node(RotateObjectNode, category='Action')
|
||||
|
|
|
@ -7,7 +7,8 @@ class RotateObjectAroundAxisNode(Node, ArmLogicTreeNode):
|
|||
'''Rotate object around axis node'''
|
||||
bl_idname = 'LNRotateObjectAroundAxisNode'
|
||||
bl_label = 'Rotate Object Around Axis'
|
||||
bl_icon = 'NONE'
|
||||
bl_description = 'Rotate Object Around Axis (Depreciated: use "Rotate Object")'
|
||||
bl_icon = 'ERROR'
|
||||
|
||||
def init(self, context):
|
||||
self.inputs.new('ArmNodeSocketAction', 'In')
|
||||
|
@ -16,5 +17,9 @@ class RotateObjectAroundAxisNode(Node, ArmLogicTreeNode):
|
|||
self.inputs[-1].default_value = [0, 0, 1]
|
||||
self.inputs.new('NodeSocketFloat', 'Angle')
|
||||
self.outputs.new('ArmNodeSocketAction', 'Out')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
row = layout.row(align=True)
|
||||
row.label(text='Depreciated. Consider using "Rotate Object"')
|
||||
|
||||
add_node(RotateObjectAroundAxisNode, category='Action')
|
||||
|
|
|
@ -9,21 +9,36 @@ class SetRotationNode(Node, ArmLogicTreeNode):
|
|||
bl_label = 'Set Rotation'
|
||||
bl_icon = 'NONE'
|
||||
|
||||
property0: EnumProperty(
|
||||
items = [('Euler Angles', 'Euler Angles', 'Euler Angles'),
|
||||
('Angle Axies (Radians)', 'Angle Axies (Radians)', 'Angle Axies (Radians)'),
|
||||
('Angle Axies (Degrees)', 'Angle Axies (Degrees)', 'Angle Axies (Degrees)'),
|
||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
||||
name='', default='Euler Angles')
|
||||
|
||||
def init(self, context):
|
||||
self.inputs.new('ArmNodeSocketAction', 'In')
|
||||
self.inputs.new('ArmNodeSocketObject', 'Object')
|
||||
self.inputs.new('NodeSocketVector', 'Euler Angles / Vector XYZ')
|
||||
self.inputs.new('NodeSocketFloat', 'Angle / W')
|
||||
self.outputs.new('ArmNodeSocketAction', 'Out')
|
||||
|
||||
def on_property_update(self, context):
|
||||
"""called by the EnumProperty, used to update the node socket labels"""
|
||||
if self.property0 == "Quaternion":
|
||||
self.inputs[2].name = "Quaternion XYZ"
|
||||
self.inputs[3].name = "Quaternion W"
|
||||
elif self.property0 == "Euler Angles":
|
||||
self.inputs[2].name = "Euler Angles"
|
||||
self.inputs[3].name = "[unused for Euler input]"
|
||||
elif self.property0.startswith("Angle Axies"):
|
||||
self.inputs[2].name = "Axis"
|
||||
self.inputs[3].name = "Angle"
|
||||
else:
|
||||
raise ValueError('No nodesocket labels for current input mode: check self-consistancy of action_set_rotation.py')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0')
|
||||
|
||||
property0: EnumProperty(
|
||||
items = [('Euler Angles', 'Euler Angles', 'Euler Angles'),
|
||||
('Angle Axies (Radians)', 'Angle Axies (Radians)', 'Angle Axies (Radians)'),
|
||||
('Angle Axies (Degrees)', 'Angle Axies (Degrees)', 'Angle Axies (Degrees)'),
|
||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
||||
name='', default='Euler Angles',
|
||||
update=on_property_update)
|
||||
|
||||
add_node(SetRotationNode, category='Action')
|
||||
|
|
|
@ -8,7 +8,7 @@ class SwitchNode(Node, ArmLogicTreeNode):
|
|||
bl_idname = 'LNSwitchNode'
|
||||
bl_label = 'Switch'
|
||||
bl_icon = 'NONE'
|
||||
min_inputs = 1
|
||||
min_inputs = 2
|
||||
min_outputs = 1
|
||||
|
||||
def __init__(self):
|
||||
|
|
21
blender/arm/logicnode/value_separate_quaternion.py
Normal file
21
blender/arm/logicnode/value_separate_quaternion.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
import bpy
|
||||
from bpy.props import *
|
||||
from bpy.types import Node, NodeSocket
|
||||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
|
||||
class SeparateQuaternionNode(Node, ArmLogicTreeNode):
|
||||
|
||||
bl_idname = 'LNSeparateQuaternionNode'
|
||||
bl_label = "Separate Quaternion"
|
||||
bl_icon = 'NONE'
|
||||
|
||||
def init(self, context):
|
||||
self.inputs.new('NodeSocketVector', 'Quaternion')
|
||||
self.outputs.new('NodeSocketFloat', 'X')
|
||||
self.outputs.new('NodeSocketFloat', 'Y')
|
||||
self.outputs.new('NodeSocketFloat', 'Z')
|
||||
self.outputs.new('NodeSocketFloat', 'W')
|
||||
|
||||
|
||||
add_node(SeparateQuaternionNode, 'Value')
|
|
@ -8,18 +8,36 @@ class VectorFromTransformNode(Node, ArmLogicTreeNode):
|
|||
bl_idname = 'LNVectorFromTransformNode'
|
||||
bl_label = 'Vector From Transform'
|
||||
bl_icon = 'NONE'
|
||||
|
||||
def init(self, context):
|
||||
self.inputs.new('NodeSocketShader', 'Transform')
|
||||
self.outputs.new('NodeSocketVector', 'Vector')
|
||||
self.outputs.new('NodeSocketVector', 'Quaternion XYZ')
|
||||
self.outputs.new('NodeSocketFloat', 'Quaternion W')
|
||||
|
||||
def on_property_update(self, context):
|
||||
"""called by the EnumProperty, used to update the node socket labels"""
|
||||
# note: the conditions on len(self.outputs) are take in account "old version" (pre-2020.9) nodes, which only have one output
|
||||
if self.property0 == "Quaternion":
|
||||
self.outputs[0].name = "Quaternion"
|
||||
if len(self.outputs) > 1:
|
||||
self.outputs[1].name = "Quaternion XYZ"
|
||||
self.outputs[2].name = "Quaternion W"
|
||||
else:
|
||||
self.outputs[0].name = "Vector"
|
||||
if len(self.outputs) > 1:
|
||||
self.outputs[1].name = "[quaternion only]"
|
||||
self.outputs[2].name = "[quaternion only]"
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0')
|
||||
|
||||
property0: EnumProperty(
|
||||
items = [('Up', 'Up', 'Up'),
|
||||
('Right', 'Right', 'Right'),
|
||||
('Look', 'Look', 'Look'),
|
||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
||||
name='', default='Look')
|
||||
|
||||
def init(self, context):
|
||||
self.inputs.new('NodeSocketShader', 'Transform')
|
||||
self.outputs.new('NodeSocketVector', 'Vector')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0')
|
||||
name='', default='Look',
|
||||
update=on_property_update)
|
||||
|
||||
add_node(VectorFromTransformNode, category='Value')
|
||||
|
|
|
@ -17,5 +17,7 @@ class QuaternionNode(Node, ArmLogicTreeNode):
|
|||
self.inputs[-1].default_value = 1.0
|
||||
|
||||
self.outputs.new('NodeSocketVector', 'Quaternion')
|
||||
self.outputs.new('NodeSocketVector', 'XYZ')
|
||||
self.outputs.new('NodeSocketFloat', 'W')
|
||||
|
||||
add_node(QuaternionNode, category='Variable')
|
||||
|
|
Loading…
Reference in a new issue