217 lines
6.5 KiB
Rust
217 lines
6.5 KiB
Rust
use bevy::{ecs::query::QueryItem, prelude::*};
|
|
|
|
use crate::{uniforms::DepthMode, InheritOutline, OutlineMode, OutlineStencil, OutlineVolume};
|
|
|
|
#[derive(Clone, Default)]
|
|
pub(crate) struct ComputedVolume {
|
|
pub(crate) enabled: bool,
|
|
pub(crate) offset: f32,
|
|
pub(crate) colour: Vec4,
|
|
}
|
|
|
|
#[derive(Clone, Default)]
|
|
pub(crate) struct ComputedStencil {
|
|
pub(crate) enabled: bool,
|
|
pub(crate) offset: f32,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct ComputedMode {
|
|
pub(crate) world_origin: Vec3,
|
|
pub(crate) depth_mode: DepthMode,
|
|
}
|
|
|
|
impl Default for ComputedMode {
|
|
fn default() -> Self {
|
|
Self {
|
|
world_origin: Vec3::NAN,
|
|
depth_mode: DepthMode::Real,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Default)]
|
|
pub(crate) enum Source {
|
|
#[default]
|
|
Set,
|
|
Inherited,
|
|
}
|
|
|
|
#[derive(Clone, Default)]
|
|
pub(crate) struct Sourced<T: Clone + Default> {
|
|
pub(crate) value: T,
|
|
pub(crate) source: Source,
|
|
}
|
|
|
|
impl<T: Clone + Default> Sourced<T> {
|
|
pub fn inherit(value: &T) -> Self {
|
|
Sourced {
|
|
value: value.clone(),
|
|
source: Source::Inherited,
|
|
}
|
|
}
|
|
|
|
pub fn set(value: T) -> Self {
|
|
Sourced {
|
|
value,
|
|
source: Source::Set,
|
|
}
|
|
}
|
|
|
|
pub fn is_changed<U: Component>(&self, tuple: &Option<Ref<U>>) -> bool {
|
|
tuple.is_some() != matches!(self.source, Source::Set)
|
|
|| if let Some(r) = tuple {
|
|
r.is_changed()
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Default)]
|
|
pub(crate) struct ComputedInternal {
|
|
pub(crate) inherited_from: Option<Entity>,
|
|
pub(crate) volume: Sourced<ComputedVolume>,
|
|
pub(crate) stencil: Sourced<ComputedStencil>,
|
|
pub(crate) mode: Sourced<ComputedMode>,
|
|
}
|
|
|
|
/// A component for storing the computed depth at which the outline lies.
|
|
#[derive(Clone, Component, Default)]
|
|
pub struct ComputedOutline(pub(crate) Option<ComputedInternal>);
|
|
|
|
type OutlineComponents<'a> = (
|
|
Ref<'a, InheritedVisibility>,
|
|
Ref<'a, GlobalTransform>,
|
|
Option<Ref<'a, OutlineVolume>>,
|
|
Option<Ref<'a, OutlineStencil>>,
|
|
Option<Ref<'a, OutlineMode>>,
|
|
);
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
pub(crate) fn compute_outline(
|
|
mut root_query: Query<
|
|
(
|
|
Entity,
|
|
&mut ComputedOutline,
|
|
OutlineComponents,
|
|
Option<&Children>,
|
|
),
|
|
Without<InheritOutline>,
|
|
>,
|
|
mut child_query_mut: Query<(&mut ComputedOutline, OutlineComponents), With<InheritOutline>>,
|
|
child_query: Query<&Children>,
|
|
) {
|
|
for (entity, mut computed, components, children) in root_query.iter_mut() {
|
|
let changed = update_computed_outline(&mut computed, components, &default(), None, false);
|
|
if let Some(cs) = children {
|
|
let parent_computed = computed.0.as_ref().unwrap();
|
|
for child in cs.iter() {
|
|
propagate_computed_outline(
|
|
parent_computed,
|
|
changed,
|
|
entity,
|
|
*child,
|
|
&mut child_query_mut,
|
|
&child_query,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn propagate_computed_outline(
|
|
parent_computed: &ComputedInternal,
|
|
parent_changed: bool,
|
|
parent_entity: Entity,
|
|
entity: Entity,
|
|
child_query_mut: &mut Query<(&mut ComputedOutline, OutlineComponents), With<InheritOutline>>,
|
|
child_query: &Query<&Children>,
|
|
) {
|
|
if let Ok((mut computed, components)) = child_query_mut.get_mut(entity) {
|
|
let changed = update_computed_outline(
|
|
&mut computed,
|
|
components,
|
|
parent_computed,
|
|
Some(parent_entity),
|
|
parent_changed,
|
|
);
|
|
if let Ok(cs) = child_query.get(entity) {
|
|
let parent_computed = &computed.0.as_ref().unwrap().clone();
|
|
for child in cs.iter() {
|
|
propagate_computed_outline(
|
|
parent_computed,
|
|
changed,
|
|
entity,
|
|
*child,
|
|
child_query_mut,
|
|
child_query,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update_computed_outline(
|
|
computed: &mut ComputedOutline,
|
|
(visibility, transform, volume, stencil, mode): QueryItem<'_, OutlineComponents>,
|
|
parent_computed: &ComputedInternal,
|
|
parent_entity: Option<Entity>,
|
|
force_update: bool,
|
|
) -> bool {
|
|
let changed = force_update
|
|
|| if let ComputedOutline(Some(computed)) = computed {
|
|
computed.inherited_from != parent_entity
|
|
|| visibility.is_changed()
|
|
|| (transform.is_changed()
|
|
&& mode
|
|
.as_ref()
|
|
.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 {
|
|
true
|
|
};
|
|
if changed {
|
|
*computed = ComputedOutline(Some(ComputedInternal {
|
|
inherited_from: parent_entity,
|
|
volume: if let Some(vol) = volume {
|
|
Sourced::set(ComputedVolume {
|
|
enabled: visibility.get() && vol.visible && vol.colour.a() != 0.0,
|
|
offset: vol.width,
|
|
colour: vol.colour.as_linear_rgba_f32().into(),
|
|
})
|
|
} else {
|
|
Sourced::inherit(&parent_computed.volume.value)
|
|
},
|
|
stencil: if let Some(sten) = stencil {
|
|
Sourced::set(ComputedStencil {
|
|
enabled: visibility.get() && sten.enabled,
|
|
offset: sten.offset,
|
|
})
|
|
} else {
|
|
Sourced::inherit(&parent_computed.stencil.value)
|
|
},
|
|
mode: if let Some(m) = mode {
|
|
Sourced::set(match m.as_ref() {
|
|
OutlineMode::FlatVertex {
|
|
model_origin: origin,
|
|
} => ComputedMode {
|
|
world_origin: transform.compute_matrix().project_point3(*origin),
|
|
depth_mode: DepthMode::Flat,
|
|
},
|
|
OutlineMode::RealVertex => ComputedMode {
|
|
world_origin: Vec3::NAN,
|
|
depth_mode: DepthMode::Real,
|
|
},
|
|
})
|
|
} else {
|
|
Sourced::inherit(&parent_computed.mode.value)
|
|
},
|
|
}));
|
|
}
|
|
changed
|
|
}
|