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