Contact: Jean-Nicolas Brunet (jnbrunet2000@gmail.com)
This SOFA plugin brings an offscreen camera that is able to take screenshot with or without GUI. Multiple offscreen cameras can be added to a single simulation scene, and take screenshots from different angles at the same time.
Here is a quick example.
<OffscreenCamera
name="beam_and_ball"
filepath="camera_%s_%i.png"
save_frame_before_first_step="true"
save_frame_after_each_n_steps="5"
multisampling="4"
widthViewport="800"
heightViewport="600"
position="-20 0 0" lookAt="0 0 0" zNear="0.01" zFar="100" projectionType="1"/>
The multisampling
data argument is an anti-aliasing parameter that will set the number of
sampling points per pixel. Power of two should be used.
The filepath
data argument will replace %s
and %i
for the component
name and step number, respectively. In this example, a screenshot will be taken just before
the simulation starts, and then at the end of every 5 steps. Hence, for a simulation of 10 steps,
the following 800x600 images will be created:
- camera_beam_and_ball_0.png
- camera_beam_and_ball_5.png
- camera_beam_and_ball_10.png
Warning: The option save_frame_before_first_step="true"
will not work with a SOFA version v20.12 or less.
Offscreen camera should only render the component within their context tree. Hence, in the following example, the first camera will take a screenshot containing both the beam and the ball, while the second camera will only see the ball. In this example, both camera capture the same point of view, but this is not restricted.
<Node name="beam">
(...)
<HexahedronFEMForceField (...) />
<OffscreenCamera
name="beam_and_ball"
filepath="camera_%s_%i.png"
save_frame_before_first_step="true"
position="-20 0 0" lookAt="0 0 0" zNear="0.01" zFar="100" projectionType="1"/>
<Node name="ball">
<OglModel (...) />
<OffscreenCamera
name="only_and_ball"
filepath="camera_%s_%i.png"
save_frame_before_first_step="true"
position="-20 0 0" lookAt="0 0 0" zNear="0.01" zFar="100" projectionType="1"/>
</Node>
</Node>
Python bindings are also availabe. Hence, from a python script, you can manually call
OffscreenCamera::save_frame(filepath)
. The position
and lookAt
data attributes
can be dynamically changed at any time from the script to move around the camera.
In the following example, a set of predefined positions is given to the camera at each
time step. A screenshot is also rendered every time.
camera_positions = get_camera_positions()
camera = root.beam.camera
for i in range(n_frames):
Sofa.Simulation.animate(root, 1)
Sofa.Simulation.updateVisual(root)
# Move the camera to the next position
with camera.position.writeableArray() as wa:
wa[0:3] = camera_positions[i+1]
# Save screenshot at time i+1
camera.save_frame(f'frame_{i+1}.jpg')
Note that this example could be rendered into an mp4 video using, for example, ffmpeg:
ffmpeg -framerate 60 -i 'frame_%d.jpg' -c:v libx264 -vf fps=30 -pix_fmt yuv420p simulation.mp4
Here, the input frame per second is set to 60, and the output to 30, which means that the video will last 2 times the simulation time.
The file examples/rotating_camera.py contains an example where two cameras are moved in an ellipse around the bending beam. The frames of both cameras are manually render into the "only_ball" and "beam_and_ball" directories, respectively. The python script also dynamically change the position of the camera after each simulation steps. The following result was computed by running the examples/rotating_camera.py file on an headless (no GUI available) server.