

use nannou::prelude::*;
fn main() {
nannou::app(model).update(update).run();
}
struct Model {
time: f32,
}
fn model(app: &App) -> Model {
app.new_window()
.size(800, 600)
.view(view)
.build()
.unwrap();
Model { time: 0.0 }
}
fn update(_app: &App, model: &mut Model, _update: Update) {
model.time += 0.02;
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
draw.background().color(rgba(0.1, 0.1, 0.1, 1.0));
let points: Vec<Point2> = (-50..=50)
.map(|i| {
let x = i as f32 * 10.0;
let y = (x * 0.1 + model.time).sin() * 100.0;
pt2(x, y)
})
.collect();
draw.polyline()
.weight(3.0)
.color(rgba(0.0, 0.8, 1.0, 0.8))
.points(points.clone());
for (i, &point) in points.iter().enumerate() {
let progress = i as f32 / points.len() as f32;
let color = rgba(
progress,
0.5 + 0.5 * (model.time * 2.0).sin(),
1.0 - progress,
0.9,
);
draw.ellipse().xy(point).radius(5.0).color(color);
}
draw.line()
.start(pt2(-500.0, 0.0))
.end(pt2(500.0, 0.0))
.weight(1.0)
.color(rgba(1.0, 1.0, 1.0, 0.2));
draw.to_frame(app, &frame).unwrap();
}
// Nannou의 기본 기능을 사용하기 위한 모듈 임포트
use nannou::prelude::*;
// 프로그램 진입점: Nannou 앱을 설정하고 실행
fn main() {
nannou::app(model).update(update).run();
}
// 애플리케이션 상태를 저장하는 구조체
// `time`은 애니메이션의 진행 정도를 나타내는 누적 시간 변수
struct Model {
time: f32,
}
// 애플리케이션 초기화 함수
fn model(app: &App) -> Model {
// 새 창 생성 및 설정
app.new_window()
.size(800, 600) // 창 크기를 800x600 픽셀로 고정
.view(view) // 렌더링 콜백으로 `view` 함수 지정
.build()
.unwrap();
// 초기 상태 반환: 시간을 0.0부터 시작
Model { time: 0.0 }
}
// 매 프레임 상태를 업데이트하는 함수
fn update(_app: &App, model: &mut Model, _update: Update) {
// 매 프레임마다 시간을 0.02씩 증가시킴 → 애니메이션 속도 조절
// 값이 클수록 사인파가 더 빠르게 움직임
model.time += 0.02;
}
// 매 프레임 화면을 그리는 함수
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
// 배경을 짙은 회색으로 설정
draw.background().color(rgba(0.1, 0.1, 0.1, 1.0));
// 사인파를 구성할 점들의 좌표를 생성:
// x 범위: -500 ~ +500 (i: -50 ~ +50, 간격 10)
let points: Vec<Point2> = (-50..=50)
.map(|i| {
let x = i as f32 * 10.0; // x 좌표: -500 ~ +500
let y = (x * 0.1 + model.time).sin() * 100.0; // y = sin(위상) * 진폭
pt2(x, y) // (x, y) 점 생성
})
.collect();
// 사인파 곡선을 선으로 연결하여 그리기
draw.polyline()
.weight(3.0) // 선 두께: 3.0
.color(rgba(0.0, 0.8, 1.0, 0.8)) // 청록색, 약간 투명
.points(points.clone()); // 위에서 생성한 점들 사용
// 사인파 위의 각 점에 원(ellipse)을 그림 — 색상은 점 위치와 시간에 따라 변화
for (i, &point) in points.iter().enumerate() {
// 진행도: 첫 번째 점은 0.0, 마지막 점은 1.0
let progress = i as f32 / points.len() as f32;
// 색상을 동적으로 계산:
let color = rgba(
progress, // 빨강: 왼쪽→오른쪽으로 증가
0.5 + 0.5 * (model.time * 2.0).sin(), // 초록: 시간에 따라 -0.5~+0.5 진동 → 0~1 범위
1.0 - progress, // 파랑: 왼쪽→오른쪽으로 감소
0.9, // 알파: 거의 불투명
);
// 각 점 위치에 반지름 5.0인 원 그리기
draw.ellipse().xy(point).radius(5.0).color(color);
}
// 중앙 가이드 라인: y = 0인 수평선 (사인파의 기준선)
draw.line()
.start(pt2(-500.0, 0.0)) // 왼쪽 끝
.end(pt2(500.0, 0.0)) // 오른쪽 끝
.weight(1.0) // 얇은 선
.color(rgba(1.0, 1.0, 1.0, 0.2)); // 흰색, 매우 투명
// 모든 그리기 명령을 프레임에 적용
draw.to_frame(app, &frame).unwrap();
}
x 좌표 = cos(θ)y 좌표 = sin(θ)y = sin(x)는 x가 증가함에 따라 y가 -1에서 +1 사이를 주기적으로 진동하는 곡선입니다.let y = (x * 0.1 + model.time).sin() * 100.0;
x * 0.1: x 좌표를 압축 → 파형의 주기(한 주기 길이)를 조절+ model.time: 시간이 지남에 따라 전체 파형이 왼쪽으로 이동 (애니메이션 효과)* 100.0: 진폭(amplitude) → 파의 높이를 100픽셀로 확대💡 왜 왼쪽으로 움직일까?
일반적으로y = sin(x - vt)는 오른쪽 이동,y = sin(x + vt)는 왼쪽 이동입니다.
여기서model.time이 증가하면 위상이 증가하므로, 파는 왼쪽으로 흐르는 것처럼 보입니다.
cos(θ) = sin(θ + π/2) → 코사인파는 사인파보다 ¼ 주기(90도) 앞선 모양입니다.sin(θ + 2π) = sin(θ) → 2π(≈6.28)마다 반복-1 ≤ sin(θ) ≤ 1이러한 특성 덕분에 사인파는 프로그래밍 아트, 게임, 오디오 처리 등에서 매우 자주 사용됩니다.