Skip to content

Commit 937555f

Browse files
committed
Merge branch 'master' of github.com:komadori/bevy_mod_outline into bevy-0.16
2 parents 1b0c334 + 1903c4b commit 937555f

6 files changed

Lines changed: 155 additions & 6 deletions

File tree

src/flood/bounds.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use bevy::ecs::component::Component;
2+
use bevy::math::{Affine3A, Mat4, UVec2, Vec4Swizzles};
3+
use bevy::prelude::*;
4+
use bevy::render::camera::Viewport;
5+
use bevy::render::extract_component::ExtractComponent;
6+
use bevy::render::primitives::Aabb;
7+
8+
use crate::computed::ComputedOutline;
9+
use crate::uniforms::DrawMode;
10+
11+
/// Component for storing mesh bounds information needed for scissoring jump-flood operations
12+
#[derive(Clone, Component)]
13+
pub struct FloodMeshBounds {
14+
/// The axis-aligned bounding box of the mesh in model space
15+
pub aabb: Aabb,
16+
/// The world-from-local transform for the entity
17+
pub world_from_local: Affine3A,
18+
}
19+
20+
impl FloodMeshBounds {
21+
pub fn calculate_screen_space_bounds(
22+
&self,
23+
clip_from_world: &Mat4,
24+
viewport: &Viewport,
25+
border: u32,
26+
) -> Option<URect> {
27+
// Get the 8 corners of the AABB
28+
let min = self.aabb.min();
29+
let max = self.aabb.max();
30+
let corners = [
31+
Vec3::new(min.x, min.y, min.z),
32+
Vec3::new(min.x, min.y, max.z),
33+
Vec3::new(min.x, max.y, min.z),
34+
Vec3::new(min.x, max.y, max.z),
35+
Vec3::new(max.x, min.y, min.z),
36+
Vec3::new(max.x, min.y, max.z),
37+
Vec3::new(max.x, max.y, min.z),
38+
Vec3::new(max.x, max.y, max.z),
39+
];
40+
41+
// Initialize min/max screen coordinates with extreme values
42+
let mut min_screen = UVec2::MAX;
43+
let mut max_screen = UVec2::MIN;
44+
45+
// Use the physical target size for width and height
46+
let width = viewport.physical_size.x;
47+
let height = viewport.physical_size.y;
48+
49+
// Project each corner to screen space and update min/max values
50+
for corner in corners.iter() {
51+
// Convert corner to world space
52+
let world_pos = self.world_from_local.transform_point3(*corner);
53+
54+
// Project to clip space
55+
let clip_pos = *clip_from_world * world_pos.extend(1.0);
56+
57+
// Perspective divide to get NDC coordinates
58+
let ndc = clip_pos.xyz() / clip_pos.w;
59+
60+
// Skip if behind camera
61+
if ndc.z < -1.0 || ndc.z > 1.0 {
62+
continue;
63+
}
64+
65+
// Convert from NDC to pixel coordinates
66+
let screen_x = ((ndc.x + 1.0) * 0.5 * width as f32).max(0.0) as u32;
67+
let screen_y = ((-ndc.y + 1.0) * 0.5 * height as f32).max(0.0) as u32;
68+
69+
min_screen.x = min_screen.x.min(screen_x);
70+
min_screen.y = min_screen.y.min(screen_y);
71+
max_screen.x = max_screen.x.max(screen_x);
72+
max_screen.y = max_screen.y.max(screen_y);
73+
}
74+
75+
// If nothing is in view, return None
76+
if min_screen.x >= max_screen.x || min_screen.y >= max_screen.y {
77+
None
78+
} else {
79+
Some(URect::new(
80+
viewport.physical_position.x + min_screen.x.saturating_sub(border).min(width),
81+
viewport.physical_position.y + min_screen.y.saturating_sub(border).min(height),
82+
viewport.physical_position.x + (max_screen.x + border).min(width),
83+
viewport.physical_position.y + (max_screen.y + border).min(height),
84+
))
85+
}
86+
}
87+
}
88+
89+
impl ExtractComponent for FloodMeshBounds {
90+
type QueryData = (
91+
&'static Aabb,
92+
&'static GlobalTransform,
93+
&'static ComputedOutline,
94+
);
95+
type QueryFilter = ();
96+
type Out = Self;
97+
98+
fn extract_component(
99+
(aabb, global_transform, computed_outline): bevy::ecs::query::QueryItem<Self::QueryData>,
100+
) -> Option<Self::Out> {
101+
let Some(computed_outline) = &computed_outline.0 else {
102+
return None;
103+
};
104+
105+
match computed_outline.mode.value.draw_mode {
106+
DrawMode::JumpFlood => Some(FloodMeshBounds {
107+
aabb: *aabb,
108+
world_from_local: global_transform.affine(),
109+
}),
110+
_ => None,
111+
}
112+
}
113+
}

