use rusb::UsbContext;
use std::time::Instant;
pub const MAX_POINTS_PER_FRAME: usize = 100_000;
#[inline(always)]
fn fast_inv_sqrt(x: f32) -> f32 {
let half = 0.5 * x;
let i = f32::to_bits(x);
let i = 0x5f3759df - (i >> 1);
let y = f32::from_bits(i);
y * (1.5 - half * y * y)
}
pub const DOLPHIN_D5_VID: u16 = 0x2BC5;
pub const DOLPHIN_D5_PID: u16 = 0x0615;
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct PointNormal {
pub x: f32,
pub y: f32,
pub z: f32,
pub nx: f32,
pub ny: f32,
pub nz: f32,
}
#[derive(Debug, Clone)]
pub struct DepthFrame {
pub points: Vec<PointNormal>,
pub timestamp_ms: u64,
pub frame_id: u32,
}
#[derive(Debug, Clone)]
pub struct CameraConfig {
pub voxel_size: f32,
pub max_depth: f32,
pub min_depth: f32,
pub max_points: usize,
pub normal_k: usize,
}
impl Default for CameraConfig {
fn default() -> Self {
Self {
voxel_size: 0.01, max_depth: 3.0,
min_depth: 0.1,
max_points: MAX_POINTS_PER_FRAME,
normal_k: 8,
}
}
}
pub trait DepthCameraDriver: Send {
fn init(&mut self) -> Result<(), CaptureError>;
fn capture_frame(&mut self) -> Result<DepthFrame, CaptureError>;
fn is_connected(&self) -> bool;
fn info(&self) -> String;
}
#[derive(Debug)]
pub enum CaptureError {
DeviceNotFound,
UsbError(String),
Timeout,
InvalidData(String),
NotInitialized,
}
impl std::fmt::Display for CaptureError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CaptureError::DeviceNotFound => write!(f, "Dolphin D5 Lite not found"),
CaptureError::UsbError(e) => write!(f, "USB error: {}", e),
CaptureError::Timeout => write!(f, "Frame capture timeout"),
CaptureError::InvalidData(e) => write!(f, "Invalid data: {}", e),
CaptureError::NotInitialized => write!(f, "Camera not initialized"),
}
}
}
pub struct DolphinD5Driver {
config: CameraConfig,
initialized: bool,
simulation_mode: bool,
frame_counter: u32,
start_time: Option<Instant>,
}
impl DolphinD5Driver {
pub fn new(config: CameraConfig) -> Self {
Self {
config,
initialized: false,
simulation_mode: false,
frame_counter: 0,
start_time: None,
}
}
pub fn is_simulation(&self) -> bool {
self.simulation_mode
}
pub fn voxel_downsample(points: &[PointNormal], voxel_size: f32) -> Vec<PointNormal> {
if voxel_size <= 0.0 || points.is_empty() {
return points.to_vec();
}
let inv_voxel = 1.0 / voxel_size;
let mut seen = std::collections::HashMap::with_capacity(points.len() / 4);
let mut result = Vec::with_capacity(points.len() / 4);
for p in points {
let vx = (p.x * inv_voxel).floor() as i32;
let vy = (p.y * inv_voxel).floor() as i32;
let vz = (p.z * inv_voxel).floor() as i32;
let key = (vx as i64) | ((vy as i64) << 21) | ((vz as i64) << 42);
if seen.insert(key, ()).is_none() {
result.push(*p);
}
}
result
}
pub fn estimate_normals(points: &mut [PointNormal], k: usize) {
if points.len() < 3 || k == 0 {
return;
}
let n = points.len();
for i in 0..n {
let p = points[i];
let prev = if i > 0 { i - 1 } else { i };
let next = if i + 1 < n { i + 1 } else { i };
let dx1 = points[next].x - p.x;
let dy1 = points[next].y - p.y;
let dz1 = points[next].z - p.z;
let dx2 = points[prev].x - p.x;
let dy2 = points[prev].y - p.y;
let dz2 = points[prev].z - p.z;
let nx = dy1 * dz2 - dz1 * dy2;
let ny = dz1 * dx2 - dx1 * dz2;
let nz = dx1 * dy2 - dy1 * dx2;
let len_sq = nx * nx + ny * ny + nz * nz;
if len_sq > 1e-16 {
let inv_len = fast_inv_sqrt(len_sq);
points[i].nx = nx * inv_len;
points[i].ny = ny * inv_len;
points[i].nz = nz * inv_len;
} else {
points[i].nx = 0.0;
points[i].ny = 1.0;
points[i].nz = 0.0;
}
}
}
}
impl DepthCameraDriver for DolphinD5Driver {
fn init(&mut self) -> Result<(), CaptureError> {
let context = rusb::Context::new()
.map_err(|e| CaptureError::UsbError(format!("USB context: {}", e)))?;
let device = context
.devices()
.map_err(|e| CaptureError::UsbError(format!("USB devices: {}", e)))?
.iter()
.find(|d| {
d.device_descriptor()
.map(|desc| {
desc.vendor_id() == DOLPHIN_D5_VID && desc.product_id() == DOLPHIN_D5_PID
})
.unwrap_or(false)
});
if device.is_none() {
self.simulation_mode = true;
self.initialized = true;
self.start_time = Some(Instant::now());
return Ok(());
}
self.initialized = true;
self.start_time = Some(Instant::now());
Ok(())
}
fn capture_frame(&mut self) -> Result<DepthFrame, CaptureError> {
if !self.initialized {
return Err(CaptureError::NotInitialized);
}
let timestamp_ms = self
.start_time
.map(|t| t.elapsed().as_millis() as u64)
.unwrap_or(0);
self.frame_counter += 1;
if self.simulation_mode {
let mut points = Vec::with_capacity(100);
let frame_f = self.frame_counter as f32 * 0.1;
for iy in 0..10 {
for ix in 0..10 {
let x = (ix as f32 - 4.5) * 0.1;
let z = (iy as f32 - 4.5) * 0.1;
let y = 1.0 + 0.1 * (x * 3.14 + frame_f).sin() * (z * 2.71 + frame_f).cos();
points.push(PointNormal {
x,
y,
z,
nx: 0.0,
ny: 1.0,
nz: 0.0,
});
}
}
return Ok(DepthFrame {
points,
timestamp_ms,
frame_id: self.frame_counter,
});
}
Ok(DepthFrame {
points: Vec::new(),
timestamp_ms,
frame_id: self.frame_counter,
})
}
fn is_connected(&self) -> bool {
self.initialized
}
fn info(&self) -> String {
format!(
"Dolphin D5 Lite (VID:{:04X} PID:{:04X}) voxel={}m max_depth={}m",
DOLPHIN_D5_VID, DOLPHIN_D5_PID, self.config.voxel_size, self.config.max_depth
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_voxel_downsample() {
let points = vec![
PointNormal {
x: 0.001,
y: 0.001,
z: 0.001,
..Default::default()
},
PointNormal {
x: 0.002,
y: 0.002,
z: 0.002,
..Default::default()
},
PointNormal {
x: 0.1,
y: 0.1,
z: 0.1,
..Default::default()
},
PointNormal {
x: 0.101,
y: 0.101,
z: 0.101,
..Default::default()
},
];
let result = DolphinD5Driver::voxel_downsample(&points, 0.01);
assert_eq!(result.len(), 2);
}
#[test]
fn test_estimate_normals() {
let mut points = vec![
PointNormal {
x: 0.0,
y: 0.0,
z: 0.0,
..Default::default()
},
PointNormal {
x: 1.0,
y: 0.0,
z: 0.0,
..Default::default()
},
PointNormal {
x: 0.0,
y: 1.0,
z: 0.0,
..Default::default()
},
];
DolphinD5Driver::estimate_normals(&mut points, 2);
assert!(points[1].nz.abs() > 0.5);
}
#[test]
fn test_config_default() {
let config = CameraConfig::default();
assert_eq!(config.max_points, MAX_POINTS_PER_FRAME);
assert!(config.voxel_size > 0.0);
}
#[test]
fn test_driver_info() {
let driver = DolphinD5Driver::new(CameraConfig::default());
let info = driver.info();
assert!(info.contains("Dolphin D5 Lite"));
}
#[test]
fn test_voxel_downsample_empty() {
let result = DolphinD5Driver::voxel_downsample(&[], 0.01);
assert!(result.is_empty());
}
#[test]
fn test_voxel_downsample_zero_voxel_size() {
let points = vec![PointNormal {
x: 1.0,
y: 2.0,
z: 3.0,
..Default::default()
}];
let result = DolphinD5Driver::voxel_downsample(&points, 0.0);
assert_eq!(result.len(), 1);
}
#[test]
fn test_estimate_normals_too_few_points() {
let mut points = vec![
PointNormal {
x: 0.0,
y: 0.0,
z: 0.0,
..Default::default()
},
PointNormal {
x: 1.0,
y: 0.0,
z: 0.0,
..Default::default()
},
];
DolphinD5Driver::estimate_normals(&mut points, 2);
assert_eq!(points[0].nx, 0.0);
}
#[test]
fn test_driver_not_initialized() {
let driver = DolphinD5Driver::new(CameraConfig::default());
assert!(!driver.is_connected());
assert!(!driver.is_simulation());
}
}