Files
hcie-rust-v3.05/hcie-brush-engine/src/dynamics.rs
T
phantom b09795ccff Implement stable serialization and restoration for dock layouts, including auto-hidden and floating panels
- Add `persistence.rs` for stable serialization of dock layouts without runtime identifiers.
- Introduce `preview.rs` for geometry handling of dock drop previews.
- Create `sizing.rs` to manage content-aware sizing policies and constraint solving for dock panels.
- Implement `welcome.rs` to provide a welcome surface when no documents are open, featuring New and Open actions.
2026-07-16 22:10:22 +03:00

118 lines
3.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Brush dynamics — pressure, velocity, tilt mapping for brush strokes.
use hcie_protocol::BrushTip;
/// Per-stroke dynamic parameters computed from input state.
#[derive(Debug, Clone, Copy, Default)]
pub struct StrokeDynamics {
pub pressure: f32, // 0.01.0
pub velocity: f32, // Pixels per frame
pub tilt: (f32, f32), // (x_tilt, y_tilt) in degrees
}
/// Compute effective brush size from base size, dynamics, and pressure.
pub fn effective_size(base_size: f32, dynamics: &StrokeDynamics, pressure_sensitive: bool) -> f32 {
if !pressure_sensitive {
return base_size;
}
let p = dynamics.pressure.clamp(0.0, 1.0);
// Quadratic falloff for natural feel
let factor = p * p * (3.0 - 2.0 * p); // smoothstep
base_size * factor.max(0.1)
}
/// Compute effective opacity from base opacity, dynamics, and pressure.
pub fn effective_opacity(
base_opacity: f32,
dynamics: &StrokeDynamics,
pressure_sensitive: bool,
) -> f32 {
if !pressure_sensitive {
return base_opacity;
}
let p = dynamics.pressure.clamp(0.0, 1.0);
base_opacity * p
}
/// Compute effective flow from base flow, dynamics, and pressure.
pub fn effective_flow(base_flow: f32, dynamics: &StrokeDynamics, pressure_sensitive: bool) -> f32 {
if !pressure_sensitive {
return base_flow;
}
let p = dynamics.pressure.clamp(0.0, 1.0);
base_flow * p
}
/// Compute velocity-based size modifier (faster = thinner).
pub fn velocity_size_modifier(velocity: f32) -> f32 {
// At 0 velocity = 1.0, at 100 px/frame = 0.5
let factor = 1.0 - (velocity / 200.0).clamp(0.0, 0.5);
factor.clamp(0.5, 1.0)
}
/// Build a `BrushTip` with dynamics applied.
pub fn build_dynamic_tip(
base: &BrushTip,
dynamics: &StrokeDynamics,
pressure_size: bool,
pressure_opacity: bool,
) -> BrushTip {
let mut tip = base.clone();
tip.size = effective_size(base.size, dynamics, pressure_size);
tip.opacity = effective_opacity(base.opacity, dynamics, pressure_opacity);
tip
}
/// Simulate tablet pressure from mouse velocity (fallback for non-tablet input).
/// Slower movement = higher pressure.
pub fn simulate_pressure_from_velocity(velocity: f32, max_velocity: f32) -> f32 {
let t = (velocity / max_velocity).clamp(0.0, 1.0);
// Inverse: slow = high pressure
(1.0 - t * t).clamp(0.2, 1.0)
}
/// Track velocity from consecutive points.
#[derive(Debug, Clone, Default)]
pub struct VelocityTracker {
last_pos: Option<(f32, f32)>,
last_time: Option<f32>,
}
impl VelocityTracker {
pub fn new() -> Self {
Self::default()
}
pub fn update(&mut self, x: f32, y: f32, time: f32) -> f32 {
let velocity = match (self.last_pos, self.last_time) {
(Some((lx, ly)), Some(lt)) => {
let dt = (time - lt).max(0.001);
let dist = ((x - lx).powi(2) + (y - ly).powi(2)).sqrt();
dist / dt
}
_ => 0.0,
};
self.last_pos = Some((x, y));
self.last_time = Some(time);
velocity
}
pub fn reset(&mut self) {
self.last_pos = None;
self.last_time = None;
}
}
/// Add jitter to a color for textured brushes.
pub fn jitter_color(color: [u8; 4], variation: u8, rng: &mut rand::rngs::ThreadRng) -> [u8; 4] {
use rand::Rng;
let jitter = variation as f32 / 255.0;
let r = (color[0] as f32 * (1.0 - jitter) + rng.gen::<f32>() * jitter * 255.0).clamp(0.0, 255.0)
as u8;
let g = (color[1] as f32 * (1.0 - jitter) + rng.gen::<f32>() * jitter * 255.0).clamp(0.0, 255.0)
as u8;
let b = (color[2] as f32 * (1.0 - jitter) + rng.gen::<f32>() * jitter * 255.0).clamp(0.0, 255.0)
as u8;
[r, g, b, color[3]]
}