#[repr(C)]pub struct Vec2 {
pub x: f32,
pub y: f32,
}Expand description
A 2-dimensional vector.
Fields§
§x: f32§y: f32Implementations§
Source§impl Vec2
impl Vec2
Sourcepub const NEG_INFINITY: Vec2
pub const NEG_INFINITY: Vec2
All f32::NEG_INFINITY.
Sourcepub const USES_CORE_SIMD: bool = false
pub const USES_CORE_SIMD: bool = false
Vec2 uses Rust Portable SIMD
Sourcepub const USES_SCALAR_MATH: bool = true
pub const USES_SCALAR_MATH: bool = true
Vec2 uses scalar math
Sourcepub const USES_WASM_SIMD: bool = false
pub const USES_WASM_SIMD: bool = false
Vec2 uses WebAssembly 128-bit SIMD
pub const USES_WASM32_SIMD: bool = false
Renamed to USES_WASM_SIMD
Sourcepub const fn new(x: f32, y: f32) -> Vec2
pub const fn new(x: f32, y: f32) -> Vec2
Creates a new vector.
Examples found in repository?
More examples
557fn push_face(vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>, a: Vec3, b: Vec3, c: Vec3) {
558 let normal = (b - a).cross(c - a).normalize();
559 let uvs = [
560 Vec2::new(0.0, 1.0),
561 Vec2::new(0.5, 0.0),
562 Vec2::new(1.0, 1.0),
563 ];
564 let base = vertices.len() as u32;
565 for (point, uv) in [a, b, c].into_iter().zip(uvs) {
566 vertices.push(Vertex::new(point, normal, uv));
567 }
568 indices.extend([base, base + 1, base + 2]);
569}480 fn push_out(self, point: Vec2, radius: f32) -> Vec2 {
481 let delta = point - self.center;
482 let escape = self.half + Vec2::splat(radius) - delta.abs();
483
484 if escape.min_element() <= 0.0 {
485 point
486 } else if escape.x < escape.y {
487 let x = self.center.x + delta.x.signum() * (self.half.x + radius);
488 Vec2::new(x, point.y)
489 } else {
490 let z = self.center.y + delta.y.signum() * (self.half.y + radius);
491 Vec2::new(point.x, z)
492 }
493 }
494}
495
496/// A cave mouth's opening: where it is, and which way along `Z` the room
497/// it leads out of lies.
498#[derive(Clone, Copy)]
499struct Mouth {
500 at: Vec3,
501 room_side: f32,
502}
503
504impl Mouth {
505 /// True where `position` lies between the pillars and past the plane
506 /// in front of the opening — where the only two outcomes are stepping
507 /// through and being held.
508 fn holds(&self, position: Vec3) -> bool {
509 let plane = self.at.z + self.room_side * MOUTH_CROSSING_INSET;
510
511 (position.x - self.at.x).abs() < MOUTH_OPENING_HALF
512 && (plane - position.z) * self.room_side > 0.0
513 }
514
515 /// Position of each of the two pillars flanking the opening.
516 fn pillars(&self) -> impl Iterator<Item = Vec3> + Clone {
517 let at = self.at;
518
519 SIDES
520 .into_iter()
521 .map(move |side| at + Vec3::X * (side * MOUTH_PILLAR_OFFSET))
522 }
523
524 /// True when the camera looks into this mouth: it always lies on `+Z`
525 /// of the player, so the mouth whose room lies that way is the one seen
526 /// from the room's side, and the one to fill with a lintel and the dark
527 /// under it. The other is looked through from behind, and leaves its
528 /// opening clear for the room to show through.
529 fn looked_into(&self) -> bool {
530 self.room_side > 0.0
531 }
532}
533
534// ---------------------------------------------------------------------
535// The water style
536// ---------------------------------------------------------------------
537
538/// The pond's whole look, over the one value it reads: how far its ripple
539/// has traveled.
540#[derive(Default, ShaderValues)]
541struct Water {
542 time: f32,
543}
544
545impl SurfaceStyle for Water {
546 const PASS: DrawPass = DrawPass::Translucent;
547 const SURFACE: Option<&'static str> = Some(include_str!("sprite_adventure_water.wgsl"));
548}
549
550surface_styles! { enum Looks { Water } }
551
552// ---------------------------------------------------------------------
553// Meshes
554// ---------------------------------------------------------------------
555
556/// The player's current area; never both drawn in one frame.
557#[derive(Clone, Copy, PartialEq, Eq)]
558enum Area {
559 Overworld,
560 Cave,
561}
562
563/// The overworld's ground tile, its texture the only thing that separates a
564/// draw of it from another.
565#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
566struct Ground;
567
568impl Mesh for Ground {
569 fn build(&self, assets: &Assets) -> MeshData {
570 Plane
571 .build(assets)
572 .with_texture(assets.texture(GROUND_SHEET).pixelated())
573 }
574}
575
576/// The shoreline sprite laid over the pond's styled water, cutout so the
577/// water shows through its cleared middle.
578#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
579struct Shore;
580
581impl Mesh for Shore {
582 fn build(&self, assets: &Assets) -> MeshData {
583 Plane
584 .build(assets)
585 .with_texture(assets.texture(POND_SHEET).pixelated())
586 .with_material(Material::lit(Color::WHITE).cutout())
587 }
588}
589
590/// A crate prop, its texture drawn over a cube.
591#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
592struct Crate;
593
594impl Mesh for Crate {
595 fn build(&self, assets: &Assets) -> MeshData {
596 Cube.build(assets)
597 .with_texture(assets.texture(CRATE_TEXTURE).pixelated())
598 }
599}
600
601/// The well's rim.
602#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
603struct Well;
604
605impl Mesh for Well {
606 fn build(&self, assets: &Assets) -> MeshData {
607 Cube.build(assets)
608 .with_texture(assets.texture(WELL_SHEET).pixelated())
609 }
610}
611
612/// The well's mouth, laid flat over the rim's top face.
613#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
614struct WellMouth;
615
616impl Mesh for WellMouth {
617 fn build(&self, assets: &Assets) -> MeshData {
618 Plane
619 .build(assets)
620 .with_texture(assets.texture(WELL_SHEET).pixelated())
621 }
622}
623
624/// A stone box: the mouth's pillars and lintel.
625#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
626struct Stone;
627
628impl Mesh for Stone {
629 fn build(&self, assets: &Assets) -> MeshData {
630 Cube.build(assets)
631 .with_texture(assets.texture(STONE_SHEET).pixelated())
632 }
633}
634
635/// A bush sprite, cutout with its own relief.
636#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
637struct Bush;
638
639impl Mesh for Bush {
640 fn build(&self, assets: &Assets) -> MeshData {
641 Quad.build(assets)
642 .with_texture(assets.texture(BUSH_SPRITE).pixelated())
643 .with_relief(assets.relief(BUSH_RELIEF))
644 .with_material(Material::lit(Color::WHITE).cutout())
645 }
646}
647
648/// A rock sprite, cutout with its own relief.
649#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
650struct Rock;
651
652impl Mesh for Rock {
653 fn build(&self, assets: &Assets) -> MeshData {
654 Quad.build(assets)
655 .with_texture(assets.texture(ROCK_SPRITE).pixelated())
656 .with_relief(assets.relief(ROCK_RELIEF))
657 .with_material(Material::lit(Color::WHITE).cutout())
658 }
659}
660
661/// A torch's post sprite, cutout with its own relief.
662#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
663struct Torch;
664
665impl Mesh for Torch {
666 fn build(&self, assets: &Assets) -> MeshData {
667 Quad.build(assets)
668 .with_texture(assets.texture(TORCH_SPRITE).pixelated())
669 .with_relief(assets.relief(TORCH_RELIEF))
670 .with_material(Material::lit(Color::WHITE).cutout())
671 }
672}
673
674/// A torch's flame sprite, added over the dark rather than lit.
675#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
676struct Flame;
677
678impl Mesh for Flame {
679 fn build(&self, assets: &Assets) -> MeshData {
680 Quad.build(assets)
681 .with_texture(assets.texture(FLAME_SHEET).pixelated())
682 .with_material(Material::color(FLAME_TINT).additive())
683 }
684}
685
686/// The player's sprite, cutout with its own relief, its sheet shared
687/// with `examples/isometric-board.rs`.
688#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
689struct Walker;
690
691impl Mesh for Walker {
692 fn build(&self, assets: &Assets) -> MeshData {
693 Quad.build(assets)
694 .with_texture(assets.texture(WALKER_SHEET).pixelated())
695 .with_relief(assets.relief(WALKER_RELIEF))
696 .with_material(Material::lit(Color::WHITE).cutout())
697 }
698}
699
700/// The cave floor tile.
701#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
702struct CaveFloor;
703
704impl Mesh for CaveFloor {
705 fn build(&self, assets: &Assets) -> MeshData {
706 Plane
707 .build(assets)
708 .with_texture(assets.texture(CAVE_SHEET).pixelated())
709 }
710}
711
712/// The cave wall face.
713#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
714struct CaveWall;
715
716impl Mesh for CaveWall {
717 fn build(&self, assets: &Assets) -> MeshData {
718 Cube.build(assets)
719 .with_texture(assets.texture(CAVE_SHEET).pixelated())
720 }
721}
722
723/// The loaded door, drawn as its source authored it.
724#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
725struct Door;
726
727impl Mesh for Door {
728 fn build(&self, assets: &Assets) -> MeshData {
729 assets.mesh(DOOR_MESH)
730 }
731}
732
733/// The loaded gem, repainted whole per draw so its glow color shifts.
734#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
735struct Gem;
736
737impl Mesh for Gem {
738 fn build(&self, assets: &Assets) -> MeshData {
739 assets.mesh(GEM_MESH)
740 }
741}
742
743// Everything this game can draw: the meshes above, plus the styled water,
744// the dark filling a looked-into mouth's opening, and the door's own frame,
745// which draw the bare engine primitives Plane, Quad and Cube.
746meshes! {
747 enum Shape {
748 Ground, Shore, Crate, Well, WellMouth, Stone, Bush, Rock, Torch,
749 Flame, Walker, CaveFloor, CaveWall, Door, Gem, Plane, Quad, Cube,
750 }
751}
752
753/// The interact click and the gem's chime, shared with the other examples.
754#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
755enum Sound {
756 Interact,
757 Gem,
758}
759
760impl Sounds for Sound {
761 fn build(&self, assets: &Assets) -> SoundData {
762 match self {
763 Sound::Interact => assets.sound("click"),
764 Sound::Gem => assets.sound("win"),
765 }
766 }
767}
768
769// ---------------------------------------------------------------------
770// Input
771// ---------------------------------------------------------------------
772
773/// Player movement: `WASD`, arrows, or a stick — the strongest reading is
774/// kept.
775#[derive(InputAxis2Action, Clone, Copy, PartialEq)]
776enum Move {
777 Walk,
778}
779
780impl InputAxis2Action for Move {
781 fn bindings(&self) -> Vec<Axis2Binding> {
782 match self {
783 Move::Walk => vec![
784 Axis2Binding::from(ButtonAxis2 {
785 left: Key::A,
786 right: Key::D,
787 down: Key::S,
788 up: Key::W,
789 }),
790 Axis2Binding::from(ButtonAxis2 {
791 left: Key::Left,
792 right: Key::Right,
793 down: Key::Down,
794 up: Key::Up,
795 }),
796 Axis2Binding::stick(Stick::Left),
797 ],
798 }
799 }
800}
801
802/// The two verbs this game reads as an edge: interacting with the door, and
803/// a reset of the world to every saved key's fallback.
804#[derive(InputButtonAction, Clone, Copy, PartialEq)]
805enum Button {
806 Interact,
807 Reset,
808}
809
810impl InputButtonAction for Button {
811 fn bindings(&self) -> Vec<ButtonBinding> {
812 match self {
813 Button::Interact => vec![Key::E.into(), Pad::West.into()],
814 Button::Reset => vec![Key::R.into()],
815 }
816 }
817}
818
819struct Controls;
820
821impl InputActions for Controls {
822 type Button = Button;
823 type Axis = NoInputAxes;
824 type Axis2 = Move;
825}
826
827// ---------------------------------------------------------------------
828// Save data
829// ---------------------------------------------------------------------
830
831/// The player's last position, read at startup and saved on area
832/// transition and gem pickup.
833#[derive(Saves, Clone, Copy)]
834enum Position {
835 X,
836 Z,
837}
838
839impl SaveKey for Position {
840 type Value = f64;
841
842 fn fallback(&self) -> f64 {
843 match self {
844 Position::X => PLAYER_SPAWN.x as f64,
845 Position::Z => PLAYER_SPAWN.z as f64,
846 }
847 }
848}
849
850/// The area the player is in, and whether the gem is taken.
851#[derive(Saves, Clone, Copy)]
852enum Flag {
853 InCave,
854 GemTaken,
855}
856
857impl SaveKey for Flag {
858 type Value = bool;
859
860 fn fallback(&self) -> bool {
861 false
862 }
863}
864
865// ---------------------------------------------------------------------
866// The player's facing
867// ---------------------------------------------------------------------
868
869/// The player's last facing: also its row in the sheet, top to bottom.
870#[derive(Clone, Copy, PartialEq)]
871enum Facing {
872 Toward = 0,
873 Right = 1,
874 Away = 2,
875 Left = 3,
876}
877
878impl Facing {
879 /// The facing `heading` points in, favoring its larger axis; `None` at
880 /// rest, so the caller can keep the last facing.
881 fn from_heading(heading: Vec2) -> Option<Self> {
882 if heading == Vec2::ZERO {
883 return None;
884 }
885 Some(if heading.x.abs() > heading.y.abs() {
886 if heading.x > 0.0 {
887 Self::Right
888 } else {
889 Self::Left
890 }
891 } else if heading.y > 0.0 {
892 Self::Away
893 } else {
894 Self::Toward
895 })
896 }
897}
898
899// ---------------------------------------------------------------------
900// The game
901// ---------------------------------------------------------------------
902
903/// The ground tile at `col, row`: the path's dirt along [`PATH_COLUMN`],
904/// the verges that edge it, and a hashed grass variant everywhere else.
905fn ground_cell(col: i32, row: i32) -> Frame {
906 let (column, sheet_row) = match col - PATH_COLUMN {
907 0 => (PATH_DIRT + row.rem_euclid(2) as u32, PATH_ROW),
908 -1 => (PATH_WEST_VERGE, PATH_ROW),
909 1 => (PATH_EAST_VERGE, PATH_ROW),
910 _ => (
911 (col * 31 + row * 17).rem_euclid(GROUND_COLUMNS as i32) as u32,
912 GRASS_ROW,
913 ),
914 };
915
916 Sheet::new(UVec2::new(GROUND_COLUMNS, GROUND_ROWS)).cell_at(UVec2::new(column, sheet_row))
917}
918
919/// The stone sheet's plain masonry, laid `tiles` times across: the sampler
920/// wraps, so a window wider than the sheet repeats the course.
921fn masonry(tiles: f32) -> Frame {
922 let course = 1.0 / STONE_ROWS as f32;
923
924 Frame::rect(Vec2::new(0.0, 1.0 - course), Vec2::new(tiles, 1.0))
925}
926
927/// The wall or door's alpha `fraction` of the way from [`SOLID`] to
928/// [`GHOST_ALPHA`].
929fn ghost_alpha(fraction: f32) -> f32 {
930 SOLID + (GHOST_ALPHA - SOLID) * fraction
931}
932
933/// The wall face in column `variant`, windowed to the meters `standing` of
934/// one course, measured up from that course's own base: every row of the
935/// cave sheet below the floor's covers [`WALL_HEIGHT`], so a course keeps
936/// the floor's texels to the meter however it is cut.
937fn cave_wall_face(variant: u32, standing: Range<f32>) -> Frame {
938 let cell = Vec2::new(1.0 / CAVE_COLUMNS as f32, 1.0 / CAVE_ROWS as f32);
939 let left = (variant % CAVE_COLUMNS) as f32 * cell.x;
940 let face = (CAVE_FLOOR_ROW + 1) as f32 * cell.y;
941 let up_from_base = |height: f32| 1.0 - (1.0 - face) * (height / WALL_HEIGHT);
942
943 Frame::rect(
944 Vec2::new(left, up_from_base(standing.end)),
945 Vec2::new(left + cell.x, up_from_base(standing.start)),
946 )
947}
948
949/// The logical point egui paints the physical pixel `pixel` at.
950fn logical(pixel: Vec2, pixels_per_point: f32) -> egui::Pos2 {
951 let point = pixel / pixels_per_point;
952 egui::pos2(point.x, point.y)
953}
954
955fn main() {
956 run(
957 Config::new("Mirage: sprite adventure")
958 .with_size(1280, 720)
959 .with_assets([
960 MODEL,
961 WALKER_SOURCE,
962 WALKER_RELIEF_SOURCE,
963 GROUND_SOURCE,
964 BUSH_SOURCE,
965 BUSH_RELIEF_SOURCE,
966 ROCK_SOURCE,
967 ROCK_RELIEF_SOURCE,
968 TORCH_RELIEF_SOURCE,
969 CRATE_SOURCE,
970 WELL_SOURCE,
971 STONE_SOURCE,
972 CAVE_SOURCE,
973 POND_SOURCE,
974 TORCH_SOURCE,
975 FLAME_SOURCE,
976 INTERACT_SOUND,
977 GEM_SOUND,
978 ]),
979 Keep::init,
980 );
981}
982
983struct Keep {
984 area: Area,
985 position: Vec3,
986 previous: Vec3,
987 facing: Facing,
988 walk_ticks: u32,
989 simulated: Duration,
990 door_opening: bool,
991 /// Ticks the door has been opening for, at a cap of
992 /// [`DOOR_SWING_TICKS`]: how long its world prompt reads "opening" once
993 /// it starts.
994 swing_ticks: u32,
995 gem_taken: bool,
996 /// How far the door wall's fade from [`SOLID`] to [`GHOST_ALPHA`] has
997 /// run as of the last tick: `0.0` to `1.0`.
998 ghost: f32,
999 /// Set by the panel's reset button, since its click lands in a frame
1000 /// rather than a tick; read and cleared on the next tick.
1001 reset_requested: bool,
1002}
1003
1004impl Keep {
1005 /// Prepares every startup-cataloged mesh and resumes wherever the last
1006 /// run left the player.
1007 fn init(ctx: &mut InitContext<'_, Keep>) -> Result<Self, Error> {
1008 let startup = ctx.startup();
1009 let gem_taken = startup.saved(Flag::GemTaken);
1010 let area = if startup.saved(Flag::InCave) {
1011 Area::Cave
1012 } else {
1013 Area::Overworld
1014 };
1015 let position = Vec3::new(
1016 startup.saved(Position::X) as f32,
1017 0.0,
1018 startup.saved(Position::Z) as f32,
1019 );
1020
1021 Ok(Self {
1022 area,
1023 position,
1024 previous: position,
1025 facing: Facing::Toward,
1026 walk_ticks: 0,
1027 simulated: Duration::ZERO,
1028 door_opening: gem_taken,
1029 swing_ticks: if gem_taken { DOOR_SWING_TICKS } else { 0 },
1030 gem_taken,
1031 ghost: 0.0,
1032 reset_requested: false,
1033 })
1034 }
1035
1036 fn camera(position: Vec3, offset: Vec3) -> Camera {
1037 Camera::new(
1038 View::look_at(position + offset, position),
1039 Projection::perspective(CAMERA_FOV),
1040 )
1041 }
1042
1043 /// Obstacles from the overworld's props: the crates, turned as they are
1044 /// drawn, the well's rim, the open water the shoreline rings, the
1045 /// mouth's pillars, and each flora's base.
1046 fn overworld_obstacles() -> impl Iterator<Item = Obstacle> + Clone {
1047 CRATE_POSITIONS
1048 .into_iter()
1049 .map(|(x, z, turn)| {
1050 Obstacle::footprint(Vec2::new(x, z), Vec2::splat(CRATE_SIZE * turned_span(turn)))
1051 })
1052 .chain([
1053 Obstacle::footprint(WELL_POSITION.xz(), WELL_SIZE.xz()),
1054 Obstacle::footprint(POND_CENTER.xz(), Vec2::splat(POND_WATER_HALF * 2.0)),
1055 ])
1056 .chain(
1057 ENTRANCE
1058 .pillars()
1059 .map(|at| Obstacle::footprint(at.xz(), MOUTH_PILLAR_SIZE.xz())),
1060 )
1061 .chain(FLORA.into_iter().map(|(x, z, rock)| {
1062 let base = if rock { ROCK_FOOTPRINT } else { BUSH_FOOTPRINT };
1063 Obstacle::footprint(Vec2::new(x, z), Vec2::splat(base))
1064 }))
1065 }
1066
1067 /// Obstacles from the cave: the torch posts, its own mouth's pillars,
1068 /// the runs of wall either side of the doorway and of the mouth, and the
1069 /// `door` leaf.
1070 fn cave_obstacles(door: Obstacle) -> impl Iterator<Item = Obstacle> + Clone {
1071 TORCH_POSITIONS
1072 .into_iter()
1073 .map(|(x, z)| Obstacle::footprint(Vec2::new(x, z), Vec2::splat(TORCH_STAND_WIDTH)))
1074 .chain(
1075 EXIT.pillars()
1076 .map(|at| Obstacle::footprint(at.xz(), MOUTH_PILLAR_SIZE.xz())),
1077 )
1078 .chain(SIDES.into_iter().flat_map(|side| {
1079 [
1080 Self::wall_run(side, DOOR_Z),
1081 Self::wall_run(side, CAVE_LIP_Z),
1082 ]
1083 }))
1084 .chain([door])
1085 }
1086
1087 /// One of the two runs of wall either side of a one-tile opening on the
1088 /// room's axis, at `z`.
1089 fn wall_run(side: f32, z: f32) -> Obstacle {
1090 Obstacle::footprint(
1091 Vec2::new(side * (DOORWAY_HALF + DOOR_WALL_END) * 0.5, z),
1092 Vec2::new(DOOR_WALL_END - DOORWAY_HALF, TILE_SIZE),
1093 )
1094 }
1095
1096 /// Obstacle from the door leaf's own footprint: the box over its four
1097 /// corners, swung back against the wall once the door is opened.
1098 fn door_obstacle(&self) -> Obstacle {
1099 let hinge = DOOR_HINGE.xz();
1100 let across = DOOR_THICKNESS * 0.5;
1101 let corner = |along: f32, aside: f32| {
1102 let (x, z) = if self.door_opening {
1103 (aside, -along)
1104 } else {
1105 (along, aside)
1106 };
1107 hinge + Vec3::new(x, 0.0, z).xz()
1108 };
1109
1110 Obstacle::over([
1111 corner(0.0, -across),
1112 corner(0.0, across),
1113 corner(DOOR_WIDTH, -across),
1114 corner(DOOR_WIDTH, across),
1115 ])
1116 }
1117
1118 /// Pushes the player out of every obstacle their circle has walked into,
1119 /// over as many passes as it takes for one to leave them where the last
1120 /// one did — overlapping obstacles need more than one.
1121 fn push_out_of(&mut self, obstacles: impl Iterator<Item = Obstacle> + Clone) {
1122 /// Passes an overlap is given to settle before the frame takes what
1123 /// it has; ones this game builds settle in two.
1124 const PASSES: u32 = 4;
1125
1126 let mut standing = self.position.xz();
1127 for _ in 0..PASSES {
1128 let settled = obstacles.clone().fold(standing, |point, obstacle| {
1129 obstacle.push_out(point, PLAYER_RADIUS)
1130 });
1131 if settled == standing {
1132 break;
1133 }
1134 standing = settled;
1135 }
1136
1137 self.position.x = standing.x;
1138 self.position.z = standing.y;
1139 }
1140
1141 fn tick_overworld(&mut self, ctx: &mut TickContext<'_, Keep>) {
1142 self.push_out_of(Self::overworld_obstacles());
1143 self.position.x = self.position.x.clamp(-CLEARING_HALF, CLEARING_HALF);
1144 self.position.z = self.position.z.clamp(-CLEARING_HALF, CLEARING_HALF);
1145
1146 if ENTRANCE.holds(self.position) {
1147 if ENTRANCE.holds(self.previous) {
1148 self.position.z = self.previous.z;
1149 } else {
1150 self.enter_cave(ctx);
1151 }
1152 }
1153 }
1154
1155 fn tick_cave(&mut self, ctx: &mut TickContext<'_, Keep>) {
1156 self.push_out_of(Self::cave_obstacles(self.door_obstacle()));
1157 self.position.x = self.position.x.clamp(-CAVE_HALF_WIDTH, CAVE_HALF_WIDTH);
1158 self.position.z = self.position.z.clamp(CAVE_WALK_FAR_Z, CAVE_WALK_NEAR_Z);
1159
1160 let target = if self.position.z < DOOR_WALL_NEAR_Z {
1161 1.0
1162 } else {
1163 0.0
1164 };
1165 let step = 1.0 / GHOST_RAMP_TICKS as f32;
1166 self.ghost += (target - self.ghost).clamp(-step, step);
1167
1168 if !self.door_opening
1169 && ctx.pressed(Button::Interact)
1170 && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS
1171 {
1172 self.door_opening = true;
1173 self.swing_ticks = 0;
1174 ctx.play(Sound::Interact);
1175 }
1176 if self.door_opening && self.swing_ticks < DOOR_SWING_TICKS {
1177 self.swing_ticks += 1;
1178 }
1179
1180 if !self.gem_taken && self.position.distance(GEM_POSITION) < PICKUP_RADIUS {
1181 self.gem_taken = true;
1182 ctx.play(Sound::Gem);
1183 ctx.save(Flag::GemTaken, true);
1184 ctx.save(Position::X, self.position.x as f64);
1185 ctx.save(Position::Z, self.position.z as f64);
1186 }
1187
1188 if EXIT.holds(self.position) {
1189 if EXIT.holds(self.previous) {
1190 self.position.z = self.previous.z;
1191 } else {
1192 self.exit_cave(ctx);
1193 }
1194 }
1195 }
1196
1197 /// Puts the player back at [`PLAYER_SPAWN`] with the cave and the gem
1198 /// returned to their saved fallbacks, all in this tick: a reset saves
1199 /// every key's own fallback, since there is nothing to clear it to.
1200 fn reset(&mut self, ctx: &mut TickContext<'_, Keep>) {
1201 ctx.save(Position::X, Position::X.fallback());
1202 ctx.save(Position::Z, Position::Z.fallback());
1203 ctx.save(Flag::InCave, Flag::InCave.fallback());
1204 ctx.save(Flag::GemTaken, Flag::GemTaken.fallback());
1205
1206 self.area = Area::Overworld;
1207 self.position = PLAYER_SPAWN;
1208 self.previous = PLAYER_SPAWN;
1209 self.gem_taken = false;
1210 self.door_opening = false;
1211 self.swing_ticks = 0;
1212 self.ghost = 0.0;
1213 }
1214
1215 /// Steps into the cave at [`CAVE_SPAWN`], saving the transition.
1216 fn enter_cave(&mut self, ctx: &mut TickContext<'_, Keep>) {
1217 self.area = Area::Cave;
1218 self.position = CAVE_SPAWN;
1219 self.previous = CAVE_SPAWN;
1220 ctx.save(Flag::InCave, true);
1221 ctx.save(Position::X, CAVE_SPAWN.x as f64);
1222 ctx.save(Position::Z, CAVE_SPAWN.z as f64);
1223 }
1224
1225 /// Steps back out to the mouth at [`RETURN_SPAWN`], saving the
1226 /// transition.
1227 fn exit_cave(&mut self, ctx: &mut TickContext<'_, Keep>) {
1228 self.area = Area::Overworld;
1229 self.position = RETURN_SPAWN;
1230 self.previous = RETURN_SPAWN;
1231 ctx.save(Flag::InCave, false);
1232 ctx.save(Position::X, RETURN_SPAWN.x as f64);
1233 ctx.save(Position::Z, RETURN_SPAWN.z as f64);
1234 }
1235
1236 fn draw_ground(&self, ctx: &mut FrameContext<'_, Keep>) {
1237 for col in -GROUND_DRAW_HALF..=GROUND_DRAW_HALF {
1238 for row in -GROUND_DRAW_HALF..=GROUND_DRAW_HALF {
1239 ctx.draw(
1240 Ground
1241 .at(Vec3::new(
1242 col as f32 * TILE_SIZE,
1243 0.0,
1244 row as f32 * TILE_SIZE,
1245 ))
1246 .frame(ground_cell(col, row)),
1247 );
1248 }
1249 }
1250 }
1251
1252 /// Two staggered rows of bushes around the clearing, open where the path
1253 /// leaves it, drawn between the camera and the ground's edge. The rows
1254 /// running along `Z` skip their two ends, which the rows running along
1255 /// `X` already cover.
1256 fn draw_hedgerow(&self, ctx: &mut FrameContext<'_, Keep>) {
1257 for (row, half) in [HEDGE_INNER_HALF, HEDGE_OUTER_HALF].into_iter().enumerate() {
1258 let row = row as i32;
1259 // The inner row covers both corners; the outer one is half a
1260 // span in from each, backing the gaps the inner row leaves.
1261 let spans = ((2.0 * half / HEDGE_STEP).round() as i32).max(1);
1262 let span = 2.0 * half / spans as f32;
1263 let steps = spans - row;
1264 for step in 0..=steps {
1265 let along = -half + (step as f32 + 0.5 * row as f32) * span;
1266 let scale = if (step + row) % 2 == 0 { 1.0 } else { 0.8 };
1267 let (width, height) = (BUSH_WIDTH * scale, BUSH_HEIGHT * scale);
1268 // The path leaves through the rows running along `X`, so only
1269 // those two open around it.
1270 let gated = along.abs() < HEDGE_GATE_HALF;
1271 let corner = step == 0 || step == steps;
1272 let places = [
1273 (along, -half, gated),
1274 (along, half, gated),
1275 (-half, along, corner),
1276 (half, along, corner),
1277 ];
1278 for (x, z, skip) in places {
1279 if skip {
1280 continue;
1281 }
1282 ctx.draw(
1283 Bush.at(Transform::from_scale_rotation_translation(
1284 Vec3::new(width, height, width),
1285 Quat::IDENTITY,
1286 Vec3::new(x, height * 0.5, z),
1287 ))
1288 .upright(),
1289 );
1290 }
1291 }
1292 }
1293 }
1294
1295 /// The pond: a square of styled water, and the shoreline sprite laid over
1296 /// it, which rings the open middle and hides the water's own edges.
1297 fn draw_pond(&self, ctx: &mut FrameContext<'_, Keep>) {
1298 ctx.draw(
1299 Plane
1300 .at(Transform::from_scale_rotation_translation(
1301 Vec3::splat(POND_WATER_HALF * 2.0),
1302 Quat::IDENTITY,
1303 POND_CENTER,
1304 ))
1305 .material(Material::shaded(WATER_COLOR, WATER_LITNESS))
1306 .surface_style::<Water>(),
1307 );
1308 ctx.draw(
1309 Shore
1310 .at(Transform::from_scale_rotation_translation(
1311 Vec3::splat(POND_HALF * 2.0),
1312 Quat::IDENTITY,
1313 Vec3::new(POND_CENTER.x, 0.0, POND_CENTER.z),
1314 ))
1315 .frame(Sheet::new(UVec2::new(POND_CELLS, 1)).cell(POND_SHORE_CELL)),
1316 );
1317 }
1318
1319 fn draw_crates(&self, ctx: &mut FrameContext<'_, Keep>) {
1320 for &(x, z, turn) in &CRATE_POSITIONS {
1321 ctx.draw(Crate.at(Transform::from_scale_rotation_translation(
1322 Vec3::splat(CRATE_SIZE),
1323 Quat::from_rotation_y(turn),
1324 Vec3::new(x, CRATE_SIZE * 0.5, z),
1325 )));
1326 }
1327 }
1328
1329 /// The well: its rim in grey masonry, and the mouth cell laid over the
1330 /// rim's top face.
1331 fn draw_well(&self, ctx: &mut FrameContext<'_, Keep>) {
1332 let cells = Sheet::new(UVec2::new(WELL_CELLS, 1));
1333 ctx.draw(
1334 Well.at(Transform::from_scale_rotation_translation(
1335 WELL_SIZE,
1336 Quat::IDENTITY,
1337 WELL_POSITION + Vec3::Y * (WELL_SIZE.y * 0.5),
1338 ))
1339 .frame(cells.cell(WELL_RIM_CELL)),
1340 );
1341 ctx.draw(
1342 WellMouth
1343 .at(Transform::from_scale_rotation_translation(
1344 Vec3::new(WELL_SIZE.x, 1.0, WELL_SIZE.z),
1345 Quat::IDENTITY,
1346 WELL_POSITION + Vec3::Y * (WELL_SIZE.y + WELL_MOUTH_LIFT),
1347 ))
1348 .frame(cells.cell(WELL_MOUTH_CELL)),
1349 );
1350 }
1351
1352 fn draw_flora(&self, ctx: &mut FrameContext<'_, Keep>) {
1353 for &(x, z, rock) in &FLORA {
1354 let (width, height) = if rock {
1355 (ROCK_WIDTH, ROCK_HEIGHT)
1356 } else {
1357 (BUSH_WIDTH, BUSH_HEIGHT)
1358 };
1359 let standing = Transform::from_scale_rotation_translation(
1360 Vec3::new(width, height, width),
1361 Quat::IDENTITY,
1362 Vec3::new(x, height * 0.5, z),
1363 );
1364 let flora: Instance<Shape, _> = if rock {
1365 Rock.at(standing).into_set()
1366 } else {
1367 Bush.at(standing).into_set()
1368 };
1369 ctx.draw(flora.upright());
1370 }
1371 }
1372
1373 /// One stone box drawn on the ground at `at`, `size` across, sampling
1374 /// the part of the sheet `frame` covers.
1375 fn draw_stone(ctx: &mut FrameContext<'_, Keep>, at: Vec3, size: Vec3, frame: Frame) {
1376 ctx.draw(
1377 Stone
1378 .at(Transform::from_scale_rotation_translation(
1379 size,
1380 Quat::IDENTITY,
1381 at + Vec3::Y * (size.y * 0.5),
1382 ))
1383 .frame(frame),
1384 );
1385 }
1386
1387 /// Two stone pillars drawn where `mouth` blocks the player, each a
1388 /// capital over its own course of masonry, and, on the one the camera
1389 /// looks into, the lintel across their tops and the dark filling
1390 /// the opening under it.
1391 fn draw_mouth(ctx: &mut FrameContext<'_, Keep>, mouth: Mouth) {
1392 for at in mouth.pillars() {
1393 Self::draw_stone(ctx, at, MOUTH_PILLAR_SIZE, Frame::default());
1394 }
1395 if !mouth.looked_into() {
1396 return;
1397 }
1398
1399 Self::draw_stone(
1400 ctx,
1401 mouth.at + Vec3::Y * MOUTH_PILLAR_SIZE.y,
1402 MOUTH_LINTEL_SIZE,
1403 masonry(MOUTH_LINTEL_TILES),
1404 );
1405 ctx.draw(
1406 Quad.at(Transform::from_scale_rotation_translation(
1407 Vec3::new(MOUTH_PILLAR_OFFSET * 2.0, MOUTH_DARK_HEIGHT, 1.0),
1408 Quat::IDENTITY,
1409 mouth.at + Vec3::Y * (MOUTH_DARK_HEIGHT * 0.5),
1410 ))
1411 .material(Material::color(Color::BLACK)),
1412 );
1413 }
1414
1415 fn draw_cave_floor(&self, ctx: &mut FrameContext<'_, Keep>) {
1416 let half = CAVE_HALF_WIDTH as i32;
1417 let near = CAVE_NEAR_Z as i32;
1418 let far = CAVE_FAR_Z as i32;
1419 for col in -half..=half {
1420 for row in far..=near {
1421 let variant = (col * 13 + row * 7).rem_euclid(CAVE_COLUMNS as i32) as u32;
1422 ctx.draw(
1423 CaveFloor
1424 .at(Vec3::new(
1425 col as f32 * TILE_SIZE,
1426 0.0,
1427 row as f32 * TILE_SIZE,
1428 ))
1429 .frame(
1430 Sheet::new(UVec2::new(CAVE_COLUMNS, CAVE_ROWS))
1431 .cell_at(UVec2::new(variant, CAVE_FLOOR_ROW)),
1432 ),
1433 );
1434 }
1435 }
1436 }
1437
1438 /// The wall drawn at `at` over the meters `standing`, in courses
1439 /// [`WALL_HEIGHT`] tall from the floor up, each cut to the part of it the
1440 /// span leaves; its faces are picked by `seed` and its stone faded to
1441 /// `fade`, which is `1.0` wherever it is solid.
1442 fn draw_wall(
1443 ctx: &mut FrameContext<'_, Keep>,
1444 at: Vec2,
1445 standing: Range<f32>,
1446 seed: i32,
1447 fade: f32,
1448 ) {
1449 for course in 0..WALL_COURSES {
1450 let base = course as f32 * WALL_HEIGHT;
1451 let low = (standing.start - base).max(0.0);
1452 let high = (standing.end - base).min(WALL_HEIGHT);
1453 if high <= low {
1454 continue;
1455 }
1456
1457 let variant = (seed + course).rem_euclid(CAVE_COLUMNS as i32) as u32;
1458 ctx.draw(
1459 CaveWall
1460 .at(Transform::from_scale_rotation_translation(
1461 Vec3::new(TILE_SIZE, high - low, TILE_SIZE),
1462 Quat::IDENTITY,
1463 Vec3::new(at.x, base + (low + high) * 0.5, at.y),
1464 ))
1465 .frame(cave_wall_face(variant, low..high))
1466 .faded(fade),
1467 );
1468 }
1469 }
1470
1471 /// The room's two side walls and its back wall, full height, and the low
1472 /// wall closing its near end between the side walls and the mouth. The
1473 /// back wall stops short of the corners the side walls already fill, and
1474 /// the near one leaves the mouth's own tile open.
1475 fn draw_cave_walls(&self, ctx: &mut FrameContext<'_, Keep>) {
1476 let half = CAVE_HALF_WIDTH as i32 + 1;
1477 let near = CAVE_NEAR_Z as i32;
1478 let far = CAVE_FAR_Z as i32;
1479
1480 for row in far..=near {
1481 let z = row as f32 * TILE_SIZE;
1482 let west = Vec2::new(-half as f32 * TILE_SIZE, z);
1483 let east = Vec2::new(half as f32 * TILE_SIZE, z);
1484 Self::draw_wall(ctx, west, 0.0..WALL_TOP, row * 5, SOLID);
1485 Self::draw_wall(ctx, east, 0.0..WALL_TOP, row * 5 + 1, SOLID);
1486 }
1487 for col in (-half + 1)..half {
1488 let x = col as f32 * TILE_SIZE;
1489 let back = Vec2::new(x, far as f32 * TILE_SIZE);
1490 Self::draw_wall(ctx, back, 0.0..WALL_TOP, col * 5 + 2, SOLID);
1491 if col != 0 {
1492 let lip = Vec2::new(x, CAVE_LIP_Z);
1493 Self::draw_wall(ctx, lip, 0.0..CAVE_LIP_HEIGHT, col * 5 + 4, SOLID);
1494 }
1495 }
1496 }
1497
1498 /// The wall the door hangs in, run across the room between the side walls
1499 /// with one tile left open on the room's axis for the doorway and stone
1500 /// filling the column over the door. A player behind the wall is drawn
1501 /// through the stacks between them and the camera, at `seen_through`,
1502 /// faded by `ghost`; the rest of it stays solid, and keeps casting.
1503 fn draw_door_wall(ctx: &mut FrameContext<'_, Keep>, seen_through: Option<f32>, ghost: f32) {
1504 let stone = |x: f32| match seen_through {
1505 Some(at) if (x - at).abs() < GHOST_CORRIDOR_HALF => ghost_alpha(ghost),
1506 _ => SOLID,
1507 };
1508 let half = CAVE_HALF_WIDTH as i32;
1509
1510 for col in (-half..=half).filter(|&col| col != 0) {
1511 let x = col as f32 * TILE_SIZE;
1512 Self::draw_wall(
1513 ctx,
1514 Vec2::new(x, DOOR_Z),
1515 0.0..WALL_TOP,
1516 col * 5 + 3,
1517 stone(x),
1518 );
1519 }
1520 Self::draw_wall(
1521 ctx,
1522 Vec2::new(0.0, DOOR_Z),
1523 DOOR_HEIGHT..WALL_TOP,
1524 3,
1525 stone(0.0),
1526 );
1527 }
1528
1529 /// The two torches: an upright cutout post apiece, the flame's loop
1530 /// burning over its binding, and the light that flame casts.
1531 fn draw_torches(&self, ctx: &mut FrameContext<'_, Keep>) {
1532 let elapsed = self.simulated.as_secs_f32();
1533 let loop_cells = Sheet::new(UVec2::new(FLAME_CELLS, 1));
1534
1535 for (index, &(x, z)) in TORCH_POSITIONS.iter().enumerate() {
1536 let base = Vec3::new(x, 0.0, z);
1537 ctx.draw(
1538 Torch
1539 .at(Transform::from_scale_rotation_translation(
1540 Vec3::new(TORCH_SPRITE_WIDTH, TORCH_STAND_HEIGHT, 1.0),
1541 Quat::IDENTITY,
1542 base + Vec3::Y * (TORCH_STAND_HEIGHT * 0.5),
1543 ))
1544 .upright(),
1545 );
1546
1547 let phase = index as f32 * 2.1;
1548 let flicker = (elapsed * FLAME_FLICKER_SPEED + phase).sin();
1549 let flame_pos =
1550 base + Vec3::Y * (TORCH_STAND_HEIGHT + FLAME_LIFT + flicker * FLAME_BOB);
1551
1552 let light_pos = flame_pos + Vec3::new(0.0, TORCH_LIGHT_LIFT, TORCH_LIGHT_STANDOFF);
1553 ctx.light(Light::point(light_pos, TORCH_LIGHT_COLOR, TORCH_LIGHT_RANGE).shadow());
1554 // The pair burn an even share of the loop apart.
1555 let offset = index as u32 * FLAME_CELLS / TORCH_POSITIONS.len() as u32;
1556 ctx.draw(
1557 Flame
1558 .at(Transform::from_scale_rotation_translation(
1559 Vec3::splat(FLAME_SIZE),
1560 Quat::IDENTITY,
1561 flame_pos,
1562 ))
1563 .billboard()
1564 .roll(flicker * FLAME_ROLL)
1565 .frame(loop_cells.cell((elapsed * FLAME_RATE) as u32 + offset)),
1566 );
1567 }
1568 }
1569
1570 /// The door at its hinge — swung back against the wall once opened —
1571 /// drawn through alongside its wall, faded by `ghost`.
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 }
1586
1587 /// The posts and lintel framing the doorway, in a color the stone never
1588 /// is, standing clear of the wall so the opening reads as a door from
1589 /// across the chamber. Glowing of their own while the door is closed and
1590 /// within [`INTERACT_RADIUS`], the cue that it opens.
1591 fn draw_door_frame(&self, ctx: &mut FrameContext<'_, Keep>, ghost: f32) {
1592 let reachable =
1593 !self.door_opening && self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1594 let material =
1595 Material::shaded(DOOR_FRAME_COLOR, DOOR_FRAME_LITNESS).emissive(if reachable {
1596 DOOR_FRAME_GLOW
1597 } else {
1598 Color::BLACK
1599 });
1600 let fade = ghost_alpha(ghost);
1601 let z = DOOR_WALL_NEAR_Z + DOOR_FRAME_STANDOFF;
1602 let jamb_height = DOOR_HEIGHT + DOOR_FRAME_THICKNESS;
1603
1604 for side in SIDES {
1605 ctx.draw(
1606 Cube.at(Transform::from_scale_rotation_translation(
1607 Vec3::new(DOOR_FRAME_THICKNESS, jamb_height, DOOR_FRAME_THICKNESS),
1608 Quat::IDENTITY,
1609 Vec3::new(
1610 side * (DOORWAY_HALF + DOOR_FRAME_THICKNESS * 0.5),
1611 jamb_height * 0.5,
1612 z,
1613 ),
1614 ))
1615 .material(material)
1616 .faded(fade),
1617 );
1618 }
1619 ctx.draw(
1620 Cube.at(Transform::from_scale_rotation_translation(
1621 Vec3::new(
1622 DOOR_WIDTH + DOOR_FRAME_THICKNESS * 2.0,
1623 DOOR_FRAME_THICKNESS,
1624 DOOR_FRAME_THICKNESS,
1625 ),
1626 Quat::IDENTITY,
1627 Vec3::new(0.0, DOOR_HEIGHT + DOOR_FRAME_THICKNESS * 0.5, z),
1628 ))
1629 .material(material)
1630 .faded(fade),
1631 );
1632 }
1633
1634 /// A world prompt over the door: what opens it while the player is
1635 /// within [`INTERACT_RADIUS`] and it is closed, and that it swings while
1636 /// it does; gone once it has swung [`DOOR_SWING_TICKS`]. Laid out and
1637 /// placed like `examples/animation.rs`'s own prompt.
1638 fn draw_door_prompt(&self, ctx: &mut FrameContext<'_, Keep>, camera: Camera) {
1639 let near = self.position.distance(INTERACT_POINT) < INTERACT_RADIUS;
1640 let swinging = self.door_opening && self.swing_ticks < DOOR_SWING_TICKS;
1641 let text = if swinging {
1642 "opening"
1643 } else if near && !self.door_opening {
1644 "e opens the door"
1645 } else {
1646 return;
1647 };
1648
1649 let galley = ctx.text_layout(text, egui::FontId::proportional(DOOR_PROMPT_SIZE));
1650 let point = INTERACT_POINT + Vec3::Y * (DOOR_HEIGHT + DOOR_PROMPT_LIFT);
1651 let window_size = ctx.window_size();
1652 let pixels_per_point = ctx.pixels_per_point();
1653 let Some(pixel) = camera.pixel_of(point, window_size) else {
1654 return;
1655 };
1656
1657 ctx.ui(|ui| {
1658 let painter = ui.painter();
1659 let at = logical(pixel, pixels_per_point);
1660 let ink = galley.mesh_bounds;
1661 let pos = egui::pos2(at.x - ink.center().x, at.y - ink.center().y);
1662 let backdrop = egui::Rect::from_center_size(
1663 at,
1664 ink.size() + egui::Vec2::splat(DOOR_PROMPT_PADDING * 2.0),
1665 );
1666 painter.rect_filled(
1667 backdrop,
1668 DOOR_PROMPT_PADDING,
1669 egui::Color32::from_black_alpha(DOOR_PROMPT_BACKDROP),
1670 );
1671 painter.galley(pos, galley, DOOR_PROMPT_COLOR);
1672 });
1673 }
1674
1675 /// The gem, spinning and bobbing over the chamber's floor, and the light
1676 /// it casts over it.
1677 fn draw_gem(&self, ctx: &mut FrameContext<'_, Keep>) {
1678 let t = self.simulated.as_secs_f32();
1679 let bob = (t * 2.0).sin() * GEM_BOB_HEIGHT;
1680 ctx.light(
1681 Light::point(
1682 GEM_POSITION + Vec3::Y * (bob + GEM_LIGHT_LIFT),
1683 GEM_LIGHT_COLOR,
1684 GEM_LIGHT_RANGE,
1685 )
1686 .shadow(),
1687 );
1688 ctx.draw(
1689 Gem.at(Transform::from_scale_rotation_translation(
1690 Vec3::ONE,
1691 Quat::from_rotation_y(t * GEM_SPIN_SPEED),
1692 GEM_POSITION + Vec3::Y * bob,
1693 ))
1694 .material(Material::shaded(GEM_COLOR, 0.7).emissive(GEM_COLOR.dimmed(1.6))),
1695 );
1696 }
1697
1698 /// The player: upright so it always faces the camera about `+Y`,
1699 /// windowed to its facing's row and the walk cycle's current frame.
1700 fn draw_walker(&self, ctx: &mut FrameContext<'_, Keep>, ground: Vec3) {
1701 let step = if self.walk_ticks > 0 {
1702 (self.walk_ticks / TICKS_PER_WALK_FRAME) % WALKER_COLUMNS
1703 } else {
1704 0
1705 };
1706 let cell = Sheet::new(UVec2::new(WALKER_COLUMNS, WALKER_ROWS))
1707 .cell_at(UVec2::new(step, self.facing as u32));
1708 let size = Vec2::new(WALKER_WIDTH, WALKER_HEIGHT);
1709
1710 ctx.draw(
1711 Walker
1712 .at(Transform::from_scale_rotation_translation(
1713 size.extend(1.0),
1714 Quat::IDENTITY,
1715 ground + Vec3::Y * (WALKER_HEIGHT * 0.5),
1716 ))
1717 .upright()
1718 .frame(cell),
1719 );
1720 }188fn facing_marker() -> MeshData {
189 const TIP: Vec3 = Vec3::new(0.0, 0.0, -0.5);
190 const BACK: [Vec3; 4] = [
191 Vec3::new(-0.5, -0.5, 0.5),
192 Vec3::new(0.5, -0.5, 0.5),
193 Vec3::new(0.5, 0.5, 0.5),
194 Vec3::new(-0.5, 0.5, 0.5),
195 ];
196
197 let mut vertices = Vec::with_capacity(BACK.len() * 3);
198 for (corner, next) in BACK.iter().zip(BACK.iter().cycle().skip(1)) {
199 let normal = (next - corner).cross(TIP - corner).normalize();
200 vertices.extend([
201 Vertex::new(*corner, normal, Vec2::new(0.0, 1.0)),
202 Vertex::new(*next, normal, Vec2::new(1.0, 1.0)),
203 Vertex::new(TIP, normal, Vec2::new(0.5, 0.0)),
204 ]);
205 }
206 let indices = (0..vertices.len() as u32).collect();
207 MeshData::new(vertices, indices)
208}
209
210/// Every sound this game plays. [`Sound::Break`] and [`Sound::Pulse`] read the
211/// same source under two names, so sustaining one and playing the other
212/// once never share a voice; [`Sound::Theme`] and [`Sound::ThemeDecoded`] do
213/// the same for the streamed side against the decoded one, since a clip
214/// decodes one way or the other for good, once built.
215#[derive(Catalog, Clone, Copy, PartialEq, Eq, Hash)]
216enum Sound {
217 Bounce,
218 Break,
219 Serve,
220 GameOver,
221 Lost,
222 Win,
223 Click,
224 Theme,
225 ThemeDecoded,
226 MenuTheme,
227 Pulse,
228}
229
230impl Sound {
231 /// The alternatives a one-shot play offers.
232 const ONE_SHOTS: [Sound; 7] = [
233 Sound::Bounce,
234 Sound::Break,
235 Sound::Serve,
236 Sound::GameOver,
237 Sound::Lost,
238 Sound::Win,
239 Sound::Click,
240 ];
241
242 /// The alternatives one source's sustain offers.
243 const SOURCE_CHOICES: [Sound; 9] = [
244 Sound::Bounce,
245 Sound::Break,
246 Sound::Serve,
247 Sound::GameOver,
248 Sound::Lost,
249 Sound::Win,
250 Sound::Click,
251 Sound::Theme,
252 Sound::ThemeDecoded,
253 ];
254
255 fn label(self) -> &'static str {
256 match self {
257 Sound::Bounce => "bounce",
258 Sound::Break => "break",
259 Sound::Serve => "serve",
260 Sound::GameOver => "game over",
261 Sound::Lost => "lost",
262 Sound::Win => "win",
263 Sound::Click => "click",
264 Sound::Theme => "theme (streamed)",
265 Sound::ThemeDecoded => "theme (decoded)",
266 Sound::MenuTheme => "menu theme",
267 Sound::Pulse => "pulse",
268 }
269 }
270}
271
272impl Sounds for Sound {
273 fn build(&self, assets: &Assets) -> SoundData {
274 match self {
275 Sound::Bounce => assets.sound("bounce"),
276 Sound::Break => assets.sound("break"),
277 Sound::Serve => assets.sound("serve"),
278 Sound::GameOver => assets.sound("gameover"),
279 Sound::Lost => assets.sound("lost"),
280 Sound::Win => assets.sound("win"),
281 Sound::Click => assets.sound("click"),
282 Sound::Theme => assets.sound("music").streamed(),
283 Sound::ThemeDecoded => assets.sound("music"),
284 Sound::MenuTheme => assets.sound("menu_music").streamed(),
285 Sound::Pulse => assets.sound("break"),
286 }
287 }
288}
289
290/// The one button this game reads: it holds a source down and moves it.
291#[derive(InputButtonAction, Clone, Copy, PartialEq)]
292enum Button {
293 Select,
294}
295
296impl InputButtonAction for Button {
297 fn bindings(&self) -> Vec<ButtonBinding> {
298 match self {
299 Button::Select => vec![MouseButton::Left.into()],
300 }
301 }
302}
303
304/// The listener's walk, in the ground plane.
305#[derive(InputAxis2Action, Clone, Copy, PartialEq)]
306enum Move {
307 Walk,
308}
309
310impl InputAxis2Action for Move {
311 fn bindings(&self) -> Vec<Axis2Binding> {
312 match self {
313 Move::Walk => vec![
314 Axis2Binding::from(ButtonAxis2 {
315 left: Key::A,
316 right: Key::D,
317 down: Key::S,
318 up: Key::W,
319 }),
320 Axis2Binding::stick(Stick::Left),
321 ],
322 }
323 }
324}
325
326struct Controls;
327
328impl InputActions for Controls {
329 type Button = Button;
330 type Axis = NoInputAxes;
331 type Axis2 = Move;
332}
333
334/// One source a drag moves: a cube on the ground, playing a sustained clip
335/// with its own gain, reference, range, and pitch.
336struct Source {
337 position: Vec3,
338 sound: Sound,
339 gain: f32,
340 reference: f32,
341 range: f32,
342 pitch: f32,
343 /// Whether this source sustains at all; off keeps startup silent.
344 enabled: bool,
345}
346
347impl Source {
348 fn new(x: f32, z: f32, sound: Sound, range: f32, enabled: bool) -> Self {
349 Self {
350 position: Vec3::new(x, SOURCE_HEIGHT, z),
351 sound,
352 gain: 0.5,
353 reference: SOURCE_REFERENCE,
354 range,
355 pitch: 1.0,
356 enabled,
357 }
358 }
359
360 /// This source's sustained cue, with the loop point that seeks far
361 /// where its choice needs one.
362 fn cue(&self) -> SoundCue<Sound> {
363 let cue = self
364 .sound
365 .at(self.position)
366 .gain(self.gain)
367 .reference(self.reference)
368 .range(self.range)
369 .pitch(self.pitch);
370 match self.sound {
371 Sound::Theme | Sound::ThemeDecoded => cue.loop_from(THEME_LOOP_FROM),
372 _ => cue,
373 }
374 }
375}
376
377struct SoundCheck {
378 master_volume: f32,
379
380 picked: Sound,
381 one_shot_gain: f32,
382 one_shot_pitch: f32,
383 one_shot_fade: f32,
384 trim_start: f32,
385 trim_end: f32,
386 one_shot_loop_from: f32,
387
388 theme_on: bool,
389 menu_on: bool,
390 pulse_on: bool,
391 cue_fade: f32,
392
393 /// Sustains [`Sound::Click`] at [`MERGE_POS_A`] and [`MERGE_POS_B`]
394 /// both at the default instance: shows the merge each source's own
395 /// instance above keeps clear of.
396 merge_demo: bool,
397
398 /// Declares [`RING_COUNT`] sustains at once, more than the engine
399 /// plays, so that the cap is heard as it allocates by level.
400 ring_demo: bool,
401
402 player: Vec2,
403 player_prev: Vec2,
404 sources: [Source; 3],
405 dragging: Option<usize>,
406
407 /// Every catalog value's length, read once at startup.
408 durations: HashMap<Sound, Duration>,
409}
410
411impl SoundCheck {
412 fn init(ctx: &mut InitContext<'_, SoundCheck>) -> Result<Self, Error> {
413 let durations = ctx.durations();
414
415 let picked = Sound::Bounce;
416 let trim_end = durations.get(&picked).copied().unwrap_or_default();
417
418 Ok(Self {
419 master_volume: 1.0,
420
421 picked,
422 one_shot_gain: 1.0,
423 one_shot_pitch: 1.0,
424 one_shot_fade: SoundCue::<Sound>::DEFAULT_FADE.as_secs_f32(),
425 trim_start: 0.0,
426 trim_end: trim_end.as_secs_f32(),
427 one_shot_loop_from: 0.0,
428
429 theme_on: false,
430 menu_on: false,
431 pulse_on: false,
432 cue_fade: 1.0,
433
434 merge_demo: false,
435 ring_demo: false,
436
437 player: Vec2::ZERO,
438 player_prev: Vec2::ZERO,
439 sources: [
440 Source::new(-2.5, -2.0, Sound::Bounce, 4.0, false),
441 Source::new(2.5, -2.0, Sound::Serve, 4.0, false),
442 Source::new(0.0, 2.8, Sound::Theme, 7.0, true),
443 ],
444 dragging: None,
445
446 durations,
447 })
448 }
449
450 fn camera(player: Vec2) -> Camera {
451 let ground = Vec3::new(player.x, 0.0, player.y);
452 Camera::new(
453 View::look_at(
454 ground + Vec3::new(0.0, CHASE_UP, CHASE_BACK),
455 ground + Vec3::Y * 0.5,
456 ),
457 Projection::perspective(55.0),
458 )
459 }
460
461 fn handle_walk(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
462 self.player_prev = self.player;
463 if ctx.ui_wants_keyboard() {
464 return;
465 }
466 let walk = ctx.axis2(Move::Walk);
467 let world = Vec2::new(walk.x, -walk.y);
468 self.player = (self.player + world * WALK_SPEED * ctx.dt().as_secs_f32())
469 .clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
470 }
471
472 /// Takes hold of the source a click's ray intersects, moves it across
473 /// the floor while the button stays down, and frees it on release.
474 fn handle_drag(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
475 // Read before the check below for the UI's own claim on the
476 // pointer, so a release over it still frees a source a drag moved
477 // there.
478 if ctx.released(Button::Select) {
479 self.dragging = None;
480 }
481 if ctx.ui_wants_pointer() {
482 return;
483 }
484 let ray = ctx
485 .last_camera()
486 .ray_through(ctx.pointer(), ctx.window_size());
487
488 if ctx.pressed(Button::Select) {
489 self.dragging = self.sources.iter().position(|source| {
490 ray.hit_sphere(source.position, SOURCE_PICK_RADIUS)
491 .is_some()
492 });
493 }
494
495 let Some(index) = self.dragging else {
496 return;
497 };
498 let Some(distance) = ray.hit_plane(ray::Plane {
499 point: Vec3::ZERO,
500 normal: Vec3::Y,
501 }) else {
502 return;
503 };
504 let hit = ray.at(distance);
505 let dropped =
506 Vec2::new(hit.x, hit.z).clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
507 self.sources[index].position = Vec3::new(dropped.x, SOURCE_HEIGHT, dropped.y);
508 }360fn 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}724fn build_rock(seed: u32) -> MeshData {
725 let corners: [Vec3; 8] = core::array::from_fn(|index| {
726 let sign = Vec3::new(
727 if index & 1 == 0 { -0.5 } else { 0.5 },
728 if index & 2 == 0 { -0.5 } else { 0.5 },
729 if index & 4 == 0 { -0.5 } else { 0.5 },
730 );
731 sign + corner_offset(seed, index as u32)
732 });
733 let corner_at = |sign: Vec3| corners[corner_index(sign)];
734
735 let mut vertices = Vec::with_capacity(ROCK_FACES.len() * 4);
736 let mut indices = Vec::with_capacity(ROCK_FACES.len() * 6);
737 for (face, &(normal, right, up)) in ROCK_FACES.iter().enumerate() {
738 let quad = [
739 corner_at(normal - right - up),
740 corner_at(normal + right - up),
741 corner_at(normal + right + up),
742 corner_at(normal - right + up),
743 ];
744 let normal = (quad[1] - quad[0]).cross(quad[3] - quad[0]).normalize();
745 let uvs = [
746 Vec2::new(0.0, 1.0),
747 Vec2::new(1.0, 1.0),
748 Vec2::new(1.0, 0.0),
749 Vec2::new(0.0, 0.0),
750 ];
751 vertices.extend(
752 quad.into_iter()
753 .zip(uvs)
754 .map(|(corner, uv)| Vertex::new(corner, normal, uv)),
755 );
756 let base = face as u32 * 4;
757 indices.extend(ROCK_TRIANGLES.map(|index| base + index));
758 }
759 MeshData::new(vertices, indices)
760}Sourcepub const fn splat(v: f32) -> Vec2
pub const fn splat(v: f32) -> Vec2
Creates a vector with all elements set to v.
Examples found in repository?
461 fn handle_walk(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
462 self.player_prev = self.player;
463 if ctx.ui_wants_keyboard() {
464 return;
465 }
466 let walk = ctx.axis2(Move::Walk);
467 let world = Vec2::new(walk.x, -walk.y);
468 self.player = (self.player + world * WALK_SPEED * ctx.dt().as_secs_f32())
469 .clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
470 }
471
472 /// Takes hold of the source a click's ray intersects, moves it across
473 /// the floor while the button stays down, and frees it on release.
474 fn handle_drag(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
475 // Read before the check below for the UI's own claim on the
476 // pointer, so a release over it still frees a source a drag moved
477 // there.
478 if ctx.released(Button::Select) {
479 self.dragging = None;
480 }
481 if ctx.ui_wants_pointer() {
482 return;
483 }
484 let ray = ctx
485 .last_camera()
486 .ray_through(ctx.pointer(), ctx.window_size());
487
488 if ctx.pressed(Button::Select) {
489 self.dragging = self.sources.iter().position(|source| {
490 ray.hit_sphere(source.position, SOURCE_PICK_RADIUS)
491 .is_some()
492 });
493 }
494
495 let Some(index) = self.dragging else {
496 return;
497 };
498 let Some(distance) = ray.hit_plane(ray::Plane {
499 point: Vec3::ZERO,
500 normal: Vec3::Y,
501 }) else {
502 return;
503 };
504 let hit = ray.at(distance);
505 let dropped =
506 Vec2::new(hit.x, hit.z).clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
507 self.sources[index].position = Vec3::new(dropped.x, SOURCE_HEIGHT, dropped.y);
508 }More examples
480 fn push_out(self, point: Vec2, radius: f32) -> Vec2 {
481 let delta = point - self.center;
482 let escape = self.half + Vec2::splat(radius) - delta.abs();
483
484 if escape.min_element() <= 0.0 {
485 point
486 } else if escape.x < escape.y {
487 let x = self.center.x + delta.x.signum() * (self.half.x + radius);
488 Vec2::new(x, point.y)
489 } else {
490 let z = self.center.y + delta.y.signum() * (self.half.y + radius);
491 Vec2::new(point.x, z)
492 }
493 }
494}
495
496/// A cave mouth's opening: where it is, and which way along `Z` the room
497/// it leads out of lies.
498#[derive(Clone, Copy)]
499struct Mouth {
500 at: Vec3,
501 room_side: f32,
502}
503
504impl Mouth {
505 /// True where `position` lies between the pillars and past the plane
506 /// in front of the opening — where the only two outcomes are stepping
507 /// through and being held.
508 fn holds(&self, position: Vec3) -> bool {
509 let plane = self.at.z + self.room_side * MOUTH_CROSSING_INSET;
510
511 (position.x - self.at.x).abs() < MOUTH_OPENING_HALF
512 && (plane - position.z) * self.room_side > 0.0
513 }
514
515 /// Position of each of the two pillars flanking the opening.
516 fn pillars(&self) -> impl Iterator<Item = Vec3> + Clone {
517 let at = self.at;
518
519 SIDES
520 .into_iter()
521 .map(move |side| at + Vec3::X * (side * MOUTH_PILLAR_OFFSET))
522 }
523
524 /// True when the camera looks into this mouth: it always lies on `+Z`
525 /// of the player, so the mouth whose room lies that way is the one seen
526 /// from the room's side, and the one to fill with a lintel and the dark
527 /// under it. The other is looked through from behind, and leaves its
528 /// opening clear for the room to show through.
529 fn looked_into(&self) -> bool {
530 self.room_side > 0.0
531 }
532}
533
534// ---------------------------------------------------------------------
535// The water style
536// ---------------------------------------------------------------------
537
538/// The pond's whole look, over the one value it reads: how far its ripple
539/// has traveled.
540#[derive(Default, ShaderValues)]
541struct Water {
542 time: f32,
543}
544
545impl SurfaceStyle for Water {
546 const PASS: DrawPass = DrawPass::Translucent;
547 const SURFACE: Option<&'static str> = Some(include_str!("sprite_adventure_water.wgsl"));
548}
549
550surface_styles! { enum Looks { Water } }
551
552// ---------------------------------------------------------------------
553// Meshes
554// ---------------------------------------------------------------------
555
556/// The player's current area; never both drawn in one frame.
557#[derive(Clone, Copy, PartialEq, Eq)]
558enum Area {
559 Overworld,
560 Cave,
561}
562
563/// The overworld's ground tile, its texture the only thing that separates a
564/// draw of it from another.
565#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
566struct Ground;
567
568impl Mesh for Ground {
569 fn build(&self, assets: &Assets) -> MeshData {
570 Plane
571 .build(assets)
572 .with_texture(assets.texture(GROUND_SHEET).pixelated())
573 }
574}
575
576/// The shoreline sprite laid over the pond's styled water, cutout so the
577/// water shows through its cleared middle.
578#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
579struct Shore;
580
581impl Mesh for Shore {
582 fn build(&self, assets: &Assets) -> MeshData {
583 Plane
584 .build(assets)
585 .with_texture(assets.texture(POND_SHEET).pixelated())
586 .with_material(Material::lit(Color::WHITE).cutout())
587 }
588}
589
590/// A crate prop, its texture drawn over a cube.
591#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
592struct Crate;
593
594impl Mesh for Crate {
595 fn build(&self, assets: &Assets) -> MeshData {
596 Cube.build(assets)
597 .with_texture(assets.texture(CRATE_TEXTURE).pixelated())
598 }
599}
600
601/// The well's rim.
602#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
603struct Well;
604
605impl Mesh for Well {
606 fn build(&self, assets: &Assets) -> MeshData {
607 Cube.build(assets)
608 .with_texture(assets.texture(WELL_SHEET).pixelated())
609 }
610}
611
612/// The well's mouth, laid flat over the rim's top face.
613#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
614struct WellMouth;
615
616impl Mesh for WellMouth {
617 fn build(&self, assets: &Assets) -> MeshData {
618 Plane
619 .build(assets)
620 .with_texture(assets.texture(WELL_SHEET).pixelated())
621 }
622}
623
624/// A stone box: the mouth's pillars and lintel.
625#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
626struct Stone;
627
628impl Mesh for Stone {
629 fn build(&self, assets: &Assets) -> MeshData {
630 Cube.build(assets)
631 .with_texture(assets.texture(STONE_SHEET).pixelated())
632 }
633}
634
635/// A bush sprite, cutout with its own relief.
636#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
637struct Bush;
638
639impl Mesh for Bush {
640 fn build(&self, assets: &Assets) -> MeshData {
641 Quad.build(assets)
642 .with_texture(assets.texture(BUSH_SPRITE).pixelated())
643 .with_relief(assets.relief(BUSH_RELIEF))
644 .with_material(Material::lit(Color::WHITE).cutout())
645 }
646}
647
648/// A rock sprite, cutout with its own relief.
649#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
650struct Rock;
651
652impl Mesh for Rock {
653 fn build(&self, assets: &Assets) -> MeshData {
654 Quad.build(assets)
655 .with_texture(assets.texture(ROCK_SPRITE).pixelated())
656 .with_relief(assets.relief(ROCK_RELIEF))
657 .with_material(Material::lit(Color::WHITE).cutout())
658 }
659}
660
661/// A torch's post sprite, cutout with its own relief.
662#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
663struct Torch;
664
665impl Mesh for Torch {
666 fn build(&self, assets: &Assets) -> MeshData {
667 Quad.build(assets)
668 .with_texture(assets.texture(TORCH_SPRITE).pixelated())
669 .with_relief(assets.relief(TORCH_RELIEF))
670 .with_material(Material::lit(Color::WHITE).cutout())
671 }
672}
673
674/// A torch's flame sprite, added over the dark rather than lit.
675#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
676struct Flame;
677
678impl Mesh for Flame {
679 fn build(&self, assets: &Assets) -> MeshData {
680 Quad.build(assets)
681 .with_texture(assets.texture(FLAME_SHEET).pixelated())
682 .with_material(Material::color(FLAME_TINT).additive())
683 }
684}
685
686/// The player's sprite, cutout with its own relief, its sheet shared
687/// with `examples/isometric-board.rs`.
688#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
689struct Walker;
690
691impl Mesh for Walker {
692 fn build(&self, assets: &Assets) -> MeshData {
693 Quad.build(assets)
694 .with_texture(assets.texture(WALKER_SHEET).pixelated())
695 .with_relief(assets.relief(WALKER_RELIEF))
696 .with_material(Material::lit(Color::WHITE).cutout())
697 }
698}
699
700/// The cave floor tile.
701#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
702struct CaveFloor;
703
704impl Mesh for CaveFloor {
705 fn build(&self, assets: &Assets) -> MeshData {
706 Plane
707 .build(assets)
708 .with_texture(assets.texture(CAVE_SHEET).pixelated())
709 }
710}
711
712/// The cave wall face.
713#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
714struct CaveWall;
715
716impl Mesh for CaveWall {
717 fn build(&self, assets: &Assets) -> MeshData {
718 Cube.build(assets)
719 .with_texture(assets.texture(CAVE_SHEET).pixelated())
720 }
721}
722
723/// The loaded door, drawn as its source authored it.
724#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
725struct Door;
726
727impl Mesh for Door {
728 fn build(&self, assets: &Assets) -> MeshData {
729 assets.mesh(DOOR_MESH)
730 }
731}
732
733/// The loaded gem, repainted whole per draw so its glow color shifts.
734#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
735struct Gem;
736
737impl Mesh for Gem {
738 fn build(&self, assets: &Assets) -> MeshData {
739 assets.mesh(GEM_MESH)
740 }
741}
742
743// Everything this game can draw: the meshes above, plus the styled water,
744// the dark filling a looked-into mouth's opening, and the door's own frame,
745// which draw the bare engine primitives Plane, Quad and Cube.
746meshes! {
747 enum Shape {
748 Ground, Shore, Crate, Well, WellMouth, Stone, Bush, Rock, Torch,
749 Flame, Walker, CaveFloor, CaveWall, Door, Gem, Plane, Quad, Cube,
750 }
751}
752
753/// The interact click and the gem's chime, shared with the other examples.
754#[derive(Catalog, Clone, PartialEq, Eq, Hash)]
755enum Sound {
756 Interact,
757 Gem,
758}
759
760impl Sounds for Sound {
761 fn build(&self, assets: &Assets) -> SoundData {
762 match self {
763 Sound::Interact => assets.sound("click"),
764 Sound::Gem => assets.sound("win"),
765 }
766 }
767}
768
769// ---------------------------------------------------------------------
770// Input
771// ---------------------------------------------------------------------
772
773/// Player movement: `WASD`, arrows, or a stick — the strongest reading is
774/// kept.
775#[derive(InputAxis2Action, Clone, Copy, PartialEq)]
776enum Move {
777 Walk,
778}
779
780impl InputAxis2Action for Move {
781 fn bindings(&self) -> Vec<Axis2Binding> {
782 match self {
783 Move::Walk => vec![
784 Axis2Binding::from(ButtonAxis2 {
785 left: Key::A,
786 right: Key::D,
787 down: Key::S,
788 up: Key::W,
789 }),
790 Axis2Binding::from(ButtonAxis2 {
791 left: Key::Left,
792 right: Key::Right,
793 down: Key::Down,
794 up: Key::Up,
795 }),
796 Axis2Binding::stick(Stick::Left),
797 ],
798 }
799 }
800}
801
802/// The two verbs this game reads as an edge: interacting with the door, and
803/// a reset of the world to every saved key's fallback.
804#[derive(InputButtonAction, Clone, Copy, PartialEq)]
805enum Button {
806 Interact,
807 Reset,
808}
809
810impl InputButtonAction for Button {
811 fn bindings(&self) -> Vec<ButtonBinding> {
812 match self {
813 Button::Interact => vec![Key::E.into(), Pad::West.into()],
814 Button::Reset => vec![Key::R.into()],
815 }
816 }
817}
818
819struct Controls;
820
821impl InputActions for Controls {
822 type Button = Button;
823 type Axis = NoInputAxes;
824 type Axis2 = Move;
825}
826
827// ---------------------------------------------------------------------
828// Save data
829// ---------------------------------------------------------------------
830
831/// The player's last position, read at startup and saved on area
832/// transition and gem pickup.
833#[derive(Saves, Clone, Copy)]
834enum Position {
835 X,
836 Z,
837}
838
839impl SaveKey for Position {
840 type Value = f64;
841
842 fn fallback(&self) -> f64 {
843 match self {
844 Position::X => PLAYER_SPAWN.x as f64,
845 Position::Z => PLAYER_SPAWN.z as f64,
846 }
847 }
848}
849
850/// The area the player is in, and whether the gem is taken.
851#[derive(Saves, Clone, Copy)]
852enum Flag {
853 InCave,
854 GemTaken,
855}
856
857impl SaveKey for Flag {
858 type Value = bool;
859
860 fn fallback(&self) -> bool {
861 false
862 }
863}
864
865// ---------------------------------------------------------------------
866// The player's facing
867// ---------------------------------------------------------------------
868
869/// The player's last facing: also its row in the sheet, top to bottom.
870#[derive(Clone, Copy, PartialEq)]
871enum Facing {
872 Toward = 0,
873 Right = 1,
874 Away = 2,
875 Left = 3,
876}
877
878impl Facing {
879 /// The facing `heading` points in, favoring its larger axis; `None` at
880 /// rest, so the caller can keep the last facing.
881 fn from_heading(heading: Vec2) -> Option<Self> {
882 if heading == Vec2::ZERO {
883 return None;
884 }
885 Some(if heading.x.abs() > heading.y.abs() {
886 if heading.x > 0.0 {
887 Self::Right
888 } else {
889 Self::Left
890 }
891 } else if heading.y > 0.0 {
892 Self::Away
893 } else {
894 Self::Toward
895 })
896 }
897}
898
899// ---------------------------------------------------------------------
900// The game
901// ---------------------------------------------------------------------
902
903/// The ground tile at `col, row`: the path's dirt along [`PATH_COLUMN`],
904/// the verges that edge it, and a hashed grass variant everywhere else.
905fn ground_cell(col: i32, row: i32) -> Frame {
906 let (column, sheet_row) = match col - PATH_COLUMN {
907 0 => (PATH_DIRT + row.rem_euclid(2) as u32, PATH_ROW),
908 -1 => (PATH_WEST_VERGE, PATH_ROW),
909 1 => (PATH_EAST_VERGE, PATH_ROW),
910 _ => (
911 (col * 31 + row * 17).rem_euclid(GROUND_COLUMNS as i32) as u32,
912 GRASS_ROW,
913 ),
914 };
915
916 Sheet::new(UVec2::new(GROUND_COLUMNS, GROUND_ROWS)).cell_at(UVec2::new(column, sheet_row))
917}
918
919/// The stone sheet's plain masonry, laid `tiles` times across: the sampler
920/// wraps, so a window wider than the sheet repeats the course.
921fn masonry(tiles: f32) -> Frame {
922 let course = 1.0 / STONE_ROWS as f32;
923
924 Frame::rect(Vec2::new(0.0, 1.0 - course), Vec2::new(tiles, 1.0))
925}
926
927/// The wall or door's alpha `fraction` of the way from [`SOLID`] to
928/// [`GHOST_ALPHA`].
929fn ghost_alpha(fraction: f32) -> f32 {
930 SOLID + (GHOST_ALPHA - SOLID) * fraction
931}
932
933/// The wall face in column `variant`, windowed to the meters `standing` of
934/// one course, measured up from that course's own base: every row of the
935/// cave sheet below the floor's covers [`WALL_HEIGHT`], so a course keeps
936/// the floor's texels to the meter however it is cut.
937fn cave_wall_face(variant: u32, standing: Range<f32>) -> Frame {
938 let cell = Vec2::new(1.0 / CAVE_COLUMNS as f32, 1.0 / CAVE_ROWS as f32);
939 let left = (variant % CAVE_COLUMNS) as f32 * cell.x;
940 let face = (CAVE_FLOOR_ROW + 1) as f32 * cell.y;
941 let up_from_base = |height: f32| 1.0 - (1.0 - face) * (height / WALL_HEIGHT);
942
943 Frame::rect(
944 Vec2::new(left, up_from_base(standing.end)),
945 Vec2::new(left + cell.x, up_from_base(standing.start)),
946 )
947}
948
949/// The logical point egui paints the physical pixel `pixel` at.
950fn logical(pixel: Vec2, pixels_per_point: f32) -> egui::Pos2 {
951 let point = pixel / pixels_per_point;
952 egui::pos2(point.x, point.y)
953}
954
955fn main() {
956 run(
957 Config::new("Mirage: sprite adventure")
958 .with_size(1280, 720)
959 .with_assets([
960 MODEL,
961 WALKER_SOURCE,
962 WALKER_RELIEF_SOURCE,
963 GROUND_SOURCE,
964 BUSH_SOURCE,
965 BUSH_RELIEF_SOURCE,
966 ROCK_SOURCE,
967 ROCK_RELIEF_SOURCE,
968 TORCH_RELIEF_SOURCE,
969 CRATE_SOURCE,
970 WELL_SOURCE,
971 STONE_SOURCE,
972 CAVE_SOURCE,
973 POND_SOURCE,
974 TORCH_SOURCE,
975 FLAME_SOURCE,
976 INTERACT_SOUND,
977 GEM_SOUND,
978 ]),
979 Keep::init,
980 );
981}
982
983struct Keep {
984 area: Area,
985 position: Vec3,
986 previous: Vec3,
987 facing: Facing,
988 walk_ticks: u32,
989 simulated: Duration,
990 door_opening: bool,
991 /// Ticks the door has been opening for, at a cap of
992 /// [`DOOR_SWING_TICKS`]: how long its world prompt reads "opening" once
993 /// it starts.
994 swing_ticks: u32,
995 gem_taken: bool,
996 /// How far the door wall's fade from [`SOLID`] to [`GHOST_ALPHA`] has
997 /// run as of the last tick: `0.0` to `1.0`.
998 ghost: f32,
999 /// Set by the panel's reset button, since its click lands in a frame
1000 /// rather than a tick; read and cleared on the next tick.
1001 reset_requested: bool,
1002}
1003
1004impl Keep {
1005 /// Prepares every startup-cataloged mesh and resumes wherever the last
1006 /// run left the player.
1007 fn init(ctx: &mut InitContext<'_, Keep>) -> Result<Self, Error> {
1008 let startup = ctx.startup();
1009 let gem_taken = startup.saved(Flag::GemTaken);
1010 let area = if startup.saved(Flag::InCave) {
1011 Area::Cave
1012 } else {
1013 Area::Overworld
1014 };
1015 let position = Vec3::new(
1016 startup.saved(Position::X) as f32,
1017 0.0,
1018 startup.saved(Position::Z) as f32,
1019 );
1020
1021 Ok(Self {
1022 area,
1023 position,
1024 previous: position,
1025 facing: Facing::Toward,
1026 walk_ticks: 0,
1027 simulated: Duration::ZERO,
1028 door_opening: gem_taken,
1029 swing_ticks: if gem_taken { DOOR_SWING_TICKS } else { 0 },
1030 gem_taken,
1031 ghost: 0.0,
1032 reset_requested: false,
1033 })
1034 }
1035
1036 fn camera(position: Vec3, offset: Vec3) -> Camera {
1037 Camera::new(
1038 View::look_at(position + offset, position),
1039 Projection::perspective(CAMERA_FOV),
1040 )
1041 }
1042
1043 /// Obstacles from the overworld's props: the crates, turned as they are
1044 /// drawn, the well's rim, the open water the shoreline rings, the
1045 /// mouth's pillars, and each flora's base.
1046 fn overworld_obstacles() -> impl Iterator<Item = Obstacle> + Clone {
1047 CRATE_POSITIONS
1048 .into_iter()
1049 .map(|(x, z, turn)| {
1050 Obstacle::footprint(Vec2::new(x, z), Vec2::splat(CRATE_SIZE * turned_span(turn)))
1051 })
1052 .chain([
1053 Obstacle::footprint(WELL_POSITION.xz(), WELL_SIZE.xz()),
1054 Obstacle::footprint(POND_CENTER.xz(), Vec2::splat(POND_WATER_HALF * 2.0)),
1055 ])
1056 .chain(
1057 ENTRANCE
1058 .pillars()
1059 .map(|at| Obstacle::footprint(at.xz(), MOUTH_PILLAR_SIZE.xz())),
1060 )
1061 .chain(FLORA.into_iter().map(|(x, z, rock)| {
1062 let base = if rock { ROCK_FOOTPRINT } else { BUSH_FOOTPRINT };
1063 Obstacle::footprint(Vec2::new(x, z), Vec2::splat(base))
1064 }))
1065 }
1066
1067 /// Obstacles from the cave: the torch posts, its own mouth's pillars,
1068 /// the runs of wall either side of the doorway and of the mouth, and the
1069 /// `door` leaf.
1070 fn cave_obstacles(door: Obstacle) -> impl Iterator<Item = Obstacle> + Clone {
1071 TORCH_POSITIONS
1072 .into_iter()
1073 .map(|(x, z)| Obstacle::footprint(Vec2::new(x, z), Vec2::splat(TORCH_STAND_WIDTH)))
1074 .chain(
1075 EXIT.pillars()
1076 .map(|at| Obstacle::footprint(at.xz(), MOUTH_PILLAR_SIZE.xz())),
1077 )
1078 .chain(SIDES.into_iter().flat_map(|side| {
1079 [
1080 Self::wall_run(side, DOOR_Z),
1081 Self::wall_run(side, CAVE_LIP_Z),
1082 ]
1083 }))
1084 .chain([door])
1085 }Sourcepub fn map<F>(self, f: F) -> Vec2
pub fn map<F>(self, f: F) -> Vec2
Returns a vector containing each element of self modified by a mapping function f.
Sourcepub fn select(mask: BVec2, if_true: Vec2, if_false: Vec2) -> Vec2
pub fn select(mask: BVec2, if_true: Vec2, if_false: Vec2) -> Vec2
Creates a vector from the elements in if_true and if_false, selecting which to use
for each element of self.
A true element in the mask uses the corresponding element from if_true, and false
uses the element from if_false.
Sourcepub const fn from_array(a: [f32; 2]) -> Vec2
pub const fn from_array(a: [f32; 2]) -> Vec2
Creates a new vector from an array.
Sourcepub const fn from_slice(slice: &[f32]) -> Vec2
pub const fn from_slice(slice: &[f32]) -> Vec2
Creates a vector from the first 2 values in slice.
§Panics
Panics if slice is less than 2 elements long.
Sourcepub fn write_to_slice(self, slice: &mut [f32])
pub fn write_to_slice(self, slice: &mut [f32])
Writes the elements of self to the first 2 elements in slice.
§Panics
Panics if slice is less than 2 elements long.
Sourcepub const fn extend(self, z: f32) -> Vec3
pub const fn extend(self, z: f32) -> Vec3
Creates a 3D vector from self and the given z value.
Examples found in repository?
1700 fn draw_walker(&self, ctx: &mut FrameContext<'_, Keep>, ground: Vec3) {
1701 let step = if self.walk_ticks > 0 {
1702 (self.walk_ticks / TICKS_PER_WALK_FRAME) % WALKER_COLUMNS
1703 } else {
1704 0
1705 };
1706 let cell = Sheet::new(UVec2::new(WALKER_COLUMNS, WALKER_ROWS))
1707 .cell_at(UVec2::new(step, self.facing as u32));
1708 let size = Vec2::new(WALKER_WIDTH, WALKER_HEIGHT);
1709
1710 ctx.draw(
1711 Walker
1712 .at(Transform::from_scale_rotation_translation(
1713 size.extend(1.0),
1714 Quat::IDENTITY,
1715 ground + Vec3::Y * (WALKER_HEIGHT * 0.5),
1716 ))
1717 .upright()
1718 .frame(cell),
1719 );
1720 }Sourcepub fn dot_into_vec(self, rhs: Vec2) -> Vec2
pub fn dot_into_vec(self, rhs: Vec2) -> Vec2
Returns a vector where every component is the dot product of self and rhs.
Sourcepub fn min(self, rhs: Vec2) -> Vec2
pub fn min(self, rhs: Vec2) -> Vec2
Returns a vector containing the minimum values for each element of self and rhs.
In other words this computes [min(x, rhs.x), min(self.y, rhs.y), ..].
NaN propogation does not follow IEEE 754-2008 semantics for minNum and may differ on different SIMD architectures.
Sourcepub fn max(self, rhs: Vec2) -> Vec2
pub fn max(self, rhs: Vec2) -> Vec2
Returns a vector containing the maximum values for each element of self and rhs.
In other words this computes [max(self.x, rhs.x), max(self.y, rhs.y), ..].
NaN propogation does not follow IEEE 754-2008 semantics for maxNum and may differ on different SIMD architectures.
Sourcepub fn clamp(self, min: Vec2, max: Vec2) -> Vec2
pub fn clamp(self, min: Vec2, max: Vec2) -> Vec2
Component-wise clamping of values, similar to f32::clamp.
Each element in min must be less-or-equal to the corresponding element in max.
NaN propogation does not follow IEEE 754-2008 semantics and may differ on different SIMD architectures.
§Panics
Will panic if min is greater than max when glam_assert is enabled.
Examples found in repository?
461 fn handle_walk(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
462 self.player_prev = self.player;
463 if ctx.ui_wants_keyboard() {
464 return;
465 }
466 let walk = ctx.axis2(Move::Walk);
467 let world = Vec2::new(walk.x, -walk.y);
468 self.player = (self.player + world * WALK_SPEED * ctx.dt().as_secs_f32())
469 .clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
470 }
471
472 /// Takes hold of the source a click's ray intersects, moves it across
473 /// the floor while the button stays down, and frees it on release.
474 fn handle_drag(&mut self, ctx: &mut TickContext<'_, SoundCheck>) {
475 // Read before the check below for the UI's own claim on the
476 // pointer, so a release over it still frees a source a drag moved
477 // there.
478 if ctx.released(Button::Select) {
479 self.dragging = None;
480 }
481 if ctx.ui_wants_pointer() {
482 return;
483 }
484 let ray = ctx
485 .last_camera()
486 .ray_through(ctx.pointer(), ctx.window_size());
487
488 if ctx.pressed(Button::Select) {
489 self.dragging = self.sources.iter().position(|source| {
490 ray.hit_sphere(source.position, SOURCE_PICK_RADIUS)
491 .is_some()
492 });
493 }
494
495 let Some(index) = self.dragging else {
496 return;
497 };
498 let Some(distance) = ray.hit_plane(ray::Plane {
499 point: Vec3::ZERO,
500 normal: Vec3::Y,
501 }) else {
502 return;
503 };
504 let hit = ray.at(distance);
505 let dropped =
506 Vec2::new(hit.x, hit.z).clamp(Vec2::splat(-PLAY_BOUND), Vec2::splat(PLAY_BOUND));
507 self.sources[index].position = Vec3::new(dropped.x, SOURCE_HEIGHT, dropped.y);
508 }Sourcepub fn min_element(self) -> f32
pub fn min_element(self) -> f32
Returns the horizontal minimum of self.
In other words this computes min(x, y, ..).
NaN propogation does not follow IEEE 754-2008 semantics and may differ on different SIMD architectures.
Examples found in repository?
480 fn push_out(self, point: Vec2, radius: f32) -> Vec2 {
481 let delta = point - self.center;
482 let escape = self.half + Vec2::splat(radius) - delta.abs();
483
484 if escape.min_element() <= 0.0 {
485 point
486 } else if escape.x < escape.y {
487 let x = self.center.x + delta.x.signum() * (self.half.x + radius);
488 Vec2::new(x, point.y)
489 } else {
490 let z = self.center.y + delta.y.signum() * (self.half.y + radius);
491 Vec2::new(point.x, z)
492 }
493 }Sourcepub fn max_element(self) -> f32
pub fn max_element(self) -> f32
Returns the horizontal maximum of self.
In other words this computes max(x, y, ..).
NaN propogation does not follow IEEE 754-2008 semantics and may differ on different SIMD architectures.
Sourcepub fn min_position(self) -> usize
pub fn min_position(self) -> usize
Returns the index of the first minimum element of self.
Sourcepub fn max_position(self) -> usize
pub fn max_position(self) -> usize
Returns the index of the first maximum element of self.
Sourcepub fn element_sum(self) -> f32
pub fn element_sum(self) -> f32
Returns the sum of all elements of self.
In other words, this computes self.x + self.y + ...
Sourcepub fn element_product(self) -> f32
pub fn element_product(self) -> f32
Returns the product of all elements of self.
In other words, this computes self.x * self.y * ...
Sourcepub fn cmpeq(self, rhs: Vec2) -> BVec2
pub fn cmpeq(self, rhs: Vec2) -> BVec2
Returns a vector mask containing the result of a == comparison for each element of
self and rhs.
In other words, this computes [self.x == rhs.x, self.y == rhs.y, ..] for all
elements.
Sourcepub fn cmpne(self, rhs: Vec2) -> BVec2
pub fn cmpne(self, rhs: Vec2) -> BVec2
Returns a vector mask containing the result of a != comparison for each element of
self and rhs.
In other words this computes [self.x != rhs.x, self.y != rhs.y, ..] for all
elements.
Sourcepub fn cmpge(self, rhs: Vec2) -> BVec2
pub fn cmpge(self, rhs: Vec2) -> BVec2
Returns a vector mask containing the result of a >= comparison for each element of
self and rhs.
In other words this computes [self.x >= rhs.x, self.y >= rhs.y, ..] for all
elements.
Sourcepub fn cmpgt(self, rhs: Vec2) -> BVec2
pub fn cmpgt(self, rhs: Vec2) -> BVec2
Returns a vector mask containing the result of a > comparison for each element of
self and rhs.
In other words this computes [self.x > rhs.x, self.y > rhs.y, ..] for all
elements.
Sourcepub fn cmple(self, rhs: Vec2) -> BVec2
pub fn cmple(self, rhs: Vec2) -> BVec2
Returns a vector mask containing the result of a <= comparison for each element of
self and rhs.
In other words this computes [self.x <= rhs.x, self.y <= rhs.y, ..] for all
elements.
Sourcepub fn cmplt(self, rhs: Vec2) -> BVec2
pub fn cmplt(self, rhs: Vec2) -> BVec2
Returns a vector mask containing the result of a < comparison for each element of
self and rhs.
In other words this computes [self.x < rhs.x, self.y < rhs.y, ..] for all
elements.
Sourcepub fn abs(self) -> Vec2
pub fn abs(self) -> Vec2
Returns a vector containing the absolute value of each element of self.
Examples found in repository?
480 fn push_out(self, point: Vec2, radius: f32) -> Vec2 {
481 let delta = point - self.center;
482 let escape = self.half + Vec2::splat(radius) - delta.abs();
483
484 if escape.min_element() <= 0.0 {
485 point
486 } else if escape.x < escape.y {
487 let x = self.center.x + delta.x.signum() * (self.half.x + radius);
488 Vec2::new(x, point.y)
489 } else {
490 let z = self.center.y + delta.y.signum() * (self.half.y + radius);
491 Vec2::new(point.x, z)
492 }
493 }Sourcepub fn signum(self) -> Vec2
pub fn signum(self) -> Vec2
Returns a vector with elements representing the sign of self.
1.0if the number is positive,+0.0orINFINITY-1.0if the number is negative,-0.0orNEG_INFINITYNANif the number isNAN
Sourcepub fn copysign(self, rhs: Vec2) -> Vec2
pub fn copysign(self, rhs: Vec2) -> Vec2
Returns a vector with signs of rhs and the magnitudes of self.
Sourcepub fn is_negative_bitmask(self) -> u32
pub fn is_negative_bitmask(self) -> u32
Returns a bitmask with the lowest 2 bits set to the sign bits from the elements of self.
A negative element results in a 1 bit and a positive element in a 0 bit. Element x goes
into the first lowest bit, element y into the second, etc.
An element is negative if it has a negative sign, including -0.0, NaNs with negative sign bit and negative infinity.
Sourcepub fn is_negative_mask(self) -> BVec2
pub fn is_negative_mask(self) -> BVec2
Returns a mask indicating which components are negative.
An element is negative if it has a negative sign, including -0.0, NaNs with negative sign bit and negative infinity.
Sourcepub fn is_finite(self) -> bool
pub fn is_finite(self) -> bool
Returns true if, and only if, all elements are finite. If any element is either
NaN, positive or negative infinity, this will return false.
Sourcepub fn is_finite_mask(self) -> BVec2
pub fn is_finite_mask(self) -> BVec2
Performs is_finite on each element of self, returning a vector mask of the results.
In other words, this computes [x.is_finite(), y.is_finite(), ...].
Sourcepub fn is_nan_mask(self) -> BVec2
pub fn is_nan_mask(self) -> BVec2
Performs is_nan on each element of self, returning a vector mask of the results.
In other words, this computes [x.is_nan(), y.is_nan(), ...].
Sourcepub fn length_squared(self) -> f32
pub fn length_squared(self) -> f32
Computes the squared length of self.
This is faster than length() as it avoids a square root operation.
Sourcepub fn length_recip(self) -> f32
pub fn length_recip(self) -> f32
Computes 1.0 / length().
For valid results, self must not be of length zero.
Sourcepub fn distance(self, rhs: Vec2) -> f32
pub fn distance(self, rhs: Vec2) -> f32
Computes the Euclidean distance between two points in space.
Sourcepub fn distance_squared(self, rhs: Vec2) -> f32
pub fn distance_squared(self, rhs: Vec2) -> f32
Compute the squared euclidean distance between two points in space.
Sourcepub fn div_euclid(self, rhs: Vec2) -> Vec2
pub fn div_euclid(self, rhs: Vec2) -> Vec2
Returns the element-wise quotient of [Euclidean division] of self by rhs.
Sourcepub fn rem_euclid(self, rhs: Vec2) -> Vec2
pub fn rem_euclid(self, rhs: Vec2) -> Vec2
Returns the element-wise remainder of Euclidean division of self by rhs.
Sourcepub fn normalize(self) -> Vec2
pub fn normalize(self) -> Vec2
Returns self normalized to length 1.0.
For valid results, self must be finite and not of length zero, nor very close to zero.
See also Self::try_normalize() and Self::normalize_or_zero().
§Panics
Will panic if the resulting normalized vector is not finite when glam_assert is enabled.
Sourcepub fn try_normalize(self) -> Option<Vec2>
pub fn try_normalize(self) -> Option<Vec2>
Returns self normalized to length 1.0 if possible, else returns None.
In particular, if the input is zero (or very close to zero), or non-finite,
the result of this operation will be None.
See also Self::normalize_or_zero().
Sourcepub fn normalize_or(self, fallback: Vec2) -> Vec2
pub fn normalize_or(self, fallback: Vec2) -> Vec2
Returns self normalized to length 1.0 if possible, else returns a
fallback value.
In particular, if the input is zero (or very close to zero), or non-finite, the result of this operation will be the fallback value.
See also Self::try_normalize().
Sourcepub fn normalize_or_zero(self) -> Vec2
pub fn normalize_or_zero(self) -> Vec2
Returns self normalized to length 1.0 if possible, else returns zero.
In particular, if the input is zero (or very close to zero), or non-finite, the result of this operation will be zero.
See also Self::try_normalize().
Sourcepub fn normalize_and_length(self) -> (Vec2, f32)
pub fn normalize_and_length(self) -> (Vec2, f32)
Returns self normalized to length 1.0 and the length of self.
If self is zero length then (Self::X, 0.0) is returned.
Sourcepub fn is_normalized(self) -> bool
pub fn is_normalized(self) -> bool
Returns whether self is length 1.0 or not.
Uses a precision threshold of approximately 1e-4.
Sourcepub fn project_onto(self, rhs: Vec2) -> Vec2
pub fn project_onto(self, rhs: Vec2) -> Vec2
Returns the vector projection of self onto rhs.
rhs must be of non-zero length.
§Panics
Will panic if rhs is zero length when glam_assert is enabled.
Sourcepub fn reject_from(self, rhs: Vec2) -> Vec2
pub fn reject_from(self, rhs: Vec2) -> Vec2
Returns the vector rejection of self from rhs.
The vector rejection is the vector perpendicular to the projection of self onto
rhs, in rhs words the result of self - self.project_onto(rhs).
rhs must be of non-zero length.
§Panics
Will panic if rhs has a length of zero when glam_assert is enabled.
Sourcepub fn project_onto_normalized(self, rhs: Vec2) -> Vec2
pub fn project_onto_normalized(self, rhs: Vec2) -> Vec2
Returns the vector projection of self onto rhs.
rhs must be normalized.
§Panics
Will panic if rhs is not normalized when glam_assert is enabled.
Sourcepub fn reject_from_normalized(self, rhs: Vec2) -> Vec2
pub fn reject_from_normalized(self, rhs: Vec2) -> Vec2
Returns the vector rejection of self from rhs.
The vector rejection is the vector perpendicular to the projection of self onto
rhs, in rhs words the result of self - self.project_onto(rhs).
rhs must be normalized.
§Panics
Will panic if rhs is not normalized when glam_assert is enabled.
Sourcepub fn round(self) -> Vec2
pub fn round(self) -> Vec2
Returns a vector containing the nearest integer to a number for each element of self.
Round half-way cases away from 0.0.
Sourcepub fn floor(self) -> Vec2
pub fn floor(self) -> Vec2
Returns a vector containing the largest integer less than or equal to a number for each
element of self.
Sourcepub fn ceil(self) -> Vec2
pub fn ceil(self) -> Vec2
Returns a vector containing the smallest integer greater than or equal to a number for
each element of self.
Sourcepub fn trunc(self) -> Vec2
pub fn trunc(self) -> Vec2
Returns a vector containing the integer part each element of self. This means numbers are
always truncated towards zero.
Sourcepub fn step(self, rhs: Vec2) -> Vec2
pub fn step(self, rhs: Vec2) -> Vec2
Returns a vector containing 0.0 if rhs < self and 1.0 otherwise.
Similar to glsl’s step(edge, x), which translates into edge.step(x)
Sourcepub fn saturate(self) -> Vec2
pub fn saturate(self) -> Vec2
Returns a vector containing all elements of self clamped to the range of [0, 1].
Sourcepub fn fract(self) -> Vec2
pub fn fract(self) -> Vec2
Returns a vector containing the fractional part of the vector as self - self.trunc().
Note that this differs from the GLSL implementation of fract which returns
self - self.floor().
Note that this is fast but not precise for large numbers.
Sourcepub fn fract_gl(self) -> Vec2
pub fn fract_gl(self) -> Vec2
Returns a vector containing the fractional part of the vector as self - self.floor().
Note that this differs from the Rust implementation of fract which returns
self - self.trunc().
Note that this is fast but not precise for large numbers.
Sourcepub fn exp(self) -> Vec2
pub fn exp(self) -> Vec2
Returns a vector containing e^self (the exponential function) for each element of
self.
Sourcepub fn ln(self) -> Vec2
pub fn ln(self) -> Vec2
Returns a vector containing the natural logarithm for each element of self.
This returns NaN when the element is negative and negative infinity when the element is zero.
Sourcepub fn log2(self) -> Vec2
pub fn log2(self) -> Vec2
Returns a vector containing the base 2 logarithm for each element of self.
This returns NaN when the element is negative and negative infinity when the element is zero.
Sourcepub fn powf(self, n: f32) -> Vec2
pub fn powf(self, n: f32) -> Vec2
Returns a vector containing each element of self raised to the power of n.
Sourcepub fn sqrt(self) -> Vec2
pub fn sqrt(self) -> Vec2
Returns a vector containing the square root for each element of self.
This returns NaN when the element is negative.
Sourcepub fn sin_cos(self) -> (Vec2, Vec2)
pub fn sin_cos(self) -> (Vec2, Vec2)
Returns a tuple of two vectors containing the sine and cosine for each element of self.
Sourcepub fn recip(self) -> Vec2
pub fn recip(self) -> Vec2
Returns a vector containing the reciprocal 1.0/n of each element of self.
Sourcepub fn lerp(self, rhs: Vec2, s: f32) -> Vec2
pub fn lerp(self, rhs: Vec2, s: f32) -> Vec2
Performs a linear interpolation between self and rhs based on the value s.
When s is 0.0, the result will be equal to self. When s is 1.0, the result
will be equal to rhs. When s is outside of range [0, 1], the result is linearly
extrapolated.
Examples found in repository?
978 fn frame(&mut self, ctx: &mut FrameContext<'_, SoundCheck>) {
979 ctx.set_volume(self.master_volume);
980
981 let player = self.player_prev.lerp(self.player, ctx.alpha());
982 let ear = Vec3::new(player.x, EYE_HEIGHT, player.y);
983 let listener = View::look_at(ear, ear + Vec3::NEG_Z);
984 ctx.set_listener(listener);
985
986 ctx.set_camera(Self::camera(player));
987 ctx.set_skybox(Sky::Room);
988 ctx.set_bloom(0.2);
989 ctx.light(Light::directional(Vec3::new(-0.4, -1.0, -0.5), SUN_COLOR).shadow());
990
991 self.draw_room(ctx);
992 self.draw_sources(ctx);
993 self.draw_listener(ctx, listener);
994 self.draw_merge_markers(ctx);
995 self.draw_ring(ctx);
996
997 self.sustain_cues(ctx);
998 for (index, source) in self.sources.iter().enumerate() {
999 if source.enabled {
1000 ctx.sustain(source.cue().instance(index as u32));
1001 }
1002 }
1003 if self.merge_demo {
1004 ctx.sustain(Sound::Click.at(MERGE_POS_A).gain(MERGE_GAIN));
1005 ctx.sustain(Sound::Click.at(MERGE_POS_B).gain(MERGE_GAIN));
1006 }
1007 self.sustain_ring(ctx);
1008
1009 self.side_panel(ctx);
1010 let (play_once, play_many) = self.one_shot_panel(ctx);
1011
1012 if play_once {
1013 ctx.play(self.one_shot_cue());
1014 }
1015 if play_many {
1016 for _ in 0..32 {
1017 ctx.play(self.one_shot_cue());
1018 }
1019 }
1020 }Sourcepub fn move_towards(self, rhs: Vec2, d: f32) -> Vec2
pub fn move_towards(self, rhs: Vec2, d: f32) -> Vec2
Moves towards rhs based on the value d.
When d is 0.0, the result will be equal to self. When d is equal to
self.distance(rhs), the result will be equal to rhs. Will not go past rhs.
Sourcepub fn midpoint(self, rhs: Vec2) -> Vec2
pub fn midpoint(self, rhs: Vec2) -> Vec2
Calculates the midpoint between self and rhs.
The midpoint is the average of, or halfway point between, two vectors.
a.midpoint(b) should yield the same result as a.lerp(b, 0.5)
while being slightly cheaper to compute.
Sourcepub fn abs_diff_eq(self, rhs: Vec2, max_abs_diff: f32) -> bool
pub fn abs_diff_eq(self, rhs: Vec2, max_abs_diff: f32) -> bool
Returns true if the absolute difference of all elements between self and rhs is
less than or equal to max_abs_diff.
This can be used to compare if two vectors contain similar elements. It works best when
comparing with a known value. The max_abs_diff that should be used used depends on
the values being compared against.
For more see comparing floating point numbers.
Sourcepub fn clamp_length(self, min: f32, max: f32) -> Vec2
pub fn clamp_length(self, min: f32, max: f32) -> Vec2
Returns a vector with a length no less than min and no more than max.
§Panics
Will panic if min is greater than max, or if either min or max is negative, when glam_assert is enabled.
Sourcepub fn clamp_length_max(self, max: f32) -> Vec2
pub fn clamp_length_max(self, max: f32) -> Vec2
Returns a vector with a length no more than max.
§Panics
Will panic if max is negative when glam_assert is enabled.
Examples found in repository?
692 fn advance(&mut self, ctx: &mut TickContext<'_, Scene>) -> f32 {
693 let control = ctx.axis2(Move::Walk).clamp_length_max(1.0);
694 let turn = Quat::from_rotation_y(self.camera_yaw);
695 let heading = turn * Vec3::X * control.x + turn * Vec3::NEG_Z * control.y;
696 let dt = ctx.dt().as_secs_f32();
697 if let Some(direction) = heading.try_normalize() {
698 let wanted = direction.x.atan2(direction.z);
699 let turn = (wanted - self.elf_yaw + core::f32::consts::PI).rem_euclid(TAU)
700 - core::f32::consts::PI;
701 self.elf_yaw += turn.clamp(-TURN_RATE * dt, TURN_RATE * dt);
702 }
703 let cap = if ctx.down(Button::Run) { 1.0 } else { WALK_CAP };
704 self.elf_pos += heading * cap * ELF_SPEED * dt;
705 heading.length() * cap
706 }More examples
486fn axis2_dot(ui: &mut egui::Ui, value: Vec2) {
487 let square_side = 36.0;
488 let radius = 4.0;
489 let (column, _response) = ui.allocate_exact_size(
490 egui::vec2(VALUE_COLUMN_WIDTH, square_side),
491 egui::Sense::hover(),
492 );
493 let rect = egui::Rect::from_center_size(column.center(), egui::Vec2::splat(square_side));
494 let painter = ui.painter();
495 painter.rect_filled(rect, 2.0, egui::Color32::from_gray(35));
496
497 let shown = value.clamp_length_max(1.0);
498 let reach = rect.width() * 0.5 - READING_MARGIN - radius;
499 let point = rect.center() + egui::vec2(shown.x, -shown.y) * reach;
500 painter.circle_filled(point, radius, highlight_color(value != Vec2::ZERO));
501 painter.rect_stroke(
502 rect,
503 2.0,
504 egui::Stroke::new(1.0, egui::Color32::from_gray(120)),
505 egui::StrokeKind::Inside,
506 );
507
508 ui.label(format!("{:.2}, {:.2}", value.x, value.y));
509}Sourcepub fn clamp_length_min(self, min: f32) -> Vec2
pub fn clamp_length_min(self, min: f32) -> Vec2
Returns a vector with a length no less than min.
§Panics
Will panic if min is negative when glam_assert is enabled.
Sourcepub fn mul_add(self, a: Vec2, b: Vec2) -> Vec2
pub fn mul_add(self, a: Vec2, b: Vec2) -> Vec2
Fused multiply-add. Computes (self * a) + b element-wise with only one rounding
error, yielding a more accurate result than an unfused multiply-add.
Using mul_add may be more performant than an unfused multiply-add if the target
architecture has a dedicated fma CPU instruction. However, this is not always true,
and will be heavily dependant on designing algorithms with specific target hardware in
mind.
Sourcepub fn reflect(self, normal: Vec2) -> Vec2
pub fn reflect(self, normal: Vec2) -> Vec2
Returns the reflection vector for a given incident vector self and surface normal
normal.
normal must be normalized.
§Panics
Will panic if normal is not normalized when glam_assert is enabled.
Sourcepub fn refract(self, normal: Vec2, eta: f32) -> Vec2
pub fn refract(self, normal: Vec2, eta: f32) -> Vec2
Returns the refraction direction for a given incident vector self, surface normal
normal and ratio of indices of refraction, eta. When total internal reflection occurs,
a zero vector will be returned.
self and normal must be normalized.
§Panics
Will panic if self or normal is not normalized when glam_assert is enabled.
Sourcepub fn from_angle(angle: f32) -> Vec2
pub fn from_angle(angle: f32) -> Vec2
Sourcepub fn to_angle(self) -> f32
pub fn to_angle(self) -> f32
Returns the angle (in radians) of this vector in the range [-π, +π].
The input does not need to be a unit vector however it must be non-zero.
Sourcepub fn angle_to(self, rhs: Vec2) -> f32
pub fn angle_to(self, rhs: Vec2) -> f32
Returns the angle of rotation (in radians) from self to rhs in the range [-π, +π].
The inputs do not need to be unit vectors however they must be non-zero.
The returned angle can be used with rotate_angle(), e.g.
self.rotate_angle(self.angle_to(rhs)) will be equal to rhs.
§Panics
Will panic if self or rhs has zero length when glam_assert is enabled.
Sourcepub fn perp_dot(self, rhs: Vec2) -> f32
pub fn perp_dot(self, rhs: Vec2) -> f32
The perpendicular dot product of self and rhs.
Also known as the wedge product, 2D cross product, and determinant.
Sourcepub fn rotate(self, rhs: Vec2) -> Vec2
pub fn rotate(self, rhs: Vec2) -> Vec2
Returns rhs rotated by the angle of self. If self is normalized,
then this just rotation. This is what you usually want. Otherwise,
it will be like a rotation with a multiplication by self’s length.
This can be used in conjunction with the from_angle() method, e.g.
Vec2::from_angle(PI).rotate(Vec2::Y) will create the vector [-1, 0]
and rotate Vec2::Y around it returning -Vec2::Y.
Sourcepub fn rotate_angle(self, angle: f32) -> Vec2
pub fn rotate_angle(self, angle: f32) -> Vec2
Rotates self by angle (in radians), equivalent to
self.rotate(Vec2::from_angle(angle)).
Sourcepub fn rotate_towards(self, rhs: Vec2, max_angle: f32) -> Vec2
pub fn rotate_towards(self, rhs: Vec2, max_angle: f32) -> Vec2
Rotates towards rhs up to max_angle (in radians).
When max_angle is 0.0, the result will be equal to self. When max_angle is equal to
self.angle_between(rhs), the result will be parallel to rhs. If max_angle is negative,
rotates towards the exact opposite of rhs. Will not go past the target.
Sourcepub fn as_i16vec2(self) -> I16Vec2
pub fn as_i16vec2(self) -> I16Vec2
Casts all elements of self to i16.
Sourcepub fn as_u16vec2(self) -> U16Vec2
pub fn as_u16vec2(self) -> U16Vec2
Casts all elements of self to u16.
Sourcepub fn as_i64vec2(self) -> I64Vec2
pub fn as_i64vec2(self) -> I64Vec2
Casts all elements of self to i64.
Sourcepub fn as_u64vec2(self) -> U64Vec2
pub fn as_u64vec2(self) -> U64Vec2
Casts all elements of self to u64.
Sourcepub fn as_isizevec2(self) -> ISizeVec2
pub fn as_isizevec2(self) -> ISizeVec2
Casts all elements of self to isize.
Sourcepub fn as_usizevec2(self) -> USizeVec2
pub fn as_usizevec2(self) -> USizeVec2
Casts all elements of self to usize.
Trait Implementations§
Source§impl AddAssign for Vec2
impl AddAssign for Vec2
Source§fn add_assign(&mut self, rhs: Vec2)
fn add_assign(&mut self, rhs: Vec2)
+= operation. Read moreSource§impl AddAssign<&Vec2> for Vec2
impl AddAssign<&Vec2> for Vec2
Source§fn add_assign(&mut self, rhs: &Vec2)
fn add_assign(&mut self, rhs: &Vec2)
+= operation. Read moreSource§impl AddAssign<&f32> for Vec2
impl AddAssign<&f32> for Vec2
Source§fn add_assign(&mut self, rhs: &f32)
fn add_assign(&mut self, rhs: &f32)
+= operation. Read moreSource§impl AddAssign<f32> for Vec2
impl AddAssign<f32> for Vec2
Source§fn add_assign(&mut self, rhs: f32)
fn add_assign(&mut self, rhs: f32)
+= operation. Read moreimpl Copy for Vec2
Source§impl DivAssign for Vec2
impl DivAssign for Vec2
Source§fn div_assign(&mut self, rhs: Vec2)
fn div_assign(&mut self, rhs: Vec2)
/= operation. Read moreSource§impl DivAssign<&Vec2> for Vec2
impl DivAssign<&Vec2> for Vec2
Source§fn div_assign(&mut self, rhs: &Vec2)
fn div_assign(&mut self, rhs: &Vec2)
/= operation. Read moreSource§impl DivAssign<&f32> for Vec2
impl DivAssign<&f32> for Vec2
Source§fn div_assign(&mut self, rhs: &f32)
fn div_assign(&mut self, rhs: &f32)
/= operation. Read moreSource§impl DivAssign<f32> for Vec2
impl DivAssign<f32> for Vec2
Source§fn div_assign(&mut self, rhs: f32)
fn div_assign(&mut self, rhs: f32)
/= operation. Read moreSource§impl MulAssign for Vec2
impl MulAssign for Vec2
Source§fn mul_assign(&mut self, rhs: Vec2)
fn mul_assign(&mut self, rhs: Vec2)
*= operation. Read moreSource§impl MulAssign<&Vec2> for Vec2
impl MulAssign<&Vec2> for Vec2
Source§fn mul_assign(&mut self, rhs: &Vec2)
fn mul_assign(&mut self, rhs: &Vec2)
*= operation. Read moreSource§impl MulAssign<&f32> for Vec2
impl MulAssign<&f32> for Vec2
Source§fn mul_assign(&mut self, rhs: &f32)
fn mul_assign(&mut self, rhs: &f32)
*= operation. Read moreSource§impl MulAssign<f32> for Vec2
impl MulAssign<f32> for Vec2
Source§fn mul_assign(&mut self, rhs: f32)
fn mul_assign(&mut self, rhs: f32)
*= operation. Read moreimpl Pod for Vec2
Source§impl RemAssign for Vec2
impl RemAssign for Vec2
Source§fn rem_assign(&mut self, rhs: Vec2)
fn rem_assign(&mut self, rhs: Vec2)
%= operation. Read moreSource§impl RemAssign<&Vec2> for Vec2
impl RemAssign<&Vec2> for Vec2
Source§fn rem_assign(&mut self, rhs: &Vec2)
fn rem_assign(&mut self, rhs: &Vec2)
%= operation. Read moreSource§impl RemAssign<&f32> for Vec2
impl RemAssign<&f32> for Vec2
Source§fn rem_assign(&mut self, rhs: &f32)
fn rem_assign(&mut self, rhs: &f32)
%= operation. Read moreSource§impl RemAssign<f32> for Vec2
impl RemAssign<f32> for Vec2
Source§fn rem_assign(&mut self, rhs: f32)
fn rem_assign(&mut self, rhs: f32)
%= operation. Read moreimpl StructuralPartialEq for Vec2
Source§impl SubAssign for Vec2
impl SubAssign for Vec2
Source§fn sub_assign(&mut self, rhs: Vec2)
fn sub_assign(&mut self, rhs: Vec2)
-= operation. Read moreSource§impl SubAssign<&Vec2> for Vec2
impl SubAssign<&Vec2> for Vec2
Source§fn sub_assign(&mut self, rhs: &Vec2)
fn sub_assign(&mut self, rhs: &Vec2)
-= operation. Read moreSource§impl SubAssign<&f32> for Vec2
impl SubAssign<&f32> for Vec2
Source§fn sub_assign(&mut self, rhs: &f32)
fn sub_assign(&mut self, rhs: &f32)
-= operation. Read moreSource§impl SubAssign<f32> for Vec2
impl SubAssign<f32> for Vec2
Source§fn sub_assign(&mut self, rhs: f32)
fn sub_assign(&mut self, rhs: f32)
-= operation. Read moreSource§impl Vec2Swizzles for Vec2
impl Vec2Swizzles for Vec2
type Vec3 = Vec3
type Vec4 = Vec4
fn xx(self) -> Vec2
fn yx(self) -> Vec2
fn yy(self) -> Vec2
fn xxx(self) -> Vec3
fn xxy(self) -> Vec3
fn xyx(self) -> Vec3
fn xyy(self) -> Vec3
fn yxx(self) -> Vec3
fn yxy(self) -> Vec3
fn yyx(self) -> Vec3
fn yyy(self) -> Vec3
fn xxxx(self) -> Vec4
fn xxxy(self) -> Vec4
fn xxyx(self) -> Vec4
fn xxyy(self) -> Vec4
fn xyxx(self) -> Vec4
fn xyxy(self) -> Vec4
fn xyyx(self) -> Vec4
fn xyyy(self) -> Vec4
fn yxxx(self) -> Vec4
fn yxxy(self) -> Vec4
fn yxyx(self) -> Vec4
fn yxyy(self) -> Vec4
fn yyxx(self) -> Vec4
fn yyxy(self) -> Vec4
fn yyyx(self) -> Vec4
fn yyyy(self) -> Vec4
fn xy(self) -> Self
Auto Trait Implementations§
impl Freeze for Vec2
impl RefUnwindSafe for Vec2
impl Send for Vec2
impl Sync for Vec2
impl Unpin for Vec2
impl UnsafeUnpin for Vec2
impl UnwindSafe for Vec2
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
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
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>
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>
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)
&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)
&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> ⓘ
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> ⓘ
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