

use nannou::prelude::*;
fn main() {
nannou::app(model).update(update).run();
}
struct Model {
circles: Vec<Circle>,
}
struct Circle {
pos: Point2,
radius: f32,
hue: f32,
}
fn model(app: &App) -> Model {
app.new_window()
.size(800, 800)
.view(view)
.build()
.unwrap();
Model { circles: Vec::new() }
}
fn update(app: &App, model: &mut Model, _update: Update) {
let pos = app.mouse.position();
let circle = Circle {
pos,
radius: 10.0,
hue: (app.elapsed_frames() as f32) % 360.0,
};
model.circles.push(circle);
for c in &mut model.circles {
c.radius *= 1.02;
}
model.circles.retain(|c| c.radius < 200.0);
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(BLACK);
for c in &model.circles {
let color = hsl(c.hue / 360.0, 0.7, 0.5);
draw.ellipse()
.xy(c.pos)
.radius(c.radius)
.color(color)
.stroke(BLACK)
.stroke_weight(1.0);
}
draw.to_frame(app, &frame).unwrap();
}
// Nannou의 기본 기능을 사용하기 위한 모듈 임포트
use nannou::prelude::*;
// 프로그램 진입점: Nannou 애플리케이션 설정 및 실행
fn main() {
nannou::app(model).update(update).run();
}
// 애플리케이션의 전체 상태를 저장하는 구조체
// 여러 개의 원(Circle)을 벡터로 관리
struct Model {
circles: Vec<Circle>,
}
// 각 원의 속성을 정의하는 구조체
struct Circle {
pos: Point2, // 원의 중심 좌표 (x, y)
radius: f32, // 원의 반지름
hue: f32, // 색상의 색조(Hue) — 0.0 ~ 360.0 범위 (도 단위)
}
// 애플리케이션 초기화 함수
fn model(app: &App) -> Model {
// 새 창 생성 및 설정
app.new_window()
.size(800, 800) // 정사각형 창 (800x800 픽셀)
.view(view) // 렌더링 콜백으로 `view` 함수 지정
.build()
.unwrap();
// 초기 상태: 빈 원 목록
Model { circles: Vec::new() }
}
// 매 프레임 상태를 업데이트하는 함수
fn update(app: &App, model: &mut Model, _update: Update) {
// 현재 마우스 커서 위치를 가져와 새 원 생성
let pos = app.mouse.position();
let circle = Circle {
pos,
radius: 10.0, // 초기 반지름은 10.0
// 색조(hue)를 프레임 수에 따라 변경 → 시간이 지남에 따라 색상이 순환
hue: (app.elapsed_frames() as f32) % 360.0,
};
// 새 원을 벡터 끝에 추가
model.circles.push(circle);
// 모든 원의 반지름을 2%씩 증가시켜 "확장" 효과 구현
for c in &mut model.circles {
c.radius *= 1.02; // 매 프레임 1.02배 → 부드러운 확대 애니메이션
}
// 반지름이 200.0 이상인 원은 화면에서 제거 (성능 및 시각적 과부하 방지)
// `retain`은 조건을 만족하는 요소만 남기고 나머지를 제거함
model.circles.retain(|c| c.radius < 200.0);
}
// 매 프레임 화면을 그리는 함수
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
// 배경을 매우 어두운 회색으로 설정 (HSLA 색상 사용)
// hsla(색조, 채도, 밝기, 투명도)
draw.background().color(hsla(0.0, 0.0, 0.01, 1.0));
// 저장된 모든 원을 순회하며 그리기
for c in &model.circles {
// HSL 색상 모델로 색상 생성:
// - hue: 0.0~1.0 범위로 정규화 (원래는 0~360도)
// - saturation(채도): 0.7 → 선명한 색상
// - lightness(밝기): 0.5 → 중간 밝기
let color = hsl(c.hue / 360.0, 0.7, 0.5);
// 타원(원) 그리기:
draw.ellipse()
.xy(c.pos) // 중심 위치
.radius(c.radius) // 반지름
.color(color) // 채우기 색상
.stroke(BLACK) // 테두리 색상: 검정
.stroke_weight(1.0); // 테두리 두께: 1.0
}
// 모든 그리기 명령을 프레임에 적용
draw.to_frame(app, &frame).unwrap();
}
app.elapsed_frames()u64 (부호 없는 64비트 정수)let hue = (app.elapsed_frames() as f32) % 360.0;
: 이 코드에서는 프레임 수를 색상의 색조(Hue) 값으로 사용합니다.
: % 360.0을 통해 0 ~ 360도 사이의 순환 값을 얻어, 시간이 지남에 따라 색상이 무지개처럼 순환하게 됩니다.
retain(|c| ...)Vec<T>의 메서드로, 주어진 조건을 만족하는 요소만 남기고 나머지를 제거합니다.
vec.retain(|item| 조건식);
: 조건식이 true이면 해당 요소 유지, false이면 제거
model.circles.retain(|c| c.radius < 200.0);
: 반지름이 200.0 미만인 원만 유지
: 반지름이 200.0 이상인 원은 화면에서 자동 삭제
💡
retain은 가변 참조(&mut Vec)에서만 사용 가능하며, 내부적으로 효율적으로 요소를 필터링합니다.
일반적인filter().collect()와 달리 기존 벡터를 직접 수정하므로 메모리 재할당이 적습니다.