diff --git a/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx b/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx index 734f6fb..8590b83 100644 --- a/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx +++ b/leenkx/Sources/leenkx/logicnode/AddPhysicsConstraintNode.hx @@ -2,9 +2,11 @@ package leenkx.logicnode; import iron.object.Object; -#if lnx_physics +#if lnx_bullet import leenkx.trait.physics.PhysicsConstraint; import leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintType; +#elseif lnx_oimo +// TODO #end class AddPhysicsConstraintNode extends LogicNode { @@ -25,7 +27,7 @@ class AddPhysicsConstraintNode extends LogicNode { if (pivotObject == null || rb1 == null || rb2 == null) return; -#if lnx_physics +#if lnx_bullet var disableCollisions: Bool = inputs[4].get(); var breakable: Bool = inputs[5].get(); @@ -108,6 +110,8 @@ class AddPhysicsConstraintNode extends LogicNode { } pivotObject.addTrait(con); } +#elseif lnx_oimo +// TODO #end runOutput(0); } diff --git a/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx b/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx index 874235b..3df20cf 100644 --- a/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx +++ b/leenkx/Sources/leenkx/logicnode/AddRigidBodyNode.hx @@ -4,7 +4,7 @@ import iron.object.Object; #if lnx_physics import leenkx.trait.physics.RigidBody; -import leenkx.trait.physics.bullet.RigidBody.Shape; +import leenkx.trait.physics.RigidBody.Shape; #end diff --git a/leenkx/Sources/leenkx/logicnode/ArrayIndexListNode.hx b/leenkx/Sources/leenkx/logicnode/ArrayIndexListNode.hx new file mode 100644 index 0000000..47318ed --- /dev/null +++ b/leenkx/Sources/leenkx/logicnode/ArrayIndexListNode.hx @@ -0,0 +1,26 @@ +package leenkx.logicnode; + +class ArrayIndexListNode extends LogicNode { + + public function new(tree: LogicTree) { + super(tree); + } + + override function get(from: Int): Dynamic { + var array: Array = inputs[0].get(); + array = array.map(item -> Std.string(item)); + var value: Dynamic = inputs[1].get(); + var from: Int = 0; + + var arrayList: Array = []; + + var index: Int = array.indexOf(Std.string(value), from); + + while(index != -1){ + arrayList.push(index); + index = array.indexOf(Std.string(value), index+1); + } + + return arrayList; + } +} \ No newline at end of file diff --git a/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx b/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx index b553a79..9d2e05b 100644 --- a/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx +++ b/leenkx/Sources/leenkx/logicnode/GoToLocationNode.hx @@ -1,7 +1,7 @@ package leenkx.logicnode; #if lnx_physics -import leenkx.trait.physics.bullet.PhysicsWorld; +import leenkx.trait.physics.PhysicsWorld; #end import leenkx.trait.navigation.Navigation; import iron.object.Object; diff --git a/leenkx/Sources/leenkx/logicnode/PhysicsConstraintNode.hx b/leenkx/Sources/leenkx/logicnode/PhysicsConstraintNode.hx index 0bce755..0547aaf 100644 --- a/leenkx/Sources/leenkx/logicnode/PhysicsConstraintNode.hx +++ b/leenkx/Sources/leenkx/logicnode/PhysicsConstraintNode.hx @@ -1,7 +1,7 @@ package leenkx.logicnode; #if lnx_physics -import leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintAxis; +import leenkx.trait.physics.PhysicsConstraint.ConstraintAxis; #end class PhysicsConstraintNode extends LogicNode { diff --git a/leenkx/Sources/leenkx/trait/physics/KinematicCharacterController.hx b/leenkx/Sources/leenkx/trait/physics/KinematicCharacterController.hx index 71c48e5..ad6d2df 100644 --- a/leenkx/Sources/leenkx/trait/physics/KinematicCharacterController.hx +++ b/leenkx/Sources/leenkx/trait/physics/KinematicCharacterController.hx @@ -10,6 +10,10 @@ class KinematicCharacterController extends iron.Trait { public function new() { typedef KinematicCharacterController = leenkx.trait.physics.bullet.KinematicCharacterController; + #else + + typedef KinematicCharacterController = leenkx.trait.physics.oimo.KinematicCharacterController; + #end #end diff --git a/leenkx/Sources/leenkx/trait/physics/PhysicsConstraint.hx b/leenkx/Sources/leenkx/trait/physics/PhysicsConstraint.hx index 5ac6d91..eeb5358 100644 --- a/leenkx/Sources/leenkx/trait/physics/PhysicsConstraint.hx +++ b/leenkx/Sources/leenkx/trait/physics/PhysicsConstraint.hx @@ -3,17 +3,16 @@ package leenkx.trait.physics; #if (!lnx_physics) class PhysicsConstraint extends iron.Trait { public function new() { super(); } } +@:enum abstract ConstraintAxis(Int) from Int to Int { } #else #if lnx_bullet - typedef PhysicsConstraint = leenkx.trait.physics.bullet.PhysicsConstraint; - + typedef ConstraintAxis = leenkx.trait.physics.bullet.PhysicsConstraint.ConstraintAxis; #else - typedef PhysicsConstraint = leenkx.trait.physics.oimo.PhysicsConstraint; - + typedef ConstraintAxis = leenkx.trait.physics.oimo.PhysicsConstraint.ConstraintAxis; #end #end diff --git a/leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx b/leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx index 3e35a03..db6f28a 100644 --- a/leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx +++ b/leenkx/Sources/leenkx/trait/physics/PhysicsWorld.hx @@ -2,6 +2,7 @@ package leenkx.trait.physics; #if (!lnx_physics) +class Hit { } class PhysicsWorld extends iron.Trait { public function new() { super(); } } #else @@ -9,11 +10,11 @@ class PhysicsWorld extends iron.Trait { public function new() { super(); } } #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 #end diff --git a/leenkx/Sources/leenkx/trait/physics/RigidBody.hx b/leenkx/Sources/leenkx/trait/physics/RigidBody.hx index 7247531..2742418 100644 --- a/leenkx/Sources/leenkx/trait/physics/RigidBody.hx +++ b/leenkx/Sources/leenkx/trait/physics/RigidBody.hx @@ -3,17 +3,20 @@ package leenkx.trait.physics; #if (!lnx_physics) class RigidBody extends iron.Trait { public function new() { super(); } } +@:enum abstract Shape(Int) from Int to Int { } #else #if lnx_bullet typedef RigidBody = leenkx.trait.physics.bullet.RigidBody; - + typedef Shape = leenkx.trait.physics.bullet.RigidBody.Shape; + #else typedef RigidBody = leenkx.trait.physics.oimo.RigidBody; - + typedef Shape = leenkx.trait.physics.oimo.RigidBody.Shape; + #end #end diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx b/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx index 8c347a3..8b067a3 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx @@ -1,5 +1,8 @@ package leenkx.trait.physics.bullet; +#if lnx_bullet +import leenkx.trait.physics.bullet.PhysicsWorld.DebugDrawMode; + import bullet.Bt.Vector3; import kha.FastFloat; @@ -18,15 +21,21 @@ class DebugDrawHelper { static inline var contactPointNormalColor = 0xffffffff; static inline var contactPointDrawLifetime = true; + final rayCastColor: Vec4 = new Vec4(0.0, 1.0, 0.0); + final rayCastHitColor: Vec4 = new Vec4(1.0, 0.0, 0.0); + final rayCastHitPointColor: Vec4 = new Vec4(1.0, 1.0, 0.0); + final physicsWorld: PhysicsWorld; final lines: Array = []; final texts: Array = []; var font: kha.Font = null; - var debugMode: PhysicsWorld.DebugDrawMode = NoDebug; + var rayCasts:Array = []; + var debugDrawMode: DebugDrawMode = NoDebug; - public function new(physicsWorld: PhysicsWorld) { + public function new(physicsWorld: PhysicsWorld, debugDrawMode: DebugDrawMode) { this.physicsWorld = physicsWorld; + this.debugDrawMode = debugDrawMode; #if lnx_ui iron.data.Data.getFont(Canvas.defaultFontName, function(defaultFont: kha.Font) { @@ -35,6 +44,11 @@ class DebugDrawHelper { #end iron.App.notifyOnRender2D(onRender); + if (debugDrawMode & DrawRayCast != 0) { + iron.App.notifyOnUpdate(function () { + rayCasts.resize(0); + }); + } } public function drawLine(from: bullet.Bt.Vector3, to: bullet.Bt.Vector3, color: bullet.Bt.Vector3) { @@ -63,25 +77,6 @@ class DebugDrawHelper { } } - // Draws raycast in its own function because - // something is conflicting with the btVector3 and JS pointer wrapping - public function drawRayCast(fx:FastFloat, fy:FastFloat, fz:FastFloat, tx:FastFloat, ty:FastFloat, tz:FastFloat, r:FastFloat, g:FastFloat, b:FastFloat) { - final fromScreenSpace = worldToScreenFast(new Vec4(fx, fy, fz, 1.0)); - final toScreenSpace = worldToScreenFast(new Vec4(tx, ty, tz, 1.0)); - - // TO DO: May still go off screen sides. - if (fromScreenSpace.w == 1 || toScreenSpace.w == 1) { - final color = kha.Color.fromFloats(r, g, b, 1.0); - lines.push({ - fromX: fromScreenSpace.x, - fromY: fromScreenSpace.y, - toX: toScreenSpace.x, - toY: toScreenSpace.y, - color: color - }); - } - } - public function drawContactPoint(pointOnB: Vector3, normalOnB: Vector3, distance: kha.FastFloat, lifeTime: Int, color: Vector3) { #if js pointOnB = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", pointOnB); @@ -126,7 +121,62 @@ class DebugDrawHelper { x: contactPointScreenSpace.x, y: contactPointScreenSpace.y, color: color, - text: Std.string(lifeTime), + text: Std.string(lifeTime), // lifeTime: number of frames the contact point existed + }); + } + } + } + + public function rayCast(rayCastData:TRayCastData) { + rayCasts.push(rayCastData); + } + + function drawRayCast(f: Vec4, t: Vec4, hit: Bool) { + final from = worldToScreenFast(f.clone()); + final to = worldToScreenFast(t.clone()); + var c: kha.Color; + + if (from.w == 1 && to.w == 1) { + if (hit) c = kha.Color.fromFloats(rayCastHitColor.x, rayCastHitColor.y, rayCastHitColor.z); + else c = kha.Color.fromFloats(rayCastColor.x, rayCastColor.y, rayCastColor.z); + + lines.push({ + fromX: from.x, + fromY: from.y, + toX: to.x, + toY: to.y, + color: c + }); + } + } + + function drawHitPoint(hp: Vec4) { + final hitPoint = worldToScreenFast(hp.clone()); + final c = kha.Color.fromFloats(rayCastHitPointColor.x, rayCastHitPointColor.y, rayCastHitPointColor.z); + + if (hitPoint.w == 1) { + lines.push({ + fromX: hitPoint.x - contactPointSizePx, + fromY: hitPoint.y - contactPointSizePx, + toX: hitPoint.x + contactPointSizePx, + toY: hitPoint.y + contactPointSizePx, + color: c + }); + + lines.push({ + fromX: hitPoint.x - contactPointSizePx, + fromY: hitPoint.y + contactPointSizePx, + toX: hitPoint.x + contactPointSizePx, + toY: hitPoint.y - contactPointSizePx, + color: c + }); + + if (font != null) { + texts.push({ + x: hitPoint.x, + y: hitPoint.y, + color: c, + text: 'RAYCAST HIT' }); } } @@ -155,13 +205,13 @@ class DebugDrawHelper { }); } - public function setDebugMode(debugMode: PhysicsWorld.DebugDrawMode) { - this.debugMode = debugMode; + public function setDebugMode(debugDrawMode: DebugDrawMode) { + this.debugDrawMode = debugDrawMode; } - public function getDebugMode(): PhysicsWorld.DebugDrawMode { + public function getDebugMode(): DebugDrawMode { #if js - return debugMode; + return debugDrawMode; #elseif hl return physicsWorld.getDebugDrawMode(); #else @@ -170,7 +220,7 @@ class DebugDrawHelper { } function onRender(g: kha.graphics2.Graphics) { - if (getDebugMode() == NoDebug && !physicsWorld.drawRaycasts) { + if (getDebugMode() == NoDebug) { return; } @@ -179,7 +229,9 @@ class DebugDrawHelper { // will cause Bullet to call the btIDebugDraw callbacks), but this way // we can ensure that--within a frame--the function will not be called // before some user-specific physics update, which would result in a - // one-frame drawing delay... + // one-frame drawing delay... Ideally we would ensure that debugDrawWorld() + // is called when all other (late) update callbacks are already executed... + physicsWorld.world.debugDrawWorld(); g.opacity = 1.0; @@ -199,6 +251,17 @@ class DebugDrawHelper { } texts.resize(0); } + + if (debugDrawMode & DrawRayCast != 0) { + for (rayCastData in rayCasts) { + if (rayCastData.hasHit) { + drawRayCast(rayCastData.from, rayCastData.hitPoint, true); + drawHitPoint(rayCastData.hitPoint); + } else { + drawRayCast(rayCastData.from, rayCastData.to, false); + } + } + } } /** @@ -241,3 +304,14 @@ class TextData { public var color: kha.Color; public var text: String; } + +@:structInit +typedef TRayCastData = { + var from: Vec4; + var to: Vec4; + var hasHit: Bool; + @:optional var hitPoint: Vec4; + @:optional var hitNormal: Vec4; +} + +#end \ No newline at end of file diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx index 39ad145..e2bea17 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx @@ -71,7 +71,6 @@ class PhysicsWorld extends Trait { public var convexHitPointWorld = new Vec4(); public var convexHitNormalWorld = new Vec4(); var pairCache: Bool = false; - public var drawRaycasts: Bool = false; static var nullvec = true; static var vec1: bullet.Bt.Vector3 = null; @@ -102,7 +101,7 @@ class PhysicsWorld extends Trait { - public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, debugDrawMode: DebugDrawMode = NoDebug, drawRaycasts: Bool = false) { + public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, debugDrawMode: DebugDrawMode = NoDebug) { super(); if (nullvec) { @@ -121,7 +120,6 @@ class PhysicsWorld extends Trait { this.timeScale = timeScale; this.maxSteps = maxSteps; this.solverIterations = solverIterations; - this.drawRaycasts = drawRaycasts; // First scene if (active == null) { @@ -408,14 +406,6 @@ class PhysicsWorld extends Trait { var worldDyn: bullet.Bt.DynamicsWorld = world; var worldCol: bullet.Bt.CollisionWorld = worldDyn; - if (this.drawRaycasts && this.debugDrawHelper != null) { - this.debugDrawHelper.drawRayCast( - rayFrom.x(), rayFrom.y(), rayFrom.z(), - rayTo.x(), rayTo.y(), rayTo.z(), - 0.73, 0.341, 1.0 - ); - } - worldCol.rayTest(rayFrom, rayTo, rayCallback); var rb: RigidBody = null; var hitInfo: Hit = null; @@ -441,6 +431,16 @@ class PhysicsWorld extends Trait { #end } + if (getDebugDrawMode() & DrawRayCast != 0) { + debugDrawHelper.rayCast({ + from: from, + to: to, + hasHit: rc.hasHit(), + hitPoint: hitPointWorld, + hitNormal: hitNormalWorld + }); + } + #if js bullet.Bt.Ammo.destroy(rayCallback); #else @@ -519,22 +519,14 @@ class PhysicsWorld extends Trait { public function setDebugDrawMode(debugDrawMode: DebugDrawMode) { if (debugDrawHelper == null) { - // Initialize if helper is null AND (standard debug mode is requested OR our custom raycast drawing is requested) - if (debugDrawMode != NoDebug || this.drawRaycasts) { - initDebugDrawing(); - } - else { - // Helper is null and no debug drawing needed, so exit + if (debugDrawMode == NoDebug) { return; } + initDebugDrawing(debugDrawMode); } - // If we reached here, the helper is initialized (or was already) - // Now set the standard Bullet debug mode on the actual drawer #if js - // Ensure drawer exists before setting mode (might have just been initialized) - var drawer = world.getDebugDrawer(); - if (drawer != null) drawer.setDebugMode(debugDrawMode); + world.getDebugDrawer().setDebugMode(debugDrawMode); #elseif hl hlDebugDrawer_setDebugMode(debugDrawMode); #end @@ -554,8 +546,8 @@ class PhysicsWorld extends Trait { #end } - function initDebugDrawing() { - debugDrawHelper = new DebugDrawHelper(this); + function initDebugDrawing(debugDrawMode: DebugDrawMode) { + debugDrawHelper = new DebugDrawHelper(this, debugDrawMode); #if js final drawer = new bullet.Bt.DebugDrawer(); @@ -691,6 +683,8 @@ enum abstract DebugDrawMode(Int) from Int to Int { **/ var DrawFrames = 1 << 15; + var DrawRayCast = 1 << 16; + @:op(~A) public inline function bitwiseNegate(): DebugDrawMode { return ~this; } diff --git a/leenkx/blender/lnx/exporter.py b/leenkx/blender/lnx/exporter.py index 40b6375..ffb2434 100644 --- a/leenkx/blender/lnx/exporter.py +++ b/leenkx/blender/lnx/exporter.py @@ -324,6 +324,20 @@ class LeenkxExporter: def export_object_transform(self, bobject: bpy.types.Object, o): wrd = bpy.data.worlds['Lnx'] + # HACK: In Blender 4.2.x, each camera must be selected to ensure its matrix is correctly assigned + if bpy.app.version >= (4, 2, 0) and bobject.type == 'CAMERA' and bobject.users_scene: + current_scene = bpy.context.window.scene + + bpy.context.window.scene = bobject.users_scene[0] + bpy.context.view_layer.update() + + bobject.select_set(True) + bpy.context.view_layer.update() + bobject.select_set(False) + + bpy.context.window.scene = current_scene + bpy.context.view_layer.update() + # Static transform o['transform'] = {'values': LeenkxExporter.write_matrix(bobject.matrix_local)} @@ -1552,8 +1566,7 @@ class LeenkxExporter: log.error(e.message) else: # Assume it was caused because of encountering n-gons - log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping. -Make sure the mesh only has tris/quads.""") + log.error(f"""object {bobject.name} contains n-gons in its mesh, so it's impossible to compute tanget space for normal mapping. Make sure the mesh only has tris/quads.""") tangdata = np.empty(num_verts * 3, dtype='