



use nannou::prelude::*;
use std::path::Path;
const POINT_COLOR: (u8, u8, u8) = (255, 255, 255);
const LINE_COLOR: (u8, u8, u8) = (255, 255, 255);
const LINE_ALPHA: u8 = 16;
const POINT_SPEED: f32 = 0.3;
struct Point {
position: Vec2,
velocity: Vec2,
color: Srgb<u8>,
}
struct Model {
points: Vec<Point>,
counter: u32,
}
fn main() {
nannou::app(model)
.update(update)
.event(event)
.view(view)
.run();
}
fn model(app: &App) -> Model {
app.new_window().size(1000, 1000).view(view).build().unwrap();
let num_points = 3000;
let mut points = Vec::new();
for _ in 0..num_points {
let position = pt2(
random_range(-500.0, 500.0),
random_range(-500.0, 500.0),
);
let velocity = pt2(
random_range(-POINT_SPEED, POINT_SPEED),
random_range(-POINT_SPEED, POINT_SPEED),
);
points.push(Point {
position,
velocity,
color: Srgb::new(POINT_COLOR.0, POINT_COLOR.1, POINT_COLOR.2),
});
}
Model { points, counter: 1 }
}
fn update(_app: &App, model: &mut Model, _update: Update) {
for point in &mut model.points {
point.position += point.velocity;
if point.position.x > 500.0 || point.position.x < -500.0 {
point.velocity.x *= -1.0;
}
if point.position.y > 500.0 || point.position.y < -500.0 {
point.velocity.y *= -1.0;
}
}
}
fn event(app: &App, model: &mut Model, event: Event) {
if let Event::WindowEvent { simple: Some(WindowEvent::KeyPressed(key)), .. } = event {
match key {
Key::P => save_frame(app, model, "png"),
Key::J => save_frame(app, model, "jpg"),
_ => {}
}
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().rgb(0.0, 0.0, 0.0);
for point in &model.points {
draw.ellipse()
.x_y(point.position.x, point.position.y)
.radius(1.0)
.color(point.color);
}
for i in 0..model.points.len() {
for j in i + 1..model.points.len() {
let p1 = &model.points[i];
let p2 = &model.points[j];
let distance = p1.position.distance(p2.position);
if distance < 40.0 {
draw.line()
.start(p1.position)
.end(p2.position)
.weight(0.4)
.color(Srgba::new(
LINE_COLOR.0,
LINE_COLOR.1,
LINE_COLOR.2,
LINE_ALPHA,
));
}
}
}
draw.to_frame(app, &frame).unwrap();
}
fn save_frame(app: &App, model: &mut Model, format: &str) {
let filename = format!("output_{}.{}", model.counter, format);
let path = Path::new(&filename);
app.main_window().capture_frame(path);
println!("Frame saved as {}", filename);
model.counter += 1;
}
// Nannou์ ๊ธฐ๋ณธ ๊ธฐ๋ฅ๊ณผ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ฒฝ๋ก ์ฒ๋ฆฌ ๊ธฐ๋ฅ์ ์ํฌํธ
use nannou::prelude::*;
use std::path::Path;
// ===== ์ ์ญ ์์ ์ ์ (์ฝ๋ ์ ์ฒด์์ ์ฌ์ฉ๋๋ ๊ณ ์ ๊ฐ) =====
// RGB ์์์ 0~255 ๋ฒ์์ u8 ํ์
์ผ๋ก ์ ์
const POINT_COLOR: (u8, u8, u8) = (255, 255, 255); // ์ ์์: ํฐ์ (R=255, G=255, B=255)
const LINE_COLOR: (u8, u8, u8) = (255, 255, 255); // ์ ์์: ํฐ์ (๋์ค์ ํฌ๋ช
๋ ์กฐ์ )
const LINE_ALPHA: u8 = 16; // ์ ์ ํฌ๋ช
๋ (0=์์ ํฌ๋ช
, 255=์์ ๋ถํฌ๋ช
)
// 16์ ์ฝ 6% ํฌ๋ช
๋ โ ๋งค์ฐ ํ๋ฆฟํ ์
const POINT_SPEED: f32 = 0.3; // ์ ์ ์ต๋ ์ด๋ ์๋ (ํฝ์
/ํ๋ ์ ๋จ์)
// =================================
// ๊ฐ๋ณ ์ ์ ์ํ๋ฅผ ์ ์ฅํ๋ ๊ตฌ์กฐ์ฒด
struct Point {
position: Vec2, // ํ์ฌ ์์น (x, y)
velocity: Vec2, // ์๋ ๋ฒกํฐ (x, y ๋ฐฉํฅ ์ด๋๋)
color: Srgb<u8>, // ์์ โ Srgb<u8>๋ 0~255 ๋ฒ์์ RGB ์์ ํ์
}
// ์ ํ๋ฆฌ์ผ์ด์
์ ์ฒด ์ํ๋ฅผ ์ ์ฅํ๋ ๊ตฌ์กฐ์ฒด
struct Model {
points: Vec<Point>, // ์์ฒ ๊ฐ์ ์ ์ ์ ์ฅํ๋ ๋ฒกํฐ
counter: u32, // ํ๋ ์ ์ ์ฅ ์ ์ฌ์ฉํ ์ผ๋ จ๋ฒํธ (1, 2, 3, ...)
}
// ํ๋ก๊ทธ๋จ ์ง์
์ : Nannou ์ ํ๋ฆฌ์ผ์ด์
์ค์
fn main() {
nannou::app(model)
.update(update) // ๋งค ํ๋ ์ ์ํ ๊ฐฑ์
.event(event) // ํค๋ณด๋ ์ด๋ฒคํธ ์ฒ๋ฆฌ
.view(view) // ํ๋ฉด ๋ ๋๋ง
.run(); // ์คํ ์์
}
// ์ ํ๋ฆฌ์ผ์ด์
์ด๊ธฐํ ํจ์
fn model(app: &App) -> Model {
// 1000x1000 ํฝ์
์ ์ ์ฌ๊ฐํ ์ฐฝ ์์ฑ
app.new_window().size(1000, 1000).view(view).build().unwrap();
// 3000๊ฐ์ ์ ์ ๋๋คํ๊ฒ ์์ฑ
let num_points = 3000;
let mut points = Vec::new();
for _ in 0..num_points {
// ์์น: ํ๋ฉด ์ ์ฒด ๋ฒ์ (-500 ~ +500, -500 ~ +500)
let position = pt2(
random_range(-500.0, 500.0),
random_range(-500.0, 500.0),
);
// ์๋: -0.3 ~ +0.3 ๋ฒ์์ ๋งค์ฐ ๋๋ฆฐ ์๋
let velocity = pt2(
random_range(-POINT_SPEED, POINT_SPEED),
random_range(-POINT_SPEED, POINT_SPEED),
);
// ์ ์์ฑ ๋ฐ ๋ฒกํฐ์ ์ถ๊ฐ
points.push(Point {
position,
velocity,
// ์ ์ญ ์์ POINT_COLOR๋ฅผ Srgb<u8> ํ์
์ผ๋ก ๋ณํ
color: Srgb::new(POINT_COLOR.0, POINT_COLOR.1, POINT_COLOR.2),
});
}
// ์ด๊ธฐ Model ๋ฐํ โ counter๋ 1๋ถํฐ ์์ (ํ์ผ๋ช
์ ์ฌ์ฉ)
Model { points, counter: 1 }
}
// ๋งค ํ๋ ์ ์ํ๋ฅผ ์
๋ฐ์ดํธํ๋ ํจ์
fn update(_app: &App, model: &mut Model, _update: Update) {
// ๋ชจ๋ ์ ์ ์ํํ๋ฉฐ ์์น ๊ฐฑ์
for point in &mut model.points {
// ์์น = ์์น + ์๋ (ํ๋ ์ ๋
๋ฆฝ์ ์ด๋์ ์๋ โ ๋งค์ฐ ๋๋ ค์ ๋ฌด๊ด)
point.position += point.velocity;
// ํ๋ฉด ๊ฒฝ๊ณ(-500 ~ +500)์์ ํ๊ธฐ๋ ๋ก์ง:
if point.position.x > 500.0 || point.position.x < -500.0 {
point.velocity.x *= -1.0; // x ๋ฐฉํฅ ์๋ ๋ฐ์
}
if point.position.y > 500.0 || point.position.y < -500.0 {
point.velocity.y *= -1.0; // y ๋ฐฉํฅ ์๋ ๋ฐ์
}
}
}
// ํค๋ณด๋ ์ด๋ฒคํธ ์ฒ๋ฆฌ ํจ์
fn event(app: &App, model: &mut Model, event: Event) {
// ํค๋ณด๋ ํค๊ฐ ๋๋ ธ๋์ง ํ์ธ
if let Event::WindowEvent { simple: Some(WindowEvent::KeyPressed(key)), .. } = event {
match key {
Key::P => save_frame(app, model, "png"), // 'P' ํค: PNG๋ก ์ ์ฅ
Key::J => save_frame(app, model, "jpg"), // 'J' ํค: JPG๋ก ์ ์ฅ
_ => {} // ๋ค๋ฅธ ํค๋ ๋ฌด์
}
}
}
// ๋งค ํ๋ ์ ํ๋ฉด์ ๊ทธ๋ฆฌ๋ ํจ์
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
// ๋ฐฐ๊ฒฝ์ ๊ฒ์์์ผ๋ก ์ค์ (rgb(0.0, 0.0, 0.0) = ๊ฒ์ )
draw.background().rgb(0.0, 0.0, 0.0);
// ๋ชจ๋ ์ ์ ์์ ์(๋ฐ์ง๋ฆ 1.0)์ผ๋ก ๊ทธ๋ฆฌ๊ธฐ
for point in &model.points {
draw.ellipse()
.x_y(point.position.x, point.position.y) // ์ค์ฌ ์ขํ
.radius(1.0) // ๋งค์ฐ ์์ ์
.color(point.color); // ํฐ์
}
// ์ ๋ค ์ฌ์ด์ ๊ฑฐ๋ฆฌ๊ฐ ๊ฐ๊น์ฐ๋ฉด ์ ์ผ๋ก ์ฐ๊ฒฐ โ "์ฐ๊ฒฐ๋ ์ " ํจ๊ณผ
// โ ๏ธ ์ด์ค ๋ฃจํ์ด๋ฏ๋ก O(nยฒ) ๋ณต์ก๋ โ 3000๊ฐ ์ ์ด๋ฉด ์ฝ 450๋ง ํ ๋น๊ต!
// ์ฑ๋ฅ์ ๋ฏผ๊ฐํ ํ๊ฒฝ์์๋ ์ต์ ํ ํ์ (์: ๊ณต๊ฐ ๋ถํ )
for i in 0..model.points.len() {
for j in i + 1..model.points.len() { // ์ค๋ณต ์ฐ๊ฒฐ ๋ฐฉ์ง (i-j์ j-i๋ ๋์ผ)
let p1 = &model.points[i];
let p2 = &model.points[j];
let distance = p1.position.distance(p2.position); // ๋ ์ ์ฌ์ด ๊ฑฐ๋ฆฌ ๊ณ์ฐ
// ๊ฑฐ๋ฆฌ๊ฐ 40.0 ํฝ์
๋ฏธ๋ง์ด๋ฉด ์ ์ฐ๊ฒฐ
if distance < 40.0 {
draw.line()
.start(p1.position) // ์ ์์์
.end(p2.position) // ์ ๋์
.weight(0.4) // ๋งค์ฐ ์์ ์
.color(Srgba::new( // Srgba๋ ์ํ(ํฌ๋ช
๋)๋ฅผ ํฌํจํ ์์
LINE_COLOR.0,
LINE_COLOR.1,
LINE_COLOR.2,
LINE_ALPHA, // 0~255 ๋ฒ์์ ํฌ๋ช
๋
));
}
}
}
// ๋ชจ๋ ๊ทธ๋ฆฌ๊ธฐ ๋ช
๋ น์ ํ๋ ์์ ์ ์ฉ
draw.to_frame(app, &frame).unwrap();
}
// ํ์ฌ ํ๋ ์์ ์ด๋ฏธ์ง ํ์ผ๋ก ์ ์ฅํ๋ ํจ์
fn save_frame(app: &App, model: &mut Model, format: &str) {
// ํ์ผ๋ช
: output_1.png, output_2.jpg, ...
let filename = format!("output_{}.{}", model.counter, format);
let path = Path::new(&filename);
// Nannou์ ์ฐฝ ์บก์ฒ ๊ธฐ๋ฅ์ผ๋ก ํ์ฌ ํ๋ ์์ ์ด๋ฏธ์ง๋ก ์ ์ฅ
app.main_window().capture_frame(path);
// ์ฝ์์ ์ ์ฅ ์๋ฃ ๋ฉ์์ง ์ถ๋ ฅ
println!("Frame saved as {}", filename);
// ๋ค์ ์ ์ฅ์ ์ํด ์นด์ดํฐ ์ฆ๊ฐ
model.counter += 1;
}