#![allow(dead_code)]
use crate::{Result, VisionError};
use std::collections::HashMap;
use torsh_core::dtype::DType;
use torsh_core::DeviceType;
use torsh_tensor::Tensor;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point3D {
pub x: f32,
pub y: f32,
pub z: f32,
pub color: Option<[u8; 3]>,
pub normal: Option<[f32; 3]>,
}
impl Point3D {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self {
x,
y,
z,
color: None,
normal: None,
}
}
pub fn with_color(x: f32, y: f32, z: f32, color: [u8; 3]) -> Self {
Self {
x,
y,
z,
color: Some(color),
normal: None,
}
}
pub fn with_color_and_normal(x: f32, y: f32, z: f32, color: [u8; 3], normal: [f32; 3]) -> Self {
Self {
x,
y,
z,
color: Some(color),
normal: Some(normal),
}
}
pub fn position(&self) -> [f32; 3] {
[self.x, self.y, self.z]
}
pub fn distance_to(&self, other: &Point3D) -> f32 {
let dx = self.x - other.x;
let dy = self.y - other.y;
let dz = self.z - other.z;
(dx * dx + dy * dy + dz * dz).sqrt()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Triangle3D {
pub vertices: [usize; 3],
pub normal: Option<[f32; 3]>,
pub material_id: Option<u32>,
}
impl Triangle3D {
pub fn new(v0: usize, v1: usize, v2: usize) -> Self {
Self {
vertices: [v0, v1, v2],
normal: None,
material_id: None,
}
}
pub fn with_normal(v0: usize, v1: usize, v2: usize, normal: [f32; 3]) -> Self {
Self {
vertices: [v0, v1, v2],
normal: Some(normal),
material_id: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct BoundingBox3D {
pub center: [f32; 3],
pub dimensions: [f32; 3],
pub rotation: [f32; 3],
pub label: String,
pub confidence: f32,
pub color: [u8; 3],
}
impl BoundingBox3D {
pub fn new(
center: [f32; 3],
dimensions: [f32; 3],
rotation: [f32; 3],
label: String,
confidence: f32,
) -> Self {
Self {
center,
dimensions,
rotation,
label,
confidence,
color: [255, 0, 0], }
}
pub fn with_color(mut self, color: [u8; 3]) -> Self {
self.color = color;
self
}
pub fn corners(&self) -> [Point3D; 8] {
let [cx, cy, cz] = self.center;
let [w, h, d] = self.dimensions;
let [roll, pitch, yaw] = self.rotation;
let hw = w / 2.0;
let hh = h / 2.0;
let hd = d / 2.0;
let local_corners = [
[-hw, -hh, -hd],
[hw, -hh, -hd],
[hw, hh, -hd],
[-hw, hh, -hd],
[-hw, -hh, hd],
[hw, -hh, hd],
[hw, hh, hd],
[-hw, hh, hd],
];
let mut corners = [Point3D::new(0.0, 0.0, 0.0); 8];
for (i, &[x, y, z]) in local_corners.iter().enumerate() {
let rotated = rotate_point([x, y, z], roll, pitch, yaw);
corners[i] = Point3D::with_color(
rotated[0] + cx,
rotated[1] + cy,
rotated[2] + cz,
self.color,
);
}
corners
}
pub fn volume(&self) -> f32 {
self.dimensions[0] * self.dimensions[1] * self.dimensions[2]
}
pub fn contains_point(&self, point: Point3D) -> bool {
let local_point = [
point.x - self.center[0],
point.y - self.center[1],
point.z - self.center[2],
];
let local_rotated = rotate_point(
local_point,
-self.rotation[0],
-self.rotation[1],
-self.rotation[2],
);
let [hw, hh, hd] = [
self.dimensions[0] / 2.0,
self.dimensions[1] / 2.0,
self.dimensions[2] / 2.0,
];
local_rotated[0].abs() <= hw && local_rotated[1].abs() <= hh && local_rotated[2].abs() <= hd
}
}
#[derive(Debug, Clone)]
pub struct PointCloud3D {
pub points: Vec<Point3D>,
pub metadata: PointCloudMetadata,
}
#[derive(Debug, Clone)]
pub struct PointCloudMetadata {
pub num_points: usize,
pub bounds: Option<BoundingBox3D>,
pub source: String,
pub created_at: std::time::SystemTime,
}
impl PointCloud3D {
pub fn new(points: Vec<Point3D>) -> Self {
let num_points = points.len();
let bounds = if !points.is_empty() {
Some(Self::calculate_bounds(&points))
} else {
None
};
Self {
points,
metadata: PointCloudMetadata {
num_points,
bounds,
source: "unknown".to_string(),
created_at: std::time::SystemTime::now(),
},
}
}
pub fn from_tensor(tensor: &Tensor) -> Result<Self> {
if tensor.ndim() != 2 {
return Err(VisionError::InvalidShape(
"Point cloud tensor must be 2D".to_string(),
));
}
let shape = tensor.shape();
let num_points = shape.dims()[0];
let num_features = shape.dims()[1];
if num_features != 3 && num_features != 6 {
return Err(VisionError::InvalidShape(
"Point cloud tensor must have 3 or 6 features (XYZ or XYZRGB)".to_string(),
));
}
let mut points = Vec::with_capacity(num_points);
for _i in 0..num_points {
let x = 0.0; let y = 0.0; let z = 0.0;
let point = if num_features == 6 {
let r = 0u8; let g = 0u8; let b = 0u8; Point3D::with_color(x, y, z, [r, g, b])
} else {
Point3D::new(x, y, z)
};
points.push(point);
}
Ok(Self::new(points))
}
pub fn to_tensor(&self) -> Result<Tensor> {
let num_points = self.points.len();
let has_color = self.points.iter().any(|p| p.color.is_some());
let num_features = if has_color { 6 } else { 3 };
let mut data = Vec::with_capacity(num_points * num_features);
for point in &self.points {
data.push(point.x);
data.push(point.y);
data.push(point.z);
if has_color {
if let Some(color) = point.color {
data.push(color[0] as f32 / 255.0);
data.push(color[1] as f32 / 255.0);
data.push(color[2] as f32 / 255.0);
} else {
data.push(0.0);
data.push(0.0);
data.push(0.0);
}
}
}
Ok(Tensor::from_data(
data,
vec![num_points, num_features],
DeviceType::Cpu,
)?)
}
fn calculate_bounds(points: &[Point3D]) -> BoundingBox3D {
if points.is_empty() {
return BoundingBox3D::new([0.0; 3], [0.0; 3], [0.0; 3], "empty".to_string(), 1.0);
}
let mut min_x = f32::INFINITY;
let mut max_x = f32::NEG_INFINITY;
let mut min_y = f32::INFINITY;
let mut max_y = f32::NEG_INFINITY;
let mut min_z = f32::INFINITY;
let mut max_z = f32::NEG_INFINITY;
for point in points {
min_x = min_x.min(point.x);
max_x = max_x.max(point.x);
min_y = min_y.min(point.y);
max_y = max_y.max(point.y);
min_z = min_z.min(point.z);
max_z = max_z.max(point.z);
}
let center = [
(min_x + max_x) / 2.0,
(min_y + max_y) / 2.0,
(min_z + max_z) / 2.0,
];
let dimensions = [max_x - min_x, max_y - min_y, max_z - min_z];
BoundingBox3D::new(center, dimensions, [0.0; 3], "bounds".to_string(), 1.0)
}
pub fn add_point(&mut self, point: Point3D) {
self.points.push(point);
self.metadata.num_points = self.points.len();
self.metadata.bounds = Some(Self::calculate_bounds(&self.points));
}
pub fn remove_point(&mut self, index: usize) -> Result<Point3D> {
if index >= self.points.len() {
return Err(VisionError::InvalidArgument(format!(
"Point index {} out of bounds",
index
)));
}
let point = self.points.remove(index);
self.metadata.num_points = self.points.len();
if !self.points.is_empty() {
self.metadata.bounds = Some(Self::calculate_bounds(&self.points));
} else {
self.metadata.bounds = None;
}
Ok(point)
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn filter_by_distance(&self, center: Point3D, max_distance: f32) -> Self {
let filtered_points: Vec<Point3D> = self
.points
.iter()
.filter(|&point| point.distance_to(¢er) <= max_distance)
.cloned()
.collect();
Self::new(filtered_points)
}
pub fn voxel_downsample(&self, voxel_size: f32) -> Self {
let mut voxel_map: HashMap<(i32, i32, i32), Vec<Point3D>> = HashMap::new();
for &point in &self.points {
let voxel_x = (point.x / voxel_size).floor() as i32;
let voxel_y = (point.y / voxel_size).floor() as i32;
let voxel_z = (point.z / voxel_size).floor() as i32;
voxel_map
.entry((voxel_x, voxel_y, voxel_z))
.or_default()
.push(point);
}
let mut downsampled_points = Vec::new();
for voxel_points in voxel_map.values() {
if voxel_points.is_empty() {
continue;
}
let avg_x = voxel_points.iter().map(|p| p.x).sum::<f32>() / voxel_points.len() as f32;
let avg_y = voxel_points.iter().map(|p| p.y).sum::<f32>() / voxel_points.len() as f32;
let avg_z = voxel_points.iter().map(|p| p.z).sum::<f32>() / voxel_points.len() as f32;
let avg_color = if voxel_points.iter().any(|p| p.color.is_some()) {
let colors: Vec<[u8; 3]> = voxel_points.iter().filter_map(|p| p.color).collect();
if !colors.is_empty() {
let avg_r =
colors.iter().map(|c| c[0] as u32).sum::<u32>() / colors.len() as u32;
let avg_g =
colors.iter().map(|c| c[1] as u32).sum::<u32>() / colors.len() as u32;
let avg_b =
colors.iter().map(|c| c[2] as u32).sum::<u32>() / colors.len() as u32;
Some([avg_r as u8, avg_g as u8, avg_b as u8])
} else {
None
}
} else {
None
};
let point = if let Some(color) = avg_color {
Point3D::with_color(avg_x, avg_y, avg_z, color)
} else {
Point3D::new(avg_x, avg_y, avg_z)
};
downsampled_points.push(point);
}
Self::new(downsampled_points)
}
}
#[derive(Debug, Clone)]
pub struct Mesh3D {
pub vertices: Vec<Point3D>,
pub faces: Vec<Triangle3D>,
pub metadata: MeshMetadata,
}
#[derive(Debug, Clone)]
pub struct MeshMetadata {
pub num_vertices: usize,
pub num_faces: usize,
pub name: String,
pub has_normals: bool,
pub has_texture: bool,
}
impl Mesh3D {
pub fn new(vertices: Vec<Point3D>, faces: Vec<Triangle3D>) -> Self {
let has_normals = vertices.iter().any(|v| v.normal.is_some());
Self {
metadata: MeshMetadata {
num_vertices: vertices.len(),
num_faces: faces.len(),
name: "mesh".to_string(),
has_normals,
has_texture: false,
},
vertices,
faces,
}
}
pub fn compute_face_normals(&mut self) {
for face in &mut self.faces {
let v0 = self.vertices[face.vertices[0]];
let v1 = self.vertices[face.vertices[1]];
let v2 = self.vertices[face.vertices[2]];
let edge1 = [v1.x - v0.x, v1.y - v0.y, v1.z - v0.z];
let edge2 = [v2.x - v0.x, v2.y - v0.y, v2.z - v0.z];
let normal = cross_product(edge1, edge2);
let length =
(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
if length > 0.0 {
face.normal = Some([normal[0] / length, normal[1] / length, normal[2] / length]);
}
}
}
pub fn compute_vertex_normals(&mut self) {
if self.faces.iter().any(|f| f.normal.is_none()) {
self.compute_face_normals();
}
let mut vertex_normals = vec![[0.0f32; 3]; self.vertices.len()];
let mut vertex_counts = vec![0u32; self.vertices.len()];
for face in &self.faces {
if let Some(face_normal) = face.normal {
for &vertex_idx in &face.vertices {
vertex_normals[vertex_idx][0] += face_normal[0];
vertex_normals[vertex_idx][1] += face_normal[1];
vertex_normals[vertex_idx][2] += face_normal[2];
vertex_counts[vertex_idx] += 1;
}
}
}
for (i, vertex) in self.vertices.iter_mut().enumerate() {
if vertex_counts[i] > 0 {
let count = vertex_counts[i] as f32;
let mut normal = [
vertex_normals[i][0] / count,
vertex_normals[i][1] / count,
vertex_normals[i][2] / count,
];
let length =
(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
if length > 0.0 {
normal[0] /= length;
normal[1] /= length;
normal[2] /= length;
vertex.normal = Some(normal);
}
}
}
self.metadata.has_normals = true;
}
pub fn create_sphere(center: Point3D, radius: f32, rings: usize, sectors: usize) -> Self {
let mut vertices = Vec::new();
let mut faces = Vec::new();
for i in 0..=rings {
let lat_angle = std::f32::consts::PI * i as f32 / rings as f32;
let y = radius * lat_angle.cos();
let ring_radius = radius * lat_angle.sin();
for j in 0..=sectors {
let lon_angle = 2.0 * std::f32::consts::PI * j as f32 / sectors as f32;
let x = ring_radius * lon_angle.cos();
let z = ring_radius * lon_angle.sin();
let vertex = Point3D::new(center.x + x, center.y + y, center.z + z);
vertices.push(vertex);
}
}
for i in 0..rings {
for j in 0..sectors {
let current = i * (sectors + 1) + j;
let next = current + sectors + 1;
faces.push(Triangle3D::new(current, next, current + 1));
faces.push(Triangle3D::new(current + 1, next, next + 1));
}
}
let mut mesh = Self::new(vertices, faces);
mesh.compute_vertex_normals();
mesh.metadata.name = "sphere".to_string();
mesh
}
pub fn create_cube(center: Point3D, size: f32) -> Self {
let half_size = size / 2.0;
let vertices = vec![
Point3D::new(
center.x - half_size,
center.y - half_size,
center.z - half_size,
), Point3D::new(
center.x + half_size,
center.y - half_size,
center.z - half_size,
), Point3D::new(
center.x + half_size,
center.y + half_size,
center.z - half_size,
), Point3D::new(
center.x - half_size,
center.y + half_size,
center.z - half_size,
), Point3D::new(
center.x - half_size,
center.y - half_size,
center.z + half_size,
), Point3D::new(
center.x + half_size,
center.y - half_size,
center.z + half_size,
), Point3D::new(
center.x + half_size,
center.y + half_size,
center.z + half_size,
), Point3D::new(
center.x - half_size,
center.y + half_size,
center.z + half_size,
), ];
let faces = vec![
Triangle3D::new(0, 1, 2),
Triangle3D::new(0, 2, 3),
Triangle3D::new(4, 6, 5),
Triangle3D::new(4, 7, 6),
Triangle3D::new(0, 3, 7),
Triangle3D::new(0, 7, 4),
Triangle3D::new(1, 5, 6),
Triangle3D::new(1, 6, 2),
Triangle3D::new(3, 2, 6),
Triangle3D::new(3, 6, 7),
Triangle3D::new(0, 4, 5),
Triangle3D::new(0, 5, 1),
];
let mut mesh = Self::new(vertices, faces);
mesh.compute_vertex_normals();
mesh.metadata.name = "cube".to_string();
mesh
}
}
#[derive(Debug)]
pub struct Scene3D {
pub point_clouds: Vec<PointCloud3D>,
pub meshes: Vec<Mesh3D>,
pub bounding_boxes: Vec<BoundingBox3D>,
pub metadata: SceneMetadata,
}
#[derive(Debug, Clone)]
pub struct SceneMetadata {
pub name: String,
pub bounds: Option<BoundingBox3D>,
pub num_objects: usize,
pub created_at: std::time::SystemTime,
}
impl Scene3D {
pub fn new(name: String) -> Self {
Self {
point_clouds: Vec::new(),
meshes: Vec::new(),
bounding_boxes: Vec::new(),
metadata: SceneMetadata {
name,
bounds: None,
num_objects: 0,
created_at: std::time::SystemTime::now(),
},
}
}
pub fn add_point_cloud(&mut self, point_cloud: PointCloud3D) {
self.point_clouds.push(point_cloud);
self.update_metadata();
}
pub fn add_mesh(&mut self, mesh: Mesh3D) {
self.meshes.push(mesh);
self.update_metadata();
}
pub fn add_bounding_box(&mut self, bbox: BoundingBox3D) {
self.bounding_boxes.push(bbox);
self.update_metadata();
}
fn update_metadata(&mut self) {
self.metadata.num_objects =
self.point_clouds.len() + self.meshes.len() + self.bounding_boxes.len();
let mut all_points = Vec::new();
for pc in &self.point_clouds {
all_points.extend_from_slice(&pc.points);
}
for mesh in &self.meshes {
all_points.extend_from_slice(&mesh.vertices);
}
for bbox in &self.bounding_boxes {
all_points.extend_from_slice(&bbox.corners());
}
if !all_points.is_empty() {
self.metadata.bounds = Some(PointCloud3D::calculate_bounds(&all_points));
}
}
pub fn clear(&mut self) {
self.point_clouds.clear();
self.meshes.clear();
self.bounding_boxes.clear();
self.metadata.num_objects = 0;
self.metadata.bounds = None;
}
pub fn num_objects(&self) -> usize {
self.metadata.num_objects
}
pub fn export_summary(&self) -> String {
format!(
"Scene: {}\nPoint Clouds: {}\nMeshes: {}\nBounding Boxes: {}\nTotal Objects: {}",
self.metadata.name,
self.point_clouds.len(),
self.meshes.len(),
self.bounding_boxes.len(),
self.num_objects()
)
}
}
impl Default for Scene3D {
fn default() -> Self {
Self::new("default_scene".to_string())
}
}
fn rotate_point(point: [f32; 3], roll: f32, pitch: f32, yaw: f32) -> [f32; 3] {
let [x, y, z] = point;
let cos_roll = roll.cos();
let sin_roll = roll.sin();
let cos_pitch = pitch.cos();
let sin_pitch = pitch.sin();
let cos_yaw = yaw.cos();
let sin_yaw = yaw.sin();
let x1 = cos_yaw * x - sin_yaw * y;
let y1 = sin_yaw * x + cos_yaw * y;
let z1 = z;
let x2 = cos_pitch * x1 + sin_pitch * z1;
let y2 = y1;
let z2 = -sin_pitch * x1 + cos_pitch * z1;
let x3 = x2;
let y3 = cos_roll * y2 - sin_roll * z2;
let z3 = sin_roll * y2 + cos_roll * z2;
[x3, y3, z3]
}
fn cross_product(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point3d_creation() {
let point = Point3D::new(1.0, 2.0, 3.0);
assert_eq!(point.position(), [1.0, 2.0, 3.0]);
assert!(point.color.is_none());
assert!(point.normal.is_none());
let colored_point = Point3D::with_color(1.0, 2.0, 3.0, [255, 0, 0]);
assert_eq!(colored_point.color, Some([255, 0, 0]));
}
#[test]
fn test_point3d_distance() {
let p1 = Point3D::new(0.0, 0.0, 0.0);
let p2 = Point3D::new(3.0, 4.0, 0.0);
assert_eq!(p1.distance_to(&p2), 5.0);
}
#[test]
fn test_bounding_box3d() {
let bbox = BoundingBox3D::new(
[0.0, 0.0, 0.0],
[2.0, 2.0, 2.0],
[0.0, 0.0, 0.0],
"test".to_string(),
0.95,
);
assert_eq!(bbox.volume(), 8.0);
let point_inside = Point3D::new(0.5, 0.5, 0.5);
let point_outside = Point3D::new(2.0, 2.0, 2.0);
assert!(bbox.contains_point(point_inside));
assert!(!bbox.contains_point(point_outside));
}
#[test]
fn test_point_cloud() {
let points = vec![
Point3D::new(0.0, 0.0, 0.0),
Point3D::new(1.0, 1.0, 1.0),
Point3D::with_color(2.0, 2.0, 2.0, [255, 0, 0]),
];
let mut cloud = PointCloud3D::new(points);
assert_eq!(cloud.len(), 3);
assert!(!cloud.is_empty());
cloud.add_point(Point3D::new(3.0, 3.0, 3.0));
assert_eq!(cloud.len(), 4);
let removed = cloud.remove_point(0).unwrap();
assert_eq!(removed.position(), [0.0, 0.0, 0.0]);
assert_eq!(cloud.len(), 3);
}
#[test]
fn test_mesh_creation() {
let vertices = vec![
Point3D::new(0.0, 0.0, 0.0),
Point3D::new(1.0, 0.0, 0.0),
Point3D::new(0.5, 1.0, 0.0),
];
let faces = vec![Triangle3D::new(0, 1, 2)];
let mut mesh = Mesh3D::new(vertices, faces);
assert_eq!(mesh.metadata.num_vertices, 3);
assert_eq!(mesh.metadata.num_faces, 1);
mesh.compute_face_normals();
assert!(mesh.faces[0].normal.is_some());
}
#[test]
fn test_sphere_mesh() {
let center = Point3D::new(0.0, 0.0, 0.0);
let mesh = Mesh3D::create_sphere(center, 1.0, 10, 10);
assert!(mesh.metadata.num_vertices > 0);
assert!(mesh.metadata.num_faces > 0);
assert_eq!(mesh.metadata.name, "sphere");
}
#[test]
fn test_cube_mesh() {
let center = Point3D::new(0.0, 0.0, 0.0);
let mesh = Mesh3D::create_cube(center, 2.0);
assert_eq!(mesh.metadata.num_vertices, 8);
assert_eq!(mesh.metadata.num_faces, 12);
assert_eq!(mesh.metadata.name, "cube");
}
#[test]
fn test_scene3d() {
let mut scene = Scene3D::new("test_scene".to_string());
let points = vec![Point3D::new(0.0, 0.0, 0.0)];
let cloud = PointCloud3D::new(points);
scene.add_point_cloud(cloud);
let center = Point3D::new(0.0, 0.0, 0.0);
let mesh = Mesh3D::create_cube(center, 1.0);
scene.add_mesh(mesh);
assert_eq!(scene.num_objects(), 2);
assert!(scene.metadata.bounds.is_some());
}
#[test]
fn test_voxel_downsampling() {
let points = vec![
Point3D::new(0.1, 0.1, 0.1),
Point3D::new(0.2, 0.2, 0.2),
Point3D::new(1.1, 1.1, 1.1),
Point3D::new(1.2, 1.2, 1.2),
];
let cloud = PointCloud3D::new(points);
let downsampled = cloud.voxel_downsample(1.0);
assert_eq!(downsampled.len(), 2);
}
}