예제 #1
0
def between_two_vectors_3d(vector1, vector2, name=None):
  """Computes quaternion over the shortest arc between two vectors.

  Result quaternion describes shortest geodesic rotation from
  vector1 to vector2.

  Note:
    In the following, A1 to An are optional batch dimensions.

  Args:
    vector1: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the first vector.
    vector2: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the second vector.
    name: A name for this op that defaults to
      "quaternion_between_two_vectors_3d".

  Returns:
    A tensor of shape `[A1, ..., An, 4]`, where the last dimension represents
    a normalized quaternion.

  Raises:
    ValueError: If the shape of `vector1` or `vector2` is not supported.
  """
  with tf.compat.v1.name_scope(name, "quaternion_between_two_vectors_3d",
                               [vector1, vector2]):
    vector1 = tf.convert_to_tensor(value=vector1)
    vector2 = tf.convert_to_tensor(value=vector2)

    shape.check_static(
        tensor=vector1, tensor_name="vector1", has_dim_equals=(-1, 3))
    shape.check_static(
        tensor=vector2, tensor_name="vector2", has_dim_equals=(-1, 3))
    shape.compare_batch_dimensions(
        tensors=(vector1, vector2), last_axes=-2, broadcast_compatible=True)

    # Make sure that we are dealing with unit vectors.
    vector1 = tf.nn.l2_normalize(vector1, axis=-1)
    vector2 = tf.nn.l2_normalize(vector2, axis=-1)
    cos_theta = vector.dot(vector1, vector2)
    real_part = 1.0 + cos_theta
    axis = vector.cross(vector1, vector2)

    # Compute arbitrary antiparallel axes to rotate around in case of opposite
    # vectors.
    x, y, z = tf.split(vector1, (1, 1, 1), axis=-1)
    x_bigger_z = tf.abs(x) > tf.abs(z)
    x_bigger_z = tf.concat([x_bigger_z] * 3, axis=-1)
    antiparallel_axis = tf.compat.v1.where(
        x_bigger_z, tf.concat((-y, x, tf.zeros_like(z)), axis=-1),
        tf.concat((tf.zeros_like(x), -z, y), axis=-1))

    # Compute rotation between two vectors.
    is_antiparallel = real_part < 1e-6
    is_antiparallel = tf.concat([is_antiparallel] * 4, axis=-1)
    rot = tf.compat.v1.where(
        is_antiparallel,
        tf.concat((antiparallel_axis, tf.zeros_like(real_part)), axis=-1),
        tf.concat((axis, real_part), axis=-1))
    return tf.nn.l2_normalize(rot, axis=-1)
예제 #2
0
    def test_cross_jacobian_preset(self, u_init, v_init):
        """Tests the Jacobian of the dot product."""
        u_tensor = tf.convert_to_tensor(value=u_init)
        v_tensor = tf.convert_to_tensor(value=v_init)

        y = vector.cross(u_tensor, v_tensor)

        self.assert_jacobian_is_correct(u_tensor, u_init, y)
        self.assert_jacobian_is_correct(v_tensor, v_init, y)
예제 #3
0
    def test_cross_random(self):
        """Tests the cross product function."""
        tensor_size = np.random.randint(1, 4)
        tensor_shape = np.random.randint(1, 10, size=tensor_size).tolist()
        axis = np.random.randint(tensor_size)
        tensor_shape[axis] = 3  # pylint: disable=invalid-sequence-index
        u = np.random.random(size=tensor_shape)
        v = np.random.random(size=tensor_shape)

        self.assertAllClose(vector.cross(u, v, axis=axis),
                            np.cross(u, v, axis=axis))
예제 #4
0
def rotate(point, axis, angle, name=None):
    r"""Rotates a 3d point using an axis-angle by applying the Rodrigues' formula.

  Rotates a vector $$\mathbf{v} \in {\mathbb{R}^3}$$ into a vector
  $$\mathbf{v}' \in {\mathbb{R}^3}$$ using the Rodrigues' rotation formula:

  $$\mathbf{v}'=\mathbf{v}\cos(\theta)+(\mathbf{a}\times\mathbf{v})\sin(\theta)
  +\mathbf{a}(\mathbf{a}\cdot\mathbf{v})(1-\cos(\theta)).$$

  Note:
    In the following, A1 to An are optional batch dimensions.

  Args:
    point: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents a 3d point to rotate.
    axis: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents a normalized axis.
    angle: A tensor of shape `[A1, ..., An, 1]`, where the last dimension
      represents an angle.
    name: A name for this op that defaults to "axis_angle_rotate".

  Returns:
    A tensor of shape `[A1, ..., An, 3]`, where the last dimension represents
    a 3d point.

  Raises:
    ValueError: If `point`, `axis`, or `angle` are of different shape or if
    their respective shape is not supported.
  """
    with tf.compat.v1.name_scope(name, "axis_angle_rotate",
                                 [point, axis, angle]):
        point = tf.convert_to_tensor(value=point)
        axis = tf.convert_to_tensor(value=axis)
        angle = tf.convert_to_tensor(value=angle)

        shape.check_static(tensor=point,
                           tensor_name="point",
                           has_dim_equals=(-1, 3))
        shape.check_static(tensor=axis,
                           tensor_name="axis",
                           has_dim_equals=(-1, 3))
        shape.check_static(tensor=angle,
                           tensor_name="angle",
                           has_dim_equals=(-1, 1))
        shape.compare_batch_dimensions(tensors=(point, axis, angle),
                                       tensor_names=("point", "axis", "angle"),
                                       last_axes=-2,
                                       broadcast_compatible=True)
        axis = asserts.assert_normalized(axis)

        cos_angle = tf.cos(angle)
        axis_dot_point = vector.dot(axis, point)
        return point * cos_angle + vector.cross(axis, point) * tf.sin(
            angle) + axis * axis_dot_point * (1.0 - cos_angle)
