owned this note
owned this note
Published
Linked with GitHub
# 2d Transforms & Sorting
The purpose of this working group is to improve Bevy's 2D support by adding a specialized 2D transform.
There's an abnormally large number of closely linked PRs in development that touch this topic: A [2d transform PR], a [UI transform PR], a [y-sorting PR], as well as the [new tilemap renderer]. So part of the purpose of this document is cross-initiative coordination.
[2d transform PR]: https://github.com/bevyengine/bevy/pull/19389
[UI transform PR]: https://github.com/bevyengine/bevy/pull/16615
[y-sorting PR]: https://github.com/bevyengine/bevy/pull/19463
[new tilemap renderer]: https://hackmd.io/@bevy/Hkum_o-xll
# Proposal
+ Merge the [ui transform PR] basically as is.
+ New PR: Replace `ZIndex` and `GlobalZIndex` with a field on `UiTransform`.
+ Clean up and merge the [2d transform PR].
+ Merge the [y-sorting PR], modified so that `ZIndex` and `YSort` are fields of `Transform2d` (leave out `SortBias` for now) and `ZIndex` actually effects the `GlobalTransform`.
## Demonstration
Here's an overview of what this would look like. (WIP)
```rust=
#[derive(Component, Default)]
pub struct Transform2d {
/// X-Y Position
translation: Vec2,
/// Rotation about Z Axis
rotation: Rot2,
/// Scale in X and Y
scale: f32,
/// Z component of position
depth: Depth2d,
/// Opt-in to y sort
y_sort: bool
}
/// Determines the layer a 2d object is drawn on.
#[derive(Default)]
pub enum Depth2d {
Relative(f32), // Parent layer + i
Absolute(f32), // Set layer to i
}
impl Default for Depth2d {
fn default() -> Depth2d {
// This default makes children be drawn slightly infront
// of their parents.
Depth2d::Relative(1.0)
}
}
```
+ Mixing 2d and 3d transforms in the same hierarchy is allowed. Both compute `GlobalTransform` given the parent's `GlobalTransform` (if it exists).
+ We will use hooks to enforce archetype invariants: Adding one transform type automatically removes the other transform type.
+ Relative values for `depth` are incorperated into transform prop. `depth` is effectivly the same as the `z` position component on `Transform3d`, but with the option to specify it in absolute terms.
+ `Camera` will no longer require `Transform`. Instead `Camera3d` will require `Transform3d` and `Camera2d` will require `Transform2d`.
---
# Detailed Analysis of Related PRs
## PR #19389: 2D Transforms - Core Assessment
### What It Actually Does
- **Renames `Transform` → `Transform3d`** (with backward-compatible alias)
- **Introduces `Transform2d`** with 2D-native fields (Vec2, Rot2, f32 scale)
- **Duplicates transform propagation systems** for 2D variants
- **Updates all sprite/2D systems** to work with new types
### Critical Issues Identified from Review
1. **The "Unpleasant" Layering Problem**
- Current PR requires 3D parent entities for Z-layering
- Forces developers back into 3D thinking for 2D problems
- Example of problematic workaround:
```rust
commands.spawn((
Name("Layer five"),
Transform3d::from_translation(Vec3::Z * 5.0), // 3D parent!
children![
(MyComponent, Transform2d::from_translation(Vec2::splat(10.0)))
]
));
```
2. **Incomplete Solution Without Sorting**
- Transform2d alone doesn't solve the core 2D ergonomics problem
- Still requires manual Z-coordinate management
- Reviewer consensus: needs sorting solution in same release
### Performance Implications
**Positive**:
- Smaller memory footprint (Vec2 vs Vec3, Rot2 vs Quat)
- Reduced computational overhead for 2D operations
- Better cache locality
**Concerns**:
- Code duplication in transform systems
- Complexity in mixed 2D/3D hierarchies
## PR #19463: Sorting Components - Integration Analysis
### The Sorting Stack
```
┌─────────────────┐
│ ZIndex (i32) │ ← Primary: Layer separation
├─────────────────┤
│ YSort (marker) │ ← Secondary: Depth illusion
├─────────────────┤
│ SortBias (f32) │ ← Tertiary: Fine-tuning
└─────────────────┘
```
### Key Reviewer Concerns
1. **Naming Conflict with UI ZIndex**
- UI already has `ZIndex` component with different semantics
- Need resolution strategy before merge
2. **Per-Entity vs Per-Layer Y-Sorting**
- Current: Per-entity `YSort` marker
- Concern: May be confusing when mixed with non-Y-sorted entities
- Reviewer suggestion: Y-sorting as layer property
3. **Missing Hierarchy Propagation**
- No automatic Z-index inheritance
- Children don't inherit parent's layer by default
- Community expectation: children should inherit parent's Z-layer
### Critical Edge Cases
- **Mixed Y-Sort Entities**: Inconsistent behavior when some entities in layer have `YSort`, others don't
- **Floating Point Precision**: Y-sorting reliability with similar Y positions
- **Performance Scaling**: CPU-based sorting limits for large entity counts
## Synergy Analysis: Why Both PRs Work Better Together
### The Perfect Marriage
PR #19463 directly solves PR #19389's biggest problem:
**Before (PR #19389 alone)**:
```rust
// Awkward 3D parent workaround
commands.spawn((
Transform3d::from_translation(Vec3::Z * 5.0),
children![(Transform2d::default(), SpriteBundle::default())]
));
```
**After (Both PRs)**:
```rust
// Clean, ergonomic 2D solution
commands.spawn((
Transform2d::from_translation(Vec2::new(x, y)),
ZIndex::new(5),
YSort, // Optional depth sorting
SpriteBundle::default()
));
```
### Integration Recommendation: Unified Transform2d
The proposed `Transform2d` with integrated depth/sorting fields addresses all core issues:
```rust
#[derive(Component, Default)]
pub struct Transform2d {
translation: Vec2,
rotation: Rot2,
scale: f32,
depth: Depth2d, // Integrated Z-layering
y_sort: bool, // Integrated Y-sorting
}
```
**Benefits**:
- Single component for all 2D transform needs
- Eliminates need for separate sorting components
- Provides both relative and absolute depth control
- Maintains transform propagation semantics
---
# Migration Strategy & Risk Assessment
## Phase 1: Foundation (Immediate)
1. **Resolve naming conflicts** - Rename 2D sorting components
2. **Integrate depth into Transform2d** - Eliminate 3D parent workaround
3. **Implement hierarchy propagation** - Children inherit parent depth by default
## Phase 2: Optimization (Follow up PRs)
1. **Performance validation** - Benchmark sorting with realistic 2D scenes
2. **GPU-based sorting** - For high entity count scenarios
3. **Advanced sorting features** - Custom sort keys, spatial partitioning
## Critical Success Factors
### Must Resolve Before Merge
1. **UI ZIndex naming conflict** - Clear disambiguation strategy
2. **Hierarchy propagation** - Automatic depth inheritance
3. **Performance benchmarking** - Validate sorting overhead acceptable
### High-Risk Areas
1. **Breaking changes** - Large mechanical refactoring
2. **Performance regression** - Additional sorting complexity
3. **User confusion** - Dual transform system complexity
### Mitigation Strategies
1. **Comprehensive backward compatibility** - Transform alias, migration tools
2. **Performance budgets** - Clear thresholds for acceptable overhead
3. **Clear documentation** - When to use 2D vs 3D transforms
---
# Community Feedback Integration
## Key Reviewer Positions
**@rparrett**: Strong opposition to 3D parent workaround
- **Resolution**: Integrated depth in Transform2d eliminates this need
**@ickshonpe**: Concerned about UI ZIndex conflict
- **Resolution**: Rename 2D version or namespace appropriately
**@hymm**: Y-sorting should be per-layer, not per-entity
- **Discussion**: Current per-entity approach provides more flexibility
**@NthTensor**: Fold sorting into Transform2d
- **Agreement**: Proposed unified approach addresses this
## Consensus Points
- **Strong support** for improved 2D ergonomics
- **Agreement** both PRs provide more value together
- **Consensus** that current Transform2d without sorting is insufficient
- **Shared concern** about performance and complexity
---
# Testing & Validation Requirements
## Critical Test Scenarios
### Sort Order Validation
```rust
// Test integrated depth system
spawn_entity(Transform2d { depth: Depth2d::Absolute(10.0), ..default() });
spawn_entity(Transform2d { depth: Depth2d::Relative(5.0), ..default() });
// Verify consistent, expected ordering
```
### Hierarchy Propagation
```rust
// Test depth inheritance
spawn_parent(Transform2d { depth: Depth2d::Absolute(5.0), ..default() }, [
spawn_child(Transform2d { depth: Depth2d::Relative(2.0), ..default() }), // Depth 7.0
spawn_child(Transform2d::default()), // Depth 6.0 (5.0 + 1.0 default)
]);
```
### Performance Benchmarks
- Entity count scaling (1K, 10K, 100K entities)
- Frame time impact measurement
- Memory usage comparison vs current system
## Acceptance Criteria
1. **Functional**: All existing 2D examples work without modification
2. **Performance**: <5% overhead compared to current Transform system
3. **Ergonomic**: No 3D parent workarounds required for common 2D scenarios
4. **Compatible**: Clear migration path for existing codebases
---
# Conclusion & Next Steps
## Recommendation: Proceed with Integrated Approach
The proposed unified `Transform2d` with integrated depth and sorting provides the best balance of:
- **Ergonomics**: Single component for all 2D transform needs
- **Performance**: Eliminates separate component overhead
- **Flexibility**: Supports both relative and absolute depth control
- **Consistency**: Maintains transform propagation semantics
## Immediate Actions Required
1. **Resolve UI ZIndex naming conflict** - Critical blocker
2. **Implement integrated Transform2d** - Combines both PR benefits
3. **Add hierarchy propagation** - Community expectation
4. **Performance validation** - Ensure acceptable overhead
## Long-term Vision
These changes establish foundation for advanced 2D features:
- Sophisticated sorting strategies (custom sort keys)
- 2D-specific optimization passes
- Integrated 2D animation systems
- Advanced 2D physics integration
The initial complexity investment is justified by the significant improvement to Bevy's 2D development experience and the foundation it provides for future 2D enhancements.