1use mirage_engine::prelude::*;
25
26const GROUND_SIZE: f32 = 18.0;
28
29const PAIR_HALF_SPACING: f32 = 1.0;
32
33const SHADING_Z: f32 = 3.4;
35const RELIEF_Z: f32 = 1.8;
36const EMISSIVE_Z: f32 = 0.2;
37
38const SPHERE_RADIUS: f32 = 0.5;
40const CUBE_SIZE: f32 = 0.85;
42
43const FRONT_POSITION: Vec3 = Vec3::new(0.0, 0.7, -1.6);
46const FRONT_SCALE: f32 = 1.4;
47
48const SPHERE_SUBDIVISIONS: u32 = 3;
50
51const REFLECT_ROW_COUNT: usize = 5;
54const REFLECT_ROW_SPACING: f32 = 1.5;
55const REFLECT_ROW_Z: f32 = -1.6;
56const REFLECT_ROW_RADIUS: f32 = 0.55;
57
58const GROUND_COLOR: Color = Color::rgb(0.24, 0.25, 0.22);
59const SHADING_TINT: Color = Color::rgb(0.55, 0.55, 0.6);
60const RELIEF_TINT: Color = Color::rgb(0.5, 0.45, 0.35);
61const EMISSIVE_BASE: Color = Color::rgb(0.04, 0.04, 0.05);
62const EMISSIVE_GLOW: Color = Color::rgb(3.2, 2.2, 0.7);
63
64const MAP_SIZE: UVec2 = UVec2::new(64, 64);
67
68const SHADING_CELL: u32 = 8;
70const SHADING_LOW: [u8; 3] = [70, 40, 15];
73const SHADING_HIGH: [u8; 3] = [255, 225, 235];
74
75const EMISSIVE_CELL: u32 = 6;
77
78const BUMP_WAVES: f32 = 6.0;
81const BUMP_SLOPE: f32 = 1.15;
82
83const BANNER_WIDTH: f32 = 1.1;
85const BANNER_HEIGHT: f32 = 0.7;
86
87const BANNER_COLUMNS: u32 = 10;
89
90const OUTPOST: Vec3 = Vec3::new(-4.6, 0.0, -0.8);
95
96const PILLARS: [(Vec3, Vec3); 3] = [
97 (Vec3::new(-1.8, 0.6, -0.6), Vec3::new(0.6, 1.2, 0.6)),
98 (Vec3::new(0.4, 0.4, -1.6), Vec3::new(0.5, 0.8, 0.5)),
99 (Vec3::new(1.7, 0.9, 0.4), Vec3::new(0.55, 1.8, 0.55)),
100];
101
102const POLE_POSITION: Vec3 = Vec3::new(-2.6, 1.0, 0.4);
103const POLE_SCALE: Vec3 = Vec3::new(0.12, 2.0, 0.12);
104const BANNER_MOUNT: Vec3 = Vec3::new(-2.54, 1.55, 0.46);
105
106const FIELD_ORB_POSITION: Vec3 = Vec3::new(1.3, 1.1, 1.6);
107const FIELD_ORB_SCALE: f32 = 0.7;
108
109const LAMP_POSITION: Vec3 = Vec3::new(2.4, 1.4, -3.0);
111const LAMP_RANGE: f32 = 5.0;
112
113const SPOT_POSITION: Vec3 = Vec3::new(-3.4, 3.0, 1.6);
116const SPOT_DIRECTION: Vec3 = Vec3::new(0.55, -1.0, -1.0);
117const SPOT_RANGE: f32 = 7.0;
118const SPOT_ANGLE: f32 = 0.5;
119
120const CAMERA_FOV: f32 = 46.0;
122const START_EYE: Vec3 = Vec3::new(-0.6, 2.2, 9.0);
125const START_YAW: f32 = 0.0;
126const START_PITCH: f32 = -0.15;
127const PITCH_LIMIT: f32 = 1.5;
130const MIN_EYE_HEIGHT: f32 = 0.3;
133const LOOK_SENSITIVITY: f32 = core::f32::consts::FRAC_PI_2 / 1280.0;
137const MOVE_SPEED: f32 = 4.0;
140const SPEED_STEP: f32 = 1.5;
142const MIN_SPEED_SCALE: f32 = 0.2;
144const MAX_SPEED_SCALE: f32 = 5.0;
145
146const START_BLOOM: f32 = 0.25;
149const START_EXPOSURE: f32 = 1.0;
150
151const DEFAULT_SKY: Color = Color::rgb(0.1, 0.1, 0.1);
154
155const CLEAR_SKY_LIGHT: f32 = 0.35;
161const CLASSIC_SKY_LIGHT: f32 = 0.35;
162const DAWN_SKY_LIGHT: f32 = 0.3;
163const SINISTER_SKY_LIGHT: f32 = 0.6;
164const LIGHT_BLUE_STARS_LIGHT: f32 = 0.8;
165const BLUE_STARS_LIGHT: f32 = 0.8;
166
167#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
170struct ShadingPlain;
171
172impl Mesh for ShadingPlain {
173 fn build(&self, assets: &Assets) -> MeshData {
174 sphere_with_material(assets, shading_material())
175 }
176}
177
178#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
181struct ShadingMapped;
182
183impl Mesh for ShadingMapped {
184 fn build(&self, assets: &Assets) -> MeshData {
185 sphere_with_material(assets, shading_material()).with_shading(shading_checker())
186 }
187}
188
189#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
191struct ReliefPlain;
192
193impl Mesh for ReliefPlain {
194 fn build(&self, assets: &Assets) -> MeshData {
195 sphere_with_material(assets, relief_material())
196 }
197}
198
199#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
202struct ReliefMapped;
203
204impl Mesh for ReliefMapped {
205 fn build(&self, assets: &Assets) -> MeshData {
206 sphere_with_material(assets, relief_material()).with_relief(relief_bumps())
207 }
208}
209
210#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
212struct EmissivePlain;
213
214impl Mesh for EmissivePlain {
215 fn build(&self, assets: &Assets) -> MeshData {
216 cube_with_material(assets, emissive_material())
217 }
218}
219
220#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
223struct EmissiveMapped;
224
225impl Mesh for EmissiveMapped {
226 fn build(&self, assets: &Assets) -> MeshData {
227 cube_with_material(assets, emissive_material()).with_emissive_map(emissive_checker())
228 }
229}
230
231#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
234struct Front;
235
236impl Mesh for Front {
237 fn build(&self, assets: &Assets) -> MeshData {
238 Sphere {
239 subdivisions: SPHERE_SUBDIVISIONS,
240 }
241 .build(assets)
242 }
243}
244
245#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
251struct BannerCloth;
252
253impl Mesh for BannerCloth {
254 fn build(&self, _: &Assets) -> MeshData {
255 banner_mesh()
256 }
257}
258
259meshes! {
264 enum Shape {
265 Plane,
266 Sphere,
267 Cube,
268 ShadingPlain,
269 ShadingMapped,
270 ReliefPlain,
271 ReliefMapped,
272 EmissivePlain,
273 EmissiveMapped,
274 Front,
275 BannerCloth,
276 }
277}
278
279fn sphere_with_material(assets: &Assets, material: Material) -> MeshData {
280 Sphere {
281 subdivisions: SPHERE_SUBDIVISIONS,
282 }
283 .build(assets)
284 .with_material(material)
285}
286
287fn cube_with_material(assets: &Assets, material: Material) -> MeshData {
288 Cube.build(assets).with_material(material)
289}
290
291fn shading_material() -> Material {
292 Material::lit(SHADING_TINT).roughness(0.5).metallic(0.5)
293}
294
295fn relief_material() -> Material {
296 Material::lit(RELIEF_TINT).roughness(0.35)
297}
298
299fn emissive_material() -> Material {
300 Material::color(EMISSIVE_BASE).emissive(EMISSIVE_GLOW)
301}
302
303fn shading_checker() -> ShadingData {
307 ShadingData::rgba8(
308 MAP_SIZE,
309 checker_pixels(MAP_SIZE, SHADING_CELL, SHADING_LOW, SHADING_HIGH),
310 )
311}
312
313fn emissive_checker() -> TextureData {
317 TextureData::rgba8(
318 MAP_SIZE,
319 checker_pixels(MAP_SIZE, EMISSIVE_CELL, [0, 0, 0], [255, 255, 255]),
320 )
321}
322
323fn checker_pixels(size: UVec2, cell: u32, low: [u8; 3], high: [u8; 3]) -> Vec<u8> {
324 let mut pixels = Vec::with_capacity((size.x * size.y * 4) as usize);
325 for y in 0..size.y {
326 for x in 0..size.x {
327 let on = ((x / cell) + (y / cell)).is_multiple_of(2);
328 let [red, green, blue] = if on { high } else { low };
329 pixels.extend_from_slice(&[red, green, blue, u8::MAX]);
330 }
331 }
332 pixels
333}
334
335fn relief_bumps() -> ReliefData {
340 let size = MAP_SIZE;
341 let turns = core::f32::consts::TAU * BUMP_WAVES;
342 let mut pixels = Vec::with_capacity((size.x * size.y * 4) as usize);
343 for y in 0..size.y {
344 for x in 0..size.x {
345 let u = (x as f32 + 0.5) / size.x as f32;
346 let v = (y as f32 + 0.5) / size.y as f32;
347 let slope_u = BUMP_SLOPE * (turns * u).cos() * (turns * v).sin();
348 let slope_v = BUMP_SLOPE * (turns * u).sin() * (turns * v).cos();
349 let normal = Vec3::new(-slope_u, -slope_v, 1.0).normalize();
350 let encode = |signed: f32| ((signed * 0.5 + 0.5) * 255.0).round() as u8;
351 pixels.extend_from_slice(&[encode(normal.x), encode(normal.y), encode(normal.z), 0]);
352 }
353 }
354 ReliefData::normals(size, pixels)
355}
356
357fn banner_mesh() -> MeshData {
361 let mut vertices = Vec::with_capacity(((BANNER_COLUMNS + 1) * 4) as usize);
362 for normal in [Vec3::Z, Vec3::NEG_Z] {
363 for column in 0..=BANNER_COLUMNS {
364 let u = column as f32 / BANNER_COLUMNS as f32;
365 let x = u * BANNER_WIDTH;
366 for v in [0.0, 1.0] {
367 vertices.push(Vertex::new(
368 Vec3::new(x, -v * BANNER_HEIGHT, 0.0),
369 normal,
370 Vec2::new(u, v),
371 ));
372 }
373 }
374 }
375
376 let side = BANNER_COLUMNS + 1;
377 let mut indices = Vec::with_capacity((BANNER_COLUMNS * 12) as usize);
378 for column in 0..BANNER_COLUMNS {
379 let top_left = column * 2;
380 let bottom_left = top_left + 1;
381 let top_right = top_left + 2;
382 let bottom_right = top_left + 3;
383 indices.extend([
384 bottom_left,
385 bottom_right,
386 top_right,
387 bottom_left,
388 top_right,
389 top_left,
390 ]);
391
392 let back = side * 2;
393 indices.extend([
394 back + top_right,
395 back + bottom_right,
396 back + bottom_left,
397 back + top_left,
398 back + top_right,
399 back + bottom_left,
400 ]);
401 }
402
403 MeshData::new(vertices, indices)
404}
405
406#[derive(Default, ShaderValues)]
410struct Banner {
411 time: f32,
412}
413
414impl SurfaceStyle for Banner {
415 const PASS: DrawPass = DrawPass::Opaque;
416 const DISPLACE: Option<&'static str> = Some(include_str!("material_playground_banner.wgsl"));
417}
418
419#[derive(Default, ShaderValues)]
423struct Field {
424 tint: Color,
425 time: f32,
426}
427
428impl SurfaceStyle for Field {
429 const PASS: DrawPass = DrawPass::Additive;
430 const SURFACE: Option<&'static str> = Some(include_str!("material_playground_field.wgsl"));
431}
432
433surface_styles! { enum Looks { Banner, Field } }
434
435#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
452enum Sky {
453 Dawn,
454 Noon,
455 Dusk,
456 Night,
457 Clear,
458 Classic,
459 ImageDawn,
460 Sinister,
461 LightBlueStars,
462 BlueStars,
463 Default,
464}
465
466impl Sky {
467 const ALL: [Sky; 11] = [
468 Self::Dawn,
469 Self::Noon,
470 Self::Dusk,
471 Self::Night,
472 Self::Clear,
473 Self::Classic,
474 Self::ImageDawn,
475 Self::Sinister,
476 Self::LightBlueStars,
477 Self::BlueStars,
478 Self::Default,
479 ];
480
481 fn name(self) -> &'static str {
482 match self {
483 Self::Dawn => "dawn",
484 Self::Noon => "noon",
485 Self::Dusk => "dusk",
486 Self::Night => "night",
487 Self::Clear => "clear day",
488 Self::Classic => "classic",
489 Self::ImageDawn => "dawn image",
490 Self::Sinister => "sinister night",
491 Self::LightBlueStars => "light blue stars",
492 Self::BlueStars => "blue stars",
493 Self::Default => "default",
494 }
495 }
496
497 fn light(self) -> f32 {
502 match self {
503 Self::Dawn => 0.4,
504 Self::Noon => 0.5,
505 Self::Dusk => 0.35,
506 Self::Night => 0.3,
507 Self::Clear => CLEAR_SKY_LIGHT,
508 Self::Classic => CLASSIC_SKY_LIGHT,
509 Self::ImageDawn => DAWN_SKY_LIGHT,
510 Self::Sinister => SINISTER_SKY_LIGHT,
511 Self::LightBlueStars => LIGHT_BLUE_STARS_LIGHT,
512 Self::BlueStars => BLUE_STARS_LIGHT,
513 Self::Default => 1.0,
514 }
515 }
516
517 fn sun(self) -> Option<(Vec3, Color, f32)> {
521 match self {
522 Self::Dawn => Some((
523 Vec3::new(-1.0, -0.15, 0.05),
524 Color::rgb(1.0, 0.7, 0.45),
525 1.4,
526 )),
527 Self::Noon => Some((
528 Vec3::new(-0.15, -1.0, -0.1),
529 Color::rgb(1.0, 1.0, 0.98),
530 1.6,
531 )),
532 Self::Dusk => Some((
533 Vec3::new(1.0, -0.15, 0.05),
534 Color::rgb(1.0, 0.55, 0.25),
535 1.2,
536 )),
537 Self::Night => Some((
538 Vec3::new(-0.3, -0.7, -0.6),
539 Color::rgb(0.55, 0.65, 0.85),
540 0.15,
541 )),
542 Self::Clear => Some((
543 Vec3::new(-0.2, -1.0, -0.15),
544 Color::rgb(1.0, 0.98, 0.9),
545 1.5,
546 )),
547 Self::Classic => Some((
548 Vec3::new(-0.4, -0.9, -0.2),
549 Color::rgb(1.0, 0.95, 0.85),
550 1.3,
551 )),
552 Self::ImageDawn => Some((Vec3::new(-1.0, -0.2, 0.1), Color::rgb(1.0, 0.75, 0.5), 1.1)),
553 Self::Sinister => Some((Vec3::new(0.4, -0.5, -0.7), Color::rgb(0.4, 0.5, 0.75), 0.1)),
554 Self::LightBlueStars | Self::BlueStars => None,
555 Self::Default => Some((Vec3::new(-0.4, -1.0, -0.6), Color::WHITE, 1.0)),
556 }
557 }
558
559 fn ground(self) -> Option<Color> {
566 match self {
567 Self::Clear => Some(Color::rgb(0.501, 0.517, 0.449)),
568 Self::Classic => Some(Color::rgb(0.420, 0.405, 0.379)),
569 Self::ImageDawn => Some(Color::rgb(0.073, 0.053, 0.032)),
570 Self::Sinister => Some(Color::rgb(0.012, 0.014, 0.020)),
571 Self::Dawn
572 | Self::Noon
573 | Self::Dusk
574 | Self::Night
575 | Self::LightBlueStars
576 | Self::BlueStars
577 | Self::Default => None,
578 }
579 }
580}
581
582impl Catalog for Sky {
583 fn catalog() -> Vec<Self> {
584 Self::ALL.to_vec()
585 }
586}
587
588impl Skyboxes for Sky {
589 fn build(&self, assets: &Assets) -> SkyboxData {
590 let sky = match self {
591 Self::Dawn => SkyboxData::gradient(
592 Color::rgb(0.55, 0.55, 0.75),
593 Color::rgb(0.95, 0.6, 0.35),
594 Color::rgb(0.12, 0.08, 0.06),
595 ),
596 Self::Noon => SkyboxData::gradient(
597 Color::rgb(0.2, 0.45, 0.85),
598 Color::rgb(0.75, 0.82, 0.9),
599 Color::rgb(0.3, 0.3, 0.28),
600 ),
601 Self::Dusk => SkyboxData::gradient(
602 Color::rgb(0.18, 0.1, 0.3),
603 Color::rgb(0.85, 0.35, 0.2),
604 Color::rgb(0.03, 0.02, 0.03),
605 ),
606 Self::Night => SkyboxData::gradient(
607 Color::rgb(0.02, 0.02, 0.06),
608 Color::rgb(0.05, 0.05, 0.1),
609 Color::rgb(0.0, 0.0, 0.0),
610 ),
611 Self::Clear => assets.skybox("sky-clear"),
612 Self::Classic => assets.skybox("sky-classic"),
613 Self::ImageDawn => assets.skybox("sky-dawn"),
614 Self::Sinister => assets.skybox("sky-sinister"),
615 Self::LightBlueStars => assets.skybox("sky-stars-lightblue"),
616 Self::BlueStars => assets.skybox("sky-stars-blue"),
617 Self::Default => SkyboxData::gradient(DEFAULT_SKY, DEFAULT_SKY, DEFAULT_SKY),
618 };
619 let sky = match self.ground() {
620 Some(ground) => sky.with_ground(ground),
621 None => sky,
622 };
623
624 sky.lit_by(self.light())
625 }
626}
627
628fn scaled(color: Color, strength: f32) -> Color {
630 Color::rgb(
631 color.red * strength,
632 color.green * strength,
633 color.blue * strength,
634 )
635}
636
637#[derive(Clone, Copy)]
640struct Glow {
641 color: Color,
642 strength: f32,
643 shadow: bool,
644}
645
646impl Glow {
647 fn scaled(self) -> Color {
649 scaled(self.color, self.strength)
650 }
651}
652
653#[derive(InputButtonAction, Clone, Copy, PartialEq)]
658enum Move {
659 Forward,
660 Back,
661 Left,
662 Right,
663 Up,
664 Down,
665 Look,
666}
667
668impl InputButtonAction for Move {
669 fn bindings(&self) -> Vec<ButtonBinding> {
670 match self {
671 Self::Forward => vec![Key::W.into()],
672 Self::Back => vec![Key::S.into()],
673 Self::Left => vec![Key::A.into()],
674 Self::Right => vec![Key::D.into()],
675 Self::Up => vec![Key::Space.into()],
676 Self::Down => vec![Key::LeftShift.into()],
677 Self::Look => vec![MouseButton::Right.into()],
678 }
679 }
680}
681
682#[derive(InputAxis2Action, Clone, Copy, PartialEq)]
684enum Turn {
685 Look,
686}
687
688impl InputAxis2Action for Turn {
689 fn bindings(&self) -> Vec<Axis2Binding> {
690 match self {
691 Self::Look => vec![Axis2Binding::pointer().scale(LOOK_SENSITIVITY)],
692 }
693 }
694}
695
696#[derive(InputAxisAction, Clone, Copy, PartialEq)]
698enum Speed {
699 Wheel,
700}
701
702impl InputAxisAction for Speed {
703 fn bindings(&self) -> Vec<AxisBinding> {
704 match self {
705 Self::Wheel => vec![AxisBinding::from(WheelDelta::Up).scale(4.0)],
706 }
707 }
708}
709
710struct Controls;
711
712impl InputActions for Controls {
713 type Button = Move;
714 type Axis = Speed;
715 type Axis2 = Turn;
716}
717
718struct Playground {
719 eye: Vec3,
720 yaw: f32,
721 pitch: f32,
722 speed_scale: f32,
723
724 sky: Sky,
725 sun_shadow: bool,
726
727 lamp: Glow,
728 spotlight: Glow,
729
730 front_tint: Color,
731 front_roughness: f32,
732 front_metallic: f32,
733 shading_map_on: bool,
734 relief_map_on: bool,
735 emissive_map_on: bool,
736
737 exposure: f32,
738 bloom: f32,
739}
740
741impl Playground {
742 fn init(ctx: &mut InitContext<'_, Self>) -> Result<Self, Error> {
743 let _ = ctx;
744 Ok(Self {
745 eye: START_EYE,
746 yaw: START_YAW,
747 pitch: START_PITCH,
748 speed_scale: 1.0,
749
750 sky: Sky::Default,
751 sun_shadow: true,
752
753 lamp: Glow {
754 color: Color::rgb(0.9, 0.55, 0.3),
755 strength: 3.0,
756 shadow: false,
757 },
758 spotlight: Glow {
759 color: Color::rgb(0.4, 0.6, 1.0),
760 strength: 6.0,
761 shadow: true,
762 },
763
764 front_tint: Color::rgb(0.7, 0.25, 0.2),
765 front_roughness: 0.4,
766 front_metallic: 0.0,
767 shading_map_on: true,
768 relief_map_on: true,
769 emissive_map_on: true,
770
771 exposure: START_EXPOSURE,
772 bloom: START_BLOOM,
773 })
774 }
775
776 fn forward(&self) -> Vec3 {
779 Vec3::new(
780 -self.pitch.cos() * self.yaw.sin(),
781 self.pitch.sin(),
782 -self.pitch.cos() * self.yaw.cos(),
783 )
784 }
785
786 fn camera(&self) -> Camera {
788 Camera::new(
789 View::look_at(self.eye, self.eye + self.forward()),
790 Projection::perspective(CAMERA_FOV),
791 )
792 }
793
794 fn fly_camera(&mut self, ctx: &mut FrameContext<'_, Self>) {
803 if !ctx.ui_wants_pointer() && ctx.down(Move::Look) {
804 let look = ctx.axis2(Turn::Look);
805 self.yaw -= look.x;
806 self.pitch = (self.pitch + look.y).clamp(-PITCH_LIMIT, PITCH_LIMIT);
807 }
808
809 let wheel = ctx.axis(Speed::Wheel);
810 if !ctx.ui_wants_pointer() && wheel != 0.0 {
811 self.speed_scale =
812 (self.speed_scale * SPEED_STEP.powf(wheel)).clamp(MIN_SPEED_SCALE, MAX_SPEED_SCALE);
813 }
814
815 let forward = self.forward();
816 let right = Vec3::new(self.yaw.cos(), 0.0, -self.yaw.sin());
817 let mut move_by = Vec3::ZERO;
818 if ctx.down(Move::Forward) {
819 move_by += forward;
820 }
821 if ctx.down(Move::Back) {
822 move_by -= forward;
823 }
824 if ctx.down(Move::Right) {
825 move_by += right;
826 }
827 if ctx.down(Move::Left) {
828 move_by -= right;
829 }
830 if ctx.down(Move::Up) {
831 move_by += Vec3::Y;
832 }
833 if ctx.down(Move::Down) {
834 move_by -= Vec3::Y;
835 }
836 if move_by.length_squared() > 1.0 {
837 move_by = move_by.normalize();
838 }
839
840 self.eye += move_by * MOVE_SPEED * self.speed_scale * ctx.dt().as_secs_f32();
841 self.eye.y = self.eye.y.max(MIN_EYE_HEIGHT);
842 }
843
844 fn front_material(&self) -> Material {
848 Material::lit(self.front_tint)
849 .roughness(self.front_roughness)
850 .metallic(self.front_metallic)
851 }
852
853 fn draw_scene(&self, ctx: &mut FrameContext<'_, Self>) {
856 ctx.draw(
857 Plane
858 .at(Transform::from_scale(Vec3::new(
859 GROUND_SIZE,
860 1.0,
861 GROUND_SIZE,
862 )))
863 .material(Material::lit(GROUND_COLOR).roughness(0.9)),
864 );
865
866 Self::draw_pair(
867 ctx,
868 SHADING_Z,
869 SPHERE_RADIUS,
870 ShadingPlain.at(Vec3::ZERO).into_set(),
871 ShadingMapped.at(Vec3::ZERO).into_set(),
872 self.shading_map_on,
873 );
874 Self::draw_pair(
875 ctx,
876 RELIEF_Z,
877 SPHERE_RADIUS,
878 ReliefPlain.at(Vec3::ZERO).into_set(),
879 ReliefMapped.at(Vec3::ZERO).into_set(),
880 self.relief_map_on,
881 );
882 Self::draw_pair(
883 ctx,
884 EMISSIVE_Z,
885 CUBE_SIZE / 2.0,
886 EmissivePlain.at(Vec3::ZERO).into_set(),
887 EmissiveMapped.at(Vec3::ZERO).into_set(),
888 self.emissive_map_on,
889 );
890
891 ctx.draw(
892 Front
893 .at(Transform::from_scale_rotation_translation(
894 Vec3::splat(FRONT_SCALE),
895 Quat::IDENTITY,
896 FRONT_POSITION,
897 ))
898 .material(self.front_material()),
899 );
900
901 self.draw_reflect_row(ctx);
902 self.draw_outpost(ctx);
903 }
904
905 fn draw_pair(
910 ctx: &mut FrameContext<'_, Self>,
911 z: f32,
912 height: f32,
913 plain: Instance<Shape, Looks>,
914 mapped: Instance<Shape, Looks>,
915 mapped_on: bool,
916 ) {
917 ctx.draw(plain.clone().at(Vec3::new(-PAIR_HALF_SPACING, height, z)));
918 let right = if mapped_on { mapped } else { plain };
919 ctx.draw(right.at(Vec3::new(PAIR_HALF_SPACING, height, z)));
920 }
921
922 fn draw_reflect_row(&self, ctx: &mut FrameContext<'_, Self>) {
926 let start = -REFLECT_ROW_SPACING * (REFLECT_ROW_COUNT as f32 - 1.0) / 2.0;
927 for index in 0..REFLECT_ROW_COUNT {
928 let x = start + index as f32 * REFLECT_ROW_SPACING;
929 let roughness = index as f32 / (REFLECT_ROW_COUNT as f32 - 1.0);
930 ctx.draw(
931 Sphere {
932 subdivisions: SPHERE_SUBDIVISIONS,
933 }
934 .at(Transform::from_scale_rotation_translation(
935 Vec3::splat(REFLECT_ROW_RADIUS * 2.0),
936 Quat::IDENTITY,
937 Vec3::new(x, REFLECT_ROW_RADIUS, REFLECT_ROW_Z),
938 ))
939 .material(
940 Material::lit(Color::WHITE)
941 .roughness(roughness)
942 .metallic(1.0),
943 ),
944 );
945 }
946 }
947
948 fn draw_outpost(&self, ctx: &mut FrameContext<'_, Self>) {
952 let clock = ctx.elapsed().as_secs_f32();
953
954 for &(position, scale) in &PILLARS {
955 ctx.draw(
956 Cube.at(Transform::from_scale_rotation_translation(
957 scale,
958 Quat::IDENTITY,
959 OUTPOST + position,
960 ))
961 .material(Material::lit(Color::rgb(0.55, 0.5, 0.45))),
962 );
963 }
964
965 ctx.draw(
966 Cube.at(Transform::from_scale_rotation_translation(
967 POLE_SCALE,
968 Quat::IDENTITY,
969 OUTPOST + POLE_POSITION,
970 ))
971 .material(Material::lit(Color::rgb(0.3, 0.24, 0.18))),
972 );
973
974 ctx.set_surface_style(Banner { time: clock });
975 ctx.draw(
976 BannerCloth
977 .at(Transform::from_translation(OUTPOST + BANNER_MOUNT))
978 .material(Material::lit(Color::rgb(0.75, 0.12, 0.12)))
979 .surface_style::<Banner>(),
980 );
981
982 ctx.set_surface_style(Field {
983 tint: Color::rgb(0.25, 0.75, 1.0),
984 time: clock,
985 });
986 ctx.draw(
987 Sphere { subdivisions: 2 }
988 .at(Transform::from_scale_rotation_translation(
989 Vec3::splat(FIELD_ORB_SCALE),
990 Quat::IDENTITY,
991 OUTPOST + FIELD_ORB_POSITION,
992 ))
993 .material(Material::color(Color::BLACK))
994 .surface_style::<Field>(),
995 );
996 }
997
998 fn lights(&self) -> Vec<Light> {
1003 let lamp = Light::point(LAMP_POSITION, self.lamp.scaled(), LAMP_RANGE);
1004 let spotlight = Light::spot(Spot {
1005 position: SPOT_POSITION,
1006 direction: SPOT_DIRECTION,
1007 color: self.spotlight.scaled(),
1008 range: SPOT_RANGE,
1009 angle: SPOT_ANGLE,
1010 });
1011
1012 let mut lights = vec![
1013 if self.lamp.shadow {
1014 lamp.shadow()
1015 } else {
1016 lamp
1017 },
1018 if self.spotlight.shadow {
1019 spotlight.shadow()
1020 } else {
1021 spotlight
1022 },
1023 ];
1024
1025 if let Some((direction, color, strength)) = self.sky.sun() {
1026 let sun = Light::directional(direction, scaled(color, strength));
1027 lights.push(if self.sun_shadow { sun.shadow() } else { sun });
1028 }
1029
1030 lights
1031 }
1032
1033 fn controls(&mut self, ctx: &mut FrameContext<'_, Self>) {
1038 let tonemap = ctx.config().tonemap();
1039 let antialiasing = ctx.config().antialiasing();
1040 let shadow_resolution = ctx.config().shadow_resolution();
1041
1042 ctx.ui(|ui| {
1043 egui::Panel::right("controls")
1044 .resizable(false)
1045 .default_size(300.0)
1046 .show(ui, |ui| {
1047 egui::ScrollArea::vertical().show(ui, |ui| {
1048 ui.label(
1049 "right mouse button to look, W/A/S/D to move, \
1050 space/shift up and down, wheel to scale speed",
1051 );
1052 ui.separator();
1053 self.lighting_controls(ui);
1054 ui.separator();
1055 self.light_controls(ui);
1056 ui.separator();
1057 self.material_controls(ui);
1058 ui.separator();
1059 ui.label(format!(
1060 "tone map {tonemap:?} \u{b7} antialiasing {antialiasing} \u{b7} \
1061 shadow {shadow_resolution}px: set at startup, not live"
1062 ));
1063 ui.add(egui::Slider::new(&mut self.exposure, 0.1..=3.0).text("exposure"));
1064 ui.add(egui::Slider::new(&mut self.bloom, 0.0..=1.0).text("bloom"));
1065 });
1066 });
1067 });
1068 }
1069
1070 fn lighting_controls(&mut self, ui: &mut egui::Ui) {
1071 ui.heading("lighting");
1072 for choice in Sky::ALL {
1073 ui.radio_value(&mut self.sky, choice, choice.name());
1074 }
1075 ui.label(format!("sky light {:.2}: set at startup", self.sky.light()));
1076
1077 match self.sky.sun() {
1078 Some((_, color, strength)) => {
1079 color_swatch(ui, "sun", color);
1080 ui.label(format!("sun strength {strength:.2}: set by the choice"));
1081 ui.checkbox(&mut self.sun_shadow, "sun shadow");
1082 }
1083 None => {
1084 ui.label("no sun: set by the choice");
1085 }
1086 }
1087 }
1088
1089 fn light_controls(&mut self, ui: &mut egui::Ui) {
1090 ui.heading("lights");
1091 glow_controls(ui, "lamp", &mut self.lamp);
1092 glow_controls(ui, "spotlight", &mut self.spotlight);
1093 }
1094
1095 fn material_controls(&mut self, ui: &mut egui::Ui) {
1096 ui.heading("material");
1097 color_row(ui, "tint", &mut self.front_tint);
1098 ui.add(egui::Slider::new(&mut self.front_roughness, 0.0..=1.0).text("roughness"));
1099 ui.add(egui::Slider::new(&mut self.front_metallic, 0.0..=1.0).text("metallic"));
1100 ui.checkbox(
1101 &mut self.shading_map_on,
1102 "shading map: occlusion \u{b7} roughness \u{b7} metallic",
1103 );
1104 ui.checkbox(&mut self.relief_map_on, "relief map: bump normals");
1105 ui.checkbox(&mut self.emissive_map_on, "emissive map: per-texel glow");
1106 }
1107}
1108
1109fn glow_controls(ui: &mut egui::Ui, label: &str, glow: &mut Glow) {
1111 ui.label(label);
1112 color_row(ui, "color", &mut glow.color);
1113 ui.add(egui::Slider::new(&mut glow.strength, 0.0..=8.0).text("strength"));
1114 ui.checkbox(&mut glow.shadow, "shadow");
1115}
1116
1117fn color_row(ui: &mut egui::Ui, label: &str, color: &mut Color) {
1120 let mut rgb = [color.red, color.green, color.blue];
1121 ui.horizontal(|ui| {
1122 ui.label(label);
1123 if ui.color_edit_button_rgb(&mut rgb).changed() {
1124 *color = Color::rgb(rgb[0], rgb[1], rgb[2]);
1125 }
1126 });
1127}
1128
1129fn color_swatch(ui: &mut egui::Ui, label: &str, color: Color) {
1132 let mut rgb = [color.red, color.green, color.blue];
1133 ui.horizontal(|ui| {
1134 ui.label(label);
1135 ui.add_enabled_ui(false, |ui| {
1136 ui.color_edit_button_rgb(&mut rgb);
1137 });
1138 });
1139}
1140
1141impl Game for Playground {
1142 type Meshes = Shape;
1143 type Sounds = NoSounds;
1144 type InputActions = Controls;
1145 type Skyboxes = Sky;
1146 type SurfaceStyles = Looks;
1147 type PostEffects = ();
1148
1149 fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
1150
1151 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
1152 self.fly_camera(ctx);
1153 ctx.set_camera(self.camera());
1154 ctx.set_skybox(self.sky);
1155 for light in self.lights() {
1156 ctx.light(light);
1157 }
1158 ctx.set_exposure(self.exposure);
1159 ctx.set_bloom(self.bloom);
1160
1161 self.draw_scene(ctx);
1162 self.controls(ctx);
1163 }
1164}
1165
1166fn main() {
1167 let config = Config::new("Mirage: material playground")
1168 .with_size(1280, 720)
1169 .with_assets([
1170 "examples/assets/sky-clear.png",
1171 "examples/assets/sky-classic.png",
1172 "examples/assets/sky-dawn.png",
1173 "examples/assets/sky-sinister.png",
1174 "examples/assets/sky-stars-lightblue.png",
1175 "examples/assets/sky-stars-blue.png",
1176 ]);
1177
1178 run(config, Playground::init);
1179}