package leenkx.logicnode; import iron.math.Vec4; import iron.math.Quat; import iron.math.Mat4; import iron.object.Object; class SetLookAtRotationNode extends LogicNode { public var property0: String; // Axis to align public var property1: String; // Use vector for target (true/false) 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; // Store the previous rotation for smooth interpolation var previousRotation: Quat = null; public function new(tree: LogicTree) { super(tree); previousRotation = new Quat(); } override function run(from: Int): Void { // Determine if we're using a vector or an object as source var useSourceVector: Bool = property2 == "true"; var objectToUse: Object = null; var objectLoc: Vec4 = null; if (useSourceVector) { // Use tree.object as the object to rotate objectToUse = tree.object; if (objectToUse == null) { runOutput(0); return; } // Get the source location directly objectLoc = inputs[1].get(); if (objectLoc == null) { runOutput(0); return; } } else { // Get the source object (or fallback to tree.object) objectToUse = (inputs.length > 1 && inputs[1] != null) ? inputs[1].get() : tree.object; if (objectToUse == null) { runOutput(0); return; } // 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 var useTargetVector: Bool = property1 == "true"; var targetLoc: Vec4 = null; if (useTargetVector) { // Get the target location directly targetLoc = inputs[2].get(); if (targetLoc == null) { runOutput(0); return; } } else { // Get the target object var targetObject: Object = inputs[2].get(); if (targetObject == null) { runOutput(0); return; } // 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 var direction = new Vec4( targetLoc.x - objectLoc.x, targetLoc.y - objectLoc.y, targetLoc.z - objectLoc.z ); direction.normalize(); // Calculate target rotation based on selected axis calculatedRotation = new Quat(); switch (property0) { case "X": calculatedRotation.fromTo(new Vec4(1, 0, 0), direction); case "-X": calculatedRotation.fromTo(new Vec4(-1, 0, 0), direction); case "Y": calculatedRotation.fromTo(new Vec4(0, 1, 0), direction); case "-Y": calculatedRotation.fromTo(new Vec4(0, -1, 0), direction); case "Z": calculatedRotation.fromTo(new Vec4(0, 0, 1), direction); case "-Z": calculatedRotation.fromTo(new Vec4(0, 0, -1), direction); } // If disable rotation on aligning axis is enabled, constrain the target rotation if (property4 == "true") { // Apply constraint to the target rotation BEFORE damping to avoid jiggling var eulerAngles = calculatedRotation.toEulerOrdered("XYZ"); // Set the rotation around the selected axis to 0 switch (property0) { case "X", "-X": eulerAngles.x = 0.0; case "Y", "-Y": eulerAngles.y = 0.0; case "Z", "-Z": eulerAngles.z = 0.0; } // Convert back to quaternion 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; // Try to get damping from input socket first (index 3), fallback to property if (inputs.length > 3 && inputs[3] != null) { var dampingInput: Dynamic = inputs[3].get(); if (dampingInput != null) { dampingValue = dampingInput; } } else { // Fallback to property for backward compatibility dampingValue = Std.parseFloat(property3); } if (dampingValue > 0.0) { // Create a fixed interpolation rate that never reaches exactly 1.0 // 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 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(currentLocalRot); invCurrent.x = -invCurrent.x; invCurrent.y = -invCurrent.y; invCurrent.z = -invCurrent.z; diffQuat.multquats(targetRotation, invCurrent); // Convert to axis-angle representation var axis = new Vec4(); var angle = diffQuat.toAxisAngle(axis); // Apply only a portion of this rotation (step) var partialAngle = angle * step; // Create partial rotation quaternion var partialRot = new Quat().fromAxisAngle(axis, partialAngle); // Apply this partial rotation to current local rotation var newLocalRot = new Quat(); newLocalRot.multquats(partialRot, currentLocalRot); // Apply the new local rotation objectToUse.transform.rot.setFrom(newLocalRot); } else { // No damping, apply instant rotation objectToUse.transform.rot.setFrom(targetRotation); } objectToUse.transform.buildMatrix(); runOutput(0); } // No output sockets needed - this node only performs actions }