Quat Math

1. Move the Quaternion node from Transform to Variable
2. Move the Separate Quaternion node from Transform to Math
3. Added Quaternion Math Node
Linked to PR - https://github.com/armory3d/iron/pull/108
This commit is contained in:
E1e5en 2020-11-08 22:40:01 +03:00
parent d210cea6d8
commit 6b804477b1
4 changed files with 372 additions and 0 deletions

View file

@ -0,0 +1,190 @@
package armory.logicnode;
import iron.math.Quat;
import iron.math.Vec4;
import iron.math.Mat4;
import kha.FastFloat;
class QuaternionMathNode extends LogicNode {
public var property0: String; // Operation
public var property1: Bool; // Separator Out
var res_q = new Quat();
var res_v = new Vec4();
var res_f: FastFloat = 0.0;
public function new(tree: LogicTree) {
super(tree);
}
override function get(from: Int): Dynamic {
switch (property0) {
// 1 argument: Module, Normalize, GetEuler
case "Module": {
var q: Quat = inputs[0].get();
if (q == null) return null;
res_q.setFrom(q);
res_f = res_q.module();
}
case "Normalize": {
var q: Quat = inputs[0].get();
if (q == null) return null;
res_q.setFrom(q);
res_q = res_q.normalize();
}
case "GetEuler": {
var q: Quat = inputs[0].get();
if (q == null) return null;
res_q.setFrom(q);
res_v = res_q.getEuler();
}
// 2 arguments: FromTo, FromMat, FromRotationMat, ToAxisAngle
case "FromTo": {
var v1: Vec4 = inputs[0].get();
var v2: Vec4 = inputs[1].get();
if ((v1 == null) || (v2 == null)) return null;
res_q.fromTo(v1, v2);
}
case "FromMat": {
var q: Quat = inputs[0].get();
var m: Mat4 = inputs[1].get();
if ((q == null) || (m == null)) return null;
res_q.setFrom(q);
res_q = res_q.fromMat(m);
}
case "FromRotationMat": {
var q: Quat = inputs[0].get();
var m: Mat4 = inputs[1].get();
if ((q == null) || (m == null)) return null;
res_q.setFrom(q);
res_q = res_q.fromRotationMat(m);
}
case "ToAxisAngle": {
var q: Quat = inputs[0].get();
var v: Vec4 = inputs[1].get();
if ((q == null) || (v == null)) return null;
res_q.setFrom(q);
res_f = res_q.toAxisAngle(v);
}
// # 3 arguments: Lerp, Slerp, FromAxisAngle, FromEuler
case "Lerp": {
var from: Quat = inputs[0].get();
var to: Quat = inputs[1].get();
var f: Float = inputs[2].get();
if ((from == null) || (to == null)) return null;
res_q = res_q.lerp(from, to, f);
}
case "Slerp": {
var from: Quat = inputs[0].get();
var to: Quat = inputs[1].get();
var f: Float = inputs[2].get();
if ((from == null) || (to == null)) return null;
res_q = res_q.slerp(from, to, f);
}
case "FromAxisAngle": {
var q: Quat = inputs[0].get();
var axis: Vec4 = inputs[1].get();
var angle: Float = inputs[2].get();
if ((q == null) || (axis == null)) return null;
res_q.setFrom(q);
res_q = res_q.fromAxisAngle(axis, angle);
}
case "FromEuler": {
var x: Float = inputs[0].get();
var y: Float = inputs[1].get();
var z: Float = inputs[2].get();
res_q = res_q.fromEuler(x, y, z);
}
// Many arguments: Add, Subtract, DotProduct, Multiply
case "Add": {
var q: Quat = inputs[0].get();
if (q == null) return null;
res_q.setFrom(q);
var q2 = new Quat();
var i = 1;
while (i < inputs.length) {
q2 = inputs[i].get();
if (q2 == null) return null;
res_q.add(q2);
i++;
}
}
case "Subtract": {
var q: Quat = inputs[0].get();
if (q == null) return null;
res_q.setFrom(q);
var q2 = new Quat();
var i = 1;
while (i < inputs.length) {
q2 = inputs[i].get();
if (q2 == null) return null;
res_q.sub(q2);
i++;
}
}
case "Multiply": {
var q: Quat = inputs[0].get();
if (q == null) return null;
res_q.setFrom(q);
var q2 = new Quat();
var i = 1;
while (i < inputs.length) {
q2 = inputs[i].get();
if (q2 == null) return null;
res_q.mult(q2);
i++;
}
}
case "MultiplyFloats": {
var q: Quat = inputs[0].get();
if (q == null) return null;
res_q.setFrom(q);
var f: Float = 1.0;
var i = 1;
while (i < inputs.length) {
f = inputs[i].get();
res_q.scale(f);
i++;
}
}
case "DotProduct": {
var q: Quat = inputs[0].get();
if (q == null) return null;
res_q.setFrom(q);
var q2 = new Quat();
var i = 1;
while (i < inputs.length) {
q2 = inputs[i].get();
if (q2 == null) return null;
res_f = res_q.dot(q2);
res_q.set(res_f, res_f, res_f, res_f);
i++;
}
}
}
// Return and check separator
switch (from) {
case 0: {
if (property0 == 'GetEuler')
return res_v;
else
return res_q;
}
case 1:
if (property1) {
return res_q.x;
} else {
return res_f;
}
case 2:
if (property1) return res_q.y;
case 3:
if (property1) return res_q.z;
case 4:
if (property1) return res_q.w;
case 5:
if (property1) return res_f;
}
return null;
}
}

