| /************************************************************ |
| PathClipper uses the Liang-Barsky line clipping algorithm (as |
| implemented in Agg) to clip the path to a given rectangle. Lines |
| will never extend outside of the rectangle. Curve segments are not |
| clipped, but are always included in their entirety. |
| */ |
| template<classVertexSource> |
| classPathClipper :publicEmbeddedQueue<3> |
| { |
| VertexSource *m_source; |
| bool m_do_clipping; |
| agg::rect_base<double> m_cliprect; |
| double m_lastX; |
| double m_lastY; |
| bool m_moveto; |
| double m_initX; |
| double m_initY; |
| bool m_has_init; |
| bool m_was_clipped; |
| |
| public: |
| PathClipper(VertexSource &source,bool do_clipping,double width,double height) |
| : m_source(&source), |
| m_do_clipping(do_clipping), |
| m_cliprect(-1.0, -1.0, width +1.0, height +1.0), |
| m_lastX(nan("")), |
| m_lastY(nan("")), |
| m_moveto(true), |
| m_initX(nan("")), |
| m_initY(nan("")), |
| m_has_init(false), |
| m_was_clipped(false) |
| { |
| // empty |
| } |
| |
| PathClipper(VertexSource &source,bool do_clipping,const agg::rect_base<double> &rect) |
| : m_source(&source), |
| m_do_clipping(do_clipping), |
| m_cliprect(rect), |
| m_lastX(nan("")), |
| m_lastY(nan("")), |
| m_moveto(true), |
| m_initX(nan("")), |
| m_initY(nan("")), |
| m_has_init(false), |
| m_was_clipped(false) |
| { |
| m_cliprect.x1 -=1.0; |
| m_cliprect.y1 -=1.0; |
| m_cliprect.x2 +=1.0; |
| m_cliprect.y2 +=1.0; |
| } |
| |
| inlinevoidrewind(unsigned path_id) |
| { |
| m_has_init =false; |
| m_was_clipped =false; |
| m_moveto =true; |
| m_source->rewind(path_id); |
| } |
| |
| intdraw_clipped_line(double x0,double y0,double x1,double y1, |
| bool closed=false) |
| { |
| unsigned moved =agg::clip_line_segment(&x0, &y0, &x1, &y1, m_cliprect); |
| // moved >= 4 - Fully clipped |
| // moved & 1 != 0 - First point has been moved |
| // moved & 2 != 0 - Second point has been moved |
| m_was_clipped = m_was_clipped || (moved !=0); |
| if (moved <4) { |
| if (moved &1 || m_moveto) { |
| queue_push(agg::path_cmd_move_to, x0, y0); |
| } |
| queue_push(agg::path_cmd_line_to, x1, y1); |
| if (closed && !m_was_clipped) { |
| // Close the path only if the end point hasn't moved. |
| queue_push(agg::path_cmd_end_poly | agg::path_flags_close, |
| x1, y1); |
| } |
| |
| m_moveto =false; |
| return1; |
| } |
| |
| return0; |
| } |
| |
| unsignedvertex(double *x,double *y) |
| { |
| unsigned code; |
| bool emit_moveto =false; |
| |
| if (!m_do_clipping) { |
| // If not doing any clipping, just pass along the vertices verbatim |
| return m_source->vertex(x, y); |
| } |
| |
| /* This is the slow path where we actually do clipping*/ |
| |
| if (queue_pop(&code, x, y)) { |
| return code; |
| } |
| |
| while ((code = m_source->vertex(x, y)) != agg::path_cmd_stop) { |
| emit_moveto =false; |
| |
| switch (code) { |
| case (agg::path_cmd_end_poly | agg::path_flags_close): |
| if (m_has_init) { |
| // Queue the line from last point to the initial point, and |
| // if never clipped, add a close code. |
| draw_clipped_line(m_lastX, m_lastY, m_initX, m_initY, |
| true); |
| }else { |
| // An empty path that is immediately closed. |
| queue_push( |
| agg::path_cmd_end_poly | agg::path_flags_close, |
| m_lastX, m_lastY); |
| } |
| // If paths were not clipped, then the above code queued |
| // something, and we should exit the loop. Otherwise, continue |
| // to the next point, as there may be a new subpath. |
| if (queue_nonempty()) { |
| goto exit_loop; |
| } |
| break; |
| |
| case agg::path_cmd_move_to: |
| |
| // was the last command a moveto (and we have |
| // seen at least one command ? |
| // if so, shove it in the queue if in clip box |
| if (m_moveto && m_has_init && |
| m_lastX >= m_cliprect.x1 && |
| m_lastX <= m_cliprect.x2 && |
| m_lastY >= m_cliprect.y1 && |
| m_lastY <= m_cliprect.y2) { |
| // push the last moveto onto the queue |
| queue_push(agg::path_cmd_move_to, m_lastX, m_lastY); |
| // flag that we need to emit it |
| emit_moveto =true; |
| } |
| // update the internal state for this moveto |
| m_initX = m_lastX = *x; |
| m_initY = m_lastY = *y; |
| m_has_init =true; |
| m_moveto =true; |
| m_was_clipped =false; |
| // if the last command was moveto exit the loop to emit the code |
| if (emit_moveto) { |
| goto exit_loop; |
| } |
| // else, break and get the next point |
| break; |
| |
| case agg::path_cmd_line_to: |
| if (draw_clipped_line(m_lastX, m_lastY, *x, *y)) { |
| m_lastX = *x; |
| m_lastY = *y; |
| goto exit_loop; |
| } |
| m_lastX = *x; |
| m_lastY = *y; |
| break; |
| |
| default: |
| if (m_moveto) { |
| queue_push(agg::path_cmd_move_to, m_lastX, m_lastY); |
| m_moveto =false; |
| } |
| |
| queue_push(code, *x, *y); |
| m_lastX = *x; |
| m_lastY = *y; |
| goto exit_loop; |
| } |
| } |
| |
| exit_loop: |
| |
| if (queue_pop(&code, x, y)) { |
| return code; |
| } |
| |
| if (m_moveto && m_has_init && |
| m_lastX >= m_cliprect.x1 && |
| m_lastX <= m_cliprect.x2 && |
| m_lastY >= m_cliprect.y1 && |
| m_lastY <= m_cliprect.y2) { |
| *x = m_lastX; |
| *y = m_lastY; |
| m_moveto =false; |
| return agg::path_cmd_move_to; |
| } |
| |
| return agg::path_cmd_stop; |
| } |
| }; |
PR summary
Based on@scottshambaugh'scomment, I took the existing 2D clipping algorithm and extended it to 3D. That turned out to be quite straightforward as it already worked on each direction independently. Most of the work was actually with regards to the plumbing to get things in and out. Then I implemented it for
Poly3DCollection
andPatch3D
.There are still a few things to finish this:
Poly3DCollection
supports masking on the input, both from the original input and also to allow combining ragged input into a single array for quicker processing. I have not tested either, and don't expect them to work properly.axlim_clip
flag; I don't know if we just want to enable this all the time and/or deprecate that option.Using the example from#8902without clipping, the bars are quite misleading, as it seems like they are in a completely different spot initially compared to when looking at them edgewise.
Screencast.From.2025-09-12.05-24-46.mp4
whereas with clipping here, we get something that isn't so weird:
Screencast.From.2025-09-12.05-25-33.mp4
This is based on#30208.
PR checklist