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:
niacdoial 2020-08-30 11:45:07 +02:00
parent a4f7030ef8
commit 17daabeb29
12 changed files with 212 additions and 31 deletions

View file

@ -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) {

View file

@ -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();

View 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;
}
}

View file

@ -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

View file

@ -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;

View file

@ -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')

View file

@ -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')

View file

@ -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')

View file

@ -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):

View 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')

View file

@ -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')

View file

@ -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')