View file

@ -0,0 +1,182 @@
from arm.logicnode.arm_nodes import *
class QuaternionMathNode(ArmLogicTreeNode):
"""Mathematical operations on quaternions."""
bl_idname = 'LNQuaternionMathNode'
bl_label = 'Quaternion Math'
arm_section = 'quaternions'
arm_version = 1
def get_bool(self):
return self.get('property1', False)
def set_bool(self, value):
self['property1'] = value
if value:
if (self.property0 == 'Module') or (self.property0 == 'DotProduct') or (self.property0 == 'ToAxisAngle'):
self.outputs.remove(self.outputs.values()[-1]) # Module/DotProduct/ToAxisAngle
self.add_output('NodeSocketFloat', 'X') # Result X
self.add_output('NodeSocketFloat', 'Y') # Result Y
self.add_output('NodeSocketFloat', 'Z') # Result Z
self.add_output('NodeSocketFloat', 'W') # Result W
if (self.property0 == 'Module'):
self.add_output('NodeSocketFloat', 'Module') # Module
if (self.property0 == 'DotProduct'):
self.add_output('NodeSocketFloat', 'Scalar') # DotProduct
if (self.property0 == 'ToAxisAngle'):
self.add_output('NodeSocketFloat', 'To Axis Angle') # ToAxisAngle
else:
if (self.property0 == 'Module') or (self.property0 == 'DotProduct') or (self.property0 == 'ToAxisAngle'):
self.outputs.remove(self.outputs.values()[-1]) # Module/DotProduct/ToAxisAngle
self.outputs.remove(self.outputs.values()[-1]) # Result X
self.outputs.remove(self.outputs.values()[-1]) # Result Y
self.outputs.remove(self.outputs.values()[-1]) # Result Z
self.outputs.remove(self.outputs.values()[-1]) # Result W
if (self.property0 == 'Module'):
self.add_output('NodeSocketFloat', 'Module') # Module
if (self.property0 == 'DotProduct'):
self.add_output('NodeSocketFloat', 'Scalar') # DotProduct
if (self.property0 == 'ToAxisAngle'):
self.add_output('NodeSocketFloat', 'To Axis Angle') # ToAxisAngle
property1: BoolProperty(name='Separator Out', default=False, set=set_bool, get=get_bool)
@staticmethod
def get_enum_id_value(obj, prop_name, value):
return obj.bl_rna.properties[prop_name].enum_items[value].identifier
@staticmethod
def get_count_in(operation_name):
return {
'Add': 0,
'Subtract': 0,
'DotProduct': 0,
'Multiply': 0,
'MultiplyFloats': 0,
'Module': 1,
'Normalize': 1,
'GetEuler': 1,
'FromTo': 2,
'FromMat': 2,
'FromRotationMat': 2,
'ToAxisAngle': 2,
'Lerp': 3,
'Slerp': 3,
'FromAxisAngle': 3,
'FromEuler': 3
}.get(operation_name, 0)
def get_enum(self):
return self.get('property0', 0)
def set_enum(self, value):
# Checking the selection of another operation
select_current = self.get_enum_id_value(self, 'property0', value)
select_prev = self.property0
if select_prev != select_current:
# Remove
count = 0
if ((select_prev == 'Add') or (select_prev == 'Subtract') or (select_prev == 'Multiply') or (select_prev == 'DotProduct')) and ((select_current == 'Add') or (select_current == 'Subtract') or (select_current == 'Multiply') or (select_current == 'DotProduct')) or (((select_current == 'Lerp') or (select_current == 'Slerp')) and ((select_prev == 'Lerp') or (select_prev == 'Slerp'))):
count = len(self.inputs)
while (len(self.inputs) > count):
self.inputs.remove(self.inputs.values()[-1])
if (select_prev == 'DotProduct') or (select_prev == 'ToAxisAngle') or (select_prev == 'Module'):
self.outputs.remove(self.outputs.values()[-1])
# Many arguments: Add, Subtract, DotProduct, Multiply, MultiplyFloat
if (self.get_count_in(select_current) == 0):
if (select_current == "MultiplyFloats"):
self.add_input('NodeSocketVector', 'Quaternion ' + str(len(self.inputs)))
self.add_input('NodeSocketFloat', 'Value ' + str(len(self.inputs)))
else:
while (len(self.inputs) < 2):
self.add_input('NodeSocketVector', 'Quaternion ' + str(len(self.inputs)))
if (select_current == 'DotProduct'):
self.add_output('NodeSocketFloat', 'Scalar')
# 3 arguments: Lerp, Slerp, FromAxisAngle, FromEuler
if (self.get_count_in(select_current) == 3):
if (select_current == 'Lerp') or (select_current == 'Slerp'):
while (len(self.inputs) < 3):
self.add_input('NodeSocketVector', 'From')
self.add_input('NodeSocketVector', 'To')
self.add_input('NodeSocketFloat', 'T')
if (select_current == 'FromAxisAngle'):
self.add_input('NodeSocketVector', 'Quaternion')
self.add_input('NodeSocketVector', 'Axis')
self.add_input('NodeSocketFloat', 'Angle')
if (select_current == 'FromEuler'):
self.add_input('NodeSocketFloat', 'X')
self.add_input('NodeSocketFloat', 'Y')
self.add_input('NodeSocketFloat', 'Z')
# 2 arguments: FromTo, FromMat, FromRotationMat, ToAxisAngle
if (self.get_count_in(select_current) == 2):
if (select_current == 'FromTo'):
self.add_input('NodeSocketVector', 'Vector ' + str(len(self.inputs)))
self.add_input('NodeSocketVector', 'Vector ' + str(len(self.inputs)))
if (select_current == 'FromMat') or (select_current == 'FromRotationMat'):
self.add_input('NodeSocketVector', 'Quaternion')
self.add_input('NodeSocketShader', 'Matrix')
if (select_current == 'ToAxisAngle'):
self.add_input('NodeSocketVector', 'Quaternion')
self.add_input('NodeSocketVector', 'Axis')
self.add_output('NodeSocketFloat', 'Angle')
# 1 argument: Module, Normalize, GetEuler
if (self.get_count_in(select_current) == 1):
self.add_input('NodeSocketVector', 'Quaternion')
if (select_current == 'Module'):
self.add_output('NodeSocketFloat', 'Module')
self['property0'] = value
property0: EnumProperty(
items = [('Add', 'Add', 'Add'),
('Subtract', 'Subtract', 'Subtract'),
('DotProduct', 'Dot Product', 'Dot Product'),
('Multiply', 'Multiply', 'Multiply'),
('MultiplyFloats', 'Multiply (Floats)', 'Multiply (Floats)'),
('Module', 'Module', 'Module'),
('Normalize', 'Normalize', 'Normalize'),
('Lerp', 'Lerp', 'Linearly interpolate'),
('Slerp', 'Slerp', 'Spherical linear interpolation'),
('FromTo', 'From To', 'From To'),
('FromMat', 'From Mat', 'From Mat'),
('FromRotationMat', 'From Rotation Mat', 'From Rotation Mat'),
('ToAxisAngle', 'To Axis Angle', 'To Axis Angle'),
('FromAxisAngle', 'From Axis Angle', 'From Axis Angle'),
('FromEuler', 'From Euler', 'From Euler'),
('GetEuler', 'To Euler', 'To Euler')],
name='', default='Add', set=set_enum, get=get_enum)
def __init__(self):
array_nodes[str(id(self))] = self
def init(self, context):
super(QuaternionMathNode, self).init(context)
self.add_input('NodeSocketVector', 'Quaternion 0', default_value=[0.0, 0.0, 0.0])
self.add_input('NodeSocketVector', 'Quaternion 1', default_value=[0.0, 0.0, 0.0])
self.add_output('NodeSocketVector', 'Result')
def draw_buttons(self, context, layout):
layout.prop(self, 'property1') # Separator Out
layout.prop(self, 'property0') # Operation
# Buttons
if (self.get_count_in(self.property0) == 0):
row = layout.row(align=True)
column = row.column(align=True)
op = column.operator('arm.node_add_input', text='Add Value', icon='PLUS', emboss=True)
op.node_index = str(id(self))
if (self.property0 == 'Add') or (self.property0 == 'Subtract') or (self.property0 == 'Multiply') or (self.property0 == 'DotProduct'):
op.name_format = 'Quaternion {0}'
else:
op.name_format = 'Value {0}'
if (self.property0 == "MultiplyFloats"):
op.socket_type = 'NodeSocketFloat'
else:
op.socket_type = 'NodeSocketVector'
column = row.column(align=True)
op = column.operator('arm.node_remove_input', text='', icon='X', emboss=True)
op.node_index = str(id(self))
if len(self.inputs) == 2:
column.enabled = False