예제 #5
0
    def test_cross_jacobian_random(self):
        """Test the Jacobian of the dot product."""
        tensor_size = np.random.randint(3)
        tensor_shape = np.random.randint(1, 10, size=(tensor_size)).tolist()
        u_init = np.random.random(size=tensor_shape + [3])
        v_init = np.random.random(size=tensor_shape + [3])
        u_tensor = tf.convert_to_tensor(value=u_init)
        v_tensor = tf.convert_to_tensor(value=v_init)

        y = vector.cross(u_tensor, v_tensor)

        self.assert_jacobian_is_correct(u_tensor, u_init, y)
        self.assert_jacobian_is_correct(v_tensor, v_init, y)
예제 #6
0
def normal(v0: type_alias.TensorLike,
           v1: type_alias.TensorLike,
           v2: type_alias.TensorLike,
           clockwise: bool = False,
           normalize: bool = True,
           name: str = "triangle_normal") -> tf.Tensor:
    """Computes face normals (triangles).

  Note:
    In the following, A1 to An are optional batch dimensions, which must be
    broadcast compatible.

  Args:
    v0: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the first vertex of a triangle.
    v1: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the second vertex of a triangle.
    v2: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the third vertex of a triangle.
    clockwise: Winding order to determine front-facing triangles.
    normalize: A `bool` indicating whether output normals should be normalized
      by the function.
    name: A name for this op. Defaults to "triangle_normal".

  Returns:
    A tensor of shape `[A1, ..., An, 3]`, where the last dimension represents
      a normalized vector.

  Raises:
    ValueError: If the shape of `v0`, `v1`, or `v2` is not supported.
  """
    with tf.name_scope(name):
        v0 = tf.convert_to_tensor(value=v0)
        v1 = tf.convert_to_tensor(value=v1)
        v2 = tf.convert_to_tensor(value=v2)

        shape.check_static(tensor=v0, tensor_name="v0", has_dim_equals=(-1, 3))
        shape.check_static(tensor=v1, tensor_name="v1", has_dim_equals=(-1, 3))
        shape.check_static(tensor=v2, tensor_name="v2", has_dim_equals=(-1, 3))
        shape.compare_batch_dimensions(tensors=(v0, v1, v2),
                                       last_axes=-2,
                                       broadcast_compatible=True)

        normal_vector = vector.cross(v1 - v0, v2 - v0, axis=-1)
        normal_vector = asserts.assert_nonzero_norm(normal_vector)
        if not clockwise:
            normal_vector *= -1.0
        if normalize:
            return tf.nn.l2_normalize(normal_vector, axis=-1)
        return normal_vector
예제 #7
0
def between_two_vectors_3d(vector1, vector2, name=None):
    """Computes quaternion over the shortest arc between two vectors.

  Result quaternion describes shortest geodesic rotation from
  vector1 to vector2.

  Note:
    In the following, A1 to An are optional batch dimensions.

  Args:
    vector1: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the first vector.
    vector2: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the second vector.
    name: A name for this op that defaults to
      "quaternion_between_two_vectors_3d".

  Returns:
    A tensor of shape `[A1, ..., An, 4]`, where the last dimension represents
    a normalized quaternion.

  Raises:
    ValueError: If the shape of `vector1` or `vector2` is not supported.
  """
    with tf.compat.v1.name_scope(name, "quaternion_between_two_vectors_3d",
                                 [vector1, vector2]):
        vector1 = tf.convert_to_tensor(value=vector1)
        vector2 = tf.convert_to_tensor(value=vector2)

        shape.check_static(tensor=vector1,
                           tensor_name="vector1",
                           has_dim_equals=(-1, 3))
        shape.check_static(tensor=vector2,
                           tensor_name="vector2",
                           has_dim_equals=(-1, 3))
        shape.compare_batch_dimensions(tensors=(vector1, vector2),
                                       last_axes=-2,
                                       broadcast_compatible=True)

        # Make sure we deal with unit vectors.
        vector1 = tf.nn.l2_normalize(vector1, axis=-1)
        vector2 = tf.nn.l2_normalize(vector2, axis=-1)
        axis = vector.cross(vector1, vector2)
        cos_theta = vector.dot(vector1, vector2)
        rot = tf.concat((axis, 1. + cos_theta), axis=-1)
        return tf.nn.l2_normalize(rot, axis=-1)
