cloudiful_bevy_outline/
lib.rs1#![doc = include_str!("../README.md")]
2#![deny(rustdoc::broken_intra_doc_links)]
3
4use bevy::ecs::hierarchy::ChildSpawnerCommands;
5use bevy::prelude::*;
6use bevy::render::render_resource::Face;
7
8pub struct GpmoOutlinePlugin {
10 default_style: OutlineStyle,
11}
12
13impl Default for GpmoOutlinePlugin {
14 fn default() -> Self {
15 Self {
16 default_style: OutlineStyle::default(),
17 }
18 }
19}
20
21impl GpmoOutlinePlugin {
22 pub fn with_default_style(default_style: OutlineStyle) -> Self {
24 Self { default_style }
25 }
26}
27
28#[derive(Resource, Debug, Clone)]
30pub struct OutlineAssets {
31 default_material: Handle<StandardMaterial>,
32 default_style: OutlineStyle,
33}
34
35impl OutlineAssets {
36 pub fn default_material(&self) -> Handle<StandardMaterial> {
37 self.default_material.clone()
38 }
39
40 pub fn default_style(&self) -> OutlineStyle {
41 self.default_style
42 }
43}
44
45#[derive(Component, Debug, Clone, Copy)]
47pub struct OutlineShell;
48
49#[derive(Debug, Clone, Copy, PartialEq)]
51pub struct OutlineStyle {
52 pub color: Color,
53 pub emissive_strength: f32,
54 pub scale: Vec3,
55}
56
57impl Default for OutlineStyle {
58 fn default() -> Self {
59 Self {
60 color: Color::srgb(0.43, 0.94, 0.98),
61 emissive_strength: 2.0,
62 scale: Vec3::splat(1.08),
63 }
64 }
65}
66
67impl Plugin for GpmoOutlinePlugin {
68 fn build(&self, app: &mut App) {
69 let default_style = self.default_style;
70 app.insert_resource(PendingOutlineStyle(default_style))
71 .add_systems(Startup, setup_outline_assets);
72 }
73}
74
75#[derive(Resource, Debug, Clone, Copy)]
76struct PendingOutlineStyle(OutlineStyle);
77
78pub fn create_outline_material(
80 materials: &mut Assets<StandardMaterial>,
81 style: OutlineStyle,
82) -> Handle<StandardMaterial> {
83 materials.add(StandardMaterial {
84 base_color: style.color,
85 emissive: style.color.to_linear() * style.emissive_strength,
86 unlit: true,
87 cull_mode: Some(Face::Front),
88 ..default()
89 })
90}
91
92pub fn outline_shell_transform(style: OutlineStyle) -> Transform {
94 Transform::from_scale(style.scale)
95}
96
97pub fn spawn_outline_mesh<'a>(
99 parent: &'a mut ChildSpawnerCommands,
100 mesh: Handle<Mesh>,
101 material: Handle<StandardMaterial>,
102 style: OutlineStyle,
103 visibility: Visibility,
104) -> EntityCommands<'a> {
105 parent.spawn((
106 OutlineShell,
107 Mesh3d(mesh),
108 MeshMaterial3d(material),
109 outline_shell_transform(style),
110 visibility,
111 ))
112}
113
114pub fn spawn_default_outline_mesh<'a>(
116 parent: &'a mut ChildSpawnerCommands,
117 outline_assets: &OutlineAssets,
118 mesh: Handle<Mesh>,
119 visibility: Visibility,
120) -> EntityCommands<'a> {
121 spawn_outline_mesh(
122 parent,
123 mesh,
124 outline_assets.default_material(),
125 outline_assets.default_style(),
126 visibility,
127 )
128}
129
130fn setup_outline_assets(
131 mut commands: Commands,
132 pending_style: Res<PendingOutlineStyle>,
133 mut materials: ResMut<Assets<StandardMaterial>>,
134) {
135 let style = pending_style.0;
136 let default_material = create_outline_material(&mut materials, style);
137 commands.insert_resource(OutlineAssets {
138 default_material,
139 default_style: style,
140 });
141}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[test]
148 fn default_style_has_positive_scale() {
149 let style = OutlineStyle::default();
150
151 assert!(style.scale.x > 1.0);
152 assert!(style.scale.y > 1.0);
153 assert!(style.scale.z > 1.0);
154 }
155
156 #[test]
157 fn outline_transform_uses_style_scale() {
158 let style = OutlineStyle {
159 scale: Vec3::splat(1.25),
160 ..default()
161 };
162
163 let transform = outline_shell_transform(style);
164
165 assert_eq!(transform.scale, Vec3::splat(1.25));
166 }
167}