๐Ÿ”ฎ :: Shrinking Iris

BamgasiJMยท2025๋…„ 9์›” 30์ผ

Nannou <Generative Art>

๋ชฉ๋ก ๋ณด๊ธฐ
36/55
post-thumbnail

๐Ÿ“ Rust Code

use nannou::prelude::*;

const NUM_PARTICLES: usize = 8_000;
const MIN_DIST: f32 = 300.0;
const MAX_DIST: f32 = 600.0;
const PARTICLE_RADIUS: f32 = 1.0;
const OBSTACLE_RADIUS: f32 = 100.0;
const FORCE_MAG: f32 = 0.08;
const FRICTION: f32 = 0.99;
const ELASTICITY: f32 = 2.5;
const ENERGY_LOSS: f32 = 0.9;
const MAX_COLLISION: u32 = 15;
const STOP_VEL: f32 = 0.1;
const WINDOW_SIZE: f32 = 800.0;

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

struct Particle {
    position: Vec2,
    velocity: Vec2,
    collision_count: u32,
    is_stopped: bool,
}

struct Model {
    particles: Vec<Particle>,
}

fn model(app: &App) -> Model {
    app.new_window()
        .size(WINDOW_SIZE as u32, WINDOW_SIZE as u32)
        .view(view)
        .build()
        .unwrap();

    let mut particles = Vec::with_capacity(NUM_PARTICLES);
    for _ in 0..NUM_PARTICLES {
        let angle = random_range(0.0, TAU);
        let dist = random_range(MIN_DIST, MAX_DIST);
        let position = vec2(angle.cos(), angle.sin()) * dist;
        particles.push(Particle {
            position,
            velocity: Vec2::ZERO,
            collision_count: 0,
            is_stopped: false,
        });
    }
    Model { particles }
}

fn update(_app: &App, model: &mut Model, _update: Update) {
    let obstacle_pos = vec2(0.0, 0.0);

    for p in model.particles.iter_mut() {
        if p.is_stopped {
            continue;
        }
        let dir = (obstacle_pos - p.position).normalize_or_zero();
        p.velocity += dir * FORCE_MAG;
        p.velocity *= FRICTION;
        p.position += p.velocity;

        // ์ถฉ๋Œ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ ๋ถ„๋ฆฌํ•˜๋ฉด ๋” ๊น”๋”
        handle_collision(p, obstacle_pos);
    }
}

fn handle_collision(p: &mut Particle, obstacle_pos: Vec2) {
    let to_particle = p.position - obstacle_pos;
    let dist = to_particle.length();
    let min_dist = OBSTACLE_RADIUS + PARTICLE_RADIUS;

    if dist < min_dist {
        p.collision_count += 1;
        let normal = to_particle.normalize_or_zero();
        p.position = obstacle_pos + normal * min_dist;
        let v_dot_n = p.velocity.dot(normal);
        p.velocity -= ELASTICITY * v_dot_n * normal;
        p.velocity *= ENERGY_LOSS;
        if p.collision_count >= MAX_COLLISION || p.velocity.length() < STOP_VEL {
            p.is_stopped = true;
            p.velocity = Vec2::ZERO;
        }
    }
}

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

    let frame_no = frame.nth();
    if frame_no == 0 {
        draw.background().color(hsla(0.0, 0.0, 0.0, 1.0));
    } else {
        draw.rect()
        .w_h(WINDOW_SIZE, WINDOW_SIZE)
        .color(hsla(0.0, 0.0, 0.0, 0.1));
    }    

    draw.ellipse()
        .xy(pt2(0.0, 0.0))
        .radius(OBSTACLE_RADIUS)
        .color(hsla(0.0, 0.0, 0.0, 0.1));

    for p in &model.particles {
        draw.ellipse()
            .xy(p.position)
            .radius(PARTICLE_RADIUS)
            .color(rgba(0.9, 0.9, 0.9, 0.5));
    }

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

๐Ÿ“ Rust Code + Comment

// =============================================================================
// 0. ์ „์—ญ ์ƒ์ˆ˜ ์ •์˜ (Global Simulation Parameters)
// =============================================================================
// ์ž…์ž ์‹œ์Šคํ…œ์˜ ๋™์ž‘์„ ์ œ์–ดํ•˜๋Š” ์กฐ์ • ๊ฐ€๋Šฅํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค.
// ์ด ๊ฐ’์„ ์ˆ˜์ •ํ•˜๋ฉด ์‹œ๋ฎฌ๋ ˆ์ด์…˜์˜ ์†๋„, ๋ฐ€๋„, ์ถฉ๋Œ ๋ฐ˜์‘ ๋“ฑ์„ ์‰ฝ๊ฒŒ ์‹คํ—˜ํ•  ์ˆ˜ ์žˆ์Œ.

