1use bevy::{color::palettes::css::ORANGE, prelude::*, render::view::Hdr};
14use rand::random;
15
16fn main() {
17 let mut app = App::new();
18
19 app.add_plugins(DefaultPlugins)
20 .add_systems(Startup, setup)
21 .add_systems(Update, example_control_system);
22
23 app.run();
24}
25
26fn setup(
28 mut commands: Commands,
29 mut meshes: ResMut<Assets<Mesh>>,
30 mut materials: ResMut<Assets<StandardMaterial>>,
31 asset_server: Res<AssetServer>,
32) {
33 let base_color = Color::srgb(0.9, 0.2, 0.3);
34 let icosphere_mesh = meshes.add(Sphere::new(0.9).mesh().ico(7).unwrap());
35
36 let opaque = commands
38 .spawn((
39 Mesh3d(icosphere_mesh.clone()),
40 MeshMaterial3d(materials.add(StandardMaterial {
41 base_color,
42 alpha_mode: AlphaMode::Opaque,
43 ..default()
44 })),
45 Transform::from_xyz(-4.0, 0.0, 0.0),
46 ExampleControls {
47 unlit: true,
48 color: true,
49 },
50 ))
51 .id();
52
53 let blend = commands
55 .spawn((
56 Mesh3d(icosphere_mesh.clone()),
57 MeshMaterial3d(materials.add(StandardMaterial {
58 base_color,
59 alpha_mode: AlphaMode::Blend,
60 ..default()
61 })),
62 Transform::from_xyz(-2.0, 0.0, 0.0),
63 ExampleControls {
64 unlit: true,
65 color: true,
66 },
67 ))
68 .id();
69
70 let premultiplied = commands
72 .spawn((
73 Mesh3d(icosphere_mesh.clone()),
74 MeshMaterial3d(materials.add(StandardMaterial {
75 base_color,
76 alpha_mode: AlphaMode::Premultiplied,
77 ..default()
78 })),
79 Transform::from_xyz(0.0, 0.0, 0.0),
80 ExampleControls {
81 unlit: true,
82 color: true,
83 },
84 ))
85 .id();
86
87 let add = commands
89 .spawn((
90 Mesh3d(icosphere_mesh.clone()),
91 MeshMaterial3d(materials.add(StandardMaterial {
92 base_color,
93 alpha_mode: AlphaMode::Add,
94 ..default()
95 })),
96 Transform::from_xyz(2.0, 0.0, 0.0),
97 ExampleControls {
98 unlit: true,
99 color: true,
100 },
101 ))
102 .id();
103
104 let multiply = commands
106 .spawn((
107 Mesh3d(icosphere_mesh),
108 MeshMaterial3d(materials.add(StandardMaterial {
109 base_color,
110 alpha_mode: AlphaMode::Multiply,
111 ..default()
112 })),
113 Transform::from_xyz(4.0, 0.0, 0.0),
114 ExampleControls {
115 unlit: true,
116 color: true,
117 },
118 ))
119 .id();
120
121 let black_material = materials.add(Color::BLACK);
123 let white_material = materials.add(Color::WHITE);
124
125 let plane_mesh = meshes.add(Plane3d::default().mesh().size(2.0, 2.0));
126
127 for x in -3..4 {
128 for z in -3..4 {
129 commands.spawn((
130 Mesh3d(plane_mesh.clone()),
131 MeshMaterial3d(if (x + z) % 2 == 0 {
132 black_material.clone()
133 } else {
134 white_material.clone()
135 }),
136 Transform::from_xyz(x as f32 * 2.0, -1.0, z as f32 * 2.0),
137 ExampleControls {
138 unlit: false,
139 color: true,
140 },
141 ));
142 }
143 }
144
145 commands.spawn((PointLight::default(), Transform::from_xyz(4.0, 8.0, 4.0)));
147
148 commands.spawn((
150 Camera3d::default(),
151 Transform::from_xyz(0.0, 2.5, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
152 Hdr,
153 #[cfg(target_arch = "wasm32")]
157 Msaa::Off,
158 ));
159
160 let text_style = TextFont {
164 font: asset_server.load("fonts/FiraMono-Medium.ttf"),
165 ..default()
166 };
167
168 let label_text_style = (text_style.clone(), TextColor(ORANGE.into()));
169
170 commands.spawn((Text::new("Up / Down — Increase / Decrease Alpha\nLeft / Right — Rotate Camera\nH - Toggle HDR\nSpacebar — Toggle Unlit\nC — Randomize Colors"),
171 text_style.clone(),
172 Node {
173 position_type: PositionType::Absolute,
174 top: px(12),
175 left: px(12),
176 ..default()
177 })
178 );
179
180 commands.spawn((
181 Text::default(),
182 text_style,
183 Node {
184 position_type: PositionType::Absolute,
185 top: px(12),
186 right: px(12),
187 ..default()
188 },
189 ExampleDisplay,
190 ));
191
192 let mut label = |entity: Entity, label: &str| {
193 commands.spawn((
194 Node {
195 position_type: PositionType::Absolute,
196 ..default()
197 },
198 ExampleLabel { entity },
199 children![(
200 Text::new(label),
201 label_text_style.clone(),
202 Node {
203 position_type: PositionType::Absolute,
204 bottom: Val::ZERO,
205 ..default()
206 },
207 TextLayout::default().with_no_wrap(),
208 )],
209 ));
210 };
211
212 label(opaque, "┌─ Opaque\n│\n│\n│\n│");
213 label(blend, "┌─ Blend\n│\n│\n│");
214 label(premultiplied, "┌─ Premultiplied\n│\n│");
215 label(add, "┌─ Add\n│");
216 label(multiply, "┌─ Multiply");
217}
218
219#[derive(Component)]
220struct ExampleControls {
221 unlit: bool,
222 color: bool,
223}
224
225#[derive(Component)]
226struct ExampleLabel {
227 entity: Entity,
228}
229
230struct ExampleState {
231 alpha: f32,
232 unlit: bool,
233}
234
235#[derive(Component)]
236struct ExampleDisplay;
237
238impl Default for ExampleState {
239 fn default() -> Self {
240 ExampleState {
241 alpha: 0.9,
242 unlit: false,
243 }
244 }
245}
246
247fn example_control_system(
248 mut materials: ResMut<Assets<StandardMaterial>>,
249 controllable: Query<(&MeshMaterial3d<StandardMaterial>, &ExampleControls)>,
250 camera: Single<
251 (
252 Entity,
253 &mut Camera,
254 &mut Transform,
255 &GlobalTransform,
256 Has<Hdr>,
257 ),
258 With<Camera3d>,
259 >,
260 mut labels: Query<(&mut Node, &ExampleLabel)>,
261 mut display: Single<&mut Text, With<ExampleDisplay>>,
262 labeled: Query<&GlobalTransform>,
263 mut state: Local<ExampleState>,
264 time: Res<Time>,
265 input: Res<ButtonInput<KeyCode>>,
266 mut commands: Commands,
267) {
268 if input.pressed(KeyCode::ArrowUp) {
269 state.alpha = (state.alpha + time.delta_secs()).min(1.0);
270 } else if input.pressed(KeyCode::ArrowDown) {
271 state.alpha = (state.alpha - time.delta_secs()).max(0.0);
272 }
273
274 if input.just_pressed(KeyCode::Space) {
275 state.unlit = !state.unlit;
276 }
277
278 let randomize_colors = input.just_pressed(KeyCode::KeyC);
279
280 for (material_handle, controls) in &controllable {
281 let material = materials.get_mut(material_handle).unwrap();
282
283 if controls.color && randomize_colors {
284 material.base_color = Srgba {
285 red: random(),
286 green: random(),
287 blue: random(),
288 alpha: state.alpha,
289 }
290 .into();
291 } else {
292 material.base_color.set_alpha(state.alpha);
293 }
294
295 if controls.unlit {
296 material.unlit = state.unlit;
297 }
298 }
299
300 let (entity, camera, mut camera_transform, camera_global_transform, hdr) = camera.into_inner();
301
302 if input.just_pressed(KeyCode::KeyH) {
303 if hdr {
304 commands.entity(entity).remove::<Hdr>();
305 } else {
306 commands.entity(entity).insert(Hdr);
307 }
308 }
309
310 let rotation = if input.pressed(KeyCode::ArrowLeft) {
311 time.delta_secs()
312 } else if input.pressed(KeyCode::ArrowRight) {
313 -time.delta_secs()
314 } else {
315 0.0
316 };
317
318 camera_transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(rotation));
319
320 for (mut node, label) in &mut labels {
321 let world_position = labeled.get(label.entity).unwrap().translation() + Vec3::Y;
322
323 let viewport_position = camera
324 .world_to_viewport(camera_global_transform, world_position)
325 .unwrap();
326
327 node.top = px(viewport_position.y);
328 node.left = px(viewport_position.x);
329 }
330
331 display.0 = format!(
332 " HDR: {}\nAlpha: {:.2}",
333 if hdr { "ON " } else { "OFF" },
334 state.alpha
335 );
336}