::BASIC_31_undulated_circle_animation

BamgasiJM·2025년 12월 17일

Nannou <BASIC>

목록 보기
40/41
post-thumbnail

📝 Rust Code

use nannou::prelude::*;
use nannou::noise::{NoiseFn, Perlin, Seedable};

// 모델: 상태를 저장
struct Model {
    smooth_points: Vec<Vec2>, // 현재 프레임에서 그려질 점들
    noise: Perlin,            // 노이즈 인스턴스 재사용
}

fn main() {
    nannou::app(model).update(update).run();
}

// 초기 설정
fn model(app: &App) -> Model {
    app.new_window()
        .size(1080, 1080)
        .view(view)
        .title("Organic Noise Blob")
        .build()
        .unwrap();

    // 고정 시드 노이즈 생성 (매 실행마다 같은 패턴 방지하려면 seed 변경)
    let noise = Perlin::new().set_seed(random());

    Model {
        smooth_points: Vec::new(),
        noise,
    }
}

// 데이터 업데이트 (매 프레임 호출)
fn update(app: &App, model: &mut Model, _update: Update) {
    let num_points = 120; // 원본 포인트 개수
    let time = app.time * 0.5; // 시간 흐름 속도 조절

    let mut raw_points = Vec::with_capacity(num_points);

    for i in 0..num_points {
        let theta = i as f32 * TAU / num_points as f32;
        
        // 💡 노이즈 공간을 3D/4D로 확장하여 시간 축 추가
        // x: cos, y: sin, z: time -> 원형 궤적을 따라 노이즈 샘플링하며 시간 변화
        let noise_val = model.noise.get([
            (theta.cos() as f64) + 1.0, // +1.0은 음수 좌표 방지용 (선택 사항)
            (theta.sin() as f64) + 1.0, 
            time as f64
        ]) as f32;

        // 반지름 계산: 기본 300 + 노이즈 변동
        let r = 300.0 + noise_val * 150.0;
        raw_points.push(pt2(theta.cos() * r, theta.sin() * r));
    }

    // 스무스 보간 적용 (세그먼트 수를 낮추면 성능 향상, 높이면 부드러움)
    model.smooth_points = catmull_rom_spline(&raw_points, 4);
}

// 화면 그리기
fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();
    
    // 잔상 효과를 위한 투명도 있는 배경 (선택 사항, 현재는 완전 덮어쓰기)
    draw.background().color(BLACK);

    // 🔹 최적화: clone() 없이 슬라이스로 전달하거나 참조 사용
    // nannou의 points_closed는 IntoIterator를 받으므로 참조를 넘길 수 있으면 좋으나,
    // Vec<Vec2> 자체를 넘기면 소유권 문제가 발생하므로, 여기서는 `cloned()` 이터레이터를 사용
    // (전체 벡터 복사보다 가벼움)
    draw.polyline()
        .weight(5.0) 
        .color(WHITE)
        .points_closed(model.smooth_points.iter().cloned()); 
        
    draw.to_frame(app, &frame).unwrap();
}

// 🧮 Catmull-Rom Spline 알고리즘 구현
// 수학적 원리: P(t) = 0.5 * (2P1 + (-P0 + P2)t + (2P0 - 5P1 + 4P2 - P3)t^2 + (-P0 + 3P1 - 3P2 + P3)t^3)
fn catmull_rom_spline(points: &[Vec2], segments: usize) -> Vec<Vec2> {
    // 결과 벡터 미리 할당 (성능 최적화)
    let n = points.len();
    let mut smoothed = Vec::with_capacity(n * segments);

    for i in 0..n {
        // 닫힌 도형을 위한 인덱스 래핑 (Wrapping)
        let p0 = points[(i + n - 1) % n];
        let p1 = points[i];
        let p2 = points[(i + 1) % n];
        let p3 = points[(i + 2) % n];

        for j in 0..segments {
            let t = j as f32 / segments as f32;
            let t2 = t * t;
            let t3 = t2 * t;

            // 보간 공식 적용
            let x = 0.5
                * ((2.0 * p1.x)
                    + (-p0.x + p2.x) * t
                    + (2.0 * p0.x - 5.0 * p1.x + 4.0 * p2.x - p3.x) * t2
                    + (-p0.x + 3.0 * p1.x - 3.0 * p2.x + p3.x) * t3);
            let y = 0.5
                * ((2.0 * p1.y)
                    + (-p0.y + p2.y) * t
                    + (2.0 * p0.y - 5.0 * p1.y + 4.0 * p2.y - p3.y) * t2
                    + (-p0.y + 3.0 * p1.y - 3.0 * p2.y + p3.y) * t3);
            
            smoothed.push(pt2(x, y));
        }
    }

    smoothed
}

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0개의 댓글