@@ -798,14 +798,17 @@ def _add_equality(self, name, type, objs_name, data, sol_params):
798798 # ------------------------------------------------------------------------------------
799799
800800 @gs .assert_built
801- def get_jacobian (self , link ):
801+ def get_jacobian (self , link , local_point = None ):
802802 """
803- Get the Jacobian matrix for a target link.
803+ Get the spatial Jacobian for a point on a target link.
804804
805805 Parameters
806806 ----------
807807 link : RigidLink
808808 The target link.
809+ local_point : torch.Tensor or None, shape (3,)
810+ Coordinates of the point in the link’s *local* frame.
811+ If None, the link origin is used (back-compat).
809812
810813 Returns
811814 -------
@@ -820,7 +823,16 @@ def get_jacobian(self, link):
820823 if self .n_dofs == 0 :
821824 gs .raise_exception ("Entity has zero dofs." )
822825
823- self ._kernel_get_jacobian (link .idx )
826+ if local_point is None :
827+ local_point = torch .zeros (3 , device = gs .device , dtype = torch .float32 )
828+ elif isinstance (local_point , np .ndarray ):
829+ local_point = torch .as_tensor (local_point , device = gs .device , dtype = torch .float32 )
830+
831+ if local_point .shape != (3 ,):
832+ gs .raise_exception ("`local_point` must be a (3,) vector in link space." )
833+
834+ p_local_ti = ti .Vector (local_point .tolist (), dt = ti .f32 )
835+ self ._kernel_get_jacobian (link .idx , p_local_ti )
824836
825837 jacobian = self ._jacobian .to_torch (gs .device ).permute (2 , 0 , 1 )
826838 if self ._solver .n_envs == 0 :
@@ -829,22 +841,24 @@ def get_jacobian(self, link):
829841 return jacobian
830842
831843 @ti .kernel
832- def _kernel_get_jacobian (self , tgt_link_idx : ti .i32 ):
844+ def _kernel_get_jacobian (self , tgt_link_idx : ti .i32 , p_local : ti . types . vector ( 3 , ti . f32 ) ):
833845 ti .loop_config (serialize = self ._solver ._para_level < gs .PARA_LEVEL .ALL )
834846 for i_b in range (self ._solver ._B ):
835847 self ._func_get_jacobian (
836848 tgt_link_idx ,
837849 i_b ,
850+ p_local ,
838851 ti .Vector .one (gs .ti_int , 3 ),
839852 ti .Vector .one (gs .ti_int , 3 ),
840853 )
841854
842855 @ti .func
843- def _func_get_jacobian (self , tgt_link_idx , i_b , pos_mask , rot_mask ):
856+ def _func_get_jacobian (self , tgt_link_idx , i_b , p_local , pos_mask , rot_mask ):
844857 for i_row , i_d in ti .ndrange (6 , self .n_dofs ):
845858 self ._jacobian [i_row , i_d , i_b ] = 0.0
846859
847- tgt_link_pos = self ._solver .links_state [tgt_link_idx , i_b ].pos
860+ tgt_link_state = self ._solver .links_state [tgt_link_idx , i_b ]
861+ tgt_link_pos = tgt_link_state .pos + gu .ti_transform_by_quat (p_local , tgt_link_state .quat )
848862 i_l = tgt_link_idx
849863 while i_l > - 1 :
850864 I_l = [i_l , i_b ] if ti .static (self .solver ._options .batch_links_info ) else i_l
@@ -864,7 +878,7 @@ def _func_get_jacobian(self, tgt_link_idx, i_b, pos_mask, rot_mask):
864878 I_d = [i_d , i_b ] if ti .static (self .solver ._options .batch_dofs_info ) else i_d
865879 i_d_jac = i_d + dof_offset - self ._dof_start
866880 rotation = gu .ti_transform_by_quat (self ._solver .dofs_info [I_d ].motion_ang , l_state .quat )
867- translation = rotation . cross (tgt_link_pos - l_state .pos )
881+ translation = (tgt_link_pos - l_state .pos ). cross ( rotation )
868882
869883 self ._jacobian [0 , i_d_jac , i_b ] = translation [0 ] * pos_mask [0 ]
870884 self ._jacobian [1 , i_d_jac , i_b ] = translation [1 ] * pos_mask [1 ]
@@ -897,7 +911,7 @@ def _func_get_jacobian(self, tgt_link_idx, i_b, pos_mask, rot_mask):
897911 i_d_jac = i_d + dof_offset - self ._dof_start
898912 I_d = [i_d , i_b ] if ti .static (self .solver ._options .batch_dofs_info ) else i_d
899913 rotation = self ._solver .dofs_info [I_d ].motion_ang
900- translation = rotation . cross (tgt_link_pos - l_state .pos )
914+ translation = (tgt_link_pos - l_state .pos ). cross ( rotation )
901915
902916 self ._jacobian [0 , i_d_jac , i_b ] = translation [0 ] * pos_mask [0 ]
903917 self ._jacobian [1 , i_d_jac , i_b ] = translation [1 ] * pos_mask [1 ]
0 commit comments