Transform 3D polygon to 2D, perform clipping, and transform back to 3D
Question: My problem is that I have two walls, represented as 2D planes in 3D space,
(`wallA` and `wallB`). These walls are overlapping. I need to convert that
into three wall sections, one for the `wallA.intersect(wallB)`, one for
`wallA.diff(wallB)`, and one for `wallB.diff(wallA)`.

What I think I need to to do is rotate them both into 2D space, without
changing their overlaps, perform the clipping to identify the diffs and
intersect, then rotate the new walls back into the original plane.

The walls are not necessarily vertical, otherwise the problem might be
simpler.

The clipping part of my problem is easily solved in 2D, using `pyclipper`.
What I'm having trouble with is the algorithm for recoverably rotating the
walls into 2D.

From what I can understand, something similar to but not exactly the same as
the steps in [this
question](http://stackoverflow.com/questions/6023166/rotating-a-3d-polygon-
into-xy-plane-while-maintaining-orientation). I've looked at
[`transforms3D`](https://pypi.python.org/pypi/transforms3d) which looks really
useful, but can't quite understand which, or what combination, of the
functions I need to use to reproduce that algorithm.

Here's an example of what I'm trying to achieve, using a really simple example
of a pair of 2 x 2 vertical surfaces that have an overlapping 1 x 1 square in
one corner.

    
    
    import pyclipper as pc
    
    wallA= [(0,0,2), (2,0,2), (2,0,0), (0,0,0)]
    wallB = [(1,0,3), (3,0,3), (3,0,1), (1,0,1)]
    expected_overlaps = [[(1,0,2), (2,0,2), (2,0,1), (1,0,1)]]
    
    wallA_2d = transform_to_2D(wallA, <whatever else is needed>)
    wallB_2d = transform_to_2D(wallB, <whatever else is needed>)
    
    scaledA = pc.scale_to_clipper(wallA_2d)
    scaledB = pc.scale_to_clipper(wallB_2d)
    clipper = pc.Pyclipper()
    clipper.AddPath(scaledA, poly_type=pc.PT_SUBJECT, closed=True)
    clipper.AddPath(scaledB, poly_type=pc.PT_CLIP, closed=True)
    
    # just showing the intersection - differences are handled similarly
    intersections = clipper.Execute(
        pc.CT_INTERSECTION, pc.PFT_NONZERO, pc.PFT_NONZERO)
    
    intersections = [pc.scale_from_clipper(i) for i in intersections]
    overlaps = [transform_to_3D(i, <whatever else is needed>) for i in intersections]
    
    assert overlaps  == expected_overlaps
    

What I'm looking for is an explanation of the steps required to write
`transform_to_2d` and `transform_to_3d`.


Answer: Rather than rotating, you can simply project. The key is to map the 3d space
onto a 2d plane in a way that you can then reverse. (Any distortion resulting
from the projection will be undone when you map back.) To do this, you should
first find the plane that contains both of your walls. Here is some example
code:

    
    
    wallA = [(0,0,2), (2,0,2), (2,0,0), (0,0,0)]
    wallB = [(1,0,3), (3,0,3), (3,0,1), (1,0,1)]
    v = (0, 1, 0) # the normal vector
    a = 0 # a number so that v[0] * x + v[1] * y + v[2] * z = a is the equation of the plane containing your walls
    
    # To calculate the normal vector in general, 
    # you would take the cross product of any two 
    # vectors in the plane of your walls, e.g.
    # (wallA[1] - wallA[0]) X (wallA[2] - wallA[0]).
    # You can then solve for a.
    
    proj_axis = max(range(3), key=lambda i: abs(v[i])) 
    # this just needs to be any number such that v[proj_axis] != 0
    
    def project(x):
        # Project onto either the xy, yz, or xz plane. (We choose the one that avoids degenerate configurations, which is the purpose of proj_axis.)
        # In this example, we would be projecting onto the xz plane.
        return tuple(c for i, c in enumerate(x) if i != proj_axis)
    
    def project_inv(x):
        # Returns the vector w in the walls' plane such that project(w) equals x.
        w = list(x)
        w[proj_axis:proj_axis] = [0.0]
        c = a
        for i in range(3):
            c -= w[i] * v[i]
        c /= v[proj_axis]
        w[proj_axis] = c
        return tuple(w)
    
    projA = [project(x) for x in wallA]
    projB = [project(x) for x in wallB]
    proj_intersection = intersection(projA, projB) # use your 2d algorithm here
    intersection = [project_inv(x) for x in proj_intersection] # this is your intersection in 3d; you can do similar things for the other pieces
    

