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); } }