forked from LeenkxTeam/LNXSDK
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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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')
|
@ -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()
|
||||||
|
Reference in New Issue
Block a user