use std::path::Path;
use crate::{Error, Result};
use super::scanner::{Parse, is_skipped, parse_numbers};
use super::window::for_each_window;
pub fn read_point_cloud(path: &Path, threads: usize) -> Result<Vec<Vec<f64>>> {
let name = path.display().to_string();
let mut sink = PointSink::new(threads);
for_each_window(path, threads, |first_line, text| {
sink.window(&name, first_line, text)
})?;
Ok(sink.points)
}
pub fn parse_point_cloud(name: &str, text: &str, threads: usize) -> Result<Vec<Vec<f64>>> {
let mut sink = PointSink::new(threads);
sink.window(name, 1, text)?;
Ok(sink.points)
}
pub(super) struct PointSink {
pub(super) parse: Parse,
pub(super) points: Vec<Vec<f64>>,
width: Option<usize>,
}
impl PointSink {
pub(super) fn new(threads: usize) -> Self {
Self {
parse: Parse::new(threads),
points: Vec::new(),
width: None,
}
}
pub(super) fn window(&mut self, name: &str, first_line: usize, text: &str) -> Result<usize> {
let (chunks, newlines) = self.parse.map(text, first_line, |first, chunk| {
point_cloud_chunk(name, first, chunk)
});
let total: usize = chunks.iter().map(|chunk| chunk.points.len()).sum();
self.points.reserve(total);
for mut chunk in chunks {
if let Some(first) = chunk.points.first() {
match self.width {
None => self.width = Some(first.len()),
Some(w) if first.len() != w => {
return Err(Error::InvalidInput(format!(
"{name}:{}: point has {} coordinates, expected {w}",
chunk.first_line,
first.len()
)));
}
Some(_) => {}
}
}
if let Some(error) = chunk.error {
return Err(error);
}
if self.points.is_empty() {
self.points = chunk.points;
} else {
self.points.append(&mut chunk.points);
}
}
Ok(newlines)
}
}
struct PointChunk {
first_line: usize,
points: Vec<Vec<f64>>,
error: Option<Error>,
}
fn point_cloud_chunk(name: &str, first_line: usize, text: &str) -> PointChunk {
let mut chunk = PointChunk {
first_line: 0,
points: Vec::new(),
error: None,
};
for (idx, line) in text.lines().enumerate() {
let lineno = first_line + idx;
if is_skipped(line) {
continue;
}
let width = chunk.points.first().map_or(0, Vec::len);
let mut point = Vec::with_capacity(width);
if let Err(t) = parse_numbers(line, |v| point.push(v)) {
chunk.error = Some(Error::InvalidInput(format!(
"{name}:{lineno}: not a number: {t:?}"
)));
break;
}
if point.is_empty() {
continue;
}
if chunk.points.is_empty() {
chunk.first_line = lineno;
} else if point.len() != width {
chunk.error = Some(Error::InvalidInput(format!(
"{name}:{lineno}: point has {} coordinates, expected {width}",
point.len()
)));
break;
}
chunk.points.push(point);
}
chunk
}