Initial port to Bevy 0.13.
This commit is contained in:
parent
bcc0cdd86a
commit
fffb75b20c
@ -11,7 +11,7 @@ keywords = ["gamedev", "bevy", "outline"]
|
|||||||
categories = ["game-engines", "rendering"]
|
categories = ["game-engines", "rendering"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = { version = "0.12", default-features = false, features = [
|
bevy = { git = "https://github.com/bevyengine/bevy.git", rev = "4ebc560dfb0fee5d3728d1d8f7362f484b784e4c", default-features = false, features = [
|
||||||
"bevy_asset",
|
"bevy_asset",
|
||||||
"bevy_render",
|
"bevy_render",
|
||||||
"bevy_pbr",
|
"bevy_pbr",
|
||||||
@ -24,10 +24,10 @@ bitfield = "0.14"
|
|||||||
interpolation = "0.2"
|
interpolation = "0.2"
|
||||||
interpolation_03 = { package = "interpolation", version = "0.3", optional = true }
|
interpolation_03 = { package = "interpolation", version = "0.3", optional = true }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
wgpu-types = "0.17"
|
wgpu-types = "0.19"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bevy = { version = "0.12", default-features = false, features = [
|
bevy = { git = "https://github.com/bevyengine/bevy.git", rev = "4ebc560dfb0fee5d3728d1d8f7362f484b784e4c", default-features = false, features = [
|
||||||
"animation",
|
"animation",
|
||||||
"bevy_gltf",
|
"bevy_gltf",
|
||||||
"bevy_pbr",
|
"bevy_pbr",
|
||||||
|
@ -51,7 +51,7 @@ fn setup(
|
|||||||
size: 500000.0,
|
size: 500000.0,
|
||||||
subdivisions: 0,
|
subdivisions: 0,
|
||||||
})),
|
})),
|
||||||
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
material: materials.add(StandardMaterial::from(Color::rgb(0.3, 0.5, 0.3))),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -36,18 +36,15 @@ fn setup(
|
|||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
) {
|
) {
|
||||||
commands.insert_resource(MyAssets {
|
commands.insert_resource(MyAssets {
|
||||||
mesh: meshes.add(
|
mesh: meshes.add(Mesh::from(Capsule {
|
||||||
Capsule {
|
|
||||||
radius: 1.0,
|
radius: 1.0,
|
||||||
rings: 10,
|
rings: 10,
|
||||||
depth: 2.0,
|
depth: 2.0,
|
||||||
latitudes: 15,
|
latitudes: 15,
|
||||||
longitudes: 15,
|
longitudes: 15,
|
||||||
..default()
|
..default()
|
||||||
}
|
})),
|
||||||
.into(),
|
material: materials.add(StandardMaterial::from(Color::BEIGE)),
|
||||||
),
|
|
||||||
material: materials.add(Color::BEIGE.into()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add light source and camera
|
// Add light source and camera
|
||||||
|
@ -32,15 +32,12 @@ fn setup(
|
|||||||
// Add sphere with child meshes sticking out of it
|
// Add sphere with child meshes sticking out of it
|
||||||
commands
|
commands
|
||||||
.spawn(PbrBundle {
|
.spawn(PbrBundle {
|
||||||
mesh: meshes.add(
|
mesh: meshes.add(Mesh::from(UVSphere {
|
||||||
UVSphere {
|
|
||||||
radius: 0.75,
|
radius: 0.75,
|
||||||
sectors: 30,
|
sectors: 30,
|
||||||
stacks: 30,
|
stacks: 30,
|
||||||
}
|
})),
|
||||||
.into(),
|
material: materials.add(StandardMaterial::from(Color::rgb(0.9, 0.1, 0.1))),
|
||||||
),
|
|
||||||
material: materials.add(Color::rgb(0.9, 0.1, 0.1).into()),
|
|
||||||
transform: Transform::from_translation(Vec3::new(0.0, 1.0, 0.0)),
|
transform: Transform::from_translation(Vec3::new(0.0, 1.0, 0.0)),
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
@ -60,18 +57,15 @@ fn setup(
|
|||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent
|
parent
|
||||||
.spawn(PbrBundle {
|
.spawn(PbrBundle {
|
||||||
mesh: meshes.add(
|
mesh: meshes.add(Mesh::from(Capsule {
|
||||||
Capsule {
|
|
||||||
radius: 0.2,
|
radius: 0.2,
|
||||||
rings: 15,
|
rings: 15,
|
||||||
depth: 1.0,
|
depth: 1.0,
|
||||||
latitudes: 15,
|
latitudes: 15,
|
||||||
longitudes: 15,
|
longitudes: 15,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
})),
|
||||||
.into(),
|
material: materials.add(StandardMaterial::from(Color::rgb(0.1, 0.1, 0.9))),
|
||||||
),
|
|
||||||
material: materials.add(Color::rgb(0.1, 0.1, 0.9).into()),
|
|
||||||
transform: Transform::from_rotation(Quat::from_axis_angle(Vec3::X, TAU / 4.0))
|
transform: Transform::from_rotation(Quat::from_axis_angle(Vec3::X, TAU / 4.0))
|
||||||
.with_translation(Vec3::new(0.0, 0.0, 0.75)),
|
.with_translation(Vec3::new(0.0, 0.0, 0.75)),
|
||||||
..default()
|
..default()
|
||||||
@ -79,16 +73,13 @@ fn setup(
|
|||||||
.insert(InheritOutlineBundle::default());
|
.insert(InheritOutlineBundle::default());
|
||||||
parent
|
parent
|
||||||
.spawn(PbrBundle {
|
.spawn(PbrBundle {
|
||||||
mesh: meshes.add(
|
mesh: meshes.add(Mesh::from(Torus {
|
||||||
Torus {
|
|
||||||
radius: 0.5,
|
radius: 0.5,
|
||||||
ring_radius: 0.1,
|
ring_radius: 0.1,
|
||||||
subdivisions_segments: 30,
|
subdivisions_segments: 30,
|
||||||
subdivisions_sides: 15,
|
subdivisions_sides: 15,
|
||||||
}
|
})),
|
||||||
.into(),
|
material: materials.add(StandardMaterial::from(Color::rgb(0.1, 0.1, 0.9))),
|
||||||
),
|
|
||||||
material: materials.add(Color::rgb(0.1, 0.1, 0.9).into()),
|
|
||||||
transform: Transform::from_rotation(Quat::from_axis_angle(Vec3::Z, TAU / 4.0))
|
transform: Transform::from_rotation(Quat::from_axis_angle(Vec3::Z, TAU / 4.0))
|
||||||
.with_translation(Vec3::new(0.0, 0.0, -0.75)),
|
.with_translation(Vec3::new(0.0, 0.0, -0.75)),
|
||||||
..default()
|
..default()
|
||||||
@ -102,7 +93,7 @@ fn setup(
|
|||||||
size: 5.0,
|
size: 5.0,
|
||||||
subdivisions: 0,
|
subdivisions: 0,
|
||||||
})),
|
})),
|
||||||
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
material: materials.add(StandardMaterial::from(Color::rgb(0.3, 0.5, 0.3))),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
commands.spawn(PointLightBundle {
|
commands.spawn(PointLightBundle {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
core_pipeline::clear_color::ClearColorConfig,
|
|
||||||
prelude::{
|
prelude::{
|
||||||
shape::{Plane, Torus},
|
shape::{Plane, Torus},
|
||||||
*,
|
*,
|
||||||
@ -45,7 +44,7 @@ fn setup(
|
|||||||
subdivisions_segments: 40,
|
subdivisions_segments: 40,
|
||||||
subdivisions_sides: 20,
|
subdivisions_sides: 20,
|
||||||
})),
|
})),
|
||||||
material: materials.add(Color::rgb(0.1, 0.1, 0.9).into()),
|
material: materials.add(StandardMaterial::from(Color::rgb(0.1, 0.1, 0.9))),
|
||||||
transform: Transform::from_rotation(Quat::from_rotation_x(0.5 * PI))
|
transform: Transform::from_rotation(Quat::from_rotation_x(0.5 * PI))
|
||||||
.with_translation(0.8 * Vec3::Y),
|
.with_translation(0.8 * Vec3::Y),
|
||||||
..default()
|
..default()
|
||||||
@ -67,7 +66,7 @@ fn setup(
|
|||||||
size: 5.0,
|
size: 5.0,
|
||||||
subdivisions: 0,
|
subdivisions: 0,
|
||||||
})),
|
})),
|
||||||
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
material: materials.add(StandardMaterial::from(Color::rgb(0.3, 0.5, 0.3))),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
commands.spawn(PointLightBundle {
|
commands.spawn(PointLightBundle {
|
||||||
@ -95,9 +94,6 @@ fn setup(
|
|||||||
.spawn(Camera3dBundle {
|
.spawn(Camera3dBundle {
|
||||||
camera: Camera {
|
camera: Camera {
|
||||||
order: i,
|
order: i,
|
||||||
..default()
|
|
||||||
},
|
|
||||||
camera_3d: Camera3d {
|
|
||||||
clear_color: if i > 0 {
|
clear_color: if i > 0 {
|
||||||
ClearColorConfig::None
|
ClearColorConfig::None
|
||||||
} else {
|
} else {
|
||||||
@ -105,6 +101,7 @@ fn setup(
|
|||||||
},
|
},
|
||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
|
camera_3d: Camera3d { ..default() },
|
||||||
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
@ -117,11 +114,11 @@ fn setup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_camera_viewports(
|
fn set_camera_viewports(
|
||||||
win_query: Query<(&Window, Changed<Window>), With<PrimaryWindow>>,
|
win_query: Query<Ref<Window>, With<PrimaryWindow>>,
|
||||||
mut query: Query<(&mut Camera, &CameraMode)>,
|
mut query: Query<(&mut Camera, &CameraMode)>,
|
||||||
) {
|
) {
|
||||||
let (win, win_changed) = win_query.get_single().unwrap();
|
let win = win_query.get_single().unwrap();
|
||||||
if win_changed {
|
if win.is_changed() {
|
||||||
// Divide window into quadrants
|
// Divide window into quadrants
|
||||||
let size = UVec2::new(win.physical_width() / 2, win.physical_height() / 2);
|
let size = UVec2::new(win.physical_width() / 2, win.physical_height() / 2);
|
||||||
for (mut camera, mode) in query.iter_mut() {
|
for (mut camera, mode) in query.iter_mut() {
|
||||||
|
@ -38,7 +38,7 @@ fn setup(
|
|||||||
commands
|
commands
|
||||||
.spawn(PbrBundle {
|
.spawn(PbrBundle {
|
||||||
mesh: meshes.add(cube_mesh),
|
mesh: meshes.add(cube_mesh),
|
||||||
material: materials.add(Color::rgb(0.1, 0.1, 0.9).into()),
|
material: materials.add(StandardMaterial::from(Color::rgb(0.1, 0.1, 0.9))),
|
||||||
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
||||||
..default()
|
..default()
|
||||||
})
|
})
|
||||||
@ -61,7 +61,7 @@ fn setup(
|
|||||||
subdivisions_segments: 20,
|
subdivisions_segments: 20,
|
||||||
subdivisions_sides: 10,
|
subdivisions_sides: 10,
|
||||||
})),
|
})),
|
||||||
material: materials.add(Color::rgb(0.9, 0.1, 0.1).into()),
|
material: materials.add(StandardMaterial::from(Color::rgb(0.9, 0.1, 0.1))),
|
||||||
transform: Transform::from_xyz(0.0, 1.2, 2.0)
|
transform: Transform::from_xyz(0.0, 1.2, 2.0)
|
||||||
.with_rotation(Quat::from_rotation_x(0.5 * PI)),
|
.with_rotation(Quat::from_rotation_x(0.5 * PI)),
|
||||||
..default()
|
..default()
|
||||||
@ -82,7 +82,7 @@ fn setup(
|
|||||||
size: 5.0,
|
size: 5.0,
|
||||||
subdivisions: 0,
|
subdivisions: 0,
|
||||||
})),
|
})),
|
||||||
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
material: materials.add(StandardMaterial::from(Color::rgb(0.3, 0.5, 0.3))),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
commands.spawn(PointLightBundle {
|
commands.spawn(PointLightBundle {
|
||||||
|
@ -58,9 +58,13 @@ impl<T: Clone + Default> Sourced<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_changed<U: Component>(&self, tuple: Option<(&U, bool)>) -> bool {
|
pub fn is_changed<U: Component>(&self, tuple: &Option<Ref<U>>) -> bool {
|
||||||
tuple.is_some() != matches!(self.source, Source::Set)
|
tuple.is_some() != matches!(self.source, Source::Set)
|
||||||
|| if let Some((_, c)) = tuple { c } else { false }
|
|| if let Some(r) = tuple {
|
||||||
|
r.is_changed()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,11 +81,11 @@ pub(crate) struct ComputedInternal {
|
|||||||
pub struct ComputedOutline(pub(crate) Option<ComputedInternal>);
|
pub struct ComputedOutline(pub(crate) Option<ComputedInternal>);
|
||||||
|
|
||||||
type OutlineComponents<'a> = (
|
type OutlineComponents<'a> = (
|
||||||
(&'a InheritedVisibility, Changed<InheritedVisibility>),
|
Ref<'a, InheritedVisibility>,
|
||||||
(&'a GlobalTransform, Changed<GlobalTransform>),
|
Ref<'a, GlobalTransform>,
|
||||||
Option<(&'a OutlineVolume, Changed<OutlineVolume>)>,
|
Option<Ref<'a, OutlineVolume>>,
|
||||||
Option<(&'a OutlineStencil, Changed<OutlineStencil>)>,
|
Option<Ref<'a, OutlineStencil>>,
|
||||||
Option<(&'a OutlineMode, Changed<OutlineMode>)>,
|
Option<Ref<'a, OutlineMode>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
@ -150,7 +154,7 @@ fn propagate_computed_outline(
|
|||||||
|
|
||||||
fn update_computed_outline(
|
fn update_computed_outline(
|
||||||
computed: &mut ComputedOutline,
|
computed: &mut ComputedOutline,
|
||||||
((visibility, changed_visibility), (transform, changed_transform), volume, stencil, mode): QueryItem<'_, OutlineComponents>,
|
(visibility, transform, volume, stencil, mode): QueryItem<'_, OutlineComponents>,
|
||||||
parent_computed: &ComputedInternal,
|
parent_computed: &ComputedInternal,
|
||||||
parent_entity: Option<Entity>,
|
parent_entity: Option<Entity>,
|
||||||
force_update: bool,
|
force_update: bool,
|
||||||
@ -158,27 +162,31 @@ fn update_computed_outline(
|
|||||||
let changed = force_update
|
let changed = force_update
|
||||||
|| if let ComputedOutline(Some(computed)) = computed {
|
|| if let ComputedOutline(Some(computed)) = computed {
|
||||||
computed.inherited_from != parent_entity
|
computed.inherited_from != parent_entity
|
||||||
|| changed_visibility
|
|| visibility.is_changed()
|
||||||
|| (changed_transform && matches!(mode, Some((OutlineMode::FlatVertex { .. }, _))))
|
|| (transform.is_changed()
|
||||||
|| computed.volume.is_changed(volume)
|
&& mode
|
||||||
|| computed.stencil.is_changed(stencil)
|
.as_ref()
|
||||||
|| computed.mode.is_changed(mode)
|
.map(|r| matches!(r.as_ref(), OutlineMode::FlatVertex { .. }))
|
||||||
|
.unwrap_or(false))
|
||||||
|
|| computed.volume.is_changed(&volume)
|
||||||
|
|| computed.stencil.is_changed(&stencil)
|
||||||
|
|| computed.mode.is_changed(&mode)
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
if changed {
|
if changed {
|
||||||
*computed = ComputedOutline(Some(ComputedInternal {
|
*computed = ComputedOutline(Some(ComputedInternal {
|
||||||
inherited_from: parent_entity,
|
inherited_from: parent_entity,
|
||||||
volume: if let Some((vol, _)) = volume {
|
volume: if let Some(vol) = volume {
|
||||||
Sourced::set(ComputedVolume {
|
Sourced::set(ComputedVolume {
|
||||||
enabled: visibility.get() && vol.visible && vol.colour.a() != 0.0,
|
enabled: visibility.get() && vol.visible && vol.colour.a() != 0.0,
|
||||||
offset: vol.width,
|
offset: vol.width,
|
||||||
colour: vol.colour.into(),
|
colour: vol.colour.as_linear_rgba_f32().into(),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Sourced::inherit(&parent_computed.volume.value)
|
Sourced::inherit(&parent_computed.volume.value)
|
||||||
},
|
},
|
||||||
stencil: if let Some((sten, _)) = stencil {
|
stencil: if let Some(sten) = stencil {
|
||||||
Sourced::set(ComputedStencil {
|
Sourced::set(ComputedStencil {
|
||||||
enabled: visibility.get() && sten.enabled,
|
enabled: visibility.get() && sten.enabled,
|
||||||
offset: sten.offset,
|
offset: sten.offset,
|
||||||
@ -186,8 +194,8 @@ fn update_computed_outline(
|
|||||||
} else {
|
} else {
|
||||||
Sourced::inherit(&parent_computed.stencil.value)
|
Sourced::inherit(&parent_computed.stencil.value)
|
||||||
},
|
},
|
||||||
mode: if let Some((m, _)) = mode {
|
mode: if let Some(m) = mode {
|
||||||
Sourced::set(match m {
|
Sourced::set(match m.as_ref() {
|
||||||
OutlineMode::FlatVertex {
|
OutlineMode::FlatVertex {
|
||||||
model_origin: origin,
|
model_origin: origin,
|
||||||
} => ComputedMode {
|
} => ComputedMode {
|
||||||
|
33
src/lib.rs
33
src/lib.rs
@ -23,18 +23,20 @@
|
|||||||
//! [`AutoGenerateOutlineNormalsPlugin`].
|
//! [`AutoGenerateOutlineNormalsPlugin`].
|
||||||
|
|
||||||
use bevy::asset::load_internal_asset;
|
use bevy::asset::load_internal_asset;
|
||||||
|
use bevy::core_pipeline::core_3d::graph::{Labels3d, SubGraph3d};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::batching::{batch_and_prepare_render_phase, write_batched_instance_buffer};
|
use bevy::render::batching::{batch_and_prepare_render_phase, write_batched_instance_buffer};
|
||||||
use bevy::render::extract_component::{
|
use bevy::render::extract_component::{
|
||||||
ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
|
ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
|
||||||
};
|
};
|
||||||
use bevy::render::mesh::MeshVertexAttribute;
|
use bevy::render::mesh::MeshVertexAttribute;
|
||||||
use bevy::render::render_graph::RenderGraph;
|
use bevy::render::render_graph::{RenderGraph, RenderLabel};
|
||||||
use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions};
|
use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions};
|
||||||
use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat};
|
use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat};
|
||||||
use bevy::render::view::{RenderLayers, VisibilitySystems};
|
use bevy::render::view::{RenderLayers, VisibilitySystems};
|
||||||
use bevy::render::{Render, RenderApp, RenderSet};
|
use bevy::render::{Render, RenderApp, RenderSet};
|
||||||
use bevy::transform::TransformSystem;
|
use bevy::transform::TransformSystem;
|
||||||
|
use bevy::ui::graph::LabelsUi;
|
||||||
use interpolation::Lerp;
|
use interpolation::Lerp;
|
||||||
|
|
||||||
use crate::draw::{
|
use crate::draw::{
|
||||||
@ -72,7 +74,10 @@ pub const ATTRIBUTE_OUTLINE_NORMAL: MeshVertexAttribute =
|
|||||||
///
|
///
|
||||||
/// This node runs after the main 3D passes and before the UI pass. The name can be used to
|
/// This node runs after the main 3D passes and before the UI pass. The name can be used to
|
||||||
/// add additional constraints on node execution order with respect to other passes.
|
/// add additional constraints on node execution order with respect to other passes.
|
||||||
pub const OUTLINE_PASS_NODE_NAME: &str = "bevy_mod_outline_node";
|
#[derive(Copy, Clone, Debug, RenderLabel, Hash, PartialEq, Eq)]
|
||||||
|
pub enum LabelsOutline {
|
||||||
|
OutlinePass,
|
||||||
|
}
|
||||||
|
|
||||||
/// A component for stenciling meshes during outline rendering.
|
/// A component for stenciling meshes during outline rendering.
|
||||||
#[derive(Clone, Component)]
|
#[derive(Clone, Component)]
|
||||||
@ -108,7 +113,7 @@ impl Lerp for OutlineStencil {
|
|||||||
fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
|
fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
|
||||||
OutlineStencil {
|
OutlineStencil {
|
||||||
enabled: lerp_bool(self.enabled, other.enabled, *scalar),
|
enabled: lerp_bool(self.enabled, other.enabled, *scalar),
|
||||||
offset: self.offset.lerp(&other.offset, scalar),
|
offset: self.offset.lerp(other.offset, *scalar),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,7 +144,7 @@ impl Lerp for OutlineVolume {
|
|||||||
fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
|
fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
|
||||||
OutlineVolume {
|
OutlineVolume {
|
||||||
visible: lerp_bool(self.visible, other.visible, *scalar),
|
visible: lerp_bool(self.visible, other.visible, *scalar),
|
||||||
width: self.width.lerp(&other.width, scalar),
|
width: self.width.lerp(other.width, *scalar),
|
||||||
colour: {
|
colour: {
|
||||||
let [r, g, b, a] = self
|
let [r, g, b, a] = self
|
||||||
.colour
|
.colour
|
||||||
@ -165,11 +170,11 @@ impl interpolation_03::Lerp for OutlineVolume {
|
|||||||
pub struct OutlineRenderLayers(pub RenderLayers);
|
pub struct OutlineRenderLayers(pub RenderLayers);
|
||||||
|
|
||||||
impl ExtractComponent for OutlineRenderLayers {
|
impl ExtractComponent for OutlineRenderLayers {
|
||||||
type Query = (
|
type QueryData = (
|
||||||
Option<&'static OutlineRenderLayers>,
|
Option<&'static OutlineRenderLayers>,
|
||||||
Option<&'static RenderLayers>,
|
Option<&'static RenderLayers>,
|
||||||
);
|
);
|
||||||
type Filter = With<ComputedOutline>;
|
type QueryFilter = With<ComputedOutline>;
|
||||||
type Out = Self;
|
type Out = Self;
|
||||||
|
|
||||||
fn extract_component(
|
fn extract_component(
|
||||||
@ -319,21 +324,13 @@ impl Plugin for OutlinePlugin {
|
|||||||
|
|
||||||
let mut graph = world.resource_mut::<RenderGraph>();
|
let mut graph = world.resource_mut::<RenderGraph>();
|
||||||
|
|
||||||
let draw_3d_graph = graph
|
let draw_3d_graph = graph.get_sub_graph_mut(SubGraph3d).unwrap();
|
||||||
.get_sub_graph_mut(bevy::core_pipeline::core_3d::graph::NAME)
|
draw_3d_graph.add_node(LabelsOutline::OutlinePass, node);
|
||||||
.unwrap();
|
|
||||||
draw_3d_graph.add_node(OUTLINE_PASS_NODE_NAME, node);
|
|
||||||
|
|
||||||
// Run after main 3D pass, but before UI psss
|
// Run after main 3D pass, but before UI psss
|
||||||
draw_3d_graph.add_node_edge(
|
draw_3d_graph.add_node_edge(Labels3d::EndMainPass, LabelsOutline::OutlinePass);
|
||||||
bevy::core_pipeline::core_3d::graph::node::END_MAIN_PASS,
|
|
||||||
OUTLINE_PASS_NODE_NAME,
|
|
||||||
);
|
|
||||||
#[cfg(feature = "bevy_ui")]
|
#[cfg(feature = "bevy_ui")]
|
||||||
draw_3d_graph.add_node_edge(
|
draw_3d_graph.add_node_edge(LabelsOutline::OutlinePass, LabelsUi::UiPass);
|
||||||
OUTLINE_PASS_NODE_NAME,
|
|
||||||
bevy::ui::draw_ui_graph::node::UI_PASS,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut App) {
|
fn finish(&self, app: &mut App) {
|
||||||
|
30
src/node.rs
30
src/node.rs
@ -10,7 +10,7 @@ use bevy::render::render_phase::{
|
|||||||
};
|
};
|
||||||
use bevy::render::render_resource::{
|
use bevy::render::render_resource::{
|
||||||
CachedRenderPipelineId, LoadOp, Operations, RenderPassDepthStencilAttachment,
|
CachedRenderPipelineId, LoadOp, Operations, RenderPassDepthStencilAttachment,
|
||||||
RenderPassDescriptor,
|
RenderPassDescriptor, StoreOp,
|
||||||
};
|
};
|
||||||
use bevy::render::view::{ExtractedView, ViewDepthTexture, ViewTarget};
|
use bevy::render::view::{ExtractedView, ViewDepthTexture, ViewTarget};
|
||||||
use bevy::render::{
|
use bevy::render::{
|
||||||
@ -217,13 +217,15 @@ impl Node for OutlineNode {
|
|||||||
label: Some("outline_stencil_pass"),
|
label: Some("outline_stencil_pass"),
|
||||||
color_attachments: &[],
|
color_attachments: &[],
|
||||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||||
view: &depth.view,
|
view: &depth.view(),
|
||||||
depth_ops: Some(Operations {
|
depth_ops: Some(Operations {
|
||||||
load: camera_3d.depth_load_op.clone().into(),
|
load: camera_3d.depth_load_op.clone().into(),
|
||||||
store: true,
|
store: StoreOp::Store,
|
||||||
}),
|
}),
|
||||||
stencil_ops: None,
|
stencil_ops: None,
|
||||||
}),
|
}),
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
};
|
};
|
||||||
let mut tracked_pass = render_context.begin_tracked_render_pass(pass_descriptor);
|
let mut tracked_pass = render_context.begin_tracked_render_pass(pass_descriptor);
|
||||||
if let Some(viewport) = camera.viewport.as_ref() {
|
if let Some(viewport) = camera.viewport.as_ref() {
|
||||||
@ -235,18 +237,17 @@ impl Node for OutlineNode {
|
|||||||
if !opaque_phase.items.is_empty() {
|
if !opaque_phase.items.is_empty() {
|
||||||
let pass_descriptor = RenderPassDescriptor {
|
let pass_descriptor = RenderPassDescriptor {
|
||||||
label: Some("outline_opaque_pass"),
|
label: Some("outline_opaque_pass"),
|
||||||
color_attachments: &[Some(target.get_color_attachment(Operations {
|
color_attachments: &[Some(target.get_color_attachment())],
|
||||||
load: LoadOp::Load,
|
|
||||||
store: true,
|
|
||||||
}))],
|
|
||||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||||
view: &depth.view,
|
view: &depth.view(),
|
||||||
depth_ops: Some(Operations {
|
depth_ops: Some(Operations {
|
||||||
load: LoadOp::Load,
|
load: LoadOp::Load,
|
||||||
store: true,
|
store: StoreOp::Store,
|
||||||
}),
|
}),
|
||||||
stencil_ops: None,
|
stencil_ops: None,
|
||||||
}),
|
}),
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
};
|
};
|
||||||
let mut tracked_pass = render_context.begin_tracked_render_pass(pass_descriptor);
|
let mut tracked_pass = render_context.begin_tracked_render_pass(pass_descriptor);
|
||||||
if let Some(viewport) = camera.viewport.as_ref() {
|
if let Some(viewport) = camera.viewport.as_ref() {
|
||||||
@ -258,18 +259,17 @@ impl Node for OutlineNode {
|
|||||||
if !transparent_phase.items.is_empty() {
|
if !transparent_phase.items.is_empty() {
|
||||||
let pass_descriptor = RenderPassDescriptor {
|
let pass_descriptor = RenderPassDescriptor {
|
||||||
label: Some("outline_transparent_pass"),
|
label: Some("outline_transparent_pass"),
|
||||||
color_attachments: &[Some(target.get_color_attachment(Operations {
|
color_attachments: &[Some(target.get_color_attachment())],
|
||||||
load: LoadOp::Load,
|
|
||||||
store: true,
|
|
||||||
}))],
|
|
||||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
|
||||||
view: &depth.view,
|
view: &depth.view(),
|
||||||
depth_ops: Some(Operations {
|
depth_ops: Some(Operations {
|
||||||
load: LoadOp::Load,
|
load: LoadOp::Load,
|
||||||
store: true,
|
store: StoreOp::Store,
|
||||||
}),
|
}),
|
||||||
stencil_ops: None,
|
stencil_ops: None,
|
||||||
}),
|
}),
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
};
|
};
|
||||||
let mut tracked_pass = render_context.begin_tracked_render_pass(pass_descriptor);
|
let mut tracked_pass = render_context.begin_tracked_render_pass(pass_descriptor);
|
||||||
if let Some(viewport) = camera.viewport.as_ref() {
|
if let Some(viewport) = camera.viewport.as_ref() {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use bevy::ecs::query::QueryItem;
|
use bevy::ecs::system::lifetimeless::SQuery;
|
||||||
use bevy::ecs::system::lifetimeless::Read;
|
|
||||||
use bevy::ecs::system::SystemParamItem;
|
use bevy::ecs::system::SystemParamItem;
|
||||||
use bevy::pbr::{
|
use bevy::pbr::{
|
||||||
setup_morph_and_skinning_defs, MeshFlags, MeshPipelineKey, MeshTransforms, MeshUniform,
|
setup_morph_and_skinning_defs, MeshFlags, MeshPipelineKey, MeshTransforms, MeshUniform,
|
||||||
@ -9,11 +8,10 @@ use bevy::pbr::{
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::batching::GetBatchData;
|
use bevy::render::batching::GetBatchData;
|
||||||
use bevy::render::render_resource::{
|
use bevy::render::render_resource::{
|
||||||
BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState,
|
BindGroupLayout, BindGroupLayoutEntry, BindingType, BlendState, BufferBindingType, BufferSize,
|
||||||
BufferBindingType, BufferSize, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState,
|
ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, Face,
|
||||||
DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PolygonMode,
|
FragmentState, FrontFace, MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology,
|
||||||
PrimitiveState, PrimitiveTopology, ShaderDefVal, ShaderSize, ShaderStages, ShaderType,
|
ShaderDefVal, ShaderSize, ShaderStages, ShaderType, StencilState, TextureFormat, VertexState,
|
||||||
StencilState, TextureFormat, VertexState,
|
|
||||||
};
|
};
|
||||||
use bevy::render::renderer::RenderDevice;
|
use bevy::render::renderer::RenderDevice;
|
||||||
use bevy::render::settings::WgpuSettings;
|
use bevy::render::settings::WgpuSettings;
|
||||||
@ -168,10 +166,9 @@ impl FromWorld for OutlinePipeline {
|
|||||||
let world = world.cell();
|
let world = world.cell();
|
||||||
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap().clone();
|
let mesh_pipeline = world.get_resource::<MeshPipeline>().unwrap().clone();
|
||||||
let render_device = world.get_resource::<RenderDevice>().unwrap();
|
let render_device = world.get_resource::<RenderDevice>().unwrap();
|
||||||
let outline_view_bind_group_layout =
|
let outline_view_bind_group_layout = render_device.create_bind_group_layout(
|
||||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
"outline_view_bind_group_layout",
|
||||||
label: Some("outline_view_bind_group_layout"),
|
&[
|
||||||
entries: &[
|
|
||||||
BindGroupLayoutEntry {
|
BindGroupLayoutEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: ShaderStages::VERTEX,
|
visibility: ShaderStages::VERTEX,
|
||||||
@ -193,20 +190,17 @@ impl FromWorld for OutlinePipeline {
|
|||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
);
|
||||||
let outline_volume_bind_group_layout =
|
let outline_volume_bind_group_layout = render_device.create_bind_group_layout(
|
||||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
"outline_volume_bind_group_layout",
|
||||||
label: Some("outline_volume_bind_group_layout"),
|
&[
|
||||||
entries: &[
|
|
||||||
BindGroupLayoutEntry {
|
BindGroupLayoutEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: ShaderStages::VERTEX,
|
visibility: ShaderStages::VERTEX,
|
||||||
ty: BindingType::Buffer {
|
ty: BindingType::Buffer {
|
||||||
ty: BufferBindingType::Uniform,
|
ty: BufferBindingType::Uniform,
|
||||||
has_dynamic_offset: true,
|
has_dynamic_offset: true,
|
||||||
min_binding_size: BufferSize::new(
|
min_binding_size: BufferSize::new(OutlineVolumeUniform::SHADER_SIZE.get()),
|
||||||
OutlineVolumeUniform::SHADER_SIZE.get(),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
@ -223,11 +217,10 @@ impl FromWorld for OutlinePipeline {
|
|||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
);
|
||||||
let outline_stencil_bind_group_layout =
|
let outline_stencil_bind_group_layout = render_device.create_bind_group_layout(
|
||||||
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
"outline_stencil_bind_group_layout",
|
||||||
label: Some("outline_stencil_bind_group_layout"),
|
&[BindGroupLayoutEntry {
|
||||||
entries: &[BindGroupLayoutEntry {
|
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: ShaderStages::VERTEX,
|
visibility: ShaderStages::VERTEX,
|
||||||
ty: BindingType::Buffer {
|
ty: BindingType::Buffer {
|
||||||
@ -237,7 +230,7 @@ impl FromWorld for OutlinePipeline {
|
|||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
}],
|
}],
|
||||||
});
|
);
|
||||||
OutlinePipeline {
|
OutlinePipeline {
|
||||||
mesh_pipeline,
|
mesh_pipeline,
|
||||||
outline_view_bind_group_layout,
|
outline_view_bind_group_layout,
|
||||||
@ -373,21 +366,20 @@ impl SpecializedMeshPipeline for OutlinePipeline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GetBatchData for OutlinePipeline {
|
impl GetBatchData for OutlinePipeline {
|
||||||
type Param = ();
|
type Param = SQuery<&'static ExtractedOutline>;
|
||||||
type Query = Read<ExtractedOutline>;
|
|
||||||
type QueryFilter = ();
|
|
||||||
type CompareData = ();
|
type CompareData = ();
|
||||||
type BufferData = MeshUniform;
|
type BufferData = MeshUniform;
|
||||||
|
|
||||||
fn get_batch_data(
|
fn get_batch_data(
|
||||||
_: &SystemParamItem<Self::Param>,
|
param: &SystemParamItem<Self::Param>,
|
||||||
outline: &QueryItem<Self::Query>,
|
entity: Entity,
|
||||||
) -> (Self::BufferData, Option<Self::CompareData>) {
|
) -> Option<(Self::BufferData, Option<Self::CompareData>)> {
|
||||||
|
let outline = param.get(entity).unwrap();
|
||||||
let ts = MeshTransforms {
|
let ts = MeshTransforms {
|
||||||
transform: (&outline.transform).into(),
|
transform: (&outline.transform).into(),
|
||||||
previous_transform: (&outline.transform).into(),
|
previous_transform: (&outline.transform).into(),
|
||||||
flags: MeshFlags::NONE.bits(),
|
flags: MeshFlags::NONE.bits(),
|
||||||
};
|
};
|
||||||
((&ts).into(), None)
|
Some((MeshUniform::new(&ts, None), None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,20 +149,23 @@ pub(crate) fn prepare_outline_volume_bind_group(
|
|||||||
pub(crate) struct SetOutlineStencilBindGroup<const I: usize>();
|
pub(crate) struct SetOutlineStencilBindGroup<const I: usize>();
|
||||||
|
|
||||||
impl<const I: usize> RenderCommand<StencilOutline> for SetOutlineStencilBindGroup<I> {
|
impl<const I: usize> RenderCommand<StencilOutline> for SetOutlineStencilBindGroup<I> {
|
||||||
type ViewWorldQuery = ();
|
type ViewQuery = ();
|
||||||
type ItemWorldQuery = Read<DynamicUniformIndex<OutlineStencilUniform>>;
|
type ItemQuery = Read<DynamicUniformIndex<OutlineStencilUniform>>;
|
||||||
type Param = SRes<OutlineStencilBindGroup>;
|
type Param = SRes<OutlineStencilBindGroup>;
|
||||||
fn render<'w>(
|
fn render<'w>(
|
||||||
_item: &StencilOutline,
|
_item: &StencilOutline,
|
||||||
_view_data: (),
|
_view_data: (),
|
||||||
entity_data: &DynamicUniformIndex<OutlineStencilUniform>,
|
entity_data: Option<&DynamicUniformIndex<OutlineStencilUniform>>,
|
||||||
bind_group: SystemParamItem<'w, '_, Self::Param>,
|
bind_group: SystemParamItem<'w, '_, Self::Param>,
|
||||||
pass: &mut TrackedRenderPass<'w>,
|
pass: &mut TrackedRenderPass<'w>,
|
||||||
) -> RenderCommandResult {
|
) -> RenderCommandResult {
|
||||||
|
let Some(dyn_uniform) = entity_data else {
|
||||||
|
return RenderCommandResult::Failure;
|
||||||
|
};
|
||||||
pass.set_bind_group(
|
pass.set_bind_group(
|
||||||
I,
|
I,
|
||||||
&bind_group.into_inner().bind_group,
|
&bind_group.into_inner().bind_group,
|
||||||
&[entity_data.index()],
|
&[dyn_uniform.index()],
|
||||||
);
|
);
|
||||||
RenderCommandResult::Success
|
RenderCommandResult::Success
|
||||||
}
|
}
|
||||||
@ -171,8 +174,8 @@ impl<const I: usize> RenderCommand<StencilOutline> for SetOutlineStencilBindGrou
|
|||||||
pub(crate) struct SetOutlineVolumeBindGroup<const I: usize>();
|
pub(crate) struct SetOutlineVolumeBindGroup<const I: usize>();
|
||||||
|
|
||||||
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetOutlineVolumeBindGroup<I> {
|
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetOutlineVolumeBindGroup<I> {
|
||||||
type ViewWorldQuery = ();
|
type ViewQuery = ();
|
||||||
type ItemWorldQuery = (
|
type ItemQuery = (
|
||||||
Read<DynamicUniformIndex<OutlineVolumeUniform>>,
|
Read<DynamicUniformIndex<OutlineVolumeUniform>>,
|
||||||
Read<DynamicUniformIndex<OutlineFragmentUniform>>,
|
Read<DynamicUniformIndex<OutlineFragmentUniform>>,
|
||||||
);
|
);
|
||||||
@ -180,14 +183,16 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetOutlineVolumeBindGrou
|
|||||||
fn render<'w>(
|
fn render<'w>(
|
||||||
_item: &P,
|
_item: &P,
|
||||||
_view_data: (),
|
_view_data: (),
|
||||||
entity_data: (
|
entity_data: Option<(
|
||||||
&DynamicUniformIndex<OutlineVolumeUniform>,
|
&DynamicUniformIndex<OutlineVolumeUniform>,
|
||||||
&DynamicUniformIndex<OutlineFragmentUniform>,
|
&DynamicUniformIndex<OutlineFragmentUniform>,
|
||||||
),
|
)>,
|
||||||
bind_group: SystemParamItem<'w, '_, Self::Param>,
|
bind_group: SystemParamItem<'w, '_, Self::Param>,
|
||||||
pass: &mut TrackedRenderPass<'w>,
|
pass: &mut TrackedRenderPass<'w>,
|
||||||
) -> RenderCommandResult {
|
) -> RenderCommandResult {
|
||||||
let (vertex, fragment) = entity_data;
|
let Some((vertex, fragment)) = entity_data else {
|
||||||
|
return RenderCommandResult::Failure;
|
||||||
|
};
|
||||||
pass.set_bind_group(
|
pass.set_bind_group(
|
||||||
I,
|
I,
|
||||||
&bind_group.into_inner().bind_group,
|
&bind_group.into_inner().bind_group,
|
||||||
|
@ -82,16 +82,16 @@ pub(crate) fn prepare_outline_view_bind_group(
|
|||||||
pub(crate) struct SetOutlineViewBindGroup<const I: usize>();
|
pub(crate) struct SetOutlineViewBindGroup<const I: usize>();
|
||||||
|
|
||||||
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetOutlineViewBindGroup<I> {
|
impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetOutlineViewBindGroup<I> {
|
||||||
type ViewWorldQuery = (
|
type ViewQuery = (
|
||||||
Read<ViewUniformOffset>,
|
Read<ViewUniformOffset>,
|
||||||
Read<DynamicUniformIndex<OutlineViewUniform>>,
|
Read<DynamicUniformIndex<OutlineViewUniform>>,
|
||||||
);
|
);
|
||||||
type ItemWorldQuery = ();
|
type ItemQuery = ();
|
||||||
type Param = SRes<OutlineViewBindGroup>;
|
type Param = SRes<OutlineViewBindGroup>;
|
||||||
fn render<'w>(
|
fn render<'w>(
|
||||||
_item: &P,
|
_item: &P,
|
||||||
(core_view_data, outline_view_data): ROQueryItem<'w, Self::ViewWorldQuery>,
|
(core_view_data, outline_view_data): ROQueryItem<'w, Self::ViewQuery>,
|
||||||
_entity_data: (),
|
_entity_data: Option<()>,
|
||||||
bind_group: SystemParamItem<'w, '_, Self::Param>,
|
bind_group: SystemParamItem<'w, '_, Self::Param>,
|
||||||
pass: &mut TrackedRenderPass<'w>,
|
pass: &mut TrackedRenderPass<'w>,
|
||||||
) -> RenderCommandResult {
|
) -> RenderCommandResult {
|
||||||
|
Loading…
Reference in New Issue
Block a user