Projecting 3D Points Into 2D Space

While finishing up my OBJ optimizer, I found out that how I handle projecting points has edge cases that make it fail, I don’t exactly understand why it works. I have a feeling that it isn’t a good method, so I want to see if anyone has advise for this.
I need to project a set of co-planar triangles (set of points) to 2d space where the relative positions and distances are kept. Since I am working with triangles, there is a guarentee that there will be 3 points, so calculating the Normal with Normal = (Pos2 - Pos1):Cross(Pos3 - Pos1).unit can be used. Past that, I am not sure what to do. This process does not need to be reversible, since I am storing them in a table.

1 Like

The cross product can become (0,0,0) if two points overlap, or (Pos2 - Pos1) points in the same direction as (Pos3 - Pos1), or if they point in exactly opposite directions. Then you take the .unit on a vector of size 0, which results in (NaN, NaN, NaN).

3 Likes

Completely forgot about that case. I already handle it in my code (“zero width” lines in triangles), so it would be safe to assume in this case that the normal will never be (NaN, NaN, NaN).

What do you mean with zero width exactly? Excluding triangles that have at least 1 side that is ~0 in length? (If so, that’s not enough)

That is the case, but I do realize the case you are talking about. I probably should modify by code to do 0-area instead, which should fix that problem.

If they’re coplanar, you can get the plane from any one. Pick a single point (pt_0) and use the normal computed as shown in your OP (make sure it’s unit-length, and call it normal).

We’ll represent this plane as a CFrame with the z vector pointing in the direction of the normal.

plane = CFrame.new(pt_0, normal) --note: this sometimes gives funky results, so use the full 12-number constructor for better results.

Then each point can be converted to a point on that plane as:

pt_on_plane = plane:pointToObjectSpace(pt)

pt_on_plane will be a Vector3 where the z component represents the separation from the plane along the normal. You can zero-out the z component and convert it back to world space, but you may find it easier to leave it in object space to do work and then convert it at the end.

2 Likes

I see what you are saying with that. The problem is that I am porting this to other languages, and I don’t understand CFrames enough to re-implement them to do this.

Ok, well… without CFrame:

If your triangle has three points, a, b, and c (doesn’t matter which is which):

  1. Get the rightVector as (a - b)
  2. Get the backVector as rightVector:Cross(c - b)
  3. Get the upVector as backVector:Cross(rightVector)
  4. Let a be the “origin” of the plane, renamed as o

For each point, pt, convert it to the plane’s space as:

x = (pt - o):Dot(rightVector);
y = (pt - o):Dot(upVector);
z = (pt - o):Dot(backVector);

Then proceed as before. If you need to map back to world space, it’s:

o + x * rightVector + y * upVector + z * backVector;
3 Likes

Looks like it works.

One thing I should mention is that the z calculation seems to be redundant because it is always going to be zero. Also thanks for figuring out a way to return the original point, so I can just build that in instead of storing a dictionary, in case line splitting is added for my optimizer in the future.

Edit:
The case that broke the last solver now works! Originally, it generated either no triangles or 1 triangle.
image

4 Likes