

use nannou::prelude::*;
const WIN_SIZE: u32 = 1000;
const NUM_POINTS: usize = 1000;
const RANGE: f32 = 230.0; // ์ค์ฌ์ ๊ณผ ์ฐ๊ฒฐ๋๋ ์ต๋ ๊ฑฐ๋ฆฌ
struct MovingPoint {
pos: Point2,
speed: f32,
}
struct Model {
points: Vec<MovingPoint>,
}
fn main() {
nannou::app(model).update(update).run();
}
fn model(app: &App) -> Model {
app.new_window()
.size(WIN_SIZE, WIN_SIZE)
.view(view)
.build()
.unwrap();
let mut points = Vec::new();
for _ in 0..NUM_POINTS {
points.push(random_point());
}
Model { points }
}
fn update(_app: &App, model: &mut Model, _update: Update) {
// ์ ๋ค ์๋๋ก ์ด๋
for p in &mut model.points {
p.pos.y -= p.speed;
}
// ํ๋ฉด ์๋ ๋ฒ์ด๋ ์ ์ ๊ฑฐ
model.points.retain(|p| p.pos.y > -(WIN_SIZE as f32) / 2.0 - 50.0);
// ๋ถ์กฑํ ์ ์ฑ์๋ฃ๊ธฐ
while model.points.len() < NUM_POINTS {
model.points.push(random_point());
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(BLACK);
// ์ค์ฌ ํ์ ์
let center = pt2(0.0, 0.0);
draw.ellipse()
.xy(center)
.radius(15.0)
.color(WHITE);
// ์ด๋ ์ ๋ค
for p in &model.points {
let dist = p.pos.distance(center);
if dist < RANGE {
draw.ellipse().xy(p.pos).radius(2.0).color(WHITE);
draw.line()
.start(center)
.end(p.pos)
.weight(0.3)
.color(WHITE);
} else {
draw.ellipse().xy(p.pos).radius(2.0).color(GRAY);
}
}
draw.to_frame(app, &frame).unwrap();
}
fn random_point() -> MovingPoint {
let x = random_range(-(WIN_SIZE as f32) / 2.0, (WIN_SIZE as f32) / 2.0);
let y = (WIN_SIZE as f32) / 2.0 + 20.0; // ํ๋ฉด ์์์ ์์ฑ
let speed = random_range(0.5, 2.0);
MovingPoint {
pos: pt2(x, y),
speed,
}
}
// nannou::prelude::*๋ nannou๋ฅผ ์ฌ์ฉํ๋๋ฐ ํ์ํ ๋๋ถ๋ถ์ ๊ธฐ๋ณธ ์์๋ค(ํจ์, ํ์
, ๋ชจ๋ ๋ฑ)์
// ํ ๋ฒ์ ๊ฐ์ ธ์ค๋ ์ญํ ์ ํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ์ ์งํ ์ ์์ต๋๋ค.
use nannou::prelude::*;
// ์ฐฝ(window)์ ๊ฐ๋ก, ์ธ๋ก ํฌ๊ธฐ๋ฅผ ์ ์ฅํ๋ ์์์
๋๋ค.
// ํ์
์ u32 (๋ถํธ ์๋ 32๋นํธ ์ ์)์ด๋ฉฐ, ๊ฐ์ 800์
๋๋ค.
const WIN_SIZE: u32 = 800;
// ํ๋ฉด์ ํ์๋ ์ (point)์ ์ด ๊ฐ์๋ฅผ ์ ์ฅํ๋ ์์์
๋๋ค.
// ํ์
์ usize (๋ฉ๋ชจ๋ฆฌ ์ฃผ์ ๊ณต๊ฐ์ ํฌ๊ธฐ์ ๊ฐ์ ๋ถํธ ์๋ ์ ์)์ด๋ฉฐ, ๊ฐ์ 20์
๋๋ค.
// ๋ฒกํฐ์ ์ธ๋ฑ์ฑ์ด๋ ๊ธธ์ด์ ๊ด๋ จ๋ ๊ฐ์ ์ฃผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
const NUM_POINTS: usize = 20;
// ํ๋ฉด ์ค์์ ์์ผ๋ก๋ถํฐ ์ํธ์์ฉ์ด ์ผ์ด๋๋ ์ต๋ ๊ฑฐ๋ฆฌ๋ฅผ ์ ์ฅํ๋ ์์์
๋๋ค.
// ์ด ๊ฑฐ๋ฆฌ ์์ ๋ค์ด์จ ์ ๋ค์ ์ค์๊ณผ ์ ์ผ๋ก ์ฐ๊ฒฐ๋ฉ๋๋ค.
// ํ์
์ f32 (32๋นํธ ๋ถ๋์์์ ์)์ด๋ฉฐ, ๊ฐ์ 150.0์
๋๋ค.
const RANGE: f32 = 150.0;
// ์์ง์ด๋ ์ ํ๋ํ๋์ ์ํ๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ๊ตฌ์กฐ์ฒด(struct)์
๋๋ค.
struct MovingPoint {
// ์ ์ 2D ์์น(x, y ์ขํ)๋ฅผ ์ ์ฅํ๋ ํ๋์
๋๋ค.
// ํ์
์ Point2์ด๋ฉฐ, nannou์์ ์ ๊ณตํ๋ 2์ฐจ์ ๋ฒกํฐ ํ์
(Vec2)์ ๋ณ์นญ(alias)์
๋๋ค.
pos: Point2,
// ์ ์ด ์๋๋ก ๋จ์ด์ง๋ ์๋๋ฅผ ์ ์ฅํ๋ ํ๋์
๋๋ค.
// ํ์
์ f32 (32๋นํธ ๋ถ๋์์์ ์)์
๋๋ค.
speed: f32,
}
// ์ด ์ ํ๋ฆฌ์ผ์ด์
์ ์ ์ฒด ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฉ์ธ ๋ชจ๋ธ ๊ตฌ์กฐ์ฒด์
๋๋ค.
struct Model {
// ์ฌ๋ฌ ๊ฐ์ MovingPoint ์ธ์คํด์ค๋ฅผ ๋ด๊ณ ์๋ ๋์ ๋ฐฐ์ด(Vector)์
๋๋ค.
// ํ๋ฉด์ ํ์๋ ๋ชจ๋ ์ ๋ค์ ๋ฐ์ดํฐ๊ฐ ์ฌ๊ธฐ์ ์ ์ฅ๋ฉ๋๋ค.
points: Vec<MovingPoint>,
}
// Rust ํ๋ก๊ทธ๋จ์ ์์์ (entry point)์
๋๋ค.
fn main() {
// nannou ์ฑ์ ์ค์ ํ๊ณ ์คํํฉ๋๋ค.
// app(model): ์ฑ์ ์ด๊ธฐ ์ํ๋ฅผ ์ค์ ํ๊ธฐ ์ํด model ํจ์๋ฅผ ์ง์ ํฉ๋๋ค.
// .update(update): ๋งค ํ๋ ์๋ง๋ค ์ฑ์ ์ํ๋ฅผ ์
๋ฐ์ดํธํ๊ธฐ ์ํด update ํจ์๋ฅผ ์ง์ ํฉ๋๋ค.
// .run(): ์ค์ ์ด ์๋ฃ๋ ์ฑ์ ๋ฉ์ธ ๋ฃจํ๋ฅผ ์์ํฉ๋๋ค.
nannou::app(model).update(update).run();
}
// ์ฑ์ด ์ฒ์ ์์๋ ๋ ํ ๋ฒ๋ง ํธ์ถ๋์ด ์ด๊ธฐ ๋ชจ๋ธ ์ํ๋ฅผ ์ค์ ํ๋ ํจ์์
๋๋ค.
// app: &App -> ํ์ฌ nannou ์ ํ๋ฆฌ์ผ์ด์
์ ๋ํ ์ฐธ์กฐ์
๋๋ค. ์ฐฝ ์์ฑ ๋ฑ์ ์ฌ์ฉ๋ฉ๋๋ค.
// ๋ฐํ ๊ฐ: ์ด๊ธฐํ๋ Model ์ธ์คํด์ค๋ฅผ ๋ฐํํฉ๋๋ค.
fn model(app: &App) -> Model {
// ์๋ก์ด ์ฐฝ์ ์์ฑํ๊ณ ์ค์ ํฉ๋๋ค.
app.new_window()
// .size(): ์ฐฝ์ ํฌ๊ธฐ๋ฅผ WIN_SIZE ์์๋ฅผ ์ด์ฉํด 800x800 ํฝ์
๋ก ์ค์ ํฉ๋๋ค.
.size(WIN_SIZE, WIN_SIZE)
// .view(): ๋งค ํ๋ ์ ํ๋ฉด์ ๊ทธ๋ฆฌ๋ ์ญํ ์ ํ view ํจ์๋ฅผ ์ง์ ํฉ๋๋ค.
.view(view)
// .build(): ์ ์ค์ ๋ค์ ๋ฐํ์ผ๋ก ์ค์ ์ฐฝ์ ์์ฑํฉ๋๋ค.
// .unwrap(): ์ฐฝ ์์ฑ์ด ์คํจํ ๊ฒฝ์ฐ ํ๋ก๊ทธ๋จ์ ์ค๋จ์ํต๋๋ค. (Result<T, E> ํ์
์ฒ๋ฆฌ)
.build()
.unwrap();
// MovingPoint๋ค์ ๋ด์ ๋น์ด์๋ ๋์ ๋ฐฐ์ด(Vector)์ ์์ฑํฉ๋๋ค.
// mut ํค์๋๋ ์ด ๋ณ์๊ฐ ๋ณ๊ฒฝ๋ ์ ์์์ ๋ํ๋
๋๋ค.
let mut points = Vec::new();
// NUM_POINTS (20)๋ฒ ๋งํผ ๋ฐ๋ณตํฉ๋๋ค.
// _ ๋ ๋ฐ๋ณต ๋ณ์๋ฅผ ์ฌ์ฉํ์ง ์๊ฒ ๋ค๋ ์๋ฏธ์
๋๋ค.
for _ in 0..NUM_POINTS {
// random_point ํจ์๋ฅผ ํธ์ถํ์ฌ ์๋ก์ด ์ ์ ์์ฑํ๊ณ , points ๋ฒกํฐ์ ์ถ๊ฐํฉ๋๋ค.
points.push(random_point());
}
// ์์ฑ๋ points ๋ฒกํฐ๋ฅผ ํฌํจํ๋ Model ๊ตฌ์กฐ์ฒด ์ธ์คํด์ค๋ฅผ ์์ฑํ์ฌ ๋ฐํํฉ๋๋ค.
// ์ด๊ฒ์ด ์ฑ์ ์ด๊ธฐ ์ํ๊ฐ ๋ฉ๋๋ค.
Model { points }
}
// ๋งค ํ๋ ์๋ง๋ค ํธ์ถ๋์ด ์ฑ์ ๋
ผ๋ฆฌ(์ํ)๋ฅผ ์
๋ฐ์ดํธํ๋ ํจ์์
๋๋ค.
// _app: &App -> ํ์ฌ ์ฑ์ ๋ํ ์ฐธ์กฐ. ์ด ์ฝ๋์์๋ ์ฌ์ฉ๋์ง ์์ _๋ก ์์ํฉ๋๋ค.
// model: &mut Model -> ์ฑ์ ์ํ๋ฅผ ๋ด๊ณ ์๋ Model์ ๋ํ '๊ฐ๋ณ' ์ฐธ์กฐ. ์ด ํจ์ ๋ด์์ ์ํ๋ฅผ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
// _update: Update -> ์ด์ ํ๋ ์ ์ดํ์ ์๊ฐ ๋ฑ ์
๋ฐ์ดํธ ๊ด๋ จ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๊ตฌ์กฐ์ฒด. ์ฌ๊ธฐ์๋ ์ฌ์ฉ๋์ง ์์ต๋๋ค.
fn update(_app: &App, model: &mut Model, _update: Update) {
// model์ points ๋ฒกํฐ์ ์๋ ๋ชจ๋ ์ ๋ค์ ์ํํ๋ฉฐ ๊ฐ๊ฐ์ ์ํ๋ฅผ ์
๋ฐ์ดํธํฉ๋๋ค.
// &mut model.points๋ ๋ฒกํฐ์ ๊ฐ ์์(p)์ ๋ํ ๊ฐ๋ณ ์ฐธ์กฐ๋ฅผ ์ป๊ธฐ ์ํจ์
๋๋ค.
for p in &mut model.points {
// ํ์ฌ ์ (p)์ y ์ขํ๋ฅผ speed ๋งํผ ๊ฐ์์์ผ ์๋๋ก ์ด๋์ํต๋๋ค.
p.pos.y -= p.speed;
}
// points ๋ฒกํฐ์์ ํน์ ์กฐ๊ฑด์ ๋ง์กฑํ์ง ์๋ ์์๋ค์ ์ ๊ฑฐํฉ๋๋ค.
// retain ๋ฉ์๋๋ ํด๋ก์ (closure)๋ฅผ ์ธ์๋ก ๋ฐ์, ํด๋ก์ ๊ฐ true๋ฅผ ๋ฐํํ๋ ์์๋ง ๋จ๊น๋๋ค.
// |p| p.pos.y > -(WIN_SIZE as f32) / 2.0 - 50.0
// -> ์ ์ y์ขํ๊ฐ ํ๋ฉด ํ๋จ(-400.0)๋ณด๋ค 50ํฝ์
๋ ์๋๋ก ๋ด๋ ค๊ฐ๋ฉด false๋ฅผ ๋ฐํํ์ฌ ์ ๊ฑฐํฉ๋๋ค.
// (WIN_SIZE as f32)๋ u32 ํ์
์ ์์๋ฅผ f32๋ก ํ๋ณํํฉ๋๋ค.
model.points.retain(|p| p.pos.y > -(WIN_SIZE as f32) / 2.0 - 50.0);
// ์ ์ด ํ๋ฉด ๋ฐ์ผ๋ก ๋๊ฐ ์ ๊ฑฐ๋ ํ, ์ ์ ๊ฐ์๊ฐ NUM_POINTS๋ณด๋ค ์ ์ผ๋ฉด ๋ค์ ์ฑ์ ๋ฃ์ต๋๋ค.
while model.points.len() < NUM_POINTS {
// random_point ํจ์๋ก ์๋ก์ด ์ ์ ์์ฑํ์ฌ points ๋ฒกํฐ์ ์ถ๊ฐํฉ๋๋ค.
model.points.push(random_point());
}
}
// ๋งค ํ๋ ์๋ง๋ค ํธ์ถ๋์ด ํ์ฌ ๋ชจ๋ธ ์ํ๋ฅผ ๋ฐํ์ผ๋ก ํ๋ฉด์ ๊ทธ๋ฆผ์ ๊ทธ๋ฆฌ๋ ํจ์์
๋๋ค.
// app: &App -> ํ์ฌ ์ฑ์ ๋ํ ์ฐธ์กฐ. Draw ๊ฐ์ฒด๋ฅผ ์ป๊ธฐ ์ํด ์ฌ์ฉ๋ฉ๋๋ค.
// model: &Model -> ํ์ฌ ์ฑ์ ์ํ๋ฅผ ๋ด๊ณ ์๋ Model์ ๋ํ '๋ถ๋ณ' ์ฐธ์กฐ. ๋ฐ์ดํฐ๋ฅผ ์ฝ๊ธฐ๋ง ํฉ๋๋ค.
// frame: Frame -> ํ์ฌ ๊ทธ๋ฆฌ๊ณ ์๋ ํ๋ ์์ ๋ํ ์ ๋ณด. ์ต์ข
๊ฒฐ๊ณผ๋ฌผ์ ์ฌ๊ธฐ์ ์ ๋ฌํฉ๋๋ค.
fn view(app: &App, model: &Model, frame: Frame) {
// ๊ทธ๋ฆฌ๊ธฐ๋ฅผ ์ํ Draw ๊ฐ์ฒด๋ฅผ ์ป์ต๋๋ค. ๋ชจ๋ ๊ทธ๋ฆฌ๊ธฐ ์์
์ ์ด ๊ฐ์ฒด๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋๋ค.
let draw = app.draw();
// ํ๋ ์์ ๋ฐฐ๊ฒฝ์ ๊ฒ์์(BLACK)์ผ๋ก ์น ํฉ๋๋ค.
draw.background().color(BLACK);
// ํ๋ฉด์ ์ ์ค์ ์ขํ(0.0, 0.0)๋ฅผ ๋ํ๋ด๋ Point2๋ฅผ ์์ฑํฉ๋๋ค.
let center = pt2(0.0, 0.0);
// ์ค์์ ๊ณ ์ ๋ ์์ ๊ทธ๋ฆฝ๋๋ค.
draw.ellipse()
// .xy(center): ์์ ์ค์ฌ ์์น๋ฅผ ์ค์ ํฉ๋๋ค.
.xy(center)
// .radius(12.0): ์์ ๋ฐ์ง๋ฆ์ ์ค์ ํฉ๋๋ค.
.radius(12.0)
// .color(WHITE): ์์ ์์์ ํฐ์์ผ๋ก ์ค์ ํฉ๋๋ค.
.color(WHITE);
// ๋ชจ๋ธ์ ๋ชจ๋ ์ ๋ค์ ์ํํ๋ฉฐ ํ๋ฉด์ ๊ทธ๋ฆฝ๋๋ค.
for p in &model.points {
// ํ์ฌ ์ (p)๊ณผ ์ค์(center) ์ฌ์ด์ ๊ฑฐ๋ฆฌ๋ฅผ ๊ณ์ฐํฉ๋๋ค.
let dist = p.pos.distance(center);
// ๋ง์ฝ ๊ฑฐ๋ฆฌ๊ฐ ๋ฏธ๋ฆฌ ์ ์๋ RANGE (150.0)๋ณด๋ค ์๋ค๋ฉด,
if dist < RANGE {
// ์ ์ ํฐ์ ์์ผ๋ก ๊ทธ๋ฆฝ๋๋ค.
draw.ellipse().xy(p.pos).radius(6.0).color(WHITE);
// ์ค์(center)์์ ํ์ฌ ์ (p)๊น์ง ์ด์ด์ง๋ ์ ์ ๊ทธ๋ฆฝ๋๋ค.
draw.line()
// .start(center): ์ ์ ์์์ ์ ์ค์ ํฉ๋๋ค.
.start(center)
// .end(p.pos): ์ ์ ๋์ ์ ์ค์ ํฉ๋๋ค.
.end(p.pos)
// .weight(1.0): ์ ์ ๋๊ป๋ฅผ ์ค์ ํฉ๋๋ค.
.weight(1.0)
// .color(WHITE): ์ ์ ์์์ ํฐ์์ผ๋ก ์ค์ ํฉ๋๋ค.
.color(WHITE);
} else {
// ๊ฑฐ๋ฆฌ๊ฐ RANGE๋ณด๋ค ํฌ๋ค๋ฉด, ์ ์ ํ์(GRAY) ์์ผ๋ก ๊ทธ๋ฆฝ๋๋ค.
draw.ellipse().xy(p.pos).radius(6.0).color(GRAY);
}
}
// ์ง๊ธ๊น์ง์ ๋ชจ๋ ๊ทธ๋ฆฌ๊ธฐ ๋ช
๋ น๋ค์ ์ค์ ํ๋ ์์ ์ ์ฉํ์ฌ ํ๋ฉด์ ํ์ํฉ๋๋ค.
draw.to_frame(app, &frame).unwrap();
}
// ์๋ก์ด MovingPoint๋ฅผ ๋ฌด์์ ์์น์ ์๋๋ก ์์ฑํ์ฌ ๋ฐํํ๋ ํฌํผ ํจ์์
๋๋ค.
// ๋ฐํ ๊ฐ: ์๋ก ์์ฑ๋ MovingPoint ์ธ์คํด์ค.
fn random_point() -> MovingPoint {
// x ์ขํ๋ฅผ ํ๋ฉด์ ๊ฐ๋ก ๋ฒ์ (-400.0 ~ 400.0) ๋ด์์ ๋ฌด์์๋ก ์ ํํฉ๋๋ค.
let x = random_range(-(WIN_SIZE as f32) / 2.0, (WIN_SIZE as f32) / 2.0);
// y ์ขํ๋ฅผ ํ๋ฉด ์๋จ(400.0)๋ณด๋ค 20ํฝ์
์๋ก ๊ณ ์ ํฉ๋๋ค. ์ ์ด ์์์๋ถํฐ ๋ํ๋๊ฒ ๋ฉ๋๋ค.
let y = (WIN_SIZE as f32) / 2.0 + 20.0;
// ์๋๋ฅผ 0.5์์ 2.0 ์ฌ์ด์ ๋ฌด์์ ๊ฐ์ผ๋ก ์ ํํฉ๋๋ค.
let speed = random_range(0.5, 2.0);
// ๊ณ์ฐ๋ ์์น(x, y)์ ์๋(speed)๋ฅผ ์ฌ์ฉํ์ฌ ์๋ก์ด MovingPoint ๊ตฌ์กฐ์ฒด ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ ๋ฐํํฉ๋๋ค.
// pt2(x, y)๋ x, y ์ขํ๋ก Point2 ํ์
์ ๋ง๋๋ ๋จ์ถ ํจ์์
๋๋ค.
MovingPoint {
pos: pt2(x, y),
speed,
}
}