Preview
App
use std::f64::consts::{PI, TAU};
use ranim::{
color::palettes::manim,
glam::{DMat4, DVec3},
items::mesh::{Sphere, Surface},
prelude::*,
utils::rate_functions::linear,
};
use ranim_anims::fading::FadingAnim;
use ranim_core::animation::Eval;
use ranim_items::vitem::{VItem, text::TextItem};
struct OrbitMotion {
src: Surface,
orbit_radius: f64,
initial_angle: f64,
total_angle: f64,
}
impl Eval<Surface> for OrbitMotion {
fn eval_alpha(&self, alpha: f64) -> Surface {
let angle = self.initial_angle + self.total_angle * alpha;
let x = self.orbit_radius * angle.cos();
let y = self.orbit_radius * angle.sin();
let mut result = self.src.clone();
result.transform = DMat4::from_translation(DVec3::new(x, y, 0.0));
result
}
}
struct LabelOrbitMotion {
src: Vec<VItem>,
orbit_radius: f64,
initial_angle: f64,
total_angle: f64,
z_offset: f64,
}
impl Eval<Vec<VItem>> for LabelOrbitMotion {
fn eval_alpha(&self, alpha: f64) -> Vec<VItem> {
let angle = self.initial_angle + self.total_angle * alpha;
let x = self.orbit_radius * angle.cos();
let y = self.orbit_radius * angle.sin();
let mut result = self.src.clone();
result.move_to(DVec3::new(x, y, self.z_offset));
result
}
}
struct PlanetData {
name: &'static str,
radius: f64,
orbit_radius: f64,
color: ranim::color::AlphaColor<ranim::color::Srgb>,
period: f64,
initial_angle: f64,
has_atmosphere: bool,
}
const PLANETS: &[PlanetData] = &[
PlanetData {
name: "Mercury",
radius: 0.3,
orbit_radius: 4.0,
color: manim::GREY_C,
period: 2.0,
initial_angle: 0.0,
has_atmosphere: false,
},
PlanetData {
name: "Venus",
radius: 0.5,
orbit_radius: 6.0,
color: manim::GOLD_E,
period: 3.0,
initial_angle: PI / 4.0,
has_atmosphere: true,
},
PlanetData {
name: "Earth",
radius: 0.5,
orbit_radius: 8.0,
color: manim::BLUE_C,
period: 4.0,
initial_angle: PI / 2.0,
has_atmosphere: true,
},
PlanetData {
name: "Mars",
radius: 0.4,
orbit_radius: 10.0,
color: manim::RED_C,
period: 5.0,
initial_angle: PI,
has_atmosphere: false,
},
PlanetData {
name: "Jupiter",
radius: 1.2,
orbit_radius: 14.0,
color: manim::ORANGE,
period: 8.0,
initial_angle: 3.0 * PI / 2.0,
has_atmosphere: true,
},
PlanetData {
name: "Saturn",
radius: 1.0,
orbit_radius: 17.0,
color: manim::GOLD_C,
period: 10.0,
initial_angle: TAU / 3.0,
has_atmosphere: true,
},
];
#[scene]
#[output(dir = "solar_system")]
fn solar_system(r: &mut RanimScene) {
let phi = 50.0f64.to_radians();
let theta = -PI / 2.0;
let distance = 60.0;
let mut cam = CameraFrame::from_spherical(phi, theta, distance);
cam.fovy = 30.0f64.to_radians();
let _r_cam = r.insert(cam);
let sun = Surface::from(
Sphere::new(2.0)
.with_resolution((30, 15))
.with_fill_color(manim::YELLOW_C.with_alpha(0.5)),
);
let _r_sun = r.insert(sun);
for planet in PLANETS {
let major_radius = planet.orbit_radius;
let minor_radius = 0.05;
let resolution = (128, 16);
let mut orbit_torus = Surface::from_uv_func(
|u, v| {
let u_angle = u * TAU;
let v_angle = v * TAU;
DVec3::new(
(major_radius + minor_radius * v_angle.cos()) * u_angle.cos(),
(major_radius + minor_radius * v_angle.cos()) * u_angle.sin(),
minor_radius * v_angle.sin(),
)
},
(0.0, 1.0),
(0.0, 1.0),
resolution,
);
let color = manim::GREY_C.with_alpha(0.3);
orbit_torus.vertex_colors = vec![color; orbit_torus.vertices.len()];
let r_orbit = r.insert_empty();
r.timeline_mut(r_orbit)
.play(orbit_torus.fade_in().with_duration(1.0));
}
for planet in PLANETS {
let x = planet.orbit_radius * planet.initial_angle.cos();
let y = planet.orbit_radius * planet.initial_angle.sin();
if planet.has_atmosphere {
let mut core = Surface::from(
Sphere::new(planet.radius * 0.9)
.with_resolution((20, 10))
.with_fill_color(planet.color.with_alpha(1.0)),
);
core.transform = DMat4::from_translation(DVec3::new(x, y, 0.0));
let r_core = r.insert(core.clone());
let mut atmosphere = Surface::from(
Sphere::new(planet.radius)
.with_resolution((20, 10))
.with_fill_color(planet.color.with_alpha(0.3)),
)
.with_smooth_normals();
atmosphere.transform = DMat4::from_translation(DVec3::new(x, y, 0.0));
let r_atmosphere = r.insert(atmosphere.clone());
let orbit_total_angle = TAU * (10.0 / planet.period);
r.timeline_mut(r_core).forward(2.0).play(
OrbitMotion {
src: core.clone(),
orbit_radius: planet.orbit_radius,
initial_angle: planet.initial_angle,
total_angle: orbit_total_angle,
}
.into_animation_cell()
.apply_to(&mut core)
.with_duration(10.0)
.with_rate_func(linear),
);
r.timeline_mut(r_atmosphere).forward(2.0).play(
OrbitMotion {
src: atmosphere.clone(),
orbit_radius: planet.orbit_radius,
initial_angle: planet.initial_angle,
total_angle: orbit_total_angle,
}
.into_animation_cell()
.apply_to(&mut atmosphere)
.with_duration(10.0)
.with_rate_func(linear),
);
} else {
let mut planet_surface = Surface::from(
Sphere::new(planet.radius)
.with_resolution((20, 10))
.with_fill_color(planet.color.with_alpha(1.0)),
)
.with_smooth_normals();
planet_surface.transform = DMat4::from_translation(DVec3::new(x, y, 0.0));
let r_planet = r.insert(planet_surface.clone());
let orbit_total_angle = TAU * (10.0 / planet.period);
r.timeline_mut(r_planet).forward(2.0).play(
OrbitMotion {
src: planet_surface.clone(),
orbit_radius: planet.orbit_radius,
initial_angle: planet.initial_angle,
total_angle: orbit_total_angle,
}
.into_animation_cell()
.apply_to(&mut planet_surface)
.with_duration(10.0)
.with_rate_func(linear),
);
}
let label_z_offset = planet.radius + 0.5;
let label = TextItem::new(planet.name, 0.6).with(|item| {
item.move_to(DVec3::new(x, y, label_z_offset))
.with_origin(AabbPoint::CENTER, |x| {
x.rotate_on_x(30.0f64.to_radians()).discard()
})
.discard()
});
let mut label_vitems = Vec::<VItem>::from(label);
let r_label = r.insert_empty();
r.timeline_mut(r_label)
.forward(1.0)
.play(label_vitems.clone().fade_in().with_duration(1.0));
let orbit_total_angle = TAU * (10.0 / planet.period);
r.timeline_mut(r_label).play(
LabelOrbitMotion {
src: label_vitems.clone(),
orbit_radius: planet.orbit_radius,
initial_angle: planet.initial_angle,
total_angle: orbit_total_angle,
z_offset: label_z_offset,
}
.into_animation_cell()
.apply_to(&mut label_vitems)
.with_duration(10.0)
.with_rate_func(linear),
);
}
r.insert_time_mark(6.0, TimeMark::Capture("preview.png".to_string()));
}