@@ -335,6 +335,84 @@ def parent(self):
335335 def attachment_site (self ):
336336 return self .mjcf_model
337337
338+ @property
339+ def root_body (self ):
340+ if self .parent :
341+ return mjcf .get_attachment_frame (self .mjcf_model )
342+ else :
343+ return self .mjcf_model .worldbody
344+
345+ def global_vector_to_local_frame (self , physics , vec_in_world_frame ):
346+ """Linearly transforms a world-frame vector into entity's local frame.
347+
348+ Note that this function does not perform an affine transformation of the
349+ vector. In other words, the input vector is assumed to be specified with
350+ respect to the same origin as this entity's local frame. This function
351+ can also be applied to matrices whose innermost dimensions are either 2 or
352+ 3. In this case, a matrix with the same leading dimensions is returned
353+ where the innermost vectors are replaced by their values computed in the
354+ local frame.
355+
356+ Args:
357+ physics: An `mjcf.Physics` instance.
358+ vec_in_world_frame: A NumPy array with last dimension of shape (2,) or
359+ (3,) that represents a vector quantity in the world frame.
360+
361+ Returns:
362+ The same quantity as `vec_in_world_frame` but reexpressed in this
363+ entity's local frame. The returned np.array has the same shape as
364+ np.asarray(vec_in_world_frame).
365+
366+ Raises:
367+ ValueError: if `vec_in_world_frame` does not have shape ending with (2,)
368+ or (3,).
369+ """
370+ vec_in_world_frame = np .asarray (vec_in_world_frame )
371+
372+ xmat = np .reshape (physics .bind (self .root_body ).xmat , (3 , 3 ))
373+ # The ordering of the np.dot is such that the transformation holds for any
374+ # matrix whose final dimensions are (2,) or (3,).
375+ if vec_in_world_frame .shape [- 1 ] == 2 :
376+ return np .dot (vec_in_world_frame , xmat [:2 , :2 ])
377+ elif vec_in_world_frame .shape [- 1 ] == 3 :
378+ return np .dot (vec_in_world_frame , xmat )
379+ else :
380+ raise ValueError ('`vec_in_world_frame` should have shape with final '
381+ 'dimension 2 or 3: got {}' .format (
382+ vec_in_world_frame .shape ))
383+
384+ def global_xmat_to_local_frame (self , physics , xmat ):
385+ """Transforms another entity's `xmat` into this entity's local frame.
386+
387+ This function takes another entity's (E) xmat, which is an SO(3) matrix
388+ from E's frame to the world frame, and turns it to a matrix that transforms
389+ from E's frame into this entity's local frame.
390+
391+ Args:
392+ physics: An `mjcf.Physics` instance.
393+ xmat: A NumPy array of shape (3, 3) or (9,) that represents another
394+ entity's xmat.
395+
396+ Returns:
397+ The `xmat` reexpressed in this entity's local frame. The returned
398+ np.array has the same shape as np.asarray(xmat).
399+
400+ Raises:
401+ ValueError: if `xmat` does not have shape (3, 3) or (9,).
402+ """
403+ xmat = np .asarray (xmat )
404+
405+ input_shape = xmat .shape
406+ if xmat .shape == (9 ,):
407+ xmat = np .reshape (xmat , (3 , 3 ))
408+
409+ self_xmat = np .reshape (physics .bind (self .root_body ).xmat , (3 , 3 ))
410+ if xmat .shape == (3 , 3 ):
411+ return np .reshape (np .dot (self_xmat .T , xmat ), input_shape )
412+ else :
413+ raise ValueError ('`xmat` should have shape (3, 3) or (9,): got {}' .format (
414+ xmat .shape ))
415+
338416 def get_pose (self , physics ):
339417 """Get the position and orientation of this entity relative to its parent.
340418
0 commit comments