Merge pull request 'Debug Raycast Drawing' (#45) from Onek8/LNXSDK:main into main

Reviewed-on: LeenkxTeam/LNXSDK#45
This commit is contained in:
LeenkxTeam 2025-04-11 08:37:13 +00:00
commit 82a53a868a
5 changed files with 56 additions and 14 deletions

View File

@ -49,9 +49,10 @@ class DebugDrawHelper {
final fromScreenSpace = worldToScreenFast(new Vec4(from.x(), from.y(), from.z(), 1.0)); 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)); 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 // 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({ lines.push({
fromX: fromScreenSpace.x, fromX: fromScreenSpace.x,
fromY: fromScreenSpace.y, 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) { public function drawContactPoint(pointOnB: Vector3, normalOnB: Vector3, distance: kha.FastFloat, lifeTime: Int, color: Vector3) {
#if js #if js
pointOnB = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", pointOnB); pointOnB = js.Syntax.code("Ammo.wrapPointer({0}, Ammo.btVector3)", pointOnB);
@ -106,7 +126,7 @@ class DebugDrawHelper {
x: contactPointScreenSpace.x, x: contactPointScreenSpace.x,
y: contactPointScreenSpace.y, y: contactPointScreenSpace.y,
color: color, color: color,
text: Std.string(lifeTime), // lifeTime: number of frames the contact point existed text: Std.string(lifeTime),
}); });
} }
} }
@ -150,7 +170,7 @@ class DebugDrawHelper {
} }
function onRender(g: kha.graphics2.Graphics) { function onRender(g: kha.graphics2.Graphics) {
if (getDebugMode() == NoDebug) { if (getDebugMode() == NoDebug && !physicsWorld.drawRaycasts) {
return; return;
} }
@ -159,8 +179,7 @@ class DebugDrawHelper {
// will cause Bullet to call the btIDebugDraw callbacks), but this way // will cause Bullet to call the btIDebugDraw callbacks), but this way
// we can ensure that--within a frame--the function will not be called // we can ensure that--within a frame--the function will not be called
// before some user-specific physics update, which would result in a // before some user-specific physics update, which would result in a
// one-frame drawing delay... Ideally we would ensure that debugDrawWorld() // one-frame drawing delay...
// is called when all other (late) update callbacks are already executed...
physicsWorld.world.debugDrawWorld(); physicsWorld.world.debugDrawWorld();
g.opacity = 1.0; g.opacity = 1.0;

View File

@ -71,6 +71,7 @@ class PhysicsWorld extends Trait {
public var convexHitPointWorld = new Vec4(); public var convexHitPointWorld = new Vec4();
public var convexHitNormalWorld = new Vec4(); public var convexHitNormalWorld = new Vec4();
var pairCache: Bool = false; var pairCache: Bool = false;
public var drawRaycasts: Bool = false;
static var nullvec = true; static var nullvec = true;
static var vec1: bullet.Bt.Vector3 = null; static var vec1: 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(); super();
if (nullvec) { if (nullvec) {
@ -120,6 +121,7 @@ class PhysicsWorld extends Trait {
this.timeScale = timeScale; this.timeScale = timeScale;
this.maxSteps = maxSteps; this.maxSteps = maxSteps;
this.solverIterations = solverIterations; this.solverIterations = solverIterations;
this.drawRaycasts = drawRaycasts;
// First scene // First scene
if (active == null) { if (active == null) {
@ -395,6 +397,7 @@ class PhysicsWorld extends Trait {
rayTo.setValue(to.x, to.y, to.z); rayTo.setValue(to.x, to.y, to.z);
var rayCallback = new bullet.Bt.ClosestRayResultCallback(rayFrom, rayTo); var rayCallback = new bullet.Bt.ClosestRayResultCallback(rayFrom, rayTo);
#if js #if js
rayCallback.set_m_collisionFilterGroup(group); rayCallback.set_m_collisionFilterGroup(group);
rayCallback.set_m_collisionFilterMask(mask); rayCallback.set_m_collisionFilterMask(mask);
@ -404,6 +407,15 @@ class PhysicsWorld extends Trait {
#end #end
var worldDyn: bullet.Bt.DynamicsWorld = world; var worldDyn: bullet.Bt.DynamicsWorld = world;
var worldCol: bullet.Bt.CollisionWorld = worldDyn; 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); worldCol.rayTest(rayFrom, rayTo, rayCallback);
var rb: RigidBody = null; var rb: RigidBody = null;
var hitInfo: Hit = null; var hitInfo: Hit = null;
@ -507,14 +519,22 @@ class PhysicsWorld extends Trait {
public function setDebugDrawMode(debugDrawMode: DebugDrawMode) { public function setDebugDrawMode(debugDrawMode: DebugDrawMode) {
if (debugDrawHelper == null) { if (debugDrawHelper == null) {
if (debugDrawMode == NoDebug) { // Initialize if helper is null AND (standard debug mode is requested OR our custom raycast drawing is requested)
return; if (debugDrawMode != NoDebug || this.drawRaycasts) {
}
initDebugDrawing(); 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 #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 #elseif hl
hlDebugDrawer_setDebugMode(debugDrawMode); hlDebugDrawer_setDebugMode(debugDrawMode);
#end #end
@ -661,10 +681,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. // 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; var FastWireframe = 1 << 13;
/** /** Draw the normal vectors of the triangles of the physics collider meshes. **/
Draw the normal vectors of the triangles of the physics collider meshes.
This only works for `Mesh` collision shapes.
**/
// Outside of Leenkx this works for a few more collision shapes // Outside of Leenkx this works for a few more collision shapes
var DrawNormals = 1 << 14; var DrawNormals = 1 << 14;

View File

@ -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 |= 16384 if wrd.lnx_bullet_dbg_draw_normals else 0
debug_draw_mode |= 32768 if wrd.lnx_bullet_dbg_draw_axis_gizmo 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(debug_draw_mode))
out_trait['parameters'].append(str(wrd.lnx_bullet_dbg_draw_raycast).lower())
self.output['traits'].append(out_trait) self.output['traits'].append(out_trait)

View File

@ -201,6 +201,10 @@ def init_properties():
name="Collider Wireframes", default=False, name="Collider Wireframes", default=False,
description="Draw wireframes of the physics collider meshes and suspensions of raycast vehicle simulations" 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( bpy.types.World.lnx_bullet_dbg_draw_aabb = BoolProperty(
name="Axis-aligned Minimum Bounding Boxes", default=False, name="Axis-aligned Minimum Bounding Boxes", default=False,
description="Draw axis-aligned minimum bounding boxes (AABBs) of the physics collider meshes" description="Draw axis-aligned minimum bounding boxes (AABBs) of the physics collider meshes"

View File

@ -2756,6 +2756,7 @@ class LNX_PT_BulletDebugDrawingPanel(bpy.types.Panel):
col = layout.column(align=False) col = layout.column(align=False)
col.prop(wrd, "lnx_bullet_dbg_draw_wireframe") 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_aabb")
col.prop(wrd, "lnx_bullet_dbg_draw_contact_points") col.prop(wrd, "lnx_bullet_dbg_draw_contact_points")
col.prop(wrd, "lnx_bullet_dbg_draw_constraints") col.prop(wrd, "lnx_bullet_dbg_draw_constraints")