Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect drawing order when plotting polygons in separate Poly3DCollections #3894

Closed
maxalbert opened this issue Dec 5, 2014 · 4 comments
Closed

Comments

@maxalbert
Copy link
Contributor

There is a bug when using multiple Poly3DCollections which can result in incorrect drawing order so that a polygon that should be on top of another one is actually displayed underneath. The script below shows a minimal example.

The example script creates two figure windows. In the first one three triangles are drawn using a single Poly3DCollection. This works as expected. In the second one the triangles are drawn using two separate Poly3DCollections. In this case the green triangle (from the second collection) is displayed underneath the red one (from the first collection) even though it should be on top of it. Interestingly, the bug disappears if the blue triangle (which does not overlap with the green one) is removed from the first collection.

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection


def create_figure():
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    ax.set_xlim(-130, 130)
    ax.set_ylim(-130, 130)
    ax.set_zlim(0, 100)
    return fig


# Red triangle
T1 = [[-55.9, 47.0, 50],
      [23.2, 52.5, 50],
      [-7.3, -55.4, 50]]

# Blue triangle
T2 = [[81.3, -54.1, 50],
      [87.7, -96.6, 50],
      [60.6, -96.9, 50]]

# Green triangle
T3 = [[68.6, -64.2, 70],
      [31.3, -64.8, 70],
      [51.8, -23.3, 70]]


# Add all triangles in a single collection. This works as expected.
fig = create_figure()
coll = Poly3DCollection([T1, T2, T3], facecolors=['r', 'b', 'g'], edgecolors=['r', 'b', 'g'])
fig.gca().add_collection(coll)
fig.gca().set_title("Correct behaviour")


# Add triangles in two separate collections. This exposes a bug where
# the green triangle is displayed underneath the red one even though
# it should be on top of it. Note, however, that if we omit the blue
# triangle (T2) from the first collection then the bug disappears.
fig = create_figure()
coll1 = Poly3DCollection([T1, T2], facecolors=['r', 'b'], edgecolors=['r', 'b'])
coll2 = Poly3DCollection([T3], facecolors=['g'], edgecolors=['g'])
fig.gca().add_collection(coll1)
fig.gca().add_collection(coll2)
fig.gca().set_title("Buggy behaviour (but works if blue triangle is removed)")

plt.show()
@maxalbert
Copy link
Contributor Author

P.S.: I'm not sure if this is related to #3884, but at a glance it looks like a separate issue.

@WeatherGod
Copy link
Member

This is a separate issue, and it is a fundamental limitation of what I call the 2.1D layering engine that matplotlib uses. The layering engine needs to sort the collections and independent artists according to a single z-order value (the ".1" of 2.1D layering). So, while some of the 3D collections are implemented to layer themselves properly (for the most part) by specially ordering the draws of their own elements on the fly (at a huge performance hit), they are completely unaware of any other artists and their spatial relationship to their own drawing elements. This leads to "Escher-esque" visual effects (they don't make spatial sense).

A necessary, but not sufficient condition for this effect is that the 3D bounding boxes of artists intersect. There are some controls to help alleviate this. A collection object can either report a minimum, a maximum, or an average zsort value for itself. It defaults to minimum. This is why removing the one triangle "fixed" it.

This is a known limitation, and addressed in the documentation. Don't use mplot3d for complex scenes. There are better tools for that such as mayavi. So, I will close this issue.

@maxalbert
Copy link
Contributor Author

Thanks for the quick reply and the detailed explanation! The example I posted is actually a very stripped down version of a larger example where I plotted various "layers" (which are all parallel to the x-y plane) using triangulations. Since these layers can be trivially ordered using their z-coordinate, I thought that matplotlib would be able to automatically pick this up and display it correctly. Unfortunately, as you pointed out, it doesn't seem to be so easy. But I just tried to give it a "nudge" by calling coll.set_sort_zpos(z_coord) on my Poly3DCollections, and this makes it work correctly. Thanks for your hints!

Out of interest, would it be feasible to have a flag which forces each artist to take all the other artists into account when computing the 3D layering of a scene? I appreciate that this might be very slow, but I can imagine situations where a user would be willing to sacrifice speed if the resulting plot looks correct.

@WeatherGod
Copy link
Member

I once considered something like that (or some sort of flattening operation
that is special-cased for art3d objects), but decided against it because I
was still chasing down other rendering bugs at the time. It might make
sense to revisit that decision. It is still important to keep in mind the
ultimate intractable limitation of mplot3d: intersecting artists. For
example, a line going through a polygon, or two polygons intersecting each
other. mplot3d just simply can not handle that.

But if the individual artist elements are well-behaved, then it might be
possible to improve the rendering a bit more if we can get the artists all
aware of each other. I'll have to see how well the PolyCollection3D code
generalizes.

On Sat, Dec 6, 2014 at 1:21 PM, maxalbert notifications@github.com wrote:

Thanks for the quick reply and the detailed explanation! The example I
posted is actually a very stripped down version of a larger example where I
plotted various "layers" (which are all parallel to the x-y plane) using
triangulations. Since these layers can be trivially ordered using their
z-coordinate, I thought that matplotlib would be able to automatically pick
this up and display it correctly. Unfortunately, as you pointed out, it
doesn't seem to be so easy. But I just tried to give it a "nudge" by
calling coll.set_sort_zpos(z_coord) on my Poly3DCollections, and this
makes it work correctly. Thanks for your hints!

Out of interest, would it be feasible to have a flag which forces each
artist to take all the other artists into account when computing the 3D
layering of a scene? I appreciate that this might be very slow, but I can
imagine situations where a user would be willing to sacrifice speed if the
resulting plot looks correct.


Reply to this email directly or view it on GitHub
#3894 (comment)
.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants