def angle_alignment_cost_base(
    state: state_def.State,
    a: Union[str, list[str]],
    b: Union[str, list[str]],
    visualize=False,
):
    """
    Return the dot product between the axes of a and the normal of the closest edge of b
    """
    # print(f'{a=}, {b=}')
    scene = state.trimesh_scene
    a_meshes = iu.meshes_from_names(scene, a)
    b_meshes = iu.meshes_from_names(scene, b)
    b_edges = []
    for b_name, b_mesh in zip(b, b_meshes):
        b_poly = iu.project_to_xy_poly(b_mesh)
        if (b_poly is not None) and (not b_poly.is_empty):
            if isinstance(b_poly, Polygon):
                for i, coord in enumerate(b_poly.exterior.coords[:-1]):
                    start, end = coord, b_poly.exterior.coords[i + 1]
                    if np.isclose(start, end).all():
                        continue
                    b_edges.append((LineString([start, end]), b_name))
            elif isinstance(b_poly, MultiPolygon):
                for sub_poly in b_poly.geoms:
                    for i, coord in enumerate(sub_poly.exterior.coords[:-1]):
                        start, end = coord, sub_poly.exterior.coords[i + 1]
                        if np.isclose(start, end).all():
                            continue
                        b_edges.append((LineString([start, end]), b_name))
        else:
            for edge3d in b_mesh.edges:
                start = b_mesh.vertices[edge3d[0]][:2]
                end = b_mesh.vertices[edge3d[1]][:2]
                if np.isclose(start, end).all():
                    continue
                b_edges.append((LineString([start, end]), b_name))

    a_blender_objs = iu.blender_objs_from_names(a)

    if visualize:
        fig, ax = plt.subplots()
        for edge, _ in b_edges:
            x, y = edge.xy
            ax.plot(x, y, color="red", linewidth=1, label="B Edges")

    score = 0

    for a_name, a_obj, a_mesh in zip(a, a_blender_objs, a_meshes):
        _, axis = get_axis(state, a_obj)
        axis = axis[:2]
        a_poly = iu.project_to_xy_poly(a_mesh)

        if a_poly is not None:
            if isinstance(a_poly, Polygon):
                a_centroid = a_poly.centroid
            elif isinstance(a_poly, MultiPolygon):
                a_centroid = a_poly.centroid
        else:
            a_centroid = Point(a_mesh.vertices[:, :2].mean(axis=0))

        filtered_b_edges = [edge for edge, b_name in b_edges if b_name != a_name]
        if len(filtered_b_edges) == 0:
            continue
        closest_line = iu.closest_edge_to_point_edge_list(filtered_b_edges, a_centroid)

        dx = closest_line.xy[0][1] - closest_line.xy[0][0]  # x1 - x0
        dy = closest_line.xy[1][1] - closest_line.xy[1][0]  # y1 - y0

        # Candidate normal vectors (perpendicular to edge)
        normal_vector_1 = np.array([dy, -dx])
        normal_vector_2 = -normal_vector_1

        # Normalize the vectors
        normal_vector_1 /= np.linalg.norm(normal_vector_1)
        normal_vector_2 /= np.linalg.norm(normal_vector_2)

        dot1 = np.dot(axis, normal_vector_1)
        dot2 = np.dot(axis, normal_vector_2)

        score1 = -dot1 / 2 + 0.5
        score2 = -dot2 / 2 + 0.5

        score += min(score1, score2)

        if visualize:
            if a_poly is not None:
                if isinstance(a_poly, Polygon):
                    x, y = a_poly.exterior.xy
                    ax.fill(x, y, alpha=0.5, fc="blue", ec="black", label="Polygon a")
                elif isinstance(a_poly, MultiPolygon):
                    for sub_poly in a_poly.geoms:
                        x, y = sub_poly.exterior.xy
                        ax.fill(
                            x, y, alpha=0.5, fc="blue", ec="black", label="Polygon a"
                        )
            else:
                x, y = a_mesh.vertices[:, 0], a_mesh.vertices[:, 1]
                ax.scatter(x, y, color="blue", label="Vertices a")

            ax.arrow(
                a_centroid.x,
                a_centroid.y,
                axis[0],
                axis[1],
                head_width=0.15,
                head_length=0.25,
                fc="green",
                ec="green",
                label="Axis of a",
            )
            x, y = closest_line.xy
            ax.plot(x, y, color="green", linewidth=2.5, label="Closest Edge")
            ax.plot(
                a_centroid.x, a_centroid.y, "o", color="black", label="Centroid of a"
            )
            mid_point = closest_line.interpolate(0.5, normalized=True)
            ax.arrow(
                mid_point.x,
                mid_point.y,
                normal_vector_1[0],
                normal_vector_1[1],
                head_width=0.15,
                head_length=0.25,
                fc="yellow",
                ec="yellow",
                label="Normal Vector 1",
            )
            ax.arrow(
                mid_point.x,
                mid_point.y,
                normal_vector_2[0],
                normal_vector_2[1],
                head_width=0.15,
                head_length=0.25,
                fc="orange",
                ec="orange",
                label="Normal Vector 2",
            )

    if visualize:
        ax.set_title("Polygons, Closest Edge and Normal")
        ax.set_aspect("equal")
        ax.grid(True)
        plt.show()

    return score

    Return the dot product between the axes of a and the normal of the closest edge of b
    
infinigen/core/constraints/evaluator/node_impl/trimesh_geometry.py