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;
|
package armory.logicnode;
|
||||||
|
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
import iron.math.Vec3;
|
import iron.math.Quat;
|
||||||
|
import iron.math.Vec4;
|
||||||
|
|
||||||
class GetRotationNode extends LogicNode {
|
class GetRotationNode extends LogicNode {
|
||||||
|
|
||||||
|
public var property0: String;
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
}
|
}
|
||||||
|
@ -16,34 +19,16 @@ class GetRotationNode extends LogicNode {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rot = object.transform.rot;
|
|
||||||
|
|
||||||
switch (from) {
|
switch(property0){
|
||||||
case 0:
|
case "Local":
|
||||||
// euler angles
|
return object.transform.rot;
|
||||||
return object.transform.rot.getEuler();
|
case "Global":{
|
||||||
case 1:
|
var useless1 = new Vec4();
|
||||||
// vector
|
var ret = new Quat();
|
||||||
var sqrtW = Math.sqrt(1 - (rot.w * rot.w));
|
object.transform.world.decompose(useless1, ret, useless1);
|
||||||
if (sqrtW == 0) {
|
return ret;
|
||||||
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;
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,6 @@ class LookAtNode extends LogicNode {
|
||||||
v2.setFrom(vto).sub(vfrom).normalize();
|
v2.setFrom(vto).sub(vfrom).normalize();
|
||||||
|
|
||||||
q.fromTo(v1, v2);
|
q.fromTo(v1, v2);
|
||||||
return q.getEuler();
|
return q;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,11 @@ package armory.logicnode;
|
||||||
|
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
import iron.math.Quat;
|
import iron.math.Quat;
|
||||||
import iron.math.Vec4;
|
|
||||||
import armory.trait.physics.RigidBody;
|
import armory.trait.physics.RigidBody;
|
||||||
|
|
||||||
class RotateObjectNode extends LogicNode {
|
class RotateObjectNode extends LogicNode {
|
||||||
|
|
||||||
public var property0 = "Euler Angles";
|
public var property0 = "Local";
|
||||||
var q = new Quat();
|
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
|
@ -16,32 +14,19 @@ class RotateObjectNode extends LogicNode {
|
||||||
|
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var object: Object = inputs[1].get();
|
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.
|
if (object == null || q == null) return;
|
||||||
// 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 || vec == null) return;
|
q.normalize();
|
||||||
|
switch (property0){
|
||||||
switch (property0) {
|
case "Local":
|
||||||
case "Euler Angles":
|
object.transform.rot.mult(q);
|
||||||
q.fromEuler(vec.x, vec.y, vec.z);
|
case "Global":
|
||||||
case "Angle Axies (Degrees)" | "Angle Axies (Radians)":
|
object.transform.rot.multquats(q, object.transform.rot);
|
||||||
var angle: Float = inputs[3].get();
|
// that function call (Quat.multquats) is weird: it both modifies the object, and returns `this`
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object.transform.rot.mult(q);
|
|
||||||
object.transform.buildMatrix();
|
object.transform.buildMatrix();
|
||||||
|
|
||||||
#if arm_physics
|
#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);
|
matrix.decompose(loc, rot, scale);
|
||||||
|
|
||||||
if (from == 0) return loc;
|
if (from == 0) return loc;
|
||||||
else if (from == 1) return rot.getEuler();
|
else if (from == 1) return rot;
|
||||||
else return scale;
|
else return scale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
|
|
||||||
package armory.logicnode;
|
package armory.logicnode;
|
||||||
|
|
||||||
import iron.object.Object;
|
import iron.object.Object;
|
||||||
import iron.math.Quat;
|
import iron.math.Quat;
|
||||||
import iron.math.Vec4;
|
|
||||||
import armory.trait.physics.RigidBody;
|
import armory.trait.physics.RigidBody;
|
||||||
|
|
||||||
class SetRotationNode extends LogicNode {
|
class SetRotationNode extends LogicNode {
|
||||||
|
|
||||||
public var property0: String;
|
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
}
|
}
|
||||||
|
@ -16,27 +14,13 @@ class SetRotationNode extends LogicNode {
|
||||||
override function run(from: Int) {
|
override function run(from: Int) {
|
||||||
var object: Object = inputs[1].get();
|
var object: Object = inputs[1].get();
|
||||||
if (object == null) return;
|
if (object == null) return;
|
||||||
var vec: Vec4 = inputs[2].get();
|
var q: Quat = inputs[2].get();
|
||||||
if (vec == null) return;
|
if (q == null) return;
|
||||||
var w: Float = inputs[3].get();
|
|
||||||
|
|
||||||
switch (property0) {
|
q.normalize();
|
||||||
case "Euler Angles":
|
object.transform.rot = q;
|
||||||
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();
|
|
||||||
}
|
|
||||||
object.transform.buildMatrix();
|
object.transform.buildMatrix();
|
||||||
|
|
||||||
#if arm_physics
|
#if arm_physics
|
||||||
var rigidBody = object.getTrait(RigidBody);
|
var rigidBody = object.getTrait(RigidBody);
|
||||||
if (rigidBody != null) {
|
if (rigidBody != null) {
|
||||||
|
|
|
@ -7,9 +7,9 @@ import iron.math.Quat;
|
||||||
class TransformNode extends LogicNode {
|
class TransformNode extends LogicNode {
|
||||||
|
|
||||||
var value: Mat4 = Mat4.identity();
|
var value: Mat4 = Mat4.identity();
|
||||||
static var q = new Quat();
|
var q = new Quat();
|
||||||
static var v1 = new Vec4();
|
var v1 = new Vec4();
|
||||||
static var v2 = new Vec4();
|
var v2 = new Vec4();
|
||||||
|
|
||||||
public function new(tree: LogicTree) {
|
public function new(tree: LogicTree) {
|
||||||
super(tree);
|
super(tree);
|
||||||
|
@ -17,18 +17,21 @@ class TransformNode extends LogicNode {
|
||||||
|
|
||||||
override function get(from: Int): Dynamic {
|
override function get(from: Int): Dynamic {
|
||||||
var loc: Vec4 = inputs[0].get();
|
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();
|
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;
|
if (loc == null || rot == null || scale == null) return null;
|
||||||
q.fromEuler(rot.x, rot.y, rot.z);
|
this.value.compose(loc, rot, scale);
|
||||||
value.compose(loc, q, scale);
|
return this.value;
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override function set(value: Dynamic) {
|
override function set(value: Dynamic) {
|
||||||
cast(value, Mat4).decompose(v1, q, v2);
|
if (inputs.length>0){
|
||||||
inputs[0].set(v1);
|
cast(value, Mat4).decompose(v1, q, v2);
|
||||||
inputs[1].set(q.getEuler());
|
inputs[0].set(v1);
|
||||||
inputs[2].set(v2);
|
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
|
import bpy
|
||||||
from bpy.props import PointerProperty
|
from bpy.props import PointerProperty, EnumProperty, FloatProperty, FloatVectorProperty
|
||||||
from bpy.types import NodeSocket
|
from bpy.types import NodeSocket
|
||||||
|
import mathutils
|
||||||
|
|
||||||
import arm.utils
|
import arm.utils
|
||||||
|
|
||||||
|
@ -68,6 +70,169 @@ class ArmAnimActionSocket(ArmCustomSocket):
|
||||||
def draw_color(self, context, node):
|
def draw_color(self, context, node):
|
||||||
return 0.8, 0.8, 0.8, 1
|
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):
|
class ArmArraySocket(ArmCustomSocket):
|
||||||
bl_idname = 'ArmNodeSocketArray'
|
bl_idname = 'ArmNodeSocketArray'
|
||||||
|
@ -118,6 +283,7 @@ class ArmObjectSocket(ArmCustomSocket):
|
||||||
def register():
|
def register():
|
||||||
bpy.utils.register_class(ArmActionSocket)
|
bpy.utils.register_class(ArmActionSocket)
|
||||||
bpy.utils.register_class(ArmAnimActionSocket)
|
bpy.utils.register_class(ArmAnimActionSocket)
|
||||||
|
bpy.utils.register_class(ArmRotationSocket)
|
||||||
bpy.utils.register_class(ArmArraySocket)
|
bpy.utils.register_class(ArmArraySocket)
|
||||||
bpy.utils.register_class(ArmObjectSocket)
|
bpy.utils.register_class(ArmObjectSocket)
|
||||||
|
|
||||||
|
@ -126,4 +292,5 @@ def unregister():
|
||||||
bpy.utils.unregister_class(ArmObjectSocket)
|
bpy.utils.unregister_class(ArmObjectSocket)
|
||||||
bpy.utils.unregister_class(ArmArraySocket)
|
bpy.utils.unregister_class(ArmArraySocket)
|
||||||
bpy.utils.unregister_class(ArmAnimActionSocket)
|
bpy.utils.unregister_class(ArmAnimActionSocket)
|
||||||
|
bpy.utils.unregister_class(ArmRotationSocket)
|
||||||
bpy.utils.unregister_class(ArmActionSocket)
|
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):
|
def init(self, context):
|
||||||
super(GetRotationNode, self).init(context)
|
super(GetRotationNode, self).init(context)
|
||||||
self.add_input('ArmNodeSocketObject', 'Object')
|
self.add_input('ArmNodeSocketObject', 'Object')
|
||||||
|
self.add_output('ArmNodeSocketRotation', 'Rotation')
|
||||||
|
|
||||||
self.add_output('NodeSocketVector', 'Euler Angles')
|
def draw_buttons(self, context, layout):
|
||||||
self.add_output('NodeSocketVector', 'Vector')
|
layout.prop(self, 'property0')
|
||||||
self.add_output('NodeSocketFloat', 'Angle (Radians)')
|
|
||||||
self.add_output('NodeSocketFloat', 'Angle (Degrees)')
|
property0: EnumProperty(
|
||||||
self.add_output('NodeSocketVector', 'Quaternion XYZ')
|
items = [('Local', 'Local', 'Local'),
|
||||||
self.add_output('NodeSocketFloat', 'Quaternion W')
|
('Global', 'Global', 'Global')],
|
||||||
|
name='', default='Local')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from arm.logicnode.arm_nodes import *
|
from arm.logicnode.arm_nodes import *
|
||||||
|
|
||||||
class LookAtNode(ArmLogicTreeNode):
|
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_idname = 'LNLookAtNode'
|
||||||
bl_label = 'Look At'
|
bl_label = 'Look At'
|
||||||
arm_section = 'rotation'
|
arm_section = 'rotation'
|
||||||
|
@ -21,7 +21,7 @@ class LookAtNode(ArmLogicTreeNode):
|
||||||
self.add_input('NodeSocketVector', 'From Location')
|
self.add_input('NodeSocketVector', 'From Location')
|
||||||
self.add_input('NodeSocketVector', 'To Location')
|
self.add_input('NodeSocketVector', 'To Location')
|
||||||
|
|
||||||
self.add_output('NodeSocketVector', 'Rotation')
|
self.add_output('ArmNodeSocketRotation', 'Rotation')
|
||||||
|
|
||||||
def draw_buttons(self, context, layout):
|
def draw_buttons(self, context, layout):
|
||||||
layout.prop(self, 'property0')
|
layout.prop(self, 'property0')
|
||||||
|
|
|
@ -11,39 +11,14 @@ class RotateObjectNode(ArmLogicTreeNode):
|
||||||
super().init(context)
|
super().init(context)
|
||||||
self.add_input('ArmNodeSocketAction', 'In')
|
self.add_input('ArmNodeSocketAction', 'In')
|
||||||
self.add_input('ArmNodeSocketObject', 'Object')
|
self.add_input('ArmNodeSocketObject', 'Object')
|
||||||
self.add_input('NodeSocketVector', 'Euler Angles')
|
self.add_input('ArmNodeSocketRotation', 'Rotation')
|
||||||
self.add_input('NodeSocketFloat', 'Angle / W')
|
|
||||||
|
|
||||||
self.add_output('ArmNodeSocketAction', 'Out')
|
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):
|
def draw_buttons(self, context, layout):
|
||||||
# this block is here to ensure backwards compatibility and warn the user.
|
layout.prop(self, 'property0')
|
||||||
# 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(
|
property0: EnumProperty(
|
||||||
items = [('Euler Angles', 'Euler Angles', 'Euler Angles'),
|
items = [('Local', 'Local F.O.R.', 'Frame of reference oriented with the object'),
|
||||||
('Angle Axies (Radians)', 'Angle Axies (Radians)', 'Angle Axies (Radians)'),
|
('Global', 'Global/Parent F.O.R.', 'Frame of reference oriented with the object\'s parent or the world')],
|
||||||
('Angle Axies (Degrees)', 'Angle Axies (Degrees)', 'Angle Axies (Degrees)'),
|
name='', default='Local')
|
||||||
('Quaternion', 'Quaternion', 'Quaternion')],
|
|
||||||
name='', default='Euler Angles',
|
|
||||||
update = on_property_update)
|
|
||||||
|
|
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_input('NodeSocketShader', 'Transform')
|
||||||
|
|
||||||
self.add_output('NodeSocketVector', 'Location')
|
self.add_output('NodeSocketVector', 'Location')
|
||||||
self.add_output('NodeSocketVector', 'Rotation')
|
self.add_output('ArmNodeSocketRotation', 'Rotation')
|
||||||
self.add_output('NodeSocketVector', 'Scale')
|
self.add_output('NodeSocketVector', 'Scale')
|
||||||
|
|
|
@ -6,37 +6,20 @@ class SetRotationNode(ArmLogicTreeNode):
|
||||||
bl_label = 'Set Object Rotation'
|
bl_label = 'Set Object Rotation'
|
||||||
arm_section = 'rotation'
|
arm_section = 'rotation'
|
||||||
arm_version = 1
|
arm_version = 1
|
||||||
|
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
super(SetRotationNode, self).init(context)
|
super(SetRotationNode, self).init(context)
|
||||||
self.add_input('ArmNodeSocketAction', 'In')
|
self.add_input('ArmNodeSocketAction', 'In')
|
||||||
self.add_input('ArmNodeSocketObject', 'Object')
|
self.add_input('ArmNodeSocketObject', 'Object')
|
||||||
self.add_input('NodeSocketVector', 'Euler Angles / Vector XYZ')
|
self.add_input('ArmNodeSocketRotation', 'Rotation')
|
||||||
self.add_input('NodeSocketFloat', 'Angle / W')
|
|
||||||
|
|
||||||
self.add_output('ArmNodeSocketAction', 'Out')
|
self.add_output('ArmNodeSocketAction', 'Out')
|
||||||
|
|
||||||
def on_property_update(self, context):
|
# def draw_buttons(self, context, layout):
|
||||||
"""called by the EnumProperty, used to update the node socket labels"""
|
# layout.prop(self, 'property0')
|
||||||
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):
|
# property0: EnumProperty(
|
||||||
layout.prop(self, 'property0')
|
# items = [('Local', 'Local', 'Local'),
|
||||||
|
# ('Global', 'Global', 'Global')],
|
||||||
property0: EnumProperty(
|
# name='', default='Local')
|
||||||
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)
|
|
||||||
|
|
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):
|
def init(self, context):
|
||||||
super(TransformNode, self).init(context)
|
super(TransformNode, self).init(context)
|
||||||
self.add_input('NodeSocketVector', 'Location')
|
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_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_idname = 'LNVectorToObjectOrientationNode'
|
||||||
bl_label = 'Vector to Object Orientation'
|
bl_label = 'Vector to Object Orientation'
|
||||||
arm_section = 'location'
|
arm_section = 'rotation'
|
||||||
arm_version = 1
|
arm_version = 1
|
||||||
|
|
||||||
def init(self, context):
|
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':
|
if inp_type == 'VECTOR':
|
||||||
return f'new armory.logicnode.VectorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]})'
|
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':
|
elif inp_type == 'RGBA':
|
||||||
return f'new armory.logicnode.ColorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]}, {default_value[3]})'
|
return f'new armory.logicnode.ColorNode(this, {default_value[0]}, {default_value[1]}, {default_value[2]}, {default_value[3]})'
|
||||||
elif inp_type == 'RGB':
|
elif inp_type == 'RGB':
|
||||||
|
|
Loading…
Reference in a new issue