Compare commits
	
		
			3 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4c5ca86030 | |||
| d726686125 | |||
| 4492995a59 | 
@ -5,21 +5,69 @@ import leenkx.trait.physics.RigidBody;
 | 
			
		||||
 | 
			
		||||
class HasContactNode extends LogicNode {
 | 
			
		||||
 | 
			
		||||
	public var property0: Bool; // "any object" checkbox
 | 
			
		||||
	
 | 
			
		||||
	// Performance optimization: Cache RigidBody lookups
 | 
			
		||||
	var cachedRb1: RigidBody = null;
 | 
			
		||||
	var cachedRb2: RigidBody = null;
 | 
			
		||||
	var lastObject1: Object = null;
 | 
			
		||||
	var lastObject2: Object = null;
 | 
			
		||||
	var lastContactCount: Int = 0;
 | 
			
		||||
	var lastFrame: Int = -1;
 | 
			
		||||
 | 
			
		||||
	public function new(tree: LogicTree) {
 | 
			
		||||
		super(tree);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	override function get(from: Int): Dynamic {
 | 
			
		||||
		var object1: Object = inputs[0].get();
 | 
			
		||||
		var object2: Object = inputs[1].get();
 | 
			
		||||
 | 
			
		||||
		if (object1 == null || object2 == null) return false;
 | 
			
		||||
		if (object1 == null) return false;
 | 
			
		||||
 | 
			
		||||
#if lnx_physics
 | 
			
		||||
		// Enhanced caching: Check both object reference AND RigidBody availability
 | 
			
		||||
		var shouldUpdateCache1 = false;
 | 
			
		||||
		if (object1 != lastObject1) {
 | 
			
		||||
			// Object reference changed - always update cache
 | 
			
		||||
			shouldUpdateCache1 = true;
 | 
			
		||||
		} else if (cachedRb1 == null) {
 | 
			
		||||
			// Same object but no cached RigidBody - check if one was added
 | 
			
		||||
			var currentRb1 = object1.getTrait(RigidBody);
 | 
			
		||||
			if (currentRb1 != null) {
 | 
			
		||||
				shouldUpdateCache1 = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (shouldUpdateCache1) {
 | 
			
		||||
			cachedRb1 = object1.getTrait(RigidBody);
 | 
			
		||||
			lastObject1 = object1;
 | 
			
		||||
		}
 | 
			
		||||
		if (cachedRb1 == null) return false;
 | 
			
		||||
		
 | 
			
		||||
		var physics = leenkx.trait.physics.PhysicsWorld.active;
 | 
			
		||||
		var rb2 = object2.getTrait(RigidBody);
 | 
			
		||||
		var rbs = physics.getContacts(object1.getTrait(RigidBody));
 | 
			
		||||
		if (rbs != null) for (rb in rbs) if (rb == rb2) return true;
 | 
			
		||||
		var rbs = physics.getContacts(cachedRb1);
 | 
			
		||||
		
 | 
			
		||||
		// Early exit if no contacts
 | 
			
		||||
		if (rbs == null || rbs.length == 0) return false;
 | 
			
		||||
		
 | 
			
		||||
		// If "any object" mode, any contact is valid - return immediately
 | 
			
		||||
		if (property0) return true;
 | 
			
		||||
		
 | 
			
		||||
		// Specific object mode - check if second input exists
 | 
			
		||||
		if (inputs.length <= 1) return false;
 | 
			
		||||
		
 | 
			
		||||
		var object2: Object = inputs[1].get();
 | 
			
		||||
		if (object2 == null) return false;
 | 
			
		||||
		
 | 
			
		||||
		// Cache second RigidBody lookup
 | 
			
		||||
		if (object2 != lastObject2) {
 | 
			
		||||
			cachedRb2 = object2.getTrait(RigidBody);
 | 
			
		||||
			lastObject2 = object2;
 | 
			
		||||
		}
 | 
			
		||||
		if (cachedRb2 == null) return false;
 | 
			
		||||
		
 | 
			
		||||
		// Use indexOf for better performance than manual loop
 | 
			
		||||
		// This is more efficient for small arrays and uses native array methods
 | 
			
		||||
		return rbs.indexOf(cachedRb2) >= 0;
 | 
			
		||||
#end
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -5,53 +5,99 @@ import leenkx.trait.physics.RigidBody;
 | 
			
		||||
 | 
			
		||||
class OnContactNode extends LogicNode {
 | 
			
		||||
 | 
			
		||||
	public var property0: String;
 | 
			
		||||
	var lastContact = false;
 | 
			
		||||
	public var property0: String; // Contact type: "begin", "overlap", "end"
 | 
			
		||||
	public var property1: Bool; // "On Any Object" checkbox
 | 
			
		||||
	
 | 
			
		||||
	// Cache for performance optimization
 | 
			
		||||
	var cachedRb1: RigidBody = null;
 | 
			
		||||
	var cachedRb2: RigidBody = null;
 | 
			
		||||
	var lastObject1: Object = null;
 | 
			
		||||
	var lastObject2: Object = null;
 | 
			
		||||
	var lastObject1TraitCount: Int = 0;
 | 
			
		||||
	var lastObject2TraitCount: Int = 0;
 | 
			
		||||
	var lastContact = false; // Track previous contact state
 | 
			
		||||
 | 
			
		||||
	public function new(tree: LogicTree) {
 | 
			
		||||
		super(tree);
 | 
			
		||||
 | 
			
		||||
		// Subscribe to update events for event-based triggering
 | 
			
		||||
		tree.notifyOnUpdate(update);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function update() {
 | 
			
		||||
		var object1: Object = inputs[0].get();
 | 
			
		||||
		var object2: Object = inputs[1].get();
 | 
			
		||||
 | 
			
		||||
		if (object1 == null) object1 = tree.object;
 | 
			
		||||
		if (object2 == null) object2 = tree.object;
 | 
			
		||||
 | 
			
		||||
		var contact = false;
 | 
			
		||||
		if (object1 == null) return;
 | 
			
		||||
 | 
			
		||||
#if lnx_physics
 | 
			
		||||
		var physics = leenkx.trait.physics.PhysicsWorld.active;
 | 
			
		||||
		var rb1 = object1.getTrait(RigidBody);
 | 
			
		||||
		if (rb1 != null) {
 | 
			
		||||
			var rbs = physics.getContacts(rb1);
 | 
			
		||||
			if (rbs != null) {
 | 
			
		||||
				var rb2 = object2.getTrait(RigidBody);
 | 
			
		||||
				for (rb in rbs) {
 | 
			
		||||
					if (rb == rb2) {
 | 
			
		||||
						contact = true;
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
		// Smart caching: update cache if object changes OR if trait count changes
 | 
			
		||||
		var shouldUpdateCache1 = (object1 != lastObject1) || 
 | 
			
		||||
		                        (lastObject1 != null && lastObject1.traits.length != lastObject1TraitCount);
 | 
			
		||||
		
 | 
			
		||||
		if (shouldUpdateCache1) {
 | 
			
		||||
			cachedRb1 = object1.getTrait(RigidBody);
 | 
			
		||||
			lastObject1 = object1;
 | 
			
		||||
			lastObject1TraitCount = object1.traits.length;
 | 
			
		||||
		}
 | 
			
		||||
		if (cachedRb1 == null) return;
 | 
			
		||||
		
 | 
			
		||||
		var rbs = leenkx.trait.physics.PhysicsWorld.active.getContacts(cachedRb1);
 | 
			
		||||
		if (rbs == null || rbs.length == 0) {
 | 
			
		||||
			// No contacts - handle "end" event
 | 
			
		||||
			if (lastContact && property0 == "end") {
 | 
			
		||||
				runOutput(0);
 | 
			
		||||
			}
 | 
			
		||||
			lastContact = false;
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// Check for contact
 | 
			
		||||
		var contact = false;
 | 
			
		||||
		
 | 
			
		||||
		// If "On Any Object" mode, any contact is valid
 | 
			
		||||
		if (property1) {
 | 
			
		||||
			contact = true;
 | 
			
		||||
		} else {
 | 
			
		||||
			// Specific object mode - check if second input exists
 | 
			
		||||
			if (inputs.length <= 1) return;
 | 
			
		||||
			
 | 
			
		||||
			var object2: Object = inputs[1].get();
 | 
			
		||||
			if (object2 == null) return;
 | 
			
		||||
			
 | 
			
		||||
			// Smart caching for second object
 | 
			
		||||
			var shouldUpdateCache2 = (object2 != lastObject2) || 
 | 
			
		||||
			                        (lastObject2 != null && lastObject2.traits.length != lastObject2TraitCount);
 | 
			
		||||
			
 | 
			
		||||
			if (shouldUpdateCache2) {
 | 
			
		||||
				cachedRb2 = object2.getTrait(RigidBody);
 | 
			
		||||
				lastObject2 = object2;
 | 
			
		||||
				lastObject2TraitCount = object2.traits.length;
 | 
			
		||||
			}
 | 
			
		||||
			if (cachedRb2 == null) return;
 | 
			
		||||
			
 | 
			
		||||
			// Check if target object is in contact list
 | 
			
		||||
			contact = rbs.indexOf(cachedRb2) >= 0;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		// Handle different contact event types
 | 
			
		||||
		var shouldTrigger = false;
 | 
			
		||||
		switch (property0) {
 | 
			
		||||
			case "begin":
 | 
			
		||||
				shouldTrigger = contact && !lastContact;
 | 
			
		||||
			case "overlap":
 | 
			
		||||
				shouldTrigger = contact;
 | 
			
		||||
			case "end":
 | 
			
		||||
				shouldTrigger = !contact && lastContact;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		lastContact = contact;
 | 
			
		||||
		
 | 
			
		||||
		if (shouldTrigger) {
 | 
			
		||||
			runOutput(0);
 | 
			
		||||
		}
 | 
			
		||||
#end
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		var b = false;
 | 
			
		||||
		switch (property0) {
 | 
			
		||||
		case "begin":
 | 
			
		||||
			b = contact && !lastContact;
 | 
			
		||||
		case "overlap":
 | 
			
		||||
			b = contact;
 | 
			
		||||
		case "end":
 | 
			
		||||
			b = !contact && lastContact;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lastContact = contact;
 | 
			
		||||
 | 
			
		||||
		if (b) runOutput(0);
 | 
			
		||||
	// Keep the get method for backward compatibility, but it's not the primary interface
 | 
			
		||||
	override function get(from: Int): Dynamic {
 | 
			
		||||
		return lastContact;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,16 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import *
 | 
			
		||||
 | 
			
		||||
def toggle_second_input(self, context):
 | 
			
		||||
    """Show/hide the second input socket based on the 'On Any Object' checkbox"""
 | 
			
		||||
    if self.property0:  # "On Any Object" is enabled
 | 
			
		||||
        # Hide the second input socket
 | 
			
		||||
        if len(self.inputs) > 1:
 | 
			
		||||
            self.inputs.remove(self.inputs.values()[-1])
 | 
			
		||||
    else:  # "On Any Object" is disabled
 | 
			
		||||
        # Show the second input socket if it doesn't exist
 | 
			
		||||
        if len(self.inputs) == 1:
 | 
			
		||||
            self.add_input('LnxNodeSocketObject', 'RB 2')
 | 
			
		||||
 | 
			
		||||
class HasContactNode(LnxLogicTreeNode):
 | 
			
		||||
    """Returns whether the given rigid body has contact with another given rigid body."""
 | 
			
		||||
    bl_idname = 'LNHasContactNode'
 | 
			
		||||
@ -7,8 +18,18 @@ class HasContactNode(LnxLogicTreeNode):
 | 
			
		||||
    lnx_section = 'contact'
 | 
			
		||||
    lnx_version = 1
 | 
			
		||||
 | 
			
		||||
    property0: HaxeBoolProperty(
 | 
			
		||||
        'property0',
 | 
			
		||||
        name='On Any Object',
 | 
			
		||||
        description='If enabled, returns true if the rigid body has contact with any object',
 | 
			
		||||
        default=False,
 | 
			
		||||
        update=toggle_second_input)
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'RB 1')
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'RB 2')
 | 
			
		||||
 | 
			
		||||
        self.add_output('LnxBoolSocket', 'Has Contact')
 | 
			
		||||
 | 
			
		||||
    def draw_buttons(self, context, layout):
 | 
			
		||||
        layout.prop(self, 'property0')  # "On Any Object" checkbox
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,16 @@
 | 
			
		||||
from lnx.logicnode.lnx_nodes import *
 | 
			
		||||
 | 
			
		||||
def toggle_second_input(self, context):
 | 
			
		||||
    """Show/hide the second input socket based on the 'On Any Object' checkbox"""
 | 
			
		||||
    if self.property1:  # "On Any Object" is enabled
 | 
			
		||||
        # Hide the second input socket
 | 
			
		||||
        if len(self.inputs) > 1:
 | 
			
		||||
            self.inputs.remove(self.inputs.values()[-1])
 | 
			
		||||
    else:  # "On Any Object" is disabled
 | 
			
		||||
        # Show the second input socket if it doesn't exist
 | 
			
		||||
        if len(self.inputs) == 1:
 | 
			
		||||
            self.add_input('LnxNodeSocketObject', 'RB 2')
 | 
			
		||||
 | 
			
		||||
class OnContactNode(LnxLogicTreeNode):
 | 
			
		||||
    """Activates the output when the rigid body make contact with
 | 
			
		||||
    another rigid body.
 | 
			
		||||
@ -23,6 +34,13 @@ class OnContactNode(LnxLogicTreeNode):
 | 
			
		||||
                 ('end', 'End', 'The contact between the rigid bodies ends')],
 | 
			
		||||
        name='', default='begin')
 | 
			
		||||
 | 
			
		||||
    property1: HaxeBoolProperty(
 | 
			
		||||
        'property1',
 | 
			
		||||
        name='On Any Object',
 | 
			
		||||
        description='If enabled, triggers on contact with any object instead of a specific object',
 | 
			
		||||
        default=False,
 | 
			
		||||
        update=toggle_second_input)
 | 
			
		||||
 | 
			
		||||
    def lnx_init(self, context):
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'RB 1')
 | 
			
		||||
        self.add_input('LnxNodeSocketObject', 'RB 2')
 | 
			
		||||
@ -31,3 +49,4 @@ class OnContactNode(LnxLogicTreeNode):
 | 
			
		||||
 | 
			
		||||
    def draw_buttons(self, context, layout):
 | 
			
		||||
        layout.prop(self, 'property0')
 | 
			
		||||
        layout.prop(self, 'property1')  # "On Any Object" checkbox
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user