@@ -233,6 +233,158 @@ def invert_3d_rotational_dofs(
233233 return wp .vec3 (angles [0 ], angles [1 ], s * angles [2 ]), wp .vec3 (velocities [0 ], velocities [1 ], s * velocities [2 ])
234234
235235
236+ @wp .func
237+ def eval_joint_motion (
238+ type : int ,
239+ q_start : int ,
240+ qd_start : int ,
241+ lin_axis_count : int ,
242+ ang_axis_count : int ,
243+ joint_q : wp .array [float ],
244+ joint_qd : wp .array [float ],
245+ joint_axis : wp .array [wp .vec3 ],
246+ ):
247+ X_j = wp .transform_identity ()
248+ v_j = wp .spatial_vector (wp .vec3 (), wp .vec3 ())
249+
250+ if type == JointType .PRISMATIC :
251+ axis = joint_axis [qd_start ]
252+ X_j = wp .transform (axis * joint_q [q_start ], wp .quat_identity ())
253+ v_j = wp .spatial_vector (axis * joint_qd [qd_start ], wp .vec3 ())
254+
255+ if type == JointType .REVOLUTE :
256+ axis = joint_axis [qd_start ]
257+ X_j = wp .transform (wp .vec3 (), wp .quat_from_axis_angle (axis , joint_q [q_start ]))
258+ v_j = wp .spatial_vector (wp .vec3 (), axis * joint_qd [qd_start ])
259+
260+ if type == JointType .BALL :
261+ X_j = wp .transform (
262+ wp .vec3 (),
263+ wp .quat (joint_q [q_start ], joint_q [q_start + 1 ], joint_q [q_start + 2 ], joint_q [q_start + 3 ]),
264+ )
265+ v_j = wp .spatial_vector (
266+ wp .vec3 (),
267+ wp .vec3 (joint_qd [qd_start ], joint_qd [qd_start + 1 ], joint_qd [qd_start + 2 ]),
268+ )
269+
270+ if type == JointType .FREE or type == JointType .DISTANCE :
271+ X_j = wp .transform (
272+ wp .vec3 (joint_q [q_start ], joint_q [q_start + 1 ], joint_q [q_start + 2 ]),
273+ wp .quat (
274+ joint_q [q_start + 3 ],
275+ joint_q [q_start + 4 ],
276+ joint_q [q_start + 5 ],
277+ joint_q [q_start + 6 ],
278+ ),
279+ )
280+ v_j = wp .spatial_vector (
281+ wp .vec3 (joint_qd [qd_start ], joint_qd [qd_start + 1 ], joint_qd [qd_start + 2 ]),
282+ wp .vec3 (joint_qd [qd_start + 3 ], joint_qd [qd_start + 4 ], joint_qd [qd_start + 5 ]),
283+ )
284+
285+ if type == JointType .D6 :
286+ pos = wp .vec3 (0.0 )
287+ rot = wp .quat_identity ()
288+ vel_v = wp .vec3 (0.0 )
289+ vel_w = wp .vec3 (0.0 )
290+
291+ # Keep these updates unrolled so joint actions remain differentiable.
292+ if lin_axis_count > 0 :
293+ axis = joint_axis [qd_start ]
294+ pos += axis * joint_q [q_start ]
295+ vel_v += axis * joint_qd [qd_start ]
296+ if lin_axis_count > 1 :
297+ axis = joint_axis [qd_start + 1 ]
298+ pos += axis * joint_q [q_start + 1 ]
299+ vel_v += axis * joint_qd [qd_start + 1 ]
300+ if lin_axis_count > 2 :
301+ axis = joint_axis [qd_start + 2 ]
302+ pos += axis * joint_q [q_start + 2 ]
303+ vel_v += axis * joint_qd [qd_start + 2 ]
304+
305+ iq = q_start + lin_axis_count
306+ iqd = qd_start + lin_axis_count
307+ if ang_axis_count == 1 :
308+ axis = joint_axis [iqd ]
309+ rot = wp .quat_from_axis_angle (axis , joint_q [iq ])
310+ vel_w = joint_qd [iqd ] * axis
311+ if ang_axis_count == 2 :
312+ rot , vel_w = compute_2d_rotational_dofs (
313+ joint_axis [iqd ],
314+ joint_axis [iqd + 1 ],
315+ joint_q [iq ],
316+ joint_q [iq + 1 ],
317+ joint_qd [iqd ],
318+ joint_qd [iqd + 1 ],
319+ )
320+ if ang_axis_count == 3 :
321+ rot , vel_w = compute_3d_rotational_dofs (
322+ joint_axis [iqd ],
323+ joint_axis [iqd + 1 ],
324+ joint_axis [iqd + 2 ],
325+ joint_q [iq ],
326+ joint_q [iq + 1 ],
327+ joint_q [iq + 2 ],
328+ joint_qd [iqd ],
329+ joint_qd [iqd + 1 ],
330+ joint_qd [iqd + 2 ],
331+ )
332+
333+ X_j = wp .transform (pos , rot )
334+ v_j = wp .spatial_vector (vel_v , vel_w )
335+
336+ return X_j , v_j
337+
338+
339+ @wp .func
340+ def eval_joint_child_state (
341+ type : int ,
342+ parent : int ,
343+ child : int ,
344+ X_wp : wp .transform ,
345+ X_pj : wp .transform ,
346+ X_cj : wp .transform ,
347+ X_j : wp .transform ,
348+ v_j : wp .spatial_vector ,
349+ body_qd : wp .array [wp .spatial_vector ],
350+ body_com : wp .array [wp .vec3 ],
351+ ):
352+ X_wpj = X_pj
353+ if parent >= 0 :
354+ X_wpj = X_wp * X_pj
355+
356+ X_wcj = X_wpj * X_j
357+ X_wc = X_wcj * wp .transform_inverse (X_cj )
358+ x_child_origin = wp .transform_get_translation (X_wc )
359+
360+ v_parent_origin = wp .vec3 ()
361+ w_parent = wp .vec3 ()
362+ if parent >= 0 :
363+ v_wp = body_qd [parent ]
364+ w_parent = wp .spatial_bottom (v_wp )
365+ v_parent_origin = com_twist_to_point_velocity (v_wp , X_wp , body_com [parent ], x_child_origin )
366+
367+ linear_joint_world = wp .transform_vector (X_wpj , wp .spatial_top (v_j ))
368+ angular_joint_world = wp .transform_vector (X_wpj , wp .spatial_bottom (v_j ))
369+ if type == JointType .FREE or type == JointType .DISTANCE :
370+ # These joint velocities are COM-referenced, while the tree recurrence is origin-referenced.
371+ v_joint_origin = com_twist_to_origin_twist (
372+ wp .spatial_vector (linear_joint_world , angular_joint_world ),
373+ X_wc ,
374+ body_com [child ],
375+ )
376+ linear_joint_origin = wp .spatial_top (v_joint_origin )
377+ else :
378+ child_origin_offset_world = x_child_origin - wp .transform_get_translation (X_wcj )
379+ linear_joint_origin = linear_joint_world + wp .cross (angular_joint_world , child_origin_offset_world )
380+
381+ v_wc_origin = wp .spatial_vector (
382+ v_parent_origin + linear_joint_origin ,
383+ w_parent + angular_joint_world ,
384+ )
385+ return X_wc , origin_twist_to_com_twist (v_wc_origin , X_wc , body_com [child ])
386+
387+
236388@wp .func
237389def eval_single_articulation_fk (
238390 joint_start : int ,
@@ -278,149 +430,36 @@ def eval_single_articulation_fk(
278430 lin_axis_count = joint_dof_dim [i , 0 ]
279431 ang_axis_count = joint_dof_dim [i , 1 ]
280432
281- X_j = wp .transform_identity ()
282- v_j = wp .spatial_vector (wp .vec3 (), wp .vec3 ())
283-
284- if type == JointType .PRISMATIC :
285- axis = joint_axis [qd_start ]
286-
287- q = joint_q [q_start ]
288- qd = joint_qd [qd_start ]
289-
290- X_j = wp .transform (axis * q , wp .quat_identity ())
291- v_j = wp .spatial_vector (axis * qd , wp .vec3 ())
292-
293- if type == JointType .REVOLUTE :
294- axis = joint_axis [qd_start ]
295-
296- q = joint_q [q_start ]
297- qd = joint_qd [qd_start ]
298-
299- X_j = wp .transform (wp .vec3 (), wp .quat_from_axis_angle (axis , q ))
300- v_j = wp .spatial_vector (wp .vec3 (), axis * qd )
301-
302- if type == JointType .BALL :
303- r = wp .quat (joint_q [q_start + 0 ], joint_q [q_start + 1 ], joint_q [q_start + 2 ], joint_q [q_start + 3 ])
304-
305- w = wp .vec3 (joint_qd [qd_start + 0 ], joint_qd [qd_start + 1 ], joint_qd [qd_start + 2 ])
306-
307- X_j = wp .transform (wp .vec3 (), r )
308- v_j = wp .spatial_vector (wp .vec3 (), w )
309-
310- if type == JointType .FREE or type == JointType .DISTANCE :
311- t = wp .transform (
312- wp .vec3 (joint_q [q_start + 0 ], joint_q [q_start + 1 ], joint_q [q_start + 2 ]),
313- wp .quat (joint_q [q_start + 3 ], joint_q [q_start + 4 ], joint_q [q_start + 5 ], joint_q [q_start + 6 ]),
314- )
315-
316- v = wp .spatial_vector (
317- wp .vec3 (joint_qd [qd_start + 0 ], joint_qd [qd_start + 1 ], joint_qd [qd_start + 2 ]),
318- wp .vec3 (joint_qd [qd_start + 3 ], joint_qd [qd_start + 4 ], joint_qd [qd_start + 5 ]),
319- )
433+ X_j , v_j = eval_joint_motion (
434+ type ,
435+ q_start ,
436+ qd_start ,
437+ lin_axis_count ,
438+ ang_axis_count ,
439+ joint_q ,
440+ joint_qd ,
441+ joint_axis ,
442+ )
320443
321- X_j = t
322- v_j = v
323-
324- if type == JointType .D6 :
325- pos = wp .vec3 (0.0 )
326- rot = wp .quat_identity ()
327- vel_v = wp .vec3 (0.0 )
328- vel_w = wp .vec3 (0.0 )
329-
330- # unroll for loop to ensure joint actions remain differentiable
331- # (since differentiating through a for loop that updates a local variable is not supported)
332-
333- if lin_axis_count > 0 :
334- axis = joint_axis [qd_start + 0 ]
335- pos += axis * joint_q [q_start + 0 ]
336- vel_v += axis * joint_qd [qd_start + 0 ]
337- if lin_axis_count > 1 :
338- axis = joint_axis [qd_start + 1 ]
339- pos += axis * joint_q [q_start + 1 ]
340- vel_v += axis * joint_qd [qd_start + 1 ]
341- if lin_axis_count > 2 :
342- axis = joint_axis [qd_start + 2 ]
343- pos += axis * joint_q [q_start + 2 ]
344- vel_v += axis * joint_qd [qd_start + 2 ]
345-
346- iq = q_start + lin_axis_count
347- iqd = qd_start + lin_axis_count
348- if ang_axis_count == 1 :
349- axis = joint_axis [iqd ]
350- rot = wp .quat_from_axis_angle (axis , joint_q [iq ])
351- vel_w = joint_qd [iqd ] * axis
352- if ang_axis_count == 2 :
353- rot , vel_w = compute_2d_rotational_dofs (
354- joint_axis [iqd + 0 ],
355- joint_axis [iqd + 1 ],
356- joint_q [iq + 0 ],
357- joint_q [iq + 1 ],
358- joint_qd [iqd + 0 ],
359- joint_qd [iqd + 1 ],
360- )
361- if ang_axis_count == 3 :
362- rot , vel_w = compute_3d_rotational_dofs (
363- joint_axis [iqd + 0 ],
364- joint_axis [iqd + 1 ],
365- joint_axis [iqd + 2 ],
366- joint_q [iq + 0 ],
367- joint_q [iq + 1 ],
368- joint_q [iq + 2 ],
369- joint_qd [iqd + 0 ],
370- joint_qd [iqd + 1 ],
371- joint_qd [iqd + 2 ],
372- )
373-
374- X_j = wp .transform (pos , rot )
375- v_j = wp .spatial_vector (vel_v , vel_w )
376-
377- # transform from world to parent joint anchor frame
378- X_wpj = X_pj
444+ X_wp = wp .transform_identity ()
379445 if parent >= 0 :
380446 X_wp = body_q [parent ]
381- X_wpj = X_wp * X_wpj
382-
383- # transform from world to joint anchor frame at child body
384- X_wcj = X_wpj * X_j
385- # transform from world to child body frame
386- X_wc = X_wcj * wp .transform_inverse (X_cj )
387-
388- # Velocity must be evaluated at the actual child-body origin. For translated
389- # joints, sampling parent motion only at the fixed
390- # parent anchor misses the transport term from the current joint displacement.
391- x_child_origin = wp .transform_get_translation (X_wc )
392- v_parent_origin = wp .vec3 ()
393- w_parent = wp .vec3 ()
394- if parent >= 0 :
395- v_wp = body_qd [parent ]
396- w_parent = wp .spatial_bottom (v_wp )
397- v_parent_origin = com_twist_to_point_velocity (v_wp , X_wp , body_com [parent ], x_child_origin )
398-
399- # Transform joint motion into world space.
400- linear_joint_world = wp .transform_vector (X_wpj , wp .spatial_top (v_j ))
401- angular_joint_world = wp .transform_vector (X_wpj , wp .spatial_bottom (v_j ))
402- if type == JointType .FREE or type == JointType .DISTANCE :
403- # FREE / DISTANCE joint linear DOFs follow Newton's COM-velocity
404- # convention, so convert the relative child COM twist to an
405- # origin-referenced twist before the tree recurrence.
406- v_joint_origin = com_twist_to_origin_twist (
407- wp .spatial_vector (linear_joint_world , angular_joint_world ),
408- X_wc ,
409- body_com [child ],
410- )
411- linear_joint_origin = wp .spatial_top (v_joint_origin )
412- else :
413- # The linear part of v_j is defined at the child joint anchor; if the
414- # child body origin is offset from that anchor, transport the joint
415- # angular motion to the body origin.
416- child_origin_offset_world = x_child_origin - wp .transform_get_translation (X_wcj )
417- linear_joint_origin = linear_joint_world + wp .cross (angular_joint_world , child_origin_offset_world )
418-
419- v_wc_origin = wp .spatial_vector (v_parent_origin + linear_joint_origin , w_parent + angular_joint_world )
447+ X_wc , v_wc = eval_joint_child_state (
448+ type ,
449+ parent ,
450+ child ,
451+ X_wp ,
452+ X_pj ,
453+ X_cj ,
454+ X_j ,
455+ v_j ,
456+ body_qd ,
457+ body_com ,
458+ )
420459
421460 if (body_flags [child ] & body_flag_filter ) != 0 :
422461 body_q [child ] = X_wc
423- body_qd [child ] = origin_twist_to_com_twist ( v_wc_origin , X_wc , body_com [ child ])
462+ body_qd [child ] = v_wc
424463
425464
426465@wp .kernel
@@ -540,6 +579,62 @@ def eval_fk(
540579 else :
541580 num_articulations = model .articulation_count
542581
582+ if num_articulations == 0 :
583+ return
584+
585+ if model .device .is_cuda and model ._fk_articulation_level_start is not None :
586+ requires_grad = (
587+ model .requires_grad
588+ or joint_q .requires_grad
589+ or joint_qd .requires_grad
590+ or model .joint_X_p .requires_grad
591+ or model .joint_X_c .requires_grad
592+ or model .joint_axis .requires_grad
593+ or model .body_com .requires_grad
594+ or state .body_q .requires_grad
595+ or state .body_qd .requires_grad
596+ )
597+ if not requires_grad :
598+ from .articulation_cuda import TILE_BLOCK_DIM , create_eval_articulation_fk_tile # noqa: PLC0415
599+
600+ kernel = create_eval_articulation_fk_tile (
601+ model ._fk_level_capacity ,
602+ body_flag_filter == BodyFlags .ALL ,
603+ model ._has_rod_joints ,
604+ )
605+ inputs = [
606+ model ._fk_articulation_level_start ,
607+ model ._fk_level_joint_start ,
608+ model ._fk_level_joints ,
609+ model ._fk_level_parent_pos ,
610+ model .articulation_count ,
611+ mask ,
612+ indices ,
613+ joint_q ,
614+ joint_qd ,
615+ model .joint_q_start ,
616+ model .joint_qd_start ,
617+ model .joint_type ,
618+ model .joint_parent ,
619+ model .joint_child ,
620+ model .joint_X_p ,
621+ model .joint_X_c ,
622+ model .joint_axis ,
623+ model .joint_dof_dim ,
624+ model .body_com ,
625+ model .body_flags ,
626+ body_flag_filter ,
627+ ]
628+ wp .launch_tiled (
629+ kernel = kernel ,
630+ dim = [num_articulations ],
631+ block_dim = TILE_BLOCK_DIM ,
632+ inputs = inputs ,
633+ outputs = [state .body_q , state .body_qd ],
634+ device = model .device ,
635+ )
636+ return
637+
543638 wp .launch (
544639 kernel = eval_articulation_fk ,
545640 dim = num_articulations ,
0 commit comments