Compare commits

3 Commits
main ... main

11 changed files with 190 additions and 255 deletions

View File

@ -24,9 +24,6 @@ class ObjectAnimation extends Animation {
public var transformMap: Map<String, FastFloat>;
var defaultSampler: ActionSampler = null;
static inline var DEFAULT_SAMPLER_ID = "__object_default_action__";
public static var trackNames: Array<String> = [ "xloc", "yloc", "zloc",
"xrot", "yrot", "zrot",
"qwrot", "qxrot", "qyrot", "qzrot",
@ -42,6 +39,7 @@ class ObjectAnimation extends Animation {
isSkinned = false;
super();
}
function getAction(action: String): TObj {
for (a in oactions) if (a != null && a.objects[0].name == action) return a.objects[0];
return null;
@ -49,29 +47,10 @@ class ObjectAnimation extends Animation {
override public function play(action = "", onComplete: Void->Void = null, blendTime = 0.0, speed = 1.0, loop = true) {
super.play(action, onComplete, blendTime, speed, loop);
if (this.action == "" && oactions != null && oactions[0] != null){
this.action = oactions[0].objects[0].name;
}
if (this.action == "" && oactions[0] != null) this.action = oactions[0].objects[0].name;
oaction = getAction(this.action);
if (oaction != null) {
isSampled = oaction.sampled != null && oaction.sampled;
if (defaultSampler != null) {
deRegisterAction(DEFAULT_SAMPLER_ID);
}
var callbacks = onComplete != null ? [onComplete] : null;
defaultSampler = new ActionSampler(this.action, speed, loop, false, callbacks);
registerAction(DEFAULT_SAMPLER_ID, defaultSampler);
if (paused) defaultSampler.paused = true;
updateAnimation = function(map: Map<String, FastFloat>) {
sampleAction(defaultSampler, map);
};
}
else {
if (defaultSampler != null) {
deRegisterAction(DEFAULT_SAMPLER_ID);
defaultSampler = null;
}
updateAnimation = null;
}
}
@ -82,13 +61,12 @@ class ObjectAnimation extends Animation {
Animation.beginProfile();
#end
if (transformMap == null) transformMap = new Map();
if(transformMap == null) transformMap = new Map();
transformMap = initTransformMap();
super.update(delta);
if (defaultSampler != null) defaultSampler.paused = paused;
if (paused) return;
if (updateAnimation == null) return;
if(updateAnimation == null) return;
if (!isSkinned) updateObjectAnimation();
#if lnx_debug

View File

@ -71,12 +71,11 @@ class ParticleSystem {
Data.getParticle(sceneName, pref.particle, function(b: ParticleData) {
data = b;
r = data.raw;
var dyn: Null<Bool> = r.dynamic_emitter;
var dynValue: Bool = true;
if (dyn != null) {
dynValue = dyn;
if (r.dynamic_emitter != null){
dynamicEmitter = r.dynamic_emitter;
} else {
dynamicEmitter = true;
}
dynamicEmitter = dynValue;
if (Scene.active.raw.gravity != null) {
gx = Scene.active.raw.gravity[0] * r.weight_gravity;
gy = Scene.active.raw.gravity[1] * r.weight_gravity;

View File

@ -1,48 +0,0 @@
package leenkx.logicnode;
import iron.object.Object;
#if lnx_physics
import leenkx.trait.physics.PhysicsCache;
import leenkx.trait.physics.RigidBody;
#end
class AnyContactNode extends LogicNode {
public var property0: String;
var lastContact = false;
public function new(tree: LogicTree) {
super(tree);
tree.notifyOnUpdate(update);
}
function update() {
var object1: Object = inputs[0].get();
if (object1 == null) object1 = tree.object;
if (object1 == null) return;
var contact = false;
#if lnx_physics
var rb1 = PhysicsCache.getCachedRigidBody(object1);
if (rb1 != null) {
var rbs = PhysicsCache.getCachedContacts(rb1);
contact = (rbs != null && rbs.length > 0);
}
#end
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);
}
}

View File

@ -1,32 +1,74 @@
package leenkx.logicnode;
import iron.object.Object;
#if lnx_physics
import leenkx.trait.physics.PhysicsCache;
import iron.object.Object;
import leenkx.trait.physics.RigidBody;
#end
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) return false;
if (object1 == null || object2 == null) return false;
#if lnx_physics
var rb1 = PhysicsCache.getCachedRigidBody(object1);
var rb2 = PhysicsCache.getCachedRigidBody(object2);
if (rb1 != null && rb2 != null) {
var rbs = PhysicsCache.getCachedContacts(rb1);
return PhysicsCache.hasContactWith(rbs, rb2);
#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;
}
}
#end
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;
}
}

View File

@ -1,54 +1,103 @@
package leenkx.logicnode;
import iron.object.Object;
#if lnx_physics
import leenkx.trait.physics.PhysicsCache;
import leenkx.trait.physics.RigidBody;
#end
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) return;
if (object1 == null) object1 = tree.object;
if (object2 == null) object2 = tree.object;
var contact = false;
#if lnx_physics
var rb1 = PhysicsCache.getCachedRigidBody(object1);
var rb2 = PhysicsCache.getCachedRigidBody(object2);
if (rb1 != null && rb2 != null) {
var rbs = PhysicsCache.getCachedContacts(rb1);
contact = PhysicsCache.hasContactWith(rbs, rb2);
}
#end
var b = false;
switch (property0) {
case "begin":
b = contact && !lastContact;
case "overlap":
b = contact;
case "end":
b = !contact && lastContact;
#if lnx_physics
// 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
}
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;
}
}

View File

@ -1,98 +0,0 @@
package leenkx.trait.physics;
import iron.object.Object;
class PhysicsCache {
#if lnx_physics
static var rbCache: Map<Int, Dynamic> = new Map();
static var contactsCache: Map<Int, Array<Dynamic>> = new Map();
static var physicsFrame: Int = 0;
static var lastQueryFrame: Int = -1;
#end
public static function getCachedRigidBody(object: Object): Dynamic {
#if (!lnx_physics)
return null;
#else
if (object == null) return null;
var cached = rbCache.get(object.uid);
if (cached != null) return cached;
#if lnx_bullet
var rb = object.getTrait(leenkx.trait.physics.bullet.RigidBody);
#else
var rb = object.getTrait(leenkx.trait.physics.oimo.RigidBody);
#end
if (rb != null) rbCache.set(object.uid, rb);
return rb;
#end
}
public static function getCachedContacts(rb: Dynamic): Array<Dynamic> {
#if (!lnx_physics)
return null;
#else
if (rb == null) return null;
var rbObjectId = (rb.object != null) ? rb.object.uid : -1;
if (rbObjectId == -1) {
#if lnx_bullet
if (leenkx.trait.physics.bullet.PhysicsWorld.active == null) return null;
return leenkx.trait.physics.bullet.PhysicsWorld.active.getContacts(rb);
#else
if (leenkx.trait.physics.oimo.PhysicsWorld.active == null) return null;
return leenkx.trait.physics.oimo.PhysicsWorld.active.getContacts(rb);
#end
}
if (lastQueryFrame == physicsFrame) {
var cached = contactsCache.get(rbObjectId);
if (cached != null) return cached;
}
lastQueryFrame = physicsFrame;
var cached = contactsCache.get(rbObjectId);
if (cached != null) return cached;
#if lnx_bullet
if (leenkx.trait.physics.bullet.PhysicsWorld.active == null) return null;
var contacts = leenkx.trait.physics.bullet.PhysicsWorld.active.getContacts(rb);
#else
if (leenkx.trait.physics.oimo.PhysicsWorld.active == null) return null;
var contacts = leenkx.trait.physics.oimo.PhysicsWorld.active.getContacts(rb);
#end
if (contacts != null) {
contactsCache.set(rbObjectId, contacts);
}
return contacts;
#end
}
public static inline function hasContactWith(contacts: Array<Dynamic>, target: Dynamic): Bool {
#if (!lnx_physics)
return false;
#else
return contacts != null && target != null && contacts.indexOf(target) >= 0;
#end
}
public static function clearCache() {
#if lnx_physics
rbCache.clear();
contactsCache.clear();
#end
}
public static function clearContactsCache() {
#if lnx_physics
physicsFrame++;
contactsCache.clear();
#end
}
}

View File

@ -8,9 +8,11 @@ class PhysicsWorld extends iron.Trait { public function new() { super(); } }
#else
#if lnx_bullet
typedef PhysicsWorld = leenkx.trait.physics.bullet.PhysicsWorld;
typedef Hit = leenkx.trait.physics.bullet.PhysicsWorld.Hit;
#else
typedef PhysicsWorld = leenkx.trait.physics.oimo.PhysicsWorld;
typedef Hit = leenkx.trait.physics.oimo.PhysicsWorld.Hit;
#end

View File

@ -7,7 +7,6 @@ import iron.system.Time;
import iron.math.Vec4;
import iron.math.Quat;
import iron.math.RayCaster;
import leenkx.trait.physics.PhysicsCache;
class Hit {
@ -146,7 +145,6 @@ class PhysicsWorld extends Trait {
iron.Scene.active.notifyOnRemove(function() {
sceneRemoved = true;
PhysicsCache.clearCache();
});
}
@ -305,8 +303,6 @@ class PhysicsWorld extends Trait {
var t = Time.fixedStep * timeScale * Time.scale;
if (t == 0.0) return; // Simulation paused
PhysicsCache.clearContactsCache();
#if lnx_debug
var startTime = kha.Scheduler.realTime();
#end

View File

@ -1,25 +0,0 @@
from lnx.logicnode.lnx_nodes import *
class AnyContactNode(LnxLogicTreeNode):
"""Activates the output when the rigid body of the connected object makes contact with
any other rigid body.
"""
bl_idname = 'LNAnyContactNode'
bl_label = 'Any Contact'
lnx_section = 'contact'
lnx_version = 1
property0: HaxeEnumProperty(
'property0',
items = [('begin', 'Begin', 'Contact with any object begins'),
('overlap', 'Overlap', 'Contact with any object is happening'),
('end', 'End', 'Contact with any object ends')],
name='', default='begin')
def lnx_init(self, context):
self.add_input('LnxNodeSocketObject', 'RB')
self.add_output('LnxNodeSocketAction', 'Out')
def draw_buttons(self, context, layout):
layout.prop(self, 'property0')

View File

@ -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

View File

@ -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