๐Ÿ”ฎ :: Collision of Realms

BamgasiJMยท2025๋…„ 10์›” 3์ผ

Nannou <Generative Art>

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

๐Ÿ“ Rust Code

use nannou::prelude::*;

const NUM_BALLS: usize = 10_000;
const WINDOW_SIZE: f32 = 800.0;
const BALL_RADIUS: f32 = 1.0;
const GRAVITY: f32 = 0.005;
const LARGE_CIRCLE_RADIUS: f32 = 400.0;
const RESTITUTION: f32 = 0.95;
const MAX_BOUNCING_COUNT: u32 = 10;
const STOP_THRESHOLD: f32 = 0.1;

struct Ball {
    position: Vec2,
    velocity: Vec2,
    center: Vec2,
    gravity_direction: f32,
    bouncing_count: u32,
    stopped: bool,
}

struct Model {
    balls: Vec<Ball>,
}

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

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

    let circle_center_top = vec2(0.0, 400.0);
    let circle_center_bottom = vec2(0.0, -400.0);
    let mut balls = Vec::new();
    
    // ์œ„์ชฝ ์› ์ค‘์‹ฌ์—์„œ ์•„๋ž˜๋กœ ๋–จ์–ด์ง€๋Š” ๊ณต๋“ค
    for _ in 0..(NUM_BALLS / 2) {
        let angle = random_range(0.0, PI);
        let speed = random_range(0.5, 1.0);
        balls.push(Ball {
            position: circle_center_top,
            velocity: vec2(angle.cos() * speed, -angle.sin() * speed),
            center: circle_center_top,
            gravity_direction: -1.0,
            bouncing_count: 0,
            stopped: false,
        });
    }
    
    // ์•„๋ž˜์ชฝ ์› ์ค‘์‹ฌ์—์„œ ์œ„๋กœ ์˜ฌ๋ผ๊ฐ€๋Š” ๊ณต๋“ค
    for _ in 0..(NUM_BALLS / 2) {
        let angle = random_range(0.0, PI);
        let speed = random_range(0.5, 1.0);
        balls.push(Ball {
            position: circle_center_bottom,
            velocity: vec2(angle.cos() * speed, angle.sin() * speed),
            center: circle_center_bottom,
            gravity_direction: 1.0,
            bouncing_count: 0,
            stopped: false,
        });
    }
    
    Model { balls }
}

fn update(_app: &App, model: &mut Model, _update: Update) {
    for ball in &mut model.balls {
        if ball.stopped {
            continue;
        }
        
        // ์ค‘๋ ฅ ์ ์šฉ (์œ„์ชฝ์€ ์•„๋ž˜๋กœ, ์•„๋ž˜์ชฝ์€ ์œ„๋กœ)
        ball.velocity.y += GRAVITY * ball.gravity_direction;
        ball.position += ball.velocity;

        // ํฐ ์›์˜ ์ค‘์‹ฌ์œผ๋กœ๋ถ€ํ„ฐ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ
        let distance_from_center = ball.position.distance(ball.center);
        let distance_diff = (distance_from_center - LARGE_CIRCLE_RADIUS).abs();
        
        // ํฐ ์›์˜ ๋ฐ˜๊ฒฝ ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ€๋ฉด ์ถฉ๋Œ ์ฒ˜๋ฆฌ
        if distance_from_center > LARGE_CIRCLE_RADIUS - BALL_RADIUS {
            ball.bouncing_count += 1;
            
            // ์›์˜ ํ‘œ๋ฉด์œผ๋กœ ์œ„์น˜ ๋ณด์ •
            let direction = (ball.position - ball.center).normalize();
            ball.position = ball.center + direction * (LARGE_CIRCLE_RADIUS - BALL_RADIUS);
            
            // ๋ฒ•์„  ๋ฒกํ„ฐ (์›์˜ ์ค‘์‹ฌ์—์„œ ๊ณต์œผ๋กœ ํ–ฅํ•˜๋Š” ๋ฐฉํ–ฅ)
            let normal = direction;
            
            // ์†๋„๋ฅผ ๋ฒ•์„  ๋ฐฉํ–ฅ๊ณผ ์ ‘์„  ๋ฐฉํ–ฅ์œผ๋กœ ๋ถ„ํ•ด
            let velocity_normal = ball.velocity.dot(normal);
            let velocity_tangent = ball.velocity - normal * velocity_normal;
            
            // ๋ฒ•์„  ๋ฐฉํ–ฅ ์†๋„๋งŒ ๋ฐ˜์ „ํ•˜๊ณ  ๋ฐ˜๋ฐœ๊ณ„์ˆ˜ ์ ์šฉ
            ball.velocity = velocity_tangent + normal * (-velocity_normal * RESTITUTION);
            
            // ์ •์ง€ ์กฐ๊ฑด ํ™•์ธ
            if ball.bouncing_count >= MAX_BOUNCING_COUNT || distance_diff < STOP_THRESHOLD {
                ball.stopped = true;
                ball.velocity = vec2(0.0, 0.0);
            }
        }
    }
}

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.01, 1.0));
    } else {
        draw.rect()
        .w_h(WINDOW_SIZE, WINDOW_SIZE)
        .color(hsla(0.0, 0.0, 0.01, 0.1));
    }    
    
    // ์ž‘์€ ๊ณต๋“ค ๊ทธ๋ฆฌ๊ธฐ (์ •์ง€๋œ ๊ณต์€ ๊ทธ๋ฆฌ์ง€ ์•Š์Œ)
    for ball in &model.balls {
        if !ball.stopped {
            draw.ellipse()
                .xy(ball.position)
                .radius(BALL_RADIUS)
                .color(rgba(1.0, 1.0, 1.0, 0.8));
        }
    }      

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

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

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