Repository: bevyengine/bevy
File: crates/bevy_ui_render/src/lib.rs (Rust)

Code before:
                            positions[1] + positions_diff[1].extend(0.),
                            positions[2] + positions_diff[2].extend(0.),
                            positions[3] + positions_diff[3].extend(0.),
                        ];

                        let points = [
                            points[0] + positions_diff[0],
                            points[1] + positions_diff[1],
                            points[2] + positions_diff[2],
                            points[3] + positions_diff[3],
                        ];

                        let transformed_rect_size = transform.transform_vector2(rect_size);

                        // Don't try to cull nodes that have a rotation
                        // In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
                        // In those two cases, the culling check can proceed normally as corners will be on
                        // horizontal / vertical lines
                        // For all other angles, bypass the culling check
                        // This does not properly handles all rotations on all axis
                        if transform.x_axis[1] == 0.0 {
                            // Cull nodes that are completely clipped
                            if positions_diff[0].x - positions_diff[1].x
                                >= transformed_rect_size.x.abs()
                                || positions_diff[1].y - positions_diff[2].y
                                    >= transformed_rect_size.y.abs()
                            {
                                continue;
                            }
                        }
                        let uvs = if flags == shader_flags::UNTEXTURED {
                            [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y]
                        } else {
                            let image = gpu_images
                                .get(extracted_uinode.image)
                                .expect("Image was checked during batching and should still exist");
                            // Rescale atlases. This is done here because we need texture data that might not be available in Extract.
                            let atlas_extent = atlas_scaling
                                .map(|scaling| image.size_2d().as_vec2() * scaling)
                                .unwrap_or(uinode_rect.max);
                            if *flip_x {
                                core::mem::swap(&mut uinode_rect.max.x, &mut uinode_rect.min.x);
                                positions_diff[0].x *= -1.;
                                positions_diff[1].x *= -1.;
                                positions_diff[2].x *= -1.;
                                positions_diff[3].x *= -1.;
                            }
                            if *flip_y {
                                core::mem::swap(&mut uinode_rect.max.y, &mut uinode_rect.min.y);
                                positions_diff[0].y *= -1.;
                                positions_diff[1].y *= -1.;

Review comment (bug):
It would be less fragile if we assigned the absolute value to `transform_rect_size` before the if statement I think. 

The `box_shadow`, `gradient`, `uimaterial_pipeline`, and `ui_texture_slice_pipeline` all have the same bug too, if you want to fix those as well while you are here 😅 

Code after:
                            positions[1] + positions_diff[1].extend(0.),
                            positions[2] + positions_diff[2].extend(0.),
                            positions[3] + positions_diff[3].extend(0.),
                        ];

                        let points = [
                            points[0] + positions_diff[0],
                            points[1] + positions_diff[1],
                            points[2] + positions_diff[2],
                            points[3] + positions_diff[3],
                        ];

                        let transformed_rect_size = transform.transform_vector2(rect_size).abs();

                        // Don't try to cull nodes that have a rotation
                        // In a rotation around the Z-axis, this value is 0.0 for an angle of 0.0 or π
                        // In those two cases, the culling check can proceed normally as corners will be on
                        // horizontal / vertical lines
                        // For all other angles, bypass the culling check
                        // This does not properly handles all rotations on all axis
                        if transform.x_axis[1] == 0.0 {
                            // Cull nodes that are completely clipped
                            if positions_diff[0].x - positions_diff[1].x >= transformed_rect_size.x
                                || positions_diff[1].y - positions_diff[2].y
                                    >= transformed_rect_size.y
                            {
                                continue;
                            }
                        }
                        let uvs = if flags == shader_flags::UNTEXTURED {
                            [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y]
                        } else {
                            let image = gpu_images
                                .get(extracted_uinode.image)
                                .expect("Image was checked during batching and should still exist");
                            // Rescale atlases. This is done here because we need texture data that might not be available in Extract.
                            let atlas_extent = atlas_scaling
                                .map(|scaling| image.size_2d().as_vec2() * scaling)
                                .unwrap_or(uinode_rect.max);
                            if *flip_x {
                                core::mem::swap(&mut uinode_rect.max.x, &mut uinode_rect.min.x);
                                positions_diff[0].x *= -1.;
                                positions_diff[1].x *= -1.;
                                positions_diff[2].x *= -1.;
                                positions_diff[3].x *= -1.;
                            }
                            if *flip_y {
                                core::mem::swap(&mut uinode_rect.max.y, &mut uinode_rect.min.y);
                                positions_diff[0].y *= -1.;
                                positions_diff[1].y *= -1.;
                                positions_diff[2].y *= -1.;