/// ์ด ์ž…์ž ์ˆ˜: ๋งŽ์„์ˆ˜๋ก ์‹œ๊ฐ์ ์œผ๋กœ ํ’๋ถ€ํ•˜์ง€๋งŒ ์„ฑ๋Šฅ ๋ถ€ํ•˜ ์ฆ๊ฐ€
const NUM_PARTICLES: usize = 8_000;

/// ์ž…์ž ์ดˆ๊ธฐ ๋ฐฐ์น˜ ์ตœ์†Œ ๊ฑฐ๋ฆฌ (์ค‘์‹ฌ์  ๊ธฐ์ค€, ํ”ฝ์…€ ๋‹จ์œ„)
/// ๋„ˆ๋ฌด ๊ฐ€๊นŒ์šฐ๋ฉด ์‹œ์ž‘ ์‹œ ๊ณผ๋„ํ•œ ์ถฉ๋Œ ๋ฐœ์ƒ
const MIN_DIST: f32 = 300.0;

/// ์ž…์ž ์ดˆ๊ธฐ ๋ฐฐ์น˜ ์ตœ๋Œ€ ๊ฑฐ๋ฆฌ (์ค‘์‹ฌ์  ๊ธฐ์ค€, ํ”ฝ์…€ ๋‹จ์œ„)
const MAX_DIST: f32 = 600.0;

/// ๊ฐœ๋ณ„ ์ž…์ž์˜ ์‹œ๊ฐ์  ๋ฐ˜์ง€๋ฆ„ (๋ Œ๋”๋ง ํฌ๊ธฐ, ํ”ฝ์…€ ๋‹จ์œ„)
const PARTICLE_RADIUS: f32 = 1.0;

/// ์ค‘์•™ ์žฅ์• ๋ฌผ(์›)์˜ ๋ฐ˜์ง€๋ฆ„ (ํ”ฝ์…€ ๋‹จ์œ„)
const OBSTACLE_RADIUS: f32 = 100.0;

/// ์ค‘์•™ ์žฅ์• ๋ฌผ์ด ์ž…์ž์— ๊ฐ€ํ•˜๋Š” ์ธ๋ ฅ์˜ ์„ธ๊ธฐ
/// ๊ฐ’์ด ํด์ˆ˜๋ก ์ž…์ž๊ฐ€ ๋” ๋น ๋ฅด๊ฒŒ ์ค‘์‹ฌ์œผ๋กœ ๋Œ๋ฆผ
const FORCE_MAG: f32 = 0.08;

/// ๊ฐ์† ๋งˆ์ฐฐ ๊ณ„์ˆ˜ (0.0 ~ 1.0)
/// 1.0 = ๋งˆ์ฐฐ ์—†์Œ, 0.9 = ๋งค ํ”„๋ ˆ์ž„ 10% ๊ฐ์†
const FRICTION: f32 = 0.99;

/// ์ถฉ๋Œ ํƒ„์„ฑ ๊ณ„์ˆ˜ (๋ฐ˜๋ฐœ ๊ฐ•๋„)
/// 1.0 = ์™„์ „ ํƒ„์„ฑ ์ถฉ๋Œ, >1.0 = ์—๋„ˆ์ง€ ์ฆํญ (๊ณผ๋„ํ•œ ํŠ•๊น€), <1.0 = ํก์ˆ˜
const ELASTICITY: f32 = 2.5;

/// ์ถฉ๋Œ ํ›„ ์—๋„ˆ์ง€ ์†์‹ค ๋น„์œจ (0.0 ~ 1.0)
/// 1.0 = ์—๋„ˆ์ง€ ๋ณด์กด, 0.9 = 10% ์—๋„ˆ์ง€ ์†์‹ค
const ENERGY_LOSS: f32 = 0.9;

