From 57f0e937d0312eead3eed3ebf41030819ace793d Mon Sep 17 00:00:00 2001 From: wuaieyo Date: Tue, 8 Jul 2025 22:48:16 +0200 Subject: [PATCH] fixed properties numbering, comments and LNXfactor to LNXFloat --- .../Sources/leenkx/logicnode/MouseLookNode.hx | 148 +++++++++++---- .../lnx/logicnode/input/LN_mouse_look.py | 169 ++++++++++++------ 2 files changed, 227 insertions(+), 90 deletions(-) diff --git a/leenkx/Sources/leenkx/logicnode/MouseLookNode.hx b/leenkx/Sources/leenkx/logicnode/MouseLookNode.hx index 58fb70b..977432e 100644 --- a/leenkx/Sources/leenkx/logicnode/MouseLookNode.hx +++ b/leenkx/Sources/leenkx/logicnode/MouseLookNode.hx @@ -6,102 +6,162 @@ import iron.object.Object; import kha.System; import kha.FastFloat; +/** + * MouseLookNode - FPS-style mouse look camera controller + * + * This node provides smooth, resolution-independent mouse look functionality for + * first-person perspective controls. It supports separate body and head objects, + * allowing for realistic FPS camera movement where the body rotates horizontally + * and the head/camera rotates vertically. + * + * Key Features: + * - Resolution-adaptive scaling for consistent feel across different screen sizes + * - Configurable axis orientations (X, Y, Z as front) + * - Optional mouse cursor locking and hiding + * - Invertible X/Y axes + * - Rotation capping/limiting for both horizontal and vertical movement + * - Smoothing support for smoother camera movement + * - Physics integration with automatic rigid body synchronization + * - Support for both local and world space head rotation + */ class MouseLookNode extends LogicNode { - public var property0: String; - public var property1: Bool; - public var property2: Bool; - public var property3: Bool; - public var property4: Bool; - public var property5: Bool; - public var property7: Bool; + // Configuration properties (set from Blender node interface) + public var property0: String; // Front axis: "X", "Y", or "Z" + public var property1: Bool; // Hide Locked: auto-lock mouse cursor + public var property2: Bool; // Invert X: invert horizontal mouse movement + public var property3: Bool; // Invert Y: invert vertical mouse movement + public var property4: Bool; // Cap Left/Right: limit horizontal rotation + public var property5: Bool; // Cap Up/Down: limit vertical rotation + public var property6: Bool; // Head Local Space: use local space for head rotation - var smoothX: Float = 0.0; - var smoothY: Float = 0.0; - var maxHorizontal: Float = Math.PI; - var maxVertical: Float = Math.PI / 2; - var currentHorizontal: Float = 0.0; - var currentVertical: Float = 0.0; + // Smoothing state variables - maintain previous frame values for interpolation + var smoothX: Float = 0.0; // Smoothed horizontal mouse delta + var smoothY: Float = 0.0; // Smoothed vertical mouse delta + + // Rotation limits (in radians) + var maxHorizontal: Float = Math.PI; // Maximum horizontal rotation (180 degrees) + var maxVertical: Float = Math.PI / 2; // Maximum vertical rotation (90 degrees) + + // Current rotation tracking for capping calculations + var currentHorizontal: Float = 0.0; // Accumulated horizontal rotation + var currentVertical: Float = 0.0; // Accumulated vertical rotation + + // Resolution scaling reference - base resolution for consistent sensitivity var baseResolutionWidth: Float = 1920.0; - static inline var BASE_SCALE: Float = 1500.0; - static var RADIAN_SCALING_FACTOR: Float = Math.PI * 50.0 / 180.0; + // Sensitivity scaling constants + static inline var BASE_SCALE: Float = 1500.0; // Base sensitivity scale factor + static var RADIAN_SCALING_FACTOR: Float = Math.PI * 50.0 / 180.0; // Degrees to radians conversion with sensitivity scaling public function new(tree: LogicTree) { super(tree); } + /** + * Main execution function called every frame when the node is active + * + * Input connections: + * [0] - Action trigger (not used in current implementation) + * [1] - Body Object: the main object that rotates horizontally + * [2] - Head Object: optional object that rotates vertically (typically camera) + * [3] - Sensitivity: mouse sensitivity multiplier + * [4] - Smoothing: movement smoothing factor (0.0 = no smoothing, 0.99 = maximum smoothing) + */ override function run(from: Int) { + // Get input values from connected nodes var bodyObject: Object = inputs[1].get(); var headObject: Object = inputs[2].get(); var sensitivity: FastFloat = inputs[3].get(); var smoothing: FastFloat = inputs[4].get(); + // Early exit if no body object is provided if (bodyObject == null) { runOutput(0); return; } + // Get mouse input state var mouse = Input.getMouse(); + // Handle automatic mouse cursor locking for FPS controls if (property1) { if (mouse.started() && !mouse.locked) { - mouse.lock(); + mouse.lock(); // Center and hide cursor, enable unlimited movement } } + // Only process mouse look when cursor is locked or mouse button is held + // This prevents unwanted camera movement when UI elements are being used if (!mouse.locked && !mouse.down()) { runOutput(0); return; } + // Get raw mouse movement delta (pixels moved since last frame) var deltaX: Float = mouse.movementX; var deltaY: Float = mouse.movementY; - if (property2) deltaX = -deltaX; - if (property3) deltaY = -deltaY; + // Apply axis inversion if configured + if (property2) deltaX = -deltaX; // Invert horizontal movement + if (property3) deltaY = -deltaY; // Invert vertical movement - // Always apply resolution-adaptive scaling + // Calculate resolution-adaptive scaling to maintain consistent sensitivity + // across different screen resolutions. Higher resolutions will have proportionally + // higher scaling to compensate for increased pixel density. var resolutionMultiplier: Float = System.windowWidth() / baseResolutionWidth; + // Apply movement smoothing if enabled + // This creates a weighted average between current and previous movement values + // to reduce jittery camera movement, especially useful for low framerates if (smoothing > 0.0) { - var smoothingFactor: Float = Math.min(smoothing, 0.99); + var smoothingFactor: Float = Math.min(smoothing, 0.99); // Cap smoothing to prevent complete freeze smoothX = smoothX * smoothingFactor + deltaX * (1.0 - smoothingFactor); smoothY = smoothY * smoothingFactor + deltaY * (1.0 - smoothingFactor); deltaX = smoothX; deltaY = smoothY; } - var horizontalAxis = new Vec4(); - var verticalAxis = new Vec4(); + // Define rotation axes based on the configured front axis + // These determine which 3D axes are used for horizontal and vertical rotation + var horizontalAxis = new Vec4(); // Axis for left/right body rotation + var verticalAxis = new Vec4(); // Axis for up/down head rotation switch (property0) { - case "X": - horizontalAxis.set(0, 0, 1); - verticalAxis.set(0, 1, 0); - case "Y": + case "X": // X-axis forward (e.g., for side-scrolling or specific orientations) + horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation + verticalAxis.set(0, 1, 0); // Y-axis for vertical rotation + case "Y": // Y-axis forward (most common for 3D games) #if lnx_yaxisup - horizontalAxis.set(0, 0, 1); - verticalAxis.set(1, 0, 0); + // Y-up coordinate system (Blender default) + horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation + verticalAxis.set(1, 0, 0); // X-axis for vertical rotation #else - horizontalAxis.set(0, 0, 1); - verticalAxis.set(1, 0, 0); + // Z-up coordinate system + horizontalAxis.set(0, 0, 1); // Z-axis for horizontal rotation + verticalAxis.set(1, 0, 0); // X-axis for vertical rotation #end - case "Z": - horizontalAxis.set(0, 1, 0); - verticalAxis.set(1, 0, 0); + case "Z": // Z-axis forward (top-down or specific orientations) + horizontalAxis.set(0, 1, 0); // Y-axis for horizontal rotation + verticalAxis.set(1, 0, 0); // X-axis for vertical rotation } - // Always apply resolution-adaptive scaling + // Calculate final sensitivity scaling combining base scale and resolution adaptation var finalScale: Float = BASE_SCALE * resolutionMultiplier; + // Apply user-defined sensitivity multiplier deltaX *= sensitivity; deltaY *= sensitivity; + // Convert pixel movement to rotation angles (radians) + // Negative values ensure natural movement direction (moving mouse right rotates right) var horizontalRotation: Float = (-deltaX / finalScale) * RADIAN_SCALING_FACTOR; var verticalRotation: Float = (-deltaY / finalScale) * RADIAN_SCALING_FACTOR; + // Apply horizontal rotation capping if enabled + // This prevents the character from rotating beyond specified limits if (property4) { currentHorizontal += horizontalRotation; + // Clamp rotation to maximum horizontal range and adjust current frame rotation if (currentHorizontal > maxHorizontal) { horizontalRotation -= (currentHorizontal - maxHorizontal); currentHorizontal = maxHorizontal; @@ -111,8 +171,11 @@ class MouseLookNode extends LogicNode { } } + // Apply vertical rotation capping if enabled + // This prevents looking too far up or down (like human neck limitations) if (property5) { currentVertical += verticalRotation; + // Clamp rotation to maximum vertical range and adjust current frame rotation if (currentVertical > maxVertical) { verticalRotation -= (currentVertical - maxVertical); currentVertical = maxVertical; @@ -122,38 +185,49 @@ class MouseLookNode extends LogicNode { } } + // Apply horizontal rotation to body object (character turning left/right) if (horizontalRotation != 0.0) { bodyObject.transform.rotate(horizontalAxis, horizontalRotation); + // Synchronize physics rigid body if present + // This ensures physics simulation stays in sync with visual transform #if lnx_physics var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody); if (rigidBody != null) rigidBody.syncTransform(); #end } + // Apply vertical rotation to head object (camera looking up/down) if (headObject != null && verticalRotation != 0.0) { - if (property7) { - // Local space rotation (recommended for FPS) + if (property6) { + // Local space rotation - recommended when head is a child of body + // This prevents gimbal lock and rotation inheritance issues headObject.transform.rotate(verticalAxis, verticalRotation); } else { - // World space rotation + // World space rotation - uses head object's current right vector + // More accurate for independent head objects but can cause issues with parenting var headVerticalAxis = headObject.transform.world.right(); headObject.transform.rotate(headVerticalAxis, verticalRotation); } + // Synchronize head physics rigid body if present #if lnx_physics var headRigidBody = headObject.getTrait(leenkx.trait.physics.RigidBody); if (headRigidBody != null) headRigidBody.syncTransform(); #end } else if (headObject == null && verticalRotation != 0.0) { + // Fallback: if no separate head object, apply vertical rotation to body + // This creates a simpler single-object camera control bodyObject.transform.rotate(verticalAxis, verticalRotation); + // Synchronize body physics rigid body #if lnx_physics var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody); if (rigidBody != null) rigidBody.syncTransform(); #end } + // Continue to next connected node in the logic tree runOutput(0); } } \ No newline at end of file diff --git a/leenkx/blender/lnx/logicnode/input/LN_mouse_look.py b/leenkx/blender/lnx/logicnode/input/LN_mouse_look.py index 11230cf..e2820d1 100644 --- a/leenkx/blender/lnx/logicnode/input/LN_mouse_look.py +++ b/leenkx/blender/lnx/logicnode/input/LN_mouse_look.py @@ -2,95 +2,158 @@ from lnx.logicnode.lnx_nodes import * class MouseLookNode(LnxLogicTreeNode): - """Controls object rotation based on mouse movement for FPS-style camera control. + """MouseLookNode - Blender UI interface for FPS-style mouse look camera controller + + This class defines the Blender node interface for the MouseLookNode logic node. + It creates the visual node that appears in Blender's logic tree editor and + defines all the properties that configure the mouse look behavior. + + The node provides controls for: + - Axis orientation configuration + - Mouse cursor behavior + - Movement inversion options + - Rotation limiting/capping + - Head rotation space behavior Features: - Built-in resolution-adaptive scaling for consistent feel across different screen resolutions + - Automatic physics synchronization for rigid bodies + - Support for both single-object and dual-object (body/head) setups """ - bl_idname = 'LNMouseLookNode' - bl_label = 'Mouse Look' - lnx_section = 'mouse' - lnx_version = 1 + + # Blender node identification + bl_idname = 'LNMouseLookNode' # Unique identifier for Blender's node system + bl_label = 'Mouse Look' # Display name in node menu and header + lnx_section = 'mouse' # Category section in node add menu + lnx_version = 1 # Node version for compatibility tracking - # Front axis property + # Property 0: Front Axis Configuration + # Determines which 3D axis represents the "forward" direction of the character/camera + # This affects how horizontal and vertical rotations are applied to the objects 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') + items=[('X', 'X Axis', 'X Axis as front'), # X-forward (side-scrolling, specific orientations) + ('Y', 'Y Axis', 'Y Axis as front'), # Y-forward (most common for 3D games) + ('Z', 'Z Axis', 'Z Axis as front')], # Z-forward (top-down, specific orientations) + name='Front', + default='Y') # Y-axis is default as it's most common in 3D game development - # Hide Locked property + # Property 1: Automatic Mouse Cursor Management + # When enabled, automatically centers and locks the mouse cursor when mouse input starts + # This is essential for FPS games to prevent cursor from leaving game window property1: HaxeBoolProperty( 'property1', name='Hide Locked', - description='Automatically center and lock the mouse cursor', - default=True) + description='Automatically center and lock the mouse cursor when mouse input begins', + default=True) # Enabled by default for typical FPS behavior - # Invert X property + # Property 2: Horizontal Movement Inversion + # Allows users to invert horizontal mouse movement (left becomes right, right becomes left) + # Some players prefer inverted controls for consistency with flight simulators property2: HaxeBoolProperty( 'property2', name='Invert X', - description='Invert horizontal mouse movement', - default=False) + description='Invert horizontal mouse movement - moving mouse right turns character left', + default=False) # Most players expect non-inverted horizontal movement - # Invert Y property + # Property 3: Vertical Movement Inversion + # Allows users to invert vertical mouse movement (up becomes down, down becomes up) + # More commonly used than horizontal inversion, especially by flight sim players property3: HaxeBoolProperty( 'property3', name='Invert Y', - description='Invert vertical mouse movement', - default=False) + description='Invert vertical mouse movement - moving mouse up looks down', + default=False) # Most players expect non-inverted vertical movement - # Cap Left/Right property + # Property 4: Horizontal Rotation Limiting + # Prevents the character from rotating beyond specified horizontal limits + # Useful for fixed-perspective games or when character shouldn't turn completely around property4: HaxeBoolProperty( 'property4', name='Cap Left / Right', - description='Limit horizontal rotation', - default=False) + description='Limit horizontal rotation to prevent full 360-degree turns', + default=False) # Disabled by default - most FPS games allow full horizontal rotation - # Cap Up/Down property + # Property 5: Vertical Rotation Limiting + # Prevents looking too far up or down, simulating human neck movement limitations + # Essential for realistic FPS games to prevent disorienting over-rotation property5: HaxeBoolProperty( 'property5', name='Cap Up / Down', - description='Limit vertical rotation', - default=True) + description='Limit vertical rotation to simulate natural neck movement (±90 degrees)', + default=True) # Enabled by default for realistic FPS behavior - # Head rotation space toggle - property7: HaxeBoolProperty( - 'property7', + # Property 6: Head Rotation Space Mode + # Controls whether head rotation uses local or world space coordinates + # Critical for preventing rotation issues when head object is child of body object + property6: HaxeBoolProperty( + 'property6', name='Head Local Space', - description='Enable it if the Head is child of the Body to avoid weird rotation', - default=False) + description='Use local space for head rotation - enable when Head is child of Body to avoid gimbal lock', + default=False) # Disabled by default, enable when using parent-child object relationships - - - def lnx_init(self, context): + """Initialize the node's input and output sockets + + This method is called when the node is first created in Blender. + It defines all the connection points (sockets) that other nodes can connect to. + + Input Sockets: + - Action In: Execution flow input (when this node should run) + - Body: The main character/player object that rotates horizontally + - Head: Optional camera/head object that rotates vertically (can be child of Body) + - Sensitivity: Mouse sensitivity multiplier (0.5 = half sensitivity, 2.0 = double sensitivity) + - Smoothing: Movement smoothing factor (0.0 = no smoothing, higher = more smoothing) + + Output Sockets: + - Action Out: Execution flow output (continues to next node after processing) + """ + + # Execution flow input - connects from previous logic node 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('LnxFactorSocket', 'Smoothing', default_value=0.0) - + + # Object inputs - require 3D objects from the scene + self.add_input('LnxNodeSocketObject', 'Body') # Main character object (required) + self.add_input('LnxNodeSocketObject', 'Head') # Camera/head object (optional) + + # Numeric inputs with sensible defaults + self.add_input('LnxFloatSocket', 'Sensitivity', default_value=0.5) # Medium sensitivity + self.add_input('LnxFloatSocket', 'Smoothing', default_value=0.0) # No smoothing by default + + # Execution flow output - connects to next logic node self.add_output('LnxNodeSocketAction', 'Out') def draw_buttons(self, context, layout): - layout.prop(self, 'property0', text='Front') - layout.prop(self, 'property1', text='Hide Locked') + """Draw the node's user interface in Blender's logic tree editor - # 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) + This method creates the visual controls that appear on the node in Blender. + It organizes properties into logical groups for better usability. - # Cap rotations section - col = layout.column(align=True) - col.prop(self, 'property4', text='Cap Left / Right') - col.prop(self, 'property5', text='Cap Up / Down') + Args: + context: Blender context (current scene, selected objects, etc.) + layout: UI layout object for arranging interface elements + """ - # Head behavior section - col = layout.column(align=True) - col.prop(self, 'property7', text='Head Local Space') \ No newline at end of file + # Basic configuration section + layout.prop(self, 'property0', text='Front') # Front axis dropdown + layout.prop(self, 'property1', text='Hide Locked') # Mouse locking checkbox + + # Movement inversion controls section + # Group X and Y inversion together for logical organization + col = layout.column(align=True) # Create aligned column + col.label(text="Invert XY:") # Section header + row = col.row(align=True) # Create horizontal row within column + row.prop(self, 'property2', text='X', toggle=True) # X inversion toggle button + row.prop(self, 'property3', text='Y', toggle=True) # Y inversion toggle button + + # Rotation limiting controls section + # Group rotation caps together since they're related functionality + col = layout.column(align=True) # Create new aligned column + col.prop(self, 'property4', text='Cap Left / Right') # Horizontal capping checkbox + col.prop(self, 'property5', text='Cap Up / Down') # Vertical capping checkbox + + # Advanced head behavior section + # Separate advanced option that affects technical behavior + col = layout.column(align=True) # Create new aligned column + col.prop(self, 'property6', text='Head Local Space') # Head rotation space checkbox \ No newline at end of file