Пример #1
0
def rotate_bounding_box(
    bounding_box: np.ndarray, quat: np.ndarray
) -> Tuple[float, float]:
    """ Rotates a bounding box by applying the quaternion and then re-computing the tightest
    possible fit of an *axis-aligned* bounding box.
    """
    pos, size = bounding_box

    # Compute 8 corners of bounding box.
    signs = np.array([[x, y, z] for x in [-1, 1] for y in [-1, 1] for z in [-1, 1]])
    corners = pos + signs * size
    assert corners.shape == (8, 3)

    # Rotate corners.
    mat = quat2mat(quat)
    rotated_corners = corners @ mat

    # Re-compute bounding-box.
    min_xyz = np.min(rotated_corners, axis=0)
    max_xyz = np.max(rotated_corners, axis=0)
    size = (max_xyz - min_xyz) / 2.0
    assert np.all(size >= 0.0)
    pos = min_xyz + size

    return pos, size
Пример #2
0
    def test_vectors2quat(self):
        """ Test constructing quaternion from two given vectors """
        vectors = [
            np.array([1, 0, 0]),
            np.array([0, 1, 0]),
            np.array([0, 0, 1]),
            -np.array([1, 0, 0]),
            -np.array([0, 1, 0]),
            -np.array([0, 0, 1]),
            random_unit_length_vec(),
            random_unit_length_vec(),
            random_unit_length_vec(),
        ]

        for v1, v2 in it.product(vectors, vectors):
            quat = vectors2quat(v1, v2)
            mat = quat2mat(quat)

            maybe_v2 = mat @ v1

            # Test that quat is normalized
            assert np.abs(np.linalg.norm(quat) - 1.0) < 1e-8

            # Make sure that given quaterion is the shortest path rotation
            # np.clip is necessary due to potential minor numerical instabilities
            assert quat_magnitude(quat) <= np.arccos(
                np.clip(v1 @ v2, -1.0, 1.0)) + 1e-8

            # Test that quaternion rotates input vector to output vector
            assert np.linalg.norm(maybe_v2 - v2) < 1e-8
Пример #3
0
def get_all_vertices(sim, object_name, subdivide_threshold=None) -> np.ndarray:
    """
    Return all vertices for given object.
    :param sim: The MjSim instance.
    :param object_name: The object name.
    :param subdivide_threshold: If provided, subdivide mesh into smaller faces.
      See subdivide_mesh for detail of this parameter.
    :return: Array of all vertices for this object.
    """
    all_verts: List[np.ndarray] = []
    all_faces: List[Optional[np.ndarray]] = []

    object_rot_mat = quat2mat(
        quat_conjugate(mat2quat(sim.data.get_body_xmat(object_name)))
    )
    geom_ids = geom_ids_of_body(sim, object_name)

    for geom_id in geom_ids:
        pos = sim.model.geom_pos[geom_id]
        quat = quat_conjugate(sim.model.geom_quat[geom_id])
        mat = quat2mat(quat)

        # Get all vertices associated with the current geom.
        verts = get_geom_vertices(sim, geom_id)
        faces = get_geom_faces(sim, geom_id)

        # Translate from geom's to body's coordinate frame.
        geom_ref_verts = verts @ mat
        geom_ref_verts = pos + geom_ref_verts

        all_verts.append(geom_ref_verts)
        all_faces.append(faces)

    if subdivide_threshold is not None and all(f is not None for f in all_faces):
        # We can only subdivide mesh with faces.
        mesh = trimesh.util.concatenate(
            [
                trimesh.Trimesh(vertices=verts, faces=faces)
                for verts, faces in zip(all_verts, all_faces)
            ]
        )

        verts = subdivide_mesh(mesh.vertices, mesh.faces, subdivide_threshold)
    else:
        verts = np.concatenate(all_verts, axis=0)

    return verts @ object_rot_mat
Пример #4
0
 def get_matrix(x: et.Element):
     pos = np.fromstring(x.attrib.get("pos"), sep=" ")
     if "euler" in x.attrib:
         euler = np.fromstring(x.attrib.get("euler"), sep=" ")
         return homogeneous_matrix_from_pos_mat(pos, rot.euler2mat(euler))
     elif "axisangle" in x.attrib:
         axis_angle = np.fromstring(x.attrib.get("axisangle"), sep=" ")
         quat = rot.quat_from_angle_and_axis(
             axis_angle[-1], np.array(axis_angle[:-1])
         )
         return homogeneous_matrix_from_pos_mat(pos, rot.quat2mat(quat))
     elif "quat" in x.attrib:
         quat = np.fromstring(x.attrib.get("quat"), sep=" ")
         return homogeneous_matrix_from_pos_mat(pos, rot.quat2mat(quat))
     else:
         quat = IDENTITY_QUAT
         return homogeneous_matrix_from_pos_mat(pos, rot.quat2mat(quat))
