You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
RendererMesh.render(renderer, viewport, mesh, model_matrix, camera) returns a list of matplotlib Artists.
Pull buffers (matplotlib_renderer_mesh.py:45-71) — TransBufUtils.to_buffer then Bufferx.to_numpy on positions, indices, model/view/projection matrices, face colors, edge colors, edge widths. Colors are normalized from 0-255 → 0-1 (/ 255.0).
Per-vertex → per-face broadcast (matplotlib_renderer_mesh.py:79-95) — matplotlib's PolyCollection wants one color/width per face. A local _to_per_face helper handles three cases:
already per-face → leave alone
per-vertex → pick the value at the first vertex of each triangle (array[indices[:, 0]])
length-1 → broadcast to face_count
MVP transform (matplotlib_renderer_mesh.py:110-113) — MathUtils.compute_mvp_matrix(...) then apply_transform_matrix(vertices, mvp) → NDC. Reshape into (face_count, 3, 3) triangles.
Drop Z (matplotlib_renderer_mesh.py:120) — faces_vertices_2d = faces_vertices_ndc[..., :2]. Z is kept around for sorting only.
Painter's sort (matplotlib_renderer_mesh.py:146-156) — when face_sorting=True, sort faces by mean NDC z descending (far → near), and re-index colors/edge_colors/edge_widths with the same permutation. Comment notes the limitation: this works within the artist; cross-object ordering would need set_zorder and is currently disabled.
Face culling (matplotlib_renderer_mesh.py:162-168) — delegates to RendererUtils.compute_faces_visible which uses the 2D cross product of edges. Sign decides CCW vs CW; magnitude < 1e-6 is treated as degenerate. FrontSide/BackSide/BothSides each pick a different predicate. Filter mask is applied to all per-face arrays.
Artist cache (matplotlib_renderer_mesh.py:173-185) — one PolyCollection per mesh uuid, stored in renderer._artists. Created hidden on first render, then set_visible(True).
uvs and normals are completely ignored — the matplotlib path is "flat triangles in screen space"; no shading, no texturing. That fits MeshBasicMaterial's name, but reinforces the open question from Mesh implementation: overview & rough edges #18 about whether those buffers should be required at construction.
Per-vertex color collapses to the first vertex of each triangle — no per-face averaging, no gouraud interpolation. So an OBJ with per-vertex colors will look blocky and asymmetric (rotating the index order changes the look).
Culling uses the screen-space cross product, not a real normal. This is correct for triangles after a perspective divide (the sign of the 2D cross product matches the front-facing orientation), but it diverges from any culling that would use geometry.normals.
Z-order across objects is commented out (matplotlib_renderer_mesh.py:187-192) — RendererUtils.update_single_artist_zorder exists but is disabled, so two overlapping meshes will paint in registration order, not depth order.
Camera position is never directly used — culling depends only on post-MVP geometry, so the camera enters only through the view/projection matrices.
How the matplotlib mesh renderer works —
src/gsp_matplotlib/renderer/matplotlib_renderer_mesh.py.Pipeline
RendererMesh.render(renderer, viewport, mesh, model_matrix, camera)returns a list of matplotlibArtists.Pull buffers (matplotlib_renderer_mesh.py:45-71) —
TransBufUtils.to_bufferthenBufferx.to_numpyon positions, indices, model/view/projection matrices, face colors, edge colors, edge widths. Colors are normalized from0-255→0-1(/ 255.0).Per-vertex → per-face broadcast (matplotlib_renderer_mesh.py:79-95) — matplotlib's
PolyCollectionwants one color/width per face. A local_to_per_facehelper handles three cases:array[indices[:, 0]])MVP transform (matplotlib_renderer_mesh.py:110-113) —
MathUtils.compute_mvp_matrix(...)thenapply_transform_matrix(vertices, mvp)→ NDC. Reshape into(face_count, 3, 3)triangles.Drop Z (matplotlib_renderer_mesh.py:120) —
faces_vertices_2d = faces_vertices_ndc[..., :2]. Z is kept around for sorting only.Painter's sort (matplotlib_renderer_mesh.py:146-156) — when
face_sorting=True, sort faces by mean NDC z descending (far → near), and re-indexcolors/edge_colors/edge_widthswith the same permutation. Comment notes the limitation: this works within the artist; cross-object ordering would needset_zorderand is currently disabled.Face culling (matplotlib_renderer_mesh.py:162-168) — delegates to
RendererUtils.compute_faces_visiblewhich uses the 2D cross product of edges. Sign decides CCW vs CW; magnitude <1e-6is treated as degenerate.FrontSide/BackSide/BothSideseach pick a different predicate. Filter mask is applied to all per-face arrays.Artist cache (matplotlib_renderer_mesh.py:173-185) — one
PolyCollectionper mesh uuid, stored inrenderer._artists. Created hidden on first render, thenset_visible(True).Update artist (matplotlib_renderer_mesh.py:199-202) —
set_verts,set_facecolor,set_edgecolor,set_linewidth.Things to notice
uvsandnormalsare completely ignored — the matplotlib path is "flat triangles in screen space"; no shading, no texturing. That fitsMeshBasicMaterial's name, but reinforces the open question from Mesh implementation: overview & rough edges #18 about whether those buffers should be required at construction.geometry.normals.Mesh.sanity_check_attributes_bufferis called at matplotlib_renderer_mesh.py:101, but that method is apass— so the call is currently free, but also free of safety. Same observation as Mesh implementation: overview & rough edges #18.RendererUtils.update_single_artist_zorderexists but is disabled, so two overlapping meshes will paint in registration order, not depth order.Links pinned to commit
6f3b86c.