.. Comment: this file is automatically generated by `update_example_docs.py`.
   It should not be modified manually.

Scene pointer events.
==========================================


This example shows how to use scene pointer events to specify rays, and how they can be
used to interact with the scene (e.g., ray-mesh intersections).

To get the demo data, see ``./assets/download_dragon_mesh.sh``.



.. code-block:: python
        :linenos:


        from __future__ import annotations

        import time
        from pathlib import Path
        from typing import cast

        import numpy as np
        import trimesh
        import trimesh.creation
        import trimesh.ray

        import viser
        import viser.transforms as tf
        from viser.theme import TitlebarConfig

        server = viser.ViserServer()
        server.gui.configure_theme(
            brand_color=(130, 0, 150),
            titlebar_content=TitlebarConfig(buttons=(), image=None),
        )
        server.scene.set_up_direction("+y")

        mesh = cast(
            trimesh.Trimesh, trimesh.load_mesh(str(Path(__file__).parent / "assets/dragon.obj"))
        )
        mesh.apply_scale(0.05)

        mesh_handle = server.scene.add_mesh_trimesh(
            name="/mesh",
            mesh=mesh,
            position=(0.0, 0.0, 0.0),
        )

        hit_pos_handles: list[viser.GlbHandle] = []


        # Buttons + callbacks will operate on a per-client basis, but will modify the global scene! :)
        @server.on_client_connect
        def _(client: viser.ClientHandle) -> None:
            # Set up the camera -- this gives a nice view of the full mesh.
            client.camera.position = np.array([0.0, 0.0, -10.0])
            client.camera.wxyz = np.array([0.0, 0.0, 0.0, 1.0])

            # Tests "click" scenepointerevent.
            click_button_handle = client.gui.add_button("Add sphere", icon=viser.Icon.POINTER)

            @click_button_handle.on_click
            def _(_):
                click_button_handle.disabled = True

                @client.scene.on_pointer_event(event_type="click")
                def _(event: viser.ScenePointerEvent) -> None:
                    # Check for intersection with the mesh, using trimesh's ray-mesh intersection.
                    # Note that mesh is in the mesh frame, so we need to transform the ray.
                    R_world_mesh = tf.SO3(mesh_handle.wxyz)
                    R_mesh_world = R_world_mesh.inverse()
                    origin = (R_mesh_world @ np.array(event.ray_origin)).reshape(1, 3)
                    direction = (R_mesh_world @ np.array(event.ray_direction)).reshape(1, 3)
                    intersector = trimesh.ray.ray_triangle.RayMeshIntersector(mesh)
                    hit_pos, _, _ = intersector.intersects_location(origin, direction)

                    if len(hit_pos) == 0:
                        return
                    client.scene.remove_pointer_callback()

                    # Get the first hit position (based on distance from the ray origin).
                    hit_pos = hit_pos[np.argmin(np.sum((hit_pos - origin) ** 2, axis=-1))]

                    # Create a sphere at the hit location.
                    hit_pos_mesh = trimesh.creation.icosphere(radius=0.1)
                    hit_pos_mesh.vertices += R_world_mesh @ hit_pos
                    hit_pos_mesh.visual.vertex_colors = (0.5, 0.0, 0.7, 1.0)  # type: ignore
                    hit_pos_handle = server.scene.add_mesh_trimesh(
                        name=f"/hit_pos_{len(hit_pos_handles)}", mesh=hit_pos_mesh
                    )
                    hit_pos_handles.append(hit_pos_handle)

                @client.scene.on_pointer_callback_removed
                def _():
                    click_button_handle.disabled = False

            # Tests "rect-select" scenepointerevent.
            paint_button_handle = client.gui.add_button("Paint mesh", icon=viser.Icon.PAINT)

            @paint_button_handle.on_click
            def _(_):
                paint_button_handle.disabled = True

                @client.scene.on_pointer_event(event_type="rect-select")
                def _(message: viser.ScenePointerEvent) -> None:
                    client.scene.remove_pointer_callback()

                    global mesh_handle
                    camera = message.client.camera

                    # Put the mesh in the camera frame.
                    R_world_mesh = tf.SO3(mesh_handle.wxyz)
                    R_mesh_world = R_world_mesh.inverse()
                    R_camera_world = tf.SE3.from_rotation_and_translation(
                        tf.SO3(camera.wxyz), camera.position
                    ).inverse()
                    vertices = cast(np.ndarray, mesh.vertices)
                    vertices = (R_mesh_world.as_matrix() @ vertices.T).T
                    vertices = (
                        R_camera_world.as_matrix()
                        @ np.hstack([vertices, np.ones((vertices.shape[0], 1))]).T
                    ).T[:, :3]

                    # Get the camera intrinsics, and project the vertices onto the image plane.
                    fov, aspect = camera.fov, camera.aspect
                    vertices_proj = vertices[:, :2] / vertices[:, 2].reshape(-1, 1)
                    vertices_proj /= np.tan(fov / 2)
                    vertices_proj[:, 0] /= aspect

                    # Move the origin to the upper-left corner, and scale to [0, 1].
                    # ... make sure to match the OpenCV's image coordinates!
                    vertices_proj = (1 + vertices_proj) / 2

                    # Select the vertices that lie inside the 2D selected box, once projected.
                    mask = (
                        (vertices_proj > np.array(message.screen_pos[0]))
                        & (vertices_proj < np.array(message.screen_pos[1]))
                    ).all(axis=1)[..., None]

                    # Update the mesh color based on whether the vertices are inside the box
                    mesh.visual.vertex_colors = np.where(  # type: ignore
                        mask, (0.5, 0.0, 0.7, 1.0), (0.9, 0.9, 0.9, 1.0)
                    )
                    mesh_handle = server.scene.add_mesh_trimesh(
                        name="/mesh",
                        mesh=mesh,
                        position=(0.0, 0.0, 0.0),
                    )

                @client.scene.on_pointer_callback_removed
                def _():
                    paint_button_handle.disabled = False

            # Button to clear spheres.
            clear_button_handle = client.gui.add_button("Clear scene", icon=viser.Icon.X)

            @clear_button_handle.on_click
            def _(_):
                """Reset the mesh color and remove all click-generated spheres."""
                global mesh_handle
                for handle in hit_pos_handles:
                    handle.remove()
                hit_pos_handles.clear()
                mesh.visual.vertex_colors = (0.9, 0.9, 0.9, 1.0)  # type: ignore
                mesh_handle = server.scene.add_mesh_trimesh(
                    name="/mesh",
                    mesh=mesh,
                    position=(0.0, 0.0, 0.0),
                )


        while True:
            time.sleep(10.0)
