"""Example: Shadow of a quadric

Visualization of a shadow of a quadric cast by a light source.
Note that materials and world settings are saved in the
corresponding .blend file.
"""

#############################################
# Setup
#############################################

# [setup-1]
# Import necessary libraries and modules
import bpy
import numpy as np

import ddg
from ddg.math.projective import homogenize

# [setup-1]

# [setup-2]
# Clear the Blender scene
ddg.blender.scene.clear()
# [setup-2]


#############################################
# Constants
#############################################

# [constants-1]
# example, visualize_generators = 'ellipsoid', False
example, visualize_generators = "one_sheeted_hyperboloid", True

# Define parameters for the quadric, the location of the light source
# and the normal and level of the projection plane
if example == "ellipsoid":
    a, b, c, d = 4, 0.5, 1, -2
    light_x, light_y, light_z = 1, 4, 4
    normal_x, normal_y, normal_z, level = 3, 1, 4, -10
if example == "one_sheeted_hyperboloid":
    a, b, c, d = 1, 0.5, -1, -1
    light_x, light_y, light_z = 1, 1.2, 4
    normal_x, normal_y, normal_z, level = 3, 1, 4, -15

# Define the position of the light source and normalize the planes
# normal
light = np.array([light_x, light_y, light_z])
normal_tmp = np.array([normal_x, normal_y, normal_z])
normal = normal_tmp / np.linalg.norm(normal_tmp)
# [constants-1]

# [constants-2]
# Sampling for Blender objects
t_cone_sampling = [0.1, 100, "c"]

if example == "ellipsoid":
    bounding_box_for_projection_plane = np.array([15, 25, 25])
    camera_loc, camera_look_at = [31, 0, -5], [0, -5, -3]

if example == "one_sheeted_hyperboloid":
    bounding_box_for_projection_plane = 5 * np.array([15, 25, 25])
    camera_loc, camera_look_at = [18, -65, 10], [0, 0, -5]

if visualize_generators:
    density_of_generators = 1 / 12 * np.pi
    generators_bounding_box = [100, 100, 5]
# [constants-2]


#############################################
# Main Construction
#############################################

# [main-construction-1]
# Create a quadric
quadric = ddg.geometry.Quadric(np.diag([a, b, c, d]))
# [main-construction-1]

# [main-construction-2]
# Construct the touching cone of the light source w.r.t. the quadric
t_cone = ddg.geometry.touching_cone(homogenize(light), quadric)
t_cone = t_cone.normalize(affine=True)
# [main-construction-2]

# [main-construction-3]
# Create a polar plane and find its intersection with the quadric
light_p = ddg.geometry.Point(homogenize(light))
polar_plane = ddg.geometry.polarize(light_p, quadric)
intersection = ddg.geometry.intersect(polar_plane, quadric)
# [main-construction-3]

# [main-construction-4]
# Create a projection plane
projection_plane = ddg.geometry.hyperplane_from_normal(normal, level=level)
# Intersect the touching cone and the projection plane
projection = ddg.geometry.intersect(t_cone, projection_plane)
# [main-construction-4]


#############################################
# Visualization
#############################################

# [visualization-1]
# Create a Blender object for the quadric
quadric_bobj = ddg.blender.convert(
    quadric, "Quadric", bounding_box=[np.inf, np.inf, 2], material="quadric"
)
ddg.blender.mesh.shade_smooth(quadric_bobj)
# Visualize the generators if necessary
if visualize_generators:
    discrete_intersection = ddg.nets.sample_smooth_net(
        ddg.to_smooth_net(intersection), density_of_generators
    )
    generators = [
        ddg.geometry.intersect(
            quadric,
            ddg.geometry.polarize(
                ddg.geometry.subspace_from_affine_points(discrete_intersection.fct(i)),
                quadric,
            ),
        )
        for i, in discrete_intersection.domain.traverser
    ]
    generator_bobjs = [
        ddg.blender.convert(
            g,
            f"generator_{i}",
            material="quadric_emission",
            bounding_box=generators_bounding_box,
        )
        for (i, g) in enumerate(generators)
    ]
# [visualization-1]

# [visualization-2]
# Create a camera
camera_bobj = ddg.blender.camera.camera(location=camera_loc)
ddg.blender.camera.look_at_point(camera_bobj, camera_look_at)
bpy.context.scene.camera = camera_bobj
# Create a light
light_bobj = ddg.blender.light.light(type_="SPOT", location=light)
if example == "ellipsoid":
    # Rotate the light source to match the touching cone orientation
    axis = ddg.geometry.cone_axis(t_cone)
    two_points = ddg.geometry.quadric_to_subspaces(
        ddg.geometry.intersect(quadric, axis)
    )
    ddg.blender.light.look_at_point(light_bobj, two_points[0].affine_point)
# Set light properties
light_bobj.data.spot_size = np.pi
light_bobj.scale *= 10
light_bobj.data.energy = 4000
light_bobj.data.spot_blend = 0
light_bobj.data.shadow_soft_size = 0.01
# [visualization-2]


# [visualization-3]
# Create a tangent cone
t_cone_snet = ddg.to_smooth_net(t_cone)
t_cone_snet.domain.intervals[1] = [0, 200]
t_cone_dnet = ddg.nets.sample_smooth_net(t_cone_snet, sampling=t_cone_sampling)
t_cone_bobj = ddg.blender.convert(t_cone_dnet, "touching_cone", material="tcone")
# [visualization-3]

# [visualization-4]
# Projection plane and intersection
center = ddg.geometry.intersect(
    ddg.geometry.subspace_from_affine_points(np.array([0, 0, 0]), light),
    projection_plane,
)
projection_plane_centered = projection_plane.center(center)

projection_plane_bobj = ddg.blender.convert(
    projection_plane_centered,
    "projection_plane",
    material="projection_plane",
    bounding_box=bounding_box_for_projection_plane,
)
projection_bobj = ddg.blender.convert(projection, "projection", material="projection")
# [visualization-4]

# [visualization-5]
# Polar plane and intersection
polar_plane_orth = ddg.geometry.orthonormalize_subspace(polar_plane)
polar_plane_bobj = ddg.blender.convert(
    polar_plane_orth, "polar_plane", material="polar_plane"
)

polar_plane_intersection_bobj = ddg.blender.convert(
    intersection, "shadow_line", material="polar"
)
# [visualization-5]