예제 #8
0
def area(v0: type_alias.TensorLike,
         v1: type_alias.TensorLike,
         v2: type_alias.TensorLike,
         name: str = "triangle_area") -> tf.Tensor:
    """Computes triangle areas.

    Note: Computed triangle area = 0.5 * | e1 x e2 | where e1 and e2 are edges
      of triangle. A degenerate triangle will return 0 area, whereas the normal
      for a degenerate triangle is not defined.


    In the following, A1 to An are optional batch dimensions, which must be
    broadcast compatible.

  Args:
    v0: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the first vertex of a triangle.
    v1: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the second vertex of a triangle.
    v2: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      represents the third vertex of a triangle.
    name: A name for this op. Defaults to "triangle_area".

  Returns:
    A tensor of shape `[A1, ..., An, 1]`, where the last dimension represents
      a normalized vector.
  """
    with tf.name_scope(name):
        v0 = tf.convert_to_tensor(value=v0)
        v1 = tf.convert_to_tensor(value=v1)
        v2 = tf.convert_to_tensor(value=v2)

        shape.check_static(tensor=v0, tensor_name="v0", has_dim_equals=(-1, 3))
        shape.check_static(tensor=v1, tensor_name="v1", has_dim_equals=(-1, 3))
        shape.check_static(tensor=v2, tensor_name="v2", has_dim_equals=(-1, 3))
        shape.compare_batch_dimensions(tensors=(v0, v1, v2),
                                       last_axes=-2,
                                       broadcast_compatible=True)

        normals = vector.cross(v1 - v0, v2 - v0, axis=-1)
        return 0.5 * tf.linalg.norm(tensor=normals, axis=-1, keepdims=True)
예제 #9
0
def right_handed(camera_position, look_at, up_vector, name=None):
    """Builds a right handed look at view matrix.

  Note:
    In the following, A1 to An are optional batch dimensions.

  Args:
    camera_position: A tensor of shape `[A1, ..., An, 3]`, where the last
      dimension represents the 3D position of the camera.
    look_at: A tensor of shape `[A1, ..., An, 3]`, with the last dimension
      storing the position where the camera is looking at.
    up_vector: A tensor of shape `[A1, ..., An, 3]`, where the last dimension
      defines the up vector of the camera.
    name: A name for this op. Defaults to 'right_handed'.

  Raises:
    ValueError: if the all the inputs are not of the same shape, or if any input
    of of an unsupported shape.

  Returns:
    A tensor of shape `[A1, ..., An, 4, 4]`, containing right handed look at
    matrices.
  """
    with tf.compat.v1.name_scope(name, "right_handed",
                                 [camera_position, look_at, up_vector]):
        camera_position = tf.convert_to_tensor(value=camera_position)
        look_at = tf.convert_to_tensor(value=look_at)
        up_vector = tf.convert_to_tensor(value=up_vector)

        shape.check_static(tensor=camera_position,
                           tensor_name="camera_position",
                           has_dim_equals=(-1, 3))
        shape.check_static(tensor=look_at,
                           tensor_name="look_at",
                           has_dim_equals=(-1, 3))
        shape.check_static(tensor=up_vector,
                           tensor_name="up_vector",
                           has_dim_equals=(-1, 3))
        shape.compare_batch_dimensions(tensors=(camera_position, look_at,
                                                up_vector),
                                       last_axes=-2,
                                       tensor_names=("camera_position",
                                                     "look_at", "up_vector"),
                                       broadcast_compatible=False)

        z_axis = tf.linalg.l2_normalize(look_at - camera_position, axis=-1)
        horizontal_axis = tf.linalg.l2_normalize(vector.cross(
            z_axis, up_vector),
                                                 axis=-1)
        vertical_axis = vector.cross(horizontal_axis, z_axis)

        batch_shape = tf.shape(input=horizontal_axis)[:-1]
        zeros = tf.zeros(shape=tf.concat((batch_shape, (3, )), axis=-1),
                         dtype=horizontal_axis.dtype)
        one = tf.ones(shape=tf.concat((batch_shape, (1, )), axis=-1),
                      dtype=horizontal_axis.dtype)

        matrix = tf.concat(
            (horizontal_axis, -vector.dot(horizontal_axis, camera_position),
             vertical_axis, -vector.dot(vertical_axis, camera_position),
             -z_axis, vector.dot(z_axis, camera_position), zeros, one),
            axis=-1)
        matrix_shape = tf.shape(input=matrix)
        output_shape = tf.concat((matrix_shape[:-1], (4, 4)), axis=-1)
        return tf.reshape(matrix, shape=output_shape)