use boostvoronoi::geo::{Coord, Line as GLine, prelude::Intersects};
use boostvoronoi::prelude as BV;
use boostvoronoi::prelude::*;
use rand::prelude::StdRng;
use rand::{Rng, SeedableRng};
use std::sync::mpsc::{Receiver, Sender};
use std::sync::{Arc, Mutex, mpsc};
use std::thread;
type I = i64;
const NUMBER_OF_THREADS: usize = 6;
const REPORT_FREQUENCY: usize = 1_000_000;
const TESTS_PER_SEED: usize = 1_000_000;
const NUMBER_OF_SEGMENTS_PER_TEST: usize = 2;
const SEED_START: u64 = 121;
enum SeedRequest {
RequestNewSeed(usize),
ErrorFrom(()),
}
#[cfg(feature = "geo")]
fn geo_line_point_distance<T: geo::GeoFloat>(coord: geo::Coord<T>, line: geo::Line<T>) -> T {
use geo::algorithm::closest_point::ClosestPoint;
use geo::{Closest, Distance, Euclidean};
let point: geo::Point<T> = coord.into();
match line.closest_point(&point) {
Closest::Intersection(c_point) | Closest::SinglePoint(c_point) => {
Euclidean.distance(c_point, point)
}
Closest::Indeterminate => {
unreachable!("Simple line segment should always have a determinate closest point")
}
}
}
fn fault_check(diagram: &Result<Diagram, BvError>, segments: Vec<GLine<I>>) -> Result<(), String> {
let mut heap: Vec<f64> = Vec::new();
let diagram = diagram.as_ref().unwrap();
let segments: Vec<GLine<f64>> = segments
.iter()
.map(|s| {
GLine::from([
(s.start.x as f64, s.start.y as f64),
(s.end.x as f64, s.end.y as f64),
])
})
.collect();
for v in diagram.vertices().iter() {
let v = Coord { x: v.x(), y: v.y() };
for s in segments.iter() {
let distance = geo_line_point_distance(v, *s);
if let Some(peek) = heap.first() {
if distance <= *peek {
if *peek - distance > 0.0001 {
heap.clear();
}
} else if distance - *peek > 0.0001 {
continue;
}
}
heap.push(distance);
}
if heap.len() < 2 {
let mut err_msg = format!(
"Got a vertex with only one close neighbour: {:?}, dist:{:?}",
v,
heap.first()
);
for s in segments.iter() {
err_msg += format!("\n {:?}, dist:{}", s, geo_line_point_distance(v, *s)).as_str();
}
return Err(err_msg);
}
heap.clear();
}
Ok(())
}
fn boostvoronoi_test(rnd_seed: u64, printout_lock: Arc<Mutex<()>>) -> Result<(), BvError> {
let to_r = 1000_i64;
let mut rng: StdRng = StdRng::seed_from_u64(rnd_seed);
for _ in 0..TESTS_PER_SEED {
let mut geo_segments = Vec::<GLine<I>>::with_capacity(4);
'gen_loop: while geo_segments.len() < NUMBER_OF_SEGMENTS_PER_TEST {
let line = GLine::from([
(rng.random_range(-to_r..to_r), rng.random_range(-to_r..to_r)),
(rng.random_range(-to_r..to_r), rng.random_range(-to_r..to_r)),
]);
for s in geo_segments.iter() {
if line.intersects(s) {
continue 'gen_loop;
}
}
geo_segments.push(line);
}
let vertices = []
.iter()
.map(|p: &[i64; 2]| -> BV::Point<i64> { p.into() })
.collect::<Vec<BV::Point<i64>>>();
let segments = geo_segments
.iter()
.map(|l| l.into())
.collect::<Vec<BV::Line<i64>>>();
let result = Builder::<I>::default()
.with_vertices(vertices.iter())?
.with_segments(segments.iter())?
.build();
if result.is_err() || fault_check(&result, geo_segments).is_err() {
let _unused = printout_lock.lock();
println!("\nfound a bad example:");
println!("-------\n{}", vertices.len());
for p in vertices.iter() {
println!("{} {}", p.x, p.y);
}
println!("{}", segments.len());
for s in segments.iter() {
println!("{} {} {} {}", s.start.x, s.start.y, s.end.x, s.end.y);
}
println!("-------");
print!("int INPUT_PTS[{}][2] = {{", vertices.len());
for p in vertices.iter() {
print!("{{{},{}}},", p.x, p.y);
}
println!("}};");
print!("int INPUT_SGS[{}][4] = {{", segments.len());
for s in segments.iter() {
print!("{{{},{},{},{}}},", s.start.x, s.start.y, s.end.x, s.end.y);
}
println!("}};");
println!("-------");
return Err(BvError::InternalError("found error".to_string()));
}
}
Ok(())
}
#[inline]
fn worker_thread_loop(
id: usize,
request_tx: Sender<SeedRequest>,
seed_rx: Receiver<u64>,
printout_lock: Arc<Mutex<()>>,
) {
loop {
let new_seed = seed_rx.recv().unwrap();
if boostvoronoi_test(new_seed, Arc::clone(&printout_lock)).is_err() {
request_tx.send(SeedRequest::ErrorFrom(())).unwrap();
};
request_tx.send(SeedRequest::RequestNewSeed(id)).unwrap();
}
}
fn main() {
let mut next_seed = SEED_START;
let printout_lock = Arc::new(Mutex::new(()));
let (request_tx, request_rx) = mpsc::channel::<SeedRequest>();
let handles: Vec<_> = (0..NUMBER_OF_THREADS)
.map(|n| {
let (thread_tx, thread_rx) = mpsc::channel::<u64>();
thread_tx.send(next_seed).unwrap();
next_seed += 1;
let thread_handle = thread::spawn({
let request_tx = request_tx.clone();
let printout_lock = Arc::clone(&printout_lock);
move || worker_thread_loop(n, request_tx, thread_rx, printout_lock)
});
(thread_handle, thread_tx)
})
.collect();
let mut iterations = 0_usize;
let duration = std::time::Instant::now();
let mut detected_errors = 0;
loop {
match request_rx
.recv_timeout(std::time::Duration::from_millis(100))
.ok()
{
Some(SeedRequest::RequestNewSeed(requesting_id)) => {
handles[requesting_id].1.send(next_seed).unwrap();
next_seed += 1;
iterations += TESTS_PER_SEED;
if iterations % REPORT_FREQUENCY == 0 {
let tdelta = duration.elapsed().as_secs_f64()
/ (iterations as f64 / REPORT_FREQUENCY as f64);
println!(
"report: {}*{} tests at an average {:.4} seconds per {} tests. Next seed:{}, errors detected:{}",
iterations / REPORT_FREQUENCY,
REPORT_FREQUENCY,
tdelta,
REPORT_FREQUENCY,
next_seed,
detected_errors,
);
}
}
None => continue,
_ => {
detected_errors += 1;
if detected_errors >= 5 {
break;
}
}
}
}
}