|
import torch |
|
import numpy as np |
|
from torch import Tensor |
|
from typing import Tuple |
|
|
|
|
|
NUM_VERTICES_IN_BOX = 4 |
|
|
|
|
|
def minkowski_sum_of_box_and_box_points(box1_points: Tensor, |
|
box2_points: Tensor) -> Tensor: |
|
"""Batched Minkowski sum of two boxes (counter-clockwise corners in xy).""" |
|
point_order_1 = torch.tensor([0, 0, 1, 1, 2, 2, 3, 3], dtype=torch.long) |
|
point_order_2 = torch.tensor([0, 1, 1, 2, 2, 3, 3, 0], dtype=torch.long) |
|
|
|
box1_start_idx, downmost_box1_edge_direction = _get_downmost_edge_in_box( |
|
box1_points) |
|
box2_start_idx, downmost_box2_edge_direction = _get_downmost_edge_in_box( |
|
box2_points) |
|
|
|
condition = (cross_product_2d(downmost_box1_edge_direction, downmost_box2_edge_direction) >= 0.) |
|
condition = condition.repeat(1, 8) |
|
|
|
box1_point_order = torch.where(condition, point_order_2, point_order_1) |
|
box1_point_order = (box1_point_order + box1_start_idx) % NUM_VERTICES_IN_BOX |
|
ordered_box1_points = torch.gather( |
|
box1_points, 1, box1_point_order.unsqueeze(-1).expand(-1, -1, 2)) |
|
|
|
box2_point_order = torch.where(condition, point_order_1, point_order_2) |
|
box2_point_order = (box2_point_order + box2_start_idx) % NUM_VERTICES_IN_BOX |
|
ordered_box2_points = torch.gather( |
|
box2_points, 1, box2_point_order.unsqueeze(-1).expand(-1, -1, 2)) |
|
|
|
minkowski_sum = ordered_box1_points + ordered_box2_points |
|
|
|
return minkowski_sum |
|
|
|
|
|
def signed_distance_from_point_to_convex_polygon(query_points: Tensor, polygon_points: Tensor) -> Tensor: |
|
"""Finds the signed distances from query points to convex polygons.""" |
|
tangent_unit_vectors, normal_unit_vectors, edge_lengths = _get_edge_info( |
|
polygon_points) |
|
|
|
query_points = query_points.unsqueeze(1) |
|
vertices_to_query_vectors = query_points - polygon_points |
|
vertices_distances = torch.norm(vertices_to_query_vectors, dim=-1) |
|
|
|
edge_signed_perp_distances = torch.sum(-normal_unit_vectors * vertices_to_query_vectors, dim=-1) |
|
|
|
is_inside = torch.all(edge_signed_perp_distances <= 0, dim=-1) |
|
|
|
projection_along_tangent = torch.sum(tangent_unit_vectors * vertices_to_query_vectors, dim=-1) |
|
projection_along_tangent_proportion = projection_along_tangent / edge_lengths |
|
|
|
is_projection_on_edge = (projection_along_tangent_proportion >= 0.) & ( |
|
projection_along_tangent_proportion <= 1.) |
|
|
|
edge_perp_distances = edge_signed_perp_distances.abs() |
|
edge_distances = torch.where(is_projection_on_edge, edge_perp_distances, torch.tensor(np.inf)) |
|
|
|
edge_and_vertex_distance = torch.cat([edge_distances, vertices_distances], dim=-1) |
|
min_distance = torch.min(edge_and_vertex_distance, dim=-1)[0] |
|
|
|
signed_distances = torch.where(is_inside, -min_distance, min_distance) |
|
|
|
return signed_distances |
|
|
|
|
|
def _get_downmost_edge_in_box(box: Tensor) -> Tuple[Tensor, Tensor]: |
|
"""Finds the downmost (lowest y-coordinate) edge in the box.""" |
|
downmost_vertex_idx = torch.argmin(box[..., 1], dim=-1, keepdim=True) |
|
|
|
edge_start_vertex = torch.gather(box, 1, downmost_vertex_idx.unsqueeze(-1).expand(-1, -1, 2)) |
|
edge_end_idx = (downmost_vertex_idx + 1) % NUM_VERTICES_IN_BOX |
|
edge_end_vertex = torch.gather(box, 1, edge_end_idx.unsqueeze(-1).expand(-1, -1, 2)) |
|
|
|
downmost_edge = edge_end_vertex - edge_start_vertex |
|
downmost_edge_length = torch.norm(downmost_edge, dim=-1, keepdim=True) |
|
downmost_edge_direction = downmost_edge / downmost_edge_length |
|
|
|
return downmost_vertex_idx, downmost_edge_direction |
|
|
|
|
|
def cross_product_2d(a: Tensor, b: Tensor) -> Tensor: |
|
return a[..., 0] * b[..., 1] - a[..., 1] * b[..., 0] |
|
|
|
|
|
def dot_product_2d(a: Tensor, b: Tensor) -> Tensor: |
|
return a[..., 0] * b[..., 0] + a[..., 1] * b[..., 1] |
|
|
|
|
|
def _get_edge_info(polygon_points: Tensor): |
|
""" |
|
Computes properties about the edges of a polygon. |
|
|
|
Args: |
|
polygon_points: Tensor containing the vertices of each polygon, with |
|
shape (num_polygons, num_points_per_polygon, 2). Each polygon is assumed |
|
to have an equal number of vertices. |
|
|
|
Returns: |
|
tangent_unit_vectors: A unit vector in (x,y) with the same direction as |
|
the tangent to the edge. Shape: (num_polygons, num_points_per_polygon, 2). |
|
normal_unit_vectors: A unit vector in (x,y) with the same direction as |
|
the normal to the edge. |
|
Shape: (num_polygons, num_points_per_polygon, 2). |
|
edge_lengths: Lengths of the edges. |
|
Shape (num_polygons, num_points_per_polygon). |
|
""" |
|
|
|
first_point_in_polygon = polygon_points[:, 0:1, :] |
|
shifted_polygon_points = torch.cat([polygon_points[:, 1:, :], first_point_in_polygon], dim=1) |
|
|
|
|
|
edge_vectors = shifted_polygon_points - polygon_points |
|
edge_lengths = torch.norm(edge_vectors, dim=-1) |
|
|
|
|
|
eps = torch.finfo(edge_lengths.dtype).eps |
|
tangent_unit_vectors = edge_vectors / (edge_lengths[..., None] + eps) |
|
|
|
normal_unit_vectors = torch.stack( |
|
[-tangent_unit_vectors[..., 1], tangent_unit_vectors[..., 0]], dim=-1 |
|
) |
|
|
|
return tangent_unit_vectors, normal_unit_vectors, edge_lengths |
|
|
|
|
|
def rotate_2d_points(xys: Tensor, rotation_yaws: Tensor) -> Tensor: |
|
"""Rotates `xys` counterclockwise using the `rotation_yaws`.""" |
|
cos_yaws = torch.cos(rotation_yaws) |
|
sin_yaws = torch.sin(rotation_yaws) |
|
|
|
rotated_x = cos_yaws * xys[..., 0] - sin_yaws * xys[..., 1] |
|
rotated_y = sin_yaws * xys[..., 0] + cos_yaws * xys[..., 1] |
|
|
|
return torch.stack([rotated_x, rotated_y], axis=-1) |
|
|