use std::vec;
use pasture_core::{
layout::attributes::POSITION_3D,
nalgebra::Vector3, containers::{BorrowedBuffer, BorrowedBufferExt},
};
use rand::Rng;
use rayon::prelude::*;
#[derive(Debug)]
pub struct Line {
first: Vector3<f64>,
second: Vector3<f64>,
ranking: usize,
}
#[derive(Debug)]
pub struct Plane {
a: f64,
b: f64,
c: f64,
d: f64,
ranking: usize,
}
fn distance_point_plane(point: &Vector3<f64>, plane: &Plane) -> f64 {
let d = (plane.a * point.x + plane.b * point.y + plane.c * point.z + plane.d).abs();
let e = (plane.a * plane.a + plane.b * plane.b + plane.c * plane.c).sqrt();
d / e
}
fn distance_point_line(point: &Vector3<f64>, line: &Line) -> f64 {
(line.second - line.first)
.cross(&(line.first - point))
.norm()
/ (line.second - line.first).norm()
}
fn generate_rng_plane<'a, T: BorrowedBuffer<'a>>(buffer: &'a T) -> Plane {
let mut rng = rand::thread_rng();
let rand1 = rng.gen_range(0..buffer.len());
let mut rand2 = rng.gen_range(0..buffer.len());
while rand1 == rand2 {
rand2 = rng.gen_range(0..buffer.len());
}
let mut rand3 = rng.gen_range(0..buffer.len());
while rand2 == rand3 || rand1 == rand3 {
rand3 = rng.gen_range(0..buffer.len());
}
let p_a: Vector3<f64> = buffer.view_attribute(&POSITION_3D).at(rand1);
let p_b: Vector3<f64> = buffer.view_attribute(&POSITION_3D).at(rand2);
let p_c: Vector3<f64> = buffer.view_attribute(&POSITION_3D).at(rand3);
let vec1 = p_b - p_a;
let vec2 = p_c - p_a;
let normal = vec1.cross(&vec2);
let d = -normal.dot(&p_a);
Plane {
a: normal.x,
b: normal.y,
c: normal.z,
d,
ranking: 0,
}
}
fn generate_rng_line<'a, T: BorrowedBuffer<'a>>(buffer: &'a T) -> Line {
let mut rng = rand::thread_rng();
let rand1 = rng.gen_range(0..buffer.len());
let mut rand2 = rng.gen_range(0..buffer.len());
while rand1 == rand2 {
rand2 = rng.gen_range(0..buffer.len());
}
Line {
first: buffer.view_attribute(&POSITION_3D).at(rand1),
second: buffer.view_attribute(&POSITION_3D).at(rand2),
ranking: 0,
}
}
fn generate_line_model<'a, T: BorrowedBuffer<'a>>(buffer: &'a T, distance_threshold: f64) -> (Line, Vec<usize>) {
let mut curr_hypo = generate_rng_line(buffer);
let mut curr_positions = vec![];
for (index, p) in buffer
.view_attribute::<Vector3<f64>>(&POSITION_3D)
.into_iter()
.enumerate()
{
let distance = distance_point_line(&p, &curr_hypo);
if distance < distance_threshold {
curr_positions.push(index);
curr_hypo.ranking += 1;
}
}
(curr_hypo, curr_positions)
}
fn generate_plane_model<'a, T: BorrowedBuffer<'a>>(buffer: &'a T,
distance_threshold: f64,
) -> (Plane, Vec<usize>) {
let mut curr_hypo = generate_rng_plane(buffer);
let mut curr_positions = vec![];
for (index, p) in buffer
.view_attribute::<Vector3<f64>>(&POSITION_3D)
.into_iter()
.enumerate()
{
let distance = distance_point_plane(&p, &curr_hypo);
if distance < distance_threshold {
curr_hypo.ranking += 1;
curr_positions.push(index);
}
}
(curr_hypo, curr_positions)
}
pub fn ransac_plane_par<'a, T: BorrowedBuffer<'a> + Sync>(buffer: &'a T,
distance_threshold: f64,
num_of_iterations: usize,
) -> (Plane, Vec<usize>) {
if buffer.len() < 3 {
panic!("buffer needs to include at least 3 points to generate a plane.");
}
(0..num_of_iterations)
.into_par_iter()
.map(|_x| {
generate_plane_model(buffer, distance_threshold)
})
.max_by(|(x, _y), (a, _b)| x.ranking.cmp(&a.ranking))
.unwrap()
}
pub fn ransac_plane_serial<'a, T: BorrowedBuffer<'a> + Sync>(buffer: &'a T,
distance_threshold: f64,
num_of_iterations: usize,
) -> (Plane, Vec<usize>) {
if buffer.len() < 3 {
panic!("buffer needs to include at least 3 points to generate a plane.");
}
(0..num_of_iterations)
.map(|_x|
generate_plane_model(buffer, distance_threshold))
.max_by(|(x, _y), (a, _b)| x.ranking.cmp(&a.ranking))
.unwrap()
}
pub fn ransac_line_par<'a, T: BorrowedBuffer<'a> + Sync>(buffer: &'a T,
distance_threshold: f64,
num_of_iterations: usize,
) -> (Line, Vec<usize>) {
if buffer.len() < 2 {
panic!("buffer needs to include at least 2 points to generate a line.");
}
(0..num_of_iterations)
.into_par_iter()
.map(|_x|
generate_line_model(buffer, distance_threshold))
.max_by(|(x, _y), (a, _b)| x.ranking.cmp(&a.ranking))
.unwrap()
}
pub fn ransac_line_serial<'a, T: BorrowedBuffer<'a>>(buffer: &'a T,
distance_threshold: f64,
num_of_iterations: usize,
) -> (Line, Vec<usize>) {
if buffer.len() < 2 {
panic!("buffer needs to include at least 2 points to generate a line.");
}
(0..num_of_iterations)
.map(|_x|
generate_line_model(buffer, distance_threshold))
.max_by(|(x, _y), (a, _b)| x.ranking.cmp(&a.ranking))
.unwrap()
}
#[cfg(test)]
mod tests {
use pasture_core::{nalgebra::Vector3, containers::HashMapBuffer,
};
use pasture_derive::PointType;
use super::*;
#[repr(C)]
#[derive(PointType, Debug, Copy, Clone, bytemuck::AnyBitPattern, bytemuck::NoUninit)]
pub struct SimplePoint {
#[pasture(BUILTIN_POSITION_3D)]
pub position: Vector3<f64>,
}
fn setup_point_cloud() -> HashMapBuffer {
(2..2002)
.map(|p| {
let mut point = SimplePoint {
position: Vector3::new(p as f64, (p * p) as f64, 1.0),
};
if p % 5 == 0 {
point.position = Vector3::new(0.0, 0.0, (p * p) as f64);
}
if p % 50 == 0 {
point.position.z = (p * p) as f64;
}
point
})
.collect()
}
#[test]
fn test_ransac_plane_par() {
let buffer = setup_point_cloud();
let (_plane, indices) = ransac_plane_par(&buffer, 0.1, 300);
assert!(indices.len() == 1600);
for i in 0..2000 {
if i % 5 != 3 {
assert!(indices.contains(&i));
}
}
}
#[test]
fn test_ransac_plane_serial() {
let buffer = setup_point_cloud();
let (_plane, indices) = ransac_plane_serial(&buffer, 0.1, 300);
assert!(indices.len() == 1600);
for i in 0..2000 {
if i % 5 != 3 {
assert!(indices.contains(&i));
}
}
}
#[test]
fn test_ransac_line_par() {
let buffer = setup_point_cloud();
let (_plane, indices) = ransac_line_par(&buffer, 0.1, 300);
assert!(indices.len() == 400);
for i in 0..2000 {
if i % 5 == 3 {
assert!(indices.contains(&i));
}
}
}
#[test]
fn test_ransac_line_serial() {
let buffer = setup_point_cloud();
let (_plane, indices) = ransac_line_serial(&buffer, 0.1, 300);
assert!(indices.len() == 400);
for i in 0..2000 {
if i % 5 == 3 {
assert!(indices.contains(&i));
}
}
}
}