๐Ÿ”ฎ :: Slinky - Concentric Ellipses

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

Nannou <Generative Art>

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

๐Ÿงฉ Pseudo-code ๊ตฌ์กฐ ์„ค๊ณ„

  • NUM_RINGS: ๋™์‹ฌ์›์˜ ๊ฐœ์ˆ˜.
  • ELLIPSE_RATIO: ์ด์‹ฌ๋ฅ ์„ ํ†ตํ•ด ์›ํ˜• โ†’ ํƒ€์›ํ˜• ๋ณ€ํ™” ์ œ์–ด.
  • DEPTH_SCALE: ์›์ด ์•ˆ์ชฝ์œผ๋กœ ๊ฐˆ์ˆ˜๋ก ์ ์  ์ž‘์•„์ง€๋Š” ๋น„์œจ.
  • COLOR_VARIATION: ๊ฐ ์›์˜ hue๋ฅผ ๋žœ๋คํ•˜๊ฒŒ ์กฐ์ ˆ.
  • LINE_WEIGHT: ๋‘๊ป˜ ์กฐ์ •์œผ๋กœ ๊นŠ์ด๊ฐ ๋ณด๊ฐ•.
  • Ring: ๊ฐ ์›์˜ ๋ฐ˜์ง€๋ฆ„, ์ƒ‰์ƒ, ์ด์‹ฌ๋ฅ  ์ •๋ณด๋ฅผ ๊ฐ€์ง.
  • Model: ์ „์ฒด ring ์ง‘ํ•ฉ์„ ๊ด€๋ฆฌํ•˜๊ณ  view์—์„œ ๋ฐ˜๋ณต ๋ Œ๋”๋ง.
  • OUTER_WEIGHT โ†’ INNER_WEIGHT
    : ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ ์›์€ ๋‘๊ป๊ณ , ์•ˆ์ชฝ์œผ๋กœ ๊ฐˆ์ˆ˜๋ก ์„ ์ด ์–‡์•„์ง.

๐Ÿ“ Rust Code

use nannou::prelude::*;

const NUM_RINGS: usize = 100;
const ELLIPSE_RATIO: f32 = 1.0;
const COLOR_VARIATION: f32 = 0.2;
const DEPTH_SCALE: f32 = 0.98;
const BASE_RADIUS: f32 = 800.0;
const HUE_BASE: f32 = 0.5;
const FOLLOW_SPEED: f32 = 0.45;

const OUTER_WEIGHT: f32 = 15.0; // ๊ฐ€์žฅ ๋ฐ”๊นฅ์ชฝ ์›์˜ ๋‘๊ป˜
const INNER_WEIGHT: f32 = 1.0; // ๊ฐ€์žฅ ์•ˆ์ชฝ ์›์˜ ๋‘๊ป˜

// ----------------------------------------------------
// ๋ณด์กฐ ํ•จ์ˆ˜: ์„ ํ˜• ๋ณด๊ฐ„ (lerp)
// ----------------------------------------------------
fn lerp(a: f32, b: f32, t: f32) -> f32 {
    a + (b - a) * t
}

// ----------------------------------------------------
// ๊ตฌ์กฐ์ฒด ์ •์˜
// ----------------------------------------------------
struct Model {
    rings: Vec<Ring>,
}

struct Ring {
    radius: f32,
    color: Hsla,
    eccentricity: f32,
    position: Vec2,
    weight: f32,
}

// ----------------------------------------------------
// impl: Ring
// ----------------------------------------------------
impl Ring {
    fn new(radius: f32, color: Hsla, eccentricity: f32, position: Vec2, weight: f32) -> Self {
        Self {
            radius,
            color,
            eccentricity,
            position,
            weight,
        }
    }

    fn follow(&mut self, target: Vec2, lerp_amount: f32) {
        self.position = self.position + (target - self.position) * lerp_amount;
    }
}

