diff --git a/leenkx/Sources/leenkx/logicnode/MouseLookNode.hx b/leenkx/Sources/leenkx/logicnode/MouseLookNode.hx index edf6517..58fb70b 100644 --- a/leenkx/Sources/leenkx/logicnode/MouseLookNode.hx +++ b/leenkx/Sources/leenkx/logicnode/MouseLookNode.hx @@ -7,44 +7,24 @@ 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; + public var property1: Bool; + public var property2: Bool; + public var property3: Bool; + public var property4: Bool; + public var property5: Bool; + public var property7: Bool; - 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 + 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; + var baseResolutionWidth: Float = 1920.0; - // 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; - - + static inline var BASE_SCALE: Float = 1500.0; + static var RADIAN_SCALING_FACTOR: Float = Math.PI * 50.0 / 180.0; public function new(tree: LogicTree) { super(tree); @@ -63,114 +43,64 @@ class MouseLookNode extends LogicNode { 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; + var deltaX: Float = mouse.movementX; + var deltaY: Float = 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 - } + // Always apply resolution-adaptive scaling + var resolutionMultiplier: Float = System.windowWidth() / baseResolutionWidth; - // 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; + var smoothingFactor: Float = Math.min(smoothing, 0.99); + smoothX = smoothX * smoothingFactor + deltaX * (1.0 - smoothingFactor); + smoothY = smoothY * smoothingFactor + deltaY * (1.0 - smoothingFactor); 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) + case "X": + horizontalAxis.set(0, 0, 1); + verticalAxis.set(0, 1, 0); + case "Y": #if lnx_yaxisup - horizontalAxis.set(0, 0, 1); // Z axis for horizontal (yaw) - verticalAxis.set(1, 0, 0); // X axis for vertical (pitch) + horizontalAxis.set(0, 0, 1); + verticalAxis.set(1, 0, 0); #else - horizontalAxis.set(0, 0, 1); // Z axis for horizontal (yaw) - verticalAxis.set(1, 0, 0); // X axis for vertical (pitch) + horizontalAxis.set(0, 0, 1); + verticalAxis.set(1, 0, 0); #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) + case "Z": + horizontalAxis.set(0, 1, 0); + verticalAxis.set(1, 0, 0); } - // Base scaling - var baseScale: FastFloat = 1500.0; - var finalScale = baseScale; + // Always apply resolution-adaptive scaling + var finalScale: Float = BASE_SCALE * resolutionMultiplier; - // 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; + var horizontalRotation: Float = (-deltaX / finalScale) * RADIAN_SCALING_FACTOR; + var verticalRotation: Float = (-deltaY / finalScale) * RADIAN_SCALING_FACTOR; - // 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 + if (property4) { currentHorizontal += horizontalRotation; if (currentHorizontal > maxHorizontal) { horizontalRotation -= (currentHorizontal - maxHorizontal); @@ -181,7 +111,7 @@ class MouseLookNode extends LogicNode { } } - if (property5) { // Cap Up/Down + if (property5) { currentVertical += verticalRotation; if (currentVertical > maxVertical) { verticalRotation -= (currentVertical - maxVertical); @@ -192,39 +122,36 @@ class MouseLookNode extends LogicNode { } } - // 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 + if (horizontalRotation != 0.0) { + bodyObject.transform.rotate(horizontalAxis, horizontalRotation); - // 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 + if (headObject != null && verticalRotation != 0.0) { + if (property7) { + // Local space rotation (recommended for FPS) + headObject.transform.rotate(verticalAxis, verticalRotation); + } else { + // World space rotation + var headVerticalAxis = headObject.transform.world.right(); + headObject.transform.rotate(headVerticalAxis, verticalRotation); + } - // 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 - } + } else if (headObject == null && verticalRotation != 0.0) { + bodyObject.transform.rotate(verticalAxis, verticalRotation); + + #if lnx_physics + var rigidBody = bodyObject.getTrait(leenkx.trait.physics.RigidBody); + if (rigidBody != null) rigidBody.syncTransform(); + #end } runOutput(0); diff --git a/leenkx/blender/lnx/logicnode/input/LN_mouse_look.py b/leenkx/blender/lnx/logicnode/input/LN_mouse_look.py index d33d111..11230cf 100644 --- a/leenkx/blender/lnx/logicnode/input/LN_mouse_look.py +++ b/leenkx/blender/lnx/logicnode/input/LN_mouse_look.py @@ -5,8 +5,7 @@ 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 + - Built-in resolution-adaptive scaling for consistent feel across different screen resolutions """ bl_idname = 'LNMouseLookNode' bl_label = 'Mouse Look' @@ -55,12 +54,12 @@ class MouseLookNode(LnxLogicTreeNode): 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', + + # Head rotation space toggle + property7: HaxeBoolProperty( + 'property7', + name='Head Local Space', + description='Enable it if the Head is child of the Body to avoid weird rotation', default=False) @@ -72,7 +71,7 @@ class MouseLookNode(LnxLogicTreeNode): 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_input('LnxFactorSocket', 'Smoothing', default_value=0.0) self.add_output('LnxNodeSocketAction', 'Out') @@ -92,10 +91,6 @@ class MouseLookNode(LnxLogicTreeNode): col.prop(self, 'property4', text='Cap Left / Right') col.prop(self, 'property5', text='Cap Up / Down') - # Separator - layout.separator() - - # Enhancement strategies section + # Head behavior section col = layout.column(align=True) - col.label(text="Enhancement Strategies:") - col.prop(self, 'property6', text='Resolution Adaptive') \ No newline at end of file + col.prop(self, 'property7', text='Head Local Space') \ No newline at end of file