66#![ expect( clippy:: identity_op) ]
77
88use emath:: {
9- GuiRounding as _, NumExt as _, Pos2 , Rangef , Rect , Rot2 , Vec2 , fast_midpoint, pos2, remap, vec2,
9+ GuiRounding as _, NumExt as _, Pos2 , Rangef , Rect , Rot2 , TSTransform , Vec2 , fast_midpoint,
10+ pos2, remap, vec2,
1011} ;
1112
1213use crate :: {
@@ -1477,20 +1478,36 @@ impl Tessellator {
14771478 clipped_shape : ClippedShape ,
14781479 out_primitives : & mut Vec < ClippedPrimitive > ,
14791480 ) {
1480- let ClippedShape { clip_rect, shape } = clipped_shape;
1481+ let ClippedShape {
1482+ clip_rect,
1483+ shape,
1484+ transform_after_tessellation : transform,
1485+ } = clipped_shape;
14811486
14821487 if !clip_rect. is_positive ( ) {
14831488 return ; // skip empty clip rectangles
14841489 }
14851490
1491+ if !transform. is_valid ( ) {
1492+ return ;
1493+ }
1494+
14861495 if let Shape :: Vec ( shapes) = shape {
14871496 for shape in shapes {
1488- self . tessellate_clipped_shape ( ClippedShape { clip_rect, shape } , out_primitives) ;
1497+ self . tessellate_clipped_shape (
1498+ ClippedShape {
1499+ clip_rect,
1500+ shape,
1501+ transform_after_tessellation : transform,
1502+ } ,
1503+ out_primitives,
1504+ ) ;
14891505 }
14901506 return ;
14911507 }
14921508
1493- if let Shape :: Callback ( callback) = shape {
1509+ if let Shape :: Callback ( mut callback) = shape {
1510+ callback. rect = transform * callback. rect ;
14941511 out_primitives. push ( ClippedPrimitive {
14951512 clip_rect,
14961513 primitive : Primitive :: Callback ( callback) ,
@@ -1522,8 +1539,21 @@ impl Tessellator {
15221539 let out = out_primitives. last_mut ( ) . unwrap ( ) ;
15231540
15241541 if let Primitive :: Mesh ( out_mesh) = & mut out. primitive {
1525- self . clip_rect = clip_rect;
1526- self . tessellate_shape ( shape, out_mesh) ;
1542+ if transform == TSTransform :: IDENTITY {
1543+ self . clip_rect = clip_rect;
1544+ self . tessellate_shape ( shape, out_mesh) ;
1545+ } else {
1546+ // Tessellate in the shape's own coordinate space, so that everything lands on the
1547+ // pixel grid there, and only then move the finished vertices into place.
1548+ // Culling has to happen in that same space, so map the clip rect back into it:
1549+ self . clip_rect = transform. inverse ( ) * clip_rect;
1550+
1551+ let vertex_start = out_mesh. vertices . len ( ) ;
1552+ self . tessellate_shape ( shape, out_mesh) ;
1553+ for vertex in & mut out_mesh. vertices [ vertex_start..] {
1554+ vertex. pos = transform * vertex. pos ;
1555+ }
1556+ }
15271557 } else {
15281558 unreachable ! ( ) ;
15291559 }
@@ -2593,10 +2623,7 @@ fn test_tessellator() {
25932623 shapes. push ( Shape :: mesh ( mesh) ) ;
25942624
25952625 let shape = Shape :: Vec ( shapes) ;
2596- let clipped_shapes = vec ! [ ClippedShape {
2597- clip_rect: rect,
2598- shape,
2599- } ] ;
2626+ let clipped_shapes = vec ! [ ClippedShape :: new( rect, shape) ] ;
26002627
26012628 let font_tex_size = [ 1024 , 1024 ] ; // unused
26022629 let prepared_discs = vec ! [ ] ; // unused
@@ -2607,6 +2634,46 @@ fn test_tessellator() {
26072634 assert_eq ! ( primitives. len( ) , 2 ) ;
26082635}
26092636
2637+ #[ test]
2638+ fn transform_before_and_after_rounding ( ) {
2639+ use crate :: * ;
2640+
2641+ fn tessellated_bounds ( clipped_shape : ClippedShape ) -> Rect {
2642+ let options = TessellationOptions {
2643+ feathering : false ,
2644+ ..Default :: default ( )
2645+ } ;
2646+ let primitives = Tessellator :: new ( 1.0 , options, [ 1024 , 1024 ] , vec ! [ ] )
2647+ . tessellate_shapes ( vec ! [ clipped_shape] ) ;
2648+ assert_eq ! ( primitives. len( ) , 1 ) ;
2649+ let Primitive :: Mesh ( mesh) = & primitives[ 0 ] . primitive else {
2650+ panic ! ( "expected a mesh" ) ;
2651+ } ;
2652+ mesh. calc_bounds ( )
2653+ }
2654+
2655+ // Off the pixel grid, so the tessellator has to round it:
2656+ let rect = Rect :: from_min_max ( pos2 ( 0.5 , 0.5 ) , pos2 ( 10.5 , 10.5 ) ) ;
2657+ let shape = Shape :: rect_filled ( rect, 0 , Color32 :: WHITE ) ;
2658+ let transform = TSTransform :: from_scaling ( 3.0 ) ;
2659+
2660+ // Transform first, then round: [1.5, 31.5] rounds to [2, 32].
2661+ let mut immediate = ClippedShape :: new ( Rect :: EVERYTHING , shape. clone ( ) ) ;
2662+ immediate. transform ( transform) ;
2663+ assert_eq ! (
2664+ tessellated_bounds( immediate) ,
2665+ Rect :: from_min_max( pos2( 2.0 , 2.0 ) , pos2( 32.0 , 32.0 ) )
2666+ ) ;
2667+
2668+ // Round first, then transform: [0.5, 10.5] rounds to [1, 11], which scales to [3, 33].
2669+ let mut after_rounding = ClippedShape :: new ( Rect :: EVERYTHING , shape) ;
2670+ after_rounding. transform_after_tessellation = transform;
2671+ assert_eq ! (
2672+ tessellated_bounds( after_rounding) ,
2673+ Rect :: from_min_max( pos2( 3.0 , 3.0 ) , pos2( 33.0 , 33.0 ) )
2674+ ) ;
2675+ }
2676+
26102677#[ test]
26112678fn path_bounding_box ( ) {
26122679 use crate :: * ;
0 commit comments