예제 #1
0
        /// <summary>
        /// Projects points onto plane (shortest distance).
        /// </summary>
        public static V3d[] Project(this Plane3d plane, V3d[] pointArray, int startIndex = 0, int count = 0)
        {
            if (pointArray == null)
            {
                throw new ArgumentNullException();
            }
            if (startIndex < 0 || startIndex >= pointArray.Length)
            {
                throw new ArgumentOutOfRangeException();
            }
            if (count < 0 || startIndex + count >= pointArray.Length)
            {
                throw new ArgumentOutOfRangeException();
            }
            if (count == 0)
            {
                count = pointArray.Length - startIndex;
            }

            var normal = plane.Normal;
            var result = new V3d[count];

            for (int i = startIndex, j = 0; j < count; i++, j++)
            {
                var p      = pointArray[i];
                var height = plane.Height(p);
                result[j] = p - height * normal;
            }
            return(result);
        }
예제 #2
0
 /// <summary>
 /// All points NOT within maxDistance of given plane.
 /// </summary>
 public static IEnumerable <Chunk> QueryPointsNotNearPlane(
     this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue
     )
 => QueryPoints(node,
                n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance),
                n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal),
                p => Math.Abs(plane.Height(p)) > maxDistance,
                minCellExponent
                );
예제 #3
0
 /// <summary>
 /// Count points within maxDistance of given plane.
 /// </summary>
 public static long CountPointsNearPlane(
     this IPointCloudNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue
     )
 => CountPoints(node,
                n => plane.Contains(maxDistance, node.BoundingBoxExactGlobal),
                n => !node.BoundingBoxExactGlobal.Intersects(plane, maxDistance),
                p => Math.Abs(plane.Height(p)) <= maxDistance,
                minCellExponent
                );
예제 #4
0
 /// <summary>
 /// All points within maxDistance of given plane.
 /// </summary>
 public static IEnumerable <Chunk> QueryPointsNearPlane(
     this PointSetNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue
     )
 => QueryPoints(node,
                n => plane.Contains(maxDistance, node.BoundingBox),
                n => !node.BoundingBox.Intersects(plane, maxDistance),
                p => Math.Abs(plane.Height(p)) <= maxDistance,
                minCellExponent
                );
예제 #5
0
 /// <summary>
 /// Count points NOT within maxDistance of given plane.
 /// </summary>
 public static long CountPointsNotNearPlane(
     this PointSetNode node, Plane3d plane, double maxDistance, int minCellExponent = int.MinValue
     )
 => CountPoints(node,
                n => !node.BoundingBox.Intersects(plane, maxDistance),
                n => plane.Contains(maxDistance, node.BoundingBox),
                p => Math.Abs(plane.Height(p)) > maxDistance,
                minCellExponent
                );
예제 #6
0
        /// <summary>
        /// Perform weighted least squares plane fitting
        /// </summary>
        /// <param name="points">Data points</param>
        /// <param name="maxIter">Maximum number of iterations</param>
        /// <param name="thres">The algorithm terminates if the change in mean squared error
        /// is below the given threshold</param>
        /// <param name="mse">Mean squared error</param>
        /// <returns>The found plane</returns>
        public static Plane3d FitPlane3dWeightedLeastSquares(this V3d[] points, int maxIter, double thres, out double mse)
        {
            if (points == null)
            {
                throw new ArgumentNullException(nameof(points));
            }
            if (points.Length <= 0)
            {
                throw new InvalidOperationException();
            }
            if (maxIter <= 0)
            {
                throw new InvalidOperationException();
            }

            Plane3d wlsPlane = new Plane3d();
            var     weights  = new double[points.Length].Set(1.0);
            var     sqDists  = new double[weights.Length];

            double meanSquaredErrorNew = 0.0;
            double ch = double.MaxValue;

            for (int iter = 0; iter < maxIter && ch > thres; iter++)
            {
                double meanSquaredErrorCurrent = meanSquaredErrorNew;

                wlsPlane = PerformOneStep(points, weights);

                sqDists.SetByIndex(i => Fun.Square(wlsPlane.Height(points[i])));

                meanSquaredErrorNew = sqDists.InnerProduct(weights,
                                                           (s, w) => s * w,
                                                           0.0, (s, m) => s + m) / weights.Aggregate((sum, x) => sum += x);

                weights.SetByIndex(i => 1.0 / (sqDists[i] + 1.0));

                ch = Fun.Abs(meanSquaredErrorNew - meanSquaredErrorCurrent);
            }
            ;

            mse = meanSquaredErrorNew;

            return(wlsPlane);
        }
