diff --git a/leenkx/Sources/leenkx/logicnode/HasContactNode.hx b/leenkx/Sources/leenkx/logicnode/HasContactNode.hx index d1871bb..44449e1 100644 --- a/leenkx/Sources/leenkx/logicnode/HasContactNode.hx +++ b/leenkx/Sources/leenkx/logicnode/HasContactNode.hx @@ -6,6 +6,14 @@ 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); @@ -13,33 +21,53 @@ class HasContactNode extends LogicNode { override function get(from: Int): Dynamic { var object1: Object = inputs[0].get(); - if (object1 == null) return false; #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) { - // If "any object" is enabled, check for any contact - if (property0) { - return rbs.length > 0; - } else { - // Original logic: check for specific object contact - // Only try to get object2 if we have a second input - if (inputs.length > 1) { - var object2: Object = inputs[1].get(); - if (object2 == null) return false; - - var rb2 = object2.getTrait(RigidBody); - for (rb in rbs) { - if (rb == rb2) return true; - } - } - } + // 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 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; } diff --git a/leenkx/Sources/leenkx/logicnode/OnContactNode.hx b/leenkx/Sources/leenkx/logicnode/OnContactNode.hx index fbfe913..595c9ff 100644 --- a/leenkx/Sources/leenkx/logicnode/OnContactNode.hx +++ b/leenkx/Sources/leenkx/logicnode/OnContactNode.hx @@ -5,64 +5,99 @@ import leenkx.trait.physics.RigidBody; class OnContactNode extends LogicNode { - public var property0: String; - public var property1: Bool; // "on any object" checkbox - 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(); - - if (object1 == null) object1 = 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) { - // If "on any object" is enabled, check for any contact - if (property1) { - contact = rbs.length > 0; - } else { - // Original logic: check for specific object contact - // Only try to get object2 if we have a second input - if (inputs.length > 1) { - var object2: Object = inputs[1].get(); - if (object2 == null) object2 = tree.object; - - 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; } }