
📝 Rust Code
use nannou::prelude::*;
use nannou::noise::{NoiseFn, Perlin};
use std::fs;
const TEXT: &str = "Bamgasi";
const FONT_SIZE: u32 = 200;
const WAVE_AMPLITUDE: f32 = 30.0;
const WAVE_FREQUENCY: f32 = 0.5;
const WAVE_SPEED: f32 = 2.0;
const NOISE_SCALE: f64 = 15.0;
const NOISE_FREQUENCY: f64 = 0.03;
const CHAR_SPACING: f32 = 100.0;
struct Model {
font: text::Font,
noise: Perlin,
}
struct CharacterInfo {
character: char,
base_x: f32,
index: usize,
}
fn main() {
nannou::app(model).update(update).view(view).run();
}
fn model(app: &App) -> Model {
app.new_window()
.size(800, 800)
.title("Wave Distortion Typography")
.view(view)
.build()
.unwrap();
let assets = app.assets_path().unwrap();
let font_path = assets.join("fonts").join("NotoSans-ExtraBold.ttf");
let font_data = fs::read(font_path).expect("폰트 파일을 읽을 수 없습니다.");
let font = text::Font::from_bytes(font_data).expect("폰트를 불러올 수 없습니다.");
Model {
font,
noise: Perlin::new(),
}
}
fn update(_app: &App, _model: &mut Model, _update: Update) {}
fn get_character_positions(text: &str) -> Vec<CharacterInfo> {
let char_count = text.chars().count();
let total_width = (char_count - 1) as f32 * CHAR_SPACING;
let start_x = -total_width / 2.0;
text.chars()
.enumerate()
.map(|(i, ch)| CharacterInfo {
character: ch,
base_x: start_x + (i as f32 * CHAR_SPACING),
index: i,
})
.collect()
}
fn calculate_distorted_position(
base_x: f32,
index: usize,
time: f32,
noise: &Perlin,
) -> (f32, f32) {
let wave_phase = (base_x * WAVE_FREQUENCY) + (time * WAVE_SPEED);
let wave_y = wave_phase.sin() * WAVE_AMPLITUDE;
let vertical_phase = (time * 1.5) + (index as f32 * 0.5);
let vertical_wave = vertical_phase.sin() * 15.0;
let noise_x = noise.get([
base_x as f64 * NOISE_FREQUENCY,
time as f64 * 0.5,
0.0,
]) * NOISE_SCALE;
let noise_y = noise.get([
base_x as f64 * NOISE_FREQUENCY,
time as f64 * 0.5,
1000.0,
]) * NOISE_SCALE;
let final_x = base_x + noise_x as f32;
let final_y = wave_y + vertical_wave + noise_y as f32;
(final_x, final_y)
}
fn calculate_rotation(base_x: f32, time: f32) -> f32 {
let rotation_phase = (base_x * 0.01) + (time * 1.2);
rotation_phase.sin() * 0.15
}
fn calculate_scale(index: usize, time: f32) -> f32 {
let scale_phase = (time * 2.0) + (index as f32 * 0.3);
1.0 + (scale_phase.sin() * 0.1)
}
fn calculate_opacity(base_x: f32, time: f32, noise: &Perlin) -> f32 {
let opacity_noise = noise.get([
base_x as f64 * 0.01,
time as f64 * 0.8,
2000.0,
]);
(0.7 + (opacity_noise * 0.3) as f32).clamp(0.5, 1.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.02, 1.0));
} else {
draw.rect()
.w_h(800.0, 800.0)
.color(hsla(0.0, 0.0, 0.02, 0.05));
}
let time = app.time;
let characters = get_character_positions(TEXT);
for char_info in characters {
let (x, y) = calculate_distorted_position(
char_info.base_x,
char_info.index,
time,
&model.noise,
);
let rotation = calculate_rotation(char_info.base_x, time);
let scale = calculate_scale(char_info.index, time);
let opacity = calculate_opacity(char_info.base_x, time, &model.noise);
let hue = (time * 0.1 + char_info.index as f32 * 0.05) % 1.0;
let color = hsla(hue, 0.7, 0.5, opacity);
draw.text(&char_info.character.to_string())
.font(model.font.clone())
.font_size((FONT_SIZE as f32 * scale) as u32)
.no_line_wrap()
.x(x)
.y(y)
.rotate(rotation)
.color(color);
}
draw.to_frame(app, &frame).unwrap();
}