예제 #7
0
        /// <summary>
        /// Perform least median of squares plane fitting.
        /// </summary>
        /// <param name="points">Data points</param>
        /// <returns>The found plane</returns>
        public static Plane3d FitPlane3dLeastMedianOfSquares(this V3d[] points)
        {
            if (points.Length < 3)
            {
                return(Plane3d.Invalid);
            }

            if (points.Length == 3)
            {
                return(new Plane3d(points.First(), points.ElementAt(1), points.Last()));
            }

            var plane = new Plane3d();

            var rnd = new Random();
            var relativeOutlierRatio = 0.4;
            var probabilityOfSuccess = 0.999;
            var numOfIterations      = (int)(Fun.Log(1.0 - probabilityOfSuccess) / Fun.Log(1.0 - (1.0 - relativeOutlierRatio).Pow(3.0)));

            var leastMedianOfSquaredResiduals = double.PositiveInfinity;

            for (int i = 0; i < numOfIterations; i++)
            {
                int  p0Index, p1Index, p2Index;
                bool randomPointsSelected;
                do
                {
                    randomPointsSelected = false;
                    p0Index = rnd.Next(points.Length);
                    p1Index = rnd.Next(points.Length);
                    p2Index = rnd.Next(points.Length);

                    if (p0Index != p1Index && p0Index != p2Index && p1Index != p2Index)
                    {
                        randomPointsSelected = true;
                    }
                } while (!randomPointsSelected);

                var p0 = points[p0Index];
                var p1 = points[p1Index];
                var p2 = points[p2Index];

                // check if points are degenerated -> same position or all along a line -> continue then
                var candidatePlane = new Plane3d(p0, p1, p2);
                if (candidatePlane.Normal.LengthSquared.IsTiny())
                {
                    continue;
                }

                var medianOfSquaredResiduals = points
                                               .Where((p, k) => k != p0Index && k != p1Index && k != p2Index)
                                               .Select(p => candidatePlane.Height(p).Square())
                                               .Median();

                if (medianOfSquaredResiduals < leastMedianOfSquaredResiduals)
                {
                    leastMedianOfSquaredResiduals = medianOfSquaredResiduals;
                    plane = candidatePlane;
                }
            }

            return(plane);
        }
예제 #8
0
        /// <summary>
        /// Clips the mesh with the given plane.
        /// The part on the positive hals will remain.
        /// Will return null when everything is clipped.
        /// Cap-faces will be built on everything that is cut open by the plane (non-convex cap faces are not handled properly).
        /// Only works for meshes without Face-/FaceVertexAttributes -> attributes will be invalid for generated faces.
        /// </summary>
        public PolyMesh ClipByPlane(Plane3d plane, double epsilon = 1e-7)
        {
            var clippedMesh = SplitOnPlane(plane, epsilon).Item2;

            // in case everything is clipped away
            if (clippedMesh == null)
            {
                return(null);
            }

            // 1. go trough all edges
            // 2. if edge on plane -> test if there is an open face along the plane (border edges)

            var vertexOnPlane = clippedMesh.PositionArray.Map(clippedMesh.VertexCount, p => plane.Height(p).Abs() <= epsilon);

            clippedMesh.BuildTopology();

            // use indices so an edge can be removed no matter what "ref"
            var edges = new IntSet(clippedMesh.EdgeCount);

            foreach (var e in clippedMesh.Edges)
            {
                if (e.IsValid)
                {
                    edges.Add(e.Index);
                }
            }

            var capFaceEdges   = new List <PolyMesh.Edge>();
            var capFaceEdgeSet = new IntSet();

            while (edges.Count > 0)
            {
                var e = clippedMesh.GetEdge(edges.First());
                edges.Remove(e.Index);

                if (e.IsAnyBorder &&
                    vertexOnPlane[e.FromVertexIndex] &&
                    vertexOnPlane[e.ToVertexIndex])
                {
                    // try to find other edges along the plane
                    // keep set of all edges so in case there the loop runs into a degenerated case an exit is possible (will generate degenerated face)
                    capFaceEdges.Clear();
                    capFaceEdgeSet.Clear();

                    var currEdge = e;
                    do
                    {
                        if (currEdge.IsValid)
                        {
                            capFaceEdges.Add(currEdge);
                            capFaceEdgeSet.Add(currEdge.Index);
                        }

                        // find next edge at start-vertex along plane
                        // the new cap face should have winding order start<-to becaues it is on the opposite of the current face-edge
                        currEdge = currEdge.FromVertex.Edges
                                   .Select(x => x.FromVertexIndex == currEdge.FromVertexIndex ? x.Opposite : x)
                                   .FirstOrDefault(x => x.IsAnyBorder && x.Index != currEdge.Index && vertexOnPlane[x.FromVertexIndex], PolyMesh.Edge.Invalid);
                    } while (currEdge.IsValid &&
                             currEdge.Index != e.Index &&
                             !capFaceEdgeSet.Contains(currEdge.Index));

                    if (capFaceEdges.Count > 2)
                    {
                        // add cap-face
                        foreach (var fe in capFaceEdges.Skip(1))
                        {
                            edges.Remove(fe.Index);
                        }

                        clippedMesh.AddFace(capFaceEdges.Select(fe => fe.ToVertexIndex).ToArray());
                    }
                }
            }

            // clear topology (is invalid if face has been added)
            clippedMesh.ClearTopology();

            return(clippedMesh);
        }
