From f5c9e70d1acf7f26d1edf060c51997c657cc77c6 Mon Sep 17 00:00:00 2001 From: wuaieyo Date: Thu, 26 Jun 2025 03:05:11 +0200 Subject: [PATCH] 1. added local rotation so that if the source object is child then it would still align to the target object. 2. removed rotation output socket since is not really needed. --- .../leenkx/logicnode/SetLookAtRotationNode.hx | 60 ++++++++++++------- .../transform/LN_set_look_at_rotation.py | 11 +++- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/leenkx/Sources/leenkx/logicnode/SetLookAtRotationNode.hx b/leenkx/Sources/leenkx/logicnode/SetLookAtRotationNode.hx index 15fb2d7..d0f60b2 100644 --- a/leenkx/Sources/leenkx/logicnode/SetLookAtRotationNode.hx +++ b/leenkx/Sources/leenkx/logicnode/SetLookAtRotationNode.hx @@ -12,6 +12,7 @@ class SetLookAtRotationNode extends LogicNode { public var property2: String; // Use vector for source (true/false) public var property3: String; // Damping value (backward compatibility, now input socket) public var property4: String; // Disable rotation on aligning axis (true/false) + public var property5: String; // Use local space (true/false) // Store the calculated rotation for output var calculatedRotation: Quat = null; @@ -51,8 +52,8 @@ class SetLookAtRotationNode extends LogicNode { return; } - // Get source object's position - objectLoc = objectToUse.transform.loc; + // Get source object's WORLD position (important for child objects) + objectLoc = new Vec4(objectToUse.transform.worldx(), objectToUse.transform.worldy(), objectToUse.transform.worldz()); } // Determine if we're using a vector or an object as target @@ -74,8 +75,8 @@ class SetLookAtRotationNode extends LogicNode { return; } - // Get target object's position - targetLoc = targetObject.transform.loc; + // Get target object's WORLD position (important for child objects) + targetLoc = new Vec4(targetObject.transform.worldx(), targetObject.transform.worldy(), targetObject.transform.worldz()); } // Calculate direction to target @@ -122,6 +123,28 @@ class SetLookAtRotationNode extends LogicNode { calculatedRotation.fromEulerOrdered(eulerAngles, "XYZ"); } + // Convert world rotation to local rotation if local space is enabled and object has a parent + var targetRotation = new Quat(); + if (property5 == "true" && objectToUse.parent != null) { + // Get parent's world rotation + var parentWorldLoc = new Vec4(); + var parentWorldRot = new Quat(); + var parentWorldScale = new Vec4(); + objectToUse.parent.transform.world.decompose(parentWorldLoc, parentWorldRot, parentWorldScale); + + // Convert world rotation to local space by removing parent's rotation influence + // local_rotation = inverse(parent_world_rotation) * world_rotation + var invParentRot = new Quat().setFrom(parentWorldRot); + invParentRot.x = -invParentRot.x; + invParentRot.y = -invParentRot.y; + invParentRot.z = -invParentRot.z; + + targetRotation.multquats(invParentRot, calculatedRotation); + } else { + // No local space conversion needed, use world rotation directly + targetRotation.setFrom(calculatedRotation); + } + // Apply rotation with damping var dampingValue: Float = 0.0; @@ -141,17 +164,17 @@ class SetLookAtRotationNode extends LogicNode { // Higher damping = slower rotation (smaller step) var step = Math.max(0.001, (1.0 - dampingValue) * 0.2); // 0.001 to 0.2 range - // Get current rotation as quaternion - var currentRot = new Quat().setFrom(objectToUse.transform.rot); + // Get current local rotation as quaternion + var currentLocalRot = new Quat().setFrom(objectToUse.transform.rot); // Calculate the difference between current and target rotation var diffQuat = new Quat(); // q1 * inverse(q2) gives the rotation from q2 to q1 - var invCurrent = new Quat().setFrom(currentRot); + var invCurrent = new Quat().setFrom(currentLocalRot); invCurrent.x = -invCurrent.x; invCurrent.y = -invCurrent.y; invCurrent.z = -invCurrent.z; - diffQuat.multquats(calculatedRotation, invCurrent); + diffQuat.multquats(targetRotation, invCurrent); // Convert to axis-angle representation var axis = new Vec4(); @@ -163,15 +186,15 @@ class SetLookAtRotationNode extends LogicNode { // Create partial rotation quaternion var partialRot = new Quat().fromAxisAngle(axis, partialAngle); - // Apply this partial rotation to current - var newRot = new Quat(); - newRot.multquats(partialRot, currentRot); + // Apply this partial rotation to current local rotation + var newLocalRot = new Quat(); + newLocalRot.multquats(partialRot, currentLocalRot); - // Apply the new rotation - objectToUse.transform.rot.setFrom(newRot); + // Apply the new local rotation + objectToUse.transform.rot.setFrom(newLocalRot); } else { // No damping, apply instant rotation - objectToUse.transform.rot.setFrom(calculatedRotation); + objectToUse.transform.rot.setFrom(targetRotation); } objectToUse.transform.buildMatrix(); @@ -179,12 +202,5 @@ class SetLookAtRotationNode extends LogicNode { runOutput(0); } - // Getter method for output sockets - override function get(from: Int): Dynamic { - // Output index 1 is the rotation socket (global rotation) - if (from == 1) { - return calculatedRotation; - } - return null; - } + // No output sockets needed - this node only performs actions } diff --git a/leenkx/blender/lnx/logicnode/transform/LN_set_look_at_rotation.py b/leenkx/blender/lnx/logicnode/transform/LN_set_look_at_rotation.py index b2d76e8..4f8fbd2 100644 --- a/leenkx/blender/lnx/logicnode/transform/LN_set_look_at_rotation.py +++ b/leenkx/blender/lnx/logicnode/transform/LN_set_look_at_rotation.py @@ -29,6 +29,8 @@ class SetLookAtRotationNode(LnxLogicTreeNode): update=lambda self, context: self.update_sockets(context) ) + + damping: bpy.props.FloatProperty( name='Damping', description='Amount of damping for rotation (0.0 = instant, 1.0 = no movement)', @@ -74,6 +76,12 @@ class SetLookAtRotationNode(LnxLogicTreeNode): ('false', 'False', 'False')], name='Disable Rotation on Aligning Axis', default='false') + property5: HaxeEnumProperty( + 'property5', + items = [('true', 'True', 'True'), + ('false', 'False', 'False')], + name='Use Local Space', default='false') + def lnx_init(self, context): # Add inputs in standard order self.inputs.new('LnxNodeSocketAction', 'In') @@ -90,8 +98,6 @@ class SetLookAtRotationNode(LnxLogicTreeNode): # Add outputs self.add_output('LnxNodeSocketAction', 'Out') - # Add rotation output socket - self.add_output('LnxRotationSocket', 'Rotation') def draw_buttons(self, context, layout): # 1. Axis Selector @@ -114,6 +120,7 @@ class SetLookAtRotationNode(LnxLogicTreeNode): self.property2 = 'true' if self.use_source_vector else 'false' self.property3 = str(self.damping) # Keep for backward compatibility self.property4 = 'true' if self.disable_rotation_on_align_axis else 'false' + self.property5 = 'true' # Always use local space functionality # Store current object references before changing sockets self.save_object_references()