dreamwell-matter 1.0.0

DreamMatter benchmark — GPU physics materialization demo and profiler
Documentation
// Follow camera — third-person camera with exponential smoothing.
//
// Tracks a target position with configurable distance, height, and smoothing.
// Mouse drag controls yaw/pitch, scroll controls distance.

/// Third-person follow camera.
pub struct FollowCamera {
    /// Distance from target (meters).
    pub distance: f32,
    /// Height offset above target (meters).
    pub height: f32,
    /// Camera yaw (radians, 0 = looking along +X).
    pub yaw: f32,
    /// Camera pitch (radians, positive = looking down).
    pub pitch: f32,
    /// Exponential smoothing factor (0 = instant, 1 = never moves).
    pub smoothing: f32,
    /// Current smoothed camera position.
    pub position: [f32; 3],
}

impl Default for FollowCamera {
    fn default() -> Self {
        Self {
            distance: 8.0,
            height: 3.0,
            yaw: std::f32::consts::FRAC_PI_2, // PI/2: camera behind player (+Z)
            pitch: 0.3,
            smoothing: 0.1,
            position: [0.0, 3.0, 8.0],
        }
    }
}

impl FollowCamera {
    pub fn new(distance: f32, height: f32) -> Self {
        Self {
            distance,
            height,
            ..Default::default()
        }
    }

    /// Update camera position to follow a target with exponential smoothing.
    /// Returns (camera_position, look_target).
    pub fn update(&mut self, target: [f32; 3], dt: f32) -> ([f32; 3], [f32; 3]) {
        // Compute ideal camera position from yaw/pitch/distance.
        let cos_pitch = self.pitch.cos();
        let sin_pitch = self.pitch.sin();
        let cos_yaw = self.yaw.cos();
        let sin_yaw = self.yaw.sin();

        let ideal = [
            target[0] + self.distance * cos_pitch * cos_yaw,
            target[1] + self.height + self.distance * sin_pitch,
            target[2] + self.distance * cos_pitch * sin_yaw,
        ];

        // Exponential lerp toward ideal position.
        let alpha = 1.0 - (-dt / self.smoothing.max(0.001)).exp();
        self.position[0] += (ideal[0] - self.position[0]) * alpha;
        self.position[1] += (ideal[1] - self.position[1]) * alpha;
        self.position[2] += (ideal[2] - self.position[2]) * alpha;

        (self.position, target)
    }

    /// Handle mouse drag input (dx, dy in screen pixels).
    pub fn handle_mouse_drag(&mut self, dx: f32, dy: f32) {
        let sensitivity = 0.005;
        self.yaw += dx * sensitivity;
        self.pitch = (self.pitch + dy * sensitivity).clamp(-1.2, 1.2);
    }

    /// Handle scroll wheel input (delta in scroll units).
    pub fn handle_scroll(&mut self, delta: f32) {
        self.distance = (self.distance - delta * 0.5).clamp(2.0, 30.0);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn camera_converges_on_target() {
        let mut camera = FollowCamera::new(10.0, 5.0);
        camera.smoothing = 0.05;
        let target = [10.0, 0.0, 10.0];

        // Run many frames to converge.
        for _ in 0..200 {
            camera.update(target, 0.016);
        }

        // Camera should be near the ideal position relative to target.
        let dx = camera.position[0] - target[0];
        let dz = camera.position[2] - target[2];
        let dist = (dx * dx + dz * dz).sqrt();
        assert!(
            (dist - camera.distance).abs() < 1.0,
            "camera should converge: dist={dist}, expected ~{}",
            camera.distance
        );
    }
}