#[derive(Debug, Clone, Copy)]
pub struct BlobState {
pub x: f32,
pub y: f32,
pub vx: f32,
pub vy: f32,
pub size: f32,
pub opacity: f32,
}
#[derive(Debug, Clone)]
#[derive(Default)]
pub struct BlobCursorState {
pub blobs: Vec<BlobState>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlobType {
Circle,
Square,
}
pub struct BlobCursor {
pub count: usize,
pub sizes: Vec<f32>,
pub opacities: Vec<f32>,
pub blob_type: BlobType,
pub fast_duration: f64,
pub slow_duration: f64,
pub inner_sizes: Vec<f32>,
}
impl Default for BlobCursor {
fn default() -> Self {
Self {
count: 3,
sizes: vec![60.0, 125.0, 75.0],
opacities: vec![0.6, 0.6, 0.6],
blob_type: BlobType::Circle,
fast_duration: 0.1,
slow_duration: 0.5,
inner_sizes: vec![20.0, 35.0, 25.0],
}
}
}
impl BlobCursor {
pub fn new() -> Self {
Self::default()
}
pub fn with_count(mut self, count: usize) -> Self {
self.count = count;
self
}
pub fn with_sizes(mut self, sizes: Vec<f32>) -> Self {
self.sizes = sizes;
self
}
pub fn with_opacities(mut self, opacities: Vec<f32>) -> Self {
self.opacities = opacities;
self
}
pub fn with_blob_type(mut self, blob_type: BlobType) -> Self {
self.blob_type = blob_type;
self
}
pub fn with_durations(mut self, fast: f64, slow: f64) -> Self {
self.fast_duration = fast;
self.slow_duration = slow;
self
}
pub fn with_inner_sizes(mut self, inner_sizes: Vec<f32>) -> Self {
self.inner_sizes = inner_sizes;
self
}
pub fn init_state(&self, initial_x: f32, initial_y: f32) -> BlobCursorState {
let blobs = (0..self.count)
.map(|i| BlobState {
x: initial_x,
y: initial_y,
vx: 0.0,
vy: 0.0,
size: self.sizes.get(i).copied().unwrap_or(60.0),
opacity: self.opacities.get(i).copied().unwrap_or(0.6),
})
.collect();
BlobCursorState { blobs }
}
pub fn update(&self, state: &mut BlobCursorState, cursor_x: f32, cursor_y: f32, dt: f64) {
for (i, blob) in state.blobs.iter_mut().enumerate() {
let is_lead = i == 0;
let duration = if is_lead {
self.fast_duration
} else {
self.slow_duration
};
let decay_rate = 4.0;
let factor = 1.0 - (-dt / (duration / decay_rate)).exp();
let factor = factor as f32;
let dx = cursor_x - blob.x;
let dy = cursor_y - blob.y;
blob.x += dx * factor;
blob.y += dy * factor;
blob.vx = dx * factor / dt as f32;
blob.vy = dy * factor / dt as f32;
}
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct GooeyFilter {
pub std_deviation: f32,
pub color_matrix: String,
}
impl Default for GooeyFilter {
fn default() -> Self {
Self {
std_deviation: 30.0,
color_matrix: "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_blob_cursor_init() {
let cursor = BlobCursor::new().with_count(3);
let state = cursor.init_state(100.0, 200.0);
assert_eq!(state.blobs.len(), 3);
for blob in &state.blobs {
assert_eq!(blob.x, 100.0);
assert_eq!(blob.y, 200.0);
assert_eq!(blob.vx, 0.0);
assert_eq!(blob.vy, 0.0);
}
}
#[test]
fn test_blob_sizes() {
let cursor = BlobCursor::new().with_sizes(vec![50.0, 100.0, 75.0]);
let state = cursor.init_state(0.0, 0.0);
assert_eq!(state.blobs[0].size, 50.0);
assert_eq!(state.blobs[1].size, 100.0);
assert_eq!(state.blobs[2].size, 75.0);
}
#[test]
fn test_blob_opacities() {
let cursor = BlobCursor::new().with_opacities(vec![0.5, 0.7, 0.9]);
let state = cursor.init_state(0.0, 0.0);
assert_eq!(state.blobs[0].opacity, 0.5);
assert_eq!(state.blobs[1].opacity, 0.7);
assert_eq!(state.blobs[2].opacity, 0.9);
}
#[test]
fn test_blob_update_moves_towards_cursor() {
let cursor = BlobCursor::new().with_count(2);
let mut state = cursor.init_state(0.0, 0.0);
cursor.update(&mut state, 100.0, 100.0, 0.016);
assert!(state.blobs[0].x > 0.0);
assert!(state.blobs[0].y > 0.0);
assert!(state.blobs[0].x < 100.0); assert!(state.blobs[0].y < 100.0);
let lead_dist = state.blobs[0].x * state.blobs[0].x + state.blobs[0].y * state.blobs[0].y;
let trail_dist =
state.blobs[1].x * state.blobs[1].x + state.blobs[1].y * state.blobs[1].y;
assert!(lead_dist > trail_dist);
}
#[test]
fn test_blob_convergence() {
let cursor = BlobCursor::new().with_count(1);
let mut state = cursor.init_state(0.0, 0.0);
for _ in 0..1000 {
cursor.update(&mut state, 100.0, 100.0, 0.016);
}
assert!((state.blobs[0].x - 100.0).abs() < 0.1);
assert!((state.blobs[0].y - 100.0).abs() < 0.1);
}
}