


SPACEBAR: ํ์ฑ ํ์ toggle
H: ํ์ฑ์ hue ๋ณ๊ฒฝ
R: ํญ์ฑ์ ์ด๋ฆ๊ณผ ๋ฐ์ง๋ฆ, ํ์ฑ์ ๊ถค๋, ๋ฐฐ๊ฒฝ ๋ณ ๋ณ๊ฒฝ
S: ์คํฌ๋ฆฐ์ท ์ ์ฅ
use nannou::prelude::*;
use rand::seq::SliceRandom;
use rand::Rng;
use std::fs;
// --- Global Constants ---
const WIDTH: u32 = 540;
const HEIGHT: u32 = 960;
const ORBIT_COUNT: usize = 10; // Number of orbits
const STARFIELD_COUNT: usize = 100; // ๋ฐฐ๊ฒฝ ๋ณ ๊ฐ์
// --- App Code ---
fn main() {
nannou::app(model).update(update).run();
}
// App state
struct Model {
star_position: Vec2, // Star position
star_radius: f32, // Star radius (randomized)
orbits: Vec<(f32, f32)>, // (orbit radius, stroke weight)
planets: Vec<(usize, f32, f32)>, // (orbit index, planet radius, angle in degrees)
planet_speeds: Vec<f32>, // ๊ฐ ํ์ฑ์ ํ์ ์๋
is_rotating: bool, // Tracks if planets are rotating
font: nannou::text::Font, // ํฐํธ
label: String, // ๋๋ค ๋ผ๋ฒจ
planet_hue: f32, // ํ์ฑ ์์์ hue (์ ์ฒด ํ์ฑ ๊ณตํต)
planet_colors: Vec<(f32, f32)>, // ๊ฐ ํ์ฑ์ (s, l)
screenshot_index: usize, // ์ ์ฅ๋๋ ์ด๋ฏธ์ง ์๋ฒ
starfield: Vec<(Vec2, f32)>, // (๋ณ ์์น, ๋ฐ์ง๋ฆ)
}
// --- Starfield generator ---
fn generate_starfield() -> Vec<(Vec2, f32)> {
let mut rng = rand::thread_rng();
let mut stars = Vec::new();
for _ in 0..STARFIELD_COUNT {
let x = rng.gen_range(-(WIDTH as f32) / 2.0..(WIDTH as f32) / 2.0);
let y = rng.gen_range(-(HEIGHT as f32) / 2.0..(HEIGHT as f32) / 2.0);
let radius = rng.gen_range(0.3..1.0);
stars.push((vec2(x, y), radius));
}
stars
}
// Called once to set up the model
fn model(app: &App) -> Model {
// Create window with key press handler
app.new_window()
.size(WIDTH, HEIGHT)
.view(view)
.key_pressed(key_pressed)
.build()
.unwrap();
let star_position = vec2(0.0, -480.0);
// Random star radius
let mut rng = rand::thread_rng();
let star_radius: f32 = rng.gen_range(150.0..=400.0);
println!("Star radius chosen: {}", star_radius);
// Dynamic min orbit radius (star_radius + 50~80)
let min_orbit_radius = star_radius + rng.gen_range(50..=80) as f32;
// Generate random orbits
let mut orbits = Vec::new();
for _ in 0..ORBIT_COUNT {
let radius = random_range(min_orbit_radius, HEIGHT as f32);
let stroke_weight = random_range(0.3, 0.5);
orbits.push((radius, stroke_weight));
}
orbits.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
// Generate planets
let mut planets = Vec::new();
for i in 0..ORBIT_COUNT {
let orbit_index = i;
let planet_radius = random_range(3.0, 10.0);
let angle_degrees = 90.0;
planets.push((orbit_index, planet_radius, angle_degrees));
}
// ์๋ ์ด๊ธฐํ (์ฒ์์ 0)
let planet_speeds = vec![0.0; ORBIT_COUNT];
// ๊ฐ ํ์ฑ์ (s, l) ์์ ๋๋ค ์์ฑ (๊ณ ์ )
let mut planet_colors = Vec::new();
for _ in 0..ORBIT_COUNT {
let s: f32 = rng.gen_range(0.5..1.0);
let l: f32 = rng.gen_range(0.4..0.8);
planet_colors.push((s, l));
}
// --- Font & Random Label ---
let assets = app.assets_path().expect("no assets directory found");
let font_path = assets.join("fonts").join("font.ttf");
let font_data = fs::read(font_path).expect("failed to read font file");
let font = nannou::text::Font::from_bytes(font_data).expect("failed to load font");
let names = [
"Kepler", "Galileo", "Newton", "Herschel", "Hubble",
"Tycho", "Halley", "Cassini", "Copernicus", "Sagan",
"Messier", "Laplace", "Chandrasekhar", "Hawking", "Eddington",
];
let name = names.choose(&mut rng).unwrap_or(&"Kepler");
let num: u16 = rng.gen_range(100..1000);
let label = format!("{}-{}", name, num);
// ํ์ฑ ์์ hue๋ ๊ธฐ๋ณธ๊ฐ 0.0
let planet_hue = 0.0;
// --- Starfield ---
let starfield = generate_starfield();
Model {
star_position,
star_radius,
orbits,
planets,
planet_speeds,
is_rotating: false,
font,
label,
planet_hue,
planet_colors,
screenshot_index: 0,
starfield,
}
}
// Handle key press events
fn key_pressed(app: &App, model: &mut Model, key: Key) {
match key {
Key::Space => {
if model.is_rotating {
model.is_rotating = false;
} else {
let mut rng = rand::thread_rng();
for speed in model.planet_speeds.iter_mut() {
*speed = rng.gen_range(0.1..1.0);
}
model.is_rotating = true;
}
}
Key::S => {
model.screenshot_index += 1;
let filename = format!("exoplanet_{:03}.png", model.screenshot_index);
app.main_window().capture_frame(&filename);
println!("๐ธ Screenshot saved: {}", filename);
}
Key::H => {
let mut rng = rand::thread_rng();
model.planet_hue = rng.gen_range(0.0..360.0);
println!("๐จ Hue changed to: {:.2}", model.planet_hue);
}
Key::R => {
let mut rng = rand::thread_rng();
// --- ์๋ก์ด ํญ์ฑ ๋ฐ์ง๋ฆ ---
model.star_radius = rng.gen_range(150.0..=400.0);
// --- ์๋ก์ด ์ต์ ๊ถค๋ ๋ฐ์ง๋ฆ (ํญ์ฑ ๋ฐ์ง๋ฆ + 50~80) ---
let min_orbit_radius = model.star_radius + rng.gen_range(50.0..=80.0);
// --- ๊ถค๋ ์๋ก ์์ฑ ---
let mut new_orbits = Vec::new();
for _ in 0..ORBIT_COUNT {
let radius = random_range(min_orbit_radius, HEIGHT as f32);
let stroke_weight = random_range(0.3, 0.5);
new_orbits.push((radius, stroke_weight));
}
new_orbits.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
model.orbits = new_orbits;
// --- ์๋ก์ด ๋ผ๋ฒจ ---
let names = [
"Kepler", "Galileo", "Newton", "Herschel", "Hubble",
"Tycho", "Halley", "Cassini", "Copernicus", "Sagan",
];
let name = names.choose(&mut rng).unwrap_or(&"Kepler");
let num: u16 = rng.gen_range(100..1000);
model.label = format!("{}-{}", name, num);
// --- ์๋ก์ด ์คํํ๋ ---
model.starfield = generate_starfield();
// --- ๋ก๊ทธ ์ถ๋ ฅ ---
println!("โจ Star radius changed to: {:.2}", model.star_radius);
println!("๐ Orbits regenerated ({} total)", model.orbits.len());
println!("๐ Starfield regenerated ({} stars)", model.starfield.len());
println!("๐ช Label changed to: {}", model.label);
}
_ => {}
}
}
// Called every frame to update the model
fn update(_app: &App, model: &mut Model, _update: Update) {
if model.is_rotating {
for ((_, _, angle_degrees), speed) in model.planets.iter_mut().zip(model.planet_speeds.iter()) {
*angle_degrees += *speed;
if *angle_degrees >= 360.0 {
*angle_degrees -= 360.0;
}
}
}
}
// Called every frame to draw the scene
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
// 1. Black background
draw.background().color(hsla(0.0, 0.0, 0.007, 1.0));
// 2. Draw starfield
for (pos, radius) in model.starfield.iter() {
draw.ellipse()
.xy(*pos)
.radius(*radius)
.color(hsla(0.0, 0.0, 1.0, 1.0));
}
// 3. Draw star
draw.ellipse()
.xy(model.star_position)
.radius(model.star_radius)
.color(hsla(200.0, 0.2, 0.8, 1.0));
// 4. Draw orbits (gray)
for (radius, stroke_weight) in model.orbits.iter() {
draw.ellipse()
.xy(model.star_position)
.radius(*radius)
.no_fill()
.stroke(hsla(0.0, 0.0, 0.3, 1.0))
.stroke_weight(*stroke_weight);
}
// 5. Draw planets
for ((orbit_index, planet_radius, angle_degrees), (s, l)) in
model.planets.iter().zip(model.planet_colors.iter())
{
let orbit_radius = model.orbits[*orbit_index].0;
let angle_rad = deg_to_rad(*angle_degrees);
let planet_pos =
model.star_position + vec2(angle_rad.cos() * orbit_radius, angle_rad.sin() * orbit_radius);
draw.ellipse()
.xy(planet_pos)
.radius(*planet_radius)
.color(hsla(model.planet_hue, *s, *l, 1.0));
}
// 6. Draw random label
draw.text(&model.label)
.font(model.font.clone())
.xy(vec2(0.0, -400.0))
.font_size(24)
.color(hsla(0.0, 0.0, 0.0, 1.0));
draw.to_frame(app, &frame).unwrap();
}
for ((_, _, angle_degrees), speed) in model.planets.iter_mut().zip(model.planet_speeds.iter()) {
*angle_degrees += *speed;
if *angle_degrees >= 360.0 {
*angle_degrees -= 360.0;
}
}
ํ์ฑ์ ๊ถค๋ ์ธ๋ฑ์ค์ ๋ฐ์ง๋ฆ์ ๊ทธ๋๋ก ์ ์งํ๋ฉด์, ๊ฐ๋์ ์๋๋ง์ ํจ์จ์ ์ผ๋ก ์ ๋ฐ์ดํธํ๋ ์ฐ์ํ ๋ฐฉ๋ฒ
iter_mut() ์ iter()iter_mut(): ์ปฌ๋ ์
์ ์์์ ๋ํ ๊ฐ๋ณ ์ฐธ์กฐ(&mut T) ๋ฅผ ์ ๊ณตํ๋ ๋ฐ๋ณต์. ์์ ๊ฐ์ ์์ ํ ์ ์์ต๋๋ค.iter(): ์ปฌ๋ ์
์ ์์์ ๋ํ ๋ถ๋ณ ์ฐธ์กฐ(&T) ๋ฅผ ์ ๊ณตํ๋ ๋ฐ๋ณต์. ์์ ๊ฐ์ ์ฝ๊ธฐ๋ง ๊ฐ๋ฅํฉ๋๋ค.zip() ๋ฉ์๋[a, b, c].zip([1, 2, 3]) โ [(a, 1), (b, 2), (c, 3)]((_, _, angle_degrees), speed)์์:
zip()์ด ์์ฑํ (planet, speed) ์planet์ (usize, f32, f32) ํ์_: ํด๋น ์์น์ ๊ฐ์ ๋ฌด์ํ๊ฒ ๋ค๋ ์๋ฏธ*:์ฐธ์กฐ(&T ๋๋ &mut T)๊ฐ ๊ฐ๋ฆฌํค๋ ์ค์ ๊ฐ์ ๊ฐ์ ธ์ต๋๋ค.
*angle_degrees๋ &mut f32์์ ์ค์ f32 ๊ฐ์ ์ ๊ทผํ์ฌ ์์ ํ ์ ์๊ฒ ํฉ๋๋ค.
โ โ๏ธ๐ ๏ธ๐๐๐จ๐ผ๏ธ๐งฉ๐ง ๐งฎ๐น๏ธ๐งฟโณ๐๏ธ๐ฎ๐๐ชฉ๐๏ธ๐๐งฐ๐
์๋ณด์นด๋
Sample
Important
Highlighting
๋น์
์ ํฉ์
purple
hot pink
yellow
์ด๊ตฌ์
orange
green
blue