Пример #5
0
def up_axis_with_sign(cube_quat):
    """ Return an axis number + sign of the cube that is the closest to pointing up """
    z_up = np.array([0, 0, 1]).reshape(3, 1)
    mtx = rotation.quat2mat(cube_quat)
    # Axis that is the closest (by dotproduct) to z-up
    axis_nr = np.abs((z_up.T @ mtx)).argmax()
    axis = mtx[:, axis_nr]
    sign = np.sign(axis @ z_up)
    return axis_nr, sign
Пример #6
0
 def test_quat2mat(self):
     s = (N, N, 4)
     quats = uniform(-1, 1, size=s) * randint(2, size=s)
     mats = quat2mat(quats)
     self.assertEqual(mats.shape, (N, N, 3, 3))
     for i in range(N):
         for j in range(N):
             # Compare to transforms3d
             res = quaternions.quat2mat(quats[i, j])
             np.testing.assert_almost_equal(mats[i, j], res)
             # Compare to MuJoCo
             quat = normalize_quat(quats[i, j])
             mat = np.zeros(9, dtype=np.float64)
             functions.mju_quat2Mat(mat, quat)
             if np.isnan(mat).any():
                 continue  # MuJoCo returned NaNs
             np.testing.assert_almost_equal(quat2mat(quat),
                                            mat.reshape((3, 3)))
Пример #7
0
 def test_mat2euler2quat2mat(self):
     s = (N, N, 3, 3)
     mats = uniform(-np.pi, np.pi, size=s) * randint(2, size=s)
     for i in range(N):
         for j in range(N):
             try:
                 mat = normalize_mat(mats[i, j])
             except:  # noqa
                 continue  # Singular Matrix or NaNs
             result = quat2mat(euler2quat(mat2euler(mat)))
             np.testing.assert_allclose(mat, result, atol=1e-8, rtol=1e-6)
Пример #8
0
def distance_quat_from_being_up(cube_quat, axis_nr, sign):
    """ How far is the cube from having given axis pointing upwards """
    mtx = rotation.quat2mat(cube_quat)

    axis = mtx[:, axis_nr]
    axis = axis * sign

    z_up = np.array([0, 0, 1]).reshape(3, 1)

    # Quaternion representing the rotation from "axis" that is almost up to
    # the actual "up" direction
    difference_quat = rotation.vectors2quat(axis, z_up[:, 0])

    return rotation.quat_normalize(difference_quat)
Пример #9
0
def get_joint_matrix(pos, angle, axis):
    def transform_rot_x_matrix(pos, angle):
        """
        Optimization - create a homogeneous matrix where rotation submatrix
        rotates around the X axis by given angle in radians
        """
        m = np.eye(4)
        m[1, 1] = m[2, 2] = np.cos(angle)
        s = np.sin(angle)
        m[1, 2] = -s
        m[2, 1] = s
        m[:3, 3] = pos
        return m

    def transform_rot_y_matrix(pos, angle):
        """
        Optimization - create a homogeneous matrix where rotation submatrix
        rotates around the Y axis by given angle in radians
        """
        m = np.eye(4)
        m[0, 0] = m[2, 2] = np.cos(angle)
        s = np.sin(angle)
        m[0, 2] = s
        m[2, 0] = -s
        m[:3, 3] = pos
        return m

    def transform_rot_z_matrix(pos, angle):
        """
        Optimization - create a homogeneous matrix where rotation submatrix
        rotates around the Z axis by given angle in radians
        """
        m = np.eye(4)
        m[0, 0] = m[1, 1] = np.cos(angle)
        s = np.sin(angle)
        m[0, 1] = -s
        m[1, 0] = s
        m[:3, 3] = pos
        return m

    if abs(axis[0]) == 1.0 and axis[1] == 0.0 and axis[2] == 0.0:
        return transform_rot_x_matrix(pos, angle * axis[0])
    elif axis[0] == 0.0 and abs(axis[1]) == 1.0 and axis[2] == 0.0:
        return transform_rot_y_matrix(pos, angle * axis[1])
    elif axis[0] == 0.0 and axis[1] == 0.0 and abs(axis[2]) == 1.0:
        return transform_rot_z_matrix(pos, angle * axis[2])
    else:
        return homogeneous_matrix_from_pos_mat(
            pos, rot.quat2mat(rot.quat_from_angle_and_axis(angle, axis))
        )
Пример #10
0
    def align_axis(cmd_quat, axis):
        """ Align quaternion into given axes """
        alignment = np.zeros(3)
        alignment[axis] = 1
        mtx = rotation.quat2mat(cmd_quat)
        # Axis that is the closest (by dotproduct) to alignment
        axis_nr = np.abs((alignment.T @ mtx)).argmax()

        # Axis of the cmd_quat
        axis = mtx[:, axis_nr]
        axis = axis * np.sign(axis @ alignment)

        difference_quat = rotation.vectors2quat(axis, alignment)

        return rotation.quat_mul(difference_quat, cmd_quat)
