Skip to content

ThreeDView

Cargo featurefeatures = ["3d"]

three::ThreeDView is Manim’s ThreeDScene. Its objects are 3D mobjects, rendered by fframes::three_d into one image over the canvas, and fixed_in_frame draws 2D on top. The camera is Manim’s: phi from the z axis, theta around it, focal_distance 20 and zoom 1, so with the default phi 0 and theta -90° the xy plane lines up with ui.space. ambient_rotation turns theta by that many radians per second, Manim’s begin_ambient_camera_rotation. ThreeDView::projector(ui) maps Manim’s points to pixels for labels on 3D points; ThreeDAxes draws its x, y and z that way.

Gallery scene: ThreeDView (a sphere and axes under a fixed title, with the camera turning).

ThreeDView demo, first frame
ThreeDView 4 s

The mobjects keep Manim’s names, sizes and defaults: Surface (a checkerboard of func(u, v)), ThreeDAxes (with c2p), Sphere, Cube, Prism, Cylinder, Cone, Torus, Dot3D, Line3D, Arrow3D, Polyhedron, the four Platonic solids and ConvexHull3D. Positions are Manim’s (x, y, z) in units, z out of the screen. Every object takes enter: Some(Create { .. }.into()) to grow from its center or FadeIn to fade in.

The scene is rebuilt for every frame. One checkerboard sphere at 1080p costs about 60 ms on 32 CPU threads; with fframes/wgpu the renderer uses the GPU.

use fframes_kit::prelude::*;
fn wave(u: f32, v: f32) -> (f32, f32, f32) {
(u, v, 0.5 * u.sin() * v.cos())
}
fn caption<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
ui.show(Label {
text: "A rotating surface".into(),
x: 960.0,
y: 120.0,
direction: Anchor::Center,
..ui.defaults()
})
}
fn surface<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
let objects: Vec<Box<dyn Object3D + 'a>> = vec![
Box::new(ThreeDAxes { ..ui.defaults() }),
Box::new(Surface { func: wave, u_range: (-3.0, 3.0), v_range: (-3.0, 3.0), ..ui.defaults() }),
Box::new(Sphere { center: (0.0, 0.0, 2.0), radius: 0.5, enter: Some(Create { ..ui.defaults() }.into()), ..ui.defaults() }),
];
ui.show(ThreeDView {
phi: 75f32.to_radians(),
theta: 30f32.to_radians(),
ambient_rotation: 0.2,
objects,
fixed_in_frame: Some(children(caption)),
..ui.defaults()
})
}

Map both ends with c2p so the arrow follows the axes’ ranges and lengths. The camera turns around the axes. Gallery scene: VectorCoordinates3D.

VectorCoordinates3D demo, first frame
VectorCoordinates3D 4 s
use fframes_kit::prelude::*;
fn vector<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
let axes = ThreeDAxes { ..ui.defaults() };
let point = axes.c2p(3.0, 2.0, 2.0);
let color = ui.theme.colors.highlight;
let objects: Vec<Box<dyn Object3D + 'a>> = vec![
Box::new(axes),
Box::new(Arrow3D {
start: axes.c2p(0.0, 0.0, 0.0), end: point, color,
enter: Some(Create { run_time: 1.0, ..ui.defaults() }.into()),
..ui.defaults()
}),
Box::new(Dot3D { point, color, radius: 0.12, ..ui.defaults() }),
];
ui.show(ThreeDView {
phi: 75f32.to_radians(), theta: 30f32.to_radians(), ambient_rotation: 0.25,
objects, ..ui.defaults()
})
}

Surface samples a function over two parameter ranges. Here u and v are the x and y coordinates and the height is (u² + v²) / 4. Gallery scene: Paraboloid3D.

Paraboloid3D demo, first frame
Paraboloid3D 4 s
use fframes_kit::prelude::*;
fn paraboloid(u: f32, v: f32) -> (f32, f32, f32) {
(u, v, 0.25 * (u * u + v * v))
}
fn surface<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
let objects: Vec<Box<dyn Object3D + 'a>> = vec![
Box::new(ThreeDAxes { show_labels: false, ..ui.defaults() }),
Box::new(Surface {
func: paraboloid, u_range: (-2.5, 2.5), v_range: (-2.5, 2.5),
resolution: (24, 24),
enter: Some(FadeIn { run_time: 1.0, ..ui.defaults() }.into()),
..ui.defaults()
}),
];
ui.show(ThreeDView {
phi: 75f32.to_radians(), theta: 30f32.to_radians(), ambient_rotation: 0.2,
objects, ..ui.defaults()
})
}