/// ์ž…์ž๊ฐ€ ๋ฉˆ์ถ”๊ธฐ ์ „๊นŒ์ง€ ํ—ˆ์šฉ๋˜๋Š” ์ตœ๋Œ€ ์ถฉ๋Œ ํšŸ์ˆ˜
/// ์ด ํšŸ์ˆ˜๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ์ž…์ž๋Š” ์ •์ง€๋จ (์„ฑ๋Šฅ/์‹œ๊ฐ์  ์•ˆ์ •ํ™”)
const MAX_COLLISION: u32 = 15;

/// ์ž…์ž๊ฐ€ "์ •์ง€ํ–ˆ๋‹ค"๊ณ  ๊ฐ„์ฃผ๋˜๋Š” ์†๋„ ์ž„๊ณ„๊ฐ’ (ํ”ฝ์…€/ํ”„๋ ˆ์ž„)
/// ์ด๋ณด๋‹ค ๋А๋ฆฌ๋ฉด ์›€์ง์ž„์ด ๋ˆˆ์— ๋„์ง€ ์•Š์œผ๋ฏ€๋กœ ์ •์ง€ ์ฒ˜๋ฆฌ
const STOP_VEL: f32 = 0.1;

/// ์œˆ๋„์šฐ ํฌ๊ธฐ (๋„ˆ๋น„ = ๋†’์ด = ์ •์‚ฌ๊ฐํ˜•)
const WINDOW_SIZE: f32 = 800.0;

// =============================================================================
// 1. ๋ฉ”์ธ ํ•จ์ˆ˜ (Application Entry Point)
// =============================================================================
// nannou ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘์ .
// `model`, `update`, `view` ์ฝœ๋ฐฑ์„ ๋“ฑ๋กํ•˜๊ณ  ์‹คํ–‰.

use nannou::prelude::*;

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

// =============================================================================
// 2. ์ž…์ž ๊ตฌ์กฐ์ฒด ์ •์˜ (Particle State)
// =============================================================================
// ๊ฐ ์ž…์ž์˜ ๋ฌผ๋ฆฌ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ.

struct Particle {
    /// ํ˜„์žฌ ์œ„์น˜ (์œˆ๋„์šฐ ์ค‘์‹ฌ์ด (0,0)์ธ ์›”๋“œ ์ขŒํ‘œ๊ณ„)
    position: Vec2,

    /// ํ˜„์žฌ ์†๋„ (ํ”„๋ ˆ์ž„๋‹น ์ด๋™ ๋ฒกํ„ฐ)
    velocity: Vec2,

    /// ์ค‘์•™ ์žฅ์• ๋ฌผ๊ณผ์˜ ์ถฉ๋Œ ํšŸ์ˆ˜
    /// ์ผ์ • ํšŸ์ˆ˜ ์ด์ƒ ์ถฉ๋Œ ์‹œ ์ž…์ž๋ฅผ ์ •์ง€์‹œ์ผœ ์„ฑ๋Šฅ/์‹œ๊ฐ์  ์•ˆ์ •ํ™”
    collision_count: u32,

    /// ์ž…์ž๊ฐ€ ๋” ์ด์ƒ ์›€์ง์ด์ง€ ์•Š๋Š”์ง€ ์—ฌ๋ถ€
    /// true์ด๋ฉด ์—…๋ฐ์ดํŠธ ๋ฐ ๋ Œ๋”๋ง ์ตœ์ ํ™” ๊ฐ€๋Šฅ
    is_stopped: bool,
}

// =============================================================================
// 3. ๋ชจ๋ธ ์ •์˜ (Simulation State)
// =============================================================================
// ์ „์ฒด ์‹œ๋ฎฌ๋ ˆ์ด์…˜์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ์ฒด.
// ๋ชจ๋“  ์ž…์ž์™€ ์žฅ์• ๋ฌผ ์ •๋ณด๋ฅผ ํฌํ•จ.

struct Model {
    /// ๋ชจ๋“  ์ž…์ž ์ธ์Šคํ„ด์Šค๋ฅผ ์ €์žฅํ•˜๋Š” ๋ฒกํ„ฐ
    particles: Vec<Particle>,
}

// =============================================================================
// 4. ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜ (Setup Simulation)
// =============================================================================
// ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœ๋จ.
// ์œˆ๋„์šฐ ์ƒ์„ฑ, ์ž…์ž ์ดˆ๊ธฐ ๋ฐฐ์น˜ ๋“ฑ์„ ์ˆ˜ํ–‰.

