From 90950970f0342ec2d28d93a37e4368ca5d9ef9b4 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Thu, 10 Apr 2025 21:21:21 +0000 Subject: [PATCH 01/12] Update leenkx/blender/lnx/props.py --- leenkx/blender/lnx/props.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/leenkx/blender/lnx/props.py b/leenkx/blender/lnx/props.py index 2bf5636..267cb30 100644 --- a/leenkx/blender/lnx/props.py +++ b/leenkx/blender/lnx/props.py @@ -201,6 +201,10 @@ def init_properties(): name="Collider Wireframes", default=False, description="Draw wireframes of the physics collider meshes and suspensions of raycast vehicle simulations" ) + bpy.types.World.lnx_bullet_dbg_draw_raycast = BoolProperty( + name="Trace Raycast", default=False, + description="Draw raycasts to trace the results" + ) bpy.types.World.lnx_bullet_dbg_draw_aabb = BoolProperty( name="Axis-aligned Minimum Bounding Boxes", default=False, description="Draw axis-aligned minimum bounding boxes (AABBs) of the physics collider meshes" From 96f69a7cfe268da85dbc223fab0e728f3f086422 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Thu, 10 Apr 2025 21:23:53 +0000 Subject: [PATCH 02/12] Update leenkx/blender/lnx/props_ui.py --- leenkx/blender/lnx/props_ui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/leenkx/blender/lnx/props_ui.py b/leenkx/blender/lnx/props_ui.py index 13cd854..3198918 100644 --- a/leenkx/blender/lnx/props_ui.py +++ b/leenkx/blender/lnx/props_ui.py @@ -2756,6 +2756,7 @@ class LNX_PT_BulletDebugDrawingPanel(bpy.types.Panel): col = layout.column(align=False) col.prop(wrd, "lnx_bullet_dbg_draw_wireframe") + col.prop(wrd, "lnx_bullet_dbg_draw_raycast") col.prop(wrd, "lnx_bullet_dbg_draw_aabb") col.prop(wrd, "lnx_bullet_dbg_draw_contact_points") col.prop(wrd, "lnx_bullet_dbg_draw_constraints") From c4b48c2d87e1940abb5530d27ac51649b2e68c06 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Thu, 10 Apr 2025 21:29:34 +0000 Subject: [PATCH 03/12] Update leenkx/blender/lnx/exporter.py --- leenkx/blender/lnx/exporter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/leenkx/blender/lnx/exporter.py b/leenkx/blender/lnx/exporter.py index 362601d..40b6375 100644 --- a/leenkx/blender/lnx/exporter.py +++ b/leenkx/blender/lnx/exporter.py @@ -3035,6 +3035,7 @@ Make sure the mesh only has tris/quads.""") debug_draw_mode |= 16384 if wrd.lnx_bullet_dbg_draw_normals else 0 debug_draw_mode |= 32768 if wrd.lnx_bullet_dbg_draw_axis_gizmo else 0 out_trait['parameters'].append(str(debug_draw_mode)) + out_trait['parameters'].append(str(wrd.lnx_bullet_dbg_draw_raycast).lower()) self.output['traits'].append(out_trait) From 62e52a731637ff85d8fa40e5527e9b1d2bc403ea Mon Sep 17 00:00:00 2001 From: Onek8 Date: Thu, 10 Apr 2025 21:45:24 +0000 Subject: [PATCH 04/12] Update leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx --- .../leenkx/trait/physics/bullet/PhysicsWorld.hx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx index 4517f76..bc50571 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx @@ -71,7 +71,8 @@ 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; static var vec2: bullet.Bt.Vector3 = null; @@ -101,7 +102,7 @@ class PhysicsWorld extends Trait { - public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, debugDrawMode: DebugDrawMode = NoDebug) { + public function new(timeScale = 1.0, maxSteps = 10, solverIterations = 10, debugDrawMode: DebugDrawMode = NoDebug, drawRaycasts: Bool = false) { super(); if (nullvec) { @@ -120,7 +121,8 @@ class PhysicsWorld extends Trait { this.timeScale = timeScale; this.maxSteps = maxSteps; this.solverIterations = solverIterations; - + this.drawRaycasts = drawRaycasts; + // First scene if (active == null) { createPhysics(); @@ -507,10 +509,12 @@ class PhysicsWorld extends Trait { public function setDebugDrawMode(debugDrawMode: DebugDrawMode) { if (debugDrawHelper == null) { - if (debugDrawMode == NoDebug) { + if (debugDrawMode != NoDebug || this.drawRaycasts) { + initDebugDrawing(); + } + else { return; } - initDebugDrawing(); } #if js @@ -526,7 +530,8 @@ class PhysicsWorld extends Trait { } #if js - return world.getDebugDrawer().getDebugMode(); + var drawer = world.getDebugDrawer(); + if (drawer != null) drawer.setDebugMode(debugDrawMode); #elseif hl return hlDebugDrawer_getDebugMode(); #else From d69e3438ff6d96c4893590806f775e44d4eec822 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Thu, 10 Apr 2025 21:55:56 +0000 Subject: [PATCH 05/12] Update leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx --- leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx b/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx index 179cfc5..52d182f 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx @@ -150,7 +150,7 @@ class DebugDrawHelper { } function onRender(g: kha.graphics2.Graphics) { - if (getDebugMode() == NoDebug) { + if (getDebugMode() == NoDebug && !physicsWorld.drawRaycasts) { return; } From 1944fc97b8e5a947203bf9289d68b9f03cd8351b Mon Sep 17 00:00:00 2001 From: Onek8 Date: Thu, 10 Apr 2025 22:22:46 +0000 Subject: [PATCH 06/12] Update leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx --- leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx index bc50571..333c410 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx @@ -530,8 +530,7 @@ class PhysicsWorld extends Trait { } #if js - var drawer = world.getDebugDrawer(); - if (drawer != null) drawer.setDebugMode(debugDrawMode); + return world.getDebugDrawer().getDebugMode(); #elseif hl return hlDebugDrawer_getDebugMode(); #else From 07d98639f26465419eb98bbbe2cba8b26246a511 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Fri, 11 Apr 2025 08:17:40 +0000 Subject: [PATCH 07/12] Update leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx --- .../trait/physics/bullet/DebugDrawHelper.hx | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx b/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx index 52d182f..8c347a3 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/DebugDrawHelper.hx @@ -49,9 +49,10 @@ class DebugDrawHelper { final fromScreenSpace = worldToScreenFast(new Vec4(from.x(), from.y(), from.z(), 1.0)); final toScreenSpace = worldToScreenFast(new Vec4(to.x(), to.y(), to.z(), 1.0)); - // For now don't draw lines if any point is outside of clip space z, // investigate how to clamp lines to clip space borders - if (fromScreenSpace.w == 1 && toScreenSpace.w == 1) { + // If at least one point is within the Z clip space (w==1), attempt to draw. + // Note: This is not full clipping, line may still go off screen sides. + if (fromScreenSpace.w == 1 || toScreenSpace.w == 1) { lines.push({ fromX: fromScreenSpace.x, fromY: fromScreenSpace.y, @@ -62,6 +63,25 @@ 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); @@ -106,7 +126,7 @@ class DebugDrawHelper { x: contactPointScreenSpace.x, y: contactPointScreenSpace.y, color: color, - text: Std.string(lifeTime), // lifeTime: number of frames the contact point existed + text: Std.string(lifeTime), }); } } @@ -159,8 +179,7 @@ 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... Ideally we would ensure that debugDrawWorld() - // is called when all other (late) update callbacks are already executed... + // one-frame drawing delay... physicsWorld.world.debugDrawWorld(); g.opacity = 1.0; From 8d1c2c51bd1c11a3c745a4e68d26e6b0023f56a4 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Fri, 11 Apr 2025 08:18:30 +0000 Subject: [PATCH 08/12] Update leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx --- .../trait/physics/bullet/PhysicsWorld.hx | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx index 333c410..4d205a4 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx @@ -72,7 +72,7 @@ class PhysicsWorld extends Trait { 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; static var vec2: bullet.Bt.Vector3 = null; @@ -122,7 +122,7 @@ class PhysicsWorld extends Trait { this.maxSteps = maxSteps; this.solverIterations = solverIterations; this.drawRaycasts = drawRaycasts; - + // First scene if (active == null) { createPhysics(); @@ -397,6 +397,7 @@ class PhysicsWorld extends Trait { rayTo.setValue(to.x, to.y, to.z); var rayCallback = new bullet.Bt.ClosestRayResultCallback(rayFrom, rayTo); + #if js rayCallback.set_m_collisionFilterGroup(group); rayCallback.set_m_collisionFilterMask(mask); @@ -406,12 +407,20 @@ class PhysicsWorld extends Trait { #end 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; - var rc: bullet.Bt.RayResultCallback = rayCallback; - if (rc.hasHit()) { + if (rayCallback.hasHit()) { #if js var co = rayCallback.get_m_collisionObject(); var body = untyped bullet.Bt.Ammo.btRigidBody.prototype.upcast(co); @@ -471,8 +480,7 @@ class PhysicsWorld extends Trait { var hitInfo: ConvexHit = null; - var cc: bullet.Bt.ClosestConvexResultCallback = convexCallback; - if (cc.hasHit()) { + if (convexCallback.hasHit()) { #if js var hit = convexCallback.get_m_hitPointWorld(); convexHitPointWorld.set(hit.x(), hit.y(), hit.z()); @@ -509,16 +517,22 @@ 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 return; } } + // If we reached here, the helper is initialized (or was already) + // Now set the standard Bullet debug mode on the actual drawer #if js - world.getDebugDrawer().setDebugMode(debugDrawMode); + // Ensure drawer exists before setting mode (might have just been initialized) + var drawer = world.getDebugDrawer(); + if (drawer != null) drawer.setDebugMode(debugDrawMode); #elseif hl hlDebugDrawer_setDebugMode(debugDrawMode); #end @@ -665,10 +679,7 @@ enum abstract DebugDrawMode(Int) from Int to Int { // We could use it in the future to toggle depth testing for lines, i.e. draw actual 3D lines if not set and Kha's g2 lines if set. var FastWireframe = 1 << 13; - /** - Draw the normal vectors of the triangles of the physics collider meshes. - This only works for `Mesh` collision shapes. - **/ + /** Draw the normal vectors of the triangles of the physics collider meshes. **/ // Outside of Leenkx this works for a few more collision shapes var DrawNormals = 1 << 14; From 94eaba731989b7d1d15f70a41b8357fb34201212 Mon Sep 17 00:00:00 2001 From: Onek8 Date: Fri, 11 Apr 2025 08:22:17 +0000 Subject: [PATCH 09/12] Update leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx --- leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx index 4d205a4..f72b5ee 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx @@ -420,7 +420,8 @@ class PhysicsWorld extends Trait { var rb: RigidBody = null; var hitInfo: Hit = null; - if (rayCallback.hasHit()) { + var rc: bullet.Bt.RayResultCallback = rayCallback; + if (rc.hasHit()) { #if js var co = rayCallback.get_m_collisionObject(); var body = untyped bullet.Bt.Ammo.btRigidBody.prototype.upcast(co); @@ -480,7 +481,8 @@ class PhysicsWorld extends Trait { var hitInfo: ConvexHit = null; - if (convexCallback.hasHit()) { + var cc: bullet.Bt.ClosestConvexResultCallback = convexCallback; + if (cc.hasHit()) { #if js var hit = convexCallback.get_m_hitPointWorld(); convexHitPointWorld.set(hit.x(), hit.y(), hit.z()); From 27bd3603175d476b8b1483d1fde3dd76c4ea4f21 Mon Sep 17 00:00:00 2001 From: LeenkxTeam Date: Fri, 11 Apr 2025 16:28:33 +0000 Subject: [PATCH 10/12] Update lib/haxebullet/hl/bullet.cpp --- lib/haxebullet/hl/bullet.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/haxebullet/hl/bullet.cpp b/lib/haxebullet/hl/bullet.cpp index 1411948..9656baa 100644 --- a/lib/haxebullet/hl/bullet.cpp +++ b/lib/haxebullet/hl/bullet.cpp @@ -579,6 +579,18 @@ HL_PRIM float HL_NAME(btVector3_length0)(_ref(btVector3)* _this) { } DEFINE_PRIM(_F32, btVector3_length0, _IDL); +HL_PRIM float* HL_NAME(btFloatArray_set_raw)( _ref(btFloatArray)* _this, float* value ) { + _unref(_this)->raw = (value); + return value; +} +DEFINE_PRIM(_BYTES,btFloatArray_set_raw,_IDL _BYTES); + +HL_PRIM int* HL_NAME(btIntArray_set_raw)( _ref(btIntArray)* _this, int* value ) { + _unref(_this)->raw = (value); + return value; +} +DEFINE_PRIM(_BYTES,btIntArray_get_raw,_IDL); + HL_PRIM float HL_NAME(btVector3_x0)(_ref(btVector3)* _this) { return _unref(_this)->x(); } From c08e1d38354dc6cb55198587879a0fa5f8a153a3 Mon Sep 17 00:00:00 2001 From: LeenkxTeam Date: Fri, 11 Apr 2025 21:04:00 +0000 Subject: [PATCH 11/12] Update leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx --- leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx index f72b5ee..39ad145 100644 --- a/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx +++ b/leenkx/Sources/leenkx/trait/physics/bullet/PhysicsWorld.hx @@ -182,8 +182,8 @@ class PhysicsWorld extends Trait { #else world = new bullet.Bt.DiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration); #end - // Set the solver iterations from Blender's rigid body world - setSolverIterations(world, solverIterations); + // TO DO: Set the solver iterations from Blender's rigid body world + // setSolverIterations(world, solverIterations); setGravity(gravity); } From 06319131fd8ea54c2c26bca42895eb8edee7ed95 Mon Sep 17 00:00:00 2001 From: LeenkxTeam Date: Fri, 11 Apr 2025 21:16:04 +0000 Subject: [PATCH 12/12] Update leenkx/blender/lnx/handlers.py --- leenkx/blender/lnx/handlers.py | 37 ++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/leenkx/blender/lnx/handlers.py b/leenkx/blender/lnx/handlers.py index 54fab49..80cbdb5 100644 --- a/leenkx/blender/lnx/handlers.py +++ b/leenkx/blender/lnx/handlers.py @@ -132,6 +132,7 @@ def always() -> float: return 0.5 + def poll_threads() -> float: """Polls the thread callback queue and if a thread has finished, it is joined with the main thread and the corresponding callback is @@ -141,21 +142,27 @@ def poll_threads() -> float: thread, callback = make.thread_callback_queue.get(block=False) except queue.Empty: return 0.25 - - thread.join() - - try: - callback() - except Exception as e: - # If there is an exception, we can no longer return the time to - # the next call to this polling function, so to keep it running - # we re-register it and then raise the original exception. - bpy.app.timers.unregister(poll_threads) - bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True) - raise e - - # Quickly check if another thread has finished - return 0.01 + if thread.is_alive(): + try: + make.thread_callback_queue.put((thread, callback), block=False) + except queue.Full: + return 0.5 + return 0.1 + else: + try: + thread.join() + callback() + except Exception as e: + # If there is an exception, we can no longer return the time to + # the next call to this polling function, so to keep it running + # we re-register it and then raise the original exception. + try: + bpy.app.timers.unregister(poll_threads) + except ValueError: + pass + bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True) + # Quickly check if another thread has finished + return 0.01 loaded_py_libraries: dict[str, types.ModuleType] = {}