src/flood/compose_output.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ impl<'w> ComposeOutputPass<'w> {
222222
render_context: &mut RenderContext<'_>,
223223
render_entity: Entity,
224224
input: &CachedTexture,
225+
bounds: &URect,
225226
) {
226227
let dynamic_index = self
227228
.world
@@ -248,6 +249,7 @@ impl<'w> ComposeOutputPass<'w> {
248249
occlusion_query_set: None,
249250
});
250251

252+
render_pass.set_scissor_rect(bounds.min.x, bounds.min.y, bounds.width(), bounds.height());
251253
render_pass.set_render_pipeline(self.render_pipeline);
252254
render_pass.set_bind_group(0, &bind_group, &[dynamic_index]);
253255
render_pass.draw(0..3, 0..1);

src/flood/flood_init.rs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bevy::prelude::*;
2-
use bevy::render::camera::ExtractedCamera;
2+
use bevy::render::camera::{ExtractedCamera, Viewport};
33
use bevy::render::mesh::allocator::MeshAllocator;
44
use bevy::render::mesh::RenderMesh;
55
use bevy::render::render_asset::RenderAssets;
@@ -20,6 +20,7 @@ use wgpu_types::ImageSubresourceRange;
2020
use crate::uniforms::ExtractedOutline;
2121
use crate::view_uniforms::OutlineQueueStatus;
2222

