///|
/// Production point-cloud operators used by calibration and reconstruction.
///
/// The functions in this file deliberately operate on plain arrays. This keeps
/// the package usable in Wasm, native batch tools, and embedded applications
/// without forcing callers to adopt a storage backend.
pub(all) struct PointCloudPipelineSummary {
count : Int
centroid : Point3
minimum : Point3
maximum : Point3
radius : Double
mean_distance : Double
maximum_distance : Double
} derive(Debug, Eq)
///|
fn point3_zero() -> Point3 {
Point3::new(x=0.0, y=0.0, z=0.0)
}
///|
fn point3_add(a : Point3, b : Point3) -> Point3 {
Point3::new(x=a.x + b.x, y=a.y + b.y, z=a.z + b.z)
}
///|
fn point3_scale(a : Point3, scale : Double) -> Point3 {
Point3::new(x=a.x * scale, y=a.y * scale, z=a.z * scale)
}
///|
fn point3_min(a : Point3, b : Point3) -> Point3 {
Point3::new(x=a.x.min(b.x), y=a.y.min(b.y), z=a.z.min(b.z))
}
///|
fn point3_max(a : Point3, b : Point3) -> Point3 {
Point3::new(x=a.x.max(b.x), y=a.y.max(b.y), z=a.z.max(b.z))
}
///|
pub fn point_cloud_centroid(points : ArrayView[Point3]) -> Point3 {
if points.length() == 0 {
return point3_zero()
}
let mut sum = point3_zero()
for point in points {
sum = point3_add(sum, point)
}
point3_scale(sum, 1.0 / points.length().to_double())
}
///|
pub fn point_cloud_bounds(points : ArrayView[Point3]) -> (Point3, Point3) {
if points.length() == 0 {
return (point3_zero(), point3_zero())
}
let mut minimum = points[0]
let mut maximum = points[0]
for point in points {
minimum = point3_min(minimum, point)
maximum = point3_max(maximum, point)
}
(minimum, maximum)
}
///|
pub fn point_cloud_radius(
points : ArrayView[Point3],
center : Point3,
) -> Double {
let mut result = 0.0
for point in points {
result = result.max(distance3(point, center))
}
result
}
///|
pub fn point_cloud_mean_distance(
points : ArrayView[Point3],
center : Point3,
) -> Double {
if points.length() == 0 {
return 0.0
}
let mut total = 0.0
for point in points {
total += distance3(point, center)
}
total / points.length().to_double()
}
///|
pub fn point_cloud_pipeline_summary(
points : ArrayView[Point3],
) -> PointCloudPipelineSummary {
let centroid = point_cloud_centroid(points)
let (minimum, maximum) = point_cloud_bounds(points)
{
count: points.length(),
centroid,
minimum,
maximum,
radius: point_cloud_radius(points, centroid),
mean_distance: point_cloud_mean_distance(points, centroid),
maximum_distance: point_cloud_radius(points, centroid),
}
}
///|
pub fn point_cloud_translate(
points : ArrayView[Point3],
offset : Vec3,
) -> Array[Point3] {
let result : Array[Point3] = []
for point in points {
result.push(point.translate(offset))
}
result
}
///|
pub fn point_cloud_scale(
points : ArrayView[Point3],
scale : Double,
) -> Array[Point3] {
let result : Array[Point3] = []
for point in points {
result.push(point3_scale(point, scale))
}
result
}
///|
pub fn point_cloud_centered(points : ArrayView[Point3]) -> Array[Point3] {
let center = point_cloud_centroid(points)
point_cloud_translate(
points,
Vec3::new(x=-center.x, y=-center.y, z=-center.z),
)
}
///|
pub fn point_cloud_normalize_radius(
points : ArrayView[Point3],
) -> Array[Point3] {
let center = point_cloud_centroid(points)
let radius = point_cloud_radius(points, center)
if radius <= 0.000000000001 {
point_cloud_centered(points)
} else {
point_cloud_scale(point_cloud_centered(points), 1.0 / radius)
}
}
///|
pub fn point_cloud_filter_radius(
points : ArrayView[Point3],
center : Point3,
radius : Double,
) -> Array[Point3] {
let result : Array[Point3] = []
let limit = radius.max(0.0)
for point in points {
if distance3(point, center) <= limit {
result.push(point)
}
}
result
}
///|
pub fn point_cloud_filter_box(
points : ArrayView[Point3],
minimum : Point3,
maximum : Point3,
) -> Array[Point3] {
let result : Array[Point3] = []
for point in points {
if point.x >= minimum.x &&
point.x <= maximum.x &&
point.y >= minimum.y &&
point.y <= maximum.y &&
point.z >= minimum.z &&
point.z <= maximum.z {
result.push(point)
}
}
result
}
///|
pub fn point_cloud_distances(
points : ArrayView[Point3],
center : Point3,
) -> Array[Double] {
let result : Array[Double] = []
for point in points {
result.push(distance3(point, center))
}
result
}
///|
pub fn point_cloud_squared_distances(
points : ArrayView[Point3],
center : Point3,
) -> Array[Double] {
let result : Array[Double] = []
for point in points {
result.push(point.minus(center).norm2())
}
result
}
///|
pub fn point_cloud_nearest_distance(
point : Point3,
points : ArrayView[Point3],
) -> Double {
if points.length() == 0 {
return 0.0
}
let mut result = distance3(point, points[0])
for candidate in points[1:] {
result = result.min(distance3(point, candidate))
}
result
}
///|
pub fn point_cloud_nearest_distances(
points : ArrayView[Point3],
) -> Array[Double] {
let result : Array[Double] = []
for i in 0.. Array[Double] {
let result : Array[Double] = []
for i in 0.. Double {
let values = point_cloud_pairwise_distances(points)
mean(values) catch {
_ => 0.0
}
}
///|
pub fn point_cloud_pairwise_maximum(points : ArrayView[Point3]) -> Double {
let mut result = 0.0
for value in point_cloud_pairwise_distances(points) {
result = result.max(value)
}
result
}
///|
pub fn point_cloud_count_within(
points : ArrayView[Point3],
center : Point3,
radius : Double,
) -> Int {
let mut count = 0
for point in points {
if distance3(point, center) <= radius.max(0.0) {
count += 1
}
}
count
}
///|
pub fn point_cloud_density(
points : ArrayView[Point3],
center : Point3,
radius : Double,
) -> Double {
let r = radius.max(0.0)
if r <= 0.0 {
0.0
} else {
point_cloud_count_within(points, center, r).to_double() /
(4.0 / 3.0 * 3.141592653589793 * r * r * r)
}
}
///|
pub fn point_cloud_remove_duplicates(
points : ArrayView[Point3],
tolerance : Double,
) -> Array[Point3] {
let result : Array[Point3] = []
let limit = tolerance.max(0.0)
for point in points {
let mut duplicate = false
for existing in result {
if distance3(point, existing) <= limit {
duplicate = true
}
}
if !duplicate {
result.push(point)
}
}
result
}
///|
pub fn point_cloud_merge(
left : ArrayView[Point3],
right : ArrayView[Point3],
) -> Array[Point3] {
let result = left.to_owned()
for point in right {
result.push(point)
}
result
}
///|
pub fn point_cloud_chunk(
points : ArrayView[Point3],
chunk_size : Int,
) -> Array[Array[Point3]] {
let result : Array[Array[Point3]] = []
let width = chunk_size.max(1)
let mut start = 0
while start < points.length() {
let end = (start + width).min(points.length())
let chunk : Array[Point3] = []
for i in start.. Array[Point3] {
let result : Array[Point3] = []
for i in 0.. Array[Point3] {
let result : Array[Point3] = []
for i in count.max(0).min(points.length()).. Array[Point3] {
let result : Array[Point3] = []
let step = stride.max(1)
for i in 0.. Array[Point3] {
let result : Array[Point3] = []
let count = left.length().max(right.length())
for i in 0.. Point3 {
let t = amount.max(0.0).min(1.0)
Point3::new(
x=a.x + (b.x - a.x) * t,
y=a.y + (b.y - a.y) * t,
z=a.z + (b.z - a.z) * t,
)
}
///|
pub fn point_cloud_resample_line(
start : Point3,
end : Point3,
count : Int,
) -> Array[Point3] {
let result : Array[Point3] = []
let samples = count.max(1)
if samples == 1 {
result.push(start)
return result
}
for i in 0.. Double {
if points.length() < 2 {
return 0.0
}
let mut result = 0.0
for i in 0..<(points.length() - 1) {
result += distance3(points[i], points[i + 1])
}
result
}
///|
pub fn point_cloud_is_finite(points : ArrayView[Point3]) -> Bool {
for point in points {
if point.x.is_nan() || point.y.is_nan() || point.z.is_nan() {
return false
}
}
true
}
///|
pub fn point_cloud_axis_range(points : ArrayView[Point3], axis : Int) -> Double {
let (minimum, maximum) = point_cloud_bounds(points)
if axis == 0 {
maximum.x - minimum.x
} else if axis == 1 {
maximum.y - minimum.y
} else {
maximum.z - minimum.z
}
}
///|
pub fn point_cloud_axis_values(
points : ArrayView[Point3],
axis : Int,
) -> Array[Double] {
let result : Array[Double] = []
for point in points {
result.push(
if axis == 0 {
point.x
} else if axis == 1 {
point.y
} else {
point.z
},
)
}
result
}
///|
pub fn point_cloud_variance_axis(
points : ArrayView[Point3],
axis : Int,
) -> Double {
variance(point_cloud_axis_values(points, axis)) catch {
_ => 0.0
}
}
///|
pub fn point_cloud_voxel_sample(
points : ArrayView[Point3],
voxel : Double,
) -> Array[Point3] {
if points.length() == 0 {
return []
}
if voxel <= 0.0 {
return points.to_owned()
}
let result : Array[Point3] = []
for point in points {
let mut occupied = false
for representative in result {
let dx = ((point.x - representative.x) / voxel).abs()
let dy = ((point.y - representative.y) / voxel).abs()
let dz = ((point.z - representative.z) / voxel).abs()
if dx < 1.0 && dy < 1.0 && dz < 1.0 {
occupied = true
}
}
if !occupied {
result.push(point)
}
}
result
}
///|
pub fn point_cloud_voxel_count(
points : ArrayView[Point3],
voxel : Double,
) -> Int {
point_cloud_voxel_sample(points, voxel).length()
}
///|
pub fn point_cloud_outlier_flags(
points : ArrayView[Point3],
threshold : Double,
) -> Array[Bool] {
let distances = point_cloud_nearest_distances(points)
let center = mean(distances) catch { _ => 0.0 }
let spread = (variance(distances) catch { _ => 0.0 }).sqrt()
let limit = center + threshold.max(0.0) * spread
let result : Array[Bool] = []
for distance in distances {
result.push(distance > limit && spread > 0.0)
}
result
}
///|
pub fn point_cloud_remove_outliers(
points : ArrayView[Point3],
threshold : Double,
) -> Array[Point3] {
let flags = point_cloud_outlier_flags(points, threshold)
let result : Array[Point3] = []
for i in 0.. Point3 {
let count = points.length().min(weights.length())
if count == 0 {
return point3_zero()
}
let mut sum = point3_zero()
let mut total = 0.0
for i in 0.. Double {
point_cloud_radius(points, point_cloud_weighted_centroid(points, weights))
}
///|
pub fn point_cloud_align_centroids(
source : ArrayView[Point3],
target : ArrayView[Point3],
) -> Array[Point3] {
let delta = point_cloud_centroid(target).minus(point_cloud_centroid(source))
point_cloud_translate(source, delta)
}
///|
pub fn point_cloud_quality_score(
points : ArrayView[Point3],
minimum_count : Int,
maximum_radius : Double,
) -> Double {
let support = if minimum_count <= 0 {
1.0
} else {
(points.length().to_double() / minimum_count.to_double()).min(1.0)
}
let compactness = if maximum_radius <= 0.0 {
0.0
} else {
(maximum_radius / point_cloud_radius(points, point_cloud_centroid(points))).min(
1.0,
)
}
support * compactness
}