๐Ÿ”ฎ :: Sperm Racing

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

Nannou <Generative Art>

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

๐Ÿ“ Rust Code

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

struct Sperm {
    head: Point2,
    velocity: Vec2,
    speed: f32,
    phase: f32,
    amp: f32,
    swimming_phase: f32,  // ํ—ค์—„์น˜๋Š” ๋ฆฌ๋“ฌ
    energy: f32,          // ์—๋„ˆ์ง€ ๋ ˆ๋ฒจ (์‹œ๊ฐ„์— ๋”ฐ๋ผ ๋ณ€ํ•จ)
    head_bob_phase: f32,  // ๋จธ๋ฆฌ ์ƒํ•˜ ์ง„๋™์šฉ ์œ„์ƒ
    head_bob_freq: f32,   // ๋จธ๋ฆฌ ์ง„๋™ ์ฃผํŒŒ์ˆ˜
}

struct Model {
    sperms: Vec<Sperm>,
    time: f32,
    noise: Perlin,
}

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

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

    Model {
        sperms: Vec::new(),
        time: 0.0,
        noise: Perlin::new(),
    }
}

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

    let win = app.window_rect();

    // ์ตœ๋Œ€ 1000๋งˆ๋ฆฌ ์œ ์ง€
    while model.sperms.len() < 1500 {
        let mut rng = rand::thread_rng();
        let y = rng.gen_range((win.bottom() + 20.0)..(win.top() - 20.0));
        let speed = rng.gen_range(57.0..105.0);  // ์†๋„ ๋ฒ”์œ„ ์กฐ์ •
        let phase = rng.gen_range(0.0..TAU);
        let amp = rng.gen_range(0.8..1.4);
        let swimming_phase = rng.gen_range(0.0..TAU);
        let head_bob_phase = rng.gen_range(0.0..TAU);
        let head_bob_freq = rng.gen_range(2.5..4.5);  // ๊ฐœ์ฒด๋ณ„ ๋‹ค๋ฅธ ์ง„๋™ ์ฃผํŒŒ์ˆ˜
        
        model.sperms.push(Sperm {
            head: pt2(win.right() + 60.0, y),
            velocity: vec2(-1.0, 0.0),
            speed,
            phase,
            amp,
            swimming_phase,
            energy: rng.gen_range(0.7..1.0),
            head_bob_phase,
            head_bob_freq,
        });
    }

    // ์ •์ž ์›€์ง์ž„ ์—…๋ฐ์ดํŠธ
    for s in model.sperms.iter_mut() {
        // ํ—ค์—„์น˜๋Š” ๋ฆฌ๋“ฌ ์—…๋ฐ์ดํŠธ (๊ฐœ์ฒด๋ณ„๋กœ ๋‹ค๋ฅธ ์ฃผ๊ธฐ)
        s.swimming_phase += dt * 6.0 * s.amp;  // ์†๋„ ์กฐ๊ธˆ ๋‚ฎ์ถค
        s.head_bob_phase += dt * s.head_bob_freq;
        
        // ์—๋„ˆ์ง€ ๋ณ€ํ™” (์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ํ”ผ๋กœ๊ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
        s.energy = (s.energy - dt * 0.05).max(0.3);
        
        // ๊ธฐ๋ณธ ์ „์ง„ ๋ฐฉํ–ฅ์— ์ˆ˜์ง/์ˆ˜ํ‰ ์ง„๋™ ์ถ”๊ฐ€
        let lateral_oscillation = (s.swimming_phase * 0.8).sin() * 6.0 * s.amp * s.energy;  // ์ง„ํญ ๊ฐ์†Œ
        let forward_oscillation = (s.swimming_phase * 0.5).cos() * 2.0 * s.amp;  // ์ง„ํญ ๊ฐ์†Œ
        
        // Perlin ๋…ธ์ด์ฆˆ๋กœ ๋ถˆ๊ทœ์น™ํ•œ ๋ฐฉํ–ฅ ๋ณ€ํ™” (๋” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ)
        let noise_x = model.noise.get([s.phase as f64 * 0.2, model.time as f64 * 0.4]) as f32;
        let noise_y = model.noise.get([s.phase as f64 * 0.2 + 100.0, model.time as f64 * 0.4]) as f32;
        
        // ๋จธ๋ฆฌ ์ƒํ•˜ ์ง„๋™ (๊ฐ์ž ๋‹ค๋ฅธ ์ฃผํŒŒ์ˆ˜์™€ ์œ„์ƒ)
        let head_bob = (s.head_bob_phase).sin() * 4.0 * s.energy;
        let head_bob_noise = model.noise.get([s.phase as f64 * 0.15, model.time as f64 * 0.6]) as f32 * 2.5;
        
        // ๋ฐฉํ–ฅ ๋ฒกํ„ฐ ๊ณ„์‚ฐ
        s.velocity = vec2(
            -1.0 + forward_oscillation * 0.08 + noise_x * 0.1,
            lateral_oscillation * 0.015 + noise_y * 0.08
        ).normalize();
        
        // ์†๋„์— ์—๋„ˆ์ง€์™€ ํ—ค์—„์น˜๋Š” ๋ฆฌ๋“ฌ ๋ฐ˜์˜ (๋” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ)
        let rhythm_variation = (s.swimming_phase * 1.2).sin() * 0.15;
        let current_speed = s.speed * s.energy * (1.0 + rhythm_variation);
        
        // ๊ธฐ๋ณธ ์ด๋™
        s.head += s.velocity * current_speed * dt;
        
        // ๋จธ๋ฆฌ์— ์ƒํ•˜ ์ง„๋™ ์ถ”๊ฐ€ (๊ธฐ๋ณธ ์ด๋™๊ณผ ๋ณ„๊ฐœ)
        s.head.y += (head_bob + head_bob_noise) * dt * 25.0;
    }

    // ํ™”๋ฉด์„ ๋ฒ—์–ด๋‚œ ์ •์ž ์ œ๊ฑฐ
    let tail_len = 80.0;  // ๊ผฌ๋ฆฌ ๊ธธ์ด ์ฆ๊ฐ€์œผ๋กœ ๊ฐ์†Œ
    model.sperms.retain(|s| s.head.x > win.left() - tail_len - 10.0);
}

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

    let tail_len = 80.0;  // ๊ผฌ๋ฆฌ ๊ธธ์ด ์ฆ๊ฐ€
    let segments = 25;    // ์„ธ๊ทธ๋จผํŠธ ์ˆ˜ ์ฆ๊ฐ€๋กœ ๋” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ
    let base_amplitude = 8.0;  // ์ง„ํญ ๊ฐ์†Œ
    let wavelength = 0.6;  // ํŒŒ์žฅ ์ฆ๊ฐ€๋กœ ๋” ๋ถ€๋“œ๋Ÿฌ์šด ๊ณก์„ 
    let wave_speed = 4.5;  // ํŒŒํ˜• ์†๋„ ๊ฐ์†Œ

    for s in model.sperms.iter() {
        let head = s.head;

        let dir = if s.velocity.length() > 0.0 {
            s.velocity.normalize()
        } else {
            vec2(-1.0, 0.0)
        };

        let perp = vec2(-dir.y, dir.x);

        // ๊ผฌ๋ฆฌ ์ ๋“ค ๊ณ„์‚ฐ
        let mut points: Vec<Point2> = Vec::with_capacity(segments + 1);
        for i in 0..=segments {
            let t = i as f32 / segments as f32;
            let base = head + dir * (-t * tail_len);

            // ๋” ๋ถ€๋“œ๋Ÿฌ์šด ํŒŒํ˜• ์กฐํ•ฉ
            let primary_wave = (model.time * wave_speed + s.phase + t / wavelength).sin();
            let secondary_wave = (model.time * wave_speed * 0.6 + s.phase * 1.3 + t / (wavelength * 1.2)).sin() * 0.3;
            
            // ํ—ค์—„์น˜๋Š” ๋ฆฌ๋“ฌ๊ณผ ์—ฐ๋™ (๋” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ)
            let rhythm_factor = (s.swimming_phase * 0.7 + t * 1.5).sin() * 0.2;
            
            // ๋” ๋ถ€๋“œ๋Ÿฌ์šด ๋…ธ์ด์ฆˆ
            let noise_val = model.noise.get([
                (s.phase as f64) * 0.25,
                (model.time as f64) * 0.8 + (t as f64) * 2.0,
            ]) as f32;

            // ๊ผฌ๋ฆฌ ๋์œผ๋กœ ๊ฐˆ์ˆ˜๋ก ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ฆ๊ฐ€ (์ œ๊ณฑ ํ•จ์ˆ˜ ์‚ฌ์šฉ)
            let intensity = (t * t * 1.5).min(1.0);
            
            let local_amp = base_amplitude * s.amp * s.energy;
            let y_offset = (primary_wave * 0.6 + secondary_wave + rhythm_factor + noise_val * 0.4) 
                         * local_amp * intensity;

            let offset = perp * y_offset;
            points.push(base + offset);
        }

        // ๋จธ๋ฆฌ ํฌ๊ธฐ 2.5๋กœ ์„ค์ •
        draw.ellipse().xy(head).radius(2.5).color(WHITE);

        // ๊ผฌ๋ฆฌ๋ฅผ ์„ธ๊ทธ๋จผํŠธ๋ณ„๋กœ ๋ถˆํˆฌ๋ช…๋„ ๋ณ€ํ™”์™€ ํ•จ๊ป˜ ๊ทธ๋ฆฌ๊ธฐ
        for i in 0..(points.len() - 1) {
            let t = i as f32 / (points.len() - 1) as f32;
            
            // ๋จธ๋ฆฌ์—์„œ ๊ผฌ๋ฆฌ๋กœ ๊ฐˆ์ˆ˜๋ก ๋ถˆํˆฌ๋ช…๋„ ๊ฐ์†Œ (1.0 -> 0.1)
            let alpha = 1.0 - (t * 0.9);
            
            // ๋‘๊ป˜๋„ ์ ์ง„์ ์œผ๋กœ ๊ฐ์†Œ
            let thickness = 2.0 * (1.0 - t * 0.6);
            
            draw.line()
                .start(points[i])
                .end(points[i + 1])
                .weight(thickness.max(0.3))
                .color(rgba(1.0, 1.0, 1.0, alpha));
        }
    }

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

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

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