// ----------------------------------------------------
// impl: Model
// ----------------------------------------------------
impl Model {
    fn new(app: &App) -> Self {
        app.new_window()
            .size(1024, 1024)
            .title("Concentric Color Ellipses Follow Mouse")
            .build()
            .unwrap();

        let mut rings = Vec::new();

        // NUM_RINGS์ด ์ ์–ด๋„ 2๊ฐœ ์ด์ƒ์ผ ๊ฒƒ์„ ๊ฐ€์ •ํ•˜๋˜, ๋ฐฉ์–ด์ ์œผ๋กœ ์ฒ˜๋ฆฌ
        let denom = if NUM_RINGS > 1 { (NUM_RINGS - 1) as f32 } else { 1.0 };

        for i in 0..NUM_RINGS {
            let t = (i as f32) / denom; // 0.0 (outer) -> 1.0 (inner)
            // ์„  ๋‘๊ป˜๋ฅผ ๋ณด๊ฐ„ (๋ฐ”๊นฅ์ชฝ์—์„œ ์•ˆ์ชฝ์œผ๋กœ)
            let weight = lerp(OUTER_WEIGHT, INNER_WEIGHT, t);

            let radius = BASE_RADIUS * DEPTH_SCALE.powf(i as f32);
            let hue = (HUE_BASE + random_f32() * COLOR_VARIATION) % 1.0;
            let color = hsla(hue, 0.8, 0.5, 1.0);

            rings.push(Ring::new(radius, color, ELLIPSE_RATIO, vec2(0.0, 0.0), weight));
        }

        Self { rings }
    }

    fn update(&mut self, app: &App) {
        let mouse = app.mouse.position();

        // ๊ฐ€์žฅ ์•ˆ์ชฝ(๊ฐ€์žฅ ์ž‘์€) ์›์ด ๋งˆ์šฐ์Šค๋ฅผ ๋”ฐ๋ผ๊ฐ€๋„๋ก: rings์˜ ๋งˆ์ง€๋ง‰ ์š”์†Œ๋ฅผ ์‚ฌ์šฉ
        if let Some(last) = self.rings.last_mut() {
            last.follow(mouse, FOLLOW_SPEED);
        }

        // ๊ทธ ์™ธ ์›๋“ค์€ ๋‚ด๋ถ€ ์ชฝ(๋” ์ž‘์€ ๋ฐ˜์ง€๋ฆ„)์— ์žˆ๋Š” ์›์„ ๋”ฐ๋ผ๊ฐ€๋„๋ก ๋ณด๊ฐ„
        // ์•ˆ์ „ํ•˜๊ฒŒ len() ๊ฒ€์‚ฌ
        if self.rings.len() >= 2 {
            for i in (0..self.rings.len() - 1).rev() {
                let target_pos = self.rings[i + 1].position;
                // ๋ฐ”๊นฅ์ชฝ์œผ๋กœ ๊ฐˆ์ˆ˜๋ก ๋ฐ˜์‘์„ ์กฐ๊ธˆ ๋А๋ฆฌ๊ฒŒ ๋งŒ๋“ค์–ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ณด์ด๋„๋ก ์กฐ์ •
                let lerp_amt = FOLLOW_SPEED * 0.8;
                self.rings[i].follow(target_pos, lerp_amt);
            }
        }
    }

    fn view(&self, app: &App, frame: Frame) {
        let draw = app.draw();
        draw.background().color(hsla(0.0, 0.0, 0.02, 1.0));

        for ring in &self.rings {
            draw.ellipse()
                .x_y(ring.position.x, ring.position.y)
                .w(ring.radius)
                .h(ring.radius * ring.eccentricity)
                .no_fill()
                .stroke(ring.color)
                .stroke_weight(ring.weight);
        }

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

// ----------------------------------------------------
// main() & ์ฝœ๋ฐฑ๋“ค
// ----------------------------------------------------
fn main() {
    nannou::app(model).update(update).view(view).run();
}

fn model(app: &App) -> Model {
    Model::new(app)
}

fn update(app: &App, model: &mut Model, _update: Update) {
    model.update(app);
}

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

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

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