pub struct Transform(/* private fields */);Expand description
A draw’s position, turn, and size.
Implementations§
Source§impl Transform
impl Transform
Sourcepub fn from_translation(translation: Vec3) -> Self
pub fn from_translation(translation: Vec3) -> Self
At translation, in meters.
Examples found in repository?
examples/material-playground.rs (line 977)
951 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 }More examples
examples/breakout-game.rs (lines 1029-1033)
995 fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996 if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997 self.paused = !self.paused;
998 }
999
1000 ctx.set_volume(self.master_volume);
1001 self.sustain_music(ctx);
1002
1003 ctx.set_camera(Self::camera());
1004
1005 let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006 ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008 let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009 ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011 // The tick moves nothing behind a menu, so a frame there draws the last
1012 // step whole rather than interpolating from the one before.
1013 let alpha = match self.phase {
1014 Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015 _ => 1.0,
1016 };
1017 let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018 let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020 ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022 self.draw_court(ctx);
1023 self.draw_bricks(ctx);
1024 self.draw_sparks(ctx);
1025 self.draw_lives(ctx);
1026
1027 ctx.draw(
1028 Paddle
1029 .at(Transform::from_translation(Vec3::new(
1030 paddle_x,
1031 PADDLE_HALF_HEIGHT,
1032 PADDLE_Z,
1033 )))
1034 .material_of(PaddlePart::Face, self.paddle_face_material()),
1035 );
1036
1037 self.draw_trail(ctx, alpha);
1038 ctx.draw(
1039 Sphere { subdivisions: 2 }
1040 .at(Transform::from_scale_rotation_translation(
1041 Vec3::splat(BALL_RADIUS * 2.0),
1042 Quat::IDENTITY,
1043 ball_pos,
1044 ))
1045 .material(
1046 Material::color(BALL_GLOW)
1047 .emissive(BALL_EMISSIVE)
1048 .additive(),
1049 ),
1050 );
1051
1052 self.overlay(ctx);
1053 }Sourcepub fn from_rotation(rotation: Quat) -> Self
pub fn from_rotation(rotation: Quat) -> Self
Turned about the origin.
Sourcepub fn from_scale(scale: Vec3) -> Self
pub fn from_scale(scale: Vec3) -> Self
Sized about the origin, per axis.
Examples found in repository?
More examples
examples/post-effects.rs (lines 95-99)
90 fn draw_scene(&self, ctx: &mut FrameContext<'_, Self>) {
91 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
92
93 ctx.draw(
94 Plane
95 .at(Transform::from_scale(Vec3::new(
96 GROUND_SIZE,
97 1.0,
98 GROUND_SIZE,
99 )))
100 .material(Material::lit(GROUND_COLOR)),
101 );
102 ctx.draw(
103 Cube.at(Transform::from_scale_rotation_translation(
104 Vec3::splat(GLOW_SIZE),
105 Quat::IDENTITY,
106 GLOW_POSITION,
107 ))
108 .material(Material::color(Color::BLACK).emissive(GLOW_COLOR)),
109 );
110 for position in SPHERE_POSITIONS {
111 ctx.draw(
112 Sphere {
113 subdivisions: SPHERE_SUBDIVISIONS,
114 }
115 .at(position)
116 .material(Material::lit(SPHERE_COLOR)),
117 );
118 }
119 }examples/ui-fonts.rs (lines 826-830)
816 fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
817 self.steer(ctx);
818
819 let camera = self.orbit.camera();
820 ctx.set_camera(camera);
821 ctx.set_skybox(Sky::Dusk);
822 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
823
824 ctx.draw(
825 Plane
826 .at(Transform::from_scale(Vec3::new(
827 PLATFORM_SIZE,
828 1.0,
829 PLATFORM_SIZE,
830 )))
831 .material(Material::lit(PLATFORM_COLOR)),
832 );
833 for station in StationKind::ALL {
834 self.draw_station(ctx, station);
835 }
836
837 let hovered = Self::hovered(ctx);
838 if !self.sheet_open {
839 if let Some(station) = hovered {
840 ctx.set_cursor(Cursor::Pointer);
841 self.draw_bracket(ctx, camera, station);
842 }
843 self.draw_prompts(ctx, camera, hovered);
844 }
845 if self.dialogue.is_some() {
846 self.draw_dialogue(ctx);
847 }
848 if self.sheet_open {
849 ctx.ui(sheet);
850 }
851 self.panel(ctx);
852 }examples/breakout-game.rs (lines 607-611)
604 fn draw_court(&self, ctx: &mut FrameContext<'_, Breakout>) {
605 ctx.draw(
606 Plane
607 .at(Transform::from_scale(Vec3::new(
608 COURT_HALF_WIDTH * 2.0,
609 1.0,
610 COURT_HALF_DEPTH * 2.0,
611 )))
612 .material(Material::lit(FLOOR_COLOR)),
613 );
614
615 let side_half = Vec3::new(WALL_THICKNESS * 0.5, WALL_HEIGHT * 0.5, COURT_HALF_DEPTH);
616 for side in [-1.0, 1.0] {
617 let x = side * (COURT_HALF_WIDTH - WALL_THICKNESS * 0.5);
618 ctx.draw(
619 Cube.at(Transform::from_scale_rotation_translation(
620 side_half * 2.0,
621 Quat::IDENTITY,
622 Vec3::new(x, side_half.y, 0.0),
623 ))
624 .material(Material::lit(WALL_COLOR)),
625 );
626 }
627
628 let top_half = Vec3::new(COURT_HALF_WIDTH, WALL_HEIGHT * 0.5, WALL_THICKNESS * 0.5);
629 ctx.draw(
630 Cube.at(Transform::from_scale_rotation_translation(
631 top_half * 2.0,
632 Quat::IDENTITY,
633 Vec3::new(0.0, top_half.y, -COURT_HALF_DEPTH + WALL_THICKNESS * 0.5),
634 ))
635 .material(Material::lit(WALL_COLOR)),
636 );
637 }examples/sound-lab.rs (lines 513-517)
510 fn draw_room(&self, ctx: &mut FrameContext<'_, SoundCheck>) {
511 ctx.draw(
512 Plane
513 .at(Transform::from_scale(Vec3::new(
514 ROOM_HALF * 2.0,
515 1.0,
516 ROOM_HALF * 2.0,
517 )))
518 .material(Material::lit(FLOOR_COLOR)),
519 );
520
521 let side_half = Vec3::new(WALL_THICKNESS * 0.5, WALL_HEIGHT * 0.5, ROOM_HALF);
522 for side in [-1.0, 1.0] {
523 let x = side * (ROOM_HALF - WALL_THICKNESS * 0.5);
524 ctx.draw(
525 Cube.at(Transform::from_scale_rotation_translation(
526 side_half * 2.0,
527 Quat::IDENTITY,
528 Vec3::new(x, side_half.y, 0.0),
529 ))
530 .material(Material::lit(WALL_COLOR)),
531 );
532 }
533 let end_half = Vec3::new(ROOM_HALF, WALL_HEIGHT * 0.5, WALL_THICKNESS * 0.5);
534 for side in [-1.0, 1.0] {
535 let z = side * (ROOM_HALF - WALL_THICKNESS * 0.5);
536 ctx.draw(
537 Cube.at(Transform::from_scale_rotation_translation(
538 end_half * 2.0,
539 Quat::IDENTITY,
540 Vec3::new(0.0, end_half.y, z),
541 ))
542 .material(Material::lit(WALL_COLOR)),
543 );
544 }
545 }Additional examples can be found in:
Sourcepub fn from_rotation_translation(rotation: Quat, translation: Vec3) -> Self
pub fn from_rotation_translation(rotation: Quat, translation: Vec3) -> Self
Turned about the origin, then moved.
Examples found in repository?
examples/sprite-adventure.rs (line 1581)
1572 fn draw_door(&self, ctx: &mut FrameContext<'_, Keep>, ghost: f32) {
1573 let fade = ghost_alpha(ghost);
1574 let swung = if self.door_opening {
1575 Quat::from_rotation_y(core::f32::consts::FRAC_PI_2)
1576 } else {
1577 Quat::IDENTITY
1578 };
1579
1580 ctx.draw(
1581 Door.at(Transform::from_rotation_translation(swung, DOOR_HINGE))
1582 .material(Material::shaded(DOOR_COLOR, DOOR_LITNESS))
1583 .faded(fade),
1584 );
1585 }More examples
examples/animation.rs (lines 1016-1019)
920 fn frame(&mut self, ctx: &mut FrameContext<'_, Scene>) {
921 self.steer_camera(ctx);
922
923 let alpha = ctx.alpha();
924 let elf_pos = self.elf_prev.lerp(self.elf_pos, alpha);
925 let elf_height = self.elf_height_prev + (self.elf_height - self.elf_height_prev) * alpha;
926 let (butterfly_pos, butterfly_yaw) = butterfly_pose(ctx.elapsed().as_secs_f32());
927
928 let camera = orbit_camera(elf_pos, self.camera_yaw, self.camera_pitch);
929 ctx.set_camera(camera);
930 ctx.set_cursor(if self.holding {
931 Cursor::Held
932 } else {
933 Cursor::Arrow
934 });
935 ctx.set_skybox(Sky::Day);
936 ctx.set_exposure(3.0);
937 ctx.set_bloom(0.2);
938 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
939 ctx.light(
940 Light::point(
941 LAMP_POST_POSITION + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP * 0.5),
942 LAMP_LIGHT_COLOR,
943 LAMP_LIGHT_RANGE,
944 )
945 .shadow(),
946 );
947 ctx.light(
948 Light::spot(Spot {
949 position: SPOT_POSITION,
950 direction: SPOT_DIRECTION,
951 color: SPOT_COLOR,
952 range: SPOT_RANGE,
953 angle: SPOT_ANGLE,
954 })
955 .shadow(),
956 );
957 ctx.light(
958 Light::point(butterfly_pos, BUTTERFLY_LIGHT_COLOR, BUTTERFLY_LIGHT_RANGE).shadow(),
959 );
960
961 ctx.draw(
962 Plane
963 .at(Transform::from_scale(Vec3::new(
964 GROUND_SIZE,
965 1.0,
966 GROUND_SIZE,
967 )))
968 .material(Material::lit(GROUND_COLOR)),
969 );
970 for patch in HURT_PATCHES {
971 ctx.draw(
972 Plane
973 .at(Transform::from_scale_rotation_translation(
974 Vec3::splat(HURT_RADIUS * 2.0),
975 Quat::IDENTITY,
976 patch,
977 ))
978 .material(Material::lit(HURT_COLOR)),
979 );
980 }
981 ctx.draw(
982 Cube.at(Transform::from_scale_rotation_translation(
983 Vec3::new(SEAT_FOOTPRINT, SEAT_HEIGHT, SEAT_FOOTPRINT),
984 Quat::IDENTITY,
985 SEAT_POSITION + Vec3::Y * SEAT_HEIGHT * 0.5,
986 ))
987 .material(Material::lit(SEAT_COLOR)),
988 );
989 ctx.draw(
990 Cube.at(Transform::from_scale_rotation_translation(
991 Vec3::new(LAMP_POST_THICKNESS, LAMP_POST_HEIGHT, LAMP_POST_THICKNESS),
992 Quat::IDENTITY,
993 LAMP_POST_POSITION + Vec3::Y * LAMP_POST_HEIGHT * 0.5,
994 ))
995 .material(Material::lit(LAMP_POST_COLOR)),
996 );
997 ctx.draw(
998 Cube.at(Transform::from_scale_rotation_translation(
999 Vec3::splat(LAMP_HEAD_SIZE),
1000 Quat::IDENTITY,
1001 LAMP_POST_POSITION
1002 + Vec3::Y * (LAMP_POST_HEIGHT + LAMP_HEAD_GAP + LAMP_HEAD_SIZE * 0.5),
1003 ))
1004 .material(Material::color(Color::BLACK).emissive(LAMP_LIGHT_COLOR)),
1005 );
1006 ctx.draw(
1007 Cube.at(Transform::from_scale_rotation_translation(
1008 Vec3::splat(SPOT_FIXTURE_SIZE),
1009 Quat::IDENTITY,
1010 SPOT_POSITION + Vec3::Y * SPOT_FIXTURE_SIZE * 0.5,
1011 ))
1012 .material(Material::lit(SPOT_FIXTURE_COLOR)),
1013 );
1014
1015 ctx.draw(
1016 Elf.at(Transform::from_rotation_translation(
1017 Quat::from_rotation_y(self.elf_yaw),
1018 elf_pos + Vec3::Y * elf_height,
1019 ))
1020 .posed(&self.elf_animator),
1021 );
1022 ctx.draw(
1023 Elf.at(Transform::from_rotation_translation(
1024 Quat::from_rotation_y(core::f32::consts::PI),
1025 SCRUBBED_ELF_POSITION,
1026 ))
1027 .posed(&self.scrubbed_animator),
1028 );
1029 ctx.draw(
1030 Butterfly
1031 .at(Transform::from_rotation_translation(
1032 Quat::from_rotation_y(butterfly_yaw),
1033 butterfly_pos,
1034 ))
1035 .posed(&self.butterfly_animator)
1036 .material(Material::lit(Color::WHITE).emissive(BUTTERFLY_EMISSIVE)),
1037 );
1038
1039 self.draw_prompts(ctx, camera);
1040 self.panel(ctx);
1041 }Sourcepub fn from_scale_rotation_translation(
scale: Vec3,
rotation: Quat,
translation: Vec3,
) -> Self
pub fn from_scale_rotation_translation( scale: Vec3, rotation: Quat, translation: Vec3, ) -> Self
Sized, then turned, then moved.
Examples found in repository?
examples/stress-preview.rs (lines 411-415)
403 fn draw_field(&self, ctx: &mut FrameContext<'_, Self>, elapsed: f32) {
404 for entry in &self.field {
405 let yaw = if self.settings.moving && entry.moving {
406 entry.phase + elapsed * MOVING_SPEED
407 } else {
408 entry.phase
409 };
410 ctx.draw(
411 Rock { seed: entry.seed }.at(Transform::from_scale_rotation_translation(
412 Vec3::ONE,
413 Quat::from_rotation_y(yaw),
414 entry.position,
415 )),
416 );
417 }
418 }More examples
examples/flock-parallelism.rs (lines 679-683)
674 fn draw_butterflies(&self, ctx: &mut FrameContext<'_, Self>) {
675 for (position, velocity, kind) in self.butterflies.each() {
676 let rotation = Quat::from_rotation_arc(Vec3::Z, Vec3::from(velocity).normalize());
677 ctx.draw(
678 Butterfly
679 .at(Transform::from_scale_rotation_translation(
680 Vec3::splat(BUTTERFLY_SCALE),
681 rotation,
682 Vec3::from(position),
683 ))
684 .posed(&self.flaps[usize::from(kind.flap)])
685 .material(Material::lit(TINTS[usize::from(kind.tint)])),
686 );
687 }
688 }examples/ui-fonts.rs (lines 672-676)
657 fn draw_station(&self, ctx: &mut FrameContext<'_, Self>, station: StationKind) {
658 let look = station.look();
659 let center = station.center();
660 let front_offset =
661 STATION_SIZE.z * 0.5 - STATION_FRONT_SIZE.z * 0.5 + STATION_FRONT_OUTWARD;
662 let front = center - Vec3::new(0.0, 0.0, front_offset);
663 for (size, position, material) in [
664 (STATION_SIZE, center, Material::lit(look.color)),
665 (
666 STATION_FRONT_SIZE,
667 front,
668 Material::color(Color::BLACK).emissive(look.glow),
669 ),
670 ] {
671 ctx.draw(
672 Cube.at(Transform::from_scale_rotation_translation(
673 size,
674 Quat::IDENTITY,
675 position,
676 ))
677 .material(material),
678 );
679 }
680 }examples/post-effects.rs (lines 103-107)
90 fn draw_scene(&self, ctx: &mut FrameContext<'_, Self>) {
91 ctx.light(Light::directional(SUN_DIRECTION, SUN_COLOR).shadow());
92
93 ctx.draw(
94 Plane
95 .at(Transform::from_scale(Vec3::new(
96 GROUND_SIZE,
97 1.0,
98 GROUND_SIZE,
99 )))
100 .material(Material::lit(GROUND_COLOR)),
101 );
102 ctx.draw(
103 Cube.at(Transform::from_scale_rotation_translation(
104 Vec3::splat(GLOW_SIZE),
105 Quat::IDENTITY,
106 GLOW_POSITION,
107 ))
108 .material(Material::color(Color::BLACK).emissive(GLOW_COLOR)),
109 );
110 for position in SPHERE_POSITIONS {
111 ctx.draw(
112 Sphere {
113 subdivisions: SPHERE_SUBDIVISIONS,
114 }
115 .at(position)
116 .material(Material::lit(SPHERE_COLOR)),
117 );
118 }
119 }examples/breakout-game.rs (lines 619-623)
604 fn draw_court(&self, ctx: &mut FrameContext<'_, Breakout>) {
605 ctx.draw(
606 Plane
607 .at(Transform::from_scale(Vec3::new(
608 COURT_HALF_WIDTH * 2.0,
609 1.0,
610 COURT_HALF_DEPTH * 2.0,
611 )))
612 .material(Material::lit(FLOOR_COLOR)),
613 );
614
615 let side_half = Vec3::new(WALL_THICKNESS * 0.5, WALL_HEIGHT * 0.5, COURT_HALF_DEPTH);
616 for side in [-1.0, 1.0] {
617 let x = side * (COURT_HALF_WIDTH - WALL_THICKNESS * 0.5);
618 ctx.draw(
619 Cube.at(Transform::from_scale_rotation_translation(
620 side_half * 2.0,
621 Quat::IDENTITY,
622 Vec3::new(x, side_half.y, 0.0),
623 ))
624 .material(Material::lit(WALL_COLOR)),
625 );
626 }
627
628 let top_half = Vec3::new(COURT_HALF_WIDTH, WALL_HEIGHT * 0.5, WALL_THICKNESS * 0.5);
629 ctx.draw(
630 Cube.at(Transform::from_scale_rotation_translation(
631 top_half * 2.0,
632 Quat::IDENTITY,
633 Vec3::new(0.0, top_half.y, -COURT_HALF_DEPTH + WALL_THICKNESS * 0.5),
634 ))
635 .material(Material::lit(WALL_COLOR)),
636 );
637 }
638
639 fn draw_bricks(&self, ctx: &mut FrameContext<'_, Breakout>) {
640 let scale = Vec3::new(
641 BRICK_HALF_WIDTH * 2.0,
642 BRICK_HALF_HEIGHT * 2.0,
643 BRICK_HALF_DEPTH * 2.0,
644 );
645 for brick in self.bricks.iter().filter(|brick| brick.hits_remaining > 0) {
646 let health = f32::from(brick.hits_remaining) / f32::from(BRICK_HITS);
647 let color = BRICK_ROW_COLORS[brick.row].dimmed(0.4 + 0.6 * health);
648 ctx.draw(
649 Cube.at(Transform::from_scale_rotation_translation(
650 scale,
651 Quat::IDENTITY,
652 brick.position,
653 ))
654 .material(Material::shaded(color, health)),
655 );
656 }
657 }
658
659 /// Draws the live spark burst: additive, tumbling by roll as they age,
660 /// shrinking and fading out over their lifetime.
661 fn draw_sparks(&self, ctx: &mut FrameContext<'_, Breakout>) {
662 for spark in &self.sparks {
663 let age = (spark.age / SPARK_LIFETIME).clamp(0.0, 1.0);
664 let fade = 1.0 - age;
665 let size = SPARK_SIZE_START.lerp(SPARK_SIZE_END, age);
666 ctx.draw(
667 Quad.at(Transform::from_scale_rotation_translation(
668 Vec3::splat(size),
669 Quat::IDENTITY,
670 spark.position,
671 ))
672 .billboard()
673 .roll(spark.roll + spark.age * SPARK_SPIN_SPEED)
674 .material(
675 Material::color(spark.color.with_alpha(fade))
676 .emissive(spark.color.dimmed(SPARK_EMISSIVE_PEAK))
677 .additive(),
678 ),
679 );
680 }
681 }
682
683 /// Draws the ball's ghost trail, each ghost smaller and more transparent
684 /// than the one ahead of it; each ghost's position interpolates between
685 /// its own last two resolved ticks by the same `alpha` the ball itself
686 /// draws at, and its radius clamps to what the ball's own radius has
687 /// left over its distance from the head, so a ghost still close to the
688 /// ball never draws past its edge.
689 fn draw_trail(&self, ctx: &mut FrameContext<'_, Breakout>, alpha: f32) {
690 let head = self.ball_trail[1].lerp(self.ball_trail[0], alpha);
691 for i in 0..TRAIL_LEN {
692 let position = self.ball_trail[i + 1].lerp(self.ball_trail[i], alpha);
693 let age = (i + 1) as f32 / TRAIL_LEN as f32;
694 let fade = (1.0 - age).max(TRAIL_ALPHA_FLOOR);
695 let radius = (BALL_RADIUS * TRAIL_SCALE_MIN.lerp(TRAIL_SCALE_MAX, fade))
696 .min((BALL_RADIUS - head.distance(position)).max(0.0));
697 let scale = Vec3::splat(radius * 2.0);
698 ctx.draw(
699 Sphere { subdivisions: 2 }
700 .at(Transform::from_scale_rotation_translation(
701 scale,
702 Quat::IDENTITY,
703 position,
704 ))
705 .material(
706 Material::color(BALL_GLOW.with_alpha(fade))
707 .emissive(BALL_EMISSIVE.dimmed(TRAIL_EMISSIVE_PEAK)),
708 ),
709 );
710 }
711 }
712
713 /// Draws one held ball for every life past the one in play, set in a
714 /// row alongside the paddle's own path.
715 fn draw_lives(&self, ctx: &mut FrameContext<'_, Breakout>) {
716 let held_lives = self.lives.saturating_sub(1);
717 for slot in 0..held_lives {
718 let z = PADDLE_Z + (slot + 1) as f32 * LIFE_ROW_SPACING;
719 ctx.draw(
720 Sphere { subdivisions: 2 }
721 .at(Transform::from_scale_rotation_translation(
722 Vec3::splat(BALL_RADIUS * 2.0),
723 Quat::IDENTITY,
724 Vec3::new(LIFE_ROW_X, BALL_RADIUS, z),
725 ))
726 .material(
727 Material::color(BALL_GLOW)
728 .emissive(BALL_EMISSIVE)
729 .additive(),
730 ),
731 );
732 }
733 }
734
735 fn overlay(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
736 let bricks_left = self
737 .bricks
738 .iter()
739 .filter(|brick| brick.hits_remaining > 0)
740 .count();
741 // Read before `ctx.ui` so a rebind changes what the hint reads this
742 // frame too.
743 let move_hint = bindings_text(ctx.bindings(Move::Paddle));
744 let pause_hint = bindings_text(ctx.bindings(Button::Pause));
745 let serve_hint = bindings_text(ctx.bindings(Button::Serve));
746 ctx.ui(|ui| {
747 ui.horizontal(|ui| {
748 ui.label(egui::RichText::new(format!("score {}", self.score)).size(32.0));
749 ui.label(format!("{bricks_left} bricks left"));
750 });
751 ui.label(format!("move: {move_hint} · {pause_hint} to pause"));
752 if self.phase == Phase::Serving {
753 ui.label(format!("{serve_hint} to serve"));
754 }
755 });
756
757 match self.phase {
758 Phase::Serving | Phase::Playing if self.paused => self.menu(ctx, "paused", false),
759 Phase::Won => self.menu(ctx, "you win", true),
760 Phase::Lost => self.menu(ctx, "game over", true),
761 _ => {}
762 }
763 }
764
765 fn menu(&mut self, ctx: &mut FrameContext<'_, Breakout>, title: &str, over: bool) {
766 let mut clicked = false;
767 let mut quit = false;
768
769 // `ctx.ui` cannot borrow `ctx`, so anything the controls list needs is
770 // read first and applied after.
771 let buttons: Vec<(Button, String)> = Button::all()
772 .into_iter()
773 .map(|action| (action, bindings_text(ctx.bindings(action))))
774 .collect();
775 let axes: Vec<(Move, String)> = Move::all()
776 .into_iter()
777 .map(|action| (action, bindings_text(ctx.bindings(action))))
778 .collect();
779 let listening = self.listening;
780 let actuated_button = (!ctx.ui_wants_keyboard())
781 .then(|| ctx.actuated_button())
782 .flatten();
783 let actuated_axis = (!ctx.ui_wants_keyboard())
784 .then(|| ctx.actuated_axis())
785 .flatten();
786 let mut reset = None;
787
788 ctx.ui(|ui| {
789 egui::Window::new(title)
790 .collapsible(false)
791 .resizable(false)
792 .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::ZERO)
793 .show(ui.ctx(), |ui| {
794 if over {
795 ui.label(format!("score {}", self.score));
796 }
797 if !over {
798 ui.add(
799 egui::Slider::new(&mut self.master_volume, 0.0..=1.0).text("volume"),
800 );
801 if ui.button("resume").clicked() {
802 self.paused = false;
803 clicked = true;
804 }
805 ui.separator();
806 ui.heading("controls");
807 for (action, text) in &buttons {
808 controls_row(
809 ui,
810 action.name(),
811 text,
812 listening == Some(Listening::Button(*action)),
813 &mut self.listening,
814 Listening::Button(*action),
815 &mut reset,
816 );
817 }
818 for (action, text) in &axes {
819 controls_row(
820 ui,
821 action.name(),
822 text,
823 listening == Some(Listening::Move(*action)),
824 &mut self.listening,
825 Listening::Move(*action),
826 &mut reset,
827 );
828 }
829 }
830 if ui.button("restart").clicked() {
831 self.restart();
832 clicked = true;
833 }
834 if ui.button("quit").clicked() {
835 quit = true;
836 }
837 });
838 });
839
840 match (self.listening, actuated_button, actuated_axis) {
841 (Some(Listening::Button(action)), Some(binding), _) => {
842 ctx.rebind(action, vec![binding]);
843 self.listening = None;
844 }
845 (Some(Listening::Move(action)), _, Some(binding)) => {
846 ctx.rebind(action, vec![binding]);
847 self.listening = None;
848 }
849 _ => {}
850 }
851 match reset {
852 Some(Listening::Button(action)) => ctx.rebind(action, action.bindings()),
853 Some(Listening::Move(action)) => ctx.rebind(action, action.bindings()),
854 None => {}
855 }
856
857 if clicked {
858 ctx.play(Sound::Click);
859 }
860 if quit {
861 ctx.close();
862 }
863 }
864
865 /// Sustains both tracks every frame, and the gain goes to whichever the
866 /// game calls for: gameplay music while a round is live, serving
867 /// included, and menu music whenever a menu covers it.
868 ///
869 /// Each fades in over [`MUSIC_CROSSFADE`] and slides every later gain
870 /// over it, which is the crossfade itself; the one at no gain costs no
871 /// voice while its playback goes on under the other.
872 fn sustain_music(&self, ctx: &mut FrameContext<'_, Breakout>) {
873 let playing = !self.paused && matches!(self.phase, Phase::Serving | Phase::Playing);
874 let gain = |wanted: bool| match wanted {
875 true => MUSIC_GAIN,
876 false => 0.0,
877 };
878
879 ctx.sustain(
880 Sound::Music
881 .gain(gain(playing))
882 .fade(MUSIC_CROSSFADE)
883 .glide(MUSIC_CROSSFADE)
884 .loop_from(MUSIC_LOOP_FROM),
885 );
886 ctx.sustain(
887 Sound::MenuMusic
888 .gain(gain(!playing))
889 .fade(MUSIC_CROSSFADE)
890 .glide(MUSIC_CROSSFADE)
891 .loop_from(MENU_MUSIC_LOOP_FROM),
892 );
893 }
894}
895
896/// One action's name, its live bindings, a rebind control that starts
897/// listening for a new one, and a reset to its defaults; cancel is a
898/// button rather than Escape, since Escape is itself a binding a listen
899/// could capture.
900fn controls_row(
901 ui: &mut egui::Ui,
902 name: &str,
903 bindings: &str,
904 listening: bool,
905 target: &mut Option<Listening>,
906 action: Listening,
907 reset: &mut Option<Listening>,
908) {
909 ui.horizontal(|ui| {
910 ui.label(format!("{name}: {bindings}"));
911 if listening {
912 ui.label("listening");
913 if ui.button("cancel").clicked() {
914 *target = None;
915 }
916 } else if ui.button("rebind").clicked() {
917 *target = Some(action);
918 }
919 if ui.button("reset").clicked() {
920 *reset = Some(action);
921 }
922 });
923}
924
925/// The controls-menu text for a live binding list: each alternative,
926/// separated, in the order the player can use them.
927fn bindings_text<B: Display>(bindings: Vec<B>) -> String {
928 bindings
929 .iter()
930 .map(ToString::to_string)
931 .collect::<Vec<_>>()
932 .join(", ")
933}
934
935fn spawn_bricks() -> Vec<Brick> {
936 let cell = BRICK_HALF_WIDTH * 2.0 + BRICK_GAP;
937 let row_span = BRICK_HALF_DEPTH * 2.0 + BRICK_ROW_GAP;
938 let grid_width = cell * BRICK_COLUMNS as f32 - BRICK_GAP;
939 let start_x = -grid_width * 0.5 + BRICK_HALF_WIDTH;
940 let start_z = -COURT_HALF_DEPTH + WALL_THICKNESS + BRICK_HALF_DEPTH + 0.6;
941
942 (0..BRICK_ROWS)
943 .flat_map(|row| {
944 (0..BRICK_COLUMNS).map(move |column| Brick {
945 row,
946 position: Vec3::new(
947 start_x + column as f32 * cell,
948 BRICK_HALF_HEIGHT,
949 start_z + row as f32 * row_span,
950 ),
951 hits_remaining: BRICK_HITS,
952 })
953 })
954 .collect()
955}
956
957impl Game for Breakout {
958 type Meshes = Shape;
959 type Sounds = Sound;
960 type InputActions = Controls;
961 type Skyboxes = NoSkyboxes;
962 type SurfaceStyles = NoSurfaceStyles;
963 type PostEffects = NoPostEffects;
964
965 fn tick(&mut self, ctx: &mut TickContext<'_, Breakout>) {
966 if self.paused {
967 return;
968 }
969
970 let dt = ctx.dt().as_secs_f32();
971 self.paddle_flash = (self.paddle_flash - dt).max(0.0);
972 self.brick_flash = (self.brick_flash - dt).max(0.0);
973 self.life_lost_flash = (self.life_lost_flash - dt).max(0.0);
974 self.step_sparks(dt);
975
976 // Decay runs before the end-screen return below, so the last pulse and
977 // burst do not stay on screen.
978 if matches!(self.phase, Phase::Won | Phase::Lost) {
979 return;
980 }
981
982 let axis = if ctx.ui_wants_keyboard() {
983 0.0
984 } else {
985 ctx.axis(Move::Paddle)
986 };
987 self.step_paddle(axis, dt);
988
989 match self.phase {
990 Phase::Serving => self.hold_ball(ctx),
991 _ => self.step_ball(ctx, dt),
992 }
993 }
994
995 fn frame(&mut self, ctx: &mut FrameContext<'_, Breakout>) {
996 if matches!(self.phase, Phase::Serving | Phase::Playing) && ctx.pressed(Button::Pause) {
997 self.paused = !self.paused;
998 }
999
1000 ctx.set_volume(self.master_volume);
1001 self.sustain_music(ctx);
1002
1003 ctx.set_camera(Self::camera());
1004
1005 let brick_pulse = (self.brick_flash / BRICK_FLASH).clamp(0.0, 1.0);
1006 ctx.set_bloom((BLOOM_BASE + brick_pulse * BLOOM_PULSE_PEAK).clamp(0.0, 1.0));
1007
1008 let life_lost_t = (self.life_lost_flash / LIFE_LOST_FLASH).clamp(0.0, 1.0);
1009 ctx.set_exposure((1.0 - life_lost_t * EXPOSURE_DIP_DEPTH).clamp(0.0, 1.0));
1010
1011 // The tick moves nothing behind a menu, so a frame there draws the last
1012 // step whole rather than interpolating from the one before.
1013 let alpha = match self.phase {
1014 Phase::Serving | Phase::Playing if !self.paused => ctx.alpha(),
1015 _ => 1.0,
1016 };
1017 let paddle_x = self.paddle_prev_x.lerp(self.paddle_x, alpha);
1018 let ball_pos = self.ball_prev.lerp(self.ball_pos, alpha);
1019
1020 ctx.light(Light::point(ball_pos, BALL_GLOW, BALL_LIGHT_RANGE).shadow());
1021
1022 self.draw_court(ctx);
1023 self.draw_bricks(ctx);
1024 self.draw_sparks(ctx);
1025 self.draw_lives(ctx);
1026
1027 ctx.draw(
1028 Paddle
1029 .at(Transform::from_translation(Vec3::new(
1030 paddle_x,
1031 PADDLE_HALF_HEIGHT,
1032 PADDLE_Z,
1033 )))
1034 .material_of(PaddlePart::Face, self.paddle_face_material()),
1035 );
1036
1037 self.draw_trail(ctx, alpha);
1038 ctx.draw(
1039 Sphere { subdivisions: 2 }
1040 .at(Transform::from_scale_rotation_translation(
1041 Vec3::splat(BALL_RADIUS * 2.0),
1042 Quat::IDENTITY,
1043 ball_pos,
1044 ))
1045 .material(
1046 Material::color(BALL_GLOW)
1047 .emissive(BALL_EMISSIVE)
1048 .additive(),
1049 ),
1050 );
1051
1052 self.overlay(ctx);
1053 }examples/isometric-board.rs (lines 512-516)
492 fn draw_board(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
493 let scale = Vec3::new(TILE_SIZE - TILE_GAP, TILE_THICKNESS, TILE_SIZE - TILE_GAP);
494 for col in 0..BOARD_TILES {
495 for row in 0..BOARD_TILES {
496 let tile = (col, row);
497 let center = tile_center(tile) - Vec3::Y * (TILE_THICKNESS * 0.5);
498 let reachable = self.selected && self.reachable(tile);
499 let hovered = self.selected && hover == Hover::Tile(tile);
500 let color = if hovered {
501 if reachable {
502 HOVER_REACHABLE_TILE
503 } else {
504 HOVER_BLOCKED_TILE
505 }
506 } else if (col + row) % 2 == 0 {
507 LIGHT_TILE
508 } else {
509 DARK_TILE
510 };
511 ctx.draw(
512 Cube.at(Transform::from_scale_rotation_translation(
513 scale,
514 Quat::IDENTITY,
515 center,
516 ))
517 .material(Material::lit(color)),
518 );
519 if reachable && !hovered {
520 self.draw_reachable_mark(ctx, tile);
521 }
522 }
523 }
524 }
525
526 /// A mark over a reachable tile, its own tone apart from the
527 /// checker's tone and the hover tone, so the checker still reads
528 /// under it.
529 fn draw_reachable_mark(&self, ctx: &mut FrameContext<'_, Board>, tile: (i32, i32)) {
530 let center = tile_center(tile) + Vec3::Y * REACHABLE_MARK_LIFT;
531 ctx.draw(
532 Plane
533 .at(Transform::from_scale_rotation_translation(
534 Vec3::new(
535 TILE_SIZE * REACHABLE_MARK_SCALE,
536 1.0,
537 TILE_SIZE * REACHABLE_MARK_SCALE,
538 ),
539 Quat::IDENTITY,
540 center,
541 ))
542 .material(Material::color(REACHABLE_MARK)),
543 );
544 }
545
546 /// A mark bright enough to read past the sprite's own tint under the
547 /// selected unit, or a smaller, dim one under the unit whose turn it
548 /// is while nothing is selected — so the current unit reads from the
549 /// ground alone.
550 fn draw_current_mark(&self, ctx: &mut FrameContext<'_, Board>) {
551 let (color, scale) = if self.selected {
552 (CURRENT_MARK, CURRENT_MARK_SCALE)
553 } else {
554 (TURN_MARK, TURN_MARK_SCALE)
555 };
556 let center = tile_center(self.current().tile) + Vec3::Y * REACHABLE_MARK_LIFT;
557 ctx.draw(
558 Plane
559 .at(Transform::from_scale_rotation_translation(
560 Vec3::new(TILE_SIZE * scale, 1.0, TILE_SIZE * scale),
561 Quat::IDENTITY,
562 center,
563 ))
564 .material(Material::color(color)),
565 );
566 }
567
568 fn draw_rocks(&self, ctx: &mut FrameContext<'_, Board>) {
569 for &(x, z, seed, scale) in &ROCKS {
570 let angle = hash_signed(seed, 99) * core::f32::consts::PI;
571 ctx.draw(
572 Rock { seed }
573 .at(Transform::from_scale_rotation_translation(
574 Vec3::splat(scale),
575 Quat::from_rotation_y(angle),
576 Vec3::new(x, 0.5 * scale, z),
577 ))
578 .material(Material::lit(ROCK_COLOR)),
579 );
580 }
581 }
582
583 fn draw_sprite(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
584 let position = self.sprite.previous.lerp(self.sprite.position, ctx.alpha());
585 let current = self.turn == Turn::Sprite;
586 let (tint, glow) = if current && self.selected {
587 (SELECTED_TINT, SELECTED_GLOW)
588 } else if current && hover == Hover::CurrentUnit {
589 (HOVER_TINT, HOVER_GLOW)
590 } else if current {
591 (TURN_TINT, TURN_GLOW)
592 } else {
593 (Color::WHITE, Color::BLACK)
594 };
595 ctx.draw(
596 Sprite
597 .at(Transform::from_scale_rotation_translation(
598 Vec3::new(SPRITE_WIDTH, SPRITE_HEIGHT, 1.0),
599 Quat::IDENTITY,
600 position,
601 ))
602 .upright()
603 .frame(sprite_frame(self.sprite.facing_right))
604 .material(Material::lit(tint).cutout().emissive(glow)),
605 );
606 }
607
608 fn draw_block(&self, ctx: &mut FrameContext<'_, Board>, hover: Hover) {
609 let position = self.block.previous.lerp(self.block.position, ctx.alpha());
610 let current = self.turn == Turn::Block;
611 let (color, glow) = if current && self.selected {
612 (SELECTED_TINT, SELECTED_GLOW)
613 } else if current && hover == Hover::CurrentUnit {
614 (HOVER_TINT, HOVER_GLOW)
615 } else if current {
616 (BLOCK_TURN, TURN_GLOW)
617 } else {
618 (BLOCK_IDLE, Color::BLACK)
619 };
620 ctx.draw(
621 Cube.at(Transform::from_scale_rotation_translation(
622 Vec3::splat(BLOCK_SIZE),
623 Quat::IDENTITY,
624 position,
625 ))
626 .material(Material::lit(color).emissive(glow)),
627 );
628 }
629
630 fn draw_ground(&self, ctx: &mut FrameContext<'_, Board>) {
631 ctx.draw(
632 Plane
633 .at(Transform::from_scale_rotation_translation(
634 Vec3::new(GROUND_HALF * 2.0, 1.0, GROUND_HALF * 2.0),
635 Quat::IDENTITY,
636 Vec3::new(0.0, GROUND_Y, 0.0),
637 ))
638 .material(Material::lit(GROUND_COLOR)),
639 );
640 }Additional examples can be found in:
Trait Implementations§
impl Copy for Transform
impl Pod for Transform
impl StructuralPartialEq for Transform
Auto Trait Implementations§
impl Freeze for Transform
impl RefUnwindSafe for Transform
impl Send for Transform
impl Sync for Transform
impl Unpin for Transform
impl UnsafeUnpin for Transform
impl UnwindSafe for Transform
Blanket Implementations§
impl<T> AnyBitPattern for Twhere
T: Pod,
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CheckedBitPattern for Twhere
T: AnyBitPattern,
impl<T> CheckedBitPattern for Twhere
T: AnyBitPattern,
Source§type Bits = T
type Bits = T
Self must have the same layout as the specified Bits except for
the possible invalid bit patterns being checked during
is_valid_bit_pattern.Source§fn is_valid_bit_pattern(_bits: &T) -> bool
fn is_valid_bit_pattern(_bits: &T) -> bool
If this function returns true, then it must be valid to reinterpret
bits
as &Self.Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<S, T> Duplex<S> for Twhere
T: FromSample<S> + ToSample<S>,
Source§impl<S> FromSample<S> for S
impl<S> FromSample<S> for S
fn from_sample_(s: S) -> S
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
Converts
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
Converts
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more