23+
use super::bounds::FloodMeshBounds;
2324
use super::node::FloodOutline;
2425
use super::{
2526
DepthMode, DrawMode, DrawOutline, OutlinePipeline, OutlineViewUniform, PassType, PipelineKey,
@@ -42,24 +43,36 @@ pub(crate) fn queue_flood_meshes(
4243
pipeline_cache: Res<PipelineCache>,
4344
render_meshes: Res<RenderAssets<RenderMesh>>,
4445
mesh_allocator: Res<MeshAllocator>,
45-
material_meshes: Query<(Entity, &MainEntity, &ExtractedOutline)>,
46+
material_meshes: Query<(Entity, &MainEntity, &ExtractedOutline, &FloodMeshBounds)>,
4647
mut flood_phases: ResMut<ViewSortedRenderPhases<FloodOutline>>,
4748
mut views: Query<(
4849
&ExtractedView,
50+
&ExtractedCamera,
4951
Option<&RenderLayers>,
5052
&mut OutlineQueueStatus,
5153
)>,
5254
) {
5355
let draw_flood = flood_draw_functions.read().get_id::<DrawOutline>().unwrap();
5456

55-
for (view, view_mask, mut queue_status) in views.iter_mut() {
57+
for (view, camera, view_mask, mut queue_status) in views.iter_mut() {
5658
let view_mask = view_mask.cloned().unwrap_or_default();
5759

5860
let Some(flood_phase) = flood_phases.get_mut(&view.retained_view_entity) else {
5961
continue;
6062
};
6163

62-
for (entity, main_entity, outline) in material_meshes.iter() {
64+
// Get the camera's physical target size for correct screen bounds calculation
65+
let fallback_viewport = Viewport {
66+
physical_size: camera.physical_target_size.unwrap_or_default(),
67+
..default()
68+
};
69+
let viewport = camera.viewport.as_ref().unwrap_or(&fallback_viewport);
70+
71+
let clip_from_world = view.clip_from_world.unwrap_or_else(|| {
72+
view.clip_from_view * view.world_from_view.compute_matrix().inverse()
73+
});
74+
75+
for (entity, main_entity, outline, mesh_bounds) in material_meshes.iter() {
6376
if !outline.volume {
6477
continue;
6578
}
@@ -76,6 +89,14 @@ pub(crate) fn queue_flood_meshes(
7689
continue;
7790
};
7891

92+
// Calculate screen-space bounds of outline
93+
let border = outline.instance_data.volume_offset.ceil() as u32;
94+
let Some(screen_space_bounds) =
95+
mesh_bounds.calculate_screen_space_bounds(&clip_from_world, viewport, border)
96+
else {
97+
continue;
98+
};
99+
79100
let (_vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&outline.mesh_id);
80101

81102
let flood_key = PipelineKey::new()
@@ -104,6 +125,7 @@ pub(crate) fn queue_flood_meshes(
104125
extra_index: PhaseItemExtraIndex::None,
105126
indexed: index_slab.is_some(),
106127
volume_offset: outline.instance_data.volume_offset,
128+
screen_space_bounds,
107129
});
108130
}
109131
}

src/flood/jump_flood.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ impl<'w> JumpFloodPass<'w> {
121121
input: &CachedTexture,
122122
output: &CachedTexture,
123123
size: u32,
124+
bounds: &URect,
124125
) {
125126
let bind_group = render_context.render_device().create_bind_group(
126127
"outline_jump_flood_bind_group",
@@ -144,6 +145,7 @@ impl<'w> JumpFloodPass<'w> {
144145
occlusion_query_set: None,
145146
});
146147

148+
render_pass.set_scissor_rect(bounds.min.x, bounds.min.y, bounds.width(), bounds.height());
147149
render_pass.set_render_pipeline(self.render_pipeline);
148150
render_pass.set_bind_group(
149151
0,

src/flood/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use bevy::asset::{load_internal_asset, weak_handle};
22
use bevy::core_pipeline::core_3d::graph::Core3d;
33
use bevy::pbr::{MeshInputUniform, MeshUniform};
44
use bevy::render::batching::gpu_preprocessing::{BatchedInstanceBuffers, GpuPreprocessingSupport};
5-
use bevy::render::extract_component::UniformComponentPlugin;
5+
use bevy::render::extract_component::{ExtractComponentPlugin, UniformComponentPlugin};
66
use bevy::render::render_phase::{
77
sort_phase_system, AddRenderCommand, DrawFunctions, SortedRenderPhasePlugin,
88
};
@@ -34,6 +34,7 @@ use crate::uniforms::{DepthMode, DrawMode};
3434
use crate::view_uniforms::OutlineViewUniform;
3535
use crate::{add_dummy_phase_buffer, NodeOutline};
3636

37+
mod bounds;
3738
mod compose_output;
3839
mod flood_init;
3940
mod jump_flood;
@@ -135,6 +136,7 @@ impl Plugin for FloodPlugin {
135136
app.add_plugins((
136137
UniformComponentPlugin::<ComposeOutputUniform>::default(),
137138
SortedRenderPhasePlugin::<FloodOutline, OutlinePipeline>::new(RenderDebugFlags::empty()),
139+
ExtractComponentPlugin::<bounds::FloodMeshBounds>::default(),
138140
))
139141
.sub_app_mut(RenderApp)
140142
.init_resource::<DrawFunctions<FloodOutline>>()

src/flood/node.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub struct FloodOutline {
3434
pub extra_index: PhaseItemExtraIndex,
3535
pub indexed: bool,
3636
pub volume_offset: f32,
37+
pub screen_space_bounds: URect,
3738
}
3839

3940
impl PhaseItem for FloodOutline {
@@ -153,17 +154,24 @@ impl ViewNode for FloodNode {
153154
} else {
154155
0
155156
};
157+
156158
for size in (0..passes).rev() {
157159
jump_flood_pass.execute(
158160
render_context,
159161
flood_textures.input(),
160162
flood_textures.output(),
161163
size,
164+
&item.screen_space_bounds,
162165
);
163166
flood_textures.flip();
164167
}
165168

166-
compose_output_pass.execute(render_context, item.entity, flood_textures.input());
169+
compose_output_pass.execute(
170+
render_context,
171+
item.entity,
172+
flood_textures.input(),
173+
&item.screen_space_bounds,
174+
);
167175
}
168176

169177
Ok(())

0 commit comments

Comments
 (0)