예제 #9
0
        /// <summary>
        /// Splits the mesh on the specified plane in a pair containing the negative
        /// (element 0) and the positive side (element 1) of the plane.
        /// Note that the normal vector of the plane need not be normalized.
        /// </summary>
        public (PolyMesh, PolyMesh) SplitOnPlane(
            Plane3d plane, double epsilon, SplitterOptions options)
        {
            var heightArray = m_positionArray.Map(
                m_vertexCount, p => plane.Height(p));
            var splitter = new PolygonSplitter(
                m_firstIndexArray, m_faceCount,
                m_vertexIndexArray, m_faceVertexCountRange.Max,
                heightArray, epsilon, options);

            var result = (default(PolyMesh), default(PolyMesh));

            for (int side = 0; side < 2; side++)
            {
                var fia = splitter.FirstIndexArray(side);
                if (fia != null)
                {
                    if (splitter.IsEqualToInput(side))
                    {
                        //result[side] = this;
                        switch (side)
                        {
                        case 0:
                            result = (this, result.Item2);
                            break;

                        case 1:
                            result = (result.Item1, this);
                            break;

                        default:
                            throw new IndexOutOfRangeException();
                        }
                    }
                    else
                    {
                        var pm = new PolyMesh()
                        {
                            FirstIndexArray    = fia,
                            VertexIndexArray   = splitter.VertexIndexArray(side),
                            InstanceAttributes = InstanceAttributes,
                            FaceAttributes     = FaceIAttributes.Select(
                                a => splitter.FaceAttribute(side, a)).ToSymbolDict(),
                            VertexAttributes = VertexIAttributes.Select(
                                a => splitter.VertexAttribute(side, a)).ToSymbolDict(),
                            FaceVertexAttributes = FaceVertexIAttributes.Select(
                                a => splitter.FaceVertexAttribute(side, a)).ToSymbolDict(),
                        };

                        //result[side] = pm;
                        switch (side)
                        {
                        case 0:
                            result = (pm, result.Item2);
                            break;

                        case 1:
                            result = (result.Item1, pm);
                            break;

                        default:
                            throw new IndexOutOfRangeException();
                        }
                    }
                }
            }

            return(result);
        }
예제 #10
0
 /// <summary>
 /// Returns true if this node is fully inside the negative halfspace defined by given plane.
 /// </summary>
 public static bool InsideNegativeHalfSpace(this PointSetNode self, Plane3d plane)
 {
     self.BoundingBox.GetMinMaxInDirection(-plane.Normal, out V3d min, out V3d max);
     return(plane.Height(min) < 0);
 }
예제 #11
0
 /// <summary>
 /// Returns true if this node intersects the negative halfspace defined by given plane.
 /// </summary>
 public static bool IntersectsNegativeHalfSpace(this PointSetNode self, Plane3d plane)
 => self.Corners.Any(p => plane.Height(p) < 0);
예제 #12
0
 /// <summary>
 /// Projects a point onto the plane (shortest distance).
 /// </summary>
 public static V3d Project(this Plane3d plane, V3d p) => p - plane.Height(p) * plane.Normal;