fn model(app: &App) -> Model {
    // ์œˆ๋„์šฐ ์ƒ์„ฑ: ํฌ๊ธฐ ์„ค์ • ๋ฐ ๋ทฐ ์ฝœ๋ฐฑ ๋“ฑ๋ก
    app.new_window()
        .size(WINDOW_SIZE as u32, WINDOW_SIZE as u32)
        .view(view)
        .build()
        .unwrap();

    // ์ž…์ž ๋ฒกํ„ฐ ์‚ฌ์ „ ํ• ๋‹น (์„ฑ๋Šฅ ์ตœ์ ํ™”)
    let mut particles = Vec::with_capacity(NUM_PARTICLES);

    // ๊ฐ ์ž…์ž๋ฅผ ์›ํ˜•์œผ๋กœ ๋ฌด์ž‘์œ„ ๋ฐฐ์น˜
    for _ in 0..NUM_PARTICLES {
        // 0 ~ 2ฯ€ ์‚ฌ์ด์˜ ๋ฌด์ž‘์œ„ ๊ฐ๋„ (TAU = 2ฯ€)
        let angle = random_range(0.0, TAU);
        // MIN_DIST ~ MAX_DIST ์‚ฌ์ด์˜ ๋ฌด์ž‘์œ„ ๊ฑฐ๋ฆฌ
        let dist = random_range(MIN_DIST, MAX_DIST);
        // ๊ทน์ขŒํ‘œ โ†’ ์ง๊ต์ขŒํ‘œ ๋ณ€ํ™˜: (r*cosฮธ, r*sinฮธ)
        let position = vec2(angle.cos(), angle.sin()) * dist;

        // ์ดˆ๊ธฐ ์†๋„๋Š” 0, ์ถฉ๋Œ ํšŸ์ˆ˜ 0, ์ •์ง€ ์ƒํƒœ ์•„๋‹˜
        particles.push(Particle {
            position,
            velocity: Vec2::ZERO,
            collision_count: 0,
            is_stopped: false,
        });
    }

    Model { particles }
}

// =============================================================================
// 5. ์—…๋ฐ์ดํŠธ ํ•จ์ˆ˜ (Per-Frame Physics Simulation)
// =============================================================================
// ๋งค ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์ž…์ž์˜ ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์ˆ˜ํ–‰.
// ์ธ๋ ฅ ์ ์šฉ โ†’ ์†๋„/์œ„์น˜ ์—…๋ฐ์ดํŠธ โ†’ ์ถฉ๋Œ ์ฒ˜๋ฆฌ ์ˆœ์„œ๋กœ ์ง„ํ–‰.

fn update(_app: &App, model: &mut Model, _update: Update) {
    // ์ค‘์•™ ์žฅ์• ๋ฌผ์˜ ์œ„์น˜ (์œˆ๋„์šฐ ์ค‘์‹ฌ)
    let obstacle_pos = vec2(0.0, 0.0);

    // ๋ชจ๋“  ์ž…์ž์— ๋Œ€ํ•ด ๋ฌผ๋ฆฌ ์—…๋ฐ์ดํŠธ ์ˆ˜ํ–‰
    for p in model.particles.iter_mut() {
        // ์ด๋ฏธ ์ •์ง€๋œ ์ž…์ž๋Š” ๊ฑด๋„ˆ๋œ€ (์„ฑ๋Šฅ ์ตœ์ ํ™”)
        if p.is_stopped {
            continue;
        }

        // 1. ์ค‘์•™ ์žฅ์• ๋ฌผ ๋ฐฉํ–ฅ์œผ๋กœ ์ธ๋ ฅ(force) ์ ์šฉ
        //    (์žฅ์• ๋ฌผ - ์ž…์ž ์œ„์น˜) = ์ž…์ž๋ฅผ ์žฅ์• ๋ฌผ ์ชฝ์œผ๋กœ ๋Œ์–ด๋‹น๊ธฐ๋Š” ๋ฐฉํ–ฅ ๋ฒกํ„ฐ
        let dir = (obstacle_pos - p.position).normalize_or_zero();
        p.velocity += dir * FORCE_MAG;

        // 2. ๋งˆ์ฐฐ(friction) ์ ์šฉ: ์†๋„๋ฅผ ์•ฝ๊ฐ„ ๊ฐ์†Œ์‹œ์ผœ ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฐ์† ๊ตฌํ˜„
        p.velocity *= FRICTION;

        // 3. ์œ„์น˜ ์—…๋ฐ์ดํŠธ: ํ˜„์žฌ ์†๋„๋งŒํผ ์ด๋™
        p.position += p.velocity;

        // 4. ์ถฉ๋Œ ์ฒ˜๋ฆฌ: ์žฅ์• ๋ฌผ๊ณผ์˜ ์ถฉ๋Œ ์—ฌ๋ถ€ ํ™•์ธ ๋ฐ ๋ฐ˜์‘
        handle_collision(p, obstacle_pos);
    }
}

