10

In 3-D space I have an unordered set of, say, 6 points; something like this:

           (A)*
                          (C)*
(E)*
                         (F)*
     (B)*

                  (D)*

The points form a 3-D contour but they are unordered. For unordered I mean that they are stored in an

unorderedList = [A - B - C - D - E - F]

I just want to reorganize this list starting from an arbitrary location (let's say point A) and traversing the points clockwise or counter-clockwise. Something like this:

orderedList = [A - E - B - D - F - C]

or

orderedList = [A - C - F - D - B - E]

I'm trying to implement an algorithm as simple as possible, since the set of points in mention corresponds to a N-ring neighborhood of each vertex on a mesh of ~420000 points, and I have to do this for each point on the mesh.

Some time ago there was a similar discussion regarding points in 2-D, but for now it's not clear for me how to go from this approach to my 3-D scenario.

Community
  • 1
  • 1
CodificandoBits
  • 970
  • 9
  • 18

2 Answers2

7

The notion of "clockwise" or "counterclockwise" is not well-defined without an axis and orientation! (proof: What if you looked at those points from the other side of your monitor screen, or flipped them, for example!)

You must define an axis and orientation, and specify it as an additional input. Ways to specify it include:

  • a line (1x=2y=3z), using the right-hand rule
  • a (unit) vector (A_x, A_y, A_z), using the right-hand rule; this is the preferred way to do so

In order to determine the orientation, you have to look deeper at your problem: You must define a "up" and "down" size of the mesh. Then for each set of points, you must take the centroid (or another "inside" point) and construct a unit vector pointing "up" which is normal to the surface. (One way to do this would be to find the least-squares-fit plane, then find the two perpendicular vectors through that point, picking the one in the "up" direction.)


You will need to use any of the above suggestions to determine your axis. This will allow you to reformulate your problem as follows:

Inputs:

  • the set of points {P_i}
  • an axis, which we shall call "the z-axis" and treat as a unit vector centered on the centroid (or somewhere "inside") of the points
  • an orientation (e.g. counterclockwise) chosen by one of the above methods

Setup:

Algorithm:

Once you have the angles, you can just sort them.

ninjagecko
  • 88,546
  • 24
  • 137
  • 145
  • 1
    All good except for `atan2`, in the plane you should just compare points using vector product. – unkulunkulu Jul 30 '11 at 05:28
  • There is nothing wrong with using `atan2`. Nevertheless, unkulunkulu's suggestion is interesting! Normally `(P1 x P2) · Z` would give you an inconsistent ordering, but if you combine it with the proper sorting technique like quicksort or a pivot-based sort, it will work. This is because cross product on a circle says "is it faster to get there by going CW or CCW?" *Other sorting algorithms may fail.* Otherwise using the cross-product is hard; for example if you try to sort by `(X x P2) · Z`, `sin` is not invertible in the range 0deg-180deg! Also one has to be careful of normalization as usual. – ninjagecko Jul 30 '11 at 07:32
  • ninjagecko, I think the approach you suggested (projecting x,y,z points onto the best-fitting plane) seems adequate in my case. However I'm thinking of an hypothetical problem: let's say that my best-fitting plane is z=0 (normal: 0,0,1) and that two of my points to be projected share the same x and y coordinates (differing only on the z-coordinate). In this case the projection from 3-D to 2-D will look as if they were only 1 point! Am I right? Am I missing something? If that's the case how to overcome this issue? – CodificandoBits Jul 30 '11 at 07:42
  • @Miguel: sorry, I didn't bother stating that. That is equivalent to asking "what should I do in my sorting algorithm if two values are the same?" The answer is that they are and you should treat them that way. If this bothers you, you can always choose a different axis. A similar issue arises with determining whether `(0,1)` is 0degrees or 360degrees; it's arbitrary due to the nature of your stated problem. In your case, I do not anticipate this bothering you since your points were probably generated "non-overlappingly" in a local neighborhood. – ninjagecko Jul 30 '11 at 12:00
1

I can't attest for the efficiency of this code, but it works, and you can optimize parts of it as needed, I'm just not good at it.
Code is in C#, using system collection classes, and linq.
Vector3 is a class with floats x, y, z, and static vector math functions.
Node is a class with Vector3 variable called pos

//Sort nodes with positions in 3d space.
//Assuming the points form a convex shape.
//Assuming points are on a single plain (or close to it).

public List<Node> sortVerticies( Vector3 normal, List<Node> nodes ) {

    Vector3 first = nodes[0].pos;

    //Sort by distance from random point to get 2 adjacent points.
    List<Node> temp = nodes.OrderBy(n => Vector3.Distance(n.pos, first ) ).ToList();

    //Create a vector from the 2 adjacent points,
    //this will be used to sort all points, except the first, by the angle to this vector.
    //Since the shape is convex, angle will not exceed 180 degrees, resulting in a proper sort.
    Vector3 refrenceVec = (temp[1].pos - first);

    //Sort by angle to reference, but we are still missing the first one.
    List<Node> results = temp.Skip(1).OrderBy(n => Vector3.Angle(refrenceVec,n.pos - first)).ToList();

    //insert the first one, at index 0.
    results.Insert(0,nodes[0]);

    //Now that it is sorted, we check if we got the direction right, if we didn't we reverse the list.
    //We compare the given normal and the cross product of the first 3 point.
    //If the magnitude of the sum of the normal and cross product is less than Sqrt(2) then then there is more than 90 between them.
    if ( (Vector3.Cross( results[1].pos-results[0].pos, results[2].pos - results[0].pos ).normalized + normal.normalized).magnitude < 1.414f ) {
        results.Reverse();
    }

    return results;
}
Max Izrin
  • 938
  • 1
  • 11
  • 17