using System;
using System.Collections.Generic;
using UnityEngine;
namespace Framework.Geometry.Volumes.Fitting {
/// Fits 3D planes to meshes
public static class Plane3Fitter {
#region enum VectorComponent
/// Individual comopnents of a 3D vector
public enum VectorComponent {
/// The X coordinate of a vector
X,
/// The Y coordinate of a vector
Y,
/// The Z coordinate of a vector
Z
}
#endregion // enum VectorComponent
/// Fits an axis aligned plane to a set of points
/// Points to which the axis aligned plane will be fitted
/// Axis of the plane's normal vector
///
/// Distance beyond which outliers will be incrementally eliminated
///
/// The offset of the plane on the specified axis
public static float FitAxisAlignedPlane(
IList points, VectorComponent axis, float maximumOutlierDelta = 0.01f
) {
// Extract the component on which the plane should be fitted.
// The other vector components quite simply don't matter :)
ArraySegment sortedValues;
{
float[] values = demuxVectorComponent(points, axis);
Array.Sort(values);
sortedValues = new ArraySegment(values, 0, values.Length);
}
return findHighestDensity(sortedValues, maximumOutlierDelta);
}
/// Finds the highest density of values within the specified values
///
/// Values of which the point of highest density will be found
///
///
/// Distance beyond which outliers will be incrementally eliminated
///
/// The point of highest density
private static float findHighestDensity(
ArraySegment sortedValues, float maximumOutlierDelta = 0.01f
) {
int startIndex = sortedValues.Offset;
int endIndex = startIndex + sortedValues.Count - 1;
// Calculate the sum of all remaining values. We will use this to find the mean
// of the values so we can see which outliers to eliminate.
float sum = sortedValues.Array[startIndex];
for(int index = startIndex + 1; index <= endIndex; ++index) {
sum += sortedValues.Array[index];
}
// Now remove outliers until all of the remaining points are within the maximum
// outlier distance, then return the median of those points.
float median;
for(;;) {
median = sum / (endIndex - startIndex + 1);
// Each iteration, eliminate the furthest point. We need to go point-by-point
// because if enough points are outliers, this would move the median in
// the first iterations so far that the point of highest density would be
// an outlier, too.
float leftDistance = Math.Abs(median - sortedValues.Array[startIndex]);
float rightDistance = Math.Abs(sortedValues.Array[endIndex] - median);
if(leftDistance > rightDistance) {
if(leftDistance < maximumOutlierDelta) {
break;
}
sum -= sortedValues.Array[startIndex];
++startIndex;
} else {
if(rightDistance < maximumOutlierDelta) {
break;
}
sum -= sortedValues.Array[endIndex];
--endIndex;
}
}
//Debug.Log("Median averaged on " + (endIndex - startIndex + 1) + " samples.");
return median;
}
#if false
///
/// Removes elements from a sorted list until the remaining values fall within
/// a specified range
///
/// List from which elements will be removed
/// Minimum value to keep in the list (inclusive)
/// Maximum value to keep in the list (exclusive)
private static void Trim(
ref ArraySegment sortedValues, float min, float max
) {
int startIndex = sortedValues.Offset;
int endIndex = startIndex + sortedValues.Count - 1;
// Remove values that are smaller than the minimum value
while(sortedValues.Array[startIndex] < min) {
++startIndex;
if(startIndex > endIndex) {
sortedValues = new ArraySegment(sortedValues.Array, sortedValues.Offset, 0);
return;
}
}
// Remove values that are larger than the maximum value
while(sortedValues.Array[endIndex] >= max) {
--endIndex;
if(endIndex < startIndex) {
sortedValues = new ArraySegment(sortedValues.Array, sortedValues.Offset, 0);
return;
}
}
sortedValues = new ArraySegment(
sortedValues.Array, startIndex, (endIndex - startIndex + 1)
);
}
#endif
/// Demuxes an individual component from a set of vectors
/// Vector that will have one of their components extracted
/// Vector component that will be demuxed
/// An array of all the values of the specified vector component
private static float[] demuxVectorComponent(
IList vectors, VectorComponent component
) {
int vectorCount = vectors.Count;
var values = new float[vectorCount];
switch(component) {
case VectorComponent.X: {
for(int index = 0; index < vectorCount; ++index) {
values[index] = vectors[index].x;
}
break;
}
case VectorComponent.Y: {
for(int index = 0; index < vectorCount; ++index) {
values[index] = vectors[index].y;
}
break;
}
case VectorComponent.Z: {
for(int index = 0; index < vectorCount; ++index) {
values[index] = vectors[index].z;
}
break;
}
default: {
throw new ArgumentException("Unsupported vector component index", "component");
}
}
return values;
}
}
} // namespace Framework.Geometry.Volumes.Fitting