1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
//! This crate provides a Bevy plugin, [`OutlinePlugin`], and associated components for
//! rendering outlines around meshes using the vertex extrusion method.
//!
//! Outlines are rendered in a seperate pass following the main 3D pass. The effect of this
//! pass is to present the outlines in depth sorted order according to the model translation
//! of each mesh. This ensures that outlines are not clipped by non-outline geometry.
//!
//! The [`OutlineVolume`] component will, by itself, cover the original object entirely with
//! the outline colour. The [`OutlineStencil`] component must also be added to prevent the body
//! of an object from being filled it. This must be added to any entity which needs to appear on
//! top of an outline.
//!
//! The [`OutlineMode`] component specifies the rendering method. Outlines may be flattened into
//! a plane in order to further avoid clipping, or left in real space.
//!
//! The [`OutlineBundle`] and [`OutlineStencilBundle`] bundles can be used to add the right
//! components, including the required [`ComputedOutline`] component. Outlines can be inherited
//! from the parent via the [`InheritOutline`] component and [`InheritOutlineBundle`].
//!
//! Vertex extrusion works best with meshes that have smooth surfaces. To avoid visual
//! artefacts when outlining meshes with hard edges, see the
//! [`OutlineMeshExt::generate_outline_normals`] function and the
//! [`AutoGenerateOutlineNormalsPlugin`].

use bevy::asset::load_internal_asset;
use bevy::core_pipeline::core_3d::graph::{Core3d, Node3d};
use bevy::prelude::*;
use bevy::render::batching::{batch_and_prepare_render_phase, write_batched_instance_buffer};
use bevy::render::extract_component::{
    ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
};
use bevy::render::mesh::MeshVertexAttribute;
use bevy::render::render_graph::{RenderGraphApp, RenderLabel};
use bevy::render::render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions};
use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat};
use bevy::render::view::{RenderLayers, VisibilitySystems};
use bevy::render::{Render, RenderApp, RenderSet};
use bevy::transform::TransformSystem;
use interpolation::Lerp;

use crate::draw::{
    queue_outline_stencil_mesh, queue_outline_volume_mesh, DrawOutline, DrawStencil,
};
use crate::node::{OpaqueOutline, OutlineNode, StencilOutline, TransparentOutline};
use crate::pipeline::{OutlinePipeline, FRAGMENT_SHADER_HANDLE, OUTLINE_SHADER_HANDLE};
use crate::uniforms::{
    extract_outline_uniforms, prepare_outline_stencil_bind_group,
    prepare_outline_volume_bind_group, set_outline_visibility, OutlineFragmentUniform,
    OutlineStencilUniform, OutlineVolumeUniform,
};
use crate::view_uniforms::{
    extract_outline_view_uniforms, prepare_outline_view_bind_group, OutlineViewUniform,
};

mod computed;
mod draw;
mod generate;
mod node;
mod pipeline;
mod scene;
mod uniforms;
mod view_uniforms;

pub use computed::*;
pub use generate::*;
pub use scene::*;

// See https://alexanderameye.github.io/notes/rendering-outlines/

/// The direction to extrude the vertex when rendering the outline.
pub const ATTRIBUTE_OUTLINE_NORMAL: MeshVertexAttribute =
    MeshVertexAttribute::new("Outline_Normal", 1585570526, VertexFormat::Float32x3);

/// Labels for render graph nodes which draw outlines.
#[derive(Copy, Clone, Debug, RenderLabel, Hash, PartialEq, Eq)]
pub enum NodeOutline {
    /// This node runs after the main 3D passes and before the UI pass.
    OutlinePass,
}

/// A component for stenciling meshes during outline rendering.
#[derive(Clone, Component)]
pub struct OutlineStencil {
    /// Enable rendering of the stencil
    pub enabled: bool,
    /// Offset of the stencil in logical pixels
    pub offset: f32,
}

impl Default for OutlineStencil {
    fn default() -> Self {
        OutlineStencil {
            enabled: true,
            offset: 0.0,
        }
    }
}

fn lerp_bool(this: bool, other: bool, scalar: f32) -> bool {
    if scalar <= 0.0 {
        this
    } else if scalar >= 1.0 {
        other
    } else {
        this | other
    }
}

impl Lerp for OutlineStencil {
    type Scalar = f32;

    fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
        OutlineStencil {
            enabled: lerp_bool(self.enabled, other.enabled, *scalar),
            offset: self.offset.lerp(other.offset, *scalar),
        }
    }
}

/// A component for rendering outlines around meshes.
#[derive(Clone, Component, Default)]
pub struct OutlineVolume {
    /// Enable rendering of the outline
    pub visible: bool,
    /// Width of the outline in logical pixels
    pub width: f32,
    /// Colour of the outline
    pub colour: Color,
}

impl Lerp for OutlineVolume {
    type Scalar = f32;

    fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
        OutlineVolume {
            visible: lerp_bool(self.visible, other.visible, *scalar),
            width: self.width.lerp(other.width, *scalar),
            colour: {
                let [r, g, b, a] = self
                    .colour
                    .as_linear_rgba_f32()
                    .lerp(&other.colour.as_linear_rgba_f32(), scalar);
                Color::rgba_linear(r, g, b, a)
            },
        }
    }
}

/// A component for specifying what layer(s) the outline should be rendered for.
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, Default)]
pub struct OutlineRenderLayers(pub RenderLayers);