Combine solids in one view and stagger their entrances. Torus lies around the z axis; its major and minor radii set the ring and tube sizes. Gallery scene: SphereAndTorus3D.

SphereAndTorus3D demo, first frame
SphereAndTorus3D 4 s
use fframes_kit::prelude::*;
fn solids<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
let objects: Vec<Box<dyn Object3D + 'a>> = vec![
Box::new(Sphere {
radius: 0.9, fill_color: ui.theme.colors.highlight,
enter: Some(Create { run_time: 0.8, ..ui.defaults() }.into()),
..ui.defaults()
}),
Box::new(Torus {
major_radius: 2.1, minor_radius: 0.18,
enter: Some(FadeIn { delay: 0.5, run_time: 0.8, ..ui.defaults() }.into()),
..ui.defaults()
}),
];
ui.show(ThreeDView {
phi: 75f32.to_radians(), theta: 30f32.to_radians(), ambient_rotation: 0.3,
objects, ..ui.defaults()
})
}

With physics and 3d, put a RadialWave in a ThreeDView to show the changing surface from two sources. Gallery scene: WaveInterference3D.

WaveInterference3D demo, first frame
WaveInterference3D 4 s
use fframes_kit::prelude::*;
fn interference<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
let wave = RadialWave {
sources: vec![(-1.2, 0.0), (1.2, 0.0)], wavelength: 1.0,
speed: 1.2, amplitude: 0.3, front: false,
x_range: (-4.0, 4.0), y_range: (-4.0, 4.0), ..ui.defaults()
};
let objects: Vec<Box<dyn Object3D + 'a>> = vec![Box::new(wave)];
ui.show(ThreeDView {
phi: 75f32.to_radians(), theta: 30f32.to_radians(), ambient_rotation: 0.15,
objects, ..ui.defaults()
})
}

With biology and 3d, the bases build a double helix that rotates about its axis. Gallery scene: DnaSequence3D.

DnaSequence3D demo, first frame
DnaSequence3D 4 s
use fframes_kit::prelude::*;
fn dna<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
ui.show(DnaHelix3D {
sequence: "GATTACAGATTACA".into(), width: 1100.0, height: 500.0,
spin: 25.0, ..ui.defaults()
})
}

With chemistry and 3d, parse XYZ coordinates once and borrow the structure on each frame. The first O–H bond lights up after one second. Gallery scene: WaterMolecule3D.

WaterMolecule3D demo, first frame
WaterMolecule3D 4 s
use fframes_kit::prelude::*;
fn water<'a>(ui: &mut Ui<'a, '_, '_>, structure: &'a Structure3D) -> Svgr<'a> {
ui.show(Molecule3D {
structure: Some(structure), width: 1000.0, height: 700.0, spin: 25.0,
highlight: vec![0], highlight_at: 1.0, ..ui.defaults()
})
}
fn water_structure() -> Result<Structure3D, XyzError> {
Structure3D::from_xyz("3\nwater\nO 0 0 0.117\nH 0 0.757 -0.469\nH 0 -0.757 -0.469\n")
}

Components are plain structs. Set the fields you need and fill the rest with ..ui.defaults(): the defaults below come from the current theme (theme. values are fields of Theme).

Field Type Default Description
phi f32 0.0 Angle of the camera from the z axis in radians; 0 looks straight down on the xy plane.
theta f32 -FRAC_PI_2 Angle of the camera around the z axis from the x axis in radians; -90 degrees is the default.
distance f32 20.0 How far the camera sits from frame_center in Manim units; changes the perspective, not the lens.
focal_distance f32 20.0 Distance in Manim units at which the lens shows the canvas’s height; the plane through frame_center lines up with ui.space when distance matches.
zoom f32 1.0 Zoom of the lens; above 1 shows less of the scene.
frame_center Point3 (0.0, 0.0, 0.0) The point the camera orbits and looks at, in Manim units (x, y, z).
ambient_rotation f32 0.0 Radians per second theta turns; 0 keeps the camera still.
objects Vec<Box<dyn Object3D>> Vec::new() The 3D objects drawn in the image, in order.
fixed_in_frame Option<Children> None Drawn over the picture in canvas pixels, not moved by the camera (a closure taking the Ui); None for none.

Methods:

  • fn camera(&self, space: Space, seconds: f32) -> Camera: The camera seconds into the view.
  • fn projector(&self, ui: &Ui) -> Projector: The projector of the picture at ui’s time.

Feature 3d · Source