

use nannou::prelude::*;
use nannou::noise::{ Perlin, NoiseFn };
use nannou::image::{DynamicImage, GenericImageView};
fn main() {
nannou::app(model).update(update).run();
}
struct Model {
_window: window::Id,
perlin: Perlin,
layers: usize,
samples: usize,
spacing: f32,
amplitude: f32,
freq: f64,
time_speed: f32,
start_time: std::time::Instant,
image_data: DynamicImage,
image_dimensions: Vec2,
}
fn model(app: &App) -> Model {
let window_id = app
.new_window()
.size(800, 800)
.title("Layered Line Depth - Image Guided")
.view(view)
.build()
.unwrap();
let perlin = Perlin::default();
// ์ด๋ฏธ์ง ๋ก๋ - ํ๋ก์ ํธ ๋ฃจํธ ๊ธฐ์ค ์๋ ๊ฒฝ๋ก
let img_path = std::path::Path::new("assets/test_image_07.jpg");
// nannou::image ํฌ๋ ์ดํธ๋ก ์ด๋ฏธ์ง ๋ก๋
let image_data = nannou::image::open(&img_path).expect("Failed to load image");
// ์ด๋ฏธ์ง ํฌ๊ธฐ ๊ฐ์ ธ์ค๊ธฐ
let (width, height) = image_data.dimensions();
let image_dimensions = vec2(width as f32, height as f32);
Model {
_window: window_id,
perlin,
layers: 130,
samples: 800,
spacing: 6.0,
amplitude: 100.0,
freq: 0.005,
time_speed: 0.2,
start_time: std::time::Instant::now(),
image_data,
image_dimensions,
}
}
fn update(_app: &App, _model: &mut Model, _update: Update) {
// ์
๋ฐ์ดํธ ๋ก์ง
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
// ์ด๋์ด ํ์ ๋ฐฐ๊ฒฝ
draw.background().hsla(0.0, 0.0, 0.01, 1.0);
let win = app.window_rect();
let t = model.start_time.elapsed().as_secs_f32();
let x_min = win.left();
let x_max = win.right();
let width = x_max - x_min;
let center_y = 0.0;
for i in 0..model.layers {
let depth = (i as f32 / (model.layers - 1) as f32) * 2.0 - 1.0;
let base_y = center_y + depth * (model.layers as f32 * 0.5 * model.spacing);
// ์๋์ชฝ์ด ๋๊ป๊ฒ: depth๊ฐ -1.0์ผ ๋ ๋๊ป๊ฒ, 1.0์ผ ๋ ๊ฐ๋๊ฒ
let stroke_w = map_range(depth, -1.0, 1.0, 2.5, 0.5);
let alpha = map_range(depth, -1.0, 1.0, 0.3, 1.0);
let mut pts: Vec<Point2> = Vec::with_capacity(model.samples);
for si in 0..model.samples {
let u = si as f32 / (model.samples - 1) as f32;
let x = x_min + u * width;
let v = i as f32 / (model.layers - 1) as f32;
// ์ด๋ฏธ์ง ์ํ ๋ฐ์ : v๋ฅผ 1.0 - v๋ก ๋ณํ
let v_flipped = 1.0 - v;
// ํฝ์
์ขํ ๊ณ์ฐ (๋ฒ์ ์ฒดํฌ ํฌํจ)
let pixel_x = ((u * model.image_dimensions.x) as u32)
.min(model.image_dimensions.x as u32 - 1);
let pixel_y = ((v_flipped * model.image_dimensions.y) as u32)
.min(model.image_dimensions.y as u32 - 1);
// ํฝ์
์์ ๊ฐ์ ธ์ค๊ธฐ
let pixel_color = model.image_data.get_pixel(pixel_x, pixel_y);
// ๋ฐ๊ธฐ ๊ณ์ฐ (์ ํํ ๋ฐฉ๋ฒ: sRGB ๊ฐ์ค์น ์ ์ฉ)
let brightness = (0.299 * pixel_color[0] as f32
+ 0.587 * pixel_color[1] as f32
+ 0.114 * pixel_color[2] as f32) / 255.0;
// ๋ฐ๊ธฐ์ ๋ฐ๋ฅธ ์งํญ ์กฐ์
let current_amplitude = map_range(brightness, 0.0, 1.0, 0.0, model.amplitude * 2.0);
let nx = (x as f64) * model.freq;
let ny = (i as f64) * 0.05 + (t as f64) * (model.time_speed as f64);
let noise_val = model.perlin.get([nx, ny]);
let dy = noise_val as f32 * current_amplitude * (1.0 - depth.abs());
let edge_falloff = cubic_ease(map_range(u, 0.0, 1.0, -1.0, 1.0).abs());
let final_y = base_y + dy * (1.0 - 0.7 * edge_falloff);
pts.push(pt2(x, final_y));
}
draw.polyline()
.weight(stroke_w)
.points(pts)
.rgba(1.0, 1.0, 1.0, alpha);
}
draw.to_frame(app, &frame).unwrap();
}
fn cubic_ease(x: f32) -> f32 {
let x = x.clamp(0.0, 1.0);
x * x * (3.0 - 2.0 * x)
}
use nannou::prelude::*; // Nannou์ ๊ธฐ๋ณธ ๊ธฐ๋ฅ ์ํฌํธ
use nannou::noise::{Perlin, NoiseFn}; // Perlin ๋
ธ์ด์ฆ ๊ด๋ จ ๊ธฐ๋ฅ ์ํฌํธ
use nannou::image::{DynamicImage, GenericImageView}; // ์ด๋ฏธ์ง ์ฒ๋ฆฌ ๊ด๋ จ ๊ธฐ๋ฅ ์ํฌํธ
// ์ ์ญ ์์
const WINDOW_WIDTH: u32 = 1000; // ์๋์ฐ ๋๋น (ํฝ์
๋จ์)
const WINDOW_HEIGHT: u32 = 1000; // ์๋์ฐ ๋์ด (ํฝ์
๋จ์)
const WINDOW_TITLE: &str = "Layered Line Depth"; // ์๋์ฐ ์ ๋ชฉ
const DEFAULT_LAYERS: usize = 130; // ๊ธฐ๋ณธ ๋ ์ด์ด ์
const DEFAULT_SAMPLES: usize = 800; // ๊ฐ ๋ ์ด์ด๋น ์ํ ํฌ์ธํธ ์
const DEFAULT_SPACING: f32 = 6.0; // ๋ ์ด์ด ๊ฐ ๊ฐ๊ฒฉ
const DEFAULT_AMPLITUDE: f32 = 30.0; // ๊ธฐ๋ณธ ํ๋ ์งํญ
const DEFAULT_FREQ: f64 = 0.005; // ๋
ธ์ด์ฆ ์ฃผํ์ (๊ฐ์ด ์์์๋ก ๋ ๋์ ํ๋)
const DEFAULT_TIME_SPEED: f32 = 0.2; // ์๊ฐ์ ๋ฐ๋ฅธ ์ ๋๋ฉ์ด์
์๋
const IMAGE_PATH: &str = "assets/test4.jpg"; // ๋ก๋ํ ์ด๋ฏธ์ง ํ์ผ ๊ฒฝ๋ก
const BACKGROUND_GRAY: f32 = 0.01; // ๋ฐฐ๊ฒฝ์์ ํ์์กฐ ๊ฐ (0=๊ฒ์ , 1=ํ์)
const MIN_STROKE_WEIGHT: f32 = 0.5; // ๋ผ์ธ์ ์ต์ ๋๊ป
const MAX_STROKE_WEIGHT: f32 = 2.5; // ๋ผ์ธ์ ์ต๋ ๋๊ป
const MIN_ALPHA: f32 = 0.3; // ๋ผ์ธ์ ์ต์ ํฌ๋ช
๋
const MAX_ALPHA: f32 = 1.0; // ๋ผ์ธ์ ์ต๋ ํฌ๋ช
๋ (๋ถํฌ๋ช
)
const EDGE_FALLOFF_FACTOR: f32 = 0.7; // ๊ฐ์ฅ์๋ฆฌ ๊ฐ์ ๊ณ์
// Model ๊ตฌ์กฐ์ฒด ์ ์ - ํ๋ก๊ทธ๋จ์ ์ํ๋ฅผ ์ ์ฅํ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ
struct Model {
perlin: Perlin, // Perlin ๋
ธ์ด์ฆ ์์ฑ๊ธฐ (๋๋คํ ์์ฐ์ค๋ฌ์ด ํจํด ์์ฑ)
layers: usize, // ๋ ์ด์ด ์ (์์ง์ผ๋ก ๊ทธ๋ ค์ง ๋ผ์ธ์ ์)
samples: usize, // ๊ฐ ๋ ์ด์ด์ ์ํ ์ (๊ฐ ๋ ์ด์ด๋ฅผ ๊ตฌ์ฑํ๋ ์ ์ ์)
spacing: f32, // ๋ ์ด์ด ๊ฐ ๊ฐ๊ฒฉ (์์ง ๊ฐ๊ฒฉ)
amplitude: f32, // ๊ธฐ๋ณธ ์งํญ (ํ๋์ ๋์ด)
freq: f64, // ๋
ธ์ด์ฆ ์ฃผํ์ (ํ๋์ ๋ฐ๋)
time_speed: f32, // ์๊ฐ์ ๋ฐ๋ฅธ ๋ณํ ์๋
start_time: std::time::Instant, // ํ๋ก๊ทธ๋จ ์์ ์๊ฐ (์ ๋๋ฉ์ด์
์๊ฐ ๊ณ์ฐ์ฉ)
image_data: DynamicImage, // ๋ก๋๋ ์ด๋ฏธ์ง ๋ฐ์ดํฐ
image_dimensions: Vec2, // ์ด๋ฏธ์ง ์ฐจ์ (๋๋น, ๋์ด)
}
// ์ ํ๋ฆฌ์ผ์ด์
๋ฉ์ธ ํจ์ - ํ๋ก๊ทธ๋จ ์ง์
์
fn main() {
nannou::app(model) // Nannou ์ ํ๋ฆฌ์ผ์ด์
์์ฑ ๋ฐ model ํจ์(์ค์ ) ์ ๋ฌ
.update(update) // ์
๋ฐ์ดํธ ํจ์ ์ค์
.run(); // ์ ํ๋ฆฌ์ผ์ด์
์คํ
}
// Model์ ์์ฑํ๊ณ ์ด๊ธฐํํ๋ ์ค์ ํจ์
fn model(app: &App) -> Model {
// ์ด๋ฏธ์ง ๋ก๋ฉ ๋๋ฒ๊น
์ฝ๋
match std::env::current_dir() {
Ok(path) => println!("ํ์ฌ ์์
๋๋ ํ ๋ฆฌ: {}", path.display()),
Err(e) => println!("ํ์ฌ ์์
๋๋ ํ ๋ฆฌ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์: {}", e),
}
// ์๋์ฐ ์์ฑ
app.new_window() // ์ ์๋์ฐ ์์ฑ ์์
.size(WINDOW_WIDTH, WINDOW_HEIGHT) // ์๋์ฐ ํฌ๊ธฐ ์ค์
.title(WINDOW_TITLE) // ์๋์ฐ ์ ๋ชฉ ์ค์
.view(view) // ๋ทฐ ํจ์ ์ค์ (๊ทธ๋ฆฌ๊ธฐ ์ฝ๋ฐฑ)
.build() // ์๋์ฐ ์์ฑ ์๋ฃ
.expect("์๋์ฐ ์์ฑ์ ์คํจํ์ต๋๋ค."); // ์๋์ฐ ์์ฑ ์คํจ ์ ํ๋ก๊ทธ๋จ ์ข
๋ฃ
// Perlin ๋
ธ์ด์ฆ ์์ฑ๊ธฐ ์ด๊ธฐํ - ์์ฐ์ค๋ฌ์ด ๋๋ค ํจํด ์์ฑ์ ์ฌ์ฉ
let perlin = Perlin::default();
// ์ด๋ฏธ์ง ๋ก๋ (๊ฒฝ๋ก๋ฅผ ํฌํจํ ์์๋ฅผ ์ง์ ์ฌ์ฉ)
let image_data = match nannou::image::open(IMAGE_PATH) {
Ok(img) => img,
Err(e) => {
eprintln!("์ด๋ฏธ์ง ๋ก๋ ์คํจ (๊ฒฝ๋ก: {:?}): {}", IMAGE_PATH, e);
std::process::exit(1);
}
};
// ์ด๋ฏธ์ง ํฌ๊ธฐ ๊ฐ์ ธ์ค๊ธฐ
let (width, height) = image_data.dimensions(); // ์ด๋ฏธ์ง์ ๋๋น์ ๋์ด ๊ฐ์ ธ์ค๊ธฐ
let image_dimensions = vec2(width as f32, height as f32); // Vec2 ํ์
์ผ๋ก ๋ณํ
// Model ์ธ์คํด์ค ๋ฐํ - ๋ชจ๋ ํ๋ ์ด๊ธฐํ
Model {
perlin,
layers: DEFAULT_LAYERS,
samples: DEFAULT_SAMPLES,
spacing: DEFAULT_SPACING,
amplitude: DEFAULT_AMPLITUDE,
freq: DEFAULT_FREQ,
time_speed: DEFAULT_TIME_SPEED,
start_time: std::time::Instant::now(),
image_data,
image_dimensions,
}
}
// ์
๋ฐ์ดํธ ํจ์ - ๋งค ํ๋ ์๋ง๋ค Model์ ์ํ๋ฅผ ๋ณ๊ฒฝ (ํ์ฌ๋ ๋น์ด์์)
fn update(_app: &App, _model: &mut Model, _update: Update) {
// ์ํ ์
๋ฐ์ดํธ ๋ก์ง์ด ํ์ํ๋ค๋ฉด ์ฌ๊ธฐ์ ์ถ๊ฐ
}
// ๋ทฐ ํจ์ ๊ตฌํ - ํ๋ ์๋ง๋ค ํธ์ถ๋์ด ํ๋ฉด์ ๊ทธ๋ฆฌ๊ธฐ ์ํ
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw(); // ๋๋ก์ ์ปจํ
์คํธ ์์ฑ
draw.background().hsla(0.0, 0.0, BACKGROUND_GRAY, 1.0); // HSLA ์์ ๋ชจ๋ธ๋ก ๋ฐฐ๊ฒฝ์ ์ค์
let win = app.window_rect(); // ํ์ฌ ์๋์ฐ์ ํฌ๊ธฐ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
let t = model.start_time.elapsed().as_secs_f32(); // ์์ ์๊ฐ๋ถํฐ ๊ฒฝ๊ณผํ ์๊ฐ(์ด) ๊ณ์ฐ
// 0์ผ๋ก ๋๋๊ธฐ ๋ฐฉ์ง๋ฅผ ์ํด ๋ถ๋ชจ๊ฐ 0์ด ๋์ง ์๋๋ก ๋ณด์ฅ
let layers_denom = (model.layers.saturating_sub(1)).max(1) as f32;
let samples_denom = (model.samples.saturating_sub(1)).max(1) as f32;
// ๊ฐ ๋ ์ด์ด์ ๋ํ ๋ผ์ธ ๊ทธ๋ฆฌ๊ธฐ
for i in 0..model.layers { // 0๋ถํฐ ๋ ์ด์ด ์๋งํผ ๋ฐ๋ณต
let depth = (i as f32 / layers_denom) * 2.0 - 1.0; // -1.0(์๋)์์ 1.0(์) ๊น์ง์ ๊น์ด ๊ฐ
let base_y = 0.0 + depth * (model.layers as f32 * 0.5 * model.spacing); // ๋ ์ด์ด์ ๊ธฐ๋ณธ y ์์น ๊ณ์ฐ (center_y=0.0)
// ์๋์ชฝ์ด ๋๊ป๊ฒ: depth๊ฐ -1.0์ผ ๋ ๋๊ป๊ฒ, 1.0์ผ ๋ ๊ฐ๋๊ฒ
let stroke_w = map_range(depth, -1.0, 1.0, MAX_STROKE_WEIGHT, MIN_STROKE_WEIGHT); // ๊น์ด์ ๋ฐ๋ผ ๋ผ์ธ ๋๊ป ๋งคํ
let alpha = map_range(depth, -1.0, 1.0, MIN_ALPHA, MAX_ALPHA); // ๊น์ด์ ๋ฐ๋ผ ํฌ๋ช
๋ ๋งคํ
let mut pts: Vec<Point2> = Vec::with_capacity(model.samples); // ์ํ ํฌ์ธํธ๋ฅผ ์ ์ฅํ ๋ฒกํฐ (์ฉ๋ ๋ฏธ๋ฆฌ ํ ๋น)
// ๊ฐ ๋ ์ด์ด์ ์ํ ํฌ์ธํธ ๊ณ์ฐ
for si in 0..model.samples { // 0๋ถํฐ ์ํ ์๋งํผ ๋ฐ๋ณต
let u = si as f32 / samples_denom; // 0.0์์ 1.0๊น์ง์ ์ ๊ทํ๋ x ์์น
let x = win.left() + u * win.w(); // ์ค์ x ์ขํ ๊ณ์ฐ
let v = i as f32 / layers_denom; // 0.0์์ 1.0๊น์ง์ ์ ๊ทํ๋ y ์์น (๋ ์ด์ด ์ธ๋ฑ์ค ๊ธฐ๋ฐ)
let v_flipped = 1.0 - v; // ์ด๋ฏธ์ง๋ฅผ ์ํ ๋ฐ์ ์ํค๊ธฐ ์ํด y ์ขํ ๋ณํ
// ํฝ์
์ขํ ๊ณ์ฐ (๋ฒ์ ์ฒดํฌ ํฌํจ)
let pixel_x = ((u * model.image_dimensions.x) as u32)
.clamp(0, model.image_dimensions.x as u32 - 1); // ์ด๋ฏธ์ง ๊ฒฝ๊ณ๋ฅผ ๋ฒ์ด๋์ง ์๋๋ก ํด๋จํ
let pixel_y = ((v_flipped * model.image_dimensions.y) as u32)
.clamp(0, model.image_dimensions.y as u32 - 1); // ์ด๋ฏธ์ง ๊ฒฝ๊ณ๋ฅผ ๋ฒ์ด๋์ง ์๋๋ก ํด๋จํ
// ํฝ์
์์ ๊ฐ์ ธ์ค๊ธฐ
let pixel_color = model.image_data.get_pixel(pixel_x, pixel_y); // ์ง์ ๋ ํฝ์
์ ์์ ๊ฐ ๊ฐ์ ธ์ค๊ธฐ
// ๋ฐ๊ธฐ ๊ณ์ฐ - sRGB ๊ฐ์ค์น ์ ์ฉ (R: 0.299, G: 0.587, B: 0.114)
let brightness = (0.299 * pixel_color[0] as f32
+ 0.587 * pixel_color[1] as f32
+ 0.114 * pixel_color[2] as f32) / 255.0; // 0.0์์ 1.0๊น์ง์ ๊ฐ์ผ๋ก ์ ๊ทํ
// ๋ฐ๊ธฐ์ ๋ฐ๋ฅธ ์งํญ ๋งตํ - ๋ฐ์ ์์ญ์์ ๋ ํฐ ์งํญ์ ๊ฐ์ง
let current_amplitude = map_range(brightness, 0.0, 1.0, 0.0, model.amplitude * 2.0);
let nx = (x as f64) * model.freq; // ๋
ธ์ด์ฆ x ์ขํ (์ค์ x ์ขํ์ ์ฃผํ์ ๊ณฑํ๊ธฐ)
let ny = (i as f64) * 0.05 + (t as f64) * (model.time_speed as f64); // ๋
ธ์ด์ฆ y ์ขํ (๋ ์ด์ด ์ธ๋ฑ์ค์ ์๊ฐ์ ๋ฐ๋ผ ๋ณํ)
let noise_val = model.perlin.get([nx, ny]); // Perlin ๋
ธ์ด์ฆ ๊ฐ ๊ฐ์ ธ์ค๊ธฐ (-1.0์์ 1.0 ์ฌ์ด)
let dy = noise_val as f32 * current_amplitude * (1.0 - depth.abs()); // y ๋ฐฉํฅ ๋ณ์ ๊ณ์ฐ
let edge_falloff = cubic_ease((u * 2.0 - 1.0).abs()); // ๊ฐ์ฅ์๋ฆฌ์์ ๋ถ๋๋ฝ๊ฒ ๊ฐ์ (u๋ฅผ -1~1 ๋ฒ์๋ก ๋ณํ ํ ์ ๋๊ฐ)
// ์ต์ข
y ์ขํ ๊ณ์ฐ
let final_y = base_y + dy * (1.0 - EDGE_FALLOFF_FACTOR * edge_falloff);
pts.push(pt2(x, final_y)); // ๊ณ์ฐ๋ ์ ์ ํฌ์ธํธ ๋ฒกํฐ์ ์ถ๊ฐ
}
// ๋ค๊ฐํ ๋ผ์ธ ๊ทธ๋ฆฌ๊ธฐ
draw.polyline() // ๋ค๊ฐํ ๋ผ์ธ ๊ทธ๋ฆฌ๊ธฐ ์์
.weight(stroke_w) // ๋ผ์ธ ๋๊ป ์ค์
.points(pts) // ๊ทธ๋ฆผ ํฌ์ธํธ๋ค ์ค์
.rgba(1.0, 1.0, 1.0, alpha); // RGB ์์๊ณผ ์ํ(ํฌ๋ช
๋) ์ค์
}
// ํ๋ ์์ ๊ทธ๋ฆฌ๊ธฐ - ํ๋ฉด์ ์ต์ข
๊ฒฐ๊ณผ ํ์
draw.to_frame(app, &frame).unwrap(); // ๊ทธ๋ฆฐ ๋ด์ฉ์ ํ๋ ์์ ์ ์ฉ
}
// ์
๋ฐฉ ๋ณด๊ฐ ํจ์ (cubic easing) - ๋ถ๋๋ฌ์ด ์ ํ์ ์ํ ๋ณด๊ฐ ํจ์
fn cubic_ease(x: f32) -> f32 {
let x = x.clamp(0.0, 1.0); // ์
๋ ฅ ๊ฐ์ 0.0๊ณผ 1.0 ์ฌ์ด๋ก ์ ํ
x * x * (3.0 - 2.0 * x) // ์
๋ฐฉ ๋ณด๊ฐ ๊ณต์ ์ ์ฉ
}