impl ExtractComponent for OutlineRenderLayers {
    type QueryData = (
        Option<&'static OutlineRenderLayers>,
        Option<&'static RenderLayers>,
    );
    type QueryFilter = With<ComputedOutline>;
    type Out = Self;

    fn extract_component(
        (outline_mask, object_mask): (Option<&OutlineRenderLayers>, Option<&RenderLayers>),
    ) -> Option<Self> {
        Some(
            outline_mask
                .copied()
                .or_else(|| object_mask.copied().map(OutlineRenderLayers))
                .unwrap_or_default(),
        )
    }
}

/// A component which specifies how the outline should be rendered.
#[derive(Clone, Component)]
#[non_exhaustive]
pub enum OutlineMode {
    /// Vertex extrusion flattened into a plane facing the camera and intersecting the specified
    /// point in model-space.
    FlatVertex { model_origin: Vec3 },
    /// Vertex extrusion in real model-space.
    RealVertex,
}

impl Default for OutlineMode {
    fn default() -> Self {
        OutlineMode::FlatVertex {
            model_origin: Vec3::ZERO,
        }
    }
}

/// A component for inheriting outlines from the parent entity.
#[derive(Clone, Component, Default)]
pub struct InheritOutline;

/// A bundle for rendering stenciled outlines around meshes.
#[derive(Bundle, Clone, Default)]
pub struct OutlineBundle {
    pub outline: OutlineVolume,
    pub stencil: OutlineStencil,
    pub mode: OutlineMode,
    pub computed: ComputedOutline,
}

/// A bundle for stenciling meshes in the outlining pass.
#[derive(Bundle, Clone, Default)]
pub struct OutlineStencilBundle {
    pub stencil: OutlineStencil,
    pub mode: OutlineMode,
    pub computed: ComputedOutline,
}

/// A bundle for inheriting outlines from the parent entity.
#[derive(Bundle, Clone, Default)]
pub struct InheritOutlineBundle {
    pub inherit: InheritOutline,
    pub computed: ComputedOutline,
}

/// Adds support for rendering outlines.
pub struct OutlinePlugin;

impl Plugin for OutlinePlugin {
    fn build(&self, app: &mut App) {
        load_internal_asset!(
            app,
            OUTLINE_SHADER_HANDLE,
            "outline.wgsl",
            Shader::from_wgsl
        );
        load_internal_asset!(
            app,
            FRAGMENT_SHADER_HANDLE,
            "fragment.wgsl",
            Shader::from_wgsl
        );

        app.add_plugins((
            ExtractComponentPlugin::<OutlineRenderLayers>::default(),
            UniformComponentPlugin::<OutlineStencilUniform>::default(),
            UniformComponentPlugin::<OutlineVolumeUniform>::default(),
            UniformComponentPlugin::<OutlineFragmentUniform>::default(),
            UniformComponentPlugin::<OutlineViewUniform>::default(),
        ))
        .add_systems(
            PostUpdate,
            (
                compute_outline
                    .after(TransformSystem::TransformPropagate)
                    .after(VisibilitySystems::VisibilityPropagate),
                set_outline_visibility.in_set(VisibilitySystems::CheckVisibility),
            ),
        )
        .sub_app_mut(RenderApp)
        .init_resource::<DrawFunctions<StencilOutline>>()
        .init_resource::<DrawFunctions<OpaqueOutline>>()
        .init_resource::<DrawFunctions<TransparentOutline>>()
        .init_resource::<SpecializedMeshPipelines<OutlinePipeline>>()
        .add_render_command::<StencilOutline, DrawStencil>()
        .add_render_command::<OpaqueOutline, DrawOutline>()
        .add_render_command::<TransparentOutline, DrawOutline>()
        .add_systems(
            ExtractSchedule,
            (extract_outline_uniforms, extract_outline_view_uniforms),
        )
        .add_systems(
            Render,
            (
                prepare_outline_view_bind_group,
                prepare_outline_stencil_bind_group,
                prepare_outline_volume_bind_group,
            )
                .in_set(RenderSet::PrepareBindGroups),
        )
        .add_systems(
            Render,
            (queue_outline_stencil_mesh, queue_outline_volume_mesh).in_set(RenderSet::QueueMeshes),
        )
        .add_systems(
            Render,
            (
                sort_phase_system::<StencilOutline>,
                sort_phase_system::<OpaqueOutline>,
                sort_phase_system::<TransparentOutline>,
            )
                .in_set(RenderSet::PhaseSort),
        )
        .add_systems(
            Render,
            (
                batch_and_prepare_render_phase::<StencilOutline, OutlinePipeline>,
                batch_and_prepare_render_phase::<OpaqueOutline, OutlinePipeline>,
                batch_and_prepare_render_phase::<TransparentOutline, OutlinePipeline>,
            )
                .in_set(RenderSet::PrepareResources),
        )
        .add_systems(
            Render,
            write_batched_instance_buffer::<OutlinePipeline>
                .in_set(RenderSet::PrepareResourcesFlush),
        )
        .add_render_graph_node::<OutlineNode>(Core3d, NodeOutline::OutlinePass)
        .add_render_graph_edges(
            Core3d,
            (
                Node3d::Tonemapping,
                NodeOutline::OutlinePass,
                Node3d::Fxaa,
                Node3d::EndMainPassPostProcessing,
            ),
        );
    }

    fn finish(&self, app: &mut App) {
        app.sub_app_mut(RenderApp)
            .init_resource::<OutlinePipeline>();
    }
}