:: BASIC_13_line_between_2_dots

BamgasiJM·2025년 9월 27일

Nannou <BASIC>

목록 보기
26/41
post-thumbnail

📝 Rust Code

use nannou::prelude::*;

const WIDTH: f32 = 800.0;
const HEIGHT: f32 = 800.0;

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

struct MovingPoint {
    pos: Vec2,
    vel: Vec2,
}

struct Model {
    p1: MovingPoint,
    p2: MovingPoint,
    width: f32,
    height: f32,
}

fn model(app: &App) -> Model {
    let _window_id = app
        .new_window()
        .size(WIDTH as u32, HEIGHT as u32)
        .title("Nannou Line Trail - HSLA")
        .view(view)
        .build()
        .unwrap();

    let p1 = MovingPoint {
        pos: vec2(-100.0, 0.0),
        vel: vec2(60.0, 25.0),
    };

    let p2 = MovingPoint {
        pos: vec2(120.0, 60.0),
        vel: vec2(-50.0, -80.0),
    };

    Model {
        p1,
        p2,
        width: WIDTH,
        height: HEIGHT,
    }
}

fn update(_app: &App, model: &mut Model, update: Update) {
    let dt = update.since_last.as_secs_f32();

    for mp in [&mut model.p1, &mut model.p2] {
        mp.pos += mp.vel * dt;

        let half_w = model.width / 2.0;
        let half_h = model.height / 2.0;

        if mp.pos.x > half_w {
            mp.pos.x = half_w;
            mp.vel.x *= -1.0;
        } else if mp.pos.x < -half_w {
            mp.pos.x = -half_w;
            mp.vel.x *= -1.0;
        }

        if mp.pos.y > half_h {
            mp.pos.y = half_h;
            mp.vel.y *= -1.0;
        } else if mp.pos.y < -half_h {
            mp.pos.y = -half_h;
            mp.vel.y *= -1.0;
        }
    }
}

fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();

    let overlay = hsla(0.0, 0.0, 0.0, 0.06);
    draw.rect().w_h(model.width, model.height).color(overlay);

    let white = hsla(0.0, 0.0, 1.0, 1.0);
    draw.line().start(model.p1.pos).end(model.p2.pos).stroke_weight(2.0).color(white);

    draw.to_frame(app, &frame).unwrap();
}

📝 Rust Code + Comment

// Nannou의 기본 기능을 사용하기 위한 모듈 임포트
use nannou::prelude::*;

// 창 크기를 상수로 정의 — 코드 재사용성과 유지보수 향상
const WIDTH: f32 = 800.0;
const HEIGHT: f32 = 800.0;

// 프로그램 진입점: Nannou 애플리케이션 설정 및 실행
fn main() {
    // 애플리케이션 빌더 생성:
    // - model: 초기 상태 설정 함수
    // - update: 매 프레임 상태 갱신 함수
    // - run(): 창 생성 및 렌더링 루프 시작
    nannou::app(model).update(update).run();
}

// 움직이는 점의 상태를 저장하는 구조체
struct MovingPoint {
    pos: Vec2, // 현재 위치 (x, y)
    vel: Vec2, // 속도 벡터 (x 방향 속도, y 방향 속도) — 단위: 픽셀/초
}

// 애플리케이션 전체 상태를 저장하는 구조체
struct Model {
    p1: MovingPoint, // 첫 번째 움직이는 점
    p2: MovingPoint, // 두 번째 움직이는 점
    width: f32,      // 창 너비 (상수 WIDTH 복사)
    height: f32,     // 창 높이 (상수 HEIGHT 복사)
}

// 애플리케이션 초기화 함수 — 창 생성 및 초기 상태 설정
fn model(app: &App) -> Model {
    // 새 창을 생성하고 설정:
    let _window_id = app
        .new_window()
        .size(WIDTH as u32, HEIGHT as u32)            // 크기를 상수로 지정 (u32로 변환 필요)
        .title("Nannou Line Trail - HSLA")            // 창 제목 설정
        .view(view)                                   // 렌더링 콜백으로 `view` 함수 지정
        .build()
        .unwrap(); // 오류 발생 시 프로그램 중단

    // 두 점의 초기 위치와 속도 설정:
    let p1 = MovingPoint {
        pos: vec2(-100.0, 0.0),   // 왼쪽 중앙 근처
        vel: vec2(60.0, 25.0),    // 오른쪽 위 방향으로 이동
    };

    let p2 = MovingPoint {
        pos: vec2(120.0, 60.0),   // 오른쪽 상단 근처
        vel: vec2(-50.0, -80.0),  // 왼쪽 아래 방향으로 이동
    };

    // 초기 Model 반환
    Model {
        p1,
        p2,
        width: WIDTH,
        height: HEIGHT,
    }
}

// 매 프레임 상태를 업데이트하는 함수 — 물리 시뮬레이션 수행
fn update(_app: &App, model: &mut Model, update: Update) {
    // 마지막 프레임 이후 경과 시간을 초 단위(f32)로 가져옴
    // → 프레임 속도에 관계없이 일관된 이동 속도 보장 (프레임 독립적 애니메이션)
    let dt = update.since_last.as_secs_f32();

    // 두 점(p1, p2)을 동시에 처리하기 위해 배열로 묶음
    for mp in [&mut model.p1, &mut model.p2] {
        // 위치 업데이트: pos = pos + vel * dt
        // → 속도(픽셀/초) × 시간(초) = 이동 거리(픽셀)
        mp.pos += mp.vel * dt;

        // 창의 절반 크기 계산 (Nannou 좌표계: 중심이 (0,0), x: -width/2 ~ +width/2)
        let half_w = model.width / 2.0;
        let half_h = model.height / 2.0;

        // X축 경계 충돌 감지 및 반사:
        if mp.pos.x > half_w {
            mp.pos.x = half_w;     // 경계를 넘지 않도록 위치 고정
            mp.vel.x *= -1.0;      // x 방향 속도 반전 → 튕기는 효과
        } else if mp.pos.x < -half_w {
            mp.pos.x = -half_w;
            mp.vel.x *= -1.0;
        }

        // Y축 경계 충돌 감지 및 반사:
        if mp.pos.y > half_h {
            mp.pos.y = half_h;
            mp.vel.y *= -1.0;
        } else if mp.pos.y < -half_h {
            mp.pos.y = -half_h;
            mp.vel.y *= -1.0;
        }
    }
}

// 매 프레임 화면을 그리는 함수
fn view(app: &App, model: &Model, frame: Frame) {
    let draw = app.draw();

    // 잔상(trail) 효과를 위한 반투명 검은색 오버레이:
    // - 매 프레임 이전 내용 위에 매우 투명한 검정색 사각형을 덮음
    // - 알파값(0.06)이 작을수록 잔상이 오래 남음
    let overlay = hsla(0.0, 0.0, 0.0, 0.06); // H=0, S=0, L=0 (검정), Alpha=0.06
    draw.rect()
        .w_h(model.width, model.height) // 창 전체 크기
        .color(overlay);

    // 두 점을 연결하는 선 그리기:
    let white = hsla(0.0, 0.0, 1.0, 1.0); // 순백색 (HSLA: 밝기=1.0, 완전 불투명)
    draw.line()
        .start(model.p1.pos)   // 선 시작점: p1 위치
        .end(model.p2.pos)     // 선 끝점: p2 위치
        .stroke_weight(2.0)    // 선 두께: 2.0 픽셀
        .color(white);

    // 모든 그리기 명령을 실제 프레임 버퍼에 적용
    draw.to_frame(app, &frame).unwrap();
}

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

0개의 댓글