


Cargo.toml ์์กด์ฑ ์ถ๊ฐ:
[dependencies]
nannou = "0.19"
rand = "0.8"
use nannou::prelude::*;
use rand::prelude::*;
// ์ฌ์ฉ์ ์ค์ ์์
const NUM_CANDIDATES: usize = 2000; // ํ๋ณด ์ค์ฌ์ ์ํ ์
const R_MIN: f32 = 2.0; // ์ต์ ์ ๋ฐ์ง๋ฆ
const R_MAX: f32 = 50.0; // ์ต๋ ์ ๋ฐ์ง๋ฆ
const RELAX_ITERS: usize = 30; // ์์น ์ต์ ํ ๋ฐ๋ณต ํ์
const RELAX_STEP: f32 = 0.5; // ์ต์ ํ ์ ์ด๋ ์คํ
ํฌ๊ธฐ
const SEED: u64 = 42; // ๋๋ค ์๋
const EDGE_EPS: f32 = 1e-6; // ๊ฒฝ๊ณ์ ํ์ ํ์ฉ ์ค์ฐจ
#[derive(Clone, Copy, Debug)]
struct Circle {
position: Vec2,
radius: f32,
}
struct Model {
circles: Vec<Circle>,
polygon: Vec<Vec2>,
_window: window::Id,
}
fn main() {
nannou::app(model).update(update).run();
}
fn model(app: &App) -> Model {
let _window = app.new_window()
.size(1080, 1080)
.view(view)
.build()
.unwrap();
// ๊ธฐ๋ณธ ๋ค๊ฐํ ์ ์ (์ฌ๊ฐํ)
let polygon = vec![
Vec2::new(-540.0, -540.0),
Vec2::new(540.0, -540.0),
Vec2::new(540.0, 540.0),
Vec2::new(-540.0, 540.0),
];
// Circle packing ์คํ
let circles = run_circle_packing(&polygon);
Model {
circles,
polygon,
_window,
}
}
fn update(_app: &App, _model: &mut Model, _update: Update) {
// ์
๋ฐ์ดํธ ๋ก์ง (ํ์์ ์ถ๊ฐ)
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
// ๋ฐฐ๊ฒฝ์ ๊ฒ์์์ผ๋ก ์ค์
draw.background().color(BLACK);
// ๋ค๊ฐํ ๊ฒฝ๊ณ์ ๊ทธ๋ฆฌ๊ธฐ (์ต์
)
for i in 0..model.polygon.len() {
let start = model.polygon[i];
let end = model.polygon[(i + 1) % model.polygon.len()];
draw.line()
.start(start)
.end(end)
.color(GRAY)
.stroke_weight(1.0);
}
// ์๋ค ๊ทธ๋ฆฌ๊ธฐ (ํฐ์)
for circle in &model.circles {
draw.ellipse()
.xy(circle.position)
.radius(circle.radius)
// .color(WHITE)
.color(hsv(random_range(0.0, 1.0), 0.5, 0.9))
.stroke_weight(0.0);
}
draw.to_frame(app, &frame).unwrap();
}
fn run_circle_packing(polygon: &[Vec2]) -> Vec<Circle> {
println!("์ํ ํ๋ณด ์์ฑ ์ค...");
let candidates = sample_candidates(polygon, NUM_CANDIDATES);
println!("ํ๋ณด ์: {}", candidates.len());
println!("๊ทธ๋ฆฌ๋ ํจํน ์ํ ์ค...");
let mut circles = greedy_pack(polygon, &candidates, R_MIN, R_MAX);
println!("์ด๊ธฐ ๋ฐฐ์น๋ ์ ์: {}", circles.len());
if RELAX_ITERS > 0 && !circles.is_empty() {
println!("๋ก์ปฌ ๋ฆด๋์ธ์ด์
์ํ ์ค...");
circles = relax_circles(polygon, circles, RELAX_ITERS, RELAX_STEP);
}
println!("์๋ฃ. ์์ฑ๋ ์ ์: {}", circles.len());
circles
}
// ์ ์์ ์ ๋ถ๊น์ง์ ์ต๋จ๊ฑฐ๋ฆฌ ๊ณ์ฐ (2D)
fn dist_point_segment_2d(p: Vec2, a: Vec2, b: Vec2) -> f32 {
let pa = p - a;
let ba = b - a;
let ba_len_squared = ba.length_squared();
if ba_len_squared == 0.0 {
return (p - a).length();
}
let t = (pa.dot(ba) / ba_len_squared).clamp(0.0, 1.0);
let proj = a + ba * t;
(p - proj).length()
}
// ์ ์ด ๋ค๊ฐํ ๋ด๋ถ์ ์๋์ง ํ๋จ (ray casting + edge check)
fn point_in_poly_2d(pt: Vec2, poly_verts: &[Vec2]) -> bool {
let n = poly_verts.len();
if n < 3 {
return false;
}
// 1) ๊ฒฝ๊ณ์ ์ฒดํฌ
for i in 0..n {
let a = poly_verts[i];
let b = poly_verts[(i + 1) % n];
if dist_point_segment_2d(pt, a, b) <= EDGE_EPS {
return true;
}
}
// 2) Ray casting ์๊ณ ๋ฆฌ์ฆ
let mut inside = false;
let x = pt.x;
let y = pt.y;
for i in 0..n {
let xi = poly_verts[i].x;
let yi = poly_verts[i].y;
let xj = poly_verts[(i + 1) % n].x;
let yj = poly_verts[(i + 1) % n].y;
if (yi > y) != (yj > y) {
let x_int = xi + (y - yi) * (xj - xi) / (yj - yi);
if x < x_int {
inside = !inside;
}
}
}
inside
}
// ์ ์์ ๋ค๊ฐํ ๊ฒฝ๊ณ์ ๊น์ง์ ์ต๋จ๊ฑฐ๋ฆฌ
fn distance_to_boundary(p: Vec2, poly_verts: &[Vec2]) -> f32 {
let mut min_dist = f32::INFINITY;
let n = poly_verts.len();
for i in 0..n {
let a = poly_verts[i];
let b = poly_verts[(i + 1) % n];
let d = dist_point_segment_2d(p, a, b);
if d < min_dist {
min_dist = d;
}
}
min_dist
}
// ์ ์์ ๊ธฐ์กด ์๋ค๊น์ง์ ์ต์ ์ฌ์ ๊ฑฐ๋ฆฌ
fn min_clearance_to_circles(p: Vec2, circles: &[Circle]) -> f32 {
if circles.is_empty() {
return f32::INFINITY;
}
let mut min_clear = f32::INFINITY;
for circle in circles {
let d = (p - circle.position).length() - circle.radius;
if d < min_clear {
min_clear = d;
}
}
min_clear
}
// ๋ค๊ฐํ ๋ด๋ถ์์ ๋ฌด์์ ํ๋ณด์ ์์ฑ
fn sample_candidates(poly_verts: &[Vec2], num_candidates: usize) -> Vec<Vec2> {
let mut rng = StdRng::seed_from_u64(SEED);
// AABB ๊ณ์ฐ
let xs: Vec<f32> = poly_verts.iter().map(|v| v.x).collect();
let ys: Vec<f32> = poly_verts.iter().map(|v| v.y).collect();
let min_x = xs.iter().fold(f32::INFINITY, |a, &b| a.min(b));
let max_x = xs.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
let min_y = ys.iter().fold(f32::INFINITY, |a, &b| a.min(b));
let max_y = ys.iter().fold(f32::NEG_INFINITY, |a, &b| a.max(b));
let mut candidates = Vec::new();
let mut tries = 0;
let max_tries = num_candidates * 20;
while candidates.len() < num_candidates && tries < max_tries {
let x = rng.gen_range(min_x..max_x);
let y = rng.gen_range(min_y..max_y);
let pt = Vec2::new(x, y);
if point_in_poly_2d(pt, poly_verts) {
candidates.push(pt);
}
tries += 1;
}
candidates
}
// ๊ทธ๋ฆฌ๋ ํจํน ์๊ณ ๋ฆฌ์ฆ
fn greedy_pack(poly_verts: &[Vec2], candidates: &[Vec2], r_min: f32, r_max: f32) -> Vec<Circle> {
// ํ๋ณด์ ๋ค์ ๊ฐ๋ฅํ ๋ฐ์ง๋ฆ ๊ธฐ์ค์ผ๋ก ์ ๋ ฌ
let mut cand_info: Vec<(Vec2, f32)> = candidates.iter()
.map(|&p| {
let r_bound = distance_to_boundary(p, poly_verts).min(r_max);
(p, r_bound)
})
.filter(|(_, r_bound)| *r_bound >= r_min)
.collect();
// ๋ฐ์ง๋ฆ ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌ
cand_info.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
let mut circles = Vec::new();
for (p, r_bound) in cand_info {
if circles.is_empty() {
circles.push(Circle {
position: p,
radius: r_bound,
});
continue;
}
let min_clear = min_clearance_to_circles(p, &circles);
let allowed = r_bound.min(min_clear);
if allowed >= r_min {
circles.push(Circle {
position: p,
radius: allowed,
});
}
}
circles
}
// ๋ก์ปฌ ๋ฆด๋์ธ์ด์
์ผ๋ก ์์น ์ต์ ํ
fn relax_circles(poly_verts: &[Vec2], mut circles: Vec<Circle>, iters: usize, step: f32) -> Vec<Circle> {
if iters == 0 || circles.is_empty() {
return circles;
}
let mut rng = StdRng::seed_from_u64(SEED + 1);
let n = circles.len();
// 8๋ฐฉํฅ ๋ฒกํฐ
let directions = vec![
Vec2::new(1.0, 0.0),
Vec2::new(-1.0, 0.0),
Vec2::new(0.0, 1.0),
Vec2::new(0.0, -1.0),
Vec2::new(0.707, 0.707),
Vec2::new(-0.707, 0.707),
Vec2::new(0.707, -0.707),
Vec2::new(-0.707, -0.707),
];
for _ in 0..iters {
let mut indices: Vec<usize> = (0..n).collect();
indices.shuffle(&mut rng);
for &i in &indices {
let pos = circles[i].position;
let r = circles[i].radius;
let mut best_pos = pos;
let cur_bound = distance_to_boundary(pos, poly_verts);
let other_circles: Vec<Circle> = circles.iter().enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, c)| *c)
.collect();
let cur_other = min_clearance_to_circles(pos, &other_circles);
let mut best_clear = cur_bound.min(cur_other);
for &dir in &directions {
let random_factor = rng.gen_range(0.0..1.0f32);
let move_dist = step * (0.5 + random_factor);
let cand = pos + dir.normalize() * move_dist;
if !point_in_poly_2d(cand, poly_verts) {
continue;
}
let bound_clear = distance_to_boundary(cand, poly_verts);
if bound_clear < r {
continue;
}
let other_clear = min_clearance_to_circles(cand, &other_circles);
if other_clear < 0.0 {
continue;
}
let total_clear = bound_clear.min(other_clear);
if total_clear > best_clear + 1e-6 {
best_clear = total_clear;
best_pos = cand;
}
}
circles[i].position = best_pos;
}
}
circles
}