# [setup-1]
from itertools import product

import bpy
import numpy as np

import ddg

ddg.blender.scene.clear()
# [setup-1]

#############################################
#      Initial data
#############################################
# [setup-2]
p = np.array([0, 0, 0])
vs_tmp = np.array([[0, 0, 1], [0, -0.6, 1], [-2, 1, 1]])
vs = [v / np.linalg.norm(v) for v in vs_tmp]
triangle_distances = np.array([[1, 1, 1], [2.1, 3.2, 2.8], [4, 8, 6]])
projection_plane_normal, projection_plane_level = [-1, 1, 0], 80
center_of_projection = [35, -27, 10]
# [setup-2]

#############################################
#      Initial setup
#############################################
# [setup-3]
# Lines of perspectivity
p_lines = [
    ddg.geometry.subspace_from_affine_points_and_directions(p, vs[i]) for i in range(3)
]
# [setup-3]


#############################################
#   Geometric construction
#############################################
# [construction-1]
# Three triangles in perspective
affine = False

if affine:
    # Implementation via affine coordinates
    triangles = [
        [
            ddg.geometry.subspace_from_affine_points(p + dists[i] * vs[i])
            for i in range(3)
        ]
        for dists in triangle_distances
    ]
else:
    # Implementation via homogeneous coordinates
    triangles = [
        [ddg.geometry.Point([1, dists[i]]).embed(p_lines[i]) for i in range(3)]
        for dists in triangle_distances
    ]
# [construction-1]

# [construction-2]
# Edges of triangles as ddg.geometry
edges = [
    [ddg.geometry.join(triangle[i], triangle[(i + 1) % 3]) for i in range(3)]
    for triangle in triangles
]

# Calculate intersection points
edges_intersections = [
    [
        ddg.geometry.intersect(line1, line2)
        for line1, line2 in zip(edges[i], edges[(i + 1) % 3])
    ]
    for i in range(3)
]

# Three lines formed by intersection points
lines = [ddg.geometry.join(*points) for points in edges_intersections]

# Calculate the second point of intersection
x = ddg.geometry.intersect(*lines)
# [construction-2]


# [construction-3]
# Prepare for visualization: Create lines/ddg.geometry that join
# intersection points and edges of triangles
def outer_points(*points):
    """Takes projective points on a line and computes, in affine
    coordinates, the 'outer' points to join their projective
    counterparts to a 1d subspace."""
    affine_points = np.array([p.affine_point for p in points])
    n = (affine_points[1] - affine_points[0]) / np.linalg.norm(
        affine_points[1] - affine_points[0]
    )

    def signed_distance(pt):
        return np.dot(n, pt - affine_points[0])

    dists = list(map(signed_distance, affine_points))
    return (
        points[np.argmin(dists)].affine_point,
        points[np.argmax(dists)].affine_point,
    )


triangles_line_segments = [
    [
        outer_points(
            edges_intersections[i - 1][j],
            edges_intersections[i][j],
            triangles[i][j],
            triangles[i][(j + 1) % 3],
        )
        for j in range(3)
    ]
    for i in range(3)
]
# [construction-3]

#############################################
#            Projection
#############################################
# [construction-4]
# Projection plane
projection_plane = ddg.geometry.hyperplane_from_normal(
    projection_plane_normal, projection_plane_level
)
projection_plane = projection_plane.orthonormalize()
# [construction-4]


#############################################
#    Visualization
#############################################
# [visualization-1]
segment_sampling = 1
line_sampling = 1
point_radius = 0.1
segment_bevel = 0.03
line_bevel = 0.06
projection_plane_kwargs = {"bounding": 45, "sampling": 1}
colors = [
    ddg.blender.material.material("orange", (0.8, 0.1, 0.036), 0, 0),
    ddg.blender.material.material("blue", (0.019, 0.052, 0.445), 0, 0),
    ddg.blender.material.material("lightblue", (0.128, 0.59, 0.8), 0, 0),
]


def draw_triangle(triangle, name=None, material=None):
    hds = ddg.halfedge.disc(3)
    for v in hds.verts:
        v.co = triangle[v.index].affine_point
    bobj = ddg.blender.convert(hds, name, material)
    return bobj


# [visualization-1]


# [visualization-2]
for i in range(3):
    segment = ddg.arrays.line_segment_from_points(
        p,
        triangles[2][i],
        [-2 / triangle_distances[2][i], 1 + 2 / triangle_distances[2][i]],
    )
    bobj = ddg.blender.convert(segment, f"p_line_{i}", material=colors[0])
    bobj.data.bevel_depth = line_bevel

bobj_triangles = [
    draw_triangle(triangles[i], name=f"triangle_{i}", material=colors[0])
    for i in range(3)
]

for i, j in product(range(3), range(3)):
    bobj = ddg.blender.convert(
        ddg.arrays.line_segment_from_points(*triangles_line_segments[i][j]),
        f"segment_{i}_{j}",
        material=colors[2],
    )
    bobj.data.bevel_depth = segment_bevel


for i, j in product(range(3), range(3)):
    bobj = ddg.blender.vertices(
        edges_intersections[i][j],
        f"meet_{i}_{j}",
        material=colors[2],
        radius=0.1,
    )

for i in range(3):
    curve = ddg.arrays.from_1d_subspace(lines[i], 200)
    bobj = ddg.blender.convert(curve, f"line_{i}", material=colors[1])
    bobj.data.bevel_depth = line_bevel

bobj_x = ddg.blender.vertices(x, "x", material=colors[1], radius=0.1)

ddg.blender.convert(projection_plane, "projection plane")
# [visualization-2]

# [visualization-3]
bobj_camera = ddg.blender.camera.camera()
bpy.context.scene.camera = bobj_camera
bobj_light = ddg.blender.light.light(
    name="Light", type_="SPOT", location=center_of_projection, energy=500000
)
bobj_light.data.shadow_soft_size = 0
ddg.blender.light.look_at_point(
    bobj_light, ddg.math.projective.dehomogenize(projection_plane.points[0])
)

bobj_camera.location = (18.5, -17, 16.6)
bobj_camera.data.clip_end = 200
bobj_camera.rotation_euler = np.array([74.2, 0, 46]) / 180 * np.pi
bobj_light.data.spot_size = 0.404916
# [visualization-3]

# [visualization-4]
# rendering settings
bpy.context.scene.render.resolution_x = 1000
bpy.context.scene.render.resolution_y = 1000
ddg.blender.render.setup_cycles_renderer(samples=8)
ddg.blender.render.set_film_transparency()
ddg.blender.render.set_world_background((0.184469, 0.184469, 0.184469, 1), 1.5)
# [visualization-4]
