Programmatic camera control

Control camera position, orientation, and parameters programmatically.

This example demonstrates how to programmatically control client cameras, including smooth animated transitions between viewpoints. Camera control is essential for guided tours, automatic viewpoint switching, or creating cinematic presentations of 3D content.

Key features:

The example shows how to create clickable frames that smoothly animate the camera to different viewpoints, and how to use atomic updates to prevent visual artifacts during camera transitions.

Source: examples/03_interaction/04_camera_commands.py

Programmatic camera control

Code

 1import time
 2
 3import numpy as np
 4
 5import viser
 6import viser.transforms as tf
 7
 8server = viser.ViserServer()
 9num_frames = 20
10
11
12@server.on_client_connect
13def _(client: viser.ClientHandle) -> None:
14
15    client.camera.far = 10.0
16
17    near_slider = client.gui.add_slider(
18        "Near", min=0.01, max=10.0, step=0.001, initial_value=client.camera.near
19    )
20    far_slider = client.gui.add_slider(
21        "Far", min=1, max=20.0, step=0.001, initial_value=client.camera.far
22    )
23
24    @near_slider.on_update
25    def _(_) -> None:
26        client.camera.near = near_slider.value
27
28    @far_slider.on_update
29    def _(_) -> None:
30        client.camera.far = far_slider.value
31
32
33@server.on_client_connect
34def _(client: viser.ClientHandle) -> None:
35
36    rng = np.random.default_rng(0)
37
38    def make_frame(i: int) -> None:
39        # Sample a random orientation + position.
40        wxyz = rng.normal(size=4)
41        wxyz /= np.linalg.norm(wxyz)
42        position = rng.uniform(-3.0, 3.0, size=(3,))
43
44        # Create a coordinate frame and label.
45        frame = client.scene.add_frame(f"/frame_{i}", wxyz=wxyz, position=position)
46        client.scene.add_label(f"/frame_{i}/label", text=f"Frame {i}")
47
48        # Move the camera when we click a frame.
49        @frame.on_click
50        def _(_):
51            T_world_current = tf.SE3.from_rotation_and_translation(
52                tf.SO3(client.camera.wxyz), client.camera.position
53            )
54            T_world_target = tf.SE3.from_rotation_and_translation(
55                tf.SO3(frame.wxyz), frame.position
56            ) @ tf.SE3.from_translation(np.array([0.0, 0.0, -0.5]))
57
58            T_current_target = T_world_current.inverse() @ T_world_target
59
60            for j in range(20):
61                T_world_set = T_world_current @ tf.SE3.exp(
62                    T_current_target.log() * j / 19.0
63                )
64
65                # We can atomically set the orientation and the position of the camera
66                # together to prevent jitter that might happen if one was set before the
67                # other.
68                with client.atomic():
69                    client.camera.wxyz = T_world_set.rotation().wxyz
70                    client.camera.position = T_world_set.translation()
71
72                client.flush()  # Optional!
73                time.sleep(1.0 / 60.0)
74
75            # Mouse interactions should orbit around the frame origin.
76            client.camera.look_at = frame.position
77
78    for i in range(num_frames):
79        make_frame(i)
80
81
82while True:
83    time.sleep(1.0)