Make rotation its own socket in logic nodes, add rotation-specific math node
(part 1: conversion code not developed)
This commit is contained in:
parent
4ea5156f1e
commit
9b1393ea41
|
@ -1,10 +1,13 @@
|
|||
package armory.logicnode;
|
||||
|
||||
import iron.object.Object;
|
||||
import iron.math.Vec3;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
|
||||
class GetRotationNode extends LogicNode {
|
||||
|
||||
public var property0: String;
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
@ -16,34 +19,16 @@ class GetRotationNode extends LogicNode {
|
|||
return null;
|
||||
}
|
||||
|
||||
var rot = object.transform.rot;
|
||||
|
||||
switch (from) {
|
||||
case 0:
|
||||
// euler angles
|
||||
return object.transform.rot.getEuler();
|
||||
case 1:
|
||||
// vector
|
||||
var sqrtW = Math.sqrt(1 - (rot.w * rot.w));
|
||||
if (sqrtW == 0) {
|
||||
return new Vec3(0, 0, 1);
|
||||
}
|
||||
return new Vec3(rot.x / sqrtW, rot.y / sqrtW, rot.z / sqrtW);
|
||||
case 2:
|
||||
// angle radians
|
||||
var angle = 2 * Math.acos(rot.w);
|
||||
return angle;
|
||||
case 3:
|
||||
// angle degrees
|
||||
var angle = 2 * Math.acos(rot.w);
|
||||
return angle * (180 / Math.PI);
|
||||
case 4:
|
||||
//quaternion xyz
|
||||
return new Vec3(rot.x, rot.y, rot.z);
|
||||
case 5:
|
||||
//quaternion w
|
||||
return rot.w;
|
||||
}
|
||||
switch(property0){
|
||||
case "Local":
|
||||
return object.transform.rot;
|
||||
case "Global":{
|
||||
var useless1 = new Vec4();
|
||||
var ret = new Quat();
|
||||
object.transform.world.decompose(useless1, ret, useless1);
|
||||
return ret;
|
||||
}}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,6 @@ class LookAtNode extends LogicNode {
|
|||
v2.setFrom(vto).sub(vfrom).normalize();
|
||||
|
||||
q.fromTo(v1, v2);
|
||||
return q.getEuler();
|
||||
return q;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,11 @@ package armory.logicnode;
|
|||
|
||||
import iron.object.Object;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
import armory.trait.physics.RigidBody;
|
||||
|
||||
class RotateObjectNode extends LogicNode {
|
||||
|
||||
public var property0 = "Euler Angles";
|
||||
var q = new Quat();
|
||||
public var property0 = "Local";
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
|
@ -16,32 +14,19 @@ class RotateObjectNode extends LogicNode {
|
|||
|
||||
override function run(from: Int) {
|
||||
var object: Object = inputs[1].get();
|
||||
var vec: Vec4 = inputs[2].get();
|
||||
var q: Quat = inputs[2].get();
|
||||
|
||||
// note: here, the next line is disabled because old versions of the node don't have a third input.
|
||||
// when those old versions will be considered remove, feel free to uncomment that, and replace the other `inputs[3].get()` by `w` in this file.
|
||||
//var w: Float = inputs[3].get();
|
||||
if (object == null || q == null) return;
|
||||
|
||||
if (object == null || vec == null) return;
|
||||
|
||||
switch (property0) {
|
||||
case "Euler Angles":
|
||||
q.fromEuler(vec.x, vec.y, vec.z);
|
||||
case "Angle Axies (Degrees)" | "Angle Axies (Radians)":
|
||||
var angle: Float = inputs[3].get();
|
||||
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, inputs[3].get());
|
||||
q.normalize();
|
||||
q.normalize();
|
||||
switch (property0){
|
||||
case "Local":
|
||||
object.transform.rot.mult(q);
|
||||
case "Global":
|
||||
object.transform.rot.multquats(q, object.transform.rot);
|
||||
// that function call (Quat.multquats) is weird: it both modifies the object, and returns `this`
|
||||
}
|
||||
|
||||
object.transform.rot.mult(q);
|
||||
object.transform.buildMatrix();
|
||||
|
||||
#if arm_physics
|
||||
|
|
90
Sources/armory/logicnode/RotationMathNode.hx
Normal file
90
Sources/armory/logicnode/RotationMathNode.hx
Normal file
|
@ -0,0 +1,90 @@
|
|||
package armory.logicnode;
|
||||
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Mat4;
|
||||
import kha.FastFloat;
|
||||
|
||||
class RotationMathNode extends LogicNode {
|
||||
|
||||
public var property0: String; // Operation
|
||||
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 {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q==null) return null;
|
||||
|
||||
var res_q: Quat = new Quat();
|
||||
switch (property0) {
|
||||
// 1 argument: Normalize, Inverse
|
||||
case "Normalize": {
|
||||
res_q.setFrom(q);
|
||||
res_q = res_q.normalize();
|
||||
}
|
||||
case "Inverse": {
|
||||
var modl = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w;
|
||||
modl = -1/modl;
|
||||
res_q.w = -q.w*modl;
|
||||
res_q.x = q.x*modl;
|
||||
res_q.y = q.y*modl;
|
||||
res_q.z = q.z*modl;
|
||||
}
|
||||
// 2 arguments: Compose, Amplify, FromTo, FromRotationMat,
|
||||
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 "Compose": {
|
||||
var v1: Quat = inputs[0].get();
|
||||
var v2: Quat = inputs[1].get();
|
||||
if ((v1 == null) || (v2 == null)) return null;
|
||||
res_q.multquats(v1,v2);
|
||||
}
|
||||
case "Amplify": {
|
||||
var v1: Quat = inputs[0].get();
|
||||
var v2: Float = inputs[1].get();
|
||||
if ((v1 == null) || (v2 == null)) return null;
|
||||
res_q.setFrom(v1);
|
||||
var fac2 = Math.sqrt(1- res_q.w*res_q.w);
|
||||
if (fac2 > 0.001) {
|
||||
var fac1 = v2*Math.acos(res_q.w);
|
||||
res_q.w = Math.cos(fac1);
|
||||
fac1 = Math.sin(fac1)/fac2;
|
||||
res_q.x *= fac1;
|
||||
res_q.y *= fac1;
|
||||
res_q.z *= fac1;
|
||||
}
|
||||
}
|
||||
//case "FromRotationMat": {
|
||||
// var m: Mat4 = inputs[1].get();
|
||||
// if (m == null) return null;
|
||||
|
||||
// res_q = res_q.fromMat(m);
|
||||
//}
|
||||
// # 3 arguments: Lerp, Slerp, FromAxisAngle, FromEuler
|
||||
case "Lerp": {
|
||||
//var from = q;
|
||||
var to: Quat = inputs[1].get();
|
||||
var f: Float = inputs[2].get();
|
||||
if ((f == null) || (to == null)) return null;
|
||||
res_q = res_q.lerp(q, to, f);
|
||||
}
|
||||
case "Slerp": {
|
||||
//var from = q;
|
||||
var to: Quat = inputs[1].get();
|
||||
var f: Float = inputs[2].get();
|
||||
if ((f == null) || (to == null)) return null;
|
||||
res_q = res_q.slerp(q, to, f);
|
||||
}
|
||||
}
|
||||
return res_q;
|
||||
}
|
||||
}
|
118
Sources/armory/logicnode/RotationNode.hx
Normal file
118
Sources/armory/logicnode/RotationNode.hx
Normal file
|
@ -0,0 +1,118 @@
|
|||
package armory.logicnode;
|
||||
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Quat;
|
||||
import kha.FastFloat;
|
||||
import iron.math.Rotation;
|
||||
|
||||
class RotationNode extends LogicNode {
|
||||
|
||||
static inline var toRAD: FastFloat = 0.017453292519943295; // 180/pi
|
||||
|
||||
public var property0: String; // type of input (EulerAngles, AxisAngle, Quaternion)
|
||||
public var property1: String; // angle unit (Deg, Rad)
|
||||
public var property2: String; // euler order (XYZ, XZY, etc…)
|
||||
|
||||
public var value: Quat;
|
||||
//var input0_cache: Vec4 = new Vec4();
|
||||
//var input1_cache: Float = 0;
|
||||
var input_length: Int = 0;
|
||||
|
||||
public function new(tree: LogicTree, x: Null<Float> = null,
|
||||
y: Null<Float> = null,
|
||||
z: Null<Float> = null,
|
||||
w: Null<Float> = null
|
||||
) {
|
||||
super(tree);
|
||||
this.value = new Quat();
|
||||
if (x!=null) this.value.set(x,y,z,w);
|
||||
for (input in inputs) {
|
||||
if (input !=null)
|
||||
this.input_length +=1;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
//var inp0 = inputs[0].get();
|
||||
//var inp
|
||||
//if (inputs[0].get())
|
||||
|
||||
switch (property0){
|
||||
case "Quaternion": {
|
||||
if (inputs[0]!=null && inputs[1]!=null) {
|
||||
var vect: Vec4 = inputs[0].get();
|
||||
value.x = vect.x;
|
||||
value.y = vect.y;
|
||||
value.z = vect.z;
|
||||
value.w = inputs[1].get();
|
||||
}
|
||||
}
|
||||
case "AxisAngle": {
|
||||
if (inputs[0]!=null && inputs[1]!=null){
|
||||
var vec: Vec4 = inputs[0].get();
|
||||
var angle: FastFloat = inputs[1].get();
|
||||
if (property1=="Deg")
|
||||
angle *= toRAD;
|
||||
value.fromAxisAngle(vec, angle);
|
||||
}
|
||||
}
|
||||
case "EulerAngles": {
|
||||
if (inputs[0] != null){
|
||||
var vec: Vec4 = new Vec4().setFrom(inputs[0].get());
|
||||
if (property1=="Deg"){
|
||||
vec.x *= toRAD;
|
||||
vec.y *= toRAD;
|
||||
vec.z *= toRAD;
|
||||
}
|
||||
this.value = Rotation.eulerToQuat(vec, property2);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return property0;
|
||||
}
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
|
||||
override function set(value: Dynamic) {
|
||||
switch (property0){
|
||||
case "Quaternion": {
|
||||
if (input_length>1) {
|
||||
var vect = new Vec4();
|
||||
vect.x = value.x;
|
||||
vect.y = value.y;
|
||||
vect.z = value.z;
|
||||
inputs[0].set(vect);
|
||||
inputs[1].set(value.w);
|
||||
}
|
||||
}
|
||||
case "AxisAngle": {
|
||||
if (input_length>1){
|
||||
var vec = new Vec4();
|
||||
var angle = this.value.toAxisAngle(vec);
|
||||
if (property1=="Deg")
|
||||
angle /= toRAD;
|
||||
inputs[0].set(vec);
|
||||
inputs[1].set(angle);
|
||||
|
||||
}
|
||||
}
|
||||
case "EulerAngles": {
|
||||
if (input_length>0){
|
||||
var vec = Rotation.quatToEuler(value, property2);
|
||||
if (property1=="Deg"){
|
||||
vec.x /= toRAD;
|
||||
vec.y /= toRAD;
|
||||
vec.z /= toRAD;
|
||||
}
|
||||
inputs[0].set(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (input_length > 0){
|
||||
// NYI
|
||||
}else this.value=value;
|
||||
}
|
||||
}
|
61
Sources/armory/logicnode/SeparateRotationNode.hx
Normal file
61
Sources/armory/logicnode/SeparateRotationNode.hx
Normal file
|
@ -0,0 +1,61 @@
|
|||
package armory.logicnode;
|
||||
|
||||
import kha.FastFloat;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
import iron.math.Rotation;
|
||||
|
||||
class SeparateRotationNode extends LogicNode {
|
||||
|
||||
public var property0 = "EulerAngles"; // EulerAngles, AxisAngle, or Quat
|
||||
public var property1 = "Rad"; // Rad or Deg
|
||||
public var property2 = "XYZ";
|
||||
|
||||
static inline var toDEG:FastFloat = 57.29577951308232; // 180/pi
|
||||
|
||||
var input_cache = new Quat();
|
||||
var euler_cache = new Vec4();
|
||||
var aa_axis_cache = new Vec4();
|
||||
var aa_angle_cache: Float = 0;
|
||||
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
||||
override function get(from: Int): Dynamic {
|
||||
var q: Quat = inputs[0].get();
|
||||
if (q == null) return null;
|
||||
q.normalize();
|
||||
|
||||
switch (property0) {
|
||||
case "EulerAngles":
|
||||
if (q!=this.input_cache)
|
||||
euler_cache = Rotation.quatToEuler(q, property2);
|
||||
if (from>0)
|
||||
return null;
|
||||
|
||||
switch (property1){
|
||||
case "Rad": return euler_cache;
|
||||
case "Deg": return new Vec4(euler_cache.x*toDEG, euler_cache.y*toDEG, euler_cache.z*toDEG);
|
||||
}
|
||||
|
||||
case "AxisAngle":
|
||||
if (q!=this.input_cache)
|
||||
aa_angle_cache = q.toAxisAngle(aa_axis_cache);
|
||||
switch (from){
|
||||
case 0: return aa_axis_cache;
|
||||
case 1: switch(property1){
|
||||
case "Rad": return aa_angle_cache;
|
||||
case "Deg": return toDEG*aa_angle_cache;
|
||||
}
|
||||
}
|
||||
case "Quat":
|
||||
switch(from){
|
||||
case 0: return new Vec4(q.x,q.y,q.z);
|
||||
case 1: return q.w;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ class SeparateTransformNode extends LogicNode {
|
|||
matrix.decompose(loc, rot, scale);
|
||||
|
||||
if (from == 0) return loc;
|
||||
else if (from == 1) return rot.getEuler();
|
||||
else if (from == 1) return rot;
|
||||
else return scale;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
|
||||
package armory.logicnode;
|
||||
|
||||
import iron.object.Object;
|
||||
import iron.math.Quat;
|
||||
import iron.math.Vec4;
|
||||
import armory.trait.physics.RigidBody;
|
||||
|
||||
class SetRotationNode extends LogicNode {
|
||||
|
||||
public var property0: String;
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
}
|
||||
|
@ -16,27 +14,13 @@ class SetRotationNode extends LogicNode {
|
|||
override function run(from: Int) {
|
||||
var object: Object = inputs[1].get();
|
||||
if (object == null) return;
|
||||
var vec: Vec4 = inputs[2].get();
|
||||
if (vec == null) return;
|
||||
var w: Float = inputs[3].get();
|
||||
var q: Quat = inputs[2].get();
|
||||
if (q == null) return;
|
||||
|
||||
switch (property0) {
|
||||
case "Euler Angles":
|
||||
object.transform.rot.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);
|
||||
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();
|
||||
}
|
||||
q.normalize();
|
||||
object.transform.rot = q;
|
||||
object.transform.buildMatrix();
|
||||
|
||||
#if arm_physics
|
||||
var rigidBody = object.getTrait(RigidBody);
|
||||
if (rigidBody != null) {
|
||||
|
|
|
@ -7,9 +7,9 @@ import iron.math.Quat;
|
|||
class TransformNode extends LogicNode {
|
||||
|
||||
var value: Mat4 = Mat4.identity();
|
||||
static var q = new Quat();
|
||||
static var v1 = new Vec4();
|
||||
static var v2 = new Vec4();
|
||||
var q = new Quat();
|
||||
var v1 = new Vec4();
|
||||
var v2 = new Vec4();
|
||||
|
||||
public function new(tree: LogicTree) {
|
||||
super(tree);
|
||||
|
@ -17,18 +17,21 @@ class TransformNode extends LogicNode {
|
|||
|
||||
override function get(from: Int): Dynamic {
|
||||
var loc: Vec4 = inputs[0].get();
|
||||
var rot: Vec4 = inputs[1].get();
|
||||
var rot: Quat = new Quat().setFrom(inputs[1].get());
|
||||
rot.normalize();
|
||||
var scale: Vec4 = inputs[2].get();
|
||||
if (loc == null && rot == null && scale == null) return this.value;
|
||||
if (loc == null || rot == null || scale == null) return null;
|
||||
q.fromEuler(rot.x, rot.y, rot.z);
|
||||
value.compose(loc, q, scale);
|
||||
return value;
|
||||
this.value.compose(loc, rot, scale);
|
||||
return this.value;
|
||||
}
|
||||
|
||||
override function set(value: Dynamic) {
|
||||
cast(value, Mat4).decompose(v1, q, v2);
|
||||
inputs[0].set(v1);
|
||||
inputs[1].set(q.getEuler());
|
||||
inputs[2].set(v2);
|
||||
if (inputs.length>0){
|
||||
cast(value, Mat4).decompose(v1, q, v2);
|
||||
inputs[0].set(v1);
|
||||
inputs[1].set(q);
|
||||
inputs[2].set(v2);
|
||||
}else this.value = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from math import pi, cos, sin, sqrt
|
||||
import bpy
|
||||
from bpy.props import PointerProperty
|
||||
from bpy.props import PointerProperty, EnumProperty, FloatProperty, FloatVectorProperty
|
||||
from bpy.types import NodeSocket
|
||||
import mathutils
|
||||
|
||||
import arm.utils
|
||||
|
||||
|
@ -69,6 +71,169 @@ class ArmAnimActionSocket(ArmCustomSocket):
|
|||
return 0.8, 0.8, 0.8, 1
|
||||
|
||||
|
||||
class ArmRotationSocket(ArmCustomSocket):
|
||||
bl_idname = 'ArmNodeSocketRotation'
|
||||
bl_label = 'Rotation Socket'
|
||||
arm_socket_type = 'ROTATION' # the internal representation is a quaternion, AKA a '4D vector' (using mathutils.Vector((x,y,z,w)))
|
||||
|
||||
def get_default_value(self):
|
||||
if self.default_value_raw is None:
|
||||
return Vector((0.0,0.0,0.0,1.0))
|
||||
else:
|
||||
return self.default_value_raw
|
||||
|
||||
def on_unit_update(self, context):
|
||||
if self.default_value_unit == 'Rad':
|
||||
fac = pi/180 # deg->rad conversion
|
||||
else:
|
||||
fac = 180/pi # rad->deg conversion
|
||||
if self.default_value_mode == 'AxisAngle':
|
||||
self.default_value_s3 *= fac
|
||||
elif self.default_value_mode == 'EulerAngles':
|
||||
self.default_value_s0 *= fac
|
||||
self.default_value_s1 *= fac
|
||||
self.default_value_s2 *= fac
|
||||
self.do_update_raw(context)
|
||||
|
||||
|
||||
def on_mode_update(self, context):
|
||||
if self.default_value_mode == 'Quat':
|
||||
summ = abs(self.default_value_s0)
|
||||
summ+= abs(self.default_value_s1)
|
||||
summ+= abs(self.default_value_s2)
|
||||
summ+= abs(self.default_value_s3)
|
||||
if summ<0.01:
|
||||
self.default_value_s3 = 1.0
|
||||
elif self.default_value_mode == 'AxisAngle':
|
||||
summ = abs(self.default_value_s0)
|
||||
summ+= abs(self.default_value_s1)
|
||||
summ+= abs(self.default_value_s2)
|
||||
if summ<1E-5:
|
||||
self.default_value_s3 = 0.0
|
||||
self.do_update_raw(context)
|
||||
|
||||
def do_update_raw(self, context):
|
||||
if self.default_value_mode == 'Quat':
|
||||
qx = self.default_value_s0
|
||||
qy = self.default_value_s1
|
||||
qz = self.default_value_s2
|
||||
qw = self.default_value_s3
|
||||
# need to normalize the quaternion for a rotation (having it be 0 is not an option)
|
||||
ql = sqrt(qx**2+qy**2+qz**2+qw**2)
|
||||
if abs(ql)<1E-5:
|
||||
qx, qy, qz, qw = 0.0,0.0,0.0,1.0
|
||||
else:
|
||||
qx /= ql
|
||||
qy /= ql
|
||||
qz /= ql
|
||||
qw /= ql
|
||||
self.default_value_raw = mathutils.Vector((qx,qy,qz,qw))
|
||||
|
||||
elif self.default_value_mode == 'AxisAngle':
|
||||
if self.default_value_unit == 'Deg':
|
||||
angle = self.default_value_s3 * pi/180
|
||||
else:
|
||||
angle = self.default_value_s3
|
||||
cang, sang = cos(angle/2), sin(angle/2)
|
||||
x = self.default_value_s0
|
||||
y = self.default_value_s1
|
||||
z = self.default_value_s2
|
||||
veclen = sqrt(x**2+y**2+z**2)
|
||||
if veclen<1E-5:
|
||||
self.default_value_raw = mathutils.Vector((0.0,0.0,0.0,1.0))
|
||||
else:
|
||||
self.default_value_raw = mathutils.Vector((
|
||||
x/veclen * sang,
|
||||
y/veclen * sang,
|
||||
z/veclen * sang,
|
||||
cang
|
||||
))
|
||||
else:
|
||||
if self.default_value_unit == 'Deg':
|
||||
x = self.default_value_s0 * pi/180
|
||||
y = self.default_value_s1 * pi/180
|
||||
z = self.default_value_s2 * pi/180
|
||||
else:
|
||||
x = self.default_value_s0
|
||||
y = self.default_value_s1
|
||||
z = self.default_value_s2
|
||||
cx, sx = cos(x/2), sin(x/2)
|
||||
cy, sy = cos(y/2), sin(y/2)
|
||||
cz, sz = cos(z/2), sin(z/2)
|
||||
|
||||
qw, qx, qy, qz = 1.0,0.0,0.0,0.0
|
||||
for direction in self.default_value_order[::-1]:
|
||||
qwi, qxi,qyi,qzi = {'X': (cx,sx,0,0), 'Y': (cy,0,sy,0), 'Z': (cz,0,0,sz)}[direction]
|
||||
|
||||
qw = qw*qwi -qx*qxi -qy*qyi -qz*qzi
|
||||
qx = qx*qwi +qw*qxi +qy*qzi -qz*qyi
|
||||
qy = qy*qwi +qw*qyi +qz*qxi -qx*qzi
|
||||
qz = qz*qwi +qw*qzi +qx*qyi -qy*qxi
|
||||
self.default_value_raw = mathutils.Vector((qx,qy,qz,qw))
|
||||
|
||||
|
||||
def draw(self, context, layout, node, text):
|
||||
if (self.is_output or self.is_linked):
|
||||
layout.label(text=self.name)
|
||||
else:
|
||||
coll1 = layout.column(align=True)
|
||||
coll1.label(text=self.name)
|
||||
bx=coll1.box()
|
||||
coll = bx.column(align=True)
|
||||
coll.prop(self, 'default_value_mode')
|
||||
if self.default_value_mode in ('EulerAngles', 'AxisAngle'):
|
||||
coll.prop(self, 'default_value_unit')
|
||||
|
||||
if self.default_value_mode == 'EulerAngles':
|
||||
coll.prop(self, 'default_value_order')
|
||||
coll.prop(self, 'default_value_s0', text='X')
|
||||
coll.prop(self, 'default_value_s1', text='Y')
|
||||
coll.prop(self, 'default_value_s2', text='Z')
|
||||
elif self.default_value_mode == 'Quat':
|
||||
coll.prop(self, 'default_value_s0', text='X')
|
||||
coll.prop(self, 'default_value_s1', text='Y')
|
||||
coll.prop(self, 'default_value_s2', text='Z')
|
||||
coll.prop(self, 'default_value_s3', text='W')
|
||||
elif self.default_value_mode == 'AxisAngle':
|
||||
coll.prop(self, 'default_value_s0', text='X')
|
||||
coll.prop(self, 'default_value_s1', text='Y')
|
||||
coll.prop(self, 'default_value_s2', text='Z')
|
||||
coll.separator()
|
||||
coll.prop(self, 'default_value_s3', text='Angle')
|
||||
|
||||
def draw_color(self, context, node):
|
||||
return 0.68, 0.22, 0.62, 1
|
||||
|
||||
default_value_mode: EnumProperty(
|
||||
items=[('EulerAngles', 'Euler Angles', 'Euler Angles'),
|
||||
('AxisAngle', 'Axis/Angle', 'Axis/Angle'),
|
||||
('Quat', 'Quaternion', 'Quaternion')],
|
||||
name='', default='EulerAngles',
|
||||
update=on_mode_update)
|
||||
|
||||
default_value_unit: EnumProperty(
|
||||
items=[('Deg', 'Degrees', 'Degrees'),
|
||||
('Rad', 'Radians', 'Radians')],
|
||||
name='', default='Rad',
|
||||
update=on_unit_update)
|
||||
default_value_order: EnumProperty(
|
||||
items=[('XYZ','XYZ','XYZ'),
|
||||
('XZY','XZY (legacy Armory euler order)','XZY (legacy Armory euler order)'),
|
||||
('YXZ','YXZ','YXZ'),
|
||||
('YZX','YZX','YZX'),
|
||||
('ZXY','ZXY','ZXY'),
|
||||
('ZYX','ZYX','ZYX')],
|
||||
name='', default='XYZ'
|
||||
)
|
||||
|
||||
default_value_s0: FloatProperty(update=do_update_raw)
|
||||
default_value_s1: FloatProperty(update=do_update_raw)
|
||||
default_value_s2: FloatProperty(update=do_update_raw)
|
||||
default_value_s3: FloatProperty(update=do_update_raw)
|
||||
|
||||
default_value_raw: FloatVectorProperty(size=4, default=(0,0,0,1))
|
||||
|
||||
|
||||
class ArmArraySocket(ArmCustomSocket):
|
||||
bl_idname = 'ArmNodeSocketArray'
|
||||
bl_label = 'Array Socket'
|
||||
|
@ -118,6 +283,7 @@ class ArmObjectSocket(ArmCustomSocket):
|
|||
def register():
|
||||
bpy.utils.register_class(ArmActionSocket)
|
||||
bpy.utils.register_class(ArmAnimActionSocket)
|
||||
bpy.utils.register_class(ArmRotationSocket)
|
||||
bpy.utils.register_class(ArmArraySocket)
|
||||
bpy.utils.register_class(ArmObjectSocket)
|
||||
|
||||
|
@ -126,4 +292,5 @@ def unregister():
|
|||
bpy.utils.unregister_class(ArmObjectSocket)
|
||||
bpy.utils.unregister_class(ArmArraySocket)
|
||||
bpy.utils.unregister_class(ArmAnimActionSocket)
|
||||
bpy.utils.unregister_class(ArmRotationSocket)
|
||||
bpy.utils.unregister_class(ArmActionSocket)
|
||||
|
|
119
blender/arm/logicnode/math/LN_rotation_math.py
Normal file
119
blender/arm/logicnode/math/LN_rotation_math.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class QuaternionMathNode(ArmLogicTreeNode):
|
||||
"""Mathematical operations on rotations."""
|
||||
bl_idname = 'LNRotationMathNode'
|
||||
bl_label = 'Rotation Math'
|
||||
arm_section = 'quaternions'
|
||||
arm_version = 1
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_count_in(operation_name):
|
||||
return {
|
||||
'Inverse': 1,
|
||||
'Normalize': 1,
|
||||
'Compose': 2,
|
||||
'Amplify': 2,
|
||||
'FromTo': 2,
|
||||
#'FromRotationMat': 2,
|
||||
'Lerp': 3,
|
||||
'Slerp': 3,
|
||||
}.get(operation_name, 0)
|
||||
|
||||
def ensure_input_socket(self, socket_number, newclass, newname):
|
||||
while len(self.inputs) < socket_number:
|
||||
self.inputs.new('NodeSocketFloat', 'BOGUS')
|
||||
if len(self.inputs) > socket_number:
|
||||
if len(self.inputs[socket_number].links) == 1:
|
||||
source_socket = self.inputs[socket_number].links[0].from_socket
|
||||
else:
|
||||
source_socket = None
|
||||
self.inputs.remove(self.inputs[socket_number])
|
||||
else:
|
||||
source_socket = None
|
||||
|
||||
|
||||
self.inputs.new(newclass, newname)
|
||||
self.inputs.move(len(self.inputs)-1, socket_number)
|
||||
if source_socket is not None:
|
||||
self.id_data.links.new(source_socket, self.inputs[socket_number])
|
||||
|
||||
def ensure_output_socket(self, socket_number, newclass, newname):
|
||||
sink_sockets = []
|
||||
while len(self.outputs) < socket_number:
|
||||
self.outputs.new('NodeSocketFloat', 'BOGUS')
|
||||
if len(self.outputs) > socket_number:
|
||||
for link in self.inputs[socket_number].links:
|
||||
sink_sockets.append(link.to_socket)
|
||||
self.inputs.remove(self.inputs[socket_number])
|
||||
|
||||
self.inputs.new(newclass, newname)
|
||||
self.inputs.move(len(self.inputs)-1, socket_number)
|
||||
for socket in sink_sockets:
|
||||
self.id_data.links.new(self.inputs[socket_number], socket)
|
||||
|
||||
def on_update_operation(self, context):
|
||||
# Checking the selection of another operation
|
||||
|
||||
|
||||
# Rotation as argument 0:
|
||||
if self.property0 in ('Inverse','Normalize','Amplify'):
|
||||
self.ensure_input_socket(0, "ArmNodeSocketRotation", "Rotation")
|
||||
self.ensure_input_socket(1, "NodeSocketFloat", "Amplification factor")
|
||||
elif self.property0 in ('Slerp','Lerp','Compose'):
|
||||
self.ensure_input_socket(0, "ArmNodeSocketRotation", "From")
|
||||
self.ensure_input_socket(1, "ArmNodeSocketRotation", "To")
|
||||
|
||||
if self.property0 == 'Compose':
|
||||
self.inputs[0].name = 'Outer rotation'
|
||||
self.inputs[1].name = 'Inner rotation'
|
||||
else:
|
||||
self.ensure_input_socket(2, "NodeSocketFloat", "Interpolation factor")
|
||||
|
||||
elif self.property0 == 'FromTo':
|
||||
self.ensure_input_socket(0, "ArmNodeSocketRotation", "From")
|
||||
self.ensure_input_socket(1, "ArmNodeSocketRotation", "To")
|
||||
|
||||
# Rotation as argument 1:
|
||||
if self.property0 in ('Compose','Lerp','Slerp'):
|
||||
if self.inputs[1].bl_idname != "ArmNodeSocketRotation":
|
||||
self.replace_input_socket(1, "ArmNodeSocketRotation", "Quaternion 2")
|
||||
if self.property0 == 'Compose':
|
||||
self.inputs[1].name = "Inner quaternion"
|
||||
# Float as argument 1:
|
||||
if self.property0 == 'Amplify':
|
||||
if self.inputs[1].bl_idname != 'NodeSocketFloat':
|
||||
self.replace_input_socket(1, "NodeSocketFloat", "Amplification factor")
|
||||
# Vector as argument 1:
|
||||
#if self.property0 == 'FromRotationMat':
|
||||
# # WHAT??
|
||||
# pass
|
||||
|
||||
while len(self.inputs) > self.get_count_in(self.property0):
|
||||
self.inputs.remove(self.inputs[len(self.inputs)-1])
|
||||
|
||||
|
||||
property0: EnumProperty(
|
||||
items = [('Compose', 'Compose (multiply)', 'compose (multiply) two rotations. Note that order of the composition matters.'),
|
||||
('Amplify', 'Amplify (multiply by float)', 'Amplify or diminish the effect of a rotation'),
|
||||
('Normalize', 'Normalize', 'Normalize'),
|
||||
('Inverse', 'Get Inverse', 'from r, get the rotation r2 so that " r×r2=r2×r= <no rotation>" '),
|
||||
('Lerp', 'Lerp', 'Linearly interpolation'),
|
||||
('Slerp', 'Slerp', 'Spherical linear interpolation'),
|
||||
('FromTo', 'From To', 'From direction To direction'),
|
||||
#('FromRotationMat', 'From Rotation Mat', 'From Rotation Mat')
|
||||
],
|
||||
name='', default='Compose', update=on_update_operation)
|
||||
|
||||
#def __init__(self):
|
||||
# array_nodes[str(id(self))] = self
|
||||
|
||||
def init(self, context):
|
||||
super(QuaternionMathNode, self).init(context)
|
||||
self.add_input('ArmNodeSocketRotation', 'Quaternion 0', default_value=[0.0, 0.0, 0.0])
|
||||
self.add_input('ArmNodeSocketRotation', 'Quaternion 1', default_value=[0.0, 0.0, 0.0])
|
||||
self.add_output('ArmNodeSocketRotation', 'Result')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0') # Operation
|
|
@ -10,10 +10,12 @@ class GetRotationNode(ArmLogicTreeNode):
|
|||
def init(self, context):
|
||||
super(GetRotationNode, self).init(context)
|
||||
self.add_input('ArmNodeSocketObject', 'Object')
|
||||
self.add_output('ArmNodeSocketRotation', 'Rotation')
|
||||
|
||||
self.add_output('NodeSocketVector', 'Euler Angles')
|
||||
self.add_output('NodeSocketVector', 'Vector')
|
||||
self.add_output('NodeSocketFloat', 'Angle (Radians)')
|
||||
self.add_output('NodeSocketFloat', 'Angle (Degrees)')
|
||||
self.add_output('NodeSocketVector', 'Quaternion XYZ')
|
||||
self.add_output('NodeSocketFloat', 'Quaternion W')
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0')
|
||||
|
||||
property0: EnumProperty(
|
||||
items = [('Local', 'Local', 'Local'),
|
||||
('Global', 'Global', 'Global')],
|
||||
name='', default='Local')
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class LookAtNode(ArmLogicTreeNode):
|
||||
"""Converts the two given coordinates to a quaternion rotation."""
|
||||
"""Returns *a* rotation that makes something look away from X,Y or Z, and instead look in the 'from->to' direction"""
|
||||
bl_idname = 'LNLookAtNode'
|
||||
bl_label = 'Look At'
|
||||
arm_section = 'rotation'
|
||||
|
@ -21,7 +21,7 @@ class LookAtNode(ArmLogicTreeNode):
|
|||
self.add_input('NodeSocketVector', 'From Location')
|
||||
self.add_input('NodeSocketVector', 'To Location')
|
||||
|
||||
self.add_output('NodeSocketVector', 'Rotation')
|
||||
self.add_output('ArmNodeSocketRotation', 'Rotation')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
layout.prop(self, 'property0')
|
||||
|
|
|
@ -11,39 +11,14 @@ class RotateObjectNode(ArmLogicTreeNode):
|
|||
super().init(context)
|
||||
self.add_input('ArmNodeSocketAction', 'In')
|
||||
self.add_input('ArmNodeSocketObject', 'Object')
|
||||
self.add_input('NodeSocketVector', 'Euler Angles')
|
||||
self.add_input('NodeSocketFloat', 'Angle / W')
|
||||
self.add_input('ArmNodeSocketRotation', 'Rotation')
|
||||
|
||||
self.add_output('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')
|
||||
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)
|
||||
items = [('Local', 'Local F.O.R.', 'Frame of reference oriented with the object'),
|
||||
('Global', 'Global/Parent F.O.R.', 'Frame of reference oriented with the object\'s parent or the world')],
|
||||
name='', default='Local')
|
||||
|
|
58
blender/arm/logicnode/transform/LN_separate_rotation.py
Normal file
58
blender/arm/logicnode/transform/LN_separate_rotation.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class SeparateRotationNode(ArmLogicTreeNode):
|
||||
"""Decompose a rotation into one of its mathematical representations"""
|
||||
bl_idname = 'LNSeparateRotationNode'
|
||||
bl_label = 'Separate Rotation'
|
||||
arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
|
||||
def init(self, context):
|
||||
super(SeparateRotationNode, self).init(context)
|
||||
self.add_input('ArmNodeSocketRotation', 'Angle')
|
||||
|
||||
self.add_output('NodeSocketVector', 'Euler Angles / Vector XYZ')
|
||||
self.add_output('NodeSocketFloat', 'Angle / W')
|
||||
|
||||
|
||||
def on_property_update(self, context):
|
||||
"""called by the EnumProperty, used to update the node socket labels"""
|
||||
if self.property0 == "Quat":
|
||||
self.outputs[0].name = "Quaternion XYZ"
|
||||
self.outputs[1].name = "Quaternion W"
|
||||
elif self.property0 == "EulerAngles":
|
||||
self.outputs[0].name = "Euler Angles"
|
||||
self.outputs[1].name = "[unused for Euler output]"
|
||||
elif self.property0.startswith("AxisAngle"):
|
||||
self.outputs[0].name = "Axis"
|
||||
self.outputs[1].name = "Angle"
|
||||
else:
|
||||
raise ValueError('No nodesocket labels for current input mode: check self-consistancy of LN_separate_rotation.py')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
coll = layout.column(align=True)
|
||||
coll.prop(self, 'property0')
|
||||
if self.property0 in ('EulerAngles','AxisAngle'):
|
||||
coll.prop(self, 'property1')
|
||||
if self.property0=='EulerAngles':
|
||||
coll.prop(self, 'property2')
|
||||
|
||||
property0: EnumProperty(
|
||||
items = [('EulerAngles', 'Euler Angles', 'Euler Angles'),
|
||||
('AxisAngle', 'Axis/Angle', 'Axis/Angle'),
|
||||
('Quat', 'Quaternion', 'Quaternion')],
|
||||
name='', default='EulerAngles',
|
||||
update=on_property_update)
|
||||
|
||||
property1: EnumProperty(
|
||||
items=[('Deg', 'Degrees', 'Degrees'),
|
||||
('Rad', 'Radians', 'Radians')],
|
||||
name='', default='Rad')
|
||||
property2: EnumProperty(
|
||||
items=[('XYZ','XYZ','XYZ'),
|
||||
('XZY','XZY (legacy Armory euler order)','XZY (legacy Armory euler order)'),
|
||||
('YXZ','YXZ','YXZ'),
|
||||
('YZX','YZX','YZX'),
|
||||
('ZXY','ZXY','ZXY'),
|
||||
('ZYX','ZYX','ZYX')],
|
||||
name='', default='XYZ')
|
|
@ -11,5 +11,5 @@ class SeparateTransformNode(ArmLogicTreeNode):
|
|||
self.add_input('NodeSocketShader', 'Transform')
|
||||
|
||||
self.add_output('NodeSocketVector', 'Location')
|
||||
self.add_output('NodeSocketVector', 'Rotation')
|
||||
self.add_output('ArmNodeSocketRotation', 'Rotation')
|
||||
self.add_output('NodeSocketVector', 'Scale')
|
||||
|
|
|
@ -7,36 +7,19 @@ class SetRotationNode(ArmLogicTreeNode):
|
|||
arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
|
||||
|
||||
def init(self, context):
|
||||
super(SetRotationNode, self).init(context)
|
||||
self.add_input('ArmNodeSocketAction', 'In')
|
||||
self.add_input('ArmNodeSocketObject', 'Object')
|
||||
self.add_input('NodeSocketVector', 'Euler Angles / Vector XYZ')
|
||||
self.add_input('NodeSocketFloat', 'Angle / W')
|
||||
self.add_input('ArmNodeSocketRotation', 'Rotation')
|
||||
|
||||
self.add_output('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')
|
||||
|
||||
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)
|
||||
# property0: EnumProperty(
|
||||
# items = [('Local', 'Local', 'Local'),
|
||||
# ('Global', 'Global', 'Global')],
|
||||
# name='', default='Local')
|
||||
|
|
12
blender/arm/logicnode/transform/LN_test_rotation.py
Normal file
12
blender/arm/logicnode/transform/LN_test_rotation.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class TestRotationNode(ArmLogicTreeNode):
|
||||
"""TO DO."""
|
||||
bl_idname = 'LNTestRotationNode'
|
||||
bl_label = 'TEST NODE DO NOT USE'
|
||||
arm_section = 'quaternions'
|
||||
arm_version = 1
|
||||
|
||||
def init(self, context):
|
||||
super(TestRotationNode, self).init(context)
|
||||
self.add_input('ArmNodeSocketRotation', 'taste')
|
|
@ -9,7 +9,7 @@ class TransformNode(ArmLogicTreeNode):
|
|||
def init(self, context):
|
||||
super(TransformNode, self).init(context)
|
||||
self.add_input('NodeSocketVector', 'Location')
|
||||
self.add_input('NodeSocketVector', 'Rotation')
|
||||
self.add_input('ArmNodeSocketRotation', 'Rotation')
|
||||
self.add_input('NodeSocketVector', 'Scale', default_value=[1.0, 1.0, 1.0])
|
||||
|
||||
self.add_output('NodeSocketShader', 'Transform')
|
||||
self.add_output('NodeSocketShader', 'Transform', is_var=True)
|
||||
|
|
|
@ -10,7 +10,7 @@ class VectorToObjectOrientationNode(ArmLogicTreeNode):
|
|||
"""
|
||||
bl_idname = 'LNVectorToObjectOrientationNode'
|
||||
bl_label = 'Vector to Object Orientation'
|
||||
arm_section = 'location'
|
||||
arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
|
||||
def init(self, context):
|
||||
|
|
58
blender/arm/logicnode/variable/LN_rotation.py
Normal file
58
blender/arm/logicnode/variable/LN_rotation.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from arm.logicnode.arm_nodes import *
|
||||
|
||||
class RotationNode(ArmLogicTreeNode):
|
||||
"""A rotation, created from one of its possible mathematical representations"""
|
||||
bl_idname = 'LNRotationNode'
|
||||
bl_label = 'Rotation'
|
||||
#arm_section = 'rotation'
|
||||
arm_version = 1
|
||||
|
||||
def init(self, context):
|
||||
super(RotationNode, self).init(context)
|
||||
self.add_input('NodeSocketVector', 'Euler Angles / Vector XYZ')
|
||||
self.add_input('NodeSocketFloat', 'Angle / W')
|
||||
|
||||
self.add_output('ArmNodeSocketRotation', 'Out', is_var=True)
|
||||
|
||||
def on_property_update(self, context):
|
||||
"""called by the EnumProperty, used to update the node socket labels"""
|
||||
if self.property0 == "Quaternion":
|
||||
self.inputs[0].name = "Quaternion XYZ"
|
||||
self.inputs[1].name = "Quaternion W"
|
||||
elif self.property0 == "EulerAngles":
|
||||
self.inputs[0].name = "Euler Angles"
|
||||
self.inputs[1].name = "[unused for Euler input]"
|
||||
elif self.property0 == "AxisAngle":
|
||||
self.inputs[0].name = "Axis"
|
||||
self.inputs[1].name = "Angle"
|
||||
else:
|
||||
raise ValueError('No nodesocket labels for current input mode: check self-consistancy of LN_rotation.py')
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
coll = layout.column(align=True)
|
||||
coll.prop(self, 'property0')
|
||||
if self.property0 in ('EulerAngles','AxisAngle'):
|
||||
coll.prop(self, 'property1')
|
||||
if self.property0=='EulerAngles':
|
||||
coll.prop(self, 'property2')
|
||||
|
||||
property0: EnumProperty(
|
||||
items = [('EulerAngles', 'Euler Angles', 'Euler Angles'),
|
||||
('AxisAngle', 'Axis/Angle', 'Axis/Angle'),
|
||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
||||
name='', default='EulerAngles',
|
||||
update=on_property_update)
|
||||
|
||||
property1: EnumProperty(
|
||||
items=[('Deg', 'Degrees', 'Degrees'),
|
||||
('Rad', 'Radians', 'Radians')],
|
||||
name='', default='Rad')
|
||||
property2: EnumProperty(
|
||||
items=[('XYZ','XYZ','XYZ'),
|
||||
('XZY','XZY (legacy Armory euler order)','XZY (legacy Armory euler order)'),
|
||||
('YXZ','YXZ','YXZ'),
|
||||
('YZX','YZX','YZX'),
|
||||
('ZXY','ZXY','ZXY'),
|
||||
('ZYX','ZYX','ZYX')],
|
||||
name='', default='XYZ'
|
||||
)
|
|
@ -294,6 +294,8 @@ def build_default_node(inp: bpy.types.NodeSocket):
|
|||
|
||||
if inp_type == 'VECTOR':
|
||||
return f'new armory.logicnode.VectorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]})'
|
||||
elif inp_type == 'ROTATION': # a rotation is internally represented as a quaternion.
|
||||
return f'new armory.logicnode.RotationNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]}, {default_value[3]})'
|
||||
elif inp_type == 'RGBA':
|
||||
return f'new armory.logicnode.ColorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]}, {default_value[3]})'
|
||||
elif inp_type == 'RGB':
|
||||
|
|
Loading…
Reference in a new issue