merge upstream
This commit is contained in:
		
							
								
								
									
										232
									
								
								leenkx/Sources/leenkx/logicnode/MouseLookNode.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								leenkx/Sources/leenkx/logicnode/MouseLookNode.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,232 @@
 | 
				
			|||||||
 | 
					package leenkx.logicnode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import iron.math.Vec4;
 | 
				
			||||||
 | 
					import iron.system.Input;
 | 
				
			||||||
 | 
					import iron.object.Object;
 | 
				
			||||||
 | 
					import kha.System;
 | 
				
			||||||
 | 
					import kha.FastFloat;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MouseLookNode extends LogicNode {
 | 
				
			||||||
 | 
						// Note: This implementation works in degrees internally and converts to radians only when applying rotations
 | 
				
			||||||
 | 
						// Sub-pixel interpolation is always enabled for optimal precision
 | 
				
			||||||
 | 
						// Features: Resolution-adaptive scaling and precise low-sensitivity support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public var property0: String; // Front axis
 | 
				
			||||||
 | 
						public var property1: Bool; // Center Mouse
 | 
				
			||||||
 | 
						public var property2: Bool; // Invert X
 | 
				
			||||||
 | 
						public var property3: Bool; // Invert Y  
 | 
				
			||||||
 | 
						public var property4: Bool; // Cap Left/Right
 | 
				
			||||||
 | 
						public var property5: Bool; // Cap Up/Down
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// New strategy toggles
 | 
				
			||||||
 | 
						public var property6: Bool; // Resolution-Adaptive Scaling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Smoothing variables
 | 
				
			||||||
 | 
						var smoothX: FastFloat = 0.0;
 | 
				
			||||||
 | 
						var smoothY: FastFloat = 0.0;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Capping limits (in degrees)
 | 
				
			||||||
 | 
						var maxHorizontal: FastFloat = 180.0; // 180 degrees
 | 
				
			||||||
 | 
						var maxVertical: FastFloat = 90.0; // 90 degrees
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Current accumulated rotations for capping
 | 
				
			||||||
 | 
						var currentHorizontal: FastFloat = 0.0;
 | 
				
			||||||
 | 
						var currentVertical: FastFloat = 0.0;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Sub-pixel interpolation accumulators
 | 
				
			||||||
 | 
						var accumulatedHorizontalRotation: FastFloat = 0.0;
 | 
				
			||||||
 | 
						var accumulatedVerticalRotation: FastFloat = 0.0;
 | 
				
			||||||
 | 
						var minimumRotationThreshold: FastFloat = 0.01; // degrees (was 0.0001 radians)
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Frame rate independence removed - not applicable to mouse input
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// Resolution adaptive scaling
 | 
				
			||||||
 | 
						var baseResolutionWidth: FastFloat = 1920.0;
 | 
				
			||||||
 | 
						var baseResolutionHeight: FastFloat = 1080.0;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public function new(tree: LogicTree) {
 | 
				
			||||||
 | 
							super(tree);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						override function run(from: Int) {
 | 
				
			||||||
 | 
							var bodyObject: Object = inputs[1].get();
 | 
				
			||||||
 | 
							var headObject: Object = inputs[2].get();
 | 
				
			||||||
 | 
							var sensitivity: FastFloat = inputs[3].get();
 | 
				
			||||||
 | 
							var smoothing: FastFloat = inputs[4].get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (bodyObject == null) {
 | 
				
			||||||
 | 
								runOutput(0);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var mouse = Input.getMouse();
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Handle mouse centering/locking
 | 
				
			||||||
 | 
							if (property1) {
 | 
				
			||||||
 | 
								if (mouse.started() && !mouse.locked) {
 | 
				
			||||||
 | 
									mouse.lock();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Only process if mouse is active
 | 
				
			||||||
 | 
							if (!mouse.locked && !mouse.down()) {
 | 
				
			||||||
 | 
								runOutput(0);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get mouse movement deltas
 | 
				
			||||||
 | 
							var deltaX: FastFloat = mouse.movementX;
 | 
				
			||||||
 | 
							var deltaY: FastFloat = mouse.movementY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Note: Sensitivity will be applied later to preserve precision for small movements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Apply inversion
 | 
				
			||||||
 | 
							if (property2) deltaX = -deltaX;
 | 
				
			||||||
 | 
							if (property3) deltaY = -deltaY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Strategy 1: Resolution-Adaptive Scaling
 | 
				
			||||||
 | 
							var resolutionMultiplier: FastFloat = 1.0;
 | 
				
			||||||
 | 
							if (property6) {
 | 
				
			||||||
 | 
								var currentWidth = System.windowWidth();
 | 
				
			||||||
 | 
								var currentHeight = System.windowHeight();
 | 
				
			||||||
 | 
								resolutionMultiplier = (currentWidth / baseResolutionWidth) * (currentHeight / baseResolutionHeight);
 | 
				
			||||||
 | 
								resolutionMultiplier = Math.sqrt(resolutionMultiplier); // Take square root to avoid over-scaling
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Frame Rate Independence disabled for mouse input - mouse deltas are inherently frame-rate independent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Apply smoothing
 | 
				
			||||||
 | 
							if (smoothing > 0.0) {
 | 
				
			||||||
 | 
								var smoothFactor = 1.0 - Math.min(smoothing, 0.99); // Prevent complete smoothing
 | 
				
			||||||
 | 
								smoothX = smoothX * smoothing + deltaX * smoothFactor;
 | 
				
			||||||
 | 
								smoothY = smoothY * smoothing + deltaY * smoothFactor;
 | 
				
			||||||
 | 
								deltaX = smoothX;
 | 
				
			||||||
 | 
								deltaY = smoothY;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Determine rotation axes based on front axis setting
 | 
				
			||||||
 | 
							var horizontalAxis = new Vec4();
 | 
				
			||||||
 | 
							var verticalAxis = new Vec4();
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							switch (property0) {
 | 
				
			||||||
 | 
								case "X": // X is front
 | 
				
			||||||
 | 
									horizontalAxis.set(0, 0, 1); // Z axis for horizontal (yaw)
 | 
				
			||||||
 | 
									verticalAxis.set(0, 1, 0);   // Y axis for vertical (pitch)
 | 
				
			||||||
 | 
								case "Y": // Y is front (default)
 | 
				
			||||||
 | 
									#if lnx_yaxisup
 | 
				
			||||||
 | 
									horizontalAxis.set(0, 0, 1); // Z axis for horizontal (yaw)
 | 
				
			||||||
 | 
									verticalAxis.set(1, 0, 0);   // X axis for vertical (pitch)
 | 
				
			||||||
 | 
									#else
 | 
				
			||||||
 | 
									horizontalAxis.set(0, 0, 1); // Z axis for horizontal (yaw)  
 | 
				
			||||||
 | 
									verticalAxis.set(1, 0, 0);   // X axis for vertical (pitch)
 | 
				
			||||||
 | 
									#end
 | 
				
			||||||
 | 
								case "Z": // Z is front
 | 
				
			||||||
 | 
									horizontalAxis.set(0, 1, 0); // Y axis for horizontal (yaw)
 | 
				
			||||||
 | 
									verticalAxis.set(1, 0, 0);   // X axis for vertical (pitch)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Base scaling
 | 
				
			||||||
 | 
							var baseScale: FastFloat = 1500.0;
 | 
				
			||||||
 | 
							var finalScale = baseScale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Apply resolution scaling
 | 
				
			||||||
 | 
							if (property6) {
 | 
				
			||||||
 | 
								finalScale *= resolutionMultiplier;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Apply sensitivity scaling after all enhancement strategies to preserve precision
 | 
				
			||||||
 | 
							deltaX *= sensitivity;
 | 
				
			||||||
 | 
							deltaY *= sensitivity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Calculate rotation amounts (in degrees)
 | 
				
			||||||
 | 
							var horizontalRotation: FastFloat = (-deltaX / finalScale) * 180.0 / Math.PI;
 | 
				
			||||||
 | 
							var verticalRotation: FastFloat = (-deltaY / finalScale) * 180.0 / Math.PI;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Note: Frame rate independence removed for mouse input as mouse deltas
 | 
				
			||||||
 | 
							// are already frame-rate independent by nature. Mouse input represents
 | 
				
			||||||
 | 
							// instantaneous user intent, not time-based movement.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Strategy 2: Sub-Pixel Interpolation (always enabled)
 | 
				
			||||||
 | 
							accumulatedHorizontalRotation += horizontalRotation;
 | 
				
			||||||
 | 
							accumulatedVerticalRotation += verticalRotation;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							// Only apply rotation if accumulated amount exceeds threshold
 | 
				
			||||||
 | 
							if (Math.abs(accumulatedHorizontalRotation) >= minimumRotationThreshold) {
 | 
				
			||||||
 | 
								horizontalRotation = accumulatedHorizontalRotation;
 | 
				
			||||||
 | 
								accumulatedHorizontalRotation = 0.0;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								horizontalRotation = 0.0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							if (Math.abs(accumulatedVerticalRotation) >= minimumRotationThreshold) {
 | 
				
			||||||
 | 
								verticalRotation = accumulatedVerticalRotation;
 | 
				
			||||||
 | 
								accumulatedVerticalRotation = 0.0;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								verticalRotation = 0.0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Apply capping constraints
 | 
				
			||||||
 | 
							if (property4) { // Cap Left/Right
 | 
				
			||||||
 | 
								currentHorizontal += horizontalRotation;
 | 
				
			||||||
 | 
								if (currentHorizontal > maxHorizontal) {
 | 
				
			||||||
 | 
									horizontalRotation -= (currentHorizontal - maxHorizontal);
 | 
				
			||||||
 | 
									currentHorizontal = maxHorizontal;
 | 
				
			||||||
 | 
								} else if (currentHorizontal < -maxHorizontal) {
 | 
				
			||||||
 | 
									horizontalRotation -= (currentHorizontal + maxHorizontal);
 | 
				
			||||||
 | 
									currentHorizontal = -maxHorizontal;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (property5) { // Cap Up/Down
 | 
				
			||||||
 | 
								currentVertical += verticalRotation;
 | 
				
			||||||
 | 
								if (currentVertical > maxVertical) {
 | 
				
			||||||
 | 
									verticalRotation -= (currentVertical - maxVertical);
 | 
				
			||||||
 | 
									currentVertical = maxVertical;
 | 
				
			||||||
 | 
								} else if (currentVertical < -maxVertical) {
 | 
				
			||||||
 | 
									verticalRotation -= (currentVertical + maxVertical);
 | 
				
			||||||
 | 
									currentVertical = -maxVertical;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Apply horizontal rotation to body (yaw)
 | 
				
			||||||
 | 
							if (Math.abs(horizontalRotation) > 0.01) { // 0.01 degrees threshold
 | 
				
			||||||
 | 
								bodyObject.transform.rotate(horizontalAxis, horizontalRotation * Math.PI / 180.0); // Convert degrees to radians
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								// Sync physics if needed
 | 
				
			||||||
 | 
								#if lnx_physics
 | 
				
			||||||
 | 
								var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody);
 | 
				
			||||||
 | 
								if (rigidBody != null) rigidBody.syncTransform();
 | 
				
			||||||
 | 
								#end
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Apply vertical rotation to head (pitch) if head object is provided
 | 
				
			||||||
 | 
							if (headObject != null && Math.abs(verticalRotation) > 0.01) { // 0.01 degrees threshold
 | 
				
			||||||
 | 
								// For head rotation, use the head's local coordinate system
 | 
				
			||||||
 | 
								var headVerticalAxis = headObject.transform.world.right();
 | 
				
			||||||
 | 
								headObject.transform.rotate(headVerticalAxis, verticalRotation * Math.PI / 180.0); // Convert degrees to radians
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
								// Sync physics if needed
 | 
				
			||||||
 | 
								#if lnx_physics
 | 
				
			||||||
 | 
								var headRigidBody = headObject.getTrait(leenkx.trait.physics.RigidBody);
 | 
				
			||||||
 | 
								if (headRigidBody != null) headRigidBody.syncTransform();
 | 
				
			||||||
 | 
								#end
 | 
				
			||||||
 | 
							} else if (headObject == null) {
 | 
				
			||||||
 | 
								// If no head object, apply vertical rotation to body as well
 | 
				
			||||||
 | 
								if (Math.abs(verticalRotation) > 0.01) { // 0.01 degrees threshold
 | 
				
			||||||
 | 
									bodyObject.transform.rotate(verticalAxis, verticalRotation * Math.PI / 180.0); // Convert degrees to radians
 | 
				
			||||||
 | 
									
 | 
				
			||||||
 | 
									// Sync physics if needed
 | 
				
			||||||
 | 
									#if lnx_physics
 | 
				
			||||||
 | 
									var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody);
 | 
				
			||||||
 | 
									if (rigidBody != null) rigidBody.syncTransform();
 | 
				
			||||||
 | 
									#end
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							runOutput(0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					} 
 | 
				
			||||||
							
								
								
									
										21
									
								
								leenkx/Sources/leenkx/logicnode/SetLightShadowNode.hx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								leenkx/Sources/leenkx/logicnode/SetLightShadowNode.hx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					package leenkx.logicnode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import iron.object.LightObject;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SetLightShadowNode extends LogicNode {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public function new(tree: LogicTree) {
 | 
				
			||||||
 | 
							super(tree);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						override function run(from: Int) {
 | 
				
			||||||
 | 
							var light: LightObject = inputs[1].get();
 | 
				
			||||||
 | 
							var shadow: Bool = inputs[2].get();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (light == null) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							light.data.raw.cast_shadow = shadow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							runOutput(0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -12,6 +12,7 @@ class SetLookAtRotationNode extends LogicNode {
 | 
				
			|||||||
	public var property2: String; // Use vector for source (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 property3: String; // Damping value (backward compatibility, now input socket)
 | 
				
			||||||
	public var property4: String; // Disable rotation on aligning axis (true/false)
 | 
						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
 | 
						// Store the calculated rotation for output
 | 
				
			||||||
	var calculatedRotation: Quat = null;
 | 
						var calculatedRotation: Quat = null;
 | 
				
			||||||
@ -51,8 +52,8 @@ class SetLookAtRotationNode extends LogicNode {
 | 
				
			|||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Get source object's position
 | 
								// Get source object's WORLD position (important for child objects)
 | 
				
			||||||
			objectLoc = objectToUse.transform.loc;
 | 
								objectLoc = new Vec4(objectToUse.transform.worldx(), objectToUse.transform.worldy(), objectToUse.transform.worldz());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Determine if we're using a vector or an object as target
 | 
							// Determine if we're using a vector or an object as target
 | 
				
			||||||
@ -74,8 +75,8 @@ class SetLookAtRotationNode extends LogicNode {
 | 
				
			|||||||
				return;
 | 
									return;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			// Get target object's position
 | 
								// Get target object's WORLD position (important for child objects)
 | 
				
			||||||
			targetLoc = targetObject.transform.loc;
 | 
								targetLoc = new Vec4(targetObject.transform.worldx(), targetObject.transform.worldy(), targetObject.transform.worldz());
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		// Calculate direction to target
 | 
							// Calculate direction to target
 | 
				
			||||||
@ -122,6 +123,28 @@ class SetLookAtRotationNode extends LogicNode {
 | 
				
			|||||||
			calculatedRotation.fromEulerOrdered(eulerAngles, "XYZ");
 | 
								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
 | 
							// Apply rotation with damping
 | 
				
			||||||
		var dampingValue: Float = 0.0;
 | 
							var dampingValue: Float = 0.0;
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
@ -141,17 +164,17 @@ class SetLookAtRotationNode extends LogicNode {
 | 
				
			|||||||
			// Higher damping = slower rotation (smaller step)
 | 
								// Higher damping = slower rotation (smaller step)
 | 
				
			||||||
			var step = Math.max(0.001, (1.0 - dampingValue) * 0.2); // 0.001 to 0.2 range
 | 
								var step = Math.max(0.001, (1.0 - dampingValue) * 0.2); // 0.001 to 0.2 range
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			// Get current rotation as quaternion
 | 
								// Get current local rotation as quaternion
 | 
				
			||||||
			var currentRot = new Quat().setFrom(objectToUse.transform.rot);
 | 
								var currentLocalRot = new Quat().setFrom(objectToUse.transform.rot);
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			// Calculate the difference between current and target rotation
 | 
								// Calculate the difference between current and target rotation
 | 
				
			||||||
			var diffQuat = new Quat();
 | 
								var diffQuat = new Quat();
 | 
				
			||||||
			// q1 * inverse(q2) gives the rotation from q2 to q1
 | 
								// 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.x = -invCurrent.x;
 | 
				
			||||||
			invCurrent.y = -invCurrent.y;
 | 
								invCurrent.y = -invCurrent.y;
 | 
				
			||||||
			invCurrent.z = -invCurrent.z;
 | 
								invCurrent.z = -invCurrent.z;
 | 
				
			||||||
			diffQuat.multquats(calculatedRotation, invCurrent);
 | 
								diffQuat.multquats(targetRotation, invCurrent);
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			// Convert to axis-angle representation
 | 
								// Convert to axis-angle representation
 | 
				
			||||||
			var axis = new Vec4();
 | 
								var axis = new Vec4();
 | 
				
			||||||
@ -163,15 +186,15 @@ class SetLookAtRotationNode extends LogicNode {
 | 
				
			|||||||
			// Create partial rotation quaternion
 | 
								// Create partial rotation quaternion
 | 
				
			||||||
			var partialRot = new Quat().fromAxisAngle(axis, partialAngle);
 | 
								var partialRot = new Quat().fromAxisAngle(axis, partialAngle);
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			// Apply this partial rotation to current
 | 
								// Apply this partial rotation to current local rotation
 | 
				
			||||||
			var newRot = new Quat();
 | 
								var newLocalRot = new Quat();
 | 
				
			||||||
			newRot.multquats(partialRot, currentRot);
 | 
								newLocalRot.multquats(partialRot, currentLocalRot);
 | 
				
			||||||
			
 | 
								
 | 
				
			||||||
			// Apply the new rotation
 | 
								// Apply the new local rotation
 | 
				
			||||||
			objectToUse.transform.rot.setFrom(newRot);
 | 
								objectToUse.transform.rot.setFrom(newLocalRot);
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			// No damping, apply instant rotation
 | 
								// No damping, apply instant rotation
 | 
				
			||||||
			objectToUse.transform.rot.setFrom(calculatedRotation);
 | 
								objectToUse.transform.rot.setFrom(targetRotation);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		objectToUse.transform.buildMatrix();
 | 
							objectToUse.transform.buildMatrix();
 | 
				
			||||||
@ -179,12 +202,5 @@ class SetLookAtRotationNode extends LogicNode {
 | 
				
			|||||||
		runOutput(0);
 | 
							runOutput(0);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Getter method for output sockets
 | 
						// No output sockets needed - this node only performs actions
 | 
				
			||||||
	override function get(from: Int): Dynamic {
 | 
					 | 
				
			||||||
		// Output index 1 is the rotation socket (global rotation)
 | 
					 | 
				
			||||||
		if (from == 1) {
 | 
					 | 
				
			||||||
			return calculatedRotation;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return null;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -280,6 +280,10 @@ class DebugConsole extends Trait {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
					function drawObjectNameInList(object: iron.object.Object, selected: Bool) {
 | 
										function drawObjectNameInList(object: iron.object.Object, selected: Bool) {
 | 
				
			||||||
						var _y = ui._y;
 | 
											var _y = ui._y;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (object.parent.name == 'Root')
 | 
				
			||||||
 | 
												ui.text(object.uid+'_'+object.name+' ('+iron.Scene.active.raw.world_ref+')');
 | 
				
			||||||
 | 
											else
 | 
				
			||||||
							ui.text(object.uid+'_'+object.name);
 | 
												ui.text(object.uid+'_'+object.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						if (object == iron.Scene.active.camera) {
 | 
											if (object == iron.Scene.active.camera) {
 | 
				
			||||||
 | 
				
			|||||||
@ -543,7 +543,9 @@ class LeenkxExporter:
 | 
				
			|||||||
        if psys.settings.instance_object is None or psys.settings.render_type != 'OBJECT' or not psys.settings.instance_object.lnx_export:
 | 
					        if psys.settings.instance_object is None or psys.settings.render_type != 'OBJECT' or not psys.settings.instance_object.lnx_export:
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
           
 | 
					           
 | 
				
			||||||
        for mod in bpy.data.objects[out_object['name']].modifiers:
 | 
					        for obj in bpy.data.objects:
 | 
				
			||||||
 | 
					            if obj.name == out_object['name']:
 | 
				
			||||||
 | 
					                for mod in obj.modifiers:
 | 
				
			||||||
                    if mod.type == 'PARTICLE_SYSTEM':
 | 
					                    if mod.type == 'PARTICLE_SYSTEM':
 | 
				
			||||||
                        if mod.particle_system.name == psys.name:
 | 
					                        if mod.particle_system.name == psys.name:
 | 
				
			||||||
                            if not mod.show_render:
 | 
					                            if not mod.show_render:
 | 
				
			||||||
@ -636,7 +638,10 @@ class LeenkxExporter:
 | 
				
			|||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for slot in bobject.material_slots:
 | 
					            for slot in bobject.material_slots:
 | 
				
			||||||
                if slot.material is None or slot.material.library is not None:
 | 
					                if slot.material is None:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                if slot.material.library is not None:
 | 
				
			||||||
 | 
					                    slot.material.lnx_particle_flag = True
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
                if slot.material.name.endswith(variant_suffix):
 | 
					                if slot.material.name.endswith(variant_suffix):
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										101
									
								
								leenkx/blender/lnx/logicnode/input/LN_mouse_look.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								leenkx/blender/lnx/logicnode/input/LN_mouse_look.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					from lnx.logicnode.lnx_nodes import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MouseLookNode(LnxLogicTreeNode):
 | 
				
			||||||
 | 
					    """Controls object rotation based on mouse movement for FPS-style camera control.
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Features:
 | 
				
			||||||
 | 
					    - Sub-pixel interpolation (always enabled) for optimal precision and smooth low-sensitivity movement
 | 
				
			||||||
 | 
					    - Resolution-adaptive scaling for consistent feel across different screen resolutions
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    bl_idname = 'LNMouseLookNode'
 | 
				
			||||||
 | 
					    bl_label = 'Mouse Look'
 | 
				
			||||||
 | 
					    lnx_section = 'mouse'
 | 
				
			||||||
 | 
					    lnx_version = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Front axis property
 | 
				
			||||||
 | 
					    property0: HaxeEnumProperty(
 | 
				
			||||||
 | 
					        'property0',
 | 
				
			||||||
 | 
					        items=[('X', 'X Axis', 'X Axis as front'),
 | 
				
			||||||
 | 
					               ('Y', 'Y Axis', 'Y Axis as front'),
 | 
				
			||||||
 | 
					               ('Z', 'Z Axis', 'Z Axis as front')],
 | 
				
			||||||
 | 
					        name='Front', default='Y')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Hide Locked property
 | 
				
			||||||
 | 
					    property1: HaxeBoolProperty(
 | 
				
			||||||
 | 
					        'property1',
 | 
				
			||||||
 | 
					        name='Hide Locked',
 | 
				
			||||||
 | 
					        description='Automatically center and lock the mouse cursor',
 | 
				
			||||||
 | 
					        default=True)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Invert X property
 | 
				
			||||||
 | 
					    property2: HaxeBoolProperty(
 | 
				
			||||||
 | 
					        'property2',
 | 
				
			||||||
 | 
					        name='Invert X',
 | 
				
			||||||
 | 
					        description='Invert horizontal mouse movement',
 | 
				
			||||||
 | 
					        default=False)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Invert Y property  
 | 
				
			||||||
 | 
					    property3: HaxeBoolProperty(
 | 
				
			||||||
 | 
					        'property3',
 | 
				
			||||||
 | 
					        name='Invert Y',
 | 
				
			||||||
 | 
					        description='Invert vertical mouse movement',
 | 
				
			||||||
 | 
					        default=False)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Cap Left/Right property
 | 
				
			||||||
 | 
					    property4: HaxeBoolProperty(
 | 
				
			||||||
 | 
					        'property4',
 | 
				
			||||||
 | 
					        name='Cap Left / Right',
 | 
				
			||||||
 | 
					        description='Limit horizontal rotation',
 | 
				
			||||||
 | 
					        default=False)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Cap Up/Down property
 | 
				
			||||||
 | 
					    property5: HaxeBoolProperty(
 | 
				
			||||||
 | 
					        'property5',
 | 
				
			||||||
 | 
					        name='Cap Up / Down',
 | 
				
			||||||
 | 
					        description='Limit vertical rotation',
 | 
				
			||||||
 | 
					        default=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Strategy toggles
 | 
				
			||||||
 | 
					    property6: HaxeBoolProperty(
 | 
				
			||||||
 | 
					        'property6',
 | 
				
			||||||
 | 
					        name='Resolution Adaptive',
 | 
				
			||||||
 | 
					        description='Scale sensitivity based on screen resolution',
 | 
				
			||||||
 | 
					        default=False)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def lnx_init(self, context):
 | 
				
			||||||
 | 
					        self.add_input('LnxNodeSocketAction', 'In')
 | 
				
			||||||
 | 
					        self.add_input('LnxNodeSocketObject', 'Body')
 | 
				
			||||||
 | 
					        self.add_input('LnxNodeSocketObject', 'Head')
 | 
				
			||||||
 | 
					        self.add_input('LnxFloatSocket', 'Sensitivity', default_value=0.5)
 | 
				
			||||||
 | 
					        self.add_input('LnxFloatSocket', 'Smoothing', default_value=0.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.add_output('LnxNodeSocketAction', 'Out')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def draw_buttons(self, context, layout):
 | 
				
			||||||
 | 
					        layout.prop(self, 'property0', text='Front')
 | 
				
			||||||
 | 
					        layout.prop(self, 'property1', text='Hide Locked')
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Invert XY section
 | 
				
			||||||
 | 
					        col = layout.column(align=True)
 | 
				
			||||||
 | 
					        col.label(text="Invert XY:")
 | 
				
			||||||
 | 
					        row = col.row(align=True)
 | 
				
			||||||
 | 
					        row.prop(self, 'property2', text='X', toggle=True)
 | 
				
			||||||
 | 
					        row.prop(self, 'property3', text='Y', toggle=True)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Cap rotations section
 | 
				
			||||||
 | 
					        col = layout.column(align=True)
 | 
				
			||||||
 | 
					        col.prop(self, 'property4', text='Cap Left / Right')
 | 
				
			||||||
 | 
					        col.prop(self, 'property5', text='Cap Up / Down')
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Separator
 | 
				
			||||||
 | 
					        layout.separator()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Enhancement strategies section
 | 
				
			||||||
 | 
					        col = layout.column(align=True)
 | 
				
			||||||
 | 
					        col.label(text="Enhancement Strategies:")
 | 
				
			||||||
 | 
					        col.prop(self, 'property6', text='Resolution Adaptive') 
 | 
				
			||||||
							
								
								
									
										14
									
								
								leenkx/blender/lnx/logicnode/light/LN_set_light_shadow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								leenkx/blender/lnx/logicnode/light/LN_set_light_shadow.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					from lnx.logicnode.lnx_nodes import *
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SetLightShadowNode(LnxLogicTreeNode):
 | 
				
			||||||
 | 
					    """Sets the shadow boolean of the given light."""
 | 
				
			||||||
 | 
					    bl_idname = 'LNSetLightShadowNode'
 | 
				
			||||||
 | 
					    bl_label = 'Set Light Shadow'
 | 
				
			||||||
 | 
					    lnx_version = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def lnx_init(self, context):
 | 
				
			||||||
 | 
					        self.add_input('LnxNodeSocketAction', 'In')
 | 
				
			||||||
 | 
					        self.add_input('LnxNodeSocketObject', 'Light')
 | 
				
			||||||
 | 
					        self.add_input('LnxBoolSocket', 'Shadow')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.add_output('LnxNodeSocketAction', 'Out')
 | 
				
			||||||
@ -29,6 +29,8 @@ class SetLookAtRotationNode(LnxLogicTreeNode):
 | 
				
			|||||||
        update=lambda self, context: self.update_sockets(context)
 | 
					        update=lambda self, context: self.update_sockets(context)
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    damping: bpy.props.FloatProperty(
 | 
					    damping: bpy.props.FloatProperty(
 | 
				
			||||||
        name='Damping',
 | 
					        name='Damping',
 | 
				
			||||||
        description='Amount of damping for rotation (0.0 = instant, 1.0 = no movement)',
 | 
					        description='Amount of damping for rotation (0.0 = instant, 1.0 = no movement)',
 | 
				
			||||||
@ -74,6 +76,12 @@ class SetLookAtRotationNode(LnxLogicTreeNode):
 | 
				
			|||||||
                 ('false', 'False', 'False')],
 | 
					                 ('false', 'False', 'False')],
 | 
				
			||||||
        name='Disable Rotation on Aligning Axis', default='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):
 | 
					    def lnx_init(self, context):
 | 
				
			||||||
        # Add inputs in standard order
 | 
					        # Add inputs in standard order
 | 
				
			||||||
        self.inputs.new('LnxNodeSocketAction', 'In')
 | 
					        self.inputs.new('LnxNodeSocketAction', 'In')
 | 
				
			||||||
@ -90,8 +98,6 @@ class SetLookAtRotationNode(LnxLogicTreeNode):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # Add outputs
 | 
					        # Add outputs
 | 
				
			||||||
        self.add_output('LnxNodeSocketAction', 'Out')
 | 
					        self.add_output('LnxNodeSocketAction', 'Out')
 | 
				
			||||||
        # Add rotation output socket
 | 
					 | 
				
			||||||
        self.add_output('LnxRotationSocket', 'Rotation')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def draw_buttons(self, context, layout):
 | 
					    def draw_buttons(self, context, layout):
 | 
				
			||||||
        # 1. Axis Selector
 | 
					        # 1. Axis Selector
 | 
				
			||||||
@ -114,6 +120,7 @@ class SetLookAtRotationNode(LnxLogicTreeNode):
 | 
				
			|||||||
        self.property2 = 'true' if self.use_source_vector else 'false'
 | 
					        self.property2 = 'true' if self.use_source_vector else 'false'
 | 
				
			||||||
        self.property3 = str(self.damping) # Keep for backward compatibility
 | 
					        self.property3 = str(self.damping) # Keep for backward compatibility
 | 
				
			||||||
        self.property4 = 'true' if self.disable_rotation_on_align_axis else 'false'
 | 
					        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
 | 
					        # Store current object references before changing sockets
 | 
				
			||||||
        self.save_object_references()
 | 
					        self.save_object_references()
 | 
				
			||||||
 | 
				
			|||||||
@ -216,6 +216,7 @@ def parse_shader(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> Tuple[st
 | 
				
			|||||||
        'ADD_SHADER',
 | 
					        'ADD_SHADER',
 | 
				
			||||||
        'BSDF_PRINCIPLED',
 | 
					        'BSDF_PRINCIPLED',
 | 
				
			||||||
        'BSDF_DIFFUSE',
 | 
					        'BSDF_DIFFUSE',
 | 
				
			||||||
 | 
					        'DIFFUSE_BSDF',
 | 
				
			||||||
        'BSDF_GLOSSY',
 | 
					        'BSDF_GLOSSY',
 | 
				
			||||||
        'BSDF_SHEEN',
 | 
					        'BSDF_SHEEN',
 | 
				
			||||||
        'AMBIENT_OCCLUSION',
 | 
					        'AMBIENT_OCCLUSION',
 | 
				
			||||||
 | 
				
			|||||||
@ -153,6 +153,7 @@ ALL_NODES: dict[str, MaterialNodeMeta] = {
 | 
				
			|||||||
    'AMBIENT_OCCLUSION': MaterialNodeMeta(parse_func=nodes_shader.parse_ambientocclusion),
 | 
					    'AMBIENT_OCCLUSION': MaterialNodeMeta(parse_func=nodes_shader.parse_ambientocclusion),
 | 
				
			||||||
    'BSDF_ANISOTROPIC': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfanisotropic),
 | 
					    'BSDF_ANISOTROPIC': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfanisotropic),
 | 
				
			||||||
    'BSDF_DIFFUSE': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfdiffuse),
 | 
					    'BSDF_DIFFUSE': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfdiffuse),
 | 
				
			||||||
 | 
					    'DIFFUSE_BSDF': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfdiffuse),
 | 
				
			||||||
    'BSDF_GLASS': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfglass),
 | 
					    'BSDF_GLASS': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfglass),
 | 
				
			||||||
    'BSDF_PRINCIPLED': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfprincipled),
 | 
					    'BSDF_PRINCIPLED': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdfprincipled),
 | 
				
			||||||
    'BSDF_TRANSLUCENT': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftranslucent),
 | 
					    'BSDF_TRANSLUCENT': MaterialNodeMeta(parse_func=nodes_shader.parse_bsdftranslucent),
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user