Пример #11
0
def align_quat_up(cube_quat, normalize=True):
    """ Align quaternion so that the closest face to being up is actually up """
    z_up = np.array([0, 0, 1]).reshape(3, 1)
    mtx = rotation.quat2mat(cube_quat)
    # Axis that is the closest (by dotproduct) to z-up
    axis_nr = np.abs((z_up.T @ mtx)).argmax()

    # Axis of the cube pointing the closest to the top
    axis = mtx[:, axis_nr]
    axis = axis * np.sign(axis @ z_up)

    # Quaternion representing the rotation from "axis" that is almost up to
    # the actual "up" direction
    difference_quat = rotation.vectors2quat(axis, z_up[:, 0])

    angle = rotation.quat_mul(difference_quat, cube_quat)
    return rotation.quat_normalize(angle) if normalize else angle
Пример #12
0
def make_composed_mesh_object(
    name: str,
    primitives: List[str],
    random_state: np.random.RandomState,
    mesh_size_range: tuple = (0.01, 0.1),
    attachment: str = "random",
    object_size: Optional[float] = None,
) -> MujocoXML:
    """
    Composes an object out of mesh primitives by combining them in a random but systematic
    way. In the resulting object, all meshes are guaranteed to be connected.

    :param name: The name of the resulting object.
    :param primitives: A list of STL files that will be used as primitives in the provided order.
    :param random_state: The random state used for sampling.
    :param mesh_size_range: Each mesh is randomly resized (iid per dimension) but each side is
        guaranteed to be within this size. This is full-size, not half-size.
    :param attachment: How primitives are connected. If "random", the parent geom is randomly
        selected from the already placed geoms. If "last", the geom that was place last is used.
    :param object_size: If this is not None, the final object) will be re-scaled so that the longest
        side has exactly object_size half-size. This parameter is in half-size, as per Mujoco
        convention.
    :return: a MujocoXML object.
    """
    assert 0 <= mesh_size_range[0] <= mesh_size_range[1]
    assert attachment in ["random", "last"]

    def compute_pos_and_size(geoms):
        min_max_xyzs = np.array([geom.min_max_xyz() for geom in geoms])
        min_xyz = np.min(min_max_xyzs[:, 0, :], axis=0)
        max_xyz = np.max(min_max_xyzs[:, 1, :], axis=0)
        size = (max_xyz - min_xyz) / 2.0
        pos = min_xyz + size
        return pos, size

    geoms: List[MeshGeom] = []
    for i, mesh_path in enumerate(primitives):
        # Load mesh.
        mesh = trimesh.load(mesh_path)

        # Scale randomly but such that the mesh is within mesh_size_range.
        min_scale = mesh_size_range[0] / mesh.bounding_box.extents
        max_scale = mesh_size_range[1] / mesh.bounding_box.extents
        assert min_scale.shape == max_scale.shape == (3,)
        scale = random_state.uniform(min_scale, max_scale) * random_state.choice(
            [-1, 1], size=3
        )
        assert scale.shape == (3,)
        scale_matrix = np.eye(4)
        np.fill_diagonal(scale_matrix[:3, :3], scale)

        # Rotate randomly.
        quat = uniform_quat(random_state)
        rotation_matrix = np.eye(4)
        rotation_matrix[:3, :3] = quat2mat(quat)

        # Apply transformations. Apply scaling first since we computed the scale
        # in the original reference frame! In principle, we could also sheer the
        # object, but we currently do not do this.
        mesh.apply_transform(scale_matrix)
        mesh.apply_transform(rotation_matrix)

        if len(geoms) == 0:
            pos = -mesh.center_mass
        else:
            if attachment == "random":
                parent_geom = random_state.choice(geoms)
            elif attachment == "last":
                parent_geom = geoms[-1]
            else:
                raise ValueError()
            # We sample 10 points here because sample_surface sometimes returns less points
            # than we requested (unclear why).
            surface_pos = trimesh.sample.sample_surface(parent_geom.mesh, 10)[0][0]
            pos = parent_geom.pos + (surface_pos - mesh.center_mass)

        geom = MeshGeom(mesh_path=mesh_path, mesh=mesh, pos=pos, quat=quat, scale=scale)
        geoms.append(geom)

    # Shift everything so that the reference of the body is at the very center of the composed
    # object. This is very important.
    off_center_pos, _ = compute_pos_and_size(geoms)
    for geom in geoms:
        geom.pos -= off_center_pos

    # Ensure that the object origin is exactly at the center.
    assert np.allclose(compute_pos_and_size(geoms)[0], 0.0)

    # Resize object.
    if object_size is not None:
        _, size = compute_pos_and_size(geoms)

        # Apply global scale (so that ratio is not changed and longest side is exactly
        # object_size).
        ratio = object_size / np.max(size)
        for geom in geoms:
            geom.scale *= ratio
            geom.pos *= ratio

    geoms_str = "\n".join([g.to_geom_xml(name, idx) for idx, g in enumerate(geoms)])
    meshes_str = "\n".join([g.to_mesh_xml(name, idx) for idx, g in enumerate(geoms)])

    xml_source = f"""
    <mujoco>
        <asset>
          {meshes_str}
        </asset>
        <worldbody>
        <body name="{name}" pos="0 0 0">
          {geoms_str}
          <joint name="{name}:joint" type="free"/>
        </body>
        </worldbody>
    </mujoco>
    """
    return MujocoXML.from_string(xml_source)