use proof_engine::math::attractors::{derivatives, rk4_step};
use proof_engine::prelude::*;
use proof_engine::render::ui_layer::UiParticle;
const POINTS: usize = 40_000;
const TIME_SCALE: f32 = 0.22;
const SUBSTEPS: usize = 2;
const SPIN: f32 = 0.05;
fn main() {
env_logger::init();
let mut engine = ProofEngine::new(EngineConfig {
window_title: "Proof Engine: Lorenz attractor".to_string(),
window_width: 1280,
window_height: 720,
render: proof_engine::config::RenderConfig {
bloom_enabled: true,
bloom_intensity: 1.1,
bloom_threshold: 0.35,
tonemap: 1.0,
exposure: 1.1,
persistence: 0.55,
vignette: 0.35,
global_illumination: false,
volumetric_fog: false,
..Default::default()
},
..Default::default()
});
let lorenz = AttractorType::Lorenz;
let mut s = Vec3::new(0.1, 0.0, 0.0);
for _ in 0..3000 {
s = rk4_step(lorenz, s, 0.005);
}
let mut points: Vec<Vec3> = (0..POINTS)
.map(|i| {
for _ in 0..3 {
s = rk4_step(lorenz, s, 0.005);
}
s + Vec3::splat(hash(i as f32) * 1e-3)
})
.collect();
let mut yaw = -1.05_f32;
let mut paused = false;
let mut sim_t = 0.0_f32;
engine.run_ui(move |engine, dt| {
if engine.input.just_pressed(Key::Escape) {
engine.input.quit_requested = true;
}
if engine.input.just_pressed(Key::Space) {
paused = !paused;
}
if engine.input.is_pressed(Key::Left) {
yaw -= dt * 1.2;
}
if engine.input.is_pressed(Key::Right) {
yaw += dt * 1.2;
}
if !paused {
let h = dt * TIME_SCALE / SUBSTEPS as f32;
for p in points.iter_mut() {
for _ in 0..SUBSTEPS {
*p = rk4_step(lorenz, *p, h);
}
}
sim_t += dt * TIME_SCALE;
yaw += dt * SPIN;
}
let (w, h) = engine.render_size();
let (w, h) = (w as f32, h as f32);
let scale = h / 58.0;
let (cx, cy) = (w * 0.5, h * 0.5);
let (sy, cyaw) = yaw.sin_cos();
let size = (h / 360.0).max(1.5);
let particles: Vec<UiParticle> = points
.iter()
.map(|p| {
let x = p.x * cyaw - p.y * sy;
let depth = p.x * sy + p.y * cyaw;
let px = cx + x * scale;
let py = cy - (p.z - 25.0) * scale;
let speed = derivatives(lorenz, *p).length();
let c = palette((speed / 190.0).clamp(0.0, 1.0));
let near = 0.75 + 0.25 * (depth / 25.0).clamp(-1.0, 1.0);
let mut q = UiParticle::new(px, py, size, size, '●', (c * near).extend(0.85));
q.emission = 0.6 + 0.9 * (speed / 190.0).min(1.0);
q
})
.collect();
engine.ui.draw_particles(particles);
let dim = Vec4::new(0.85, 0.88, 0.92, 0.75);
engine.ui.draw_text(20.0, 18.0, "Lorenz attractor dx/dt = 10(y - x) dy/dt = x(28 - z) - y dz/dt = xy - 8z/3", 1.0, dim);
let status = format!(
"{POINTS} points RK4 t = {sim_t:.2}{} [Space] pause [Left/Right] turn",
if paused { " (paused)" } else { "" }
);
engine.ui.draw_text(20.0, 42.0, &status, 1.0, Vec4::new(0.85, 0.88, 0.92, 0.55));
});
}
fn palette(t: f32) -> Vec3 {
let cool = Vec3::new(0.10, 0.55, 0.70);
let mid = Vec3::new(0.85, 0.80, 0.70);
let hot = Vec3::new(1.40, 0.55, 0.16);
if t < 0.5 {
cool.lerp(mid, t * 2.0)
} else {
mid.lerp(hot, (t - 0.5) * 2.0)
}
}
fn hash(x: f32) -> f32 {
((x * 12.9898).sin() * 43_758.547).fract().abs()
}