// =============================================================================
// 6. ์ถฉ๋Œ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ (Collision Response Logic)
// =============================================================================
// ์ž…์ž๊ฐ€ ์ค‘์•™ ์žฅ์• ๋ฌผ(์›)๊ณผ ์ถฉ๋Œํ–ˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•˜๊ณ ,
// ์ถฉ๋Œ ์‹œ ์œ„์น˜ ๋ณด์ •, ์†๋„ ๋ฐ˜์‚ฌ, ์—๋„ˆ์ง€ ์†์‹ค, ์ •์ง€ ์กฐ๊ฑด ๋“ฑ์„ ์ฒ˜๋ฆฌ.

fn handle_collision(p: &mut Particle, obstacle_pos: Vec2) {
    // ์ž…์ž ์ค‘์‹ฌ์—์„œ ์žฅ์• ๋ฌผ ์ค‘์‹ฌ๊นŒ์ง€์˜ ๋ฒกํ„ฐ
    let to_particle = p.position - obstacle_pos;
    // ๋‘ ์ค‘์‹ฌ ์‚ฌ์ด์˜ ๊ฑฐ๋ฆฌ
    let dist = to_particle.length();
    // ์ถฉ๋Œ ํŒ๋‹จ ๊ธฐ์ค€ ๊ฑฐ๋ฆฌ = ์žฅ์• ๋ฌผ ๋ฐ˜์ง€๋ฆ„ + ์ž…์ž ๋ฐ˜์ง€๋ฆ„
    let min_dist = OBSTACLE_RADIUS + PARTICLE_RADIUS;

    // ์ถฉ๋Œ ๋ฐœ์ƒ ์กฐ๊ฑด: ์‹ค์ œ ๊ฑฐ๋ฆฌ < ์ตœ์†Œ ํ—ˆ์šฉ ๊ฑฐ๋ฆฌ
    if dist < min_dist {
        // ์ถฉ๋Œ ํšŸ์ˆ˜ ์ฆ๊ฐ€
        p.collision_count += 1;

        // ๋ฒ•์„  ๋ฒกํ„ฐ: ์žฅ์• ๋ฌผ ์ค‘์‹ฌ์—์„œ ์ž…์ž ๋ฐฉํ–ฅ์œผ๋กœ ํ–ฅํ•จ (์ถฉ๋Œ ๋ฉด์˜ ์ˆ˜์ง ๋ฐฉํ–ฅ)
        let normal = to_particle.normalize_or_zero();

        // ์œ„์น˜ ๋ณด์ •: ์ž…์ž๋ฅผ ์žฅ์• ๋ฌผ ํ‘œ๋ฉด์— ๋”ฑ ๋งž๋„๋ก ์ด๋™ (์นจํˆฌ ๋ฐฉ์ง€)
        p.position = obstacle_pos + normal * min_dist;

        // ์†๋„ ๋ฒกํ„ฐ๋ฅผ ๋ฒ•์„  ๋ฐฉํ–ฅ์œผ๋กœ ํˆฌ์˜ โ†’ ์ถฉ๋Œ ๋ฐฉํ–ฅ ์„ฑ๋ถ„๋งŒ ์ถ”์ถœ
        let v_dot_n = p.velocity.dot(normal);

        // ํƒ„์„ฑ ๋ฐ˜์‚ฌ: 
        //   v' = v - (1 + elasticity) * (vยทn) * n
        // ์—ฌ๊ธฐ์„œ๋Š” (1 + elasticity) ๋Œ€์‹  `ELASTICITY`๋ฅผ ์ง์ ‘ ์‚ฌ์šฉ
        // ELASTICITY > 1.0์ด๋ฉด ๋ฐ˜์‚ฌ ์‹œ ์†๋„ ์ฆํญ (๊ณผ๋„ํ•œ ํŠ•๊น€ ํšจ๊ณผ)
        p.velocity -= ELASTICITY * v_dot_n * normal;

        // ์—๋„ˆ์ง€ ์†์‹ค ์ ์šฉ: ์ถฉ๋Œ ํ›„ ์†๋„ ๊ฐ์†Œ
        p.velocity *= ENERGY_LOSS;

        // ์ •์ง€ ์กฐ๊ฑด ์ฒดํฌ:
        //   - ์ตœ๋Œ€ ์ถฉ๋Œ ํšŸ์ˆ˜ ๋„๋‹ฌ OR
        //   - ์†๋„๊ฐ€ ๋งค์šฐ ๋А๋ ค์ ธ ๋ˆˆ์— ๋„์ง€ ์•Š์Œ
        if p.collision_count >= MAX_COLLISION || p.velocity.length() < STOP_VEL {
            p.is_stopped = true;
            p.velocity = Vec2::ZERO; // ์™„์ „ ์ •์ง€
        }
    }
}

