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:
viser.CameraHandle.wxyz
andviser.CameraHandle.position
for setting camera poseviser.CameraHandle.near
andviser.CameraHandle.far
for clipping plane controlviser.CameraHandle.look_at
for orbit center positioningviser.ViserServer.atomic()
for synchronized camera updatesSmooth interpolation using SE(3) transformations
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

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)