// =============================================================================
// 7. ๋ทฐ ํ•จ์ˆ˜ (Rendering)
// =============================================================================
// ๋งค ํ”„๋ ˆ์ž„ ํ™”๋ฉด์— ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฒฐ๊ณผ๋ฅผ ๋ Œ๋”๋ง.
// ๋ฐฐ๊ฒฝ ํŽ˜์ด๋“œ ํšจ๊ณผ, ์žฅ์• ๋ฌผ, ์ž…์ž ๋“ฑ์„ ๊ทธ๋ฆผ.

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

    // ํ”„๋ ˆ์ž„ ๋ฒˆํ˜ธ ๊ฐ€์ ธ์˜ค๊ธฐ (์ฒซ ํ”„๋ ˆ์ž„ ์—ฌ๋ถ€ ํ™•์ธ์šฉ)
    let frame_no = frame.nth();

    if frame_no == 0 {
        // ์ฒซ ํ”„๋ ˆ์ž„: ์™„์ „ ๊ฒ€์€ ๋ฐฐ๊ฒฝ์œผ๋กœ ์ดˆ๊ธฐํ™”
        draw.background().color(hsla(0.0, 0.0, 0.0, 1.0));
    } else {
        // ์ดํ›„ ํ”„๋ ˆ์ž„: ์•ฝ๊ฐ„ ํˆฌ๋ช…ํ•œ ๊ฒ€์€ ์‚ฌ๊ฐํ˜•์„ ์ „์ฒด ํ™”๋ฉด์— ๋ฎ์–ด
        // ์ด์ „ ํ”„๋ ˆ์ž„์˜ ํ”์ ์„ ์„œ์„œํžˆ ํ๋ฆฌ๊ฒŒ ๋งŒ๋“ฆ (trail ํšจ๊ณผ)
        draw.rect()
            .w_h(WINDOW_SIZE, WINDOW_SIZE)
            .color(hsla(0.0, 0.0, 0.0, 0.1)); // 10% ๋ถˆํˆฌ๋ช…๋„
    }

    // ์ค‘์•™ ์žฅ์• ๋ฌผ ๊ทธ๋ฆฌ๊ธฐ (ํˆฌ๋ช…ํ•œ ์›)
    draw.ellipse()
        .xy(pt2(0.0, 0.0))                 // ์ค‘์‹ฌ ์ขŒํ‘œ
        .radius(OBSTACLE_RADIUS)           // ๋ฐ˜์ง€๋ฆ„
        .color(hsla(0.0, 0.0, 0.0, 0.1));  // ๋งค์šฐ ํˆฌ๋ช…ํ•œ ๊ฒ€์ •

    // ๋ชจ๋“  ์ž…์ž ๊ทธ๋ฆฌ๊ธฐ
    for p in &model.particles {
        draw.ellipse()
            .xy(p.position)                // ์ž…์ž ์œ„์น˜
            .radius(PARTICLE_RADIUS)       // ์ž…์ž ํฌ๊ธฐ
            .color(rgba(0.9, 0.9, 0.9, 0.5)); // ๋ฐ์€ ํšŒ์ƒ‰, 50% ํˆฌ๋ช…
    }

    // GPU์— ๊ทธ๋ฆฌ๊ธฐ ๋ช…๋ น ์ œ์ถœ
    draw.to_frame(app, &frame).unwrap();
}

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

0๊ฐœ์˜ ๋Œ“๊ธ€